@lemmabase/lemma-engine 0.8.22 → 0.9.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
@@ -1,6 +1,6 @@
1
1
  # @lemmabase/lemma-engine
2
2
 
3
- > [Lemma](https://github.com/lemma/lemma) is a declarative language for business rules. **This package is the engine, compiled to WebAssembly** - runs in the browser, on Node, Bun, Deno, Cloudflare Workers, Vercel Edge, etc.
3
+ > [Lemma](https://github.com/lemma/lemma) is a declarative language for business rules. **This package is the Lemma engine for JavaScript and TypeScript** browser, Node, Bun, Deno, Cloudflare Workers, Vercel Edge, etc.
4
4
 
5
5
  Pricing tiers, tax brackets, leave entitlement, eligibility checks, discount stacks: the rules that change, that auditors ask about, that legal writes in PDFs and engineers re-implement in operational code... Lemma is a language built specifically for your business rules. It is readable by stakeholders, executable anywhere, and impossible to drift out of sync.
6
6
 
@@ -28,22 +28,22 @@ rule total:
28
28
  import { Lemma } from '@lemmabase/lemma-engine';
29
29
 
30
30
  const engine = await Lemma();
31
- await engine.load(pricing, 'pricing.lemma');
31
+ await engine.load({ 'pricing.lemma': pricing });
32
32
 
33
- const response = engine.run(null, 'pricing', null, { quantity: 50, is_vip: false }, null);
33
+ const response = engine.run({ spec: 'pricing', data: { quantity: 50, is_vip: false } });
34
34
  // response.results.unit_price → 16 eur
35
35
  // response.results.total → 800 eur
36
36
  ```
37
37
 
38
- The `Response` carries every rule's value (or `veto` if no result could be computed), the input snapshot, and the source location of every rule that fired, allowing you to render an audit trail in your UI.
38
+ The `Response` carries every rule's value (or `veto` if no result could be computed). When inputs are still unbound, that rule includes `missing_data` (`string[]` input keys). Types, prefilled literals, and suggestions are on `engine.show(...)` (`Show.data`) only not on the evaluate response.
39
39
 
40
40
  ## Why use it from JavaScript?
41
41
 
42
42
  - **Deterministic.** `(spec, data, effective_date) → result`. No DB, no clock, no ambient state. Same inputs → same outputs, every time.
43
- - **Explainable.** The `Response` tells you which rules contributed and why; pair it with the [CLI](https://github.com/lemma/lemma) for a full reasoning trace.
43
+ - **Explainable.** Pass `explain: true` in your `run()` options to get a per-rule explanation tree; see [api.v1.json](https://github.com/lemma/lemma/blob/main/documentation/schemas/api.v1.json). Pair with the [CLI](https://github.com/lemma/lemma) for human reasoning tables.
44
44
  - **Time-aware.** Multiple versions of the same spec coexist. Pass an `effective` date and the engine resolves the version in force on that day.
45
45
  - **Statically checked.** Type errors, missing data, cycles, measure-family mismatches - all caught at `load()` time. Bad specs never reach `run()`.
46
- - **Runs anywhere V8 does.** ~2 MB WASM, no native binary, no postinstall script.
46
+ - **Runs anywhere JavaScript does.** ~2 MB package, no native binary, no postinstall script.
47
47
  - **Editor in a tab.** Includes an in-process language server and a Monaco adapter, so you can build a real Lemma editor experience client-side - diagnostics, completion, formatting... even without setting up a server.
48
48
 
49
49
  ## Install
@@ -60,9 +60,9 @@ import { Lemma } from '@lemmabase/lemma-engine';
60
60
  const engine = await Lemma();
61
61
  ```
62
62
 
63
- `Lemma()` initializes the WASM module once and returns an `Engine`. Serve over **http(s)**, not `file://`. For manual control: `init()` then `new Engine()`.
63
+ `Lemma()` initializes the engine once and returns an `Engine`. Serve over **http(s)**, not `file://`. For manual control: `init()` then `new Engine()`.
64
64
 
65
- If your bundler emits IIFE, can't resolve `import.meta.url`, or refuses to ship `lemma_bg.wasm` as a separate asset, use the inlined entry - it embeds the wasm bytes in the JS bundle:
65
+ If your bundler emits IIFE, can't resolve `import.meta.url`, or refuses to ship the engine module as a separate asset, use the inlined entry everything ships in one JS bundle:
66
66
 
67
67
  ```javascript
68
68
  import { Lemma } from '@lemmabase/lemma-engine/iife';
@@ -86,7 +86,7 @@ import { Lemma } from '@lemmabase/lemma-engine';
86
86
  const engine = await Lemma();
87
87
  ```
88
88
 
89
- For zero-fetch startup with a preloaded module: `initSync({ module })` then `new Engine()`.
89
+ For zero-fetch startup: `initSync({ module })` then `new Engine()`.
90
90
 
91
91
  ## In-process LSP + Monaco
92
92
 
@@ -111,39 +111,40 @@ A pre-wired Monaco adapter ships at `@lemmabase/lemma-engine/monaco`.
111
111
 
112
112
  | Method | Description |
113
113
  |--------|-------------|
114
- | `load(code, attribute?)` | Parse and validate a `.lemma` spec set. Resolves on success; rejects with `EngineError[]`. |
115
- | `load_batch(sources, dependency?)` | Load many sources in one planning pass (see `lemma.d.ts`). |
114
+ | `load(code)` | Load inline Lemma source as a volatile workspace source |
115
+ | `load(sources)` | Load multiple sources in one planning pass (`Record<label, text>` or `[label, code][]`; object keys keep insertion order; `@org/pkg` keys tag dependencies) |
116
116
  | `fetch(name)` | Download registry source only; resolves with `{ source, id }`. Does not load. Rejects with `EngineError[]`. |
117
- | `list()` | JSON array of `ResolvedRepository`: each has `repository` and `specs` (spec sets). Always includes embedded `lemma` / `spec units`. |
118
- | `format_repository(repo)` | Canonical Lemma source for a loaded repository, formatted from the in-engine AST. Use `"lemma"` for the embedded units stdlib. |
119
- | `schema(repo, name, effective?)` | `SpecSchema`; `repo` null for workspace. |
120
- | `run(repo, name, ruleNames, data, effective?, explain?)` | Evaluate. Omit/`null` `ruleNames` for all rules; pass a non-empty array to scope. `[]` errors. Returns a `Response`. |
117
+ | `list()` | Slim catalog: `ResolvedRepository[]` with `repository` and temporal `specs` rows. Always includes embedded `lemma` / `spec units`. |
118
+ | `show(repo, name, effective?)` | Spec interface + temporal window; `repo` null for workspace. |
119
+ | `source(repo, spec?, effective?)` | Canonical Lemma source text. Omit `spec` for whole repository. |
120
+ | `run({ spec, repository?, effective?, data?, rules?, explain? })` | Evaluate. Omit `rules` for all rules; pass a non-empty array to scope. `[]` errors. Returns a `Response`. `explain: true` adds per-rule explanation trees. |
121
+ | `remove(repo, name, effective?)` | Remove a temporal spec slice. |
122
+ | `limits()` | Resource limits for this engine. |
121
123
  | `format(code, attribute?)` | Canonical formatting; throws `EngineError` on parse error. |
122
124
 
123
125
  Full TypeScript types are bundled - see `lemma.d.ts`.
124
126
 
125
127
  ### Registry dependencies
126
128
 
127
- Specs that reference `uses … @org/pkg` need that package available. `fetch` only downloads; call `load_batch` to load the dependency, then load your workspace:
129
+ Specs that reference `uses … @org/pkg` need that package available. `fetch` only downloads; call `load` with the dependency id as the source label, then load your workspace:
128
130
 
129
131
  ```javascript
130
132
  import { Lemma } from '@lemmabase/lemma-engine';
131
133
 
132
134
  const engine = await Lemma();
133
135
  const { source, id } = await engine.fetch('@iso/countries');
134
- await engine.load_batch({ '': source }, id);
135
- await engine.load(sourceThatUsesStd, 'app.lemma');
136
+ await engine.load({ [id]: source, 'app.lemma': sourceThatUsesStd });
136
137
  ```
137
138
 
138
139
  In the browser, the registry must allow your origin (CORS). Use `https` or `http://localhost` when using `fetch`.
139
140
 
140
141
  ## Status
141
142
 
142
- Lemma is pre-1.0. The WASM API is stable for most use cases, but breaking changes may occur between minor versions. Pin your dependency version and review the [changelog](https://github.com/lemma/lemma/blob/main/CHANGELOG.md) before upgrading.
143
+ Lemma is pre-1.0. The JavaScript API is stable for most use cases, but breaking changes may occur between minor versions. Pin your dependency version and review the [changelog](https://github.com/lemma/lemma/blob/main/CHANGELOG.md) before upgrading.
143
144
 
144
- ### WASM panic behavior
145
+ ### Runtime traps (internal bugs)
145
146
 
146
- Rust panics cannot unwind on the `wasm32` target, so an internal invariant violation (a bug) traps the WASM instance. The call throws a `RuntimeError` which you can catch with `try/catch` to fail gracefully, but the module's linear memory is poisoned — constructing a new `Engine()` from the same initialized module is not safe. To recover, re-initialize the WASM module (`init()` again) or, for robust containment, run the engine in a Web Worker and respawn the worker on trap. The panic message (prefixed `BUG: ...`) is logged to the console before the trap. All domain-level failures (invalid specs, bad data, impossible rules) are reported as `EngineError[]` or vetoes and never cause traps.
147
+ An internal invariant violation (a bug in the engine) traps the runtime. The call throws a `RuntimeError` which you can catch with `try/catch`, but the loaded module is poisoned — constructing a new `Engine()` from the same initialization is not safe. To recover, call `init()` again or run the engine in a Web Worker and respawn the worker on trap. Domain failures (invalid specs, bad data, impossible rules) are reported as `EngineError[]` or vetoes and never cause traps.
147
148
 
148
149
  ## Related
149
150
 
@@ -13,7 +13,7 @@ export class Engine {
13
13
  [Symbol.dispose](): void;
14
14
  /**
15
15
  * Download Lemma source for a registry identifier via [`crate::registry::LemmaBase`]. Returns `{ source, id }`.
16
- * Does not load this [`WasmEngine`]; call [`Self::load_batch`], etc., yourself.
16
+ * Does not load this [`WasmEngine`]; call [`Self::load`] with `{ [id]: source }`.
17
17
  */
18
18
  fetch(name: string): Promise<any>;
19
19
  /**
@@ -21,43 +21,41 @@ export class Engine {
21
21
  */
22
22
  format(code: string, attribute?: string | null): any;
23
23
  /**
24
- * Canonical Lemma source for `repository`, formatted from the in-engine AST (e.g. `"lemma"`).
24
+ * Resource limits configured for this engine.
25
25
  */
26
- format_repository(repository: string): string;
26
+ limits(): any;
27
27
  /**
28
- * Same data as [`Engine::list`]: grouped [`ResolvedRepository`] JSON without planning.
28
+ * Catalog of loaded repositories and specs (metadata only, no source).
29
29
  */
30
30
  list(): any;
31
31
  /**
32
- * Load Lemma source. Throws with an array of serialized errors
33
- * (same shape as `EngineError` in `engine/packages/npm/lemma.d.ts`).
34
- */
35
- load(code: string, attribute: string): void;
36
- /**
37
- * Load multiple Lemma sources in one planning pass (same as [`Engine::load_batch`]).
32
+ * Load Lemma source(s).
38
33
  *
39
- * `sources` is a plain object mapping path labels to source text. Labels become
40
- * [`SourceType::Path`]; use `""` as a key for [`SourceType::Volatile`].
34
+ * - string one volatile workspace source
35
+ * - plain object or `[label, code][]` labeled sources in one planning pass
41
36
  *
42
- * `dependency`: when non-empty after trim, sources are tagged as that dependency id.
43
- *
44
- * Throws with an array of `JsError` on failure.
37
+ * Throws with an array of serialized errors on failure. `null` / `undefined` are rejected.
45
38
  */
46
- load_batch(sources: any, dependency?: string | null): void;
39
+ load(sources: any): void;
47
40
  constructor();
41
+ /**
42
+ * Remove a temporal spec slice. `effective`: ISO datetime string or omit for now.
43
+ */
44
+ remove(repository: string | null | undefined, spec: string, effective?: string | null): void;
48
45
  /**
49
46
  * Evaluate spec. Returns [`crate::evaluation::Response`] as a JS object. Throws on planning/runtime error.
50
47
  *
51
- * `repository`: repository qualifier (`@org/pkg`), or `null`/empty for workspace (same as
52
- * [`Engine::run`] `repo: None`).
48
+ * Accepts an options object: `{ spec, repository?, effective?, data?, rules?, explain? }`.
53
49
  */
54
- run(repository: string | null | undefined, spec: string, rule_names: any, data_values: any, effective?: string | null, explain?: boolean | null): any;
50
+ run(options: any): any;
55
51
  /**
56
- * Planning schema for the spec ([`crate::planning::execution_plan::SpecSchema`]). Throws on error.
57
- *
58
- * `repository`: qualifier string or `null`/empty for workspace ([`Engine::schema`]).
52
+ * Spec interface and temporal window at `effective`. Lemma text is [`Self::source`].
53
+ */
54
+ show(repository: string | null | undefined, spec: string, effective?: string | null): any;
55
+ /**
56
+ * Formatted canonical Lemma source. Omit `spec` for whole-repository text.
59
57
  */
60
- schema(repository: string | null | undefined, spec: string, effective?: string | null): any;
58
+ source(repository?: string | null, spec?: string | null, effective?: string | null): string;
61
59
  }
62
60
 
63
61
  export class IntoUnderlyingByteSource {
@@ -121,18 +119,19 @@ export interface InitOutput {
121
119
  readonly intounderlyingsource_cancel: (a: number) => void;
122
120
  readonly intounderlyingsource_pull: (a: number, b: number) => number;
123
121
  readonly __wbg_engine_free: (a: number, b: number) => void;
124
- readonly wasmengine_fetch: (a: number, b: number, c: number) => number;
125
- readonly wasmengine_format: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
126
- readonly wasmengine_format_repository: (a: number, b: number, c: number, d: number) => void;
127
- readonly wasmengine_list: (a: number, b: number) => void;
128
- readonly wasmengine_load: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
129
- readonly wasmengine_load_batch: (a: number, b: number, c: number, d: number, e: number) => void;
130
- readonly wasmengine_new: () => number;
131
- readonly wasmengine_run: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number) => void;
132
- readonly wasmengine_schema: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => void;
133
- readonly __wasm_bindgen_func_elem_12297: (a: number, b: number, c: number, d: number) => void;
134
- readonly __wasm_bindgen_func_elem_2740: (a: number, b: number, c: number, d: number) => void;
135
- readonly __wasm_bindgen_func_elem_12302: (a: number, b: number, c: number, d: number) => void;
122
+ readonly engine_fetch: (a: number, b: number, c: number) => number;
123
+ readonly engine_format: (a: number, b: number, c: number, d: number, e: number, f: number) => void;
124
+ readonly engine_limits: (a: number, b: number) => void;
125
+ readonly engine_list: (a: number, b: number) => void;
126
+ readonly engine_load: (a: number, b: number, c: number) => void;
127
+ readonly engine_new: () => number;
128
+ readonly engine_remove: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => void;
129
+ readonly engine_run: (a: number, b: number, c: number) => void;
130
+ readonly engine_show: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => void;
131
+ readonly engine_source: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => void;
132
+ readonly __wasm_bindgen_func_elem_12433: (a: number, b: number, c: number, d: number) => void;
133
+ readonly __wasm_bindgen_func_elem_2735: (a: number, b: number, c: number, d: number) => void;
134
+ readonly __wasm_bindgen_func_elem_12440: (a: number, b: number, c: number, d: number) => void;
136
135
  readonly __wbindgen_export: (a: number, b: number) => number;
137
136
  readonly __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
138
137
  readonly __wbindgen_export3: (a: number) => void;