@mdbase-dev/connect 0.1.0-beta.83 → 0.1.0-beta.85

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.
Files changed (42) hide show
  1. package/README.md +33 -6
  2. package/dist/advanced.d.ts +1 -1
  3. package/dist/application-session.d.ts +31 -10
  4. package/dist/application-session.js +278 -64
  5. package/dist/authorization-refresh.d.ts +14 -0
  6. package/dist/authorization-refresh.js +41 -0
  7. package/dist/authorization-url.d.ts +1 -0
  8. package/dist/authorization-url.js +9 -0
  9. package/dist/browser/integrity.json +1 -1
  10. package/dist/browser/mdbase-connect.min.js +3 -3
  11. package/dist/browser/mdbase-connect.min.js.sha384 +1 -1
  12. package/dist/connect.d.ts +4 -1
  13. package/dist/connect.js +33 -26
  14. package/dist/connection-file-transport.d.ts +3 -0
  15. package/dist/connection-file-transport.js +59 -16
  16. package/dist/connection-transport-internals.d.ts +2 -0
  17. package/dist/connection-transport.d.ts +3 -1
  18. package/dist/connection-transport.js +90 -82
  19. package/dist/grant-key-leases.d.ts +24 -0
  20. package/dist/grant-key-leases.js +144 -0
  21. package/dist/hosted-file-request.d.ts +2 -2
  22. package/dist/hosted-file-request.js +4 -4
  23. package/dist/hosted-operation-response.d.ts +5 -0
  24. package/dist/hosted-operation-response.js +26 -0
  25. package/dist/internal-types.d.ts +2 -0
  26. package/dist/local-file-transport.d.ts +8 -2
  27. package/dist/local-file-transport.js +71 -20
  28. package/dist/mdbase-connect.d.ts +1 -0
  29. package/dist/mdbase-connect.js +3 -0
  30. package/dist/outcomes.d.ts +12 -12
  31. package/dist/outcomes.js +6 -0
  32. package/dist/pending-recovery-transports.d.ts +1 -0
  33. package/dist/pending-recovery-transports.js +7 -3
  34. package/dist/retired-grant-keys.d.ts +9 -0
  35. package/dist/retired-grant-keys.js +56 -0
  36. package/dist/session.d.ts +36 -7
  37. package/dist/session.js +287 -34
  38. package/dist/shared-operation.d.ts +7 -0
  39. package/dist/shared-operation.js +41 -0
  40. package/dist/token-response.d.ts +2 -1
  41. package/dist/token-response.js +2 -1
  42. package/package.json +2 -2
@@ -0,0 +1,144 @@
1
+ export class GrantKeyLeaseRegistry {
2
+ namespace;
3
+ onFinalRelease;
4
+ leases = new Map();
5
+ locks = webLockManager();
6
+ retainAutomaticallyRetiredKeys = this.locks === null;
7
+ constructor(namespace, onFinalRelease) {
8
+ this.namespace = namespace;
9
+ this.onFinalRelease = onFinalRelease;
10
+ }
11
+ async acquire(collectionId, keyHandle, signal) {
12
+ const releaseLocal = this.acquireLocal(collectionId, keyHandle);
13
+ if (!this.locks)
14
+ return releaseLocal;
15
+ try {
16
+ const releaseWeb = await acquireSharedWebLock(this.locks, this.lockName(collectionId, keyHandle), signal);
17
+ return once(() => {
18
+ releaseWeb();
19
+ releaseLocal();
20
+ });
21
+ }
22
+ catch (error) {
23
+ releaseLocal();
24
+ throw error;
25
+ }
26
+ }
27
+ deleteExclusive(collectionId, keyHandle, action) {
28
+ if (this.retainAutomaticallyRetiredKeys)
29
+ return;
30
+ if (this.locks) {
31
+ void this.locks.request(this.lockName(collectionId, keyHandle), { mode: "exclusive" }, action).catch(() => undefined);
32
+ }
33
+ else if (!this.has(collectionId, keyHandle)) {
34
+ void Promise.resolve().then(action).catch(() => undefined);
35
+ }
36
+ }
37
+ has(collectionId, keyHandle) {
38
+ return this.leases.get(collectionId)?.has(keyHandle) ?? false;
39
+ }
40
+ handles(collectionId) {
41
+ return this.leases.get(collectionId)?.keys() ?? [];
42
+ }
43
+ acquireLocal(collectionId, keyHandle) {
44
+ const collection = this.leases.get(collectionId) ?? new Map();
45
+ collection.set(keyHandle, (collection.get(keyHandle) ?? 0) + 1);
46
+ this.leases.set(collectionId, collection);
47
+ return once(() => {
48
+ const remaining = (collection.get(keyHandle) ?? 1) - 1;
49
+ if (remaining > 0)
50
+ collection.set(keyHandle, remaining);
51
+ else {
52
+ collection.delete(keyHandle);
53
+ if (collection.size === 0)
54
+ this.leases.delete(collectionId);
55
+ this.onFinalRelease(collectionId, keyHandle);
56
+ }
57
+ });
58
+ }
59
+ lockName(collectionId, keyHandle) {
60
+ return `mdbase-connect:grant-key:${this.namespace}:${collectionId}:${keyHandle}`;
61
+ }
62
+ }
63
+ export class GrantKeyLeaseSet {
64
+ collectionId;
65
+ acquire;
66
+ releases = new Map();
67
+ constructor(collectionId, acquire) {
68
+ this.collectionId = collectionId;
69
+ this.acquire = acquire;
70
+ }
71
+ async retain(tokenOrHandle, signal) {
72
+ const handle = typeof tokenOrHandle === "string" ? tokenOrHandle : tokenOrHandle?.keyHandle;
73
+ if (!handle)
74
+ return;
75
+ let lease = this.releases.get(handle);
76
+ if (!lease) {
77
+ lease = this.acquire(this.collectionId, handle, signal);
78
+ this.releases.set(handle, lease);
79
+ }
80
+ try {
81
+ await lease;
82
+ }
83
+ catch (error) {
84
+ if (this.releases.get(handle) === lease)
85
+ this.releases.delete(handle);
86
+ this.release();
87
+ throw error;
88
+ }
89
+ }
90
+ release() {
91
+ for (const lease of this.releases.values())
92
+ void lease.then((release) => release());
93
+ this.releases.clear();
94
+ }
95
+ }
96
+ export async function retainCurrentGrantToken(readCurrent, leases, signal) {
97
+ while (true) {
98
+ const snapshot = readCurrent();
99
+ if (!snapshot)
100
+ return null;
101
+ await leases.retain(snapshot, signal);
102
+ const current = readCurrent();
103
+ if (!current)
104
+ return null;
105
+ if (current.keyHandle === snapshot.keyHandle)
106
+ return current;
107
+ }
108
+ }
109
+ function webLockManager() {
110
+ const locks = typeof navigator === "undefined"
111
+ ? undefined
112
+ : navigator.locks;
113
+ if (locks)
114
+ return locks;
115
+ const browser = typeof window !== "undefined"
116
+ || typeof globalThis.importScripts === "function";
117
+ return browser ? null : undefined;
118
+ }
119
+ async function acquireSharedWebLock(locks, name, signal) {
120
+ let acquired;
121
+ let rejectAcquisition;
122
+ let release;
123
+ const ready = new Promise((resolve, reject) => {
124
+ acquired = resolve;
125
+ rejectAcquisition = reject;
126
+ });
127
+ const held = new Promise((resolve) => { release = resolve; });
128
+ const request = locks.request(name, { mode: "shared", ...(signal ? { signal } : {}) }, async () => {
129
+ acquired();
130
+ await held;
131
+ });
132
+ void request.catch(rejectAcquisition);
133
+ await ready;
134
+ return once(release);
135
+ }
136
+ function once(action) {
137
+ let called = false;
138
+ return () => {
139
+ if (called)
140
+ return;
141
+ called = true;
142
+ action();
143
+ };
144
+ }
@@ -1,6 +1,6 @@
1
1
  import type { StoredToken } from "./internal-types.js";
2
2
  type ProofHeaders = (token: StoredToken, method: "GET" | "POST" | "DELETE", url: string, body: string | undefined, credential: string) => Promise<Record<string, string>>;
3
3
  export declare function sendHostedFileRequest(token: StoredToken, method: "GET" | "POST" | "DELETE", path: string, input: unknown, signal: AbortSignal | undefined, proofHeaders: ProofHeaders): Promise<Response>;
4
- export declare function performHostedFileRequest<Result>(token: StoredToken, method: "GET" | "POST" | "DELETE", path: string, input: unknown, signal: AbortSignal | undefined, refresh: () => Promise<StoredToken>, proofHeaders: ProofHeaders): Promise<Result>;
5
- export declare function performHostedFilePartRequest(token: StoredToken, path: string, expectedLength: number, signal: AbortSignal | undefined, refresh: () => Promise<StoredToken>, proofHeaders: ProofHeaders): Promise<ReadableStream<Uint8Array>>;
4
+ export declare function performHostedFileRequest<Result>(token: StoredToken, method: "GET" | "POST" | "DELETE", path: string, input: unknown, signal: AbortSignal | undefined, refresh: (signal?: AbortSignal) => Promise<StoredToken>, proofHeaders: ProofHeaders, retain?: (token: StoredToken) => Promise<StoredToken>): Promise<Result>;
5
+ export declare function performHostedFilePartRequest(token: StoredToken, path: string, expectedLength: number, signal: AbortSignal | undefined, refresh: (signal?: AbortSignal) => Promise<StoredToken>, proofHeaders: ProofHeaders, retain?: (token: StoredToken) => Promise<StoredToken>): Promise<ReadableStream<Uint8Array>>;
6
6
  export {};
@@ -20,11 +20,11 @@ export async function sendHostedFileRequest(token, method, path, input, signal,
20
20
  ...(signal ? { signal } : {})
21
21
  });
22
22
  }
23
- export async function performHostedFileRequest(token, method, path, input, signal, refresh, proofHeaders) {
23
+ export async function performHostedFileRequest(token, method, path, input, signal, refresh, proofHeaders, retain = async (token) => token) {
24
24
  let active = token;
25
25
  let response = await sendHostedFileRequest(active, method, path, input, signal, proofHeaders);
26
26
  if (response.status === 401 && active.refreshToken) {
27
- active = await refresh();
27
+ active = await retain(await refresh(signal));
28
28
  if (!active.authority || !active.fileCapability) {
29
29
  throw connectError("authority_authorization_changed", "Reconnect this collection before accessing its files.");
30
30
  }
@@ -36,11 +36,11 @@ export async function performHostedFileRequest(token, method, path, input, signa
36
36
  }
37
37
  return body;
38
38
  }
39
- export async function performHostedFilePartRequest(token, path, expectedLength, signal, refresh, proofHeaders) {
39
+ export async function performHostedFilePartRequest(token, path, expectedLength, signal, refresh, proofHeaders, retain = async (token) => token) {
40
40
  let active = token;
41
41
  let response = await sendHostedFileRequest(active, "GET", path, undefined, signal, proofHeaders);
42
42
  if (response.status === 401 && active.refreshToken) {
43
- active = await refresh();
43
+ active = await retain(await refresh(signal));
44
44
  if (!active.authority || !active.fileCapability) {
45
45
  throw connectError("authority_authorization_changed", "Reconnect this collection before accessing its files.");
46
46
  }
@@ -0,0 +1,5 @@
1
+ import { type MdbaseConnectError } from "./errors.js";
2
+ export declare function decodeHostedOperationResponse(response: Response, hosted: boolean): Promise<{
3
+ body: any;
4
+ httpError?: MdbaseConnectError;
5
+ }>;
@@ -0,0 +1,26 @@
1
+ import { connectError, serverConnectError } from "./errors.js";
2
+ import { decodeJsonResponse } from "./runtime-utils.js";
3
+ export async function decodeHostedOperationResponse(response, hosted) {
4
+ try {
5
+ return {
6
+ body: await decodeJsonResponse(response, "invalid_operation_response", "The collection authority returned a response that is not valid JSON.")
7
+ };
8
+ }
9
+ catch (cause) {
10
+ if (!response.ok && hosted && isDefinitiveHostedHttpRejection(response.status)) {
11
+ return {
12
+ body: undefined,
13
+ httpError: serverConnectError("operation_failed", "Collection operation failed.", {
14
+ status: response.status,
15
+ operationOutcome: "rejected"
16
+ })
17
+ };
18
+ }
19
+ throw connectError("invalid_operation_response", "The collection authority returned a response that is not valid JSON.", { cause });
20
+ }
21
+ }
22
+ function isDefinitiveHostedHttpRejection(status) {
23
+ return status >= 400
24
+ && status < 500
25
+ && ![408, 425, 429].includes(status);
26
+ }
@@ -54,6 +54,8 @@ export interface StoredToken {
54
54
  fileCapability?: FileCapability;
55
55
  applicationOrigin?: string;
56
56
  keyHandle?: string;
57
+ /** Previous grant keys retained for durable pending-mutation recovery. */
58
+ retiredKeyHandles?: string[];
57
59
  savedAt: number;
58
60
  authority?: {
59
61
  operationsUrl: string;
@@ -5,12 +5,14 @@ interface LocalFileTransportOptions {
5
5
  keyStore: GrantKeyStore;
6
6
  serverUrl: string;
7
7
  loopbackUrl: string;
8
+ currentToken?(): StoredToken | null;
8
9
  authorizedToken(signal?: AbortSignal): Promise<StoredToken | null>;
9
10
  refreshAuthorization(signal?: AbortSignal): Promise<StoredToken>;
10
11
  shouldAttemptDirect(token: StoredToken): Promise<boolean>;
11
12
  onDirectAvailable(): void;
12
13
  onDirectUnavailable(): void;
13
14
  onRelayAvailable(): void;
15
+ acquireGrantKeyLease?(collectionId: string, keyHandle: string, signal?: AbortSignal): Promise<() => void>;
14
16
  }
15
17
  /** Keeps grant-bound file crypto and loopback framing out of the operation transport. */
16
18
  export declare class LocalFileTransport {
@@ -19,13 +21,17 @@ export declare class LocalFileTransport {
19
21
  control<Result>(token: StoredToken, method: "GET" | "POST" | "DELETE", path: string, input: unknown, signal?: AbortSignal): Promise<Result>;
20
22
  private controlAttempt;
21
23
  private decryptControlResponse;
22
- uploadChunk(session: FileTransferSession, chunkIndex: number, bytes: Uint8Array, signal?: AbortSignal): Promise<void>;
23
- downloadChunk(session: FileTransferSession, chunkIndex: number, signal?: AbortSignal): Promise<Uint8Array>;
24
+ uploadChunk(session: FileTransferSession, chunkIndex: number, bytes: Uint8Array, signal?: AbortSignal, authorizedToken?: StoredToken): Promise<void>;
25
+ private uploadChunkWithLease;
26
+ downloadChunk(session: FileTransferSession, chunkIndex: number, signal?: AbortSignal, authorizedToken?: StoredToken): Promise<Uint8Array>;
27
+ private downloadChunkWithLease;
24
28
  private decryptDownloadResponse;
25
29
  private encryptControl;
26
30
  private sendRelayControl;
27
31
  private sendRelayUpload;
28
32
  private sendRelayDownload;
29
33
  private localFileToken;
34
+ private grantKeyLeases;
35
+ private retainEntryToken;
30
36
  }
31
37
  export {};
@@ -2,6 +2,7 @@ import { decodeFileFrame, FILE_PROTOCOL_VERSION, FILE_TRANSFER_PROTOCOL_VERSION
2
2
  import { decryptRelayResponse, encryptRelayRequest } from "./crypto.js";
3
3
  import { MdbaseConnectError, connectError, serverConnectError } from "./errors.js";
4
4
  import { GrantFileTransferCipher } from "./file-crypto.js";
5
+ import { GrantKeyLeaseSet, retainCurrentGrantToken } from "./grant-key-leases.js";
5
6
  import { directFallbackStatus, loopbackRequest } from "./operation-helpers.js";
6
7
  import { apiError, decodeJsonResponse } from "./runtime-utils.js";
7
8
  import { isConnectorBusy, isExplicitConnectorBusyResponse, MAX_CONNECTOR_BUSY_RETRIES, waitForConnectorAvailability } from "./transient-retry.js";
@@ -12,9 +13,16 @@ export class LocalFileTransport {
12
13
  this.options = options;
13
14
  }
14
15
  async control(token, method, path, input, signal) {
15
- return this.controlAttempt(token, method, path, input, signal, false, 0);
16
+ const leases = this.grantKeyLeases(token.collectionId);
17
+ try {
18
+ token = await this.retainEntryToken(token, leases, signal);
19
+ return await this.controlAttempt(token, method, path, input, signal, false, 0, leases);
20
+ }
21
+ finally {
22
+ leases.release();
23
+ }
16
24
  }
17
- async controlAttempt(token, method, path, input, signal, freshReadRetried, connectorBusyRetries) {
25
+ async controlAttempt(token, method, path, input, signal, freshReadRetried, connectorBusyRetries, leases) {
18
26
  requireEncryption(token);
19
27
  const controlInput = localFileControlInput(method, path, input);
20
28
  let encryptedRequest = await this.encryptControl(token, controlInput);
@@ -38,7 +46,7 @@ export class LocalFileTransport {
38
46
  || await isExplicitConnectorBusyResponse(response))) {
39
47
  if (response.ok)
40
48
  this.options.onDirectAvailable();
41
- return this.decryptControlResponse(token, method, path, input, signal, encryptedRequest, response, freshReadRetried, connectorBusyRetries);
49
+ return this.decryptControlResponse(token, method, path, input, signal, encryptedRequest, response, freshReadRetried, connectorBusyRetries, leases);
42
50
  }
43
51
  if (response)
44
52
  this.options.onDirectUnavailable();
@@ -47,33 +55,35 @@ export class LocalFileTransport {
47
55
  token = token.expiresAt > Date.now() + 30_000
48
56
  ? token
49
57
  : await this.options.refreshAuthorization(signal);
58
+ token = await this.retainEntryToken(token, leases, signal);
50
59
  requireLocalFileToken(token);
51
60
  encryptedRequest = await this.encryptControl(token, controlInput);
52
61
  }
53
62
  response = await this.sendRelayControl(token, encryptedRequest, signal);
54
63
  if (await refreshableBindingFailure(response, token)) {
55
64
  token = await this.options.refreshAuthorization(signal);
65
+ token = await this.retainEntryToken(token, leases, signal);
56
66
  requireLocalFileToken(token);
57
67
  encryptedRequest = await this.encryptControl(token, controlInput);
58
68
  response = await this.sendRelayControl(token, encryptedRequest, signal);
59
69
  }
60
70
  if (response.ok)
61
71
  this.options.onRelayAvailable();
62
- return this.decryptControlResponse(token, method, path, input, signal, encryptedRequest, response, freshReadRetried, connectorBusyRetries);
72
+ return this.decryptControlResponse(token, method, path, input, signal, encryptedRequest, response, freshReadRetried, connectorBusyRetries, leases);
63
73
  }
64
- async decryptControlResponse(token, method, path, input, signal, encryptedRequest, response, freshReadRetried, connectorBusyRetries) {
74
+ async decryptControlResponse(token, method, path, input, signal, encryptedRequest, response, freshReadRetried, connectorBusyRetries, leases) {
65
75
  const body = await decodeJsonResponse(response, "invalid_operation_response", "The collection authority returned an invalid file control response.");
66
76
  if (!response.ok) {
67
77
  const error = apiError(body, "operation_failed", "Collection file request failed.", response.status);
68
78
  if (isConnectorBusy(error)
69
79
  && connectorBusyRetries < MAX_CONNECTOR_BUSY_RETRIES) {
70
80
  await waitForConnectorAvailability(connectorBusyRetries, signal);
71
- return this.controlAttempt(token, method, path, input, signal, freshReadRetried, connectorBusyRetries + 1);
81
+ return this.controlAttempt(token, method, path, input, signal, freshReadRetried, connectorBusyRetries + 1, leases);
72
82
  }
73
83
  if (error.code === "fresh_request_required"
74
84
  && method === "GET"
75
85
  && !freshReadRetried) {
76
- return this.controlAttempt(token, method, path, input, signal, true, connectorBusyRetries);
86
+ return this.controlAttempt(token, method, path, input, signal, true, connectorBusyRetries, leases);
77
87
  }
78
88
  throw error;
79
89
  }
@@ -86,21 +96,32 @@ export class LocalFileTransport {
86
96
  && decrypted.problem.code === "fresh_request_required"
87
97
  && method === "GET"
88
98
  && !freshReadRetried) {
89
- return this.controlAttempt(token, method, path, input, signal, true, connectorBusyRetries);
99
+ return this.controlAttempt(token, method, path, input, signal, true, connectorBusyRetries, leases);
90
100
  }
91
101
  if (!decrypted.ok) {
92
102
  const error = encryptedProblem(decrypted.problem);
93
103
  if (isConnectorBusy(error)
94
104
  && connectorBusyRetries < MAX_CONNECTOR_BUSY_RETRIES) {
95
105
  await waitForConnectorAvailability(connectorBusyRetries, signal);
96
- return this.controlAttempt(token, method, path, input, signal, freshReadRetried, connectorBusyRetries + 1);
106
+ return this.controlAttempt(token, method, path, input, signal, freshReadRetried, connectorBusyRetries + 1, leases);
97
107
  }
98
108
  throw error;
99
109
  }
100
110
  return decrypted.result;
101
111
  }
102
- async uploadChunk(session, chunkIndex, bytes, signal) {
103
- const token = await this.localFileToken(session, "upload", signal);
112
+ async uploadChunk(session, chunkIndex, bytes, signal, authorizedToken) {
113
+ const token = authorizedToken ?? await this.localFileToken(session, "upload", signal);
114
+ const leases = this.grantKeyLeases(token.collectionId);
115
+ try {
116
+ const current = await this.retainEntryToken(token, leases, signal);
117
+ validateLocalFileToken(current, session, "upload");
118
+ return await this.uploadChunkWithLease(current, leases, session, chunkIndex, bytes, signal);
119
+ }
120
+ finally {
121
+ leases.release();
122
+ }
123
+ }
124
+ async uploadChunkWithLease(token, leases, session, chunkIndex, bytes, signal) {
104
125
  let deliveryToken = token;
105
126
  let encoded = await encryptedUploadChunk(this.options.keyStore, deliveryToken, session, chunkIndex, bytes);
106
127
  let response;
@@ -125,6 +146,7 @@ export class LocalFileTransport {
125
146
  response = await this.sendRelayUpload(deliveryToken, encoded, signal);
126
147
  if (response.status === 401 && deliveryToken.refreshToken) {
127
148
  deliveryToken = await this.options.refreshAuthorization(signal);
149
+ deliveryToken = await this.retainEntryToken(deliveryToken, leases, signal);
128
150
  requireLocalFileToken(deliveryToken);
129
151
  encoded = await encryptedUploadChunk(this.options.keyStore, deliveryToken, session, chunkIndex, bytes);
130
152
  response = await this.sendRelayUpload(deliveryToken, encoded, signal);
@@ -135,8 +157,19 @@ export class LocalFileTransport {
135
157
  }
136
158
  this.options.onRelayAvailable();
137
159
  }
138
- async downloadChunk(session, chunkIndex, signal) {
139
- let token = await this.localFileToken(session, "download", signal);
160
+ async downloadChunk(session, chunkIndex, signal, authorizedToken) {
161
+ let token = authorizedToken ?? await this.localFileToken(session, "download", signal);
162
+ const leases = this.grantKeyLeases(token.collectionId);
163
+ try {
164
+ token = await this.retainEntryToken(token, leases, signal);
165
+ validateLocalFileToken(token, session, "download");
166
+ return await this.downloadChunkWithLease(token, leases, session, chunkIndex, signal);
167
+ }
168
+ finally {
169
+ leases.release();
170
+ }
171
+ }
172
+ async downloadChunkWithLease(token, leases, session, chunkIndex, signal) {
140
173
  let response;
141
174
  if (await this.options.shouldAttemptDirect(token)) {
142
175
  try {
@@ -159,6 +192,7 @@ export class LocalFileTransport {
159
192
  response = await this.sendRelayDownload(token, session, chunkIndex, signal);
160
193
  if (response.status === 401 && token.refreshToken) {
161
194
  token = await this.options.refreshAuthorization(signal);
195
+ token = await this.retainEntryToken(token, leases, signal);
162
196
  requireLocalFileToken(token);
163
197
  response = await this.sendRelayDownload(token, session, chunkIndex, signal);
164
198
  }
@@ -225,15 +259,32 @@ export class LocalFileTransport {
225
259
  }
226
260
  async localFileToken(session, direction, signal) {
227
261
  const token = await this.options.authorizedToken(signal);
228
- if (!token || token.authority || !token.fileCapability || !token.encryption
229
- || !token.grantId || !token.keyHandle
230
- || session.direction !== direction
231
- || session.protection !== "grant_aead_v1") {
232
- throw connectError("not_authorized", "This connection cannot use encrypted local file delivery.");
233
- }
234
- framedChunkSize(session);
262
+ validateLocalFileToken(token, session, direction);
235
263
  return token;
236
264
  }
265
+ grantKeyLeases(collectionId) {
266
+ return new GrantKeyLeaseSet(collectionId, this.options.acquireGrantKeyLease
267
+ ?? (() => Promise.resolve(() => undefined)));
268
+ }
269
+ async retainEntryToken(snapshot, leases, signal) {
270
+ if (!this.options.currentToken) {
271
+ await leases.retain(snapshot, signal);
272
+ return snapshot;
273
+ }
274
+ const current = await retainCurrentGrantToken(this.options.currentToken, leases, signal);
275
+ if (current)
276
+ return current;
277
+ throw connectError("not_authorized", "Reconnect this application to continue.");
278
+ }
279
+ }
280
+ function validateLocalFileToken(token, session, direction) {
281
+ if (!token || token.authority || !token.fileCapability || !token.encryption
282
+ || !token.grantId || !token.keyHandle
283
+ || session.direction !== direction
284
+ || session.protection !== "grant_aead_v1") {
285
+ throw connectError("not_authorized", "This connection cannot use encrypted local file delivery.");
286
+ }
287
+ framedChunkSize(session);
237
288
  }
238
289
  function requireLocalFileToken(token) {
239
290
  if (token.authority || !token.fileCapability) {
@@ -20,6 +20,7 @@ export declare class MdbaseConnect<Frontmatter extends JsonObject = JsonObject>
20
20
  connections(): MdbaseConnectionInfo[];
21
21
  connection(collectionId: string): MdbaseConnection<Frontmatter> | null;
22
22
  unavailableReason(collectionId: string): MdbaseUnavailableReason | null;
23
+ connectionApplicationId(collectionId: string): string | null;
23
24
  onConnectionsChange(listener: (connections: MdbaseConnectionInfo[]) => void): () => void;
24
25
  forgetAll(): void;
25
26
  /** Forget a public TOFU pin after independently verifying a replaced connector. */
@@ -35,6 +35,9 @@ export class MdbaseConnect {
35
35
  unavailableReason(collectionId) {
36
36
  return this.internals.unavailableReason(collectionId);
37
37
  }
38
+ connectionApplicationId(collectionId) {
39
+ return this.internals.connectionApplicationId(collectionId);
40
+ }
38
41
  onConnectionsChange(listener) {
39
42
  return this.internals.onConnectionsChange(listener);
40
43
  }
@@ -13,19 +13,19 @@ export type ConnectFailure<Code extends ConnectProblemCode = ConnectProblemCode>
13
13
  * for programming errors and broken SDK invariants.
14
14
  */
15
15
  export type ConnectOutcome<Value, Code extends ConnectProblemCode = ConnectProblemCode> = ConnectSuccess<Value> | ConnectFailure<Code>;
16
- export type CommonOperationProblemCode = "access_denied" | "access_paused" | "authorization_binding_incompatible" | "authorization_expired" | "capability_contract_incompatible" | "collection_access_denied" | "connector_identity_changed" | "connector_offline" | "connector_upgrade_required" | "direct_operation_rejected" | "durable_mutation_unsupported" | "operation_outcome_unknown" | "encryption_required" | "encrypted_relay_rejected" | "hosted_provider_unavailable" | "insufficient_access" | "invalid_encrypted_response" | "invalid_operation_response" | "invalid_request" | "missing_grant_key" | "mutation_recovery_expired" | "mutation_request_conflict" | "not_authorized" | "operation_cancelled" | "operation_failed" | "rate_limited" | "relay_authorization_expired" | "relay_unavailable" | "temporarily_unavailable" | "timeout" | "transport_protocol_incompatible" | "unsupported_operation";
17
- export declare const COMMON_OPERATION_PROBLEM_CODES: readonly ["access_denied", "access_paused", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation"];
16
+ export type CommonOperationProblemCode = "access_denied" | "access_paused" | "application_declaration_mismatch" | "authorization_binding_incompatible" | "authorization_expired" | "capability_contract_incompatible" | "collection_access_denied" | "connector_identity_changed" | "connector_offline" | "connector_upgrade_required" | "direct_operation_rejected" | "durable_mutation_unsupported" | "operation_outcome_unknown" | "encryption_required" | "encrypted_relay_rejected" | "hosted_provider_unavailable" | "insufficient_access" | "invalid_encrypted_response" | "invalid_operation_response" | "invalid_request" | "missing_grant_key" | "mutation_recovery_expired" | "mutation_request_conflict" | "not_authorized" | "operation_cancelled" | "operation_failed" | "rate_limited" | "relay_authorization_expired" | "relay_unavailable" | "temporarily_unavailable" | "timeout" | "transport_protocol_incompatible" | "unsupported_operation";
17
+ export declare const COMMON_OPERATION_PROBLEM_CODES: readonly ["access_denied", "access_paused", "application_declaration_mismatch", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation"];
18
18
  export declare const ALL_CONNECT_PROBLEM_CODES: ConnectProblemCode[];
19
19
  export type RegistrationProblemCode = "discovery_failed" | "invalid_operation_response" | "invalid_application_manifest" | "invalid_request" | "manifest_load_failed" | "rate_limited" | "temporarily_unavailable" | "timeout";
20
20
  export declare const REGISTRATION_PROBLEM_CODES: readonly ["discovery_failed", "invalid_operation_response", "invalid_application_manifest", "invalid_request", "manifest_load_failed", "rate_limited", "temporarily_unavailable", "timeout"];
21
- export type AuthorizationProblemCode = RegistrationProblemCode | "access_denied" | "application_identity_unavailable" | "approval_window_blocked" | "authorization_binding_incompatible" | "authorization_cancelled" | "authorization_replayed" | "browser_required" | "capability_contract_incompatible" | "collection_access_denied" | "collection_configuration_invalid" | "collection_contracts_missing" | "collection_incompatible" | "collection_invalid" | "collection_kind_unsupported" | "collection_mismatch" | "collection_not_found" | "collection_type_registry_invalid" | "collection_version_unsupported" | "connector_identity_changed" | "connector_offline" | "connector_upgrade_required" | "device_authorization_failed" | "durable_mutation_unsupported" | "encryption_required" | "expired_token" | "invalid_application_authorization" | "invalid_callback" | "invalid_device_authorization_response" | "invalid_token_response" | "scope_denied" | "reconnect_required" | "token_exchange_failed" | "transport_protocol_incompatible";
22
- export declare const AUTHORIZATION_PROBLEM_CODES: readonly ["discovery_failed", "invalid_operation_response", "invalid_application_manifest", "invalid_request", "manifest_load_failed", "rate_limited", "temporarily_unavailable", "timeout", "access_denied", "application_identity_unavailable", "approval_window_blocked", "authorization_binding_incompatible", "authorization_cancelled", "authorization_replayed", "browser_required", "capability_contract_incompatible", "collection_access_denied", "collection_configuration_invalid", "collection_contracts_missing", "collection_incompatible", "collection_invalid", "collection_kind_unsupported", "collection_mismatch", "collection_not_found", "collection_type_registry_invalid", "collection_version_unsupported", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "device_authorization_failed", "durable_mutation_unsupported", "encryption_required", "expired_token", "invalid_application_authorization", "invalid_callback", "invalid_device_authorization_response", "invalid_token_response", "scope_denied", "reconnect_required", "token_exchange_failed", "transport_protocol_incompatible"];
21
+ export type AuthorizationProblemCode = RegistrationProblemCode | "access_denied" | "application_declaration_mismatch" | "application_identity_unavailable" | "approval_window_blocked" | "authorization_binding_incompatible" | "authorization_cancelled" | "authorization_replayed" | "browser_required" | "capability_contract_incompatible" | "collection_access_denied" | "collection_configuration_invalid" | "collection_contracts_missing" | "collection_incompatible" | "collection_invalid" | "collection_kind_unsupported" | "collection_mismatch" | "collection_not_found" | "collection_type_registry_invalid" | "collection_version_unsupported" | "connector_identity_changed" | "connector_offline" | "connector_upgrade_required" | "device_authorization_failed" | "durable_mutation_unsupported" | "encryption_required" | "expired_token" | "invalid_application_authorization" | "invalid_callback" | "invalid_device_authorization_response" | "invalid_token_response" | "scope_denied" | "reconnect_required" | "token_exchange_failed" | "transport_protocol_incompatible";
22
+ export declare const AUTHORIZATION_PROBLEM_CODES: readonly ["discovery_failed", "invalid_operation_response", "invalid_application_manifest", "invalid_request", "manifest_load_failed", "rate_limited", "temporarily_unavailable", "timeout", "access_denied", "application_declaration_mismatch", "application_identity_unavailable", "approval_window_blocked", "authorization_binding_incompatible", "authorization_cancelled", "authorization_replayed", "browser_required", "capability_contract_incompatible", "collection_access_denied", "collection_configuration_invalid", "collection_contracts_missing", "collection_incompatible", "collection_invalid", "collection_kind_unsupported", "collection_mismatch", "collection_not_found", "collection_type_registry_invalid", "collection_version_unsupported", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "device_authorization_failed", "durable_mutation_unsupported", "encryption_required", "expired_token", "invalid_application_authorization", "invalid_callback", "invalid_device_authorization_response", "invalid_token_response", "scope_denied", "reconnect_required", "token_exchange_failed", "transport_protocol_incompatible"];
23
23
  export type NotificationProblemCode = RegistrationProblemCode | "authorization_expired" | "invalid_operation_response" | "invalid_push_subscription" | "managed_fcm_not_declared" | "not_authorized" | "notification_criterion_not_declared" | "notification_reauthorization_required" | "notification_registration_failed" | "notification_unregistration_failed" | "notifications_not_declared" | "notifications_unavailable";
24
24
  export declare const NOTIFICATION_PROBLEM_CODES: readonly ["discovery_failed", "invalid_operation_response", "invalid_application_manifest", "invalid_request", "manifest_load_failed", "rate_limited", "temporarily_unavailable", "timeout", "authorization_expired", "invalid_operation_response", "invalid_push_subscription", "managed_fcm_not_declared", "not_authorized", "notification_criterion_not_declared", "notification_reauthorization_required", "notification_registration_failed", "notification_unregistration_failed", "notifications_not_declared", "notifications_unavailable"];
25
25
  export type DirectAccessProblemCode = "connector_offline" | "connector_upgrade_required" | "invalid_operation_response" | "not_authorized" | "operation_failed" | "temporarily_unavailable" | "timeout";
26
26
  export declare const DIRECT_ACCESS_PROBLEM_CODES: readonly ["connector_offline", "connector_upgrade_required", "invalid_operation_response", "not_authorized", "operation_failed", "temporarily_unavailable", "timeout"];
27
- export type SessionProblemCode = AuthorizationProblemCode | "collection_not_ready" | "collection_not_selected" | "unknown_collection";
28
- export declare const SESSION_PROBLEM_CODES: readonly ["discovery_failed", "invalid_operation_response", "invalid_application_manifest", "invalid_request", "manifest_load_failed", "rate_limited", "temporarily_unavailable", "timeout", "access_denied", "application_identity_unavailable", "approval_window_blocked", "authorization_binding_incompatible", "authorization_cancelled", "authorization_replayed", "browser_required", "capability_contract_incompatible", "collection_access_denied", "collection_configuration_invalid", "collection_contracts_missing", "collection_incompatible", "collection_invalid", "collection_kind_unsupported", "collection_mismatch", "collection_not_found", "collection_type_registry_invalid", "collection_version_unsupported", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "device_authorization_failed", "durable_mutation_unsupported", "encryption_required", "expired_token", "invalid_application_authorization", "invalid_callback", "invalid_device_authorization_response", "invalid_token_response", "scope_denied", "reconnect_required", "token_exchange_failed", "transport_protocol_incompatible", "collection_not_ready", "collection_not_selected", "unknown_collection"];
27
+ export type SessionProblemCode = AuthorizationProblemCode | "collection_not_ready" | "collection_not_selected" | "operation_cancelled" | "session_destroyed" | "session_not_started" | "session_starting" | "unknown_collection";
28
+ export declare const SESSION_PROBLEM_CODES: readonly ["discovery_failed", "invalid_operation_response", "invalid_application_manifest", "invalid_request", "manifest_load_failed", "rate_limited", "temporarily_unavailable", "timeout", "access_denied", "application_declaration_mismatch", "application_identity_unavailable", "approval_window_blocked", "authorization_binding_incompatible", "authorization_cancelled", "authorization_replayed", "browser_required", "capability_contract_incompatible", "collection_access_denied", "collection_configuration_invalid", "collection_contracts_missing", "collection_incompatible", "collection_invalid", "collection_kind_unsupported", "collection_mismatch", "collection_not_found", "collection_type_registry_invalid", "collection_version_unsupported", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "device_authorization_failed", "durable_mutation_unsupported", "encryption_required", "expired_token", "invalid_application_authorization", "invalid_callback", "invalid_device_authorization_response", "invalid_token_response", "scope_denied", "reconnect_required", "token_exchange_failed", "transport_protocol_incompatible", "collection_not_ready", "collection_not_selected", "operation_cancelled", "session_destroyed", "session_not_started", "session_starting", "unknown_collection"];
29
29
  export type CollectionSetupProblemCode = "collection_configuration_invalid" | "collection_invalid" | "collection_type_registry_invalid" | "collection_version_unsupported";
30
30
  export type CollectionDescriptionProblemCode = CommonOperationProblemCode | CollectionSetupProblemCode;
31
31
  export type CollectionReadProblemCode = CollectionDescriptionProblemCode | "invalid_path" | "operation_invalid";
@@ -34,12 +34,12 @@ export type CollectionMutationProblemCode = CollectionReadProblemCode | "concurr
34
34
  export type CollectionChangesProblemCode = CollectionDescriptionProblemCode | "change_cursor_reset";
35
35
  export type CollectionTypeProblemCode = CollectionMutationProblemCode | "type_pack_provision_failed";
36
36
  export declare const COLLECTION_SETUP_PROBLEM_CODES: readonly ["collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported"];
37
- export declare const COLLECTION_DESCRIPTION_PROBLEM_CODES: readonly ["access_denied", "access_paused", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported"];
38
- export declare const COLLECTION_READ_PROBLEM_CODES: readonly ["access_denied", "access_paused", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported", "invalid_path", "operation_invalid"];
39
- export declare const COLLECTION_QUERY_PROBLEM_CODES: readonly ["access_denied", "access_paused", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported", "invalid_path", "operation_invalid", "cursor_capacity_exhausted", "generation_expired", "invalid_read_cursor", "query_snapshot_changed", "sandbox_unsupported"];
40
- export declare const COLLECTION_MUTATION_PROBLEM_CODES: readonly ["access_denied", "access_paused", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported", "invalid_path", "operation_invalid", "concurrent_modification", "invalid_preflight", "pending_mutation_unresolved"];
41
- export declare const COLLECTION_CHANGES_PROBLEM_CODES: readonly ["access_denied", "access_paused", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported", "change_cursor_reset"];
42
- export declare const COLLECTION_TYPE_PROBLEM_CODES: readonly ["access_denied", "access_paused", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported", "invalid_path", "operation_invalid", "concurrent_modification", "invalid_preflight", "pending_mutation_unresolved", "type_pack_provision_failed"];
37
+ export declare const COLLECTION_DESCRIPTION_PROBLEM_CODES: readonly ["access_denied", "access_paused", "application_declaration_mismatch", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported"];
38
+ export declare const COLLECTION_READ_PROBLEM_CODES: readonly ["access_denied", "access_paused", "application_declaration_mismatch", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported", "invalid_path", "operation_invalid"];
39
+ export declare const COLLECTION_QUERY_PROBLEM_CODES: readonly ["access_denied", "access_paused", "application_declaration_mismatch", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported", "invalid_path", "operation_invalid", "cursor_capacity_exhausted", "generation_expired", "invalid_read_cursor", "query_snapshot_changed", "sandbox_unsupported"];
40
+ export declare const COLLECTION_MUTATION_PROBLEM_CODES: readonly ["access_denied", "access_paused", "application_declaration_mismatch", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported", "invalid_path", "operation_invalid", "concurrent_modification", "invalid_preflight", "pending_mutation_unresolved"];
41
+ export declare const COLLECTION_CHANGES_PROBLEM_CODES: readonly ["access_denied", "access_paused", "application_declaration_mismatch", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported", "change_cursor_reset"];
42
+ export declare const COLLECTION_TYPE_PROBLEM_CODES: readonly ["access_denied", "access_paused", "application_declaration_mismatch", "authorization_binding_incompatible", "authorization_expired", "capability_contract_incompatible", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "durable_mutation_unsupported", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "mutation_recovery_expired", "mutation_request_conflict", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "transport_protocol_incompatible", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported", "invalid_path", "operation_invalid", "concurrent_modification", "invalid_preflight", "pending_mutation_unresolved", "type_pack_provision_failed"];
43
43
  export declare function connectSuccess<Value>(value: Value, diagnostics?: MdbaseDiagnostic[]): ConnectSuccess<Value>;
44
44
  export declare function connectFailure<Code extends ConnectProblemCode>(problem: ConnectProblem<Code>): ConnectFailure<Code>;
45
45
  export declare function captureConnectOutcome<Value, Code extends ConnectProblemCode>(operation: () => Promise<Value>, allowedCodes: readonly Code[]): Promise<ConnectOutcome<Value, Code>>;
package/dist/outcomes.js CHANGED
@@ -3,6 +3,7 @@ import { MdbaseConnectError, unknownConnectProblem } from "./errors.js";
3
3
  export const COMMON_OPERATION_PROBLEM_CODES = [
4
4
  "access_denied",
5
5
  "access_paused",
6
+ "application_declaration_mismatch",
6
7
  "authorization_binding_incompatible",
7
8
  "authorization_expired",
8
9
  "capability_contract_incompatible",
@@ -48,6 +49,7 @@ export const REGISTRATION_PROBLEM_CODES = [
48
49
  export const AUTHORIZATION_PROBLEM_CODES = [
49
50
  ...REGISTRATION_PROBLEM_CODES,
50
51
  "access_denied",
52
+ "application_declaration_mismatch",
51
53
  "application_identity_unavailable",
52
54
  "approval_window_blocked",
53
55
  "authorization_binding_incompatible",
@@ -108,6 +110,10 @@ export const SESSION_PROBLEM_CODES = [
108
110
  ...AUTHORIZATION_PROBLEM_CODES,
109
111
  "collection_not_ready",
110
112
  "collection_not_selected",
113
+ "operation_cancelled",
114
+ "session_destroyed",
115
+ "session_not_started",
116
+ "session_starting",
111
117
  "unknown_collection"
112
118
  ];
113
119
  export const COLLECTION_SETUP_PROBLEM_CODES = [
@@ -1,4 +1,5 @@
1
1
  import type { CollectionOperation, OperationTransportProtocolVersion } from "@mdbase-dev/connect-protocol";
2
2
  export declare function pendingRecoveryTransports(storage: Storage, storagePrefix: string, operations: readonly CollectionOperation[], collectionId?: string): OperationTransportProtocolVersion[];
3
3
  export declare function pendingMutationsUseKey(storage: Storage, baseKey: string, keyHandle: string): boolean;
4
+ export declare function pendingMutationKeyHandles(storage: Storage, baseKey: string): Set<string>;
4
5
  export declare function removePendingMutations(storage: Storage, baseKey: string, removeKey: (keyHandle: string) => void): void;