@riffdb/client 0.2.0 → 0.2.2
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 +27 -1
- package/dist/driver.js +359 -45
- package/dist/index.d.ts +86 -3
- package/dist/index.js +521 -55
- package/package.json +1 -1
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,29 @@ 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
|
+
/** Returns the resolved application identity verified during driver handshake. */
|
|
651
|
+
get identity() {
|
|
652
|
+
return this.driver.identity;
|
|
653
|
+
}
|
|
654
|
+
async invoke(operation, input, options = {}) {
|
|
655
|
+
return this.driver.invoke(operation, input, options);
|
|
656
|
+
}
|
|
657
|
+
async batch(operation, items, concurrency, checkpoint, options = {}) {
|
|
658
|
+
return this.driver.batch(operation, items, concurrency, checkpoint, options);
|
|
659
|
+
}
|
|
660
|
+
async shutdown() {
|
|
661
|
+
await this.driver.shutdown();
|
|
662
|
+
}
|
|
396
663
|
async executeNamedQuery(request, options = {}) {
|
|
397
664
|
validateIdentity(request);
|
|
398
665
|
const operation = requireDriverOperation(request.driverOperation);
|
|
@@ -564,6 +831,33 @@ export class DriverGeneratedApplicationTransport {
|
|
|
564
831
|
return decodeDriverMutationResult(requireDriverValue(result));
|
|
565
832
|
}
|
|
566
833
|
}
|
|
834
|
+
for (const methodName of [
|
|
835
|
+
"executeNamedQuery",
|
|
836
|
+
"executeCommand",
|
|
837
|
+
"executeVectorInspection",
|
|
838
|
+
"executeCommandBatch",
|
|
839
|
+
"consumeEventStream",
|
|
840
|
+
"acknowledgeEvent",
|
|
841
|
+
"negativeAcknowledgeEvent",
|
|
842
|
+
"seekEventConsumer",
|
|
843
|
+
"eventConsumerStatus",
|
|
844
|
+
"consumeContextualSubscription",
|
|
845
|
+
"acknowledgeContextualItem",
|
|
846
|
+
"negativeAcknowledgeContextualItem",
|
|
847
|
+
"contextualSubscriptionStatus",
|
|
848
|
+
"executeContextualReaction",
|
|
849
|
+
"watchNamedQuery",
|
|
850
|
+
]) {
|
|
851
|
+
Object.defineProperty(DriverApplicationTransport.prototype, methodName, {
|
|
852
|
+
configurable: true,
|
|
853
|
+
writable: true,
|
|
854
|
+
value(...args) {
|
|
855
|
+
const adapter = new DriverGeneratedApplicationTransport(this);
|
|
856
|
+
const fn = adapter[methodName];
|
|
857
|
+
return fn.apply(adapter, args);
|
|
858
|
+
},
|
|
859
|
+
});
|
|
860
|
+
}
|
|
567
861
|
function validateDriverReactiveRequest(value) {
|
|
568
862
|
if (!HASH.test(value.reactiveModuleHash) || !SYMBOL.test(value.operationName)) {
|
|
569
863
|
throw new Error("invalid generated reactive operation identity");
|
|
@@ -613,6 +907,42 @@ function decodeDriverMutationResult(value) {
|
|
|
613
907
|
}
|
|
614
908
|
function decodeDriverConsumerStatus(value) {
|
|
615
909
|
const status = driverRecord(value);
|
|
910
|
+
if (status.kind !== undefined) {
|
|
911
|
+
const kind = driverEnum(status.kind);
|
|
912
|
+
if (kind === "protected") {
|
|
913
|
+
const historyIncarnation = driverPositiveU64(requiredDriverField(status, "history_incarnation"));
|
|
914
|
+
const rawCursor = requiredDriverField(status, "progress_cursor");
|
|
915
|
+
let progressCursor;
|
|
916
|
+
if (rawCursor.type === "bytes") {
|
|
917
|
+
if (rawCursor.value.length < 1 || rawCursor.value.length > 512) {
|
|
918
|
+
throw new Error("invalid RiffDB driver consumer status");
|
|
919
|
+
}
|
|
920
|
+
progressCursor = rawCursor.value;
|
|
921
|
+
}
|
|
922
|
+
else if (rawCursor.type === "string") {
|
|
923
|
+
progressCursor = expectBoundedString(rawCursor.value, 512);
|
|
924
|
+
if (progressCursor.length < 1) {
|
|
925
|
+
throw new Error("invalid RiffDB driver consumer status");
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
else {
|
|
929
|
+
throw new Error("invalid RiffDB driver consumer status");
|
|
930
|
+
}
|
|
931
|
+
return {
|
|
932
|
+
kind: "protected",
|
|
933
|
+
revision: 0n,
|
|
934
|
+
checkpoint: progressCursor,
|
|
935
|
+
progressCursor,
|
|
936
|
+
historyIncarnation,
|
|
937
|
+
liveLeases: 0,
|
|
938
|
+
retries: 0,
|
|
939
|
+
deadLetters: 0,
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
if (kind !== "exact") {
|
|
943
|
+
throw new Error("invalid RiffDB driver consumer status");
|
|
944
|
+
}
|
|
945
|
+
}
|
|
616
946
|
return {
|
|
617
947
|
revision: driverPositiveU64(requiredDriverField(status, "revision")),
|
|
618
948
|
checkpoint: driverString(requiredDriverField(status, "checkpoint"), 256),
|
|
@@ -701,13 +1031,13 @@ function decodeDriverLiveUpdate(value, applicationHead, responseCursor) {
|
|
|
701
1031
|
return {
|
|
702
1032
|
type: "terminal",
|
|
703
1033
|
reason,
|
|
704
|
-
lastApplicationHead:
|
|
1034
|
+
lastApplicationHead: driverNonnegativeU64(requiredDriverField(record, "application_head")),
|
|
705
1035
|
historyIncarnation: driverPositiveU64(requiredDriverField(record, "history_incarnation")),
|
|
706
1036
|
};
|
|
707
1037
|
}
|
|
708
1038
|
const checkpoint = driverRecord(requiredDriverField(update, "checkpoint"));
|
|
709
1039
|
const cursor = driverString(requiredDriverField(checkpoint, "cursor"), 16_384);
|
|
710
|
-
const head =
|
|
1040
|
+
const head = driverNonnegativeU64(requiredDriverField(checkpoint, "application_head"));
|
|
711
1041
|
const history = driverPositiveU64(requiredDriverField(checkpoint, "history_incarnation"));
|
|
712
1042
|
if (applicationHead !== head || responseCursor !== cursor) {
|
|
713
1043
|
throw new Error("RiffDB driver live frontier is inconsistent");
|
|
@@ -774,6 +1104,7 @@ function decodeDriverDynamic(value) {
|
|
|
774
1104
|
throw new Error("invalid RiffDB driver money");
|
|
775
1105
|
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
1106
|
}
|
|
1107
|
+
case "vector": return decodeVectorBits(value.value.component_bits, value.value.component_bits.length);
|
|
777
1108
|
case "list": return value.value.map(decodeDriverDynamic);
|
|
778
1109
|
case "record": return decodeDriverDynamicRecord(value.value);
|
|
779
1110
|
}
|
|
@@ -805,6 +1136,14 @@ function driverPositiveU64(value) {
|
|
|
805
1136
|
throw new Error("invalid RiffDB driver u64");
|
|
806
1137
|
return parsed;
|
|
807
1138
|
}
|
|
1139
|
+
function driverNonnegativeU64(value) {
|
|
1140
|
+
if (value.type !== "u64")
|
|
1141
|
+
throw new Error("invalid RiffDB driver u64");
|
|
1142
|
+
const parsed = BigInt(value.value);
|
|
1143
|
+
if (parsed < 0n || parsed > (1n << 64n) - 1n)
|
|
1144
|
+
throw new Error("invalid RiffDB driver u64");
|
|
1145
|
+
return parsed;
|
|
1146
|
+
}
|
|
808
1147
|
function driverBoundedU32(value) {
|
|
809
1148
|
if (value.type !== "u64")
|
|
810
1149
|
throw new Error("invalid RiffDB driver u32");
|
|
@@ -899,14 +1238,14 @@ function encodeDriverValue(value, schema, budgetPath) {
|
|
|
899
1238
|
if (typeof value !== "boolean")
|
|
900
1239
|
throw new Error("invalid generated RiffDB bool input");
|
|
901
1240
|
return { type: "bool", value };
|
|
902
|
-
case "i64":
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
case "u64":
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1241
|
+
case "i64": {
|
|
1242
|
+
const parsed = coerceI64Input(value, "invalid generated RiffDB i64 input");
|
|
1243
|
+
return { type: "i64", value: parsed.toString() };
|
|
1244
|
+
}
|
|
1245
|
+
case "u64": {
|
|
1246
|
+
const parsed = coerceU64Input(value, "invalid generated RiffDB u64 input");
|
|
1247
|
+
return { type: "u64", value: parsed.toString() };
|
|
1248
|
+
}
|
|
910
1249
|
case "string": {
|
|
911
1250
|
const string = expectBoundedString(value, 262_144);
|
|
912
1251
|
if (schema.maximumBytes !== undefined && new TextEncoder().encode(string).length > schema.maximumBytes && budgetPath !== undefined) {
|
|
@@ -932,10 +1271,8 @@ function encodeDriverValue(value, schema, budgetPath) {
|
|
|
932
1271
|
throw new Error("invalid generated RiffDB date input");
|
|
933
1272
|
return { type: "date", value: String(value) };
|
|
934
1273
|
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) } };
|
|
1274
|
+
const timestamp = coerceTimestampInput(value, "invalid generated RiffDB timestamp input");
|
|
1275
|
+
return { type: "timestamp", value: { seconds: timestamp.seconds.toString(), nanos: timestamp.nanos } };
|
|
939
1276
|
}
|
|
940
1277
|
case "decimal": return { type: "decimal", value: encodeDriverDecimal(value, schema) };
|
|
941
1278
|
case "money": {
|
|
@@ -1120,7 +1457,7 @@ function decodeDriverValue(value, schema) {
|
|
|
1120
1457
|
throw new Error("invalid RiffDB driver record result");
|
|
1121
1458
|
output[field.name] = decodeDriverValue(fieldValue, field.schema);
|
|
1122
1459
|
}
|
|
1123
|
-
return output;
|
|
1460
|
+
return attachRecordToJson(output);
|
|
1124
1461
|
}
|
|
1125
1462
|
switch (schema.kind) {
|
|
1126
1463
|
case "bool":
|
|
@@ -1167,7 +1504,7 @@ function decodeDriverValue(value, schema) {
|
|
|
1167
1504
|
break;
|
|
1168
1505
|
case "timestamp":
|
|
1169
1506
|
if (value.type === "timestamp")
|
|
1170
|
-
return
|
|
1507
|
+
return createTimestampValue(BigInt(value.value.seconds), boundedInteger(value.value.nanos, 0, 999_999_999));
|
|
1171
1508
|
break;
|
|
1172
1509
|
case "decimal":
|
|
1173
1510
|
if (value.type === "decimal")
|
|
@@ -1175,7 +1512,7 @@ function decodeDriverValue(value, schema) {
|
|
|
1175
1512
|
break;
|
|
1176
1513
|
case "money":
|
|
1177
1514
|
if (value.type === "money" && value.value.currency === schema.currency)
|
|
1178
|
-
return { currency: value.value.currency, amount: decodeDriverDecimal(value.value.amount, schema) };
|
|
1515
|
+
return attachMoneyToJson({ currency: value.value.currency, amount: decodeDriverDecimal(value.value.amount, schema) });
|
|
1179
1516
|
break;
|
|
1180
1517
|
case "vector":
|
|
1181
1518
|
if (value.type === "vector")
|
|
@@ -1196,7 +1533,7 @@ function decodeDriverDecimal(value, schema) {
|
|
|
1196
1533
|
const coefficient = Uint8Array.from(Buffer.from(value.coefficient, "base64"));
|
|
1197
1534
|
if (coefficient.byteLength < 1 || coefficient.byteLength > 16)
|
|
1198
1535
|
throw new Error("RiffDB driver decimal coefficient is invalid");
|
|
1199
|
-
return { coefficientTwosComplement: coefficient, precision: schema.precision, scale: schema.scale };
|
|
1536
|
+
return attachDecimalToJson({ coefficientTwosComplement: coefficient, precision: schema.precision, scale: schema.scale });
|
|
1200
1537
|
}
|
|
1201
1538
|
function decodeTypedCommandResult(result, request) {
|
|
1202
1539
|
if (expectHash(result.plan_hash) !== request.planHash) {
|
|
@@ -1207,7 +1544,7 @@ function decodeTypedCommandResult(result, request) {
|
|
|
1207
1544
|
if (schema === undefined)
|
|
1208
1545
|
throw new Error("RiffDB application response has an unknown outcome");
|
|
1209
1546
|
const payload = decodeWire(result.outcome, schema);
|
|
1210
|
-
const outcome = { outcome: outcomeName, ...exactObject(payload) };
|
|
1547
|
+
const outcome = attachRecordToJson({ outcome: outcomeName, ...exactObject(payload) });
|
|
1211
1548
|
const typed = {
|
|
1212
1549
|
outcome,
|
|
1213
1550
|
contractVersion: positiveNumber(result.contract_version),
|
|
@@ -1367,16 +1704,14 @@ function encodeValue(value, schema, budgetPath) {
|
|
|
1367
1704
|
if (typeof value !== "boolean")
|
|
1368
1705
|
throw new Error("invalid boolean input");
|
|
1369
1706
|
return value;
|
|
1370
|
-
case "i64":
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
}
|
|
1379
|
-
return { $u64: value.toString() };
|
|
1707
|
+
case "i64": {
|
|
1708
|
+
const parsed = coerceI64Input(value, "invalid i64 input");
|
|
1709
|
+
return { $i64: parsed.toString() };
|
|
1710
|
+
}
|
|
1711
|
+
case "u64": {
|
|
1712
|
+
const parsed = coerceU64Input(value, "invalid u64 input");
|
|
1713
|
+
return { $u64: parsed.toString() };
|
|
1714
|
+
}
|
|
1380
1715
|
case "decimal": {
|
|
1381
1716
|
const decimal = encodeDecimal(value);
|
|
1382
1717
|
if (schema.precision === undefined || schema.scale === undefined
|
|
@@ -1416,14 +1751,8 @@ function encodeValue(value, schema, budgetPath) {
|
|
|
1416
1751
|
}
|
|
1417
1752
|
return { $date: value };
|
|
1418
1753
|
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 } };
|
|
1754
|
+
const timestamp = coerceTimestampInput(value, "invalid timestamp input");
|
|
1755
|
+
return { $timestamp: { seconds: timestamp.seconds.toString(), nanos: timestamp.nanos } };
|
|
1427
1756
|
}
|
|
1428
1757
|
case "vector": return { $vector: encodeVector(value, schema.dimension) };
|
|
1429
1758
|
case "limit":
|
|
@@ -1500,7 +1829,7 @@ function normalizeQueryFields(value) {
|
|
|
1500
1829
|
throw new Error("duplicate RiffDB query field");
|
|
1501
1830
|
result[fieldName] = decodeTagged(entry.value);
|
|
1502
1831
|
}
|
|
1503
|
-
return result;
|
|
1832
|
+
return attachRecordToJson(result);
|
|
1504
1833
|
});
|
|
1505
1834
|
switch (field.cardinality) {
|
|
1506
1835
|
case "one":
|
|
@@ -1533,13 +1862,13 @@ function decodeTagged(value) {
|
|
|
1533
1862
|
case "uuid": return expectBoundedString(input.value, 262_144);
|
|
1534
1863
|
case "enum": return expectSymbol(input.name);
|
|
1535
1864
|
case "date": return Number(expectDecimal(input.days_since_unix_epoch));
|
|
1536
|
-
case "timestamp": return
|
|
1865
|
+
case "timestamp": return createTimestampValue(BigInt(expectDecimal(input.seconds)), nonnegativeNumber(input.nanos));
|
|
1537
1866
|
case "bytes": return Uint8Array.from(Buffer.from(expectBoundedString(input.value, MAX_OUTPUT_BYTES), "base64"));
|
|
1538
1867
|
case "decimal": return decodeDecimal(input);
|
|
1539
|
-
case "money": return {
|
|
1868
|
+
case "money": return attachMoneyToJson({
|
|
1540
1869
|
currency: expectCurrency(input.currency),
|
|
1541
1870
|
amount: decodeDecimal(exactObject(input.amount)),
|
|
1542
|
-
};
|
|
1871
|
+
});
|
|
1543
1872
|
case "vector": return decodeVector(input.components, undefined);
|
|
1544
1873
|
case "list":
|
|
1545
1874
|
if (!Array.isArray(input.values))
|
|
@@ -1575,9 +1904,9 @@ function decodeDecimal(input) {
|
|
|
1575
1904
|
if (coefficient.byteLength < 1 || coefficient.byteLength > 16)
|
|
1576
1905
|
return fail();
|
|
1577
1906
|
if (input.precision === undefined)
|
|
1578
|
-
return { coefficientTwosComplement: coefficient, scale };
|
|
1907
|
+
return attachDecimalToJson({ coefficientTwosComplement: coefficient, scale });
|
|
1579
1908
|
const precision = positiveNumber(input.precision);
|
|
1580
|
-
return { coefficientTwosComplement: coefficient, scale, precision };
|
|
1909
|
+
return attachDecimalToJson({ coefficientTwosComplement: coefficient, scale, precision });
|
|
1581
1910
|
}
|
|
1582
1911
|
function decodePlain(value, schema) {
|
|
1583
1912
|
if (schema.kind === "optional")
|
|
@@ -1596,7 +1925,7 @@ function decodePlain(value, schema) {
|
|
|
1596
1925
|
return fail();
|
|
1597
1926
|
for (const field of schema.fields)
|
|
1598
1927
|
output[field.name] = decodePlain(input[field.name], field.schema);
|
|
1599
|
-
return output;
|
|
1928
|
+
return attachRecordToJson(output);
|
|
1600
1929
|
}
|
|
1601
1930
|
switch (schema.kind) {
|
|
1602
1931
|
case "bool":
|
|
@@ -1625,8 +1954,9 @@ function decodePlain(value, schema) {
|
|
|
1625
1954
|
break;
|
|
1626
1955
|
case "timestamp": {
|
|
1627
1956
|
const input = exactObject(value);
|
|
1628
|
-
if (typeof input.seconds === "bigint" && Number.isInteger(input.nanos))
|
|
1629
|
-
return
|
|
1957
|
+
if (typeof input.seconds === "bigint" && Number.isInteger(input.nanos)) {
|
|
1958
|
+
return createTimestampValue(input.seconds, input.nanos);
|
|
1959
|
+
}
|
|
1630
1960
|
break;
|
|
1631
1961
|
}
|
|
1632
1962
|
case "bytes":
|
|
@@ -1641,10 +1971,10 @@ function decodePlain(value, schema) {
|
|
|
1641
1971
|
const currency = expectCurrency(input.currency);
|
|
1642
1972
|
if (schema.currency !== undefined && currency !== schema.currency)
|
|
1643
1973
|
return fail();
|
|
1644
|
-
return {
|
|
1974
|
+
return attachMoneyToJson({
|
|
1645
1975
|
currency,
|
|
1646
1976
|
amount: normalizeDecodedDecimal(input.amount, schema.precision, schema.scale),
|
|
1647
|
-
};
|
|
1977
|
+
});
|
|
1648
1978
|
}
|
|
1649
1979
|
case "vector": return decodeVector(value, schema.dimension);
|
|
1650
1980
|
default: break;
|
|
@@ -1659,11 +1989,11 @@ function normalizeDecodedDecimal(value, expectedPrecision, expectedScale) {
|
|
|
1659
1989
|
|| (expectedScale !== undefined && encoded.scale !== expectedScale))
|
|
1660
1990
|
return fail();
|
|
1661
1991
|
const precision = expectedPrecision ?? encoded.precision;
|
|
1662
|
-
return {
|
|
1992
|
+
return attachDecimalToJson({
|
|
1663
1993
|
coefficientTwosComplement: input.coefficientTwosComplement,
|
|
1664
1994
|
scale: encoded.scale,
|
|
1665
1995
|
...(precision === undefined ? {} : { precision }),
|
|
1666
|
-
};
|
|
1996
|
+
});
|
|
1667
1997
|
}
|
|
1668
1998
|
function decodeWire(value, schema) {
|
|
1669
1999
|
if (schema.kind !== "record")
|
|
@@ -1876,7 +2206,7 @@ function encodeVectorBits(value, dimension) {
|
|
|
1876
2206
|
});
|
|
1877
2207
|
}
|
|
1878
2208
|
function decodeVectorBits(value, dimension) {
|
|
1879
|
-
if (!Array.isArray(value) || value.length !== dimension)
|
|
2209
|
+
if (!Array.isArray(value) || dimension < 1 || dimension > 4_096 || value.length !== dimension)
|
|
1880
2210
|
return fail();
|
|
1881
2211
|
const float = new Float32Array(1);
|
|
1882
2212
|
const bits = new Uint32Array(float.buffer);
|
|
@@ -1903,6 +2233,26 @@ function decodeVector(value, dimension) {
|
|
|
1903
2233
|
}
|
|
1904
2234
|
function decodeReactiveConsumerStatus(value) {
|
|
1905
2235
|
const status = exactObject(value);
|
|
2236
|
+
if (status.kind !== undefined) {
|
|
2237
|
+
const kind = expectSymbol(status.kind);
|
|
2238
|
+
if (kind === "protected") {
|
|
2239
|
+
const progressCursor = expectBoundedString(status.progress_cursor, 512);
|
|
2240
|
+
if (progressCursor.length < 1)
|
|
2241
|
+
return fail();
|
|
2242
|
+
return {
|
|
2243
|
+
kind: "protected",
|
|
2244
|
+
revision: 0n,
|
|
2245
|
+
checkpoint: progressCursor,
|
|
2246
|
+
progressCursor,
|
|
2247
|
+
historyIncarnation: positiveBigInt(status.history_incarnation),
|
|
2248
|
+
liveLeases: 0,
|
|
2249
|
+
retries: 0,
|
|
2250
|
+
deadLetters: 0,
|
|
2251
|
+
};
|
|
2252
|
+
}
|
|
2253
|
+
if (kind !== "exact")
|
|
2254
|
+
return fail();
|
|
2255
|
+
}
|
|
1906
2256
|
return {
|
|
1907
2257
|
revision: positiveBigInt(status.revision),
|
|
1908
2258
|
checkpoint: expectBoundedString(status.checkpoint, 64),
|
|
@@ -2031,6 +2381,122 @@ function signedTwosComplement(value) {
|
|
|
2031
2381
|
}
|
|
2032
2382
|
return fail();
|
|
2033
2383
|
}
|
|
2384
|
+
function decodeSignedTwosComplement(bytes) {
|
|
2385
|
+
if (bytes.byteLength < 1 || bytes.byteLength > 16)
|
|
2386
|
+
return fail();
|
|
2387
|
+
let value = 0n;
|
|
2388
|
+
for (const byte of bytes) {
|
|
2389
|
+
value = (value << 8n) | BigInt(byte);
|
|
2390
|
+
}
|
|
2391
|
+
if ((bytes[0] & 0x80) !== 0) {
|
|
2392
|
+
value -= 1n << BigInt(bytes.byteLength * 8);
|
|
2393
|
+
}
|
|
2394
|
+
return value;
|
|
2395
|
+
}
|
|
2396
|
+
function coerceI64Input(value, errorMessage) {
|
|
2397
|
+
let parsed;
|
|
2398
|
+
if (typeof value === "bigint") {
|
|
2399
|
+
parsed = value;
|
|
2400
|
+
}
|
|
2401
|
+
else if (typeof value === "number") {
|
|
2402
|
+
if (!Number.isSafeInteger(value))
|
|
2403
|
+
throw new Error(errorMessage);
|
|
2404
|
+
parsed = BigInt(value);
|
|
2405
|
+
}
|
|
2406
|
+
else if (typeof value === "string" && /^-?(?:0|[1-9][0-9]*)$/.test(value)) {
|
|
2407
|
+
parsed = BigInt(value);
|
|
2408
|
+
}
|
|
2409
|
+
else {
|
|
2410
|
+
throw new Error(errorMessage);
|
|
2411
|
+
}
|
|
2412
|
+
if (parsed < -(1n << 63n) || parsed > (1n << 63n) - 1n) {
|
|
2413
|
+
throw new Error(errorMessage);
|
|
2414
|
+
}
|
|
2415
|
+
return parsed;
|
|
2416
|
+
}
|
|
2417
|
+
function coerceU64Input(value, errorMessage) {
|
|
2418
|
+
let parsed;
|
|
2419
|
+
if (typeof value === "bigint") {
|
|
2420
|
+
parsed = value;
|
|
2421
|
+
}
|
|
2422
|
+
else if (typeof value === "number") {
|
|
2423
|
+
if (!Number.isSafeInteger(value) || value < 0)
|
|
2424
|
+
throw new Error(errorMessage);
|
|
2425
|
+
parsed = BigInt(value);
|
|
2426
|
+
}
|
|
2427
|
+
else if (typeof value === "string" && /^(?:0|[1-9][0-9]*)$/.test(value)) {
|
|
2428
|
+
parsed = BigInt(value);
|
|
2429
|
+
}
|
|
2430
|
+
else {
|
|
2431
|
+
throw new Error(errorMessage);
|
|
2432
|
+
}
|
|
2433
|
+
if (parsed < 0n || parsed > (1n << 64n) - 1n) {
|
|
2434
|
+
throw new Error(errorMessage);
|
|
2435
|
+
}
|
|
2436
|
+
return parsed;
|
|
2437
|
+
}
|
|
2438
|
+
function coerceTimestampInput(value, errorMessage) {
|
|
2439
|
+
if (value instanceof Date) {
|
|
2440
|
+
const ms = value.getTime();
|
|
2441
|
+
if (!Number.isFinite(ms))
|
|
2442
|
+
throw new Error(errorMessage);
|
|
2443
|
+
const seconds = BigInt(Math.floor(ms / 1000));
|
|
2444
|
+
const nanos = (((ms % 1000) + 1000) % 1000) * 1_000_000;
|
|
2445
|
+
return { seconds, nanos };
|
|
2446
|
+
}
|
|
2447
|
+
if (typeof value === "string") {
|
|
2448
|
+
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);
|
|
2449
|
+
if (match === null)
|
|
2450
|
+
throw new Error(errorMessage);
|
|
2451
|
+
const rawYear = match[1];
|
|
2452
|
+
if (/^-0+$/.test(rawYear))
|
|
2453
|
+
throw new Error(errorMessage);
|
|
2454
|
+
const year = BigInt(rawYear);
|
|
2455
|
+
const month = Number(match[2]);
|
|
2456
|
+
const day = Number(match[3]);
|
|
2457
|
+
const hour = Number(match[4]);
|
|
2458
|
+
const minute = Number(match[5]);
|
|
2459
|
+
const second = Number(match[6]);
|
|
2460
|
+
if (month < 1
|
|
2461
|
+
|| month > 12
|
|
2462
|
+
|| day < 1
|
|
2463
|
+
|| day > daysInGregorianMonth(year, month)
|
|
2464
|
+
|| hour > 23
|
|
2465
|
+
|| minute > 59
|
|
2466
|
+
|| second > 59) {
|
|
2467
|
+
throw new Error(errorMessage);
|
|
2468
|
+
}
|
|
2469
|
+
let offsetSeconds = 0n;
|
|
2470
|
+
if (match[8] !== "Z") {
|
|
2471
|
+
const offsetSign = match[9] === "-" ? -1n : 1n;
|
|
2472
|
+
const offsetHours = Number(match[10]);
|
|
2473
|
+
const offsetMinutes = Number(match[11]);
|
|
2474
|
+
if (offsetHours > 23 || offsetMinutes > 59) {
|
|
2475
|
+
throw new Error(errorMessage);
|
|
2476
|
+
}
|
|
2477
|
+
offsetSeconds = offsetSign * BigInt((offsetHours * 60 + offsetMinutes) * 60);
|
|
2478
|
+
}
|
|
2479
|
+
const seconds = daysFromCivil(year, month, day) * 86400n + BigInt(hour * 3600 + minute * 60 + second) - offsetSeconds;
|
|
2480
|
+
if (seconds < -(1n << 63n) || seconds > (1n << 63n) - 1n) {
|
|
2481
|
+
throw new Error(errorMessage);
|
|
2482
|
+
}
|
|
2483
|
+
const nanos = match[7] === undefined ? 0 : Number(match[7].padEnd(9, "0"));
|
|
2484
|
+
if (!Number.isInteger(nanos) || nanos < 0 || nanos > 999_999_999) {
|
|
2485
|
+
throw new Error(errorMessage);
|
|
2486
|
+
}
|
|
2487
|
+
return { seconds, nanos };
|
|
2488
|
+
}
|
|
2489
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
2490
|
+
const input = value;
|
|
2491
|
+
const seconds = coerceI64Input(input.seconds, errorMessage);
|
|
2492
|
+
const rawNanos = input.nanos === undefined ? 0 : input.nanos;
|
|
2493
|
+
if (typeof rawNanos !== "number" || !Number.isInteger(rawNanos) || rawNanos < 0 || rawNanos > 999_999_999) {
|
|
2494
|
+
throw new Error(errorMessage);
|
|
2495
|
+
}
|
|
2496
|
+
return { seconds, nanos: rawNanos };
|
|
2497
|
+
}
|
|
2498
|
+
throw new Error(errorMessage);
|
|
2499
|
+
}
|
|
2034
2500
|
function positiveNumber(value) {
|
|
2035
2501
|
const number = typeof value === "string" ? Number(expectDecimal(value)) : value;
|
|
2036
2502
|
if (!Number.isSafeInteger(number) || number < 1)
|
|
@@ -2051,4 +2517,4 @@ function positiveBigInt(value) {
|
|
|
2051
2517
|
function fail() {
|
|
2052
2518
|
throw new Error("invalid RiffDB application response");
|
|
2053
2519
|
}
|
|
2054
|
-
export { DRIVER_ERROR_REGISTRY_HASH, DRIVER_PROTOCOL_VERSION, DRIVER_VALUE_REGISTRY_HASH, DriverApplicationError, DriverApplicationTransport, } from "./driver.js";
|
|
2520
|
+
export { DRIVER_ERROR_REGISTRY_HASH, DRIVER_PROTOCOL_VERSION, DRIVER_VALUE_REGISTRY_HASH, DriverApplicationError, DriverApplicationTransport, resolveTransportOptionsFromEnv, } from "./driver.js";
|