@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.
@@ -1 +1 @@
1
- sha384-bNuQ6oZAI3yOsHt34NNTiVWa2gbZsyM2B4Iy1Gy4WQdXBSxEtUIgtaPLepNJYZjW
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 ?? {}, { ...options, signal: budget.signal }, pending));
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
- encryptedRequest = pending.envelope;
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 && (directDeliveryUncertain || resumingMutation))
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
- const response = await fetch(operationUrl, {
520
- method: "POST",
521
- headers: {
522
- authorization: `Bearer ${token.authority?.accessToken ?? token.accessToken}`,
523
- "content-type": "application/json",
524
- ...proof
525
- },
526
- body: operationBody,
527
- signal: options.signal
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(error));
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");
@@ -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, { status, details: body?.error?.details });
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.62",
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.62"
36
+ "@mdbase-dev/connect-protocol": "0.1.0-beta.64"
37
37
  },
38
38
  "devDependencies": {
39
39
  "esbuild": "^0.28.1",