@kollors/deep-json-server 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +12 -9
  2. package/README.ru.md +12 -9
  3. package/dist/bin/deep-json-server.d.ts +2 -0
  4. package/dist/bin/deep-json-server.js +11 -0
  5. package/dist/bin/deep-json-server.js.map +1 -0
  6. package/dist/index.d.ts +5 -0
  7. package/dist/index.js +2 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/src/cli.d.ts +4 -0
  10. package/dist/src/cli.js +80 -0
  11. package/dist/src/cli.js.map +1 -0
  12. package/dist/src/config.d.ts +54 -0
  13. package/dist/src/config.js +145 -0
  14. package/dist/src/config.js.map +1 -0
  15. package/{src → dist/src}/constants.js +1 -0
  16. package/dist/src/constants.js.map +1 -0
  17. package/dist/src/database.d.ts +20 -0
  18. package/dist/src/database.js +136 -0
  19. package/dist/src/database.js.map +1 -0
  20. package/dist/src/files/contract.d.ts +67 -0
  21. package/dist/src/files/contract.js +106 -0
  22. package/dist/src/files/contract.js.map +1 -0
  23. package/dist/src/files/disk-store.d.ts +5 -0
  24. package/dist/src/files/disk-store.js +313 -0
  25. package/dist/src/files/disk-store.js.map +1 -0
  26. package/dist/src/files/index.d.ts +5 -0
  27. package/dist/src/files/index.js +6 -0
  28. package/dist/src/files/index.js.map +1 -0
  29. package/dist/src/files/memory-store.d.ts +3 -0
  30. package/dist/src/files/memory-store.js +80 -0
  31. package/dist/src/files/memory-store.js.map +1 -0
  32. package/dist/src/files/routes.d.ts +6 -0
  33. package/dist/src/files/routes.js +77 -0
  34. package/dist/src/files/routes.js.map +1 -0
  35. package/dist/src/openapi/config.d.ts +11 -0
  36. package/dist/src/openapi/config.js +204 -0
  37. package/dist/src/openapi/config.js.map +1 -0
  38. package/dist/src/openapi/document.d.ts +9 -0
  39. package/dist/src/openapi/document.js +338 -0
  40. package/dist/src/openapi/document.js.map +1 -0
  41. package/dist/src/openapi/index.d.ts +10 -0
  42. package/dist/src/openapi/index.js +27 -0
  43. package/dist/src/openapi/index.js.map +1 -0
  44. package/dist/src/openapi/inference.d.ts +14 -0
  45. package/dist/src/openapi/inference.js +145 -0
  46. package/dist/src/openapi/inference.js.map +1 -0
  47. package/dist/src/query/filter.d.ts +6 -0
  48. package/dist/src/query/filter.js +248 -0
  49. package/dist/src/query/filter.js.map +1 -0
  50. package/{src → dist/src}/query/index.js +1 -0
  51. package/dist/src/query/index.js.map +1 -0
  52. package/dist/src/query/pagination.d.ts +11 -0
  53. package/dist/src/query/pagination.js +28 -0
  54. package/dist/src/query/pagination.js.map +1 -0
  55. package/dist/src/query/sort.d.ts +1 -0
  56. package/dist/src/query/sort.js +54 -0
  57. package/dist/src/query/sort.js.map +1 -0
  58. package/dist/src/relation-metadata.d.ts +10 -0
  59. package/dist/src/relation-metadata.js +32 -0
  60. package/dist/src/relation-metadata.js.map +1 -0
  61. package/dist/src/relations.d.ts +12 -0
  62. package/dist/src/relations.js +155 -0
  63. package/dist/src/relations.js.map +1 -0
  64. package/dist/src/server.d.ts +13 -0
  65. package/dist/src/server.js +188 -0
  66. package/dist/src/server.js.map +1 -0
  67. package/dist/src/types.d.ts +52 -0
  68. package/dist/src/types.js +2 -0
  69. package/dist/src/types.js.map +1 -0
  70. package/dist/src/utils.d.ts +20 -0
  71. package/dist/src/utils.js +63 -0
  72. package/dist/src/utils.js.map +1 -0
  73. package/package.json +16 -15
  74. package/bin/deep-json-server.js +0 -11
  75. package/index.js +0 -12
  76. package/src/cli.js +0 -91
  77. package/src/config.js +0 -203
  78. package/src/database.js +0 -148
  79. package/src/files.js +0 -633
  80. package/src/openapi/config.js +0 -124
  81. package/src/openapi/document.js +0 -394
  82. package/src/openapi/index.js +0 -36
  83. package/src/openapi/inference.js +0 -187
  84. package/src/query/filter.js +0 -283
  85. package/src/query/pagination.js +0 -44
  86. package/src/query/sort.js +0 -63
  87. package/src/relation-metadata.js +0 -41
  88. package/src/relations.js +0 -173
  89. package/src/server.js +0 -250
  90. package/src/utils.js +0 -84
  91. package/types/index.d.ts +0 -21
  92. package/types/src/config.d.ts +0 -73
  93. package/types/src/database.d.ts +0 -33
  94. package/types/src/files.d.ts +0 -64
  95. package/types/src/openapi/config.d.ts +0 -3
  96. package/types/src/openapi/document.d.ts +0 -11
  97. package/types/src/openapi/index.d.ts +0 -9
  98. package/types/src/openapi/inference.d.ts +0 -9
  99. package/types/src/query/filter.d.ts +0 -3
  100. package/types/src/query/pagination.d.ts +0 -8
  101. package/types/src/query/sort.d.ts +0 -1
  102. package/types/src/relation-metadata.d.ts +0 -9
  103. package/types/src/relations.d.ts +0 -3
  104. package/types/src/server.d.ts +0 -14
  105. package/types/src/utils.d.ts +0 -18
  106. /package/{types → dist}/src/constants.d.ts +0 -0
  107. /package/{types → dist}/src/query/index.d.ts +0 -0
package/README.md CHANGED
@@ -53,7 +53,7 @@ The API is available at `http://127.0.0.1:4001` by default. For example, `GET ht
53
53
 
54
54
  ## Configuration and startup
55
55
 
56
- Create the ESM module `server.config.js`. The example below enables every feature:
56
+ Create the ESM module `server.config.js`. The example below shows settings for every feature:
57
57
 
58
58
  ```js
59
59
  import process from 'node:process';
@@ -71,6 +71,7 @@ export default {
71
71
  path: 'mock/openapi-schema.yaml',
72
72
  },
73
73
  server: {
74
+ cors: true,
74
75
  host: '127.0.0.1',
75
76
  logger: true,
76
77
  maxFileSize: 100 * 1024 * 1024,
@@ -91,6 +92,7 @@ Configuration keys:
91
92
  | `files.metadata` | Together with `files.directory` | JSON file containing file metadata on disk |
92
93
  | `files.data` | Instead of the `directory` and `metadata` pair | In-memory files with `Uint8Array` contents |
93
94
  | `openapi.path` | Required by the `--openapi` and `--openapi-only` CLI flags | Generated OpenAPI YAML file; the programmatic API can return a document without this path |
95
+ | `server.cors` | No | Enables permissive CORS headers and `OPTIONS` routes; defaults to `true` |
94
96
  | `server.host` | No | Host used by the CLI, `server.openapi()`, and argument-less `server.fastify().listen()`; defaults to `127.0.0.1` |
95
97
  | `server.logger` | No | Fastify logger settings; defaults to `true` |
96
98
  | `server.maxFileSize` | No | Maximum uploaded-file size in bytes when file routes are enabled; defaults to 100 MiB |
@@ -219,7 +221,7 @@ DELETE /movies/:id
219
221
 
220
222
  `POST` generates a string ID. `PUT` completely replaces the selected record, while `PATCH` updates only supplied fields; both preserve the existing ID and its type. An `id` supplied in any request body cannot override the server-controlled ID. All write operations — `POST`, `PUT`, `PATCH` and `DELETE` — are serialized; disk storage persists them in JSON, while memory storage retains them until the process exits.
221
223
 
222
- The database file must exist before startup. Resource names may contain Latin letters, numbers, `_` and `-`, and must start with a letter. Every resource is an array of JSON objects. Every record must have a non-empty string or finite numeric `id`; IDs must be unique within a resource when compared as strings, so `1` and `"1"` cannot coexist. The server rereads the file before every GET and write operation, so valid external edits become visible without a restart.
224
+ The database file must exist before startup. Resource names may contain Latin letters, numbers, `_` and `-`, and must start with a letter. Every resource is an array of JSON objects. Every record must have a non-empty string or finite numeric `id`; IDs must be unique within a resource when compared as strings, so `1` and `"1"` cannot coexist. All nested values must be JSON-compatible: finite numbers, strings, booleans, `null`, arrays, and plain objects. The server rereads the file before every resource GET and write operation, so valid edits to existing resources become visible immediately. Resource names and routes are discovered at startup; restart the server after adding, removing, or renaming a top-level resource.
223
225
 
224
226
  Successful writes return the created, replaced, updated or deleted record. Errors use an appropriate HTTP status and this JSON shape:
225
227
 
@@ -301,7 +303,7 @@ GET /movies?title:contains=ardenia
301
303
 
302
304
  Simple filter values recognize JSON primitives: numbers, `true`, `false` and `null`. Values with leading zeroes, such as `001`, remain strings. Unknown operators, invalid logical conditions and filter paths that do not exist in a non-empty resource return `400`.
303
305
 
304
- Multiple simple query filters are combined with `AND`. For an `in` filter, separate values with commas: `GET /movies?id:in=1,2`. On an array field, `in` means that at least one field element matches at least one supplied value. `every` returns `true` for an empty array, while `some` returns `false`.
306
+ Different simple query filters are combined with `AND`. Repeating the same equality filter selects any of its values, so `GET /movies?id=1&id=2` is equivalent to `GET /movies?id:in=1,2`. For an `in` filter, values are separated with commas. On an array field, `in` means that at least one field element matches at least one supplied value. `every` returns `true` for an empty array, while `some` returns `false`.
305
307
 
306
308
  If `_where` is present, it is the complete filter and other simple filter parameters are ignored. The examples show readable JSON; an HTTP client must URL-encode `_where` when constructing the URL manually, for example with `encodeURIComponent(JSON.stringify(where))`.
307
309
 
@@ -405,7 +407,7 @@ Content-Type: application/json
405
407
 
406
408
  `PATCH` returns the updated metadata with status `200`; a conflicting destination 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 mock-server origin.
407
409
 
408
- In disk mode, the binary is stored at `<files.directory>/<directory>/<name>`. The metadata file contains only `directory`, `mimeType`, and `name`; `size` is read from the actual file, while response URLs are computed. The server creates directories automatically. The metadata file may be absent initially and is created on the first upload. Do not edit stored files or metadata while the server is running. Metadata created by versions before this path-based API is not compatible with the new format.
410
+ In disk mode, the binary is stored at `<files.directory>/<directory>/<name>`. The metadata file contains only `directory`, `mimeType`, and `name`; `size` is read from the actual file, while response URLs are computed. The server creates directories automatically and keeps validated metadata in memory while running. Use a disk-backed database and file storage from only one server process at a time, and do not edit stored files or metadata until that process stops. Paths below `files.directory` may not contain symbolic links, and file names are restricted to values that are portable across supported operating systems. The metadata file may be absent initially and is created on the first upload. Metadata created by versions before this path-based API is not compatible with the new format.
409
411
 
410
412
  The upload is raw binary rather than `multipart/form-data`, so `XMLHttpRequest.upload.onprogress` can report progress while the browser sends a `File` directly with `xhr.send(file)`. The default maximum size is 100 MiB and can be changed through `server.maxFileSize`. Missing or unsafe headers and paths return `400`, an exceeded limit returns `413`, and a missing, malformed, or Fastify-unsupported `Content-Type` returns `400` or `415`, depending on which validation stage rejects it.
411
413
 
@@ -459,7 +461,7 @@ To include file routes in the document, configure the `files` section and add `-
459
461
 
460
462
  The generator infers resources and field types from all database records. Every inferred field is optional by default, while the top-level `id` is always required in response schemas and is omitted from create and update request schemas. Add other required fields to `required`. A nested required path marks that nested property as required; it does not automatically make every parent path required, so list the parent separately when necessary.
461
463
 
462
- Different value types are inferred independently and combined through `oneOf`. Configuration is validated before generation: `$info`, resource and schema names, and the structure of `properties` are validated, while paths from `required` and `formats` must exist in the resulting schema.
464
+ Different non-overlapping value types are inferred independently and combined through `oneOf`; mixed integers and decimal numbers are represented by one `number` schema. Configuration is validated before generation: `$info`, resource and component names, supported `properties` keywords and their value types are checked, while paths from `required` and `formats` must exist in the resulting schema. Explicit component names may contain ASCII letters, digits, dots, underscores, and hyphens.
463
465
 
464
466
  Use `properties` to describe fields that cannot be inferred, particularly for an empty resource. Explicit properties are merged with inferred properties:
465
467
 
@@ -493,7 +495,7 @@ Use `name` when a resource needs an explicit schema name instead of the automati
493
495
  }
494
496
  ```
495
497
 
496
- The generated document describes CRUD endpoints, pagination, sorting, deep filters, `_embed`, and both direct and reverse response relations inferred from `...Id` and `...Ids` fields. When `--files` is present, it also describes raw binary upload, download and deletion endpoints. A numeric database ID is described as `integer | string`, because a later `POST` creates a string ID in the same resource. The document can be used as input for tools such as RTK Query OpenAPI Codegen. OpenAPI is generated only with `--openapi` or `--openapi-only`; normal server startup does not rewrite the file.
498
+ The generated document describes CRUD endpoints, pagination, sorting, deep filters, `_embed`, and both direct and reverse response relations inferred from `...Id` and `...Ids` fields. When `--files` is present, it also describes raw binary upload, download and deletion endpoints with their actual arbitrary media types. A numeric database ID is described as `integer | string`, because a later `POST` creates a string ID in the same resource. Generation rejects duplicate schema names, operation IDs, and invalid schema overrides before producing a document. The document can be used as input for tools such as RTK Query OpenAPI Codegen. OpenAPI is generated only with `--openapi` or `--openapi-only`; normal server startup does not rewrite the file.
497
499
 
498
500
  During normal startup, request bodies are validated against the same inferred and configured schemas. `POST` and `PUT` enforce configured required fields; `PATCH` validates only fields that are actually supplied. `formats` and `properties` apply to all three methods. Unlisted additional object fields remain allowed. Invalid bodies return `400`.
499
501
 
@@ -515,6 +517,7 @@ const config = {
515
517
  path: 'mock/openapi-schema.yaml',
516
518
  },
517
519
  server: {
520
+ cors: true,
518
521
  host: '127.0.0.1',
519
522
  logger: false,
520
523
  maxFileSize: 100 * 1024 * 1024,
@@ -565,8 +568,8 @@ await memoryFastify.close();
565
568
 
566
569
  `createServer()` accepts exactly the same config shape as `server.config.js`. It loads and clones the configured sources, then returns a facade with two operations:
567
570
 
568
- | Member | Meaning |
569
- | --- | --- | --- |
571
+ | Method | Purpose |
572
+ | --- | --- |
570
573
  | `server.fastify()` | Lazily creates and caches the real Fastify instance; every native method remains available, and argument-less `listen()` uses `server.host` and `server.port` |
571
574
  | `server.openapi()` | Returns an OpenAPI document and also writes it when `openapi.path` is configured |
572
575
 
@@ -576,4 +579,4 @@ An argument-less `server.fastify().listen()` uses `server.host` and `server.port
576
579
 
577
580
  ## Scope and security
578
581
 
579
- Deep JSON Server is intended for local development and automated tests. It has no authentication or authorization, allows CORS from every origin, persists accepted writes when disk storage is configured and does not enforce referential integrity. Keep the default loopback host unless the surrounding environment provides its own access controls; do not expose the server or file routes to an untrusted network.
582
+ Deep JSON Server is intended for local development and automated tests. It has no authentication or authorization, allows CORS from every origin by default, persists accepted writes when disk storage is configured and does not enforce referential integrity. Set `server.cors` to `false` to disable the built-in CORS headers and `OPTIONS` routes. Keep the default loopback host unless the surrounding environment provides its own access controls; do not expose the server or file routes to an untrusted network.
package/README.ru.md CHANGED
@@ -53,7 +53,7 @@ npx deep-json-server server.config.js
53
53
 
54
54
  ## Конфигурация и запуск
55
55
 
56
- Создайте ESM-модуль `server.config.js`. В примере ниже включены настройки всех возможностей:
56
+ Создайте ESM-модуль `server.config.js`. В примере ниже показаны настройки всех возможностей:
57
57
 
58
58
  ```js
59
59
  import process from 'node:process';
@@ -71,6 +71,7 @@ export default {
71
71
  path: 'mock/openapi-schema.yaml',
72
72
  },
73
73
  server: {
74
+ cors: true,
74
75
  host: '127.0.0.1',
75
76
  logger: true,
76
77
  maxFileSize: 100 * 1024 * 1024,
@@ -91,6 +92,7 @@ export default {
91
92
  | `files.metadata` | Вместе с `files.directory` | JSON-файл с метаданными файлов на диске |
92
93
  | `files.data` | Вместо пары `directory` и `metadata` | Файлы в памяти с содержимым в `Uint8Array` |
93
94
  | `openapi.path` | Обязателен для CLI-флагов `--openapi` и `--openapi-only` | Генерируемый YAML-файл OpenAPI; программный API может вернуть документ без этого пути |
95
+ | `server.cors` | Необязателен | Включает разрешающие CORS-заголовки и маршруты `OPTIONS`; по умолчанию `true` |
94
96
  | `server.host` | Необязателен | Адрес для CLI, `server.openapi()` и вызова `server.fastify().listen()` без аргументов; по умолчанию `127.0.0.1` |
95
97
  | `server.logger` | Необязателен | Настройки логгера Fastify; по умолчанию `true` |
96
98
  | `server.maxFileSize` | Необязателен | Максимальный размер загружаемого файла в байтах при включённых файловых маршрутах; по умолчанию 100 МиБ |
@@ -219,7 +221,7 @@ DELETE /movies/:id
219
221
 
220
222
  `POST` генерирует строковый ID. `PUT` полностью заменяет выбранную запись, а `PATCH` изменяет только переданные поля; обе операции сохраняют существующий ID и его тип. Поле `id` в теле любого запроса не может переопределить ID, которым управляет сервер. Все операции записи — `POST`, `PUT`, `PATCH` и `DELETE` — выполняются последовательно; дисковое хранилище записывает их в JSON, а хранилище в памяти сохраняет до завершения процесса.
221
223
 
222
- Файл базы должен существовать до запуска. Имена ресурсов могут содержать латинские буквы, цифры, `_` и `-` и должны начинаться с буквы. Каждый ресурс является массивом JSON-объектов. У каждой записи должен быть непустой строковый или конечный числовой `id`; ID должны быть уникальны внутри ресурса при сравнении как строки, поэтому `1` и `"1"` не могут существовать одновременно. Перед каждым GET-запросом и изменением сервер заново читает файл, поэтому корректные внешние правки становятся видны без перезапуска.
224
+ Файл базы должен существовать до запуска. Имена ресурсов могут содержать латинские буквы, цифры, `_` и `-` и должны начинаться с буквы. Каждый ресурс является массивом JSON-объектов. У каждой записи должен быть непустой строковый или конечный числовой `id`; ID должны быть уникальны внутри ресурса при сравнении как строки, поэтому `1` и `"1"` не могут существовать одновременно. Все вложенные значения должны быть совместимы с JSON: конечные числа, строки, boolean, `null`, массивы и обычные объекты. Перед каждым GET-запросом к ресурсу и изменением сервер заново читает файл, поэтому корректные правки существующих ресурсов становятся видны сразу. Имена ресурсов и маршруты определяются при запуске; после добавления, удаления или переименования массива верхнего уровня перезапустите сервер.
223
225
 
224
226
  Успешная операция записи возвращает созданную, заменённую, обновлённую или удалённую запись. Ошибки используют подходящий HTTP-статус и следующий JSON-формат:
225
227
 
@@ -301,7 +303,7 @@ GET /movies?title:contains=тени
301
303
 
302
304
  В простых фильтрах распознаются JSON-примитивы: числа, `true`, `false` и `null`. Значения с ведущими нулями, например `001`, остаются строками. Неизвестные операторы, некорректные логические условия и отсутствующие в непустом ресурсе пути фильтра возвращают `400`.
303
305
 
304
- Несколько простых query-фильтров объединяются через `AND`. Для оператора `in` перечислите значения через запятую: `GET /movies?id:in=1,2`. Для поля-массива `in` означает, что хотя бы один элемент поля совпадает хотя бы с одним переданным значением. `every` для пустого массива возвращает `true`, а `some` — `false`.
306
+ Разные простые query-фильтры объединяются через `AND`. Повтор одного фильтра равенства выбирает любое из его значений, поэтому `GET /movies?id=1&id=2` равнозначен `GET /movies?id:in=1,2`. Для оператора `in` значения перечисляются через запятую. Для поля-массива `in` означает, что хотя бы один элемент поля совпадает хотя бы с одним переданным значением. `every` для пустого массива возвращает `true`, а `some` — `false`.
305
307
 
306
308
  Если передан `_where`, он становится полным фильтром, а остальные простые параметры фильтрации игнорируются. В примерах JSON оставлен читаемым; при ручном формировании URL HTTP-клиент должен закодировать значение `_where`, например через `encodeURIComponent(JSON.stringify(where))`.
307
309
 
@@ -405,7 +407,7 @@ Content-Type: application/json
405
407
 
406
408
  `PATCH` возвращает обновлённые метаданные со статусом `200`; занятый конечный путь возвращает `409`. `DELETE` отвечает статусом `204` без тела. Если файл не найден, любая операция по пути возвращает `404`. Пути в URL задаются относительно `files.directory`, а все возвращаемые URL — относительно адреса mock-сервера.
407
409
 
408
- При хранении на диске бинарный файл находится по пути `<files.directory>/<directory>/<name>`. Файл метаданных содержит только `directory`, `mimeType` и `name`; `size` считывается у фактического файла, а URL вычисляются. Сервер автоматически создаёт директории. Изначально файл метаданных может отсутствовать: он создаётся при первой загрузке. Не редактируйте сохранённые файлы или метаданные во время работы сервера. Метаданные, созданные версиями до перехода на адресацию по пути, несовместимы с новым форматом.
410
+ При хранении на диске бинарный файл находится по пути `<files.directory>/<directory>/<name>`. Файл метаданных содержит только `directory`, `mimeType` и `name`; `size` считывается у фактического файла, а URL вычисляются. Используйте дисковую базу и файловое хранилище только из одного процесса сервера одновременно и не редактируйте сохранённые файлы или метаданные до его остановки. Сервер автоматически создаёт директории, пути внутри `files.directory` не могут содержать символические ссылки, а имена файлов ограничены переносимыми между поддерживаемыми операционными системами значениями. Изначально файл метаданных может отсутствовать: он создаётся при первой загрузке. Метаданные, созданные версиями до перехода на адресацию по пути, несовместимы с новым форматом.
409
411
 
410
412
  Используется бинарное тело запроса, а не `multipart/form-data`, поэтому `XMLHttpRequest.upload.onprogress` может показывать прогресс при непосредственной отправке `File` через `xhr.send(file)`. Максимальный размер по умолчанию равен 100 МиБ и настраивается через `server.maxFileSize`. Отсутствующие или небезопасные заголовки и пути возвращают `400`, превышение лимита — `413`, а отсутствующий, некорректный или не поддерживаемый Fastify `Content-Type` — `400` либо `415` в зависимости от этапа проверки.
411
413
 
@@ -459,7 +461,7 @@ deep-json-server --openapi-only server.config.js
459
461
 
460
462
  Генератор определяет ресурсы и типы полей по всем записям базы. По умолчанию все найденные поля необязательные, а поле `id` верхнего уровня всегда обязательное в схемах ответа и исключается из схем создания и обновления. Остальные обязательные поля перечисляются в `required`. Вложенный обязательный путь делает обязательным само вложенное свойство, но не все его родительские пути; при необходимости родителя нужно перечислить отдельно.
461
463
 
462
- Разные типы значений определяются независимо и объединяются через `oneOf`. Перед генерацией проверяются `$info`, имена ресурсов и схем, а также структура `properties`; пути из `required` и `formats` должны существовать в итоговой схеме.
464
+ Разные непересекающиеся типы значений определяются независимо и объединяются через `oneOf`; сочетание целых и дробных чисел описывается одной схемой `number`. Перед генерацией проверяются `$info`, имена ресурсов и компонентов, поддерживаемые ключи `properties` и типы их значений; пути из `required` и `formats` должны существовать в итоговой схеме. Явные имена компонентов могут содержать латинские буквы, цифры, точки, подчёркивания и дефисы.
463
465
 
464
466
  Используйте `properties`, чтобы описать поля, которые невозможно определить автоматически, особенно у пустого ресурса. Явно заданные свойства объединяются с найденными автоматически:
465
467
 
@@ -493,7 +495,7 @@ deep-json-server --openapi-only server.config.js
493
495
  }
494
496
  ```
495
497
 
496
- В сгенерированном документе описаны CRUD, пагинация, сортировка, глубокие фильтры, `_embed`, а также прямые и обратные связи в ответах, определённые по полям `...Id` и `...Ids`. При наличии `--files` в него также добавляются маршруты бинарной загрузки, получения и удаления файлов. Числовой ID базы описывается как `integer | string`, поскольку последующий `POST` создаст строковый ID в том же ресурсе. Файл можно передать, например, в RTK Query OpenAPI Codegen. OpenAPI создаётся только с параметром `--openapi` или `--openapi-only`; обычный запуск сервера файл не перезаписывает.
498
+ В сгенерированном документе описаны CRUD, пагинация, сортировка, глубокие фильтры, `_embed`, а также прямые и обратные связи в ответах, определённые по полям `...Id` и `...Ids`. При наличии `--files` в него также добавляются маршруты бинарной загрузки, получения и удаления файлов с фактическими произвольными MIME-типами. Числовой ID базы описывается как `integer | string`, поскольку последующий `POST` создаст строковый ID в том же ресурсе. До создания документа генератор отклоняет повторяющиеся имена схем и `operationId`, а также некорректные переопределения схем. Файл можно передать, например, в RTK Query OpenAPI Codegen. OpenAPI создаётся только с параметром `--openapi` или `--openapi-only`; обычный запуск сервера файл не перезаписывает.
497
499
 
498
500
  При обычном запуске тела запросов проверяются по тем же автоматически выведенным и настроенным схемам. Для `POST` и `PUT` проверяются обязательные поля из `required`; `PATCH` проверяет только фактически переданные поля. Настройки `formats` и `properties` применяются ко всем трём методам. Не перечисленные дополнительные поля объекта остаются разрешёнными. Некорректные тела возвращают `400`.
499
501
 
@@ -515,6 +517,7 @@ const config = {
515
517
  path: 'mock/openapi-schema.yaml',
516
518
  },
517
519
  server: {
520
+ cors: true,
518
521
  host: '127.0.0.1',
519
522
  logger: false,
520
523
  maxFileSize: 100 * 1024 * 1024,
@@ -565,8 +568,8 @@ await memoryFastify.close();
565
568
 
566
569
  `createServer()` принимает точно ту же структуру конфига, что и `server.config.js`. Функция загружает и клонирует настроенные источники, а затем возвращает фасад с двумя операциями:
567
570
 
568
- | Член | Назначение |
569
- | --- | --- | --- |
571
+ | Метод | Назначение |
572
+ | --- | --- |
570
573
  | `server.fastify()` | Лениво создаёт и кэширует настоящий экземпляр Fastify; все нативные методы доступны, а `listen()` без аргументов использует `server.host` и `server.port` |
571
574
  | `server.openapi()` | Возвращает документ OpenAPI и дополнительно записывает его, если настроен `openapi.path` |
572
575
 
@@ -576,4 +579,4 @@ await memoryFastify.close();
576
579
 
577
580
  ## Назначение и безопасность
578
581
 
579
- Deep JSON Server предназначен для локальной разработки и автоматических тестов. В нём нет аутентификации и авторизации, CORS разрешён для любого источника, при дисковом хранилище принятые изменения сохраняются в настроенные файлы, а ссылочная целостность не проверяется. Оставляйте адрес loopback по умолчанию, если внешняя среда не предоставляет собственный контроль доступа; не открывайте сервер и файловые маршруты для недоверенной сети.
582
+ Deep JSON Server предназначен для локальной разработки и автоматических тестов. В нём нет аутентификации и авторизации, CORS по умолчанию разрешён для любого источника, при дисковом хранилище принятые изменения сохраняются в настроенные файлы, а ссылочная целостность не проверяется. Установите `server.cors: false`, чтобы отключить встроенные CORS-заголовки и маршруты `OPTIONS`. Оставляйте адрес loopback по умолчанию, если внешняя среда не предоставляет собственный контроль доступа; не открывайте сервер и файловые маршруты для недоверенной сети.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ import process from 'node:process';
3
+ import { runCli } from '../src/cli.js';
4
+ try {
5
+ await runCli();
6
+ }
7
+ catch (error) {
8
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
9
+ process.exitCode = 1;
10
+ }
11
+ //# sourceMappingURL=deep-json-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deep-json-server.js","sourceRoot":"","sources":["../../bin/deep-json-server.ts"],"names":[],"mappings":";AAEA,OAAO,OAAO,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvC,IAAI,CAAC;IACH,MAAM,MAAM,EAAE,CAAC;AACjB,CAAC;AAAC,OAAO,KAAK,EAAE,CAAC;IACf,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACpF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC"}
@@ -0,0 +1,5 @@
1
+ export type { DatabaseConfig, DeepJsonServerConfig, FilesConfig, MemoryFile, OpenapiConfig, ServerConfig } from './src/config.js';
2
+ export type { FileMetadata, FileUpdate } from './src/files/contract.js';
3
+ export type { ServerFacade } from './src/server.js';
4
+ export { createServer } from './src/server.js';
5
+ export type { OpenapiDocument } from './src/types.js';
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { createServer } from './src/server.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { createServer } from './server.js';
2
+ export declare function runCli(args?: string[], services?: {
3
+ createServer: typeof createServer;
4
+ }): Promise<void>;
@@ -0,0 +1,80 @@
1
+ import process from 'node:process';
2
+ import { readServerConfig } from './config.js';
3
+ import { DEFAULT_HOST, DEFAULT_PORT } from './constants.js';
4
+ import { createServer } from './server.js';
5
+ const HELP_TEXT = `Deep JSON Server
6
+
7
+ Использование:
8
+ deep-json-server [--files] [--openapi | --openapi-only] <server.config.js>
9
+
10
+ Параметры:
11
+ --files Добавить файловые маршруты в сервер и OpenAPI
12
+ --openapi Сгенерировать OpenAPI и запустить сервер
13
+ --openapi-only Сгенерировать OpenAPI и завершить работу
14
+ --help Показать справку`;
15
+ const parseArguments = (args) => {
16
+ const options = { files: false, openapiMode: 'none' };
17
+ let configPath;
18
+ args.forEach((argument) => {
19
+ if (argument === '--files') {
20
+ options.files = true;
21
+ }
22
+ else if (argument === '--openapi') {
23
+ if (options.openapiMode !== 'none') {
24
+ throw new Error('Параметры --openapi и --openapi-only нельзя использовать одновременно');
25
+ }
26
+ options.openapiMode = 'generate';
27
+ }
28
+ else if (argument === '--openapi-only') {
29
+ if (options.openapiMode !== 'none') {
30
+ throw new Error('Параметры --openapi и --openapi-only нельзя использовать одновременно');
31
+ }
32
+ options.openapiMode = 'only';
33
+ }
34
+ else if (argument.startsWith('-')) {
35
+ throw new Error(`Неизвестный параметр: ${argument}`);
36
+ }
37
+ else if (configPath == null) {
38
+ configPath = argument;
39
+ }
40
+ else {
41
+ throw new Error('Можно указать только один файл конфигурации');
42
+ }
43
+ });
44
+ if (configPath == null) {
45
+ throw new Error('Укажите путь к файлу конфигурации');
46
+ }
47
+ return { configPath, ...options };
48
+ };
49
+ const validateModeConfig = (config, { files, openapiMode }) => {
50
+ if (openapiMode !== 'none' && config.openapi.path == null) {
51
+ throw new Error(`Для --openapi${openapiMode === 'only' ? '-only' : ''} укажите ключ config.openapi.path`);
52
+ }
53
+ if (files && config.files == null) {
54
+ throw new Error('Для --files укажите секцию config.files');
55
+ }
56
+ };
57
+ export async function runCli(args = process.argv.slice(2), services = { createServer }) {
58
+ if (args.includes('--help')) {
59
+ process.stdout.write(`${HELP_TEXT}\n`);
60
+ return;
61
+ }
62
+ const { configPath, files, openapiMode } = parseArguments(args);
63
+ const config = await readServerConfig(configPath);
64
+ const host = config.server.host ?? process.env.HOST ?? DEFAULT_HOST;
65
+ const port = config.server.port ?? Number(process.env.PORT ?? DEFAULT_PORT);
66
+ validateModeConfig(config, { files, openapiMode });
67
+ const runtimeConfig = { ...config, server: { ...config.server, host, port } };
68
+ const serverFacade = await services.createServer(runtimeConfig, { files });
69
+ if (openapiMode !== 'none') {
70
+ await serverFacade.openapi();
71
+ process.stdout.write(`OpenAPI-схема сохранена в ${config.openapi.path}\n`);
72
+ }
73
+ if (openapiMode === 'only') {
74
+ return;
75
+ }
76
+ const fastify = serverFacade.fastify();
77
+ await fastify.listen();
78
+ fastify.log.info({ database: 'path' in config.database ? config.database.path : 'memory' }, 'Deep JSON Server запущен');
79
+ }
80
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ import type { FastifyServerOptions } from 'fastify';
2
+ import type { DatabaseData, JsonObject } from './types.js';
3
+ export type DatabaseSchema = JsonObject;
4
+ export type DatabaseConfig = {
5
+ data: DatabaseData;
6
+ path?: never;
7
+ schema?: DatabaseSchema | string;
8
+ } | {
9
+ data?: never;
10
+ path: string;
11
+ schema?: DatabaseSchema | string;
12
+ };
13
+ export interface MemoryFile {
14
+ content: Uint8Array;
15
+ directory?: string;
16
+ mimeType: string;
17
+ name: string;
18
+ }
19
+ export type FilesConfig = {
20
+ data: MemoryFile[];
21
+ directory?: never;
22
+ metadata?: never;
23
+ } | {
24
+ data?: never;
25
+ directory: string;
26
+ metadata: string;
27
+ };
28
+ export interface OpenapiConfig {
29
+ path?: string;
30
+ }
31
+ export interface ServerConfig {
32
+ cors?: boolean;
33
+ host?: string;
34
+ logger?: FastifyServerOptions['logger'];
35
+ maxFileSize?: number;
36
+ maxPageSize?: number;
37
+ port?: number;
38
+ }
39
+ export interface DeepJsonServerConfig {
40
+ database: DatabaseConfig;
41
+ files?: FilesConfig;
42
+ openapi?: OpenapiConfig;
43
+ server?: ServerConfig;
44
+ }
45
+ export interface NormalizedServerConfig {
46
+ database: DatabaseConfig;
47
+ files?: FilesConfig;
48
+ openapi: OpenapiConfig;
49
+ server: ServerConfig;
50
+ }
51
+ /** Validates configuration and resolves relative paths. */
52
+ export declare const normalizeServerConfig: (config: DeepJsonServerConfig, directoryPath?: string) => NormalizedServerConfig;
53
+ /** Loads an ES module config and resolves paths from its directory. */
54
+ export declare function readServerConfig(configPath: string): Promise<NormalizedServerConfig>;
@@ -0,0 +1,145 @@
1
+ import { dirname, resolve } from 'node:path';
2
+ import { pathToFileURL } from 'node:url';
3
+ import { assertKnownKeys, isObject } from './utils.js';
4
+ const CONFIG_KEYS = new Set(['database', 'files', 'openapi', 'server']);
5
+ const DATABASE_KEYS = new Set(['data', 'path', 'schema']);
6
+ const FILES_KEYS = new Set(['data', 'directory', 'metadata']);
7
+ const OPENAPI_KEYS = new Set(['path']);
8
+ const SERVER_KEYS = new Set(['cors', 'host', 'logger', 'maxFileSize', 'maxPageSize', 'port']);
9
+ let configImportIndex = 0;
10
+ function getObject(value, path, required = false) {
11
+ if (value == null && !required) {
12
+ return undefined;
13
+ }
14
+ if (!isObject(value)) {
15
+ throw new Error(`Ключ ${path} должен быть JSON-объектом`);
16
+ }
17
+ return value;
18
+ }
19
+ function getString(value, path, required = false) {
20
+ if (value == null && !required) {
21
+ return undefined;
22
+ }
23
+ if (typeof value !== 'string' || value.trim() === '') {
24
+ throw new Error(`Ключ ${path} должен содержать непустую строку`);
25
+ }
26
+ return value;
27
+ }
28
+ const resolveConfigPath = (value, directoryPath) => (value == null ? undefined : resolve(directoryPath, value));
29
+ const getPositiveInteger = (value, path) => {
30
+ if (value == null) {
31
+ return undefined;
32
+ }
33
+ if (typeof value !== 'number' || !Number.isInteger(value) || value < 1) {
34
+ throw new Error(`Ключ ${path} должен быть положительным целым числом`);
35
+ }
36
+ return value;
37
+ };
38
+ const normalizeSchema = (schema, directoryPath) => {
39
+ if (schema == null) {
40
+ return undefined;
41
+ }
42
+ if (typeof schema === 'string') {
43
+ return resolveConfigPath(getString(schema, 'config.database.schema', true), directoryPath);
44
+ }
45
+ return getObject(schema, 'config.database.schema', true);
46
+ };
47
+ const normalizeDatabase = (value, directoryPath) => {
48
+ const database = getObject(value, 'config.database', true);
49
+ assertKnownKeys(database, DATABASE_KEYS, 'config.database');
50
+ const hasData = database.data != null;
51
+ const hasPath = database.path != null;
52
+ if (hasData === hasPath) {
53
+ throw new Error('Укажите ровно один из ключей config.database.path и config.database.data');
54
+ }
55
+ const schema = normalizeSchema(database.schema, directoryPath);
56
+ if (hasData) {
57
+ return { data: getObject(database.data, 'config.database.data', true), schema };
58
+ }
59
+ return { path: resolveConfigPath(getString(database.path, 'config.database.path', true), directoryPath), schema };
60
+ };
61
+ const normalizeFiles = (value, directoryPath) => {
62
+ const files = getObject(value, 'config.files');
63
+ if (files == null) {
64
+ return undefined;
65
+ }
66
+ assertKnownKeys(files, FILES_KEYS, 'config.files');
67
+ const hasData = files.data != null;
68
+ const hasDiskStorage = files.directory != null || files.metadata != null;
69
+ if (hasData === hasDiskStorage) {
70
+ throw new Error('Укажите либо config.files.data, либо пару config.files.directory и config.files.metadata');
71
+ }
72
+ if (hasData) {
73
+ if (!Array.isArray(files.data)) {
74
+ throw new Error('Ключ config.files.data должен содержать массив');
75
+ }
76
+ return { data: files.data };
77
+ }
78
+ return {
79
+ directory: resolveConfigPath(getString(files.directory, 'config.files.directory', true), directoryPath),
80
+ metadata: resolveConfigPath(getString(files.metadata, 'config.files.metadata', true), directoryPath),
81
+ };
82
+ };
83
+ const normalizeConfig = (config, directoryPath = '.') => {
84
+ if (!isObject(config)) {
85
+ throw new Error('Конфигурация сервера должна содержать JSON-объект');
86
+ }
87
+ assertKnownKeys(config, CONFIG_KEYS, 'config');
88
+ const database = normalizeDatabase(config.database, directoryPath);
89
+ const files = normalizeFiles(config.files, directoryPath);
90
+ const openapi = getObject(config.openapi, 'config.openapi') ?? {};
91
+ const server = getObject(config.server, 'config.server') ?? {};
92
+ assertKnownKeys(openapi, OPENAPI_KEYS, 'config.openapi');
93
+ assertKnownKeys(server, SERVER_KEYS, 'config.server');
94
+ const openapiPath = getString(openapi.path, 'config.openapi.path');
95
+ const cors = server.cors;
96
+ const host = getString(server.host, 'config.server.host');
97
+ const logger = server.logger;
98
+ const maxFileSize = getPositiveInteger(server.maxFileSize, 'config.server.maxFileSize');
99
+ const maxPageSize = getPositiveInteger(server.maxPageSize, 'config.server.maxPageSize');
100
+ const port = server.port;
101
+ if (port != null && (typeof port !== 'number' || !Number.isInteger(port) || port < 0 || port > 65_535)) {
102
+ throw new Error('Ключ config.server.port должен быть целым числом от 0 до 65535');
103
+ }
104
+ if (logger != null && typeof logger !== 'boolean' && !isObject(logger)) {
105
+ throw new Error('Ключ config.server.logger должен содержать boolean или JSON-объект');
106
+ }
107
+ if (cors != null && typeof cors !== 'boolean') {
108
+ throw new Error('Ключ config.server.cors должен содержать boolean');
109
+ }
110
+ return {
111
+ database,
112
+ files,
113
+ openapi: { path: resolveConfigPath(openapiPath, directoryPath) },
114
+ server: {
115
+ cors: cors,
116
+ host,
117
+ logger: logger,
118
+ maxFileSize,
119
+ maxPageSize,
120
+ port: port,
121
+ },
122
+ };
123
+ };
124
+ /** Validates configuration and resolves relative paths. */
125
+ export const normalizeServerConfig = (config, directoryPath) => normalizeConfig(config, directoryPath);
126
+ /** Loads an ES module config and resolves paths from its directory. */
127
+ export async function readServerConfig(configPath) {
128
+ const resolvedConfigPath = resolve(getString(configPath, 'config', true));
129
+ let config;
130
+ try {
131
+ const configUrl = pathToFileURL(resolvedConfigPath);
132
+ // Bypass the module cache so repeated reads use the latest config.
133
+ configUrl.searchParams.set('deep-json-server-import', String(configImportIndex++));
134
+ config = (await import(configUrl.href)).default;
135
+ }
136
+ catch (error) {
137
+ const message = error instanceof Error ? error.message : String(error);
138
+ throw new Error(`Не удалось загрузить конфигурацию ${resolvedConfigPath}: ${message}`, { cause: error });
139
+ }
140
+ if (!isObject(config)) {
141
+ throw new Error('Конфигурация сервера должна экспортировать JSON-объект через export default');
142
+ }
143
+ return normalizeConfig(config, dirname(resolvedConfigPath));
144
+ }
145
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
@@ -3,3 +3,4 @@ export const DEFAULT_MAX_FILE_SIZE = 100 * 1024 * 1024;
3
3
  export const DEFAULT_MAX_PAGE_SIZE = 1000;
4
4
  export const DEFAULT_PAGE_SIZE = 10;
5
5
  export const DEFAULT_PORT = 4001;
6
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,YAAY,GAAG,WAAW,CAAC;AACxC,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AACvD,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAC1C,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAC;AACpC,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { DatabaseConfig } from './config.js';
2
+ import type { DatabaseData, DatabaseRecord } from './types.js';
3
+ export interface DatabaseContainer {
4
+ data: DatabaseData;
5
+ }
6
+ export interface DatabaseStore {
7
+ database: DatabaseContainer;
8
+ path?: string;
9
+ read(): Promise<DatabaseData>;
10
+ update<T>(operation: (database: DatabaseContainer) => T): Promise<T>;
11
+ }
12
+ export declare const validateJsonValue: (value: unknown, path: string, ancestors?: WeakSet<object>) => void;
13
+ export declare const validateDatabase: (data: unknown) => DatabaseData;
14
+ export declare const readJsonObjectFile: (path: string, label: string) => Promise<Record<string, unknown>>;
15
+ export declare const readDatabaseFile: (databasePath: string) => Promise<DatabaseData>;
16
+ /** Creates a disk- or memory-backed database with serialized updates. */
17
+ export declare const createDatabaseStore: (config: DatabaseConfig) => Promise<DatabaseStore>;
18
+ export declare const getCollection: (database: DatabaseContainer, resource: string) => DatabaseRecord[];
19
+ export declare const findItemIndex: (collection: DatabaseRecord[], id: unknown) => number;
20
+ export declare const createId: (collection: DatabaseRecord[]) => string;