@reddb-io/browser 0.0.0-stage → 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +111 -0
- package/README.md +334 -2
- package/package.json +44 -4
- package/scripts/verify-dist.mjs +12 -0
- package/src/index.d.ts +63 -0
- package/src/index.js +150 -0
- package/src/protocol.js +10 -0
- package/src/wasm/reddb_wasm.d.ts +89 -0
- package/src/wasm/reddb_wasm.js +497 -0
- package/src/wasm/reddb_wasm_bg.wasm +0 -0
- package/src/wasm/reddb_wasm_bg.wasm.d.ts +30 -0
- package/src/worker.js +127 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/** Shared Rust/WASM runtime and experimental artifact storage. */
|
|
2
|
+
import { BrowserError, RUNTIME_ABI_VERSION, VALUE_ENCODING } from './protocol.js'
|
|
3
|
+
export { BrowserError } from './protocol.js'
|
|
4
|
+
|
|
5
|
+
function createClient(suppliedWorker) {
|
|
6
|
+
if (!globalThis.Worker) throw new BrowserError('RUNTIME_UNSUPPORTED', 'RedDB requires module Workers and WebAssembly')
|
|
7
|
+
if (suppliedWorker !== undefined && !(suppliedWorker instanceof Worker)) {
|
|
8
|
+
throw new BrowserError('INVALID_ARGUMENT', 'worker must be a dedicated Worker instance')
|
|
9
|
+
}
|
|
10
|
+
const worker = suppliedWorker ?? new Worker(new URL('./worker.js', import.meta.url), { type: 'module' })
|
|
11
|
+
const pending = new Map()
|
|
12
|
+
let sequence = 0
|
|
13
|
+
let closed = false
|
|
14
|
+
let terminated = false
|
|
15
|
+
|
|
16
|
+
function shutdown(error = new BrowserError('CLOSED', 'This runtime handle is closed')) {
|
|
17
|
+
closed = true
|
|
18
|
+
for (const request of pending.values()) request.reject(error)
|
|
19
|
+
pending.clear()
|
|
20
|
+
if (!terminated) worker.terminate()
|
|
21
|
+
terminated = true
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
worker.onmessage = ({ data }) => {
|
|
25
|
+
if (terminated) return
|
|
26
|
+
if (data?.abiVersion !== RUNTIME_ABI_VERSION) {
|
|
27
|
+
shutdown(new BrowserError('VERSION_MISMATCH', 'Worker runtime ABI does not match this SDK'))
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
if (!Number.isSafeInteger(data.id) || data.id < 1) {
|
|
31
|
+
shutdown(new BrowserError('WORKER_FAILED', 'Invalid runtime response identifier'))
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
const request = pending.get(data.id)
|
|
35
|
+
if (!request) return
|
|
36
|
+
pending.delete(data.id)
|
|
37
|
+
if (data.error) request.reject(new BrowserError(data.error.code, data.error.message))
|
|
38
|
+
else request.resolve(data.result)
|
|
39
|
+
}
|
|
40
|
+
worker.onerror = (event) => shutdown(new BrowserError('WORKER_FAILED', event.message || 'RedDB worker failed'))
|
|
41
|
+
worker.onmessageerror = () => shutdown(new BrowserError('WORKER_FAILED', 'Unable to decode a worker response'))
|
|
42
|
+
|
|
43
|
+
function call(method, params = {}) {
|
|
44
|
+
if (closed) return Promise.reject(new BrowserError('CLOSED', 'This runtime handle is closed'))
|
|
45
|
+
const id = ++sequence
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
pending.set(id, { resolve, reject })
|
|
48
|
+
try { worker.postMessage({ abiVersion: RUNTIME_ABI_VERSION, id, method, params }) }
|
|
49
|
+
catch (error) { pending.delete(id); reject(error) }
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const ready = call('initialize').then(info => {
|
|
54
|
+
if (info?.abiVersion !== RUNTIME_ABI_VERSION || info.valueEncoding !== VALUE_ENCODING
|
|
55
|
+
|| info.capabilities?.queryValidation !== true || info.capabilities?.scalarQueries !== true) {
|
|
56
|
+
throw new BrowserError('VERSION_MISMATCH', 'WASM runtime capabilities do not match this SDK')
|
|
57
|
+
}
|
|
58
|
+
return info
|
|
59
|
+
}).catch(error => { shutdown(error); throw error })
|
|
60
|
+
ready.catch(() => {}) // A bridge can be closed before its first invocation.
|
|
61
|
+
let closing
|
|
62
|
+
return {
|
|
63
|
+
ready, call, shutdown,
|
|
64
|
+
closeFile() {
|
|
65
|
+
if (!closing) {
|
|
66
|
+
closing = closed ? Promise.resolve() : call('close').finally(() => shutdown())
|
|
67
|
+
closed = true
|
|
68
|
+
}
|
|
69
|
+
return closing
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function runtimeMethods(client) {
|
|
75
|
+
const query = (method, sql) => typeof sql === 'string'
|
|
76
|
+
? client.call(method, { sql })
|
|
77
|
+
: Promise.reject(new BrowserError('INVALID_ARGUMENT', 'Expected an RQL string'))
|
|
78
|
+
return {
|
|
79
|
+
info: () => client.call('info'),
|
|
80
|
+
validateQuery: sql => query('validateQuery', sql),
|
|
81
|
+
executeScalar: sql => query('executeScalar', sql),
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Storage-independent Rust/WASM operations; does not open an OPFS file. */
|
|
86
|
+
export async function createRuntime({ worker } = {}) {
|
|
87
|
+
const client = createClient(worker)
|
|
88
|
+
await client.ready
|
|
89
|
+
return { ...runtimeMethods(client), close() { client.shutdown(); return Promise.resolve() } }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** JSON-safe boundary for Dart, .NET and other JS-interop hosts. */
|
|
93
|
+
export function createInteropRuntime({ worker } = {}) {
|
|
94
|
+
let client
|
|
95
|
+
let ready
|
|
96
|
+
let closed = false
|
|
97
|
+
try {
|
|
98
|
+
client = createClient(worker)
|
|
99
|
+
ready = client.ready.then(() => runtimeMethods(client))
|
|
100
|
+
} catch (error) { ready = Promise.reject(error) }
|
|
101
|
+
ready.catch(() => {})
|
|
102
|
+
return {
|
|
103
|
+
async invoke(method, params = {}) {
|
|
104
|
+
try {
|
|
105
|
+
if (closed) throw new BrowserError('CLOSED', 'This runtime handle is closed')
|
|
106
|
+
const runtime = await ready
|
|
107
|
+
if (!params || typeof params !== 'object' || Array.isArray(params)) {
|
|
108
|
+
throw new BrowserError('INVALID_ARGUMENT', 'Expected an argument object')
|
|
109
|
+
}
|
|
110
|
+
let result
|
|
111
|
+
switch (method) {
|
|
112
|
+
case 'info': result = await runtime.info(); break
|
|
113
|
+
case 'validateQuery': result = await runtime.validateQuery(params.sql); break
|
|
114
|
+
case 'executeScalar': result = await runtime.executeScalar(params.sql); break
|
|
115
|
+
default: throw new BrowserError('INVALID_ARGUMENT', 'Unknown runtime operation')
|
|
116
|
+
}
|
|
117
|
+
return { ok: true, result: result ?? null }
|
|
118
|
+
} catch (error) {
|
|
119
|
+
return { ok: false, error: {
|
|
120
|
+
code: typeof error.code === 'string' ? error.code : 'RUNTIME_ERROR',
|
|
121
|
+
message: error.message || String(error),
|
|
122
|
+
} }
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
close() { closed = true; client?.shutdown() },
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Open an exclusively owned, canonical .rdb artifact in an OPFS worker. */
|
|
130
|
+
export async function openArtifact({ name, worker } = {}) {
|
|
131
|
+
if (typeof name !== 'string' || !/^[a-zA-Z0-9_-][a-zA-Z0-9_.-]{0,79}$/.test(name)) {
|
|
132
|
+
throw new BrowserError('INVALID_NAME', 'Artifact names must contain 1–80 letters, digits, dots, underscores or hyphens')
|
|
133
|
+
}
|
|
134
|
+
if (!globalThis.isSecureContext || !globalThis.Worker || !globalThis.navigator?.storage?.getDirectory || !globalThis.navigator?.locks) {
|
|
135
|
+
throw new BrowserError('STORAGE_UNSUPPORTED', 'RedDB Browser requires HTTPS, module Workers, OPFS and Web Locks')
|
|
136
|
+
}
|
|
137
|
+
const client = createClient(worker)
|
|
138
|
+
try { await client.ready; await client.call('open', { name }) }
|
|
139
|
+
catch (error) { client.shutdown(error); throw error }
|
|
140
|
+
const { call } = client
|
|
141
|
+
return {
|
|
142
|
+
...runtimeMethods(client),
|
|
143
|
+
generation: () => call('generation'),
|
|
144
|
+
readWal: () => call('readWal'),
|
|
145
|
+
readSnapshot: () => call('readSnapshot'),
|
|
146
|
+
append: (payloads) => call('append', { payloads }),
|
|
147
|
+
checkpoint: (snapshot, minWalBytes = 0) => call('checkpoint', { snapshot, minWalBytes }),
|
|
148
|
+
close: client.closeFile,
|
|
149
|
+
}
|
|
150
|
+
}
|
package/src/protocol.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
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
|
+
/**
|
|
30
|
+
* Contract shared by the JS worker and foreign-language browser adapters.
|
|
31
|
+
*/
|
|
32
|
+
export function runtimeInfo(): any;
|
|
33
|
+
|
|
34
|
+
export function validateQuery(sql: string): void;
|
|
35
|
+
|
|
36
|
+
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
|
37
|
+
|
|
38
|
+
export interface InitOutput {
|
|
39
|
+
readonly memory: WebAssembly.Memory;
|
|
40
|
+
readonly __wbg_browserartifact_free: (a: number, b: number) => void;
|
|
41
|
+
readonly browserartifact_append: (a: number, b: any) => [number, number];
|
|
42
|
+
readonly browserartifact_checkpoint: (a: number, b: number, c: number, d: number) => [number, number];
|
|
43
|
+
readonly browserartifact_create: (a: any, b: number, c: number, d: number) => [number, number, number];
|
|
44
|
+
readonly browserartifact_generation: (a: number) => [number, number, number, number];
|
|
45
|
+
readonly browserartifact_open: (a: any, b: number, c: number) => [number, number, number];
|
|
46
|
+
readonly browserartifact_readSnapshot: (a: number) => [number, number, number, number];
|
|
47
|
+
readonly browserartifact_readWal: (a: number) => [number, number, number];
|
|
48
|
+
readonly executeScalar: (a: number, b: number) => [number, number, number];
|
|
49
|
+
readonly runtimeInfo: () => [number, number, number];
|
|
50
|
+
readonly validateQuery: (a: number, b: number) => [number, number];
|
|
51
|
+
readonly rust_zstd_wasm_shim_calloc: (a: number, b: number) => number;
|
|
52
|
+
readonly rust_zstd_wasm_shim_free: (a: number) => void;
|
|
53
|
+
readonly rust_zstd_wasm_shim_malloc: (a: number) => number;
|
|
54
|
+
readonly rust_zstd_wasm_shim_memcmp: (a: number, b: number, c: number) => number;
|
|
55
|
+
readonly rust_zstd_wasm_shim_memcpy: (a: number, b: number, c: number) => number;
|
|
56
|
+
readonly rust_zstd_wasm_shim_memmove: (a: number, b: number, c: number) => number;
|
|
57
|
+
readonly rust_zstd_wasm_shim_memset: (a: number, b: number, c: number) => number;
|
|
58
|
+
readonly rust_zstd_wasm_shim_qsort: (a: number, b: number, c: number, d: number) => void;
|
|
59
|
+
readonly __wbindgen_malloc: (a: number, b: number) => number;
|
|
60
|
+
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
|
61
|
+
readonly __wbindgen_exn_store: (a: number) => void;
|
|
62
|
+
readonly __externref_table_alloc: () => number;
|
|
63
|
+
readonly __wbindgen_externrefs: WebAssembly.Table;
|
|
64
|
+
readonly __externref_table_dealloc: (a: number) => void;
|
|
65
|
+
readonly __wbindgen_free: (a: number, b: number, c: number) => void;
|
|
66
|
+
readonly __wbindgen_start: () => void;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type SyncInitInput = BufferSource | WebAssembly.Module;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Instantiates the given `module`, which can either be bytes or
|
|
73
|
+
* a precompiled `WebAssembly.Module`.
|
|
74
|
+
*
|
|
75
|
+
* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
|
|
76
|
+
*
|
|
77
|
+
* @returns {InitOutput}
|
|
78
|
+
*/
|
|
79
|
+
export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
|
|
83
|
+
* for everything else, calls `WebAssembly.instantiate` directly.
|
|
84
|
+
*
|
|
85
|
+
* @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
|
|
86
|
+
*
|
|
87
|
+
* @returns {Promise<InitOutput>}
|
|
88
|
+
*/
|
|
89
|
+
export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;
|