@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/lemma.d.ts CHANGED
@@ -12,14 +12,13 @@ export interface RegistryFetchResult {
12
12
  declare module './lemma.bindings.js' {
13
13
  interface Engine {
14
14
  /**
15
- * Load multiple Lemma sources in one planning pass. Object keys become error-reporting
16
- * paths (`SourceType::Path`); use `""` for volatile/inline. Non-empty `dependency` tags
17
- * the batch as that dependency id. Throws `EngineError[]` on failure.
15
+ * Load Lemma source(s).
16
+ * - string volatile workspace source
17
+ * - object labeled sources (key insertion order); `[label, code][]` labeled sources (array order)
18
+ * Throws `EngineError[]` on failure. `null`/`undefined` rejected.
18
19
  */
19
- load_batch(
20
- sources: Record<string, string>,
21
- dependency?: string | null,
22
- ): void;
20
+ load(code: string): void;
21
+ load(sources: Record<string, string> | Array<[string, string]>): void;
23
22
 
24
23
  /**
25
24
  * Download Lemma source from the registry for `name` (e.g. `@org/pkg`). Resolves with
@@ -29,43 +28,70 @@ declare module './lemma.bindings.js' {
29
28
 
30
29
  /**
31
30
  * JSON serialization of `Vec<ResolvedRepository>` from [`Engine::list`]:
32
- * each item has `repository` ([`LemmaRepository`]) and `specs` (`LemmaSpecSet[]`),
33
- * each set has `repository`, `name`, and `specs` (`LemmaSpec[]`).
31
+ * each item has `repository` (name or null for workspace) and `specs`
32
+ * (`ListedSpec` rows: name, effective_from, effective_to).
34
33
  */
35
- list(): ResolvedRepositoryJson[];
34
+ list(): ResolvedRepository[];
36
35
 
37
36
  /**
38
- * Formatted Lemma source for a loaded repository (from in-engine AST). Use `"lemma"` for embedded units stdlib.
37
+ * Spec interface and temporal window at `effective`. Lemma text is {@link Engine.source}.
39
38
  */
40
- format_repository(repository: string): string;
39
+ show(
40
+ repository: string | null | undefined,
41
+ spec: string,
42
+ effective?: string | null,
43
+ ): Show;
41
44
 
42
45
  /**
43
- * `repository`: qualifier or `null`/omit for workspace — same as `Engine::schema` `repo`.
46
+ * Formatted canonical Lemma source. Omit `spec` for whole-repository text.
44
47
  */
45
- schema(
48
+ source(
46
49
  repository: string | null | undefined,
47
- spec: string,
50
+ spec?: string | null,
48
51
  effective?: string | null,
49
- ): SpecSchema;
52
+ ): string;
50
53
 
51
54
  /**
52
- * `repository`: qualifier or `null`/omit for workspace — same as `Engine::run` `repo`.
53
- *
54
- * `data_values`: pass integers as numbers, decimals as strings (e.g.
55
- * `{ quantity: 42, rate: "0.075" }`). Non-integer numbers are rejected
56
- * to prevent silent precision loss from IEEE 754 doubles.
55
+ * Remove a temporal spec slice. `effective`: ISO datetime or omit for now.
57
56
  */
58
- run(
57
+ remove(
59
58
  repository: string | null | undefined,
60
59
  spec: string,
61
- rule_names: string[] | string,
62
- data_values: Record<string, unknown>,
63
60
  effective?: string | null,
64
- explain?: boolean,
65
- ): EvaluationResponse;
61
+ ): void;
62
+
63
+ /** Resource limits configured for this engine. */
64
+ limits(): ResourceLimits;
65
+
66
+ /**
67
+ * Canonical formatting of Lemma source. Throws `EngineError` on parse error.
68
+ * `attribute` is an optional path label used in error messages.
69
+ */
70
+ format(code: string, attribute?: string | null): string;
71
+
72
+ /**
73
+ * Evaluate a spec. Pass integers as numbers, decimals as strings in `data`.
74
+ */
75
+ run(options: RunOptions): Response;
66
76
  }
67
77
  }
68
78
 
79
+ /** Options for {@link Engine.run}. */
80
+ export interface RunOptions {
81
+ /** Spec name (required). */
82
+ spec: string;
83
+ /** Repository qualifier (e.g. `@org/repo`), or omit for workspace. */
84
+ repository?: string | null;
85
+ /** ISO datetime for temporal resolution, or omit for now. */
86
+ effective?: string | null;
87
+ /** Input data values. Pass integers as numbers, decimals as strings. */
88
+ data?: Record<string, unknown>;
89
+ /** Rule names to evaluate, or omit for all rules. */
90
+ rules?: string[] | string | null;
91
+ /** Include explanation tree in response. */
92
+ explain?: boolean;
93
+ }
94
+
69
95
  /**
70
96
  * Source location attached to an {@link EngineError}. Line and column are
71
97
  * 1-based; `length` is the UTF-8 byte length of the offending span.
@@ -78,8 +104,8 @@ export interface EngineErrorSource {
78
104
  }
79
105
 
80
106
  /**
81
- * Structured error thrown by {@link Engine.run}, {@link Engine.schema},
82
- * {@link Engine.format}, {@link Engine.load}, and {@link Engine.load_batch}
107
+ * Structured error thrown by {@link Engine.run}, {@link Engine.show},
108
+ * {@link Engine.load}, and {@link Engine.fetch}
83
109
  * (as an array), and rejected from {@link Engine.fetch} (as an array).
84
110
  *
85
111
  * - `kind` classifies the failure ("parsing" for syntax, "validation" for
@@ -109,68 +135,129 @@ export interface EngineError {
109
135
  suggestion: string | null;
110
136
  /** Present for `missing_repository` and `registry` errors (`@…` id). */
111
137
  repository: string | null;
138
+ /** Present only for `kind: "registry"`. */
139
+ registry_kind:
140
+ | "not_found"
141
+ | "unauthorized"
142
+ | "network_error"
143
+ | "server_error"
144
+ | "other"
145
+ | null;
146
+ /** Present only for `kind: "request"`. */
147
+ request_kind: "spec_not_found" | "rule_not_found" | "invalid_request" | null;
148
+ /** Present only for `kind: "resource_limit"`. */
149
+ limit_name: string | null;
150
+ limit_value: string | null;
151
+ actual_value: string | null;
112
152
  }
113
153
 
114
154
  // ---------------------------------------------------------------------------
115
- // Schema envelope (return shape of Engine.schema)
155
+ // Show envelope (return shape of Engine.show)
116
156
  // ---------------------------------------------------------------------------
117
157
 
118
- /** Literal value produced by `JSON.stringify` on a Lemma `LiteralValue`. */
119
- export type LiteralValue = unknown;
158
+ /**
159
+ * API value fields shared by `RuleResult` (flattened into its top-level fields),
160
+ * `ShowData.prefilled`, `ShowData.suggestion`, and range endpoints.
161
+ * A `None` field is absent (not `null`) per Rust `skip_serializing_if`.
162
+ * When present: always `display`, plus exactly one typed field.
163
+ */
164
+ export interface RuleResultValueEndpoint {
165
+ /** Engine-rendered string (`LiteralValue::display_value`). */
166
+ display?: string;
167
+ /** All declared measure units, keyed by unit name. */
168
+ measure?: Record<string, string>;
169
+ /** All declared ratio units, keyed by unit name. */
170
+ ratio?: Record<string, string>;
171
+ number?: string;
172
+ boolean?: boolean;
173
+ text?: string;
174
+ date?: string;
175
+ time?: string;
176
+ calendar?: { value: string; unit: string };
177
+ }
178
+
179
+ /**
180
+ * API value shared by `RuleResult` (flattened into its top-level fields),
181
+ * `ShowData.prefilled`, and `ShowData.suggestion`. When present: always `display`,
182
+ * plus exactly one typed field for a non-range value; `range` is set instead for a
183
+ * range value. A range endpoint (`range.from`/`range.to`) never itself carries a
184
+ * `range` field.
185
+ */
186
+ export interface RuleResultValue extends RuleResultValueEndpoint {
187
+ range?: { from: RuleResultValueEndpoint; to: RuleResultValueEndpoint };
188
+ }
189
+
190
+ /** Where a custom type's extension chain is rooted: local to this spec, or imported. */
191
+ export type TypeDefiningSpec = { kind: "local" } | { kind: "import" };
120
192
 
121
- /** Extension classification serialized on every {@link LemmaType}. */
122
193
  export type TypeExtends =
123
- | "primitive"
194
+ | { kind: "primitive" }
124
195
  | {
196
+ kind: "custom";
125
197
  parent: string;
126
198
  family: string;
127
- defining_spec: unknown;
199
+ defining_spec: TypeDefiningSpec;
128
200
  };
129
201
 
130
- export interface UnitDef {
202
+ /** A unit-scoped bound (Measure/DateRange/TimeRange/MeasureRange minimum/maximum/lower/upper). */
203
+ export interface NamedBound {
204
+ value: string;
205
+ unit: string;
206
+ }
207
+
208
+ export interface MeasureUnit {
131
209
  name: string;
132
210
  factor: { numer: string; denom: string };
133
- minimum?: string | null;
134
- maximum?: string | null;
135
- default?: string | null;
211
+ /** (measure_ref, exponent) pairs from a compound unit declaration (e.g. meter/second). */
212
+ derived_measure_factors: [string, number][];
213
+ decomposition: Record<string, number>;
214
+ minimum?: string;
215
+ maximum?: string;
216
+ suggestion?: string;
136
217
  }
137
218
 
138
- export interface RatioUnitDef {
219
+ export interface RatioUnit {
139
220
  name: string;
140
221
  value: { numer: string; denom: string };
141
- minimum?: string | null;
142
- maximum?: string | null;
143
- default?: string | null;
222
+ minimum?: string;
223
+ maximum?: string;
224
+ suggestion?: string;
144
225
  }
145
226
 
146
- /** Discriminated union over the 10 Lemma type kinds. Field `kind` is the
147
- * serde tag; kind-specific fields sit at the top level next to `kind`,
148
- * `name`, and `extends`. */
227
+ /**
228
+ * Discriminated union over the 12 Lemma type kinds reachable at the API boundary.
229
+ * Field `kind` is the serde tag; kind-specific fields sit at the top level next to
230
+ * `kind`, `name`, and `extends`. The `veto`/`undetermined` `TypeSpecification`
231
+ * variants are internal sentinels that never reach a successfully planned API
232
+ * response and are intentionally excluded.
233
+ */
149
234
  export type LemmaType =
150
235
  & { name: string | null; extends: TypeExtends }
151
236
  & (
152
237
  | { kind: "boolean"; help: string }
153
238
  | {
154
239
  kind: "measure";
155
- minimum: string | null;
156
- maximum: string | null;
240
+ minimum: NamedBound | null;
241
+ maximum: NamedBound | null;
157
242
  decimals: number | null;
158
- units: UnitDef[];
243
+ units: MeasureUnit[];
244
+ traits: ("duration" | "calendar")[];
245
+ decomposition: Record<string, number> | null;
159
246
  help: string;
160
247
  }
161
248
  | {
162
- kind: "measure range";
249
+ kind: "number";
163
250
  minimum: string | null;
164
251
  maximum: string | null;
165
252
  decimals: number | null;
166
- units: UnitDef[];
167
253
  help: string;
168
254
  }
169
255
  | {
170
- kind: "number";
256
+ kind: "numberrange";
257
+ lower: string | null;
258
+ upper: string | null;
171
259
  minimum: string | null;
172
260
  maximum: string | null;
173
- decimals: number | null;
174
261
  help: string;
175
262
  }
176
263
  | {
@@ -178,139 +265,209 @@ export type LemmaType =
178
265
  minimum: string | null;
179
266
  maximum: string | null;
180
267
  decimals: number | null;
181
- units: RatioUnitDef[];
268
+ units: RatioUnit[];
182
269
  help: string;
183
270
  }
184
271
  | {
185
- kind: "ratio range";
272
+ kind: "ratiorange";
273
+ lower: string | null;
274
+ upper: string | null;
186
275
  minimum: string | null;
187
276
  maximum: string | null;
188
- decimals: number | null;
189
- units: RatioUnitDef[];
277
+ units: RatioUnit[];
190
278
  help: string;
191
279
  }
192
280
  | {
193
281
  kind: "text";
194
- minimum: number | null;
195
- maximum: number | null;
196
282
  length: number | null;
197
283
  options: string[];
198
284
  help: string;
199
285
  }
200
286
  | { kind: "date"; minimum: string | null; maximum: string | null; help: string }
287
+ | {
288
+ kind: "daterange";
289
+ lower: string | null;
290
+ upper: string | null;
291
+ minimum: NamedBound | null;
292
+ maximum: NamedBound | null;
293
+ help: string;
294
+ }
201
295
  | { kind: "time"; minimum: string | null; maximum: string | null; help: string }
202
- | { kind: "veto"; message: string | null }
296
+ | {
297
+ kind: "timerange";
298
+ lower: string | null;
299
+ upper: string | null;
300
+ minimum: NamedBound | null;
301
+ maximum: NamedBound | null;
302
+ help: string;
303
+ }
304
+ | {
305
+ kind: "measurerange";
306
+ lower: NamedBound | null;
307
+ upper: NamedBound | null;
308
+ minimum: NamedBound | null;
309
+ maximum: NamedBound | null;
310
+ units: MeasureUnit[];
311
+ decomposition: Record<string, number> | null;
312
+ help: string;
313
+ }
203
314
  );
204
315
 
205
316
  /** One input declared in a spec. Omitted fields are absent (not `null`). */
206
- export interface DataEntry {
317
+ export interface ShowData {
207
318
  type: LemmaType;
208
319
  /** Spec literal or literal `with` binding; UIs may skip review. */
209
- prefilled?: LiteralValue;
210
- /** Caller overlay when schema was built with supplied values. */
211
- supplied?: LiteralValue;
212
- /** `-> default ...` suggestion; prompt with prefill in interactive UIs. */
213
- default?: LiteralValue;
320
+ prefilled?: RuleResultValue;
321
+ /** `-> suggest ...` suggestion; prompt with prefill in interactive UIs. */
322
+ suggestion?: RuleResultValue;
323
+ /** Local rule names that transitively need this data (planning time). */
324
+ needed_by_rules: string[];
214
325
  }
215
326
 
216
327
  /** Return shape of {@link Engine.run}. */
217
- export interface EvaluationResponse {
328
+ export interface Response {
218
329
  spec: string;
219
330
  effective: string;
331
+ /** Declared temporal window of the resolved spec version actually evaluated. */
332
+ spec_effective_from?: string;
333
+ spec_effective_to?: string;
220
334
  results: Record<string, RuleResult>;
221
- data: EvaluationDataEntry[];
222
335
  }
223
336
 
224
- export interface RuleResult {
337
+ /**
338
+ * A rule's result. Fields of `RuleResultValue` are flattened directly onto this
339
+ * object (the Rust side uses `#[serde(flatten)]`), so a measure result's map appears
340
+ * at `result.measure`, not nested under a `value` key.
341
+ */
342
+ export type RuleResult = RuleResultValue & {
225
343
  vetoed: boolean;
226
- display?: string | null;
227
- veto_reason?: string | null;
344
+ veto_reason?: string;
228
345
  rule_type: string;
229
- measure?: Record<string, string> | null;
230
- ratio?: Record<string, string> | null;
231
- number?: string | null;
232
- boolean?: boolean | null;
233
- text?: string | null;
234
- date?: unknown | null;
235
- time?: unknown | null;
236
- calendar?: { value: string; unit: string } | null;
237
- range?: { from: RuleResultPayload; to: RuleResultPayload } | null;
238
- explanation?: unknown | null;
346
+ /** Input keys still unbound for this rule (run-data-aware; same keys as Show.data). */
347
+ missing_data?: string[];
348
+ /** Present when `run(..., explain: true)`. Shape: documentation/schemas/api.v1.json (`RuleResult.explanation`). */
349
+ explanation?: Explanation;
350
+ };
351
+
352
+ /** One evaluated unless condition, stated as a fact. */
353
+ export interface Cause {
354
+ condition: string;
355
+ value: string;
356
+ children?: ExplanationNode[];
357
+ }
358
+
359
+ export interface ConversionStep {
360
+ role: "outcome" | "rule" | "source";
361
+ text: string;
239
362
  }
240
363
 
241
- export interface RuleResultPayload {
242
- measure?: Record<string, string> | null;
243
- ratio?: Record<string, string> | null;
244
- number?: string | null;
245
- boolean?: boolean | null;
246
- text?: string | null;
247
- date?: unknown | null;
248
- time?: unknown | null;
249
- calendar?: { value: string; unit: string } | null;
364
+ /** Nested explanation tree node (tagged by `type`). */
365
+ export type ExplanationNode =
366
+ | {
367
+ type: "rule";
368
+ name: string;
369
+ result: string;
370
+ body: string;
371
+ causes?: Cause[];
372
+ children?: ExplanationNode[];
373
+ }
374
+ | {
375
+ type: "compose";
376
+ expression: string;
377
+ operands: ExplanationNode[];
378
+ }
379
+ | {
380
+ type: "data";
381
+ name: string;
382
+ display: string;
383
+ }
384
+ | {
385
+ type: "data_unused";
386
+ name: string;
387
+ }
388
+ | {
389
+ type: "conversion";
390
+ expression: string;
391
+ steps: ConversionStep[];
392
+ operands: ExplanationNode[];
393
+ }
394
+ | {
395
+ type: "veto";
396
+ message?: string;
397
+ };
398
+
399
+ /** Root and nested rule explanation (same shape). */
400
+ export interface Explanation {
401
+ type: "rule";
402
+ name: string;
403
+ result: string;
404
+ body: string;
405
+ causes?: Cause[];
406
+ children?: ExplanationNode[];
250
407
  }
251
408
 
252
- export interface EvaluationDataEntry {
253
- path: string;
254
- value: unknown;
409
+ /** Half-open `[effective_from, effective_to)` for one loaded temporal row. */
410
+ export interface ShowVersion {
411
+ effective_from?: string;
412
+ effective_to?: string;
255
413
  }
256
414
 
257
- /** Return shape of {@link Engine.schema}. */
258
- export interface SpecSchema {
415
+ /** Provenance of a loaded source. Externally tagged; the unit `Volatile` variant
416
+ * is the bare string `"volatile"`. */
417
+ export type SourceType = "volatile" | { path: string } | { dependency: string };
418
+
419
+ /** Parsed literal value (meta field value). Externally tagged. */
420
+ export type LiteralValue =
421
+ | { number: string }
422
+ | { number_with_unit: [string, string] }
423
+ | { text: string }
424
+ | { date: string }
425
+ | { time: string }
426
+ | { boolean: "true" | "false" | "yes" | "no" }
427
+ | { range: [LiteralValue, LiteralValue] };
428
+
429
+ /** Spec `meta` field value. Externally tagged. */
430
+ export type MetaValue = { literal: LiteralValue } | { unquoted: string };
431
+
432
+ /** Return shape of {@link Engine.show}. */
433
+ export interface Show {
259
434
  spec: string;
260
- data: Record<string, DataEntry>;
435
+ commentary?: string;
436
+ effective_from?: string;
437
+ effective_to?: string;
438
+ start_line: number;
439
+ source_type?: SourceType;
440
+ versions?: ShowVersion[];
441
+ data: Record<string, ShowData>;
261
442
  /** Rule result types; measure and ratio entries expose `units[]` like their data counterparts. */
262
443
  rules: Record<string, LemmaType>;
263
- meta: Record<string, unknown>;
264
- }
265
-
266
- /** JSON mirror of Rust `ResolvedRepository` (engine `list`). */
267
- export interface ResolvedRepositoryJson {
268
- repository: LemmaRepositoryJson;
269
- /** [`LemmaSpecSet`] list for this resolved repository. */
270
- specs: LemmaSpecSetJson[];
444
+ meta: Record<string, MetaValue>;
271
445
  }
272
446
 
273
- /** JSON mirror of Rust `LemmaSpecSet` as serialized by the engine. */
274
- export interface LemmaSpecSetJson {
275
- repository: LemmaRepositoryJson;
447
+ /** Slim listed spec row (engine `list`). */
448
+ export interface ListedSpec {
276
449
  name: string;
277
- /** Temporal versions, ascending `effective_from` (same order as `iter_specs`). */
278
- specs: LemmaSpecJson[];
279
- }
280
-
281
- /** JSON mirror of Rust `LemmaRepository`. */
282
- export interface LemmaRepositoryJson {
283
- name: string | null;
284
- dependency: string | null;
285
- start_line: number;
286
- source_type: unknown;
450
+ effective_from?: string;
451
+ effective_to?: string;
287
452
  }
288
453
 
289
- /** JSON mirror of Rust `EffectiveDate` (externally tagged). */
290
- export type EffectiveDateJson =
291
- | { Origin: null }
292
- | { DateTimeValue: DateTimeValueJson };
293
-
294
- /** JSON mirror of Rust `DateTimeValue`. */
295
- export interface DateTimeValueJson {
296
- year: number;
297
- month: number;
298
- day: number;
299
- hour: number;
300
- minute: number;
301
- second: number;
302
- microsecond: number;
303
- timezone: unknown;
454
+ /** Rust `ResolvedRepository` (engine `list`). */
455
+ export interface ResolvedRepository {
456
+ /** Absent for the local workspace group (only real repositories carry a name). */
457
+ repository?: string;
458
+ specs: ListedSpec[];
304
459
  }
305
460
 
306
- /** JSON mirror of Rust `LemmaSpec` (full AST; deep nodes are engine-shaped). */
307
- export interface LemmaSpecJson {
308
- name: string;
309
- effective_from: EffectiveDateJson;
310
- source_type: unknown;
311
- start_line: number;
312
- commentary: string | null;
313
- data: unknown[];
314
- rules: unknown[];
315
- meta_fields: unknown[];
461
+ /** Rust `ResourceLimits`. */
462
+ export interface ResourceLimits {
463
+ max_source_size_bytes: number;
464
+ max_expression_depth: number;
465
+ max_expression_count: number;
466
+ max_data_value_bytes: number;
467
+ max_loaded_bytes: number;
468
+ max_sources: number;
469
+ max_normalized_expression_nodes: number;
470
+ max_spec_dependency_depth: number;
471
+ max_dag_specs: number;
472
+ max_normal_form_depth: number;
316
473
  }