@coldsmirk/abacus-core 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -28
- package/dist/index.cjs +30 -301
- package/dist/index.d.cts +48 -156
- package/dist/index.d.ts +48 -156
- package/dist/index.js +28 -297
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Part of [abacus](https://github.com/coldsmirk/abacus). To edit expressions in an
|
|
|
9
9
|
- **Evaluate** ZEN expressions against a data context, sync or async.
|
|
10
10
|
- **Compile** a structured, UI-authored condition tree into a single ZEN boolean expression — and **lift** the canonical form back into a tree for round-trip editing — plus **select** a matching branch by priority.
|
|
11
11
|
- **Type-analyze** an expression against a variable-context type tree — the inferred type of every span, autocomplete metadata, and positioned diagnostics that power an editor.
|
|
12
|
-
- **Project
|
|
12
|
+
- **Project a JSON Schema field tree** — a [caliper](https://github.com/coldsmirk/caliper) `SchemaTree` — onto an expression variable type.
|
|
13
13
|
- **Localize** all editor-produced text through a typed, extensible message catalog.
|
|
14
14
|
- **Strong typing** end to end, dual ESM / CJS, and a single lazily-loaded WebAssembly engine instance.
|
|
15
15
|
|
|
@@ -19,7 +19,7 @@ Part of [abacus](https://github.com/coldsmirk/abacus). To edit expressions in an
|
|
|
19
19
|
pnpm add @coldsmirk/abacus-core
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
The ZEN WebAssembly engine (`@gorules/zen-engine-wasm`)
|
|
22
|
+
The ZEN WebAssembly engine (`@gorules/zen-engine-wasm`) and the schema-tree model (`@coldsmirk/caliper-core`, whose `SchemaTree` appears in this package's public surface) are regular dependencies and install automatically. Node.js >= 24; browsers need ES2023 support (Chrome/Edge 111+, Safari 16.2+, Firefox 115+) — the bundles ship untranspiled.
|
|
23
23
|
|
|
24
24
|
## Quick start
|
|
25
25
|
|
|
@@ -216,46 +216,30 @@ interface ExpressionCompletion {
|
|
|
216
216
|
|
|
217
217
|
## JSON Schema field trees
|
|
218
218
|
|
|
219
|
-
The
|
|
219
|
+
The field-tree model itself lives in [`@coldsmirk/caliper-core`](https://www.npmjs.com/package/@coldsmirk/caliper-core) — `parseSchemaTree`, `serializeSchemaTree`, `inferSchema`, `newSchemaTreeField`, and the `SchemaTree*` types (with `<SchemaBuilder>` / `<JsonBuilder>` in `@coldsmirk/caliper-mantine`). This package owns only the bridge to ZEN: `schemaTreeToExpressionType` turns a `SchemaTree` into the variable type an expression is analyzed against.
|
|
220
220
|
|
|
221
221
|
```ts
|
|
222
|
-
import {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
serializeSchemaTree
|
|
227
|
-
} from "@coldsmirk/abacus-core";
|
|
228
|
-
|
|
229
|
-
const inferred = inferSchema({
|
|
222
|
+
import { schemaTreeToExpressionType } from "@coldsmirk/abacus-core";
|
|
223
|
+
import { inferSchema, parseSchemaTree } from "@coldsmirk/caliper-core";
|
|
224
|
+
|
|
225
|
+
const parsed = parseSchemaTree(JSON.stringify(inferSchema({
|
|
230
226
|
amount: 120,
|
|
231
227
|
customer: { name: "Ada" },
|
|
232
228
|
tags: ["priority"]
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
const parsed = parseSchemaTree(JSON.stringify(inferred));
|
|
229
|
+
})));
|
|
236
230
|
|
|
237
231
|
if (parsed.ok) {
|
|
238
|
-
serializeSchemaTree(parsed.tree); // pretty-printed schema JSON
|
|
239
232
|
schemaTreeToExpressionType(parsed.tree); // { Object: { amount: "Number", ... } }
|
|
240
|
-
} else {
|
|
241
|
-
parsed.issue; // a localizable SchemaTreeIssue
|
|
242
233
|
}
|
|
243
234
|
```
|
|
244
235
|
|
|
245
236
|
```ts
|
|
246
|
-
function inferSchema(sample: Json): Record<string, Json>;
|
|
247
|
-
function parseSchemaTree(text: string): SchemaTreeParseResult;
|
|
248
|
-
function serializeSchemaTree(tree: SchemaTree): string;
|
|
249
237
|
function schemaTreeToExpressionType(tree: SchemaTree): ExpressionType;
|
|
250
|
-
function newSchemaTreeField(overrides?: Partial<Omit<SchemaTreeField, "id">>): SchemaTreeField;
|
|
251
|
-
const SCHEMA_DIALECT_2020_12 = "https://json-schema.org/draft/2020-12/schema";
|
|
252
238
|
```
|
|
253
239
|
|
|
254
|
-
`
|
|
255
|
-
|
|
256
|
-
The parser preserves JSON Schema applicability, not merely its visible fields. A schema with `properties` or `items` but no `type` still permits instances of other types, so `SchemaTree.explicitType` and an object/array field's `explicitType` remember that omission and serialization does not add an object/array type. Hand-built trees that omit the optional metadata keep the original builder default and emit an explicit type.
|
|
240
|
+
`SchemaTree` is imported from `@coldsmirk/caliper-core` — a regular dependency of this package, so the emitted types resolve on install — and is deliberately **not** re-exported here: a caller holding a tree already imports the module that produced it.
|
|
257
241
|
|
|
258
|
-
|
|
242
|
+
Every named field becomes a typed variable: `string` → `String`, `number` / `integer` → `Number`, `boolean` → `Bool`, `object` → a nested `Object`, `array` → `Array` (elements `Any` when the source declared no `items`), and an untyped field → `Any`. Blank draft rows from `newSchemaTreeField()` are skipped, while blank and prototype-like property names that came from real `properties` keys (carrying caliper's `preserveBlankName`) stay in the scope.
|
|
259
243
|
|
|
260
244
|
## Compiling conditions
|
|
261
245
|
|
|
@@ -483,10 +467,10 @@ class ExpressionNotReadyError extends ExpressionError { // a *Sync helper calle
|
|
|
483
467
|
- **Templates** — `analyzeTemplate` / `analyzeTemplateSync`, `getTemplateDiagnostics` / `getTemplateDiagnosticsSync`, `parseTemplateHoles`, `templateHoleAt`
|
|
484
468
|
- **Conditions** — `compileCondition`, `compileGroup`, `compileBranch`, `selectBranch`, `selectBranchWith`, `toZenLiteral`, `isZenRepresentableNumber`, `CONDITION_OPERATORS`, `conditionOperatorArity`
|
|
485
469
|
- **Condition trees** — `compileConditionTree`, `liftConditionTree`, `emptyConditionGroup`, `newConditionNodeId`, `ensureConditionNodeIds`, `MAX_CONDITION_TREE_DEPTH`, `CONDITION_TREE_OPERATORS`
|
|
486
|
-
- **JSON Schema** — `
|
|
470
|
+
- **JSON Schema** — `schemaTreeToExpressionType` (the model itself is `@coldsmirk/caliper-core`)
|
|
487
471
|
- **Localization** — `configureExpressionMessages`, `registerExpressionLocale`, `getExpressionMessages`, `subscribeExpressionMessages`, `enMessages`, `zhCNMessages`
|
|
488
472
|
- **Errors** — `ExpressionError`, `ExpressionNotReadyError`
|
|
489
|
-
- **Types** — `ExpressionType`, `ExpressionMode`, `TemplateHole`, `ExpressionContext`, `ExpressionEngine`, `LoadEngineOptions`, `ExpressionAnalysis`, `ExpressionTypeSpan`, `ExpressionCompletion`, `ExpressionDiagnostic`, `ExpressionMessages`, `ExpressionMessagesListener`, `ExpressionLocale`, `BuiltInExpressionLocale`, `ConfigureMessagesOptions`, `ConditionInput`, `FieldConditionInput`, `ExpressionConditionInput`, `ConditionGroupInput`, `ConditionBranchInput`, `ConditionOperator`, `ConditionOperatorArity`, `BranchSelection`, `ConditionTreeGroup`, `ConditionTreeNode`, `ConditionTreeRule`, `ConditionTreeOperator`, `ConditionTreeValue`, `ConditionScalar
|
|
473
|
+
- **Types** — `ExpressionType`, `ExpressionMode`, `TemplateHole`, `ExpressionContext`, `ExpressionEngine`, `LoadEngineOptions`, `ExpressionAnalysis`, `ExpressionTypeSpan`, `ExpressionCompletion`, `ExpressionDiagnostic`, `ExpressionMessages`, `ExpressionMessagesListener`, `ExpressionLocale`, `BuiltInExpressionLocale`, `ConfigureMessagesOptions`, `ConditionInput`, `FieldConditionInput`, `ExpressionConditionInput`, `ConditionGroupInput`, `ConditionBranchInput`, `ConditionOperator`, `ConditionOperatorArity`, `BranchSelection`, `ConditionTreeGroup`, `ConditionTreeNode`, `ConditionTreeRule`, `ConditionTreeOperator`, `ConditionTreeValue`, `ConditionScalar`
|
|
490
474
|
|
|
491
475
|
## License
|
|
492
476
|
|
package/dist/index.cjs
CHANGED
|
@@ -361,7 +361,7 @@ function isZenConsistentNumber(value) {
|
|
|
361
361
|
const SUBJECT_PATTERN = /^[A-Z_$][\w$]*(?:\.[A-Z_$][\w$]*|\[\d+\])*$/i;
|
|
362
362
|
const SUBJECT_IDENTIFIER_PATTERN = /[A-Z_$][\w$]*/gi;
|
|
363
363
|
const SUBJECT_INDEX_PATTERN = /\[(?<index>\d+)\]/g;
|
|
364
|
-
const
|
|
364
|
+
const ZEN_RESERVED_WORDS = new Set([
|
|
365
365
|
"and",
|
|
366
366
|
"or",
|
|
367
367
|
"not",
|
|
@@ -371,10 +371,13 @@ const ZEN_ROOT_RESERVED_WORDS = new Set([
|
|
|
371
371
|
"null"
|
|
372
372
|
]);
|
|
373
373
|
const ZEN_MEMBER_RESERVED_WORDS = new Set(["true", "false"]);
|
|
374
|
+
function isZenReservedWord(word) {
|
|
375
|
+
return ZEN_RESERVED_WORDS.has(word);
|
|
376
|
+
}
|
|
374
377
|
function isIdentifierPath(subject) {
|
|
375
378
|
if (!SUBJECT_PATTERN.test(subject)) return false;
|
|
376
379
|
const [root, ...members] = subject.match(SUBJECT_IDENTIFIER_PATTERN) ?? [];
|
|
377
|
-
if (root === void 0 ||
|
|
380
|
+
if (root === void 0 || isZenReservedWord(root) || members.some((member) => ZEN_MEMBER_RESERVED_WORDS.has(member))) return false;
|
|
378
381
|
for (const match of subject.matchAll(SUBJECT_INDEX_PATTERN)) if (match.groups?.index === void 0 || !isZenUnsignedIntegerText(match.groups.index)) return false;
|
|
379
382
|
return true;
|
|
380
383
|
}
|
|
@@ -640,14 +643,14 @@ function compileRule(rule) {
|
|
|
640
643
|
function matchesOperatorArity(rule) {
|
|
641
644
|
switch (conditionOperatorArity(rule.operator)) {
|
|
642
645
|
case "scalar": return isConditionScalar(rule.right);
|
|
643
|
-
case "array": return
|
|
646
|
+
case "array": return isConditionScalarArray(rule.right);
|
|
644
647
|
case "none": return rule.right === void 0;
|
|
645
648
|
}
|
|
646
649
|
}
|
|
647
650
|
function isConditionScalar(value) {
|
|
648
651
|
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
649
652
|
}
|
|
650
|
-
function
|
|
653
|
+
function isConditionScalarArray(value) {
|
|
651
654
|
return isArray(value) && value.every((item) => isConditionScalar(item));
|
|
652
655
|
}
|
|
653
656
|
const TWO_CHAR_PUNCTUATION = new Set([
|
|
@@ -672,7 +675,7 @@ const IDENT_PART = /[\w$]/;
|
|
|
672
675
|
const DIGIT = /\d/;
|
|
673
676
|
const NUMBER_PATTERN = /^\d+(?:\.\d+)?(?:e[+-]?\d+)?/i;
|
|
674
677
|
const INTEGER_PATTERN = /^\d+$/;
|
|
675
|
-
function
|
|
678
|
+
function parseCanonicalZenNumber(text) {
|
|
676
679
|
const value = Number(text);
|
|
677
680
|
return String(value) === text && isZenRepresentableNumber(value) ? value : null;
|
|
678
681
|
}
|
|
@@ -734,7 +737,7 @@ function liftConditionTree(expression) {
|
|
|
734
737
|
return token.value;
|
|
735
738
|
}
|
|
736
739
|
if (token.kind === "number") {
|
|
737
|
-
const value =
|
|
740
|
+
const value = parseCanonicalZenNumber(token.value);
|
|
738
741
|
if (value === null) return null;
|
|
739
742
|
pos += 1;
|
|
740
743
|
return value;
|
|
@@ -753,7 +756,7 @@ function liftConditionTree(expression) {
|
|
|
753
756
|
if (token.kind === "punct" && token.value === "-") {
|
|
754
757
|
const digits = peek(1);
|
|
755
758
|
if (digits === void 0 || digits.kind !== "number") return null;
|
|
756
|
-
const value =
|
|
759
|
+
const value = parseCanonicalZenNumber(digits.value);
|
|
757
760
|
if (value === null || value === 0 || !isZenRepresentableNumber(-value)) return null;
|
|
758
761
|
pos += 2;
|
|
759
762
|
return -value;
|
|
@@ -1258,6 +1261,23 @@ async function satisfiesType(actual, expected) {
|
|
|
1258
1261
|
function satisfiesTypeSync(actual, expected) {
|
|
1259
1262
|
return getEngineSync().satisfies(actual, expected);
|
|
1260
1263
|
}
|
|
1264
|
+
function fieldType(field) {
|
|
1265
|
+
switch (field.type) {
|
|
1266
|
+
case "string": return "String";
|
|
1267
|
+
case "number": return "Number";
|
|
1268
|
+
case "integer": return "Number";
|
|
1269
|
+
case "boolean": return "Bool";
|
|
1270
|
+
case "object": return { Object: fieldRecord(field.children) };
|
|
1271
|
+
case "array": return { Array: field.items === null ? "Any" : fieldType(field.items) };
|
|
1272
|
+
case "any": return "Any";
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
function fieldRecord(fields) {
|
|
1276
|
+
return Object.fromEntries(fields.filter((field) => field.name.trim() !== "" || field.preserveBlankName === true).map((field) => [field.name, fieldType(field)]));
|
|
1277
|
+
}
|
|
1278
|
+
function schemaTreeToExpressionType(tree) {
|
|
1279
|
+
return { Object: fieldRecord(tree.fields) };
|
|
1280
|
+
}
|
|
1261
1281
|
const HOLE_PATTERN = /\{\{(?<expression>[^{}]*)\}\}/g;
|
|
1262
1282
|
function parseTemplateHoles(source) {
|
|
1263
1283
|
const holes = [];
|
|
@@ -1318,301 +1338,11 @@ async function getTemplateDiagnostics(source) {
|
|
|
1318
1338
|
await loadEngine();
|
|
1319
1339
|
return getTemplateDiagnosticsSync(source);
|
|
1320
1340
|
}
|
|
1321
|
-
function fieldType(field) {
|
|
1322
|
-
switch (field.type) {
|
|
1323
|
-
case "string": return "String";
|
|
1324
|
-
case "number": return "Number";
|
|
1325
|
-
case "integer": return "Number";
|
|
1326
|
-
case "boolean": return "Bool";
|
|
1327
|
-
case "object": return { Object: fieldRecord(field.children) };
|
|
1328
|
-
case "array": return { Array: field.items === null ? "Any" : fieldType(field.items) };
|
|
1329
|
-
case "any": return "Any";
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
function fieldRecord(fields) {
|
|
1333
|
-
return Object.fromEntries(fields.filter((field) => field.name.trim() !== "" || field.preserveBlankName === true).map((field) => [field.name, fieldType(field)]));
|
|
1334
|
-
}
|
|
1335
|
-
function schemaTreeToExpressionType(tree) {
|
|
1336
|
-
return { Object: fieldRecord(tree.fields) };
|
|
1337
|
-
}
|
|
1338
|
-
function mergeSchemas(a, b) {
|
|
1339
|
-
if (a.type === "object" && b.type === "object") {
|
|
1340
|
-
const left = a.properties ?? {};
|
|
1341
|
-
const right = b.properties ?? {};
|
|
1342
|
-
const entries = [];
|
|
1343
|
-
const keys = new Set([...Object.keys(left), ...Object.keys(right)]);
|
|
1344
|
-
for (const key of keys) {
|
|
1345
|
-
const hasLeft = Object.hasOwn(left, key);
|
|
1346
|
-
const hasRight = Object.hasOwn(right, key);
|
|
1347
|
-
entries.push([key, hasLeft && hasRight ? mergeSchemas(left[key], right[key]) : hasLeft ? left[key] : right[key]]);
|
|
1348
|
-
}
|
|
1349
|
-
const properties = Object.fromEntries(entries);
|
|
1350
|
-
return Object.keys(properties).length > 0 ? {
|
|
1351
|
-
type: "object",
|
|
1352
|
-
properties
|
|
1353
|
-
} : { type: "object" };
|
|
1354
|
-
}
|
|
1355
|
-
if (a.type === "array" && b.type === "array") return a.items !== void 0 && b.items !== void 0 ? {
|
|
1356
|
-
type: "array",
|
|
1357
|
-
items: mergeSchemas(a.items, b.items)
|
|
1358
|
-
} : { type: "array" };
|
|
1359
|
-
return JSON.stringify(a) === JSON.stringify(b) ? a : {};
|
|
1360
|
-
}
|
|
1361
|
-
function inferSchema(sample) {
|
|
1362
|
-
if (sample === null) return {};
|
|
1363
|
-
if (Array.isArray(sample)) {
|
|
1364
|
-
if (sample.length === 0) return { type: "array" };
|
|
1365
|
-
return {
|
|
1366
|
-
type: "array",
|
|
1367
|
-
items: sample.map((element) => inferSchema(element)).reduce((left, right) => mergeSchemas(left, right))
|
|
1368
|
-
};
|
|
1369
|
-
}
|
|
1370
|
-
if (typeof sample === "string") return { type: "string" };
|
|
1371
|
-
if (typeof sample === "number") return { type: "number" };
|
|
1372
|
-
if (typeof sample === "boolean") return { type: "boolean" };
|
|
1373
|
-
const properties = Object.fromEntries(Object.entries(sample).map(([key, value]) => [key, inferSchema(value)]));
|
|
1374
|
-
return Object.keys(properties).length > 0 ? {
|
|
1375
|
-
type: "object",
|
|
1376
|
-
properties
|
|
1377
|
-
} : { type: "object" };
|
|
1378
|
-
}
|
|
1379
|
-
const SCHEMA_DIALECT_2020_12 = "https://json-schema.org/draft/2020-12/schema";
|
|
1380
|
-
const fieldIdRealm = Math.random().toString(36).slice(2, 7);
|
|
1381
|
-
let fieldIdCounter = 0;
|
|
1382
|
-
function newSchemaTreeField(overrides = {}) {
|
|
1383
|
-
fieldIdCounter += 1;
|
|
1384
|
-
return {
|
|
1385
|
-
id: `sf-${fieldIdRealm}-${fieldIdCounter}`,
|
|
1386
|
-
name: "",
|
|
1387
|
-
type: "string",
|
|
1388
|
-
required: false,
|
|
1389
|
-
description: "",
|
|
1390
|
-
children: [],
|
|
1391
|
-
items: null,
|
|
1392
|
-
...overrides
|
|
1393
|
-
};
|
|
1394
|
-
}
|
|
1395
|
-
const SCALAR_TYPES = new Set([
|
|
1396
|
-
"string",
|
|
1397
|
-
"number",
|
|
1398
|
-
"integer",
|
|
1399
|
-
"boolean"
|
|
1400
|
-
]);
|
|
1401
|
-
const ROOT_KEYS = new Set([
|
|
1402
|
-
"$schema",
|
|
1403
|
-
"type",
|
|
1404
|
-
"properties",
|
|
1405
|
-
"required",
|
|
1406
|
-
"description"
|
|
1407
|
-
]);
|
|
1408
|
-
const SUBSCHEMA_KEYS = new Set([
|
|
1409
|
-
"type",
|
|
1410
|
-
"properties",
|
|
1411
|
-
"required",
|
|
1412
|
-
"items",
|
|
1413
|
-
"description"
|
|
1414
|
-
]);
|
|
1415
|
-
var Unsupported = class extends Error {
|
|
1416
|
-
issue;
|
|
1417
|
-
constructor(issue) {
|
|
1418
|
-
super(issue.code);
|
|
1419
|
-
this.issue = issue;
|
|
1420
|
-
}
|
|
1421
|
-
};
|
|
1422
|
-
function asPlainObject(value) {
|
|
1423
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
1424
|
-
}
|
|
1425
|
-
function checkKeys(schema, allowed) {
|
|
1426
|
-
for (const key of Object.keys(schema)) if (!allowed.has(key)) throw new Unsupported({
|
|
1427
|
-
code: "unsupported-keyword",
|
|
1428
|
-
keyword: key
|
|
1429
|
-
});
|
|
1430
|
-
}
|
|
1431
|
-
function parseObjectFields(schema) {
|
|
1432
|
-
const properties = schema.properties === void 0 ? null : asPlainObject(schema.properties);
|
|
1433
|
-
if (schema.properties !== void 0 && properties === null) throw new Unsupported({ code: "invalid-properties" });
|
|
1434
|
-
const fields = [];
|
|
1435
|
-
const entries = Object.entries(properties ?? {});
|
|
1436
|
-
for (const [name, subschema] of entries) fields.push({
|
|
1437
|
-
...parseSubschema(subschema, name),
|
|
1438
|
-
name,
|
|
1439
|
-
...name.trim() === "" && { preserveBlankName: true }
|
|
1440
|
-
});
|
|
1441
|
-
if (schema.required !== void 0) {
|
|
1442
|
-
if (!Array.isArray(schema.required)) throw new Unsupported({ code: "invalid-required" });
|
|
1443
|
-
for (const entry of schema.required) {
|
|
1444
|
-
if (typeof entry !== "string") throw new Unsupported({ code: "invalid-required" });
|
|
1445
|
-
const field = fields.find((candidate) => candidate.name === entry);
|
|
1446
|
-
if (field === void 0) throw new Unsupported({
|
|
1447
|
-
code: "unknown-required-field",
|
|
1448
|
-
field: entry
|
|
1449
|
-
});
|
|
1450
|
-
field.required = true;
|
|
1451
|
-
}
|
|
1452
|
-
}
|
|
1453
|
-
return fields;
|
|
1454
|
-
}
|
|
1455
|
-
function parseDescription(schema) {
|
|
1456
|
-
if (schema.description === void 0) return "";
|
|
1457
|
-
if (typeof schema.description !== "string") throw new Unsupported({ code: "invalid-description" });
|
|
1458
|
-
return schema.description;
|
|
1459
|
-
}
|
|
1460
|
-
function ensureAbsent(schema, key, holder) {
|
|
1461
|
-
if (schema[key] !== void 0) throw new Unsupported({
|
|
1462
|
-
code: "misplaced-keyword",
|
|
1463
|
-
keyword: key,
|
|
1464
|
-
holder
|
|
1465
|
-
});
|
|
1466
|
-
}
|
|
1467
|
-
function parseSubschema(value, name) {
|
|
1468
|
-
const schema = asPlainObject(value);
|
|
1469
|
-
if (schema === null) throw new Unsupported({
|
|
1470
|
-
code: "invalid-field-definition",
|
|
1471
|
-
field: name
|
|
1472
|
-
});
|
|
1473
|
-
checkKeys(schema, SUBSCHEMA_KEYS);
|
|
1474
|
-
const { type } = schema;
|
|
1475
|
-
if (type !== void 0 && typeof type !== "string") throw new Unsupported({
|
|
1476
|
-
code: "invalid-field-type",
|
|
1477
|
-
field: name
|
|
1478
|
-
});
|
|
1479
|
-
const description = parseDescription(schema);
|
|
1480
|
-
if (type === "object" || type === void 0 && (schema.properties !== void 0 || schema.required !== void 0)) {
|
|
1481
|
-
ensureAbsent(schema, "items", "array");
|
|
1482
|
-
return newSchemaTreeField({
|
|
1483
|
-
type: "object",
|
|
1484
|
-
description,
|
|
1485
|
-
children: parseObjectFields(schema),
|
|
1486
|
-
explicitType: type === "object"
|
|
1487
|
-
});
|
|
1488
|
-
}
|
|
1489
|
-
if (type === "array" || type === void 0 && schema.items !== void 0) {
|
|
1490
|
-
ensureAbsent(schema, "properties", "object");
|
|
1491
|
-
ensureAbsent(schema, "required", "object");
|
|
1492
|
-
return newSchemaTreeField({
|
|
1493
|
-
type: "array",
|
|
1494
|
-
description,
|
|
1495
|
-
items: schema.items === void 0 ? null : parseSubschema(schema.items, name),
|
|
1496
|
-
explicitType: type === "array"
|
|
1497
|
-
});
|
|
1498
|
-
}
|
|
1499
|
-
if (type === void 0) {
|
|
1500
|
-
ensureAbsent(schema, "items", "array");
|
|
1501
|
-
return newSchemaTreeField({
|
|
1502
|
-
type: "any",
|
|
1503
|
-
description
|
|
1504
|
-
});
|
|
1505
|
-
}
|
|
1506
|
-
if (!SCALAR_TYPES.has(type)) throw new Unsupported({
|
|
1507
|
-
code: "unsupported-field-type",
|
|
1508
|
-
field: name,
|
|
1509
|
-
type
|
|
1510
|
-
});
|
|
1511
|
-
ensureAbsent(schema, "properties", "object");
|
|
1512
|
-
ensureAbsent(schema, "required", "object");
|
|
1513
|
-
ensureAbsent(schema, "items", "array");
|
|
1514
|
-
return newSchemaTreeField({
|
|
1515
|
-
type,
|
|
1516
|
-
description
|
|
1517
|
-
});
|
|
1518
|
-
}
|
|
1519
|
-
function parseSchemaTree(text) {
|
|
1520
|
-
if (text.trim() === "") return {
|
|
1521
|
-
ok: true,
|
|
1522
|
-
tree: {
|
|
1523
|
-
fields: [],
|
|
1524
|
-
dialect: false,
|
|
1525
|
-
description: ""
|
|
1526
|
-
}
|
|
1527
|
-
};
|
|
1528
|
-
let parsed;
|
|
1529
|
-
try {
|
|
1530
|
-
parsed = JSON.parse(text);
|
|
1531
|
-
} catch {
|
|
1532
|
-
return {
|
|
1533
|
-
ok: false,
|
|
1534
|
-
issue: { code: "invalid-json" }
|
|
1535
|
-
};
|
|
1536
|
-
}
|
|
1537
|
-
try {
|
|
1538
|
-
const root = asPlainObject(parsed);
|
|
1539
|
-
if (root === null) throw new Unsupported({ code: "root-not-object" });
|
|
1540
|
-
checkKeys(root, ROOT_KEYS);
|
|
1541
|
-
const dialect = root.$schema !== void 0;
|
|
1542
|
-
if (dialect && root.$schema !== "https://json-schema.org/draft/2020-12/schema") throw new Unsupported({ code: "unsupported-dialect" });
|
|
1543
|
-
if (root.type !== void 0 && root.type !== "object") throw new Unsupported({ code: "root-not-object" });
|
|
1544
|
-
return {
|
|
1545
|
-
ok: true,
|
|
1546
|
-
tree: {
|
|
1547
|
-
fields: parseObjectFields(root),
|
|
1548
|
-
dialect,
|
|
1549
|
-
description: parseDescription(root),
|
|
1550
|
-
explicitType: root.type === "object"
|
|
1551
|
-
}
|
|
1552
|
-
};
|
|
1553
|
-
} catch (error) {
|
|
1554
|
-
if (error instanceof Unsupported) return {
|
|
1555
|
-
ok: false,
|
|
1556
|
-
issue: error.issue
|
|
1557
|
-
};
|
|
1558
|
-
throw error;
|
|
1559
|
-
}
|
|
1560
|
-
}
|
|
1561
|
-
function serializeObjectBody(fields) {
|
|
1562
|
-
const named = fields.filter((field) => field.name.trim() !== "" || field.preserveBlankName === true);
|
|
1563
|
-
const finalByName = /* @__PURE__ */ new Map();
|
|
1564
|
-
const finalFields = [];
|
|
1565
|
-
const body = {};
|
|
1566
|
-
for (const field of named) finalByName.set(field.name, field);
|
|
1567
|
-
finalByName.forEach((field) => {
|
|
1568
|
-
finalFields.push(field);
|
|
1569
|
-
});
|
|
1570
|
-
if (finalFields.length > 0) body.properties = Object.fromEntries(finalFields.map((field) => [field.name, serializeField(field)]));
|
|
1571
|
-
const required = finalFields.filter((field) => field.required).map((field) => field.name);
|
|
1572
|
-
if (required.length > 0) body.required = required;
|
|
1573
|
-
return body;
|
|
1574
|
-
}
|
|
1575
|
-
function serializeField(field) {
|
|
1576
|
-
const description = field.description === "" ? {} : { description: field.description };
|
|
1577
|
-
switch (field.type) {
|
|
1578
|
-
case "any": return { ...description };
|
|
1579
|
-
case "object": return {
|
|
1580
|
-
...field.explicitType !== false && { type: "object" },
|
|
1581
|
-
...description,
|
|
1582
|
-
...serializeObjectBody(field.children)
|
|
1583
|
-
};
|
|
1584
|
-
case "array": {
|
|
1585
|
-
const type = field.explicitType === false ? {} : { type: "array" };
|
|
1586
|
-
return field.items === null || field.items.type === "any" && field.items.description === "" ? {
|
|
1587
|
-
...type,
|
|
1588
|
-
...description
|
|
1589
|
-
} : {
|
|
1590
|
-
...type,
|
|
1591
|
-
...description,
|
|
1592
|
-
items: serializeField(field.items)
|
|
1593
|
-
};
|
|
1594
|
-
}
|
|
1595
|
-
default: return {
|
|
1596
|
-
type: field.type,
|
|
1597
|
-
...description
|
|
1598
|
-
};
|
|
1599
|
-
}
|
|
1600
|
-
}
|
|
1601
|
-
function serializeSchemaTree(tree) {
|
|
1602
|
-
const root = {
|
|
1603
|
-
...tree.dialect && { $schema: "https://json-schema.org/draft/2020-12/schema" },
|
|
1604
|
-
...tree.explicitType !== false && { type: "object" },
|
|
1605
|
-
...tree.description !== "" && { description: tree.description },
|
|
1606
|
-
...serializeObjectBody(tree.fields)
|
|
1607
|
-
};
|
|
1608
|
-
return `${JSON.stringify(root, null, 2)}\n`;
|
|
1609
|
-
}
|
|
1610
1341
|
exports.CONDITION_OPERATORS = CONDITION_OPERATORS;
|
|
1611
1342
|
exports.CONDITION_TREE_OPERATORS = CONDITION_TREE_OPERATORS;
|
|
1612
1343
|
exports.ExpressionError = ExpressionError;
|
|
1613
1344
|
exports.ExpressionNotReadyError = ExpressionNotReadyError;
|
|
1614
1345
|
exports.MAX_CONDITION_TREE_DEPTH = MAX_CONDITION_TREE_DEPTH;
|
|
1615
|
-
exports.SCHEMA_DIALECT_2020_12 = SCHEMA_DIALECT_2020_12;
|
|
1616
1346
|
exports.analyzeTemplate = analyzeTemplate;
|
|
1617
1347
|
exports.analyzeTemplateSync = analyzeTemplateSync;
|
|
1618
1348
|
exports.analyzeTypes = analyzeTypes;
|
|
@@ -1640,14 +1370,14 @@ exports.getEngineSync = getEngineSync;
|
|
|
1640
1370
|
exports.getExpressionMessages = getExpressionMessages;
|
|
1641
1371
|
exports.getTemplateDiagnostics = getTemplateDiagnostics;
|
|
1642
1372
|
exports.getTemplateDiagnosticsSync = getTemplateDiagnosticsSync;
|
|
1643
|
-
exports.
|
|
1373
|
+
exports.isConditionScalarArray = isConditionScalarArray;
|
|
1644
1374
|
exports.isEngineReady = isEngineReady;
|
|
1645
1375
|
exports.isZenRepresentableNumber = isZenRepresentableNumber;
|
|
1376
|
+
exports.isZenReservedWord = isZenReservedWord;
|
|
1646
1377
|
exports.liftConditionTree = liftConditionTree;
|
|
1647
1378
|
exports.loadEngine = loadEngine;
|
|
1648
1379
|
exports.newConditionNodeId = newConditionNodeId;
|
|
1649
|
-
exports.
|
|
1650
|
-
exports.parseSchemaTree = parseSchemaTree;
|
|
1380
|
+
exports.parseCanonicalZenNumber = parseCanonicalZenNumber;
|
|
1651
1381
|
exports.parseTemplateHoles = parseTemplateHoles;
|
|
1652
1382
|
exports.registerExpressionLocale = registerExpressionLocale;
|
|
1653
1383
|
exports.resetEngine = resetEngine;
|
|
@@ -1656,7 +1386,6 @@ exports.satisfiesTypeSync = satisfiesTypeSync;
|
|
|
1656
1386
|
exports.schemaTreeToExpressionType = schemaTreeToExpressionType;
|
|
1657
1387
|
exports.selectBranch = selectBranch;
|
|
1658
1388
|
exports.selectBranchWith = selectBranchWith;
|
|
1659
|
-
exports.serializeSchemaTree = serializeSchemaTree;
|
|
1660
1389
|
exports.subscribeExpressionMessages = subscribeExpressionMessages;
|
|
1661
1390
|
exports.templateHoleAt = templateHoleAt;
|
|
1662
1391
|
exports.toZenLiteral = toZenLiteral;
|