@autochitect/engine 1.1.2 → 1.1.4

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
@@ -8,27 +8,88 @@ A 245KB WebAssembly cost estimation engine. Define cost models in a purpose-buil
8
8
  npm install @autochitect/engine
9
9
  ```
10
10
 
11
- ## Quick start
11
+ ## Node.js
12
12
 
13
13
  ```javascript
14
14
  import { createEngine } from '@autochitect/engine';
15
15
 
16
- const engine = await createEngine(
17
- new URL('@autochitect/engine/engine.wasm', import.meta.url)
18
- );
16
+ const engine = await createEngine();
19
17
 
20
18
  const result = engine.estimate(`
21
- revenue: Input
22
- cost_ratio: Input
23
- costs = revenue * cost_ratio
24
- profit = revenue - costs
19
+ seats: Input
20
+ base_price_per_seat = 12
21
+ storage_gb: Input
22
+ storage_price_per_gb = 0.50
23
+
24
+ seat_cost = seats * base_price_per_seat
25
+ storage_cost = storage_gb * storage_price_per_gb
26
+ subtotal = seat_cost + storage_cost
27
+
28
+ discount_rate = IF(seats > 50, 0.20, IF(seats > 20, 0.10, 0))
29
+ discount = subtotal * discount_rate
30
+ monthly_total = subtotal - discount
31
+ annual_total = monthly_total * 12
25
32
  `, {
26
- revenue: 500000,
27
- cost_ratio: 0.6
33
+ seats: 35,
34
+ storage_gb: 200,
28
35
  });
29
36
 
30
37
  console.log(result.results);
31
- // { revenue: 500000, cost_ratio: 0.6, costs: 300000, profit: 200000 }
38
+ // {
39
+ // seats: 35, base_price_per_seat: 12, storage_gb: 200, storage_price_per_gb: 0.5,
40
+ // seat_cost: 420, storage_cost: 100, subtotal: 520,
41
+ // discount_rate: 0.1, discount: 52, monthly_total: 468, annual_total: 5616
42
+ // }
43
+ ```
44
+
45
+ ## Browser
46
+
47
+ No install needed — paste this into an HTML file and open it.
48
+
49
+ ```html
50
+ <script type="module">
51
+ import { createEngine } from 'https://esm.sh/@autochitect/engine';
52
+
53
+ const engine = await createEngine(
54
+ 'https://unpkg.com/@autochitect/engine/engine.wasm'
55
+ );
56
+
57
+ const result = engine.estimate(`
58
+ seats: Input
59
+ base_price_per_seat = 12
60
+ storage_gb: Input
61
+ storage_price_per_gb = 0.50
62
+
63
+ seat_cost = seats * base_price_per_seat
64
+ storage_cost = storage_gb * storage_price_per_gb
65
+ subtotal = seat_cost + storage_cost
66
+
67
+ discount_rate = IF(seats > 50, 0.20, IF(seats > 20, 0.10, 0))
68
+ discount = subtotal * discount_rate
69
+ monthly_total = subtotal - discount
70
+ annual_total = monthly_total * 12
71
+ `, {
72
+ seats: 35,
73
+ storage_gb: 200,
74
+ });
75
+
76
+ console.log(result.results);
77
+ // {
78
+ // seats: 35, base_price_per_seat: 12, storage_gb: 200, storage_price_per_gb: 0.5,
79
+ // seat_cost: 420, storage_cost: 100, subtotal: 520,
80
+ // discount_rate: 0.1, discount: 52, monthly_total: 468, annual_total: 5616
81
+ // }
82
+ </script>
83
+ ```
84
+
85
+ If you use a bundler (Vite, webpack, etc.), import normally:
86
+
87
+ ```javascript
88
+ import { createEngine } from '@autochitect/engine';
89
+
90
+ const engine = await createEngine(
91
+ new URL('@autochitect/engine/engine.wasm', import.meta.url)
92
+ );
32
93
  ```
33
94
 
34
95
  ## What you get
@@ -41,8 +102,6 @@ console.log(result.results);
41
102
 
42
103
  ## The DSL
43
104
 
44
- The language is deliberately small. It's designed for cost estimation and financial modeling specifically.
45
-
46
105
  ```
47
106
  # Inputs — bind to external data (your JSON)
48
107
  revenue: Input("annual_revenue")
@@ -72,81 +131,6 @@ cumulative = SCAN(monthly, 0, LAMBDA(acc, m, acc + m))
72
131
 
73
132
  **MAP** transforms arrays element-wise. **SCAN** accumulates (like reduce, but returns intermediate results). **LAMBDA** defines inline functions with named parameters.
74
133
 
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
- ```
109
-
110
- ## Browser usage
111
-
112
- ```html
113
- <script type="module">
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 }
137
- </script>
138
- ```
139
-
140
- When using a bundler (Vite, webpack, etc.), import the WASM file directly:
141
-
142
- ```javascript
143
- import { createEngine } from '@autochitect/engine';
144
-
145
- const engine = await createEngine(
146
- new URL('@autochitect/engine/engine.wasm', import.meta.url)
147
- );
148
- ```
149
-
150
134
  ## Options
151
135
 
152
136
  ```javascript
@@ -0,0 +1,125 @@
1
+ const WASI_SHIM = {
2
+ fd_write: (fd, iovs_ptr, iovs_len, nwritten_ptr, mem) => {
3
+ const view = new DataView(mem.buffer);
4
+ let written = 0;
5
+ for (let i = 0; i < iovs_len; i++) {
6
+ written += view.getUint32(iovs_ptr + i * 8 + 4, true);
7
+ }
8
+ view.setUint32(nwritten_ptr, written, true);
9
+ return 0;
10
+ },
11
+ fd_close: () => 0,
12
+ fd_seek: (fd, lo, hi, whence, ptr, mem) => {
13
+ new DataView(mem.buffer).setBigUint64(ptr, 0n, true);
14
+ return 0;
15
+ },
16
+ fd_fdstat_get: (fd, buf, mem) => {
17
+ for (let i = 0; i < 24; i++) new DataView(mem.buffer).setUint8(buf + i, 0);
18
+ return 0;
19
+ },
20
+ environ_sizes_get: (cp, bp, mem) => {
21
+ const v = new DataView(mem.buffer);
22
+ v.setUint32(cp, 0, true);
23
+ v.setUint32(bp, 0, true);
24
+ return 0;
25
+ },
26
+ args_sizes_get: (ap, bp, mem) => {
27
+ const v = new DataView(mem.buffer);
28
+ v.setUint32(ap, 0, true);
29
+ v.setUint32(bp, 0, true);
30
+ return 0;
31
+ },
32
+ clock_time_get: (id, prec, ptr, mem) => {
33
+ new DataView(mem.buffer).setBigUint64(ptr, BigInt(Date.now()) * 1000000n, true);
34
+ return 0;
35
+ },
36
+ proc_exit: (code) => { throw new Error(`proc_exit(${code})`); },
37
+ };
38
+
39
+ const STUB_ERRNO = {
40
+ fd_read: 0, fd_sync: 0, fd_pread: 0, fd_pwrite: 0, fd_readdir: 0,
41
+ fd_prestat_get: 8, fd_prestat_dir_name: 28,
42
+ fd_filestat_get: 0, fd_filestat_set_size: 0, fd_filestat_set_times: 0,
43
+ environ_get: 0, args_get: 0,
44
+ path_open: 44, path_create_directory: 44, path_filestat_get: 44,
45
+ path_filestat_set_times: 44, path_link: 44, path_readlink: 44,
46
+ path_remove_directory: 44, path_rename: 44, path_symlink: 44,
47
+ path_unlink_file: 44, sched_yield: 0, poll_oneoff: 0,
48
+ sock_accept: 58, sock_recv: 58, sock_send: 58, sock_shutdown: 58,
49
+ };
50
+
51
+ function buildWasiImports(memoryRef) {
52
+ const imports = {};
53
+ for (const [name, fn] of Object.entries(WASI_SHIM)) {
54
+ if (typeof fn === 'function') {
55
+ if (name === 'proc_exit') {
56
+ imports[name] = fn;
57
+ } else {
58
+ imports[name] = (...args) => fn(...args, memoryRef.current);
59
+ }
60
+ }
61
+ }
62
+ for (const [name, errno] of Object.entries(STUB_ERRNO)) {
63
+ imports[name] = () => errno;
64
+ }
65
+ imports.clock_res_get = (id, ptr) => {
66
+ new DataView(memoryRef.current.buffer).setBigUint64(ptr, 1000000n, true);
67
+ return 0;
68
+ };
69
+ imports.random_get = (buf, len) => {
70
+ const view = new Uint8Array(memoryRef.current.buffer, buf, len);
71
+ if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
72
+ crypto.getRandomValues(view);
73
+ } else {
74
+ for (let i = 0; i < len; i++) view[i] = Math.floor(Math.random() * 256);
75
+ }
76
+ return 0;
77
+ };
78
+ return imports;
79
+ }
80
+
81
+ export async function createEngine(wasmSource) {
82
+ const memoryRef = { current: null };
83
+ const imports = { wasi_snapshot_preview1: buildWasiImports(memoryRef) };
84
+
85
+ let bytes;
86
+ if (wasmSource instanceof ArrayBuffer || ArrayBuffer.isView(wasmSource)) {
87
+ bytes = wasmSource;
88
+ } else if (wasmSource instanceof Response || (typeof wasmSource === 'object' && typeof wasmSource.then === 'function')) {
89
+ const response = await wasmSource;
90
+ bytes = await response.arrayBuffer();
91
+ } else if (wasmSource instanceof URL) {
92
+ bytes = await (await fetch(wasmSource)).arrayBuffer();
93
+ } else if (typeof wasmSource === 'string') {
94
+ bytes = await (await fetch(wasmSource)).arrayBuffer();
95
+ } else {
96
+ throw new Error('wasmSource must be a URL, URL string, Response, or ArrayBuffer');
97
+ }
98
+
99
+ const { instance } = await WebAssembly.instantiate(bytes, imports);
100
+ memoryRef.current = instance.exports.memory;
101
+
102
+ function writeString(str) {
103
+ const enc = new TextEncoder().encode(str);
104
+ const ptr = instance.exports.wasm_alloc(enc.length);
105
+ if (ptr === 0) throw new Error('WASM allocation failed');
106
+ new Uint8Array(memoryRef.current.buffer, ptr, enc.length).set(enc);
107
+ return { ptr, len: enc.length };
108
+ }
109
+
110
+ return {
111
+ estimate(source, inputs = {}, options = {}) {
112
+ const includeGraph = options.graph !== false;
113
+ const src = writeString(source);
114
+ const json = writeString(JSON.stringify(inputs));
115
+ const resultLen = instance.exports.wasm_estimate(
116
+ src.ptr, src.len,
117
+ json.ptr, json.len,
118
+ includeGraph ? 1 : 0
119
+ );
120
+ const resultPtr = instance.exports.wasm_result_ptr();
121
+ const resultBytes = new Uint8Array(memoryRef.current.buffer, resultPtr, resultLen);
122
+ return JSON.parse(new TextDecoder().decode(resultBytes));
123
+ },
124
+ };
125
+ }
package/index.d.ts CHANGED
@@ -21,7 +21,7 @@ export interface EstimateOptions {
21
21
  }
22
22
 
23
23
  export interface Engine {
24
- estimate(source: string, inputs?: Record<string, unknown>, options?: EstimateOptions): EstimateResult;
24
+ estimate(source: string, inputs?: Record<string, unknown> | object, options?: EstimateOptions): EstimateResult;
25
25
  }
26
26
 
27
- export function createEngine(wasmSource: string | Response | ArrayBuffer): Promise<Engine>;
27
+ export function createEngine(wasmSource?: string | URL | Response | ArrayBuffer): Promise<Engine>;
package/index.mjs CHANGED
@@ -82,6 +82,13 @@ export async function createEngine(wasmSource) {
82
82
  const memoryRef = { current: null };
83
83
  const imports = { wasi_snapshot_preview1: buildWasiImports(memoryRef) };
84
84
 
85
+ if (wasmSource == null && typeof globalThis.process !== 'undefined') {
86
+ const { fileURLToPath } = await import('url');
87
+ const { dirname, join } = await import('path');
88
+ const fs = await import('fs');
89
+ wasmSource = join(dirname(fileURLToPath(import.meta.url)), 'engine.wasm');
90
+ }
91
+
85
92
  let bytes;
86
93
  if (wasmSource instanceof ArrayBuffer || ArrayBuffer.isView(wasmSource)) {
87
94
  bytes = wasmSource;
package/package.json CHANGED
@@ -1,13 +1,23 @@
1
1
  {
2
2
  "name": "@autochitect/engine",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
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",
8
8
  "types": "index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./index.d.ts",
12
+ "browser": "./index.browser.mjs",
13
+ "import": "./index.mjs",
14
+ "default": "./index.mjs"
15
+ },
16
+ "./engine.wasm": "./engine.wasm"
17
+ },
9
18
  "files": [
10
19
  "index.mjs",
20
+ "index.browser.mjs",
11
21
  "index.d.ts",
12
22
  "engine.wasm",
13
23
  "LICENSE",
@@ -26,7 +36,7 @@
26
36
  ],
27
37
  "repository": {
28
38
  "type": "git",
29
- "url": "git@github.com:autochitect/engine.git"
39
+ "url": "git+ssh://git@github.com/autochitect/engine.git"
30
40
  },
31
41
  "homepage": "https://autochitect.com/engine"
32
42
  }