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

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 (124) hide show
  1. package/README.md +104 -53
  2. package/README.ru.md +103 -50
  3. package/dist/index.d.ts +5 -1
  4. package/dist/index.js +1 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/src/cli.d.ts +2 -2
  7. package/dist/src/cli.js +97 -48
  8. package/dist/src/cli.js.map +1 -1
  9. package/dist/src/config.d.ts +14 -1
  10. package/dist/src/config.js +56 -8
  11. package/dist/src/config.js.map +1 -1
  12. package/dist/src/constants.d.ts +1 -0
  13. package/dist/src/constants.js +1 -0
  14. package/dist/src/constants.js.map +1 -1
  15. package/dist/src/database.d.ts +0 -1
  16. package/dist/src/database.js +12 -10
  17. package/dist/src/database.js.map +1 -1
  18. package/dist/src/engine.d.ts +21 -10
  19. package/dist/src/engine.js +77 -144
  20. package/dist/src/engine.js.map +1 -1
  21. package/dist/src/errors.d.ts +6 -0
  22. package/dist/src/errors.js +9 -0
  23. package/dist/src/errors.js.map +1 -0
  24. package/dist/src/features.d.ts +8 -0
  25. package/dist/src/features.js +18 -0
  26. package/dist/src/features.js.map +1 -0
  27. package/dist/src/files/contract.d.ts +0 -30
  28. package/dist/src/files/contract.js +6 -47
  29. package/dist/src/files/contract.js.map +1 -1
  30. package/dist/src/files/disk-store.d.ts +2 -1
  31. package/dist/src/files/disk-store.js +28 -18
  32. package/dist/src/files/disk-store.js.map +1 -1
  33. package/dist/src/files/http.d.ts +31 -0
  34. package/dist/src/files/http.js +44 -0
  35. package/dist/src/files/http.js.map +1 -0
  36. package/dist/src/files/index.d.ts +1 -1
  37. package/dist/src/files/index.js +1 -1
  38. package/dist/src/files/index.js.map +1 -1
  39. package/dist/src/files/memory-store.js +8 -7
  40. package/dist/src/files/memory-store.js.map +1 -1
  41. package/dist/src/files/openapi.js +2 -4
  42. package/dist/src/files/openapi.js.map +1 -1
  43. package/dist/src/files/routes.js +3 -2
  44. package/dist/src/files/routes.js.map +1 -1
  45. package/dist/src/graphql/entry.d.ts +3 -0
  46. package/dist/src/graphql/entry.js +3 -0
  47. package/dist/src/graphql/entry.js.map +1 -0
  48. package/dist/src/graphql/preflight.d.ts +3 -0
  49. package/dist/src/graphql/preflight.js +30 -0
  50. package/dist/src/graphql/preflight.js.map +1 -0
  51. package/dist/src/graphql/public.d.ts +3 -0
  52. package/dist/src/graphql/public.js +11 -0
  53. package/dist/src/graphql/public.js.map +1 -0
  54. package/dist/src/graphql/resolvers.d.ts +8 -0
  55. package/dist/src/graphql/resolvers.js +38 -0
  56. package/dist/src/graphql/resolvers.js.map +1 -0
  57. package/dist/src/graphql/routes.d.ts +4 -0
  58. package/dist/src/graphql/routes.js +27 -0
  59. package/dist/src/graphql/routes.js.map +1 -0
  60. package/dist/src/graphql/write.d.ts +1 -0
  61. package/dist/src/graphql/write.js +7 -0
  62. package/dist/src/graphql/write.js.map +1 -0
  63. package/dist/src/graphql.d.ts +1 -2
  64. package/dist/src/graphql.js +24 -34
  65. package/dist/src/graphql.js.map +1 -1
  66. package/dist/src/http/errors.d.ts +6 -0
  67. package/dist/src/http/errors.js +3 -0
  68. package/dist/src/http/errors.js.map +1 -0
  69. package/dist/src/model.d.ts +2 -3
  70. package/dist/src/model.js +54 -12
  71. package/dist/src/model.js.map +1 -1
  72. package/dist/src/openapi/document.js +36 -29
  73. package/dist/src/openapi/document.js.map +1 -1
  74. package/dist/src/openapi/entry.d.ts +3 -0
  75. package/dist/src/openapi/entry.js +2 -0
  76. package/dist/src/openapi/entry.js.map +1 -0
  77. package/dist/src/openapi/helpers.d.ts +17 -0
  78. package/dist/src/openapi/helpers.js +4 -0
  79. package/dist/src/openapi/helpers.js.map +1 -0
  80. package/dist/src/openapi/index.js +6 -4
  81. package/dist/src/openapi/index.js.map +1 -1
  82. package/dist/src/openapi/public.d.ts +17 -0
  83. package/dist/src/openapi/public.js +18 -0
  84. package/dist/src/openapi/public.js.map +1 -0
  85. package/dist/src/pagination.d.ts +7 -0
  86. package/dist/src/pagination.js +12 -0
  87. package/dist/src/pagination.js.map +1 -0
  88. package/dist/src/paths.d.ts +6 -0
  89. package/dist/src/paths.js +47 -0
  90. package/dist/src/paths.js.map +1 -0
  91. package/dist/src/query/contract.d.ts +3 -0
  92. package/dist/src/query/contract.js +19 -0
  93. package/dist/src/query/contract.js.map +1 -0
  94. package/dist/src/query/filter.d.ts +3 -1
  95. package/dist/src/query/filter.js +78 -76
  96. package/dist/src/query/filter.js.map +1 -1
  97. package/dist/src/query/options.d.ts +1 -14
  98. package/dist/src/query/options.js +6 -109
  99. package/dist/src/query/options.js.map +1 -1
  100. package/dist/src/relation-metadata.d.ts +0 -1
  101. package/dist/src/relation-metadata.js +0 -1
  102. package/dist/src/relation-metadata.js.map +1 -1
  103. package/dist/src/rest/options.d.ts +14 -0
  104. package/dist/src/rest/options.js +108 -0
  105. package/dist/src/rest/options.js.map +1 -0
  106. package/dist/src/rest/projection.d.ts +6 -0
  107. package/dist/src/rest/projection.js +38 -0
  108. package/dist/src/rest/projection.js.map +1 -0
  109. package/dist/src/rest/routes.d.ts +3 -0
  110. package/dist/src/rest/routes.js +48 -0
  111. package/dist/src/rest/routes.js.map +1 -0
  112. package/dist/src/schema.d.ts +8 -0
  113. package/dist/src/schema.js +13 -0
  114. package/dist/src/schema.js.map +1 -0
  115. package/dist/src/server/public.d.ts +3 -0
  116. package/dist/src/server/public.js +2 -0
  117. package/dist/src/server/public.js.map +1 -0
  118. package/dist/src/server.d.ts +5 -7
  119. package/dist/src/server.js +86 -143
  120. package/dist/src/server.js.map +1 -1
  121. package/dist/src/utils.d.ts +0 -7
  122. package/dist/src/utils.js +0 -4
  123. package/dist/src/utils.js.map +1 -1
  124. package/package.json +13 -1
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  A JSON-backed mock server with REST, GraphQL, nested queries, binary files and schema exports. Requires Node.js 22 or newer.
6
6
 
7
- **1.0.0-alpha.1 is a breaking prerelease.** The old `$schema`/`$info` model format and `_where`, `_sort`, `_embed`, `_page`, `_perPage` query parameters are no longer supported.
7
+ **1.0.0-alpha.3 is a prerelease.** When upgrading from 0.x, update your model schema and query parameters using the examples below.
8
8
 
9
9
  ## Installation
10
10
 
@@ -12,29 +12,35 @@ A JSON-backed mock server with REST, GraphQL, nested queries, binary files and s
12
12
  npm install @kollors/deep-json-server@alpha
13
13
  ```
14
14
 
15
- The `alpha` npm channel is separate from `latest`. Install an exact version with `@1.0.0-alpha.1`.
15
+ To install a specific version, use `@1.0.0-alpha.3`.
16
16
 
17
17
  ## Quick start
18
18
 
19
+ Create two files in the same directory.
20
+
21
+ `database.json`:
22
+
23
+ ```json
24
+ {
25
+ "users": [
26
+ { "id": "1", "fullName": "Мира Волкова" }
27
+ ]
28
+ }
29
+ ```
30
+
19
31
  `server.config.js`:
20
32
 
21
33
  ```js
22
34
  export default {
23
- database: { path: './database.json', schema: './schema.json' },
24
- graphql: { enabled: true, path: './generated/schema.graphql' },
25
- openapi: { path: './generated/openapi.yaml' },
26
- server: { host: '127.0.0.1', port: 4001, pageSize: 10, maxPageSize: 100 },
35
+ database: { path: './database.json' },
27
36
  };
28
37
  ```
29
38
 
30
39
  ```sh
31
40
  npx deep-json-server server.config.js
32
- npx deep-json-server --openapi-only --graphql-only server.config.js
33
41
  ```
34
42
 
35
- The second command exports both schemas without listening on a port. Starting the server alone does not write schema files.
36
-
37
- Complete catalog examples: [database](examples/database.json), [model schema](examples/schema.json), [configuration](examples/server.config.js).
43
+ The user list is available at `http://127.0.0.1:4001/users`.
38
44
 
39
45
  ## Configuration
40
46
 
@@ -42,8 +48,10 @@ Complete catalog examples: [database](examples/database.json), [model schema](ex
42
48
  |---|---|
43
49
  | `database.path` / `database.data` | Exactly one: JSON file or in-memory collection object |
44
50
  | `database.schema` | Model object or JSON schema-file path; optional for REST |
51
+ | `openapi.enabled` | Enable the specification endpoint; default `false` |
52
+ | `openapi.endpoint` | Specification path; default `/openapi.json` |
45
53
  | `openapi.path` | YAML export destination |
46
- | `openapi.info` | Optional `title`, `version`, `description` |
54
+ | `openapi.info` | Optional metadata object: required `title` and `version`, optional `description` |
47
55
  | `graphql.enabled` | Enable GraphQL HTTP endpoint; default `false` |
48
56
  | `graphql.endpoint` | Endpoint path; default `/graphql` |
49
57
  | `graphql.path` | GraphQL SDL export destination |
@@ -54,24 +62,46 @@ Complete catalog examples: [database](examples/database.json), [model schema](ex
54
62
  | `files.data` | In-memory binary files |
55
63
  | `files.directory`, `files.metadata` | Disk storage directory and metadata JSON file; both required |
56
64
 
57
- Configuration-file paths resolve relative to that file. Direct `createServer()` paths resolve relative to the working directory. In-memory input is copied.
65
+ 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.
58
66
 
59
- CLI flags:
67
+ Set `server.port` to `0` to let the operating system choose an available port. The OpenAPI endpoint uses a relative server URL.
60
68
 
61
- | Flag | Action |
69
+ | CLI flag | Action |
62
70
  |---|---|
63
- | `--files` | Enable binary-file routes |
64
- | `--graphql` | Enable GraphQL endpoint |
65
- | `--openapi` | Export OpenAPI and start |
66
- | `--openapi-only` | Export OpenAPI without starting |
67
- | `--graphql-schema` | Export GraphQL SDL and start |
68
- | `--graphql-only` | Export GraphQL SDL without starting |
69
- | `--help` | Show usage |
71
+ | `--files` | Enable file routes |
72
+ | `--graphql` | Enable the GraphQL API |
73
+ | `--openapi` | Enable the OpenAPI endpoint |
74
+ | `--host <host>` | Server address |
75
+ | `--port <port>` | Server port |
76
+ | `--help`, `-h` | Show help |
77
+ | `--version`, `-v` | Show package version |
78
+
79
+ Setting priority: CLI → configuration → `HOST`/`PORT` → defaults. File routes are enabled when `files` is configured.
80
+
81
+ To generate schemas, specify the format and configuration file:
82
+
83
+ ```sh
84
+ npx deep-json-server generate openapi server.config.js
85
+ npx deep-json-server generate graphql server.config.js
86
+ npx deep-json-server generate openapi,graphql server.config.js
87
+ ```
88
+
89
+ The command reads `database.schema` and writes schemas to `openapi.path` and `graphql.path`. A configuration for generation only can contain:
90
+
91
+ ```js
92
+ export default {
93
+ database: { schema: './schema.json' },
94
+ openapi: { path: './generated/openapi.yaml' },
95
+ graphql: { path: './generated/schema.graphql' },
96
+ };
97
+ ```
70
98
 
71
- Both exporters can be combined. Any `--*-only` flag prevents startup. CLI file routes require `--files`; programmatic use enables them when `files` is configured unless overridden through the second `createServer()` argument.
99
+ Each format needs its own output file. The command rejects destinations that would overwrite the configuration, database, schema or file metadata.
72
100
 
73
101
  ## Model schema
74
102
 
103
+ Examples: [database](examples/database.json), [model schema](examples/schema.json), [configuration](examples/server.config.js).
104
+
75
105
  ```json
76
106
  {
77
107
  "Country": {
@@ -104,11 +134,13 @@ Both exporters can be combined. Any `--*-only` flag prevents startup. CLI file r
104
134
  | OpenAPI 3.0.3 export | Available | Error when requested |
105
135
  | GraphQL SDL / API | Available | Error when requested |
106
136
 
107
- Explicit schemas are strict: undeclared fields and collections are rejected, except storage keys inferred from relations. Existing data is validated on startup. Schemas can be exported for empty collections or an empty database object. REST without a schema retains the standard generated `id` behavior.
137
+ 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.
138
+
139
+ 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=*`. Fields with mixed value types can be read, but using them in `where`, `order` or `nested` requires an explicit schema.
108
140
 
109
141
  ### Fields
110
142
 
111
- Types: `string`, `number`, `boolean`, `object`, or a model name. Append `[]` for an array. No `integer`, `relation`, `items`, or multidimensional type strings. Nested fields use full dotted paths, for example `actors.fullName`. Objects inside arrays may contain their own arrays.
143
+ The `type` property accepts `string`, `number`, `boolean`, `object`, or a model name. Append `[]` for an array: `string[]`, `object[]`, `Genre[]`. Use dotted paths for nested fields, such as `actors.fullName`.
112
144
 
113
145
  | Properties | Meaning |
114
146
  |---|---|
@@ -116,7 +148,7 @@ Types: `string`, `number`, `boolean`, `object`, or a model name. Append `[]` for
116
148
  | `description`, `example` | Documentation and example value |
117
149
  | `required`, `nullable` | Defaults `false`; presence and explicit null are separate |
118
150
  | `default` | Value when omitted on create/replace; PATCH does not insert defaults |
119
- | `enum` | Allowed values; for arrays, allowed element values |
151
+ | `enum` | Allowed strings, numbers or booleans; for arrays, allowed element values |
120
152
  | `primary` | Root primary key; mandatory, unique, non-null and immutable |
121
153
  | `generated` | `uuid` for strings, `increment` for numbers; server supplies the value |
122
154
  | `readOnly`, `writeOnly` | Output-only or input-only; mutually exclusive |
@@ -125,11 +157,13 @@ Types: `string`, `number`, `boolean`, `object`, or a model name. Append `[]` for
125
157
  | `minimum`, `maximum` | Inclusive numeric bounds |
126
158
  | `source`, `target`, `onDelete` | Relation metadata |
127
159
 
128
- String/numeric constraints on `string[]`/`number[]` apply to every element. `required`/`nullable` apply to the entire array, and `default`/`example` contain a complete array. Array elements are non-null. There are no item-count or uniqueness constraints. A required ordinary array may be empty.
160
+ String and numeric constraints on `string[]`/`number[]` apply to every element. `required` and `nullable` apply to the entire array; `default` and `example` contain a complete array. Elements must match the array's type and be non-null. `required` requires the field to be present; an ordinary array may still be empty.
129
161
 
130
- Primary keys can be named `username`, `code`, etc.; exactly one root string/number primary key is required. Without `generated`, the client supplies it during creation. Generated fields are root fields, absent from create/replace/update input; they cannot have `default`. Replace preserves generated and read-only root values.
162
+ 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. 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.
131
163
 
132
- For example, a `LocalUser` with primary `username` and `password: {"type":"string","required":true,"writeOnly":true}` has `localUser(username: ...)` and `/localUsers/{username}`. `writeOnly` excludes passwords from responses, scope, filters and ordering. It does not implement hashing or authentication.
164
+ 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.
165
+
166
+ Objects used in GraphQL must have at least one field visible in responses; REST also accepts empty objects.
133
167
 
134
168
  ### Relations
135
169
 
@@ -141,20 +175,20 @@ For example, a `LocalUser` with primary `username` and `password: {"type":"strin
141
175
  }
142
176
  ```
143
177
 
144
- `Genre` produces an object; `Genre[]` produces a list. `source` defaults to the current model's primary key, `target` to the target model's primary key. Both paths are rooted at their respective records. Within `actors`, `actors.genreIds` reads the current actor's IDs. An omitted `source` still means the root model key, not `actors.id`.
178
+ `Genre` returns an object; `Genre[]` returns a list. `source` defaults to the current model's primary key, `target` to the target model's primary key. These defaults also apply to nested relations. Paths start at the root of their respective records: in this example, `actors.genreIds` contains the current actor's genre keys.
145
179
 
146
- Storage keys remain in the database and are included among own fields. Their types can be inferred from the target key. For an undeclared source pointing to a target primary key, a list relation implies an array of keys; a single relation implies a scalar key. Declare storage fields explicitly when the mapping is ambiguous. Generation never guesses from the first database record.
180
+ Relation keys are stored in the database and included among the record's own fields. Their types are inferred from the matched keys. A `source` field pointing to a target primary key can be omitted from the field declarations: the schema infers an array of keys for a list relation or a scalar key for a single relation. Declare the storage field explicitly when the mapping is ambiguous.
147
181
 
148
182
  Reverse example: `User.movies = {"type":"Movie[]","target":"actors.userId"}`. A movie is returned once even if several actors match. A single relation resolving to multiple targets is invalid.
149
183
 
150
- Every supplied direct reference must resolve. `required: true` on a relation requires at least one target before response filtering/pagination. Reverse views with a primary source may be empty unless required. Missing single relations return `null`.
184
+ Every supplied direct relation key must point to an existing record. `required: true` on a relation requires at least one target before response filtering/pagination. Reverse relations using the primary key as `source` may be empty unless required. Missing single relations return `null`.
151
185
 
152
186
  `onDelete` describes what happens **when a target record is deleted**:
153
187
 
154
188
  - `restrict` (default): refuse deletion while a surviving owner refers to the target.
155
189
  - `cascade`: delete the referring owner. For `User.country`, deleting the country deletes its users. For `Movie.actors.user`, deleting the user removes matching actor elements and retains the movie.
156
190
 
157
- Deletion computes the cascade closure, handles cycles, checks restrictions and validates remaining data before committing. A failure cancels the complete operation. Policies also apply to explicitly declared reverse relation views; configure both directions deliberately when both are present.
191
+ 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.
158
192
 
159
193
  ## Queries and responses
160
194
 
@@ -164,9 +198,11 @@ Collections and lists of objects, including embedded `object[]` fields, return:
164
198
  { "data": [], "total": 0 }
165
199
  ```
166
200
 
167
- Primitive arrays remain plain arrays. Every object list accepts optional `where`, `order`, `pager`. Processing order is filter → sort → pagination. `total` is the filtered count before pagination. Page numbers start at 1; default pagination applies even when omitted. Exceeding `maxPageSize`, fractional values and nonpositive values are errors. Out-of-range pages return empty `data` with the correct `total`.
201
+ Primitive arrays are returned as plain arrays. Every object list accepts optional `where`, `order` and `pager`. Processing order is filter → sort → pagination. `total` is the filtered record count before pagination.
202
+
203
+ 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`.
168
204
 
169
- `where` uses field operators `eq`, `ne`, `in`, string `contains`/`startsWith`/`endsWith`, and comparisons `gt`, `gte`, `lt`, `lte`. Logical composition uses `and`, `or`, `not`. Arrays support `some`, `every`, `none`; primitive arrays also support `contains`, `in`. String matching is case-insensitive. Field filters use operator objects, not shorthand scalar values.
205
+ `where` uses field operators `eq`, `ne`, `in`, string `contains`/`startsWith`/`endsWith`, and comparisons `gt`, `gte`, `lt`, `lte`. Combine conditions with `and`, `or`, `not`. Arrays support `some`, `every`, `none`; primitive arrays also support `contains`, `in`. String matching is case-insensitive. A field condition is an object containing an operator, such as `{ "id": { "eq": "1" } }`.
170
206
 
171
207
  ```json
172
208
  {
@@ -182,9 +218,9 @@ Primitive arrays remain plain arrays. Every object list accepts optional `where`
182
218
  }
183
219
  ```
184
220
 
185
- Root filters choose parents. Filters inside a selected relation only trim that relation; they do not remove the parent. Each parent's child list is processed independently. Filtering does not require a relation to be included in the response.
221
+ Root `where` selects records from the main collection. `where` inside a relation filters its elements while retaining the parent record. Each nested list is processed independently. Filtering by a relation works independently of its inclusion in the response.
186
222
 
187
- `order` is an array of `{ "field": "fullName", "direction": "ASC" }` rules. Earlier rules have priority; complete ties retain storage order. Null and missing values compare equally. REST uses dotted field paths; GraphQL uses generated enums (`profile_name` for `profile.name`). Ambiguous enum names cause a generation error. Sorting parents by a relation or an array is unsupported; sorting inside the relation is supported.
223
+ `order` is an array of `{ "field": "fullName", "direction": "ASC" }` rules. Earlier rules have priority; equal values retain storage order. Null and missing values compare equally. REST uses dotted field paths; GraphQL uses generated enums (`profile_name` for `profile.name`). Ambiguous enum names cause a generation error. Sorting supports scalar fields of the current object, including nested fields. Related lists accept their own `order`.
188
224
 
189
225
  ### REST
190
226
 
@@ -197,7 +233,7 @@ Root filters choose parents. Filters inside a selected relation only trim that r
197
233
  | PATCH | `/users/{id}` | `userUpdate` |
198
234
  | DELETE | `/users/{id}` | `userDelete` |
199
235
 
200
- The path key name follows the primary key. POST/PUT/PATCH receive raw record objects. PUT replaces the record while retaining its key and server-owned root values. PATCH shallowly merges supplied fields; supplied nested objects are full replacements. Create/replace enforce required fields. Update validates supplied values and the final record. Missing targets return 404; conflicts return 409. DELETE returns the deleted record.
236
+ 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`. DELETE returns the deleted record.
201
237
 
202
238
  Query parameters `where`, `order`, `pager`, `nested` contain JSON. `scope` is a selection string. Example shown before URL encoding:
203
239
 
@@ -215,7 +251,7 @@ const params = new URLSearchParams({
215
251
  const response = await fetch(`/users?${params}`);
216
252
  ```
217
253
 
218
- `scope=*,actors(user(id,fullName),genres(*))` selects own fields and explicit relations. `*` selects only own fields of the current object, including inferred storage keys and excluding `writeOnly`. It never recursively expands relations. Without scope, own fields are selected. Wrappers `data`/`total` remain present.
254
+ `scope=*,actors(user(id,fullName),genres(*))` selects own fields and the specified relations. `*` includes the current object's own fields and stored keys, except `writeOnly` fields. Relations are listed explicitly. The default selection is own fields. Lists retain the `{ data, total }` response structure.
219
255
 
220
256
  `nested` maps full response paths to list options:
221
257
 
@@ -229,7 +265,7 @@ const response = await fetch(`/users?${params}`);
229
265
  }
230
266
  ```
231
267
 
232
- A nested path must be selected by scope and must address an object list. Single-record routes and mutations accept `scope` and `nested`; root list parameters only apply to collection GET. Invalid names and unsafe paths return 400.
268
+ A path in `nested` must be selected by `scope` and point to an object list. Single-record routes and mutations accept `scope` and `nested`; root `where`, `order` and `pager` apply to collection GET. Invalid names and unsafe paths return `400`.
233
269
 
234
270
  ### GraphQL
235
271
 
@@ -252,10 +288,9 @@ query {
252
288
  }
253
289
  ```
254
290
 
255
- Single queries are `user(id: ...)`, with no `ById`; missing records yield null. Mutation names are `userCreate(data: ...)`, `userReplace(id: ..., data: ...)`, `userUpdate(id: ..., data: ...)`, `userDelete(id: ...)`. A generated-only model creates records without a `data` argument. Mutations use the same validation and storage operations as REST. Selecting relations in mutation results shapes the response only.
256
-
257
- String primary keys use GraphQL `ID`; ordinary strings use `String`, numbers use `Float`, pagination uses `Int`. Schema enums preserve valid string labels; other values receive `VALUE_0`, `VALUE_1`, etc. Schema constraints such as string length and formats are enforced by the shared runtime validator; SDL alone cannot express all constraints. Introspection and ordinary query/mutation execution are supported; this release does not add subscriptions or bulk mutations.
291
+ The query `user(id: ...)` returns one record or `null` if it is missing. Mutations are `userCreate(data: ...)`, `userReplace(id: ..., data: ...)`, `userUpdate(id: ..., data: ...)`, `userDelete(id: ...)`. For models containing only generated fields, the create mutation takes no `data` argument. Writes and validation follow the same rules as REST. Relations selected in a mutation result determine the response contents.
258
292
 
293
+ 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. Errors include `extensions.code`: `INVALID_INPUT`, `INVALID_QUERY`, `NOT_FOUND`, `CONFLICT` or `INTERNAL_ERROR`.
259
294
 
260
295
  ## Example database
261
296
 
@@ -363,10 +398,10 @@ String primary keys use GraphQL `ID`; ordinary strings use `String`, numbers use
363
398
 
364
399
  ## Files
365
400
 
366
- Add `files.directory` and `files.metadata` to the server config, then pass `--files` to enable raw binary uploads:
401
+ Add `files.directory` and `files.metadata` to the configuration and start the server:
367
402
 
368
403
  ```bash
369
- deep-json-server --files server.config.js
404
+ deep-json-server server.config.js
370
405
  ```
371
406
 
372
407
  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.
@@ -422,37 +457,53 @@ Content-Type: application/json
422
457
 
423
458
  `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.
424
459
 
425
- In disk mode, the binary is stored at `<files.directory>/<directory>/<name>`. The metadata file contains only `directory`, `mimeType`, and `name`; `size` is read from the actual file, while response URLs are computed. The server creates directories automatically and keeps validated metadata in memory while running. Use a disk-backed database and file storage from only one server process at a time, and do not edit stored files or metadata until that process stops. Paths below `files.directory` may not contain symbolic links, and file names are restricted to values that are portable across supported operating systems. The metadata file may be absent initially and is created on the first upload. Metadata created by versions before this path-based API is not compatible with the new format.
460
+ 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.
461
+
462
+ 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, loaded configuration or metadata file.
426
463
 
427
- 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`. 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.
464
+ 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.
428
465
 
429
466
  ## Programmatic API
430
467
 
431
468
  ```js
432
- import { createServer } from '@kollors/deep-json-server';
469
+ import { createServer } from '@kollors/deep-json-server/server';
433
470
  import config from './server.config.js';
434
471
 
435
472
  const facade = await createServer(config);
436
- const openapi = await facade.openapi();
437
- const sdl = await facade.graphql();
438
473
  const server = facade.fastify();
439
474
  await server.listen();
440
475
  // await server.close();
441
476
  ```
442
477
 
443
- Accessors are lazy; exporting schemas does not listen or initialize disk file storage. Override runtime features with `createServer(config, { files: false, graphql: true })`.
478
+ 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. Override server features with `createServer(config, { files: false, graphql: true, openapi: true })`.
479
+
480
+ The root import `@kollors/deep-json-server` also provides these functions. Server adapters load when enabled. Generators can be used independently:
481
+
482
+ ```js
483
+ import { generateOpenapi, writeOpenapi } from '@kollors/deep-json-server/openapi';
484
+ import { generateGraphql, writeGraphql } from '@kollors/deep-json-server/graphql';
485
+
486
+ const document = await generateOpenapi('./schema.json', { files: true });
487
+ const sdl = await generateGraphql('./schema.json');
488
+ await writeOpenapi(document, './generated/openapi.yaml');
489
+ await writeGraphql(sdl, './generated/schema.graphql');
490
+ ```
491
+
492
+ `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`.
444
493
 
445
494
  ## Storage and development
446
495
 
447
- Updates are serialized within one server instance and validated on a draft before persistence. Use one writer 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. UUID generation uses Node's built-in crypto API.
496
+ 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.
448
497
 
449
- This is a mock server; there is no authentication or password hashing. File routes keep their independent storage and validation.
498
+ The server is intended for mocking APIs. Implement authentication and password hashing in your application if needed.
450
499
 
451
500
  ```sh
452
501
  npm ci
453
502
  npm run verify
454
503
  ```
455
504
 
456
- Verification runs type checking, lint, coverage gates and installation checks against the packed package. A new alpha version in `package.json` pushed to `main` creates its version tag and publishes through GitHub Actions trusted publishing to npm `alpha`. An existing tag skips automatic publication. Explicit version-tag pushes also publish; stable versions use `latest`. The publishing script rejects mismatched Git tags.
505
+ The command checks types, code style, test coverage and installation from the package archive.
506
+
507
+ To publish a new alpha, update the version in `package.json`, `package-lock.json` and `src/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`.
457
508
 
458
509
  License: MIT.