@sekiban/dcb-core 0.2.2 → 0.3.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 +9 -0
- package/dist/conditionalCommit.d.ts +98 -0
- package/dist/conditionalCommit.js +479 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +406 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,6 +8,15 @@ surface available from `@sekiban/dcb-core`.
|
|
|
8
8
|
|
|
9
9
|
It is not the earlier, unrelated `@sekiban/core` package line.
|
|
10
10
|
|
|
11
|
+
## Query contracts
|
|
12
|
+
|
|
13
|
+
The root export includes structural query contracts and nothing that runs them.
|
|
14
|
+
`QueryContract` and `ListQueryContract` carry a kind, an id, a version, and
|
|
15
|
+
bidirectional `encode` / `decode` codecs. `QueryPage` is the list result shape.
|
|
16
|
+
There is no handler, schema, or source view on these contracts. A later client
|
|
17
|
+
can accept a contract plus params and return the decoded result; this package
|
|
18
|
+
does not provide that call.
|
|
19
|
+
|
|
11
20
|
## License
|
|
12
21
|
|
|
13
22
|
Elastic License 2.0. See [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conditional-commit wire values and deterministic helpers for the public
|
|
3
|
+
* versioned conditional-commit route.
|
|
4
|
+
*/
|
|
5
|
+
export declare const CONDITIONAL_COMMIT_VERSION: 1;
|
|
6
|
+
export declare const CONDITIONAL_COMMIT_DERIVATION_VERSION: 2;
|
|
7
|
+
export declare const CONDITIONAL_COMMIT_CANONICALIZATION_VERSION: 1;
|
|
8
|
+
export declare const CONDITIONAL_COMMIT_DOMAIN_SEPARATOR: "sekiban.dcb.conditional-append.unique-key";
|
|
9
|
+
export declare const CONDITIONAL_COMMIT_MAX_UTF8_FIELD_BYTES: 512;
|
|
10
|
+
export declare const CONDITIONAL_COMMIT_MAX_PAYLOAD_BYTES: number;
|
|
11
|
+
export declare const CONDITIONAL_COMMIT_MAX_REQUEST_BYTES: number;
|
|
12
|
+
export type ConditionalCommitValidationCode = "conditional_commit_invalid_json" | "conditional_commit_unknown_member" | "conditional_commit_invalid_version" | "conditional_commit_invalid_envelope" | "conditional_commit_invalid_key" | "conditional_commit_invalid_candidate" | "conditional_commit_invalid_base64" | "conditional_commit_invalid_utf8" | "conditional_commit_invalid_payload_json" | "conditional_commit_payload_root_not_object" | "conditional_commit_payload_not_canonicalizable" | "conditional_commit_event_not_registered" | "conditional_commit_invalid_tag_count" | "conditional_commit_size_limit_exceeded";
|
|
13
|
+
export type ConditionalCommitInDoubtReason = "winner_unreadable" | "ambiguous_after_append" | "committed_state_unverified" | "live_precondition_blocked" | "bootstrap_epoch_changed";
|
|
14
|
+
export interface SerializedConditionalEventCandidate {
|
|
15
|
+
readonly payload: string;
|
|
16
|
+
readonly eventPayloadName: string;
|
|
17
|
+
readonly tags: readonly [string];
|
|
18
|
+
}
|
|
19
|
+
export interface SerializedConditionalCommitRequest {
|
|
20
|
+
readonly version: 1;
|
|
21
|
+
readonly idempotencyKey: string;
|
|
22
|
+
readonly eventCandidate: SerializedConditionalEventCandidate;
|
|
23
|
+
}
|
|
24
|
+
export interface ConditionalCommitReceipt {
|
|
25
|
+
readonly winnerEventId: string;
|
|
26
|
+
readonly winnerSortableUniqueId: string;
|
|
27
|
+
readonly operationFingerprint: string;
|
|
28
|
+
}
|
|
29
|
+
export interface ConditionalWrittenEvent {
|
|
30
|
+
readonly payload: string;
|
|
31
|
+
readonly sortableUniqueIdValue: string;
|
|
32
|
+
readonly id: string;
|
|
33
|
+
readonly eventMetadata: {
|
|
34
|
+
readonly causationId: string;
|
|
35
|
+
readonly correlationId: "SerializedCommit";
|
|
36
|
+
readonly executedUser: "SerializedSekibanExecutor";
|
|
37
|
+
};
|
|
38
|
+
readonly tags: readonly [string];
|
|
39
|
+
readonly eventPayloadName: string;
|
|
40
|
+
}
|
|
41
|
+
export type ConditionalCommitResult = {
|
|
42
|
+
readonly version: 1;
|
|
43
|
+
readonly outcome: "Appended";
|
|
44
|
+
readonly receipt: ConditionalCommitReceipt;
|
|
45
|
+
readonly writtenEvents: readonly [ConditionalWrittenEvent];
|
|
46
|
+
} | {
|
|
47
|
+
readonly version: 1;
|
|
48
|
+
readonly outcome: "AlreadyCommittedSameOperation";
|
|
49
|
+
readonly receipt: ConditionalCommitReceipt;
|
|
50
|
+
readonly writtenEvents: readonly [];
|
|
51
|
+
} | {
|
|
52
|
+
readonly version: 1;
|
|
53
|
+
readonly outcome: "KeyReuseConflict";
|
|
54
|
+
readonly code: "conditional_commit_key_reuse_conflict";
|
|
55
|
+
readonly error: "The idempotency key is already bound to a different operation.";
|
|
56
|
+
readonly retryable: false;
|
|
57
|
+
} | {
|
|
58
|
+
readonly version: 1;
|
|
59
|
+
readonly outcome: "Unsupported";
|
|
60
|
+
readonly code: "conditional_commit_unsupported";
|
|
61
|
+
readonly error: "Conditional commit is not supported by this deployment.";
|
|
62
|
+
readonly retryable: false;
|
|
63
|
+
};
|
|
64
|
+
export type ConditionalCommitErrorResponse = {
|
|
65
|
+
readonly version: 1;
|
|
66
|
+
readonly code: ConditionalCommitValidationCode;
|
|
67
|
+
readonly error: string;
|
|
68
|
+
readonly retryable: false;
|
|
69
|
+
} | {
|
|
70
|
+
readonly version: 1;
|
|
71
|
+
readonly code: "conditional_commit_in_doubt";
|
|
72
|
+
readonly error: "The conditional commit outcome is not yet verified; resend the same prepared request.";
|
|
73
|
+
readonly retryable: true;
|
|
74
|
+
readonly reason: ConditionalCommitInDoubtReason;
|
|
75
|
+
} | {
|
|
76
|
+
readonly version: 1;
|
|
77
|
+
readonly code: "conditional_commit_committed_state_corrupt";
|
|
78
|
+
readonly error: "The durable conditional commit state could not be verified.";
|
|
79
|
+
readonly retryable: false;
|
|
80
|
+
};
|
|
81
|
+
export interface ConditionalCommitFingerprintInput {
|
|
82
|
+
readonly serviceId: string;
|
|
83
|
+
readonly idempotencyKey: string;
|
|
84
|
+
readonly eventPayloadName: string;
|
|
85
|
+
readonly canonicalPayload: Uint8Array;
|
|
86
|
+
readonly tag: string;
|
|
87
|
+
}
|
|
88
|
+
export declare class ConditionalCommitProtocolError extends Error {
|
|
89
|
+
readonly code: ConditionalCommitValidationCode;
|
|
90
|
+
readonly retryable = false;
|
|
91
|
+
constructor(code: ConditionalCommitValidationCode);
|
|
92
|
+
}
|
|
93
|
+
export declare function normalizeConditionalCommitKey(value: string): string;
|
|
94
|
+
export declare function canonicalizeConditionalCommitPayload(value: unknown): Uint8Array;
|
|
95
|
+
export declare function conditionalCommitFingerprintPreimage(input: ConditionalCommitFingerprintInput): Uint8Array;
|
|
96
|
+
export declare function computeConditionalCommitFingerprint(input: ConditionalCommitFingerprintInput): Promise<string>;
|
|
97
|
+
export declare function parseSerializedConditionalCommitRequest(value: unknown): SerializedConditionalCommitRequest;
|
|
98
|
+
export declare function parseSerializedConditionalCommitRequestBody(body: string | Uint8Array): SerializedConditionalCommitRequest;
|
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conditional-commit wire values and deterministic helpers for the public
|
|
3
|
+
* versioned conditional-commit route.
|
|
4
|
+
*/
|
|
5
|
+
export const CONDITIONAL_COMMIT_VERSION = 1;
|
|
6
|
+
export const CONDITIONAL_COMMIT_DERIVATION_VERSION = 2;
|
|
7
|
+
export const CONDITIONAL_COMMIT_CANONICALIZATION_VERSION = 1;
|
|
8
|
+
export const CONDITIONAL_COMMIT_DOMAIN_SEPARATOR = "sekiban.dcb.conditional-append.unique-key";
|
|
9
|
+
export const CONDITIONAL_COMMIT_MAX_UTF8_FIELD_BYTES = 512;
|
|
10
|
+
export const CONDITIONAL_COMMIT_MAX_PAYLOAD_BYTES = 192 * 1024;
|
|
11
|
+
export const CONDITIONAL_COMMIT_MAX_REQUEST_BYTES = 320 * 1024;
|
|
12
|
+
const INT32_MIN = -2147483648;
|
|
13
|
+
const INT32_MAX = 2147483647;
|
|
14
|
+
const BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
15
|
+
const BASE64_INDEX = new Map([...BASE64_ALPHABET].map((char, index) => [char, index]));
|
|
16
|
+
const VALIDATION_MESSAGES = Object.freeze({
|
|
17
|
+
conditional_commit_invalid_json: "The request body is not valid JSON.",
|
|
18
|
+
conditional_commit_unknown_member: "The request contains an unknown member.",
|
|
19
|
+
conditional_commit_invalid_version: "The conditional commit version is not supported.",
|
|
20
|
+
conditional_commit_invalid_envelope: "The conditional commit envelope is invalid.",
|
|
21
|
+
conditional_commit_invalid_key: "The idempotency key is invalid.",
|
|
22
|
+
conditional_commit_invalid_candidate: "The event candidate is invalid.",
|
|
23
|
+
conditional_commit_invalid_base64: "The event payload is not valid base64.",
|
|
24
|
+
conditional_commit_invalid_utf8: "The event payload is not valid UTF-8.",
|
|
25
|
+
conditional_commit_invalid_payload_json: "The event payload is not valid JSON.",
|
|
26
|
+
conditional_commit_payload_root_not_object: "The event payload root must be a plain object.",
|
|
27
|
+
conditional_commit_payload_not_canonicalizable: "The event payload cannot be canonicalized.",
|
|
28
|
+
conditional_commit_event_not_registered: "The event payload name is not registered.",
|
|
29
|
+
conditional_commit_invalid_tag_count: "Conditional commit requires exactly one tag.",
|
|
30
|
+
conditional_commit_size_limit_exceeded: "The conditional commit request exceeds a size limit.",
|
|
31
|
+
});
|
|
32
|
+
export class ConditionalCommitProtocolError extends Error {
|
|
33
|
+
code;
|
|
34
|
+
retryable = false;
|
|
35
|
+
constructor(code) {
|
|
36
|
+
super(VALIDATION_MESSAGES[code]);
|
|
37
|
+
this.name = "ConditionalCommitProtocolError";
|
|
38
|
+
this.code = code;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function fail(code) {
|
|
42
|
+
throw new ConditionalCommitProtocolError(code);
|
|
43
|
+
}
|
|
44
|
+
/** Ill-formed UTF-16 must fail before trim, NFC, or UTF-8 replacement. */
|
|
45
|
+
function assertWellFormedUtf16(value, code) {
|
|
46
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
47
|
+
const unit = value.charCodeAt(index);
|
|
48
|
+
if (unit >= 0xd800 && unit <= 0xdbff) {
|
|
49
|
+
const next = index + 1 < value.length ? value.charCodeAt(index + 1) : -1;
|
|
50
|
+
if (next < 0xdc00 || next > 0xdfff)
|
|
51
|
+
fail(code);
|
|
52
|
+
index += 1;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (unit >= 0xdc00 && unit <= 0xdfff)
|
|
56
|
+
fail(code);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function isTrimCodePoint(codePoint) {
|
|
60
|
+
return (codePoint >= 0x0009 && codePoint <= 0x000d)
|
|
61
|
+
|| codePoint === 0x0020
|
|
62
|
+
|| codePoint === 0x0085
|
|
63
|
+
|| codePoint === 0x00a0
|
|
64
|
+
|| codePoint === 0x1680
|
|
65
|
+
|| (codePoint >= 0x2000 && codePoint <= 0x200a)
|
|
66
|
+
|| codePoint === 0x2028
|
|
67
|
+
|| codePoint === 0x2029
|
|
68
|
+
|| codePoint === 0x202f
|
|
69
|
+
|| codePoint === 0x205f
|
|
70
|
+
|| codePoint === 0x3000;
|
|
71
|
+
}
|
|
72
|
+
function trimDotNet(value) {
|
|
73
|
+
let start = 0;
|
|
74
|
+
let end = value.length;
|
|
75
|
+
while (start < end) {
|
|
76
|
+
const codePoint = value.codePointAt(start);
|
|
77
|
+
if (codePoint === undefined || !isTrimCodePoint(codePoint))
|
|
78
|
+
break;
|
|
79
|
+
start += codePoint > 0xffff ? 2 : 1;
|
|
80
|
+
}
|
|
81
|
+
while (end > start) {
|
|
82
|
+
const previous = value.charCodeAt(end - 1);
|
|
83
|
+
let codePoint = previous;
|
|
84
|
+
let width = 1;
|
|
85
|
+
if (previous >= 0xdc00 && previous <= 0xdfff && end >= 2) {
|
|
86
|
+
const high = value.charCodeAt(end - 2);
|
|
87
|
+
if (high >= 0xd800 && high <= 0xdbff) {
|
|
88
|
+
codePoint = value.codePointAt(end - 2) ?? previous;
|
|
89
|
+
width = 2;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (!isTrimCodePoint(codePoint))
|
|
93
|
+
break;
|
|
94
|
+
end -= width;
|
|
95
|
+
}
|
|
96
|
+
return value.slice(start, end);
|
|
97
|
+
}
|
|
98
|
+
export function normalizeConditionalCommitKey(value) {
|
|
99
|
+
if (typeof value !== "string")
|
|
100
|
+
fail("conditional_commit_invalid_key");
|
|
101
|
+
assertWellFormedUtf16(value, "conditional_commit_invalid_key");
|
|
102
|
+
const normalized = trimDotNet(value).normalize("NFC");
|
|
103
|
+
const size = new TextEncoder().encode(normalized).byteLength;
|
|
104
|
+
if (size < 1 || size > CONDITIONAL_COMMIT_MAX_UTF8_FIELD_BYTES)
|
|
105
|
+
fail("conditional_commit_invalid_key");
|
|
106
|
+
return normalized;
|
|
107
|
+
}
|
|
108
|
+
function isPlainRecord(value) {
|
|
109
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
110
|
+
return false;
|
|
111
|
+
const prototype = Object.getPrototypeOf(value);
|
|
112
|
+
return prototype === Object.prototype || prototype === null;
|
|
113
|
+
}
|
|
114
|
+
function emitValue(value, active) {
|
|
115
|
+
if (value === null)
|
|
116
|
+
return "null";
|
|
117
|
+
if (typeof value === "boolean")
|
|
118
|
+
return value ? "true" : "false";
|
|
119
|
+
if (typeof value === "string") {
|
|
120
|
+
assertWellFormedUtf16(value, "conditional_commit_payload_not_canonicalizable");
|
|
121
|
+
return JSON.stringify(value);
|
|
122
|
+
}
|
|
123
|
+
if (typeof value === "number") {
|
|
124
|
+
if (!Number.isFinite(value))
|
|
125
|
+
fail("conditional_commit_payload_not_canonicalizable");
|
|
126
|
+
return JSON.stringify(value);
|
|
127
|
+
}
|
|
128
|
+
if (typeof value !== "object")
|
|
129
|
+
fail("conditional_commit_payload_not_canonicalizable");
|
|
130
|
+
if (Array.isArray(value))
|
|
131
|
+
return emitArray(value, active);
|
|
132
|
+
if (!isPlainRecord(value))
|
|
133
|
+
fail("conditional_commit_payload_not_canonicalizable");
|
|
134
|
+
return emitRecord(value, active);
|
|
135
|
+
}
|
|
136
|
+
function emitArray(value, active) {
|
|
137
|
+
if (active.has(value))
|
|
138
|
+
fail("conditional_commit_payload_not_canonicalizable");
|
|
139
|
+
const keys = Reflect.ownKeys(value);
|
|
140
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
|
|
141
|
+
if (lengthDescriptor === undefined
|
|
142
|
+
|| lengthDescriptor.enumerable === true
|
|
143
|
+
|| lengthDescriptor.get !== undefined
|
|
144
|
+
|| lengthDescriptor.set !== undefined) {
|
|
145
|
+
fail("conditional_commit_payload_not_canonicalizable");
|
|
146
|
+
}
|
|
147
|
+
for (const key of keys) {
|
|
148
|
+
if (key === "length")
|
|
149
|
+
continue;
|
|
150
|
+
if (typeof key !== "string" || !/^(?:0|[1-9]\d*)$/.test(key) || Number(key) >= value.length) {
|
|
151
|
+
fail("conditional_commit_payload_not_canonicalizable");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const parts = [];
|
|
155
|
+
active.add(value);
|
|
156
|
+
try {
|
|
157
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
158
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
159
|
+
if (descriptor === undefined
|
|
160
|
+
|| descriptor.enumerable !== true
|
|
161
|
+
|| descriptor.get !== undefined
|
|
162
|
+
|| descriptor.set !== undefined
|
|
163
|
+
|| !("value" in descriptor)) {
|
|
164
|
+
fail("conditional_commit_payload_not_canonicalizable");
|
|
165
|
+
}
|
|
166
|
+
parts.push(emitValue(descriptor.value, active));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
finally {
|
|
170
|
+
active.delete(value);
|
|
171
|
+
}
|
|
172
|
+
return `[${parts.join(",")}]`;
|
|
173
|
+
}
|
|
174
|
+
/** Object keys are emitted in UTF-16 order. JSON.stringify would reorder integer-like names. */
|
|
175
|
+
function emitRecord(value, active) {
|
|
176
|
+
if (active.has(value))
|
|
177
|
+
fail("conditional_commit_payload_not_canonicalizable");
|
|
178
|
+
const keys = Reflect.ownKeys(value);
|
|
179
|
+
const names = [];
|
|
180
|
+
const values = new Map();
|
|
181
|
+
for (const key of keys) {
|
|
182
|
+
if (typeof key !== "string")
|
|
183
|
+
fail("conditional_commit_payload_not_canonicalizable");
|
|
184
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
185
|
+
if (descriptor === undefined
|
|
186
|
+
|| descriptor.enumerable !== true
|
|
187
|
+
|| descriptor.get !== undefined
|
|
188
|
+
|| descriptor.set !== undefined
|
|
189
|
+
|| !("value" in descriptor)) {
|
|
190
|
+
fail("conditional_commit_payload_not_canonicalizable");
|
|
191
|
+
}
|
|
192
|
+
names.push(key);
|
|
193
|
+
values.set(key, descriptor.value);
|
|
194
|
+
}
|
|
195
|
+
names.sort();
|
|
196
|
+
const parts = [];
|
|
197
|
+
active.add(value);
|
|
198
|
+
try {
|
|
199
|
+
for (const key of names) {
|
|
200
|
+
parts.push(`${JSON.stringify(key)}:${emitValue(values.get(key), active)}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
active.delete(value);
|
|
205
|
+
}
|
|
206
|
+
return `{${parts.join(",")}}`;
|
|
207
|
+
}
|
|
208
|
+
export function canonicalizeConditionalCommitPayload(value) {
|
|
209
|
+
if (!isPlainRecord(value))
|
|
210
|
+
fail("conditional_commit_payload_root_not_object");
|
|
211
|
+
const bytes = new TextEncoder().encode(emitRecord(value, new WeakSet()));
|
|
212
|
+
if (bytes.byteLength > CONDITIONAL_COMMIT_MAX_PAYLOAD_BYTES)
|
|
213
|
+
fail("conditional_commit_size_limit_exceeded");
|
|
214
|
+
return bytes;
|
|
215
|
+
}
|
|
216
|
+
function appendInt32BE(target, value) {
|
|
217
|
+
if (!Number.isSafeInteger(value) || value < INT32_MIN || value > INT32_MAX) {
|
|
218
|
+
fail("conditional_commit_size_limit_exceeded");
|
|
219
|
+
}
|
|
220
|
+
target.push((value >>> 24) & 0xff, (value >>> 16) & 0xff, (value >>> 8) & 0xff, value & 0xff);
|
|
221
|
+
}
|
|
222
|
+
function appendUtf8(target, value, code, maxBytes) {
|
|
223
|
+
if (typeof value !== "string")
|
|
224
|
+
fail(code);
|
|
225
|
+
assertWellFormedUtf16(value, code);
|
|
226
|
+
const bytes = new TextEncoder().encode(value);
|
|
227
|
+
if (bytes.byteLength < 1)
|
|
228
|
+
fail(code);
|
|
229
|
+
if (maxBytes !== undefined && bytes.byteLength > maxBytes)
|
|
230
|
+
fail("conditional_commit_size_limit_exceeded");
|
|
231
|
+
appendInt32BE(target, bytes.byteLength);
|
|
232
|
+
for (const byte of bytes)
|
|
233
|
+
target.push(byte);
|
|
234
|
+
}
|
|
235
|
+
function appendPayload(target, payload) {
|
|
236
|
+
if (!(payload instanceof Uint8Array))
|
|
237
|
+
fail("conditional_commit_payload_not_canonicalizable");
|
|
238
|
+
if (!Number.isSafeInteger(payload.byteLength) || payload.byteLength < 0) {
|
|
239
|
+
fail("conditional_commit_size_limit_exceeded");
|
|
240
|
+
}
|
|
241
|
+
if (payload.byteLength > CONDITIONAL_COMMIT_MAX_PAYLOAD_BYTES)
|
|
242
|
+
fail("conditional_commit_size_limit_exceeded");
|
|
243
|
+
appendInt32BE(target, payload.byteLength);
|
|
244
|
+
for (let index = 0; index < payload.byteLength; index += 1) {
|
|
245
|
+
const byte = payload[index];
|
|
246
|
+
if (byte === undefined)
|
|
247
|
+
fail("conditional_commit_payload_not_canonicalizable");
|
|
248
|
+
target.push(byte);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
export function conditionalCommitFingerprintPreimage(input) {
|
|
252
|
+
if (typeof input !== "object" || input === null)
|
|
253
|
+
fail("conditional_commit_invalid_envelope");
|
|
254
|
+
const key = normalizeConditionalCommitKey(input.idempotencyKey);
|
|
255
|
+
const bytes = [];
|
|
256
|
+
appendInt32BE(bytes, CONDITIONAL_COMMIT_DERIVATION_VERSION);
|
|
257
|
+
appendInt32BE(bytes, CONDITIONAL_COMMIT_CANONICALIZATION_VERSION);
|
|
258
|
+
appendUtf8(bytes, CONDITIONAL_COMMIT_DOMAIN_SEPARATOR, "conditional_commit_invalid_envelope");
|
|
259
|
+
appendUtf8(bytes, input.serviceId, "conditional_commit_invalid_envelope");
|
|
260
|
+
appendUtf8(bytes, key, "conditional_commit_invalid_key", CONDITIONAL_COMMIT_MAX_UTF8_FIELD_BYTES);
|
|
261
|
+
appendUtf8(bytes, input.eventPayloadName, "conditional_commit_invalid_candidate", CONDITIONAL_COMMIT_MAX_UTF8_FIELD_BYTES);
|
|
262
|
+
appendPayload(bytes, input.canonicalPayload);
|
|
263
|
+
appendInt32BE(bytes, 1);
|
|
264
|
+
appendUtf8(bytes, input.tag, "conditional_commit_invalid_candidate", CONDITIONAL_COMMIT_MAX_UTF8_FIELD_BYTES);
|
|
265
|
+
return Uint8Array.from(bytes);
|
|
266
|
+
}
|
|
267
|
+
export async function computeConditionalCommitFingerprint(input) {
|
|
268
|
+
const preimage = conditionalCommitFingerprintPreimage(input);
|
|
269
|
+
const buffer = new ArrayBuffer(preimage.byteLength);
|
|
270
|
+
new Uint8Array(buffer).set(preimage);
|
|
271
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", buffer);
|
|
272
|
+
let hex = "";
|
|
273
|
+
for (const byte of new Uint8Array(digest))
|
|
274
|
+
hex += byte.toString(16).padStart(2, "0");
|
|
275
|
+
return hex;
|
|
276
|
+
}
|
|
277
|
+
const REQUEST_KEYS = ["version", "idempotencyKey", "eventCandidate"];
|
|
278
|
+
const CANDIDATE_KEYS = ["payload", "eventPayloadName", "tags"];
|
|
279
|
+
function descriptorValue(value, key, code) {
|
|
280
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
281
|
+
if (descriptor === undefined
|
|
282
|
+
|| descriptor.enumerable !== true
|
|
283
|
+
|| descriptor.get !== undefined
|
|
284
|
+
|| descriptor.set !== undefined
|
|
285
|
+
|| !("value" in descriptor)) {
|
|
286
|
+
fail(code);
|
|
287
|
+
}
|
|
288
|
+
return descriptor.value;
|
|
289
|
+
}
|
|
290
|
+
function assertClosedKeys(value, allowed, shapeCode) {
|
|
291
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
292
|
+
if (typeof key !== "string" || !allowed.includes(key))
|
|
293
|
+
fail("conditional_commit_unknown_member");
|
|
294
|
+
descriptorValue(value, key, shapeCode);
|
|
295
|
+
}
|
|
296
|
+
for (const key of allowed) {
|
|
297
|
+
if (!Object.prototype.hasOwnProperty.call(value, key))
|
|
298
|
+
fail(shapeCode);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function readTag(value) {
|
|
302
|
+
if (!Array.isArray(value))
|
|
303
|
+
fail("conditional_commit_invalid_candidate");
|
|
304
|
+
const keys = Reflect.ownKeys(value);
|
|
305
|
+
if (keys.some((key) => typeof key === "symbol"))
|
|
306
|
+
fail("conditional_commit_unknown_member");
|
|
307
|
+
if (value.length !== 1)
|
|
308
|
+
fail("conditional_commit_invalid_tag_count");
|
|
309
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
|
|
310
|
+
if (lengthDescriptor === undefined
|
|
311
|
+
|| lengthDescriptor.enumerable === true
|
|
312
|
+
|| lengthDescriptor.get !== undefined
|
|
313
|
+
|| lengthDescriptor.set !== undefined) {
|
|
314
|
+
fail("conditional_commit_invalid_candidate");
|
|
315
|
+
}
|
|
316
|
+
for (const key of keys) {
|
|
317
|
+
if (key === "length")
|
|
318
|
+
continue;
|
|
319
|
+
if (key !== "0")
|
|
320
|
+
fail("conditional_commit_invalid_candidate");
|
|
321
|
+
}
|
|
322
|
+
const tag = descriptorValue(value, "0", "conditional_commit_invalid_candidate");
|
|
323
|
+
if (typeof tag !== "string")
|
|
324
|
+
fail("conditional_commit_invalid_candidate");
|
|
325
|
+
return tag;
|
|
326
|
+
}
|
|
327
|
+
function boundedField(value, code) {
|
|
328
|
+
if (typeof value !== "string")
|
|
329
|
+
fail(code);
|
|
330
|
+
assertWellFormedUtf16(value, code);
|
|
331
|
+
const size = new TextEncoder().encode(value).byteLength;
|
|
332
|
+
if (size < 1)
|
|
333
|
+
fail(code);
|
|
334
|
+
if (size > CONDITIONAL_COMMIT_MAX_UTF8_FIELD_BYTES)
|
|
335
|
+
fail("conditional_commit_size_limit_exceeded");
|
|
336
|
+
return value;
|
|
337
|
+
}
|
|
338
|
+
function encodeBase64(bytes) {
|
|
339
|
+
let output = "";
|
|
340
|
+
let index = 0;
|
|
341
|
+
for (; index + 2 < bytes.length; index += 3) {
|
|
342
|
+
const chunk = ((bytes[index] ?? 0) << 16) | ((bytes[index + 1] ?? 0) << 8) | (bytes[index + 2] ?? 0);
|
|
343
|
+
output += BASE64_ALPHABET[(chunk >>> 18) & 63]
|
|
344
|
+
+ BASE64_ALPHABET[(chunk >>> 12) & 63]
|
|
345
|
+
+ BASE64_ALPHABET[(chunk >>> 6) & 63]
|
|
346
|
+
+ BASE64_ALPHABET[chunk & 63];
|
|
347
|
+
}
|
|
348
|
+
if (index < bytes.length) {
|
|
349
|
+
const first = bytes[index] ?? 0;
|
|
350
|
+
const second = index + 1 < bytes.length ? bytes[index + 1] ?? 0 : 0;
|
|
351
|
+
const chunk = (first << 16) | (second << 8);
|
|
352
|
+
output += BASE64_ALPHABET[(chunk >>> 18) & 63];
|
|
353
|
+
output += BASE64_ALPHABET[(chunk >>> 12) & 63];
|
|
354
|
+
output += index + 1 < bytes.length ? BASE64_ALPHABET[(chunk >>> 6) & 63] : "=";
|
|
355
|
+
output += "=";
|
|
356
|
+
}
|
|
357
|
+
return output;
|
|
358
|
+
}
|
|
359
|
+
function decodeCanonicalBase64(text) {
|
|
360
|
+
if (typeof text !== "string" || text.length === 0 || text.length % 4 !== 0)
|
|
361
|
+
fail("conditional_commit_invalid_base64");
|
|
362
|
+
const padding = text.endsWith("==") ? 2 : text.endsWith("=") ? 1 : 0;
|
|
363
|
+
if (padding === 1 && text.endsWith("=="))
|
|
364
|
+
fail("conditional_commit_invalid_base64");
|
|
365
|
+
const body = text.slice(0, text.length - padding);
|
|
366
|
+
if (body.includes("="))
|
|
367
|
+
fail("conditional_commit_invalid_base64");
|
|
368
|
+
for (const char of body) {
|
|
369
|
+
if (!BASE64_INDEX.has(char))
|
|
370
|
+
fail("conditional_commit_invalid_base64");
|
|
371
|
+
}
|
|
372
|
+
const decodedLength = (text.length / 4) * 3 - padding;
|
|
373
|
+
if (!Number.isSafeInteger(decodedLength) || decodedLength < 0)
|
|
374
|
+
fail("conditional_commit_invalid_base64");
|
|
375
|
+
if (decodedLength > CONDITIONAL_COMMIT_MAX_PAYLOAD_BYTES)
|
|
376
|
+
fail("conditional_commit_size_limit_exceeded");
|
|
377
|
+
const bytes = new Uint8Array(decodedLength);
|
|
378
|
+
let offset = 0;
|
|
379
|
+
for (let index = 0; index < text.length; index += 4) {
|
|
380
|
+
const last = index + 4 >= text.length;
|
|
381
|
+
const a = BASE64_INDEX.get(text[index] ?? "");
|
|
382
|
+
const b = BASE64_INDEX.get(text[index + 1] ?? "");
|
|
383
|
+
const cChar = text[index + 2] ?? "";
|
|
384
|
+
const dChar = text[index + 3] ?? "";
|
|
385
|
+
const c = last && cChar === "=" ? 0 : BASE64_INDEX.get(cChar);
|
|
386
|
+
const d = last && dChar === "=" ? 0 : BASE64_INDEX.get(dChar);
|
|
387
|
+
if (a === undefined || b === undefined || c === undefined || d === undefined)
|
|
388
|
+
fail("conditional_commit_invalid_base64");
|
|
389
|
+
const chunk = (a << 18) | (b << 12) | (c << 6) | d;
|
|
390
|
+
const produced = last ? 3 - padding : 3;
|
|
391
|
+
bytes[offset] = (chunk >>> 16) & 0xff;
|
|
392
|
+
if (produced > 1)
|
|
393
|
+
bytes[offset + 1] = (chunk >>> 8) & 0xff;
|
|
394
|
+
if (produced > 2)
|
|
395
|
+
bytes[offset + 2] = chunk & 0xff;
|
|
396
|
+
offset += produced;
|
|
397
|
+
}
|
|
398
|
+
if (offset !== decodedLength || encodeBase64(bytes) !== text)
|
|
399
|
+
fail("conditional_commit_invalid_base64");
|
|
400
|
+
return bytes;
|
|
401
|
+
}
|
|
402
|
+
function decodeStrictUtf8(bytes) {
|
|
403
|
+
try {
|
|
404
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
405
|
+
}
|
|
406
|
+
catch {
|
|
407
|
+
fail("conditional_commit_invalid_utf8");
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
export function parseSerializedConditionalCommitRequest(value) {
|
|
411
|
+
if (!isPlainRecord(value))
|
|
412
|
+
fail("conditional_commit_invalid_envelope");
|
|
413
|
+
assertClosedKeys(value, REQUEST_KEYS, "conditional_commit_invalid_envelope");
|
|
414
|
+
if (descriptorValue(value, "version", "conditional_commit_invalid_envelope") !== 1) {
|
|
415
|
+
fail("conditional_commit_invalid_version");
|
|
416
|
+
}
|
|
417
|
+
const idempotencyKey = normalizeConditionalCommitKey(descriptorValue(value, "idempotencyKey", "conditional_commit_invalid_key"));
|
|
418
|
+
const candidate = descriptorValue(value, "eventCandidate", "conditional_commit_invalid_candidate");
|
|
419
|
+
if (!isPlainRecord(candidate))
|
|
420
|
+
fail("conditional_commit_invalid_candidate");
|
|
421
|
+
assertClosedKeys(candidate, CANDIDATE_KEYS, "conditional_commit_invalid_candidate");
|
|
422
|
+
const payloadText = descriptorValue(candidate, "payload", "conditional_commit_invalid_candidate");
|
|
423
|
+
if (typeof payloadText !== "string")
|
|
424
|
+
fail("conditional_commit_invalid_candidate");
|
|
425
|
+
const eventPayloadName = boundedField(descriptorValue(candidate, "eventPayloadName", "conditional_commit_invalid_candidate"), "conditional_commit_invalid_candidate");
|
|
426
|
+
const tag = boundedField(readTag(descriptorValue(candidate, "tags", "conditional_commit_invalid_candidate")), "conditional_commit_invalid_candidate");
|
|
427
|
+
const decoded = decodeCanonicalBase64(payloadText);
|
|
428
|
+
const jsonText = decodeStrictUtf8(decoded);
|
|
429
|
+
let parsed;
|
|
430
|
+
try {
|
|
431
|
+
parsed = JSON.parse(jsonText);
|
|
432
|
+
}
|
|
433
|
+
catch {
|
|
434
|
+
fail("conditional_commit_invalid_payload_json");
|
|
435
|
+
}
|
|
436
|
+
if (!isPlainRecord(parsed))
|
|
437
|
+
fail("conditional_commit_payload_root_not_object");
|
|
438
|
+
canonicalizeConditionalCommitPayload(parsed);
|
|
439
|
+
return {
|
|
440
|
+
version: 1,
|
|
441
|
+
idempotencyKey,
|
|
442
|
+
eventCandidate: {
|
|
443
|
+
payload: payloadText,
|
|
444
|
+
eventPayloadName,
|
|
445
|
+
tags: [tag],
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
export function parseSerializedConditionalCommitRequestBody(body) {
|
|
450
|
+
let text;
|
|
451
|
+
if (typeof body === "string") {
|
|
452
|
+
assertWellFormedUtf16(body, "conditional_commit_invalid_json");
|
|
453
|
+
if (new TextEncoder().encode(body).byteLength > CONDITIONAL_COMMIT_MAX_REQUEST_BYTES) {
|
|
454
|
+
fail("conditional_commit_size_limit_exceeded");
|
|
455
|
+
}
|
|
456
|
+
text = body;
|
|
457
|
+
}
|
|
458
|
+
else if (body instanceof Uint8Array) {
|
|
459
|
+
if (body.byteLength > CONDITIONAL_COMMIT_MAX_REQUEST_BYTES)
|
|
460
|
+
fail("conditional_commit_size_limit_exceeded");
|
|
461
|
+
try {
|
|
462
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(body);
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
fail("conditional_commit_invalid_json");
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
fail("conditional_commit_invalid_json");
|
|
470
|
+
}
|
|
471
|
+
let value;
|
|
472
|
+
try {
|
|
473
|
+
value = JSON.parse(text);
|
|
474
|
+
}
|
|
475
|
+
catch {
|
|
476
|
+
fail("conditional_commit_invalid_json");
|
|
477
|
+
}
|
|
478
|
+
return parseSerializedConditionalCommitRequest(value);
|
|
479
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -216,4 +216,57 @@ export type DomainDefinitionOptions = {
|
|
|
216
216
|
readonly mvs?: readonly DomainComponentDefinition[];
|
|
217
217
|
};
|
|
218
218
|
export declare function defineDomain(options: DomainDefinitionOptions): DomainDefinition;
|
|
219
|
+
export type QueryJsonValue = null | boolean | number | string | readonly QueryJsonValue[] | {
|
|
220
|
+
readonly [key: string]: QueryJsonValue;
|
|
221
|
+
};
|
|
222
|
+
export interface QueryCodec<Value> {
|
|
223
|
+
readonly encode: (value: Value) => QueryJsonValue;
|
|
224
|
+
readonly decode: (value: unknown) => Value;
|
|
225
|
+
}
|
|
226
|
+
export type QueryRow<Row> = Readonly<{
|
|
227
|
+
key: string;
|
|
228
|
+
value: Readonly<Row>;
|
|
229
|
+
sourceSuid: string;
|
|
230
|
+
}>;
|
|
231
|
+
export type QueryProjection<Row> = Readonly<{
|
|
232
|
+
rows: readonly QueryRow<Row>[];
|
|
233
|
+
readHead?: string;
|
|
234
|
+
}>;
|
|
235
|
+
export interface QueryHandlerContext {
|
|
236
|
+
readonly readHead?: string;
|
|
237
|
+
}
|
|
238
|
+
export interface QueryContract<Params, Result> {
|
|
239
|
+
readonly kind: "query";
|
|
240
|
+
readonly id: string;
|
|
241
|
+
readonly version: number;
|
|
242
|
+
readonly params: QueryCodec<Params>;
|
|
243
|
+
readonly result: QueryCodec<Result>;
|
|
244
|
+
}
|
|
245
|
+
export interface ListQueryContract<Params, Item> {
|
|
246
|
+
readonly kind: "list-query";
|
|
247
|
+
readonly id: string;
|
|
248
|
+
readonly version: number;
|
|
249
|
+
readonly params: QueryCodec<Params>;
|
|
250
|
+
readonly item: QueryCodec<Item>;
|
|
251
|
+
}
|
|
252
|
+
export interface ListQueryControls {
|
|
253
|
+
readonly pageNumber?: number;
|
|
254
|
+
readonly pageSize?: number;
|
|
255
|
+
readonly descending?: boolean;
|
|
256
|
+
readonly consistency?: "safe" | "unsafe";
|
|
257
|
+
readonly waitForSortableUniqueId?: string;
|
|
258
|
+
readonly signal?: AbortSignal;
|
|
259
|
+
}
|
|
260
|
+
export interface QueryPageMetadata {
|
|
261
|
+
readonly totalCount: number;
|
|
262
|
+
readonly totalPages: number;
|
|
263
|
+
readonly currentPage: number;
|
|
264
|
+
readonly pageSize: number;
|
|
265
|
+
readonly readHead?: string;
|
|
266
|
+
}
|
|
267
|
+
export interface QueryPage<Item> extends QueryPageMetadata {
|
|
268
|
+
readonly items: readonly Item[];
|
|
269
|
+
}
|
|
270
|
+
export { CONDITIONAL_COMMIT_CANONICALIZATION_VERSION, CONDITIONAL_COMMIT_DERIVATION_VERSION, CONDITIONAL_COMMIT_DOMAIN_SEPARATOR, CONDITIONAL_COMMIT_MAX_PAYLOAD_BYTES, CONDITIONAL_COMMIT_MAX_REQUEST_BYTES, CONDITIONAL_COMMIT_MAX_UTF8_FIELD_BYTES, CONDITIONAL_COMMIT_VERSION, ConditionalCommitProtocolError, canonicalizeConditionalCommitPayload, computeConditionalCommitFingerprint, conditionalCommitFingerprintPreimage, normalizeConditionalCommitKey, parseSerializedConditionalCommitRequest, parseSerializedConditionalCommitRequestBody, } from "./conditionalCommit.js";
|
|
271
|
+
export type { ConditionalCommitErrorResponse, ConditionalCommitFingerprintInput, ConditionalCommitInDoubtReason, ConditionalCommitReceipt, ConditionalCommitResult, ConditionalCommitValidationCode, ConditionalWrittenEvent, SerializedConditionalCommitRequest, SerializedConditionalEventCandidate, } from "./conditionalCommit.js";
|
|
219
272
|
export * from "./materializedView.js";
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,395 @@
|
|
|
1
|
+
// src/conditionalCommit.ts
|
|
2
|
+
var CONDITIONAL_COMMIT_VERSION = 1;
|
|
3
|
+
var CONDITIONAL_COMMIT_DERIVATION_VERSION = 2;
|
|
4
|
+
var CONDITIONAL_COMMIT_CANONICALIZATION_VERSION = 1;
|
|
5
|
+
var CONDITIONAL_COMMIT_DOMAIN_SEPARATOR = "sekiban.dcb.conditional-append.unique-key";
|
|
6
|
+
var CONDITIONAL_COMMIT_MAX_UTF8_FIELD_BYTES = 512;
|
|
7
|
+
var CONDITIONAL_COMMIT_MAX_PAYLOAD_BYTES = 192 * 1024;
|
|
8
|
+
var CONDITIONAL_COMMIT_MAX_REQUEST_BYTES = 320 * 1024;
|
|
9
|
+
var INT32_MIN = -2147483648;
|
|
10
|
+
var INT32_MAX = 2147483647;
|
|
11
|
+
var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
12
|
+
var BASE64_INDEX = new Map([...BASE64_ALPHABET].map((char, index) => [char, index]));
|
|
13
|
+
var VALIDATION_MESSAGES = Object.freeze({
|
|
14
|
+
conditional_commit_invalid_json: "The request body is not valid JSON.",
|
|
15
|
+
conditional_commit_unknown_member: "The request contains an unknown member.",
|
|
16
|
+
conditional_commit_invalid_version: "The conditional commit version is not supported.",
|
|
17
|
+
conditional_commit_invalid_envelope: "The conditional commit envelope is invalid.",
|
|
18
|
+
conditional_commit_invalid_key: "The idempotency key is invalid.",
|
|
19
|
+
conditional_commit_invalid_candidate: "The event candidate is invalid.",
|
|
20
|
+
conditional_commit_invalid_base64: "The event payload is not valid base64.",
|
|
21
|
+
conditional_commit_invalid_utf8: "The event payload is not valid UTF-8.",
|
|
22
|
+
conditional_commit_invalid_payload_json: "The event payload is not valid JSON.",
|
|
23
|
+
conditional_commit_payload_root_not_object: "The event payload root must be a plain object.",
|
|
24
|
+
conditional_commit_payload_not_canonicalizable: "The event payload cannot be canonicalized.",
|
|
25
|
+
conditional_commit_event_not_registered: "The event payload name is not registered.",
|
|
26
|
+
conditional_commit_invalid_tag_count: "Conditional commit requires exactly one tag.",
|
|
27
|
+
conditional_commit_size_limit_exceeded: "The conditional commit request exceeds a size limit."
|
|
28
|
+
});
|
|
29
|
+
var ConditionalCommitProtocolError = class extends Error {
|
|
30
|
+
code;
|
|
31
|
+
retryable = false;
|
|
32
|
+
constructor(code) {
|
|
33
|
+
super(VALIDATION_MESSAGES[code]);
|
|
34
|
+
this.name = "ConditionalCommitProtocolError";
|
|
35
|
+
this.code = code;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
function fail(code) {
|
|
39
|
+
throw new ConditionalCommitProtocolError(code);
|
|
40
|
+
}
|
|
41
|
+
function assertWellFormedUtf16(value, code) {
|
|
42
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
43
|
+
const unit = value.charCodeAt(index);
|
|
44
|
+
if (unit >= 55296 && unit <= 56319) {
|
|
45
|
+
const next = index + 1 < value.length ? value.charCodeAt(index + 1) : -1;
|
|
46
|
+
if (next < 56320 || next > 57343) fail(code);
|
|
47
|
+
index += 1;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (unit >= 56320 && unit <= 57343) fail(code);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function isTrimCodePoint(codePoint) {
|
|
54
|
+
return codePoint >= 9 && codePoint <= 13 || codePoint === 32 || codePoint === 133 || codePoint === 160 || codePoint === 5760 || codePoint >= 8192 && codePoint <= 8202 || codePoint === 8232 || codePoint === 8233 || codePoint === 8239 || codePoint === 8287 || codePoint === 12288;
|
|
55
|
+
}
|
|
56
|
+
function trimDotNet(value) {
|
|
57
|
+
let start = 0;
|
|
58
|
+
let end = value.length;
|
|
59
|
+
while (start < end) {
|
|
60
|
+
const codePoint = value.codePointAt(start);
|
|
61
|
+
if (codePoint === void 0 || !isTrimCodePoint(codePoint)) break;
|
|
62
|
+
start += codePoint > 65535 ? 2 : 1;
|
|
63
|
+
}
|
|
64
|
+
while (end > start) {
|
|
65
|
+
const previous = value.charCodeAt(end - 1);
|
|
66
|
+
let codePoint = previous;
|
|
67
|
+
let width = 1;
|
|
68
|
+
if (previous >= 56320 && previous <= 57343 && end >= 2) {
|
|
69
|
+
const high = value.charCodeAt(end - 2);
|
|
70
|
+
if (high >= 55296 && high <= 56319) {
|
|
71
|
+
codePoint = value.codePointAt(end - 2) ?? previous;
|
|
72
|
+
width = 2;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (!isTrimCodePoint(codePoint)) break;
|
|
76
|
+
end -= width;
|
|
77
|
+
}
|
|
78
|
+
return value.slice(start, end);
|
|
79
|
+
}
|
|
80
|
+
function normalizeConditionalCommitKey(value) {
|
|
81
|
+
if (typeof value !== "string") fail("conditional_commit_invalid_key");
|
|
82
|
+
assertWellFormedUtf16(value, "conditional_commit_invalid_key");
|
|
83
|
+
const normalized = trimDotNet(value).normalize("NFC");
|
|
84
|
+
const size = new TextEncoder().encode(normalized).byteLength;
|
|
85
|
+
if (size < 1 || size > CONDITIONAL_COMMIT_MAX_UTF8_FIELD_BYTES) fail("conditional_commit_invalid_key");
|
|
86
|
+
return normalized;
|
|
87
|
+
}
|
|
88
|
+
function isPlainRecord(value) {
|
|
89
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
90
|
+
const prototype = Object.getPrototypeOf(value);
|
|
91
|
+
return prototype === Object.prototype || prototype === null;
|
|
92
|
+
}
|
|
93
|
+
function emitValue(value, active) {
|
|
94
|
+
if (value === null) return "null";
|
|
95
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
96
|
+
if (typeof value === "string") {
|
|
97
|
+
assertWellFormedUtf16(value, "conditional_commit_payload_not_canonicalizable");
|
|
98
|
+
return JSON.stringify(value);
|
|
99
|
+
}
|
|
100
|
+
if (typeof value === "number") {
|
|
101
|
+
if (!Number.isFinite(value)) fail("conditional_commit_payload_not_canonicalizable");
|
|
102
|
+
return JSON.stringify(value);
|
|
103
|
+
}
|
|
104
|
+
if (typeof value !== "object") fail("conditional_commit_payload_not_canonicalizable");
|
|
105
|
+
if (Array.isArray(value)) return emitArray(value, active);
|
|
106
|
+
if (!isPlainRecord(value)) fail("conditional_commit_payload_not_canonicalizable");
|
|
107
|
+
return emitRecord(value, active);
|
|
108
|
+
}
|
|
109
|
+
function emitArray(value, active) {
|
|
110
|
+
if (active.has(value)) fail("conditional_commit_payload_not_canonicalizable");
|
|
111
|
+
const keys = Reflect.ownKeys(value);
|
|
112
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
|
|
113
|
+
if (lengthDescriptor === void 0 || lengthDescriptor.enumerable === true || lengthDescriptor.get !== void 0 || lengthDescriptor.set !== void 0) {
|
|
114
|
+
fail("conditional_commit_payload_not_canonicalizable");
|
|
115
|
+
}
|
|
116
|
+
for (const key of keys) {
|
|
117
|
+
if (key === "length") continue;
|
|
118
|
+
if (typeof key !== "string" || !/^(?:0|[1-9]\d*)$/.test(key) || Number(key) >= value.length) {
|
|
119
|
+
fail("conditional_commit_payload_not_canonicalizable");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const parts = [];
|
|
123
|
+
active.add(value);
|
|
124
|
+
try {
|
|
125
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
126
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
127
|
+
if (descriptor === void 0 || descriptor.enumerable !== true || descriptor.get !== void 0 || descriptor.set !== void 0 || !("value" in descriptor)) {
|
|
128
|
+
fail("conditional_commit_payload_not_canonicalizable");
|
|
129
|
+
}
|
|
130
|
+
parts.push(emitValue(descriptor.value, active));
|
|
131
|
+
}
|
|
132
|
+
} finally {
|
|
133
|
+
active.delete(value);
|
|
134
|
+
}
|
|
135
|
+
return `[${parts.join(",")}]`;
|
|
136
|
+
}
|
|
137
|
+
function emitRecord(value, active) {
|
|
138
|
+
if (active.has(value)) fail("conditional_commit_payload_not_canonicalizable");
|
|
139
|
+
const keys = Reflect.ownKeys(value);
|
|
140
|
+
const names = [];
|
|
141
|
+
const values = /* @__PURE__ */ new Map();
|
|
142
|
+
for (const key of keys) {
|
|
143
|
+
if (typeof key !== "string") fail("conditional_commit_payload_not_canonicalizable");
|
|
144
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
145
|
+
if (descriptor === void 0 || descriptor.enumerable !== true || descriptor.get !== void 0 || descriptor.set !== void 0 || !("value" in descriptor)) {
|
|
146
|
+
fail("conditional_commit_payload_not_canonicalizable");
|
|
147
|
+
}
|
|
148
|
+
names.push(key);
|
|
149
|
+
values.set(key, descriptor.value);
|
|
150
|
+
}
|
|
151
|
+
names.sort();
|
|
152
|
+
const parts = [];
|
|
153
|
+
active.add(value);
|
|
154
|
+
try {
|
|
155
|
+
for (const key of names) {
|
|
156
|
+
parts.push(`${JSON.stringify(key)}:${emitValue(values.get(key), active)}`);
|
|
157
|
+
}
|
|
158
|
+
} finally {
|
|
159
|
+
active.delete(value);
|
|
160
|
+
}
|
|
161
|
+
return `{${parts.join(",")}}`;
|
|
162
|
+
}
|
|
163
|
+
function canonicalizeConditionalCommitPayload(value) {
|
|
164
|
+
if (!isPlainRecord(value)) fail("conditional_commit_payload_root_not_object");
|
|
165
|
+
const bytes = new TextEncoder().encode(emitRecord(value, /* @__PURE__ */ new WeakSet()));
|
|
166
|
+
if (bytes.byteLength > CONDITIONAL_COMMIT_MAX_PAYLOAD_BYTES) fail("conditional_commit_size_limit_exceeded");
|
|
167
|
+
return bytes;
|
|
168
|
+
}
|
|
169
|
+
function appendInt32BE(target, value) {
|
|
170
|
+
if (!Number.isSafeInteger(value) || value < INT32_MIN || value > INT32_MAX) {
|
|
171
|
+
fail("conditional_commit_size_limit_exceeded");
|
|
172
|
+
}
|
|
173
|
+
target.push(value >>> 24 & 255, value >>> 16 & 255, value >>> 8 & 255, value & 255);
|
|
174
|
+
}
|
|
175
|
+
function appendUtf8(target, value, code, maxBytes) {
|
|
176
|
+
if (typeof value !== "string") fail(code);
|
|
177
|
+
assertWellFormedUtf16(value, code);
|
|
178
|
+
const bytes = new TextEncoder().encode(value);
|
|
179
|
+
if (bytes.byteLength < 1) fail(code);
|
|
180
|
+
if (maxBytes !== void 0 && bytes.byteLength > maxBytes) fail("conditional_commit_size_limit_exceeded");
|
|
181
|
+
appendInt32BE(target, bytes.byteLength);
|
|
182
|
+
for (const byte of bytes) target.push(byte);
|
|
183
|
+
}
|
|
184
|
+
function appendPayload(target, payload) {
|
|
185
|
+
if (!(payload instanceof Uint8Array)) fail("conditional_commit_payload_not_canonicalizable");
|
|
186
|
+
if (!Number.isSafeInteger(payload.byteLength) || payload.byteLength < 0) {
|
|
187
|
+
fail("conditional_commit_size_limit_exceeded");
|
|
188
|
+
}
|
|
189
|
+
if (payload.byteLength > CONDITIONAL_COMMIT_MAX_PAYLOAD_BYTES) fail("conditional_commit_size_limit_exceeded");
|
|
190
|
+
appendInt32BE(target, payload.byteLength);
|
|
191
|
+
for (let index = 0; index < payload.byteLength; index += 1) {
|
|
192
|
+
const byte = payload[index];
|
|
193
|
+
if (byte === void 0) fail("conditional_commit_payload_not_canonicalizable");
|
|
194
|
+
target.push(byte);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function conditionalCommitFingerprintPreimage(input) {
|
|
198
|
+
if (typeof input !== "object" || input === null) fail("conditional_commit_invalid_envelope");
|
|
199
|
+
const key = normalizeConditionalCommitKey(input.idempotencyKey);
|
|
200
|
+
const bytes = [];
|
|
201
|
+
appendInt32BE(bytes, CONDITIONAL_COMMIT_DERIVATION_VERSION);
|
|
202
|
+
appendInt32BE(bytes, CONDITIONAL_COMMIT_CANONICALIZATION_VERSION);
|
|
203
|
+
appendUtf8(bytes, CONDITIONAL_COMMIT_DOMAIN_SEPARATOR, "conditional_commit_invalid_envelope");
|
|
204
|
+
appendUtf8(bytes, input.serviceId, "conditional_commit_invalid_envelope");
|
|
205
|
+
appendUtf8(bytes, key, "conditional_commit_invalid_key", CONDITIONAL_COMMIT_MAX_UTF8_FIELD_BYTES);
|
|
206
|
+
appendUtf8(bytes, input.eventPayloadName, "conditional_commit_invalid_candidate", CONDITIONAL_COMMIT_MAX_UTF8_FIELD_BYTES);
|
|
207
|
+
appendPayload(bytes, input.canonicalPayload);
|
|
208
|
+
appendInt32BE(bytes, 1);
|
|
209
|
+
appendUtf8(bytes, input.tag, "conditional_commit_invalid_candidate", CONDITIONAL_COMMIT_MAX_UTF8_FIELD_BYTES);
|
|
210
|
+
return Uint8Array.from(bytes);
|
|
211
|
+
}
|
|
212
|
+
async function computeConditionalCommitFingerprint(input) {
|
|
213
|
+
const preimage = conditionalCommitFingerprintPreimage(input);
|
|
214
|
+
const buffer = new ArrayBuffer(preimage.byteLength);
|
|
215
|
+
new Uint8Array(buffer).set(preimage);
|
|
216
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", buffer);
|
|
217
|
+
let hex = "";
|
|
218
|
+
for (const byte of new Uint8Array(digest)) hex += byte.toString(16).padStart(2, "0");
|
|
219
|
+
return hex;
|
|
220
|
+
}
|
|
221
|
+
var REQUEST_KEYS = ["version", "idempotencyKey", "eventCandidate"];
|
|
222
|
+
var CANDIDATE_KEYS = ["payload", "eventPayloadName", "tags"];
|
|
223
|
+
function descriptorValue(value, key, code) {
|
|
224
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
225
|
+
if (descriptor === void 0 || descriptor.enumerable !== true || descriptor.get !== void 0 || descriptor.set !== void 0 || !("value" in descriptor)) {
|
|
226
|
+
fail(code);
|
|
227
|
+
}
|
|
228
|
+
return descriptor.value;
|
|
229
|
+
}
|
|
230
|
+
function assertClosedKeys(value, allowed, shapeCode) {
|
|
231
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
232
|
+
if (typeof key !== "string" || !allowed.includes(key)) fail("conditional_commit_unknown_member");
|
|
233
|
+
descriptorValue(value, key, shapeCode);
|
|
234
|
+
}
|
|
235
|
+
for (const key of allowed) {
|
|
236
|
+
if (!Object.prototype.hasOwnProperty.call(value, key)) fail(shapeCode);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function readTag(value) {
|
|
240
|
+
if (!Array.isArray(value)) fail("conditional_commit_invalid_candidate");
|
|
241
|
+
const keys = Reflect.ownKeys(value);
|
|
242
|
+
if (keys.some((key) => typeof key === "symbol")) fail("conditional_commit_unknown_member");
|
|
243
|
+
if (value.length !== 1) fail("conditional_commit_invalid_tag_count");
|
|
244
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
|
|
245
|
+
if (lengthDescriptor === void 0 || lengthDescriptor.enumerable === true || lengthDescriptor.get !== void 0 || lengthDescriptor.set !== void 0) {
|
|
246
|
+
fail("conditional_commit_invalid_candidate");
|
|
247
|
+
}
|
|
248
|
+
for (const key of keys) {
|
|
249
|
+
if (key === "length") continue;
|
|
250
|
+
if (key !== "0") fail("conditional_commit_invalid_candidate");
|
|
251
|
+
}
|
|
252
|
+
const tag = descriptorValue(value, "0", "conditional_commit_invalid_candidate");
|
|
253
|
+
if (typeof tag !== "string") fail("conditional_commit_invalid_candidate");
|
|
254
|
+
return tag;
|
|
255
|
+
}
|
|
256
|
+
function boundedField(value, code) {
|
|
257
|
+
if (typeof value !== "string") fail(code);
|
|
258
|
+
assertWellFormedUtf16(value, code);
|
|
259
|
+
const size = new TextEncoder().encode(value).byteLength;
|
|
260
|
+
if (size < 1) fail(code);
|
|
261
|
+
if (size > CONDITIONAL_COMMIT_MAX_UTF8_FIELD_BYTES) fail("conditional_commit_size_limit_exceeded");
|
|
262
|
+
return value;
|
|
263
|
+
}
|
|
264
|
+
function encodeBase64(bytes) {
|
|
265
|
+
let output = "";
|
|
266
|
+
let index = 0;
|
|
267
|
+
for (; index + 2 < bytes.length; index += 3) {
|
|
268
|
+
const chunk = (bytes[index] ?? 0) << 16 | (bytes[index + 1] ?? 0) << 8 | (bytes[index + 2] ?? 0);
|
|
269
|
+
output += BASE64_ALPHABET[chunk >>> 18 & 63] + BASE64_ALPHABET[chunk >>> 12 & 63] + BASE64_ALPHABET[chunk >>> 6 & 63] + BASE64_ALPHABET[chunk & 63];
|
|
270
|
+
}
|
|
271
|
+
if (index < bytes.length) {
|
|
272
|
+
const first = bytes[index] ?? 0;
|
|
273
|
+
const second = index + 1 < bytes.length ? bytes[index + 1] ?? 0 : 0;
|
|
274
|
+
const chunk = first << 16 | second << 8;
|
|
275
|
+
output += BASE64_ALPHABET[chunk >>> 18 & 63];
|
|
276
|
+
output += BASE64_ALPHABET[chunk >>> 12 & 63];
|
|
277
|
+
output += index + 1 < bytes.length ? BASE64_ALPHABET[chunk >>> 6 & 63] : "=";
|
|
278
|
+
output += "=";
|
|
279
|
+
}
|
|
280
|
+
return output;
|
|
281
|
+
}
|
|
282
|
+
function decodeCanonicalBase64(text) {
|
|
283
|
+
if (typeof text !== "string" || text.length === 0 || text.length % 4 !== 0) fail("conditional_commit_invalid_base64");
|
|
284
|
+
const padding = text.endsWith("==") ? 2 : text.endsWith("=") ? 1 : 0;
|
|
285
|
+
if (padding === 1 && text.endsWith("==")) fail("conditional_commit_invalid_base64");
|
|
286
|
+
const body = text.slice(0, text.length - padding);
|
|
287
|
+
if (body.includes("=")) fail("conditional_commit_invalid_base64");
|
|
288
|
+
for (const char of body) {
|
|
289
|
+
if (!BASE64_INDEX.has(char)) fail("conditional_commit_invalid_base64");
|
|
290
|
+
}
|
|
291
|
+
const decodedLength = text.length / 4 * 3 - padding;
|
|
292
|
+
if (!Number.isSafeInteger(decodedLength) || decodedLength < 0) fail("conditional_commit_invalid_base64");
|
|
293
|
+
if (decodedLength > CONDITIONAL_COMMIT_MAX_PAYLOAD_BYTES) fail("conditional_commit_size_limit_exceeded");
|
|
294
|
+
const bytes = new Uint8Array(decodedLength);
|
|
295
|
+
let offset = 0;
|
|
296
|
+
for (let index = 0; index < text.length; index += 4) {
|
|
297
|
+
const last = index + 4 >= text.length;
|
|
298
|
+
const a = BASE64_INDEX.get(text[index] ?? "");
|
|
299
|
+
const b = BASE64_INDEX.get(text[index + 1] ?? "");
|
|
300
|
+
const cChar = text[index + 2] ?? "";
|
|
301
|
+
const dChar = text[index + 3] ?? "";
|
|
302
|
+
const c = last && cChar === "=" ? 0 : BASE64_INDEX.get(cChar);
|
|
303
|
+
const d = last && dChar === "=" ? 0 : BASE64_INDEX.get(dChar);
|
|
304
|
+
if (a === void 0 || b === void 0 || c === void 0 || d === void 0) fail("conditional_commit_invalid_base64");
|
|
305
|
+
const chunk = a << 18 | b << 12 | c << 6 | d;
|
|
306
|
+
const produced = last ? 3 - padding : 3;
|
|
307
|
+
bytes[offset] = chunk >>> 16 & 255;
|
|
308
|
+
if (produced > 1) bytes[offset + 1] = chunk >>> 8 & 255;
|
|
309
|
+
if (produced > 2) bytes[offset + 2] = chunk & 255;
|
|
310
|
+
offset += produced;
|
|
311
|
+
}
|
|
312
|
+
if (offset !== decodedLength || encodeBase64(bytes) !== text) fail("conditional_commit_invalid_base64");
|
|
313
|
+
return bytes;
|
|
314
|
+
}
|
|
315
|
+
function decodeStrictUtf8(bytes) {
|
|
316
|
+
try {
|
|
317
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
318
|
+
} catch {
|
|
319
|
+
fail("conditional_commit_invalid_utf8");
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function parseSerializedConditionalCommitRequest(value) {
|
|
323
|
+
if (!isPlainRecord(value)) fail("conditional_commit_invalid_envelope");
|
|
324
|
+
assertClosedKeys(value, REQUEST_KEYS, "conditional_commit_invalid_envelope");
|
|
325
|
+
if (descriptorValue(value, "version", "conditional_commit_invalid_envelope") !== 1) {
|
|
326
|
+
fail("conditional_commit_invalid_version");
|
|
327
|
+
}
|
|
328
|
+
const idempotencyKey = normalizeConditionalCommitKey(
|
|
329
|
+
descriptorValue(value, "idempotencyKey", "conditional_commit_invalid_key")
|
|
330
|
+
);
|
|
331
|
+
const candidate = descriptorValue(value, "eventCandidate", "conditional_commit_invalid_candidate");
|
|
332
|
+
if (!isPlainRecord(candidate)) fail("conditional_commit_invalid_candidate");
|
|
333
|
+
assertClosedKeys(candidate, CANDIDATE_KEYS, "conditional_commit_invalid_candidate");
|
|
334
|
+
const payloadText = descriptorValue(candidate, "payload", "conditional_commit_invalid_candidate");
|
|
335
|
+
if (typeof payloadText !== "string") fail("conditional_commit_invalid_candidate");
|
|
336
|
+
const eventPayloadName = boundedField(
|
|
337
|
+
descriptorValue(candidate, "eventPayloadName", "conditional_commit_invalid_candidate"),
|
|
338
|
+
"conditional_commit_invalid_candidate"
|
|
339
|
+
);
|
|
340
|
+
const tag = boundedField(readTag(descriptorValue(candidate, "tags", "conditional_commit_invalid_candidate")), "conditional_commit_invalid_candidate");
|
|
341
|
+
const decoded = decodeCanonicalBase64(payloadText);
|
|
342
|
+
const jsonText = decodeStrictUtf8(decoded);
|
|
343
|
+
let parsed;
|
|
344
|
+
try {
|
|
345
|
+
parsed = JSON.parse(jsonText);
|
|
346
|
+
} catch {
|
|
347
|
+
fail("conditional_commit_invalid_payload_json");
|
|
348
|
+
}
|
|
349
|
+
if (!isPlainRecord(parsed)) fail("conditional_commit_payload_root_not_object");
|
|
350
|
+
canonicalizeConditionalCommitPayload(parsed);
|
|
351
|
+
return {
|
|
352
|
+
version: 1,
|
|
353
|
+
idempotencyKey,
|
|
354
|
+
eventCandidate: {
|
|
355
|
+
payload: payloadText,
|
|
356
|
+
eventPayloadName,
|
|
357
|
+
tags: [tag]
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
function parseSerializedConditionalCommitRequestBody(body) {
|
|
362
|
+
let text;
|
|
363
|
+
if (typeof body === "string") {
|
|
364
|
+
assertWellFormedUtf16(body, "conditional_commit_invalid_json");
|
|
365
|
+
if (new TextEncoder().encode(body).byteLength > CONDITIONAL_COMMIT_MAX_REQUEST_BYTES) {
|
|
366
|
+
fail("conditional_commit_size_limit_exceeded");
|
|
367
|
+
}
|
|
368
|
+
text = body;
|
|
369
|
+
} else if (body instanceof Uint8Array) {
|
|
370
|
+
if (body.byteLength > CONDITIONAL_COMMIT_MAX_REQUEST_BYTES) fail("conditional_commit_size_limit_exceeded");
|
|
371
|
+
try {
|
|
372
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(body);
|
|
373
|
+
} catch {
|
|
374
|
+
fail("conditional_commit_invalid_json");
|
|
375
|
+
}
|
|
376
|
+
} else {
|
|
377
|
+
fail("conditional_commit_invalid_json");
|
|
378
|
+
}
|
|
379
|
+
let value;
|
|
380
|
+
try {
|
|
381
|
+
value = JSON.parse(text);
|
|
382
|
+
} catch {
|
|
383
|
+
fail("conditional_commit_invalid_json");
|
|
384
|
+
}
|
|
385
|
+
return parseSerializedConditionalCommitRequest(value);
|
|
386
|
+
}
|
|
387
|
+
|
|
1
388
|
// src/materializedView.ts
|
|
2
389
|
function nonEmpty(value, code, message) {
|
|
3
390
|
if (typeof value !== "string" || value.length === 0) throw new Error(`${code}: ${message}`);
|
|
4
391
|
}
|
|
5
|
-
function
|
|
392
|
+
function descriptorValue2(descriptor, value) {
|
|
6
393
|
if (descriptor.valueType === "text") {
|
|
7
394
|
if (typeof value !== "string") throw new Error(`MV index ${descriptor.id} expected a text value`);
|
|
8
395
|
return value;
|
|
@@ -61,7 +448,7 @@ function normalizeIndexEntries(values, descriptors, rowKey) {
|
|
|
61
448
|
return Object.freeze({
|
|
62
449
|
indexId: entry.indexId,
|
|
63
450
|
valueType,
|
|
64
|
-
value:
|
|
451
|
+
value: descriptorValue2(descriptor, entry.value),
|
|
65
452
|
rowKey: entry.rowKey
|
|
66
453
|
});
|
|
67
454
|
}));
|
|
@@ -111,7 +498,7 @@ function normalizePlan(result, descriptors, event) {
|
|
|
111
498
|
generatedEntries.push(Object.freeze({
|
|
112
499
|
indexId: descriptor.id,
|
|
113
500
|
valueType: descriptor.valueType,
|
|
114
|
-
value:
|
|
501
|
+
value: descriptorValue2(descriptor, candidate),
|
|
115
502
|
rowKey: row.rowKey
|
|
116
503
|
}));
|
|
117
504
|
}
|
|
@@ -165,7 +552,7 @@ var JsonValidationError = class extends Error {
|
|
|
165
552
|
this.path = path;
|
|
166
553
|
}
|
|
167
554
|
};
|
|
168
|
-
var
|
|
555
|
+
var isPlainRecord2 = (value) => {
|
|
169
556
|
const prototype = Object.getPrototypeOf(value);
|
|
170
557
|
return prototype === Object.prototype || prototype === null;
|
|
171
558
|
};
|
|
@@ -190,7 +577,7 @@ function assertJsonValue(value, boundary = "value") {
|
|
|
190
577
|
if (Array.isArray(candidate)) {
|
|
191
578
|
return candidate.map((item, index) => visit(item, `${path}[${index}]`));
|
|
192
579
|
}
|
|
193
|
-
if (!
|
|
580
|
+
if (!isPlainRecord2(candidate)) {
|
|
194
581
|
throw new JsonValidationError(`Value at ${path} must be a plain JSON object`, boundary, path);
|
|
195
582
|
}
|
|
196
583
|
const result = {};
|
|
@@ -470,12 +857,23 @@ function defineDomain(options) {
|
|
|
470
857
|
});
|
|
471
858
|
}
|
|
472
859
|
export {
|
|
860
|
+
CONDITIONAL_COMMIT_CANONICALIZATION_VERSION,
|
|
861
|
+
CONDITIONAL_COMMIT_DERIVATION_VERSION,
|
|
862
|
+
CONDITIONAL_COMMIT_DOMAIN_SEPARATOR,
|
|
863
|
+
CONDITIONAL_COMMIT_MAX_PAYLOAD_BYTES,
|
|
864
|
+
CONDITIONAL_COMMIT_MAX_REQUEST_BYTES,
|
|
865
|
+
CONDITIONAL_COMMIT_MAX_UTF8_FIELD_BYTES,
|
|
866
|
+
CONDITIONAL_COMMIT_VERSION,
|
|
473
867
|
CanonicalEventIdentityError,
|
|
868
|
+
ConditionalCommitProtocolError,
|
|
474
869
|
DcbDefinitionError,
|
|
475
870
|
DomainDefinitionError,
|
|
476
871
|
JsonValidationError,
|
|
477
872
|
assertJsonValue,
|
|
478
873
|
canonicalEventKey,
|
|
874
|
+
canonicalizeConditionalCommitPayload,
|
|
875
|
+
computeConditionalCommitFingerprint,
|
|
876
|
+
conditionalCommitFingerprintPreimage,
|
|
479
877
|
defineCommand,
|
|
480
878
|
defineDomain,
|
|
481
879
|
defineEvent,
|
|
@@ -486,7 +884,10 @@ export {
|
|
|
486
884
|
defineTag,
|
|
487
885
|
done,
|
|
488
886
|
noop,
|
|
887
|
+
normalizeConditionalCommitKey,
|
|
489
888
|
parseCanonicalEventKey,
|
|
889
|
+
parseSerializedConditionalCommitRequest,
|
|
890
|
+
parseSerializedConditionalCommitRequestBody,
|
|
490
891
|
reject,
|
|
491
892
|
validateJsonValue
|
|
492
893
|
};
|