@lemmabase/lemma-engine 0.9.0 → 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
@@ -14,7 +14,7 @@ declare module './lemma.bindings.js' {
14
14
  /**
15
15
  * Load Lemma source(s).
16
16
  * - string → volatile workspace source
17
- * - object or `[label, code][]` → labeled sources in one planning pass
17
+ * - object labeled sources (key insertion order); `[label, code][]` → labeled sources (array order)
18
18
  * Throws `EngineError[]` on failure. `null`/`undefined` rejected.
19
19
  */
20
20
  load(code: string): void;
@@ -31,7 +31,7 @@ declare module './lemma.bindings.js' {
31
31
  * each item has `repository` (name or null for workspace) and `specs`
32
32
  * (`ListedSpec` rows: name, effective_from, effective_to).
33
33
  */
34
- list(): ResolvedRepositoryJson[];
34
+ list(): ResolvedRepository[];
35
35
 
36
36
  /**
37
37
  * Spec interface and temporal window at `effective`. Lemma text is {@link Engine.source}.
@@ -61,7 +61,7 @@ declare module './lemma.bindings.js' {
61
61
  ): void;
62
62
 
63
63
  /** Resource limits configured for this engine. */
64
- limits(): ResourceLimitsJson;
64
+ limits(): ResourceLimits;
65
65
 
66
66
  /**
67
67
  * Canonical formatting of Lemma source. Throws `EngineError` on parse error.
@@ -70,19 +70,28 @@ declare module './lemma.bindings.js' {
70
70
  format(code: string, attribute?: string | null): string;
71
71
 
72
72
  /**
73
- * `data_values`: pass integers as numbers, decimals as strings.
73
+ * Evaluate a spec. Pass integers as numbers, decimals as strings in `data`.
74
74
  */
75
- run(
76
- repository: string | null | undefined,
77
- spec: string,
78
- effective: string | null | undefined,
79
- data_values?: Record<string, unknown>,
80
- rule_names?: string[] | string | null,
81
- explain?: boolean,
82
- ): EvaluationResponse;
75
+ run(options: RunOptions): Response;
83
76
  }
84
77
  }
85
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
+
86
95
  /**
87
96
  * Source location attached to an {@link EngineError}. Line and column are
88
97
  * 1-based; `length` is the UTF-8 byte length of the offending span.
@@ -126,75 +135,129 @@ export interface EngineError {
126
135
  suggestion: string | null;
127
136
  /** Present for `missing_repository` and `registry` errors (`@…` id). */
128
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;
129
152
  }
130
153
 
131
154
  // ---------------------------------------------------------------------------
132
155
  // Show envelope (return shape of Engine.show)
133
156
  // ---------------------------------------------------------------------------
134
157
 
135
- /** Literal value on API wire (`suggestion`, `prefilled`, committed `value`). Canonical plan storage omits `measure` / `ratio` maps. */
136
- export interface WireLiteralValue {
137
- value: unknown;
138
- lemma_type: LemmaType;
139
- display_value: string;
140
- /** All declared measure units when the type has unit definitions. */
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. */
141
168
  measure?: Record<string, string>;
142
- /** All declared ratio units when the type has unit definitions. */
169
+ /** All declared ratio units, keyed by unit name. */
143
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 };
144
188
  }
145
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" };
192
+
146
193
  export type TypeExtends =
147
- | "primitive"
194
+ | { kind: "primitive" }
148
195
  | {
196
+ kind: "custom";
149
197
  parent: string;
150
198
  family: string;
151
- defining_spec: unknown;
199
+ defining_spec: TypeDefiningSpec;
152
200
  };
153
201
 
154
- 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 {
155
209
  name: string;
156
210
  factor: { numer: string; denom: string };
157
- minimum?: string | null;
158
- maximum?: string | null;
159
- suggestion?: 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;
160
217
  }
161
218
 
162
- export interface RatioUnitDef {
219
+ export interface RatioUnit {
163
220
  name: string;
164
221
  value: { numer: string; denom: string };
165
- minimum?: string | null;
166
- maximum?: string | null;
167
- suggestion?: string | null;
222
+ minimum?: string;
223
+ maximum?: string;
224
+ suggestion?: string;
168
225
  }
169
226
 
170
- /** Discriminated union over the 10 Lemma type kinds. Field `kind` is the
171
- * serde tag; kind-specific fields sit at the top level next to `kind`,
172
- * `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
+ */
173
234
  export type LemmaType =
174
235
  & { name: string | null; extends: TypeExtends }
175
236
  & (
176
237
  | { kind: "boolean"; help: string }
177
238
  | {
178
239
  kind: "measure";
179
- minimum: string | null;
180
- maximum: string | null;
240
+ minimum: NamedBound | null;
241
+ maximum: NamedBound | null;
181
242
  decimals: number | null;
182
- units: UnitDef[];
243
+ units: MeasureUnit[];
244
+ traits: ("duration" | "calendar")[];
245
+ decomposition: Record<string, number> | null;
183
246
  help: string;
184
247
  }
185
248
  | {
186
- kind: "measure range";
249
+ kind: "number";
187
250
  minimum: string | null;
188
251
  maximum: string | null;
189
252
  decimals: number | null;
190
- units: UnitDef[];
191
253
  help: string;
192
254
  }
193
255
  | {
194
- kind: "number";
256
+ kind: "numberrange";
257
+ lower: string | null;
258
+ upper: string | null;
195
259
  minimum: string | null;
196
260
  maximum: string | null;
197
- decimals: number | null;
198
261
  help: string;
199
262
  }
200
263
  | {
@@ -202,76 +265,98 @@ export type LemmaType =
202
265
  minimum: string | null;
203
266
  maximum: string | null;
204
267
  decimals: number | null;
205
- units: RatioUnitDef[];
268
+ units: RatioUnit[];
206
269
  help: string;
207
270
  }
208
271
  | {
209
- kind: "ratio range";
272
+ kind: "ratiorange";
273
+ lower: string | null;
274
+ upper: string | null;
210
275
  minimum: string | null;
211
276
  maximum: string | null;
212
- decimals: number | null;
213
- units: RatioUnitDef[];
277
+ units: RatioUnit[];
214
278
  help: string;
215
279
  }
216
280
  | {
217
281
  kind: "text";
218
- minimum: number | null;
219
- maximum: number | null;
220
282
  length: number | null;
221
283
  options: string[];
222
284
  help: string;
223
285
  }
224
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
+ }
225
295
  | { kind: "time"; minimum: string | null; maximum: string | null; help: string }
226
- | { 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
+ }
227
314
  );
228
315
 
229
316
  /** One input declared in a spec. Omitted fields are absent (not `null`). */
230
- export interface DataEntry {
317
+ export interface ShowData {
231
318
  type: LemmaType;
232
319
  /** Spec literal or literal `with` binding; UIs may skip review. */
233
- prefilled?: WireLiteralValue;
320
+ prefilled?: RuleResultValue;
234
321
  /** `-> suggest ...` suggestion; prompt with prefill in interactive UIs. */
235
- suggestion?: WireLiteralValue;
322
+ suggestion?: RuleResultValue;
236
323
  /** Local rule names that transitively need this data (planning time). */
237
324
  needed_by_rules: string[];
238
325
  }
239
326
 
240
327
  /** Return shape of {@link Engine.run}. */
241
- export interface EvaluationResponse {
328
+ export interface Response {
242
329
  spec: string;
243
330
  effective: string;
331
+ /** Declared temporal window of the resolved spec version actually evaluated. */
332
+ spec_effective_from?: string;
333
+ spec_effective_to?: string;
244
334
  results: Record<string, RuleResult>;
245
335
  }
246
336
 
247
- 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 & {
248
343
  vetoed: boolean;
249
- display?: string | null;
250
- veto_reason?: string | null;
344
+ veto_reason?: string;
251
345
  rule_type: string;
252
- /** Input keys still unbound for this rule (overlay-aware; same keys as Show.data). */
346
+ /** Input keys still unbound for this rule (run-data-aware; same keys as Show.data). */
253
347
  missing_data?: string[];
254
- measure?: Record<string, string> | null;
255
- ratio?: Record<string, string> | null;
256
- number?: string | null;
257
- boolean?: boolean | null;
258
- text?: string | null;
259
- date?: unknown | null;
260
- time?: unknown | null;
261
- calendar?: { value: string; unit: string } | null;
262
- range?: { from: RuleResultPayload; to: RuleResultPayload } | null;
263
- /** Present when `run(..., explain: true)`. Shape: documentation/schemas/explanation.v1.json */
264
- explanation?: Explanation | null;
265
- }
348
+ /** Present when `run(..., explain: true)`. Shape: documentation/schemas/api.v1.json (`RuleResult.explanation`). */
349
+ explanation?: Explanation;
350
+ };
266
351
 
267
352
  /** One evaluated unless condition, stated as a fact. */
268
- export interface ExplanationCause {
353
+ export interface Cause {
269
354
  condition: string;
270
355
  value: string;
271
356
  children?: ExplanationNode[];
272
357
  }
273
358
 
274
- export interface ExplanationConversionStep {
359
+ export interface ConversionStep {
275
360
  role: "outcome" | "rule" | "source";
276
361
  text: string;
277
362
  }
@@ -283,7 +368,7 @@ export type ExplanationNode =
283
368
  name: string;
284
369
  result: string;
285
370
  body: string;
286
- causes?: ExplanationCause[];
371
+ causes?: Cause[];
287
372
  children?: ExplanationNode[];
288
373
  }
289
374
  | {
@@ -303,16 +388,12 @@ export type ExplanationNode =
303
388
  | {
304
389
  type: "conversion";
305
390
  expression: string;
306
- steps: ExplanationConversionStep[];
391
+ steps: ConversionStep[];
307
392
  operands: ExplanationNode[];
308
393
  }
309
394
  | {
310
395
  type: "veto";
311
396
  message?: string;
312
- }
313
- | {
314
- type: "unit_equivalence";
315
- text: string;
316
397
  };
317
398
 
318
399
  /** Root and nested rule explanation (same shape). */
@@ -321,69 +402,64 @@ export interface Explanation {
321
402
  name: string;
322
403
  result: string;
323
404
  body: string;
324
- causes?: ExplanationCause[];
405
+ causes?: Cause[];
325
406
  children?: ExplanationNode[];
326
407
  }
327
408
 
328
- export interface RuleResultPayload {
329
- measure?: Record<string, string> | null;
330
- ratio?: Record<string, string> | null;
331
- number?: string | null;
332
- boolean?: boolean | null;
333
- text?: string | null;
334
- date?: unknown | null;
335
- time?: unknown | null;
336
- calendar?: { value: string; unit: string } | null;
337
- }
338
-
339
409
  /** Half-open `[effective_from, effective_to)` for one loaded temporal row. */
340
410
  export interface ShowVersion {
341
- effective_from?: string | null;
342
- effective_to?: string | null;
411
+ effective_from?: string;
412
+ effective_to?: string;
343
413
  }
344
414
 
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
+
345
432
  /** Return shape of {@link Engine.show}. */
346
433
  export interface Show {
347
434
  spec: string;
348
- commentary?: string | null;
349
- effective_from?: string | null;
350
- effective_to?: string | null;
435
+ commentary?: string;
436
+ effective_from?: string;
437
+ effective_to?: string;
351
438
  start_line: number;
352
- source_type?: string | null;
439
+ source_type?: SourceType;
353
440
  versions?: ShowVersion[];
354
- data: Record<string, DataEntry>;
441
+ data: Record<string, ShowData>;
355
442
  /** Rule result types; measure and ratio entries expose `units[]` like their data counterparts. */
356
443
  rules: Record<string, LemmaType>;
357
- meta: Record<string, unknown>;
444
+ meta: Record<string, MetaValue>;
358
445
  }
359
446
 
360
- /** JSON mirror of slim listed spec row (engine `list`). */
361
- export interface ListedSpecJson {
447
+ /** Slim listed spec row (engine `list`). */
448
+ export interface ListedSpec {
362
449
  name: string;
363
- effective_from?: DateTimeValueJson | null;
364
- effective_to?: DateTimeValueJson | null;
365
- }
366
-
367
- /** JSON mirror of Rust `ResolvedRepository` (engine `list`). */
368
- export interface ResolvedRepositoryJson {
369
- repository: string | null;
370
- specs: ListedSpecJson[];
450
+ effective_from?: string;
451
+ effective_to?: string;
371
452
  }
372
453
 
373
- /** JSON mirror of Rust `DateTimeValue`. */
374
- export interface DateTimeValueJson {
375
- year: number;
376
- month: number;
377
- day: number;
378
- hour: number;
379
- minute: number;
380
- second: number;
381
- microsecond: number;
382
- 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[];
383
459
  }
384
460
 
385
- /** JSON mirror of Rust `ResourceLimits`. */
386
- export interface ResourceLimitsJson {
461
+ /** Rust `ResourceLimits`. */
462
+ export interface ResourceLimits {
387
463
  max_source_size_bytes: number;
388
464
  max_expression_depth: number;
389
465
  max_expression_count: number;