@mdbase-dev/connect 0.1.0-beta.32 → 0.1.0-beta.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -15
- package/dist/advanced.d.ts +2 -0
- package/dist/advanced.js +2 -0
- package/dist/application-session.d.ts +31 -11
- package/dist/application-session.js +188 -73
- 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/collection-client.d.ts +9 -6
- package/dist/collection-client.js +522 -77
- package/dist/connect-authorization-helpers.d.ts +3 -0
- package/dist/connect-authorization-helpers.js +13 -0
- package/dist/connect.js +4 -6
- package/dist/connection.d.ts +7 -6
- package/dist/connection.js +103 -89
- package/dist/crypto-entry.d.ts +6 -3
- package/dist/crypto-entry.js +3 -3
- package/dist/file-transfer-internals.d.ts +2 -1
- package/dist/file-transfer-internals.js +38 -0
- package/dist/files.d.ts +20 -2
- package/dist/files.js +43 -19
- package/dist/index.d.ts +24 -16
- package/dist/index.js +10 -14
- package/dist/internal-types.d.ts +1 -0
- package/dist/operation-helpers.js +5 -5
- package/dist/operation-types.d.ts +404 -39
- package/dist/session.d.ts +1 -1
- package/dist/session.js +2 -1
- package/dist/watch-policy.d.ts +8 -0
- package/dist/watch-policy.js +12 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -7,6 +7,13 @@ applications. See the [portable application guide](../../docs/portable-apps.md)
|
|
|
7
7
|
for the v1 manifest, device-code flow, version-pinned CDN URL, SRI metadata,
|
|
8
8
|
and `file://` storage boundary.
|
|
9
9
|
|
|
10
|
+
Ordinary applications install only `@mdbase-dev/connect`. Add
|
|
11
|
+
`@mdbase-dev/connect-dev` for declaration/type-pack authoring,
|
|
12
|
+
`@mdbase-dev/connect-testing` for supported test fixtures,
|
|
13
|
+
`@mdbase-dev/connect-sync` for an offline mirror, or the Pickle package only
|
|
14
|
+
when the application uses that feature. Wire and authority implementations use
|
|
15
|
+
`@mdbase-dev/connect-protocol`; ordinary application code does not.
|
|
16
|
+
|
|
10
17
|
The complete developer guide covers
|
|
11
18
|
[setup](https://mdbase.dev/sdk/quickstart/),
|
|
12
19
|
[manifests and contracts](https://mdbase.dev/sdk/manifest/),
|
|
@@ -33,7 +40,8 @@ const session = mdbase.application({
|
|
|
33
40
|
});
|
|
34
41
|
const unsubscribeSession = session.subscribe(() => render(session.getSnapshot()));
|
|
35
42
|
|
|
36
|
-
const
|
|
43
|
+
const startup = new AbortController();
|
|
44
|
+
const started = await session.start({ signal: startup.signal, timeoutMs: 20_000 });
|
|
37
45
|
if (!started.ok) {
|
|
38
46
|
renderProblem(started.problem);
|
|
39
47
|
return;
|
|
@@ -56,10 +64,15 @@ if (!queried.ok) {
|
|
|
56
64
|
return;
|
|
57
65
|
}
|
|
58
66
|
const workouts = queried.value;
|
|
67
|
+
const current = await connection.read({ path: workouts.results[0].path });
|
|
68
|
+
if (!current.ok) {
|
|
69
|
+
renderProblem(current.problem);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
59
72
|
const updated = await connection.update({
|
|
60
|
-
path:
|
|
73
|
+
path: current.value.path,
|
|
61
74
|
patch: { completed: true },
|
|
62
|
-
|
|
75
|
+
ifRevision: current.value.revision
|
|
63
76
|
});
|
|
64
77
|
if (!updated.ok) renderProblem(updated.problem);
|
|
65
78
|
|
|
@@ -85,6 +98,12 @@ reserved for programming errors and broken SDK invariants. See
|
|
|
85
98
|
[typed outcomes and recovery](../../docs/sdk-outcomes.md) for the complete
|
|
86
99
|
problem model, setup failures, mutation uncertainty, and UI guidance.
|
|
87
100
|
|
|
101
|
+
SDK call options, operation inputs, results, descriptors, and progress events
|
|
102
|
+
use camelCase. The client translates canonical snake_case protocol payloads at
|
|
103
|
+
the authority boundary. Versioned JSON documents such as `MdbaseAppManifest`
|
|
104
|
+
and `TypePackProvision`, problem detail objects, diagnostics, and user-owned
|
|
105
|
+
frontmatter retain the exact field names defined by their own schemas.
|
|
106
|
+
|
|
88
107
|
React applications can use the same session without introducing another state
|
|
89
108
|
owner:
|
|
90
109
|
|
|
@@ -132,16 +151,16 @@ versioned capabilities in their manifest; they never maintain a parallel array
|
|
|
132
151
|
of protocol operations. Use `getSnapshot()` and `subscribe()` directly or
|
|
133
152
|
through your framework's external-store integration.
|
|
134
153
|
|
|
135
|
-
The session distinguishes `authorization_required`, `
|
|
136
|
-
`
|
|
154
|
+
The session distinguishes `authorization_required`, `checking_setup`,
|
|
155
|
+
`setup_review_required`, `ready`, `unavailable`, and `blocked`. Setup
|
|
137
156
|
inspection is read-only. If an update is needed, render the supplied plan and
|
|
138
157
|
apply the exact assessment the user reviewed:
|
|
139
158
|
|
|
140
159
|
```ts
|
|
141
160
|
const snapshot = session.getSnapshot();
|
|
142
|
-
if (snapshot.status === "
|
|
143
|
-
|
|
144
|
-
const applied = await session.
|
|
161
|
+
if (snapshot.status === "setup_review_required") {
|
|
162
|
+
renderCollectionSetup(snapshot.update);
|
|
163
|
+
const applied = await session.applyCollectionSetup({ timeoutMs: 30_000 });
|
|
145
164
|
if (!applied.ok) renderProblem(applied.problem);
|
|
146
165
|
}
|
|
147
166
|
```
|
|
@@ -267,10 +286,10 @@ one-shot reminders at the collection authority:
|
|
|
267
286
|
```ts
|
|
268
287
|
await connection.reconcileTimers({
|
|
269
288
|
namespace: "workout-reminders",
|
|
270
|
-
|
|
289
|
+
criterionId: "workout.reminder",
|
|
271
290
|
timers: [{
|
|
272
291
|
id: "workout-42",
|
|
273
|
-
|
|
292
|
+
fireAt: new Date("2026-07-25T10:00:00Z").toISOString()
|
|
274
293
|
}]
|
|
275
294
|
});
|
|
276
295
|
```
|
|
@@ -311,7 +330,7 @@ if (!current.ok) return renderProblem(current.problem);
|
|
|
311
330
|
const updatedType = await connection.updateType({
|
|
312
331
|
path: current.value.path,
|
|
313
332
|
document: current.value.document.replace("version: 1", "version: 2"),
|
|
314
|
-
|
|
333
|
+
ifRevision: current.value.revision
|
|
315
334
|
});
|
|
316
335
|
if (!updatedType.ok) renderProblem(updatedType.problem);
|
|
317
336
|
```
|
|
@@ -329,15 +348,15 @@ const provision = await response.json();
|
|
|
329
348
|
const installedBy = "dev.example.catalog";
|
|
330
349
|
const assessment = await connection.assessTypePack({
|
|
331
350
|
provision,
|
|
332
|
-
|
|
351
|
+
installedBy
|
|
333
352
|
});
|
|
334
353
|
if (!assessment.ok) return renderProblem(assessment.problem);
|
|
335
354
|
|
|
336
355
|
renderDefinitionChanges(assessment.value);
|
|
337
356
|
const applied = await connection.applyTypePack({
|
|
338
357
|
provision,
|
|
339
|
-
|
|
340
|
-
|
|
358
|
+
installedBy,
|
|
359
|
+
expectedAssessmentDigest: assessment.value.assessmentDigest
|
|
341
360
|
});
|
|
342
361
|
if (!applied.ok) return renderProblem(applied.problem);
|
|
343
362
|
```
|
|
@@ -467,10 +486,13 @@ is not granted collection-wide type-management access.
|
|
|
467
486
|
|
|
468
487
|
The SDK returns typed outcomes, carries successful mdbase diagnostics alongside
|
|
469
488
|
the value, carries revision tokens in typed record results, and accepts
|
|
470
|
-
`
|
|
489
|
+
`ifRevision` on mutations. `describe()` exposes
|
|
471
490
|
JSON Schemas, portable type definitions, canonical collection settings, and
|
|
472
491
|
first-class data contracts. `watch()` resumes from a local collection cursor;
|
|
473
492
|
the Connect server does not store the change feed.
|
|
493
|
+
`queryAll()` treats `timeoutMs` as one deadline for the complete paginated
|
|
494
|
+
query. The caller-driven `queryPages()` iterator instead accepts
|
|
495
|
+
`pageTimeoutMs`, an independent budget for each page, plus a lifetime `signal`.
|
|
474
496
|
`preflightRename()` and `preflightDelete()` run the canonical collection
|
|
475
497
|
operation without changing records or advancing the change cursor, so an app
|
|
476
498
|
can show authoritative reference impact before asking for confirmation.
|
package/dist/advanced.d.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
export { MdbaseCollectionClient } from "./collection-client.js";
|
|
3
3
|
export type { MdbaseCollectionTransport } from "./operation-types.js";
|
|
4
4
|
export { createPkce } from "./runtime-utils.js";
|
|
5
|
+
export { connectError, connectProblem, operationProblem } from "./errors.js";
|
|
6
|
+
export { connectFailure, connectSuccess } from "./outcomes.js";
|
|
5
7
|
export type { MdbaseApplicationVerificationStore, MdbaseApplicationSessionConnect } from "./application-session.js";
|
|
6
8
|
export { MdbaseSession } from "./session.js";
|
|
7
9
|
export type { MdbaseSessionConnect, MdbaseSessionOptions } from "./session.js";
|
package/dist/advanced.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
/** Low-level construction and test seams. Ordinary applications use the root package. */
|
|
2
2
|
export { MdbaseCollectionClient } from "./collection-client.js";
|
|
3
3
|
export { createPkce } from "./runtime-utils.js";
|
|
4
|
+
export { connectError, connectProblem, operationProblem } from "./errors.js";
|
|
5
|
+
export { connectFailure, connectSuccess } from "./outcomes.js";
|
|
4
6
|
export { MdbaseSession } from "./session.js";
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { type ApplicationCapabilityId, type JsonObject, type MdbaseAppManifest
|
|
1
|
+
import { type ApplicationCapabilityId, type JsonObject, type MdbaseAppManifest } from "@mdbase-dev/connect-protocol";
|
|
2
2
|
import { type MdbaseEffectiveCapabilities } from "./capabilities.js";
|
|
3
3
|
import type { MdbaseAuthorizationOutcome, MdbaseAuthorizeOptions, MdbaseConnection } from "./connection.js";
|
|
4
4
|
import type { MdbaseConnectionInfo } from "./connection-types.js";
|
|
5
5
|
import { type ConnectOutcome, type CollectionTypeProblemCode, type AuthorizationProblemCode, type RegistrationProblemCode, type SessionProblemCode } from "./outcomes.js";
|
|
6
6
|
import type { MdbaseApplicationSelection, MdbaseSelectionHistory } from "./selection.js";
|
|
7
|
-
import type { ConnectRequestOptions } from "./operation-types.js";
|
|
7
|
+
import type { CollectionSetupAssessment, ConfigurationSetupAssessment, ConnectRequestOptions, TypePackAssessment } from "./operation-types.js";
|
|
8
8
|
import { type ResolvedConnectTimeouts } from "./request-budget.js";
|
|
9
|
+
import type { Application } from "./internal-types.js";
|
|
9
10
|
import { type MdbaseSessionConnect, type MdbaseUnavailableReason } from "./session.js";
|
|
10
11
|
export interface MdbaseApplicationSessionConnect<Frontmatter extends JsonObject = JsonObject> extends MdbaseSessionConnect<Frontmatter> {
|
|
12
|
+
register(options?: ConnectRequestOptions): Promise<ConnectOutcome<Application, RegistrationProblemCode>>;
|
|
11
13
|
manifest(options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseAppManifest, RegistrationProblemCode>>;
|
|
12
14
|
}
|
|
13
15
|
export interface MdbaseApplicationSessionOptions {
|
|
@@ -35,7 +37,18 @@ export interface MdbaseDefinitionUpdate {
|
|
|
35
37
|
currentVersion?: string;
|
|
36
38
|
desiredVersion: string;
|
|
37
39
|
resources: TypePackAssessment["resources"];
|
|
38
|
-
contractSetups: TypePackAssessment["
|
|
40
|
+
contractSetups: TypePackAssessment["contractSetups"];
|
|
41
|
+
canApply: boolean;
|
|
42
|
+
reason: string;
|
|
43
|
+
}
|
|
44
|
+
export interface MdbaseCollectionSetupUpdate {
|
|
45
|
+
status: CollectionSetupAssessment["status"];
|
|
46
|
+
applicable: boolean;
|
|
47
|
+
assessmentDigest: string;
|
|
48
|
+
collectionRevision: string;
|
|
49
|
+
provisionDigest: string;
|
|
50
|
+
configuration: ConfigurationSetupAssessment[];
|
|
51
|
+
typePacks: MdbaseDefinitionUpdate[];
|
|
39
52
|
canApply: boolean;
|
|
40
53
|
reason: string;
|
|
41
54
|
}
|
|
@@ -54,10 +67,10 @@ export type MdbaseApplicationSessionSnapshot = {
|
|
|
54
67
|
} | ({
|
|
55
68
|
status: "authorization_required";
|
|
56
69
|
} & ApplicationSessionContext) | ({
|
|
57
|
-
status: "
|
|
70
|
+
status: "checking_setup";
|
|
58
71
|
} & ApplicationSessionContext) | ({
|
|
59
|
-
status: "
|
|
60
|
-
|
|
72
|
+
status: "setup_review_required";
|
|
73
|
+
update: MdbaseCollectionSetupUpdate;
|
|
61
74
|
} & ApplicationSessionContext) | ({
|
|
62
75
|
status: "ready";
|
|
63
76
|
verification: "cached" | "verified";
|
|
@@ -77,7 +90,7 @@ export type MdbaseApplicationSessionSnapshot = {
|
|
|
77
90
|
/**
|
|
78
91
|
* The application-level lifecycle boundary. It derives authorization from the
|
|
79
92
|
* bundled manifest, exposes semantic capabilities, and never mutates collection
|
|
80
|
-
*
|
|
93
|
+
* setup without an explicit `applyCollectionSetup` call.
|
|
81
94
|
*/
|
|
82
95
|
export declare class MdbaseApplicationSession<Frontmatter extends JsonObject = JsonObject> {
|
|
83
96
|
private readonly connect;
|
|
@@ -87,14 +100,19 @@ export declare class MdbaseApplicationSession<Frontmatter extends JsonObject = J
|
|
|
87
100
|
private readonly verificationStore;
|
|
88
101
|
private snapshot;
|
|
89
102
|
private manifest;
|
|
103
|
+
private application;
|
|
90
104
|
private base;
|
|
91
105
|
private stopBase?;
|
|
92
|
-
private
|
|
106
|
+
private setupAssessment;
|
|
93
107
|
private verificationGeneration;
|
|
108
|
+
private lifecycleGeneration;
|
|
109
|
+
private startOperation;
|
|
94
110
|
constructor(connect: MdbaseApplicationSessionConnect<Frontmatter>, options: MdbaseApplicationSessionOptions, timeouts?: ResolvedConnectTimeouts);
|
|
95
111
|
start(options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseApplicationSessionSnapshot, SessionProblemCode>>;
|
|
112
|
+
private beginStart;
|
|
96
113
|
private startWithinBudget;
|
|
97
114
|
destroy(): void;
|
|
115
|
+
private cleanupBase;
|
|
98
116
|
getSnapshot(): MdbaseApplicationSessionSnapshot;
|
|
99
117
|
subscribe(listener: () => void): () => void;
|
|
100
118
|
connection(): MdbaseConnection<Frontmatter> | null;
|
|
@@ -110,16 +128,18 @@ export declare class MdbaseApplicationSession<Frontmatter extends JsonObject = J
|
|
|
110
128
|
}, options?: Omit<MdbaseAuthorizeOptions, "operations" | "returnTo" | "target">): Promise<ConnectOutcome<MdbaseAuthorizationOutcome<Frontmatter>, SessionProblemCode>>;
|
|
111
129
|
handleAuthorizationCallback(callbackUrl: string | URL, options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseConnection<Frontmatter>, AuthorizationProblemCode>>;
|
|
112
130
|
completeAuthorization(callbackUrl?: string | URL, options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseConnection<Frontmatter>, AuthorizationProblemCode>>;
|
|
113
|
-
ensureCapabilities(capabilities: ApplicationCapabilityId[]): Promise<ConnectOutcome<MdbaseAuthorizationOutcome<Frontmatter> | {
|
|
131
|
+
ensureCapabilities(capabilities: ApplicationCapabilityId[], options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseAuthorizationOutcome<Frontmatter> | {
|
|
114
132
|
kind: "unchanged";
|
|
115
133
|
connection: MdbaseConnection<Frontmatter>;
|
|
116
134
|
}, SessionProblemCode>>;
|
|
117
|
-
|
|
135
|
+
applyCollectionSetup(options?: ConnectRequestOptions): Promise<ConnectOutcome<MdbaseApplicationSessionSnapshot, CollectionTypeProblemCode | "collection_not_ready">>;
|
|
118
136
|
private refresh;
|
|
119
|
-
private
|
|
137
|
+
private verifySetup;
|
|
138
|
+
private collectionSetupInput;
|
|
120
139
|
private context;
|
|
121
140
|
private publish;
|
|
122
141
|
private requireBase;
|
|
123
142
|
private requireManifest;
|
|
143
|
+
private requireApplication;
|
|
124
144
|
}
|
|
125
145
|
export {};
|
|
@@ -3,7 +3,7 @@ import { capabilityOperations, operationsForApplicationCapabilities } from "@mdb
|
|
|
3
3
|
import { effectiveCapabilities } from "./capabilities.js";
|
|
4
4
|
import { connectProblem } from "./errors.js";
|
|
5
5
|
import { connectFailure, connectSuccess } from "./outcomes.js";
|
|
6
|
-
import { resolveConnectTimeouts, withRequestBudget } from "./request-budget.js";
|
|
6
|
+
import { createRequestBudget, requestAbortReason, resolveConnectTimeouts, withRequestBudget } from "./request-budget.js";
|
|
7
7
|
import { defaultCallbackUrl } from "./runtime-utils.js";
|
|
8
8
|
import { MdbaseSession } from "./session.js";
|
|
9
9
|
export class MdbaseMemoryVerificationStore {
|
|
@@ -21,7 +21,7 @@ export class MdbaseMemoryVerificationStore {
|
|
|
21
21
|
/**
|
|
22
22
|
* The application-level lifecycle boundary. It derives authorization from the
|
|
23
23
|
* bundled manifest, exposes semantic capabilities, and never mutates collection
|
|
24
|
-
*
|
|
24
|
+
* setup without an explicit `applyCollectionSetup` call.
|
|
25
25
|
*/
|
|
26
26
|
export class MdbaseApplicationSession {
|
|
27
27
|
connect;
|
|
@@ -31,10 +31,13 @@ export class MdbaseApplicationSession {
|
|
|
31
31
|
verificationStore;
|
|
32
32
|
snapshot = { status: "opening", connections: [] };
|
|
33
33
|
manifest = null;
|
|
34
|
+
application = null;
|
|
34
35
|
base = null;
|
|
35
36
|
stopBase;
|
|
36
|
-
|
|
37
|
+
setupAssessment = null;
|
|
37
38
|
verificationGeneration = 0;
|
|
39
|
+
lifecycleGeneration = 0;
|
|
40
|
+
startOperation = null;
|
|
38
41
|
constructor(connect, options, timeouts = resolveConnectTimeouts()) {
|
|
39
42
|
this.connect = connect;
|
|
40
43
|
this.options = options;
|
|
@@ -42,9 +45,42 @@ export class MdbaseApplicationSession {
|
|
|
42
45
|
this.verificationStore = options.verificationStore ?? defaultVerificationStore();
|
|
43
46
|
}
|
|
44
47
|
start(options) {
|
|
45
|
-
|
|
48
|
+
if (this.base && !this.startOperation)
|
|
49
|
+
return Promise.resolve(connectSuccess(this.snapshot));
|
|
50
|
+
const operation = this.startOperation ?? this.beginStart();
|
|
51
|
+
operation.waiters += 1;
|
|
52
|
+
return withRequestBudget(options, this.timeouts.watchStartMs, () => operation.promise)
|
|
53
|
+
.finally(() => {
|
|
54
|
+
operation.waiters -= 1;
|
|
55
|
+
if (operation.waiters === 0 && this.startOperation === operation) {
|
|
56
|
+
operation.controller.abort();
|
|
57
|
+
this.startOperation = null;
|
|
58
|
+
this.lifecycleGeneration += 1;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
beginStart() {
|
|
63
|
+
this.snapshot = { status: "opening", connections: [] };
|
|
64
|
+
const controller = new AbortController();
|
|
65
|
+
const generation = ++this.lifecycleGeneration;
|
|
66
|
+
const operation = {
|
|
67
|
+
promise: this.startWithinBudget({ signal: controller.signal, timeoutMs: null }, generation),
|
|
68
|
+
controller,
|
|
69
|
+
waiters: 0,
|
|
70
|
+
generation
|
|
71
|
+
};
|
|
72
|
+
this.startOperation = operation;
|
|
73
|
+
const settled = () => {
|
|
74
|
+
if (this.startOperation === operation)
|
|
75
|
+
this.startOperation = null;
|
|
76
|
+
};
|
|
77
|
+
operation.promise.then(settled, settled);
|
|
78
|
+
return operation;
|
|
46
79
|
}
|
|
47
|
-
async startWithinBudget(options) {
|
|
80
|
+
async startWithinBudget(options, generation) {
|
|
81
|
+
const registration = await this.connect.register(options);
|
|
82
|
+
if (!registration.ok)
|
|
83
|
+
return registration;
|
|
48
84
|
const manifest = await this.connect.manifest(options);
|
|
49
85
|
if (!manifest.ok)
|
|
50
86
|
return manifest;
|
|
@@ -61,24 +97,60 @@ export class MdbaseApplicationSession {
|
|
|
61
97
|
}
|
|
62
98
|
}));
|
|
63
99
|
}
|
|
100
|
+
if (generation !== this.lifecycleGeneration || options.signal?.aborted) {
|
|
101
|
+
throw requestAbortReason(options.signal ?? new AbortController().signal);
|
|
102
|
+
}
|
|
103
|
+
this.application = registration.value;
|
|
64
104
|
this.manifest = manifest.value;
|
|
65
|
-
|
|
105
|
+
const base = new MdbaseSession(this.connect, {
|
|
66
106
|
selection: this.options.selection,
|
|
67
107
|
autoSelect: this.options.autoSelect,
|
|
68
108
|
operations: operationsForSession(capabilities)
|
|
69
109
|
});
|
|
70
|
-
this.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
110
|
+
this.base = base;
|
|
111
|
+
this.stopBase = base.subscribe(() => {
|
|
112
|
+
if (this.base === base)
|
|
113
|
+
void this.refresh();
|
|
114
|
+
});
|
|
115
|
+
try {
|
|
116
|
+
const started = await base.start(options);
|
|
117
|
+
if (!started.ok) {
|
|
118
|
+
this.cleanupBase(base);
|
|
119
|
+
return started;
|
|
120
|
+
}
|
|
121
|
+
if (generation !== this.lifecycleGeneration || options.signal?.aborted) {
|
|
122
|
+
throw requestAbortReason(options.signal ?? new AbortController().signal);
|
|
123
|
+
}
|
|
124
|
+
await this.refresh(true, options);
|
|
125
|
+
if (generation !== this.lifecycleGeneration || options.signal?.aborted) {
|
|
126
|
+
throw requestAbortReason(options.signal ?? new AbortController().signal);
|
|
127
|
+
}
|
|
128
|
+
return connectSuccess(this.snapshot);
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
this.cleanupBase(base);
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
76
134
|
}
|
|
77
135
|
destroy() {
|
|
136
|
+
this.lifecycleGeneration += 1;
|
|
78
137
|
this.verificationGeneration += 1;
|
|
138
|
+
this.startOperation?.controller.abort();
|
|
139
|
+
this.startOperation = null;
|
|
140
|
+
if (this.base)
|
|
141
|
+
this.cleanupBase(this.base);
|
|
142
|
+
this.application = null;
|
|
143
|
+
this.manifest = null;
|
|
144
|
+
this.setupAssessment = null;
|
|
145
|
+
this.snapshot = { status: "opening", connections: [] };
|
|
146
|
+
this.listeners.clear();
|
|
147
|
+
}
|
|
148
|
+
cleanupBase(base) {
|
|
149
|
+
if (this.base !== base)
|
|
150
|
+
return;
|
|
79
151
|
this.stopBase?.();
|
|
80
152
|
this.stopBase = undefined;
|
|
81
|
-
|
|
153
|
+
base.destroy();
|
|
82
154
|
this.base = null;
|
|
83
155
|
}
|
|
84
156
|
getSnapshot() {
|
|
@@ -110,7 +182,7 @@ export class MdbaseApplicationSession {
|
|
|
110
182
|
completeAuthorization(callbackUrl, options) {
|
|
111
183
|
return this.requireBase().handleAuthorizationCallback(callbackUrl === undefined ? defaultCallbackUrl() : String(callbackUrl), options);
|
|
112
184
|
}
|
|
113
|
-
ensureCapabilities(capabilities) {
|
|
185
|
+
ensureCapabilities(capabilities, options) {
|
|
114
186
|
const manifestRequirements = this.requireManifest().requirements?.capabilities;
|
|
115
187
|
const declared = new Set([
|
|
116
188
|
...(manifestRequirements?.required ?? []),
|
|
@@ -131,35 +203,42 @@ export class MdbaseApplicationSession {
|
|
|
131
203
|
}
|
|
132
204
|
})));
|
|
133
205
|
}
|
|
134
|
-
return this.requireBase().ensureOperations(operationsForIds(capabilities));
|
|
206
|
+
return this.requireBase().ensureOperations(operationsForIds(capabilities), options);
|
|
135
207
|
}
|
|
136
|
-
async
|
|
137
|
-
if (this.snapshot.status !== "
|
|
138
|
-
return connectFailure(connectProblem("collection_not_ready", "There
|
|
208
|
+
async applyCollectionSetup(options) {
|
|
209
|
+
if (this.snapshot.status !== "setup_review_required" || !this.setupAssessment) {
|
|
210
|
+
return connectFailure(connectProblem("collection_not_ready", "There is no reviewed collection setup to apply."));
|
|
139
211
|
}
|
|
140
|
-
if (this.
|
|
141
|
-
return connectFailure(connectProblem("collection_not_ready", "Resolve the
|
|
212
|
+
if (!this.setupAssessment.applicable) {
|
|
213
|
+
return connectFailure(connectProblem("collection_not_ready", "Resolve the collection setup conflicts before applying this update."));
|
|
142
214
|
}
|
|
143
215
|
const connection = this.connection();
|
|
144
216
|
if (!connection) {
|
|
145
217
|
return connectFailure(connectProblem("collection_not_ready", "The collection is not ready."));
|
|
146
218
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
219
|
+
const assessment = this.setupAssessment;
|
|
220
|
+
const input = {
|
|
221
|
+
...this.collectionSetupInput(),
|
|
222
|
+
expectedAssessmentDigest: assessment.assessmentDigest,
|
|
223
|
+
expectedCollectionRevision: assessment.collectionRevision,
|
|
224
|
+
expectedProvisionDigest: assessment.provisionDigest,
|
|
225
|
+
allowTypePackDowngrades: assessment.typePacks
|
|
226
|
+
.filter((pack) => pack.status === "downgrade")
|
|
227
|
+
.map((pack) => pack.desired.id)
|
|
228
|
+
};
|
|
229
|
+
const budget = createRequestBudget(options, this.timeouts.requestMs);
|
|
230
|
+
const requestOptions = { signal: budget.signal, timeoutMs: null };
|
|
231
|
+
try {
|
|
232
|
+
const applied = await connection.applyCollectionSetup(input, requestOptions);
|
|
157
233
|
if (!applied.ok)
|
|
158
234
|
return applied;
|
|
235
|
+
this.setupAssessment = null;
|
|
236
|
+
await this.verifySetup(this.context(connection), ++this.verificationGeneration, requestOptions);
|
|
237
|
+
return connectSuccess(this.snapshot);
|
|
238
|
+
}
|
|
239
|
+
finally {
|
|
240
|
+
budget.dispose();
|
|
159
241
|
}
|
|
160
|
-
this.reviewEntries = [];
|
|
161
|
-
await this.verifyDefinitions(this.context(connection), ++this.verificationGeneration, options);
|
|
162
|
-
return connectSuccess(this.snapshot);
|
|
163
242
|
}
|
|
164
243
|
async refresh(awaitVerification = false, options) {
|
|
165
244
|
if (!this.base || !this.manifest)
|
|
@@ -167,7 +246,7 @@ export class MdbaseApplicationSession {
|
|
|
167
246
|
const current = this.base.getSnapshot();
|
|
168
247
|
this.verificationGeneration += 1;
|
|
169
248
|
const generation = this.verificationGeneration;
|
|
170
|
-
this.
|
|
249
|
+
this.setupAssessment = null;
|
|
171
250
|
if (current.status === "unselected") {
|
|
172
251
|
this.publish({ status: "unselected", connections: current.connections });
|
|
173
252
|
return;
|
|
@@ -181,12 +260,9 @@ export class MdbaseApplicationSession {
|
|
|
181
260
|
this.publish({ status: "authorization_required", ...context });
|
|
182
261
|
return;
|
|
183
262
|
}
|
|
184
|
-
const
|
|
185
|
-
.includes("
|
|
186
|
-
|
|
187
|
-
? this.manifest.provisions?.type_packs ?? []
|
|
188
|
-
: [];
|
|
189
|
-
if (provisions.length === 0) {
|
|
263
|
+
const managesSetup = this.manifest.requirements?.capabilities?.required
|
|
264
|
+
.includes("collection.setup.apply") ?? false;
|
|
265
|
+
if (!managesSetup) {
|
|
190
266
|
this.publish({ status: "ready", verification: "verified", ...context });
|
|
191
267
|
return;
|
|
192
268
|
}
|
|
@@ -195,46 +271,34 @@ export class MdbaseApplicationSession {
|
|
|
195
271
|
this.publish({ status: "ready", verification: "cached", ...context });
|
|
196
272
|
}
|
|
197
273
|
else {
|
|
198
|
-
this.publish({ status: "
|
|
274
|
+
this.publish({ status: "checking_setup", ...context });
|
|
199
275
|
}
|
|
200
|
-
const verification = this.
|
|
276
|
+
const verification = this.verifySetup(context, generation, options);
|
|
201
277
|
if (awaitVerification)
|
|
202
278
|
await verification;
|
|
203
279
|
}
|
|
204
|
-
async
|
|
280
|
+
async verifySetup(context, generation, options) {
|
|
205
281
|
const connection = this.connection();
|
|
206
282
|
const manifest = this.requireManifest();
|
|
207
283
|
if (!connection || connection.collectionId !== context.collectionId)
|
|
208
284
|
return;
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
.
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const outcome = options
|
|
218
|
-
? await connection.assessTypePack(input, options)
|
|
219
|
-
: await connection.assessTypePack(input);
|
|
220
|
-
if (generation !== this.verificationGeneration)
|
|
221
|
-
return;
|
|
222
|
-
if (!outcome.ok) {
|
|
223
|
-
this.publish({ status: "blocked", problem: outcome.problem, ...context });
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
if (outcome.value.status !== "current") {
|
|
227
|
-
entries.push({ provision, assessment: outcome.value });
|
|
228
|
-
}
|
|
285
|
+
const outcome = options
|
|
286
|
+
? await connection.assessCollectionSetup(this.collectionSetupInput(), options)
|
|
287
|
+
: await connection.assessCollectionSetup(this.collectionSetupInput());
|
|
288
|
+
if (generation !== this.verificationGeneration)
|
|
289
|
+
return;
|
|
290
|
+
if (!outcome.ok) {
|
|
291
|
+
this.publish({ status: "blocked", problem: outcome.problem, ...context });
|
|
292
|
+
return;
|
|
229
293
|
}
|
|
230
294
|
if (generation !== this.verificationGeneration)
|
|
231
295
|
return;
|
|
232
|
-
if (
|
|
233
|
-
this.
|
|
296
|
+
if (outcome.value.status !== "current") {
|
|
297
|
+
this.setupAssessment = outcome.value;
|
|
234
298
|
this.verificationStore.remove(verificationKey(manifest, context.collectionId));
|
|
235
299
|
this.publish({
|
|
236
|
-
status: "
|
|
237
|
-
|
|
300
|
+
status: "setup_review_required",
|
|
301
|
+
update: collectionSetupUpdate(outcome.value, manifest.provisions?.type_packs ?? []),
|
|
238
302
|
...context
|
|
239
303
|
});
|
|
240
304
|
return;
|
|
@@ -242,6 +306,21 @@ export class MdbaseApplicationSession {
|
|
|
242
306
|
this.verificationStore.write(verificationKey(manifest, context.collectionId), verificationValue(manifest));
|
|
243
307
|
this.publish({ status: "ready", verification: "verified", ...context });
|
|
244
308
|
}
|
|
309
|
+
collectionSetupInput() {
|
|
310
|
+
const manifest = this.requireManifest();
|
|
311
|
+
const application = this.requireApplication();
|
|
312
|
+
return {
|
|
313
|
+
applicationId: declarationIdFromFamilyIdentity(application.family_identity),
|
|
314
|
+
declarationDigest: `sha256:${application.manifest_digest}`,
|
|
315
|
+
requirements: {
|
|
316
|
+
configuration: manifest.requirements?.configuration ?? []
|
|
317
|
+
},
|
|
318
|
+
provisions: {
|
|
319
|
+
configuration: manifest.provisions?.configuration ?? [],
|
|
320
|
+
typePacks: manifest.provisions?.type_packs ?? []
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
}
|
|
245
324
|
context(connection) {
|
|
246
325
|
const info = connection.info();
|
|
247
326
|
return {
|
|
@@ -268,6 +347,11 @@ export class MdbaseApplicationSession {
|
|
|
268
347
|
throw new Error("Start the application session before using it.");
|
|
269
348
|
return this.manifest;
|
|
270
349
|
}
|
|
350
|
+
requireApplication() {
|
|
351
|
+
if (!this.application)
|
|
352
|
+
throw new Error("Start the application session before using it.");
|
|
353
|
+
return this.application;
|
|
354
|
+
}
|
|
271
355
|
}
|
|
272
356
|
function operationsForSession(requirements) {
|
|
273
357
|
return operationsForApplicationCapabilities(requirements);
|
|
@@ -276,8 +360,7 @@ function operationsForIds(capabilities) {
|
|
|
276
360
|
const definitions = capabilities.flatMap((id) => capabilityOperations(id));
|
|
277
361
|
return [...new Set(definitions)];
|
|
278
362
|
}
|
|
279
|
-
function definitionUpdate(
|
|
280
|
-
const { assessment } = entry;
|
|
363
|
+
function definitionUpdate(provision, assessment) {
|
|
281
364
|
const statusReason = assessment.status === "conflict"
|
|
282
365
|
? "Existing user-owned resources conflict with this application definition pack."
|
|
283
366
|
: assessment.status === "install"
|
|
@@ -285,27 +368,59 @@ function definitionUpdate(entry) {
|
|
|
285
368
|
: `This collection has a ${assessment.status} definition change to review.`;
|
|
286
369
|
return {
|
|
287
370
|
id: assessment.desired.id,
|
|
288
|
-
name:
|
|
371
|
+
name: provision.manifest.name ?? assessment.desired.id,
|
|
289
372
|
status: assessment.status,
|
|
290
373
|
applicable: assessment.applicable,
|
|
291
|
-
assessmentDigest: assessment.
|
|
374
|
+
assessmentDigest: assessment.assessmentDigest,
|
|
292
375
|
...(assessment.current ? { currentVersion: assessment.current.version } : {}),
|
|
293
376
|
desiredVersion: assessment.desired.version,
|
|
294
377
|
resources: assessment.resources,
|
|
295
|
-
contractSetups: assessment.
|
|
378
|
+
contractSetups: assessment.contractSetups,
|
|
296
379
|
canApply: assessment.applicable,
|
|
297
380
|
reason: statusReason
|
|
298
381
|
};
|
|
299
382
|
}
|
|
383
|
+
function collectionSetupUpdate(assessment, provisions) {
|
|
384
|
+
const typePacks = assessment.typePacks.map((pack) => {
|
|
385
|
+
const provision = provisions.find((candidate) => candidate.manifest.id === pack.desired.id);
|
|
386
|
+
if (!provision) {
|
|
387
|
+
throw new Error(`Collection setup returned undeclared type pack '${pack.desired.id}'.`);
|
|
388
|
+
}
|
|
389
|
+
return definitionUpdate(provision, pack);
|
|
390
|
+
});
|
|
391
|
+
const configurationChanges = assessment.configuration.filter((entry) => entry.action !== "current").length;
|
|
392
|
+
const reason = assessment.status === "conflict"
|
|
393
|
+
? "Existing collection policy conflicts with this application's required setup."
|
|
394
|
+
: `This application requires ${configurationChanges} configuration change${configurationChanges === 1 ? "" : "s"} and ${typePacks.length} definition update${typePacks.length === 1 ? "" : "s"}.`;
|
|
395
|
+
return {
|
|
396
|
+
status: assessment.status,
|
|
397
|
+
applicable: assessment.applicable,
|
|
398
|
+
assessmentDigest: assessment.assessmentDigest,
|
|
399
|
+
collectionRevision: assessment.collectionRevision,
|
|
400
|
+
provisionDigest: assessment.provisionDigest,
|
|
401
|
+
configuration: assessment.configuration,
|
|
402
|
+
typePacks,
|
|
403
|
+
canApply: assessment.applicable,
|
|
404
|
+
reason
|
|
405
|
+
};
|
|
406
|
+
}
|
|
300
407
|
function verificationKey(manifest, collectionId) {
|
|
301
408
|
return `mdbase-application-session:v1:${manifest.id}:${collectionId}`;
|
|
302
409
|
}
|
|
303
410
|
function verificationValue(manifest) {
|
|
304
411
|
return JSON.stringify({
|
|
305
412
|
capabilities: manifest.requirements?.capabilities,
|
|
306
|
-
|
|
413
|
+
requirements: manifest.requirements?.configuration ?? [],
|
|
414
|
+
provisions: manifest.provisions ?? {}
|
|
307
415
|
});
|
|
308
416
|
}
|
|
417
|
+
function declarationIdFromFamilyIdentity(familyIdentity) {
|
|
418
|
+
const prefix = "bundle:";
|
|
419
|
+
if (!familyIdentity.startsWith(prefix) || familyIdentity.length === prefix.length) {
|
|
420
|
+
throw new Error("The registered application has no valid declaration identity.");
|
|
421
|
+
}
|
|
422
|
+
return familyIdentity.slice(prefix.length);
|
|
423
|
+
}
|
|
309
424
|
function defaultVerificationStore() {
|
|
310
425
|
if (typeof localStorage !== "undefined") {
|
|
311
426
|
return {
|