diff --git a/fixtures/db.json b/fixtures/db.json index 4e736d11..2e78d36a 100644 --- a/fixtures/db.json +++ b/fixtures/db.json @@ -1,11 +1,25 @@ { "posts": [ - { "id": "1", "title": "a title" }, - { "id": "2", "title": "another title" } + { + "id": "1", + "title": "a title" + }, + { + "id": "2", + "title": "another title" + } ], "comments": [ - { "id": "1", "text": "a comment about post 1", "postId": "1" }, - { "id": "2", "text": "another comment about post 1", "postId": "1" } + { + "id": "1", + "text": "a comment about post 1", + "postId": "1" + }, + { + "id": "2", + "text": "another comment about post 1", + "postId": "1" + } ], "profile": { "name": "typicode" diff --git a/package-lock.json b/package-lock.json index 60d0c463..cb0a0a98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "json5": "^2.2.3", "lowdb": "^7.0.1", "milliparsec": "^4.0.0", + "rxjs": "^7.8.1", "sirv": "^2.0.4", "sort-on": "^6.1.0" }, @@ -1412,12 +1413,13 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -2056,10 +2058,11 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2388,6 +2391,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -2589,12 +2593,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -3171,7 +3176,7 @@ "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" } @@ -3552,6 +3557,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -3652,8 +3658,7 @@ "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/tsx": { "version": "4.19.1", diff --git a/package.json b/package.json index a0cfb299..14a7c237 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "json5": "^2.2.3", "lowdb": "^7.0.1", "milliparsec": "^4.0.0", + "rxjs": "^7.8.1", "sirv": "^2.0.4", "sort-on": "^6.1.0" } diff --git a/src/app.ts b/src/app.ts index b8c5e79e..f9428c89 100644 --- a/src/app.ts +++ b/src/app.ts @@ -8,6 +8,8 @@ import { Low } from 'lowdb' import { json } from 'milliparsec' import sirv from 'sirv' +import { Subject } from 'rxjs' + import { Data, isItem, Service } from './service.js' const __dirname = dirname(fileURLToPath(import.meta.url)) @@ -30,6 +32,8 @@ export function createApp(db: Low, options: AppOptions = {}) { // Create app const app = new App() + const event$ = new Subject(); + // Static files app.use(sirv('public', { dev: !isProduction })) options.static @@ -55,6 +59,20 @@ export function createApp(db: Low, options: AppOptions = {}) { res.send(eta.render('index.html', { data: db.data })), ) + app.get('/events', (_req, res) => { + res.header('Content-Type', 'text/event-stream'); + + event$.subscribe({ + next: (data) => { + res.write(`data: ${JSON.stringify(data)}\n\n`); + res.flushHeaders(); + }, + complete: () => { + res.end(); + }, + }); + }) + app.get('/:name', (req, res, next) => { const { name = '' } = req.params const query = Object.fromEntries( @@ -86,6 +104,12 @@ export function createApp(db: Low, options: AppOptions = {}) { if (isItem(req.body)) { res.locals['data'] = await service.create(name, req.body) } + event$.next( + { + type: "post", + params: req.params + } + ); next?.() }) @@ -94,6 +118,12 @@ export function createApp(db: Low, options: AppOptions = {}) { if (isItem(req.body)) { res.locals['data'] = await service.update(name, req.body) } + event$.next( + { + type: "put", + params: req.params + } + ); next?.() }) @@ -102,6 +132,12 @@ export function createApp(db: Low, options: AppOptions = {}) { if (isItem(req.body)) { res.locals['data'] = await service.updateById(name, id, req.body) } + event$.next( + { + type: "put", + params: req.params + } + ); next?.() }) @@ -110,6 +146,12 @@ export function createApp(db: Low, options: AppOptions = {}) { if (isItem(req.body)) { res.locals['data'] = await service.patch(name, req.body) } + event$.next( + { + type: "patch", + params: req.params + } + ); next?.() }) @@ -118,6 +160,12 @@ export function createApp(db: Low, options: AppOptions = {}) { if (isItem(req.body)) { res.locals['data'] = await service.patchById(name, id, req.body) } + event$.next( + { + type: "patch", + params: req.params + } + ); next?.() }) @@ -128,6 +176,12 @@ export function createApp(db: Low, options: AppOptions = {}) { id, req.query['_dependent'], ) + event$.next( + { + type: "delete", + params: req.params + } + ); next?.() })