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