@spine-event-engine/delivery-client 2.0.0-snapshot.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +133 -0
- package/REFERENCE.md +89 -0
- package/dist/client/client.d.ts +143 -0
- package/dist/client/client.d.ts.map +1 -0
- package/dist/client/client.js +459 -0
- package/dist/client/client.js.map +1 -0
- package/dist/client/shard-observation.d.ts +35 -0
- package/dist/client/shard-observation.d.ts.map +1 -0
- package/dist/client/shard-observation.js +178 -0
- package/dist/client/shard-observation.js.map +1 -0
- package/dist/client/types.d.ts +229 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +128 -0
- package/dist/client/types.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/remote/adapters.d.ts +102 -0
- package/dist/remote/adapters.d.ts.map +1 -0
- package/dist/remote/adapters.js +405 -0
- package/dist/remote/adapters.js.map +1 -0
- package/dist/remote/remote-delivery.d.ts +65 -0
- package/dist/remote/remote-delivery.d.ts.map +1 -0
- package/dist/remote/remote-delivery.js +176 -0
- package/dist/remote/remote-delivery.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/wire/codec.d.ts +107 -0
- package/dist/wire/codec.d.ts.map +1 -0
- package/dist/wire/codec.js +776 -0
- package/dist/wire/codec.js.map +1 -0
- package/package.json +35 -0
|
@@ -0,0 +1,776 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026, CodeMatters. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
|
5
|
+
* in compliance with the License. You may obtain a copy of the License at
|
|
6
|
+
*
|
|
7
|
+
* https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
*
|
|
9
|
+
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
|
10
|
+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
|
11
|
+
* or implied. See the License for the specific language governing permissions and limitations under
|
|
12
|
+
* the License.
|
|
13
|
+
*/
|
|
14
|
+
import { create, fromBinary, toBinary } from "@bufbuild/protobuf";
|
|
15
|
+
import { AnySchema, Int32ValueSchema, Int64ValueSchema, StringValueSchema, } from "@bufbuild/protobuf/wkt";
|
|
16
|
+
import { ShardIndex } from "@spine-event-engine/server";
|
|
17
|
+
import { CommandSchema, EventSchema } from "@spine-event-engine/proto";
|
|
18
|
+
import { ShardStatus, } from "@spine-event-engine/proto/delivery-server";
|
|
19
|
+
import { InboxIdSchema, InboxLabel, InboxMessageSchema, InboxMessageStatus, InboxMessageIdSchema, InboxSignalIdSchema, ShardIndexSchema, WorkerIdSchema, } from "@spine-event-engine/proto/delivery";
|
|
20
|
+
import { DeliveryProtocolError, MAX_DELIVERY_BATCH_MESSAGES, MAX_DELIVERY_RPC_BYTES, MAX_DELIVERY_WORKER_BYTES, MAX_INBOX_PAYLOAD_BYTES, } from "../client/types.js";
|
|
21
|
+
/**
|
|
22
|
+
* Holds primitive validation and immutable-value operations for the delivery wire protocol.
|
|
23
|
+
*/
|
|
24
|
+
const DeliveryValues = Object.freeze({
|
|
25
|
+
// prettier-ignore
|
|
26
|
+
/**
|
|
27
|
+
* Determines whether a text value contains non-whitespace characters.
|
|
28
|
+
*
|
|
29
|
+
* @param value Supplies the text value.
|
|
30
|
+
* @returns Whether the value contains text.
|
|
31
|
+
*/
|
|
32
|
+
hasText(value) {
|
|
33
|
+
return value.trim().length > 0;
|
|
34
|
+
},
|
|
35
|
+
/**
|
|
36
|
+
* Counts bytes in a UTF-8 text value.
|
|
37
|
+
*
|
|
38
|
+
* @param value Supplies the text value.
|
|
39
|
+
* @returns The UTF-8 byte count.
|
|
40
|
+
*/
|
|
41
|
+
utf8Bytes(value) {
|
|
42
|
+
return new TextEncoder().encode(value).byteLength;
|
|
43
|
+
},
|
|
44
|
+
/**
|
|
45
|
+
* Freezes an object before returning it.
|
|
46
|
+
* @typeParam Value Describes the object type.
|
|
47
|
+
* @param value Supplies the object to freeze.
|
|
48
|
+
* @returns The frozen object.
|
|
49
|
+
*/
|
|
50
|
+
freeze(value) {
|
|
51
|
+
return Object.freeze(value);
|
|
52
|
+
},
|
|
53
|
+
/**
|
|
54
|
+
* Validates an integer in an inclusive range.
|
|
55
|
+
*
|
|
56
|
+
* @param value Supplies the number to validate.
|
|
57
|
+
* @param minimum Defines the inclusive lower bound.
|
|
58
|
+
* @param maximum Defines the inclusive upper bound.
|
|
59
|
+
* @param name Names the value in validation errors.
|
|
60
|
+
* @returns The validated value.
|
|
61
|
+
*/
|
|
62
|
+
bounded(value, minimum, maximum, name) {
|
|
63
|
+
if (!Number.isSafeInteger(value) || value < minimum || value > maximum)
|
|
64
|
+
throw new TypeError(`${name} is invalid.`);
|
|
65
|
+
return value;
|
|
66
|
+
},
|
|
67
|
+
/**
|
|
68
|
+
* Validates a finite number in an inclusive range.
|
|
69
|
+
*
|
|
70
|
+
* @param value Supplies the number to validate.
|
|
71
|
+
* @param minimum Defines the inclusive lower bound.
|
|
72
|
+
* @param maximum Defines the inclusive upper bound.
|
|
73
|
+
* @param name Names the value in validation errors.
|
|
74
|
+
* @returns The validated value.
|
|
75
|
+
*/
|
|
76
|
+
finite(value, minimum, maximum, name) {
|
|
77
|
+
if (!Number.isFinite(value) || value < minimum || value > maximum)
|
|
78
|
+
throw new TypeError(`${name} is invalid.`);
|
|
79
|
+
return value;
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
const DeliveryTargetIds = Object.freeze({
|
|
83
|
+
validate(typeUrl, value) {
|
|
84
|
+
switch (typeUrl) {
|
|
85
|
+
case "type.googleapis.com/google.protobuf.StringValue":
|
|
86
|
+
if (!DeliveryValues.hasText(fromBinary(StringValueSchema, value).value))
|
|
87
|
+
throw new TypeError("String target ID is blank.");
|
|
88
|
+
break;
|
|
89
|
+
case "type.googleapis.com/google.protobuf.Int32Value":
|
|
90
|
+
fromBinary(Int32ValueSchema, value);
|
|
91
|
+
break;
|
|
92
|
+
case "type.googleapis.com/google.protobuf.Int64Value":
|
|
93
|
+
fromBinary(Int64ValueSchema, value);
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
/**
|
|
99
|
+
* Encodes and decodes inbox messages without exposing mutable wire values.
|
|
100
|
+
*/
|
|
101
|
+
const DeliveryMessageCodec = Object.freeze({
|
|
102
|
+
// prettier-ignore
|
|
103
|
+
/**
|
|
104
|
+
* Encodes one bounded, single-shard inbox batch.
|
|
105
|
+
*
|
|
106
|
+
* @param messages Supplies the messages to encode.
|
|
107
|
+
* @returns The encoded messages, their IDs, and their common shard.
|
|
108
|
+
*/
|
|
109
|
+
encodeBatch(messages) {
|
|
110
|
+
if (messages.length === 0 || messages.length > MAX_DELIVERY_BATCH_MESSAGES)
|
|
111
|
+
throw new TypeError("Delivery message batch is invalid.");
|
|
112
|
+
const ids = messages.map((message) => DeliveryMessageCodec.encodeId(message.id));
|
|
113
|
+
if (new Set(ids).size !== ids.length)
|
|
114
|
+
throw new TypeError("Delivery message batch contains duplicate IDs.");
|
|
115
|
+
const first = messages.at(0);
|
|
116
|
+
if (first === undefined)
|
|
117
|
+
throw new TypeError("Delivery message batch is invalid.");
|
|
118
|
+
const shard = DeliveryShardCodec.encode(first.id.shard);
|
|
119
|
+
for (const message of messages) {
|
|
120
|
+
const messageShard = DeliveryShardCodec.encode(message.id.shard);
|
|
121
|
+
if (messageShard.index !== shard.index || messageShard.ofTotal !== shard.ofTotal)
|
|
122
|
+
throw new TypeError("Delivery message batch spans multiple shards.");
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
ids: Object.freeze(ids),
|
|
126
|
+
shard,
|
|
127
|
+
messages: messages.map((message) => DeliveryMessageCodec.encode(message)),
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
/**
|
|
131
|
+
* Encodes an inbox message for the delivery server.
|
|
132
|
+
*
|
|
133
|
+
* @param message Supplies the message to encode.
|
|
134
|
+
* @returns The validated wire message.
|
|
135
|
+
*/
|
|
136
|
+
encode(message) {
|
|
137
|
+
const id = DeliveryMessageCodec.encodeId(message.id);
|
|
138
|
+
const shard = DeliveryShardCodec.encode(message.id.shard);
|
|
139
|
+
if (message.shard.index !== message.id.shard.index ||
|
|
140
|
+
message.shard.ofTotal !== message.id.shard.ofTotal)
|
|
141
|
+
throw new TypeError("Delivery inbox message shard is invalid.");
|
|
142
|
+
const target = DeliveryMessageCodec.target(message.inboxId);
|
|
143
|
+
const whenReceived = DeliveryRequestCodec.timestamp(message.whenReceived);
|
|
144
|
+
const keepUntil = message.keepUntil === undefined
|
|
145
|
+
? undefined
|
|
146
|
+
: DeliveryRequestCodec.timestamp(message.keepUntil);
|
|
147
|
+
if (typeof message.signalId !== "string" || !DeliveryValues.hasText(message.signalId))
|
|
148
|
+
throw new TypeError("Delivery inbox signal ID is invalid.");
|
|
149
|
+
if (typeof message.version !== "bigint" ||
|
|
150
|
+
message.version < 0n ||
|
|
151
|
+
message.version > BigInt(0x7fffffff))
|
|
152
|
+
throw new TypeError("Delivery inbox message version is invalid.");
|
|
153
|
+
return create(InboxMessageSchema, {
|
|
154
|
+
id: create(InboxMessageIdSchema, { uuid: id, index: shard }),
|
|
155
|
+
signalId: create(InboxSignalIdSchema, { value: message.signalId }),
|
|
156
|
+
inboxId: create(InboxIdSchema, {
|
|
157
|
+
entityId: { id: { typeUrl: target.typeUrl, value: target.value } },
|
|
158
|
+
typeUrl: message.inboxId.targetTypeUrl,
|
|
159
|
+
}),
|
|
160
|
+
payload: DeliveryMessageCodec.payload(message.signal),
|
|
161
|
+
label: DeliveryMessageCodec.label(message.label),
|
|
162
|
+
status: DeliveryMessageCodec.status(message.status),
|
|
163
|
+
whenReceived,
|
|
164
|
+
version: Number(message.version),
|
|
165
|
+
...(keepUntil === undefined ? {} : { keepUntil }),
|
|
166
|
+
});
|
|
167
|
+
},
|
|
168
|
+
/**
|
|
169
|
+
* Decodes an inbox message and confirms its expected shard.
|
|
170
|
+
*
|
|
171
|
+
* @param message Supplies the wire message.
|
|
172
|
+
* @param expectedShard Identifies the requested shard.
|
|
173
|
+
* @returns The detached immutable inbox message.
|
|
174
|
+
*/
|
|
175
|
+
decode(message, expectedShard) {
|
|
176
|
+
const id = message.id;
|
|
177
|
+
const inboxId = message.inboxId;
|
|
178
|
+
const entityId = inboxId?.entityId?.id;
|
|
179
|
+
if (id === undefined ||
|
|
180
|
+
inboxId === undefined ||
|
|
181
|
+
entityId === undefined ||
|
|
182
|
+
message.signalId === undefined ||
|
|
183
|
+
message.whenReceived === undefined ||
|
|
184
|
+
!DeliveryValues.hasText(id.uuid) ||
|
|
185
|
+
!DeliveryValues.hasText(message.signalId.value) ||
|
|
186
|
+
!DeliveryValues.hasText(inboxId.typeUrl) ||
|
|
187
|
+
!DeliveryValues.hasText(entityId.typeUrl) ||
|
|
188
|
+
id.index === undefined)
|
|
189
|
+
throw DeliveryRequestCodec.protocol();
|
|
190
|
+
const shard = DeliveryShardCodec.decode(id.index);
|
|
191
|
+
if (shard.index !== expectedShard.index || shard.ofTotal !== expectedShard.ofTotal)
|
|
192
|
+
throw DeliveryRequestCodec.protocol();
|
|
193
|
+
const keepUntil = message.keepUntil === undefined ? undefined : DeliveryShardCodec.date(message.keepUntil);
|
|
194
|
+
if (!Number.isSafeInteger(message.version) || message.version < 0)
|
|
195
|
+
throw DeliveryRequestCodec.protocol();
|
|
196
|
+
const targetId = DeliveryMessageCodec.decodeTarget(entityId.typeUrl, entityId.value);
|
|
197
|
+
return DeliveryValues.freeze({
|
|
198
|
+
id: DeliveryValues.freeze({ value: id.uuid, shard }),
|
|
199
|
+
inboxId: DeliveryValues.freeze({
|
|
200
|
+
targetId,
|
|
201
|
+
targetTypeUrl: inboxId.typeUrl,
|
|
202
|
+
}),
|
|
203
|
+
signalId: message.signalId.value,
|
|
204
|
+
signal: DeliveryMessageCodec.decodePayload(message),
|
|
205
|
+
label: DeliveryMessageCodec.decodeLabel(message.label),
|
|
206
|
+
status: DeliveryMessageCodec.decodeStatus(message.status),
|
|
207
|
+
shard,
|
|
208
|
+
whenReceived: DeliveryShardCodec.date(message.whenReceived),
|
|
209
|
+
version: BigInt(message.version),
|
|
210
|
+
...(keepUntil === undefined ? {} : { keepUntil }),
|
|
211
|
+
});
|
|
212
|
+
},
|
|
213
|
+
/**
|
|
214
|
+
* Validates and returns an inbox message identifier.
|
|
215
|
+
*
|
|
216
|
+
* @param id Supplies the identifier to encode.
|
|
217
|
+
* @returns The identifier value.
|
|
218
|
+
*/
|
|
219
|
+
encodeId(id) {
|
|
220
|
+
if (typeof id.value !== "string" || !DeliveryValues.hasText(id.value))
|
|
221
|
+
throw new TypeError("Delivery inbox message ID is invalid.");
|
|
222
|
+
return id.value;
|
|
223
|
+
},
|
|
224
|
+
/**
|
|
225
|
+
* Copies an inbox message for later exact-snapshot checks.
|
|
226
|
+
*
|
|
227
|
+
* @param value Supplies the message to copy.
|
|
228
|
+
* @returns A detached immutable message.
|
|
229
|
+
*/
|
|
230
|
+
snapshot(value) {
|
|
231
|
+
return DeliveryValues.freeze({
|
|
232
|
+
id: DeliveryValues.freeze({
|
|
233
|
+
value: value.id.value,
|
|
234
|
+
shard: DeliveryShardCodec.snapshot(value.id.shard),
|
|
235
|
+
}),
|
|
236
|
+
inboxId: DeliveryValues.freeze({
|
|
237
|
+
targetId: DeliveryValues.freeze(create(AnySchema, {
|
|
238
|
+
typeUrl: value.inboxId.targetId.typeUrl,
|
|
239
|
+
value: new Uint8Array(value.inboxId.targetId.value),
|
|
240
|
+
})),
|
|
241
|
+
targetTypeUrl: value.inboxId.targetTypeUrl,
|
|
242
|
+
}),
|
|
243
|
+
signalId: value.signalId,
|
|
244
|
+
...(value.signal === undefined
|
|
245
|
+
? {}
|
|
246
|
+
: {
|
|
247
|
+
signal: DeliveryValues.freeze(create(AnySchema, {
|
|
248
|
+
typeUrl: value.signal.typeUrl,
|
|
249
|
+
value: new Uint8Array(value.signal.value),
|
|
250
|
+
})),
|
|
251
|
+
}),
|
|
252
|
+
label: value.label,
|
|
253
|
+
status: value.status,
|
|
254
|
+
shard: DeliveryShardCodec.snapshot(value.shard),
|
|
255
|
+
whenReceived: new Date(value.whenReceived.getTime()),
|
|
256
|
+
version: value.version,
|
|
257
|
+
...(value.keepUntil === undefined ? {} : { keepUntil: new Date(value.keepUntil.getTime()) }),
|
|
258
|
+
});
|
|
259
|
+
},
|
|
260
|
+
/**
|
|
261
|
+
* Parses an inbox target identifier.
|
|
262
|
+
*
|
|
263
|
+
* @param inbox Supplies the inbox identifier.
|
|
264
|
+
* @returns The type URL and serialized target ID.
|
|
265
|
+
*/
|
|
266
|
+
target(inbox) {
|
|
267
|
+
if (typeof inbox.targetId.typeUrl !== "string" ||
|
|
268
|
+
!DeliveryValues.hasText(inbox.targetId.typeUrl) ||
|
|
269
|
+
!(inbox.targetId.value instanceof Uint8Array) ||
|
|
270
|
+
typeof inbox.targetTypeUrl !== "string" ||
|
|
271
|
+
!DeliveryValues.hasText(inbox.targetTypeUrl))
|
|
272
|
+
throw new TypeError("Delivery inbox ID is invalid.");
|
|
273
|
+
try {
|
|
274
|
+
DeliveryTargetIds.validate(inbox.targetId.typeUrl, inbox.targetId.value);
|
|
275
|
+
return {
|
|
276
|
+
typeUrl: inbox.targetId.typeUrl,
|
|
277
|
+
value: new Uint8Array(inbox.targetId.value),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
throw new TypeError("Delivery inbox ID is invalid.", { cause: error });
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
/**
|
|
285
|
+
* Decodes an inbox target identifier while preserving plain framework IDs.
|
|
286
|
+
*
|
|
287
|
+
* @param typeUrl Identifies the wire entity-ID message.
|
|
288
|
+
* @param value Contains the serialized entity-ID message.
|
|
289
|
+
* @returns The framework target identifier.
|
|
290
|
+
*/
|
|
291
|
+
decodeTarget(typeUrl, value) {
|
|
292
|
+
if (!DeliveryValues.hasText(typeUrl))
|
|
293
|
+
throw DeliveryRequestCodec.protocol();
|
|
294
|
+
try {
|
|
295
|
+
DeliveryTargetIds.validate(typeUrl, value);
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
throw DeliveryRequestCodec.protocol();
|
|
299
|
+
}
|
|
300
|
+
return create(AnySchema, { typeUrl, value: new Uint8Array(value) });
|
|
301
|
+
},
|
|
302
|
+
/**
|
|
303
|
+
* Encodes a delivered signal payload.
|
|
304
|
+
*
|
|
305
|
+
* @param signal Supplies the optional serialized signal.
|
|
306
|
+
* @returns The wire payload union.
|
|
307
|
+
*/
|
|
308
|
+
payload(signal) {
|
|
309
|
+
if (signal === undefined)
|
|
310
|
+
return { case: undefined };
|
|
311
|
+
if (signal.typeUrl === "type.spine.io/spine.core.Command")
|
|
312
|
+
return { case: "command", value: fromBinary(CommandSchema, signal.value) };
|
|
313
|
+
if (signal.typeUrl === "type.spine.io/spine.core.Event")
|
|
314
|
+
return { case: "event", value: fromBinary(EventSchema, signal.value) };
|
|
315
|
+
throw new TypeError("Delivery inbox message payload is invalid.");
|
|
316
|
+
},
|
|
317
|
+
/**
|
|
318
|
+
* Encodes an inbox label.
|
|
319
|
+
*
|
|
320
|
+
* @param value Supplies the label.
|
|
321
|
+
* @returns The wire enum value.
|
|
322
|
+
*/
|
|
323
|
+
label(value) {
|
|
324
|
+
const labels = {
|
|
325
|
+
HANDLE_COMMAND: InboxLabel.HANDLE_COMMAND,
|
|
326
|
+
UPDATE_SUBSCRIBER: InboxLabel.UPDATE_SUBSCRIBER,
|
|
327
|
+
REACT_UPON_EVENT: InboxLabel.REACT_UPON_EVENT,
|
|
328
|
+
CATCH_UP: InboxLabel.CATCH_UP,
|
|
329
|
+
};
|
|
330
|
+
if (!(value in labels))
|
|
331
|
+
throw new TypeError("Delivery inbox message label is invalid.");
|
|
332
|
+
return labels[value];
|
|
333
|
+
},
|
|
334
|
+
/**
|
|
335
|
+
* Encodes an inbox status.
|
|
336
|
+
*
|
|
337
|
+
* @param value Supplies the status.
|
|
338
|
+
* @returns The wire enum value.
|
|
339
|
+
*/
|
|
340
|
+
status(value) {
|
|
341
|
+
const statuses = {
|
|
342
|
+
TO_DELIVER: InboxMessageStatus.TO_DELIVER,
|
|
343
|
+
SCHEDULED: InboxMessageStatus.SCHEDULED,
|
|
344
|
+
DELIVERED: InboxMessageStatus.DELIVERED,
|
|
345
|
+
TO_CATCH_UP: InboxMessageStatus.TO_CATCH_UP,
|
|
346
|
+
};
|
|
347
|
+
if (!(value in statuses))
|
|
348
|
+
throw new TypeError("Delivery inbox message status is invalid.");
|
|
349
|
+
return statuses[value];
|
|
350
|
+
},
|
|
351
|
+
/**
|
|
352
|
+
* Decodes a delivered signal payload.
|
|
353
|
+
*
|
|
354
|
+
* @param message Supplies the wire message.
|
|
355
|
+
* @returns The immutable serialized signal.
|
|
356
|
+
*/
|
|
357
|
+
decodePayload(message) {
|
|
358
|
+
const wire = message.payload;
|
|
359
|
+
if (wire.case !== "command" && wire.case !== "event")
|
|
360
|
+
throw DeliveryRequestCodec.protocol();
|
|
361
|
+
const schema = wire.case === "command" ? CommandSchema : EventSchema;
|
|
362
|
+
const encoded = toBinary(schema, wire.value, { writeUnknownFields: false });
|
|
363
|
+
if (encoded.byteLength > MAX_INBOX_PAYLOAD_BYTES)
|
|
364
|
+
throw DeliveryRequestCodec.protocol();
|
|
365
|
+
return DeliveryValues.freeze(create(AnySchema, {
|
|
366
|
+
typeUrl: `type.spine.io/${schema.typeName}`,
|
|
367
|
+
value: new Uint8Array(encoded),
|
|
368
|
+
}));
|
|
369
|
+
},
|
|
370
|
+
/**
|
|
371
|
+
* Decodes an inbox label.
|
|
372
|
+
*
|
|
373
|
+
* @param value Supplies the wire enum value.
|
|
374
|
+
* @returns The domain label.
|
|
375
|
+
*/
|
|
376
|
+
decodeLabel(value) {
|
|
377
|
+
switch (value) {
|
|
378
|
+
case InboxLabel.HANDLE_COMMAND:
|
|
379
|
+
return "HANDLE_COMMAND";
|
|
380
|
+
case InboxLabel.UPDATE_SUBSCRIBER:
|
|
381
|
+
return "UPDATE_SUBSCRIBER";
|
|
382
|
+
case InboxLabel.REACT_UPON_EVENT:
|
|
383
|
+
return "REACT_UPON_EVENT";
|
|
384
|
+
case InboxLabel.CATCH_UP:
|
|
385
|
+
return "CATCH_UP";
|
|
386
|
+
default:
|
|
387
|
+
throw DeliveryRequestCodec.protocol();
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
/**
|
|
391
|
+
* Decodes an inbox status.
|
|
392
|
+
*
|
|
393
|
+
* @param value Supplies the wire enum value.
|
|
394
|
+
* @returns The domain status.
|
|
395
|
+
*/
|
|
396
|
+
decodeStatus(value) {
|
|
397
|
+
switch (value) {
|
|
398
|
+
case InboxMessageStatus.TO_DELIVER:
|
|
399
|
+
return "TO_DELIVER";
|
|
400
|
+
case InboxMessageStatus.SCHEDULED:
|
|
401
|
+
return "SCHEDULED";
|
|
402
|
+
case InboxMessageStatus.DELIVERED:
|
|
403
|
+
return "DELIVERED";
|
|
404
|
+
case InboxMessageStatus.TO_CATCH_UP:
|
|
405
|
+
return "TO_CATCH_UP";
|
|
406
|
+
default:
|
|
407
|
+
throw DeliveryRequestCodec.protocol();
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
});
|
|
411
|
+
/**
|
|
412
|
+
* Encodes and decodes delivery shards, workers, and exclusive sessions.
|
|
413
|
+
*/
|
|
414
|
+
const DeliveryShardCodec = Object.freeze({
|
|
415
|
+
// prettier-ignore
|
|
416
|
+
/**
|
|
417
|
+
* Decodes an administrative shard observation.
|
|
418
|
+
*
|
|
419
|
+
* @param value Supplies the wire observation.
|
|
420
|
+
* @returns The immutable remote observation.
|
|
421
|
+
*/
|
|
422
|
+
decodeObservation(value) {
|
|
423
|
+
if (value.index === undefined || !Number.isSafeInteger(value.messages) || value.messages < 0)
|
|
424
|
+
throw DeliveryRequestCodec.protocol();
|
|
425
|
+
const lastPicked = value.lastPicked === undefined ? undefined : DeliveryShardCodec.date(value.lastPicked);
|
|
426
|
+
return DeliveryValues.freeze({
|
|
427
|
+
shard: DeliveryShardCodec.decode(value.index),
|
|
428
|
+
status: DeliveryShardCodec.status(value.status),
|
|
429
|
+
...(lastPicked === undefined ? {} : { lastPicked }),
|
|
430
|
+
messages: value.messages,
|
|
431
|
+
});
|
|
432
|
+
},
|
|
433
|
+
/**
|
|
434
|
+
* Decodes an administrative shard update.
|
|
435
|
+
*
|
|
436
|
+
* @param value Supplies the wire update.
|
|
437
|
+
* @returns The immutable remote observation.
|
|
438
|
+
*/
|
|
439
|
+
decodeUpdate(value) {
|
|
440
|
+
if (value.index === undefined ||
|
|
441
|
+
!Number.isSafeInteger(value.newMessagesCount) ||
|
|
442
|
+
value.newMessagesCount < 0)
|
|
443
|
+
throw DeliveryRequestCodec.protocol();
|
|
444
|
+
const lastPicked = value.whenLastPicked === undefined
|
|
445
|
+
? undefined
|
|
446
|
+
: DeliveryShardCodec.date(value.whenLastPicked);
|
|
447
|
+
return DeliveryValues.freeze({
|
|
448
|
+
shard: DeliveryShardCodec.decode(value.index),
|
|
449
|
+
status: DeliveryShardCodec.status(value.newStatus),
|
|
450
|
+
...(lastPicked === undefined ? {} : { lastPicked }),
|
|
451
|
+
messages: value.newMessagesCount,
|
|
452
|
+
});
|
|
453
|
+
},
|
|
454
|
+
/**
|
|
455
|
+
* Encodes a shard index.
|
|
456
|
+
*
|
|
457
|
+
* @param value Supplies the shard index.
|
|
458
|
+
* @returns The wire shard index.
|
|
459
|
+
*/
|
|
460
|
+
encode(value) {
|
|
461
|
+
if (!(value instanceof ShardIndex))
|
|
462
|
+
throw new TypeError("Delivery shard is invalid.");
|
|
463
|
+
return create(ShardIndexSchema, { index: value.index, ofTotal: value.ofTotal });
|
|
464
|
+
},
|
|
465
|
+
/**
|
|
466
|
+
* Copies a shard index.
|
|
467
|
+
*
|
|
468
|
+
* @param value Supplies the shard index.
|
|
469
|
+
* @returns The detached shard index.
|
|
470
|
+
*/
|
|
471
|
+
snapshot(value) {
|
|
472
|
+
return new ShardIndex(value.index, value.ofTotal);
|
|
473
|
+
},
|
|
474
|
+
/**
|
|
475
|
+
* Encodes a delivery worker identifier.
|
|
476
|
+
*
|
|
477
|
+
* @param value Supplies the worker identifier.
|
|
478
|
+
* @returns The wire worker identifier.
|
|
479
|
+
*/
|
|
480
|
+
encodeWorker(value) {
|
|
481
|
+
if (typeof value.nodeId !== "string" ||
|
|
482
|
+
!DeliveryValues.hasText(value.nodeId) ||
|
|
483
|
+
typeof value.value !== "string" ||
|
|
484
|
+
!DeliveryValues.hasText(value.value) ||
|
|
485
|
+
DeliveryValues.utf8Bytes(value.nodeId) + DeliveryValues.utf8Bytes(value.value) >
|
|
486
|
+
MAX_DELIVERY_WORKER_BYTES)
|
|
487
|
+
throw new TypeError("Delivery worker ID is invalid.");
|
|
488
|
+
return create(WorkerIdSchema, { nodeId: { value: value.nodeId }, value: value.value });
|
|
489
|
+
},
|
|
490
|
+
/**
|
|
491
|
+
* Decodes a successful exclusive pickup.
|
|
492
|
+
*
|
|
493
|
+
* @param value Supplies the pickup response.
|
|
494
|
+
* @param expectedShard Identifies the requested shard.
|
|
495
|
+
* @param expectedWorker Identifies the requesting worker.
|
|
496
|
+
* @returns The immutable exclusive session.
|
|
497
|
+
*/
|
|
498
|
+
decodePicked(value, expectedShard, expectedWorker) {
|
|
499
|
+
if (value.shard === undefined || value.worker === undefined || value.whenPicked === undefined)
|
|
500
|
+
throw DeliveryRequestCodec.protocol();
|
|
501
|
+
const shard = DeliveryShardCodec.decode(value.shard);
|
|
502
|
+
const worker = DeliveryShardCodec.decodeWorker(value.worker);
|
|
503
|
+
if (shard.index !== expectedShard.index ||
|
|
504
|
+
shard.ofTotal !== expectedShard.ofTotal ||
|
|
505
|
+
worker.nodeId !== expectedWorker.nodeId ||
|
|
506
|
+
worker.value !== expectedWorker.value)
|
|
507
|
+
throw DeliveryRequestCodec.protocol();
|
|
508
|
+
return DeliveryValues.freeze({
|
|
509
|
+
kind: "EXCLUSIVE",
|
|
510
|
+
shard,
|
|
511
|
+
worker,
|
|
512
|
+
whenPicked: DeliveryShardCodec.date(value.whenPicked),
|
|
513
|
+
});
|
|
514
|
+
},
|
|
515
|
+
/**
|
|
516
|
+
* Validates an already-picked response.
|
|
517
|
+
*
|
|
518
|
+
* @param value Supplies the response fields.
|
|
519
|
+
* @param expectedShard Identifies the requested shard.
|
|
520
|
+
*/
|
|
521
|
+
validatePicked(value, expectedShard) {
|
|
522
|
+
if (value.worker === undefined || value.whenPicked === undefined)
|
|
523
|
+
throw DeliveryRequestCodec.protocol();
|
|
524
|
+
if (value.shard !== undefined) {
|
|
525
|
+
const shard = DeliveryShardCodec.decode(value.shard);
|
|
526
|
+
if (shard.index !== expectedShard.index || shard.ofTotal !== expectedShard.ofTotal)
|
|
527
|
+
throw DeliveryRequestCodec.protocol();
|
|
528
|
+
}
|
|
529
|
+
DeliveryShardCodec.decodeWorker(value.worker);
|
|
530
|
+
DeliveryShardCodec.date(value.whenPicked);
|
|
531
|
+
},
|
|
532
|
+
/**
|
|
533
|
+
* Decodes an expired exclusive session.
|
|
534
|
+
*
|
|
535
|
+
* @param value Supplies the expired-session response.
|
|
536
|
+
* @returns The immutable released session.
|
|
537
|
+
*/
|
|
538
|
+
decodeReleased(value) {
|
|
539
|
+
if (value.shard === undefined ||
|
|
540
|
+
value.worker === undefined ||
|
|
541
|
+
value.whenPicked === undefined ||
|
|
542
|
+
value.whenReleased === undefined)
|
|
543
|
+
throw DeliveryRequestCodec.protocol();
|
|
544
|
+
return DeliveryValues.freeze({
|
|
545
|
+
kind: "EXCLUSIVE",
|
|
546
|
+
shard: DeliveryShardCodec.decode(value.shard),
|
|
547
|
+
worker: DeliveryShardCodec.decodeWorker(value.worker),
|
|
548
|
+
whenPicked: DeliveryShardCodec.date(value.whenPicked),
|
|
549
|
+
whenReleased: DeliveryShardCodec.date(value.whenReleased),
|
|
550
|
+
});
|
|
551
|
+
},
|
|
552
|
+
/**
|
|
553
|
+
* Decodes a protocol timestamp.
|
|
554
|
+
*
|
|
555
|
+
* @param value Supplies the timestamp fields.
|
|
556
|
+
* @returns The decoded date.
|
|
557
|
+
*/
|
|
558
|
+
date(value) {
|
|
559
|
+
if (!Number.isInteger(value.nanos) || value.nanos < 0 || value.nanos >= 1_000_000_000)
|
|
560
|
+
throw DeliveryRequestCodec.protocol();
|
|
561
|
+
const millis = Number(value.seconds) * 1000 + Math.floor(value.nanos / 1_000_000);
|
|
562
|
+
if (!Number.isSafeInteger(millis))
|
|
563
|
+
throw DeliveryRequestCodec.protocol();
|
|
564
|
+
const result = new Date(millis);
|
|
565
|
+
if (Number.isNaN(result.getTime()))
|
|
566
|
+
throw DeliveryRequestCodec.protocol();
|
|
567
|
+
return result;
|
|
568
|
+
},
|
|
569
|
+
/**
|
|
570
|
+
* Decodes a shard status.
|
|
571
|
+
*
|
|
572
|
+
* @param value Supplies the wire status.
|
|
573
|
+
* @returns The remote observation status.
|
|
574
|
+
*/
|
|
575
|
+
status(value) {
|
|
576
|
+
switch (value) {
|
|
577
|
+
case ShardStatus.PICKED:
|
|
578
|
+
return "PICKED";
|
|
579
|
+
case ShardStatus.NOT_PICKED:
|
|
580
|
+
return "NOT_PICKED";
|
|
581
|
+
default:
|
|
582
|
+
throw DeliveryRequestCodec.protocol();
|
|
583
|
+
}
|
|
584
|
+
},
|
|
585
|
+
/**
|
|
586
|
+
* Decodes a shard index.
|
|
587
|
+
*
|
|
588
|
+
* @param value Supplies the wire shard index.
|
|
589
|
+
* @returns The validated shard index.
|
|
590
|
+
*/
|
|
591
|
+
decode(value) {
|
|
592
|
+
try {
|
|
593
|
+
return new ShardIndex(value.index, value.ofTotal);
|
|
594
|
+
}
|
|
595
|
+
catch {
|
|
596
|
+
throw DeliveryRequestCodec.protocol();
|
|
597
|
+
}
|
|
598
|
+
},
|
|
599
|
+
/**
|
|
600
|
+
* Decodes a worker identifier.
|
|
601
|
+
*
|
|
602
|
+
* @param value Supplies the wire worker identifier.
|
|
603
|
+
* @returns The immutable worker identifier.
|
|
604
|
+
*/
|
|
605
|
+
decodeWorker(value) {
|
|
606
|
+
if (value.nodeId === undefined ||
|
|
607
|
+
!DeliveryValues.hasText(value.nodeId.value) ||
|
|
608
|
+
!DeliveryValues.hasText(value.value))
|
|
609
|
+
throw DeliveryRequestCodec.protocol();
|
|
610
|
+
return DeliveryValues.freeze({ nodeId: value.nodeId.value, value: value.value });
|
|
611
|
+
},
|
|
612
|
+
});
|
|
613
|
+
/**
|
|
614
|
+
* Validates delivery RPC values and prepares call-scoped protocol data.
|
|
615
|
+
*/
|
|
616
|
+
const DeliveryRequestCodec = Object.freeze({
|
|
617
|
+
// prettier-ignore
|
|
618
|
+
/**
|
|
619
|
+
* Encodes an inactivity duration.
|
|
620
|
+
*
|
|
621
|
+
* @param value Supplies milliseconds.
|
|
622
|
+
* @returns The protocol duration fields.
|
|
623
|
+
*/
|
|
624
|
+
duration(value) {
|
|
625
|
+
const maximumMilliseconds = 315_576_000_000_000;
|
|
626
|
+
if (!Number.isSafeInteger(value) || value <= 0 || value > maximumMilliseconds)
|
|
627
|
+
throw new TypeError("Delivery inactivity duration is invalid.");
|
|
628
|
+
return { seconds: BigInt(Math.floor(value / 1000)), nanos: (value % 1000) * 1_000_000 };
|
|
629
|
+
},
|
|
630
|
+
/**
|
|
631
|
+
* Encodes a date timestamp.
|
|
632
|
+
*
|
|
633
|
+
* @param value Supplies the date.
|
|
634
|
+
* @returns The protocol timestamp fields.
|
|
635
|
+
*/
|
|
636
|
+
timestamp(value) {
|
|
637
|
+
if (!(value instanceof Date) || Number.isNaN(value.getTime()))
|
|
638
|
+
throw new TypeError("Delivery inbox timestamp is invalid.");
|
|
639
|
+
const millis = value.getTime();
|
|
640
|
+
return {
|
|
641
|
+
seconds: BigInt(Math.floor(millis / 1000)),
|
|
642
|
+
nanos: (((millis % 1000) + 1000) % 1000) * 1_000_000,
|
|
643
|
+
};
|
|
644
|
+
},
|
|
645
|
+
/**
|
|
646
|
+
* Creates a protocol-validation error.
|
|
647
|
+
*
|
|
648
|
+
* @returns The protocol error.
|
|
649
|
+
*/
|
|
650
|
+
protocol() {
|
|
651
|
+
return new DeliveryProtocolError();
|
|
652
|
+
},
|
|
653
|
+
/**
|
|
654
|
+
* Creates Connect call options.
|
|
655
|
+
*
|
|
656
|
+
* @param signal Supplies the optional cancellation signal.
|
|
657
|
+
* @param timeoutMs Supplies the validated timeout.
|
|
658
|
+
* @returns The call options.
|
|
659
|
+
*/
|
|
660
|
+
callOptions(signal, timeoutMs) {
|
|
661
|
+
if (timeoutMs === undefined)
|
|
662
|
+
return signal === undefined ? {} : { signal };
|
|
663
|
+
return signal === undefined ? { timeoutMs } : { timeoutMs, signal };
|
|
664
|
+
},
|
|
665
|
+
/**
|
|
666
|
+
* Validates a page size.
|
|
667
|
+
*
|
|
668
|
+
* @param value Supplies the page size.
|
|
669
|
+
* @returns The validated page size.
|
|
670
|
+
*/
|
|
671
|
+
pageSize(value) {
|
|
672
|
+
return DeliveryValues.bounded(value, 1, 1_000, "Delivery page size");
|
|
673
|
+
},
|
|
674
|
+
/**
|
|
675
|
+
* Validates an integer in an inclusive range.
|
|
676
|
+
*
|
|
677
|
+
* @param value Supplies the number to validate.
|
|
678
|
+
* @param minimum Defines the inclusive lower bound.
|
|
679
|
+
* @param maximum Defines the inclusive upper bound.
|
|
680
|
+
* @param name Names the value in validation errors.
|
|
681
|
+
* @returns The validated value.
|
|
682
|
+
*/
|
|
683
|
+
bounded(value, minimum, maximum, name) {
|
|
684
|
+
return DeliveryValues.bounded(value, minimum, maximum, name);
|
|
685
|
+
},
|
|
686
|
+
/**
|
|
687
|
+
* Normalizes client options to bounded immutable values.
|
|
688
|
+
*
|
|
689
|
+
* @param options Supplies the optional client settings.
|
|
690
|
+
* @returns The normalized settings.
|
|
691
|
+
*/
|
|
692
|
+
normalize(options) {
|
|
693
|
+
return Object.freeze({
|
|
694
|
+
pageSize: DeliveryRequestCodec.pageSize(options.pageSize ?? 100),
|
|
695
|
+
readRetries: DeliveryRequestCodec.retries(options.readRetries ?? 0),
|
|
696
|
+
retryBackoffMs: DeliveryRequestCodec.backoff(options.retryBackoffMs ?? 0),
|
|
697
|
+
observationReconnects: DeliveryRequestCodec.retries(options.observationReconnects ?? 0),
|
|
698
|
+
observationReconnectBackoffMs: DeliveryRequestCodec.backoff(options.observationReconnectBackoffMs ?? 0),
|
|
699
|
+
observationBufferSize: DeliveryValues.bounded(options.observationBufferSize ?? 100, 1, 1_000, "Delivery observation buffer size"),
|
|
700
|
+
});
|
|
701
|
+
},
|
|
702
|
+
/**
|
|
703
|
+
* Validates a delivery service base URL.
|
|
704
|
+
*
|
|
705
|
+
* @param value Supplies the URL.
|
|
706
|
+
*/
|
|
707
|
+
baseUrl(value) {
|
|
708
|
+
if (typeof value !== "string")
|
|
709
|
+
throw new TypeError("Delivery client base URL is invalid.");
|
|
710
|
+
let url;
|
|
711
|
+
try {
|
|
712
|
+
url = new URL(value);
|
|
713
|
+
}
|
|
714
|
+
catch {
|
|
715
|
+
throw new TypeError("Delivery client base URL is invalid.");
|
|
716
|
+
}
|
|
717
|
+
if ((url.protocol !== "http:" && url.protocol !== "https:") || url.pathname !== "/")
|
|
718
|
+
throw new TypeError("Delivery client base URL is invalid.");
|
|
719
|
+
},
|
|
720
|
+
/**
|
|
721
|
+
* Rejects an oversized RPC request.
|
|
722
|
+
*
|
|
723
|
+
* @param schema Supplies the message schema.
|
|
724
|
+
* @param value Supplies the message value.
|
|
725
|
+
*/
|
|
726
|
+
requestBytes(schema, value) {
|
|
727
|
+
if (toBinary(schema, value).byteLength > MAX_DELIVERY_RPC_BYTES)
|
|
728
|
+
throw new TypeError("Delivery RPC request exceeds the 4 MiB limit.");
|
|
729
|
+
},
|
|
730
|
+
/**
|
|
731
|
+
* Rejects an oversized RPC response when it can be re-encoded.
|
|
732
|
+
*
|
|
733
|
+
* @param schema Supplies the message schema.
|
|
734
|
+
* @param value Supplies the message value.
|
|
735
|
+
*/
|
|
736
|
+
responseBytes(schema, value) {
|
|
737
|
+
try {
|
|
738
|
+
if (toBinary(schema, value).byteLength > MAX_DELIVERY_RPC_BYTES)
|
|
739
|
+
throw DeliveryRequestCodec.protocol();
|
|
740
|
+
}
|
|
741
|
+
catch (error) {
|
|
742
|
+
if (error instanceof DeliveryProtocolError)
|
|
743
|
+
throw error;
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
},
|
|
747
|
+
/**
|
|
748
|
+
* Validates read retry count.
|
|
749
|
+
*
|
|
750
|
+
* @param value Supplies the retry count.
|
|
751
|
+
* @returns The validated count.
|
|
752
|
+
*/
|
|
753
|
+
retries(value) {
|
|
754
|
+
return DeliveryValues.bounded(value, 0, 5, "Delivery read retries");
|
|
755
|
+
},
|
|
756
|
+
/**
|
|
757
|
+
* Validates retry backoff milliseconds.
|
|
758
|
+
*
|
|
759
|
+
* @param value Supplies the backoff.
|
|
760
|
+
* @returns The validated backoff.
|
|
761
|
+
*/
|
|
762
|
+
backoff(value) {
|
|
763
|
+
return DeliveryValues.finite(value, 0, 10_000, "Delivery retry backoff");
|
|
764
|
+
},
|
|
765
|
+
/**
|
|
766
|
+
* Validates RPC timeout milliseconds.
|
|
767
|
+
*
|
|
768
|
+
* @param value Supplies the timeout.
|
|
769
|
+
* @returns The validated timeout.
|
|
770
|
+
*/
|
|
771
|
+
timeout(value) {
|
|
772
|
+
return DeliveryValues.bounded(value, 1, 120_000, "Delivery request timeout");
|
|
773
|
+
},
|
|
774
|
+
});
|
|
775
|
+
export { DeliveryMessageCodec, DeliveryRequestCodec, DeliveryShardCodec };
|
|
776
|
+
//# sourceMappingURL=codec.js.map
|