@atscript/db 0.1.142 → 0.1.143

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 (39) hide show
  1. package/dist/agg.d.cts +1 -1
  2. package/dist/agg.d.mts +1 -1
  3. package/dist/{buckets-Bv4pah66.d.cts → buckets-Ba5lP_o9.d.cts} +358 -17
  4. package/dist/{buckets-CjL7F-hp.d.mts → buckets-BlJ4cdlr.d.mts} +358 -17
  5. package/dist/{column-diff-DiBbXyLA.d.cts → column-diff-BlGxPobU.d.cts} +1 -1
  6. package/dist/{column-diff-CfPNcP6e.cjs → column-diff-D_Kyuh0S.cjs} +906 -209
  7. package/dist/{column-diff-n-k5KY0u.d.mts → column-diff-PqA_edXA.d.mts} +1 -1
  8. package/dist/{column-diff-CmFNXV8C.mjs → column-diff-e2oHc71_.mjs} +850 -177
  9. package/dist/index.cjs +16 -10
  10. package/dist/index.d.cts +30 -5
  11. package/dist/index.d.mts +30 -5
  12. package/dist/index.mjs +6 -5
  13. package/dist/nested-writer-CnOOAehr.mjs +880 -0
  14. package/dist/nested-writer-xfQwxplL.cjs +1029 -0
  15. package/dist/{object-DSN0h9lB.d.cts → object-CtPYTIvy.d.cts} +3 -1
  16. package/dist/{object-DSN0h9lB.d.mts → object-CtPYTIvy.d.mts} +3 -1
  17. package/dist/object-Djg28csK.cjs +101 -0
  18. package/dist/object-TkiJQ-Dp.mjs +66 -0
  19. package/dist/rel.cjs +5 -2
  20. package/dist/rel.d.cts +76 -10
  21. package/dist/rel.d.mts +76 -10
  22. package/dist/rel.mjs +3 -3
  23. package/dist/{relation-helpers-B59to_dG.d.mts → relation-helpers-JTEyZzVd.d.cts} +1 -1
  24. package/dist/{relation-helpers-DQ_nRsV9.d.cts → relation-helpers-MTwzaSGA.d.mts} +1 -1
  25. package/dist/{relation-loader-CgJ8bK6X.cjs → relation-loader-CBPY6kM7.cjs} +1 -1
  26. package/dist/{relation-loader-CuhEBzFU.mjs → relation-loader-D9XuXaMv.mjs} +1 -1
  27. package/dist/sync.cjs +1 -1
  28. package/dist/sync.d.cts +2 -2
  29. package/dist/sync.d.mts +2 -2
  30. package/dist/sync.mjs +1 -1
  31. package/dist/{validator-DASnXf1j.cjs → validator-CVS-onRg.cjs} +0 -101
  32. package/dist/{validator-D8bPsXPN.mjs → validator-Clu2q_7z.mjs} +1 -66
  33. package/dist/validator.cjs +4 -3
  34. package/dist/validator.d.cts +1 -1
  35. package/dist/validator.d.mts +1 -1
  36. package/dist/validator.mjs +2 -1
  37. package/package.json +1 -1
  38. package/dist/nested-writer-BO3vhbkP.mjs +0 -661
  39. package/dist/nested-writer-DYsRxZ5f.cjs +0 -768
@@ -13,6 +13,8 @@ declare function isEmptyObject(obj: Record<string, unknown>): boolean;
13
13
  * runtime reconstruction on the Readable.
14
14
  */
15
15
  declare function findAncestorInSet(path: string, set: ReadonlySet<string>): string | undefined;
16
+ /** `path` itself if it is in `set`, else its nearest ancestor in `set` ({@link findAncestorInSet}). */
17
+ declare function selfOrAncestor(path: string, set: ReadonlySet<string>): string | undefined;
16
18
  /**
17
19
  * Reads a dot-path (or its segments) off a nested plain object: `undefined`
18
20
  * past a missing step, or a step that is not a plain object (arrays are not
@@ -27,4 +29,4 @@ declare function getPath(row: Record<string, unknown>, path: string | readonly s
27
29
  */
28
30
  declare function deletePath(target: Record<string, unknown>, path: string | readonly string[]): void;
29
31
  //#endregion
30
- export { isPlainObject as a, isEmptyObject as i, findAncestorInSet as n, getPath as r, deletePath as t };
32
+ export { isPlainObject as a, isEmptyObject as i, findAncestorInSet as n, selfOrAncestor as o, getPath as r, deletePath as t };
@@ -13,6 +13,8 @@ declare function isEmptyObject(obj: Record<string, unknown>): boolean;
13
13
  * runtime reconstruction on the Readable.
14
14
  */
15
15
  declare function findAncestorInSet(path: string, set: ReadonlySet<string>): string | undefined;
16
+ /** `path` itself if it is in `set`, else its nearest ancestor in `set` ({@link findAncestorInSet}). */
17
+ declare function selfOrAncestor(path: string, set: ReadonlySet<string>): string | undefined;
16
18
  /**
17
19
  * Reads a dot-path (or its segments) off a nested plain object: `undefined`
18
20
  * past a missing step, or a step that is not a plain object (arrays are not
@@ -27,4 +29,4 @@ declare function getPath(row: Record<string, unknown>, path: string | readonly s
27
29
  */
28
30
  declare function deletePath(target: Record<string, unknown>, path: string | readonly string[]): void;
29
31
  //#endregion
30
- export { isPlainObject as a, isEmptyObject as i, findAncestorInSet as n, getPath as r, deletePath as t };
32
+ export { isPlainObject as a, isEmptyObject as i, findAncestorInSet as n, selfOrAncestor as o, getPath as r, deletePath as t };
@@ -0,0 +1,101 @@
1
+ //#region src/shared/object.ts
2
+ /**
3
+ * Plain object: prototype is `Object.prototype` or `null` — never a class
4
+ * instance (`Date`, `Uint8Array`, `ObjectId`, …) and never an array.
5
+ * Browser-safe; shared by the write paths, the HTTP shape gate and the client.
6
+ */
7
+ function isPlainObject(value) {
8
+ if (typeof value !== "object" || value === null) return false;
9
+ const proto = Object.getPrototypeOf(value);
10
+ return proto === Object.prototype || proto === null;
11
+ }
12
+ /** Zero-allocation emptiness check for a plain object (`{}` → `true`). */
13
+ function isEmptyObject(obj) {
14
+ for (const _ in obj) return false;
15
+ return true;
16
+ }
17
+ /**
18
+ * Finds the nearest ancestor of `path` that belongs to `set`.
19
+ * Used by both the build pipeline (in `_classifyFields`) and
20
+ * runtime reconstruction on the Readable.
21
+ */
22
+ function findAncestorInSet(path, set) {
23
+ let pos = path.length;
24
+ while ((pos = path.lastIndexOf(".", pos - 1)) !== -1) {
25
+ const ancestor = path.slice(0, pos);
26
+ if (set.has(ancestor)) return ancestor;
27
+ }
28
+ }
29
+ /** `path` itself if it is in `set`, else its nearest ancestor in `set` ({@link findAncestorInSet}). */
30
+ function selfOrAncestor(path, set) {
31
+ return set.has(path) ? path : findAncestorInSet(path, set);
32
+ }
33
+ /** Splits a dot-path once; an already split path is used as-is. */
34
+ function segmentsOf(path) {
35
+ return typeof path === "string" ? path.split(".") : path;
36
+ }
37
+ /**
38
+ * Reads a dot-path (or its segments) off a nested plain object: `undefined`
39
+ * past a missing step, or a step that is not a plain object (arrays are not
40
+ * descended into — no positional indexing).
41
+ * @since 0.1.141
42
+ */
43
+ function getPath(row, path) {
44
+ let current = row;
45
+ for (const seg of segmentsOf(path)) {
46
+ if (current === null || typeof current !== "object" || Array.isArray(current)) return;
47
+ current = current[seg];
48
+ }
49
+ return current;
50
+ }
51
+ /**
52
+ * Deletes a dot-path (or its segments) from a nested plain object; a no-op
53
+ * when an intermediate step is missing or not a plain object.
54
+ * @since 0.1.141
55
+ */
56
+ function deletePath(target, path) {
57
+ const segments = segmentsOf(path);
58
+ let current = target;
59
+ for (let i = 0; i < segments.length - 1; i++) {
60
+ if (current === null || typeof current !== "object" || Array.isArray(current)) return;
61
+ current = current[segments[i]];
62
+ }
63
+ if (current !== null && typeof current === "object" && !Array.isArray(current)) delete current[segments[segments.length - 1]];
64
+ }
65
+ //#endregion
66
+ Object.defineProperty(exports, "deletePath", {
67
+ enumerable: true,
68
+ get: function() {
69
+ return deletePath;
70
+ }
71
+ });
72
+ Object.defineProperty(exports, "findAncestorInSet", {
73
+ enumerable: true,
74
+ get: function() {
75
+ return findAncestorInSet;
76
+ }
77
+ });
78
+ Object.defineProperty(exports, "getPath", {
79
+ enumerable: true,
80
+ get: function() {
81
+ return getPath;
82
+ }
83
+ });
84
+ Object.defineProperty(exports, "isEmptyObject", {
85
+ enumerable: true,
86
+ get: function() {
87
+ return isEmptyObject;
88
+ }
89
+ });
90
+ Object.defineProperty(exports, "isPlainObject", {
91
+ enumerable: true,
92
+ get: function() {
93
+ return isPlainObject;
94
+ }
95
+ });
96
+ Object.defineProperty(exports, "selfOrAncestor", {
97
+ enumerable: true,
98
+ get: function() {
99
+ return selfOrAncestor;
100
+ }
101
+ });
@@ -0,0 +1,66 @@
1
+ //#region src/shared/object.ts
2
+ /**
3
+ * Plain object: prototype is `Object.prototype` or `null` — never a class
4
+ * instance (`Date`, `Uint8Array`, `ObjectId`, …) and never an array.
5
+ * Browser-safe; shared by the write paths, the HTTP shape gate and the client.
6
+ */
7
+ function isPlainObject(value) {
8
+ if (typeof value !== "object" || value === null) return false;
9
+ const proto = Object.getPrototypeOf(value);
10
+ return proto === Object.prototype || proto === null;
11
+ }
12
+ /** Zero-allocation emptiness check for a plain object (`{}` → `true`). */
13
+ function isEmptyObject(obj) {
14
+ for (const _ in obj) return false;
15
+ return true;
16
+ }
17
+ /**
18
+ * Finds the nearest ancestor of `path` that belongs to `set`.
19
+ * Used by both the build pipeline (in `_classifyFields`) and
20
+ * runtime reconstruction on the Readable.
21
+ */
22
+ function findAncestorInSet(path, set) {
23
+ let pos = path.length;
24
+ while ((pos = path.lastIndexOf(".", pos - 1)) !== -1) {
25
+ const ancestor = path.slice(0, pos);
26
+ if (set.has(ancestor)) return ancestor;
27
+ }
28
+ }
29
+ /** `path` itself if it is in `set`, else its nearest ancestor in `set` ({@link findAncestorInSet}). */
30
+ function selfOrAncestor(path, set) {
31
+ return set.has(path) ? path : findAncestorInSet(path, set);
32
+ }
33
+ /** Splits a dot-path once; an already split path is used as-is. */
34
+ function segmentsOf(path) {
35
+ return typeof path === "string" ? path.split(".") : path;
36
+ }
37
+ /**
38
+ * Reads a dot-path (or its segments) off a nested plain object: `undefined`
39
+ * past a missing step, or a step that is not a plain object (arrays are not
40
+ * descended into — no positional indexing).
41
+ * @since 0.1.141
42
+ */
43
+ function getPath(row, path) {
44
+ let current = row;
45
+ for (const seg of segmentsOf(path)) {
46
+ if (current === null || typeof current !== "object" || Array.isArray(current)) return;
47
+ current = current[seg];
48
+ }
49
+ return current;
50
+ }
51
+ /**
52
+ * Deletes a dot-path (or its segments) from a nested plain object; a no-op
53
+ * when an intermediate step is missing or not a plain object.
54
+ * @since 0.1.141
55
+ */
56
+ function deletePath(target, path) {
57
+ const segments = segmentsOf(path);
58
+ let current = target;
59
+ for (let i = 0; i < segments.length - 1; i++) {
60
+ if (current === null || typeof current !== "object" || Array.isArray(current)) return;
61
+ current = current[segments[i]];
62
+ }
63
+ if (current !== null && typeof current === "object" && !Array.isArray(current)) delete current[segments[segments.length - 1]];
64
+ }
65
+ //#endregion
66
+ export { isPlainObject as a, isEmptyObject as i, findAncestorInSet as n, selfOrAncestor as o, getPath as r, deletePath as t };
package/dist/rel.cjs CHANGED
@@ -1,6 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_nested_writer = require("./nested-writer-DYsRxZ5f.cjs");
3
- const require_relation_loader = require("./relation-loader-CgJ8bK6X.cjs");
2
+ const require_nested_writer = require("./nested-writer-xfQwxplL.cjs");
3
+ const require_relation_loader = require("./relation-loader-CBPY6kM7.cjs");
4
+ exports.applyPatchNestedTo = require_nested_writer.applyPatchNestedTo;
4
5
  exports.batchInsertNestedFrom = require_nested_writer.batchInsertNestedFrom;
5
6
  exports.batchInsertNestedTo = require_nested_writer.batchInsertNestedTo;
6
7
  exports.batchInsertNestedVia = require_nested_writer.batchInsertNestedVia;
@@ -14,6 +15,8 @@ exports.checkDepthOverflow = require_nested_writer.checkDepthOverflow;
14
15
  exports.findFKForRelation = require_nested_writer.findFKForRelation;
15
16
  exports.findRemoteFK = require_nested_writer.findRemoteFK;
16
17
  exports.loadRelationsImpl = require_relation_loader.loadRelationsImpl;
18
+ exports.planNestedFromVia = require_nested_writer.planNestedFromVia;
19
+ exports.planPatchNestedTo = require_nested_writer.planPatchNestedTo;
17
20
  exports.preValidateNestedFrom = require_nested_writer.preValidateNestedFrom;
18
21
  exports.resolveRelationTargetTable = require_nested_writer.resolveRelationTargetTable;
19
22
  exports.validateBatch = require_nested_writer.validateBatch;
package/dist/rel.d.cts CHANGED
@@ -1,6 +1,6 @@
1
- import { Dt as TWriteTableResolver, It as BaseDbAdapter, Mt as TableMetadata, St as TTableResolver, q as TDbRelation, un as TGenericLogger, z as TDbForeignKey } from "./buckets-Bv4pah66.cjs";
1
+ import { B as TDbForeignKey, Bt as BaseDbAdapter, Et as TTableResolver, It as TableMetadata, J as TDbRelation, hn as TGenericLogger, jt as TWriteTableResolver, m as AtscriptDbWritable, p as AtscriptDbTableLike } from "./buckets-Ba5lP_o9.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-DQ_nRsV9.cjs";
3
+ import { n as findRemoteFK, r as resolveRelationTargetTable, t as findFKForRelation } from "./relation-helpers-JTEyZzVd.cjs";
4
4
  import { FilterExpr, WithRelation } from "@uniqu/core";
5
5
  import { Validator } from "@atscript/typescript/utils";
6
6
 
@@ -40,7 +40,9 @@ interface TNestedWriterHost {
40
40
  _extractRecordFilter(payload: Record<string, unknown>): FilterExpr;
41
41
  findOne(query: {
42
42
  filter: FilterExpr;
43
- controls: Record<string, never>;
43
+ controls: {
44
+ $select?: string[];
45
+ };
44
46
  }): Promise<Record<string, unknown> | null>;
45
47
  }
46
48
  /**
@@ -75,27 +77,91 @@ declare function batchInsertNestedVia(host: TNestedWriterHost, originals: Array<
75
77
  */
76
78
  declare function batchReplaceNestedTo(host: TNestedWriterHost, items: Array<Record<string, unknown>>, maxDepth: number, depth: number): Promise<void>;
77
79
  /**
78
- * Batch-replaces FROM dependents after the main replace.
80
+ * Batch-replaces FROM dependents after the main replace. `plan` is the
81
+ * {@link planNestedFromVia} result for `originals` (`mode: "replace"`);
82
+ * omitted, the plan is made here first.
79
83
  */
80
- declare function batchReplaceNestedFrom(host: TNestedWriterHost, originals: Array<Record<string, unknown>>, maxDepth: number, depth: number): Promise<void>;
84
+ declare function batchReplaceNestedFrom(host: TNestedWriterHost, originals: Array<Record<string, unknown>>, maxDepth: number, depth: number, plan?: TNestedFromViaPlan): Promise<void>;
81
85
  /**
82
86
  * Handles VIA (M:N) relations during replace.
83
87
  */
84
88
  declare function batchReplaceNestedVia(host: TNestedWriterHost, originals: Array<Record<string, unknown>>, maxDepth: number, depth: number): Promise<void>;
85
89
  /**
86
- * Batch-patches TO dependencies before the main patch.
87
- * Reads FK values from DB if not present in the payload.
90
+ * The FROM / VIA nested writes of a batch, validated by
91
+ * {@link planNestedFromVia} before the main write (since 0.1.143).
92
+ */
93
+ interface TNestedFromViaPlan {
94
+ /**
95
+ * Per FROM nav field: the {@link pkTupleKey}s of the keyed children that
96
+ * exist under the parent naming them. Their writes must match (a child
97
+ * re-parented meanwhile → `CONFLICT`, rolled back).
98
+ */
99
+ readonly owned: ReadonlyMap<string, ReadonlySet<string>>;
100
+ }
101
+ /**
102
+ * Validates the FROM and VIA nested writes of a batch BEFORE the main write
103
+ * (since 0.1.143), so a rejected nested operation never leaves the main
104
+ * write applied — even on adapters whose transaction is a pass-through. One
105
+ * read per relation for the whole batch; the entries are evaluated in memory
106
+ * in apply order (`$replace` → `$update` → `$upsert`). `mode: "patch"`
107
+ * checks the operators, `mode: "replace"` the plain arrays of a PUT:
108
+ * - FROM: a keyed child that exists under another parent (or none) → `CONFLICT`;
109
+ * a `$update` entry without the child PK → `NOT_FOUND`;
110
+ * - VIA: a keyed `$update` / `$upsert` target not linked to the parent →
111
+ * `CONFLICT`; a `$update` entry without the target PK → `NOT_FOUND`.
112
+ *
113
+ * The apply phases write the planned FROM children through filters
114
+ * constrained to their parent (`{ pk, fk: parentPK }`), so a child
115
+ * re-parented between the plan and the write is never touched.
116
+ */
117
+ declare function planNestedFromVia(host: TNestedWriterHost, originals: Array<Record<string, unknown>>, mode: "patch" | "replace"): Promise<TNestedFromViaPlan>;
118
+ /** One TO relation's nested patches, planned before the main patch — see {@link planPatchNestedTo}. */
119
+ interface TNestedToPatchPlan {
120
+ navField: string;
121
+ targetTable: AtscriptDbTableLike & AtscriptDbWritable;
122
+ /** `index` = position of the source item in the batch. */
123
+ entries: Array<{
124
+ index: number;
125
+ patch: Record<string, unknown>;
126
+ }>;
127
+ }
128
+ /**
129
+ * Plans the TO relation patches of a batch BEFORE the main patch (since
130
+ * 0.1.143). A nested TO object always patches the row the STORED foreign key
131
+ * references. `targets[i]` is item `i`'s stored row (at least its foreign-key
132
+ * columns; `null` = missing) as the table pinned it inside the transaction;
133
+ * omitted, each item's row is read here. Rejected with `INVALID_QUERY`
134
+ * (nothing written): a payload that changes that foreign key AND carries a
135
+ * nested object for the same relation, and a nested object whose own key
136
+ * names a different row. An item whose source row does not exist is skipped
137
+ * (the main patch matches nothing for it). A stored `null` foreign key (or a
138
+ * composite one) → `FK_VIOLATION`.
139
+ */
140
+ declare function planPatchNestedTo(host: TNestedWriterHost, items: Array<Record<string, unknown>>, targets?: ReadonlyArray<Record<string, unknown> | null | undefined>): Promise<TNestedToPatchPlan[]>;
141
+ /**
142
+ * Applies the TO patches {@link planPatchNestedTo} planned — after the main
143
+ * patch, only for the items it matched (`matched[i]`; all when omitted).
144
+ */
145
+ declare function applyPatchNestedTo(plans: ReadonlyArray<TNestedToPatchPlan>, maxDepth: number, depth: number, matched?: ReadonlyArray<boolean>): Promise<void>;
146
+ /**
147
+ * Batch-patches TO dependencies: {@link planPatchNestedTo} +
148
+ * {@link applyPatchNestedTo} in one step (the table itself plans before and
149
+ * applies after its main patch).
88
150
  */
89
151
  declare function batchPatchNestedTo(host: TNestedWriterHost, items: Array<Record<string, unknown>>, maxDepth: number, depth: number): Promise<void>;
90
152
  /**
91
153
  * Batch-patches FROM (1:N) dependencies after the main patch.
92
154
  * Supports patch operators: $replace, $insert, $remove, $update, $upsert.
155
+ * `plan` is the {@link planNestedFromVia} result for `originals`
156
+ * (`mode: "patch"`); omitted, the plan is made here first.
93
157
  */
94
- declare function batchPatchNestedFrom(host: TNestedWriterHost, originals: Array<Record<string, unknown>>, maxDepth: number, depth: number): Promise<void>;
158
+ declare function batchPatchNestedFrom(host: TNestedWriterHost, originals: Array<Record<string, unknown>>, maxDepth: number, depth: number, plan?: TNestedFromViaPlan): Promise<void>;
95
159
  /**
96
160
  * Batch-patches VIA (M:N) dependencies after the main patch.
97
161
  * Supports patch operators: $replace, $insert, $remove, $update, $upsert.
162
+ * `plan` marks `originals` as already validated by {@link planNestedFromVia}
163
+ * (`mode: "patch"`); omitted, they are validated here first.
98
164
  */
99
- declare function batchPatchNestedVia(host: TNestedWriterHost, originals: Array<Record<string, unknown>>, maxDepth: number, depth: number): Promise<void>;
165
+ declare function batchPatchNestedVia(host: TNestedWriterHost, originals: Array<Record<string, unknown>>, maxDepth: number, depth: number, plan?: TNestedFromViaPlan): Promise<void>;
100
166
  //#endregion
101
- export { type TNestedWriterHost, type TRelationLoaderHost, batchInsertNestedFrom, batchInsertNestedTo, batchInsertNestedVia, batchPatchNestedFrom, batchPatchNestedTo, batchPatchNestedVia, batchReplaceNestedFrom, batchReplaceNestedTo, batchReplaceNestedVia, checkDepthOverflow, findFKForRelation, findRemoteFK, loadRelationsImpl, preValidateNestedFrom, resolveRelationTargetTable, validateBatch };
167
+ export { type TNestedFromViaPlan, type TNestedToPatchPlan, type TNestedWriterHost, type TRelationLoaderHost, applyPatchNestedTo, batchInsertNestedFrom, batchInsertNestedTo, batchInsertNestedVia, batchPatchNestedFrom, batchPatchNestedTo, batchPatchNestedVia, batchReplaceNestedFrom, batchReplaceNestedTo, batchReplaceNestedVia, checkDepthOverflow, findFKForRelation, findRemoteFK, loadRelationsImpl, planNestedFromVia, planPatchNestedTo, preValidateNestedFrom, resolveRelationTargetTable, validateBatch };
package/dist/rel.d.mts CHANGED
@@ -1,6 +1,6 @@
1
- import { Dt as TWriteTableResolver, It as BaseDbAdapter, Mt as TableMetadata, St as TTableResolver, q as TDbRelation, un as TGenericLogger, z as TDbForeignKey } from "./buckets-CjL7F-hp.mjs";
1
+ import { B as TDbForeignKey, Bt as BaseDbAdapter, Et as TTableResolver, It as TableMetadata, J as TDbRelation, hn as TGenericLogger, jt as TWriteTableResolver, m as AtscriptDbWritable, p as AtscriptDbTableLike } from "./buckets-BlJ4cdlr.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-B59to_dG.mjs";
3
+ import { n as findRemoteFK, r as resolveRelationTargetTable, t as findFKForRelation } from "./relation-helpers-MTwzaSGA.mjs";
4
4
  import { Validator } from "@atscript/typescript/utils";
5
5
  import { FilterExpr, WithRelation } from "@uniqu/core";
6
6
 
@@ -40,7 +40,9 @@ interface TNestedWriterHost {
40
40
  _extractRecordFilter(payload: Record<string, unknown>): FilterExpr;
41
41
  findOne(query: {
42
42
  filter: FilterExpr;
43
- controls: Record<string, never>;
43
+ controls: {
44
+ $select?: string[];
45
+ };
44
46
  }): Promise<Record<string, unknown> | null>;
45
47
  }
46
48
  /**
@@ -75,27 +77,91 @@ declare function batchInsertNestedVia(host: TNestedWriterHost, originals: Array<
75
77
  */
76
78
  declare function batchReplaceNestedTo(host: TNestedWriterHost, items: Array<Record<string, unknown>>, maxDepth: number, depth: number): Promise<void>;
77
79
  /**
78
- * Batch-replaces FROM dependents after the main replace.
80
+ * Batch-replaces FROM dependents after the main replace. `plan` is the
81
+ * {@link planNestedFromVia} result for `originals` (`mode: "replace"`);
82
+ * omitted, the plan is made here first.
79
83
  */
80
- declare function batchReplaceNestedFrom(host: TNestedWriterHost, originals: Array<Record<string, unknown>>, maxDepth: number, depth: number): Promise<void>;
84
+ declare function batchReplaceNestedFrom(host: TNestedWriterHost, originals: Array<Record<string, unknown>>, maxDepth: number, depth: number, plan?: TNestedFromViaPlan): Promise<void>;
81
85
  /**
82
86
  * Handles VIA (M:N) relations during replace.
83
87
  */
84
88
  declare function batchReplaceNestedVia(host: TNestedWriterHost, originals: Array<Record<string, unknown>>, maxDepth: number, depth: number): Promise<void>;
85
89
  /**
86
- * Batch-patches TO dependencies before the main patch.
87
- * Reads FK values from DB if not present in the payload.
90
+ * The FROM / VIA nested writes of a batch, validated by
91
+ * {@link planNestedFromVia} before the main write (since 0.1.143).
92
+ */
93
+ interface TNestedFromViaPlan {
94
+ /**
95
+ * Per FROM nav field: the {@link pkTupleKey}s of the keyed children that
96
+ * exist under the parent naming them. Their writes must match (a child
97
+ * re-parented meanwhile → `CONFLICT`, rolled back).
98
+ */
99
+ readonly owned: ReadonlyMap<string, ReadonlySet<string>>;
100
+ }
101
+ /**
102
+ * Validates the FROM and VIA nested writes of a batch BEFORE the main write
103
+ * (since 0.1.143), so a rejected nested operation never leaves the main
104
+ * write applied — even on adapters whose transaction is a pass-through. One
105
+ * read per relation for the whole batch; the entries are evaluated in memory
106
+ * in apply order (`$replace` → `$update` → `$upsert`). `mode: "patch"`
107
+ * checks the operators, `mode: "replace"` the plain arrays of a PUT:
108
+ * - FROM: a keyed child that exists under another parent (or none) → `CONFLICT`;
109
+ * a `$update` entry without the child PK → `NOT_FOUND`;
110
+ * - VIA: a keyed `$update` / `$upsert` target not linked to the parent →
111
+ * `CONFLICT`; a `$update` entry without the target PK → `NOT_FOUND`.
112
+ *
113
+ * The apply phases write the planned FROM children through filters
114
+ * constrained to their parent (`{ pk, fk: parentPK }`), so a child
115
+ * re-parented between the plan and the write is never touched.
116
+ */
117
+ declare function planNestedFromVia(host: TNestedWriterHost, originals: Array<Record<string, unknown>>, mode: "patch" | "replace"): Promise<TNestedFromViaPlan>;
118
+ /** One TO relation's nested patches, planned before the main patch — see {@link planPatchNestedTo}. */
119
+ interface TNestedToPatchPlan {
120
+ navField: string;
121
+ targetTable: AtscriptDbTableLike & AtscriptDbWritable;
122
+ /** `index` = position of the source item in the batch. */
123
+ entries: Array<{
124
+ index: number;
125
+ patch: Record<string, unknown>;
126
+ }>;
127
+ }
128
+ /**
129
+ * Plans the TO relation patches of a batch BEFORE the main patch (since
130
+ * 0.1.143). A nested TO object always patches the row the STORED foreign key
131
+ * references. `targets[i]` is item `i`'s stored row (at least its foreign-key
132
+ * columns; `null` = missing) as the table pinned it inside the transaction;
133
+ * omitted, each item's row is read here. Rejected with `INVALID_QUERY`
134
+ * (nothing written): a payload that changes that foreign key AND carries a
135
+ * nested object for the same relation, and a nested object whose own key
136
+ * names a different row. An item whose source row does not exist is skipped
137
+ * (the main patch matches nothing for it). A stored `null` foreign key (or a
138
+ * composite one) → `FK_VIOLATION`.
139
+ */
140
+ declare function planPatchNestedTo(host: TNestedWriterHost, items: Array<Record<string, unknown>>, targets?: ReadonlyArray<Record<string, unknown> | null | undefined>): Promise<TNestedToPatchPlan[]>;
141
+ /**
142
+ * Applies the TO patches {@link planPatchNestedTo} planned — after the main
143
+ * patch, only for the items it matched (`matched[i]`; all when omitted).
144
+ */
145
+ declare function applyPatchNestedTo(plans: ReadonlyArray<TNestedToPatchPlan>, maxDepth: number, depth: number, matched?: ReadonlyArray<boolean>): Promise<void>;
146
+ /**
147
+ * Batch-patches TO dependencies: {@link planPatchNestedTo} +
148
+ * {@link applyPatchNestedTo} in one step (the table itself plans before and
149
+ * applies after its main patch).
88
150
  */
89
151
  declare function batchPatchNestedTo(host: TNestedWriterHost, items: Array<Record<string, unknown>>, maxDepth: number, depth: number): Promise<void>;
90
152
  /**
91
153
  * Batch-patches FROM (1:N) dependencies after the main patch.
92
154
  * Supports patch operators: $replace, $insert, $remove, $update, $upsert.
155
+ * `plan` is the {@link planNestedFromVia} result for `originals`
156
+ * (`mode: "patch"`); omitted, the plan is made here first.
93
157
  */
94
- declare function batchPatchNestedFrom(host: TNestedWriterHost, originals: Array<Record<string, unknown>>, maxDepth: number, depth: number): Promise<void>;
158
+ declare function batchPatchNestedFrom(host: TNestedWriterHost, originals: Array<Record<string, unknown>>, maxDepth: number, depth: number, plan?: TNestedFromViaPlan): Promise<void>;
95
159
  /**
96
160
  * Batch-patches VIA (M:N) dependencies after the main patch.
97
161
  * Supports patch operators: $replace, $insert, $remove, $update, $upsert.
162
+ * `plan` marks `originals` as already validated by {@link planNestedFromVia}
163
+ * (`mode: "patch"`); omitted, they are validated here first.
98
164
  */
99
- declare function batchPatchNestedVia(host: TNestedWriterHost, originals: Array<Record<string, unknown>>, maxDepth: number, depth: number): Promise<void>;
165
+ declare function batchPatchNestedVia(host: TNestedWriterHost, originals: Array<Record<string, unknown>>, maxDepth: number, depth: number, plan?: TNestedFromViaPlan): Promise<void>;
100
166
  //#endregion
101
- export { type TNestedWriterHost, type TRelationLoaderHost, batchInsertNestedFrom, batchInsertNestedTo, batchInsertNestedVia, batchPatchNestedFrom, batchPatchNestedTo, batchPatchNestedVia, batchReplaceNestedFrom, batchReplaceNestedTo, batchReplaceNestedVia, checkDepthOverflow, findFKForRelation, findRemoteFK, loadRelationsImpl, preValidateNestedFrom, resolveRelationTargetTable, validateBatch };
167
+ export { type TNestedFromViaPlan, type TNestedToPatchPlan, type TNestedWriterHost, type TRelationLoaderHost, applyPatchNestedTo, batchInsertNestedFrom, batchInsertNestedTo, batchInsertNestedVia, batchPatchNestedFrom, batchPatchNestedTo, batchPatchNestedVia, batchReplaceNestedFrom, batchReplaceNestedTo, batchReplaceNestedVia, checkDepthOverflow, findFKForRelation, findRemoteFK, loadRelationsImpl, planNestedFromVia, planPatchNestedTo, preValidateNestedFrom, resolveRelationTargetTable, validateBatch };
package/dist/rel.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { t as loadRelationsImpl } from "./relation-loader-CuhEBzFU.mjs";
2
- import { _ as resolveRelationTargetTable, a as batchPatchNestedTo, c as batchReplaceNestedTo, d as preValidateNestedFrom, f as validateBatch, g as findRemoteFK, h as findFKForRelation, i as batchPatchNestedFrom, l as batchReplaceNestedVia, n as batchInsertNestedTo, o as batchPatchNestedVia, r as batchInsertNestedVia, s as batchReplaceNestedFrom, t as batchInsertNestedFrom, u as checkDepthOverflow } from "./nested-writer-BO3vhbkP.mjs";
3
- export { batchInsertNestedFrom, batchInsertNestedTo, batchInsertNestedVia, batchPatchNestedFrom, batchPatchNestedTo, batchPatchNestedVia, batchReplaceNestedFrom, batchReplaceNestedTo, batchReplaceNestedVia, checkDepthOverflow, findFKForRelation, findRemoteFK, loadRelationsImpl, preValidateNestedFrom, resolveRelationTargetTable, validateBatch };
1
+ import { t as loadRelationsImpl } from "./relation-loader-D9XuXaMv.mjs";
2
+ import { C as findRemoteFK, S as findFKForRelation, a as batchPatchNestedFrom, c as batchReplaceNestedFrom, d as checkDepthOverflow, f as planNestedFromVia, h as validateBatch, i as batchInsertNestedVia, l as batchReplaceNestedTo, m as preValidateNestedFrom, n as batchInsertNestedFrom, o as batchPatchNestedTo, p as planPatchNestedTo, r as batchInsertNestedTo, s as batchPatchNestedVia, t as applyPatchNestedTo, u as batchReplaceNestedVia, w as resolveRelationTargetTable } from "./nested-writer-CnOOAehr.mjs";
3
+ export { applyPatchNestedTo, batchInsertNestedFrom, batchInsertNestedTo, batchInsertNestedVia, batchPatchNestedFrom, batchPatchNestedTo, batchPatchNestedVia, batchReplaceNestedFrom, batchReplaceNestedTo, batchReplaceNestedVia, checkDepthOverflow, findFKForRelation, findRemoteFK, loadRelationsImpl, planNestedFromVia, planPatchNestedTo, preValidateNestedFrom, resolveRelationTargetTable, validateBatch };
@@ -1,4 +1,4 @@
1
- import { q as TDbRelation, z as TDbForeignKey } from "./buckets-CjL7F-hp.mjs";
1
+ import { B as TDbForeignKey, J as TDbRelation } from "./buckets-Ba5lP_o9.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 { q as TDbRelation, z as TDbForeignKey } from "./buckets-Bv4pah66.cjs";
1
+ import { B as TDbForeignKey, J as TDbRelation } from "./buckets-BlJ4cdlr.mjs";
2
2
  import { TAtscriptAnnotatedType } from "@atscript/typescript/utils";
3
3
 
4
4
  //#region src/rel/relation-helpers.d.ts
@@ -1,4 +1,4 @@
1
- const require_nested_writer = require("./nested-writer-DYsRxZ5f.cjs");
1
+ const require_nested_writer = require("./nested-writer-xfQwxplL.cjs");
2
2
  //#region src/rel/relation-loader.ts
3
3
  var relation_loader_exports = /* @__PURE__ */ require("./plugin.cjs").__exportAll({
4
4
  ensureSelectIncludesFields: () => ensureSelectIncludesFields,
@@ -1,4 +1,4 @@
1
- import { _ as resolveRelationTargetTable, g as findRemoteFK, h as findFKForRelation } from "./nested-writer-BO3vhbkP.mjs";
1
+ import { C as findRemoteFK, S as findFKForRelation, w as resolveRelationTargetTable } from "./nested-writer-CnOOAehr.mjs";
2
2
  //#region \0rolldown/runtime.js
3
3
  var __defProp = Object.defineProperty;
4
4
  var __exportAll = (all, no_symbols) => {
package/dist/sync.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_db_error = require("./db-error-DTkkeu5b.cjs");
3
- const require_column_diff = require("./column-diff-CfPNcP6e.cjs");
3
+ const require_column_diff = require("./column-diff-D_Kyuh0S.cjs");
4
4
  //#region src/schema/table-option-diff.ts
5
5
  /**
6
6
  * Computes the difference between desired and existing table options.
package/dist/sync.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { G as TDbObjectKind, Lt as DbSpace, R as TDbFieldMeta, _t as TReferencingForeignKey, gt as TPrimaryKeyChange, it as TEnsureTableOptions, nt as TDerivedChangeReason, ot as TExistingForeignKey, st as TExistingTableOption, un as TGenericLogger, xt as TTableOptionDiff, z as TDbForeignKey } from "./buckets-Bv4pah66.cjs";
2
- import { a as TTableSnapshot, c as TViewSnapshot, d as computeTableHash, f as computeTableSnapshot, h as snapshotToExistingTableOptions, i as TForeignKeySnapshot, l as canonicalizeQueryNode, m as snapshotToExistingColumns, o as TViewColumnSnapshot, p as computeViewSnapshot, r as TFieldSnapshot, s as TViewJoinSnapshot, t as computeColumnDiff, u as computeSchemaHash } from "./column-diff-DiBbXyLA.cjs";
1
+ import { B as TDbForeignKey, K as TDbObjectKind, Tt as TTableOptionDiff, Vt as DbSpace, at as TDerivedChangeReason, bt as TReferencingForeignKey, hn as TGenericLogger, lt as TExistingForeignKey, st as TEnsureTableOptions, ut as TExistingTableOption, yt as TPrimaryKeyChange, z as TDbFieldMeta } from "./buckets-Ba5lP_o9.cjs";
2
+ import { a as TTableSnapshot, c as TViewSnapshot, d as computeTableHash, f as computeTableSnapshot, h as snapshotToExistingTableOptions, i as TForeignKeySnapshot, l as canonicalizeQueryNode, m as snapshotToExistingColumns, o as TViewColumnSnapshot, p as computeViewSnapshot, r as TFieldSnapshot, s as TViewJoinSnapshot, t as computeColumnDiff, u as computeSchemaHash } from "./column-diff-BlGxPobU.cjs";
3
3
  import { TAtscriptAnnotatedType } from "@atscript/typescript/utils";
4
4
 
5
5
  //#region src/schema/sync-entry.d.ts
package/dist/sync.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { G as TDbObjectKind, Lt as DbSpace, R as TDbFieldMeta, _t as TReferencingForeignKey, gt as TPrimaryKeyChange, it as TEnsureTableOptions, nt as TDerivedChangeReason, ot as TExistingForeignKey, st as TExistingTableOption, un as TGenericLogger, xt as TTableOptionDiff, z as TDbForeignKey } from "./buckets-CjL7F-hp.mjs";
2
- import { a as TTableSnapshot, c as TViewSnapshot, d as computeTableHash, f as computeTableSnapshot, h as snapshotToExistingTableOptions, i as TForeignKeySnapshot, l as canonicalizeQueryNode, m as snapshotToExistingColumns, o as TViewColumnSnapshot, p as computeViewSnapshot, r as TFieldSnapshot, s as TViewJoinSnapshot, t as computeColumnDiff, u as computeSchemaHash } from "./column-diff-n-k5KY0u.mjs";
1
+ import { B as TDbForeignKey, K as TDbObjectKind, Tt as TTableOptionDiff, Vt as DbSpace, at as TDerivedChangeReason, bt as TReferencingForeignKey, hn as TGenericLogger, lt as TExistingForeignKey, st as TEnsureTableOptions, ut as TExistingTableOption, yt as TPrimaryKeyChange, z as TDbFieldMeta } from "./buckets-BlJ4cdlr.mjs";
2
+ import { a as TTableSnapshot, c as TViewSnapshot, d as computeTableHash, f as computeTableSnapshot, h as snapshotToExistingTableOptions, i as TForeignKeySnapshot, l as canonicalizeQueryNode, m as snapshotToExistingColumns, o as TViewColumnSnapshot, p as computeViewSnapshot, r as TFieldSnapshot, s as TViewJoinSnapshot, t as computeColumnDiff, u as computeSchemaHash } from "./column-diff-PqA_edXA.mjs";
3
3
  import { TAtscriptAnnotatedType } from "@atscript/typescript/utils";
4
4
 
5
5
  //#region src/schema/sync-entry.d.ts
package/dist/sync.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { r as DbError } from "./db-error-D5uilS_A.mjs";
2
- import { Z as aliasTargetOf, a as hasForeignKeyChanges, c as computeTableHash, d as snapshotToExistingColumns, f as snapshotToExistingTableOptions, i as fkKey, l as computeTableSnapshot, o as canonicalizeQueryNode, p as viewSnapshotSources, r as computeForeignKeyDiff, rt as NoopLogger, s as computeSchemaHash, t as computeColumnDiff, u as computeViewSnapshot } from "./column-diff-CmFNXV8C.mjs";
2
+ import { a as hasForeignKeyChanges, c as computeTableHash, d as snapshotToExistingColumns, f as snapshotToExistingTableOptions, i as fkKey, l as computeTableSnapshot, o as canonicalizeQueryNode, p as viewSnapshotSources, r as computeForeignKeyDiff, s as computeSchemaHash, st as NoopLogger, t as computeColumnDiff, tt as aliasTargetOf, u as computeViewSnapshot } from "./column-diff-e2oHc71_.mjs";
3
3
  //#region src/schema/table-option-diff.ts
4
4
  /**
5
5
  * Computes the difference between desired and existing table options.