@mdbase-dev/connect 0.1.0-beta.21 → 0.1.0-beta.22

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 (49) hide show
  1. package/README.md +108 -38
  2. package/dist/authorization-url.d.ts +2 -1
  3. package/dist/authorization-url.js +5 -7
  4. package/dist/browser/integrity.json +1 -1
  5. package/dist/browser/mdbase-connect.min.js +3 -3
  6. package/dist/browser/mdbase-connect.min.js.sha384 +1 -1
  7. package/dist/collection-client.d.ts +34 -31
  8. package/dist/collection-client.js +107 -61
  9. package/dist/connect.d.ts +4 -3
  10. package/dist/connect.js +53 -38
  11. package/dist/connection-file-transport.d.ts +31 -0
  12. package/dist/connection-file-transport.js +42 -0
  13. package/dist/connection-notifications.js +21 -21
  14. package/dist/connection-transport.d.ts +2 -0
  15. package/dist/connection-transport.js +95 -37
  16. package/dist/connection-types.d.ts +2 -1
  17. package/dist/connection.d.ts +49 -45
  18. package/dist/connection.js +73 -33
  19. package/dist/crypto.d.ts +5 -6
  20. package/dist/crypto.js +13 -13
  21. package/dist/errors.d.ts +38 -24
  22. package/dist/errors.js +130 -71
  23. package/dist/file-crypto.d.ts +25 -0
  24. package/dist/file-crypto.js +132 -0
  25. package/dist/file-sha256.d.ts +12 -0
  26. package/dist/file-sha256.js +143 -0
  27. package/dist/file-stream-source.d.ts +17 -0
  28. package/dist/file-stream-source.js +157 -0
  29. package/dist/files.d.ts +91 -0
  30. package/dist/files.js +644 -0
  31. package/dist/hosted-file-request.d.ts +6 -0
  32. package/dist/hosted-file-request.js +63 -0
  33. package/dist/index.d.ts +4 -1
  34. package/dist/index.js +3 -0
  35. package/dist/internal-types.d.ts +3 -1
  36. package/dist/local-file-transport.d.ts +30 -0
  37. package/dist/local-file-transport.js +367 -0
  38. package/dist/notifications.js +5 -5
  39. package/dist/operation-helpers.d.ts +2 -2
  40. package/dist/operation-helpers.js +22 -18
  41. package/dist/operation-types.d.ts +3 -4
  42. package/dist/outcomes.d.ts +51 -0
  43. package/dist/outcomes.js +168 -0
  44. package/dist/runtime-utils.d.ts +6 -1
  45. package/dist/runtime-utils.js +90 -11
  46. package/dist/selection.js +3 -3
  47. package/dist/session.d.ts +9 -8
  48. package/dist/session.js +31 -20
  49. package/package.json +2 -2
package/README.md CHANGED
@@ -27,49 +27,49 @@ const session = mdbase.createSession({
27
27
  selection: new MdbaseBrowserSelection()
28
28
  });
29
29
 
30
- await session.start();
30
+ const started = await session.start();
31
+ if (!started.ok) {
32
+ renderProblem(started.problem);
33
+ return;
34
+ }
31
35
  if (session.getSnapshot().status === "unselected") {
32
- await session.authorize("choose");
36
+ const authorized = await session.authorize("choose");
37
+ if (!authorized.ok) {
38
+ renderProblem(authorized.problem);
39
+ return;
40
+ }
33
41
  }
34
42
 
35
43
  const snapshot = session.getSnapshot();
36
44
  if (snapshot.status !== "ready") throw new Error("Choose an authorized collection.");
37
45
  const connection = snapshot.connection;
38
- const description = await connection.describe();
39
- const workouts = await connection.query({ types: ["workout"] });
40
- await connection.update({
46
+ const queried = await connection.query({ types: ["workout"] });
47
+ if (!queried.ok) {
48
+ renderProblem(queried.problem);
49
+ return;
50
+ }
51
+ const workouts = queried.value;
52
+ const updated = await connection.update({
41
53
  path: "workouts/monday.md",
42
54
  patch: { completed: true },
43
- if_revision: workouts.result.results[0].revision
44
- });
45
-
46
- const preview = await connection.preflightRename({
47
- from: "workouts/monday.md",
48
- to: "archive/monday.md",
49
- update_refs: true,
50
- if_revision: workouts.result.results[0].revision
51
- });
52
- console.log(preview.result.references_affected);
53
-
54
- const controller = new AbortController();
55
- await connection.renameWithProgress({
56
- from: "workouts/monday.md",
57
- to: "archive/monday.md",
58
- update_refs: true,
59
- if_revision: workouts.result.results[0].revision
60
- }, {
61
- preflight: preview.result,
62
- signal: controller.signal,
63
- onProgress: ({ state, estimate, cancellable }) => {
64
- console.log(state, estimate?.affectedRecords, cancellable);
65
- }
55
+ if_revision: workouts.results[0].revision
66
56
  });
57
+ if (!updated.ok) renderProblem(updated.problem);
67
58
 
68
59
  for await (const change of connection.watch()) {
69
- console.log(change.type, change.payload.path);
60
+ if (!change.ok) {
61
+ renderProblem(change.problem);
62
+ break;
63
+ }
64
+ console.log(change.value.type, change.value.payload.path);
70
65
  }
71
66
  ```
72
67
 
68
+ Expected failures are returned as typed `ConnectOutcome` values. Exceptions are
69
+ reserved for programming errors and broken SDK invariants. See
70
+ [typed outcomes and recovery](../../docs/sdk-outcomes.md) for the complete
71
+ problem model, setup failures, mutation uncertainty, and UI guidance.
72
+
73
73
  `MdbaseConnect` is the application-level authorization registry.
74
74
  `MdbaseSession` is the normal application boundary: it owns active collection
75
75
  selection, authorization callback completion, access capabilities, and one
@@ -174,8 +174,8 @@ if (!capabilities.sufficient) {
174
174
  `requestOperations()` is a no-op when the current grant is sufficient. When a
175
175
  replacement grant is needed, it requests the least-privilege union of the
176
176
  already granted operations and the missing requirements. An
177
- `insufficient_access` error carries the same `grantedOperations`,
178
- `missingOperations`, and `requiredOperations` metadata with a `reauthorize`
177
+ An `insufficient_access` problem carries the same `granted_operations`,
178
+ `missing_operations`, and `required_operations` metadata with a `reauthorize`
179
179
  recovery action.
180
180
 
181
181
  Applications that declare an `mdbase.runtime.timer.fired` notification criterion can keep
@@ -222,12 +222,15 @@ schema:
222
222
  `
223
223
  });
224
224
 
225
+ if (!created.ok) return renderProblem(created.problem);
225
226
  const current = await connection.readType({ name: "workout" });
226
- await connection.updateType({
227
- path: current.result.path,
228
- document: current.result.document.replace("version: 1", "version: 2"),
229
- if_revision: current.result.revision
227
+ if (!current.ok) return renderProblem(current.problem);
228
+ const updatedType = await connection.updateType({
229
+ path: current.value.path,
230
+ document: current.value.document.replace("version: 1", "version: 2"),
231
+ if_revision: current.value.revision
230
232
  });
233
+ if (!updatedType.ok) renderProblem(updatedType.problem);
231
234
  ```
232
235
 
233
236
  Request `read_type`, `create_type`, and `update_type` during authorization.
@@ -250,7 +253,8 @@ For a local collection, ask for same-computer access from a user gesture:
250
253
 
251
254
  ```ts
252
255
  const status = await connection.checkDirectAccess();
253
- if (status === "permission_required") {
256
+ if (!status.ok) return renderProblem(status.problem);
257
+ if (status.value === "permission_required") {
254
258
  directButton.onclick = () => connection.requestDirectAccess();
255
259
  }
256
260
 
@@ -353,8 +357,9 @@ installs each missing type pack transactionally, verifies its exact contracts
353
357
  and implementations, and creates the scoped grant afterward. The application
354
358
  is not granted collection-wide type-management access.
355
359
 
356
- The SDK returns the mdbase operation envelope, carries revision tokens in typed
357
- record results, and accepts `if_revision` on mutations. `describe()` exposes
360
+ The SDK returns typed outcomes, carries successful mdbase diagnostics alongside
361
+ the value, carries revision tokens in typed record results, and accepts
362
+ `if_revision` on mutations. `describe()` exposes
358
363
  JSON Schemas, portable type definitions, canonical collection settings, and
359
364
  first-class data contracts. `watch()` resumes from a local collection cursor;
360
365
  the Connect server does not store the change feed.
@@ -399,6 +404,71 @@ local and hosted authorities.
399
404
  provider credential. It refreshes the grant-bound capability as needed and can
400
405
  be passed directly to `@mdbase-dev/connect-sync` for an offline application cache.
401
406
 
407
+ Applications request non-Markdown files separately from record contracts:
408
+
409
+ ```ts
410
+ requirements: {
411
+ contracts: [],
412
+ files: {
413
+ actions: ["list", "read", "add", "replace", "move", "delete"],
414
+ scope: { kind: "selected_folders", folders: ["Photos"] }
415
+ }
416
+ }
417
+ ```
418
+
419
+ The granted connection exposes one storage-neutral facade. Hosted uploads go
420
+ directly to private R2 objects through short-lived prepared requests. Downloads
421
+ use authenticated bounded ranges so the authority can recheck access between
422
+ parts. Object keys, multipart ETags, provider credentials, retries, and
423
+ integrity checks stay inside the SDK.
424
+
425
+ ```ts
426
+ const saved = await connection.files.upload("Photos/image.jpg", browserFile, {
427
+ onProgress: ({ phase, transferredBytes, totalBytes }) => {
428
+ console.log(phase, transferredBytes, totalBytes);
429
+ }
430
+ });
431
+
432
+ const large = await connection.files.uploadStream("Media/video.mp4", {
433
+ size: manifest.size,
434
+ contentDigest: manifest.sha256,
435
+ stream: response.body!
436
+ });
437
+
438
+ for await (const file of connection.files.list({ folder: "Photos" })) {
439
+ const stream = await connection.files.downloadStream(file);
440
+ await stream.pipeTo(destination);
441
+ }
442
+
443
+ const moved = await connection.files.move(saved, "Archive/image.jpg");
444
+ await connection.files.delete(moved);
445
+ ```
446
+
447
+ `download()` and `downloadBytes()` are convenient for values up to 64 MiB.
448
+ Use `downloadStream()` for larger files; it verifies the pinned revision while
449
+ forwarding response chunks with native stream backpressure rather than
450
+ buffering a complete hosted range.
451
+ The live stream retries unopened chunks and can switch a local download between
452
+ direct and relay. It does not persist progress after consumption stops or
453
+ resume a hosted range after part of that range has reached the consumer;
454
+ restart `downloadStream()` to reopen the pinned revision in those cases.
455
+
456
+ `upload()` accepts `Blob`, `ArrayBuffer`, and typed-array values and hashes them
457
+ before opening the transfer. `uploadStream()` accepts a `ReadableStream` or
458
+ async iterable plus its exact size and `sha256:…` commitment. It verifies the
459
+ stream while uploading sequentially without accumulating multiple file parts.
460
+ SDK memory is bounded by one assembled negotiated part plus at most one source
461
+ chunk; a single-part upload may therefore hold the complete file. Yielded
462
+ source chunks must fit within the authority's negotiated upload part; ordinary
463
+ browser and Node streams already produce much smaller chunks. After an
464
+ ambiguous failure, call it again with a newly opened source and the same
465
+ `transferId` to resume safely.
466
+
467
+ Both lifecycle methods are optimistic and use the descriptor revision by
468
+ default. Pass a stable `mutationId` when retrying after an ambiguous network
469
+ failure; the authority returns the original durable receipt instead of applying
470
+ the change twice.
471
+
402
472
  Record-facing code can depend on `MdbaseCollectionClient` instead of the OAuth
403
473
  client. It accepts a small `MdbaseCollectionTransport`, which is the stable seam
404
474
  used by Connect, the developer sandbox, and future hosted providers. This keeps
@@ -1,3 +1,4 @@
1
+ import type { ConnectProblem } from "@mdbase-dev/connect-protocol";
1
2
  export declare function cleanAuthorizationParameters(url: URL): URL;
2
3
  export declare function isAuthorizationCallbackUrl(value: string): boolean;
3
- export declare function authorizationReturnToFromError(error: unknown): string | undefined;
4
+ export declare function authorizationReturnToFromProblem(problem: ConnectProblem): string | undefined;
@@ -1,4 +1,3 @@
1
- import { MdbaseConnectError } from "./errors.js";
2
1
  export function cleanAuthorizationParameters(url) {
3
2
  for (const parameter of ["code", "state", "error", "error_description"]) {
4
3
  url.searchParams.delete(parameter);
@@ -16,12 +15,11 @@ export function isAuthorizationCallbackUrl(value) {
16
15
  return url.searchParams.has("state")
17
16
  && (url.searchParams.has("code") || url.searchParams.has("error"));
18
17
  }
19
- export function authorizationReturnToFromError(error) {
20
- if (!(error instanceof MdbaseConnectError)
21
- || !error.details
22
- || typeof error.details !== "object"
23
- || Array.isArray(error.details))
18
+ export function authorizationReturnToFromProblem(problem) {
19
+ if (!problem.details
20
+ || typeof problem.details !== "object"
21
+ || Array.isArray(problem.details))
24
22
  return undefined;
25
- const returnTo = error.details.returnTo;
23
+ const returnTo = problem.details.return_to;
26
24
  return typeof returnTo === "string" ? returnTo : undefined;
27
25
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "file": "mdbase-connect.min.js",
3
- "integrity": "sha384-u+z1WOpeMWPwuYV9kDMHHAqQEZKGn+NwX9tjAYtNztyMD7WJbsdj+Be++i43R9nY"
3
+ "integrity": "sha384-5i/BVtYUAqTluHzqWtHkb2tI4mKjuOsQMdAKhtXjddN95BAkTPOpub7++MQX+fU0"
4
4
  }