@atscript/db 0.1.38 → 0.1.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -303
- package/dist/agg.cjs +8 -3
- package/dist/agg.d.cts +7 -0
- package/dist/agg.d.mts +7 -0
- package/dist/agg.mjs +7 -3
- package/dist/control-DRgryKeg.cjs +14 -0
- package/dist/{control_as-bjmwe24C.mjs → control-IANbnfjG.mjs} +6 -18
- package/dist/db-readable-BQQzfguJ.d.cts +1249 -0
- package/dist/db-readable-Bbr4CjMb.d.mts +1249 -0
- package/dist/db-space-BUrQ5BFm.d.mts +309 -0
- package/dist/db-space-Vxpcnyt5.d.cts +309 -0
- package/dist/db-validator-plugin-07kDiis2.d.cts +22 -0
- package/dist/db-validator-plugin-CiqsHTI_.d.mts +22 -0
- package/dist/db-view-BntnAmXO.cjs +3071 -0
- package/dist/db-view-ZsoN91-q.mjs +2970 -0
- package/dist/index.cjs +95 -2801
- package/dist/index.d.cts +137 -0
- package/dist/index.d.mts +137 -0
- package/dist/index.mjs +55 -2761
- package/dist/{nested-writer-BkqL7cp3.cjs → nested-writer-BDXsDMPP.cjs} +196 -150
- package/dist/{nested-writer-NEN51mnR.mjs → nested-writer-Dmm1gbZV.mjs} +118 -70
- package/dist/ops-BdRAFLKY.d.mts +67 -0
- package/dist/ops-DXJ4Zw0P.d.cts +67 -0
- package/dist/ops.cjs +123 -0
- package/dist/ops.d.cts +2 -0
- package/dist/ops.d.mts +2 -0
- package/dist/ops.mjs +112 -0
- package/dist/plugin.cjs +90 -109
- package/dist/plugin.d.cts +6 -0
- package/dist/plugin.d.mts +6 -0
- package/dist/plugin.mjs +29 -49
- package/dist/rel.cjs +20 -20
- package/dist/rel.d.cts +119 -0
- package/dist/rel.d.mts +119 -0
- package/dist/rel.mjs +4 -5
- package/dist/{relation-helpers-guFL_oRf.cjs → relation-helpers-BYvsE1tR.cjs} +26 -22
- package/dist/{relation-helpers-DyBIlQnB.mjs → relation-helpers-CLasawQq.mjs} +11 -6
- package/dist/{relation-loader-Dv7qXYq7.mjs → relation-loader-BEOTXNcq.mjs} +63 -43
- package/dist/{relation-loader-CpnDRf9k.cjs → relation-loader-CRC5LcqM.cjs} +74 -49
- package/dist/shared.cjs +13 -13
- package/dist/{shared.d.ts → shared.d.cts} +14 -13
- package/dist/shared.d.mts +71 -0
- package/dist/shared.mjs +2 -3
- package/dist/sync.cjs +300 -252
- package/dist/sync.d.cts +369 -0
- package/dist/sync.d.mts +369 -0
- package/dist/sync.mjs +284 -233
- package/dist/{validation-utils-DEoCMmEb.cjs → validation-utils-DVJDijnB.cjs} +141 -109
- package/dist/{validation-utils-DhR_mtKa.mjs → validation-utils-DhjIjP1-.mjs} +71 -37
- package/package.json +31 -30
- package/LICENSE +0 -21
- package/dist/agg-BJFJ3dFQ.mjs +0 -8
- package/dist/agg-DnUWAOK8.cjs +0 -14
- package/dist/agg.d.ts +0 -13
- package/dist/chunk-CrpGerW8.cjs +0 -31
- package/dist/control_as-BFPERAF_.cjs +0 -28
- package/dist/index.d.ts +0 -1706
- package/dist/logger-B7oxCfLQ.mjs +0 -12
- package/dist/logger-Dt2v_-wb.cjs +0 -18
- package/dist/plugin.d.ts +0 -5
- package/dist/rel.d.ts +0 -1305
- package/dist/relation-loader-D4mTw6yH.cjs +0 -4
- package/dist/relation-loader-Ggy1ujwR.mjs +0 -4
- package/dist/sync.d.ts +0 -1878
|
@@ -1,33 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
let _atscript_core = require("@atscript/core");
|
|
2
|
+
//#region src/shared/annotation-utils.ts
|
|
3
|
+
/**
|
|
4
|
+
* Traverse from annotation token → prop → structure → interface
|
|
5
|
+
* to check if the parent interface has @db.table.
|
|
6
|
+
*/
|
|
6
7
|
function getDbTableOwner(token) {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
if (!struct || !(0, __atscript_core.isStructure)(struct)) return undefined;
|
|
8
|
+
const struct = token.parentNode.ownerNode;
|
|
9
|
+
if (!struct || !(0, _atscript_core.isStructure)(struct)) return;
|
|
10
10
|
const iface = struct.ownerNode;
|
|
11
|
-
return iface && (0,
|
|
11
|
+
return iface && (0, _atscript_core.isInterface)(iface) ? iface : struct;
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Get the parent structure node from an annotation token.
|
|
15
|
+
*/
|
|
13
16
|
function getParentStruct(token) {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
return struct && (0, __atscript_core.isStructure)(struct) ? struct : undefined;
|
|
17
|
+
const struct = token.parentNode.ownerNode;
|
|
18
|
+
return struct && (0, _atscript_core.isStructure)(struct) ? struct : void 0;
|
|
17
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Get the parent interface name (for error messages and cross-type resolution).
|
|
22
|
+
*/
|
|
18
23
|
function getParentTypeName(token) {
|
|
19
24
|
const struct = getParentStruct(token);
|
|
20
|
-
if (!struct) return
|
|
25
|
+
if (!struct) return;
|
|
21
26
|
const iface = struct.ownerNode;
|
|
22
|
-
return iface && (0,
|
|
27
|
+
return iface && (0, _atscript_core.isInterface)(iface) ? iface.id : struct.id;
|
|
23
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Validate that an annotation is on a field with the expected base type.
|
|
31
|
+
*/
|
|
24
32
|
function validateFieldBaseType(token, doc, annotationName, expectedType) {
|
|
25
33
|
const errors = [];
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
if (!definition || !(0, __atscript_core.isRef)(definition)) return errors;
|
|
34
|
+
const definition = token.parentNode.getDefinition();
|
|
35
|
+
if (!definition || !(0, _atscript_core.isRef)(definition)) return errors;
|
|
29
36
|
const unwound = doc.unwindType(definition.id, definition.chain);
|
|
30
|
-
if (!unwound || !(0,
|
|
37
|
+
if (!unwound || !(0, _atscript_core.isPrimitive)(unwound.def)) return errors;
|
|
31
38
|
const ct = unwound.def.config.type;
|
|
32
39
|
const baseType = typeof ct === "object" ? ct.kind === "final" ? ct.value : ct.kind : ct;
|
|
33
40
|
const allowed = Array.isArray(expectedType) ? expectedType : [expectedType];
|
|
@@ -38,20 +45,35 @@ function validateFieldBaseType(token, doc, annotationName, expectedType) {
|
|
|
38
45
|
});
|
|
39
46
|
return errors;
|
|
40
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Extract target type name from a navigational field definition.
|
|
50
|
+
* Unwraps arrays (e.g., `Post[]` → `Post`).
|
|
51
|
+
*/
|
|
41
52
|
function getNavTargetTypeName(field) {
|
|
42
53
|
let def = field.getDefinition();
|
|
43
|
-
if ((0,
|
|
44
|
-
if ((0,
|
|
45
|
-
return undefined;
|
|
54
|
+
if ((0, _atscript_core.isArray)(def)) def = def?.getDefinition();
|
|
55
|
+
if ((0, _atscript_core.isRef)(def)) return def.id;
|
|
46
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Get the alias argument from an annotation on a field.
|
|
59
|
+
*/
|
|
47
60
|
function getAnnotationAlias(prop, annotationName) {
|
|
48
61
|
const annotations = prop.annotations?.filter((a) => a.name === annotationName);
|
|
49
|
-
if (!annotations || annotations.length === 0) return
|
|
50
|
-
return annotations[0].args.length > 0 ? annotations[0].args[0].text :
|
|
62
|
+
if (!annotations || annotations.length === 0) return;
|
|
63
|
+
return annotations[0].args.length > 0 ? annotations[0].args[0].text : void 0;
|
|
51
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Factory for @db.rel.onDelete / @db.rel.onUpdate — identical validation logic,
|
|
67
|
+
* only the annotation name and description verb differ.
|
|
68
|
+
*/
|
|
52
69
|
function refActionAnnotation(name) {
|
|
53
|
-
return new
|
|
54
|
-
description: `Referential action when the target ${name === "onDelete" ? "row is deleted" : "key is updated"}. Only valid on @db.rel.FK fields.\n\n
|
|
70
|
+
return new _atscript_core.AnnotationSpec({
|
|
71
|
+
description: `Referential action when the target ${name === "onDelete" ? "row is deleted" : "key is updated"}. Only valid on @db.rel.FK fields.\n\n**Example:**
|
|
72
|
+
\`\`\`atscript
|
|
73
|
+
@db.rel.FK
|
|
74
|
+
@db.rel.${name} "cascade"\nauthorId: User.id
|
|
75
|
+
\`\`\`
|
|
76
|
+
`,
|
|
55
77
|
nodeType: ["prop"],
|
|
56
78
|
argument: {
|
|
57
79
|
name: "action",
|
|
@@ -65,7 +87,7 @@ function refActionAnnotation(name) {
|
|
|
65
87
|
],
|
|
66
88
|
description: "Referential action: \"cascade\", \"restrict\", \"noAction\", \"setNull\", or \"setDefault\"."
|
|
67
89
|
},
|
|
68
|
-
validate(token, args,
|
|
90
|
+
validate(token, args, _doc) {
|
|
69
91
|
const errors = [];
|
|
70
92
|
const field = token.parentNode;
|
|
71
93
|
if (field.countAnnotations("db.rel.FK") === 0) errors.push({
|
|
@@ -95,8 +117,7 @@ function refActionAnnotation(name) {
|
|
|
95
117
|
for (const [, prop] of struct.props) {
|
|
96
118
|
if (prop.countAnnotations("db.rel.FK") === 0) continue;
|
|
97
119
|
if (prop.countAnnotations(annotationName) === 0) continue;
|
|
98
|
-
|
|
99
|
-
if (propFkAlias === fkAlias) count++;
|
|
120
|
+
if (getAnnotationAlias(prop, "db.rel.FK") === fkAlias) count++;
|
|
100
121
|
}
|
|
101
122
|
if (count > 1) errors.push({
|
|
102
123
|
message: `Composite FK '${fkAlias}' has @db.rel.${name} on multiple fields — declare it on exactly one`,
|
|
@@ -109,17 +130,18 @@ function refActionAnnotation(name) {
|
|
|
109
130
|
}
|
|
110
131
|
});
|
|
111
132
|
}
|
|
112
|
-
|
|
113
133
|
//#endregion
|
|
114
|
-
//#region
|
|
134
|
+
//#region src/shared/validation-utils.ts
|
|
135
|
+
/**
|
|
136
|
+
* Validate a ref annotation argument against the document's type registry.
|
|
137
|
+
* Returns diagnostic messages for unknown types or fields.
|
|
138
|
+
*/
|
|
115
139
|
function validateRefArgument(token, doc, options) {
|
|
116
140
|
const messages = [];
|
|
117
|
-
const
|
|
118
|
-
const [typeName, ...chain] = text.split(".");
|
|
141
|
+
const [typeName, ...chain] = token.text.split(".");
|
|
119
142
|
const decl = doc.getDeclarationOwnerNode(typeName);
|
|
120
143
|
if (!decl) {
|
|
121
|
-
|
|
122
|
-
if (regDef?.imported) return messages;
|
|
144
|
+
if (doc.registry.definitions.get(typeName)?.imported) return messages;
|
|
123
145
|
messages.push({
|
|
124
146
|
severity: 1,
|
|
125
147
|
message: `Unknown type '${typeName}'.`,
|
|
@@ -128,8 +150,7 @@ function validateRefArgument(token, doc, options) {
|
|
|
128
150
|
return messages;
|
|
129
151
|
}
|
|
130
152
|
if (chain.length > 0) {
|
|
131
|
-
|
|
132
|
-
if (!unwound) {
|
|
153
|
+
if (!doc.unwindType(typeName, chain)) {
|
|
133
154
|
messages.push({
|
|
134
155
|
severity: 1,
|
|
135
156
|
message: `Field '${chain.join(".")}' does not exist on type '${typeName}'.`,
|
|
@@ -139,8 +160,7 @@ function validateRefArgument(token, doc, options) {
|
|
|
139
160
|
}
|
|
140
161
|
}
|
|
141
162
|
if (options?.requireDbTable && decl.node) {
|
|
142
|
-
|
|
143
|
-
if (!hasDbTable) messages.push({
|
|
163
|
+
if (!(decl.node.countAnnotations("db.table") > 0)) messages.push({
|
|
144
164
|
severity: 1,
|
|
145
165
|
message: `Type '${typeName}' must have @db.table annotation.`,
|
|
146
166
|
range: token.range
|
|
@@ -148,28 +168,30 @@ function validateRefArgument(token, doc, options) {
|
|
|
148
168
|
}
|
|
149
169
|
return messages;
|
|
150
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Find all `@db.rel.FK` fields on a type that reference `targetTypeName`.
|
|
173
|
+
* Resolves `extends` to include inherited fields.
|
|
174
|
+
*/
|
|
151
175
|
function findFKFieldsPointingTo(doc, iface, targetTypeName, alias) {
|
|
152
176
|
const results = [];
|
|
153
177
|
let struct;
|
|
154
|
-
if ((0,
|
|
178
|
+
if ((0, _atscript_core.isInterface)(iface) && iface.hasExtends) {
|
|
155
179
|
const resolved = doc.resolveInterfaceExtends(iface);
|
|
156
|
-
if (resolved && (0,
|
|
180
|
+
if (resolved && (0, _atscript_core.isStructure)(resolved)) struct = resolved;
|
|
157
181
|
}
|
|
158
|
-
if (!struct) struct = (0,
|
|
182
|
+
if (!struct) struct = (0, _atscript_core.isStructure)(iface) ? iface : (0, _atscript_core.isInterface)(iface) && (0, _atscript_core.isStructure)(iface.getDefinition()) ? iface.getDefinition() : void 0;
|
|
159
183
|
if (!struct) return results;
|
|
160
184
|
for (const [name, prop] of struct.props) {
|
|
161
185
|
if (prop.countAnnotations("db.rel.FK") === 0) continue;
|
|
162
186
|
const def = prop.getDefinition();
|
|
163
|
-
if (!def || !(0,
|
|
187
|
+
if (!def || !(0, _atscript_core.isRef)(def)) continue;
|
|
164
188
|
const ref = def;
|
|
165
189
|
if (!ref.hasChain) continue;
|
|
166
190
|
const refTypeName = ref.id;
|
|
167
191
|
const refField = ref.chain.map((c) => c.text).join(".");
|
|
168
192
|
if (refTypeName !== targetTypeName) continue;
|
|
169
|
-
if (alias !==
|
|
170
|
-
|
|
171
|
-
const hasMatchingAlias = fkAnnotations?.some((a) => a.args.length > 0 && a.args[0].text === alias);
|
|
172
|
-
if (!hasMatchingAlias) continue;
|
|
193
|
+
if (alias !== void 0) {
|
|
194
|
+
if (!(prop.annotations?.filter((a) => a.name === "db.rel.FK"))?.some((a) => a.args.length > 0 && a.args[0].text === alias)) continue;
|
|
173
195
|
}
|
|
174
196
|
results.push({
|
|
175
197
|
name,
|
|
@@ -189,9 +211,20 @@ const viewAnnotationNames = [
|
|
|
189
211
|
"db.view.filter",
|
|
190
212
|
"db.view.materialized"
|
|
191
213
|
];
|
|
214
|
+
/**
|
|
215
|
+
* Check if a node has any @db.view.* annotation.
|
|
216
|
+
*/
|
|
192
217
|
function hasAnyViewAnnotation(node) {
|
|
193
218
|
return viewAnnotationNames.some((name) => node.countAnnotations(name) > 0);
|
|
194
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Validate that all type refs in a query expression are within the allowed scope.
|
|
222
|
+
*
|
|
223
|
+
* @param queryToken - The query arg token (must have .queryNode)
|
|
224
|
+
* @param allowedTypes - Type names allowed as qualified refs
|
|
225
|
+
* @param unqualifiedTarget - Type name for resolving unqualified refs, or null to disallow them
|
|
226
|
+
* @param doc - The document for type lookups
|
|
227
|
+
*/
|
|
195
228
|
function validateQueryScope(queryToken, allowedTypes, unqualifiedTarget, doc) {
|
|
196
229
|
const errors = [];
|
|
197
230
|
const queryNode = queryToken.queryNode;
|
|
@@ -209,13 +242,13 @@ function validateQueryScope(queryToken, allowedTypes, unqualifiedTarget, doc) {
|
|
|
209
242
|
severity: 1,
|
|
210
243
|
range: ref.fieldRef.range
|
|
211
244
|
});
|
|
212
|
-
else {
|
|
245
|
+
else {
|
|
213
246
|
const unwound = doc.unwindType(unqualifiedTarget);
|
|
214
247
|
if (unwound) {
|
|
215
248
|
const targetDef = unwound.def;
|
|
216
|
-
if ((0,
|
|
217
|
-
const struct = (0,
|
|
218
|
-
if (struct && (0,
|
|
249
|
+
if ((0, _atscript_core.isInterface)(targetDef) || (0, _atscript_core.isStructure)(targetDef)) {
|
|
250
|
+
const struct = (0, _atscript_core.isInterface)(targetDef) ? targetDef.getDefinition() : targetDef;
|
|
251
|
+
if (struct && (0, _atscript_core.isStructure)(struct) && !struct.props.has(ref.fieldRef.text)) errors.push({
|
|
219
252
|
message: `Field '${ref.fieldRef.text}' does not exist on '${unqualifiedTarget}'`,
|
|
220
253
|
severity: 1,
|
|
221
254
|
range: ref.fieldRef.range
|
|
@@ -225,8 +258,8 @@ else {
|
|
|
225
258
|
}
|
|
226
259
|
}
|
|
227
260
|
function walkExpr(expr) {
|
|
228
|
-
if ((0,
|
|
229
|
-
else if ((0,
|
|
261
|
+
if ((0, _atscript_core.isQueryLogical)(expr)) for (const operand of expr.operands) walkExpr(operand);
|
|
262
|
+
else if ((0, _atscript_core.isQueryComparison)(expr)) {
|
|
230
263
|
walkFieldRef(expr.left);
|
|
231
264
|
if (expr.right && "fieldRef" in expr.right) walkFieldRef(expr.right);
|
|
232
265
|
}
|
|
@@ -234,71 +267,70 @@ else if ((0, __atscript_core.isQueryComparison)(expr)) {
|
|
|
234
267
|
walkExpr(queryNode.expression);
|
|
235
268
|
return errors;
|
|
236
269
|
}
|
|
237
|
-
|
|
238
270
|
//#endregion
|
|
239
|
-
Object.defineProperty(exports,
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
271
|
+
Object.defineProperty(exports, "findFKFieldsPointingTo", {
|
|
272
|
+
enumerable: true,
|
|
273
|
+
get: function() {
|
|
274
|
+
return findFKFieldsPointingTo;
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
Object.defineProperty(exports, "getAnnotationAlias", {
|
|
278
|
+
enumerable: true,
|
|
279
|
+
get: function() {
|
|
280
|
+
return getAnnotationAlias;
|
|
281
|
+
}
|
|
244
282
|
});
|
|
245
|
-
Object.defineProperty(exports,
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
283
|
+
Object.defineProperty(exports, "getDbTableOwner", {
|
|
284
|
+
enumerable: true,
|
|
285
|
+
get: function() {
|
|
286
|
+
return getDbTableOwner;
|
|
287
|
+
}
|
|
250
288
|
});
|
|
251
|
-
Object.defineProperty(exports,
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
289
|
+
Object.defineProperty(exports, "getNavTargetTypeName", {
|
|
290
|
+
enumerable: true,
|
|
291
|
+
get: function() {
|
|
292
|
+
return getNavTargetTypeName;
|
|
293
|
+
}
|
|
256
294
|
});
|
|
257
|
-
Object.defineProperty(exports,
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
295
|
+
Object.defineProperty(exports, "getParentStruct", {
|
|
296
|
+
enumerable: true,
|
|
297
|
+
get: function() {
|
|
298
|
+
return getParentStruct;
|
|
299
|
+
}
|
|
262
300
|
});
|
|
263
|
-
Object.defineProperty(exports,
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
301
|
+
Object.defineProperty(exports, "getParentTypeName", {
|
|
302
|
+
enumerable: true,
|
|
303
|
+
get: function() {
|
|
304
|
+
return getParentTypeName;
|
|
305
|
+
}
|
|
268
306
|
});
|
|
269
|
-
Object.defineProperty(exports,
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
307
|
+
Object.defineProperty(exports, "hasAnyViewAnnotation", {
|
|
308
|
+
enumerable: true,
|
|
309
|
+
get: function() {
|
|
310
|
+
return hasAnyViewAnnotation;
|
|
311
|
+
}
|
|
274
312
|
});
|
|
275
|
-
Object.defineProperty(exports,
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
313
|
+
Object.defineProperty(exports, "refActionAnnotation", {
|
|
314
|
+
enumerable: true,
|
|
315
|
+
get: function() {
|
|
316
|
+
return refActionAnnotation;
|
|
317
|
+
}
|
|
280
318
|
});
|
|
281
|
-
Object.defineProperty(exports,
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
319
|
+
Object.defineProperty(exports, "validateFieldBaseType", {
|
|
320
|
+
enumerable: true,
|
|
321
|
+
get: function() {
|
|
322
|
+
return validateFieldBaseType;
|
|
323
|
+
}
|
|
286
324
|
});
|
|
287
|
-
Object.defineProperty(exports,
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
325
|
+
Object.defineProperty(exports, "validateQueryScope", {
|
|
326
|
+
enumerable: true,
|
|
327
|
+
get: function() {
|
|
328
|
+
return validateQueryScope;
|
|
329
|
+
}
|
|
292
330
|
});
|
|
293
|
-
Object.defineProperty(exports,
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
331
|
+
Object.defineProperty(exports, "validateRefArgument", {
|
|
332
|
+
enumerable: true,
|
|
333
|
+
get: function() {
|
|
334
|
+
return validateRefArgument;
|
|
335
|
+
}
|
|
298
336
|
});
|
|
299
|
-
Object.defineProperty(exports, 'validateRefArgument', {
|
|
300
|
-
enumerable: true,
|
|
301
|
-
get: function () {
|
|
302
|
-
return validateRefArgument;
|
|
303
|
-
}
|
|
304
|
-
});
|
|
@@ -1,28 +1,37 @@
|
|
|
1
1
|
import { AnnotationSpec, isArray, isInterface, isPrimitive, isQueryComparison, isQueryLogical, isRef, isStructure } from "@atscript/core";
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
//#region src/shared/annotation-utils.ts
|
|
3
|
+
/**
|
|
4
|
+
* Traverse from annotation token → prop → structure → interface
|
|
5
|
+
* to check if the parent interface has @db.table.
|
|
6
|
+
*/
|
|
4
7
|
function getDbTableOwner(token) {
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
if (!struct || !isStructure(struct)) return undefined;
|
|
8
|
+
const struct = token.parentNode.ownerNode;
|
|
9
|
+
if (!struct || !isStructure(struct)) return;
|
|
8
10
|
const iface = struct.ownerNode;
|
|
9
11
|
return iface && isInterface(iface) ? iface : struct;
|
|
10
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Get the parent structure node from an annotation token.
|
|
15
|
+
*/
|
|
11
16
|
function getParentStruct(token) {
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
return struct && isStructure(struct) ? struct : undefined;
|
|
17
|
+
const struct = token.parentNode.ownerNode;
|
|
18
|
+
return struct && isStructure(struct) ? struct : void 0;
|
|
15
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Get the parent interface name (for error messages and cross-type resolution).
|
|
22
|
+
*/
|
|
16
23
|
function getParentTypeName(token) {
|
|
17
24
|
const struct = getParentStruct(token);
|
|
18
|
-
if (!struct) return
|
|
25
|
+
if (!struct) return;
|
|
19
26
|
const iface = struct.ownerNode;
|
|
20
27
|
return iface && isInterface(iface) ? iface.id : struct.id;
|
|
21
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Validate that an annotation is on a field with the expected base type.
|
|
31
|
+
*/
|
|
22
32
|
function validateFieldBaseType(token, doc, annotationName, expectedType) {
|
|
23
33
|
const errors = [];
|
|
24
|
-
const
|
|
25
|
-
const definition = field.getDefinition();
|
|
34
|
+
const definition = token.parentNode.getDefinition();
|
|
26
35
|
if (!definition || !isRef(definition)) return errors;
|
|
27
36
|
const unwound = doc.unwindType(definition.id, definition.chain);
|
|
28
37
|
if (!unwound || !isPrimitive(unwound.def)) return errors;
|
|
@@ -36,20 +45,35 @@ function validateFieldBaseType(token, doc, annotationName, expectedType) {
|
|
|
36
45
|
});
|
|
37
46
|
return errors;
|
|
38
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Extract target type name from a navigational field definition.
|
|
50
|
+
* Unwraps arrays (e.g., `Post[]` → `Post`).
|
|
51
|
+
*/
|
|
39
52
|
function getNavTargetTypeName(field) {
|
|
40
53
|
let def = field.getDefinition();
|
|
41
54
|
if (isArray(def)) def = def?.getDefinition();
|
|
42
55
|
if (isRef(def)) return def.id;
|
|
43
|
-
return undefined;
|
|
44
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Get the alias argument from an annotation on a field.
|
|
59
|
+
*/
|
|
45
60
|
function getAnnotationAlias(prop, annotationName) {
|
|
46
61
|
const annotations = prop.annotations?.filter((a) => a.name === annotationName);
|
|
47
|
-
if (!annotations || annotations.length === 0) return
|
|
48
|
-
return annotations[0].args.length > 0 ? annotations[0].args[0].text :
|
|
62
|
+
if (!annotations || annotations.length === 0) return;
|
|
63
|
+
return annotations[0].args.length > 0 ? annotations[0].args[0].text : void 0;
|
|
49
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Factory for @db.rel.onDelete / @db.rel.onUpdate — identical validation logic,
|
|
67
|
+
* only the annotation name and description verb differ.
|
|
68
|
+
*/
|
|
50
69
|
function refActionAnnotation(name) {
|
|
51
70
|
return new AnnotationSpec({
|
|
52
|
-
description: `Referential action when the target ${name === "onDelete" ? "row is deleted" : "key is updated"}. Only valid on @db.rel.FK fields.\n\n
|
|
71
|
+
description: `Referential action when the target ${name === "onDelete" ? "row is deleted" : "key is updated"}. Only valid on @db.rel.FK fields.\n\n**Example:**
|
|
72
|
+
\`\`\`atscript
|
|
73
|
+
@db.rel.FK
|
|
74
|
+
@db.rel.${name} "cascade"\nauthorId: User.id
|
|
75
|
+
\`\`\`
|
|
76
|
+
`,
|
|
53
77
|
nodeType: ["prop"],
|
|
54
78
|
argument: {
|
|
55
79
|
name: "action",
|
|
@@ -63,7 +87,7 @@ function refActionAnnotation(name) {
|
|
|
63
87
|
],
|
|
64
88
|
description: "Referential action: \"cascade\", \"restrict\", \"noAction\", \"setNull\", or \"setDefault\"."
|
|
65
89
|
},
|
|
66
|
-
validate(token, args,
|
|
90
|
+
validate(token, args, _doc) {
|
|
67
91
|
const errors = [];
|
|
68
92
|
const field = token.parentNode;
|
|
69
93
|
if (field.countAnnotations("db.rel.FK") === 0) errors.push({
|
|
@@ -93,8 +117,7 @@ function refActionAnnotation(name) {
|
|
|
93
117
|
for (const [, prop] of struct.props) {
|
|
94
118
|
if (prop.countAnnotations("db.rel.FK") === 0) continue;
|
|
95
119
|
if (prop.countAnnotations(annotationName) === 0) continue;
|
|
96
|
-
|
|
97
|
-
if (propFkAlias === fkAlias) count++;
|
|
120
|
+
if (getAnnotationAlias(prop, "db.rel.FK") === fkAlias) count++;
|
|
98
121
|
}
|
|
99
122
|
if (count > 1) errors.push({
|
|
100
123
|
message: `Composite FK '${fkAlias}' has @db.rel.${name} on multiple fields — declare it on exactly one`,
|
|
@@ -107,17 +130,18 @@ function refActionAnnotation(name) {
|
|
|
107
130
|
}
|
|
108
131
|
});
|
|
109
132
|
}
|
|
110
|
-
|
|
111
133
|
//#endregion
|
|
112
|
-
//#region
|
|
134
|
+
//#region src/shared/validation-utils.ts
|
|
135
|
+
/**
|
|
136
|
+
* Validate a ref annotation argument against the document's type registry.
|
|
137
|
+
* Returns diagnostic messages for unknown types or fields.
|
|
138
|
+
*/
|
|
113
139
|
function validateRefArgument(token, doc, options) {
|
|
114
140
|
const messages = [];
|
|
115
|
-
const
|
|
116
|
-
const [typeName, ...chain] = text.split(".");
|
|
141
|
+
const [typeName, ...chain] = token.text.split(".");
|
|
117
142
|
const decl = doc.getDeclarationOwnerNode(typeName);
|
|
118
143
|
if (!decl) {
|
|
119
|
-
|
|
120
|
-
if (regDef?.imported) return messages;
|
|
144
|
+
if (doc.registry.definitions.get(typeName)?.imported) return messages;
|
|
121
145
|
messages.push({
|
|
122
146
|
severity: 1,
|
|
123
147
|
message: `Unknown type '${typeName}'.`,
|
|
@@ -126,8 +150,7 @@ function validateRefArgument(token, doc, options) {
|
|
|
126
150
|
return messages;
|
|
127
151
|
}
|
|
128
152
|
if (chain.length > 0) {
|
|
129
|
-
|
|
130
|
-
if (!unwound) {
|
|
153
|
+
if (!doc.unwindType(typeName, chain)) {
|
|
131
154
|
messages.push({
|
|
132
155
|
severity: 1,
|
|
133
156
|
message: `Field '${chain.join(".")}' does not exist on type '${typeName}'.`,
|
|
@@ -137,8 +160,7 @@ function validateRefArgument(token, doc, options) {
|
|
|
137
160
|
}
|
|
138
161
|
}
|
|
139
162
|
if (options?.requireDbTable && decl.node) {
|
|
140
|
-
|
|
141
|
-
if (!hasDbTable) messages.push({
|
|
163
|
+
if (!(decl.node.countAnnotations("db.table") > 0)) messages.push({
|
|
142
164
|
severity: 1,
|
|
143
165
|
message: `Type '${typeName}' must have @db.table annotation.`,
|
|
144
166
|
range: token.range
|
|
@@ -146,6 +168,10 @@ function validateRefArgument(token, doc, options) {
|
|
|
146
168
|
}
|
|
147
169
|
return messages;
|
|
148
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Find all `@db.rel.FK` fields on a type that reference `targetTypeName`.
|
|
173
|
+
* Resolves `extends` to include inherited fields.
|
|
174
|
+
*/
|
|
149
175
|
function findFKFieldsPointingTo(doc, iface, targetTypeName, alias) {
|
|
150
176
|
const results = [];
|
|
151
177
|
let struct;
|
|
@@ -153,7 +179,7 @@ function findFKFieldsPointingTo(doc, iface, targetTypeName, alias) {
|
|
|
153
179
|
const resolved = doc.resolveInterfaceExtends(iface);
|
|
154
180
|
if (resolved && isStructure(resolved)) struct = resolved;
|
|
155
181
|
}
|
|
156
|
-
if (!struct) struct = isStructure(iface) ? iface : isInterface(iface) && isStructure(iface.getDefinition()) ? iface.getDefinition() :
|
|
182
|
+
if (!struct) struct = isStructure(iface) ? iface : isInterface(iface) && isStructure(iface.getDefinition()) ? iface.getDefinition() : void 0;
|
|
157
183
|
if (!struct) return results;
|
|
158
184
|
for (const [name, prop] of struct.props) {
|
|
159
185
|
if (prop.countAnnotations("db.rel.FK") === 0) continue;
|
|
@@ -164,10 +190,8 @@ function findFKFieldsPointingTo(doc, iface, targetTypeName, alias) {
|
|
|
164
190
|
const refTypeName = ref.id;
|
|
165
191
|
const refField = ref.chain.map((c) => c.text).join(".");
|
|
166
192
|
if (refTypeName !== targetTypeName) continue;
|
|
167
|
-
if (alias !==
|
|
168
|
-
|
|
169
|
-
const hasMatchingAlias = fkAnnotations?.some((a) => a.args.length > 0 && a.args[0].text === alias);
|
|
170
|
-
if (!hasMatchingAlias) continue;
|
|
193
|
+
if (alias !== void 0) {
|
|
194
|
+
if (!(prop.annotations?.filter((a) => a.name === "db.rel.FK"))?.some((a) => a.args.length > 0 && a.args[0].text === alias)) continue;
|
|
171
195
|
}
|
|
172
196
|
results.push({
|
|
173
197
|
name,
|
|
@@ -187,9 +211,20 @@ const viewAnnotationNames = [
|
|
|
187
211
|
"db.view.filter",
|
|
188
212
|
"db.view.materialized"
|
|
189
213
|
];
|
|
214
|
+
/**
|
|
215
|
+
* Check if a node has any @db.view.* annotation.
|
|
216
|
+
*/
|
|
190
217
|
function hasAnyViewAnnotation(node) {
|
|
191
218
|
return viewAnnotationNames.some((name) => node.countAnnotations(name) > 0);
|
|
192
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Validate that all type refs in a query expression are within the allowed scope.
|
|
222
|
+
*
|
|
223
|
+
* @param queryToken - The query arg token (must have .queryNode)
|
|
224
|
+
* @param allowedTypes - Type names allowed as qualified refs
|
|
225
|
+
* @param unqualifiedTarget - Type name for resolving unqualified refs, or null to disallow them
|
|
226
|
+
* @param doc - The document for type lookups
|
|
227
|
+
*/
|
|
193
228
|
function validateQueryScope(queryToken, allowedTypes, unqualifiedTarget, doc) {
|
|
194
229
|
const errors = [];
|
|
195
230
|
const queryNode = queryToken.queryNode;
|
|
@@ -207,7 +242,7 @@ function validateQueryScope(queryToken, allowedTypes, unqualifiedTarget, doc) {
|
|
|
207
242
|
severity: 1,
|
|
208
243
|
range: ref.fieldRef.range
|
|
209
244
|
});
|
|
210
|
-
else {
|
|
245
|
+
else {
|
|
211
246
|
const unwound = doc.unwindType(unqualifiedTarget);
|
|
212
247
|
if (unwound) {
|
|
213
248
|
const targetDef = unwound.def;
|
|
@@ -224,7 +259,7 @@ else {
|
|
|
224
259
|
}
|
|
225
260
|
function walkExpr(expr) {
|
|
226
261
|
if (isQueryLogical(expr)) for (const operand of expr.operands) walkExpr(operand);
|
|
227
|
-
else if (isQueryComparison(expr)) {
|
|
262
|
+
else if (isQueryComparison(expr)) {
|
|
228
263
|
walkFieldRef(expr.left);
|
|
229
264
|
if (expr.right && "fieldRef" in expr.right) walkFieldRef(expr.right);
|
|
230
265
|
}
|
|
@@ -232,6 +267,5 @@ else if (isQueryComparison(expr)) {
|
|
|
232
267
|
walkExpr(queryNode.expression);
|
|
233
268
|
return errors;
|
|
234
269
|
}
|
|
235
|
-
|
|
236
270
|
//#endregion
|
|
237
|
-
export {
|
|
271
|
+
export { getAnnotationAlias as a, getParentStruct as c, validateFieldBaseType as d, validateRefArgument as i, getParentTypeName as l, hasAnyViewAnnotation as n, getDbTableOwner as o, validateQueryScope as r, getNavTargetTypeName as s, findFKFieldsPointingTo as t, refActionAnnotation as u };
|