@maroonedog/luq 2.2.0 → 2.3.1

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 (144) hide show
  1. package/README.md +47 -602
  2. package/dist/builder/compile-declarations.d.ts +7 -1
  3. package/dist/builder/compile-declarations.js +18 -8
  4. package/dist/builder/compile-declarations.mjs +18 -8
  5. package/dist/builder/create-builder.js +2 -3
  6. package/dist/builder/create-builder.mjs +2 -3
  7. package/dist/builder/create-field-builder.js +13 -1
  8. package/dist/builder/create-field-builder.mjs +13 -1
  9. package/dist/builder/declared-calls-store.d.ts +9 -0
  10. package/dist/builder/declared-calls-store.js +19 -0
  11. package/dist/builder/declared-calls-store.mjs +15 -0
  12. package/dist/builder/field-declared-calls.types.d.ts +6 -0
  13. package/dist/builder/field-declared-calls.types.js +2 -0
  14. package/dist/builder/field-declared-calls.types.mjs +1 -0
  15. package/dist/builder/field-entry.types.d.ts +9 -3
  16. package/dist/builder/field-options.types.d.ts +26 -0
  17. package/dist/chain/bundle-paths.types.d.ts +9 -12
  18. package/dist/chain/chain-node-store.d.ts +5 -0
  19. package/dist/chain/chain-node-store.js +15 -0
  20. package/dist/chain/chain-node-store.mjs +11 -0
  21. package/dist/chain/collect-field-rules.d.ts +13 -2
  22. package/dist/chain/collect-field-rules.js +10 -3
  23. package/dist/chain/collect-field-rules.mjs +10 -3
  24. package/dist/chain/create-chain-node.d.ts +10 -0
  25. package/dist/chain/create-chain-node.js +19 -9
  26. package/dist/chain/create-chain-node.mjs +19 -9
  27. package/dist/chain/create-field-slots.js +9 -1
  28. package/dist/chain/create-field-slots.mjs +9 -1
  29. package/dist/chain/declaration-recorder.port.d.ts +31 -0
  30. package/dist/chain/declaration-recorder.port.js +17 -0
  31. package/dist/chain/declaration-recorder.port.mjs +13 -0
  32. package/dist/chain/declared-call.types.d.ts +15 -0
  33. package/dist/chain/declared-call.types.js +2 -0
  34. package/dist/chain/declared-call.types.mjs +1 -0
  35. package/dist/chain/index.d.ts +3 -1
  36. package/dist/chain/slot-type-guard.d.ts +10 -0
  37. package/dist/chain/slot-type-guard.js +37 -0
  38. package/dist/chain/slot-type-guard.mjs +34 -0
  39. package/dist/compile/compile-array-node.js +1 -0
  40. package/dist/compile/compile-array-node.mjs +1 -0
  41. package/dist/compile/compile-field.d.ts +1 -0
  42. package/dist/compile/compile-field.js +8 -6
  43. package/dist/compile/compile-field.mjs +8 -6
  44. package/dist/compile/compile-schema.js +4 -0
  45. package/dist/compile/compile-schema.mjs +4 -0
  46. package/dist/compile/group-array-fields.d.ts +1 -0
  47. package/dist/compile/split-rules-by-kind.js +10 -14
  48. package/dist/compile/split-rules-by-kind.mjs +10 -14
  49. package/dist/compile/validation-plan.types.d.ts +19 -6
  50. package/dist/core/type-erasure.d.ts +36 -30
  51. package/dist/core/type-erasure.js +36 -30
  52. package/dist/core/type-erasure.mjs +36 -30
  53. package/dist/json-schema/build-from-schema.js +8 -1
  54. package/dist/json-schema/build-from-schema.mjs +8 -1
  55. package/dist/json-schema/create-structural-context.js +3 -0
  56. package/dist/json-schema/create-structural-context.mjs +3 -0
  57. package/dist/json-schema/declare-additional-properties.d.ts +7 -7
  58. package/dist/json-schema/declare-additional-properties.js +7 -7
  59. package/dist/json-schema/declare-additional-properties.mjs +7 -7
  60. package/dist/json-schema/declare-object-keywords.js +4 -4
  61. package/dist/json-schema/declare-object-keywords.mjs +4 -4
  62. package/dist/json-schema/follow-json-pointer.d.ts +7 -6
  63. package/dist/json-schema/follow-json-pointer.js +24 -24
  64. package/dist/json-schema/follow-json-pointer.mjs +24 -24
  65. package/dist/json-schema/ref-resolution-error.js +3 -3
  66. package/dist/json-schema/ref-resolution-error.mjs +3 -3
  67. package/dist/json-schema/schema-registry.js +12 -11
  68. package/dist/json-schema/schema-registry.mjs +12 -11
  69. package/dist/json-schema/uri-reference.js +12 -12
  70. package/dist/json-schema/uri-reference.mjs +12 -12
  71. package/dist/path/create-value-writer.js +12 -12
  72. package/dist/path/create-value-writer.mjs +12 -12
  73. package/dist/path/reserved-segment.d.ts +16 -16
  74. package/dist/path/reserved-segment.js +17 -21
  75. package/dist/path/reserved-segment.mjs +17 -21
  76. package/dist/plugins/index.generated.js +2 -2
  77. package/dist/plugins/index.generated.mjs +2 -2
  78. package/dist/plugins/manifest.generated.js +2 -2
  79. package/dist/plugins/manifest.generated.mjs +2 -2
  80. package/dist/plugins/object/object.d.ts +6 -0
  81. package/dist/plugins/object/object.js +9 -11
  82. package/dist/plugins/object/object.mjs +10 -12
  83. package/dist/plugins/object-additional-properties/select-additional-keys.d.ts +8 -8
  84. package/dist/plugins/object-additional-properties/select-additional-keys.js +16 -16
  85. package/dist/plugins/object-additional-properties/select-additional-keys.mjs +16 -16
  86. package/dist/plugins/stitch/stitch.d.ts +10 -14
  87. package/dist/plugins/stitch-with/stitch-with.d.ts +1 -1
  88. package/dist/plugins/stitch-with/stitch-with.js +22 -24
  89. package/dist/plugins/stitch-with/stitch-with.mjs +22 -24
  90. package/dist/plugins/string-min/string-min.js +6 -8
  91. package/dist/plugins/string-min/string-min.mjs +6 -8
  92. package/dist/presets/presets.d.ts +10 -11
  93. package/dist/presets/presets.js +22 -23
  94. package/dist/presets/presets.mjs +22 -23
  95. package/dist/runtime/create-field-validator.js +4 -6
  96. package/dist/runtime/create-field-validator.mjs +4 -6
  97. package/dist/runtime/create-validator.js +11 -11
  98. package/dist/runtime/create-validator.mjs +11 -11
  99. package/dist/runtime/output-writer.js +5 -1
  100. package/dist/runtime/output-writer.mjs +5 -1
  101. package/dist/runtime/run-array-node.js +6 -6
  102. package/dist/runtime/run-array-node.mjs +6 -6
  103. package/dist/runtime/run-field.js +18 -19
  104. package/dist/runtime/run-field.mjs +18 -19
  105. package/dist/standard-schema/assemble-json-schema.d.ts +4 -0
  106. package/dist/standard-schema/assemble-json-schema.js +95 -0
  107. package/dist/standard-schema/assemble-json-schema.mjs +92 -0
  108. package/dist/standard-schema/declaration-recorder.d.ts +6 -0
  109. package/dist/standard-schema/declaration-recorder.js +30 -0
  110. package/dist/standard-schema/declaration-recorder.mjs +27 -0
  111. package/dist/standard-schema/declarations-unavailable-error.d.ts +4 -0
  112. package/dist/standard-schema/declarations-unavailable-error.js +32 -0
  113. package/dist/standard-schema/declarations-unavailable-error.mjs +28 -0
  114. package/dist/standard-schema/emit-field-schema.d.ts +9 -0
  115. package/dist/standard-schema/emit-field-schema.js +68 -0
  116. package/dist/standard-schema/emit-field-schema.mjs +65 -0
  117. package/dist/standard-schema/index.d.ts +5 -0
  118. package/dist/standard-schema/index.js +9 -1
  119. package/dist/standard-schema/index.mjs +4 -0
  120. package/dist/standard-schema/json-schema-target.d.ts +6 -0
  121. package/dist/standard-schema/json-schema-target.js +44 -0
  122. package/dist/standard-schema/json-schema-target.mjs +39 -0
  123. package/dist/standard-schema/plugin-keyword-map.d.ts +3 -0
  124. package/dist/standard-schema/plugin-keyword-map.js +93 -0
  125. package/dist/standard-schema/plugin-keyword-map.mjs +90 -0
  126. package/dist/standard-schema/split-issue-path.d.ts +6 -4
  127. package/dist/standard-schema/split-issue-path.js +15 -13
  128. package/dist/standard-schema/split-issue-path.mjs +15 -13
  129. package/dist/standard-schema/standard-schema.types.d.ts +8 -7
  130. package/dist/standard-schema/standard-schema.types.js +6 -6
  131. package/dist/standard-schema/standard-schema.types.mjs +6 -6
  132. package/dist/standard-schema/to-standard-json-schema.d.ts +19 -0
  133. package/dist/standard-schema/to-standard-json-schema.js +36 -0
  134. package/dist/standard-schema/to-standard-json-schema.mjs +33 -0
  135. package/dist/standard-schema/to-standard-schema.d.ts +16 -15
  136. package/dist/standard-schema/to-standard-schema.js +15 -22
  137. package/dist/standard-schema/to-standard-schema.mjs +15 -22
  138. package/dist/standard-schema/unrepresentable-rule-error.d.ts +15 -0
  139. package/dist/standard-schema/unrepresentable-rule-error.js +43 -0
  140. package/dist/standard-schema/unrepresentable-rule-error.mjs +38 -0
  141. package/dist/types/index.d.ts +12 -12
  142. package/dist/types/index.js +7 -7
  143. package/dist/types/index.mjs +7 -7
  144. package/package.json +1 -1
@@ -40,23 +40,23 @@ function hasRejectingIssue(issues) {
40
40
  return issues.some((issue) => issue.severity === "error");
41
41
  }
42
42
  /**
43
- * 再帰しないプランのための、共有の何もしないランナー。
43
+ * The shared do-nothing runner for a plan that cannot recurse.
44
44
  *
45
- * createRecursionRunner WeakSet と四つのクロージャを作る。プランに
46
- * 再帰規則が一つも無くても validate() のたびに作っていて、それが固定コストの
47
- * 88% を占めていた 計測で 9.7%。プランが再帰を含むかは build() 時に
48
- * 分かることで、実行時に問い直すことではない。
45
+ * Building a real runner allocates a WeakSet and several closures. Doing that
46
+ * on every validate() of a plan with no recursive rule in it was most of the
47
+ * fixed per-call cost. Whether a plan can recurse is known at build time and
48
+ * is not a question to re-ask at validation time.
49
49
  *
50
- * 呼ばれることはない。呼ばれるのは field.recursion null でない場合だけで、
51
- * そのときは planCanRecurse true を返しているので本物が渡っている。
50
+ * Never actually called: a field only reaches its runner when it declares
51
+ * recursion, and a plan containing one gets the real runner instead.
52
52
  */
53
53
  const NO_RECURSION = () => { };
54
54
  /**
55
- * ルートのランナーが要るか。
55
+ * Whether the root needs a real runner.
56
56
  *
57
- * 見るのはルートの直下と配列ノードの要素だけでよい。合成 (oneOf など)
58
- * 枝は run-branch が自前のランナーを作って入れ子のプランを回すので、
59
- * ここで渡すものを使わない。
57
+ * Only the fields directly under the root and the elements of array nodes
58
+ * matter. A composite branch makes its own runner for the nested plan, so it
59
+ * never uses the one passed from here.
60
60
  */
61
61
  function planCanRecurse(plan) {
62
62
  return (plan.fields.some((field) => field.recursion !== null) ||
@@ -35,23 +35,23 @@ export function hasRejectingIssue(issues) {
35
35
  return issues.some((issue) => issue.severity === "error");
36
36
  }
37
37
  /**
38
- * 再帰しないプランのための、共有の何もしないランナー。
38
+ * The shared do-nothing runner for a plan that cannot recurse.
39
39
  *
40
- * createRecursionRunner WeakSet と四つのクロージャを作る。プランに
41
- * 再帰規則が一つも無くても validate() のたびに作っていて、それが固定コストの
42
- * 88% を占めていた 計測で 9.7%。プランが再帰を含むかは build() 時に
43
- * 分かることで、実行時に問い直すことではない。
40
+ * Building a real runner allocates a WeakSet and several closures. Doing that
41
+ * on every validate() of a plan with no recursive rule in it was most of the
42
+ * fixed per-call cost. Whether a plan can recurse is known at build time and
43
+ * is not a question to re-ask at validation time.
44
44
  *
45
- * 呼ばれることはない。呼ばれるのは field.recursion null でない場合だけで、
46
- * そのときは planCanRecurse true を返しているので本物が渡っている。
45
+ * Never actually called: a field only reaches its runner when it declares
46
+ * recursion, and a plan containing one gets the real runner instead.
47
47
  */
48
48
  const NO_RECURSION = () => { };
49
49
  /**
50
- * ルートのランナーが要るか。
50
+ * Whether the root needs a real runner.
51
51
  *
52
- * 見るのはルートの直下と配列ノードの要素だけでよい。合成 (oneOf など)
53
- * 枝は run-branch が自前のランナーを作って入れ子のプランを回すので、
54
- * ここで渡すものを使わない。
52
+ * Only the fields directly under the root and the elements of array nodes
53
+ * matter. A composite branch makes its own runner for the nested plan, so it
54
+ * never uses the one passed from here.
55
55
  */
56
56
  function planCanRecurse(plan) {
57
57
  return (plan.fields.some((field) => field.recursion !== null) ||
@@ -24,8 +24,12 @@ function createArrayWriteTargets(nodes) {
24
24
  * hands back the very object it was given.
25
25
  */
26
26
  function createPlanWriteTargets(plan) {
27
- if (!plan.hasTransforms && !plan.hasDefaults)
27
+ // A field declaring only a normalizer is still written back by parse().
28
+ // Leave it out of this condition and no writer is made, so the value read
29
+ // comes back unchanged and in silence.
30
+ if (!plan.hasTransforms && !plan.hasDefaults && !plan.hasNormalizers) {
28
31
  return null;
32
+ }
29
33
  return createArrayWriteTargets(plan.arrays);
30
34
  }
31
35
  /**
@@ -17,8 +17,12 @@ export function createArrayWriteTargets(nodes) {
17
17
  * hands back the very object it was given.
18
18
  */
19
19
  export function createPlanWriteTargets(plan) {
20
- if (!plan.hasTransforms && !plan.hasDefaults)
20
+ // A field declaring only a normalizer is still written back by parse().
21
+ // Leave it out of this condition and no writer is made, so the value read
22
+ // comes back unchanged and in silence.
23
+ if (!plan.hasTransforms && !plan.hasDefaults && !plan.hasNormalizers) {
21
24
  return null;
25
+ }
22
26
  return createArrayWriteTargets(plan.arrays);
23
27
  }
24
28
  /**
@@ -57,12 +57,12 @@ function runElements(node, array, context, target) {
57
57
  const nodePath = node.renderedPath;
58
58
  const elementSink = context.sink.forArrayElements();
59
59
  const nested = target === undefined ? output_writer_1.NO_WRITE_TARGETS : target.nested;
60
- // 要素コンテキストはノードごとに一つ。7つの成員のうち要素ごとに変わるのは
61
- // `item` だけで、残る6つは validate() 一回のあいだ、あるいはこのノードの
62
- // あいだ動かない。毎要素で組み直していたのは、動かないものを動くものと
63
- // 同じ場所に置いていたからにすぎない。**プラグインには渡らない**
64
- // プラグインが見るのは runField が組む RuleContext で、そちらは `item`
65
- // 都度読み直すので、この使い回しは外から観測できない。
60
+ // One element context per node. `item` is the only member that changes per
61
+ // element; the rest hold still for the whole node, and rebuilding them per
62
+ // element only happened because the fixed parts sat beside the moving one.
63
+ // **It never reaches a plugin** a plugin sees the RuleContext that running
64
+ // a field assembles, and that reads `item` afresh, so reusing this is not
65
+ // observable from outside.
66
66
  const elementContext = {
67
67
  root: context.root,
68
68
  sink: elementSink,
@@ -54,12 +54,12 @@ function runElements(node, array, context, target) {
54
54
  const nodePath = node.renderedPath;
55
55
  const elementSink = context.sink.forArrayElements();
56
56
  const nested = target === undefined ? NO_WRITE_TARGETS : target.nested;
57
- // 要素コンテキストはノードごとに一つ。7つの成員のうち要素ごとに変わるのは
58
- // `item` だけで、残る6つは validate() 一回のあいだ、あるいはこのノードの
59
- // あいだ動かない。毎要素で組み直していたのは、動かないものを動くものと
60
- // 同じ場所に置いていたからにすぎない。**プラグインには渡らない**
61
- // プラグインが見るのは runField が組む RuleContext で、そちらは `item`
62
- // 都度読み直すので、この使い回しは外から観測できない。
57
+ // One element context per node. `item` is the only member that changes per
58
+ // element; the rest hold still for the whole node, and rebuilding them per
59
+ // element only happened because the fixed parts sat beside the moving one.
60
+ // **It never reaches a plugin** a plugin sees the RuleContext that running
61
+ // a field assembles, and that reads `item` afresh, so reusing this is not
62
+ // observable from outside.
63
63
  const elementContext = {
64
64
  root: context.root,
65
65
  sink: elementSink,
@@ -12,7 +12,7 @@ exports.FIELD_VALUE_UNCHANGED = Object.freeze({
12
12
  function runField(field, subject, context) {
13
13
  const ruleContext = new field_rule_context_1.FieldRuleContext(context.root, context.indices, field.renderedPath, context.item, context.external);
14
14
  const read = field.read(subject);
15
- const value = applyDefault(field, read, context.root);
15
+ const value = applyNormalize(field, applyDefault(field, read, context.root));
16
16
  if (!(0, decide_presence_1.decidePresence)(field, value, ruleContext, context.sink)) {
17
17
  return exports.FIELD_VALUE_UNCHANGED;
18
18
  }
@@ -40,15 +40,21 @@ function applyDefault(field, value, root) {
40
40
  return field.defaultOf(root);
41
41
  return value;
42
42
  }
43
+ /**
44
+ * Tidies the value before anything judges it: after default, before presence.
45
+ *
46
+ * Never called for undefined or null. That closes the accident where a
47
+ * caller's `(v) => String(v).trim()` turns a missing field into the string
48
+ * `"undefined"` and walks it past `.required()`. Absence is default's job.
49
+ */
50
+ function applyNormalize(field, value) {
51
+ if (field.normalize === null || value === undefined || value === null) {
52
+ return value;
53
+ }
54
+ return field.normalize(value);
55
+ }
43
56
  /** A closed gate ends the field successfully: no check, no transform. */
44
57
  function openGates(field, value, ruleContext) {
45
- // 添字ループである。for-of ではない。ここが回るのはコンパイル済みの
46
- // 凍結配列で、凍結配列は V8 では PACKED_FROZEN_ELEMENTS になり、配列
47
- // イテレータの高速化パスから外れる — イテレータと IteratorResult が
48
- // 消去されず、要素×フィールドの回数だけ確保される。配列シェイプでは
49
- // それだけで全ゴミの 45〜54% を占めていた (独立に5通りの改変で -45%
50
- // 〜 -54%)。凍結は落とさない: コンパイル層の不変条件であり、凍結を
51
- // 外しても添字ループより速くはならない。
52
58
  const gates = field.gates;
53
59
  for (let i = 0; i < gates.length; i += 1) {
54
60
  const gate = gates[i];
@@ -60,17 +66,10 @@ function openGates(field, value, ruleContext) {
60
66
  return true;
61
67
  }
62
68
  /**
63
- * 失敗したときだけ通る側。ループ本体から出してある。
64
- *
65
- * runChecks はバイトコードで 302 バイトあり、TurboFan の呼び出し先
66
- * インライン予算 (既定で累計 920 バイト) の最大の落選候補として
67
- * --trace-turbo-inlining に名指しされていた。その 302 バイトの大半が、
68
- * 受理された値では一度も走らない issue の組み立てである。ここへ出すと
69
- * 残るループ本体が縮み、受理パスで 6.4% 速くなった。
70
- *
71
- * 中断の判定はここに含めない。issue を足したあとに shouldStopField を
72
- * 見るという順序が abortEarlyOnEachField の意味そのものなので、呼び出し側に
73
- * 並べて置いておく。
69
+ * The side taken only on failure. Lifted out of the loop body because most of
70
+ * that body was issue construction an accepted value never runs, and its size
71
+ * was what pushed the check loop past the inlining budget. Stopping is not
72
+ * decided here: add the issue, then ask — that order is what abortEarly means.
74
73
  */
75
74
  function reportCheckFailure(check, detail, value, ruleContext, context) {
76
75
  context.sink.add((0, create_issue_1.createIssue)({
@@ -8,7 +8,7 @@ export const FIELD_VALUE_UNCHANGED = Object.freeze({
8
8
  export function runField(field, subject, context) {
9
9
  const ruleContext = new FieldRuleContext(context.root, context.indices, field.renderedPath, context.item, context.external);
10
10
  const read = field.read(subject);
11
- const value = applyDefault(field, read, context.root);
11
+ const value = applyNormalize(field, applyDefault(field, read, context.root));
12
12
  if (!decidePresence(field, value, ruleContext, context.sink)) {
13
13
  return FIELD_VALUE_UNCHANGED;
14
14
  }
@@ -36,15 +36,21 @@ function applyDefault(field, value, root) {
36
36
  return field.defaultOf(root);
37
37
  return value;
38
38
  }
39
+ /**
40
+ * Tidies the value before anything judges it: after default, before presence.
41
+ *
42
+ * Never called for undefined or null. That closes the accident where a
43
+ * caller's `(v) => String(v).trim()` turns a missing field into the string
44
+ * `"undefined"` and walks it past `.required()`. Absence is default's job.
45
+ */
46
+ function applyNormalize(field, value) {
47
+ if (field.normalize === null || value === undefined || value === null) {
48
+ return value;
49
+ }
50
+ return field.normalize(value);
51
+ }
39
52
  /** A closed gate ends the field successfully: no check, no transform. */
40
53
  function openGates(field, value, ruleContext) {
41
- // 添字ループである。for-of ではない。ここが回るのはコンパイル済みの
42
- // 凍結配列で、凍結配列は V8 では PACKED_FROZEN_ELEMENTS になり、配列
43
- // イテレータの高速化パスから外れる — イテレータと IteratorResult が
44
- // 消去されず、要素×フィールドの回数だけ確保される。配列シェイプでは
45
- // それだけで全ゴミの 45〜54% を占めていた (独立に5通りの改変で -45%
46
- // 〜 -54%)。凍結は落とさない: コンパイル層の不変条件であり、凍結を
47
- // 外しても添字ループより速くはならない。
48
54
  const gates = field.gates;
49
55
  for (let i = 0; i < gates.length; i += 1) {
50
56
  const gate = gates[i];
@@ -56,17 +62,10 @@ function openGates(field, value, ruleContext) {
56
62
  return true;
57
63
  }
58
64
  /**
59
- * 失敗したときだけ通る側。ループ本体から出してある。
60
- *
61
- * runChecks はバイトコードで 302 バイトあり、TurboFan の呼び出し先
62
- * インライン予算 (既定で累計 920 バイト) の最大の落選候補として
63
- * --trace-turbo-inlining に名指しされていた。その 302 バイトの大半が、
64
- * 受理された値では一度も走らない issue の組み立てである。ここへ出すと
65
- * 残るループ本体が縮み、受理パスで 6.4% 速くなった。
66
- *
67
- * 中断の判定はここに含めない。issue を足したあとに shouldStopField を
68
- * 見るという順序が abortEarlyOnEachField の意味そのものなので、呼び出し側に
69
- * 並べて置いておく。
65
+ * The side taken only on failure. Lifted out of the loop body because most of
66
+ * that body was issue construction an accepted value never runs, and its size
67
+ * was what pushed the check loop past the inlining budget. Stopping is not
68
+ * decided here: add the issue, then ask — that order is what abortEarly means.
70
69
  */
71
70
  function reportCheckFailure(check, detail, value, ruleContext, context) {
72
71
  context.sink.add(createIssue({
@@ -0,0 +1,4 @@
1
+ import type { FieldDeclaredCalls } from "../builder/field-declared-calls.types";
2
+ import type { UnrepresentablePolicy } from "./unrepresentable-rule-error";
3
+ /** Assembles the root schema from the list of declarations. */
4
+ export declare function assembleJsonSchema(fields: readonly FieldDeclaredCalls[], policy: UnrepresentablePolicy): Record<string, unknown>;
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.assembleJsonSchema = assembleJsonSchema;
4
+ // ===========================================================================
5
+ // L10 src/standard-schema/assemble-json-schema.ts
6
+ //
7
+ // Builds the nested object and array schema from a list of paths.
8
+ //
9
+ // `"owner.name"` becomes `properties.owner.properties.name`, and
10
+ // `"employees[*].name"` becomes
11
+ // `properties.employees.items.properties.name`. Paths are read with the
12
+ // existing path parser rather than a second grammar written here.
13
+ //
14
+ // `required` belongs to the PARENT. In JSON Schema it says "this object must
15
+ // carry this key", which is a fact about the object, not about the key.
16
+ // ===========================================================================
17
+ const parse_field_path_1 = require("../path/parse-field-path");
18
+ const emit_field_schema_1 = require("./emit-field-schema");
19
+ const declarations_unavailable_error_1 = require("./declarations-unavailable-error");
20
+ const newNode = () => ({
21
+ properties: new Map(),
22
+ required: new Set(),
23
+ element: null,
24
+ leaf: null,
25
+ });
26
+ /** Descends through `key`, creating it when absent. */
27
+ function descend(node, key) {
28
+ const existing = node.properties.get(key);
29
+ if (existing !== undefined)
30
+ return existing;
31
+ const created = newNode();
32
+ node.properties.set(key, created);
33
+ return created;
34
+ }
35
+ /** Places one path in the tree, setting the schema where the leaf lands. */
36
+ function place(root, segments, schema, isRequired) {
37
+ let node = root;
38
+ let owner = null;
39
+ let ownerKey = "";
40
+ for (const segment of segments) {
41
+ if (segment.kind === "each") {
42
+ node.element ?? (node.element = newNode());
43
+ node = node.element;
44
+ owner = null;
45
+ continue;
46
+ }
47
+ owner = node;
48
+ ownerKey = segment.key;
49
+ node = descend(node, segment.key);
50
+ }
51
+ node.leaf = schema;
52
+ if (isRequired && owner !== null)
53
+ owner.required.add(ownerKey);
54
+ }
55
+ /**
56
+ * Freezes a node into a JSON Schema.
57
+ *
58
+ * On a node that has children, the leaf schema is the FOUNDATION. When both
59
+ * `.object.required()` and `"user.name"` are declared, the properties from
60
+ * the latter sit on top of the `type: "object"` from the former.
61
+ */
62
+ function freeze(node) {
63
+ const schema = { ...node.leaf };
64
+ if (node.element !== null) {
65
+ schema["type"] ?? (schema["type"] = "array");
66
+ schema["items"] = freeze(node.element);
67
+ }
68
+ if (node.properties.size > 0) {
69
+ schema["type"] ?? (schema["type"] = "object");
70
+ const properties = {};
71
+ for (const [key, child] of node.properties) {
72
+ properties[key] = freeze(child);
73
+ }
74
+ schema["properties"] = properties;
75
+ }
76
+ if (node.required.size > 0)
77
+ schema["required"] = [...node.required];
78
+ return schema;
79
+ }
80
+ /** Assembles the root schema from the list of declarations. */
81
+ function assembleJsonSchema(fields, policy) {
82
+ const root = newNode();
83
+ for (const field of fields) {
84
+ // One field with no record is enough to refuse, whatever the policy says.
85
+ if (field.calls === null)
86
+ throw new declarations_unavailable_error_1.DeclarationsUnavailableError(field.path);
87
+ const emitted = (0, emit_field_schema_1.emitFieldSchema)(field.path, field.calls, policy);
88
+ place(root, (0, parse_field_path_1.parseFieldPath)(field.path), emitted.schema, emitted.isRequired);
89
+ }
90
+ const schema = freeze(root);
91
+ // Even with nothing declared, or everything dropped, the root is an object:
92
+ // the builder only accepts an object type in the first place.
93
+ schema["type"] ?? (schema["type"] = "object");
94
+ return schema;
95
+ }
@@ -0,0 +1,92 @@
1
+ // ===========================================================================
2
+ // L10 src/standard-schema/assemble-json-schema.ts
3
+ //
4
+ // Builds the nested object and array schema from a list of paths.
5
+ //
6
+ // `"owner.name"` becomes `properties.owner.properties.name`, and
7
+ // `"employees[*].name"` becomes
8
+ // `properties.employees.items.properties.name`. Paths are read with the
9
+ // existing path parser rather than a second grammar written here.
10
+ //
11
+ // `required` belongs to the PARENT. In JSON Schema it says "this object must
12
+ // carry this key", which is a fact about the object, not about the key.
13
+ // ===========================================================================
14
+ import { parseFieldPath } from "../path/parse-field-path.mjs";
15
+ import { emitFieldSchema } from "./emit-field-schema.mjs";
16
+ import { DeclarationsUnavailableError } from "./declarations-unavailable-error.mjs";
17
+ const newNode = () => ({
18
+ properties: new Map(),
19
+ required: new Set(),
20
+ element: null,
21
+ leaf: null,
22
+ });
23
+ /** Descends through `key`, creating it when absent. */
24
+ function descend(node, key) {
25
+ const existing = node.properties.get(key);
26
+ if (existing !== undefined)
27
+ return existing;
28
+ const created = newNode();
29
+ node.properties.set(key, created);
30
+ return created;
31
+ }
32
+ /** Places one path in the tree, setting the schema where the leaf lands. */
33
+ function place(root, segments, schema, isRequired) {
34
+ let node = root;
35
+ let owner = null;
36
+ let ownerKey = "";
37
+ for (const segment of segments) {
38
+ if (segment.kind === "each") {
39
+ node.element ?? (node.element = newNode());
40
+ node = node.element;
41
+ owner = null;
42
+ continue;
43
+ }
44
+ owner = node;
45
+ ownerKey = segment.key;
46
+ node = descend(node, segment.key);
47
+ }
48
+ node.leaf = schema;
49
+ if (isRequired && owner !== null)
50
+ owner.required.add(ownerKey);
51
+ }
52
+ /**
53
+ * Freezes a node into a JSON Schema.
54
+ *
55
+ * On a node that has children, the leaf schema is the FOUNDATION. When both
56
+ * `.object.required()` and `"user.name"` are declared, the properties from
57
+ * the latter sit on top of the `type: "object"` from the former.
58
+ */
59
+ function freeze(node) {
60
+ const schema = { ...node.leaf };
61
+ if (node.element !== null) {
62
+ schema["type"] ?? (schema["type"] = "array");
63
+ schema["items"] = freeze(node.element);
64
+ }
65
+ if (node.properties.size > 0) {
66
+ schema["type"] ?? (schema["type"] = "object");
67
+ const properties = {};
68
+ for (const [key, child] of node.properties) {
69
+ properties[key] = freeze(child);
70
+ }
71
+ schema["properties"] = properties;
72
+ }
73
+ if (node.required.size > 0)
74
+ schema["required"] = [...node.required];
75
+ return schema;
76
+ }
77
+ /** Assembles the root schema from the list of declarations. */
78
+ export function assembleJsonSchema(fields, policy) {
79
+ const root = newNode();
80
+ for (const field of fields) {
81
+ // One field with no record is enough to refuse, whatever the policy says.
82
+ if (field.calls === null)
83
+ throw new DeclarationsUnavailableError(field.path);
84
+ const emitted = emitFieldSchema(field.path, field.calls, policy);
85
+ place(root, parseFieldPath(field.path), emitted.schema, emitted.isRequired);
86
+ }
87
+ const schema = freeze(root);
88
+ // Even with nothing declared, or everything dropped, the root is an object:
89
+ // the builder only accepts an object type in the first place.
90
+ schema["type"] ?? (schema["type"] = "object");
91
+ return schema;
92
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Idempotent. Call it at module scope, never from inside a function: the
3
+ * recorder has to be in place before any chain runs, and hiding the call
4
+ * behind a function makes that ordering depend on who calls it first.
5
+ */
6
+ export declare function installJsonSchemaDeclarationRecorder(): void;
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.installJsonSchemaDeclarationRecorder = installJsonSchemaDeclarationRecorder;
4
+ const declaration_recorder_port_1 = require("../chain/declaration-recorder.port");
5
+ const NONE = Object.freeze([]);
6
+ const callsByNode = new WeakMap();
7
+ const recorder = {
8
+ record(parent, child, plugin, slot, args) {
9
+ callsByNode.set(child, [
10
+ ...(callsByNode.get(parent) ?? NONE),
11
+ { pluginName: plugin.name, method: plugin.method, slot, args },
12
+ ]);
13
+ },
14
+ inherit(parent, child) {
15
+ const calls = callsByNode.get(parent);
16
+ if (calls !== undefined)
17
+ callsByNode.set(child, calls);
18
+ },
19
+ read(node) {
20
+ return callsByNode.get(node);
21
+ },
22
+ };
23
+ /**
24
+ * Idempotent. Call it at module scope, never from inside a function: the
25
+ * recorder has to be in place before any chain runs, and hiding the call
26
+ * behind a function makes that ordering depend on who calls it first.
27
+ */
28
+ function installJsonSchemaDeclarationRecorder() {
29
+ (0, declaration_recorder_port_1.installDeclarationRecorder)(recorder);
30
+ }
@@ -0,0 +1,27 @@
1
+ import { installDeclarationRecorder, } from "../chain/declaration-recorder.port.mjs";
2
+ const NONE = Object.freeze([]);
3
+ const callsByNode = new WeakMap();
4
+ const recorder = {
5
+ record(parent, child, plugin, slot, args) {
6
+ callsByNode.set(child, [
7
+ ...(callsByNode.get(parent) ?? NONE),
8
+ { pluginName: plugin.name, method: plugin.method, slot, args },
9
+ ]);
10
+ },
11
+ inherit(parent, child) {
12
+ const calls = callsByNode.get(parent);
13
+ if (calls !== undefined)
14
+ callsByNode.set(child, calls);
15
+ },
16
+ read(node) {
17
+ return callsByNode.get(node);
18
+ },
19
+ };
20
+ /**
21
+ * Idempotent. Call it at module scope, never from inside a function: the
22
+ * recorder has to be in place before any chain runs, and hiding the call
23
+ * behind a function makes that ordering depend on who calls it first.
24
+ */
25
+ export function installJsonSchemaDeclarationRecorder() {
26
+ installDeclarationRecorder(recorder);
27
+ }
@@ -0,0 +1,4 @@
1
+ export declare class DeclarationsUnavailableError extends Error {
2
+ readonly fieldPath?: string | undefined;
3
+ constructor(fieldPath?: string | undefined);
4
+ }
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ // ===========================================================================
3
+ // L10 src/standard-schema/declarations-unavailable-error.ts
4
+ //
5
+ // "Nothing was declared" and "what was declared is not known" are different
6
+ // answers, and only one of them can be written out.
7
+ //
8
+ // Collapsing them into an empty list produces a schema that permits everything
9
+ // and looks entirely confident about it. This refusal exists to stop that.
10
+ //
11
+ // It outranks the unrepresentable policy: `omit` is permission to drop a
12
+ // declaration that cannot be written, not permission to write without knowing
13
+ // what was declared.
14
+ // ===========================================================================
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.DeclarationsUnavailableError = void 0;
17
+ class DeclarationsUnavailableError extends Error {
18
+ constructor(fieldPath) {
19
+ super((fieldPath === undefined
20
+ ? "This validator carries no declarations"
21
+ : `"${fieldPath}" carries no declarations`) +
22
+ ", so no JSON Schema can be emitted from it. Two things cause " +
23
+ "this. A validator from fromJsonSchema() was assembled from rules " +
24
+ "directly and never went through the builder chain. Otherwise the " +
25
+ 'build() ran before "@maroonedog/luq/standard-schema" was loaded — ' +
26
+ "import it from the module that builds the validator, or from one " +
27
+ "evaluated before it.");
28
+ this.fieldPath = fieldPath;
29
+ this.name = "DeclarationsUnavailableError";
30
+ }
31
+ }
32
+ exports.DeclarationsUnavailableError = DeclarationsUnavailableError;
@@ -0,0 +1,28 @@
1
+ // ===========================================================================
2
+ // L10 src/standard-schema/declarations-unavailable-error.ts
3
+ //
4
+ // "Nothing was declared" and "what was declared is not known" are different
5
+ // answers, and only one of them can be written out.
6
+ //
7
+ // Collapsing them into an empty list produces a schema that permits everything
8
+ // and looks entirely confident about it. This refusal exists to stop that.
9
+ //
10
+ // It outranks the unrepresentable policy: `omit` is permission to drop a
11
+ // declaration that cannot be written, not permission to write without knowing
12
+ // what was declared.
13
+ // ===========================================================================
14
+ export class DeclarationsUnavailableError extends Error {
15
+ constructor(fieldPath) {
16
+ super((fieldPath === undefined
17
+ ? "This validator carries no declarations"
18
+ : `"${fieldPath}" carries no declarations`) +
19
+ ", so no JSON Schema can be emitted from it. Two things cause " +
20
+ "this. A validator from fromJsonSchema() was assembled from rules " +
21
+ "directly and never went through the builder chain. Otherwise the " +
22
+ 'build() ran before "@maroonedog/luq/standard-schema" was loaded — ' +
23
+ "import it from the module that builds the validator, or from one " +
24
+ "evaluated before it.");
25
+ this.fieldPath = fieldPath;
26
+ this.name = "DeclarationsUnavailableError";
27
+ }
28
+ }
@@ -0,0 +1,9 @@
1
+ import type { DeclaredCall } from "../chain/declared-call.types";
2
+ import { type UnrepresentablePolicy } from "./unrepresentable-rule-error";
3
+ /** One emitted field. `required` is separate: the parent assembles it. */
4
+ export interface EmittedField {
5
+ readonly schema: Record<string, unknown>;
6
+ readonly isRequired: boolean;
7
+ }
8
+ /** Makes one field's schema from that field's declared calls. */
9
+ export declare function emitFieldSchema(fieldPath: string, calls: readonly DeclaredCall[], policy: UnrepresentablePolicy): EmittedField;