@geeqdev/geeq-sdk 0.0.1 → 0.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.
Files changed (122) hide show
  1. package/.idea/geeq_sdk.iml +12 -0
  2. package/.idea/modules.xml +8 -0
  3. package/.idea/vcs.xml +6 -0
  4. package/.nvmrc +1 -0
  5. package/CONTRIBUTING.md +20 -0
  6. package/example/.env.example +4 -0
  7. package/example/README.md +304 -0
  8. package/example/USAGE.md +74 -0
  9. package/example/index.html +12 -0
  10. package/example/package-lock.json +1557 -0
  11. package/example/package.json +25 -0
  12. package/example/scripts/browser-check.mjs +1421 -0
  13. package/example/scripts/match-derivation.mjs +65 -0
  14. package/example/src/App.vue +138 -0
  15. package/example/src/components/AuthorityPanel.vue +277 -0
  16. package/example/src/components/ChainPanel.vue +267 -0
  17. package/example/src/components/ChainState.vue +59 -0
  18. package/example/src/components/CopyButton.vue +25 -0
  19. package/example/src/components/CreateAssetPanel.vue +313 -0
  20. package/example/src/components/DigitalAssetsPanel.vue +68 -0
  21. package/example/src/components/EvidencePanel.vue +396 -0
  22. package/example/src/components/ExplorerPanel.vue +119 -0
  23. package/example/src/components/KeypairPanel.vue +182 -0
  24. package/example/src/components/PhrasePanel.vue +105 -0
  25. package/example/src/components/SocketWarning.vue +18 -0
  26. package/example/src/components/StepGate.vue +24 -0
  27. package/example/src/components/TransferPanel.vue +416 -0
  28. package/example/src/components/TxDeadline.vue +19 -0
  29. package/example/src/components/TxFee.vue +31 -0
  30. package/example/src/components/TxStatus.vue +105 -0
  31. package/example/src/composables/useSocketConnection.ts +80 -0
  32. package/example/src/env.d.ts +12 -0
  33. package/example/src/main.ts +5 -0
  34. package/example/src/pennies.ts +7 -0
  35. package/example/src/style.css +149 -0
  36. package/example/tsconfig.json +19 -0
  37. package/example/vite.config.ts +26 -0
  38. package/jest.config.js +18 -0
  39. package/package.json +1 -5
  40. package/scripts/copy-wasm.mjs +21 -0
  41. package/scripts/smoke.mjs +382 -0
  42. package/src/address/record-type.ts +77 -0
  43. package/src/address/wallet-address.ts +105 -0
  44. package/src/amount.ts +143 -0
  45. package/src/assert.ts +30 -0
  46. package/src/capnp/README.md +15 -0
  47. package/src/capnp/adminNetTx.capnp.ts +66 -0
  48. package/src/capnp/appLayBlock.capnp.ts +26 -0
  49. package/src/capnp/appLayLedger.capnp.ts +35 -0
  50. package/src/capnp/appLayRec.capnp.ts +159 -0
  51. package/src/capnp/chainStateView.capnp.ts +49 -0
  52. package/src/capnp/coinAcctRec.capnp.ts +193 -0
  53. package/src/capnp/genesisBlock.capnp.ts +56 -0
  54. package/src/capnp/hubContentStmt.capnp.ts +59 -0
  55. package/src/capnp/misc.capnp.ts +19 -0
  56. package/src/capnp/netActorRec.capnp.ts +64 -0
  57. package/src/capnp/nodeContentStmt.capnp.ts +62 -0
  58. package/src/capnp/nodePayloadList.capnp.ts +28 -0
  59. package/src/capnp/nodeTxList.capnp.ts +37 -0
  60. package/src/capnp/paramAcctRec.capnp.ts +669 -0
  61. package/src/capnp/payload.capnp.ts +261 -0
  62. package/src/capnp/proxyAcctRec.capnp.ts +259 -0
  63. package/src/capnp/relayContentStmt.capnp.ts +85 -0
  64. package/src/capnp/sysAcctRec.capnp.ts +47 -0
  65. package/src/capnp/userAppTx.capnp.ts +269 -0
  66. package/src/capnp/userValTx.capnp.ts +1999 -0
  67. package/src/capnp/valLayBlock.capnp.ts +93 -0
  68. package/src/capnp/valLayLedger.capnp.ts +80 -0
  69. package/src/chain/geeq-chain.ts +1094 -0
  70. package/src/chain/geeq-node.ts +105 -0
  71. package/src/chain/nonce-guard.ts +73 -0
  72. package/src/chain/select.ts +149 -0
  73. package/src/crypto/account-id.ts +83 -0
  74. package/src/crypto/keypair.ts +221 -0
  75. package/src/crypto/mnemonic.ts +69 -0
  76. package/src/encoding/base64.ts +58 -0
  77. package/src/encoding/hex.ts +42 -0
  78. package/src/errors.ts +122 -0
  79. package/src/index.ts +195 -0
  80. package/src/ledger/coin-accounts.ts +73 -0
  81. package/src/ledger/decode.ts +147 -0
  82. package/src/ledger/geeq-ledger.ts +184 -0
  83. package/src/ledger/net-actors.ts +129 -0
  84. package/src/ledger/nft-records.ts +49 -0
  85. package/src/ledger/params.ts +82 -0
  86. package/src/ledger/proxy-accounts.ts +51 -0
  87. package/src/net/discovery.ts +231 -0
  88. package/src/net/explorer.ts +123 -0
  89. package/src/net/read-ledger.ts +58 -0
  90. package/src/net/routes.ts +79 -0
  91. package/src/net/transport.ts +221 -0
  92. package/src/net/websocket.ts +94 -0
  93. package/src/runtime/bindings.ts +38 -0
  94. package/src/runtime/init.ts +49 -0
  95. package/src/runtime/wasm-loader.browser.ts +22 -0
  96. package/src/runtime/wasm-loader.node.ts +16 -0
  97. package/src/socket/geeq-socket.ts +827 -0
  98. package/src/socket/tracked-tx.ts +319 -0
  99. package/src/socket/tx-status.ts +131 -0
  100. package/src/tx/attestation.ts +222 -0
  101. package/src/tx/content.ts +82 -0
  102. package/src/tx/encode.ts +107 -0
  103. package/src/tx/expiry.ts +11 -0
  104. package/src/tx/fee.ts +31 -0
  105. package/src/tx/metadata.ts +100 -0
  106. package/src/tx/nft.ts +108 -0
  107. package/src/tx/proxy.ts +156 -0
  108. package/src/tx/transfer.ts +162 -0
  109. package/src/types.ts +503 -0
  110. package/src/wasm/README.md +22 -0
  111. package/src/wasm/browser/geeq_core.d.ts +1224 -0
  112. package/src/wasm/browser/geeq_core.js +6014 -0
  113. package/src/wasm/browser/geeq_core_bg.wasm +0 -0
  114. package/src/wasm/browser/geeq_core_bg.wasm.d.ts +447 -0
  115. package/src/wasm/nodejs/geeq_core.d.ts +753 -0
  116. package/src/wasm/nodejs/geeq_core.js +6016 -0
  117. package/src/wasm/nodejs/geeq_core_bg.wasm +0 -0
  118. package/src/wasm/nodejs/geeq_core_bg.wasm.d.ts +447 -0
  119. package/src/wasm/nodejs/package.json +3 -0
  120. package/tsconfig.build.json +8 -0
  121. package/tsconfig.json +20 -0
  122. package/tsup.config.ts +60 -0
@@ -0,0 +1,221 @@
1
+ import { GeeqError, networkUnreachable, nodeError } from '../errors'
2
+
3
+ /** A response, narrowed to what this SDK reads. A WHATWG `Response` satisfies it. */
4
+ export interface GeeqHttpResponse {
5
+ readonly ok: boolean
6
+ readonly status: number
7
+ text(): Promise<string>
8
+ }
9
+
10
+ /** A request, narrowed to what this SDK sends. Assignable to `RequestInit`. */
11
+ export interface GeeqRequestInit {
12
+ readonly method?: string
13
+ readonly headers?: Readonly<Record<string, string>>
14
+ readonly body?: string
15
+ readonly signal?: AbortSignal
16
+ }
17
+
18
+ /**
19
+ * How the SDK reaches a node.
20
+ *
21
+ * Deliberately a structural subset of WHATWG `fetch`, so `{ transport: fetch }`
22
+ * typechecks with no adapter, and so does undici with a custom dispatcher, a relative
23
+ * URL through a dev proxy, or a scripted array of responses in a test.
24
+ */
25
+ export type GeeqTransport = (
26
+ url: string,
27
+ init?: GeeqRequestInit,
28
+ ) => Promise<GeeqHttpResponse>
29
+
30
+ /**
31
+ * `globalThis.fetch`, resolved at call time.
32
+ *
33
+ * Never a stored reference: an unbound `fetch` throws `Illegal invocation` in a
34
+ * browser, and capturing it would defeat any test that swaps `globalThis.fetch` after
35
+ * this module is imported.
36
+ */
37
+ export const defaultTransport: GeeqTransport = (url, init) =>
38
+ globalThis.fetch(url, init as RequestInit)
39
+
40
+ export interface RequestOptions {
41
+ readonly transport: GeeqTransport
42
+ readonly timeoutMs: number
43
+ readonly retries: number
44
+ readonly headers?: Readonly<Record<string, string>> | undefined
45
+ readonly signal?: AbortSignal | undefined
46
+ readonly method?: string | undefined
47
+ readonly body?: string | undefined
48
+ }
49
+
50
+ /**
51
+ * The one failure a browser can be certain about before making a request.
52
+ *
53
+ * An https page may not load an http subresource. The browser blocks it and reports a
54
+ * generic network failure, so issuing the request would only turn a knowable cause into
55
+ * an ambiguous one.
56
+ */
57
+ function assertSchemeReachable(url: string): void {
58
+ const location = (globalThis as { location?: { protocol?: string } }).location
59
+ if (location?.protocol !== 'https:') return
60
+
61
+ let scheme: string
62
+ try {
63
+ scheme = new URL(url).protocol
64
+ } catch {
65
+ return
66
+ }
67
+ if (scheme !== 'http:') return
68
+
69
+ throw networkUnreachable(
70
+ `This page is served over https and the node URL ${url} is http, so the browser ` +
71
+ 'will block the request as mixed content before it is sent. Use the node\'s https ' +
72
+ 'URL (port 2082 by default), or serve this page over http while developing.',
73
+ )
74
+ }
75
+
76
+ /** The four causes a browser cannot tell apart, each with what to do about it. */
77
+ function opaqueFailureMessage(url: string): string {
78
+ return (
79
+ `Could not reach the Geeq node at ${url}. A browser reports network failures ` +
80
+ 'opaquely, so this is one of four things: (1) the node does not answer CORS for ' +
81
+ 'this origin — pass your own `transport` routing through a proxy you control; ' +
82
+ "(2) the node's TLS certificate is not trusted, which is common because nodes are " +
83
+ 'addressed by bare IP — open the URL in a tab to see the interstitial; (3) this ' +
84
+ 'page is https and the node is http, which browsers block; or (4) the node is down ' +
85
+ 'or the URL is wrong.'
86
+ )
87
+ }
88
+
89
+ function describeCause(cause: unknown): string | undefined {
90
+ if (cause !== null && typeof cause === 'object') {
91
+ const { code } = cause as { code?: unknown }
92
+ if (typeof code === 'string') return code
93
+ }
94
+ return undefined
95
+ }
96
+
97
+ /** Pull the node's own `details` out of an error body without trusting its shape. */
98
+ function nodeDetailsOf(body: string): string | undefined {
99
+ try {
100
+ const parsed: unknown = JSON.parse(body)
101
+ if (parsed !== null && typeof parsed === 'object') {
102
+ const { details, error } = parsed as { details?: unknown; error?: unknown }
103
+ if (typeof details === 'string' && details !== '') return details
104
+ // A block explorer sends its reasons as a list.
105
+ if (Array.isArray(details)) {
106
+ const joined = details.filter((item) => typeof item === 'string').join('; ')
107
+ if (joined !== '') return joined
108
+ }
109
+ if (typeof error === 'string' && error !== '') return error
110
+ }
111
+ } catch {
112
+ // Not JSON. The raw body is still the most useful thing we have.
113
+ }
114
+ return body === '' ? undefined : body.slice(0, 400)
115
+ }
116
+
117
+ /**
118
+ * `signal.throwIfAborted()`, written out: a polyfilled `AbortSignal` may not have it.
119
+ * A call rather than an inline check also keeps TypeScript from narrowing `aborted` to
120
+ * false for the rest of the function — it can change across an `await`.
121
+ */
122
+ function throwIfAborted(signal: AbortSignal | undefined): void {
123
+ if (signal?.aborted === true) throw signal.reason
124
+ }
125
+
126
+ /**
127
+ * Make one request and return its body, mapping every failure onto a typed error.
128
+ *
129
+ * Retries only a transport failure, and only because reads are idempotent. A response
130
+ * that arrived is never retried: the node has decided.
131
+ */
132
+ export async function requestText(url: string, options: RequestOptions): Promise<string> {
133
+ assertSchemeReachable(url)
134
+
135
+ const { signal } = options
136
+ let lastCause: unknown
137
+ for (let attempt = 0; attempt <= options.retries; attempt += 1) {
138
+ // An 'abort' listener never fires for a signal that is already aborted, so without
139
+ // this the request would go out anyway — and so would a retry after an abort.
140
+ throwIfAborted(signal)
141
+
142
+ // Composed by hand rather than with AbortSignal.any, which needs Node 20.3 and
143
+ // Safari 17.4 — too new for a library that supports Node 20. The caller's reason is
144
+ // passed on, so a fetch that honours it rejects with exactly what the caller aborted with.
145
+ const controller = new AbortController()
146
+ const onAbort = (): void => controller.abort(signal?.reason)
147
+ signal?.addEventListener('abort', onAbort)
148
+ let timedOut = false
149
+ const timer = setTimeout(() => {
150
+ timedOut = true
151
+ controller.abort()
152
+ }, options.timeoutMs)
153
+
154
+ try {
155
+ const response = await options.transport(url, {
156
+ signal: controller.signal,
157
+ ...(options.method === undefined ? {} : { method: options.method }),
158
+ ...(options.headers === undefined ? {} : { headers: options.headers }),
159
+ ...(options.body === undefined ? {} : { body: options.body }),
160
+ })
161
+
162
+ const body = await response.text()
163
+ if (!response.ok) {
164
+ const details = nodeDetailsOf(body)
165
+ throw nodeError(
166
+ `The node at ${url} answered HTTP ${response.status}` +
167
+ (details === undefined ? ' with no detail.' : `: ${JSON.stringify(details)}.`),
168
+ response.status,
169
+ details,
170
+ )
171
+ }
172
+ return body
173
+ } catch (cause) {
174
+ if (cause instanceof GeeqError) throw cause
175
+
176
+ // A caller-driven abort is theirs to observe, not ours to relabel.
177
+ if (signal?.aborted === true) throw signal.reason
178
+
179
+ if (timedOut) {
180
+ throw networkUnreachable(
181
+ `The node at ${url} did not answer within ${options.timeoutMs}ms. Raise ` +
182
+ 'timeoutMs if the node is simply slow, or try another node.',
183
+ cause,
184
+ )
185
+ }
186
+ lastCause = cause
187
+ } finally {
188
+ clearTimeout(timer)
189
+ signal?.removeEventListener('abort', onAbort)
190
+ }
191
+ }
192
+
193
+ const code = describeCause(lastCause)
194
+ throw networkUnreachable(
195
+ code === undefined
196
+ ? opaqueFailureMessage(url)
197
+ : `Could not reach the Geeq node at ${url}: ${code}.`,
198
+ lastCause,
199
+ )
200
+ }
201
+
202
+ /**
203
+ * Wait for `promise`, or stop waiting when `signal` aborts.
204
+ *
205
+ * For a request several callers share: aborting one caller's wait must not cancel the
206
+ * request out from under the others, so the request runs on without any caller's signal
207
+ * and each caller races its own against it.
208
+ *
209
+ * @internal
210
+ */
211
+ export function raceAbort<T>(promise: Promise<T>, signal: AbortSignal | undefined): Promise<T> {
212
+ if (signal === undefined) return promise
213
+ if (signal.aborted) return Promise.reject(signal.reason)
214
+ return new Promise<T>((resolve, reject) => {
215
+ const onAbort = (): void => reject(signal.reason)
216
+ signal.addEventListener('abort', onAbort, { once: true })
217
+ promise
218
+ .then(resolve, reject)
219
+ .finally(() => signal.removeEventListener('abort', onAbort))
220
+ })
221
+ }
@@ -0,0 +1,94 @@
1
+ import { socketUnreachable } from '../errors'
2
+
3
+ /** `readyState` of an open connection. The same number in browsers and in `ws`. */
4
+ export const WEBSOCKET_OPEN = 1
5
+
6
+ /** What a WebSocket hands its `message` listeners, narrowed to the one field read. */
7
+ export interface GeeqWebSocketMessageEvent {
8
+ readonly data: unknown
9
+ }
10
+
11
+ /**
12
+ * A WebSocket, narrowed to what this SDK uses.
13
+ *
14
+ * Deliberately structural, like {@link GeeqTransport}: a browser `WebSocket`, Node 22's
15
+ * global one and the `ws` package's class all satisfy it with no adapter, and so does a
16
+ * scripted fake in a test. Listeners are attached with `addEventListener` rather than
17
+ * `onmessage` and friends because a method is compared bivariantly, which is what lets
18
+ * each implementation's own event types through.
19
+ */
20
+ export interface GeeqWebSocketLike {
21
+ readonly readyState: number
22
+ send(data: string): void
23
+ close(code?: number, reason?: string): void
24
+ addEventListener(type: 'open', listener: (event: unknown) => void): void
25
+ addEventListener(type: 'message', listener: (event: GeeqWebSocketMessageEvent) => void): void
26
+ addEventListener(type: 'close', listener: (event: unknown) => void): void
27
+ addEventListener(type: 'error', listener: (event: unknown) => void): void
28
+ }
29
+
30
+ /**
31
+ * How the SDK opens a socket.
32
+ *
33
+ * `(url) => new WebSocket(url)` is the whole contract. Pass one to use the `ws` package on
34
+ * Node 20, to add headers or a TLS agent there, or to script a connection in a test.
35
+ */
36
+ export type GeeqWebSocketFactory = (url: string) => GeeqWebSocketLike
37
+
38
+ /**
39
+ * `new globalThis.WebSocket(url)`, resolved at call time.
40
+ *
41
+ * Never a stored reference, for the same reason as `defaultTransport`: a test that swaps
42
+ * the global after import must still be honoured. Node 20 has no global WebSocket at all,
43
+ * and a library that supports Node 20 cannot assume one, so its absence is reported with
44
+ * the one-line fix rather than as a `TypeError`.
45
+ */
46
+ export const defaultWebSocketFactory: GeeqWebSocketFactory = (url) => {
47
+ const WebSocketClass = (globalThis as { WebSocket?: new (url: string) => GeeqWebSocketLike })
48
+ .WebSocket
49
+ if (typeof WebSocketClass !== 'function') {
50
+ throw socketUnreachable(
51
+ 'This runtime has no global WebSocket — Node 20 does not, Node 22 and every browser ' +
52
+ 'do. Pass one explicitly: `webSocket: (url) => new WebSocket(url)` with ' +
53
+ '`import WebSocket from "ws"`.',
54
+ )
55
+ }
56
+ return new WebSocketClass(url)
57
+ }
58
+
59
+ /**
60
+ * The one socket failure a browser can be certain about before trying.
61
+ *
62
+ * An https page may not open a `ws://` socket; the browser refuses it outright and reports
63
+ * nothing useful, so the handshake would only turn a knowable cause into an opaque one.
64
+ */
65
+ export function assertSocketSchemeReachable(url: string): void {
66
+ const location = (globalThis as { location?: { protocol?: string } }).location
67
+ if (location?.protocol !== 'https:') return
68
+
69
+ let scheme: string
70
+ try {
71
+ scheme = new URL(url).protocol
72
+ } catch {
73
+ return
74
+ }
75
+ if (scheme !== 'ws:') return
76
+
77
+ throw socketUnreachable(
78
+ `This page is served over https and the socket URL ${url} is ws://, so the browser ` +
79
+ 'will block it as mixed content before it connects. Use the node\'s wss:// URL ' +
80
+ '(port 2082 by default), or serve this page over http while developing.',
81
+ )
82
+ }
83
+
84
+ /** The causes a browser cannot tell apart when a handshake fails, with the fix for each. */
85
+ export function opaqueSocketFailureMessage(url: string): string {
86
+ return (
87
+ `Could not open the Geeq node socket at ${url}. A browser reports a failed handshake ` +
88
+ 'with no detail, so this is one of four things: (1) the node\'s TLS certificate is ' +
89
+ 'not trusted, which is common because nodes are addressed by bare IP — open the ' +
90
+ "node's https URL in a tab to see the interstitial; (2) this page is https and the " +
91
+ 'socket is ws://, which browsers block; (3) the URL is wrong — the path is /ws/, with ' +
92
+ 'the trailing slash, on the same port as the HTTP API; or (4) the node is down.'
93
+ )
94
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The single place the SDK reaches into the WebAssembly engine.
3
+ *
4
+ * Every binding is imported by name and re-exported as a real identifier, never
5
+ * looked up by string. A string-keyed lookup here would break under any consumer
6
+ * minifier that renames module bindings, and it would fail silently for exactly
7
+ * one code path — the failure mode this indirection exists to prevent.
8
+ */
9
+ export {
10
+ Keypair as WasmKeypair,
11
+ WalletAddress as WasmWalletAddress,
12
+ RecordType as WasmRecordType,
13
+ CoinType as WasmCoinType,
14
+ verify as wasmVerify,
15
+ mnemonicToSeed as wasmMnemonicToSeed,
16
+ generateSeedId as wasmGenerateSeedId,
17
+ // A proxy's account id, derived from its admin's public key and an index exactly as the
18
+ // Geeq wallet derives it. See src/tx/proxy.ts.
19
+ generateIrn as wasmGenerateIrn,
20
+ IrnRecordType as WasmIrnRecordType,
21
+ // Transaction canonicalisation. The engine cannot *build* a transaction — every
22
+ // transaction class it exposes has a private constructor — but it is the only
23
+ // thing that can turn a Cap'n Proto message into the flattened byte form the
24
+ // chain signs over, so structures are built with capnp and flattened here.
25
+ Payload as WasmPayload,
26
+ // Both forms of the transaction are needed and they are NOT interchangeable. The
27
+ // engine flattens `UserAppTxData` without the signature — that is the preimage the
28
+ // signature is made over — and `UserAppTx` with it, which is what the chain hashes to
29
+ // identify the transaction. See src/tx/attestation.ts.
30
+ UserAppTx as WasmUserAppTx,
31
+ UserAppTxData as WasmUserAppTxData,
32
+ // The validation-layer transaction — coin transfers — has the same two forms, but the
33
+ // node identifies it by the Data (signature-excluded) flatten, not the signed one.
34
+ // See src/tx/transfer.ts.
35
+ UserValTx as WasmUserValTx,
36
+ UserValTxData as WasmUserValTxData,
37
+ AttestationSubgroupData as WasmAttestationSubgroupData,
38
+ } from '../wasm/browser/geeq_core'
@@ -0,0 +1,49 @@
1
+ import { GeeqError, notInitialized } from '../errors'
2
+ import type { InitOptions } from '../types'
3
+ import { loadWasm } from './wasm-loader.browser'
4
+
5
+ let inflight: Promise<void> | null = null
6
+ let ready = false
7
+
8
+ /**
9
+ * Load the Geeq engine. Idempotent and single-flight: concurrent callers share one
10
+ * load, and repeat calls after success resolve immediately.
11
+ *
12
+ * You rarely need to call this yourself — every async entry point in the SDK awaits
13
+ * it first. Call it at startup if you would rather pay the cost up front.
14
+ */
15
+ export function initGeeq(options: InitOptions = {}): Promise<void> {
16
+ if (ready) return Promise.resolve()
17
+
18
+ if (inflight === null) {
19
+ inflight = loadWasm(options.wasm)
20
+ .then(() => {
21
+ ready = true
22
+ })
23
+ .catch((cause: unknown) => {
24
+ // Clear the cached promise so a later call can retry with, say, an
25
+ // explicit `wasm` source rather than being stuck with the first failure.
26
+ inflight = null
27
+ throw new GeeqError(
28
+ 'init-failed',
29
+ 'Could not load the Geeq WebAssembly engine. If your bundler does not emit ' +
30
+ 'geeq_core_bg.wasm automatically, pass its URL as initGeeq({ wasm }).',
31
+ { cause },
32
+ )
33
+ })
34
+ }
35
+
36
+ return inflight
37
+ }
38
+
39
+ /** True once the engine is loaded and the synchronous APIs are usable. */
40
+ export function isGeeqReady(): boolean {
41
+ return ready
42
+ }
43
+
44
+ /** @internal Guard for synchronous APIs that require a loaded engine. */
45
+ export function assertReady(what: string): void {
46
+ if (!ready) {
47
+ throw notInitialized(what)
48
+ }
49
+ }
@@ -0,0 +1,22 @@
1
+ import initWasm from '../wasm/browser/geeq_core'
2
+ import type { WasmSource } from '../types'
3
+
4
+ /**
5
+ * Browser loader: finds the engine's .wasm file and starts it.
6
+ *
7
+ * If the app passes its own `source`, that is used. Otherwise it is the .wasm file
8
+ * that ships next to this script in `dist/browser/`.
9
+ *
10
+ * `new URL('./geeq_core_bg.wasm', import.meta.url)` means "geeq_core_bg.wasm in the
11
+ * same folder as this file". Keep it written exactly this way: Vite, webpack, Rollup
12
+ * and Parcel spot this pattern and copy the .wasm into the app's build for you.
13
+ *
14
+ * The Node build swaps this module for `wasm-loader.node.ts` at bundle time.
15
+ */
16
+ export async function loadWasm(source?: WasmSource): Promise<void> {
17
+ const wasm = source ?? new URL('./geeq_core_bg.wasm', import.meta.url)
18
+
19
+ // `module_or_path` is the option name wasm-bindgen expects. Passing `wasm`
20
+ // directly instead also works, but prints a deprecation warning.
21
+ await initWasm({ module_or_path: wasm })
22
+ }
@@ -0,0 +1,16 @@
1
+ import { readFile } from 'node:fs/promises'
2
+ import initWasm from '../wasm/browser/geeq_core'
3
+ import type { WasmSource } from '../types'
4
+
5
+ /**
6
+ * Node loader: same job as the browser loader, but Node's `fetch` cannot read
7
+ * local files, so the .wasm is read off disk here and passed in as bytes.
8
+ * Used by the Node build; the test suite swaps in its own stub (see jest.config.js).
9
+ */
10
+ export async function loadWasm(source?: WasmSource): Promise<void> {
11
+ const wasm = source ?? (await readFile(new URL('./geeq_core_bg.wasm', import.meta.url)))
12
+
13
+ // `module_or_path` is the option name wasm-bindgen expects. Passing `wasm`
14
+ // directly instead also works, but prints a deprecation warning.
15
+ await initWasm({ module_or_path: wasm })
16
+ }