@atscript/db 0.1.161 → 0.1.162
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agg.cjs +1 -1
- package/dist/agg.d.cts +1 -1
- package/dist/agg.d.mts +1 -1
- package/dist/agg.mjs +1 -1
- package/dist/{aggregate-fns-B_sgl4mB.mjs → aggregate-fns-DsS9UQVB.mjs} +1 -1
- package/dist/{aggregate-fns-DFtKAv7Q.cjs → aggregate-fns-DupYzFD7.cjs} +1 -1
- package/dist/{buckets-8mG67Mtw.d.mts → buckets-DUTcrT4Z.d.mts} +475 -57
- package/dist/{buckets-B4T_alUe.d.cts → buckets-_iZV39Z3.d.cts} +475 -57
- package/dist/{column-diff-DpeUbhEI.cjs → column-diff-CZsMGQ_w.cjs} +846 -232
- package/dist/{column-diff-DR1glykD.mjs → column-diff-pdn53AIj.mjs} +829 -233
- package/dist/{validator-B3xlbrWf.d.cts → db-error-B_DRL1E9.d.cts} +18 -89
- package/dist/{validator-B3xlbrWf.d.mts → db-error-B_DRL1E9.d.mts} +18 -89
- package/dist/{db-error-JcfJqqpk.cjs → db-error-DmF1_xrz.cjs} +13 -0
- package/dist/{db-error-eSsQX7rt.mjs → db-error-hraC12B9.mjs} +8 -1
- package/dist/{db-validator-plugin-BWy60OvG.d.cts → db-validator-plugin-wRqMYNBI.d.cts} +6 -0
- package/dist/{db-validator-plugin-BWy60OvG.d.mts → db-validator-plugin-wRqMYNBI.d.mts} +6 -0
- package/dist/{fk-diff-5gX42hBt.d.mts → fk-diff-B5jW68T3.d.cts} +34 -10
- package/dist/{fk-diff-BWdQ7kRZ.d.cts → fk-diff-PhVBYohc.d.mts} +34 -10
- package/dist/index.cjs +12 -11
- package/dist/index.d.cts +24 -21
- package/dist/index.d.mts +24 -21
- package/dist/index.mjs +9 -12
- package/dist/{nested-writer-BOj_c_lI.mjs → nested-writer-ClUX0DB0.mjs} +16 -22
- package/dist/{nested-writer-Bl1SZS7x.cjs → nested-writer-yCy6wGuN.cjs} +16 -22
- package/dist/{object-CtPYTIvy.d.cts → object-Czia_qD4.d.cts} +7 -1
- package/dist/{object-CtPYTIvy.d.mts → object-Czia_qD4.d.mts} +7 -1
- package/dist/{object-Djg28csK.cjs → object-GjsuPNqo.cjs} +19 -0
- package/dist/{object-TkiJQ-Dp.mjs → object-PQ1J8jri.mjs} +14 -1
- package/dist/ops.cjs +6 -21
- package/dist/ops.mjs +6 -21
- package/dist/plugin.cjs +263 -323
- package/dist/plugin.mjs +163 -223
- package/dist/rel.cjs +2 -2
- package/dist/rel.d.cts +3 -3
- package/dist/rel.d.mts +3 -3
- package/dist/rel.mjs +2 -2
- package/dist/{relation-helpers-17_2f5hX.d.mts → relation-helpers-BAEkP4bH.d.cts} +1 -1
- package/dist/{relation-helpers-BLBrLQYi.d.cts → relation-helpers-BSYwxOSA.d.mts} +1 -1
- package/dist/{relation-loader-DeulLrAw.cjs → relation-loader-DujL_Mh9.cjs} +1 -1
- package/dist/{relation-loader-CNBMPfRr.mjs → relation-loader-Qv3LrQ42.mjs} +1 -1
- package/dist/{search-fields-CE8cmj9X.mjs → search-fields-0U0FAFGj.mjs} +47 -1
- package/dist/{search-fields-CT_UZ9fg.cjs → search-fields-Bi1n-pB1.cjs} +64 -0
- package/dist/search-fields-CHqqcC3W.d.cts +30 -0
- package/dist/search-fields-CHqqcC3W.d.mts +30 -0
- package/dist/shared.cjs +42 -26
- package/dist/shared.d.cts +98 -4
- package/dist/shared.d.mts +98 -4
- package/dist/shared.mjs +4 -2
- package/dist/sync.cjs +228 -65
- package/dist/sync.d.cts +41 -5
- package/dist/sync.d.mts +41 -5
- package/dist/sync.mjs +228 -66
- package/dist/validator-BcBtg8yW.d.cts +88 -0
- package/dist/validator-BcBtg8yW.d.mts +88 -0
- package/dist/{validator-B4YDLUkj.cjs → validator-DK9Ijpo7.cjs} +3 -2
- package/dist/{validator-B3fPJjS1.mjs → validator-DuDWsIdQ.mjs} +3 -2
- package/dist/validator.cjs +3 -3
- package/dist/validator.d.cts +4 -3
- package/dist/validator.d.mts +4 -3
- package/dist/validator.mjs +3 -3
- package/dist/{validation-utils-CtLNlBtB.cjs → view-validation-D50KWU7O.cjs} +452 -18
- package/dist/{validation-utils-BGjqc2jU.mjs → view-validation-QbqqXEAM.mjs} +364 -20
- package/package.json +6 -6
|
@@ -1,16 +1,59 @@
|
|
|
1
|
-
const
|
|
1
|
+
const require_aggregate_fns = require("./aggregate-fns-DupYzFD7.cjs");
|
|
2
|
+
const require_search_fields = require("./search-fields-Bi1n-pB1.cjs");
|
|
2
3
|
let _atscript_core = require("@atscript/core");
|
|
3
4
|
//#region src/shared/annotation-utils.ts
|
|
4
|
-
/**
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Names of a field's effective annotations: its own plus the ones it inherits
|
|
7
|
+
* through its type ref (`price: Order.price`), with the `passedWhenReferred:
|
|
8
|
+
* false` ones dropped — the set codegen writes into the field's runtime
|
|
9
|
+
* metadata (`AtscriptDoc.evalAnnotationsForNode`). Compile checks that mirror
|
|
10
|
+
* a runtime metadata check read this, not the own-only `countAnnotations`.
|
|
11
|
+
* @since 0.1.162
|
|
12
|
+
*/
|
|
13
|
+
function effectiveAnnotationNames(field, doc) {
|
|
14
|
+
const names = new Set(field.annotations?.map((a) => a.name));
|
|
15
|
+
for (const a of doc.evalAnnotationsForNode(field) ?? []) names.add(a.name);
|
|
16
|
+
return names;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Asserts the field carrying this annotation does not also carry any of
|
|
20
|
+
* `others` — its own or inherited through its type ref (since 0.1.162).
|
|
21
|
+
*/
|
|
22
|
+
function validateExclusiveWith(token, selfName, others, doc) {
|
|
6
23
|
const errors = [];
|
|
7
24
|
const field = token.parentNode;
|
|
8
|
-
|
|
9
|
-
|
|
25
|
+
const effective = effectiveAnnotationNames(field, doc);
|
|
26
|
+
for (const { key, displayName } of others) {
|
|
27
|
+
if (!effective.has(key)) continue;
|
|
28
|
+
const inherited = field.countAnnotations(key) === 0;
|
|
29
|
+
errors.push({
|
|
30
|
+
message: inherited ? `${selfName} cannot coexist with ${displayName ?? `@${key}`}, which this field inherits from its type reference — pick one form, not both` : `${selfName} cannot coexist with ${displayName ?? `@${key}`} on the same field — pick one form, not both`,
|
|
31
|
+
severity: 1,
|
|
32
|
+
range: token.range
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return errors;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Reports each of `pairs` (`[annotation, why]`) the field carrying `token`
|
|
39
|
+
* also carries — its own or inherited through its type ref (the field's
|
|
40
|
+
* effective annotations, what the runtime mirror reads off the metadata):
|
|
41
|
+
* `${tag} cannot coexist with @${annotation} — ${why}`.
|
|
42
|
+
* @since 0.1.162
|
|
43
|
+
*/
|
|
44
|
+
function validateIncompatible(token, tag, pairs, doc) {
|
|
45
|
+
const effective = effectiveAnnotationNames(token.parentNode, doc);
|
|
46
|
+
return pairs.filter(([name]) => effective.has(name)).map(([name, why]) => ({
|
|
47
|
+
message: `${tag} cannot coexist with @${name} — ${why}`,
|
|
10
48
|
severity: 1,
|
|
11
49
|
range: token.range
|
|
12
|
-
});
|
|
13
|
-
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
/** Whether a prop is a field of a view interface (`@db.view` / `@db.view.for`). */
|
|
53
|
+
function isViewField(field) {
|
|
54
|
+
const struct = field.ownerNode;
|
|
55
|
+
const owner = struct && (0, _atscript_core.isStructure)(struct) ? struct.ownerNode : void 0;
|
|
56
|
+
return !!owner && (0, _atscript_core.isInterface)(owner) && (owner.countAnnotations("db.view") > 0 || owner.countAnnotations("db.view.for") > 0);
|
|
14
57
|
}
|
|
15
58
|
/** Primitive base type (`string`, `number`, `decimal`, …) of a resolved definition, if it is a primitive. */
|
|
16
59
|
function primitiveBaseType(def) {
|
|
@@ -52,15 +95,19 @@ function searchFieldVerdict(field, doc) {
|
|
|
52
95
|
const leaf = doc.unwindType(def.id, def.chain)?.def;
|
|
53
96
|
const base = primitiveBaseType(leaf);
|
|
54
97
|
if (base === void 0) return { kind: "other" };
|
|
98
|
+
const effective = effectiveAnnotationNames(field, doc);
|
|
55
99
|
const kind = require_search_fields.searchKindOf({
|
|
56
100
|
base,
|
|
57
101
|
tags: (0, _atscript_core.isPrimitive)(leaf) ? leaf.tags : void 0,
|
|
58
|
-
expectInt:
|
|
59
|
-
increment:
|
|
60
|
-
now:
|
|
61
|
-
precision:
|
|
102
|
+
expectInt: effective.has("expect.int"),
|
|
103
|
+
increment: effective.has("db.default.increment"),
|
|
104
|
+
now: effective.has("db.default.now"),
|
|
105
|
+
precision: effective.has("db.column.precision")
|
|
62
106
|
});
|
|
63
|
-
if (typeof kind === "object")
|
|
107
|
+
if (typeof kind === "object") {
|
|
108
|
+
if (kind.problem === require_search_fields.SEARCH_PROBLEM.float && isViewField(field)) return { problem: require_search_fields.SEARCH_PROBLEM.floatViewField };
|
|
109
|
+
return kind;
|
|
110
|
+
}
|
|
64
111
|
return { kind: kind === "integer" ? "integer" : base === "string" ? "string" : "other" };
|
|
65
112
|
}
|
|
66
113
|
/**
|
|
@@ -173,12 +220,7 @@ function getAnnotationAlias(prop, annotationName) {
|
|
|
173
220
|
*/
|
|
174
221
|
function refActionAnnotation(name) {
|
|
175
222
|
return new _atscript_core.AnnotationSpec({
|
|
176
|
-
description: `Referential action when the target ${name === "onDelete" ? "row is deleted" : "key is updated"}. Only valid on @db.rel.FK fields
|
|
177
|
-
\`\`\`atscript
|
|
178
|
-
@db.rel.FK
|
|
179
|
-
@db.rel.${name} "cascade"\nauthorId: User.id
|
|
180
|
-
\`\`\`
|
|
181
|
-
`,
|
|
223
|
+
description: (0, _atscript_core.annotationExample)(`Referential action when the target ${name === "onDelete" ? "row is deleted" : "key is updated"}. Only valid on @db.rel.FK fields.`, "@db.rel.FK", `@db.rel.${name} "cascade"`, "authorId: User.id"),
|
|
182
224
|
nodeType: ["prop"],
|
|
183
225
|
passedWhenReferred: false,
|
|
184
226
|
argument: {
|
|
@@ -468,18 +510,356 @@ function forEachFieldRef(expr, fn) {
|
|
|
468
510
|
}
|
|
469
511
|
}
|
|
470
512
|
//#endregion
|
|
513
|
+
//#region src/shared/view-validation.ts
|
|
514
|
+
/** `@db.agg.*` annotations that are never NULL (the counts: 0 over no value). */
|
|
515
|
+
const NULL_SAFE_AGG_ANNOTATIONS = require_aggregate_fns.SUPPORTED_AGGREGATE_FNS.filter((fn) => !require_aggregate_fns.NULL_WHEN_EMPTY_AGGREGATE_FNS.has(fn)).map((fn) => `db.agg.${fn}`);
|
|
516
|
+
/**
|
|
517
|
+
* Walks a chain ref step by step (`Order.payload`, `Order.payload.customer`, …)
|
|
518
|
+
* and reports the JSON root, arrays, encryption, optionality and the leaf type.
|
|
519
|
+
* A chain shorter than 2 is not a field path (`resolved: false`).
|
|
520
|
+
* @since 0.1.141
|
|
521
|
+
*/
|
|
522
|
+
function jsonChainInfo(ref, doc) {
|
|
523
|
+
const typeName = ref.id ?? "";
|
|
524
|
+
const chain = ref.chain.map((t) => t.text);
|
|
525
|
+
const info = {
|
|
526
|
+
typeName,
|
|
527
|
+
chain,
|
|
528
|
+
resolved: false,
|
|
529
|
+
viaArray: false,
|
|
530
|
+
viaEncrypted: false,
|
|
531
|
+
optional: false
|
|
532
|
+
};
|
|
533
|
+
if (!typeName || chain.length < 2) return info;
|
|
534
|
+
for (let i = 1; i <= chain.length; i++) {
|
|
535
|
+
const step = doc.unwindType(typeName, chain.slice(0, i));
|
|
536
|
+
if (!step) return info;
|
|
537
|
+
const node = step.node;
|
|
538
|
+
const prop = node && (0, _atscript_core.isProp)(node) ? node : void 0;
|
|
539
|
+
if (prop?.has("optional")) info.optional = true;
|
|
540
|
+
if (prop && prop.countAnnotations("db.encrypted") > 0) info.viaEncrypted = true;
|
|
541
|
+
const isJsonProp = prop !== void 0 && prop.countAnnotations("db.json") > 0;
|
|
542
|
+
const isArr = (0, _atscript_core.isArray)(step.def);
|
|
543
|
+
if (isArr) info.viaArray = true;
|
|
544
|
+
if (info.jsonRoot === void 0 && (isJsonProp || isArr)) info.jsonRoot = i;
|
|
545
|
+
if (i === chain.length) info.leafType = primitiveBaseType(step.def);
|
|
546
|
+
}
|
|
547
|
+
info.resolved = true;
|
|
548
|
+
return info;
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* The read seals a view field takes from the source field it reads, as the
|
|
552
|
+
* compiler sees them (compile-time mirror of the runtime `sourceFieldSeals`,
|
|
553
|
+
* which stamps them onto the view column — these annotations are
|
|
554
|
+
* `passedWhenReferred: false`, so they are not in the field's own metadata):
|
|
555
|
+
* - `writeOnly` — the source path or an ancestor object is `@db.writeOnly`;
|
|
556
|
+
* - `encrypted` — the source path or an ancestor object is `@db.encrypted`.
|
|
557
|
+
*
|
|
558
|
+
* (A `@db.json` source is no seal: a view reads a primitive JSON column's
|
|
559
|
+
* typed value, and an object one needs its own `@db.json`.)
|
|
560
|
+
*
|
|
561
|
+
* The source is the chain ref (`pin?: Account.pin`), or else the entry table's
|
|
562
|
+
* same-named field (or an aggregate's field) of a `@db.view.for` view. A source
|
|
563
|
+
* field that is itself a view field passes on its own seals. Computed fields
|
|
564
|
+
* and fields of anything but a view report no seal.
|
|
565
|
+
* @since 0.1.162
|
|
566
|
+
*/
|
|
567
|
+
function viewFieldSeals(field, doc, depth = 0) {
|
|
568
|
+
const seals = {
|
|
569
|
+
writeOnly: false,
|
|
570
|
+
encrypted: false
|
|
571
|
+
};
|
|
572
|
+
if (depth > 8 || !isViewField(field) || field.countAnnotations("db.compute") > 0) return seals;
|
|
573
|
+
let typeName;
|
|
574
|
+
let path;
|
|
575
|
+
const def = field.getDefinition();
|
|
576
|
+
if (def && (0, _atscript_core.isRef)(def) && def.hasChain) {
|
|
577
|
+
typeName = def.id;
|
|
578
|
+
path = def.chain.map((t) => t.text);
|
|
579
|
+
} else {
|
|
580
|
+
const view = field.ownerNode.ownerNode;
|
|
581
|
+
typeName = getAnnotationAlias(view, "db.view.for");
|
|
582
|
+
const agg = field.annotations?.find((a) => require_aggregate_fns.AGG_ANNOTATIONS.includes(a.name));
|
|
583
|
+
const aggField = agg?.args[0]?.text;
|
|
584
|
+
if (agg && (!aggField || aggField === "*")) return seals;
|
|
585
|
+
path = (aggField ?? field.id ?? "").split(".");
|
|
586
|
+
}
|
|
587
|
+
if (!typeName || !path.length || !path[0]) return seals;
|
|
588
|
+
let step = doc.unwindType(typeName, [path[0]]);
|
|
589
|
+
for (let i = 1; step; i++) {
|
|
590
|
+
const node = step.node;
|
|
591
|
+
if (node && (0, _atscript_core.isProp)(node)) {
|
|
592
|
+
if (node.countAnnotations("db.writeOnly") > 0) seals.writeOnly = true;
|
|
593
|
+
if (node.countAnnotations("db.encrypted") > 0) seals.encrypted = true;
|
|
594
|
+
const inner = viewFieldSeals(node, step.doc, depth + 1);
|
|
595
|
+
seals.writeOnly ||= inner.writeOnly;
|
|
596
|
+
seals.encrypted ||= inner.encrypted;
|
|
597
|
+
}
|
|
598
|
+
if (i === path.length) break;
|
|
599
|
+
step = propStep(step, path[i]);
|
|
600
|
+
}
|
|
601
|
+
return seals;
|
|
602
|
+
}
|
|
603
|
+
/** The step to the `name` prop of `step`'s object definition — its type ref resolved. */
|
|
604
|
+
function propStep(step, name) {
|
|
605
|
+
const def = step.def;
|
|
606
|
+
const prop = (0, _atscript_core.isInterface)(def) || (0, _atscript_core.isStructure)(def) ? def.props.get(name) : void 0;
|
|
607
|
+
const propDef = prop?.getDefinition();
|
|
608
|
+
if (!prop || !propDef) return void 0;
|
|
609
|
+
if (!(0, _atscript_core.isRef)(propDef)) return {
|
|
610
|
+
doc: step.doc,
|
|
611
|
+
node: prop,
|
|
612
|
+
def: propDef
|
|
613
|
+
};
|
|
614
|
+
const unwound = step.doc.unwindType(propDef.id, propDef.chain);
|
|
615
|
+
return unwound && {
|
|
616
|
+
doc: unwound.doc,
|
|
617
|
+
node: prop,
|
|
618
|
+
def: unwound.def
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
/** {@link viewFieldSeals} of `field`, computed on first use. */
|
|
622
|
+
function lazySeals(field, doc) {
|
|
623
|
+
let seals;
|
|
624
|
+
return () => seals ??= viewFieldSeals(field, doc);
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* A search annotation (`@db.column.searchable` / `@db.index.fulltext`) on a
|
|
628
|
+
* view field whose source is `@db.writeOnly` — a search hit would reveal the
|
|
629
|
+
* sealed value (the runtime refuses it at sync and on every query), or
|
|
630
|
+
* `undefined`. The field's own `@db.writeOnly` is reported by that spec.
|
|
631
|
+
* @since 0.1.162
|
|
632
|
+
*/
|
|
633
|
+
function viewSourceSealProblem(token, doc, annotation, seals = lazySeals(token.parentNode, doc)) {
|
|
634
|
+
const field = token.parentNode;
|
|
635
|
+
if (field.countAnnotations("db.writeOnly") > 0) return void 0;
|
|
636
|
+
if (!seals().writeOnly) return void 0;
|
|
637
|
+
return {
|
|
638
|
+
message: `${annotation} on "${field.id}", which reads a @db.writeOnly source field — search results would reveal the sealed value`,
|
|
639
|
+
severity: 1,
|
|
640
|
+
range: token.range
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* A search / index feature (`@db.column.searchable`, `@db.index.fulltext`,
|
|
645
|
+
* `@db.index.geo`, `@db.search.vector`, `@db.search.filter`) on a field that
|
|
646
|
+
* would read ciphertext: a field inside an `@db.encrypted` object, or a view
|
|
647
|
+
* field over an encrypted source — or `undefined`. The field's own
|
|
648
|
+
* `@db.encrypted` is reported by that spec.
|
|
649
|
+
* @since 0.1.162
|
|
650
|
+
*/
|
|
651
|
+
function encryptedSearchProblem(token, doc, annotation) {
|
|
652
|
+
return encryptedProblem(token, annotation, lazySeals(token.parentNode, doc));
|
|
653
|
+
}
|
|
654
|
+
function encryptedProblem(token, annotation, seals) {
|
|
655
|
+
const field = token.parentNode;
|
|
656
|
+
if (field.countAnnotations("db.encrypted") > 0) return void 0;
|
|
657
|
+
const where = encryptedAncestorName(field);
|
|
658
|
+
const why = where !== void 0 ? `it is inside the @db.encrypted field "${where}"` : seals().encrypted ? "it reads a @db.encrypted source field" : void 0;
|
|
659
|
+
if (!why) return void 0;
|
|
660
|
+
return {
|
|
661
|
+
message: `${annotation} on "${field.id}" — ${why}; search over ciphertext is impossible`,
|
|
662
|
+
severity: 1,
|
|
663
|
+
range: token.range
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* {@link viewSourceSealProblem}, else {@link encryptedSearchProblem} — the
|
|
668
|
+
* view field's source seals computed once.
|
|
669
|
+
*/
|
|
670
|
+
function sealedSearchProblem(token, doc, annotation) {
|
|
671
|
+
const seals = lazySeals(token.parentNode, doc);
|
|
672
|
+
return viewSourceSealProblem(token, doc, annotation, seals) ?? encryptedProblem(token, annotation, seals);
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* An annotation's `validate` refusing it on a field that reads ciphertext
|
|
676
|
+
* ({@link encryptedSearchProblem}) — for the search features of sibling
|
|
677
|
+
* adapter plugins too.
|
|
678
|
+
* @since 0.1.162
|
|
679
|
+
*/
|
|
680
|
+
function refuseCiphertext(annotation) {
|
|
681
|
+
return (token, _args, doc) => {
|
|
682
|
+
const problem = encryptedSearchProblem(token, doc, annotation);
|
|
683
|
+
return problem ? [problem] : [];
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Navigation relation annotations — a navigation field holds another table's
|
|
688
|
+
* rows, never part of this table's stored value.
|
|
689
|
+
* @since 0.1.162
|
|
690
|
+
*/
|
|
691
|
+
const NAV_ANNOTATIONS = [
|
|
692
|
+
"db.rel.to",
|
|
693
|
+
"db.rel.from",
|
|
694
|
+
"db.rel.via"
|
|
695
|
+
];
|
|
696
|
+
/** Whether a prop is a navigation field ({@link NAV_ANNOTATIONS}). */
|
|
697
|
+
function isNavProp(prop) {
|
|
698
|
+
return NAV_ANNOTATIONS.some((key) => prop.countAnnotations(key) > 0);
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* Walks the fields stored in `root` (an interface, a structure or a type ref)
|
|
702
|
+
* depth-first — nested objects and type refs included, navigation fields and
|
|
703
|
+
* arrays skipped — and returns the first non-`undefined` result of `visit`.
|
|
704
|
+
* `path` is the field's dotted path below `prefix`; `viaRef` tells whether
|
|
705
|
+
* a type ref was followed to reach it.
|
|
706
|
+
* @since 0.1.162
|
|
707
|
+
*/
|
|
708
|
+
function findStoredField(root, doc, visit, prefix = "") {
|
|
709
|
+
const seen = /* @__PURE__ */ new Set();
|
|
710
|
+
const walk = (def, defDoc, viaRef, prefix) => {
|
|
711
|
+
if (!def || seen.has(def)) return void 0;
|
|
712
|
+
seen.add(def);
|
|
713
|
+
if ((0, _atscript_core.isRef)(def)) {
|
|
714
|
+
const unwound = defDoc.unwindType(def.id, def.chain);
|
|
715
|
+
return unwound ? walk(unwound.def, unwound.doc, true, prefix) : void 0;
|
|
716
|
+
}
|
|
717
|
+
const props = (0, _atscript_core.isInterface)(def) || (0, _atscript_core.isStructure)(def) ? def.props : void 0;
|
|
718
|
+
if (!props) return void 0;
|
|
719
|
+
for (const [name, prop] of props) {
|
|
720
|
+
if (isNavProp(prop)) continue;
|
|
721
|
+
const path = prefix ? `${prefix}.${name}` : name;
|
|
722
|
+
const found = visit(prop, path, viaRef) ?? walk(prop.getDefinition(), defDoc, viaRef, path);
|
|
723
|
+
if (found !== void 0) return found;
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
return walk(root, doc, false, prefix);
|
|
727
|
+
}
|
|
728
|
+
/**
|
|
729
|
+
* A search feature ({@link ENCRYPTED_SEARCH_FEATURES}) declared inside the
|
|
730
|
+
* TYPE of the `@db.encrypted` field carrying `token`, reached through a type
|
|
731
|
+
* ref (`@db.encrypted profile: Address`, where `Address` has
|
|
732
|
+
* `@db.index.fulltext city`) — the shared type's own annotations cannot see
|
|
733
|
+
* that this use encrypts them. Fields declared inline are reported by their
|
|
734
|
+
* own annotation (see {@link encryptedSearchProblem}).
|
|
735
|
+
* @since 0.1.162
|
|
736
|
+
*/
|
|
737
|
+
function encryptedTypeSearchProblem(token, doc) {
|
|
738
|
+
const field = token.parentNode;
|
|
739
|
+
const found = findStoredField(field.getDefinition(), doc, (prop, path, viaRef) => {
|
|
740
|
+
if (!viaRef) return void 0;
|
|
741
|
+
const feature = require_search_fields.ENCRYPTED_SEARCH_FEATURES.find((key) => prop.countAnnotations(key) > 0);
|
|
742
|
+
return feature ? {
|
|
743
|
+
path,
|
|
744
|
+
feature
|
|
745
|
+
} : void 0;
|
|
746
|
+
}, field.id ?? "");
|
|
747
|
+
if (!found) return void 0;
|
|
748
|
+
return {
|
|
749
|
+
message: `@db.encrypted on "${field.id}" — its type declares @${found.feature} on "${found.path}"; search over ciphertext is impossible`,
|
|
750
|
+
severity: 1,
|
|
751
|
+
range: token.range
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
/** Name of the nearest `@db.encrypted` prop enclosing `field` (inline nested objects), if any. */
|
|
755
|
+
function encryptedAncestorName(field) {
|
|
756
|
+
for (let node = field.ownerNode; node; node = node.ownerNode) if ((0, _atscript_core.isProp)(node) && node.countAnnotations("db.encrypted") > 0) return node.id;
|
|
757
|
+
}
|
|
758
|
+
/** Props of a view interface (its own structure; views don't use `extends`). */
|
|
759
|
+
function viewProps(owner) {
|
|
760
|
+
if ((0, _atscript_core.isInterface)(owner)) return owner.props;
|
|
761
|
+
const def = owner.getDefinition();
|
|
762
|
+
return def && (0, _atscript_core.isStructure)(def) ? def.props : void 0;
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* VW8: a chain ref that passes through a `@db.json` or array node reads a
|
|
766
|
+
* JSON leaf, which views can only extract as a string / number / boolean.
|
|
767
|
+
* Intermediate nodes that `unwindType` can't reach are skipped (sync-time
|
|
768
|
+
* resolution reports them).
|
|
769
|
+
*/
|
|
770
|
+
function validateJsonChain(fieldName, ref, doc, range) {
|
|
771
|
+
const info = jsonChainInfo(ref, doc);
|
|
772
|
+
if (!info.resolved || info.jsonRoot === void 0 || info.jsonRoot >= info.chain.length) return [];
|
|
773
|
+
if (info.leafType !== void 0 && require_search_fields.JSON_LEAF_TYPES.has(info.leafType)) return [];
|
|
774
|
+
return [{
|
|
775
|
+
message: `Field "${fieldName}" reads "${info.typeName}.${info.chain.join(".")}" inside a JSON-stored field — it must end at a string, number or boolean leaf`,
|
|
776
|
+
severity: 1,
|
|
777
|
+
range
|
|
778
|
+
}];
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Whole-view checks, run once per `@db.view.for` interface:
|
|
782
|
+
*
|
|
783
|
+
* - VW7 — a field reading from a left-joined table must be optional (the
|
|
784
|
+
* join yields NULL for unmatched rows); `@db.agg.count` / `countDistinct`
|
|
785
|
+
* fields are exempt (they count 0, never NULL).
|
|
786
|
+
* - VW8 — a chain ref through a `@db.json` or array node must end at a
|
|
787
|
+
* primitive string / number / boolean leaf.
|
|
788
|
+
* @since 0.1.136
|
|
789
|
+
*/
|
|
790
|
+
function validateViewInterface(owner, doc) {
|
|
791
|
+
const errors = [];
|
|
792
|
+
const props = viewProps(owner);
|
|
793
|
+
if (!props) return errors;
|
|
794
|
+
const leftJoined = /* @__PURE__ */ new Set();
|
|
795
|
+
for (const join of viewJoins(owner)) {
|
|
796
|
+
const target = join.args[0]?.text;
|
|
797
|
+
if (target && join.args[2]?.text === "left") leftJoined.add(target);
|
|
798
|
+
}
|
|
799
|
+
for (const [fieldName, prop] of props) {
|
|
800
|
+
const def = prop.getDefinition();
|
|
801
|
+
if (!def || !(0, _atscript_core.isRef)(def)) continue;
|
|
802
|
+
const ref = def;
|
|
803
|
+
const range = (prop.token("identifier") ?? ref.token("identifier"))?.range;
|
|
804
|
+
if (!range) continue;
|
|
805
|
+
if (ref.id && leftJoined.has(ref.id) && !prop.has("optional") && !NULL_SAFE_AGG_ANNOTATIONS.some((name) => prop.countAnnotations(name) > 0)) errors.push({
|
|
806
|
+
message: `Field "${fieldName}" reads from left-joined "${ref.id}" and must be optional (${fieldName}?: …)`,
|
|
807
|
+
severity: 1,
|
|
808
|
+
range
|
|
809
|
+
});
|
|
810
|
+
errors.push(...validateJsonChain(fieldName, ref, doc, range));
|
|
811
|
+
}
|
|
812
|
+
return errors;
|
|
813
|
+
}
|
|
814
|
+
//#endregion
|
|
815
|
+
Object.defineProperty(exports, "NAV_ANNOTATIONS", {
|
|
816
|
+
enumerable: true,
|
|
817
|
+
get: function() {
|
|
818
|
+
return NAV_ANNOTATIONS;
|
|
819
|
+
}
|
|
820
|
+
});
|
|
471
821
|
Object.defineProperty(exports, "earlierJoinTargets", {
|
|
472
822
|
enumerable: true,
|
|
473
823
|
get: function() {
|
|
474
824
|
return earlierJoinTargets;
|
|
475
825
|
}
|
|
476
826
|
});
|
|
827
|
+
Object.defineProperty(exports, "effectiveAnnotationNames", {
|
|
828
|
+
enumerable: true,
|
|
829
|
+
get: function() {
|
|
830
|
+
return effectiveAnnotationNames;
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
Object.defineProperty(exports, "encryptedAncestorName", {
|
|
834
|
+
enumerable: true,
|
|
835
|
+
get: function() {
|
|
836
|
+
return encryptedAncestorName;
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
Object.defineProperty(exports, "encryptedSearchProblem", {
|
|
840
|
+
enumerable: true,
|
|
841
|
+
get: function() {
|
|
842
|
+
return encryptedSearchProblem;
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
Object.defineProperty(exports, "encryptedTypeSearchProblem", {
|
|
846
|
+
enumerable: true,
|
|
847
|
+
get: function() {
|
|
848
|
+
return encryptedTypeSearchProblem;
|
|
849
|
+
}
|
|
850
|
+
});
|
|
477
851
|
Object.defineProperty(exports, "findFKFieldsPointingTo", {
|
|
478
852
|
enumerable: true,
|
|
479
853
|
get: function() {
|
|
480
854
|
return findFKFieldsPointingTo;
|
|
481
855
|
}
|
|
482
856
|
});
|
|
857
|
+
Object.defineProperty(exports, "findStoredField", {
|
|
858
|
+
enumerable: true,
|
|
859
|
+
get: function() {
|
|
860
|
+
return findStoredField;
|
|
861
|
+
}
|
|
862
|
+
});
|
|
483
863
|
Object.defineProperty(exports, "findViewCycle", {
|
|
484
864
|
enumerable: true,
|
|
485
865
|
get: function() {
|
|
@@ -546,18 +926,36 @@ Object.defineProperty(exports, "isDbSourceDecl", {
|
|
|
546
926
|
return isDbSourceDecl;
|
|
547
927
|
}
|
|
548
928
|
});
|
|
929
|
+
Object.defineProperty(exports, "isNavProp", {
|
|
930
|
+
enumerable: true,
|
|
931
|
+
get: function() {
|
|
932
|
+
return isNavProp;
|
|
933
|
+
}
|
|
934
|
+
});
|
|
549
935
|
Object.defineProperty(exports, "isNullableUnion", {
|
|
550
936
|
enumerable: true,
|
|
551
937
|
get: function() {
|
|
552
938
|
return isNullableUnion;
|
|
553
939
|
}
|
|
554
940
|
});
|
|
941
|
+
Object.defineProperty(exports, "isViewField", {
|
|
942
|
+
enumerable: true,
|
|
943
|
+
get: function() {
|
|
944
|
+
return isViewField;
|
|
945
|
+
}
|
|
946
|
+
});
|
|
555
947
|
Object.defineProperty(exports, "joinTargets", {
|
|
556
948
|
enumerable: true,
|
|
557
949
|
get: function() {
|
|
558
950
|
return joinTargets;
|
|
559
951
|
}
|
|
560
952
|
});
|
|
953
|
+
Object.defineProperty(exports, "jsonChainInfo", {
|
|
954
|
+
enumerable: true,
|
|
955
|
+
get: function() {
|
|
956
|
+
return jsonChainInfo;
|
|
957
|
+
}
|
|
958
|
+
});
|
|
561
959
|
Object.defineProperty(exports, "primitiveBaseType", {
|
|
562
960
|
enumerable: true,
|
|
563
961
|
get: function() {
|
|
@@ -570,6 +968,18 @@ Object.defineProperty(exports, "refActionAnnotation", {
|
|
|
570
968
|
return refActionAnnotation;
|
|
571
969
|
}
|
|
572
970
|
});
|
|
971
|
+
Object.defineProperty(exports, "refuseCiphertext", {
|
|
972
|
+
enumerable: true,
|
|
973
|
+
get: function() {
|
|
974
|
+
return refuseCiphertext;
|
|
975
|
+
}
|
|
976
|
+
});
|
|
977
|
+
Object.defineProperty(exports, "sealedSearchProblem", {
|
|
978
|
+
enumerable: true,
|
|
979
|
+
get: function() {
|
|
980
|
+
return sealedSearchProblem;
|
|
981
|
+
}
|
|
982
|
+
});
|
|
573
983
|
Object.defineProperty(exports, "searchFieldVerdict", {
|
|
574
984
|
enumerable: true,
|
|
575
985
|
get: function() {
|
|
@@ -588,6 +998,12 @@ Object.defineProperty(exports, "validateFieldBaseType", {
|
|
|
588
998
|
return validateFieldBaseType;
|
|
589
999
|
}
|
|
590
1000
|
});
|
|
1001
|
+
Object.defineProperty(exports, "validateIncompatible", {
|
|
1002
|
+
enumerable: true,
|
|
1003
|
+
get: function() {
|
|
1004
|
+
return validateIncompatible;
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
591
1007
|
Object.defineProperty(exports, "validateQueryScope", {
|
|
592
1008
|
enumerable: true,
|
|
593
1009
|
get: function() {
|
|
@@ -606,12 +1022,30 @@ Object.defineProperty(exports, "validateSiblingStringField", {
|
|
|
606
1022
|
return validateSiblingStringField;
|
|
607
1023
|
}
|
|
608
1024
|
});
|
|
1025
|
+
Object.defineProperty(exports, "validateViewInterface", {
|
|
1026
|
+
enumerable: true,
|
|
1027
|
+
get: function() {
|
|
1028
|
+
return validateViewInterface;
|
|
1029
|
+
}
|
|
1030
|
+
});
|
|
1031
|
+
Object.defineProperty(exports, "viewFieldSeals", {
|
|
1032
|
+
enumerable: true,
|
|
1033
|
+
get: function() {
|
|
1034
|
+
return viewFieldSeals;
|
|
1035
|
+
}
|
|
1036
|
+
});
|
|
609
1037
|
Object.defineProperty(exports, "viewJoins", {
|
|
610
1038
|
enumerable: true,
|
|
611
1039
|
get: function() {
|
|
612
1040
|
return viewJoins;
|
|
613
1041
|
}
|
|
614
1042
|
});
|
|
1043
|
+
Object.defineProperty(exports, "viewProps", {
|
|
1044
|
+
enumerable: true,
|
|
1045
|
+
get: function() {
|
|
1046
|
+
return viewProps;
|
|
1047
|
+
}
|
|
1048
|
+
});
|
|
615
1049
|
Object.defineProperty(exports, "viewScopeTypes", {
|
|
616
1050
|
enumerable: true,
|
|
617
1051
|
get: function() {
|