@romedb/node 0.2.13 → 0.2.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -10
- package/THIRD_PARTY_CRATE_NOTICES.md +4 -4
- package/client.cjs +1 -482
- package/client.d.ts +1 -93
- package/client.mjs +1 -496
- package/embedded-client.cjs +5 -4
- package/embedded-client.d.ts +2 -2
- package/embedded-worker.cjs +2 -1
- package/index.cjs +6 -1
- package/index.d.ts +7 -1
- package/package.json +5 -4
- package/romedb.node +2 -2
- package/values.cjs +1 -210
- package/values.d.ts +1 -15
- package/values.mjs +1 -220
- package/worker.cjs +1 -1
- package/client-internal.cjs +0 -2
- package/request-journal.cjs +0 -47
package/embedded-worker.cjs
CHANGED
|
@@ -35,7 +35,8 @@ if (database) parentPort.on('message', ({ id, operation, body, cancellationKey }
|
|
|
35
35
|
if (fenced) fail('RDB_STORAGE', 'Embedded outcome is uncertain; close and reconcile the database', 'unknown');
|
|
36
36
|
if (operation === 'export') {
|
|
37
37
|
// Writes run on this thread, so none commits between the start of the copy's snapshot and reading the sequence.
|
|
38
|
-
const
|
|
38
|
+
const copy = JSON.parse(body);
|
|
39
|
+
const pending = database.startFileExport(copy.destination, cancellationKey, copy.emptyTables, copy.newIdentity);
|
|
39
40
|
const snapshot = sequence;
|
|
40
41
|
const finished = pending.finish().then(
|
|
41
42
|
bytes => parentPort.postMessage({ id, result: { sequence: snapshot, bytes: Number(bytes) } }),
|
package/index.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
+
const { checkCopyOptions } = require('./values.cjs');
|
|
2
3
|
const { NativeDatabase, interruptConnection, createCancellationToken, cancelOperation, releaseCancellationToken, vectorFromComponents, vectorFromSparseEntries } = require('./native.cjs');
|
|
3
4
|
class Record {
|
|
4
5
|
constructor(table, key) {
|
|
@@ -6,6 +7,8 @@ class Record {
|
|
|
6
7
|
this.table = table; this.key = key;
|
|
7
8
|
Object.freeze(this);
|
|
8
9
|
}
|
|
10
|
+
toString() { return `${this.table}:${this.key}`; }
|
|
11
|
+
equals(other) { return other instanceof Record && other.table === this.table && other.key === this.key; }
|
|
9
12
|
}
|
|
10
13
|
class Vector {
|
|
11
14
|
static float32(values) { return constructVector('float32', values); }
|
|
@@ -414,7 +417,9 @@ class AsyncDatabase {
|
|
|
414
417
|
}
|
|
415
418
|
async exportFile(destination, options = {}) {
|
|
416
419
|
if (typeof destination !== 'string' || !destination || destination.length > 4096 || destination.includes('\0')) throw new TypeError('exportFile requires a bounded destination path');
|
|
417
|
-
|
|
420
|
+
checkCopyOptions(options);
|
|
421
|
+
const copy = JSON.stringify({ emptyTables: options.emptyTables, newIdentity: options.newIdentity });
|
|
422
|
+
try { return await this.#request('exportFile', [require('node:path').resolve(destination), copy], false, options.signal, options.timeoutMs); }
|
|
418
423
|
catch (error) {
|
|
419
424
|
const coded = /^(RDB_[A-Z0-9_]+): (.*)$/s.exec(error.message);
|
|
420
425
|
if (!coded) throw error;
|
package/index.d.ts
CHANGED
|
@@ -67,6 +67,12 @@ export class Database {
|
|
|
67
67
|
|
|
68
68
|
export interface ExecuteOptions { signal?: AbortSignal; timeoutMs?: number; }
|
|
69
69
|
export interface FileExport { bytes: number; }
|
|
70
|
+
export interface FileExportOptions extends ExecuteOptions {
|
|
71
|
+
/** These tables keep their schema and indexes but no rows, and the copy gets a new identity. */
|
|
72
|
+
emptyTables?: readonly string[];
|
|
73
|
+
/** Leave out the database ID, the sequence and the receipts: the copy is a new database. */
|
|
74
|
+
newIdentity?: boolean;
|
|
75
|
+
}
|
|
70
76
|
export class AsyncDatabase {
|
|
71
77
|
collection<T extends object = Document>(name: string): AsyncCollection<T>;
|
|
72
78
|
private constructor();
|
|
@@ -77,7 +83,7 @@ export class AsyncDatabase {
|
|
|
77
83
|
exportDocuments(table: string, format?: TransferFormat, options?: ExecuteOptions): Promise<string>;
|
|
78
84
|
importDocuments(table: string, input: string, format?: TransferFormat, options?: ExecuteOptions): Promise<ImportReport>;
|
|
79
85
|
/** Copy an MVCC database into a new private file while queries continue; the destination and its log files must not exist. */
|
|
80
|
-
exportFile(destination: string, options?:
|
|
86
|
+
exportFile(destination: string, options?: FileExportOptions): Promise<FileExport>;
|
|
81
87
|
execute(sql: string, parameters?: Parameters, options?: ExecuteOptions): Promise<QueryResult>;
|
|
82
88
|
profileSelect(sql: string, parameters?: Parameters, options?: ExecuteOptions): Promise<ProfiledQuery>;
|
|
83
89
|
/** Checks write results atomically; does not bound engine or logical materialization memory. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@romedb/node",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.15",
|
|
4
4
|
"description": "RomeDB for Node.js: the embedded database (Linux x64 native addon) and a Web-API client for romedb serve and RomeDB Cloud",
|
|
5
5
|
"main": "index.cjs",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -28,6 +28,9 @@
|
|
|
28
28
|
"engines": {
|
|
29
29
|
"node": ">=22"
|
|
30
30
|
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@romedb/client": "0.2.15"
|
|
33
|
+
},
|
|
31
34
|
"devDependencies": {
|
|
32
35
|
"typescript": "5.8.3"
|
|
33
36
|
},
|
|
@@ -46,14 +49,12 @@
|
|
|
46
49
|
"client.cjs",
|
|
47
50
|
"client.mjs",
|
|
48
51
|
"client.d.ts",
|
|
49
|
-
"client-internal.cjs",
|
|
50
52
|
"embedded-client.cjs",
|
|
51
53
|
"embedded-client.d.ts",
|
|
52
54
|
"embedded-worker.cjs",
|
|
53
55
|
"values.cjs",
|
|
54
56
|
"values.mjs",
|
|
55
57
|
"values.d.ts",
|
|
56
|
-
"request-journal.cjs",
|
|
57
58
|
"worker.cjs",
|
|
58
59
|
"native.cjs",
|
|
59
60
|
"index.d.ts",
|
|
@@ -65,7 +66,7 @@
|
|
|
65
66
|
"RUST-LIBRARY-NOTICES.html"
|
|
66
67
|
],
|
|
67
68
|
"scripts": {
|
|
68
|
-
"test": "node
|
|
69
|
+
"test": "node --test --test-concurrency=2 test.cjs client-native.test.cjs embedded-client.test.cjs shared-classes.test.cjs",
|
|
69
70
|
"typecheck": "tsc --noEmit --strict --target ES2022 --module Node16 types.test.ts client.types.test.ts embedded-client.types.test.ts values.types.test.ts"
|
|
70
71
|
}
|
|
71
72
|
}
|
package/romedb.node
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: package/romedb.node
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 41455968 bytes
|
|
4
|
+
sha256: b22956bbb11270a12867bee37606446ad39e6deb07d9aa7f9d5645f936be60c2
|
package/values.cjs
CHANGED
|
@@ -1,211 +1,2 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class Float64 {
|
|
5
|
-
constructor(value) {
|
|
6
|
-
if (typeof value !== 'number' || !Number.isFinite(value)) throw new TypeError('Float64 requires a finite number');
|
|
7
|
-
this.value = value;
|
|
8
|
-
Object.freeze(this);
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
class Record {
|
|
13
|
-
constructor(table, key) {
|
|
14
|
-
if (typeof table !== 'string' || !table || table.includes('\0') || /^(?:__romedb_|__turso_internal_|sqlite_)/i.test(table)) throw new TypeError('Record requires a nonreserved logical table name');
|
|
15
|
-
if ((typeof key !== 'string' && typeof key !== 'bigint') || key === '') throw new TypeError('Record requires a nonempty string or bigint key');
|
|
16
|
-
if (typeof key === 'bigint') checkInteger(key);
|
|
17
|
-
this.table = table;
|
|
18
|
-
this.key = key;
|
|
19
|
-
Object.freeze(this);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
class Vector {
|
|
24
|
-
#bytes;
|
|
25
|
-
constructor(bytes) {
|
|
26
|
-
if (!(bytes instanceof Uint8Array) || bytes.byteLength < 1 || bytes.byteLength > 524_289) throw new TypeError('Vector requires bounded encoded bytes');
|
|
27
|
-
this.#bytes = new Uint8Array(bytes);
|
|
28
|
-
Object.freeze(this);
|
|
29
|
-
}
|
|
30
|
-
get bytes() { return new Uint8Array(this.#bytes); }
|
|
31
|
-
static float32(values) { return denseVector(values, 4); }
|
|
32
|
-
static float64(values) { return denseVector(values, 8); }
|
|
33
|
-
static sparse32Entries(dimensions, entries) {
|
|
34
|
-
checkDimensions(dimensions);
|
|
35
|
-
if (!Array.isArray(entries) || entries.length > dimensions) throw new TypeError('sparse entries require bounded index/value pairs');
|
|
36
|
-
let previous = -1;
|
|
37
|
-
const nonzero = [];
|
|
38
|
-
for (const entry of entries) {
|
|
39
|
-
if (!Array.isArray(entry) || entry.length !== 2) throw new TypeError('sparse entries require index/value pairs');
|
|
40
|
-
const [index, value] = entry;
|
|
41
|
-
if (!Number.isInteger(index) || index <= previous || index >= dimensions) throw new RangeError('sparse indices must be increasing and within dimensions');
|
|
42
|
-
checkComponent(value, 4);
|
|
43
|
-
const rounded = Math.fround(value);
|
|
44
|
-
if (rounded !== 0) nonzero.push([index, rounded]);
|
|
45
|
-
previous = index;
|
|
46
|
-
}
|
|
47
|
-
const bytes = new Uint8Array(nonzero.length * 8 + 5);
|
|
48
|
-
const view = new DataView(bytes.buffer);
|
|
49
|
-
nonzero.forEach(([index, value], i) => {
|
|
50
|
-
view.setFloat32(i * 4, value, true);
|
|
51
|
-
view.setUint32(nonzero.length * 4 + i * 4, index, true);
|
|
52
|
-
});
|
|
53
|
-
view.setUint32(nonzero.length * 8, dimensions, true);
|
|
54
|
-
bytes[bytes.length - 1] = 9;
|
|
55
|
-
return new Vector(bytes);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function denseVector(values, width) {
|
|
60
|
-
if (!Array.isArray(values) && !(values instanceof Float32Array) && !(values instanceof Float64Array)) throw new TypeError('vector components require a number array');
|
|
61
|
-
checkDimensions(values.length);
|
|
62
|
-
const bytes = new Uint8Array(values.length * width + (width === 8 ? 1 : 0));
|
|
63
|
-
const view = new DataView(bytes.buffer);
|
|
64
|
-
for (let i = 0; i < values.length; i++) {
|
|
65
|
-
checkComponent(values[i], width);
|
|
66
|
-
if (width === 4) view.setFloat32(i * width, values[i], true);
|
|
67
|
-
else view.setFloat64(i * width, values[i], true);
|
|
68
|
-
}
|
|
69
|
-
if (width === 8) bytes[bytes.length - 1] = 2;
|
|
70
|
-
return new Vector(bytes);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function checkDimensions(dimensions) {
|
|
74
|
-
if (!Number.isInteger(dimensions) || dimensions < 1 || dimensions > 65_536) throw new RangeError('vector dimensions must be 1..65536');
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function checkComponent(value, width) {
|
|
78
|
-
if (typeof value !== 'number' || !Number.isFinite(value)) throw new TypeError('vector components must be finite');
|
|
79
|
-
if (width === 4 && !Number.isFinite(Math.fround(value))) throw new RangeError('vector component is outside the finite float32 range');
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function encodeValue(value, depth = 0, base64 = false) {
|
|
83
|
-
checkDepth(depth);
|
|
84
|
-
const tagged = (type, value) => ({ type, value });
|
|
85
|
-
if (value === null) return { type: 'Null' };
|
|
86
|
-
if (typeof value === 'boolean') return tagged('Boolean', value);
|
|
87
|
-
if (typeof value === 'string') return tagged('String', text(value));
|
|
88
|
-
if (typeof value === 'bigint') { checkInteger(value); return tagged('Integer', value.toString()); }
|
|
89
|
-
if (value instanceof Float64) return encodeFloat(value.value);
|
|
90
|
-
if (typeof value === 'number') {
|
|
91
|
-
if (!Number.isFinite(value)) throw new TypeError('non-finite number');
|
|
92
|
-
if (Number.isInteger(value) && !Object.is(value, -0)) {
|
|
93
|
-
if (!Number.isSafeInteger(value)) throw new RangeError('use bigint or Float64 for integers outside the safe Number range');
|
|
94
|
-
return tagged('Integer', String(value));
|
|
95
|
-
}
|
|
96
|
-
return encodeFloat(value);
|
|
97
|
-
}
|
|
98
|
-
if (value instanceof Record) return tagged('Record', { table: text(value.table), key: encodeValue(value.key) });
|
|
99
|
-
if (value instanceof Vector) return tagged('Vector', base64 ? toBase64(value.bytes) : [...value.bytes]);
|
|
100
|
-
if (value instanceof Uint8Array) return tagged('Binary', base64 ? toBase64(value) : [...value]);
|
|
101
|
-
if (Array.isArray(value)) {
|
|
102
|
-
const entries = [];
|
|
103
|
-
for (let i = 0; i < value.length; i++) {
|
|
104
|
-
if (!Object.hasOwn(value, i)) throw new TypeError('array holes are not database values');
|
|
105
|
-
entries.push(encodeValue(value[i], depth + 1, base64));
|
|
106
|
-
}
|
|
107
|
-
return tagged('Array', entries);
|
|
108
|
-
}
|
|
109
|
-
if (plainObject(value)) return tagged('Object', Object.fromEntries(Object.entries(value).map(([key, entry]) => [text(key), encodeValue(entry, depth + 1, base64)])));
|
|
110
|
-
throw new TypeError('unsupported parameter value');
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function byteArray(payload) {
|
|
114
|
-
if (!Array.isArray(payload) || payload.length > 524_289 || payload.some(n => !Number.isInteger(n) || n < 0 || n > 255)) throw new TypeError('invalid byte array');
|
|
115
|
-
return Uint8Array.from(payload);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function base64Bytes(payload) {
|
|
119
|
-
if (payload.length > 1_048_576 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(payload)) throw new TypeError('invalid base64 bytes');
|
|
120
|
-
const binary = atob(payload);
|
|
121
|
-
const bytes = new Uint8Array(binary.length);
|
|
122
|
-
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
123
|
-
if (toBase64(bytes) !== payload) throw new TypeError('invalid base64 bytes');
|
|
124
|
-
return bytes;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function toBase64(bytes) {
|
|
128
|
-
let binary = '';
|
|
129
|
-
for (let i = 0; i < bytes.length; i += 32_768) binary += String.fromCharCode(...bytes.subarray(i, i + 32_768));
|
|
130
|
-
return btoa(binary);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function encodeFloat(value) {
|
|
134
|
-
const view = new DataView(new ArrayBuffer(8));
|
|
135
|
-
view.setFloat64(0, value);
|
|
136
|
-
let hex = '';
|
|
137
|
-
for (let i = 0; i < 8; i++) hex += view.getUint8(i).toString(16).padStart(2, '0');
|
|
138
|
-
return { type: 'Number', value: hex };
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function decodeValue(value, depth = 0) {
|
|
142
|
-
checkDepth(depth);
|
|
143
|
-
if (!plainObject(value) || typeof value.type !== 'string') throw new TypeError('invalid tagged value');
|
|
144
|
-
const keys = Object.keys(value);
|
|
145
|
-
if (value.type === 'Null') {
|
|
146
|
-
if (keys.length !== 1) throw new TypeError('invalid Null value');
|
|
147
|
-
return null;
|
|
148
|
-
}
|
|
149
|
-
if (keys.length !== 2 || !Object.hasOwn(value, 'value')) throw new TypeError('invalid tagged value fields');
|
|
150
|
-
const payload = value.value;
|
|
151
|
-
switch (value.type) {
|
|
152
|
-
case 'Boolean':
|
|
153
|
-
if (typeof payload !== 'boolean') throw new TypeError('invalid Boolean value');
|
|
154
|
-
return payload;
|
|
155
|
-
case 'String':
|
|
156
|
-
if (typeof payload !== 'string') throw new TypeError('invalid String value');
|
|
157
|
-
return payload;
|
|
158
|
-
case 'Integer': {
|
|
159
|
-
if (typeof payload !== 'string' || !/^(?:0|-?[1-9][0-9]{0,18})$/.test(payload)) throw new TypeError('invalid canonical int64');
|
|
160
|
-
const integer = BigInt(payload);
|
|
161
|
-
checkInteger(integer);
|
|
162
|
-
return integer;
|
|
163
|
-
}
|
|
164
|
-
case 'Number': {
|
|
165
|
-
if (typeof payload !== 'string' || !/^[0-9a-f]{16}$/.test(payload)) throw new TypeError('invalid float64 bits');
|
|
166
|
-
const view = new DataView(new ArrayBuffer(8));
|
|
167
|
-
for (let i = 0; i < 8; i++) view.setUint8(i, parseInt(payload.slice(i * 2, i * 2 + 2), 16));
|
|
168
|
-
return new Float64(view.getFloat64(0));
|
|
169
|
-
}
|
|
170
|
-
case 'Binary': case 'Vector': {
|
|
171
|
-
const bytes = typeof payload === 'string' ? base64Bytes(payload) : byteArray(payload);
|
|
172
|
-
return value.type === 'Vector' ? new Vector(bytes) : bytes;
|
|
173
|
-
}
|
|
174
|
-
case 'Record': {
|
|
175
|
-
if (!plainObject(payload) || Object.keys(payload).length !== 2 || !Object.hasOwn(payload, 'table') || !Object.hasOwn(payload, 'key')) throw new TypeError('invalid Record value');
|
|
176
|
-
// Historical Cloud receipts keep their original enum-key encoding.
|
|
177
|
-
if (plainObject(payload.key) && Object.keys(payload.key).length === 1) {
|
|
178
|
-
const kind = Object.keys(payload.key)[0];
|
|
179
|
-
if (['String', 'Integer'].includes(kind)) return new Record(payload.table, decodeValue({ type: kind, value: payload.key[kind] }));
|
|
180
|
-
}
|
|
181
|
-
if (!['String', 'Integer'].includes(payload.key?.type)) throw new TypeError('invalid Record key');
|
|
182
|
-
return new Record(payload.table, decodeValue(payload.key));
|
|
183
|
-
}
|
|
184
|
-
case 'Array':
|
|
185
|
-
if (!Array.isArray(payload)) throw new TypeError('invalid Array value');
|
|
186
|
-
return payload.map(entry => decodeValue(entry, depth + 1));
|
|
187
|
-
case 'Object':
|
|
188
|
-
if (!plainObject(payload)) throw new TypeError('invalid Object value');
|
|
189
|
-
return Object.fromEntries(Object.entries(payload).map(([key, entry]) => [key, decodeValue(entry, depth + 1)]));
|
|
190
|
-
default: throw new TypeError('unsupported value tag');
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function text(value) {
|
|
195
|
-
if (!value.isWellFormed()) throw new TypeError('strings must be well-formed Unicode');
|
|
196
|
-
return value;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function checkInteger(value) {
|
|
200
|
-
if (value < -(1n << 63n) || value >= (1n << 63n)) throw new RangeError('integer outside int64');
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function checkDepth(depth) {
|
|
204
|
-
if (depth > 64) throw new RangeError('value nesting exceeds 64');
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function plainObject(value) {
|
|
208
|
-
return value !== null && typeof value === 'object' && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
module.exports = { Float64, Record, Vector, encodeValue, decodeValue, plainObject, text };
|
|
2
|
+
module.exports = require('@romedb/client/values');
|
package/values.d.ts
CHANGED
|
@@ -1,15 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
export class Record { constructor(table: string, key: string | bigint); readonly table: string; readonly key: string | bigint; }
|
|
3
|
-
export class Vector {
|
|
4
|
-
constructor(bytes: Uint8Array);
|
|
5
|
-
/** A copy of the encoded bytes. */
|
|
6
|
-
readonly bytes: Uint8Array;
|
|
7
|
-
static float32(values: readonly number[] | Float32Array | Float64Array): Vector;
|
|
8
|
-
static float64(values: readonly number[] | Float32Array | Float64Array): Vector;
|
|
9
|
-
static sparse32Entries(dimensions: number, entries: readonly (readonly [number, number])[]): Vector;
|
|
10
|
-
}
|
|
11
|
-
/** Binary values decode as plain Uint8Array (not Buffer) from 0.2.12. */
|
|
12
|
-
export type Value = null | boolean | bigint | number | string | Float64 | Record | Vector | Uint8Array | Value[] | { [key: string]: Value };
|
|
13
|
-
export type PortableValue = { type: 'Null' } | { type: 'Boolean'; value: boolean } | { type: 'Integer' | 'Number' | 'String'; value: string } | { type: 'Binary' | 'Vector'; value: number[] | string } | { type: 'Record'; value: { table: string; key: { type: 'Integer' | 'String'; value: string } } } | { type: 'Array'; value: PortableValue[] } | { type: 'Object'; value: { [key: string]: PortableValue } };
|
|
14
|
-
export function encodeValue(value: Value): PortableValue;
|
|
15
|
-
export function decodeValue(value: unknown): Value;
|
|
1
|
+
export * from '@romedb/client/values';
|
package/values.mjs
CHANGED
|
@@ -1,220 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const commonjs = { exports: {} };
|
|
4
|
-
(function (module, exports, require) {
|
|
5
|
-
'use strict';
|
|
6
|
-
// Web APIs only: this file also runs in Workers and browsers through values.mjs.
|
|
7
|
-
|
|
8
|
-
class Float64 {
|
|
9
|
-
constructor(value) {
|
|
10
|
-
if (typeof value !== 'number' || !Number.isFinite(value)) throw new TypeError('Float64 requires a finite number');
|
|
11
|
-
this.value = value;
|
|
12
|
-
Object.freeze(this);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
class Record {
|
|
17
|
-
constructor(table, key) {
|
|
18
|
-
if (typeof table !== 'string' || !table || table.includes('\0') || /^(?:__romedb_|__turso_internal_|sqlite_)/i.test(table)) throw new TypeError('Record requires a nonreserved logical table name');
|
|
19
|
-
if ((typeof key !== 'string' && typeof key !== 'bigint') || key === '') throw new TypeError('Record requires a nonempty string or bigint key');
|
|
20
|
-
if (typeof key === 'bigint') checkInteger(key);
|
|
21
|
-
this.table = table;
|
|
22
|
-
this.key = key;
|
|
23
|
-
Object.freeze(this);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
class Vector {
|
|
28
|
-
#bytes;
|
|
29
|
-
constructor(bytes) {
|
|
30
|
-
if (!(bytes instanceof Uint8Array) || bytes.byteLength < 1 || bytes.byteLength > 524_289) throw new TypeError('Vector requires bounded encoded bytes');
|
|
31
|
-
this.#bytes = new Uint8Array(bytes);
|
|
32
|
-
Object.freeze(this);
|
|
33
|
-
}
|
|
34
|
-
get bytes() { return new Uint8Array(this.#bytes); }
|
|
35
|
-
static float32(values) { return denseVector(values, 4); }
|
|
36
|
-
static float64(values) { return denseVector(values, 8); }
|
|
37
|
-
static sparse32Entries(dimensions, entries) {
|
|
38
|
-
checkDimensions(dimensions);
|
|
39
|
-
if (!Array.isArray(entries) || entries.length > dimensions) throw new TypeError('sparse entries require bounded index/value pairs');
|
|
40
|
-
let previous = -1;
|
|
41
|
-
const nonzero = [];
|
|
42
|
-
for (const entry of entries) {
|
|
43
|
-
if (!Array.isArray(entry) || entry.length !== 2) throw new TypeError('sparse entries require index/value pairs');
|
|
44
|
-
const [index, value] = entry;
|
|
45
|
-
if (!Number.isInteger(index) || index <= previous || index >= dimensions) throw new RangeError('sparse indices must be increasing and within dimensions');
|
|
46
|
-
checkComponent(value, 4);
|
|
47
|
-
const rounded = Math.fround(value);
|
|
48
|
-
if (rounded !== 0) nonzero.push([index, rounded]);
|
|
49
|
-
previous = index;
|
|
50
|
-
}
|
|
51
|
-
const bytes = new Uint8Array(nonzero.length * 8 + 5);
|
|
52
|
-
const view = new DataView(bytes.buffer);
|
|
53
|
-
nonzero.forEach(([index, value], i) => {
|
|
54
|
-
view.setFloat32(i * 4, value, true);
|
|
55
|
-
view.setUint32(nonzero.length * 4 + i * 4, index, true);
|
|
56
|
-
});
|
|
57
|
-
view.setUint32(nonzero.length * 8, dimensions, true);
|
|
58
|
-
bytes[bytes.length - 1] = 9;
|
|
59
|
-
return new Vector(bytes);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function denseVector(values, width) {
|
|
64
|
-
if (!Array.isArray(values) && !(values instanceof Float32Array) && !(values instanceof Float64Array)) throw new TypeError('vector components require a number array');
|
|
65
|
-
checkDimensions(values.length);
|
|
66
|
-
const bytes = new Uint8Array(values.length * width + (width === 8 ? 1 : 0));
|
|
67
|
-
const view = new DataView(bytes.buffer);
|
|
68
|
-
for (let i = 0; i < values.length; i++) {
|
|
69
|
-
checkComponent(values[i], width);
|
|
70
|
-
if (width === 4) view.setFloat32(i * width, values[i], true);
|
|
71
|
-
else view.setFloat64(i * width, values[i], true);
|
|
72
|
-
}
|
|
73
|
-
if (width === 8) bytes[bytes.length - 1] = 2;
|
|
74
|
-
return new Vector(bytes);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function checkDimensions(dimensions) {
|
|
78
|
-
if (!Number.isInteger(dimensions) || dimensions < 1 || dimensions > 65_536) throw new RangeError('vector dimensions must be 1..65536');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function checkComponent(value, width) {
|
|
82
|
-
if (typeof value !== 'number' || !Number.isFinite(value)) throw new TypeError('vector components must be finite');
|
|
83
|
-
if (width === 4 && !Number.isFinite(Math.fround(value))) throw new RangeError('vector component is outside the finite float32 range');
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function encodeValue(value, depth = 0, base64 = false) {
|
|
87
|
-
checkDepth(depth);
|
|
88
|
-
const tagged = (type, value) => ({ type, value });
|
|
89
|
-
if (value === null) return { type: 'Null' };
|
|
90
|
-
if (typeof value === 'boolean') return tagged('Boolean', value);
|
|
91
|
-
if (typeof value === 'string') return tagged('String', text(value));
|
|
92
|
-
if (typeof value === 'bigint') { checkInteger(value); return tagged('Integer', value.toString()); }
|
|
93
|
-
if (value instanceof Float64) return encodeFloat(value.value);
|
|
94
|
-
if (typeof value === 'number') {
|
|
95
|
-
if (!Number.isFinite(value)) throw new TypeError('non-finite number');
|
|
96
|
-
if (Number.isInteger(value) && !Object.is(value, -0)) {
|
|
97
|
-
if (!Number.isSafeInteger(value)) throw new RangeError('use bigint or Float64 for integers outside the safe Number range');
|
|
98
|
-
return tagged('Integer', String(value));
|
|
99
|
-
}
|
|
100
|
-
return encodeFloat(value);
|
|
101
|
-
}
|
|
102
|
-
if (value instanceof Record) return tagged('Record', { table: text(value.table), key: encodeValue(value.key) });
|
|
103
|
-
if (value instanceof Vector) return tagged('Vector', base64 ? toBase64(value.bytes) : [...value.bytes]);
|
|
104
|
-
if (value instanceof Uint8Array) return tagged('Binary', base64 ? toBase64(value) : [...value]);
|
|
105
|
-
if (Array.isArray(value)) {
|
|
106
|
-
const entries = [];
|
|
107
|
-
for (let i = 0; i < value.length; i++) {
|
|
108
|
-
if (!Object.hasOwn(value, i)) throw new TypeError('array holes are not database values');
|
|
109
|
-
entries.push(encodeValue(value[i], depth + 1, base64));
|
|
110
|
-
}
|
|
111
|
-
return tagged('Array', entries);
|
|
112
|
-
}
|
|
113
|
-
if (plainObject(value)) return tagged('Object', Object.fromEntries(Object.entries(value).map(([key, entry]) => [text(key), encodeValue(entry, depth + 1, base64)])));
|
|
114
|
-
throw new TypeError('unsupported parameter value');
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function byteArray(payload) {
|
|
118
|
-
if (!Array.isArray(payload) || payload.length > 524_289 || payload.some(n => !Number.isInteger(n) || n < 0 || n > 255)) throw new TypeError('invalid byte array');
|
|
119
|
-
return Uint8Array.from(payload);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function base64Bytes(payload) {
|
|
123
|
-
if (payload.length > 1_048_576 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(payload)) throw new TypeError('invalid base64 bytes');
|
|
124
|
-
const binary = atob(payload);
|
|
125
|
-
const bytes = new Uint8Array(binary.length);
|
|
126
|
-
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
127
|
-
if (toBase64(bytes) !== payload) throw new TypeError('invalid base64 bytes');
|
|
128
|
-
return bytes;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function toBase64(bytes) {
|
|
132
|
-
let binary = '';
|
|
133
|
-
for (let i = 0; i < bytes.length; i += 32_768) binary += String.fromCharCode(...bytes.subarray(i, i + 32_768));
|
|
134
|
-
return btoa(binary);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function encodeFloat(value) {
|
|
138
|
-
const view = new DataView(new ArrayBuffer(8));
|
|
139
|
-
view.setFloat64(0, value);
|
|
140
|
-
let hex = '';
|
|
141
|
-
for (let i = 0; i < 8; i++) hex += view.getUint8(i).toString(16).padStart(2, '0');
|
|
142
|
-
return { type: 'Number', value: hex };
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function decodeValue(value, depth = 0) {
|
|
146
|
-
checkDepth(depth);
|
|
147
|
-
if (!plainObject(value) || typeof value.type !== 'string') throw new TypeError('invalid tagged value');
|
|
148
|
-
const keys = Object.keys(value);
|
|
149
|
-
if (value.type === 'Null') {
|
|
150
|
-
if (keys.length !== 1) throw new TypeError('invalid Null value');
|
|
151
|
-
return null;
|
|
152
|
-
}
|
|
153
|
-
if (keys.length !== 2 || !Object.hasOwn(value, 'value')) throw new TypeError('invalid tagged value fields');
|
|
154
|
-
const payload = value.value;
|
|
155
|
-
switch (value.type) {
|
|
156
|
-
case 'Boolean':
|
|
157
|
-
if (typeof payload !== 'boolean') throw new TypeError('invalid Boolean value');
|
|
158
|
-
return payload;
|
|
159
|
-
case 'String':
|
|
160
|
-
if (typeof payload !== 'string') throw new TypeError('invalid String value');
|
|
161
|
-
return payload;
|
|
162
|
-
case 'Integer': {
|
|
163
|
-
if (typeof payload !== 'string' || !/^(?:0|-?[1-9][0-9]{0,18})$/.test(payload)) throw new TypeError('invalid canonical int64');
|
|
164
|
-
const integer = BigInt(payload);
|
|
165
|
-
checkInteger(integer);
|
|
166
|
-
return integer;
|
|
167
|
-
}
|
|
168
|
-
case 'Number': {
|
|
169
|
-
if (typeof payload !== 'string' || !/^[0-9a-f]{16}$/.test(payload)) throw new TypeError('invalid float64 bits');
|
|
170
|
-
const view = new DataView(new ArrayBuffer(8));
|
|
171
|
-
for (let i = 0; i < 8; i++) view.setUint8(i, parseInt(payload.slice(i * 2, i * 2 + 2), 16));
|
|
172
|
-
return new Float64(view.getFloat64(0));
|
|
173
|
-
}
|
|
174
|
-
case 'Binary': case 'Vector': {
|
|
175
|
-
const bytes = typeof payload === 'string' ? base64Bytes(payload) : byteArray(payload);
|
|
176
|
-
return value.type === 'Vector' ? new Vector(bytes) : bytes;
|
|
177
|
-
}
|
|
178
|
-
case 'Record': {
|
|
179
|
-
if (!plainObject(payload) || Object.keys(payload).length !== 2 || !Object.hasOwn(payload, 'table') || !Object.hasOwn(payload, 'key')) throw new TypeError('invalid Record value');
|
|
180
|
-
// Historical Cloud receipts keep their original enum-key encoding.
|
|
181
|
-
if (plainObject(payload.key) && Object.keys(payload.key).length === 1) {
|
|
182
|
-
const kind = Object.keys(payload.key)[0];
|
|
183
|
-
if (['String', 'Integer'].includes(kind)) return new Record(payload.table, decodeValue({ type: kind, value: payload.key[kind] }));
|
|
184
|
-
}
|
|
185
|
-
if (!['String', 'Integer'].includes(payload.key?.type)) throw new TypeError('invalid Record key');
|
|
186
|
-
return new Record(payload.table, decodeValue(payload.key));
|
|
187
|
-
}
|
|
188
|
-
case 'Array':
|
|
189
|
-
if (!Array.isArray(payload)) throw new TypeError('invalid Array value');
|
|
190
|
-
return payload.map(entry => decodeValue(entry, depth + 1));
|
|
191
|
-
case 'Object':
|
|
192
|
-
if (!plainObject(payload)) throw new TypeError('invalid Object value');
|
|
193
|
-
return Object.fromEntries(Object.entries(payload).map(([key, entry]) => [key, decodeValue(entry, depth + 1)]));
|
|
194
|
-
default: throw new TypeError('unsupported value tag');
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function text(value) {
|
|
199
|
-
if (!value.isWellFormed()) throw new TypeError('strings must be well-formed Unicode');
|
|
200
|
-
return value;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function checkInteger(value) {
|
|
204
|
-
if (value < -(1n << 63n) || value >= (1n << 63n)) throw new RangeError('integer outside int64');
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function checkDepth(depth) {
|
|
208
|
-
if (depth > 64) throw new RangeError('value nesting exceeds 64');
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function plainObject(value) {
|
|
212
|
-
return value !== null && typeof value === 'object' && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
module.exports = { Float64, Record, Vector, encodeValue, decodeValue, plainObject, text };
|
|
216
|
-
})(commonjs, commonjs.exports, specifier => {
|
|
217
|
-
throw new TypeError(`Cannot load ${specifier}`);
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
export const { Float64, Record, Vector, encodeValue, decodeValue, plainObject, text } = commonjs.exports;
|
|
1
|
+
export * from '@romedb/client/values';
|
package/worker.cjs
CHANGED
|
@@ -10,7 +10,7 @@ parentPort.on('message', ({ id, method, args, cancellationKey }) => {
|
|
|
10
10
|
if (method === 'exportFile') {
|
|
11
11
|
const key = cancellationKey ?? createCancellationToken();
|
|
12
12
|
const finished = Promise.resolve()
|
|
13
|
-
.then(() => db.startFileExport(args[0], key).finish())
|
|
13
|
+
.then(() => { const copy = JSON.parse(args[1]); return db.startFileExport(args[0], key, copy.emptyTables, copy.newIdentity).finish(); })
|
|
14
14
|
.then(bytes => parentPort.postMessage({ id, result: { bytes: Number(bytes) } }), error => parentPort.postMessage({ id, error: { message: error.message } }))
|
|
15
15
|
.finally(() => { copies.delete(key); if (cancellationKey === undefined) releaseCancellationToken(key); });
|
|
16
16
|
copies.set(key, finished);
|
package/client-internal.cjs
DELETED
package/request-journal.cjs
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
const fs = require('node:fs/promises');
|
|
3
|
-
const { constants } = require('node:fs');
|
|
4
|
-
const path = require('node:path');
|
|
5
|
-
const { Buffer } = require('node:buffer');
|
|
6
|
-
|
|
7
|
-
const MAX_JOURNAL = 2_359_296;
|
|
8
|
-
|
|
9
|
-
async function saveRequest(file, request, validate) {
|
|
10
|
-
validate(request);
|
|
11
|
-
const text = JSON.stringify(request);
|
|
12
|
-
if (Buffer.byteLength(text) > MAX_JOURNAL) throw new RangeError('request journal exceeds byte limit');
|
|
13
|
-
const handle = await fs.open(file, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0), 0o600);
|
|
14
|
-
try {
|
|
15
|
-
await handle.writeFile(text, 'utf8');
|
|
16
|
-
await handle.sync();
|
|
17
|
-
} catch (error) {
|
|
18
|
-
await handle.close();
|
|
19
|
-
await fs.unlink(file);
|
|
20
|
-
throw error;
|
|
21
|
-
}
|
|
22
|
-
await handle.close();
|
|
23
|
-
const directory = await fs.open(path.dirname(path.resolve(file)), constants.O_RDONLY | (constants.O_DIRECTORY ?? 0));
|
|
24
|
-
try { await directory.sync(); }
|
|
25
|
-
finally { await directory.close(); }
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async function loadRequest(file, validate) {
|
|
29
|
-
const handle = await fs.open(file, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
30
|
-
try {
|
|
31
|
-
const stat = await handle.stat();
|
|
32
|
-
if (!stat.isFile() || stat.nlink !== 1 || (stat.mode & 0o077) !== 0 || (typeof process.getuid === 'function' && stat.uid !== process.getuid()) || stat.size > MAX_JOURNAL) throw new Error('request journal requires a private bounded regular file');
|
|
33
|
-
const bytes = Buffer.alloc(MAX_JOURNAL + 1);
|
|
34
|
-
let length = 0;
|
|
35
|
-
while (length < bytes.length) {
|
|
36
|
-
const result = await handle.read(bytes, length, bytes.length - length, null);
|
|
37
|
-
if (!result.bytesRead) break;
|
|
38
|
-
length += result.bytesRead;
|
|
39
|
-
}
|
|
40
|
-
if (length > MAX_JOURNAL) throw new RangeError('request journal exceeds byte limit');
|
|
41
|
-
const request = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes.subarray(0, length)));
|
|
42
|
-
validate(request);
|
|
43
|
-
return Object.freeze(request);
|
|
44
|
-
} finally { await handle.close(); }
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
module.exports = { saveRequest, loadRequest };
|