@danceroutine/tango-schema 1.1.3 → 1.3.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.
- package/dist/domain/Model.d.ts +17 -4
- package/dist/domain/index.d.ts +1 -1
- package/dist/domain-Cufz6y1q.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/model/Model.d.ts +5 -2
- package/dist/model/ModelAugmentorRegistry.d.ts +2 -2
- package/dist/model/ModelDefinition.d.ts +5 -3
- package/dist/model/decorators/Decorators.d.ts +54 -6
- package/dist/model/decorators/domain/DecoratedFieldKind.d.ts +6 -0
- package/dist/model/decorators/domain/ModelRef.d.ts +11 -0
- package/dist/model/decorators/domain/RelationDecoratedSchema.d.ts +24 -0
- package/dist/model/decorators/domain/RelationDecoratorConfig.d.ts +35 -0
- package/dist/model/decorators/{types.d.ts → domain/TangoFieldMeta.d.ts} +6 -5
- package/dist/model/decorators/domain/ZodTypeAny.d.ts +2 -0
- package/dist/model/decorators/index.d.ts +9 -2
- package/dist/model/{internal → fields}/FieldMetadataStore.d.ts +2 -1
- package/dist/model/fields/FinalizedStorageArtifacts.d.ts +11 -0
- package/dist/model/{inferFields.d.ts → fields/inferFieldsFromSchema.d.ts} +7 -2
- package/dist/model/index.d.ts +13 -3
- package/dist/model/index.js +2 -2
- package/dist/model/internal/InternalSchemaModel.d.ts +31 -0
- package/dist/model/registry/ModelRegistry.d.ts +35 -2
- package/dist/model/registry/index.d.ts +4 -0
- package/dist/model/relations/NormalizedRelationStorageDescriptor.d.ts +36 -0
- package/dist/model/relations/RelationBuilder.d.ts +19 -0
- package/dist/model/relations/RelationDescriptorNormalizer.d.ts +30 -0
- package/dist/model/relations/RelationSpec.d.ts +48 -0
- package/dist/model/relations/ResolvedRelationGraph.d.ts +43 -0
- package/dist/model/relations/ResolvedRelationGraphBuilder.d.ts +48 -0
- package/dist/model/relations/SchemaNaming.d.ts +11 -0
- package/dist/model/relations/index.d.ts +13 -0
- package/dist/model-YLW1ydkV.js +1144 -0
- package/dist/model-YLW1ydkV.js.map +1 -0
- package/package.json +3 -2
- package/dist/model/RelationBuilder.d.ts +0 -12
- package/dist/model-DI8lQH1W.js +0 -585
- package/dist/model-DI8lQH1W.js.map +0 -1
|
@@ -0,0 +1,1144 @@
|
|
|
1
|
+
import { getLogger } from "@danceroutine/tango-core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
|
+
|
|
5
|
+
//#region rolldown:runtime
|
|
6
|
+
var __defProp = Object.defineProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all) __defProp(target, name, {
|
|
9
|
+
get: all[name],
|
|
10
|
+
enumerable: true
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/model/fields/FieldMetadataStore.ts
|
|
16
|
+
const fieldMetadataStore = new WeakMap();
|
|
17
|
+
function getFieldMetadata(schema) {
|
|
18
|
+
return fieldMetadataStore.get(schema);
|
|
19
|
+
}
|
|
20
|
+
function setFieldMetadata(schema, meta) {
|
|
21
|
+
const existing = fieldMetadataStore.get(schema);
|
|
22
|
+
fieldMetadataStore.set(schema, {
|
|
23
|
+
...existing,
|
|
24
|
+
...meta
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/model/decorators/domain/ModelRef.ts
|
|
30
|
+
function createTypedModelRef(key) {
|
|
31
|
+
return Object.freeze({ key });
|
|
32
|
+
}
|
|
33
|
+
function isTypedModelRef(value) {
|
|
34
|
+
return typeof value === "object" && value !== null && typeof value.key === "string";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/model/decorators/domain/DecoratedFieldKind.ts
|
|
39
|
+
const InternalDecoratedFieldKind = {
|
|
40
|
+
FOREIGN_KEY: "foreignKey",
|
|
41
|
+
ONE_TO_ONE: "oneToOne",
|
|
42
|
+
MANY_TO_MANY: "manyToMany"
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/model/decorators/Decorators.ts
|
|
47
|
+
function isZodType(value) {
|
|
48
|
+
return !!value && typeof value === "object" && "safeParse" in value && typeof value.safeParse === "function";
|
|
49
|
+
}
|
|
50
|
+
function decorate(schema, meta) {
|
|
51
|
+
setFieldMetadata(schema, meta);
|
|
52
|
+
return schema;
|
|
53
|
+
}
|
|
54
|
+
const warnedDecoratorKinds = new Set();
|
|
55
|
+
function warnDeprecatedSchemaOverload(kind, replacement) {
|
|
56
|
+
if (warnedDecoratorKinds.has(kind)) return;
|
|
57
|
+
warnedDecoratorKinds.add(kind);
|
|
58
|
+
getLogger("tango.schema.decorators").warn(`Deprecated positional schema overload used for t.${kind}(...). Prefer ${replacement} instead.`);
|
|
59
|
+
}
|
|
60
|
+
function maybeDecorator(schemaOrUndefined, meta) {
|
|
61
|
+
if (schemaOrUndefined) return decorate(schemaOrUndefined, meta);
|
|
62
|
+
return (schema) => decorate(schema, meta);
|
|
63
|
+
}
|
|
64
|
+
function primaryKey(schema) {
|
|
65
|
+
return maybeDecorator(schema, {
|
|
66
|
+
primaryKey: true,
|
|
67
|
+
notNull: true
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function unique(schema) {
|
|
71
|
+
return maybeDecorator(schema, { unique: true });
|
|
72
|
+
}
|
|
73
|
+
function nullValue(schema) {
|
|
74
|
+
return maybeDecorator(schema, { notNull: false });
|
|
75
|
+
}
|
|
76
|
+
function notNull(schema) {
|
|
77
|
+
return maybeDecorator(schema, { notNull: true });
|
|
78
|
+
}
|
|
79
|
+
function defaultValue(schema, value) {
|
|
80
|
+
return decorate(schema, { default: value });
|
|
81
|
+
}
|
|
82
|
+
function dbDefault(schema, value) {
|
|
83
|
+
return decorate(schema, { dbDefault: value });
|
|
84
|
+
}
|
|
85
|
+
function dbColumn(schema, name) {
|
|
86
|
+
return decorate(schema, { dbColumn: name });
|
|
87
|
+
}
|
|
88
|
+
function dbIndex(schema) {
|
|
89
|
+
return decorate(schema, { dbIndex: true });
|
|
90
|
+
}
|
|
91
|
+
function choices(schema, values) {
|
|
92
|
+
return decorate(schema, { choices: values });
|
|
93
|
+
}
|
|
94
|
+
function validators(schema, ...values) {
|
|
95
|
+
return decorate(schema, { validators: values });
|
|
96
|
+
}
|
|
97
|
+
function helpText(schema, text) {
|
|
98
|
+
return decorate(schema, { helpText: text });
|
|
99
|
+
}
|
|
100
|
+
function errorMessages(schema, map) {
|
|
101
|
+
return decorate(schema, { errorMessages: map });
|
|
102
|
+
}
|
|
103
|
+
var FieldDecoratorBuilderImpl = class {
|
|
104
|
+
constructor(schema) {
|
|
105
|
+
this.schema = schema;
|
|
106
|
+
}
|
|
107
|
+
defaultValue(value) {
|
|
108
|
+
decorate(this.schema, { default: value });
|
|
109
|
+
return this;
|
|
110
|
+
}
|
|
111
|
+
dbDefault(value) {
|
|
112
|
+
decorate(this.schema, { dbDefault: value });
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
dbColumn(name) {
|
|
116
|
+
decorate(this.schema, { dbColumn: name });
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
dbIndex() {
|
|
120
|
+
decorate(this.schema, { dbIndex: true });
|
|
121
|
+
return this;
|
|
122
|
+
}
|
|
123
|
+
choices(values) {
|
|
124
|
+
decorate(this.schema, { choices: values });
|
|
125
|
+
return this;
|
|
126
|
+
}
|
|
127
|
+
validators(...values) {
|
|
128
|
+
decorate(this.schema, { validators: values });
|
|
129
|
+
return this;
|
|
130
|
+
}
|
|
131
|
+
helpText(text) {
|
|
132
|
+
decorate(this.schema, { helpText: text });
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
errorMessages(map) {
|
|
136
|
+
decorate(this.schema, { errorMessages: map });
|
|
137
|
+
return this;
|
|
138
|
+
}
|
|
139
|
+
build() {
|
|
140
|
+
return this.schema;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
function field(schema) {
|
|
144
|
+
return new FieldDecoratorBuilderImpl(schema);
|
|
145
|
+
}
|
|
146
|
+
function applyRelationMetadata(schema, meta, config) {
|
|
147
|
+
return decorate(schema, {
|
|
148
|
+
...meta,
|
|
149
|
+
forwardName: config?.name,
|
|
150
|
+
reverseName: config?.relatedName
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
function toReferentialOptions(config) {
|
|
154
|
+
if (!config) return undefined;
|
|
155
|
+
if (config.column === undefined && config.onDelete === undefined && config.onUpdate === undefined) return undefined;
|
|
156
|
+
return {
|
|
157
|
+
column: config.column,
|
|
158
|
+
onDelete: config.onDelete,
|
|
159
|
+
onUpdate: config.onUpdate
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function modelRef(key) {
|
|
163
|
+
return createTypedModelRef(key);
|
|
164
|
+
}
|
|
165
|
+
function foreignKey(target, schemaOrOptions, maybeOptions) {
|
|
166
|
+
if (isZodType(schemaOrOptions)) {
|
|
167
|
+
warnDeprecatedSchemaOverload(InternalDecoratedFieldKind.FOREIGN_KEY, "t.foreignKey(target, { field: schema, ...options })");
|
|
168
|
+
return applyRelationMetadata(schemaOrOptions, {
|
|
169
|
+
relationKind: InternalDecoratedFieldKind.FOREIGN_KEY,
|
|
170
|
+
references: {
|
|
171
|
+
target,
|
|
172
|
+
options: maybeOptions
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
const config = schemaOrOptions;
|
|
177
|
+
const schema = config?.field ?? z.number().int();
|
|
178
|
+
return applyRelationMetadata(schema, {
|
|
179
|
+
relationKind: InternalDecoratedFieldKind.FOREIGN_KEY,
|
|
180
|
+
references: {
|
|
181
|
+
target,
|
|
182
|
+
options: toReferentialOptions(config)
|
|
183
|
+
},
|
|
184
|
+
notNull: config?.field ? undefined : true
|
|
185
|
+
}, config);
|
|
186
|
+
}
|
|
187
|
+
function oneToOne(target, schemaOrOptions, maybeOptions) {
|
|
188
|
+
if (isZodType(schemaOrOptions)) {
|
|
189
|
+
warnDeprecatedSchemaOverload(InternalDecoratedFieldKind.ONE_TO_ONE, "t.oneToOne(target, { field: schema, ...options })");
|
|
190
|
+
return applyRelationMetadata(schemaOrOptions, {
|
|
191
|
+
relationKind: InternalDecoratedFieldKind.ONE_TO_ONE,
|
|
192
|
+
unique: true,
|
|
193
|
+
references: {
|
|
194
|
+
target,
|
|
195
|
+
options: maybeOptions
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
const config = schemaOrOptions;
|
|
200
|
+
const schema = config?.field ?? z.number().int();
|
|
201
|
+
return applyRelationMetadata(schema, {
|
|
202
|
+
relationKind: InternalDecoratedFieldKind.ONE_TO_ONE,
|
|
203
|
+
unique: true,
|
|
204
|
+
references: {
|
|
205
|
+
target,
|
|
206
|
+
options: toReferentialOptions(config)
|
|
207
|
+
},
|
|
208
|
+
notNull: config?.field ? undefined : true
|
|
209
|
+
}, config);
|
|
210
|
+
}
|
|
211
|
+
function manyToMany(target, schemaOrConfig) {
|
|
212
|
+
if (isZodType(schemaOrConfig)) {
|
|
213
|
+
warnDeprecatedSchemaOverload(InternalDecoratedFieldKind.MANY_TO_MANY, "t.manyToMany(target, { field: schema, name })");
|
|
214
|
+
return applyRelationMetadata(schemaOrConfig, {
|
|
215
|
+
relationKind: InternalDecoratedFieldKind.MANY_TO_MANY,
|
|
216
|
+
references: { target }
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
if (schemaOrConfig?.relatedName !== undefined) throw new Error("t.manyToMany(...) does not support relatedName yet.");
|
|
220
|
+
const config = schemaOrConfig;
|
|
221
|
+
const schema = config?.field ?? z.array(z.number().int());
|
|
222
|
+
return applyRelationMetadata(schema, {
|
|
223
|
+
relationKind: InternalDecoratedFieldKind.MANY_TO_MANY,
|
|
224
|
+
references: { target }
|
|
225
|
+
}, config);
|
|
226
|
+
}
|
|
227
|
+
const Decorators = {
|
|
228
|
+
field,
|
|
229
|
+
modelRef,
|
|
230
|
+
primaryKey,
|
|
231
|
+
unique,
|
|
232
|
+
null: nullValue,
|
|
233
|
+
notNull,
|
|
234
|
+
default: defaultValue,
|
|
235
|
+
dbDefault,
|
|
236
|
+
dbColumn,
|
|
237
|
+
dbIndex,
|
|
238
|
+
choices,
|
|
239
|
+
validators,
|
|
240
|
+
helpText,
|
|
241
|
+
errorMessages,
|
|
242
|
+
foreignKey,
|
|
243
|
+
oneToOne,
|
|
244
|
+
manyToMany
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
//#endregion
|
|
248
|
+
//#region src/model/decorators/index.ts
|
|
249
|
+
var decorators_exports = {};
|
|
250
|
+
__export(decorators_exports, {
|
|
251
|
+
Decorators: () => Decorators,
|
|
252
|
+
InternalDecoratedFieldKind: () => InternalDecoratedFieldKind,
|
|
253
|
+
createTypedModelRef: () => createTypedModelRef,
|
|
254
|
+
isTypedModelRef: () => isTypedModelRef,
|
|
255
|
+
t: () => Decorators
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
//#endregion
|
|
259
|
+
//#region src/model/meta/Meta.ts
|
|
260
|
+
const Meta = {
|
|
261
|
+
ordering(...fields) {
|
|
262
|
+
return { ordering: fields };
|
|
263
|
+
},
|
|
264
|
+
managed(value) {
|
|
265
|
+
return { managed: value };
|
|
266
|
+
},
|
|
267
|
+
defaultRelatedName(value) {
|
|
268
|
+
return { defaultRelatedName: value };
|
|
269
|
+
},
|
|
270
|
+
indexes(...indexes) {
|
|
271
|
+
return { indexes };
|
|
272
|
+
},
|
|
273
|
+
constraints(...constraints) {
|
|
274
|
+
return { constraints };
|
|
275
|
+
},
|
|
276
|
+
uniqueTogether(...sets) {
|
|
277
|
+
return { constraints: sets.map((fields) => ({
|
|
278
|
+
kind: "uniqueTogether",
|
|
279
|
+
fields
|
|
280
|
+
})) };
|
|
281
|
+
},
|
|
282
|
+
indexTogether(...sets) {
|
|
283
|
+
return { indexes: sets.map((on, index) => ({
|
|
284
|
+
name: `idx_${on.join("_")}_${index}`,
|
|
285
|
+
on
|
|
286
|
+
})) };
|
|
287
|
+
},
|
|
288
|
+
merge(...fragments) {
|
|
289
|
+
return fragments.reduce((acc, fragment) => ({
|
|
290
|
+
ordering: fragment.ordering ?? acc.ordering,
|
|
291
|
+
managed: fragment.managed ?? acc.managed,
|
|
292
|
+
defaultRelatedName: fragment.defaultRelatedName ?? acc.defaultRelatedName,
|
|
293
|
+
indexes: [...acc.indexes ?? [], ...fragment.indexes ?? []],
|
|
294
|
+
constraints: [...acc.constraints ?? [], ...fragment.constraints ?? []]
|
|
295
|
+
}), {});
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
//#endregion
|
|
300
|
+
//#region src/model/meta/index.ts
|
|
301
|
+
var meta_exports = {};
|
|
302
|
+
__export(meta_exports, {
|
|
303
|
+
Meta: () => Meta,
|
|
304
|
+
m: () => Meta
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
//#endregion
|
|
308
|
+
//#region src/model/constraints/Constraints.ts
|
|
309
|
+
const Constraints = {
|
|
310
|
+
unique(fields, options) {
|
|
311
|
+
return {
|
|
312
|
+
kind: "unique",
|
|
313
|
+
fields,
|
|
314
|
+
...options
|
|
315
|
+
};
|
|
316
|
+
},
|
|
317
|
+
check(condition, options) {
|
|
318
|
+
return {
|
|
319
|
+
kind: "check",
|
|
320
|
+
condition,
|
|
321
|
+
...options
|
|
322
|
+
};
|
|
323
|
+
},
|
|
324
|
+
exclusion(definition) {
|
|
325
|
+
return {
|
|
326
|
+
kind: "exclusion",
|
|
327
|
+
...definition
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
//#endregion
|
|
333
|
+
//#region src/model/constraints/Indexes.ts
|
|
334
|
+
const Indexes = { index(on, options) {
|
|
335
|
+
const suffix = on.join("_");
|
|
336
|
+
return {
|
|
337
|
+
name: options?.name ?? `idx_${suffix}`,
|
|
338
|
+
on,
|
|
339
|
+
unique: options?.unique,
|
|
340
|
+
where: options?.where
|
|
341
|
+
};
|
|
342
|
+
} };
|
|
343
|
+
|
|
344
|
+
//#endregion
|
|
345
|
+
//#region src/model/constraints/index.ts
|
|
346
|
+
var constraints_exports = {};
|
|
347
|
+
__export(constraints_exports, {
|
|
348
|
+
Constraints: () => Constraints,
|
|
349
|
+
Indexes: () => Indexes,
|
|
350
|
+
c: () => Constraints,
|
|
351
|
+
i: () => Indexes
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
//#endregion
|
|
355
|
+
//#region src/domain/internal/InternalFieldType.ts
|
|
356
|
+
const InternalFieldType = {
|
|
357
|
+
SERIAL: "serial",
|
|
358
|
+
INT: "int",
|
|
359
|
+
BIGINT: "bigint",
|
|
360
|
+
TEXT: "text",
|
|
361
|
+
BOOL: "bool",
|
|
362
|
+
TIMESTAMPTZ: "timestamptz",
|
|
363
|
+
JSONB: "jsonb",
|
|
364
|
+
UUID: "uuid"
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
//#endregion
|
|
368
|
+
//#region src/domain/internal/zod/isDate.ts
|
|
369
|
+
function isDate(value) {
|
|
370
|
+
return value !== null && value !== undefined && Object.prototype.toString.call(value) === "[object Date]";
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
//#endregion
|
|
374
|
+
//#region src/domain/internal/zod/hasConstructorName.ts
|
|
375
|
+
function hasConstructorName(value, name) {
|
|
376
|
+
return !!value && typeof value === "object" && value.constructor?.name === name;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
//#endregion
|
|
380
|
+
//#region src/domain/internal/zod/isZodArray.ts
|
|
381
|
+
function isZodArray(value) {
|
|
382
|
+
return hasConstructorName(value, "ZodArray");
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
//#endregion
|
|
386
|
+
//#region src/domain/internal/zod/isZodBoolean.ts
|
|
387
|
+
function isZodBoolean(value) {
|
|
388
|
+
return hasConstructorName(value, "ZodBoolean");
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
//#endregion
|
|
392
|
+
//#region src/domain/internal/zod/isZodDate.ts
|
|
393
|
+
function isZodDate(value) {
|
|
394
|
+
return hasConstructorName(value, "ZodDate");
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
//#endregion
|
|
398
|
+
//#region src/domain/internal/zod/isZodDefault.ts
|
|
399
|
+
function isZodDefault(value) {
|
|
400
|
+
return hasConstructorName(value, "ZodDefault");
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
//#endregion
|
|
404
|
+
//#region src/domain/internal/zod/isZodNullable.ts
|
|
405
|
+
function isZodNullable(value) {
|
|
406
|
+
return hasConstructorName(value, "ZodNullable");
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
//#endregion
|
|
410
|
+
//#region src/domain/internal/zod/isZodNumber.ts
|
|
411
|
+
function isZodNumber(value) {
|
|
412
|
+
return hasConstructorName(value, "ZodNumber");
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
//#endregion
|
|
416
|
+
//#region src/domain/internal/zod/isZodObject.ts
|
|
417
|
+
function isZodObject(value) {
|
|
418
|
+
return hasConstructorName(value, "ZodObject");
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
//#endregion
|
|
422
|
+
//#region src/domain/internal/zod/isZodOptional.ts
|
|
423
|
+
function isZodOptional(value) {
|
|
424
|
+
return hasConstructorName(value, "ZodOptional");
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
//#endregion
|
|
428
|
+
//#region src/domain/internal/zod/isZodString.ts
|
|
429
|
+
function isZodString(value) {
|
|
430
|
+
return hasConstructorName(value, "ZodString");
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
//#endregion
|
|
434
|
+
//#region src/model/fields/inferFieldsFromSchema.ts
|
|
435
|
+
/**
|
|
436
|
+
* Infer one storage field from a Zod schema member plus any Tango decorator metadata.
|
|
437
|
+
*
|
|
438
|
+
* The registry and optional target resolver are used only when the field carries
|
|
439
|
+
* reference metadata that must be translated into concrete table/primary-key names.
|
|
440
|
+
*/
|
|
441
|
+
function inferField(name, zodType, meta, registry, resolveReferenceTarget) {
|
|
442
|
+
let type;
|
|
443
|
+
let notNull$1 = true;
|
|
444
|
+
let defaultValue$1 = undefined;
|
|
445
|
+
let unwrapped = zodType;
|
|
446
|
+
if (isZodOptional(unwrapped)) {
|
|
447
|
+
notNull$1 = false;
|
|
448
|
+
unwrapped = unwrapped.unwrap();
|
|
449
|
+
}
|
|
450
|
+
if (isZodNullable(unwrapped)) {
|
|
451
|
+
notNull$1 = false;
|
|
452
|
+
unwrapped = unwrapped.unwrap();
|
|
453
|
+
}
|
|
454
|
+
if (isZodDefault(unwrapped)) {
|
|
455
|
+
const def = unwrapped._zod.def.defaultValue;
|
|
456
|
+
if (isDate(def)) defaultValue$1 = { now: true };
|
|
457
|
+
else if (typeof def === "string" || typeof def === "number") defaultValue$1 = String(def);
|
|
458
|
+
unwrapped = unwrapped.removeDefault();
|
|
459
|
+
}
|
|
460
|
+
if (isZodString(unwrapped)) type = InternalFieldType.TEXT;
|
|
461
|
+
else if (isZodNumber(unwrapped)) {
|
|
462
|
+
const checks = unwrapped._zod.def.checks ?? [];
|
|
463
|
+
const isInt = checks.some((c) => "format" in c._zod.def && c._zod.def.format === "safeint");
|
|
464
|
+
type = isInt ? InternalFieldType.INT : InternalFieldType.BIGINT;
|
|
465
|
+
} else if (isZodBoolean(unwrapped)) type = InternalFieldType.BOOL;
|
|
466
|
+
else if (isZodDate(unwrapped)) type = InternalFieldType.TIMESTAMPTZ;
|
|
467
|
+
else if (isZodObject(unwrapped) || isZodArray(unwrapped)) type = InternalFieldType.JSONB;
|
|
468
|
+
else return null;
|
|
469
|
+
const field$1 = {
|
|
470
|
+
name,
|
|
471
|
+
type,
|
|
472
|
+
notNull: notNull$1,
|
|
473
|
+
default: defaultValue$1
|
|
474
|
+
};
|
|
475
|
+
if (!meta) return field$1;
|
|
476
|
+
if (meta.dbColumn) field$1.name = meta.dbColumn;
|
|
477
|
+
if (typeof meta.notNull === "boolean") field$1.notNull = meta.notNull;
|
|
478
|
+
if (meta.default !== undefined) field$1.default = meta.default;
|
|
479
|
+
if (meta.primaryKey) field$1.primaryKey = true;
|
|
480
|
+
if (meta.unique) field$1.unique = true;
|
|
481
|
+
if (meta.relationKind === InternalDecoratedFieldKind.MANY_TO_MANY) return null;
|
|
482
|
+
if (meta.references) {
|
|
483
|
+
const targetMetadata = resolveReferenceTarget ? resolveReferenceTarget(meta.references.target) : resolveReferenceTargetFromRegistry(meta.references.target, registry, meta.references.options?.column);
|
|
484
|
+
field$1.references = {
|
|
485
|
+
table: targetMetadata.table,
|
|
486
|
+
column: meta.references.options?.column ?? targetMetadata.pk,
|
|
487
|
+
onDelete: meta.references.options?.onDelete,
|
|
488
|
+
onUpdate: meta.references.options?.onUpdate
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
return field$1;
|
|
492
|
+
}
|
|
493
|
+
function resolveReferenceTargetFromRegistry(target, registry, explicitColumn) {
|
|
494
|
+
const targetModel = registry.resolveRef(target);
|
|
495
|
+
const primaryKey$1 = explicitColumn ?? targetModel.metadata.fields.find((candidate) => candidate.primaryKey)?.name ?? "id";
|
|
496
|
+
return {
|
|
497
|
+
table: targetModel.metadata.table,
|
|
498
|
+
pk: primaryKey$1
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
function inferFieldsFromSchema(schema, options) {
|
|
502
|
+
const registry = options?.registry ?? ModelRegistry.global();
|
|
503
|
+
const shape = schema.shape;
|
|
504
|
+
const fields = [];
|
|
505
|
+
for (const [name, zodType] of Object.entries(shape)) {
|
|
506
|
+
const field$1 = inferField(name, zodType, getFieldMetadata(zodType), registry, options?.resolveReferenceTarget);
|
|
507
|
+
if (field$1) fields.push(field$1);
|
|
508
|
+
}
|
|
509
|
+
return fields;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
//#endregion
|
|
513
|
+
//#region src/domain/internal/InternalRelationType.ts
|
|
514
|
+
const InternalRelationType = {
|
|
515
|
+
HAS_MANY: "hasMany",
|
|
516
|
+
BELONGS_TO: "belongsTo",
|
|
517
|
+
HAS_ONE: "hasOne"
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
//#endregion
|
|
521
|
+
//#region src/model/relations/RelationBuilder.ts
|
|
522
|
+
var RelationBuilder = class {
|
|
523
|
+
/** Declare a one-to-many relation from this model to `target`. */
|
|
524
|
+
hasMany(target, foreignKey$1) {
|
|
525
|
+
return {
|
|
526
|
+
type: InternalRelationType.HAS_MANY,
|
|
527
|
+
target,
|
|
528
|
+
foreignKey: foreignKey$1
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
/** Declare an owning relation to a parent model. */
|
|
532
|
+
belongsTo(target, foreignKey$1, localKey) {
|
|
533
|
+
return {
|
|
534
|
+
type: InternalRelationType.BELONGS_TO,
|
|
535
|
+
target,
|
|
536
|
+
foreignKey: foreignKey$1,
|
|
537
|
+
localKey
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
/** Declare a one-to-one relation from this model to `target`. */
|
|
541
|
+
hasOne(target, foreignKey$1) {
|
|
542
|
+
return {
|
|
543
|
+
type: InternalRelationType.HAS_ONE,
|
|
544
|
+
target,
|
|
545
|
+
foreignKey: foreignKey$1
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
//#endregion
|
|
551
|
+
//#region src/model/relations/RelationDescriptorNormalizer.ts
|
|
552
|
+
var RelationDescriptorNormalizer = class RelationDescriptorNormalizer {
|
|
553
|
+
constructor(sourceModelKey, schema) {
|
|
554
|
+
this.sourceModelKey = sourceModelKey;
|
|
555
|
+
this.schema = schema;
|
|
556
|
+
}
|
|
557
|
+
static normalize(sourceModelKey, schema) {
|
|
558
|
+
return new RelationDescriptorNormalizer(sourceModelKey, schema).normalize();
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Run the field-authored relation normalization pipeline for one model
|
|
562
|
+
* schema and emit descriptors that later relation stages can resolve.
|
|
563
|
+
*/
|
|
564
|
+
normalize() {
|
|
565
|
+
const descriptors = [];
|
|
566
|
+
for (const candidate of this.collectRelationCandidates()) {
|
|
567
|
+
const descriptor = this.normalizeCandidate(candidate);
|
|
568
|
+
if (descriptor) descriptors.push(descriptor);
|
|
569
|
+
}
|
|
570
|
+
return descriptors;
|
|
571
|
+
}
|
|
572
|
+
collectRelationCandidates() {
|
|
573
|
+
return Object.entries(this.schema.shape).map(([sourceSchemaFieldKey, zodType]) => ({
|
|
574
|
+
sourceSchemaFieldKey,
|
|
575
|
+
zodType
|
|
576
|
+
}));
|
|
577
|
+
}
|
|
578
|
+
normalizeCandidate(candidate) {
|
|
579
|
+
const meta = getFieldMetadata(candidate.zodType);
|
|
580
|
+
if (!meta?.references || !meta.relationKind) return undefined;
|
|
581
|
+
return {
|
|
582
|
+
edgeId: this.buildEdgeId(candidate.sourceSchemaFieldKey, meta.relationKind),
|
|
583
|
+
sourceModelKey: this.sourceModelKey,
|
|
584
|
+
sourceSchemaFieldKey: candidate.sourceSchemaFieldKey,
|
|
585
|
+
targetRef: meta.references.target,
|
|
586
|
+
origin: meta.relationKind,
|
|
587
|
+
localFieldName: candidate.sourceSchemaFieldKey,
|
|
588
|
+
dbColumnName: meta.dbColumn ?? candidate.sourceSchemaFieldKey,
|
|
589
|
+
referencedTargetColumn: meta.references.options?.column,
|
|
590
|
+
onDelete: meta.references.options?.onDelete,
|
|
591
|
+
onUpdate: meta.references.options?.onUpdate,
|
|
592
|
+
unique: meta.unique || meta.relationKind === InternalDecoratedFieldKind.ONE_TO_ONE,
|
|
593
|
+
explicitForwardName: meta.forwardName,
|
|
594
|
+
explicitReverseName: meta.reverseName,
|
|
595
|
+
namingHint: this.deriveNamingHint(candidate.sourceSchemaFieldKey),
|
|
596
|
+
provenance: "field-decorator"
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
buildEdgeId(sourceSchemaFieldKey, origin) {
|
|
600
|
+
return `${this.sourceModelKey}:${sourceSchemaFieldKey}:${origin}`;
|
|
601
|
+
}
|
|
602
|
+
deriveNamingHint(fieldKey) {
|
|
603
|
+
if (fieldKey.endsWith("Id") && fieldKey.length > 2) return fieldKey.slice(0, -2);
|
|
604
|
+
if (fieldKey.endsWith("_id") && fieldKey.length > 3) return fieldKey.slice(0, -3);
|
|
605
|
+
return fieldKey;
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
//#endregion
|
|
610
|
+
//#region src/model/relations/SchemaNaming.ts
|
|
611
|
+
function toSnakeCase(value) {
|
|
612
|
+
return value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[\s-]+/g, "_").toLowerCase();
|
|
613
|
+
}
|
|
614
|
+
function pluralize(value) {
|
|
615
|
+
if (/(s|x|z|ch|sh)$/.test(value)) return `${value}es`;
|
|
616
|
+
if (/[^aeiou]y$/.test(value)) return `${value.slice(0, -1)}ies`;
|
|
617
|
+
return `${value}s`;
|
|
618
|
+
}
|
|
619
|
+
function deriveTableName(name) {
|
|
620
|
+
return pluralize(toSnakeCase(name));
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
//#endregion
|
|
624
|
+
//#region src/model/internal/InternalSchemaModel.ts
|
|
625
|
+
var InternalSchemaModel = class InternalSchemaModel {
|
|
626
|
+
static BRAND = "tango.schema.internal_schema_model";
|
|
627
|
+
__tangoBrand = InternalSchemaModel.BRAND;
|
|
628
|
+
metadata;
|
|
629
|
+
schema;
|
|
630
|
+
hooks;
|
|
631
|
+
registry;
|
|
632
|
+
normalizedRelations;
|
|
633
|
+
explicitFields;
|
|
634
|
+
explicitRelations;
|
|
635
|
+
constructor(registry, metadata, schema, hooks, normalizedRelations, explicitFields, explicitRelations) {
|
|
636
|
+
this.registry = registry;
|
|
637
|
+
this.metadata = metadata;
|
|
638
|
+
this.schema = schema;
|
|
639
|
+
this.hooks = hooks;
|
|
640
|
+
this.normalizedRelations = Object.freeze([...normalizedRelations]);
|
|
641
|
+
this.explicitFields = explicitFields ? Object.freeze([...explicitFields]) : undefined;
|
|
642
|
+
this.explicitRelations = explicitRelations;
|
|
643
|
+
}
|
|
644
|
+
static create(definition, registry) {
|
|
645
|
+
InternalSchemaModel.validateDefinition(definition);
|
|
646
|
+
const builder = new RelationBuilder();
|
|
647
|
+
const relations = definition.relations ? Object.freeze(definition.relations(builder)) : undefined;
|
|
648
|
+
const key = `${definition.namespace}/${definition.name}`;
|
|
649
|
+
const table = definition.table?.trim() || deriveTableName(definition.name);
|
|
650
|
+
const normalizedRelations = RelationDescriptorNormalizer.normalize(key, definition.schema);
|
|
651
|
+
const metadata = {
|
|
652
|
+
namespace: definition.namespace,
|
|
653
|
+
name: definition.name,
|
|
654
|
+
key,
|
|
655
|
+
table,
|
|
656
|
+
fields: [],
|
|
657
|
+
indexes: definition.indexes,
|
|
658
|
+
relations,
|
|
659
|
+
ordering: definition.ordering,
|
|
660
|
+
managed: definition.managed,
|
|
661
|
+
defaultRelatedName: definition.defaultRelatedName,
|
|
662
|
+
constraints: definition.constraints
|
|
663
|
+
};
|
|
664
|
+
Object.defineProperty(metadata, "fields", {
|
|
665
|
+
enumerable: true,
|
|
666
|
+
configurable: false,
|
|
667
|
+
get: () => registry.getFinalizedFields(key)
|
|
668
|
+
});
|
|
669
|
+
Object.freeze(metadata);
|
|
670
|
+
return new InternalSchemaModel(registry, metadata, definition.schema, definition.hooks, normalizedRelations, definition.fields, relations);
|
|
671
|
+
}
|
|
672
|
+
static isInternalSchemaModel(value) {
|
|
673
|
+
return typeof value === "object" && value !== null && value.__tangoBrand === InternalSchemaModel.BRAND;
|
|
674
|
+
}
|
|
675
|
+
static getRegistryOwner(model) {
|
|
676
|
+
return InternalSchemaModel.require(model).registry;
|
|
677
|
+
}
|
|
678
|
+
static getNormalizedRelations(model) {
|
|
679
|
+
return InternalSchemaModel.require(model).normalizedRelations;
|
|
680
|
+
}
|
|
681
|
+
static getExplicitFields(model) {
|
|
682
|
+
return InternalSchemaModel.require(model).explicitFields;
|
|
683
|
+
}
|
|
684
|
+
static getExplicitRelations(model) {
|
|
685
|
+
return InternalSchemaModel.require(model).explicitRelations;
|
|
686
|
+
}
|
|
687
|
+
static validateDefinition(definition) {
|
|
688
|
+
if (!definition.namespace.trim()) throw new Error("Model.namespace is required and cannot be empty.");
|
|
689
|
+
if (!definition.name.trim()) throw new Error("Model.name is required and cannot be empty.");
|
|
690
|
+
if (definition.table !== undefined && !definition.table.trim()) throw new Error("Model.table cannot be empty when provided.");
|
|
691
|
+
}
|
|
692
|
+
static require(model) {
|
|
693
|
+
if (!InternalSchemaModel.isInternalSchemaModel(model)) throw new Error(`Model '${model.metadata.key}' is missing internal registry ownership metadata.`);
|
|
694
|
+
return model;
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
//#endregion
|
|
699
|
+
//#region src/model/relations/RelationSpec.ts
|
|
700
|
+
const InternalRelationPublicKind = {
|
|
701
|
+
BELONGS_TO: "belongsTo",
|
|
702
|
+
HAS_ONE: "hasOne",
|
|
703
|
+
HAS_MANY: "hasMany",
|
|
704
|
+
MANY_TO_MANY: "manyToMany"
|
|
705
|
+
};
|
|
706
|
+
const InternalRelationStorageStrategy = {
|
|
707
|
+
REFERENCE: "reference",
|
|
708
|
+
REVERSE_REFERENCE: "reverse_reference",
|
|
709
|
+
MANY_TO_MANY: "many_to_many"
|
|
710
|
+
};
|
|
711
|
+
const InternalRelationCardinality = {
|
|
712
|
+
SINGLE: "single",
|
|
713
|
+
MANY: "many"
|
|
714
|
+
};
|
|
715
|
+
const InternalRelationProvenance = {
|
|
716
|
+
FIELD_DECORATOR: "field-decorator",
|
|
717
|
+
RELATIONS_API: "relations-api",
|
|
718
|
+
SYNTHESIZED_REVERSE: "synthesized-reverse"
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
//#endregion
|
|
722
|
+
//#region src/model/relations/ResolvedRelationGraphBuilder.ts
|
|
723
|
+
const REFERENCE_CAPABILITIES = Object.freeze({
|
|
724
|
+
migratable: true,
|
|
725
|
+
queryable: true,
|
|
726
|
+
hydratable: true
|
|
727
|
+
});
|
|
728
|
+
const MANY_TO_MANY_CAPABILITIES = Object.freeze({
|
|
729
|
+
migratable: false,
|
|
730
|
+
queryable: false,
|
|
731
|
+
hydratable: false
|
|
732
|
+
});
|
|
733
|
+
const RELATION_NAME_SEPARATOR = ":";
|
|
734
|
+
var ResolvedRelationGraphBuilder = class ResolvedRelationGraphBuilder {
|
|
735
|
+
byModel = new Map();
|
|
736
|
+
byEdgeId = new Map();
|
|
737
|
+
matchedOverrides = new Set();
|
|
738
|
+
constructor(options) {
|
|
739
|
+
this.options = options;
|
|
740
|
+
}
|
|
741
|
+
static build(options) {
|
|
742
|
+
return new ResolvedRelationGraphBuilder(options).build();
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Resolve every model's normalized relation descriptors into a single
|
|
746
|
+
* registry-scoped graph and fail when authoring ambiguity remains.
|
|
747
|
+
*/
|
|
748
|
+
build() {
|
|
749
|
+
for (const model of this.options.models) this.addModelRelations(model);
|
|
750
|
+
for (const model of this.options.models) this.assertAllOverridesMatched(model);
|
|
751
|
+
return {
|
|
752
|
+
version: this.options.version,
|
|
753
|
+
byModel: this.byModel,
|
|
754
|
+
byEdgeId: this.byEdgeId
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
addModelRelations(model) {
|
|
758
|
+
const explicitRelations = InternalSchemaModel.getExplicitRelations(model) ?? {};
|
|
759
|
+
for (const descriptor of InternalSchemaModel.getNormalizedRelations(model)) {
|
|
760
|
+
const targetModel = this.options.resolveRef(descriptor.targetRef);
|
|
761
|
+
if (descriptor.origin === "manyToMany") {
|
|
762
|
+
const relationName = descriptor.explicitForwardName ?? descriptor.namingHint;
|
|
763
|
+
this.addResolvedRelation({
|
|
764
|
+
edgeId: descriptor.edgeId,
|
|
765
|
+
sourceModelKey: model.metadata.key,
|
|
766
|
+
targetModelKey: targetModel.metadata.key,
|
|
767
|
+
name: relationName,
|
|
768
|
+
kind: InternalRelationPublicKind.MANY_TO_MANY,
|
|
769
|
+
storageStrategy: InternalRelationStorageStrategy.MANY_TO_MANY,
|
|
770
|
+
cardinality: InternalRelationCardinality.MANY,
|
|
771
|
+
capabilities: MANY_TO_MANY_CAPABILITIES,
|
|
772
|
+
provenance: InternalRelationProvenance.FIELD_DECORATOR,
|
|
773
|
+
alias: `${toSnakeCase(model.metadata.name)}_${relationName}`
|
|
774
|
+
});
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
this.addReferenceRelations(model, descriptor, targetModel, explicitRelations);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
addReferenceRelations(sourceModel, descriptor, targetModel, explicitRelations) {
|
|
781
|
+
const forwardOverride = this.findForwardOverride(sourceModel, targetModel, descriptor, explicitRelations);
|
|
782
|
+
if (forwardOverride) this.markOverrideMatched(sourceModel.metadata.key, forwardOverride[0]);
|
|
783
|
+
const forwardName = forwardOverride?.[0] ?? descriptor.explicitForwardName ?? descriptor.namingHint;
|
|
784
|
+
const targetPrimaryKey = this.getPrimaryKey(targetModel.metadata.key);
|
|
785
|
+
this.addResolvedRelation({
|
|
786
|
+
edgeId: descriptor.edgeId,
|
|
787
|
+
sourceModelKey: sourceModel.metadata.key,
|
|
788
|
+
targetModelKey: targetModel.metadata.key,
|
|
789
|
+
name: forwardName,
|
|
790
|
+
inverseEdgeId: `${descriptor.edgeId}:inverse`,
|
|
791
|
+
kind: InternalRelationPublicKind.BELONGS_TO,
|
|
792
|
+
storageStrategy: InternalRelationStorageStrategy.REFERENCE,
|
|
793
|
+
cardinality: InternalRelationCardinality.SINGLE,
|
|
794
|
+
localFieldName: descriptor.dbColumnName,
|
|
795
|
+
targetFieldName: descriptor.referencedTargetColumn ?? targetPrimaryKey,
|
|
796
|
+
capabilities: REFERENCE_CAPABILITIES,
|
|
797
|
+
provenance: InternalRelationProvenance.FIELD_DECORATOR,
|
|
798
|
+
alias: `${toSnakeCase(targetModel.metadata.name)}_${forwardName}`
|
|
799
|
+
});
|
|
800
|
+
const reverseOverride = this.findReverseOverride(sourceModel, targetModel, descriptor);
|
|
801
|
+
if (reverseOverride) this.markOverrideMatched(targetModel.metadata.key, reverseOverride[0]);
|
|
802
|
+
const reverseKind = descriptor.unique ? InternalRelationPublicKind.HAS_ONE : InternalRelationPublicKind.HAS_MANY;
|
|
803
|
+
const reverseCardinality = descriptor.unique ? InternalRelationCardinality.SINGLE : InternalRelationCardinality.MANY;
|
|
804
|
+
const reverseName = reverseOverride?.[0] ?? descriptor.explicitReverseName ?? this.deriveReverseName(sourceModel, reverseCardinality);
|
|
805
|
+
this.addResolvedRelation({
|
|
806
|
+
edgeId: `${descriptor.edgeId}:inverse`,
|
|
807
|
+
sourceModelKey: targetModel.metadata.key,
|
|
808
|
+
targetModelKey: sourceModel.metadata.key,
|
|
809
|
+
name: reverseName,
|
|
810
|
+
inverseEdgeId: descriptor.edgeId,
|
|
811
|
+
kind: reverseKind,
|
|
812
|
+
storageStrategy: InternalRelationStorageStrategy.REVERSE_REFERENCE,
|
|
813
|
+
cardinality: reverseCardinality,
|
|
814
|
+
localFieldName: descriptor.dbColumnName,
|
|
815
|
+
targetFieldName: descriptor.referencedTargetColumn ?? targetPrimaryKey,
|
|
816
|
+
capabilities: REFERENCE_CAPABILITIES,
|
|
817
|
+
provenance: reverseOverride ? InternalRelationProvenance.RELATIONS_API : InternalRelationProvenance.SYNTHESIZED_REVERSE,
|
|
818
|
+
alias: `${toSnakeCase(sourceModel.metadata.name)}_${reverseName}`
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
findForwardOverride(sourceModel, targetModel, descriptor, explicitRelations) {
|
|
822
|
+
return Object.entries(explicitRelations).find(([, relation]) => {
|
|
823
|
+
const relationTargetKey = this.resolveRelationTargetKey(sourceModel, relation.target);
|
|
824
|
+
return relation.type === InternalRelationPublicKind.BELONGS_TO && relationTargetKey === targetModel.metadata.key && relation.foreignKey === descriptor.sourceSchemaFieldKey;
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
findReverseOverride(sourceModel, targetModel, descriptor) {
|
|
828
|
+
const reverseModelRelations = InternalSchemaModel.getExplicitRelations(targetModel) ?? {};
|
|
829
|
+
const reverseKind = descriptor.unique ? InternalRelationPublicKind.HAS_ONE : InternalRelationPublicKind.HAS_MANY;
|
|
830
|
+
return Object.entries(reverseModelRelations).find(([, relation]) => {
|
|
831
|
+
const relationTargetKey = this.resolveRelationTargetKey(targetModel, relation.target);
|
|
832
|
+
return relationTargetKey === sourceModel.metadata.key && relation.type === reverseKind && relation.foreignKey === descriptor.sourceSchemaFieldKey;
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
assertAllOverridesMatched(model) {
|
|
836
|
+
const explicitRelations = InternalSchemaModel.getExplicitRelations(model);
|
|
837
|
+
if (!explicitRelations) return;
|
|
838
|
+
for (const relationName of Object.keys(explicitRelations)) {
|
|
839
|
+
const marker = this.buildOverrideMarker(model.metadata.key, relationName);
|
|
840
|
+
if (!this.matchedOverrides.has(marker)) throw new Error(`Relation override '${relationName}' on model '${model.metadata.key}' does not match a field-authored relation.`);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
addResolvedRelation(descriptor) {
|
|
844
|
+
const modelRelations = this.byModel.get(descriptor.sourceModelKey) ?? new Map();
|
|
845
|
+
const existing = modelRelations.get(descriptor.name);
|
|
846
|
+
if (existing) throw new Error(`Ambiguous relation name '${descriptor.name}' on model '${descriptor.sourceModelKey}'. Add an explicit relations override.`);
|
|
847
|
+
modelRelations.set(descriptor.name, descriptor);
|
|
848
|
+
this.byModel.set(descriptor.sourceModelKey, modelRelations);
|
|
849
|
+
this.byEdgeId.set(descriptor.edgeId, descriptor);
|
|
850
|
+
}
|
|
851
|
+
getPrimaryKey(modelKey) {
|
|
852
|
+
return this.options.storage.byModel.get(modelKey).pk;
|
|
853
|
+
}
|
|
854
|
+
markOverrideMatched(modelKey, relationName) {
|
|
855
|
+
this.matchedOverrides.add(this.buildOverrideMarker(modelKey, relationName));
|
|
856
|
+
}
|
|
857
|
+
buildOverrideMarker(modelKey, relationName) {
|
|
858
|
+
return `${modelKey}${RELATION_NAME_SEPARATOR}${relationName}`;
|
|
859
|
+
}
|
|
860
|
+
resolveRelationTargetKey(sourceModel, target) {
|
|
861
|
+
if (target.includes("/")) return target;
|
|
862
|
+
return `${sourceModel.metadata.namespace}/${target}`;
|
|
863
|
+
}
|
|
864
|
+
deriveReverseName(sourceModel, cardinality) {
|
|
865
|
+
if (sourceModel.metadata.defaultRelatedName) return sourceModel.metadata.defaultRelatedName;
|
|
866
|
+
const snake = toSnakeCase(sourceModel.metadata.name);
|
|
867
|
+
return cardinality === InternalRelationCardinality.MANY ? pluralize(snake) : snake;
|
|
868
|
+
}
|
|
869
|
+
};
|
|
870
|
+
|
|
871
|
+
//#endregion
|
|
872
|
+
//#region src/model/registry/ModelRegistry.ts
|
|
873
|
+
const DEFAULT_IDENTIFIER_NAME = "id";
|
|
874
|
+
const activeRegistryStorage = new AsyncLocalStorage();
|
|
875
|
+
var ModelRegistry = class ModelRegistry {
|
|
876
|
+
static globalRegistry;
|
|
877
|
+
models = new Map();
|
|
878
|
+
version = 0;
|
|
879
|
+
storageCache;
|
|
880
|
+
relationGraphCache;
|
|
881
|
+
/**
|
|
882
|
+
* Return the shared process-wide registry used by `Model(...)`.
|
|
883
|
+
*/
|
|
884
|
+
static global() {
|
|
885
|
+
if (!ModelRegistry.globalRegistry) ModelRegistry.globalRegistry = new ModelRegistry();
|
|
886
|
+
return ModelRegistry.globalRegistry;
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Return the registry currently bound to model construction work.
|
|
890
|
+
*/
|
|
891
|
+
static active() {
|
|
892
|
+
return activeRegistryStorage.getStore() ?? ModelRegistry.global();
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Run work with a specific registry bound as the active construction target.
|
|
896
|
+
*/
|
|
897
|
+
static async runWithRegistry(registry, work) {
|
|
898
|
+
return await activeRegistryStorage.run(registry, work);
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Register a model on the shared global registry.
|
|
902
|
+
*/
|
|
903
|
+
static register(model) {
|
|
904
|
+
ModelRegistry.global().register(model);
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Register several models on the shared global registry.
|
|
908
|
+
*/
|
|
909
|
+
static registerMany(models) {
|
|
910
|
+
ModelRegistry.global().registerMany(models);
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Resolve a model from the shared registry by namespace and name.
|
|
914
|
+
*/
|
|
915
|
+
static get(namespace, name) {
|
|
916
|
+
return ModelRegistry.global().get(namespace, name);
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Resolve a model from the shared registry by its `namespace/name` key.
|
|
920
|
+
*/
|
|
921
|
+
static getByKey(key) {
|
|
922
|
+
return ModelRegistry.global().getByKey(key);
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* Resolve any supported model reference form against the shared registry.
|
|
926
|
+
*/
|
|
927
|
+
static resolveRef(ref) {
|
|
928
|
+
return ModelRegistry.global().resolveRef(ref);
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Clear the shared registry, which is mainly useful in tests.
|
|
932
|
+
*/
|
|
933
|
+
static clear() {
|
|
934
|
+
ModelRegistry.global().clear();
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Return the owning registry for a model.
|
|
938
|
+
*/
|
|
939
|
+
static getOwner(model) {
|
|
940
|
+
return InternalSchemaModel.getRegistryOwner(model);
|
|
941
|
+
}
|
|
942
|
+
/**
|
|
943
|
+
* Register a model on this registry instance.
|
|
944
|
+
*/
|
|
945
|
+
register(model) {
|
|
946
|
+
const owner = InternalSchemaModel.getRegistryOwner(model);
|
|
947
|
+
if (owner !== this) throw new Error(`Model '${model.metadata.key}' belongs to a different registry and cannot be registered here.`);
|
|
948
|
+
const existing = this.models.get(model.metadata.key);
|
|
949
|
+
if (existing && existing !== model) throw new Error(`Model '${model.metadata.key}' is already registered in this registry.`);
|
|
950
|
+
this.models.set(model.metadata.key, model);
|
|
951
|
+
this.bumpVersion();
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Register several models on this registry instance.
|
|
955
|
+
*/
|
|
956
|
+
registerMany(models) {
|
|
957
|
+
for (const model of models) this.register(model);
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Resolve a model from this registry instance by namespace and name.
|
|
961
|
+
*/
|
|
962
|
+
get(namespace, name) {
|
|
963
|
+
return this.getByKey(`${namespace}/${name}`);
|
|
964
|
+
}
|
|
965
|
+
/**
|
|
966
|
+
* Resolve a model from this registry instance by its `namespace/name` key.
|
|
967
|
+
*/
|
|
968
|
+
getByKey(key) {
|
|
969
|
+
return this.models.get(key);
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* Resolve a string, callback, or direct model reference into a model object.
|
|
973
|
+
*/
|
|
974
|
+
resolveRef(ref) {
|
|
975
|
+
if (typeof ref === "string" || isTypedModelRef(ref)) {
|
|
976
|
+
const key = typeof ref === "string" ? ref : ref.key;
|
|
977
|
+
const model$1 = this.getByKey(key);
|
|
978
|
+
if (!model$1) throw new Error(`Unable to resolve model reference '${key}'. Ensure it is registered in ModelRegistry.`);
|
|
979
|
+
return model$1;
|
|
980
|
+
}
|
|
981
|
+
const model = typeof ref === "function" ? ref() : ref;
|
|
982
|
+
if (InternalSchemaModel.getRegistryOwner(model) !== this) throw new Error(`Model reference '${model.metadata.key}' belongs to a different registry and cannot be resolved here.`);
|
|
983
|
+
return model;
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* Finalize storage-only artifacts for all models in this registry.
|
|
987
|
+
*/
|
|
988
|
+
finalizeStorageArtifacts() {
|
|
989
|
+
if (this.storageCache?.version === this.version) return this.storageCache;
|
|
990
|
+
const primaryKeyByModel = new Map();
|
|
991
|
+
for (const model of this.models.values()) primaryKeyByModel.set(model.metadata.key, this.inferPrimaryKeyName(model));
|
|
992
|
+
const byModel = new Map();
|
|
993
|
+
for (const model of this.models.values()) {
|
|
994
|
+
const explicitFields = InternalSchemaModel.getExplicitFields(model);
|
|
995
|
+
const inferredFields = inferFieldsFromSchema(model.schema, {
|
|
996
|
+
registry: this,
|
|
997
|
+
resolveReferenceTarget: (target) => {
|
|
998
|
+
const targetModel = this.resolveRef(target);
|
|
999
|
+
return {
|
|
1000
|
+
table: targetModel.metadata.table,
|
|
1001
|
+
pk: primaryKeyByModel.get(targetModel.metadata.key)
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
});
|
|
1005
|
+
const fields = this.freezeFields(this.mergeStorageFields(inferredFields, explicitFields));
|
|
1006
|
+
const primaryKey$1 = fields.find((field$1) => field$1.primaryKey)?.name ?? DEFAULT_IDENTIFIER_NAME;
|
|
1007
|
+
byModel.set(model.metadata.key, {
|
|
1008
|
+
key: model.metadata.key,
|
|
1009
|
+
table: model.metadata.table,
|
|
1010
|
+
fields,
|
|
1011
|
+
pk: primaryKey$1
|
|
1012
|
+
});
|
|
1013
|
+
}
|
|
1014
|
+
const finalized = {
|
|
1015
|
+
version: this.version,
|
|
1016
|
+
byModel
|
|
1017
|
+
};
|
|
1018
|
+
this.storageCache = finalized;
|
|
1019
|
+
return finalized;
|
|
1020
|
+
}
|
|
1021
|
+
/**
|
|
1022
|
+
* Return finalized storage fields for a specific model.
|
|
1023
|
+
*/
|
|
1024
|
+
getFinalizedFields(model) {
|
|
1025
|
+
const key = typeof model === "string" ? model : model.metadata.key;
|
|
1026
|
+
const fields = this.finalizeStorageArtifacts().byModel.get(key)?.fields;
|
|
1027
|
+
if (!fields) throw new Error(`No finalized storage fields are available for model '${key}'.`);
|
|
1028
|
+
return fields;
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Resolve the registry's relation graph from finalized storage artifacts.
|
|
1032
|
+
*/
|
|
1033
|
+
getResolvedRelationGraph() {
|
|
1034
|
+
if (this.relationGraphCache?.version === this.version) return this.relationGraphCache;
|
|
1035
|
+
const finalized = ResolvedRelationGraphBuilder.build({
|
|
1036
|
+
version: this.version,
|
|
1037
|
+
models: this.values(),
|
|
1038
|
+
storage: this.finalizeStorageArtifacts(),
|
|
1039
|
+
resolveRef: (ref) => this.resolveRef(ref)
|
|
1040
|
+
});
|
|
1041
|
+
this.relationGraphCache = finalized;
|
|
1042
|
+
return finalized;
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Remove all registered models from this registry instance.
|
|
1046
|
+
*/
|
|
1047
|
+
clear() {
|
|
1048
|
+
this.models.clear();
|
|
1049
|
+
this.bumpVersion();
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Return all registered models in insertion order.
|
|
1053
|
+
*/
|
|
1054
|
+
values() {
|
|
1055
|
+
return Array.from(this.models.values());
|
|
1056
|
+
}
|
|
1057
|
+
bumpVersion() {
|
|
1058
|
+
this.version += 1;
|
|
1059
|
+
this.storageCache = undefined;
|
|
1060
|
+
this.relationGraphCache = undefined;
|
|
1061
|
+
}
|
|
1062
|
+
freezeFields(fields) {
|
|
1063
|
+
return Object.freeze(fields.map((field$1) => Object.freeze({ ...field$1 })));
|
|
1064
|
+
}
|
|
1065
|
+
inferPrimaryKeyName(model) {
|
|
1066
|
+
for (const [fieldKey, zodType] of Object.entries(model.schema.shape)) {
|
|
1067
|
+
const meta = getFieldMetadata(zodType);
|
|
1068
|
+
if (meta?.primaryKey) return meta.dbColumn ?? fieldKey;
|
|
1069
|
+
}
|
|
1070
|
+
const explicitFields = InternalSchemaModel.getExplicitFields(model);
|
|
1071
|
+
if (explicitFields) return explicitFields.find((field$1) => field$1.primaryKey)?.name ?? DEFAULT_IDENTIFIER_NAME;
|
|
1072
|
+
return DEFAULT_IDENTIFIER_NAME;
|
|
1073
|
+
}
|
|
1074
|
+
mergeStorageFields(inferredFields, explicitFields) {
|
|
1075
|
+
if (!explicitFields?.length) return inferredFields;
|
|
1076
|
+
const mergedFields = new Map(inferredFields.map((field$1) => [field$1.name, field$1]));
|
|
1077
|
+
for (const explicitField of explicitFields) mergedFields.set(explicitField.name, explicitField);
|
|
1078
|
+
return Array.from(mergedFields.values());
|
|
1079
|
+
}
|
|
1080
|
+
};
|
|
1081
|
+
|
|
1082
|
+
//#endregion
|
|
1083
|
+
//#region src/model/registry/index.ts
|
|
1084
|
+
var registry_exports = {};
|
|
1085
|
+
__export(registry_exports, { ModelRegistry: () => ModelRegistry });
|
|
1086
|
+
|
|
1087
|
+
//#endregion
|
|
1088
|
+
//#region src/model/relations/index.ts
|
|
1089
|
+
var relations_exports = {};
|
|
1090
|
+
__export(relations_exports, { RelationBuilder: () => RelationBuilder });
|
|
1091
|
+
|
|
1092
|
+
//#endregion
|
|
1093
|
+
//#region src/model/ModelAugmentorRegistry.ts
|
|
1094
|
+
const modelAugmentors = new Set();
|
|
1095
|
+
function registerModelAugmentor(augmentor) {
|
|
1096
|
+
modelAugmentors.add(augmentor);
|
|
1097
|
+
for (const model of ModelRegistry.global().values()) augmentor(model);
|
|
1098
|
+
return () => {
|
|
1099
|
+
modelAugmentors.delete(augmentor);
|
|
1100
|
+
};
|
|
1101
|
+
}
|
|
1102
|
+
function applyModelAugmentors(model) {
|
|
1103
|
+
for (const augmentor of modelAugmentors) augmentor(model);
|
|
1104
|
+
return model;
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
//#endregion
|
|
1108
|
+
//#region src/model/Model.ts
|
|
1109
|
+
function Model(definition) {
|
|
1110
|
+
const registry = definition.registry ?? ModelRegistry.active();
|
|
1111
|
+
const model = applyModelAugmentors(InternalSchemaModel.create(definition, registry));
|
|
1112
|
+
registry.register(model);
|
|
1113
|
+
return model;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
//#endregion
|
|
1117
|
+
//#region src/model/index.ts
|
|
1118
|
+
var model_exports = {};
|
|
1119
|
+
__export(model_exports, {
|
|
1120
|
+
Constraints: () => Constraints,
|
|
1121
|
+
Decorators: () => Decorators,
|
|
1122
|
+
Indexes: () => Indexes,
|
|
1123
|
+
InternalDecoratedFieldKind: () => InternalDecoratedFieldKind,
|
|
1124
|
+
Meta: () => Meta,
|
|
1125
|
+
Model: () => Model,
|
|
1126
|
+
ModelRegistry: () => ModelRegistry,
|
|
1127
|
+
RelationBuilder: () => RelationBuilder,
|
|
1128
|
+
c: () => Constraints,
|
|
1129
|
+
constraints: () => constraints_exports,
|
|
1130
|
+
createTypedModelRef: () => createTypedModelRef,
|
|
1131
|
+
decorators: () => decorators_exports,
|
|
1132
|
+
i: () => Indexes,
|
|
1133
|
+
isTypedModelRef: () => isTypedModelRef,
|
|
1134
|
+
m: () => Meta,
|
|
1135
|
+
meta: () => meta_exports,
|
|
1136
|
+
registerModelAugmentor: () => registerModelAugmentor,
|
|
1137
|
+
registry: () => registry_exports,
|
|
1138
|
+
relations: () => relations_exports,
|
|
1139
|
+
t: () => Decorators
|
|
1140
|
+
});
|
|
1141
|
+
|
|
1142
|
+
//#endregion
|
|
1143
|
+
export { Constraints, Decorators, Indexes, InternalDecoratedFieldKind, Meta, Model, ModelRegistry, RelationBuilder, constraints_exports, createTypedModelRef, decorators_exports, isTypedModelRef, meta_exports, model_exports, registerModelAugmentor, registry_exports, relations_exports };
|
|
1144
|
+
//# sourceMappingURL=model-YLW1ydkV.js.map
|