@mdbase-dev/connect 0.1.0-beta.62 → 0.1.0-beta.64
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/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/connection-transport.js +28 -24
- package/dist/files.js +7 -1
- package/dist/operation-helpers.d.ts +4 -1
- package/dist/operation-helpers.js +29 -1
- package/dist/runtime-utils.js +6 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
sha384-
|
|
1
|
+
sha384-lle3e5SEN8S45RwEaaerEa1VKEBzMw7JZGQcuO1VI734WP7P95QHHR5DbXASXaaL
|
|
@@ -4,7 +4,7 @@ import { MdbaseConnectError, connectError, serverConnectError } from "./errors.j
|
|
|
4
4
|
import { ConnectionFileTransport } from "./connection-file-transport.js";
|
|
5
5
|
import { authorityProofHeaders } from "./authority-proof.js";
|
|
6
6
|
import { PendingMutationStore } from "./pending-mutation-store.js";
|
|
7
|
-
import { directFallbackStatus, isMutation, localNetworkPermission, loopbackRequest, operationFingerprint, operationTransportError, sameAuthorization, throwIfCancelled, unknownMutationOutcome } from "./operation-helpers.js";
|
|
7
|
+
import { directFallbackStatus, encryptedOperationError, fetchOperationRequest, isMutation, localNetworkPermission, loopbackRequest, operationFingerprint, operationTransportError, sameAuthorization, throwIfCancelled, unknownMutationOutcome, withOperationDeadline } from "./operation-helpers.js";
|
|
8
8
|
import { apiError, decodeJsonResponse, oauthErrorCode, parseStored, } from "./runtime-utils.js";
|
|
9
9
|
import { readStoredToken } from "./stored-token.js";
|
|
10
10
|
import { requestOptionsWithinBudget, withCooperativeRequestBudget, withRequestBudget } from "./request-budget.js";
|
|
@@ -124,7 +124,7 @@ export class ConnectionTransport {
|
|
|
124
124
|
if (!pending) {
|
|
125
125
|
throw connectError("no_pending_mutation", "There is no interrupted mutation to resume.");
|
|
126
126
|
}
|
|
127
|
-
return withCooperativeRequestBudget(options, this.timeouts.requestMs, (budget) => this.performOperationWithinBudget(pending.operation, pending.request?.input ?? {},
|
|
127
|
+
return withCooperativeRequestBudget(options, this.timeouts.requestMs, (budget) => this.performOperationWithinBudget(pending.operation, pending.request?.input ?? {}, requestOptionsWithinBudget(options ?? {}, budget), pending));
|
|
128
128
|
}
|
|
129
129
|
async performOperation(operation, input, options = {}) {
|
|
130
130
|
return withCooperativeRequestBudget(options, this.timeouts.requestMs, (budget) => this.performOperationWithinBudget(operation, input, requestOptionsWithinBudget(options, budget)));
|
|
@@ -211,6 +211,8 @@ export class ConnectionTransport {
|
|
|
211
211
|
&& !freshReadRetried) {
|
|
212
212
|
return this.performOperationWithinBudget(operation, input, options, undefined, true);
|
|
213
213
|
}
|
|
214
|
+
if (attempt.pendingMutation && error.outcomeUnknown)
|
|
215
|
+
throw error;
|
|
214
216
|
if (attempt.pendingMutation
|
|
215
217
|
&& (attempt.directDeliveryUncertain
|
|
216
218
|
|| (attempt.encryptedRequest && attempt.resumingMutation))) {
|
|
@@ -248,16 +250,14 @@ export class ConnectionTransport {
|
|
|
248
250
|
&& !freshReadRetried) {
|
|
249
251
|
return this.performOperationWithinBudget(operation, input, options, undefined, true);
|
|
250
252
|
}
|
|
253
|
+
if (!decrypted.ok) {
|
|
254
|
+
const error = encryptedOperationError(decrypted.problem);
|
|
255
|
+
if (attempt.pendingMutation && !error.outcomeUnknown)
|
|
256
|
+
this.clearPendingMutation(attempt.requestId);
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
251
259
|
if (attempt.pendingMutation)
|
|
252
260
|
this.clearPendingMutation(attempt.requestId);
|
|
253
|
-
if (!decrypted.ok)
|
|
254
|
-
throw serverConnectError(decrypted.problem.code === "unknown"
|
|
255
|
-
? decrypted.problem.server_code
|
|
256
|
-
: decrypted.problem.code, decrypted.problem.message, {
|
|
257
|
-
details: decrypted.problem.details,
|
|
258
|
-
operationOutcome: decrypted.problem.operation_outcome ?? "rejected",
|
|
259
|
-
traceId: decrypted.problem.trace_id
|
|
260
|
-
});
|
|
261
261
|
return decrypted.result;
|
|
262
262
|
}
|
|
263
263
|
catch (error) {
|
|
@@ -289,7 +289,7 @@ export class ConnectionTransport {
|
|
|
289
289
|
const recovery = await retryBusy(error);
|
|
290
290
|
if (recovery.retried)
|
|
291
291
|
return recovery.result;
|
|
292
|
-
if (attempt.pendingMutation)
|
|
292
|
+
if (attempt.pendingMutation && !error.outcomeUnknown)
|
|
293
293
|
this.clearPendingMutation(attempt.requestId);
|
|
294
294
|
throw error;
|
|
295
295
|
}
|
|
@@ -377,7 +377,9 @@ export class ConnectionTransport {
|
|
|
377
377
|
if (!pending.envelope) {
|
|
378
378
|
throw connectError("pending_mutation_unresolved", "The pending write belongs to a different transport. Reconnect before retrying it.");
|
|
379
379
|
}
|
|
380
|
-
|
|
380
|
+
// Preserve the durable identity and ciphertext, but refresh the
|
|
381
|
+
// unauthenticated scheduling deadline for this recovery attempt.
|
|
382
|
+
encryptedRequest = withOperationDeadline(pending.envelope, options.deadlineUnixMs);
|
|
381
383
|
}
|
|
382
384
|
else {
|
|
383
385
|
encryptedRequest = await encryptRelayRequest(this.keyStore, token.keyHandle, { grantId: token.grantId, applicationId: token.clientId, encryption: token.encryption }, operation, input, requestId, options.deadlineUnixMs);
|
|
@@ -457,8 +459,11 @@ export class ConnectionTransport {
|
|
|
457
459
|
this.markDirectUnavailable();
|
|
458
460
|
}
|
|
459
461
|
catch (error) {
|
|
460
|
-
if (options.signal?.aborted)
|
|
462
|
+
if (options.signal?.aborted) {
|
|
463
|
+
if (pendingMutation)
|
|
464
|
+
throw unknownMutationOutcome(requestId, error);
|
|
461
465
|
throw error;
|
|
466
|
+
}
|
|
462
467
|
directDeliveryUncertain = true;
|
|
463
468
|
if ((await localNetworkPermission()) === "denied")
|
|
464
469
|
this.setDirectStatus("denied");
|
|
@@ -493,7 +498,7 @@ export class ConnectionTransport {
|
|
|
493
498
|
});
|
|
494
499
|
}
|
|
495
500
|
catch (error) {
|
|
496
|
-
if (pendingMutation
|
|
501
|
+
if (pendingMutation)
|
|
497
502
|
throw unknownMutationOutcome(requestId, error);
|
|
498
503
|
throw error;
|
|
499
504
|
}
|
|
@@ -516,16 +521,15 @@ export class ConnectionTransport {
|
|
|
516
521
|
const proof = token.authority
|
|
517
522
|
? await authorityProofHeaders(this.keyStore, token, "POST", operationUrl, operationBody, token.authority.accessToken)
|
|
518
523
|
: {};
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
});
|
|
524
|
+
let response;
|
|
525
|
+
try {
|
|
526
|
+
response = await fetchOperationRequest(operationUrl, token.authority?.accessToken ?? token.accessToken, proof, operationBody, options.signal);
|
|
527
|
+
}
|
|
528
|
+
catch (error) {
|
|
529
|
+
if (pendingMutation)
|
|
530
|
+
throw unknownMutationOutcome(requestId, error);
|
|
531
|
+
throw error;
|
|
532
|
+
}
|
|
529
533
|
if (response.ok)
|
|
530
534
|
this.setRoute(token.authority ? "remote" : "relay");
|
|
531
535
|
return {
|
package/dist/files.js
CHANGED
|
@@ -387,8 +387,14 @@ export class MdbaseFileClient {
|
|
|
387
387
|
}
|
|
388
388
|
}
|
|
389
389
|
catch (error) {
|
|
390
|
+
const failure = options.signal?.aborted
|
|
391
|
+
? connectError("operation_cancelled", "The file transfer was cancelled.", {
|
|
392
|
+
operationOutcome: "not_sent",
|
|
393
|
+
cause: options.signal.reason
|
|
394
|
+
})
|
|
395
|
+
: error;
|
|
390
396
|
await finish();
|
|
391
|
-
controller.error(normalizeFileError(
|
|
397
|
+
controller.error(normalizeFileError(failure));
|
|
392
398
|
}
|
|
393
399
|
},
|
|
394
400
|
cancel: finish
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CollectionOperation } from "@mdbase-dev/connect-protocol";
|
|
1
|
+
import { type CollectionOperation, type ConnectProblem, type EncryptedRelayOperationRequest } 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";
|
|
@@ -9,10 +9,13 @@ export declare function loopbackRequest(init: RequestInit): LoopbackRequestInit;
|
|
|
9
9
|
export declare function localNetworkPermission(): Promise<PermissionState | null>;
|
|
10
10
|
export declare function directFallbackStatus(status: number): boolean;
|
|
11
11
|
export declare function isMutation(operation: CollectionOperation, input?: unknown): boolean;
|
|
12
|
+
export declare function withOperationDeadline(request: EncryptedRelayOperationRequest, deadlineUnixMs?: number): EncryptedRelayOperationRequest;
|
|
12
13
|
export declare function uniqueOperations(operations: CollectionOperation[]): CollectionOperation[];
|
|
13
14
|
export declare function sameAuthorization(left: StoredToken, right: StoredToken): boolean;
|
|
14
15
|
export declare function throwIfCancelled(signal?: AbortSignal): void;
|
|
15
16
|
export declare function operationTransportError(error: unknown, signal: AbortSignal | undefined, pendingRequestId: string | undefined, unavailableCode: "hosted_provider_unavailable" | "relay_unavailable"): Error;
|
|
17
|
+
export declare function encryptedOperationError(problem: ConnectProblem): MdbaseConnectError;
|
|
18
|
+
export declare function fetchOperationRequest(url: string, accessToken: string, proof: Record<string, string>, body: string, signal?: AbortSignal): Promise<Response>;
|
|
16
19
|
export declare function isCancellation(error: unknown, signal?: AbortSignal): boolean;
|
|
17
20
|
export declare function assertRenamePreview(input: RenameInput, preview: RenamePreflightResult): void;
|
|
18
21
|
export declare function assertDeletePreview(input: DeleteInput, preview: DeletePreflightResult): void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isMutatingOperation, mutationFingerprint } from "@mdbase-dev/connect-protocol";
|
|
2
|
-
import { MdbaseConnectError, connectError } from "./errors.js";
|
|
2
|
+
import { MdbaseConnectError, connectError, serverConnectError } from "./errors.js";
|
|
3
3
|
export function loopbackRequest(init) {
|
|
4
4
|
return { ...init, credentials: "omit", targetAddressSpace: "loopback" };
|
|
5
5
|
}
|
|
@@ -22,6 +22,15 @@ export function directFallbackStatus(status) {
|
|
|
22
22
|
export function isMutation(operation, input) {
|
|
23
23
|
return isMutatingOperation(operation, input);
|
|
24
24
|
}
|
|
25
|
+
export function withOperationDeadline(request, deadlineUnixMs) {
|
|
26
|
+
if (request.deadline_unix_ms === deadlineUnixMs)
|
|
27
|
+
return request;
|
|
28
|
+
const { deadline_unix_ms: _previousDeadline, ...identity } = request;
|
|
29
|
+
return {
|
|
30
|
+
...identity,
|
|
31
|
+
...(deadlineUnixMs === undefined ? {} : { deadline_unix_ms: deadlineUnixMs })
|
|
32
|
+
};
|
|
33
|
+
}
|
|
25
34
|
export function uniqueOperations(operations) {
|
|
26
35
|
return [...new Set(operations)];
|
|
27
36
|
}
|
|
@@ -61,6 +70,25 @@ export function operationTransportError(error, signal, pendingRequestId, unavail
|
|
|
61
70
|
}
|
|
62
71
|
return error instanceof Error ? error : new Error(String(error));
|
|
63
72
|
}
|
|
73
|
+
export function encryptedOperationError(problem) {
|
|
74
|
+
return serverConnectError(problem.code === "unknown" ? problem.server_code : problem.code, problem.message, {
|
|
75
|
+
details: problem.details,
|
|
76
|
+
operationOutcome: problem.operation_outcome ?? "rejected",
|
|
77
|
+
traceId: problem.trace_id
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
export function fetchOperationRequest(url, accessToken, proof, body, signal) {
|
|
81
|
+
return fetch(url, {
|
|
82
|
+
method: "POST",
|
|
83
|
+
headers: {
|
|
84
|
+
authorization: `Bearer ${accessToken}`,
|
|
85
|
+
"content-type": "application/json",
|
|
86
|
+
...proof
|
|
87
|
+
},
|
|
88
|
+
body,
|
|
89
|
+
signal
|
|
90
|
+
});
|
|
91
|
+
}
|
|
64
92
|
export function isCancellation(error, signal) {
|
|
65
93
|
return signal?.aborted === true
|
|
66
94
|
|| (error instanceof MdbaseConnectError && error.code === "operation_cancelled");
|
package/dist/runtime-utils.js
CHANGED
|
@@ -49,7 +49,12 @@ export async function createPkce() {
|
|
|
49
49
|
return { verifier, challenge: bytesToBase64Url(new Uint8Array(digest)) };
|
|
50
50
|
}
|
|
51
51
|
export function apiError(body, fallbackCode, fallbackMessage, status) {
|
|
52
|
-
return serverConnectError(oauthErrorCode(body) ?? body?.error?.code ?? fallbackCode, body?.error_description ?? body?.error?.message ?? fallbackMessage, {
|
|
52
|
+
return serverConnectError(oauthErrorCode(body) ?? body?.error?.code ?? fallbackCode, body?.error_description ?? body?.error?.message ?? fallbackMessage, {
|
|
53
|
+
status,
|
|
54
|
+
details: body?.error?.details,
|
|
55
|
+
operationOutcome: body?.error?.operation_outcome,
|
|
56
|
+
traceId: body?.error?.trace_id
|
|
57
|
+
});
|
|
53
58
|
}
|
|
54
59
|
export function oauthErrorCode(body) {
|
|
55
60
|
return typeof body?.error === "string" ? body.error : undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mdbase-dev/connect",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.64",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"README.md"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@mdbase-dev/connect-protocol": "0.1.0-beta.
|
|
36
|
+
"@mdbase-dev/connect-protocol": "0.1.0-beta.64"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"esbuild": "^0.28.1",
|