@mdbase-dev/connect 0.1.0-beta.94 → 0.1.0-beta.96
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.
- package/README.md +48 -13
- package/dist/application-contract.d.ts +22 -0
- package/dist/application-contract.js +75 -0
- package/dist/application-session.d.ts +10 -10
- package/dist/application-session.js +74 -52
- package/dist/browser/integrity.json +1 -1
- package/dist/browser/mdbase-connect.min.js +3 -3
- package/dist/browser/mdbase-connect.min.js.sha384 +1 -1
- package/dist/capabilities.d.ts +4 -3
- package/dist/capabilities.js +15 -31
- package/dist/connect-authorization-helpers.d.ts +1 -0
- package/dist/connect-authorization-helpers.js +12 -0
- package/dist/connect-options.d.ts +2 -1
- package/dist/connect.d.ts +2 -1
- package/dist/connect.js +25 -25
- package/dist/connection.d.ts +11 -0
- package/dist/connection.js +38 -11
- package/dist/index.d.ts +3 -1
- package/dist/internal-types.d.ts +4 -2
- package/dist/internal-types.js +1 -1
- package/dist/mdbase-connect.d.ts +1 -1
- package/dist/operation-types.d.ts +4 -0
- package/dist/query-pagination.js +6 -3
- package/dist/request-coordinator.js +1 -0
- package/dist/runtime-utils.d.ts +8 -1
- package/dist/runtime-utils.js +13 -0
- package/dist/session.d.ts +11 -4
- package/dist/session.js +41 -8
- package/dist/structured-readiness.d.ts +77 -0
- package/dist/structured-readiness.js +126 -0
- package/package.json +2 -2
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
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { ContractRequirement, FileAction, JsonObject } from "@mdbase-dev/connect-protocol";
|
|
2
|
+
import type { MdbaseApplicationManifest } from "./application-contract.js";
|
|
3
|
+
import type { MdbaseConnectionInfo } from "./connection-types.js";
|
|
4
|
+
import { type MdbaseEffectiveCapabilities, type MdbaseCapabilityEvidence } from "./capabilities.js";
|
|
5
|
+
import type { MdbaseConnection } from "./connection.js";
|
|
6
|
+
import type { ConnectRequestOptions } from "./operation-types.js";
|
|
7
|
+
type ReadinessPublication = {
|
|
8
|
+
status: "authorization_required";
|
|
9
|
+
} | {
|
|
10
|
+
status: "blocked";
|
|
11
|
+
problem: {
|
|
12
|
+
code: string;
|
|
13
|
+
message: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
/** Live verification owns no session state; callers supply identity and cancellation guards. */
|
|
17
|
+
export declare function verifyStructuredReadiness<Frontmatter extends JsonObject>(input: {
|
|
18
|
+
collectionId: string;
|
|
19
|
+
readiness: MdbaseStructuredReadiness;
|
|
20
|
+
contracts: ContractRequirement[];
|
|
21
|
+
connection: MdbaseConnection<Frontmatter>;
|
|
22
|
+
controller: AbortController;
|
|
23
|
+
options?: ConnectRequestOptions;
|
|
24
|
+
requestMs: number | null;
|
|
25
|
+
isCurrent: () => boolean;
|
|
26
|
+
publish: (result: ReadinessPublication) => void;
|
|
27
|
+
}): Promise<boolean>;
|
|
28
|
+
export declare function publishedStructuredReadiness(source: MdbaseStructuredReadiness, status: string, verification?: "cached" | "verified"): MdbaseStructuredReadiness;
|
|
29
|
+
export interface ApplicationSessionContext {
|
|
30
|
+
collectionId: string;
|
|
31
|
+
info: MdbaseConnectionInfo;
|
|
32
|
+
capabilities: MdbaseEffectiveCapabilities;
|
|
33
|
+
/** Present only for semantic contract v2. */
|
|
34
|
+
readiness?: MdbaseStructuredReadiness;
|
|
35
|
+
connections: MdbaseConnectionInfo[];
|
|
36
|
+
}
|
|
37
|
+
export declare function applicationReadinessContext(manifest: MdbaseApplicationManifest, collectionId: string, info: MdbaseConnectionInfo, connections: MdbaseConnectionInfo[]): ApplicationSessionContext;
|
|
38
|
+
export declare function verificationKey(manifest: MdbaseApplicationManifest, collectionId: string): string;
|
|
39
|
+
export declare function verificationValue(manifest: MdbaseApplicationManifest): string;
|
|
40
|
+
export declare function collectionSetupInput(manifest: MdbaseApplicationManifest, applicationId: string, declarationDigest: string, typePackAdoptions?: Record<string, Record<string, string>>): {
|
|
41
|
+
applicationId: string;
|
|
42
|
+
declarationDigest: string;
|
|
43
|
+
requirements: {
|
|
44
|
+
configuration: import("@mdbase-dev/connect-protocol").ConfigurationRequirement[];
|
|
45
|
+
};
|
|
46
|
+
provisions: {
|
|
47
|
+
configuration: import("@mdbase-dev/connect-protocol").ConfigurationProvision[];
|
|
48
|
+
typePacks: import("@mdbase-dev/connect-protocol").TypePackProvision[];
|
|
49
|
+
};
|
|
50
|
+
typePackAdoptions?: Record<string, Record<string, string>> | undefined;
|
|
51
|
+
};
|
|
52
|
+
/** Independent v2 declarations; never legacy capability aliases. */
|
|
53
|
+
export interface MdbaseStructuredReadiness {
|
|
54
|
+
files: {
|
|
55
|
+
state: "not_required" | "available" | "requires_authorization";
|
|
56
|
+
missingRequiredActions: FileAction[];
|
|
57
|
+
missingOptionalActions: FileAction[];
|
|
58
|
+
scopeMatches: boolean;
|
|
59
|
+
evidence: MdbaseCapabilityEvidence[];
|
|
60
|
+
};
|
|
61
|
+
contracts: {
|
|
62
|
+
state: "not_required" | "checking" | "verified" | "requires_setup" | "requires_authorization" | "temporarily_unavailable";
|
|
63
|
+
missing: ContractRequirement[];
|
|
64
|
+
evidence: MdbaseCapabilityEvidence[];
|
|
65
|
+
};
|
|
66
|
+
setup: {
|
|
67
|
+
state: "not_required" | "checking" | "review_required" | "current" | "cached" | "blocked" | "requires_authorization";
|
|
68
|
+
evidence: MdbaseCapabilityEvidence[];
|
|
69
|
+
};
|
|
70
|
+
notifications: {
|
|
71
|
+
state: "not_required" | "requires_registration";
|
|
72
|
+
registration: "unverified";
|
|
73
|
+
evidence: MdbaseCapabilityEvidence[];
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export declare function structuredReadiness(manifest: MdbaseApplicationManifest, info: MdbaseConnectionInfo): MdbaseStructuredReadiness;
|
|
77
|
+
export {};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/* The versioned protocol package is the sole capability-to-operation compiler. */
|
|
2
|
+
import { effectiveCapabilities } from "./capabilities.js";
|
|
3
|
+
import { MdbaseConnectError } from "./errors.js";
|
|
4
|
+
import { withRequestBudget } from "./request-budget.js";
|
|
5
|
+
/** Live verification owns no session state; callers supply identity and cancellation guards. */
|
|
6
|
+
export async function verifyStructuredReadiness(input) {
|
|
7
|
+
const { collectionId, readiness, contracts, connection, controller, options, requestMs, isCurrent, publish } = input;
|
|
8
|
+
const abort = () => controller.abort(options?.signal?.reason);
|
|
9
|
+
if (options?.signal?.aborted)
|
|
10
|
+
abort();
|
|
11
|
+
else
|
|
12
|
+
options?.signal?.addEventListener("abort", abort, { once: true });
|
|
13
|
+
try {
|
|
14
|
+
const outcome = await withRequestBudget({ ...options, signal: controller.signal }, requestMs, request => connection.describe(request));
|
|
15
|
+
if (!isCurrent())
|
|
16
|
+
return false;
|
|
17
|
+
if (!outcome.ok) {
|
|
18
|
+
const authorizationRequired = ["access_denied", "collection_access_denied", "insufficient_access", "not_authorized", "authorization_expired", "application_declaration_mismatch"].includes(outcome.problem.code);
|
|
19
|
+
readiness.contracts = { state: authorizationRequired ? "requires_authorization" : "temporarily_unavailable", missing: [], evidence: [{ source: "runtime", fact: outcome.problem.message }] };
|
|
20
|
+
publish(authorizationRequired
|
|
21
|
+
? { status: "authorization_required" }
|
|
22
|
+
: { status: "blocked", problem: outcome.problem });
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
if (outcome.value.collectionId !== collectionId) {
|
|
26
|
+
readiness.contracts = { state: "temporarily_unavailable", missing: [], evidence: [] };
|
|
27
|
+
publish({ status: "blocked", problem: { code: "collection_not_ready", message: "Description belongs to a different collection." } });
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const missing = contracts.filter(required => !outcome.value.contracts.some(actual => actual.id === required.id && actual.version === required.version && actual.digest === required.digest));
|
|
31
|
+
readiness.contracts = { state: missing.length ? "requires_setup" : "verified", missing,
|
|
32
|
+
evidence: [{ source: "authority", fact: "Exact contract ID, version and digest checked by live describe for this collection." }] };
|
|
33
|
+
if (missing.length) {
|
|
34
|
+
publish({ status: "blocked", problem: { code: "collection_not_ready", message: "The collection is missing required exact contracts." } });
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (!isCurrent())
|
|
40
|
+
return false;
|
|
41
|
+
if (!(error instanceof MdbaseConnectError))
|
|
42
|
+
throw error;
|
|
43
|
+
readiness.contracts = { state: "temporarily_unavailable", missing: [], evidence: [{ source: "runtime", fact: error.problem.message }] };
|
|
44
|
+
publish({ status: "blocked", problem: error.problem });
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
options?.signal?.removeEventListener("abort", abort);
|
|
49
|
+
}
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
export function publishedStructuredReadiness(source, status, verification) {
|
|
53
|
+
const readiness = structuredClone(source);
|
|
54
|
+
if (readiness.setup.state !== "not_required" && readiness.setup.state !== "current") {
|
|
55
|
+
readiness.setup.state = status === "ready" ? (verification === "cached" ? "cached" : "current")
|
|
56
|
+
: status === "setup_review_required" ? "review_required"
|
|
57
|
+
: status === "authorization_required" ? "requires_authorization"
|
|
58
|
+
: status === "blocked" ? "blocked" : "checking";
|
|
59
|
+
readiness.setup.evidence = [{ source: status === "ready" && verification === "cached" ? "runtime" : "authority",
|
|
60
|
+
fact: `Collection setup verification: ${readiness.setup.state}.` }];
|
|
61
|
+
}
|
|
62
|
+
return readiness;
|
|
63
|
+
}
|
|
64
|
+
export function applicationReadinessContext(manifest, collectionId, info, connections) {
|
|
65
|
+
return {
|
|
66
|
+
collectionId, info,
|
|
67
|
+
capabilities: effectiveCapabilities(manifest.requirements.capabilities, manifest, info),
|
|
68
|
+
...(manifest.requirements?.capabilities?.contract_version === 2
|
|
69
|
+
? { readiness: structuredReadiness(manifest, info) } : {}),
|
|
70
|
+
connections
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export function verificationKey(manifest, collectionId) {
|
|
74
|
+
return `mdbase-application-session:v1:${manifest.id}:${collectionId}`;
|
|
75
|
+
}
|
|
76
|
+
export function verificationValue(manifest) {
|
|
77
|
+
return JSON.stringify({
|
|
78
|
+
capabilities: manifest.requirements?.capabilities,
|
|
79
|
+
requirements: manifest.requirements?.configuration ?? [],
|
|
80
|
+
provisions: manifest.provisions ?? {}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
export function collectionSetupInput(manifest, applicationId, declarationDigest, typePackAdoptions) {
|
|
84
|
+
return {
|
|
85
|
+
applicationId, declarationDigest,
|
|
86
|
+
requirements: { configuration: manifest.requirements?.configuration ?? [] },
|
|
87
|
+
provisions: {
|
|
88
|
+
configuration: manifest.provisions?.configuration ?? [],
|
|
89
|
+
typePacks: manifest.provisions?.type_packs ?? []
|
|
90
|
+
},
|
|
91
|
+
...(typePackAdoptions ? { typePackAdoptions } : {})
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function sameScope(left, right) {
|
|
95
|
+
if (!right || left.kind !== right.kind)
|
|
96
|
+
return false;
|
|
97
|
+
if (left.kind === "collection" || right.kind === "collection")
|
|
98
|
+
return true;
|
|
99
|
+
const a = [...new Set(left.folders)].sort();
|
|
100
|
+
const b = [...new Set(right.folders)].sort();
|
|
101
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
102
|
+
}
|
|
103
|
+
export function structuredReadiness(manifest, info) {
|
|
104
|
+
const files = manifest.requirements?.files;
|
|
105
|
+
const requirement = files && "required" in files ? files : undefined;
|
|
106
|
+
const scopeMatches = !requirement || sameScope(requirement.scope, info.fileCapability?.scope);
|
|
107
|
+
const missing = (actions) => actions.filter(action => !scopeMatches || !info.fileCapability?.actions.includes(action));
|
|
108
|
+
const missingRequiredActions = missing(requirement?.required ?? []);
|
|
109
|
+
const missingOptionalActions = missing(requirement?.optional ?? []);
|
|
110
|
+
const contracts = manifest.requirements?.contracts ?? [];
|
|
111
|
+
const setup = (manifest.provisions?.type_packs.length ?? 0) > 0 || (manifest.provisions?.configuration?.length ?? 0) > 0;
|
|
112
|
+
return {
|
|
113
|
+
files: {
|
|
114
|
+
state: !requirement ? "not_required" : missingRequiredActions.length ? "requires_authorization" : "available",
|
|
115
|
+
missingRequiredActions, missingOptionalActions, scopeMatches,
|
|
116
|
+
evidence: [{ source: "authorization", fact: "Compared exact file actions and folder scope with connection.info().fileCapability." }]
|
|
117
|
+
},
|
|
118
|
+
contracts: { state: contracts.length ? "checking" : "not_required", missing: [], evidence: [] },
|
|
119
|
+
setup: { state: setup ? "checking" : "not_required", evidence: [] },
|
|
120
|
+
notifications: {
|
|
121
|
+
state: manifest.notifications?.criteria.length ? "requires_registration" : "not_required",
|
|
122
|
+
registration: "unverified",
|
|
123
|
+
evidence: [{ source: "runtime", fact: "The connection API exposes registration commands but no current delivery-registration query. Manifest criteria do not prove approval or delivery registration." }]
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mdbase-dev/connect",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.96",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"README.md"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@mdbase-dev/connect-protocol": "0.1.0-beta.
|
|
36
|
+
"@mdbase-dev/connect-protocol": "0.1.0-beta.96"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"esbuild": "^0.28.1",
|