@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 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 and infer JSON Schema** through a lossless field-tree subset, then turn that tree into an expression variable type.
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`) is a regular dependency and installs automatically. Node.js >= 24; browsers need ES2023 support (Chrome/Edge 111+, Safari 16.2+, Firefox 115+) — the bundles ship untranspiled.
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 schema helpers provide the data model behind a visual JSON Schema builder without depending on a UI framework. The tree hosts an object-oriented draft 2020-12 subset: `type`, `properties`, `items`, `required`, and `description`, plus the optional root `$schema` declaration. Parsing refuses every unsupported construct with a structured `SchemaTreeIssue`; it never accepts a keyword and then silently removes it on serialization.
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
- inferSchema,
224
- parseSchemaTree,
225
- schemaTreeToExpressionType,
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
- `inferSchema` is intentionally conservative: integer samples infer `number`, no property becomes `required` from one observation, `null` says nothing (`{}`), and heterogeneous arrays merge to the loosest schema the field-tree subset can express.
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
- Property names are arbitrary JSON strings. Empty, whitespace-only, and prototype-like names such as `__proto__` round-trip and enter the generated `ExpressionType`. A parsed blank name carries `preserveBlankName: true`; a blank row from `newSchemaTreeField()` has no marker and remains an unfinished UI draft that is omitted from serialization and expression types. Hosts editing a parsed tree should update nodes immutably (for example, with object spread) so these optional source-preservation fields survive.
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** — `inferSchema`, `parseSchemaTree`, `serializeSchemaTree`, `schemaTreeToExpressionType`, `newSchemaTreeField`, `SCHEMA_DIALECT_2020_12`
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`, `Json`, `SchemaTree`, `SchemaTreeField`, `SchemaTreeFieldType`, `SchemaTreeIssue`, `SchemaTreeParseResult`
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 ZEN_ROOT_RESERVED_WORDS = new Set([
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 || ZEN_ROOT_RESERVED_WORDS.has(root) || members.some((member) => ZEN_MEMBER_RESERVED_WORDS.has(member))) return false;
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 isScalarArray(rule.right);
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 isScalarArray(value) {
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 parseCanonicalNumber(text) {
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 = parseCanonicalNumber(token.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 = parseCanonicalNumber(digits.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.inferSchema = inferSchema;
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.newSchemaTreeField = newSchemaTreeField;
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;