@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
@@ -1,5 +1,4 @@
1
- import type { CollectionOperation, JsonObject, QueryRecord, RecordDocument } from "@mdbase-dev/connect-protocol";
2
- import type { MdbaseConnectError } from "./errors.js";
1
+ import type { CollectionOperation, ConnectProblem, JsonObject, QueryRecord, RecordDocument } from "@mdbase-dev/connect-protocol";
3
2
  export interface MdbaseDesiredTimer {
4
3
  /** Stable within the timer namespace. */
5
4
  id: string;
@@ -241,11 +240,11 @@ export type WatchStatus = {
241
240
  cursor?: number;
242
241
  attempt: number;
243
242
  retryInMs: number;
244
- error: unknown;
243
+ problem: ConnectProblem;
245
244
  } | {
246
245
  state: "reset_required";
247
246
  cursor: number;
248
- error: MdbaseConnectError;
247
+ problem: ConnectProblem<"change_cursor_reset">;
249
248
  };
250
249
  /** Provider-neutral operation transport used by the typed collection client. */
251
250
  export interface MdbaseCollectionTransport {
@@ -0,0 +1,51 @@
1
+ import type { ConnectProblem, ConnectProblemCode, MdbaseDiagnostic } from "@mdbase-dev/connect-protocol";
2
+ export type ConnectSuccess<Value> = {
3
+ ok: true;
4
+ value: Value;
5
+ diagnostics: MdbaseDiagnostic[];
6
+ };
7
+ export type ConnectFailure<Code extends ConnectProblemCode = ConnectProblemCode> = {
8
+ ok: false;
9
+ problem: ConnectProblem<Code>;
10
+ };
11
+ /**
12
+ * Every expected failure from the public SDK is data. Exceptions are reserved
13
+ * for programming errors and broken SDK invariants.
14
+ */
15
+ export type ConnectOutcome<Value, Code extends ConnectProblemCode = ConnectProblemCode> = ConnectSuccess<Value> | ConnectFailure<Code>;
16
+ export type CommonOperationProblemCode = "access_denied" | "access_paused" | "authorization_expired" | "collection_access_denied" | "connector_identity_changed" | "connector_offline" | "connector_upgrade_required" | "direct_operation_rejected" | "operation_outcome_unknown" | "encryption_required" | "encrypted_relay_rejected" | "hosted_provider_unavailable" | "insufficient_access" | "invalid_encrypted_response" | "invalid_operation_response" | "invalid_request" | "missing_grant_key" | "not_authorized" | "operation_cancelled" | "operation_failed" | "rate_limited" | "relay_authorization_expired" | "relay_unavailable" | "temporarily_unavailable" | "timeout" | "unsupported_operation";
17
+ export declare const COMMON_OPERATION_PROBLEM_CODES: readonly ["access_denied", "access_paused", "authorization_expired", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "unsupported_operation"];
18
+ export declare const ALL_CONNECT_PROBLEM_CODES: ConnectProblemCode[];
19
+ export type RegistrationProblemCode = "discovery_failed" | "invalid_application_manifest" | "invalid_request" | "manifest_load_failed" | "rate_limited" | "temporarily_unavailable" | "timeout";
20
+ export declare const REGISTRATION_PROBLEM_CODES: readonly ["discovery_failed", "invalid_application_manifest", "invalid_request", "manifest_load_failed", "rate_limited", "temporarily_unavailable", "timeout"];
21
+ export type AuthorizationProblemCode = RegistrationProblemCode | "access_denied" | "approval_window_blocked" | "authorization_cancelled" | "browser_required" | "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" | "encryption_required" | "expired_token" | "invalid_callback" | "invalid_device_authorization_response" | "invalid_token_response" | "scope_denied" | "token_exchange_failed";
22
+ export declare const AUTHORIZATION_PROBLEM_CODES: readonly ["discovery_failed", "invalid_application_manifest", "invalid_request", "manifest_load_failed", "rate_limited", "temporarily_unavailable", "timeout", "access_denied", "approval_window_blocked", "authorization_cancelled", "browser_required", "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", "encryption_required", "expired_token", "invalid_callback", "invalid_device_authorization_response", "invalid_token_response", "scope_denied", "token_exchange_failed"];
23
+ export type NotificationProblemCode = RegistrationProblemCode | "authorization_expired" | "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
+ export declare const NOTIFICATION_PROBLEM_CODES: readonly ["discovery_failed", "invalid_application_manifest", "invalid_request", "manifest_load_failed", "rate_limited", "temporarily_unavailable", "timeout", "authorization_expired", "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
+ export type DirectAccessProblemCode = "connector_offline" | "connector_upgrade_required" | "invalid_operation_response" | "not_authorized" | "operation_failed" | "temporarily_unavailable" | "timeout";
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_application_manifest", "invalid_request", "manifest_load_failed", "rate_limited", "temporarily_unavailable", "timeout", "access_denied", "approval_window_blocked", "authorization_cancelled", "browser_required", "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", "encryption_required", "expired_token", "invalid_callback", "invalid_device_authorization_response", "invalid_token_response", "scope_denied", "token_exchange_failed", "collection_not_ready", "collection_not_selected", "unknown_collection"];
29
+ export type CollectionSetupProblemCode = "collection_configuration_invalid" | "collection_invalid" | "collection_type_registry_invalid" | "collection_version_unsupported";
30
+ export type CollectionDescriptionProblemCode = CommonOperationProblemCode | CollectionSetupProblemCode;
31
+ export type CollectionReadProblemCode = CollectionDescriptionProblemCode | "invalid_path" | "operation_invalid";
32
+ export type CollectionQueryProblemCode = CollectionReadProblemCode | "query_snapshot_changed" | "sandbox_unsupported";
33
+ export type CollectionMutationProblemCode = CollectionReadProblemCode | "concurrent_modification" | "invalid_preflight" | "pending_mutation_unresolved";
34
+ export type CollectionChangesProblemCode = CollectionDescriptionProblemCode | "change_cursor_reset";
35
+ export type CollectionTypeProblemCode = CollectionMutationProblemCode | "type_pack_provision_failed";
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_expired", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "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_expired", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "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_expired", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "unsupported_operation", "collection_configuration_invalid", "collection_invalid", "collection_type_registry_invalid", "collection_version_unsupported", "invalid_path", "operation_invalid", "query_snapshot_changed", "sandbox_unsupported"];
40
+ export declare const COLLECTION_MUTATION_PROBLEM_CODES: readonly ["access_denied", "access_paused", "authorization_expired", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "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_expired", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "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_expired", "collection_access_denied", "connector_identity_changed", "connector_offline", "connector_upgrade_required", "direct_operation_rejected", "operation_outcome_unknown", "encryption_required", "encrypted_relay_rejected", "hosted_provider_unavailable", "insufficient_access", "invalid_encrypted_response", "invalid_operation_response", "invalid_request", "missing_grant_key", "not_authorized", "operation_cancelled", "operation_failed", "rate_limited", "relay_authorization_expired", "relay_unavailable", "temporarily_unavailable", "timeout", "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
+ export declare function connectSuccess<Value>(value: Value, diagnostics?: MdbaseDiagnostic[]): ConnectSuccess<Value>;
44
+ export declare function connectFailure<Code extends ConnectProblemCode>(problem: ConnectProblem<Code>): ConnectFailure<Code>;
45
+ export declare function captureConnectOutcome<Value, Code extends ConnectProblemCode>(operation: () => Promise<Value>, allowedCodes: readonly Code[]): Promise<ConnectOutcome<Value, Code>>;
46
+ export declare class ConnectOutcomeError extends Error {
47
+ readonly problem: ConnectProblem;
48
+ constructor(problem: ConnectProblem);
49
+ }
50
+ /** Explicitly opt back into exception flow for scripts and narrow adapters. */
51
+ export declare function unwrapConnectOutcome<Value>(outcome: ConnectOutcome<Value>): Value;
@@ -0,0 +1,168 @@
1
+ import { CONNECT_PROBLEM_CATALOG } from "@mdbase-dev/connect-protocol";
2
+ import { MdbaseConnectError, unknownConnectProblem } from "./errors.js";
3
+ export const COMMON_OPERATION_PROBLEM_CODES = [
4
+ "access_denied",
5
+ "access_paused",
6
+ "authorization_expired",
7
+ "collection_access_denied",
8
+ "connector_identity_changed",
9
+ "connector_offline",
10
+ "connector_upgrade_required",
11
+ "direct_operation_rejected",
12
+ "operation_outcome_unknown",
13
+ "encryption_required",
14
+ "encrypted_relay_rejected",
15
+ "hosted_provider_unavailable",
16
+ "insufficient_access",
17
+ "invalid_encrypted_response",
18
+ "invalid_operation_response",
19
+ "invalid_request",
20
+ "missing_grant_key",
21
+ "not_authorized",
22
+ "operation_cancelled",
23
+ "operation_failed",
24
+ "rate_limited",
25
+ "relay_authorization_expired",
26
+ "relay_unavailable",
27
+ "temporarily_unavailable",
28
+ "timeout",
29
+ "unsupported_operation"
30
+ ];
31
+ export const ALL_CONNECT_PROBLEM_CODES = Object.keys(CONNECT_PROBLEM_CATALOG);
32
+ export const REGISTRATION_PROBLEM_CODES = [
33
+ "discovery_failed",
34
+ "invalid_application_manifest",
35
+ "invalid_request",
36
+ "manifest_load_failed",
37
+ "rate_limited",
38
+ "temporarily_unavailable",
39
+ "timeout"
40
+ ];
41
+ export const AUTHORIZATION_PROBLEM_CODES = [
42
+ ...REGISTRATION_PROBLEM_CODES,
43
+ "access_denied",
44
+ "approval_window_blocked",
45
+ "authorization_cancelled",
46
+ "browser_required",
47
+ "collection_access_denied",
48
+ "collection_configuration_invalid",
49
+ "collection_contracts_missing",
50
+ "collection_incompatible",
51
+ "collection_invalid",
52
+ "collection_kind_unsupported",
53
+ "collection_mismatch",
54
+ "collection_not_found",
55
+ "collection_type_registry_invalid",
56
+ "collection_version_unsupported",
57
+ "connector_identity_changed",
58
+ "connector_offline",
59
+ "connector_upgrade_required",
60
+ "device_authorization_failed",
61
+ "encryption_required",
62
+ "expired_token",
63
+ "invalid_callback",
64
+ "invalid_device_authorization_response",
65
+ "invalid_token_response",
66
+ "scope_denied",
67
+ "token_exchange_failed"
68
+ ];
69
+ export const NOTIFICATION_PROBLEM_CODES = [
70
+ ...REGISTRATION_PROBLEM_CODES,
71
+ "authorization_expired",
72
+ "invalid_push_subscription",
73
+ "managed_fcm_not_declared",
74
+ "not_authorized",
75
+ "notification_criterion_not_declared",
76
+ "notification_reauthorization_required",
77
+ "notification_registration_failed",
78
+ "notification_unregistration_failed",
79
+ "notifications_not_declared",
80
+ "notifications_unavailable"
81
+ ];
82
+ export const DIRECT_ACCESS_PROBLEM_CODES = [
83
+ "connector_offline",
84
+ "connector_upgrade_required",
85
+ "invalid_operation_response",
86
+ "not_authorized",
87
+ "operation_failed",
88
+ "temporarily_unavailable",
89
+ "timeout"
90
+ ];
91
+ export const SESSION_PROBLEM_CODES = [
92
+ ...AUTHORIZATION_PROBLEM_CODES,
93
+ "collection_not_ready",
94
+ "collection_not_selected",
95
+ "unknown_collection"
96
+ ];
97
+ export const COLLECTION_SETUP_PROBLEM_CODES = [
98
+ "collection_configuration_invalid",
99
+ "collection_invalid",
100
+ "collection_type_registry_invalid",
101
+ "collection_version_unsupported"
102
+ ];
103
+ export const COLLECTION_DESCRIPTION_PROBLEM_CODES = [
104
+ ...COMMON_OPERATION_PROBLEM_CODES,
105
+ ...COLLECTION_SETUP_PROBLEM_CODES
106
+ ];
107
+ export const COLLECTION_READ_PROBLEM_CODES = [
108
+ ...COLLECTION_DESCRIPTION_PROBLEM_CODES,
109
+ "invalid_path",
110
+ "operation_invalid"
111
+ ];
112
+ export const COLLECTION_QUERY_PROBLEM_CODES = [
113
+ ...COLLECTION_READ_PROBLEM_CODES,
114
+ "query_snapshot_changed",
115
+ "sandbox_unsupported"
116
+ ];
117
+ export const COLLECTION_MUTATION_PROBLEM_CODES = [
118
+ ...COLLECTION_READ_PROBLEM_CODES,
119
+ "concurrent_modification",
120
+ "invalid_preflight",
121
+ "pending_mutation_unresolved"
122
+ ];
123
+ export const COLLECTION_CHANGES_PROBLEM_CODES = [
124
+ ...COLLECTION_DESCRIPTION_PROBLEM_CODES,
125
+ "change_cursor_reset"
126
+ ];
127
+ export const COLLECTION_TYPE_PROBLEM_CODES = [
128
+ ...COLLECTION_MUTATION_PROBLEM_CODES,
129
+ "type_pack_provision_failed"
130
+ ];
131
+ export function connectSuccess(value, diagnostics = []) {
132
+ return { ok: true, value, diagnostics };
133
+ }
134
+ export function connectFailure(problem) {
135
+ return { ok: false, problem };
136
+ }
137
+ export async function captureConnectOutcome(operation, allowedCodes) {
138
+ try {
139
+ return connectSuccess(await operation());
140
+ }
141
+ catch (error) {
142
+ if (!(error instanceof MdbaseConnectError))
143
+ throw error;
144
+ const problem = error.problem;
145
+ if (problem.code === "unknown" || allowedCodes.includes(problem.code)) {
146
+ return connectFailure(problem);
147
+ }
148
+ return connectFailure(unknownConnectProblem(problem.code, problem.message, {
149
+ details: problem.details,
150
+ operationOutcome: problem.operation_outcome,
151
+ traceId: problem.trace_id
152
+ }));
153
+ }
154
+ }
155
+ export class ConnectOutcomeError extends Error {
156
+ problem;
157
+ constructor(problem) {
158
+ super(problem.message);
159
+ this.problem = problem;
160
+ this.name = "ConnectOutcomeError";
161
+ }
162
+ }
163
+ /** Explicitly opt back into exception flow for scripts and narrow adapters. */
164
+ export function unwrapConnectOutcome(outcome) {
165
+ if (!outcome.ok)
166
+ throw new ConnectOutcomeError(outcome.problem);
167
+ return outcome.value;
168
+ }
@@ -1,7 +1,10 @@
1
- import type { GrantEncryption, GrantScope, MdbaseAppManifest } from "@mdbase-dev/connect-protocol";
1
+ import type { FileCapability, GrantEncryption, GrantScope, MdbaseAppManifest } from "@mdbase-dev/connect-protocol";
2
2
  import { MdbaseConnectError } from "./errors.js";
3
3
  import type { StoredToken } from "./internal-types.js";
4
4
  import type { MdbaseDeviceAuthorization } from "./authorization-types.js";
5
+ type NetworkProblemCode = "notification_registration_failed" | "notification_unregistration_failed" | "notifications_unavailable" | "temporarily_unavailable";
6
+ /** Normalize fetch rejection at the exact I/O boundary. */
7
+ export declare function connectFetch(input: RequestInfo | URL, init: RequestInit | undefined, code: NetworkProblemCode, message: string): Promise<Response>;
5
8
  export declare function canonicalLoopbackUrl(value: string): string;
6
9
  export declare function createPkce(): Promise<{
7
10
  verifier: string;
@@ -11,6 +14,7 @@ export declare function apiError(body: any, fallbackCode: string, fallbackMessag
11
14
  export declare function oauthErrorCode(body: any): string | undefined;
12
15
  export declare function validAuthorityTokenResponse(value: unknown, collectionId: unknown): boolean;
13
16
  export declare function validStoredAuthority(authority: StoredToken["authority"], collectionId: string): boolean;
17
+ export declare function validFileCapability(value: unknown): value is FileCapability;
14
18
  export declare function validStoredEncryption(encryption: StoredToken["encryption"], collectionId: string): encryption is GrantEncryption;
15
19
  export declare function parseDeviceAuthorization(body: any): MdbaseDeviceAuthorization;
16
20
  export declare function parseStored<T>(value: string | null): T | null;
@@ -31,3 +35,4 @@ export declare class MemoryStorage implements Storage {
31
35
  }
32
36
  export declare function isOpaquePortableManifest(manifest: MdbaseAppManifest | string): boolean;
33
37
  export declare function manifestStorageFingerprint(manifest: MdbaseAppManifest): string;
38
+ export {};
@@ -1,7 +1,16 @@
1
1
  import { validateGrantEncryption } from "./crypto.js";
2
- import { MdbaseConnectError } from "./errors.js";
2
+ import { connectError, serverConnectError } from "./errors.js";
3
3
  import { canonicalJson } from "./operation-helpers.js";
4
4
  import { bytesToBase64Url, randomBase64Url } from "./base64.js";
5
+ /** Normalize fetch rejection at the exact I/O boundary. */
6
+ export async function connectFetch(input, init, code, message) {
7
+ try {
8
+ return await fetch(input, init);
9
+ }
10
+ catch (cause) {
11
+ throw connectError(code, message, { cause });
12
+ }
13
+ }
5
14
  export function canonicalLoopbackUrl(value) {
6
15
  const url = new URL(value);
7
16
  if (url.protocol !== "http:"
@@ -11,7 +20,7 @@ export function canonicalLoopbackUrl(value) {
11
20
  || url.pathname !== "/"
12
21
  || url.search
13
22
  || url.hash) {
14
- throw new MdbaseConnectError("invalid_loopback_url", "loopbackUrl must be an HTTP origin on 127.0.0.1 or ::1.");
23
+ throw connectError("invalid_loopback_url", "loopbackUrl must be an HTTP origin on 127.0.0.1 or ::1.");
15
24
  }
16
25
  return url.origin;
17
26
  }
@@ -21,7 +30,7 @@ export async function createPkce() {
21
30
  return { verifier, challenge: bytesToBase64Url(new Uint8Array(digest)) };
22
31
  }
23
32
  export function apiError(body, fallbackCode, fallbackMessage, status) {
24
- return new MdbaseConnectError(oauthErrorCode(body) ?? body?.error?.code ?? fallbackCode, body?.error_description ?? body?.error?.message ?? fallbackMessage, { status, details: body?.error?.details });
33
+ return serverConnectError(oauthErrorCode(body) ?? body?.error?.code ?? fallbackCode, body?.error_description ?? body?.error?.message ?? fallbackMessage, { status, details: body?.error?.details });
25
34
  }
26
35
  export function oauthErrorCode(body) {
27
36
  return typeof body?.error === "string" ? body.error : undefined;
@@ -34,6 +43,7 @@ export function validAuthorityTokenResponse(value, collectionId) {
34
43
  const authority = value;
35
44
  if (typeof authority.operations_url !== "string"
36
45
  || typeof authority.sync_url !== "string"
46
+ || typeof authority.files_url !== "string"
37
47
  || typeof authority.replica_id !== "string"
38
48
  || authority.replica_id.length === 0
39
49
  || typeof authority.access_token !== "string"
@@ -45,7 +55,8 @@ export function validAuthorityTokenResponse(value, collectionId) {
45
55
  try {
46
56
  const operations = new URL(authority.operations_url);
47
57
  const sync = new URL(authority.sync_url);
48
- return [operations, sync].every((url) => (url.protocol === "https:"
58
+ const files = new URL(authority.files_url);
59
+ return [operations, sync, files].every((url) => (url.protocol === "https:"
49
60
  || (url.protocol === "http:"
50
61
  && ["localhost", "127.0.0.1", "[::1]", "::1"].includes(url.hostname)))
51
62
  && !url.username
@@ -54,9 +65,12 @@ export function validAuthorityTokenResponse(value, collectionId) {
54
65
  && !url.hash)
55
66
  && /^\/v1\/authorities\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/operations$/i.test(operations.pathname)
56
67
  && /^\/v1\/authorities\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/sync$/i.test(sync.pathname)
68
+ && /^\/v1\/authorities\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/files$/i.test(files.pathname)
57
69
  && operations.origin === sync.origin
70
+ && operations.origin === files.origin
58
71
  && operations.pathname.split("/")[3] === collectionId
59
- && sync.pathname.split("/")[3] === collectionId;
72
+ && sync.pathname.split("/")[3] === collectionId
73
+ && files.pathname.split("/")[3] === collectionId;
60
74
  }
61
75
  catch {
62
76
  return false;
@@ -66,11 +80,76 @@ export function validStoredAuthority(authority, collectionId) {
66
80
  return validAuthorityTokenResponse({
67
81
  operations_url: authority?.operationsUrl,
68
82
  sync_url: authority?.syncUrl,
83
+ files_url: authority?.filesUrl,
69
84
  replica_id: authority?.replicaId,
70
85
  access_token: authority?.accessToken,
71
86
  proof_public_key: authority?.proofPublicKey
72
87
  }, collectionId);
73
88
  }
89
+ export function validFileCapability(value) {
90
+ if (!value || typeof value !== "object" || Array.isArray(value))
91
+ return false;
92
+ const capability = value;
93
+ const actions = capability.actions;
94
+ if (capability.kind !== "files"
95
+ || capability.protocol_version !== 1
96
+ || !Array.isArray(actions)
97
+ || actions.length === 0
98
+ || actions.length > 6
99
+ || new Set(actions).size !== actions.length
100
+ || actions.some((action) => ![
101
+ "list",
102
+ "read",
103
+ "add",
104
+ "replace",
105
+ "move",
106
+ "delete"
107
+ ].includes(action)))
108
+ return false;
109
+ const scope = capability.scope;
110
+ if (!scope || typeof scope !== "object" || Array.isArray(scope))
111
+ return false;
112
+ if (scope.kind === "collection") {
113
+ return Object.keys(scope).length === 1;
114
+ }
115
+ return scope.kind === "selected_folders"
116
+ && Object.keys(scope).length === 2
117
+ && Array.isArray(scope.folders)
118
+ && scope.folders.length > 0
119
+ && scope.folders.length <= 100
120
+ && new Set(scope.folders).size === scope.folders.length
121
+ && scope.folders.every((folder) => typeof folder === "string"
122
+ && folder.length > 0
123
+ && folder.length <= 1_024
124
+ && validFileFolder(folder));
125
+ }
126
+ function validFileFolder(folder) {
127
+ const reserved = new Set([
128
+ ".mdbase",
129
+ ".git",
130
+ "node_modules",
131
+ "_contracts",
132
+ "_schemas",
133
+ "_types",
134
+ "_views"
135
+ ]);
136
+ return !folder.startsWith("/")
137
+ && !folder.endsWith("/")
138
+ && !folder.includes("\\")
139
+ && folder.split("/").every((component) => {
140
+ const stem = component.split(".")[0]?.toUpperCase() ?? "";
141
+ return component !== ""
142
+ && component !== "."
143
+ && component !== ".."
144
+ && !component.startsWith(".")
145
+ && !component.endsWith(" ")
146
+ && !component.endsWith(".")
147
+ && !/[<>:"|?*]/.test(component)
148
+ && !reserved.has(component.toLowerCase())
149
+ && !["CON", "PRN", "AUX", "NUL"].includes(stem)
150
+ && !/^(COM|LPT)[1-9]$/.test(stem);
151
+ });
152
+ }
74
153
  export function validStoredEncryption(encryption, collectionId) {
75
154
  if (!encryption || typeof encryption !== "object" || Array.isArray(encryption)) {
76
155
  return false;
@@ -92,7 +171,7 @@ export function parseDeviceAuthorization(body) {
92
171
  || typeof body?.verification_uri_complete !== "string"
93
172
  || !Number.isFinite(body?.expires_in)
94
173
  || !Number.isFinite(body?.interval)) {
95
- throw new MdbaseConnectError("invalid_device_authorization_response", "Connect returned an invalid downloaded application authorization response.");
174
+ throw connectError("invalid_device_authorization_response", "Connect returned an invalid downloaded application authorization response.");
96
175
  }
97
176
  return {
98
177
  userCode: body.user_code,
@@ -148,22 +227,22 @@ export function stripTrailingSlash(value) {
148
227
  }
149
228
  export function defaultManifestSource() {
150
229
  if (typeof location === "undefined") {
151
- throw new MdbaseConnectError("manifest_required", "manifest is required outside a browser environment.");
230
+ throw connectError("manifest_required", "manifest is required outside a browser environment.");
152
231
  }
153
232
  if (location.origin === "null") {
154
- throw new MdbaseConnectError("manifest_required", "Downloaded applications must provide their v1 portable manifest inline.");
233
+ throw connectError("manifest_required", "Downloaded applications must provide their v1 portable manifest inline.");
155
234
  }
156
235
  return new URL("/.well-known/mdbase-app.json", location.origin).href;
157
236
  }
158
237
  export function defaultRedirectUri() {
159
238
  if (typeof location === "undefined") {
160
- throw new MdbaseConnectError("redirect_uri_required", "redirectUri is required outside a browser environment.");
239
+ throw connectError("redirect_uri_required", "redirectUri is required outside a browser environment.");
161
240
  }
162
241
  return location.href.split(/[?#]/)[0];
163
242
  }
164
243
  export function defaultCallbackUrl() {
165
244
  if (typeof location === "undefined") {
166
- throw new MdbaseConnectError("callback_url_required", "callbackUrl is required outside a browser environment.");
245
+ throw connectError("callback_url_required", "callbackUrl is required outside a browser environment.");
167
246
  }
168
247
  return location.href;
169
248
  }
@@ -171,7 +250,7 @@ export function defaultStorage(memoryOnly) {
171
250
  if (memoryOnly)
172
251
  return new MemoryStorage();
173
252
  if (typeof localStorage === "undefined") {
174
- throw new MdbaseConnectError("storage_required", "storage is required outside a browser environment.");
253
+ throw connectError("storage_required", "storage is required outside a browser environment.");
175
254
  }
176
255
  try {
177
256
  const probe = `mdbase-connect:probe:${randomBase64Url(8)}`;
package/dist/selection.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { cleanAuthorizationParameters, isAuthorizationCallbackUrl } from "./authorization-url.js";
2
- import { MdbaseConnectError } from "./errors.js";
2
+ import { connectError } from "./errors.js";
3
3
  /** URL-backed collection selection for browser and browser-shell applications. */
4
4
  export class MdbaseBrowserSelection {
5
5
  collectionParameter;
@@ -71,13 +71,13 @@ export class MdbaseBrowserSelection {
71
71
  }
72
72
  currentUrl() {
73
73
  if (typeof location === "undefined") {
74
- throw new MdbaseConnectError("browser_required", "Browser selection requires a browser environment.");
74
+ throw connectError("browser_required", "Browser selection requires a browser environment.");
75
75
  }
76
76
  return new URL(location.href);
77
77
  }
78
78
  browserHistory() {
79
79
  if (typeof history === "undefined") {
80
- throw new MdbaseConnectError("browser_required", "Browser selection requires a browser environment.");
80
+ throw connectError("browser_required", "Browser selection requires a browser environment.");
81
81
  }
82
82
  return history;
83
83
  }
package/dist/session.d.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  import type { CollectionOperation, JsonObject } from "@mdbase-dev/connect-protocol";
2
2
  import { MdbaseConnection, type MdbaseAuthorizationOutcome, type MdbaseAuthorizationResult, type MdbaseAuthorizeOptions } from "./connection.js";
3
3
  import type { MdbaseAuthorizationCapabilities, MdbaseConnectionInfo } from "./connection-types.js";
4
+ import { type AuthorizationProblemCode, type ConnectOutcome, type SessionProblemCode } from "./outcomes.js";
4
5
  import type { MdbaseSelectionHistory, MdbaseSessionSelection } from "./selection.js";
5
6
  export interface MdbaseSessionConnect<Frontmatter extends JsonObject> {
6
- authorize(options?: MdbaseAuthorizeOptions): Promise<MdbaseAuthorizationOutcome<Frontmatter>>;
7
- completeAuthorization(callbackUrl: string): Promise<MdbaseAuthorizationResult<Frontmatter>>;
7
+ authorize(options?: MdbaseAuthorizeOptions): Promise<ConnectOutcome<MdbaseAuthorizationOutcome<Frontmatter>, AuthorizationProblemCode>>;
8
+ completeAuthorization(callbackUrl: string): Promise<ConnectOutcome<MdbaseAuthorizationResult<Frontmatter>, AuthorizationProblemCode>>;
8
9
  connections(): MdbaseConnectionInfo[];
9
10
  connection(collectionId: string): MdbaseConnection<Frontmatter> | null;
10
11
  unavailableReason(collectionId: string): MdbaseUnavailableReason | null;
@@ -51,25 +52,25 @@ export declare class MdbaseSession<Frontmatter extends JsonObject = JsonObject>
51
52
  private transactionDepth;
52
53
  private callbackPromise;
53
54
  constructor(connect: MdbaseSessionConnect<Frontmatter>, options: MdbaseSessionOptions);
54
- start(): Promise<MdbaseSessionSnapshot<Frontmatter>>;
55
+ start(): Promise<ConnectOutcome<MdbaseSessionSnapshot<Frontmatter>, SessionProblemCode>>;
55
56
  destroy(): void;
56
57
  getSnapshot(): MdbaseSessionSnapshot<Frontmatter>;
57
58
  subscribe(listener: () => void): () => void;
58
59
  select(collectionId: string, options?: {
59
60
  history?: MdbaseSelectionHistory;
60
- }): MdbaseConnection<Frontmatter>;
61
+ }): ConnectOutcome<MdbaseConnection<Frontmatter>, "unknown_collection">;
61
62
  clearSelection(options?: {
62
63
  history?: MdbaseSelectionHistory;
63
64
  }): void;
64
65
  forget(collectionId: string): void;
65
66
  authorize(target: "choose" | "selected" | {
66
67
  collectionId: string;
67
- }, options?: Omit<MdbaseAuthorizeOptions, "operations" | "returnTo" | "target">): Promise<MdbaseAuthorizationOutcome<Frontmatter>>;
68
- ensureOperations(requiredOperations: CollectionOperation[]): Promise<MdbaseAuthorizationOutcome<Frontmatter> | {
68
+ }, options?: Omit<MdbaseAuthorizeOptions, "operations" | "returnTo" | "target">): Promise<ConnectOutcome<MdbaseAuthorizationOutcome<Frontmatter>, SessionProblemCode>>;
69
+ ensureOperations(requiredOperations: CollectionOperation[]): Promise<ConnectOutcome<MdbaseAuthorizationOutcome<Frontmatter> | {
69
70
  kind: "unchanged";
70
71
  connection: MdbaseConnection<Frontmatter>;
71
- }>;
72
- handleAuthorizationCallback(callbackUrl: string): Promise<MdbaseConnection<Frontmatter>>;
72
+ }, SessionProblemCode>>;
73
+ handleAuthorizationCallback(callbackUrl: string): Promise<ConnectOutcome<MdbaseConnection<Frontmatter>, AuthorizationProblemCode>>;
73
74
  private autoSelectOnlyConnection;
74
75
  private refresh;
75
76
  private refreshOnce;