@atscript/db 0.1.140 → 0.1.141
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agg.d.cts +1 -1
- package/dist/agg.d.mts +1 -1
- package/dist/{buckets-C-27xmtq.d.cts → buckets-Bv4pah66.d.cts} +225 -22
- package/dist/{buckets-BFG2RYRW.d.mts → buckets-CjL7F-hp.d.mts} +225 -22
- package/dist/{column-diff-CgxgFKzx.cjs → column-diff-CfPNcP6e.cjs} +663 -239
- package/dist/{column-diff-BwOA5101.mjs → column-diff-CmFNXV8C.mjs} +638 -220
- package/dist/column-diff-DiBbXyLA.d.cts +211 -0
- package/dist/column-diff-n-k5KY0u.d.mts +211 -0
- package/dist/derived-rules-0sKn4f5C.mjs +44 -0
- package/dist/derived-rules-YstgIxG-.cjs +67 -0
- package/dist/index.cjs +38 -4
- package/dist/index.d.cts +23 -6
- package/dist/index.d.mts +23 -6
- package/dist/index.mjs +34 -4
- package/dist/{nested-writer-FWD5oOYh.mjs → nested-writer-BO3vhbkP.mjs} +8 -4
- package/dist/{nested-writer-BZNCuqI6.cjs → nested-writer-DYsRxZ5f.cjs} +8 -4
- package/dist/object-DSN0h9lB.d.cts +30 -0
- package/dist/object-DSN0h9lB.d.mts +30 -0
- package/dist/plugin.cjs +392 -139
- package/dist/plugin.mjs +392 -139
- package/dist/rel.cjs +2 -2
- package/dist/rel.d.cts +2 -2
- package/dist/rel.d.mts +2 -2
- package/dist/rel.mjs +2 -2
- package/dist/{relation-helpers-D3Zu0Mta.d.mts → relation-helpers-B59to_dG.d.mts} +5 -4
- package/dist/{relation-helpers-DxrvS6ar.d.cts → relation-helpers-DQ_nRsV9.d.cts} +5 -4
- package/dist/{relation-loader-6ZB_5KFq.cjs → relation-loader-CgJ8bK6X.cjs} +1 -1
- package/dist/{relation-loader-CTFaZpVa.mjs → relation-loader-CuhEBzFU.mjs} +1 -1
- package/dist/shared.cjs +6 -1
- package/dist/shared.d.cts +48 -9
- package/dist/shared.d.mts +48 -9
- package/dist/shared.mjs +2 -2
- package/dist/sync.cjs +331 -105
- package/dist/sync.d.cts +62 -163
- package/dist/sync.d.mts +62 -163
- package/dist/sync.mjs +331 -105
- package/dist/{validation-utils-B4h-GW4d.mjs → validation-utils-CMR4fe2M.mjs} +99 -34
- package/dist/{validation-utils-Dg0hW6dn.cjs → validation-utils-DOsB4e6G.cjs} +128 -33
- package/dist/{validator-Drb2N-YL.d.cts → validator-Bw6ks9Hy.d.cts} +1 -11
- package/dist/{validator-Drb2N-YL.d.mts → validator-Bw6ks9Hy.d.mts} +1 -11
- package/dist/{validator-Ch7UIQl9.mjs → validator-D8bPsXPN.mjs} +54 -2
- package/dist/{validator-BtZbcLN2.cjs → validator-DASnXf1j.cjs} +77 -1
- package/dist/validator.cjs +1 -1
- package/dist/validator.d.cts +2 -1
- package/dist/validator.d.mts +2 -1
- package/dist/validator.mjs +1 -1
- package/package.json +6 -6
- package/dist/column-diff-BmqvgBWw.d.cts +0 -24
- package/dist/column-diff-DPkbZIVE.d.mts +0 -24
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
const require_db_error = require("./db-error-DTkkeu5b.cjs");
|
|
2
2
|
const require_aggregate_fns = require("./aggregate-fns-CGBv3E8S.cjs");
|
|
3
|
-
const require_nested_writer = require("./nested-writer-
|
|
3
|
+
const require_nested_writer = require("./nested-writer-DYsRxZ5f.cjs");
|
|
4
|
+
const require_derived_rules = require("./derived-rules-YstgIxG-.cjs");
|
|
5
|
+
const require_validator = require("./validator-DASnXf1j.cjs");
|
|
4
6
|
require("./agg.cjs");
|
|
5
|
-
const require_validator = require("./validator-BtZbcLN2.cjs");
|
|
6
7
|
const require_ops = require("./ops.cjs");
|
|
7
8
|
let _atscript_typescript_utils = require("@atscript/typescript/utils");
|
|
8
9
|
let _uniqu_core = require("@uniqu/core");
|
|
@@ -101,22 +102,184 @@ function jsonValueAncestor(path, jsonValueParents) {
|
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
104
|
//#endregion
|
|
104
|
-
//#region src/table/
|
|
105
|
-
|
|
106
|
-
function
|
|
107
|
-
|
|
105
|
+
//#region src/table/view-source.ts
|
|
106
|
+
/** Resolves a compiled type reference (a class, a lazy `() => T`, or `{ type: () => T }`). */
|
|
107
|
+
function resolveTypeRef(value) {
|
|
108
|
+
if ((0, _atscript_typescript_utils.isAnnotatedType)(value)) return value;
|
|
109
|
+
if (typeof value === "function") {
|
|
110
|
+
const resolved = value();
|
|
111
|
+
return (0, _atscript_typescript_utils.isAnnotatedType)(resolved) ? resolved : void 0;
|
|
112
|
+
}
|
|
113
|
+
if (value && typeof value === "object" && typeof value.type === "function") return resolveTypeRef(value.type);
|
|
108
114
|
}
|
|
109
115
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
116
|
+
* The table / view a `@db.alias` type stands for, or `undefined` when `type`
|
|
117
|
+
* is not an alias.
|
|
118
|
+
* @since 0.1.141
|
|
113
119
|
*/
|
|
114
|
-
function
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
120
|
+
function aliasTargetOf(type) {
|
|
121
|
+
const target = type?.metadata?.get("db.alias");
|
|
122
|
+
if (target === void 0) return void 0;
|
|
123
|
+
const resolved = resolveTypeRef(target);
|
|
124
|
+
if (!resolved) throw new Error(`@db.alias of "${type?.id ?? ""}" does not resolve to an annotated type`);
|
|
125
|
+
return resolved;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* How a view plan addresses a source type: a plain table or view is scoped
|
|
129
|
+
* by its physical name; a `@db.alias` type is scoped by its own type name
|
|
130
|
+
* over the aliased table / view (`JOIN "employees" AS "Manager"`).
|
|
131
|
+
* @since 0.1.141
|
|
132
|
+
*/
|
|
133
|
+
function viewSourceOf(type) {
|
|
134
|
+
const target = aliasTargetOf(type);
|
|
135
|
+
if (!target) {
|
|
136
|
+
const table = require_nested_writer.tableNameOf(type);
|
|
137
|
+
return {
|
|
138
|
+
name: table,
|
|
139
|
+
table,
|
|
140
|
+
type
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (aliasTargetOf(target)) throw new Error(`@db.alias "${type.id ?? ""}" targets "${target.id ?? ""}", which is itself a @db.alias — alias the table or view directly`);
|
|
144
|
+
return {
|
|
145
|
+
name: type.id ?? "",
|
|
146
|
+
table: require_nested_writer.tableNameOf(target),
|
|
147
|
+
type: target,
|
|
148
|
+
alias: true
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
const indexCache = /* @__PURE__ */ new WeakMap();
|
|
152
|
+
function sourceIndex(type) {
|
|
153
|
+
let idx = indexCache.get(type);
|
|
154
|
+
if (idx) return idx;
|
|
155
|
+
idx = {
|
|
156
|
+
flatMap: /* @__PURE__ */ new Map(),
|
|
157
|
+
columnMap: /* @__PURE__ */ new Map(),
|
|
158
|
+
documentColumnMap: /* @__PURE__ */ new Map(),
|
|
159
|
+
unstored: /* @__PURE__ */ new Set(),
|
|
160
|
+
jsonRoots: /* @__PURE__ */ new Set(),
|
|
161
|
+
encrypted: /* @__PURE__ */ new Set(),
|
|
162
|
+
optional: /* @__PURE__ */ new Set(),
|
|
163
|
+
derived: /* @__PURE__ */ new Map()
|
|
164
|
+
};
|
|
165
|
+
if (type.type.kind === "object") {
|
|
166
|
+
const navFields = /* @__PURE__ */ new Set();
|
|
167
|
+
const collected = [];
|
|
168
|
+
idx.flatMap = (0, _atscript_typescript_utils.flattenAnnotatedType)(type, {
|
|
169
|
+
excludePhantomTypes: true,
|
|
170
|
+
onField: (path, fieldType, metadata) => {
|
|
171
|
+
if (isNavRelation(metadata)) navFields.add(path);
|
|
172
|
+
collected.push([
|
|
173
|
+
path,
|
|
174
|
+
fieldType,
|
|
175
|
+
metadata
|
|
176
|
+
]);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
const storage = [];
|
|
180
|
+
for (const [path, fieldType, metadata] of collected) {
|
|
181
|
+
if (!path || require_validator.findAncestorInSet(path, navFields) !== void 0) continue;
|
|
182
|
+
const column = metadata.get("db.column");
|
|
183
|
+
if (column) {
|
|
184
|
+
idx.columnMap.set(path, column);
|
|
185
|
+
if (columnOverrideApplies(path, true)) idx.documentColumnMap.set(path, column);
|
|
186
|
+
}
|
|
187
|
+
if (metadata.has("db.ignore") || navFields.has(path)) idx.unstored.add(path);
|
|
188
|
+
if (metadata.has("db.column.derived") && fieldType.ref?.field) idx.derived.set(path, fieldType.ref.field);
|
|
189
|
+
if (metadata.has("db.encrypted")) storage.push([path, false]);
|
|
190
|
+
else if (metadata.has("db.json") || resolveDesignType(fieldType) === "array") storage.push([path, true]);
|
|
191
|
+
}
|
|
192
|
+
storage.sort(([a], [b]) => a.split(".").length - b.split(".").length);
|
|
193
|
+
for (const [path, json] of storage) if (require_validator.findAncestorInSet(path, idx.jsonRoots) === void 0 && require_validator.findAncestorInSet(path, idx.encrypted) === void 0) (json ? idx.jsonRoots : idx.encrypted).add(path);
|
|
194
|
+
for (const [path, node] of idx.flatMap) if (path && node.optional) idx.optional.add(path);
|
|
195
|
+
}
|
|
196
|
+
indexCache.set(type, idx);
|
|
197
|
+
return idx;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Resolves a LOGICAL path of a source table (a view field's chain ref, an
|
|
201
|
+
* aggregate's field, a predicate operand) to where it is physically stored.
|
|
202
|
+
* Internal — `AtscriptDbView.resolveRefSource` is the public entry.
|
|
203
|
+
*
|
|
204
|
+
* Relational rules (`TableMetadata`'s): the outermost `@db.json` or array
|
|
205
|
+
* node with segments remaining is the column and the rest becomes
|
|
206
|
+
* {@link TViewSource.jsonPath}; a flattened leaf is its parent segments
|
|
207
|
+
* joined with `__` plus its `@db.column` (or segment); a top-level field is
|
|
208
|
+
* its `@db.column` or name. Nested-object adapters use the document path
|
|
209
|
+
* (only a top-level key is renamed) and never a JSON path.
|
|
210
|
+
*
|
|
211
|
+
* A `@db.column.derived` path (since 0.1.141) resolves to its generated
|
|
212
|
+
* column on relational adapters and to its JSON-leaf source path on
|
|
213
|
+
* nested-object adapters — always optional (the extraction yields NULL for a
|
|
214
|
+
* missing or off-type leaf).
|
|
215
|
+
*
|
|
216
|
+
* A path the type does not declare resolves to itself — the database
|
|
217
|
+
* reports the unknown column, as before.
|
|
218
|
+
* @throws for a path without storage (`@db.ignore`, a navigation relation,
|
|
219
|
+
* or inside one), and — relational — for a path inside an `@db.encrypted` field.
|
|
220
|
+
*/
|
|
221
|
+
function resolveViewSource(sourceType, logicalPath, nestedObjects) {
|
|
222
|
+
const idx = sourceIndex(sourceType);
|
|
223
|
+
const node = idx.flatMap.get(logicalPath);
|
|
224
|
+
const derivedSource = idx.derived.get(logicalPath);
|
|
225
|
+
if (node && derivedSource !== void 0) {
|
|
226
|
+
if (nestedObjects) return {
|
|
227
|
+
...resolveViewSource(sourceType, derivedSource, true),
|
|
228
|
+
optional: true
|
|
229
|
+
};
|
|
230
|
+
return {
|
|
231
|
+
column: relationalColumnName(logicalPath, idx.columnMap.get(logicalPath), false),
|
|
232
|
+
designType: resolveDesignType(node),
|
|
233
|
+
optional: true
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
const jsonRoot = require_validator.selfOrAncestor(logicalPath, idx.jsonRoots);
|
|
237
|
+
const optional = require_validator.selfOrAncestor(logicalPath, idx.optional) !== void 0 || jsonRoot !== void 0 && jsonRoot !== logicalPath;
|
|
238
|
+
if (!node) return {
|
|
239
|
+
column: logicalPath,
|
|
240
|
+
designType: "unknown",
|
|
241
|
+
optional
|
|
242
|
+
};
|
|
243
|
+
const unstored = require_validator.selfOrAncestor(logicalPath, idx.unstored);
|
|
244
|
+
if (unstored !== void 0) throw new Error(`"${logicalPath}" has no column — "${unstored}" is @db.ignore or a navigation relation`);
|
|
245
|
+
const designType = resolveDesignType(node);
|
|
246
|
+
if (nestedObjects) return {
|
|
247
|
+
column: documentPath(idx.documentColumnMap, logicalPath),
|
|
248
|
+
designType,
|
|
249
|
+
optional
|
|
250
|
+
};
|
|
251
|
+
const encrypted = require_validator.findAncestorInSet(logicalPath, idx.encrypted);
|
|
252
|
+
if (encrypted !== void 0) throw new Error(`"${logicalPath}" is inside the @db.encrypted field "${encrypted}" — reference "${encrypted}" itself`);
|
|
253
|
+
if (jsonRoot !== void 0) {
|
|
254
|
+
const column = relationalColumnName(jsonRoot, idx.columnMap.get(jsonRoot), jsonRoot.includes("."));
|
|
255
|
+
return jsonRoot === logicalPath ? {
|
|
256
|
+
column,
|
|
257
|
+
designType,
|
|
258
|
+
optional
|
|
259
|
+
} : {
|
|
260
|
+
column,
|
|
261
|
+
jsonPath: logicalPath.slice(jsonRoot.length + 1).split("."),
|
|
262
|
+
designType,
|
|
263
|
+
optional
|
|
264
|
+
};
|
|
119
265
|
}
|
|
266
|
+
if (designType === "object" && !idx.encrypted.has(logicalPath)) return {
|
|
267
|
+
column: logicalPath.replace(/\./g, "__"),
|
|
268
|
+
designType,
|
|
269
|
+
flattened: true,
|
|
270
|
+
optional
|
|
271
|
+
};
|
|
272
|
+
return {
|
|
273
|
+
column: relationalColumnName(logicalPath, idx.columnMap.get(logicalPath), logicalPath.includes(".")),
|
|
274
|
+
designType,
|
|
275
|
+
optional
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
//#endregion
|
|
279
|
+
//#region src/table/table-metadata.ts
|
|
280
|
+
const INDEX_PREFIX = "atscript__";
|
|
281
|
+
function indexKey(type, name) {
|
|
282
|
+
return `${INDEX_PREFIX}${type}__${name.replace(/[^a-z0-9_.-]/gi, "_").replace(/_+/g, "_").slice(0, 117 - type.length - 2)}`;
|
|
120
283
|
}
|
|
121
284
|
/**
|
|
122
285
|
* Whether a `@db.column` / `@db.column.renamed` on `path` applies: always on
|
|
@@ -192,6 +355,22 @@ var TableMetadata = class {
|
|
|
192
355
|
nestedObjects;
|
|
193
356
|
flatMap;
|
|
194
357
|
fieldDescriptors;
|
|
358
|
+
/**
|
|
359
|
+
* The descriptors schema sync manages as columns of this table: the
|
|
360
|
+
* non-ignored ones — on nested-object adapters without the
|
|
361
|
+
* `@db.column.derived` fields, which store nothing there (their
|
|
362
|
+
* `physicalName` is the source's document path). The desired side of every
|
|
363
|
+
* column diff and the column list of a fresh create.
|
|
364
|
+
* @since 0.1.141
|
|
365
|
+
*/
|
|
366
|
+
columnDescriptors = [];
|
|
367
|
+
/**
|
|
368
|
+
* The columns that hold a value of their own — non-ignored and not derived
|
|
369
|
+
* (a generated column is computed, never assigned): what a table recreation
|
|
370
|
+
* copies and a full replace assigns.
|
|
371
|
+
* @since 0.1.141
|
|
372
|
+
*/
|
|
373
|
+
storedDescriptors = [];
|
|
195
374
|
primaryKeys = [];
|
|
196
375
|
preferredId = [];
|
|
197
376
|
originalMetaIdFields = [];
|
|
@@ -212,6 +391,13 @@ var TableMetadata = class {
|
|
|
212
391
|
quantityRefByField = /* @__PURE__ */ new Map();
|
|
213
392
|
/** Logical paths annotated with `@db.encrypted` — stored as one opaque ciphertext column. */
|
|
214
393
|
encryptedFields = /* @__PURE__ */ new Set();
|
|
394
|
+
/**
|
|
395
|
+
* `@db.column.derived` fields (top-level logical name → what they read),
|
|
396
|
+
* since 0.1.141. A generated column on relational adapters; on nested-object
|
|
397
|
+
* adapters nothing is stored — {@link physicalPath} maps the name to the
|
|
398
|
+
* source's document path and reads fill the field from it.
|
|
399
|
+
*/
|
|
400
|
+
derivedFields = /* @__PURE__ */ new Map();
|
|
215
401
|
pathToPhysical = /* @__PURE__ */ new Map();
|
|
216
402
|
physicalToPath = /* @__PURE__ */ new Map();
|
|
217
403
|
flattenedParents = /* @__PURE__ */ new Set();
|
|
@@ -268,9 +454,12 @@ var TableMetadata = class {
|
|
|
268
454
|
get isBuilt() {
|
|
269
455
|
return this._built;
|
|
270
456
|
}
|
|
271
|
-
/**
|
|
457
|
+
/**
|
|
458
|
+
* {@link documentPath} over this table's `columnMap`. A `@db.column.derived`
|
|
459
|
+
* field has no stored path of its own: it maps to its source leaf.
|
|
460
|
+
*/
|
|
272
461
|
documentPath(path) {
|
|
273
|
-
return documentPath(this.columnMap, path);
|
|
462
|
+
return documentPath(this.columnMap, this.derivedFields.get(path)?.sourcePath ?? path);
|
|
274
463
|
}
|
|
275
464
|
/**
|
|
276
465
|
* Physical name of a logical path: the document path on nested-object
|
|
@@ -278,7 +467,17 @@ var TableMetadata = class {
|
|
|
278
467
|
* `@db.column` override).
|
|
279
468
|
*/
|
|
280
469
|
physicalPath(logical) {
|
|
281
|
-
|
|
470
|
+
if (this.nestedObjects) return this.documentPath(logical);
|
|
471
|
+
return this.pathToPhysical.get(logical) ?? this.columnMap.get(logical) ?? logical;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Drops the `@db.column.derived` keys of a write payload in place (a
|
|
475
|
+
* derived field is always top-level): the column is computed from the row
|
|
476
|
+
* and never written, so a row read back can be written back as-is.
|
|
477
|
+
* @since 0.1.141
|
|
478
|
+
*/
|
|
479
|
+
stripDerived(data) {
|
|
480
|
+
for (const field of this.derivedFields.keys()) delete data[field];
|
|
282
481
|
}
|
|
283
482
|
/**
|
|
284
483
|
* Runs the full metadata compilation pipeline. Called once by
|
|
@@ -313,11 +512,15 @@ var TableMetadata = class {
|
|
|
313
512
|
}
|
|
314
513
|
});
|
|
315
514
|
for (const entry of collected) {
|
|
316
|
-
if (findAncestorInSet(entry.path, this.navFields) !== void 0)
|
|
515
|
+
if (require_validator.findAncestorInSet(entry.path, this.navFields) !== void 0) {
|
|
516
|
+
this.ignoredFields.add(entry.path);
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
317
519
|
this._scanGenericAnnotations(entry.path, entry.type, entry.metadata, logger);
|
|
520
|
+
if (entry.metadata.has("db.column.derived")) this.derivedFields.set(entry.path, this._validateDerivedField(entry.path, entry.type, entry.metadata, type));
|
|
318
521
|
adapter.onFieldScanned?.(entry.path, entry.type, entry.metadata);
|
|
319
522
|
}
|
|
320
|
-
for (const path of this.encryptedFields) if (findAncestorInSet(path, this.encryptedFields) !== void 0) this.encryptedFields.delete(path);
|
|
523
|
+
for (const path of this.encryptedFields) if (require_validator.findAncestorInSet(path, this.encryptedFields) !== void 0) this.encryptedFields.delete(path);
|
|
321
524
|
if (!this.nestedObjects) this._classifyFields();
|
|
322
525
|
const overrides = adapter.getMetadataOverrides?.(this);
|
|
323
526
|
if (overrides) this._applyOverrides(overrides);
|
|
@@ -337,11 +540,10 @@ var TableMetadata = class {
|
|
|
337
540
|
this.jsonFields.clear();
|
|
338
541
|
this._built = true;
|
|
339
542
|
adapter.onAfterFlatten?.();
|
|
340
|
-
if (this.nestedObjects
|
|
341
|
-
|
|
342
|
-
} else for (const [path, physical] of this.pathToPhysical) {
|
|
543
|
+
if (this.nestedObjects) for (const fd of this.columnDescriptors) this.allPhysicalFields.push(fd.physicalName);
|
|
544
|
+
else for (const [path, physical] of this.pathToPhysical) {
|
|
343
545
|
if (this.navFields.has(path)) continue;
|
|
344
|
-
if (findAncestorInSet(path, this.navFields) !== void 0) continue;
|
|
546
|
+
if (require_validator.findAncestorInSet(path, this.navFields) !== void 0) continue;
|
|
345
547
|
this.allPhysicalFields.push(physical);
|
|
346
548
|
}
|
|
347
549
|
}
|
|
@@ -485,6 +687,45 @@ var TableMetadata = class {
|
|
|
485
687
|
if (metadata.has("db.default.increment") || metadata.has("db.default.now")) reject("@db.default.increment / @db.default.now", "engine-side defaults bypass the encryption transform");
|
|
486
688
|
if (metadata.get("db.patch.strategy") === "merge") reject("@db.patch.strategy \"merge\"", "ciphertext is opaque — partial merges would silently drop omitted keys");
|
|
487
689
|
}
|
|
690
|
+
/**
|
|
691
|
+
* Build-time diagnostics for `@db.column.derived` (rules D1–D8 of the
|
|
692
|
+
* derived-column design) — the runtime mirror of the compile-time check, so
|
|
693
|
+
* pre-compiled models fail fast — and the resolution of what the field
|
|
694
|
+
* reads: the source leaf's JSON column + path (relational layout, via the
|
|
695
|
+
* views' `resolveViewSource`) and its declared type.
|
|
696
|
+
*/
|
|
697
|
+
_validateDerivedField(fieldName, fieldType, metadata, rootType) {
|
|
698
|
+
const reject = (why) => {
|
|
699
|
+
throw new Error(`@db.column.derived on "${fieldName}": ${why}`);
|
|
700
|
+
};
|
|
701
|
+
if (fieldName.includes(".")) reject("only a top-level field of a table can be derived");
|
|
702
|
+
for (const [name, why] of require_derived_rules.DERIVED_INCOMPATIBLE) if (metadata.has(name)) reject(`cannot coexist with @${name} — ${why}`);
|
|
703
|
+
const ref = fieldType.ref;
|
|
704
|
+
if (!ref?.field) reject("requires a chain reference into a @db.json field of the same table (e.g. `customerId: Order.payload.customer.id`)");
|
|
705
|
+
const target = ref.type();
|
|
706
|
+
if (target !== rootType) reject(`must reference the enclosing table "${rootType.id ?? ""}", not "${target?.id ?? ""}" — a derived column reads its own row`);
|
|
707
|
+
const sourcePath = ref.field;
|
|
708
|
+
const segments = sourcePath.split(".");
|
|
709
|
+
let jsonRoot;
|
|
710
|
+
for (let i = 1; i <= segments.length; i++) {
|
|
711
|
+
const prefix = segments.slice(0, i).join(".");
|
|
712
|
+
const node = this.flatMap.get(prefix);
|
|
713
|
+
if (!node) reject(`path "${sourcePath}" does not exist on the table`);
|
|
714
|
+
if (node.metadata.has("db.encrypted")) reject(`path "${sourcePath}" reads inside the @db.encrypted field "${prefix}" — ciphertext cannot be extracted`);
|
|
715
|
+
if (resolveDesignType(node) === "array") reject(`path "${sourcePath}" crosses the array "${prefix}" — a derived column reads one scalar leaf`);
|
|
716
|
+
if (jsonRoot === void 0 && node.metadata.has("db.json")) jsonRoot = prefix;
|
|
717
|
+
}
|
|
718
|
+
if (jsonRoot === void 0 || jsonRoot === sourcePath) reject(`path "${sourcePath}" does not read inside a @db.json field — a flattened or scalar column needs no derived column`);
|
|
719
|
+
const leafType = resolveDesignType(this.flatMap.get(sourcePath));
|
|
720
|
+
if (!require_derived_rules.isJsonLeafType(leafType)) return reject(`path "${sourcePath}" must end at a string, number or boolean leaf (got "${leafType}")`);
|
|
721
|
+
const source = resolveViewSource(rootType, sourcePath, false);
|
|
722
|
+
return {
|
|
723
|
+
sourcePath,
|
|
724
|
+
sourceColumn: source.column,
|
|
725
|
+
jsonPath: source.jsonPath,
|
|
726
|
+
type: leafType
|
|
727
|
+
};
|
|
728
|
+
}
|
|
488
729
|
/** Build-time diagnostics for `@db.index.geo` (§3 of the geo-index spec). */
|
|
489
730
|
_validateGeoIndexField(fieldName, fieldType, metadata) {
|
|
490
731
|
const reject = (why) => {
|
|
@@ -521,7 +762,7 @@ var TableMetadata = class {
|
|
|
521
762
|
_classifyFields() {
|
|
522
763
|
for (const [path, type] of this.flatMap.entries()) {
|
|
523
764
|
if (!path) continue;
|
|
524
|
-
if (this.encryptedFields.has(path) || findAncestorInSet(path, this.encryptedFields)) continue;
|
|
765
|
+
if (this.encryptedFields.has(path) || require_validator.findAncestorInSet(path, this.encryptedFields)) continue;
|
|
525
766
|
const designType = resolveDesignType(type);
|
|
526
767
|
const isJson = this.jsonFields.has(path);
|
|
527
768
|
const isArray = designType === "array";
|
|
@@ -537,9 +778,9 @@ var TableMetadata = class {
|
|
|
537
778
|
for (const [path] of this.flatMap.entries()) {
|
|
538
779
|
if (!path) continue;
|
|
539
780
|
if (this.flattenedParents.has(path)) continue;
|
|
540
|
-
if (findAncestorInSet(path, this.jsonFields) !== void 0) continue;
|
|
541
|
-
if (findAncestorInSet(path, this.encryptedFields) !== void 0) continue;
|
|
542
|
-
const isFlattened = findAncestorInSet(path, this.flattenedParents) !== void 0;
|
|
781
|
+
if (require_validator.findAncestorInSet(path, this.jsonFields) !== void 0) continue;
|
|
782
|
+
if (require_validator.findAncestorInSet(path, this.encryptedFields) !== void 0) continue;
|
|
783
|
+
const isFlattened = require_validator.findAncestorInSet(path, this.flattenedParents) !== void 0;
|
|
543
784
|
const physicalName = relationalColumnName(path, this.columnMap.get(path), isFlattened);
|
|
544
785
|
this.pathToPhysical.set(path, physicalName);
|
|
545
786
|
this.physicalToPath.set(physicalName, path);
|
|
@@ -573,7 +814,7 @@ var TableMetadata = class {
|
|
|
573
814
|
for (const fd of this.fieldDescriptors) {
|
|
574
815
|
physicalNames.add(fd.physicalName);
|
|
575
816
|
if (fd.ignored) continue;
|
|
576
|
-
if (this.navFields.has(fd.path) || findAncestorInSet(fd.path, this.navFields) !== void 0) continue;
|
|
817
|
+
if (this.navFields.has(fd.path) || require_validator.findAncestorInSet(fd.path, this.navFields) !== void 0) continue;
|
|
577
818
|
this.descriptorByPath.set(fd.path, fd);
|
|
578
819
|
if (fd.storage === "json") jsonParents.add(fd.path);
|
|
579
820
|
if (isJsonValueField(fd)) jsonValueParents.add(fd.path);
|
|
@@ -617,12 +858,12 @@ var TableMetadata = class {
|
|
|
617
858
|
for (const [path, type] of this.flatMap.entries()) {
|
|
618
859
|
if (!path) continue;
|
|
619
860
|
if (!skipFlattening && this.flattenedParents.has(path)) continue;
|
|
620
|
-
if (!skipFlattening && findAncestorInSet(path, this.jsonFields) !== void 0) continue;
|
|
861
|
+
if (!skipFlattening && require_validator.findAncestorInSet(path, this.jsonFields) !== void 0) continue;
|
|
621
862
|
const isEncrypted = this.encryptedFields.has(path);
|
|
622
|
-
const underEncrypted = findAncestorInSet(path, this.encryptedFields) !== void 0;
|
|
863
|
+
const underEncrypted = require_validator.findAncestorInSet(path, this.encryptedFields) !== void 0;
|
|
623
864
|
if (!skipFlattening && underEncrypted) continue;
|
|
624
865
|
const isJson = this.jsonFields.has(path);
|
|
625
|
-
const isFlattened = !skipFlattening && findAncestorInSet(path, this.flattenedParents) !== void 0;
|
|
866
|
+
const isFlattened = !skipFlattening && require_validator.findAncestorInSet(path, this.flattenedParents) !== void 0;
|
|
626
867
|
const designType = isEncrypted ? "string" : isJson ? "json" : resolveDesignType(type);
|
|
627
868
|
let storage;
|
|
628
869
|
if (skipFlattening) storage = "column";
|
|
@@ -659,12 +900,17 @@ var TableMetadata = class {
|
|
|
659
900
|
unitCode,
|
|
660
901
|
unitRefField,
|
|
661
902
|
encrypted: isEncrypted || underEncrypted || void 0,
|
|
662
|
-
isGeoPoint: isGeoPointType(type) || void 0
|
|
903
|
+
isGeoPoint: isGeoPointType(type) || void 0,
|
|
904
|
+
derived: this.derivedFields.get(path)
|
|
663
905
|
});
|
|
664
906
|
}
|
|
665
907
|
this._resolveFkTargetFields(descriptors);
|
|
908
|
+
Object.freeze(descriptors);
|
|
909
|
+
this.fieldDescriptors = descriptors;
|
|
910
|
+
this.columnDescriptors = Object.freeze(descriptors.filter((fd) => !fd.ignored && !(skipFlattening && fd.derived)));
|
|
911
|
+
this.storedDescriptors = Object.freeze(descriptors.filter((fd) => !fd.ignored && !fd.derived));
|
|
666
912
|
const fmtHook = adapter.formatValue?.bind(adapter);
|
|
667
|
-
if (fmtHook) for (const fd of
|
|
913
|
+
if (fmtHook) for (const fd of this.columnDescriptors) {
|
|
668
914
|
const fmt = fmtHook(fd);
|
|
669
915
|
if (fmt) if (typeof fmt === "function") {
|
|
670
916
|
if (!this.toStorageFormatters) this.toStorageFormatters = /* @__PURE__ */ new Map();
|
|
@@ -680,8 +926,6 @@ var TableMetadata = class {
|
|
|
680
926
|
}
|
|
681
927
|
}
|
|
682
928
|
}
|
|
683
|
-
Object.freeze(descriptors);
|
|
684
|
-
this.fieldDescriptors = descriptors;
|
|
685
929
|
}
|
|
686
930
|
/**
|
|
687
931
|
* Resolves `fkTargetField` for FK fields in field descriptors.
|
|
@@ -915,6 +1159,79 @@ var UniquSelect = class UniquSelect {
|
|
|
915
1159
|
};
|
|
916
1160
|
//#endregion
|
|
917
1161
|
//#region src/strategies/field-mapping.ts
|
|
1162
|
+
const NO_PLAN = {
|
|
1163
|
+
fill: [],
|
|
1164
|
+
prune: [],
|
|
1165
|
+
unexclude: /* @__PURE__ */ new Set()
|
|
1166
|
+
};
|
|
1167
|
+
/** The no-projection plan of a table (every derived field, nothing pruned) — built once per table. */
|
|
1168
|
+
const fullPlans = /* @__PURE__ */ new WeakMap();
|
|
1169
|
+
/** An object-form `$select` (inclusion or exclusion) — not an array. */
|
|
1170
|
+
function isObjectForm(select) {
|
|
1171
|
+
return select !== null && typeof select === "object" && !Array.isArray(select);
|
|
1172
|
+
}
|
|
1173
|
+
/** An exclusion projection: object form whose first flag is not `1` / `true`. */
|
|
1174
|
+
function isExclusionProjection(select) {
|
|
1175
|
+
if (!isObjectForm(select)) return false;
|
|
1176
|
+
for (const flag of Object.values(select)) return flag !== 1 && flag !== true;
|
|
1177
|
+
return false;
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* The logical keys a read names, in one pass: the `$groupBy` dimensions
|
|
1181
|
+
* (array or single string) and the `$select` inclusions (array-form strings,
|
|
1182
|
+
* object-form keys flagged `1` / `true`). Empty without a projection.
|
|
1183
|
+
*/
|
|
1184
|
+
function requestedKeys(controls) {
|
|
1185
|
+
const out = [];
|
|
1186
|
+
const groupBy = controls?.$groupBy;
|
|
1187
|
+
if (typeof groupBy === "string") out.push(groupBy);
|
|
1188
|
+
else if (Array.isArray(groupBy)) {
|
|
1189
|
+
for (const key of groupBy) if (typeof key === "string") out.push(key);
|
|
1190
|
+
}
|
|
1191
|
+
const select = controls?.$select;
|
|
1192
|
+
if (Array.isArray(select)) {
|
|
1193
|
+
for (const item of select) if (typeof item === "string") out.push(item);
|
|
1194
|
+
} else if (isObjectForm(select)) {
|
|
1195
|
+
for (const [key, flag] of Object.entries(select)) if (flag === 1 || flag === true) out.push(key);
|
|
1196
|
+
}
|
|
1197
|
+
return out;
|
|
1198
|
+
}
|
|
1199
|
+
/** {@link TPrunePath} of `path`, keeping the ancestors something in `requested` lives under. */
|
|
1200
|
+
function prunePath(path, requested) {
|
|
1201
|
+
const segments = path.split(".");
|
|
1202
|
+
for (let depth = segments.length - 1; depth >= 1; depth--) {
|
|
1203
|
+
const prefix = `${segments.slice(0, depth).join(".")}.`;
|
|
1204
|
+
for (const r of requested) if (r.startsWith(prefix)) return {
|
|
1205
|
+
segments,
|
|
1206
|
+
keep: depth
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
return {
|
|
1210
|
+
segments,
|
|
1211
|
+
keep: 0
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
/** Deletes a pruned path from a row, then every ancestor it left empty (down to `keep`). */
|
|
1215
|
+
function pruneNestedPath(row, { segments, keep }) {
|
|
1216
|
+
require_validator.deletePath(row, segments);
|
|
1217
|
+
for (let depth = segments.length - 1; depth > keep; depth--) {
|
|
1218
|
+
const ancestor = segments.slice(0, depth);
|
|
1219
|
+
const value = require_validator.getPath(row, ancestor);
|
|
1220
|
+
if (!isObjectForm(value) || Object.keys(value).length > 0) return;
|
|
1221
|
+
require_validator.deletePath(row, ancestor);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
/**
|
|
1225
|
+
* Fills the `@db.column.derived` fields a read asked for from their source
|
|
1226
|
+
* leaf (as stored — no type guard; `null` for a missing leaf) and removes
|
|
1227
|
+
* the source paths the caller did not select, so an inclusion `$select` of
|
|
1228
|
+
* a derived field never leaks its source and an exclusion of the source
|
|
1229
|
+
* still yields the derived value.
|
|
1230
|
+
*/
|
|
1231
|
+
function fillDerived(row, plan) {
|
|
1232
|
+
for (const [name, source] of plan.fill) row[name] = require_validator.getPath(row, source) ?? null;
|
|
1233
|
+
for (const path of plan.prune) pruneNestedPath(row, path);
|
|
1234
|
+
}
|
|
918
1235
|
/** Coerces a storage value (0/1/null) back to a JS boolean. */
|
|
919
1236
|
function toBool(value) {
|
|
920
1237
|
if (value === null || value === void 0) return value;
|
|
@@ -932,6 +1249,15 @@ function toDecimalString(value) {
|
|
|
932
1249
|
* and `RelationalFieldMapper` (flattened columns, SQL).
|
|
933
1250
|
*/
|
|
934
1251
|
var FieldMappingStrategy = class {
|
|
1252
|
+
/**
|
|
1253
|
+
* {@link reconstructFromRead} over every row of one read — the per-read
|
|
1254
|
+
* work (the derived read plan on document adapters) is done once for all
|
|
1255
|
+
* of them. The default maps the rows one by one.
|
|
1256
|
+
* @since 0.1.141
|
|
1257
|
+
*/
|
|
1258
|
+
reconstructRows(rows, meta, controls) {
|
|
1259
|
+
return rows.map((row) => this.reconstructFromRead(row, meta, controls));
|
|
1260
|
+
}
|
|
935
1261
|
/**
|
|
936
1262
|
* Whether {@link physicalPath} can differ from the logical path for this
|
|
937
1263
|
* table; `false` lets the path translations hand their input back as-is.
|
|
@@ -1027,7 +1353,7 @@ var FieldMappingStrategy = class {
|
|
|
1027
1353
|
*/
|
|
1028
1354
|
translateFilter(filter, meta) {
|
|
1029
1355
|
if (!filter || typeof filter !== "object") return filter;
|
|
1030
|
-
if (!meta.toStorageFormatters && meta.columnMap.size === 0) return filter;
|
|
1356
|
+
if (!meta.toStorageFormatters && meta.columnMap.size === 0 && meta.derivedFields.size === 0) return filter;
|
|
1031
1357
|
const result = {};
|
|
1032
1358
|
for (const [key, value] of Object.entries(filter)) if (key === "$and" || key === "$or") result[key] = value.map((f) => this.translateFilter(f, meta));
|
|
1033
1359
|
else if (key === "$not") result[key] = this.translateFilter(value, meta);
|
|
@@ -1161,6 +1487,7 @@ var FieldMappingStrategy = class {
|
|
|
1161
1487
|
if (fieldType) data[pk] = adapter.prepareId(data[pk], fieldType);
|
|
1162
1488
|
}
|
|
1163
1489
|
for (const field of meta.ignoredFields) if (!field.includes(".")) delete data[field];
|
|
1490
|
+
meta.stripDerived(data);
|
|
1164
1491
|
}
|
|
1165
1492
|
};
|
|
1166
1493
|
/**
|
|
@@ -1169,12 +1496,86 @@ var FieldMappingStrategy = class {
|
|
|
1169
1496
|
* value coercion.
|
|
1170
1497
|
*/
|
|
1171
1498
|
var DocumentFieldMapper = class extends FieldMappingStrategy {
|
|
1172
|
-
reconstructFromRead(row, meta) {
|
|
1499
|
+
reconstructFromRead(row, meta, controls) {
|
|
1500
|
+
return this._reconstruct(row, meta, this._derivedPlanFor(meta, controls));
|
|
1501
|
+
}
|
|
1502
|
+
reconstructRows(rows, meta, controls) {
|
|
1503
|
+
const plan = this._derivedPlanFor(meta, controls);
|
|
1504
|
+
return rows.map((row) => this._reconstruct(row, meta, plan));
|
|
1505
|
+
}
|
|
1506
|
+
_derivedPlanFor(meta, controls) {
|
|
1507
|
+
return meta.derivedFields.size > 0 ? this.derivedReadPlan(controls, meta) : void 0;
|
|
1508
|
+
}
|
|
1509
|
+
_reconstruct(row, meta, plan) {
|
|
1173
1510
|
this.coerceFieldValues(row, meta);
|
|
1174
1511
|
this.applyFromStorageFormatters(row, meta);
|
|
1175
1512
|
if (meta.columnMap.size > 0) this.reverseColumnRenames(row, meta);
|
|
1513
|
+
if (plan) fillDerived(row, plan);
|
|
1176
1514
|
return row;
|
|
1177
1515
|
}
|
|
1516
|
+
/** See {@link TDerivedReadPlan}. */
|
|
1517
|
+
derivedReadPlan(controls, meta) {
|
|
1518
|
+
if (meta.derivedFields.size === 0) return NO_PLAN;
|
|
1519
|
+
const select = controls?.$select;
|
|
1520
|
+
const groupBy = controls?.$groupBy;
|
|
1521
|
+
if (!(Array.isArray(groupBy) ? groupBy.length > 0 : typeof groupBy === "string") && (select === void 0 || select === null || isObjectForm(select) && Object.keys(select).length === 0)) {
|
|
1522
|
+
let plan = fullPlans.get(meta);
|
|
1523
|
+
if (!plan) {
|
|
1524
|
+
plan = {
|
|
1525
|
+
fill: [...meta.derivedFields].map(([name, d]) => [name, d.sourcePath.split(".")]),
|
|
1526
|
+
prune: [],
|
|
1527
|
+
unexclude: /* @__PURE__ */ new Set()
|
|
1528
|
+
};
|
|
1529
|
+
fullPlans.set(meta, plan);
|
|
1530
|
+
}
|
|
1531
|
+
return plan;
|
|
1532
|
+
}
|
|
1533
|
+
const keys = requestedKeys(controls);
|
|
1534
|
+
const requested = new Set(keys.filter((key) => !meta.derivedFields.has(key)));
|
|
1535
|
+
const fill = [];
|
|
1536
|
+
const prune = /* @__PURE__ */ new Set();
|
|
1537
|
+
if (isExclusionProjection(select)) {
|
|
1538
|
+
const excluded = new Set(Object.entries(select).filter(([, flag]) => flag === 0 || flag === false).map(([key]) => key));
|
|
1539
|
+
const unexclude = /* @__PURE__ */ new Set();
|
|
1540
|
+
for (const [name, derived] of meta.derivedFields) {
|
|
1541
|
+
if (excluded.has(name)) continue;
|
|
1542
|
+
fill.push([name, derived.sourcePath.split(".")]);
|
|
1543
|
+
for (let key = require_validator.selfOrAncestor(derived.sourcePath, excluded); key !== void 0; key = require_validator.findAncestorInSet(key, excluded)) {
|
|
1544
|
+
unexclude.add(key);
|
|
1545
|
+
prune.add(key);
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
return {
|
|
1549
|
+
fill,
|
|
1550
|
+
prune: [...prune].map((p) => prunePath(p, requested)),
|
|
1551
|
+
unexclude
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
1554
|
+
const names = new Set(keys);
|
|
1555
|
+
for (const [name, derived] of meta.derivedFields) {
|
|
1556
|
+
if (!names.has(name)) continue;
|
|
1557
|
+
fill.push([name, derived.sourcePath.split(".")]);
|
|
1558
|
+
if (require_validator.selfOrAncestor(derived.sourcePath, requested) === void 0) prune.add(derived.sourcePath);
|
|
1559
|
+
}
|
|
1560
|
+
return {
|
|
1561
|
+
fill,
|
|
1562
|
+
prune: [...prune].map((p) => prunePath(p, requested)),
|
|
1563
|
+
unexclude: /* @__PURE__ */ new Set()
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
/**
|
|
1567
|
+
* {@link FieldMappingStrategy.physicalSelect} plus the derived-field rules
|
|
1568
|
+
* of an exclusion projection: a derived key is not a stored path (dropping
|
|
1569
|
+
* it just leaves the field unfilled), and the source subtree of a wanted
|
|
1570
|
+
* derived field is fetched even when excluded (pruned after the read).
|
|
1571
|
+
*/
|
|
1572
|
+
physicalSelect(select, meta) {
|
|
1573
|
+
if (meta.derivedFields.size === 0 || !isExclusionProjection(select)) return super.physicalSelect(select, meta);
|
|
1574
|
+
const { unexclude } = this.derivedReadPlan({ $select: select }, meta);
|
|
1575
|
+
const stored = {};
|
|
1576
|
+
for (const [key, flag] of Object.entries(select)) if (!meta.derivedFields.has(key) && !unexclude.has(key)) stored[key] = flag;
|
|
1577
|
+
return super.physicalSelect(stored, meta);
|
|
1578
|
+
}
|
|
1178
1579
|
/**
|
|
1179
1580
|
* Every field-path position goes through `@db.column` renames
|
|
1180
1581
|
* ({@link TableMetadata.documentPath}): filter keys, `$select` fields
|
|
@@ -1199,7 +1600,7 @@ var DocumentFieldMapper = class extends FieldMappingStrategy {
|
|
|
1199
1600
|
return meta.documentPath(logical);
|
|
1200
1601
|
}
|
|
1201
1602
|
renamesPaths(meta) {
|
|
1202
|
-
return meta.columnMap.size > 0;
|
|
1603
|
+
return meta.columnMap.size > 0 || meta.derivedFields.size > 0;
|
|
1203
1604
|
}
|
|
1204
1605
|
prepareForWrite(payload, meta, adapter) {
|
|
1205
1606
|
const data = { ...payload };
|
|
@@ -1229,7 +1630,7 @@ var DocumentFieldMapper = class extends FieldMappingStrategy {
|
|
|
1229
1630
|
* for queries, filters, and controls.
|
|
1230
1631
|
*/
|
|
1231
1632
|
var RelationalFieldMapper = class extends FieldMappingStrategy {
|
|
1232
|
-
reconstructFromRead(row, meta) {
|
|
1633
|
+
reconstructFromRead(row, meta, _controls) {
|
|
1233
1634
|
if (!meta.requiresMappings) return this.applyFromStorageFormatters(this.coerceFieldValues(row, meta), meta);
|
|
1234
1635
|
if (meta.onlyColumnRenames) {
|
|
1235
1636
|
this.coerceFieldValues(row, meta);
|
|
@@ -1462,7 +1863,7 @@ function assertGeoPoint(point, path) {
|
|
|
1462
1863
|
}]);
|
|
1463
1864
|
}
|
|
1464
1865
|
function isEncryptedRef(meta, field) {
|
|
1465
|
-
return meta.encryptedFields.has(field) || findAncestorInSet(field, meta.encryptedFields) !== void 0;
|
|
1866
|
+
return meta.encryptedFields.has(field) || require_validator.findAncestorInSet(field, meta.encryptedFields) !== void 0;
|
|
1466
1867
|
}
|
|
1467
1868
|
function encryptedRefError(code, field, what) {
|
|
1468
1869
|
return new require_db_error.DbError(code, [{
|
|
@@ -1764,19 +2165,19 @@ function collectQueryPaths(query, aggregate) {
|
|
|
1764
2165
|
*/
|
|
1765
2166
|
function classifyQueryPath(source, path) {
|
|
1766
2167
|
if (source.navFields.has(path)) return { kind: "nav" };
|
|
1767
|
-
const navHead = findAncestorInSet(path, source.navFields);
|
|
2168
|
+
const navHead = require_validator.findAncestorInSet(path, source.navFields);
|
|
1768
2169
|
if (navHead !== void 0) return {
|
|
1769
2170
|
kind: "nav",
|
|
1770
2171
|
parent: navHead
|
|
1771
2172
|
};
|
|
1772
2173
|
if (source.leaves.has(path)) return { kind: "leaf" };
|
|
1773
2174
|
if (source.objectParents.has(path)) return { kind: "objectParent" };
|
|
1774
|
-
const jsonParent = findAncestorInSet(path, source.jsonParents);
|
|
2175
|
+
const jsonParent = require_validator.findAncestorInSet(path, source.jsonParents);
|
|
1775
2176
|
if (jsonParent !== void 0) return {
|
|
1776
2177
|
kind: "jsonDescendant",
|
|
1777
2178
|
parent: jsonParent
|
|
1778
2179
|
};
|
|
1779
|
-
const encryptedParent = findAncestorInSet(path, source.encryptedFields);
|
|
2180
|
+
const encryptedParent = require_validator.findAncestorInSet(path, source.encryptedFields);
|
|
1780
2181
|
if (encryptedParent !== void 0) return {
|
|
1781
2182
|
kind: "encryptedDescendant",
|
|
1782
2183
|
parent: encryptedParent
|
|
@@ -2065,11 +2466,7 @@ var AtscriptDbReadable = class {
|
|
|
2065
2466
|
this._tableResolver = _tableResolver;
|
|
2066
2467
|
if (!(0, _atscript_typescript_utils.isAnnotatedType)(_type)) throw new Error("Atscript Annotated Type expected");
|
|
2067
2468
|
if (_type.type.kind !== "object") throw new Error("Database type must be an object type");
|
|
2068
|
-
|
|
2069
|
-
const dbTable = _type.metadata.get("db.table");
|
|
2070
|
-
const dbViewName = _type.metadata.get("db.view");
|
|
2071
|
-
const fallbackName = _type.id || "";
|
|
2072
|
-
this.tableName = adapterName || dbTable || dbViewName || fallbackName;
|
|
2469
|
+
this.tableName = adapter.getAdapterTableName?.(_type) || require_nested_writer.tableNameOf(_type);
|
|
2073
2470
|
if (!this.tableName) throw new Error("@db.table or @db.view annotation expected");
|
|
2074
2471
|
this.schema = _type.metadata.get("db.schema");
|
|
2075
2472
|
this._syncMethod = _type.metadata.get("db.sync.method");
|
|
@@ -2239,11 +2636,25 @@ var AtscriptDbReadable = class {
|
|
|
2239
2636
|
return this._metaIdPhysical;
|
|
2240
2637
|
}
|
|
2241
2638
|
/**
|
|
2242
|
-
*
|
|
2243
|
-
* `undefined` when the table has no version column.
|
|
2244
|
-
*
|
|
2639
|
+
* Logical field name of the field annotated with `@db.column.version`, or
|
|
2640
|
+
* `undefined` when the table has no version column. This is the key used in
|
|
2641
|
+
* `$cas: { <versionColumn>: N }`, in write payloads, in rows read back, and in
|
|
2642
|
+
* `/meta`'s `versionColumn` — a `@db.column 'physical_name'` rename on the
|
|
2643
|
+
* field changes only the storage column (see {@link versionColumnPhysical}).
|
|
2245
2644
|
*/
|
|
2246
2645
|
get versionColumn() {
|
|
2646
|
+
this._ensureBuilt();
|
|
2647
|
+
return this._meta.versionField;
|
|
2648
|
+
}
|
|
2649
|
+
/**
|
|
2650
|
+
* Physical column name of the `@db.column.version` field (after any
|
|
2651
|
+
* `@db.column` rename), or `undefined` when the table has no version column.
|
|
2652
|
+
* Adapters use it for the auto-bump, the CAS predicate, and the insert-time
|
|
2653
|
+
* `0` backfill — all of which operate on already-mapped physical rows.
|
|
2654
|
+
*
|
|
2655
|
+
* @internal Adapter-facing surface; not part of the consumer API.
|
|
2656
|
+
*/
|
|
2657
|
+
get versionColumnPhysical() {
|
|
2247
2658
|
this._ensureBuilt();
|
|
2248
2659
|
const field = this._meta.versionField;
|
|
2249
2660
|
if (field === void 0) return void 0;
|
|
@@ -2278,6 +2689,14 @@ var AtscriptDbReadable = class {
|
|
|
2278
2689
|
this._ensureBuilt();
|
|
2279
2690
|
return this._meta.ignoredFields;
|
|
2280
2691
|
}
|
|
2692
|
+
/**
|
|
2693
|
+
* `@db.column.derived` fields (logical name → what they read) — computed
|
|
2694
|
+
* from a JSON leaf of the same row, never written. Since 0.1.141.
|
|
2695
|
+
*/
|
|
2696
|
+
get derivedFields() {
|
|
2697
|
+
this._ensureBuilt();
|
|
2698
|
+
return this._meta.derivedFields;
|
|
2699
|
+
}
|
|
2281
2700
|
/** Navigational fields (`@db.rel.to` / `@db.rel.from`) — not stored as columns. */
|
|
2282
2701
|
get navFields() {
|
|
2283
2702
|
this._ensureBuilt();
|
|
@@ -2333,6 +2752,14 @@ var AtscriptDbReadable = class {
|
|
|
2333
2752
|
};
|
|
2334
2753
|
}
|
|
2335
2754
|
/**
|
|
2755
|
+
* Physical rows of one read → logical rows: the field mapper's per-read
|
|
2756
|
+
* work (the derived read plan on document adapters) is done once for all
|
|
2757
|
+
* of them. Every read path funnels through here.
|
|
2758
|
+
*/
|
|
2759
|
+
_fromRead(rows, controls) {
|
|
2760
|
+
return this._fieldMapper.reconstructRows(rows, this._meta, controls);
|
|
2761
|
+
}
|
|
2762
|
+
/**
|
|
2336
2763
|
* Pre-computed field metadata for adapter use.
|
|
2337
2764
|
*/
|
|
2338
2765
|
get fieldDescriptors() {
|
|
@@ -2340,6 +2767,26 @@ var AtscriptDbReadable = class {
|
|
|
2340
2767
|
return this._meta.fieldDescriptors;
|
|
2341
2768
|
}
|
|
2342
2769
|
/**
|
|
2770
|
+
* The descriptors schema sync manages as columns: non-ignored, and on
|
|
2771
|
+
* nested-object adapters without the `@db.column.derived` fields (they store
|
|
2772
|
+
* nothing there). See `TableMetadata.columnDescriptors`.
|
|
2773
|
+
* @since 0.1.141
|
|
2774
|
+
*/
|
|
2775
|
+
get columnDescriptors() {
|
|
2776
|
+
this._ensureBuilt();
|
|
2777
|
+
return this._meta.columnDescriptors;
|
|
2778
|
+
}
|
|
2779
|
+
/**
|
|
2780
|
+
* The columns that hold a value of their own (non-ignored, not derived) —
|
|
2781
|
+
* what a table recreation copies and a full replace assigns. See
|
|
2782
|
+
* `TableMetadata.storedDescriptors`.
|
|
2783
|
+
* @since 0.1.141
|
|
2784
|
+
*/
|
|
2785
|
+
get storedDescriptors() {
|
|
2786
|
+
this._ensureBuilt();
|
|
2787
|
+
return this._meta.storedDescriptors;
|
|
2788
|
+
}
|
|
2789
|
+
/**
|
|
2343
2790
|
* The target table of the navigation relation `navField` (since 0.1.134),
|
|
2344
2791
|
* resolved through the table resolver this readable was built with (the
|
|
2345
2792
|
* `DbSpace`'s). `undefined` when `navField` is not a relation of this table
|
|
@@ -2399,7 +2846,7 @@ var AtscriptDbReadable = class {
|
|
|
2399
2846
|
const translatedQuery = this._fieldMapper.translateQuery(query, this._meta);
|
|
2400
2847
|
const result = await this.adapter.findOne(translatedQuery);
|
|
2401
2848
|
if (!result) return null;
|
|
2402
|
-
const row = this.
|
|
2849
|
+
const [row] = this._fromRead([result], query.controls);
|
|
2403
2850
|
await this._decryptRows([row]);
|
|
2404
2851
|
if (withRelations?.length) await this.loadRelations([row], withRelations);
|
|
2405
2852
|
return row;
|
|
@@ -2414,7 +2861,8 @@ var AtscriptDbReadable = class {
|
|
|
2414
2861
|
this._guardQuery(query);
|
|
2415
2862
|
const withRelations = query.controls?.$with;
|
|
2416
2863
|
const translatedQuery = this._fieldMapper.translateQuery(query, this._meta);
|
|
2417
|
-
const
|
|
2864
|
+
const results = await this.adapter.findMany(translatedQuery);
|
|
2865
|
+
const rows = this._fromRead(results, query.controls);
|
|
2418
2866
|
await this._decryptRows(rows);
|
|
2419
2867
|
if (withRelations?.length) await this.loadRelations(rows, withRelations);
|
|
2420
2868
|
return rows;
|
|
@@ -2440,7 +2888,7 @@ var AtscriptDbReadable = class {
|
|
|
2440
2888
|
const withRelations = query.controls?.$with;
|
|
2441
2889
|
const translated = this._fieldMapper.translateQuery(query, this._meta);
|
|
2442
2890
|
const result = await this.adapter.findManyWithCount(translated);
|
|
2443
|
-
const rows = result.data
|
|
2891
|
+
const rows = this._fromRead(result.data, query.controls);
|
|
2444
2892
|
await this._decryptRows(rows);
|
|
2445
2893
|
if (withRelations?.length) await this.loadRelations(rows, withRelations);
|
|
2446
2894
|
return {
|
|
@@ -2521,7 +2969,8 @@ var AtscriptDbReadable = class {
|
|
|
2521
2969
|
}
|
|
2522
2970
|
guardAggregate(this._meta, this.adapter, query, buckets);
|
|
2523
2971
|
const dbQuery = this._fieldMapper.translateAggregateQuery(query, this._meta, buckets);
|
|
2524
|
-
|
|
2972
|
+
const results = await this.adapter.aggregate(dbQuery);
|
|
2973
|
+
return this._fromRead(results, query.controls);
|
|
2525
2974
|
}
|
|
2526
2975
|
/** Whether the underlying adapter supports text search. */
|
|
2527
2976
|
isSearchable() {
|
|
@@ -2556,7 +3005,8 @@ var AtscriptDbReadable = class {
|
|
|
2556
3005
|
this._guardQuery(query);
|
|
2557
3006
|
const withRelations = query.controls?.$with;
|
|
2558
3007
|
const translated = this._fieldMapper.translateQuery(query, this._meta);
|
|
2559
|
-
const
|
|
3008
|
+
const results = await this.adapter.search(text, translated, indexName);
|
|
3009
|
+
const rows = this._fromRead(results, query.controls);
|
|
2560
3010
|
await this._decryptRows(rows);
|
|
2561
3011
|
if (withRelations?.length) await this.loadRelations(rows, withRelations);
|
|
2562
3012
|
return rows;
|
|
@@ -2571,7 +3021,7 @@ var AtscriptDbReadable = class {
|
|
|
2571
3021
|
const withRelations = query.controls?.$with;
|
|
2572
3022
|
const translated = this._fieldMapper.translateQuery(query, this._meta);
|
|
2573
3023
|
const result = await this.adapter.searchWithCount(text, translated, indexName);
|
|
2574
|
-
const rows = result.data
|
|
3024
|
+
const rows = this._fromRead(result.data, query.controls);
|
|
2575
3025
|
await this._decryptRows(rows);
|
|
2576
3026
|
if (withRelations?.length) await this.loadRelations(rows, withRelations);
|
|
2577
3027
|
return {
|
|
@@ -2596,7 +3046,8 @@ var AtscriptDbReadable = class {
|
|
|
2596
3046
|
this._guardQuery(query);
|
|
2597
3047
|
const withRelations = (query?.controls)?.$with;
|
|
2598
3048
|
const translated = this._fieldMapper.translateQuery(query || {}, this._meta);
|
|
2599
|
-
const
|
|
3049
|
+
const results = await this.adapter.vectorSearch(vector, translated, indexName);
|
|
3050
|
+
const rows = this._fromRead(results, query?.controls);
|
|
2600
3051
|
await this._decryptRows(rows);
|
|
2601
3052
|
if (withRelations?.length) await this.loadRelations(rows, withRelations);
|
|
2602
3053
|
return rows;
|
|
@@ -2615,7 +3066,7 @@ var AtscriptDbReadable = class {
|
|
|
2615
3066
|
const withRelations = (query?.controls)?.$with;
|
|
2616
3067
|
const translated = this._fieldMapper.translateQuery(query || {}, this._meta);
|
|
2617
3068
|
const result = await this.adapter.vectorSearchWithCount(vector, translated, indexName);
|
|
2618
|
-
const rows = result.data
|
|
3069
|
+
const rows = this._fromRead(result.data, query?.controls);
|
|
2619
3070
|
await this._decryptRows(rows);
|
|
2620
3071
|
if (withRelations?.length) await this.loadRelations(rows, withRelations);
|
|
2621
3072
|
return {
|
|
@@ -2653,7 +3104,8 @@ var AtscriptDbReadable = class {
|
|
|
2653
3104
|
async geoSearch(pointOrIndex, maybePointOrQuery, maybeQuery) {
|
|
2654
3105
|
const { point, query, indexName } = this._resolveGeoSearchArgs(pointOrIndex, maybePointOrQuery, maybeQuery);
|
|
2655
3106
|
const { translated, withRelations } = this._prepareGeoSearch(point, query, indexName);
|
|
2656
|
-
const
|
|
3107
|
+
const results = await this.adapter.geoSearch(point, translated, indexName);
|
|
3108
|
+
const rows = this._fromRead(results, query?.controls);
|
|
2657
3109
|
await this._decryptRows(rows);
|
|
2658
3110
|
if (withRelations?.length) await this.loadRelations(rows, withRelations);
|
|
2659
3111
|
return rows;
|
|
@@ -2669,7 +3121,7 @@ var AtscriptDbReadable = class {
|
|
|
2669
3121
|
const { point, query, indexName } = this._resolveGeoSearchArgs(pointOrIndex, maybePointOrQuery, maybeQuery);
|
|
2670
3122
|
const { translated, withRelations } = this._prepareGeoSearch(point, query, indexName);
|
|
2671
3123
|
const result = await this.adapter.geoSearchWithCount(point, translated, indexName);
|
|
2672
|
-
const rows = result.data
|
|
3124
|
+
const rows = this._fromRead(result.data, query?.controls);
|
|
2673
3125
|
await this._decryptRows(rows);
|
|
2674
3126
|
if (withRelations?.length) await this.loadRelations(rows, withRelations);
|
|
2675
3127
|
return {
|
|
@@ -2829,7 +3281,7 @@ var AtscriptDbReadable = class {
|
|
|
2829
3281
|
* Public entry point for relation loading. Used by adapters for nested $with delegation.
|
|
2830
3282
|
*/
|
|
2831
3283
|
async loadRelations(rows, withRelations) {
|
|
2832
|
-
const { loadRelationsImpl } = await Promise.resolve().then(() => require("./relation-loader-
|
|
3284
|
+
const { loadRelationsImpl } = await Promise.resolve().then(() => require("./relation-loader-CgJ8bK6X.cjs")).then((n) => n.relation_loader_exports);
|
|
2833
3285
|
return loadRelationsImpl(rows, withRelations, this);
|
|
2834
3286
|
}
|
|
2835
3287
|
/**
|
|
@@ -4306,6 +4758,7 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
4306
4758
|
for (const navField of this._meta.navFields) delete data[navField];
|
|
4307
4759
|
const filter = this._extractRecordFilter(data, opts);
|
|
4308
4760
|
for (const key of Object.keys(filter)) delete data[key];
|
|
4761
|
+
this._meta.stripDerived(data);
|
|
4309
4762
|
if (versionColumn !== void 0) assertNoVersionWrites(data, versionColumn);
|
|
4310
4763
|
const translatedFilter = this._fieldMapper.translateFilter(filter, this._meta);
|
|
4311
4764
|
if (require_validator.isEmptyObject(data) && expectedVersion === void 0) {
|
|
@@ -4455,6 +4908,7 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
4455
4908
|
this._guardMutationFilter(filter);
|
|
4456
4909
|
await this._integrity.validateForeignKeys([data], this._meta, this._fkLookupResolver, this._writeTableResolver, true);
|
|
4457
4910
|
const dataCopy = _cloneWritePayload(data);
|
|
4911
|
+
this._meta.stripDerived(dataCopy);
|
|
4458
4912
|
const versionColumn = this.versionColumn;
|
|
4459
4913
|
if ("$cas" in dataCopy) throw new require_db_error.DbError("INVALID_QUERY", [{
|
|
4460
4914
|
path: "$cas",
|
|
@@ -4738,120 +5192,6 @@ var AtscriptDbTable = class extends AtscriptDbReadable {
|
|
|
4738
5192
|
}
|
|
4739
5193
|
};
|
|
4740
5194
|
//#endregion
|
|
4741
|
-
//#region src/table/view-source.ts
|
|
4742
|
-
const indexCache = /* @__PURE__ */ new WeakMap();
|
|
4743
|
-
function sourceIndex(type) {
|
|
4744
|
-
let idx = indexCache.get(type);
|
|
4745
|
-
if (idx) return idx;
|
|
4746
|
-
idx = {
|
|
4747
|
-
flatMap: /* @__PURE__ */ new Map(),
|
|
4748
|
-
columnMap: /* @__PURE__ */ new Map(),
|
|
4749
|
-
documentColumnMap: /* @__PURE__ */ new Map(),
|
|
4750
|
-
unstored: /* @__PURE__ */ new Set(),
|
|
4751
|
-
jsonRoots: /* @__PURE__ */ new Set(),
|
|
4752
|
-
encrypted: /* @__PURE__ */ new Set(),
|
|
4753
|
-
optional: /* @__PURE__ */ new Set()
|
|
4754
|
-
};
|
|
4755
|
-
if (type.type.kind === "object") {
|
|
4756
|
-
const navFields = /* @__PURE__ */ new Set();
|
|
4757
|
-
const collected = [];
|
|
4758
|
-
idx.flatMap = (0, _atscript_typescript_utils.flattenAnnotatedType)(type, {
|
|
4759
|
-
excludePhantomTypes: true,
|
|
4760
|
-
onField: (path, fieldType, metadata) => {
|
|
4761
|
-
if (isNavRelation(metadata)) navFields.add(path);
|
|
4762
|
-
collected.push([
|
|
4763
|
-
path,
|
|
4764
|
-
fieldType,
|
|
4765
|
-
metadata
|
|
4766
|
-
]);
|
|
4767
|
-
}
|
|
4768
|
-
});
|
|
4769
|
-
const storage = [];
|
|
4770
|
-
for (const [path, fieldType, metadata] of collected) {
|
|
4771
|
-
if (!path || findAncestorInSet(path, navFields) !== void 0) continue;
|
|
4772
|
-
const column = metadata.get("db.column");
|
|
4773
|
-
if (column) {
|
|
4774
|
-
idx.columnMap.set(path, column);
|
|
4775
|
-
if (columnOverrideApplies(path, true)) idx.documentColumnMap.set(path, column);
|
|
4776
|
-
}
|
|
4777
|
-
if (metadata.has("db.ignore") || navFields.has(path)) idx.unstored.add(path);
|
|
4778
|
-
if (metadata.has("db.encrypted")) storage.push([path, false]);
|
|
4779
|
-
else if (metadata.has("db.json") || resolveDesignType(fieldType) === "array") storage.push([path, true]);
|
|
4780
|
-
}
|
|
4781
|
-
storage.sort(([a], [b]) => a.split(".").length - b.split(".").length);
|
|
4782
|
-
for (const [path, json] of storage) if (findAncestorInSet(path, idx.jsonRoots) === void 0 && findAncestorInSet(path, idx.encrypted) === void 0) (json ? idx.jsonRoots : idx.encrypted).add(path);
|
|
4783
|
-
for (const [path, node] of idx.flatMap) if (path && node.optional) idx.optional.add(path);
|
|
4784
|
-
}
|
|
4785
|
-
indexCache.set(type, idx);
|
|
4786
|
-
return idx;
|
|
4787
|
-
}
|
|
4788
|
-
/** `path` itself if it is in `set`, else its nearest ancestor in `set`. */
|
|
4789
|
-
function selfOrAncestor(path, set) {
|
|
4790
|
-
return set.has(path) ? path : findAncestorInSet(path, set);
|
|
4791
|
-
}
|
|
4792
|
-
/**
|
|
4793
|
-
* Resolves a LOGICAL path of a source table (a view field's chain ref, an
|
|
4794
|
-
* aggregate's field, a predicate operand) to where it is physically stored.
|
|
4795
|
-
* Internal — `AtscriptDbView.resolveRefSource` is the public entry.
|
|
4796
|
-
*
|
|
4797
|
-
* Relational rules (`TableMetadata`'s): the outermost `@db.json` or array
|
|
4798
|
-
* node with segments remaining is the column and the rest becomes
|
|
4799
|
-
* {@link TViewSource.jsonPath}; a flattened leaf is its parent segments
|
|
4800
|
-
* joined with `__` plus its `@db.column` (or segment); a top-level field is
|
|
4801
|
-
* its `@db.column` or name. Nested-object adapters use the document path
|
|
4802
|
-
* (only a top-level key is renamed) and never a JSON path.
|
|
4803
|
-
*
|
|
4804
|
-
* A path the type does not declare resolves to itself — the database
|
|
4805
|
-
* reports the unknown column, as before.
|
|
4806
|
-
* @throws for a path without storage (`@db.ignore`, a navigation relation,
|
|
4807
|
-
* or inside one), and — relational — for a path inside an `@db.encrypted` field.
|
|
4808
|
-
*/
|
|
4809
|
-
function resolveViewSource(sourceType, logicalPath, nestedObjects) {
|
|
4810
|
-
const idx = sourceIndex(sourceType);
|
|
4811
|
-
const node = idx.flatMap.get(logicalPath);
|
|
4812
|
-
const jsonRoot = selfOrAncestor(logicalPath, idx.jsonRoots);
|
|
4813
|
-
const optional = selfOrAncestor(logicalPath, idx.optional) !== void 0 || jsonRoot !== void 0 && jsonRoot !== logicalPath;
|
|
4814
|
-
if (!node) return {
|
|
4815
|
-
column: logicalPath,
|
|
4816
|
-
designType: "unknown",
|
|
4817
|
-
optional
|
|
4818
|
-
};
|
|
4819
|
-
const unstored = selfOrAncestor(logicalPath, idx.unstored);
|
|
4820
|
-
if (unstored !== void 0) throw new Error(`"${logicalPath}" has no column — "${unstored}" is @db.ignore or a navigation relation`);
|
|
4821
|
-
const designType = resolveDesignType(node);
|
|
4822
|
-
if (nestedObjects) return {
|
|
4823
|
-
column: documentPath(idx.documentColumnMap, logicalPath),
|
|
4824
|
-
designType,
|
|
4825
|
-
optional
|
|
4826
|
-
};
|
|
4827
|
-
const encrypted = findAncestorInSet(logicalPath, idx.encrypted);
|
|
4828
|
-
if (encrypted !== void 0) throw new Error(`"${logicalPath}" is inside the @db.encrypted field "${encrypted}" — reference "${encrypted}" itself`);
|
|
4829
|
-
if (jsonRoot !== void 0) {
|
|
4830
|
-
const column = relationalColumnName(jsonRoot, idx.columnMap.get(jsonRoot), jsonRoot.includes("."));
|
|
4831
|
-
return jsonRoot === logicalPath ? {
|
|
4832
|
-
column,
|
|
4833
|
-
designType,
|
|
4834
|
-
optional
|
|
4835
|
-
} : {
|
|
4836
|
-
column,
|
|
4837
|
-
jsonPath: logicalPath.slice(jsonRoot.length + 1).split("."),
|
|
4838
|
-
designType,
|
|
4839
|
-
optional
|
|
4840
|
-
};
|
|
4841
|
-
}
|
|
4842
|
-
if (designType === "object" && !idx.encrypted.has(logicalPath)) return {
|
|
4843
|
-
column: logicalPath.replace(/\./g, "__"),
|
|
4844
|
-
designType,
|
|
4845
|
-
flattened: true,
|
|
4846
|
-
optional
|
|
4847
|
-
};
|
|
4848
|
-
return {
|
|
4849
|
-
column: relationalColumnName(logicalPath, idx.columnMap.get(logicalPath), logicalPath.includes(".")),
|
|
4850
|
-
designType,
|
|
4851
|
-
optional
|
|
4852
|
-
};
|
|
4853
|
-
}
|
|
4854
|
-
//#endregion
|
|
4855
5195
|
//#region src/table/db-view.ts
|
|
4856
5196
|
/** The `@db.agg.*` annotations, one per supported aggregate function. */
|
|
4857
5197
|
const AGG_KEYS = require_aggregate_fns.SUPPORTED_AGGREGATE_FNS.map((fn) => `db.agg.${fn}`);
|
|
@@ -4881,11 +5221,6 @@ function readViewAgg(metadata) {
|
|
|
4881
5221
|
};
|
|
4882
5222
|
}
|
|
4883
5223
|
}
|
|
4884
|
-
const JSON_LEAF_TYPES = new Set([
|
|
4885
|
-
"string",
|
|
4886
|
-
"number",
|
|
4887
|
-
"boolean"
|
|
4888
|
-
]);
|
|
4889
5224
|
/**
|
|
4890
5225
|
* Database view abstraction driven by Atscript `@db.view.*` annotations.
|
|
4891
5226
|
*
|
|
@@ -4927,16 +5262,19 @@ var AtscriptDbView = class extends AtscriptDbReadable {
|
|
|
4927
5262
|
const metadata = this._type.metadata;
|
|
4928
5263
|
const forRef = metadata.get("db.view.for");
|
|
4929
5264
|
const entryType = typeof forRef === "function" ? forRef : forRef.type;
|
|
4930
|
-
const
|
|
5265
|
+
const entry = viewSourceOf(entryType());
|
|
5266
|
+
if (entry.alias) throw new Error(`View "${this.tableName}": @db.view.for "${entry.name}" is a @db.alias — a join alias cannot be the entry table`);
|
|
5267
|
+
const entryTable = entry.table;
|
|
4931
5268
|
const rawJoins = metadata.get("db.view.joins");
|
|
4932
5269
|
const joins = [];
|
|
4933
5270
|
if (rawJoins) for (const join of rawJoins) {
|
|
4934
5271
|
const targetRef = join.target;
|
|
4935
5272
|
const targetType = typeof targetRef === "function" ? targetRef : targetRef.type;
|
|
4936
|
-
const
|
|
5273
|
+
const target = viewSourceOf(targetType());
|
|
4937
5274
|
joins.push({
|
|
4938
5275
|
targetType,
|
|
4939
|
-
targetTable:
|
|
5276
|
+
targetTable: target.table,
|
|
5277
|
+
scope: target.name,
|
|
4940
5278
|
condition: join.condition,
|
|
4941
5279
|
kind: join.kind === "left" ? "left" : "inner"
|
|
4942
5280
|
});
|
|
@@ -4960,19 +5298,22 @@ var AtscriptDbView = class extends AtscriptDbReadable {
|
|
|
4960
5298
|
}
|
|
4961
5299
|
/**
|
|
4962
5300
|
* Resolves a view query field ref (join condition, `@db.view.filter`,
|
|
4963
|
-
* conditional-aggregate predicate) to its
|
|
5301
|
+
* conditional-aggregate predicate) to its scope name and PHYSICAL source on
|
|
4964
5302
|
* this view's adapter — the column (or document path) with `TableMetadata`'s
|
|
4965
5303
|
* layout rules, the path inside a JSON column, and whether the value may be
|
|
4966
|
-
* absent. An unqualified ref resolves against the entry table.
|
|
5304
|
+
* absent. An unqualified ref resolves against the entry table. `table` is
|
|
5305
|
+
* the physical table / view name, or the alias name for a `@db.alias`
|
|
5306
|
+
* target (since 0.1.141); a source that is itself a managed view resolves
|
|
5307
|
+
* to that view's own columns.
|
|
4967
5308
|
* @throws for a ref without storage (`@db.ignore`, navigation relation) or
|
|
4968
5309
|
* inside an `@db.encrypted` field (relational adapters).
|
|
4969
5310
|
* @since 0.1.136
|
|
4970
5311
|
*/
|
|
4971
5312
|
resolveRefSource(ref) {
|
|
4972
|
-
const
|
|
5313
|
+
const src = viewSourceOf(ref.type ? ref.type() : this.viewPlan.entryType());
|
|
4973
5314
|
return {
|
|
4974
|
-
table:
|
|
4975
|
-
source: resolveViewSource(type, ref.field, this._nested)
|
|
5315
|
+
table: src.name,
|
|
5316
|
+
source: resolveViewSource(src.type, ref.field, this._nested)
|
|
4976
5317
|
};
|
|
4977
5318
|
}
|
|
4978
5319
|
/**
|
|
@@ -5024,7 +5365,7 @@ var AtscriptDbView = class extends AtscriptDbReadable {
|
|
|
5024
5365
|
const fail = (field, message) => {
|
|
5025
5366
|
throw new Error(`View "${this.tableName}" field "${field}": ${message}`);
|
|
5026
5367
|
};
|
|
5027
|
-
const leftJoined = new Set(plan.joins.filter((j) => j.kind === "left").map((j) => j.
|
|
5368
|
+
const leftJoined = new Set(plan.joins.filter((j) => j.kind === "left").map((j) => j.scope));
|
|
5028
5369
|
for (const [fieldName, fieldType] of this._type.type.props.entries()) {
|
|
5029
5370
|
if (ignored.has(fieldName)) continue;
|
|
5030
5371
|
const agg = readViewAgg(fieldType.metadata);
|
|
@@ -5032,16 +5373,18 @@ var AtscriptDbView = class extends AtscriptDbReadable {
|
|
|
5032
5373
|
if (aggField === "*" && agg?.aggFn !== "count") fail(fieldName, `aggregate "${agg?.aggFn}" needs a field — only count accepts *`);
|
|
5033
5374
|
let sourceType;
|
|
5034
5375
|
let sourcePath;
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5376
|
+
const chainRef = fieldType.ref?.field ? fieldType.ref : void 0;
|
|
5377
|
+
if (chainRef) {
|
|
5378
|
+
sourceType = chainRef.type();
|
|
5379
|
+
sourcePath = chainRef.field;
|
|
5038
5380
|
} else {
|
|
5039
5381
|
sourceType = plan.entryType();
|
|
5040
5382
|
sourcePath = aggField && aggField !== "*" ? aggField : fieldName;
|
|
5041
5383
|
}
|
|
5042
|
-
const
|
|
5384
|
+
const src = viewSourceOf(sourceType);
|
|
5385
|
+
const sourceTable = src.name;
|
|
5043
5386
|
const joinNullable = leftJoined.has(sourceTable);
|
|
5044
|
-
if (aggField === "*" && !
|
|
5387
|
+
if (aggField === "*" && !chainRef) {
|
|
5045
5388
|
mappings.push({
|
|
5046
5389
|
viewColumn: viewName(fieldName) ?? fieldName,
|
|
5047
5390
|
viewPath: fieldName,
|
|
@@ -5051,7 +5394,7 @@ var AtscriptDbView = class extends AtscriptDbReadable {
|
|
|
5051
5394
|
});
|
|
5052
5395
|
continue;
|
|
5053
5396
|
}
|
|
5054
|
-
const source = resolveViewSource(
|
|
5397
|
+
const source = resolveViewSource(src.type, sourcePath, nested);
|
|
5055
5398
|
const ownColumn = viewName(fieldName);
|
|
5056
5399
|
if (ownColumn === void 0 && !nested) {
|
|
5057
5400
|
if (!source.flattened) fail(fieldName, "source is a JSON column — add @db.json to the view field");
|
|
@@ -5059,7 +5402,7 @@ var AtscriptDbView = class extends AtscriptDbReadable {
|
|
|
5059
5402
|
for (const [viewPath, viewColumn] of meta.pathToPhysical) {
|
|
5060
5403
|
if (!viewPath.startsWith(prefix) || ignored.has(viewPath)) continue;
|
|
5061
5404
|
const leafPath = `${sourcePath}.${viewPath.slice(prefix.length)}`;
|
|
5062
|
-
const leaf = resolveViewSource(
|
|
5405
|
+
const leaf = resolveViewSource(src.type, leafPath, nested);
|
|
5063
5406
|
mappings.push(this._leafMapping(viewPath, viewColumn, sourceTable, leaf, joinNullable));
|
|
5064
5407
|
}
|
|
5065
5408
|
continue;
|
|
@@ -5083,7 +5426,7 @@ var AtscriptDbView = class extends AtscriptDbReadable {
|
|
|
5083
5426
|
};
|
|
5084
5427
|
if (joinNullable || source.optional || source.designType === "unknown") mapping.nullable = true;
|
|
5085
5428
|
if (source.jsonPath) {
|
|
5086
|
-
if (!
|
|
5429
|
+
if (!require_derived_rules.isJsonLeafType(source.designType)) throw new Error(`View "${this.tableName}" field "${viewPath}": JSON extraction supports string, number and boolean leaves only`);
|
|
5087
5430
|
mapping.json = {
|
|
5088
5431
|
path: source.jsonPath,
|
|
5089
5432
|
type: source.designType
|
|
@@ -5106,9 +5449,33 @@ function isAtscriptDbView(readable) {
|
|
|
5106
5449
|
}
|
|
5107
5450
|
//#endregion
|
|
5108
5451
|
//#region src/schema/schema-hash.ts
|
|
5109
|
-
/**
|
|
5110
|
-
|
|
5111
|
-
|
|
5452
|
+
/**
|
|
5453
|
+
* The physical sources a stored view snapshot reads: its entry table and the
|
|
5454
|
+
* physical table of every join (an aliased join's `table`). External views
|
|
5455
|
+
* (no plan) yield `[]`.
|
|
5456
|
+
* @since 0.1.141
|
|
5457
|
+
*/
|
|
5458
|
+
function viewSnapshotSources(snapshot) {
|
|
5459
|
+
const sources = (snapshot.joinTables ?? []).map((j) => j.table ?? j.targetTable);
|
|
5460
|
+
return snapshot.entryTable ? [snapshot.entryTable, ...sources] : sources;
|
|
5461
|
+
}
|
|
5462
|
+
/**
|
|
5463
|
+
* The descriptors a snapshot lists: the non-ignored ones — plus, on a
|
|
5464
|
+
* nested-object adapter, the subfields of navigation properties. Those are
|
|
5465
|
+
* `ignored` since 0.1.141 (never columns: out of the plan, the column diff
|
|
5466
|
+
* and every write), but earlier releases wrote them into a document
|
|
5467
|
+
* adapter's snapshot; they stay listed so the upgrade leaves every stored
|
|
5468
|
+
* hash unchanged. {@link snapshotToExistingColumns} (given the readable)
|
|
5469
|
+
* leaves them out when a stored snapshot stands in for the live columns.
|
|
5470
|
+
*/
|
|
5471
|
+
function snapshotFields(readable) {
|
|
5472
|
+
const nav = readable.navFields;
|
|
5473
|
+
if (!nav?.size || !readable.dbAdapter.supportsNestedObjects()) return readable.fieldDescriptors.filter((f) => !f.ignored);
|
|
5474
|
+
return readable.fieldDescriptors.filter((f) => !f.ignored || require_validator.findAncestorInSet(f.path, nav) !== void 0);
|
|
5475
|
+
}
|
|
5476
|
+
/** Extracts sorted field snapshots from a readable's {@link snapshotFields}. */
|
|
5477
|
+
function extractFieldSnapshots(readable, typeMapper) {
|
|
5478
|
+
return snapshotFields(readable).map((f) => {
|
|
5112
5479
|
const snap = {
|
|
5113
5480
|
physicalName: f.physicalName,
|
|
5114
5481
|
designType: f.designType,
|
|
@@ -5119,6 +5486,11 @@ function extractFieldSnapshots(fields, typeMapper) {
|
|
|
5119
5486
|
if (f.defaultValue) snap.defaultValue = f.defaultValue;
|
|
5120
5487
|
if (typeMapper) snap.mappedType = typeMapper(f);
|
|
5121
5488
|
if (f.encrypted) snap.encrypted = true;
|
|
5489
|
+
if (f.derived) snap.derived = {
|
|
5490
|
+
sourceColumn: f.derived.sourceColumn,
|
|
5491
|
+
jsonPath: [...f.derived.jsonPath],
|
|
5492
|
+
type: f.derived.type
|
|
5493
|
+
};
|
|
5122
5494
|
return snap;
|
|
5123
5495
|
}).toSorted((a, b) => a.physicalName.localeCompare(b.physicalName));
|
|
5124
5496
|
}
|
|
@@ -5132,7 +5504,7 @@ function extractFieldSnapshots(fields, typeMapper) {
|
|
|
5132
5504
|
* for precise type change detection.
|
|
5133
5505
|
*/
|
|
5134
5506
|
function computeTableSnapshot(readable, typeMapper, tableOptions) {
|
|
5135
|
-
const fields = extractFieldSnapshots(readable
|
|
5507
|
+
const fields = extractFieldSnapshots(readable, typeMapper);
|
|
5136
5508
|
const indexes = [...readable.indexes.values()].map((idx) => ({
|
|
5137
5509
|
key: idx.key,
|
|
5138
5510
|
type: idx.type,
|
|
@@ -5163,7 +5535,7 @@ function computeTableSnapshot(readable, typeMapper, tableOptions) {
|
|
|
5163
5535
|
* detecting view definition changes.
|
|
5164
5536
|
*/
|
|
5165
5537
|
function computeViewSnapshot(view) {
|
|
5166
|
-
const fields = extractFieldSnapshots(view
|
|
5538
|
+
const fields = extractFieldSnapshots(view);
|
|
5167
5539
|
if (view.isExternal) return {
|
|
5168
5540
|
tableName: view.tableName,
|
|
5169
5541
|
viewType: "E",
|
|
@@ -5193,7 +5565,8 @@ function computeViewSnapshot(view) {
|
|
|
5193
5565
|
entryTable: plan.entryTable,
|
|
5194
5566
|
joinTables: plan.joins.map((j) => {
|
|
5195
5567
|
const join = {
|
|
5196
|
-
targetTable: j.
|
|
5568
|
+
targetTable: j.scope,
|
|
5569
|
+
...j.scope !== j.targetTable ? { table: j.targetTable } : {},
|
|
5197
5570
|
condition: canonical(j.condition)
|
|
5198
5571
|
};
|
|
5199
5572
|
if (j.kind === "left") join.kind = "left";
|
|
@@ -5255,10 +5628,19 @@ function computeTableHash(snapshot) {
|
|
|
5255
5628
|
* native column introspection (e.g., MongoDB).
|
|
5256
5629
|
*
|
|
5257
5630
|
* The `type` field uses `mappedType` when available (adapter-specific),
|
|
5258
|
-
* falling back to `designType`.
|
|
5631
|
+
* falling back to `designType`. Derived fields are left out (since 0.1.141):
|
|
5632
|
+
* an adapter without column introspection stores no derived column, and a
|
|
5633
|
+
* `physicalName` of one is its SOURCE path — reporting it as an existing
|
|
5634
|
+
* column would have the diff drop the source leaf.
|
|
5635
|
+
*
|
|
5636
|
+
* With `readable`, the subfields of its navigation properties are left out
|
|
5637
|
+
* too: a document adapter's snapshot lists them (for hash stability — see
|
|
5638
|
+
* `snapshotFields`) although nothing is stored under a nav field — they are
|
|
5639
|
+
* not columns to drop.
|
|
5259
5640
|
*/
|
|
5260
|
-
function snapshotToExistingColumns(snapshot) {
|
|
5261
|
-
|
|
5641
|
+
function snapshotToExistingColumns(snapshot, readable) {
|
|
5642
|
+
const navPrefixes = readable && readable.navFields.size > 0 ? readable.fieldDescriptors.filter((fd) => readable.navFields.has(fd.path)).map((fd) => `${fd.physicalName}.`) : [];
|
|
5643
|
+
return snapshot.fields.filter((f) => !f.derived && !navPrefixes.some((p) => f.physicalName.startsWith(p))).map((f) => ({
|
|
5262
5644
|
name: f.physicalName,
|
|
5263
5645
|
type: f.mappedType ?? f.designType,
|
|
5264
5646
|
notnull: !f.optional,
|
|
@@ -5337,6 +5719,18 @@ function fkPropertiesDiffer(desired, existing) {
|
|
|
5337
5719
|
//#endregion
|
|
5338
5720
|
//#region src/schema/column-diff.ts
|
|
5339
5721
|
/**
|
|
5722
|
+
* Why a derived column (desired, live, or both) must be dropped and re-added,
|
|
5723
|
+
* or `undefined` when it is unchanged. Nullability and defaults are never
|
|
5724
|
+
* compared for one: a generated column is nullable and has no DEFAULT.
|
|
5725
|
+
*/
|
|
5726
|
+
function derivedChangeReason(field, existingCol, typeMapper, snap) {
|
|
5727
|
+
if (!field.derived) return existingCol.generated ? "kind" : void 0;
|
|
5728
|
+
if (!existingCol.generated) return "kind";
|
|
5729
|
+
const stored = snap?.derived;
|
|
5730
|
+
if (stored && (stored.sourceColumn !== field.derived.sourceColumn || stored.type !== field.derived.type || stored.jsonPath.length !== field.derived.jsonPath.length || stored.jsonPath.some((seg, i) => seg !== field.derived.jsonPath[i]))) return "expression";
|
|
5731
|
+
if (typeMapper && isColumnTypeChanged(existingCol.type, typeMapper(field))) return "type";
|
|
5732
|
+
}
|
|
5733
|
+
/**
|
|
5340
5734
|
* Whether a live column's type differs from the type the adapter's
|
|
5341
5735
|
* `typeMapper` gives the field — the one rule schema sync diffs column types
|
|
5342
5736
|
* by (case-insensitive). Exported for adapters that must agree with it (the
|
|
@@ -5349,15 +5743,22 @@ function isColumnTypeChanged(existingType, expectedType) {
|
|
|
5349
5743
|
/**
|
|
5350
5744
|
* Computes the difference between desired schema fields and existing database columns.
|
|
5351
5745
|
*
|
|
5352
|
-
* @param desired - Field descriptors from the Atscript type (after flattening)
|
|
5746
|
+
* @param desired - Field descriptors from the Atscript type (after flattening) —
|
|
5747
|
+
* the readable's `columnDescriptors`; ignored descriptors are skipped.
|
|
5353
5748
|
* @param existing - Columns currently in the database (from introspection).
|
|
5354
5749
|
* @param typeMapper - Optional function to map field metadata to DB-native type strings.
|
|
5355
5750
|
* Receives the full field meta (design type, annotations, PK status, etc.)
|
|
5356
5751
|
* so adapters can produce context-aware types (e.g., `VARCHAR(255)` from maxLength).
|
|
5357
5752
|
* Required for type change detection.
|
|
5753
|
+
* @param opts.snapshot - The table's stored snapshot (since 0.1.141) — the baseline a
|
|
5754
|
+
* derived column's expression is compared with (the engine normalizes
|
|
5755
|
+
* the expression it stores, so the live column cannot be). Without
|
|
5756
|
+
* one, only a kind or type drift is seen.
|
|
5358
5757
|
*/
|
|
5359
|
-
function computeColumnDiff(desired, existing, typeMapper) {
|
|
5758
|
+
function computeColumnDiff(desired, existing, typeMapper, opts) {
|
|
5360
5759
|
const existingByName = new Map(existing.map((c) => [c.name, c]));
|
|
5760
|
+
const snapshotByName = opts?.snapshot ? new Map(opts.snapshot.fields.map((f) => [f.physicalName, f])) : void 0;
|
|
5761
|
+
const derivedChanged = [];
|
|
5361
5762
|
const desiredByName = /* @__PURE__ */ new Map();
|
|
5362
5763
|
const renamedOldNames = /* @__PURE__ */ new Set();
|
|
5363
5764
|
const added = [];
|
|
@@ -5366,44 +5767,60 @@ function computeColumnDiff(desired, existing, typeMapper) {
|
|
|
5366
5767
|
const nullableChanged = [];
|
|
5367
5768
|
const defaultChanged = [];
|
|
5368
5769
|
const conflicts = [];
|
|
5770
|
+
/**
|
|
5771
|
+
* A derived column (on either side) is compared as a whole — any drift is a
|
|
5772
|
+
* drop + add, and nullability / defaults are not the model's to manage.
|
|
5773
|
+
* `true` when the pair was a derived one (handled here).
|
|
5774
|
+
*/
|
|
5775
|
+
const checkDerived = (field, col, snapKey) => {
|
|
5776
|
+
if (!field.derived && !col.generated) return false;
|
|
5777
|
+
const reason = derivedChangeReason(field, col, typeMapper, snapshotByName?.get(snapKey));
|
|
5778
|
+
if (reason) derivedChanged.push({
|
|
5779
|
+
field,
|
|
5780
|
+
reason
|
|
5781
|
+
});
|
|
5782
|
+
return true;
|
|
5783
|
+
};
|
|
5369
5784
|
for (const field of desired) {
|
|
5370
5785
|
if (field.ignored) continue;
|
|
5371
5786
|
desiredByName.set(field.physicalName, field);
|
|
5372
5787
|
const existingCol = existingByName.get(field.physicalName);
|
|
5373
|
-
if (existingCol)
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
oldName: field.renamedFrom,
|
|
5377
|
-
conflictsWith: field.physicalName
|
|
5378
|
-
});
|
|
5379
|
-
renamedOldNames.add(field.renamedFrom);
|
|
5380
|
-
} else {
|
|
5381
|
-
if (typeMapper) {
|
|
5382
|
-
if (isColumnTypeChanged(existingCol.type, typeMapper(field))) typeChanged.push({
|
|
5788
|
+
if (existingCol) {
|
|
5789
|
+
if (field.renamedFrom && existingByName.has(field.renamedFrom)) {
|
|
5790
|
+
conflicts.push({
|
|
5383
5791
|
field,
|
|
5384
|
-
|
|
5792
|
+
oldName: field.renamedFrom,
|
|
5793
|
+
conflictsWith: field.physicalName
|
|
5385
5794
|
});
|
|
5386
|
-
|
|
5387
|
-
if (!field
|
|
5388
|
-
|
|
5389
|
-
|
|
5795
|
+
renamedOldNames.add(field.renamedFrom);
|
|
5796
|
+
} else if (!checkDerived(field, existingCol, field.physicalName)) {
|
|
5797
|
+
if (typeMapper) {
|
|
5798
|
+
if (isColumnTypeChanged(existingCol.type, typeMapper(field))) typeChanged.push({
|
|
5799
|
+
field,
|
|
5800
|
+
existingType: existingCol.type
|
|
5801
|
+
});
|
|
5802
|
+
}
|
|
5803
|
+
if (!field.isPrimaryKey && !existingCol.pk) {
|
|
5804
|
+
const desiredNotNull = !field.optional;
|
|
5805
|
+
if (existingCol.notnull !== desiredNotNull) nullableChanged.push({
|
|
5806
|
+
field,
|
|
5807
|
+
wasNullable: !existingCol.notnull
|
|
5808
|
+
});
|
|
5809
|
+
}
|
|
5810
|
+
const desiredDefault = serializeDefaultValue(field.defaultValue);
|
|
5811
|
+
if (existingCol.dflt_value !== void 0 && existingCol.dflt_value !== desiredDefault) defaultChanged.push({
|
|
5390
5812
|
field,
|
|
5391
|
-
|
|
5813
|
+
oldDefault: existingCol.dflt_value,
|
|
5814
|
+
newDefault: desiredDefault
|
|
5392
5815
|
});
|
|
5393
5816
|
}
|
|
5394
|
-
|
|
5395
|
-
if (existingCol.dflt_value !== void 0 && existingCol.dflt_value !== desiredDefault) defaultChanged.push({
|
|
5396
|
-
field,
|
|
5397
|
-
oldDefault: existingCol.dflt_value,
|
|
5398
|
-
newDefault: desiredDefault
|
|
5399
|
-
});
|
|
5400
|
-
}
|
|
5401
|
-
else if (field.renamedFrom && existingByName.has(field.renamedFrom)) {
|
|
5817
|
+
} else if (field.renamedFrom && existingByName.has(field.renamedFrom)) {
|
|
5402
5818
|
renamed.push({
|
|
5403
5819
|
field,
|
|
5404
5820
|
oldName: field.renamedFrom
|
|
5405
5821
|
});
|
|
5406
5822
|
renamedOldNames.add(field.renamedFrom);
|
|
5823
|
+
checkDerived(field, existingByName.get(field.renamedFrom), field.renamedFrom);
|
|
5407
5824
|
} else added.push(field);
|
|
5408
5825
|
}
|
|
5409
5826
|
const diff = {
|
|
@@ -5415,6 +5832,7 @@ function computeColumnDiff(desired, existing, typeMapper) {
|
|
|
5415
5832
|
defaultChanged,
|
|
5416
5833
|
conflicts
|
|
5417
5834
|
};
|
|
5835
|
+
if (derivedChanged.length > 0) diff.derivedChanged = derivedChanged;
|
|
5418
5836
|
if (existing.length > 0) {
|
|
5419
5837
|
const newNameByOld = new Map(renamed.map((r) => [r.oldName, r.field.physicalName]));
|
|
5420
5838
|
const from = existing.filter((c) => c.pk).map((c) => newNameByOld.get(c.name) ?? c.name);
|
|
@@ -5529,6 +5947,12 @@ Object.defineProperty(exports, "acceptedOperatorsHint", {
|
|
|
5529
5947
|
return acceptedOperatorsHint;
|
|
5530
5948
|
}
|
|
5531
5949
|
});
|
|
5950
|
+
Object.defineProperty(exports, "aliasTargetOf", {
|
|
5951
|
+
enumerable: true,
|
|
5952
|
+
get: function() {
|
|
5953
|
+
return aliasTargetOf;
|
|
5954
|
+
}
|
|
5955
|
+
});
|
|
5532
5956
|
Object.defineProperty(exports, "assertGeoPoint", {
|
|
5533
5957
|
enumerable: true,
|
|
5534
5958
|
get: function() {
|
|
@@ -5625,12 +6049,6 @@ Object.defineProperty(exports, "decomposePatch", {
|
|
|
5625
6049
|
return decomposePatch;
|
|
5626
6050
|
}
|
|
5627
6051
|
});
|
|
5628
|
-
Object.defineProperty(exports, "findAncestorInSet", {
|
|
5629
|
-
enumerable: true,
|
|
5630
|
-
get: function() {
|
|
5631
|
-
return findAncestorInSet;
|
|
5632
|
-
}
|
|
5633
|
-
});
|
|
5634
6052
|
Object.defineProperty(exports, "fkKey", {
|
|
5635
6053
|
enumerable: true,
|
|
5636
6054
|
get: function() {
|
|
@@ -5763,3 +6181,9 @@ Object.defineProperty(exports, "unsupportedOperatorMessage", {
|
|
|
5763
6181
|
return unsupportedOperatorMessage;
|
|
5764
6182
|
}
|
|
5765
6183
|
});
|
|
6184
|
+
Object.defineProperty(exports, "viewSnapshotSources", {
|
|
6185
|
+
enumerable: true,
|
|
6186
|
+
get: function() {
|
|
6187
|
+
return viewSnapshotSources;
|
|
6188
|
+
}
|
|
6189
|
+
});
|