@atscript/db 0.1.139 → 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.
Files changed (49) hide show
  1. package/dist/agg.d.cts +1 -1
  2. package/dist/agg.d.mts +1 -1
  3. package/dist/{buckets-C-27xmtq.d.cts → buckets-Bv4pah66.d.cts} +225 -22
  4. package/dist/{buckets-BFG2RYRW.d.mts → buckets-CjL7F-hp.d.mts} +225 -22
  5. package/dist/{column-diff-CgxgFKzx.cjs → column-diff-CfPNcP6e.cjs} +663 -239
  6. package/dist/{column-diff-BwOA5101.mjs → column-diff-CmFNXV8C.mjs} +638 -220
  7. package/dist/column-diff-DiBbXyLA.d.cts +211 -0
  8. package/dist/column-diff-n-k5KY0u.d.mts +211 -0
  9. package/dist/derived-rules-0sKn4f5C.mjs +44 -0
  10. package/dist/derived-rules-YstgIxG-.cjs +67 -0
  11. package/dist/index.cjs +38 -4
  12. package/dist/index.d.cts +23 -6
  13. package/dist/index.d.mts +23 -6
  14. package/dist/index.mjs +34 -4
  15. package/dist/{nested-writer-FWD5oOYh.mjs → nested-writer-BO3vhbkP.mjs} +8 -4
  16. package/dist/{nested-writer-BZNCuqI6.cjs → nested-writer-DYsRxZ5f.cjs} +8 -4
  17. package/dist/object-DSN0h9lB.d.cts +30 -0
  18. package/dist/object-DSN0h9lB.d.mts +30 -0
  19. package/dist/plugin.cjs +392 -139
  20. package/dist/plugin.mjs +392 -139
  21. package/dist/rel.cjs +2 -2
  22. package/dist/rel.d.cts +2 -2
  23. package/dist/rel.d.mts +2 -2
  24. package/dist/rel.mjs +2 -2
  25. package/dist/{relation-helpers-D3Zu0Mta.d.mts → relation-helpers-B59to_dG.d.mts} +5 -4
  26. package/dist/{relation-helpers-DxrvS6ar.d.cts → relation-helpers-DQ_nRsV9.d.cts} +5 -4
  27. package/dist/{relation-loader-6ZB_5KFq.cjs → relation-loader-CgJ8bK6X.cjs} +1 -1
  28. package/dist/{relation-loader-CTFaZpVa.mjs → relation-loader-CuhEBzFU.mjs} +1 -1
  29. package/dist/shared.cjs +6 -1
  30. package/dist/shared.d.cts +48 -9
  31. package/dist/shared.d.mts +48 -9
  32. package/dist/shared.mjs +2 -2
  33. package/dist/sync.cjs +331 -105
  34. package/dist/sync.d.cts +62 -163
  35. package/dist/sync.d.mts +62 -163
  36. package/dist/sync.mjs +331 -105
  37. package/dist/{validation-utils-B4h-GW4d.mjs → validation-utils-CMR4fe2M.mjs} +99 -34
  38. package/dist/{validation-utils-Dg0hW6dn.cjs → validation-utils-DOsB4e6G.cjs} +128 -33
  39. package/dist/{validator-Drb2N-YL.d.cts → validator-Bw6ks9Hy.d.cts} +1 -11
  40. package/dist/{validator-Drb2N-YL.d.mts → validator-Bw6ks9Hy.d.mts} +1 -11
  41. package/dist/{validator-Ch7UIQl9.mjs → validator-D8bPsXPN.mjs} +54 -2
  42. package/dist/{validator-BtZbcLN2.cjs → validator-DASnXf1j.cjs} +77 -1
  43. package/dist/validator.cjs +1 -1
  44. package/dist/validator.d.cts +2 -1
  45. package/dist/validator.d.mts +2 -1
  46. package/dist/validator.mjs +1 -1
  47. package/package.json +6 -6
  48. package/dist/column-diff-BmqvgBWw.d.cts +0 -24
  49. package/dist/column-diff-DPkbZIVE.d.mts +0 -24
@@ -1,3 +1,4 @@
1
+ import { t as DB_ENTITY_ANNOTATIONS } from "./derived-rules-0sKn4f5C.mjs";
1
2
  import { AnnotationSpec, isArray, isInterface, isPrimitive, isQueryComparison, isQueryLogical, isRef, isStructure } from "@atscript/core";
2
3
  //#region src/shared/annotation-utils.ts
3
4
  /** Asserts the field carrying this annotation does not also carry any of `others`. */
@@ -177,6 +178,14 @@ function refActionAnnotation(name) {
177
178
  }
178
179
  //#endregion
179
180
  //#region src/shared/validation-utils.ts
181
+ /** Whether a declaration node is a `@db.table` or a `@db.view` (managed or external). */
182
+ function isDbSourceDecl(node) {
183
+ return DB_ENTITY_ANNOTATIONS.some((name) => node.countAnnotations(name) > 0);
184
+ }
185
+ /** Whether a declaration node is a `@db.alias` type. @since 0.1.141 */
186
+ function isAliasDecl(node) {
187
+ return node.countAnnotations("db.alias") > 0;
188
+ }
180
189
  /**
181
190
  * Validate a ref annotation argument against the document's type registry.
182
191
  * Returns diagnostic messages for unknown types or fields.
@@ -204,16 +213,60 @@ function validateRefArgument(token, doc, options) {
204
213
  return messages;
205
214
  }
206
215
  }
207
- if (options?.requireDbTable && decl.node) {
208
- if (!(decl.node.countAnnotations("db.table") > 0)) messages.push({
209
- severity: 1,
210
- message: `Type '${typeName}' must have @db.table annotation.`,
211
- range: token.range
212
- });
213
- }
216
+ if (options?.accept && decl.node && !options.accept(decl.node)) messages.push({
217
+ severity: 1,
218
+ message: isAliasDecl(decl.node) ? `Type '${typeName}' is a @db.alias — a join alias cannot be used here, reference the aliased table or view.` : `Type '${typeName}' ${options.expected ?? "is not accepted here."}`,
219
+ range: token.range
220
+ });
214
221
  return messages;
215
222
  }
216
223
  /**
224
+ * The declaration a view source name stands for: the named table / view, or
225
+ * — for a `@db.alias` type — the aliased table / view. `undefined` when the
226
+ * name does not resolve (unknown, or an import that is not loaded yet).
227
+ * @since 0.1.141
228
+ */
229
+ function resolveViewSourceDecl(name, doc) {
230
+ const decl = doc.getDeclarationOwnerNode(name);
231
+ if (!decl?.node) return;
232
+ const aliasTarget = getAnnotationAlias(decl.node, "db.alias");
233
+ if (aliasTarget !== void 0) {
234
+ const target = decl.doc.getDeclarationOwnerNode(aliasTarget);
235
+ return target?.node ? {
236
+ doc: target.doc,
237
+ node: target.node
238
+ } : void 0;
239
+ }
240
+ return {
241
+ doc: decl.doc,
242
+ node: decl.node
243
+ };
244
+ }
245
+ /**
246
+ * VW9 — the dependency cycle a view closes, as the list of view names from
247
+ * the view back to itself (`["A", "B", "A"]`), or `undefined`. Walks
248
+ * `@db.view.for` / `@db.view.joins` of the referenced declarations across
249
+ * documents; `@db.alias` sources resolve to their target.
250
+ * @since 0.1.141
251
+ */
252
+ function findViewCycle(owner, doc) {
253
+ const start = owner.id;
254
+ if (!start) return;
255
+ const visited = /* @__PURE__ */ new Set();
256
+ const walk = (node, nodeDoc, path) => {
257
+ for (const name of viewScopeTypes(node)) {
258
+ const source = resolveViewSourceDecl(name, nodeDoc);
259
+ if (!source) continue;
260
+ if (source.node === owner) return [...path, start];
261
+ if (visited.has(source.node)) continue;
262
+ visited.add(source.node);
263
+ const cycle = walk(source.node, source.doc, [...path, source.node.id ?? name]);
264
+ if (cycle) return cycle;
265
+ }
266
+ };
267
+ return walk(owner, doc, [start]);
268
+ }
269
+ /**
217
270
  * Find all `@db.rel.FK` fields on a type that reference `targetTypeName`.
218
271
  * Resolves `extends` to include inherited fields.
219
272
  */
@@ -265,6 +318,16 @@ function joinTargets(joins) {
265
318
  return joins.map((a) => a.args[0]?.text).filter((t) => !!t);
266
319
  }
267
320
  /**
321
+ * The targets of the `@db.view.joins` declared before `join` among the
322
+ * view's `joins` ({@link viewJoins}) — what a chained join condition may
323
+ * reference besides its target and the entry.
324
+ * @since 0.1.141
325
+ */
326
+ function earlierJoinTargets(joins, join) {
327
+ const position = joins.indexOf(join);
328
+ return joinTargets(position === -1 ? [] : joins.slice(0, position));
329
+ }
330
+ /**
268
331
  * The type names a view predicate (`@db.view.filter`, a conditional
269
332
  * `@db.agg.*`) may reference: the `@db.view.for` entry table, then every
270
333
  * `@db.view.joins` target.
@@ -281,19 +344,23 @@ function hasAnyViewAnnotation(node) {
281
344
  return viewAnnotationNames.some((name) => node.countAnnotations(name) > 0);
282
345
  }
283
346
  /**
284
- * Validate that all type refs in a query expression are within the allowed scope.
347
+ * Validate that all field refs in a query expression are within `scope` —
348
+ * the same {@link TQueryScope} the editor completes (`lsp-scopes`):
349
+ * qualified refs must name one of `scope.allowedTypes`; an unqualified ref
350
+ * (a dotted path included) must be a field of `scope.unqualifiedTarget`, or
351
+ * is rejected outright when that is `null`.
285
352
  *
286
353
  * @param queryToken - The query arg token (must have .queryNode)
287
- * @param allowedTypes - Type names allowed as qualified refs
288
- * @param unqualifiedTarget - Type name for resolving unqualified refs, or null to disallow them
354
+ * @param scope - The scope of the argument
289
355
  * @param doc - The document for type lookups
290
356
  * @param scopeHint - Replaces the default "expected 'A' or 'B'" tail of the out-of-scope message
291
357
  */
292
- function validateQueryScope(queryToken, allowedTypes, unqualifiedTarget, doc, scopeHint) {
358
+ function validateQueryScope(queryToken, scope, doc, scopeHint) {
293
359
  const errors = [];
294
360
  const queryNode = queryToken.queryNode;
295
361
  if (!queryNode) return errors;
296
- function walkFieldRef(ref) {
362
+ const { allowedTypes, unqualifiedTarget } = scope;
363
+ forEachFieldRef(queryNode.expression, (ref) => {
297
364
  if (ref.typeRef) {
298
365
  const typeName = ref.typeRef.text;
299
366
  if (!allowedTypes.includes(typeName)) errors.push({
@@ -307,29 +374,27 @@ function validateQueryScope(queryToken, allowedTypes, unqualifiedTarget, doc, sc
307
374
  range: ref.fieldRef.range
308
375
  });
309
376
  else {
310
- const unwound = doc.unwindType(unqualifiedTarget);
311
- if (unwound) {
312
- const targetDef = unwound.def;
313
- if (isInterface(targetDef) || isStructure(targetDef)) {
314
- const struct = isInterface(targetDef) ? targetDef.getDefinition() : targetDef;
315
- if (struct && isStructure(struct) && !struct.props.has(ref.fieldRef.text)) errors.push({
316
- message: `Field '${ref.fieldRef.text}' does not exist on '${unqualifiedTarget}'`,
317
- severity: 1,
318
- range: ref.fieldRef.range
319
- });
320
- }
321
- }
322
- }
323
- }
324
- function walkExpr(expr) {
325
- if (isQueryLogical(expr)) for (const operand of expr.operands) walkExpr(operand);
326
- else if (isQueryComparison(expr)) {
327
- walkFieldRef(expr.left);
328
- if (expr.right && "fieldRef" in expr.right) walkFieldRef(expr.right);
377
+ const targetDef = doc.unwindType(unqualifiedTarget)?.def;
378
+ if (targetDef && (isInterface(targetDef) || isStructure(targetDef)) && !doc.unwindType(unqualifiedTarget, ref.fieldRef.text.split("."))) errors.push({
379
+ message: `Field '${ref.fieldRef.text}' does not exist on '${unqualifiedTarget}'`,
380
+ severity: 1,
381
+ range: ref.fieldRef.range
382
+ });
329
383
  }
330
- }
331
- walkExpr(queryNode.expression);
384
+ });
332
385
  return errors;
333
386
  }
387
+ /**
388
+ * Calls `fn` for every field reference of a query expression — both operands
389
+ * of a field-to-field comparison included.
390
+ * @since 0.1.141
391
+ */
392
+ function forEachFieldRef(expr, fn) {
393
+ if (isQueryLogical(expr)) for (const operand of expr.operands) forEachFieldRef(operand, fn);
394
+ else if (isQueryComparison(expr)) {
395
+ fn(expr.left);
396
+ if (expr.right && "fieldRef" in expr.right) fn(expr.right);
397
+ }
398
+ }
334
399
  //#endregion
335
- export { validateSiblingStringField as _, validateRefArgument as a, getAnnotationAlias as c, getParentStruct as d, getParentTypeName as f, validateFieldBaseType as g, validateExclusiveWith as h, validateQueryScope as i, getDbTableOwner as l, refActionAnnotation as m, hasAnyViewAnnotation as n, viewJoins as o, primitiveBaseType as p, joinTargets as r, viewScopeTypes as s, findFKFieldsPointingTo as t, getNavTargetTypeName as u };
400
+ export { validateSiblingStringField as S, getParentTypeName as _, hasAnyViewAnnotation as a, validateExclusiveWith as b, joinTargets as c, viewJoins as d, viewScopeTypes as f, getParentStruct as g, getNavTargetTypeName as h, forEachFieldRef as i, validateQueryScope as l, getDbTableOwner as m, findFKFieldsPointingTo as n, isAliasDecl as o, getAnnotationAlias as p, findViewCycle as r, isDbSourceDecl as s, earlierJoinTargets as t, validateRefArgument as u, primitiveBaseType as v, validateFieldBaseType as x, refActionAnnotation as y };
@@ -1,3 +1,4 @@
1
+ const require_derived_rules = require("./derived-rules-YstgIxG-.cjs");
1
2
  let _atscript_core = require("@atscript/core");
2
3
  //#region src/shared/annotation-utils.ts
3
4
  /** Asserts the field carrying this annotation does not also carry any of `others`. */
@@ -177,6 +178,14 @@ function refActionAnnotation(name) {
177
178
  }
178
179
  //#endregion
179
180
  //#region src/shared/validation-utils.ts
181
+ /** Whether a declaration node is a `@db.table` or a `@db.view` (managed or external). */
182
+ function isDbSourceDecl(node) {
183
+ return require_derived_rules.DB_ENTITY_ANNOTATIONS.some((name) => node.countAnnotations(name) > 0);
184
+ }
185
+ /** Whether a declaration node is a `@db.alias` type. @since 0.1.141 */
186
+ function isAliasDecl(node) {
187
+ return node.countAnnotations("db.alias") > 0;
188
+ }
180
189
  /**
181
190
  * Validate a ref annotation argument against the document's type registry.
182
191
  * Returns diagnostic messages for unknown types or fields.
@@ -204,16 +213,60 @@ function validateRefArgument(token, doc, options) {
204
213
  return messages;
205
214
  }
206
215
  }
207
- if (options?.requireDbTable && decl.node) {
208
- if (!(decl.node.countAnnotations("db.table") > 0)) messages.push({
209
- severity: 1,
210
- message: `Type '${typeName}' must have @db.table annotation.`,
211
- range: token.range
212
- });
213
- }
216
+ if (options?.accept && decl.node && !options.accept(decl.node)) messages.push({
217
+ severity: 1,
218
+ message: isAliasDecl(decl.node) ? `Type '${typeName}' is a @db.alias — a join alias cannot be used here, reference the aliased table or view.` : `Type '${typeName}' ${options.expected ?? "is not accepted here."}`,
219
+ range: token.range
220
+ });
214
221
  return messages;
215
222
  }
216
223
  /**
224
+ * The declaration a view source name stands for: the named table / view, or
225
+ * — for a `@db.alias` type — the aliased table / view. `undefined` when the
226
+ * name does not resolve (unknown, or an import that is not loaded yet).
227
+ * @since 0.1.141
228
+ */
229
+ function resolveViewSourceDecl(name, doc) {
230
+ const decl = doc.getDeclarationOwnerNode(name);
231
+ if (!decl?.node) return;
232
+ const aliasTarget = getAnnotationAlias(decl.node, "db.alias");
233
+ if (aliasTarget !== void 0) {
234
+ const target = decl.doc.getDeclarationOwnerNode(aliasTarget);
235
+ return target?.node ? {
236
+ doc: target.doc,
237
+ node: target.node
238
+ } : void 0;
239
+ }
240
+ return {
241
+ doc: decl.doc,
242
+ node: decl.node
243
+ };
244
+ }
245
+ /**
246
+ * VW9 — the dependency cycle a view closes, as the list of view names from
247
+ * the view back to itself (`["A", "B", "A"]`), or `undefined`. Walks
248
+ * `@db.view.for` / `@db.view.joins` of the referenced declarations across
249
+ * documents; `@db.alias` sources resolve to their target.
250
+ * @since 0.1.141
251
+ */
252
+ function findViewCycle(owner, doc) {
253
+ const start = owner.id;
254
+ if (!start) return;
255
+ const visited = /* @__PURE__ */ new Set();
256
+ const walk = (node, nodeDoc, path) => {
257
+ for (const name of viewScopeTypes(node)) {
258
+ const source = resolveViewSourceDecl(name, nodeDoc);
259
+ if (!source) continue;
260
+ if (source.node === owner) return [...path, start];
261
+ if (visited.has(source.node)) continue;
262
+ visited.add(source.node);
263
+ const cycle = walk(source.node, source.doc, [...path, source.node.id ?? name]);
264
+ if (cycle) return cycle;
265
+ }
266
+ };
267
+ return walk(owner, doc, [start]);
268
+ }
269
+ /**
217
270
  * Find all `@db.rel.FK` fields on a type that reference `targetTypeName`.
218
271
  * Resolves `extends` to include inherited fields.
219
272
  */
@@ -265,6 +318,16 @@ function joinTargets(joins) {
265
318
  return joins.map((a) => a.args[0]?.text).filter((t) => !!t);
266
319
  }
267
320
  /**
321
+ * The targets of the `@db.view.joins` declared before `join` among the
322
+ * view's `joins` ({@link viewJoins}) — what a chained join condition may
323
+ * reference besides its target and the entry.
324
+ * @since 0.1.141
325
+ */
326
+ function earlierJoinTargets(joins, join) {
327
+ const position = joins.indexOf(join);
328
+ return joinTargets(position === -1 ? [] : joins.slice(0, position));
329
+ }
330
+ /**
268
331
  * The type names a view predicate (`@db.view.filter`, a conditional
269
332
  * `@db.agg.*`) may reference: the `@db.view.for` entry table, then every
270
333
  * `@db.view.joins` target.
@@ -281,19 +344,23 @@ function hasAnyViewAnnotation(node) {
281
344
  return viewAnnotationNames.some((name) => node.countAnnotations(name) > 0);
282
345
  }
283
346
  /**
284
- * Validate that all type refs in a query expression are within the allowed scope.
347
+ * Validate that all field refs in a query expression are within `scope` —
348
+ * the same {@link TQueryScope} the editor completes (`lsp-scopes`):
349
+ * qualified refs must name one of `scope.allowedTypes`; an unqualified ref
350
+ * (a dotted path included) must be a field of `scope.unqualifiedTarget`, or
351
+ * is rejected outright when that is `null`.
285
352
  *
286
353
  * @param queryToken - The query arg token (must have .queryNode)
287
- * @param allowedTypes - Type names allowed as qualified refs
288
- * @param unqualifiedTarget - Type name for resolving unqualified refs, or null to disallow them
354
+ * @param scope - The scope of the argument
289
355
  * @param doc - The document for type lookups
290
356
  * @param scopeHint - Replaces the default "expected 'A' or 'B'" tail of the out-of-scope message
291
357
  */
292
- function validateQueryScope(queryToken, allowedTypes, unqualifiedTarget, doc, scopeHint) {
358
+ function validateQueryScope(queryToken, scope, doc, scopeHint) {
293
359
  const errors = [];
294
360
  const queryNode = queryToken.queryNode;
295
361
  if (!queryNode) return errors;
296
- function walkFieldRef(ref) {
362
+ const { allowedTypes, unqualifiedTarget } = scope;
363
+ forEachFieldRef(queryNode.expression, (ref) => {
297
364
  if (ref.typeRef) {
298
365
  const typeName = ref.typeRef.text;
299
366
  if (!allowedTypes.includes(typeName)) errors.push({
@@ -307,37 +374,53 @@ function validateQueryScope(queryToken, allowedTypes, unqualifiedTarget, doc, sc
307
374
  range: ref.fieldRef.range
308
375
  });
309
376
  else {
310
- const unwound = doc.unwindType(unqualifiedTarget);
311
- if (unwound) {
312
- const targetDef = unwound.def;
313
- if ((0, _atscript_core.isInterface)(targetDef) || (0, _atscript_core.isStructure)(targetDef)) {
314
- const struct = (0, _atscript_core.isInterface)(targetDef) ? targetDef.getDefinition() : targetDef;
315
- if (struct && (0, _atscript_core.isStructure)(struct) && !struct.props.has(ref.fieldRef.text)) errors.push({
316
- message: `Field '${ref.fieldRef.text}' does not exist on '${unqualifiedTarget}'`,
317
- severity: 1,
318
- range: ref.fieldRef.range
319
- });
320
- }
321
- }
322
- }
323
- }
324
- function walkExpr(expr) {
325
- if ((0, _atscript_core.isQueryLogical)(expr)) for (const operand of expr.operands) walkExpr(operand);
326
- else if ((0, _atscript_core.isQueryComparison)(expr)) {
327
- walkFieldRef(expr.left);
328
- if (expr.right && "fieldRef" in expr.right) walkFieldRef(expr.right);
377
+ const targetDef = doc.unwindType(unqualifiedTarget)?.def;
378
+ if (targetDef && ((0, _atscript_core.isInterface)(targetDef) || (0, _atscript_core.isStructure)(targetDef)) && !doc.unwindType(unqualifiedTarget, ref.fieldRef.text.split("."))) errors.push({
379
+ message: `Field '${ref.fieldRef.text}' does not exist on '${unqualifiedTarget}'`,
380
+ severity: 1,
381
+ range: ref.fieldRef.range
382
+ });
329
383
  }
330
- }
331
- walkExpr(queryNode.expression);
384
+ });
332
385
  return errors;
333
386
  }
387
+ /**
388
+ * Calls `fn` for every field reference of a query expression — both operands
389
+ * of a field-to-field comparison included.
390
+ * @since 0.1.141
391
+ */
392
+ function forEachFieldRef(expr, fn) {
393
+ if ((0, _atscript_core.isQueryLogical)(expr)) for (const operand of expr.operands) forEachFieldRef(operand, fn);
394
+ else if ((0, _atscript_core.isQueryComparison)(expr)) {
395
+ fn(expr.left);
396
+ if (expr.right && "fieldRef" in expr.right) fn(expr.right);
397
+ }
398
+ }
334
399
  //#endregion
400
+ Object.defineProperty(exports, "earlierJoinTargets", {
401
+ enumerable: true,
402
+ get: function() {
403
+ return earlierJoinTargets;
404
+ }
405
+ });
335
406
  Object.defineProperty(exports, "findFKFieldsPointingTo", {
336
407
  enumerable: true,
337
408
  get: function() {
338
409
  return findFKFieldsPointingTo;
339
410
  }
340
411
  });
412
+ Object.defineProperty(exports, "findViewCycle", {
413
+ enumerable: true,
414
+ get: function() {
415
+ return findViewCycle;
416
+ }
417
+ });
418
+ Object.defineProperty(exports, "forEachFieldRef", {
419
+ enumerable: true,
420
+ get: function() {
421
+ return forEachFieldRef;
422
+ }
423
+ });
341
424
  Object.defineProperty(exports, "getAnnotationAlias", {
342
425
  enumerable: true,
343
426
  get: function() {
@@ -374,6 +457,18 @@ Object.defineProperty(exports, "hasAnyViewAnnotation", {
374
457
  return hasAnyViewAnnotation;
375
458
  }
376
459
  });
460
+ Object.defineProperty(exports, "isAliasDecl", {
461
+ enumerable: true,
462
+ get: function() {
463
+ return isAliasDecl;
464
+ }
465
+ });
466
+ Object.defineProperty(exports, "isDbSourceDecl", {
467
+ enumerable: true,
468
+ get: function() {
469
+ return isDbSourceDecl;
470
+ }
471
+ });
377
472
  Object.defineProperty(exports, "joinTargets", {
378
473
  enumerable: true,
379
474
  get: function() {
@@ -78,16 +78,6 @@ type TDbPatch<T> = { [K in keyof T]?: T[K] extends Array<infer _> ? TArrayPatch<
78
78
  */
79
79
  declare function getKeyProps(def: TAtscriptAnnotatedType<TAtscriptTypeArray>): Set<string>;
80
80
  //#endregion
81
- //#region src/shared/object.d.ts
82
- /**
83
- * Plain object: prototype is `Object.prototype` or `null` — never a class
84
- * instance (`Date`, `Uint8Array`, `ObjectId`, …) and never an array.
85
- * Browser-safe; shared by the write paths, the HTTP shape gate and the client.
86
- */
87
- declare function isPlainObject(value: unknown): value is Record<string, unknown>;
88
- /** Zero-allocation emptiness check for a plain object (`{}` → `true`). */
89
- declare function isEmptyObject(obj: Record<string, unknown>): boolean;
90
- //#endregion
91
81
  //#region src/validator.d.ts
92
82
  /** Write operation mode for validator configuration. */
93
83
  type ValidatorMode = "insert" | "patch" | "replace";
@@ -148,4 +138,4 @@ interface ValidationContext {
148
138
  */
149
139
  declare function buildValidationContext(type: TAtscriptAnnotatedType<TAtscriptTypeObject>): ValidationContext;
150
140
  //#endregion
151
- export { DbErrorCode as _, buildValidationContext as a, isNavRelation as c, TArrayPatch as d, TDbPatch as f, DbError as g, CasMismatchError as h, buildPatchPartial as i, isEmptyObject as l, CasExhaustedError as m, ValidatorMode as n, dbPlugin as o, getKeyProps as p, buildDbValidator as r, forceNavNonOptional as s, ValidationContext as t, isPlainObject as u, bucketTimeZoneUnavailable as v };
141
+ export { buildValidationContext as a, isNavRelation as c, getKeyProps as d, CasExhaustedError as f, bucketTimeZoneUnavailable as g, DbErrorCode as h, buildPatchPartial as i, TArrayPatch as l, DbError as m, ValidatorMode as n, dbPlugin as o, CasMismatchError as p, buildDbValidator as r, forceNavNonOptional as s, ValidationContext as t, TDbPatch as u };
@@ -78,16 +78,6 @@ type TDbPatch<T> = { [K in keyof T]?: T[K] extends Array<infer _> ? TArrayPatch<
78
78
  */
79
79
  declare function getKeyProps(def: TAtscriptAnnotatedType<TAtscriptTypeArray>): Set<string>;
80
80
  //#endregion
81
- //#region src/shared/object.d.ts
82
- /**
83
- * Plain object: prototype is `Object.prototype` or `null` — never a class
84
- * instance (`Date`, `Uint8Array`, `ObjectId`, …) and never an array.
85
- * Browser-safe; shared by the write paths, the HTTP shape gate and the client.
86
- */
87
- declare function isPlainObject(value: unknown): value is Record<string, unknown>;
88
- /** Zero-allocation emptiness check for a plain object (`{}` → `true`). */
89
- declare function isEmptyObject(obj: Record<string, unknown>): boolean;
90
- //#endregion
91
81
  //#region src/validator.d.ts
92
82
  /** Write operation mode for validator configuration. */
93
83
  type ValidatorMode = "insert" | "patch" | "replace";
@@ -148,4 +138,4 @@ interface ValidationContext {
148
138
  */
149
139
  declare function buildValidationContext(type: TAtscriptAnnotatedType<TAtscriptTypeObject>): ValidationContext;
150
140
  //#endregion
151
- export { DbErrorCode as _, buildValidationContext as a, isNavRelation as c, TArrayPatch as d, TDbPatch as f, DbError as g, CasMismatchError as h, buildPatchPartial as i, isEmptyObject as l, CasExhaustedError as m, ValidatorMode as n, dbPlugin as o, getKeyProps as p, buildDbValidator as r, forceNavNonOptional as s, ValidationContext as t, isPlainObject as u, bucketTimeZoneUnavailable as v };
141
+ export { buildValidationContext as a, isNavRelation as c, getKeyProps as d, CasExhaustedError as f, bucketTimeZoneUnavailable as g, DbErrorCode as h, buildPatchPartial as i, TArrayPatch as l, DbError as m, ValidatorMode as n, dbPlugin as o, CasMismatchError as p, buildDbValidator as r, forceNavNonOptional as s, ValidationContext as t, TDbPatch as u };
@@ -17,6 +17,54 @@ function isEmptyObject(obj) {
17
17
  for (const _ in obj) return false;
18
18
  return true;
19
19
  }
20
+ /**
21
+ * Finds the nearest ancestor of `path` that belongs to `set`.
22
+ * Used by both the build pipeline (in `_classifyFields`) and
23
+ * runtime reconstruction on the Readable.
24
+ */
25
+ function findAncestorInSet(path, set) {
26
+ let pos = path.length;
27
+ while ((pos = path.lastIndexOf(".", pos - 1)) !== -1) {
28
+ const ancestor = path.slice(0, pos);
29
+ if (set.has(ancestor)) return ancestor;
30
+ }
31
+ }
32
+ /** `path` itself if it is in `set`, else its nearest ancestor in `set` ({@link findAncestorInSet}). */
33
+ function selfOrAncestor(path, set) {
34
+ return set.has(path) ? path : findAncestorInSet(path, set);
35
+ }
36
+ /** Splits a dot-path once; an already split path is used as-is. */
37
+ function segmentsOf(path) {
38
+ return typeof path === "string" ? path.split(".") : path;
39
+ }
40
+ /**
41
+ * Reads a dot-path (or its segments) off a nested plain object: `undefined`
42
+ * past a missing step, or a step that is not a plain object (arrays are not
43
+ * descended into — no positional indexing).
44
+ * @since 0.1.141
45
+ */
46
+ function getPath(row, path) {
47
+ let current = row;
48
+ for (const seg of segmentsOf(path)) {
49
+ if (current === null || typeof current !== "object" || Array.isArray(current)) return;
50
+ current = current[seg];
51
+ }
52
+ return current;
53
+ }
54
+ /**
55
+ * Deletes a dot-path (or its segments) from a nested plain object; a no-op
56
+ * when an intermediate step is missing or not a plain object.
57
+ * @since 0.1.141
58
+ */
59
+ function deletePath(target, path) {
60
+ const segments = segmentsOf(path);
61
+ let current = target;
62
+ for (let i = 0; i < segments.length - 1; i++) {
63
+ if (current === null || typeof current !== "object" || Array.isArray(current)) return;
64
+ current = current[segments[i]];
65
+ }
66
+ if (current !== null && typeof current === "object" && !Array.isArray(current)) delete current[segments[segments.length - 1]];
67
+ }
20
68
  //#endregion
21
69
  //#region src/patch/patch-types.ts
22
70
  /**
@@ -92,7 +140,7 @@ function createDbValidatorPlugin() {
92
140
  if (isTo || isFrom || isVia) return handleNavField(ctx, def, value, dbCtx, isTo, isFrom, isVia);
93
141
  if (value === void 0 && (dbCtx.mode === "insert" || dbCtx.mode === "replace")) {
94
142
  const meta = def.metadata;
95
- const serverManaged = meta.has("db.default") || meta.has("db.default.increment") || meta.has("db.default.uuid") || meta.has("db.default.now") || meta.has("db.column.version");
143
+ const serverManaged = meta.has("db.default") || meta.has("db.default.increment") || meta.has("db.default.uuid") || meta.has("db.default.now") || meta.has("db.column.version") || meta.has("db.column.derived");
96
144
  const hasFK = meta.has("db.rel.FK");
97
145
  if (serverManaged || hasFK) {
98
146
  if (dbCtx.mode === "replace" && meta.has("meta.id") && !isInsideNavField(ctx.path, dbCtx.navFields)) return;
@@ -101,6 +149,10 @@ function createDbValidatorPlugin() {
101
149
  }
102
150
  if (dbCtx.mode === "patch") {
103
151
  if (isDbFieldOp(value)) {
152
+ if (def.metadata.has("db.column.derived")) {
153
+ ctx.error("Field operations ($inc/$dec/$mul) are not allowed on a @db.column.derived field — it is computed from its @db.json source; patch the source leaf instead");
154
+ return false;
155
+ }
104
156
  if (dbCtx.flatMap && !isFieldOpAllowed(ctx.path, dbCtx.flatMap, dbCtx.navFields)) {
105
157
  ctx.error("Field operations ($inc/$dec/$mul) are not supported inside @db.json fields or nested objects without @db.patch.strategy \"merge\"");
106
158
  return false;
@@ -400,4 +452,4 @@ function buildValidationContext(type) {
400
452
  };
401
453
  }
402
454
  //#endregion
403
- export { forceNavNonOptional as a, getKeyProps as c, dbPlugin as i, isEmptyObject as l, buildPatchPartial as n, isNavRelation as o, buildValidationContext as r, createDbValidatorPlugin as s, buildDbValidator as t, isPlainObject as u };
455
+ export { forceNavNonOptional as a, getKeyProps as c, getPath as d, isEmptyObject as f, dbPlugin as i, deletePath as l, selfOrAncestor as m, buildPatchPartial as n, isNavRelation as o, isPlainObject as p, buildValidationContext as r, createDbValidatorPlugin as s, buildDbValidator as t, findAncestorInSet as u };
@@ -17,6 +17,54 @@ function isEmptyObject(obj) {
17
17
  for (const _ in obj) return false;
18
18
  return true;
19
19
  }
20
+ /**
21
+ * Finds the nearest ancestor of `path` that belongs to `set`.
22
+ * Used by both the build pipeline (in `_classifyFields`) and
23
+ * runtime reconstruction on the Readable.
24
+ */
25
+ function findAncestorInSet(path, set) {
26
+ let pos = path.length;
27
+ while ((pos = path.lastIndexOf(".", pos - 1)) !== -1) {
28
+ const ancestor = path.slice(0, pos);
29
+ if (set.has(ancestor)) return ancestor;
30
+ }
31
+ }
32
+ /** `path` itself if it is in `set`, else its nearest ancestor in `set` ({@link findAncestorInSet}). */
33
+ function selfOrAncestor(path, set) {
34
+ return set.has(path) ? path : findAncestorInSet(path, set);
35
+ }
36
+ /** Splits a dot-path once; an already split path is used as-is. */
37
+ function segmentsOf(path) {
38
+ return typeof path === "string" ? path.split(".") : path;
39
+ }
40
+ /**
41
+ * Reads a dot-path (or its segments) off a nested plain object: `undefined`
42
+ * past a missing step, or a step that is not a plain object (arrays are not
43
+ * descended into — no positional indexing).
44
+ * @since 0.1.141
45
+ */
46
+ function getPath(row, path) {
47
+ let current = row;
48
+ for (const seg of segmentsOf(path)) {
49
+ if (current === null || typeof current !== "object" || Array.isArray(current)) return;
50
+ current = current[seg];
51
+ }
52
+ return current;
53
+ }
54
+ /**
55
+ * Deletes a dot-path (or its segments) from a nested plain object; a no-op
56
+ * when an intermediate step is missing or not a plain object.
57
+ * @since 0.1.141
58
+ */
59
+ function deletePath(target, path) {
60
+ const segments = segmentsOf(path);
61
+ let current = target;
62
+ for (let i = 0; i < segments.length - 1; i++) {
63
+ if (current === null || typeof current !== "object" || Array.isArray(current)) return;
64
+ current = current[segments[i]];
65
+ }
66
+ if (current !== null && typeof current === "object" && !Array.isArray(current)) delete current[segments[segments.length - 1]];
67
+ }
20
68
  //#endregion
21
69
  //#region src/patch/patch-types.ts
22
70
  /**
@@ -92,7 +140,7 @@ function createDbValidatorPlugin() {
92
140
  if (isTo || isFrom || isVia) return handleNavField(ctx, def, value, dbCtx, isTo, isFrom, isVia);
93
141
  if (value === void 0 && (dbCtx.mode === "insert" || dbCtx.mode === "replace")) {
94
142
  const meta = def.metadata;
95
- const serverManaged = meta.has("db.default") || meta.has("db.default.increment") || meta.has("db.default.uuid") || meta.has("db.default.now") || meta.has("db.column.version");
143
+ const serverManaged = meta.has("db.default") || meta.has("db.default.increment") || meta.has("db.default.uuid") || meta.has("db.default.now") || meta.has("db.column.version") || meta.has("db.column.derived");
96
144
  const hasFK = meta.has("db.rel.FK");
97
145
  if (serverManaged || hasFK) {
98
146
  if (dbCtx.mode === "replace" && meta.has("meta.id") && !isInsideNavField(ctx.path, dbCtx.navFields)) return;
@@ -101,6 +149,10 @@ function createDbValidatorPlugin() {
101
149
  }
102
150
  if (dbCtx.mode === "patch") {
103
151
  if (require_ops.isDbFieldOp(value)) {
152
+ if (def.metadata.has("db.column.derived")) {
153
+ ctx.error("Field operations ($inc/$dec/$mul) are not allowed on a @db.column.derived field — it is computed from its @db.json source; patch the source leaf instead");
154
+ return false;
155
+ }
104
156
  if (dbCtx.flatMap && !isFieldOpAllowed(ctx.path, dbCtx.flatMap, dbCtx.navFields)) {
105
157
  ctx.error("Field operations ($inc/$dec/$mul) are not supported inside @db.json fields or nested objects without @db.patch.strategy \"merge\"");
106
158
  return false;
@@ -430,6 +482,18 @@ Object.defineProperty(exports, "dbPlugin", {
430
482
  return dbPlugin;
431
483
  }
432
484
  });
485
+ Object.defineProperty(exports, "deletePath", {
486
+ enumerable: true,
487
+ get: function() {
488
+ return deletePath;
489
+ }
490
+ });
491
+ Object.defineProperty(exports, "findAncestorInSet", {
492
+ enumerable: true,
493
+ get: function() {
494
+ return findAncestorInSet;
495
+ }
496
+ });
433
497
  Object.defineProperty(exports, "forceNavNonOptional", {
434
498
  enumerable: true,
435
499
  get: function() {
@@ -442,6 +506,12 @@ Object.defineProperty(exports, "getKeyProps", {
442
506
  return getKeyProps;
443
507
  }
444
508
  });
509
+ Object.defineProperty(exports, "getPath", {
510
+ enumerable: true,
511
+ get: function() {
512
+ return getPath;
513
+ }
514
+ });
445
515
  Object.defineProperty(exports, "isEmptyObject", {
446
516
  enumerable: true,
447
517
  get: function() {
@@ -460,3 +530,9 @@ Object.defineProperty(exports, "isPlainObject", {
460
530
  return isPlainObject;
461
531
  }
462
532
  });
533
+ Object.defineProperty(exports, "selfOrAncestor", {
534
+ enumerable: true,
535
+ get: function() {
536
+ return selfOrAncestor;
537
+ }
538
+ });
@@ -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_validator = require("./validator-BtZbcLN2.cjs");
3
+ const require_validator = require("./validator-DASnXf1j.cjs");
4
4
  const require_ops = require("./ops.cjs");
5
5
  exports.$cas = require_ops.$cas;
6
6
  exports.$dec = require_ops.$dec;
@@ -1,4 +1,5 @@
1
1
  import { a as $mul, c as $update, d as TDbFieldOp, g as separateCas, h as reconcileCas, i as $insert, l as $upsert, m as isDbFieldOp, n as $dec, o as $remove, r as $inc, s as $replace, t as $cas, u as TDbCas } from "./ops-AqhV7s9o.cjs";
2
- import { a as buildValidationContext, c as isNavRelation, d as TArrayPatch, f as TDbPatch, g as DbError, i as buildPatchPartial, l as isEmptyObject, n as ValidatorMode, o as dbPlugin, p as getKeyProps, r as buildDbValidator, s as forceNavNonOptional, t as ValidationContext, u as isPlainObject } from "./validator-Drb2N-YL.cjs";
2
+ import { a as isPlainObject, i as isEmptyObject } from "./object-DSN0h9lB.cjs";
3
+ import { a as buildValidationContext, c as isNavRelation, d as getKeyProps, i as buildPatchPartial, l as TArrayPatch, m as DbError, n as ValidatorMode, o as dbPlugin, r as buildDbValidator, s as forceNavNonOptional, t as ValidationContext, u as TDbPatch } from "./validator-Bw6ks9Hy.cjs";
3
4
  import { n as createDbValidatorPlugin, t as DbValidationContext } from "./db-validator-plugin-BWy60OvG.cjs";
4
5
  export { $cas, $dec, $inc, $insert, $mul, $remove, $replace, $update, $upsert, DbError, type DbValidationContext, type TArrayPatch, type TDbCas, type TDbFieldOp, type TDbPatch, ValidationContext, ValidatorMode, buildDbValidator, buildPatchPartial, buildValidationContext, createDbValidatorPlugin, dbPlugin, forceNavNonOptional, getKeyProps, isDbFieldOp, isEmptyObject, isNavRelation, isPlainObject, reconcileCas, separateCas };