@mdbase-dev/connect 0.1.0-beta.94 → 0.1.0-beta.95

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.
@@ -1 +1 @@
1
- sha384-kDjazWo3W17cBn4WByvZ0jGGc1aqQxNTWglefSAMrEoEzGRgo8YNzRYSI3sQb8tx
1
+ sha384-pt5qe5l2IuGgDjsId1oPNVT/zCR+0TLeEJNt6xKdZQSxAACXxaCqoxbZd37EriJh
@@ -1,4 +1,5 @@
1
- import { type ApplicationCapabilityId, type ApplicationCapabilityRequirements, type MdbaseAppManifest } from "@mdbase-dev/connect-protocol";
1
+ import { type ApplicationCapabilityRequirements, type LegacyApplicationCapabilityRequirements } from "@mdbase-dev/connect-protocol";
2
+ import type { MdbaseApplicationCapabilityId as ApplicationCapabilityId, MdbaseApplicationManifest as MdbaseAppManifest } from "./application-contract.js";
2
3
  import type { MdbaseConnectionInfo } from "./connection-types.js";
3
4
  export type MdbaseCapabilityState = "available" | "requires_authorization" | "requires_setup" | "temporarily_unavailable" | "unsupported";
4
5
  export interface MdbaseCapabilityEvidence {
@@ -16,8 +17,8 @@ export interface MdbaseEffectiveCapability {
16
17
  details?: Record<string, unknown>;
17
18
  }
18
19
  export interface MdbaseEffectiveCapabilities {
19
- contractVersion: 1;
20
+ contractVersion: 1 | 2;
20
21
  values: Partial<Record<ApplicationCapabilityId, MdbaseEffectiveCapability>>;
21
22
  requiredAvailable: boolean;
22
23
  }
23
- export declare function effectiveCapabilities(requirements: ApplicationCapabilityRequirements, manifest: MdbaseAppManifest, connection: MdbaseConnectionInfo): MdbaseEffectiveCapabilities;
24
+ export declare function effectiveCapabilities(requirements: ApplicationCapabilityRequirements | LegacyApplicationCapabilityRequirements, manifest: MdbaseAppManifest, connection: MdbaseConnectionInfo): MdbaseEffectiveCapabilities;
@@ -1,19 +1,19 @@
1
- import { capabilityOperations } from "@mdbase-dev/connect-protocol";
1
+ import { capabilityOperationsForContractVersion } from "@mdbase-dev/connect-protocol";
2
2
  export function effectiveCapabilities(requirements, manifest, connection) {
3
3
  const required = new Set(requirements.required);
4
4
  const declared = [...new Set([...requirements.required, ...(requirements.optional ?? [])])];
5
5
  const values = {};
6
6
  for (const id of declared) {
7
- values[id] = effectiveCapability(id, required.has(id), manifest, connection);
7
+ values[id] = effectiveCapability(id, required.has(id), connection, requirements.contract_version, manifest);
8
8
  }
9
9
  return {
10
- contractVersion: 1,
10
+ contractVersion: requirements.contract_version,
11
11
  values,
12
12
  requiredAvailable: requirements.required.every((id) => values[id]?.state === "available")
13
13
  };
14
14
  }
15
- function effectiveCapability(id, required, manifest, connection) {
16
- const operations = capabilityOperations(id);
15
+ function effectiveCapability(id, required, connection, version, manifest) {
16
+ const operations = capabilityOperationsForContractVersion(version, id) ?? [];
17
17
  const missingOperations = operations.filter((operation) => !connection.operations.includes(operation));
18
18
  const base = {
19
19
  id,
@@ -22,7 +22,7 @@ function effectiveCapability(id, required, manifest, connection) {
22
22
  missingOperations,
23
23
  evidence: [{
24
24
  source: "application",
25
- fact: `${required ? "Required" : "Optional"} in capability contract v1.`
25
+ fact: `${required ? "Required" : "Optional"} in capability contract v${version}.`
26
26
  }]
27
27
  };
28
28
  if (missingOperations.length > 0) {
@@ -47,35 +47,20 @@ function effectiveCapability(id, required, manifest, connection) {
47
47
  }]
48
48
  };
49
49
  }
50
- if (id.startsWith("files.")) {
50
+ // Compatibility readiness is deliberately limited to v1 aliases. V2 needs
51
+ // structured file/notification/runtime evidence, not these legacy shortcuts.
52
+ if (version === 1 && id.startsWith("files.")) {
51
53
  const action = id.slice("files.".length);
52
54
  if (!connection.fileCapability?.actions.includes(action)) {
53
55
  return {
54
56
  ...base,
55
57
  state: connection.fileCapability ? "requires_authorization" : "unsupported",
56
- reason: connection.fileCapability
57
- ? `The current file grant does not include ${action}.`
58
- : "This connection has no file capability.",
59
- evidence: [...base.evidence, {
60
- source: "authorization",
61
- fact: connection.fileCapability
62
- ? `Granted file actions: ${connection.fileCapability.actions.join(", ")}.`
63
- : "No file capability was granted."
64
- }]
58
+ reason: connection.fileCapability ? `The current file grant does not include ${action}.` : "This connection has no file capability."
65
59
  };
66
60
  }
67
61
  }
68
- if (id === "notifications.background-delivery"
69
- && (manifest.notifications?.criteria.length ?? 0) === 0) {
70
- return {
71
- ...base,
72
- state: "requires_setup",
73
- reason: "The application manifest declares no notification criteria.",
74
- evidence: [...base.evidence, {
75
- source: "application",
76
- fact: "No authority notification criteria are declared."
77
- }]
78
- };
62
+ if (version === 1 && id === "notifications.background-delivery" && (manifest.notifications?.criteria.length ?? 0) === 0) {
63
+ return { ...base, state: "requires_setup", reason: "The application manifest declares no notification criteria." };
79
64
  }
80
65
  return {
81
66
  ...base,
@@ -91,7 +76,7 @@ function effectiveCapability(id, required, manifest, connection) {
91
76
  ? "Backed by a durable hosted mdbase authority."
92
77
  : "Backed by a user-operated mdbase connector."
93
78
  }],
94
- ...(id === "sync.offline-replica"
79
+ ...(id === "offline.replica" || (version === 1 && id === "sync.offline-replica")
95
80
  ? {
96
81
  details: {
97
82
  durability: "device",
@@ -100,8 +85,7 @@ function effectiveCapability(id, required, manifest, connection) {
100
85
  }
101
86
  }
102
87
  : {}),
103
- ...(id === "notifications.background-delivery"
104
- ? { details: { delivery: "authority", payload: "opaque" } }
105
- : {})
88
+ ...(version === 1 && id === "notifications.background-delivery"
89
+ ? { details: { delivery: "authority", payload: "opaque" } } : {})
106
90
  };
107
91
  }
@@ -1,4 +1,5 @@
1
- import type { JsonObject, MdbaseAppManifest } from "@mdbase-dev/connect-protocol";
1
+ import type { MdbaseApplicationManifest as MdbaseAppManifest } from "./application-contract.js";
2
+ import type { JsonObject } from "@mdbase-dev/connect-protocol";
2
3
  import type { GrantKeyStore } from "./crypto.js";
3
4
  import type { ApplicationIdentityStore } from "./application-identity.js";
4
5
  export interface MdbaseConnectTimeouts {
package/dist/connect.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import type { JsonObject, MdbaseAppManifest } from "@mdbase-dev/connect-protocol";
1
+ import type { JsonObject } from "@mdbase-dev/connect-protocol";
2
+ import { type MdbaseApplicationManifest as MdbaseAppManifest } from "./application-contract.js";
2
3
  import { type ApplicationIdentityStore } from "./application-identity.js";
3
4
  import { MdbaseConnection, type MdbaseAuthorizationOutcome, type MdbaseAuthorizationResult, type MdbaseAuthorizeOptions, type MdbaseConnectEnvironment } from "./connection.js";
4
5
  import type { MdbaseConnectOptions } from "./connect-options.js";
package/dist/connect.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { APPLICATION_AUTHORIZATION_PROTOCOL_VERSION, authorizationContractRequirements, DEFAULT_LOOPBACK_PORT, GRANT_ENCRYPTION_PROTOCOL_VERSION, RELAY_ENCRYPTION_SUITE } from "@mdbase-dev/connect-protocol";
2
+ import { authorizationOperations, authorizationFiles, semanticContractVersion, validateAuthorizationSelection } from "./application-contract.js";
2
3
  import { abortableDelay } from "./async.js";
3
4
  import { authorizationCallbackState } from "./authorization-url.js";
4
5
  import { randomBase64Url } from "./base64.js";
@@ -8,13 +9,11 @@ import { addConnectionId, connectionIds, removeConnectionId } from "./connection
8
9
  import { authorizationAbort, declarationIdFromFamilyIdentity } from "./connect-authorization-helpers.js";
9
10
  import { IndexedDbGrantKeyStore, MemoryGrantKeyStore } from "./crypto.js";
10
11
  import { MdbaseConnectError, connectError, serverConnectError } from "./errors.js";
11
- import { DEFAULT_OPERATIONS } from "./internal-types.js";
12
12
  import { prepareAuthorizationWindow, publishPopupAuthorizationCompletion, waitForPopupAuthorization } from "./popup-authorization.js";
13
- import { uniqueOperations } from "./operation-helpers.js";
14
13
  import { pendingRecoveryTransports, removePendingMutations } from "./pending-recovery-transports.js";
15
14
  import { GrantKeyLeaseRegistry } from "./grant-key-leases.js";
16
15
  import { resolveConnectTimeouts, withRequestBudget } from "./request-budget.js";
17
- import { MemoryStorage, apiError, applicationStorageOrigin, canonicalLoopbackUrl, collectionIdFromTokenKey, connectFetch, createPkce, defaultManifestSource, defaultRedirectUri, defaultStorage, decodeJsonResponse, isOpaquePortableManifest, manifestStorageFingerprint, oauthErrorCode, parseDeviceAuthorization, parseStored, stripTrailingSlash, validAuthorityTokenResponse } from "./runtime-utils.js";
16
+ import { MemoryStorage, apiError, applicationStorageOrigin, canonicalLoopbackUrl, collectionIdFromTokenKey, connectFetch, createPkce, defaultStorage, decodeJsonResponse, isOpaquePortableManifest, resolveManifestSource, oauthErrorCode, parseDeviceAuthorization, parseStored, stripTrailingSlash, validAuthorityTokenResponse } from "./runtime-utils.js";
18
17
  import { storedTokenFromResponse } from "./token-response.js";
19
18
  import { deleteUnusedGrantKey, planRetiredGrantKeys, storedTokenKeyHandles } from "./retired-grant-keys.js";
20
19
  import { SharedOperationMap } from "./shared-operation.js";
@@ -41,18 +40,11 @@ export class MdbaseConnectInternals {
41
40
  invalidatedConnections = new Map();
42
41
  constructor(options) {
43
42
  this.serverUrl = stripTrailingSlash(String(options.serverUrl));
44
- this.manifest = options.manifest instanceof URL
45
- ? options.manifest.href
46
- : options.manifest ?? defaultManifestSource();
47
- this.manifestSource = typeof this.manifest === "string"
48
- ? this.manifest
49
- : this.manifest.distribution === "portable"
50
- ? `bundle:${this.manifest.id}:${manifestStorageFingerprint(this.manifest)}`
51
- : `bundle:${this.manifest.id}`;
43
+ const declaration = resolveManifestSource(options.manifest, options.redirectUri);
44
+ this.manifest = declaration.manifest;
45
+ this.manifestSource = declaration.manifestSource;
52
46
  const opaquePortable = isOpaquePortableManifest(this.manifest);
53
- this.redirectUri = options.redirectUri === undefined ? (typeof this.manifest !== "string" && this.manifest.distribution === "portable"
54
- ? ""
55
- : defaultRedirectUri()) : String(options.redirectUri);
47
+ this.redirectUri = declaration.redirectUri;
56
48
  this.storage = options.storage ?? defaultStorage(opaquePortable);
57
49
  this.relayEncryption = options.relayEncryption ?? "required";
58
50
  this.keyStore = options.keyStore ?? (opaquePortable ? new MemoryGrantKeyStore() : new IndexedDbGrantKeyStore());
@@ -168,6 +160,13 @@ export class MdbaseConnectInternals {
168
160
  navigate: this.navigate,
169
161
  register: (signal) => this.registerWithinBudget(signal)
170
162
  });
163
+ try {
164
+ validateAuthorizationSelection(application.requirements, options);
165
+ }
166
+ catch (error) {
167
+ popup?.close();
168
+ throw error;
169
+ }
171
170
  if (application.distribution === "portable") {
172
171
  if (options.openVerification)
173
172
  popup?.close();
@@ -192,7 +191,8 @@ export class MdbaseConnectInternals {
192
191
  await this.keyStore.delete(keyHandle);
193
192
  throw error;
194
193
  }
195
- const operations = uniqueOperations(options.operations ?? DEFAULT_OPERATIONS);
194
+ const operations = authorizationOperations(application, options);
195
+ const requestedFiles = authorizationFiles(application.requirements);
196
196
  const issuedAt = new Date();
197
197
  const proof = await signApplicationAuthorization({
198
198
  protocol_version: APPLICATION_AUTHORIZATION_PROTOCOL_VERSION,
@@ -211,11 +211,9 @@ export class MdbaseConnectInternals {
211
211
  redirect_uri: this.redirectUri,
212
212
  state,
213
213
  code_challenge: challenge,
214
- contracts: authorizationContractRequirements(operations, application.requirements?.files, pendingRecoveryTransports(this.storage, this.storagePrefix(), operations, targetCollectionId)),
214
+ contracts: authorizationContractRequirements(operations, requestedFiles, pendingRecoveryTransports(this.storage, this.storagePrefix(), operations, targetCollectionId), semanticContractVersion(application.requirements)),
215
215
  requested_operations: operations,
216
- ...(application.requirements?.files
217
- ? { requested_files: application.requirements.files }
218
- : {}),
216
+ ...(requestedFiles ? { requested_files: requestedFiles } : {}),
219
217
  ...(targetCollectionId ? { collection_id: targetCollectionId } : {})
220
218
  }, installation);
221
219
  const pending = {
@@ -310,7 +308,8 @@ export class MdbaseConnectInternals {
310
308
  await this.keyStore.delete(keyHandle);
311
309
  throw error;
312
310
  }
313
- const operations = uniqueOperations(options.operations ?? DEFAULT_OPERATIONS);
311
+ const operations = authorizationOperations(application, options);
312
+ const requestedFiles = authorizationFiles(application.requirements);
314
313
  const authorizationId = crypto.randomUUID();
315
314
  const issuedAt = new Date();
316
315
  const proof = await signApplicationAuthorization({
@@ -328,11 +327,9 @@ export class MdbaseConnectInternals {
328
327
  issued_at: issuedAt.toISOString(),
329
328
  expires_at: new Date(issuedAt.getTime() + 10 * 60 * 1_000).toISOString(),
330
329
  code_challenge: challenge,
331
- contracts: authorizationContractRequirements(operations, application.requirements?.files, pendingRecoveryTransports(this.storage, this.storagePrefix(), operations, options.target?.kind === "collection" ? options.target.collectionId : undefined)),
330
+ contracts: authorizationContractRequirements(operations, requestedFiles, pendingRecoveryTransports(this.storage, this.storagePrefix(), operations, options.target?.kind === "collection" ? options.target.collectionId : undefined), semanticContractVersion(application.requirements)),
332
331
  requested_operations: operations,
333
- ...(application.requirements?.files
334
- ? { requested_files: application.requirements.files }
335
- : {}),
332
+ ...(requestedFiles ? { requested_files: requestedFiles } : {}),
336
333
  ...(options.target?.kind === "collection"
337
334
  ? { collection_id: options.target.collectionId }
338
335
  : {})
@@ -1,4 +1,5 @@
1
1
  import type { CollectionOperation, CollectionTypeDocument, FileCapability, GrantScope, JsonObject } from "@mdbase-dev/connect-protocol";
2
+ import { type MdbaseApplicationCapabilityId as ApplicationCapabilityId } from "./application-contract.js";
2
3
  import { type ConnectionTransportInternals } from "./connection-transport.js";
3
4
  import type { DirectAccessStatus, MdbaseAuthorizationCapabilities, MdbaseConnectionInfo, MdbaseConnectionRoute, MdbaseSyncConnection } from "./connection-types.js";
4
5
  import type { GrantKeyStore } from "./crypto.js";
@@ -16,7 +17,10 @@ export type MdbaseAuthorizationTarget = {
16
17
  collectionId: string;
17
18
  };
18
19
  export interface MdbaseAuthorizeOptions extends ConnectRequestOptions {
20
+ /** Exact independent operations, for legacy v1 declarations only. Cannot be mixed with capabilities. */
19
21
  operations?: CollectionOperation[];
22
+ /** Capability groups to request. Required groups are always included; optional groups default to all. */
23
+ capabilities?: ApplicationCapabilityId[];
20
24
  /** Keep a web application loaded while Connect owns the approval ceremony. */
21
25
  presentation?: "redirect" | "popup";
22
26
  /** Choose any compatible collection, or require one exact collection. */
@@ -29,7 +33,10 @@ export interface MdbaseAuthorizeOptions extends ConnectRequestOptions {
29
33
  openVerification?: (authorization: MdbaseDeviceAuthorization) => void | Promise<void>;
30
34
  }
31
35
  export interface MdbaseConnectionAuthorizeOptions extends ConnectRequestOptions {
36
+ /** Exact independent operations, for legacy v1 declarations only. */
32
37
  operations?: CollectionOperation[];
38
+ /** Capability groups to request. Required groups are always included; optional groups default to all. */
39
+ capabilities?: ApplicationCapabilityId[];
33
40
  presentation?: "redirect" | "popup";
34
41
  returnTo?: string;
35
42
  onDeviceCode?: (authorization: MdbaseDeviceAuthorization) => void;
@@ -80,6 +87,10 @@ export declare class MdbaseConnection<Frontmatter extends JsonObject = JsonObjec
80
87
  authorizationCapabilities(requiredOperations?: CollectionOperation[]): MdbaseAuthorizationCapabilities;
81
88
  hasOperations(requiredOperations: CollectionOperation[]): boolean;
82
89
  authorize(options?: MdbaseConnectionAuthorizeOptions): Promise<ConnectOutcome<MdbaseAuthorizationOutcome<Frontmatter>, AuthorizationProblemCode>>;
90
+ requestCapabilities(capabilities: ApplicationCapabilityId[], options?: MdbaseConnectionAuthorizeOptions): Promise<ConnectOutcome<MdbaseAuthorizationOutcome<Frontmatter> | {
91
+ kind: "unchanged";
92
+ connection: MdbaseConnection<Frontmatter>;
93
+ }, AuthorizationProblemCode>>;
83
94
  requestOperations(requiredOperations: CollectionOperation[], options?: Omit<MdbaseConnectionAuthorizeOptions, "operations">): Promise<ConnectOutcome<MdbaseAuthorizationOutcome<Frontmatter> | {
84
95
  kind: "unchanged";
85
96
  connection: MdbaseConnection<Frontmatter>;
@@ -1,3 +1,5 @@
1
+ import { capabilityOperationsForContractVersion } from "@mdbase-dev/connect-protocol";
2
+ import { retainedCapabilities, semanticContractVersion, validateAuthorizationSelection } from "./application-contract.js";
1
3
  import { MdbaseCollectionClient } from "./collection-client.js";
2
4
  import { ConnectionNotifications } from "./connection-notifications.js";
3
5
  import { ConnectionTransport } from "./connection-transport.js";
@@ -106,18 +108,43 @@ export class MdbaseConnection {
106
108
  target: { kind: "collection", collectionId: this.collectionId }
107
109
  }), AUTHORIZATION_PROBLEM_CODES);
108
110
  }
111
+ async requestCapabilities(capabilities, options = {}) {
112
+ return captureConnectOutcome(async () => {
113
+ const application = await this.internals.register(options);
114
+ validateAuthorizationSelection(application.requirements, { ...options, capabilities });
115
+ const sufficient = this.info() !== null && capabilities.every((capability) => (capabilityOperationsForContractVersion(semanticContractVersion(application.requirements), capability) ?? [])
116
+ .every((operation) => this.operations.includes(operation)));
117
+ if (sufficient)
118
+ return { kind: "unchanged", connection: this };
119
+ if (semanticContractVersion(application.requirements) === 1) {
120
+ const { capabilities: _selection, ...legacyOptions } = options;
121
+ return this.internals.authorize({
122
+ ...legacyOptions,
123
+ target: { kind: "collection", collectionId: this.collectionId },
124
+ operations: [...new Set([...this.operations, ...capabilities.flatMap((id) => capabilityOperationsForContractVersion(1, id) ?? [])])]
125
+ });
126
+ }
127
+ const retained = retainedCapabilities(application.requirements, this.operations);
128
+ return this.internals.authorize({
129
+ ...options,
130
+ target: { kind: "collection", collectionId: this.collectionId },
131
+ capabilities: [...new Set([...retained, ...capabilities])]
132
+ });
133
+ }, AUTHORIZATION_PROBLEM_CODES);
134
+ }
109
135
  async requestOperations(requiredOperations, options = {}) {
110
- const capabilities = this.authorizationCapabilities(requiredOperations);
111
- if (capabilities.sufficient) {
112
- return connectSuccess({ kind: "unchanged", connection: this });
113
- }
114
- return this.authorize({
115
- ...options,
116
- operations: uniqueOperations([
117
- ...capabilities.grantedOperations,
118
- ...capabilities.missingOperations
119
- ])
120
- });
136
+ return captureConnectOutcome(async () => {
137
+ const application = await this.internals.register(options);
138
+ validateAuthorizationSelection(application.requirements, { ...options, operations: requiredOperations });
139
+ const authorization = this.authorizationCapabilities(requiredOperations);
140
+ if (authorization.sufficient)
141
+ return { kind: "unchanged", connection: this };
142
+ return this.internals.authorize({
143
+ ...options,
144
+ target: { kind: "collection", collectionId: this.collectionId },
145
+ operations: [...new Set([...authorization.grantedOperations, ...requiredOperations])]
146
+ });
147
+ }, AUTHORIZATION_PROBLEM_CODES);
121
148
  }
122
149
  onConnectionChange(listener) {
123
150
  this.connectionListeners.add(listener);
package/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@ export { MdbaseConnection } from "./connection.js";
7
7
  export type { MdbaseAuthorizationTarget, MdbaseAuthorizeOptions, MdbaseConnectionAuthorizeOptions, MdbaseConnectEnvironment, MdbaseAuthorizationResult, MdbaseAuthorizationOutcome } from "./connection.js";
8
8
  export type { MdbaseConnectionRoute, DirectAccessStatus, MdbaseConnectionInfo, MdbaseAuthorizationCapabilities, MdbaseSyncTransport, MdbaseSyncConnection } from "./connection-types.js";
9
9
  export { effectiveCapabilities } from "./capabilities.js";
10
+ export type { MdbaseStructuredReadiness } from "./structured-readiness.js";
10
11
  export type { MdbaseCapabilityState, MdbaseCapabilityEvidence, MdbaseEffectiveCapability, MdbaseEffectiveCapabilities } from "./capabilities.js";
11
12
  export { MdbaseConnectError, isRetryableConnectError } from "./errors.js";
12
13
  export type { ConnectErrorContext } from "./errors.js";
@@ -22,4 +23,5 @@ export { MdbaseBrowserSelection, MdbaseMemorySelection } from "./selection.js";
22
23
  export type { MdbaseSelectionHistory, MdbaseApplicationSelection, MdbaseBrowserSelectionOptions } from "./selection.js";
23
24
  export type { MdbaseUnavailableReason } from "./session.js";
24
25
  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";
26
+ export type { MdbaseApplicationManifest, MdbaseApplicationManifest as MdbaseAppManifest, MdbaseApplicationRequirements, MdbaseApplicationCapabilityId as ApplicationCapabilityId, MdbaseApplicationCapabilityRequirements as ApplicationCapabilityRequirements } from "./application-contract.js";
27
+ export type { ApplicationProvisions, ApplicationRequirements, LegacyApplicationRequirements, LegacyApplicationCapabilityId, LegacyApplicationCapabilityRequirements, LegacyMdbaseAppManifest, ApplicationNotifications, ApplicationAuthorizationBinding, ApplicationAuthorizationProof, FileAction, FileCapability, CollectionOperation as MdbaseOperation, CollectionTypeDocument, ConnectOperationOutcome, ConnectProblem, ConnectProblemCategory, ConnectProblemCode, ConnectProblemDetailsByCode, ConnectRecoveryAction, ContractRequirement, GrantScope, JsonObject, NotificationCriterion, MdbaseDiagnostic, MdbaseOperationEnvelope, KnownConnectProblem, TypePackManifest, TypePackManifestResource, TypePackProvision, TypePackSourceResource, UnknownConnectProblem } from "@mdbase-dev/connect-protocol";
@@ -1,4 +1,5 @@
1
- import type { ApplicationNotifications, ApplicationRequirements, CollectionOperation, EncryptedRelayOperationRequest, GrantEncryption, FileCapability, GrantScope, MdbaseOperationRequest, MutationOperationIdentifier } from "@mdbase-dev/connect-protocol";
1
+ import type { ApplicationNotifications, ApplicationProvisions, CollectionOperation, EncryptedRelayOperationRequest, GrantEncryption, FileCapability, GrantScope, MdbaseOperationRequest, MutationOperationIdentifier } from "@mdbase-dev/connect-protocol";
2
+ import type { MdbaseApplicationRequirements as ApplicationRequirements } from "./application-contract.js";
2
3
  import { encryptRelayRequest } from "./crypto.js";
3
4
  export interface Application {
4
5
  id: string;
@@ -10,6 +11,7 @@ export interface Application {
10
11
  project_url?: string;
11
12
  notifications?: ApplicationNotifications;
12
13
  requirements: ApplicationRequirements;
14
+ provisions?: ApplicationProvisions;
13
15
  }
14
16
  export interface StoredAuthorization {
15
17
  version: 1;
@@ -95,4 +97,4 @@ export interface OperationAttempt {
95
97
  pendingMutationRecord?: PendingMutation;
96
98
  resumingMutation?: boolean;
97
99
  }
98
- export declare const DEFAULT_OPERATIONS: CollectionOperation[];
100
+ export { DEFAULT_OPERATIONS } from "./application-contract.js";
@@ -1 +1 @@
1
- export const DEFAULT_OPERATIONS = ["describe", "changes", "read", "query"];
1
+ export { DEFAULT_OPERATIONS } from "./application-contract.js";
@@ -14,7 +14,7 @@ export declare class MdbaseConnect<Frontmatter extends JsonObject = JsonObject>
14
14
  authorize(options?: MdbaseAuthorizeOptions): Promise<ConnectOutcome<MdbaseAuthorizationOutcome<Frontmatter>, AuthorizationProblemCode>>;
15
15
  /** The ordinary application lifecycle entry point. */
16
16
  application(options: MdbaseApplicationSessionOptions): MdbaseApplicationSession<Frontmatter>;
17
- manifest(options?: ConnectRequestOptions): Promise<ConnectOutcome<import("@mdbase-dev/connect-protocol").MdbaseAppManifest, RegistrationProblemCode>>;
17
+ manifest(options?: ConnectRequestOptions): Promise<ConnectOutcome<import("./application-contract.js").MdbaseApplicationManifest, RegistrationProblemCode>>;
18
18
  environment(): MdbaseConnectEnvironment;
19
19
  completeAuthorization(callbackUrl?: string | URL, options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseAuthorizationResult<Frontmatter>, AuthorizationProblemCode>>;
20
20
  connections(): MdbaseConnectionInfo[];
@@ -79,6 +79,7 @@ export class CollectionRequestCoordinator {
79
79
  if (queued.started || queued.settled)
80
80
  return;
81
81
  queued.settled = true;
82
+ queue.splice(queue.indexOf(queued), 1);
82
83
  queued.reject(requestAbortReason(combined.signal));
83
84
  combined.dispose();
84
85
  this.releaseLatest(coordination.family, replacement);
@@ -1,7 +1,14 @@
1
- import type { FileCapability, GrantEncryption, GrantScope, MdbaseAppManifest } from "@mdbase-dev/connect-protocol";
1
+ import type { FileCapability, GrantEncryption, GrantScope } from "@mdbase-dev/connect-protocol";
2
+ import type { MdbaseApplicationManifest as MdbaseAppManifest } from "./application-contract.js";
2
3
  import { MdbaseConnectError } from "./errors.js";
3
4
  import type { StoredToken } from "./internal-types.js";
4
5
  import type { MdbaseDeviceAuthorization } from "./authorization-types.js";
6
+ /** Resolve bundled declarations and their stable storage identity without loading URLs. */
7
+ export declare function resolveManifestSource(input?: MdbaseAppManifest | string | URL, redirect?: string | URL): {
8
+ manifest: MdbaseAppManifest | string;
9
+ manifestSource: string;
10
+ redirectUri: string;
11
+ };
5
12
  type NetworkProblemCode = "notification_registration_failed" | "notification_unregistration_failed" | "notifications_unavailable" | "temporarily_unavailable";
6
13
  /** Normalize fetch rejection at the exact I/O boundary. */
7
14
  export declare function connectFetch(input: RequestInfo | URL, init: RequestInit | undefined, code: NetworkProblemCode, message: string): Promise<Response>;
@@ -2,6 +2,19 @@ import { validateGrantEncryption } from "./crypto.js";
2
2
  import { connectError, serverConnectError } from "./errors.js";
3
3
  import { canonicalJson } from "./operation-helpers.js";
4
4
  import { bytesToBase64Url, randomBase64Url } from "./base64.js";
5
+ /** Resolve bundled declarations and their stable storage identity without loading URLs. */
6
+ export function resolveManifestSource(input, redirect) {
7
+ const manifest = input instanceof URL ? input.href : input ?? defaultManifestSource();
8
+ const manifestSource = typeof manifest === "string"
9
+ ? manifest
10
+ : manifest.distribution === "portable"
11
+ ? `bundle:${manifest.id}:${manifestStorageFingerprint(manifest)}`
12
+ : `bundle:${manifest.id}`;
13
+ const redirectUri = redirect === undefined ? (typeof manifest !== "string" && manifest.distribution === "portable"
14
+ ? ""
15
+ : defaultRedirectUri()) : String(redirect);
16
+ return { manifest, manifestSource, redirectUri };
17
+ }
5
18
  /** Normalize fetch rejection at the exact I/O boundary. */
6
19
  export async function connectFetch(input, init, code, message) {
7
20
  try {
package/dist/session.d.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import type { CollectionOperation, ConnectProblem, JsonObject } from "@mdbase-dev/connect-protocol";
2
+ import { type MdbaseApplicationManifest, type MdbaseApplicationCapabilityId as ApplicationCapabilityId } from "./application-contract.js";
2
3
  import { MdbaseConnection, type MdbaseAuthorizationOutcome, type MdbaseAuthorizationResult, type MdbaseAuthorizeOptions } from "./connection.js";
3
4
  import type { MdbaseAuthorizationCapabilities, MdbaseConnectionInfo } from "./connection-types.js";
4
- import { type AuthorizationProblemCode, type ConnectOutcome, type SessionProblemCode } from "./outcomes.js";
5
+ import { type AuthorizationProblemCode, type RegistrationProblemCode, type ConnectOutcome, type SessionProblemCode } from "./outcomes.js";
5
6
  import type { MdbaseSelectionHistory, MdbaseApplicationSelection } from "./selection.js";
6
7
  import type { ConnectRequestOptions } from "./operation-types.js";
7
8
  export interface MdbaseSessionConnect<Frontmatter extends JsonObject> {
9
+ manifest?(options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseApplicationManifest, RegistrationProblemCode>>;
8
10
  authorize(options?: MdbaseAuthorizeOptions): Promise<ConnectOutcome<MdbaseAuthorizationOutcome<Frontmatter>, AuthorizationProblemCode>>;
9
11
  completeAuthorization(callbackUrl: string, options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseAuthorizationResult<Frontmatter>, AuthorizationProblemCode>>;
10
12
  connections(): MdbaseConnectionInfo[];
@@ -87,11 +89,16 @@ export declare class MdbaseSession<Frontmatter extends JsonObject = JsonObject>
87
89
  forget(collectionId: string): ConnectOutcome<void, SessionProblemCode>;
88
90
  authorize(target: "choose" | "selected" | {
89
91
  collectionId: string;
90
- }, options?: Omit<MdbaseAuthorizeOptions, "operations" | "returnTo" | "target">): Promise<ConnectOutcome<MdbaseAuthorizationOutcome<Frontmatter>, SessionProblemCode>>;
91
- ensureOperations(requiredOperations: CollectionOperation[], options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseAuthorizationOutcome<Frontmatter> | {
92
+ }, options?: Omit<MdbaseAuthorizeOptions, "returnTo" | "target">): Promise<ConnectOutcome<MdbaseAuthorizationOutcome<Frontmatter>, SessionProblemCode>>;
93
+ ensureOperations(requiredOperations: CollectionOperation[], options?: ConnectRequestOptions): Promise<ConnectOutcome<{
92
94
  kind: "unchanged";
93
95
  connection: MdbaseConnection<Frontmatter>;
94
- }, SessionProblemCode>>;
96
+ } | MdbaseAuthorizationOutcome<Frontmatter>, SessionProblemCode>>;
97
+ ensureCapabilities(requiredCapabilities: ApplicationCapabilityId[], options?: ConnectRequestOptions): Promise<ConnectOutcome<{
98
+ kind: "unchanged";
99
+ connection: MdbaseConnection<Frontmatter>;
100
+ } | MdbaseAuthorizationOutcome<Frontmatter>, SessionProblemCode>>;
101
+ private ensureAuthorization;
95
102
  handleAuthorizationCallback(callbackUrl: string, options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseConnection<Frontmatter>, SessionProblemCode>>;
96
103
  private completeAuthorizationCallback;
97
104
  private beginCallbackCompletion;
package/dist/session.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { APPLICATION_CAPABILITY_DEFINITIONS, capabilityOperationsForContractVersion } from "@mdbase-dev/connect-protocol";
2
+ import { retainedCapabilities, semanticContractVersion, validateAuthorizationSelection } from "./application-contract.js";
1
3
  import { authorizationCallbackState, authorizationReturnToFromProblem, isAuthorizationCallbackUrl } from "./authorization-url.js";
2
4
  import { MdbaseConnectError, connectProblem } from "./errors.js";
3
5
  import { DEFAULT_OPERATIONS } from "./internal-types.js";
@@ -221,9 +223,16 @@ export class MdbaseSession {
221
223
  const operation = this.beginLifecycleOperation(options);
222
224
  this.transactionDepth += 1;
223
225
  try {
226
+ const declaration = await this.connect.manifest?.(operation.options);
227
+ if (declaration && !declaration.ok)
228
+ return declaration;
229
+ if (!this.lifecycleCurrent(operation.generation))
230
+ return this.destroyedOutcome(connectSuccess(undefined));
231
+ const version = declaration ? semanticContractVersion(declaration.value.requirements) : 1;
224
232
  const outcome = await this.connect.authorize({
225
233
  ...operation.options,
226
- operations: this.operations,
234
+ ...(version === 1 && options.operations === undefined && options.capabilities === undefined
235
+ ? { operations: this.operations } : {}),
227
236
  target: targetCollectionId === null
228
237
  ? { kind: "choose" }
229
238
  : { kind: "collection", collectionId: targetCollectionId },
@@ -242,7 +251,13 @@ export class MdbaseSession {
242
251
  this.refresh();
243
252
  }
244
253
  }
245
- async ensureOperations(requiredOperations, options) {
254
+ ensureOperations(requiredOperations, options) {
255
+ return this.ensureAuthorization({ operations: requiredOperations }, options);
256
+ }
257
+ ensureCapabilities(requiredCapabilities, options) {
258
+ return this.ensureAuthorization({ capabilities: requiredCapabilities }, options);
259
+ }
260
+ async ensureAuthorization(selection, options) {
246
261
  const lifecycle = this.lifecycleProblem();
247
262
  if (lifecycle)
248
263
  return connectFailure(lifecycle);
@@ -250,17 +265,30 @@ export class MdbaseSession {
250
265
  if (current.status !== "ready") {
251
266
  return connectFailure(connectProblem("collection_not_ready", "Choose an authorized collection first."));
252
267
  }
253
- const required = uniqueOperations(requiredOperations);
254
- const capabilities = current.connection.authorizationCapabilities(required);
255
- if (capabilities.sufficient) {
256
- return connectSuccess({ kind: "unchanged", connection: current.connection });
257
- }
258
268
  const operation = this.beginLifecycleOperation(options);
259
269
  this.transactionDepth += 1;
260
270
  try {
271
+ const declaration = await this.connect.manifest?.(operation.options);
272
+ if (declaration && !declaration.ok)
273
+ return declaration;
274
+ if (!this.lifecycleCurrent(operation.generation))
275
+ return this.destroyedOutcome(connectSuccess(undefined));
276
+ // Custom advanced session adapters predating manifest() retain exact v1
277
+ // operations. The concrete SDK always supplies its declared manifest.
278
+ const requirements = declaration?.value.requirements ?? ("operations" in selection
279
+ ? { access: "full_collection", contracts: [] }
280
+ : { access: "full_collection", contracts: [], capabilities: { contract_version: 2, required: [], optional: Object.keys(APPLICATION_CAPABILITY_DEFINITIONS) } });
281
+ validateAuthorizationSelection(requirements, { ...options, ...selection });
282
+ const requiredOperations = "operations" in selection ? selection.operations : selection.capabilities.flatMap((id) => capabilityOperationsForContractVersion(semanticContractVersion(requirements), id) ?? []);
283
+ const authorization = current.connection.authorizationCapabilities(requiredOperations);
284
+ if (authorization.sufficient)
285
+ return connectSuccess({ kind: "unchanged", connection: current.connection });
286
+ const incremental = "operations" in selection || semanticContractVersion(requirements) === 1
287
+ ? { operations: uniqueOperations([...authorization.grantedOperations, ...requiredOperations]) }
288
+ : { capabilities: [...new Set([...retainedCapabilities(requirements, authorization.grantedOperations), ...selection.capabilities])] };
261
289
  const outcome = await this.connect.authorize({
262
290
  ...operation.options,
263
- operations: uniqueOperations([...capabilities.grantedOperations, ...capabilities.missingOperations]),
291
+ ...incremental,
264
292
  target: { kind: "collection", collectionId: current.collectionId },
265
293
  returnTo: this.selection.authorizationReturnTo()
266
294
  });
@@ -271,6 +299,11 @@ export class MdbaseSession {
271
299
  }
272
300
  return outcome;
273
301
  }
302
+ catch (error) {
303
+ if (error instanceof MdbaseConnectError)
304
+ return connectFailure(error.problem);
305
+ throw error;
306
+ }
274
307
  finally {
275
308
  operation.dispose();
276
309
  this.transactionDepth -= 1;