@coldsmirk/abacus-core 0.2.0 → 0.3.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 +54 -3
- package/dist/index.cjs +627 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +189 -3
- package/dist/index.d.ts +189 -3
- package/dist/index.js +618 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Part of [abacus](https://github.com/coldsmirk/abacus). To edit expressions in an
|
|
|
7
7
|
## What you get
|
|
8
8
|
|
|
9
9
|
- **Evaluate** ZEN expressions against a data context, sync or async.
|
|
10
|
-
- **Compile** a structured, UI-authored condition tree into a single ZEN boolean expression
|
|
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
12
|
- **Localize** all editor-produced text through a typed, extensible message catalog.
|
|
13
13
|
- **Strong typing** end to end, dual ESM / CJS, and a single lazily-loaded WebAssembly engine instance.
|
|
@@ -44,6 +44,8 @@ ZEN has two expression flavours, selected throughout this package by a `mode: "s
|
|
|
44
44
|
- **standard** — computes a value of any type, e.g. `amount * 1.2`, `customer.name`, `upper(code)`.
|
|
45
45
|
- **unary** — a boolean _test_ against an implicit subject `$`, e.g. `$ > 1000`, `$ in ['BJ', 'SH']`. Used for conditions.
|
|
46
46
|
|
|
47
|
+
The `ExpressionMode` union carries a third value, **`"template"`**, for the editor layer: literal text with `{{ expression }}` holes, each hole a standard expression. It is edited and validated hole-by-hole (`analyzeTemplateSync`, `getTemplateDiagnosticsSync`, `parseTemplateHoles` / `templateHoleAt`); the single-expression functions treat `"template"` as `"standard"`. Templates are not directly evaluable here — the ZEN wasm exposes no template renderer — so there is no `evaluateTemplate`.
|
|
48
|
+
|
|
47
49
|
### The engine loads once, lazily
|
|
48
50
|
|
|
49
51
|
The multi-megabyte WebAssembly binary is fetched on first use via a dynamic `import()`, then cached as a single frozen instance for the lifetime of the page. Every helper comes in two forms:
|
|
@@ -292,6 +294,53 @@ type BranchSelection =
|
|
|
292
294
|
| { matched: false; branchId: string | null }; // default fallback, or null if none
|
|
293
295
|
```
|
|
294
296
|
|
|
297
|
+
### Condition trees
|
|
298
|
+
|
|
299
|
+
The flat model above is one AND-group per branch. For a structured builder UI — arbitrarily nested AND/OR groups of typed comparison rules — the package also models a **condition tree** and compiles it to a single canonical expression, with an exact inverse:
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
interface ConditionTreeRule { // a leaf comparison
|
|
303
|
+
kind: "rule";
|
|
304
|
+
left: string; // an identifier path, like `subject`
|
|
305
|
+
operator: ConditionTreeOperator; // the compiler's full 14-operator set
|
|
306
|
+
right?: ConditionTreeValue; // per the operator's arity (see below)
|
|
307
|
+
}
|
|
308
|
+
interface ConditionTreeGroup { // an and/or group of rules and subgroups
|
|
309
|
+
kind: "group";
|
|
310
|
+
op: "and" | "or";
|
|
311
|
+
items: readonly ConditionTreeNode[];
|
|
312
|
+
}
|
|
313
|
+
type ConditionTreeNode = ConditionTreeGroup | ConditionTreeRule;
|
|
314
|
+
|
|
315
|
+
function compileConditionTree(tree: ConditionTreeGroup): string;
|
|
316
|
+
function liftConditionTree(expression: string): ConditionTreeGroup | null;
|
|
317
|
+
function conditionOperatorArity(operator: ConditionOperator): "scalar" | "array" | "none";
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
```ts
|
|
321
|
+
import { compileConditionTree, liftConditionTree } from "@coldsmirk/abacus-core";
|
|
322
|
+
|
|
323
|
+
const tree = {
|
|
324
|
+
kind: "group", op: "or", items: [
|
|
325
|
+
{ kind: "group", op: "and", items: [
|
|
326
|
+
{ kind: "rule", left: "amount", operator: "gt", right: 1000 },
|
|
327
|
+
{ kind: "rule", left: "vip", operator: "eq", right: true },
|
|
328
|
+
] },
|
|
329
|
+
{ kind: "rule", left: "region", operator: "eq", right: "CN" },
|
|
330
|
+
],
|
|
331
|
+
} as const;
|
|
332
|
+
|
|
333
|
+
compileConditionTree(tree); // → "(amount > 1000 and vip == true) or region == 'CN'"
|
|
334
|
+
liftConditionTree("(amount > 1000 and vip == true) or region == 'CN'"); // → the tree above
|
|
335
|
+
liftConditionTree("len(name) > 3"); // → null: not canonical
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
A rule's `right` follows its operator's **arity** — a single scalar (`string | number | boolean`) for the comparison and string operators, an array of scalars for `in` / `not_in`, absent for `is_empty` / `is_not_empty`. `conditionOperatorArity` classifies the operators so a condition UI can pick value editors from the same definition the compiler enforces. `CONDITION_TREE_OPERATORS` is the same runtime list as `CONDITION_OPERATORS` under the tree name.
|
|
339
|
+
|
|
340
|
+
The emitted expression is **canonical**: nested groups are always parenthesized (never relying on `and`/`or` precedence), single-item groups collapse to their item, and a rule the tree cannot represent canonically — a non-path `left`, an off-arity `right`, a value with no ZEN literal — is dropped like an invalid flat condition. A tree with nothing compilable yields `""`.
|
|
341
|
+
|
|
342
|
+
`liftConditionTree` recognizes exactly that canonical subset (whitespace-tolerantly), so the two functions round-trip: everything `compileConditionTree` emits lifts back to an equal tree, and anything else — a raw hand-authored expression, mixed `and`/`or` without parentheses, groups nested beyond 64 levels — returns `null` rather than guessing at structure. That `null` is a host's signal to fall back to raw-expression editing instead of a structured builder; [`@coldsmirk/abacus-mantine`](https://www.npmjs.com/package/@coldsmirk/abacus-mantine)'s `<ConditionBuilder>` is built on exactly this contract.
|
|
343
|
+
|
|
295
344
|
### Safety: subjects, values, and escaping
|
|
296
345
|
|
|
297
346
|
The compiler is designed not to become an expression-injection sink:
|
|
@@ -365,10 +414,12 @@ class ExpressionNotReadyError extends ExpressionError { // a *Sync helper calle
|
|
|
365
414
|
- **Evaluate** — `evaluate`, `evaluateSync`, `evaluateUnary`, `evaluateUnarySync`
|
|
366
415
|
- **Engine lifecycle** — `loadEngine`, `isEngineReady`, `getEngineSync`, `getEngineError`, `configureEngine`, `resetEngine`
|
|
367
416
|
- **Type analysis** — `analyzeTypes` / `analyzeTypesSync`, `getDiagnostics` / `getDiagnosticsSync`, `getCompletionItems` / `getCompletionItemsSync`, `satisfiesType` / `satisfiesTypeSync`
|
|
368
|
-
- **
|
|
417
|
+
- **Templates** — `analyzeTemplate` / `analyzeTemplateSync`, `getTemplateDiagnostics` / `getTemplateDiagnosticsSync`, `parseTemplateHoles`, `templateHoleAt`
|
|
418
|
+
- **Conditions** — `compileCondition`, `compileGroup`, `compileBranch`, `selectBranch`, `selectBranchWith`, `toZenLiteral`, `CONDITION_OPERATORS`, `conditionOperatorArity`
|
|
419
|
+
- **Condition trees** — `compileConditionTree`, `liftConditionTree`, `CONDITION_TREE_OPERATORS`
|
|
369
420
|
- **Localization** — `configureExpressionMessages`, `registerExpressionLocale`, `getExpressionMessages`, `enMessages`, `zhCNMessages`
|
|
370
421
|
- **Errors** — `ExpressionError`, `ExpressionNotReadyError`
|
|
371
|
-
- **Types** — `ExpressionType`, `ExpressionMode`, `ExpressionContext`, `ExpressionEngine`, `LoadEngineOptions`, `ExpressionAnalysis`, `ExpressionTypeSpan`, `ExpressionCompletion`, `ExpressionDiagnostic`, `ExpressionMessages`, `ExpressionLocale`, `BuiltInExpressionLocale`, `ConfigureMessagesOptions`, `ConditionInput`, `FieldConditionInput`, `ExpressionConditionInput`, `ConditionGroupInput`, `ConditionBranchInput`, `ConditionOperator`, `BranchSelection`
|
|
422
|
+
- **Types** — `ExpressionType`, `ExpressionMode`, `TemplateHole`, `ExpressionContext`, `ExpressionEngine`, `LoadEngineOptions`, `ExpressionAnalysis`, `ExpressionTypeSpan`, `ExpressionCompletion`, `ExpressionDiagnostic`, `ExpressionMessages`, `ExpressionLocale`, `BuiltInExpressionLocale`, `ConfigureMessagesOptions`, `ConditionInput`, `FieldConditionInput`, `ExpressionConditionInput`, `ConditionGroupInput`, `ConditionBranchInput`, `ConditionOperator`, `ConditionOperatorArity`, `BranchSelection`, `ConditionTreeGroup`, `ConditionTreeNode`, `ConditionTreeRule`, `ConditionTreeOperator`, `ConditionTreeValue`, `ConditionScalar`
|
|
372
423
|
|
|
373
424
|
## License
|
|
374
425
|
|