@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 +111 -0
- package/README.md +260 -2
- package/package.json +42 -4
- package/scripts/verify-dist.mjs +12 -0
- package/src/index.d.ts +27 -0
- package/src/index.js +79 -0
- package/src/wasm/reddb_wasm.d.ts +83 -0
- package/src/wasm/reddb_wasm.js +485 -0
- package/src/wasm/reddb_wasm_bg.wasm +0 -0
- package/src/wasm/reddb_wasm_bg.wasm.d.ts +29 -0
- package/src/worker.js +106 -0
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
|
-
#
|
|
1
|
+
# RedDB Browser — experimental persistence and query engine
|
|
2
2
|
|
|
3
|
-
This
|
|
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
|
|
4
|
-
"
|
|
5
|
-
"
|
|
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
|
+
}
|