json-server 1.0.0-alpha.1 → 1.0.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,21 +12,29 @@ npm install json-server
12
12
 
13
13
  Create a `db.json` file or run `json-server db.json` to create one with some default resources
14
14
 
15
+ > [!TIP]
16
+ > You can also use [json5](https://json5.org/) format by creating a `db.json5` instead
17
+
15
18
  ```json
16
19
  {
17
20
  "posts": [
18
- { "id": "1", "title": "string" },
19
- { "id": "2", "title": "some post" }
21
+ { "id": "1", "title": "a title" },
22
+ { "id": "2", "title": "another title" }
20
23
  ],
21
24
  "comments": [
22
- { "id": "1", "text": "some text", "postId": "1" },
23
- { "id": "2", "text": "some text", "postId": "1" }
25
+ { "id": "1", "text": "a comment about post 1", "postId": "1" },
26
+ { "id": "2", "text": "another comment about post 1", "postId": "1" }
24
27
  ]
25
28
  }
26
29
  ```
27
30
 
28
31
  ```shell
29
32
  json-server db.json
33
+ curl -H "Accept: application/json" -X GET http://localhost:3000/posts/1
34
+ {
35
+ "id": "1",
36
+ "title": "a title"
37
+ }
30
38
  ```
31
39
 
32
40
  Run `json-server --help` for a list of options
@@ -0,0 +1,9 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ import { PathLike } from 'fs';
3
+ import { Adapter } from 'lowdb';
4
+ export declare class JSON5File<T> implements Adapter<T> {
5
+ #private;
6
+ constructor(filename: PathLike);
7
+ read(): Promise<T | null>;
8
+ write(obj: T): Promise<void>;
9
+ }
@@ -0,0 +1,20 @@
1
+ import JSON5 from 'json5';
2
+ import { TextFile } from 'lowdb/node';
3
+ export class JSON5File {
4
+ #adapter;
5
+ constructor(filename) {
6
+ this.#adapter = new TextFile(filename);
7
+ }
8
+ async read() {
9
+ const data = await this.#adapter.read();
10
+ if (data === null) {
11
+ return null;
12
+ }
13
+ else {
14
+ return JSON5.parse(data);
15
+ }
16
+ }
17
+ write(obj) {
18
+ return this.#adapter.write(JSON5.stringify(obj, null, 2));
19
+ }
20
+ }
@@ -0,0 +1,11 @@
1
+ import { Adapter } from 'lowdb';
2
+ export declare class Observer<T> {
3
+ #private;
4
+ onReadStart: () => void;
5
+ onReadEnd: () => void;
6
+ onWriteStart: () => void;
7
+ onWriteEnd: () => void;
8
+ constructor(adapter: Adapter<T>);
9
+ read(): Promise<T | null>;
10
+ write(arg: T): Promise<void>;
11
+ }
@@ -0,0 +1,30 @@
1
+ // Lowdb adapter to observe read/write events
2
+ export class Observer {
3
+ #adapter;
4
+ onReadStart = function () {
5
+ return;
6
+ };
7
+ onReadEnd = function () {
8
+ return;
9
+ };
10
+ onWriteStart = function () {
11
+ return;
12
+ };
13
+ onWriteEnd = function () {
14
+ return;
15
+ };
16
+ constructor(adapter) {
17
+ this.#adapter = adapter;
18
+ }
19
+ async read() {
20
+ this.onReadStart();
21
+ const data = await this.#adapter.read();
22
+ this.onReadEnd();
23
+ return data;
24
+ }
25
+ async write(arg) {
26
+ this.onWriteStart();
27
+ await this.#adapter.write(arg);
28
+ this.onWriteEnd();
29
+ }
30
+ }
package/lib/app.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { App } from '@tinyhttp/app';
2
+ import { Low } from 'lowdb';
3
+ import { Data } from './service.js';
4
+ export type AppOptions = {
5
+ logger?: boolean;
6
+ static?: string[];
7
+ };
8
+ export declare function createApp(db: Low<Data>, options?: AppOptions): App<import("@tinyhttp/app").Request, import("@tinyhttp/app").Response<unknown>>;
package/lib/app.js ADDED
@@ -0,0 +1,75 @@
1
+ import { dirname, isAbsolute, join } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { App } from '@tinyhttp/app';
4
+ import { Eta } from 'eta';
5
+ import { json } from 'milliparsec';
6
+ import sirv from 'sirv';
7
+ import { isItem, Service } from './service.js';
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const isProduction = process.env['NODE_ENV'] === 'production';
10
+ const eta = new Eta({
11
+ views: join(__dirname, '../views'),
12
+ cache: isProduction,
13
+ });
14
+ export function createApp(db, options = {}) {
15
+ // Create service
16
+ const service = new Service(db);
17
+ // Create app
18
+ const app = new App();
19
+ // Body parser
20
+ app.use(json());
21
+ // Static files
22
+ app.use(sirv(join(__dirname, '../public')));
23
+ options.static
24
+ ?.map((path) => (isAbsolute(path) ? path : join(process.cwd(), path)))
25
+ .forEach((dir) => app.use(sirv(dir, { dev: !isProduction })));
26
+ app.get('/', (_req, res) => res.send(eta.render('index.html', { data: db.data })));
27
+ app.get('/:name', (req, res, next) => {
28
+ const { name = '' } = req.params;
29
+ res.locals['data'] = service.find(name, req.query);
30
+ next();
31
+ });
32
+ app.get('/:name/:id', (req, res, next) => {
33
+ const { name = '', id = '' } = req.params;
34
+ res.locals['data'] = service.findById(name, id, req.query);
35
+ next();
36
+ });
37
+ app.post('/:name', async (req, res, next) => {
38
+ const { name = '' } = req.params;
39
+ if (isItem(req.body)) {
40
+ res.locals['data'] = await service.create(name, req.body);
41
+ }
42
+ next();
43
+ });
44
+ app.put('/:name/:id', async (req, res, next) => {
45
+ const { name = '', id = '' } = req.params;
46
+ if (isItem(req.body)) {
47
+ res.locals['data'] = await service.update(name, id, req.body);
48
+ }
49
+ next();
50
+ });
51
+ app.patch('/:name/:id', async (req, res, next) => {
52
+ const { name = '', id = '' } = req.params;
53
+ if (isItem(req.body)) {
54
+ res.locals['data'] = await service.patch(name, id, req.body);
55
+ }
56
+ next();
57
+ });
58
+ app.delete('/:name/:id', async (req, res, next) => {
59
+ const { name = '', id = '' } = req.params;
60
+ res.locals['data'] = await service.destroy(name, id);
61
+ next();
62
+ });
63
+ app.use('/:name', (req, res) => {
64
+ const { data } = res.locals;
65
+ if (data === undefined) {
66
+ res.sendStatus(404);
67
+ }
68
+ else {
69
+ if (req.method === 'POST')
70
+ res.status(201);
71
+ res.json(data);
72
+ }
73
+ });
74
+ return app;
75
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,73 @@
1
+ import assert from 'node:assert/strict';
2
+ import { writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import test from 'node:test';
5
+ import getPort from 'get-port';
6
+ import { Low, Memory } from 'lowdb';
7
+ import { temporaryDirectory } from 'tempy';
8
+ import { createApp } from './app.js';
9
+ const port = await getPort();
10
+ // Create custom static dir with an html file
11
+ const tmpDir = temporaryDirectory();
12
+ const file = 'file.html';
13
+ writeFileSync(join(tmpDir, file), 'utf-8');
14
+ // Create app
15
+ const db = new Low(new Memory(), {});
16
+ db.data = {
17
+ posts: [{ id: '1', title: 'foo' }],
18
+ comments: [{ id: '1', postId: '1' }],
19
+ };
20
+ const app = createApp(db, { static: [tmpDir] });
21
+ await new Promise((resolve, reject) => {
22
+ try {
23
+ const server = app.listen(port, () => resolve());
24
+ test.after(() => server.close());
25
+ }
26
+ catch (err) {
27
+ reject(err);
28
+ }
29
+ });
30
+ await test('createApp', async () => {
31
+ // URLs
32
+ const POSTS = '/posts';
33
+ const POST_1 = '/posts/1';
34
+ const POST_NOT_FOUND = '/posts/-1';
35
+ const COMMENTS = '/comments';
36
+ const POST_COMMENTS = '/comments?postId=1';
37
+ const NOT_FOUND = '/not-found';
38
+ const arr = [
39
+ // Static
40
+ { method: 'GET', url: '/', statusCode: 200 },
41
+ { method: 'GET', url: '/output.css', statusCode: 200 },
42
+ { method: 'GET', url: `/${file}`, statusCode: 200 },
43
+ // API
44
+ { method: 'GET', url: POSTS, statusCode: 200 },
45
+ { method: 'GET', url: POST_1, statusCode: 200 },
46
+ { method: 'GET', url: POST_NOT_FOUND, statusCode: 404 },
47
+ { method: 'GET', url: COMMENTS, statusCode: 200 },
48
+ { method: 'GET', url: POST_COMMENTS, statusCode: 200 },
49
+ { method: 'GET', url: NOT_FOUND, statusCode: 404 },
50
+ { method: 'POST', url: POSTS, statusCode: 201 },
51
+ { method: 'POST', url: POST_1, statusCode: 404 },
52
+ { method: 'POST', url: POST_NOT_FOUND, statusCode: 404 },
53
+ { method: 'POST', url: NOT_FOUND, statusCode: 404 },
54
+ { method: 'PUT', url: POSTS, statusCode: 404 },
55
+ { method: 'PUT', url: POST_1, statusCode: 200 },
56
+ { method: 'PUT', url: POST_NOT_FOUND, statusCode: 404 },
57
+ { method: 'PUT', url: NOT_FOUND, statusCode: 404 },
58
+ { method: 'PATCH', url: POSTS, statusCode: 404 },
59
+ { method: 'PATCH', url: POST_1, statusCode: 200 },
60
+ { method: 'PATCH', url: POST_NOT_FOUND, statusCode: 404 },
61
+ { method: 'PATCH', url: NOT_FOUND, statusCode: 404 },
62
+ { method: 'DELETE', url: POSTS, statusCode: 404 },
63
+ { method: 'DELETE', url: POST_1, statusCode: 200 },
64
+ { method: 'DELETE', url: POST_NOT_FOUND, statusCode: 404 },
65
+ { method: 'DELETE', url: NOT_FOUND, statusCode: 404 },
66
+ ];
67
+ for (const tc of arr) {
68
+ const response = await fetch(`http://localhost:${port}${tc.url}`, {
69
+ method: tc.method,
70
+ });
71
+ assert.equal(response.status, tc.statusCode, `${response.status} !== ${tc.statusCode} ${tc.method} ${tc.url} failed`);
72
+ }
73
+ });
package/lib/bin.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/lib/bin.js ADDED
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from 'node:fs';
3
+ import { extname, join } from 'node:path';
4
+ import { parseArgs } from 'node:util';
5
+ import { watch } from 'chokidar';
6
+ import { Low } from 'lowdb';
7
+ import { JSONFile } from 'lowdb/node';
8
+ import { createApp } from './app.js';
9
+ import { JSON5File } from './JSON5File.js';
10
+ import { Observer } from './Observer.js';
11
+ // Parse args
12
+ const { values, positionals } = parseArgs({
13
+ args: process.argv.slice(2),
14
+ options: {
15
+ port: {
16
+ type: 'string',
17
+ short: 'p',
18
+ },
19
+ host: {
20
+ type: 'string',
21
+ short: 'h',
22
+ },
23
+ static: {
24
+ type: 'string',
25
+ short: 's',
26
+ multiple: true,
27
+ },
28
+ help: {
29
+ type: 'boolean',
30
+ },
31
+ version: {
32
+ type: 'boolean',
33
+ },
34
+ },
35
+ allowPositionals: true,
36
+ });
37
+ if (values.help || positionals.length === 0) {
38
+ console.log(`Usage: json-server [options] [file]
39
+ Options:
40
+ -p, --port <port> Port (default: 3000)
41
+ -h, --host <host> Host (default: localhost)
42
+ -s, --static <dir> Static files directory (multiple allowed)
43
+ --help Show this message
44
+ `);
45
+ }
46
+ if (values.version) {
47
+ const pkg = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf8'));
48
+ console.log(pkg.version);
49
+ process.exit();
50
+ }
51
+ // App args and options
52
+ const file = positionals[0] ?? 'db.json';
53
+ const port = parseInt(values.port ?? process.env['PORT'] ?? '3000');
54
+ const host = values.host ?? process.env['HOST'] ?? 'localhost';
55
+ // Set up database
56
+ let adapter;
57
+ if (extname(file) === '.json5') {
58
+ adapter = new JSON5File(file);
59
+ }
60
+ else {
61
+ adapter = new JSONFile(file);
62
+ }
63
+ const observer = new Observer(adapter);
64
+ const db = new Low(observer, {});
65
+ await db.read();
66
+ // Create app
67
+ const app = createApp(db, { logger: false, static: values.static });
68
+ function routes(db) {
69
+ return Object.keys(db.data).map((key) => `http://${host}:${port}/${key}`);
70
+ }
71
+ // Watch file for changes
72
+ if (process.env['NODE_ENV'] !== 'production') {
73
+ let writing = false; // true if the file is being written to by the app
74
+ observer.onWriteStart = () => {
75
+ writing = true;
76
+ };
77
+ observer.onWriteEnd = () => {
78
+ writing = false;
79
+ };
80
+ observer.onReadStart = () => console.log(`reloading ${file}...`);
81
+ observer.onReadEnd = () => console.log('reloaded');
82
+ watch(file).on('change', () => {
83
+ // Do no reload if the file is being written to by the app
84
+ if (!writing) {
85
+ db.read()
86
+ .then(() => routes(db))
87
+ .catch((e) => {
88
+ if (e instanceof SyntaxError) {
89
+ return console.log(e.message);
90
+ }
91
+ console.log(e);
92
+ });
93
+ }
94
+ });
95
+ }
96
+ app.listen(port, () => {
97
+ console.log(`Started on :${port}`);
98
+ console.log(routes(db));
99
+ });
@@ -0,0 +1,39 @@
1
+ import { Low } from 'lowdb';
2
+ export type Headers = Record<string, string>;
3
+ export type Item = Record<string, unknown>;
4
+ export type Data = Record<string, Item[]>;
5
+ export declare function isItem(obj: unknown): obj is Item;
6
+ export declare function isData(obj: unknown): obj is Record<string, Item[]>;
7
+ export type PaginatedItems = {
8
+ first: number;
9
+ prev: number | null;
10
+ next: number | null;
11
+ last: number;
12
+ pages: number;
13
+ items: number;
14
+ data: Item[];
15
+ };
16
+ export declare class Service {
17
+ #private;
18
+ constructor(db: Low<Data>);
19
+ list(): string[];
20
+ has(name: string): boolean;
21
+ findById(name: string, id: string, query: {
22
+ _include?: string[];
23
+ }): Item | undefined;
24
+ find(name: string, query?: {
25
+ [key: string]: unknown;
26
+ } & {
27
+ _include?: string[];
28
+ _sort?: string;
29
+ _start?: number;
30
+ _end?: number;
31
+ _limit?: number;
32
+ _page?: number;
33
+ _per_page?: number;
34
+ }): Item[] | PaginatedItems | undefined;
35
+ create(name: string, data?: Omit<Item, 'id'>): Promise<Item | undefined>;
36
+ update(name: string, id: string, body?: Omit<Item, 'id'>): Promise<Item | undefined>;
37
+ patch(name: string, id: string, body?: Omit<Item, 'id'>): Promise<Item | undefined>;
38
+ destroy(name: string, id: string, dependents?: string[]): Promise<Item | undefined>;
39
+ }
package/lib/service.js ADDED
@@ -0,0 +1,270 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { getProperty } from 'dot-prop';
3
+ import inflection from 'inflection';
4
+ import sortOn from 'sort-on';
5
+ export function isItem(obj) {
6
+ return typeof obj === 'object' && obj !== null;
7
+ }
8
+ export function isData(obj) {
9
+ if (typeof obj !== 'object' || obj === null) {
10
+ return false;
11
+ }
12
+ const data = obj;
13
+ return Object.values(data).every((value) => Array.isArray(value) && value.every(isItem));
14
+ }
15
+ var Operator;
16
+ (function (Operator) {
17
+ Operator["lt"] = "lt";
18
+ Operator["lte"] = "lte";
19
+ Operator["gt"] = "gt";
20
+ Operator["gte"] = "gte";
21
+ Operator["ne"] = "ne";
22
+ Operator["default"] = "";
23
+ })(Operator || (Operator = {}));
24
+ function isOperator(value) {
25
+ return Object.values(Operator).includes(value);
26
+ }
27
+ function include(db, name, item, related) {
28
+ if (inflection.singularize(related) === related) {
29
+ const relatedData = db.data[inflection.pluralize(related)];
30
+ if (!relatedData) {
31
+ return item;
32
+ }
33
+ const foreignKey = `${related}Id`;
34
+ const relatedItem = relatedData.find((relatedItem) => {
35
+ return relatedItem['id'] === item[foreignKey];
36
+ });
37
+ return { ...item, [related]: relatedItem };
38
+ }
39
+ const relatedData = db.data[related];
40
+ if (!relatedData) {
41
+ return item;
42
+ }
43
+ const foreignKey = `${inflection.singularize(name)}Id`;
44
+ const relatedItems = relatedData.filter((relatedItem) => relatedItem[foreignKey] === item['id']);
45
+ return { ...item, [related]: relatedItems };
46
+ }
47
+ function nullifyForeignKey(db, name, id) {
48
+ const foreignKey = `${inflection.singularize(name)}Id`;
49
+ Object.entries(db.data).forEach(([key, items]) => {
50
+ // Skip
51
+ if (key === name)
52
+ return;
53
+ // Nullify
54
+ items.forEach((item) => {
55
+ if (item[foreignKey] === id) {
56
+ item[foreignKey] = null;
57
+ }
58
+ });
59
+ });
60
+ }
61
+ function deleteDependents(db, name, dependents) {
62
+ const foreignKey = `${inflection.singularize(name)}Id`;
63
+ Object.entries(db.data).forEach(([key, items]) => {
64
+ // Skip
65
+ if (key === name || !dependents.includes(key))
66
+ return;
67
+ // Delete if foreign key is null
68
+ db.data[key] = items.filter((item) => item[foreignKey] !== null);
69
+ });
70
+ }
71
+ export class Service {
72
+ #db;
73
+ constructor(db) {
74
+ this.#db = db;
75
+ }
76
+ #get(name) {
77
+ return this.#db.data[name];
78
+ }
79
+ list() {
80
+ return Object.keys(this.#db?.data || {});
81
+ }
82
+ has(name) {
83
+ return Object.prototype.hasOwnProperty.call(this.#db?.data, name);
84
+ }
85
+ findById(name, id, query) {
86
+ let item = this.#get(name)?.find((item) => item['id'] === id);
87
+ query._include?.forEach((related) => {
88
+ if (item !== undefined)
89
+ item = include(this.#db, name, item, related);
90
+ });
91
+ return item;
92
+ }
93
+ find(name, query = {}) {
94
+ let items = this.#get(name);
95
+ // Not found
96
+ if (items === undefined)
97
+ return;
98
+ // Include
99
+ query._include?.forEach((related) => {
100
+ if (items !== undefined)
101
+ items = items.map((item) => include(this.#db, name, item, related));
102
+ });
103
+ // Return list if no query params
104
+ if (Object.keys(query).length === 0) {
105
+ return items;
106
+ }
107
+ // Convert query params to conditions
108
+ const conds = {};
109
+ for (const [key, value] of Object.entries(query)) {
110
+ if (value === undefined || typeof value !== 'string') {
111
+ continue;
112
+ }
113
+ const re = /_(lt|lte|gt|gte|ne)$/;
114
+ const reArr = re.exec(key);
115
+ const op = reArr?.at(1);
116
+ if (op && isOperator(op)) {
117
+ const field = key.replace(re, '');
118
+ conds[field] = [op, value];
119
+ continue;
120
+ }
121
+ if (['_sort', '_start', '_end', '_limit', '_page', '_per_page'].includes(key))
122
+ continue;
123
+ conds[key] = [Operator.default, value];
124
+ }
125
+ // Loop through conditions and filter items
126
+ const res = items.filter((item) => {
127
+ for (const [key, [op, paramValue]] of Object.entries(conds)) {
128
+ if (paramValue && !Array.isArray(paramValue)) {
129
+ const itemValue = getProperty(item, key);
130
+ switch (op) {
131
+ // item_gt=value
132
+ case Operator.gt: {
133
+ if (!(typeof itemValue === 'number' &&
134
+ itemValue > parseInt(paramValue))) {
135
+ return false;
136
+ }
137
+ break;
138
+ }
139
+ // item_gte=value
140
+ case Operator.gte: {
141
+ if (!(typeof itemValue === 'number' &&
142
+ itemValue >= parseInt(paramValue))) {
143
+ return false;
144
+ }
145
+ break;
146
+ }
147
+ // item_lt=value
148
+ case Operator.lt: {
149
+ if (!(typeof itemValue === 'number' &&
150
+ itemValue < parseInt(paramValue))) {
151
+ return false;
152
+ }
153
+ break;
154
+ }
155
+ // item_lte=value
156
+ case Operator.lte: {
157
+ if (!(typeof itemValue === 'number' &&
158
+ itemValue <= parseInt(paramValue))) {
159
+ return false;
160
+ }
161
+ break;
162
+ }
163
+ // item_ne=value
164
+ case Operator.ne: {
165
+ if (!(itemValue != paramValue))
166
+ return false;
167
+ break;
168
+ }
169
+ // item=value
170
+ case Operator.default: {
171
+ if (!(itemValue == paramValue))
172
+ return false;
173
+ }
174
+ }
175
+ }
176
+ }
177
+ return true;
178
+ });
179
+ const sort = query._sort || '';
180
+ const sorted = sortOn(res, sort.split(','));
181
+ const start = query._start;
182
+ const end = query._end;
183
+ const limit = query._limit;
184
+ if (start === undefined && limit) {
185
+ return sorted.slice(0, limit);
186
+ }
187
+ if (start && limit) {
188
+ return sorted.slice(start, start + limit);
189
+ }
190
+ let page = query._page;
191
+ const perPage = query._per_page || 10;
192
+ if (page) {
193
+ const items = sorted.length;
194
+ const pages = Math.ceil(items / perPage);
195
+ // Ensure page is within the valid range
196
+ page = Math.max(1, Math.min(page, pages));
197
+ const first = 1;
198
+ const prev = page > 1 ? page - 1 : null;
199
+ const next = page < pages ? page + 1 : null;
200
+ const last = pages;
201
+ const start = (page - 1) * perPage;
202
+ const end = start + perPage;
203
+ const data = sorted.slice(start, end);
204
+ return {
205
+ first,
206
+ prev,
207
+ next,
208
+ last,
209
+ pages,
210
+ items,
211
+ data,
212
+ };
213
+ }
214
+ return sorted.slice(start, end);
215
+ }
216
+ async create(name, data = {}) {
217
+ const items = this.#get(name);
218
+ if (items === undefined)
219
+ return;
220
+ const nextData = { id: randomBytes(2).toString('hex'), ...data };
221
+ items.push(nextData);
222
+ await this.#db.write();
223
+ return nextData;
224
+ }
225
+ async update(name, id, body = {}) {
226
+ const items = this.#get(name);
227
+ if (items === undefined)
228
+ return;
229
+ const index = items.findIndex((item) => item['id'] === id);
230
+ if (index === -1)
231
+ return;
232
+ const item = items.at(index);
233
+ if (item) {
234
+ const nextItem = { ...body, id: item['id'] };
235
+ items.splice(index, 1, nextItem);
236
+ await this.#db.write();
237
+ return nextItem;
238
+ }
239
+ return;
240
+ }
241
+ async patch(name, id, body = {}) {
242
+ const items = this.#get(name);
243
+ if (items === undefined)
244
+ return;
245
+ const index = items.findIndex((item) => item['id'] === id);
246
+ if (index === -1)
247
+ return;
248
+ const item = items.at(index);
249
+ if (item) {
250
+ const nextItem = { ...item, ...body, id: item['id'] };
251
+ items.splice(index, 1, nextItem);
252
+ await this.#db.write();
253
+ return nextItem;
254
+ }
255
+ return;
256
+ }
257
+ async destroy(name, id, dependents = []) {
258
+ const items = this.#get(name);
259
+ if (items === undefined)
260
+ return;
261
+ const index = items.findIndex((item) => item['id'] === id);
262
+ if (index === -1)
263
+ return;
264
+ const item = items.splice(index, 1)[0];
265
+ nullifyForeignKey(this.#db, name, id);
266
+ deleteDependents(this.#db, name, dependents);
267
+ await this.#db.write();
268
+ return item;
269
+ }
270
+ }
@@ -0,0 +1 @@
1
+ export {};