@riffdb/client 0.0.0-stage → 0.2.0
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/LICENSE-APACHE +201 -0
- package/LICENSE-MIT +27 -0
- package/README.md +44 -2
- package/conformance-manifest-v1.json +15 -0
- package/dist/driver.d.ts +171 -0
- package/dist/driver.js +932 -0
- package/dist/index.d.ts +332 -0
- package/dist/index.js +2054 -0
- package/dist/operator.d.ts +65 -0
- package/dist/operator.js +313 -0
- package/package.json +1 -6
package/dist/index.js
ADDED
|
@@ -0,0 +1,2054 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
const executeFile = promisify(execFile);
|
|
7
|
+
const MAX_OUTPUT_BYTES = 4_194_304;
|
|
8
|
+
const HASH = /^[0-9a-f]{64}$/;
|
|
9
|
+
const SYMBOL = /^[A-Za-z][A-Za-z0-9_.-]{0,255}$/;
|
|
10
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
11
|
+
export class InputBudgetError extends Error {
|
|
12
|
+
cause;
|
|
13
|
+
path;
|
|
14
|
+
constructor(cause, path) {
|
|
15
|
+
super("generated command input exceeds its compiled budget");
|
|
16
|
+
this.cause = cause;
|
|
17
|
+
this.path = path;
|
|
18
|
+
this.name = "InputBudgetError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Constructs an exact fixed-point value from decimal text without using a
|
|
23
|
+
* JavaScript floating-point number.
|
|
24
|
+
*/
|
|
25
|
+
export function exactDecimal(value, precision, scale) {
|
|
26
|
+
if (!Number.isInteger(precision) || precision < 1 || precision > 38
|
|
27
|
+
|| !Number.isInteger(scale) || scale < 0 || scale > precision) {
|
|
28
|
+
throw new Error("invalid exact decimal type");
|
|
29
|
+
}
|
|
30
|
+
if (typeof value !== "string" || value.length < 1 || value.length > 128) {
|
|
31
|
+
throw new Error("invalid exact decimal text");
|
|
32
|
+
}
|
|
33
|
+
const match = /^(-?)([0-9]+)(?:\.([0-9]+))?$/.exec(value);
|
|
34
|
+
if (match === null)
|
|
35
|
+
throw new Error("invalid exact decimal text");
|
|
36
|
+
const fractional = match[3] ?? "";
|
|
37
|
+
if (fractional.length > scale)
|
|
38
|
+
throw new Error("exact decimal exceeds scale");
|
|
39
|
+
const magnitudeText = `${match[2]}${fractional.padEnd(scale, "0")}`;
|
|
40
|
+
let coefficient = BigInt(magnitudeText);
|
|
41
|
+
if (match[1] === "-" && coefficient !== 0n)
|
|
42
|
+
coefficient = -coefficient;
|
|
43
|
+
const limit = 10n ** BigInt(precision);
|
|
44
|
+
if (coefficient <= -limit || coefficient >= limit)
|
|
45
|
+
throw new Error("exact decimal exceeds precision");
|
|
46
|
+
return {
|
|
47
|
+
coefficientTwosComplement: signedTwosComplement(coefficient),
|
|
48
|
+
scale,
|
|
49
|
+
precision,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** Constructs the exact precision-38, scale-2 value used by `money<CURRENCY>`. */
|
|
53
|
+
export function exactMoney(currency, value) {
|
|
54
|
+
if (!/^[A-Z]{3}$/.test(currency))
|
|
55
|
+
throw new Error("invalid exact money currency");
|
|
56
|
+
return { currency, amount: exactDecimal(value, 38, 2) };
|
|
57
|
+
}
|
|
58
|
+
export class CliApplicationTransport {
|
|
59
|
+
options;
|
|
60
|
+
constructor(options) {
|
|
61
|
+
this.options = options;
|
|
62
|
+
if (options.riffdbPath.length === 0 || options.riffdbPath.length > 4096
|
|
63
|
+
|| options.endpoint.length === 0 || options.endpoint.length > 2048
|
|
64
|
+
|| options.credentialFile.length === 0 || options.credentialFile.length > 4096) {
|
|
65
|
+
throw new Error("invalid RiffDB application transport configuration");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async executeNamedQuery(request, options = {}) {
|
|
69
|
+
validateIdentity(request);
|
|
70
|
+
const parameters = encodeValue(request.parameters, request.parameterSchema);
|
|
71
|
+
const args = this.baseArguments();
|
|
72
|
+
args.push("query", "run-named", request.queryName, "--module-hash", request.moduleHash, "--contract-lineage", request.contractLineage, "--contract-version", String(request.contractVersion));
|
|
73
|
+
if (options.cursor !== undefined)
|
|
74
|
+
args.push("--cursor", options.cursor);
|
|
75
|
+
if (options.readAfterCommit !== undefined) {
|
|
76
|
+
if (options.readAfterCommit < 1n)
|
|
77
|
+
throw new Error("invalid read-after-commit fence");
|
|
78
|
+
args.push("--read-after-commit", options.readAfterCommit.toString());
|
|
79
|
+
}
|
|
80
|
+
if (options.consistency === "admissionHead")
|
|
81
|
+
args.push("--consistency", "admission-head");
|
|
82
|
+
const envelope = await this.invoke(args, parameters, request.decodeError);
|
|
83
|
+
const result = exactObject(envelope.result);
|
|
84
|
+
const rawIdentity = exactObject(result.identity);
|
|
85
|
+
const outcome = expectSymbol(result.outcome);
|
|
86
|
+
const resultSchema = request.resultSchemas[outcome];
|
|
87
|
+
if (resultSchema === undefined)
|
|
88
|
+
throw new Error("RiffDB application response has an unknown outcome");
|
|
89
|
+
const value = decodePlain({ outcome, ...normalizeQueryFields(result.fields) }, {
|
|
90
|
+
kind: "record",
|
|
91
|
+
fields: [
|
|
92
|
+
{ name: "outcome", schema: { kind: "string" } },
|
|
93
|
+
...recordFields(resultSchema),
|
|
94
|
+
],
|
|
95
|
+
});
|
|
96
|
+
const typed = {
|
|
97
|
+
identity: {
|
|
98
|
+
contractLineage: expectSymbol(rawIdentity.contract_lineage),
|
|
99
|
+
contractVersion: positiveNumber(rawIdentity.contract_version),
|
|
100
|
+
contractBundleHash: expectHash(rawIdentity.contract_bundle_hash),
|
|
101
|
+
moduleHash: expectHash(rawIdentity.module_hash),
|
|
102
|
+
queryName: expectSymbol(rawIdentity.query_name),
|
|
103
|
+
planHash: expectHash(rawIdentity.plan_hash),
|
|
104
|
+
},
|
|
105
|
+
value,
|
|
106
|
+
applicationHead: positiveBigInt(result.application_head),
|
|
107
|
+
};
|
|
108
|
+
if (typed.identity.contractLineage !== request.contractLineage
|
|
109
|
+
|| typed.identity.contractVersion !== request.contractVersion
|
|
110
|
+
|| typed.identity.contractBundleHash !== request.contractBundleHash
|
|
111
|
+
|| typed.identity.moduleHash !== request.moduleHash
|
|
112
|
+
|| typed.identity.queryName !== request.queryName
|
|
113
|
+
|| typed.identity.planHash !== request.planHash) {
|
|
114
|
+
throw new Error("RiffDB application identity mismatch");
|
|
115
|
+
}
|
|
116
|
+
if (result.next_cursor !== null && result.next_cursor !== undefined) {
|
|
117
|
+
return { ...typed, nextCursor: expectBoundedString(result.next_cursor, 4096) };
|
|
118
|
+
}
|
|
119
|
+
return typed;
|
|
120
|
+
}
|
|
121
|
+
async executeCommand(request, attemptBudget) {
|
|
122
|
+
validateIdentity(request);
|
|
123
|
+
if (!Number.isInteger(attemptBudget) || attemptBudget < 1 || attemptBudget > 10) {
|
|
124
|
+
throw new Error("invalid command attempt budget");
|
|
125
|
+
}
|
|
126
|
+
const input = encodeValue(request.input, request.inputSchema);
|
|
127
|
+
const args = this.baseArguments(attemptBudget);
|
|
128
|
+
args.push("command", "run", request.commandName, "--expected-version", String(request.contractVersion));
|
|
129
|
+
const envelope = await this.invoke(args, input, request.decodeError);
|
|
130
|
+
return decodeTypedCommandResult(exactObject(envelope.result), request);
|
|
131
|
+
}
|
|
132
|
+
async executeVectorInspection(request, options = {}) {
|
|
133
|
+
validateIdentity(request);
|
|
134
|
+
const limit = boundedInteger(request.limit, 1, 500);
|
|
135
|
+
const partition = JSON.stringify(encodeValue(request.partition, request.partitionSchema));
|
|
136
|
+
const args = this.baseArguments();
|
|
137
|
+
args.push("query", "inspect-vector", request.entity, request.field, "--partition", partition, "--kind", request.inspectionKind === "staleness" ? "stale" : "outdated", "--limit", String(limit), "--contract-lineage", request.contractLineage, "--contract-version", String(request.contractVersion));
|
|
138
|
+
if (options.cursor !== undefined)
|
|
139
|
+
args.push("--cursor-hex", options.cursor);
|
|
140
|
+
const envelope = await this.invokeArguments(args);
|
|
141
|
+
if (envelope.ok !== true)
|
|
142
|
+
throw new Error("RiffDB vector inspection failed");
|
|
143
|
+
const value = decodePlainVectorInspection(exactObject(envelope.result));
|
|
144
|
+
const raw = exactObject(envelope.result);
|
|
145
|
+
return {
|
|
146
|
+
value,
|
|
147
|
+
...(raw.observed_frontier === null || raw.observed_frontier === undefined
|
|
148
|
+
? {} : { applicationHead: positiveBigInt(raw.observed_frontier) }),
|
|
149
|
+
...(raw.next_cursor === null || raw.next_cursor === undefined
|
|
150
|
+
? {} : { nextCursor: expectBoundedString(raw.next_cursor, 32) }),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
async *consumeEventStream(request, options = {}) {
|
|
154
|
+
validateReactiveRequest(request);
|
|
155
|
+
const batchLimit = boundedInteger(options.batchLimit ?? 1, 1, 64);
|
|
156
|
+
const inFlightLimit = boundedInteger(options.inFlightLimit ?? 16, 1, 64);
|
|
157
|
+
const leaseSeconds = boundedInteger(options.leaseSeconds ?? 60, 5, 900);
|
|
158
|
+
const maximumWaitMs = boundedInteger(options.maximumWaitMs ?? 30_000, 0, 30_000);
|
|
159
|
+
while (true) {
|
|
160
|
+
const args = this.reactiveArguments(request);
|
|
161
|
+
args.push("event", "consume", "--module-hash", request.reactiveModuleHash, "--operation", request.operationName, "--consumer-name", request.consumerName, "--batch-limit", String(batchLimit), "--in-flight-limit", String(inFlightLimit), "--lease-seconds", String(leaseSeconds), "--wait-nanos", String(maximumWaitMs * 1_000_000));
|
|
162
|
+
addReactiveParameters(args, request.parameters, request.parameterSchema);
|
|
163
|
+
const envelope = await this.invokeArguments(args);
|
|
164
|
+
const result = exactObject(envelope.result);
|
|
165
|
+
const status = decodeReactiveConsumerStatus(result.status);
|
|
166
|
+
const historyIncarnation = status.historyIncarnation;
|
|
167
|
+
const events = expectArray(result.events).map((value) => {
|
|
168
|
+
const delivery = exactObject(value);
|
|
169
|
+
const fields = Object.fromEntries(expectArray(delivery.fields).map((field) => {
|
|
170
|
+
const item = exactObject(field);
|
|
171
|
+
return [expectSymbol(item.name), decodeTagged(item.value)];
|
|
172
|
+
}));
|
|
173
|
+
const expiration = exactObject(delivery.expires_at);
|
|
174
|
+
return {
|
|
175
|
+
eventId: expectBoundedString(delivery.event_id, 64),
|
|
176
|
+
event: { type: expectSymbol(delivery.event_name), ...fields },
|
|
177
|
+
attempt: positiveNumber(delivery.attempt),
|
|
178
|
+
leaseToken: expectHash(delivery.lease_token),
|
|
179
|
+
expiresAt: `${expectBoundedString(expiration.seconds, 32)}.${String(boundedInteger(expiration.nanos, 0, 999_999_999)).padStart(9, "0")}`,
|
|
180
|
+
historyIncarnation,
|
|
181
|
+
};
|
|
182
|
+
});
|
|
183
|
+
yield { events, waitTimedOut: result.wait_timed_out === true, status };
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
async acknowledgeEvent(request, delivery) {
|
|
187
|
+
return this.mutateEventLease("ack", request, delivery);
|
|
188
|
+
}
|
|
189
|
+
async negativeAcknowledgeEvent(request, delivery, retryDelayMs = 0) {
|
|
190
|
+
return this.mutateEventLease("nack", request, delivery, retryDelayMs);
|
|
191
|
+
}
|
|
192
|
+
async seekEventConsumer(request, checkpoint) {
|
|
193
|
+
validateReactiveRequest(request);
|
|
194
|
+
const args = this.reactiveArguments(request);
|
|
195
|
+
args.push("event", "seek", "--module-hash", request.reactiveModuleHash, "--operation", request.operationName, "--consumer-name", request.consumerName, "--checkpoint", expectBoundedString(checkpoint, 64));
|
|
196
|
+
addReactiveParameters(args, request.parameters, request.parameterSchema);
|
|
197
|
+
return decodeEventMutationResult((await this.invokeArguments(args)).result);
|
|
198
|
+
}
|
|
199
|
+
async eventConsumerStatus(request) {
|
|
200
|
+
validateReactiveRequest(request);
|
|
201
|
+
const args = this.reactiveArguments(request);
|
|
202
|
+
args.push("event", "status", "--module-hash", request.reactiveModuleHash, "--operation", request.operationName, "--consumer-name", request.consumerName);
|
|
203
|
+
addReactiveParameters(args, request.parameters, request.parameterSchema);
|
|
204
|
+
const result = exactObject((await this.invokeArguments(args)).result);
|
|
205
|
+
if (result.found !== true)
|
|
206
|
+
return undefined;
|
|
207
|
+
return decodeReactiveConsumerStatus(result.status);
|
|
208
|
+
}
|
|
209
|
+
async consumeContextualSubscription(request, maximumWaitMs = 30_000) {
|
|
210
|
+
validateReactiveRequest(request);
|
|
211
|
+
const wait = boundedInteger(maximumWaitMs, 0, 30_000);
|
|
212
|
+
const args = this.reactiveArguments(request);
|
|
213
|
+
args.push("contextual", "next", "--module-hash", request.reactiveModuleHash, "--operation", request.operationName, "--consumer-name", request.consumerName, "--wait-nanos", String(wait * 1_000_000));
|
|
214
|
+
addReactiveParameters(args, request.parameters, request.parameterSchema);
|
|
215
|
+
const result = exactObject((await this.invokeArguments(args)).result);
|
|
216
|
+
return {
|
|
217
|
+
items: expectArray(result.items).map((value) => decodeContextualItem(value)),
|
|
218
|
+
waitTimedOut: result.wait_timed_out === true,
|
|
219
|
+
status: decodeReactiveConsumerStatus(result.status),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
async acknowledgeContextualItem(request, item) {
|
|
223
|
+
return this.mutateContextualItem("ack", request, item);
|
|
224
|
+
}
|
|
225
|
+
async negativeAcknowledgeContextualItem(request, item, retryDelayMs = 0) {
|
|
226
|
+
return this.mutateContextualItem("nack", request, item, retryDelayMs);
|
|
227
|
+
}
|
|
228
|
+
async contextualSubscriptionStatus(request) {
|
|
229
|
+
validateReactiveRequest(request);
|
|
230
|
+
const args = this.reactiveArguments(request);
|
|
231
|
+
args.push("contextual", "status", "--module-hash", request.reactiveModuleHash, "--operation", request.operationName, "--consumer-name", request.consumerName);
|
|
232
|
+
addReactiveParameters(args, request.parameters, request.parameterSchema);
|
|
233
|
+
const result = exactObject((await this.invokeArguments(args)).result);
|
|
234
|
+
if (result.found !== true)
|
|
235
|
+
return undefined;
|
|
236
|
+
return decodeReactiveConsumerStatus(result.status);
|
|
237
|
+
}
|
|
238
|
+
async executeContextualReaction(request, reaction, command) {
|
|
239
|
+
validateReactiveRequest(request);
|
|
240
|
+
validateIdentity(command);
|
|
241
|
+
if (reaction.commandName !== command.commandName) {
|
|
242
|
+
throw new Error("contextual reaction command identity mismatch");
|
|
243
|
+
}
|
|
244
|
+
const input = encodeValue(command.input, command.inputSchema);
|
|
245
|
+
const args = this.reactiveArguments(request);
|
|
246
|
+
args.push("contextual", "react", "--module-hash", request.reactiveModuleHash, "--operation", request.operationName, "--consumer-name", request.consumerName, "--reaction", expectSymbol(reaction.name), "--causation-token", expectBoundedLowerHex(reaction.causationToken, 2_048), "--command-name", command.commandName, "--expected-version", String(command.contractVersion));
|
|
247
|
+
addReactiveParameters(args, request.parameters, request.parameterSchema);
|
|
248
|
+
const envelope = await this.invoke(args, input, command.decodeError, "input");
|
|
249
|
+
return decodeTypedCommandResult(exactObject(envelope.result), command);
|
|
250
|
+
}
|
|
251
|
+
async *watchNamedQuery(request) {
|
|
252
|
+
if (!HASH.test(request.reactiveModuleHash) || !SYMBOL.test(request.operationName)) {
|
|
253
|
+
throw new Error("invalid reactive operation identity");
|
|
254
|
+
}
|
|
255
|
+
let cursor = request.cursor;
|
|
256
|
+
while (true) {
|
|
257
|
+
const args = this.baseArguments();
|
|
258
|
+
args.push("query", "watch", request.operationName, "--module-hash", request.reactiveModuleHash);
|
|
259
|
+
addReactiveParameters(args, request.parameters, request.parameterSchema);
|
|
260
|
+
if (cursor !== undefined)
|
|
261
|
+
args.push("--cursor", cursor);
|
|
262
|
+
const result = exactObject((await this.invokeArguments(args)).result);
|
|
263
|
+
const type = expectSymbol(result.type);
|
|
264
|
+
if (type === "terminal") {
|
|
265
|
+
yield {
|
|
266
|
+
type,
|
|
267
|
+
reason: expectSymbol(result.reason),
|
|
268
|
+
...liveTerminalFrontier(result.last_frontier),
|
|
269
|
+
};
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
cursor = expectBoundedString(result.cursor, 16_384);
|
|
273
|
+
const frontier = exactObject(result.frontier);
|
|
274
|
+
const applicationHead = nonnegativeBigInt(frontier.application_head);
|
|
275
|
+
const historyIncarnation = positiveBigInt(frontier.history_incarnation);
|
|
276
|
+
if (type === "snapshot") {
|
|
277
|
+
yield { type, value: decodeLiveValue(result.result), cursor, applicationHead, historyIncarnation };
|
|
278
|
+
}
|
|
279
|
+
else if (type === "reset") {
|
|
280
|
+
yield { type, reason: expectSymbol(result.reason), value: decodeLiveValue(result.result), cursor, applicationHead, historyIncarnation };
|
|
281
|
+
}
|
|
282
|
+
else if (type === "patch") {
|
|
283
|
+
yield { type, resultField: expectSymbol(result.result_field), operations: expectArray(result.operations).map(decodeLivePatchOperation), cursor, applicationHead, historyIncarnation };
|
|
284
|
+
}
|
|
285
|
+
else if (type === "checkpoint") {
|
|
286
|
+
yield { type, cursor, applicationHead, historyIncarnation };
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
throw new Error("invalid RiffDB live query update");
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
async mutateEventLease(action, request, delivery, retryDelayMs = 0) {
|
|
294
|
+
validateReactiveRequest(request);
|
|
295
|
+
const delay = boundedInteger(retryDelayMs, 0, 3_600_000);
|
|
296
|
+
const args = this.reactiveArguments(request);
|
|
297
|
+
args.push("event", action, "--module-hash", request.reactiveModuleHash, "--operation", request.operationName, "--consumer-name", request.consumerName, "--event-id", expectBoundedString(delivery.eventId, 64), "--lease-token", expectHash(delivery.leaseToken), "--history-incarnation", delivery.historyIncarnation.toString());
|
|
298
|
+
if (action === "nack")
|
|
299
|
+
args.push("--retry-delay-nanos", String(delay * 1_000_000));
|
|
300
|
+
addReactiveParameters(args, request.parameters, request.parameterSchema);
|
|
301
|
+
return decodeEventMutationResult((await this.invokeArguments(args)).result);
|
|
302
|
+
}
|
|
303
|
+
async mutateContextualItem(action, request, item, retryDelayMs = 0) {
|
|
304
|
+
validateReactiveRequest(request);
|
|
305
|
+
const delay = boundedInteger(retryDelayMs, 0, 3_600_000);
|
|
306
|
+
const args = this.reactiveArguments(request);
|
|
307
|
+
args.push("contextual", action, "--module-hash", request.reactiveModuleHash, "--operation", request.operationName, "--consumer-name", request.consumerName, "--event-id", expectBoundedString(item.delivery.eventId, 64), "--lease-token", expectHash(item.delivery.leaseToken), "--history-incarnation", item.delivery.historyIncarnation.toString());
|
|
308
|
+
if (action === "nack")
|
|
309
|
+
args.push("--retry-delay-nanos", String(delay * 1_000_000));
|
|
310
|
+
addReactiveParameters(args, request.parameters, request.parameterSchema);
|
|
311
|
+
return decodeEventMutationResult((await this.invokeArguments(args)).result);
|
|
312
|
+
}
|
|
313
|
+
reactiveArguments(request) {
|
|
314
|
+
validateReactiveRequest(request);
|
|
315
|
+
return this.baseArguments();
|
|
316
|
+
}
|
|
317
|
+
baseArguments(attemptBudget) {
|
|
318
|
+
const args = [
|
|
319
|
+
"--endpoint", this.options.endpoint,
|
|
320
|
+
"--output", "json",
|
|
321
|
+
"--credential-file", this.options.credentialFile,
|
|
322
|
+
];
|
|
323
|
+
if (attemptBudget !== undefined)
|
|
324
|
+
args.push("--max-attempts", String(attemptBudget));
|
|
325
|
+
return args;
|
|
326
|
+
}
|
|
327
|
+
async invoke(args, input, decodeError, inputKind) {
|
|
328
|
+
const directory = await mkdtemp(join(tmpdir(), "riffdb-typescript-"));
|
|
329
|
+
const inputPath = join(directory, "input.json");
|
|
330
|
+
try {
|
|
331
|
+
await writeFile(inputPath, `${JSON.stringify(input)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
332
|
+
const commandArgs = [...args];
|
|
333
|
+
const selectedInput = inputKind ?? (commandArgs.includes("command") ? "input" : "parameters");
|
|
334
|
+
commandArgs.push(selectedInput === "input" ? "--input" : "--parameters", inputPath);
|
|
335
|
+
let stdout;
|
|
336
|
+
try {
|
|
337
|
+
({ stdout } = await executeFile(this.options.riffdbPath, commandArgs, {
|
|
338
|
+
encoding: "utf8",
|
|
339
|
+
maxBuffer: MAX_OUTPUT_BYTES,
|
|
340
|
+
timeout: 35_000,
|
|
341
|
+
}));
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
const candidate = exactObject(error);
|
|
345
|
+
stdout = typeof candidate.stdout === "string" ? candidate.stdout : "";
|
|
346
|
+
}
|
|
347
|
+
const envelope = exactObject(JSON.parse(stdout));
|
|
348
|
+
if (envelope.schema !== "riffdb.cli.output/v1")
|
|
349
|
+
throw new Error("invalid RiffDB CLI envelope");
|
|
350
|
+
if (envelope.ok !== true) {
|
|
351
|
+
const raw = exactObject(envelope.error);
|
|
352
|
+
if (raw.type === "application")
|
|
353
|
+
throw decodeError(normalizeError(raw));
|
|
354
|
+
throw new Error("RiffDB application transport failed");
|
|
355
|
+
}
|
|
356
|
+
return envelope;
|
|
357
|
+
}
|
|
358
|
+
finally {
|
|
359
|
+
await rm(directory, { recursive: true, force: true });
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
async invokeArguments(args) {
|
|
363
|
+
let stdout;
|
|
364
|
+
try {
|
|
365
|
+
({ stdout } = await executeFile(this.options.riffdbPath, args, {
|
|
366
|
+
encoding: "utf8", maxBuffer: MAX_OUTPUT_BYTES, timeout: 35_000,
|
|
367
|
+
}));
|
|
368
|
+
}
|
|
369
|
+
catch (error) {
|
|
370
|
+
const candidate = exactObject(error);
|
|
371
|
+
stdout = typeof candidate.stdout === "string" ? candidate.stdout : "";
|
|
372
|
+
}
|
|
373
|
+
const envelope = exactObject(JSON.parse(stdout));
|
|
374
|
+
if (envelope.schema !== "riffdb.cli.output/v1")
|
|
375
|
+
throw new Error("invalid RiffDB CLI envelope");
|
|
376
|
+
if (envelope.ok !== true)
|
|
377
|
+
throw new Error("RiffDB reactive operation failed");
|
|
378
|
+
return envelope;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Generated-facade adapter for one retained Rust driver-host session.
|
|
383
|
+
*
|
|
384
|
+
* The adapter owns only checked value assembly and result decoding. Remote
|
|
385
|
+
* trust, credentials, retries, pooling, and uncertainty remain in Rust.
|
|
386
|
+
*/
|
|
387
|
+
export class DriverGeneratedApplicationTransport {
|
|
388
|
+
driver;
|
|
389
|
+
constructor(driver) {
|
|
390
|
+
this.driver = driver;
|
|
391
|
+
}
|
|
392
|
+
/** Returns the bound tenant identifier when this session is tenant-scoped. */
|
|
393
|
+
get tenant() {
|
|
394
|
+
return this.driver.tenant;
|
|
395
|
+
}
|
|
396
|
+
async executeNamedQuery(request, options = {}) {
|
|
397
|
+
validateIdentity(request);
|
|
398
|
+
const operation = requireDriverOperation(request.driverOperation);
|
|
399
|
+
const result = await this.driver.invoke(operation, encodeDriverRecord(request.parameters, request.parameterSchema), {
|
|
400
|
+
...(options.cursor === undefined ? {} : { cursor: options.cursor }),
|
|
401
|
+
...(options.readAfterCommit === undefined ? {} : { readAfterCommit: options.readAfterCommit }),
|
|
402
|
+
...(options.consistency === undefined ? {} : { queryConsistency: options.consistency }),
|
|
403
|
+
...(request.compactDecoder === undefined ? {} : { acceptCompactResult: true }),
|
|
404
|
+
...(request.packedDecoder === undefined ? {} : { acceptPackedResult: true }),
|
|
405
|
+
});
|
|
406
|
+
if (result.applicationHead === undefined)
|
|
407
|
+
throw new Error("RiffDB driver omitted the query frontier");
|
|
408
|
+
let value;
|
|
409
|
+
if (result.packed !== undefined) {
|
|
410
|
+
if (request.packedDecoder === undefined || result.value !== undefined || result.compact !== undefined)
|
|
411
|
+
throw new Error("RiffDB driver returned an unexpected packed result");
|
|
412
|
+
value = request.packedDecoder(result.packed);
|
|
413
|
+
}
|
|
414
|
+
else if (result.compact !== undefined) {
|
|
415
|
+
if (request.compactDecoder === undefined || result.value !== undefined)
|
|
416
|
+
throw new Error("RiffDB driver returned an unexpected compact result");
|
|
417
|
+
value = request.compactDecoder(result.compact);
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
const raw = requireDriverValue(result);
|
|
421
|
+
const outcome = driverOutcome(raw);
|
|
422
|
+
const resultSchema = request.resultSchemas[outcome];
|
|
423
|
+
if (resultSchema === undefined)
|
|
424
|
+
throw new Error("RiffDB driver returned an unknown query outcome");
|
|
425
|
+
value = decodeDriverValue(raw, {
|
|
426
|
+
kind: "record",
|
|
427
|
+
fields: [
|
|
428
|
+
{ name: "outcome", schema: { kind: "enum" } },
|
|
429
|
+
...recordFields(resultSchema),
|
|
430
|
+
],
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
identity: {
|
|
435
|
+
contractLineage: request.contractLineage,
|
|
436
|
+
contractVersion: request.contractVersion,
|
|
437
|
+
contractBundleHash: request.contractBundleHash,
|
|
438
|
+
moduleHash: request.moduleHash,
|
|
439
|
+
queryName: request.queryName,
|
|
440
|
+
planHash: request.planHash,
|
|
441
|
+
},
|
|
442
|
+
value,
|
|
443
|
+
applicationHead: result.applicationHead,
|
|
444
|
+
...(result.cursor === undefined ? {} : { nextCursor: result.cursor }),
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
async executeCommand(request, attemptBudget) {
|
|
448
|
+
validateIdentity(request);
|
|
449
|
+
const operation = requireDriverOperation(request.driverOperation);
|
|
450
|
+
const result = await this.driver.invoke(operation, encodeDriverRecord(request.input, request.inputSchema), { maximumAttempts: boundedInteger(attemptBudget, 1, 10) });
|
|
451
|
+
return decodeDriverCommandResult(request, requireDriverValue(result), result.applicationHead, result.cursor, result.replayed);
|
|
452
|
+
}
|
|
453
|
+
async executeVectorInspection(request, options = {}) {
|
|
454
|
+
validateIdentity(request);
|
|
455
|
+
const operation = requireDriverOperation(request.driverOperation);
|
|
456
|
+
const result = await this.driver.invoke(operation, {
|
|
457
|
+
partition: encodeDriverValue(request.partition, request.partitionSchema),
|
|
458
|
+
limit: { type: "u64", value: BigInt(boundedInteger(request.limit, 1, 500)).toString() },
|
|
459
|
+
}, { ...(options.cursor === undefined ? {} : { cursor: options.cursor }) });
|
|
460
|
+
const value = decodeDriverVectorInspection(requireDriverValue(result));
|
|
461
|
+
return {
|
|
462
|
+
value,
|
|
463
|
+
...(result.applicationHead === undefined ? {} : { applicationHead: result.applicationHead }),
|
|
464
|
+
...(result.cursor === undefined ? {} : { nextCursor: result.cursor }),
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
async executeCommandBatch(request, inputs, concurrency, checkpoint, attemptBudget) {
|
|
468
|
+
validateIdentity(request);
|
|
469
|
+
const operation = requireDriverOperation(request.driverOperation);
|
|
470
|
+
const result = await this.driver.batch(operation, inputs.map((input) => encodeDriverRecord(input, request.inputSchema)), concurrency, checkpoint, { maximumAttempts: boundedInteger(attemptBudget, 1, 10) });
|
|
471
|
+
return {
|
|
472
|
+
checkpoint: result.checkpoint,
|
|
473
|
+
items: result.items.map((item) => item.result === undefined
|
|
474
|
+
? { index: item.index, ...(item.error === undefined ? {} : { error: item.error }) }
|
|
475
|
+
: {
|
|
476
|
+
index: item.index,
|
|
477
|
+
result: decodeDriverCommandResult(request, item.result.value, item.result.commitSequence, item.result.outcomeUri, item.result.replayed),
|
|
478
|
+
}),
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
async *consumeEventStream(request, options = {}) {
|
|
482
|
+
validateDriverReactiveRequest(request);
|
|
483
|
+
const batchLimit = boundedInteger(options.batchLimit ?? 1, 1, 64);
|
|
484
|
+
const inFlightLimit = boundedInteger(options.inFlightLimit ?? 16, 1, 64);
|
|
485
|
+
const leaseSeconds = boundedInteger(options.leaseSeconds ?? 60, 5, 900);
|
|
486
|
+
const maximumWaitMs = boundedInteger(options.maximumWaitMs ?? 30_000, 0, 30_000);
|
|
487
|
+
while (options.signal?.aborted !== true) {
|
|
488
|
+
const input = reactiveDriverInput(request);
|
|
489
|
+
input.batch_limit = driverU64(BigInt(batchLimit));
|
|
490
|
+
input.in_flight_limit = driverU64(BigInt(inFlightLimit));
|
|
491
|
+
input.lease_seconds = driverU64(BigInt(leaseSeconds));
|
|
492
|
+
const result = await this.driver.invoke(requireReactiveDriverOperation(request, "next"), input, { deadlineMillis: Math.max(1, maximumWaitMs), ...(options.signal === undefined ? {} : { signal: options.signal }) });
|
|
493
|
+
yield decodeDriverEventBatch(requireDriverValue(result));
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
async acknowledgeEvent(request, delivery) {
|
|
497
|
+
return this.mutateDriverLease("ack", request, delivery);
|
|
498
|
+
}
|
|
499
|
+
async negativeAcknowledgeEvent(request, delivery, retryDelayMs = 0) {
|
|
500
|
+
const delay = boundedInteger(retryDelayMs, 0, 300_000);
|
|
501
|
+
return this.mutateDriverLease("nack", request, delivery, BigInt(delay) * 1000000n);
|
|
502
|
+
}
|
|
503
|
+
async seekEventConsumer(request, checkpoint) {
|
|
504
|
+
const input = reactiveDriverInput(request);
|
|
505
|
+
input.checkpoint = { type: "string", value: expectBoundedString(checkpoint, 256) };
|
|
506
|
+
const result = await this.driver.invoke(requireReactiveDriverOperation(request, "seek"), input);
|
|
507
|
+
return decodeDriverMutationResult(requireDriverValue(result));
|
|
508
|
+
}
|
|
509
|
+
async eventConsumerStatus(request) {
|
|
510
|
+
const result = await this.driver.invoke(requireReactiveDriverOperation(request, "status"), reactiveDriverInput(request));
|
|
511
|
+
const value = requireDriverValue(result);
|
|
512
|
+
return value.type === "null" ? undefined : decodeDriverConsumerStatus(value);
|
|
513
|
+
}
|
|
514
|
+
async consumeContextualSubscription(request, maximumWaitMs = 30_000, signal) {
|
|
515
|
+
const wait = boundedInteger(maximumWaitMs, 0, 30_000);
|
|
516
|
+
const result = await this.driver.invoke(requireReactiveDriverOperation(request, "next"), reactiveDriverInput(request), { deadlineMillis: Math.max(1, wait), ...(signal === undefined ? {} : { signal }) });
|
|
517
|
+
return decodeDriverContextualBatch(requireDriverValue(result));
|
|
518
|
+
}
|
|
519
|
+
async acknowledgeContextualItem(request, item) {
|
|
520
|
+
return this.mutateDriverLease("ack", request, item.delivery);
|
|
521
|
+
}
|
|
522
|
+
async negativeAcknowledgeContextualItem(request, item, retryDelayMs = 0) {
|
|
523
|
+
const delay = boundedInteger(retryDelayMs, 0, 300_000);
|
|
524
|
+
return this.mutateDriverLease("nack", request, item.delivery, BigInt(delay) * 1000000n);
|
|
525
|
+
}
|
|
526
|
+
async contextualSubscriptionStatus(request) {
|
|
527
|
+
return this.eventConsumerStatus(request);
|
|
528
|
+
}
|
|
529
|
+
async executeContextualReaction(request, reaction, command) {
|
|
530
|
+
validateIdentity(command);
|
|
531
|
+
if (reaction.commandName !== command.commandName) {
|
|
532
|
+
throw new Error("contextual reaction command identity mismatch");
|
|
533
|
+
}
|
|
534
|
+
const action = `react_${snakeDriverAction(reaction.name)}`;
|
|
535
|
+
const input = reactiveDriverInput(request);
|
|
536
|
+
input.causation_token = { type: "string", value: expectBoundedString(reaction.causationToken, 16_384) };
|
|
537
|
+
input.input = encodeDriverValue(command.input, command.inputSchema);
|
|
538
|
+
const result = await this.driver.invoke(requireReactiveDriverOperation(request, action), input);
|
|
539
|
+
return decodeDriverCommandResult(command, requireDriverValue(result), result.applicationHead, result.cursor, result.replayed);
|
|
540
|
+
}
|
|
541
|
+
async *watchNamedQuery(request) {
|
|
542
|
+
validateDriverReactiveRequest(request);
|
|
543
|
+
let cursor = request.cursor;
|
|
544
|
+
while (request.signal?.aborted !== true) {
|
|
545
|
+
const input = reactiveDriverInput(request);
|
|
546
|
+
if (cursor !== undefined)
|
|
547
|
+
input.cursor = { type: "string", value: expectBoundedString(cursor, 16_384) };
|
|
548
|
+
const result = await this.driver.invoke(requireReactiveDriverOperation(request, "watch"), input, { ...(cursor === undefined ? {} : { cursor }), ...(request.signal === undefined ? {} : { signal: request.signal }) });
|
|
549
|
+
const update = decodeDriverLiveUpdate(requireDriverValue(result), result.applicationHead, result.cursor);
|
|
550
|
+
yield update;
|
|
551
|
+
if (update.type === "terminal")
|
|
552
|
+
return;
|
|
553
|
+
cursor = update.cursor;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
async mutateDriverLease(action, request, delivery, retryDelayNanos) {
|
|
557
|
+
const input = reactiveDriverInput(request);
|
|
558
|
+
input.event_id = { type: "string", value: expectBoundedString(delivery.eventId, 256) };
|
|
559
|
+
input.lease_token = { type: "string", value: expectBoundedString(delivery.leaseToken, 16_384) };
|
|
560
|
+
input.history_incarnation = driverU64(delivery.historyIncarnation);
|
|
561
|
+
if (action === "nack")
|
|
562
|
+
input.retry_delay_nanos = driverU64(retryDelayNanos ?? 0n);
|
|
563
|
+
const result = await this.driver.invoke(requireReactiveDriverOperation(request, action), input);
|
|
564
|
+
return decodeDriverMutationResult(requireDriverValue(result));
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
function validateDriverReactiveRequest(value) {
|
|
568
|
+
if (!HASH.test(value.reactiveModuleHash) || !SYMBOL.test(value.operationName)) {
|
|
569
|
+
throw new Error("invalid generated reactive operation identity");
|
|
570
|
+
}
|
|
571
|
+
if (value.parameterSchema.kind !== "record") {
|
|
572
|
+
throw new Error("invalid generated reactive parameter schema");
|
|
573
|
+
}
|
|
574
|
+
if (value.consumerName !== undefined)
|
|
575
|
+
expectSymbol(value.consumerName);
|
|
576
|
+
if (value.driverOperations === undefined || Object.keys(value.driverOperations).length < 1
|
|
577
|
+
|| Object.keys(value.driverOperations).length > 16) {
|
|
578
|
+
throw new Error("generated reactive binding has no driver operation identities");
|
|
579
|
+
}
|
|
580
|
+
for (const [action, operation] of Object.entries(value.driverOperations)) {
|
|
581
|
+
if (!/^[a-z][a-z0-9_]{0,127}$/.test(action))
|
|
582
|
+
throw new Error("invalid generated reactive action");
|
|
583
|
+
requireDriverOperation(operation);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
function requireReactiveDriverOperation(request, action) {
|
|
587
|
+
const operation = request.driverOperations?.[action];
|
|
588
|
+
if (operation === undefined)
|
|
589
|
+
throw new Error("generated reactive binding has no driver action identity");
|
|
590
|
+
return requireDriverOperation(operation);
|
|
591
|
+
}
|
|
592
|
+
function reactiveDriverInput(request) {
|
|
593
|
+
const input = {
|
|
594
|
+
parameters: encodeDriverValue(request.parameters, request.parameterSchema),
|
|
595
|
+
};
|
|
596
|
+
if (request.consumerName !== undefined) {
|
|
597
|
+
input.consumer_name = { type: "string", value: expectSymbol(request.consumerName) };
|
|
598
|
+
}
|
|
599
|
+
return input;
|
|
600
|
+
}
|
|
601
|
+
function driverU64(value) {
|
|
602
|
+
if (value < 0n || value > (1n << 64n) - 1n)
|
|
603
|
+
throw new Error("invalid generated RiffDB u64 value");
|
|
604
|
+
return { type: "u64", value: value.toString() };
|
|
605
|
+
}
|
|
606
|
+
function decodeDriverMutationResult(value) {
|
|
607
|
+
if (value.type !== "enum" || ![
|
|
608
|
+
"applied", "state_changed", "not_found", "outstanding_lease", "stale_lease", "lease_expired",
|
|
609
|
+
].includes(value.value)) {
|
|
610
|
+
throw new Error("invalid RiffDB driver mutation result");
|
|
611
|
+
}
|
|
612
|
+
return value.value;
|
|
613
|
+
}
|
|
614
|
+
function decodeDriverConsumerStatus(value) {
|
|
615
|
+
const status = driverRecord(value);
|
|
616
|
+
return {
|
|
617
|
+
revision: driverPositiveU64(requiredDriverField(status, "revision")),
|
|
618
|
+
checkpoint: driverString(requiredDriverField(status, "checkpoint"), 256),
|
|
619
|
+
historyIncarnation: driverPositiveU64(requiredDriverField(status, "history_incarnation")),
|
|
620
|
+
liveLeases: driverBoundedU32(requiredDriverField(status, "live_leases")),
|
|
621
|
+
retries: driverBoundedU32(requiredDriverField(status, "retries")),
|
|
622
|
+
deadLetters: driverBoundedU32(requiredDriverField(status, "dead_letters")),
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
function decodeDriverEventBatch(value) {
|
|
626
|
+
const batch = driverRecord(value);
|
|
627
|
+
const events = driverList(requiredDriverField(batch, "events")).map(decodeDriverDelivery);
|
|
628
|
+
const timedOut = requiredDriverField(batch, "wait_timed_out");
|
|
629
|
+
if (timedOut.type !== "bool")
|
|
630
|
+
throw new Error("invalid RiffDB driver event batch");
|
|
631
|
+
return {
|
|
632
|
+
events,
|
|
633
|
+
waitTimedOut: timedOut.value,
|
|
634
|
+
status: decodeDriverConsumerStatus(requiredDriverField(batch, "status")),
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
function decodeDriverDelivery(value) {
|
|
638
|
+
const delivery = driverRecord(value);
|
|
639
|
+
const eventName = driverEnum(requiredDriverField(delivery, "event_name"));
|
|
640
|
+
const fields = driverRecord(requiredDriverField(delivery, "fields"));
|
|
641
|
+
const expiration = requiredDriverField(delivery, "expires_at");
|
|
642
|
+
if (expiration.type !== "timestamp")
|
|
643
|
+
throw new Error("invalid RiffDB driver event lease");
|
|
644
|
+
return {
|
|
645
|
+
eventId: driverString(requiredDriverField(delivery, "event_id"), 256),
|
|
646
|
+
event: { type: eventName, ...decodeDriverDynamicRecord(fields) },
|
|
647
|
+
attempt: driverBoundedU32(requiredDriverField(delivery, "attempt")),
|
|
648
|
+
leaseToken: driverString(requiredDriverField(delivery, "lease_token"), 16_384),
|
|
649
|
+
expiresAt: `${expiration.value.seconds}.${String(expiration.value.nanos).padStart(9, "0")}`,
|
|
650
|
+
historyIncarnation: driverPositiveU64(requiredDriverField(delivery, "history_incarnation")),
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
function decodeDriverContextualBatch(value) {
|
|
654
|
+
const batch = driverRecord(value);
|
|
655
|
+
const timedOut = requiredDriverField(batch, "wait_timed_out");
|
|
656
|
+
if (timedOut.type !== "bool")
|
|
657
|
+
throw new Error("invalid RiffDB driver contextual batch");
|
|
658
|
+
return {
|
|
659
|
+
items: driverList(requiredDriverField(batch, "items")).map((raw) => {
|
|
660
|
+
const item = driverRecord(raw);
|
|
661
|
+
return {
|
|
662
|
+
delivery: decodeDriverDelivery(requiredDriverField(item, "delivery")),
|
|
663
|
+
contextHead: driverPositiveU64(requiredDriverField(item, "context_head")),
|
|
664
|
+
hydrations: driverList(requiredDriverField(item, "hydrations")).map((rawHydration) => {
|
|
665
|
+
const hydration = driverRecord(rawHydration);
|
|
666
|
+
const fields = {};
|
|
667
|
+
for (const [name, field] of Object.entries(hydration)) {
|
|
668
|
+
if (name !== "name" && name !== "outcome")
|
|
669
|
+
fields[name] = decodeDriverDynamic(field);
|
|
670
|
+
}
|
|
671
|
+
return {
|
|
672
|
+
name: driverString(requiredDriverField(hydration, "name"), 256),
|
|
673
|
+
outcome: driverEnum(requiredDriverField(hydration, "outcome")),
|
|
674
|
+
fields,
|
|
675
|
+
};
|
|
676
|
+
}),
|
|
677
|
+
availableReactions: driverList(requiredDriverField(item, "available_reactions")).map((rawReaction) => {
|
|
678
|
+
const reaction = driverRecord(rawReaction);
|
|
679
|
+
return {
|
|
680
|
+
name: driverString(requiredDriverField(reaction, "name"), 256),
|
|
681
|
+
commandName: driverString(requiredDriverField(reaction, "command_name"), 256),
|
|
682
|
+
commandId: driverBoundedU32(requiredDriverField(reaction, "command_id")),
|
|
683
|
+
causationToken: driverString(requiredDriverField(reaction, "causation_token"), 16_384),
|
|
684
|
+
};
|
|
685
|
+
}),
|
|
686
|
+
};
|
|
687
|
+
}),
|
|
688
|
+
waitTimedOut: timedOut.value,
|
|
689
|
+
status: decodeDriverConsumerStatus(requiredDriverField(batch, "status")),
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
function decodeDriverLiveUpdate(value, applicationHead, responseCursor) {
|
|
693
|
+
const update = driverRecord(value);
|
|
694
|
+
const kind = driverEnum(requiredDriverField(update, "kind"));
|
|
695
|
+
if (kind === "terminal") {
|
|
696
|
+
const reason = driverEnum(requiredDriverField(update, "reason"));
|
|
697
|
+
const frontier = requiredDriverField(update, "last_frontier");
|
|
698
|
+
if (frontier.type === "null")
|
|
699
|
+
return { type: "terminal", reason };
|
|
700
|
+
const record = driverRecord(frontier);
|
|
701
|
+
return {
|
|
702
|
+
type: "terminal",
|
|
703
|
+
reason,
|
|
704
|
+
lastApplicationHead: driverPositiveU64(requiredDriverField(record, "application_head")),
|
|
705
|
+
historyIncarnation: driverPositiveU64(requiredDriverField(record, "history_incarnation")),
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
const checkpoint = driverRecord(requiredDriverField(update, "checkpoint"));
|
|
709
|
+
const cursor = driverString(requiredDriverField(checkpoint, "cursor"), 16_384);
|
|
710
|
+
const head = driverPositiveU64(requiredDriverField(checkpoint, "application_head"));
|
|
711
|
+
const history = driverPositiveU64(requiredDriverField(checkpoint, "history_incarnation"));
|
|
712
|
+
if (applicationHead !== head || responseCursor !== cursor) {
|
|
713
|
+
throw new Error("RiffDB driver live frontier is inconsistent");
|
|
714
|
+
}
|
|
715
|
+
if (kind === "snapshot") {
|
|
716
|
+
return { type: "snapshot", value: decodeDriverDynamic(requiredDriverField(update, "result")), cursor, applicationHead: head, historyIncarnation: history };
|
|
717
|
+
}
|
|
718
|
+
if (kind === "reset") {
|
|
719
|
+
return { type: "reset", reason: driverEnum(requiredDriverField(update, "reason")), value: decodeDriverDynamic(requiredDriverField(update, "result")), cursor, applicationHead: head, historyIncarnation: history };
|
|
720
|
+
}
|
|
721
|
+
if (kind === "checkpoint") {
|
|
722
|
+
return { type: "checkpoint", cursor, applicationHead: head, historyIncarnation: history };
|
|
723
|
+
}
|
|
724
|
+
if (kind !== "patch")
|
|
725
|
+
throw new Error("invalid RiffDB driver live update");
|
|
726
|
+
return {
|
|
727
|
+
type: "patch",
|
|
728
|
+
resultField: driverString(requiredDriverField(update, "result_field"), 256),
|
|
729
|
+
operations: driverList(requiredDriverField(update, "operations")).map(decodeDriverPatchOperation),
|
|
730
|
+
cursor,
|
|
731
|
+
applicationHead: head,
|
|
732
|
+
historyIncarnation: history,
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
function decodeDriverPatchOperation(value) {
|
|
736
|
+
const operation = driverRecord(value);
|
|
737
|
+
const kind = driverEnum(requiredDriverField(operation, "operation"));
|
|
738
|
+
if (kind === "insert" || kind === "replace") {
|
|
739
|
+
const record = decodeDriverDynamic(requiredDriverField(operation, "record"));
|
|
740
|
+
if (!isPlainRecord(record))
|
|
741
|
+
throw new Error("invalid RiffDB driver live record");
|
|
742
|
+
return { type: kind, index: driverBoundedU32(requiredDriverField(operation, "index")), record };
|
|
743
|
+
}
|
|
744
|
+
const key = decodeDriverDynamic(requiredDriverField(operation, "key"));
|
|
745
|
+
if (!isPlainRecord(key))
|
|
746
|
+
throw new Error("invalid RiffDB driver live key");
|
|
747
|
+
if (kind === "remove") {
|
|
748
|
+
return { type: "remove", index: driverBoundedU32(requiredDriverField(operation, "index")), key };
|
|
749
|
+
}
|
|
750
|
+
if (kind === "move") {
|
|
751
|
+
return { type: "move", from: driverBoundedU32(requiredDriverField(operation, "from")), to: driverBoundedU32(requiredDriverField(operation, "to")), key };
|
|
752
|
+
}
|
|
753
|
+
throw new Error("invalid RiffDB driver live patch operation");
|
|
754
|
+
}
|
|
755
|
+
function decodeDriverDynamic(value) {
|
|
756
|
+
switch (value.type) {
|
|
757
|
+
case "null": return null;
|
|
758
|
+
case "bool": return value.value;
|
|
759
|
+
case "i64":
|
|
760
|
+
case "u64": return BigInt(value.value);
|
|
761
|
+
case "string":
|
|
762
|
+
case "uuid":
|
|
763
|
+
case "enum": return value.value;
|
|
764
|
+
case "bytes": return Uint8Array.from(Buffer.from(value.value, "base64"));
|
|
765
|
+
case "date": return Number(value.value);
|
|
766
|
+
case "timestamp": return { seconds: BigInt(value.value.seconds), nanos: value.value.nanos };
|
|
767
|
+
case "decimal": {
|
|
768
|
+
if (value.value.precision === undefined)
|
|
769
|
+
throw new Error("invalid RiffDB driver decimal");
|
|
770
|
+
return { coefficientTwosComplement: Uint8Array.from(Buffer.from(value.value.coefficient, "base64")), scale: value.value.scale, precision: value.value.precision };
|
|
771
|
+
}
|
|
772
|
+
case "money": {
|
|
773
|
+
if (value.value.amount.precision === undefined)
|
|
774
|
+
throw new Error("invalid RiffDB driver money");
|
|
775
|
+
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
|
+
}
|
|
777
|
+
case "list": return value.value.map(decodeDriverDynamic);
|
|
778
|
+
case "record": return decodeDriverDynamicRecord(value.value);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
function decodeDriverDynamicRecord(value) {
|
|
782
|
+
return Object.fromEntries(Object.entries(value).map(([name, field]) => [name, decodeDriverDynamic(field)]));
|
|
783
|
+
}
|
|
784
|
+
function driverRecord(value) {
|
|
785
|
+
if (value.type !== "record")
|
|
786
|
+
throw new Error("invalid RiffDB driver record");
|
|
787
|
+
return value.value;
|
|
788
|
+
}
|
|
789
|
+
function driverList(value) {
|
|
790
|
+
if (value.type !== "list")
|
|
791
|
+
throw new Error("invalid RiffDB driver list");
|
|
792
|
+
return value.value;
|
|
793
|
+
}
|
|
794
|
+
function requiredDriverField(value, name) {
|
|
795
|
+
const field = value[name];
|
|
796
|
+
if (field === undefined)
|
|
797
|
+
throw new Error("RiffDB driver result is missing a field");
|
|
798
|
+
return field;
|
|
799
|
+
}
|
|
800
|
+
function driverPositiveU64(value) {
|
|
801
|
+
if (value.type !== "u64")
|
|
802
|
+
throw new Error("invalid RiffDB driver u64");
|
|
803
|
+
const parsed = BigInt(value.value);
|
|
804
|
+
if (parsed < 1n || parsed > (1n << 64n) - 1n)
|
|
805
|
+
throw new Error("invalid RiffDB driver u64");
|
|
806
|
+
return parsed;
|
|
807
|
+
}
|
|
808
|
+
function driverBoundedU32(value) {
|
|
809
|
+
if (value.type !== "u64")
|
|
810
|
+
throw new Error("invalid RiffDB driver u32");
|
|
811
|
+
const parsed = BigInt(value.value);
|
|
812
|
+
if (parsed < 0n)
|
|
813
|
+
throw new Error("invalid RiffDB driver u32");
|
|
814
|
+
if (parsed > 4294967295n)
|
|
815
|
+
throw new Error("invalid RiffDB driver u32");
|
|
816
|
+
return Number(parsed);
|
|
817
|
+
}
|
|
818
|
+
function driverString(value, maximum) {
|
|
819
|
+
if (value.type !== "string")
|
|
820
|
+
throw new Error("invalid RiffDB driver string");
|
|
821
|
+
return expectBoundedString(value.value, maximum);
|
|
822
|
+
}
|
|
823
|
+
function driverEnum(value) {
|
|
824
|
+
if (value.type !== "enum")
|
|
825
|
+
throw new Error("invalid RiffDB driver enum");
|
|
826
|
+
return expectSymbol(value.value);
|
|
827
|
+
}
|
|
828
|
+
function snakeDriverAction(value) {
|
|
829
|
+
const result = value.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^A-Za-z0-9]+/g, "_").toLowerCase();
|
|
830
|
+
if (!/^[a-z][a-z0-9_]{0,127}$/.test(result))
|
|
831
|
+
throw new Error("invalid contextual reaction identity");
|
|
832
|
+
return result;
|
|
833
|
+
}
|
|
834
|
+
function isPlainRecord(value) {
|
|
835
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Uint8Array);
|
|
836
|
+
}
|
|
837
|
+
function requireDriverOperation(operation) {
|
|
838
|
+
if (operation === undefined)
|
|
839
|
+
throw new Error("generated RiffDB binding has no driver operation identity");
|
|
840
|
+
return operation;
|
|
841
|
+
}
|
|
842
|
+
function encodeDriverRecord(value, schema) {
|
|
843
|
+
if (schema.kind !== "record")
|
|
844
|
+
throw new Error("invalid generated RiffDB record schema");
|
|
845
|
+
const encoded = encodeDriverValue(value, schema);
|
|
846
|
+
if (encoded.type !== "record")
|
|
847
|
+
throw new Error("invalid generated RiffDB record");
|
|
848
|
+
return encoded.value;
|
|
849
|
+
}
|
|
850
|
+
function encodeDriverValue(value, schema, budgetPath) {
|
|
851
|
+
if (schema.kind === "optional") {
|
|
852
|
+
return value === null || value === undefined ? { type: "null" } : encodeDriverValue(value, schema.value, budgetPath);
|
|
853
|
+
}
|
|
854
|
+
if (schema.kind === "list") {
|
|
855
|
+
if (!Array.isArray(value)
|
|
856
|
+
|| value.length < (schema.minimum ?? 0)
|
|
857
|
+
|| value.length > (schema.maximum ?? 4_096)) {
|
|
858
|
+
if (budgetPath !== undefined)
|
|
859
|
+
throw new InputBudgetError("collection_count", budgetPath);
|
|
860
|
+
throw new Error("invalid generated RiffDB list input");
|
|
861
|
+
}
|
|
862
|
+
const encoded = value.map((item, index) => encodeDriverValue(item, schema.value, budgetPath === undefined ? undefined : { ...budgetPath, index }));
|
|
863
|
+
if (schema.aggregateCanonicalElementBytes !== undefined) {
|
|
864
|
+
let aggregate = 0;
|
|
865
|
+
for (const item of value) {
|
|
866
|
+
aggregate += canonicalValueEncodedLength(item, schema.value);
|
|
867
|
+
if (!Number.isSafeInteger(aggregate) || aggregate > schema.aggregateCanonicalElementBytes) {
|
|
868
|
+
if (budgetPath !== undefined)
|
|
869
|
+
throw new InputBudgetError("aggregate_canonical_element_bytes", budgetPath);
|
|
870
|
+
throw new Error("invalid generated RiffDB input");
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
return { type: "list", value: encoded };
|
|
875
|
+
}
|
|
876
|
+
if (schema.kind === "record") {
|
|
877
|
+
const input = exactObject(value);
|
|
878
|
+
const fields = new Map(schema.fields.map((field) => [field.name, field.schema]));
|
|
879
|
+
if (Object.keys(input).some((name) => !fields.has(name)))
|
|
880
|
+
throw new Error("invalid generated RiffDB record input");
|
|
881
|
+
const output = {};
|
|
882
|
+
for (const [name, fieldSchema] of fields) {
|
|
883
|
+
const fieldValue = input[name];
|
|
884
|
+
if (fieldValue === undefined && fieldSchema.kind === "optional")
|
|
885
|
+
continue;
|
|
886
|
+
if (fieldValue === undefined)
|
|
887
|
+
throw new Error("generated RiffDB input is missing a field");
|
|
888
|
+
const childPath = budgetPath === undefined
|
|
889
|
+
? (fieldSchema.kind === "list" && fieldSchema.aggregateCanonicalElementBytes !== undefined
|
|
890
|
+
? { collection: name }
|
|
891
|
+
: undefined)
|
|
892
|
+
: { ...budgetPath, leaf: name };
|
|
893
|
+
output[name] = encodeDriverValue(fieldValue, fieldSchema, childPath);
|
|
894
|
+
}
|
|
895
|
+
return { type: "record", value: output };
|
|
896
|
+
}
|
|
897
|
+
switch (schema.kind) {
|
|
898
|
+
case "bool":
|
|
899
|
+
if (typeof value !== "boolean")
|
|
900
|
+
throw new Error("invalid generated RiffDB bool input");
|
|
901
|
+
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() };
|
|
910
|
+
case "string": {
|
|
911
|
+
const string = expectBoundedString(value, 262_144);
|
|
912
|
+
if (schema.maximumBytes !== undefined && new TextEncoder().encode(string).length > schema.maximumBytes && budgetPath !== undefined) {
|
|
913
|
+
throw new InputBudgetError("individual_value_bytes", budgetPath);
|
|
914
|
+
}
|
|
915
|
+
return { type: "string", value: string };
|
|
916
|
+
}
|
|
917
|
+
case "cursor": return { type: "string", value: expectBoundedString(value, 262_144) };
|
|
918
|
+
case "uuid":
|
|
919
|
+
if (typeof value !== "string" || !UUID.test(value))
|
|
920
|
+
throw new Error("invalid generated RiffDB UUID input");
|
|
921
|
+
return { type: "uuid", value };
|
|
922
|
+
case "enum": return { type: "enum", value: expectSymbol(value) };
|
|
923
|
+
case "bytes":
|
|
924
|
+
if (!(value instanceof Uint8Array) || value.byteLength > 1_048_576)
|
|
925
|
+
throw new Error("invalid generated RiffDB bytes input");
|
|
926
|
+
if (schema.maximumBytes !== undefined && value.byteLength > schema.maximumBytes && budgetPath !== undefined) {
|
|
927
|
+
throw new InputBudgetError("individual_value_bytes", budgetPath);
|
|
928
|
+
}
|
|
929
|
+
return { type: "bytes", value: Buffer.from(value).toString("base64") };
|
|
930
|
+
case "date":
|
|
931
|
+
if (!Number.isInteger(value) || value < -2_147_483_648 || value > 2_147_483_647)
|
|
932
|
+
throw new Error("invalid generated RiffDB date input");
|
|
933
|
+
return { type: "date", value: String(value) };
|
|
934
|
+
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) } };
|
|
939
|
+
}
|
|
940
|
+
case "decimal": return { type: "decimal", value: encodeDriverDecimal(value, schema) };
|
|
941
|
+
case "money": {
|
|
942
|
+
const money = exactObject(value);
|
|
943
|
+
const currency = expectCurrency(money.currency);
|
|
944
|
+
if (currency !== schema.currency)
|
|
945
|
+
throw new Error("generated RiffDB money currency does not match");
|
|
946
|
+
return { type: "money", value: { currency, amount: encodeDriverDecimal(money.amount, schema) } };
|
|
947
|
+
}
|
|
948
|
+
case "vector": return {
|
|
949
|
+
type: "vector",
|
|
950
|
+
value: { component_bits: encodeVectorBits(value, schema.dimension) },
|
|
951
|
+
};
|
|
952
|
+
case "limit": return { type: "u64", value: String(boundedInteger(value, 1, 500)) };
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
function encodeDriverDecimal(value, schema) {
|
|
956
|
+
const decimal = encodeDecimal(value);
|
|
957
|
+
if (schema.precision === undefined || schema.scale === undefined || decimal.scale !== schema.scale
|
|
958
|
+
|| (decimal.precision !== undefined && decimal.precision !== schema.precision)) {
|
|
959
|
+
throw new Error("generated RiffDB decimal does not match its schema");
|
|
960
|
+
}
|
|
961
|
+
return { coefficient: decimal.coefficient_twos_complement, precision: schema.precision, scale: schema.scale };
|
|
962
|
+
}
|
|
963
|
+
function driverOutcome(value) {
|
|
964
|
+
if (value.type !== "record")
|
|
965
|
+
throw new Error("RiffDB driver returned a non-record application result");
|
|
966
|
+
const outcome = value.value.outcome;
|
|
967
|
+
if (outcome?.type !== "enum")
|
|
968
|
+
throw new Error("RiffDB driver returned no declared outcome");
|
|
969
|
+
return expectSymbol(outcome.value);
|
|
970
|
+
}
|
|
971
|
+
function requireDriverValue(result) {
|
|
972
|
+
if (result.value === undefined || result.compact !== undefined) {
|
|
973
|
+
throw new Error("RiffDB driver returned an unexpected compact result");
|
|
974
|
+
}
|
|
975
|
+
return result.value;
|
|
976
|
+
}
|
|
977
|
+
function decodeDriverVectorInspection(value) {
|
|
978
|
+
if (value.type !== "record")
|
|
979
|
+
throw new Error("RiffDB driver returned invalid vector inspection");
|
|
980
|
+
const kindValue = value.value.kind;
|
|
981
|
+
if (kindValue?.type !== "enum")
|
|
982
|
+
throw new Error("RiffDB driver returned invalid vector inspection");
|
|
983
|
+
const u64 = (name, optional = false) => {
|
|
984
|
+
const field = value.value[name];
|
|
985
|
+
if (optional && field?.type === "null")
|
|
986
|
+
return null;
|
|
987
|
+
if (field?.type !== "u64" || !/^(?:0|[1-9][0-9]*)$/.test(field.value))
|
|
988
|
+
throw new Error("RiffDB driver returned invalid vector inspection");
|
|
989
|
+
return BigInt(field.value);
|
|
990
|
+
};
|
|
991
|
+
const items = () => {
|
|
992
|
+
const field = value.value.items;
|
|
993
|
+
if (field?.type !== "list" || field.value.length > 500 || field.value.some((item) => item.type !== "record")) {
|
|
994
|
+
throw new Error("RiffDB driver returned invalid vector inspection");
|
|
995
|
+
}
|
|
996
|
+
return field.value.map((item) => item.value);
|
|
997
|
+
};
|
|
998
|
+
switch (kindValue.value) {
|
|
999
|
+
case "staleness_summary": {
|
|
1000
|
+
const breached = value.value.slo_breached;
|
|
1001
|
+
if (breached?.type !== "bool")
|
|
1002
|
+
throw new Error("RiffDB driver returned invalid vector inspection");
|
|
1003
|
+
return { kind: "staleness_summary", totalEntities: u64("total_entities"), staleCount: u64("stale_count"), staleEntityCountThreshold: u64("stale_entity_count_threshold"), sloBreached: breached.value };
|
|
1004
|
+
}
|
|
1005
|
+
case "model_version_summary":
|
|
1006
|
+
return { kind: "model_version_summary", currentCount: u64("current_count"), outdatedCount: u64("outdated_count") };
|
|
1007
|
+
case "stale_entities":
|
|
1008
|
+
return {
|
|
1009
|
+
kind: "stale_entities",
|
|
1010
|
+
items: items().map((item) => ({
|
|
1011
|
+
entityKey: decodeDriverBytes(item.entity_key),
|
|
1012
|
+
newestSourceWrite: decodeDriverU64(item.newest_source_write),
|
|
1013
|
+
embeddingWrite: item.embedding_write?.type === "null" ? null : decodeDriverU64(item.embedding_write),
|
|
1014
|
+
})),
|
|
1015
|
+
observedFrontier: u64("observed_frontier", true),
|
|
1016
|
+
};
|
|
1017
|
+
case "outdated_model_entities":
|
|
1018
|
+
return {
|
|
1019
|
+
kind: "outdated_model_entities",
|
|
1020
|
+
items: items().map((item) => ({
|
|
1021
|
+
entityKey: decodeDriverBytes(item.entity_key),
|
|
1022
|
+
model: decodeDriverString(item.model),
|
|
1023
|
+
modelVersion: decodeDriverString(item.model_version),
|
|
1024
|
+
embeddingWrite: decodeDriverU64(item.embedding_write),
|
|
1025
|
+
})),
|
|
1026
|
+
observedFrontier: u64("observed_frontier", true),
|
|
1027
|
+
};
|
|
1028
|
+
default: throw new Error("RiffDB driver returned invalid vector inspection");
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
function decodeDriverU64(value) {
|
|
1032
|
+
if (value?.type !== "u64" || !/^(?:0|[1-9][0-9]*)$/.test(value.value))
|
|
1033
|
+
throw new Error("RiffDB driver returned invalid vector inspection");
|
|
1034
|
+
return BigInt(value.value);
|
|
1035
|
+
}
|
|
1036
|
+
function decodeDriverString(value) {
|
|
1037
|
+
if (value?.type !== "string")
|
|
1038
|
+
throw new Error("RiffDB driver returned invalid vector inspection");
|
|
1039
|
+
return expectBoundedString(value.value, 256);
|
|
1040
|
+
}
|
|
1041
|
+
function decodeDriverBytes(value) {
|
|
1042
|
+
if (value?.type !== "bytes")
|
|
1043
|
+
throw new Error("RiffDB driver returned invalid vector inspection");
|
|
1044
|
+
const bytes = Uint8Array.from(Buffer.from(value.value, "base64"));
|
|
1045
|
+
if (bytes.byteLength < 1 || bytes.byteLength > 1_048_576)
|
|
1046
|
+
throw new Error("RiffDB driver returned invalid vector inspection");
|
|
1047
|
+
return bytes;
|
|
1048
|
+
}
|
|
1049
|
+
function decodePlainVectorInspection(value) {
|
|
1050
|
+
switch (value.kind) {
|
|
1051
|
+
case "staleness_summary":
|
|
1052
|
+
return { kind: value.kind, totalEntities: positiveOrZeroBigInt(value.total_entities), staleCount: positiveOrZeroBigInt(value.stale_count), staleEntityCountThreshold: positiveOrZeroBigInt(value.stale_entity_count_threshold), sloBreached: value.slo_breached === true };
|
|
1053
|
+
case "model_version_summary":
|
|
1054
|
+
return { kind: value.kind, currentCount: positiveOrZeroBigInt(value.current_count), outdatedCount: positiveOrZeroBigInt(value.outdated_count) };
|
|
1055
|
+
case "stale_entities":
|
|
1056
|
+
return { kind: value.kind, items: boundedPlainInspectionItems(value.items).map((item) => ({ entityKey: lowerHexBytes(item.entity_key), newestSourceWrite: positiveBigInt(item.newest_source_write), embeddingWrite: item.embedding_write === null ? null : positiveBigInt(item.embedding_write) })), observedFrontier: value.observed_frontier === null ? null : positiveBigInt(value.observed_frontier) };
|
|
1057
|
+
case "outdated_model_entities":
|
|
1058
|
+
return { kind: value.kind, items: boundedPlainInspectionItems(value.items).map((item) => ({ entityKey: lowerHexBytes(item.entity_key), model: expectBoundedString(item.model, 256), modelVersion: expectBoundedString(item.model_version, 256), embeddingWrite: positiveBigInt(item.embedding_write) })), observedFrontier: value.observed_frontier === null ? null : positiveBigInt(value.observed_frontier) };
|
|
1059
|
+
default: throw new Error("RiffDB CLI returned invalid vector inspection");
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
function boundedPlainInspectionItems(value) {
|
|
1063
|
+
if (!Array.isArray(value) || value.length > 500)
|
|
1064
|
+
throw new Error("RiffDB CLI returned invalid vector inspection");
|
|
1065
|
+
return value.map(exactObject);
|
|
1066
|
+
}
|
|
1067
|
+
function positiveOrZeroBigInt(value) {
|
|
1068
|
+
if (typeof value !== "string" || !/^(?:0|[1-9][0-9]*)$/.test(value))
|
|
1069
|
+
throw new Error("invalid nonnegative integer");
|
|
1070
|
+
return BigInt(value);
|
|
1071
|
+
}
|
|
1072
|
+
function lowerHexBytes(value) {
|
|
1073
|
+
const text = expectBoundedString(value, 2_097_152);
|
|
1074
|
+
if (text.length < 2 || text.length % 2 !== 0 || !/^[0-9a-f]+$/.test(text))
|
|
1075
|
+
throw new Error("invalid lower-hex bytes");
|
|
1076
|
+
return Uint8Array.from(text.match(/../g).map((byte) => Number.parseInt(byte, 16)));
|
|
1077
|
+
}
|
|
1078
|
+
function decodeDriverCommandResult(request, value, commitSequence, outcomeUri, replayed) {
|
|
1079
|
+
const outcome = driverOutcome(value);
|
|
1080
|
+
const schema = request.outcomeSchemas[outcome];
|
|
1081
|
+
if (schema === undefined)
|
|
1082
|
+
throw new Error("RiffDB driver returned an unknown command outcome");
|
|
1083
|
+
return {
|
|
1084
|
+
outcome: decodeDriverValue(value, {
|
|
1085
|
+
kind: "record",
|
|
1086
|
+
fields: [{ name: "outcome", schema: { kind: "enum" } }, ...recordFields(schema)],
|
|
1087
|
+
}),
|
|
1088
|
+
contractVersion: request.contractVersion,
|
|
1089
|
+
planHash: request.planHash,
|
|
1090
|
+
replayed,
|
|
1091
|
+
...(commitSequence === undefined ? {} : { commitSequence }),
|
|
1092
|
+
...(outcomeUri === undefined ? {} : { outcomeUri }),
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
function decodeDriverValue(value, schema) {
|
|
1096
|
+
if (schema.kind === "optional")
|
|
1097
|
+
return value.type === "null" ? null : decodeDriverValue(value, schema.value);
|
|
1098
|
+
if (schema.kind === "list") {
|
|
1099
|
+
if (value.type !== "list"
|
|
1100
|
+
|| value.value.length < (schema.minimum ?? 0)
|
|
1101
|
+
|| value.value.length > (schema.maximum ?? 4_096)) {
|
|
1102
|
+
throw new Error("invalid RiffDB driver list result");
|
|
1103
|
+
}
|
|
1104
|
+
return value.value.map((item) => decodeDriverValue(item, schema.value));
|
|
1105
|
+
}
|
|
1106
|
+
if (schema.kind === "record") {
|
|
1107
|
+
if (value.type !== "record")
|
|
1108
|
+
throw new Error("invalid RiffDB driver record result");
|
|
1109
|
+
const allowed = new Set(schema.fields.map((field) => field.name));
|
|
1110
|
+
if (Object.keys(value.value).some((name) => !allowed.has(name)))
|
|
1111
|
+
throw new Error("invalid RiffDB driver record result");
|
|
1112
|
+
const output = {};
|
|
1113
|
+
for (const field of schema.fields) {
|
|
1114
|
+
const fieldValue = value.value[field.name];
|
|
1115
|
+
if (fieldValue === undefined && field.schema.kind === "optional") {
|
|
1116
|
+
output[field.name] = null;
|
|
1117
|
+
continue;
|
|
1118
|
+
}
|
|
1119
|
+
if (fieldValue === undefined)
|
|
1120
|
+
throw new Error("invalid RiffDB driver record result");
|
|
1121
|
+
output[field.name] = decodeDriverValue(fieldValue, field.schema);
|
|
1122
|
+
}
|
|
1123
|
+
return output;
|
|
1124
|
+
}
|
|
1125
|
+
switch (schema.kind) {
|
|
1126
|
+
case "bool":
|
|
1127
|
+
if (value.type === "bool")
|
|
1128
|
+
return value.value;
|
|
1129
|
+
break;
|
|
1130
|
+
case "i64":
|
|
1131
|
+
if (value.type === "i64") {
|
|
1132
|
+
const parsed = BigInt(value.value);
|
|
1133
|
+
if (parsed >= -(1n << 63n) && parsed <= (1n << 63n) - 1n)
|
|
1134
|
+
return parsed;
|
|
1135
|
+
}
|
|
1136
|
+
break;
|
|
1137
|
+
case "u64":
|
|
1138
|
+
if (value.type === "u64") {
|
|
1139
|
+
const parsed = BigInt(value.value);
|
|
1140
|
+
if (parsed >= 0n && parsed <= (1n << 64n) - 1n)
|
|
1141
|
+
return parsed;
|
|
1142
|
+
}
|
|
1143
|
+
break;
|
|
1144
|
+
case "string":
|
|
1145
|
+
case "cursor":
|
|
1146
|
+
if (value.type === "string")
|
|
1147
|
+
return expectBoundedString(value.value, 262_144);
|
|
1148
|
+
break;
|
|
1149
|
+
case "uuid":
|
|
1150
|
+
if (value.type === "uuid" && UUID.test(value.value))
|
|
1151
|
+
return value.value;
|
|
1152
|
+
break;
|
|
1153
|
+
case "enum":
|
|
1154
|
+
if (value.type === "enum")
|
|
1155
|
+
return expectSymbol(value.value);
|
|
1156
|
+
break;
|
|
1157
|
+
case "bytes":
|
|
1158
|
+
if (value.type === "bytes")
|
|
1159
|
+
return Uint8Array.from(Buffer.from(value.value, "base64"));
|
|
1160
|
+
break;
|
|
1161
|
+
case "date":
|
|
1162
|
+
if (value.type === "date") {
|
|
1163
|
+
const parsed = Number(value.value);
|
|
1164
|
+
if (Number.isInteger(parsed) && parsed >= -2_147_483_648 && parsed <= 2_147_483_647)
|
|
1165
|
+
return parsed;
|
|
1166
|
+
}
|
|
1167
|
+
break;
|
|
1168
|
+
case "timestamp":
|
|
1169
|
+
if (value.type === "timestamp")
|
|
1170
|
+
return { seconds: BigInt(value.value.seconds), nanos: boundedInteger(value.value.nanos, 0, 999_999_999) };
|
|
1171
|
+
break;
|
|
1172
|
+
case "decimal":
|
|
1173
|
+
if (value.type === "decimal")
|
|
1174
|
+
return decodeDriverDecimal(value.value, schema);
|
|
1175
|
+
break;
|
|
1176
|
+
case "money":
|
|
1177
|
+
if (value.type === "money" && value.value.currency === schema.currency)
|
|
1178
|
+
return { currency: value.value.currency, amount: decodeDriverDecimal(value.value.amount, schema) };
|
|
1179
|
+
break;
|
|
1180
|
+
case "vector":
|
|
1181
|
+
if (value.type === "vector")
|
|
1182
|
+
return decodeVectorBits(value.value.component_bits, schema.dimension);
|
|
1183
|
+
break;
|
|
1184
|
+
case "limit":
|
|
1185
|
+
if (value.type === "u64")
|
|
1186
|
+
return boundedInteger(Number(value.value), 1, 500);
|
|
1187
|
+
break;
|
|
1188
|
+
}
|
|
1189
|
+
throw new Error("RiffDB driver result does not match the generated schema");
|
|
1190
|
+
}
|
|
1191
|
+
function decodeDriverDecimal(value, schema) {
|
|
1192
|
+
if (schema.precision === undefined || schema.scale === undefined
|
|
1193
|
+
|| (value.precision !== undefined && value.precision !== null && value.precision !== schema.precision)
|
|
1194
|
+
|| value.scale !== schema.scale)
|
|
1195
|
+
throw new Error("RiffDB driver decimal does not match its schema");
|
|
1196
|
+
const coefficient = Uint8Array.from(Buffer.from(value.coefficient, "base64"));
|
|
1197
|
+
if (coefficient.byteLength < 1 || coefficient.byteLength > 16)
|
|
1198
|
+
throw new Error("RiffDB driver decimal coefficient is invalid");
|
|
1199
|
+
return { coefficientTwosComplement: coefficient, precision: schema.precision, scale: schema.scale };
|
|
1200
|
+
}
|
|
1201
|
+
function decodeTypedCommandResult(result, request) {
|
|
1202
|
+
if (expectHash(result.plan_hash) !== request.planHash) {
|
|
1203
|
+
throw new Error("RiffDB application identity mismatch");
|
|
1204
|
+
}
|
|
1205
|
+
const outcomeName = expectSymbol(result.outcome_type);
|
|
1206
|
+
const schema = request.outcomeSchemas[outcomeName];
|
|
1207
|
+
if (schema === undefined)
|
|
1208
|
+
throw new Error("RiffDB application response has an unknown outcome");
|
|
1209
|
+
const payload = decodeWire(result.outcome, schema);
|
|
1210
|
+
const outcome = { outcome: outcomeName, ...exactObject(payload) };
|
|
1211
|
+
const typed = {
|
|
1212
|
+
outcome,
|
|
1213
|
+
contractVersion: positiveNumber(result.contract_version),
|
|
1214
|
+
planHash: request.planHash,
|
|
1215
|
+
replayed: result.status === "replayed",
|
|
1216
|
+
};
|
|
1217
|
+
if (result.commit_sequence === undefined)
|
|
1218
|
+
return typed;
|
|
1219
|
+
return {
|
|
1220
|
+
...typed,
|
|
1221
|
+
commitSequence: positiveBigInt(result.commit_sequence),
|
|
1222
|
+
...(result.outcome_uri === undefined
|
|
1223
|
+
? {}
|
|
1224
|
+
: { outcomeUri: expectBoundedString(result.outcome_uri, 4096) }),
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
function decodeContextualItem(value) {
|
|
1228
|
+
const item = exactObject(value);
|
|
1229
|
+
return {
|
|
1230
|
+
delivery: decodeContextualDelivery(item.delivery),
|
|
1231
|
+
contextHead: positiveBigInt(item.context_head),
|
|
1232
|
+
hydrations: expectArray(item.hydrations).map(decodeContextualHydration),
|
|
1233
|
+
availableReactions: expectArray(item.available_reactions).map((raw) => {
|
|
1234
|
+
const reaction = exactObject(raw);
|
|
1235
|
+
return {
|
|
1236
|
+
name: expectSymbol(reaction.name),
|
|
1237
|
+
commandName: expectSymbol(reaction.command_name),
|
|
1238
|
+
commandId: positiveNumber(reaction.command_id),
|
|
1239
|
+
causationToken: expectBoundedLowerHex(reaction.causation_token, 2_048),
|
|
1240
|
+
};
|
|
1241
|
+
}),
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
function decodeContextualDelivery(value) {
|
|
1245
|
+
const delivery = exactObject(value);
|
|
1246
|
+
const fields = Object.fromEntries(expectArray(delivery.fields).map((raw) => {
|
|
1247
|
+
const field = exactObject(raw);
|
|
1248
|
+
return [expectSymbol(field.name), decodeTagged(field.value)];
|
|
1249
|
+
}));
|
|
1250
|
+
const expiration = exactObject(delivery.expires_at);
|
|
1251
|
+
return {
|
|
1252
|
+
eventId: expectBoundedString(delivery.event_id, 64),
|
|
1253
|
+
event: { type: expectSymbol(delivery.event_name), ...fields },
|
|
1254
|
+
attempt: positiveNumber(delivery.attempt),
|
|
1255
|
+
leaseToken: expectHash(delivery.lease_token),
|
|
1256
|
+
expiresAt: `${expectBoundedString(expiration.seconds, 32)}.${String(boundedInteger(expiration.nanos, 0, 999_999_999)).padStart(9, "0")}`,
|
|
1257
|
+
historyIncarnation: positiveBigInt(delivery.history_incarnation),
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
function decodeContextualHydration(value) {
|
|
1261
|
+
const hydration = exactObject(value);
|
|
1262
|
+
const fields = Object.fromEntries(expectArray(hydration.fields).map((raw) => {
|
|
1263
|
+
const field = exactObject(raw);
|
|
1264
|
+
const rows = expectArray(field.rows).map((rawRow) => {
|
|
1265
|
+
const row = exactObject(rawRow);
|
|
1266
|
+
expectBoundedString(row.entity, 256);
|
|
1267
|
+
return Object.fromEntries(expectArray(row.fields).map((rawValue) => {
|
|
1268
|
+
const item = exactObject(rawValue);
|
|
1269
|
+
return [expectSymbol(item.name), decodeTagged(item.value)];
|
|
1270
|
+
}));
|
|
1271
|
+
});
|
|
1272
|
+
const cardinality = boundedInteger(field.cardinality, 1, 3);
|
|
1273
|
+
if ((cardinality === 1 && rows.length !== 1) || (cardinality === 2 && rows.length > 1)) {
|
|
1274
|
+
throw new Error("invalid RiffDB contextual cardinality");
|
|
1275
|
+
}
|
|
1276
|
+
return [expectSymbol(field.name), cardinality === 3 ? rows : (rows[0] ?? null)];
|
|
1277
|
+
}));
|
|
1278
|
+
return {
|
|
1279
|
+
name: expectSymbol(hydration.name),
|
|
1280
|
+
outcome: expectSymbol(hydration.outcome),
|
|
1281
|
+
fields,
|
|
1282
|
+
};
|
|
1283
|
+
}
|
|
1284
|
+
function expectBoundedLowerHex(value, maximum) {
|
|
1285
|
+
const text = expectBoundedString(value, maximum);
|
|
1286
|
+
if (text.length <= 64 || text.length % 2 !== 0 || !/^[0-9a-f]+$/.test(text)) {
|
|
1287
|
+
throw new Error("invalid opaque RiffDB token");
|
|
1288
|
+
}
|
|
1289
|
+
return text;
|
|
1290
|
+
}
|
|
1291
|
+
function validateIdentity(value) {
|
|
1292
|
+
expectSymbol(value.contractLineage);
|
|
1293
|
+
positiveNumber(value.contractVersion);
|
|
1294
|
+
if (value.moduleHash !== undefined)
|
|
1295
|
+
expectHash(value.moduleHash);
|
|
1296
|
+
if (value.planHash !== undefined)
|
|
1297
|
+
expectHash(value.planHash);
|
|
1298
|
+
if (value.queryName !== undefined)
|
|
1299
|
+
expectSymbol(value.queryName);
|
|
1300
|
+
if (value.commandName !== undefined)
|
|
1301
|
+
expectSymbol(value.commandName);
|
|
1302
|
+
}
|
|
1303
|
+
function encodeValue(value, schema, budgetPath) {
|
|
1304
|
+
if (schema.kind === "optional") {
|
|
1305
|
+
return value === null || value === undefined ? null : encodeValue(value, schema.value, budgetPath);
|
|
1306
|
+
}
|
|
1307
|
+
if (schema.kind === "list") {
|
|
1308
|
+
if (!Array.isArray(value)
|
|
1309
|
+
|| (schema.minimum !== undefined && value.length < schema.minimum)
|
|
1310
|
+
|| (schema.maximum !== undefined && value.length > schema.maximum)) {
|
|
1311
|
+
if (budgetPath !== undefined)
|
|
1312
|
+
throw new InputBudgetError("collection_count", budgetPath);
|
|
1313
|
+
throw new Error("invalid generated application input");
|
|
1314
|
+
}
|
|
1315
|
+
const encoded = value.map((item, index) => encodeValue(item, schema.value, budgetPath === undefined ? undefined : { ...budgetPath, index }));
|
|
1316
|
+
if (schema.aggregateCanonicalElementBytes !== undefined) {
|
|
1317
|
+
let aggregate = 0;
|
|
1318
|
+
for (const item of value) {
|
|
1319
|
+
aggregate += canonicalValueEncodedLength(item, schema.value);
|
|
1320
|
+
if (!Number.isSafeInteger(aggregate) || aggregate > schema.aggregateCanonicalElementBytes) {
|
|
1321
|
+
if (budgetPath !== undefined)
|
|
1322
|
+
throw new InputBudgetError("aggregate_canonical_element_bytes", budgetPath);
|
|
1323
|
+
throw new Error("invalid generated application input");
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
return encoded;
|
|
1328
|
+
}
|
|
1329
|
+
if (schema.kind === "record") {
|
|
1330
|
+
const input = exactObject(value);
|
|
1331
|
+
const allowed = new Set(schema.fields.map((field) => field.name));
|
|
1332
|
+
if (Object.keys(input).some((name) => !allowed.has(name))) {
|
|
1333
|
+
throw new Error("invalid generated application input");
|
|
1334
|
+
}
|
|
1335
|
+
const output = {};
|
|
1336
|
+
for (const field of schema.fields) {
|
|
1337
|
+
const fieldValue = input[field.name];
|
|
1338
|
+
if (fieldValue === undefined && field.schema.kind === "optional")
|
|
1339
|
+
continue;
|
|
1340
|
+
if (fieldValue === undefined)
|
|
1341
|
+
continue;
|
|
1342
|
+
const childPath = budgetPath === undefined
|
|
1343
|
+
? (field.schema.kind === "list" && field.schema.aggregateCanonicalElementBytes !== undefined
|
|
1344
|
+
? { collection: field.name }
|
|
1345
|
+
: undefined)
|
|
1346
|
+
: { ...budgetPath, leaf: field.name };
|
|
1347
|
+
output[field.name] = encodeValue(fieldValue, field.schema, childPath);
|
|
1348
|
+
}
|
|
1349
|
+
return output;
|
|
1350
|
+
}
|
|
1351
|
+
switch (schema.kind) {
|
|
1352
|
+
case "uuid":
|
|
1353
|
+
if (typeof value !== "string" || !UUID.test(value))
|
|
1354
|
+
throw new Error("invalid UUID input");
|
|
1355
|
+
return { $uuid: value };
|
|
1356
|
+
case "enum":
|
|
1357
|
+
return { $enum: expectSymbol(value) };
|
|
1358
|
+
case "string": {
|
|
1359
|
+
const string = expectBoundedString(value, 262_144);
|
|
1360
|
+
if (schema.maximumBytes !== undefined && new TextEncoder().encode(string).length > schema.maximumBytes && budgetPath !== undefined) {
|
|
1361
|
+
throw new InputBudgetError("individual_value_bytes", budgetPath);
|
|
1362
|
+
}
|
|
1363
|
+
return string;
|
|
1364
|
+
}
|
|
1365
|
+
case "cursor": return expectBoundedString(value, 262_144);
|
|
1366
|
+
case "bool":
|
|
1367
|
+
if (typeof value !== "boolean")
|
|
1368
|
+
throw new Error("invalid boolean input");
|
|
1369
|
+
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() };
|
|
1380
|
+
case "decimal": {
|
|
1381
|
+
const decimal = encodeDecimal(value);
|
|
1382
|
+
if (schema.precision === undefined || schema.scale === undefined
|
|
1383
|
+
|| decimal.scale !== schema.scale
|
|
1384
|
+
|| (decimal.precision !== undefined && decimal.precision !== schema.precision)) {
|
|
1385
|
+
throw new Error("decimal input does not match the generated type");
|
|
1386
|
+
}
|
|
1387
|
+
return { $decimal: { ...decimal, precision: schema.precision } };
|
|
1388
|
+
}
|
|
1389
|
+
case "money": {
|
|
1390
|
+
const input = exactObject(value);
|
|
1391
|
+
const currency = expectCurrency(input.currency);
|
|
1392
|
+
const amount = encodeDecimal(input.amount);
|
|
1393
|
+
if (schema.currency === undefined || schema.precision === undefined || schema.scale === undefined
|
|
1394
|
+
|| currency !== schema.currency || amount.scale !== schema.scale
|
|
1395
|
+
|| (amount.precision !== undefined && amount.precision !== schema.precision)) {
|
|
1396
|
+
throw new Error("money input does not match the generated type");
|
|
1397
|
+
}
|
|
1398
|
+
return {
|
|
1399
|
+
$money: {
|
|
1400
|
+
currency,
|
|
1401
|
+
amount: { ...amount, precision: schema.precision },
|
|
1402
|
+
},
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
case "bytes":
|
|
1406
|
+
if (!(value instanceof Uint8Array) || value.byteLength > 1_048_576) {
|
|
1407
|
+
throw new Error("invalid bytes input");
|
|
1408
|
+
}
|
|
1409
|
+
if (schema.maximumBytes !== undefined && value.byteLength > schema.maximumBytes && budgetPath !== undefined) {
|
|
1410
|
+
throw new InputBudgetError("individual_value_bytes", budgetPath);
|
|
1411
|
+
}
|
|
1412
|
+
return { $bytes: Buffer.from(value).toString("base64") };
|
|
1413
|
+
case "date":
|
|
1414
|
+
if (!Number.isInteger(value) || value < -2_147_483_648 || value > 2_147_483_647) {
|
|
1415
|
+
throw new Error("invalid date input");
|
|
1416
|
+
}
|
|
1417
|
+
return { $date: value };
|
|
1418
|
+
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 } };
|
|
1427
|
+
}
|
|
1428
|
+
case "vector": return { $vector: encodeVector(value, schema.dimension) };
|
|
1429
|
+
case "limit":
|
|
1430
|
+
if (!Number.isInteger(value) || value < 1 || value > 500) {
|
|
1431
|
+
throw new Error("invalid limit input");
|
|
1432
|
+
}
|
|
1433
|
+
return value;
|
|
1434
|
+
default:
|
|
1435
|
+
throw new Error("generated application scalar is not supported by the CLI transport");
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
function canonicalValueEncodedLength(value, schema) {
|
|
1439
|
+
if (schema.kind === "optional") {
|
|
1440
|
+
return value === null || value === undefined ? 2 : canonicalValueEncodedLength(value, schema.value);
|
|
1441
|
+
}
|
|
1442
|
+
if (schema.kind === "list") {
|
|
1443
|
+
if (!Array.isArray(value))
|
|
1444
|
+
throw new Error("invalid generated application input");
|
|
1445
|
+
return value.reduce((total, item) => total + canonicalValueEncodedLength(item, schema.value), 6);
|
|
1446
|
+
}
|
|
1447
|
+
if (schema.kind === "record") {
|
|
1448
|
+
const input = exactObject(value);
|
|
1449
|
+
return schema.fields.reduce((total, field) => {
|
|
1450
|
+
const fieldValue = input[field.name];
|
|
1451
|
+
if (fieldValue === undefined && field.schema.kind !== "optional") {
|
|
1452
|
+
throw new Error("invalid generated application input");
|
|
1453
|
+
}
|
|
1454
|
+
return total + 4 + canonicalValueEncodedLength(fieldValue, field.schema);
|
|
1455
|
+
}, 6);
|
|
1456
|
+
}
|
|
1457
|
+
switch (schema.kind) {
|
|
1458
|
+
case "bool": return 3;
|
|
1459
|
+
case "i64":
|
|
1460
|
+
case "u64": return 10;
|
|
1461
|
+
case "decimal": return 20;
|
|
1462
|
+
case "money": return 23;
|
|
1463
|
+
case "string":
|
|
1464
|
+
case "cursor": return 6 + new TextEncoder().encode(expectBoundedString(value, 262_144)).byteLength;
|
|
1465
|
+
case "bytes": {
|
|
1466
|
+
if (!(value instanceof Uint8Array))
|
|
1467
|
+
throw new Error("invalid generated application input");
|
|
1468
|
+
return 6 + value.byteLength;
|
|
1469
|
+
}
|
|
1470
|
+
case "timestamp": return 14;
|
|
1471
|
+
case "date": return 6;
|
|
1472
|
+
case "uuid": return 18;
|
|
1473
|
+
case "enum": return 10;
|
|
1474
|
+
case "vector": {
|
|
1475
|
+
if (!Array.isArray(value))
|
|
1476
|
+
throw new Error("invalid generated application input");
|
|
1477
|
+
return 6 + value.length * 4;
|
|
1478
|
+
}
|
|
1479
|
+
default: throw new Error("invalid generated application input");
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
function normalizeQueryFields(value) {
|
|
1483
|
+
if (!Array.isArray(value) || value.length > 4096)
|
|
1484
|
+
throw new Error("invalid RiffDB query fields");
|
|
1485
|
+
const output = {};
|
|
1486
|
+
for (const item of value) {
|
|
1487
|
+
const field = exactObject(item);
|
|
1488
|
+
const name = expectSymbol(field.name);
|
|
1489
|
+
if (name in output || !Array.isArray(field.records))
|
|
1490
|
+
throw new Error("invalid RiffDB query fields");
|
|
1491
|
+
const records = field.records.map((record) => {
|
|
1492
|
+
const raw = exactObject(record);
|
|
1493
|
+
if (!Array.isArray(raw.fields))
|
|
1494
|
+
throw new Error("invalid RiffDB query record");
|
|
1495
|
+
const result = {};
|
|
1496
|
+
for (const item of raw.fields) {
|
|
1497
|
+
const entry = exactObject(item);
|
|
1498
|
+
const fieldName = expectSymbol(entry.name);
|
|
1499
|
+
if (fieldName in result)
|
|
1500
|
+
throw new Error("duplicate RiffDB query field");
|
|
1501
|
+
result[fieldName] = decodeTagged(entry.value);
|
|
1502
|
+
}
|
|
1503
|
+
return result;
|
|
1504
|
+
});
|
|
1505
|
+
switch (field.cardinality) {
|
|
1506
|
+
case "one":
|
|
1507
|
+
if (records.length !== 1)
|
|
1508
|
+
throw new Error("invalid one cardinality");
|
|
1509
|
+
output[name] = records[0];
|
|
1510
|
+
break;
|
|
1511
|
+
case "maybe":
|
|
1512
|
+
if (records.length > 1)
|
|
1513
|
+
throw new Error("invalid maybe cardinality");
|
|
1514
|
+
output[name] = records[0] ?? null;
|
|
1515
|
+
break;
|
|
1516
|
+
case "many":
|
|
1517
|
+
output[name] = records;
|
|
1518
|
+
break;
|
|
1519
|
+
default:
|
|
1520
|
+
throw new Error("invalid RiffDB query cardinality");
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
return output;
|
|
1524
|
+
}
|
|
1525
|
+
function decodeTagged(value) {
|
|
1526
|
+
const input = exactObject(value);
|
|
1527
|
+
switch (input.type) {
|
|
1528
|
+
case "null": return null;
|
|
1529
|
+
case "bool": return input.value === true ? true : input.value === false ? false : fail();
|
|
1530
|
+
case "i64":
|
|
1531
|
+
case "u64": return BigInt(expectDecimal(input.value));
|
|
1532
|
+
case "string":
|
|
1533
|
+
case "uuid": return expectBoundedString(input.value, 262_144);
|
|
1534
|
+
case "enum": return expectSymbol(input.name);
|
|
1535
|
+
case "date": return Number(expectDecimal(input.days_since_unix_epoch));
|
|
1536
|
+
case "timestamp": return { seconds: BigInt(expectDecimal(input.seconds)), nanos: nonnegativeNumber(input.nanos) };
|
|
1537
|
+
case "bytes": return Uint8Array.from(Buffer.from(expectBoundedString(input.value, MAX_OUTPUT_BYTES), "base64"));
|
|
1538
|
+
case "decimal": return decodeDecimal(input);
|
|
1539
|
+
case "money": return {
|
|
1540
|
+
currency: expectCurrency(input.currency),
|
|
1541
|
+
amount: decodeDecimal(exactObject(input.amount)),
|
|
1542
|
+
};
|
|
1543
|
+
case "vector": return decodeVector(input.components, undefined);
|
|
1544
|
+
case "list":
|
|
1545
|
+
if (!Array.isArray(input.values))
|
|
1546
|
+
return fail();
|
|
1547
|
+
return input.values.map(decodeTagged);
|
|
1548
|
+
default: return fail();
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
function encodeDecimal(value) {
|
|
1552
|
+
const input = exactObject(value);
|
|
1553
|
+
const coefficient = input.coefficientTwosComplement;
|
|
1554
|
+
if (!(coefficient instanceof Uint8Array) || coefficient.byteLength < 1 || coefficient.byteLength > 16
|
|
1555
|
+
|| !Number.isInteger(input.scale) || input.scale < 0
|
|
1556
|
+
|| input.scale > 4_294_967_295) {
|
|
1557
|
+
throw new Error("invalid decimal input");
|
|
1558
|
+
}
|
|
1559
|
+
const output = {
|
|
1560
|
+
coefficient_twos_complement: Buffer.from(coefficient).toString("base64"),
|
|
1561
|
+
scale: input.scale,
|
|
1562
|
+
};
|
|
1563
|
+
if (input.precision !== undefined) {
|
|
1564
|
+
if (!Number.isInteger(input.precision) || input.precision < 1
|
|
1565
|
+
|| input.precision > 4_294_967_295) {
|
|
1566
|
+
throw new Error("invalid decimal precision");
|
|
1567
|
+
}
|
|
1568
|
+
output.precision = input.precision;
|
|
1569
|
+
}
|
|
1570
|
+
return output;
|
|
1571
|
+
}
|
|
1572
|
+
function decodeDecimal(input) {
|
|
1573
|
+
const coefficient = Uint8Array.from(Buffer.from(expectBoundedString(input.coefficient_twos_complement, 24), "base64"));
|
|
1574
|
+
const scale = nonnegativeNumber(input.scale);
|
|
1575
|
+
if (coefficient.byteLength < 1 || coefficient.byteLength > 16)
|
|
1576
|
+
return fail();
|
|
1577
|
+
if (input.precision === undefined)
|
|
1578
|
+
return { coefficientTwosComplement: coefficient, scale };
|
|
1579
|
+
const precision = positiveNumber(input.precision);
|
|
1580
|
+
return { coefficientTwosComplement: coefficient, scale, precision };
|
|
1581
|
+
}
|
|
1582
|
+
function decodePlain(value, schema) {
|
|
1583
|
+
if (schema.kind === "optional")
|
|
1584
|
+
return value === null ? null : decodePlain(value, schema.value);
|
|
1585
|
+
if (schema.kind === "list") {
|
|
1586
|
+
if (!Array.isArray(value)
|
|
1587
|
+
|| (schema.minimum !== undefined && value.length < schema.minimum)
|
|
1588
|
+
|| (schema.maximum !== undefined && value.length > schema.maximum))
|
|
1589
|
+
return fail();
|
|
1590
|
+
return value.map((item) => decodePlain(item, schema.value));
|
|
1591
|
+
}
|
|
1592
|
+
if (schema.kind === "record") {
|
|
1593
|
+
const input = exactObject(value);
|
|
1594
|
+
const output = {};
|
|
1595
|
+
if (Object.keys(input).length !== schema.fields.length)
|
|
1596
|
+
return fail();
|
|
1597
|
+
for (const field of schema.fields)
|
|
1598
|
+
output[field.name] = decodePlain(input[field.name], field.schema);
|
|
1599
|
+
return output;
|
|
1600
|
+
}
|
|
1601
|
+
switch (schema.kind) {
|
|
1602
|
+
case "bool":
|
|
1603
|
+
if (typeof value === "boolean")
|
|
1604
|
+
return value;
|
|
1605
|
+
break;
|
|
1606
|
+
case "i64":
|
|
1607
|
+
case "u64":
|
|
1608
|
+
if (typeof value === "bigint")
|
|
1609
|
+
return value;
|
|
1610
|
+
break;
|
|
1611
|
+
case "string":
|
|
1612
|
+
case "enum":
|
|
1613
|
+
case "cursor":
|
|
1614
|
+
if (typeof value === "string")
|
|
1615
|
+
return value;
|
|
1616
|
+
break;
|
|
1617
|
+
case "uuid":
|
|
1618
|
+
if (typeof value === "string" && UUID.test(value))
|
|
1619
|
+
return value;
|
|
1620
|
+
break;
|
|
1621
|
+
case "date":
|
|
1622
|
+
case "limit":
|
|
1623
|
+
if (Number.isInteger(value))
|
|
1624
|
+
return value;
|
|
1625
|
+
break;
|
|
1626
|
+
case "timestamp": {
|
|
1627
|
+
const input = exactObject(value);
|
|
1628
|
+
if (typeof input.seconds === "bigint" && Number.isInteger(input.nanos))
|
|
1629
|
+
return value;
|
|
1630
|
+
break;
|
|
1631
|
+
}
|
|
1632
|
+
case "bytes":
|
|
1633
|
+
if (value instanceof Uint8Array)
|
|
1634
|
+
return value;
|
|
1635
|
+
break;
|
|
1636
|
+
case "decimal": {
|
|
1637
|
+
return normalizeDecodedDecimal(value, schema.precision, schema.scale);
|
|
1638
|
+
}
|
|
1639
|
+
case "money": {
|
|
1640
|
+
const input = exactObject(value);
|
|
1641
|
+
const currency = expectCurrency(input.currency);
|
|
1642
|
+
if (schema.currency !== undefined && currency !== schema.currency)
|
|
1643
|
+
return fail();
|
|
1644
|
+
return {
|
|
1645
|
+
currency,
|
|
1646
|
+
amount: normalizeDecodedDecimal(input.amount, schema.precision, schema.scale),
|
|
1647
|
+
};
|
|
1648
|
+
}
|
|
1649
|
+
case "vector": return decodeVector(value, schema.dimension);
|
|
1650
|
+
default: break;
|
|
1651
|
+
}
|
|
1652
|
+
return fail();
|
|
1653
|
+
}
|
|
1654
|
+
function normalizeDecodedDecimal(value, expectedPrecision, expectedScale) {
|
|
1655
|
+
const input = exactObject(value);
|
|
1656
|
+
const encoded = encodeDecimal(value);
|
|
1657
|
+
if ((expectedPrecision !== undefined && encoded.precision !== undefined
|
|
1658
|
+
&& encoded.precision !== expectedPrecision)
|
|
1659
|
+
|| (expectedScale !== undefined && encoded.scale !== expectedScale))
|
|
1660
|
+
return fail();
|
|
1661
|
+
const precision = expectedPrecision ?? encoded.precision;
|
|
1662
|
+
return {
|
|
1663
|
+
coefficientTwosComplement: input.coefficientTwosComplement,
|
|
1664
|
+
scale: encoded.scale,
|
|
1665
|
+
...(precision === undefined ? {} : { precision }),
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
function decodeWire(value, schema) {
|
|
1669
|
+
if (schema.kind !== "record")
|
|
1670
|
+
return decodePlain(decodeTagged(value), schema);
|
|
1671
|
+
const input = exactObject(value);
|
|
1672
|
+
if (input.type !== "record" || !Array.isArray(input.fields))
|
|
1673
|
+
return fail();
|
|
1674
|
+
const fields = new Map();
|
|
1675
|
+
for (const item of input.fields) {
|
|
1676
|
+
const field = exactObject(item);
|
|
1677
|
+
const id = positiveNumber(field.field_id);
|
|
1678
|
+
if (fields.has(id))
|
|
1679
|
+
return fail();
|
|
1680
|
+
fields.set(id, field.value);
|
|
1681
|
+
}
|
|
1682
|
+
const output = {};
|
|
1683
|
+
for (const field of schema.fields) {
|
|
1684
|
+
if (field.wireId === undefined)
|
|
1685
|
+
return fail();
|
|
1686
|
+
const raw = fields.get(field.wireId);
|
|
1687
|
+
if (raw === undefined)
|
|
1688
|
+
return fail();
|
|
1689
|
+
output[field.name] = decodeWire(raw, field.schema);
|
|
1690
|
+
fields.delete(field.wireId);
|
|
1691
|
+
}
|
|
1692
|
+
if (fields.size !== 0)
|
|
1693
|
+
return fail();
|
|
1694
|
+
return output;
|
|
1695
|
+
}
|
|
1696
|
+
function normalizeError(input) {
|
|
1697
|
+
const output = {
|
|
1698
|
+
type: input.type,
|
|
1699
|
+
code: input.code,
|
|
1700
|
+
message: input.message,
|
|
1701
|
+
category: input.category,
|
|
1702
|
+
recoveryAction: input.recovery_action,
|
|
1703
|
+
operation: input.operation,
|
|
1704
|
+
symbolPath: input.symbol_path ?? [],
|
|
1705
|
+
fixes: input.fixes,
|
|
1706
|
+
};
|
|
1707
|
+
if (input.contract_lineage !== undefined)
|
|
1708
|
+
output.contractLineage = input.contract_lineage;
|
|
1709
|
+
if (input.contract_version !== undefined)
|
|
1710
|
+
output.contractVersion = Number(expectDecimal(input.contract_version));
|
|
1711
|
+
if (input.operation_symbol !== undefined)
|
|
1712
|
+
output.operationSymbol = input.operation_symbol;
|
|
1713
|
+
if (input.source_span !== undefined) {
|
|
1714
|
+
const span = exactObject(input.source_span);
|
|
1715
|
+
output.sourceSpan = { start: Number(expectDecimal(span.start)), end: Number(expectDecimal(span.end)) };
|
|
1716
|
+
}
|
|
1717
|
+
if (input.trace_id !== undefined)
|
|
1718
|
+
output.traceId = input.trace_id;
|
|
1719
|
+
if (input.incident_id !== undefined)
|
|
1720
|
+
output.incidentId = input.incident_id;
|
|
1721
|
+
return output;
|
|
1722
|
+
}
|
|
1723
|
+
function recordFields(schema) {
|
|
1724
|
+
if (schema.kind !== "record")
|
|
1725
|
+
return fail();
|
|
1726
|
+
return schema.fields;
|
|
1727
|
+
}
|
|
1728
|
+
function exactObject(value) {
|
|
1729
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
1730
|
+
return fail();
|
|
1731
|
+
return value;
|
|
1732
|
+
}
|
|
1733
|
+
function expectArray(value) {
|
|
1734
|
+
if (!Array.isArray(value) || value.length > 100_000)
|
|
1735
|
+
throw new Error("invalid RiffDB collection");
|
|
1736
|
+
return value;
|
|
1737
|
+
}
|
|
1738
|
+
function boundedInteger(value, minimum, maximum) {
|
|
1739
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < minimum || value > maximum) {
|
|
1740
|
+
throw new Error("invalid bounded integer");
|
|
1741
|
+
}
|
|
1742
|
+
return value;
|
|
1743
|
+
}
|
|
1744
|
+
function nonnegativeBigInt(value) {
|
|
1745
|
+
if (typeof value !== "string" || !/^(0|[1-9][0-9]{0,19})$/.test(value)) {
|
|
1746
|
+
throw new Error("invalid nonnegative integer");
|
|
1747
|
+
}
|
|
1748
|
+
return BigInt(value);
|
|
1749
|
+
}
|
|
1750
|
+
function validateReactiveRequest(request) {
|
|
1751
|
+
if (!HASH.test(request.reactiveModuleHash)
|
|
1752
|
+
|| !SYMBOL.test(request.operationName)
|
|
1753
|
+
|| !/^[A-Za-z][A-Za-z0-9_-]{0,63}$/.test(request.consumerName)) {
|
|
1754
|
+
throw new Error("invalid reactive consumer identity");
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
function addReactiveParameters(args, parameters, parameterSchema) {
|
|
1758
|
+
const values = exactObject(parameters);
|
|
1759
|
+
if (parameterSchema.kind !== "record")
|
|
1760
|
+
throw new Error("invalid reactive parameter schema");
|
|
1761
|
+
const schemas = new Map(parameterSchema.fields.map((field) => [field.name, field.schema]));
|
|
1762
|
+
const entries = Object.entries(values).sort(([left], [right]) => left.localeCompare(right));
|
|
1763
|
+
if (entries.length > 256 || entries.length !== schemas.size) {
|
|
1764
|
+
throw new Error("invalid reactive parameters");
|
|
1765
|
+
}
|
|
1766
|
+
for (const [name, value] of entries) {
|
|
1767
|
+
const schema = schemas.get(name);
|
|
1768
|
+
if (!SYMBOL.test(name) || schema === undefined) {
|
|
1769
|
+
throw new Error("invalid reactive parameter name");
|
|
1770
|
+
}
|
|
1771
|
+
args.push("--parameter", `${name}=${JSON.stringify(encodeCliReactiveValue(value, schema))}`);
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
function encodeCliReactiveValue(value, schema) {
|
|
1775
|
+
switch (schema.kind) {
|
|
1776
|
+
case "bool":
|
|
1777
|
+
if (typeof value !== "boolean")
|
|
1778
|
+
return fail();
|
|
1779
|
+
return { type: "bool", value };
|
|
1780
|
+
case "i64":
|
|
1781
|
+
if (typeof value !== "bigint" || value < -(1n << 63n) || value > (1n << 63n) - 1n)
|
|
1782
|
+
return fail();
|
|
1783
|
+
return { type: "i64", value: value.toString() };
|
|
1784
|
+
case "u64":
|
|
1785
|
+
if (typeof value !== "bigint" || value < 0n || value > (1n << 64n) - 1n)
|
|
1786
|
+
return fail();
|
|
1787
|
+
return { type: "u64", value: value.toString() };
|
|
1788
|
+
case "string":
|
|
1789
|
+
case "cursor":
|
|
1790
|
+
return { type: "string", value: expectBoundedString(value, 262_144) };
|
|
1791
|
+
case "uuid":
|
|
1792
|
+
if (typeof value !== "string" || !UUID.test(value))
|
|
1793
|
+
return fail();
|
|
1794
|
+
return { type: "uuid", value };
|
|
1795
|
+
case "enum":
|
|
1796
|
+
{
|
|
1797
|
+
const name = expectSymbol(value);
|
|
1798
|
+
const variantId = schema.variants?.[name];
|
|
1799
|
+
if (schema.typeId === undefined || variantId === undefined)
|
|
1800
|
+
return fail();
|
|
1801
|
+
return { type: "enum", type_id: positiveNumber(schema.typeId), variant_id: positiveNumber(variantId), name };
|
|
1802
|
+
}
|
|
1803
|
+
case "bytes":
|
|
1804
|
+
if (!(value instanceof Uint8Array) || value.byteLength > 1_048_576)
|
|
1805
|
+
return fail();
|
|
1806
|
+
return { type: "bytes", value: Buffer.from(value).toString("base64") };
|
|
1807
|
+
case "date":
|
|
1808
|
+
return { type: "date", days_since_unix_epoch: boundedInteger(value, -2_147_483_648, 2_147_483_647) };
|
|
1809
|
+
case "timestamp": {
|
|
1810
|
+
const timestamp = exactObject(value);
|
|
1811
|
+
if (typeof timestamp.seconds !== "bigint"
|
|
1812
|
+
|| timestamp.seconds < -(1n << 63n)
|
|
1813
|
+
|| timestamp.seconds > (1n << 63n) - 1n)
|
|
1814
|
+
return fail();
|
|
1815
|
+
return {
|
|
1816
|
+
type: "timestamp",
|
|
1817
|
+
seconds: timestamp.seconds.toString(),
|
|
1818
|
+
nanos: boundedInteger(timestamp.nanos, 0, 999_999_999),
|
|
1819
|
+
};
|
|
1820
|
+
}
|
|
1821
|
+
case "decimal": {
|
|
1822
|
+
const decimal = encodeDecimal(value);
|
|
1823
|
+
if (schema.precision === undefined || schema.scale === undefined
|
|
1824
|
+
|| decimal.scale !== schema.scale
|
|
1825
|
+
|| (decimal.precision !== undefined && decimal.precision !== schema.precision))
|
|
1826
|
+
return fail();
|
|
1827
|
+
return { type: "decimal", ...decimal, precision: schema.precision };
|
|
1828
|
+
}
|
|
1829
|
+
case "money": {
|
|
1830
|
+
const money = exactObject(value);
|
|
1831
|
+
return {
|
|
1832
|
+
type: "money",
|
|
1833
|
+
currency: schema.currency === undefined ? fail() : expectCurrency(schema.currency),
|
|
1834
|
+
amount: (() => {
|
|
1835
|
+
const amount = encodeDecimal(money.amount);
|
|
1836
|
+
if (money.currency !== schema.currency || schema.precision === undefined || schema.scale === undefined
|
|
1837
|
+
|| amount.scale !== schema.scale
|
|
1838
|
+
|| (amount.precision !== undefined && amount.precision !== schema.precision))
|
|
1839
|
+
return fail();
|
|
1840
|
+
return { ...amount, precision: schema.precision };
|
|
1841
|
+
})(),
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1844
|
+
case "vector": return {
|
|
1845
|
+
type: "vector",
|
|
1846
|
+
components: encodeVector(value, schema.dimension),
|
|
1847
|
+
};
|
|
1848
|
+
case "limit":
|
|
1849
|
+
return { type: "u64", value: String(boundedInteger(value, 1, 500)) };
|
|
1850
|
+
case "optional":
|
|
1851
|
+
case "list":
|
|
1852
|
+
case "record":
|
|
1853
|
+
throw new Error("invalid reactive parameter schema");
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
function encodeVector(value, dimension) {
|
|
1857
|
+
if (!Array.isArray(value) || !Number.isInteger(dimension) || dimension < 1
|
|
1858
|
+
|| dimension > 4_096 || value.length !== dimension)
|
|
1859
|
+
return fail();
|
|
1860
|
+
return value.map((component) => {
|
|
1861
|
+
if (typeof component !== "number" || !Number.isFinite(component))
|
|
1862
|
+
return fail();
|
|
1863
|
+
const rounded = Math.fround(component === 0 ? 0 : component);
|
|
1864
|
+
if (!Number.isFinite(rounded))
|
|
1865
|
+
return fail();
|
|
1866
|
+
return rounded;
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1869
|
+
function encodeVectorBits(value, dimension) {
|
|
1870
|
+
const vector = encodeVector(value, dimension);
|
|
1871
|
+
const float = new Float32Array(1);
|
|
1872
|
+
const bits = new Uint32Array(float.buffer);
|
|
1873
|
+
return vector.map((component) => {
|
|
1874
|
+
float[0] = component;
|
|
1875
|
+
return bits[0];
|
|
1876
|
+
});
|
|
1877
|
+
}
|
|
1878
|
+
function decodeVectorBits(value, dimension) {
|
|
1879
|
+
if (!Array.isArray(value) || value.length !== dimension)
|
|
1880
|
+
return fail();
|
|
1881
|
+
const float = new Float32Array(1);
|
|
1882
|
+
const bits = new Uint32Array(float.buffer);
|
|
1883
|
+
return value.map((componentBits) => {
|
|
1884
|
+
bits[0] = boundedInteger(componentBits, 0, 4_294_967_295);
|
|
1885
|
+
const component = float[0];
|
|
1886
|
+
if (!Number.isFinite(component))
|
|
1887
|
+
return fail();
|
|
1888
|
+
return component === 0 ? 0 : component;
|
|
1889
|
+
});
|
|
1890
|
+
}
|
|
1891
|
+
function decodeVector(value, dimension) {
|
|
1892
|
+
if (!Array.isArray(value) || value.length < 1 || value.length > 4_096
|
|
1893
|
+
|| (dimension !== undefined && value.length !== dimension))
|
|
1894
|
+
return fail();
|
|
1895
|
+
return value.map((component) => {
|
|
1896
|
+
if (typeof component !== "number" || !Number.isFinite(component))
|
|
1897
|
+
return fail();
|
|
1898
|
+
const rounded = Math.fround(component);
|
|
1899
|
+
if (!Number.isFinite(rounded) || rounded !== component)
|
|
1900
|
+
return fail();
|
|
1901
|
+
return component === 0 ? 0 : component;
|
|
1902
|
+
});
|
|
1903
|
+
}
|
|
1904
|
+
function decodeReactiveConsumerStatus(value) {
|
|
1905
|
+
const status = exactObject(value);
|
|
1906
|
+
return {
|
|
1907
|
+
revision: positiveBigInt(status.revision),
|
|
1908
|
+
checkpoint: expectBoundedString(status.checkpoint, 64),
|
|
1909
|
+
historyIncarnation: positiveBigInt(status.history_incarnation),
|
|
1910
|
+
liveLeases: nonnegativeNumber(status.live_leases),
|
|
1911
|
+
retries: nonnegativeNumber(status.retries),
|
|
1912
|
+
deadLetters: nonnegativeNumber(status.dead_letters),
|
|
1913
|
+
};
|
|
1914
|
+
}
|
|
1915
|
+
function decodeEventMutationResult(value) {
|
|
1916
|
+
const result = expectSymbol(exactObject(value).result);
|
|
1917
|
+
if (result === "applied" || result === "state_changed" || result === "not_found"
|
|
1918
|
+
|| result === "outstanding_lease" || result === "stale_lease" || result === "lease_expired") {
|
|
1919
|
+
return result;
|
|
1920
|
+
}
|
|
1921
|
+
throw new Error("invalid RiffDB consumer mutation result");
|
|
1922
|
+
}
|
|
1923
|
+
function decodeLiveValue(value) {
|
|
1924
|
+
const result = exactObject(value);
|
|
1925
|
+
const output = { outcome: expectSymbol(result.outcome) };
|
|
1926
|
+
for (const rawField of expectArray(result.fields)) {
|
|
1927
|
+
const field = exactObject(rawField);
|
|
1928
|
+
const name = expectSymbol(field.name);
|
|
1929
|
+
const records = expectArray(field.records).map((rawRecord) => {
|
|
1930
|
+
const record = exactObject(rawRecord);
|
|
1931
|
+
return Object.fromEntries(expectArray(record.fields).map((rawValue) => {
|
|
1932
|
+
const item = exactObject(rawValue);
|
|
1933
|
+
return [expectSymbol(item.name), decodeTagged(item.value)];
|
|
1934
|
+
}));
|
|
1935
|
+
});
|
|
1936
|
+
const cardinality = boundedInteger(field.cardinality, 1, 3);
|
|
1937
|
+
if ((cardinality === 1 && records.length !== 1) || (cardinality === 2 && records.length > 1)) {
|
|
1938
|
+
throw new Error("invalid RiffDB live query cardinality");
|
|
1939
|
+
}
|
|
1940
|
+
output[name] = cardinality === 3 ? records : (records[0] ?? null);
|
|
1941
|
+
}
|
|
1942
|
+
return output;
|
|
1943
|
+
}
|
|
1944
|
+
function decodeLivePatchOperation(value) {
|
|
1945
|
+
const operation = exactObject(value);
|
|
1946
|
+
const type = expectSymbol(operation.type);
|
|
1947
|
+
if (type === "insert" || type === "replace") {
|
|
1948
|
+
return {
|
|
1949
|
+
type,
|
|
1950
|
+
index: boundedInteger(operation.index, 0, 65_535),
|
|
1951
|
+
record: decodeLiveRecord(operation.record),
|
|
1952
|
+
};
|
|
1953
|
+
}
|
|
1954
|
+
if (type === "remove") {
|
|
1955
|
+
return {
|
|
1956
|
+
type,
|
|
1957
|
+
index: boundedInteger(operation.index, 0, 65_535),
|
|
1958
|
+
key: decodeLiveValueRecord(operation.key),
|
|
1959
|
+
};
|
|
1960
|
+
}
|
|
1961
|
+
if (type === "move") {
|
|
1962
|
+
return {
|
|
1963
|
+
type,
|
|
1964
|
+
from: boundedInteger(operation.from, 0, 65_535),
|
|
1965
|
+
to: boundedInteger(operation.to, 0, 65_535),
|
|
1966
|
+
key: decodeLiveValueRecord(operation.key),
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
throw new Error("invalid RiffDB live patch operation");
|
|
1970
|
+
}
|
|
1971
|
+
function decodeLiveRecord(value) {
|
|
1972
|
+
const record = exactObject(value);
|
|
1973
|
+
expectSymbol(record.entity);
|
|
1974
|
+
return decodeLiveValueRecord(record.fields);
|
|
1975
|
+
}
|
|
1976
|
+
function decodeLiveValueRecord(value) {
|
|
1977
|
+
const record = exactObject(value);
|
|
1978
|
+
return Object.fromEntries(Object.entries(record).map(([name, field]) => {
|
|
1979
|
+
expectSymbol(name);
|
|
1980
|
+
return [name, decodeTagged(field)];
|
|
1981
|
+
}));
|
|
1982
|
+
}
|
|
1983
|
+
function liveTerminalFrontier(value) {
|
|
1984
|
+
if (value === null || value === undefined)
|
|
1985
|
+
return {};
|
|
1986
|
+
const frontier = exactObject(value);
|
|
1987
|
+
return {
|
|
1988
|
+
lastApplicationHead: nonnegativeBigInt(frontier.application_head),
|
|
1989
|
+
historyIncarnation: positiveBigInt(frontier.history_incarnation),
|
|
1990
|
+
};
|
|
1991
|
+
}
|
|
1992
|
+
function expectSymbol(value) {
|
|
1993
|
+
if (typeof value !== "string" || !SYMBOL.test(value))
|
|
1994
|
+
return fail();
|
|
1995
|
+
return value;
|
|
1996
|
+
}
|
|
1997
|
+
function expectHash(value) {
|
|
1998
|
+
if (typeof value !== "string" || !HASH.test(value))
|
|
1999
|
+
return fail();
|
|
2000
|
+
return value;
|
|
2001
|
+
}
|
|
2002
|
+
function expectDecimal(value) {
|
|
2003
|
+
if (typeof value !== "string" || !/^-?[0-9]+$/.test(value))
|
|
2004
|
+
return fail();
|
|
2005
|
+
return value;
|
|
2006
|
+
}
|
|
2007
|
+
function expectBoundedString(value, maximum) {
|
|
2008
|
+
if (typeof value !== "string" || value.length > maximum)
|
|
2009
|
+
return fail();
|
|
2010
|
+
return value;
|
|
2011
|
+
}
|
|
2012
|
+
function expectCurrency(value) {
|
|
2013
|
+
if (typeof value !== "string" || !/^[A-Z]{3}$/.test(value))
|
|
2014
|
+
return fail();
|
|
2015
|
+
return value;
|
|
2016
|
+
}
|
|
2017
|
+
function signedTwosComplement(value) {
|
|
2018
|
+
for (let width = 1; width <= 16; width += 1) {
|
|
2019
|
+
const bits = BigInt(width * 8);
|
|
2020
|
+
const minimum = -(1n << (bits - 1n));
|
|
2021
|
+
const maximum = (1n << (bits - 1n)) - 1n;
|
|
2022
|
+
if (value < minimum || value > maximum)
|
|
2023
|
+
continue;
|
|
2024
|
+
let encoded = value < 0n ? (1n << bits) + value : value;
|
|
2025
|
+
const bytes = new Uint8Array(width);
|
|
2026
|
+
for (let index = width - 1; index >= 0; index -= 1) {
|
|
2027
|
+
bytes[index] = Number(encoded & 0xffn);
|
|
2028
|
+
encoded >>= 8n;
|
|
2029
|
+
}
|
|
2030
|
+
return bytes;
|
|
2031
|
+
}
|
|
2032
|
+
return fail();
|
|
2033
|
+
}
|
|
2034
|
+
function positiveNumber(value) {
|
|
2035
|
+
const number = typeof value === "string" ? Number(expectDecimal(value)) : value;
|
|
2036
|
+
if (!Number.isSafeInteger(number) || number < 1)
|
|
2037
|
+
return fail();
|
|
2038
|
+
return number;
|
|
2039
|
+
}
|
|
2040
|
+
function nonnegativeNumber(value) {
|
|
2041
|
+
if (!Number.isSafeInteger(value) || value < 0)
|
|
2042
|
+
return fail();
|
|
2043
|
+
return value;
|
|
2044
|
+
}
|
|
2045
|
+
function positiveBigInt(value) {
|
|
2046
|
+
const number = BigInt(expectDecimal(value));
|
|
2047
|
+
if (number < 1n)
|
|
2048
|
+
return fail();
|
|
2049
|
+
return number;
|
|
2050
|
+
}
|
|
2051
|
+
function fail() {
|
|
2052
|
+
throw new Error("invalid RiffDB application response");
|
|
2053
|
+
}
|
|
2054
|
+
export { DRIVER_ERROR_REGISTRY_HASH, DRIVER_PROTOCOL_VERSION, DRIVER_VALUE_REGISTRY_HASH, DriverApplicationError, DriverApplicationTransport, } from "./driver.js";
|