@zhin.js/plugin-runtime 1.1.17 → 1.1.18
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/lib/admission.d.ts +15 -3
- package/lib/admission.js +10 -5
- package/lib/data-migration.d.ts +105 -0
- package/lib/data-migration.js +16 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.js +6 -0
- package/lib/outbound-host.d.ts +32 -9
- package/lib/outbound-host.js +3 -0
- package/lib/plugin-console-rpc.d.ts +11 -1
- package/lib/plugin-events.d.ts +47 -0
- package/lib/plugin-events.js +30 -0
- package/lib/plugin.d.ts +6 -1
- package/lib/plugin.js +2 -0
- package/lib/relational-target-migration.d.ts +68 -0
- package/lib/relational-target-migration.js +571 -0
- package/lib/root-controller.d.ts +2 -2
- package/lib/root-controller.js +2 -0
- package/lib/snapshot.d.ts +22 -5
- package/lib/snapshot.js +60 -14
- package/lib/token.d.ts +5 -1
- package/lib/token.js +13 -0
- package/package.json +1 -1
package/lib/snapshot.js
CHANGED
|
@@ -11,18 +11,20 @@ export class GenerationConflictError extends Error {
|
|
|
11
11
|
}
|
|
12
12
|
const snapshotLeaseAuthority = Symbol('SnapshotLeaseAuthority');
|
|
13
13
|
export class SnapshotLease {
|
|
14
|
+
#released = false;
|
|
14
15
|
value;
|
|
15
16
|
releaseRecord;
|
|
16
|
-
retirementSignal;
|
|
17
|
-
#released = false;
|
|
18
|
-
constructor(value, releaseRecord, authority,
|
|
19
17
|
/** Aborted on publication of a replacement or Root close, before lease disposal. */
|
|
20
|
-
retirementSignal
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
retirementSignal;
|
|
19
|
+
/** Root closure only; shared by leases from every generation and never aborted by HMR. */
|
|
20
|
+
stopSignal;
|
|
21
|
+
constructor(input, authority) {
|
|
24
22
|
if (authority !== snapshotLeaseAuthority)
|
|
25
23
|
throw new TypeError('SnapshotLease is owned by SnapshotStore');
|
|
24
|
+
this.value = input.value;
|
|
25
|
+
this.releaseRecord = input.releaseRecord;
|
|
26
|
+
this.retirementSignal = input.retirementSignal;
|
|
27
|
+
this.stopSignal = input.stopSignal;
|
|
26
28
|
}
|
|
27
29
|
get active() {
|
|
28
30
|
return !this.#released;
|
|
@@ -38,11 +40,14 @@ export class SnapshotStore {
|
|
|
38
40
|
onDisposalError;
|
|
39
41
|
#current;
|
|
40
42
|
#retired = new Set();
|
|
41
|
-
#leases = new
|
|
43
|
+
#leases = new WeakMap();
|
|
44
|
+
#rootResource;
|
|
42
45
|
#closed = false;
|
|
46
|
+
#stop = new AbortController();
|
|
43
47
|
constructor(initial, onDisposalError = () => undefined, ...state) {
|
|
44
48
|
this.onDisposalError = onDisposalError;
|
|
45
49
|
const initialState = state[0];
|
|
50
|
+
this.#rootResource = state[1];
|
|
46
51
|
const snapshot = createSnapshotView(0, initial);
|
|
47
52
|
const committed = Object.freeze({ snapshot, state: initialState });
|
|
48
53
|
const admissions = collectSnapshotAdmissions(snapshot);
|
|
@@ -57,7 +62,8 @@ export class SnapshotStore {
|
|
|
57
62
|
retired: false,
|
|
58
63
|
};
|
|
59
64
|
this.#current = record;
|
|
60
|
-
replaceGenerationAdmissions(new Set(), admissions, this,
|
|
65
|
+
replaceGenerationAdmissions({ previous: new Set(), next: admissions, owner: this,
|
|
66
|
+
publishNext: () => undefined, acquireNext: () => this.#retain(record), stopSignal: this.#stop.signal });
|
|
61
67
|
}
|
|
62
68
|
get current() {
|
|
63
69
|
return this.#current.snapshot;
|
|
@@ -69,14 +75,43 @@ export class SnapshotStore {
|
|
|
69
75
|
acquire() {
|
|
70
76
|
if (this.#closed)
|
|
71
77
|
throw new Error('SnapshotStore is closed');
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
78
|
+
return this.#lease(this.#current);
|
|
79
|
+
}
|
|
80
|
+
retain(lease) {
|
|
81
|
+
if (this.#closed)
|
|
82
|
+
throw new Error('SnapshotStore is closed');
|
|
83
|
+
const record = this.#leases.get(lease);
|
|
84
|
+
if (!record || !lease.active)
|
|
85
|
+
throw new Error('Snapshot retention requires an active owned lease');
|
|
86
|
+
return this.#lease(record);
|
|
87
|
+
}
|
|
88
|
+
#lease(record) {
|
|
89
|
+
const releaseRecord = this.#retain(record);
|
|
90
|
+
const lease = new SnapshotLease({
|
|
91
|
+
value: record.snapshot,
|
|
92
|
+
releaseRecord: () => {
|
|
93
|
+
this.#leases.set(lease, null);
|
|
94
|
+
releaseRecord();
|
|
95
|
+
},
|
|
96
|
+
retirementSignal: record.retirement.signal,
|
|
97
|
+
stopSignal: this.#stop.signal,
|
|
98
|
+
}, snapshotLeaseAuthority);
|
|
99
|
+
this.#leases.set(lease, record);
|
|
75
100
|
return lease;
|
|
76
101
|
}
|
|
77
102
|
owns(lease) {
|
|
78
103
|
return this.#leases.has(lease);
|
|
79
104
|
}
|
|
105
|
+
rootResource(lease, token) {
|
|
106
|
+
const record = this.#leases.get(lease);
|
|
107
|
+
if (!record || !lease.active)
|
|
108
|
+
throw new Error('Root resource access requires an active owned SnapshotLease');
|
|
109
|
+
// Manually authored snapshots retain their explicit resource contract. Real
|
|
110
|
+
// Runtime Roots inject the same-generation Scope resolver, including privacy checks.
|
|
111
|
+
return (this.#rootResource
|
|
112
|
+
? this.#rootResource(record.state, record.snapshot.root, token)
|
|
113
|
+
: record.snapshot.resources.get(record.snapshot.root)?.get(token.id));
|
|
114
|
+
}
|
|
80
115
|
commit(expectedGeneration, prepared) {
|
|
81
116
|
// The caller owns a candidate until this method succeeds. This lets the
|
|
82
117
|
// transaction authority undo handoff state before disposing a rejection.
|
|
@@ -101,7 +136,8 @@ export class SnapshotStore {
|
|
|
101
136
|
leases: 0,
|
|
102
137
|
retired: false,
|
|
103
138
|
};
|
|
104
|
-
replaceGenerationAdmissions(previous.admissions, admissions,
|
|
139
|
+
replaceGenerationAdmissions({ previous: previous.admissions, next: admissions, owner: this,
|
|
140
|
+
publishNext: () => { this.#current = next; }, acquireNext: () => this.#retain(next), stopSignal: this.#stop.signal });
|
|
105
141
|
// Commit only swaps the active pointer. The previous record may still be
|
|
106
142
|
// serving in-flight work, so disposal is deferred to its final lease.
|
|
107
143
|
previous.retired = true;
|
|
@@ -114,7 +150,10 @@ export class SnapshotStore {
|
|
|
114
150
|
if (this.#closed)
|
|
115
151
|
return;
|
|
116
152
|
this.#closed = true;
|
|
117
|
-
replaceGenerationAdmissions(this.#current.admissions, new Set(), this,
|
|
153
|
+
replaceGenerationAdmissions({ previous: this.#current.admissions, next: new Set(), owner: this,
|
|
154
|
+
publishNext: () => undefined, stopSignal: this.#stop.signal });
|
|
155
|
+
// Stop observers may synchronously release transports; no gate may admit fresh work then.
|
|
156
|
+
this.#stop.abort();
|
|
118
157
|
this.#current.retired = true;
|
|
119
158
|
this.#current.retirement.abort();
|
|
120
159
|
this.#retired.add(this.#current);
|
|
@@ -226,3 +265,10 @@ class ReadonlyMapView {
|
|
|
226
265
|
function readonlyMap(values) {
|
|
227
266
|
return new ReadonlyMapView(values);
|
|
228
267
|
}
|
|
268
|
+
/** Compatibility for Host embedders that implement the original minimal SnapshotReader. */
|
|
269
|
+
export function readLeasedRootResource(reader, lease, token) {
|
|
270
|
+
if (!reader.owns(lease) || !lease.active)
|
|
271
|
+
throw new Error('Root resource access requires an active owned SnapshotLease');
|
|
272
|
+
return reader.rootResource ? reader.rootResource(lease, token)
|
|
273
|
+
: lease.value.resources.get(lease.value.root)?.get(token.id);
|
|
274
|
+
}
|
package/lib/token.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DisposeStack, type Dispose } from './dispose.js';
|
|
2
|
-
import { type PluginId, type TokenId } from './identity.js';
|
|
2
|
+
import { type FeatureId, type PluginId, type TokenId } from './identity.js';
|
|
3
3
|
export interface Token<T> {
|
|
4
4
|
readonly id: TokenId;
|
|
5
5
|
readonly description?: string;
|
|
@@ -7,6 +7,10 @@ export interface Token<T> {
|
|
|
7
7
|
}
|
|
8
8
|
export declare function createToken<T>(id: string, description?: string): Token<T>;
|
|
9
9
|
export declare function createRootPrivateToken<T>(id: string, description?: string): Token<T>;
|
|
10
|
+
/** A Root-private capability readable only while projecting its owning Feature. */
|
|
11
|
+
export declare function createFeaturePrivateToken<T>(id: string, feature: FeatureId, description?: string): Token<T>;
|
|
12
|
+
/** @internal The actual candidate Root Scope never enters the Feature context. */
|
|
13
|
+
export declare function readFeaturePrivateResource<T>(scope: Pick<Scope, 'has' | 'use'>, feature: FeatureId, token: Token<T>): T | undefined;
|
|
10
14
|
/** @internal Ends a facade's candidate declarations on seal or failed setup disposal. */
|
|
11
15
|
export declare const scopeSetupFinalizer: unique symbol;
|
|
12
16
|
export declare class Scope {
|
package/lib/token.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DisposeStack } from './dispose.js';
|
|
2
2
|
import { tokenId } from './identity.js';
|
|
3
3
|
const rootPrivateTokens = new WeakSet();
|
|
4
|
+
const featurePrivateTokens = new WeakMap();
|
|
4
5
|
export function createToken(id, description) {
|
|
5
6
|
return Object.freeze({ id: tokenId(id), description });
|
|
6
7
|
}
|
|
@@ -9,6 +10,18 @@ export function createRootPrivateToken(id, description) {
|
|
|
9
10
|
rootPrivateTokens.add(token);
|
|
10
11
|
return Object.freeze(token);
|
|
11
12
|
}
|
|
13
|
+
/** A Root-private capability readable only while projecting its owning Feature. */
|
|
14
|
+
export function createFeaturePrivateToken(id, feature, description) {
|
|
15
|
+
const token = createRootPrivateToken(id, description);
|
|
16
|
+
featurePrivateTokens.set(token, feature);
|
|
17
|
+
return token;
|
|
18
|
+
}
|
|
19
|
+
/** @internal The actual candidate Root Scope never enters the Feature context. */
|
|
20
|
+
export function readFeaturePrivateResource(scope, feature, token) {
|
|
21
|
+
if (featurePrivateTokens.get(token) !== feature)
|
|
22
|
+
throw new Error(`Feature cannot access Root resource ${token.id}`);
|
|
23
|
+
return scope.has(token) ? scope.use(token) : undefined;
|
|
24
|
+
}
|
|
12
25
|
/** @internal Ends a facade's candidate declarations on seal or failed setup disposal. */
|
|
13
26
|
export const scopeSetupFinalizer = Symbol('ScopeSetupFinalizer');
|
|
14
27
|
export class Scope {
|