@maroonedog/luq 2.3.1 → 2.4.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.
@@ -0,0 +1,7 @@
1
+ export { flattenSchema } from "../json-schema";
2
+ export type { ReadChildSchemas, SchemaFieldDeclaration } from "../json-schema";
3
+ export { readChildSchemas } from "../json-schema";
4
+ export { isDraft07Schema, isSchemaObject } from "../json-schema";
5
+ /** The vocabulary itself: what a tool must have a decision for. */
6
+ export { listDraft07Keywords } from "../json-schema";
7
+ export type { ChildSchema, Draft07Schema, Draft07SchemaObject, } from "../json-schema";
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listDraft07Keywords = exports.isSchemaObject = exports.isDraft07Schema = exports.readChildSchemas = exports.flattenSchema = void 0;
4
+ // ===========================================================================
5
+ // src/schema-tooling/index.ts — re-exports only. Nothing is defined here.
6
+ //
7
+ // The surface published as `@maroonedog/luq/schema-tooling`, for a tool that
8
+ // reads a JSON Schema document at build time instead of validating one at run
9
+ // time — a code generator, above all.
10
+ //
11
+ // It is listed by hand and kept short on purpose. Everything named here is
12
+ // public API from the moment it ships, so the file answers one question only:
13
+ // what does a generator need in order to decide the same field paths the
14
+ // run-time conversion decides? Anything a generator does not need stays
15
+ // internal, and re-exporting the whole layer would have frozen all of it.
16
+ // ===========================================================================
17
+ var json_schema_1 = require("../json-schema");
18
+ Object.defineProperty(exports, "flattenSchema", { enumerable: true, get: function () { return json_schema_1.flattenSchema; } });
19
+ var json_schema_2 = require("../json-schema");
20
+ Object.defineProperty(exports, "readChildSchemas", { enumerable: true, get: function () { return json_schema_2.readChildSchemas; } });
21
+ var json_schema_3 = require("../json-schema");
22
+ Object.defineProperty(exports, "isDraft07Schema", { enumerable: true, get: function () { return json_schema_3.isDraft07Schema; } });
23
+ Object.defineProperty(exports, "isSchemaObject", { enumerable: true, get: function () { return json_schema_3.isSchemaObject; } });
24
+ /** The vocabulary itself: what a tool must have a decision for. */
25
+ var json_schema_4 = require("../json-schema");
26
+ Object.defineProperty(exports, "listDraft07Keywords", { enumerable: true, get: function () { return json_schema_4.listDraft07Keywords; } });
@@ -0,0 +1,18 @@
1
+ // ===========================================================================
2
+ // src/schema-tooling/index.ts — re-exports only. Nothing is defined here.
3
+ //
4
+ // The surface published as `@maroonedog/luq/schema-tooling`, for a tool that
5
+ // reads a JSON Schema document at build time instead of validating one at run
6
+ // time — a code generator, above all.
7
+ //
8
+ // It is listed by hand and kept short on purpose. Everything named here is
9
+ // public API from the moment it ships, so the file answers one question only:
10
+ // what does a generator need in order to decide the same field paths the
11
+ // run-time conversion decides? Anything a generator does not need stays
12
+ // internal, and re-exporting the whole layer would have frozen all of it.
13
+ // ===========================================================================
14
+ export { flattenSchema } from "../json-schema/index.mjs";
15
+ export { readChildSchemas } from "../json-schema/index.mjs";
16
+ export { isDraft07Schema, isSchemaObject } from "../json-schema/index.mjs";
17
+ /** The vocabulary itself: what a tool must have a decision for. */
18
+ export { listDraft07Keywords } from "../json-schema/index.mjs";
@@ -55,6 +55,16 @@ function emitFieldSchema(fieldPath, calls, policy) {
55
55
  continue;
56
56
  }
57
57
  const keywords = toKeywords(call.args);
58
+ // undefined is the table saying it cannot express THESE arguments, which
59
+ // is the same answer as having no entry at all: refuse, rather than emit a
60
+ // schema missing a constraint the validator enforces. null is different —
61
+ // it means type or presence already carries the declaration.
62
+ if (keywords === undefined) {
63
+ if (policy === "throw") {
64
+ throw new unrepresentable_rule_error_1.UnrepresentableRuleError(fieldPath, call.pluginName, "no JSON Schema keyword expresses the arguments it was given");
65
+ }
66
+ continue;
67
+ }
58
68
  if (keywords === null)
59
69
  continue;
60
70
  Object.assign(schema, keywords);
@@ -52,6 +52,16 @@ export function emitFieldSchema(fieldPath, calls, policy) {
52
52
  continue;
53
53
  }
54
54
  const keywords = toKeywords(call.args);
55
+ // undefined is the table saying it cannot express THESE arguments, which
56
+ // is the same answer as having no entry at all: refuse, rather than emit a
57
+ // schema missing a constraint the validator enforces. null is different —
58
+ // it means type or presence already carries the declaration.
59
+ if (keywords === undefined) {
60
+ if (policy === "throw") {
61
+ throw new UnrepresentableRuleError(fieldPath, call.pluginName, "no JSON Schema keyword expresses the arguments it was given");
62
+ }
63
+ continue;
64
+ }
55
65
  if (keywords === null)
56
66
  continue;
57
67
  Object.assign(schema, keywords);
@@ -1,3 +1,16 @@
1
- /** A keyword fragment made from the arguments; null means type or presence handles it. */
2
- export type ToKeywords = (args: readonly unknown[]) => Record<string, unknown> | null;
1
+ /**
2
+ * A keyword fragment made from the arguments. Three answers, and the
3
+ * difference between the last two is the point:
4
+ *
5
+ * an object the keywords this declaration means
6
+ * null it adds no keyword, and that is correct — type or presence
7
+ * already carries it
8
+ * undefined it cannot be expressed with these arguments, so the caller
9
+ * must refuse rather than emit a schema that quietly says
10
+ * something else
11
+ *
12
+ * Collapsing the last two into `null` is how a constraint disappears without
13
+ * a trace, which is the failure UnrepresentableRuleError exists to prevent.
14
+ */
15
+ export type ToKeywords = (args: readonly unknown[]) => Record<string, unknown> | null | undefined;
3
16
  export declare const PLUGIN_KEYWORDS: Readonly<Record<string, ToKeywords>>;
@@ -37,9 +37,14 @@ exports.PLUGIN_KEYWORDS = Object.freeze({
37
37
  maxLength: numberAt(args, 0),
38
38
  }),
39
39
  // Draft-07's `pattern` is an ECMA-262 SOURCE string. Flags cannot be
40
- // spelled there, and dropping them changes the meaning, so a flagged
41
- // RegExp counts as unwritable rather than being silently narrowed.
42
- stringPattern: (args) => args[0] instanceof RegExp ? { pattern: args[0].source } : null,
40
+ // spelled there, and dropping them changes the meaning `/^a.c$/i`
41
+ // accepts "ABC" and `{"pattern":"^a.c$"}` does not so a flagged RegExp
42
+ // is unwritable rather than something to narrow quietly. Anything that is
43
+ // not a RegExp at all is unwritable for the same reason: refusing is the
44
+ // only answer that does not lose the constraint in silence.
45
+ stringPattern: (args) => args[0] instanceof RegExp && args[0].flags === ""
46
+ ? { pattern: args[0].source }
47
+ : undefined,
43
48
  stringContentEncoding: (args) => ({ contentEncoding: args[0] }),
44
49
  stringContentMediaType: (args) => ({ contentMediaType: args[0] }),
45
50
  // --- formats (names taken verbatim from the format map) ---------------
@@ -81,7 +86,9 @@ exports.PLUGIN_KEYWORDS = Object.freeze({
81
86
  arrayUnique: () => ({ uniqueItems: true }),
82
87
  // --- values -----------------------------------------------------------
83
88
  literal: (args) => ({ const: args[0] }),
84
- oneOf: (args) => (Array.isArray(args[0]) ? { enum: args[0] } : null),
89
+ // `enum` needs the list itself. Given something else there is no list to
90
+ // write, and answering "no keyword" would drop the constraint.
91
+ oneOf: (args) => (Array.isArray(args[0]) ? { enum: args[0] } : undefined),
85
92
  // --- handled by type or presence, adding no keyword -------------------
86
93
  // numberInteger becomes `type: "integer"`, so it feeds the type decision.
87
94
  numberInteger: () => null,
@@ -34,9 +34,14 @@ export const PLUGIN_KEYWORDS = Object.freeze({
34
34
  maxLength: numberAt(args, 0),
35
35
  }),
36
36
  // Draft-07's `pattern` is an ECMA-262 SOURCE string. Flags cannot be
37
- // spelled there, and dropping them changes the meaning, so a flagged
38
- // RegExp counts as unwritable rather than being silently narrowed.
39
- stringPattern: (args) => args[0] instanceof RegExp ? { pattern: args[0].source } : null,
37
+ // spelled there, and dropping them changes the meaning `/^a.c$/i`
38
+ // accepts "ABC" and `{"pattern":"^a.c$"}` does not so a flagged RegExp
39
+ // is unwritable rather than something to narrow quietly. Anything that is
40
+ // not a RegExp at all is unwritable for the same reason: refusing is the
41
+ // only answer that does not lose the constraint in silence.
42
+ stringPattern: (args) => args[0] instanceof RegExp && args[0].flags === ""
43
+ ? { pattern: args[0].source }
44
+ : undefined,
40
45
  stringContentEncoding: (args) => ({ contentEncoding: args[0] }),
41
46
  stringContentMediaType: (args) => ({ contentMediaType: args[0] }),
42
47
  // --- formats (names taken verbatim from the format map) ---------------
@@ -78,7 +83,9 @@ export const PLUGIN_KEYWORDS = Object.freeze({
78
83
  arrayUnique: () => ({ uniqueItems: true }),
79
84
  // --- values -----------------------------------------------------------
80
85
  literal: (args) => ({ const: args[0] }),
81
- oneOf: (args) => (Array.isArray(args[0]) ? { enum: args[0] } : null),
86
+ // `enum` needs the list itself. Given something else there is no list to
87
+ // write, and answering "no keyword" would drop the constraint.
88
+ oneOf: (args) => (Array.isArray(args[0]) ? { enum: args[0] } : undefined),
82
89
  // --- handled by type or presence, adding no keyword -------------------
83
90
  // numberInteger becomes `type: "integer"`, so it feeds the type decision.
84
91
  numberInteger: () => null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maroonedog/luq",
3
- "version": "2.3.1",
3
+ "version": "2.4.1",
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",
@@ -47,6 +47,11 @@
47
47
  "import": "./dist/plugins/index.mjs",
48
48
  "require": "./dist/plugins/index.js"
49
49
  },
50
+ "./schema-tooling": {
51
+ "types": "./dist/schema-tooling/index.d.ts",
52
+ "import": "./dist/schema-tooling/index.mjs",
53
+ "require": "./dist/schema-tooling/index.js"
54
+ },
50
55
  "./plugins/arrayContains": {
51
56
  "types": "./dist/plugins/arrayContains.d.ts",
52
57
  "import": "./dist/plugins/arrayContains.mjs",