@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.
- package/README.md +346 -188
- package/README.ru.md +343 -185
- package/dist/bin/deep-json-server.js +1 -1
- package/dist/bin/deep-json-server.js.map +1 -1
- package/dist/index.d.ts +12 -8
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/src/auth/contract.d.ts +27 -0
- package/dist/src/auth/contract.js +5 -0
- package/dist/src/auth/contract.js.map +1 -0
- package/dist/src/auth/password.d.ts +13 -0
- package/dist/src/auth/password.js +37 -0
- package/dist/src/auth/password.js.map +1 -0
- package/dist/src/auth/public.d.ts +3 -0
- package/dist/src/auth/public.js +5 -0
- package/dist/src/auth/public.js.map +1 -0
- package/dist/src/auth/routes.d.ts +6 -0
- package/dist/src/auth/routes.js +15 -0
- package/dist/src/auth/routes.js.map +1 -0
- package/dist/src/auth/service.d.ts +39 -0
- package/dist/src/auth/service.js +143 -0
- package/dist/src/auth/service.js.map +1 -0
- package/dist/src/cli/index.d.ts +7 -0
- package/dist/src/{cli.js → cli/index.js} +32 -12
- package/dist/src/cli/index.js.map +1 -0
- package/dist/src/{constants.d.ts → core/constants.d.ts} +1 -1
- package/dist/src/{constants.js → core/constants.js} +1 -1
- package/dist/src/core/constants.js.map +1 -0
- package/dist/src/core/database.d.ts +50 -0
- package/dist/src/{database.js → core/database.js} +21 -1
- package/dist/src/core/database.js.map +1 -0
- package/dist/src/core/engine.d.ts +64 -0
- package/dist/src/{engine.js → core/engine.js} +64 -18
- package/dist/src/core/engine.js.map +1 -0
- package/dist/src/core/errors.d.ts +9 -0
- package/dist/src/core/errors.js +12 -0
- package/dist/src/core/errors.js.map +1 -0
- package/dist/src/core/http-errors.d.ts +9 -0
- package/dist/src/core/http-errors.js +15 -0
- package/dist/src/core/http-errors.js.map +1 -0
- package/dist/src/core/lifecycle/mutation.d.ts +49 -0
- package/dist/src/core/lifecycle/mutation.js +170 -0
- package/dist/src/core/lifecycle/mutation.js.map +1 -0
- package/dist/src/core/lifecycle/options.d.ts +20 -0
- package/dist/src/core/lifecycle/options.js +21 -0
- package/dist/src/core/lifecycle/options.js.map +1 -0
- package/dist/src/core/model.d.ts +130 -0
- package/dist/src/{model.js → core/model.js} +106 -10
- package/dist/src/core/model.js.map +1 -0
- package/dist/src/{mutations → core/mutations}/write.d.ts +3 -2
- package/dist/src/{mutations → core/mutations}/write.js +14 -4
- package/dist/src/core/mutations/write.js.map +1 -0
- package/dist/src/core/pagination.d.ts +10 -0
- package/dist/src/{pagination.js → core/pagination.js} +3 -0
- package/dist/src/core/pagination.js.map +1 -0
- package/dist/src/core/paths.d.ts +16 -0
- package/dist/src/{paths.js → core/paths.js} +14 -1
- package/dist/src/core/paths.js.map +1 -0
- package/dist/src/core/query/contract.d.ts +6 -0
- package/dist/src/{query → core/query}/contract.js +3 -0
- package/dist/src/core/query/contract.js.map +1 -0
- package/dist/src/core/query/filter.d.ts +6 -0
- package/dist/src/{query → core/query}/filter.js +18 -4
- package/dist/src/core/query/filter.js.map +1 -0
- package/dist/src/core/query/options.d.ts +30 -0
- package/dist/src/{query → core/query}/options.js +12 -0
- package/dist/src/core/query/options.js.map +1 -0
- package/dist/src/core/records.d.ts +46 -0
- package/dist/src/{records.js → core/records.js} +24 -3
- package/dist/src/core/records.js.map +1 -0
- package/dist/src/core/relation-metadata.d.ts +15 -0
- package/dist/src/{relation-metadata.js → core/relation-metadata.js} +7 -1
- package/dist/src/core/relation-metadata.js.map +1 -0
- package/dist/src/core/types.d.ts +10 -0
- package/dist/src/{types.js.map → core/types.js.map} +1 -1
- package/dist/src/core/utils.d.ts +56 -0
- package/dist/src/core/utils.js +102 -0
- package/dist/src/core/utils.js.map +1 -0
- package/dist/src/files/contract.d.ts +33 -0
- package/dist/src/files/contract.js +25 -3
- package/dist/src/files/contract.js.map +1 -1
- package/dist/src/files/disk-store.d.ts +3 -0
- package/dist/src/files/disk-store.js +27 -3
- package/dist/src/files/disk-store.js.map +1 -1
- package/dist/src/files/http.d.ts +8 -3
- package/dist/src/files/http.js +9 -0
- package/dist/src/files/http.js.map +1 -1
- package/dist/src/files/index.d.ts +4 -3
- package/dist/src/files/index.js +3 -1
- package/dist/src/files/index.js.map +1 -1
- package/dist/src/files/memory-store.d.ts +4 -1
- package/dist/src/files/memory-store.js +8 -2
- package/dist/src/files/memory-store.js.map +1 -1
- package/dist/src/files/routes.d.ts +3 -0
- package/dist/src/files/routes.js +25 -1
- package/dist/src/files/routes.js.map +1 -1
- package/dist/src/graphql/entry.d.ts +2 -2
- package/dist/src/graphql/entry.js.map +1 -1
- package/dist/src/graphql/lazy.d.ts +10 -0
- package/dist/src/graphql/lazy.js +13 -0
- package/dist/src/graphql/lazy.js.map +1 -0
- package/dist/src/graphql/preflight.d.ts +4 -1
- package/dist/src/graphql/preflight.js +3 -0
- package/dist/src/graphql/preflight.js.map +1 -1
- package/dist/src/graphql/public.d.ts +10 -2
- package/dist/src/graphql/public.js +10 -4
- package/dist/src/graphql/public.js.map +1 -1
- package/dist/src/graphql/resolvers.d.ts +10 -2
- package/dist/src/graphql/resolvers.js +8 -2
- package/dist/src/graphql/resolvers.js.map +1 -1
- package/dist/src/graphql/routes.d.ts +6 -2
- package/dist/src/graphql/routes.js +6 -3
- package/dist/src/graphql/routes.js.map +1 -1
- package/dist/src/graphql/schema.d.ts +6 -0
- package/dist/src/{graphql.js → graphql/schema.js} +14 -11
- package/dist/src/graphql/schema.js.map +1 -0
- package/dist/src/graphql/write.d.ts +3 -0
- package/dist/src/graphql/write.js +3 -0
- package/dist/src/graphql/write.js.map +1 -1
- package/dist/src/openapi/auth.d.ts +13 -0
- package/dist/src/openapi/auth.js +72 -0
- package/dist/src/openapi/auth.js.map +1 -0
- package/dist/src/openapi/document.d.ts +7 -3
- package/dist/src/openapi/document.js +49 -34
- package/dist/src/openapi/document.js.map +1 -1
- package/dist/src/openapi/entry.d.ts +2 -2
- package/dist/src/openapi/entry.js.map +1 -1
- package/dist/src/openapi/files.d.ts +6 -0
- package/dist/src/{files/openapi.js → openapi/files.js} +6 -3
- package/dist/src/openapi/files.js.map +1 -0
- package/dist/src/openapi/helpers.d.ts +10 -1
- package/dist/src/openapi/helpers.js +9 -0
- package/dist/src/openapi/helpers.js.map +1 -1
- package/dist/src/openapi/index.d.ts +7 -1
- package/dist/src/openapi/index.js +14 -5
- package/dist/src/openapi/index.js.map +1 -1
- package/dist/src/openapi/lazy.d.ts +11 -0
- package/dist/src/openapi/lazy.js +13 -0
- package/dist/src/openapi/lazy.js.map +1 -0
- package/dist/src/openapi/public.d.ts +11 -2
- package/dist/src/openapi/public.js +12 -4
- package/dist/src/openapi/public.js.map +1 -1
- package/dist/src/{types.d.ts → openapi/types.d.ts} +2 -10
- package/dist/src/openapi/types.js +2 -0
- package/dist/src/openapi/types.js.map +1 -0
- package/dist/src/rest/options.d.ts +11 -2
- package/dist/src/rest/options.js +12 -3
- package/dist/src/rest/options.js.map +1 -1
- package/dist/src/rest/projection.d.ts +10 -4
- package/dist/src/rest/projection.js +10 -4
- package/dist/src/rest/projection.js.map +1 -1
- package/dist/src/rest/routes.d.ts +6 -2
- package/dist/src/rest/routes.js +7 -3
- package/dist/src/rest/routes.js.map +1 -1
- package/dist/src/server/config.d.ts +79 -0
- package/dist/src/{config.js → server/config.js} +73 -12
- package/dist/src/server/config.js.map +1 -0
- package/dist/src/server/create.d.ts +18 -0
- package/dist/src/{server.js → server/create.js} +39 -21
- package/dist/src/server/create.js.map +1 -0
- package/dist/src/server/features.d.ts +15 -0
- package/dist/src/server/features.js +31 -0
- package/dist/src/server/features.js.map +1 -0
- package/dist/src/server/public.d.ts +3 -3
- package/dist/src/server/public.js +1 -1
- package/dist/src/server/public.js.map +1 -1
- package/package.json +5 -1
- package/dist/src/cli.d.ts +0 -4
- package/dist/src/cli.js.map +0 -1
- package/dist/src/config.d.ts +0 -81
- package/dist/src/config.js.map +0 -1
- package/dist/src/constants.js.map +0 -1
- package/dist/src/database.d.ts +0 -20
- package/dist/src/database.js.map +0 -1
- package/dist/src/engine.d.ts +0 -36
- package/dist/src/engine.js.map +0 -1
- package/dist/src/errors.d.ts +0 -6
- package/dist/src/errors.js +0 -9
- package/dist/src/errors.js.map +0 -1
- package/dist/src/features.d.ts +0 -8
- package/dist/src/features.js +0 -18
- package/dist/src/features.js.map +0 -1
- package/dist/src/files/openapi.d.ts +0 -3
- package/dist/src/files/openapi.js.map +0 -1
- package/dist/src/graphql.d.ts +0 -3
- package/dist/src/graphql.js.map +0 -1
- package/dist/src/http/errors.d.ts +0 -6
- package/dist/src/http/errors.js +0 -3
- package/dist/src/http/errors.js.map +0 -1
- package/dist/src/model.d.ts +0 -72
- package/dist/src/model.js.map +0 -1
- package/dist/src/mutations/write.js.map +0 -1
- package/dist/src/pagination.d.ts +0 -7
- package/dist/src/pagination.js.map +0 -1
- package/dist/src/paths.d.ts +0 -6
- package/dist/src/paths.js.map +0 -1
- package/dist/src/query/contract.d.ts +0 -3
- package/dist/src/query/contract.js.map +0 -1
- package/dist/src/query/filter.d.ts +0 -3
- package/dist/src/query/filter.js.map +0 -1
- package/dist/src/query/options.d.ts +0 -18
- package/dist/src/query/options.js.map +0 -1
- package/dist/src/records.d.ts +0 -25
- package/dist/src/records.js.map +0 -1
- package/dist/src/relation-metadata.d.ts +0 -9
- package/dist/src/relation-metadata.js.map +0 -1
- package/dist/src/schema.d.ts +0 -8
- package/dist/src/schema.js +0 -13
- package/dist/src/schema.js.map +0 -1
- package/dist/src/server.d.ts +0 -12
- package/dist/src/server.js.map +0 -1
- package/dist/src/utils.d.ts +0 -12
- package/dist/src/utils.js +0 -58
- package/dist/src/utils.js.map +0 -1
- /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
|
|
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.
|
|
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.
|
|
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` |
|
|
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
|
-
|
|
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
|
-
|
|
172
|
-
"
|
|
173
|
-
|
|
174
|
-
|
|
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
|
|
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
|
|
189
|
-
- `cascade`: delete the referring
|
|
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`.
|
|
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`.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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
|
-
##
|
|
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
|
-
|
|
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.
|