@coldsmirk/abacus-core 0.9.0 → 0.10.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 +17 -294
- package/dist/index.d.cts +9 -155
- package/dist/index.d.ts +9 -155
- package/dist/index.js +18 -290
- 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
|
@@ -1258,6 +1258,23 @@ async function satisfiesType(actual, expected) {
|
|
|
1258
1258
|
function satisfiesTypeSync(actual, expected) {
|
|
1259
1259
|
return getEngineSync().satisfies(actual, expected);
|
|
1260
1260
|
}
|
|
1261
|
+
function fieldType(field) {
|
|
1262
|
+
switch (field.type) {
|
|
1263
|
+
case "string": return "String";
|
|
1264
|
+
case "number": return "Number";
|
|
1265
|
+
case "integer": return "Number";
|
|
1266
|
+
case "boolean": return "Bool";
|
|
1267
|
+
case "object": return { Object: fieldRecord(field.children) };
|
|
1268
|
+
case "array": return { Array: field.items === null ? "Any" : fieldType(field.items) };
|
|
1269
|
+
case "any": return "Any";
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
function fieldRecord(fields) {
|
|
1273
|
+
return Object.fromEntries(fields.filter((field) => field.name.trim() !== "" || field.preserveBlankName === true).map((field) => [field.name, fieldType(field)]));
|
|
1274
|
+
}
|
|
1275
|
+
function schemaTreeToExpressionType(tree) {
|
|
1276
|
+
return { Object: fieldRecord(tree.fields) };
|
|
1277
|
+
}
|
|
1261
1278
|
const HOLE_PATTERN = /\{\{(?<expression>[^{}]*)\}\}/g;
|
|
1262
1279
|
function parseTemplateHoles(source) {
|
|
1263
1280
|
const holes = [];
|
|
@@ -1318,301 +1335,11 @@ async function getTemplateDiagnostics(source) {
|
|
|
1318
1335
|
await loadEngine();
|
|
1319
1336
|
return getTemplateDiagnosticsSync(source);
|
|
1320
1337
|
}
|
|
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
1338
|
exports.CONDITION_OPERATORS = CONDITION_OPERATORS;
|
|
1611
1339
|
exports.CONDITION_TREE_OPERATORS = CONDITION_TREE_OPERATORS;
|
|
1612
1340
|
exports.ExpressionError = ExpressionError;
|
|
1613
1341
|
exports.ExpressionNotReadyError = ExpressionNotReadyError;
|
|
1614
1342
|
exports.MAX_CONDITION_TREE_DEPTH = MAX_CONDITION_TREE_DEPTH;
|
|
1615
|
-
exports.SCHEMA_DIALECT_2020_12 = SCHEMA_DIALECT_2020_12;
|
|
1616
1343
|
exports.analyzeTemplate = analyzeTemplate;
|
|
1617
1344
|
exports.analyzeTemplateSync = analyzeTemplateSync;
|
|
1618
1345
|
exports.analyzeTypes = analyzeTypes;
|
|
@@ -1640,14 +1367,11 @@ exports.getEngineSync = getEngineSync;
|
|
|
1640
1367
|
exports.getExpressionMessages = getExpressionMessages;
|
|
1641
1368
|
exports.getTemplateDiagnostics = getTemplateDiagnostics;
|
|
1642
1369
|
exports.getTemplateDiagnosticsSync = getTemplateDiagnosticsSync;
|
|
1643
|
-
exports.inferSchema = inferSchema;
|
|
1644
1370
|
exports.isEngineReady = isEngineReady;
|
|
1645
1371
|
exports.isZenRepresentableNumber = isZenRepresentableNumber;
|
|
1646
1372
|
exports.liftConditionTree = liftConditionTree;
|
|
1647
1373
|
exports.loadEngine = loadEngine;
|
|
1648
1374
|
exports.newConditionNodeId = newConditionNodeId;
|
|
1649
|
-
exports.newSchemaTreeField = newSchemaTreeField;
|
|
1650
|
-
exports.parseSchemaTree = parseSchemaTree;
|
|
1651
1375
|
exports.parseTemplateHoles = parseTemplateHoles;
|
|
1652
1376
|
exports.registerExpressionLocale = registerExpressionLocale;
|
|
1653
1377
|
exports.resetEngine = resetEngine;
|
|
@@ -1656,7 +1380,6 @@ exports.satisfiesTypeSync = satisfiesTypeSync;
|
|
|
1656
1380
|
exports.schemaTreeToExpressionType = schemaTreeToExpressionType;
|
|
1657
1381
|
exports.selectBranch = selectBranch;
|
|
1658
1382
|
exports.selectBranchWith = selectBranchWith;
|
|
1659
|
-
exports.serializeSchemaTree = serializeSchemaTree;
|
|
1660
1383
|
exports.subscribeExpressionMessages = subscribeExpressionMessages;
|
|
1661
1384
|
exports.templateHoleAt = templateHoleAt;
|
|
1662
1385
|
exports.toZenLiteral = toZenLiteral;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { InitInput } from "@gorules/zen-engine-wasm";
|
|
2
|
+
import { SchemaTree } from "@coldsmirk/caliper-core";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* How an expression editor interprets its document:
|
|
@@ -669,6 +670,13 @@ declare function getExpressionMessages(): ExpressionMessages;
|
|
|
669
670
|
* function; listeners run synchronously after the new catalog becomes active.
|
|
670
671
|
*/
|
|
671
672
|
declare function subscribeExpressionMessages(listener: ExpressionMessagesListener): () => void;
|
|
673
|
+
/**
|
|
674
|
+
* The expression scope a schema tree declares: each named field becomes a typed variable, so a
|
|
675
|
+
* host can hand the result straight to `<ExpressionInput variables={…}>` (or `analyzeTypes`)
|
|
676
|
+
* and complete against the declared shape. Blank draft rows are skipped, while blank property
|
|
677
|
+
* names preserved by `@coldsmirk/caliper-core`'s `parseSchemaTree` remain part of the scope.
|
|
678
|
+
*/
|
|
679
|
+
declare function schemaTreeToExpressionType(tree: SchemaTree): ExpressionType;
|
|
672
680
|
/**
|
|
673
681
|
* One `{{ expression }}` hole located in a template document. `from` / `to` are
|
|
674
682
|
* the character offsets of the inner expression itself — the text between the
|
|
@@ -735,158 +743,4 @@ declare function getTemplateDiagnosticsSync(source: string): ExpressionDiagnosti
|
|
|
735
743
|
* Async {@link getTemplateDiagnosticsSync}, loading the engine on first use.
|
|
736
744
|
*/
|
|
737
745
|
declare function getTemplateDiagnostics(source: string): Promise<ExpressionDiagnostic[]>;
|
|
738
|
-
|
|
739
|
-
* The field-tree model behind the visual JSON Schema builder: a lossless projection of the
|
|
740
|
-
* JSON Schema subset the tree can host (`type` / `properties` / `items`, plus `required`).
|
|
741
|
-
* The schema JSON stays the single source of truth: {@link parseSchemaTree} REFUSES anything
|
|
742
|
-
* outside the subset (never a lossy rewrite), so a hand-written `oneOf` or `format` keeps its
|
|
743
|
-
* document JSON-only instead of silently vanishing on the first visual edit.
|
|
744
|
-
*/
|
|
745
|
-
/**
|
|
746
|
-
* The one dialect the tree round-trips: a document may omit `$schema` or declare exactly this.
|
|
747
|
-
*/
|
|
748
|
-
declare const SCHEMA_DIALECT_2020_12 = "https://json-schema.org/draft/2020-12/schema";
|
|
749
|
-
type SchemaTreeFieldType = "any" | "array" | "boolean" | "integer" | "number" | "object" | "string";
|
|
750
|
-
interface SchemaTreeField {
|
|
751
|
-
/**
|
|
752
|
-
* Stable row identity for React keys and targeted edits — a UI concern, never serialized.
|
|
753
|
-
*/
|
|
754
|
-
id: string;
|
|
755
|
-
/**
|
|
756
|
-
* The property name under its parent object; unused on an array-element node.
|
|
757
|
-
*/
|
|
758
|
-
name: string;
|
|
759
|
-
type: SchemaTreeFieldType;
|
|
760
|
-
/**
|
|
761
|
-
* Listed in the parent object's `required`; unused on an array-element node.
|
|
762
|
-
*/
|
|
763
|
-
required: boolean;
|
|
764
|
-
/**
|
|
765
|
-
* The schema's `description` annotation; blank means none (not serialized).
|
|
766
|
-
*/
|
|
767
|
-
description: string;
|
|
768
|
-
/**
|
|
769
|
-
* Sub-fields, meaningful when `type` is `object`.
|
|
770
|
-
*/
|
|
771
|
-
children: SchemaTreeField[];
|
|
772
|
-
/**
|
|
773
|
-
* The element node, meaningful when `type` is `array`; `null` means untyped elements.
|
|
774
|
-
*/
|
|
775
|
-
items: SchemaTreeField | null;
|
|
776
|
-
/**
|
|
777
|
-
* For an object or array node parsed from JSON, whether its source explicitly
|
|
778
|
-
* declared that type. `false` preserves a type-less applicator; omitted keeps the
|
|
779
|
-
* historical hand-built-tree default of emitting the type.
|
|
780
|
-
*/
|
|
781
|
-
explicitType?: boolean;
|
|
782
|
-
/**
|
|
783
|
-
* Whether a blank / whitespace-only name came from a real `properties` key and must be
|
|
784
|
-
* persisted. Omitted blank names are unfinished UI rows and do not serialize.
|
|
785
|
-
*/
|
|
786
|
-
preserveBlankName?: boolean;
|
|
787
|
-
}
|
|
788
|
-
interface SchemaTree {
|
|
789
|
-
fields: SchemaTreeField[];
|
|
790
|
-
/**
|
|
791
|
-
* Whether the source declared `$schema` (re-emitted verbatim on serialize).
|
|
792
|
-
*/
|
|
793
|
-
dialect: boolean;
|
|
794
|
-
/**
|
|
795
|
-
* The root schema's `description` annotation, preserved verbatim across the round trip
|
|
796
|
-
* (the field view offers no root-level editor); blank means none.
|
|
797
|
-
*/
|
|
798
|
-
description: string;
|
|
799
|
-
/**
|
|
800
|
-
* For a tree parsed from JSON, whether the root explicitly declared `type: "object"`.
|
|
801
|
-
* `false` preserves a type-less root; omitted keeps the historical hand-built-tree default
|
|
802
|
-
* of emitting the type.
|
|
803
|
-
*/
|
|
804
|
-
explicitType?: boolean;
|
|
805
|
-
}
|
|
806
|
-
/**
|
|
807
|
-
* Why a document cannot be projected onto the field tree — structured so a UI can localize
|
|
808
|
-
* the explanation. Every code names the offending part where one exists.
|
|
809
|
-
*/
|
|
810
|
-
type SchemaTreeIssue = {
|
|
811
|
-
code: "invalid-description";
|
|
812
|
-
} | {
|
|
813
|
-
code: "invalid-field-definition";
|
|
814
|
-
field: string;
|
|
815
|
-
} | {
|
|
816
|
-
code: "invalid-field-type";
|
|
817
|
-
field: string;
|
|
818
|
-
} | {
|
|
819
|
-
code: "invalid-json";
|
|
820
|
-
} | {
|
|
821
|
-
code: "invalid-properties";
|
|
822
|
-
} | {
|
|
823
|
-
code: "invalid-required";
|
|
824
|
-
} | {
|
|
825
|
-
code: "misplaced-keyword";
|
|
826
|
-
keyword: string;
|
|
827
|
-
holder: "array" | "object";
|
|
828
|
-
} | {
|
|
829
|
-
code: "root-not-object";
|
|
830
|
-
} | {
|
|
831
|
-
code: "unknown-required-field";
|
|
832
|
-
field: string;
|
|
833
|
-
} | {
|
|
834
|
-
code: "unsupported-dialect";
|
|
835
|
-
} | {
|
|
836
|
-
code: "unsupported-field-type";
|
|
837
|
-
field: string;
|
|
838
|
-
type: string;
|
|
839
|
-
} | {
|
|
840
|
-
code: "unsupported-keyword";
|
|
841
|
-
keyword: string;
|
|
842
|
-
};
|
|
843
|
-
type SchemaTreeParseResult = {
|
|
844
|
-
ok: true;
|
|
845
|
-
tree: SchemaTree;
|
|
846
|
-
} | {
|
|
847
|
-
ok: false;
|
|
848
|
-
issue: SchemaTreeIssue;
|
|
849
|
-
};
|
|
850
|
-
/**
|
|
851
|
-
* A fresh {@link SchemaTreeField}, defaulting to an optional unnamed string field. The id is a
|
|
852
|
-
* tagged counter, not a UUID: field identity is a UI concern with no persistence or
|
|
853
|
-
* cross-process meaning — exactly the guarantee a React key needs.
|
|
854
|
-
*/
|
|
855
|
-
declare function newSchemaTreeField(overrides?: Partial<Omit<SchemaTreeField, "id">>): SchemaTreeField;
|
|
856
|
-
/**
|
|
857
|
-
* The expression scope a schema tree declares: each named field becomes a typed variable, so a
|
|
858
|
-
* host can hand the result straight to `<ExpressionInput variables={…}>` (or `analyzeTypes`)
|
|
859
|
-
* and complete against the declared shape. Blank draft rows are skipped, while blank property
|
|
860
|
-
* names preserved by {@link parseSchemaTree} remain part of the scope.
|
|
861
|
-
*/
|
|
862
|
-
declare function schemaTreeToExpressionType(tree: SchemaTree): ExpressionType;
|
|
863
|
-
/**
|
|
864
|
-
* Infer a draft 2020-12 JSON Schema from a sample payload — the "generate from example" path of
|
|
865
|
-
* the visual schema builder. Deliberately emits only the subset {@link parseSchemaTree} hosts
|
|
866
|
-
* (`type` / `properties` / `items`): integers stay `number` (a sample's `5` rarely promises
|
|
867
|
-
* integers forever) and nothing is marked `required` (validation strictness is an authoring
|
|
868
|
-
* decision, not something a single sample can attest).
|
|
869
|
-
*/
|
|
870
|
-
interface JsonObject {
|
|
871
|
-
[key: string]: Json;
|
|
872
|
-
}
|
|
873
|
-
type Json = null | boolean | number | string | Json[] | JsonObject;
|
|
874
|
-
/**
|
|
875
|
-
* The schema a sample value attests: scalars map to their type, objects carry every observed
|
|
876
|
-
* property, arrays take the merged schema of their elements (an empty or mixed array leaves the
|
|
877
|
-
* items open). `null` says nothing about the real type, so it infers `{}` (any).
|
|
878
|
-
*/
|
|
879
|
-
declare function inferSchema(sample: Json): JsonObject;
|
|
880
|
-
/**
|
|
881
|
-
* Project a schema document onto the field tree, or refuse with a structured
|
|
882
|
-
* {@link SchemaTreeIssue} when it uses anything beyond the subset. A blank document parses as
|
|
883
|
-
* an empty tree.
|
|
884
|
-
*/
|
|
885
|
-
declare function parseSchemaTree(text: string): SchemaTreeParseResult;
|
|
886
|
-
/**
|
|
887
|
-
* The field tree back as pretty-printed schema JSON. A parsed type-less object root stays
|
|
888
|
-
* type-less; a hand-built tree still emits an object root by default. Clearing the contract
|
|
889
|
-
* entirely (an empty document) stays a source-mode action.
|
|
890
|
-
*/
|
|
891
|
-
declare function serializeSchemaTree(tree: SchemaTree): string;
|
|
892
|
-
export { type BranchSelection, type BuiltInExpressionLocale, CONDITION_OPERATORS, CONDITION_TREE_OPERATORS, type ConditionBranchInput, type ConditionGroupInput, type ConditionInput, type ConditionOperator, type ConditionOperatorArity, type ConditionScalar, type ConditionTreeGroup, type ConditionTreeNode, type ConditionTreeOperator, type ConditionTreeRule, type ConditionTreeValue, type ConfigureMessagesOptions, type ExpressionAnalysis, type ExpressionCompletion, type ExpressionConditionInput, type ExpressionContext, type ExpressionDiagnostic, type ExpressionEngine, ExpressionError, type ExpressionLocale, type ExpressionMessages, type ExpressionMessagesListener, type ExpressionMode, ExpressionNotReadyError, type ExpressionType, type ExpressionTypeSpan, type FieldConditionInput, type Json, type LoadEngineOptions, MAX_CONDITION_TREE_DEPTH, SCHEMA_DIALECT_2020_12, type SchemaTree, type SchemaTreeField, type SchemaTreeFieldType, type SchemaTreeIssue, type SchemaTreeParseResult, type TemplateHole, analyzeTemplate, analyzeTemplateSync, analyzeTypes, analyzeTypesSync, compileBranch, compileCondition, compileConditionTree, compileGroup, conditionOperatorArity, configureEngine, configureExpressionMessages, emptyConditionGroup, enMessages, ensureConditionNodeIds, evaluate, evaluateSync, evaluateUnary, evaluateUnarySync, getCompletionItems, getCompletionItemsSync, getDiagnostics, getDiagnosticsSync, getEngineError, getEngineSync, getExpressionMessages, getTemplateDiagnostics, getTemplateDiagnosticsSync, inferSchema, isEngineReady, isZenRepresentableNumber, liftConditionTree, loadEngine, newConditionNodeId, newSchemaTreeField, parseSchemaTree, parseTemplateHoles, registerExpressionLocale, resetEngine, satisfiesType, satisfiesTypeSync, schemaTreeToExpressionType, selectBranch, selectBranchWith, serializeSchemaTree, subscribeExpressionMessages, templateHoleAt, toZenLiteral, zhCNMessages };
|
|
746
|
+
export { type BranchSelection, type BuiltInExpressionLocale, CONDITION_OPERATORS, CONDITION_TREE_OPERATORS, type ConditionBranchInput, type ConditionGroupInput, type ConditionInput, type ConditionOperator, type ConditionOperatorArity, type ConditionScalar, type ConditionTreeGroup, type ConditionTreeNode, type ConditionTreeOperator, type ConditionTreeRule, type ConditionTreeValue, type ConfigureMessagesOptions, type ExpressionAnalysis, type ExpressionCompletion, type ExpressionConditionInput, type ExpressionContext, type ExpressionDiagnostic, type ExpressionEngine, ExpressionError, type ExpressionLocale, type ExpressionMessages, type ExpressionMessagesListener, type ExpressionMode, ExpressionNotReadyError, type ExpressionType, type ExpressionTypeSpan, type FieldConditionInput, type LoadEngineOptions, MAX_CONDITION_TREE_DEPTH, type TemplateHole, analyzeTemplate, analyzeTemplateSync, analyzeTypes, analyzeTypesSync, compileBranch, compileCondition, compileConditionTree, compileGroup, conditionOperatorArity, configureEngine, configureExpressionMessages, emptyConditionGroup, enMessages, ensureConditionNodeIds, evaluate, evaluateSync, evaluateUnary, evaluateUnarySync, getCompletionItems, getCompletionItemsSync, getDiagnostics, getDiagnosticsSync, getEngineError, getEngineSync, getExpressionMessages, getTemplateDiagnostics, getTemplateDiagnosticsSync, isEngineReady, isZenRepresentableNumber, liftConditionTree, loadEngine, newConditionNodeId, parseTemplateHoles, registerExpressionLocale, resetEngine, satisfiesType, satisfiesTypeSync, schemaTreeToExpressionType, selectBranch, selectBranchWith, subscribeExpressionMessages, templateHoleAt, toZenLiteral, zhCNMessages };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { InitInput } from "@gorules/zen-engine-wasm";
|
|
2
|
+
import { SchemaTree } from "@coldsmirk/caliper-core";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* How an expression editor interprets its document:
|
|
@@ -669,6 +670,13 @@ declare function getExpressionMessages(): ExpressionMessages;
|
|
|
669
670
|
* function; listeners run synchronously after the new catalog becomes active.
|
|
670
671
|
*/
|
|
671
672
|
declare function subscribeExpressionMessages(listener: ExpressionMessagesListener): () => void;
|
|
673
|
+
/**
|
|
674
|
+
* The expression scope a schema tree declares: each named field becomes a typed variable, so a
|
|
675
|
+
* host can hand the result straight to `<ExpressionInput variables={…}>` (or `analyzeTypes`)
|
|
676
|
+
* and complete against the declared shape. Blank draft rows are skipped, while blank property
|
|
677
|
+
* names preserved by `@coldsmirk/caliper-core`'s `parseSchemaTree` remain part of the scope.
|
|
678
|
+
*/
|
|
679
|
+
declare function schemaTreeToExpressionType(tree: SchemaTree): ExpressionType;
|
|
672
680
|
/**
|
|
673
681
|
* One `{{ expression }}` hole located in a template document. `from` / `to` are
|
|
674
682
|
* the character offsets of the inner expression itself — the text between the
|
|
@@ -735,158 +743,4 @@ declare function getTemplateDiagnosticsSync(source: string): ExpressionDiagnosti
|
|
|
735
743
|
* Async {@link getTemplateDiagnosticsSync}, loading the engine on first use.
|
|
736
744
|
*/
|
|
737
745
|
declare function getTemplateDiagnostics(source: string): Promise<ExpressionDiagnostic[]>;
|
|
738
|
-
|
|
739
|
-
* The field-tree model behind the visual JSON Schema builder: a lossless projection of the
|
|
740
|
-
* JSON Schema subset the tree can host (`type` / `properties` / `items`, plus `required`).
|
|
741
|
-
* The schema JSON stays the single source of truth: {@link parseSchemaTree} REFUSES anything
|
|
742
|
-
* outside the subset (never a lossy rewrite), so a hand-written `oneOf` or `format` keeps its
|
|
743
|
-
* document JSON-only instead of silently vanishing on the first visual edit.
|
|
744
|
-
*/
|
|
745
|
-
/**
|
|
746
|
-
* The one dialect the tree round-trips: a document may omit `$schema` or declare exactly this.
|
|
747
|
-
*/
|
|
748
|
-
declare const SCHEMA_DIALECT_2020_12 = "https://json-schema.org/draft/2020-12/schema";
|
|
749
|
-
type SchemaTreeFieldType = "any" | "array" | "boolean" | "integer" | "number" | "object" | "string";
|
|
750
|
-
interface SchemaTreeField {
|
|
751
|
-
/**
|
|
752
|
-
* Stable row identity for React keys and targeted edits — a UI concern, never serialized.
|
|
753
|
-
*/
|
|
754
|
-
id: string;
|
|
755
|
-
/**
|
|
756
|
-
* The property name under its parent object; unused on an array-element node.
|
|
757
|
-
*/
|
|
758
|
-
name: string;
|
|
759
|
-
type: SchemaTreeFieldType;
|
|
760
|
-
/**
|
|
761
|
-
* Listed in the parent object's `required`; unused on an array-element node.
|
|
762
|
-
*/
|
|
763
|
-
required: boolean;
|
|
764
|
-
/**
|
|
765
|
-
* The schema's `description` annotation; blank means none (not serialized).
|
|
766
|
-
*/
|
|
767
|
-
description: string;
|
|
768
|
-
/**
|
|
769
|
-
* Sub-fields, meaningful when `type` is `object`.
|
|
770
|
-
*/
|
|
771
|
-
children: SchemaTreeField[];
|
|
772
|
-
/**
|
|
773
|
-
* The element node, meaningful when `type` is `array`; `null` means untyped elements.
|
|
774
|
-
*/
|
|
775
|
-
items: SchemaTreeField | null;
|
|
776
|
-
/**
|
|
777
|
-
* For an object or array node parsed from JSON, whether its source explicitly
|
|
778
|
-
* declared that type. `false` preserves a type-less applicator; omitted keeps the
|
|
779
|
-
* historical hand-built-tree default of emitting the type.
|
|
780
|
-
*/
|
|
781
|
-
explicitType?: boolean;
|
|
782
|
-
/**
|
|
783
|
-
* Whether a blank / whitespace-only name came from a real `properties` key and must be
|
|
784
|
-
* persisted. Omitted blank names are unfinished UI rows and do not serialize.
|
|
785
|
-
*/
|
|
786
|
-
preserveBlankName?: boolean;
|
|
787
|
-
}
|
|
788
|
-
interface SchemaTree {
|
|
789
|
-
fields: SchemaTreeField[];
|
|
790
|
-
/**
|
|
791
|
-
* Whether the source declared `$schema` (re-emitted verbatim on serialize).
|
|
792
|
-
*/
|
|
793
|
-
dialect: boolean;
|
|
794
|
-
/**
|
|
795
|
-
* The root schema's `description` annotation, preserved verbatim across the round trip
|
|
796
|
-
* (the field view offers no root-level editor); blank means none.
|
|
797
|
-
*/
|
|
798
|
-
description: string;
|
|
799
|
-
/**
|
|
800
|
-
* For a tree parsed from JSON, whether the root explicitly declared `type: "object"`.
|
|
801
|
-
* `false` preserves a type-less root; omitted keeps the historical hand-built-tree default
|
|
802
|
-
* of emitting the type.
|
|
803
|
-
*/
|
|
804
|
-
explicitType?: boolean;
|
|
805
|
-
}
|
|
806
|
-
/**
|
|
807
|
-
* Why a document cannot be projected onto the field tree — structured so a UI can localize
|
|
808
|
-
* the explanation. Every code names the offending part where one exists.
|
|
809
|
-
*/
|
|
810
|
-
type SchemaTreeIssue = {
|
|
811
|
-
code: "invalid-description";
|
|
812
|
-
} | {
|
|
813
|
-
code: "invalid-field-definition";
|
|
814
|
-
field: string;
|
|
815
|
-
} | {
|
|
816
|
-
code: "invalid-field-type";
|
|
817
|
-
field: string;
|
|
818
|
-
} | {
|
|
819
|
-
code: "invalid-json";
|
|
820
|
-
} | {
|
|
821
|
-
code: "invalid-properties";
|
|
822
|
-
} | {
|
|
823
|
-
code: "invalid-required";
|
|
824
|
-
} | {
|
|
825
|
-
code: "misplaced-keyword";
|
|
826
|
-
keyword: string;
|
|
827
|
-
holder: "array" | "object";
|
|
828
|
-
} | {
|
|
829
|
-
code: "root-not-object";
|
|
830
|
-
} | {
|
|
831
|
-
code: "unknown-required-field";
|
|
832
|
-
field: string;
|
|
833
|
-
} | {
|
|
834
|
-
code: "unsupported-dialect";
|
|
835
|
-
} | {
|
|
836
|
-
code: "unsupported-field-type";
|
|
837
|
-
field: string;
|
|
838
|
-
type: string;
|
|
839
|
-
} | {
|
|
840
|
-
code: "unsupported-keyword";
|
|
841
|
-
keyword: string;
|
|
842
|
-
};
|
|
843
|
-
type SchemaTreeParseResult = {
|
|
844
|
-
ok: true;
|
|
845
|
-
tree: SchemaTree;
|
|
846
|
-
} | {
|
|
847
|
-
ok: false;
|
|
848
|
-
issue: SchemaTreeIssue;
|
|
849
|
-
};
|
|
850
|
-
/**
|
|
851
|
-
* A fresh {@link SchemaTreeField}, defaulting to an optional unnamed string field. The id is a
|
|
852
|
-
* tagged counter, not a UUID: field identity is a UI concern with no persistence or
|
|
853
|
-
* cross-process meaning — exactly the guarantee a React key needs.
|
|
854
|
-
*/
|
|
855
|
-
declare function newSchemaTreeField(overrides?: Partial<Omit<SchemaTreeField, "id">>): SchemaTreeField;
|
|
856
|
-
/**
|
|
857
|
-
* The expression scope a schema tree declares: each named field becomes a typed variable, so a
|
|
858
|
-
* host can hand the result straight to `<ExpressionInput variables={…}>` (or `analyzeTypes`)
|
|
859
|
-
* and complete against the declared shape. Blank draft rows are skipped, while blank property
|
|
860
|
-
* names preserved by {@link parseSchemaTree} remain part of the scope.
|
|
861
|
-
*/
|
|
862
|
-
declare function schemaTreeToExpressionType(tree: SchemaTree): ExpressionType;
|
|
863
|
-
/**
|
|
864
|
-
* Infer a draft 2020-12 JSON Schema from a sample payload — the "generate from example" path of
|
|
865
|
-
* the visual schema builder. Deliberately emits only the subset {@link parseSchemaTree} hosts
|
|
866
|
-
* (`type` / `properties` / `items`): integers stay `number` (a sample's `5` rarely promises
|
|
867
|
-
* integers forever) and nothing is marked `required` (validation strictness is an authoring
|
|
868
|
-
* decision, not something a single sample can attest).
|
|
869
|
-
*/
|
|
870
|
-
interface JsonObject {
|
|
871
|
-
[key: string]: Json;
|
|
872
|
-
}
|
|
873
|
-
type Json = null | boolean | number | string | Json[] | JsonObject;
|
|
874
|
-
/**
|
|
875
|
-
* The schema a sample value attests: scalars map to their type, objects carry every observed
|
|
876
|
-
* property, arrays take the merged schema of their elements (an empty or mixed array leaves the
|
|
877
|
-
* items open). `null` says nothing about the real type, so it infers `{}` (any).
|
|
878
|
-
*/
|
|
879
|
-
declare function inferSchema(sample: Json): JsonObject;
|
|
880
|
-
/**
|
|
881
|
-
* Project a schema document onto the field tree, or refuse with a structured
|
|
882
|
-
* {@link SchemaTreeIssue} when it uses anything beyond the subset. A blank document parses as
|
|
883
|
-
* an empty tree.
|
|
884
|
-
*/
|
|
885
|
-
declare function parseSchemaTree(text: string): SchemaTreeParseResult;
|
|
886
|
-
/**
|
|
887
|
-
* The field tree back as pretty-printed schema JSON. A parsed type-less object root stays
|
|
888
|
-
* type-less; a hand-built tree still emits an object root by default. Clearing the contract
|
|
889
|
-
* entirely (an empty document) stays a source-mode action.
|
|
890
|
-
*/
|
|
891
|
-
declare function serializeSchemaTree(tree: SchemaTree): string;
|
|
892
|
-
export { type BranchSelection, type BuiltInExpressionLocale, CONDITION_OPERATORS, CONDITION_TREE_OPERATORS, type ConditionBranchInput, type ConditionGroupInput, type ConditionInput, type ConditionOperator, type ConditionOperatorArity, type ConditionScalar, type ConditionTreeGroup, type ConditionTreeNode, type ConditionTreeOperator, type ConditionTreeRule, type ConditionTreeValue, type ConfigureMessagesOptions, type ExpressionAnalysis, type ExpressionCompletion, type ExpressionConditionInput, type ExpressionContext, type ExpressionDiagnostic, type ExpressionEngine, ExpressionError, type ExpressionLocale, type ExpressionMessages, type ExpressionMessagesListener, type ExpressionMode, ExpressionNotReadyError, type ExpressionType, type ExpressionTypeSpan, type FieldConditionInput, type Json, type LoadEngineOptions, MAX_CONDITION_TREE_DEPTH, SCHEMA_DIALECT_2020_12, type SchemaTree, type SchemaTreeField, type SchemaTreeFieldType, type SchemaTreeIssue, type SchemaTreeParseResult, type TemplateHole, analyzeTemplate, analyzeTemplateSync, analyzeTypes, analyzeTypesSync, compileBranch, compileCondition, compileConditionTree, compileGroup, conditionOperatorArity, configureEngine, configureExpressionMessages, emptyConditionGroup, enMessages, ensureConditionNodeIds, evaluate, evaluateSync, evaluateUnary, evaluateUnarySync, getCompletionItems, getCompletionItemsSync, getDiagnostics, getDiagnosticsSync, getEngineError, getEngineSync, getExpressionMessages, getTemplateDiagnostics, getTemplateDiagnosticsSync, inferSchema, isEngineReady, isZenRepresentableNumber, liftConditionTree, loadEngine, newConditionNodeId, newSchemaTreeField, parseSchemaTree, parseTemplateHoles, registerExpressionLocale, resetEngine, satisfiesType, satisfiesTypeSync, schemaTreeToExpressionType, selectBranch, selectBranchWith, serializeSchemaTree, subscribeExpressionMessages, templateHoleAt, toZenLiteral, zhCNMessages };
|
|
746
|
+
export { type BranchSelection, type BuiltInExpressionLocale, CONDITION_OPERATORS, CONDITION_TREE_OPERATORS, type ConditionBranchInput, type ConditionGroupInput, type ConditionInput, type ConditionOperator, type ConditionOperatorArity, type ConditionScalar, type ConditionTreeGroup, type ConditionTreeNode, type ConditionTreeOperator, type ConditionTreeRule, type ConditionTreeValue, type ConfigureMessagesOptions, type ExpressionAnalysis, type ExpressionCompletion, type ExpressionConditionInput, type ExpressionContext, type ExpressionDiagnostic, type ExpressionEngine, ExpressionError, type ExpressionLocale, type ExpressionMessages, type ExpressionMessagesListener, type ExpressionMode, ExpressionNotReadyError, type ExpressionType, type ExpressionTypeSpan, type FieldConditionInput, type LoadEngineOptions, MAX_CONDITION_TREE_DEPTH, type TemplateHole, analyzeTemplate, analyzeTemplateSync, analyzeTypes, analyzeTypesSync, compileBranch, compileCondition, compileConditionTree, compileGroup, conditionOperatorArity, configureEngine, configureExpressionMessages, emptyConditionGroup, enMessages, ensureConditionNodeIds, evaluate, evaluateSync, evaluateUnary, evaluateUnarySync, getCompletionItems, getCompletionItemsSync, getDiagnostics, getDiagnosticsSync, getEngineError, getEngineSync, getExpressionMessages, getTemplateDiagnostics, getTemplateDiagnosticsSync, isEngineReady, isZenRepresentableNumber, liftConditionTree, loadEngine, newConditionNodeId, parseTemplateHoles, registerExpressionLocale, resetEngine, satisfiesType, satisfiesTypeSync, schemaTreeToExpressionType, selectBranch, selectBranchWith, subscribeExpressionMessages, templateHoleAt, toZenLiteral, zhCNMessages };
|
package/dist/index.js
CHANGED
|
@@ -1257,6 +1257,23 @@ async function satisfiesType(actual, expected) {
|
|
|
1257
1257
|
function satisfiesTypeSync(actual, expected) {
|
|
1258
1258
|
return getEngineSync().satisfies(actual, expected);
|
|
1259
1259
|
}
|
|
1260
|
+
function fieldType(field) {
|
|
1261
|
+
switch (field.type) {
|
|
1262
|
+
case "string": return "String";
|
|
1263
|
+
case "number": return "Number";
|
|
1264
|
+
case "integer": return "Number";
|
|
1265
|
+
case "boolean": return "Bool";
|
|
1266
|
+
case "object": return { Object: fieldRecord(field.children) };
|
|
1267
|
+
case "array": return { Array: field.items === null ? "Any" : fieldType(field.items) };
|
|
1268
|
+
case "any": return "Any";
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
function fieldRecord(fields) {
|
|
1272
|
+
return Object.fromEntries(fields.filter((field) => field.name.trim() !== "" || field.preserveBlankName === true).map((field) => [field.name, fieldType(field)]));
|
|
1273
|
+
}
|
|
1274
|
+
function schemaTreeToExpressionType(tree) {
|
|
1275
|
+
return { Object: fieldRecord(tree.fields) };
|
|
1276
|
+
}
|
|
1260
1277
|
const HOLE_PATTERN = /\{\{(?<expression>[^{}]*)\}\}/g;
|
|
1261
1278
|
function parseTemplateHoles(source) {
|
|
1262
1279
|
const holes = [];
|
|
@@ -1317,293 +1334,4 @@ async function getTemplateDiagnostics(source) {
|
|
|
1317
1334
|
await loadEngine();
|
|
1318
1335
|
return getTemplateDiagnosticsSync(source);
|
|
1319
1336
|
}
|
|
1320
|
-
|
|
1321
|
-
switch (field.type) {
|
|
1322
|
-
case "string": return "String";
|
|
1323
|
-
case "number": return "Number";
|
|
1324
|
-
case "integer": return "Number";
|
|
1325
|
-
case "boolean": return "Bool";
|
|
1326
|
-
case "object": return { Object: fieldRecord(field.children) };
|
|
1327
|
-
case "array": return { Array: field.items === null ? "Any" : fieldType(field.items) };
|
|
1328
|
-
case "any": return "Any";
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
function fieldRecord(fields) {
|
|
1332
|
-
return Object.fromEntries(fields.filter((field) => field.name.trim() !== "" || field.preserveBlankName === true).map((field) => [field.name, fieldType(field)]));
|
|
1333
|
-
}
|
|
1334
|
-
function schemaTreeToExpressionType(tree) {
|
|
1335
|
-
return { Object: fieldRecord(tree.fields) };
|
|
1336
|
-
}
|
|
1337
|
-
function mergeSchemas(a, b) {
|
|
1338
|
-
if (a.type === "object" && b.type === "object") {
|
|
1339
|
-
const left = a.properties ?? {};
|
|
1340
|
-
const right = b.properties ?? {};
|
|
1341
|
-
const entries = [];
|
|
1342
|
-
const keys = new Set([...Object.keys(left), ...Object.keys(right)]);
|
|
1343
|
-
for (const key of keys) {
|
|
1344
|
-
const hasLeft = Object.hasOwn(left, key);
|
|
1345
|
-
const hasRight = Object.hasOwn(right, key);
|
|
1346
|
-
entries.push([key, hasLeft && hasRight ? mergeSchemas(left[key], right[key]) : hasLeft ? left[key] : right[key]]);
|
|
1347
|
-
}
|
|
1348
|
-
const properties = Object.fromEntries(entries);
|
|
1349
|
-
return Object.keys(properties).length > 0 ? {
|
|
1350
|
-
type: "object",
|
|
1351
|
-
properties
|
|
1352
|
-
} : { type: "object" };
|
|
1353
|
-
}
|
|
1354
|
-
if (a.type === "array" && b.type === "array") return a.items !== void 0 && b.items !== void 0 ? {
|
|
1355
|
-
type: "array",
|
|
1356
|
-
items: mergeSchemas(a.items, b.items)
|
|
1357
|
-
} : { type: "array" };
|
|
1358
|
-
return JSON.stringify(a) === JSON.stringify(b) ? a : {};
|
|
1359
|
-
}
|
|
1360
|
-
function inferSchema(sample) {
|
|
1361
|
-
if (sample === null) return {};
|
|
1362
|
-
if (Array.isArray(sample)) {
|
|
1363
|
-
if (sample.length === 0) return { type: "array" };
|
|
1364
|
-
return {
|
|
1365
|
-
type: "array",
|
|
1366
|
-
items: sample.map((element) => inferSchema(element)).reduce((left, right) => mergeSchemas(left, right))
|
|
1367
|
-
};
|
|
1368
|
-
}
|
|
1369
|
-
if (typeof sample === "string") return { type: "string" };
|
|
1370
|
-
if (typeof sample === "number") return { type: "number" };
|
|
1371
|
-
if (typeof sample === "boolean") return { type: "boolean" };
|
|
1372
|
-
const properties = Object.fromEntries(Object.entries(sample).map(([key, value]) => [key, inferSchema(value)]));
|
|
1373
|
-
return Object.keys(properties).length > 0 ? {
|
|
1374
|
-
type: "object",
|
|
1375
|
-
properties
|
|
1376
|
-
} : { type: "object" };
|
|
1377
|
-
}
|
|
1378
|
-
const SCHEMA_DIALECT_2020_12 = "https://json-schema.org/draft/2020-12/schema";
|
|
1379
|
-
const fieldIdRealm = Math.random().toString(36).slice(2, 7);
|
|
1380
|
-
let fieldIdCounter = 0;
|
|
1381
|
-
function newSchemaTreeField(overrides = {}) {
|
|
1382
|
-
fieldIdCounter += 1;
|
|
1383
|
-
return {
|
|
1384
|
-
id: `sf-${fieldIdRealm}-${fieldIdCounter}`,
|
|
1385
|
-
name: "",
|
|
1386
|
-
type: "string",
|
|
1387
|
-
required: false,
|
|
1388
|
-
description: "",
|
|
1389
|
-
children: [],
|
|
1390
|
-
items: null,
|
|
1391
|
-
...overrides
|
|
1392
|
-
};
|
|
1393
|
-
}
|
|
1394
|
-
const SCALAR_TYPES = new Set([
|
|
1395
|
-
"string",
|
|
1396
|
-
"number",
|
|
1397
|
-
"integer",
|
|
1398
|
-
"boolean"
|
|
1399
|
-
]);
|
|
1400
|
-
const ROOT_KEYS = new Set([
|
|
1401
|
-
"$schema",
|
|
1402
|
-
"type",
|
|
1403
|
-
"properties",
|
|
1404
|
-
"required",
|
|
1405
|
-
"description"
|
|
1406
|
-
]);
|
|
1407
|
-
const SUBSCHEMA_KEYS = new Set([
|
|
1408
|
-
"type",
|
|
1409
|
-
"properties",
|
|
1410
|
-
"required",
|
|
1411
|
-
"items",
|
|
1412
|
-
"description"
|
|
1413
|
-
]);
|
|
1414
|
-
var Unsupported = class extends Error {
|
|
1415
|
-
issue;
|
|
1416
|
-
constructor(issue) {
|
|
1417
|
-
super(issue.code);
|
|
1418
|
-
this.issue = issue;
|
|
1419
|
-
}
|
|
1420
|
-
};
|
|
1421
|
-
function asPlainObject(value) {
|
|
1422
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
1423
|
-
}
|
|
1424
|
-
function checkKeys(schema, allowed) {
|
|
1425
|
-
for (const key of Object.keys(schema)) if (!allowed.has(key)) throw new Unsupported({
|
|
1426
|
-
code: "unsupported-keyword",
|
|
1427
|
-
keyword: key
|
|
1428
|
-
});
|
|
1429
|
-
}
|
|
1430
|
-
function parseObjectFields(schema) {
|
|
1431
|
-
const properties = schema.properties === void 0 ? null : asPlainObject(schema.properties);
|
|
1432
|
-
if (schema.properties !== void 0 && properties === null) throw new Unsupported({ code: "invalid-properties" });
|
|
1433
|
-
const fields = [];
|
|
1434
|
-
const entries = Object.entries(properties ?? {});
|
|
1435
|
-
for (const [name, subschema] of entries) fields.push({
|
|
1436
|
-
...parseSubschema(subschema, name),
|
|
1437
|
-
name,
|
|
1438
|
-
...name.trim() === "" && { preserveBlankName: true }
|
|
1439
|
-
});
|
|
1440
|
-
if (schema.required !== void 0) {
|
|
1441
|
-
if (!Array.isArray(schema.required)) throw new Unsupported({ code: "invalid-required" });
|
|
1442
|
-
for (const entry of schema.required) {
|
|
1443
|
-
if (typeof entry !== "string") throw new Unsupported({ code: "invalid-required" });
|
|
1444
|
-
const field = fields.find((candidate) => candidate.name === entry);
|
|
1445
|
-
if (field === void 0) throw new Unsupported({
|
|
1446
|
-
code: "unknown-required-field",
|
|
1447
|
-
field: entry
|
|
1448
|
-
});
|
|
1449
|
-
field.required = true;
|
|
1450
|
-
}
|
|
1451
|
-
}
|
|
1452
|
-
return fields;
|
|
1453
|
-
}
|
|
1454
|
-
function parseDescription(schema) {
|
|
1455
|
-
if (schema.description === void 0) return "";
|
|
1456
|
-
if (typeof schema.description !== "string") throw new Unsupported({ code: "invalid-description" });
|
|
1457
|
-
return schema.description;
|
|
1458
|
-
}
|
|
1459
|
-
function ensureAbsent(schema, key, holder) {
|
|
1460
|
-
if (schema[key] !== void 0) throw new Unsupported({
|
|
1461
|
-
code: "misplaced-keyword",
|
|
1462
|
-
keyword: key,
|
|
1463
|
-
holder
|
|
1464
|
-
});
|
|
1465
|
-
}
|
|
1466
|
-
function parseSubschema(value, name) {
|
|
1467
|
-
const schema = asPlainObject(value);
|
|
1468
|
-
if (schema === null) throw new Unsupported({
|
|
1469
|
-
code: "invalid-field-definition",
|
|
1470
|
-
field: name
|
|
1471
|
-
});
|
|
1472
|
-
checkKeys(schema, SUBSCHEMA_KEYS);
|
|
1473
|
-
const { type } = schema;
|
|
1474
|
-
if (type !== void 0 && typeof type !== "string") throw new Unsupported({
|
|
1475
|
-
code: "invalid-field-type",
|
|
1476
|
-
field: name
|
|
1477
|
-
});
|
|
1478
|
-
const description = parseDescription(schema);
|
|
1479
|
-
if (type === "object" || type === void 0 && (schema.properties !== void 0 || schema.required !== void 0)) {
|
|
1480
|
-
ensureAbsent(schema, "items", "array");
|
|
1481
|
-
return newSchemaTreeField({
|
|
1482
|
-
type: "object",
|
|
1483
|
-
description,
|
|
1484
|
-
children: parseObjectFields(schema),
|
|
1485
|
-
explicitType: type === "object"
|
|
1486
|
-
});
|
|
1487
|
-
}
|
|
1488
|
-
if (type === "array" || type === void 0 && schema.items !== void 0) {
|
|
1489
|
-
ensureAbsent(schema, "properties", "object");
|
|
1490
|
-
ensureAbsent(schema, "required", "object");
|
|
1491
|
-
return newSchemaTreeField({
|
|
1492
|
-
type: "array",
|
|
1493
|
-
description,
|
|
1494
|
-
items: schema.items === void 0 ? null : parseSubschema(schema.items, name),
|
|
1495
|
-
explicitType: type === "array"
|
|
1496
|
-
});
|
|
1497
|
-
}
|
|
1498
|
-
if (type === void 0) {
|
|
1499
|
-
ensureAbsent(schema, "items", "array");
|
|
1500
|
-
return newSchemaTreeField({
|
|
1501
|
-
type: "any",
|
|
1502
|
-
description
|
|
1503
|
-
});
|
|
1504
|
-
}
|
|
1505
|
-
if (!SCALAR_TYPES.has(type)) throw new Unsupported({
|
|
1506
|
-
code: "unsupported-field-type",
|
|
1507
|
-
field: name,
|
|
1508
|
-
type
|
|
1509
|
-
});
|
|
1510
|
-
ensureAbsent(schema, "properties", "object");
|
|
1511
|
-
ensureAbsent(schema, "required", "object");
|
|
1512
|
-
ensureAbsent(schema, "items", "array");
|
|
1513
|
-
return newSchemaTreeField({
|
|
1514
|
-
type,
|
|
1515
|
-
description
|
|
1516
|
-
});
|
|
1517
|
-
}
|
|
1518
|
-
function parseSchemaTree(text) {
|
|
1519
|
-
if (text.trim() === "") return {
|
|
1520
|
-
ok: true,
|
|
1521
|
-
tree: {
|
|
1522
|
-
fields: [],
|
|
1523
|
-
dialect: false,
|
|
1524
|
-
description: ""
|
|
1525
|
-
}
|
|
1526
|
-
};
|
|
1527
|
-
let parsed;
|
|
1528
|
-
try {
|
|
1529
|
-
parsed = JSON.parse(text);
|
|
1530
|
-
} catch {
|
|
1531
|
-
return {
|
|
1532
|
-
ok: false,
|
|
1533
|
-
issue: { code: "invalid-json" }
|
|
1534
|
-
};
|
|
1535
|
-
}
|
|
1536
|
-
try {
|
|
1537
|
-
const root = asPlainObject(parsed);
|
|
1538
|
-
if (root === null) throw new Unsupported({ code: "root-not-object" });
|
|
1539
|
-
checkKeys(root, ROOT_KEYS);
|
|
1540
|
-
const dialect = root.$schema !== void 0;
|
|
1541
|
-
if (dialect && root.$schema !== "https://json-schema.org/draft/2020-12/schema") throw new Unsupported({ code: "unsupported-dialect" });
|
|
1542
|
-
if (root.type !== void 0 && root.type !== "object") throw new Unsupported({ code: "root-not-object" });
|
|
1543
|
-
return {
|
|
1544
|
-
ok: true,
|
|
1545
|
-
tree: {
|
|
1546
|
-
fields: parseObjectFields(root),
|
|
1547
|
-
dialect,
|
|
1548
|
-
description: parseDescription(root),
|
|
1549
|
-
explicitType: root.type === "object"
|
|
1550
|
-
}
|
|
1551
|
-
};
|
|
1552
|
-
} catch (error) {
|
|
1553
|
-
if (error instanceof Unsupported) return {
|
|
1554
|
-
ok: false,
|
|
1555
|
-
issue: error.issue
|
|
1556
|
-
};
|
|
1557
|
-
throw error;
|
|
1558
|
-
}
|
|
1559
|
-
}
|
|
1560
|
-
function serializeObjectBody(fields) {
|
|
1561
|
-
const named = fields.filter((field) => field.name.trim() !== "" || field.preserveBlankName === true);
|
|
1562
|
-
const finalByName = /* @__PURE__ */ new Map();
|
|
1563
|
-
const finalFields = [];
|
|
1564
|
-
const body = {};
|
|
1565
|
-
for (const field of named) finalByName.set(field.name, field);
|
|
1566
|
-
finalByName.forEach((field) => {
|
|
1567
|
-
finalFields.push(field);
|
|
1568
|
-
});
|
|
1569
|
-
if (finalFields.length > 0) body.properties = Object.fromEntries(finalFields.map((field) => [field.name, serializeField(field)]));
|
|
1570
|
-
const required = finalFields.filter((field) => field.required).map((field) => field.name);
|
|
1571
|
-
if (required.length > 0) body.required = required;
|
|
1572
|
-
return body;
|
|
1573
|
-
}
|
|
1574
|
-
function serializeField(field) {
|
|
1575
|
-
const description = field.description === "" ? {} : { description: field.description };
|
|
1576
|
-
switch (field.type) {
|
|
1577
|
-
case "any": return { ...description };
|
|
1578
|
-
case "object": return {
|
|
1579
|
-
...field.explicitType !== false && { type: "object" },
|
|
1580
|
-
...description,
|
|
1581
|
-
...serializeObjectBody(field.children)
|
|
1582
|
-
};
|
|
1583
|
-
case "array": {
|
|
1584
|
-
const type = field.explicitType === false ? {} : { type: "array" };
|
|
1585
|
-
return field.items === null || field.items.type === "any" && field.items.description === "" ? {
|
|
1586
|
-
...type,
|
|
1587
|
-
...description
|
|
1588
|
-
} : {
|
|
1589
|
-
...type,
|
|
1590
|
-
...description,
|
|
1591
|
-
items: serializeField(field.items)
|
|
1592
|
-
};
|
|
1593
|
-
}
|
|
1594
|
-
default: return {
|
|
1595
|
-
type: field.type,
|
|
1596
|
-
...description
|
|
1597
|
-
};
|
|
1598
|
-
}
|
|
1599
|
-
}
|
|
1600
|
-
function serializeSchemaTree(tree) {
|
|
1601
|
-
const root = {
|
|
1602
|
-
...tree.dialect && { $schema: "https://json-schema.org/draft/2020-12/schema" },
|
|
1603
|
-
...tree.explicitType !== false && { type: "object" },
|
|
1604
|
-
...tree.description !== "" && { description: tree.description },
|
|
1605
|
-
...serializeObjectBody(tree.fields)
|
|
1606
|
-
};
|
|
1607
|
-
return `${JSON.stringify(root, null, 2)}\n`;
|
|
1608
|
-
}
|
|
1609
|
-
export { CONDITION_OPERATORS, CONDITION_TREE_OPERATORS, ExpressionError, ExpressionNotReadyError, MAX_CONDITION_TREE_DEPTH, SCHEMA_DIALECT_2020_12, analyzeTemplate, analyzeTemplateSync, analyzeTypes, analyzeTypesSync, compileBranch, compileCondition, compileConditionTree, compileGroup, conditionOperatorArity, configureEngine, configureExpressionMessages, emptyConditionGroup, enMessages, ensureConditionNodeIds, evaluate, evaluateSync, evaluateUnary, evaluateUnarySync, getCompletionItems, getCompletionItemsSync, getDiagnostics, getDiagnosticsSync, getEngineError, getEngineSync, getExpressionMessages, getTemplateDiagnostics, getTemplateDiagnosticsSync, inferSchema, isEngineReady, isZenRepresentableNumber, liftConditionTree, loadEngine, newConditionNodeId, newSchemaTreeField, parseSchemaTree, parseTemplateHoles, registerExpressionLocale, resetEngine, satisfiesType, satisfiesTypeSync, schemaTreeToExpressionType, selectBranch, selectBranchWith, serializeSchemaTree, subscribeExpressionMessages, templateHoleAt, toZenLiteral, zhCNMessages };
|
|
1337
|
+
export { CONDITION_OPERATORS, CONDITION_TREE_OPERATORS, ExpressionError, ExpressionNotReadyError, MAX_CONDITION_TREE_DEPTH, analyzeTemplate, analyzeTemplateSync, analyzeTypes, analyzeTypesSync, compileBranch, compileCondition, compileConditionTree, compileGroup, conditionOperatorArity, configureEngine, configureExpressionMessages, emptyConditionGroup, enMessages, ensureConditionNodeIds, evaluate, evaluateSync, evaluateUnary, evaluateUnarySync, getCompletionItems, getCompletionItemsSync, getDiagnostics, getDiagnosticsSync, getEngineError, getEngineSync, getExpressionMessages, getTemplateDiagnostics, getTemplateDiagnosticsSync, isEngineReady, isZenRepresentableNumber, liftConditionTree, loadEngine, newConditionNodeId, parseTemplateHoles, registerExpressionLocale, resetEngine, satisfiesType, satisfiesTypeSync, schemaTreeToExpressionType, selectBranch, selectBranchWith, subscribeExpressionMessages, templateHoleAt, toZenLiteral, zhCNMessages };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coldsmirk/abacus-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Framework-agnostic ZEN expression engine: compile, evaluate, and type-analyze expressions over the GoRules ZEN WASM engine.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"zen",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"dist"
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
|
+
"@coldsmirk/caliper-core": "^0.1.0",
|
|
46
47
|
"@gorules/zen-engine-wasm": "^0.23.1"
|
|
47
48
|
},
|
|
48
49
|
"engines": {
|