@player-ui/context-plugin 0.16.0--canary.891.38194
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/dist/ContextPlugin.native.js +1407 -0
- package/dist/ContextPlugin.native.js.map +1 -0
- package/dist/cjs/index.cjs +621 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/index.legacy-esm.js +578 -0
- package/dist/index.mjs +578 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +34 -0
- package/src/__tests__/history.test.ts +27 -0
- package/src/__tests__/key.test.ts +22 -0
- package/src/__tests__/plugin.test.ts +376 -0
- package/src/__tests__/state-plugin.test.ts +346 -0
- package/src/__tests__/store.test.ts +107 -0
- package/src/history.ts +25 -0
- package/src/index.ts +6 -0
- package/src/key.ts +35 -0
- package/src/plugin.ts +261 -0
- package/src/state-plugin.ts +286 -0
- package/src/store.ts +205 -0
- package/src/symbols.ts +1 -0
- package/src/types.ts +57 -0
- package/src/utils.ts +17 -0
- package/types/history.d.ts +13 -0
- package/types/index.d.ts +7 -0
- package/types/key.d.ts +20 -0
- package/types/plugin.d.ts +64 -0
- package/types/state-plugin.d.ts +92 -0
- package/types/store.d.ts +26 -0
- package/types/symbols.d.ts +2 -0
- package/types/types.d.ts +45 -0
- package/types/utils.d.ts +7 -0
package/src/store.ts
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ContextEntryDescriptor,
|
|
3
|
+
ContextKey,
|
|
4
|
+
ContextTransform,
|
|
5
|
+
FrozenContextEntry,
|
|
6
|
+
FrozenContextSnapshot,
|
|
7
|
+
} from "./types";
|
|
8
|
+
import { nameOfContextKey } from "./key";
|
|
9
|
+
|
|
10
|
+
type Entry = {
|
|
11
|
+
key: ContextKey;
|
|
12
|
+
hasLiteral: boolean;
|
|
13
|
+
literal?: unknown;
|
|
14
|
+
transform?: ContextTransform<unknown>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const deepFreezeEntry = (entry: FrozenContextEntry): FrozenContextEntry => {
|
|
18
|
+
Object.freeze(entry);
|
|
19
|
+
return entry;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A poisoned stand-in for a function captured into a history snapshot. The
|
|
24
|
+
* key and description survive in the snapshot — the capability *existed* —
|
|
25
|
+
* but the callable is dead: invoking it throws because the flow it was bound
|
|
26
|
+
* to has ended. This preserves the distinction between "context that never
|
|
27
|
+
* held this action" and "an action that is no longer valid".
|
|
28
|
+
*/
|
|
29
|
+
const tombstone = (description: string) => (): never => {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`[ContextPlugin] Action "${description}" is no longer valid — its flow has ended`,
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Recursively replace every function in [value] (top-level or nested) with a
|
|
37
|
+
* {@link tombstone}, so a frozen history snapshot carries no live callables.
|
|
38
|
+
*/
|
|
39
|
+
const tombstoneFunctions = (value: unknown, description: string): unknown => {
|
|
40
|
+
if (typeof value === "function") {
|
|
41
|
+
return tombstone(description);
|
|
42
|
+
}
|
|
43
|
+
if (Array.isArray(value)) {
|
|
44
|
+
return value.map((item) => tombstoneFunctions(item, description));
|
|
45
|
+
}
|
|
46
|
+
if (value !== null && typeof value === "object") {
|
|
47
|
+
const out: Record<string, unknown> = {};
|
|
48
|
+
for (const [k, v] of Object.entries(value)) {
|
|
49
|
+
out[k] = tombstoneFunctions(v, description);
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Mutable per-flow context store. Owned by a single flow lifecycle; the plugin
|
|
58
|
+
* rotates the store on flow end and freezes the prior one into a snapshot.
|
|
59
|
+
*/
|
|
60
|
+
export class ContextStore {
|
|
61
|
+
private entries = new Map<symbol, Entry>();
|
|
62
|
+
/** source symbol -> set of target symbols (reverse index for invalidation). */
|
|
63
|
+
private dependents = new Map<symbol, Set<symbol>>();
|
|
64
|
+
|
|
65
|
+
register(key: ContextKey): boolean {
|
|
66
|
+
if (this.entries.has(key.symbol)) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
this.entries.set(key.symbol, { key, hasLiteral: false });
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
set<Value>(key: ContextKey<Value>, value: Value): void {
|
|
74
|
+
const existing = this.entries.get(key.symbol);
|
|
75
|
+
if (existing) {
|
|
76
|
+
existing.hasLiteral = true;
|
|
77
|
+
existing.literal = value;
|
|
78
|
+
existing.key = key;
|
|
79
|
+
} else {
|
|
80
|
+
this.entries.set(key.symbol, {
|
|
81
|
+
key,
|
|
82
|
+
hasLiteral: true,
|
|
83
|
+
literal: value,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
registerTransform<Value>(
|
|
89
|
+
key: ContextKey<Value>,
|
|
90
|
+
transform: ContextTransform<Value>,
|
|
91
|
+
): { previousSources?: ReadonlyArray<ContextKey> } {
|
|
92
|
+
const existing = this.entries.get(key.symbol);
|
|
93
|
+
const previousSources = existing?.transform?.sources;
|
|
94
|
+
|
|
95
|
+
if (previousSources) {
|
|
96
|
+
for (const src of previousSources) {
|
|
97
|
+
this.dependents.get(src.symbol)?.delete(key.symbol);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const stored: ContextTransform<unknown> = {
|
|
102
|
+
sources: transform.sources,
|
|
103
|
+
compute: transform.compute as ContextTransform<unknown>["compute"],
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
if (existing) {
|
|
107
|
+
existing.transform = stored;
|
|
108
|
+
existing.key = key;
|
|
109
|
+
} else {
|
|
110
|
+
this.entries.set(key.symbol, {
|
|
111
|
+
key,
|
|
112
|
+
hasLiteral: false,
|
|
113
|
+
transform: stored,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
for (const src of transform.sources) {
|
|
118
|
+
let set = this.dependents.get(src.symbol);
|
|
119
|
+
if (!set) {
|
|
120
|
+
set = new Set();
|
|
121
|
+
this.dependents.set(src.symbol, set);
|
|
122
|
+
}
|
|
123
|
+
set.add(key.symbol);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return { previousSources };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
get<Value>(key: ContextKey<Value>): Value | undefined {
|
|
130
|
+
return this.compute(key.symbol) as Value | undefined;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
has(key: ContextKey): boolean {
|
|
134
|
+
const entry = this.entries.get(key.symbol);
|
|
135
|
+
return Boolean(entry && (entry.hasLiteral || entry.transform));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Return the keys that depend on the given source key (direct dependents only). */
|
|
139
|
+
dependentsOf(sourceSymbol: symbol): ReadonlyArray<ContextKey> {
|
|
140
|
+
const set = this.dependents.get(sourceSymbol);
|
|
141
|
+
if (!set || set.size === 0) {
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
const out: ContextKey[] = [];
|
|
145
|
+
for (const targetSymbol of set) {
|
|
146
|
+
const target = this.entries.get(targetSymbol);
|
|
147
|
+
if (target) {
|
|
148
|
+
out.push(target.key);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return out;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
list(): ReadonlyArray<ContextEntryDescriptor> {
|
|
155
|
+
const out: ContextEntryDescriptor[] = [];
|
|
156
|
+
for (const entry of this.entries.values()) {
|
|
157
|
+
out.push({
|
|
158
|
+
symbol: entry.key.symbol,
|
|
159
|
+
description: entry.key.description,
|
|
160
|
+
hasValue: entry.hasLiteral,
|
|
161
|
+
hasTransform: Boolean(entry.transform),
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
return out;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
freeze(meta: { flowId?: string; endedAt: number }): FrozenContextSnapshot {
|
|
168
|
+
const frozenEntries: FrozenContextEntry[] = [];
|
|
169
|
+
for (const entry of this.entries.values()) {
|
|
170
|
+
if (!entry.hasLiteral && !entry.transform) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const computed = this.compute(entry.key.symbol);
|
|
174
|
+
const value = tombstoneFunctions(computed, entry.key.description);
|
|
175
|
+
frozenEntries.push(
|
|
176
|
+
deepFreezeEntry({
|
|
177
|
+
symbol: entry.key.symbol,
|
|
178
|
+
name: nameOfContextKey(entry.key),
|
|
179
|
+
description: entry.key.description,
|
|
180
|
+
value,
|
|
181
|
+
}),
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
const bySymbol = new Map(frozenEntries.map((e) => [e.symbol, e.value]));
|
|
185
|
+
const snapshot: FrozenContextSnapshot = {
|
|
186
|
+
flowId: meta.flowId,
|
|
187
|
+
endedAt: meta.endedAt,
|
|
188
|
+
entries: Object.freeze(frozenEntries),
|
|
189
|
+
get<Value>(key: ContextKey<Value>): Value | undefined {
|
|
190
|
+
return bySymbol.get(key.symbol) as Value | undefined;
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
return Object.freeze(snapshot);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private compute(sym: symbol): unknown {
|
|
197
|
+
const entry = this.entries.get(sym);
|
|
198
|
+
if (!entry) return undefined;
|
|
199
|
+
if (entry.hasLiteral) return entry.literal;
|
|
200
|
+
if (!entry.transform) return undefined;
|
|
201
|
+
const reader = <V>(otherKey: { symbol: symbol }): V | undefined =>
|
|
202
|
+
this.compute(otherKey.symbol) as V | undefined;
|
|
203
|
+
return entry.transform.compute(reader);
|
|
204
|
+
}
|
|
205
|
+
}
|
package/src/symbols.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const ContextPluginSymbol = Symbol.for("ContextPlugin");
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export type ContextKey<Value = unknown> = {
|
|
2
|
+
readonly symbol: symbol;
|
|
3
|
+
readonly description: string;
|
|
4
|
+
/** Phantom marker so TS can infer `Value` from a key. Not present at runtime. */
|
|
5
|
+
readonly __value?: Value;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type ContextReader = <Value>(
|
|
9
|
+
key: ContextKey<Value>,
|
|
10
|
+
) => Value | undefined;
|
|
11
|
+
|
|
12
|
+
export type ContextTransform<Value> = {
|
|
13
|
+
/** Source keys whose updates should invalidate (and notify subscribers of) this target. */
|
|
14
|
+
sources: ReadonlyArray<ContextKey>;
|
|
15
|
+
/** Compute the target value, reading from other context entries. */
|
|
16
|
+
compute: (read: ContextReader) => Value | undefined;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type ContextSubscriber<Value> = (
|
|
20
|
+
value: Value | undefined,
|
|
21
|
+
key: ContextKey<Value>,
|
|
22
|
+
) => void;
|
|
23
|
+
|
|
24
|
+
export type ContextGlobalSubscriber = (value: unknown, key: ContextKey) => void;
|
|
25
|
+
|
|
26
|
+
export type ContextEntryDescriptor = {
|
|
27
|
+
symbol: symbol;
|
|
28
|
+
description: string;
|
|
29
|
+
hasValue: boolean;
|
|
30
|
+
hasTransform: boolean;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type SubscriptionToken = `ctx_${number}`;
|
|
34
|
+
|
|
35
|
+
export type FrozenContextEntry = {
|
|
36
|
+
readonly symbol: symbol;
|
|
37
|
+
/**
|
|
38
|
+
* The entry's key name (from `nameOfContextKey`), or `undefined` for
|
|
39
|
+
* non-namespaced keys. Unlike `symbol`, this is a plain string that survives
|
|
40
|
+
* the native bridge, so native consumers read snapshot entries by name.
|
|
41
|
+
*/
|
|
42
|
+
readonly name: string | undefined;
|
|
43
|
+
readonly description: string;
|
|
44
|
+
readonly value: unknown;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type FrozenContextSnapshot = {
|
|
48
|
+
readonly flowId?: string;
|
|
49
|
+
readonly endedAt: number;
|
|
50
|
+
readonly entries: ReadonlyArray<FrozenContextEntry>;
|
|
51
|
+
/**
|
|
52
|
+
* Read a frozen entry by its key — the same typed access as live context.
|
|
53
|
+
* Returns `undefined` if the key was not present when the snapshot froze.
|
|
54
|
+
* Function-valued entries return their tombstone (a throwing callable).
|
|
55
|
+
*/
|
|
56
|
+
get<Value>(key: ContextKey<Value>): Value | undefined;
|
|
57
|
+
};
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Player } from "@player-ui/player";
|
|
2
|
+
import { ContextPlugin } from "./plugin";
|
|
3
|
+
import { ContextPluginSymbol } from "./symbols";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Returns the existing ContextPlugin or creates and registers a new one.
|
|
7
|
+
*/
|
|
8
|
+
export function getContextPlugin(player: Player): ContextPlugin {
|
|
9
|
+
const existing = player.findPlugin<ContextPlugin>(ContextPluginSymbol);
|
|
10
|
+
const plugin = existing ?? new ContextPlugin();
|
|
11
|
+
|
|
12
|
+
if (!existing) {
|
|
13
|
+
player.registerPlugin(plugin);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return plugin;
|
|
17
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FrozenContextSnapshot } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Append-only stack of frozen per-flow snapshots. The plugin pushes one
|
|
4
|
+
* snapshot on each flow end; consumers read via `entries()`.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ContextHistory {
|
|
7
|
+
private stack;
|
|
8
|
+
push(snapshot: FrozenContextSnapshot): void;
|
|
9
|
+
entries(): ReadonlyArray<FrozenContextSnapshot>;
|
|
10
|
+
size(): number;
|
|
11
|
+
clear(): void;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=history.d.ts.map
|
package/types/index.d.ts
ADDED
package/types/key.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ContextKey } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Create a typed, globally-identifiable context key.
|
|
4
|
+
*
|
|
5
|
+
* Identity is backed by `Symbol.for`, so two keys created with the same `name`
|
|
6
|
+
* in different bundles refer to the same store entry. The `description` is the
|
|
7
|
+
* human-readable label used by introspection consumers.
|
|
8
|
+
*/
|
|
9
|
+
export declare const defineContextKey: <Value>(name: string, description: string) => ContextKey<Value>;
|
|
10
|
+
/**
|
|
11
|
+
* Resolve the global symbol for a context key name. Used by native wrappers
|
|
12
|
+
* that cross the JS bridge with string names instead of JS symbols.
|
|
13
|
+
*/
|
|
14
|
+
export declare const resolveContextKeySymbol: (name: string) => symbol;
|
|
15
|
+
/**
|
|
16
|
+
* Reverse-derive the name from a key created via `defineContextKey`. Returns
|
|
17
|
+
* `undefined` if the key's symbol was not created in the context namespace.
|
|
18
|
+
*/
|
|
19
|
+
export declare const nameOfContextKey: (key: ContextKey) => string | undefined;
|
|
20
|
+
//# sourceMappingURL=key.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Player, PlayerPlugin } from "@player-ui/player";
|
|
2
|
+
import { SyncHook, SyncWaterfallHook } from "tapable-ts";
|
|
3
|
+
import { ContextStore } from "./store";
|
|
4
|
+
import { ContextHistory } from "./history";
|
|
5
|
+
import type { ContextEntryDescriptor, ContextGlobalSubscriber, ContextKey, ContextSubscriber, ContextTransform, FrozenContextSnapshot, SubscriptionToken } from "./types";
|
|
6
|
+
/**
|
|
7
|
+
* Maintains a per-flow store of context entries keyed by symbol, with
|
|
8
|
+
* registered transforms for derived values and a subscription API for
|
|
9
|
+
* external consumers. On flow end the active store is frozen into a snapshot
|
|
10
|
+
* and pushed onto a history stack; a fresh store is created for the next flow,
|
|
11
|
+
* with transforms re-applied. Subscribers persist across flow rotations.
|
|
12
|
+
*/
|
|
13
|
+
export declare class ContextPlugin implements PlayerPlugin {
|
|
14
|
+
name: string;
|
|
15
|
+
static Symbol: symbol;
|
|
16
|
+
readonly symbol: symbol;
|
|
17
|
+
readonly hooks: {
|
|
18
|
+
onSet: SyncHook<[ContextKey, unknown], Record<string, any>>;
|
|
19
|
+
resolveValue: SyncWaterfallHook<[unknown, ContextKey], Record<string, any>>;
|
|
20
|
+
onRegister: SyncHook<[ContextKey], Record<string, any>>;
|
|
21
|
+
onFlowFrozen: SyncHook<[FrozenContextSnapshot], Record<string, any>>;
|
|
22
|
+
};
|
|
23
|
+
protected store: ContextStore;
|
|
24
|
+
protected historyStack: ContextHistory;
|
|
25
|
+
private transforms;
|
|
26
|
+
private perKeySubs;
|
|
27
|
+
private globalSubs;
|
|
28
|
+
private tokenIndex;
|
|
29
|
+
private currentFlowId;
|
|
30
|
+
constructor();
|
|
31
|
+
apply(player: Player): void;
|
|
32
|
+
register<Value>(key: ContextKey<Value>): void;
|
|
33
|
+
set<Value>(key: ContextKey<Value>, value: Value): void;
|
|
34
|
+
get<Value>(key: ContextKey<Value>): Value | undefined;
|
|
35
|
+
has(key: ContextKey): boolean;
|
|
36
|
+
registerTransform<Value>(key: ContextKey<Value>, transform: ContextTransform<Value>): void;
|
|
37
|
+
subscribe<Value>(key: ContextKey<Value>, handler: ContextSubscriber<Value>): SubscriptionToken;
|
|
38
|
+
subscribeAll(handler: ContextGlobalSubscriber): SubscriptionToken;
|
|
39
|
+
unsubscribe(token: SubscriptionToken): void;
|
|
40
|
+
list(): ReadonlyArray<ContextEntryDescriptor>;
|
|
41
|
+
history(): ReadonlyArray<FrozenContextSnapshot>;
|
|
42
|
+
snapshot(): FrozenContextSnapshot;
|
|
43
|
+
/**
|
|
44
|
+
* Bridge-friendly: set a value by string name. Used by native wrappers that
|
|
45
|
+
* cannot construct a `ContextKey` object directly.
|
|
46
|
+
*/
|
|
47
|
+
setByName(name: string, description: string, value: unknown): void;
|
|
48
|
+
/** Bridge-friendly: get a value by string name. */
|
|
49
|
+
getByName(name: string): unknown;
|
|
50
|
+
/** Bridge-friendly: check presence by string name. */
|
|
51
|
+
hasByName(name: string): boolean;
|
|
52
|
+
/** Bridge-friendly: subscribe by string name. */
|
|
53
|
+
subscribeByName(name: string, description: string, handler: (value: unknown, name: string) => void): SubscriptionToken;
|
|
54
|
+
/**
|
|
55
|
+
* Bridge-friendly: subscribe to all updates. The handler receives the
|
|
56
|
+
* key's resolved name (or undefined for non-namespaced keys).
|
|
57
|
+
*/
|
|
58
|
+
subscribeAllByName(handler: (value: unknown, name: string | undefined, description: string) => void): SubscriptionToken;
|
|
59
|
+
private ensureNamedKey;
|
|
60
|
+
private notify;
|
|
61
|
+
private rotateStore;
|
|
62
|
+
private nextToken;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=plugin.d.ts.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { Player, PlayerPlugin } from "@player-ui/player";
|
|
2
|
+
import type { ContextKey } from "./types";
|
|
3
|
+
/** Set a single binding in the Player data model. */
|
|
4
|
+
export type SetDataAction = (binding: string, value: unknown) => void;
|
|
5
|
+
/** Transition the running flow using the given transition value. */
|
|
6
|
+
export type TransitionAction = (transition: string) => void;
|
|
7
|
+
/** A single validation, projected to its serializable fields. */
|
|
8
|
+
export type ContextValidation = {
|
|
9
|
+
severity: "error" | "warning";
|
|
10
|
+
message: string;
|
|
11
|
+
displayTarget?: "page" | "section" | "field";
|
|
12
|
+
blocking?: boolean | "once";
|
|
13
|
+
};
|
|
14
|
+
/** Validation state for the running view, keyed by binding. */
|
|
15
|
+
export type ValidationContext = {
|
|
16
|
+
/** Whether the view has no blocking validations (derived, no side effects). */
|
|
17
|
+
canTransition: boolean;
|
|
18
|
+
/** Active validations per binding string. */
|
|
19
|
+
byBinding: Record<string, ReadonlyArray<ContextValidation>>;
|
|
20
|
+
};
|
|
21
|
+
/** Identifier of the currently-running flow. */
|
|
22
|
+
export declare const flowIdContextKey: ContextKey<string>;
|
|
23
|
+
/** Name of the current FSM state within the running flow. */
|
|
24
|
+
export declare const flowStateContextKey: ContextKey<string>;
|
|
25
|
+
/** Identifier of the view currently resolved by the ViewController. */
|
|
26
|
+
export declare const viewIdContextKey: ContextKey<string>;
|
|
27
|
+
/** Full resolved view object for the current FSM state. */
|
|
28
|
+
export declare const viewContextKey: ContextKey<unknown>;
|
|
29
|
+
/** Full data model tree for the running flow. */
|
|
30
|
+
export declare const dataContextKey: ContextKey<unknown>;
|
|
31
|
+
/** Player flow status: not-started, in-progress, completed, or error. */
|
|
32
|
+
export declare const playerStatusContextKey: ContextKey<string>;
|
|
33
|
+
/** Validation state for the running view, keyed by binding. */
|
|
34
|
+
export declare const validationContextKey: ContextKey<ValidationContext>;
|
|
35
|
+
/**
|
|
36
|
+
* Action entry whose value is a callable that sets a binding in the data
|
|
37
|
+
* model. Read via `ctx.get(setDataActionKey)`; absent until a flow is running.
|
|
38
|
+
*/
|
|
39
|
+
export declare const setDataActionKey: ContextKey<SetDataAction>;
|
|
40
|
+
/**
|
|
41
|
+
* Action entry whose value is a callable that transitions the running flow.
|
|
42
|
+
* Read via `ctx.get(transitionActionKey)`; absent until a flow is running.
|
|
43
|
+
*/
|
|
44
|
+
export declare const transitionActionKey: ContextKey<TransitionAction>;
|
|
45
|
+
/**
|
|
46
|
+
* Aggregated snapshot composed from every other StateContextPlugin key.
|
|
47
|
+
*
|
|
48
|
+
* Actions are scoped to the construct they operate on — `transition` lives
|
|
49
|
+
* under `flow`, `set` under `data` — rather than in a flat actions bag. Each
|
|
50
|
+
* is bound to the live controller and is absent until a flow is in-progress.
|
|
51
|
+
*/
|
|
52
|
+
export type PlayerStateContext = {
|
|
53
|
+
status?: string;
|
|
54
|
+
flow: {
|
|
55
|
+
id?: string;
|
|
56
|
+
state?: string;
|
|
57
|
+
/** Transition the running flow (e.g. 'Next'). */
|
|
58
|
+
transition?: TransitionAction;
|
|
59
|
+
};
|
|
60
|
+
view: {
|
|
61
|
+
id?: string;
|
|
62
|
+
resolved?: unknown;
|
|
63
|
+
};
|
|
64
|
+
data: {
|
|
65
|
+
/** Full data model tree for the running flow. */
|
|
66
|
+
model?: unknown;
|
|
67
|
+
/** Set a value in the data model at the given binding. */
|
|
68
|
+
set?: SetDataAction;
|
|
69
|
+
};
|
|
70
|
+
/** Validation state for the running view, keyed by binding. */
|
|
71
|
+
validation: ValidationContext;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Single roll-up key that aggregates every other [[StateContextPlugin]] entry
|
|
75
|
+
* into one object. Backed by a transform — reading recomputes from the latest
|
|
76
|
+
* source values, and subscribers fire whenever any source updates.
|
|
77
|
+
*/
|
|
78
|
+
export declare const playerStateContextKey: ContextKey<PlayerStateContext>;
|
|
79
|
+
/**
|
|
80
|
+
* A consumer plugin that mirrors Player runtime state into the ContextPlugin
|
|
81
|
+
* store. Registers a small, opinionated set of context entries (flow id,
|
|
82
|
+
* current FSM state, view id, full view, data model, status) so external
|
|
83
|
+
* automation/devtools can observe the running Player without tapping every
|
|
84
|
+
* controller hook themselves.
|
|
85
|
+
*
|
|
86
|
+
* Auto-registers a [[ContextPlugin]] if one isn't already on the player.
|
|
87
|
+
*/
|
|
88
|
+
export declare class StateContextPlugin implements PlayerPlugin {
|
|
89
|
+
name: string;
|
|
90
|
+
apply(player: Player): void;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=state-plugin.d.ts.map
|
package/types/store.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ContextEntryDescriptor, ContextKey, ContextTransform, FrozenContextSnapshot } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Mutable per-flow context store. Owned by a single flow lifecycle; the plugin
|
|
4
|
+
* rotates the store on flow end and freezes the prior one into a snapshot.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ContextStore {
|
|
7
|
+
private entries;
|
|
8
|
+
/** source symbol -> set of target symbols (reverse index for invalidation). */
|
|
9
|
+
private dependents;
|
|
10
|
+
register(key: ContextKey): boolean;
|
|
11
|
+
set<Value>(key: ContextKey<Value>, value: Value): void;
|
|
12
|
+
registerTransform<Value>(key: ContextKey<Value>, transform: ContextTransform<Value>): {
|
|
13
|
+
previousSources?: ReadonlyArray<ContextKey>;
|
|
14
|
+
};
|
|
15
|
+
get<Value>(key: ContextKey<Value>): Value | undefined;
|
|
16
|
+
has(key: ContextKey): boolean;
|
|
17
|
+
/** Return the keys that depend on the given source key (direct dependents only). */
|
|
18
|
+
dependentsOf(sourceSymbol: symbol): ReadonlyArray<ContextKey>;
|
|
19
|
+
list(): ReadonlyArray<ContextEntryDescriptor>;
|
|
20
|
+
freeze(meta: {
|
|
21
|
+
flowId?: string;
|
|
22
|
+
endedAt: number;
|
|
23
|
+
}): FrozenContextSnapshot;
|
|
24
|
+
private compute;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=store.d.ts.map
|
package/types/types.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type ContextKey<Value = unknown> = {
|
|
2
|
+
readonly symbol: symbol;
|
|
3
|
+
readonly description: string;
|
|
4
|
+
/** Phantom marker so TS can infer `Value` from a key. Not present at runtime. */
|
|
5
|
+
readonly __value?: Value;
|
|
6
|
+
};
|
|
7
|
+
export type ContextReader = <Value>(key: ContextKey<Value>) => Value | undefined;
|
|
8
|
+
export type ContextTransform<Value> = {
|
|
9
|
+
/** Source keys whose updates should invalidate (and notify subscribers of) this target. */
|
|
10
|
+
sources: ReadonlyArray<ContextKey>;
|
|
11
|
+
/** Compute the target value, reading from other context entries. */
|
|
12
|
+
compute: (read: ContextReader) => Value | undefined;
|
|
13
|
+
};
|
|
14
|
+
export type ContextSubscriber<Value> = (value: Value | undefined, key: ContextKey<Value>) => void;
|
|
15
|
+
export type ContextGlobalSubscriber = (value: unknown, key: ContextKey) => void;
|
|
16
|
+
export type ContextEntryDescriptor = {
|
|
17
|
+
symbol: symbol;
|
|
18
|
+
description: string;
|
|
19
|
+
hasValue: boolean;
|
|
20
|
+
hasTransform: boolean;
|
|
21
|
+
};
|
|
22
|
+
export type SubscriptionToken = `ctx_${number}`;
|
|
23
|
+
export type FrozenContextEntry = {
|
|
24
|
+
readonly symbol: symbol;
|
|
25
|
+
/**
|
|
26
|
+
* The entry's key name (from `nameOfContextKey`), or `undefined` for
|
|
27
|
+
* non-namespaced keys. Unlike `symbol`, this is a plain string that survives
|
|
28
|
+
* the native bridge, so native consumers read snapshot entries by name.
|
|
29
|
+
*/
|
|
30
|
+
readonly name: string | undefined;
|
|
31
|
+
readonly description: string;
|
|
32
|
+
readonly value: unknown;
|
|
33
|
+
};
|
|
34
|
+
export type FrozenContextSnapshot = {
|
|
35
|
+
readonly flowId?: string;
|
|
36
|
+
readonly endedAt: number;
|
|
37
|
+
readonly entries: ReadonlyArray<FrozenContextEntry>;
|
|
38
|
+
/**
|
|
39
|
+
* Read a frozen entry by its key — the same typed access as live context.
|
|
40
|
+
* Returns `undefined` if the key was not present when the snapshot froze.
|
|
41
|
+
* Function-valued entries return their tombstone (a throwing callable).
|
|
42
|
+
*/
|
|
43
|
+
get<Value>(key: ContextKey<Value>): Value | undefined;
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=types.d.ts.map
|
package/types/utils.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Player } from "@player-ui/player";
|
|
2
|
+
import { ContextPlugin } from "./plugin";
|
|
3
|
+
/**
|
|
4
|
+
* Returns the existing ContextPlugin or creates and registers a new one.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getContextPlugin(player: Player): ContextPlugin;
|
|
7
|
+
//# sourceMappingURL=utils.d.ts.map
|