@coldsmirk/abacus-core 0.3.1 → 0.4.1

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
@@ -1,9 +1,4 @@
1
1
  //#region src/engine/errors.ts
2
- /**
3
- * Error raised when the ZEN engine fails to load or an expression cannot be
4
- * evaluated. The original failure is preserved on {@link cause} and the
5
- * offending expression (if any) on {@link expression}.
6
- */
7
2
  var ExpressionError = class extends Error {
8
3
  expression;
9
4
  constructor(message, expression, cause) {
@@ -12,11 +7,6 @@ var ExpressionError = class extends Error {
12
7
  this.expression = expression;
13
8
  }
14
9
  };
15
- /**
16
- * Raised by the synchronous evaluation helpers when the engine has not finished
17
- * initializing yet. Await {@link loadEngine} (or render under
18
- * `<ExpressionEngineProvider>`) before evaluating synchronously.
19
- */
20
10
  var ExpressionNotReadyError = class extends ExpressionError {
21
11
  constructor(message = "Expression engine is not initialized. Await loadEngine() or render under <ExpressionEngineProvider>.") {
22
12
  super(message);
@@ -25,11 +15,6 @@ var ExpressionNotReadyError = class extends ExpressionError {
25
15
  };
26
16
  //#endregion
27
17
  //#region src/internal/predicates.ts
28
- /**
29
- * Minimal internal type predicates. Inlined so the engine stays dependency-free
30
- * (no shared-utility package), keeping `@coldsmirk/abacus-core` framework- and
31
- * ecosystem-agnostic.
32
- */
33
18
  function isUndefined(value) {
34
19
  return value === void 0;
35
20
  }
@@ -64,32 +49,10 @@ function loadFailureMessage() {
64
49
  if (typeof window === "undefined") return `${base}. In a non-browser or non-DOM host (Node, SSR, Web Worker) the wasm cannot be auto-resolved; call configureEngine({ wasmInput }) with the wasm bytes or URL before loading.`;
65
50
  return base;
66
51
  }
67
- /**
68
- * Configure how the wasm binary is located, before the engine loads. Must be
69
- * called before the first {@link loadEngine} (or any evaluation), so that the
70
- * configured input is the one actually used — calling it after the engine has
71
- * started loading throws, rather than silently taking no effect.
72
- */
73
52
  function configureEngine(options) {
74
53
  if (enginePromise || engineSync) throw new ExpressionError("configureEngine() must be called before the engine loads.");
75
54
  configuredInput = options.wasmInput;
76
55
  }
77
- /**
78
- * Load and initialize the ZEN expression engine exactly once. Concurrent and
79
- * subsequent calls share the same in-flight promise / resolved instance. Set a
80
- * custom wasm source up front with {@link configureEngine}.
81
- *
82
- * The GoRules wasm dependency is reached via a dynamic `import()` on purpose: it
83
- * keeps this module usable from the package's CommonJS build (the dep is
84
- * ESM-only, so a static `require` would throw) and defers the multi-megabyte
85
- * wasm download until the engine is actually needed. Do NOT convert this to a
86
- * static import.
87
- *
88
- * A failed load is not cached: the rejected promise is dropped (the error is
89
- * latched on {@link getEngineError} for inspection), so calling `loadEngine()`
90
- * again retries from scratch. This is the clear-and-reload retry primitive after
91
- * a transient failure — an error boundary should call it before resetting.
92
- */
93
56
  function loadEngine() {
94
57
  if (enginePromise) return enginePromise;
95
58
  engineError = null;
@@ -153,35 +116,16 @@ function loadEngine() {
153
116
  });
154
117
  return enginePromise;
155
118
  }
156
- /**
157
- * Whether the engine has finished initializing and is ready for sync use.
158
- */
159
119
  function isEngineReady() {
160
120
  return engineSync?.isReady() ?? false;
161
121
  }
162
- /**
163
- * The error from the last failed {@link loadEngine} attempt, or `null`. Used by
164
- * the React provider to surface a wasm-load failure to an error boundary rather
165
- * than suspending forever, and by imperative pollers to tell "failed" apart from
166
- * "still loading". Cleared when a new load starts or {@link resetEngine}.
167
- */
168
122
  function getEngineError() {
169
123
  return engineError;
170
124
  }
171
- /**
172
- * Return the already-initialized engine synchronously, or throw
173
- * {@link ExpressionNotReadyError} if {@link loadEngine} has not resolved yet.
174
- */
175
125
  function getEngineSync() {
176
126
  if (!engineSync) throw new ExpressionNotReadyError();
177
127
  return engineSync;
178
128
  }
179
- /**
180
- * Reset the engine singleton — drops the loaded engine, the cached type context,
181
- * the latched error, and the configured wasm input. Mainly for tests, but also the
182
- * way to re-run {@link configureEngine} after a load (configure throws once the
183
- * engine has started loading).
184
- */
185
129
  function resetEngine() {
186
130
  typeContextCache?.handle.free();
187
131
  typeContextCache = null;
@@ -192,16 +136,6 @@ function resetEngine() {
192
136
  }
193
137
  //#endregion
194
138
  //#region src/internal/env.ts
195
- /**
196
- * Best-effort development-mode flag for diagnostic-only code paths.
197
- *
198
- * Detected from `process.env.NODE_ENV` (defined by Node, test runners, webpack,
199
- * and Vite's SSR/`define`). The `typeof` guard is load-bearing: in a plain
200
- * browser bundle `process` is not declared at all, and any bare reference would
201
- * throw a ReferenceError while this module is being imported. Hosts without a
202
- * `process` default to "production" — the safe choice, since `isDev` only gates
203
- * extra developer-facing warnings.
204
- */
205
139
  const isDev = detectDev();
206
140
  function detectDev() {
207
141
  if (typeof process === "undefined") return false;
@@ -209,14 +143,6 @@ function detectDev() {
209
143
  }
210
144
  //#endregion
211
145
  //#region src/condition/subject.ts
212
- /**
213
- * Shared subject-path guard for the condition compilers. A subject / left-hand
214
- * path is emitted **verbatim** into ZEN source, so both {@link compileCondition}
215
- * (which writes that source) and {@link liftConditionTree} (which reads it back)
216
- * gate paths through this one predicate — a single definition of "what is a safe
217
- * field path" that cannot drift between the writer and the reader, which matters
218
- * because the guard is also the compiler's injection defense.
219
- */
220
146
  const SUBJECT_PATTERN = /^[A-Z_$][\w$]*(?:\.[A-Z_$][\w$]*|\[\d+\])*$/i;
221
147
  const ZEN_RESERVED_WORDS = new Set([
222
148
  "and",
@@ -227,26 +153,11 @@ const ZEN_RESERVED_WORDS = new Set([
227
153
  "false",
228
154
  "null"
229
155
  ]);
230
- /**
231
- * Whether `subject` is a plain identifier path safe to emit verbatim into ZEN
232
- * source: dotted/indexed segments only, none of which is a ZEN reserved word.
233
- */
234
156
  function isIdentifierPath(subject) {
235
157
  return SUBJECT_PATTERN.test(subject) && (subject.match(/[A-Z_$][\w$]*/gi) ?? []).every((segment) => !ZEN_RESERVED_WORDS.has(segment));
236
158
  }
237
159
  //#endregion
238
160
  //#region src/condition/compile.ts
239
- /**
240
- * Serialize a JavaScript value into a ZEN literal. Nullish becomes `null`;
241
- * numbers / booleans / bigints are emitted verbatim; strings are quoted via
242
- * {@link encodeZenString}; arrays become `[a, b, ...]`.
243
- *
244
- * Throws {@link ExpressionError} for a value with no faithful ZEN
245
- * representation — an object, symbol, or function, or a string containing both
246
- * quote styles. Callers that need a sentinel instead of a throw go through
247
- * {@link compileCondition}, which degrades such a value to a non-compiling
248
- * (null) condition.
249
- */
250
161
  function toZenLiteral(value) {
251
162
  if (isNullish(value)) return "null";
252
163
  if (typeof value === "number") {
@@ -258,13 +169,6 @@ function toZenLiteral(value) {
258
169
  if (isArray(value)) return `[${value.map((item) => toZenLiteral(item)).join(", ")}]`;
259
170
  throw new ExpressionError(`Value of type "${typeof value}" has no ZEN literal representation`);
260
171
  }
261
- /**
262
- * Encode a string as a ZEN literal. ZEN string literals are **raw** between
263
- * matching quotes and honor no backslash escapes (`'a\nb'` is the four
264
- * characters `a \ n b`), so the encoder must not escape — it picks a delimiter
265
- * the value does not contain. A value containing both quote styles cannot be
266
- * represented as a raw ZEN literal and throws.
267
- */
268
172
  function encodeZenString(value) {
269
173
  const hasSingle = value.includes("'");
270
174
  const hasDouble = value.includes("\"");
@@ -274,15 +178,6 @@ function encodeZenString(value) {
274
178
  function toArrayLiteral(value) {
275
179
  return isArray(value) ? toZenLiteral(value) : `[${toZenLiteral(value)}]`;
276
180
  }
277
- /**
278
- * Emit the ZEN emptiness test matching the backend field evaluator's
279
- * `isEmptyValue`: null, blank (whitespace-only) text, and empty arrays are
280
- * empty; numbers and booleans never are. ZEN's `or` short-circuits, so the
281
- * type-guarded branches never evaluate against a null subject. The backend
282
- * additionally treats an empty map as empty for totality, but form values —
283
- * the only subjects this compiler targets — are never objects, and ZEN's
284
- * `len()` does not accept one.
285
- */
286
181
  function zenIsEmpty(subject) {
287
182
  return `(${subject} == null or (type(${subject}) == 'string' and len(trim(${subject})) == 0) or (type(${subject}) == 'array' and len(${subject}) == 0))`;
288
183
  }
@@ -305,12 +200,6 @@ function compileFieldCondition(subject, operator, value) {
305
200
  default: throw new ExpressionError(`Unsupported operator: ${String(operator)}`);
306
201
  }
307
202
  }
308
- /**
309
- * Compile a single condition into a ZEN boolean expression. Field conditions map
310
- * their operator to ZEN; expression conditions are wrapped in parentheses to
311
- * preserve their grouping. Returns `null` when the condition is empty, its
312
- * subject is not an identifier path, or its value has no ZEN representation.
313
- */
314
203
  function compileCondition(condition) {
315
204
  if (condition.kind === "expression") {
316
205
  const expression = condition.expression.trim();
@@ -324,34 +213,14 @@ function compileCondition(condition) {
324
213
  return null;
325
214
  }
326
215
  }
327
- /**
328
- * Compile a condition group (its conditions joined with AND). Returns `null`
329
- * when the group has no compilable conditions.
330
- */
331
216
  function compileGroup(group) {
332
217
  const parts = group.conditions.map((condition) => compileCondition(condition)).filter((part) => part !== null);
333
218
  return parts.length === 0 ? null : parts.join(" and ");
334
219
  }
335
- /**
336
- * Compile a branch's condition groups into a single ZEN expression (groups
337
- * joined with OR). Returns `null` when the branch has no compilable groups
338
- * (e.g. a default branch).
339
- */
340
220
  function compileBranch(branch) {
341
221
  const groups = (branch.conditionGroups ?? []).map((group) => compileGroup(group)).filter((group) => group !== null);
342
222
  return groups.length === 0 ? null : groups.map((group) => `(${group})`).join(" or ");
343
223
  }
344
- /**
345
- * Pick the matching branch for the given context using a pre-loaded engine.
346
- * Non-default branches are tested in ascending `priority` order; the first whose
347
- * compiled expression evaluates to `true` wins. Falls back to the default
348
- * branch, or a `null` id when neither matches.
349
- *
350
- * A branch whose expression throws for the given context (e.g. ZEN's `>`
351
- * throws when the subject is missing or non-numeric) is treated as not matching
352
- * rather than propagating — so a missing field degrades to the default branch
353
- * instead of crashing the caller.
354
- */
355
224
  function selectBranchWith(branches, context, engine) {
356
225
  const ordered = branches.toSorted((a, b) => a.priority - b.priority);
357
226
  for (const branch of ordered) {
@@ -388,30 +257,11 @@ function reportEvaluationFailure(engine, expression, error) {
388
257
  }
389
258
  if (!isNullish(diagnostic)) console.warn(`[expression] compiled branch expression failed to parse: ${expression}`, diagnostic, error);
390
259
  }
391
- /**
392
- * Pick the matching branch for the given context, loading the ZEN engine on
393
- * first use. See {@link selectBranchWith} for the selection semantics.
394
- */
395
260
  async function selectBranch(branches, context) {
396
261
  return selectBranchWith(branches, context, await loadEngine());
397
262
  }
398
263
  //#endregion
399
264
  //#region src/condition/types.ts
400
- /**
401
- * Structural shapes for the visual condition model compiled to ZEN. They mirror
402
- * the condition types used in the form and flow editors so a host can map those
403
- * definitions to {@link compileCondition} / {@link selectBranch} without this
404
- * package depending on either editor. The editors keep a flat working model for
405
- * form ergonomics (a row can hold both a half-typed field triple and an
406
- * expression while the author toggles between them); this discriminated input is
407
- * the narrowed, compiler-facing shape where each kind carries only what it uses.
408
- */
409
- /**
410
- * The closed operator vocabulary understood by {@link compileCondition}, as a
411
- * runtime constant so validators can build allow-lists from it instead of
412
- * re-declaring the set. The {@link ConditionOperator} type derives from this
413
- * array — one definition site for both the type and the runtime list.
414
- */
415
265
  const CONDITION_OPERATORS = [
416
266
  "eq",
417
267
  "ne",
@@ -444,32 +294,15 @@ const CONDITION_OPERATOR_ARITIES = {
444
294
  is_empty: "none",
445
295
  is_not_empty: "none"
446
296
  };
447
- /**
448
- * The arity of `operator`'s right-hand operand (see
449
- * {@link ConditionOperatorArity}). One definition site next to
450
- * {@link CONDITION_OPERATORS}, so the tree compiler's arity enforcement and a
451
- * condition editor's operand controls classify operators identically instead of
452
- * each keeping a drift-prone copy.
453
- */
454
297
  function conditionOperatorArity(operator) {
455
298
  return CONDITION_OPERATOR_ARITIES[operator];
456
299
  }
457
300
  //#endregion
458
301
  //#region src/condition/compile-tree.ts
459
- /**
460
- * Compile a condition tree to a canonical ZEN expression, or `""` when no rule in
461
- * the tree is compilable (see the module note for the drop semantics).
462
- */
463
302
  function compileConditionTree(tree) {
464
303
  const normalized = normalizeNode(tree);
465
304
  return normalized === null ? "" : emitNode(normalized, true);
466
305
  }
467
- /**
468
- * Prune a node to its compilable core: drop rules {@link compileRule} rejects, drop
469
- * emptied groups, and collapse a single-surviving-item group to that item (so the
470
- * emitted shape carries no redundant parentheses). Returns `null` when nothing in
471
- * the node survives.
472
- */
473
306
  function normalizeNode(node) {
474
307
  if (node.kind === "rule") return compileRule(node) === null ? null : node;
475
308
  const items = node.items.map((item) => normalizeNode(item)).filter((item) => item !== null);
@@ -481,27 +314,11 @@ function normalizeNode(node) {
481
314
  items
482
315
  };
483
316
  }
484
- /**
485
- * Render a normalized node. A rule emits its lowered ZEN; a group joins its items
486
- * with ` and ` / ` or ` and, unless it is the top-level group, wraps them in
487
- * parentheses so the tree structure survives ZEN's operator precedence on lift.
488
- */
489
317
  function emitNode(node, topLevel) {
490
318
  if (node.kind === "rule") return compileRule(node);
491
319
  const joined = node.items.map((item) => emitNode(item, false)).join(node.op === "and" ? " and " : " or ");
492
320
  return topLevel ? joined : `(${joined})`;
493
321
  }
494
- /**
495
- * Lower a leaf rule to ZEN via {@link compileCondition}, or `null` for a rule the
496
- * compiler cannot represent. The rule's `right` must match its operator's arity —
497
- * a single scalar for the comparison / string operators, an array of scalars for
498
- * `in` / `not_in`, absent for the emptiness operators (the contract
499
- * {@link ConditionTreeValue} documents); an off-arity rule is non-compilable and
500
- * drops. Enforcing arity here, not just relying on `compileCondition`, is what
501
- * keeps every emitted expression liftable: the flat compiler's lax value handling
502
- * would happily serialize e.g. a missing `right` as a `null` literal, which is not
503
- * part of the canonical grammar.
504
- */
505
322
  function compileRule(rule) {
506
323
  if (!matchesOperatorArity(rule)) return null;
507
324
  return compileCondition({
@@ -562,13 +379,6 @@ const CALL_OPERATORS = {
562
379
  endsWith: "ends_with"
563
380
  };
564
381
  const MAX_GROUP_DEPTH = 64;
565
- /**
566
- * Lift a ZEN expression to a condition tree, or `null` when it is not in the
567
- * canonical form {@link compileConditionTree} produces (the consumer then keeps the
568
- * raw expression). Groups nested deeper than 64 parenthesized levels are refused as
569
- * non-canonical rather than risking parser-stack overflow on adversarial input. The
570
- * returned root is always a group.
571
- */
572
382
  function liftConditionTree(expression) {
573
383
  const tokens = tokenize(expression);
574
384
  if (tokens === null || tokens.length === 0) return null;
@@ -800,12 +610,6 @@ function liftConditionTree(expression) {
800
610
  if (parsed === null || pos !== tokens.length) return null;
801
611
  return asGroup(parsed);
802
612
  }
803
- /**
804
- * Read a dotted/indexed identifier path starting at `from`, validated through
805
- * {@link isIdentifierPath}. Pure over the token array (no cursor) so it can probe a
806
- * candidate path — the emptiness oracle reads the blob's subject without committing
807
- * the cursor. Returns the path and the index just past it, or `null`.
808
- */
809
613
  function readPath(tokens, from) {
810
614
  const head = tokens[from];
811
615
  if (head === void 0 || head.kind !== "ident") return null;
@@ -843,13 +647,6 @@ function asGroup(node) {
843
647
  function isPunct(token, value) {
844
648
  return token !== void 0 && token.kind === "punct" && token.value === value;
845
649
  }
846
- /**
847
- * Split a ZEN expression into tokens, or `null` on an unexpected character or an
848
- * unterminated string. Whitespace is dropped, so the parser (and the emptiness
849
- * oracle's token match) is insensitive to spacing. String literals are read raw
850
- * between matching quotes — ZEN honors no backslash escapes, mirroring the encoder
851
- * in {@link compileCondition}.
852
- */
853
650
  function tokenize(input) {
854
651
  const tokens = [];
855
652
  let index = 0;
@@ -912,56 +709,20 @@ function tokenize(input) {
912
709
  }
913
710
  //#endregion
914
711
  //#region src/condition/tree-types.ts
915
- /**
916
- * The visual condition **tree** model: an arbitrarily nested and/or tree of typed
917
- * comparison rules that {@link compileConditionTree} serializes to a single ZEN
918
- * boolean expression and {@link liftConditionTree} reconstructs from one. It is a
919
- * distinct, self-contained shape from the compiler's flat {@link ConditionInput} —
920
- * the tree is what a structured builder UI edits, whereas `ConditionInput` is the
921
- * narrowed per-condition shape the compiler consumes. The two meet only at the
922
- * leaf: a {@link ConditionTreeRule} lowers to a field {@link ConditionInput} so the
923
- * operator vocabulary, literal encoding, and injection guard have one owner.
924
- */
925
- /**
926
- * The tree's operator vocabulary as a runtime list — the compiler's full
927
- * {@link CONDITION_OPERATORS} set under the tree name. An alias, not a second
928
- * hand-maintained list, so the tree vocabulary can never drift from the compiler's;
929
- * it gives tree code and the builder UI one tree-named import for the operators they
930
- * support.
931
- */
932
712
  const CONDITION_TREE_OPERATORS = CONDITION_OPERATORS;
933
713
  //#endregion
934
714
  //#region src/engine/evaluate.ts
935
- /**
936
- * Evaluate a standard ZEN expression, loading the engine on first use.
937
- *
938
- * `T` is an **unchecked** assertion — the value is returned as `T` with no
939
- * runtime validation, and ZEN's result type depends on the expression and
940
- * context. Prefer the `unknown` default and narrow at the call site.
941
- */
942
715
  async function evaluate(expression, context) {
943
716
  await loadEngine();
944
717
  return evaluateSync(expression, context);
945
718
  }
946
- /**
947
- * Evaluate a ZEN unary (test) expression, loading the engine on first use.
948
- */
949
719
  async function evaluateUnary(expression, context) {
950
720
  await loadEngine();
951
721
  return evaluateUnarySync(expression, context);
952
722
  }
953
- /**
954
- * Evaluate a standard ZEN expression synchronously. Throws
955
- * {@link ExpressionNotReadyError} when the engine has not loaded yet — use this
956
- * only behind a readiness gate (e.g. under `<ExpressionEngineProvider>`).
957
- */
958
723
  function evaluateSync(expression, context) {
959
724
  return getEngineSync().evaluate(expression, context);
960
725
  }
961
- /**
962
- * Evaluate a ZEN unary (test) expression synchronously. Throws
963
- * {@link ExpressionNotReadyError} when the engine has not loaded yet.
964
- */
965
726
  function evaluateUnarySync(expression, context) {
966
727
  return getEngineSync().evaluateUnary(expression, context);
967
728
  }
@@ -1070,9 +831,6 @@ const ZH_SOURCE_LABELS = {
1070
831
  function sourceLabelFrom(table, fallback, type) {
1071
832
  return (type === void 0 ? void 0 : table[type]) ?? fallback;
1072
833
  }
1073
- /**
1074
- * Built-in English message catalog (the default).
1075
- */
1076
834
  const enMessages = {
1077
835
  sourceLabel: (type) => sourceLabelFrom(EN_SOURCE_LABELS, "Error", type),
1078
836
  completionInfo: (info) => info,
@@ -1080,9 +838,6 @@ const enMessages = {
1080
838
  expectedBoolean: (actualType) => `Expected a boolean test expression, received \`${actualType}\`.`,
1081
839
  expectedType: (expectedType, actualType) => `Expected \`${expectedType}\`, received \`${actualType}\`.`
1082
840
  };
1083
- /**
1084
- * Built-in Simplified Chinese message catalog.
1085
- */
1086
841
  const zhCNMessages = {
1087
842
  sourceLabel: (type) => sourceLabelFrom(ZH_SOURCE_LABELS, "错误", type),
1088
843
  completionInfo: (info) => info === "" ? "" : COMPLETION_INFO_ZH[info] ?? info,
@@ -1092,20 +847,9 @@ const zhCNMessages = {
1092
847
  };
1093
848
  const localeRegistry = new Map([["en-US", enMessages], ["zh-CN", zhCNMessages]]);
1094
849
  let activeMessages = enMessages;
1095
- /**
1096
- * Register (or replace) the message catalog for a locale key, making it selectable
1097
- * via {@link configureExpressionMessages}. This is how a host adds a language the
1098
- * library does not ship — the built-in locales are registered the same way, so
1099
- * there is no privileged path.
1100
- */
1101
850
  function registerExpressionLocale(locale, messages) {
1102
851
  localeRegistry.set(locale, messages);
1103
852
  }
1104
- /**
1105
- * Configure the active message catalog. Pass a `locale`, a partial `messages`
1106
- * override, or both (overrides win). Idempotent and module-global, so a host
1107
- * configures it once (e.g. through `ExpressionConfigProvider`).
1108
- */
1109
853
  function configureExpressionMessages({ locale, messages }) {
1110
854
  const base = locale === void 0 ? activeMessages : localeRegistry.get(locale) ?? activeMessages;
1111
855
  activeMessages = messages === void 0 ? base : {
@@ -1113,9 +857,6 @@ function configureExpressionMessages({ locale, messages }) {
1113
857
  ...messages
1114
858
  };
1115
859
  }
1116
- /**
1117
- * The active {@link ExpressionMessages} catalog (English by default).
1118
- */
1119
860
  function getExpressionMessages() {
1120
861
  return activeMessages;
1121
862
  }
@@ -1125,11 +866,6 @@ function parseOffset(text) {
1125
866
  const trimmed = text?.trim();
1126
867
  return trimmed ? Number(trimmed) : NaN;
1127
868
  }
1128
- /**
1129
- * Parse a trailing `... at (from, to)` / `... at pos` position out of a ZEN error
1130
- * message. Returns a `[from, to]` range — a single offset collapses to
1131
- * `[pos, pos]` — or `null` when no position is present.
1132
- */
1133
869
  function extractPosition(message) {
1134
870
  const segments = message.split(" at ");
1135
871
  const last = segments.length <= 1 ? void 0 : segments.at(-1);
@@ -1140,10 +876,6 @@ function extractPosition(message) {
1140
876
  const to = parseOffset(right);
1141
877
  return [from, Number.isNaN(to) ? from : to];
1142
878
  }
1143
- /**
1144
- * Normalize the wasm validate payload (`null` or `{ type, source }`) into a
1145
- * positioned {@link ExpressionDiagnostic}, or `null` when the expression is valid.
1146
- */
1147
879
  function normalizeDiagnostic(raw, source) {
1148
880
  if (raw === null || raw === void 0) return null;
1149
881
  const errorType = isRecord(raw) && typeof raw.type === "string" ? raw.type : void 0;
@@ -1156,10 +888,6 @@ function normalizeDiagnostic(raw, source) {
1156
888
  source: getExpressionMessages().sourceLabel(errorType)
1157
889
  };
1158
890
  }
1159
- /**
1160
- * Normalize the wasm `getCompletions` payload into a typed list, dropping
1161
- * malformed entries and stripping the backtick markers ZEN wraps type names in.
1162
- */
1163
891
  function normalizeCompletions(raw) {
1164
892
  if (!Array.isArray(raw)) return [];
1165
893
  const messages = getExpressionMessages();
@@ -1175,75 +903,38 @@ function normalizeCompletions(raw) {
1175
903
  }];
1176
904
  });
1177
905
  }
1178
- /**
1179
- * Validate an expression, loading the engine on first use. Resolves to `null`
1180
- * when the expression is valid, or a positioned diagnostic otherwise.
1181
- */
1182
906
  async function getDiagnostics(expression, mode) {
1183
907
  await loadEngine();
1184
908
  return getDiagnosticsSync(expression, mode);
1185
909
  }
1186
- /**
1187
- * Synchronous {@link getDiagnostics}. Throws `ExpressionNotReadyError` if the
1188
- * engine has not loaded yet — use only behind a readiness gate.
1189
- */
1190
910
  function getDiagnosticsSync(expression, mode) {
1191
911
  const engine = getEngineSync();
1192
912
  return normalizeDiagnostic(mode === "unary" ? engine.validateUnary(expression) : engine.validate(expression), expression);
1193
913
  }
1194
- /**
1195
- * Return the ZEN built-in completion list, loading the engine on first use.
1196
- */
1197
914
  async function getCompletionItems() {
1198
915
  await loadEngine();
1199
916
  return getCompletionItemsSync();
1200
917
  }
1201
- /**
1202
- * Synchronous {@link getCompletionItems}. Throws `ExpressionNotReadyError` if the
1203
- * engine has not loaded yet.
1204
- */
1205
918
  function getCompletionItemsSync() {
1206
919
  return normalizeCompletions(getEngineSync().getCompletions());
1207
920
  }
1208
- /**
1209
- * Type-check an expression against a `variables` context, loading the engine on
1210
- * first use. See {@link ExpressionAnalysis}.
1211
- */
1212
921
  async function analyzeTypes(variables, source, mode) {
1213
922
  await loadEngine();
1214
923
  return analyzeTypesSync(variables, source, mode);
1215
924
  }
1216
- /**
1217
- * Synchronous {@link analyzeTypes}. Throws `ExpressionNotReadyError` if the
1218
- * engine has not loaded yet.
1219
- */
1220
925
  function analyzeTypesSync(variables, source, mode) {
1221
926
  return getEngineSync().analyze(variables, source, mode === "unary");
1222
927
  }
1223
- /**
1224
- * Whether `actual` satisfies (is assignable to) `expected`, loading the engine on
1225
- * first use. Used for expected-return-type validation.
1226
- */
1227
928
  async function satisfiesType(actual, expected) {
1228
929
  await loadEngine();
1229
930
  return satisfiesTypeSync(actual, expected);
1230
931
  }
1231
- /**
1232
- * Synchronous {@link satisfiesType}. Throws `ExpressionNotReadyError` if the
1233
- * engine has not loaded yet.
1234
- */
1235
932
  function satisfiesTypeSync(actual, expected) {
1236
933
  return getEngineSync().satisfies(actual, expected);
1237
934
  }
1238
935
  //#endregion
1239
936
  //#region src/engine/template.ts
1240
937
  const HOLE_PATTERN = /\{\{(?<expression>[^{}]*)\}\}/g;
1241
- /**
1242
- * Extract every `{{ expression }}` hole from a template document, in source
1243
- * order. Literal text outside holes is ignored. Pure string scan — no engine
1244
- * required — matching the `@gorules/lezer-zen-template` hole grammar, so it is
1245
- * consistent with the editor's highlighting and completion gating.
1246
- */
1247
938
  function parseTemplateHoles(source) {
1248
939
  const holes = [];
1249
940
  HOLE_PATTERN.lastIndex = 0;
@@ -1258,31 +949,12 @@ function parseTemplateHoles(source) {
1258
949
  }
1259
950
  return holes;
1260
951
  }
1261
- /**
1262
- * The template hole whose inner expression range contains `pos` (boundaries
1263
- * included, so a caret sitting right after `{{` or right before `}}` counts as
1264
- * inside), or `null` when `pos` is in literal text. Drives the editor's
1265
- * hole-scoped completion and hover: intelligence fires inside a hole, nothing in
1266
- * the surrounding literal text.
1267
- */
1268
952
  function templateHoleAt(source, pos) {
1269
953
  return parseTemplateHoles(source).find((hole) => pos >= hole.from && pos <= hole.to) ?? null;
1270
954
  }
1271
955
  function hasExpression(hole) {
1272
956
  return hole.expression.trim().length > 0;
1273
957
  }
1274
- /**
1275
- * Type-check every hole of a template against a `variables` context and merge the
1276
- * results into one {@link ExpressionAnalysis} whose spans are offset onto the
1277
- * template document. Each hole is a standard ZEN value expression; literal text
1278
- * contributes nothing. `rootKind` is the context itself (shared by every hole),
1279
- * so top-level completion works even in an empty hole.
1280
- *
1281
- * Best-effort: a hole that fails to analyze (e.g. mid-edit syntax) is skipped
1282
- * rather than discarding the spans of its siblings. Throws
1283
- * {@link ExpressionNotReadyError} if the engine has not loaded — use only behind
1284
- * a readiness gate.
1285
- */
1286
958
  function analyzeTemplateSync(variables, source) {
1287
959
  if (!isEngineReady()) throw new ExpressionNotReadyError();
1288
960
  const spans = [];
@@ -1300,22 +972,10 @@ function analyzeTemplateSync(variables, source) {
1300
972
  spans
1301
973
  };
1302
974
  }
1303
- /**
1304
- * Async {@link analyzeTemplateSync}, loading the engine on first use.
1305
- */
1306
975
  async function analyzeTemplate(variables, source) {
1307
976
  await loadEngine();
1308
977
  return analyzeTemplateSync(variables, source);
1309
978
  }
1310
- /**
1311
- * Validate every hole of a template and return their syntax diagnostics, each
1312
- * offset onto the template document (empty holes and literal text produce none).
1313
- * The single-expression {@link getDiagnosticsSync} yields at most one diagnostic
1314
- * per hole, so a template with several broken holes surfaces each one.
1315
- *
1316
- * Throws {@link ExpressionNotReadyError} if the engine has not loaded — use only
1317
- * behind a readiness gate.
1318
- */
1319
979
  function getTemplateDiagnosticsSync(source) {
1320
980
  if (!isEngineReady()) throw new ExpressionNotReadyError();
1321
981
  const diagnostics = [];
@@ -1330,14 +990,9 @@ function getTemplateDiagnosticsSync(source) {
1330
990
  }
1331
991
  return diagnostics;
1332
992
  }
1333
- /**
1334
- * Async {@link getTemplateDiagnosticsSync}, loading the engine on first use.
1335
- */
1336
993
  async function getTemplateDiagnostics(source) {
1337
994
  await loadEngine();
1338
995
  return getTemplateDiagnosticsSync(source);
1339
996
  }
1340
997
  //#endregion
1341
998
  export { CONDITION_OPERATORS, CONDITION_TREE_OPERATORS, ExpressionError, ExpressionNotReadyError, analyzeTemplate, analyzeTemplateSync, analyzeTypes, analyzeTypesSync, compileBranch, compileCondition, compileConditionTree, compileGroup, conditionOperatorArity, configureEngine, configureExpressionMessages, enMessages, evaluate, evaluateSync, evaluateUnary, evaluateUnarySync, getCompletionItems, getCompletionItemsSync, getDiagnostics, getDiagnosticsSync, getEngineError, getEngineSync, getExpressionMessages, getTemplateDiagnostics, getTemplateDiagnosticsSync, isEngineReady, liftConditionTree, loadEngine, parseTemplateHoles, registerExpressionLocale, resetEngine, satisfiesType, satisfiesTypeSync, selectBranch, selectBranchWith, templateHoleAt, toZenLiteral, zhCNMessages };
1342
-
1343
- //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coldsmirk/abacus-core",
3
- "version": "0.3.1",
3
+ "version": "0.4.1",
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",
@@ -17,7 +17,7 @@
17
17
  "url": "git+https://github.com/coldsmirk/abacus.git",
18
18
  "directory": "packages/core"
19
19
  },
20
- "license": "Apache-2.0",
20
+ "license": "UNLICENSED",
21
21
  "author": {
22
22
  "name": "Venus"
23
23
  },