@atscript/db 0.1.38 → 0.1.40

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 (64) hide show
  1. package/README.md +42 -303
  2. package/dist/agg.cjs +8 -3
  3. package/dist/agg.d.cts +7 -0
  4. package/dist/agg.d.mts +7 -0
  5. package/dist/agg.mjs +7 -3
  6. package/dist/control-DRgryKeg.cjs +14 -0
  7. package/dist/{control_as-bjmwe24C.mjs → control-IANbnfjG.mjs} +6 -18
  8. package/dist/db-readable-BQQzfguJ.d.cts +1249 -0
  9. package/dist/db-readable-Bbr4CjMb.d.mts +1249 -0
  10. package/dist/db-space-BUrQ5BFm.d.mts +309 -0
  11. package/dist/db-space-Vxpcnyt5.d.cts +309 -0
  12. package/dist/db-validator-plugin-07kDiis2.d.cts +22 -0
  13. package/dist/db-validator-plugin-CiqsHTI_.d.mts +22 -0
  14. package/dist/db-view-BntnAmXO.cjs +3071 -0
  15. package/dist/db-view-ZsoN91-q.mjs +2970 -0
  16. package/dist/index.cjs +95 -2801
  17. package/dist/index.d.cts +137 -0
  18. package/dist/index.d.mts +137 -0
  19. package/dist/index.mjs +55 -2761
  20. package/dist/{nested-writer-BkqL7cp3.cjs → nested-writer-BDXsDMPP.cjs} +196 -150
  21. package/dist/{nested-writer-NEN51mnR.mjs → nested-writer-Dmm1gbZV.mjs} +118 -70
  22. package/dist/ops-BdRAFLKY.d.mts +67 -0
  23. package/dist/ops-DXJ4Zw0P.d.cts +67 -0
  24. package/dist/ops.cjs +123 -0
  25. package/dist/ops.d.cts +2 -0
  26. package/dist/ops.d.mts +2 -0
  27. package/dist/ops.mjs +112 -0
  28. package/dist/plugin.cjs +90 -109
  29. package/dist/plugin.d.cts +6 -0
  30. package/dist/plugin.d.mts +6 -0
  31. package/dist/plugin.mjs +29 -49
  32. package/dist/rel.cjs +20 -20
  33. package/dist/rel.d.cts +119 -0
  34. package/dist/rel.d.mts +119 -0
  35. package/dist/rel.mjs +4 -5
  36. package/dist/{relation-helpers-guFL_oRf.cjs → relation-helpers-BYvsE1tR.cjs} +26 -22
  37. package/dist/{relation-helpers-DyBIlQnB.mjs → relation-helpers-CLasawQq.mjs} +11 -6
  38. package/dist/{relation-loader-Dv7qXYq7.mjs → relation-loader-BEOTXNcq.mjs} +63 -43
  39. package/dist/{relation-loader-CpnDRf9k.cjs → relation-loader-CRC5LcqM.cjs} +74 -49
  40. package/dist/shared.cjs +13 -13
  41. package/dist/{shared.d.ts → shared.d.cts} +14 -13
  42. package/dist/shared.d.mts +71 -0
  43. package/dist/shared.mjs +2 -3
  44. package/dist/sync.cjs +300 -252
  45. package/dist/sync.d.cts +369 -0
  46. package/dist/sync.d.mts +369 -0
  47. package/dist/sync.mjs +284 -233
  48. package/dist/{validation-utils-DEoCMmEb.cjs → validation-utils-DVJDijnB.cjs} +141 -109
  49. package/dist/{validation-utils-DhR_mtKa.mjs → validation-utils-DhjIjP1-.mjs} +71 -37
  50. package/package.json +31 -30
  51. package/LICENSE +0 -21
  52. package/dist/agg-BJFJ3dFQ.mjs +0 -8
  53. package/dist/agg-DnUWAOK8.cjs +0 -14
  54. package/dist/agg.d.ts +0 -13
  55. package/dist/chunk-CrpGerW8.cjs +0 -31
  56. package/dist/control_as-BFPERAF_.cjs +0 -28
  57. package/dist/index.d.ts +0 -1706
  58. package/dist/logger-B7oxCfLQ.mjs +0 -12
  59. package/dist/logger-Dt2v_-wb.cjs +0 -18
  60. package/dist/plugin.d.ts +0 -5
  61. package/dist/rel.d.ts +0 -1305
  62. package/dist/relation-loader-D4mTw6yH.cjs +0 -4
  63. package/dist/relation-loader-Ggy1ujwR.mjs +0 -4
  64. package/dist/sync.d.ts +0 -1878
@@ -0,0 +1,3071 @@
1
+ const require_nested_writer = require("./nested-writer-BDXsDMPP.cjs");
2
+ const require_agg = require("./agg.cjs");
3
+ const require_relation_helpers = require("./relation-helpers-BYvsE1tR.cjs");
4
+ const require_ops = require("./ops.cjs");
5
+ let _atscript_typescript_utils = require("@atscript/typescript/utils");
6
+ let node_async_hooks = require("node:async_hooks");
7
+ //#region src/logger.ts
8
+ const NoopLogger = {
9
+ error: () => {},
10
+ warn: () => {},
11
+ log: () => {},
12
+ info: () => {},
13
+ debug: () => {}
14
+ };
15
+ //#endregion
16
+ //#region src/table/table-metadata.ts
17
+ const INDEX_PREFIX = "atscript__";
18
+ function indexKey(type, name) {
19
+ return `${INDEX_PREFIX}${type}__${name.replace(/[^a-z0-9_.-]/gi, "_").replace(/_+/g, "_").slice(0, 117 - type.length - 2)}`;
20
+ }
21
+ /**
22
+ * Finds the nearest ancestor of `path` that belongs to `set`.
23
+ * Used by both the build pipeline (in `_classifyFields`) and
24
+ * runtime reconstruction on the Readable.
25
+ */
26
+ function findAncestorInSet(path, set) {
27
+ let pos = path.length;
28
+ while ((pos = path.lastIndexOf(".", pos - 1)) !== -1) {
29
+ const ancestor = path.slice(0, pos);
30
+ if (set.has(ancestor)) return ancestor;
31
+ }
32
+ }
33
+ /** Returns true if `metadata` indicates a navigation relation field. */
34
+ function isNavRelation(metadata) {
35
+ return metadata.has("db.rel.to") || metadata.has("db.rel.from") || metadata.has("db.rel.via");
36
+ }
37
+ /**
38
+ * Computed metadata for a database table or view.
39
+ *
40
+ * Contains all field metadata, physical mapping indexes, relation definitions,
41
+ * and constraint information derived from Atscript annotations. Built lazily
42
+ * on first access via {@link build}, then immutable.
43
+ *
44
+ * This class owns the build pipeline that was previously part of
45
+ * `AtscriptDbReadable._flatten()`. The Readable delegates all metadata
46
+ * access to this class.
47
+ */
48
+ var TableMetadata = class {
49
+ nestedObjects;
50
+ flatMap;
51
+ fieldDescriptors;
52
+ primaryKeys = [];
53
+ originalMetaIdFields = [];
54
+ indexes = /* @__PURE__ */ new Map();
55
+ foreignKeys = /* @__PURE__ */ new Map();
56
+ relations = /* @__PURE__ */ new Map();
57
+ navFields = /* @__PURE__ */ new Set();
58
+ ignoredFields = /* @__PURE__ */ new Set();
59
+ uniqueProps = /* @__PURE__ */ new Set();
60
+ defaults = /* @__PURE__ */ new Map();
61
+ columnMap = /* @__PURE__ */ new Map();
62
+ dimensions = [];
63
+ measures = [];
64
+ pathToPhysical = /* @__PURE__ */ new Map();
65
+ physicalToPath = /* @__PURE__ */ new Map();
66
+ flattenedParents = /* @__PURE__ */ new Set();
67
+ jsonFields = /* @__PURE__ */ new Set();
68
+ selectExpansion = /* @__PURE__ */ new Map();
69
+ booleanFields = /* @__PURE__ */ new Set();
70
+ decimalFields = /* @__PURE__ */ new Set();
71
+ allPhysicalFields = [];
72
+ requiresMappings = false;
73
+ toStorageFormatters;
74
+ fromStorageFormatters;
75
+ /** Leaf field descriptors indexed by physical column name (read path). */
76
+ leafByPhysical = /* @__PURE__ */ new Map();
77
+ /** Leaf field descriptors indexed by logical path (write/patch/filter paths). */
78
+ leafByLogical = /* @__PURE__ */ new Map();
79
+ _built = false;
80
+ _collateMap = /* @__PURE__ */ new Map();
81
+ _columnFromMap = /* @__PURE__ */ new Map();
82
+ constructor(nestedObjects) {
83
+ this.nestedObjects = nestedObjects;
84
+ }
85
+ get isBuilt() {
86
+ return this._built;
87
+ }
88
+ /**
89
+ * Runs the full metadata compilation pipeline. Called once by
90
+ * `AtscriptDbReadable._ensureBuilt()` on first metadata access.
91
+ *
92
+ * Pipeline steps:
93
+ * 1. `adapter.onBeforeFlatten(type)` — adapter hook
94
+ * 2. `flattenAnnotatedType()` — collect field tuples, detect nav fields eagerly
95
+ * 3. Replay non-nav-descendant tuples through annotation scanning + adapter.onFieldScanned
96
+ * 4. Classify fields and build path maps (skipped for nested-objects adapters)
97
+ * 5. `adapter.getMetadataOverrides()` → `_applyOverrides()` (PK/unique/inject adjustments)
98
+ * 6. Build field descriptors (TDbFieldMeta[])
99
+ * 7. Build leaf field indexes (skipped for nested-objects adapters)
100
+ * 8. Finalize indexes (resolve field names to physical)
101
+ * 9. `adapter.onAfterFlatten()` — adapter hook (read-only bookkeeping)
102
+ * 10. Build allPhysicalFields list
103
+ */
104
+ build(type, adapter, logger) {
105
+ if (this._built) return;
106
+ adapter.onBeforeFlatten?.(type);
107
+ const collected = [];
108
+ this.flatMap = (0, _atscript_typescript_utils.flattenAnnotatedType)(type, {
109
+ topLevelArrayTag: adapter.getTopLevelArrayTag?.() ?? "db.__topLevelArray",
110
+ excludePhantomTypes: true,
111
+ onField: (path, fieldType, metadata) => {
112
+ if (isNavRelation(metadata)) this.navFields.add(path);
113
+ collected.push({
114
+ path,
115
+ type: fieldType,
116
+ metadata
117
+ });
118
+ }
119
+ });
120
+ for (const entry of collected) {
121
+ if (findAncestorInSet(entry.path, this.navFields) !== void 0) continue;
122
+ this._scanGenericAnnotations(entry.path, entry.type, entry.metadata, logger);
123
+ adapter.onFieldScanned?.(entry.path, entry.type, entry.metadata);
124
+ }
125
+ if (!this.nestedObjects) this._classifyFields();
126
+ const overrides = adapter.getMetadataOverrides?.(this);
127
+ if (overrides) this._applyOverrides(overrides);
128
+ this._buildFieldDescriptors(adapter);
129
+ if (!this.nestedObjects) this._buildLeafIndexes();
130
+ this._finalizeIndexes();
131
+ this._collateMap.clear();
132
+ this._columnFromMap.clear();
133
+ this.jsonFields.clear();
134
+ this._built = true;
135
+ adapter.onAfterFlatten?.();
136
+ if (this.nestedObjects && this.flatMap) {
137
+ for (const path of this.flatMap.keys()) if (path && !this.ignoredFields.has(path)) this.allPhysicalFields.push(path);
138
+ } else for (const physical of this.pathToPhysical.values()) this.allPhysicalFields.push(physical);
139
+ }
140
+ /**
141
+ * Applies adapter-provided metadata overrides atomically.
142
+ * Processing order: injectFields → removePrimaryKeys → addPrimaryKeys → addUniqueFields.
143
+ */
144
+ _applyOverrides(overrides) {
145
+ if (overrides.injectFields) for (const { path, type } of overrides.injectFields) this.flatMap.set(path, type);
146
+ if (overrides.removePrimaryKeys) for (const field of overrides.removePrimaryKeys) {
147
+ const idx = this.primaryKeys.indexOf(field);
148
+ if (idx >= 0) this.primaryKeys.splice(idx, 1);
149
+ }
150
+ if (overrides.addPrimaryKeys) {
151
+ for (const field of overrides.addPrimaryKeys) if (!this.primaryKeys.includes(field)) this.primaryKeys.push(field);
152
+ }
153
+ if (overrides.addUniqueFields) for (const field of overrides.addUniqueFields) this.uniqueProps.add(field);
154
+ }
155
+ /**
156
+ * Scans `@db.*` and `@meta.id` annotations on a field during flattening.
157
+ */
158
+ _scanGenericAnnotations(fieldName, fieldType, metadata, logger) {
159
+ if (metadata.has("meta.id")) {
160
+ this.primaryKeys.push(fieldName);
161
+ this.originalMetaIdFields.push(fieldName);
162
+ }
163
+ const column = metadata.get("db.column");
164
+ if (column) this.columnMap.set(fieldName, column);
165
+ const columnFrom = metadata.get("db.column.renamed");
166
+ if (columnFrom) this._columnFromMap.set(fieldName, columnFrom);
167
+ const resolvedDefault = resolveDefaultFromMetadata(metadata);
168
+ if (resolvedDefault) this.defaults.set(fieldName, resolvedDefault);
169
+ if (metadata.has("db.ignore")) this.ignoredFields.add(fieldName);
170
+ if (isNavRelation(metadata)) {
171
+ this.navFields.add(fieldName);
172
+ this.ignoredFields.add(fieldName);
173
+ const direction = metadata.has("db.rel.to") ? "to" : metadata.has("db.rel.from") ? "from" : "via";
174
+ const raw = direction === "via" ? metadata.get("db.rel.via") : metadata.get(`db.rel.${direction}`);
175
+ const alias = raw === true || typeof raw === "function" ? void 0 : raw;
176
+ const isArr = fieldType.type.kind === "array";
177
+ const elementType = isArr ? fieldType.type.of : fieldType;
178
+ const resolveTarget = () => elementType?.ref?.type() ?? elementType;
179
+ this.relations.set(fieldName, {
180
+ direction,
181
+ alias,
182
+ targetType: resolveTarget,
183
+ isArray: isArr,
184
+ ...direction === "via" ? { viaType: raw } : {}
185
+ });
186
+ }
187
+ if (metadata.has("db.rel.FK")) {
188
+ const raw = metadata.get("db.rel.FK");
189
+ const alias = raw === true ? void 0 : raw;
190
+ if (fieldType.ref) {
191
+ const refTarget = fieldType.ref.type();
192
+ const targetTable = refTarget?.metadata?.get("db.table") || refTarget?.id || "";
193
+ const targetField = fieldType.ref.field;
194
+ const key = alias || `__auto_${fieldName}`;
195
+ const existing = this.foreignKeys.get(key);
196
+ if (existing) {
197
+ existing.fields.push(fieldName);
198
+ existing.targetFields.push(targetField);
199
+ } else this.foreignKeys.set(key, {
200
+ fields: [fieldName],
201
+ targetTable,
202
+ targetFields: [targetField],
203
+ targetTypeRef: fieldType.ref.type,
204
+ alias
205
+ });
206
+ }
207
+ }
208
+ const onDelete = metadata.get("db.rel.onDelete");
209
+ const onUpdate = metadata.get("db.rel.onUpdate");
210
+ if (onDelete || onUpdate) {
211
+ for (const fk of this.foreignKeys.values()) if (fk.fields.includes(fieldName)) {
212
+ if (onDelete) fk.onDelete = onDelete;
213
+ if (onUpdate) fk.onUpdate = onUpdate;
214
+ break;
215
+ }
216
+ }
217
+ for (const index of metadata.get("db.index.plain") || []) {
218
+ const name = index === true ? fieldName : index?.name || fieldName;
219
+ const sort = (index === true ? void 0 : index?.sort) || "asc";
220
+ this._addIndexField("plain", name, fieldName, { sort });
221
+ }
222
+ for (const index of metadata.get("db.index.unique") || []) {
223
+ const name = index === true ? fieldName : typeof index === "string" ? index : index?.name || fieldName;
224
+ this._addIndexField("unique", name, fieldName);
225
+ }
226
+ for (const index of metadata.get("db.index.fulltext") || []) {
227
+ const name = index === true ? fieldName : typeof index === "string" ? index : index?.name || fieldName;
228
+ const weight = index !== true && typeof index === "object" ? index?.weight : void 0;
229
+ this._addIndexField("fulltext", name, fieldName, { weight });
230
+ }
231
+ const collate = metadata.get("db.column.collate");
232
+ if (collate) this._collateMap.set(fieldName, collate);
233
+ const hasExplicitIndex = metadata.has("db.index.plain") || metadata.has("db.index.unique") || metadata.has("db.index.fulltext");
234
+ if (metadata.has("db.json")) {
235
+ this.jsonFields.add(fieldName);
236
+ if (hasExplicitIndex) logger.warn(`@db.index on a @db.json field "${fieldName}" — most databases cannot index into JSON columns`);
237
+ }
238
+ if (metadata.has("db.column.dimension")) {
239
+ this.dimensions.push(fieldName);
240
+ if (!hasExplicitIndex) this._addIndexField("plain", fieldName, fieldName);
241
+ }
242
+ if (metadata.has("db.column.measure")) this.measures.push(fieldName);
243
+ }
244
+ _addIndexField(type, name, field, opts) {
245
+ const key = indexKey(type, name);
246
+ const index = this.indexes.get(key);
247
+ const indexField = {
248
+ name: field,
249
+ sort: opts?.sort ?? "asc"
250
+ };
251
+ if (opts?.weight !== void 0) indexField.weight = opts.weight;
252
+ if (index) index.fields.push(indexField);
253
+ else this.indexes.set(key, {
254
+ key,
255
+ name,
256
+ type,
257
+ fields: [indexField]
258
+ });
259
+ }
260
+ /**
261
+ * Classifies each field as column, flattened, json, or parent-object.
262
+ * Builds the bidirectional pathToPhysical / physicalToPath maps.
263
+ */
264
+ _classifyFields() {
265
+ for (const [path, type] of this.flatMap.entries()) {
266
+ if (!path) continue;
267
+ const designType = resolveDesignType(type);
268
+ const isJson = this.jsonFields.has(path);
269
+ const isArray = designType === "array";
270
+ const isObject = designType === "object";
271
+ if (isArray) this.jsonFields.add(path);
272
+ else if (isObject && isJson) {} else if (isObject && !isJson) this.flattenedParents.add(path);
273
+ }
274
+ for (const ignoredField of this.ignoredFields) if (this.flattenedParents.has(ignoredField)) {
275
+ const prefix = `${ignoredField}.`;
276
+ for (const path of this.flatMap.keys()) if (path.startsWith(prefix)) this.ignoredFields.add(path);
277
+ }
278
+ for (const parentPath of this.flattenedParents) if (this.columnMap.has(parentPath)) throw new Error(`@db.column cannot rename a flattened object field "${parentPath}" — apply @db.column to individual nested fields, or use @db.json to store as a single column`);
279
+ for (const [path] of this.flatMap.entries()) {
280
+ if (!path) continue;
281
+ if (this.flattenedParents.has(path)) continue;
282
+ if (findAncestorInSet(path, this.jsonFields) !== void 0) continue;
283
+ const isFlattened = findAncestorInSet(path, this.flattenedParents) !== void 0;
284
+ const columnOverride = this.columnMap.get(path);
285
+ let physicalName;
286
+ if (columnOverride) physicalName = isFlattened ? this._flattenedPrefix(path) + columnOverride : columnOverride;
287
+ else physicalName = isFlattened ? path.replace(/\./g, "__") : path;
288
+ this.pathToPhysical.set(path, physicalName);
289
+ this.physicalToPath.set(physicalName, path);
290
+ const fieldType = this.flatMap.get(path);
291
+ if (fieldType) {
292
+ const dt = resolveDesignType(fieldType);
293
+ if (dt === "boolean") this.booleanFields.add(physicalName);
294
+ else if (dt === "decimal") this.decimalFields.add(physicalName);
295
+ }
296
+ }
297
+ for (const parentPath of this.flattenedParents) {
298
+ const prefix = `${parentPath}.`;
299
+ const leaves = [];
300
+ for (const [path, physical] of this.pathToPhysical) if (path.startsWith(prefix)) leaves.push(physical);
301
+ if (leaves.length > 0) this.selectExpansion.set(parentPath, leaves);
302
+ }
303
+ this.requiresMappings = this.flattenedParents.size > 0 || this.jsonFields.size > 0;
304
+ }
305
+ /** Returns the `__`-separated parent prefix for a dot-separated path, or empty string for top-level paths. */
306
+ _flattenedPrefix(path) {
307
+ const lastDot = path.lastIndexOf(".");
308
+ return lastDot >= 0 ? `${path.slice(0, lastDot).replace(/\./g, "__")}__` : "";
309
+ }
310
+ /**
311
+ * Indexes `fieldDescriptors` into two lookup maps for unified
312
+ * read/write field classification in the RelationalFieldMapper.
313
+ */
314
+ _buildLeafIndexes() {
315
+ for (const fd of this.fieldDescriptors) {
316
+ if (fd.ignored) continue;
317
+ this.leafByPhysical.set(fd.physicalName, fd);
318
+ this.leafByLogical.set(fd.path, fd);
319
+ }
320
+ }
321
+ /**
322
+ * Builds field descriptors, physical-name lookup, and value formatters.
323
+ * Called once during build() — everything it needs
324
+ * (flatMap, indexes, columnMap, etc.) is already populated.
325
+ */
326
+ _buildFieldDescriptors(adapter) {
327
+ const descriptors = [];
328
+ const skipFlattening = this.nestedObjects;
329
+ const indexedFields = /* @__PURE__ */ new Set();
330
+ for (const index of this.indexes.values()) for (const f of index.fields) indexedFields.add(f.name);
331
+ for (const [path, type] of this.flatMap.entries()) {
332
+ if (!path) continue;
333
+ if (!skipFlattening && this.flattenedParents.has(path)) continue;
334
+ if (!skipFlattening && findAncestorInSet(path, this.jsonFields) !== void 0) continue;
335
+ const isJson = this.jsonFields.has(path);
336
+ const isFlattened = !skipFlattening && findAncestorInSet(path, this.flattenedParents) !== void 0;
337
+ const designType = isJson ? "json" : resolveDesignType(type);
338
+ let storage;
339
+ if (skipFlattening) storage = "column";
340
+ else if (isJson) storage = "json";
341
+ else if (isFlattened) storage = "flattened";
342
+ else storage = "column";
343
+ const physicalName = skipFlattening ? this.columnMap.get(path) ?? path : this.pathToPhysical.get(path) ?? this.columnMap.get(path) ?? path;
344
+ const fromLocal = this._columnFromMap.get(path);
345
+ let renamedFrom;
346
+ if (fromLocal) renamedFrom = isFlattened ? this._flattenedPrefix(path) + fromLocal : fromLocal;
347
+ descriptors.push({
348
+ path,
349
+ type,
350
+ physicalName,
351
+ designType,
352
+ optional: type.optional === true,
353
+ isPrimaryKey: this.primaryKeys.includes(path),
354
+ ignored: this.ignoredFields.has(path),
355
+ defaultValue: this.defaults.get(path),
356
+ storage,
357
+ flattenedFrom: isFlattened ? path : void 0,
358
+ renamedFrom,
359
+ collate: this._collateMap.get(path),
360
+ isIndexed: indexedFields.has(path) || void 0
361
+ });
362
+ }
363
+ this._resolveFkTargetFields(descriptors);
364
+ const fmtHook = adapter.formatValue?.bind(adapter);
365
+ if (fmtHook) for (const fd of descriptors) {
366
+ const fmt = fmtHook(fd);
367
+ if (fmt) if (typeof fmt === "function") {
368
+ if (!this.toStorageFormatters) this.toStorageFormatters = /* @__PURE__ */ new Map();
369
+ this.toStorageFormatters.set(fd.physicalName, fmt);
370
+ } else {
371
+ if (fmt.toStorage) {
372
+ if (!this.toStorageFormatters) this.toStorageFormatters = /* @__PURE__ */ new Map();
373
+ this.toStorageFormatters.set(fd.physicalName, fmt.toStorage);
374
+ }
375
+ if (fmt.fromStorage) {
376
+ if (!this.fromStorageFormatters) this.fromStorageFormatters = /* @__PURE__ */ new Map();
377
+ this.fromStorageFormatters.set(fd.physicalName, fmt.fromStorage);
378
+ }
379
+ }
380
+ }
381
+ Object.freeze(descriptors);
382
+ this.fieldDescriptors = descriptors;
383
+ }
384
+ /**
385
+ * Resolves `fkTargetField` for FK fields in field descriptors.
386
+ */
387
+ _resolveFkTargetFields(descriptors) {
388
+ if (this.foreignKeys.size === 0) return;
389
+ const fkFieldToTarget = /* @__PURE__ */ new Map();
390
+ for (const fk of this.foreignKeys.values()) {
391
+ if (!fk.targetTypeRef) continue;
392
+ for (let i = 0; i < fk.fields.length; i++) fkFieldToTarget.set(fk.fields[i], {
393
+ targetTypeRef: fk.targetTypeRef,
394
+ targetField: fk.targetFields[i]
395
+ });
396
+ }
397
+ if (fkFieldToTarget.size === 0) return;
398
+ const flatCache = /* @__PURE__ */ new Map();
399
+ for (const descriptor of descriptors) {
400
+ const target = fkFieldToTarget.get(descriptor.path);
401
+ if (!target) continue;
402
+ const targetType = target.targetTypeRef();
403
+ if (!targetType) continue;
404
+ let targetFlatMap = flatCache.get(targetType);
405
+ if (!targetFlatMap) {
406
+ targetFlatMap = (0, _atscript_typescript_utils.flattenAnnotatedType)(targetType);
407
+ flatCache.set(targetType, targetFlatMap);
408
+ }
409
+ const targetFieldType = targetFlatMap.get(target.targetField);
410
+ if (!targetFieldType) continue;
411
+ const targetMetadata = targetFieldType.metadata;
412
+ descriptor.fkTargetField = {
413
+ path: target.targetField,
414
+ type: targetFieldType,
415
+ physicalName: target.targetField,
416
+ designType: resolveDesignType(targetFieldType),
417
+ optional: false,
418
+ isPrimaryKey: targetMetadata?.has("meta.id") ?? false,
419
+ ignored: false,
420
+ storage: "column",
421
+ defaultValue: targetMetadata ? resolveDefaultFromMetadata(targetMetadata) : void 0,
422
+ collate: targetMetadata?.get("db.column.collate")
423
+ };
424
+ }
425
+ }
426
+ _finalizeIndexes() {
427
+ for (const index of this.indexes.values()) if (index.type === "unique" && index.fields.length === 1) this.uniqueProps.add(index.fields[0].name);
428
+ for (const index of this.indexes.values()) for (const field of index.fields) field.name = this.pathToPhysical.get(field.name) ?? this.columnMap.get(field.name) ?? field.name;
429
+ }
430
+ };
431
+ //#endregion
432
+ //#region src/query/uniqu-select.ts
433
+ /**
434
+ * Wraps a raw `$select` value and provides lazy-cached conversions
435
+ * to the forms different adapters need.
436
+ *
437
+ * Only instantiated when `$select` is actually provided —
438
+ * `controls.$select` is `UniquSelect | undefined`.
439
+ *
440
+ * For exclusion → inclusion inversion, pass `allFields` (physical field names).
441
+ */
442
+ var UniquSelect = class UniquSelect {
443
+ static UNRESOLVED = Symbol("unresolved");
444
+ _raw;
445
+ _allFields;
446
+ _array = UniquSelect.UNRESOLVED;
447
+ _projection = UniquSelect.UNRESOLVED;
448
+ _aggregates = UniquSelect.UNRESOLVED;
449
+ constructor(raw, allFields) {
450
+ this._raw = raw;
451
+ this._allFields = allFields;
452
+ }
453
+ /** Type guard: checks if a value is an AggregateExpr ({$fn, $field}). */
454
+ static _isAggregateExpr(v) {
455
+ return typeof v === "object" && v !== null && "$fn" in v && "$field" in v;
456
+ }
457
+ /**
458
+ * Resolved inclusion array of plain field names (strings only).
459
+ * AggregateExpr objects are filtered out.
460
+ * For exclusion form, inverts using `allFields` from constructor.
461
+ */
462
+ get asArray() {
463
+ if (this._array !== UniquSelect.UNRESOLVED) return this._array;
464
+ if (Array.isArray(this._raw)) {
465
+ this._array = this._raw.filter((item) => typeof item === "string");
466
+ return this._array;
467
+ }
468
+ const raw = this._raw;
469
+ const entries = Object.entries(raw);
470
+ if (entries.length === 0) {
471
+ this._array = void 0;
472
+ return;
473
+ }
474
+ if (entries[0][1] === 1) this._array = entries.filter((e) => e[1] === 1).map((e) => e[0]);
475
+ else if (this._allFields) {
476
+ const excluded = new Set(entries.filter((e) => e[1] === 0).map((e) => e[0]));
477
+ this._array = this._allFields.filter((f) => !excluded.has(f));
478
+ } else this._array = void 0;
479
+ return this._array;
480
+ }
481
+ /**
482
+ * Record projection preserving original semantics.
483
+ * Returns original object as-is if raw was object.
484
+ * Converts `string[]` to `{field: 1}` inclusion object.
485
+ * AggregateExpr objects in array form are ignored.
486
+ */
487
+ get asProjection() {
488
+ if (this._projection !== UniquSelect.UNRESOLVED) return this._projection;
489
+ if (!Array.isArray(this._raw)) {
490
+ const raw = this._raw;
491
+ this._projection = Object.keys(raw).length === 0 ? void 0 : raw;
492
+ return this._projection;
493
+ }
494
+ const strings = this.asArray;
495
+ if (!strings || strings.length === 0) {
496
+ this._projection = void 0;
497
+ return;
498
+ }
499
+ const result = {};
500
+ for (const item of strings) result[item] = 1;
501
+ this._projection = result;
502
+ return this._projection;
503
+ }
504
+ /**
505
+ * Extracts AggregateExpr entries from array-form $select.
506
+ * Returns undefined if no aggregates present or if $select is object form.
507
+ */
508
+ get aggregates() {
509
+ if (this._aggregates !== UniquSelect.UNRESOLVED) return this._aggregates;
510
+ if (!Array.isArray(this._raw)) {
511
+ this._aggregates = void 0;
512
+ return;
513
+ }
514
+ const aggs = this._raw.filter((v) => UniquSelect._isAggregateExpr(v));
515
+ this._aggregates = aggs.length > 0 ? aggs : void 0;
516
+ return this._aggregates;
517
+ }
518
+ /** Whether the $select contains any AggregateExpr entries. */
519
+ get hasAggregates() {
520
+ return !!this.aggregates?.length;
521
+ }
522
+ };
523
+ //#endregion
524
+ //#region src/strategies/field-mapping.ts
525
+ /** Coerces a storage value (0/1/null) back to a JS boolean. */
526
+ function toBool(value) {
527
+ if (value === null || value === void 0) return value;
528
+ return !!value;
529
+ }
530
+ function toDecimalString(value) {
531
+ if (value === null || value === void 0) return value;
532
+ if (typeof value === "string") return value;
533
+ if (typeof value === "number") return String(value);
534
+ return value;
535
+ }
536
+ /**
537
+ * Strategy for mapping data between logical field shapes and physical storage.
538
+ * Two implementations: {@link DocumentFieldMapper} (nested objects, NoSQL)
539
+ * and `RelationalFieldMapper` (flattened columns, SQL).
540
+ */
541
+ var FieldMappingStrategy = class {
542
+ /**
543
+ * Recursively walks a filter expression, applying adapter-specific value
544
+ * formatting via `formatFilterValue`. Shared by both document and relational
545
+ * mappers (relational adds key-renaming via `translateFilterWithRename`).
546
+ */
547
+ translateFilter(filter, meta) {
548
+ if (!filter || typeof filter !== "object") return filter;
549
+ if (!meta.toStorageFormatters) return filter;
550
+ const result = {};
551
+ for (const [key, value] of Object.entries(filter)) if (key === "$and" || key === "$or") result[key] = value.map((f) => this.translateFilter(f, meta));
552
+ else if (key === "$not") result[key] = this.translateFilter(value, meta);
553
+ else if (key.startsWith("$")) result[key] = value;
554
+ else result[key] = this.formatFilterValue(key, value, meta);
555
+ return result;
556
+ }
557
+ /**
558
+ * Coerces field values from storage representation to JS types
559
+ * (booleans from 0/1, decimals from number to string).
560
+ */
561
+ coerceFieldValues(row, meta) {
562
+ if (meta.booleanFields.size === 0 && meta.decimalFields.size === 0) return row;
563
+ for (const field of meta.booleanFields) if (field in row) row[field] = toBool(row[field]);
564
+ for (const field of meta.decimalFields) if (field in row) row[field] = toDecimalString(row[field]);
565
+ return row;
566
+ }
567
+ /**
568
+ * Applies adapter-specific fromStorage formatting to a row read from the database.
569
+ * Converts storage representations back to JS values (e.g. Date → epoch ms).
570
+ */
571
+ applyFromStorageFormatters(row, meta) {
572
+ if (!meta.fromStorageFormatters) return row;
573
+ for (const [col, fmt] of meta.fromStorageFormatters) {
574
+ const val = row[col];
575
+ if (val !== null && val !== void 0) row[col] = fmt(val);
576
+ }
577
+ return row;
578
+ }
579
+ /**
580
+ * Sets a value at a dot-notation path, creating intermediate objects as needed.
581
+ */
582
+ setNestedValue(obj, dotPath, value) {
583
+ const parts = dotPath.split(".");
584
+ let current = obj;
585
+ for (let i = 0; i < parts.length - 1; i++) {
586
+ const part = parts[i];
587
+ if (current[part] === void 0 || current[part] === null) current[part] = {};
588
+ current = current[part];
589
+ }
590
+ current[parts[parts.length - 1]] = value;
591
+ }
592
+ /**
593
+ * If all children of a flattened parent are null, collapse the parent to null.
594
+ */
595
+ reconstructNullParent(obj, parentPath, meta) {
596
+ const parts = parentPath.split(".");
597
+ let current = obj;
598
+ for (let i = 0; i < parts.length - 1; i++) {
599
+ if (current[parts[i]] === void 0) return;
600
+ current = current[parts[i]];
601
+ }
602
+ const lastPart = parts[parts.length - 1];
603
+ const parentObj = current[lastPart];
604
+ if (typeof parentObj !== "object" || parentObj === null) return;
605
+ let allNull = true;
606
+ const parentKeys = Object.keys(parentObj);
607
+ for (const k of parentKeys) {
608
+ const v = parentObj[k];
609
+ if (v !== null && v !== void 0) {
610
+ allNull = false;
611
+ break;
612
+ }
613
+ }
614
+ if (allNull) {
615
+ const parentType = meta.flatMap?.get(parentPath);
616
+ current[lastPart] = parentType?.optional ? null : {};
617
+ }
618
+ }
619
+ /**
620
+ * Applies adapter-specific value formatting to a single filter value.
621
+ * Handles direct values, operator objects ({$gt: v}), and $in/$nin arrays.
622
+ */
623
+ formatFilterValue(physicalName, value, meta) {
624
+ const fmt = meta.toStorageFormatters?.get(physicalName);
625
+ if (!fmt) return value;
626
+ if (value === null || value === void 0) return value;
627
+ if (typeof value !== "object") return fmt(value);
628
+ const ops = value;
629
+ const formatted = {};
630
+ for (const [op, opVal] of Object.entries(ops)) if ((op === "$in" || op === "$nin") && Array.isArray(opVal)) formatted[op] = opVal.map((v) => v === null || v === void 0 ? v : fmt(v));
631
+ else if (op.startsWith("$") && opVal !== null && opVal !== void 0) formatted[op] = fmt(opVal);
632
+ else formatted[op] = opVal;
633
+ return formatted;
634
+ }
635
+ /**
636
+ * Applies adapter-specific value formatting to prepared (physical-named) data.
637
+ */
638
+ formatWriteValues(data, meta) {
639
+ if (!meta.toStorageFormatters) return data;
640
+ for (const [col, fmt] of meta.toStorageFormatters) {
641
+ const val = data[col];
642
+ if (val !== null && val !== void 0) data[col] = fmt(val);
643
+ }
644
+ return data;
645
+ }
646
+ /**
647
+ * Prepares primary key values and strips ignored fields.
648
+ * Shared pre-processing for both document and relational write paths.
649
+ */
650
+ prepareCommon(data, meta, adapter) {
651
+ for (const pk of meta.primaryKeys) if (data[pk] !== void 0) {
652
+ const fieldType = meta.flatMap?.get(pk);
653
+ if (fieldType) data[pk] = adapter.prepareId(data[pk], fieldType);
654
+ }
655
+ for (const field of meta.ignoredFields) if (!field.includes(".")) delete data[field];
656
+ }
657
+ };
658
+ /**
659
+ * Field mapper for document-oriented adapters (e.g. MongoDB).
660
+ * Nested objects are preserved as-is. Only applies column renames and
661
+ * value coercion.
662
+ */
663
+ var DocumentFieldMapper = class extends FieldMappingStrategy {
664
+ reconstructFromRead(row, meta) {
665
+ return this.applyFromStorageFormatters(this.coerceFieldValues(row, meta), meta);
666
+ }
667
+ translateQuery(query, meta) {
668
+ const controls = query.controls;
669
+ return {
670
+ filter: meta.toStorageFormatters ? this.translateFilter(query.filter, meta) : query.filter,
671
+ controls: {
672
+ ...controls,
673
+ $with: void 0,
674
+ $select: controls?.$select ? new UniquSelect(controls.$select, meta.allPhysicalFields) : void 0
675
+ },
676
+ insights: query.insights
677
+ };
678
+ }
679
+ translateAggregateQuery(query, meta) {
680
+ const controls = query.controls;
681
+ return {
682
+ filter: meta.toStorageFormatters ? this.translateFilter(query.filter, meta) : query.filter ?? {},
683
+ controls: {
684
+ ...controls,
685
+ $with: void 0,
686
+ $select: controls.$select ? new UniquSelect(controls.$select, meta.allPhysicalFields) : void 0
687
+ },
688
+ insights: query.insights
689
+ };
690
+ }
691
+ prepareForWrite(payload, meta, adapter) {
692
+ const data = { ...payload };
693
+ this.prepareCommon(data, meta, adapter);
694
+ for (const [logical, physical] of meta.columnMap.entries()) if (logical in data) {
695
+ data[physical] = data[logical];
696
+ delete data[logical];
697
+ }
698
+ return this.formatWriteValues(data, meta);
699
+ }
700
+ translatePatchKeys(update, meta) {
701
+ return this.formatWriteValues(update, meta);
702
+ }
703
+ };
704
+ //#endregion
705
+ //#region src/strategies/relational-field-mapper.ts
706
+ /**
707
+ * Field mapper for relational adapters (e.g. SQLite, MySQL).
708
+ * Flattens nested objects to `__`-separated column names and
709
+ * reconstructs them on read. Applies full physical-name translation
710
+ * for queries, filters, and controls.
711
+ */
712
+ var RelationalFieldMapper = class extends FieldMappingStrategy {
713
+ reconstructFromRead(row, meta) {
714
+ if (!meta.requiresMappings) return this.applyFromStorageFormatters(this.coerceFieldValues(row, meta), meta);
715
+ const result = {};
716
+ const fromFmts = meta.fromStorageFormatters;
717
+ for (const physical of Object.keys(row)) {
718
+ const fd = meta.leafByPhysical.get(physical);
719
+ if (!fd) {
720
+ result[physical] = row[physical];
721
+ continue;
722
+ }
723
+ let raw = row[physical];
724
+ const fromFmt = fromFmts?.get(physical);
725
+ if (fromFmt && raw !== null && raw !== void 0) raw = fromFmt(raw);
726
+ const value = fd.designType === "boolean" ? toBool(raw) : fd.designType === "decimal" ? toDecimalString(raw) : raw;
727
+ if (fd.storage === "json") this.setNestedValue(result, fd.path, typeof value === "string" ? JSON.parse(value) : value);
728
+ else if (fd.storage === "flattened") this.setNestedValue(result, fd.path, value);
729
+ else result[fd.path] = value;
730
+ }
731
+ for (const parentPath of meta.flattenedParents) this.reconstructNullParent(result, parentPath, meta);
732
+ return result;
733
+ }
734
+ translateQuery(query, meta) {
735
+ if (!meta.requiresMappings) {
736
+ const controls = query.controls;
737
+ return {
738
+ filter: meta.toStorageFormatters ? this.translateFilter(query.filter, meta) : query.filter,
739
+ controls: {
740
+ ...controls,
741
+ $with: void 0,
742
+ $select: controls?.$select ? new UniquSelect(controls.$select, meta.allPhysicalFields) : void 0
743
+ },
744
+ insights: query.insights
745
+ };
746
+ }
747
+ return {
748
+ filter: this.translateFilterWithRename(query.filter, meta),
749
+ controls: query.controls ? this.translateControls(query.controls, meta) : {},
750
+ insights: query.insights
751
+ };
752
+ }
753
+ translateAggregateQuery(query, meta) {
754
+ const controls = query.controls;
755
+ const filter = meta.requiresMappings ? this.translateFilterWithRename(query.filter ?? {}, meta) : meta.toStorageFormatters ? this.translateFilter(query.filter ?? {}, meta) : query.filter ?? {};
756
+ const groupBy = controls.$groupBy.map((field) => meta.leafByLogical.get(field)?.physicalName ?? field);
757
+ let select;
758
+ if (controls.$select) select = controls.$select.map((item) => {
759
+ if (typeof item === "string") return meta.leafByLogical.get(item)?.physicalName ?? item;
760
+ if (item.$field === "*") return item;
761
+ return {
762
+ ...item,
763
+ $field: meta.leafByLogical.get(item.$field)?.physicalName ?? item.$field
764
+ };
765
+ });
766
+ const aliases = /* @__PURE__ */ new Set();
767
+ if (controls.$select) {
768
+ for (const item of controls.$select) if (typeof item !== "string") aliases.add(require_agg.resolveAlias(item));
769
+ }
770
+ let sort;
771
+ if (controls.$sort) {
772
+ const translated = {};
773
+ for (const [key, dir] of Object.entries(controls.$sort)) if (aliases.has(key)) translated[key] = dir;
774
+ else {
775
+ const physical = meta.leafByLogical.get(key)?.physicalName ?? key;
776
+ translated[physical] = dir;
777
+ }
778
+ sort = translated;
779
+ }
780
+ let having;
781
+ if (controls.$having) having = meta.requiresMappings ? this.translateFilterWithRename(controls.$having, meta) : meta.toStorageFormatters ? this.translateFilter(controls.$having, meta) : controls.$having;
782
+ return {
783
+ filter,
784
+ controls: {
785
+ $groupBy: groupBy,
786
+ $select: select ? new UniquSelect(select, meta.allPhysicalFields) : void 0,
787
+ $sort: sort,
788
+ $having: having,
789
+ $skip: controls.$skip,
790
+ $limit: controls.$limit,
791
+ $count: controls.$count
792
+ },
793
+ insights: query.insights
794
+ };
795
+ }
796
+ /**
797
+ * Translates filter with key renaming from logical to physical names.
798
+ * Used by the relational query path where field paths must be mapped
799
+ * to `__`-separated column names.
800
+ */
801
+ translateFilterWithRename(filter, meta) {
802
+ if (!filter || typeof filter !== "object") return filter;
803
+ const result = {};
804
+ for (const [key, value] of Object.entries(filter)) if (key === "$and" || key === "$or") result[key] = value.map((f) => this.translateFilterWithRename(f, meta));
805
+ else if (key === "$not") result[key] = this.translateFilterWithRename(value, meta);
806
+ else if (key.startsWith("$")) result[key] = value;
807
+ else {
808
+ const physical = meta.leafByLogical.get(key)?.physicalName ?? key;
809
+ result[physical] = this.formatFilterValue(physical, value, meta);
810
+ }
811
+ return result;
812
+ }
813
+ prepareForWrite(payload, meta, adapter) {
814
+ const data = { ...payload };
815
+ this.prepareCommon(data, meta, adapter);
816
+ if (!meta.requiresMappings) {
817
+ for (const [logical, physical] of meta.columnMap.entries()) if (logical in data) {
818
+ data[physical] = data[logical];
819
+ delete data[logical];
820
+ }
821
+ return this.formatWriteValues(data, meta);
822
+ }
823
+ return this.formatWriteValues(this.flattenPayload(data, meta), meta);
824
+ }
825
+ translatePatchKeys(update, meta) {
826
+ if (!meta.requiresMappings && !meta.toStorageFormatters) return update;
827
+ const result = {};
828
+ const updateKeys = Object.keys(update);
829
+ for (const key of updateKeys) {
830
+ const value = update[key];
831
+ const operatorMatch = key.match(/^(.+?)(\.__\$.+)$/);
832
+ const basePath = operatorMatch ? operatorMatch[1] : key;
833
+ const suffix = operatorMatch ? operatorMatch[2] : "";
834
+ const fd = meta.leafByLogical.get(basePath);
835
+ const finalKey = (fd?.physicalName ?? basePath) + suffix;
836
+ if (fd?.storage === "json" && typeof value === "object" && value !== null && !suffix) result[finalKey] = JSON.stringify(value);
837
+ else result[finalKey] = value;
838
+ }
839
+ return this.formatWriteValues(result, meta);
840
+ }
841
+ /**
842
+ * Translates field names in sort and projection controls from
843
+ * logical dot-paths to physical column names.
844
+ */
845
+ translateControls(controls, meta) {
846
+ if (!controls) return {};
847
+ const result = {
848
+ ...controls,
849
+ $select: void 0,
850
+ $with: void 0
851
+ };
852
+ if (controls.$sort) {
853
+ const translated = {};
854
+ const sortObj = controls.$sort;
855
+ const sortKeys = Object.keys(sortObj);
856
+ for (const key of sortKeys) {
857
+ if (meta.flattenedParents.has(key)) continue;
858
+ const physical = meta.leafByLogical.get(key)?.physicalName ?? key;
859
+ translated[physical] = sortObj[key];
860
+ }
861
+ result.$sort = translated;
862
+ }
863
+ if (controls.$select) {
864
+ let translatedRaw;
865
+ if (Array.isArray(controls.$select)) {
866
+ const expanded = [];
867
+ for (const key of controls.$select) {
868
+ const expansion = meta.selectExpansion.get(key);
869
+ if (expansion) expanded.push(...expansion);
870
+ else expanded.push(meta.leafByLogical.get(key)?.physicalName ?? key);
871
+ }
872
+ translatedRaw = expanded;
873
+ } else {
874
+ const translated = {};
875
+ const selectObj = controls.$select;
876
+ const selectKeys = Object.keys(selectObj);
877
+ for (const key of selectKeys) {
878
+ const val = selectObj[key];
879
+ const expansion = meta.selectExpansion.get(key);
880
+ if (expansion) for (const leaf of expansion) translated[leaf] = val;
881
+ else {
882
+ const physical = meta.leafByLogical.get(key)?.physicalName ?? key;
883
+ translated[physical] = val;
884
+ }
885
+ }
886
+ translatedRaw = translated;
887
+ }
888
+ result.$select = new UniquSelect(translatedRaw, meta.allPhysicalFields);
889
+ }
890
+ return result;
891
+ }
892
+ /**
893
+ * Flattens nested object fields into __-separated keys and
894
+ * JSON-stringifies @db.json / array fields.
895
+ */
896
+ flattenPayload(data, meta) {
897
+ const result = {};
898
+ for (const key of Object.keys(data)) this.writeFlattenedField(key, data[key], result, meta);
899
+ return result;
900
+ }
901
+ /**
902
+ * Classifies and writes a single field to the result object.
903
+ * Recurses into nested objects that should be flattened.
904
+ */
905
+ writeFlattenedField(path, value, result, meta) {
906
+ if (meta.ignoredFields.has(path)) return;
907
+ if (meta.flattenedParents.has(path)) {
908
+ if (value === null || value === void 0) this.setFlattenedChildrenNull(path, result, meta);
909
+ else if (typeof value === "object" && !Array.isArray(value)) {
910
+ const obj = value;
911
+ for (const key of Object.keys(obj)) this.writeFlattenedField(`${path}.${key}`, obj[key], result, meta);
912
+ }
913
+ } else {
914
+ const fd = meta.leafByLogical.get(path);
915
+ const physical = fd?.physicalName ?? path.replace(/\./g, "__");
916
+ if (fd?.storage === "json") result[physical] = value !== void 0 && value !== null ? JSON.stringify(value) : value;
917
+ else result[physical] = value;
918
+ }
919
+ }
920
+ /**
921
+ * When a parent object is null/undefined, set all its flattened children to null.
922
+ */
923
+ setFlattenedChildrenNull(parentPath, result, meta) {
924
+ const prefix = `${parentPath}.`;
925
+ for (const [path, fd] of meta.leafByLogical.entries()) if (path.startsWith(prefix)) result[fd.physicalName] = null;
926
+ }
927
+ };
928
+ //#endregion
929
+ //#region src/table/db-readable.ts
930
+ /**
931
+ * Resolves the design type from an annotated type.
932
+ * Encapsulates the `kind === ''` check and fallback logic that
933
+ * otherwise trips up every adapter author.
934
+ *
935
+ * For union types (e.g., from flattened `{...} | {...}` objects):
936
+ * - If all members resolve to the same type → returns that type (strong type)
937
+ * - If members disagree → returns `'union'` (out of scope for type management)
938
+ */
939
+ function resolveDesignType(fieldType) {
940
+ if (fieldType.type.kind === "") return fieldType.type.designType ?? "string";
941
+ if (fieldType.type.kind === "object") return "object";
942
+ if (fieldType.type.kind === "array") return "array";
943
+ if (fieldType.type.kind === "union") {
944
+ const items = fieldType.type.items;
945
+ if (items.length > 0) {
946
+ const resolved = items.map((item) => resolveDesignType(item));
947
+ if (resolved.every((type) => type === resolved[0])) return resolved[0];
948
+ }
949
+ return "union";
950
+ }
951
+ return "string";
952
+ }
953
+ /**
954
+ * Resolves `@db.default.*` annotations from a metadata map into a {@link TDbDefaultValue}.
955
+ * Used both during normal field descriptor construction and for FK target field resolution.
956
+ */
957
+ function resolveDefaultFromMetadata(metadata) {
958
+ const defaultValue = metadata.get("db.default");
959
+ if (defaultValue !== void 0) return {
960
+ kind: "value",
961
+ value: defaultValue
962
+ };
963
+ if (metadata.has("db.default.increment")) {
964
+ const startValue = metadata.get("db.default.increment");
965
+ return {
966
+ kind: "fn",
967
+ fn: "increment",
968
+ start: typeof startValue === "number" ? startValue : void 0
969
+ };
970
+ }
971
+ if (metadata.has("db.default.uuid")) return {
972
+ kind: "fn",
973
+ fn: "uuid"
974
+ };
975
+ if (metadata.has("db.default.now")) return {
976
+ kind: "fn",
977
+ fn: "now"
978
+ };
979
+ }
980
+ /**
981
+ * Checks whether an id value is type-compatible with a field's design type.
982
+ * Used by `findById` to skip primary-key lookup when the id clearly can't match,
983
+ * falling through to unique-property search instead.
984
+ */
985
+ function isIdCompatible(id, fieldType) {
986
+ switch (resolveDesignType(fieldType)) {
987
+ case "number":
988
+ if (typeof id === "number") return true;
989
+ if (typeof id === "string") return id !== "" && !Number.isNaN(Number(id));
990
+ return false;
991
+ case "boolean": return typeof id === "boolean";
992
+ case "object":
993
+ case "array": return typeof id === "object" && id !== null;
994
+ default: return typeof id === "string";
995
+ }
996
+ }
997
+ /**
998
+ * Shared read-only database abstraction driven by Atscript annotations.
999
+ *
1000
+ * Contains all field metadata computation, read operations, query translation,
1001
+ * relation loading, and result reconstruction. Extended by both
1002
+ * {@link AtscriptDbTable} (adds write operations) and {@link AtscriptDbView}
1003
+ * (adds view plan/DDL).
1004
+ */
1005
+ var AtscriptDbReadable = class {
1006
+ /** Resolved table/collection/view name. */
1007
+ tableName;
1008
+ /** Database schema/namespace from `@db.schema` (if set). */
1009
+ schema;
1010
+ /** Sync method from `@db.sync.method` ('drop' | 'recreate' | undefined). */
1011
+ _syncMethod;
1012
+ /** Previous table/view name from `@db.table.renamed` or `@db.view.renamed`. */
1013
+ renamedFrom;
1014
+ /** Computed metadata for this table/view. Built lazily on first access. */
1015
+ _meta;
1016
+ /** Strategy for mapping between logical field shapes and physical storage. */
1017
+ _fieldMapper;
1018
+ _writeTableResolver;
1019
+ constructor(_type, adapter, logger = NoopLogger, _tableResolver) {
1020
+ this._type = _type;
1021
+ this.adapter = adapter;
1022
+ this.logger = logger;
1023
+ this._tableResolver = _tableResolver;
1024
+ if (!(0, _atscript_typescript_utils.isAnnotatedType)(_type)) throw new Error("Atscript Annotated Type expected");
1025
+ if (_type.type.kind !== "object") throw new Error("Database type must be an object type");
1026
+ const adapterName = adapter.getAdapterTableName?.(_type);
1027
+ const dbTable = _type.metadata.get("db.table");
1028
+ const dbViewName = _type.metadata.get("db.view");
1029
+ const fallbackName = _type.id || "";
1030
+ this.tableName = adapterName || dbTable || dbViewName || fallbackName;
1031
+ if (!this.tableName) throw new Error("@db.table or @db.view annotation expected");
1032
+ this.schema = _type.metadata.get("db.schema");
1033
+ this._syncMethod = _type.metadata.get("db.sync.method");
1034
+ this.renamedFrom = _type.metadata.get("db.table.renamed") ?? _type.metadata.get("db.view.renamed");
1035
+ this._meta = new TableMetadata(adapter.supportsNestedObjects());
1036
+ this._fieldMapper = adapter.supportsNestedObjects() ? new DocumentFieldMapper() : new RelationalFieldMapper();
1037
+ adapter.registerReadable(this, logger);
1038
+ }
1039
+ /** Ensures metadata is built. Called before any metadata access. */
1040
+ _ensureBuilt() {
1041
+ if (!this._meta.isBuilt) this._meta.build(this.type, this.adapter, this.logger);
1042
+ }
1043
+ /** Whether this readable is a view (overridden in AtscriptDbView). */
1044
+ get isView() {
1045
+ return false;
1046
+ }
1047
+ /** Returns the underlying adapter with its concrete type preserved. */
1048
+ getAdapter() {
1049
+ return this.adapter;
1050
+ }
1051
+ /** The raw annotated type. */
1052
+ get type() {
1053
+ return this._type;
1054
+ }
1055
+ /** Lazily-built flat map of all fields (dot-notation paths → annotated types). */
1056
+ get flatMap() {
1057
+ this._ensureBuilt();
1058
+ return this._meta.flatMap;
1059
+ }
1060
+ /** All computed indexes from `@db.index.*` annotations. */
1061
+ get indexes() {
1062
+ this._ensureBuilt();
1063
+ return this._meta.indexes;
1064
+ }
1065
+ /** Primary key field names from `@meta.id`. */
1066
+ get primaryKeys() {
1067
+ this._ensureBuilt();
1068
+ return this._meta.primaryKeys;
1069
+ }
1070
+ /** Original `@meta.id` field names as declared in the schema (before adapter manipulation). */
1071
+ get originalMetaIdFields() {
1072
+ this._ensureBuilt();
1073
+ return this._meta.originalMetaIdFields;
1074
+ }
1075
+ /** Dimension fields from `@db.column.dimension`. */
1076
+ get dimensions() {
1077
+ this._ensureBuilt();
1078
+ return this._meta.dimensions;
1079
+ }
1080
+ /** Measure fields from `@db.column.measure`. */
1081
+ get measures() {
1082
+ this._ensureBuilt();
1083
+ return this._meta.measures;
1084
+ }
1085
+ /** Sync method for structural changes: 'drop' (lossy), 'recreate' (lossless), or undefined (manual). */
1086
+ get syncMethod() {
1087
+ return this._syncMethod;
1088
+ }
1089
+ /** Logical → physical column name mapping from `@db.column`. */
1090
+ get columnMap() {
1091
+ this._ensureBuilt();
1092
+ return this._meta.columnMap;
1093
+ }
1094
+ /** Default values from `@db.default.*`. */
1095
+ get defaults() {
1096
+ this._ensureBuilt();
1097
+ return this._meta.defaults;
1098
+ }
1099
+ /** Fields excluded from DB via `@db.ignore`. */
1100
+ get ignoredFields() {
1101
+ this._ensureBuilt();
1102
+ return this._meta.ignoredFields;
1103
+ }
1104
+ /** Navigational fields (`@db.rel.to` / `@db.rel.from`) — not stored as columns. */
1105
+ get navFields() {
1106
+ this._ensureBuilt();
1107
+ return this._meta.navFields;
1108
+ }
1109
+ /** Single-field unique index properties. */
1110
+ get uniqueProps() {
1111
+ this._ensureBuilt();
1112
+ return this._meta.uniqueProps;
1113
+ }
1114
+ /** Foreign key constraints from `@db.rel.FK` annotations. */
1115
+ get foreignKeys() {
1116
+ this._ensureBuilt();
1117
+ return this._meta.foreignKeys;
1118
+ }
1119
+ /** Navigational relation metadata from `@db.rel.to` / `@db.rel.from`. */
1120
+ get relations() {
1121
+ this._ensureBuilt();
1122
+ return this._meta.relations;
1123
+ }
1124
+ /** The underlying database adapter instance. */
1125
+ get dbAdapter() {
1126
+ return this.adapter;
1127
+ }
1128
+ /**
1129
+ * Enables or disables verbose (debug-level) DB call logging for this table/view.
1130
+ * When disabled (default), no log strings are constructed — zero overhead.
1131
+ */
1132
+ setVerbose(enabled) {
1133
+ this.adapter.setVerbose(enabled);
1134
+ }
1135
+ /** Precomputed logical dot-path → physical column name map. */
1136
+ get pathToPhysical() {
1137
+ this._ensureBuilt();
1138
+ return this._meta.pathToPhysical;
1139
+ }
1140
+ /** Precomputed physical column name → logical dot-path map (inverse). */
1141
+ get physicalToPath() {
1142
+ this._ensureBuilt();
1143
+ return this._meta.physicalToPath;
1144
+ }
1145
+ /** Descriptor for the primary ID field(s). */
1146
+ getIdDescriptor() {
1147
+ this._ensureBuilt();
1148
+ return {
1149
+ fields: [...this._meta.primaryKeys],
1150
+ isComposite: this._meta.primaryKeys.length > 1
1151
+ };
1152
+ }
1153
+ /**
1154
+ * Pre-computed field metadata for adapter use.
1155
+ */
1156
+ get fieldDescriptors() {
1157
+ this._ensureBuilt();
1158
+ return this._meta.fieldDescriptors;
1159
+ }
1160
+ /**
1161
+ * Creates a new validator with custom options.
1162
+ */
1163
+ createValidator(opts) {
1164
+ return this._type.validator(opts);
1165
+ }
1166
+ /**
1167
+ * Finds a single record matching the query.
1168
+ * The return type automatically excludes nav props unless they are
1169
+ * explicitly requested via `$with`.
1170
+ */
1171
+ async findOne(query) {
1172
+ this._ensureBuilt();
1173
+ const withRelations = query.controls?.$with;
1174
+ const translatedQuery = this._fieldMapper.translateQuery(query, this._meta);
1175
+ const result = await this.adapter.findOne(translatedQuery);
1176
+ if (!result) return null;
1177
+ const row = this._fieldMapper.reconstructFromRead(result, this._meta);
1178
+ if (withRelations?.length) await this.loadRelations([row], withRelations);
1179
+ return row;
1180
+ }
1181
+ /**
1182
+ * Finds all records matching the query.
1183
+ * The return type automatically excludes nav props unless they are
1184
+ * explicitly requested via `$with`.
1185
+ */
1186
+ async findMany(query) {
1187
+ this._ensureBuilt();
1188
+ const withRelations = query.controls?.$with;
1189
+ const translatedQuery = this._fieldMapper.translateQuery(query, this._meta);
1190
+ const rows = (await this.adapter.findMany(translatedQuery)).map((row) => this._fieldMapper.reconstructFromRead(row, this._meta));
1191
+ if (withRelations?.length) await this.loadRelations(rows, withRelations);
1192
+ return rows;
1193
+ }
1194
+ /**
1195
+ * Counts records matching the query.
1196
+ */
1197
+ async count(query) {
1198
+ this._ensureBuilt();
1199
+ query ??= {
1200
+ filter: {},
1201
+ controls: {}
1202
+ };
1203
+ return this.adapter.count(this._fieldMapper.translateQuery(query, this._meta));
1204
+ }
1205
+ /**
1206
+ * Finds records and total count in a single logical call.
1207
+ */
1208
+ async findManyWithCount(query) {
1209
+ this._ensureBuilt();
1210
+ const withRelations = query.controls?.$with;
1211
+ const translated = this._fieldMapper.translateQuery(query, this._meta);
1212
+ const result = await this.adapter.findManyWithCount(translated);
1213
+ const rows = result.data.map((row) => this._fieldMapper.reconstructFromRead(row, this._meta));
1214
+ if (withRelations?.length) await this.loadRelations(rows, withRelations);
1215
+ return {
1216
+ data: rows,
1217
+ count: result.count
1218
+ };
1219
+ }
1220
+ /**
1221
+ * Executes an aggregate query with GROUP BY and aggregate functions.
1222
+ *
1223
+ * Validates:
1224
+ * - Plain fields in $select are a subset of $groupBy
1225
+ * - When dimensions/measures are defined (strict mode): $groupBy fields
1226
+ * must be dimensions, aggregate $field values must be measures (or '*')
1227
+ *
1228
+ * Translates field names, delegates to adapter.aggregate(),
1229
+ * then reverse-maps and applies fromStorage formatters on results.
1230
+ */
1231
+ async aggregate(query) {
1232
+ this._ensureBuilt();
1233
+ const { $groupBy, $select } = query.controls;
1234
+ if ($select) {
1235
+ const groupBySet = new Set($groupBy);
1236
+ for (const item of $select) if (typeof item === "string" && !groupBySet.has(item)) throw new require_nested_writer.DbError("INVALID_QUERY", [{
1237
+ path: "$select",
1238
+ message: `Plain field "${item}" in $select must also appear in $groupBy`
1239
+ }]);
1240
+ }
1241
+ const { dimensions, measures } = this._meta;
1242
+ if (dimensions.length > 0 || measures.length > 0) {
1243
+ const dimSet = new Set(dimensions);
1244
+ const measSet = new Set(measures);
1245
+ for (const field of $groupBy) if (!dimSet.has(field)) throw new require_nested_writer.DbError("INVALID_QUERY", [{
1246
+ path: "$groupBy",
1247
+ message: `Field "${field}" is not a dimension`
1248
+ }]);
1249
+ if ($select) {
1250
+ for (const item of $select) if (typeof item !== "string" && item.$field !== "*" && !measSet.has(item.$field)) throw new require_nested_writer.DbError("INVALID_QUERY", [{
1251
+ path: "$select",
1252
+ message: `Aggregate field "${item.$field}" is not a measure`
1253
+ }]);
1254
+ }
1255
+ }
1256
+ const dbQuery = this._fieldMapper.translateAggregateQuery(query, this._meta);
1257
+ return (await this.adapter.aggregate(dbQuery)).map((row) => {
1258
+ const mapped = {};
1259
+ for (const [key, value] of Object.entries(row)) {
1260
+ const logical = this._meta.physicalToPath.get(key) ?? key;
1261
+ const fmt = this._meta.fromStorageFormatters?.get(key);
1262
+ mapped[logical] = fmt && value !== null && value !== void 0 ? fmt(value) : value;
1263
+ }
1264
+ return mapped;
1265
+ });
1266
+ }
1267
+ /** Whether the underlying adapter supports text search. */
1268
+ isSearchable() {
1269
+ return this.adapter.isSearchable();
1270
+ }
1271
+ /** Returns available search indexes from the adapter. */
1272
+ getSearchIndexes() {
1273
+ return this.adapter.getSearchIndexes();
1274
+ }
1275
+ /**
1276
+ * Full-text search with query translation and result reconstruction.
1277
+ */
1278
+ async search(text, query, indexName) {
1279
+ this._ensureBuilt();
1280
+ const withRelations = query.controls?.$with;
1281
+ const translated = this._fieldMapper.translateQuery(query, this._meta);
1282
+ const rows = (await this.adapter.search(text, translated, indexName)).map((row) => this._fieldMapper.reconstructFromRead(row, this._meta));
1283
+ if (withRelations?.length) await this.loadRelations(rows, withRelations);
1284
+ return rows;
1285
+ }
1286
+ /**
1287
+ * Full-text search with count for paginated search results.
1288
+ */
1289
+ async searchWithCount(text, query, indexName) {
1290
+ this._ensureBuilt();
1291
+ const withRelations = query.controls?.$with;
1292
+ const translated = this._fieldMapper.translateQuery(query, this._meta);
1293
+ const result = await this.adapter.searchWithCount(text, translated, indexName);
1294
+ const rows = result.data.map((row) => this._fieldMapper.reconstructFromRead(row, this._meta));
1295
+ if (withRelations?.length) await this.loadRelations(rows, withRelations);
1296
+ return {
1297
+ data: rows,
1298
+ count: result.count
1299
+ };
1300
+ }
1301
+ /** Whether the underlying adapter supports vector similarity search. */
1302
+ isVectorSearchable() {
1303
+ return this.adapter.isVectorSearchable();
1304
+ }
1305
+ /**
1306
+ * Vector similarity search with query translation and result reconstruction.
1307
+ *
1308
+ * Overloads:
1309
+ * - `vectorSearch(vector, query?)` — uses default vector index
1310
+ * - `vectorSearch(indexName, vector, query?)` — targets a specific vector index
1311
+ */
1312
+ async vectorSearch(vectorOrIndex, maybeVectorOrQuery, maybeQuery) {
1313
+ const { vector, query, indexName } = this._resolveVectorSearchArgs(vectorOrIndex, maybeVectorOrQuery, maybeQuery);
1314
+ this._ensureBuilt();
1315
+ const withRelations = (query?.controls)?.$with;
1316
+ const translated = this._fieldMapper.translateQuery(query || {}, this._meta);
1317
+ const rows = (await this.adapter.vectorSearch(vector, translated, indexName)).map((row) => this._fieldMapper.reconstructFromRead(row, this._meta));
1318
+ if (withRelations?.length) await this.loadRelations(rows, withRelations);
1319
+ return rows;
1320
+ }
1321
+ /**
1322
+ * Vector similarity search with count for paginated results.
1323
+ *
1324
+ * Overloads:
1325
+ * - `vectorSearchWithCount(vector, query?)` — uses default vector index
1326
+ * - `vectorSearchWithCount(indexName, vector, query?)` — targets a specific vector index
1327
+ */
1328
+ async vectorSearchWithCount(vectorOrIndex, maybeVectorOrQuery, maybeQuery) {
1329
+ const { vector, query, indexName } = this._resolveVectorSearchArgs(vectorOrIndex, maybeVectorOrQuery, maybeQuery);
1330
+ this._ensureBuilt();
1331
+ const withRelations = (query?.controls)?.$with;
1332
+ const translated = this._fieldMapper.translateQuery(query || {}, this._meta);
1333
+ const result = await this.adapter.vectorSearchWithCount(vector, translated, indexName);
1334
+ const rows = result.data.map((row) => this._fieldMapper.reconstructFromRead(row, this._meta));
1335
+ if (withRelations?.length) await this.loadRelations(rows, withRelations);
1336
+ return {
1337
+ data: rows,
1338
+ count: result.count
1339
+ };
1340
+ }
1341
+ /** Resolves overloaded vector search arguments into canonical form. */
1342
+ _resolveVectorSearchArgs(vectorOrIndex, maybeVectorOrQuery, maybeQuery) {
1343
+ if (Array.isArray(vectorOrIndex)) return {
1344
+ vector: vectorOrIndex,
1345
+ query: maybeVectorOrQuery,
1346
+ indexName: void 0
1347
+ };
1348
+ return {
1349
+ vector: maybeVectorOrQuery,
1350
+ query: maybeQuery,
1351
+ indexName: vectorOrIndex
1352
+ };
1353
+ }
1354
+ /**
1355
+ * Finds a single record by any type-compatible identifier — primary key
1356
+ * or single-field unique index.
1357
+ * The return type excludes nav props unless `$with` is provided in controls.
1358
+ *
1359
+ * ```typescript
1360
+ * // Without relations — nav props stripped from result
1361
+ * const user = await table.findById('123')
1362
+ *
1363
+ * // With relations — only requested nav props appear
1364
+ * const user = await table.findById('123', { controls: { $with: [{ name: 'posts' }] } })
1365
+ * ```
1366
+ */
1367
+ async findById(id, query) {
1368
+ this._ensureBuilt();
1369
+ const filter = this._resolveIdFilter(id);
1370
+ if (!filter) return null;
1371
+ return await this.findOne({
1372
+ filter,
1373
+ controls: query?.controls || {}
1374
+ });
1375
+ }
1376
+ /**
1377
+ * Resolves an id value into a filter expression.
1378
+ */
1379
+ _resolveIdFilter(id) {
1380
+ const orFilters = [];
1381
+ const pkFields = this.primaryKeys;
1382
+ if (pkFields.length === 1) {
1383
+ const filter = this._tryFieldFilter(pkFields[0], id);
1384
+ if (filter) orFilters.push(filter);
1385
+ } else if (pkFields.length > 1 && typeof id === "object" && id !== null) {
1386
+ const idObj = id;
1387
+ const compositeFilter = {};
1388
+ let valid = true;
1389
+ for (const field of pkFields) {
1390
+ const fieldType = this.flatMap.get(field);
1391
+ if (fieldType && !isIdCompatible(idObj[field], fieldType)) {
1392
+ valid = false;
1393
+ break;
1394
+ }
1395
+ try {
1396
+ compositeFilter[field] = fieldType ? this.adapter.prepareId(idObj[field], fieldType) : idObj[field];
1397
+ } catch {
1398
+ valid = false;
1399
+ break;
1400
+ }
1401
+ }
1402
+ if (valid) orFilters.push(compositeFilter);
1403
+ }
1404
+ for (const prop of this.uniqueProps) {
1405
+ const filter = this._tryFieldFilter(prop, id);
1406
+ if (filter) orFilters.push(filter);
1407
+ }
1408
+ if (typeof id === "object" && id !== null && orFilters.length === 0) {
1409
+ const idObj = id;
1410
+ for (const index of this._meta.indexes.values()) {
1411
+ if (index.type !== "unique" || index.fields.length < 2) continue;
1412
+ const compoundFilter = {};
1413
+ let valid = true;
1414
+ for (const indexField of index.fields) {
1415
+ const fieldName = indexField.name;
1416
+ if (idObj[fieldName] === void 0) {
1417
+ valid = false;
1418
+ break;
1419
+ }
1420
+ const fieldType = this.flatMap.get(fieldName);
1421
+ if (fieldType && !isIdCompatible(idObj[fieldName], fieldType)) {
1422
+ valid = false;
1423
+ break;
1424
+ }
1425
+ try {
1426
+ compoundFilter[fieldName] = fieldType ? this.adapter.prepareId(idObj[fieldName], fieldType) : idObj[fieldName];
1427
+ } catch {
1428
+ valid = false;
1429
+ break;
1430
+ }
1431
+ }
1432
+ if (valid) orFilters.push(compoundFilter);
1433
+ }
1434
+ }
1435
+ if (orFilters.length === 0) return null;
1436
+ if (orFilters.length === 1) return orFilters[0];
1437
+ return { $or: orFilters };
1438
+ }
1439
+ /**
1440
+ * Attempts to build a single-field filter `{ field: preparedId }`.
1441
+ */
1442
+ _tryFieldFilter(field, id) {
1443
+ const fieldType = this.flatMap.get(field);
1444
+ if (fieldType && !isIdCompatible(id, fieldType)) return null;
1445
+ try {
1446
+ const prepared = fieldType ? this.adapter.prepareId(id, fieldType) : id;
1447
+ return { [field]: prepared };
1448
+ } catch {
1449
+ return null;
1450
+ }
1451
+ }
1452
+ /**
1453
+ * Public entry point for relation loading. Used by adapters for nested $with delegation.
1454
+ */
1455
+ async loadRelations(rows, withRelations) {
1456
+ const { loadRelationsImpl } = await Promise.resolve().then(() => require("./relation-loader-CRC5LcqM.cjs")).then((n) => n.relation_loader_exports);
1457
+ return loadRelationsImpl(rows, withRelations, this);
1458
+ }
1459
+ /**
1460
+ * Finds the FK entry that connects a `@db.rel.to` relation to its target.
1461
+ * Thin wrapper — delegates to relation-loader for shared use with db-table.ts write path.
1462
+ */
1463
+ _findFKForRelation(relation) {
1464
+ return require_relation_helpers.findFKForRelation(relation, this._meta.foreignKeys);
1465
+ }
1466
+ /**
1467
+ * Finds a FK on a remote table that points back to this table.
1468
+ * Thin wrapper — delegates to relation-loader for shared use with db-table.ts write path.
1469
+ */
1470
+ _findRemoteFK(targetTable, thisTableName, alias) {
1471
+ return require_relation_helpers.findRemoteFK(targetTable, thisTableName, alias);
1472
+ }
1473
+ };
1474
+ //#endregion
1475
+ //#region src/strategies/integrity.ts
1476
+ /**
1477
+ * Strategy for referential integrity enforcement.
1478
+ * Two implementations: {@link NativeIntegrity} (DB handles FK constraints)
1479
+ * and `ApplicationIntegrity` (generic layer validates + cascades).
1480
+ */
1481
+ var IntegrityStrategy = class {};
1482
+ /**
1483
+ * Integrity strategy for adapters with native FK support (e.g. SQLite, MySQL).
1484
+ * All operations are no-ops — the database engine enforces constraints.
1485
+ */
1486
+ var NativeIntegrity = class extends IntegrityStrategy {
1487
+ async validateForeignKeys() {}
1488
+ async cascadeBeforeDelete() {}
1489
+ needsCascade() {
1490
+ return false;
1491
+ }
1492
+ };
1493
+ //#endregion
1494
+ //#region src/base-adapter.ts
1495
+ const EMPTY_DEFAULT_FNS = /* @__PURE__ */ new Set();
1496
+ const txStorage = new node_async_hooks.AsyncLocalStorage();
1497
+ /**
1498
+ * Abstract base class for database adapters.
1499
+ *
1500
+ * Adapter instances are 1:1 with readable instances (tables or views).
1501
+ * When an {@link AtscriptDbReadable} is created with an adapter, it calls
1502
+ * {@link registerReadable} to establish a bidirectional relationship:
1503
+ *
1504
+ * ```
1505
+ * AtscriptDbReadable ──delegates ops──▶ BaseDbAdapter
1506
+ * ◀──reads metadata── (via this._table)
1507
+ * ```
1508
+ *
1509
+ * Adapter authors can access all computed metadata through `this._table`:
1510
+ * - `this._table.tableName` — resolved table/collection/view name
1511
+ * - `this._table.flatMap` — all fields as dot-notation paths
1512
+ * - `this._table.indexes` — computed index definitions
1513
+ * - `this._table.primaryKeys` — primary key field names
1514
+ * - `this._table.columnMap` — logical → physical column mappings
1515
+ * - `this._table.defaults` — default value configurations
1516
+ * - `this._table.ignoredFields` — fields excluded from DB
1517
+ * - `this._table.uniqueProps` — single-field unique index properties
1518
+ * - `this._table.isView` — whether this is a view (vs a table)
1519
+ */
1520
+ var BaseDbAdapter = class {
1521
+ _table;
1522
+ _metaIdPhysical;
1523
+ /**
1524
+ * Returns the physical column name of the single @meta.id field (if any).
1525
+ * Used to return the user's logical ID instead of the DB-generated ID on insert.
1526
+ */
1527
+ _getMetaIdPhysical() {
1528
+ if (this._metaIdPhysical === void 0) {
1529
+ const fields = this._table.originalMetaIdFields;
1530
+ if (fields.length === 1) {
1531
+ const field = fields[0];
1532
+ this._metaIdPhysical = this._table.columnMap.get(field) ?? field;
1533
+ } else this._metaIdPhysical = null;
1534
+ }
1535
+ return this._metaIdPhysical;
1536
+ }
1537
+ /**
1538
+ * Resolves the correct insertedId: prefers the user-supplied PK value
1539
+ * from the data over the DB-generated fallback (e.g. rowid, _id).
1540
+ */
1541
+ _resolveInsertedId(data, dbGeneratedId) {
1542
+ const metaIdPhysical = this._getMetaIdPhysical();
1543
+ return metaIdPhysical ? data[metaIdPhysical] ?? dbGeneratedId : dbGeneratedId;
1544
+ }
1545
+ /** Logger instance — set via {@link registerReadable} from the readable's logger. */
1546
+ logger = NoopLogger;
1547
+ /** When true, adapter logs DB calls via `logger.debug`. Off by default. */
1548
+ _verbose = false;
1549
+ /**
1550
+ * Called by {@link AtscriptDbReadable} constructor. Gives the adapter access
1551
+ * to the readable's computed metadata for internal use in query rendering,
1552
+ * index sync, etc.
1553
+ */
1554
+ registerReadable(readable, logger) {
1555
+ this._table = readable;
1556
+ if (logger) this.logger = logger;
1557
+ }
1558
+ /**
1559
+ * Enables or disables verbose (debug-level) logging for this adapter.
1560
+ * When disabled, no log strings are constructed — zero overhead.
1561
+ */
1562
+ setVerbose(enabled) {
1563
+ this._verbose = enabled;
1564
+ }
1565
+ /**
1566
+ * Logs a debug message if verbose mode is enabled.
1567
+ * Adapters call this to log DB operations with zero overhead when disabled.
1568
+ */
1569
+ _log(...args) {
1570
+ if (!this._verbose) return;
1571
+ this.logger.debug(...args);
1572
+ }
1573
+ /**
1574
+ * Runs `fn` inside a database transaction. Nested calls (from related tables
1575
+ * within the same async chain) reuse the existing transaction automatically.
1576
+ *
1577
+ * The generic layer handles nesting detection via `AsyncLocalStorage`.
1578
+ * Adapters override `_beginTransaction`, `_commitTransaction`, and
1579
+ * `_rollbackTransaction` to provide raw DB-specific transaction primitives.
1580
+ */
1581
+ async withTransaction(fn) {
1582
+ if (txStorage.getStore()) return fn();
1583
+ const ctx = { state: void 0 };
1584
+ ctx.state = await this._beginTransaction();
1585
+ return txStorage.run(ctx, async () => {
1586
+ try {
1587
+ const result = await fn();
1588
+ await this._commitTransaction(ctx.state);
1589
+ return result;
1590
+ } catch (error) {
1591
+ try {
1592
+ await this._rollbackTransaction(ctx.state);
1593
+ } catch {}
1594
+ throw error;
1595
+ }
1596
+ });
1597
+ }
1598
+ /**
1599
+ * Returns the opaque transaction state from the current async context.
1600
+ * Adapters use this to retrieve DB-specific state (e.g., MongoDB `ClientSession`).
1601
+ */
1602
+ _getTransactionState() {
1603
+ return txStorage.getStore()?.state;
1604
+ }
1605
+ /**
1606
+ * Runs `fn` inside the transaction ALS context with the given state.
1607
+ * Adapters that override `withTransaction` (e.g., to use MongoDB's
1608
+ * `session.withTransaction()` Convenient API) use this to set up the
1609
+ * shared context so that nested adapters see the same session.
1610
+ * If a context already exists (nesting), it's reused.
1611
+ */
1612
+ _runInTransactionContext(state, fn) {
1613
+ if (txStorage.getStore()) return fn();
1614
+ return txStorage.run({ state }, fn);
1615
+ }
1616
+ /**
1617
+ * Starts a raw transaction. Returns opaque state stored in the async context.
1618
+ * Override in adapters that support transactions.
1619
+ */
1620
+ async _beginTransaction() {}
1621
+ /** Commits the raw transaction. Override in adapters that support transactions. */
1622
+ async _commitTransaction(_state) {}
1623
+ /** Rolls back the raw transaction. Override in adapters that support transactions. */
1624
+ async _rollbackTransaction(_state) {}
1625
+ /**
1626
+ * Returns additional validator plugins for this adapter.
1627
+ * These are merged with the built-in Atscript validators.
1628
+ *
1629
+ * Example: MongoDB adapter returns ObjectId validation plugin.
1630
+ */
1631
+ getValidatorPlugins() {
1632
+ return [];
1633
+ }
1634
+ /**
1635
+ * Transforms an ID value for the database.
1636
+ * Override to convert string → ObjectId, parse numeric IDs, etc.
1637
+ *
1638
+ * @param id - The raw ID value.
1639
+ * @param fieldType - The annotated type of the ID field.
1640
+ * @returns The transformed ID value.
1641
+ */
1642
+ prepareId(id, _fieldType) {
1643
+ return id;
1644
+ }
1645
+ /**
1646
+ * Whether this adapter supports native patch operations.
1647
+ * When `true`, {@link AtscriptDbTable} delegates patch payloads to
1648
+ * {@link nativePatch} instead of using the generic decomposition.
1649
+ */
1650
+ supportsNativePatch() {
1651
+ return false;
1652
+ }
1653
+ /**
1654
+ * Whether this adapter handles nested objects natively.
1655
+ * When `true`, the generic layer skips flattening and
1656
+ * passes nested objects as-is to the adapter.
1657
+ * MongoDB returns `true`; relational adapters return `false` (default).
1658
+ */
1659
+ supportsNestedObjects() {
1660
+ return false;
1661
+ }
1662
+ /**
1663
+ * Whether the DB engine handles static `@db.default "value"` natively
1664
+ * via column-level DEFAULT clauses in CREATE TABLE.
1665
+ * When `true`, `_applyDefaults()` skips client-side value defaults,
1666
+ * letting the DB apply its own DEFAULT. SQL adapters return `true`;
1667
+ * document stores (MongoDB) return `false` and apply defaults client-side.
1668
+ */
1669
+ supportsNativeValueDefaults() {
1670
+ return false;
1671
+ }
1672
+ /**
1673
+ * Function default names handled natively by this adapter's DB engine.
1674
+ * Fields with these defaults are omitted from INSERT when no value is provided,
1675
+ * letting the DB apply its own DEFAULT expression (e.g. CURRENT_TIMESTAMP, UUID()).
1676
+ *
1677
+ * Override in adapters whose DB engine supports function defaults.
1678
+ * The generic layer checks this in `_applyDefaults()` to decide whether
1679
+ * to generate the value client-side or leave it for the DB.
1680
+ */
1681
+ nativeDefaultFns() {
1682
+ return EMPTY_DEFAULT_FNS;
1683
+ }
1684
+ /**
1685
+ * Whether this adapter enforces foreign key constraints natively.
1686
+ * When `true`, the generic layer skips application-level cascade/setNull
1687
+ * on delete — the DB engine handles it (e.g. SQLite `ON DELETE CASCADE`).
1688
+ * When `false` (default), the generic layer implements cascade logic
1689
+ * by finding child records and deleting/nullifying them before the parent.
1690
+ */
1691
+ supportsNativeForeignKeys() {
1692
+ return false;
1693
+ }
1694
+ /**
1695
+ * Whether this adapter handles `$with` relation loading natively.
1696
+ * When `true`, the table layer delegates to {@link loadRelations}
1697
+ * instead of using the generic batch-loading strategy.
1698
+ *
1699
+ * Adapters can use this to implement SQL JOINs, MongoDB `$lookup`,
1700
+ * or other DB-native relation loading optimizations.
1701
+ *
1702
+ * Default: `false` — the table layer uses application-level batch loading.
1703
+ */
1704
+ supportsNativeRelations() {
1705
+ return false;
1706
+ }
1707
+ /**
1708
+ * Loads relations onto result rows using adapter-native operations.
1709
+ * Only called when {@link supportsNativeRelations} returns `true`.
1710
+ *
1711
+ * The adapter receives the rows to enrich, the `$with` relation specs,
1712
+ * and the table's relation/FK metadata for resolution.
1713
+ *
1714
+ * @param rows - The result rows to enrich (mutable — add relation properties in place).
1715
+ * @param withRelations - The `$with` specs from the query.
1716
+ * @param relations - This table's relation metadata (from `@db.rel.to`/`@db.rel.from`).
1717
+ * @param foreignKeys - This table's FK metadata (from `@db.rel.FK`).
1718
+ * @param tableResolver - Optional callback to resolve annotated types to table metadata (needed for FROM/VIA relations).
1719
+ */
1720
+ async loadRelations(_rows, _withRelations, _relations, _foreignKeys, _tableResolver) {
1721
+ throw new Error("Native relation loading not supported by this adapter");
1722
+ }
1723
+ /**
1724
+ * Applies a patch payload using native database operations.
1725
+ * Only called when {@link supportsNativePatch} returns `true`.
1726
+ *
1727
+ * @param filter - Filter identifying the record to patch.
1728
+ * @param patch - The patch payload with array operations.
1729
+ * @returns Update result.
1730
+ */
1731
+ async nativePatch(_filter, _patch, _ops) {
1732
+ throw new Error("Native patch not supported by this adapter");
1733
+ }
1734
+ /**
1735
+ * Resolves the full table name, optionally including the schema prefix.
1736
+ * Override for databases that don't support schemas (e.g., SQLite).
1737
+ *
1738
+ * @param includeSchema - Whether to prepend `schema.` prefix (default: true).
1739
+ */
1740
+ resolveTableName(includeSchema = true) {
1741
+ const schema = this._table.schema;
1742
+ const name = this._table.tableName;
1743
+ return includeSchema && schema ? `${schema}.${name}` : name;
1744
+ }
1745
+ /**
1746
+ * Template method for index synchronization.
1747
+ * Implements the diff algorithm (list → compare → create/drop).
1748
+ * Adapters provide the three DB-specific primitives.
1749
+ *
1750
+ * @example
1751
+ * ```typescript
1752
+ * async syncIndexes() {
1753
+ * await this.syncIndexesWithDiff({
1754
+ * listExisting: async () => this.driver.all('PRAGMA index_list(...)'),
1755
+ * createIndex: async (index) => this.driver.exec('CREATE INDEX ...'),
1756
+ * dropIndex: async (name) => this.driver.exec('DROP INDEX ...'),
1757
+ * shouldSkipType: (type) => type === 'fulltext',
1758
+ * })
1759
+ * }
1760
+ * ```
1761
+ */
1762
+ async syncIndexesWithDiff(opts) {
1763
+ const prefix = opts.prefix ?? "atscript__";
1764
+ const existing = await opts.listExisting();
1765
+ const existingNames = new Set(existing.filter((i) => i.name.startsWith(prefix)).map((i) => i.name));
1766
+ const desiredNames = /* @__PURE__ */ new Set();
1767
+ for (const index of this._table.indexes.values()) {
1768
+ if (opts.shouldSkipType?.(index.type)) continue;
1769
+ desiredNames.add(index.key);
1770
+ if (!existingNames.has(index.key)) await opts.createIndex(index);
1771
+ }
1772
+ for (const name of existingNames) if (!desiredNames.has(name)) await opts.dropIndex(name);
1773
+ }
1774
+ /**
1775
+ * Returns available search indexes for this adapter.
1776
+ * UI uses this to show index picker. Override in adapters that support search.
1777
+ */
1778
+ getSearchIndexes() {
1779
+ return [];
1780
+ }
1781
+ /**
1782
+ * Whether this adapter supports text search.
1783
+ * Default: `true` when {@link getSearchIndexes} returns any entries.
1784
+ */
1785
+ isSearchable() {
1786
+ return this.getSearchIndexes().length > 0;
1787
+ }
1788
+ /**
1789
+ * Whether this adapter supports vector similarity search.
1790
+ * Override in adapters that support vector search.
1791
+ */
1792
+ isVectorSearchable() {
1793
+ return false;
1794
+ }
1795
+ /**
1796
+ * Full-text search. Override in adapters that support search.
1797
+ *
1798
+ * @param text - Search text.
1799
+ * @param query - Filter, sort, limit, etc.
1800
+ * @param indexName - Optional search index to target.
1801
+ */
1802
+ async search(_text, _query, _indexName) {
1803
+ throw new Error("Search not supported by this adapter");
1804
+ }
1805
+ /**
1806
+ * Full-text search with count (for paginated search results).
1807
+ *
1808
+ * @param text - Search text.
1809
+ * @param query - Filter, sort, limit, etc.
1810
+ * @param indexName - Optional search index to target.
1811
+ */
1812
+ async searchWithCount(_text, _query, _indexName) {
1813
+ throw new Error("Search not supported by this adapter");
1814
+ }
1815
+ /**
1816
+ * Vector similarity search. Override in adapters that support vector search.
1817
+ *
1818
+ * @param vector - Pre-computed embedding vector.
1819
+ * @param query - Filter, sort, limit, etc.
1820
+ * @param indexName - Optional vector index to target (for multi-vector documents).
1821
+ */
1822
+ async vectorSearch(_vector, _query, _indexName) {
1823
+ throw new Error("Vector search not supported by this adapter");
1824
+ }
1825
+ /**
1826
+ * Vector similarity search with count (for paginated results).
1827
+ *
1828
+ * @param vector - Pre-computed embedding vector.
1829
+ * @param query - Filter, sort, limit, etc.
1830
+ * @param indexName - Optional vector index to target (for multi-vector documents).
1831
+ */
1832
+ async vectorSearchWithCount(_vector, _query, _indexName) {
1833
+ throw new Error("Vector search not supported by this adapter");
1834
+ }
1835
+ /**
1836
+ * Fetches records and total count in one call.
1837
+ * Default: two parallel calls. Adapters may override for single-query optimization.
1838
+ */
1839
+ async findManyWithCount(query) {
1840
+ const [data, count] = await Promise.all([this.findMany(query), this.count(query)]);
1841
+ return {
1842
+ data,
1843
+ count
1844
+ };
1845
+ }
1846
+ /**
1847
+ * Executes an aggregate query (GROUP BY + aggregate functions).
1848
+ * Default throws — override in adapters that support aggregation.
1849
+ */
1850
+ async aggregate(_query) {
1851
+ throw new Error("Aggregation not supported by this adapter");
1852
+ }
1853
+ /**
1854
+ * When true, the adapter can handle column type changes in-place
1855
+ * (e.g. MySQL's ALTER TABLE MODIFY COLUMN) without requiring table recreation.
1856
+ * The generic sync layer will delegate type changes to {@link syncColumns}
1857
+ * instead of requiring `@db.sync.method "recreate"` or `"drop"`.
1858
+ */
1859
+ supportsColumnModify;
1860
+ };
1861
+ //#endregion
1862
+ //#region src/strategies/application-integrity.ts
1863
+ const MAX_CASCADE_DEPTH = 100;
1864
+ const cascadeStorage = new node_async_hooks.AsyncLocalStorage();
1865
+ /**
1866
+ * Integrity strategy for adapters without native FK support (e.g. MongoDB).
1867
+ * Validates FK constraints and executes cascade/setNull actions at
1868
+ * the application level.
1869
+ */
1870
+ var ApplicationIntegrity = class extends IntegrityStrategy {
1871
+ /**
1872
+ * Validates FK constraints by querying target tables for referenced records.
1873
+ * Collects unique FK values across items, batches them into target-table
1874
+ * lookups, and throws FK_VIOLATION if any references are missing.
1875
+ */
1876
+ async validateForeignKeys(items, meta, fkLookupResolver, writeTableResolver, partial, excludeTargetTable) {
1877
+ if (!fkLookupResolver) return;
1878
+ const checks = [];
1879
+ for (const [, fk] of meta.foreignKeys) {
1880
+ if (excludeTargetTable && fk.targetTable === excludeTargetTable) continue;
1881
+ const valueSets = fk.fields.map(() => /* @__PURE__ */ new Set());
1882
+ for (const item of items) {
1883
+ if (partial && !fk.fields.some((f) => f in item)) continue;
1884
+ let allPresent = true;
1885
+ const vals = [];
1886
+ for (const field of fk.fields) {
1887
+ const v = item[field];
1888
+ if (v === null || v === void 0) {
1889
+ allPresent = false;
1890
+ break;
1891
+ }
1892
+ vals.push(v);
1893
+ }
1894
+ if (!allPresent) continue;
1895
+ for (let i = 0; i < vals.length; i++) valueSets[i].add(vals[i]);
1896
+ }
1897
+ if (valueSets[0].size === 0) continue;
1898
+ let target = fkLookupResolver(fk.targetTable);
1899
+ if (!target && fk.targetTypeRef && writeTableResolver) {
1900
+ const resolved = writeTableResolver(fk.targetTypeRef());
1901
+ if (resolved) target = { count: (filter) => resolved.count({ filter }) };
1902
+ }
1903
+ if (!target) continue;
1904
+ const filter = {};
1905
+ let firstValues = [];
1906
+ for (let i = 0; i < fk.targetFields.length; i++) {
1907
+ const values = [...valueSets[i]];
1908
+ if (i === 0) firstValues = values;
1909
+ filter[fk.targetFields[i]] = values.length === 1 ? values[0] : { $in: values };
1910
+ }
1911
+ const expectedCount = firstValues.length;
1912
+ checks.push(async () => {
1913
+ if (await target.count(filter) < expectedCount) {
1914
+ const sample = firstValues.slice(0, 3).join(", ");
1915
+ const suffix = firstValues.length > 3 ? `, ... (${firstValues.length} total)` : "";
1916
+ throw new require_nested_writer.DbError("FK_VIOLATION", [{
1917
+ path: fk.fields.join(", "),
1918
+ message: `FK constraint violation: "${fk.fields.join(", ")}" references non-existent record in "${fk.targetTable}" (values: ${sample}${suffix})`
1919
+ }]);
1920
+ }
1921
+ });
1922
+ }
1923
+ if (checks.length > 0) await Promise.all(checks.map((fn) => fn()));
1924
+ }
1925
+ /**
1926
+ * Applies cascade/setNull actions on child tables before deleting parent records.
1927
+ * Finds all records matching `filter`, extracts their PK values, then for each
1928
+ * child table with a FK pointing to this table:
1929
+ * - `restrict`: throws if any children exist
1930
+ * - `cascade`: recursively deletes child records
1931
+ * - `setNull`: sets FK fields to null
1932
+ */
1933
+ async cascadeBeforeDelete(filter, tableName, meta, cascadeResolver, translateFilter, adapter) {
1934
+ const parentCtx = cascadeStorage.getStore();
1935
+ const visited = parentCtx?.visited ?? /* @__PURE__ */ new Set();
1936
+ const depth = (parentCtx?.depth ?? 0) + 1;
1937
+ if (depth > MAX_CASCADE_DEPTH) throw new require_nested_writer.DbError("CASCADE_CYCLE", [{
1938
+ path: tableName,
1939
+ message: `Cascade delete aborted: chain exceeded ${MAX_CASCADE_DEPTH} levels, likely caused by a circular or deeply nested cascade relationship`
1940
+ }]);
1941
+ const targets = cascadeResolver(tableName);
1942
+ if (targets.length === 0) return;
1943
+ const neededLogical = /* @__PURE__ */ new Set();
1944
+ for (const t of targets) for (const tf of t.fk.targetFields) neededLogical.add(tf);
1945
+ for (const pk of meta.primaryKeys) neededLogical.add(pk);
1946
+ const physicalToLogical = /* @__PURE__ */ new Map();
1947
+ const physicalFields = [];
1948
+ for (const logical of neededLogical) {
1949
+ const physical = meta.pathToPhysical.get(logical) ?? meta.columnMap.get(logical) ?? logical;
1950
+ physicalFields.push(physical);
1951
+ physicalToLogical.set(physical, logical);
1952
+ }
1953
+ const rawRecords = await adapter.findMany({
1954
+ filter: translateFilter(filter),
1955
+ controls: { $select: new UniquSelect(physicalFields) }
1956
+ });
1957
+ if (rawRecords.length === 0) return;
1958
+ const allRecords = rawRecords.map((r) => {
1959
+ const mapped = {};
1960
+ for (const [key, val] of Object.entries(r)) mapped[physicalToLogical.get(key) ?? key] = val;
1961
+ return mapped;
1962
+ });
1963
+ const pkFields = meta.primaryKeys;
1964
+ const addedKeys = [];
1965
+ const records = [];
1966
+ for (const record of allRecords) {
1967
+ const key = this.recordKey(tableName, pkFields, record);
1968
+ if (visited.has(key)) continue;
1969
+ visited.add(key);
1970
+ addedKeys.push(key);
1971
+ records.push(record);
1972
+ }
1973
+ if (records.length === 0) return;
1974
+ try {
1975
+ await cascadeStorage.run({
1976
+ visited,
1977
+ depth
1978
+ }, async () => {
1979
+ const restrictChecks = [];
1980
+ for (const target of targets) {
1981
+ if (target.fk.onDelete !== "restrict") continue;
1982
+ const childFilter = this.buildCascadeChildFilter(records, target.fk);
1983
+ if (!childFilter) continue;
1984
+ restrictChecks.push(target.count(childFilter).then((count) => {
1985
+ if (count > 0) throw new require_nested_writer.DbError("CONFLICT", [{
1986
+ path: tableName,
1987
+ message: `Cannot delete from "${tableName}": ${count} record(s) in "${target.childTable}" (${target.fk.fields.join(", ")}) reference it (RESTRICT)`
1988
+ }]);
1989
+ }));
1990
+ }
1991
+ if (restrictChecks.length > 0) await Promise.all(restrictChecks);
1992
+ for (const target of targets) {
1993
+ const action = target.fk.onDelete;
1994
+ if (!action || action === "noAction" || action === "restrict") continue;
1995
+ const childFilter = this.buildCascadeChildFilter(records, target.fk);
1996
+ if (!childFilter) continue;
1997
+ switch (action) {
1998
+ case "cascade":
1999
+ await target.deleteMany(childFilter);
2000
+ break;
2001
+ case "setNull": {
2002
+ const nullData = {};
2003
+ for (const f of target.fk.fields) nullData[f] = null;
2004
+ await target.updateMany(childFilter, nullData);
2005
+ break;
2006
+ }
2007
+ }
2008
+ }
2009
+ });
2010
+ } finally {
2011
+ for (const key of addedKeys) visited.delete(key);
2012
+ }
2013
+ }
2014
+ needsCascade(cascadeResolver) {
2015
+ return !!cascadeResolver;
2016
+ }
2017
+ recordKey(tableName, pkFields, record) {
2018
+ let key = tableName;
2019
+ for (const f of pkFields) {
2020
+ const v = record[f];
2021
+ key += `\0${v === null || v === void 0 ? "" : String(v)}`;
2022
+ }
2023
+ return key;
2024
+ }
2025
+ /**
2026
+ * Builds a filter for child records whose FK matches the deleted parent's PK values.
2027
+ */
2028
+ buildCascadeChildFilter(parentRecords, fk) {
2029
+ if (fk.fields.length === 1 && fk.targetFields.length === 1) {
2030
+ const pkField = fk.targetFields[0];
2031
+ const values = parentRecords.map((r) => r[pkField]).filter((v) => v !== void 0 && v !== null);
2032
+ if (values.length === 0) return;
2033
+ return values.length === 1 ? { [fk.fields[0]]: values[0] } : { [fk.fields[0]]: { $in: values } };
2034
+ }
2035
+ const orFilters = [];
2036
+ for (const record of parentRecords) {
2037
+ const condition = {};
2038
+ let valid = true;
2039
+ for (let i = 0; i < fk.fields.length; i++) {
2040
+ const val = record[fk.targetFields[i]];
2041
+ if (val === void 0 || val === null) {
2042
+ valid = false;
2043
+ break;
2044
+ }
2045
+ condition[fk.fields[i]] = val;
2046
+ }
2047
+ if (valid) orFilters.push(condition);
2048
+ }
2049
+ if (orFilters.length === 0) return;
2050
+ return orFilters.length === 1 ? orFilters[0] : { $or: orFilters };
2051
+ }
2052
+ };
2053
+ //#endregion
2054
+ //#region src/patch/patch-types.ts
2055
+ /**
2056
+ * Extracts `@expect.array.key` properties from an array-of-objects type.
2057
+ * These keys uniquely identify an element inside the array and are used
2058
+ * for `$update`, `$remove`, and `$upsert` operations.
2059
+ *
2060
+ * @param def - Atscript array type definition.
2061
+ * @returns Set of property names marked as keys; empty set if none.
2062
+ */
2063
+ function getKeyProps(def) {
2064
+ if (def.type.of.type.kind === "object") {
2065
+ const objType = def.type.of.type;
2066
+ const keyProps = /* @__PURE__ */ new Set();
2067
+ for (const [key, val] of objType.props.entries()) if (val.metadata.get("expect.array.key")) keyProps.add(key);
2068
+ return keyProps;
2069
+ }
2070
+ return /* @__PURE__ */ new Set();
2071
+ }
2072
+ //#endregion
2073
+ //#region src/db-validator-plugin.ts
2074
+ /** Set of recognised array‑patch operator keys. */
2075
+ const PATCH_OPS = new Set([
2076
+ "$replace",
2077
+ "$insert",
2078
+ "$upsert",
2079
+ "$update",
2080
+ "$remove"
2081
+ ]);
2082
+ /**
2083
+ * Returns `true` when `value` looks like a patch‑operator object
2084
+ * (at least one key is a recognised operator and no unknown keys).
2085
+ */
2086
+ function isPatchOperatorObject(value) {
2087
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
2088
+ const keys = Object.keys(value);
2089
+ if (keys.length === 0) return false;
2090
+ return keys.every((k) => PATCH_OPS.has(k));
2091
+ }
2092
+ /**
2093
+ * Validator plugin for database operations.
2094
+ *
2095
+ * Handles navigation field constraints and delegates to the standard validator
2096
+ * for type checking. The annotated type tree already includes nav fields with
2097
+ * their full target types — this plugin controls WHEN recursion is allowed
2098
+ * based on the operation mode (insert/replace/patch).
2099
+ *
2100
+ * Replaces the old `navFieldsValidatorPlugin` (which blindly skipped all nav
2101
+ * fields) and `_checkNavProps()` (which validated constraints separately).
2102
+ */
2103
+ function createDbValidatorPlugin() {
2104
+ return (ctx, def, value) => {
2105
+ const dbCtx = ctx.context;
2106
+ if (!dbCtx) return;
2107
+ const isTo = def.metadata.has("db.rel.to");
2108
+ const isFrom = def.metadata.has("db.rel.from");
2109
+ const isVia = def.metadata.has("db.rel.via");
2110
+ if (isTo || isFrom || isVia) return handleNavField(ctx, def, value, dbCtx, isTo, isFrom, isVia);
2111
+ if (dbCtx.mode === "patch" && require_ops.isDbFieldOp(value)) return true;
2112
+ if (dbCtx.mode === "patch" && def.type.kind === "array" && dbCtx.flatMap) {
2113
+ const flatEntry = dbCtx.flatMap.get(ctx.path);
2114
+ if (flatEntry?.metadata?.has("db.__topLevelArray") && !flatEntry.metadata.has("db.json")) return handleArrayPatch(ctx, def, value);
2115
+ }
2116
+ };
2117
+ }
2118
+ function handleNavField(ctx, def, value, dbCtx, isTo, isFrom, isVia) {
2119
+ const pathParts = ctx.path.split(".");
2120
+ const fieldName = pathParts[pathParts.length - 1] || ctx.path;
2121
+ if (value === null) {
2122
+ ctx.error(`Cannot process null navigation property '${fieldName}'`);
2123
+ return false;
2124
+ }
2125
+ if (value === void 0) return true;
2126
+ if (dbCtx.mode === "patch") {
2127
+ if (isFrom || isVia) {
2128
+ if (isPatchOperatorObject(value)) return validateNavPatchOps(ctx, def, value, fieldName);
2129
+ const relType = isFrom ? "1:N" : "M:N";
2130
+ ctx.error(`Cannot patch ${relType} relation '${fieldName}' with a plain value — use patch operators ({ $insert, $remove, $replace, $update, $upsert })`);
2131
+ return false;
2132
+ }
2133
+ }
2134
+ if (isVia) return true;
2135
+ }
2136
+ /**
2137
+ * Validates patch operator values against the nav field's target array type.
2138
+ * Each operator's items are validated against the element type.
2139
+ */
2140
+ function validateNavPatchOps(ctx, def, ops, fieldName) {
2141
+ if (def.type.kind !== "array") {
2142
+ ctx.error(`Cannot use patch operators on non-array relation '${fieldName}'`);
2143
+ return false;
2144
+ }
2145
+ const arrayDef = def;
2146
+ for (const op of [
2147
+ "$replace",
2148
+ "$insert",
2149
+ "$upsert"
2150
+ ]) if (ops[op] !== void 0) {
2151
+ if (!ctx.validateAnnotatedType(arrayDef, ops[op])) return false;
2152
+ }
2153
+ for (const op of ["$update", "$remove"]) if (ops[op] !== void 0) {
2154
+ if (!validatePartialItems(ctx, arrayDef, ops[op], op, true)) return false;
2155
+ }
2156
+ return true;
2157
+ }
2158
+ /**
2159
+ * Handles patch‑mode validation for top‑level embedded arrays.
2160
+ *
2161
+ * When the incoming value is:
2162
+ * - A plain array → falls through to default array validation ($replace semantics)
2163
+ * - A patch operator object → validates each operator's payload individually
2164
+ */
2165
+ function handleArrayPatch(ctx, def, value) {
2166
+ if (Array.isArray(value)) return;
2167
+ if (typeof value !== "object" || value === null) return;
2168
+ const ops = value;
2169
+ const keys = Object.keys(ops);
2170
+ if (keys.length === 0 || !keys.every((k) => PATCH_OPS.has(k))) {
2171
+ if (keys.some((k) => PATCH_OPS.has(k))) {
2172
+ const unknown = keys.filter((k) => !PATCH_OPS.has(k));
2173
+ ctx.error(`Unknown patch operator(s): ${unknown.join(", ")}. Allowed: $replace, $insert, $upsert, $update, $remove`);
2174
+ return false;
2175
+ }
2176
+ return;
2177
+ }
2178
+ for (const op of [
2179
+ "$replace",
2180
+ "$insert",
2181
+ "$upsert"
2182
+ ]) if (ops[op] !== void 0) {
2183
+ if (!ctx.validateAnnotatedType(def, ops[op])) return false;
2184
+ }
2185
+ const isMerge = def.metadata.get("db.patch.strategy") === "merge";
2186
+ for (const op of ["$update", "$remove"]) if (ops[op] !== void 0) {
2187
+ if (!validatePartialItems(ctx, def, ops[op], op, isMerge)) return false;
2188
+ }
2189
+ return true;
2190
+ }
2191
+ /**
2192
+ * Validates `$update` / `$remove` items.
2193
+ *
2194
+ * Each item must be an object. For object arrays with `@expect.array.key` fields,
2195
+ * key properties are required and non‑key properties are validated but optional.
2196
+ * For primitive arrays, items are validated directly against the element type.
2197
+ */
2198
+ function validatePartialItems(ctx, arrayDef, items, op, isMerge) {
2199
+ if (!Array.isArray(items)) {
2200
+ ctx.error(`${op} must be an array`);
2201
+ return false;
2202
+ }
2203
+ const elementDef = arrayDef.type.of;
2204
+ if (elementDef.type.kind !== "object") return ctx.validateAnnotatedType(arrayDef, items);
2205
+ const keyProps = getKeyProps(arrayDef);
2206
+ for (let i = 0; i < items.length; i++) {
2207
+ const item = items[i];
2208
+ if (typeof item !== "object" || item === null || Array.isArray(item)) {
2209
+ ctx.error(`${op}[${i}]: expected object`);
2210
+ return false;
2211
+ }
2212
+ const rec = item;
2213
+ if (keyProps.size > 0) {
2214
+ for (const kp of keyProps) if (rec[kp] === void 0 || rec[kp] === null) {
2215
+ ctx.error(`${op}[${i}]: key field '${kp}' is required`);
2216
+ return false;
2217
+ }
2218
+ }
2219
+ const objType = elementDef.type;
2220
+ for (const [key, val] of Object.entries(rec)) {
2221
+ const propDef = objType.props.get(key);
2222
+ if (propDef) {
2223
+ if (!ctx.validateAnnotatedType(propDef, val)) return false;
2224
+ }
2225
+ }
2226
+ if (op === "$update" && isMerge !== true) {
2227
+ for (const [propName, propDef] of objType.props) if (!propDef.optional && !keyProps.has(propName) && rec[propName] === void 0) {
2228
+ ctx.error(`${op}[${i}]: field '${propName}' is required (replace strategy)`);
2229
+ return false;
2230
+ }
2231
+ }
2232
+ }
2233
+ return true;
2234
+ }
2235
+ //#endregion
2236
+ //#region src/patch/array-ops-resolver.ts
2237
+ /**
2238
+ * Resolves array patch operator keys (`__$insert`, `__$remove`, `__$upsert`,
2239
+ * `__$update`, `__$keys`) in a decomposed update into plain resolved arrays.
2240
+ *
2241
+ * This is the generic fallback for adapters that don't support native patch
2242
+ * operations (e.g., SQLite). It performs a read‑modify‑write:
2243
+ *
2244
+ * 1. Detects `__$` operator keys in the update object
2245
+ * 2. Reads the current record to get existing array values
2246
+ * 3. Applies operations in‑memory
2247
+ * 4. Replaces operator keys with resolved plain values
2248
+ *
2249
+ * @param update - The decomposed update (from `decomposePatch`), potentially
2250
+ * containing `field.__$insert`, `field.__$remove`, etc.
2251
+ * @param currentRecord - The current record fetched from the database (only the
2252
+ * fields that have array ops). Can be `null` if the record doesn't exist.
2253
+ * @param table - The AtscriptDbTable for metadata access (key props, unique items).
2254
+ * @returns A new update object with all `__$` keys resolved to plain values.
2255
+ */
2256
+ function resolveArrayOps(update, currentRecord, table) {
2257
+ const resolved = {};
2258
+ const opsMap = /* @__PURE__ */ new Map();
2259
+ const seenOpsFields = /* @__PURE__ */ new Set();
2260
+ for (const [key, value] of Object.entries(update)) {
2261
+ const match = key.match(/^(.+?)\.__\$(.+)$/);
2262
+ if (!match) {
2263
+ resolved[key] = value;
2264
+ continue;
2265
+ }
2266
+ const field = match[1];
2267
+ const op = match[2];
2268
+ seenOpsFields.add(field);
2269
+ let fieldOps = opsMap.get(field);
2270
+ if (!fieldOps) {
2271
+ fieldOps = {};
2272
+ opsMap.set(field, fieldOps);
2273
+ }
2274
+ switch (op) {
2275
+ case "insert":
2276
+ fieldOps.insert = value;
2277
+ break;
2278
+ case "remove":
2279
+ fieldOps.remove = value;
2280
+ break;
2281
+ case "upsert":
2282
+ fieldOps.upsert = value;
2283
+ break;
2284
+ case "update":
2285
+ fieldOps.update = value;
2286
+ break;
2287
+ case "keys":
2288
+ fieldOps.keys = value;
2289
+ break;
2290
+ }
2291
+ }
2292
+ for (const [field, ops] of opsMap) {
2293
+ const raw = currentRecord?.[field] ?? [];
2294
+ const current = typeof raw === "string" ? JSON.parse(raw) : raw;
2295
+ const arrayType = table.flatMap.get(field);
2296
+ const keyProps = arrayType ? getKeyProps(arrayType) : /* @__PURE__ */ new Set();
2297
+ const uniqueItems = arrayType?.metadata?.get("expect.array.uniqueItems");
2298
+ const mergeStrategy = arrayType?.metadata?.get("db.patch.strategy") === "merge";
2299
+ resolved[field] = applyOps(current, ops, ops.keys && ops.keys.length > 0 ? new Set(ops.keys) : keyProps, !!uniqueItems, mergeStrategy);
2300
+ }
2301
+ return resolved;
2302
+ }
2303
+ /**
2304
+ * Extracts the set of field names that have array ops in the update.
2305
+ * Used to determine which fields need to be fetched from the current record.
2306
+ */
2307
+ function getArrayOpsFields(update) {
2308
+ const fields = /* @__PURE__ */ new Set();
2309
+ for (const key of Object.keys(update)) {
2310
+ const match = key.match(/^(.+?)\.__\$(.+)$/);
2311
+ if (match) fields.add(match[1]);
2312
+ }
2313
+ return fields;
2314
+ }
2315
+ /**
2316
+ * Applies patch operations to an array in‑memory.
2317
+ * Order: remove → update → upsert → insert (most intuitive semantics).
2318
+ */
2319
+ function applyOps(current, ops, keyProps, uniqueItems, mergeStrategy) {
2320
+ let result = [...current];
2321
+ if (ops.remove) result = applyRemove(result, ops.remove, keyProps);
2322
+ if (ops.update) result = applyUpdate(result, ops.update, keyProps);
2323
+ if (ops.upsert) result = applyUpsert(result, ops.upsert, keyProps, mergeStrategy);
2324
+ if (ops.insert) result = applyInsert(result, ops.insert, keyProps, uniqueItems);
2325
+ return result;
2326
+ }
2327
+ function applyRemove(arr, items, keyProps) {
2328
+ if (keyProps.size === 0) {
2329
+ const removeSet = new Set(items.map((i) => JSON.stringify(i)));
2330
+ return arr.filter((el) => !removeSet.has(JSON.stringify(el)));
2331
+ }
2332
+ return arr.filter((el) => {
2333
+ const elObj = el;
2334
+ return !items.some((item) => matchByKeys(elObj, item, keyProps));
2335
+ });
2336
+ }
2337
+ function applyUpdate(arr, items, keyProps) {
2338
+ if (keyProps.size === 0) return arr;
2339
+ return arr.map((el) => {
2340
+ const elObj = el;
2341
+ const match = items.find((item) => matchByKeys(elObj, item, keyProps));
2342
+ if (match) return {
2343
+ ...elObj,
2344
+ ...match
2345
+ };
2346
+ return el;
2347
+ });
2348
+ }
2349
+ function applyUpsert(arr, items, keyProps, mergeStrategy) {
2350
+ const result = [...arr];
2351
+ for (const item of items) if (keyProps.size === 0) {
2352
+ const idx = result.findIndex((el) => JSON.stringify(el) === JSON.stringify(item));
2353
+ if (idx >= 0) result[idx] = item;
2354
+ else result.push(item);
2355
+ } else {
2356
+ const itemObj = item;
2357
+ const idx = result.findIndex((el) => matchByKeys(el, itemObj, keyProps));
2358
+ if (idx >= 0) result[idx] = mergeStrategy ? {
2359
+ ...result[idx],
2360
+ ...itemObj
2361
+ } : itemObj;
2362
+ else result.push(item);
2363
+ }
2364
+ return result;
2365
+ }
2366
+ function applyInsert(arr, items, keyProps, uniqueItems) {
2367
+ if (!uniqueItems) return [...arr, ...items];
2368
+ const result = [...arr];
2369
+ for (const item of items) if (!(keyProps.size > 0 ? result.some((el) => matchByKeys(el, item, keyProps)) : result.some((el) => JSON.stringify(el) === JSON.stringify(item)))) result.push(item);
2370
+ return result;
2371
+ }
2372
+ function matchByKeys(a, b, keys) {
2373
+ for (const key of keys) if (a[key] !== b[key]) return false;
2374
+ return true;
2375
+ }
2376
+ //#endregion
2377
+ //#region src/patch/patch-decomposer.ts
2378
+ /**
2379
+ * Decomposes a patch payload into a flat update object for adapters
2380
+ * that don't support native patch operations.
2381
+ *
2382
+ * Handles:
2383
+ * - Top-level array patches (`$replace`, `$insert`, `$upsert`, `$update`, `$remove`)
2384
+ * - Merge strategy for nested objects
2385
+ * - Simple field sets
2386
+ *
2387
+ * For adapters with native patch support (e.g., MongoDB aggregation pipelines),
2388
+ * use {@link BaseDbAdapter.nativePatch} instead.
2389
+ *
2390
+ * @param payload - The patch payload from the user.
2391
+ * @param table - The AtscriptDbTable instance for metadata access.
2392
+ * @returns A flat update object suitable for a basic `updateOne` call.
2393
+ */
2394
+ function decomposePatch(payload, table) {
2395
+ const update = {};
2396
+ flattenPatchPayload(payload, "", update, table, "db.__topLevelArray");
2397
+ return update;
2398
+ }
2399
+ function flattenPatchPayload(payload, prefix, update, table, topLevelArrayTag) {
2400
+ for (const [_key, value] of Object.entries(payload)) {
2401
+ const key = prefix ? `${prefix}.${_key}` : _key;
2402
+ if (table.primaryKeys.includes(key)) continue;
2403
+ const flatType = table.flatMap.get(key);
2404
+ const isTopLevelArray = flatType?.metadata?.get(topLevelArrayTag);
2405
+ if (typeof value === "object" && value !== null && !Array.isArray(value) && isTopLevelArray && !flatType?.metadata?.has("db.json")) decomposeArrayPatch(key, value, flatType, update, table);
2406
+ else if (typeof value === "object" && value !== null && !Array.isArray(value) && flatType?.metadata?.get("db.patch.strategy") === "merge") flattenPatchPayload(value, key, update, table, topLevelArrayTag);
2407
+ else update[key] = value;
2408
+ }
2409
+ }
2410
+ /**
2411
+ * Decomposes array patch operators into simple field updates.
2412
+ *
2413
+ * For adapters without native array operations, this does a best-effort
2414
+ * decomposition:
2415
+ * - `$replace` → direct set
2416
+ * - `$insert` → value to append (adapter must handle)
2417
+ * - `$upsert` → value to upsert by key (adapter must handle)
2418
+ * - `$update` → value to update by key (adapter must handle)
2419
+ * - `$remove` → value to remove by key (adapter must handle)
2420
+ *
2421
+ * Note: For full correctness with `$insert`/`$upsert`/`$update`/`$remove`,
2422
+ * the adapter should implement native patch support. This generic decomposition
2423
+ * handles `$replace` directly and stores the other operations in a structured
2424
+ * format the adapter can interpret.
2425
+ */
2426
+ function decomposeArrayPatch(key, value, fieldType, update, _table) {
2427
+ const keyProps = fieldType.type.kind === "array" ? getKeyProps(fieldType) : /* @__PURE__ */ new Set();
2428
+ if (value.$replace !== void 0) {
2429
+ update[key] = value.$replace;
2430
+ return;
2431
+ }
2432
+ if (value.$insert !== void 0) update[`${key}.__$insert`] = value.$insert;
2433
+ if (value.$upsert !== void 0) update[`${key}.__$upsert`] = value.$upsert;
2434
+ if (value.$update !== void 0) update[`${key}.__$update`] = value.$update;
2435
+ if (value.$remove !== void 0) update[`${key}.__$remove`] = value.$remove;
2436
+ if (keyProps.size > 0 && (value.$upsert !== void 0 || value.$update !== void 0 || value.$remove !== void 0)) update[`${key}.__$keys`] = [...keyProps];
2437
+ }
2438
+ //#endregion
2439
+ //#region src/table/db-table.ts
2440
+ /**
2441
+ * Generic database table abstraction driven by Atscript `@db.*` annotations.
2442
+ *
2443
+ * Extends {@link AtscriptDbReadable} (read operations, field metadata, query
2444
+ * translation, relation loading) with write operations, validators, and
2445
+ * schema management.
2446
+ *
2447
+ * ```typescript
2448
+ * const adapter = new MongoAdapter(db)
2449
+ * const users = new AtscriptDbTable(UsersType, adapter)
2450
+ * await users.insertOne({ name: 'John', email: 'john@example.com' })
2451
+ * ```
2452
+ *
2453
+ * @typeParam T - The Atscript annotated type for this table.
2454
+ * @typeParam DataType - The inferred data shape from the annotated type.
2455
+ */
2456
+ /** Zero-allocation emptiness check for objects. */
2457
+ function _isEmptyObj(obj) {
2458
+ for (const _ in obj) return false;
2459
+ return true;
2460
+ }
2461
+ /** Translates a single ops record from logical to physical column names. */
2462
+ function _translateOpsRecord(rec, meta) {
2463
+ const out = {};
2464
+ for (const key in rec) out[meta.leafByLogical.get(key)?.physicalName ?? key] = rec[key];
2465
+ return out;
2466
+ }
2467
+ /** Translates ops keys from logical field names to physical column names. */
2468
+ function _translateOpsKeys(ops, meta) {
2469
+ return {
2470
+ inc: ops.inc ? _translateOpsRecord(ops.inc, meta) : void 0,
2471
+ mul: ops.mul ? _translateOpsRecord(ops.mul, meta) : void 0
2472
+ };
2473
+ }
2474
+ /**
2475
+ * Forces nav fields non-optional so the plugin handles null/undefined
2476
+ * checks (validator skips optional+null before plugins run).
2477
+ */
2478
+ function forceNavNonOptional(type) {
2479
+ if (type.metadata?.has("db.rel.to") || type.metadata?.has("db.rel.from") || type.metadata?.has("db.rel.via")) return type.optional ? {
2480
+ ...type,
2481
+ optional: false
2482
+ } : type;
2483
+ return type;
2484
+ }
2485
+ /** Makes PK, defaulted, and FK fields optional; forces nav fields non-optional. */
2486
+ function insertReplace(type) {
2487
+ if (type.metadata?.has("meta.id") || type.metadata?.has("db.default") || type.metadata?.has("db.default.increment") || type.metadata?.has("db.default.uuid") || type.metadata?.has("db.default.now") || type.metadata?.has("db.rel.FK")) return {
2488
+ ...type,
2489
+ optional: true
2490
+ };
2491
+ return forceNavNonOptional(type);
2492
+ }
2493
+ var AtscriptDbTable = class extends AtscriptDbReadable {
2494
+ _cascadeResolver;
2495
+ _fkLookupResolver;
2496
+ _integrity;
2497
+ validators = /* @__PURE__ */ new Map();
2498
+ constructor(_type, adapter, logger, _tableResolver, _writeTableResolver) {
2499
+ super(_type, adapter, logger, _tableResolver);
2500
+ if (_writeTableResolver) this._writeTableResolver = _writeTableResolver;
2501
+ this._integrity = adapter.supportsNativeForeignKeys() ? new NativeIntegrity() : new ApplicationIntegrity();
2502
+ }
2503
+ /**
2504
+ * Sets the cascade resolver for application-level cascade deletes.
2505
+ * Called by DbSpace after table creation.
2506
+ */
2507
+ setCascadeResolver(resolver) {
2508
+ this._cascadeResolver = resolver;
2509
+ }
2510
+ /**
2511
+ * Sets the FK lookup resolver for application-level FK validation.
2512
+ * Called by DbSpace after table creation.
2513
+ */
2514
+ setFkLookupResolver(resolver) {
2515
+ this._fkLookupResolver = resolver;
2516
+ }
2517
+ /**
2518
+ * Returns a cached validator for the given purpose.
2519
+ * Built with adapter plugins from {@link BaseDbAdapter.getValidatorPlugins}.
2520
+ *
2521
+ * Standard purposes: `'insert'`, `'update'`, `'patch'`.
2522
+ * Adapters may define additional purposes.
2523
+ */
2524
+ getValidator(purpose) {
2525
+ if (!this.validators.has(purpose)) {
2526
+ const validator = this._buildValidator(purpose);
2527
+ this.validators.set(purpose, validator);
2528
+ }
2529
+ return this.validators.get(purpose);
2530
+ }
2531
+ /**
2532
+ * Inserts a single record. Delegates to {@link insertMany} for unified
2533
+ * nested creation support.
2534
+ */
2535
+ async insertOne(payload, opts) {
2536
+ return { insertedId: (await this.insertMany([payload], opts)).insertedIds[0] };
2537
+ }
2538
+ /**
2539
+ * Inserts multiple records with batch-optimized nested creation.
2540
+ *
2541
+ * Supports **nested creation**: if payloads include data for navigation
2542
+ * fields (`@db.rel.to` / `@db.rel.from`), related records are created
2543
+ * automatically in batches. TO dependencies are batch-created first
2544
+ * (their PKs become our FKs), FROM dependents are batch-created after
2545
+ * (they receive our PKs as their FKs). Fully recursive — nested records
2546
+ * with their own nav data trigger further batch inserts at each level.
2547
+ * Recursive up to `maxDepth` (default 3).
2548
+ */
2549
+ async insertMany(payloads, opts) {
2550
+ this._ensureBuilt();
2551
+ const maxDepth = opts?.maxDepth ?? 3;
2552
+ const depth = opts?._depth ?? 0;
2553
+ const canNest = depth < maxDepth && this._writeTableResolver && this._meta.navFields.size > 0;
2554
+ if (!canNest && this._meta.navFields.size > 0) require_nested_writer.checkDepthOverflow(payloads, maxDepth, this._meta);
2555
+ return require_nested_writer.enrichFkViolation(this._meta, () => this.adapter.withTransaction(async () => {
2556
+ const items = payloads.map((p) => this._applyDefaults({ ...p }));
2557
+ require_nested_writer.validateBatch(this.getValidator("insert"), items, { mode: "insert" });
2558
+ const host = this;
2559
+ if (canNest) await require_nested_writer.batchInsertNestedTo(host, items, maxDepth, depth);
2560
+ const prepared = [];
2561
+ for (const data of items) {
2562
+ for (const navField of this._meta.navFields) delete data[navField];
2563
+ prepared.push(this._fieldMapper.prepareForWrite(data, this._meta, this.adapter));
2564
+ }
2565
+ await this._integrity.validateForeignKeys(items, this._meta, this._fkLookupResolver, this._writeTableResolver);
2566
+ if (canNest) await require_nested_writer.preValidateNestedFrom(host, payloads);
2567
+ const result = await this.adapter.insertMany(prepared);
2568
+ if (canNest) await require_nested_writer.batchInsertNestedFrom(host, payloads, result.insertedIds, maxDepth, depth);
2569
+ if (canNest) await require_nested_writer.batchInsertNestedVia(host, payloads, result.insertedIds, maxDepth, depth);
2570
+ return result;
2571
+ }));
2572
+ }
2573
+ /**
2574
+ * Replaces a single record identified by primary key(s).
2575
+ * Delegates to {@link bulkReplace} for unified nested relation support.
2576
+ */
2577
+ async replaceOne(payload, opts) {
2578
+ return this.bulkReplace([payload], opts);
2579
+ }
2580
+ /**
2581
+ * Replaces multiple records with deep nested relation support.
2582
+ *
2583
+ * Supports all relation types (TO, FROM, VIA). TO dependencies are
2584
+ * replaced first (their PKs become our FKs), FROM dependents are replaced
2585
+ * after (they receive our PKs as their FKs), VIA relations clear and
2586
+ * re-create junction rows. Fully recursive up to `maxDepth` (default 3).
2587
+ */
2588
+ async bulkReplace(payloads, opts) {
2589
+ this._ensureBuilt();
2590
+ const maxDepth = opts?.maxDepth ?? 3;
2591
+ const depth = opts?._depth ?? 0;
2592
+ const canNest = depth < maxDepth && this._writeTableResolver && this._meta.navFields.size > 0;
2593
+ if (!canNest && this._meta.navFields.size > 0) require_nested_writer.checkDepthOverflow(payloads, maxDepth, this._meta);
2594
+ return require_nested_writer.enrichFkViolation(this._meta, () => this.adapter.withTransaction(async () => {
2595
+ const items = payloads.map((p) => this._applyDefaults({ ...p }));
2596
+ const originals = canNest ? payloads.map((p) => ({ ...p })) : [];
2597
+ require_nested_writer.validateBatch(this.getValidator("bulkReplace"), items, { mode: "replace" });
2598
+ const host = this;
2599
+ if (canNest) await require_nested_writer.batchReplaceNestedTo(host, items, maxDepth, depth);
2600
+ await this._integrity.validateForeignKeys(items, this._meta, this._fkLookupResolver, this._writeTableResolver);
2601
+ if (canNest) await require_nested_writer.preValidateNestedFrom(host, originals);
2602
+ let matchedCount = 0;
2603
+ let modifiedCount = 0;
2604
+ for (const data of items) {
2605
+ for (const navField of this._meta.navFields) delete data[navField];
2606
+ const filter = this._extractPrimaryKeyFilter(data);
2607
+ const prepared = this._fieldMapper.prepareForWrite(data, this._meta, this.adapter);
2608
+ const result = await this.adapter.replaceOne(this._fieldMapper.translateFilter(filter, this._meta), prepared);
2609
+ matchedCount += result.matchedCount;
2610
+ modifiedCount += result.modifiedCount;
2611
+ }
2612
+ if (canNest) await require_nested_writer.batchReplaceNestedFrom(host, originals, maxDepth, depth);
2613
+ if (canNest) await require_nested_writer.batchReplaceNestedVia(host, originals, maxDepth, depth);
2614
+ return {
2615
+ matchedCount,
2616
+ modifiedCount
2617
+ };
2618
+ }));
2619
+ }
2620
+ /**
2621
+ * Partially updates a single record identified by primary key(s).
2622
+ * Delegates to {@link bulkUpdate} for unified nested relation support.
2623
+ */
2624
+ async updateOne(payload, opts) {
2625
+ return this.bulkUpdate([payload], opts);
2626
+ }
2627
+ /**
2628
+ * Partially updates multiple records with deep nested relation support.
2629
+ *
2630
+ * Only TO relations (1:1, N:1) are supported for patching. FROM/VIA
2631
+ * relations will error — use {@link bulkReplace} for those.
2632
+ * Recursive up to `maxDepth` (default 3).
2633
+ */
2634
+ async bulkUpdate(payloads, opts) {
2635
+ this._ensureBuilt();
2636
+ const maxDepth = opts?.maxDepth ?? 3;
2637
+ const depth = opts?._depth ?? 0;
2638
+ const canNest = depth < maxDepth && this._writeTableResolver && this._meta.navFields.size > 0;
2639
+ if (!canNest && this._meta.navFields.size > 0) require_nested_writer.checkDepthOverflow(payloads, maxDepth, this._meta);
2640
+ return require_nested_writer.enrichFkViolation(this._meta, () => this.adapter.withTransaction(async () => {
2641
+ require_nested_writer.validateBatch(this.getValidator("bulkUpdate"), payloads, {
2642
+ mode: "patch",
2643
+ flatMap: this.flatMap
2644
+ });
2645
+ const originals = canNest ? payloads.map((p) => ({ ...p })) : [];
2646
+ const host = this;
2647
+ if (canNest) await require_nested_writer.batchPatchNestedTo(host, payloads, maxDepth, depth);
2648
+ await this._integrity.validateForeignKeys(payloads, this._meta, this._fkLookupResolver, this._writeTableResolver, true);
2649
+ let matchedCount = 0;
2650
+ let modifiedCount = 0;
2651
+ for (const payload of payloads) {
2652
+ const data = { ...payload };
2653
+ for (const navField of this._meta.navFields) delete data[navField];
2654
+ const filter = this._extractPrimaryKeyFilter(data);
2655
+ for (const pk of this._meta.primaryKeys) delete data[pk];
2656
+ const ops = require_ops.separateFieldOps(data);
2657
+ if (_isEmptyObj(data) && !ops) {
2658
+ matchedCount += 1;
2659
+ modifiedCount += 0;
2660
+ continue;
2661
+ }
2662
+ let result;
2663
+ const translatedFilter = this._fieldMapper.translateFilter(filter, this._meta);
2664
+ const translatedOps = ops ? _translateOpsKeys(ops, this._meta) : void 0;
2665
+ if (this.adapter.supportsNativePatch()) result = await this.adapter.nativePatch(translatedFilter, data, translatedOps);
2666
+ else {
2667
+ const update = decomposePatch(data, this);
2668
+ const translatedUpdate = this._fieldMapper.translatePatchKeys(update, this._meta);
2669
+ if (getArrayOpsFields(translatedUpdate).size > 0) {
2670
+ const resolved = resolveArrayOps(translatedUpdate, await this.adapter.findOne({
2671
+ filter: translatedFilter,
2672
+ controls: {}
2673
+ }), this);
2674
+ result = await this.adapter.updateOne(translatedFilter, resolved, translatedOps);
2675
+ } else result = await this.adapter.updateOne(translatedFilter, translatedUpdate, translatedOps);
2676
+ }
2677
+ matchedCount += result.matchedCount;
2678
+ modifiedCount += result.modifiedCount;
2679
+ }
2680
+ if (canNest) await require_nested_writer.batchPatchNestedFrom(host, originals, maxDepth, depth);
2681
+ if (canNest) await require_nested_writer.batchPatchNestedVia(host, originals, maxDepth, depth);
2682
+ return {
2683
+ matchedCount,
2684
+ modifiedCount
2685
+ };
2686
+ }));
2687
+ }
2688
+ /**
2689
+ * Deletes a single record by any type-compatible identifier — primary key
2690
+ * or single-field unique index. Uses the same resolution logic as `findById`.
2691
+ *
2692
+ * When the adapter does not support native foreign keys (e.g. MongoDB),
2693
+ * cascade and setNull actions are applied before the delete.
2694
+ */
2695
+ async deleteOne(id) {
2696
+ this._ensureBuilt();
2697
+ const filter = this._resolveIdFilter(id);
2698
+ if (!filter) return { deletedCount: 0 };
2699
+ if (this._integrity.needsCascade(this._cascadeResolver)) return require_nested_writer.remapDeleteFkViolation(this.tableName, () => this.adapter.withTransaction(async () => {
2700
+ await this._integrity.cascadeBeforeDelete(filter, this.tableName, this._meta, this._cascadeResolver, (f) => this._fieldMapper.translateFilter(f, this._meta), this.adapter);
2701
+ return this.adapter.deleteOne(this._fieldMapper.translateFilter(filter, this._meta));
2702
+ }));
2703
+ return require_nested_writer.remapDeleteFkViolation(this.tableName, () => this.adapter.deleteOne(this._fieldMapper.translateFilter(filter, this._meta)));
2704
+ }
2705
+ async updateMany(filter, data) {
2706
+ this._ensureBuilt();
2707
+ await this._integrity.validateForeignKeys([data], this._meta, this._fkLookupResolver, this._writeTableResolver, true);
2708
+ const dataCopy = { ...data };
2709
+ const ops = require_ops.separateFieldOps(dataCopy);
2710
+ const translatedOps = ops ? _translateOpsKeys(ops, this._meta) : void 0;
2711
+ return require_nested_writer.enrichFkViolation(this._meta, () => this.adapter.updateMany(this._fieldMapper.translateFilter(filter, this._meta), this._fieldMapper.prepareForWrite(dataCopy, this._meta, this.adapter), translatedOps));
2712
+ }
2713
+ async replaceMany(filter, data) {
2714
+ this._ensureBuilt();
2715
+ await this._integrity.validateForeignKeys([data], this._meta, this._fkLookupResolver, this._writeTableResolver);
2716
+ return require_nested_writer.enrichFkViolation(this._meta, () => this.adapter.replaceMany(this._fieldMapper.translateFilter(filter, this._meta), this._fieldMapper.prepareForWrite({ ...data }, this._meta, this.adapter)));
2717
+ }
2718
+ async deleteMany(filter) {
2719
+ this._ensureBuilt();
2720
+ if (this._integrity.needsCascade(this._cascadeResolver)) return require_nested_writer.remapDeleteFkViolation(this.tableName, () => this.adapter.withTransaction(async () => {
2721
+ await this._integrity.cascadeBeforeDelete(filter, this.tableName, this._meta, this._cascadeResolver, (f) => this._fieldMapper.translateFilter(f, this._meta), this.adapter);
2722
+ return this.adapter.deleteMany(this._fieldMapper.translateFilter(filter, this._meta));
2723
+ }));
2724
+ return require_nested_writer.remapDeleteFkViolation(this.tableName, () => this.adapter.deleteMany(this._fieldMapper.translateFilter(filter, this._meta)));
2725
+ }
2726
+ /**
2727
+ * Synchronizes indexes between Atscript definitions and the database.
2728
+ */
2729
+ async syncIndexes() {
2730
+ this._ensureBuilt();
2731
+ return this.adapter.syncIndexes();
2732
+ }
2733
+ /**
2734
+ * Ensures the table/collection exists in the database.
2735
+ */
2736
+ async ensureTable() {
2737
+ this._ensureBuilt();
2738
+ return this.adapter.ensureTable();
2739
+ }
2740
+ /**
2741
+ * Applies default values for fields that are missing from the payload.
2742
+ * Defaults handled natively by the DB engine are skipped — the field stays
2743
+ * absent so the DB's own DEFAULT clause applies.
2744
+ */
2745
+ _applyDefaults(data) {
2746
+ const nativeValues = this.adapter.supportsNativeValueDefaults();
2747
+ const nativeFns = this.adapter.nativeDefaultFns();
2748
+ for (const [field, def] of this._meta.defaults.entries()) if (data[field] === void 0) {
2749
+ if (def.kind === "value" && !nativeValues) {
2750
+ const fieldType = this._meta.flatMap?.get(field);
2751
+ data[field] = (fieldType?.type.kind === "" && fieldType.type.designType) === "string" ? def.value : JSON.parse(def.value);
2752
+ } else if (def.kind === "fn" && !nativeFns.has(def.fn)) switch (def.fn) {
2753
+ case "now":
2754
+ data[field] = Date.now();
2755
+ break;
2756
+ case "uuid":
2757
+ data[field] = crypto.randomUUID();
2758
+ break;
2759
+ }
2760
+ }
2761
+ return data;
2762
+ }
2763
+ /**
2764
+ * Extracts primary key field(s) from a payload to build a filter.
2765
+ */
2766
+ _extractPrimaryKeyFilter(payload) {
2767
+ const pkFields = this.primaryKeys;
2768
+ if (pkFields.length === 0) throw new require_nested_writer.DbError("NOT_FOUND", [{
2769
+ path: "",
2770
+ message: "No primary key defined — cannot extract filter"
2771
+ }]);
2772
+ const filter = {};
2773
+ for (const field of pkFields) {
2774
+ if (payload[field] === void 0) throw new require_nested_writer.DbError("NOT_FOUND", [{
2775
+ path: field,
2776
+ message: `Missing primary key field "${field}" in payload`
2777
+ }]);
2778
+ const fieldType = this.flatMap.get(field);
2779
+ filter[field] = fieldType ? this.adapter.prepareId(payload[field], fieldType) : payload[field];
2780
+ }
2781
+ return filter;
2782
+ }
2783
+ /**
2784
+ * Pre-validate items (type validation + FK constraints) without inserting them.
2785
+ * Used by parent tables to validate FROM children before the main insert,
2786
+ * ensuring errors are caught before the parent is committed.
2787
+ *
2788
+ * @param opts.excludeFkTargetTable - Skip FK validation to this table (the parent).
2789
+ */
2790
+ async preValidateItems(items, opts) {
2791
+ this._ensureBuilt();
2792
+ require_nested_writer.validateBatch(this.getValidator("insert"), items.map((raw) => this._applyDefaults({ ...raw })), { mode: "insert" });
2793
+ await this._integrity.validateForeignKeys(items, this._meta, this._fkLookupResolver, this._writeTableResolver, false, opts?.excludeFkTargetTable);
2794
+ }
2795
+ /**
2796
+ * Builds a validator for a given purpose with adapter plugins.
2797
+ *
2798
+ * Uses annotation-based `replace` callback to make `@meta.id` and
2799
+ * `@db.default` fields optional — works at all nesting levels
2800
+ * (including inside nav field target types).
2801
+ */
2802
+ _buildValidator(purpose) {
2803
+ const dbPlugin = createDbValidatorPlugin();
2804
+ const plugins = [...this.adapter.getValidatorPlugins(), dbPlugin];
2805
+ switch (purpose) {
2806
+ case "insert": return this.createValidator({
2807
+ plugins,
2808
+ replace: insertReplace
2809
+ });
2810
+ case "patch": return this.createValidator({
2811
+ plugins,
2812
+ partial: true,
2813
+ replace: forceNavNonOptional
2814
+ });
2815
+ case "bulkReplace": return this.createValidator({
2816
+ plugins,
2817
+ replace: insertReplace
2818
+ });
2819
+ case "bulkUpdate": {
2820
+ const navFields = this._meta.navFields;
2821
+ return this.createValidator({
2822
+ plugins,
2823
+ partial: (_def, path) => {
2824
+ if (path === "") return true;
2825
+ const root = path.split(".")[0];
2826
+ if (navFields.has(root)) return true;
2827
+ return _def.metadata.get("db.patch.strategy") === "merge";
2828
+ },
2829
+ replace: forceNavNonOptional
2830
+ });
2831
+ }
2832
+ default: return this.createValidator({ plugins });
2833
+ }
2834
+ }
2835
+ };
2836
+ //#endregion
2837
+ //#region src/table/db-view.ts
2838
+ /**
2839
+ * Database view abstraction driven by Atscript `@db.view.*` annotations.
2840
+ *
2841
+ * Extends {@link AtscriptDbReadable} with view plan resolution — entry table,
2842
+ * joins, filter, and materialization flag. Read operations are inherited;
2843
+ * write operations are not available on views.
2844
+ *
2845
+ * ```typescript
2846
+ * const adapter = new SqliteAdapter(db)
2847
+ * const activeUsers = new AtscriptDbView(ActiveUsersType, adapter)
2848
+ * const users = await activeUsers.findMany({ filter: {}, controls: {} })
2849
+ * ```
2850
+ */
2851
+ var AtscriptDbView = class extends AtscriptDbReadable {
2852
+ _viewPlan;
2853
+ get isView() {
2854
+ return true;
2855
+ }
2856
+ /**
2857
+ * Whether this is an external view — declared with `@db.view` only,
2858
+ * without `@db.view.for`. External views reference pre-existing DB views
2859
+ * and are not managed (created/dropped) by schema sync.
2860
+ */
2861
+ get isExternal() {
2862
+ return !this._type.metadata.has("db.view.for");
2863
+ }
2864
+ /**
2865
+ * Lazily resolves the view plan from `@db.view.*` metadata.
2866
+ *
2867
+ * - `db.view.for` → entry type ref (required)
2868
+ * - `db.view.joins` → array of `{ target, condition }` (optional, multiple)
2869
+ * - `db.view.filter` → query tree (optional)
2870
+ * - `db.view.materialized` → boolean (optional)
2871
+ */
2872
+ get viewPlan() {
2873
+ if (this._viewPlan) return this._viewPlan;
2874
+ if (this.isExternal) throw new Error(`Cannot compute view plan for external view "${this.tableName}". External views (declared without @db.view.for) reference pre-existing DB views.`);
2875
+ const metadata = this._type.metadata;
2876
+ const forRef = metadata.get("db.view.for");
2877
+ const entryType = typeof forRef === "function" ? forRef : forRef.type;
2878
+ const entryTypeResolved = entryType();
2879
+ const entryTable = entryTypeResolved?.metadata?.get("db.table") || entryTypeResolved?.id || "";
2880
+ const rawJoins = metadata.get("db.view.joins");
2881
+ const joins = [];
2882
+ if (rawJoins) for (const join of rawJoins) {
2883
+ const targetRef = join.target;
2884
+ const targetType = typeof targetRef === "function" ? targetRef : targetRef.type;
2885
+ const targetTypeResolved = targetType();
2886
+ const targetTable = targetTypeResolved?.metadata?.get("db.table") || targetTypeResolved?.id || "";
2887
+ joins.push({
2888
+ targetType,
2889
+ targetTable,
2890
+ condition: join.condition
2891
+ });
2892
+ }
2893
+ this._viewPlan = {
2894
+ entryType,
2895
+ entryTable,
2896
+ joins,
2897
+ filter: metadata.get("db.view.filter"),
2898
+ having: metadata.get("db.view.having"),
2899
+ materialized: metadata.has("db.view.materialized")
2900
+ };
2901
+ return this._viewPlan;
2902
+ }
2903
+ /**
2904
+ * Resolves a query field ref to a quoted `table.column` SQL fragment.
2905
+ *
2906
+ * @param ref - The field reference from the query tree.
2907
+ * @param qi - Identifier quoting function (e.g. backtick for MySQL, double-quote for SQLite).
2908
+ * Defaults to double-quote wrapping for backwards compatibility.
2909
+ */
2910
+ resolveFieldRef(ref, qi = (n) => `"${n}"`) {
2911
+ if (!ref.type) {
2912
+ const plan = this.viewPlan;
2913
+ return `${qi(plan.entryTable)}.${qi(ref.field)}`;
2914
+ }
2915
+ const resolved = ref.type();
2916
+ return `${qi(resolved?.metadata?.get("db.table") || resolved?.id || "")}.${qi(ref.field)}`;
2917
+ }
2918
+ /**
2919
+ * Maps each view field to its source table and column via ref chain.
2920
+ * Fields without refs (inline definitions) map to the entry table with the same name.
2921
+ */
2922
+ getViewColumnMappings() {
2923
+ const plan = this.viewPlan;
2924
+ const mappings = [];
2925
+ if (this._type.type.kind !== "object") return mappings;
2926
+ const aggKeys = [
2927
+ "db.agg.sum",
2928
+ "db.agg.avg",
2929
+ "db.agg.count",
2930
+ "db.agg.min",
2931
+ "db.agg.max"
2932
+ ];
2933
+ for (const [fieldName, fieldType] of this._type.type.props.entries()) {
2934
+ let aggFn;
2935
+ let aggField;
2936
+ for (const key of aggKeys) {
2937
+ const val = fieldType.metadata?.get(key);
2938
+ if (val !== void 0) {
2939
+ aggFn = key.split(".")[2];
2940
+ aggField = typeof val === "string" ? val : "*";
2941
+ break;
2942
+ }
2943
+ }
2944
+ if (fieldType.ref) {
2945
+ const resolved = fieldType.ref.type();
2946
+ const sourceTable = resolved?.metadata?.get("db.table") || resolved?.id || "";
2947
+ const sourceColumn = fieldType.ref.field || fieldName;
2948
+ mappings.push({
2949
+ viewColumn: fieldName,
2950
+ sourceTable,
2951
+ sourceColumn,
2952
+ aggFn,
2953
+ aggField
2954
+ });
2955
+ } else {
2956
+ const sourceColumn = aggField && aggField !== "*" ? aggField : fieldName;
2957
+ mappings.push({
2958
+ viewColumn: fieldName,
2959
+ sourceTable: plan.entryTable,
2960
+ sourceColumn,
2961
+ aggFn,
2962
+ aggField
2963
+ });
2964
+ }
2965
+ }
2966
+ return mappings;
2967
+ }
2968
+ };
2969
+ //#endregion
2970
+ Object.defineProperty(exports, "ApplicationIntegrity", {
2971
+ enumerable: true,
2972
+ get: function() {
2973
+ return ApplicationIntegrity;
2974
+ }
2975
+ });
2976
+ Object.defineProperty(exports, "AtscriptDbReadable", {
2977
+ enumerable: true,
2978
+ get: function() {
2979
+ return AtscriptDbReadable;
2980
+ }
2981
+ });
2982
+ Object.defineProperty(exports, "AtscriptDbTable", {
2983
+ enumerable: true,
2984
+ get: function() {
2985
+ return AtscriptDbTable;
2986
+ }
2987
+ });
2988
+ Object.defineProperty(exports, "AtscriptDbView", {
2989
+ enumerable: true,
2990
+ get: function() {
2991
+ return AtscriptDbView;
2992
+ }
2993
+ });
2994
+ Object.defineProperty(exports, "BaseDbAdapter", {
2995
+ enumerable: true,
2996
+ get: function() {
2997
+ return BaseDbAdapter;
2998
+ }
2999
+ });
3000
+ Object.defineProperty(exports, "DocumentFieldMapper", {
3001
+ enumerable: true,
3002
+ get: function() {
3003
+ return DocumentFieldMapper;
3004
+ }
3005
+ });
3006
+ Object.defineProperty(exports, "FieldMappingStrategy", {
3007
+ enumerable: true,
3008
+ get: function() {
3009
+ return FieldMappingStrategy;
3010
+ }
3011
+ });
3012
+ Object.defineProperty(exports, "IntegrityStrategy", {
3013
+ enumerable: true,
3014
+ get: function() {
3015
+ return IntegrityStrategy;
3016
+ }
3017
+ });
3018
+ Object.defineProperty(exports, "NativeIntegrity", {
3019
+ enumerable: true,
3020
+ get: function() {
3021
+ return NativeIntegrity;
3022
+ }
3023
+ });
3024
+ Object.defineProperty(exports, "NoopLogger", {
3025
+ enumerable: true,
3026
+ get: function() {
3027
+ return NoopLogger;
3028
+ }
3029
+ });
3030
+ Object.defineProperty(exports, "RelationalFieldMapper", {
3031
+ enumerable: true,
3032
+ get: function() {
3033
+ return RelationalFieldMapper;
3034
+ }
3035
+ });
3036
+ Object.defineProperty(exports, "TableMetadata", {
3037
+ enumerable: true,
3038
+ get: function() {
3039
+ return TableMetadata;
3040
+ }
3041
+ });
3042
+ Object.defineProperty(exports, "UniquSelect", {
3043
+ enumerable: true,
3044
+ get: function() {
3045
+ return UniquSelect;
3046
+ }
3047
+ });
3048
+ Object.defineProperty(exports, "createDbValidatorPlugin", {
3049
+ enumerable: true,
3050
+ get: function() {
3051
+ return createDbValidatorPlugin;
3052
+ }
3053
+ });
3054
+ Object.defineProperty(exports, "decomposePatch", {
3055
+ enumerable: true,
3056
+ get: function() {
3057
+ return decomposePatch;
3058
+ }
3059
+ });
3060
+ Object.defineProperty(exports, "getKeyProps", {
3061
+ enumerable: true,
3062
+ get: function() {
3063
+ return getKeyProps;
3064
+ }
3065
+ });
3066
+ Object.defineProperty(exports, "resolveDesignType", {
3067
+ enumerable: true,
3068
+ get: function() {
3069
+ return resolveDesignType;
3070
+ }
3071
+ });