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
+93
View File
@@ -0,0 +1,93 @@
import { Machine, MachineSnapshot } from "../machine.js";
//#region src/core/reactors/create-machine-reactor.d.ts
/**
* A reactive state-deriving function used in the `monitor` field.
*
* Returns the target state the reactor should be in. Any signals read inside
* the fn body create reactive dependencies — the framework re-evaluates it when
* those signals change and automatically calls `transition()` when the returned
* state differs from the current one.
*/
type ReactorDeriveFn<State extends string> = () => State;
/**
* An effect function used in reactor `entry` and `effects` blocks.
*
* May return a cleanup function that runs before each re-evaluation and on
* state exit (including destroy).
*/
type ReactorEffectFn = () => (() => void) | {
abort(): void;
} | void;
/**
* Per-state effect grouping for a single reactor state.
*
* - `entry` effects run once on state entry. The fn body is automatically
* untracked — no `untrack()` calls are needed inside. Use this for
* one-time setup: reading current values, attaching event listeners, etc.
* - `effects` run on state entry and re-run whenever a signal read inside
* the fn body changes. Use `untrack()` for reads you do not want to track.
* Use this for work that must stay in sync with reactive state.
*
* Both are optional; pass `{}` for states with no effects.
*/
type ReactorStateDefinition = {
entry?: ReactorEffectFn | ReactorEffectFn[];
effects?: ReactorEffectFn | ReactorEffectFn[];
};
/**
* Full reactor definition passed to `createMachineReactor`.
*
* `State` is the set of domain-meaningful states. `'destroying'` and
* `'destroyed'` are always added by the framework as implicit terminal states —
* do not include them here.
*/
type ReactorDefinition<State extends string> = {
/** Initial state. */
initial: State;
/**
* Reactive state derivation. Registered before per-state effects — the
* ordering guarantee ensures transitions fired here take effect before
* per-state effects re-evaluate in the same flush.
*/
monitor?: ReactorDeriveFn<State> | ReactorDeriveFn<State>[];
/**
* Per-state effect groupings. Every valid state must be declared — pass `{}`
* for states with no effects. `entry` and `effects` each become independent
* `effect()` calls gated on that state, with their own cleanup lifecycles.
*/
states: Record<State, ReactorStateDefinition>;
};
/** Live reactor instance returned by `createMachineReactor`. */
type Reactor<State extends string> = Machine<MachineSnapshot<State>>;
/**
* Creates a reactive Reactor from a declarative definition.
*
* A Reactor is driven by subscriptions to external signals rather than
* imperative messages. Each state holds an array of effect functions —
* every element becomes one independent `effect()` call gated on that state,
* with its own dependency tracking and cleanup lifecycle.
*
* `'destroying'` and `'destroyed'` are always implicit terminal states.
* `destroy()` transitions through both in sequence: `'destroying'` first (for
* potential async teardown in a future extension), then immediately `'destroyed'`
* for the synchronous base case. Active effect cleanups fire via disposal.
*
* @example
* const reactor = createMachineReactor({
* initial: 'waiting',
* monitor: () => srcSignal.get() ? 'active' : 'waiting',
* states: {
* active: {
* // entry: runs once on state entry; fn body is automatically untracked.
* entry: () => listen(el, 'play', handler),
* // effects: re-runs whenever tracked signals change.
* effects: () => { currentTimeSignal.get(); return cleanup; },
* },
* waiting: {},
* }
* });
*/
declare function createMachineReactor<State extends string>(def: ReactorDefinition<State>): Reactor<State | 'destroying' | 'destroyed'>;
//#endregion
export { Reactor, ReactorDefinition, ReactorDeriveFn, ReactorEffectFn, ReactorStateDefinition, createMachineReactor };
//# sourceMappingURL=create-machine-reactor.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"create-machine-reactor.d.ts","names":[],"sources":["../../../../src/core/reactors/create-machine-reactor.ts"],"mappings":";;;;;;;;;;KAiBY,gBAAgB,8BAA8B;;;;;;;KAQ9C;EAAyC;;;;;;;;;;;;;;KAczC;EACV,QAAQ,kBAAkB;EAC1B,UAAU,kBAAkB;;;;;;;;;KAUlB,kBAAkB;;EAE5B,SAAS;;;;;;EAMT,UAAU,gBAAgB,SAAS,gBAAgB;;;;;;EAMnD,QAAQ,OAAO,OAAO;;;KAQZ,QAAQ,wBAAwB,QAAQ,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAwCpD,qBAAqB,sBACnC,KAAK,kBAAkB,SACtB,QAAQ"}
+83
View File
@@ -0,0 +1,83 @@
import { untrack } from "../signals/primitives.js";
import { effect } from "../signals/effect.js";
import { createMachineCore } from "../machine.js";
//#region src/core/reactors/create-machine-reactor.ts
const toArray = (x) => x === void 0 ? [] : Array.isArray(x) ? x : [x];
/**
* Creates a reactive Reactor from a declarative definition.
*
* A Reactor is driven by subscriptions to external signals rather than
* imperative messages. Each state holds an array of effect functions —
* every element becomes one independent `effect()` call gated on that state,
* with its own dependency tracking and cleanup lifecycle.
*
* `'destroying'` and `'destroyed'` are always implicit terminal states.
* `destroy()` transitions through both in sequence: `'destroying'` first (for
* potential async teardown in a future extension), then immediately `'destroyed'`
* for the synchronous base case. Active effect cleanups fire via disposal.
*
* @example
* const reactor = createMachineReactor({
* initial: 'waiting',
* monitor: () => srcSignal.get() ? 'active' : 'waiting',
* states: {
* active: {
* // entry: runs once on state entry; fn body is automatically untracked.
* entry: () => listen(el, 'play', handler),
* // effects: re-runs whenever tracked signals change.
* effects: () => { currentTimeSignal.get(); return cleanup; },
* },
* waiting: {},
* }
* });
*/
function createMachineReactor(def) {
const { snapshotSignal, getState, transition } = createMachineCore({ value: def.initial });
const effectDisposals = [];
const wrapResult = (result) => {
if (!result) return void 0;
if (typeof result === "function") return result;
return () => result.abort();
};
const untracked = (baseCall) => () => untrack(baseCall);
const isTerminal = (snapshot) => snapshot.value === "destroying" || snapshot.value === "destroyed";
const descriptors = [...toArray(def.monitor).map((fn) => ({
fn: () => {
const target = fn();
if (target !== getState()) transition(target);
},
shouldSkip: isTerminal
})), ...Object.entries(def.states).flatMap(([state, stateDef]) => {
const isNotState = (snapshot) => snapshot.value !== state;
return [...toArray(stateDef.entry).map((fn) => ({
fn,
shouldSkip: isNotState,
toFnCall: untracked
})), ...toArray(stateDef.effects).map((fn) => ({
fn,
shouldSkip: isNotState
}))];
})];
const toEffect = ({ fn, shouldSkip, toFnCall = (baseCall) => baseCall }) => effect(() => {
if (shouldSkip(snapshotSignal.get())) return;
const baseCall = () => fn();
return wrapResult(toFnCall(baseCall)());
});
effectDisposals.push(...descriptors.map(toEffect));
return {
get snapshot() {
return snapshotSignal;
},
destroy() {
const state = getState();
if (state === "destroying" || state === "destroyed") return;
transition("destroying");
transition("destroyed");
for (const dispose of effectDisposals) dispose();
}
};
}
//#endregion
export { createMachineReactor };
//# sourceMappingURL=create-machine-reactor.js.map
File diff suppressed because one or more lines are too long