@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.
Files changed (63) hide show
  1. package/dist/agg.cjs +1 -1
  2. package/dist/agg.d.cts +1 -1
  3. package/dist/agg.d.mts +1 -1
  4. package/dist/agg.mjs +1 -1
  5. package/dist/{aggregate-fns-B_sgl4mB.mjs → aggregate-fns-DsS9UQVB.mjs} +1 -1
  6. package/dist/{aggregate-fns-DFtKAv7Q.cjs → aggregate-fns-DupYzFD7.cjs} +1 -1
  7. package/dist/{buckets-8mG67Mtw.d.mts → buckets-DUTcrT4Z.d.mts} +475 -57
  8. package/dist/{buckets-B4T_alUe.d.cts → buckets-_iZV39Z3.d.cts} +475 -57
  9. package/dist/{column-diff-DpeUbhEI.cjs → column-diff-CZsMGQ_w.cjs} +846 -232
  10. package/dist/{column-diff-DR1glykD.mjs → column-diff-pdn53AIj.mjs} +829 -233
  11. package/dist/{validator-B3xlbrWf.d.cts → db-error-B_DRL1E9.d.cts} +18 -89
  12. package/dist/{validator-B3xlbrWf.d.mts → db-error-B_DRL1E9.d.mts} +18 -89
  13. package/dist/{db-error-JcfJqqpk.cjs → db-error-DmF1_xrz.cjs} +13 -0
  14. package/dist/{db-error-eSsQX7rt.mjs → db-error-hraC12B9.mjs} +8 -1
  15. package/dist/{db-validator-plugin-BWy60OvG.d.cts → db-validator-plugin-wRqMYNBI.d.cts} +6 -0
  16. package/dist/{db-validator-plugin-BWy60OvG.d.mts → db-validator-plugin-wRqMYNBI.d.mts} +6 -0
  17. package/dist/{fk-diff-5gX42hBt.d.mts → fk-diff-B5jW68T3.d.cts} +34 -10
  18. package/dist/{fk-diff-BWdQ7kRZ.d.cts → fk-diff-PhVBYohc.d.mts} +34 -10
  19. package/dist/index.cjs +12 -11
  20. package/dist/index.d.cts +24 -21
  21. package/dist/index.d.mts +24 -21
  22. package/dist/index.mjs +9 -12
  23. package/dist/{nested-writer-BOj_c_lI.mjs → nested-writer-ClUX0DB0.mjs} +16 -22
  24. package/dist/{nested-writer-Bl1SZS7x.cjs → nested-writer-yCy6wGuN.cjs} +16 -22
  25. package/dist/{object-CtPYTIvy.d.cts → object-Czia_qD4.d.cts} +7 -1
  26. package/dist/{object-CtPYTIvy.d.mts → object-Czia_qD4.d.mts} +7 -1
  27. package/dist/{object-Djg28csK.cjs → object-GjsuPNqo.cjs} +19 -0
  28. package/dist/{object-TkiJQ-Dp.mjs → object-PQ1J8jri.mjs} +14 -1
  29. package/dist/ops.cjs +6 -21
  30. package/dist/ops.mjs +6 -21
  31. package/dist/plugin.cjs +263 -323
  32. package/dist/plugin.mjs +163 -223
  33. package/dist/rel.cjs +2 -2
  34. package/dist/rel.d.cts +3 -3
  35. package/dist/rel.d.mts +3 -3
  36. package/dist/rel.mjs +2 -2
  37. package/dist/{relation-helpers-17_2f5hX.d.mts → relation-helpers-BAEkP4bH.d.cts} +1 -1
  38. package/dist/{relation-helpers-BLBrLQYi.d.cts → relation-helpers-BSYwxOSA.d.mts} +1 -1
  39. package/dist/{relation-loader-DeulLrAw.cjs → relation-loader-DujL_Mh9.cjs} +1 -1
  40. package/dist/{relation-loader-CNBMPfRr.mjs → relation-loader-Qv3LrQ42.mjs} +1 -1
  41. package/dist/{search-fields-CE8cmj9X.mjs → search-fields-0U0FAFGj.mjs} +47 -1
  42. package/dist/{search-fields-CT_UZ9fg.cjs → search-fields-Bi1n-pB1.cjs} +64 -0
  43. package/dist/search-fields-CHqqcC3W.d.cts +30 -0
  44. package/dist/search-fields-CHqqcC3W.d.mts +30 -0
  45. package/dist/shared.cjs +42 -26
  46. package/dist/shared.d.cts +98 -4
  47. package/dist/shared.d.mts +98 -4
  48. package/dist/shared.mjs +4 -2
  49. package/dist/sync.cjs +228 -65
  50. package/dist/sync.d.cts +41 -5
  51. package/dist/sync.d.mts +41 -5
  52. package/dist/sync.mjs +228 -66
  53. package/dist/validator-BcBtg8yW.d.cts +88 -0
  54. package/dist/validator-BcBtg8yW.d.mts +88 -0
  55. package/dist/{validator-B4YDLUkj.cjs → validator-DK9Ijpo7.cjs} +3 -2
  56. package/dist/{validator-B3fPJjS1.mjs → validator-DuDWsIdQ.mjs} +3 -2
  57. package/dist/validator.cjs +3 -3
  58. package/dist/validator.d.cts +4 -3
  59. package/dist/validator.d.mts +4 -3
  60. package/dist/validator.mjs +3 -3
  61. package/dist/{validation-utils-CtLNlBtB.cjs → view-validation-D50KWU7O.cjs} +452 -18
  62. package/dist/{validation-utils-BGjqc2jU.mjs → view-validation-QbqqXEAM.mjs} +364 -20
  63. package/package.json +6 -6
@@ -1,16 +1,59 @@
1
- const require_search_fields = require("./search-fields-CT_UZ9fg.cjs");
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
- /** Asserts the field carrying this annotation does not also carry any of `others`. */
5
- function validateExclusiveWith(token, selfName, others) {
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
- for (const { key, displayName } of others) if (field.countAnnotations(key) > 0) errors.push({
9
- message: `${selfName} cannot coexist with ${displayName ?? `@${key}`} on the same field — pick one form, not both`,
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
- return errors;
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: field.countAnnotations("expect.int") > 0,
59
- increment: field.countAnnotations("db.default.increment") > 0,
60
- now: field.countAnnotations("db.default.now") > 0,
61
- precision: field.countAnnotations("db.column.precision") > 0
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") return kind;
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.\n\n**Example:**
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() {