@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/dist/index.js CHANGED
@@ -360,7 +360,7 @@ function isZenConsistentNumber(value) {
360
360
  const SUBJECT_PATTERN = /^[A-Z_$][\w$]*(?:\.[A-Z_$][\w$]*|\[\d+\])*$/i;
361
361
  const SUBJECT_IDENTIFIER_PATTERN = /[A-Z_$][\w$]*/gi;
362
362
  const SUBJECT_INDEX_PATTERN = /\[(?<index>\d+)\]/g;
363
- const ZEN_ROOT_RESERVED_WORDS = new Set([
363
+ const ZEN_RESERVED_WORDS = new Set([
364
364
  "and",
365
365
  "or",
366
366
  "not",
@@ -370,10 +370,13 @@ const ZEN_ROOT_RESERVED_WORDS = new Set([
370
370
  "null"
371
371
  ]);
372
372
  const ZEN_MEMBER_RESERVED_WORDS = new Set(["true", "false"]);
373
+ function isZenReservedWord(word) {
374
+ return ZEN_RESERVED_WORDS.has(word);
375
+ }
373
376
  function isIdentifierPath(subject) {
374
377
  if (!SUBJECT_PATTERN.test(subject)) return false;
375
378
  const [root, ...members] = subject.match(SUBJECT_IDENTIFIER_PATTERN) ?? [];
376
- if (root === void 0 || ZEN_ROOT_RESERVED_WORDS.has(root) || members.some((member) => ZEN_MEMBER_RESERVED_WORDS.has(member))) return false;
379
+ if (root === void 0 || isZenReservedWord(root) || members.some((member) => ZEN_MEMBER_RESERVED_WORDS.has(member))) return false;
377
380
  for (const match of subject.matchAll(SUBJECT_INDEX_PATTERN)) if (match.groups?.index === void 0 || !isZenUnsignedIntegerText(match.groups.index)) return false;
378
381
  return true;
379
382
  }
@@ -639,14 +642,14 @@ function compileRule(rule) {
639
642
  function matchesOperatorArity(rule) {
640
643
  switch (conditionOperatorArity(rule.operator)) {
641
644
  case "scalar": return isConditionScalar(rule.right);
642
- case "array": return isScalarArray(rule.right);
645
+ case "array": return isConditionScalarArray(rule.right);
643
646
  case "none": return rule.right === void 0;
644
647
  }
645
648
  }
646
649
  function isConditionScalar(value) {
647
650
  return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
648
651
  }
649
- function isScalarArray(value) {
652
+ function isConditionScalarArray(value) {
650
653
  return isArray(value) && value.every((item) => isConditionScalar(item));
651
654
  }
652
655
  const TWO_CHAR_PUNCTUATION = new Set([
@@ -671,7 +674,7 @@ const IDENT_PART = /[\w$]/;
671
674
  const DIGIT = /\d/;
672
675
  const NUMBER_PATTERN = /^\d+(?:\.\d+)?(?:e[+-]?\d+)?/i;
673
676
  const INTEGER_PATTERN = /^\d+$/;
674
- function parseCanonicalNumber(text) {
677
+ function parseCanonicalZenNumber(text) {
675
678
  const value = Number(text);
676
679
  return String(value) === text && isZenRepresentableNumber(value) ? value : null;
677
680
  }
@@ -733,7 +736,7 @@ function liftConditionTree(expression) {
733
736
  return token.value;
734
737
  }
735
738
  if (token.kind === "number") {
736
- const value = parseCanonicalNumber(token.value);
739
+ const value = parseCanonicalZenNumber(token.value);
737
740
  if (value === null) return null;
738
741
  pos += 1;
739
742
  return value;
@@ -752,7 +755,7 @@ function liftConditionTree(expression) {
752
755
  if (token.kind === "punct" && token.value === "-") {
753
756
  const digits = peek(1);
754
757
  if (digits === void 0 || digits.kind !== "number") return null;
755
- const value = parseCanonicalNumber(digits.value);
758
+ const value = parseCanonicalZenNumber(digits.value);
756
759
  if (value === null || value === 0 || !isZenRepresentableNumber(-value)) return null;
757
760
  pos += 2;
758
761
  return -value;
@@ -1257,6 +1260,23 @@ async function satisfiesType(actual, expected) {
1257
1260
  function satisfiesTypeSync(actual, expected) {
1258
1261
  return getEngineSync().satisfies(actual, expected);
1259
1262
  }
1263
+ function fieldType(field) {
1264
+ switch (field.type) {
1265
+ case "string": return "String";
1266
+ case "number": return "Number";
1267
+ case "integer": return "Number";
1268
+ case "boolean": return "Bool";
1269
+ case "object": return { Object: fieldRecord(field.children) };
1270
+ case "array": return { Array: field.items === null ? "Any" : fieldType(field.items) };
1271
+ case "any": return "Any";
1272
+ }
1273
+ }
1274
+ function fieldRecord(fields) {
1275
+ return Object.fromEntries(fields.filter((field) => field.name.trim() !== "" || field.preserveBlankName === true).map((field) => [field.name, fieldType(field)]));
1276
+ }
1277
+ function schemaTreeToExpressionType(tree) {
1278
+ return { Object: fieldRecord(tree.fields) };
1279
+ }
1260
1280
  const HOLE_PATTERN = /\{\{(?<expression>[^{}]*)\}\}/g;
1261
1281
  function parseTemplateHoles(source) {
1262
1282
  const holes = [];
@@ -1317,293 +1337,4 @@ async function getTemplateDiagnostics(source) {
1317
1337
  await loadEngine();
1318
1338
  return getTemplateDiagnosticsSync(source);
1319
1339
  }
1320
- function fieldType(field) {
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 };
1340
+ 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, isConditionScalarArray, isEngineReady, isZenRepresentableNumber, isZenReservedWord, liftConditionTree, loadEngine, newConditionNodeId, parseCanonicalZenNumber, 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.9.0",
3
+ "version": "0.11.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": {