@riffdb/client 0.2.0 → 0.2.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/README.md +3 -3
- package/dist/driver.d.ts +25 -0
- package/dist/driver.js +239 -27
- package/dist/index.d.ts +48 -3
- package/dist/index.js +484 -55
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,8 +7,8 @@ compiler IDs, storage keys, or an authorization bypass.
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npm install --save-dev @riffdb/cli@0.1
|
|
11
|
-
npm install @riffdb/client@0.1
|
|
10
|
+
npm install --save-dev @riffdb/cli@0.2.1
|
|
11
|
+
npm install @riffdb/client@0.2.1
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
## Four-step application story
|
|
@@ -40,6 +40,6 @@ response must be resolved with the same idempotency key.
|
|
|
40
40
|
|
|
41
41
|
## Version compatibility
|
|
42
42
|
|
|
43
|
-
This is a 0.
|
|
43
|
+
This is a 0.2.x pre-1.0 package. It carries the exact durable-format statement
|
|
44
44
|
from `release/durable-format-manifest-v1.json`; downgrade is unsupported and a
|
|
45
45
|
breaking epoch requires symbolic export/reimport. Sealed and explicitly offline bundles retain vendored @riffdb/application, riffdb-application, riffdb.dev/application, and the source-contained Rust SDK.
|
package/dist/driver.d.ts
CHANGED
|
@@ -59,6 +59,18 @@ export interface ExactMoneyValue<Currency extends string = string> {
|
|
|
59
59
|
export declare function exactDecimal(value: string, precision: number, scale: number): ExactDecimalValue;
|
|
60
60
|
export declare function exactMoney<const Currency extends string>(currency: Currency, value: string): ExactMoneyValue<Currency>;
|
|
61
61
|
export interface DriverApplicationIdentity {
|
|
62
|
+
readonly applicationManifestHash?: string;
|
|
63
|
+
readonly operationCatalogHash?: string;
|
|
64
|
+
readonly contractLineage: string;
|
|
65
|
+
readonly contractVersion: bigint;
|
|
66
|
+
readonly contractBundleHash: string;
|
|
67
|
+
readonly database?: string;
|
|
68
|
+
readonly role: string;
|
|
69
|
+
readonly roleDefinitionHash?: string;
|
|
70
|
+
readonly remoteIdentityHash?: string;
|
|
71
|
+
readonly tenant?: string;
|
|
72
|
+
}
|
|
73
|
+
export interface ResolvedDriverApplicationIdentity {
|
|
62
74
|
readonly applicationManifestHash: string;
|
|
63
75
|
readonly operationCatalogHash: string;
|
|
64
76
|
readonly contractLineage: string;
|
|
@@ -75,6 +87,15 @@ export interface DriverApplicationTransportOptions {
|
|
|
75
87
|
readonly identity: DriverApplicationIdentity;
|
|
76
88
|
readonly tenant?: string;
|
|
77
89
|
}
|
|
90
|
+
export interface DriverConnectFromEnvOptions {
|
|
91
|
+
readonly socketPath?: string;
|
|
92
|
+
readonly identity?: Partial<DriverApplicationIdentity>;
|
|
93
|
+
readonly role?: string;
|
|
94
|
+
readonly database?: string;
|
|
95
|
+
readonly tenant?: string;
|
|
96
|
+
readonly prefix?: string;
|
|
97
|
+
readonly envPrefix?: string;
|
|
98
|
+
}
|
|
78
99
|
export interface DriverOperation {
|
|
79
100
|
readonly name: string;
|
|
80
101
|
readonly inputSchemaHash: string;
|
|
@@ -163,9 +184,13 @@ export declare class DriverApplicationTransport {
|
|
|
163
184
|
private constructor();
|
|
164
185
|
/** Returns the bound tenant identifier when this session is tenant-scoped. */
|
|
165
186
|
get tenant(): string | undefined;
|
|
187
|
+
/** Returns the resolved application identity verified during driver handshake. */
|
|
188
|
+
get identity(): ResolvedDriverApplicationIdentity;
|
|
166
189
|
static connect(options: DriverApplicationTransportOptions): Promise<DriverApplicationTransport>;
|
|
190
|
+
static connectFromEnv(options?: DriverConnectFromEnvOptions): Promise<DriverApplicationTransport>;
|
|
167
191
|
invoke(operation: DriverOperation, input: Readonly<Record<string, DriverValue>>, options?: DriverInvokeOptions): Promise<DriverResult>;
|
|
168
192
|
batch(operation: DriverOperation, items: ReadonlyArray<Readonly<Record<string, DriverValue>>>, concurrency: number, checkpoint: number, options?: DriverInvokeOptions): Promise<DriverBatchResult>;
|
|
169
193
|
/** Stops new work, sends a clean local EOF, and rejects unfinished waits. */
|
|
170
194
|
shutdown(): Promise<void>;
|
|
171
195
|
}
|
|
196
|
+
export declare function resolveTransportOptionsFromEnv(options?: DriverConnectFromEnvOptions): DriverApplicationTransportOptions;
|
package/dist/driver.js
CHANGED
|
@@ -63,10 +63,12 @@ export class DriverApplicationError extends Error {
|
|
|
63
63
|
export class DriverApplicationTransport {
|
|
64
64
|
#socket;
|
|
65
65
|
#identity;
|
|
66
|
+
#resolvedIdentity;
|
|
66
67
|
#tenant;
|
|
67
68
|
#requestPrefix;
|
|
68
69
|
#pending = new Map();
|
|
69
|
-
#
|
|
70
|
+
#inputChunks = [];
|
|
71
|
+
#inputLength = 0;
|
|
70
72
|
#nextRequest = 1;
|
|
71
73
|
#closed = false;
|
|
72
74
|
constructor(socket, identity, tenant) {
|
|
@@ -83,6 +85,13 @@ export class DriverApplicationTransport {
|
|
|
83
85
|
get tenant() {
|
|
84
86
|
return this.#tenant;
|
|
85
87
|
}
|
|
88
|
+
/** Returns the resolved application identity verified during driver handshake. */
|
|
89
|
+
get identity() {
|
|
90
|
+
if (this.#resolvedIdentity === undefined) {
|
|
91
|
+
throw new Error("RiffDB driver session has not completed handshake");
|
|
92
|
+
}
|
|
93
|
+
return this.#resolvedIdentity;
|
|
94
|
+
}
|
|
86
95
|
static async connect(options) {
|
|
87
96
|
const tenant = resolveTransportTenant(options);
|
|
88
97
|
validateTransportOptions(options);
|
|
@@ -102,6 +111,9 @@ export class DriverApplicationTransport {
|
|
|
102
111
|
throw error;
|
|
103
112
|
}
|
|
104
113
|
}
|
|
114
|
+
static async connectFromEnv(options = {}) {
|
|
115
|
+
return DriverApplicationTransport.connect(resolveTransportOptionsFromEnv(options));
|
|
116
|
+
}
|
|
105
117
|
async invoke(operation, input, options = {}) {
|
|
106
118
|
validateOperation(operation);
|
|
107
119
|
validateDriverRecord(input);
|
|
@@ -138,7 +150,7 @@ export class DriverApplicationTransport {
|
|
|
138
150
|
checkpoint,
|
|
139
151
|
options: lowerOptions(options),
|
|
140
152
|
}, requestId, options.signal);
|
|
141
|
-
return decodeBatchResult(response);
|
|
153
|
+
return decodeBatchResult(response, items.length, checkpoint);
|
|
142
154
|
}
|
|
143
155
|
/** Stops new work, sends a clean local EOF, and rejects unfinished waits. */
|
|
144
156
|
async shutdown() {
|
|
@@ -157,35 +169,63 @@ export class DriverApplicationTransport {
|
|
|
157
169
|
async #handshake() {
|
|
158
170
|
const requestId = this.#requestId("handshake");
|
|
159
171
|
const identity = this.#identity;
|
|
172
|
+
const manifestHash = identity.applicationManifestHash ?? "";
|
|
173
|
+
const catalogHash = identity.operationCatalogHash ?? "";
|
|
174
|
+
const database = identity.database ?? "";
|
|
175
|
+
const roleDefinitionHash = identity.roleDefinitionHash ?? "";
|
|
176
|
+
const remoteIdentityHash = identity.remoteIdentityHash ?? "";
|
|
160
177
|
const response = await this.#request({
|
|
161
178
|
type: "handshake",
|
|
162
179
|
request_id: requestId,
|
|
163
180
|
protocol_version: DRIVER_PROTOCOL_VERSION,
|
|
164
|
-
application_manifest_hash:
|
|
165
|
-
operation_catalog_hash:
|
|
181
|
+
application_manifest_hash: manifestHash,
|
|
182
|
+
operation_catalog_hash: catalogHash,
|
|
166
183
|
contract_lineage: identity.contractLineage,
|
|
167
184
|
contract_version: identity.contractVersion,
|
|
168
185
|
contract_bundle_hash: identity.contractBundleHash,
|
|
169
186
|
value_registry_hash: DRIVER_VALUE_REGISTRY_HASH,
|
|
170
187
|
error_registry_hash: DRIVER_ERROR_REGISTRY_HASH,
|
|
171
|
-
database
|
|
188
|
+
database,
|
|
172
189
|
role: identity.role,
|
|
173
|
-
role_definition_hash:
|
|
174
|
-
remote_identity_hash:
|
|
190
|
+
role_definition_hash: roleDefinitionHash,
|
|
191
|
+
remote_identity_hash: remoteIdentityHash,
|
|
175
192
|
}, requestId);
|
|
176
193
|
if (response.type !== "handshake"
|
|
177
194
|
|| response.protocol_version !== DRIVER_PROTOCOL_VERSION
|
|
178
|
-
|| response.application_manifest_hash !==
|
|
179
|
-
|| response.
|
|
195
|
+
|| typeof response.application_manifest_hash !== "string"
|
|
196
|
+
|| !HASH.test(response.application_manifest_hash)
|
|
197
|
+
|| (manifestHash !== "" && response.application_manifest_hash !== manifestHash)
|
|
198
|
+
|| typeof response.operation_catalog_hash !== "string"
|
|
199
|
+
|| !HASH.test(response.operation_catalog_hash)
|
|
200
|
+
|| (catalogHash !== "" && response.operation_catalog_hash !== catalogHash)
|
|
180
201
|
|| response.contract_lineage !== identity.contractLineage
|
|
181
202
|
|| requiredPositiveBigInt(response.contract_version) !== identity.contractVersion
|
|
182
203
|
|| response.contract_bundle_hash !== identity.contractBundleHash
|
|
183
|
-
|| response.database !==
|
|
204
|
+
|| typeof response.database !== "string"
|
|
205
|
+
|| !SYMBOL.test(response.database)
|
|
206
|
+
|| (database !== "" && response.database !== database)
|
|
184
207
|
|| response.role !== identity.role
|
|
185
|
-
|| response.role_definition_hash !==
|
|
186
|
-
|| response.
|
|
208
|
+
|| typeof response.role_definition_hash !== "string"
|
|
209
|
+
|| !HASH.test(response.role_definition_hash)
|
|
210
|
+
|| (roleDefinitionHash !== "" && response.role_definition_hash !== roleDefinitionHash)
|
|
211
|
+
|| typeof response.remote_identity_hash !== "string"
|
|
212
|
+
|| !HASH.test(response.remote_identity_hash)
|
|
213
|
+
|| (remoteIdentityHash !== "" && response.remote_identity_hash !== remoteIdentityHash)) {
|
|
187
214
|
throw new Error("RiffDB driver identity mismatch");
|
|
188
215
|
}
|
|
216
|
+
this.#resolvedIdentity = {
|
|
217
|
+
applicationManifestHash: response.application_manifest_hash,
|
|
218
|
+
operationCatalogHash: response.operation_catalog_hash,
|
|
219
|
+
contractLineage: identity.contractLineage,
|
|
220
|
+
contractVersion: identity.contractVersion,
|
|
221
|
+
contractBundleHash: identity.contractBundleHash,
|
|
222
|
+
database: response.database,
|
|
223
|
+
role: identity.role,
|
|
224
|
+
roleDefinitionHash: response.role_definition_hash,
|
|
225
|
+
remoteIdentityHash: response.remote_identity_hash,
|
|
226
|
+
...(this.#tenant === undefined ? {} : { tenant: this.#tenant }),
|
|
227
|
+
};
|
|
228
|
+
this.#identity = this.#resolvedIdentity;
|
|
189
229
|
}
|
|
190
230
|
async #request(request, requestId, signal) {
|
|
191
231
|
if (this.#closed || this.#socket.destroyed)
|
|
@@ -230,31 +270,37 @@ export class DriverApplicationTransport {
|
|
|
230
270
|
}
|
|
231
271
|
}
|
|
232
272
|
#receive(chunk) {
|
|
233
|
-
if (this.#closed)
|
|
273
|
+
if (this.#closed || chunk.length === 0)
|
|
234
274
|
return;
|
|
235
|
-
this.#
|
|
236
|
-
|
|
237
|
-
|
|
275
|
+
this.#inputChunks.push(chunk);
|
|
276
|
+
this.#inputLength += chunk.length;
|
|
277
|
+
while (this.#inputLength >= 4) {
|
|
278
|
+
const length = this.#peekFrameLength();
|
|
238
279
|
if (length < 2 || length > MAX_FRAME_BYTES) {
|
|
280
|
+
this.#inputChunks.length = 0;
|
|
281
|
+
this.#inputLength = 0;
|
|
239
282
|
this.#fail(new Error("RiffDB driver returned an invalid frame"));
|
|
240
283
|
this.#socket.destroy();
|
|
241
284
|
return;
|
|
242
285
|
}
|
|
243
|
-
if (this.#
|
|
286
|
+
if (this.#inputLength < length + 4)
|
|
244
287
|
return;
|
|
245
|
-
const body = this.#
|
|
246
|
-
this.#input = this.#input.subarray(length + 4);
|
|
288
|
+
const body = this.#consumeFrame(length);
|
|
247
289
|
let response;
|
|
248
290
|
try {
|
|
249
291
|
response = exactObject(decodeJson(UTF8_DECODER.decode(body)));
|
|
250
292
|
}
|
|
251
293
|
catch {
|
|
294
|
+
this.#inputChunks.length = 0;
|
|
295
|
+
this.#inputLength = 0;
|
|
252
296
|
this.#fail(new Error("RiffDB driver returned an invalid message"));
|
|
253
297
|
this.#socket.destroy();
|
|
254
298
|
return;
|
|
255
299
|
}
|
|
256
300
|
const requestId = response.request_id;
|
|
257
301
|
if (typeof requestId !== "string" || !REQUEST_ID.test(requestId)) {
|
|
302
|
+
this.#inputChunks.length = 0;
|
|
303
|
+
this.#inputLength = 0;
|
|
258
304
|
this.#fail(new Error("RiffDB driver returned an invalid request identity"));
|
|
259
305
|
this.#socket.destroy();
|
|
260
306
|
return;
|
|
@@ -266,6 +312,80 @@ export class DriverApplicationTransport {
|
|
|
266
312
|
pending.resolve(response);
|
|
267
313
|
}
|
|
268
314
|
}
|
|
315
|
+
#peekFrameLength() {
|
|
316
|
+
const first = this.#inputChunks[0];
|
|
317
|
+
if (first.length >= 4) {
|
|
318
|
+
return first.readUInt32BE(0);
|
|
319
|
+
}
|
|
320
|
+
let b0 = 0;
|
|
321
|
+
let b1 = 0;
|
|
322
|
+
let b2 = 0;
|
|
323
|
+
let b3 = 0;
|
|
324
|
+
let idx = 0;
|
|
325
|
+
outer: for (const chunk of this.#inputChunks) {
|
|
326
|
+
for (let i = 0; i < chunk.length; i += 1) {
|
|
327
|
+
const byte = chunk[i];
|
|
328
|
+
if (idx === 0)
|
|
329
|
+
b0 = byte;
|
|
330
|
+
else if (idx === 1)
|
|
331
|
+
b1 = byte;
|
|
332
|
+
else if (idx === 2)
|
|
333
|
+
b2 = byte;
|
|
334
|
+
else {
|
|
335
|
+
b3 = byte;
|
|
336
|
+
break outer;
|
|
337
|
+
}
|
|
338
|
+
idx += 1;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return ((b0 << 24) | (b1 << 16) | (b2 << 8) | b3) >>> 0;
|
|
342
|
+
}
|
|
343
|
+
#consumeFrame(bodyLength) {
|
|
344
|
+
this.#skipBytes(4);
|
|
345
|
+
const first = this.#inputChunks[0];
|
|
346
|
+
if (first.length >= bodyLength) {
|
|
347
|
+
const body = first.subarray(0, bodyLength);
|
|
348
|
+
if (first.length === bodyLength) {
|
|
349
|
+
this.#inputChunks.shift();
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
this.#inputChunks[0] = first.subarray(bodyLength);
|
|
353
|
+
}
|
|
354
|
+
this.#inputLength -= bodyLength;
|
|
355
|
+
return body;
|
|
356
|
+
}
|
|
357
|
+
const body = Buffer.allocUnsafe(bodyLength);
|
|
358
|
+
let offset = 0;
|
|
359
|
+
while (offset < bodyLength) {
|
|
360
|
+
const chunk = this.#inputChunks[0];
|
|
361
|
+
const take = Math.min(chunk.length, bodyLength - offset);
|
|
362
|
+
chunk.copy(body, offset, 0, take);
|
|
363
|
+
offset += take;
|
|
364
|
+
if (take === chunk.length) {
|
|
365
|
+
this.#inputChunks.shift();
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
this.#inputChunks[0] = chunk.subarray(take);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
this.#inputLength -= bodyLength;
|
|
372
|
+
return body;
|
|
373
|
+
}
|
|
374
|
+
#skipBytes(count) {
|
|
375
|
+
let remaining = count;
|
|
376
|
+
while (remaining > 0) {
|
|
377
|
+
const chunk = this.#inputChunks[0];
|
|
378
|
+
if (chunk.length <= remaining) {
|
|
379
|
+
remaining -= chunk.length;
|
|
380
|
+
this.#inputChunks.shift();
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
this.#inputChunks[0] = chunk.subarray(remaining);
|
|
384
|
+
remaining = 0;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
this.#inputLength -= count;
|
|
388
|
+
}
|
|
269
389
|
#fail(error) {
|
|
270
390
|
for (const pending of this.#pending.values())
|
|
271
391
|
pending.reject(error);
|
|
@@ -675,20 +795,30 @@ function decodeResult(response) {
|
|
|
675
795
|
...(cursor === undefined ? {} : { cursor }),
|
|
676
796
|
};
|
|
677
797
|
}
|
|
678
|
-
function decodeBatchResult(response) {
|
|
798
|
+
function decodeBatchResult(response, expectedTotal, requestedCheckpoint) {
|
|
679
799
|
if (response.type !== "batch_result" || !Array.isArray(response.items)
|
|
680
800
|
|| response.items.length > MAX_COLLECTION_ITEMS) {
|
|
681
801
|
throw new Error("RiffDB driver returned an invalid batch result");
|
|
682
802
|
}
|
|
683
803
|
const checkpoint = boundedInteger(response.checkpoint, 0, MAX_COLLECTION_ITEMS);
|
|
684
804
|
const total = boundedInteger(response.total, 1, MAX_COLLECTION_ITEMS);
|
|
685
|
-
if (checkpoint > total
|
|
805
|
+
if (total !== expectedTotal || checkpoint < requestedCheckpoint || checkpoint > total
|
|
806
|
+
|| response.items.length !== total - requestedCheckpoint) {
|
|
686
807
|
throw new Error("RiffDB driver returned an invalid batch result");
|
|
808
|
+
}
|
|
809
|
+
const seenIndices = new Set();
|
|
687
810
|
const items = response.items.map((raw) => {
|
|
688
811
|
const item = exactObject(raw);
|
|
689
|
-
const index = boundedInteger(item.index,
|
|
812
|
+
const index = boundedInteger(item.index, requestedCheckpoint, total - 1);
|
|
813
|
+
if (seenIndices.has(index)) {
|
|
814
|
+
throw new Error("RiffDB driver returned an invalid batch item");
|
|
815
|
+
}
|
|
816
|
+
seenIndices.add(index);
|
|
690
817
|
const outcome = exactObject(item.outcome);
|
|
691
818
|
if (outcome.type === "result") {
|
|
819
|
+
if (index === checkpoint) {
|
|
820
|
+
throw new Error("RiffDB driver returned an invalid batch result");
|
|
821
|
+
}
|
|
692
822
|
const value = validateDriverValue(outcome.value, 0);
|
|
693
823
|
const commitSequence = optionalPositiveBigInt(outcome.commit_sequence);
|
|
694
824
|
const outcomeUri = optionalBoundedString(outcome.outcome_uri, 16_384);
|
|
@@ -704,8 +834,12 @@ function decodeBatchResult(response) {
|
|
|
704
834
|
},
|
|
705
835
|
};
|
|
706
836
|
}
|
|
707
|
-
if (outcome.type === "error")
|
|
837
|
+
if (outcome.type === "error") {
|
|
838
|
+
if (index < checkpoint) {
|
|
839
|
+
throw new Error("RiffDB driver returned an invalid batch result");
|
|
840
|
+
}
|
|
708
841
|
return { index, error: decodeError(outcome) };
|
|
842
|
+
}
|
|
709
843
|
throw new Error("RiffDB driver returned an invalid batch item");
|
|
710
844
|
});
|
|
711
845
|
return { items, checkpoint, total };
|
|
@@ -740,8 +874,10 @@ function decodeError(response) {
|
|
|
740
874
|
...(incidentId === undefined ? {} : { incidentId }),
|
|
741
875
|
});
|
|
742
876
|
}
|
|
877
|
+
const ENV_RESOLVED = Symbol("riffdb.envResolved");
|
|
743
878
|
function resolveTransportTenant(options) {
|
|
744
|
-
const
|
|
879
|
+
const allowEnvFallback = !(ENV_RESOLVED in options);
|
|
880
|
+
const envTenant = allowEnvFallback ? process.env.RIFFDB_TENANT : undefined;
|
|
745
881
|
const candidate = options.tenant ?? options.identity.tenant ?? (envTenant && envTenant.length > 0 ? envTenant : undefined);
|
|
746
882
|
if (candidate === undefined)
|
|
747
883
|
return undefined;
|
|
@@ -750,19 +886,95 @@ function resolveTransportTenant(options) {
|
|
|
750
886
|
}
|
|
751
887
|
return candidate;
|
|
752
888
|
}
|
|
889
|
+
export function resolveTransportOptionsFromEnv(options = {}) {
|
|
890
|
+
if (options.prefix !== undefined &&
|
|
891
|
+
options.envPrefix !== undefined &&
|
|
892
|
+
options.prefix !== options.envPrefix) {
|
|
893
|
+
throw new Error("conflicting RiffDB environment prefixes");
|
|
894
|
+
}
|
|
895
|
+
const rawPrefix = options.prefix ?? options.envPrefix;
|
|
896
|
+
if (rawPrefix !== undefined &&
|
|
897
|
+
(typeof rawPrefix !== "string" ||
|
|
898
|
+
rawPrefix.trim().length === 0 ||
|
|
899
|
+
!/^[A-Za-z_][A-Za-z0-9_]*$/.test(rawPrefix))) {
|
|
900
|
+
throw new Error("invalid RiffDB environment prefix");
|
|
901
|
+
}
|
|
902
|
+
const normalizedPrefix = rawPrefix === undefined
|
|
903
|
+
? "RIFFDB_"
|
|
904
|
+
: rawPrefix.endsWith("_")
|
|
905
|
+
? rawPrefix
|
|
906
|
+
: `${rawPrefix}_`;
|
|
907
|
+
const candidatePrefixes = normalizedPrefix === "RIFFDB_" || normalizedPrefix.endsWith("RIFFDB_")
|
|
908
|
+
? [normalizedPrefix]
|
|
909
|
+
: [`${normalizedPrefix}RIFFDB_`, normalizedPrefix];
|
|
910
|
+
const readEnv = (suffix) => {
|
|
911
|
+
for (const candidatePrefix of candidatePrefixes) {
|
|
912
|
+
const raw = process.env[`${candidatePrefix}${suffix}`];
|
|
913
|
+
if (raw !== undefined && raw.trim().length > 0) {
|
|
914
|
+
return raw.trim();
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
return undefined;
|
|
918
|
+
};
|
|
919
|
+
const socketPath = options.socketPath ?? readEnv("DRIVER_SOCKET") ?? "";
|
|
920
|
+
const base = options.identity ?? {};
|
|
921
|
+
const contractLineage = base.contractLineage ?? readEnv("LINEAGE") ?? "";
|
|
922
|
+
const rawVersion = base.contractVersion ?? readEnv("CONTRACT_VERSION");
|
|
923
|
+
let contractVersion = 0n;
|
|
924
|
+
if (typeof rawVersion === "bigint") {
|
|
925
|
+
contractVersion = rawVersion;
|
|
926
|
+
}
|
|
927
|
+
else if (typeof rawVersion === "string" && /^[0-9]+$/.test(rawVersion)) {
|
|
928
|
+
contractVersion = BigInt(rawVersion);
|
|
929
|
+
}
|
|
930
|
+
const contractBundleHash = base.contractBundleHash ?? readEnv("BUNDLE_HASH") ?? "";
|
|
931
|
+
const role = options.role ?? base.role ?? readEnv("ROLE") ?? "";
|
|
932
|
+
const database = options.database ?? base.database ?? readEnv("DATABASE");
|
|
933
|
+
const applicationManifestHash = base.applicationManifestHash ?? readEnv("MANIFEST_HASH");
|
|
934
|
+
const operationCatalogHash = base.operationCatalogHash ?? readEnv("CATALOG_HASH");
|
|
935
|
+
const roleDefinitionHash = base.roleDefinitionHash ?? readEnv("ROLE_HASH");
|
|
936
|
+
const remoteIdentityHash = base.remoteIdentityHash ?? readEnv("REMOTE_HASH");
|
|
937
|
+
const tenant = options.tenant ?? base.tenant ?? readEnv("TENANT");
|
|
938
|
+
const identity = {
|
|
939
|
+
contractLineage,
|
|
940
|
+
contractVersion,
|
|
941
|
+
contractBundleHash,
|
|
942
|
+
role,
|
|
943
|
+
...(database !== undefined && database.length > 0 ? { database } : {}),
|
|
944
|
+
...(applicationManifestHash !== undefined && applicationManifestHash.length > 0 ? { applicationManifestHash } : {}),
|
|
945
|
+
...(operationCatalogHash !== undefined && operationCatalogHash.length > 0 ? { operationCatalogHash } : {}),
|
|
946
|
+
...(roleDefinitionHash !== undefined && roleDefinitionHash.length > 0 ? { roleDefinitionHash } : {}),
|
|
947
|
+
...(remoteIdentityHash !== undefined && remoteIdentityHash.length > 0 ? { remoteIdentityHash } : {}),
|
|
948
|
+
...(tenant !== undefined && tenant.length > 0 ? { tenant } : {}),
|
|
949
|
+
};
|
|
950
|
+
const resolved = {
|
|
951
|
+
socketPath,
|
|
952
|
+
identity,
|
|
953
|
+
...(tenant !== undefined && tenant.length > 0 ? { tenant } : {}),
|
|
954
|
+
};
|
|
955
|
+
Object.defineProperty(resolved, ENV_RESOLVED, { value: true, enumerable: false });
|
|
956
|
+
return resolved;
|
|
957
|
+
}
|
|
753
958
|
function validateTransportOptions(options) {
|
|
754
959
|
if (!options.socketPath.startsWith("/") || options.socketPath.length > 4_096) {
|
|
755
960
|
throw new Error("invalid RiffDB driver socket path");
|
|
756
961
|
}
|
|
757
962
|
const identity = options.identity;
|
|
963
|
+
if (!HASH.test(identity.contractBundleHash)) {
|
|
964
|
+
throw new Error("invalid RiffDB driver identity");
|
|
965
|
+
}
|
|
758
966
|
for (const value of [
|
|
759
967
|
identity.applicationManifestHash, identity.operationCatalogHash,
|
|
760
|
-
identity.
|
|
968
|
+
identity.roleDefinitionHash, identity.remoteIdentityHash,
|
|
761
969
|
]) {
|
|
762
|
-
if (!HASH.test(value))
|
|
970
|
+
if (value !== undefined && value !== "" && !HASH.test(value)) {
|
|
763
971
|
throw new Error("invalid RiffDB driver identity");
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
if (identity.database !== undefined && identity.database !== "" && !SYMBOL.test(identity.database)) {
|
|
975
|
+
throw new Error("invalid RiffDB driver identity");
|
|
764
976
|
}
|
|
765
|
-
if (!SYMBOL.test(identity.contractLineage)
|
|
977
|
+
if (!SYMBOL.test(identity.contractLineage)
|
|
766
978
|
|| !SYMBOL.test(identity.role) || identity.contractVersion < 1n
|
|
767
979
|
|| identity.contractVersion > 18446744073709551615n) {
|
|
768
980
|
throw new Error("invalid RiffDB driver identity");
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DriverApplicationTransport, type DriverApplicationError, type DriverCompactQueryResult, type DriverPackedQueryResult, type DriverOperation } from "./driver.js";
|
|
1
|
+
import { DriverApplicationTransport, type DriverApplicationError, type DriverApplicationTransportOptions, type DriverConnectFromEnvOptions, type DriverCompactQueryResult, type DriverPackedQueryResult, type DriverOperation } from "./driver.js";
|
|
2
2
|
export type ApplicationValueSchema = {
|
|
3
3
|
readonly kind: "bool" | "i64" | "u64" | "string" | "uuid" | "bytes" | "date" | "timestamp" | "cursor" | "limit";
|
|
4
4
|
readonly maximumBytes?: number;
|
|
@@ -51,12 +51,52 @@ export interface ExactDecimalValue {
|
|
|
51
51
|
readonly coefficientTwosComplement: Uint8Array;
|
|
52
52
|
readonly scale: number;
|
|
53
53
|
readonly precision: number;
|
|
54
|
+
toString?(): string;
|
|
55
|
+
toJSON?(): string;
|
|
54
56
|
}
|
|
55
57
|
/** One exact currency value accepted by generated `money<CURRENCY>` fields. */
|
|
56
58
|
export interface ExactMoneyValue<Currency extends string = string> {
|
|
57
59
|
readonly currency: Currency;
|
|
58
60
|
readonly amount: ExactDecimalValue;
|
|
61
|
+
toString?(): string;
|
|
62
|
+
toJSON?(): {
|
|
63
|
+
readonly currency: Currency;
|
|
64
|
+
readonly amount: string;
|
|
65
|
+
};
|
|
59
66
|
}
|
|
67
|
+
/** One exact UTC timestamp returned by generated query and command fields. */
|
|
68
|
+
export interface TimestampValue {
|
|
69
|
+
readonly seconds: bigint;
|
|
70
|
+
readonly nanos: number;
|
|
71
|
+
toISOString(): string;
|
|
72
|
+
toDate(): Date;
|
|
73
|
+
toJSON(): string;
|
|
74
|
+
toString(): string;
|
|
75
|
+
}
|
|
76
|
+
export type TimestampInput = TimestampValue | {
|
|
77
|
+
readonly seconds: bigint | number | string;
|
|
78
|
+
readonly nanos?: number;
|
|
79
|
+
} | Date | string;
|
|
80
|
+
export type IntegerInput = bigint | number | string;
|
|
81
|
+
export declare class RiffTimestamp implements TimestampValue {
|
|
82
|
+
readonly seconds: bigint;
|
|
83
|
+
readonly nanos: number;
|
|
84
|
+
constructor(seconds: bigint, nanos?: number);
|
|
85
|
+
static from(value: TimestampInput): TimestampValue;
|
|
86
|
+
static now(): TimestampValue;
|
|
87
|
+
toISOString(): string;
|
|
88
|
+
toDate(): Date;
|
|
89
|
+
toJSON(): string;
|
|
90
|
+
toString(): string;
|
|
91
|
+
}
|
|
92
|
+
export declare function createTimestampValue(seconds: bigint, nanos: number): TimestampValue;
|
|
93
|
+
export declare function formatTimestampIso(seconds: bigint, nanos: number): string;
|
|
94
|
+
export declare function formatExactDecimal(value: {
|
|
95
|
+
readonly coefficientTwosComplement: Uint8Array;
|
|
96
|
+
readonly scale: number;
|
|
97
|
+
}): string;
|
|
98
|
+
export declare function toJsonValue(value: unknown, key?: string): unknown;
|
|
99
|
+
export declare function attachRecordToJson<T extends Record<string, unknown>>(record: T): T;
|
|
60
100
|
/**
|
|
61
101
|
* Constructs an exact fixed-point value from decimal text without using a
|
|
62
102
|
* JavaScript floating-point number.
|
|
@@ -198,8 +238,10 @@ export interface ContextualBatch<E> {
|
|
|
198
238
|
readonly status: ReactiveConsumerStatus;
|
|
199
239
|
}
|
|
200
240
|
export interface ReactiveConsumerStatus {
|
|
241
|
+
readonly kind?: "exact" | "protected";
|
|
201
242
|
readonly revision: bigint;
|
|
202
243
|
readonly checkpoint: string;
|
|
244
|
+
readonly progressCursor?: string;
|
|
203
245
|
readonly historyIncarnation: bigint;
|
|
204
246
|
readonly liveLeases: number;
|
|
205
247
|
readonly retries: number;
|
|
@@ -294,8 +336,11 @@ export declare class CliApplicationTransport {
|
|
|
294
336
|
export declare class DriverGeneratedApplicationTransport {
|
|
295
337
|
private readonly driver;
|
|
296
338
|
constructor(driver: DriverApplicationTransport);
|
|
339
|
+
static connect(options: DriverApplicationTransportOptions): Promise<DriverGeneratedApplicationTransport>;
|
|
340
|
+
static connectFromEnv(options?: DriverConnectFromEnvOptions): Promise<DriverGeneratedApplicationTransport>;
|
|
297
341
|
/** Returns the bound tenant identifier when this session is tenant-scoped. */
|
|
298
342
|
get tenant(): string | undefined;
|
|
343
|
+
shutdown(): Promise<void>;
|
|
299
344
|
executeNamedQuery<P, R>(request: NamedQueryRequest<P, R>, options?: QueryOptions): Promise<TypedQueryResult<R>>;
|
|
300
345
|
executeCommand<I, R>(request: CommandRequest<I, R>, attemptBudget: number): Promise<TypedCommandResult<R>>;
|
|
301
346
|
executeVectorInspection<P, R>(request: VectorInspectionRequest<P, R>, options?: VectorInspectionOptions): Promise<TypedVectorInspectionResult<R>>;
|
|
@@ -328,5 +373,5 @@ export declare class DriverGeneratedApplicationTransport {
|
|
|
328
373
|
}): AsyncIterable<LiveQueryUpdate<T>>;
|
|
329
374
|
private mutateDriverLease;
|
|
330
375
|
}
|
|
331
|
-
export { DRIVER_ERROR_REGISTRY_HASH, DRIVER_PROTOCOL_VERSION, DRIVER_VALUE_REGISTRY_HASH, DriverApplicationError, DriverApplicationTransport, } from "./driver.js";
|
|
332
|
-
export type { DriverApplicationIdentity, DriverApplicationTransportOptions, DriverBatchItem, DriverBatchResult, DriverBatchSuccess, DriverDecimal, DriverErrorDetails, DriverInvokeOptions, DriverMoney, DriverOperation, DriverPackedColumn, DriverPackedQueryResult, DriverResult, DriverTimestamp, DriverValue, } from "./driver.js";
|
|
376
|
+
export { DRIVER_ERROR_REGISTRY_HASH, DRIVER_PROTOCOL_VERSION, DRIVER_VALUE_REGISTRY_HASH, DriverApplicationError, DriverApplicationTransport, resolveTransportOptionsFromEnv, } from "./driver.js";
|
|
377
|
+
export type { DriverApplicationIdentity, DriverApplicationTransportOptions, DriverConnectFromEnvOptions, DriverBatchItem, DriverBatchResult, DriverBatchSuccess, DriverDecimal, DriverErrorDetails, DriverInvokeOptions, DriverMoney, DriverOperation, DriverPackedColumn, DriverPackedQueryResult, DriverResult, DriverTimestamp, DriverValue, ResolvedDriverApplicationIdentity, } from "./driver.js";
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
|
+
import { DriverApplicationTransport, } from "./driver.js";
|
|
6
7
|
const executeFile = promisify(execFile);
|
|
7
8
|
const MAX_OUTPUT_BYTES = 4_194_304;
|
|
8
9
|
const HASH = /^[0-9a-f]{64}$/;
|
|
@@ -18,6 +19,253 @@ export class InputBudgetError extends Error {
|
|
|
18
19
|
this.name = "InputBudgetError";
|
|
19
20
|
}
|
|
20
21
|
}
|
|
22
|
+
export class RiffTimestamp {
|
|
23
|
+
seconds;
|
|
24
|
+
nanos;
|
|
25
|
+
constructor(seconds, nanos = 0) {
|
|
26
|
+
if (typeof seconds !== "bigint" || seconds < -(1n << 63n) || seconds > (1n << 63n) - 1n) {
|
|
27
|
+
throw new Error("invalid RiffDB timestamp seconds");
|
|
28
|
+
}
|
|
29
|
+
if (!Number.isInteger(nanos) || nanos < 0 || nanos > 999_999_999) {
|
|
30
|
+
throw new Error("invalid RiffDB timestamp nanos");
|
|
31
|
+
}
|
|
32
|
+
this.seconds = seconds;
|
|
33
|
+
this.nanos = nanos;
|
|
34
|
+
}
|
|
35
|
+
static from(value) {
|
|
36
|
+
const parsed = coerceTimestampInput(value, "invalid RiffDB timestamp input");
|
|
37
|
+
return createTimestampValue(parsed.seconds, parsed.nanos);
|
|
38
|
+
}
|
|
39
|
+
static now() {
|
|
40
|
+
return RiffTimestamp.from(new Date());
|
|
41
|
+
}
|
|
42
|
+
toISOString() {
|
|
43
|
+
return formatTimestampIso(this.seconds, this.nanos);
|
|
44
|
+
}
|
|
45
|
+
toDate() {
|
|
46
|
+
return new Date(Number(this.seconds) * 1000 + Math.floor(this.nanos / 1_000_000));
|
|
47
|
+
}
|
|
48
|
+
toJSON() {
|
|
49
|
+
return this.toISOString();
|
|
50
|
+
}
|
|
51
|
+
toString() {
|
|
52
|
+
return this.toISOString();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function createTimestampValue(seconds, nanos) {
|
|
56
|
+
const target = { seconds, nanos };
|
|
57
|
+
Object.defineProperties(target, {
|
|
58
|
+
toISOString: {
|
|
59
|
+
value() {
|
|
60
|
+
return formatTimestampIso(this.seconds, this.nanos);
|
|
61
|
+
},
|
|
62
|
+
enumerable: false,
|
|
63
|
+
configurable: true,
|
|
64
|
+
},
|
|
65
|
+
toDate: {
|
|
66
|
+
value() {
|
|
67
|
+
return new Date(Number(this.seconds) * 1000 + Math.floor(this.nanos / 1_000_000));
|
|
68
|
+
},
|
|
69
|
+
enumerable: false,
|
|
70
|
+
configurable: true,
|
|
71
|
+
},
|
|
72
|
+
toJSON: {
|
|
73
|
+
value() {
|
|
74
|
+
return formatTimestampIso(this.seconds, this.nanos);
|
|
75
|
+
},
|
|
76
|
+
enumerable: false,
|
|
77
|
+
configurable: true,
|
|
78
|
+
},
|
|
79
|
+
toString: {
|
|
80
|
+
value() {
|
|
81
|
+
return formatTimestampIso(this.seconds, this.nanos);
|
|
82
|
+
},
|
|
83
|
+
enumerable: false,
|
|
84
|
+
configurable: true,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
return target;
|
|
88
|
+
}
|
|
89
|
+
function isGregorianLeapYear(year) {
|
|
90
|
+
return year % 4n === 0n && (year % 100n !== 0n || year % 400n === 0n);
|
|
91
|
+
}
|
|
92
|
+
function daysInGregorianMonth(year, month) {
|
|
93
|
+
switch (month) {
|
|
94
|
+
case 1:
|
|
95
|
+
case 3:
|
|
96
|
+
case 5:
|
|
97
|
+
case 7:
|
|
98
|
+
case 8:
|
|
99
|
+
case 10:
|
|
100
|
+
case 12:
|
|
101
|
+
return 31;
|
|
102
|
+
case 4:
|
|
103
|
+
case 6:
|
|
104
|
+
case 9:
|
|
105
|
+
case 11:
|
|
106
|
+
return 30;
|
|
107
|
+
case 2:
|
|
108
|
+
return isGregorianLeapYear(year) ? 29 : 28;
|
|
109
|
+
default:
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function daysFromCivil(year, month, day) {
|
|
114
|
+
const adjustedYear = month <= 2 ? year - 1n : year;
|
|
115
|
+
const era = (adjustedYear >= 0n ? adjustedYear : adjustedYear - 399n) / 400n;
|
|
116
|
+
const yoe = adjustedYear - era * 400n;
|
|
117
|
+
const m = BigInt(month);
|
|
118
|
+
const d = BigInt(day);
|
|
119
|
+
const doy = (153n * (m > 2n ? m - 3n : m + 9n) + 2n) / 5n + d - 1n;
|
|
120
|
+
const doe = yoe * 365n + yoe / 4n - yoe / 100n + doy;
|
|
121
|
+
return era * 146097n + doe - 719468n;
|
|
122
|
+
}
|
|
123
|
+
function civilFromDays(days) {
|
|
124
|
+
const z = days + 719468n;
|
|
125
|
+
const era = (z >= 0n ? z : z - 146096n) / 146097n;
|
|
126
|
+
const doe = z - era * 146097n;
|
|
127
|
+
const yoe = (doe - doe / 1460n + doe / 36524n - doe / 146096n) / 365n;
|
|
128
|
+
const y = yoe + era * 400n;
|
|
129
|
+
const doy = doe - (365n * yoe + yoe / 4n - yoe / 100n);
|
|
130
|
+
const mp = (5n * doy + 2n) / 153n;
|
|
131
|
+
const d = doy - (153n * mp + 2n) / 5n + 1n;
|
|
132
|
+
const m = mp < 10n ? mp + 3n : mp - 9n;
|
|
133
|
+
return {
|
|
134
|
+
year: m <= 2n ? y + 1n : y,
|
|
135
|
+
month: Number(m),
|
|
136
|
+
day: Number(d),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
export function formatTimestampIso(seconds, nanos) {
|
|
140
|
+
const daySeconds = ((seconds % 86400n) + 86400n) % 86400n;
|
|
141
|
+
const days = (seconds - daySeconds) / 86400n;
|
|
142
|
+
const { year, month, day } = civilFromDays(days);
|
|
143
|
+
const hour = Number(daySeconds / 3600n);
|
|
144
|
+
const minute = Number((daySeconds % 3600n) / 60n);
|
|
145
|
+
const second = Number(daySeconds % 60n);
|
|
146
|
+
const yearPart = year < 0n
|
|
147
|
+
? `-${(-year).toString().padStart(6, "0")}`
|
|
148
|
+
: year > 9999n
|
|
149
|
+
? `+${year.toString().padStart(6, "0")}`
|
|
150
|
+
: year.toString().padStart(4, "0");
|
|
151
|
+
const datePart = `${yearPart}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}T${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}:${String(second).padStart(2, "0")}`;
|
|
152
|
+
const fractionPart = nanos % 1_000_000 === 0
|
|
153
|
+
? String(Math.floor(nanos / 1_000_000)).padStart(3, "0")
|
|
154
|
+
: String(nanos).padStart(9, "0").replace(/0+$/, "");
|
|
155
|
+
return `${datePart}.${fractionPart}Z`;
|
|
156
|
+
}
|
|
157
|
+
export function formatExactDecimal(value) {
|
|
158
|
+
let coefficient = decodeSignedTwosComplement(value.coefficientTwosComplement);
|
|
159
|
+
const negative = coefficient < 0n;
|
|
160
|
+
if (negative)
|
|
161
|
+
coefficient = -coefficient;
|
|
162
|
+
const scale = value.scale;
|
|
163
|
+
if (scale === 0) {
|
|
164
|
+
return `${negative ? "-" : ""}${coefficient.toString()}`;
|
|
165
|
+
}
|
|
166
|
+
const digits = coefficient.toString().padStart(scale + 1, "0");
|
|
167
|
+
const split = digits.length - scale;
|
|
168
|
+
return `${negative ? "-" : ""}${digits.slice(0, split)}.${digits.slice(split)}`;
|
|
169
|
+
}
|
|
170
|
+
function attachDecimalToJson(decimal) {
|
|
171
|
+
Object.defineProperties(decimal, {
|
|
172
|
+
toString: {
|
|
173
|
+
value() {
|
|
174
|
+
return formatExactDecimal(this);
|
|
175
|
+
},
|
|
176
|
+
enumerable: false,
|
|
177
|
+
configurable: true,
|
|
178
|
+
},
|
|
179
|
+
toJSON: {
|
|
180
|
+
value() {
|
|
181
|
+
return formatExactDecimal(this);
|
|
182
|
+
},
|
|
183
|
+
enumerable: false,
|
|
184
|
+
configurable: true,
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
return decimal;
|
|
188
|
+
}
|
|
189
|
+
function attachMoneyToJson(money) {
|
|
190
|
+
Object.defineProperties(money, {
|
|
191
|
+
toString: {
|
|
192
|
+
value() {
|
|
193
|
+
return `${this.currency} ${formatExactDecimal(this.amount)}`;
|
|
194
|
+
},
|
|
195
|
+
enumerable: false,
|
|
196
|
+
configurable: true,
|
|
197
|
+
},
|
|
198
|
+
toJSON: {
|
|
199
|
+
value() {
|
|
200
|
+
return { currency: this.currency, amount: formatExactDecimal(this.amount) };
|
|
201
|
+
},
|
|
202
|
+
enumerable: false,
|
|
203
|
+
configurable: true,
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
return money;
|
|
207
|
+
}
|
|
208
|
+
export function toJsonValue(value, key) {
|
|
209
|
+
if (value === null || value === undefined || typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
|
|
210
|
+
return value;
|
|
211
|
+
}
|
|
212
|
+
if (typeof value === "bigint") {
|
|
213
|
+
if (key !== undefined && (key === "revision" || key.endsWith("_revision") || key.endsWith("Revision"))) {
|
|
214
|
+
return value.toString();
|
|
215
|
+
}
|
|
216
|
+
if (value >= BigInt(Number.MIN_SAFE_INTEGER) && value <= BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
217
|
+
return Number(value);
|
|
218
|
+
}
|
|
219
|
+
return value.toString();
|
|
220
|
+
}
|
|
221
|
+
if (value instanceof Date) {
|
|
222
|
+
return value.toISOString();
|
|
223
|
+
}
|
|
224
|
+
if (value instanceof Uint8Array) {
|
|
225
|
+
return Buffer.from(value).toString("base64");
|
|
226
|
+
}
|
|
227
|
+
if (Array.isArray(value)) {
|
|
228
|
+
return value.map((item) => toJsonValue(item));
|
|
229
|
+
}
|
|
230
|
+
if (typeof value === "object") {
|
|
231
|
+
const record = value;
|
|
232
|
+
if (typeof record.seconds === "bigint" && typeof record.nanos === "number") {
|
|
233
|
+
return formatTimestampIso(record.seconds, record.nanos);
|
|
234
|
+
}
|
|
235
|
+
if (record.coefficientTwosComplement instanceof Uint8Array && typeof record.scale === "number") {
|
|
236
|
+
return formatExactDecimal(record);
|
|
237
|
+
}
|
|
238
|
+
if (typeof record.currency === "string" && typeof record.amount === "object" && record.amount !== null) {
|
|
239
|
+
const amount = record.amount;
|
|
240
|
+
if (amount.coefficientTwosComplement instanceof Uint8Array && typeof amount.scale === "number") {
|
|
241
|
+
return {
|
|
242
|
+
currency: record.currency,
|
|
243
|
+
amount: formatExactDecimal(amount),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
const output = {};
|
|
248
|
+
for (const [entryKey, entryValue] of Object.entries(record)) {
|
|
249
|
+
output[entryKey] = toJsonValue(entryValue, entryKey);
|
|
250
|
+
}
|
|
251
|
+
return output;
|
|
252
|
+
}
|
|
253
|
+
return value;
|
|
254
|
+
}
|
|
255
|
+
export function attachRecordToJson(record) {
|
|
256
|
+
Object.defineProperty(record, "toJSON", {
|
|
257
|
+
value() {
|
|
258
|
+
const output = {};
|
|
259
|
+
for (const [entryKey, entryValue] of Object.entries(this)) {
|
|
260
|
+
output[entryKey] = toJsonValue(entryValue, entryKey);
|
|
261
|
+
}
|
|
262
|
+
return output;
|
|
263
|
+
},
|
|
264
|
+
enumerable: false,
|
|
265
|
+
configurable: true,
|
|
266
|
+
});
|
|
267
|
+
return record;
|
|
268
|
+
}
|
|
21
269
|
/**
|
|
22
270
|
* Constructs an exact fixed-point value from decimal text without using a
|
|
23
271
|
* JavaScript floating-point number.
|
|
@@ -43,17 +291,17 @@ export function exactDecimal(value, precision, scale) {
|
|
|
43
291
|
const limit = 10n ** BigInt(precision);
|
|
44
292
|
if (coefficient <= -limit || coefficient >= limit)
|
|
45
293
|
throw new Error("exact decimal exceeds precision");
|
|
46
|
-
return {
|
|
294
|
+
return attachDecimalToJson({
|
|
47
295
|
coefficientTwosComplement: signedTwosComplement(coefficient),
|
|
48
296
|
scale,
|
|
49
297
|
precision,
|
|
50
|
-
};
|
|
298
|
+
});
|
|
51
299
|
}
|
|
52
300
|
/** Constructs the exact precision-38, scale-2 value used by `money<CURRENCY>`. */
|
|
53
301
|
export function exactMoney(currency, value) {
|
|
54
302
|
if (!/^[A-Z]{3}$/.test(currency))
|
|
55
303
|
throw new Error("invalid exact money currency");
|
|
56
|
-
return { currency, amount: exactDecimal(value, 38, 2) };
|
|
304
|
+
return attachMoneyToJson({ currency, amount: exactDecimal(value, 38, 2) });
|
|
57
305
|
}
|
|
58
306
|
export class CliApplicationTransport {
|
|
59
307
|
options;
|
|
@@ -389,10 +637,19 @@ export class DriverGeneratedApplicationTransport {
|
|
|
389
637
|
constructor(driver) {
|
|
390
638
|
this.driver = driver;
|
|
391
639
|
}
|
|
640
|
+
static async connect(options) {
|
|
641
|
+
return new DriverGeneratedApplicationTransport(await DriverApplicationTransport.connect(options));
|
|
642
|
+
}
|
|
643
|
+
static async connectFromEnv(options = {}) {
|
|
644
|
+
return new DriverGeneratedApplicationTransport(await DriverApplicationTransport.connectFromEnv(options));
|
|
645
|
+
}
|
|
392
646
|
/** Returns the bound tenant identifier when this session is tenant-scoped. */
|
|
393
647
|
get tenant() {
|
|
394
648
|
return this.driver.tenant;
|
|
395
649
|
}
|
|
650
|
+
async shutdown() {
|
|
651
|
+
await this.driver.shutdown();
|
|
652
|
+
}
|
|
396
653
|
async executeNamedQuery(request, options = {}) {
|
|
397
654
|
validateIdentity(request);
|
|
398
655
|
const operation = requireDriverOperation(request.driverOperation);
|
|
@@ -613,6 +870,42 @@ function decodeDriverMutationResult(value) {
|
|
|
613
870
|
}
|
|
614
871
|
function decodeDriverConsumerStatus(value) {
|
|
615
872
|
const status = driverRecord(value);
|
|
873
|
+
if (status.kind !== undefined) {
|
|
874
|
+
const kind = driverEnum(status.kind);
|
|
875
|
+
if (kind === "protected") {
|
|
876
|
+
const historyIncarnation = driverPositiveU64(requiredDriverField(status, "history_incarnation"));
|
|
877
|
+
const rawCursor = requiredDriverField(status, "progress_cursor");
|
|
878
|
+
let progressCursor;
|
|
879
|
+
if (rawCursor.type === "bytes") {
|
|
880
|
+
if (rawCursor.value.length < 1 || rawCursor.value.length > 512) {
|
|
881
|
+
throw new Error("invalid RiffDB driver consumer status");
|
|
882
|
+
}
|
|
883
|
+
progressCursor = rawCursor.value;
|
|
884
|
+
}
|
|
885
|
+
else if (rawCursor.type === "string") {
|
|
886
|
+
progressCursor = expectBoundedString(rawCursor.value, 512);
|
|
887
|
+
if (progressCursor.length < 1) {
|
|
888
|
+
throw new Error("invalid RiffDB driver consumer status");
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
else {
|
|
892
|
+
throw new Error("invalid RiffDB driver consumer status");
|
|
893
|
+
}
|
|
894
|
+
return {
|
|
895
|
+
kind: "protected",
|
|
896
|
+
revision: 0n,
|
|
897
|
+
checkpoint: progressCursor,
|
|
898
|
+
progressCursor,
|
|
899
|
+
historyIncarnation,
|
|
900
|
+
liveLeases: 0,
|
|
901
|
+
retries: 0,
|
|
902
|
+
deadLetters: 0,
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
if (kind !== "exact") {
|
|
906
|
+
throw new Error("invalid RiffDB driver consumer status");
|
|
907
|
+
}
|
|
908
|
+
}
|
|
616
909
|
return {
|
|
617
910
|
revision: driverPositiveU64(requiredDriverField(status, "revision")),
|
|
618
911
|
checkpoint: driverString(requiredDriverField(status, "checkpoint"), 256),
|
|
@@ -701,13 +994,13 @@ function decodeDriverLiveUpdate(value, applicationHead, responseCursor) {
|
|
|
701
994
|
return {
|
|
702
995
|
type: "terminal",
|
|
703
996
|
reason,
|
|
704
|
-
lastApplicationHead:
|
|
997
|
+
lastApplicationHead: driverNonnegativeU64(requiredDriverField(record, "application_head")),
|
|
705
998
|
historyIncarnation: driverPositiveU64(requiredDriverField(record, "history_incarnation")),
|
|
706
999
|
};
|
|
707
1000
|
}
|
|
708
1001
|
const checkpoint = driverRecord(requiredDriverField(update, "checkpoint"));
|
|
709
1002
|
const cursor = driverString(requiredDriverField(checkpoint, "cursor"), 16_384);
|
|
710
|
-
const head =
|
|
1003
|
+
const head = driverNonnegativeU64(requiredDriverField(checkpoint, "application_head"));
|
|
711
1004
|
const history = driverPositiveU64(requiredDriverField(checkpoint, "history_incarnation"));
|
|
712
1005
|
if (applicationHead !== head || responseCursor !== cursor) {
|
|
713
1006
|
throw new Error("RiffDB driver live frontier is inconsistent");
|
|
@@ -774,6 +1067,7 @@ function decodeDriverDynamic(value) {
|
|
|
774
1067
|
throw new Error("invalid RiffDB driver money");
|
|
775
1068
|
return { currency: value.value.currency, amount: { coefficientTwosComplement: Uint8Array.from(Buffer.from(value.value.amount.coefficient, "base64")), scale: value.value.amount.scale, precision: value.value.amount.precision } };
|
|
776
1069
|
}
|
|
1070
|
+
case "vector": return decodeVectorBits(value.value.component_bits, value.value.component_bits.length);
|
|
777
1071
|
case "list": return value.value.map(decodeDriverDynamic);
|
|
778
1072
|
case "record": return decodeDriverDynamicRecord(value.value);
|
|
779
1073
|
}
|
|
@@ -805,6 +1099,14 @@ function driverPositiveU64(value) {
|
|
|
805
1099
|
throw new Error("invalid RiffDB driver u64");
|
|
806
1100
|
return parsed;
|
|
807
1101
|
}
|
|
1102
|
+
function driverNonnegativeU64(value) {
|
|
1103
|
+
if (value.type !== "u64")
|
|
1104
|
+
throw new Error("invalid RiffDB driver u64");
|
|
1105
|
+
const parsed = BigInt(value.value);
|
|
1106
|
+
if (parsed < 0n || parsed > (1n << 64n) - 1n)
|
|
1107
|
+
throw new Error("invalid RiffDB driver u64");
|
|
1108
|
+
return parsed;
|
|
1109
|
+
}
|
|
808
1110
|
function driverBoundedU32(value) {
|
|
809
1111
|
if (value.type !== "u64")
|
|
810
1112
|
throw new Error("invalid RiffDB driver u32");
|
|
@@ -899,14 +1201,14 @@ function encodeDriverValue(value, schema, budgetPath) {
|
|
|
899
1201
|
if (typeof value !== "boolean")
|
|
900
1202
|
throw new Error("invalid generated RiffDB bool input");
|
|
901
1203
|
return { type: "bool", value };
|
|
902
|
-
case "i64":
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
case "u64":
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1204
|
+
case "i64": {
|
|
1205
|
+
const parsed = coerceI64Input(value, "invalid generated RiffDB i64 input");
|
|
1206
|
+
return { type: "i64", value: parsed.toString() };
|
|
1207
|
+
}
|
|
1208
|
+
case "u64": {
|
|
1209
|
+
const parsed = coerceU64Input(value, "invalid generated RiffDB u64 input");
|
|
1210
|
+
return { type: "u64", value: parsed.toString() };
|
|
1211
|
+
}
|
|
910
1212
|
case "string": {
|
|
911
1213
|
const string = expectBoundedString(value, 262_144);
|
|
912
1214
|
if (schema.maximumBytes !== undefined && new TextEncoder().encode(string).length > schema.maximumBytes && budgetPath !== undefined) {
|
|
@@ -932,10 +1234,8 @@ function encodeDriverValue(value, schema, budgetPath) {
|
|
|
932
1234
|
throw new Error("invalid generated RiffDB date input");
|
|
933
1235
|
return { type: "date", value: String(value) };
|
|
934
1236
|
case "timestamp": {
|
|
935
|
-
const timestamp =
|
|
936
|
-
|
|
937
|
-
throw new Error("invalid generated RiffDB timestamp input");
|
|
938
|
-
return { type: "timestamp", value: { seconds: timestamp.seconds.toString(), nanos: boundedInteger(timestamp.nanos, 0, 999_999_999) } };
|
|
1237
|
+
const timestamp = coerceTimestampInput(value, "invalid generated RiffDB timestamp input");
|
|
1238
|
+
return { type: "timestamp", value: { seconds: timestamp.seconds.toString(), nanos: timestamp.nanos } };
|
|
939
1239
|
}
|
|
940
1240
|
case "decimal": return { type: "decimal", value: encodeDriverDecimal(value, schema) };
|
|
941
1241
|
case "money": {
|
|
@@ -1120,7 +1420,7 @@ function decodeDriverValue(value, schema) {
|
|
|
1120
1420
|
throw new Error("invalid RiffDB driver record result");
|
|
1121
1421
|
output[field.name] = decodeDriverValue(fieldValue, field.schema);
|
|
1122
1422
|
}
|
|
1123
|
-
return output;
|
|
1423
|
+
return attachRecordToJson(output);
|
|
1124
1424
|
}
|
|
1125
1425
|
switch (schema.kind) {
|
|
1126
1426
|
case "bool":
|
|
@@ -1167,7 +1467,7 @@ function decodeDriverValue(value, schema) {
|
|
|
1167
1467
|
break;
|
|
1168
1468
|
case "timestamp":
|
|
1169
1469
|
if (value.type === "timestamp")
|
|
1170
|
-
return
|
|
1470
|
+
return createTimestampValue(BigInt(value.value.seconds), boundedInteger(value.value.nanos, 0, 999_999_999));
|
|
1171
1471
|
break;
|
|
1172
1472
|
case "decimal":
|
|
1173
1473
|
if (value.type === "decimal")
|
|
@@ -1175,7 +1475,7 @@ function decodeDriverValue(value, schema) {
|
|
|
1175
1475
|
break;
|
|
1176
1476
|
case "money":
|
|
1177
1477
|
if (value.type === "money" && value.value.currency === schema.currency)
|
|
1178
|
-
return { currency: value.value.currency, amount: decodeDriverDecimal(value.value.amount, schema) };
|
|
1478
|
+
return attachMoneyToJson({ currency: value.value.currency, amount: decodeDriverDecimal(value.value.amount, schema) });
|
|
1179
1479
|
break;
|
|
1180
1480
|
case "vector":
|
|
1181
1481
|
if (value.type === "vector")
|
|
@@ -1196,7 +1496,7 @@ function decodeDriverDecimal(value, schema) {
|
|
|
1196
1496
|
const coefficient = Uint8Array.from(Buffer.from(value.coefficient, "base64"));
|
|
1197
1497
|
if (coefficient.byteLength < 1 || coefficient.byteLength > 16)
|
|
1198
1498
|
throw new Error("RiffDB driver decimal coefficient is invalid");
|
|
1199
|
-
return { coefficientTwosComplement: coefficient, precision: schema.precision, scale: schema.scale };
|
|
1499
|
+
return attachDecimalToJson({ coefficientTwosComplement: coefficient, precision: schema.precision, scale: schema.scale });
|
|
1200
1500
|
}
|
|
1201
1501
|
function decodeTypedCommandResult(result, request) {
|
|
1202
1502
|
if (expectHash(result.plan_hash) !== request.planHash) {
|
|
@@ -1207,7 +1507,7 @@ function decodeTypedCommandResult(result, request) {
|
|
|
1207
1507
|
if (schema === undefined)
|
|
1208
1508
|
throw new Error("RiffDB application response has an unknown outcome");
|
|
1209
1509
|
const payload = decodeWire(result.outcome, schema);
|
|
1210
|
-
const outcome = { outcome: outcomeName, ...exactObject(payload) };
|
|
1510
|
+
const outcome = attachRecordToJson({ outcome: outcomeName, ...exactObject(payload) });
|
|
1211
1511
|
const typed = {
|
|
1212
1512
|
outcome,
|
|
1213
1513
|
contractVersion: positiveNumber(result.contract_version),
|
|
@@ -1367,16 +1667,14 @@ function encodeValue(value, schema, budgetPath) {
|
|
|
1367
1667
|
if (typeof value !== "boolean")
|
|
1368
1668
|
throw new Error("invalid boolean input");
|
|
1369
1669
|
return value;
|
|
1370
|
-
case "i64":
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
}
|
|
1379
|
-
return { $u64: value.toString() };
|
|
1670
|
+
case "i64": {
|
|
1671
|
+
const parsed = coerceI64Input(value, "invalid i64 input");
|
|
1672
|
+
return { $i64: parsed.toString() };
|
|
1673
|
+
}
|
|
1674
|
+
case "u64": {
|
|
1675
|
+
const parsed = coerceU64Input(value, "invalid u64 input");
|
|
1676
|
+
return { $u64: parsed.toString() };
|
|
1677
|
+
}
|
|
1380
1678
|
case "decimal": {
|
|
1381
1679
|
const decimal = encodeDecimal(value);
|
|
1382
1680
|
if (schema.precision === undefined || schema.scale === undefined
|
|
@@ -1416,14 +1714,8 @@ function encodeValue(value, schema, budgetPath) {
|
|
|
1416
1714
|
}
|
|
1417
1715
|
return { $date: value };
|
|
1418
1716
|
case "timestamp": {
|
|
1419
|
-
const
|
|
1420
|
-
|
|
1421
|
-
const nanos = input.nanos;
|
|
1422
|
-
if (typeof seconds !== "bigint" || seconds < -(1n << 63n) || seconds > (1n << 63n) - 1n
|
|
1423
|
-
|| !Number.isInteger(nanos) || nanos < 0 || nanos > 999_999_999) {
|
|
1424
|
-
throw new Error("invalid timestamp input");
|
|
1425
|
-
}
|
|
1426
|
-
return { $timestamp: { seconds: seconds.toString(), nanos } };
|
|
1717
|
+
const timestamp = coerceTimestampInput(value, "invalid timestamp input");
|
|
1718
|
+
return { $timestamp: { seconds: timestamp.seconds.toString(), nanos: timestamp.nanos } };
|
|
1427
1719
|
}
|
|
1428
1720
|
case "vector": return { $vector: encodeVector(value, schema.dimension) };
|
|
1429
1721
|
case "limit":
|
|
@@ -1500,7 +1792,7 @@ function normalizeQueryFields(value) {
|
|
|
1500
1792
|
throw new Error("duplicate RiffDB query field");
|
|
1501
1793
|
result[fieldName] = decodeTagged(entry.value);
|
|
1502
1794
|
}
|
|
1503
|
-
return result;
|
|
1795
|
+
return attachRecordToJson(result);
|
|
1504
1796
|
});
|
|
1505
1797
|
switch (field.cardinality) {
|
|
1506
1798
|
case "one":
|
|
@@ -1533,13 +1825,13 @@ function decodeTagged(value) {
|
|
|
1533
1825
|
case "uuid": return expectBoundedString(input.value, 262_144);
|
|
1534
1826
|
case "enum": return expectSymbol(input.name);
|
|
1535
1827
|
case "date": return Number(expectDecimal(input.days_since_unix_epoch));
|
|
1536
|
-
case "timestamp": return
|
|
1828
|
+
case "timestamp": return createTimestampValue(BigInt(expectDecimal(input.seconds)), nonnegativeNumber(input.nanos));
|
|
1537
1829
|
case "bytes": return Uint8Array.from(Buffer.from(expectBoundedString(input.value, MAX_OUTPUT_BYTES), "base64"));
|
|
1538
1830
|
case "decimal": return decodeDecimal(input);
|
|
1539
|
-
case "money": return {
|
|
1831
|
+
case "money": return attachMoneyToJson({
|
|
1540
1832
|
currency: expectCurrency(input.currency),
|
|
1541
1833
|
amount: decodeDecimal(exactObject(input.amount)),
|
|
1542
|
-
};
|
|
1834
|
+
});
|
|
1543
1835
|
case "vector": return decodeVector(input.components, undefined);
|
|
1544
1836
|
case "list":
|
|
1545
1837
|
if (!Array.isArray(input.values))
|
|
@@ -1575,9 +1867,9 @@ function decodeDecimal(input) {
|
|
|
1575
1867
|
if (coefficient.byteLength < 1 || coefficient.byteLength > 16)
|
|
1576
1868
|
return fail();
|
|
1577
1869
|
if (input.precision === undefined)
|
|
1578
|
-
return { coefficientTwosComplement: coefficient, scale };
|
|
1870
|
+
return attachDecimalToJson({ coefficientTwosComplement: coefficient, scale });
|
|
1579
1871
|
const precision = positiveNumber(input.precision);
|
|
1580
|
-
return { coefficientTwosComplement: coefficient, scale, precision };
|
|
1872
|
+
return attachDecimalToJson({ coefficientTwosComplement: coefficient, scale, precision });
|
|
1581
1873
|
}
|
|
1582
1874
|
function decodePlain(value, schema) {
|
|
1583
1875
|
if (schema.kind === "optional")
|
|
@@ -1596,7 +1888,7 @@ function decodePlain(value, schema) {
|
|
|
1596
1888
|
return fail();
|
|
1597
1889
|
for (const field of schema.fields)
|
|
1598
1890
|
output[field.name] = decodePlain(input[field.name], field.schema);
|
|
1599
|
-
return output;
|
|
1891
|
+
return attachRecordToJson(output);
|
|
1600
1892
|
}
|
|
1601
1893
|
switch (schema.kind) {
|
|
1602
1894
|
case "bool":
|
|
@@ -1625,8 +1917,9 @@ function decodePlain(value, schema) {
|
|
|
1625
1917
|
break;
|
|
1626
1918
|
case "timestamp": {
|
|
1627
1919
|
const input = exactObject(value);
|
|
1628
|
-
if (typeof input.seconds === "bigint" && Number.isInteger(input.nanos))
|
|
1629
|
-
return
|
|
1920
|
+
if (typeof input.seconds === "bigint" && Number.isInteger(input.nanos)) {
|
|
1921
|
+
return createTimestampValue(input.seconds, input.nanos);
|
|
1922
|
+
}
|
|
1630
1923
|
break;
|
|
1631
1924
|
}
|
|
1632
1925
|
case "bytes":
|
|
@@ -1641,10 +1934,10 @@ function decodePlain(value, schema) {
|
|
|
1641
1934
|
const currency = expectCurrency(input.currency);
|
|
1642
1935
|
if (schema.currency !== undefined && currency !== schema.currency)
|
|
1643
1936
|
return fail();
|
|
1644
|
-
return {
|
|
1937
|
+
return attachMoneyToJson({
|
|
1645
1938
|
currency,
|
|
1646
1939
|
amount: normalizeDecodedDecimal(input.amount, schema.precision, schema.scale),
|
|
1647
|
-
};
|
|
1940
|
+
});
|
|
1648
1941
|
}
|
|
1649
1942
|
case "vector": return decodeVector(value, schema.dimension);
|
|
1650
1943
|
default: break;
|
|
@@ -1659,11 +1952,11 @@ function normalizeDecodedDecimal(value, expectedPrecision, expectedScale) {
|
|
|
1659
1952
|
|| (expectedScale !== undefined && encoded.scale !== expectedScale))
|
|
1660
1953
|
return fail();
|
|
1661
1954
|
const precision = expectedPrecision ?? encoded.precision;
|
|
1662
|
-
return {
|
|
1955
|
+
return attachDecimalToJson({
|
|
1663
1956
|
coefficientTwosComplement: input.coefficientTwosComplement,
|
|
1664
1957
|
scale: encoded.scale,
|
|
1665
1958
|
...(precision === undefined ? {} : { precision }),
|
|
1666
|
-
};
|
|
1959
|
+
});
|
|
1667
1960
|
}
|
|
1668
1961
|
function decodeWire(value, schema) {
|
|
1669
1962
|
if (schema.kind !== "record")
|
|
@@ -1876,7 +2169,7 @@ function encodeVectorBits(value, dimension) {
|
|
|
1876
2169
|
});
|
|
1877
2170
|
}
|
|
1878
2171
|
function decodeVectorBits(value, dimension) {
|
|
1879
|
-
if (!Array.isArray(value) || value.length !== dimension)
|
|
2172
|
+
if (!Array.isArray(value) || dimension < 1 || dimension > 4_096 || value.length !== dimension)
|
|
1880
2173
|
return fail();
|
|
1881
2174
|
const float = new Float32Array(1);
|
|
1882
2175
|
const bits = new Uint32Array(float.buffer);
|
|
@@ -1903,6 +2196,26 @@ function decodeVector(value, dimension) {
|
|
|
1903
2196
|
}
|
|
1904
2197
|
function decodeReactiveConsumerStatus(value) {
|
|
1905
2198
|
const status = exactObject(value);
|
|
2199
|
+
if (status.kind !== undefined) {
|
|
2200
|
+
const kind = expectSymbol(status.kind);
|
|
2201
|
+
if (kind === "protected") {
|
|
2202
|
+
const progressCursor = expectBoundedString(status.progress_cursor, 512);
|
|
2203
|
+
if (progressCursor.length < 1)
|
|
2204
|
+
return fail();
|
|
2205
|
+
return {
|
|
2206
|
+
kind: "protected",
|
|
2207
|
+
revision: 0n,
|
|
2208
|
+
checkpoint: progressCursor,
|
|
2209
|
+
progressCursor,
|
|
2210
|
+
historyIncarnation: positiveBigInt(status.history_incarnation),
|
|
2211
|
+
liveLeases: 0,
|
|
2212
|
+
retries: 0,
|
|
2213
|
+
deadLetters: 0,
|
|
2214
|
+
};
|
|
2215
|
+
}
|
|
2216
|
+
if (kind !== "exact")
|
|
2217
|
+
return fail();
|
|
2218
|
+
}
|
|
1906
2219
|
return {
|
|
1907
2220
|
revision: positiveBigInt(status.revision),
|
|
1908
2221
|
checkpoint: expectBoundedString(status.checkpoint, 64),
|
|
@@ -2031,6 +2344,122 @@ function signedTwosComplement(value) {
|
|
|
2031
2344
|
}
|
|
2032
2345
|
return fail();
|
|
2033
2346
|
}
|
|
2347
|
+
function decodeSignedTwosComplement(bytes) {
|
|
2348
|
+
if (bytes.byteLength < 1 || bytes.byteLength > 16)
|
|
2349
|
+
return fail();
|
|
2350
|
+
let value = 0n;
|
|
2351
|
+
for (const byte of bytes) {
|
|
2352
|
+
value = (value << 8n) | BigInt(byte);
|
|
2353
|
+
}
|
|
2354
|
+
if ((bytes[0] & 0x80) !== 0) {
|
|
2355
|
+
value -= 1n << BigInt(bytes.byteLength * 8);
|
|
2356
|
+
}
|
|
2357
|
+
return value;
|
|
2358
|
+
}
|
|
2359
|
+
function coerceI64Input(value, errorMessage) {
|
|
2360
|
+
let parsed;
|
|
2361
|
+
if (typeof value === "bigint") {
|
|
2362
|
+
parsed = value;
|
|
2363
|
+
}
|
|
2364
|
+
else if (typeof value === "number") {
|
|
2365
|
+
if (!Number.isSafeInteger(value))
|
|
2366
|
+
throw new Error(errorMessage);
|
|
2367
|
+
parsed = BigInt(value);
|
|
2368
|
+
}
|
|
2369
|
+
else if (typeof value === "string" && /^-?(?:0|[1-9][0-9]*)$/.test(value)) {
|
|
2370
|
+
parsed = BigInt(value);
|
|
2371
|
+
}
|
|
2372
|
+
else {
|
|
2373
|
+
throw new Error(errorMessage);
|
|
2374
|
+
}
|
|
2375
|
+
if (parsed < -(1n << 63n) || parsed > (1n << 63n) - 1n) {
|
|
2376
|
+
throw new Error(errorMessage);
|
|
2377
|
+
}
|
|
2378
|
+
return parsed;
|
|
2379
|
+
}
|
|
2380
|
+
function coerceU64Input(value, errorMessage) {
|
|
2381
|
+
let parsed;
|
|
2382
|
+
if (typeof value === "bigint") {
|
|
2383
|
+
parsed = value;
|
|
2384
|
+
}
|
|
2385
|
+
else if (typeof value === "number") {
|
|
2386
|
+
if (!Number.isSafeInteger(value) || value < 0)
|
|
2387
|
+
throw new Error(errorMessage);
|
|
2388
|
+
parsed = BigInt(value);
|
|
2389
|
+
}
|
|
2390
|
+
else if (typeof value === "string" && /^(?:0|[1-9][0-9]*)$/.test(value)) {
|
|
2391
|
+
parsed = BigInt(value);
|
|
2392
|
+
}
|
|
2393
|
+
else {
|
|
2394
|
+
throw new Error(errorMessage);
|
|
2395
|
+
}
|
|
2396
|
+
if (parsed < 0n || parsed > (1n << 64n) - 1n) {
|
|
2397
|
+
throw new Error(errorMessage);
|
|
2398
|
+
}
|
|
2399
|
+
return parsed;
|
|
2400
|
+
}
|
|
2401
|
+
function coerceTimestampInput(value, errorMessage) {
|
|
2402
|
+
if (value instanceof Date) {
|
|
2403
|
+
const ms = value.getTime();
|
|
2404
|
+
if (!Number.isFinite(ms))
|
|
2405
|
+
throw new Error(errorMessage);
|
|
2406
|
+
const seconds = BigInt(Math.floor(ms / 1000));
|
|
2407
|
+
const nanos = (((ms % 1000) + 1000) % 1000) * 1_000_000;
|
|
2408
|
+
return { seconds, nanos };
|
|
2409
|
+
}
|
|
2410
|
+
if (typeof value === "string") {
|
|
2411
|
+
const match = /^([+-]?\d{4,19})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,9}))?(Z|([+-])(\d{2}):(\d{2}))$/.exec(value);
|
|
2412
|
+
if (match === null)
|
|
2413
|
+
throw new Error(errorMessage);
|
|
2414
|
+
const rawYear = match[1];
|
|
2415
|
+
if (/^-0+$/.test(rawYear))
|
|
2416
|
+
throw new Error(errorMessage);
|
|
2417
|
+
const year = BigInt(rawYear);
|
|
2418
|
+
const month = Number(match[2]);
|
|
2419
|
+
const day = Number(match[3]);
|
|
2420
|
+
const hour = Number(match[4]);
|
|
2421
|
+
const minute = Number(match[5]);
|
|
2422
|
+
const second = Number(match[6]);
|
|
2423
|
+
if (month < 1
|
|
2424
|
+
|| month > 12
|
|
2425
|
+
|| day < 1
|
|
2426
|
+
|| day > daysInGregorianMonth(year, month)
|
|
2427
|
+
|| hour > 23
|
|
2428
|
+
|| minute > 59
|
|
2429
|
+
|| second > 59) {
|
|
2430
|
+
throw new Error(errorMessage);
|
|
2431
|
+
}
|
|
2432
|
+
let offsetSeconds = 0n;
|
|
2433
|
+
if (match[8] !== "Z") {
|
|
2434
|
+
const offsetSign = match[9] === "-" ? -1n : 1n;
|
|
2435
|
+
const offsetHours = Number(match[10]);
|
|
2436
|
+
const offsetMinutes = Number(match[11]);
|
|
2437
|
+
if (offsetHours > 23 || offsetMinutes > 59) {
|
|
2438
|
+
throw new Error(errorMessage);
|
|
2439
|
+
}
|
|
2440
|
+
offsetSeconds = offsetSign * BigInt((offsetHours * 60 + offsetMinutes) * 60);
|
|
2441
|
+
}
|
|
2442
|
+
const seconds = daysFromCivil(year, month, day) * 86400n + BigInt(hour * 3600 + minute * 60 + second) - offsetSeconds;
|
|
2443
|
+
if (seconds < -(1n << 63n) || seconds > (1n << 63n) - 1n) {
|
|
2444
|
+
throw new Error(errorMessage);
|
|
2445
|
+
}
|
|
2446
|
+
const nanos = match[7] === undefined ? 0 : Number(match[7].padEnd(9, "0"));
|
|
2447
|
+
if (!Number.isInteger(nanos) || nanos < 0 || nanos > 999_999_999) {
|
|
2448
|
+
throw new Error(errorMessage);
|
|
2449
|
+
}
|
|
2450
|
+
return { seconds, nanos };
|
|
2451
|
+
}
|
|
2452
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
2453
|
+
const input = value;
|
|
2454
|
+
const seconds = coerceI64Input(input.seconds, errorMessage);
|
|
2455
|
+
const rawNanos = input.nanos === undefined ? 0 : input.nanos;
|
|
2456
|
+
if (typeof rawNanos !== "number" || !Number.isInteger(rawNanos) || rawNanos < 0 || rawNanos > 999_999_999) {
|
|
2457
|
+
throw new Error(errorMessage);
|
|
2458
|
+
}
|
|
2459
|
+
return { seconds, nanos: rawNanos };
|
|
2460
|
+
}
|
|
2461
|
+
throw new Error(errorMessage);
|
|
2462
|
+
}
|
|
2034
2463
|
function positiveNumber(value) {
|
|
2035
2464
|
const number = typeof value === "string" ? Number(expectDecimal(value)) : value;
|
|
2036
2465
|
if (!Number.isSafeInteger(number) || number < 1)
|
|
@@ -2051,4 +2480,4 @@ function positiveBigInt(value) {
|
|
|
2051
2480
|
function fail() {
|
|
2052
2481
|
throw new Error("invalid RiffDB application response");
|
|
2053
2482
|
}
|
|
2054
|
-
export { DRIVER_ERROR_REGISTRY_HASH, DRIVER_PROTOCOL_VERSION, DRIVER_VALUE_REGISTRY_HASH, DriverApplicationError, DriverApplicationTransport, } from "./driver.js";
|
|
2483
|
+
export { DRIVER_ERROR_REGISTRY_HASH, DRIVER_PROTOCOL_VERSION, DRIVER_VALUE_REGISTRY_HASH, DriverApplicationError, DriverApplicationTransport, resolveTransportOptionsFromEnv, } from "./driver.js";
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"description":"First-party application runtime for the Rust-owned RiffDB driver host","engines":{"node":">=22"},"exports":{".":"./dist/index.js","./operator":"./dist/operator.js"},"files":["dist","README.md","conformance-manifest-v1.json"],"license":"MIT OR Apache-2.0","name":"@riffdb/client","repository":{"type":"git","url":"git+https://github.com/riffdb/riffdb.git"},"type":"module","types":"./dist/index.d.ts","version":"0.2.
|
|
1
|
+
{"description":"First-party application runtime for the Rust-owned RiffDB driver host","engines":{"node":">=22"},"exports":{".":"./dist/index.js","./operator":"./dist/operator.js"},"files":["dist","README.md","conformance-manifest-v1.json"],"license":"MIT OR Apache-2.0","name":"@riffdb/client","repository":{"type":"git","url":"git+https://github.com/riffdb/riffdb.git"},"type":"module","types":"./dist/index.d.ts","version":"0.2.1"}
|