@kollors/deep-json-server 1.0.0-alpha.5 → 1.0.0-alpha.7

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 (215) hide show
  1. package/README.md +346 -188
  2. package/README.ru.md +343 -185
  3. package/dist/bin/deep-json-server.js +1 -1
  4. package/dist/bin/deep-json-server.js.map +1 -1
  5. package/dist/index.d.ts +12 -8
  6. package/dist/index.js +4 -2
  7. package/dist/index.js.map +1 -1
  8. package/dist/src/auth/contract.d.ts +27 -0
  9. package/dist/src/auth/contract.js +5 -0
  10. package/dist/src/auth/contract.js.map +1 -0
  11. package/dist/src/auth/password.d.ts +13 -0
  12. package/dist/src/auth/password.js +37 -0
  13. package/dist/src/auth/password.js.map +1 -0
  14. package/dist/src/auth/public.d.ts +3 -0
  15. package/dist/src/auth/public.js +5 -0
  16. package/dist/src/auth/public.js.map +1 -0
  17. package/dist/src/auth/routes.d.ts +6 -0
  18. package/dist/src/auth/routes.js +15 -0
  19. package/dist/src/auth/routes.js.map +1 -0
  20. package/dist/src/auth/service.d.ts +39 -0
  21. package/dist/src/auth/service.js +143 -0
  22. package/dist/src/auth/service.js.map +1 -0
  23. package/dist/src/cli/index.d.ts +7 -0
  24. package/dist/src/{cli.js → cli/index.js} +32 -12
  25. package/dist/src/cli/index.js.map +1 -0
  26. package/dist/src/{constants.d.ts → core/constants.d.ts} +1 -1
  27. package/dist/src/{constants.js → core/constants.js} +1 -1
  28. package/dist/src/core/constants.js.map +1 -0
  29. package/dist/src/core/database.d.ts +50 -0
  30. package/dist/src/{database.js → core/database.js} +21 -1
  31. package/dist/src/core/database.js.map +1 -0
  32. package/dist/src/core/engine.d.ts +64 -0
  33. package/dist/src/{engine.js → core/engine.js} +64 -18
  34. package/dist/src/core/engine.js.map +1 -0
  35. package/dist/src/core/errors.d.ts +9 -0
  36. package/dist/src/core/errors.js +12 -0
  37. package/dist/src/core/errors.js.map +1 -0
  38. package/dist/src/core/http-errors.d.ts +9 -0
  39. package/dist/src/core/http-errors.js +15 -0
  40. package/dist/src/core/http-errors.js.map +1 -0
  41. package/dist/src/core/lifecycle/mutation.d.ts +49 -0
  42. package/dist/src/core/lifecycle/mutation.js +170 -0
  43. package/dist/src/core/lifecycle/mutation.js.map +1 -0
  44. package/dist/src/core/lifecycle/options.d.ts +20 -0
  45. package/dist/src/core/lifecycle/options.js +21 -0
  46. package/dist/src/core/lifecycle/options.js.map +1 -0
  47. package/dist/src/core/model.d.ts +130 -0
  48. package/dist/src/{model.js → core/model.js} +106 -10
  49. package/dist/src/core/model.js.map +1 -0
  50. package/dist/src/{mutations → core/mutations}/write.d.ts +3 -2
  51. package/dist/src/{mutations → core/mutations}/write.js +14 -4
  52. package/dist/src/core/mutations/write.js.map +1 -0
  53. package/dist/src/core/pagination.d.ts +10 -0
  54. package/dist/src/{pagination.js → core/pagination.js} +3 -0
  55. package/dist/src/core/pagination.js.map +1 -0
  56. package/dist/src/core/paths.d.ts +16 -0
  57. package/dist/src/{paths.js → core/paths.js} +14 -1
  58. package/dist/src/core/paths.js.map +1 -0
  59. package/dist/src/core/query/contract.d.ts +6 -0
  60. package/dist/src/{query → core/query}/contract.js +3 -0
  61. package/dist/src/core/query/contract.js.map +1 -0
  62. package/dist/src/core/query/filter.d.ts +6 -0
  63. package/dist/src/{query → core/query}/filter.js +18 -4
  64. package/dist/src/core/query/filter.js.map +1 -0
  65. package/dist/src/core/query/options.d.ts +30 -0
  66. package/dist/src/{query → core/query}/options.js +12 -0
  67. package/dist/src/core/query/options.js.map +1 -0
  68. package/dist/src/core/records.d.ts +46 -0
  69. package/dist/src/{records.js → core/records.js} +24 -3
  70. package/dist/src/core/records.js.map +1 -0
  71. package/dist/src/core/relation-metadata.d.ts +15 -0
  72. package/dist/src/{relation-metadata.js → core/relation-metadata.js} +7 -1
  73. package/dist/src/core/relation-metadata.js.map +1 -0
  74. package/dist/src/core/types.d.ts +10 -0
  75. package/dist/src/{types.js.map → core/types.js.map} +1 -1
  76. package/dist/src/core/utils.d.ts +56 -0
  77. package/dist/src/core/utils.js +102 -0
  78. package/dist/src/core/utils.js.map +1 -0
  79. package/dist/src/files/contract.d.ts +33 -0
  80. package/dist/src/files/contract.js +25 -3
  81. package/dist/src/files/contract.js.map +1 -1
  82. package/dist/src/files/disk-store.d.ts +3 -0
  83. package/dist/src/files/disk-store.js +27 -3
  84. package/dist/src/files/disk-store.js.map +1 -1
  85. package/dist/src/files/http.d.ts +8 -3
  86. package/dist/src/files/http.js +9 -0
  87. package/dist/src/files/http.js.map +1 -1
  88. package/dist/src/files/index.d.ts +4 -3
  89. package/dist/src/files/index.js +3 -1
  90. package/dist/src/files/index.js.map +1 -1
  91. package/dist/src/files/memory-store.d.ts +4 -1
  92. package/dist/src/files/memory-store.js +8 -2
  93. package/dist/src/files/memory-store.js.map +1 -1
  94. package/dist/src/files/routes.d.ts +3 -0
  95. package/dist/src/files/routes.js +25 -1
  96. package/dist/src/files/routes.js.map +1 -1
  97. package/dist/src/graphql/entry.d.ts +2 -2
  98. package/dist/src/graphql/entry.js.map +1 -1
  99. package/dist/src/graphql/lazy.d.ts +10 -0
  100. package/dist/src/graphql/lazy.js +13 -0
  101. package/dist/src/graphql/lazy.js.map +1 -0
  102. package/dist/src/graphql/preflight.d.ts +4 -1
  103. package/dist/src/graphql/preflight.js +3 -0
  104. package/dist/src/graphql/preflight.js.map +1 -1
  105. package/dist/src/graphql/public.d.ts +10 -2
  106. package/dist/src/graphql/public.js +10 -4
  107. package/dist/src/graphql/public.js.map +1 -1
  108. package/dist/src/graphql/resolvers.d.ts +10 -2
  109. package/dist/src/graphql/resolvers.js +8 -2
  110. package/dist/src/graphql/resolvers.js.map +1 -1
  111. package/dist/src/graphql/routes.d.ts +6 -2
  112. package/dist/src/graphql/routes.js +6 -3
  113. package/dist/src/graphql/routes.js.map +1 -1
  114. package/dist/src/graphql/schema.d.ts +6 -0
  115. package/dist/src/{graphql.js → graphql/schema.js} +14 -11
  116. package/dist/src/graphql/schema.js.map +1 -0
  117. package/dist/src/graphql/write.d.ts +3 -0
  118. package/dist/src/graphql/write.js +3 -0
  119. package/dist/src/graphql/write.js.map +1 -1
  120. package/dist/src/openapi/auth.d.ts +13 -0
  121. package/dist/src/openapi/auth.js +72 -0
  122. package/dist/src/openapi/auth.js.map +1 -0
  123. package/dist/src/openapi/document.d.ts +7 -3
  124. package/dist/src/openapi/document.js +49 -34
  125. package/dist/src/openapi/document.js.map +1 -1
  126. package/dist/src/openapi/entry.d.ts +2 -2
  127. package/dist/src/openapi/entry.js.map +1 -1
  128. package/dist/src/openapi/files.d.ts +6 -0
  129. package/dist/src/{files/openapi.js → openapi/files.js} +6 -3
  130. package/dist/src/openapi/files.js.map +1 -0
  131. package/dist/src/openapi/helpers.d.ts +10 -1
  132. package/dist/src/openapi/helpers.js +9 -0
  133. package/dist/src/openapi/helpers.js.map +1 -1
  134. package/dist/src/openapi/index.d.ts +7 -1
  135. package/dist/src/openapi/index.js +14 -5
  136. package/dist/src/openapi/index.js.map +1 -1
  137. package/dist/src/openapi/lazy.d.ts +11 -0
  138. package/dist/src/openapi/lazy.js +13 -0
  139. package/dist/src/openapi/lazy.js.map +1 -0
  140. package/dist/src/openapi/public.d.ts +11 -2
  141. package/dist/src/openapi/public.js +12 -4
  142. package/dist/src/openapi/public.js.map +1 -1
  143. package/dist/src/{types.d.ts → openapi/types.d.ts} +2 -10
  144. package/dist/src/openapi/types.js +2 -0
  145. package/dist/src/openapi/types.js.map +1 -0
  146. package/dist/src/rest/options.d.ts +11 -2
  147. package/dist/src/rest/options.js +12 -3
  148. package/dist/src/rest/options.js.map +1 -1
  149. package/dist/src/rest/projection.d.ts +10 -4
  150. package/dist/src/rest/projection.js +10 -4
  151. package/dist/src/rest/projection.js.map +1 -1
  152. package/dist/src/rest/routes.d.ts +6 -2
  153. package/dist/src/rest/routes.js +7 -3
  154. package/dist/src/rest/routes.js.map +1 -1
  155. package/dist/src/server/config.d.ts +79 -0
  156. package/dist/src/{config.js → server/config.js} +73 -12
  157. package/dist/src/server/config.js.map +1 -0
  158. package/dist/src/server/create.d.ts +18 -0
  159. package/dist/src/{server.js → server/create.js} +39 -21
  160. package/dist/src/server/create.js.map +1 -0
  161. package/dist/src/server/features.d.ts +15 -0
  162. package/dist/src/server/features.js +31 -0
  163. package/dist/src/server/features.js.map +1 -0
  164. package/dist/src/server/public.d.ts +3 -3
  165. package/dist/src/server/public.js +1 -1
  166. package/dist/src/server/public.js.map +1 -1
  167. package/package.json +5 -1
  168. package/dist/src/cli.d.ts +0 -4
  169. package/dist/src/cli.js.map +0 -1
  170. package/dist/src/config.d.ts +0 -81
  171. package/dist/src/config.js.map +0 -1
  172. package/dist/src/constants.js.map +0 -1
  173. package/dist/src/database.d.ts +0 -20
  174. package/dist/src/database.js.map +0 -1
  175. package/dist/src/engine.d.ts +0 -36
  176. package/dist/src/engine.js.map +0 -1
  177. package/dist/src/errors.d.ts +0 -6
  178. package/dist/src/errors.js +0 -9
  179. package/dist/src/errors.js.map +0 -1
  180. package/dist/src/features.d.ts +0 -8
  181. package/dist/src/features.js +0 -18
  182. package/dist/src/features.js.map +0 -1
  183. package/dist/src/files/openapi.d.ts +0 -3
  184. package/dist/src/files/openapi.js.map +0 -1
  185. package/dist/src/graphql.d.ts +0 -3
  186. package/dist/src/graphql.js.map +0 -1
  187. package/dist/src/http/errors.d.ts +0 -6
  188. package/dist/src/http/errors.js +0 -3
  189. package/dist/src/http/errors.js.map +0 -1
  190. package/dist/src/model.d.ts +0 -72
  191. package/dist/src/model.js.map +0 -1
  192. package/dist/src/mutations/write.js.map +0 -1
  193. package/dist/src/pagination.d.ts +0 -7
  194. package/dist/src/pagination.js.map +0 -1
  195. package/dist/src/paths.d.ts +0 -6
  196. package/dist/src/paths.js.map +0 -1
  197. package/dist/src/query/contract.d.ts +0 -3
  198. package/dist/src/query/contract.js.map +0 -1
  199. package/dist/src/query/filter.d.ts +0 -3
  200. package/dist/src/query/filter.js.map +0 -1
  201. package/dist/src/query/options.d.ts +0 -18
  202. package/dist/src/query/options.js.map +0 -1
  203. package/dist/src/records.d.ts +0 -25
  204. package/dist/src/records.js.map +0 -1
  205. package/dist/src/relation-metadata.d.ts +0 -9
  206. package/dist/src/relation-metadata.js.map +0 -1
  207. package/dist/src/schema.d.ts +0 -8
  208. package/dist/src/schema.js +0 -13
  209. package/dist/src/schema.js.map +0 -1
  210. package/dist/src/server.d.ts +0 -12
  211. package/dist/src/server.js.map +0 -1
  212. package/dist/src/utils.d.ts +0 -12
  213. package/dist/src/utils.js +0 -58
  214. package/dist/src/utils.js.map +0 -1
  215. /package/dist/src/{types.js → core/types.js} +0 -0
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  [Русский](README.ru.md)
4
4
 
5
- A JSON-backed mock server with REST, GraphQL, nested queries, binary files and schema exports. Requires Node.js 22 or newer.
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.5 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
+ **1.0.0-alpha.7 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.
8
8
 
9
9
  ## Installation
10
10
 
@@ -12,7 +12,7 @@ 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
- To install a specific version, use `@1.0.0-alpha.5`.
15
+ To install a specific version, use `@1.0.0-alpha.7`.
16
16
 
17
17
  ## Quick start
18
18
 
@@ -42,12 +42,16 @@ npx deep-json-server server.config.js
42
42
 
43
43
  The user list is available at `http://127.0.0.1:4001/users`.
44
44
 
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.
46
+
45
47
  ## Configuration
46
48
 
47
49
  | Setting | Meaning |
48
50
  |---|---|
49
- | `database.path` / `database.data` | Exactly one: JSON file or in-memory collection object |
51
+ | `database.path` / `database.data` | Choose one to start the server: a JSON file or an in-memory collection object |
50
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` |
51
55
  | `openapi.enabled` | Enable the specification endpoint; default `false` |
52
56
  | `openapi.endpoint` | Specification path; default `/openapi.json` |
53
57
  | `openapi.path` | YAML export destination |
@@ -55,6 +59,8 @@ The user list is available at `http://127.0.0.1:4001/users`.
55
59
  | `graphql.enabled` | Enable GraphQL HTTP endpoint; default `false` |
56
60
  | `graphql.endpoint` | Endpoint path; default `/graphql` |
57
61
  | `graphql.path` | GraphQL SDL export destination |
62
+ | `auth.users` | Path to a JSON array of auth users, or an in-memory array |
63
+ | `auth.expiresIn` | Session lifetime in seconds; default 3600 |
58
64
  | `server.host`, `server.port` | Defaults `127.0.0.1`, `4001`; CLI also reads `HOST`/`PORT` |
59
65
  | `server.pageSize`, `server.maxPageSize` | Defaults 10 and 100; default size is capped by the maximum |
60
66
  | `server.cors`, `server.logger` | Default `true`; logger also accepts Fastify logger options |
@@ -66,9 +72,13 @@ Relative paths resolve from the configuration file's directory. When passing a c
66
72
 
67
73
  Set `server.port` to `0` to let the operating system choose an available port. The OpenAPI endpoint uses a relative server URL.
68
74
 
75
+ ### CLI
76
+
69
77
  | CLI flag | Action |
70
78
  |---|---|
71
- | `--files` | Enable file routes |
79
+ | `--files` | Enable file routes; requires `files` configuration |
80
+ | `--timestamps` | Enable record timestamps globally |
81
+ | `--soft-delete` | Enable soft deletion globally |
72
82
  | `--graphql` | Enable the GraphQL API |
73
83
  | `--openapi` | Enable the OpenAPI endpoint |
74
84
  | `--host <host>` | Server address |
@@ -76,27 +86,7 @@ Set `server.port` to `0` to let the operating system choose an available port. T
76
86
  | `--help`, `-h` | Show help |
77
87
  | `--version`, `-v` | Show package version |
78
88
 
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
- ```
98
-
99
- Each format needs its own output file. The command rejects destinations that would overwrite the configuration, database, schema or file metadata.
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.
100
90
 
101
91
  ## Model schema
102
92
 
@@ -138,6 +128,8 @@ Explicit schemas are strict: undeclared fields and collections are rejected, exc
138
128
 
139
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.
140
130
 
131
+ 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
+
141
133
  ### Fields
142
134
 
143
135
  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`.
@@ -168,10 +160,12 @@ Objects used in GraphQL must have at least one field visible in responses; REST
168
160
  ### Relations
169
161
 
170
162
  ```json
171
- "actors.genres": {
172
- "type": "Genre[]",
173
- "source": "actors.genreIds",
174
- "required": true
163
+ {
164
+ "actors.genres": {
165
+ "type": "Genre[]",
166
+ "source": "actors.genreIds",
167
+ "required": true
168
+ }
175
169
  }
176
170
  ```
177
171
 
@@ -179,19 +173,25 @@ Objects used in GraphQL must have at least one field visible in responses; REST
179
173
 
180
174
  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.
181
175
 
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.
176
+ Reverse example: `User.movies = {"type":"Movie[]","target":"actors.userId"}`. A movie is returned once even if several actors match. A single relation that matches multiple records causes an error.
183
177
 
184
178
  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`.
185
179
 
186
180
  `onDelete` describes what happens **when a target record is deleted**:
187
181
 
188
- - `restrict` (default): refuse deletion while a surviving owner refers to the target.
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.
182
+ - `restrict` (default): refuse deletion while a retained record refers to the target.
183
+ - `cascade`: delete the referring record. For `User.country`, deleting the country deletes its users. For `Movie.actors.user`, deleting the user removes matching actor elements and retains the movie.
190
184
 
191
185
  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.
192
186
 
193
187
  ## Queries and responses
194
188
 
189
+ The movie, actor and genre examples use the full [example schema](examples/schema.json). Run them with the [example configuration](examples/server.config.js):
190
+
191
+ ```sh
192
+ npx deep-json-server examples/server.config.js
193
+ ```
194
+
195
195
  Collections and lists of objects, including embedded `object[]` fields, return:
196
196
 
197
197
  ```json
@@ -202,7 +202,7 @@ Primitive arrays are returned as plain arrays. Every object list accepts optiona
202
202
 
203
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`.
204
204
 
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" } }`.
205
+ `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. A field condition is an object containing an operator, such as `{ "id": { "eq": "1" } }`.
206
206
 
207
207
  ```json
208
208
  {
@@ -220,10 +220,12 @@ Use `page` and `pageSize` in `pager`. The default is the first page with the siz
220
220
 
221
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.
222
222
 
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`.
223
+ `order` is an array of `{ "field": "fullName", "direction": "ASC" }` rules. `ASC` sorts in ascending order and `DESC` in descending order. 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`.
224
224
 
225
225
  ### REST
226
226
 
227
+ `GET /` returns collection names: `{ "resources": ["users", "movies"] }`. Each collection has these routes:
228
+
227
229
  | Method | Path | Operation |
228
230
  |---|---|---|
229
231
  | GET | `/users` | `userList` |
@@ -233,62 +235,13 @@ Root `where` selects records from the main collection. `where` inside a relation
233
235
  | PATCH | `/users/{id}` | `userUpdate` |
234
236
  | DELETE | `/users/{id}` | `userDelete` |
235
237
 
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.
237
-
238
- ### Nested writes
239
-
240
- Storage keys such as `genreIds: ["1"]` only set a relation. Relation fields also accept records to create or update:
241
-
242
- ```http
243
- PATCH /movies/1
244
- Content-Type: application/json
245
-
246
- {
247
- "actors": [
248
- {
249
- "userId": "1",
250
- "genres": [
251
- "1",
252
- { "id": "2", "name": "Updated genre" },
253
- { "name": "New genre" }
254
- ]
255
- }
256
- ]
257
- }
258
- ```
259
-
260
- | Relation value | Behavior |
261
- |---|---|
262
- | A key, such as `"1"` | Link an existing record without changing it |
263
- | An object with a primary key | PATCH updates supplied fields; PUT replaces the related record |
264
- | An object without a primary key | Create a related record with defaults and a generated key |
238
+ 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`.
265
239
 
266
- The key name and type follow the target model. An object containing only a key still counts as an update: in PUT it must include the model's required fields. Replacement preserves primary keys, generated values and `readOnly` fields. In POST, nested objects with existing keys receive partial updates. A missing target is an error; creating a nested record without a key requires an autogenerated primary key.
267
-
268
- A supplied list replaces the relation's membership. PATCH preserves omitted relations; PUT clears omitted writable links. `[]` clears a list and `null` clears a nullable single relation. Removing a link does not delete the related record. Required relations must remain populated.
269
-
270
- Use either the relation field or its storage key in an object, for example `genres` or `genreIds`. 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.
271
-
272
- 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.
273
-
274
- GraphQL accepts typed objects in relation fields. To change only the links in a replace mutation, use storage keys such as `genreIds`. For example:
275
-
276
- ```graphql
277
- mutation {
278
- movieUpdate(id: "1", data: {
279
- actors: [{
280
- userId: "1"
281
- genres: [{ id: "2", name: "Updated genre" }, { name: "New genre" }]
282
- }]
283
- }) {
284
- actors { data { genres { data { id name } } } }
285
- }
286
- }
287
- ```
240
+ 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" }`.
288
241
 
289
242
  ### REST query parameters
290
243
 
291
- REST accepts one query parameter, `scope`, containing a JSON array `[fields, arguments?]`. The first object selects fields; the optional second object supplies `where`, `order` and `pager` for a list. The same format applies to the root query, embedded objects and relations.
244
+ REST record routes accept one query parameter, `scope`, containing a JSON array `[fields, arguments?]`. The first object selects fields; the optional second object supplies `where`, `order` and `pager` for a list. The same format applies to the root query, embedded objects and relations.
292
245
 
293
246
  Select users and their movies with independent ordering and pagination:
294
247
 
@@ -340,13 +293,71 @@ Omitting `scope` returns own fields, as with `[{"*":true}]`. An empty selection
340
293
 
341
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.
342
295
 
343
- OpenAPI remains at version 3.0.3. It cannot define a separate schema for each array position: the documentation describes the elements, and the server strictly validates their order.
296
+ ### Nested writes
297
+
298
+ Storage keys such as `genreIds: ["1"]` only set a relation. Relation fields also accept records to create or update:
299
+
300
+ ```http
301
+ PATCH /movies/1
302
+ Content-Type: application/json
303
+
304
+ {
305
+ "actors": [
306
+ {
307
+ "userId": "1",
308
+ "genres": [
309
+ "1",
310
+ { "id": "2", "name": "Updated genre" },
311
+ { "name": "New genre" }
312
+ ]
313
+ }
314
+ ]
315
+ }
316
+ ```
317
+
318
+ | Relation value | Behavior |
319
+ |---|---|
320
+ | A key, such as `"1"` | Link an existing record without changing it |
321
+ | An object with a primary key | PATCH updates supplied fields; PUT replaces the related record |
322
+ | An object without a primary key | Create a related record with defaults and a generated key |
323
+
324
+ The key name and type follow the target model. An object containing only a key still counts as an update: in PUT it must include the model's required fields. Replacement preserves primary keys, generated values and `readOnly` fields. In POST, nested objects with existing keys receive partial updates. A missing target is an error; creating a nested record without a key requires an autogenerated primary key.
325
+
326
+ A supplied list replaces the relation's membership. PATCH preserves omitted relations; PUT clears omitted writable links. `[]` clears a list and `null` clears a nullable single relation. Removing a link does not delete the related record. Required relations must remain populated.
327
+
328
+ Use either the relation field or its storage key in an object, for example `genres` or `genreIds`. 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.
329
+
330
+ 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.
331
+
332
+ GraphQL accepts typed objects in relation fields. To change only the links in a replace mutation, use storage keys such as `genreIds`. For example:
333
+
334
+ ```graphql
335
+ mutation {
336
+ movieUpdate(id: "1", data: {
337
+ actors: [{
338
+ userId: "1"
339
+ genres: [{ id: "2", name: "Updated genre" }, { name: "New genre" }]
340
+ }]
341
+ }) {
342
+ actors { data { genres { data { id name } } } }
343
+ }
344
+ }
345
+ ```
344
346
 
345
347
  ### GraphQL
346
348
 
349
+ Set `database.schema` and enable `graphql.enabled: true` in the configuration, or start the server with `--graphql`:
350
+
351
+ ```sh
352
+ npx deep-json-server --graphql server.config.js
353
+ ```
354
+
355
+ Send requests to `/graphql` using POST with `Content-Type: application/json` and a body of `{ "query": "…", "variables": {} }`. Change the path through `graphql.endpoint`.
356
+
347
357
  ```graphql
348
358
  query {
349
359
  userList(
360
+ where: { fullName: { contains: "Мира" } }
350
361
  order: [{ field: fullName, direction: ASC }]
351
362
  pager: { page: 1, pageSize: 20 }
352
363
  ) {
@@ -354,7 +365,11 @@ query {
354
365
  data {
355
366
  id
356
367
  fullName
357
- movies(order: [{ field: title, direction: ASC }], pager: { pageSize: 5 }) {
368
+ movies(
369
+ where: { title: { contains: "Тени" } }
370
+ order: [{ field: title, direction: ASC }]
371
+ pager: { pageSize: 5 }
372
+ ) {
358
373
  total
359
374
  data { id title }
360
375
  }
@@ -365,7 +380,243 @@ query {
365
380
 
366
381
  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.
367
382
 
368
- 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`.
383
+ 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.
384
+
385
+ 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.
386
+
387
+ ## OpenAPI and schema exports
388
+
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
+ ```
400
+
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:
402
+
403
+ ```js
404
+ export default {
405
+ database: { schema: './schema.json' },
406
+ openapi: { path: './generated/openapi.yaml' },
407
+ graphql: { path: './generated/schema.graphql' },
408
+ };
409
+ ```
410
+
411
+ Each format needs its own output file. The command rejects destinations that would overwrite the configuration, database, schema, auth users or file metadata.
412
+
413
+ ## Authentication
414
+
415
+ The `auth` section enables REST login and protects record changes in REST and GraphQL. Reads and all file operations remain public.
416
+
417
+ Create an auth user in a separate file with `setup-auth.mjs`:
418
+
419
+ ```js
420
+ import { writeFile } from 'node:fs/promises';
421
+ import { hashPassword } from '@kollors/deep-json-server/auth';
422
+
423
+ const password = process.env.DJS_PASSWORD;
424
+ 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 });
428
+ ```
429
+
430
+ Set `DJS_PASSWORD` and run `node setup-auth.mjs`. Add the file to your server configuration:
431
+
432
+ ```js
433
+ export default {
434
+ database: { path: './database.json' },
435
+ auth: { users: './auth.json', expiresIn: 3600 },
436
+ };
437
+ ```
438
+
439
+ Start with `npx deep-json-server server.config.js`. The presence of `auth` enables the module; omit the section to leave record changes open. Each 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. Auth users are stored separately from database collections. The file is read at startup; restart the server after editing it.
440
+
441
+ | REST request | Input | Response |
442
+ |---|---|---|
443
+ | `POST /auth/login` | JSON `{ "username": "admin", "password": "…" }` | `{ accessToken, expiresIn, user: { id, username, isAdmin } }` |
444
+ | `GET /auth/me` | Bearer token | `{ id, username, isAdmin }` |
445
+ | `POST /auth/logout` | Bearer token | `{ success: true }` |
446
+
447
+ Pass the token in `Authorization: Bearer <accessToken>`. Incorrect credentials and invalid or expired tokens return HTTP 401. Sessions are kept in memory and disappear on restart; logout revokes the supplied token. Login can return HTTP 429 when the server has too many concurrent login attempts or active sessions.
448
+
449
+ OpenAPI describes Bearer authentication for record changes, `/auth/me` and `/auth/logout`. 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. GraphQL and OpenAPI require `database.schema`.
450
+
451
+ ## Record dates, deletion and ownership
452
+
453
+ Configure defaults in `database`:
454
+
455
+ ```js
456
+ export default {
457
+ database: {
458
+ path: './database.json',
459
+ schema: './schema.json',
460
+ timestamps: true,
461
+ softDelete: true,
462
+ },
463
+ auth: { users: './auth.json' },
464
+ };
465
+ ```
466
+
467
+ 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:
468
+
469
+ ```json
470
+ {
471
+ "Note": {
472
+ "collection": "notes",
473
+ "timestamps": false,
474
+ "softDelete": true,
475
+ "fields": {
476
+ "id": { "type": "string", "primary": true, "generated": "uuid" },
477
+ "text": { "type": "string", "required": true }
478
+ }
479
+ }
480
+ }
481
+ ```
482
+
483
+ | Field | Enabled by | Meaning |
484
+ |---|---|---|
485
+ | `createdAt`, `updatedAt` | `timestamps` | Creation and last update time |
486
+ | `deletedAt` | `softDelete` | Deletion time, or `null` for an active record |
487
+ | `createdById`, `updatedById` | `auth` | Creator/owner and last editor |
488
+ | `deletedById` | `auth` + `softDelete` | User who deleted the record, or `null` |
489
+
490
+ 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.
491
+
492
+ 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.
493
+
494
+ Lists return active records by default. To select deleted records, specify `deletedAt` in `where`:
495
+
496
+ ```json
497
+ [
498
+ { "id": true, "deletedAt": true },
499
+ { "where": { "deletedAt": { "ne": null } } }
500
+ ]
501
+ ```
502
+
503
+ Use `eq: null` for active records, `ne: null` for deleted records, or combine both with `or` for all records. The same filters work in GraphQL. An explicit `deletedAt` condition, including one inside `and`, `or` or `not`, replaces the default at that level. Relation filters and relation lists apply this rule independently. Singular relations hide deleted targets; primary-key queries can still retrieve them directly.
504
+
505
+ Cascade deletion follows `onDelete` and each affected model's `softDelete`. Restoring the record that initiated a cascade also restores records deleted by that operation, excluding those deleted earlier. Physically deleted records cannot be recovered. If a cascade removed an embedded object, restoration requires the affected object field to remain unchanged since deletion. A conflict or missing required relation rejects the whole operation.
506
+
507
+ 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.
508
+
509
+ 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 an existing record without changing it does not require owning it. Each mutation is atomic: denied changes leave all affected records unchanged.
510
+
511
+ 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.
512
+
513
+ ## Files
514
+
515
+ Files are available through REST and documented in OpenAPI. Add storage to the configuration:
516
+
517
+ ```js
518
+ export default {
519
+ database: { path: './database.json' },
520
+ files: { directory: './uploads', metadata: './files.json' },
521
+ };
522
+ ```
523
+
524
+ Start the server:
525
+
526
+ ```bash
527
+ npx deep-json-server server.config.js
528
+ ```
529
+
530
+ 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.
531
+
532
+ 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:
533
+
534
+ ```http
535
+ POST /_files/storage
536
+ Content-Name: shadows-of-ardenia.jpg
537
+ Content-Directory: posters
538
+ Content-Type: image/jpeg
539
+
540
+ <binary body>
541
+ ```
542
+
543
+ A new file returns status `201` and its computed metadata:
544
+
545
+ ```json
546
+ {
547
+ "directory": "posters",
548
+ "downloadUrl": "/_files/download/posters/shadows-of-ardenia.jpg",
549
+ "metadataUrl": "/_files/metadata/posters/shadows-of-ardenia.jpg",
550
+ "mimeType": "image/jpeg",
551
+ "name": "shadows-of-ardenia.jpg",
552
+ "size": 182340,
553
+ "url": "/_files/storage/posters/shadows-of-ardenia.jpg"
554
+ }
555
+ ```
556
+
557
+ The combination of `directory` and `name` identifies a file. Uploading to an existing path returns `409`. Pass `Content-Override: true` to replace it; a successful replacement returns `200`. The server supports these file routes:
558
+
559
+ ```text
560
+ POST /_files/storage Upload or replace a file
561
+ GET /_files/storage/* Return file contents inline
562
+ PATCH /_files/storage/* Rename or move a file
563
+ DELETE /_files/storage/* Delete a file
564
+
565
+ GET /_files/metadata/* Return file metadata as JSON
566
+ GET /_files/download/* Download a file as an attachment
567
+ ```
568
+
569
+ Rename, move, or perform both operations with a JSON body. At least one field is required:
570
+
571
+ ```http
572
+ PATCH /_files/storage/posters/shadows-of-ardenia.jpg
573
+ Content-Type: application/json
574
+
575
+ {
576
+ "directory": "archive/posters",
577
+ "name": "ardenia-shadows.jpg"
578
+ }
579
+ ```
580
+
581
+ `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.
582
+
583
+ 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.
584
+
585
+ 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.
586
+
587
+ 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.
588
+
589
+ ## Programmatic API
590
+
591
+ ```js
592
+ import { createServer } from '@kollors/deep-json-server/server';
593
+ import config from './server.config.js';
594
+
595
+ const facade = await createServer(config);
596
+ const server = facade.fastify();
597
+ await server.listen();
598
+ // await server.close();
599
+ ```
600
+
601
+ 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.
602
+
603
+ 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.
604
+
605
+ The root import `@kollors/deep-json-server` also provides these functions. Server adapters load when enabled. Generators can be used independently:
606
+
607
+ ```js
608
+ import { generateOpenapi, writeOpenapi } from '@kollors/deep-json-server/openapi';
609
+ import { generateGraphql, writeGraphql } from '@kollors/deep-json-server/graphql';
610
+
611
+ const document = await generateOpenapi('./schema.json', { files: true });
612
+ const sdl = await generateGraphql('./schema.json');
613
+ await writeOpenapi(document, './generated/openapi.yaml');
614
+ await writeGraphql(sdl, './generated/schema.graphql');
615
+ ```
616
+
617
+ 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.
618
+
619
+ `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`.
369
620
 
370
621
  ## Example database
371
622
 
@@ -471,106 +722,13 @@ String primary keys use GraphQL `ID`; ordinary strings use `String`, numbers use
471
722
  }
472
723
  ```
473
724
 
474
- ## Files
475
-
476
- Add `files.directory` and `files.metadata` to the configuration and start the server:
477
-
478
- ```bash
479
- deep-json-server server.config.js
480
- ```
481
-
482
- 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.
483
-
484
- 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:
485
-
486
- ```http
487
- POST /_files/storage
488
- Content-Name: shadows-of-ardenia.jpg
489
- Content-Directory: posters
490
- Content-Type: image/jpeg
491
-
492
- <binary body>
493
- ```
494
-
495
- A new file returns status `201` and its computed metadata:
496
-
497
- ```json
498
- {
499
- "directory": "posters",
500
- "downloadUrl": "/_files/download/posters/shadows-of-ardenia.jpg",
501
- "metadataUrl": "/_files/metadata/posters/shadows-of-ardenia.jpg",
502
- "mimeType": "image/jpeg",
503
- "name": "shadows-of-ardenia.jpg",
504
- "size": 182340,
505
- "url": "/_files/storage/posters/shadows-of-ardenia.jpg"
506
- }
507
- ```
508
-
509
- The combination of `directory` and `name` identifies a file. Uploading to an existing path returns `409`. Pass `Content-Override: true` to replace it; a successful replacement returns `200`. The server supports these file routes:
510
-
511
- ```text
512
- POST /_files/storage Upload or replace a file
513
- GET /_files/storage/* Return file contents inline
514
- PATCH /_files/storage/* Rename or move a file
515
- DELETE /_files/storage/* Delete a file
516
-
517
- GET /_files/metadata/* Return file metadata as JSON
518
- GET /_files/download/* Download a file as an attachment
519
- ```
520
-
521
- Rename, move, or perform both operations with a JSON body. At least one field is required:
522
-
523
- ```http
524
- PATCH /_files/storage/posters/shadows-of-ardenia.jpg
525
- Content-Type: application/json
526
-
527
- {
528
- "directory": "archive/posters",
529
- "name": "ardenia-shadows.jpg"
530
- }
531
- ```
532
-
533
- `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.
534
-
535
- 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.
536
-
537
- 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.
538
-
539
- 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.
540
-
541
- ## Programmatic API
542
-
543
- ```js
544
- import { createServer } from '@kollors/deep-json-server/server';
545
- import config from './server.config.js';
546
-
547
- const facade = await createServer(config);
548
- const server = facade.fastify();
549
- await server.listen();
550
- // await server.close();
551
- ```
552
-
553
- 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 })`.
554
-
555
- The root import `@kollors/deep-json-server` also provides these functions. Server adapters load when enabled. Generators can be used independently:
556
-
557
- ```js
558
- import { generateOpenapi, writeOpenapi } from '@kollors/deep-json-server/openapi';
559
- import { generateGraphql, writeGraphql } from '@kollors/deep-json-server/graphql';
560
-
561
- const document = await generateOpenapi('./schema.json', { files: true });
562
- const sdl = await generateGraphql('./schema.json');
563
- await writeOpenapi(document, './generated/openapi.yaml');
564
- await writeGraphql(sdl, './generated/schema.graphql');
565
- ```
566
-
567
- `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`.
568
-
569
- ## Storage and development
725
+ ## Data storage
570
726
 
571
727
  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.
572
728
 
573
- The server is intended for mocking APIs. Implement authentication and password hashing in your application if needed.
729
+ ## Development
730
+
731
+ Source modules: `rest`, `graphql`, `openapi`, `auth`, `files`, `cli` and `server`. Shared models, storage, queries and mutation rules live in `core`. `server` connects the modules; API generators load independently of the HTTP runtime.
574
732
 
575
733
  ```sh
576
734
  npm ci
@@ -579,6 +737,6 @@ npm run verify
579
737
 
580
738
  The command checks types, code style, test coverage and installation from the package archive.
581
739
 
582
- 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`.
740
+ 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`.
583
741
 
584
742
  License: MIT.