@atscript/db 0.1.38

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.
@@ -0,0 +1,993 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, '__esModule', { value: true });
3
+ const require_chunk = require('./chunk-CrpGerW8.cjs');
4
+ const require_validation_utils = require('./validation-utils-DEoCMmEb.cjs');
5
+ const __atscript_core = require_chunk.__toESM(require("@atscript/core"));
6
+
7
+ //#region packages/db/src/plugin/annotations/agg.ts
8
+ const dbAggAnnotations = { agg: {
9
+ sum: new __atscript_core.AnnotationSpec({
10
+ description: "Declares a view field as SUM of a source column.",
11
+ nodeType: ["prop"],
12
+ argument: {
13
+ name: "field",
14
+ type: "string",
15
+ description: "Source column name to sum."
16
+ },
17
+ validate(token, _args, doc) {
18
+ return require_validation_utils.validateFieldBaseType(token, doc, "@db.agg.sum", ["number", "decimal"]);
19
+ }
20
+ }),
21
+ avg: new __atscript_core.AnnotationSpec({
22
+ description: "Declares a view field as AVG of a source column.",
23
+ nodeType: ["prop"],
24
+ argument: {
25
+ name: "field",
26
+ type: "string",
27
+ description: "Source column name to average."
28
+ },
29
+ validate(token, _args, doc) {
30
+ return require_validation_utils.validateFieldBaseType(token, doc, "@db.agg.avg", ["number", "decimal"]);
31
+ }
32
+ }),
33
+ count: new __atscript_core.AnnotationSpec({
34
+ description: "Declares a view field as COUNT. Without argument: COUNT(*). With field name argument: COUNT(field) (non-null count).",
35
+ nodeType: ["prop"],
36
+ argument: {
37
+ name: "field",
38
+ type: "string",
39
+ optional: true,
40
+ description: "Source column name to count non-null values. Omit for COUNT(*)."
41
+ },
42
+ validate(token, _args, doc) {
43
+ return require_validation_utils.validateFieldBaseType(token, doc, "@db.agg.count", ["number"]);
44
+ }
45
+ }),
46
+ min: new __atscript_core.AnnotationSpec({
47
+ description: "Declares a view field as MIN of a source column.",
48
+ nodeType: ["prop"],
49
+ argument: {
50
+ name: "field",
51
+ type: "string",
52
+ description: "Source column name."
53
+ }
54
+ }),
55
+ max: new __atscript_core.AnnotationSpec({
56
+ description: "Declares a view field as MAX of a source column.",
57
+ nodeType: ["prop"],
58
+ argument: {
59
+ name: "field",
60
+ type: "string",
61
+ description: "Source column name."
62
+ }
63
+ })
64
+ } };
65
+
66
+ //#endregion
67
+ //#region packages/db/src/plugin/annotations/column.ts
68
+ const dbColumnAnnotations = {
69
+ patch: { strategy: new __atscript_core.AnnotationSpec({
70
+ description: "Defines the **patching strategy** for updating nested objects.\n\n- **\"replace\"** → The field or object will be **fully replaced**.\n- **\"merge\"** → The field or object will be **merged recursively** (applies only to objects, not arrays).\n\n**Example:**\n```atscript\n@db.patch.strategy \"merge\"\nsettings: {\n notifications: boolean\n preferences: {\n theme: string\n }\n}\n```\n",
71
+ nodeType: ["prop"],
72
+ multiple: false,
73
+ argument: {
74
+ name: "strategy",
75
+ type: "string",
76
+ description: "The **patch strategy** for this field: `\"replace\"` (default) or `\"merge\"`.",
77
+ values: ["replace", "merge"]
78
+ },
79
+ validate(token, args, doc) {
80
+ const field = token.parentNode;
81
+ const errors = [];
82
+ const definition = field.getDefinition();
83
+ if (!definition) return errors;
84
+ let wrongType = false;
85
+ if ((0, __atscript_core.isRef)(definition)) {
86
+ const def = doc.unwindType(definition.id, definition.chain)?.def;
87
+ if (!(0, __atscript_core.isStructure)(def) && !(0, __atscript_core.isInterface)(def) && !(0, __atscript_core.isArray)(def)) wrongType = true;
88
+ } else if (!(0, __atscript_core.isStructure)(definition) && !(0, __atscript_core.isInterface)(definition) && !(0, __atscript_core.isArray)(definition)) wrongType = true;
89
+ if (wrongType) errors.push({
90
+ message: `@db.patch.strategy requires a field of type object or array`,
91
+ severity: 1,
92
+ range: token.range
93
+ });
94
+ return errors;
95
+ }
96
+ }) },
97
+ column: {
98
+ $self: new __atscript_core.AnnotationSpec({
99
+ description: "Overrides the physical column name in the database. For nested (flattened) fields, the parent prefix is still prepended automatically.\n\n**Example:**\n```atscript\n@db.column \"first_name\"\nfirstName: string\n// → physical column: first_name\n\n// Nested:\naddress: {\n @db.column \"zip_code\"\n zip: string\n}\n// → physical column: address__zip_code\n```\n",
100
+ nodeType: ["prop"],
101
+ argument: {
102
+ name: "name",
103
+ type: "string",
104
+ description: "The column/field name (without parent prefix for nested fields)."
105
+ }
106
+ }),
107
+ renamed: new __atscript_core.AnnotationSpec({
108
+ description: "Specifies the previous local field name for column rename migration. The sync engine generates ALTER TABLE RENAME COLUMN instead of drop+add.\n\n**Example:**\n```atscript\n@db.column.renamed \"zip\"\npostalCode: string\n// Renames address__zip → address__postalCode\n```\n",
109
+ nodeType: ["prop"],
110
+ argument: {
111
+ name: "oldName",
112
+ type: "string",
113
+ description: "The old local field name (parent prefix is reconstructed automatically)."
114
+ }
115
+ }),
116
+ collate: new __atscript_core.AnnotationSpec({
117
+ description: "Portable collation for string comparison and sorting. Adapters map the generic value to their native collation.\n\n- **\"binary\"** — exact byte comparison (case-sensitive)\n- **\"nocase\"** — case-insensitive comparison\n- **\"unicode\"** — full Unicode-aware sorting\n\nFor adapter-specific collations, use `@db.<engine>.collate` instead.\n\n**Example:**\n```atscript\n@db.column.collate \"nocase\"\nusername: string\n```\n",
118
+ nodeType: ["prop"],
119
+ argument: {
120
+ name: "collation",
121
+ type: "string",
122
+ values: [
123
+ "binary",
124
+ "nocase",
125
+ "unicode"
126
+ ],
127
+ description: "Portable collation mode: \"binary\", \"nocase\", or \"unicode\"."
128
+ },
129
+ validate(token, args, doc) {
130
+ return require_validation_utils.validateFieldBaseType(token, doc, "@db.column.collate", "string");
131
+ }
132
+ }),
133
+ precision: new __atscript_core.AnnotationSpec({
134
+ description: "Sets decimal precision and scale for database storage. Adapters map this to their native decimal type (e.g., `DECIMAL(10,2)` in SQL, ignored in MongoDB).\n\nFor `decimal` fields the runtime value is a string; for `number` fields this is a DB storage hint only.\n\n**Example:**\n```atscript\n@db.column.precision 10, 2\nprice: decimal\n```\n",
135
+ nodeType: ["prop"],
136
+ argument: [{
137
+ name: "precision",
138
+ type: "number",
139
+ description: "Total number of significant digits."
140
+ }, {
141
+ name: "scale",
142
+ type: "number",
143
+ description: "Number of digits after the decimal point."
144
+ }],
145
+ validate(token, args, doc) {
146
+ return require_validation_utils.validateFieldBaseType(token, doc, "@db.column.precision", ["number", "decimal"]);
147
+ }
148
+ }),
149
+ dimension: new __atscript_core.AnnotationSpec({
150
+ description: "Marks a field as a dimension — groupable in aggregate queries ($groupBy). Dimension fields automatically receive a database index during schema sync.",
151
+ nodeType: ["prop"]
152
+ }),
153
+ measure: new __atscript_core.AnnotationSpec({
154
+ description: "Marks a field as a measure — aggregatable in aggregate queries (sum, avg, count, min, max). Only valid on numeric or decimal fields.",
155
+ nodeType: ["prop"],
156
+ validate(token, _args, doc) {
157
+ return require_validation_utils.validateFieldBaseType(token, doc, "@db.column.measure", ["number", "decimal"]);
158
+ }
159
+ })
160
+ },
161
+ default: {
162
+ $self: new __atscript_core.AnnotationSpec({
163
+ description: "Sets a static DB-level default value (used in DDL DEFAULT clause). For string fields the value is used as-is; for other types it is parsed as JSON.\n\n**Example:**\n```atscript\n@db.default \"active\"\nstatus: string\n```\n",
164
+ nodeType: ["prop"],
165
+ argument: {
166
+ name: "value",
167
+ type: "string",
168
+ description: "Static default value. Strings used as-is; other types parsed via JSON.parse()."
169
+ }
170
+ }),
171
+ increment: new __atscript_core.AnnotationSpec({
172
+ description: "Auto-incrementing integer default. Each adapter maps this to its native mechanism (e.g., `AUTO_INCREMENT` in MySQL, `INTEGER PRIMARY KEY` in SQLite, counter collection in MongoDB).\n\n**Example:**\n```atscript\n@db.default.increment\nid: number.int\n\n// With optional start value:\n@db.default.increment 1000\nid: number.int\n```\n",
173
+ nodeType: ["prop"],
174
+ argument: {
175
+ optional: true,
176
+ name: "start",
177
+ type: "number",
178
+ description: "Starting value for the auto-increment sequence. Adapter-specific behavior; some adapters may ignore this."
179
+ },
180
+ validate(token, args, doc) {
181
+ return require_validation_utils.validateFieldBaseType(token, doc, "db.default.increment", "number");
182
+ }
183
+ }),
184
+ uuid: new __atscript_core.AnnotationSpec({
185
+ description: "UUID generation default. Each adapter maps this to its native mechanism (e.g., `DEFAULT (UUID())` in MySQL, `gen_random_uuid()` in PostgreSQL, app-level in SQLite).\n\n**Example:**\n```atscript\n@db.default.uuid\nid: string.uuid\n```\n",
186
+ nodeType: ["prop"],
187
+ validate(token, args, doc) {
188
+ return require_validation_utils.validateFieldBaseType(token, doc, "db.default.uuid", "string");
189
+ }
190
+ }),
191
+ now: new __atscript_core.AnnotationSpec({
192
+ description: "Current timestamp default. Each adapter maps this to its native mechanism (e.g., `DEFAULT CURRENT_TIMESTAMP` in MySQL, `DEFAULT now()` in PostgreSQL).\n\n**Example:**\n```atscript\n@db.default.now\ncreatedAt: number.timestamp\n```\n",
193
+ nodeType: ["prop"],
194
+ validate(token, args, doc) {
195
+ return require_validation_utils.validateFieldBaseType(token, doc, "db.default.now", ["number", "string"]);
196
+ }
197
+ })
198
+ },
199
+ json: new __atscript_core.AnnotationSpec({
200
+ description: "Forces a field to be stored as a single JSON column instead of being flattened into separate columns. Use on nested object fields that should remain as JSON in the database.\n\n**Example:**\n```atscript\n@db.json\nmetadata: { key: string, value: string }\n```\n",
201
+ nodeType: ["prop"],
202
+ validate(token, _args, doc) {
203
+ const errors = [];
204
+ const field = token.parentNode;
205
+ const definition = field.getDefinition();
206
+ if (definition && (0, __atscript_core.isRef)(definition)) {
207
+ const unwound = doc.unwindType(definition.id, definition.chain);
208
+ if (unwound && (0, __atscript_core.isPrimitive)(unwound.def)) errors.push({
209
+ message: "@db.json on a primitive field has no effect — primitive fields are already stored as scalar columns",
210
+ severity: 2,
211
+ range: token.range
212
+ });
213
+ }
214
+ return errors;
215
+ }
216
+ }),
217
+ ignore: new __atscript_core.AnnotationSpec({
218
+ description: "Excludes a field from the database schema. The field exists in the Atscript type but has no column in the DB.\n\n**Example:**\n```atscript\n@db.ignore\ndisplayName: string\n```\n",
219
+ nodeType: ["prop"],
220
+ validate(token, args, doc) {
221
+ const errors = [];
222
+ const field = token.parentNode;
223
+ if (field.countAnnotations("meta.id") > 0) errors.push({
224
+ message: `@db.ignore cannot coexist with @meta.id — a field cannot be both a primary key and excluded from the database`,
225
+ severity: 1,
226
+ range: token.range
227
+ });
228
+ return errors;
229
+ }
230
+ })
231
+ };
232
+
233
+ //#endregion
234
+ //#region packages/db/src/plugin/annotations/index-ann.ts
235
+ const dbIndexAnnotations = { index: {
236
+ plain: new __atscript_core.AnnotationSpec({
237
+ description: "Standard (non-unique) index for query performance. Fields sharing the same index name form a composite index.\n\n**Example:**\n```atscript\n@db.index.plain \"idx_timeline\", \"desc\"\ncreatedAt: number.timestamp\n```\n",
238
+ nodeType: ["prop"],
239
+ multiple: true,
240
+ mergeStrategy: "append",
241
+ argument: [{
242
+ optional: true,
243
+ name: "name",
244
+ type: "string",
245
+ description: "Index name / composite group name."
246
+ }, {
247
+ optional: true,
248
+ name: "sort",
249
+ type: "string",
250
+ values: ["asc", "desc"],
251
+ description: "Sort direction. Defaults to \"asc\"."
252
+ }]
253
+ }),
254
+ unique: new __atscript_core.AnnotationSpec({
255
+ description: "Unique index — ensures no two rows/documents have the same value(s). Fields sharing the same index name form a composite unique constraint.\n\n**Example:**\n```atscript\n@db.index.unique \"tenant_email\"\nemail: string.email\n```\n",
256
+ nodeType: ["prop"],
257
+ multiple: true,
258
+ mergeStrategy: "append",
259
+ argument: {
260
+ optional: true,
261
+ name: "name",
262
+ type: "string",
263
+ description: "Index name / composite group name."
264
+ }
265
+ }),
266
+ fulltext: new __atscript_core.AnnotationSpec({
267
+ description: "Full-text search index. Fields sharing the same index name form a composite full-text index.\n\n**Example:**\n```atscript\n@db.index.fulltext \"ft_content\"\ntitle: string\n\n@db.index.fulltext \"ft_content\", 5\nbio: string\n```\n",
268
+ nodeType: ["prop"],
269
+ multiple: true,
270
+ mergeStrategy: "append",
271
+ argument: [{
272
+ optional: true,
273
+ name: "name",
274
+ type: "string",
275
+ description: "Index name / composite group name."
276
+ }, {
277
+ optional: true,
278
+ name: "weight",
279
+ type: "number",
280
+ description: "Field importance in search results (higher = more relevant). Defaults to `1`. Supported by databases with weighted fulltext (e.g., MongoDB, PostgreSQL)."
281
+ }]
282
+ })
283
+ } };
284
+
285
+ //#endregion
286
+ //#region packages/db/src/plugin/annotations/rel.ts
287
+ const dbRelAnnotations = { rel: {
288
+ FK: new __atscript_core.AnnotationSpec({
289
+ description: "Declares a foreign key constraint on this field. The field must use a chain reference type (e.g., `User.id`) to specify the FK target.\n\n**Example:**\n```atscript\n@db.rel.FK\nauthorId: User.id\n\n// With alias (required when multiple FKs point to the same type)\n@db.rel.FK \"author\"\nauthorId: User.id\n```\n",
290
+ nodeType: ["prop"],
291
+ argument: {
292
+ optional: true,
293
+ name: "alias",
294
+ type: "string",
295
+ description: "Alias for pairing with @db.rel.to. Required when multiple FKs point to the same target type."
296
+ },
297
+ validate(token, args, doc) {
298
+ const errors = [];
299
+ const field = token.parentNode;
300
+ const alias = args[0]?.text;
301
+ const owner = require_validation_utils.getDbTableOwner(token);
302
+ if (!owner || owner.countAnnotations("db.table") === 0) errors.push({
303
+ message: "@db.rel.FK is only valid on fields of a @db.table interface",
304
+ severity: 1,
305
+ range: token.range
306
+ });
307
+ if (field.countAnnotations("db.rel.to") > 0 || field.countAnnotations("db.rel.from") > 0) errors.push({
308
+ message: "A field cannot be both a foreign key and a navigational property",
309
+ severity: 1,
310
+ range: token.range
311
+ });
312
+ const definition = field.getDefinition();
313
+ if (!definition || !(0, __atscript_core.isRef)(definition) || !definition.hasChain) {
314
+ errors.push({
315
+ message: `@db.rel.FK requires a chain reference type (e.g. User.id), got scalar type`,
316
+ severity: 1,
317
+ range: token.range
318
+ });
319
+ return errors;
320
+ }
321
+ const ref = definition;
322
+ const refTypeName = ref.id;
323
+ const chainFields = ref.chain.map((c) => c.text);
324
+ const targetUnwound = doc.unwindType(refTypeName);
325
+ if (targetUnwound) {
326
+ const targetDef = targetUnwound.def;
327
+ if ((0, __atscript_core.isInterface)(targetDef) || (0, __atscript_core.isStructure)(targetDef)) {
328
+ const struct = (0, __atscript_core.isInterface)(targetDef) ? targetDef.getDefinition() : targetDef;
329
+ if (struct && (0, __atscript_core.isStructure)(struct) && chainFields.length > 0) {
330
+ const targetProp = struct.props.get(chainFields[0]);
331
+ if (targetProp) {
332
+ if (targetProp.countAnnotations("meta.id") === 0 && targetProp.countAnnotations("db.index.unique") === 0) errors.push({
333
+ message: `@db.rel.FK target '${refTypeName}.${chainFields.join(".")}' is not a primary key (@meta.id) or unique (@db.index.unique) field`,
334
+ severity: 1,
335
+ range: token.range
336
+ });
337
+ const propDef = targetProp.getDefinition();
338
+ if (propDef && (0, __atscript_core.isRef)(propDef)) {
339
+ const propUnwound = targetUnwound.doc.unwindType(propDef.id, propDef.chain);
340
+ if (propUnwound && !(0, __atscript_core.isPrimitive)(propUnwound.def)) errors.push({
341
+ message: `Foreign key field must resolve to a scalar type (number, string, etc.), got '${propDef.id}'`,
342
+ severity: 1,
343
+ range: token.range
344
+ });
345
+ } else if (propDef && !(0, __atscript_core.isPrimitive)(propDef)) errors.push({
346
+ message: `Foreign key field must resolve to a scalar type (number, string, etc.)`,
347
+ severity: 1,
348
+ range: token.range
349
+ });
350
+ }
351
+ }
352
+ }
353
+ }
354
+ if (!alias) {
355
+ const struct = require_validation_utils.getParentStruct(token);
356
+ if (struct) {
357
+ let sameTargetCount = 0;
358
+ for (const [, prop] of struct.props) {
359
+ if (prop.countAnnotations("db.rel.FK") === 0) continue;
360
+ const def = prop.getDefinition();
361
+ if (!def || !(0, __atscript_core.isRef)(def)) continue;
362
+ const r = def;
363
+ if (!r.hasChain) continue;
364
+ if (r.id === refTypeName) {
365
+ if (!require_validation_utils.getAnnotationAlias(prop, "db.rel.FK")) sameTargetCount++;
366
+ }
367
+ }
368
+ if (sameTargetCount > 1) errors.push({
369
+ message: `Multiple @db.rel.FK fields resolve to type '${refTypeName}' — add alias to disambiguate`,
370
+ severity: 1,
371
+ range: token.range
372
+ });
373
+ }
374
+ }
375
+ return errors;
376
+ }
377
+ }),
378
+ to: new __atscript_core.AnnotationSpec({
379
+ description: "Forward navigational property — the FK is on **this** interface. The compiler resolves the matching @db.rel.FK by target type or alias.\n\n**Example:**\n```atscript\n@db.rel.to\nauthor?: User\n\n// With alias\n@db.rel.to \"author\"\nauthor?: User\n```\n",
380
+ nodeType: ["prop"],
381
+ argument: {
382
+ optional: true,
383
+ name: "alias",
384
+ type: "string",
385
+ description: "Match a local @db.rel.FK by alias."
386
+ },
387
+ validate(token, args, doc) {
388
+ const errors = [];
389
+ const field = token.parentNode;
390
+ const alias = args[0]?.text;
391
+ const owner = require_validation_utils.getDbTableOwner(token);
392
+ if (!owner || owner.countAnnotations("db.table") === 0) errors.push({
393
+ message: "@db.rel.to is only valid on fields of a @db.table interface",
394
+ severity: 1,
395
+ range: token.range
396
+ });
397
+ if (field.countAnnotations("db.rel.FK") > 0) errors.push({
398
+ message: "A field cannot be both a foreign key and a navigational property",
399
+ severity: 1,
400
+ range: token.range
401
+ });
402
+ const targetTypeName = require_validation_utils.getNavTargetTypeName(field);
403
+ if (!targetTypeName) return errors;
404
+ const unwound = doc.unwindType(targetTypeName);
405
+ if (unwound) {
406
+ const targetDef = unwound.def;
407
+ const targetNode = (0, __atscript_core.isInterface)(targetDef) ? targetDef : undefined;
408
+ if (!targetNode || targetNode.countAnnotations("db.table") === 0) errors.push({
409
+ message: `@db.rel.to target '${targetTypeName}' is not a @db.table entity`,
410
+ severity: 1,
411
+ range: token.range
412
+ });
413
+ }
414
+ const fieldDef = field.getDefinition();
415
+ if (fieldDef && fieldDef.entity === "group" && fieldDef.op === "|") errors.push({
416
+ message: "@db.rel.to does not support union types — use separate relations",
417
+ severity: 1,
418
+ range: token.range
419
+ });
420
+ const struct = require_validation_utils.getParentStruct(token);
421
+ if (struct) {
422
+ const fieldName = field.id;
423
+ for (const [name, prop] of struct.props) {
424
+ if (name === fieldName) continue;
425
+ if (prop.countAnnotations("db.rel.to") === 0) continue;
426
+ const propAlias = require_validation_utils.getAnnotationAlias(prop, "db.rel.to");
427
+ if ((alias || undefined) === (propAlias || undefined)) {
428
+ const otherTarget = require_validation_utils.getNavTargetTypeName(prop);
429
+ if (otherTarget === targetTypeName) {
430
+ errors.push({
431
+ message: `Duplicate @db.rel.to '${alias || targetTypeName}' — only one forward navigational property per alias`,
432
+ severity: 1,
433
+ range: token.range
434
+ });
435
+ break;
436
+ }
437
+ }
438
+ }
439
+ if (alias) {
440
+ const matches = require_validation_utils.findFKFieldsPointingTo(doc, struct, targetTypeName, alias);
441
+ if (matches.length === 0) errors.push({
442
+ message: `No @db.rel.FK '${alias}' found on this interface`,
443
+ severity: 1,
444
+ range: token.range
445
+ });
446
+ } else {
447
+ const matches = require_validation_utils.findFKFieldsPointingTo(doc, struct, targetTypeName);
448
+ if (matches.length === 0) errors.push({
449
+ message: `No @db.rel.FK on this interface points to '${targetTypeName}' — did you mean @db.rel.from?`,
450
+ severity: 1,
451
+ range: token.range
452
+ });
453
+ else if (matches.length > 1) errors.push({
454
+ message: `Multiple @db.rel.FK fields point to '${targetTypeName}' — add alias to disambiguate`,
455
+ severity: 1,
456
+ range: token.range
457
+ });
458
+ }
459
+ }
460
+ return errors;
461
+ }
462
+ }),
463
+ from: new __atscript_core.AnnotationSpec({
464
+ description: "Inverse navigational property — the FK is on the **target** interface, pointing back to this one.\n\n**Example:**\n```atscript\n@db.rel.from\nposts: Post[]\n\n// With alias\n@db.rel.from \"original\"\ncomments: Comment[]\n```\n",
465
+ nodeType: ["prop"],
466
+ argument: {
467
+ optional: true,
468
+ name: "alias",
469
+ type: "string",
470
+ description: "Match a @db.rel.FK on the target interface by alias."
471
+ },
472
+ validate(token, args, doc) {
473
+ const errors = [];
474
+ const field = token.parentNode;
475
+ const alias = args[0]?.text;
476
+ const owner = require_validation_utils.getDbTableOwner(token);
477
+ if (!owner || owner.countAnnotations("db.table") === 0) errors.push({
478
+ message: "@db.rel.from is only valid on fields of a @db.table interface",
479
+ severity: 1,
480
+ range: token.range
481
+ });
482
+ if (field.countAnnotations("db.rel.FK") > 0) errors.push({
483
+ message: "A field cannot be both a foreign key and a navigational property",
484
+ severity: 1,
485
+ range: token.range
486
+ });
487
+ const targetTypeName = require_validation_utils.getNavTargetTypeName(field);
488
+ if (!targetTypeName) return errors;
489
+ const unwound = doc.unwindType(targetTypeName);
490
+ if (!unwound) return errors;
491
+ const targetDef = unwound.def;
492
+ const targetDoc = unwound.doc;
493
+ if (!(0, __atscript_core.isInterface)(targetDef) || targetDef.countAnnotations("db.table") === 0) {
494
+ errors.push({
495
+ message: `@db.rel.from target '${targetTypeName}' is not a @db.table entity`,
496
+ severity: 1,
497
+ range: token.range
498
+ });
499
+ return errors;
500
+ }
501
+ const struct = require_validation_utils.getParentStruct(token);
502
+ if (struct) {
503
+ const fieldName = field.id;
504
+ for (const [name, prop] of struct.props) {
505
+ if (name === fieldName) continue;
506
+ if (prop.countAnnotations("db.rel.from") === 0) continue;
507
+ const propAlias = require_validation_utils.getAnnotationAlias(prop, "db.rel.from");
508
+ if ((alias || undefined) === (propAlias || undefined)) {
509
+ const otherTarget = require_validation_utils.getNavTargetTypeName(prop);
510
+ if (otherTarget === targetTypeName) {
511
+ errors.push({
512
+ message: `Duplicate @db.rel.from '${alias || targetTypeName}' — only one inverse navigational property per alias`,
513
+ severity: 1,
514
+ range: token.range
515
+ });
516
+ break;
517
+ }
518
+ }
519
+ }
520
+ }
521
+ const thisTypeName = require_validation_utils.getParentTypeName(token);
522
+ if (!thisTypeName) return errors;
523
+ const matches = require_validation_utils.findFKFieldsPointingTo(targetDoc, targetDef, thisTypeName, alias);
524
+ if (alias) {
525
+ if (matches.length === 0) errors.push({
526
+ message: `No @db.rel.FK '${alias}' found on '${targetTypeName}'`,
527
+ severity: 1,
528
+ range: token.range
529
+ });
530
+ } else if (matches.length === 0) errors.push({
531
+ message: `No @db.rel.FK on '${targetTypeName}' points to '${thisTypeName}'`,
532
+ severity: 1,
533
+ range: token.range
534
+ });
535
+ else if (matches.length > 1) errors.push({
536
+ message: `'${targetTypeName}' has multiple @db.rel.FK fields pointing to '${thisTypeName}' — add alias`,
537
+ severity: 1,
538
+ range: token.range
539
+ });
540
+ const fieldDef = field.getDefinition();
541
+ if (!(0, __atscript_core.isArray)(fieldDef) && matches.length === 1) {
542
+ const fkProp = matches[0].prop;
543
+ if (fkProp.countAnnotations("db.index.unique") === 0) errors.push({
544
+ message: `@db.rel.from '${field.id}' has singular type '${targetTypeName}' (1:1) but the FK on '${targetTypeName}' is not @db.index.unique — did you mean '${targetTypeName}[]' (1:N)?`,
545
+ severity: 2,
546
+ range: token.range
547
+ });
548
+ }
549
+ return errors;
550
+ }
551
+ }),
552
+ onDelete: require_validation_utils.refActionAnnotation("onDelete"),
553
+ onUpdate: require_validation_utils.refActionAnnotation("onUpdate"),
554
+ via: new __atscript_core.AnnotationSpec({
555
+ description: "Declares a many-to-many navigational property through an explicit junction table. `@db.rel.via` is self-sufficient — no `@db.rel.from` pairing is needed.\n\n**Example:**\n```atscript\n@db.rel.via PostTag\ntags: Tag[]\n```\n",
556
+ nodeType: ["prop"],
557
+ argument: {
558
+ name: "junction",
559
+ type: "ref",
560
+ description: "The junction table type (must have @db.table and @db.rel.FK fields pointing to both sides)."
561
+ },
562
+ validate(token, args, doc) {
563
+ const errors = [];
564
+ const field = token.parentNode;
565
+ if (field.countAnnotations("db.rel.to") > 0 || field.countAnnotations("db.rel.from") > 0) errors.push({
566
+ message: "@db.rel.via is self-sufficient — cannot be combined with @db.rel.to or @db.rel.from",
567
+ severity: 1,
568
+ range: token.range
569
+ });
570
+ const definition = field.getDefinition();
571
+ if (!(0, __atscript_core.isArray)(definition)) errors.push({
572
+ message: "@db.rel.via requires an array type (e.g. Tag[])",
573
+ severity: 1,
574
+ range: token.range
575
+ });
576
+ if (!args[0]) return errors;
577
+ const junctionName = args[0].text;
578
+ errors.push(...require_validation_utils.validateRefArgument(args[0], doc, { requireDbTable: true }));
579
+ if (errors.length > 0) return errors;
580
+ const junctionUnwound = doc.unwindType(junctionName);
581
+ if (!junctionUnwound) return errors;
582
+ const junctionDef = junctionUnwound.def;
583
+ if (!(0, __atscript_core.isInterface)(junctionDef)) return errors;
584
+ const thisTypeName = require_validation_utils.getParentTypeName(token);
585
+ const targetTypeName = require_validation_utils.getNavTargetTypeName(field);
586
+ if (!thisTypeName || !targetTypeName) return errors;
587
+ const fksToThis = require_validation_utils.findFKFieldsPointingTo(junctionUnwound.doc, junctionDef, thisTypeName);
588
+ if (fksToThis.length === 0) errors.push({
589
+ message: `Junction '${junctionName}' has no @db.rel.FK pointing to '${thisTypeName}'`,
590
+ severity: 1,
591
+ range: args[0].range
592
+ });
593
+ else if (fksToThis.length > 1) errors.push({
594
+ message: `Junction '${junctionName}' has multiple @db.rel.FK pointing to '${thisTypeName}' — not supported`,
595
+ severity: 1,
596
+ range: args[0].range
597
+ });
598
+ if (targetTypeName !== thisTypeName) {
599
+ const fksToTarget = require_validation_utils.findFKFieldsPointingTo(junctionUnwound.doc, junctionDef, targetTypeName);
600
+ if (fksToTarget.length === 0) errors.push({
601
+ message: `Junction '${junctionName}' has no @db.rel.FK pointing to '${targetTypeName}'`,
602
+ severity: 1,
603
+ range: args[0].range
604
+ });
605
+ else if (fksToTarget.length > 1) errors.push({
606
+ message: `Junction '${junctionName}' has multiple @db.rel.FK pointing to '${targetTypeName}' — not supported`,
607
+ severity: 1,
608
+ range: args[0].range
609
+ });
610
+ }
611
+ return errors;
612
+ }
613
+ }),
614
+ filter: new __atscript_core.AnnotationSpec({
615
+ description: "Applies a filter to a navigational property, restricting which related records are loaded.\n\n**Example:**\n```atscript\n@db.rel.from\n@db.rel.filter `Post.published = true`\npublishedPosts: Post[]\n```\n",
616
+ nodeType: ["prop"],
617
+ argument: {
618
+ name: "condition",
619
+ type: "query",
620
+ description: "Filter expression restricting which related records are loaded."
621
+ },
622
+ validate(token, args, doc) {
623
+ const errors = [];
624
+ const field = token.parentNode;
625
+ const hasTo = field.countAnnotations("db.rel.to") > 0;
626
+ const hasFrom = field.countAnnotations("db.rel.from") > 0;
627
+ const hasVia = field.countAnnotations("db.rel.via") > 0;
628
+ if (!hasTo && !hasFrom && !hasVia) {
629
+ errors.push({
630
+ message: "@db.rel.filter is only valid on navigational fields (@db.rel.to, @db.rel.from, or @db.rel.via)",
631
+ severity: 1,
632
+ range: token.range
633
+ });
634
+ return errors;
635
+ }
636
+ if (!args[0]?.queryNode) return errors;
637
+ const targetTypeName = require_validation_utils.getNavTargetTypeName(field);
638
+ if (!targetTypeName) return errors;
639
+ const allowedTypes = [targetTypeName];
640
+ if (hasVia) {
641
+ const junctionType = require_validation_utils.getAnnotationAlias(field, "db.rel.via");
642
+ if (junctionType) allowedTypes.push(junctionType);
643
+ }
644
+ errors.push(...require_validation_utils.validateQueryScope(args[0], allowedTypes, targetTypeName, doc));
645
+ return errors;
646
+ }
647
+ })
648
+ } };
649
+
650
+ //#endregion
651
+ //#region packages/db/src/plugin/annotations/search.ts
652
+ const dbSearchAnnotations = { search: {
653
+ vector: {
654
+ $self: new __atscript_core.AnnotationSpec({
655
+ description: "Marks a field as a **vector embedding** for **similarity search**.\n\n- Each adapter maps this to its native vector type and index:\n - **MongoDB** → Atlas `$vectorSearch` index\n - **MySQL 9+** → `VECTOR(N)` column + `VEC_DISTANCE_*` functions\n - **PostgreSQL** → pgvector `vector(N)` column + distance operators\n - **SQLite** → JSON storage (no native vector support)\n\n**Example:**\n```atscript\n@db.search.vector 1536, \"cosine\"\nembedding: db.vector\n```\n",
656
+ nodeType: ["prop"],
657
+ multiple: false,
658
+ argument: [
659
+ {
660
+ optional: false,
661
+ name: "dimensions",
662
+ type: "number",
663
+ description: "The **number of dimensions in the vector** (must match your embedding model output).",
664
+ values: [
665
+ "512",
666
+ "768",
667
+ "1024",
668
+ "1536",
669
+ "3072",
670
+ "4096"
671
+ ]
672
+ },
673
+ {
674
+ optional: true,
675
+ name: "similarity",
676
+ type: "string",
677
+ description: "The **similarity metric** for vector search. Defaults to `\"cosine\"`.\n\n**Available options:** `\"cosine\"`, `\"euclidean\"`, `\"dotProduct\"`.",
678
+ values: [
679
+ "cosine",
680
+ "euclidean",
681
+ "dotProduct"
682
+ ]
683
+ },
684
+ {
685
+ optional: true,
686
+ name: "indexName",
687
+ type: "string",
688
+ description: "The **name of the vector search index** (optional, defaults to the field name)."
689
+ }
690
+ ]
691
+ }),
692
+ threshold: new __atscript_core.AnnotationSpec({
693
+ description: "Sets a **default minimum similarity threshold** for vector search queries on this field.\n\n- Results with a similarity score below this threshold are excluded.\n- Query-time `$threshold` control overrides this default.\n- Value range: `0` to `1` (where `1` means exact match).\n\n**Example:**\n```atscript\n@db.search.vector 1536, \"cosine\"\n@db.search.vector.threshold 0.7\nembedding: db.vector\n```\n",
694
+ nodeType: ["prop"],
695
+ multiple: false,
696
+ argument: {
697
+ optional: false,
698
+ name: "value",
699
+ type: "number",
700
+ description: "Minimum similarity score (`0`–`1`). Results below this threshold are excluded."
701
+ }
702
+ })
703
+ },
704
+ filter: new __atscript_core.AnnotationSpec({
705
+ description: "Assigns a field as a **pre-filter** for a **vector search index**.\n\n- Filters allow vector search queries to return results only within a specific subset.\n- The referenced index must be defined using `@db.search.vector`.\n\n**Example:**\n```atscript\n@db.search.vector 1536, \"cosine\"\nembedding: db.vector\n\n@db.search.filter \"embedding\"\ncategory: string\n```\n",
706
+ nodeType: ["prop"],
707
+ multiple: true,
708
+ argument: {
709
+ optional: false,
710
+ name: "indexName",
711
+ type: "string",
712
+ description: "The **name of the vector search index** (field name or explicit indexName from `@db.search.vector`) this field filters."
713
+ }
714
+ })
715
+ } };
716
+
717
+ //#endregion
718
+ //#region packages/db/src/plugin/annotations/table.ts
719
+ const dbTableAnnotations = {
720
+ table: {
721
+ $self: new __atscript_core.AnnotationSpec({
722
+ description: "Marks an interface as a database-persisted entity (table in SQL, collection in MongoDB). If the name argument is omitted, the adapter derives the table name from the interface name.\n\n**Example:**\n```atscript\n@db.table \"users\"\nexport interface User { ... }\n```\n",
723
+ nodeType: ["interface"],
724
+ argument: {
725
+ optional: true,
726
+ name: "name",
727
+ type: "string",
728
+ description: "Table/collection name. If omitted, derived from interface name."
729
+ },
730
+ validate(token, _args, _doc) {
731
+ const errors = [];
732
+ const owner = token.parentNode;
733
+ if (require_validation_utils.hasAnyViewAnnotation(owner)) errors.push({
734
+ message: "An interface cannot be both a @db.table and a @db.view",
735
+ severity: 1,
736
+ range: token.range
737
+ });
738
+ return errors;
739
+ }
740
+ }),
741
+ renamed: new __atscript_core.AnnotationSpec({
742
+ description: "Specifies the previous table name for table rename migration. The sync engine generates ALTER TABLE RENAME instead of drop+create.\n\n**Example:**\n```atscript\n@db.table \"app_users\"\n@db.table.renamed \"users\"\nexport interface User { ... }\n```\n",
743
+ nodeType: ["interface"],
744
+ argument: {
745
+ name: "oldName",
746
+ type: "string",
747
+ description: "The previous table/collection name."
748
+ },
749
+ validate(token, _args, _doc) {
750
+ const errors = [];
751
+ const owner = token.parentNode;
752
+ if (owner.countAnnotations("db.table") === 0) errors.push({
753
+ message: "@db.table.renamed requires @db.table on the same interface",
754
+ severity: 1,
755
+ range: token.range
756
+ });
757
+ return errors;
758
+ }
759
+ })
760
+ },
761
+ schema: new __atscript_core.AnnotationSpec({
762
+ description: "Assigns the entity to a database schema/namespace.\n\n**Example:**\n```atscript\n@db.table \"users\"\n@db.schema \"auth\"\nexport interface User { ... }\n```\n",
763
+ nodeType: ["interface"],
764
+ argument: {
765
+ name: "name",
766
+ type: "string",
767
+ description: "Schema/namespace name."
768
+ }
769
+ }),
770
+ sync: { method: new __atscript_core.AnnotationSpec({
771
+ description: "Controls how the sync engine handles structural changes that cannot be applied via ALTER TABLE.\n\n- `\"recreate\"` — lossless: create temp table, copy data, drop old, rename.\n- `\"drop\"` — lossy: drop table entirely and create from scratch.\n\nWithout this annotation, structural changes fail with an error requiring manual intervention.\n\n**Example:**\n```atscript\n@db.sync.method \"drop\"\ninterface Logs { ... }\n```\n",
772
+ nodeType: ["interface"],
773
+ argument: {
774
+ name: "method",
775
+ type: "string",
776
+ description: "Sync method: \"drop\" (lossy) or \"recreate\" (lossless with data copy).",
777
+ values: ["drop", "recreate"]
778
+ }
779
+ }) }
780
+ };
781
+
782
+ //#endregion
783
+ //#region packages/db/src/plugin/annotations/view.ts
784
+ const dbViewAnnotations = { view: {
785
+ $self: new __atscript_core.AnnotationSpec({
786
+ description: "Marks an interface as a **database view**. Optionally takes a view name argument.\n\n**Example:**\n```atscript\n@db.view \"active_premium_users\"\n@db.view.for User\nexport interface ActivePremiumUser { ... }\n```\n",
787
+ nodeType: ["interface"],
788
+ argument: {
789
+ optional: true,
790
+ name: "name",
791
+ type: "string",
792
+ description: "The view name in the database. If omitted, derived from the interface name."
793
+ },
794
+ validate(token, _args, _doc) {
795
+ const errors = [];
796
+ const owner = token.parentNode;
797
+ if (owner.countAnnotations("db.table") > 0) errors.push({
798
+ message: "An interface cannot be both a @db.table and a @db.view",
799
+ severity: 1,
800
+ range: token.range
801
+ });
802
+ return errors;
803
+ }
804
+ }),
805
+ for: new __atscript_core.AnnotationSpec({
806
+ description: "Specifies the entry/primary table for a computed view. Required for views that map fields via chain refs.\n\n**Example:**\n```atscript\n@db.view.for Order\n@db.view.filter `Order.status = 'active'`\nexport interface ActiveOrderDetails { ... }\n```\n",
807
+ nodeType: ["interface"],
808
+ argument: {
809
+ name: "entry",
810
+ type: "ref",
811
+ description: "The primary/entry table type (must have @db.table)."
812
+ },
813
+ validate(token, args, doc) {
814
+ const errors = [];
815
+ const owner = token.parentNode;
816
+ if (owner.countAnnotations("db.table") > 0) errors.push({
817
+ message: "An interface cannot be both a @db.table and a @db.view",
818
+ severity: 1,
819
+ range: token.range
820
+ });
821
+ if (args[0]) errors.push(...require_validation_utils.validateRefArgument(args[0], doc, { requireDbTable: true }));
822
+ return errors;
823
+ }
824
+ }),
825
+ joins: new __atscript_core.AnnotationSpec({
826
+ description: "Declares an explicit join for a view. Use when no `@db.rel.*` path exists between the entry table and the target.\n\n**Example:**\n```atscript\n@db.view.for Order\n@db.view.joins Warehouse, `Warehouse.regionId = Order.regionId`\nexport interface OrderWarehouse { ... }\n```\n",
827
+ nodeType: ["interface"],
828
+ multiple: true,
829
+ mergeStrategy: "append",
830
+ argument: [{
831
+ name: "target",
832
+ type: "ref",
833
+ description: "The table type to join (must have @db.table)."
834
+ }, {
835
+ name: "condition",
836
+ type: "query",
837
+ description: "Join condition expression."
838
+ }],
839
+ validate(token, args, doc) {
840
+ const errors = [];
841
+ const owner = token.parentNode;
842
+ if (!require_validation_utils.hasAnyViewAnnotation(owner) && !args[0]) {
843
+ errors.push({
844
+ message: "@db.view.joins is only valid on @db.view interfaces",
845
+ severity: 1,
846
+ range: token.range
847
+ });
848
+ return errors;
849
+ }
850
+ if (args[0]) errors.push(...require_validation_utils.validateRefArgument(args[0], doc, { requireDbTable: true }));
851
+ const entryTypeName = require_validation_utils.getAnnotationAlias(owner, "db.view.for");
852
+ if (!entryTypeName) {
853
+ errors.push({
854
+ message: "@db.view.joins requires @db.view.for to identify the entry table",
855
+ severity: 1,
856
+ range: token.range
857
+ });
858
+ return errors;
859
+ }
860
+ if (args[1]?.queryNode && args[0]) {
861
+ const joinTargetName = args[0].text;
862
+ errors.push(...require_validation_utils.validateQueryScope(args[1], [joinTargetName, entryTypeName], entryTypeName, doc));
863
+ }
864
+ return errors;
865
+ }
866
+ }),
867
+ filter: new __atscript_core.AnnotationSpec({
868
+ description: "WHERE clause for a view, filtering which rows are included.\n\n**Example:**\n```atscript\n@db.view.for User\n@db.view.filter `User.status = 'active' and User.age >= 18`\nexport interface ActiveUser { ... }\n```\n",
869
+ nodeType: ["interface"],
870
+ argument: {
871
+ name: "condition",
872
+ type: "query",
873
+ description: "Filter expression for the view WHERE clause."
874
+ },
875
+ validate(token, args, doc) {
876
+ const errors = [];
877
+ const owner = token.parentNode;
878
+ if (!require_validation_utils.hasAnyViewAnnotation(owner) && !args[0]) {
879
+ errors.push({
880
+ message: "@db.view.filter is only valid on @db.view interfaces",
881
+ severity: 1,
882
+ range: token.range
883
+ });
884
+ return errors;
885
+ }
886
+ if (!args[0]?.queryNode) return errors;
887
+ const entryTypeName = require_validation_utils.getAnnotationAlias(owner, "db.view.for");
888
+ if (!entryTypeName) {
889
+ errors.push({
890
+ message: "@db.view.filter requires @db.view.for to identify the entry table",
891
+ severity: 1,
892
+ range: token.range
893
+ });
894
+ return errors;
895
+ }
896
+ const allowedTypes = [entryTypeName];
897
+ const joinsAnnotations = owner.annotations?.filter((a) => a.name === "db.view.joins");
898
+ if (joinsAnnotations) {
899
+ for (const join of joinsAnnotations) if (join.args[0]) allowedTypes.push(join.args[0].text);
900
+ }
901
+ errors.push(...require_validation_utils.validateQueryScope(args[0], allowedTypes, entryTypeName, doc));
902
+ return errors;
903
+ }
904
+ }),
905
+ materialized: new __atscript_core.AnnotationSpec({
906
+ description: "Marks a view as materialized (precomputed, stored on disk). Supported by PostgreSQL, CockroachDB, Oracle, SQL Server (indexed views), Snowflake. MongoDB supports on-demand materialized views via $merge/$out. Not applicable to MySQL or SQLite.\n\n**Example:**\n```atscript\n@db.view.materialized\n@db.view.for User\n@db.view.filter `User.status = 'active'`\nexport interface ActiveUsers { ... }\n```\n",
907
+ nodeType: ["interface"],
908
+ validate(token, _args, _doc) {
909
+ const errors = [];
910
+ const owner = token.parentNode;
911
+ if (!require_validation_utils.hasAnyViewAnnotation(owner)) errors.push({
912
+ message: "@db.view.materialized is only valid on @db.view interfaces",
913
+ severity: 1,
914
+ range: token.range
915
+ });
916
+ return errors;
917
+ }
918
+ }),
919
+ renamed: new __atscript_core.AnnotationSpec({
920
+ description: "Specifies the previous view name for view rename migration. The sync engine drops the old view and creates the new one.\n\n**Example:**\n```atscript\n@db.view \"active_premium_users\"\n@db.view.renamed \"active_users\"\n@db.view.for User\nexport interface ActivePremiumUser { ... }\n```\n",
921
+ nodeType: ["interface"],
922
+ argument: {
923
+ name: "oldName",
924
+ type: "string",
925
+ description: "The previous view name."
926
+ },
927
+ validate(token, _args, _doc) {
928
+ const errors = [];
929
+ const owner = token.parentNode;
930
+ if (!require_validation_utils.hasAnyViewAnnotation(owner)) errors.push({
931
+ message: "@db.view.renamed requires @db.view on the same interface",
932
+ severity: 1,
933
+ range: token.range
934
+ });
935
+ return errors;
936
+ }
937
+ }),
938
+ having: new __atscript_core.AnnotationSpec({
939
+ description: "Post-aggregation filter (HAVING clause) for analytical views. References view field aliases with applied aggregate functions.\n\n**Example:**\n```atscript\n@db.view\n@db.view.for Order\n@db.view.having `totalRevenue > 100`\nexport interface TopCategories { ... }\n```\n",
940
+ nodeType: ["interface"],
941
+ argument: {
942
+ name: "condition",
943
+ type: "query",
944
+ description: "HAVING condition referencing view aliases."
945
+ },
946
+ validate(token, _args, _doc) {
947
+ const errors = [];
948
+ const owner = token.parentNode;
949
+ if (!require_validation_utils.hasAnyViewAnnotation(owner)) errors.push({
950
+ message: "@db.view.having is only valid on @db.view interfaces",
951
+ severity: 1,
952
+ range: token.range
953
+ });
954
+ return errors;
955
+ }
956
+ })
957
+ } };
958
+
959
+ //#endregion
960
+ //#region packages/db/src/plugin/index.ts
961
+ const dbPlugin = () => ({
962
+ name: "db",
963
+ config() {
964
+ return {
965
+ annotations: { db: {
966
+ patch: dbColumnAnnotations.patch,
967
+ table: dbTableAnnotations.table,
968
+ schema: dbTableAnnotations.schema,
969
+ index: dbIndexAnnotations.index,
970
+ column: dbColumnAnnotations.column,
971
+ default: dbColumnAnnotations.default,
972
+ json: dbColumnAnnotations.json,
973
+ ignore: dbColumnAnnotations.ignore,
974
+ sync: dbTableAnnotations.sync,
975
+ rel: dbRelAnnotations.rel,
976
+ view: dbViewAnnotations.view,
977
+ agg: dbAggAnnotations.agg,
978
+ search: dbSearchAnnotations.search
979
+ } },
980
+ primitives: { db: { extensions: { vector: {
981
+ type: {
982
+ kind: "array",
983
+ of: "number"
984
+ },
985
+ documentation: "Represents a **vector embedding** (array of numbers) for **similarity search**.\n\n- Equivalent to `number[]` but explicitly marks the field as a vector embedding.\n- Each adapter maps this to its native vector type:\n - **MongoDB** → BSON array\n - **MySQL 9+** → `VECTOR(N)`\n - **PostgreSQL** → pgvector `vector(N)`\n - **SQLite** → JSON\n\n**Example:**\n```atscript\n@db.search.vector 1536, \"cosine\"\nembedding: db.vector\n```\n"
986
+ } } } }
987
+ };
988
+ }
989
+ });
990
+
991
+ //#endregion
992
+ exports.dbPlugin = dbPlugin
993
+ exports.default = dbPlugin