@reddb-io/browser 0.0.0-stage → 0.1.0-alpha.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/LICENSE ADDED
@@ -0,0 +1,111 @@
1
+ Business Source License 1.1
2
+
3
+ Parameters
4
+
5
+ Licensor: RedDB.io <hello@reddb.io>
6
+
7
+ Licensed Work: RedDB
8
+ The Licensed Work is (c) 2026 RedDB.io
9
+
10
+ Additional Use Grant: You may make use of the Licensed Work, provided that
11
+ you do not use the Licensed Work for a Database
12
+ Service.
13
+
14
+ A "Database Service" is a commercial offering that
15
+ allows third parties (other than your employees and
16
+ individual contractors) to access the functionality
17
+ of the Licensed Work by creating tables, documents,
18
+ graphs, vectors, key-value entries, queues, or
19
+ time-series whose schemas and/or data are determined
20
+ by those third parties, or by querying, storing, or
21
+ retrieving data determined by those third parties,
22
+ whether as a hosted, managed, "as-a-service",
23
+ database-as-a-service (DBaaS), platform-as-a-service
24
+ (PaaS), software-as-a-service (SaaS), or cloud
25
+ database product.
26
+
27
+ For clarity, the Additional Use Grant permits, among
28
+ other things, the following uses, none of which
29
+ constitute a Database Service: personal use; internal
30
+ use within your own organization; research and
31
+ educational use; development and testing; use in
32
+ open source projects; and self-hosting the Licensed
33
+ Work for your own organization's own data.
34
+
35
+ Change Date: 2030-06-22
36
+
37
+ Change License: GNU Affero General Public License version 3.0
38
+
39
+ For information about alternative licensing arrangements for the Licensed
40
+ Work, please contact hello@reddb.io.
41
+
42
+ Notice
43
+
44
+ Business Source License 1.1
45
+
46
+ Terms
47
+
48
+ The Licensor hereby grants you the right to copy, modify, create derivative
49
+ works, redistribute, and make non-production use of the Licensed Work. The
50
+ Licensor may make an Additional Use Grant, above, permitting limited
51
+ production use.
52
+
53
+ Effective on the Change Date, or the fourth anniversary of the first publicly
54
+ available distribution of a specific version of the Licensed Work under this
55
+ License, whichever comes first, the Licensor hereby grants you rights under
56
+ the terms of the Change License, and the rights granted in the paragraph
57
+ above terminate.
58
+
59
+ If your use of the Licensed Work does not comply with the requirements
60
+ currently in effect as described in this License, you must purchase a
61
+ commercial license from the Licensor, its affiliated entities, or authorized
62
+ resellers, or you must refrain from using the Licensed Work.
63
+
64
+ All copies of the original and modified Licensed Work, and derivative works
65
+ of the Licensed Work, are subject to this License. This License applies
66
+ separately for each version of the Licensed Work and the Change Date may vary
67
+ for each version of the Licensed Work released by Licensor.
68
+
69
+ You must conspicuously display this License on each original or modified copy
70
+ of the Licensed Work. If you receive the Licensed Work in original or
71
+ modified form from a third party, the terms and conditions set forth in this
72
+ License apply to your use of that work.
73
+
74
+ Any use of the Licensed Work in violation of this License will automatically
75
+ terminate your rights under this License for the current and all other
76
+ versions of the Licensed Work.
77
+
78
+ This License does not grant you any right in any trademark or logo of
79
+ Licensor or its affiliates (provided that you may use a trademark or logo of
80
+ Licensor as expressly required by this License).
81
+
82
+ TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
83
+ AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
84
+ EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
85
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
86
+ TITLE.
87
+
88
+ MariaDB hereby grants you permission to use this License's text to license
89
+ your works, and to refer to it using the trademark "Business Source License",
90
+ as long as you comply with the Covenants of Licensor below.
91
+
92
+ Covenants of Licensor
93
+
94
+ In consideration of the right to use this License's text and the "Business
95
+ Source License" name and trademark, Licensor covenants to MariaDB, and to all
96
+ other recipients of the licensed work to be provided by Licensor:
97
+
98
+ 1. To specify as the Change License the GPL Version 2.0 or any later version,
99
+ or a license that is compatible with GPL Version 2.0 or a later version,
100
+ where "compatible" means that software provided under the Change License can
101
+ be included in a program with software provided under GPL Version 2.0 or a
102
+ later version. Licensor may specify additional Change Licenses without
103
+ limitation.
104
+
105
+ 2. To either: (a) specify an additional grant of rights to use that does not
106
+ impose any additional restriction on the right granted in this License, as
107
+ the Additional Use Grant; or (b) insert the text "None".
108
+
109
+ 3. To specify a Change Date.
110
+
111
+ 4. Not to modify this License in any other way.
package/README.md CHANGED
@@ -1,3 +1,261 @@
1
- # Temporary Holding Version
1
+ # RedDB Browser — experimental persistence and query engine
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ This experimental package is the first distribution of RedDB Browser. It runs the
4
+ canonical Rust `.rdb` artifact publication/recovery code, RQL parser and shared
5
+ scalar evaluator in a dedicated WebAssembly worker, persisting through a real
6
+ OPFS sync access handle.
7
+ It requires a secure context, module workers, OPFS and Web Locks.
8
+
9
+ The target is a fully offline local database with a JavaScript/TypeScript API.
10
+ The Rust/WASM engine ships with the package; local operation must not require a
11
+ RedDB server, account, CDN or external API. Applications bundle the JS, worker
12
+ and WASM assets and make them available locally or through their own offline
13
+ application cache. Sync is optional and must never gate local reads or writes.
14
+
15
+ **It is not a database SDK yet.** It executes storage-free scalar SELECTs;
16
+ queries over stored data, five-model local transactions, synchronization,
17
+ conflict resolution and forwarding between tabs remain pending. It is published on the `experimental` npm channel and does not replace the remote `@reddb-io/client`.
18
+
19
+ ## Use from JS/TS
20
+
21
+ See the [application guide](https://github.com/reddb-io/reddb/blob/main/docs/clients/browser.md) for installation,
22
+ Vite/React/Vue/Svelte, plain JavaScript, Worker ownership and offline assets.
23
+
24
+ ```sh
25
+ npm add @reddb-io/browser@experimental
26
+ ```
27
+
28
+ ```ts
29
+ import { openArtifact } from '@reddb-io/browser'
30
+ import RedDBWorker from '@reddb-io/browser/worker?worker'
31
+
32
+ const artifact = await openArtifact({ name: 'my-app', worker: new RedDBWorker() })
33
+ try {
34
+ console.log(await artifact.executeScalar('SELECT 42 AS answer'))
35
+ } finally {
36
+ await artifact.close()
37
+ }
38
+ ```
39
+
40
+ Consumers receive built JS/Worker/WASM assets and TypeScript declarations; they
41
+ need no Rust toolchain. The commands below are for engine/package contributors.
42
+
43
+ ## Build and verify
44
+
45
+ Install the repository-pinned Rust toolchain, its `wasm32-unknown-unknown` target,
46
+ and `wasm-bindgen-cli` **0.2.120**. Install workspace dependencies with pnpm.
47
+
48
+ ```sh
49
+ pnpm --filter @reddb-io/browser build
50
+ pnpm --filter @reddb-io/browser exec playwright install --with-deps chromium firefox webkit
51
+ pnpm --filter @reddb-io/browser test
52
+ cargo test --locked -p reddb-io-file
53
+ cargo test --locked -p reddb-io-engine
54
+ ```
55
+
56
+ The `WASM_BINDGEN` environment variable can point at the pinned CLI executable.
57
+ `REDDB_BROWSER_BROWSERS=chromium` restricts the local browser test matrix; CI runs all
58
+ three engines. Generated JS/WASM bindings are ignored and rebuilt from Rust.
59
+
60
+ The tests probe the browser's actual native APIs. If a port lacks synchronous
61
+ OPFS (currently Playwright's WPE WebKit runner), its persistence tests explicitly
62
+ report skips; the unsupported-browser error path still runs. I/O or format
63
+ failures never cause a skip. Chromium and Firefox exercise persistence and
64
+ cross-tab ownership. WebKit persistence and real Safari/iOS qualification remain
65
+ pending; a green CI run does not imply that coverage.
66
+
67
+ The conformance tests compile a separate test-only WASM artifact and execute the same
68
+ assertions as the native engine tests: scalar goldens/errors, compiled row
69
+ filtering/sorting/projection, joins, aggregation, windows, set operations and
70
+ cancellation. These do not require OPFS, so they run even on browser ports
71
+ without synchronous storage. The same worker also checks the shared 16 KiB page
72
+ layout, checksum corruption detection, freelist trunk recovery, pinned-page
73
+ eviction and dirty writeback. Native unit tests for these primitives run from
74
+ `reddb-engine`; native module paths retain the same types, with pager restart
75
+ coverage. The native pager now shares byte I/O, sealed superblock writes and
76
+ in-file double-write staging/recovery over `VfsFile` with WASM. Shared fixtures
77
+ interrupt each write/flush in page publication, root publication and recovery,
78
+ and check short reads, checksums and bounded frames. A test-only OPFS worker
79
+ recovers a torn page after a full browser restart with networking disabled
80
+ and the HTTP host disconnected.
81
+ Those fixtures are excluded from the production SDK. Pager orchestration now
82
+ also lives in `reddb-engine`: cached I/O, deferred reclamation, freelist and
83
+ allocation fences, physical-tail reservation and checkpoint markers. The native
84
+ adapter supplies file locks, encryption and its WAL writer through a synchronous
85
+ barrier; portable pagers without a WAL reject nonzero page LSNs. Shared native/WASM
86
+ fixtures cover failed allocation fences and WAL barriers. The OPFS restart fixture
87
+ uses this complete pager and verifies checkpoint completion through another fresh
88
+ pager. The production JS/TS API is unchanged: storage-backed queries, local
89
+ transactions, runtime WAL/MVCC integration and encryption qualification remain pending.
90
+ Native differential tests additionally compare
91
+ the scalar corpus to actual `RedDBRuntime` query results.
92
+
93
+ The shared core also owns the native B-tree/cursor, compressed value layout and
94
+ overflow chains. Native APIs alias these implementations through a page-access
95
+ contract; the browser executes them in test-only WASM fixtures. Conformance covers
96
+ splits, scans, seeks, updates/deletes, large values and reclamation. An additional
97
+ OPFS test mutates a native-created tree, restarts the browser with the HTTP host
98
+ stopped, and reopens the exported file natively. The fixture includes native
99
+ encoded entities as well as opaque B-tree values. These lower-level records do
100
+ not supply runtime collection schemas or atomic database transactions. No raw
101
+ tree API is added to the production SDK.
102
+
103
+ ## Current query contract
104
+
105
+ ```js
106
+ const artifact = await openArtifact({ name: 'example' })
107
+ await artifact.executeScalar('SELECT 2 + 3 * 4 AS answer')
108
+ // { columns: ['answer'], rows: [[14]] }
109
+ await artifact.close()
110
+ ```
111
+
112
+ `executeScalar` uses the shared Rust evaluator, built-in type/operator/function
113
+ catalogs and output labels. It accepts storage-free scalar SELECT expressions
114
+ and literal LIMIT/OFFSET; unsupported clauses and stored-data queries fail
115
+ explicitly. It never reads or changes the artifact. Integer values outside JS's
116
+ safe range retain canonical `$int`/`$uint` tags instead of rounding. This small
117
+ entrypoint is an execution milestone, not the full RQL/storage runtime.
118
+
119
+ ## Current storage contract
120
+
121
+ `openArtifact({ name })` opens or creates `reddb-browser/<name>.rdb` inside the
122
+ origin's private filesystem. It exposes `append`, `readWal`, `checkpoint`,
123
+ `readSnapshot`, `generation`, `validateQuery` and `close`. These are artifact
124
+ primitives: WAL payloads are native binary records, and checkpoint images are
125
+ complete native store snapshots. Passing arbitrary SQL as a payload does not
126
+ execute it. The caller must incorporate committed WAL into a checkpoint image.
127
+
128
+ The browser and native file authority share the same format codecs and write
129
+ ordering. Acknowledgement follows both the WAL flush and root publication flush.
130
+ After a storage operation fails, that Rust handle rejects further I/O until it
131
+ is reopened and recovered. Existing corrupt files are preserved and reported;
132
+ they are never silently replaced. A second open returns `FILE_BUSY`; closing or
133
+ terminating the owning worker releases the lock. Invalid input and parse errors
134
+ do not invalidate the storage handle.
135
+
136
+ The host must provide exclusive ownership when using Rust's `EmbeddedRdbFile`
137
+ directly. The browser wrapper enforces this with Web Locks and OPFS's default
138
+ exclusive access handle. The prototype does not use relaxed/read-write-unsafe
139
+ access, emulate file locks, or require `SharedArrayBuffer` or isolation headers.
140
+
141
+ OPFS is subject to origin quota, storage eviction and explicit site-data clearing.
142
+ Applications can request persistent storage through `navigator.storage.persist()`;
143
+ these primitives do not promise that a browser will grant it or sync after closing.
144
+
145
+ ## Offline asset loading
146
+
147
+ The SDK does not register a service worker or control application updates. A
148
+ PWA should include `index.js`, `worker.js`, `wasm/reddb_wasm.js` and
149
+ `wasm/reddb_wasm_bg.wasm` from `src/` in its own versioned application cache,
150
+ along with its application shell. Those assets must remain available together
151
+ after installation. A locally packaged host can serve the same assets locally.
152
+ HTTPS or a trusted localhost context is required for OPFS and Web Locks.
153
+
154
+ The browser suite includes a test host's service worker and restarts the entire
155
+ browser with networking disabled. It reloads the JS/worker/WASM assets, recovers
156
+ OPFS data, writes again and executes a scalar query without reaching the HTTP
157
+ host. This verifies the current artifact/scalar surface offline; storage-backed
158
+ queries and application transactions still require the engine work below.
159
+
160
+ ## Shared transaction state
161
+
162
+ The canonical native MVCC snapshot manager, visibility predicate, serializable
163
+ read/write conflict tracking and per-connection transaction state now live in
164
+ `reddb-engine::transaction`. The server uses the same types and algorithms.
165
+ Native/WASM fixtures cover snapshot versus read-committed reads, aborted writers,
166
+ savepoint sub-XIDs, failed commit preparation, publication-guard lifetime,
167
+ retained vacuum horizons, checkpoint exclusions, unique-key reservations and
168
+ serializable row/vault predicates with tenant separation. On Chromium/Firefox,
169
+ the test host also caches this separate conformance artifact and reruns the suite
170
+ after a full offline browser restart with the HTTP listener stopped.
171
+
172
+ These are volatile engine assertions in a test-only WASM artifact. They do not
173
+ expose transactions through the JS/TS SDK or qualify durable browser transaction
174
+ recovery. Storage-backed dispatch, write conflict validation, WAL publication and
175
+ recovery still require the runtime integration below.
176
+
177
+ ## Shared entities and read visibility
178
+
179
+ The native `UnifiedEntity` model (rows/documents, graph, vectors, time series,
180
+ queues and auxiliary references/embeddings) now lives in
181
+ `reddb-engine::storage::entity`. Native paths re-export those same types.
182
+ Entity timestamps retain Unix **seconds**; WASM obtains the host wall clock from
183
+ `Date.now`, while query timing continues to use the monotonic performance clock.
184
+ The browser test worker independently checks entity/embedding/reference timestamps
185
+ against JS time, including after the offline restart.
186
+
187
+ `SnapshotContext`, own/aborted subtransaction visibility, logical-ID SSI read
188
+ tracking and table-row version selection are also shared. The native store
189
+ supplies candidates through `TableRowVersionStore`; its existing dispatch,
190
+ auth/tenant/moderation gates and statement-frame lifetime remain in the host.
191
+ Native persisted-row tests cover both dump and paged reopen through the shared
192
+ entity/visibility selector. Six new native/WASM cases exercise the representation
193
+ and read rules, repeated offline on Chromium/Firefox. They use in-memory fixtures
194
+ in WASM, not browser-persisted collections.
195
+
196
+ Native value tags, checked decoding and Zstd compression now have one owner in
197
+ `reddb-file::native_value`; the logical entity adapter lives in
198
+ `reddb-engine::storage::entity_codec`. Native dump, paged-record and WAL paths
199
+ use that same adapter. A frozen corpus produced by the original native writer
200
+ checks all store versions V1–V12 and all value variants, including exact integers,
201
+ compressed text/blob values, document bodies, MVCC fields and auxiliary data.
202
+ Four native/WASM conformance cases compare the original bytes and reject
203
+ truncated values, invalid lengths/tags, inconsistent compressed lengths and
204
+ excessive array nesting. Arrays use a checked cursor and an explicit decoding
205
+ stack bounded by the existing JSON nesting limit. This is not a general
206
+ browser memory-budget qualification.
207
+
208
+ The OPFS tree fixture now imports eight encoded native entities, decodes and
209
+ updates their sequence/timestamps in WASM, restarts the browser offline with the
210
+ HTTP host stopped, and exports for native verification. It retains opaque
211
+ metadata bytes without interpreting native metadata policy. A separate native
212
+ dump test checks the complete entity corpus and actual native metadata. These
213
+ remain test-only records without application collection schemas or transactional
214
+ publication. Storage-backed execution and durable runtime transaction/WAL
215
+ integration are still needed before JS/TS can query collections. No file bytes,
216
+ format versions or public application APIs change.
217
+
218
+ ## Shared query records and materialization
219
+
220
+ `reddb-engine::query::unified` now owns the native `UnifiedRecord`/`UnifiedResult`
221
+ representation, interned system keys, matched graph records/paths and query
222
+ statistics. Native module paths re-export the exact same types. Native stored
223
+ graph nodes retain their conversion through `From<&StoredNode>`; their storage
224
+ and graph executor remain native.
225
+
226
+ `reddb-engine::query::records` owns the existing full/lean/projected/universal
227
+ entity materializers, by value and by reference, including catalog-schema
228
+ fallback, binary-document field promotion, managed timestamps, logical `rid`,
229
+ KV/document capabilities, graph endpoints, typed time-series tags and identity
230
+ hints. The native runtime calls these same functions. Representation does not
231
+ supply authorization, tenancy, visibility, secret handling, catalog discovery or
232
+ transaction publication; those policies remain with the caller/runtime.
233
+
234
+ A frozen pre-extraction native oracle checks 112 results (eight paths over
235
+ 14 entity shapes), preserving existing differences between materialization
236
+ paths. Six relocated native record-layout property tests and four shared
237
+ native/WASM cases cover schema overwrite/overflow, projections, document bodies,
238
+ typed tags, graph representation and identities. The offline OPFS fixture now
239
+ materializes full/projected records from its persisted entities after restart
240
+ and verifies their fields before native export. Native integration compares
241
+ shared records with actual stored-data query results after dump and paged reopen.
242
+
243
+ These are internal/test-only engine paths. The public JS/TS API still exposes
244
+ artifact operations and storage-free scalar SELECTs; stored-data dispatch,
245
+ durable runtime transactions/WAL and application collection catalogs remain
246
+ unfinished. No file format, wire envelope or public browser API changes here.
247
+
248
+ ## Remaining work toward the approved Browser design
249
+
250
+ 1. Extract the remaining storage-coupled executor/runtime into the shared portable engine and
251
+ preserve native query/transaction semantics for tables, documents, KV, graph
252
+ and vectors. Port clocks, crypto randomness and task scheduling.
253
+ 2. Build the application-facing SDK, local transaction/outbox atomicity, change
254
+ observation, and one-owner request forwarding between tabs.
255
+ 3. Implement versioned, policy-filtered snapshot and resumable change protocols
256
+ in `reddb-wire` and the server. Revalidate normal authorization/RLS on upload;
257
+ preserve transaction grouping, idempotency, stable graph references and deletes.
258
+ 4. Add explicit conflict resolution, expired-cursor rebootstrap without losing
259
+ pending intent, identity separation, offline assets and the storage-status UI.
260
+ 5. Validate two-device sync and engine conformance, Safari/iOS on real devices,
261
+ quota/failure cases, bundle size, startup, memory and query performance.
package/package.json CHANGED
@@ -1,6 +1,44 @@
1
1
  {
2
2
  "name": "@reddb-io/browser",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0-alpha.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "description": "Experimental RedDB browser persistence and shared scalar execution; stored-data queries and sync remain pending.",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./src/index.d.ts",
10
+ "import": "./src/index.js"
11
+ },
12
+ "./worker": "./src/worker.js",
13
+ "./package.json": "./package.json"
14
+ },
15
+ "scripts": {
16
+ "build": "node scripts/build.mjs",
17
+ "test": "node scripts/build-test.mjs && node --test test/browser.test.mjs",
18
+ "prepack": "node scripts/verify-dist.mjs",
19
+ "test:distribution": "node --test test/distribution.test.mjs"
20
+ },
21
+ "devDependencies": {
22
+ "playwright": "1.61.1",
23
+ "vite": "8.3.5",
24
+ "typescript": "7.0.2"
25
+ },
26
+ "license": "BUSL-1.1",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/reddb-io/reddb.git",
30
+ "directory": "packages/browser"
31
+ },
32
+ "homepage": "https://github.com/reddb-io/reddb/tree/main/packages/browser",
33
+ "types": "./src/index.d.ts",
34
+ "files": [
35
+ "src/",
36
+ "scripts/verify-dist.mjs",
37
+ "README.md",
38
+ "LICENSE"
39
+ ],
40
+ "publishConfig": {
41
+ "access": "public",
42
+ "tag": "experimental"
43
+ }
44
+ }
@@ -0,0 +1,12 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { dirname, resolve } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+
5
+ const directory = resolve(dirname(fileURLToPath(import.meta.url)), '..')
6
+ for (const path of ['src/index.js', 'src/index.d.ts', 'src/worker.js', 'src/wasm/reddb_wasm.js', 'LICENSE']) {
7
+ if (!readFileSync(resolve(directory, path)).length) throw new Error(`Missing browser distribution file: ${path}`)
8
+ }
9
+ const wasm = readFileSync(resolve(directory, 'src/wasm/reddb_wasm_bg.wasm'))
10
+ if (wasm.length < 8 || !wasm.subarray(0, 8).equals(Buffer.from([0, 97, 115, 109, 1, 0, 0, 0]))) {
11
+ throw new Error('Build the browser WASM before packing: pnpm --filter @reddb-io/browser build')
12
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ /** Experimental persistence and scalar execution; stored-data queries and sync remain pending. */
2
+ export class BrowserError extends Error {
3
+ readonly code: string
4
+ constructor(code: string, message: string)
5
+ }
6
+
7
+ export interface Artifact {
8
+ /** Parse with the canonical RQL front-end; does not execute the statement. */
9
+ validateQuery(sql: string): Promise<void>
10
+ /** Storage-free scalar SELECT only; does not read or mutate the artifact. */
11
+ executeScalar(sql: string): Promise<{ columns: string[]; rows: unknown[][] }>
12
+ generation(): Promise<string>
13
+ readWal(): Promise<Uint8Array[]>
14
+ readSnapshot(): Promise<Uint8Array | undefined>
15
+ /** Low-level native WAL payloads, not SQL commands. */
16
+ append(payloads: Uint8Array[]): Promise<void>
17
+ /** Complete native store snapshot; the caller must fold in the committed WAL. */
18
+ checkpoint(snapshot: Uint8Array, minWalBytes?: number): Promise<void>
19
+ close(): Promise<void>
20
+ }
21
+
22
+ /** Open/create an exclusively owned OPFS .rdb file. Another owner returns FILE_BUSY. */
23
+ export function openArtifact(options: {
24
+ name: string
25
+ /** Fresh module Worker supplied by a bundler (e.g. Vite ?worker). The artifact owns its lifetime. */
26
+ worker?: Worker
27
+ }): Promise<Artifact>
package/src/index.js ADDED
@@ -0,0 +1,79 @@
1
+ /** Experimental artifact storage and storage-free scalar execution. */
2
+
3
+ export class BrowserError extends Error {
4
+ constructor(code, message) {
5
+ super(message)
6
+ this.name = 'BrowserError'
7
+ this.code = code
8
+ }
9
+ }
10
+
11
+ /** Open an exclusively owned, canonical .rdb artifact in an OPFS worker. */
12
+ export async function openArtifact({ name, worker: suppliedWorker } = {}) {
13
+ if (typeof name !== 'string' || !/^[a-zA-Z0-9_-][a-zA-Z0-9_.-]{0,79}$/.test(name)) {
14
+ throw new BrowserError('INVALID_NAME', 'Artifact names must contain 1–80 letters, digits, dots, underscores or hyphens')
15
+ }
16
+ if (!globalThis.isSecureContext || !globalThis.Worker || !globalThis.navigator?.storage?.getDirectory || !globalThis.navigator?.locks) {
17
+ throw new BrowserError('STORAGE_UNSUPPORTED', 'RedDB Browser requires HTTPS, module Workers, OPFS and Web Locks')
18
+ }
19
+
20
+ if (suppliedWorker !== undefined && !(suppliedWorker instanceof Worker)) {
21
+ throw new BrowserError('INVALID_ARGUMENT', 'worker must be a dedicated Worker instance')
22
+ }
23
+ const worker = suppliedWorker ?? new Worker(new URL('./worker.js', import.meta.url), { type: 'module' })
24
+ const pending = new Map()
25
+ let sequence = 0
26
+ let closed = false
27
+
28
+ function fail(error) {
29
+ closed = true
30
+ for (const request of pending.values()) request.reject(error)
31
+ pending.clear()
32
+ worker.terminate()
33
+ }
34
+
35
+ worker.onmessage = ({ data }) => {
36
+ const request = pending.get(data.id)
37
+ if (!request) return
38
+ pending.delete(data.id)
39
+ if (data.error) request.reject(new BrowserError(data.error.code, data.error.message))
40
+ else request.resolve(data.result)
41
+ }
42
+ worker.onerror = (event) => fail(new BrowserError('WORKER_FAILED', event.message || 'RedDB storage worker failed'))
43
+ worker.onmessageerror = () => fail(new BrowserError('WORKER_FAILED', 'Unable to decode a storage worker response'))
44
+
45
+ function call(method, params = {}) {
46
+ if (closed) return Promise.reject(new BrowserError('CLOSED', 'This artifact handle is closed'))
47
+ const id = ++sequence
48
+ return new Promise((resolve, reject) => {
49
+ pending.set(id, { resolve, reject })
50
+ try { worker.postMessage({ id, method, params }) }
51
+ catch (error) { pending.delete(id); reject(error) }
52
+ })
53
+ }
54
+
55
+ try { await call('open', { name }) }
56
+ catch (error) { fail(error); throw error }
57
+
58
+ let closing
59
+ return {
60
+ validateQuery: (sql) => call('validateQuery', { sql }),
61
+ executeScalar: (sql) => call('executeScalar', { sql }),
62
+ generation: () => call('generation'),
63
+ readWal: () => call('readWal'),
64
+ readSnapshot: () => call('readSnapshot'),
65
+ append: (payloads) => call('append', { payloads }),
66
+ checkpoint: (snapshot, minWalBytes = 0) => call('checkpoint', { snapshot, minWalBytes }),
67
+ close() {
68
+ if (!closing) {
69
+ closing = closed ? Promise.resolve() : call('close').finally(() => {
70
+ closed = true
71
+ worker.terminate()
72
+ })
73
+ // Reject calls issued after close(), including calls in the same turn.
74
+ closed = true
75
+ }
76
+ return closing
77
+ },
78
+ }
79
+ }
@@ -0,0 +1,83 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+
4
+ /**
5
+ * Canonical `.rdb` artifact I/O. The worker owns and closes the underlying
6
+ * exclusive access handle; freeing this wrapper never unlocks it early.
7
+ */
8
+ export class BrowserArtifact {
9
+ private constructor();
10
+ free(): void;
11
+ [Symbol.dispose](): void;
12
+ append(payloads: Array<any>): void;
13
+ checkpoint(snapshot: Uint8Array, min_wal_bytes: number): void;
14
+ static create(handle: any, name: string, created_at_unix_ms: number): BrowserArtifact;
15
+ /**
16
+ * String-valued counters preserve all u64 bits across JavaScript.
17
+ */
18
+ generation(): string;
19
+ static open(handle: any, name: string): BrowserArtifact;
20
+ readSnapshot(): Uint8Array | undefined;
21
+ readWal(): Array<any>;
22
+ }
23
+
24
+ /**
25
+ * Experimental storage-free execution, using the native scalar evaluator.
26
+ */
27
+ export function executeScalar(sql: string): any;
28
+
29
+ export function validateQuery(sql: string): void;
30
+
31
+ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
32
+
33
+ export interface InitOutput {
34
+ readonly memory: WebAssembly.Memory;
35
+ readonly __wbg_browserartifact_free: (a: number, b: number) => void;
36
+ readonly browserartifact_append: (a: number, b: any) => [number, number];
37
+ readonly browserartifact_checkpoint: (a: number, b: number, c: number, d: number) => [number, number];
38
+ readonly browserartifact_create: (a: any, b: number, c: number, d: number) => [number, number, number];
39
+ readonly browserartifact_generation: (a: number) => [number, number, number, number];
40
+ readonly browserartifact_open: (a: any, b: number, c: number) => [number, number, number];
41
+ readonly browserartifact_readSnapshot: (a: number) => [number, number, number, number];
42
+ readonly browserartifact_readWal: (a: number) => [number, number, number];
43
+ readonly executeScalar: (a: number, b: number) => [number, number, number];
44
+ readonly validateQuery: (a: number, b: number) => [number, number];
45
+ readonly rust_zstd_wasm_shim_calloc: (a: number, b: number) => number;
46
+ readonly rust_zstd_wasm_shim_free: (a: number) => void;
47
+ readonly rust_zstd_wasm_shim_malloc: (a: number) => number;
48
+ readonly rust_zstd_wasm_shim_memcmp: (a: number, b: number, c: number) => number;
49
+ readonly rust_zstd_wasm_shim_memcpy: (a: number, b: number, c: number) => number;
50
+ readonly rust_zstd_wasm_shim_memmove: (a: number, b: number, c: number) => number;
51
+ readonly rust_zstd_wasm_shim_memset: (a: number, b: number, c: number) => number;
52
+ readonly rust_zstd_wasm_shim_qsort: (a: number, b: number, c: number, d: number) => void;
53
+ readonly __wbindgen_malloc: (a: number, b: number) => number;
54
+ readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
55
+ readonly __wbindgen_exn_store: (a: number) => void;
56
+ readonly __externref_table_alloc: () => number;
57
+ readonly __wbindgen_externrefs: WebAssembly.Table;
58
+ readonly __externref_table_dealloc: (a: number) => void;
59
+ readonly __wbindgen_free: (a: number, b: number, c: number) => void;
60
+ readonly __wbindgen_start: () => void;
61
+ }
62
+
63
+ export type SyncInitInput = BufferSource | WebAssembly.Module;
64
+
65
+ /**
66
+ * Instantiates the given `module`, which can either be bytes or
67
+ * a precompiled `WebAssembly.Module`.
68
+ *
69
+ * @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
70
+ *
71
+ * @returns {InitOutput}
72
+ */
73
+ export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
74
+
75
+ /**
76
+ * If `module_or_path` is {RequestInfo} or {URL}, makes a request and
77
+ * for everything else, calls `WebAssembly.instantiate` directly.
78
+ *
79
+ * @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
80
+ *
81
+ * @returns {Promise<InitOutput>}
82
+ */
83
+ export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;
@@ -0,0 +1,485 @@
1
+ /* @ts-self-types="./reddb_wasm.d.ts" */
2
+
3
+ /**
4
+ * Canonical `.rdb` artifact I/O. The worker owns and closes the underlying
5
+ * exclusive access handle; freeing this wrapper never unlocks it early.
6
+ */
7
+ export class BrowserArtifact {
8
+ static __wrap(ptr) {
9
+ const obj = Object.create(BrowserArtifact.prototype);
10
+ obj.__wbg_ptr = ptr;
11
+ BrowserArtifactFinalization.register(obj, obj.__wbg_ptr, obj);
12
+ return obj;
13
+ }
14
+ __destroy_into_raw() {
15
+ const ptr = this.__wbg_ptr;
16
+ this.__wbg_ptr = 0;
17
+ BrowserArtifactFinalization.unregister(this);
18
+ return ptr;
19
+ }
20
+ free() {
21
+ const ptr = this.__destroy_into_raw();
22
+ wasm.__wbg_browserartifact_free(ptr, 0);
23
+ }
24
+ /**
25
+ * @param {Array<any>} payloads
26
+ */
27
+ append(payloads) {
28
+ const ret = wasm.browserartifact_append(this.__wbg_ptr, payloads);
29
+ if (ret[1]) {
30
+ throw takeFromExternrefTable0(ret[0]);
31
+ }
32
+ }
33
+ /**
34
+ * @param {Uint8Array} snapshot
35
+ * @param {number} min_wal_bytes
36
+ */
37
+ checkpoint(snapshot, min_wal_bytes) {
38
+ const ptr0 = passArray8ToWasm0(snapshot, wasm.__wbindgen_malloc);
39
+ const len0 = WASM_VECTOR_LEN;
40
+ const ret = wasm.browserartifact_checkpoint(this.__wbg_ptr, ptr0, len0, min_wal_bytes);
41
+ if (ret[1]) {
42
+ throw takeFromExternrefTable0(ret[0]);
43
+ }
44
+ }
45
+ /**
46
+ * @param {any} handle
47
+ * @param {string} name
48
+ * @param {number} created_at_unix_ms
49
+ * @returns {BrowserArtifact}
50
+ */
51
+ static create(handle, name, created_at_unix_ms) {
52
+ const ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
53
+ const len0 = WASM_VECTOR_LEN;
54
+ const ret = wasm.browserartifact_create(handle, ptr0, len0, created_at_unix_ms);
55
+ if (ret[2]) {
56
+ throw takeFromExternrefTable0(ret[1]);
57
+ }
58
+ return BrowserArtifact.__wrap(ret[0]);
59
+ }
60
+ /**
61
+ * String-valued counters preserve all u64 bits across JavaScript.
62
+ * @returns {string}
63
+ */
64
+ generation() {
65
+ let deferred2_0;
66
+ let deferred2_1;
67
+ try {
68
+ const ret = wasm.browserartifact_generation(this.__wbg_ptr);
69
+ var ptr1 = ret[0];
70
+ var len1 = ret[1];
71
+ if (ret[3]) {
72
+ ptr1 = 0; len1 = 0;
73
+ throw takeFromExternrefTable0(ret[2]);
74
+ }
75
+ deferred2_0 = ptr1;
76
+ deferred2_1 = len1;
77
+ return getStringFromWasm0(ptr1, len1);
78
+ } finally {
79
+ wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
80
+ }
81
+ }
82
+ /**
83
+ * @param {any} handle
84
+ * @param {string} name
85
+ * @returns {BrowserArtifact}
86
+ */
87
+ static open(handle, name) {
88
+ const ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
89
+ const len0 = WASM_VECTOR_LEN;
90
+ const ret = wasm.browserartifact_open(handle, ptr0, len0);
91
+ if (ret[2]) {
92
+ throw takeFromExternrefTable0(ret[1]);
93
+ }
94
+ return BrowserArtifact.__wrap(ret[0]);
95
+ }
96
+ /**
97
+ * @returns {Uint8Array | undefined}
98
+ */
99
+ readSnapshot() {
100
+ const ret = wasm.browserartifact_readSnapshot(this.__wbg_ptr);
101
+ if (ret[3]) {
102
+ throw takeFromExternrefTable0(ret[2]);
103
+ }
104
+ let v1;
105
+ if (ret[0] !== 0) {
106
+ v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
107
+ wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
108
+ }
109
+ return v1;
110
+ }
111
+ /**
112
+ * @returns {Array<any>}
113
+ */
114
+ readWal() {
115
+ const ret = wasm.browserartifact_readWal(this.__wbg_ptr);
116
+ if (ret[2]) {
117
+ throw takeFromExternrefTable0(ret[1]);
118
+ }
119
+ return takeFromExternrefTable0(ret[0]);
120
+ }
121
+ }
122
+ if (Symbol.dispose) BrowserArtifact.prototype[Symbol.dispose] = BrowserArtifact.prototype.free;
123
+
124
+ /**
125
+ * Experimental storage-free execution, using the native scalar evaluator.
126
+ * @param {string} sql
127
+ * @returns {any}
128
+ */
129
+ export function executeScalar(sql) {
130
+ const ptr0 = passStringToWasm0(sql, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
131
+ const len0 = WASM_VECTOR_LEN;
132
+ const ret = wasm.executeScalar(ptr0, len0);
133
+ if (ret[2]) {
134
+ throw takeFromExternrefTable0(ret[1]);
135
+ }
136
+ return takeFromExternrefTable0(ret[0]);
137
+ }
138
+
139
+ /**
140
+ * @param {string} sql
141
+ */
142
+ export function validateQuery(sql) {
143
+ const ptr0 = passStringToWasm0(sql, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
144
+ const len0 = WASM_VECTOR_LEN;
145
+ const ret = wasm.validateQuery(ptr0, len0);
146
+ if (ret[1]) {
147
+ throw takeFromExternrefTable0(ret[0]);
148
+ }
149
+ }
150
+ function __wbg_get_imports() {
151
+ const import0 = {
152
+ __proto__: null,
153
+ __wbg___wbindgen_string_get_965592073e5d848c: function(arg0, arg1) {
154
+ const obj = arg1;
155
+ const ret = typeof(obj) === 'string' ? obj : undefined;
156
+ var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
157
+ var len1 = WASM_VECTOR_LEN;
158
+ getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
159
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
160
+ },
161
+ __wbg___wbindgen_throw_9c75d47bf9e7731e: function(arg0, arg1) {
162
+ throw new Error(getStringFromWasm0(arg0, arg1));
163
+ },
164
+ __wbg_flush_8a925e51a05f983d: function() { return handleError(function (arg0) {
165
+ arg0.flush();
166
+ }, arguments); },
167
+ __wbg_getSize_ef1f7ad2400c4958: function() { return handleError(function (arg0) {
168
+ const ret = arg0.getSize();
169
+ return ret;
170
+ }, arguments); },
171
+ __wbg_get_unchecked_be562b1421656321: function(arg0, arg1) {
172
+ const ret = arg0[arg1 >>> 0];
173
+ return ret;
174
+ },
175
+ __wbg_instanceof_Error_5e21755e9d9cbee5: function(arg0) {
176
+ let result;
177
+ try {
178
+ result = arg0 instanceof Error;
179
+ } catch (_) {
180
+ result = false;
181
+ }
182
+ const ret = result;
183
+ return ret;
184
+ },
185
+ __wbg_instanceof_Uint8Array_57d77acd50e4c44d: function(arg0) {
186
+ let result;
187
+ try {
188
+ result = arg0 instanceof Uint8Array;
189
+ } catch (_) {
190
+ result = false;
191
+ }
192
+ const ret = result;
193
+ return ret;
194
+ },
195
+ __wbg_length_0a6ce016dc1460b0: function(arg0) {
196
+ const ret = arg0.length;
197
+ return ret;
198
+ },
199
+ __wbg_length_ba3c032602efe310: function(arg0) {
200
+ const ret = arg0.length;
201
+ return ret;
202
+ },
203
+ __wbg_message_d5628ca19de920d3: function(arg0) {
204
+ const ret = arg0.message;
205
+ return ret;
206
+ },
207
+ __wbg_new_3baa8d9866155c79: function() {
208
+ const ret = new Array();
209
+ return ret;
210
+ },
211
+ __wbg_new_c9ea13ea803a692e: function(arg0, arg1) {
212
+ const ret = new Error(getStringFromWasm0(arg0, arg1));
213
+ return ret;
214
+ },
215
+ __wbg_new_from_slice_5a173c243af2e823: function(arg0, arg1) {
216
+ const ret = new Uint8Array(getArrayU8FromWasm0(arg0, arg1));
217
+ return ret;
218
+ },
219
+ __wbg_parse_342d5616e14beccc: function() { return handleError(function (arg0, arg1) {
220
+ const ret = JSON.parse(getStringFromWasm0(arg0, arg1));
221
+ return ret;
222
+ }, arguments); },
223
+ __wbg_prototypesetcall_fd4050e806e1d519: function(arg0, arg1, arg2) {
224
+ Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), arg2);
225
+ },
226
+ __wbg_push_60a5366c0bb22a7d: function(arg0, arg1) {
227
+ const ret = arg0.push(arg1);
228
+ return ret;
229
+ },
230
+ __wbg_readAt_38b95ee3bea009e0: function() { return handleError(function (arg0, arg1, arg2, arg3) {
231
+ const ret = arg0.readAt(getArrayU8FromWasm0(arg1, arg2), arg3);
232
+ return ret;
233
+ }, arguments); },
234
+ __wbg_set_5337f8ac82364a3f: function() { return handleError(function (arg0, arg1, arg2) {
235
+ const ret = Reflect.set(arg0, arg1, arg2);
236
+ return ret;
237
+ }, arguments); },
238
+ __wbg_truncate_d9e55b46e14f9493: function() { return handleError(function (arg0, arg1) {
239
+ arg0.truncate(arg1);
240
+ }, arguments); },
241
+ __wbg_writeAt_1aacc9360d02366e: function() { return handleError(function (arg0, arg1, arg2, arg3) {
242
+ const ret = arg0.writeAt(getArrayU8FromWasm0(arg1, arg2), arg3);
243
+ return ret;
244
+ }, arguments); },
245
+ __wbindgen_cast_0000000000000001: function(arg0, arg1) {
246
+ // Cast intrinsic for `Ref(String) -> Externref`.
247
+ const ret = getStringFromWasm0(arg0, arg1);
248
+ return ret;
249
+ },
250
+ __wbindgen_init_externref_table: function() {
251
+ const table = wasm.__wbindgen_externrefs;
252
+ const offset = table.grow(4);
253
+ table.set(0, undefined);
254
+ table.set(offset + 0, undefined);
255
+ table.set(offset + 1, null);
256
+ table.set(offset + 2, true);
257
+ table.set(offset + 3, false);
258
+ },
259
+ };
260
+ return {
261
+ __proto__: null,
262
+ "./reddb_wasm_bg.js": import0,
263
+ };
264
+ }
265
+
266
+ const BrowserArtifactFinalization = (typeof FinalizationRegistry === 'undefined')
267
+ ? { register: () => {}, unregister: () => {} }
268
+ : new FinalizationRegistry(ptr => wasm.__wbg_browserartifact_free(ptr, 1));
269
+
270
+ function addToExternrefTable0(obj) {
271
+ const idx = wasm.__externref_table_alloc();
272
+ wasm.__wbindgen_externrefs.set(idx, obj);
273
+ return idx;
274
+ }
275
+
276
+ function getArrayU8FromWasm0(ptr, len) {
277
+ ptr = ptr >>> 0;
278
+ return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
279
+ }
280
+
281
+ let cachedDataViewMemory0 = null;
282
+ function getDataViewMemory0() {
283
+ if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
284
+ cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
285
+ }
286
+ return cachedDataViewMemory0;
287
+ }
288
+
289
+ function getStringFromWasm0(ptr, len) {
290
+ return decodeText(ptr >>> 0, len);
291
+ }
292
+
293
+ let cachedUint8ArrayMemory0 = null;
294
+ function getUint8ArrayMemory0() {
295
+ if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
296
+ cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
297
+ }
298
+ return cachedUint8ArrayMemory0;
299
+ }
300
+
301
+ function handleError(f, args) {
302
+ try {
303
+ return f.apply(this, args);
304
+ } catch (e) {
305
+ const idx = addToExternrefTable0(e);
306
+ wasm.__wbindgen_exn_store(idx);
307
+ }
308
+ }
309
+
310
+ function isLikeNone(x) {
311
+ return x === undefined || x === null;
312
+ }
313
+
314
+ function passArray8ToWasm0(arg, malloc) {
315
+ const ptr = malloc(arg.length * 1, 1) >>> 0;
316
+ getUint8ArrayMemory0().set(arg, ptr / 1);
317
+ WASM_VECTOR_LEN = arg.length;
318
+ return ptr;
319
+ }
320
+
321
+ function passStringToWasm0(arg, malloc, realloc) {
322
+ if (realloc === undefined) {
323
+ const buf = cachedTextEncoder.encode(arg);
324
+ const ptr = malloc(buf.length, 1) >>> 0;
325
+ getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
326
+ WASM_VECTOR_LEN = buf.length;
327
+ return ptr;
328
+ }
329
+
330
+ let len = arg.length;
331
+ let ptr = malloc(len, 1) >>> 0;
332
+
333
+ const mem = getUint8ArrayMemory0();
334
+
335
+ let offset = 0;
336
+
337
+ for (; offset < len; offset++) {
338
+ const code = arg.charCodeAt(offset);
339
+ if (code > 0x7F) break;
340
+ mem[ptr + offset] = code;
341
+ }
342
+ if (offset !== len) {
343
+ if (offset !== 0) {
344
+ arg = arg.slice(offset);
345
+ }
346
+ ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
347
+ const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
348
+ const ret = cachedTextEncoder.encodeInto(arg, view);
349
+
350
+ offset += ret.written;
351
+ ptr = realloc(ptr, len, offset, 1) >>> 0;
352
+ }
353
+
354
+ WASM_VECTOR_LEN = offset;
355
+ return ptr;
356
+ }
357
+
358
+ function takeFromExternrefTable0(idx) {
359
+ const value = wasm.__wbindgen_externrefs.get(idx);
360
+ wasm.__externref_table_dealloc(idx);
361
+ return value;
362
+ }
363
+
364
+ let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
365
+ cachedTextDecoder.decode();
366
+ const MAX_SAFARI_DECODE_BYTES = 2146435072;
367
+ let numBytesDecoded = 0;
368
+ function decodeText(ptr, len) {
369
+ numBytesDecoded += len;
370
+ if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {
371
+ cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
372
+ cachedTextDecoder.decode();
373
+ numBytesDecoded = len;
374
+ }
375
+ return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
376
+ }
377
+
378
+ const cachedTextEncoder = new TextEncoder();
379
+
380
+ if (!('encodeInto' in cachedTextEncoder)) {
381
+ cachedTextEncoder.encodeInto = function (arg, view) {
382
+ const buf = cachedTextEncoder.encode(arg);
383
+ view.set(buf);
384
+ return {
385
+ read: arg.length,
386
+ written: buf.length
387
+ };
388
+ };
389
+ }
390
+
391
+ let WASM_VECTOR_LEN = 0;
392
+
393
+ let wasmModule, wasmInstance, wasm;
394
+ function __wbg_finalize_init(instance, module) {
395
+ wasmInstance = instance;
396
+ wasm = instance.exports;
397
+ wasmModule = module;
398
+ cachedDataViewMemory0 = null;
399
+ cachedUint8ArrayMemory0 = null;
400
+ wasm.__wbindgen_start();
401
+ return wasm;
402
+ }
403
+
404
+ async function __wbg_load(module, imports) {
405
+ if (typeof Response === 'function' && module instanceof Response) {
406
+ if (typeof WebAssembly.instantiateStreaming === 'function') {
407
+ try {
408
+ return await WebAssembly.instantiateStreaming(module, imports);
409
+ } catch (e) {
410
+ const validResponse = module.ok && expectedResponseType(module.type);
411
+
412
+ if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') {
413
+ console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
414
+
415
+ } else { throw e; }
416
+ }
417
+ }
418
+
419
+ const bytes = await module.arrayBuffer();
420
+ return await WebAssembly.instantiate(bytes, imports);
421
+ } else {
422
+ const instance = await WebAssembly.instantiate(module, imports);
423
+
424
+ if (instance instanceof WebAssembly.Instance) {
425
+ return { instance, module };
426
+ } else {
427
+ return instance;
428
+ }
429
+ }
430
+
431
+ function expectedResponseType(type) {
432
+ switch (type) {
433
+ case 'basic': case 'cors': case 'default': return true;
434
+ }
435
+ return false;
436
+ }
437
+ }
438
+
439
+ function initSync(module) {
440
+ if (wasm !== undefined) return wasm;
441
+
442
+
443
+ if (module !== undefined) {
444
+ if (Object.getPrototypeOf(module) === Object.prototype) {
445
+ ({module} = module)
446
+ } else {
447
+ console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
448
+ }
449
+ }
450
+
451
+ const imports = __wbg_get_imports();
452
+ if (!(module instanceof WebAssembly.Module)) {
453
+ module = new WebAssembly.Module(module);
454
+ }
455
+ const instance = new WebAssembly.Instance(module, imports);
456
+ return __wbg_finalize_init(instance, module);
457
+ }
458
+
459
+ async function __wbg_init(module_or_path) {
460
+ if (wasm !== undefined) return wasm;
461
+
462
+
463
+ if (module_or_path !== undefined) {
464
+ if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
465
+ ({module_or_path} = module_or_path)
466
+ } else {
467
+ console.warn('using deprecated parameters for the initialization function; pass a single object instead')
468
+ }
469
+ }
470
+
471
+ if (module_or_path === undefined) {
472
+ module_or_path = new URL('reddb_wasm_bg.wasm', import.meta.url);
473
+ }
474
+ const imports = __wbg_get_imports();
475
+
476
+ if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
477
+ module_or_path = fetch(module_or_path);
478
+ }
479
+
480
+ const { instance, module } = await __wbg_load(await module_or_path, imports);
481
+
482
+ return __wbg_finalize_init(instance, module);
483
+ }
484
+
485
+ export { initSync, __wbg_init as default };
Binary file
@@ -0,0 +1,29 @@
1
+ /* tslint:disable */
2
+ /* eslint-disable */
3
+ export const memory: WebAssembly.Memory;
4
+ export const __wbg_browserartifact_free: (a: number, b: number) => void;
5
+ export const browserartifact_append: (a: number, b: any) => [number, number];
6
+ export const browserartifact_checkpoint: (a: number, b: number, c: number, d: number) => [number, number];
7
+ export const browserartifact_create: (a: any, b: number, c: number, d: number) => [number, number, number];
8
+ export const browserartifact_generation: (a: number) => [number, number, number, number];
9
+ export const browserartifact_open: (a: any, b: number, c: number) => [number, number, number];
10
+ export const browserartifact_readSnapshot: (a: number) => [number, number, number, number];
11
+ export const browserartifact_readWal: (a: number) => [number, number, number];
12
+ export const executeScalar: (a: number, b: number) => [number, number, number];
13
+ export const validateQuery: (a: number, b: number) => [number, number];
14
+ export const rust_zstd_wasm_shim_calloc: (a: number, b: number) => number;
15
+ export const rust_zstd_wasm_shim_free: (a: number) => void;
16
+ export const rust_zstd_wasm_shim_malloc: (a: number) => number;
17
+ export const rust_zstd_wasm_shim_memcmp: (a: number, b: number, c: number) => number;
18
+ export const rust_zstd_wasm_shim_memcpy: (a: number, b: number, c: number) => number;
19
+ export const rust_zstd_wasm_shim_memmove: (a: number, b: number, c: number) => number;
20
+ export const rust_zstd_wasm_shim_memset: (a: number, b: number, c: number) => number;
21
+ export const rust_zstd_wasm_shim_qsort: (a: number, b: number, c: number, d: number) => void;
22
+ export const __wbindgen_malloc: (a: number, b: number) => number;
23
+ export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
24
+ export const __wbindgen_exn_store: (a: number) => void;
25
+ export const __externref_table_alloc: () => number;
26
+ export const __wbindgen_externrefs: WebAssembly.Table;
27
+ export const __externref_table_dealloc: (a: number) => void;
28
+ export const __wbindgen_free: (a: number, b: number, c: number) => void;
29
+ export const __wbindgen_start: () => void;
package/src/worker.js ADDED
@@ -0,0 +1,106 @@
1
+ import init, { BrowserArtifact, validateQuery, executeScalar } from './wasm/reddb_wasm.js'
2
+
3
+ let artifact
4
+ let access
5
+ let release
6
+ let ownership
7
+ let queue = Promise.resolve()
8
+
9
+ function error(code, message) {
10
+ return Object.assign(new Error(message), { code })
11
+ }
12
+
13
+ function synchronous(value) {
14
+ if (value && typeof value.then === 'function') {
15
+ value.catch(() => {})
16
+ throw error('STORAGE_UNSUPPORTED', 'This browser implements asynchronous OPFS access; synchronous access is required')
17
+ }
18
+ return value
19
+ }
20
+
21
+ async function open(name) {
22
+ await init()
23
+ await new Promise((resolve, reject) => {
24
+ ownership = navigator.locks.request(`reddb-browser:${name}`, { ifAvailable: true }, async (lock) => {
25
+ if (!lock) throw error('FILE_BUSY', 'This artifact is already open in another worker or tab')
26
+ try {
27
+ const root = await navigator.storage.getDirectory()
28
+ const directory = await root.getDirectoryHandle('reddb-browser', { create: true })
29
+ const file = await directory.getFileHandle(`${name}.rdb`, { create: true })
30
+ if (typeof file.createSyncAccessHandle !== 'function') {
31
+ throw error('STORAGE_UNSUPPORTED', 'This browser does not support synchronous OPFS access in a dedicated worker')
32
+ }
33
+ access = await file.createSyncAccessHandle()
34
+ const fileIo = {
35
+ readAt: (buffer, offset) => synchronous(access.read(buffer, { at: offset })),
36
+ writeAt: (buffer, offset) => synchronous(access.write(buffer, { at: offset })),
37
+ getSize: () => synchronous(access.getSize()),
38
+ truncate: (size) => synchronous(access.truncate(size)),
39
+ flush: () => synchronous(access.flush()),
40
+ }
41
+ artifact = fileIo.getSize() === 0
42
+ ? BrowserArtifact.create(fileIo, name, Date.now())
43
+ : BrowserArtifact.open(fileIo, name)
44
+ const lifetime = new Promise((resolveRelease) => { release = resolveRelease })
45
+ resolve()
46
+ await lifetime
47
+ } catch (failure) {
48
+ reject(failure)
49
+ throw failure
50
+ } finally {
51
+ artifact?.free()
52
+ artifact = undefined
53
+ if (access) synchronous(access.close())
54
+ access = undefined
55
+ }
56
+ })
57
+ // Observe early lock/open failures, including ifAvailable returning null.
58
+ ownership.catch(reject)
59
+ })
60
+ }
61
+
62
+ function bytes(value) {
63
+ if (!(value instanceof Uint8Array)) throw error('INVALID_ARGUMENT', 'Expected a Uint8Array')
64
+ return value
65
+ }
66
+
67
+ async function execute(method, params) {
68
+ if (method === 'open') return open(params.name)
69
+ if (!artifact) throw error('CLOSED', 'The artifact is not open')
70
+ switch (method) {
71
+ case 'validateQuery':
72
+ if (typeof params.sql !== 'string') throw error('INVALID_ARGUMENT', 'Expected an RQL string')
73
+ return validateQuery(params.sql)
74
+ case 'executeScalar':
75
+ if (typeof params.sql !== 'string') throw error('INVALID_ARGUMENT', 'Expected an RQL string')
76
+ return executeScalar(params.sql)
77
+ case 'generation': return artifact.generation()
78
+ case 'readWal': return artifact.readWal()
79
+ case 'readSnapshot': return artifact.readSnapshot()
80
+ case 'append':
81
+ if (!Array.isArray(params.payloads)) throw error('INVALID_ARGUMENT', 'Expected an array of WAL payloads')
82
+ return artifact.append(params.payloads.map(bytes))
83
+ case 'checkpoint':
84
+ if (!Number.isInteger(params.minWalBytes) || params.minWalBytes < 0 || params.minWalBytes > 0xffffffff) {
85
+ throw error('INVALID_ARGUMENT', 'WAL capacity must be an unsigned 32-bit integer')
86
+ }
87
+ return artifact.checkpoint(bytes(params.snapshot), params.minWalBytes)
88
+ case 'close':
89
+ release()
90
+ await ownership
91
+ return
92
+ default: throw error('INVALID_ARGUMENT', `Unknown artifact operation: ${method}`)
93
+ }
94
+ }
95
+
96
+ self.onmessage = ({ data: { id, method, params } }) => {
97
+ // Opening is asynchronous; all following operations wait for ownership.
98
+ queue = queue.then(async () => {
99
+ try {
100
+ const result = await execute(method, params)
101
+ self.postMessage({ id, result })
102
+ } catch (failure) {
103
+ self.postMessage({ id, error: { code: failure.code || 'ARTIFACT_ERROR', message: failure.message || String(failure) } })
104
+ }
105
+ })
106
+ }