@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/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: driverPositiveU64(requiredDriverField(record, "application_head")),
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 = driverPositiveU64(requiredDriverField(checkpoint, "application_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
- if (typeof value !== "bigint" || value < -(1n << 63n) || value > (1n << 63n) - 1n)
904
- throw new Error("invalid generated RiffDB i64 input");
905
- return { type: "i64", value: value.toString() };
906
- case "u64":
907
- if (typeof value !== "bigint" || value < 0n || value > (1n << 64n) - 1n)
908
- throw new Error("invalid generated RiffDB u64 input");
909
- return { type: "u64", value: value.toString() };
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 = exactObject(value);
936
- if (typeof timestamp.seconds !== "bigint" || timestamp.seconds < -(1n << 63n) || timestamp.seconds > (1n << 63n) - 1n)
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 { seconds: BigInt(value.value.seconds), nanos: boundedInteger(value.value.nanos, 0, 999_999_999) };
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
- if (typeof value !== "bigint" || value < -(1n << 63n) || value > (1n << 63n) - 1n) {
1372
- throw new Error("invalid i64 input");
1373
- }
1374
- return { $i64: value.toString() };
1375
- case "u64":
1376
- if (typeof value !== "bigint" || value < 0n || value > (1n << 64n) - 1n) {
1377
- throw new Error("invalid u64 input");
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 input = exactObject(value);
1420
- const seconds = input.seconds;
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 { seconds: BigInt(expectDecimal(input.seconds)), nanos: nonnegativeNumber(input.nanos) };
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 value;
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";