@autochitect/engine 1.0.0 → 1.1.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 +94 -29
- package/engine.wasm +0 -0
- package/index.d.ts +3 -3
- package/index.mjs +20 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @autochitect/engine
|
|
2
2
|
|
|
3
|
-
A 245KB WebAssembly
|
|
3
|
+
A 245KB WebAssembly cost estimation engine. Define cost models in a purpose-built DSL, get instant estimates with full dependency tracing. Runs entirely client-side — no server, no latency, no data leaving the browser.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install
|
|
8
|
+
npm install @autochitect/engine
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
## Quick start
|
|
12
12
|
|
|
13
13
|
```javascript
|
|
14
|
-
import { createEngine } from '
|
|
14
|
+
import { createEngine } from '@autochitect/engine';
|
|
15
15
|
|
|
16
16
|
const engine = await createEngine(
|
|
17
|
-
new URL('
|
|
17
|
+
new URL('@autochitect/engine/engine.wasm', import.meta.url)
|
|
18
18
|
);
|
|
19
19
|
|
|
20
|
-
const result = engine.
|
|
20
|
+
const result = engine.estimate(`
|
|
21
21
|
revenue: Input
|
|
22
22
|
cost_ratio: Input
|
|
23
23
|
costs = revenue * cost_ratio
|
|
@@ -35,66 +35,131 @@ console.log(result.results);
|
|
|
35
35
|
|
|
36
36
|
**`result.results`** — computed values for every variable.
|
|
37
37
|
|
|
38
|
-
**`result.graph`** — the full dependency DAG showing how each value was derived. Nodes have `kind` (input,
|
|
38
|
+
**`result.graph`** — the full dependency DAG showing how each value was derived. Nodes have `kind` (input, formula, map, scan) and edges show data flow. Build audit trails, trace calculations back to source inputs, or render interactive cost breakdowns.
|
|
39
39
|
|
|
40
40
|
**`result.errors`** / **`result.warnings`** — with line and column numbers.
|
|
41
41
|
|
|
42
42
|
## The DSL
|
|
43
43
|
|
|
44
|
-
The language is deliberately small. It's
|
|
44
|
+
The language is deliberately small. It's designed for cost estimation and financial modeling specifically.
|
|
45
45
|
|
|
46
46
|
```
|
|
47
|
-
#
|
|
47
|
+
# Inputs — bind to external data (your JSON)
|
|
48
48
|
revenue: Input("annual_revenue")
|
|
49
|
-
|
|
49
|
+
headcount: Input
|
|
50
|
+
|
|
51
|
+
# Constants — use formula assignment
|
|
52
|
+
tax_rate = 0.21
|
|
53
|
+
avg_salary = 85000
|
|
54
|
+
|
|
55
|
+
# Params — tunable scenario knobs (passed via inputs)
|
|
50
56
|
growth: Param
|
|
51
57
|
|
|
52
|
-
# Formulas
|
|
53
|
-
|
|
58
|
+
# Formulas — define cost relationships
|
|
59
|
+
labor_cost = headcount * avg_salary
|
|
60
|
+
gross_profit = revenue * (1 - tax_rate)
|
|
54
61
|
net_income = gross_profit * (1 - tax_rate)
|
|
55
62
|
|
|
56
|
-
# Array operations
|
|
63
|
+
# Array operations — project over time
|
|
57
64
|
periods = SEQUENCE(12, 1, 1)
|
|
58
65
|
monthly = MAP(periods, LAMBDA(p, revenue / 12 * POWER(1 + growth, p)))
|
|
59
66
|
|
|
60
|
-
# Accumulation
|
|
67
|
+
# Accumulation — running totals
|
|
61
68
|
cumulative = SCAN(monthly, 0, LAMBDA(acc, m, acc + m))
|
|
62
69
|
```
|
|
63
70
|
|
|
64
|
-
**Inputs** bind to external data (your JSON). **Constants** are
|
|
71
|
+
**Inputs** bind to external data (your JSON). **Constants** are defined with formula assignment (`name = value`). **Params** are tunable scenario knobs passed via the inputs object.
|
|
65
72
|
|
|
66
73
|
**MAP** transforms arrays element-wise. **SCAN** accumulates (like reduce, but returns intermediate results). **LAMBDA** defines inline functions with named parameters.
|
|
67
74
|
|
|
68
|
-
The engine compiles this into a directed acyclic graph, topologically sorts it, and
|
|
75
|
+
The engine compiles this into a directed acyclic graph, topologically sorts it, and estimates in one pass. The graph is immutable — switching scenarios just swaps the input context, so it's instant.
|
|
76
|
+
|
|
77
|
+
## Node.js usage
|
|
78
|
+
|
|
79
|
+
```javascript
|
|
80
|
+
import { createEngine } from '@autochitect/engine';
|
|
81
|
+
import { createRequire } from 'module';
|
|
82
|
+
|
|
83
|
+
const require = createRequire(import.meta.url);
|
|
84
|
+
const wasmPath = require.resolve('@autochitect/engine/engine.wasm');
|
|
85
|
+
const engine = await createEngine(wasmPath);
|
|
86
|
+
|
|
87
|
+
const result = engine.estimate(`
|
|
88
|
+
headcount: Input
|
|
89
|
+
avg_salary = 85000
|
|
90
|
+
benefits_rate = 0.3
|
|
91
|
+
|
|
92
|
+
labor_cost = headcount * avg_salary
|
|
93
|
+
benefits = labor_cost * benefits_rate
|
|
94
|
+
total_people_cost = labor_cost + benefits
|
|
95
|
+
`, {
|
|
96
|
+
headcount: 12,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
console.log(result.results);
|
|
100
|
+
// {
|
|
101
|
+
// headcount: 12,
|
|
102
|
+
// avg_salary: 85000,
|
|
103
|
+
// benefits_rate: 0.3,
|
|
104
|
+
// labor_cost: 1020000,
|
|
105
|
+
// benefits: 306000,
|
|
106
|
+
// total_people_cost: 1326000
|
|
107
|
+
// }
|
|
108
|
+
```
|
|
69
109
|
|
|
70
110
|
## Browser usage
|
|
71
111
|
|
|
72
112
|
```html
|
|
73
113
|
<script type="module">
|
|
74
|
-
import { createEngine } from './node_modules/
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
114
|
+
import { createEngine } from './node_modules/@autochitect/engine/index.mjs';
|
|
115
|
+
|
|
116
|
+
const engine = await createEngine(
|
|
117
|
+
new URL('./node_modules/@autochitect/engine/engine.wasm', import.meta.url)
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const result = engine.estimate(`
|
|
121
|
+
units: Input
|
|
122
|
+
price_per_unit: Input
|
|
123
|
+
discount_rate: Input
|
|
124
|
+
|
|
125
|
+
subtotal = units * price_per_unit
|
|
126
|
+
discount = subtotal * discount_rate
|
|
127
|
+
total = subtotal - discount
|
|
128
|
+
`, {
|
|
129
|
+
units: 1000,
|
|
130
|
+
price_per_unit: 49.99,
|
|
131
|
+
discount_rate: 0.15,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
console.log(result.results);
|
|
135
|
+
// { units: 1000, price_per_unit: 49.99, discount_rate: 0.15,
|
|
136
|
+
// subtotal: 49990, discount: 7498.5, total: 42491.5 }
|
|
78
137
|
</script>
|
|
79
138
|
```
|
|
80
139
|
|
|
81
|
-
|
|
140
|
+
When using a bundler (Vite, webpack, etc.), import the WASM file directly:
|
|
82
141
|
|
|
83
142
|
```javascript
|
|
84
|
-
import { createEngine } from '
|
|
85
|
-
import { fileURLToPath } from 'url';
|
|
86
|
-
import { dirname, join } from 'path';
|
|
143
|
+
import { createEngine } from '@autochitect/engine';
|
|
87
144
|
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
145
|
+
const engine = await createEngine(
|
|
146
|
+
new URL('@autochitect/engine/engine.wasm', import.meta.url)
|
|
147
|
+
);
|
|
91
148
|
```
|
|
92
149
|
|
|
93
150
|
## Options
|
|
94
151
|
|
|
95
152
|
```javascript
|
|
96
|
-
// Skip the dependency graph for faster
|
|
97
|
-
engine.
|
|
153
|
+
// Skip the dependency graph for faster estimation
|
|
154
|
+
engine.estimate(source, inputs, { graph: false });
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## TypeScript
|
|
158
|
+
|
|
159
|
+
Full type definitions are included. Key types:
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import type { Engine, EstimateResult, EstimateOptions } from '@autochitect/engine';
|
|
98
163
|
```
|
|
99
164
|
|
|
100
165
|
## License
|
package/engine.wasm
CHANGED
|
Binary file
|
package/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export interface
|
|
1
|
+
export interface EstimateResult {
|
|
2
2
|
success: boolean;
|
|
3
3
|
results: Record<string, number | number[] | string | null>;
|
|
4
4
|
errors: Array<{ message: string; line: number; col: number }>;
|
|
@@ -16,12 +16,12 @@ export interface EvalResult {
|
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export interface
|
|
19
|
+
export interface EstimateOptions {
|
|
20
20
|
graph?: boolean;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export interface Engine {
|
|
24
|
-
|
|
24
|
+
estimate(source: string, inputs?: Record<string, unknown>, options?: EstimateOptions): EstimateResult;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export function createEngine(wasmSource: string | Response | ArrayBuffer): Promise<Engine>;
|
package/index.mjs
CHANGED
|
@@ -88,16 +88,31 @@ export async function createEngine(wasmSource) {
|
|
|
88
88
|
} else if (wasmSource instanceof Response || (typeof wasmSource === 'object' && typeof wasmSource.then === 'function')) {
|
|
89
89
|
const response = await wasmSource;
|
|
90
90
|
bytes = await response.arrayBuffer();
|
|
91
|
+
} else if (wasmSource instanceof URL) {
|
|
92
|
+
if (wasmSource.protocol === 'file:') {
|
|
93
|
+
const fs = await import('fs');
|
|
94
|
+
bytes = fs.readFileSync(wasmSource);
|
|
95
|
+
} else {
|
|
96
|
+
bytes = await (await fetch(wasmSource)).arrayBuffer();
|
|
97
|
+
}
|
|
91
98
|
} else if (typeof wasmSource === 'string') {
|
|
92
|
-
|
|
99
|
+
const isFilePath = wasmSource.startsWith('/') || wasmSource.startsWith('./') || wasmSource.startsWith('../') || wasmSource.startsWith('file://') || /^[a-zA-Z]:\\/.test(wasmSource);
|
|
100
|
+
if (isFilePath && typeof globalThis.process !== 'undefined') {
|
|
101
|
+
const fs = await import('fs');
|
|
102
|
+
if (wasmSource.startsWith('file://')) {
|
|
103
|
+
const { fileURLToPath } = await import('url');
|
|
104
|
+
bytes = fs.readFileSync(fileURLToPath(wasmSource));
|
|
105
|
+
} else {
|
|
106
|
+
bytes = fs.readFileSync(wasmSource);
|
|
107
|
+
}
|
|
108
|
+
} else if (typeof fetch !== 'undefined') {
|
|
93
109
|
bytes = await (await fetch(wasmSource)).arrayBuffer();
|
|
94
110
|
} else {
|
|
95
111
|
const fs = await import('fs');
|
|
96
|
-
const path = await import('path');
|
|
97
112
|
bytes = fs.readFileSync(wasmSource);
|
|
98
113
|
}
|
|
99
114
|
} else {
|
|
100
|
-
throw new Error('wasmSource must be a URL string, Response, or ArrayBuffer');
|
|
115
|
+
throw new Error('wasmSource must be a URL, URL string, file path, Response, or ArrayBuffer');
|
|
101
116
|
}
|
|
102
117
|
|
|
103
118
|
const { instance } = await WebAssembly.instantiate(bytes, imports);
|
|
@@ -112,11 +127,11 @@ export async function createEngine(wasmSource) {
|
|
|
112
127
|
}
|
|
113
128
|
|
|
114
129
|
return {
|
|
115
|
-
|
|
130
|
+
estimate(source, inputs = {}, options = {}) {
|
|
116
131
|
const includeGraph = options.graph !== false;
|
|
117
132
|
const src = writeString(source);
|
|
118
133
|
const json = writeString(JSON.stringify(inputs));
|
|
119
|
-
const resultLen = instance.exports.
|
|
134
|
+
const resultLen = instance.exports.wasm_estimate(
|
|
120
135
|
src.ptr, src.len,
|
|
121
136
|
json.ptr, json.len,
|
|
122
137
|
includeGraph ? 1 : 0
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autochitect/engine",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "A 245KB WebAssembly financial modeling engine. Define cost models in a purpose-built DSL, get instant
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "A 245KB WebAssembly financial modeling engine. Define cost models in a purpose-built DSL, get instant estimates with full dependency tracing. No server required.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "index.mjs",
|