@benrogmans/lemma-engine 0.5.2 → 0.6.2
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 +279 -33
- package/lemma_bg.wasm +0 -0
- package/package.json +6 -8
package/README.md
CHANGED
|
@@ -1,55 +1,301 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @benrogmans/lemma-engine
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Lemma is a declarative programming language for expressing business rules. This WebAssembly build for JavaScript and TypeScript provides an embeddable runtime, so that Lemma code can be evaluated anywhere.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**New to Lemma?** Check out the [language introduction](https://github.com/benrogmans/lemma#quick-start) and [examples](https://github.com/benrogmans/lemma/tree/main/docs/examples).
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Installation
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
```bash
|
|
10
|
+
npm install @benrogmans/lemma-engine
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## JavaScript API Reference
|
|
14
|
+
|
|
15
|
+
### Initialization
|
|
16
|
+
|
|
17
|
+
#### Browser
|
|
18
|
+
```javascript
|
|
19
|
+
import init, { WasmEngine } from '@benrogmans/lemma-engine';
|
|
20
|
+
|
|
21
|
+
// Async initialization (recommended for browsers)
|
|
22
|
+
await init();
|
|
23
|
+
const engine = new WasmEngine();
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
#### Node.js
|
|
27
|
+
```javascript
|
|
28
|
+
import { readFileSync } from 'fs';
|
|
29
|
+
import { WasmEngine, initSync } from '@benrogmans/lemma-engine';
|
|
30
|
+
|
|
31
|
+
// Synchronous initialization for Node.js
|
|
32
|
+
const wasmBytes = readFileSync('node_modules/@benrogmans/lemma-engine/lemma_bg.wasm');
|
|
33
|
+
initSync(wasmBytes);
|
|
34
|
+
const engine = new WasmEngine();
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
#### Bundlers (Webpack, Vite, etc.)
|
|
38
|
+
```javascript
|
|
39
|
+
import init, { WasmEngine } from '@benrogmans/lemma-engine';
|
|
40
|
+
import wasmUrl from '@benrogmans/lemma-engine/lemma_bg.wasm?url';
|
|
41
|
+
|
|
42
|
+
// Initialize with URL
|
|
43
|
+
await init(wasmUrl);
|
|
44
|
+
const engine = new WasmEngine();
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Core Methods
|
|
15
48
|
|
|
16
|
-
|
|
49
|
+
#### `addLemmaCode(code: string, filename: string): string`
|
|
17
50
|
|
|
18
|
-
|
|
19
|
-
use lemma::{Engine, LemmaResult};
|
|
51
|
+
Adds a Lemma document to the engine.
|
|
20
52
|
|
|
21
|
-
|
|
22
|
-
|
|
53
|
+
```javascript
|
|
54
|
+
const result = engine.addLemmaCode(`
|
|
55
|
+
doc employee_contract
|
|
23
56
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
fact quantity = 5
|
|
28
|
-
rule total = base_price * quantity
|
|
29
|
-
"#, "pricing.lemma")?;
|
|
57
|
+
fact salary = 5000 eur
|
|
58
|
+
fact start_date = 2024-01-15
|
|
59
|
+
fact vacation_days = 25
|
|
30
60
|
|
|
31
|
-
|
|
32
|
-
|
|
61
|
+
rule annual_salary = salary * 12
|
|
62
|
+
rule daily_rate = salary / 21
|
|
63
|
+
`, 'employee.lemma');
|
|
33
64
|
|
|
34
|
-
|
|
65
|
+
const response = JSON.parse(result);
|
|
66
|
+
if (response.success) {
|
|
67
|
+
console.log('Document loaded:', response.data);
|
|
68
|
+
} else {
|
|
69
|
+
console.error('Error:', response.error);
|
|
35
70
|
}
|
|
36
71
|
```
|
|
37
72
|
|
|
38
|
-
|
|
73
|
+
#### `evaluate(docName: string, facts: string): string`
|
|
39
74
|
|
|
40
|
-
|
|
41
|
-
- **Language guide**: [Lemma language documentation](https://github.com/benrogmans/lemma/tree/main/docs)
|
|
42
|
-
- **Examples**: [Complete examples](https://github.com/benrogmans/lemma/tree/main/docs/examples)
|
|
75
|
+
Evaluates a document with optional runtime facts.
|
|
43
76
|
|
|
44
|
-
|
|
77
|
+
```javascript
|
|
78
|
+
// Evaluate with default facts
|
|
79
|
+
const result1 = engine.evaluate('employee_contract', '{}');
|
|
45
80
|
|
|
46
|
-
|
|
81
|
+
// Evaluate with runtime fact overrides (as JSON object)
|
|
82
|
+
const result2 = engine.evaluate('employee_contract', JSON.stringify({
|
|
83
|
+
salary: 6000,
|
|
84
|
+
vacation_days: 30
|
|
85
|
+
}));
|
|
47
86
|
|
|
48
|
-
|
|
49
|
-
|
|
87
|
+
const response = JSON.parse(result2);
|
|
88
|
+
if (response.success) {
|
|
89
|
+
console.log('Document:', response.data.document);
|
|
90
|
+
console.log('Rules:', response.data.rules);
|
|
91
|
+
// Access specific rule results directly:
|
|
92
|
+
// response.data.rules.annual_salary.value
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
#### `listDocuments(): string`
|
|
97
|
+
|
|
98
|
+
Lists all loaded documents in the engine.
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
const result = engine.listDocuments();
|
|
102
|
+
const response = JSON.parse(result);
|
|
103
|
+
|
|
104
|
+
if (response.success) {
|
|
105
|
+
console.log('Loaded documents:', response.data);
|
|
106
|
+
// response.data is an array of document names
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Response Format
|
|
111
|
+
|
|
112
|
+
All methods return a JSON string with this structure:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
interface WasmResponse {
|
|
116
|
+
success: boolean;
|
|
117
|
+
data?: any; // Success data (varies by method)
|
|
118
|
+
error?: string; // Error message if success is false
|
|
119
|
+
warnings?: string[]; // Optional warnings
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Complete Example
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
import init, { WasmEngine } from '@benrogmans/lemma-engine';
|
|
127
|
+
|
|
128
|
+
async function calculatePricing() {
|
|
129
|
+
// Initialize WASM
|
|
130
|
+
await init();
|
|
131
|
+
const engine = new WasmEngine();
|
|
132
|
+
|
|
133
|
+
// Define pricing rules
|
|
134
|
+
const pricingDoc = `
|
|
135
|
+
doc product_pricing
|
|
136
|
+
|
|
137
|
+
fact base_price = 100 usd
|
|
138
|
+
fact quantity = 1
|
|
139
|
+
fact is_member = false
|
|
140
|
+
fact promo_code = ""
|
|
141
|
+
|
|
142
|
+
rule bulk_discount = 0%
|
|
143
|
+
unless quantity >= 10 then 5%
|
|
144
|
+
unless quantity >= 50 then 10%
|
|
145
|
+
unless quantity >= 100 then 15%
|
|
146
|
+
|
|
147
|
+
rule member_discount = 0%
|
|
148
|
+
unless is_member then 10%
|
|
149
|
+
|
|
150
|
+
rule promo_discount = 0%
|
|
151
|
+
unless promo_code == "SAVE10" then 10%
|
|
152
|
+
unless promo_code == "SAVE20" then 20%
|
|
153
|
+
|
|
154
|
+
rule best_discount = bulk_discount?
|
|
155
|
+
unless member_discount? >= bulk_discount? then member_discount?
|
|
156
|
+
unless promo_discount? >= bulk_discount? then promo_discount?
|
|
157
|
+
unless member_discount? >= promo_discount? then member_discount?
|
|
158
|
+
unless promo_discount? >= member_discount? then promo_discount?
|
|
159
|
+
|
|
160
|
+
rule final_price = base_price * quantity * (1 - best_discount?)
|
|
161
|
+
`;
|
|
162
|
+
|
|
163
|
+
// Load the document
|
|
164
|
+
const loadResult = JSON.parse(
|
|
165
|
+
engine.addLemmaCode(pricingDoc, 'pricing.lemma')
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
if (!loadResult.success) {
|
|
169
|
+
throw new Error(loadResult.error);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Calculate for different scenarios
|
|
173
|
+
const scenarios = [
|
|
174
|
+
{ quantity: 25, is_member: false, promo_code: "" },
|
|
175
|
+
{ quantity: 10, is_member: true, promo_code: "" },
|
|
176
|
+
{ quantity: 5, is_member: false, promo_code: "SAVE20" }
|
|
177
|
+
];
|
|
178
|
+
|
|
179
|
+
for (const scenario of scenarios) {
|
|
180
|
+
const result = JSON.parse(
|
|
181
|
+
engine.evaluate('product_pricing', JSON.stringify(scenario))
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
if (result.success) {
|
|
185
|
+
console.log(`Scenario:`, scenario);
|
|
186
|
+
// Access rule results directly by name
|
|
187
|
+
const finalPrice = result.data.rules.final_price.value;
|
|
188
|
+
const bestDiscount = result.data.rules.best_discount.value;
|
|
189
|
+
console.log(`Final price:`, finalPrice);
|
|
190
|
+
console.log(`Discount applied:`, bestDiscount);
|
|
191
|
+
console.log('---');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
calculatePricing().catch(console.error);
|
|
50
197
|
```
|
|
51
198
|
|
|
199
|
+
### TypeScript Support
|
|
200
|
+
|
|
201
|
+
The package includes TypeScript definitions. For better type safety:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
import init, { WasmEngine } from '@benrogmans/lemma-engine';
|
|
205
|
+
|
|
206
|
+
interface PricingFacts {
|
|
207
|
+
quantity: number;
|
|
208
|
+
is_member: boolean;
|
|
209
|
+
promo_code: string;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
interface PricingResults {
|
|
213
|
+
base_price: string;
|
|
214
|
+
final_price: string;
|
|
215
|
+
best_discount: string;
|
|
216
|
+
// ... other fields
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
interface EvaluationResponse {
|
|
220
|
+
success: boolean;
|
|
221
|
+
data?: {
|
|
222
|
+
document: string;
|
|
223
|
+
rules: {
|
|
224
|
+
[ruleName: string]: {
|
|
225
|
+
value: any; // The computed value (e.g., {Number: "100"}, {Unit: "50 EUR"})
|
|
226
|
+
veto?: string; // Present if rule was vetoed
|
|
227
|
+
missing_facts?: string[]; // Present if rule couldn't be evaluated
|
|
228
|
+
operations?: Array<{ // Operation records (always present if rule was evaluated)
|
|
229
|
+
type: string; // "fact_used", "operation_executed", "final_result", etc.
|
|
230
|
+
// Additional fields depend on type
|
|
231
|
+
}>;
|
|
232
|
+
};
|
|
233
|
+
};
|
|
234
|
+
};
|
|
235
|
+
error?: string;
|
|
236
|
+
warnings?: string[]; // Document-level warnings
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function typedExample() {
|
|
240
|
+
await init();
|
|
241
|
+
const engine = new WasmEngine();
|
|
242
|
+
|
|
243
|
+
// ... load document
|
|
244
|
+
|
|
245
|
+
const facts = {
|
|
246
|
+
quantity: 10,
|
|
247
|
+
is_member: true,
|
|
248
|
+
promo_code: "SAVE10"
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const result: EvaluationResponse = JSON.parse(
|
|
252
|
+
engine.evaluate('product_pricing', JSON.stringify(facts))
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
if (result.success && result.data) {
|
|
256
|
+
const price = result.data.rules.final_price?.value;
|
|
257
|
+
// price might be {Unit: "100 USD"} or {Number: "100"}
|
|
258
|
+
// depending on the rule's result type
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Error Handling
|
|
264
|
+
|
|
265
|
+
```javascript
|
|
266
|
+
try {
|
|
267
|
+
const result = JSON.parse(
|
|
268
|
+
engine.addLemmaCode('invalid syntax', 'bad.lemma')
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
if (!result.success) {
|
|
272
|
+
console.error('Lemma error:', result.error);
|
|
273
|
+
// Handle parse/semantic errors
|
|
274
|
+
}
|
|
275
|
+
} catch (e) {
|
|
276
|
+
// Handle JSON parse errors or WASM exceptions
|
|
277
|
+
console.error('System error:', e);
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Performance Tips
|
|
282
|
+
|
|
283
|
+
1. **Initialize once**: The WASM module should be initialized once per application
|
|
284
|
+
2. **Reuse engines**: Create one `WasmEngine` and load multiple documents
|
|
285
|
+
3. **Batch operations**: Load all documents before evaluating
|
|
286
|
+
4. **Cache results**: Evaluation results can be cached if facts don't change
|
|
287
|
+
|
|
288
|
+
### Compatibility
|
|
289
|
+
|
|
290
|
+
Works in modern browsers with WebAssembly support and Node.js with ES module support.
|
|
291
|
+
|
|
52
292
|
## License
|
|
53
293
|
|
|
54
|
-
Apache
|
|
294
|
+
Apache-2.0
|
|
295
|
+
|
|
296
|
+
## Links
|
|
55
297
|
|
|
298
|
+
- [GitHub Repository](https://github.com/benrogmans/lemma)
|
|
299
|
+
- [Lemma Language Guide](https://github.com/benrogmans/lemma/tree/main/docs)
|
|
300
|
+
- [Examples](https://github.com/benrogmans/lemma/tree/main/docs/examples)
|
|
301
|
+
- [Report Issues](https://github.com/benrogmans/lemma/issues)
|
package/lemma_bg.wasm
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@benrogmans/lemma-engine",
|
|
3
|
+
"version": "0.6.2",
|
|
4
|
+
"description": "The programming language that means business.",
|
|
3
5
|
"type": "module",
|
|
4
|
-
"version": "0.5.2",
|
|
5
|
-
"description": "Lemma: A declarative programming language for business rules. Run in browsers with WebAssembly.",
|
|
6
6
|
"main": "lemma.js",
|
|
7
7
|
"types": "lemma.d.ts",
|
|
8
8
|
"files": [
|
|
@@ -13,13 +13,15 @@
|
|
|
13
13
|
"lemma_bg.wasm.d.ts"
|
|
14
14
|
],
|
|
15
15
|
"keywords": [
|
|
16
|
-
"business-rules",
|
|
17
16
|
"logic",
|
|
17
|
+
"rules",
|
|
18
18
|
"declarative",
|
|
19
|
+
"dsl",
|
|
20
|
+
"rust",
|
|
19
21
|
"wasm",
|
|
20
22
|
"webassembly"
|
|
21
23
|
],
|
|
22
|
-
"author": "
|
|
24
|
+
"author": "benrogmans <ben@amrai.nl>",
|
|
23
25
|
"license": "Apache-2.0",
|
|
24
26
|
"repository": {
|
|
25
27
|
"type": "git",
|
|
@@ -28,9 +30,5 @@
|
|
|
28
30
|
"homepage": "https://github.com/benrogmans/lemma",
|
|
29
31
|
"bugs": {
|
|
30
32
|
"url": "https://github.com/benrogmans/lemma/issues"
|
|
31
|
-
},
|
|
32
|
-
"scripts": {
|
|
33
|
-
"test": "node test-wasm.js"
|
|
34
33
|
}
|
|
35
34
|
}
|
|
36
|
-
|