hledger-wasm 0.1.0

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 ADDED
@@ -0,0 +1,35 @@
1
+ # hledger-wasm
2
+
3
+ hledger compiled to WebAssembly for running in the browser.
4
+
5
+ ## Usage
6
+
7
+ ```javascript
8
+ import { init, accounts, balance, print, aregister, commodities } from 'hledger-wasm';
9
+
10
+ await init();
11
+
12
+ const journal = `
13
+ 2024-01-01 Opening
14
+ assets:bank 1000 USD
15
+ equity:opening
16
+ `;
17
+
18
+ const accountList = await accounts(journal);
19
+ const balances = await balance(journal);
20
+ ```
21
+
22
+ ## API
23
+
24
+ All functions take journal content as a string and return Promises.
25
+
26
+ - `init()` - Initialize the WASM module (call once)
27
+ - `accounts(journal)` - Get account names as `string[]`
28
+ - `print(journal)` - Get transactions as objects
29
+ - `balance(journal)` - Get balances as `[accountName, amounts][]`
30
+ - `aregister(journal, account)` - Get transactions for an account
31
+ - `commodities(journal)` - Get commodity names as `string[]`
32
+
33
+ ## window.hledgerWasm
34
+
35
+ The bridge automatically exposes these functions on `window.hledgerWasm`.
Binary file
@@ -0,0 +1,180 @@
1
+ /**
2
+ * hledger-wasm JavaScript bridge
3
+ * Provides window.hledgerWasm API for running hledger commands in the browser
4
+ */
5
+
6
+ import { WASI, OpenFile, File, ConsoleStdout, PreopenDirectory } from "@bjorn3/browser_wasi_shim";
7
+
8
+ let wasmInstance = null;
9
+ let wasiInstance = null;
10
+
11
+ // Configurable WASM path - can be set before calling init()
12
+ let wasmPath = "/wasm/hledger-wasm.wasm";
13
+
14
+ /**
15
+ * Set the path to the hledger WASM file
16
+ */
17
+ export function setWasmPath(path) {
18
+ wasmPath = path;
19
+ }
20
+
21
+ /**
22
+ * Initialize the hledger WASM module
23
+ * Must be called once before using other functions
24
+ */
25
+ export async function init() {
26
+ if (wasmInstance) {
27
+ return; // Already initialized
28
+ }
29
+
30
+ const response = await fetch(wasmPath);
31
+ const wasmBytes = await response.arrayBuffer();
32
+
33
+ // Create a minimal WASI environment
34
+ const fds = [
35
+ new OpenFile(new File([])), // stdin
36
+ ConsoleStdout.lineBuffered((msg) => console.log("[hledger]", msg)),
37
+ ConsoleStdout.lineBuffered((msg) => console.error("[hledger]", msg)),
38
+ new PreopenDirectory("/", new Map()),
39
+ ];
40
+
41
+ wasiInstance = new WASI([], [], fds, { debug: false });
42
+
43
+ const { instance } = await WebAssembly.instantiate(wasmBytes, {
44
+ wasi_snapshot_preview1: wasiInstance.wasiImport,
45
+ });
46
+
47
+ wasmInstance = instance;
48
+ wasiInstance.initialize(instance);
49
+ }
50
+
51
+ /**
52
+ * Write journal content to virtual filesystem and run hledger command
53
+ */
54
+ async function runHledger(journalContent, command, ...args) {
55
+ if (!wasmInstance) {
56
+ throw new Error("hledger WASM not initialized. Call init() first.");
57
+ }
58
+
59
+ // Capture stdout
60
+ let stdout = "";
61
+ let stderr = "";
62
+
63
+ const fds = [
64
+ new OpenFile(new File([])), // stdin
65
+ ConsoleStdout.lineBuffered((msg) => { stdout += msg + "\n"; }),
66
+ ConsoleStdout.lineBuffered((msg) => { stderr += msg + "\n"; }),
67
+ new PreopenDirectory("/", new Map([
68
+ ["journal.hledger", new File(new TextEncoder().encode(journalContent))],
69
+ ])),
70
+ ];
71
+
72
+ const wasi = new WASI(
73
+ ["hledger-wasm", command, "/journal.hledger", ...args],
74
+ [],
75
+ fds,
76
+ { debug: false }
77
+ );
78
+
79
+ const response = await fetch(wasmPath);
80
+ const wasmBytes = await response.arrayBuffer();
81
+
82
+ const { instance } = await WebAssembly.instantiate(wasmBytes, {
83
+ wasi_snapshot_preview1: wasi.wasiImport,
84
+ });
85
+
86
+ wasi.initialize(instance);
87
+
88
+ try {
89
+ instance.exports._start();
90
+ } catch (e) {
91
+ if (e.message !== "exit with exit code 0" && !e.message.includes("unreachable")) {
92
+ console.error("hledger error:", e);
93
+ }
94
+ }
95
+
96
+ if (stderr.trim()) {
97
+ console.warn("hledger stderr:", stderr);
98
+ }
99
+
100
+ return stdout.trim();
101
+ }
102
+
103
+ /**
104
+ * Get account names from journal
105
+ * @param {string} journal - Journal content as string
106
+ * @returns {Promise<string[]>} - Array of account names
107
+ */
108
+ export async function accounts(journal) {
109
+ const result = await runHledger(journal, "accounts");
110
+ if (!result) return [];
111
+ return JSON.parse(result);
112
+ }
113
+
114
+ /**
115
+ * Get transactions (print command)
116
+ * @param {string} journal - Journal content as string
117
+ * @returns {Promise<object[]>} - Array of transaction objects
118
+ */
119
+ export async function print(journal) {
120
+ const result = await runHledger(journal, "print");
121
+ if (!result) return [];
122
+ return JSON.parse(result);
123
+ }
124
+
125
+ /**
126
+ * Get account balances
127
+ * @param {string} journal - Journal content as string
128
+ * @returns {Promise<Array<[string, object[]]>>} - Array of [accountName, amounts] tuples
129
+ */
130
+ export async function balance(journal) {
131
+ const result = await runHledger(journal, "balance");
132
+ if (!result) return [];
133
+ return JSON.parse(result);
134
+ }
135
+
136
+ /**
137
+ * Get account register (transactions for a specific account)
138
+ * @param {string} journal - Journal content as string
139
+ * @param {string} account - Account name or prefix
140
+ * @returns {Promise<object[]>} - Array of transaction objects
141
+ */
142
+ export async function aregister(journal, account) {
143
+ const result = await runHledger(journal, "aregister", account);
144
+ if (!result) return [];
145
+ return JSON.parse(result);
146
+ }
147
+
148
+ /**
149
+ * Get commodities used in journal
150
+ * @param {string} journal - Journal content as string
151
+ * @returns {Promise<string[]>} - Array of commodity names
152
+ */
153
+ export async function commodities(journal) {
154
+ const result = await runHledger(journal, "commodities");
155
+ if (!result) return [];
156
+ return JSON.parse(result);
157
+ }
158
+
159
+ // Expose on window for Rust wasm_bindgen interop
160
+ if (typeof window !== "undefined") {
161
+ window.hledgerWasm = {
162
+ init,
163
+ setWasmPath,
164
+ accounts,
165
+ print,
166
+ balance,
167
+ aregister,
168
+ commodities,
169
+ };
170
+ }
171
+
172
+ export default {
173
+ init,
174
+ setWasmPath,
175
+ accounts,
176
+ print,
177
+ balance,
178
+ aregister,
179
+ commodities,
180
+ };
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "hledger-wasm",
3
+ "version": "0.1.0",
4
+ "description": "hledger compiled to WebAssembly for browser use",
5
+ "type": "module",
6
+ "main": "js/hledger-bridge.js",
7
+ "exports": {
8
+ ".": "./js/hledger-bridge.js",
9
+ "./hledger-wasm.wasm": "./dist/hledger-wasm.wasm"
10
+ },
11
+ "files": [
12
+ "js/hledger-bridge.js",
13
+ "dist/hledger-wasm.wasm"
14
+ ],
15
+ "scripts": {
16
+ "build": "./build.sh"
17
+ },
18
+ "dependencies": {
19
+ "@bjorn3/browser_wasi_shim": "^0.4.2"
20
+ },
21
+ "keywords": ["hledger", "wasm", "webassembly", "accounting", "ledger"],
22
+ "license": "GPL-3.0-or-later",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/reesericci/hledger-wasm"
26
+ }
27
+ }