@kollors/deep-json-server 1.0.0-rc.9 → 1.0.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,21 +4,19 @@
4
4
 
5
5
  [Release notes](CHANGELOG.md) · [Migration from 0.9.0](MIGRATION.md)
6
6
 
7
- A JSON mock server with REST, GraphQL, related records, file uploads and schema exports. Supports user login, owner and administrator permissions, record timestamps and soft deletion. Requires Node.js 22 or newer.
8
-
9
- **Breaking changes in 1.0.0.** See the [migration guide](MIGRATION.md) when upgrading from 0.9.0. The REST `scope` wildcard selects only scalar fields that do not store relation keys. Arrays, objects, relations and explicitly declared relation keys must be selected explicitly. With auth enabled, record permissions are available through the virtual `actions` field.
7
+ A JSON server for building mock APIs with REST, GraphQL, relations, file uploads and schema exports. Supports authentication, owner and administrator permissions, timestamps and soft deletion. Requires Node.js 22 or newer.
10
8
 
11
9
  ## Installation
12
10
 
13
11
  ```sh
14
- npm install @kollors/deep-json-server@rc
12
+ npm install @kollors/deep-json-server
15
13
  ```
16
14
 
17
- To install this release candidate, use `@1.0.0-rc.9`.
15
+ To pin version 1.0.0, use `npm install @kollors/deep-json-server@1.0.0`.
18
16
 
19
17
  ## Quick start
20
18
 
21
- Create two files in the same directory.
19
+ Create two files in the same directory. The configuration file uses `.mjs` because it is an ES module. For a `.js` file, set `"type": "module"` in your project's `package.json`.
22
20
 
23
21
  `database.json`:
24
22
 
@@ -30,23 +28,25 @@ Create two files in the same directory.
30
28
  }
31
29
  ```
32
30
 
33
- `server.config.js`:
31
+ `server.config.mjs`:
34
32
 
35
33
  ```js
36
34
  export default { storage: 'file', database: { source: './database.json' } };
37
35
  ```
38
36
 
39
37
  ```sh
40
- npx deep-json-server server.config.js
38
+ npx deep-json-server server.config.mjs
41
39
  ```
42
40
 
43
41
  The user list is available at `http://127.0.0.1:4001/users`.
44
42
 
45
- Add a [model schema](#model-schema) to define relations and validation. See [queries](#queries-and-responses), [authentication](#authentication), [soft deletion](#record-dates-deletion-and-ownership), [files](#files) and the [programmatic API](#programmatic-api) for more.
43
+ Add a [model schema](#model-schema) to define relations and validation. The sections below cover [queries](#queries-and-responses), [authentication](#authentication), [soft deletion](#record-dates-deletion-and-ownership), [files](#files) and the [programmatic API](#programmatic-api).
46
44
 
47
45
  ## Configuration
48
46
 
49
- With `storage: 'file'`, provide paths for every source and the schema. With `'memory'`, provide data directly. Use the same mode throughout the configuration. Add `auth` or `files` to enable those REST services. Add `graphql` to configure the GraphQL endpoint and `openapi` to serve the REST specification. `graphql: {}` and `openapi: {}` use default endpoint paths; add `target` to export a schema. GraphQL must also be enabled by root `api` in the model schema.
47
+ With `storage: 'file'`, provide paths for every source and the schema. With `'memory'`, provide data directly. Use the same mode throughout the configuration.
48
+
49
+ Add `auth` or `files` to enable those REST services. Add `graphql` to configure the GraphQL endpoint and `openapi` to serve the REST specification. `graphql: {}` and `openapi: {}` use default endpoint paths; add `target` to export a schema. With a `graphql` section, an explicit root `api` must include `graphql`. If root `api` is omitted, the server uses `['rest', 'graphql']`.
50
50
 
51
51
  ```js
52
52
  export default {
@@ -113,7 +113,7 @@ Set the root `api` in `schema.json`:
113
113
  }
114
114
  ```
115
115
 
116
- Use `"api": ["rest"]` with `openapi: {}` for REST and its OpenAPI description, or `["rest", "graphql"]` with both sections for both database APIs. The `graphql` section is required exactly when the schema root includes `graphql`. OpenAPI describes database routes only when REST is enabled; authentication and file routes are included whenever their sections are configured.
116
+ Use `"api": ["rest"]` with `openapi: {}` for REST and its OpenAPI description, or `["rest", "graphql"]` with both sections for both database APIs. If you set the schema's root `api` explicitly, include `graphql` if and only if the configuration has a `graphql` section. If root `api` is omitted, the server derives it from the configuration. OpenAPI describes database routes only when REST is enabled; authentication and file routes are included whenever their sections are configured.
117
117
 
118
118
  ### CLI
119
119
 
@@ -188,9 +188,13 @@ Model definitions belong in `models`. The schema root accepts `models`, `api`, `
188
188
 
189
189
  Explicit `['rest']` enables database REST routes; `['graphql']` enables GraphQL operations; `['rest', 'graphql']` enables both. OpenAPI describes REST routes when `openapi` is configured. Root `api: []` is invalid; `[]` is allowed only at the model level.
190
190
 
191
- A model without its own `api` inherits the root value. A model can override it with `['rest']`, `['graphql']`, both, or `[]` to hide it from both. A model cannot enable an API absent from root `api`. OpenAPI includes models with REST enabled. GraphQL generation needs at least one GraphQL model; REST OpenAPI generation needs at least one REST model. On a GraphQL-only server, OpenAPI can still describe configured auth and file routes. Related models must allow the same API. Model names must be valid identifiers; type and operation collisions cause errors. `and`, `or` and `not` are reserved filter names.
191
+ A model without its own `api` inherits the root value. A model can override it with `['rest']`, `['graphql']`, both, or `[]` to hide it from both. A model cannot enable an API absent from root `api`.
192
+
193
+ OpenAPI includes models with REST enabled. GraphQL generation needs at least one GraphQL model; REST OpenAPI generation needs at least one REST model. On a GraphQL-only server, OpenAPI can still describe configured auth and file routes.
192
194
 
193
- Thus an explicit root `"api": ["rest", "graphql"]` enables both APIs for models that inherit the root setting. The array order does not matter.
195
+ Related models must allow the same API. Model names must be valid identifiers; type and operation collisions cause errors. `and`, `or` and `not` are reserved filter names.
196
+
197
+ The order of values in `api` does not matter.
194
198
 
195
199
  For example, with root `api: ['rest', 'graphql']`, a model set to `api: ['rest']` has REST routes and OpenAPI paths but no GraphQL operations; a model set to `api: ['graphql']` has GraphQL operations but no REST routes or OpenAPI paths.
196
200
 
@@ -232,7 +236,11 @@ String and numeric constraints on `string[]`/`number[]` apply to every element.
232
236
 
233
237
  Each `enum` value must match the field type and its constraints. Use `nullable: true` to allow `null` for the field; do not include `null` in `enum`. Invalid values are rejected when the schema is loaded.
234
238
 
235
- Each model requires exactly one primary key of type `string` or `number`, declared at the top level. The name is arbitrary: `id`, `username`, `code`. If `generated` is omitted, the client supplies the value on creation. Generated fields must be declared at the top level, are excluded from input types and cannot have `default`. Replacing a record preserves generated values and read-only fields, including nested objects. Setting a nullable parent to `null` is rejected if it would remove stored read-only descendants. To protect fields inside an array, mark the entire array or its containing object as `readOnly`. Objects containing only server-managed fields are output-only.
239
+ Each model requires exactly one primary key of type `string` or `number`, declared at the top level. The name is arbitrary: `id`, `username`, `code`. If `generated` is omitted, the client supplies the value on creation.
240
+
241
+ Generated fields must be declared at the top level, are excluded from input types and cannot have `default`.
242
+
243
+ Replacing a record preserves generated values and read-only fields, including nested objects. Setting a nullable parent to `null` is rejected if it would remove stored read-only descendants. To protect fields inside an array, mark the entire array or its containing object as `readOnly`. Objects containing only server-managed fields are output-only.
236
244
 
237
245
  For example, a `LocalUser` with primary key `username` and `password: {"type":"string","required":true,"writeOnly":true}` has `localUser(username: ...)` and `/localUsers/{username}`. A `writeOnly` field accepts input and is excluded from responses, `scope`, filters and ordering.
238
246
 
@@ -257,7 +265,11 @@ On the `current` side, `target` defaults to the related model's primary key. `so
257
265
 
258
266
  `source` is always a path in the current model, and `target` is always a path in the model named by `type`. For an inverse relation, the paths are swapped: `Country.users` has `source: "id"` and `target: "countryId"`. Specify a path when the stored key has a custom name. If multiple relations connect the same models, disambiguate the inverse declarations, for example `Country.birthUsers = {"type":"User[]","keyOn":"related","target":"birthCountryId"}` and `Country.residenceUsers = {"type":"User[]","keyOn":"related","target":"residenceCountryId"}`. Missing, inconsistent, or ambiguous pairs fail when the schema loads.
259
267
 
260
- Relation keys remain in the database even when they are absent from `fields`. On the `current` side, a `source` field can be omitted when the target key type is known: the server infers an array of keys for a list relation or a scalar key for a single relation. This applies to primary and non-primary target fields. An inferred key is internal: REST and GraphQL cannot read, select, filter, sort or write it directly, and OpenAPI does not describe it. The relation itself remains available. Declare the key in `fields`, for example `"countryId": { "type": "string" }`, to expose it in the APIs. An ambiguous mapping also requires an explicit declaration.
268
+ Relation keys remain in the database even when they are absent from `fields`. On the `current` side, the key named by `source` need not be declared in `fields` if the target key type is known: the server infers an array of keys for a list relation or a scalar key for a single relation. This applies to primary and non-primary target fields.
269
+
270
+ An inferred key is internal: REST and GraphQL cannot read, select, filter, sort or write it directly, and OpenAPI does not describe it. The relation itself remains available.
271
+
272
+ Declare the key in `fields`, for example `"countryId": { "type": "string" }`, to expose it in the APIs. An ambiguous mapping also requires an explicit declaration.
261
273
 
262
274
  For example, `publishers: { "type": "Publisher[]", "keyOn": "current", "target": "code" }` infers `publisherCodes: { "type": "string[]" }` when `Publisher.code` is a string, even if the primary key is `Publisher.id`. Explicitly declared storage key types are preserved.
263
275
 
@@ -276,6 +288,8 @@ Cascading deletion runs as one operation, including cyclic relations. A validati
276
288
 
277
289
  ## Example database
278
290
 
291
+ The database below uses the full [example schema](examples/schema.json). It includes movies, genres and publishers in addition to the users and countries shown in the smaller schema above.
292
+
279
293
  ```json
280
294
  {
281
295
  "countries": [
@@ -368,7 +382,7 @@ Cascading deletion runs as one operation, including cyclic relations. A validati
368
382
 
369
383
  ## Queries and responses
370
384
 
371
- The movie, actor and genre examples use the full [example schema](examples/schema.json). Run them with the [example configuration](examples/server.config.js):
385
+ Run the examples below with the [example configuration](examples/server.config.js):
372
386
 
373
387
  ```sh
374
388
  npx deep-json-server examples/server.config.js
@@ -384,7 +398,9 @@ Primitive arrays are returned as plain arrays. Every object list accepts optiona
384
398
 
385
399
  Use `page` and `pageSize` in `pager`. The default is the first page with the size from `server.pageSize`. Both values must be positive integers; `pageSize` is limited by `server.maxPageSize`. Out-of-range pages return empty `data` with the total matching record count in `total`.
386
400
 
387
- `where` uses field operators `eq`, `ne`, `in`, string `contains`/`startsWith`/`endsWith`, and comparisons `gt`, `gte`, `lt`, `lte`. Conditions in the same object must all match. `and` and `or` take arrays of conditions; `not` takes one condition and can also be used inside a field filter. Arrays support `some`, `every`, `none`; primitive arrays also support `contains`, `in`. String `contains`, `startsWith` and `endsWith` ignore case; `eq`, `ne` and `in` compare exact values. For strings, `gt`, `gte`, `lt` and `lte` use the same case-insensitive, numeric-aware order as sorting. A field condition is an object containing an operator, such as `{ "id": { "eq": "1" } }`.
401
+ `where` uses field operators `eq`, `ne`, `in`, string `contains`/`startsWith`/`endsWith`, and comparisons `gt`, `gte`, `lt`, `lte`. Conditions in the same object must all match. `and` and `or` take arrays of conditions; `not` takes one condition and can also be used inside a field filter. Arrays support `some`, `every`, `none`; primitive arrays also support `contains`, `in`.
402
+
403
+ String `contains`, `startsWith` and `endsWith` ignore case; `eq`, `ne` and `in` compare exact values. For strings, `gt`, `gte`, `lt` and `lte` use the same case-insensitive, numeric-aware order as sorting. A field condition is an object containing an operator, such as `{ "id": { "eq": "1" } }`.
388
404
 
389
405
  ```json
390
406
  {
@@ -417,7 +433,7 @@ When REST is enabled by root `api`, `GET /` lists only REST-enabled models: `{ "
417
433
  | PATCH | `/users/{id}` | `userUpdate` |
418
434
  | DELETE | `/users/{id}` | `userDelete` |
419
435
 
420
- The path parameter name follows the primary key. POST, PUT and PATCH accept a JSON record object. PUT replaces the record while retaining its key and server-managed fields. PATCH merges fields at the top level; supplied nested objects are replaced while preserving their read-only fields. Creation and replacement require all mandatory fields. Updates validate supplied values and the final record. Missing records return `404`; conflicts return `409`.
436
+ The path parameter name follows the primary key. POST, PUT and PATCH accept a JSON record object. PUT replaces the record while retaining its key and server-managed fields. PATCH merges fields at the top level; supplied nested objects are replaced while preserving their read-only fields. Creation and replacement require all writable mandatory fields without defaults. Updates validate supplied values and the final record. Missing records return `404`; conflicts return `409`.
421
437
 
422
438
  POST returns the created record with status `201`; PUT, PATCH and DELETE return the updated or deleted record with status `200`. REST errors use `{ "error": "Error description" }`.
423
439
 
@@ -448,7 +464,11 @@ const params = new URLSearchParams({ scope: JSON.stringify(scope) });
448
464
  const response = await fetch(`/users?${params}`);
449
465
  ```
450
466
 
451
- Select scalars and primitive arrays with `true`, and objects or relations with their own scope arrays. Without arguments, the array contains only the fields object. `"*": true` includes only scalar fields of the current model that do not store relation keys. Arrays, objects, relations, their keys and `writeOnly` fields are not included by the wildcard. With a schema, a relation key can be selected as `{ "*": true, "countryId": true }` only if it is declared in `fields`. Without a schema, a key present in stored records can be selected explicitly even if omitted from `*`; the wildcard omits it when the server infers a relation from its name and an existing collection.
467
+ Select scalars and primitive arrays with `true`, and objects or relations with their own scope arrays. Without arguments, the array contains only the fields object.
468
+
469
+ `"*": true` includes only scalar fields of the current model that do not store relation keys. Arrays, objects, relations, their keys and `writeOnly` fields are not included by the wildcard.
470
+
471
+ With a schema, a relation key can be selected as `{ "*": true, "countryId": true }` only if it is declared in `fields`. Without a schema, a key present in stored records can be selected explicitly even if omitted from `*`; the wildcard omits it when the server infers a relation from its name and an existing collection.
452
472
 
453
473
  For example, select a movie's own fields, its actors' users and sorted genres:
454
474
 
@@ -472,9 +492,22 @@ For example, select a movie's own fields, its actors' users and sorted genres:
472
492
 
473
493
  Omitting `scope` returns scalar fields, as with `[{"*":true}]`. An empty selection `[{}]` returns an object without fields. Lists retain the `{ data, total }` response structure.
474
494
 
475
- Arguments are available only on lists. A list can instead use `{ "union": [scope, ...] }`: each part is a normal list scope, parts run in array order, and the first record for each primary key is kept. For embedded object arrays without primary keys, each source element is kept once, even if several parts select it. Distinct elements with equal contents remain separate. Single-record queries and mutation responses can set arguments on their embedded lists. Parameters are validated even on empty data; an invalid response selection rolls back record changes. Invalid scopes return `400`. The JSON length limit is 10,000 characters; selection depth is limited to 32 levels.
495
+ Arguments are available only on lists. A list can instead use `{ "union": [scope, ...] }`: each part is a normal list scope, parts run in array order, and the first record for each primary key is kept.
476
496
 
477
- REST and GraphQL also enforce a shared budget for each request: at most 1,000 selection nodes, 10,000 expanded records, 100,000 accounted execution steps, and a conservative JSON size estimate of 8 MiB. Nested lists, `union` parts and GraphQL aliases consume the same budget. Exceeding a limit returns REST `400` or GraphQL `INVALID_QUERY`; reduce selected fields, nesting or page sizes. REST mutations roll back if their response projection exceeds the budget.
497
+ For embedded object arrays without primary keys, each source element is kept once, even if several parts select it. Distinct elements with equal contents remain separate.
498
+
499
+ Single-record queries and mutation responses can set arguments on their embedded lists.
500
+
501
+ Parameters are validated even on empty data; an invalid response selection rolls back record changes. Invalid scopes return `400`. The JSON length limit is 10,000 characters; selection depth is limited to 32 levels.
502
+
503
+ REST and GraphQL limit the total work per request:
504
+
505
+ - 1,000 nodes in the field-selection tree.
506
+ - 10,000 records expanded into the response.
507
+ - 100,000 counted processing steps, including filtering and sorting.
508
+ - 8 MiB for the estimated JSON response size.
509
+
510
+ Nested lists, `union` parts and GraphQL aliases share these limits. The size estimate includes a margin for JSON encoding, so a request may reach the limit before its actual response reaches 8 MiB. Exceeding a limit returns REST `400` or GraphQL `INVALID_QUERY`; reduce selected fields, nesting or page sizes. REST mutations roll back if building their response exceeds a limit.
478
511
 
479
512
  ### Nested writes
480
513
 
@@ -504,11 +537,15 @@ Content-Type: application/json
504
537
  | An object with a primary key and other fields | PATCH updates supplied fields; PUT replaces the related record |
505
538
  | An object without a primary key | Create a related record with defaults and a generated key |
506
539
 
507
- The key name and type follow the target model. An object containing only that key is a reference in POST, PUT and PATCH; it does not require the target's other mandatory fields. Adding other fields makes it an update: PUT requires all mandatory fields and removes omitted optional fields or resets them to defaults; POST/PATCH update supplied fields. Replacement preserves primary keys, generated values and `readOnly` fields. A missing target is an error; creating a nested record without a key requires an autogenerated primary key. Declared storage keys, for example `countryId`, can also change links directly.
540
+ The key name and type follow the target model. An object containing only that key is a reference in POST, PUT and PATCH; it does not require the target's other mandatory fields.
541
+
542
+ Adding other fields makes it an update: PUT requires writable mandatory fields without defaults and removes omitted optional fields or resets them to defaults; POST/PATCH update supplied fields. Replacement preserves primary keys, generated values and `readOnly` fields.
543
+
544
+ A missing target is an error; creating a nested record without a key requires an autogenerated primary key. Declared storage keys, for example `countryId`, can also change links directly.
508
545
 
509
546
  A supplied list replaces the relation's membership. PATCH preserves omitted relations. PUT replaces the current record's writable fields, including its own relation keys, but preserves omitted `keyOn: "related"` links whose keys live in other records. `[]` clears a list and `null` clears a nullable single relation. Removing a link does not delete the related record. Required single relations must remain linked; required list relations may be empty.
510
547
 
511
- When the storage key is declared in `fields`, use either the relation field or its key in an object, for example `genres` or `genreIds`. Supplying both fields returns `400 INVALID_INPUT`, even when their key sets match:
548
+ When the storage key is declared in `fields`, use either the relation field or its key in an object, for example `genres` or `genreIds`. Supplying both fields returns REST `400` or GraphQL `INVALID_INPUT`, even when their key sets match:
512
549
 
513
550
  ```json
514
551
  {
@@ -519,7 +556,7 @@ When the storage key is declared in `fields`, use either the relation field or i
519
556
 
520
557
  The server rejects both fields together and rolls back the operation. Relation fields accept objects only; a declared key such as `genreIds` changes links without creating or updating related records. Reverse relations update the target key. If a target path crosses an array and the server cannot identify one element to attach, provide the array with the intended keys explicitly. Protected keys cannot be changed.
521
558
 
522
- All nested changes belong to the main record's transaction. A validation error, missing record or invalid response selection rolls back the entire operation. Updating a shared record affects every record linked to it.
559
+ All nested writes belong to the main record's transaction. A validation error or missing target rolls back all changes. In REST, an invalid response selection also rolls back the write. GraphQL checks selections before writing, but an error while resolving a committed mutation's result does not undo it; see [GraphQL](#graphql). Updating a shared record affects every record linked to it.
523
560
 
524
561
  GraphQL accepts typed objects in relation fields. To change only links, including in a replace mutation, pass objects containing only primary keys or use a declared storage key such as `genreIds`. Required fields for nested creation or replacement with additional fields are checked at runtime. For example:
525
562
 
@@ -541,7 +578,7 @@ mutation {
541
578
  Set `database.schema`, include `graphql` in the schema's root `api`, and add `graphql: {}` to the configuration:
542
579
 
543
580
  ```sh
544
- npx deep-json-server server.config.js
581
+ npx deep-json-server server.config.mjs
545
582
  ```
546
583
 
547
584
  Send requests to `/graphql` using POST with `Content-Type: application/json` and a body of `{ "query": "…", "variables": {} }`. Change the path through `graphql.endpoint`.
@@ -577,9 +614,9 @@ When the primary key is named `data`, Update/Replace mutations accept its value
577
614
 
578
615
  String primary keys use GraphQL `ID`; ordinary strings use `String`, numbers use `Float`, and pagination parameters use `Int`. Schema enums preserve valid string labels; other values receive `VALUE_0`, `VALUE_1`, etc. String lengths, formats and other model constraints are validated by the server during request execution. Introspection is available for exploring the schema. Selected list arguments are validated before executing mutations.
579
616
 
580
- Errors include `extensions.code`: `INVALID_INPUT`, `INVALID_QUERY`, `NOT_FOUND`, `CONFLICT`, `UNAUTHENTICATED`, `FORBIDDEN` or `INTERNAL_ERROR`. GraphQL syntax and type errors appear in the standard `errors` array. Query depth is limited to 32 levels. The request budgets described for REST also apply to GraphQL. Selection limits and list arguments are checked before mutations run. Errors while resolving a mutation result, including runtime budget exhaustion, do not undo a mutation that has already committed; query the record to check its state before retrying.
617
+ Errors include `extensions.code`: `INVALID_INPUT`, `INVALID_QUERY`, `NOT_FOUND`, `CONFLICT`, `UNAUTHENTICATED`, `FORBIDDEN` or `INTERNAL_ERROR`. GraphQL syntax and type errors appear in the standard `errors` array. Query depth is limited to 32 levels. The request limits described for REST also apply to GraphQL. Selection limits and list arguments are checked before mutations run. Errors while resolving a mutation result, including exceeding a request limit, do not undo a mutation that has already committed; query the record to check its state before retrying.
581
618
 
582
- A GraphQL operation containing several mutation fields is not one transaction: fields execute in order, and earlier committed changes remain if a later result fails. Runtime budget exhaustion returns `data: null` and stops subsequent mutation fields, but `data: null` does not mean writes were rolled back. Recover the saved state with a smaller query. Automatically retrying a create mutation with a generated key can create a second record.
619
+ A GraphQL operation containing several mutation fields is not one transaction: fields execute in order, and earlier committed changes remain if a later result fails. Exceeding a request limit during execution returns `data: null` and stops subsequent mutation fields. Check the saved state with a smaller query before retrying: `data: null` does not indicate a rollback, and retrying a create mutation with a generated key can create a second record.
583
620
 
584
621
  ## OpenAPI and schema exports
585
622
 
@@ -598,13 +635,13 @@ export default {
598
635
  ```
599
636
 
600
637
  ```bash
601
- npx deep-json-server server.config.js --generate-only
602
- npx deep-json-server server.config.js --generate
638
+ npx deep-json-server server.config.mjs --generate-only
639
+ npx deep-json-server server.config.mjs --generate
603
640
  ```
604
641
 
605
642
  `--generate-only` exports and exits; `--generate` starts the server after exporting. Configuration sections select the formats. Each selected format requires its own `target`. The OpenAPI HTTP endpoint returns JSON; file exports from `writeOpenapi()` and the CLI are YAML. Missing sections, missing targets or generation errors fail the command before server startup.
606
643
 
607
- Export does not open the database, user records or files. Every selected `target` is checked before writing and cannot overwrite the configuration, database, schema, users, counters or file metadata.
644
+ Schema generation does not load database records, auth users or uploaded files. Every selected `target` is checked before writing and cannot overwrite the configuration, database, schema, users, locks, counters or file metadata.
608
645
 
609
646
  ## Authentication
610
647
 
@@ -639,7 +676,7 @@ export default {
639
676
  };
640
677
  ```
641
678
 
642
- Start with `npx deep-json-server server.config.js`. Each initial user needs a unique string `id`, a unique `username` and a `passwordHash` created by the helper. `isAdmin` defaults to `false`, and `sessions` defaults to `[]`, so existing user files remain valid. Passwords use salted scrypt hashes.
679
+ Start with `npx deep-json-server server.config.mjs`. Each initial user needs a unique string `id`, a unique `username` and a `passwordHash` created by the helper. `isAdmin` defaults to `false`, and `sessions` defaults to `[]`, so existing user files remain valid. Passwords use salted scrypt hashes.
643
680
 
644
681
  With `storage: 'memory'`, pass an array in `auth.source`:
645
682
 
@@ -662,7 +699,7 @@ export default {
662
699
 
663
700
  Auth users and their sessions are stored together in `auth.source`, separately from the database. Each entry in a user's `sessions` array contains `{ tokenHash, expiresAt }`: `tokenHash` is the SHA-256 hash of the access token as 64 lowercase hexadecimal characters, and `expiresAt` is an absolute Unix timestamp in milliseconds. Raw tokens are never stored, and auth responses expose neither password hashes nor session records. Token hashes must be unique across all users.
664
701
 
665
- With `storage: 'file'`, login, logout and user changes are saved to the same JSON file before a successful response. Valid sessions survive server restarts: the client can keep using its existing token. A password change and removal of that user's sessions are saved together. A failed write leaves the previous users and sessions unchanged. Restart the server after editing the file manually; if you replace a password hash manually, also clear that user's `sessions`.
702
+ With `storage: 'file'`, changes to users and sessions are saved to the JSON file before a successful response. Valid sessions survive server restarts: the client can keep using its existing token. A password change and removal of that user's sessions are saved together. A failed write leaves the previous users and sessions unchanged. Stop the server before editing the file manually; if you replace a password hash, also clear that user's `sessions`, then start the server again.
666
703
 
667
704
  With `storage: 'memory'`, the supplied user array is not modified. Every startup loads the initial users and sessions again. Sessions created at runtime disappear on restart; unexpired sessions supplied in the initial data are restored, including any that were logged out during the previous run. The client still needs the corresponding raw token; it cannot recover it from `tokenHash`.
668
705
 
@@ -717,7 +754,7 @@ query {
717
754
 
718
755
  `actions` is available on root and related model records, including mutation results. It cannot be written, filtered or ordered. Plain embedded objects and auth user responses do not receive it.
719
756
 
720
- When `openapi` is configured, its document describes auth routes, optional Bearer authentication on record reads, required authentication on record changes and the `actions` response field. In Swagger UI, paste a token from login into **Authorize**. For schema exports, enable auth in the configuration and run `npx deep-json-server server.config.js --generate-only`; the users file is not read during generation. Auth methods are exposed through REST. GraphQL checks the same token when reading permissions or changing records. GraphQL and OpenAPI require `database.schema`.
757
+ When `openapi` is configured, its document describes auth routes, optional Bearer authentication on record reads, required authentication on record changes and the `actions` response field. In Swagger UI, paste a token from login into **Authorize**. For schema exports, enable auth in the configuration and run `npx deep-json-server server.config.mjs --generate-only`; the users file is not read during generation. Auth methods are exposed through REST. GraphQL checks the same token when reading permissions or changing records. GraphQL and OpenAPI require `database.schema`.
721
758
 
722
759
  ## Record dates, deletion and ownership
723
760
 
@@ -758,7 +795,7 @@ Precedence: model → schema root → `false`. Explicit `false` disables an inhe
758
795
 
759
796
  Dates are UTC ISO 8601 strings. New records receive the same creation and update time and, with auth, the authenticated user's ID as creator and editor. PUT/PATCH preserve the creator and creation time. DELETE updates the deletion fields and the enabled last-update fields. Existing records with unknown dates or authors expose `null`. These fields are read-only in REST and GraphQL; embedded plain objects do not receive their own audit fields. Previously stored audit values are retained when their features are disabled.
760
797
 
761
- With soft deletion, DELETE retains the record in the database. Repeating DELETE on an already deleted record leaves its deletion details unchanged. Fetching by primary key returns deleted records too. A successful PUT/PATCH restores the record by clearing `deletedAt` and `deletedById`. An empty PATCH restores it without replacing other fields; PUT requires all mandatory fields.
798
+ With soft deletion, DELETE retains the record in the database. Repeating DELETE on an already deleted record leaves its deletion details unchanged. Fetching by primary key returns deleted records too. A successful PUT/PATCH restores the record by clearing `deletedAt` and `deletedById`. An empty PATCH restores it without replacing other fields; PUT follows the usual replacement rules.
762
799
 
763
800
  Lists return active records by default. To select deleted records, specify `deletedAt` in `where`:
764
801
 
@@ -775,7 +812,11 @@ Cascade deletion follows `onDelete` and each affected model's `softDelete`. Rest
775
812
 
776
813
  Restoration metadata is stored with the records and survives restarts; include it when backing up the database. The internal `djsDeletion` field is reserved and is not exposed by either API.
777
814
 
778
- With auth, any authenticated user can create records. Updating, deleting and restoring require ownership through `createdById` or `isAdmin: true`. Only administrators can change unowned records. Administrator edits preserve the original owner. The rules cover nested writes, changes to relation storage keys, cascades and restoration. Linking a record through a primary-key-only object or a declared storage key does not require owning the unchanged target. A nested object with additional fields requires permission to update it. Changing inverse links still requires permission for every record whose stored key changes. Each mutation is atomic: denied changes leave all affected records unchanged.
815
+ With auth, any authenticated user can create records. Updating, deleting and restoring require ownership through `createdById` or `isAdmin: true`. Only administrators can change unowned records. Administrator edits preserve the original owner. The rules cover nested writes, changes to relation storage keys, cascades and restoration.
816
+
817
+ Linking a record through a primary-key-only object or a declared storage key does not require owning the unchanged target. A nested object with additional fields requires permission to update it. Changing inverse links still requires permission for every record whose stored key changes.
818
+
819
+ Each record change, including its nested writes, is atomic: a permission failure leaves all records affected by that change unchanged. In GraphQL, this applies to each mutation field; see [GraphQL](#graphql) for operations with several mutation fields.
779
820
 
780
821
  REST returns 401 for an invalid or missing token and 403 for insufficient permissions. GraphQL applies the same rules to mutations and returns `UNAUTHENTICATED` or `FORBIDDEN`; obtain the token through REST login and send `Authorization: Bearer <token>`. GraphQL reads remain public, as do OPTIONS requests and every file operation.
781
822
 
@@ -794,7 +835,7 @@ export default {
794
835
  Start the server:
795
836
 
796
837
  ```bash
797
- npx deep-json-server server.config.js
838
+ npx deep-json-server server.config.mjs
798
839
  ```
799
840
 
800
841
  For temporary tests, choose `storage: 'memory'` and pass an array in `files.source`. Each initial record contains `name`, `mimeType`, binary `content` as a `Uint8Array`, and an optional `directory`. Uploaded files then remain in memory until the process exits.
@@ -854,7 +895,7 @@ Content-Type: application/json
854
895
 
855
896
  In disk mode, the binary is stored at `<files.source>/<directory>/<name>`. Metadata defaults to `<files.source>/.files.json`; set `files.metadata` for another location. Directories and the metadata file are created when needed.
856
897
 
857
- Use one server process per writable disk source. A second server sharing the database, auth users, file storage directory or metadata fails at startup. Locks are single JSON files next to their sources: `database.json` uses `database-lock.json`, with the last extension removed from the configured source name. A lock records its type, process ID and owner token. Closing the server removes the lock files; after a crash the operating system releases the locks and the next server reuses the abandoned files. Disk locking uses the native `fs-native-extensions` dependency; memory storage and schema generation do not load it. Symbolic-link sources lock both the original entry and its target. Stop the server before editing stored files or metadata manually. Paths inside the file storage directory cannot contain symbolic links. Uploads and renames cannot overwrite the database, its lock, counters, schema, auth users, loaded configuration or metadata file.
898
+ Paths inside the file storage directory cannot contain symbolic links. Uploads and renames cannot overwrite the database, its lock, counters, schema, auth users, loaded configuration or metadata file. Stop the server before editing stored files or metadata manually. Shared disk sources are protected by the locks described under [data storage](#data-storage).
858
899
 
859
900
  Send the file as a binary request body. In a browser, use `xhr.send(file)` and track progress through `XMLHttpRequest.upload.onprogress`. The default maximum size is 100 MiB and can be changed through `server.maxFileSize`. Missing or unsafe headers and paths return `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.
860
901
 
@@ -862,7 +903,7 @@ Send the file as a binary request body. In a browser, use `xhr.send(file)` and t
862
903
 
863
904
  ```js
864
905
  import { createServer } from '@kollors/deep-json-server/server';
865
- import config from './server.config.js';
906
+ import config from './server.config.mjs';
866
907
 
867
908
  const facade = await createServer(config);
868
909
  const server = facade.fastify();
@@ -870,9 +911,9 @@ await server.listen();
870
911
  // await server.close();
871
912
  ```
872
913
 
873
- The `openapi()` and `graphql()` methods return schemas and require `database.schema`. `fastify()` returns the server instance for configuration and startup. The database and enabled services initialize on `ready()`, `listen()` or the first `inject()`; initialization errors stop startup.
914
+ `fastify()` returns the server instance for configuration and startup. The database and enabled services initialize on `ready()`, `listen()` or the first `inject()`; initialization errors stop startup.
874
915
 
875
- `createServer(config)` takes the same configuration object as the CLI. When `openapi` is configured, `package.source` is required. The `openapi()` and `graphql()` methods require their respective sections. They return schemas without writing files.
916
+ `createServer(config)` takes the same configuration object as the CLI. The `openapi()` and `graphql()` methods require `database.schema` and their respective configuration sections. OpenAPI also requires `package.source`. Both methods return schema documents without writing files.
876
917
 
877
918
  The returned Fastify instance accepts standard `listen()` options, including a Unix socket path. Calling `listen()` without options uses `server.host` and `server.port`.
878
919
 
@@ -890,11 +931,15 @@ await writeGraphql(sdl, './generated/schema.graphql');
890
931
 
891
932
  Standalone generators accept a schema path or object without a server configuration. If root `api` is omitted, `generateOpenapi()` defaults it to REST and `generateGraphql()` to GraphQL. Explicit root and model `api` values still apply; the selected format must have an enabled model. Timestamps and soft deletion come from the schema. `{ auth: true }` adds ownership and `actions` fields; OpenAPI also describes auth routes and token requirements. `hashPassword()` is available from the root package.
892
933
 
893
- `generateOpenapi()` requires `packagePath` and also accepts `host`, `port`, `pageSize` and `maxPageSize`. It reads OpenAPI metadata from that package. Pass a schema object instead of a path if preferred. Servers and generators use their own copy of the model. Pagination sizes must be positive integers; `pageSize` cannot exceed `maxPageSize`.
934
+ `generateOpenapi()` requires `packagePath` and also accepts `host`, `port`, `pageSize` and `maxPageSize`. It reads OpenAPI metadata from that package. Servers and generators use their own copy of the model. Pagination sizes must be positive integers; `pageSize` cannot exceed `maxPageSize`.
894
935
 
895
936
  ## Data storage
896
937
 
897
- Updates run sequentially within one server instance and are validated on a copy of the data before saving. Use one server process per database file. `increment` counters are stored next to the database in `<database path>.counters.json`; keep that file with the database. Numbers are reserved before the data write, so a failed write can leave gaps but cannot reuse a reserved number.
938
+ Updates run sequentially within one server instance and are validated on a copy of the data before saving. `increment` counters are stored next to the database in `<database path>.counters.json`; keep that file with the database. Numbers are reserved before the data write, so a failed write can leave gaps but cannot reuse a reserved number.
939
+
940
+ Use one server process per writable disk source. A second server sharing the database, auth users, file storage directory or metadata fails at startup. Each lock is a JSON file next to its source: `database.json` uses `database-lock.json`. The name drops the source's last extension and adds `-lock.json`. The file records the lock type, process ID and owner token.
941
+
942
+ Closing the server removes the lock files. After a crash, the operating system releases the locks and the next server reuses the abandoned files. Disk locking uses the native `fs-native-extensions` dependency; memory storage and schema generation do not load it. For a symbolic-link source, the server locks both the link path and its target. Stop the server before editing any stored source manually.
898
943
 
899
944
  ## Development
900
945
 
@@ -905,8 +950,10 @@ npm ci
905
950
  npm run verify
906
951
  ```
907
952
 
908
- `npm run verify` checks types, code style, test coverage and installation from the package archive.
953
+ `npm run verify` checks runtime dependencies with `npm audit`, then runs type checks, lint, coverage-gated tests and installation checks against the package archive. The installed-package checks cover public types, CLI startup over HTTP and recovery after a process crash.
954
+
955
+ For a prerelease, update the version in `package.json` and `package-lock.json`, then push the commit to `main`. For a stable release, also push its `v<version>` tag, such as `v1.0.0`. The runtime and CLI read the version from package metadata.
909
956
 
910
- To publish a prerelease, update the version in `package.json` and `package-lock.json` (the runtime and CLI read it from package metadata), then push the commit to `main`. GitHub Actions creates its `v<version>` tag and publishes through trusted publishing to the `alpha`, `beta` or `rc` channel. Stable versions publish to `latest` from an explicitly pushed version tag.
957
+ GitHub Actions runs the full verification on Linux, Windows and macOS with Node.js 22, 24 and 26. Publication starts only when all nine jobs pass. Prereleases receive a version tag automatically and publish to `alpha`, `beta` or `rc`; stable versions publish to `latest`. npm authenticates the workflow through trusted publishing, so it does not need a stored npm token.
911
958
 
912
959
  License: MIT.