@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
package/dist/files.js CHANGED
@@ -2,48 +2,55 @@ import { FILE_PROTOCOL_VERSION } from "@mdbase-dev/connect-protocol";
2
2
  import { MdbaseConnectError, connectError } from "./errors.js";
3
3
  import { IncrementalSha256 } from "./file-sha256.js";
4
4
  import { BinaryPartReader } from "./file-stream-source.js";
5
- const HASH_CHUNK_BYTES = 1024 * 1024;
6
- const DEFAULT_CONCURRENCY = 4;
7
- const MAX_CONCURRENCY = 8;
8
- const MAX_OBJECT_ATTEMPTS = 3;
9
- const MAX_BUFFERED_DOWNLOAD_BYTES = 64 * 1024 * 1024;
10
- const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
11
- const SHA256_DIGEST = /^sha256:[0-9a-f]{64}$/u;
5
+ import { MAX_BUFFERED_DOWNLOAD_BYTES, MAX_OBJECT_ATTEMPTS, SHA256_DIGEST, UUID, browserObjectHeaders, chunkLength, hashBlob, mapConcurrent, normalizeFileError, requirePreparedPart, requireTransferSession, requireTransferStatus, retryChunk, sourceBlob, throwIfAborted, uploadBodyBytes, uploadBodyLength, validConcurrency, validPageSize } from "./file-transfer-internals.js";
6
+ import { createRequestBudget, resolveConnectTimeouts, withCooperativeRequestBudget } from "./request-budget.js";
12
7
  /** Ergonomic facade over resumable, authority-specific file delivery. */
13
8
  export class MdbaseFileClient {
14
9
  capability;
15
10
  request;
16
11
  framed;
17
12
  hosted;
18
- constructor(capability, request, framed, hosted) {
13
+ timeouts;
14
+ constructor(capability, request, framed, hosted, timeouts = resolveConnectTimeouts()) {
19
15
  this.capability = capability;
20
16
  this.request = request;
21
17
  this.framed = framed;
22
18
  this.hosted = hosted;
19
+ this.timeouts = timeouts;
23
20
  }
24
21
  async *list(options = {}) {
25
- this.requireAction("list");
26
- let after;
27
- do {
28
- throwIfAborted(options.signal);
29
- const query = new URLSearchParams({
30
- protocol_version: String(FILE_PROTOCOL_VERSION),
31
- ...(options.folder ? { folder: options.folder } : {}),
32
- ...(after ? { after } : {}),
33
- ...(options.pageSize ? { limit: String(validPageSize(options.pageSize)) } : {})
34
- });
35
- const page = await this.request("GET", `?${query.toString()}`, undefined, options.signal);
36
- if (page.protocol_version !== 1 || page.type !== "files_page" || !Array.isArray(page.files)) {
37
- throw connectError("invalid_operation_response", "The authority returned an invalid file page.");
38
- }
39
- for (const file of page.files) {
40
- throwIfAborted(options.signal);
41
- yield file;
42
- }
43
- after = page.next;
44
- } while (after);
22
+ const budget = createRequestBudget(options, this.timeouts.requestMs);
23
+ const signal = budget.signal;
24
+ try {
25
+ this.requireAction("list");
26
+ let after;
27
+ do {
28
+ throwIfAborted(signal);
29
+ const query = new URLSearchParams({
30
+ protocol_version: String(FILE_PROTOCOL_VERSION),
31
+ ...(options.folder ? { folder: options.folder } : {}),
32
+ ...(after ? { after } : {}),
33
+ ...(options.pageSize ? { limit: String(validPageSize(options.pageSize)) } : {})
34
+ });
35
+ const page = await this.request("GET", `?${query.toString()}`, undefined, signal);
36
+ if (page.protocol_version !== 1 || page.type !== "files_page" || !Array.isArray(page.files)) {
37
+ throw connectError("invalid_operation_response", "The authority returned an invalid file page.");
38
+ }
39
+ for (const file of page.files) {
40
+ throwIfAborted(signal);
41
+ yield clientFileDescriptor(file);
42
+ }
43
+ after = page.next;
44
+ } while (after);
45
+ }
46
+ finally {
47
+ budget.dispose();
48
+ }
45
49
  }
46
50
  async upload(path, source, options = {}) {
51
+ return withCooperativeRequestBudget(options, this.timeouts.uploadMs, (budget) => this.uploadWithinBudget(path, source, { ...options, signal: budget.signal, timeoutMs: null }));
52
+ }
53
+ async uploadWithinBudget(path, source, options) {
47
54
  this.requireAction(options.ifRevision ? "replace" : "add");
48
55
  const blob = sourceBlob(source, options.mediaType);
49
56
  const concurrency = validConcurrency(options.concurrency);
@@ -63,6 +70,13 @@ export class MdbaseFileClient {
63
70
  * Reuse `transferId` with a newly opened source after an ambiguous failure.
64
71
  */
65
72
  async uploadStream(path, source, options = {}) {
73
+ return withCooperativeRequestBudget(options, this.timeouts.uploadMs, (budget) => this.uploadStreamWithinBudget(path, source, {
74
+ ...options,
75
+ signal: budget.signal,
76
+ timeoutMs: null
77
+ }));
78
+ }
79
+ async uploadStreamWithinBudget(path, source, options) {
66
80
  this.requireAction(options.ifRevision ? "replace" : "add");
67
81
  if (!Number.isSafeInteger(source.size) || source.size < 0) {
68
82
  throw connectError("invalid_request", "Streamed file size must be a non-negative safe integer.");
@@ -116,16 +130,21 @@ export class MdbaseFileClient {
116
130
  const partCount = session.strategy.kind === "object_put"
117
131
  ? 1
118
132
  : Math.ceil(size / partSize);
133
+ const status = await this.request("GET", `transfers/${encodeURIComponent(transferId)}`, undefined, options.signal);
134
+ requireTransferStatus(status, session);
135
+ if (status.state !== "open" && status.state !== "committed") {
136
+ throw connectError("invalid_operation_response", `The authority cannot resume a ${status.state} file transfer.`);
137
+ }
119
138
  prepareSource?.(partSize);
120
- if (!framed && session.received.length === partCount) {
121
- const replay = await this.tryReplayUploadCommit(transferId, session.strategy.kind === "object_multipart" ? session.uploaded_parts : undefined, options.signal);
139
+ if (!framed && status.received.length === partCount) {
140
+ const replay = await this.tryReplayUploadCommit(transferId, session.strategy.kind === "object_multipart" ? status.uploaded_parts : undefined, options.signal);
122
141
  if (replay) {
123
142
  committed = true;
124
- return replay.file;
143
+ return clientFileDescriptor(replay.file);
125
144
  }
126
145
  }
127
- const received = new Set(session.received);
128
- const uploadedParts = new Map((session.uploaded_parts ?? []).map((part) => [part.part_number - 1, part]));
146
+ const received = new Set(status.received);
147
+ const uploadedParts = new Map(status.uploaded_parts.map((part) => [part.part_number - 1, part]));
129
148
  let transferredBytes = [...received].reduce((total, index) => total + chunkLength(size, partSize, index), 0);
130
149
  const parts = await mapConcurrent(partCount, concurrency, async (partIndex) => {
131
150
  const offset = partIndex * partSize;
@@ -160,7 +179,7 @@ export class MdbaseFileClient {
160
179
  throw connectError("invalid_operation_response", "The authority returned an invalid file receipt.");
161
180
  }
162
181
  committed = true;
163
- return receipt.file;
182
+ return clientFileDescriptor(receipt.file);
164
183
  }
165
184
  catch (error) {
166
185
  throw normalizeFileError(error);
@@ -188,10 +207,13 @@ export class MdbaseFileClient {
188
207
  const copy = new Uint8Array(chunk.byteLength);
189
208
  copy.set(chunk);
190
209
  return copy.buffer;
191
- }), { type: file.media_type ?? "" });
210
+ }), { type: file.mediaType ?? "" });
192
211
  }
193
212
  /** Download and verify a file with network backpressure and bounded buffering. */
194
213
  async downloadStream(file, options = {}) {
214
+ return withCooperativeRequestBudget(options, this.timeouts.requestMs, (budget) => this.openDownloadStream(file, options, budget.signal));
215
+ }
216
+ async openDownloadStream(file, options, startupSignal) {
195
217
  this.requireAction("read");
196
218
  validConcurrency(options.concurrency);
197
219
  const transferId = crypto.randomUUID();
@@ -201,9 +223,9 @@ export class MdbaseFileClient {
201
223
  protocol_version: FILE_PROTOCOL_VERSION,
202
224
  type: "open_file_download",
203
225
  transfer_id: transferId,
204
- file_id: file.file_id,
226
+ file_id: file.fileId,
205
227
  revision: file.revision
206
- }, options.signal);
228
+ }, startupSignal);
207
229
  requireTransferSession(session, transferId, "download");
208
230
  if (session.total_size !== file.size
209
231
  || session.strategy.kind !== "object_ranges"
@@ -240,7 +262,7 @@ export class MdbaseFileClient {
240
262
  await this.abort(transferId);
241
263
  };
242
264
  const verify = () => {
243
- if (receivedBytes !== file.size || `sha256:${hash.digestHex()}` !== file.content_digest) {
265
+ if (receivedBytes !== file.size || `sha256:${hash.digestHex()}` !== file.contentDigest) {
244
266
  throw connectError("invalid_operation_response", "Downloaded file bytes failed integrity verification.");
245
267
  }
246
268
  };
@@ -333,15 +355,18 @@ export class MdbaseFileClient {
333
355
  return new Uint8Array(await (await this.download(file, options)).arrayBuffer());
334
356
  }
335
357
  async move(file, path, options = {}) {
358
+ return withCooperativeRequestBudget(options, this.timeouts.requestMs, (budget) => this.moveWithinBudget(file, path, { ...options, signal: budget.signal, timeoutMs: null }));
359
+ }
360
+ async moveWithinBudget(file, path, options) {
336
361
  this.requireAction("move");
337
362
  const mutationId = options.mutationId ?? crypto.randomUUID();
338
363
  let receipt;
339
364
  try {
340
- receipt = await this.request("POST", `${encodeURIComponent(file.file_id)}/move`, {
365
+ receipt = await this.request("POST", `${encodeURIComponent(file.fileId)}/move`, {
341
366
  protocol_version: FILE_PROTOCOL_VERSION,
342
367
  type: "move_file",
343
368
  mutation_id: mutationId,
344
- file_id: file.file_id,
369
+ file_id: file.fileId,
345
370
  if_revision: options.ifRevision ?? file.revision,
346
371
  from_path: file.path,
347
372
  path,
@@ -356,22 +381,25 @@ export class MdbaseFileClient {
356
381
  if (receipt.protocol_version !== 1
357
382
  || receipt.type !== "file_moved"
358
383
  || receipt.mutation_id !== mutationId
359
- || receipt.file.file_id !== file.file_id
384
+ || receipt.file.file_id !== file.fileId
360
385
  || receipt.file.path !== path) {
361
386
  throw connectError("invalid_operation_response", "The authority returned an invalid file move receipt.");
362
387
  }
363
- return receipt.file;
388
+ return clientFileDescriptor(receipt.file);
364
389
  }
365
390
  async delete(file, options = {}) {
391
+ return withCooperativeRequestBudget(options, this.timeouts.requestMs, (budget) => this.deleteWithinBudget(file, { ...options, signal: budget.signal, timeoutMs: null }));
392
+ }
393
+ async deleteWithinBudget(file, options) {
366
394
  this.requireAction("delete");
367
395
  const mutationId = options.mutationId ?? crypto.randomUUID();
368
396
  let receipt;
369
397
  try {
370
- receipt = await this.request("POST", `${encodeURIComponent(file.file_id)}/delete`, {
398
+ receipt = await this.request("POST", `${encodeURIComponent(file.fileId)}/delete`, {
371
399
  protocol_version: FILE_PROTOCOL_VERSION,
372
400
  type: "delete_file",
373
401
  mutation_id: mutationId,
374
- file_id: file.file_id,
402
+ file_id: file.fileId,
375
403
  if_revision: options.ifRevision ?? file.revision,
376
404
  path: file.path
377
405
  }, options.signal);
@@ -382,11 +410,18 @@ export class MdbaseFileClient {
382
410
  if (receipt.protocol_version !== 1
383
411
  || receipt.type !== "file_deleted"
384
412
  || receipt.mutation_id !== mutationId
385
- || receipt.file_id !== file.file_id
413
+ || receipt.file_id !== file.fileId
386
414
  || receipt.previous_path !== file.path) {
387
415
  throw connectError("invalid_operation_response", "The authority returned an invalid file delete receipt.");
388
416
  }
389
- return receipt;
417
+ return {
418
+ protocolVersion: receipt.protocol_version,
419
+ type: receipt.type,
420
+ mutationId: receipt.mutation_id,
421
+ fileId: receipt.file_id,
422
+ previousPath: receipt.previous_path,
423
+ revision: receipt.revision
424
+ };
390
425
  }
391
426
  async uploadPart(transferId, partIndex, offset, body, contentLength, requireEtag, signal) {
392
427
  for (let attempt = 1; attempt <= MAX_OBJECT_ATTEMPTS; attempt += 1) {
@@ -465,180 +500,15 @@ export class MdbaseFileClient {
465
500
  await this.request("DELETE", `transfers/${encodeURIComponent(transferId)}`).catch(() => undefined);
466
501
  }
467
502
  }
468
- async function hashBlob(blob, signal, progress) {
469
- const hash = new IncrementalSha256();
470
- for (let offset = 0; offset < blob.size; offset += HASH_CHUNK_BYTES) {
471
- throwIfAborted(signal);
472
- const chunk = new Uint8Array(await blob.slice(offset, offset + HASH_CHUNK_BYTES).arrayBuffer());
473
- hash.update(chunk);
474
- progress(Math.min(blob.size, offset + chunk.byteLength));
475
- }
476
- if (blob.size === 0)
477
- progress(0);
478
- return hash.digestHex();
479
- }
480
- function sourceBlob(source, mediaType) {
481
- if (source instanceof Blob) {
482
- return mediaType && mediaType !== source.type
483
- ? source.slice(0, source.size, mediaType)
484
- : source;
485
- }
486
- if (source instanceof ArrayBuffer)
487
- return new Blob([source], { type: mediaType });
488
- const copy = new Uint8Array(source.byteLength);
489
- copy.set(new Uint8Array(source.buffer, source.byteOffset, source.byteLength));
490
- return new Blob([copy.buffer], { type: mediaType });
491
- }
492
- function uploadBodyLength(body) {
493
- return body instanceof Blob ? body.size : body.byteLength;
494
- }
495
- async function uploadBodyBytes(body) {
496
- return body instanceof Blob
497
- ? new Uint8Array(await body.arrayBuffer())
498
- : body;
499
- }
500
- function validConcurrency(value = DEFAULT_CONCURRENCY) {
501
- if (!Number.isInteger(value) || value < 1 || value > MAX_CONCURRENCY) {
502
- throw connectError("invalid_request", `File concurrency must be between 1 and ${MAX_CONCURRENCY}.`);
503
- }
504
- return value;
505
- }
506
- function validPageSize(value) {
507
- if (!Number.isInteger(value) || value < 1 || value > 1_000) {
508
- throw connectError("invalid_request", "File page size must be between 1 and 1000.");
509
- }
510
- return value;
511
- }
512
- function requireTransferSession(session, transferId, direction) {
513
- if (session.protocol_version !== 1
514
- || session.type !== "file_transfer"
515
- || session.transfer_id !== transferId
516
- || session.direction !== direction
517
- || !Number.isSafeInteger(session.total_size)
518
- || session.total_size < 0
519
- || !Array.isArray(session.received)
520
- || !validTransferStrategy(session)) {
521
- throw connectError("invalid_operation_response", "The authority returned an invalid file transfer session.");
522
- }
523
- const partSize = session.strategy.kind === "framed_chunks"
524
- ? session.strategy.chunk_size
525
- : session.strategy.kind === "object_put"
526
- ? Math.max(1, session.total_size)
527
- : session.strategy.part_size;
528
- const partCount = session.strategy.kind === "object_put"
529
- ? 1
530
- : Math.ceil(session.total_size / partSize);
531
- if (new Set(session.received).size !== session.received.length
532
- || session.received.some((index) => !Number.isSafeInteger(index) || index < 0 || index >= partCount)) {
533
- throw connectError("invalid_operation_response", "The authority returned invalid transfer progress.");
534
- }
535
- const uploadedParts = session.uploaded_parts ?? [];
536
- if (!Array.isArray(uploadedParts)
537
- || uploadedParts.some((part, index) => !Number.isSafeInteger(part?.part_number)
538
- || part.part_number < 1
539
- || part.part_number > partCount
540
- || typeof part.etag !== "string"
541
- || part.etag.length === 0
542
- || part.etag.length > 255
543
- || (index > 0 && uploadedParts[index - 1].part_number >= part.part_number))
544
- || (session.strategy.kind === "object_multipart"
545
- ? uploadedParts.length !== session.received.length
546
- || uploadedParts.some((part, index) => part.part_number - 1 !== session.received[index])
547
- : uploadedParts.length !== 0)) {
548
- throw connectError("invalid_operation_response", "The authority returned invalid uploaded part receipts.");
549
- }
550
- }
551
- function validTransferStrategy(session) {
552
- if (session.strategy.kind === "framed_chunks") {
553
- return session.protection === "grant_aead_v1"
554
- && Number.isSafeInteger(session.strategy.chunk_size)
555
- && session.strategy.chunk_size > 0;
556
- }
557
- if (session.protection !== "transport_tls")
558
- return false;
559
- if (session.strategy.kind === "object_put")
560
- return true;
561
- return Number.isSafeInteger(session.strategy.part_size) && session.strategy.part_size > 0;
562
- }
563
- function chunkLength(totalSize, partSize, index) {
564
- return Math.min(partSize, Math.max(0, totalSize - index * partSize));
565
- }
566
- async function retryChunk(work, signal) {
567
- for (let attempt = 1; attempt <= MAX_OBJECT_ATTEMPTS; attempt += 1) {
568
- throwIfAborted(signal);
569
- try {
570
- return await work();
571
- }
572
- catch (error) {
573
- if (signal?.aborted || attempt === MAX_OBJECT_ATTEMPTS)
574
- throw error;
575
- }
576
- }
577
- throw new Error("Unreachable file chunk retry state.");
578
- }
579
- function requirePreparedPart(part, transferId, partIndex, offset, contentLength, method) {
580
- let url;
581
- try {
582
- url = new URL(part.url);
583
- }
584
- catch {
585
- throw connectError("invalid_operation_response", "The authority returned an invalid object URL.");
586
- }
587
- if (part.protocol_version !== 1
588
- || part.type !== "file_part"
589
- || part.transfer_id !== transferId
590
- || part.part_index !== partIndex
591
- || part.offset !== offset
592
- || part.content_length !== contentLength
593
- || part.method.toUpperCase() !== method
594
- || url.protocol !== "https:"
595
- && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]", "::1"].includes(url.hostname))
596
- || url.username
597
- || url.password) {
598
- throw connectError("invalid_operation_response", "The authority returned an invalid prepared file part.");
599
- }
600
- }
601
- function browserObjectHeaders(headers) {
602
- const result = new Headers();
603
- for (const [name, value] of Object.entries(headers)) {
604
- if (["host", "content-length"].includes(name.toLowerCase()))
605
- continue;
606
- result.set(name, value);
607
- }
608
- return result;
609
- }
610
- async function mapConcurrent(count, concurrency, work) {
611
- const results = new Array(count);
612
- let next = 0;
613
- await Promise.all(Array.from({ length: Math.min(count, concurrency) }, async () => {
614
- while (next < count) {
615
- const index = next;
616
- next += 1;
617
- results[index] = await work(index);
618
- }
619
- }));
620
- return results;
621
- }
622
- function throwIfAborted(signal) {
623
- if (signal?.aborted) {
624
- throw connectError("operation_cancelled", "The file transfer was cancelled.", {
625
- operationOutcome: "not_sent",
626
- cause: signal.reason
627
- });
628
- }
629
- }
630
- function normalizeFileError(error) {
631
- if (error instanceof MdbaseConnectError)
632
- return error;
633
- if (typeof DOMException !== "undefined"
634
- && error instanceof DOMException
635
- && error.name === "AbortError") {
636
- return connectError("operation_cancelled", "The file transfer was cancelled.", {
637
- operationOutcome: "not_sent",
638
- cause: error
639
- });
640
- }
641
- return connectError("temporarily_unavailable", "The file transfer could not be completed.", {
642
- cause: error
643
- });
503
+ function clientFileDescriptor(file) {
504
+ return {
505
+ fileId: file.file_id,
506
+ path: file.path,
507
+ revision: file.revision,
508
+ contentDigest: file.content_digest,
509
+ size: file.size,
510
+ ...(file.media_type ? { mediaType: file.media_type } : {}),
511
+ mediaClass: file.media_class,
512
+ modifiedAt: file.modified_at
513
+ };
644
514
  }
@@ -1,5 +1,5 @@
1
1
  import { connectError } from "./errors.js";
2
- import { apiError } from "./runtime-utils.js";
2
+ import { apiError, decodeJsonResponse } from "./runtime-utils.js";
3
3
  export async function sendHostedFileRequest(token, method, path, input, signal, proofHeaders) {
4
4
  if (!token.authority) {
5
5
  throw connectError("not_remote_authority", "This authorization has no remote authority endpoint.");
@@ -30,7 +30,7 @@ export async function performHostedFileRequest(token, method, path, input, signa
30
30
  }
31
31
  response = await sendHostedFileRequest(active, method, path, input, signal, proofHeaders);
32
32
  }
33
- const body = await response.json().catch(() => ({}));
33
+ const body = await decodeJsonResponse(response, "invalid_operation_response", "The hosted authority returned an invalid file response.");
34
34
  if (!response.ok) {
35
35
  throw apiError(body, "operation_failed", "Collection file request failed.", response.status);
36
36
  }
@@ -47,7 +47,7 @@ export async function performHostedFilePartRequest(token, path, expectedLength,
47
47
  response = await sendHostedFileRequest(active, "GET", path, undefined, signal, proofHeaders);
48
48
  }
49
49
  if (!response.ok) {
50
- const body = await response.json().catch(() => ({}));
50
+ const body = await decodeJsonResponse(response, "invalid_operation_response", "The hosted authority returned an invalid file range error response.");
51
51
  throw apiError(body, "operation_failed", "Collection file range request failed.", response.status);
52
52
  }
53
53
  const contentLength = response.headers.get("content-length");
package/dist/index.d.ts CHANGED
@@ -1,20 +1,25 @@
1
- export { decryptRelayResponse, encryptRelayRequest, IndexedDbGrantKeyStore, MemoryGrantKeyStore, RelayCryptoError, type RelayBinding, type GrantKeyRecord, type GrantKeyStore } from "./crypto.js";
2
- export { FileTransferCryptoError, GrantFileTransferCipher, type FileTransferBinding } from "./file-crypto.js";
3
- export { ApplicationIdentityStoreError, IndexedDbApplicationIdentityStore, MemoryApplicationIdentityStore, applicationInstallationId, authorizationSigningMessage, signApplicationAuthorization, type ApplicationIdentity, type ApplicationIdentityStore } from "./application-identity.js";
4
- export * from "./authorization-types.js";
5
- export * from "./application-session.js";
6
- export * from "./collection-client.js";
7
- export * from "./connect-options.js";
8
- export * from "./mdbase-connect.js";
9
- export * from "./connection.js";
10
- export * from "./connection-types.js";
11
- export * from "./capabilities.js";
12
- export * from "./errors.js";
13
- export * from "./files.js";
14
- export * from "./notifications.js";
15
- export * from "./outcomes.js";
16
- export * from "./operation-types.js";
17
- export * from "./selection.js";
1
+ export type { MdbaseDeviceAuthorization } from "./authorization-types.js";
2
+ export { MdbaseApplicationSession, MdbaseMemoryVerificationStore } from "./application-session.js";
3
+ export type { MdbaseApplicationSessionOptions, MdbaseApplicationVerificationStore, MdbaseDefinitionUpdate, MdbaseCollectionSetupUpdate, MdbaseApplicationSessionSnapshot } from "./application-session.js";
4
+ export type { MdbaseConnectOptions, MdbaseConnectTimeouts, MdbaseFrontmatter } from "./connect-options.js";
5
+ export { MdbaseConnect } from "./mdbase-connect.js";
6
+ export { MdbaseConnection } from "./connection.js";
7
+ export type { MdbaseAuthorizationTarget, MdbaseAuthorizeOptions, MdbaseConnectionAuthorizeOptions, MdbaseConnectEnvironment, MdbaseAuthorizationResult, MdbaseAuthorizationOutcome } from "./connection.js";
8
+ export type { MdbaseConnectionRoute, DirectAccessStatus, MdbaseConnectionInfo, MdbaseAuthorizationCapabilities, MdbaseSyncTransport, MdbaseSyncConnection } from "./connection-types.js";
9
+ export { effectiveCapabilities } from "./capabilities.js";
10
+ export type { MdbaseCapabilityState, MdbaseCapabilityEvidence, MdbaseEffectiveCapability, MdbaseEffectiveCapabilities } from "./capabilities.js";
11
+ export { MdbaseConnectError, isRetryableConnectError } from "./errors.js";
12
+ export type { ConnectErrorContext } from "./errors.js";
13
+ export { MdbaseFileClient } from "./files.js";
14
+ export type { MdbaseFileSource, CollectionFileDescriptor, MdbaseFileDeleteReceipt, MdbaseFileStreamSource, MdbaseFileProgress, MdbaseFileListOptions, MdbaseFileUploadOptions, MdbaseFileStreamUploadOptions, MdbaseFileDownloadOptions, MdbaseFileMoveOptions, MdbaseFileDeleteOptions } from "./files.js";
15
+ export { parseMdbasePushPayload, parseMdbaseNativeNotificationData, showMdbasePushNotification } from "./notifications.js";
16
+ export type { MdbaseNotificationRegistrationOptions, MdbaseNotificationRegistration, MdbaseNativeNotificationRegistrationOptions, MdbaseNativeNotificationRegistration, MdbaseNativeNotificationData, MdbasePushPayload } from "./notifications.js";
17
+ export type { ConnectSuccess, ConnectFailure, ConnectOutcome, CommonOperationProblemCode, RegistrationProblemCode, AuthorizationProblemCode, NotificationProblemCode, DirectAccessProblemCode, SessionProblemCode, CollectionSetupProblemCode, CollectionDescriptionProblemCode, CollectionReadProblemCode, CollectionQueryProblemCode, CollectionMutationProblemCode, CollectionChangesProblemCode, CollectionTypeProblemCode } from "./outcomes.js";
18
+ export type { MdbaseDesiredTimer, MdbaseTimer, MdbaseTimerList, MdbaseTimerReconciliation, ReadInput, DataContractSelector, CollectionFileMetadata, DataContractViewIdentity, QueryRecord, RecordDocument, QueryInput, QueryProjection, QuerySelectionExpression, QueryOrder, QuerySummary, QueryResult, QueryPagesOptions, QueryAllOptions, QueryPage, ConnectRequestOptions, MutationEstimate, MutationProgressState, MutationProgress, MutationProgressOptions, RenameProgressOptions, DeleteProgressOptions, PendingMutationSummary, PendingMutation, CreateInput, UpdateInput, DeleteInput, DeleteResult, DeletePreflightResult, RenameInput, RenameResult, RenamePreflightResult, ReadTypeInput, CreateTypeInput, UpdateTypeInput, ReadViewSourceInput, CreateViewSourceInput, UpdateViewSourceInput, DeleteViewSourceInput, DeleteViewSourceResult, ExecuteViewInput, SavedViewPresentation, SavedViewSource, SavedViewProperty, SavedNamedView, SavedViewDocument, SavedViewList, SavedViewSourceDocument, SavedViewExecution, ContractSetupChoice, TypePackResourceDiff, TypePackReceipt, TypePackAssessment, TypePackApplyResult, ConfigurationContributionValue, ConfigurationRequirement, ConfigurationProvision, ApplicationCollectionSetupRequirements, ApplicationCollectionSetupProvisions, ConfigurationSetupConflict, ConfigurationSetupAssessment, CollectionSetupAssessment, CollectionSetupReceipt, CollectionSetupApplyResult, AssessTypePackInput, ApplyTypePackInput, AssessCollectionSetupInput, ApplyCollectionSetupInput, ChangesInput, WatchOptions, WatchRetryOptions, WatchStatus, WatchInput, MdbaseWatchSubscription, CollectionChange, CollectionChangesPage, CollectionDescription, CollectionContractDescriptor, CollectionContractImplementationDescriptor, CollectionTypeDescriptor } from "./operation-types.js";
19
+ export { externalStore } from "./external-store.js";
20
+ export type { MdbaseExternalStore } from "./external-store.js";
21
+ export { MdbaseBrowserSelection, MdbaseMemorySelection } from "./selection.js";
22
+ export type { MdbaseSelectionHistory, MdbaseApplicationSelection, MdbaseBrowserSelectionOptions } from "./selection.js";
18
23
  export type { MdbaseUnavailableReason } from "./session.js";
19
- export { createPkce } from "./runtime-utils.js";
20
- export type { ApplicationProvisions, ApplicationCapabilityId, ApplicationCapabilityRequirements, ApplicationRequirements, ApplicationNotifications, ApplicationAuthorizationBinding, ApplicationAuthorizationProof, CollectionChange, CollectionChangesPage, CollectionContractDescriptor, CollectionDescription, CollectionFileMetadata, FileAction, CollectionOperation as MdbaseOperation, CollectionTypeDescriptor, CollectionTypeDocument, ConnectOperationOutcome, ConnectProblem, ConnectProblemCategory, ConnectProblemCode, ConnectProblemDetailsByCode, ConnectRecoveryAction, ContractRequirement, DataContractViewIdentity, GrantScope, JsonObject, MdbaseAppManifest, NotificationCriterion, MdbaseDiagnostic, MdbaseOperationEnvelope, KnownConnectProblem, RecordDocument, QueryRecord, SavedNamedView, SavedViewDocument, SavedViewExecution, SavedViewList, SavedViewPresentation, SavedViewProperty, SavedViewSource, SavedViewSourceDocument, ReadViewSourceInput, CreateViewSourceInput, UpdateViewSourceInput, DeleteViewSourceInput, DeleteViewSourceResult, ExecuteViewInput, TypePackManifest, TypePackManifestResource, TypePackApplyResult, TypePackAssessment, TypePackReceipt, AssessTypePackInput, ApplyTypePackInput, TypePackProvision, TypePackResourceDiff, TypePackSourceResource, UnknownConnectProblem } from "@mdbase-dev/connect-protocol";
24
+ export { MDBASE_RECORD_CREATED_CONTRACT, MDBASE_TIMER_FIRED_CONTRACT, operationsForApplicationCapabilities } from "@mdbase-dev/connect-protocol";
25
+ export type { ApplicationProvisions, ApplicationCapabilityId, ApplicationCapabilityRequirements, ApplicationRequirements, ApplicationNotifications, ApplicationAuthorizationBinding, ApplicationAuthorizationProof, FileAction, FileCapability, CollectionOperation as MdbaseOperation, CollectionTypeDocument, ConnectOperationOutcome, ConnectProblem, ConnectProblemCategory, ConnectProblemCode, ConnectProblemDetailsByCode, ConnectRecoveryAction, ContractRequirement, GrantScope, JsonObject, MdbaseAppManifest, NotificationCriterion, MdbaseDiagnostic, MdbaseOperationEnvelope, KnownConnectProblem, TypePackManifest, TypePackManifestResource, TypePackProvision, TypePackSourceResource, UnknownConnectProblem } from "@mdbase-dev/connect-protocol";
package/dist/index.js CHANGED
@@ -1,18 +1,10 @@
1
- export { decryptRelayResponse, encryptRelayRequest, IndexedDbGrantKeyStore, MemoryGrantKeyStore, RelayCryptoError } from "./crypto.js";
2
- export { FileTransferCryptoError, GrantFileTransferCipher } from "./file-crypto.js";
3
- export { ApplicationIdentityStoreError, IndexedDbApplicationIdentityStore, MemoryApplicationIdentityStore, applicationInstallationId, authorizationSigningMessage, signApplicationAuthorization } from "./application-identity.js";
4
- export * from "./authorization-types.js";
5
- export * from "./application-session.js";
6
- export * from "./collection-client.js";
7
- export * from "./connect-options.js";
8
- export * from "./mdbase-connect.js";
9
- export * from "./connection.js";
10
- export * from "./connection-types.js";
11
- export * from "./capabilities.js";
12
- export * from "./errors.js";
13
- export * from "./files.js";
14
- export * from "./notifications.js";
15
- export * from "./outcomes.js";
16
- export * from "./operation-types.js";
17
- export * from "./selection.js";
18
- export { createPkce } from "./runtime-utils.js";
1
+ export { MdbaseApplicationSession, MdbaseMemoryVerificationStore } from "./application-session.js";
2
+ export { MdbaseConnect } from "./mdbase-connect.js";
3
+ export { MdbaseConnection } from "./connection.js";
4
+ export { effectiveCapabilities } from "./capabilities.js";
5
+ export { MdbaseConnectError, isRetryableConnectError } from "./errors.js";
6
+ export { MdbaseFileClient } from "./files.js";
7
+ export { parseMdbasePushPayload, parseMdbaseNativeNotificationData, showMdbasePushNotification } from "./notifications.js";
8
+ export { externalStore } from "./external-store.js";
9
+ export { MdbaseBrowserSelection, MdbaseMemorySelection } from "./selection.js";
10
+ export { MDBASE_RECORD_CREATED_CONTRACT, MDBASE_TIMER_FIRED_CONTRACT, operationsForApplicationCapabilities } from "@mdbase-dev/connect-protocol";
@@ -1,7 +1,8 @@
1
- import type { ApplicationNotifications, ApplicationRequirements, CollectionOperation, EncryptedRelayOperationRequest, GrantEncryption, FileCapability, GrantScope } from "@mdbase-dev/connect-protocol";
1
+ import type { ApplicationNotifications, ApplicationRequirements, CollectionOperation, EncryptedRelayOperationRequest, GrantEncryption, FileCapability, GrantScope, MdbaseOperationRequest, MutationOperationIdentifier } from "@mdbase-dev/connect-protocol";
2
2
  import { encryptRelayRequest } from "./crypto.js";
3
3
  export interface Application {
4
4
  id: string;
5
+ family_identity: string;
5
6
  manifest_digest: string;
6
7
  name: string;
7
8
  distribution?: "web" | "portable";
@@ -58,10 +59,16 @@ export interface PendingMutation {
58
59
  collectionId: string;
59
60
  grantId?: string;
60
61
  keyId?: string;
62
+ keyHandle?: string;
63
+ applicationId?: string;
64
+ encryption?: GrantEncryption;
61
65
  operation: CollectionOperation;
66
+ mutation?: MutationOperationIdentifier;
62
67
  inputFingerprint: string;
63
68
  requestId: string;
64
69
  envelope?: EncryptedRelayOperationRequest;
70
+ /** Exact plaintext protocol request for transports that do not use a grant envelope. */
71
+ request?: MdbaseOperationRequest;
65
72
  createdAt: number;
66
73
  }
67
74
  export interface OperationAttempt {
@@ -70,6 +77,7 @@ export interface OperationAttempt {
70
77
  encryptedRequest?: Awaited<ReturnType<typeof encryptRelayRequest>>;
71
78
  directDeliveryUncertain?: boolean;
72
79
  pendingMutation?: boolean;
80
+ pendingMutationRecord?: PendingMutation;
73
81
  resumingMutation?: boolean;
74
82
  }
75
83
  export declare const DEFAULT_OPERATIONS: CollectionOperation[];
@@ -5,8 +5,8 @@ interface LocalFileTransportOptions {
5
5
  keyStore: GrantKeyStore;
6
6
  serverUrl: string;
7
7
  loopbackUrl: string;
8
- authorizedToken(): Promise<StoredToken | null>;
9
- refreshAuthorization(): Promise<StoredToken>;
8
+ authorizedToken(signal?: AbortSignal): Promise<StoredToken | null>;
9
+ refreshAuthorization(signal?: AbortSignal): Promise<StoredToken>;
10
10
  shouldAttemptDirect(token: StoredToken): Promise<boolean>;
11
11
  onDirectAvailable(): void;
12
12
  onDirectUnavailable(): void;