mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
build(spf): build26 from 45504a2b
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import { signal } from "../signals/primitives.js";
|
||||
//#region src/core/composition/create-composition.ts
|
||||
/**
|
||||
* Create a composition from a set of behaviors.
|
||||
*
|
||||
* Composition unions the behaviors' declared `stateKeys` / `contextKeys`
|
||||
* to know which signals to create. Each signal is seeded from
|
||||
* `initialState` / `initialContext` when supplied, defaulting to
|
||||
* `undefined`. Behaviors are responsible for writing their own slots
|
||||
* once their preconditions are met.
|
||||
*
|
||||
* Cross-behavior type conflicts (e.g. two behaviors disagreeing on a
|
||||
* field's type) surface as a compose-time type error via
|
||||
* `ValidateComposition`.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const composition = createComposition([resolvePresentation, switchVideoTrack], {
|
||||
* config: { parsePresentation: parseMultivariantPlaylist, initialBandwidth: 2_000_000 },
|
||||
* initialState: { bandwidthState: { fastEstimate: 0, ... } },
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
/**
|
||||
* Create a typed signal map for a given set of keys, seeded from an
|
||||
* optional partial initial value.
|
||||
*
|
||||
* Pipeline: `Set` dedupes the iterable (insertion order preserved, so
|
||||
* first occurrence wins) → `Object.fromEntries` materializes one
|
||||
* `signal()` per unique key, seeded from `initial[key]` or `undefined`.
|
||||
*
|
||||
* Per-key value types live in TypeScript only — at runtime every signal
|
||||
* is `Signal<unknown>`. The boundary cast at the return narrows the wide
|
||||
* `Record<PropertyKey, Signal<unknown>>` shape to the caller's expected
|
||||
* per-key types from `S`.
|
||||
*
|
||||
* Used by `createComposition` to derive engine state/context maps from
|
||||
* the union of behaviors' declared `stateKeys` / `contextKeys`.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* interface State { count?: number; label?: string }
|
||||
* const state = buildSignalMap<State>(['count', 'label'], { count: 5 });
|
||||
* state.count.get(); // 5
|
||||
* state.label.get(); // undefined
|
||||
* ```
|
||||
*/
|
||||
function buildSignalMap(keys, initial) {
|
||||
const init = initial;
|
||||
const uniqueKeys = new Set(keys);
|
||||
return Object.fromEntries([...uniqueKeys].map((key) => [key, signal(init[key])]));
|
||||
}
|
||||
function createComposition(behaviors, options) {
|
||||
const validBehaviors = behaviors;
|
||||
const state = buildSignalMap(validBehaviors.flatMap((b) => b.stateKeys), options?.initialState ?? {});
|
||||
const context = buildSignalMap(validBehaviors.flatMap((b) => b.contextKeys), options?.initialContext ?? {});
|
||||
const deps = {
|
||||
state,
|
||||
context,
|
||||
config: options?.config ?? {}
|
||||
};
|
||||
const cleanups = validBehaviors.map((behavior) => behavior.setup(deps));
|
||||
return {
|
||||
state,
|
||||
context,
|
||||
async destroy() {
|
||||
const results = [];
|
||||
for (const cleanup of cleanups) {
|
||||
if (cleanup == null) continue;
|
||||
if (typeof cleanup === "function") results.push(cleanup());
|
||||
else if ("destroy" in cleanup) results.push(cleanup.destroy());
|
||||
}
|
||||
await Promise.all(results);
|
||||
for (const sig of Object.values(state)) sig.set(void 0);
|
||||
for (const sig of Object.values(context)) sig.set(void 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
function defineBehavior(behavior) {
|
||||
return behavior;
|
||||
}
|
||||
//#endregion
|
||||
export { buildSignalMap, createComposition, defineBehavior };
|
||||
|
||||
//# sourceMappingURL=create-composition.js.map
|
||||
File diff suppressed because one or more lines are too long
+39
@@ -0,0 +1,39 @@
|
||||
//#region src/core/composition/share-signals.ts
|
||||
/**
|
||||
* Behavior factory that hands the composition's signal refs to a
|
||||
* consumer-supplied callback (`config.onSignalsReady`) at setup time.
|
||||
*
|
||||
* Generic over `S` and `C` — the caller instantiates with their own
|
||||
* state/context types, and the callback's parameter shape is fully
|
||||
* type-driven from those. Suitable for both reads and writes (per-slot
|
||||
* intent can be expressed by typing captured refs as `Signal<T>` or
|
||||
* `ReadonlySignal<T>` at the call site).
|
||||
*
|
||||
* By default declares no keys; the composition's state/context maps come from
|
||||
* other behaviors. Pass `inputStateKeys` / `inputContextKeys` to *materialize*
|
||||
* consumer-input slots that no other behavior produces — a slot the consumer
|
||||
* writes (e.g. `userAudioTrackSelection`) but only a rule reads. shareSignals
|
||||
* is the consumer boundary, so it's the natural place to bring those slots into
|
||||
* existence; readers then treat them as optional.
|
||||
*
|
||||
* Uses a `Behavior<>` literal (not `defineBehavior`) so its (possibly empty,
|
||||
* possibly partial) key arrays don't trip the exhaustiveness check — the
|
||||
* setup-param state/context shapes describe what the callback receives (the
|
||||
* full `S` / `C`), not the subset this behavior materializes.
|
||||
*/
|
||||
function makeShareSignals(inputStateKeys = [], inputContextKeys = []) {
|
||||
return {
|
||||
stateKeys: inputStateKeys,
|
||||
contextKeys: inputContextKeys,
|
||||
setup: ({ state, context, config }) => {
|
||||
config.onSignalsReady?.({
|
||||
state,
|
||||
context
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
//#endregion
|
||||
export { makeShareSignals };
|
||||
|
||||
//# sourceMappingURL=share-signals.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"share-signals.js","names":[],"sources":["../../../../src/core/composition/share-signals.ts"],"sourcesContent":["import type { Behavior, ContextSignals, StateSignals } from './create-composition';\n\n/**\n * Config consumed by the `shareSignals` behavior.\n *\n * The callback fires once during composition setup with the composition's\n * state and context signal refs. Capture them to drive the composition\n * externally (writes) or observe its state (reads).\n *\n * The callback runs while other behaviors are still in their setup phase —\n * for the typical \"capture refs, use later\" pattern this is fine (signal\n * refs are stable identities), but reading inside the callback may yield\n * only initial-seed values rather than what later behaviors write.\n */\nexport interface ShareSignalsConfig<S extends object, C extends object> {\n onSignalsReady?: (signals: { state: StateSignals<S>; context: ContextSignals<C> }) => void;\n}\n\n/**\n * Behavior factory that hands the composition's signal refs to a\n * consumer-supplied callback (`config.onSignalsReady`) at setup time.\n *\n * Generic over `S` and `C` — the caller instantiates with their own\n * state/context types, and the callback's parameter shape is fully\n * type-driven from those. Suitable for both reads and writes (per-slot\n * intent can be expressed by typing captured refs as `Signal<T>` or\n * `ReadonlySignal<T>` at the call site).\n *\n * By default declares no keys; the composition's state/context maps come from\n * other behaviors. Pass `inputStateKeys` / `inputContextKeys` to *materialize*\n * consumer-input slots that no other behavior produces — a slot the consumer\n * writes (e.g. `userAudioTrackSelection`) but only a rule reads. shareSignals\n * is the consumer boundary, so it's the natural place to bring those slots into\n * existence; readers then treat them as optional.\n *\n * Uses a `Behavior<>` literal (not `defineBehavior`) so its (possibly empty,\n * possibly partial) key arrays don't trip the exhaustiveness check — the\n * setup-param state/context shapes describe what the callback receives (the\n * full `S` / `C`), not the subset this behavior materializes.\n */\nexport function makeShareSignals<S extends object, C extends object>(\n inputStateKeys: readonly (keyof S)[] = [],\n inputContextKeys: readonly (keyof C)[] = []\n): Behavior<StateSignals<S>, ContextSignals<C>, ShareSignalsConfig<S, C>> {\n return {\n stateKeys: inputStateKeys,\n contextKeys: inputContextKeys,\n setup: ({ state, context, config }) => {\n config.onSignalsReady?.({ state, context });\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAwCA,SAAgB,iBACd,iBAAuC,CAAC,GACxC,mBAAyC,CAAC,GAC8B;CACxE,OAAO;EACL,WAAW;EACX,aAAa;EACb,QAAQ,EAAE,OAAO,SAAS,aAAa;GACrC,OAAO,iBAAiB;IAAE;IAAO;GAAQ,CAAC;EAC5C;CACF;AACF"}
|
||||
Reference in New Issue
Block a user