@maroonedog/luq 2.4.2 → 2.4.3

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.
@@ -1,4 +1,5 @@
1
1
  export declare class IndexStack {
2
+ private readonly basePath;
2
3
  private readonly nodePaths;
3
4
  private readonly openIndices;
4
5
  /**
@@ -10,6 +11,11 @@ export declare class IndexStack {
10
11
  * raises no issue never renders its own prefix at all.
11
12
  */
12
13
  private currentPrefix;
14
+ /**
15
+ * Where this run sits inside the whole value: `""` for the run the caller
16
+ * started, and the entering rule's path for a branch or a recursive re-entry.
17
+ */
18
+ constructor(basePath?: string);
13
19
  /** How many array levels are currently open. */
14
20
  get depth(): number;
15
21
  /** The open indices, outermost first: `[0, 2]` inside `grid[0][2]`. */
@@ -25,7 +31,10 @@ export declare class IndexStack {
25
31
  * it took 12% of the array shape's self time.
26
32
  */
27
33
  private readTop;
28
- /** `""` at the root, `items[0]` inside the first element of `items`. */
34
+ /**
35
+ * `""` at the root of the run the caller started, `items[0]` inside the first
36
+ * element of `items`, and the base path when a nested run has no array open.
37
+ */
29
38
  get prefix(): string;
30
39
  /**
31
40
  * Enters element `index` of an array node. `nodePath` is the node's own path
@@ -25,12 +25,25 @@
25
25
  // stack lives for one validate() call, and 10k elements must not allocate 10k
26
26
  // index arrays. It is also why FieldRuleContext remembers the path it built —
27
27
  // this stack has moved on by the time a retained context could read it.
28
+ //
29
+ // A NESTED run starts from a BASE PATH rather than from the root. A branch plan
30
+ // and a recursive re-entry each declare their fields against their own subject,
31
+ // so a field renders `name` where the consumer must read `user.name`. The base
32
+ // is handed to the stack, so the path is right the first time it is built — and
33
+ // the message, which is rendered from that same path, names the field the issue
34
+ // reports. Rewriting the path afterwards cannot do that: by then the message has
35
+ // already been rendered against the short one.
28
36
  // ===========================================================================
29
37
  Object.defineProperty(exports, "__esModule", { value: true });
30
38
  exports.IndexStack = void 0;
31
39
  exports.joinIssuePath = joinIssuePath;
32
40
  class IndexStack {
33
- constructor() {
41
+ /**
42
+ * Where this run sits inside the whole value: `""` for the run the caller
43
+ * started, and the entering rule's path for a branch or a recursive re-entry.
44
+ */
45
+ constructor(basePath = "") {
46
+ this.basePath = basePath;
34
47
  this.nodePaths = [];
35
48
  this.openIndices = [];
36
49
  /**
@@ -65,7 +78,7 @@ class IndexStack {
65
78
  const already = this.currentPrefix;
66
79
  if (already !== null)
67
80
  return already;
68
- let built = "";
81
+ let built = this.basePath;
69
82
  for (let i = 0; i < this.nodePaths.length; i += 1) {
70
83
  const nodePath = this.nodePaths[i];
71
84
  const index = this.openIndices[i];
@@ -76,7 +89,10 @@ class IndexStack {
76
89
  this.currentPrefix = built;
77
90
  return built;
78
91
  }
79
- /** `""` at the root, `items[0]` inside the first element of `items`. */
92
+ /**
93
+ * `""` at the root of the run the caller started, `items[0]` inside the first
94
+ * element of `items`, and the base path when a nested run has no array open.
95
+ */
80
96
  get prefix() {
81
97
  return this.readTop();
82
98
  }
@@ -24,9 +24,22 @@
24
24
  // stack lives for one validate() call, and 10k elements must not allocate 10k
25
25
  // index arrays. It is also why FieldRuleContext remembers the path it built —
26
26
  // this stack has moved on by the time a retained context could read it.
27
+ //
28
+ // A NESTED run starts from a BASE PATH rather than from the root. A branch plan
29
+ // and a recursive re-entry each declare their fields against their own subject,
30
+ // so a field renders `name` where the consumer must read `user.name`. The base
31
+ // is handed to the stack, so the path is right the first time it is built — and
32
+ // the message, which is rendered from that same path, names the field the issue
33
+ // reports. Rewriting the path afterwards cannot do that: by then the message has
34
+ // already been rendered against the short one.
27
35
  // ===========================================================================
28
36
  export class IndexStack {
29
- constructor() {
37
+ /**
38
+ * Where this run sits inside the whole value: `""` for the run the caller
39
+ * started, and the entering rule's path for a branch or a recursive re-entry.
40
+ */
41
+ constructor(basePath = "") {
42
+ this.basePath = basePath;
30
43
  this.nodePaths = [];
31
44
  this.openIndices = [];
32
45
  /**
@@ -61,7 +74,7 @@ export class IndexStack {
61
74
  const already = this.currentPrefix;
62
75
  if (already !== null)
63
76
  return already;
64
- let built = "";
77
+ let built = this.basePath;
65
78
  for (let i = 0; i < this.nodePaths.length; i += 1) {
66
79
  const nodePath = this.nodePaths[i];
67
80
  const index = this.openIndices[i];
@@ -72,7 +85,10 @@ export class IndexStack {
72
85
  this.currentPrefix = built;
73
86
  return built;
74
87
  }
75
- /** `""` at the root, `items[0]` inside the first element of `items`. */
88
+ /**
89
+ * `""` at the root of the run the caller started, `items[0]` inside the first
90
+ * element of `items`, and the base path when a nested run has no array open.
91
+ */
76
92
  get prefix() {
77
93
  return this.readTop();
78
94
  }
@@ -9,7 +9,7 @@ export type { FieldRunContext, FieldRunOutcome, RecursionRunner, } from "./run-f
9
9
  export { NO_WRITE_TARGETS, createArrayWriteTargets, createPlanWriteTargets, replaceElement, writeFieldValue, } from "./output-writer";
10
10
  export type { ArrayWriteTarget } from "./output-writer";
11
11
  export { runArrayNodes } from "./run-array-node";
12
- export { prefixIssuePaths, runPlan } from "./run-plan";
12
+ export { runPlan } from "./run-plan";
13
13
  export { RECURSION_ABORT_POLICY, createRecursionRunner } from "./run-recursion";
14
14
  export type { RecursionHost } from "./run-recursion";
15
15
  export { BRANCH_ABORT_POLICY, createBranchExecutor } from "./run-branch";
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createFieldValidator = exports.hasRejectingIssue = exports.createValidator = exports.ROOT_MISSING_CODE = exports.createBranchExecutor = exports.BRANCH_ABORT_POLICY = exports.createRecursionRunner = exports.RECURSION_ABORT_POLICY = exports.runPlan = exports.prefixIssuePaths = exports.runArrayNodes = exports.writeFieldValue = exports.replaceElement = exports.createPlanWriteTargets = exports.createArrayWriteTargets = exports.NO_WRITE_TARGETS = exports.runField = exports.FIELD_VALUE_UNCHANGED = exports.decidePresence = exports.renderFallbackMessage = exports.createIssue = exports.joinIssuePath = exports.IndexStack = exports.resolveAbortPolicy = exports.IssueSink = exports.DEFAULT_ABORT_EARLY_ON_EACH_FIELD = exports.DEFAULT_ABORT_EARLY = exports.ARRAY_ELEMENTS_ABORT_ON_EACH_FIELD = void 0;
3
+ exports.createFieldValidator = exports.hasRejectingIssue = exports.createValidator = exports.ROOT_MISSING_CODE = exports.createBranchExecutor = exports.BRANCH_ABORT_POLICY = exports.createRecursionRunner = exports.RECURSION_ABORT_POLICY = exports.runPlan = exports.runArrayNodes = exports.writeFieldValue = exports.replaceElement = exports.createPlanWriteTargets = exports.createArrayWriteTargets = exports.NO_WRITE_TARGETS = exports.runField = exports.FIELD_VALUE_UNCHANGED = exports.decidePresence = exports.renderFallbackMessage = exports.createIssue = exports.joinIssuePath = exports.IndexStack = exports.resolveAbortPolicy = exports.IssueSink = exports.DEFAULT_ABORT_EARLY_ON_EACH_FIELD = exports.DEFAULT_ABORT_EARLY = exports.ARRAY_ELEMENTS_ABORT_ON_EACH_FIELD = void 0;
4
4
  // ===========================================================================
5
5
  // L5 src/runtime/index.ts — re-exports only. The engine: one plan in, issues
6
6
  // and (for parse) a copy-on-write output out. Nothing is defined in this file.
@@ -31,7 +31,6 @@ Object.defineProperty(exports, "writeFieldValue", { enumerable: true, get: funct
31
31
  var run_array_node_1 = require("./run-array-node");
32
32
  Object.defineProperty(exports, "runArrayNodes", { enumerable: true, get: function () { return run_array_node_1.runArrayNodes; } });
33
33
  var run_plan_1 = require("./run-plan");
34
- Object.defineProperty(exports, "prefixIssuePaths", { enumerable: true, get: function () { return run_plan_1.prefixIssuePaths; } });
35
34
  Object.defineProperty(exports, "runPlan", { enumerable: true, get: function () { return run_plan_1.runPlan; } });
36
35
  var run_recursion_1 = require("./run-recursion");
37
36
  Object.defineProperty(exports, "RECURSION_ABORT_POLICY", { enumerable: true, get: function () { return run_recursion_1.RECURSION_ABORT_POLICY; } });
@@ -9,7 +9,7 @@ export { decidePresence } from "./decide-presence.mjs";
9
9
  export { FIELD_VALUE_UNCHANGED, runField } from "./run-field.mjs";
10
10
  export { NO_WRITE_TARGETS, createArrayWriteTargets, createPlanWriteTargets, replaceElement, writeFieldValue, } from "./output-writer.mjs";
11
11
  export { runArrayNodes } from "./run-array-node.mjs";
12
- export { prefixIssuePaths, runPlan } from "./run-plan.mjs";
12
+ export { runPlan } from "./run-plan.mjs";
13
13
  export { RECURSION_ABORT_POLICY, createRecursionRunner } from "./run-recursion.mjs";
14
14
  export { BRANCH_ABORT_POLICY, createBranchExecutor } from "./run-branch.mjs";
15
15
  export { ROOT_MISSING_CODE, createValidator, hasRejectingIssue, } from "./create-validator.mjs";
@@ -48,7 +48,13 @@ function createBranchExecutor() {
48
48
  (0, run_plan_1.runPlan)(plan, value, {
49
49
  root: ruleContext.root,
50
50
  sink,
51
- indices: new index_stack_1.IndexStack(),
51
+ // The branch plan's fields are declared against the branch's own
52
+ // subject, so a field of it renders `name` while the consumer must read
53
+ // `user.name`. Starting the stack at the composite's path is what makes
54
+ // the cause's message agree with the cause's path: both come from here,
55
+ // and a message rendered against the short path could not be corrected
56
+ // afterwards.
57
+ indices: new index_stack_1.IndexStack(ruleContext.path),
52
58
  shouldApplyTransforms: false,
53
59
  runRecursion: (0, run_recursion_1.createRecursionRunner)({
54
60
  root: ruleContext.root,
@@ -60,8 +66,7 @@ function createBranchExecutor() {
60
66
  });
61
67
  if (sink.count === 0)
62
68
  return types_1.PASS;
63
- const causes = (0, run_plan_1.prefixIssuePaths)(ruleContext.path, sink.issues);
64
- return (0, types_1.fail)({ causes: Object.freeze(causes) });
69
+ return (0, types_1.fail)({ causes: Object.freeze(sink.issues) });
65
70
  },
66
71
  };
67
72
  }
@@ -22,7 +22,7 @@ import { PASS, fail } from "../types/index.mjs";
22
22
  import { IndexStack } from "./index-stack.mjs";
23
23
  import { IssueSink } from "./issue-sink.mjs";
24
24
  import { createRecursionRunner } from "./run-recursion.mjs";
25
- import { prefixIssuePaths, runPlan } from "./run-plan.mjs";
25
+ import { runPlan } from "./run-plan.mjs";
26
26
  /**
27
27
  * A branch is a decision, not a report: the first failure already answers it,
28
28
  * so both aborts stay on. Collecting every issue of every alternative of a
@@ -44,7 +44,13 @@ export function createBranchExecutor() {
44
44
  runPlan(plan, value, {
45
45
  root: ruleContext.root,
46
46
  sink,
47
- indices: new IndexStack(),
47
+ // The branch plan's fields are declared against the branch's own
48
+ // subject, so a field of it renders `name` while the consumer must read
49
+ // `user.name`. Starting the stack at the composite's path is what makes
50
+ // the cause's message agree with the cause's path: both come from here,
51
+ // and a message rendered against the short path could not be corrected
52
+ // afterwards.
53
+ indices: new IndexStack(ruleContext.path),
48
54
  shouldApplyTransforms: false,
49
55
  runRecursion: createRecursionRunner({
50
56
  root: ruleContext.root,
@@ -56,8 +62,7 @@ export function createBranchExecutor() {
56
62
  });
57
63
  if (sink.count === 0)
58
64
  return PASS;
59
- const causes = prefixIssuePaths(ruleContext.path, sink.issues);
60
- return fail({ causes: Object.freeze(causes) });
65
+ return fail({ causes: Object.freeze(sink.issues) });
61
66
  },
62
67
  };
63
68
  }
@@ -1,4 +1,3 @@
1
- import type { ValidationIssue } from "../types";
2
1
  import type { ValidationPlan } from "../compile/validation-plan.types";
3
2
  import type { FieldRunContext } from "./run-field";
4
3
  import type { ArrayWriteTarget } from "./output-writer";
@@ -15,14 +14,9 @@ import type { ArrayWriteTarget } from "./output-writer";
15
14
  */
16
15
  export declare function runPlan(plan: ValidationPlan, subject: unknown, context: FieldRunContext, targets?: readonly ArrayWriteTarget[]): unknown;
17
16
  /**
18
- * Re-bases the issues of a NESTED plan onto the path of the rule that entered
19
- * it. A branch's fields and a recursive re-entry's fields are both declared
20
- * relative to their own subject, so their issues come back as `name`; the
21
- * enclosing rule knows the subject sits at `user`, and the consumer must read
22
- * `user.name`.
23
- *
24
- * It lives here because both callers already depend on this module and neither
25
- * may depend on the other — run-branch creates a recursion runner, so
26
- * run-recursion could not own it without a cycle.
17
+ * A nested plan does NOT come back to have its issue paths corrected. A branch
18
+ * and a recursive re-entry each start their IndexStack at the entering rule's
19
+ * path, so every issue is built at its full path and its message is rendered
20
+ * from that same path. Rewriting the path afterwards used to leave the message
21
+ * naming the field's short name, so one issue reported two different fields.
27
22
  */
28
- export declare function prefixIssuePaths(prefix: string, issues: readonly ValidationIssue[]): readonly ValidationIssue[];
@@ -1,8 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.runPlan = runPlan;
4
- exports.prefixIssuePaths = prefixIssuePaths;
5
- const index_stack_1 = require("./index-stack");
6
4
  const run_array_node_1 = require("./run-array-node");
7
5
  const run_field_1 = require("./run-field");
8
6
  const output_writer_1 = require("./output-writer");
@@ -32,23 +30,9 @@ function runPlan(plan, subject, context, targets = output_writer_1.NO_WRITE_TARG
32
30
  return (0, run_array_node_1.runArrayNodes)(plan.arrays, current, context, targets);
33
31
  }
34
32
  /**
35
- * Re-bases the issues of a NESTED plan onto the path of the rule that entered
36
- * it. A branch's fields and a recursive re-entry's fields are both declared
37
- * relative to their own subject, so their issues come back as `name`; the
38
- * enclosing rule knows the subject sits at `user`, and the consumer must read
39
- * `user.name`.
40
- *
41
- * It lives here because both callers already depend on this module and neither
42
- * may depend on the other — run-branch creates a recursion runner, so
43
- * run-recursion could not own it without a cycle.
33
+ * A nested plan does NOT come back to have its issue paths corrected. A branch
34
+ * and a recursive re-entry each start their IndexStack at the entering rule's
35
+ * path, so every issue is built at its full path and its message is rendered
36
+ * from that same path. Rewriting the path afterwards used to leave the message
37
+ * naming the field's short name, so one issue reported two different fields.
44
38
  */
45
- function prefixIssuePaths(prefix, issues) {
46
- if (prefix === "")
47
- return issues;
48
- return issues.map((issue) => Object.freeze({
49
- path: (0, index_stack_1.joinIssuePath)(prefix, issue.path),
50
- code: issue.code,
51
- message: issue.message,
52
- severity: issue.severity,
53
- }));
54
- }
@@ -1,4 +1,3 @@
1
- import { joinIssuePath } from "./index-stack.mjs";
2
1
  import { runArrayNodes } from "./run-array-node.mjs";
3
2
  import { runField } from "./run-field.mjs";
4
3
  import { NO_WRITE_TARGETS, writeFieldValue } from "./output-writer.mjs";
@@ -28,23 +27,9 @@ export function runPlan(plan, subject, context, targets = NO_WRITE_TARGETS) {
28
27
  return runArrayNodes(plan.arrays, current, context, targets);
29
28
  }
30
29
  /**
31
- * Re-bases the issues of a NESTED plan onto the path of the rule that entered
32
- * it. A branch's fields and a recursive re-entry's fields are both declared
33
- * relative to their own subject, so their issues come back as `name`; the
34
- * enclosing rule knows the subject sits at `user`, and the consumer must read
35
- * `user.name`.
36
- *
37
- * It lives here because both callers already depend on this module and neither
38
- * may depend on the other — run-branch creates a recursion runner, so
39
- * run-recursion could not own it without a cycle.
30
+ * A nested plan does NOT come back to have its issue paths corrected. A branch
31
+ * and a recursive re-entry each start their IndexStack at the entering rule's
32
+ * path, so every issue is built at its full path and its message is rendered
33
+ * from that same path. Rewriting the path afterwards used to leave the message
34
+ * naming the field's short name, so one issue reported two different fields.
40
35
  */
41
- export function prefixIssuePaths(prefix, issues) {
42
- if (prefix === "")
43
- return issues;
44
- return issues.map((issue) => Object.freeze({
45
- path: joinIssuePath(prefix, issue.path),
46
- code: issue.code,
47
- message: issue.message,
48
- severity: issue.severity,
49
- }));
50
- }
@@ -15,11 +15,13 @@ exports.createRecursionRunner = createRecursionRunner;
15
15
  // Legacy truncated at maxDepth as a SUCCESS, which meant a structure deeper
16
16
  // than the limit silently skipped every rule below it.
17
17
  //
18
- // The nested plan runs into its OWN sink and its OWN index stack, and its
19
- // issues are re-based onto the entering field's path afterwards. That is what
20
- // makes `node.child.name` come out right: the plan's fields are declared
21
- // relative to the plan's subject and know nothing about where they were
22
- // re-entered from.
18
+ // The nested plan runs into its OWN sink, and its OWN index stack STARTED AT
19
+ // the entering field's path. That is what makes `node.child.name` come out
20
+ // right: the plan's fields are declared relative to the plan's subject and know
21
+ // nothing about where they were re-entered from, so the stack is what carries
22
+ // the descent. Giving it the path up front rather than rewriting the issues
23
+ // afterwards is also what makes a message agree with the path beside it — a
24
+ // message is rendered once, from that path, at the moment the issue is built.
23
25
  //
24
26
  // A re-entry never writes. RecursionRunner returns void by contract, so there
25
27
  // is nowhere for a transformed subtree to go; recursion is validation, and
@@ -88,12 +90,12 @@ function bindRecursionRunner(host, progress) {
88
90
  (0, run_plan_1.runPlan)(policy.plan.resolve(), value, {
89
91
  root: host.root,
90
92
  sink: nested,
91
- indices: new index_stack_1.IndexStack(),
93
+ indices: new index_stack_1.IndexStack(path),
92
94
  shouldApplyTransforms: false,
93
95
  runRecursion: bindRecursionRunner({ ...host, sink: nested }, progress),
94
96
  external: host.external,
95
97
  });
96
- for (const issue of (0, run_plan_1.prefixIssuePaths)(path, nested.issues)) {
98
+ for (const issue of nested.issues) {
97
99
  host.sink.add(issue);
98
100
  }
99
101
  }
@@ -11,11 +11,13 @@
11
11
  // Legacy truncated at maxDepth as a SUCCESS, which meant a structure deeper
12
12
  // than the limit silently skipped every rule below it.
13
13
  //
14
- // The nested plan runs into its OWN sink and its OWN index stack, and its
15
- // issues are re-based onto the entering field's path afterwards. That is what
16
- // makes `node.child.name` come out right: the plan's fields are declared
17
- // relative to the plan's subject and know nothing about where they were
18
- // re-entered from.
14
+ // The nested plan runs into its OWN sink, and its OWN index stack STARTED AT
15
+ // the entering field's path. That is what makes `node.child.name` come out
16
+ // right: the plan's fields are declared relative to the plan's subject and know
17
+ // nothing about where they were re-entered from, so the stack is what carries
18
+ // the descent. Giving it the path up front rather than rewriting the issues
19
+ // afterwards is also what makes a message agree with the path beside it — a
20
+ // message is rendered once, from that path, at the moment the issue is built.
19
21
  //
20
22
  // A re-entry never writes. RecursionRunner returns void by contract, so there
21
23
  // is nowhere for a transformed subtree to go; recursion is validation, and
@@ -25,7 +27,7 @@ import { isArray } from "../types/index.mjs";
25
27
  import { createIssue } from "./create-issue.mjs";
26
28
  import { IndexStack } from "./index-stack.mjs";
27
29
  import { IssueSink } from "./issue-sink.mjs";
28
- import { prefixIssuePaths, runPlan } from "./run-plan.mjs";
30
+ import { runPlan } from "./run-plan.mjs";
29
31
  /**
30
32
  * Legacy contract, kept: fields do not abort each other inside a recursive
31
33
  * re-entry. A descent that stopped at its first issue would report one leaf of
@@ -84,12 +86,12 @@ function bindRecursionRunner(host, progress) {
84
86
  runPlan(policy.plan.resolve(), value, {
85
87
  root: host.root,
86
88
  sink: nested,
87
- indices: new IndexStack(),
89
+ indices: new IndexStack(path),
88
90
  shouldApplyTransforms: false,
89
91
  runRecursion: bindRecursionRunner({ ...host, sink: nested }, progress),
90
92
  external: host.external,
91
93
  });
92
- for (const issue of prefixIssuePaths(path, nested.issues)) {
94
+ for (const issue of nested.issues) {
93
95
  host.sink.add(issue);
94
96
  }
95
97
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maroonedog/luq",
3
- "version": "2.4.2",
3
+ "version": "2.4.3",
4
4
  "description": "Universal Model & API Definition Platform - TypeScript validation library evolving into cross-language code generation",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",