build(spf): build26 from 45504a2b

This commit is contained in:
publish
2026-08-05 18:57:39 +02:00
commit 8920135306
424 changed files with 17907 additions and 0 deletions
+85
View File
@@ -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
View File
@@ -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
+1
View File
@@ -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"}