@romedb/node 0.2.14 → 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 +15 -7
- package/THIRD_PARTY_CRATE_NOTICES.md +4 -4
- package/client.cjs +1 -488
- package/client.d.ts +1 -105
- package/client.mjs +1 -502
- package/embedded-client.cjs +1 -1
- package/package.json +5 -4
- package/romedb.node +2 -2
- package/values.cjs +1 -220
- package/values.d.ts +1 -15
- package/values.mjs +1 -230
- package/client-internal.cjs +0 -2
- package/request-journal.cjs +0 -47
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,221 +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
|
-
toString() { return `${this.table}:${this.key}`; }
|
|
22
|
-
equals(other) { return other instanceof Record && other.table === this.table && other.key === this.key; }
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
class Vector {
|
|
26
|
-
#bytes;
|
|
27
|
-
constructor(bytes) {
|
|
28
|
-
if (!(bytes instanceof Uint8Array) || bytes.byteLength < 1 || bytes.byteLength > 524_289) throw new TypeError('Vector requires bounded encoded bytes');
|
|
29
|
-
this.#bytes = new Uint8Array(bytes);
|
|
30
|
-
Object.freeze(this);
|
|
31
|
-
}
|
|
32
|
-
get bytes() { return new Uint8Array(this.#bytes); }
|
|
33
|
-
static float32(values) { return denseVector(values, 4); }
|
|
34
|
-
static float64(values) { return denseVector(values, 8); }
|
|
35
|
-
static sparse32Entries(dimensions, entries) {
|
|
36
|
-
checkDimensions(dimensions);
|
|
37
|
-
if (!Array.isArray(entries) || entries.length > dimensions) throw new TypeError('sparse entries require bounded index/value pairs');
|
|
38
|
-
let previous = -1;
|
|
39
|
-
const nonzero = [];
|
|
40
|
-
for (const entry of entries) {
|
|
41
|
-
if (!Array.isArray(entry) || entry.length !== 2) throw new TypeError('sparse entries require index/value pairs');
|
|
42
|
-
const [index, value] = entry;
|
|
43
|
-
if (!Number.isInteger(index) || index <= previous || index >= dimensions) throw new RangeError('sparse indices must be increasing and within dimensions');
|
|
44
|
-
checkComponent(value, 4);
|
|
45
|
-
const rounded = Math.fround(value);
|
|
46
|
-
if (rounded !== 0) nonzero.push([index, rounded]);
|
|
47
|
-
previous = index;
|
|
48
|
-
}
|
|
49
|
-
const bytes = new Uint8Array(nonzero.length * 8 + 5);
|
|
50
|
-
const view = new DataView(bytes.buffer);
|
|
51
|
-
nonzero.forEach(([index, value], i) => {
|
|
52
|
-
view.setFloat32(i * 4, value, true);
|
|
53
|
-
view.setUint32(nonzero.length * 4 + i * 4, index, true);
|
|
54
|
-
});
|
|
55
|
-
view.setUint32(nonzero.length * 8, dimensions, true);
|
|
56
|
-
bytes[bytes.length - 1] = 9;
|
|
57
|
-
return new Vector(bytes);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function denseVector(values, width) {
|
|
62
|
-
if (!Array.isArray(values) && !(values instanceof Float32Array) && !(values instanceof Float64Array)) throw new TypeError('vector components require a number array');
|
|
63
|
-
checkDimensions(values.length);
|
|
64
|
-
const bytes = new Uint8Array(values.length * width + (width === 8 ? 1 : 0));
|
|
65
|
-
const view = new DataView(bytes.buffer);
|
|
66
|
-
for (let i = 0; i < values.length; i++) {
|
|
67
|
-
checkComponent(values[i], width);
|
|
68
|
-
if (width === 4) view.setFloat32(i * width, values[i], true);
|
|
69
|
-
else view.setFloat64(i * width, values[i], true);
|
|
70
|
-
}
|
|
71
|
-
if (width === 8) bytes[bytes.length - 1] = 2;
|
|
72
|
-
return new Vector(bytes);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function checkDimensions(dimensions) {
|
|
76
|
-
if (!Number.isInteger(dimensions) || dimensions < 1 || dimensions > 65_536) throw new RangeError('vector dimensions must be 1..65536');
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function checkComponent(value, width) {
|
|
80
|
-
if (typeof value !== 'number' || !Number.isFinite(value)) throw new TypeError('vector components must be finite');
|
|
81
|
-
if (width === 4 && !Number.isFinite(Math.fround(value))) throw new RangeError('vector component is outside the finite float32 range');
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function encodeValue(value, depth = 0, base64 = false) {
|
|
85
|
-
checkDepth(depth);
|
|
86
|
-
const tagged = (type, value) => ({ type, value });
|
|
87
|
-
if (value === null) return { type: 'Null' };
|
|
88
|
-
if (typeof value === 'boolean') return tagged('Boolean', value);
|
|
89
|
-
if (typeof value === 'string') return tagged('String', text(value));
|
|
90
|
-
if (typeof value === 'bigint') { checkInteger(value); return tagged('Integer', value.toString()); }
|
|
91
|
-
if (value instanceof Float64) return encodeFloat(value.value);
|
|
92
|
-
if (typeof value === 'number') {
|
|
93
|
-
if (!Number.isFinite(value)) throw new TypeError('non-finite number');
|
|
94
|
-
if (Number.isInteger(value) && !Object.is(value, -0)) {
|
|
95
|
-
if (!Number.isSafeInteger(value)) throw new RangeError('use bigint or Float64 for integers outside the safe Number range');
|
|
96
|
-
return tagged('Integer', String(value));
|
|
97
|
-
}
|
|
98
|
-
return encodeFloat(value);
|
|
99
|
-
}
|
|
100
|
-
if (value instanceof Record) return tagged('Record', { table: text(value.table), key: encodeValue(value.key) });
|
|
101
|
-
if (value instanceof Vector) return tagged('Vector', base64 ? toBase64(value.bytes) : [...value.bytes]);
|
|
102
|
-
if (value instanceof Uint8Array) return tagged('Binary', base64 ? toBase64(value) : [...value]);
|
|
103
|
-
if (Array.isArray(value)) {
|
|
104
|
-
const entries = [];
|
|
105
|
-
for (let i = 0; i < value.length; i++) {
|
|
106
|
-
if (!Object.hasOwn(value, i)) throw new TypeError('array holes are not database values');
|
|
107
|
-
entries.push(encodeValue(value[i], depth + 1, base64));
|
|
108
|
-
}
|
|
109
|
-
return tagged('Array', entries);
|
|
110
|
-
}
|
|
111
|
-
if (plainObject(value)) return tagged('Object', Object.fromEntries(Object.entries(value).map(([key, entry]) => [text(key), encodeValue(entry, depth + 1, base64)])));
|
|
112
|
-
throw new TypeError('unsupported parameter value');
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function byteArray(payload) {
|
|
116
|
-
if (!Array.isArray(payload) || payload.length > 524_289 || payload.some(n => !Number.isInteger(n) || n < 0 || n > 255)) throw new TypeError('invalid byte array');
|
|
117
|
-
return Uint8Array.from(payload);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function base64Bytes(payload) {
|
|
121
|
-
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');
|
|
122
|
-
const binary = atob(payload);
|
|
123
|
-
const bytes = new Uint8Array(binary.length);
|
|
124
|
-
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
125
|
-
if (toBase64(bytes) !== payload) throw new TypeError('invalid base64 bytes');
|
|
126
|
-
return bytes;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function toBase64(bytes) {
|
|
130
|
-
let binary = '';
|
|
131
|
-
for (let i = 0; i < bytes.length; i += 32_768) binary += String.fromCharCode(...bytes.subarray(i, i + 32_768));
|
|
132
|
-
return btoa(binary);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function encodeFloat(value) {
|
|
136
|
-
const view = new DataView(new ArrayBuffer(8));
|
|
137
|
-
view.setFloat64(0, value);
|
|
138
|
-
let hex = '';
|
|
139
|
-
for (let i = 0; i < 8; i++) hex += view.getUint8(i).toString(16).padStart(2, '0');
|
|
140
|
-
return { type: 'Number', value: hex };
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function decodeValue(value, depth = 0) {
|
|
144
|
-
checkDepth(depth);
|
|
145
|
-
if (!plainObject(value) || typeof value.type !== 'string') throw new TypeError('invalid tagged value');
|
|
146
|
-
const keys = Object.keys(value);
|
|
147
|
-
if (value.type === 'Null') {
|
|
148
|
-
if (keys.length !== 1) throw new TypeError('invalid Null value');
|
|
149
|
-
return null;
|
|
150
|
-
}
|
|
151
|
-
if (keys.length !== 2 || !Object.hasOwn(value, 'value')) throw new TypeError('invalid tagged value fields');
|
|
152
|
-
const payload = value.value;
|
|
153
|
-
switch (value.type) {
|
|
154
|
-
case 'Boolean':
|
|
155
|
-
if (typeof payload !== 'boolean') throw new TypeError('invalid Boolean value');
|
|
156
|
-
return payload;
|
|
157
|
-
case 'String':
|
|
158
|
-
if (typeof payload !== 'string') throw new TypeError('invalid String value');
|
|
159
|
-
return payload;
|
|
160
|
-
case 'Integer': {
|
|
161
|
-
if (typeof payload !== 'string' || !/^(?:0|-?[1-9][0-9]{0,18})$/.test(payload)) throw new TypeError('invalid canonical int64');
|
|
162
|
-
const integer = BigInt(payload);
|
|
163
|
-
checkInteger(integer);
|
|
164
|
-
return integer;
|
|
165
|
-
}
|
|
166
|
-
case 'Number': {
|
|
167
|
-
if (typeof payload !== 'string' || !/^[0-9a-f]{16}$/.test(payload)) throw new TypeError('invalid float64 bits');
|
|
168
|
-
const view = new DataView(new ArrayBuffer(8));
|
|
169
|
-
for (let i = 0; i < 8; i++) view.setUint8(i, parseInt(payload.slice(i * 2, i * 2 + 2), 16));
|
|
170
|
-
return new Float64(view.getFloat64(0));
|
|
171
|
-
}
|
|
172
|
-
case 'Binary': case 'Vector': {
|
|
173
|
-
const bytes = typeof payload === 'string' ? base64Bytes(payload) : byteArray(payload);
|
|
174
|
-
return value.type === 'Vector' ? new Vector(bytes) : bytes;
|
|
175
|
-
}
|
|
176
|
-
case 'Record': {
|
|
177
|
-
if (!plainObject(payload) || Object.keys(payload).length !== 2 || !Object.hasOwn(payload, 'table') || !Object.hasOwn(payload, 'key')) throw new TypeError('invalid Record value');
|
|
178
|
-
// Historical Cloud receipts keep their original enum-key encoding.
|
|
179
|
-
if (plainObject(payload.key) && Object.keys(payload.key).length === 1) {
|
|
180
|
-
const kind = Object.keys(payload.key)[0];
|
|
181
|
-
if (['String', 'Integer'].includes(kind)) return new Record(payload.table, decodeValue({ type: kind, value: payload.key[kind] }));
|
|
182
|
-
}
|
|
183
|
-
if (!['String', 'Integer'].includes(payload.key?.type)) throw new TypeError('invalid Record key');
|
|
184
|
-
return new Record(payload.table, decodeValue(payload.key));
|
|
185
|
-
}
|
|
186
|
-
case 'Array':
|
|
187
|
-
if (!Array.isArray(payload)) throw new TypeError('invalid Array value');
|
|
188
|
-
return payload.map(entry => decodeValue(entry, depth + 1));
|
|
189
|
-
case 'Object':
|
|
190
|
-
if (!plainObject(payload)) throw new TypeError('invalid Object value');
|
|
191
|
-
return Object.fromEntries(Object.entries(payload).map(([key, entry]) => [key, decodeValue(entry, depth + 1)]));
|
|
192
|
-
default: throw new TypeError('unsupported value tag');
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function text(value) {
|
|
197
|
-
if (!value.isWellFormed()) throw new TypeError('strings must be well-formed Unicode');
|
|
198
|
-
return value;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function checkInteger(value) {
|
|
202
|
-
if (value < -(1n << 63n) || value >= (1n << 63n)) throw new RangeError('integer outside int64');
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function checkDepth(depth) {
|
|
206
|
-
if (depth > 64) throw new RangeError('value nesting exceeds 64');
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function checkCopyOptions(options) {
|
|
210
|
-
const { emptyTables, newIdentity } = options;
|
|
211
|
-
if (emptyTables !== undefined && (!Array.isArray(emptyTables) || emptyTables.length > 256 || emptyTables.some(name => typeof name !== 'string' || !name || name.length > 255 || /[,\0]/.test(name)))) {
|
|
212
|
-
throw new TypeError('emptyTables takes up to 256 table names without commas');
|
|
213
|
-
}
|
|
214
|
-
if (newIdentity !== undefined && typeof newIdentity !== 'boolean') throw new TypeError('newIdentity must be a boolean');
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function plainObject(value) {
|
|
218
|
-
return value !== null && typeof value === 'object' && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
module.exports = { Float64, Record, Vector, encodeValue, decodeValue, plainObject, text, checkCopyOptions };
|
|
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; /** `table:key`. */ toString(): string; /** The same table and key, including the key's type. */ equals(other: unknown): boolean; }
|
|
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,230 +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
|
-
toString() { return `${this.table}:${this.key}`; }
|
|
26
|
-
equals(other) { return other instanceof Record && other.table === this.table && other.key === this.key; }
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
class Vector {
|
|
30
|
-
#bytes;
|
|
31
|
-
constructor(bytes) {
|
|
32
|
-
if (!(bytes instanceof Uint8Array) || bytes.byteLength < 1 || bytes.byteLength > 524_289) throw new TypeError('Vector requires bounded encoded bytes');
|
|
33
|
-
this.#bytes = new Uint8Array(bytes);
|
|
34
|
-
Object.freeze(this);
|
|
35
|
-
}
|
|
36
|
-
get bytes() { return new Uint8Array(this.#bytes); }
|
|
37
|
-
static float32(values) { return denseVector(values, 4); }
|
|
38
|
-
static float64(values) { return denseVector(values, 8); }
|
|
39
|
-
static sparse32Entries(dimensions, entries) {
|
|
40
|
-
checkDimensions(dimensions);
|
|
41
|
-
if (!Array.isArray(entries) || entries.length > dimensions) throw new TypeError('sparse entries require bounded index/value pairs');
|
|
42
|
-
let previous = -1;
|
|
43
|
-
const nonzero = [];
|
|
44
|
-
for (const entry of entries) {
|
|
45
|
-
if (!Array.isArray(entry) || entry.length !== 2) throw new TypeError('sparse entries require index/value pairs');
|
|
46
|
-
const [index, value] = entry;
|
|
47
|
-
if (!Number.isInteger(index) || index <= previous || index >= dimensions) throw new RangeError('sparse indices must be increasing and within dimensions');
|
|
48
|
-
checkComponent(value, 4);
|
|
49
|
-
const rounded = Math.fround(value);
|
|
50
|
-
if (rounded !== 0) nonzero.push([index, rounded]);
|
|
51
|
-
previous = index;
|
|
52
|
-
}
|
|
53
|
-
const bytes = new Uint8Array(nonzero.length * 8 + 5);
|
|
54
|
-
const view = new DataView(bytes.buffer);
|
|
55
|
-
nonzero.forEach(([index, value], i) => {
|
|
56
|
-
view.setFloat32(i * 4, value, true);
|
|
57
|
-
view.setUint32(nonzero.length * 4 + i * 4, index, true);
|
|
58
|
-
});
|
|
59
|
-
view.setUint32(nonzero.length * 8, dimensions, true);
|
|
60
|
-
bytes[bytes.length - 1] = 9;
|
|
61
|
-
return new Vector(bytes);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function denseVector(values, width) {
|
|
66
|
-
if (!Array.isArray(values) && !(values instanceof Float32Array) && !(values instanceof Float64Array)) throw new TypeError('vector components require a number array');
|
|
67
|
-
checkDimensions(values.length);
|
|
68
|
-
const bytes = new Uint8Array(values.length * width + (width === 8 ? 1 : 0));
|
|
69
|
-
const view = new DataView(bytes.buffer);
|
|
70
|
-
for (let i = 0; i < values.length; i++) {
|
|
71
|
-
checkComponent(values[i], width);
|
|
72
|
-
if (width === 4) view.setFloat32(i * width, values[i], true);
|
|
73
|
-
else view.setFloat64(i * width, values[i], true);
|
|
74
|
-
}
|
|
75
|
-
if (width === 8) bytes[bytes.length - 1] = 2;
|
|
76
|
-
return new Vector(bytes);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function checkDimensions(dimensions) {
|
|
80
|
-
if (!Number.isInteger(dimensions) || dimensions < 1 || dimensions > 65_536) throw new RangeError('vector dimensions must be 1..65536');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function checkComponent(value, width) {
|
|
84
|
-
if (typeof value !== 'number' || !Number.isFinite(value)) throw new TypeError('vector components must be finite');
|
|
85
|
-
if (width === 4 && !Number.isFinite(Math.fround(value))) throw new RangeError('vector component is outside the finite float32 range');
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function encodeValue(value, depth = 0, base64 = false) {
|
|
89
|
-
checkDepth(depth);
|
|
90
|
-
const tagged = (type, value) => ({ type, value });
|
|
91
|
-
if (value === null) return { type: 'Null' };
|
|
92
|
-
if (typeof value === 'boolean') return tagged('Boolean', value);
|
|
93
|
-
if (typeof value === 'string') return tagged('String', text(value));
|
|
94
|
-
if (typeof value === 'bigint') { checkInteger(value); return tagged('Integer', value.toString()); }
|
|
95
|
-
if (value instanceof Float64) return encodeFloat(value.value);
|
|
96
|
-
if (typeof value === 'number') {
|
|
97
|
-
if (!Number.isFinite(value)) throw new TypeError('non-finite number');
|
|
98
|
-
if (Number.isInteger(value) && !Object.is(value, -0)) {
|
|
99
|
-
if (!Number.isSafeInteger(value)) throw new RangeError('use bigint or Float64 for integers outside the safe Number range');
|
|
100
|
-
return tagged('Integer', String(value));
|
|
101
|
-
}
|
|
102
|
-
return encodeFloat(value);
|
|
103
|
-
}
|
|
104
|
-
if (value instanceof Record) return tagged('Record', { table: text(value.table), key: encodeValue(value.key) });
|
|
105
|
-
if (value instanceof Vector) return tagged('Vector', base64 ? toBase64(value.bytes) : [...value.bytes]);
|
|
106
|
-
if (value instanceof Uint8Array) return tagged('Binary', base64 ? toBase64(value) : [...value]);
|
|
107
|
-
if (Array.isArray(value)) {
|
|
108
|
-
const entries = [];
|
|
109
|
-
for (let i = 0; i < value.length; i++) {
|
|
110
|
-
if (!Object.hasOwn(value, i)) throw new TypeError('array holes are not database values');
|
|
111
|
-
entries.push(encodeValue(value[i], depth + 1, base64));
|
|
112
|
-
}
|
|
113
|
-
return tagged('Array', entries);
|
|
114
|
-
}
|
|
115
|
-
if (plainObject(value)) return tagged('Object', Object.fromEntries(Object.entries(value).map(([key, entry]) => [text(key), encodeValue(entry, depth + 1, base64)])));
|
|
116
|
-
throw new TypeError('unsupported parameter value');
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function byteArray(payload) {
|
|
120
|
-
if (!Array.isArray(payload) || payload.length > 524_289 || payload.some(n => !Number.isInteger(n) || n < 0 || n > 255)) throw new TypeError('invalid byte array');
|
|
121
|
-
return Uint8Array.from(payload);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function base64Bytes(payload) {
|
|
125
|
-
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');
|
|
126
|
-
const binary = atob(payload);
|
|
127
|
-
const bytes = new Uint8Array(binary.length);
|
|
128
|
-
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
129
|
-
if (toBase64(bytes) !== payload) throw new TypeError('invalid base64 bytes');
|
|
130
|
-
return bytes;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function toBase64(bytes) {
|
|
134
|
-
let binary = '';
|
|
135
|
-
for (let i = 0; i < bytes.length; i += 32_768) binary += String.fromCharCode(...bytes.subarray(i, i + 32_768));
|
|
136
|
-
return btoa(binary);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function encodeFloat(value) {
|
|
140
|
-
const view = new DataView(new ArrayBuffer(8));
|
|
141
|
-
view.setFloat64(0, value);
|
|
142
|
-
let hex = '';
|
|
143
|
-
for (let i = 0; i < 8; i++) hex += view.getUint8(i).toString(16).padStart(2, '0');
|
|
144
|
-
return { type: 'Number', value: hex };
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function decodeValue(value, depth = 0) {
|
|
148
|
-
checkDepth(depth);
|
|
149
|
-
if (!plainObject(value) || typeof value.type !== 'string') throw new TypeError('invalid tagged value');
|
|
150
|
-
const keys = Object.keys(value);
|
|
151
|
-
if (value.type === 'Null') {
|
|
152
|
-
if (keys.length !== 1) throw new TypeError('invalid Null value');
|
|
153
|
-
return null;
|
|
154
|
-
}
|
|
155
|
-
if (keys.length !== 2 || !Object.hasOwn(value, 'value')) throw new TypeError('invalid tagged value fields');
|
|
156
|
-
const payload = value.value;
|
|
157
|
-
switch (value.type) {
|
|
158
|
-
case 'Boolean':
|
|
159
|
-
if (typeof payload !== 'boolean') throw new TypeError('invalid Boolean value');
|
|
160
|
-
return payload;
|
|
161
|
-
case 'String':
|
|
162
|
-
if (typeof payload !== 'string') throw new TypeError('invalid String value');
|
|
163
|
-
return payload;
|
|
164
|
-
case 'Integer': {
|
|
165
|
-
if (typeof payload !== 'string' || !/^(?:0|-?[1-9][0-9]{0,18})$/.test(payload)) throw new TypeError('invalid canonical int64');
|
|
166
|
-
const integer = BigInt(payload);
|
|
167
|
-
checkInteger(integer);
|
|
168
|
-
return integer;
|
|
169
|
-
}
|
|
170
|
-
case 'Number': {
|
|
171
|
-
if (typeof payload !== 'string' || !/^[0-9a-f]{16}$/.test(payload)) throw new TypeError('invalid float64 bits');
|
|
172
|
-
const view = new DataView(new ArrayBuffer(8));
|
|
173
|
-
for (let i = 0; i < 8; i++) view.setUint8(i, parseInt(payload.slice(i * 2, i * 2 + 2), 16));
|
|
174
|
-
return new Float64(view.getFloat64(0));
|
|
175
|
-
}
|
|
176
|
-
case 'Binary': case 'Vector': {
|
|
177
|
-
const bytes = typeof payload === 'string' ? base64Bytes(payload) : byteArray(payload);
|
|
178
|
-
return value.type === 'Vector' ? new Vector(bytes) : bytes;
|
|
179
|
-
}
|
|
180
|
-
case 'Record': {
|
|
181
|
-
if (!plainObject(payload) || Object.keys(payload).length !== 2 || !Object.hasOwn(payload, 'table') || !Object.hasOwn(payload, 'key')) throw new TypeError('invalid Record value');
|
|
182
|
-
// Historical Cloud receipts keep their original enum-key encoding.
|
|
183
|
-
if (plainObject(payload.key) && Object.keys(payload.key).length === 1) {
|
|
184
|
-
const kind = Object.keys(payload.key)[0];
|
|
185
|
-
if (['String', 'Integer'].includes(kind)) return new Record(payload.table, decodeValue({ type: kind, value: payload.key[kind] }));
|
|
186
|
-
}
|
|
187
|
-
if (!['String', 'Integer'].includes(payload.key?.type)) throw new TypeError('invalid Record key');
|
|
188
|
-
return new Record(payload.table, decodeValue(payload.key));
|
|
189
|
-
}
|
|
190
|
-
case 'Array':
|
|
191
|
-
if (!Array.isArray(payload)) throw new TypeError('invalid Array value');
|
|
192
|
-
return payload.map(entry => decodeValue(entry, depth + 1));
|
|
193
|
-
case 'Object':
|
|
194
|
-
if (!plainObject(payload)) throw new TypeError('invalid Object value');
|
|
195
|
-
return Object.fromEntries(Object.entries(payload).map(([key, entry]) => [key, decodeValue(entry, depth + 1)]));
|
|
196
|
-
default: throw new TypeError('unsupported value tag');
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function text(value) {
|
|
201
|
-
if (!value.isWellFormed()) throw new TypeError('strings must be well-formed Unicode');
|
|
202
|
-
return value;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function checkInteger(value) {
|
|
206
|
-
if (value < -(1n << 63n) || value >= (1n << 63n)) throw new RangeError('integer outside int64');
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function checkDepth(depth) {
|
|
210
|
-
if (depth > 64) throw new RangeError('value nesting exceeds 64');
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function checkCopyOptions(options) {
|
|
214
|
-
const { emptyTables, newIdentity } = options;
|
|
215
|
-
if (emptyTables !== undefined && (!Array.isArray(emptyTables) || emptyTables.length > 256 || emptyTables.some(name => typeof name !== 'string' || !name || name.length > 255 || /[,\0]/.test(name)))) {
|
|
216
|
-
throw new TypeError('emptyTables takes up to 256 table names without commas');
|
|
217
|
-
}
|
|
218
|
-
if (newIdentity !== undefined && typeof newIdentity !== 'boolean') throw new TypeError('newIdentity must be a boolean');
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function plainObject(value) {
|
|
222
|
-
return value !== null && typeof value === 'object' && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
module.exports = { Float64, Record, Vector, encodeValue, decodeValue, plainObject, text, checkCopyOptions };
|
|
226
|
-
})(commonjs, commonjs.exports, specifier => {
|
|
227
|
-
throw new TypeError(`Cannot load ${specifier}`);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
export const { Float64, Record, Vector, encodeValue, decodeValue, plainObject, text, checkCopyOptions } = commonjs.exports;
|
|
1
|
+
export * from '@romedb/client/values';
|
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 };
|