@kollors/deep-json-server 1.0.0-alpha.8 → 1.0.0-beta.1

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.
Files changed (112) hide show
  1. package/README.md +279 -217
  2. package/README.ru.md +282 -220
  3. package/dist/index.d.ts +1 -2
  4. package/dist/src/auth/contract.d.ts +3 -2
  5. package/dist/src/auth/contract.js.map +1 -1
  6. package/dist/src/auth/service.js +1 -1
  7. package/dist/src/auth/service.js.map +1 -1
  8. package/dist/src/auth/store.d.ts +1 -1
  9. package/dist/src/auth/store.js.map +1 -1
  10. package/dist/src/cli/index.d.ts +2 -2
  11. package/dist/src/cli/index.js +59 -80
  12. package/dist/src/cli/index.js.map +1 -1
  13. package/dist/src/core/constants.d.ts +1 -1
  14. package/dist/src/core/constants.js +1 -1
  15. package/dist/src/core/constants.js.map +1 -1
  16. package/dist/src/core/database.d.ts +6 -11
  17. package/dist/src/core/database.js +2 -2
  18. package/dist/src/core/database.js.map +1 -1
  19. package/dist/src/core/engine.d.ts +3 -15
  20. package/dist/src/core/engine.js +10 -69
  21. package/dist/src/core/engine.js.map +1 -1
  22. package/dist/src/core/lifecycle/mutation.js +4 -4
  23. package/dist/src/core/lifecycle/mutation.js.map +1 -1
  24. package/dist/src/core/lifecycle/options.d.ts +15 -0
  25. package/dist/src/core/lifecycle/options.js +13 -0
  26. package/dist/src/core/lifecycle/options.js.map +1 -1
  27. package/dist/src/core/model.d.ts +17 -2
  28. package/dist/src/core/model.js +28 -7
  29. package/dist/src/core/model.js.map +1 -1
  30. package/dist/src/core/mutations/write.js +4 -0
  31. package/dist/src/core/mutations/write.js.map +1 -1
  32. package/dist/src/core/paths.d.ts +1 -1
  33. package/dist/src/core/paths.js +5 -5
  34. package/dist/src/core/paths.js.map +1 -1
  35. package/dist/src/core/query/execute.d.ts +25 -0
  36. package/dist/src/core/query/execute.js +82 -0
  37. package/dist/src/core/query/execute.js.map +1 -0
  38. package/dist/src/core/query/filter.js +1 -1
  39. package/dist/src/core/query/filter.js.map +1 -1
  40. package/dist/src/core/query/options.js +2 -2
  41. package/dist/src/core/query/options.js.map +1 -1
  42. package/dist/src/core/records.d.ts +2 -1
  43. package/dist/src/core/records.js +5 -2
  44. package/dist/src/core/records.js.map +1 -1
  45. package/dist/src/core/storage.d.ts +2 -0
  46. package/dist/src/core/storage.js +2 -0
  47. package/dist/src/core/storage.js.map +1 -0
  48. package/dist/src/files/contract.d.ts +6 -7
  49. package/dist/src/files/contract.js.map +1 -1
  50. package/dist/src/files/disk-metadata.d.ts +9 -0
  51. package/dist/src/files/disk-metadata.js +44 -0
  52. package/dist/src/files/disk-metadata.js.map +1 -0
  53. package/dist/src/files/disk-paths.d.ts +15 -0
  54. package/dist/src/files/disk-paths.js +100 -0
  55. package/dist/src/files/disk-paths.js.map +1 -0
  56. package/dist/src/files/disk-store.js +21 -165
  57. package/dist/src/files/disk-store.js.map +1 -1
  58. package/dist/src/files/index.d.ts +1 -1
  59. package/dist/src/files/index.js +5 -2
  60. package/dist/src/files/index.js.map +1 -1
  61. package/dist/src/files/memory-store.js +3 -3
  62. package/dist/src/files/memory-store.js.map +1 -1
  63. package/dist/src/graphql/generate.d.ts +3 -2
  64. package/dist/src/graphql/generate.js +3 -1
  65. package/dist/src/graphql/generate.js.map +1 -1
  66. package/dist/src/graphql/preflight.js +2 -0
  67. package/dist/src/graphql/preflight.js.map +1 -1
  68. package/dist/src/graphql/resolvers.js +7 -3
  69. package/dist/src/graphql/resolvers.js.map +1 -1
  70. package/dist/src/graphql/routes.js +4 -1
  71. package/dist/src/graphql/routes.js.map +1 -1
  72. package/dist/src/graphql/schema.js +5 -2
  73. package/dist/src/graphql/schema.js.map +1 -1
  74. package/dist/src/openapi/document.js +30 -19
  75. package/dist/src/openapi/document.js.map +1 -1
  76. package/dist/src/openapi/generate.js +3 -1
  77. package/dist/src/openapi/generate.js.map +1 -1
  78. package/dist/src/openapi/options.d.ts +0 -2
  79. package/dist/src/openapi/registry.d.ts +11 -0
  80. package/dist/src/openapi/registry.js +20 -0
  81. package/dist/src/openapi/registry.js.map +1 -0
  82. package/dist/src/rest/options.d.ts +11 -3
  83. package/dist/src/rest/options.js +15 -0
  84. package/dist/src/rest/options.js.map +1 -1
  85. package/dist/src/rest/projection.d.ts +15 -3
  86. package/dist/src/rest/projection.js +49 -15
  87. package/dist/src/rest/projection.js.map +1 -1
  88. package/dist/src/rest/routes.js +18 -7
  89. package/dist/src/rest/routes.js.map +1 -1
  90. package/dist/src/server/bootstrap.d.ts +7 -0
  91. package/dist/src/server/bootstrap.js +48 -0
  92. package/dist/src/server/bootstrap.js.map +1 -0
  93. package/dist/src/server/config.d.ts +28 -30
  94. package/dist/src/server/config.js +82 -169
  95. package/dist/src/server/config.js.map +1 -1
  96. package/dist/src/server/create.d.ts +3 -5
  97. package/dist/src/server/create.js +24 -89
  98. package/dist/src/server/create.js.map +1 -1
  99. package/dist/src/server/features.d.ts +0 -11
  100. package/dist/src/server/features.js +0 -20
  101. package/dist/src/server/features.js.map +1 -1
  102. package/dist/src/server/http.d.ts +12 -0
  103. package/dist/src/server/http.js +40 -0
  104. package/dist/src/server/http.js.map +1 -0
  105. package/dist/src/server/model.d.ts +6 -0
  106. package/dist/src/server/model.js +14 -0
  107. package/dist/src/server/model.js.map +1 -0
  108. package/dist/src/server/openapi-options.d.ts +6 -0
  109. package/dist/src/server/openapi-options.js +15 -0
  110. package/dist/src/server/openapi-options.js.map +1 -0
  111. package/dist/src/server/public.d.ts +1 -2
  112. package/package.json +3 -3
package/README.md CHANGED
@@ -4,15 +4,15 @@
4
4
 
5
5
  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.
6
6
 
7
- **1.0.0-alpha.8 is a prerelease.** REST queries use `scope=[fields, arguments?]` at every level. When upgrading from an earlier version, update query parameters using the examples below; upgrading from 0.x also requires the new model schema.
7
+ **Breaking changes: 1.0.0-beta.1.** REST `scope` wildcard now selects only scalar fields. Arrays, objects and relations must be selected explicitly. With auth enabled, record permissions are available through the virtual `actions` field.
8
8
 
9
9
  ## Installation
10
10
 
11
11
  ```sh
12
- npm install @kollors/deep-json-server@alpha
12
+ npm install @kollors/deep-json-server@beta
13
13
  ```
14
14
 
15
- To install a specific version, use `@1.0.0-alpha.8`.
15
+ To install a specific version, use `@1.0.0-beta.1`.
16
16
 
17
17
  ## Quick start
18
18
 
@@ -31,9 +31,7 @@ Create two files in the same directory.
31
31
  `server.config.js`:
32
32
 
33
33
  ```js
34
- export default {
35
- database: { path: './database.json' },
36
- };
34
+ export default { storage: 'file', database: { source: './database.json' } };
37
35
  ```
38
36
 
39
37
  ```sh
@@ -46,47 +44,53 @@ Add a [model schema](#model-schema) to define relations and validation. See [que
46
44
 
47
45
  ## Configuration
48
46
 
47
+ With `storage: 'file'`, all sources and the schema are paths. With `'memory'`, they are in-memory data. Modes cannot be mixed. The presence of `auth`, `files`, `graphql` and `openapi` enables those modules. `graphql: {}` and `openapi: {}` enable only the HTTP endpoints at their defaults; add `target` to export a schema.
48
+
49
+ ```js
50
+ export default {
51
+ storage: 'file',
52
+ database: { source: './database.json', schema: './schema.json' },
53
+ auth: { source: './users.json', expiresIn: 3600 },
54
+ files: { source: './uploads' },
55
+ graphql: { target: './generated/schema.graphql' },
56
+ openapi: { target: './generated/openapi.yaml' },
57
+ server: { host: '127.0.0.1', port: 4001 },
58
+ };
59
+ ```
60
+
49
61
  | Setting | Meaning |
50
62
  |---|---|
51
- | `database.path` / `database.data` | Choose one to start the server: a JSON file or an in-memory collection object |
52
- | `database.schema` | Model object or JSON schema-file path; optional for REST |
53
- | `database.timestamps` | Add creation and update times; default `false` |
54
- | `database.softDelete` | Mark records as deleted and allow restoration; default `false` |
55
- | `openapi.enabled` | Enable the specification endpoint; default `false` |
56
- | `openapi.endpoint` | Specification path; default `/openapi.json` |
57
- | `openapi.path` | YAML export destination |
58
- | `openapi.info` | Optional metadata object: required `title` and `version`, optional `description` |
59
- | `graphql.enabled` | Enable GraphQL HTTP endpoint; default `false` |
60
- | `graphql.endpoint` | Endpoint path; default `/graphql` |
61
- | `graphql.path` | GraphQL SDL export destination |
62
- | `auth.users` | Path to a JSON array of auth users, or an in-memory array |
63
+ | `storage` | Required: `file` or `memory`; applies to all sources and the schema |
64
+ | `database.source` | Database JSON path or collection object |
65
+ | `database.schema` | Schema JSON path or schema object; optional for REST |
66
+ | `auth.source` | Users JSON path or user array |
63
67
  | `auth.expiresIn` | Session lifetime in seconds; default 3600 |
68
+ | `files.source` | Files directory or initial file array |
69
+ | `files.metadata` | For `file` only: metadata JSON path; defaults to `.files.json` inside `files.source` |
70
+ | `graphql.endpoint` | HTTP endpoint; default `/graphql` |
71
+ | `graphql.target` | GraphQL SDL export destination |
72
+ | `openapi.endpoint` | HTTP endpoint; default `/openapi.json` |
73
+ | `openapi.target` | OpenAPI export destination |
74
+ | `openapi.info` | Metadata: required `title` and `version`, optional `description` |
64
75
  | `server.host`, `server.port` | Defaults `127.0.0.1`, `4001`; CLI also reads `HOST`/`PORT` |
65
76
  | `server.pageSize`, `server.maxPageSize` | Defaults 10 and 100; default size is capped by the maximum |
66
77
  | `server.cors`, `server.logger` | Default `true`; logger also accepts Fastify logger options |
67
78
  | `server.maxFileSize` | Default 100 MiB |
68
- | `files.data` | In-memory binary files |
69
- | `files.directory`, `files.metadata` | Disk storage directory and metadata JSON file; both required |
70
-
71
- Relative paths resolve from the configuration file's directory. When passing a configuration object to `createServer()`, paths resolve from the working directory. The server works with a copy of in-memory input and metadata.
72
79
 
73
- Set `server.port` to `0` to let the operating system choose an available port. The OpenAPI endpoint uses a relative server URL.
80
+ Relative paths resolve from the configuration file directory, or from the working directory with `createServer(config)`. In-memory data, including the schema, is copied. Port `0` lets the system choose an available port.
74
81
 
75
82
  ### CLI
76
83
 
77
- | CLI flag | Action |
84
+ | Flag | Action |
78
85
  |---|---|
79
- | `--files` | Enable file routes; requires `files` configuration |
80
- | `--timestamps` | Enable record timestamps globally |
81
- | `--soft-delete` | Enable soft deletion globally |
82
- | `--graphql` | Enable the GraphQL API |
83
- | `--openapi` | Enable the OpenAPI endpoint |
86
+ | `--generate` | Export schemas, then start the server |
87
+ | `--generate-only` | Export schemas and exit |
84
88
  | `--host <host>` | Server address |
85
89
  | `--port <port>` | Server port |
86
- | `--help`, `-h` | Show help |
87
- | `--version`, `-v` | Show package version |
90
+ | `--help, -h` | Help |
91
+ | `--version, -v` | Package version |
88
92
 
89
- Host and port priority: CLI → configuration → `HOST`/`PORT` → defaults. The `files` section enables file routes; `auth` enables login and permission checks. Model settings can override global `timestamps` and `softDelete` values.
93
+ Address precedence: CLI → configuration → `HOST`/`PORT` → defaults. Without generation flags, only the server starts. `--generate` and `--generate-only` are mutually exclusive.
90
94
 
91
95
  ## Model schema
92
96
 
@@ -94,28 +98,52 @@ Examples: [database](examples/database.json), [model schema](examples/schema.jso
94
98
 
95
99
  ```json
96
100
  {
97
- "Country": {
98
- "collection": "countries",
99
- "api": ["openapi", "graphql"],
100
- "fields": {
101
- "id": { "type": "string", "primary": true, "generated": "uuid" },
102
- "name": { "type": "string", "required": true },
103
- "users": { "type": "User[]", "target": "countryId" }
104
- }
105
- },
106
- "User": {
107
- "collection": "users",
108
- "api": ["openapi", "graphql"],
109
- "fields": {
110
- "id": { "type": "string", "primary": true, "generated": "uuid" },
111
- "fullName": { "type": "string", "required": true },
112
- "country": { "type": "Country", "source": "countryId" }
101
+ "api": [
102
+ "openapi",
103
+ "graphql"
104
+ ],
105
+ "models": {
106
+ "Country": {
107
+ "collection": "countries",
108
+ "fields": {
109
+ "id": {
110
+ "type": "string",
111
+ "primary": true,
112
+ "generated": "uuid"
113
+ },
114
+ "name": {
115
+ "type": "string",
116
+ "required": true
117
+ },
118
+ "users": {
119
+ "type": "User[]",
120
+ "target": "countryId"
121
+ }
122
+ }
123
+ },
124
+ "User": {
125
+ "collection": "users",
126
+ "fields": {
127
+ "id": {
128
+ "type": "string",
129
+ "primary": true,
130
+ "generated": "uuid"
131
+ },
132
+ "fullName": {
133
+ "type": "string",
134
+ "required": true
135
+ },
136
+ "country": {
137
+ "type": "Country",
138
+ "source": "countryId"
139
+ }
140
+ }
113
141
  }
114
142
  }
115
143
  }
116
144
  ```
117
145
 
118
- `api` defaults to `["openapi", "graphql"]`. `[]` excludes the model from both exports and GraphQL, while REST remains available. Related models must enable the same export format. Names must be valid identifiers and generated type/operation names must not collide. `and`, `or`, `not` are reserved filter names.
146
+ Model definitions belong in `models`. The schema root can define `api`, `timestamps` and `softDelete`; a model can override each setting. API precedence is model → schema root → configuration sections. Arrays replace the inherited value; `[]` excludes a model from GraphQL and OpenAPI while REST remains available. An explicit list does not activate an absent module. Related models must allow the same format. Model names must be valid identifiers; type and operation collisions cause errors. `and`, `or` and `not` are reserved filter names.
119
147
 
120
148
  | Capability | With schema | Without schema |
121
149
  |---|---|---|
@@ -126,7 +154,7 @@ Examples: [database](examples/database.json), [model schema](examples/schema.jso
126
154
 
127
155
  Explicit schemas are strict: undeclared fields and collections are rejected, except storage keys inferred from relations. Existing data is validated on startup. Generation uses the model definitions.
128
156
 
129
- Schemaless REST generates an `id` and preserves arbitrary JSON fields. Filters and individual field selections use identifier-style names; other fields are returned through `scope=[{"*":true}]`. Fields with mixed value types can be read, but filtering, ordering and paging heterogeneous lists require an explicit schema.
157
+ Schemaless REST generates an `id` and preserves arbitrary JSON fields. Filters and individual field selections use identifier-style names. `scope=[{"*":true}]` returns top-level JSON scalars; arrays and objects must be selected by name. Fields with mixed value types can be read explicitly, but filtering, ordering and paging heterogeneous lists require an explicit schema.
130
158
 
131
159
  Each model requires `collection`, the database collection and REST path name, and `fields`, its field definitions. The model name (`User`) determines GraphQL type and operation names. `api` controls format availability; `timestamps` and `softDelete` override global settings for that model.
132
160
 
@@ -184,6 +212,110 @@ Every supplied direct relation key must point to an existing record. `required:
184
212
 
185
213
  Cascading deletion runs as one operation, including cyclic relations. A validation failure cancels the entire operation. `onDelete` rules also apply to explicitly declared reverse relations; account for both rules when defining both directions.
186
214
 
215
+ ## Example database
216
+
217
+ ```json
218
+ {
219
+ "countries": [
220
+ {
221
+ "id": "1",
222
+ "isArchived": false,
223
+ "name": "Ардения"
224
+ },
225
+ {
226
+ "id": "2",
227
+ "isArchived": false,
228
+ "name": "Велория"
229
+ }
230
+ ],
231
+ "genres": [
232
+ {
233
+ "id": "1",
234
+ "isArchived": false,
235
+ "name": "Криминал",
236
+ "parentIds": []
237
+ },
238
+ {
239
+ "id": "2",
240
+ "isArchived": false,
241
+ "name": "Гангстер",
242
+ "parentIds": ["1"]
243
+ },
244
+ {
245
+ "id": "3",
246
+ "isArchived": false,
247
+ "name": "Драма",
248
+ "parentIds": []
249
+ },
250
+ {
251
+ "id": "4",
252
+ "isArchived": false,
253
+ "name": "Комедия",
254
+ "parentIds": []
255
+ }
256
+ ],
257
+ "movies": [
258
+ {
259
+ "actors": [
260
+ {
261
+ "genreIds": ["2", "3"],
262
+ "id": "movie-1-actor-1",
263
+ "userId": "1"
264
+ },
265
+ {
266
+ "genreIds": ["3"],
267
+ "id": "movie-1-actor-2",
268
+ "userId": "2"
269
+ }
270
+ ],
271
+ "coverSrc": "https://example.com/covers/shadows-of-ardenia.jpg",
272
+ "description": "Наследница портового города раскрывает заговор двух соперничающих семей.",
273
+ "id": "1",
274
+ "isArchived": false,
275
+ "publisherIds": ["2"],
276
+ "title": "Тени Ардении"
277
+ },
278
+ {
279
+ "actors": [],
280
+ "coverSrc": "https://example.com/covers/northern-star.jpg",
281
+ "description": "Ночной администратор старого отеля случайно становится участником поисков пропавшей картины.",
282
+ "id": "2",
283
+ "isArchived": false,
284
+ "publisherIds": ["1"],
285
+ "title": "Полночь в «Северной звезде»"
286
+ }
287
+ ],
288
+ "publishers": [
289
+ {
290
+ "id": "1",
291
+ "isArchived": false,
292
+ "name": "Northlight Studio"
293
+ },
294
+ {
295
+ "id": "2",
296
+ "isArchived": false,
297
+ "name": "Aurora Pictures"
298
+ }
299
+ ],
300
+ "users": [
301
+ {
302
+ "bornAt": "1988-03-14",
303
+ "countryId": "1",
304
+ "fullName": "Мира Волкова",
305
+ "id": "1",
306
+ "isArchived": false
307
+ },
308
+ {
309
+ "bornAt": "1991-11-02",
310
+ "countryId": "2",
311
+ "fullName": "Леон Ветров",
312
+ "id": "2",
313
+ "isArchived": false
314
+ }
315
+ ]
316
+ }
317
+ ```
318
+
187
319
  ## Queries and responses
188
320
 
189
321
  The movie, actor and genre examples use the full [example schema](examples/schema.json). Run them with the [example configuration](examples/server.config.js):
@@ -252,10 +384,7 @@ const scope = [
252
384
  fullName: true,
253
385
  movies: [
254
386
  { id: true, title: true },
255
- {
256
- order: [{ field: 'title', direction: 'ASC' }],
257
- pager: { page: 1, pageSize: 5 },
258
- },
387
+ { order: [{ field: 'title', direction: 'ASC' }], pager: { page: 1, pageSize: 5 } },
259
388
  ],
260
389
  },
261
390
  {
@@ -268,7 +397,7 @@ const params = new URLSearchParams({ scope: JSON.stringify(scope) });
268
397
  const response = await fetch(`/users?${params}`);
269
398
  ```
270
399
 
271
- Select ordinary fields with `true` and objects or relations with their own scope arrays. Without arguments, the array contains only the fields object. `"*": true` includes own fields and stored keys, except `writeOnly` fields; select relations explicitly.
400
+ 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 scalar fields only. Arrays, objects, relations and `writeOnly` fields are not included by the wildcard.
272
401
 
273
402
  For example, select a movie's own fields, its actors' users and sorted genres:
274
403
 
@@ -289,9 +418,9 @@ For example, select a movie's own fields, its actors' users and sorted genres:
289
418
  ]
290
419
  ```
291
420
 
292
- Omitting `scope` returns own fields, as with `[{"*":true}]`. An empty selection `[{}]` returns an object without fields. Lists retain the `{ data, total }` response structure.
421
+ Omitting `scope` returns scalar fields, as with `[{"*":true}]`. An empty selection `[{}]` returns an object without fields. Lists retain the `{ data, total }` response structure.
293
422
 
294
- Arguments are available only on lists. 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.
423
+ 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. This also works for nested lists. 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.
295
424
 
296
425
  ### Nested writes
297
426
 
@@ -346,10 +475,10 @@ mutation {
346
475
 
347
476
  ### GraphQL
348
477
 
349
- Set `database.schema` and enable `graphql.enabled: true` in the configuration, or start the server with `--graphql`:
478
+ Set `database.schema` and add `graphql: {}` to the configuration:
350
479
 
351
480
  ```sh
352
- npx deep-json-server --graphql server.config.js
481
+ npx deep-json-server server.config.js
353
482
  ```
354
483
 
355
484
  Send requests to `/graphql` using POST with `Content-Type: application/json` and a body of `{ "query": "…", "variables": {} }`. Change the path through `graphql.endpoint`.
@@ -386,29 +515,27 @@ Errors include `extensions.code`: `INVALID_INPUT`, `INVALID_QUERY`, `NOT_FOUND`,
386
515
 
387
516
  ## OpenAPI and schema exports
388
517
 
389
- Exports use OpenAPI 3.0.3. The purpose of each `scope` array position is described in text; the server validates their order.
390
-
391
- To serve the specification over HTTP, set `database.schema` and enable `openapi.enabled: true`, or start the server with `--openapi`. The JSON document is available at `/openapi.json` by default; change the path through `openapi.endpoint`. Open it in a separately installed Swagger UI or import it into an API client.
392
-
393
- To generate schemas, specify the format and configuration file:
394
-
395
- ```sh
396
- npx deep-json-server generate openapi server.config.js
397
- npx deep-json-server generate graphql server.config.js
398
- npx deep-json-server generate openapi,graphql server.config.js
399
- ```
518
+ Exports use OpenAPI 3.0.3. Add `openapi: {}` and `database.schema` to serve the specification at `/openapi.json`. Change the route with `openapi.endpoint`. Open the document in Swagger UI or import it into an API client.
400
519
 
401
- Generation runs without starting the server or reading database records. The command reads `database.schema` and writes schemas to `openapi.path` and `graphql.path`. The `enabled` flags control HTTP endpoints and are not required for exports. A configuration for generation only can contain:
520
+ Set output paths to save schemas:
402
521
 
403
522
  ```js
404
523
  export default {
405
- database: { schema: './schema.json' },
406
- openapi: { path: './generated/openapi.yaml' },
407
- graphql: { path: './generated/schema.graphql' },
524
+ storage: 'file',
525
+ database: { source: './database.json', schema: './schema.json' },
526
+ openapi: { target: './generated/openapi.yaml' },
527
+ graphql: { target: './generated/schema.graphql' },
408
528
  };
409
529
  ```
410
530
 
411
- Each format needs its own output file. The command rejects destinations that would overwrite the configuration, database, schema, auth users or file metadata.
531
+ ```bash
532
+ npx deep-json-server server.config.js --generate-only
533
+ npx deep-json-server server.config.js --generate
534
+ ```
535
+
536
+ `--generate-only` exports and exits; `--generate` starts the server after exporting. Configuration sections select the formats. Each selected format requires its own `target`. Missing sections, missing targets or generation errors fail the command before server startup.
537
+
538
+ 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.
412
539
 
413
540
  ## Authentication
414
541
 
@@ -422,23 +549,30 @@ import { hashPassword } from '@kollors/deep-json-server/auth';
422
549
 
423
550
  const password = process.env.DJS_PASSWORD;
424
551
  if (!password) throw new Error('Set DJS_PASSWORD');
425
- await writeFile('./auth.json', JSON.stringify([
426
- { id: '1', username: 'admin', passwordHash: await hashPassword(password), isAdmin: true },
427
- ], null, 2), { flag: 'wx', mode: 0o600 });
552
+ await writeFile(
553
+ './auth.json',
554
+ JSON.stringify(
555
+ [{ id: '1', username: 'admin', passwordHash: await hashPassword(password), isAdmin: true }],
556
+ null,
557
+ 2,
558
+ ),
559
+ { flag: 'wx', mode: 0o600 },
560
+ );
428
561
  ```
429
562
 
430
563
  Set `DJS_PASSWORD` and run `node setup-auth.mjs`. Add the file to your server configuration:
431
564
 
432
565
  ```js
433
566
  export default {
434
- database: { path: './database.json' },
435
- auth: { users: './auth.json', expiresIn: 3600 },
567
+ storage: 'file',
568
+ database: { source: './database.json' },
569
+ auth: { source: './auth.json', expiresIn: 3600 },
436
570
  };
437
571
  ```
438
572
 
439
573
  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`. Passwords use salted scrypt hashes.
440
574
 
441
- You can pass an array in `auth.users` instead of a path:
575
+ With `storage: 'memory'`, pass an array in `auth.source`:
442
576
 
443
577
  ```js
444
578
  import { hashPassword } from '@kollors/deep-json-server/auth';
@@ -447,16 +581,17 @@ const password = process.env.DJS_PASSWORD;
447
581
  if (!password) throw new Error('Set DJS_PASSWORD');
448
582
 
449
583
  export default {
450
- database: { data: { items: [] } },
584
+ storage: 'memory',
585
+ database: { source: { items: [] } },
451
586
  auth: {
452
- users: [
587
+ source: [
453
588
  { id: '1', username: 'admin', passwordHash: await hashPassword(password), isAdmin: true },
454
589
  ],
455
590
  },
456
591
  };
457
592
  ```
458
593
 
459
- Auth users are stored separately from database collections. With a string in `auth.users`, registration, password changes and admin status changes are saved to that JSON file through the same `lowdb` used by the main database. With an array, changes remain in an internal memory copy and disappear on restart; the original array is unchanged. This choice is independent of `database.path` or `database.data`. A file write failure leaves the user and active sessions unchanged. The file is read at startup; restart the server after editing it manually.
594
+ Auth users are stored separately from the database. In file mode, changes are saved to `auth.source`; in memory mode, they disappear on restart. The supplied user array is not modified. Restart the server after editing the file manually.
460
595
 
461
596
  | REST request | JSON body | Response |
462
597
  |---|---|---|
@@ -475,40 +610,70 @@ Only administrators can change `isAdmin`. They can grant or remove another user'
475
610
 
476
611
  Invalid or expired tokens return `401`, insufficient permissions return `403`, and an absent user for an otherwise permitted operation returns `404`. Invalid request bodies return `400`. Login, registration and password changes may return `429` when too many password computations are running; login also limits active sessions. Sessions are kept in memory and disappear on restart. Logout revokes only the supplied token.
477
612
 
478
- OpenAPI describes all auth routes and their Bearer token requirements. In Swagger UI, paste a token from login into **Authorize**. For schema exports, enable auth in the configuration and run `generate openapi server.config.js`; the users file is not read during generation. Auth methods are exposed through REST. GraphQL checks the same token when changing records. GraphQL and OpenAPI require `database.schema`.
613
+ Every model record has a virtual `actions` object when auth is enabled. It is calculated for the current user and is never stored. In REST, request it explicitly through `scope`:
479
614
 
480
- ## Record dates, deletion and ownership
615
+ ```json
616
+ [
617
+ {
618
+ "id": true,
619
+ "actions": [{ "*": true }]
620
+ }
621
+ ]
622
+ ```
481
623
 
482
- Configure defaults in `database`:
624
+ The object contains `update`, `replace` and `delete`. An owner or administrator receives `true`; an anonymous or unrelated user receives `false`. Records without `createdById` are editable only by administrators. A missing token keeps reads public and returns false flags. A supplied invalid token returns `401` in REST or `UNAUTHENTICATED` in GraphQL.
483
625
 
484
- ```js
485
- export default {
486
- database: {
487
- path: './database.json',
488
- schema: './schema.json',
489
- timestamps: true,
490
- softDelete: true,
491
- },
492
- auth: { users: './auth.json' },
493
- };
626
+ GraphQL exposes the same field and requires its normal explicit selection:
627
+
628
+ ```graphql
629
+ query {
630
+ itemList {
631
+ data {
632
+ id
633
+ actions {
634
+ update
635
+ replace
636
+ delete
637
+ }
638
+ }
639
+ }
640
+ }
494
641
  ```
495
642
 
496
- Each model can override `timestamps` and `softDelete` beside `collection` and `fields`. An omitted setting inherits the global value; `true` or `false` overrides it. CLI flags override global configuration, and model settings take priority over both. Without a schema, global settings apply to every collection. This model disables timestamps and keeps deleted records regardless of the global settings:
643
+ `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.
644
+
645
+ OpenAPI 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`.
646
+
647
+ ## Record dates, deletion and ownership
648
+
649
+ Set defaults at the schema root. Here `Note` inherits soft deletion and disables timestamps:
497
650
 
498
651
  ```json
499
652
  {
500
- "Note": {
501
- "collection": "notes",
502
- "timestamps": false,
503
- "softDelete": true,
504
- "fields": {
505
- "id": { "type": "string", "primary": true, "generated": "uuid" },
506
- "text": { "type": "string", "required": true }
653
+ "timestamps": true,
654
+ "softDelete": true,
655
+ "models": {
656
+ "Note": {
657
+ "collection": "notes",
658
+ "timestamps": false,
659
+ "fields": {
660
+ "id": {
661
+ "type": "string",
662
+ "primary": true,
663
+ "generated": "uuid"
664
+ },
665
+ "text": {
666
+ "type": "string",
667
+ "required": true
668
+ }
669
+ }
507
670
  }
508
671
  }
509
672
  }
510
673
  ```
511
674
 
675
+ Precedence: model → schema root → `false`. Explicit `false` disables an inherited setting. Without a schema, timestamps and soft deletion are disabled.
676
+
512
677
  | Field | Enabled by | Meaning |
513
678
  |---|---|---|
514
679
  | `createdAt`, `updatedAt` | `timestamps` | Creation and last update time |
@@ -545,8 +710,9 @@ Files are available through REST and documented in OpenAPI. Add storage to the c
545
710
 
546
711
  ```js
547
712
  export default {
548
- database: { path: './database.json' },
549
- files: { directory: './uploads', metadata: './files.json' },
713
+ storage: 'file',
714
+ database: { source: './database.json' },
715
+ files: { source: './uploads' },
550
716
  };
551
717
  ```
552
718
 
@@ -556,7 +722,7 @@ Start the server:
556
722
  npx deep-json-server server.config.js
557
723
  ```
558
724
 
559
- For temporary tests, use `files.data` instead. 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.
725
+ 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.
560
726
 
561
727
  Upload one file directly as the request body. `Content-Name` contains the URI-encoded file name, `Content-Type` contains its MIME type, and the optional `Content-Directory` contains the URI-encoded relative directory:
562
728
 
@@ -607,9 +773,9 @@ Content-Type: application/json
607
773
  }
608
774
  ```
609
775
 
610
- `PATCH` returns the updated metadata with status `200`; if a file already exists at the new path, the server returns `409`. `DELETE` returns `204` without a response body. A missing file returns `404` on every path-based operation. File paths in URLs are relative to `files.directory`, and all returned URLs are relative to the server origin.
776
+ `PATCH` returns the updated metadata with status `200`; if a file already exists at the new path, the server returns `409`. `DELETE` returns `204` without a response body. A missing file returns `404` on every path-based operation. File paths in URLs are relative to `files.source`, and all returned URLs are relative to the server origin.
611
777
 
612
- In disk mode, the binary is stored at `<files.directory>/<directory>/<name>`. Metadata stores `directory`, `mimeType` and `name`; the server reads the size from the file and builds its URLs. Directories and the metadata file are created when needed.
778
+ 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.
613
779
 
614
780
  Use one server process per disk database and file store. Stop it before editing stored files or metadata manually. Storage paths cannot contain symbolic links. Uploads and renames cannot overwrite the database, counters, schema, auth users, loaded configuration or metadata file.
615
781
 
@@ -629,7 +795,7 @@ await server.listen();
629
795
 
630
796
  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.
631
797
 
632
- The second argument overrides module settings, for example `createServer(config, { files: false, graphql: true })`. Supported flags are `files`, `graphql`, `openapi` and `auth`. Enabling auth or files requires the corresponding configuration section; GraphQL and OpenAPI require a model schema.
798
+ `createServer(config)` takes one argument. Configuration sections control modules exactly as in the CLI. The `openapi()` and `graphql()` methods require their respective sections. They return schemas without writing files.
633
799
 
634
800
  The root import `@kollors/deep-json-server` also provides these functions. Server adapters load when enabled. Generators can be used independently:
635
801
 
@@ -643,114 +809,10 @@ await writeOpenapi(document, './generated/openapi.yaml');
643
809
  await writeGraphql(sdl, './generated/schema.graphql');
644
810
  ```
645
811
 
646
- Both generators accept `timestamps`, `softDelete` and `auth` options for the record fields. With `{ auth: true }`, OpenAPI also includes REST auth routes and mutation security requirements. `hashPassword()` is also available from the root package.
812
+ Standalone generators accept a schema path or object without a server configuration. The selected function supplies the default format; schema and model `api` settings can restrict it. 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.
647
813
 
648
814
  `generateOpenapi()` also accepts `host`, `port`, `pageSize`, `maxPageSize` and `info`. 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`.
649
815
 
650
- ## Example database
651
-
652
- ```json
653
- {
654
- "countries": [
655
- {
656
- "id": "1",
657
- "isArchived": false,
658
- "name": "Ардения"
659
- },
660
- {
661
- "id": "2",
662
- "isArchived": false,
663
- "name": "Велория"
664
- }
665
- ],
666
- "genres": [
667
- {
668
- "id": "1",
669
- "isArchived": false,
670
- "name": "Криминал",
671
- "parentIds": []
672
- },
673
- {
674
- "id": "2",
675
- "isArchived": false,
676
- "name": "Гангстер",
677
- "parentIds": ["1"]
678
- },
679
- {
680
- "id": "3",
681
- "isArchived": false,
682
- "name": "Драма",
683
- "parentIds": []
684
- },
685
- {
686
- "id": "4",
687
- "isArchived": false,
688
- "name": "Комедия",
689
- "parentIds": []
690
- }
691
- ],
692
- "movies": [
693
- {
694
- "actors": [
695
- {
696
- "genreIds": ["2", "3"],
697
- "id": "movie-1-actor-1",
698
- "userId": "1"
699
- },
700
- {
701
- "genreIds": ["3"],
702
- "id": "movie-1-actor-2",
703
- "userId": "2"
704
- }
705
- ],
706
- "coverSrc": "https://example.com/covers/shadows-of-ardenia.jpg",
707
- "description": "Наследница портового города раскрывает заговор двух соперничающих семей.",
708
- "id": "1",
709
- "isArchived": false,
710
- "publisherIds": ["2"],
711
- "title": "Тени Ардении"
712
- },
713
- {
714
- "actors": [],
715
- "coverSrc": "https://example.com/covers/northern-star.jpg",
716
- "description": "Ночной администратор старого отеля случайно становится участником поисков пропавшей картины.",
717
- "id": "2",
718
- "isArchived": false,
719
- "publisherIds": ["1"],
720
- "title": "Полночь в «Северной звезде»"
721
- }
722
- ],
723
- "publishers": [
724
- {
725
- "id": "1",
726
- "isArchived": false,
727
- "name": "Northlight Studio"
728
- },
729
- {
730
- "id": "2",
731
- "isArchived": false,
732
- "name": "Aurora Pictures"
733
- }
734
- ],
735
- "users": [
736
- {
737
- "bornAt": "1988-03-14",
738
- "countryId": "1",
739
- "fullName": "Мира Волкова",
740
- "id": "1",
741
- "isArchived": false
742
- },
743
- {
744
- "bornAt": "1991-11-02",
745
- "countryId": "2",
746
- "fullName": "Леон Ветров",
747
- "id": "2",
748
- "isArchived": false
749
- }
750
- ]
751
- }
752
- ```
753
-
754
816
  ## Data storage
755
817
 
756
818
  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.
@@ -766,6 +828,6 @@ npm run verify
766
828
 
767
829
  The command checks types, code style, test coverage and installation from the package archive.
768
830
 
769
- To publish a new alpha, update the version in `package.json`, `package-lock.json` and `src/core/constants.ts`, then push to `main`. GitHub Actions creates the version tag and publishes to npm `alpha` through trusted publishing. Already published versions are skipped. If the tag exists but publication failed, a retry uses that tag and verifies that the package files match it. Pushing a version tag also triggers publication; stable versions publish to `latest`.
831
+ To publish a prerelease, update the version in `package.json`, `package-lock.json` and `src/core/constants.ts`, 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.
770
832
 
771
833
  License: MIT.