@mdbase-dev/connect 0.1.0-beta.31 → 0.1.0-beta.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +112 -34
  2. package/dist/advanced.d.ts +9 -0
  3. package/dist/advanced.js +6 -0
  4. package/dist/application-session.d.ts +39 -14
  5. package/dist/application-session.js +201 -70
  6. package/dist/async.js +11 -5
  7. package/dist/authority-proof.d.ts +3 -0
  8. package/dist/authority-proof.js +24 -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/browser.d.ts +3 -0
  13. package/dist/browser.js +5 -0
  14. package/dist/capabilities.js +2 -1
  15. package/dist/collection-client.d.ts +36 -33
  16. package/dist/collection-client.js +549 -104
  17. package/dist/connect-authorization-helpers.d.ts +3 -0
  18. package/dist/connect-authorization-helpers.js +13 -0
  19. package/dist/connect-options.d.ts +12 -4
  20. package/dist/connect.d.ts +12 -4
  21. package/dist/connect.js +102 -103
  22. package/dist/connection-file-transport.d.ts +2 -2
  23. package/dist/connection-file-transport.js +4 -4
  24. package/dist/connection-notifications.d.ts +10 -4
  25. package/dist/connection-notifications.js +41 -23
  26. package/dist/connection-transport.d.ts +19 -15
  27. package/dist/connection-transport.js +202 -202
  28. package/dist/connection-types.d.ts +5 -4
  29. package/dist/connection.d.ts +47 -46
  30. package/dist/connection.js +320 -162
  31. package/dist/crypto-entry.d.ts +7 -0
  32. package/dist/crypto-entry.js +4 -0
  33. package/dist/crypto.js +4 -4
  34. package/dist/external-store.d.ts +10 -0
  35. package/dist/external-store.js +10 -0
  36. package/dist/file-transfer-internals.d.ts +23 -0
  37. package/dist/file-transfer-internals.js +224 -0
  38. package/dist/files.d.ts +36 -14
  39. package/dist/files.js +92 -222
  40. package/dist/hosted-file-request.js +3 -3
  41. package/dist/index.d.ts +24 -19
  42. package/dist/index.js +10 -18
  43. package/dist/internal-types.d.ts +9 -1
  44. package/dist/local-file-transport.d.ts +2 -2
  45. package/dist/local-file-transport.js +13 -13
  46. package/dist/mdbase-connect.d.ts +6 -4
  47. package/dist/mdbase-connect.js +9 -8
  48. package/dist/notifications.d.ts +3 -2
  49. package/dist/operation-helpers.d.ts +3 -3
  50. package/dist/operation-helpers.js +17 -34
  51. package/dist/operation-types.d.ts +435 -45
  52. package/dist/outcomes.d.ts +15 -15
  53. package/dist/outcomes.js +13 -1
  54. package/dist/pending-mutation-store.d.ts +13 -0
  55. package/dist/pending-mutation-store.js +64 -0
  56. package/dist/request-budget.d.ts +27 -0
  57. package/dist/request-budget.js +95 -0
  58. package/dist/runtime-utils.d.ts +2 -0
  59. package/dist/runtime-utils.js +19 -0
  60. package/dist/session.d.ts +5 -4
  61. package/dist/session.js +6 -5
  62. package/dist/stored-token.d.ts +10 -0
  63. package/dist/stored-token.js +60 -0
  64. package/dist/token-response.d.ts +11 -0
  65. package/dist/token-response.js +77 -0
  66. package/dist/watch-policy.d.ts +8 -0
  67. package/dist/watch-policy.js +12 -0
  68. package/package.json +12 -2
@@ -1,11 +1,12 @@
1
1
  import { MdbaseCollectionClient } from "./collection-client.js";
2
2
  import { ConnectionNotifications } from "./connection-notifications.js";
3
3
  import { ConnectionTransport } from "./connection-transport.js";
4
- import { MdbaseConnectError } from "./errors.js";
4
+ import { MdbaseConnectError, connectProblem, operationProblem } from "./errors.js";
5
5
  import { MdbaseFileClient } from "./files.js";
6
6
  import { DEFAULT_OPERATIONS } from "./internal-types.js";
7
7
  import { assertDeletePreview, assertRenamePreview, deleteEstimate, isCancellation, renameEstimate, throwIfCancelled, uniqueOperations } from "./operation-helpers.js";
8
- import { AUTHORIZATION_PROBLEM_CODES, COLLECTION_MUTATION_PROBLEM_CODES, DIRECT_ACCESS_PROBLEM_CODES, NOTIFICATION_PROBLEM_CODES, REGISTRATION_PROBLEM_CODES, captureConnectOutcome, connectFailure, connectSuccess } from "./outcomes.js";
8
+ import { AUTHORIZATION_PROBLEM_CODES, COLLECTION_CHANGES_PROBLEM_CODES, COLLECTION_MUTATION_PROBLEM_CODES, DIRECT_ACCESS_PROBLEM_CODES, NOTIFICATION_PROBLEM_CODES, REGISTRATION_PROBLEM_CODES, captureConnectOutcome, connectFailure, connectSuccess } from "./outcomes.js";
9
+ import { withCooperativeRequestBudget, withRequestBudget } from "./request-budget.js";
9
10
  export class MdbaseConnection {
10
11
  internals;
11
12
  collectionId;
@@ -25,6 +26,7 @@ export class MdbaseConnection {
25
26
  loopbackUrl: internals.loopbackUrl,
26
27
  collectionId,
27
28
  internals,
29
+ timeouts: internals.timeouts,
28
30
  onChange: () => this.emitConnection()
29
31
  });
30
32
  this.files = new MdbaseFileClient(() => this.fileCapability, (method, path, input, signal) => this.transport.files.control(method, path, input, signal), {
@@ -32,16 +34,17 @@ export class MdbaseConnection {
32
34
  downloadChunk: (session, chunkIndex, signal) => this.transport.files.downloadChunk(session, chunkIndex, signal)
33
35
  }, {
34
36
  downloadPart: (session, partIndex, expectedLength, signal) => this.transport.files.downloadHostedPart(session, partIndex, expectedLength, signal)
35
- });
37
+ }, internals.timeouts);
36
38
  this.collectionClient = new MdbaseCollectionClient({
37
39
  operation: (operation, input, requestOptions) => this.transport.performOperation(operation, input, requestOptions)
38
- });
40
+ }, internals.timeouts.requestMs);
39
41
  this.notifications = new ConnectionNotifications({
40
42
  serverUrl: internals.serverUrl,
41
43
  storage: internals.storage,
42
- authorizedToken: () => this.transport.authorizedToken(),
43
- register: () => this.internals.register(),
44
- notificationKey: (transport) => internals.notificationKey(collectionId, transport)
44
+ authorizedToken: (signal) => this.transport.authorizedToken({ signal, timeoutMs: null }),
45
+ register: (signal) => this.internals.register({ signal, timeoutMs: null }),
46
+ notificationKey: (transport) => internals.notificationKey(collectionId, transport),
47
+ requestTimeoutMs: internals.timeouts.requestMs
45
48
  });
46
49
  }
47
50
  get displayName() {
@@ -63,8 +66,8 @@ export class MdbaseConnection {
63
66
  get route() {
64
67
  return this.transport.route;
65
68
  }
66
- register() {
67
- return captureConnectOutcome(() => this.internals.register(), REGISTRATION_PROBLEM_CODES);
69
+ register(options) {
70
+ return captureConnectOutcome(() => this.internals.register(options), REGISTRATION_PROBLEM_CODES);
68
71
  }
69
72
  info() {
70
73
  const token = this.transport.currentToken();
@@ -123,12 +126,12 @@ export class MdbaseConnection {
123
126
  notifyStorageChanged() {
124
127
  this.transport.notifyStorageChanged();
125
128
  }
126
- checkDirectAccess() {
127
- return captureConnectOutcome(() => this.transport.checkDirectAccess(), DIRECT_ACCESS_PROBLEM_CODES);
129
+ checkDirectAccess(options) {
130
+ return captureConnectOutcome(() => this.transport.checkDirectAccess(options), DIRECT_ACCESS_PROBLEM_CODES);
128
131
  }
129
132
  /** Call from a user gesture to request browser permission for direct local access. */
130
- requestDirectAccess() {
131
- return captureConnectOutcome(() => this.transport.requestDirectAccess(), DIRECT_ACCESS_PROBLEM_CODES);
133
+ requestDirectAccess(options) {
134
+ return captureConnectOutcome(() => this.transport.requestDirectAccess(options), DIRECT_ACCESS_PROBLEM_CODES);
132
135
  }
133
136
  disableDirectAccess() {
134
137
  this.transport.disableDirectAccess();
@@ -151,21 +154,21 @@ export class MdbaseConnection {
151
154
  collectionId,
152
155
  replicaId,
153
156
  transport: {
154
- openSession: () => this.transport.performOperation("sync", { action: "open_session" }),
155
- snapshot: (snapshotId, page) => this.transport.performOperation("sync", {
157
+ openSession: (options) => this.transport.performOperation("sync", { action: "open_session" }, syncRequestOptions(options, this.internals.timeouts.syncMs)),
158
+ snapshot: (snapshotId, page, options) => this.transport.performOperation("sync", {
156
159
  action: "snapshot",
157
160
  snapshot_id: snapshotId,
158
161
  ...(page ? { page } : {})
159
- }),
160
- changes: (after, limit = 200) => this.transport.performOperation("sync", {
162
+ }, syncRequestOptions(options, this.internals.timeouts.syncMs)),
163
+ changes: (after, limit = 200, options) => this.transport.performOperation("sync", {
161
164
  action: "changes",
162
165
  after,
163
166
  limit
164
- }),
165
- mutate: (mutation) => this.transport.performOperation("sync", {
167
+ }, syncRequestOptions(options, this.internals.timeouts.syncMs)),
168
+ mutate: (mutation, options) => this.transport.performOperation("sync", {
166
169
  action: "mutate",
167
170
  mutation
168
- })
171
+ }, syncRequestOptions(options, this.internals.timeouts.syncMs))
169
172
  }
170
173
  };
171
174
  }
@@ -174,15 +177,15 @@ export class MdbaseConnection {
174
177
  collectionId,
175
178
  replicaId,
176
179
  transport: {
177
- openSession: () => this.transport.performAuthoritySyncRequest(collectionId, replicaId, "POST", "sessions"),
178
- snapshot: (snapshotId, page) => {
180
+ openSession: (options) => this.transport.performAuthoritySyncRequest(collectionId, replicaId, "POST", "sessions", undefined, options),
181
+ snapshot: (snapshotId, page, options) => {
179
182
  const query = new URLSearchParams({ snapshot_id: snapshotId });
180
183
  if (page)
181
184
  query.set("page", page);
182
- return this.transport.performAuthoritySyncRequest(collectionId, replicaId, "GET", `snapshot?${query}`);
185
+ return this.transport.performAuthoritySyncRequest(collectionId, replicaId, "GET", `snapshot?${query}`, undefined, options);
183
186
  },
184
- changes: (after, limit = 200) => this.transport.performAuthoritySyncRequest(collectionId, replicaId, "GET", `changes?${new URLSearchParams({ after: String(after), limit: String(limit) })}`),
185
- mutate: (mutation) => this.transport.performAuthoritySyncRequest(collectionId, replicaId, "POST", "mutations", mutation)
187
+ changes: (after, limit = 200, options) => this.transport.performAuthoritySyncRequest(collectionId, replicaId, "GET", `changes?${new URLSearchParams({ after: String(after), limit: String(limit) })}`, undefined, options),
188
+ mutate: (mutation, options) => this.transport.performAuthoritySyncRequest(collectionId, replicaId, "POST", "mutations", mutation, options)
186
189
  }
187
190
  };
188
191
  }
@@ -198,25 +201,25 @@ export class MdbaseConnection {
198
201
  registerNativeNotifications(options) {
199
202
  return captureConnectOutcome(() => this.notifications.registerNativeNotifications(options), NOTIFICATION_PROBLEM_CODES);
200
203
  }
201
- unregisterNativeNotifications() {
202
- return captureConnectOutcome(() => this.notifications.unregisterNativeNotifications(), NOTIFICATION_PROBLEM_CODES);
204
+ unregisterNativeNotifications(options) {
205
+ return captureConnectOutcome(() => this.notifications.unregisterNativeNotifications(options), NOTIFICATION_PROBLEM_CODES);
203
206
  }
204
- unregisterNotifications(serviceWorker) {
205
- return captureConnectOutcome(() => this.notifications.unregisterNotifications(serviceWorker), NOTIFICATION_PROBLEM_CODES);
207
+ unregisterNotifications(serviceWorker, options) {
208
+ return captureConnectOutcome(() => this.notifications.unregisterNotifications(serviceWorker, options), NOTIFICATION_PROBLEM_CODES);
206
209
  }
207
210
  forget() {
208
211
  const token = this.transport.currentToken();
209
- this.internals.removeToken(this.collectionId, token?.keyHandle, "not_authorized");
212
+ this.internals.removeToken(this.collectionId, token?.keyHandle, "not_authorized", true);
210
213
  this.transport.notifyStorageChanged();
211
214
  }
212
- describe() {
213
- return this.collectionClient.describe();
215
+ describe(options) {
216
+ return this.collectionClient.describe(options);
214
217
  }
215
218
  changes(input = {}, options) {
216
219
  return this.collectionClient.changes(input, options);
217
220
  }
218
- read(input) {
219
- return this.collectionClient.read(input);
221
+ read(input, options) {
222
+ return this.collectionClient.read(input, options);
220
223
  }
221
224
  query(input = {}, options) {
222
225
  return this.collectionClient.query(input, options);
@@ -227,29 +230,29 @@ export class MdbaseConnection {
227
230
  queryAll(input = {}, options = {}) {
228
231
  return this.collectionClient.queryAll(input, options);
229
232
  }
230
- listViews() {
231
- return this.collectionClient.listViews();
233
+ listViews(options) {
234
+ return this.collectionClient.listViews(options);
232
235
  }
233
- executeView(input) {
234
- return this.collectionClient.executeView(input);
236
+ executeView(input, options) {
237
+ return this.collectionClient.executeView(input, options);
235
238
  }
236
- readViewSource(input) {
237
- return this.collectionClient.readViewSource(input);
239
+ readViewSource(input, options) {
240
+ return this.collectionClient.readViewSource(input, options);
238
241
  }
239
- createViewSource(input) {
240
- return this.collectionClient.createViewSource(input);
242
+ createViewSource(input, options) {
243
+ return this.collectionClient.createViewSource(input, options);
241
244
  }
242
- updateViewSource(input) {
243
- return this.collectionClient.updateViewSource(input);
245
+ updateViewSource(input, options) {
246
+ return this.collectionClient.updateViewSource(input, options);
244
247
  }
245
- deleteViewSource(input) {
246
- return this.collectionClient.deleteViewSource(input);
248
+ deleteViewSource(input, options) {
249
+ return this.collectionClient.deleteViewSource(input, options);
247
250
  }
248
- create(input) {
249
- return this.collectionClient.create(input);
251
+ create(input, options) {
252
+ return this.collectionClient.create(input, options);
250
253
  }
251
- update(input) {
252
- return this.collectionClient.update(input);
254
+ update(input, options) {
255
+ return this.collectionClient.update(input, options);
253
256
  }
254
257
  delete(input, options) {
255
258
  return this.collectionClient.delete(input, options);
@@ -264,150 +267,188 @@ export class MdbaseConnection {
264
267
  return this.collectionClient.preflightRename(input, options);
265
268
  }
266
269
  async renameWithProgress(input, options = {}) {
267
- const started = Date.now();
268
- const resumed = this.pendingMutation()?.operation === "rename";
269
- let estimate;
270
- const emit = (state, cancellable, completedUnits = 0) => {
271
- options.onProgress?.({
272
- operation: "rename",
273
- state,
274
- elapsedMs: Date.now() - started,
275
- cancellable,
276
- resumed,
277
- completedUnits,
278
- ...(estimate ? { estimate } : {})
279
- });
280
- };
281
- try {
282
- throwIfCancelled(options.signal);
283
- emit("preflighting", true);
284
- const previewOutcome = options.preflight
285
- ? connectSuccess(options.preflight)
286
- : await this.preflightRename(input, { signal: options.signal });
287
- if (!previewOutcome.ok)
288
- return previewOutcome;
289
- const preview = previewOutcome.value;
270
+ return withCooperativeRequestBudget(options, this.internals.timeouts.requestMs, async (budget) => {
271
+ const started = Date.now();
272
+ const resumed = false;
273
+ let estimate;
274
+ const emit = (state, cancellable, completedUnits = 0) => {
275
+ options.onProgress?.({
276
+ operation: "rename",
277
+ state,
278
+ elapsedMs: Date.now() - started,
279
+ cancellable,
280
+ resumed,
281
+ completedUnits,
282
+ ...(estimate ? { estimate } : {})
283
+ });
284
+ };
290
285
  try {
291
- assertRenamePreview(input, preview);
286
+ throwIfCancelled(budget.signal);
287
+ emit("preflighting", true);
288
+ const previewOutcome = options.preflight
289
+ ? connectSuccess(options.preflight)
290
+ : await this.preflightRename(input, { signal: budget.signal, timeoutMs: null });
291
+ if (!previewOutcome.ok)
292
+ return previewOutcome;
293
+ const preview = previewOutcome.value;
294
+ try {
295
+ assertRenamePreview(input, preview);
296
+ }
297
+ catch (error) {
298
+ if (error instanceof MdbaseConnectError) {
299
+ return connectFailure(error.problem);
300
+ }
301
+ throw error;
302
+ }
303
+ estimate = renameEstimate(input, preview);
304
+ emit("ready", true);
305
+ throwIfCancelled(budget.signal);
306
+ const cancellable = this.transport.hasResumableMutationTransport();
307
+ emit("applying", cancellable);
308
+ const result = await this.rename(input, cancellable
309
+ ? { signal: budget.signal, timeoutMs: null }
310
+ : { timeoutMs: remainingTimeout(budget.remainingMs()) });
311
+ if (result.ok)
312
+ emit("completed", false, estimate.totalUnits);
313
+ return result;
292
314
  }
293
315
  catch (error) {
316
+ if (isCancellation(error, budget.signal))
317
+ emit("cancelled", false);
294
318
  if (error instanceof MdbaseConnectError) {
295
319
  return connectFailure(error.problem);
296
320
  }
297
321
  throw error;
298
322
  }
299
- estimate = renameEstimate(input, preview);
300
- emit("ready", true);
301
- throwIfCancelled(options.signal);
302
- const cancellable = this.transport.hasResumableMutationTransport();
303
- emit("applying", cancellable);
304
- const result = await this.rename(input, cancellable ? { signal: options.signal } : undefined);
305
- if (result.ok)
306
- emit("completed", false, estimate.totalUnits);
307
- return result;
308
- }
309
- catch (error) {
310
- if (isCancellation(error, options.signal))
311
- emit("cancelled", false);
312
- if (error instanceof MdbaseConnectError) {
313
- return connectFailure(error.problem);
314
- }
315
- throw error;
316
- }
323
+ });
317
324
  }
318
325
  async deleteWithProgress(input, options = {}) {
319
- const started = Date.now();
320
- const resumed = this.pendingMutation()?.operation === "delete";
321
- let estimate;
322
- const emit = (state, cancellable, completedUnits = 0) => {
323
- options.onProgress?.({
324
- operation: "delete",
325
- state,
326
- elapsedMs: Date.now() - started,
327
- cancellable,
328
- resumed,
329
- completedUnits,
330
- ...(estimate ? { estimate } : {})
331
- });
332
- };
333
- try {
334
- throwIfCancelled(options.signal);
335
- emit("preflighting", true);
336
- const previewOutcome = options.preflight
337
- ? connectSuccess(options.preflight)
338
- : await this.preflightDelete(input, { signal: options.signal });
339
- if (!previewOutcome.ok)
340
- return previewOutcome;
341
- const preview = previewOutcome.value;
326
+ return withCooperativeRequestBudget(options, this.internals.timeouts.requestMs, async (budget) => {
327
+ const started = Date.now();
328
+ const resumed = false;
329
+ let estimate;
330
+ const emit = (state, cancellable, completedUnits = 0) => {
331
+ options.onProgress?.({
332
+ operation: "delete",
333
+ state,
334
+ elapsedMs: Date.now() - started,
335
+ cancellable,
336
+ resumed,
337
+ completedUnits,
338
+ ...(estimate ? { estimate } : {})
339
+ });
340
+ };
342
341
  try {
343
- assertDeletePreview(input, preview);
342
+ throwIfCancelled(budget.signal);
343
+ emit("preflighting", true);
344
+ const previewOutcome = options.preflight
345
+ ? connectSuccess(options.preflight)
346
+ : await this.preflightDelete(input, { signal: budget.signal, timeoutMs: null });
347
+ if (!previewOutcome.ok)
348
+ return previewOutcome;
349
+ const preview = previewOutcome.value;
350
+ try {
351
+ assertDeletePreview(input, preview);
352
+ }
353
+ catch (error) {
354
+ if (error instanceof MdbaseConnectError) {
355
+ return connectFailure(error.problem);
356
+ }
357
+ throw error;
358
+ }
359
+ estimate = deleteEstimate(preview);
360
+ emit("ready", true);
361
+ throwIfCancelled(budget.signal);
362
+ const cancellable = this.transport.hasResumableMutationTransport();
363
+ emit("applying", cancellable);
364
+ const result = await this.delete(input, cancellable
365
+ ? { signal: budget.signal, timeoutMs: null }
366
+ : { timeoutMs: remainingTimeout(budget.remainingMs()) });
367
+ if (result.ok)
368
+ emit("completed", false, estimate.totalUnits);
369
+ return result;
344
370
  }
345
371
  catch (error) {
372
+ if (isCancellation(error, budget.signal))
373
+ emit("cancelled", false);
346
374
  if (error instanceof MdbaseConnectError) {
347
375
  return connectFailure(error.problem);
348
376
  }
349
377
  throw error;
350
378
  }
351
- estimate = deleteEstimate(preview);
352
- emit("ready", true);
353
- throwIfCancelled(options.signal);
354
- const cancellable = this.transport.hasResumableMutationTransport();
355
- emit("applying", cancellable);
356
- const result = await this.delete(input, cancellable ? { signal: options.signal } : undefined);
357
- if (result.ok)
358
- emit("completed", false, estimate.totalUnits);
359
- return result;
360
- }
361
- catch (error) {
362
- if (isCancellation(error, options.signal))
363
- emit("cancelled", false);
364
- if (error instanceof MdbaseConnectError) {
365
- return connectFailure(error.problem);
366
- }
367
- throw error;
368
- }
379
+ });
380
+ }
381
+ pendingMutations() {
382
+ return this.transport.pendingMutations().map((summary) => ({
383
+ ...summary,
384
+ recover: (options) => captureConnectOutcome(() => this.recoverPendingMutation(summary.requestId, options), COLLECTION_MUTATION_PROBLEM_CODES)
385
+ }));
386
+ }
387
+ pendingMutation(requestId) {
388
+ const summary = this.transport.pendingMutation(requestId);
389
+ if (!summary)
390
+ return null;
391
+ return {
392
+ ...summary,
393
+ recover: (options) => captureConnectOutcome(() => this.recoverPendingMutation(requestId, options), COLLECTION_MUTATION_PROBLEM_CODES)
394
+ };
369
395
  }
370
- pendingMutation() {
371
- return this.transport.pendingMutation();
396
+ async recoverPendingMutation(requestId, options) {
397
+ const result = await this.transport.recoverPendingMutation(requestId, options);
398
+ if (result && typeof result === "object" && "valid" in result) {
399
+ const envelope = result;
400
+ if (!envelope.valid)
401
+ throw new MdbaseConnectError(operationProblem(envelope));
402
+ return envelope.result;
403
+ }
404
+ return result;
372
405
  }
373
- resumePendingMutation(input, options) {
374
- return captureConnectOutcome(() => this.transport.resumePendingMutation(input, options), COLLECTION_MUTATION_PROBLEM_CODES);
406
+ validate(input = {}, options) {
407
+ return this.collectionClient.validate(input, options);
375
408
  }
376
- validate(input = {}) {
377
- return this.collectionClient.validate(input);
409
+ readType(input, options) {
410
+ return this.collectionClient.readType(input, options);
378
411
  }
379
- readType(input) {
380
- return this.collectionClient.readType(input);
412
+ createType(input, options) {
413
+ return this.collectionClient.createType(input, options);
381
414
  }
382
- createType(input) {
383
- return this.collectionClient.createType(input);
415
+ updateType(input, options) {
416
+ return this.collectionClient.updateType(input, options);
384
417
  }
385
- updateType(input) {
386
- return this.collectionClient.updateType(input);
418
+ assessTypePack(input, options) {
419
+ return this.collectionClient.assessTypePack(input, options);
387
420
  }
388
- assessTypePack(input) {
389
- return this.collectionClient.assessTypePack(input);
421
+ applyTypePack(input, options) {
422
+ return this.collectionClient.applyTypePack(input, options);
390
423
  }
391
- applyTypePack(input) {
392
- return this.collectionClient.applyTypePack(input);
424
+ assessCollectionSetup(input, options) {
425
+ return this.collectionClient.assessCollectionSetup(input, options);
393
426
  }
394
- listTimers(namespace) {
395
- return this.collectionClient.listTimers(namespace);
427
+ applyCollectionSetup(input, options) {
428
+ return this.collectionClient.applyCollectionSetup(input, options);
396
429
  }
397
- putTimer(input) {
398
- return this.collectionClient.putTimer(input);
430
+ listTimers(namespace, options) {
431
+ return this.collectionClient.listTimers(namespace, options);
399
432
  }
400
- cancelTimer(input) {
401
- return this.collectionClient.cancelTimer(input);
433
+ putTimer(input, options) {
434
+ return this.collectionClient.putTimer(input, options);
402
435
  }
403
- reconcileTimers(input) {
404
- return this.collectionClient.reconcileTimers(input);
436
+ cancelTimer(input, options) {
437
+ return this.collectionClient.cancelTimer(input, options);
405
438
  }
406
- watch(options = {}) {
407
- return this.collectionClient.watch(options);
439
+ reconcileTimers(input, options) {
440
+ return this.collectionClient.reconcileTimers(input, options);
408
441
  }
409
- operation(operation, input, options) {
410
- return this.collectionClient.operation(operation, input, options);
442
+ watch(input = {}, options = {}) {
443
+ return captureConnectOutcome(() => withRequestBudget(options, this.internals.timeouts.watchStartMs, async (budget) => {
444
+ const initial = await this.collectionClient.changes(input.cursor === undefined ? {} : { after: input.cursor, limit: 200 }, { signal: budget.signal, timeoutMs: null });
445
+ if (!initial.ok)
446
+ throw new MdbaseConnectError(initial.problem);
447
+ if (initial.value.reset) {
448
+ throw new MdbaseConnectError(connectProblem("change_cursor_reset", "The collection change cursor expired. Refresh collection state before subscribing again."));
449
+ }
450
+ return new CollectionWatchSubscription(this.collectionClient, initial.value.cursor, input, input.cursor === undefined ? [] : initial.value.events, this.internals.timeouts.watchStartMs);
451
+ }), COLLECTION_CHANGES_PROBLEM_CODES);
411
452
  }
412
453
  emitConnection() {
413
454
  const connection = this.info();
@@ -415,3 +456,120 @@ export class MdbaseConnection {
415
456
  listener(connection);
416
457
  }
417
458
  }
459
+ class CollectionWatchSubscription {
460
+ client;
461
+ input;
462
+ watchStartTimeoutMs;
463
+ changes = new Set();
464
+ statuses = new Set();
465
+ problems = new Set();
466
+ controller = new AbortController();
467
+ removeLifetimeAbort;
468
+ currentStatus;
469
+ currentProblem = null;
470
+ pendingChanges;
471
+ constructor(client, cursor, input, pendingChanges, watchStartTimeoutMs) {
472
+ this.client = client;
473
+ this.input = input;
474
+ this.watchStartTimeoutMs = watchStartTimeoutMs;
475
+ this.pendingChanges = [...pendingChanges];
476
+ this.currentStatus = { state: "connected", cursor, recovered: false };
477
+ const lifetimeSignal = input.lifetimeSignal;
478
+ if (lifetimeSignal?.aborted)
479
+ this.close();
480
+ else if (lifetimeSignal) {
481
+ const close = () => this.close();
482
+ lifetimeSignal.addEventListener("abort", close, { once: true });
483
+ this.removeLifetimeAbort = () => lifetimeSignal.removeEventListener("abort", close);
484
+ }
485
+ if (!this.controller.signal.aborted)
486
+ void this.run(cursor);
487
+ }
488
+ get status() {
489
+ return this.currentStatus;
490
+ }
491
+ get problem() {
492
+ return this.currentProblem;
493
+ }
494
+ subscribe(listener, onStatus, onProblem) {
495
+ this.changes.add(listener);
496
+ if (onStatus) {
497
+ this.statuses.add(onStatus);
498
+ onStatus(this.currentStatus);
499
+ }
500
+ if (onProblem) {
501
+ this.problems.add(onProblem);
502
+ if (this.currentProblem)
503
+ onProblem(this.currentProblem);
504
+ }
505
+ for (const change of this.pendingChanges)
506
+ listener(change);
507
+ this.pendingChanges = [];
508
+ return () => {
509
+ this.changes.delete(listener);
510
+ if (onStatus)
511
+ this.statuses.delete(onStatus);
512
+ if (onProblem)
513
+ this.problems.delete(onProblem);
514
+ };
515
+ }
516
+ close() {
517
+ if (this.controller.signal.aborted)
518
+ return;
519
+ this.controller.abort();
520
+ this.removeLifetimeAbort?.();
521
+ this.removeLifetimeAbort = undefined;
522
+ const cursor = "cursor" in this.currentStatus ? this.currentStatus.cursor : undefined;
523
+ this.publishStatus({ state: "closed", ...(cursor === undefined ? {} : { cursor }) });
524
+ }
525
+ async run(cursor) {
526
+ let firstStatus = true;
527
+ const iterator = this.client.watch({
528
+ cursor,
529
+ pollIntervalMs: this.input.pollIntervalMs,
530
+ retry: this.input.retry,
531
+ signal: this.controller.signal,
532
+ timeoutMs: this.watchStartTimeoutMs,
533
+ onStatus: (status) => {
534
+ if (firstStatus && status.state === "connecting") {
535
+ firstStatus = false;
536
+ return;
537
+ }
538
+ firstStatus = false;
539
+ this.publishStatus(status);
540
+ }
541
+ });
542
+ try {
543
+ for await (const outcome of iterator) {
544
+ if (this.controller.signal.aborted)
545
+ return;
546
+ if (!outcome.ok) {
547
+ this.currentProblem = outcome.problem;
548
+ for (const listener of this.problems)
549
+ listener(outcome.problem);
550
+ return;
551
+ }
552
+ for (const listener of this.changes)
553
+ listener(outcome.value);
554
+ }
555
+ }
556
+ finally {
557
+ if (!this.controller.signal.aborted)
558
+ this.close();
559
+ }
560
+ }
561
+ publishStatus(status) {
562
+ this.currentStatus = status;
563
+ for (const listener of this.statuses)
564
+ listener(status);
565
+ }
566
+ }
567
+ function syncRequestOptions(options, defaultTimeoutMs) {
568
+ return {
569
+ ...options,
570
+ timeoutMs: options?.timeoutMs === undefined ? defaultTimeoutMs : options.timeoutMs
571
+ };
572
+ }
573
+ function remainingTimeout(remainingMs) {
574
+ return remainingMs === null ? null : Math.max(1, Math.ceil(remainingMs));
575
+ }
@@ -0,0 +1,7 @@
1
+ /** Opt-in cryptographic storage and transport primitives. */
2
+ export { IndexedDbGrantKeyStore, MemoryGrantKeyStore, RelayCryptoError, signAuthorityRequest, authorityProofMessage, encryptRelayRequest, decryptRelayResponse, deriveP256SharedSecret, validateGrantEncryption } from "./crypto.js";
3
+ export type { GrantKeyRecord, GrantKeyStore, RelayBinding, AuthorityProofInput } from "./crypto.js";
4
+ export { FileTransferCryptoError, GrantFileTransferCipher, deriveFileTransferKey } from "./file-crypto.js";
5
+ export type { FileTransferBinding } from "./file-crypto.js";
6
+ export { IndexedDbApplicationIdentityStore, MemoryApplicationIdentityStore, ApplicationIdentityStoreError, applicationInstallationId, signApplicationAuthorization, applicationIdentity } from "./application-identity.js";
7
+ export type { ApplicationIdentity, ApplicationIdentityStore } from "./application-identity.js";
@@ -0,0 +1,4 @@
1
+ /** Opt-in cryptographic storage and transport primitives. */
2
+ export { IndexedDbGrantKeyStore, MemoryGrantKeyStore, RelayCryptoError, signAuthorityRequest, authorityProofMessage, encryptRelayRequest, decryptRelayResponse, deriveP256SharedSecret, validateGrantEncryption } from "./crypto.js";
3
+ export { FileTransferCryptoError, GrantFileTransferCipher, deriveFileTransferKey } from "./file-crypto.js";
4
+ export { IndexedDbApplicationIdentityStore, MemoryApplicationIdentityStore, ApplicationIdentityStoreError, applicationInstallationId, signApplicationAuthorization, applicationIdentity } from "./application-identity.js";