@kollors/deep-json-server 0.5.0 → 0.6.0

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
@@ -4,7 +4,7 @@
4
4
 
5
5
  [GitHub](https://github.com/kollors/deep-json-server) | [npm](https://www.npmjs.com/package/@kollors/deep-json-server)
6
6
 
7
- A small JSON REST mock server with CRUD, pagination, deep filters and recursive relationship embedding. It keeps the database in one readable JSON file and infers soft relations from conventional keys such as `countryId`, `genreIds` and `publisherIds`.
7
+ A small REST mock server with CRUD, pagination, deep filtering, recursive relationship embedding, binary files, and OpenAPI generation. Data can be stored in JSON files or memory, and relations are inferred from conventional keys such as `countryId`, `genreIds`, and `publisherIds`.
8
8
 
9
9
  ## Installation
10
10
 
@@ -14,6 +14,43 @@ Node.js 20 or newer is required.
14
14
  npm install --save-dev @kollors/deep-json-server
15
15
  ```
16
16
 
17
+ ## Quick start
18
+
19
+ Create the database file `mock/database.json` before startup:
20
+
21
+ ```json
22
+ {
23
+ "movies": [
24
+ { "id": "1", "title": "Shadows of Ardenia" }
25
+ ]
26
+ }
27
+ ```
28
+
29
+ Create the ESM module `server.config.js` next to `package.json`:
30
+
31
+ ```js
32
+ export default {
33
+ database: {
34
+ path: 'mock/database.json',
35
+ },
36
+ };
37
+ ```
38
+
39
+ Start the server:
40
+
41
+ ```bash
42
+ npx deep-json-server server.config.js
43
+ ```
44
+
45
+ The API is available at `http://127.0.0.1:4001` by default. For example, `GET http://127.0.0.1:4001/movies` returns this page:
46
+
47
+ ```json
48
+ {
49
+ "data": [{ "id": "1", "title": "Shadows of Ardenia" }],
50
+ "total": 1
51
+ }
52
+ ```
53
+
17
54
  ## Configuration and startup
18
55
 
19
56
  Create the ESM module `server.config.js`. The example below enables every feature:
@@ -35,6 +72,9 @@ export default {
35
72
  },
36
73
  server: {
37
74
  host: '127.0.0.1',
75
+ logger: true,
76
+ maxFileSize: 100 * 1024 * 1024,
77
+ maxPageSize: 1000,
38
78
  port: 4001,
39
79
  },
40
80
  };
@@ -42,25 +82,51 @@ export default {
42
82
 
43
83
  Configuration keys:
44
84
 
45
- | Key | Required | Purpose |
85
+ | Key | Condition | Purpose |
46
86
  | --- | --- | --- |
47
- | `database.path` | Always | Existing JSON database file |
48
- | `database.schema` | No | JSON overrides for request validation and OpenAPI schemas |
49
- | `files.directory` | With `--files` | Directory for binary contents |
50
- | `files.metadata` | With `--files` | JSON file containing uploaded-file metadata |
51
- | `openapi.path` | With `--openapi` | Generated OpenAPI YAML file |
52
- | `server.host` | No | Listening host; falls back to `HOST`, then `127.0.0.1` |
53
- | `server.port` | No | Listening port; falls back to `PORT`, then `4001` |
87
+ | `database.path` | Exactly one of `path` or `data` is required | Existing JSON database file |
88
+ | `database.data` | Exactly one of `path` or `data` is required | Database object stored in memory |
89
+ | `database.schema` | No | Path to JSON overrides or an object with request-validation and OpenAPI settings |
90
+ | `files.directory` | Together with `files.metadata` | Directory for binary contents on disk |
91
+ | `files.metadata` | Together with `files.directory` | JSON file containing file metadata on disk |
92
+ | `files.data` | Instead of the `directory` and `metadata` pair | In-memory files with `Uint8Array` contents |
93
+ | `openapi.path` | Required by the `--openapi` and `--openapi-only` CLI flags | Generated OpenAPI YAML file; the programmatic API can return a document without this path |
94
+ | `server.host` | No | Host used by the CLI, `server.openapi()`, and argument-less `server.fastify().listen()`; defaults to `127.0.0.1` |
95
+ | `server.logger` | No | Fastify logger settings; defaults to `true` |
96
+ | `server.maxFileSize` | No | Maximum uploaded-file size in bytes when file routes are enabled; defaults to 100 MiB |
97
+ | `server.maxPageSize` | No | Maximum allowed `_perPage` in the API and OpenAPI; defaults to `1000` |
98
+ | `server.port` | No | Port used by the CLI, `server.openapi()`, and argument-less `server.fastify().listen()`; defaults to `4001` |
99
+
100
+ `server.port` must be an integer from `0` to `65535`. The value `0` lets Fastify select an available port at runtime, but cannot be used to generate an OpenAPI server URL, which requires a port from `1` to `65535`. Both `server.maxFileSize` and `server.maxPageSize` must be positive integers.
54
101
 
55
102
  All relative paths are resolved from the directory containing `server.config.js`, not from the current working directory. Unknown keys, empty paths and invalid value types are rejected before startup. The config is executable JavaScript, so it can read environment variables, import other modules and calculate values before exporting the object. A `.js` config with `export default` requires an ESM project (`"type": "module"`); in a CommonJS project, use the same contents in `server.config.mjs`.
56
103
 
57
- Add the commands you need to `package.json`:
104
+ The same config may keep everything in memory. `database.path` and `database.data` are mutually exclusive; `database.schema` accepts either a path or an object. Likewise, `files.data` cannot be combined with `files.directory` or `files.metadata`:
105
+
106
+ ```js
107
+ export default {
108
+ database: {
109
+ data: { movies: [{ id: '1', title: 'Shadows of Ardenia' }] },
110
+ schema: { $info: { title: 'Movie API', version: '1.0.0' } },
111
+ },
112
+ files: {
113
+ data: [{ content: new Uint8Array([1, 2, 3]), id: 'file-1', mimeType: 'application/octet-stream', name: 'example.bin' }],
114
+ },
115
+ };
116
+ ```
117
+
118
+ In-memory values are cloned during initialization. CRUD and file operations therefore do not mutate the exported config object, and their results disappear when the process exits.
119
+
120
+ Add the commands you need to `package.json`. Here, `mock:openapi:files` updates OpenAPI first and then keeps the server running with file routes:
58
121
 
59
122
  ```json
60
123
  {
61
124
  "scripts": {
62
- "mock": "deep-json-server --files server.config.js",
63
- "openapi": "deep-json-server --openapi --files server.config.js"
125
+ "mock": "deep-json-server server.config.js",
126
+ "mock:files": "deep-json-server --files server.config.js",
127
+ "mock:openapi:files": "deep-json-server --files --openapi server.config.js",
128
+ "openapi": "deep-json-server --openapi-only server.config.js",
129
+ "openapi:files": "deep-json-server --files --openapi-only server.config.js"
64
130
  }
65
131
  }
66
132
  ```
@@ -71,24 +137,26 @@ CLI modes:
71
137
  | --- | --- |
72
138
  | `deep-json-server server.config.js` | Starts the CRUD server without file routes |
73
139
  | `deep-json-server --files server.config.js` | Starts the CRUD server with file routes |
74
- | `deep-json-server --openapi server.config.js` | Generates OpenAPI and exits |
75
- | `deep-json-server --openapi --files server.config.js` | Generates OpenAPI with file routes and exits |
140
+ | `deep-json-server --openapi server.config.js` | Generates OpenAPI and starts the CRUD server |
141
+ | `deep-json-server --files --openapi server.config.js` | Generates OpenAPI with file routes and starts the server with them |
142
+ | `deep-json-server --openapi-only server.config.js` | Generates OpenAPI and exits |
143
+ | `deep-json-server --files --openapi-only server.config.js` | Generates OpenAPI with file routes and exits |
76
144
 
77
- `--openapi` never starts the HTTP server. `--files` is independent: without it, file routes are neither registered nor added to OpenAPI. Run `deep-json-server --help` to print the CLI summary.
145
+ `--files` is independent: without it, file routes are neither registered nor added to OpenAPI, even when the config contains a `files` section. The `--openapi` and `--openapi-only` flags are mutually exclusive. Run `deep-json-server --help` to print the CLI summary.
78
146
 
79
147
  ## Example database
80
148
 
81
- This example is based on a movie catalog. `Gangster film` demonstrates a relationship with a parent genre.
149
+ Below is an example movie catalog with sample data. `Gangster` is linked to its parent genre, `Crime`.
82
150
 
83
151
  ```json
84
152
  {
85
153
  "countries": [
86
- { "id": "1", "isArchived": false, "name": "Russia" },
87
- { "id": "2", "isArchived": false, "name": "United States" }
154
+ { "id": "1", "isArchived": false, "name": "Ardenia" },
155
+ { "id": "2", "isArchived": false, "name": "Veloria" }
88
156
  ],
89
157
  "genres": [
90
158
  { "id": "1", "isArchived": false, "name": "Crime", "parentIds": [] },
91
- { "id": "2", "isArchived": false, "name": "Gangster film", "parentIds": ["1"] },
159
+ { "id": "2", "isArchived": false, "name": "Gangster", "parentIds": ["1"] },
92
160
  { "id": "3", "isArchived": false, "name": "Drama", "parentIds": [] },
93
161
  { "id": "4", "isArchived": false, "name": "Comedy", "parentIds": [] }
94
162
  ],
@@ -98,39 +166,39 @@ This example is based on a movie catalog. `Gangster film` demonstrates a relatio
98
166
  { "genreIds": ["2", "3"], "id": "movie-1-actor-1", "userId": "1" },
99
167
  { "genreIds": ["3"], "id": "movie-1-actor-2", "userId": "2" }
100
168
  ],
101
- "coverSrc": "https://image.tmdb.org/t/p/w500/3bhkrj58Vtu7enYsRolD1fZdja1.jpg",
102
- "description": "The story of the Corleone family and the transfer of power from one generation to the next.",
169
+ "coverSrc": "https://example.com/covers/shadows-of-ardenia.jpg",
170
+ "description": "The heir to a port city uncovers a conspiracy between two rival families.",
103
171
  "id": "1",
104
172
  "isArchived": false,
105
173
  "publisherIds": ["2"],
106
- "title": "The Godfather"
174
+ "title": "Shadows of Ardenia"
107
175
  },
108
176
  {
109
177
  "actors": [],
110
- "coverSrc": "https://image.tmdb.org/t/p/w500/eWdyYQreja6JGCzqHWXpWHDrrPo.jpg",
111
- "description": "The adventures of a concierge and his young assistant in a famous European hotel.",
178
+ "coverSrc": "https://example.com/covers/northern-star.jpg",
179
+ "description": "A night manager at an old hotel is drawn into the search for a missing painting.",
112
180
  "id": "2",
113
181
  "isArchived": false,
114
182
  "publisherIds": ["1"],
115
- "title": "The Grand Budapest Hotel"
183
+ "title": "Midnight at the Northern Star"
116
184
  }
117
185
  ],
118
186
  "publishers": [
119
- { "id": "1", "isArchived": false, "name": "A24" },
120
- { "id": "2", "isArchived": false, "name": "Paramount Pictures" }
187
+ { "id": "1", "isArchived": false, "name": "Northlight Studio" },
188
+ { "id": "2", "isArchived": false, "name": "Aurora Pictures" }
121
189
  ],
122
190
  "users": [
123
191
  {
124
- "bornAt": "1989-01-25",
192
+ "bornAt": "1988-03-14",
125
193
  "countryId": "1",
126
- "fullName": "Alexander Petrov",
194
+ "fullName": "Mira Volkova",
127
195
  "id": "1",
128
196
  "isArchived": false
129
197
  },
130
198
  {
131
- "bornAt": "1984-09-05",
132
- "countryId": "1",
133
- "fullName": "Yulia Peresild",
199
+ "bornAt": "1991-11-02",
200
+ "countryId": "2",
201
+ "fullName": "Leon Vetrov",
134
202
  "id": "2",
135
203
  "isArchived": false
136
204
  }
@@ -149,14 +217,14 @@ PATCH /movies/:id
149
217
  DELETE /movies/:id
150
218
  ```
151
219
 
152
- `POST` generates a string ID. `PUT` completely replaces the selected record, while `PATCH` updates only supplied fields; both preserve the existing ID and its type. An `id` supplied in any request body cannot override the server-controlled ID. All write operations — `POST`, `PUT`, `PATCH` and `DELETE` — are serialized and persisted in the JSON file.
220
+ `POST` generates a string ID. `PUT` completely replaces the selected record, while `PATCH` updates only supplied fields; both preserve the existing ID and its type. An `id` supplied in any request body cannot override the server-controlled ID. All write operations — `POST`, `PUT`, `PATCH` and `DELETE` — are serialized; disk storage persists them in JSON, while memory storage retains them until the process exits.
153
221
 
154
222
  The database file must exist before startup. Resource names may contain Latin letters, numbers, `_` and `-`, and must start with a letter. Every resource is an array of JSON objects. Every record must have a non-empty string or finite numeric `id`; IDs must be unique within a resource when compared as strings, so `1` and `"1"` cannot coexist. The server rereads the file before every GET and write operation, so valid external edits become visible without a restart.
155
223
 
156
224
  Successful writes return the created, replaced, updated or deleted record. Errors use an appropriate HTTP status and this JSON shape:
157
225
 
158
226
  ```json
159
- { "error": "Human-readable message" }
227
+ { "error": "..." }
160
228
  ```
161
229
 
162
230
  ## Pagination and sorting
@@ -165,21 +233,18 @@ Successful writes return the created, replaced, updated or deleted record. Error
165
233
  GET /movies?_page=1&_perPage=10&_sort=-id,title
166
234
  ```
167
235
 
168
- A GET collection always returns a page object. `_page` defaults to `1`, and `_perPage` defaults to `10`:
236
+ A collection GET always returns the current page data and the total number of records after filtering. `_page` defaults to `1`, and `_perPage` defaults to `10`:
169
237
 
170
238
  ```json
171
239
  {
172
240
  "data": [],
173
- "first": 1,
174
- "items": 0,
175
- "last": 1,
176
- "next": null,
177
- "pages": 1,
178
- "prev": null
241
+ "total": 0
179
242
  }
180
243
  ```
181
244
 
182
- Both pagination parameters must be positive integers. `_perPage` cannot exceed `1000` by default; use the programmatic `maxPageSize` option to change that limit. Invalid values return `400` instead of being silently corrected. A page beyond the last page returns an empty `data` array and points `prev` to the last available page instead of silently clamping the request.
245
+ `data` contains only the records on the requested page. `total` is the number of all records matching the filter before pagination is applied. When needed, a client can calculate the last page as `Math.max(1, Math.ceil(total / pageSize))`.
246
+
247
+ Both pagination parameters must be positive integers. `_perPage` cannot exceed `1000` by default; change the limit through `server.maxPageSize` in the config passed to either the CLI or `createServer()`. Invalid values return `400` instead of being silently corrected. A page beyond the last page returns an empty `data` array while preserving the actual `total` value.
183
248
 
184
249
  `_sort` accepts comma-separated field paths. Rules are applied from left to right; prefix a field with `-` for descending order. Dot paths can address nested object fields, including fields added by `_embed`, for example `GET /users?_embed=country&_sort=country.name,-id`. Unknown or unsafe sort fields return `400`.
185
250
 
@@ -188,7 +253,7 @@ Both pagination parameters must be positive integers. `_perPage` cannot exceed `
188
253
  Pass a JSON object through `_where`:
189
254
 
190
255
  ```http
191
- GET /movies?_where={"title":{"contains":"father"}}
256
+ GET /movies?_where={"title":{"contains":"ardenia"}}
192
257
  ```
193
258
 
194
259
  Nested objects and arrays can be filtered at any depth. Conditions in one object use `AND` by default:
@@ -196,7 +261,7 @@ Nested objects and arrays can be filtered at any depth. Conditions in one object
196
261
  ```json
197
262
  {
198
263
  "actors": { "some": { "userId": { "eq": "1" } } },
199
- "title": { "contains": "father" }
264
+ "title": { "contains": "ardenia" }
200
265
  }
201
266
  ```
202
267
 
@@ -207,7 +272,7 @@ Use `and`, `or` and `not` for explicit logical groups:
207
272
  "and": [
208
273
  {
209
274
  "or": [
210
- { "title": { "contains": "father" } },
275
+ { "title": { "contains": "ardenia" } },
211
276
  { "actors": { "some": { "userId": { "eq": "2" } } } }
212
277
  ]
213
278
  },
@@ -231,12 +296,14 @@ Field operators:
231
296
  Simple query parameters are supported too:
232
297
 
233
298
  ```http
234
- GET /movies?title:contains=father
299
+ GET /movies?title:contains=ardenia
235
300
  ```
236
301
 
237
302
  Simple filter values recognize JSON primitives: numbers, `true`, `false` and `null`. Values with leading zeroes, such as `001`, remain strings. Unknown operators, invalid logical conditions and filter paths that do not exist in a non-empty resource return `400`.
238
303
 
239
- For a simple `in` filter, separate values with commas: `GET /movies?id:in=1,2`. If `_where` is present, it is the complete filter and other simple filter parameters are ignored. The examples show readable JSON; an HTTP client must URL-encode `_where` when constructing the URL manually.
304
+ Multiple simple query filters are combined with `AND`. For an `in` filter, separate values with commas: `GET /movies?id:in=1,2`. On an array field, `in` means that at least one field element matches at least one supplied value. `every` returns `true` for an empty array, while `some` returns `false`.
305
+
306
+ If `_where` is present, it is the complete filter and other simple filter parameters are ignored. The examples show readable JSON; an HTTP client must URL-encode `_where` when constructing the URL manually, for example with `encodeURIComponent(JSON.stringify(where))`.
240
307
 
241
308
  Filtering is performed after `_embed`. This means a filter can address fields added by an embedded relation when the same request includes that `_embed`; stored `...Id` and `...Ids` fields can always be filtered directly.
242
309
 
@@ -248,7 +315,7 @@ Use `_embed` to add related records to the response:
248
315
  GET /movies/1?_embed=actors.user.country&_embed=actors.genres&_embed=publishers
249
316
  ```
250
317
 
251
- The response contains actors, each actor's user and genres, the user's country, and publishers. Original ID fields remain in the response, and the database file is not modified. Embedding can follow any number of levels:
318
+ The response contains actors, each actor's user and genres, the user's country, and publishers. Original ID fields remain in the response, and the database file is not modified. The server imposes no fixed depth limit, but every required level must be written explicitly in the finite `_embed` path:
252
319
 
253
320
  ```http
254
321
  GET /movies/1?_embed=actors.user.country
@@ -285,35 +352,37 @@ Add `files.directory` and `files.metadata` to the server config, then pass `--fi
285
352
  deep-json-server --files server.config.js
286
353
  ```
287
354
 
355
+ For temporary tests, use `files.data` instead. Each initial record contains `id`, `name`, `mimeType`, and binary `content` as a `Uint8Array`; `size` and `url` are derived automatically. Uploaded files then remain in memory until the process exits.
356
+
288
357
  Upload one file as the request body. Both headers are required: `Content-Name` contains the relative logical name encoded with `encodeURIComponent`, and `Content-Type` contains the file MIME type:
289
358
 
290
359
  ```http
291
360
  POST /_files
292
- Content-Name: posters%2Fthe-godfather.jpg
361
+ Content-Name: posters%2Fshadows-of-ardenia.jpg
293
362
  Content-Type: image/jpeg
294
363
 
295
364
  <binary body>
296
365
  ```
297
366
 
298
- The response contains metadata and a stable URL:
367
+ A successful upload returns status `201`, metadata, and a stable URL:
299
368
 
300
369
  ```json
301
370
  {
302
371
  "id": "generated-id",
303
372
  "mimeType": "image/jpeg",
304
- "name": "posters/the-godfather.jpg",
373
+ "name": "posters/shadows-of-ardenia.jpg",
305
374
  "size": 182340,
306
375
  "url": "/_files/generated-id"
307
376
  }
308
377
  ```
309
378
 
310
- Use `GET /_files/:id` to download the original bytes and `DELETE /_files/:id` to delete both the binary contents and their metadata. The returned `url` is relative to the mock-server origin. The server creates the configured directories automatically, stores binary contents under generated IDs without relying on the original file name, and keeps the logical names and other metadata in `files.metadata`. The metadata file may be absent initially and is created on the first upload. Do not edit it while the server is running.
379
+ `GET /_files/:id` returns the original bytes with status `200`. `DELETE /_files/:id` removes both the binary contents and metadata, and returns the deleted metadata with status `200`. The returned `url` is relative to the mock-server origin. The server creates the configured directories automatically, stores binary contents under generated IDs without relying on the original file name, and keeps the logical names and other metadata in `files.metadata`. The metadata file may be absent initially and is created on the first upload. Do not edit it while the server is running.
311
380
 
312
- The upload is raw binary rather than `multipart/form-data`, so `XMLHttpRequest.upload.onprogress` can report progress while the browser sends a `File` directly with `xhr.send(file)`. The default maximum size is 100 MiB and can be changed through the programmatic `maxFileSize` option. Unsafe or absolute `Content-Name` paths return `400`, an exceeded limit returns `413`, and a malformed or unsupported `Content-Type` returns `400` or `415`.
381
+ The upload is raw binary rather than `multipart/form-data`, so `XMLHttpRequest.upload.onprogress` can report progress while the browser sends a `File` directly with `xhr.send(file)`. The default maximum size is 100 MiB and can be changed through `server.maxFileSize`. A missing or unsafe `Content-Name` returns `400`, an exceeded limit returns `413`, and a missing, malformed, or Fastify-unsupported `Content-Type` returns `400` or `415`, depending on which validation stage rejects it.
313
382
 
314
383
  ## Database schema and OpenAPI generation
315
384
 
316
- The optional JSON file referenced by `database.schema`, for example `mock/database-schema.json`, customizes inferred schemas. It is read both during normal server startup and during OpenAPI generation:
385
+ The optional path or object in `database.schema` customizes inferred schemas. This is a Deep JSON Server configuration format, not a standard JSON Schema document: `$schema` is an object containing resource settings. For example, `mock/database-schema.json` may contain:
317
386
 
318
387
  ```json
319
388
  {
@@ -349,12 +418,16 @@ Schema configuration:
349
418
  | `$schema.<resource>.formats` | OpenAPI formats for inferred or explicit string fields, such as `date`, `date-time` or `uri` |
350
419
  | `$schema.<resource>.properties` | Recursive OpenAPI-compatible field schemas merged with inference |
351
420
 
421
+ `formats` is shorthand for assigning `format` to an existing string field. `properties` can fully describe a field—including its `type`, `format`, constraints, and nested properties—or add a field that is absent from the data. If both mechanisms assign a format to the same field, the value from `formats` is applied last.
422
+
352
423
  Set `openapi.path` in the server config, then generate an OpenAPI 3.0.3 file and exit:
353
424
 
354
425
  ```bash
355
- deep-json-server --openapi --files server.config.js
426
+ deep-json-server --openapi-only server.config.js
356
427
  ```
357
428
 
429
+ To include file routes in the document, configure the `files` section and add `--files`: `deep-json-server --files --openapi-only server.config.js`.
430
+
358
431
  The generator infers resources and field types from all database records. Every inferred field is optional by default, while the top-level `id` is always required in response schemas and is omitted from create and update request schemas. Add other required fields to `required`. A nested required path marks that nested property as required; it does not automatically make every parent path required, so list the parent separately when necessary.
359
432
 
360
433
  Different value types are inferred independently and combined through `oneOf`. Configuration is validated before generation: `$info`, resource and schema names, and the structure of `properties` are validated, while paths from `required` and `formats` must exist in the resulting schema.
@@ -377,7 +450,7 @@ Use `properties` to describe fields that cannot be inferred, particularly for an
377
450
 
378
451
  An empty resource still receives a required string `id` property because IDs created by the server are strings. Generation stops with an actionable error when resources produce duplicate schema names or operation IDs; use an explicit `name` to resolve schema-name collisions. The output directory is created automatically, and the configured YAML file is replaced on every generation.
379
452
 
380
- `$info` becomes the OpenAPI `info` object, while resource settings live under `$schema`. The OpenAPI `servers` entry is generated automatically from `server.host` and `server.port`, their `HOST` and `PORT` environment variable fallbacks, or the default `http://127.0.0.1:4001`.
453
+ `$info` becomes the OpenAPI `info` object, while resource settings live under `$schema`. In the CLI, the OpenAPI `servers` entry uses `server.host` and `server.port`, then the `HOST` and `PORT` environment-variable fallbacks, and finally `http://127.0.0.1:4001`. A direct `createServer()` call does not read those environment variables automatically: `server.openapi()` uses the config values or the same default URL.
381
454
 
382
455
  Use `name` when a resource needs an explicit schema name instead of the automatically singularized name:
383
456
 
@@ -391,73 +464,87 @@ Use `name` when a resource needs an explicit schema name instead of the automati
391
464
  }
392
465
  ```
393
466
 
394
- The generated document describes CRUD endpoints, pagination, sorting, deep filters, `_embed`, and both direct and reverse response relations inferred from `...Id` and `...Ids` fields. When `--files` is present, it also describes raw binary upload, download and deletion endpoints. A numeric database ID is described as `integer | string`, because a later `POST` creates a string ID in the same resource. The document can be used as input for tools such as RTK Query OpenAPI Codegen. OpenAPI is generated only when `--openapi` is passed; normal server startup does not rewrite the file.
467
+ The generated document describes CRUD endpoints, pagination, sorting, deep filters, `_embed`, and both direct and reverse response relations inferred from `...Id` and `...Ids` fields. When `--files` is present, it also describes raw binary upload, download and deletion endpoints. A numeric database ID is described as `integer | string`, because a later `POST` creates a string ID in the same resource. The document can be used as input for tools such as RTK Query OpenAPI Codegen. OpenAPI is generated only with `--openapi` or `--openapi-only`; normal server startup does not rewrite the file.
395
468
 
396
469
  During normal startup, request bodies are validated against the same inferred and configured schemas. `POST` and `PUT` enforce configured required fields; `PATCH` validates only fields that are actually supplied. `formats` and `properties` apply to all three methods. Unlisted additional object fields remain allowed. Invalid bodies return `400`.
397
470
 
398
- ## Scope and security
399
-
400
- Deep JSON Server is intended for local development and automated tests. It has no authentication or authorization, allows CORS from every origin, persists accepted writes directly to the configured files and does not enforce referential integrity. Keep the default loopback host unless the surrounding environment provides its own access controls; do not expose the server or file routes to an untrusted network.
401
-
402
471
  ## Programmatic API
403
472
 
404
473
  ```js
405
- import { createOpenApiDocument, createServer, generateOpenApi, startServer } from '@kollors/deep-json-server';
406
-
407
- // Create an instance without opening a port, for example for tests.
408
- const server = await createServer({
409
- databasePath: 'mock/database.json',
410
- filesDirectoryPath: 'mock/files',
411
- filesMetadataPath: 'mock/files/_database.json',
412
- logger: false,
413
- maxFileSize: 100 * 1024 * 1024,
414
- maxPageSize: 1000,
415
- schemaPath: 'mock/database-schema.json',
416
- });
474
+ import { createServer } from '@kollors/deep-json-server';
417
475
 
418
- const response = await server.inject({ method: 'GET', url: '/movies' });
476
+ const config = {
477
+ database: {
478
+ path: 'mock/database.json',
479
+ schema: 'mock/database-schema.json',
480
+ },
481
+ files: {
482
+ directory: 'mock/files',
483
+ metadata: 'mock/files/_database.json',
484
+ },
485
+ openapi: {
486
+ path: 'mock/openapi-schema.yaml',
487
+ },
488
+ server: {
489
+ host: '127.0.0.1',
490
+ logger: false,
491
+ maxFileSize: 100 * 1024 * 1024,
492
+ maxPageSize: 1000,
493
+ port: 4001,
494
+ },
495
+ };
419
496
 
420
- await server.close();
497
+ // Make a request without opening a network port—useful in automated tests.
498
+ const server = await createServer(config);
499
+ const fastify = server.fastify();
500
+ const response = await fastify.inject({ method: 'GET', url: '/movies' });
421
501
 
422
- // Create an instance and start listening.
423
- const listeningServer = await startServer({
424
- databasePath: 'mock/database.json',
425
- host: '127.0.0.1',
426
- port: 4001,
427
- schemaPath: 'mock/database-schema.json',
428
- });
502
+ console.log(response.json());
503
+
504
+ // Return the document and write it to config.openapi.path.
505
+ const document = await server.openapi();
429
506
 
430
- await listeningServer.close();
507
+ await fastify.close();
431
508
 
432
- // Read the database and schema files, then write an OpenAPI YAML file.
433
- await generateOpenApi({
434
- databasePath: 'mock/database.json',
435
- files: true,
436
- host: '127.0.0.1',
437
- outputPath: 'mock/openapi-schema.yaml',
438
- port: 4001,
439
- schemaPath: 'mock/database-schema.json',
509
+ // Start a network server. With no arguments, listen uses server.host and server.port.
510
+ const runningServer = await createServer(config);
511
+ const runningFastify = runningServer.fastify();
512
+
513
+ await runningFastify.listen();
514
+
515
+ // Later, during application shutdown:
516
+ await runningFastify.close();
517
+
518
+ // Keep the database, schema, and files entirely in memory.
519
+ const memoryServer = await createServer({
520
+ database: {
521
+ data: { movies: [{ id: '1', title: 'Shadows of Ardenia' }] },
522
+ schema: { $info: { title: 'Movie API', version: '1.0.0' } },
523
+ },
524
+ files: {
525
+ data: [{ content: new Uint8Array([1, 2, 3]), id: 'file-1', mimeType: 'application/octet-stream', name: 'example.bin' }],
526
+ },
440
527
  });
441
528
 
442
- // Build the same kind of OpenAPI document entirely in memory.
443
- const document = createOpenApiDocument(
444
- { movies: [{ id: '1', title: 'The Godfather' }] },
445
- { $info: { title: 'Movie API', version: '1.0.0' } },
446
- { files: true, host: '127.0.0.1', port: 4001 },
447
- );
529
+ const memoryFastify = memoryServer.fastify();
530
+ const memoryResponse = await memoryFastify.inject({ method: 'GET', url: '/movies/1' });
531
+
532
+ console.log(memoryResponse.json());
533
+
534
+ await memoryFastify.close();
448
535
  ```
449
536
 
450
- Programmatic options:
537
+ `createServer()` accepts exactly the same config shape as `server.config.js`. It loads and clones the configured sources, then returns a facade with two operations:
451
538
 
452
- | Option | Used by | Meaning |
539
+ | Member | Meaning |
453
540
  | --- | --- | --- |
454
- | `databasePath` | `createServer`, `startServer`, `generateOpenApi` | Required JSON database path |
455
- | `schemaPath` | The same three functions | Optional database-schema path |
456
- | `filesDirectoryPath`, `filesMetadataPath` | `createServer`, `startServer` | Optional pair enabling file routes |
457
- | `files` | `generateOpenApi`, `createOpenApiDocument` | Whether file routes appear in OpenAPI |
458
- | `host`, `port` | `startServer`, `generateOpenApi`, `createOpenApiDocument` | Listening address or generated `servers` URL |
459
- | `logger` | `createServer`, `startServer` | Fastify logger settings; defaults to `true` |
460
- | `maxPageSize`, `maxFileSize` | `createServer`, `startServer` | Runtime limits; defaults are 1000 records and 100 MiB |
461
- | `outputPath` | `generateOpenApi` | Required generated YAML path |
462
-
463
- `createServer()` returns a Fastify instance without opening a network port, which is useful with `server.inject()` in tests. `startServer()` also starts listening. `generateOpenApi()` reads files and writes YAML, while `createOpenApiDocument()` works with in-memory database and schema objects and does not write a file. These functions do not read `server.config.js`; pass their options explicitly. The package includes generated TypeScript declarations for all exported functions.
541
+ | `server.fastify()` | Lazily creates and caches the real Fastify instance; every native method remains available, and argument-less `listen()` uses `server.host` and `server.port` |
542
+ | `server.openapi()` | Returns an OpenAPI document and also writes it when `openapi.path` is configured |
543
+
544
+ File routes are enabled programmatically when a `files` section is present. The second argument has the shape `{ files?: boolean }`: pass `{ files: false }` to keep a configured store disabled, or `{ files: true }` to require a `files` section and enable the routes. `server.openapi()` uses the same feature state as `server.fastify()`.
545
+
546
+ An argument-less `server.fastify().listen()` uses `server.host` and `server.port`, falling back to `127.0.0.1:4001`. Explicit `listen(options)` values take precedence. Relative paths passed directly to `createServer()` resolve from the current working directory; paths loaded from `server.config.js` resolve from the config directory. The package includes generated TypeScript declarations for the facade and every config variant.
547
+
548
+ ## Scope and security
549
+
550
+ Deep JSON Server is intended for local development and automated tests. It has no authentication or authorization, allows CORS from every origin, persists accepted writes when disk storage is configured and does not enforce referential integrity. Keep the default loopback host unless the surrounding environment provides its own access controls; do not expose the server or file routes to an untrusted network.