mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
85 lines
3.0 KiB
JavaScript
85 lines
3.0 KiB
JavaScript
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
|