@atscript/db 0.1.148 → 0.1.150

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 (35) hide show
  1. package/dist/agg.d.cts +1 -1
  2. package/dist/agg.d.mts +1 -1
  3. package/dist/{buckets-DJiYlMXc.d.cts → buckets-24Ac4sIS.d.cts} +80 -5
  4. package/dist/{buckets-CNdTOnei.d.mts → buckets-XOz0Bw5x.d.mts} +80 -5
  5. package/dist/{column-diff-CUU4GvYg.cjs → column-diff-CKKBVDvx.cjs} +646 -341
  6. package/dist/{column-diff-Cp6ZoyRE.mjs → column-diff-CKLDWBbo.mjs} +522 -253
  7. package/dist/{fk-diff-BQ4krij8.d.cts → fk-diff-CCLiJ9sJ.d.cts} +2 -1
  8. package/dist/{fk-diff-DsaIijVX.d.mts → fk-diff-R8eU7gq-.d.mts} +2 -1
  9. package/dist/index.cjs +10 -3
  10. package/dist/index.d.cts +66 -4
  11. package/dist/index.d.mts +66 -4
  12. package/dist/index.mjs +3 -3
  13. package/dist/plugin.cjs +143 -36
  14. package/dist/plugin.mjs +138 -31
  15. package/dist/rel.d.cts +2 -2
  16. package/dist/rel.d.mts +2 -2
  17. package/dist/{relation-helpers-kX7jjgME.d.cts → relation-helpers-DjVJN2ng.d.cts} +1 -1
  18. package/dist/{relation-helpers-Ba0v49sn.d.mts → relation-helpers-H7UEVkHs.d.mts} +1 -1
  19. package/dist/search-fields-DBIZKwyb.mjs +117 -0
  20. package/dist/search-fields-DwrsJUC1.cjs +158 -0
  21. package/dist/shared.cjs +3 -1
  22. package/dist/shared.d.cts +19 -1
  23. package/dist/shared.d.mts +19 -1
  24. package/dist/shared.mjs +2 -2
  25. package/dist/sync.cjs +1 -1
  26. package/dist/sync.d.cts +2 -2
  27. package/dist/sync.d.mts +2 -2
  28. package/dist/sync.mjs +1 -1
  29. package/dist/{validation-utils-Dq0uZ7ef.mjs → validation-utils-DqZAH6aT.mjs} +44 -2
  30. package/dist/{validation-utils-Da2GjobR.cjs → validation-utils-debIYpq0.cjs} +56 -2
  31. package/package.json +6 -6
  32. package/dist/derived-rules-0sKn4f5C.mjs +0 -44
  33. package/dist/derived-rules-YstgIxG-.cjs +0 -67
  34. package/dist/numeric-operand-B1jKH7x5.mjs +0 -21
  35. package/dist/numeric-operand-DKfiRLYp.cjs +0 -26
package/dist/plugin.cjs CHANGED
@@ -34,10 +34,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
34
34
  }) : target, mod));
35
35
  //#endregion
36
36
  const require_aggregate_fns = require("./aggregate-fns-C-UJRobm.cjs");
37
- const require_derived_rules = require("./derived-rules-YstgIxG-.cjs");
38
- const require_numeric_operand = require("./numeric-operand-DKfiRLYp.cjs");
37
+ const require_search_fields = require("./search-fields-DwrsJUC1.cjs");
39
38
  require("./consts-BzRfCcH2.cjs");
40
- const require_validation_utils = require("./validation-utils-Da2GjobR.cjs");
39
+ const require_validation_utils = require("./validation-utils-debIYpq0.cjs");
41
40
  let node_path = require("node:path");
42
41
  node_path = __toESM(node_path, 1);
43
42
  let node_url = require("node:url");
@@ -383,7 +382,7 @@ const dbAliasAnnotations = { alias: new _atscript_core.AnnotationSpec({
383
382
  const errors = [];
384
383
  const owner = token.parentNode;
385
384
  const target = args[0]?.text;
386
- for (const name of require_derived_rules.DB_ENTITY_ANNOTATIONS) if (owner.countAnnotations(name) > 0) {
385
+ for (const name of require_search_fields.DB_ENTITY_ANNOTATIONS) if (owner.countAnnotations(name) > 0) {
387
386
  errors.push({
388
387
  message: `A @db.alias type cannot carry @${name} — it names a join scope over "${target ?? "…"}", not a table or view`,
389
388
  severity: 1,
@@ -509,7 +508,7 @@ function viewProps(owner) {
509
508
  function validateJsonChain(fieldName, ref, doc, range) {
510
509
  const info = jsonChainInfo(ref, doc);
511
510
  if (!info.resolved || info.jsonRoot === void 0 || info.jsonRoot >= info.chain.length) return [];
512
- if (info.leafType !== void 0 && require_derived_rules.JSON_LEAF_TYPES.has(info.leafType)) return [];
511
+ if (info.leafType !== void 0 && require_search_fields.JSON_LEAF_TYPES.has(info.leafType)) return [];
513
512
  return [{
514
513
  message: `Field "${fieldName}" reads "${info.typeName}.${info.chain.join(".")}" inside a JSON-stored field — it must end at a string, number or boolean leaf`,
515
514
  severity: 1,
@@ -552,6 +551,68 @@ function validateViewInterface(owner, doc) {
552
551
  }
553
552
  //#endregion
554
553
  //#region src/plugin/annotations/column.ts
554
+ /** Nav-field annotations: a navigation field has no column on this table. */
555
+ const NAV_ANNOTATIONS = [
556
+ "db.rel.to",
557
+ "db.rel.from",
558
+ "db.rel.via"
559
+ ];
560
+ /**
561
+ * Compile-time placement rules of `@db.column.version.exempt` (E1–E5, W1, W2).
562
+ * E6 (derived) lives in `DERIVED_INCOMPATIBLE`. The runtime mirror is
563
+ * `TableMetadata._finalizeVersionExempt`.
564
+ * @since 0.1.150
565
+ */
566
+ function validateVersionExempt(token) {
567
+ const errors = [];
568
+ const field = token.parentNode;
569
+ const fail = (message, severity = 1) => {
570
+ errors.push({
571
+ message,
572
+ severity,
573
+ range: token.range
574
+ });
575
+ };
576
+ const tag = "@db.column.version.exempt";
577
+ if (field.countAnnotations("db.column.version") > 0) fail(`${tag} cannot mark the version column itself`);
578
+ if (field.countAnnotations("meta.id") > 0) fail(`${tag} cannot mark a primary key — it identifies the row and is never patched`);
579
+ if (NAV_ANNOTATIONS.some((n) => field.countAnnotations(n) > 0)) fail(`${tag} cannot mark a navigation field — related rows follow their own table's versioning`);
580
+ if (field.countAnnotations("db.ignore") > 0) fail(`${tag} has no effect on an ignored field`, 2);
581
+ let node = field;
582
+ let tableOwner;
583
+ let reported = false;
584
+ while (node.ownerNode && (0, _atscript_core.isStructure)(node.ownerNode)) {
585
+ let up = node.ownerNode.ownerNode;
586
+ let viaArray = false;
587
+ while (up && (0, _atscript_core.isArray)(up)) {
588
+ viaArray = true;
589
+ up = up.ownerNode;
590
+ }
591
+ if (!up) break;
592
+ if ((0, _atscript_core.isInterface)(up)) {
593
+ tableOwner = up;
594
+ break;
595
+ }
596
+ if (!(0, _atscript_core.isProp)(up)) break;
597
+ if (!reported && viaArray) {
598
+ fail(`${tag} cannot sit inside an array — mark the array field '${up.id ?? ""}' itself; array elements are not separate columns`);
599
+ reported = true;
600
+ } else if (!reported && up.countAnnotations("db.json") > 0) {
601
+ fail(`${tag} cannot sit inside a @db.json field — mark the @db.json field '${up.id ?? ""}' itself; a JSON column is written as one value`);
602
+ reported = true;
603
+ }
604
+ node = up;
605
+ }
606
+ if (tableOwner && (0, _atscript_core.isInterface)(tableOwner) && tableOwner.countAnnotations("db.table") > 0) {
607
+ const struct = tableOwner.getDefinition();
608
+ let hasVersion = false;
609
+ if (struct && (0, _atscript_core.isStructure)(struct)) {
610
+ for (const [, prop] of struct.props) if (prop.countAnnotations("db.column.version") > 0) hasVersion = true;
611
+ }
612
+ if (!hasVersion) fail(`${tag} has no effect — table '${tableOwner.id ?? ""}' declares no @db.column.version`, 2);
613
+ }
614
+ return errors;
615
+ }
555
616
  const dbColumnAnnotations = {
556
617
  patch: { strategy: new _atscript_core.AnnotationSpec({
557
618
  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",
@@ -658,7 +719,7 @@ const dbColumnAnnotations = {
658
719
  fail("@db.column.derived is only valid on a top-level field of a @db.table interface");
659
720
  return errors;
660
721
  }
661
- for (const [name, why] of require_derived_rules.DERIVED_INCOMPATIBLE) if (field.countAnnotations(name) > 0) fail(`@db.column.derived cannot coexist with @${name} — ${why}`);
722
+ for (const [name, why] of require_search_fields.DERIVED_INCOMPATIBLE) if (field.countAnnotations(name) > 0) fail(`@db.column.derived cannot coexist with @${name} — ${why}`);
662
723
  const definition = field.getDefinition();
663
724
  if (!definition || !(0, _atscript_core.isRef)(definition) || !definition.hasChain) {
664
725
  fail("@db.column.derived requires a chain reference into a @db.json field of the same table (e.g. `customerId: Order.payload.customer.id`)");
@@ -690,7 +751,7 @@ const dbColumnAnnotations = {
690
751
  fail(`@db.column.derived path '${path}' reads inside a @db.encrypted field — ciphertext cannot be extracted`);
691
752
  return errors;
692
753
  }
693
- if (info.leafType === void 0 || !require_derived_rules.JSON_LEAF_TYPES.has(info.leafType)) {
754
+ if (info.leafType === void 0 || !require_search_fields.JSON_LEAF_TYPES.has(info.leafType)) {
694
755
  fail(`@db.column.derived path '${path}' must end at a string, number or boolean leaf` + (info.leafType ? ` (got '${info.leafType}')` : ""));
695
756
  return errors;
696
757
  }
@@ -714,38 +775,55 @@ const dbColumnAnnotations = {
714
775
  filterable: columnCapability("filterable", "filtering"),
715
776
  sortable: columnCapability("sortable", "sorting"),
716
777
  searchable: new _atscript_core.AnnotationSpec({
717
- description: "Includes this column in the generic `$search` fallback: when the adapter reports no native search capability (no FTS / Atlas index configured), the readable controller matches the `$search` term as a case-insensitive substring across all `@db.column.searchable` fields (`$or`). Where adapter-native search IS available it wins and this annotation is not consulted. The term is escaped literally — no user-supplied regex. String-typed columns only.\n\n**Example:**\n```atscript\n@db.table \"jobs\"\nexport interface Job {\n @db.column.searchable\n jobName: string\n @db.column.searchable\n description: string\n}\n```\n",
778
+ description: "Includes this column in the generic `$search` fallback: when the adapter reports no native search capability (no FTS / Atlas index configured), the readable controller matches the `$search` term as a case-insensitive substring across all `@db.column.searchable` fields (`$or`). Where adapter-native search IS available it wins and this annotation is not consulted. The term is escaped literally — no user-supplied regex. String and integer columns only: an integer column (`number.int` and its sizes, `@expect.int`, `@db.default.increment`) matches when the term is a substring of the number's decimal text (`2946` finds `29461277`). Floats, decimals and timestamps are refused.\n\n**Example:**\n```atscript\n@db.table \"jobs\"\nexport interface Job {\n @db.column.searchable\n jobName: string\n @db.column.searchable\n description: string\n @db.column.searchable\n refNo: number.int\n}\n```\n",
718
779
  nodeType: ["prop"],
719
780
  passedWhenReferred: false,
720
781
  multiple: false,
721
782
  validate(token, _args, doc) {
722
- return require_validation_utils.validateFieldBaseType(token, doc, "@db.column.searchable", ["string"]);
723
- }
724
- }),
725
- version: new _atscript_core.AnnotationSpec({
726
- description: "Marks a numeric column as the row's version for optimistic concurrency control (OCC). The adapter auto-increments this column on every UPDATE, and callers may pass `$cas: { <col>: N }` in a write payload to make the update conditional on the current version. Direct writes to the version column (as plain SET, `$inc`, or `$mul`) are rejected.\n\n**Constraints:**\n- At most one version column per table.\n- Must resolve to an integer type (`int`, `int32`, `int64`, etc.).\n- Default value on insert is `0`.\n\n**Example:**\n```atscript\n@db.column.version\nversion: int\n```\n",
727
- nodeType: ["prop"],
728
- passedWhenReferred: false,
729
- validate(token, _args, doc) {
730
- const errors = require_validation_utils.validateFieldBaseType(token, doc, "@db.column.version", "number");
731
- if (token.parentNode.has("optional")) errors.push({
732
- message: "@db.column.version requires a non-optional field — version columns are always populated (default 0)",
783
+ const verdict = require_validation_utils.searchFieldVerdict(token.parentNode, doc);
784
+ if (!("problem" in verdict)) return [];
785
+ return [{
786
+ message: `@db.column.searchable needs a string or an integer field — "${token.parentNode.id}" ${verdict.problem}`,
733
787
  severity: 1,
734
788
  range: token.range
735
- });
736
- const struct = require_validation_utils.getParentStruct(token);
737
- if (struct) {
738
- let count = 0;
739
- for (const [, prop] of struct.props) if (prop.countAnnotations("db.column.version") > 0) count++;
740
- if (count > 1) errors.push({
741
- message: "At most one @db.column.version per table",
789
+ }];
790
+ }
791
+ }),
792
+ version: {
793
+ $self: new _atscript_core.AnnotationSpec({
794
+ description: "Marks a numeric column as the row's version for optimistic concurrency control (OCC). The adapter auto-increments this column on every UPDATE, and callers may pass `$cas: { <col>: N }` in a write payload to make the update conditional on the current version. Direct writes to the version column (as plain SET, `$inc`, or `$mul`) are rejected. Fields marked `@db.column.version.exempt` do not bump it.\n\n**Constraints:**\n- At most one version column per table.\n- Must resolve to an integer type (`int`, `int32`, `int64`, etc.).\n- Default value on insert is `0`.\n\n**Example:**\n```atscript\n@db.column.version\nversion: int\n```\n",
795
+ nodeType: ["prop"],
796
+ passedWhenReferred: false,
797
+ validate(token, _args, doc) {
798
+ const errors = require_validation_utils.validateFieldBaseType(token, doc, "@db.column.version", "number");
799
+ if (token.parentNode.has("optional")) errors.push({
800
+ message: "@db.column.version requires a non-optional field — version columns are always populated (default 0)",
742
801
  severity: 1,
743
802
  range: token.range
744
803
  });
804
+ const struct = require_validation_utils.getParentStruct(token);
805
+ if (struct) {
806
+ let count = 0;
807
+ for (const [, prop] of struct.props) if (prop.countAnnotations("db.column.version") > 0) count++;
808
+ if (count > 1) errors.push({
809
+ message: "At most one @db.column.version per table",
810
+ severity: 1,
811
+ range: token.range
812
+ });
813
+ }
814
+ return errors;
745
815
  }
746
- return errors;
747
- }
748
- })
816
+ }),
817
+ exempt: new _atscript_core.AnnotationSpec({
818
+ description: "Marks a field as **version-exempt**: a patch that writes ONLY exempt fields (`updateOne` / `bulkUpdate` without `$cas`, `updateMany`) leaves `@db.column.version` unchanged and adds no version check. A patch touching any other field bumps as usual; `$cas`, replace and `touchMany` always bump. Use it for derived/reporting columns refreshed in the background (scores, counters, caches) so they do not invalidate versions held by editors. On an object field it covers every nested field.\n\n**Example:**\n```atscript\n@db.column.version\nversion: number.int\n@db.column.version.exempt\nscore: number\n```\n",
819
+ nodeType: ["prop"],
820
+ passedWhenReferred: false,
821
+ multiple: false,
822
+ validate(token) {
823
+ return validateVersionExempt(token);
824
+ }
825
+ })
826
+ }
749
827
  },
750
828
  default: {
751
829
  $self: new _atscript_core.AnnotationSpec({
@@ -858,17 +936,23 @@ const dbColumnAnnotations = {
858
936
  }
859
937
  }),
860
938
  writeOnly: new _atscript_core.AnnotationSpec({
861
- description: "Marks a field as write-only over HTTP: it may be set through insert/update/replace payloads but NEVER appears in read responses — the readable controller excludes it from every projection, rejects filtering/sorting/grouping on it, and `/meta` serves its TYPE (flagged `writeOnly`) so client preflight validation and generated forms still know its shape. The classic case is a sealed secret (pair with `@db.encrypted`): writable via generic forms, unreadable by anyone.\n\nServer-side code reading through `AtscriptDbTable` still sees the value — the seal is an HTTP-layer contract, not a storage one.\n\n**Example:**\n```atscript\n@db.writeOnly\n@db.encrypted\ncredentials?: string\n```\n",
939
+ description: "Marks a field as write-only over HTTP: it may be set through insert/update/replace payloads but NEVER appears in read responses — the readable controller excludes it from every projection, rejects filtering/sorting/grouping on it, and `/meta` serves its TYPE (flagged `writeOnly`) so client preflight validation and generated forms still know its shape. The classic case is a sealed secret (pair with `@db.encrypted`): writable via generic forms, unreadable by anyone.\n\nServer-side code reading through `AtscriptDbTable` still sees the value — the seal is an HTTP-layer contract, not a storage one. It cannot be combined with `@db.index.fulltext` or `@db.column.searchable`: a search hit would reveal the sealed value.\n\n**Example:**\n```atscript\n@db.writeOnly\n@db.encrypted\ncredentials?: string\n```\n",
862
940
  nodeType: ["prop"],
863
941
  passedWhenReferred: false,
864
942
  multiple: false,
865
943
  validate(token, _args, _doc) {
866
944
  const errors = [];
867
- if (token.parentNode.countAnnotations("meta.id") > 0) errors.push({
945
+ const field = token.parentNode;
946
+ if (field.countAnnotations("meta.id") > 0) errors.push({
868
947
  message: `@db.writeOnly cannot coexist with @meta.id — the primary key must be readable`,
869
948
  severity: 1,
870
949
  range: token.range
871
950
  });
951
+ for (const name of ["db.index.fulltext", "db.column.searchable"]) if (field.countAnnotations(name) > 0) errors.push({
952
+ message: `@db.writeOnly cannot coexist with @${name} — search results would reveal the sealed value`,
953
+ severity: 1,
954
+ range: token.range
955
+ });
872
956
  return errors;
873
957
  }
874
958
  })
@@ -931,7 +1015,7 @@ function operandProblem(prop, doc) {
931
1015
  const leaf = def && (0, _atscript_core.isRef)(def) ? doc.unwindType(def.id, def.chain)?.def : def;
932
1016
  const base = require_validation_utils.primitiveBaseType(leaf);
933
1017
  if (base === void 0) return void 0;
934
- return require_numeric_operand.numericTypeProblem({
1018
+ return require_search_fields.numericTypeProblem({
935
1019
  base,
936
1020
  tags: (0, _atscript_core.isPrimitive)(leaf) ? leaf.tags : void 0
937
1021
  });
@@ -1051,7 +1135,30 @@ const dbIndexAnnotations = { index: {
1051
1135
  }
1052
1136
  }),
1053
1137
  fulltext: new _atscript_core.AnnotationSpec({
1054
- 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",
1138
+ description: "Full-text search index. Fields sharing the same index name form a composite full-text index. A string member is part of the engine's text index. An integer member (`number.int` and its sizes, `@expect.int`, `@db.default.increment`) is never part of the physical text index: when the whole search term is a whole number (`2946`, `-12`; no leading zeros), rows whose member equals it exactly also match (OR'd with the text match). An integer member must be index-backed — the primary key (first `@meta.id`) or the first field of a `@db.index.plain` / `@db.index.unique`. Floats, decimals and timestamps are refused.\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@db.index.fulltext \"ft_content\"\n@db.index.unique\nrefNo: number.int\n```\n",
1139
+ validate(token, args, doc) {
1140
+ const field = token.parentNode;
1141
+ const verdict = require_validation_utils.searchFieldVerdict(field, doc);
1142
+ if ("problem" in verdict) return [{
1143
+ message: `@db.index.fulltext needs a string or an integer field — "${field.id}" ${verdict.problem}`,
1144
+ severity: 1,
1145
+ range: token.range
1146
+ }];
1147
+ if (verdict.kind !== "integer") return [];
1148
+ const errors = [];
1149
+ const missing = require_validation_utils.integerMemberIndexProblem(token);
1150
+ if (missing) errors.push({
1151
+ message: missing,
1152
+ severity: 1,
1153
+ range: token.range
1154
+ });
1155
+ if (args[1]) errors.push({
1156
+ message: "the weight is ignored on an integer member (it is matched by exact number, not by text)",
1157
+ severity: 2,
1158
+ range: args[1].range
1159
+ });
1160
+ return errors;
1161
+ },
1055
1162
  nodeType: ["prop"],
1056
1163
  passedWhenReferred: false,
1057
1164
  multiple: true,
@@ -1065,7 +1172,7 @@ const dbIndexAnnotations = { index: {
1065
1172
  optional: true,
1066
1173
  name: "weight",
1067
1174
  type: "number",
1068
- description: "Field importance in search results (higher = more relevant). Defaults to `1`. Supported by databases with weighted fulltext (e.g., MongoDB, PostgreSQL)."
1175
+ description: "Field importance in search results (higher = more relevant). Defaults to `1`. Supported by databases with weighted fulltext (e.g., MongoDB, PostgreSQL). Ignored on integer members."
1069
1176
  }]
1070
1177
  }),
1071
1178
  geo: new _atscript_core.AnnotationSpec({
@@ -1110,7 +1217,7 @@ function checkCompositeFkCoverage(token, alias, refTypeName, targetStruct, targe
1110
1217
  }
1111
1218
  const dbRelAnnotations = { rel: {
1112
1219
  FK: new _atscript_core.AnnotationSpec({
1113
- description: "Declares a foreign key reference on this field. The field must use a chain reference type (e.g., `User.id`) whose target is a primary key (`@meta.id`) or unique (`@db.index.unique`) field.\n\n**Dual role:**\n- On a `@db.table` interface, `@db.rel.FK` additionally drives DB-relation semantics — relation loading with `@db.rel.to` / `@db.rel.from`, junction pairing with `@db.rel.via`, etc.\n- On any other interface (value-help sources, WF forms, plain interfaces), `@db.rel.FK` acts purely as the value-help indicator: the client-side picker resolver uses it to decide which fields render a value-help picker. The target's `@db.http.path` (stamped by its readable controller) supplies the picker URL.\n- In `/meta` (and `/meta/form/:name`) the marker is inherited through reference chains: a field declared as `code: Issue.code` where `Issue.code: Dict.code` carries `@db.rel.FK` is served with `ref` pointing at the terminal field (`Dict.code`) and `db.rel.FK: true`, so view fields get the dictionary picker. Runtime metadata is untouched.\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",
1220
+ description: "Declares a foreign key reference on this field. The field must use a chain reference type (e.g., `User.id`) whose target is a primary key (`@meta.id`) or unique (`@db.index.unique`) field.\n\n**Dual role:**\n- On a `@db.table` interface, `@db.rel.FK` additionally drives DB-relation semantics — relation loading with `@db.rel.to` / `@db.rel.from`, junction pairing with `@db.rel.via`, etc.\n- On any other interface (value-help sources, WF forms, plain interfaces), `@db.rel.FK` acts purely as the value-help indicator: the client-side picker resolver uses it to decide which fields render a value-help picker. The target's `@db.http.path` (published by its canonical readable controller) supplies the picker URL.\n- In `/meta` (and `/meta/form/:name`) the marker is inherited through reference chains: a field declared as `code: Issue.code` where `Issue.code: Dict.code` carries `@db.rel.FK` is served with `ref` pointing at the terminal field (`Dict.code`) and `db.rel.FK: true`, so view fields get the dictionary picker. Runtime metadata is untouched.\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",
1114
1221
  nodeType: ["prop"],
1115
1222
  passedWhenReferred: false,
1116
1223
  argument: {
@@ -1702,7 +1809,7 @@ const dbTableAnnotations = {
1702
1809
  }
1703
1810
  }),
1704
1811
  http: { path: new _atscript_core.AnnotationSpec({
1705
- description: "HTTP endpoint path where this table is served. Used by the UI for value-help on FK fields. Gets overwritten by the final controller prefix at runtime.\n\n**Example:**\n```atscript\n@db.table \"authors\"\n@db.http.path \"/authors\"\nexport interface Author { ... }\n```\n",
1812
+ description: "HTTP endpoint path where this table is served. Used by the UI for value-help on FK fields. After app.init() the app publishes the canonical controller route here at runtime; a model mounted on several routes needs `canonical: true` on one of its controllers (or declare this annotation and mount that controller without an explicit prefix).\n\n**Example:**\n```atscript\n@db.table \"authors\"\n@db.http.path \"/authors\"\nexport interface Author { ... }\n```\n",
1706
1813
  nodeType: ["interface"],
1707
1814
  passedWhenReferred: false,
1708
1815
  argument: {
package/dist/plugin.mjs CHANGED
@@ -1,8 +1,7 @@
1
1
  import { i as NULL_WHEN_EMPTY_AGGREGATE_FNS, o as SUPPORTED_AGGREGATE_FNS, t as AGG_ANNOTATIONS } from "./aggregate-fns-CyaZyb9I.mjs";
2
- import { n as DERIVED_INCOMPATIBLE, r as JSON_LEAF_TYPES, t as DB_ENTITY_ANNOTATIONS } from "./derived-rules-0sKn4f5C.mjs";
3
- import { t as numericTypeProblem } from "./numeric-operand-B1jKH7x5.mjs";
2
+ import { a as DERIVED_INCOMPATIBLE, i as DB_ENTITY_ANNOTATIONS, o as JSON_LEAF_TYPES, r as numericTypeProblem } from "./search-fields-DBIZKwyb.mjs";
4
3
  import "./consts-C_-5_pFq.mjs";
5
- import { S as validateSiblingStringField, _ as getParentTypeName, a as hasAnyViewAnnotation, b as validateExclusiveWith, d as viewJoins, f as viewScopeTypes, g as getParentStruct, h as getNavTargetTypeName, i as forEachFieldRef, l as validateQueryScope, m as getDbTableOwner, n as findFKFieldsPointingTo, o as isAliasDecl, p as getAnnotationAlias, r as findViewCycle, s as isDbSourceDecl, t as earlierJoinTargets, u as validateRefArgument, v as primitiveBaseType, x as validateFieldBaseType, y as refActionAnnotation } from "./validation-utils-Dq0uZ7ef.mjs";
4
+ import { C as validateFieldBaseType, S as validateExclusiveWith, _ as getParentTypeName, a as hasAnyViewAnnotation, b as refActionAnnotation, d as viewJoins, f as viewScopeTypes, g as getParentStruct, h as getNavTargetTypeName, i as forEachFieldRef, l as validateQueryScope, m as getDbTableOwner, n as findFKFieldsPointingTo, o as isAliasDecl, p as getAnnotationAlias, r as findViewCycle, s as isDbSourceDecl, t as earlierJoinTargets, u as validateRefArgument, v as integerMemberIndexProblem, w as validateSiblingStringField, x as searchFieldVerdict, y as primitiveBaseType } from "./validation-utils-DqZAH6aT.mjs";
6
5
  import path from "node:path";
7
6
  import { fileURLToPath } from "node:url";
8
7
  import { AnnotationSpec, DEFAULT_FORMAT, getFieldsForType, isArray, isInterface, isPrimitive, isProp, isQueryComparison, isQueryLogical, isRef, isStructure } from "@atscript/core";
@@ -516,6 +515,68 @@ function validateViewInterface(owner, doc) {
516
515
  }
517
516
  //#endregion
518
517
  //#region src/plugin/annotations/column.ts
518
+ /** Nav-field annotations: a navigation field has no column on this table. */
519
+ const NAV_ANNOTATIONS = [
520
+ "db.rel.to",
521
+ "db.rel.from",
522
+ "db.rel.via"
523
+ ];
524
+ /**
525
+ * Compile-time placement rules of `@db.column.version.exempt` (E1–E5, W1, W2).
526
+ * E6 (derived) lives in `DERIVED_INCOMPATIBLE`. The runtime mirror is
527
+ * `TableMetadata._finalizeVersionExempt`.
528
+ * @since 0.1.150
529
+ */
530
+ function validateVersionExempt(token) {
531
+ const errors = [];
532
+ const field = token.parentNode;
533
+ const fail = (message, severity = 1) => {
534
+ errors.push({
535
+ message,
536
+ severity,
537
+ range: token.range
538
+ });
539
+ };
540
+ const tag = "@db.column.version.exempt";
541
+ if (field.countAnnotations("db.column.version") > 0) fail(`${tag} cannot mark the version column itself`);
542
+ if (field.countAnnotations("meta.id") > 0) fail(`${tag} cannot mark a primary key — it identifies the row and is never patched`);
543
+ if (NAV_ANNOTATIONS.some((n) => field.countAnnotations(n) > 0)) fail(`${tag} cannot mark a navigation field — related rows follow their own table's versioning`);
544
+ if (field.countAnnotations("db.ignore") > 0) fail(`${tag} has no effect on an ignored field`, 2);
545
+ let node = field;
546
+ let tableOwner;
547
+ let reported = false;
548
+ while (node.ownerNode && isStructure(node.ownerNode)) {
549
+ let up = node.ownerNode.ownerNode;
550
+ let viaArray = false;
551
+ while (up && isArray(up)) {
552
+ viaArray = true;
553
+ up = up.ownerNode;
554
+ }
555
+ if (!up) break;
556
+ if (isInterface(up)) {
557
+ tableOwner = up;
558
+ break;
559
+ }
560
+ if (!isProp(up)) break;
561
+ if (!reported && viaArray) {
562
+ fail(`${tag} cannot sit inside an array — mark the array field '${up.id ?? ""}' itself; array elements are not separate columns`);
563
+ reported = true;
564
+ } else if (!reported && up.countAnnotations("db.json") > 0) {
565
+ fail(`${tag} cannot sit inside a @db.json field — mark the @db.json field '${up.id ?? ""}' itself; a JSON column is written as one value`);
566
+ reported = true;
567
+ }
568
+ node = up;
569
+ }
570
+ if (tableOwner && isInterface(tableOwner) && tableOwner.countAnnotations("db.table") > 0) {
571
+ const struct = tableOwner.getDefinition();
572
+ let hasVersion = false;
573
+ if (struct && isStructure(struct)) {
574
+ for (const [, prop] of struct.props) if (prop.countAnnotations("db.column.version") > 0) hasVersion = true;
575
+ }
576
+ if (!hasVersion) fail(`${tag} has no effect — table '${tableOwner.id ?? ""}' declares no @db.column.version`, 2);
577
+ }
578
+ return errors;
579
+ }
519
580
  const dbColumnAnnotations = {
520
581
  patch: { strategy: new AnnotationSpec({
521
582
  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",
@@ -678,38 +739,55 @@ const dbColumnAnnotations = {
678
739
  filterable: columnCapability("filterable", "filtering"),
679
740
  sortable: columnCapability("sortable", "sorting"),
680
741
  searchable: new AnnotationSpec({
681
- description: "Includes this column in the generic `$search` fallback: when the adapter reports no native search capability (no FTS / Atlas index configured), the readable controller matches the `$search` term as a case-insensitive substring across all `@db.column.searchable` fields (`$or`). Where adapter-native search IS available it wins and this annotation is not consulted. The term is escaped literally — no user-supplied regex. String-typed columns only.\n\n**Example:**\n```atscript\n@db.table \"jobs\"\nexport interface Job {\n @db.column.searchable\n jobName: string\n @db.column.searchable\n description: string\n}\n```\n",
742
+ description: "Includes this column in the generic `$search` fallback: when the adapter reports no native search capability (no FTS / Atlas index configured), the readable controller matches the `$search` term as a case-insensitive substring across all `@db.column.searchable` fields (`$or`). Where adapter-native search IS available it wins and this annotation is not consulted. The term is escaped literally — no user-supplied regex. String and integer columns only: an integer column (`number.int` and its sizes, `@expect.int`, `@db.default.increment`) matches when the term is a substring of the number's decimal text (`2946` finds `29461277`). Floats, decimals and timestamps are refused.\n\n**Example:**\n```atscript\n@db.table \"jobs\"\nexport interface Job {\n @db.column.searchable\n jobName: string\n @db.column.searchable\n description: string\n @db.column.searchable\n refNo: number.int\n}\n```\n",
682
743
  nodeType: ["prop"],
683
744
  passedWhenReferred: false,
684
745
  multiple: false,
685
746
  validate(token, _args, doc) {
686
- return validateFieldBaseType(token, doc, "@db.column.searchable", ["string"]);
687
- }
688
- }),
689
- version: new AnnotationSpec({
690
- description: "Marks a numeric column as the row's version for optimistic concurrency control (OCC). The adapter auto-increments this column on every UPDATE, and callers may pass `$cas: { <col>: N }` in a write payload to make the update conditional on the current version. Direct writes to the version column (as plain SET, `$inc`, or `$mul`) are rejected.\n\n**Constraints:**\n- At most one version column per table.\n- Must resolve to an integer type (`int`, `int32`, `int64`, etc.).\n- Default value on insert is `0`.\n\n**Example:**\n```atscript\n@db.column.version\nversion: int\n```\n",
691
- nodeType: ["prop"],
692
- passedWhenReferred: false,
693
- validate(token, _args, doc) {
694
- const errors = validateFieldBaseType(token, doc, "@db.column.version", "number");
695
- if (token.parentNode.has("optional")) errors.push({
696
- message: "@db.column.version requires a non-optional field — version columns are always populated (default 0)",
747
+ const verdict = searchFieldVerdict(token.parentNode, doc);
748
+ if (!("problem" in verdict)) return [];
749
+ return [{
750
+ message: `@db.column.searchable needs a string or an integer field — "${token.parentNode.id}" ${verdict.problem}`,
697
751
  severity: 1,
698
752
  range: token.range
699
- });
700
- const struct = getParentStruct(token);
701
- if (struct) {
702
- let count = 0;
703
- for (const [, prop] of struct.props) if (prop.countAnnotations("db.column.version") > 0) count++;
704
- if (count > 1) errors.push({
705
- message: "At most one @db.column.version per table",
753
+ }];
754
+ }
755
+ }),
756
+ version: {
757
+ $self: new AnnotationSpec({
758
+ description: "Marks a numeric column as the row's version for optimistic concurrency control (OCC). The adapter auto-increments this column on every UPDATE, and callers may pass `$cas: { <col>: N }` in a write payload to make the update conditional on the current version. Direct writes to the version column (as plain SET, `$inc`, or `$mul`) are rejected. Fields marked `@db.column.version.exempt` do not bump it.\n\n**Constraints:**\n- At most one version column per table.\n- Must resolve to an integer type (`int`, `int32`, `int64`, etc.).\n- Default value on insert is `0`.\n\n**Example:**\n```atscript\n@db.column.version\nversion: int\n```\n",
759
+ nodeType: ["prop"],
760
+ passedWhenReferred: false,
761
+ validate(token, _args, doc) {
762
+ const errors = validateFieldBaseType(token, doc, "@db.column.version", "number");
763
+ if (token.parentNode.has("optional")) errors.push({
764
+ message: "@db.column.version requires a non-optional field — version columns are always populated (default 0)",
706
765
  severity: 1,
707
766
  range: token.range
708
767
  });
768
+ const struct = getParentStruct(token);
769
+ if (struct) {
770
+ let count = 0;
771
+ for (const [, prop] of struct.props) if (prop.countAnnotations("db.column.version") > 0) count++;
772
+ if (count > 1) errors.push({
773
+ message: "At most one @db.column.version per table",
774
+ severity: 1,
775
+ range: token.range
776
+ });
777
+ }
778
+ return errors;
709
779
  }
710
- return errors;
711
- }
712
- })
780
+ }),
781
+ exempt: new AnnotationSpec({
782
+ description: "Marks a field as **version-exempt**: a patch that writes ONLY exempt fields (`updateOne` / `bulkUpdate` without `$cas`, `updateMany`) leaves `@db.column.version` unchanged and adds no version check. A patch touching any other field bumps as usual; `$cas`, replace and `touchMany` always bump. Use it for derived/reporting columns refreshed in the background (scores, counters, caches) so they do not invalidate versions held by editors. On an object field it covers every nested field.\n\n**Example:**\n```atscript\n@db.column.version\nversion: number.int\n@db.column.version.exempt\nscore: number\n```\n",
783
+ nodeType: ["prop"],
784
+ passedWhenReferred: false,
785
+ multiple: false,
786
+ validate(token) {
787
+ return validateVersionExempt(token);
788
+ }
789
+ })
790
+ }
713
791
  },
714
792
  default: {
715
793
  $self: new AnnotationSpec({
@@ -822,17 +900,23 @@ const dbColumnAnnotations = {
822
900
  }
823
901
  }),
824
902
  writeOnly: new AnnotationSpec({
825
- description: "Marks a field as write-only over HTTP: it may be set through insert/update/replace payloads but NEVER appears in read responses — the readable controller excludes it from every projection, rejects filtering/sorting/grouping on it, and `/meta` serves its TYPE (flagged `writeOnly`) so client preflight validation and generated forms still know its shape. The classic case is a sealed secret (pair with `@db.encrypted`): writable via generic forms, unreadable by anyone.\n\nServer-side code reading through `AtscriptDbTable` still sees the value — the seal is an HTTP-layer contract, not a storage one.\n\n**Example:**\n```atscript\n@db.writeOnly\n@db.encrypted\ncredentials?: string\n```\n",
903
+ description: "Marks a field as write-only over HTTP: it may be set through insert/update/replace payloads but NEVER appears in read responses — the readable controller excludes it from every projection, rejects filtering/sorting/grouping on it, and `/meta` serves its TYPE (flagged `writeOnly`) so client preflight validation and generated forms still know its shape. The classic case is a sealed secret (pair with `@db.encrypted`): writable via generic forms, unreadable by anyone.\n\nServer-side code reading through `AtscriptDbTable` still sees the value — the seal is an HTTP-layer contract, not a storage one. It cannot be combined with `@db.index.fulltext` or `@db.column.searchable`: a search hit would reveal the sealed value.\n\n**Example:**\n```atscript\n@db.writeOnly\n@db.encrypted\ncredentials?: string\n```\n",
826
904
  nodeType: ["prop"],
827
905
  passedWhenReferred: false,
828
906
  multiple: false,
829
907
  validate(token, _args, _doc) {
830
908
  const errors = [];
831
- if (token.parentNode.countAnnotations("meta.id") > 0) errors.push({
909
+ const field = token.parentNode;
910
+ if (field.countAnnotations("meta.id") > 0) errors.push({
832
911
  message: `@db.writeOnly cannot coexist with @meta.id — the primary key must be readable`,
833
912
  severity: 1,
834
913
  range: token.range
835
914
  });
915
+ for (const name of ["db.index.fulltext", "db.column.searchable"]) if (field.countAnnotations(name) > 0) errors.push({
916
+ message: `@db.writeOnly cannot coexist with @${name} — search results would reveal the sealed value`,
917
+ severity: 1,
918
+ range: token.range
919
+ });
836
920
  return errors;
837
921
  }
838
922
  })
@@ -1015,7 +1099,30 @@ const dbIndexAnnotations = { index: {
1015
1099
  }
1016
1100
  }),
1017
1101
  fulltext: new AnnotationSpec({
1018
- 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",
1102
+ description: "Full-text search index. Fields sharing the same index name form a composite full-text index. A string member is part of the engine's text index. An integer member (`number.int` and its sizes, `@expect.int`, `@db.default.increment`) is never part of the physical text index: when the whole search term is a whole number (`2946`, `-12`; no leading zeros), rows whose member equals it exactly also match (OR'd with the text match). An integer member must be index-backed — the primary key (first `@meta.id`) or the first field of a `@db.index.plain` / `@db.index.unique`. Floats, decimals and timestamps are refused.\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@db.index.fulltext \"ft_content\"\n@db.index.unique\nrefNo: number.int\n```\n",
1103
+ validate(token, args, doc) {
1104
+ const field = token.parentNode;
1105
+ const verdict = searchFieldVerdict(field, doc);
1106
+ if ("problem" in verdict) return [{
1107
+ message: `@db.index.fulltext needs a string or an integer field — "${field.id}" ${verdict.problem}`,
1108
+ severity: 1,
1109
+ range: token.range
1110
+ }];
1111
+ if (verdict.kind !== "integer") return [];
1112
+ const errors = [];
1113
+ const missing = integerMemberIndexProblem(token);
1114
+ if (missing) errors.push({
1115
+ message: missing,
1116
+ severity: 1,
1117
+ range: token.range
1118
+ });
1119
+ if (args[1]) errors.push({
1120
+ message: "the weight is ignored on an integer member (it is matched by exact number, not by text)",
1121
+ severity: 2,
1122
+ range: args[1].range
1123
+ });
1124
+ return errors;
1125
+ },
1019
1126
  nodeType: ["prop"],
1020
1127
  passedWhenReferred: false,
1021
1128
  multiple: true,
@@ -1029,7 +1136,7 @@ const dbIndexAnnotations = { index: {
1029
1136
  optional: true,
1030
1137
  name: "weight",
1031
1138
  type: "number",
1032
- description: "Field importance in search results (higher = more relevant). Defaults to `1`. Supported by databases with weighted fulltext (e.g., MongoDB, PostgreSQL)."
1139
+ description: "Field importance in search results (higher = more relevant). Defaults to `1`. Supported by databases with weighted fulltext (e.g., MongoDB, PostgreSQL). Ignored on integer members."
1033
1140
  }]
1034
1141
  }),
1035
1142
  geo: new AnnotationSpec({
@@ -1074,7 +1181,7 @@ function checkCompositeFkCoverage(token, alias, refTypeName, targetStruct, targe
1074
1181
  }
1075
1182
  const dbRelAnnotations = { rel: {
1076
1183
  FK: new AnnotationSpec({
1077
- description: "Declares a foreign key reference on this field. The field must use a chain reference type (e.g., `User.id`) whose target is a primary key (`@meta.id`) or unique (`@db.index.unique`) field.\n\n**Dual role:**\n- On a `@db.table` interface, `@db.rel.FK` additionally drives DB-relation semantics — relation loading with `@db.rel.to` / `@db.rel.from`, junction pairing with `@db.rel.via`, etc.\n- On any other interface (value-help sources, WF forms, plain interfaces), `@db.rel.FK` acts purely as the value-help indicator: the client-side picker resolver uses it to decide which fields render a value-help picker. The target's `@db.http.path` (stamped by its readable controller) supplies the picker URL.\n- In `/meta` (and `/meta/form/:name`) the marker is inherited through reference chains: a field declared as `code: Issue.code` where `Issue.code: Dict.code` carries `@db.rel.FK` is served with `ref` pointing at the terminal field (`Dict.code`) and `db.rel.FK: true`, so view fields get the dictionary picker. Runtime metadata is untouched.\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",
1184
+ description: "Declares a foreign key reference on this field. The field must use a chain reference type (e.g., `User.id`) whose target is a primary key (`@meta.id`) or unique (`@db.index.unique`) field.\n\n**Dual role:**\n- On a `@db.table` interface, `@db.rel.FK` additionally drives DB-relation semantics — relation loading with `@db.rel.to` / `@db.rel.from`, junction pairing with `@db.rel.via`, etc.\n- On any other interface (value-help sources, WF forms, plain interfaces), `@db.rel.FK` acts purely as the value-help indicator: the client-side picker resolver uses it to decide which fields render a value-help picker. The target's `@db.http.path` (published by its canonical readable controller) supplies the picker URL.\n- In `/meta` (and `/meta/form/:name`) the marker is inherited through reference chains: a field declared as `code: Issue.code` where `Issue.code: Dict.code` carries `@db.rel.FK` is served with `ref` pointing at the terminal field (`Dict.code`) and `db.rel.FK: true`, so view fields get the dictionary picker. Runtime metadata is untouched.\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",
1078
1185
  nodeType: ["prop"],
1079
1186
  passedWhenReferred: false,
1080
1187
  argument: {
@@ -1666,7 +1773,7 @@ const dbTableAnnotations = {
1666
1773
  }
1667
1774
  }),
1668
1775
  http: { path: new AnnotationSpec({
1669
- description: "HTTP endpoint path where this table is served. Used by the UI for value-help on FK fields. Gets overwritten by the final controller prefix at runtime.\n\n**Example:**\n```atscript\n@db.table \"authors\"\n@db.http.path \"/authors\"\nexport interface Author { ... }\n```\n",
1776
+ description: "HTTP endpoint path where this table is served. Used by the UI for value-help on FK fields. After app.init() the app publishes the canonical controller route here at runtime; a model mounted on several routes needs `canonical: true` on one of its controllers (or declare this annotation and mount that controller without an explicit prefix).\n\n**Example:**\n```atscript\n@db.table \"authors\"\n@db.http.path \"/authors\"\nexport interface Author { ... }\n```\n",
1670
1777
  nodeType: ["interface"],
1671
1778
  passedWhenReferred: false,
1672
1779
  argument: {
package/dist/rel.d.cts CHANGED
@@ -1,6 +1,6 @@
1
- import { Jt as BaseDbAdapter, Nt as TTableResolver, Rt as TWriteTableResolver, U as TDbForeignKey, Ut as TableMetadata, Vn as TGenericLogger, et as TDbRelation, h as AtscriptDbWritable, m as AtscriptDbTableLike } from "./buckets-DJiYlMXc.cjs";
1
+ import { Hn as TGenericLogger, Pt as TTableResolver, U as TDbForeignKey, Wt as TableMetadata, Yt as BaseDbAdapter, et as TDbRelation, h as AtscriptDbWritable, m as AtscriptDbTableLike, zt as TWriteTableResolver } from "./buckets-24Ac4sIS.cjs";
2
2
  import { t as DbValidationContext } from "./db-validator-plugin-BWy60OvG.cjs";
3
- import { n as findRemoteFK, r as resolveRelationTargetTable, t as findFKForRelation } from "./relation-helpers-kX7jjgME.cjs";
3
+ import { n as findRemoteFK, r as resolveRelationTargetTable, t as findFKForRelation } from "./relation-helpers-DjVJN2ng.cjs";
4
4
  import { FilterExpr, WithRelation } from "@uniqu/core";
5
5
  import { Validator } from "@atscript/typescript/utils";
6
6
 
package/dist/rel.d.mts CHANGED
@@ -1,6 +1,6 @@
1
- import { Jt as BaseDbAdapter, Nt as TTableResolver, Rt as TWriteTableResolver, U as TDbForeignKey, Ut as TableMetadata, Vn as TGenericLogger, et as TDbRelation, h as AtscriptDbWritable, m as AtscriptDbTableLike } from "./buckets-CNdTOnei.mjs";
1
+ import { Hn as TGenericLogger, Pt as TTableResolver, U as TDbForeignKey, Wt as TableMetadata, Yt as BaseDbAdapter, et as TDbRelation, h as AtscriptDbWritable, m as AtscriptDbTableLike, zt as TWriteTableResolver } from "./buckets-XOz0Bw5x.mjs";
2
2
  import { t as DbValidationContext } from "./db-validator-plugin-BWy60OvG.mjs";
3
- import { n as findRemoteFK, r as resolveRelationTargetTable, t as findFKForRelation } from "./relation-helpers-Ba0v49sn.mjs";
3
+ import { n as findRemoteFK, r as resolveRelationTargetTable, t as findFKForRelation } from "./relation-helpers-H7UEVkHs.mjs";
4
4
  import { Validator } from "@atscript/typescript/utils";
5
5
  import { FilterExpr, WithRelation } from "@uniqu/core";
6
6
 
@@ -1,4 +1,4 @@
1
- import { U as TDbForeignKey, et as TDbRelation } from "./buckets-DJiYlMXc.cjs";
1
+ import { U as TDbForeignKey, et as TDbRelation } from "./buckets-24Ac4sIS.cjs";
2
2
  import { TAtscriptAnnotatedType } from "@atscript/typescript/utils";
3
3
 
4
4
  //#region src/rel/relation-helpers.d.ts
@@ -1,4 +1,4 @@
1
- import { U as TDbForeignKey, et as TDbRelation } from "./buckets-CNdTOnei.mjs";
1
+ import { U as TDbForeignKey, et as TDbRelation } from "./buckets-XOz0Bw5x.mjs";
2
2
  import { TAtscriptAnnotatedType } from "@atscript/typescript/utils";
3
3
 
4
4
  //#region src/rel/relation-helpers.d.ts