@mdbase-dev/connect 0.1.0-beta.31 → 0.1.0-beta.33
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 +112 -34
- package/dist/advanced.d.ts +9 -0
- package/dist/advanced.js +6 -0
- package/dist/application-session.d.ts +39 -14
- package/dist/application-session.js +201 -70
- package/dist/async.js +11 -5
- package/dist/authority-proof.d.ts +3 -0
- package/dist/authority-proof.js +24 -0
- package/dist/browser/integrity.json +1 -1
- package/dist/browser/mdbase-connect.min.js +3 -3
- package/dist/browser/mdbase-connect.min.js.sha384 +1 -1
- package/dist/browser.d.ts +3 -0
- package/dist/browser.js +5 -0
- package/dist/capabilities.js +2 -1
- package/dist/collection-client.d.ts +36 -33
- package/dist/collection-client.js +549 -104
- package/dist/connect-authorization-helpers.d.ts +3 -0
- package/dist/connect-authorization-helpers.js +13 -0
- package/dist/connect-options.d.ts +12 -4
- package/dist/connect.d.ts +12 -4
- package/dist/connect.js +102 -103
- package/dist/connection-file-transport.d.ts +2 -2
- package/dist/connection-file-transport.js +4 -4
- package/dist/connection-notifications.d.ts +10 -4
- package/dist/connection-notifications.js +41 -23
- package/dist/connection-transport.d.ts +19 -15
- package/dist/connection-transport.js +202 -202
- package/dist/connection-types.d.ts +5 -4
- package/dist/connection.d.ts +47 -46
- package/dist/connection.js +320 -162
- package/dist/crypto-entry.d.ts +7 -0
- package/dist/crypto-entry.js +4 -0
- package/dist/crypto.js +4 -4
- package/dist/external-store.d.ts +10 -0
- package/dist/external-store.js +10 -0
- package/dist/file-transfer-internals.d.ts +23 -0
- package/dist/file-transfer-internals.js +224 -0
- package/dist/files.d.ts +36 -14
- package/dist/files.js +92 -222
- package/dist/hosted-file-request.js +3 -3
- package/dist/index.d.ts +24 -19
- package/dist/index.js +10 -18
- package/dist/internal-types.d.ts +9 -1
- package/dist/local-file-transport.d.ts +2 -2
- package/dist/local-file-transport.js +13 -13
- package/dist/mdbase-connect.d.ts +6 -4
- package/dist/mdbase-connect.js +9 -8
- package/dist/notifications.d.ts +3 -2
- package/dist/operation-helpers.d.ts +3 -3
- package/dist/operation-helpers.js +17 -34
- package/dist/operation-types.d.ts +435 -45
- package/dist/outcomes.d.ts +15 -15
- package/dist/outcomes.js +13 -1
- package/dist/pending-mutation-store.d.ts +13 -0
- package/dist/pending-mutation-store.js +64 -0
- package/dist/request-budget.d.ts +27 -0
- package/dist/request-budget.js +95 -0
- package/dist/runtime-utils.d.ts +2 -0
- package/dist/runtime-utils.js +19 -0
- package/dist/session.d.ts +5 -4
- package/dist/session.js +6 -5
- package/dist/stored-token.d.ts +10 -0
- package/dist/stored-token.js +60 -0
- package/dist/token-response.d.ts +11 -0
- package/dist/token-response.js +77 -0
- package/dist/watch-policy.d.ts +8 -0
- package/dist/watch-policy.js +12 -0
- package/package.json +12 -2
|
@@ -3,7 +3,7 @@ import { decryptRelayResponse, encryptRelayRequest } from "./crypto.js";
|
|
|
3
3
|
import { MdbaseConnectError, connectError, serverConnectError } from "./errors.js";
|
|
4
4
|
import { GrantFileTransferCipher } from "./file-crypto.js";
|
|
5
5
|
import { directFallbackStatus, loopbackRequest } from "./operation-helpers.js";
|
|
6
|
-
import { apiError } from "./runtime-utils.js";
|
|
6
|
+
import { apiError, decodeJsonResponse } from "./runtime-utils.js";
|
|
7
7
|
/** Keeps grant-bound file crypto and loopback framing out of the operation transport. */
|
|
8
8
|
export class LocalFileTransport {
|
|
9
9
|
options;
|
|
@@ -39,7 +39,7 @@ export class LocalFileTransport {
|
|
|
39
39
|
}
|
|
40
40
|
response = await this.sendRelayControl(token, encryptedRequest, signal);
|
|
41
41
|
if (await refreshableBindingFailure(response, token)) {
|
|
42
|
-
token = await this.options.refreshAuthorization();
|
|
42
|
+
token = await this.options.refreshAuthorization(signal);
|
|
43
43
|
requireLocalFileToken(token);
|
|
44
44
|
encryptedRequest = await this.encryptControl(token, controlInput);
|
|
45
45
|
response = await this.sendRelayControl(token, encryptedRequest, signal);
|
|
@@ -49,7 +49,7 @@ export class LocalFileTransport {
|
|
|
49
49
|
return this.decryptControlResponse(token, encryptedRequest, response);
|
|
50
50
|
}
|
|
51
51
|
async decryptControlResponse(token, encryptedRequest, response) {
|
|
52
|
-
const body = await response
|
|
52
|
+
const body = await decodeJsonResponse(response, "invalid_operation_response", "The collection authority returned an invalid file control response.");
|
|
53
53
|
if (!response.ok) {
|
|
54
54
|
throw apiError(body, "operation_failed", "Collection file request failed.", response.status);
|
|
55
55
|
}
|
|
@@ -63,7 +63,7 @@ export class LocalFileTransport {
|
|
|
63
63
|
return decrypted.result;
|
|
64
64
|
}
|
|
65
65
|
async uploadChunk(session, chunkIndex, bytes, signal) {
|
|
66
|
-
const token = await this.localFileToken(session, "upload");
|
|
66
|
+
const token = await this.localFileToken(session, "upload", signal);
|
|
67
67
|
let deliveryToken = token;
|
|
68
68
|
let encoded = await encryptedUploadChunk(this.options.keyStore, deliveryToken, session, chunkIndex, bytes);
|
|
69
69
|
let response;
|
|
@@ -86,19 +86,19 @@ export class LocalFileTransport {
|
|
|
86
86
|
}
|
|
87
87
|
response = await this.sendRelayUpload(deliveryToken, encoded, signal);
|
|
88
88
|
if (response.status === 401 && deliveryToken.refreshToken) {
|
|
89
|
-
deliveryToken = await this.options.refreshAuthorization();
|
|
89
|
+
deliveryToken = await this.options.refreshAuthorization(signal);
|
|
90
90
|
requireLocalFileToken(deliveryToken);
|
|
91
91
|
encoded = await encryptedUploadChunk(this.options.keyStore, deliveryToken, session, chunkIndex, bytes);
|
|
92
92
|
response = await this.sendRelayUpload(deliveryToken, encoded, signal);
|
|
93
93
|
}
|
|
94
94
|
if (!response.ok) {
|
|
95
|
-
const body = await response
|
|
95
|
+
const body = await decodeJsonResponse(response, "invalid_operation_response", "The collection authority returned an invalid file upload response.");
|
|
96
96
|
throw apiError(body, "operation_failed", "Encrypted file chunk upload failed.", response.status);
|
|
97
97
|
}
|
|
98
98
|
this.options.onRelayAvailable();
|
|
99
99
|
}
|
|
100
100
|
async downloadChunk(session, chunkIndex, signal) {
|
|
101
|
-
let token = await this.localFileToken(session, "download");
|
|
101
|
+
let token = await this.localFileToken(session, "download", signal);
|
|
102
102
|
let response;
|
|
103
103
|
if (await this.options.shouldAttemptDirect(token)) {
|
|
104
104
|
try {
|
|
@@ -119,7 +119,7 @@ export class LocalFileTransport {
|
|
|
119
119
|
}
|
|
120
120
|
response = await this.sendRelayDownload(token, session, chunkIndex, signal);
|
|
121
121
|
if (response.status === 401 && token.refreshToken) {
|
|
122
|
-
token = await this.options.refreshAuthorization();
|
|
122
|
+
token = await this.options.refreshAuthorization(signal);
|
|
123
123
|
requireLocalFileToken(token);
|
|
124
124
|
response = await this.sendRelayDownload(token, session, chunkIndex, signal);
|
|
125
125
|
}
|
|
@@ -129,7 +129,7 @@ export class LocalFileTransport {
|
|
|
129
129
|
}
|
|
130
130
|
async decryptDownloadResponse(token, session, chunkIndex, response) {
|
|
131
131
|
if (!response.ok) {
|
|
132
|
-
const body = await response
|
|
132
|
+
const body = await decodeJsonResponse(response, "invalid_operation_response", "The collection authority returned an invalid file download response.");
|
|
133
133
|
throw apiError(body, "operation_failed", "Encrypted file chunk download failed.", response.status);
|
|
134
134
|
}
|
|
135
135
|
if (response.headers.get("content-type")?.split(";", 1)[0]
|
|
@@ -184,8 +184,8 @@ export class LocalFileTransport {
|
|
|
184
184
|
signal
|
|
185
185
|
});
|
|
186
186
|
}
|
|
187
|
-
async localFileToken(session, direction) {
|
|
188
|
-
const token = await this.options.authorizedToken();
|
|
187
|
+
async localFileToken(session, direction, signal) {
|
|
188
|
+
const token = await this.options.authorizedToken(signal);
|
|
189
189
|
if (!token || token.authority || !token.fileCapability || !token.encryption
|
|
190
190
|
|| !token.grantId || !token.keyHandle
|
|
191
191
|
|| session.direction !== direction
|
|
@@ -218,7 +218,7 @@ function fileUploadRequest(encoded, signal) {
|
|
|
218
218
|
async function requireSuccessfulChunkResponse(response, message) {
|
|
219
219
|
if (response.ok)
|
|
220
220
|
return;
|
|
221
|
-
const body = await response
|
|
221
|
+
const body = await decodeJsonResponse(response, "invalid_operation_response", "The collection authority returned an invalid file transfer response.");
|
|
222
222
|
throw apiError(body, "operation_failed", message, response.status);
|
|
223
223
|
}
|
|
224
224
|
async function refreshableBindingFailure(response, token) {
|
|
@@ -228,7 +228,7 @@ async function refreshableBindingFailure(response, token) {
|
|
|
228
228
|
return true;
|
|
229
229
|
if (response.status !== 409)
|
|
230
230
|
return false;
|
|
231
|
-
const body = await response.clone().
|
|
231
|
+
const body = await decodeJsonResponse(response.clone(), "invalid_operation_response", "The collection authority returned an invalid file response.").catch(() => null);
|
|
232
232
|
return body?.error?.code === "encryption_binding_stale";
|
|
233
233
|
}
|
|
234
234
|
function requireEncryption(token) {
|
package/dist/mdbase-connect.d.ts
CHANGED
|
@@ -6,15 +6,17 @@ import { type AuthorizationProblemCode, type ConnectOutcome, type RegistrationPr
|
|
|
6
6
|
import { MdbaseApplicationSession, type MdbaseApplicationSessionOptions } from "./application-session.js";
|
|
7
7
|
import type { MdbaseUnavailableReason } from "./session.js";
|
|
8
8
|
import type { Application } from "./internal-types.js";
|
|
9
|
+
import type { ConnectRequestOptions } from "./operation-types.js";
|
|
9
10
|
export declare class MdbaseConnect<Frontmatter extends JsonObject = JsonObject> {
|
|
10
11
|
private readonly internals;
|
|
11
12
|
constructor(options: MdbaseConnectOptions);
|
|
12
|
-
register(): Promise<ConnectOutcome<Application, RegistrationProblemCode>>;
|
|
13
|
+
register(options?: ConnectRequestOptions): Promise<ConnectOutcome<Application, RegistrationProblemCode>>;
|
|
13
14
|
authorize(options?: MdbaseAuthorizeOptions): Promise<ConnectOutcome<MdbaseAuthorizationOutcome<Frontmatter>, AuthorizationProblemCode>>;
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
/** The ordinary application lifecycle entry point. */
|
|
16
|
+
application(options: MdbaseApplicationSessionOptions): MdbaseApplicationSession<Frontmatter>;
|
|
17
|
+
manifest(options?: ConnectRequestOptions): Promise<ConnectOutcome<import("@mdbase-dev/connect-protocol").MdbaseAppManifest, RegistrationProblemCode>>;
|
|
16
18
|
environment(): MdbaseConnectEnvironment;
|
|
17
|
-
completeAuthorization(callbackUrl?: string): Promise<ConnectOutcome<MdbaseAuthorizationResult<Frontmatter>, AuthorizationProblemCode>>;
|
|
19
|
+
completeAuthorization(callbackUrl?: string | URL, options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseAuthorizationResult<Frontmatter>, AuthorizationProblemCode>>;
|
|
18
20
|
connections(): MdbaseConnectionInfo[];
|
|
19
21
|
connection(collectionId: string): MdbaseConnection<Frontmatter> | null;
|
|
20
22
|
unavailableReason(collectionId: string): MdbaseUnavailableReason | null;
|
package/dist/mdbase-connect.js
CHANGED
|
@@ -7,23 +7,24 @@ export class MdbaseConnect {
|
|
|
7
7
|
constructor(options) {
|
|
8
8
|
this.internals = new MdbaseConnectInternals(options);
|
|
9
9
|
}
|
|
10
|
-
register() {
|
|
11
|
-
return captureConnectOutcome(() => this.internals.register(), REGISTRATION_PROBLEM_CODES);
|
|
10
|
+
register(options) {
|
|
11
|
+
return captureConnectOutcome(() => this.internals.register(options), REGISTRATION_PROBLEM_CODES);
|
|
12
12
|
}
|
|
13
13
|
authorize(options = {}) {
|
|
14
14
|
return captureConnectOutcome(() => this.internals.authorize(options), AUTHORIZATION_PROBLEM_CODES);
|
|
15
15
|
}
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
/** The ordinary application lifecycle entry point. */
|
|
17
|
+
application(options) {
|
|
18
|
+
return new MdbaseApplicationSession(this, options, this.internals.timeouts);
|
|
18
19
|
}
|
|
19
|
-
manifest() {
|
|
20
|
-
return captureConnectOutcome(() => this.internals.manifestDeclaration(), REGISTRATION_PROBLEM_CODES);
|
|
20
|
+
manifest(options) {
|
|
21
|
+
return captureConnectOutcome(() => this.internals.manifestDeclaration(options), REGISTRATION_PROBLEM_CODES);
|
|
21
22
|
}
|
|
22
23
|
environment() {
|
|
23
24
|
return this.internals.environment();
|
|
24
25
|
}
|
|
25
|
-
completeAuthorization(callbackUrl) {
|
|
26
|
-
return captureConnectOutcome(() => this.internals.completeAuthorization(callbackUrl
|
|
26
|
+
completeAuthorization(callbackUrl, options) {
|
|
27
|
+
return captureConnectOutcome(() => this.internals.completeAuthorization(callbackUrl === undefined ? defaultCallbackUrl() : String(callbackUrl), options), AUTHORIZATION_PROBLEM_CODES);
|
|
27
28
|
}
|
|
28
29
|
connections() {
|
|
29
30
|
return this.internals.connections();
|
package/dist/notifications.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import type { ConnectRequestOptions } from "./operation-types.js";
|
|
2
|
+
export interface MdbaseNotificationRegistrationOptions extends ConnectRequestOptions {
|
|
2
3
|
serviceWorker: ServiceWorkerRegistration;
|
|
3
4
|
/** Manifest criterion IDs to enable. Omit to enable every declared criterion. */
|
|
4
5
|
criteria?: string[];
|
|
@@ -10,7 +11,7 @@ export interface MdbaseNotificationRegistration {
|
|
|
10
11
|
installationId: string;
|
|
11
12
|
criteria: string[];
|
|
12
13
|
}
|
|
13
|
-
export interface MdbaseNativeNotificationRegistrationOptions {
|
|
14
|
+
export interface MdbaseNativeNotificationRegistrationOptions extends ConnectRequestOptions {
|
|
14
15
|
/** Current FCM registration token. Refresh by calling this method again. */
|
|
15
16
|
token: string;
|
|
16
17
|
/** Manifest criterion IDs to enable. Omit to enable every declared criterion. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type CollectionOperation } from "@mdbase-dev/connect-protocol";
|
|
2
2
|
import { MdbaseConnectError } from "./errors.js";
|
|
3
3
|
import type { StoredToken } from "./internal-types.js";
|
|
4
4
|
import type { DeleteInput, DeletePreflightResult, MutationEstimate, RenameInput, RenamePreflightResult } from "./operation-types.js";
|
|
@@ -12,7 +12,7 @@ export declare function isMutation(operation: CollectionOperation, input?: unkno
|
|
|
12
12
|
export declare function uniqueOperations(operations: CollectionOperation[]): CollectionOperation[];
|
|
13
13
|
export declare function sameAuthorization(left: StoredToken, right: StoredToken): boolean;
|
|
14
14
|
export declare function throwIfCancelled(signal?: AbortSignal): void;
|
|
15
|
-
export declare function operationTransportError(error: unknown, signal: AbortSignal | undefined,
|
|
15
|
+
export declare function operationTransportError(error: unknown, signal: AbortSignal | undefined, pendingRequestId: string | undefined, unavailableCode: "hosted_provider_unavailable" | "relay_unavailable"): Error;
|
|
16
16
|
export declare function isCancellation(error: unknown, signal?: AbortSignal): boolean;
|
|
17
17
|
export declare function assertRenamePreview(input: RenameInput, preview: RenamePreflightResult): void;
|
|
18
18
|
export declare function assertDeletePreview(input: DeleteInput, preview: DeletePreflightResult): void;
|
|
@@ -21,4 +21,4 @@ export declare function deleteEstimate(preview: DeletePreflightResult): Mutation
|
|
|
21
21
|
export declare function operationFingerprint(operation: CollectionOperation, input: unknown): Promise<string>;
|
|
22
22
|
export declare function canonicalJson(value: unknown): string;
|
|
23
23
|
export declare function sortJson(value: unknown): unknown;
|
|
24
|
-
export declare function unknownMutationOutcome(cause: unknown): MdbaseConnectError;
|
|
24
|
+
export declare function unknownMutationOutcome(requestId: string, cause: unknown): MdbaseConnectError;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { isMutatingOperation, mutationFingerprint } from "@mdbase-dev/connect-protocol";
|
|
1
2
|
import { MdbaseConnectError, connectError } from "./errors.js";
|
|
2
|
-
import { bytesToBase64Url } from "./base64.js";
|
|
3
3
|
export function loopbackRequest(init) {
|
|
4
4
|
return { ...init, credentials: "omit", targetAddressSpace: "loopback" };
|
|
5
5
|
}
|
|
@@ -20,24 +20,7 @@ export function directFallbackStatus(status) {
|
|
|
20
20
|
return status === 404 || status === 405 || status === 426 || status >= 500;
|
|
21
21
|
}
|
|
22
22
|
export function isMutation(operation, input) {
|
|
23
|
-
|
|
24
|
-
&& input.dry_run === true)
|
|
25
|
-
return false;
|
|
26
|
-
return (operation === "sync"
|
|
27
|
-
&& input !== null
|
|
28
|
-
&& typeof input === "object"
|
|
29
|
-
&& !Array.isArray(input)
|
|
30
|
-
&& input.action === "mutate")
|
|
31
|
-
|| operation === "create"
|
|
32
|
-
|| operation === "update"
|
|
33
|
-
|| operation === "delete"
|
|
34
|
-
|| operation === "rename"
|
|
35
|
-
|| operation === "create_type"
|
|
36
|
-
|| operation === "update_type"
|
|
37
|
-
|| operation === "apply_type_pack"
|
|
38
|
-
|| operation === "put_timer"
|
|
39
|
-
|| operation === "cancel_timer"
|
|
40
|
-
|| operation === "reconcile_timers";
|
|
23
|
+
return isMutatingOperation(operation, input);
|
|
41
24
|
}
|
|
42
25
|
export function uniqueOperations(operations) {
|
|
43
26
|
return [...new Set(operations)];
|
|
@@ -55,19 +38,21 @@ export function throwIfCancelled(signal) {
|
|
|
55
38
|
return;
|
|
56
39
|
throw connectError("operation_cancelled", "The operation was cancelled before it changed the collection.", { operationOutcome: "not_sent", cause: signal.reason });
|
|
57
40
|
}
|
|
58
|
-
export function operationTransportError(error, signal,
|
|
41
|
+
export function operationTransportError(error, signal, pendingRequestId, unavailableCode) {
|
|
59
42
|
// Problems raised before transport dispatch already describe the
|
|
60
43
|
// authoritative outcome. Do not overwrite them merely because an older
|
|
61
44
|
// pending mutation also exists in storage.
|
|
62
45
|
if (error instanceof MdbaseConnectError)
|
|
63
46
|
return error;
|
|
64
47
|
if (signal?.aborted) {
|
|
65
|
-
if (
|
|
66
|
-
return unknownMutationOutcome(error);
|
|
48
|
+
if (pendingRequestId)
|
|
49
|
+
return unknownMutationOutcome(pendingRequestId, error);
|
|
50
|
+
if (signal.reason instanceof MdbaseConnectError)
|
|
51
|
+
return signal.reason;
|
|
67
52
|
return connectError("operation_cancelled", "The operation was cancelled before it changed the collection.", { operationOutcome: "not_sent", cause: error });
|
|
68
53
|
}
|
|
69
|
-
if (
|
|
70
|
-
return unknownMutationOutcome(error);
|
|
54
|
+
if (pendingRequestId) {
|
|
55
|
+
return unknownMutationOutcome(pendingRequestId, error);
|
|
71
56
|
}
|
|
72
57
|
if (error instanceof TypeError) {
|
|
73
58
|
return connectError(unavailableCode, unavailableCode === "hosted_provider_unavailable"
|
|
@@ -81,21 +66,21 @@ export function isCancellation(error, signal) {
|
|
|
81
66
|
|| (error instanceof MdbaseConnectError && error.code === "operation_cancelled");
|
|
82
67
|
}
|
|
83
68
|
export function assertRenamePreview(input, preview) {
|
|
84
|
-
if (preview.
|
|
69
|
+
if (preview.dryRun !== true || preview.wouldRename !== true
|
|
85
70
|
|| preview.from !== input.from || preview.to !== input.to) {
|
|
86
71
|
throw connectError("invalid_preflight", "The rename preview does not match this mutation. Run the preview again.");
|
|
87
72
|
}
|
|
88
73
|
}
|
|
89
74
|
export function assertDeletePreview(input, preview) {
|
|
90
|
-
if (preview.
|
|
75
|
+
if (preview.dryRun !== true || preview.wouldDelete !== true || preview.path !== input.path) {
|
|
91
76
|
throw connectError("invalid_preflight", "The delete preview does not match this mutation. Run the preview again.");
|
|
92
77
|
}
|
|
93
78
|
}
|
|
94
79
|
export function renameEstimate(input, preview) {
|
|
95
|
-
if (input.
|
|
80
|
+
if (input.updateRefs === false) {
|
|
96
81
|
return { affectedRecords: 0, totalUnits: 1, warnings: 0 };
|
|
97
82
|
}
|
|
98
|
-
const references = preview.
|
|
83
|
+
const references = preview.referencesAffected ?? [];
|
|
99
84
|
return {
|
|
100
85
|
affectedRecords: new Set(references.map((reference) => reference.path)).size,
|
|
101
86
|
totalUnits: 1 + references.length,
|
|
@@ -104,15 +89,13 @@ export function renameEstimate(input, preview) {
|
|
|
104
89
|
}
|
|
105
90
|
export function deleteEstimate(preview) {
|
|
106
91
|
return {
|
|
107
|
-
affectedRecords: new Set((preview.
|
|
92
|
+
affectedRecords: new Set((preview.brokenLinks ?? []).map((reference) => reference.path)).size,
|
|
108
93
|
totalUnits: 1,
|
|
109
94
|
warnings: 0
|
|
110
95
|
};
|
|
111
96
|
}
|
|
112
97
|
export async function operationFingerprint(operation, input) {
|
|
113
|
-
|
|
114
|
-
const digest = await crypto.subtle.digest("SHA-256", encoded);
|
|
115
|
-
return bytesToBase64Url(new Uint8Array(digest));
|
|
98
|
+
return mutationFingerprint(operation, input ?? {});
|
|
116
99
|
}
|
|
117
100
|
export function canonicalJson(value) {
|
|
118
101
|
return JSON.stringify(sortJson(value));
|
|
@@ -128,6 +111,6 @@ export function sortJson(value) {
|
|
|
128
111
|
}
|
|
129
112
|
return value;
|
|
130
113
|
}
|
|
131
|
-
export function unknownMutationOutcome(cause) {
|
|
132
|
-
return connectError("operation_outcome_unknown", "The write may have completed, but mdbase could not recover its authoritative result. Retry the exact same write to recover safely.", { operationOutcome: "unknown", cause });
|
|
114
|
+
export function unknownMutationOutcome(requestId, cause) {
|
|
115
|
+
return connectError("operation_outcome_unknown", "The write may have completed, but mdbase could not recover its authoritative result. Retry the exact same write to recover safely.", { operationOutcome: "unknown", details: { request_id: requestId }, cause });
|
|
133
116
|
}
|