@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 CHANGED
@@ -1,23 +1,23 @@
1
- # forecaster
1
+ # @autochitect/engine
2
2
 
3
- A 245KB WebAssembly financial modeling engine. Write cost models in a purpose-built DSL, get instant evaluation with a full dependency graph. Runs entirely client-side — no server, no latency, no data leaving the browser.
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 forecaster
8
+ npm install @autochitect/engine
9
9
  ```
10
10
 
11
11
  ## Quick start
12
12
 
13
13
  ```javascript
14
- import { createEngine } from 'forecaster';
14
+ import { createEngine } from '@autochitect/engine';
15
15
 
16
16
  const engine = await createEngine(
17
- new URL('forecaster/engine.wasm', import.meta.url)
17
+ new URL('@autochitect/engine/engine.wasm', import.meta.url)
18
18
  );
19
19
 
20
- const result = engine.evaluate(`
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, constant, formula, map, scan) and edges show data flow. Use this to build audit trails, trace calculations back to source inputs, or render interactive visualizations.
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 not a spreadsheet formula language — it's designed for financial modeling specifically.
44
+ The language is deliberately small. It's designed for cost estimation and financial modeling specifically.
45
45
 
46
46
  ```
47
- # Declarations
47
+ # Inputs — bind to external data (your JSON)
48
48
  revenue: Input("annual_revenue")
49
- tax_rate: Const(0.21)
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
- gross_profit = revenue * (1 - cost_ratio)
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 fixed values. **References** alias other variables.
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 evaluates in one pass. The graph is immutable — switching scenarios just swaps the input context, so it's instant.
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/forecaster/index.mjs';
75
- const engine = await createEngine('./node_modules/forecaster/engine.wasm');
76
- const result = engine.evaluate('x = 2 + 2', {});
77
- document.body.textContent = JSON.stringify(result.results);
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
- ## Node.js usage
140
+ When using a bundler (Vite, webpack, etc.), import the WASM file directly:
82
141
 
83
142
  ```javascript
84
- import { createEngine } from 'forecaster';
85
- import { fileURLToPath } from 'url';
86
- import { dirname, join } from 'path';
143
+ import { createEngine } from '@autochitect/engine';
87
144
 
88
- const __dirname = dirname(fileURLToPath(import.meta.url));
89
- const wasmPath = join(__dirname, 'node_modules/forecaster/engine.wasm');
90
- const engine = await createEngine(wasmPath);
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 evaluation
97
- engine.evaluate(source, inputs, { graph: false });
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 EvalResult {
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 EvalOptions {
19
+ export interface EstimateOptions {
20
20
  graph?: boolean;
21
21
  }
22
22
 
23
23
  export interface Engine {
24
- evaluate(source: string, inputs?: Record<string, unknown>, options?: EvalOptions): EvalResult;
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
- if (typeof fetch !== 'undefined') {
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
- evaluate(source, inputs = {}, options = {}) {
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.wasm_eval(
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.0.0",
4
- "description": "A 245KB WebAssembly financial modeling engine. Define cost models in a purpose-built DSL, get instant evaluation with full dependency tracing. No server required.",
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",