@kollors/deep-json-server 1.0.0-beta.2 → 1.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/README.md +5 -3
  2. package/README.ru.md +5 -3
  3. package/dist/src/auth/password.js +1 -1
  4. package/dist/src/auth/password.js.map +1 -1
  5. package/dist/src/auth/service.d.ts +1 -1
  6. package/dist/src/auth/service.js +2 -2
  7. package/dist/src/auth/service.js.map +1 -1
  8. package/dist/src/cli/index.js +8 -2
  9. package/dist/src/cli/index.js.map +1 -1
  10. package/dist/src/core/constants.d.ts +1 -1
  11. package/dist/src/core/constants.js +1 -1
  12. package/dist/src/core/database.d.ts +4 -4
  13. package/dist/src/core/database.js +15 -12
  14. package/dist/src/core/database.js.map +1 -1
  15. package/dist/src/core/engine.d.ts +3 -1
  16. package/dist/src/core/engine.js +33 -24
  17. package/dist/src/core/engine.js.map +1 -1
  18. package/dist/src/core/lifecycle/mutation.d.ts +4 -4
  19. package/dist/src/core/lifecycle/mutation.js +36 -11
  20. package/dist/src/core/lifecycle/mutation.js.map +1 -1
  21. package/dist/src/core/lifecycle/options.d.ts +2 -2
  22. package/dist/src/core/lifecycle/options.js.map +1 -1
  23. package/dist/src/core/model/infer.d.ts +7 -0
  24. package/dist/src/core/model/infer.js +88 -0
  25. package/dist/src/core/model/infer.js.map +1 -0
  26. package/dist/src/core/model/load.d.ts +9 -0
  27. package/dist/src/core/model/load.js +215 -0
  28. package/dist/src/core/model/load.js.map +1 -0
  29. package/dist/src/core/model/policy.d.ts +15 -0
  30. package/dist/src/core/model/policy.js +29 -0
  31. package/dist/src/core/model/policy.js.map +1 -0
  32. package/dist/src/core/model/tree.d.ts +68 -0
  33. package/dist/src/core/model/tree.js +168 -0
  34. package/dist/src/core/model/tree.js.map +1 -0
  35. package/dist/src/core/model/types.d.ts +83 -0
  36. package/dist/src/core/model/types.js +2 -0
  37. package/dist/src/core/model/types.js.map +1 -0
  38. package/dist/src/core/model/validation.d.ts +24 -0
  39. package/dist/src/core/model/validation.js +96 -0
  40. package/dist/src/core/model/validation.js.map +1 -0
  41. package/dist/src/core/model.d.ts +6 -146
  42. package/dist/src/core/model.js +6 -534
  43. package/dist/src/core/model.js.map +1 -1
  44. package/dist/src/core/mutations/write.d.ts +33 -0
  45. package/dist/src/core/mutations/write.js +46 -12
  46. package/dist/src/core/mutations/write.js.map +1 -1
  47. package/dist/src/core/paths.d.ts +0 -8
  48. package/dist/src/core/paths.js +3 -19
  49. package/dist/src/core/paths.js.map +1 -1
  50. package/dist/src/core/query/execute.js +2 -2
  51. package/dist/src/core/query/execute.js.map +1 -1
  52. package/dist/src/core/query/filter.js +1 -1
  53. package/dist/src/core/query/filter.js.map +1 -1
  54. package/dist/src/core/query/options.js +1 -1
  55. package/dist/src/core/query/options.js.map +1 -1
  56. package/dist/src/core/records.js +5 -4
  57. package/dist/src/core/records.js.map +1 -1
  58. package/dist/src/core/relation-metadata.js +1 -1
  59. package/dist/src/core/relation-metadata.js.map +1 -1
  60. package/dist/src/core/snapshot.d.ts +10 -0
  61. package/dist/src/core/snapshot.js +15 -0
  62. package/dist/src/core/snapshot.js.map +1 -0
  63. package/dist/src/core/types.d.ts +5 -0
  64. package/dist/src/core/utils.d.ts +4 -0
  65. package/dist/src/core/utils.js +8 -0
  66. package/dist/src/core/utils.js.map +1 -1
  67. package/dist/src/files/contract.js +3 -1
  68. package/dist/src/files/contract.js.map +1 -1
  69. package/dist/src/graphql/preflight.js +2 -1
  70. package/dist/src/graphql/preflight.js.map +1 -1
  71. package/dist/src/graphql/resolvers.js +6 -3
  72. package/dist/src/graphql/resolvers.js.map +1 -1
  73. package/dist/src/graphql/schema.js +3 -3
  74. package/dist/src/graphql/schema.js.map +1 -1
  75. package/dist/src/openapi/document.js +7 -7
  76. package/dist/src/openapi/document.js.map +1 -1
  77. package/dist/src/rest/options.d.ts +2 -2
  78. package/dist/src/rest/options.js +3 -3
  79. package/dist/src/rest/options.js.map +1 -1
  80. package/dist/src/rest/projection.d.ts +6 -3
  81. package/dist/src/rest/projection.js +17 -10
  82. package/dist/src/rest/projection.js.map +1 -1
  83. package/dist/src/rest/routes.js +6 -6
  84. package/dist/src/rest/routes.js.map +1 -1
  85. package/dist/src/server/bootstrap.js +1 -1
  86. package/dist/src/server/bootstrap.js.map +1 -1
  87. package/dist/src/server/config.d.ts +10 -6
  88. package/dist/src/server/config.js +39 -37
  89. package/dist/src/server/config.js.map +1 -1
  90. package/dist/src/server/http.d.ts +1 -1
  91. package/dist/src/server/http.js +1 -1
  92. package/dist/src/server/input-paths.d.ts +8 -0
  93. package/dist/src/server/input-paths.js +19 -0
  94. package/dist/src/server/input-paths.js.map +1 -0
  95. package/package.json +2 -2
@@ -0,0 +1,29 @@
1
+ /** Определяет доступность поля для выбранного режима записи, учитывая защищённые дочерние поля.
2
+ * @example writable({ readOnly: true, … }, 'update') → false.
3
+ */
4
+ export function writable(node, mode, relations = false) {
5
+ if (node.relation)
6
+ return relations;
7
+ if (node.generated || node.readOnly || (node.primary && mode !== 'create'))
8
+ return false;
9
+ const children = Object.values(node.children);
10
+ return node.base !== 'object' || children.length === 0 || children.some((child) => writable(child, mode, relations));
11
+ }
12
+ /** Проверяет наличие явной модели и доступность выбранного формата для её связей.
13
+ * @example assertApi(undefined, 'graphql') → ошибка; согласованная модель → undefined.
14
+ */
15
+ export function assertApi(model, api) {
16
+ if (!model?.explicit)
17
+ throw new Error(`${api} requires an explicit model schema`);
18
+ for (const entity of model.entities.filter((e) => e.api.includes(api)))
19
+ for (const node of Object.values(entity.fields))
20
+ if (node.relation && !node.relation.api.includes(api))
21
+ throw new Error(`${entity.name}.${node.path}: ${node.relation.name} does not enable ${api}`);
22
+ }
23
+ /** Определяет обязательность поля в теле операции, независимо от формата API.
24
+ * @example Обязательное поле без default: create → true; корневое update → false.
25
+ */
26
+ export function requiredInput(node, mode, root = false, relations = false) {
27
+ return !!node.required && !(root && mode === 'update') && !(mode !== 'stored' && node.default !== undefined) && !(relations && node.relationKey);
28
+ }
29
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../../../../src/core/model/policy.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAU,EAAE,IAAe,EAAE,SAAS,GAAG,KAAK;IACrE,IAAI,IAAI,CAAC,QAAQ;QAAE,OAAO,SAAS,CAAC;IACpC,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,IAAI,KAAK,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IACzF,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC9C,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC;AACvH,CAAC;AACD;;GAEG;AACH,MAAM,UAAU,SAAS,CAAC,KAAwB,EAAE,GAA0B;IAC5E,IAAI,CAAC,KAAK,EAAE,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,GAAG,oCAAoC,CAAC,CAAC;IAClF,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACpE,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;YAC7C,IAAI,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,QAAQ,CAAC,IAAI,oBAAoB,GAAG,EAAE,CAAC,CAAC;AAC1J,CAAC;AACD;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,IAAU,EAAE,IAAe,EAAE,IAAI,GAAG,KAAK,EAAE,SAAS,GAAG,KAAK;IACxF,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC,IAAI,IAAI,IAAI,KAAK,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,CAAC,IAAI,CAAC,CAAC,SAAS,IAAI,IAAI,CAAC,WAAW,CAAC,CAAC;AACnJ,CAAC"}
@@ -0,0 +1,68 @@
1
+ import { type RecordOptions } from '../lifecycle/options.js';
2
+ import type { Entity, Field, Model, Node, RelationNode } from './types.js';
3
+ export declare const NAME: RegExp;
4
+ /** Разбивает путь по точкам и проверяет допустимость каждого имени.
5
+ * @example pathParts('profile.name') → ['profile', 'name']; pathParts('a..b') → ошибка.
6
+ */
7
+ export declare const pathParts: (path: string) => string[];
8
+ /** Находит самый длинный префикс пути среди ключей объекта, учитывая границы сегментов.
9
+ * @example bindingFor({ a: {}, 'a.b': {} }, 'a.b.id') → 'a.b'; bindingFor({ a: {} }, 'abc.id') → undefined.
10
+ */
11
+ export declare function bindingFor(bindings: Record<string, unknown>, path: string): string | undefined;
12
+ /** Проверяет, что все сегменты пути объявлены и доступны для записи, включая родительские объекты.
13
+ * @example Для поля name без ограничений canWriteKey(entity, 'name') → true; для первичного ключа → false.
14
+ */
15
+ export declare function canWriteKey(entity: Entity, path: string): boolean;
16
+ /** Выбирает обратную запись, если источник первичный; иначе — если источник защищён, а целевой ключ изменяемый.
17
+ * @example При source = 'id', target = 'authorId' и первичных ключах id → true.
18
+ */
19
+ export declare function isReverseRelation(entity: Entity, node: Node): boolean;
20
+ /** Собирает значения по вложенному пути, проходя через массивы; отсутствующие значения и null пропускает.
21
+ * @example readPath({ rows: [{ id: 1 }, { id: 2 }] }, 'rows.id') → [1, 2].
22
+ */
23
+ export declare const readPath: (value: unknown, path: string | string[]) => unknown[];
24
+ /** Возвращает последний сегмент пути поля.
25
+ * @example childName({ path: 'profile.name', … }) → 'name'.
26
+ */
27
+ export declare const childName: (node: Node) => string;
28
+ /** Для связи возвращает целевую сущность и её корень; обычный узел сохраняет без изменений.
29
+ * @example canonicalNode(author, { relation: book }) → [book, book.root].
30
+ * @example canonicalNode(author, author.root) → [author, author.root].
31
+ */
32
+ export declare const canonicalNode: (entity: Entity, node: Node) => [Entity, Node];
33
+ /** Соединяет имя сущности и путь поля, заменяя точки подчёркиваниями.
34
+ * @example nodeName({ name: 'User', … }, { path: 'profile.name', … }) → 'User_profile_name'.
35
+ */
36
+ export declare const nodeName: (entity: Entity, node: Node) => string;
37
+ /** Переводит первую букву имени сущности в нижний регистр.
38
+ * @example operationName({ name: 'UserProfile', … }) → 'userProfile'.
39
+ */
40
+ export declare const operationName: (entity: Entity) => string;
41
+ /** Создаёт узел с разобранным типом и пустым словарём дочерних полей.
42
+ * @example newNode('tags', { type: 'string[]' }) → узел с base: 'string', many: true.
43
+ */
44
+ export declare const newNode: (path: string, field: Field) => Node;
45
+ /** Добавляет поле по пути, создавая промежуточные объекты и сохраняя уже объявленных детей.
46
+ * @example Путь 'profile.name' и type: 'string' → узлы profile и profile.name в дереве.
47
+ */
48
+ export declare function addField(entity: Entity, path: string, field: Field, implicit?: boolean): Node;
49
+ /** Помечает непервичные поля, в которых хранятся ключи связей.
50
+ * @example Для связи user с source = 'userId' поле userId получает relationKey = true, а первичный id остаётся обычным полем.
51
+ */
52
+ export declare function markRelationKeys(model: Model): void;
53
+ /** Добавляет защищённые поля дат, авторов и прав согласно настройкам сущности.
54
+ * @example timestamps: true → поля createdAt и updatedAt; занятое имя → ошибка.
55
+ */
56
+ export declare function systemFields(entity: Entity, options: Required<RecordOptions>): void;
57
+ /** Возвращает объявленное поле или сообщает о нарушенной структуре модели.
58
+ * @example Поле id существует → его узел; отсутствует → ошибка с именем поля.
59
+ */
60
+ export declare function fieldAt(entity: Entity, path: string): Node;
61
+ /** Проверяет, что узел содержит подготовленную связь с обоими путями ключей.
62
+ * @example Узел user с relation, source и target → тот же узел; скаляр → ошибка.
63
+ */
64
+ export declare function requireRelation(node: Node): RelationNode;
65
+ /** Привязывает узел к целевой сущности, сохраняя ссылки из дерева полей.
66
+ * @example source = 'userId', target = 'id' → узел с обязательными путями связи.
67
+ */
68
+ export declare function linkRelation(node: Node, relation: Entity, source: string, target: string): RelationNode;
@@ -0,0 +1,168 @@
1
+ import { domainError } from '../errors.js';
2
+ import { DELETION_META } from '../lifecycle/options.js';
3
+ import { defined, isObject, isSafeKey } from '../utils.js';
4
+ export const NAME = /^[A-Za-z][A-Za-z0-9_]*$/;
5
+ /** Разбивает путь по точкам и проверяет допустимость каждого имени.
6
+ * @example pathParts('profile.name') → ['profile', 'name']; pathParts('a..b') → ошибка.
7
+ */
8
+ export const pathParts = (path) => {
9
+ const parts = path.split('.');
10
+ if (parts.some((p) => !NAME.test(p) || !isSafeKey(p)))
11
+ throw domainError('INVALID_INPUT', `Invalid field path: ${path}`);
12
+ return parts;
13
+ };
14
+ /** Находит самый длинный префикс пути среди ключей объекта, учитывая границы сегментов.
15
+ * @example bindingFor({ a: {}, 'a.b': {} }, 'a.b.id') → 'a.b'; bindingFor({ a: {} }, 'abc.id') → undefined.
16
+ */
17
+ export function bindingFor(bindings, path) {
18
+ let nearest;
19
+ for (const prefix of Object.keys(bindings))
20
+ if ((path === prefix || path.startsWith(`${prefix}.`)) && (!nearest || prefix.length > nearest.length))
21
+ nearest = prefix;
22
+ return nearest;
23
+ }
24
+ /** Проверяет, что все сегменты пути объявлены и доступны для записи, включая родительские объекты.
25
+ * @example Для поля name без ограничений canWriteKey(entity, 'name') → true; для первичного ключа → false.
26
+ */
27
+ export function canWriteKey(entity, path) {
28
+ const parts = pathParts(path);
29
+ return parts.every((_, index) => {
30
+ const field = entity.fields[parts.slice(0, index + 1).join('.')];
31
+ return field && !field.primary && !field.generated && !field.readOnly;
32
+ });
33
+ }
34
+ /** Выбирает обратную запись, если источник первичный; иначе — если источник защищён, а целевой ключ изменяемый.
35
+ * @example При source = 'id', target = 'authorId' и первичных ключах id → true.
36
+ */
37
+ export function isReverseRelation(entity, node) {
38
+ const relation = requireRelation(node);
39
+ return relation.source === entity.primary || (!canWriteKey(entity, relation.source) && canWriteKey(relation.relation, relation.target));
40
+ }
41
+ /** Собирает значения по вложенному пути, проходя через массивы; отсутствующие значения и null пропускает.
42
+ * @example readPath({ rows: [{ id: 1 }, { id: 2 }] }, 'rows.id') → [1, 2].
43
+ */
44
+ export const readPath = (value, path) => {
45
+ const parts = typeof path === 'string' ? pathParts(path) : path;
46
+ const [head, ...tail] = parts;
47
+ if (head === undefined)
48
+ return Array.isArray(value) ? value.flatMap((v) => readPath(v, [])) : value == null ? [] : [value];
49
+ if (Array.isArray(value))
50
+ return value.flatMap((v) => readPath(v, parts));
51
+ return isObject(value) && Object.hasOwn(value, head) ? readPath(value[head], tail) : [];
52
+ };
53
+ /** Возвращает последний сегмент пути поля.
54
+ * @example childName({ path: 'profile.name', … }) → 'name'.
55
+ */
56
+ export const childName = (node) => node.path.slice(node.path.lastIndexOf('.') + 1);
57
+ /** Для связи возвращает целевую сущность и её корень; обычный узел сохраняет без изменений.
58
+ * @example canonicalNode(author, { relation: book }) → [book, book.root].
59
+ * @example canonicalNode(author, author.root) → [author, author.root].
60
+ */
61
+ export const canonicalNode = (entity, node) => (node.relation ? [node.relation, node.relation.root] : [entity, node]);
62
+ /** Соединяет имя сущности и путь поля, заменяя точки подчёркиваниями.
63
+ * @example nodeName({ name: 'User', … }, { path: 'profile.name', … }) → 'User_profile_name'.
64
+ */
65
+ export const nodeName = (entity, node) => (node.path ? `${entity.name}_${node.path.replaceAll('.', '_')}` : entity.name);
66
+ /** Переводит первую букву имени сущности в нижний регистр.
67
+ * @example operationName({ name: 'UserProfile', … }) → 'userProfile'.
68
+ */
69
+ export const operationName = (entity) => entity.name.charAt(0).toLowerCase() + entity.name.slice(1);
70
+ /** Создаёт узел с разобранным типом и пустым словарём дочерних полей.
71
+ * @example newNode('tags', { type: 'string[]' }) → узел с base: 'string', many: true.
72
+ */
73
+ export const newNode = (path, field) => ({ ...field, path, base: field.type.replace(/\[\]$/, ''), many: field.type.endsWith('[]'), children: Object.create(null) });
74
+ /** Добавляет поле по пути, создавая промежуточные объекты и сохраняя уже объявленных детей.
75
+ * @example Путь 'profile.name' и type: 'string' → узлы profile и profile.name в дереве.
76
+ */
77
+ export function addField(entity, path, field, implicit = false) {
78
+ const parts = pathParts(path);
79
+ let parent = entity.root;
80
+ for (let i = 0; i < parts.length - 1; i++) {
81
+ const prefix = parts.slice(0, i + 1).join('.');
82
+ const part = defined(parts[i], 'field path segment');
83
+ const child = parent.children[part] ?? newNode(prefix, { type: 'object' });
84
+ parent.children[part] = child;
85
+ entity.fields[prefix] = child;
86
+ parent = child;
87
+ if (parent.base !== 'object')
88
+ throw new Error(`Field ${entity.name}.${prefix} cannot contain fields`);
89
+ }
90
+ const key = defined(parts.at(-1), 'field path');
91
+ const node = newNode(path, field);
92
+ node.implicit = implicit;
93
+ node.children = parent.children[key]?.children ?? Object.create(null);
94
+ parent.children[key] = node;
95
+ entity.fields[path] = node;
96
+ return node;
97
+ }
98
+ /** Помечает непервичные поля, в которых хранятся ключи связей.
99
+ * @example Для связи user с source = 'userId' поле userId получает relationKey = true, а первичный id остаётся обычным полем.
100
+ */
101
+ export function markRelationKeys(model) {
102
+ for (const entity of model.entities)
103
+ for (const node of Object.values(entity.fields)) {
104
+ if (!node.relation)
105
+ continue;
106
+ const sourceField = entity.fields[node.source];
107
+ const targetField = node.relation.fields[node.target];
108
+ if (sourceField && !sourceField.primary)
109
+ sourceField.relationKey = true;
110
+ if (targetField && !targetField.primary)
111
+ targetField.relationKey = true;
112
+ }
113
+ }
114
+ /** Добавляет защищённые поля дат, авторов и прав согласно настройкам сущности.
115
+ * @example timestamps: true → поля createdAt и updatedAt; занятое имя → ошибка.
116
+ */
117
+ export function systemFields(entity, options) {
118
+ const names = [
119
+ ...(entity.timestamps ? ['createdAt', 'updatedAt'] : []),
120
+ DELETION_META,
121
+ ...(entity.softDelete ? ['deletedAt'] : []),
122
+ ...(options.auth ? ['createdById', 'updatedById', ...(entity.softDelete ? ['deletedById'] : [])] : []),
123
+ ];
124
+ entity.root.softDelete = entity.softDelete;
125
+ for (const name of names) {
126
+ if (entity.fields[name])
127
+ throw new Error(`Reserved system field ${entity.name}.${name}`);
128
+ const node = addField(entity, name, { type: 'string', readOnly: true, nullable: true, ...(name.endsWith('At') ? { format: 'date-time' } : {}) });
129
+ node.system = true;
130
+ if (name === DELETION_META) {
131
+ node.internal = true;
132
+ node.writeOnly = true;
133
+ }
134
+ }
135
+ if (options.auth) {
136
+ if (entity.fields.actions)
137
+ throw new Error(`Reserved system field ${entity.name}.actions`);
138
+ const actions = addField(entity, 'actions', { type: 'object', required: true, readOnly: true });
139
+ actions.system = true;
140
+ actions.virtual = 'actions';
141
+ for (const name of ['update', 'replace', 'delete'])
142
+ addField(entity, `actions.${name}`, { type: 'boolean', required: true, readOnly: true });
143
+ }
144
+ }
145
+ /** Возвращает объявленное поле или сообщает о нарушенной структуре модели.
146
+ * @example Поле id существует → его узел; отсутствует → ошибка с именем поля.
147
+ */
148
+ export function fieldAt(entity, path) {
149
+ const node = entity.fields[path];
150
+ if (!node)
151
+ throw new Error(`Unknown field ${entity.name}.${path}`);
152
+ return node;
153
+ }
154
+ /** Проверяет, что узел содержит подготовленную связь с обоими путями ключей.
155
+ * @example Узел user с relation, source и target → тот же узел; скаляр → ошибка.
156
+ */
157
+ export function requireRelation(node) {
158
+ if (!node.relation)
159
+ throw new Error(`Expected relation at ${node.path}`);
160
+ return node;
161
+ }
162
+ /** Привязывает узел к целевой сущности, сохраняя ссылки из дерева полей.
163
+ * @example source = 'userId', target = 'id' → узел с обязательными путями связи.
164
+ */
165
+ export function linkRelation(node, relation, source, target) {
166
+ return Object.assign(node, { relation, source, target });
167
+ }
168
+ //# sourceMappingURL=tree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree.js","sourceRoot":"","sources":["../../../../src/core/model/tree.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ import type { RecordOptions } from '../lifecycle/options.js';
2
+ import type { JsonValue } from '../types.js';
3
+ export interface Field {
4
+ type: string;
5
+ description?: string;
6
+ example?: JsonValue;
7
+ required?: boolean;
8
+ nullable?: boolean;
9
+ default?: JsonValue;
10
+ enum?: JsonValue[];
11
+ primary?: boolean;
12
+ generated?: 'uuid' | 'increment';
13
+ readOnly?: boolean;
14
+ writeOnly?: boolean;
15
+ minLength?: number;
16
+ maxLength?: number;
17
+ pattern?: string;
18
+ format?: 'date' | 'date-time' | 'email' | 'uri' | 'uuid';
19
+ minimum?: number;
20
+ maximum?: number;
21
+ source?: string;
22
+ target?: string;
23
+ onDelete?: 'restrict' | 'cascade';
24
+ }
25
+ export interface EntityDefinition {
26
+ timestamps?: boolean;
27
+ softDelete?: boolean;
28
+ collection: string;
29
+ api?: ('openapi' | 'graphql')[];
30
+ fields: Record<string, Field>;
31
+ }
32
+ export type ApiFormat = 'openapi' | 'graphql';
33
+ export interface ModelSchema {
34
+ models: Record<string, EntityDefinition>;
35
+ api?: ApiFormat[];
36
+ timestamps?: boolean;
37
+ softDelete?: boolean;
38
+ }
39
+ export interface ModelOptions {
40
+ auth?: boolean;
41
+ api?: ApiFormat[];
42
+ }
43
+ interface NodeFields extends Field {
44
+ system?: boolean;
45
+ internal?: boolean;
46
+ virtual?: 'actions';
47
+ softDelete?: boolean;
48
+ path: string;
49
+ base: string;
50
+ many: boolean;
51
+ children: Record<string, Node>;
52
+ implicit?: boolean;
53
+ mixed?: boolean;
54
+ relationKey?: boolean;
55
+ }
56
+ export interface ValueNode extends NodeFields {
57
+ relation?: undefined;
58
+ }
59
+ export interface RelationNode extends NodeFields {
60
+ relation: Entity;
61
+ source: string;
62
+ target: string;
63
+ }
64
+ export type Node = ValueNode | RelationNode;
65
+ export interface Entity {
66
+ timestamps: boolean;
67
+ softDelete: boolean;
68
+ name: string;
69
+ collection: string;
70
+ api: ('openapi' | 'graphql')[];
71
+ primary: string;
72
+ fields: Record<string, Node>;
73
+ root: Node;
74
+ }
75
+ export interface Model {
76
+ options: Required<RecordOptions>;
77
+ entities: Entity[];
78
+ byName: Map<string, Entity>;
79
+ byCollection: Map<string, Entity>;
80
+ explicit: boolean;
81
+ }
82
+ export type ValidationSchema = Record<string, unknown>;
83
+ export {};
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/core/model/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,24 @@
1
+ import { Ajv } from 'ajv';
2
+ import { type InputMode } from './policy.js';
3
+ import type { Entity, Node, ValidationSchema } from './types.js';
4
+ /** Создаёт независимый валидатор JSON Schema с форматами и строгой проверкой без преобразования данных.
5
+ * @example createValidator().validate({ type: 'number' }, '1') → false.
6
+ */
7
+ export declare const createValidator: () => Ajv;
8
+ export declare const entityValidators: WeakMap<Entity, Ajv>;
9
+ /** Строит схему проверки значения с ограничениями, массивом и допустимостью null.
10
+ * @example Для строкового узла без ограничений valueSchema(node) → { type: 'string' }.
11
+ */
12
+ export declare function valueSchema(node: Node): ValidationSchema;
13
+ /** Строит схему объекта для выбранного режима; при частичном обновлении корневые поля необязательны.
14
+ * @example Для пустого узла → { type: 'object', properties: {}, additionalProperties: false }.
15
+ */
16
+ export declare function objectSchema(node: Node, mode: InputMode, root?: boolean, relationSchema?: (node: Node) => ValidationSchema): ValidationSchema;
17
+ /** Описывает ввод связанной записи: первичный ключ или объект, при необходимости — массив таких значений.
18
+ * @example Связь со строковым ключом → anyOf для строки и объекта; many: true → массив.
19
+ */
20
+ export declare function relationInputSchema(node: Node, object?: ValidationSchema): ValidationSchema;
21
+ /** Проверяет запись по схеме выбранного режима и выбрасывает исключение при несоответствии.
22
+ * @example Обязательное строковое name: { name: 'Анна' } → undefined; { name: 2 } → ошибка.
23
+ */
24
+ export declare function validateRecord(entity: Entity, value: unknown, mode: InputMode, relations?: boolean): void;
@@ -0,0 +1,96 @@
1
+ import { Ajv } from 'ajv';
2
+ import addFormats from 'ajv-formats';
3
+ import { domainError } from '../errors.js';
4
+ import { AUDIT_FIELDS } from '../lifecycle/options.js';
5
+ import { requiredInput, writable } from './policy.js';
6
+ import { fieldAt, requireRelation } from './tree.js';
7
+ /** Создаёт независимый валидатор JSON Schema с форматами и строгой проверкой без преобразования данных.
8
+ * @example createValidator().validate({ type: 'number' }, '1') → false.
9
+ */
10
+ export const createValidator = () => {
11
+ const ajv = new Ajv({ allErrors: true, coerceTypes: false, removeAdditional: false, strict: true, ownProperties: true });
12
+ addFormats.default(ajv);
13
+ return ajv;
14
+ };
15
+ export const entityValidators = new WeakMap();
16
+ /** Строит схему проверки значения с ограничениями, массивом и допустимостью null.
17
+ * @example Для строкового узла без ограничений valueSchema(node) → { type: 'string' }.
18
+ */
19
+ export function valueSchema(node) {
20
+ let schema = node.base === 'object' ? objectSchema(node, 'stored') : { type: node.base };
21
+ for (const key of ['minLength', 'maxLength', 'pattern', 'format', 'minimum', 'maximum', 'enum'])
22
+ if (node[key] !== undefined)
23
+ schema[key] = node[key];
24
+ if (node.many)
25
+ schema = { type: 'array', items: schema };
26
+ if (node.nullable)
27
+ schema = { anyOf: [schema, { type: 'null' }] };
28
+ return schema;
29
+ }
30
+ /** Строит схему объекта для выбранного режима; при частичном обновлении корневые поля необязательны.
31
+ * @example Для пустого узла → { type: 'object', properties: {}, additionalProperties: false }.
32
+ */
33
+ export function objectSchema(node, mode, root = false, relationSchema) {
34
+ const properties = {};
35
+ const required = [];
36
+ for (const [key, child] of Object.entries(node.children)) {
37
+ if (child.virtual)
38
+ continue;
39
+ if (child.relation) {
40
+ if (mode !== 'stored' && relationSchema)
41
+ properties[key] = relationSchema(child);
42
+ continue;
43
+ }
44
+ if (mode !== 'stored' && !writable(child, mode, !!relationSchema))
45
+ continue;
46
+ properties[key] =
47
+ child.base === 'object'
48
+ ? (() => {
49
+ let s = objectSchema(child, mode, false, relationSchema);
50
+ if (child.many)
51
+ s = { type: 'array', items: s };
52
+ return child.nullable ? { anyOf: [s, { type: 'null' }] } : s;
53
+ })()
54
+ : valueSchema(child);
55
+ if (requiredInput(child, mode, root, !!relationSchema))
56
+ required.push(key);
57
+ }
58
+ // Сохраняем ранее записанные даты и авторов при отключении соответствующих флагов.
59
+ if (root && mode === 'stored')
60
+ for (const name of AUDIT_FIELDS)
61
+ properties[name] ??= { type: ['string', 'null'], ...(name.endsWith('At') ? { format: 'date-time' } : {}) };
62
+ return { type: 'object', properties, additionalProperties: false, ...(required.length ? { required } : {}) };
63
+ }
64
+ /** Описывает ввод связанной записи: первичный ключ или объект, при необходимости — массив таких значений.
65
+ * @example Связь со строковым ключом → anyOf для строки и объекта; many: true → массив.
66
+ */
67
+ export function relationInputSchema(node, object = { type: 'object' }) {
68
+ const target = requireRelation(node).relation;
69
+ let schema = { anyOf: [valueSchema(fieldAt(target, target.primary)), object] };
70
+ if (node.many)
71
+ schema = { type: 'array', items: schema };
72
+ else if (node.nullable)
73
+ schema = { anyOf: [schema, { type: 'null' }] };
74
+ return schema;
75
+ }
76
+ const validators = new WeakMap();
77
+ /** Проверяет запись по схеме выбранного режима и выбрасывает исключение при несоответствии.
78
+ * @example Обязательное строковое name: { name: 'Анна' } → undefined; { name: 2 } → ошибка.
79
+ */
80
+ export function validateRecord(entity, value, mode, relations = false) {
81
+ const ajv = entityValidators.get(entity);
82
+ let cache = validators.get(entity);
83
+ if (!cache) {
84
+ cache = new Map();
85
+ validators.set(entity, cache);
86
+ }
87
+ const key = `${mode}:${relations}`;
88
+ let validate = cache.get(key);
89
+ if (!validate) {
90
+ validate = ajv.compile(objectSchema(entity.root, mode, true, relations ? (node) => relationInputSchema(node) : undefined));
91
+ cache.set(key, validate);
92
+ }
93
+ if (!validate(value))
94
+ throw domainError('INVALID_INPUT', `${entity.name}: ${ajv.errorsText(validate.errors)}`);
95
+ }
96
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../../../../src/core/model/validation.ts"],"names":[],"mappings":"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"}