@mdbase-dev/connect 0.1.0-beta.59 → 0.1.0-beta.60

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-69NPRMI4u0984KosSyXFlbNrbhqiCITqyXNmYsN/zEQtLEztYB+0g+zi9QKz97ij
1
+ sha384-8q/ad0vEZFISbJVmcgiwghE8Zi5/u4kBtZPskPy+cHhXMEwZoHGTiGdujLx57o8/
@@ -32,11 +32,7 @@ export declare class ConnectionTransport {
32
32
  requestDirectAccess(options?: ConnectRequestOptions): Promise<DirectAccessStatus>;
33
33
  private requestDirectAccessWithinBudget;
34
34
  disableDirectAccess(): void;
35
- /**
36
- * Return one offline-replication transport regardless of whether the
37
- * authority is remote, directly connected, or relay connected.
38
- * Authority credentials and routing stay private inside the SDK.
39
- */
35
+ /** Return pending writes without exposing authority transport credentials. */
40
36
  pendingMutations(): readonly PendingMutationSummary[];
41
37
  pendingMutation(requestId: string): PendingMutationSummary | null;
42
38
  recoverPendingMutation<Result>(requestId: string, options?: ConnectRequestOptions): Promise<Result>;
@@ -10,6 +10,8 @@ import { readStoredToken } from "./stored-token.js";
10
10
  import { withCooperativeRequestBudget, withRequestBudget } from "./request-budget.js";
11
11
  import { sendAuthoritySyncRequest } from "./authority-sync-request.js";
12
12
  import { freshEncryptedRequest } from "./fresh-encrypted-request.js";
13
+ import { isExplicitConnectorBusyResponse, retryExplicitConnectorBusy } from "./transient-retry.js";
14
+ import { probeLoopbackAccess, tokenSupportsDirectAccess } from "./direct-access.js";
13
15
  export class ConnectionTransport {
14
16
  serverUrl;
15
17
  storage;
@@ -63,9 +65,7 @@ export class ConnectionTransport {
63
65
  get route() {
64
66
  return this.currentToken()?.authority ? "remote" : this.currentRoute;
65
67
  }
66
- notifyStorageChanged() {
67
- this.emitConnection();
68
- }
68
+ notifyStorageChanged() { this.emitConnection(); }
69
69
  async checkDirectAccess(options = {}) {
70
70
  return withRequestBudget(options, this.timeouts.watchStartMs, (budget) => this.checkDirectAccessWithinBudget(budget.signal));
71
71
  }
@@ -99,11 +99,7 @@ export class ConnectionTransport {
99
99
  this.setDirectStatus("disabled");
100
100
  this.setRoute("relay");
101
101
  }
102
- /**
103
- * Return one offline-replication transport regardless of whether the
104
- * authority is remote, directly connected, or relay connected.
105
- * Authority credentials and routing stay private inside the SDK.
106
- */
102
+ /** Return pending writes without exposing authority transport credentials. */
107
103
  pendingMutations() {
108
104
  return this.storedPendingMutations().map((pending) => ({
109
105
  requestId: pending.requestId,
@@ -136,7 +132,7 @@ export class ConnectionTransport {
136
132
  signal: budget.signal
137
133
  }));
138
134
  }
139
- async performOperationWithinBudget(operation, input, options, storedPending, freshReadRetried = false) {
135
+ async performOperationWithinBudget(operation, input, options, storedPending, freshReadRetried = false, connectorBusyRetries = 0, knownRejectedMutationRetry = false) {
140
136
  throwIfCancelled(options.signal);
141
137
  let token = this.currentToken();
142
138
  if (!token)
@@ -160,7 +156,7 @@ export class ConnectionTransport {
160
156
  ?? (isMutation(operation, input) ? crypto.randomUUID() : undefined);
161
157
  let attempt;
162
158
  try {
163
- attempt = await this.sendOperation(token, operation, input, tryDirect, options, pendingRequestId);
159
+ attempt = await this.sendOperation(token, operation, input, tryDirect, options, pendingRequestId, knownRejectedMutationRetry);
164
160
  }
165
161
  catch (error) {
166
162
  throw operationTransportError(error, options.signal, pendingRequestId !== undefined && this.storedPendingMutation(pendingRequestId) !== null
@@ -181,7 +177,7 @@ export class ConnectionTransport {
181
177
  token = await this.refreshAuthorization(options.signal);
182
178
  tryDirect = await this.shouldAttemptDirect(token);
183
179
  try {
184
- attempt = await this.sendOperation(token, operation, input, tryDirect, options, pendingRequestId);
180
+ attempt = await this.sendOperation(token, operation, input, tryDirect, options, pendingRequestId, knownRejectedMutationRetry);
185
181
  }
186
182
  catch (error) {
187
183
  throw operationTransportError(error, options.signal, pendingRequestId !== undefined && this.storedPendingMutation(pendingRequestId) !== null
@@ -190,6 +186,15 @@ export class ConnectionTransport {
190
186
  }
191
187
  response = attempt.response;
192
188
  }
189
+ const retryBusy = (error) => retryExplicitConnectorBusy({
190
+ error,
191
+ attempt,
192
+ completedRetries: connectorBusyRetries,
193
+ signal: options.signal,
194
+ knownRejectedMutationRetry,
195
+ clearPending: (requestId) => this.clearPendingMutation(requestId),
196
+ retry: (pending, knownRejected) => this.performOperationWithinBudget(operation, input, options, pending, freshReadRetried, connectorBusyRetries + 1, knownRejected)
197
+ });
193
198
  let body;
194
199
  try {
195
200
  body = await decodeJsonResponse(response, "invalid_operation_response", "The collection authority returned a response that is not valid JSON.");
@@ -201,6 +206,9 @@ export class ConnectionTransport {
201
206
  }
202
207
  if (!response.ok) {
203
208
  const error = apiError(body, "operation_failed", "Collection operation failed.", response.status);
209
+ const recovery = await retryBusy(error);
210
+ if (recovery.retried)
211
+ return recovery.result;
204
212
  if (error.code === "fresh_request_required"
205
213
  && !attempt.pendingMutation
206
214
  && !freshReadRetried) {
@@ -280,9 +288,13 @@ export class ConnectionTransport {
280
288
  throw unknownMutationOutcome(attempt.requestId, new Error("Operation rejection did not contain a canonical problem."));
281
289
  throw connectError("invalid_operation_response", "The collection authority returned an invalid operation problem.");
282
290
  }
291
+ const error = new MdbaseConnectError(body.problem);
292
+ const recovery = await retryBusy(error);
293
+ if (recovery.retried)
294
+ return recovery.result;
283
295
  if (attempt.pendingMutation)
284
296
  this.clearPendingMutation(attempt.requestId);
285
- throw new MdbaseConnectError(body.problem);
297
+ throw error;
286
298
  }
287
299
  if (body.ok !== true || !("result" in body)) {
288
300
  if (attempt.pendingMutation)
@@ -328,7 +340,7 @@ export class ConnectionTransport {
328
340
  }
329
341
  });
330
342
  }
331
- async sendOperation(token, operation, input, tryDirect, options = {}, pendingRequestId) {
343
+ async sendOperation(token, operation, input, tryDirect, options = {}, pendingRequestId, knownRejectedMutationRetry = false) {
332
344
  let body = input ?? {};
333
345
  let encryptedRequest;
334
346
  let pendingMutation = false;
@@ -347,7 +359,7 @@ export class ConnectionTransport {
347
359
  throw connectError("pending_mutation_unresolved", "A previous write still has an unknown outcome. Retry that exact write before making another change.");
348
360
  }
349
361
  requestId = pending.requestId;
350
- resumingMutation = true;
362
+ resumingMutation = !knownRejectedMutationRetry;
351
363
  }
352
364
  pendingMutation = true;
353
365
  }
@@ -429,7 +441,8 @@ export class ConnectionTransport {
429
441
  body: JSON.stringify(encryptedRequest),
430
442
  signal: options.signal
431
443
  }));
432
- if (!directFallbackStatus(response.status)) {
444
+ if (!directFallbackStatus(response.status)
445
+ || await isExplicitConnectorBusyResponse(response)) {
433
446
  if (response.ok) {
434
447
  this.markDirectAvailable();
435
448
  this.setRoute("direct");
@@ -528,20 +541,9 @@ export class ConnectionTransport {
528
541
  };
529
542
  }
530
543
  directCapable(token) {
531
- if (!token || token.authority || !token.encryption || !token.grantId || !token.keyHandle)
532
- return false;
533
- if (this.directAccessMode === "disabled")
534
- return false;
535
- if (typeof location !== "undefined"
536
- && token.applicationOrigin
537
- && token.applicationOrigin !== location.origin)
538
- return false;
539
- return true;
540
- }
541
- hasResumableMutationTransport() {
542
- const token = this.currentToken();
543
- return Boolean(token);
544
+ return tokenSupportsDirectAccess(token, this.directAccessMode);
544
545
  }
546
+ hasResumableMutationTransport() { return this.currentToken() !== null; }
545
547
  directEligible(token) {
546
548
  return token !== null
547
549
  && this.directCapable(token)
@@ -569,23 +571,9 @@ export class ConnectionTransport {
569
571
  }
570
572
  async probeDirectAccess(signal) {
571
573
  this.setDirectStatus("checking");
572
- try {
573
- const response = await fetch(`${this.loopbackUrl}/v1/ready`, loopbackRequest({
574
- method: "GET",
575
- cache: "no-store",
576
- signal
577
- }));
578
- const body = await decodeJsonResponse(response, "invalid_operation_response", "The connector returned an invalid readiness response.").catch(() => null);
579
- if (response.ok
580
- && body?.service === "mdbase-connect"
581
- && body?.loopback_protocol_version === 1
582
- && body?.operation_transport_protocol_version === OPERATION_TRANSPORT_PROTOCOL_VERSION) {
583
- this.markDirectAvailable();
584
- return "available";
585
- }
586
- }
587
- catch {
588
- // Permission denial, unsupported mixed content, and an absent connector all reject fetch.
574
+ if (await probeLoopbackAccess(this.loopbackUrl, OPERATION_TRANSPORT_PROTOCOL_VERSION, signal)) {
575
+ this.markDirectAvailable();
576
+ return "available";
589
577
  }
590
578
  if ((await localNetworkPermission()) === "denied")
591
579
  return this.setDirectStatus("denied");
@@ -615,9 +603,7 @@ export class ConnectionTransport {
615
603
  this.emitConnection();
616
604
  }
617
605
  }
618
- emitConnection() {
619
- this.onChange();
620
- }
606
+ emitConnection() { this.onChange(); }
621
607
  invalidateRejectedAuthorization(rejected) {
622
608
  const current = parseStored(this.storage.getItem(this.internals.tokenKey(this.collectionId)));
623
609
  if (!current || !sameAuthorization(current, rejected))
@@ -0,0 +1,3 @@
1
+ import type { StoredToken } from "./internal-types.js";
2
+ export declare function tokenSupportsDirectAccess(token: StoredToken | null, mode: "auto" | "disabled"): boolean;
3
+ export declare function probeLoopbackAccess(loopbackUrl: string, expectedOperationProtocol: number, signal?: AbortSignal): Promise<boolean>;
@@ -0,0 +1,29 @@
1
+ import { loopbackRequest } from "./operation-helpers.js";
2
+ import { decodeJsonResponse } from "./runtime-utils.js";
3
+ export function tokenSupportsDirectAccess(token, mode) {
4
+ if (!token || token.authority || !token.encryption || !token.grantId || !token.keyHandle) {
5
+ return false;
6
+ }
7
+ if (mode === "disabled")
8
+ return false;
9
+ return typeof location === "undefined"
10
+ || !token.applicationOrigin
11
+ || token.applicationOrigin === location.origin;
12
+ }
13
+ export async function probeLoopbackAccess(loopbackUrl, expectedOperationProtocol, signal) {
14
+ try {
15
+ const response = await fetch(`${loopbackUrl}/v1/ready`, loopbackRequest({
16
+ method: "GET",
17
+ cache: "no-store",
18
+ signal
19
+ }));
20
+ const body = await decodeJsonResponse(response, "invalid_operation_response", "The connector returned an invalid readiness response.").catch(() => null);
21
+ return response.ok
22
+ && body?.service === "mdbase-connect"
23
+ && body?.loopback_protocol_version === 1
24
+ && body?.operation_transport_protocol_version === expectedOperationProtocol;
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ }
@@ -1,5 +1,6 @@
1
1
  import { MdbaseConnectError, connectError } from "./errors.js";
2
2
  import { IncrementalSha256 } from "./file-sha256.js";
3
+ import { MAX_TRANSIENT_CHUNK_ATTEMPTS, waitForTransientChunkRetry } from "./transient-retry.js";
3
4
  const HASH_CHUNK_BYTES = 1024 * 1024;
4
5
  const DEFAULT_CONCURRENCY = 4;
5
6
  const MAX_CONCURRENCY = 8;
@@ -144,14 +145,17 @@ export function chunkLength(totalSize, partSize, index) {
144
145
  return Math.min(partSize, Math.max(0, totalSize - index * partSize));
145
146
  }
146
147
  export async function retryChunk(work, signal) {
147
- for (let attempt = 1; attempt <= MAX_OBJECT_ATTEMPTS; attempt += 1) {
148
+ for (let attempt = 1; attempt <= MAX_TRANSIENT_CHUNK_ATTEMPTS; attempt += 1) {
148
149
  throwIfAborted(signal);
149
150
  try {
150
151
  return await work();
151
152
  }
152
153
  catch (error) {
153
- if (signal?.aborted || attempt === MAX_OBJECT_ATTEMPTS)
154
+ if (signal?.aborted
155
+ || attempt === MAX_TRANSIENT_CHUNK_ATTEMPTS
156
+ || (error instanceof MdbaseConnectError && !error.retryable))
154
157
  throw error;
158
+ await waitForTransientChunkRetry(attempt, signal);
155
159
  }
156
160
  }
157
161
  throw new Error("Unreachable file chunk retry state.");
@@ -4,6 +4,7 @@ import { MdbaseConnectError, connectError, serverConnectError } from "./errors.j
4
4
  import { GrantFileTransferCipher } from "./file-crypto.js";
5
5
  import { directFallbackStatus, loopbackRequest } from "./operation-helpers.js";
6
6
  import { apiError, decodeJsonResponse } from "./runtime-utils.js";
7
+ import { isConnectorBusy, isExplicitConnectorBusyResponse, MAX_CONNECTOR_BUSY_RETRIES, waitForConnectorAvailability } from "./transient-retry.js";
7
8
  /** Keeps grant-bound file crypto and loopback framing out of the operation transport. */
8
9
  export class LocalFileTransport {
9
10
  options;
@@ -11,9 +12,9 @@ export class LocalFileTransport {
11
12
  this.options = options;
12
13
  }
13
14
  async control(token, method, path, input, signal) {
14
- return this.controlAttempt(token, method, path, input, signal, false);
15
+ return this.controlAttempt(token, method, path, input, signal, false, 0);
15
16
  }
16
- async controlAttempt(token, method, path, input, signal, freshReadRetried) {
17
+ async controlAttempt(token, method, path, input, signal, freshReadRetried, connectorBusyRetries) {
17
18
  requireEncryption(token);
18
19
  const controlInput = localFileControlInput(method, path, input);
19
20
  let encryptedRequest = await this.encryptControl(token, controlInput);
@@ -33,10 +34,11 @@ export class LocalFileTransport {
33
34
  throw error;
34
35
  this.options.onDirectUnavailable();
35
36
  }
36
- if (response && !directFallbackStatus(response.status)) {
37
+ if (response && (!directFallbackStatus(response.status)
38
+ || await isExplicitConnectorBusyResponse(response))) {
37
39
  if (response.ok)
38
40
  this.options.onDirectAvailable();
39
- return this.decryptControlResponse(token, method, path, input, signal, encryptedRequest, response, freshReadRetried);
41
+ return this.decryptControlResponse(token, method, path, input, signal, encryptedRequest, response, freshReadRetried, connectorBusyRetries);
40
42
  }
41
43
  if (response)
42
44
  this.options.onDirectUnavailable();
@@ -57,16 +59,21 @@ export class LocalFileTransport {
57
59
  }
58
60
  if (response.ok)
59
61
  this.options.onRelayAvailable();
60
- return this.decryptControlResponse(token, method, path, input, signal, encryptedRequest, response, freshReadRetried);
62
+ return this.decryptControlResponse(token, method, path, input, signal, encryptedRequest, response, freshReadRetried, connectorBusyRetries);
61
63
  }
62
- async decryptControlResponse(token, method, path, input, signal, encryptedRequest, response, freshReadRetried) {
64
+ async decryptControlResponse(token, method, path, input, signal, encryptedRequest, response, freshReadRetried, connectorBusyRetries) {
63
65
  const body = await decodeJsonResponse(response, "invalid_operation_response", "The collection authority returned an invalid file control response.");
64
66
  if (!response.ok) {
65
67
  const error = apiError(body, "operation_failed", "Collection file request failed.", response.status);
68
+ if (isConnectorBusy(error)
69
+ && connectorBusyRetries < MAX_CONNECTOR_BUSY_RETRIES) {
70
+ await waitForConnectorAvailability(connectorBusyRetries, signal);
71
+ return this.controlAttempt(token, method, path, input, signal, freshReadRetried, connectorBusyRetries + 1);
72
+ }
66
73
  if (error.code === "fresh_request_required"
67
74
  && method === "GET"
68
75
  && !freshReadRetried) {
69
- return this.controlAttempt(token, method, path, input, signal, true);
76
+ return this.controlAttempt(token, method, path, input, signal, true, connectorBusyRetries);
70
77
  }
71
78
  throw error;
72
79
  }
@@ -79,10 +86,17 @@ export class LocalFileTransport {
79
86
  && decrypted.problem.code === "fresh_request_required"
80
87
  && method === "GET"
81
88
  && !freshReadRetried) {
82
- return this.controlAttempt(token, method, path, input, signal, true);
89
+ return this.controlAttempt(token, method, path, input, signal, true, connectorBusyRetries);
90
+ }
91
+ if (!decrypted.ok) {
92
+ const error = encryptedProblem(decrypted.problem);
93
+ if (isConnectorBusy(error)
94
+ && connectorBusyRetries < MAX_CONNECTOR_BUSY_RETRIES) {
95
+ await waitForConnectorAvailability(connectorBusyRetries, signal);
96
+ return this.controlAttempt(token, method, path, input, signal, freshReadRetried, connectorBusyRetries + 1);
97
+ }
98
+ throw error;
83
99
  }
84
- if (!decrypted.ok)
85
- throwEncryptedProblem(decrypted.problem);
86
100
  return decrypted.result;
87
101
  }
88
102
  async uploadChunk(session, chunkIndex, bytes, signal) {
@@ -99,7 +113,8 @@ export class LocalFileTransport {
99
113
  throw error;
100
114
  this.options.onDirectUnavailable();
101
115
  }
102
- if (response && !directFallbackStatus(response.status)) {
116
+ if (response && (!directFallbackStatus(response.status)
117
+ || await isExplicitConnectorBusyResponse(response))) {
103
118
  if (response.ok)
104
119
  this.options.onDirectAvailable();
105
120
  return requireSuccessfulChunkResponse(response, "Encrypted file chunk upload failed.");
@@ -132,7 +147,8 @@ export class LocalFileTransport {
132
147
  throw error;
133
148
  this.options.onDirectUnavailable();
134
149
  }
135
- if (response && !directFallbackStatus(response.status)) {
150
+ if (response && (!directFallbackStatus(response.status)
151
+ || await isExplicitConnectorBusyResponse(response))) {
136
152
  if (response.ok)
137
153
  this.options.onDirectAvailable();
138
154
  return this.decryptDownloadResponse(token, session, chunkIndex, response);
@@ -313,8 +329,8 @@ function validateDownloadFrame(session, chunkIndex, encoded) {
313
329
  throw connectError("invalid_operation_response", "The connector returned a file frame for a different chunk.");
314
330
  }
315
331
  }
316
- function throwEncryptedProblem(problem) {
317
- throw serverConnectError(problem.code === "unknown" ? problem.server_code : problem.code, problem.message, {
332
+ function encryptedProblem(problem) {
333
+ return serverConnectError(problem.code === "unknown" ? problem.server_code : problem.code, problem.message, {
318
334
  details: problem.details,
319
335
  operationOutcome: problem.operation_outcome ?? "rejected",
320
336
  traceId: problem.trace_id
@@ -0,0 +1,22 @@
1
+ import { MdbaseConnectError } from "./errors.js";
2
+ import type { OperationAttempt, PendingMutation } from "./internal-types.js";
3
+ export declare const MAX_CONNECTOR_BUSY_RETRIES = 8;
4
+ export declare const MAX_TRANSIENT_CHUNK_ATTEMPTS = 10;
5
+ export declare function isConnectorBusy(error: unknown): error is MdbaseConnectError;
6
+ export declare function retryExplicitConnectorBusy<Result>(options: {
7
+ error: unknown;
8
+ attempt: OperationAttempt;
9
+ completedRetries: number;
10
+ signal?: AbortSignal;
11
+ knownRejectedMutationRetry: boolean;
12
+ clearPending(requestId: string): void;
13
+ retry(pending: PendingMutation | undefined, knownRejected: boolean): Promise<Result>;
14
+ }): Promise<{
15
+ retried: false;
16
+ } | {
17
+ retried: true;
18
+ result: Result;
19
+ }>;
20
+ export declare function isExplicitConnectorBusyResponse(response: Response): Promise<boolean>;
21
+ export declare function waitForConnectorAvailability(retryIndex: number, signal?: AbortSignal): Promise<void>;
22
+ export declare function waitForTransientChunkRetry(completedAttempt: number, signal?: AbortSignal): Promise<void>;
@@ -0,0 +1,59 @@
1
+ import { abortableDelay } from "./async.js";
2
+ import { MdbaseConnectError, connectError } from "./errors.js";
3
+ export const MAX_CONNECTOR_BUSY_RETRIES = 8;
4
+ export const MAX_TRANSIENT_CHUNK_ATTEMPTS = 10;
5
+ export function isConnectorBusy(error) {
6
+ return error instanceof MdbaseConnectError && error.code === "connector_busy";
7
+ }
8
+ export async function retryExplicitConnectorBusy(options) {
9
+ if (!isConnectorBusy(options.error)
10
+ || options.completedRetries >= MAX_CONNECTOR_BUSY_RETRIES) {
11
+ return { retried: false };
12
+ }
13
+ try {
14
+ await waitForConnectorAvailability(options.completedRetries, options.signal);
15
+ }
16
+ catch (error) {
17
+ if (options.attempt.pendingMutation
18
+ && (options.knownRejectedMutationRetry || !options.attempt.resumingMutation)) {
19
+ options.clearPending(options.attempt.requestId);
20
+ }
21
+ throw error;
22
+ }
23
+ return {
24
+ retried: true,
25
+ result: await options.retry(options.attempt.pendingMutationRecord, options.knownRejectedMutationRetry
26
+ || (options.attempt.pendingMutation === true && !options.attempt.resumingMutation))
27
+ };
28
+ }
29
+ export async function isExplicitConnectorBusyResponse(response) {
30
+ if (response.status !== 502 && response.status !== 503)
31
+ return false;
32
+ const body = await response.clone().json().catch(() => null);
33
+ return body?.error === "connector_busy"
34
+ || (typeof body?.error === "object"
35
+ && body.error?.code === "connector_busy");
36
+ }
37
+ export function waitForConnectorAvailability(retryIndex, signal) {
38
+ return retryDelay(jitteredBackoff(50, 800, retryIndex), signal);
39
+ }
40
+ export function waitForTransientChunkRetry(completedAttempt, signal) {
41
+ return retryDelay(jitteredBackoff(100, 1_000, completedAttempt - 1), signal);
42
+ }
43
+ async function retryDelay(milliseconds, signal) {
44
+ try {
45
+ await abortableDelay(milliseconds, signal);
46
+ }
47
+ catch (cause) {
48
+ if (signal?.aborted) {
49
+ if (signal.reason instanceof MdbaseConnectError)
50
+ throw signal.reason;
51
+ throw connectError("operation_cancelled", "The operation was cancelled while waiting for connector capacity.", { operationOutcome: "not_sent", cause });
52
+ }
53
+ throw cause;
54
+ }
55
+ }
56
+ function jitteredBackoff(base, cap, exponent) {
57
+ const bounded = Math.min(cap, base * (2 ** Math.max(0, exponent)));
58
+ return Math.max(1, Math.round(bounded * (0.75 + Math.random() * 0.5)));
59
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mdbase-dev/connect",
3
- "version": "0.1.0-beta.59",
3
+ "version": "0.1.0-beta.60",
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.59"
36
+ "@mdbase-dev/connect-protocol": "0.1.0-beta.60"
37
37
  },
38
38
  "devDependencies": {
39
39
  "esbuild": "^0.28.1",