@laserdata/laser-sdk 0.1.0 → 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/README.md +65 -5
- package/dist/bridges/a2a.d.ts +1 -1
- package/dist/bridges/a2a.js +1 -1
- package/dist/client/capabilities.d.ts +9 -1
- package/dist/client/capabilities.d.ts.map +1 -1
- package/dist/client/capabilities.js +48 -20
- package/dist/client/capabilities.js.map +1 -1
- package/dist/client/laser.d.ts +8 -0
- package/dist/client/laser.d.ts.map +1 -1
- package/dist/client/laser.js +41 -9
- package/dist/client/laser.js.map +1 -1
- package/dist/full.d.ts +1 -0
- package/dist/full.d.ts.map +1 -1
- package/dist/full.js +1 -0
- package/dist/full.js.map +1 -1
- package/dist/iggy/apache-iggy.d.ts.map +1 -1
- package/dist/iggy/apache-iggy.js +1 -6
- package/dist/iggy/apache-iggy.js.map +1 -1
- package/dist/index.d.ts +8 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/managed/destinations.d.ts +19 -0
- package/dist/managed/destinations.d.ts.map +1 -0
- package/dist/managed/destinations.js +90 -0
- package/dist/managed/destinations.js.map +1 -0
- package/dist/managed/query.d.ts +26 -19
- package/dist/managed/query.d.ts.map +1 -1
- package/dist/managed/query.js +97 -55
- package/dist/managed/query.js.map +1 -1
- package/dist/stream/publish.d.ts +3 -0
- package/dist/stream/publish.d.ts.map +1 -1
- package/dist/stream/publish.js +25 -0
- package/dist/stream/publish.js.map +1 -1
- package/dist/stream/record.d.ts +4 -1
- package/dist/stream/record.d.ts.map +1 -1
- package/dist/stream/record.js +34 -3
- package/dist/stream/record.js.map +1 -1
- package/dist/wire/arrow.d.ts +39 -0
- package/dist/wire/arrow.d.ts.map +1 -0
- package/dist/wire/arrow.js +133 -0
- package/dist/wire/arrow.js.map +1 -0
- package/dist/wire/cbor.d.ts +9 -1
- package/dist/wire/cbor.d.ts.map +1 -1
- package/dist/wire/cbor.js +45 -2
- package/dist/wire/cbor.js.map +1 -1
- package/dist/wire/checkpoint.d.ts +386 -0
- package/dist/wire/checkpoint.d.ts.map +1 -0
- package/dist/wire/checkpoint.js +1373 -0
- package/dist/wire/checkpoint.js.map +1 -0
- package/dist/wire/codes.d.ts +8 -0
- package/dist/wire/codes.d.ts.map +1 -1
- package/dist/wire/codes.js +9 -0
- package/dist/wire/codes.js.map +1 -1
- package/dist/wire/commands.d.ts +11 -3
- package/dist/wire/commands.d.ts.map +1 -1
- package/dist/wire/commands.js +69 -3
- package/dist/wire/commands.js.map +1 -1
- package/dist/wire/control.d.ts +3 -12
- package/dist/wire/control.d.ts.map +1 -1
- package/dist/wire/control.js +10 -43
- package/dist/wire/control.js.map +1 -1
- package/dist/wire/destination.d.ts +86 -0
- package/dist/wire/destination.d.ts.map +1 -0
- package/dist/wire/destination.js +251 -0
- package/dist/wire/destination.js.map +1 -0
- package/dist/wire/headers.d.ts +1 -0
- package/dist/wire/headers.d.ts.map +1 -1
- package/dist/wire/headers.js +1 -0
- package/dist/wire/headers.js.map +1 -1
- package/dist/wire/hello.d.ts +82 -11
- package/dist/wire/hello.d.ts.map +1 -1
- package/dist/wire/hello.js +291 -73
- package/dist/wire/hello.js.map +1 -1
- package/dist/wire/http.d.ts +152 -1
- package/dist/wire/http.d.ts.map +1 -1
- package/dist/wire/http.js +487 -8
- package/dist/wire/http.js.map +1 -1
- package/dist/wire/ids.d.ts +60 -0
- package/dist/wire/ids.d.ts.map +1 -1
- package/dist/wire/ids.js +143 -0
- package/dist/wire/ids.js.map +1 -1
- package/dist/wire/index.d.ts +5 -0
- package/dist/wire/index.d.ts.map +1 -1
- package/dist/wire/index.js +5 -0
- package/dist/wire/index.js.map +1 -1
- package/dist/wire/limits.d.ts +7 -0
- package/dist/wire/limits.d.ts.map +1 -1
- package/dist/wire/limits.js +7 -0
- package/dist/wire/limits.js.map +1 -1
- package/dist/wire/query.d.ts +192 -97
- package/dist/wire/query.d.ts.map +1 -1
- package/dist/wire/query.js +968 -443
- package/dist/wire/query.js.map +1 -1
- package/dist/wire/result.d.ts +6 -0
- package/dist/wire/result.d.ts.map +1 -1
- package/dist/wire/result.js +19 -2
- package/dist/wire/result.js.map +1 -1
- package/dist/wire/schema.d.ts +121 -0
- package/dist/wire/schema.d.ts.map +1 -0
- package/dist/wire/schema.js +778 -0
- package/dist/wire/schema.js.map +1 -0
- package/dist/wire/source.d.ts +35 -0
- package/dist/wire/source.d.ts.map +1 -0
- package/dist/wire/source.js +105 -0
- package/dist/wire/source.js.map +1 -0
- package/package.json +6 -6
|
@@ -0,0 +1,1373 @@
|
|
|
1
|
+
import { CodecError, InvalidError } from "../client/errors.js";
|
|
2
|
+
import { decodeOne, encodeNamed, expectMap, expectString, field, singleVariantTag } from "./cbor.js";
|
|
3
|
+
import { CHECKPOINT_OP_VERSION } from "./codes.js";
|
|
4
|
+
import { decodeBackendBinding, decodeMaterializationDestination, decodeProjectionRef, decodeQueryRoute, encodeBackendBinding, encodeMaterializationDestination, encodeProjectionRef, encodeQueryRoute, validateMaterializationDestination, validateQueryRoute } from "./destination.js";
|
|
5
|
+
import { CheckpointOwnerId, CheckpointRequestId, DestinationId, PreparedAttemptId, QueryRouteId } from "./ids.js";
|
|
6
|
+
import { MAX_PAGE_SIZE } from "./limits.js";
|
|
7
|
+
import { MAX_LOGICAL_SCHEMA_FIELDS, decodeLogicalSchemaRef, decodeTypedValue, encodeLogicalSchemaRef, encodeTypedValue, validateTypedValue } from "./schema.js";
|
|
8
|
+
import { decodeSourceIncarnation, encodeSourceIncarnation, validateSourceIncarnation } from "./source.js";
|
|
9
|
+
export const MAX_CHECKPOINT_PARTITIONS = 65_536;
|
|
10
|
+
export const MAX_ATTEMPT_OBJECTS = 100_000;
|
|
11
|
+
export const MAX_CREDENTIAL_GENERATIONS = 32;
|
|
12
|
+
export const MAX_MANIFEST_IDENTITY_BYTES = 4_096;
|
|
13
|
+
export const MAX_CHECKPOINT_ERROR_BYTES = 4_096;
|
|
14
|
+
export const MAX_REPAIR_DETAIL_BYTES = 4_096;
|
|
15
|
+
export const MAX_CHECKPOINT_LEASE_DURATION_MICROS = 300000000n;
|
|
16
|
+
export const SUPERVISOR_ASSERTION_VERSION = 1;
|
|
17
|
+
export const MAX_SUPERVISOR_ASSERTION_TTL_MICROS = 300000000n;
|
|
18
|
+
export const SUPERVISOR_ASSERTION_KEY_ID_BYTES = 8;
|
|
19
|
+
export const SUPERVISOR_ASSERTION_SIGNATURE_BYTES = 64;
|
|
20
|
+
const DESTINATION_BLOCK_CODES = [
|
|
21
|
+
"decode",
|
|
22
|
+
"schema",
|
|
23
|
+
"projection",
|
|
24
|
+
"value",
|
|
25
|
+
"size",
|
|
26
|
+
"retention_gap",
|
|
27
|
+
"prepared_attempt",
|
|
28
|
+
"backend_generation",
|
|
29
|
+
"backend_unavailable",
|
|
30
|
+
"table_identity",
|
|
31
|
+
"catalog_outcome_unknown",
|
|
32
|
+
"source_incarnation",
|
|
33
|
+
"authorization"
|
|
34
|
+
];
|
|
35
|
+
function mapOf(entries) {
|
|
36
|
+
return new Map(entries);
|
|
37
|
+
}
|
|
38
|
+
function optional(map, key, value, encode = (item) => item) {
|
|
39
|
+
if (value !== undefined)
|
|
40
|
+
map.set(key, encode(value));
|
|
41
|
+
}
|
|
42
|
+
function commonMutation(map, value) {
|
|
43
|
+
map.set("destination_id", value.destinationId.toBytes());
|
|
44
|
+
map.set("destination_generation", value.destinationGeneration);
|
|
45
|
+
optional(map, "expected_checkpoint_revision", value.expectedCheckpointRevision);
|
|
46
|
+
optional(map, "expected_definition_revision", value.expectedDefinitionRevision);
|
|
47
|
+
}
|
|
48
|
+
function decodeLease(map, context) {
|
|
49
|
+
return {
|
|
50
|
+
owner: CheckpointOwnerId.fromBytes(field.requiredBytes(map, "owner", context)),
|
|
51
|
+
epoch: field.requiredU64(map, "epoch", context),
|
|
52
|
+
sequence: field.requiredU64(map, "sequence", context),
|
|
53
|
+
deadlineMicros: field.requiredU64(map, "deadline_micros", context)
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function encodeLease(value) {
|
|
57
|
+
return mapOf([
|
|
58
|
+
["owner", value.owner.toBytes()],
|
|
59
|
+
["epoch", value.epoch],
|
|
60
|
+
["sequence", value.sequence],
|
|
61
|
+
["deadline_micros", value.deadlineMicros]
|
|
62
|
+
]);
|
|
63
|
+
}
|
|
64
|
+
function encodePartition(value) {
|
|
65
|
+
return mapOf([
|
|
66
|
+
["incarnation", encodeSourceIncarnation(value.incarnation)],
|
|
67
|
+
["started_at_offset", value.startedAtOffset],
|
|
68
|
+
["next_offset", value.nextOffset],
|
|
69
|
+
["lifecycle", value.lifecycle]
|
|
70
|
+
]);
|
|
71
|
+
}
|
|
72
|
+
function decodePartition(map, context) {
|
|
73
|
+
return {
|
|
74
|
+
incarnation: decodeSourceIncarnation(field.requiredMap(map, "incarnation", context), context),
|
|
75
|
+
startedAtOffset: field.requiredU64(map, "started_at_offset", context),
|
|
76
|
+
nextOffset: field.requiredU64(map, "next_offset", context),
|
|
77
|
+
lifecycle: enumWord(map, "lifecycle", context, ["active", "removed", "recreated"])
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function encodeRange(value) {
|
|
81
|
+
return mapOf([
|
|
82
|
+
["incarnation", encodeSourceIncarnation(value.incarnation)],
|
|
83
|
+
["start", value.start],
|
|
84
|
+
["end_exclusive", value.endExclusive]
|
|
85
|
+
]);
|
|
86
|
+
}
|
|
87
|
+
function encodeRequirement(value) {
|
|
88
|
+
const map = new Map([["kind", value.kind]]);
|
|
89
|
+
switch (value.kind) {
|
|
90
|
+
case "assert_table_uuid":
|
|
91
|
+
map.set("table_uuid", value.tableUuid);
|
|
92
|
+
break;
|
|
93
|
+
case "assert_metadata_identity":
|
|
94
|
+
map.set("identity", value.identity);
|
|
95
|
+
break;
|
|
96
|
+
case "assert_current_snapshot":
|
|
97
|
+
optional(map, "snapshot_id", value.snapshotId);
|
|
98
|
+
break;
|
|
99
|
+
case "assert_current_schema":
|
|
100
|
+
map.set("schema_id", value.schemaId);
|
|
101
|
+
break;
|
|
102
|
+
case "assert_default_partition_spec":
|
|
103
|
+
map.set("partition_spec_id", value.partitionSpecId);
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
return map;
|
|
107
|
+
}
|
|
108
|
+
function encodeTable(value) {
|
|
109
|
+
const map = mapOf([
|
|
110
|
+
["table_uuid", value.tableUuid],
|
|
111
|
+
["base_metadata_identity", value.baseMetadataIdentity]
|
|
112
|
+
]);
|
|
113
|
+
optional(map, "base_snapshot_id", value.baseSnapshotId);
|
|
114
|
+
map.set("schema_id", value.schemaId);
|
|
115
|
+
map.set("partition_spec_id", value.partitionSpecId);
|
|
116
|
+
map.set("commit_requirements", value.commitRequirements.map(encodeRequirement));
|
|
117
|
+
return map;
|
|
118
|
+
}
|
|
119
|
+
function encodeCompletion(value) {
|
|
120
|
+
return mapOf([
|
|
121
|
+
["id", value.id.toBytes()],
|
|
122
|
+
["table_uuid", value.tableUuid],
|
|
123
|
+
["snapshot_id", value.snapshotId],
|
|
124
|
+
["manifest_digest", value.manifestDigest],
|
|
125
|
+
["resulting_boundary_digest", value.resultingBoundaryDigest],
|
|
126
|
+
["ranges", value.ranges.map(encodeRange)],
|
|
127
|
+
["completion_revision", value.completionRevision]
|
|
128
|
+
]);
|
|
129
|
+
}
|
|
130
|
+
export function encodeRetentionGap(value) {
|
|
131
|
+
return mapOf([
|
|
132
|
+
["incarnation", encodeSourceIncarnation(value.incarnation)],
|
|
133
|
+
["required_next_offset", value.requiredNextOffset],
|
|
134
|
+
["retained_start", value.retainedStart]
|
|
135
|
+
]);
|
|
136
|
+
}
|
|
137
|
+
export function encodeDestinationBlock(value) {
|
|
138
|
+
const map = mapOf([
|
|
139
|
+
["code", value.code],
|
|
140
|
+
["message", value.message]
|
|
141
|
+
]);
|
|
142
|
+
optional(map, "incarnation", value.incarnation, encodeSourceIncarnation);
|
|
143
|
+
optional(map, "offset", value.offset);
|
|
144
|
+
optional(map, "row_ordinal", value.rowOrdinal);
|
|
145
|
+
return map;
|
|
146
|
+
}
|
|
147
|
+
function encodeRepair(value) {
|
|
148
|
+
return mapOf([
|
|
149
|
+
["action", value.action],
|
|
150
|
+
["detail", value.detail]
|
|
151
|
+
]);
|
|
152
|
+
}
|
|
153
|
+
function encodePreparedAttempt(value) {
|
|
154
|
+
return mapOf([
|
|
155
|
+
["id", value.id.toBytes()],
|
|
156
|
+
["destination_id", value.destinationId.toBytes()],
|
|
157
|
+
["destination_generation", value.destinationGeneration],
|
|
158
|
+
["backend", encodeBackendBinding(value.backend)],
|
|
159
|
+
["owner", value.owner.toBytes()],
|
|
160
|
+
["epoch", value.epoch],
|
|
161
|
+
["created_at_checkpoint_revision", value.createdAtCheckpointRevision],
|
|
162
|
+
["table", encodeTable(value.table)],
|
|
163
|
+
["schema_fingerprint", value.schemaFingerprint],
|
|
164
|
+
["projection", encodeProjectionRef(value.projection)],
|
|
165
|
+
["ranges", value.ranges.map(encodeRange)],
|
|
166
|
+
["resulting_boundary", value.resultingBoundary.map(encodePartition)],
|
|
167
|
+
["resulting_boundary_digest", value.resultingBoundaryDigest],
|
|
168
|
+
["manifest_identity", value.manifestIdentity],
|
|
169
|
+
["manifest_digest", value.manifestDigest],
|
|
170
|
+
[
|
|
171
|
+
"objects",
|
|
172
|
+
value.objects.map((object) => mapOf([
|
|
173
|
+
["identity", object.identity],
|
|
174
|
+
["size_bytes", object.sizeBytes],
|
|
175
|
+
["row_count", object.rowCount],
|
|
176
|
+
["sha256", object.sha256],
|
|
177
|
+
[
|
|
178
|
+
"columns",
|
|
179
|
+
object.columns.map((column) => {
|
|
180
|
+
const map = mapOf([
|
|
181
|
+
["field_id", column.fieldId],
|
|
182
|
+
["value_count", column.valueCount],
|
|
183
|
+
["null_count", column.nullCount],
|
|
184
|
+
["nan_count", column.nanCount]
|
|
185
|
+
]);
|
|
186
|
+
optional(map, "lower_bound", column.lowerBound, encodeTypedValue);
|
|
187
|
+
optional(map, "upper_bound", column.upperBound, encodeTypedValue);
|
|
188
|
+
return map;
|
|
189
|
+
})
|
|
190
|
+
]
|
|
191
|
+
]))
|
|
192
|
+
],
|
|
193
|
+
[
|
|
194
|
+
"credential_generations",
|
|
195
|
+
value.credentialGenerations.map((item) => mapOf([
|
|
196
|
+
["role", item.role],
|
|
197
|
+
["generation", item.generation]
|
|
198
|
+
]))
|
|
199
|
+
]
|
|
200
|
+
]);
|
|
201
|
+
}
|
|
202
|
+
export function encodePreparedAttemptSummary(value) {
|
|
203
|
+
return mapOf([
|
|
204
|
+
["id", value.id.toBytes()],
|
|
205
|
+
["owner", value.owner.toBytes()],
|
|
206
|
+
["epoch", value.epoch],
|
|
207
|
+
["table", encodeTable(value.table)],
|
|
208
|
+
["schema_fingerprint", value.schemaFingerprint],
|
|
209
|
+
["projection", encodeProjectionRef(value.projection)],
|
|
210
|
+
["manifest_identity", value.manifestIdentity],
|
|
211
|
+
["manifest_digest", value.manifestDigest],
|
|
212
|
+
["resulting_boundary_digest", value.resultingBoundaryDigest],
|
|
213
|
+
["ranges", value.ranges.map(encodeRange)],
|
|
214
|
+
["object_count", value.objectCount],
|
|
215
|
+
[
|
|
216
|
+
"credential_generations",
|
|
217
|
+
value.credentialGenerations.map((item) => mapOf([
|
|
218
|
+
["role", item.role],
|
|
219
|
+
["generation", item.generation]
|
|
220
|
+
]))
|
|
221
|
+
]
|
|
222
|
+
]);
|
|
223
|
+
}
|
|
224
|
+
export function encodePublicCheckpointMutation(value) {
|
|
225
|
+
const map = new Map([["kind", value.kind]]);
|
|
226
|
+
switch (value.kind) {
|
|
227
|
+
case "register_destination":
|
|
228
|
+
map.set("destination", encodeMaterializationDestination(value.destination));
|
|
229
|
+
break;
|
|
230
|
+
case "register_query_route":
|
|
231
|
+
map.set("route", encodeQueryRoute(value.route));
|
|
232
|
+
break;
|
|
233
|
+
case "remove_query_route":
|
|
234
|
+
map.set("route_id", value.routeId.toBytes());
|
|
235
|
+
map.set("route_generation", value.routeGeneration);
|
|
236
|
+
map.set("expected_definition_revision", value.expectedDefinitionRevision);
|
|
237
|
+
break;
|
|
238
|
+
case "bind_table":
|
|
239
|
+
commonMutation(map, value);
|
|
240
|
+
map.set("table_uuid", value.tableUuid);
|
|
241
|
+
break;
|
|
242
|
+
case "set_desired_state":
|
|
243
|
+
commonMutation(map, value);
|
|
244
|
+
map.set("desired_state", value.desiredState);
|
|
245
|
+
break;
|
|
246
|
+
case "add_partition":
|
|
247
|
+
case "observe_partition_lifecycle":
|
|
248
|
+
commonMutation(map, value);
|
|
249
|
+
map.set("partition_id", value.partitionId);
|
|
250
|
+
break;
|
|
251
|
+
case "acquire_lease":
|
|
252
|
+
case "takeover_lease":
|
|
253
|
+
commonMutation(map, value);
|
|
254
|
+
map.set("owner", value.owner.toBytes());
|
|
255
|
+
map.set("expected_lease_sequence", value.expectedLeaseSequence);
|
|
256
|
+
map.set("lease_duration_micros", value.leaseDurationMicros);
|
|
257
|
+
break;
|
|
258
|
+
case "renew_lease":
|
|
259
|
+
commonMutation(map, value);
|
|
260
|
+
map.set("owner", value.owner.toBytes());
|
|
261
|
+
map.set("epoch", value.epoch);
|
|
262
|
+
map.set("expected_lease_sequence", value.expectedLeaseSequence);
|
|
263
|
+
map.set("lease_duration_micros", value.leaseDurationMicros);
|
|
264
|
+
break;
|
|
265
|
+
case "prepare":
|
|
266
|
+
map.set("expected_checkpoint_revision", value.expectedCheckpointRevision);
|
|
267
|
+
map.set("attempt", encodePreparedAttempt(value.attempt));
|
|
268
|
+
break;
|
|
269
|
+
case "complete":
|
|
270
|
+
map.set("destination_id", value.destinationId.toBytes());
|
|
271
|
+
map.set("destination_generation", value.destinationGeneration);
|
|
272
|
+
map.set("owner", value.owner.toBytes());
|
|
273
|
+
map.set("epoch", value.epoch);
|
|
274
|
+
map.set("expected_checkpoint_revision", value.expectedCheckpointRevision);
|
|
275
|
+
map.set("completion", encodeCompletion(value.completion));
|
|
276
|
+
break;
|
|
277
|
+
case "record_block":
|
|
278
|
+
commonMutation(map, value);
|
|
279
|
+
map.set("block", encodeDestinationBlock(value.block));
|
|
280
|
+
break;
|
|
281
|
+
case "clear_block":
|
|
282
|
+
commonMutation(map, value);
|
|
283
|
+
map.set("expected_code", value.expectedCode);
|
|
284
|
+
break;
|
|
285
|
+
case "record_retention_gap":
|
|
286
|
+
commonMutation(map, value);
|
|
287
|
+
map.set("gap", encodeRetentionGap(value.gap));
|
|
288
|
+
break;
|
|
289
|
+
case "accept_retention_gap":
|
|
290
|
+
commonMutation(map, value);
|
|
291
|
+
map.set("next_offset", value.nextOffset);
|
|
292
|
+
break;
|
|
293
|
+
case "supersede_generation":
|
|
294
|
+
map.set("expected_definition_revision", value.expectedDefinitionRevision);
|
|
295
|
+
map.set("replacement", encodeMaterializationDestination(value.replacement));
|
|
296
|
+
break;
|
|
297
|
+
case "record_repair":
|
|
298
|
+
commonMutation(map, value);
|
|
299
|
+
map.set("repair", encodeRepair(value.repair));
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
return map;
|
|
303
|
+
}
|
|
304
|
+
function decodePreparedAttempt(map, context) {
|
|
305
|
+
return {
|
|
306
|
+
id: PreparedAttemptId.fromBytes(field.requiredBytes(map, "id", context)),
|
|
307
|
+
destinationId: DestinationId.fromBytes(field.requiredBytes(map, "destination_id", context)),
|
|
308
|
+
destinationGeneration: field.requiredU64(map, "destination_generation", context),
|
|
309
|
+
backend: decodeBackendBinding(field.requiredMap(map, "backend", context), `${context}.backend`),
|
|
310
|
+
owner: CheckpointOwnerId.fromBytes(field.requiredBytes(map, "owner", context)),
|
|
311
|
+
epoch: field.requiredU64(map, "epoch", context),
|
|
312
|
+
createdAtCheckpointRevision: field.requiredU64(map, "created_at_checkpoint_revision", context),
|
|
313
|
+
table: decodeTable(field.requiredMap(map, "table", context), `${context}.table`),
|
|
314
|
+
schemaFingerprint: field.requiredBytes(map, "schema_fingerprint", context),
|
|
315
|
+
projection: decodeProjectionRef(field.requiredMap(map, "projection", context), `${context}.projection`),
|
|
316
|
+
ranges: field.requiredArray(map, "ranges", context, (item, index) => decodeRange(expectMap(item, `${context}.ranges[${String(index)}]`), `${context}.ranges`)),
|
|
317
|
+
resultingBoundary: field.requiredArray(map, "resulting_boundary", context, (item, index) => decodePartition(expectMap(item, `${context}.resulting_boundary[${String(index)}]`), `${context}.resulting_boundary`)),
|
|
318
|
+
resultingBoundaryDigest: field.requiredBytes(map, "resulting_boundary_digest", context),
|
|
319
|
+
manifestIdentity: field.requiredString(map, "manifest_identity", context),
|
|
320
|
+
manifestDigest: field.requiredBytes(map, "manifest_digest", context),
|
|
321
|
+
objects: field.requiredArray(map, "objects", context, (item, index) => decodeAttemptObject(expectMap(item, `${context}.objects[${String(index)}]`), `${context}.objects[${String(index)}]`)),
|
|
322
|
+
credentialGenerations: field.requiredArray(map, "credential_generations", context, (item, index) => decodeCredential(expectMap(item, `${context}.credential_generations[${String(index)}]`), `${context}.credential_generations[${String(index)}]`))
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function decodeAttemptObject(map, context) {
|
|
326
|
+
return {
|
|
327
|
+
identity: field.requiredString(map, "identity", context),
|
|
328
|
+
sizeBytes: field.requiredU64(map, "size_bytes", context),
|
|
329
|
+
rowCount: field.requiredU64(map, "row_count", context),
|
|
330
|
+
sha256: field.requiredBytes(map, "sha256", context),
|
|
331
|
+
columns: field.requiredArray(map, "columns", context, (item, index) => decodeAttemptColumnMetrics(expectMap(item, `${context}.columns[${String(index)}]`), `${context}.columns[${String(index)}]`))
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function decodeAttemptColumnMetrics(map, context) {
|
|
335
|
+
const lowerBound = map.get("lower_bound");
|
|
336
|
+
const upperBound = map.get("upper_bound");
|
|
337
|
+
return {
|
|
338
|
+
fieldId: field.requiredU32(map, "field_id", context),
|
|
339
|
+
valueCount: field.requiredU64(map, "value_count", context),
|
|
340
|
+
nullCount: field.requiredU64(map, "null_count", context),
|
|
341
|
+
nanCount: field.requiredU64(map, "nan_count", context),
|
|
342
|
+
...(lowerBound === undefined
|
|
343
|
+
? {}
|
|
344
|
+
: { lowerBound: decodeTypedValue(lowerBound, `${context}.lower_bound`) }),
|
|
345
|
+
...(upperBound === undefined
|
|
346
|
+
? {}
|
|
347
|
+
: { upperBound: decodeTypedValue(upperBound, `${context}.upper_bound`) })
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
function decodeMutationDestination(map, context) {
|
|
351
|
+
return {
|
|
352
|
+
destinationId: DestinationId.fromBytes(field.requiredBytes(map, "destination_id", context)),
|
|
353
|
+
destinationGeneration: field.requiredU64(map, "destination_generation", context)
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
export function decodePublicCheckpointMutation(map, context) {
|
|
357
|
+
const kind = field.requiredString(map, "kind", context);
|
|
358
|
+
const destination = () => decodeMutationDestination(map, context);
|
|
359
|
+
const checkpointRevision = () => field.requiredU64(map, "expected_checkpoint_revision", context);
|
|
360
|
+
const definitionRevision = () => field.requiredU64(map, "expected_definition_revision", context);
|
|
361
|
+
switch (kind) {
|
|
362
|
+
case "register_destination":
|
|
363
|
+
return {
|
|
364
|
+
kind,
|
|
365
|
+
destination: decodeMaterializationDestination(field.requiredMap(map, "destination", context), `${context}.destination`)
|
|
366
|
+
};
|
|
367
|
+
case "register_query_route":
|
|
368
|
+
return {
|
|
369
|
+
kind,
|
|
370
|
+
route: decodeQueryRoute(field.requiredMap(map, "route", context), `${context}.route`)
|
|
371
|
+
};
|
|
372
|
+
case "remove_query_route":
|
|
373
|
+
return {
|
|
374
|
+
kind,
|
|
375
|
+
routeId: QueryRouteId.fromBytes(field.requiredBytes(map, "route_id", context)),
|
|
376
|
+
routeGeneration: field.requiredU64(map, "route_generation", context),
|
|
377
|
+
expectedDefinitionRevision: definitionRevision()
|
|
378
|
+
};
|
|
379
|
+
case "bind_table":
|
|
380
|
+
return {
|
|
381
|
+
kind,
|
|
382
|
+
...destination(),
|
|
383
|
+
expectedDefinitionRevision: definitionRevision(),
|
|
384
|
+
tableUuid: field.requiredBytes(map, "table_uuid", context)
|
|
385
|
+
};
|
|
386
|
+
case "set_desired_state":
|
|
387
|
+
return {
|
|
388
|
+
kind,
|
|
389
|
+
...destination(),
|
|
390
|
+
expectedDefinitionRevision: definitionRevision(),
|
|
391
|
+
desiredState: enumWord(map, "desired_state", context, ["disabled", "enabled"])
|
|
392
|
+
};
|
|
393
|
+
case "add_partition":
|
|
394
|
+
case "observe_partition_lifecycle":
|
|
395
|
+
return {
|
|
396
|
+
kind,
|
|
397
|
+
...destination(),
|
|
398
|
+
expectedCheckpointRevision: checkpointRevision(),
|
|
399
|
+
partitionId: field.requiredU32(map, "partition_id", context)
|
|
400
|
+
};
|
|
401
|
+
case "acquire_lease":
|
|
402
|
+
case "takeover_lease":
|
|
403
|
+
return {
|
|
404
|
+
kind,
|
|
405
|
+
...destination(),
|
|
406
|
+
owner: CheckpointOwnerId.fromBytes(field.requiredBytes(map, "owner", context)),
|
|
407
|
+
expectedLeaseSequence: field.requiredU64(map, "expected_lease_sequence", context),
|
|
408
|
+
leaseDurationMicros: field.requiredU64(map, "lease_duration_micros", context)
|
|
409
|
+
};
|
|
410
|
+
case "renew_lease":
|
|
411
|
+
return {
|
|
412
|
+
kind,
|
|
413
|
+
...destination(),
|
|
414
|
+
owner: CheckpointOwnerId.fromBytes(field.requiredBytes(map, "owner", context)),
|
|
415
|
+
epoch: field.requiredU64(map, "epoch", context),
|
|
416
|
+
expectedLeaseSequence: field.requiredU64(map, "expected_lease_sequence", context),
|
|
417
|
+
leaseDurationMicros: field.requiredU64(map, "lease_duration_micros", context)
|
|
418
|
+
};
|
|
419
|
+
case "prepare":
|
|
420
|
+
return {
|
|
421
|
+
kind,
|
|
422
|
+
expectedCheckpointRevision: checkpointRevision(),
|
|
423
|
+
attempt: decodePreparedAttempt(field.requiredMap(map, "attempt", context), `${context}.attempt`)
|
|
424
|
+
};
|
|
425
|
+
case "complete":
|
|
426
|
+
return {
|
|
427
|
+
kind,
|
|
428
|
+
...destination(),
|
|
429
|
+
owner: CheckpointOwnerId.fromBytes(field.requiredBytes(map, "owner", context)),
|
|
430
|
+
epoch: field.requiredU64(map, "epoch", context),
|
|
431
|
+
expectedCheckpointRevision: checkpointRevision(),
|
|
432
|
+
completion: decodeCompletion(field.requiredMap(map, "completion", context), `${context}.completion`)
|
|
433
|
+
};
|
|
434
|
+
case "record_block":
|
|
435
|
+
return {
|
|
436
|
+
kind,
|
|
437
|
+
...destination(),
|
|
438
|
+
expectedCheckpointRevision: checkpointRevision(),
|
|
439
|
+
block: decodeDestinationBlock(field.requiredMap(map, "block", context), `${context}.block`)
|
|
440
|
+
};
|
|
441
|
+
case "clear_block":
|
|
442
|
+
return {
|
|
443
|
+
kind,
|
|
444
|
+
...destination(),
|
|
445
|
+
expectedCheckpointRevision: checkpointRevision(),
|
|
446
|
+
expectedCode: enumWord(map, "expected_code", context, DESTINATION_BLOCK_CODES)
|
|
447
|
+
};
|
|
448
|
+
case "record_retention_gap":
|
|
449
|
+
return {
|
|
450
|
+
kind,
|
|
451
|
+
...destination(),
|
|
452
|
+
expectedCheckpointRevision: checkpointRevision(),
|
|
453
|
+
gap: decodeRetentionGap(field.requiredMap(map, "gap", context), `${context}.gap`)
|
|
454
|
+
};
|
|
455
|
+
case "accept_retention_gap":
|
|
456
|
+
return {
|
|
457
|
+
kind,
|
|
458
|
+
...destination(),
|
|
459
|
+
expectedCheckpointRevision: checkpointRevision(),
|
|
460
|
+
nextOffset: field.requiredU64(map, "next_offset", context)
|
|
461
|
+
};
|
|
462
|
+
case "supersede_generation":
|
|
463
|
+
return {
|
|
464
|
+
kind,
|
|
465
|
+
expectedDefinitionRevision: definitionRevision(),
|
|
466
|
+
replacement: decodeMaterializationDestination(field.requiredMap(map, "replacement", context), `${context}.replacement`)
|
|
467
|
+
};
|
|
468
|
+
case "record_repair":
|
|
469
|
+
return {
|
|
470
|
+
kind,
|
|
471
|
+
...destination(),
|
|
472
|
+
expectedCheckpointRevision: checkpointRevision(),
|
|
473
|
+
repair: decodeRepair(field.requiredMap(map, "repair", context), `${context}.repair`)
|
|
474
|
+
};
|
|
475
|
+
default:
|
|
476
|
+
throw new CodecError(`unknown public checkpoint mutation \`${kind}\``, context, "kind");
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
function encodeAssertion(value) {
|
|
480
|
+
const claims = mapOf([
|
|
481
|
+
["v", value.claims.v],
|
|
482
|
+
["request_id", value.claims.requestId.toBytes()],
|
|
483
|
+
["deployment_id", value.claims.deploymentId],
|
|
484
|
+
["cloud_user_id", value.claims.cloudUserId],
|
|
485
|
+
["action", value.claims.action],
|
|
486
|
+
["destination_id", value.claims.destinationId.toBytes()],
|
|
487
|
+
["destination_generation", value.claims.destinationGeneration]
|
|
488
|
+
]);
|
|
489
|
+
optional(claims, "expected_revision", value.claims.expectedRevision);
|
|
490
|
+
claims.set("issued_at_micros", value.claims.issuedAtMicros);
|
|
491
|
+
claims.set("expires_at_micros", value.claims.expiresAtMicros);
|
|
492
|
+
return mapOf([
|
|
493
|
+
["claims", claims],
|
|
494
|
+
["key_id", value.keyId],
|
|
495
|
+
["signature", value.signature]
|
|
496
|
+
]);
|
|
497
|
+
}
|
|
498
|
+
function decodeAssertion(map, context) {
|
|
499
|
+
const claims = field.requiredMap(map, "claims", context);
|
|
500
|
+
const expectedRevision = field.optionalU64(claims, "expected_revision", `${context}.claims`);
|
|
501
|
+
return {
|
|
502
|
+
claims: {
|
|
503
|
+
v: field.requiredU32(claims, "v", `${context}.claims`),
|
|
504
|
+
requestId: CheckpointRequestId.fromBytes(field.requiredBytes(claims, "request_id", `${context}.claims`)),
|
|
505
|
+
deploymentId: field.requiredU32(claims, "deployment_id", `${context}.claims`),
|
|
506
|
+
cloudUserId: field.requiredU32(claims, "cloud_user_id", `${context}.claims`),
|
|
507
|
+
action: enumWord(claims, "action", `${context}.claims`, [
|
|
508
|
+
"accept_retention_gap",
|
|
509
|
+
"supersede_generation",
|
|
510
|
+
"record_repair"
|
|
511
|
+
]),
|
|
512
|
+
destinationId: DestinationId.fromBytes(field.requiredBytes(claims, "destination_id", `${context}.claims`)),
|
|
513
|
+
destinationGeneration: field.requiredU64(claims, "destination_generation", `${context}.claims`),
|
|
514
|
+
...(expectedRevision === undefined ? {} : { expectedRevision }),
|
|
515
|
+
issuedAtMicros: field.requiredU64(claims, "issued_at_micros", `${context}.claims`),
|
|
516
|
+
expiresAtMicros: field.requiredU64(claims, "expires_at_micros", `${context}.claims`)
|
|
517
|
+
},
|
|
518
|
+
keyId: field.requiredBytes(map, "key_id", context),
|
|
519
|
+
signature: field.requiredBytes(map, "signature", context)
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
export function encodeCheckpointRequest(value) {
|
|
523
|
+
const map = mapOf([
|
|
524
|
+
["v", value.v],
|
|
525
|
+
["request_id", value.requestId.toBytes()],
|
|
526
|
+
["expected_global_state_revision", value.expectedGlobalStateRevision],
|
|
527
|
+
["mutation", encodePublicCheckpointMutation(value.mutation)]
|
|
528
|
+
]);
|
|
529
|
+
optional(map, "supervisor_assertion", value.supervisorAssertion, encodeAssertion);
|
|
530
|
+
return map;
|
|
531
|
+
}
|
|
532
|
+
export function decodeCheckpointRequest(map, context) {
|
|
533
|
+
const supervisorAssertion = field.optionalMap(map, "supervisor_assertion", context);
|
|
534
|
+
return {
|
|
535
|
+
v: field.requiredU32(map, "v", context),
|
|
536
|
+
requestId: CheckpointRequestId.fromBytes(field.requiredBytes(map, "request_id", context)),
|
|
537
|
+
expectedGlobalStateRevision: field.requiredU64(map, "expected_global_state_revision", context),
|
|
538
|
+
mutation: decodePublicCheckpointMutation(field.requiredMap(map, "mutation", context), `${context}.mutation`),
|
|
539
|
+
...(supervisorAssertion === undefined
|
|
540
|
+
? {}
|
|
541
|
+
: {
|
|
542
|
+
supervisorAssertion: decodeAssertion(supervisorAssertion, `${context}.supervisor_assertion`)
|
|
543
|
+
})
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
export function decodeCheckpointRequestFrame(bytes) {
|
|
547
|
+
const context = "CheckpointRequestEnvelope";
|
|
548
|
+
const value = decodeCheckpointRequest(expectMap(decodeOne(bytes, context), context), context);
|
|
549
|
+
validateCheckpointRequest(value);
|
|
550
|
+
return value;
|
|
551
|
+
}
|
|
552
|
+
export function encodeCheckpointRequestFrame(value) {
|
|
553
|
+
validateCheckpointRequest(value);
|
|
554
|
+
return encodeNamed(encodeCheckpointRequest(value));
|
|
555
|
+
}
|
|
556
|
+
export function validateCheckpointRequest(value) {
|
|
557
|
+
if (value.v !== CHECKPOINT_OP_VERSION || value.requestId.asU128() === 0n)
|
|
558
|
+
throw new InvalidError("checkpoint request version or identity is invalid");
|
|
559
|
+
validatePublicCheckpointMutation(value.mutation);
|
|
560
|
+
validateSupervisorAssertionBinding(value);
|
|
561
|
+
}
|
|
562
|
+
export function validatePublicCheckpointMutation(value) {
|
|
563
|
+
switch (value.kind) {
|
|
564
|
+
case "register_destination":
|
|
565
|
+
validateMaterializationDestination(value.destination);
|
|
566
|
+
return;
|
|
567
|
+
case "register_query_route":
|
|
568
|
+
validateQueryRoute(value.route);
|
|
569
|
+
return;
|
|
570
|
+
case "remove_query_route":
|
|
571
|
+
if (value.routeId.asU128() === 0n ||
|
|
572
|
+
value.routeGeneration === 0n ||
|
|
573
|
+
value.expectedDefinitionRevision === 0n)
|
|
574
|
+
throw new InvalidError("query route identity, generation, and revision must be nonzero");
|
|
575
|
+
return;
|
|
576
|
+
case "bind_table":
|
|
577
|
+
validateDestinationRevision(value.destinationId, value.destinationGeneration, value.expectedDefinitionRevision, "definition");
|
|
578
|
+
fixedBytes(value.tableUuid, 16, "table UUID");
|
|
579
|
+
return;
|
|
580
|
+
case "set_desired_state":
|
|
581
|
+
validateDestinationRevision(value.destinationId, value.destinationGeneration, value.expectedDefinitionRevision, "definition");
|
|
582
|
+
return;
|
|
583
|
+
case "add_partition":
|
|
584
|
+
case "observe_partition_lifecycle":
|
|
585
|
+
case "clear_block":
|
|
586
|
+
case "accept_retention_gap":
|
|
587
|
+
validateDestinationRevision(value.destinationId, value.destinationGeneration, value.expectedCheckpointRevision, "checkpoint");
|
|
588
|
+
return;
|
|
589
|
+
case "acquire_lease":
|
|
590
|
+
case "takeover_lease":
|
|
591
|
+
validateLeaseRequest(value);
|
|
592
|
+
return;
|
|
593
|
+
case "renew_lease":
|
|
594
|
+
validateLeaseRequest(value);
|
|
595
|
+
if (value.epoch === 0n)
|
|
596
|
+
throw new InvalidError("lease epoch must be nonzero");
|
|
597
|
+
return;
|
|
598
|
+
case "prepare":
|
|
599
|
+
if (value.expectedCheckpointRevision === 0n)
|
|
600
|
+
throw new InvalidError("expected checkpoint revision must be nonzero");
|
|
601
|
+
validatePreparedAttempt(value.attempt);
|
|
602
|
+
return;
|
|
603
|
+
case "complete":
|
|
604
|
+
validateDestinationRevision(value.destinationId, value.destinationGeneration, value.expectedCheckpointRevision, "checkpoint");
|
|
605
|
+
if (value.owner.asU128() === 0n || value.epoch === 0n)
|
|
606
|
+
throw new InvalidError("completion owner and epoch must be nonzero");
|
|
607
|
+
validateCompletedAttempt(value.completion);
|
|
608
|
+
return;
|
|
609
|
+
case "record_block":
|
|
610
|
+
validateDestinationRevision(value.destinationId, value.destinationGeneration, value.expectedCheckpointRevision, "checkpoint");
|
|
611
|
+
validateDestinationBlock(value.block);
|
|
612
|
+
return;
|
|
613
|
+
case "record_retention_gap":
|
|
614
|
+
validateDestinationRevision(value.destinationId, value.destinationGeneration, value.expectedCheckpointRevision, "checkpoint");
|
|
615
|
+
validateRetentionGap(value.gap);
|
|
616
|
+
return;
|
|
617
|
+
case "supersede_generation":
|
|
618
|
+
if (value.expectedDefinitionRevision === 0n)
|
|
619
|
+
throw new InvalidError("expected definition revision must be nonzero");
|
|
620
|
+
validateMaterializationDestination(value.replacement);
|
|
621
|
+
return;
|
|
622
|
+
case "record_repair":
|
|
623
|
+
validateDestinationRevision(value.destinationId, value.destinationGeneration, value.expectedCheckpointRevision, "checkpoint");
|
|
624
|
+
validateRepairRecord(value.repair);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
function validateSupervisorAssertionBinding(value) {
|
|
628
|
+
let required;
|
|
629
|
+
switch (value.mutation.kind) {
|
|
630
|
+
case "accept_retention_gap":
|
|
631
|
+
required = [
|
|
632
|
+
"accept_retention_gap",
|
|
633
|
+
value.mutation.destinationId,
|
|
634
|
+
value.mutation.destinationGeneration,
|
|
635
|
+
value.mutation.expectedCheckpointRevision
|
|
636
|
+
];
|
|
637
|
+
break;
|
|
638
|
+
case "supersede_generation":
|
|
639
|
+
required = [
|
|
640
|
+
"supersede_generation",
|
|
641
|
+
value.mutation.replacement.id,
|
|
642
|
+
value.mutation.replacement.generation,
|
|
643
|
+
value.mutation.expectedDefinitionRevision
|
|
644
|
+
];
|
|
645
|
+
break;
|
|
646
|
+
case "record_repair":
|
|
647
|
+
required = [
|
|
648
|
+
"record_repair",
|
|
649
|
+
value.mutation.destinationId,
|
|
650
|
+
value.mutation.destinationGeneration,
|
|
651
|
+
value.mutation.expectedCheckpointRevision
|
|
652
|
+
];
|
|
653
|
+
break;
|
|
654
|
+
case "register_destination":
|
|
655
|
+
case "register_query_route":
|
|
656
|
+
case "remove_query_route":
|
|
657
|
+
case "bind_table":
|
|
658
|
+
case "set_desired_state":
|
|
659
|
+
case "add_partition":
|
|
660
|
+
case "observe_partition_lifecycle":
|
|
661
|
+
case "acquire_lease":
|
|
662
|
+
case "takeover_lease":
|
|
663
|
+
case "renew_lease":
|
|
664
|
+
case "prepare":
|
|
665
|
+
case "complete":
|
|
666
|
+
case "record_block":
|
|
667
|
+
case "clear_block":
|
|
668
|
+
case "record_retention_gap":
|
|
669
|
+
break;
|
|
670
|
+
}
|
|
671
|
+
if (required === undefined) {
|
|
672
|
+
if (value.supervisorAssertion !== undefined)
|
|
673
|
+
throw new InvalidError("supervisor assertion is not accepted for this checkpoint mutation");
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
const assertion = value.supervisorAssertion;
|
|
677
|
+
if (assertion === undefined)
|
|
678
|
+
throw new InvalidError("high-risk checkpoint mutation requires a supervisor assertion");
|
|
679
|
+
validateSupervisorAssertion(assertion);
|
|
680
|
+
const [action, destinationId, destinationGeneration, expectedRevision] = required;
|
|
681
|
+
if (assertion.claims.requestId.asU128() !== value.requestId.asU128() ||
|
|
682
|
+
assertion.claims.action !== action ||
|
|
683
|
+
assertion.claims.destinationId.asU128() !== destinationId.asU128() ||
|
|
684
|
+
assertion.claims.destinationGeneration !== destinationGeneration ||
|
|
685
|
+
assertion.claims.expectedRevision !== expectedRevision)
|
|
686
|
+
throw new InvalidError("supervisor assertion is not bound to this request, action, and destination");
|
|
687
|
+
}
|
|
688
|
+
export function validateSupervisorAssertion(value) {
|
|
689
|
+
const claims = value.claims;
|
|
690
|
+
if (claims.v !== SUPERVISOR_ASSERTION_VERSION)
|
|
691
|
+
throw new InvalidError("supervisor assertion version is invalid");
|
|
692
|
+
if (claims.requestId.asU128() === 0n ||
|
|
693
|
+
claims.deploymentId === 0 ||
|
|
694
|
+
claims.cloudUserId === 0 ||
|
|
695
|
+
claims.destinationId.asU128() === 0n ||
|
|
696
|
+
claims.destinationGeneration === 0n)
|
|
697
|
+
throw new InvalidError("supervisor assertion identities must be nonzero");
|
|
698
|
+
if (claims.issuedAtMicros === 0n ||
|
|
699
|
+
claims.expiresAtMicros <= claims.issuedAtMicros ||
|
|
700
|
+
claims.expiresAtMicros - claims.issuedAtMicros > MAX_SUPERVISOR_ASSERTION_TTL_MICROS)
|
|
701
|
+
throw new InvalidError("supervisor assertion validity window is invalid");
|
|
702
|
+
fixedBytes(value.keyId, SUPERVISOR_ASSERTION_KEY_ID_BYTES, "supervisor assertion key id");
|
|
703
|
+
fixedBytes(value.signature, SUPERVISOR_ASSERTION_SIGNATURE_BYTES, "supervisor assertion signature");
|
|
704
|
+
}
|
|
705
|
+
function validateDestinationRevision(destinationId, destinationGeneration, revision, revisionKind) {
|
|
706
|
+
if (destinationId.asU128() === 0n || destinationGeneration === 0n)
|
|
707
|
+
throw new InvalidError("checkpoint destination identity and generation must be nonzero");
|
|
708
|
+
if (revision === 0n)
|
|
709
|
+
throw new InvalidError(`expected ${revisionKind} revision must be nonzero`);
|
|
710
|
+
}
|
|
711
|
+
function validateLeaseRequest(value) {
|
|
712
|
+
if (value.destinationId.asU128() === 0n ||
|
|
713
|
+
value.destinationGeneration === 0n ||
|
|
714
|
+
value.owner.asU128() === 0n ||
|
|
715
|
+
value.leaseDurationMicros === 0n ||
|
|
716
|
+
value.leaseDurationMicros > MAX_CHECKPOINT_LEASE_DURATION_MICROS)
|
|
717
|
+
throw new InvalidError("lease destination, owner, and generation must be nonzero, and duration must be within the configured cap");
|
|
718
|
+
}
|
|
719
|
+
function validateBackendBinding(value) {
|
|
720
|
+
if (value.resourceId.asU128() === 0n || value.generation === 0n)
|
|
721
|
+
throw new InvalidError("backend binding identity and generation must be nonzero");
|
|
722
|
+
}
|
|
723
|
+
function validateProjectionRef(value) {
|
|
724
|
+
if (value.id.length === 0 || value.version === 0)
|
|
725
|
+
throw new InvalidError("projection identity and version must be nonzero");
|
|
726
|
+
}
|
|
727
|
+
function validateLogicalSchemaRef(value) {
|
|
728
|
+
if (value.id.asU128() === 0n || value.version === 0)
|
|
729
|
+
throw new InvalidError("logical schema identity and version must be nonzero");
|
|
730
|
+
fixedBytes(value.fingerprint, 32, "schema fingerprint");
|
|
731
|
+
}
|
|
732
|
+
function validatePartition(value) {
|
|
733
|
+
validateSourceIncarnation(value.incarnation);
|
|
734
|
+
if (value.startedAtOffset > value.nextOffset)
|
|
735
|
+
throw new InvalidError("partition start offset must not exceed its next offset");
|
|
736
|
+
}
|
|
737
|
+
function validateRange(value) {
|
|
738
|
+
validateSourceIncarnation(value.incarnation);
|
|
739
|
+
if (value.start >= value.endExclusive)
|
|
740
|
+
throw new InvalidError("source range is empty or inverted");
|
|
741
|
+
}
|
|
742
|
+
function validateRanges(value) {
|
|
743
|
+
if (value.length < 1 || value.length > MAX_CHECKPOINT_PARTITIONS)
|
|
744
|
+
throw new InvalidError("source range partition count is invalid");
|
|
745
|
+
let previousPartition;
|
|
746
|
+
let namespace;
|
|
747
|
+
for (const range of value) {
|
|
748
|
+
validateRange(range);
|
|
749
|
+
const current = sourceNamespace(range.incarnation);
|
|
750
|
+
if (namespace !== undefined && namespace !== current)
|
|
751
|
+
throw new InvalidError("source ranges must share one cluster, stream, and topic");
|
|
752
|
+
if (previousPartition !== undefined && previousPartition >= range.incarnation.partitionId)
|
|
753
|
+
throw new InvalidError("source ranges must be ordered by ascending partition id");
|
|
754
|
+
namespace = current;
|
|
755
|
+
previousPartition = range.incarnation.partitionId;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
function validatePreparedTable(value) {
|
|
759
|
+
fixedBytes(value.tableUuid, 16, "table UUID");
|
|
760
|
+
validateObjectIdentity(value.baseMetadataIdentity);
|
|
761
|
+
if ((value.baseSnapshotId !== undefined && value.baseSnapshotId <= 0n) ||
|
|
762
|
+
!Number.isInteger(value.schemaId) ||
|
|
763
|
+
value.schemaId < 0 ||
|
|
764
|
+
!Number.isInteger(value.partitionSpecId) ||
|
|
765
|
+
value.partitionSpecId < 0)
|
|
766
|
+
throw new InvalidError("prepared table snapshot, schema, or partition spec is invalid");
|
|
767
|
+
const requirements = value.commitRequirements;
|
|
768
|
+
if (requirements.length !== 5)
|
|
769
|
+
throw new InvalidError("Iceberg commit requirements are not canonical");
|
|
770
|
+
const [table, metadata, snapshot, schema, spec] = requirements;
|
|
771
|
+
if (table?.kind !== "assert_table_uuid" ||
|
|
772
|
+
!equalBytes(table.tableUuid, value.tableUuid) ||
|
|
773
|
+
metadata?.kind !== "assert_metadata_identity" ||
|
|
774
|
+
metadata.identity !== value.baseMetadataIdentity ||
|
|
775
|
+
snapshot?.kind !== "assert_current_snapshot" ||
|
|
776
|
+
snapshot.snapshotId !== value.baseSnapshotId ||
|
|
777
|
+
schema?.kind !== "assert_current_schema" ||
|
|
778
|
+
schema.schemaId !== value.schemaId ||
|
|
779
|
+
spec?.kind !== "assert_default_partition_spec" ||
|
|
780
|
+
spec.partitionSpecId !== value.partitionSpecId)
|
|
781
|
+
throw new InvalidError("Iceberg commit requirements must contain the exact frozen table preconditions in canonical order");
|
|
782
|
+
}
|
|
783
|
+
function validateAttemptObject(value) {
|
|
784
|
+
validateObjectIdentity(value.identity);
|
|
785
|
+
if (value.sizeBytes === 0n || value.rowCount === 0n)
|
|
786
|
+
throw new InvalidError("attempt object size and row count must be nonzero");
|
|
787
|
+
fixedBytes(value.sha256, 32, "attempt object digest");
|
|
788
|
+
if (value.columns.length > MAX_LOGICAL_SCHEMA_FIELDS)
|
|
789
|
+
throw new InvalidError("attempt object column metric count exceeds the schema field cap");
|
|
790
|
+
const fields = new Set();
|
|
791
|
+
for (const column of value.columns) {
|
|
792
|
+
validateAttemptColumnMetrics(column);
|
|
793
|
+
if (fields.has(column.fieldId))
|
|
794
|
+
throw new InvalidError("attempt object repeats a column metric");
|
|
795
|
+
fields.add(column.fieldId);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
function validateAttemptColumnMetrics(value) {
|
|
799
|
+
if (value.fieldId === 0 ||
|
|
800
|
+
value.nullCount > value.valueCount ||
|
|
801
|
+
value.nanCount > value.valueCount - value.nullCount)
|
|
802
|
+
throw new InvalidError("attempt column metrics are invalid");
|
|
803
|
+
if (value.lowerBound !== undefined)
|
|
804
|
+
validateTypedValue(value.lowerBound);
|
|
805
|
+
if (value.upperBound !== undefined)
|
|
806
|
+
validateTypedValue(value.upperBound);
|
|
807
|
+
if ((value.lowerBound === undefined) !== (value.upperBound === undefined))
|
|
808
|
+
throw new InvalidError("attempt column bounds must be both present or both absent");
|
|
809
|
+
}
|
|
810
|
+
function validateCredentialGenerations(value) {
|
|
811
|
+
if (value.length < 1 || value.length > MAX_CREDENTIAL_GENERATIONS)
|
|
812
|
+
throw new InvalidError("credential generation role count is invalid");
|
|
813
|
+
const roles = new Set();
|
|
814
|
+
for (const credential of value) {
|
|
815
|
+
const bytes = new TextEncoder().encode(credential.role).length;
|
|
816
|
+
if (bytes < 1 ||
|
|
817
|
+
bytes > 64 ||
|
|
818
|
+
!/^[a-z0-9_-]+$/.test(credential.role) ||
|
|
819
|
+
credential.generation === 0n)
|
|
820
|
+
throw new InvalidError("credential role or generation is invalid");
|
|
821
|
+
if (roles.has(credential.role))
|
|
822
|
+
throw new InvalidError("credential role appears more than once");
|
|
823
|
+
roles.add(credential.role);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
function validatePreparedAttempt(value) {
|
|
827
|
+
if (value.id.asU128() === 0n ||
|
|
828
|
+
value.destinationId.asU128() === 0n ||
|
|
829
|
+
value.destinationGeneration === 0n)
|
|
830
|
+
throw new InvalidError("prepared attempt identity and destination must be nonzero");
|
|
831
|
+
validateBackendBinding(value.backend);
|
|
832
|
+
if (value.owner.asU128() === 0n || value.epoch === 0n)
|
|
833
|
+
throw new InvalidError("prepared attempt owner and epoch must be nonzero");
|
|
834
|
+
if (value.createdAtCheckpointRevision === 0n)
|
|
835
|
+
throw new InvalidError("prepared attempt creation revision must be nonzero");
|
|
836
|
+
validatePreparedTable(value.table);
|
|
837
|
+
fixedBytes(value.schemaFingerprint, 32, "schema fingerprint");
|
|
838
|
+
validateProjectionRef(value.projection);
|
|
839
|
+
fixedBytes(value.resultingBoundaryDigest, 32, "resulting boundary digest");
|
|
840
|
+
fixedBytes(value.manifestDigest, 32, "manifest digest");
|
|
841
|
+
validateRanges(value.ranges);
|
|
842
|
+
if (value.resultingBoundary.length !== value.ranges.length)
|
|
843
|
+
throw new InvalidError("prepared attempt boundary must cover every source range exactly once");
|
|
844
|
+
for (const [index, partition] of value.resultingBoundary.entries()) {
|
|
845
|
+
validatePartition(partition);
|
|
846
|
+
const range = value.ranges[index];
|
|
847
|
+
if (range === undefined ||
|
|
848
|
+
!equalIncarnation(partition.incarnation, range.incarnation) ||
|
|
849
|
+
partition.nextOffset !== range.endExclusive ||
|
|
850
|
+
partition.lifecycle !== "active")
|
|
851
|
+
throw new InvalidError("prepared attempt boundary does not match its source ranges");
|
|
852
|
+
}
|
|
853
|
+
validateObjectIdentity(value.manifestIdentity);
|
|
854
|
+
if (value.objects.length < 1 || value.objects.length > MAX_ATTEMPT_OBJECTS)
|
|
855
|
+
throw new InvalidError("prepared attempt object count is invalid");
|
|
856
|
+
const identities = new Set();
|
|
857
|
+
for (const object of value.objects) {
|
|
858
|
+
validateAttemptObject(object);
|
|
859
|
+
if (identities.has(object.identity))
|
|
860
|
+
throw new InvalidError("prepared attempt repeats an object identity");
|
|
861
|
+
identities.add(object.identity);
|
|
862
|
+
}
|
|
863
|
+
validateCredentialGenerations(value.credentialGenerations);
|
|
864
|
+
}
|
|
865
|
+
function validatePreparedAttemptSummary(value) {
|
|
866
|
+
if (value.id.asU128() === 0n || value.owner.asU128() === 0n || value.epoch === 0n)
|
|
867
|
+
throw new InvalidError("prepared attempt summary identity must be nonzero");
|
|
868
|
+
validatePreparedTable(value.table);
|
|
869
|
+
fixedBytes(value.schemaFingerprint, 32, "schema fingerprint");
|
|
870
|
+
validateProjectionRef(value.projection);
|
|
871
|
+
validateObjectIdentity(value.manifestIdentity);
|
|
872
|
+
fixedBytes(value.manifestDigest, 32, "manifest digest");
|
|
873
|
+
fixedBytes(value.resultingBoundaryDigest, 32, "resulting boundary digest");
|
|
874
|
+
validateRanges(value.ranges);
|
|
875
|
+
if (value.objectCount < 1 || value.objectCount > MAX_ATTEMPT_OBJECTS)
|
|
876
|
+
throw new InvalidError("prepared attempt summary object count is invalid");
|
|
877
|
+
validateCredentialGenerations(value.credentialGenerations);
|
|
878
|
+
}
|
|
879
|
+
function validateCompletedAttempt(value) {
|
|
880
|
+
if (value.id.asU128() === 0n)
|
|
881
|
+
throw new InvalidError("completed attempt id must be nonzero");
|
|
882
|
+
fixedBytes(value.tableUuid, 16, "completed attempt table UUID");
|
|
883
|
+
if (value.snapshotId <= 0n)
|
|
884
|
+
throw new InvalidError("completed attempt snapshot id must be positive");
|
|
885
|
+
fixedBytes(value.manifestDigest, 32, "manifest digest");
|
|
886
|
+
fixedBytes(value.resultingBoundaryDigest, 32, "resulting boundary digest");
|
|
887
|
+
if (value.completionRevision === 0n)
|
|
888
|
+
throw new InvalidError("completed attempt revision must be nonzero");
|
|
889
|
+
validateRanges(value.ranges);
|
|
890
|
+
}
|
|
891
|
+
function validateRetentionGap(value) {
|
|
892
|
+
validateSourceIncarnation(value.incarnation);
|
|
893
|
+
if (value.requiredNextOffset >= value.retainedStart)
|
|
894
|
+
throw new InvalidError("retention gap requires next offset below retained start");
|
|
895
|
+
}
|
|
896
|
+
function validateDestinationBlock(value) {
|
|
897
|
+
validateBoundedText("checkpoint error message", value.message, MAX_CHECKPOINT_ERROR_BYTES);
|
|
898
|
+
if (value.incarnation !== undefined)
|
|
899
|
+
validateSourceIncarnation(value.incarnation);
|
|
900
|
+
}
|
|
901
|
+
function validateRepairRecord(value) {
|
|
902
|
+
validateBoundedText("repair detail", value.detail, MAX_REPAIR_DETAIL_BYTES);
|
|
903
|
+
}
|
|
904
|
+
function validateLease(value) {
|
|
905
|
+
if (value.owner.asU128() === 0n ||
|
|
906
|
+
value.epoch === 0n ||
|
|
907
|
+
value.sequence === 0n ||
|
|
908
|
+
value.deadlineMicros === 0n)
|
|
909
|
+
throw new InvalidError("checkpoint lease identity and counters must be nonzero");
|
|
910
|
+
}
|
|
911
|
+
function validateObjectIdentity(value) {
|
|
912
|
+
const bytes = new TextEncoder().encode(value).length;
|
|
913
|
+
if (bytes < 1 || bytes > MAX_MANIFEST_IDENTITY_BYTES)
|
|
914
|
+
throw new InvalidError("object identity length is invalid");
|
|
915
|
+
if (value.includes("?") || value.includes("#") || value.includes("@") || value.includes("//"))
|
|
916
|
+
throw new InvalidError("object identity must be canonical, provider-relative, and secret-free");
|
|
917
|
+
}
|
|
918
|
+
function validateBoundedText(label, value, cap) {
|
|
919
|
+
const bytes = new TextEncoder().encode(value).length;
|
|
920
|
+
if (bytes < 1 || bytes > cap)
|
|
921
|
+
throw new InvalidError(`${label} length is invalid`);
|
|
922
|
+
}
|
|
923
|
+
function fixedBytes(value, expected, label) {
|
|
924
|
+
if (value.length !== expected)
|
|
925
|
+
throw new InvalidError(`${label} must contain ${String(expected)} bytes`);
|
|
926
|
+
}
|
|
927
|
+
function equalBytes(left, right) {
|
|
928
|
+
return left.length === right.length && left.every((byte, index) => byte === right[index]);
|
|
929
|
+
}
|
|
930
|
+
function sourceNamespace(value) {
|
|
931
|
+
return `${value.cluster.asU128().toString()}:${String(value.streamId)}:${String(value.topicId)}`;
|
|
932
|
+
}
|
|
933
|
+
function equalIncarnation(left, right) {
|
|
934
|
+
return (left.cluster.asU128() === right.cluster.asU128() &&
|
|
935
|
+
left.streamId === right.streamId &&
|
|
936
|
+
left.topicId === right.topicId &&
|
|
937
|
+
left.partitionId === right.partitionId &&
|
|
938
|
+
left.partitionCreatedRevision === right.partitionCreatedRevision);
|
|
939
|
+
}
|
|
940
|
+
function decodeError(value, context) {
|
|
941
|
+
const [tag, body] = singleVariantTag(value, context);
|
|
942
|
+
const kind = tag.replaceAll(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
|
|
943
|
+
if (kind === "invalid" || kind === "unavailable")
|
|
944
|
+
return { kind, message: expectString(body, context) };
|
|
945
|
+
if (kind === "not_found" || kind === "lease_lost" || kind === "unauthorized")
|
|
946
|
+
return { kind };
|
|
947
|
+
const map = expectMap(body, context);
|
|
948
|
+
if (kind === "conflict")
|
|
949
|
+
return { kind, observedRevision: field.requiredU64(map, "observed_revision", context) };
|
|
950
|
+
if (kind === "version")
|
|
951
|
+
return {
|
|
952
|
+
kind,
|
|
953
|
+
expected: field.requiredU32(map, "expected", context),
|
|
954
|
+
got: field.requiredU32(map, "got", context)
|
|
955
|
+
};
|
|
956
|
+
throw new CodecError(`unknown checkpoint error \`${tag}\``, context, "error");
|
|
957
|
+
}
|
|
958
|
+
function decodeMutationResult(map, context) {
|
|
959
|
+
const lease = field.optionalMap(map, "lease", context);
|
|
960
|
+
return {
|
|
961
|
+
requestId: CheckpointRequestId.fromBytes(field.requiredBytes(map, "request_id", context)),
|
|
962
|
+
destinationId: DestinationId.fromBytes(field.requiredBytes(map, "destination_id", context)),
|
|
963
|
+
destinationGeneration: field.requiredU64(map, "destination_generation", context),
|
|
964
|
+
globalStateRevision: field.requiredU64(map, "global_state_revision", context),
|
|
965
|
+
definitionRevision: field.requiredU64(map, "definition_revision", context),
|
|
966
|
+
checkpointRevision: field.requiredU64(map, "checkpoint_revision", context),
|
|
967
|
+
...(lease === undefined ? {} : { lease: decodeLease(lease, context) })
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
export function decodeCheckpointReply(bytes) {
|
|
971
|
+
const context = "CheckpointReply";
|
|
972
|
+
const [tag, body] = singleVariantTag(decodeOne(bytes, context), context);
|
|
973
|
+
if (tag === "Ok") {
|
|
974
|
+
const result = decodeMutationResult(expectMap(body, context), context);
|
|
975
|
+
validateCheckpointMutationResult(result);
|
|
976
|
+
return { kind: "ok", result };
|
|
977
|
+
}
|
|
978
|
+
if (tag === "Err")
|
|
979
|
+
return { kind: "err", error: decodeError(body, context) };
|
|
980
|
+
throw new CodecError(`unknown checkpoint reply \`${tag}\``, context, "reply");
|
|
981
|
+
}
|
|
982
|
+
export function decodeRetentionGap(map, context) {
|
|
983
|
+
return {
|
|
984
|
+
incarnation: decodeSourceIncarnation(field.requiredMap(map, "incarnation", context), context),
|
|
985
|
+
requiredNextOffset: field.requiredU64(map, "required_next_offset", context),
|
|
986
|
+
retainedStart: field.requiredU64(map, "retained_start", context)
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
export function decodeDestinationBlock(map, context) {
|
|
990
|
+
const incarnation = field.optionalMap(map, "incarnation", context);
|
|
991
|
+
const offset = field.optionalU64(map, "offset", context);
|
|
992
|
+
const rowOrdinal = field.optionalU32(map, "row_ordinal", context);
|
|
993
|
+
return {
|
|
994
|
+
code: enumWord(map, "code", context, DESTINATION_BLOCK_CODES),
|
|
995
|
+
message: field.requiredString(map, "message", context),
|
|
996
|
+
...(incarnation === undefined
|
|
997
|
+
? {}
|
|
998
|
+
: { incarnation: decodeSourceIncarnation(incarnation, context) }),
|
|
999
|
+
...(offset === undefined ? {} : { offset }),
|
|
1000
|
+
...(rowOrdinal === undefined ? {} : { rowOrdinal })
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
function decodeRange(map, context) {
|
|
1004
|
+
return {
|
|
1005
|
+
incarnation: decodeSourceIncarnation(field.requiredMap(map, "incarnation", context), context),
|
|
1006
|
+
start: field.requiredU64(map, "start", context),
|
|
1007
|
+
endExclusive: field.requiredU64(map, "end_exclusive", context)
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
function decodeRequirement(map, context) {
|
|
1011
|
+
const kind = enumWord(map, "kind", context, [
|
|
1012
|
+
"assert_table_uuid",
|
|
1013
|
+
"assert_metadata_identity",
|
|
1014
|
+
"assert_current_snapshot",
|
|
1015
|
+
"assert_current_schema",
|
|
1016
|
+
"assert_default_partition_spec"
|
|
1017
|
+
]);
|
|
1018
|
+
if (kind === "assert_table_uuid")
|
|
1019
|
+
return { kind, tableUuid: field.requiredBytes(map, "table_uuid", context) };
|
|
1020
|
+
if (kind === "assert_metadata_identity")
|
|
1021
|
+
return { kind, identity: field.requiredString(map, "identity", context) };
|
|
1022
|
+
if (kind === "assert_current_snapshot") {
|
|
1023
|
+
const snapshotId = field.optionalI64(map, "snapshot_id", context);
|
|
1024
|
+
return { kind, ...(snapshotId === undefined ? {} : { snapshotId }) };
|
|
1025
|
+
}
|
|
1026
|
+
if (kind === "assert_current_schema")
|
|
1027
|
+
return { kind, schemaId: field.requiredI32(map, "schema_id", context) };
|
|
1028
|
+
return { kind, partitionSpecId: field.requiredI32(map, "partition_spec_id", context) };
|
|
1029
|
+
}
|
|
1030
|
+
function decodeTable(map, context) {
|
|
1031
|
+
const baseSnapshotId = field.optionalI64(map, "base_snapshot_id", context);
|
|
1032
|
+
return {
|
|
1033
|
+
tableUuid: field.requiredBytes(map, "table_uuid", context),
|
|
1034
|
+
baseMetadataIdentity: field.requiredString(map, "base_metadata_identity", context),
|
|
1035
|
+
...(baseSnapshotId === undefined ? {} : { baseSnapshotId }),
|
|
1036
|
+
schemaId: field.requiredI32(map, "schema_id", context),
|
|
1037
|
+
partitionSpecId: field.requiredI32(map, "partition_spec_id", context),
|
|
1038
|
+
commitRequirements: field.requiredArray(map, "commit_requirements", context, (item, index) => decodeRequirement(expectMap(item, `${context}.commit_requirements[${String(index)}]`), `${context}.commit_requirements[${String(index)}]`))
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
function decodeCredential(map, context) {
|
|
1042
|
+
return {
|
|
1043
|
+
role: field.requiredString(map, "role", context),
|
|
1044
|
+
generation: field.requiredU64(map, "generation", context)
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
export function decodePreparedAttemptSummary(map, context) {
|
|
1048
|
+
return {
|
|
1049
|
+
id: PreparedAttemptId.fromBytes(field.requiredBytes(map, "id", context)),
|
|
1050
|
+
owner: CheckpointOwnerId.fromBytes(field.requiredBytes(map, "owner", context)),
|
|
1051
|
+
epoch: field.requiredU64(map, "epoch", context),
|
|
1052
|
+
table: decodeTable(field.requiredMap(map, "table", context), `${context}.table`),
|
|
1053
|
+
schemaFingerprint: field.requiredBytes(map, "schema_fingerprint", context),
|
|
1054
|
+
projection: decodeProjectionRef(field.requiredMap(map, "projection", context), `${context}.projection`),
|
|
1055
|
+
manifestIdentity: field.requiredString(map, "manifest_identity", context),
|
|
1056
|
+
manifestDigest: field.requiredBytes(map, "manifest_digest", context),
|
|
1057
|
+
resultingBoundaryDigest: field.requiredBytes(map, "resulting_boundary_digest", context),
|
|
1058
|
+
ranges: field.requiredArray(map, "ranges", context, (item, index) => decodeRange(expectMap(item, `${context}.ranges[${String(index)}]`), `${context}.ranges[${String(index)}]`)),
|
|
1059
|
+
objectCount: field.requiredU32(map, "object_count", context),
|
|
1060
|
+
credentialGenerations: field.requiredArray(map, "credential_generations", context, (item, index) => decodeCredential(expectMap(item, `${context}.credential_generations[${String(index)}]`), `${context}.credential_generations[${String(index)}]`))
|
|
1061
|
+
};
|
|
1062
|
+
}
|
|
1063
|
+
function decodeCompletion(map, context) {
|
|
1064
|
+
return {
|
|
1065
|
+
id: PreparedAttemptId.fromBytes(field.requiredBytes(map, "id", context)),
|
|
1066
|
+
tableUuid: field.requiredBytes(map, "table_uuid", context),
|
|
1067
|
+
snapshotId: field.requiredI64(map, "snapshot_id", context),
|
|
1068
|
+
manifestDigest: field.requiredBytes(map, "manifest_digest", context),
|
|
1069
|
+
resultingBoundaryDigest: field.requiredBytes(map, "resulting_boundary_digest", context),
|
|
1070
|
+
ranges: field.requiredArray(map, "ranges", context, (item, index) => decodeRange(expectMap(item, `${context}.ranges[${String(index)}]`), `${context}.ranges[${String(index)}]`)),
|
|
1071
|
+
completionRevision: field.requiredU64(map, "completion_revision", context)
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
function decodeRepair(map, context) {
|
|
1075
|
+
return {
|
|
1076
|
+
action: enumWord(map, "action", context, [
|
|
1077
|
+
"reconciled_prepared_attempt",
|
|
1078
|
+
"accepted_retention_gap",
|
|
1079
|
+
"cleared_retryable_block",
|
|
1080
|
+
"superseded_generation"
|
|
1081
|
+
]),
|
|
1082
|
+
detail: field.requiredString(map, "detail", context)
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
export function encodeDestinationCheckpointStatus(value) {
|
|
1086
|
+
const map = mapOf([
|
|
1087
|
+
["destination_id", value.destinationId.toBytes()],
|
|
1088
|
+
["destination_generation", value.destinationGeneration],
|
|
1089
|
+
["backend", encodeBackendBinding(value.backend)],
|
|
1090
|
+
["schema", encodeLogicalSchemaRef(value.schema)],
|
|
1091
|
+
["projection", encodeProjectionRef(value.projection)],
|
|
1092
|
+
["global_state_revision", value.globalStateRevision],
|
|
1093
|
+
["definition_revision", value.definitionRevision],
|
|
1094
|
+
["checkpoint_revision", value.checkpointRevision],
|
|
1095
|
+
["desired_state", value.desiredState],
|
|
1096
|
+
["effective_state", value.effectiveState]
|
|
1097
|
+
]);
|
|
1098
|
+
optional(map, "table_uuid", value.tableUuid);
|
|
1099
|
+
optional(map, "owner", value.owner, encodeLease);
|
|
1100
|
+
map.set("partitions", value.partitions.map(encodePartition));
|
|
1101
|
+
optional(map, "prepared_attempt", value.preparedAttempt, encodePreparedAttemptSummary);
|
|
1102
|
+
optional(map, "last_completion", value.lastCompletion, encodeCompletion);
|
|
1103
|
+
optional(map, "retention_gap", value.retentionGap, encodeRetentionGap);
|
|
1104
|
+
optional(map, "block", value.block, encodeDestinationBlock);
|
|
1105
|
+
optional(map, "last_repair", value.lastRepair, encodeRepair);
|
|
1106
|
+
map.set("consistency", value.consistency);
|
|
1107
|
+
return map;
|
|
1108
|
+
}
|
|
1109
|
+
export function decodeDestinationCheckpointStatus(map, context) {
|
|
1110
|
+
const tableUuid = field.optionalBytes(map, "table_uuid", context);
|
|
1111
|
+
const owner = field.optionalMap(map, "owner", context);
|
|
1112
|
+
const prepared = field.optionalMap(map, "prepared_attempt", context);
|
|
1113
|
+
const completion = field.optionalMap(map, "last_completion", context);
|
|
1114
|
+
const gap = field.optionalMap(map, "retention_gap", context);
|
|
1115
|
+
const block = field.optionalMap(map, "block", context);
|
|
1116
|
+
const repair = field.optionalMap(map, "last_repair", context);
|
|
1117
|
+
const value = {
|
|
1118
|
+
destinationId: DestinationId.fromBytes(field.requiredBytes(map, "destination_id", context)),
|
|
1119
|
+
destinationGeneration: field.requiredU64(map, "destination_generation", context),
|
|
1120
|
+
backend: decodeBackendBinding(field.requiredMap(map, "backend", context), context),
|
|
1121
|
+
schema: decodeLogicalSchemaRef(field.requiredMap(map, "schema", context), context),
|
|
1122
|
+
projection: decodeProjectionRef(field.requiredMap(map, "projection", context), context),
|
|
1123
|
+
globalStateRevision: field.requiredU64(map, "global_state_revision", context),
|
|
1124
|
+
definitionRevision: field.requiredU64(map, "definition_revision", context),
|
|
1125
|
+
checkpointRevision: field.requiredU64(map, "checkpoint_revision", context),
|
|
1126
|
+
desiredState: enumWord(map, "desired_state", context, ["disabled", "enabled"]),
|
|
1127
|
+
effectiveState: enumWord(map, "effective_state", context, [
|
|
1128
|
+
"disabled",
|
|
1129
|
+
"waiting_for_backend",
|
|
1130
|
+
"ready",
|
|
1131
|
+
"running",
|
|
1132
|
+
"blocked"
|
|
1133
|
+
]),
|
|
1134
|
+
...(tableUuid === undefined ? {} : { tableUuid }),
|
|
1135
|
+
...(owner === undefined ? {} : { owner: decodeLease(owner, context) }),
|
|
1136
|
+
partitions: field.requiredArray(map, "partitions", context, (item, index) => decodePartition(expectMap(item, `${context}.partitions[${String(index)}]`), context)),
|
|
1137
|
+
...(prepared === undefined
|
|
1138
|
+
? {}
|
|
1139
|
+
: {
|
|
1140
|
+
preparedAttempt: decodePreparedAttemptSummary(prepared, `${context}.prepared_attempt`)
|
|
1141
|
+
}),
|
|
1142
|
+
...(completion === undefined
|
|
1143
|
+
? {}
|
|
1144
|
+
: { lastCompletion: decodeCompletion(completion, `${context}.last_completion`) }),
|
|
1145
|
+
...(gap === undefined ? {} : { retentionGap: decodeRetentionGap(gap, context) }),
|
|
1146
|
+
...(block === undefined ? {} : { block: decodeDestinationBlock(block, context) }),
|
|
1147
|
+
...(repair === undefined ? {} : { lastRepair: decodeRepair(repair, `${context}.last_repair`) }),
|
|
1148
|
+
consistency: enumWord(map, "consistency", context, ["linearizable", "potentially_stale"])
|
|
1149
|
+
};
|
|
1150
|
+
validateDestinationCheckpointStatus(value);
|
|
1151
|
+
return value;
|
|
1152
|
+
}
|
|
1153
|
+
function decodeView(map, context) {
|
|
1154
|
+
return {
|
|
1155
|
+
destination: decodeMaterializationDestination(field.requiredMap(map, "destination", context), context),
|
|
1156
|
+
status: decodeDestinationCheckpointStatus(field.requiredMap(map, "status", context), context)
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
export function validateCheckpointMutationResult(value) {
|
|
1160
|
+
if (value.requestId.asU128() === 0n ||
|
|
1161
|
+
value.destinationId.asU128() === 0n ||
|
|
1162
|
+
value.destinationGeneration === 0n ||
|
|
1163
|
+
value.globalStateRevision === 0n ||
|
|
1164
|
+
value.definitionRevision === 0n)
|
|
1165
|
+
throw new InvalidError("checkpoint mutation result identity, generation, global revision, and definition revision must be nonzero");
|
|
1166
|
+
if (value.lease !== undefined)
|
|
1167
|
+
validateLease(value.lease);
|
|
1168
|
+
}
|
|
1169
|
+
export function validateDestinationCheckpointStatus(value) {
|
|
1170
|
+
if (value.destinationId.asU128() === 0n || value.destinationGeneration === 0n)
|
|
1171
|
+
throw new InvalidError("checkpoint status destination identity must be nonzero");
|
|
1172
|
+
validateBackendBinding(value.backend);
|
|
1173
|
+
validateLogicalSchemaRef(value.schema);
|
|
1174
|
+
validateProjectionRef(value.projection);
|
|
1175
|
+
if (value.globalStateRevision === 0n || value.definitionRevision === 0n)
|
|
1176
|
+
throw new InvalidError("checkpoint status global and definition revisions must be nonzero");
|
|
1177
|
+
if (value.partitions.length > MAX_CHECKPOINT_PARTITIONS)
|
|
1178
|
+
throw new InvalidError("checkpoint status partition count exceeds its bound");
|
|
1179
|
+
const partitions = new Set();
|
|
1180
|
+
let namespace;
|
|
1181
|
+
for (const partition of value.partitions) {
|
|
1182
|
+
validatePartition(partition);
|
|
1183
|
+
const current = sourceNamespace(partition.incarnation);
|
|
1184
|
+
if (namespace !== undefined && namespace !== current)
|
|
1185
|
+
throw new InvalidError("checkpoint partitions must share one cluster, stream, and topic");
|
|
1186
|
+
if (partitions.has(partition.incarnation.partitionId))
|
|
1187
|
+
throw new InvalidError("checkpoint status repeats a partition");
|
|
1188
|
+
namespace = current;
|
|
1189
|
+
partitions.add(partition.incarnation.partitionId);
|
|
1190
|
+
}
|
|
1191
|
+
if (value.owner !== undefined)
|
|
1192
|
+
validateLease(value.owner);
|
|
1193
|
+
if (value.tableUuid !== undefined)
|
|
1194
|
+
fixedBytes(value.tableUuid, 16, "table UUID");
|
|
1195
|
+
if (value.preparedAttempt !== undefined)
|
|
1196
|
+
validatePreparedAttemptSummary(value.preparedAttempt);
|
|
1197
|
+
if (value.lastCompletion !== undefined)
|
|
1198
|
+
validateCompletedAttempt(value.lastCompletion);
|
|
1199
|
+
if (value.retentionGap !== undefined)
|
|
1200
|
+
validateRetentionGap(value.retentionGap);
|
|
1201
|
+
if (value.block !== undefined)
|
|
1202
|
+
validateDestinationBlock(value.block);
|
|
1203
|
+
if (value.lastRepair !== undefined)
|
|
1204
|
+
validateRepairRecord(value.lastRepair);
|
|
1205
|
+
}
|
|
1206
|
+
export function validateDestinationCheckpointView(value) {
|
|
1207
|
+
validateMaterializationDestination(value.destination);
|
|
1208
|
+
validateDestinationCheckpointStatus(value.status);
|
|
1209
|
+
if (value.destination.id.asU128() !== value.status.destinationId.asU128() ||
|
|
1210
|
+
value.destination.generation !== value.status.destinationGeneration ||
|
|
1211
|
+
value.destination.definitionRevision !== value.status.definitionRevision ||
|
|
1212
|
+
value.destination.backend.resourceId.asU128() !== value.status.backend.resourceId.asU128() ||
|
|
1213
|
+
value.destination.backend.generation !== value.status.backend.generation ||
|
|
1214
|
+
value.destination.schema.id.asU128() !== value.status.schema.id.asU128() ||
|
|
1215
|
+
value.destination.schema.version !== value.status.schema.version ||
|
|
1216
|
+
!equalBytes(value.destination.schema.fingerprint, value.status.schema.fingerprint) ||
|
|
1217
|
+
value.destination.projection.id !== value.status.projection.id ||
|
|
1218
|
+
value.destination.projection.version !== value.status.projection.version)
|
|
1219
|
+
throw new InvalidError("destination declaration and checkpoint status do not describe the same generation");
|
|
1220
|
+
}
|
|
1221
|
+
export function validateCheckpointReadReply(value) {
|
|
1222
|
+
if (value.kind === "err")
|
|
1223
|
+
return;
|
|
1224
|
+
if (value.kind === "destination") {
|
|
1225
|
+
if (value.destination !== undefined)
|
|
1226
|
+
validateDestinationCheckpointView(value.destination);
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
if (value.kind === "destinations") {
|
|
1230
|
+
if (value.page.globalStateRevision === 0n || value.page.destinations.length > MAX_PAGE_SIZE)
|
|
1231
|
+
throw new InvalidError("destination page metadata is invalid");
|
|
1232
|
+
const ids = new Set();
|
|
1233
|
+
for (const destination of value.page.destinations) {
|
|
1234
|
+
validateDestinationCheckpointView(destination);
|
|
1235
|
+
const id = destination.destination.id.asU128().toString();
|
|
1236
|
+
if (destination.status.globalStateRevision > value.page.globalStateRevision || ids.has(id))
|
|
1237
|
+
throw new InvalidError("destination page contains duplicate or future state");
|
|
1238
|
+
ids.add(id);
|
|
1239
|
+
}
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
if (value.page.globalStateRevision === 0n || value.page.routes.length > MAX_PAGE_SIZE)
|
|
1243
|
+
throw new InvalidError("query route page metadata is invalid");
|
|
1244
|
+
const ids = new Set();
|
|
1245
|
+
for (const route of value.page.routes) {
|
|
1246
|
+
validateQueryRoute(route);
|
|
1247
|
+
const id = route.id.asU128().toString();
|
|
1248
|
+
if (ids.has(id))
|
|
1249
|
+
throw new InvalidError("query route page repeats a route id");
|
|
1250
|
+
ids.add(id);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
function validCheckpointReadReply(value) {
|
|
1254
|
+
validateCheckpointReadReply(value);
|
|
1255
|
+
return value;
|
|
1256
|
+
}
|
|
1257
|
+
export function decodeCheckpointReadReply(bytes) {
|
|
1258
|
+
const context = "CheckpointReadReply";
|
|
1259
|
+
const [tag, body] = singleVariantTag(decodeOne(bytes, context), context);
|
|
1260
|
+
if (tag === "Destination") {
|
|
1261
|
+
return validCheckpointReadReply(body === null
|
|
1262
|
+
? { kind: "destination" }
|
|
1263
|
+
: { kind: "destination", destination: decodeView(expectMap(body, context), context) });
|
|
1264
|
+
}
|
|
1265
|
+
if (tag === "Destinations") {
|
|
1266
|
+
const map = expectMap(body, context);
|
|
1267
|
+
const next = field.optionalBytes(map, "next_after", context);
|
|
1268
|
+
return validCheckpointReadReply({
|
|
1269
|
+
kind: "destinations",
|
|
1270
|
+
page: {
|
|
1271
|
+
destinations: field.requiredArray(map, "destinations", context, (item, index) => decodeView(expectMap(item, `${context}.destinations[${String(index)}]`), context)),
|
|
1272
|
+
...(next === undefined ? {} : { nextAfter: DestinationId.fromBytes(next) }),
|
|
1273
|
+
globalStateRevision: field.requiredU64(map, "global_state_revision", context),
|
|
1274
|
+
consistency: enumWord(map, "consistency", context, ["linearizable", "potentially_stale"])
|
|
1275
|
+
}
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
if (tag === "QueryRoutes") {
|
|
1279
|
+
const map = expectMap(body, context);
|
|
1280
|
+
const next = field.optionalBytes(map, "next_after", context);
|
|
1281
|
+
return validCheckpointReadReply({
|
|
1282
|
+
kind: "query_routes",
|
|
1283
|
+
page: {
|
|
1284
|
+
routes: field.requiredArray(map, "routes", context, (item, index) => decodeQueryRoute(expectMap(item, `${context}.routes[${String(index)}]`), context)),
|
|
1285
|
+
...(next === undefined ? {} : { nextAfter: QueryRouteId.fromBytes(next) }),
|
|
1286
|
+
globalStateRevision: field.requiredU64(map, "global_state_revision", context),
|
|
1287
|
+
consistency: enumWord(map, "consistency", context, ["linearizable", "potentially_stale"])
|
|
1288
|
+
}
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
if (tag === "Err")
|
|
1292
|
+
return { kind: "err", error: decodeError(body, context) };
|
|
1293
|
+
throw new CodecError(`unknown checkpoint read reply \`${tag}\``, context, "reply");
|
|
1294
|
+
}
|
|
1295
|
+
function enumWord(map, key, context, allowed) {
|
|
1296
|
+
const value = field.requiredString(map, key, context);
|
|
1297
|
+
if (!allowed.includes(value))
|
|
1298
|
+
throw new CodecError(`unknown ${key} \`${value}\``, context, key);
|
|
1299
|
+
return value;
|
|
1300
|
+
}
|
|
1301
|
+
export function encodeDestinationGetRequest(value) {
|
|
1302
|
+
validateDestinationGetRequest(value);
|
|
1303
|
+
return mapOf([
|
|
1304
|
+
["v", value.v],
|
|
1305
|
+
["destination_id", value.destinationId.toBytes()],
|
|
1306
|
+
["consistency", value.consistency]
|
|
1307
|
+
]);
|
|
1308
|
+
}
|
|
1309
|
+
export function encodeDestinationListRequest(value) {
|
|
1310
|
+
validateDestinationListRequest(value);
|
|
1311
|
+
const filter = new Map();
|
|
1312
|
+
optional(filter, "source_stream", value.filter.sourceStream);
|
|
1313
|
+
optional(filter, "source_topic", value.filter.sourceTopic);
|
|
1314
|
+
optional(filter, "name_contains", value.filter.nameContains);
|
|
1315
|
+
const map = mapOf([
|
|
1316
|
+
["v", value.v],
|
|
1317
|
+
["filter", filter]
|
|
1318
|
+
]);
|
|
1319
|
+
optional(map, "after", value.after, (item) => item.toBytes());
|
|
1320
|
+
map.set("limit", value.limit);
|
|
1321
|
+
map.set("consistency", value.consistency);
|
|
1322
|
+
return map;
|
|
1323
|
+
}
|
|
1324
|
+
export function encodeQueryRouteListRequest(value) {
|
|
1325
|
+
validateQueryRouteListRequest(value);
|
|
1326
|
+
const map = mapOf([["v", value.v]]);
|
|
1327
|
+
optional(map, "name_contains", value.nameContains);
|
|
1328
|
+
optional(map, "after", value.after, (item) => item.toBytes());
|
|
1329
|
+
map.set("limit", value.limit);
|
|
1330
|
+
map.set("consistency", value.consistency);
|
|
1331
|
+
return map;
|
|
1332
|
+
}
|
|
1333
|
+
export function validateDestinationGetRequest(value) {
|
|
1334
|
+
validateCheckpointReadVersion(value.v);
|
|
1335
|
+
if (value.destinationId.asU128() === 0n)
|
|
1336
|
+
throw new InvalidError("destination id must be nonzero");
|
|
1337
|
+
}
|
|
1338
|
+
export function validateDestinationListRequest(value) {
|
|
1339
|
+
validateCheckpointReadVersion(value.v);
|
|
1340
|
+
validateReadLimit(value.limit);
|
|
1341
|
+
validateOptionalFilter("source stream", value.filter.sourceStream);
|
|
1342
|
+
validateOptionalFilter("source topic", value.filter.sourceTopic);
|
|
1343
|
+
validateOptionalFilter("destination name", value.filter.nameContains);
|
|
1344
|
+
}
|
|
1345
|
+
export function validateQueryRouteListRequest(value) {
|
|
1346
|
+
validateCheckpointReadVersion(value.v);
|
|
1347
|
+
validateReadLimit(value.limit);
|
|
1348
|
+
validateOptionalFilter("query route name", value.nameContains);
|
|
1349
|
+
}
|
|
1350
|
+
function validateCheckpointReadVersion(value) {
|
|
1351
|
+
if (value !== CHECKPOINT_OP_VERSION)
|
|
1352
|
+
throw new InvalidError("checkpoint read version is invalid");
|
|
1353
|
+
}
|
|
1354
|
+
function validateReadLimit(value) {
|
|
1355
|
+
if (!Number.isInteger(value) || value < 1 || value > MAX_PAGE_SIZE)
|
|
1356
|
+
throw new InvalidError("checkpoint read limit is outside its bound");
|
|
1357
|
+
}
|
|
1358
|
+
function validateOptionalFilter(label, value) {
|
|
1359
|
+
if (value === undefined)
|
|
1360
|
+
return;
|
|
1361
|
+
const bytes = new TextEncoder().encode(value).length;
|
|
1362
|
+
if (bytes < 1 || bytes > 255 || hasControlCharacter(value))
|
|
1363
|
+
throw new InvalidError(`${label} filter is invalid`);
|
|
1364
|
+
}
|
|
1365
|
+
function hasControlCharacter(value) {
|
|
1366
|
+
for (const character of value) {
|
|
1367
|
+
const code = character.codePointAt(0) ?? 0;
|
|
1368
|
+
if (code < 32 || code === 127)
|
|
1369
|
+
return true;
|
|
1370
|
+
}
|
|
1371
|
+
return false;
|
|
1372
|
+
}
|
|
1373
|
+
//# sourceMappingURL=checkpoint.js.map
|