@atscript/db 0.1.39 → 0.1.41

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 +18 -18
  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-CMI9TOo1.cjs +3096 -0
  15. package/dist/db-view-Esy2fDxw.mjs +2995 -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 +30 -29
  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
package/dist/sync.d.ts DELETED
@@ -1,1878 +0,0 @@
1
- import { TAtscriptAnnotatedType, TAtscriptTypeObject, TAtscriptDataType, FlatOf, PrimaryKeyOf, OwnPropsOf, NavPropsOf, TValidatorOptions, Validator, TValidatorPlugin, TMetadataMap, AtscriptQueryNode, AtscriptQueryFieldRef } from '@atscript/typescript/utils';
2
- import { UniqueryControls, AggregateExpr, WithRelation, FilterExpr, UniqueryInsights, Uniquery, AggregateQuery } from '@uniqu/core';
3
-
4
- /**
5
- * Wraps a raw `$select` value and provides lazy-cached conversions
6
- * to the forms different adapters need.
7
- *
8
- * Only instantiated when `$select` is actually provided —
9
- * `controls.$select` is `UniquSelect | undefined`.
10
- *
11
- * For exclusion → inclusion inversion, pass `allFields` (physical field names).
12
- */
13
- declare class UniquSelect {
14
- private static readonly UNRESOLVED;
15
- private _raw;
16
- private _allFields?;
17
- private _array;
18
- private _projection;
19
- private _aggregates;
20
- constructor(raw: UniqueryControls['$select'], allFields?: string[]);
21
- /** Type guard: checks if a value is an AggregateExpr ({$fn, $field}). */
22
- private static _isAggregateExpr;
23
- /**
24
- * Resolved inclusion array of plain field names (strings only).
25
- * AggregateExpr objects are filtered out.
26
- * For exclusion form, inverts using `allFields` from constructor.
27
- */
28
- get asArray(): string[] | undefined;
29
- /**
30
- * Record projection preserving original semantics.
31
- * Returns original object as-is if raw was object.
32
- * Converts `string[]` to `{field: 1}` inclusion object.
33
- * AggregateExpr objects in array form are ignored.
34
- */
35
- get asProjection(): Record<string, 0 | 1> | undefined;
36
- /**
37
- * Extracts AggregateExpr entries from array-form $select.
38
- * Returns undefined if no aggregates present or if $select is object form.
39
- */
40
- get aggregates(): AggregateExpr[] | undefined;
41
- /** Whether the $select contains any AggregateExpr entries. */
42
- get hasAggregates(): boolean;
43
- }
44
-
45
- /** Controls with resolved projection. Used in the adapter interface. */
46
- interface DbControls extends Omit<UniqueryControls, '$select'> {
47
- $select?: UniquSelect;
48
- }
49
- /** Query object with resolved projection. Passed to adapter methods. */
50
- interface DbQuery {
51
- filter: FilterExpr;
52
- controls: DbControls;
53
- /** Pre-computed query insights (field → operators). Adapters may use this to apply query-time behaviour (e.g. collation). */
54
- insights?: UniqueryInsights;
55
- }
56
- /** Describes an available search index exposed by a database adapter. */
57
- interface TSearchIndexInfo {
58
- /** Index name. Empty string or 'DEFAULT' for the default index. */
59
- name: string;
60
- /** Human-readable label for UI display. */
61
- description?: string;
62
- /** Index type: text search or vector similarity search. */
63
- type?: 'text' | 'vector';
64
- }
65
- interface TDbInsertResult {
66
- insertedId: unknown;
67
- }
68
- interface TDbInsertManyResult {
69
- insertedCount: number;
70
- insertedIds: unknown[];
71
- }
72
- interface TDbUpdateResult {
73
- matchedCount: number;
74
- modifiedCount: number;
75
- }
76
- interface TDbDeleteResult {
77
- deletedCount: number;
78
- }
79
- type TDbIndexType = 'plain' | 'unique' | 'fulltext';
80
- interface TDbIndexField {
81
- name: string;
82
- sort: 'asc' | 'desc';
83
- weight?: number;
84
- }
85
- interface TDbIndex {
86
- /** Unique key used for identity/diffing (e.g., "atscript__plain__email") */
87
- key: string;
88
- /** Human-readable index name. */
89
- name: string;
90
- /** Index type. */
91
- type: TDbIndexType;
92
- /** Ordered list of fields in the index. */
93
- fields: TDbIndexField[];
94
- }
95
- type TDbDefaultFn = 'increment' | 'uuid' | 'now';
96
- type TDbCollation = 'binary' | 'nocase' | 'unicode';
97
- type TDbDefaultValue = {
98
- kind: 'value';
99
- value: string;
100
- } | {
101
- kind: 'fn';
102
- fn: TDbDefaultFn;
103
- start?: number;
104
- };
105
- interface TIdDescriptor {
106
- /** Field names that form the primary key. */
107
- fields: string[];
108
- /** Whether this is a composite key (multiple fields). */
109
- isComposite: boolean;
110
- }
111
- type TDbStorageType = 'column' | 'flattened' | 'json';
112
- interface TDbFieldMeta {
113
- /** The dot-notation path to this field (logical name). */
114
- path: string;
115
- /** The annotated type for this field. */
116
- type: TAtscriptAnnotatedType;
117
- /** Physical column/field name (from @db.column, __-separated for flattened, or same as path). */
118
- physicalName: string;
119
- /** Resolved design type: 'string', 'number', 'boolean', 'object', 'json', etc. */
120
- designType: string;
121
- /** Whether the field is optional. */
122
- optional: boolean;
123
- /** Whether this field is part of the primary key (@meta.id). */
124
- isPrimaryKey: boolean;
125
- /** Whether this field is excluded from the DB (@db.ignore). */
126
- ignored: boolean;
127
- /** Default value from @db.default.* */
128
- defaultValue?: TDbDefaultValue;
129
- /**
130
- * How this field is stored in the database.
131
- * - 'column': a standard scalar column (default for primitives)
132
- * - 'flattened': a leaf scalar from a flattened nested object
133
- * - 'json': stored as a single JSON column (arrays, @db.json fields)
134
- */
135
- storage: TDbStorageType;
136
- /**
137
- * For flattened fields: the dot-notation path (same as `path`).
138
- * E.g., for physicalName 'contact__email', this is 'contact.email'.
139
- * Undefined for non-flattened fields.
140
- */
141
- flattenedFrom?: string;
142
- /** Old physical column name from @db.column.renamed (for rename migration). */
143
- renamedFrom?: string;
144
- /** Collation from @db.column.collate (e.g. 'nocase', 'binary', 'unicode'). */
145
- collate?: TDbCollation;
146
- /** Whether this field participates in any index (@db.index.plain, @db.index.unique, @db.index.fulltext). */
147
- isIndexed?: boolean;
148
- /**
149
- * For FK fields: the resolved field metadata of the referenced (target) PK column.
150
- * Adapters use this in `typeMapper` to produce matching DB types for FK columns
151
- * (e.g., `typeMapper(field.fkTargetField)` to inherit the target PK's DB type).
152
- * Undefined for non-FK fields or when the target cannot be resolved.
153
- */
154
- fkTargetField?: TDbFieldMeta;
155
- }
156
- interface TValueFormatterPair {
157
- /** Converts a JS value to storage representation (write + filter paths). */
158
- toStorage: (value: unknown) => unknown;
159
- /** Converts a storage value back to JS representation (read path). */
160
- fromStorage: (value: unknown) => unknown;
161
- }
162
- type TDbReferentialAction = 'cascade' | 'restrict' | 'noAction' | 'setNull' | 'setDefault';
163
- interface TDbForeignKey {
164
- /** FK field names on this table (local columns). */
165
- fields: string[];
166
- /** Target table name (from the chain ref's type @db.table annotation). */
167
- targetTable: string;
168
- /** Target field names on the referenced table. */
169
- targetFields: string[];
170
- /** Lazy reference to the target annotated type (for on-demand table resolution). */
171
- targetTypeRef?: () => TAtscriptAnnotatedType;
172
- /** Alias grouping FK fields (if any). */
173
- alias?: string;
174
- /** Referential action on delete. */
175
- onDelete?: TDbReferentialAction;
176
- /** Referential action on update. */
177
- onUpdate?: TDbReferentialAction;
178
- }
179
- /** Describes an existing column in the database (from introspection). */
180
- interface TExistingColumn {
181
- name: string;
182
- type: string;
183
- notnull: boolean;
184
- pk: boolean;
185
- /** Serialized default value (e.g., "'active'", "NULL"). */
186
- dflt_value?: string;
187
- }
188
- /** Result of comparing desired schema against existing database columns. */
189
- interface TColumnDiff {
190
- added: TDbFieldMeta[];
191
- removed: TExistingColumn[];
192
- renamed: Array<{
193
- field: TDbFieldMeta;
194
- oldName: string;
195
- }>;
196
- typeChanged: Array<{
197
- field: TDbFieldMeta;
198
- existingType: string;
199
- }>;
200
- nullableChanged: Array<{
201
- field: TDbFieldMeta;
202
- wasNullable: boolean;
203
- }>;
204
- defaultChanged: Array<{
205
- field: TDbFieldMeta;
206
- oldDefault?: string;
207
- newDefault?: string;
208
- }>;
209
- conflicts: Array<{
210
- field: TDbFieldMeta;
211
- oldName: string;
212
- conflictsWith: string;
213
- }>;
214
- }
215
- /** Result of applying column diff to the database. */
216
- interface TSyncColumnResult {
217
- added: string[];
218
- renamed: string[];
219
- }
220
- /** A single table-level option in unified key-value format. */
221
- interface TExistingTableOption {
222
- key: string;
223
- value: string;
224
- }
225
- /** Result of comparing desired table options against existing ones. */
226
- interface TTableOptionDiff {
227
- changed: Array<{
228
- key: string;
229
- oldValue: string;
230
- newValue: string;
231
- /** Whether applying this change requires dropping and recreating the table. */
232
- destructive: boolean;
233
- }>;
234
- }
235
- /**
236
- * Adapter-provided metadata adjustments applied atomically during the
237
- * build pipeline, before field descriptors are built.
238
- *
239
- * Replaces the old pattern where adapters mutated metadata via
240
- * back-references (`this._table.addPrimaryKey()`, etc.).
241
- */
242
- interface TMetadataOverrides {
243
- /** Fields to add as primary keys. */
244
- addPrimaryKeys?: string[];
245
- /** Fields to remove from primary keys. */
246
- removePrimaryKeys?: string[];
247
- /** Fields to register as having a unique constraint. */
248
- addUniqueFields?: string[];
249
- /** Synthetic fields to inject into flatMap (e.g. MongoDB's `_id`). */
250
- injectFields?: Array<{
251
- path: string;
252
- type: TAtscriptAnnotatedType;
253
- }>;
254
- }
255
- /**
256
- * Callback that resolves an annotated type to a queryable table instance.
257
- * Required for `$with` relation loading — each table needs to query related tables.
258
- *
259
- * Typically provided by the driver/registry (e.g. `DbSpace.getTable`).
260
- */
261
- type TTableResolver = (type: TAtscriptAnnotatedType) => Pick<AtscriptDbTableLike, 'findMany' | 'loadRelations' | 'primaryKeys' | 'relations' | 'foreignKeys'> | undefined;
262
- /** Minimal table interface used by the table resolver. Avoids circular dependency with AtscriptDbTable. */
263
- interface AtscriptDbTableLike {
264
- findMany(query: unknown): Promise<Array<Record<string, unknown>>>;
265
- loadRelations(rows: Array<Record<string, unknown>>, withRelations: WithRelation[]): Promise<void>;
266
- primaryKeys: readonly string[];
267
- relations: ReadonlyMap<string, TDbRelation>;
268
- foreignKeys: ReadonlyMap<string, TDbForeignKey>;
269
- }
270
- /** Minimal writable table interface for nested creation/update. */
271
- interface AtscriptDbWritable {
272
- insertOne(payload: Record<string, unknown>, opts?: {
273
- maxDepth?: number;
274
- }): Promise<TDbInsertResult>;
275
- insertMany(payloads: Array<Record<string, unknown>>, opts?: {
276
- maxDepth?: number;
277
- _depth?: number;
278
- }): Promise<TDbInsertManyResult>;
279
- replaceOne(payload: Record<string, unknown>, opts?: {
280
- maxDepth?: number;
281
- }): Promise<TDbUpdateResult>;
282
- bulkReplace(payloads: Array<Record<string, unknown>>, opts?: {
283
- maxDepth?: number;
284
- _depth?: number;
285
- }): Promise<TDbUpdateResult>;
286
- updateOne(payload: Record<string, unknown>, opts?: {
287
- maxDepth?: number;
288
- }): Promise<TDbUpdateResult>;
289
- bulkUpdate(payloads: Array<Record<string, unknown>>, opts?: {
290
- maxDepth?: number;
291
- _depth?: number;
292
- }): Promise<TDbUpdateResult>;
293
- findOne(query: unknown): Promise<Record<string, unknown> | null>;
294
- count(query: {
295
- filter: Record<string, unknown>;
296
- }): Promise<number>;
297
- deleteMany(filter: unknown): Promise<TDbDeleteResult>;
298
- /** Pre-validate items (type + FK constraints) without inserting them. */
299
- preValidateItems(items: Array<Record<string, unknown>>, opts?: {
300
- excludeFkTargetTable?: string;
301
- }): Promise<void>;
302
- }
303
- /**
304
- * Callback that resolves an annotated type to a writable table instance.
305
- * Used for nested creation — inserting related records inline.
306
- */
307
- type TWriteTableResolver = (type: TAtscriptAnnotatedType) => (AtscriptDbTableLike & AtscriptDbWritable) | undefined;
308
- /**
309
- * A child table that may need cascade/setNull processing when a parent is deleted.
310
- * Returned by the cascade resolver.
311
- */
312
- interface TCascadeTarget {
313
- /** FK on the child table that references the parent being deleted. */
314
- fk: TDbForeignKey;
315
- /** Name of the child table that holds this FK. */
316
- childTable: string;
317
- /** Delete matching child records (goes through AtscriptDbTable for recursive cascade). */
318
- deleteMany(filter: Record<string, unknown>): Promise<TDbDeleteResult>;
319
- /** Update matching child records (for setNull — sets FK fields to null). */
320
- updateMany(filter: Record<string, unknown>, data: Record<string, unknown>): Promise<TDbUpdateResult>;
321
- /** Count matching child records (for restrict — check existence before delete). */
322
- count(filter: Record<string, unknown>): Promise<number>;
323
- }
324
- /**
325
- * Callback that finds all child tables with FKs pointing to a given parent table.
326
- * Used by AtscriptDbTable to implement application-level cascade deletes.
327
- */
328
- type TCascadeResolver = (tableName: string) => TCascadeTarget[];
329
- /**
330
- * Minimal interface for querying a target table during FK validation.
331
- * Only `count` is needed — we check if the referenced record exists.
332
- */
333
- interface TFkLookupTarget {
334
- count(filter: Record<string, unknown>): Promise<number>;
335
- }
336
- /**
337
- * Callback that resolves a table name to a queryable target for FK validation.
338
- * Returns undefined if the target table is not registered in the space.
339
- */
340
- type TFkLookupResolver = (tableName: string) => TFkLookupTarget | undefined;
341
- interface TDbRelation {
342
- /** Direction: 'to' (FK is local), 'from' (FK is remote), or 'via' (M:N junction). */
343
- direction: 'to' | 'from' | 'via';
344
- /** The alias used for pairing (if any). */
345
- alias?: string;
346
- /** Target type's annotated type reference. */
347
- targetType: () => TAtscriptAnnotatedType;
348
- /** Whether this is an array relation (one-to-many). */
349
- isArray: boolean;
350
- /** Junction type reference for 'via' (M:N) relations. */
351
- viaType?: () => TAtscriptAnnotatedType;
352
- }
353
-
354
- interface TGenericLogger {
355
- error(...messages: any[]): void;
356
- warn(...messages: any[]): void;
357
- log(...messages: any[]): void;
358
- info(...messages: any[]): void;
359
- debug(...messages: any[]): void;
360
- }
361
-
362
- /**
363
- * Computed metadata for a database table or view.
364
- *
365
- * Contains all field metadata, physical mapping indexes, relation definitions,
366
- * and constraint information derived from Atscript annotations. Built lazily
367
- * on first access via {@link build}, then immutable.
368
- *
369
- * This class owns the build pipeline that was previously part of
370
- * `AtscriptDbReadable._flatten()`. The Readable delegates all metadata
371
- * access to this class.
372
- */
373
- declare class TableMetadata {
374
- readonly nestedObjects: boolean;
375
- flatMap: Map<string, TAtscriptAnnotatedType>;
376
- fieldDescriptors: readonly TDbFieldMeta[];
377
- primaryKeys: string[];
378
- originalMetaIdFields: string[];
379
- indexes: Map<string, TDbIndex>;
380
- foreignKeys: Map<string, TDbForeignKey>;
381
- relations: Map<string, TDbRelation>;
382
- navFields: Set<string>;
383
- ignoredFields: Set<string>;
384
- uniqueProps: Set<string>;
385
- defaults: Map<string, TDbDefaultValue>;
386
- columnMap: Map<string, string>;
387
- dimensions: string[];
388
- measures: string[];
389
- pathToPhysical: Map<string, string>;
390
- physicalToPath: Map<string, string>;
391
- flattenedParents: Set<string>;
392
- jsonFields: Set<string>;
393
- selectExpansion: Map<string, string[]>;
394
- booleanFields: Set<string>;
395
- decimalFields: Set<string>;
396
- allPhysicalFields: string[];
397
- requiresMappings: boolean;
398
- toStorageFormatters?: Map<string, (value: unknown) => unknown>;
399
- fromStorageFormatters?: Map<string, (value: unknown) => unknown>;
400
- /** Leaf field descriptors indexed by physical column name (read path). */
401
- leafByPhysical: Map<string, TDbFieldMeta>;
402
- /** Leaf field descriptors indexed by logical path (write/patch/filter paths). */
403
- leafByLogical: Map<string, TDbFieldMeta>;
404
- private _built;
405
- private _collateMap;
406
- private _columnFromMap;
407
- constructor(nestedObjects: boolean);
408
- get isBuilt(): boolean;
409
- /**
410
- * Runs the full metadata compilation pipeline. Called once by
411
- * `AtscriptDbReadable._ensureBuilt()` on first metadata access.
412
- *
413
- * Pipeline steps:
414
- * 1. `adapter.onBeforeFlatten(type)` — adapter hook
415
- * 2. `flattenAnnotatedType()` — collect field tuples, detect nav fields eagerly
416
- * 3. Replay non-nav-descendant tuples through annotation scanning + adapter.onFieldScanned
417
- * 4. Classify fields and build path maps (skipped for nested-objects adapters)
418
- * 5. `adapter.getMetadataOverrides()` → `_applyOverrides()` (PK/unique/inject adjustments)
419
- * 6. Build field descriptors (TDbFieldMeta[])
420
- * 7. Build leaf field indexes (skipped for nested-objects adapters)
421
- * 8. Finalize indexes (resolve field names to physical)
422
- * 9. `adapter.onAfterFlatten()` — adapter hook (read-only bookkeeping)
423
- * 10. Build allPhysicalFields list
424
- */
425
- build(type: TAtscriptAnnotatedType<TAtscriptTypeObject>, adapter: BaseDbAdapter, logger: TGenericLogger): void;
426
- /**
427
- * Applies adapter-provided metadata overrides atomically.
428
- * Processing order: injectFields → removePrimaryKeys → addPrimaryKeys → addUniqueFields.
429
- */
430
- private _applyOverrides;
431
- /**
432
- * Scans `@db.*` and `@meta.id` annotations on a field during flattening.
433
- */
434
- private _scanGenericAnnotations;
435
- private _addIndexField;
436
- /**
437
- * Classifies each field as column, flattened, json, or parent-object.
438
- * Builds the bidirectional pathToPhysical / physicalToPath maps.
439
- */
440
- private _classifyFields;
441
- /** Returns the `__`-separated parent prefix for a dot-separated path, or empty string for top-level paths. */
442
- private _flattenedPrefix;
443
- /**
444
- * Indexes `fieldDescriptors` into two lookup maps for unified
445
- * read/write field classification in the RelationalFieldMapper.
446
- */
447
- private _buildLeafIndexes;
448
- /**
449
- * Builds field descriptors, physical-name lookup, and value formatters.
450
- * Called once during build() — everything it needs
451
- * (flatMap, indexes, columnMap, etc.) is already populated.
452
- */
453
- private _buildFieldDescriptors;
454
- /**
455
- * Resolves `fkTargetField` for FK fields in field descriptors.
456
- */
457
- private _resolveFkTargetFields;
458
- private _finalizeIndexes;
459
- }
460
-
461
- /**
462
- * Strategy for mapping data between logical field shapes and physical storage.
463
- * Two implementations: {@link DocumentFieldMapper} (nested objects, NoSQL)
464
- * and `RelationalFieldMapper` (flattened columns, SQL).
465
- */
466
- declare abstract class FieldMappingStrategy {
467
- abstract reconstructFromRead(row: Record<string, unknown>, meta: TableMetadata): Record<string, unknown>;
468
- abstract translateQuery(query: Uniquery, meta: TableMetadata): DbQuery;
469
- abstract translateAggregateQuery(query: AggregateQuery, meta: TableMetadata): DbQuery;
470
- /**
471
- * Recursively walks a filter expression, applying adapter-specific value
472
- * formatting via `formatFilterValue`. Shared by both document and relational
473
- * mappers (relational adds key-renaming via `translateFilterWithRename`).
474
- */
475
- translateFilter(filter: FilterExpr, meta: TableMetadata): FilterExpr;
476
- abstract prepareForWrite(payload: Record<string, unknown>, meta: TableMetadata, adapter: BaseDbAdapter): Record<string, unknown>;
477
- abstract translatePatchKeys(update: Record<string, unknown>, meta: TableMetadata): Record<string, unknown>;
478
- /**
479
- * Coerces field values from storage representation to JS types
480
- * (booleans from 0/1, decimals from number to string).
481
- */
482
- protected coerceFieldValues(row: Record<string, unknown>, meta: TableMetadata): Record<string, unknown>;
483
- /**
484
- * Applies adapter-specific fromStorage formatting to a row read from the database.
485
- * Converts storage representations back to JS values (e.g. Date → epoch ms).
486
- */
487
- protected applyFromStorageFormatters(row: Record<string, unknown>, meta: TableMetadata): Record<string, unknown>;
488
- /**
489
- * Sets a value at a dot-notation path, creating intermediate objects as needed.
490
- */
491
- protected setNestedValue(obj: Record<string, unknown>, dotPath: string, value: unknown): void;
492
- /**
493
- * If all children of a flattened parent are null, collapse the parent to null.
494
- */
495
- protected reconstructNullParent(obj: Record<string, unknown>, parentPath: string, meta: TableMetadata): void;
496
- /**
497
- * Applies adapter-specific value formatting to a single filter value.
498
- * Handles direct values, operator objects ({$gt: v}), and $in/$nin arrays.
499
- */
500
- protected formatFilterValue(physicalName: string, value: unknown, meta: TableMetadata): unknown;
501
- /**
502
- * Applies adapter-specific value formatting to prepared (physical-named) data.
503
- */
504
- protected formatWriteValues(data: Record<string, unknown>, meta: TableMetadata): Record<string, unknown>;
505
- /**
506
- * Prepares primary key values and strips ignored fields.
507
- * Shared pre-processing for both document and relational write paths.
508
- */
509
- protected prepareCommon(data: Record<string, unknown>, meta: TableMetadata, adapter: BaseDbAdapter): void;
510
- }
511
-
512
- /**
513
- * Extracts nav prop names from a query's `$with` array.
514
- * Returns `never` when `$with` is absent → all nav props stripped from response.
515
- */
516
- type ExtractWith<Q> = Q extends {
517
- controls: {
518
- $with: Array<{
519
- name: infer N extends string;
520
- }>;
521
- };
522
- } ? N : never;
523
- /**
524
- * Computes the response type for a query:
525
- * - Strips all nav props from the base DataType
526
- * - Adds back only the nav props requested via `$with`
527
- *
528
- * When no `$with` is provided, result is `Omit<DataType, keyof NavType>`.
529
- * When `$with: [{ name: 'author' }]`, result includes `author` from DataType.
530
- * When the query type is not a literal (e.g. a variable typed as `Uniquery`),
531
- * falls back to `DataType` (all nav props optional, as declared).
532
- */
533
- type DbResponse<Data, Nav, Q> = [
534
- keyof Nav
535
- ] extends [never] ? Data : Omit<Data, keyof Nav & string> & Pick<Data, ExtractWith<Q> & keyof Data & string>;
536
- /**
537
- * Shared read-only database abstraction driven by Atscript annotations.
538
- *
539
- * Contains all field metadata computation, read operations, query translation,
540
- * relation loading, and result reconstruction. Extended by both
541
- * {@link AtscriptDbTable} (adds write operations) and {@link AtscriptDbView}
542
- * (adds view plan/DDL).
543
- */
544
- declare class AtscriptDbReadable<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType, DataType = TAtscriptDataType<T>, FlatType = FlatOf<T>, A extends BaseDbAdapter = BaseDbAdapter, IdType = PrimaryKeyOf<T>, OwnProps = OwnPropsOf<T>, NavType extends Record<string, unknown> = NavPropsOf<T>> {
545
- protected readonly _type: T;
546
- protected readonly adapter: A;
547
- protected readonly logger: TGenericLogger;
548
- protected readonly _tableResolver?: TTableResolver | undefined;
549
- /** Resolved table/collection/view name. */
550
- readonly tableName: string;
551
- /** Database schema/namespace from `@db.schema` (if set). */
552
- readonly schema: string | undefined;
553
- /** Sync method from `@db.sync.method` ('drop' | 'recreate' | undefined). */
554
- protected readonly _syncMethod: 'drop' | 'recreate' | undefined;
555
- /** Previous table/view name from `@db.table.renamed` or `@db.view.renamed`. */
556
- readonly renamedFrom: string | undefined;
557
- /** Computed metadata for this table/view. Built lazily on first access. */
558
- protected readonly _meta: TableMetadata;
559
- /** Strategy for mapping between logical field shapes and physical storage. */
560
- protected readonly _fieldMapper: FieldMappingStrategy;
561
- protected _writeTableResolver?: TWriteTableResolver;
562
- constructor(_type: T, adapter: A, logger?: TGenericLogger, _tableResolver?: TTableResolver | undefined);
563
- /** Ensures metadata is built. Called before any metadata access. */
564
- protected _ensureBuilt(): void;
565
- /** Whether this readable is a view (overridden in AtscriptDbView). */
566
- get isView(): boolean;
567
- /** Returns the underlying adapter with its concrete type preserved. */
568
- getAdapter(): A;
569
- /** The raw annotated type. */
570
- get type(): TAtscriptAnnotatedType<TAtscriptTypeObject>;
571
- /** Lazily-built flat map of all fields (dot-notation paths → annotated types). */
572
- get flatMap(): Map<string, TAtscriptAnnotatedType>;
573
- /** All computed indexes from `@db.index.*` annotations. */
574
- get indexes(): Map<string, TDbIndex>;
575
- /** Primary key field names from `@meta.id`. */
576
- get primaryKeys(): readonly string[];
577
- /** Original `@meta.id` field names as declared in the schema (before adapter manipulation). */
578
- get originalMetaIdFields(): readonly string[];
579
- /** Dimension fields from `@db.column.dimension`. */
580
- get dimensions(): readonly string[];
581
- /** Measure fields from `@db.column.measure`. */
582
- get measures(): readonly string[];
583
- /** Sync method for structural changes: 'drop' (lossy), 'recreate' (lossless), or undefined (manual). */
584
- get syncMethod(): 'drop' | 'recreate' | undefined;
585
- /** Logical → physical column name mapping from `@db.column`. */
586
- get columnMap(): ReadonlyMap<string, string>;
587
- /** Default values from `@db.default.*`. */
588
- get defaults(): ReadonlyMap<string, TDbDefaultValue>;
589
- /** Fields excluded from DB via `@db.ignore`. */
590
- get ignoredFields(): ReadonlySet<string>;
591
- /** Navigational fields (`@db.rel.to` / `@db.rel.from`) — not stored as columns. */
592
- get navFields(): ReadonlySet<string>;
593
- /** Single-field unique index properties. */
594
- get uniqueProps(): ReadonlySet<string>;
595
- /** Foreign key constraints from `@db.rel.FK` annotations. */
596
- get foreignKeys(): ReadonlyMap<string, TDbForeignKey>;
597
- /** Navigational relation metadata from `@db.rel.to` / `@db.rel.from`. */
598
- get relations(): ReadonlyMap<string, TDbRelation>;
599
- /** The underlying database adapter instance. */
600
- get dbAdapter(): A;
601
- /**
602
- * Enables or disables verbose (debug-level) DB call logging for this table/view.
603
- * When disabled (default), no log strings are constructed — zero overhead.
604
- */
605
- setVerbose(enabled: boolean): void;
606
- /** Precomputed logical dot-path → physical column name map. */
607
- get pathToPhysical(): ReadonlyMap<string, string>;
608
- /** Precomputed physical column name → logical dot-path map (inverse). */
609
- get physicalToPath(): ReadonlyMap<string, string>;
610
- /** Descriptor for the primary ID field(s). */
611
- getIdDescriptor(): TIdDescriptor;
612
- /**
613
- * Pre-computed field metadata for adapter use.
614
- */
615
- get fieldDescriptors(): readonly TDbFieldMeta[];
616
- /**
617
- * Creates a new validator with custom options.
618
- */
619
- createValidator(opts?: Partial<TValidatorOptions>): Validator<T, DataType>;
620
- /**
621
- * Finds a single record matching the query.
622
- * The return type automatically excludes nav props unless they are
623
- * explicitly requested via `$with`.
624
- */
625
- findOne<Q extends Uniquery<OwnProps, NavType>>(query: Q): Promise<DbResponse<DataType, NavType, Q> | null>;
626
- /**
627
- * Finds all records matching the query.
628
- * The return type automatically excludes nav props unless they are
629
- * explicitly requested via `$with`.
630
- */
631
- findMany<Q extends Uniquery<OwnProps, NavType>>(query: Q): Promise<Array<DbResponse<DataType, NavType, Q>>>;
632
- /**
633
- * Counts records matching the query.
634
- */
635
- count(query?: Uniquery<OwnProps, NavType>): Promise<number>;
636
- /**
637
- * Finds records and total count in a single logical call.
638
- */
639
- findManyWithCount<Q extends Uniquery<OwnProps, NavType>>(query: Q): Promise<{
640
- data: Array<DbResponse<DataType, NavType, Q>>;
641
- count: number;
642
- }>;
643
- /**
644
- * Executes an aggregate query with GROUP BY and aggregate functions.
645
- *
646
- * Validates:
647
- * - Plain fields in $select are a subset of $groupBy
648
- * - When dimensions/measures are defined (strict mode): $groupBy fields
649
- * must be dimensions, aggregate $field values must be measures (or '*')
650
- *
651
- * Translates field names, delegates to adapter.aggregate(),
652
- * then reverse-maps and applies fromStorage formatters on results.
653
- */
654
- aggregate(query: AggregateQuery): Promise<Array<Record<string, unknown>>>;
655
- /** Whether the underlying adapter supports text search. */
656
- isSearchable(): boolean;
657
- /** Returns available search indexes from the adapter. */
658
- getSearchIndexes(): TSearchIndexInfo[];
659
- /**
660
- * Full-text search with query translation and result reconstruction.
661
- */
662
- search<Q extends Uniquery<OwnProps, NavType>>(text: string, query: Q, indexName?: string): Promise<Array<DbResponse<DataType, NavType, Q>>>;
663
- /**
664
- * Full-text search with count for paginated search results.
665
- */
666
- searchWithCount<Q extends Uniquery<OwnProps, NavType>>(text: string, query: Q, indexName?: string): Promise<{
667
- data: Array<DbResponse<DataType, NavType, Q>>;
668
- count: number;
669
- }>;
670
- /** Whether the underlying adapter supports vector similarity search. */
671
- isVectorSearchable(): boolean;
672
- /**
673
- * Vector similarity search with query translation and result reconstruction.
674
- *
675
- * Overloads:
676
- * - `vectorSearch(vector, query?)` — uses default vector index
677
- * - `vectorSearch(indexName, vector, query?)` — targets a specific vector index
678
- */
679
- vectorSearch<Q extends Uniquery<OwnProps, NavType>>(vectorOrIndex: number[] | string, maybeVectorOrQuery?: number[] | Q, maybeQuery?: Q): Promise<Array<DbResponse<DataType, NavType, Q>>>;
680
- /**
681
- * Vector similarity search with count for paginated results.
682
- *
683
- * Overloads:
684
- * - `vectorSearchWithCount(vector, query?)` — uses default vector index
685
- * - `vectorSearchWithCount(indexName, vector, query?)` — targets a specific vector index
686
- */
687
- vectorSearchWithCount<Q extends Uniquery<OwnProps, NavType>>(vectorOrIndex: number[] | string, maybeVectorOrQuery?: number[] | Q, maybeQuery?: Q): Promise<{
688
- data: Array<DbResponse<DataType, NavType, Q>>;
689
- count: number;
690
- }>;
691
- /** Resolves overloaded vector search arguments into canonical form. */
692
- private _resolveVectorSearchArgs;
693
- /**
694
- * Finds a single record by any type-compatible identifier — primary key
695
- * or single-field unique index.
696
- * The return type excludes nav props unless `$with` is provided in controls.
697
- *
698
- * ```typescript
699
- * // Without relations — nav props stripped from result
700
- * const user = await table.findById('123')
701
- *
702
- * // With relations — only requested nav props appear
703
- * const user = await table.findById('123', { controls: { $with: [{ name: 'posts' }] } })
704
- * ```
705
- */
706
- findById<Q extends {
707
- controls?: UniqueryControls<OwnProps, NavType>;
708
- } = Record<string, never>>(id: IdType, query?: Q): Promise<DbResponse<DataType, NavType, Q> | null>;
709
- /**
710
- * Resolves an id value into a filter expression.
711
- */
712
- protected _resolveIdFilter(id: unknown): FilterExpr | null;
713
- /**
714
- * Attempts to build a single-field filter `{ field: preparedId }`.
715
- */
716
- private _tryFieldFilter;
717
- /**
718
- * Public entry point for relation loading. Used by adapters for nested $with delegation.
719
- */
720
- loadRelations(rows: Array<Record<string, unknown>>, withRelations: WithRelation[]): Promise<void>;
721
- /**
722
- * Finds the FK entry that connects a `@db.rel.to` relation to its target.
723
- * Thin wrapper — delegates to relation-loader for shared use with db-table.ts write path.
724
- */
725
- protected _findFKForRelation(relation: TDbRelation): {
726
- localFields: string[];
727
- targetFields: string[];
728
- } | undefined;
729
- /**
730
- * Finds a FK on a remote table that points back to this table.
731
- * Thin wrapper — delegates to relation-loader for shared use with db-table.ts write path.
732
- */
733
- protected _findRemoteFK(targetTable: {
734
- foreignKeys: ReadonlyMap<string, TDbForeignKey>;
735
- }, thisTableName: string, alias?: string): TDbForeignKey | undefined;
736
- }
737
-
738
- /**
739
- * Abstract base class for database adapters.
740
- *
741
- * Adapter instances are 1:1 with readable instances (tables or views).
742
- * When an {@link AtscriptDbReadable} is created with an adapter, it calls
743
- * {@link registerReadable} to establish a bidirectional relationship:
744
- *
745
- * ```
746
- * AtscriptDbReadable ──delegates ops──▶ BaseDbAdapter
747
- * ◀──reads metadata── (via this._table)
748
- * ```
749
- *
750
- * Adapter authors can access all computed metadata through `this._table`:
751
- * - `this._table.tableName` — resolved table/collection/view name
752
- * - `this._table.flatMap` — all fields as dot-notation paths
753
- * - `this._table.indexes` — computed index definitions
754
- * - `this._table.primaryKeys` — primary key field names
755
- * - `this._table.columnMap` — logical → physical column mappings
756
- * - `this._table.defaults` — default value configurations
757
- * - `this._table.ignoredFields` — fields excluded from DB
758
- * - `this._table.uniqueProps` — single-field unique index properties
759
- * - `this._table.isView` — whether this is a view (vs a table)
760
- */
761
- declare abstract class BaseDbAdapter {
762
- protected _table: AtscriptDbReadable<any, any, any, any, any, any, any>;
763
- private _metaIdPhysical;
764
- /**
765
- * Returns the physical column name of the single @meta.id field (if any).
766
- * Used to return the user's logical ID instead of the DB-generated ID on insert.
767
- */
768
- protected _getMetaIdPhysical(): string | null;
769
- /**
770
- * Resolves the correct insertedId: prefers the user-supplied PK value
771
- * from the data over the DB-generated fallback (e.g. rowid, _id).
772
- */
773
- protected _resolveInsertedId(data: Record<string, unknown>, dbGeneratedId: unknown): unknown;
774
- /** Logger instance — set via {@link registerReadable} from the readable's logger. */
775
- protected logger: TGenericLogger;
776
- /** When true, adapter logs DB calls via `logger.debug`. Off by default. */
777
- protected _verbose: boolean;
778
- /**
779
- * Called by {@link AtscriptDbReadable} constructor. Gives the adapter access
780
- * to the readable's computed metadata for internal use in query rendering,
781
- * index sync, etc.
782
- */
783
- registerReadable(readable: AtscriptDbReadable<any, any, any, any, any, any, any>, logger?: TGenericLogger): void;
784
- /**
785
- * Enables or disables verbose (debug-level) logging for this adapter.
786
- * When disabled, no log strings are constructed — zero overhead.
787
- */
788
- setVerbose(enabled: boolean): void;
789
- /**
790
- * Logs a debug message if verbose mode is enabled.
791
- * Adapters call this to log DB operations with zero overhead when disabled.
792
- */
793
- protected _log(...args: unknown[]): void;
794
- /**
795
- * Runs `fn` inside a database transaction. Nested calls (from related tables
796
- * within the same async chain) reuse the existing transaction automatically.
797
- *
798
- * The generic layer handles nesting detection via `AsyncLocalStorage`.
799
- * Adapters override `_beginTransaction`, `_commitTransaction`, and
800
- * `_rollbackTransaction` to provide raw DB-specific transaction primitives.
801
- */
802
- withTransaction<T>(fn: () => Promise<T>): Promise<T>;
803
- /**
804
- * Returns the opaque transaction state from the current async context.
805
- * Adapters use this to retrieve DB-specific state (e.g., MongoDB `ClientSession`).
806
- */
807
- protected _getTransactionState(): unknown;
808
- /**
809
- * Runs `fn` inside the transaction ALS context with the given state.
810
- * Adapters that override `withTransaction` (e.g., to use MongoDB's
811
- * `session.withTransaction()` Convenient API) use this to set up the
812
- * shared context so that nested adapters see the same session.
813
- * If a context already exists (nesting), it's reused.
814
- */
815
- protected _runInTransactionContext<T>(state: unknown, fn: () => Promise<T>): Promise<T>;
816
- /**
817
- * Starts a raw transaction. Returns opaque state stored in the async context.
818
- * Override in adapters that support transactions.
819
- */
820
- protected _beginTransaction(): Promise<unknown>;
821
- /** Commits the raw transaction. Override in adapters that support transactions. */
822
- protected _commitTransaction(_state: unknown): Promise<void>;
823
- /** Rolls back the raw transaction. Override in adapters that support transactions. */
824
- protected _rollbackTransaction(_state: unknown): Promise<void>;
825
- /**
826
- * Returns additional validator plugins for this adapter.
827
- * These are merged with the built-in Atscript validators.
828
- *
829
- * Example: MongoDB adapter returns ObjectId validation plugin.
830
- */
831
- getValidatorPlugins(): TValidatorPlugin[];
832
- /**
833
- * Transforms an ID value for the database.
834
- * Override to convert string → ObjectId, parse numeric IDs, etc.
835
- *
836
- * @param id - The raw ID value.
837
- * @param fieldType - The annotated type of the ID field.
838
- * @returns The transformed ID value.
839
- */
840
- prepareId(id: unknown, fieldType: TAtscriptAnnotatedType): unknown;
841
- /**
842
- * Whether this adapter supports native patch operations.
843
- * When `true`, {@link AtscriptDbTable} delegates patch payloads to
844
- * {@link nativePatch} instead of using the generic decomposition.
845
- */
846
- supportsNativePatch(): boolean;
847
- /**
848
- * Whether this adapter handles nested objects natively.
849
- * When `true`, the generic layer skips flattening and
850
- * passes nested objects as-is to the adapter.
851
- * MongoDB returns `true`; relational adapters return `false` (default).
852
- */
853
- supportsNestedObjects(): boolean;
854
- /**
855
- * Whether the DB engine handles static `@db.default "value"` natively
856
- * via column-level DEFAULT clauses in CREATE TABLE.
857
- * When `true`, `_applyDefaults()` skips client-side value defaults,
858
- * letting the DB apply its own DEFAULT. SQL adapters return `true`;
859
- * document stores (MongoDB) return `false` and apply defaults client-side.
860
- */
861
- supportsNativeValueDefaults(): boolean;
862
- /**
863
- * Function default names handled natively by this adapter's DB engine.
864
- * Fields with these defaults are omitted from INSERT when no value is provided,
865
- * letting the DB apply its own DEFAULT expression (e.g. CURRENT_TIMESTAMP, UUID()).
866
- *
867
- * Override in adapters whose DB engine supports function defaults.
868
- * The generic layer checks this in `_applyDefaults()` to decide whether
869
- * to generate the value client-side or leave it for the DB.
870
- */
871
- nativeDefaultFns(): ReadonlySet<TDbDefaultFn>;
872
- /**
873
- * Whether this adapter enforces foreign key constraints natively.
874
- * When `true`, the generic layer skips application-level cascade/setNull
875
- * on delete — the DB engine handles it (e.g. SQLite `ON DELETE CASCADE`).
876
- * When `false` (default), the generic layer implements cascade logic
877
- * by finding child records and deleting/nullifying them before the parent.
878
- */
879
- supportsNativeForeignKeys(): boolean;
880
- /**
881
- * Whether this adapter handles `$with` relation loading natively.
882
- * When `true`, the table layer delegates to {@link loadRelations}
883
- * instead of using the generic batch-loading strategy.
884
- *
885
- * Adapters can use this to implement SQL JOINs, MongoDB `$lookup`,
886
- * or other DB-native relation loading optimizations.
887
- *
888
- * Default: `false` — the table layer uses application-level batch loading.
889
- */
890
- supportsNativeRelations(): boolean;
891
- /**
892
- * Loads relations onto result rows using adapter-native operations.
893
- * Only called when {@link supportsNativeRelations} returns `true`.
894
- *
895
- * The adapter receives the rows to enrich, the `$with` relation specs,
896
- * and the table's relation/FK metadata for resolution.
897
- *
898
- * @param rows - The result rows to enrich (mutable — add relation properties in place).
899
- * @param withRelations - The `$with` specs from the query.
900
- * @param relations - This table's relation metadata (from `@db.rel.to`/`@db.rel.from`).
901
- * @param foreignKeys - This table's FK metadata (from `@db.rel.FK`).
902
- * @param tableResolver - Optional callback to resolve annotated types to table metadata (needed for FROM/VIA relations).
903
- */
904
- loadRelations(rows: Array<Record<string, unknown>>, withRelations: WithRelation[], relations: ReadonlyMap<string, TDbRelation>, foreignKeys: ReadonlyMap<string, TDbForeignKey>, tableResolver?: TTableResolver): Promise<void>;
905
- /**
906
- * Applies a patch payload using native database operations.
907
- * Only called when {@link supportsNativePatch} returns `true`.
908
- *
909
- * @param filter - Filter identifying the record to patch.
910
- * @param patch - The patch payload with array operations.
911
- * @returns Update result.
912
- */
913
- nativePatch(filter: FilterExpr, patch: unknown): Promise<TDbUpdateResult>;
914
- /**
915
- * Called before field flattening begins.
916
- * Use to extract table-level adapter-specific annotations.
917
- *
918
- * Example: MongoDB adapter extracts `@db.mongo.search.dynamic`.
919
- */
920
- onBeforeFlatten?(type: TAtscriptAnnotatedType): void;
921
- /**
922
- * Called for each non-nav-descendant field during the build pipeline.
923
- * Fields nested under navigation relations (`@db.rel.to/from/via`) are
924
- * never delivered to this callback — adapters do not need to filter them.
925
- *
926
- * Use to extract field-level adapter-specific annotations.
927
- * Example: MongoDB adapter extracts `@db.mongo.search.vector`, `@db.mongo.search.text`.
928
- */
929
- onFieldScanned?(field: string, type: TAtscriptAnnotatedType, metadata: TMetadataMap<AtscriptMetadata>): void;
930
- /**
931
- * Returns metadata overrides applied during the build pipeline.
932
- * Called after field scanning/classification, before field descriptors are built.
933
- *
934
- * Use this to adjust primary keys, inject synthetic fields, or register
935
- * unique constraints — instead of mutating metadata via back-references.
936
- *
937
- * @param meta - The table metadata (direct reference, not through readable getters).
938
- */
939
- getMetadataOverrides?(meta: TableMetadata): TMetadataOverrides | undefined;
940
- /**
941
- * Called after all fields are scanned.
942
- * Use to finalize adapter-specific computed state.
943
- * Access table metadata via `this._table`.
944
- */
945
- onAfterFlatten?(): void;
946
- /**
947
- * Returns an adapter-specific table name.
948
- * For example, MongoDB reads from `@db.mongo.collection`.
949
- * Return `undefined` to fall back to `@db.table` or the interface name.
950
- */
951
- getAdapterTableName?(type: TAtscriptAnnotatedType): string | undefined;
952
- /**
953
- * Returns the metadata tag used to mark top-level arrays during flattening.
954
- * Default: `'db.__topLevelArray'`
955
- *
956
- * Override to use adapter-specific tags (e.g., `'db.mongo.__topLevelArray'`).
957
- */
958
- getTopLevelArrayTag?(): string;
959
- /**
960
- * Resolves the full table name, optionally including the schema prefix.
961
- * Override for databases that don't support schemas (e.g., SQLite).
962
- *
963
- * @param includeSchema - Whether to prepend `schema.` prefix (default: true).
964
- */
965
- resolveTableName(includeSchema?: boolean): string;
966
- /**
967
- * Template method for index synchronization.
968
- * Implements the diff algorithm (list → compare → create/drop).
969
- * Adapters provide the three DB-specific primitives.
970
- *
971
- * @example
972
- * ```typescript
973
- * async syncIndexes() {
974
- * await this.syncIndexesWithDiff({
975
- * listExisting: async () => this.driver.all('PRAGMA index_list(...)'),
976
- * createIndex: async (index) => this.driver.exec('CREATE INDEX ...'),
977
- * dropIndex: async (name) => this.driver.exec('DROP INDEX ...'),
978
- * shouldSkipType: (type) => type === 'fulltext',
979
- * })
980
- * }
981
- * ```
982
- */
983
- protected syncIndexesWithDiff(opts: {
984
- listExisting(): Promise<Array<{
985
- name: string;
986
- }>>;
987
- createIndex(index: TDbIndex): Promise<void>;
988
- dropIndex(name: string): Promise<void>;
989
- prefix?: string;
990
- shouldSkipType?(type: TDbIndex['type']): boolean;
991
- }): Promise<void>;
992
- /**
993
- * Returns available search indexes for this adapter.
994
- * UI uses this to show index picker. Override in adapters that support search.
995
- */
996
- getSearchIndexes(): TSearchIndexInfo[];
997
- /**
998
- * Whether this adapter supports text search.
999
- * Default: `true` when {@link getSearchIndexes} returns any entries.
1000
- */
1001
- isSearchable(): boolean;
1002
- /**
1003
- * Whether this adapter supports vector similarity search.
1004
- * Override in adapters that support vector search.
1005
- */
1006
- isVectorSearchable(): boolean;
1007
- /**
1008
- * Full-text search. Override in adapters that support search.
1009
- *
1010
- * @param text - Search text.
1011
- * @param query - Filter, sort, limit, etc.
1012
- * @param indexName - Optional search index to target.
1013
- */
1014
- search(text: string, query: DbQuery, indexName?: string): Promise<Array<Record<string, unknown>>>;
1015
- /**
1016
- * Full-text search with count (for paginated search results).
1017
- *
1018
- * @param text - Search text.
1019
- * @param query - Filter, sort, limit, etc.
1020
- * @param indexName - Optional search index to target.
1021
- */
1022
- searchWithCount(text: string, query: DbQuery, indexName?: string): Promise<{
1023
- data: Array<Record<string, unknown>>;
1024
- count: number;
1025
- }>;
1026
- /**
1027
- * Vector similarity search. Override in adapters that support vector search.
1028
- *
1029
- * @param vector - Pre-computed embedding vector.
1030
- * @param query - Filter, sort, limit, etc.
1031
- * @param indexName - Optional vector index to target (for multi-vector documents).
1032
- */
1033
- vectorSearch(vector: number[], query: DbQuery, indexName?: string): Promise<Array<Record<string, unknown>>>;
1034
- /**
1035
- * Vector similarity search with count (for paginated results).
1036
- *
1037
- * @param vector - Pre-computed embedding vector.
1038
- * @param query - Filter, sort, limit, etc.
1039
- * @param indexName - Optional vector index to target (for multi-vector documents).
1040
- */
1041
- vectorSearchWithCount(vector: number[], query: DbQuery, indexName?: string): Promise<{
1042
- data: Array<Record<string, unknown>>;
1043
- count: number;
1044
- }>;
1045
- /**
1046
- * Fetches records and total count in one call.
1047
- * Default: two parallel calls. Adapters may override for single-query optimization.
1048
- */
1049
- findManyWithCount(query: DbQuery): Promise<{
1050
- data: Array<Record<string, unknown>>;
1051
- count: number;
1052
- }>;
1053
- /**
1054
- * Executes an aggregate query (GROUP BY + aggregate functions).
1055
- * Default throws — override in adapters that support aggregation.
1056
- */
1057
- aggregate(_query: DbQuery): Promise<Array<Record<string, unknown>>>;
1058
- abstract insertOne(data: Record<string, unknown>): Promise<TDbInsertResult>;
1059
- abstract insertMany(data: Array<Record<string, unknown>>): Promise<TDbInsertManyResult>;
1060
- abstract replaceOne(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
1061
- abstract updateOne(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
1062
- abstract deleteOne(filter: FilterExpr): Promise<TDbDeleteResult>;
1063
- abstract findOne(query: DbQuery): Promise<Record<string, unknown> | null>;
1064
- abstract findMany(query: DbQuery): Promise<Array<Record<string, unknown>>>;
1065
- abstract count(query: DbQuery): Promise<number>;
1066
- abstract updateMany(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
1067
- abstract replaceMany(filter: FilterExpr, data: Record<string, unknown>): Promise<TDbUpdateResult>;
1068
- abstract deleteMany(filter: FilterExpr): Promise<TDbDeleteResult>;
1069
- /**
1070
- * Synchronizes indexes between the Atscript definitions and the database.
1071
- * Uses `this._table.indexes` for the full index definitions.
1072
- */
1073
- abstract syncIndexes(): Promise<void>;
1074
- /**
1075
- * Ensures the table exists in the database, creating it if needed.
1076
- * Uses `this._table.tableName`, `this._table.schema`, etc.
1077
- */
1078
- abstract ensureTable(): Promise<void>;
1079
- /**
1080
- * Synchronizes foreign key constraints between Atscript definitions and the database.
1081
- * Uses `this._table.foreignKeys` for the full FK definitions.
1082
- * Optional — only relational adapters need to implement this.
1083
- */
1084
- /**
1085
- * Post-sync hook called after all table operations (columns, indexes, FKs)
1086
- * are complete. Adapters can use this for finalization work such as
1087
- * resetting auto-increment sequences to match existing data.
1088
- * Optional — most adapters don't need this.
1089
- */
1090
- afterSyncTable?(): Promise<void>;
1091
- syncForeignKeys?(): Promise<void>;
1092
- /**
1093
- * Drops FK constraints identified by their canonical local column key.
1094
- * Called by the sync executor before column operations to remove stale FKs
1095
- * that would otherwise block ALTER COLUMN.
1096
- *
1097
- * @param fkFieldKeys - Canonical FK keys (sorted local field names, comma-joined).
1098
- */
1099
- dropForeignKeys?(fkFieldKeys: string[]): Promise<void>;
1100
- /**
1101
- * Returns the desired table options from Atscript annotations.
1102
- * Called after onBeforeFlatten/onAfterFlatten, so adapter-specific state
1103
- * (e.g., engine, charset, capped options) is populated.
1104
- *
1105
- * Values are stringified for consistent comparison.
1106
- * Returns undefined if the adapter has no table-level options.
1107
- */
1108
- getDesiredTableOptions?(): TExistingTableOption[];
1109
- /**
1110
- * Returns the current table options from the live database.
1111
- * Primary source for option diffing (DB-first strategy).
1112
- *
1113
- * Returns undefined if the adapter cannot introspect table options.
1114
- * In that case, schema sync falls back to stored snapshot.
1115
- */
1116
- getExistingTableOptions?(): Promise<TExistingTableOption[]>;
1117
- /**
1118
- * Applies non-destructive table option changes (e.g., MySQL ALTER TABLE ENGINE=X).
1119
- * Called for each non-destructive change in the diff.
1120
- * Destructive changes go through dropTable+ensureTable or recreateTable.
1121
- */
1122
- applyTableOptions?(changes: TTableOptionDiff['changed']): Promise<void>;
1123
- /**
1124
- * Returns the set of option keys where a value change requires table recreation.
1125
- * Default: empty (all changes are non-destructive).
1126
- */
1127
- destructiveOptionKeys?(): ReadonlySet<string>;
1128
- /**
1129
- * Checks whether the table/collection already exists in the database.
1130
- * Used by schema sync to determine create vs in-sync status for
1131
- * adapters that don't implement column introspection (e.g. MongoDB).
1132
- */
1133
- tableExists?(): Promise<boolean>;
1134
- /**
1135
- * Returns existing columns from the database via introspection.
1136
- * Used by schema sync for column diffing.
1137
- * Optional — schema-less adapters (MongoDB) skip this.
1138
- */
1139
- getExistingColumns?(): Promise<TExistingColumn[]>;
1140
- /**
1141
- * When true, the adapter can handle column type changes in-place
1142
- * (e.g. MySQL's ALTER TABLE MODIFY COLUMN) without requiring table recreation.
1143
- * The generic sync layer will delegate type changes to {@link syncColumns}
1144
- * instead of requiring `@db.sync.method "recreate"` or `"drop"`.
1145
- */
1146
- supportsColumnModify?: boolean;
1147
- /**
1148
- * Applies column diff (ALTER TABLE ADD COLUMN, etc.).
1149
- * The generic layer computes the diff; adapters execute DB-specific DDL.
1150
- * Optional — only relational adapters implement this.
1151
- */
1152
- syncColumns?(diff: TColumnDiff): Promise<TSyncColumnResult>;
1153
- /**
1154
- * Recreates the table losslessly: create temp → copy data → drop old → rename.
1155
- * Used by `@db.sync.method "recreate"` when structural changes can't be ALTER'd.
1156
- * Optional — only relational adapters implement this.
1157
- */
1158
- recreateTable?(): Promise<void>;
1159
- /**
1160
- * Drops the table entirely.
1161
- * Used by `@db.sync.method "drop"` for tables with ephemeral data.
1162
- * Optional — only relational adapters implement this.
1163
- */
1164
- dropTable?(): Promise<void>;
1165
- /**
1166
- * Drops one or more columns from the table.
1167
- * Used by schema sync to remove stale columns no longer in the schema.
1168
- * Optional — only relational adapters implement this.
1169
- */
1170
- dropColumns?(columns: string[]): Promise<void>;
1171
- /**
1172
- * Drops a table by name (without needing a registered readable).
1173
- * Used by schema sync to remove tables no longer in the schema.
1174
- * Optional — only relational adapters implement this.
1175
- */
1176
- dropTableByName?(tableName: string): Promise<void>;
1177
- /**
1178
- * Drops a view by name (without needing a registered readable).
1179
- * Used by schema sync to remove views no longer in the schema.
1180
- * Optional — only relational adapters implement this.
1181
- */
1182
- dropViewByName?(viewName: string): Promise<void>;
1183
- /**
1184
- * Renames a table/collection from `oldName` to the adapter's current table name.
1185
- * Used by schema sync when `@db.table.renamed` is present.
1186
- * Optional — only relational adapters implement this.
1187
- */
1188
- renameTable?(oldName: string): Promise<void>;
1189
- /**
1190
- * Introspects columns for an arbitrary table name (not the adapter's own table).
1191
- * Used by schema sync `plan()` to inspect a table under its old name before rename.
1192
- * Optional — only relational adapters implement this.
1193
- */
1194
- getExistingColumnsForTable?(tableName: string): Promise<TExistingColumn[]>;
1195
- /**
1196
- * Maps a field's metadata to the adapter's native column type string.
1197
- * Receives the full field descriptor (design type, annotations, PK status, etc.)
1198
- * so adapters can produce context-aware types (e.g., `VARCHAR(255)` from maxLength).
1199
- * Used by schema sync to detect column type changes.
1200
- * Optional — adapters that don't implement this skip type change detection.
1201
- */
1202
- typeMapper?(field: TDbFieldMeta): string;
1203
- /**
1204
- * Returns a value formatter for a field, or undefined if no formatting is needed.
1205
- * Called once per field during build. The returned formatter(s) are cached and
1206
- * applied during write preparation, filter translation, and read reconstruction.
1207
- *
1208
- * Can return:
1209
- * - A bare function: used as `toStorage` only (write + filter paths)
1210
- * - A `TValueFormatterPair`: `toStorage` for writes/filters, `fromStorage` for reads
1211
- * - `undefined`: no formatting needed
1212
- *
1213
- * This avoids per-value method dispatch — only fields that need formatting
1214
- * get a formatter function, and the generic layer skips fields without one.
1215
- *
1216
- * Example: MySQL returns a pair for TIMESTAMP-mapped fields (epoch ms ↔ datetime string).
1217
- */
1218
- formatValue?(field: TDbFieldMeta): TValueFormatterPair | ((value: unknown) => unknown) | undefined;
1219
- }
1220
-
1221
- /**
1222
- * Strategy for referential integrity enforcement.
1223
- * Two implementations: {@link NativeIntegrity} (DB handles FK constraints)
1224
- * and `ApplicationIntegrity` (generic layer validates + cascades).
1225
- */
1226
- declare abstract class IntegrityStrategy {
1227
- abstract validateForeignKeys(items: Array<Record<string, unknown>>, meta: TableMetadata, fkLookupResolver: TFkLookupResolver | undefined, writeTableResolver: TWriteTableResolver | undefined, partial?: boolean, excludeTargetTable?: string): Promise<void>;
1228
- abstract cascadeBeforeDelete(filter: FilterExpr, tableName: string, meta: TableMetadata, cascadeResolver: TCascadeResolver, translateFilter: (f: FilterExpr) => FilterExpr, adapter: BaseDbAdapter): Promise<void>;
1229
- abstract needsCascade(cascadeResolver: TCascadeResolver | undefined): boolean;
1230
- }
1231
-
1232
- /**
1233
- * Generic database table abstraction driven by Atscript `@db.*` annotations.
1234
- *
1235
- * Extends {@link AtscriptDbReadable} (read operations, field metadata, query
1236
- * translation, relation loading) with write operations, validators, and
1237
- * schema management.
1238
- *
1239
- * ```typescript
1240
- * const adapter = new MongoAdapter(db)
1241
- * const users = new AtscriptDbTable(UsersType, adapter)
1242
- * await users.insertOne({ name: 'John', email: 'john@example.com' })
1243
- * ```
1244
- *
1245
- * @typeParam T - The Atscript annotated type for this table.
1246
- * @typeParam DataType - The inferred data shape from the annotated type.
1247
- */
1248
- declare class AtscriptDbTable<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType, DataType = TAtscriptDataType<T>, FlatType = FlatOf<T>, A extends BaseDbAdapter = BaseDbAdapter, IdType = PrimaryKeyOf<T>, OwnProps = OwnPropsOf<T>, NavType extends Record<string, unknown> = NavPropsOf<T>> extends AtscriptDbReadable<T, DataType, FlatType, A, IdType, OwnProps, NavType> {
1249
- protected _cascadeResolver?: TCascadeResolver;
1250
- protected _fkLookupResolver?: TFkLookupResolver;
1251
- protected readonly _integrity: IntegrityStrategy;
1252
- protected readonly validators: Map<string, Validator<T, DataType>>;
1253
- constructor(_type: T, adapter: A, logger?: TGenericLogger, _tableResolver?: TTableResolver, _writeTableResolver?: TWriteTableResolver);
1254
- /**
1255
- * Sets the cascade resolver for application-level cascade deletes.
1256
- * Called by DbSpace after table creation.
1257
- */
1258
- setCascadeResolver(resolver: TCascadeResolver): void;
1259
- /**
1260
- * Sets the FK lookup resolver for application-level FK validation.
1261
- * Called by DbSpace after table creation.
1262
- */
1263
- setFkLookupResolver(resolver: TFkLookupResolver): void;
1264
- /**
1265
- * Returns a cached validator for the given purpose.
1266
- * Built with adapter plugins from {@link BaseDbAdapter.getValidatorPlugins}.
1267
- *
1268
- * Standard purposes: `'insert'`, `'update'`, `'patch'`.
1269
- * Adapters may define additional purposes.
1270
- */
1271
- getValidator(purpose: string): Validator<T, DataType>;
1272
- /**
1273
- * Inserts a single record. Delegates to {@link insertMany} for unified
1274
- * nested creation support.
1275
- */
1276
- insertOne(payload: Partial<DataType> & Record<string, unknown>, opts?: {
1277
- maxDepth?: number;
1278
- }): Promise<TDbInsertResult>;
1279
- /**
1280
- * Inserts multiple records with batch-optimized nested creation.
1281
- *
1282
- * Supports **nested creation**: if payloads include data for navigation
1283
- * fields (`@db.rel.to` / `@db.rel.from`), related records are created
1284
- * automatically in batches. TO dependencies are batch-created first
1285
- * (their PKs become our FKs), FROM dependents are batch-created after
1286
- * (they receive our PKs as their FKs). Fully recursive — nested records
1287
- * with their own nav data trigger further batch inserts at each level.
1288
- * Recursive up to `maxDepth` (default 3).
1289
- */
1290
- insertMany(payloads: Array<Partial<DataType> & Record<string, unknown>>, opts?: {
1291
- maxDepth?: number;
1292
- }): Promise<TDbInsertManyResult>;
1293
- /**
1294
- * Replaces a single record identified by primary key(s).
1295
- * Delegates to {@link bulkReplace} for unified nested relation support.
1296
- */
1297
- replaceOne(payload: DataType & Record<string, unknown>, opts?: {
1298
- maxDepth?: number;
1299
- }): Promise<TDbUpdateResult>;
1300
- /**
1301
- * Replaces multiple records with deep nested relation support.
1302
- *
1303
- * Supports all relation types (TO, FROM, VIA). TO dependencies are
1304
- * replaced first (their PKs become our FKs), FROM dependents are replaced
1305
- * after (they receive our PKs as their FKs), VIA relations clear and
1306
- * re-create junction rows. Fully recursive up to `maxDepth` (default 3).
1307
- */
1308
- bulkReplace(payloads: Array<DataType & Record<string, unknown>>, opts?: {
1309
- maxDepth?: number;
1310
- }): Promise<TDbUpdateResult>;
1311
- /**
1312
- * Partially updates a single record identified by primary key(s).
1313
- * Delegates to {@link bulkUpdate} for unified nested relation support.
1314
- */
1315
- updateOne(payload: Partial<DataType> & Record<string, unknown>, opts?: {
1316
- maxDepth?: number;
1317
- }): Promise<TDbUpdateResult>;
1318
- /**
1319
- * Partially updates multiple records with deep nested relation support.
1320
- *
1321
- * Only TO relations (1:1, N:1) are supported for patching. FROM/VIA
1322
- * relations will error — use {@link bulkReplace} for those.
1323
- * Recursive up to `maxDepth` (default 3).
1324
- */
1325
- bulkUpdate(payloads: Array<Partial<DataType> & Record<string, unknown>>, opts?: {
1326
- maxDepth?: number;
1327
- }): Promise<TDbUpdateResult>;
1328
- /**
1329
- * Deletes a single record by any type-compatible identifier — primary key
1330
- * or single-field unique index. Uses the same resolution logic as `findById`.
1331
- *
1332
- * When the adapter does not support native foreign keys (e.g. MongoDB),
1333
- * cascade and setNull actions are applied before the delete.
1334
- */
1335
- deleteOne(id: IdType): Promise<TDbDeleteResult>;
1336
- updateMany(filter: FilterExpr<FlatType>, data: Partial<DataType> & Record<string, unknown>): Promise<TDbUpdateResult>;
1337
- replaceMany(filter: FilterExpr<FlatType>, data: Record<string, unknown>): Promise<TDbUpdateResult>;
1338
- deleteMany(filter: FilterExpr<FlatType>): Promise<TDbDeleteResult>;
1339
- /**
1340
- * Synchronizes indexes between Atscript definitions and the database.
1341
- */
1342
- syncIndexes(): Promise<void>;
1343
- /**
1344
- * Ensures the table/collection exists in the database.
1345
- */
1346
- ensureTable(): Promise<void>;
1347
- /**
1348
- * Applies default values for fields that are missing from the payload.
1349
- * Defaults handled natively by the DB engine are skipped — the field stays
1350
- * absent so the DB's own DEFAULT clause applies.
1351
- */
1352
- protected _applyDefaults(data: Record<string, unknown>): Record<string, unknown>;
1353
- /**
1354
- * Extracts primary key field(s) from a payload to build a filter.
1355
- */
1356
- protected _extractPrimaryKeyFilter(payload: Record<string, unknown>): FilterExpr;
1357
- /**
1358
- * Pre-validate items (type validation + FK constraints) without inserting them.
1359
- * Used by parent tables to validate FROM children before the main insert,
1360
- * ensuring errors are caught before the parent is committed.
1361
- *
1362
- * @param opts.excludeFkTargetTable - Skip FK validation to this table (the parent).
1363
- */
1364
- preValidateItems(items: Array<Record<string, unknown>>, opts?: {
1365
- excludeFkTargetTable?: string;
1366
- }): Promise<void>;
1367
- /**
1368
- * Builds a validator for a given purpose with adapter plugins.
1369
- *
1370
- * Uses annotation-based `replace` callback to make `@meta.id` and
1371
- * `@db.default` fields optional — works at all nesting levels
1372
- * (including inside nav field target types).
1373
- */
1374
- protected _buildValidator(purpose: string): Validator<T, DataType>;
1375
- }
1376
-
1377
- /** A single join in a view query plan. */
1378
- interface TViewJoin {
1379
- targetType: () => TAtscriptAnnotatedType;
1380
- targetTable: string;
1381
- condition: AtscriptQueryNode;
1382
- }
1383
- /** Resolved view query plan produced by AtscriptDbView. */
1384
- interface TViewPlan {
1385
- entryType: () => TAtscriptAnnotatedType;
1386
- entryTable: string;
1387
- joins: TViewJoin[];
1388
- filter?: AtscriptQueryNode;
1389
- having?: AtscriptQueryNode;
1390
- materialized: boolean;
1391
- }
1392
-
1393
- interface TViewColumnMapping {
1394
- viewColumn: string;
1395
- sourceTable: string;
1396
- sourceColumn: string;
1397
- /** Aggregate function name ('sum'|'avg'|'count'|'min'|'max') if this is an aggregate column. */
1398
- aggFn?: string;
1399
- /** Source field for the aggregate function ('*' for COUNT(*)). */
1400
- aggField?: string;
1401
- }
1402
- /**
1403
- * Database view abstraction driven by Atscript `@db.view.*` annotations.
1404
- *
1405
- * Extends {@link AtscriptDbReadable} with view plan resolution — entry table,
1406
- * joins, filter, and materialization flag. Read operations are inherited;
1407
- * write operations are not available on views.
1408
- *
1409
- * ```typescript
1410
- * const adapter = new SqliteAdapter(db)
1411
- * const activeUsers = new AtscriptDbView(ActiveUsersType, adapter)
1412
- * const users = await activeUsers.findMany({ filter: {}, controls: {} })
1413
- * ```
1414
- */
1415
- declare class AtscriptDbView<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType, DataType = TAtscriptDataType<T>, FlatType = FlatOf<T>, A extends BaseDbAdapter = BaseDbAdapter, IdType = PrimaryKeyOf<T>, OwnProps = OwnPropsOf<T>, NavType extends Record<string, unknown> = NavPropsOf<T>> extends AtscriptDbReadable<T, DataType, FlatType, A, IdType, OwnProps, NavType> {
1416
- private _viewPlan?;
1417
- get isView(): boolean;
1418
- /**
1419
- * Whether this is an external view — declared with `@db.view` only,
1420
- * without `@db.view.for`. External views reference pre-existing DB views
1421
- * and are not managed (created/dropped) by schema sync.
1422
- */
1423
- get isExternal(): boolean;
1424
- /**
1425
- * Lazily resolves the view plan from `@db.view.*` metadata.
1426
- *
1427
- * - `db.view.for` → entry type ref (required)
1428
- * - `db.view.joins` → array of `{ target, condition }` (optional, multiple)
1429
- * - `db.view.filter` → query tree (optional)
1430
- * - `db.view.materialized` → boolean (optional)
1431
- */
1432
- get viewPlan(): TViewPlan;
1433
- /**
1434
- * Resolves a query field ref to a quoted `table.column` SQL fragment.
1435
- *
1436
- * @param ref - The field reference from the query tree.
1437
- * @param qi - Identifier quoting function (e.g. backtick for MySQL, double-quote for SQLite).
1438
- * Defaults to double-quote wrapping for backwards compatibility.
1439
- */
1440
- resolveFieldRef(ref: AtscriptQueryFieldRef, qi?: (name: string) => string): string;
1441
- /**
1442
- * Maps each view field to its source table and column via ref chain.
1443
- * Fields without refs (inline definitions) map to the entry table with the same name.
1444
- */
1445
- getViewColumnMappings(): TViewColumnMapping[];
1446
- }
1447
-
1448
- /**
1449
- * Adapter factory function. Called once per table/view to create a fresh adapter instance.
1450
- * Each readable gets its own adapter (1:1 relationship required by BaseDbAdapter).
1451
- */
1452
- type TAdapterFactory = () => BaseDbAdapter;
1453
- /**
1454
- * A database space — a registry of tables and views sharing the same adapter type and driver.
1455
- *
1456
- * `DbSpace` solves the cross-table discovery problem: when table A has a relation
1457
- * to table B, it needs to find and query table B. The space acts as the registry
1458
- * that makes this possible via the table resolver callback.
1459
- *
1460
- * Each table/view gets its own adapter instance (created by the factory), but all
1461
- * share the same space and can discover each other for `$with` relation loading.
1462
- *
1463
- * ```typescript
1464
- * // SQLite
1465
- * const driver = new BetterSqlite3Driver(':memory:')
1466
- * const db = new DbSpace(() => new SqliteAdapter(driver))
1467
- * const users = db.getTable(UsersType)
1468
- * const activeUsers = db.getView(ActiveUsersType)
1469
- * ```
1470
- */
1471
- declare class DbSpace {
1472
- protected readonly adapterFactory: TAdapterFactory;
1473
- protected readonly logger: TGenericLogger;
1474
- private _readables;
1475
- /** All tables created in this space — used for reverse FK lookup during cascade. */
1476
- private _allTables;
1477
- /** Lazily created adapter for administrative ops (drop table/view) that don't need a registered readable. */
1478
- private _adminAdapter?;
1479
- constructor(adapterFactory: TAdapterFactory, logger?: TGenericLogger);
1480
- /**
1481
- * Auto-detects whether the type is a table or view and returns the
1482
- * appropriate instance. Uses `@db.view` or `@db.view.for` presence to distinguish.
1483
- */
1484
- get<T extends TAtscriptAnnotatedType>(type: T, logger?: TGenericLogger): AtscriptDbReadable<T>;
1485
- /**
1486
- * Returns the table for the given annotated type.
1487
- * Creates the table + adapter on first access, caches for subsequent calls.
1488
- */
1489
- getTable<T extends TAtscriptAnnotatedType>(type: T, logger?: TGenericLogger): AtscriptDbTable<T>;
1490
- /**
1491
- * Returns the view for the given annotated type.
1492
- * Creates the view + adapter on first access, caches for subsequent calls.
1493
- */
1494
- getView<T extends TAtscriptAnnotatedType>(type: T, logger?: TGenericLogger): AtscriptDbView<T>;
1495
- /**
1496
- * Returns the adapter for the given annotated type.
1497
- * Creates the table/view + adapter on first access if needed.
1498
- */
1499
- getAdapter(type: TAtscriptAnnotatedType): BaseDbAdapter;
1500
- /**
1501
- * Drops a table by name. Used by schema sync to remove tables no longer in the schema.
1502
- */
1503
- dropTableByName(tableName: string): Promise<void>;
1504
- /**
1505
- * Drops a view by name. Used by schema sync to remove views no longer in the schema.
1506
- */
1507
- dropViewByName(viewName: string): Promise<void>;
1508
- private _getAdminAdapter;
1509
- /**
1510
- * Finds all child tables with FKs pointing to the given parent table name.
1511
- * Accesses `table.foreignKeys` which triggers `_flatten()` if needed.
1512
- */
1513
- private _getCascadeTargets;
1514
- /**
1515
- * Resolves a table name to a queryable target for FK validation.
1516
- * Searches all registered tables for one with the matching table name.
1517
- */
1518
- private _getFkLookupTarget;
1519
- }
1520
-
1521
- interface TSyncColors {
1522
- green(s: string): string;
1523
- red(s: string): string;
1524
- cyan(s: string): string;
1525
- yellow(s: string): string;
1526
- bold(s: string): string;
1527
- dim(s: string): string;
1528
- underline(s: string): string;
1529
- }
1530
- type TSyncEntryStatus = 'create' | 'alter' | 'drop' | 'in-sync' | 'error';
1531
- interface TSyncEntryInit {
1532
- name: string;
1533
- /** 'V' = virtual view, 'M' = materialized view, 'E' = external view, undefined = table */
1534
- viewType?: 'V' | 'M' | 'E';
1535
- status: TSyncEntryStatus;
1536
- syncMethod?: 'drop' | 'recreate';
1537
- columnsToAdd?: TDbFieldMeta[];
1538
- columnsToRename?: Array<{
1539
- from: string;
1540
- to: string;
1541
- }>;
1542
- typeChanges?: Array<{
1543
- column: string;
1544
- fromType: string;
1545
- toType: string;
1546
- }>;
1547
- nullableChanges?: Array<{
1548
- column: string;
1549
- toNullable: boolean;
1550
- }>;
1551
- defaultChanges?: Array<{
1552
- column: string;
1553
- oldDefault?: string;
1554
- newDefault?: string;
1555
- }>;
1556
- columnsToDrop?: string[];
1557
- optionChanges?: TTableOptionDiff['changed'];
1558
- fkAdded?: Array<{
1559
- fields: string[];
1560
- targetTable: string;
1561
- }>;
1562
- fkRemoved?: Array<{
1563
- fields: string[];
1564
- targetTable: string;
1565
- }>;
1566
- fkChanged?: Array<{
1567
- fields: string[];
1568
- targetTable: string;
1569
- details: string;
1570
- }>;
1571
- columnsAdded?: string[];
1572
- columnsRenamed?: string[];
1573
- columnsDropped?: string[];
1574
- recreated?: boolean;
1575
- errors?: string[];
1576
- renamedFrom?: string;
1577
- }
1578
- declare class SyncEntry {
1579
- readonly name: string;
1580
- /** 'V' = virtual view, 'M' = materialized view, 'E' = external view, undefined = table */
1581
- readonly viewType?: 'V' | 'M' | 'E';
1582
- readonly status: TSyncEntryStatus;
1583
- readonly syncMethod?: 'drop' | 'recreate';
1584
- readonly columnsToAdd: TDbFieldMeta[];
1585
- readonly columnsToRename: Array<{
1586
- from: string;
1587
- to: string;
1588
- }>;
1589
- readonly typeChanges: Array<{
1590
- column: string;
1591
- fromType: string;
1592
- toType: string;
1593
- }>;
1594
- readonly nullableChanges: Array<{
1595
- column: string;
1596
- toNullable: boolean;
1597
- }>;
1598
- readonly defaultChanges: Array<{
1599
- column: string;
1600
- oldDefault?: string;
1601
- newDefault?: string;
1602
- }>;
1603
- readonly columnsToDrop: string[];
1604
- readonly optionChanges: TTableOptionDiff['changed'];
1605
- readonly fkAdded: Array<{
1606
- fields: string[];
1607
- targetTable: string;
1608
- }>;
1609
- readonly fkRemoved: Array<{
1610
- fields: string[];
1611
- targetTable: string;
1612
- }>;
1613
- readonly fkChanged: Array<{
1614
- fields: string[];
1615
- targetTable: string;
1616
- details: string;
1617
- }>;
1618
- readonly columnsAdded: string[];
1619
- readonly columnsRenamed: string[];
1620
- readonly columnsDropped: string[];
1621
- readonly recreated: boolean;
1622
- readonly errors: string[];
1623
- readonly renamedFrom?: string;
1624
- constructor(init: TSyncEntryInit);
1625
- /** Whether this entry involves destructive operations */
1626
- get destructive(): boolean;
1627
- /** Whether this entry represents any change (not in-sync) */
1628
- get hasChanges(): boolean;
1629
- /** Whether this entry has errors */
1630
- get hasErrors(): boolean;
1631
- /** Render this entry for display */
1632
- print(mode: 'plan' | 'result', colors?: TSyncColors): string[];
1633
- private labelAndPrefix;
1634
- private printError;
1635
- private printPlan;
1636
- private printResult;
1637
- private printInSync;
1638
- }
1639
-
1640
- interface TFieldSnapshot {
1641
- physicalName: string;
1642
- designType: string;
1643
- optional: boolean;
1644
- isPrimaryKey: boolean;
1645
- storage: TDbStorageType;
1646
- defaultValue?: TDbDefaultValue;
1647
- /** Adapter-specific mapped type (e.g., "VARCHAR(255)", "INTEGER"). */
1648
- mappedType?: string;
1649
- }
1650
- interface TIndexSnapshot {
1651
- key: string;
1652
- type: string;
1653
- fields: Array<{
1654
- name: string;
1655
- sort: string;
1656
- }>;
1657
- }
1658
- interface TForeignKeySnapshot {
1659
- fields: string[];
1660
- targetTable: string;
1661
- targetFields: string[];
1662
- onDelete?: string;
1663
- onUpdate?: string;
1664
- }
1665
- interface TTableSnapshot {
1666
- tableName: string;
1667
- fields: TFieldSnapshot[];
1668
- indexes: TIndexSnapshot[];
1669
- foreignKeys: TForeignKeySnapshot[];
1670
- /** Adapter-specific table-level options (e.g., MySQL engine/charset, MongoDB capped). */
1671
- tableOptions?: TExistingTableOption[];
1672
- }
1673
- interface TViewSnapshot {
1674
- tableName: string;
1675
- viewType: 'V' | 'M' | 'E';
1676
- entryTable?: string;
1677
- joinTables?: string[];
1678
- filterHash?: string;
1679
- materialized?: boolean;
1680
- fields: TFieldSnapshot[];
1681
- }
1682
- /**
1683
- * Extracts a canonical, serializable snapshot from a readable's metadata.
1684
- * Sorted deterministically so the hash is stable across runs.
1685
- *
1686
- * @param readable - The table/view readable.
1687
- * @param typeMapper - Optional adapter-specific type mapper. When provided,
1688
- * each field's mapped type (e.g., "VARCHAR(255)") is stored in the snapshot
1689
- * for precise type change detection.
1690
- */
1691
- declare function computeTableSnapshot(readable: AtscriptDbReadable, typeMapper?: (field: TDbFieldMeta) => string, tableOptions?: TExistingTableOption[]): TTableSnapshot;
1692
- /**
1693
- * Extracts a canonical, serializable snapshot from a view's metadata.
1694
- * Captures view plan (entry table, joins, filter, materialization) for
1695
- * detecting view definition changes.
1696
- */
1697
- declare function computeViewSnapshot(view: AtscriptDbView): TViewSnapshot;
1698
- /**
1699
- * Computes a deterministic hash string from multiple table snapshots.
1700
- * Uses FNV-1a for speed — not cryptographic, just needs stability + collision resistance.
1701
- */
1702
- declare function computeSchemaHash(snapshots: Array<TTableSnapshot | TViewSnapshot>): string;
1703
- /**
1704
- * Computes a hash for a single table/view snapshot.
1705
- * Used for per-table change detection via stored snapshots.
1706
- */
1707
- declare function computeTableHash(snapshot: TTableSnapshot | TViewSnapshot): string;
1708
- /**
1709
- * Converts stored snapshot fields to `TExistingColumn[]` format
1710
- * for use with `computeColumnDiff`. Used by adapters that lack
1711
- * native column introspection (e.g., MongoDB).
1712
- *
1713
- * The `type` field uses `mappedType` when available (adapter-specific),
1714
- * falling back to `designType`.
1715
- */
1716
- declare function snapshotToExistingColumns(snapshot: TTableSnapshot): TExistingColumn[];
1717
- /**
1718
- * Extracts table options from a stored snapshot for diff comparison.
1719
- * Used as fallback when an adapter lacks native table option introspection.
1720
- */
1721
- declare function snapshotToExistingTableOptions(snapshot: TTableSnapshot): TExistingTableOption[];
1722
-
1723
- /**
1724
- * Reads a stored table snapshot from the control table.
1725
- * Use this for introspection/test utilities without coupling to control table internals.
1726
- */
1727
- declare function readStoredSnapshot(space: DbSpace, tableName: string): Promise<TTableSnapshot | null>;
1728
- declare function readStoredSnapshot(space: DbSpace, tableName: string, asView: true): Promise<TViewSnapshot | null>;
1729
-
1730
- interface TSyncPlan {
1731
- status: 'up-to-date' | 'changes-needed';
1732
- schemaHash: string;
1733
- entries: SyncEntry[];
1734
- }
1735
- interface TSyncOptions {
1736
- /** Pod/instance identifier for distributed locking. Default: random UUID. */
1737
- podId?: string;
1738
- /** Lock TTL in milliseconds. Default: 30000 (30s). */
1739
- lockTtlMs?: number;
1740
- /** How long to wait for another pod's lock before giving up. Default: 60000 (60s). */
1741
- waitTimeoutMs?: number;
1742
- /** Poll interval when waiting for lock. Default: 500ms. */
1743
- pollIntervalMs?: number;
1744
- /** Force sync even if hash matches. Default: false. */
1745
- force?: boolean;
1746
- /** Safe mode — skip destructive operations (column drops, table drops). Default: false. */
1747
- safe?: boolean;
1748
- }
1749
- interface TSyncResult {
1750
- status: 'up-to-date' | 'synced' | 'synced-by-peer';
1751
- schemaHash: string;
1752
- entries: SyncEntry[];
1753
- }
1754
- declare class SchemaSync {
1755
- private readonly space;
1756
- private readonly store;
1757
- private readonly logger;
1758
- constructor(space: DbSpace, logger?: TGenericLogger);
1759
- /**
1760
- * Resolves types into categorized readables and computes the schema hash.
1761
- * Passes each adapter's typeMapper for precise type tracking in snapshots.
1762
- */
1763
- private resolveAndHash;
1764
- /**
1765
- * Checks an external view: verifies it exists in the DB and columns match.
1766
- * Returns a SyncEntry with status 'in-sync' or 'error'.
1767
- */
1768
- private checkExternalView;
1769
- /**
1770
- * Detects tables/views present in the previous sync but absent from the current schema.
1771
- * Returns SyncEntry instances with status 'drop'.
1772
- */
1773
- private detectRemoved;
1774
- /**
1775
- * Starts a periodic heartbeat that extends the lock's TTL while sync runs.
1776
- * Returns a handle with `stop()` to cancel and `getAbortReason()` to check
1777
- * whether the lock was stolen or unexpectedly removed.
1778
- */
1779
- private startHeartbeat;
1780
- /** Throws if the heartbeat detected a stolen/missing lock. */
1781
- private assertLockHeld;
1782
- /**
1783
- * Runs schema synchronization with distributed locking.
1784
- */
1785
- run(types: TAtscriptAnnotatedType[], opts?: TSyncOptions): Promise<TSyncResult>;
1786
- /**
1787
- * Computes a dry-run plan showing what `run()` would do, without executing any DDL.
1788
- */
1789
- plan(types: TAtscriptAnnotatedType[], opts?: Pick<TSyncOptions, 'force' | 'safe'>): Promise<TSyncPlan>;
1790
- /** Fallback typeMapper for snapshot-based Path B: compares designType directly, skips unions. */
1791
- private resolveTypeMapper;
1792
- private planTable;
1793
- /**
1794
- * Populates plan init from a column diff (shared by Path A and Path B).
1795
- */
1796
- private populatePlanFromDiff;
1797
- /**
1798
- * Computes table option diff using DB-first introspection with snapshot fallback.
1799
- * Returns null if the adapter has no table options.
1800
- */
1801
- private diffTableOptions;
1802
- private planView;
1803
- private buildExecutorDeps;
1804
- }
1805
-
1806
- /**
1807
- * Computes the difference between desired schema fields and existing database columns.
1808
- *
1809
- * @param desired - Field descriptors from the Atscript type (after flattening).
1810
- * @param existing - Columns currently in the database (from introspection).
1811
- * @param typeMapper - Optional function to map field metadata to DB-native type strings.
1812
- * Receives the full field meta (design type, annotations, PK status, etc.)
1813
- * so adapters can produce context-aware types (e.g., `VARCHAR(255)` from maxLength).
1814
- * Required for type change detection.
1815
- */
1816
- declare function computeColumnDiff(desired: readonly TDbFieldMeta[], existing: TExistingColumn[], typeMapper?: (field: TDbFieldMeta) => string): TColumnDiff;
1817
-
1818
- /**
1819
- * Computes the difference between desired and existing table options.
1820
- *
1821
- * Options present in desired but absent from existing are ignored (initial state).
1822
- * Options present in existing but absent from desired are ignored (sticky options).
1823
- * Only value changes on matching keys are tracked.
1824
- *
1825
- * @param desired - Options from Atscript annotations (via adapter.getDesiredTableOptions()).
1826
- * @param existing - Options from DB introspection or snapshot fallback.
1827
- * @param destructiveKeys - Option keys where a value change requires table recreation.
1828
- */
1829
- declare function computeTableOptionDiff(desired: readonly TExistingTableOption[], existing: readonly TExistingTableOption[], destructiveKeys?: ReadonlySet<string>): TTableOptionDiff;
1830
-
1831
- interface TForeignKeyDiff {
1832
- /** FKs present in desired but not in stored snapshot (new). */
1833
- added: TDbForeignKey[];
1834
- /** FKs present in stored snapshot but not in desired (removed). */
1835
- removed: TForeignKeySnapshot[];
1836
- /** FKs where fields match but target, onDelete, or onUpdate differ. */
1837
- changed: Array<{
1838
- desired: TDbForeignKey;
1839
- existing: TForeignKeySnapshot;
1840
- }>;
1841
- }
1842
- /** Canonical key for an FK: sorted local field names, comma-joined. */
1843
- declare function fkKey(fields: readonly string[]): string;
1844
- /**
1845
- * Compares desired FK constraints against stored snapshot to detect
1846
- * additions, removals, and property changes (target table, target fields,
1847
- * onDelete, onUpdate).
1848
- */
1849
- declare function computeForeignKeyDiff(desired: ReadonlyMap<string, TDbForeignKey>, existingSnapshot: readonly TForeignKeySnapshot[]): TForeignKeyDiff;
1850
- /** Whether the FK diff contains any changes. */
1851
- declare function hasForeignKeyChanges(diff: TForeignKeyDiff): boolean;
1852
-
1853
- /**
1854
- * Synchronizes database schema with distributed locking.
1855
- * Safe to call from multiple concurrent processes/pods.
1856
- *
1857
- * ```typescript
1858
- * import { syncSchema } from '@atscript/db/sync'
1859
- *
1860
- * const db = new DbSpace(() => new SqliteAdapter(driver))
1861
- * await syncSchema(db, [UsersType, PostsType, CommentsType])
1862
- * ```
1863
- *
1864
- * The function:
1865
- * 1. Creates an `__atscript_control` table for lock coordination
1866
- * 2. Computes a schema hash — skips entirely if nothing changed
1867
- * 3. Acquires a distributed lock so only one process syncs
1868
- * 4. Creates tables, adds new columns, syncs indexes
1869
- * 5. Stores the new hash and releases the lock
1870
- *
1871
- * @param space - The DbSpace containing the adapter factory.
1872
- * @param types - Atscript annotated types to synchronize.
1873
- * @param opts - Lock TTL, wait timeout, force mode, etc.
1874
- */
1875
- declare function syncSchema(space: DbSpace, types: TAtscriptAnnotatedType[], opts?: TSyncOptions): Promise<TSyncResult>;
1876
-
1877
- export { SchemaSync, SyncEntry, computeColumnDiff, computeForeignKeyDiff, computeSchemaHash, computeTableHash, computeTableOptionDiff, computeTableSnapshot, computeViewSnapshot, fkKey, hasForeignKeyChanges, readStoredSnapshot, snapshotToExistingColumns, snapshotToExistingTableOptions, syncSchema };
1878
- export type { TFieldSnapshot, TForeignKeyDiff, TForeignKeySnapshot, TSyncColors, TSyncEntryStatus, TSyncOptions, TSyncPlan, TSyncResult, TTableSnapshot, TViewSnapshot };