@directive-run/core 1.17.2 → 1.18.0
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/adapter-utils.d.cts +1 -1
- package/dist/adapter-utils.d.ts +1 -1
- package/dist/chunk-3LBPXJO2.cjs +3 -0
- package/dist/chunk-3LBPXJO2.cjs.map +1 -0
- package/dist/chunk-4QCKAJCX.cjs +2 -0
- package/dist/chunk-4QCKAJCX.cjs.map +1 -0
- package/dist/chunk-NPKUAG3Q.js +3 -0
- package/dist/chunk-NPKUAG3Q.js.map +1 -0
- package/dist/{chunk-F5JPF4YX.js → chunk-SGTWS4S2.js} +3 -3
- package/dist/chunk-SGTWS4S2.js.map +1 -0
- package/dist/chunk-WKCMOU2F.js +2 -0
- package/dist/chunk-WKCMOU2F.js.map +1 -0
- package/dist/{chunk-SUCLMJSZ.cjs → chunk-YYBU4TCO.cjs} +3 -3
- package/dist/chunk-YYBU4TCO.cjs.map +1 -0
- package/dist/{index-D_oNDe4l.d.cts → index-DW5awWlo.d.ts} +25 -2
- package/dist/{index-c5wGodzh.d.ts → index-uuMlmmrP.d.cts} +25 -2
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -6
- package/dist/index.d.ts +21 -6
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/internals.cjs +1 -1
- package/dist/internals.d.cts +204 -5
- package/dist/internals.d.ts +204 -5
- package/dist/internals.js +1 -1
- package/dist/plugins/index.cjs +2 -2
- package/dist/plugins/index.cjs.map +1 -1
- package/dist/plugins/index.d.cts +2 -2
- package/dist/plugins/index.d.ts +2 -2
- package/dist/plugins/index.js +1 -1
- package/dist/plugins/index.js.map +1 -1
- package/dist/{plugins-BpNMkJRw.d.ts → plugins-xpm4B7Nt.d.cts} +550 -1
- package/dist/{plugins-BpNMkJRw.d.cts → plugins-xpm4B7Nt.d.ts} +550 -1
- package/dist/{predicate-BinuVL33.d.ts → predicate-CMGFjIeu.d.cts} +2 -2
- package/dist/{predicate-DN1MnDbi.d.cts → predicate-fpUoy77T.d.ts} +2 -2
- package/dist/system-6D4C5QZL.cjs +2 -0
- package/dist/{system-RHSYFDOB.cjs.map → system-6D4C5QZL.cjs.map} +1 -1
- package/dist/system-ATW7RUEU.js +2 -0
- package/dist/{system-MMCZKPI2.js.map → system-ATW7RUEU.js.map} +1 -1
- package/dist/testing.cjs +1 -1
- package/dist/testing.d.cts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +1 -1
- package/dist/worker.cjs +1 -1
- package/dist/worker.d.cts +1 -1
- package/dist/worker.d.ts +1 -1
- package/dist/worker.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-4MTC4BSK.cjs +0 -2
- package/dist/chunk-4MTC4BSK.cjs.map +0 -1
- package/dist/chunk-F5JPF4YX.js.map +0 -1
- package/dist/chunk-Q5UTBMX2.cjs +0 -3
- package/dist/chunk-Q5UTBMX2.cjs.map +0 -1
- package/dist/chunk-RWJV7QLM.js +0 -3
- package/dist/chunk-RWJV7QLM.js.map +0 -1
- package/dist/chunk-SUCLMJSZ.cjs.map +0 -1
- package/dist/chunk-YYFRZJO2.js +0 -2
- package/dist/chunk-YYFRZJO2.js.map +0 -1
- package/dist/system-MMCZKPI2.js +0 -2
- package/dist/system-RHSYFDOB.cjs +0 -2
|
@@ -519,6 +519,20 @@ type RequirementPayloadSchema = Record<string, SchemaType<unknown>>;
|
|
|
519
519
|
* ```
|
|
520
520
|
*/
|
|
521
521
|
type RequirementsSchema = Record<string, RequirementPayloadSchema>;
|
|
522
|
+
/**
|
|
523
|
+
* Infer the requirement payload type from a requirement payload schema.
|
|
524
|
+
*/
|
|
525
|
+
type InferRequirementPayload<P extends RequirementPayloadSchema> = {
|
|
526
|
+
[K in keyof P]: P[K] extends SchemaType<infer T> ? T : never;
|
|
527
|
+
};
|
|
528
|
+
/**
|
|
529
|
+
* Infer all requirements from a requirements schema as a discriminated union.
|
|
530
|
+
*/
|
|
531
|
+
type InferRequirementsFromSchema<R extends RequirementsSchema> = {
|
|
532
|
+
[K in keyof R]: {
|
|
533
|
+
type: K;
|
|
534
|
+
} & InferRequirementPayload<R[K]>;
|
|
535
|
+
}[keyof R];
|
|
522
536
|
/**
|
|
523
537
|
* Requirement output from a constraint - can be single, array, or null.
|
|
524
538
|
* - Single requirement: `{ type: "RESTOCK", sku: "ABC" }`
|
|
@@ -689,6 +703,277 @@ interface EffectDef<S extends Schema> {
|
|
|
689
703
|
/** Map of effect definitions */
|
|
690
704
|
type EffectsDef<S extends Schema> = Record<string, EffectDef<S>>;
|
|
691
705
|
|
|
706
|
+
/**
|
|
707
|
+
* Source Types — typed external event sources.
|
|
708
|
+
*
|
|
709
|
+
* A `source` is the inbound dual of an {@link EffectDef effect}:
|
|
710
|
+
*
|
|
711
|
+
* - **Effects** are outbound. They observe fact changes and push to the
|
|
712
|
+
* external world (DOM mutation, WebSocket send, logging, analytics).
|
|
713
|
+
* Effects cannot dispatch `system.events`.
|
|
714
|
+
* - **Sources** are inbound. They subscribe to an external event stream
|
|
715
|
+
* (Supabase realtime channel, WebSocket message stream, browser event,
|
|
716
|
+
* polling timer) and publish *into* the system's event queue. Sources
|
|
717
|
+
* never read facts.
|
|
718
|
+
*
|
|
719
|
+
* Sources sit on the same lifecycle plane as effects but mount once per
|
|
720
|
+
* system instance (not on every fact change). `attach` runs at
|
|
721
|
+
* `system.start()`; the returned unsubscribe runs at `system.stop()`.
|
|
722
|
+
*
|
|
723
|
+
* ## Why this exists
|
|
724
|
+
*
|
|
725
|
+
* The "hook-as-bridge" pattern — a `useEffect` that owns an external
|
|
726
|
+
* subscription, maps incoming payloads, and dispatches `sys.events.X()` —
|
|
727
|
+
* shows up in 7+ call sites across the Sizls workspace (Minglingo's
|
|
728
|
+
* `useActiveRoundSystem`, `useBattleRoyaleSystem`, `eventClaims.realtime.ts`,
|
|
729
|
+
* etc.). Each site re-derives the lifecycle plumbing.
|
|
730
|
+
*
|
|
731
|
+
* A `source` declares that plumbing as a first-class module field. The
|
|
732
|
+
* engine owns the lifecycle; the author owns only the subscribe + publish
|
|
733
|
+
* logic. The same shape works in non-React consumers (Workers, node, tests).
|
|
734
|
+
*
|
|
735
|
+
* @example Supabase realtime channel
|
|
736
|
+
* ```typescript
|
|
737
|
+
* sources: {
|
|
738
|
+
* gameUpdates: {
|
|
739
|
+
* attach: (publish) => {
|
|
740
|
+
* const channel = supabase.channel(`game:${gameId}`).on(
|
|
741
|
+
* 'postgres_changes',
|
|
742
|
+
* { event: 'UPDATE', table: 'games', filter: `id=eq.${gameId}` },
|
|
743
|
+
* (payload) => publish('REALTIME_GAME_UPDATE', { gameState: map(payload.new) }),
|
|
744
|
+
* ).subscribe();
|
|
745
|
+
* return () => channel.unsubscribe();
|
|
746
|
+
* },
|
|
747
|
+
* },
|
|
748
|
+
* }
|
|
749
|
+
* ```
|
|
750
|
+
*
|
|
751
|
+
* @example Polling timer
|
|
752
|
+
* ```typescript
|
|
753
|
+
* sources: {
|
|
754
|
+
* heartbeat: {
|
|
755
|
+
* attach: (publish) => {
|
|
756
|
+
* const id = setInterval(() => publish('HEARTBEAT_TICK', undefined), 5000);
|
|
757
|
+
* return () => clearInterval(id);
|
|
758
|
+
* },
|
|
759
|
+
* },
|
|
760
|
+
* }
|
|
761
|
+
* ```
|
|
762
|
+
*
|
|
763
|
+
* @example No-op (test) source — note the `() => () => undefined` shape:
|
|
764
|
+
* `attach` MUST return a function. Returning `() => undefined` (one level)
|
|
765
|
+
* would return `undefined`, which the manager logs as "did not return an
|
|
766
|
+
* unsubscribe function" and skips. Always two levels.
|
|
767
|
+
* ```typescript
|
|
768
|
+
* sources: {
|
|
769
|
+
* ignored: { attach: () => () => undefined },
|
|
770
|
+
* // ^^^^^^^^^^^^^^^^^^^
|
|
771
|
+
* // | the function that's the unsubscribe
|
|
772
|
+
* // the function that's `attach`'s return value
|
|
773
|
+
* }
|
|
774
|
+
* ```
|
|
775
|
+
*
|
|
776
|
+
* @example Typed publish — wrap once for compile-time event-name + payload safety
|
|
777
|
+
* ```typescript
|
|
778
|
+
* import type { SourcePublish } from '@directive-run/core';
|
|
779
|
+
*
|
|
780
|
+
* function createPublisher(publish: SourcePublish) {
|
|
781
|
+
* return {
|
|
782
|
+
* TICK: (delta: number) => publish('TICK', { delta }),
|
|
783
|
+
* HEARTBEAT: () => publish('HEARTBEAT'),
|
|
784
|
+
* };
|
|
785
|
+
* }
|
|
786
|
+
*
|
|
787
|
+
* sources: {
|
|
788
|
+
* ticker: {
|
|
789
|
+
* attach: (publish) => {
|
|
790
|
+
* const p = createPublisher(publish);
|
|
791
|
+
* const id = setInterval(() => p.TICK(1), 1000); // ← typed
|
|
792
|
+
* return () => clearInterval(id);
|
|
793
|
+
* },
|
|
794
|
+
* },
|
|
795
|
+
* }
|
|
796
|
+
* ```
|
|
797
|
+
*/
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Cleanup function returned by a source's `attach`.
|
|
801
|
+
*
|
|
802
|
+
* Called once at `system.stop()`. On the next `system.start()` (the manager
|
|
803
|
+
* supports the full start → stop → start lifecycle), the source's `attach`
|
|
804
|
+
* runs again and returns a FRESH unsubscribe. Implementations SHOULD guard
|
|
805
|
+
* repeated teardown defensively, but the manager will only invoke the
|
|
806
|
+
* captured unsubscribe once per attach cycle.
|
|
807
|
+
*
|
|
808
|
+
* The name is intentional — sources are typically wrapping a stateful
|
|
809
|
+
* external subscription (Supabase channel, WebSocket, browser listener), so
|
|
810
|
+
* "unsubscribe" is the verb authors recognise. Note that effects use
|
|
811
|
+
* `EffectCleanup` instead; the asymmetric naming captures the asymmetric
|
|
812
|
+
* concern (an effect cleans up a side effect; a source unsubscribes from
|
|
813
|
+
* an external stream).
|
|
814
|
+
*/
|
|
815
|
+
type SourceUnsubscribe = () => void | Promise<void>;
|
|
816
|
+
/**
|
|
817
|
+
* Typed event dispatcher passed to a source's `attach`. Calls into the same
|
|
818
|
+
* dispatch queue used by `system.events.X(payload)`.
|
|
819
|
+
*
|
|
820
|
+
* The string event name is unchecked at this level (kept as `string` to
|
|
821
|
+
* avoid coupling the source primitive to the consuming module's event
|
|
822
|
+
* schema). The authoring module typically wraps `publish` in a typed helper
|
|
823
|
+
* — see the {@link SourceDef} example for the recommended pattern. The
|
|
824
|
+
* runtime semantics:
|
|
825
|
+
*
|
|
826
|
+
* - `publish('KNOWN_EVENT', payload)` — dispatches into the module's event
|
|
827
|
+
* handler. The payload is augmented with `{ type: eventName, ...payload }`
|
|
828
|
+
* before passing to the handler (same shape as `system.events.X(payload)`).
|
|
829
|
+
* - `publish('UNKNOWN_EVENT', ...)` — in development, logs a
|
|
830
|
+
* `[Directive] Unknown event type` warning and drops the dispatch.
|
|
831
|
+
* In production, silently drops. **This is a footgun for sources** —
|
|
832
|
+
* strongly prefer wrapping `publish` in a typed factory (per the example).
|
|
833
|
+
* - `publish(event, undefined)` — dispatches with an empty payload object.
|
|
834
|
+
* - Calling `publish` after `system.destroy()` — silently no-ops. The
|
|
835
|
+
* engine guards against post-destroy dispatch so stale source callbacks
|
|
836
|
+
* cannot mutate a torn-down store.
|
|
837
|
+
*/
|
|
838
|
+
/**
|
|
839
|
+
* Type-wrapped as an interface (rather than a bare function type) so
|
|
840
|
+
* additive minors can attach optional methods (`error`, `complete` —
|
|
841
|
+
* see RFC 0008 Observer-protocol posture) without a major bump. The
|
|
842
|
+
* call signature is unchanged: existing `publish('EVENT', payload)`
|
|
843
|
+
* call sites keep working.
|
|
844
|
+
*/
|
|
845
|
+
interface SourcePublish {
|
|
846
|
+
(event: string, payload?: unknown): void;
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* A source definition — attaches an external event stream to the system
|
|
850
|
+
* lifecycle.
|
|
851
|
+
*
|
|
852
|
+
* ## Lifecycle
|
|
853
|
+
*
|
|
854
|
+
* - `attach(publish)` runs once at `system.start()`. It is sync.
|
|
855
|
+
* - The returned `SourceUnsubscribe` runs at `system.stop()`.
|
|
856
|
+
* - Multiple sources per module are supported; ordering is registration
|
|
857
|
+
* order at `attach`, reverse-registration order at unsubscribe.
|
|
858
|
+
*
|
|
859
|
+
* ## Restrictions
|
|
860
|
+
*
|
|
861
|
+
* - `attach` is **synchronous**. Returning a `Promise<Unsubscribe>` does NOT
|
|
862
|
+
* work — the engine discards the Promise and treats it as "no cleanup
|
|
863
|
+
* function returned" (logs an error + skips the source). Do async setup
|
|
864
|
+
* inside the subscription's own internals; `attach` returns immediately
|
|
865
|
+
* with the synchronous unsubscribe function.
|
|
866
|
+
* - Sources cannot subscribe to system facts. Use {@link EffectDef effects}
|
|
867
|
+
* for fact-reactive behavior. If you need to re-subscribe when a fact
|
|
868
|
+
* changes, use `system.registerModule()` to swap the source's owning
|
|
869
|
+
* module, OR drive the subscription via an effect that owns its own
|
|
870
|
+
* channel.
|
|
871
|
+
* - The publish callback dispatches events normally — resolvers, fact
|
|
872
|
+
* handlers, and downstream effects all run. Authors SHOULD throttle /
|
|
873
|
+
* debounce inside the source if the inbound rate may exceed the system's
|
|
874
|
+
* reconciliation budget.
|
|
875
|
+
* - **Don't subscribe to the same external channel from both a source AND
|
|
876
|
+
* an effect.** The effect will re-run on fact changes, the source mounts
|
|
877
|
+
* once — you'll get 2× messages with silent duplicates. Pick one.
|
|
878
|
+
*/
|
|
879
|
+
/**
|
|
880
|
+
* Source-side runtime-error reporter. Optional second argument to
|
|
881
|
+
* `attach` per RFC 0008. Authors call this when the underlying stream
|
|
882
|
+
* errors mid-flight (WebSocket disconnect, Supabase channel goes
|
|
883
|
+
* stale, polling fetch throws) instead of publishing magic event names
|
|
884
|
+
* like `STREAM_ERROR`. The manager forwards the error through the same
|
|
885
|
+
* sinks as attach/cleanup failures, with `phase: "runtime"`.
|
|
886
|
+
*
|
|
887
|
+
* @example
|
|
888
|
+
* ```ts
|
|
889
|
+
* sources: {
|
|
890
|
+
* ws: {
|
|
891
|
+
* attach: (publish, reportError) => {
|
|
892
|
+
* const sock = new WebSocket(url);
|
|
893
|
+
* sock.addEventListener('error', () => reportError(new Error('WS error')));
|
|
894
|
+
* sock.addEventListener('message', (e) => publish('MSG', JSON.parse(e.data)));
|
|
895
|
+
* return () => sock.close();
|
|
896
|
+
* },
|
|
897
|
+
* },
|
|
898
|
+
* }
|
|
899
|
+
* ```
|
|
900
|
+
*/
|
|
901
|
+
type SourceReportError = (error: Error) => void;
|
|
902
|
+
interface SourceDef {
|
|
903
|
+
/**
|
|
904
|
+
* Mount the source against the system. Runs once at `system.start()`.
|
|
905
|
+
*
|
|
906
|
+
* @param publish - dispatch typed events into the system's event queue.
|
|
907
|
+
* @param reportError - report a runtime error from the source's
|
|
908
|
+
* underlying stream. Fires `source.error` observation events with
|
|
909
|
+
* `phase: "runtime"` (distinct from `"attach"` and `"cleanup"`)
|
|
910
|
+
* so observers can attribute the failure correctly. Optional —
|
|
911
|
+
* sources that never error mid-flight don't need it.
|
|
912
|
+
* @returns a cleanup function that runs at `system.stop()`.
|
|
913
|
+
*/
|
|
914
|
+
attach: (publish: SourcePublish, reportError?: SourceReportError) => SourceUnsubscribe;
|
|
915
|
+
/** Optional metadata for debugging and devtools (never read on hot path). */
|
|
916
|
+
meta?: DefinitionMeta;
|
|
917
|
+
/**
|
|
918
|
+
* How the manager absorbs publishes that would overwhelm the
|
|
919
|
+
* reconcile loop. Default: `"none"` — every publish dispatches
|
|
920
|
+
* straight through to the engine.
|
|
921
|
+
*
|
|
922
|
+
* Set `"lastWriteWins"` for high-frequency sources (cursor movement,
|
|
923
|
+
* sensor telemetry, channel storms). The manager coalesces publishes
|
|
924
|
+
* with the same event name within a single microtask: only the last
|
|
925
|
+
* payload of the cycle dispatches; earlier ones bump `dropCount` and
|
|
926
|
+
* record `lastDropReason: "coalesced"` per source so operators can
|
|
927
|
+
* see the rate of debouncing on `system.inspect().sources`.
|
|
928
|
+
*
|
|
929
|
+
* `"all"` is a no-op equivalent to `"none"` — it names the intent
|
|
930
|
+
* (no coalesce, every publish counts) for readers.
|
|
931
|
+
*
|
|
932
|
+
* Choose `"none"` (default) for low-frequency lifecycle sources
|
|
933
|
+
* (MCP connect, DO alarm, WebSocket open/close). Choose
|
|
934
|
+
* `"lastWriteWins"` for any source that could publish faster than
|
|
935
|
+
* the reconcile loop can drain. See `RFC 0007` for throughput
|
|
936
|
+
* budgets per coalesce strategy and the rationale.
|
|
937
|
+
*
|
|
938
|
+
* Coalescing applies per-event-name: two different event names from
|
|
939
|
+
* the same source coalesce independently, so a `"priceTick"` storm
|
|
940
|
+
* doesn't drop a one-shot `"connected"` event.
|
|
941
|
+
*
|
|
942
|
+
* **Limitation:** the coalesce STRATEGY is uniform across all event
|
|
943
|
+
* names on a source — you can't mix (e.g. `lastWriteWins` for
|
|
944
|
+
* `priceTick` and `none` for `connected` from the same source).
|
|
945
|
+
* Workaround: split into two source declarations on the module if
|
|
946
|
+
* the strategies must differ. A future RFC may add per-event-name
|
|
947
|
+
* strategy overrides.
|
|
948
|
+
*/
|
|
949
|
+
coalesce?: "none" | "lastWriteWins" | "all";
|
|
950
|
+
/**
|
|
951
|
+
* Called when the host runtime signals the isolate is about to be
|
|
952
|
+
* evicted (Cloudflare DO hibernation, Workers memory pressure, etc.).
|
|
953
|
+
* Use this to actively close external subscriptions BEFORE the
|
|
954
|
+
* isolate dies, so the broker / remote service doesn't accumulate
|
|
955
|
+
* ghost subscriptions visible as "phantom presence" bugs at fleet
|
|
956
|
+
* scale.
|
|
957
|
+
*
|
|
958
|
+
* Distinct from `unsubscribe()`: eviction can fire WITHOUT a
|
|
959
|
+
* `system.stop()` having been called. The host runtime invokes this
|
|
960
|
+
* via `system.evict()`, which fires every source's `onEvict()` in
|
|
961
|
+
* registration order, then `destroyAsync()`. The entire call is
|
|
962
|
+
* awaitable up to a runtime-supplied deadline.
|
|
963
|
+
*
|
|
964
|
+
* Optional — sources whose underlying transport is short-lived
|
|
965
|
+
* (browser WebSocket, in-process EventEmitter) don't need it. RFC
|
|
966
|
+
* 0009 documents the full DO-eviction recipe.
|
|
967
|
+
*/
|
|
968
|
+
onEvict?: () => void | Promise<void>;
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Map of source definitions, keyed by source name. Collision rules match
|
|
972
|
+
* effects/events/constraints: a source name must be unique across all
|
|
973
|
+
* modules in a system.
|
|
974
|
+
*/
|
|
975
|
+
type SourcesDef = Record<string, SourceDef>;
|
|
976
|
+
|
|
692
977
|
/**
|
|
693
978
|
* Event Types - Type definitions for events and event handlers
|
|
694
979
|
*/
|
|
@@ -911,8 +1196,63 @@ interface ResolverDef<S extends Schema, R extends Requirement = Requirement> {
|
|
|
911
1196
|
/** Optional metadata for debugging and devtools (never read on hot path). */
|
|
912
1197
|
meta?: DefinitionMeta;
|
|
913
1198
|
}
|
|
1199
|
+
/**
|
|
1200
|
+
* Inferred requirement type helper.
|
|
1201
|
+
* Constructs a requirement type from a requirements schema entry.
|
|
1202
|
+
*
|
|
1203
|
+
* @typeParam R - The requirements schema
|
|
1204
|
+
* @typeParam T - The requirement type key
|
|
1205
|
+
*
|
|
1206
|
+
* @example
|
|
1207
|
+
* ```typescript
|
|
1208
|
+
* const requirements = {
|
|
1209
|
+
* FETCH_USER: { userId: t.string() },
|
|
1210
|
+
* SEND_EMAIL: { to: t.string(), subject: t.string() },
|
|
1211
|
+
* };
|
|
1212
|
+
*
|
|
1213
|
+
* // InferredReq<typeof requirements, "FETCH_USER"> = { type: "FETCH_USER"; userId: string }
|
|
1214
|
+
* ```
|
|
1215
|
+
*/
|
|
1216
|
+
type InferredReq<R extends RequirementsSchema, T extends keyof R & string> = {
|
|
1217
|
+
type: T;
|
|
1218
|
+
} & InferRequirementPayload<R[T]>;
|
|
1219
|
+
/**
|
|
1220
|
+
* Typed resolver for a specific requirement type.
|
|
1221
|
+
*/
|
|
1222
|
+
type TypedResolverForType<S extends Schema, R extends RequirementsSchema, T extends keyof R & string> = {
|
|
1223
|
+
/** Requirement type to handle */
|
|
1224
|
+
requirement: T;
|
|
1225
|
+
/** Custom key function for deduplication */
|
|
1226
|
+
key?: (req: InferredReq<R, T>) => string;
|
|
1227
|
+
/** Retry policy */
|
|
1228
|
+
retry?: RetryPolicy;
|
|
1229
|
+
/** Timeout for resolver execution (ms) */
|
|
1230
|
+
timeout?: number;
|
|
1231
|
+
/** Batch configuration */
|
|
1232
|
+
batch?: BatchConfig;
|
|
1233
|
+
/** Resolve function for single requirement */
|
|
1234
|
+
resolve?: (req: InferredReq<R, T>, ctx: ResolverContext<S>) => Promise<void>;
|
|
1235
|
+
/** Resolve function for batched requirements (all-or-nothing) */
|
|
1236
|
+
resolveBatch?: (reqs: InferredReq<R, T>[], ctx: ResolverContext<S>) => Promise<void>;
|
|
1237
|
+
/** Resolve function for batched requirements with per-item results */
|
|
1238
|
+
resolveBatchWithResults?: (reqs: InferredReq<R, T>[], ctx: ResolverContext<S>) => Promise<BatchResolveResults>;
|
|
1239
|
+
};
|
|
1240
|
+
/**
|
|
1241
|
+
* Union of all typed resolver configurations for all requirement types.
|
|
1242
|
+
* TypeScript narrows based on the `requirement` literal value.
|
|
1243
|
+
*/
|
|
1244
|
+
type AnyTypedResolver<S extends Schema, R extends RequirementsSchema> = {
|
|
1245
|
+
[T in keyof R & string]: TypedResolverForType<S, R, T>;
|
|
1246
|
+
}[keyof R & string];
|
|
914
1247
|
/** Map of resolver definitions */
|
|
915
1248
|
type ResolversDef<S extends Schema> = Record<string, ResolverDef<S, Requirement>>;
|
|
1249
|
+
/**
|
|
1250
|
+
* Map of typed resolver definitions (schema-based variant).
|
|
1251
|
+
* Each resolver uses `requirement: "TYPE"` with types inferred from the requirements schema.
|
|
1252
|
+
*
|
|
1253
|
+
* @internal Use `TypedResolversDef` from `types/module.ts` for the public module-based API.
|
|
1254
|
+
*/
|
|
1255
|
+
type SchemaTypedResolversDef<S extends Schema, R extends RequirementsSchema> = Record<string, AnyTypedResolver<S, R> | ResolverDef<S, Requirement & InferRequirementsFromSchema<R>>>;
|
|
916
1256
|
/** Resolver status */
|
|
917
1257
|
type ResolverStatus = {
|
|
918
1258
|
state: "idle";
|
|
@@ -1946,6 +2286,15 @@ interface ModuleDef<M extends ModuleSchema = ModuleSchema> {
|
|
|
1946
2286
|
derive?: TypedDerivationsDef<M>;
|
|
1947
2287
|
events?: TypedEventsDef<M>;
|
|
1948
2288
|
effects?: EffectsDef<M["facts"]>;
|
|
2289
|
+
/**
|
|
2290
|
+
* Typed external event sources. See {@link SourceDef} for the primitive's
|
|
2291
|
+
* lifecycle + rationale. Each source attaches at `system.start()` and
|
|
2292
|
+
* tears down at `system.stop()`. Use for Supabase realtime channels,
|
|
2293
|
+
* WebSocket message streams, polling timers, browser event listeners —
|
|
2294
|
+
* any inbound external event the module needs to map into its own event
|
|
2295
|
+
* dispatch surface.
|
|
2296
|
+
*/
|
|
2297
|
+
sources?: SourcesDef;
|
|
1949
2298
|
constraints?: TypedConstraintsDef<M>;
|
|
1950
2299
|
resolvers?: TypedResolversDef<M>;
|
|
1951
2300
|
hooks?: ModuleHooks<M>;
|
|
@@ -2200,6 +2549,78 @@ interface SystemInspection {
|
|
|
2200
2549
|
id: string;
|
|
2201
2550
|
meta?: DefinitionMeta;
|
|
2202
2551
|
}>;
|
|
2552
|
+
/**
|
|
2553
|
+
* All declared source names with the module that owns each, plus per-source
|
|
2554
|
+
* telemetry surfaced for production-debug ("which source is publishing?",
|
|
2555
|
+
* "when did this source last fire?", "is this source errored?", "is the
|
|
2556
|
+
* engine silently dropping publishes from this source?") without requiring
|
|
2557
|
+
* a custom plugin to be installed first.
|
|
2558
|
+
*
|
|
2559
|
+
* Counters reset at every `system.start()` cycle — a stop → start does not
|
|
2560
|
+
* carry "ghost" counts from the previous cycle. Timestamps are wall-clock
|
|
2561
|
+
* milliseconds (Date.now()); `null` means "never happened in this cycle".
|
|
2562
|
+
*
|
|
2563
|
+
* `dropCount` / `lastDropReason` / `lastDropAt` count publishes the
|
|
2564
|
+
* engine's dispatch guard rejected (post-stop, BLOCKED_PROPS event name,
|
|
2565
|
+
* empty / non-string event name). Without these, attackers — or a buggy
|
|
2566
|
+
* source — could probe BLOCKED_PROPS / the isRunning guard invisibly:
|
|
2567
|
+
* telemetry would never advance and no plugin hook would fire.
|
|
2568
|
+
*
|
|
2569
|
+
* `attachedSourceCount` (below) is the aggregate count of `attached: true`
|
|
2570
|
+
* rows; both must stay in lockstep.
|
|
2571
|
+
*/
|
|
2572
|
+
sources: Array<{
|
|
2573
|
+
id: string;
|
|
2574
|
+
moduleId: string;
|
|
2575
|
+
meta?: DefinitionMeta;
|
|
2576
|
+
/** True while the source's attach succeeded and its unsubscribe is held. */
|
|
2577
|
+
attached: boolean;
|
|
2578
|
+
/** Wall-clock ms when the source most recently attached, or null. */
|
|
2579
|
+
attachedAt: number | null;
|
|
2580
|
+
/** Wall-clock ms when the source most recently detached, or null. */
|
|
2581
|
+
detachedAt: number | null;
|
|
2582
|
+
/** Total publish() invocations the engine accepted since the last attachAll. */
|
|
2583
|
+
publishCount: number;
|
|
2584
|
+
/** Wall-clock ms of the most recent accepted publish() call, or null. */
|
|
2585
|
+
lastPublishAt: number | null;
|
|
2586
|
+
/**
|
|
2587
|
+
* Total publish() invocations the engine's dispatch guard rejected this
|
|
2588
|
+
* cycle. Operators monitor this to spot misconfigured sources (wrong
|
|
2589
|
+
* event names) or probing of the BLOCKED_PROPS / isRunning guards.
|
|
2590
|
+
*/
|
|
2591
|
+
dropCount: number;
|
|
2592
|
+
/**
|
|
2593
|
+
* Reason for the most recent rejected publish, or `null`.
|
|
2594
|
+
* - `"post-destroy"` / `"post-stop"` — leaked transport firing
|
|
2595
|
+
* outside the running lifecycle window
|
|
2596
|
+
* - `"blocked-event-name"` / `"invalid-event-name"` — engine
|
|
2597
|
+
* dispatch guard rejected the publish (BLOCKED_PROPS / empty)
|
|
2598
|
+
* - `"coalesced"` — manager debounced a same-event-name publish
|
|
2599
|
+
* when `SourceDef.coalesce === "lastWriteWins"`
|
|
2600
|
+
*/
|
|
2601
|
+
lastDropReason: "post-destroy" | "post-stop" | "blocked-event-name" | "invalid-event-name" | "coalesced" | null;
|
|
2602
|
+
/** Wall-clock ms of the most recent rejected publish, or `null`. */
|
|
2603
|
+
lastDropAt: number | null;
|
|
2604
|
+
/** Total attach + cleanup errors since the last attachAll. */
|
|
2605
|
+
errorCount: number;
|
|
2606
|
+
/**
|
|
2607
|
+
* The most recent error from this source, or `null`. `message` is
|
|
2608
|
+
* truncated to a fixed maximum length so a source whose `attach()`
|
|
2609
|
+
* throws with a payload-embedded message does not write unbounded data
|
|
2610
|
+
* here AND downstream into the audit ledger. `phase: "runtime"`
|
|
2611
|
+
* (RFC 0008) flags errors that the source reported mid-flight via
|
|
2612
|
+
* the `reportError` callback `attach` receives as its second
|
|
2613
|
+
* argument (distinct from lifecycle `"attach"` / `"cleanup"`
|
|
2614
|
+
* failures).
|
|
2615
|
+
*/
|
|
2616
|
+
lastError: {
|
|
2617
|
+
phase: "attach" | "cleanup" | "runtime";
|
|
2618
|
+
message: string;
|
|
2619
|
+
at: number;
|
|
2620
|
+
} | null;
|
|
2621
|
+
}>;
|
|
2622
|
+
/** Number of sources currently attached (i.e. system is in the `attached` phase + their attach() succeeded). */
|
|
2623
|
+
attachedSourceCount: number;
|
|
2203
2624
|
/** All defined derivation names with optional metadata */
|
|
2204
2625
|
derivations: Array<{
|
|
2205
2626
|
id: string;
|
|
@@ -2627,6 +3048,48 @@ type ObservationEvent = {
|
|
|
2627
3048
|
type: "effect.error";
|
|
2628
3049
|
id: string;
|
|
2629
3050
|
error: unknown;
|
|
3051
|
+
}
|
|
3052
|
+
/**
|
|
3053
|
+
* Source attached at `system.start()` (or at registerModule when the
|
|
3054
|
+
* system was already running). Carries the source id + the module that
|
|
3055
|
+
* declared it so plugins can attribute per-module sources.
|
|
3056
|
+
*/
|
|
3057
|
+
| {
|
|
3058
|
+
type: "source.attach";
|
|
3059
|
+
id: string;
|
|
3060
|
+
moduleId: string;
|
|
3061
|
+
}
|
|
3062
|
+
/**
|
|
3063
|
+
* A source published an event into the system's event queue. Fires
|
|
3064
|
+
* BEFORE the event handler runs; pair with `fact.change` events to
|
|
3065
|
+
* trace the downstream effect.
|
|
3066
|
+
*/
|
|
3067
|
+
| {
|
|
3068
|
+
type: "source.publish";
|
|
3069
|
+
id: string;
|
|
3070
|
+
moduleId: string;
|
|
3071
|
+
eventName: string;
|
|
3072
|
+
}
|
|
3073
|
+
/** Source detached at `system.stop()` (reverse-registration order). */
|
|
3074
|
+
| {
|
|
3075
|
+
type: "source.detach";
|
|
3076
|
+
id: string;
|
|
3077
|
+
moduleId: string;
|
|
3078
|
+
}
|
|
3079
|
+
/**
|
|
3080
|
+
* Source `attach` / unsubscribe threw, OR the source called the
|
|
3081
|
+
* `reportError` callback that `attach` received as its second
|
|
3082
|
+
* argument (RFC 0008) — handled, isolated, observable.
|
|
3083
|
+
* `phase: "runtime"` distinguishes mid-flight stream errors
|
|
3084
|
+
* (WebSocket disconnect, Supabase channel goes stale) from lifecycle
|
|
3085
|
+
* `"attach"` / `"cleanup"` failures so observers attribute correctly.
|
|
3086
|
+
*/
|
|
3087
|
+
| {
|
|
3088
|
+
type: "source.error";
|
|
3089
|
+
id: string;
|
|
3090
|
+
moduleId: string;
|
|
3091
|
+
phase: "attach" | "cleanup" | "runtime";
|
|
3092
|
+
error: unknown;
|
|
2630
3093
|
} | {
|
|
2631
3094
|
type: "derivation.compute";
|
|
2632
3095
|
id: string;
|
|
@@ -2660,6 +3123,23 @@ interface System<M extends ModuleSchema = ModuleSchema> {
|
|
|
2660
3123
|
* Observe all lifecycle events as a typed stream.
|
|
2661
3124
|
* Returns an unsubscribe function.
|
|
2662
3125
|
*
|
|
3126
|
+
* ## Timing semantics
|
|
3127
|
+
*
|
|
3128
|
+
* Observers receive events that fire **from the moment they subscribe
|
|
3129
|
+
* onwards**. Past events are NOT replayed. Consequences:
|
|
3130
|
+
*
|
|
3131
|
+
* - An observer registered BEFORE `system.start()` sees the initial
|
|
3132
|
+
* `system.start`, `source.attach`, etc. events emitted during start.
|
|
3133
|
+
* - An observer registered AFTER `system.start()` does NOT see those
|
|
3134
|
+
* initial events. To reconstruct the current state, use
|
|
3135
|
+
* `system.inspect()` (`facts`, `sources`, `constraints`, etc.) at
|
|
3136
|
+
* subscription time, then layer the live observer stream on top.
|
|
3137
|
+
* - The unsubscribe function is idempotent — calling it more than once
|
|
3138
|
+
* is safe.
|
|
3139
|
+
*
|
|
3140
|
+
* This mirrors RxJS Subject + DOM EventTarget conventions; there is no
|
|
3141
|
+
* hidden replay buffer.
|
|
3142
|
+
*
|
|
2663
3143
|
* @example
|
|
2664
3144
|
* ```typescript
|
|
2665
3145
|
* const unsub = system.observe((event) => {
|
|
@@ -2668,6 +3148,13 @@ interface System<M extends ModuleSchema = ModuleSchema> {
|
|
|
2668
3148
|
* }
|
|
2669
3149
|
* });
|
|
2670
3150
|
* ```
|
|
3151
|
+
*
|
|
3152
|
+
* @example Reconstructing state on late subscription
|
|
3153
|
+
* ```typescript
|
|
3154
|
+
* const snapshot = system.inspect();
|
|
3155
|
+
* console.log('active sources:', snapshot.attachedSourceCount);
|
|
3156
|
+
* const unsub = system.observe((event) => { ... }); // forward-only
|
|
3157
|
+
* ```
|
|
2671
3158
|
*/
|
|
2672
3159
|
observe(observer: (event: ObservationEvent) => void): () => void;
|
|
2673
3160
|
/** Per-run trace entries (null if trace is not enabled) */
|
|
@@ -2677,6 +3164,35 @@ interface System<M extends ModuleSchema = ModuleSchema> {
|
|
|
2677
3164
|
start(): void;
|
|
2678
3165
|
stop(): void;
|
|
2679
3166
|
destroy(): void;
|
|
3167
|
+
/**
|
|
3168
|
+
* RFC 0009: async-aware variant of `stop()`. Awaits each source's
|
|
3169
|
+
* unsubscribe before resolving. Use when sources have async
|
|
3170
|
+
* unsubscribes (Supabase `channel.unsubscribe()`, Cloudflare DO
|
|
3171
|
+
* storage flushes) and the caller needs to know teardown actually
|
|
3172
|
+
* completed before continuing.
|
|
3173
|
+
*/
|
|
3174
|
+
stopAsync(): Promise<void>;
|
|
3175
|
+
/**
|
|
3176
|
+
* RFC 0009: async-aware variant of `destroy()`. Equivalent to
|
|
3177
|
+
* `stopAsync` followed by `destroy`. Use in DO `webSocketClose` /
|
|
3178
|
+
* `alarm` handlers where the caller must let the runtime evict the
|
|
3179
|
+
* isolate cleanly.
|
|
3180
|
+
*/
|
|
3181
|
+
destroyAsync(): Promise<void>;
|
|
3182
|
+
/**
|
|
3183
|
+
* RFC 0009: signal the host runtime is about to evict this isolate.
|
|
3184
|
+
* Fires every source's `onEvict()` in registration order (so sources
|
|
3185
|
+
* with downstream dependencies close in the right order), then calls
|
|
3186
|
+
* `destroyAsync()`. Cloudflare DO consumers call this from their
|
|
3187
|
+
* `alarm()` / `webSocketClose()` handlers BEFORE letting the
|
|
3188
|
+
* runtime evict so external brokers don't accumulate ghost
|
|
3189
|
+
* subscriptions.
|
|
3190
|
+
*
|
|
3191
|
+
* @param deadline - Optional wall-clock ms deadline. If supplied,
|
|
3192
|
+
* evicts that have not resolved by `deadline` are abandoned (the
|
|
3193
|
+
* isolate will die anyway). Defaults to no deadline.
|
|
3194
|
+
*/
|
|
3195
|
+
evict(deadline?: number): Promise<void>;
|
|
2680
3196
|
readonly isRunning: boolean;
|
|
2681
3197
|
readonly isSettled: boolean;
|
|
2682
3198
|
/** Whether all modules have completed initialization */
|
|
@@ -3041,6 +3557,39 @@ interface Plugin<M extends ModuleSchema = ModuleSchema> {
|
|
|
3041
3557
|
* @param error - The error that was thrown
|
|
3042
3558
|
*/
|
|
3043
3559
|
onEffectError?: (id: string, error: unknown) => void;
|
|
3560
|
+
/**
|
|
3561
|
+
* Called when a typed external event source attaches at `system.start()`
|
|
3562
|
+
* (or when a dynamically registered module brings new sources to an
|
|
3563
|
+
* already-running system).
|
|
3564
|
+
* @param id - The source ID (key on the module's `sources:` map).
|
|
3565
|
+
* @param moduleId - The owning module's id.
|
|
3566
|
+
*/
|
|
3567
|
+
onSourceAttach?: (id: string, moduleId: string) => void;
|
|
3568
|
+
/**
|
|
3569
|
+
* Called when a source publishes an event into the system's event queue.
|
|
3570
|
+
* Fires BEFORE the event handler runs. Pair with `onFactChange` to trace
|
|
3571
|
+
* the downstream state mutation.
|
|
3572
|
+
* @param id - The source ID.
|
|
3573
|
+
* @param moduleId - The owning module's id.
|
|
3574
|
+
* @param eventName - The dispatched event name (the first arg to `publish`).
|
|
3575
|
+
*/
|
|
3576
|
+
onSourcePublish?: (id: string, moduleId: string, eventName: string) => void;
|
|
3577
|
+
/**
|
|
3578
|
+
* Called when a source detaches at `system.stop()` (reverse-registration
|
|
3579
|
+
* order across all modules).
|
|
3580
|
+
* @param id - The source ID.
|
|
3581
|
+
* @param moduleId - The owning module's id.
|
|
3582
|
+
*/
|
|
3583
|
+
onSourceDetach?: (id: string, moduleId: string) => void;
|
|
3584
|
+
/**
|
|
3585
|
+
* Called when a source's `attach` or unsubscribe throws. Errors are
|
|
3586
|
+
* isolated (one bad source never blocks others) but observable here.
|
|
3587
|
+
* @param id - The source ID.
|
|
3588
|
+
* @param moduleId - The owning module's id.
|
|
3589
|
+
* @param phase - Which lifecycle stage threw.
|
|
3590
|
+
* @param error - The thrown value (normalized to Error inside the manager).
|
|
3591
|
+
*/
|
|
3592
|
+
onSourceError?: (id: string, moduleId: string, phase: "attach" | "cleanup" | "runtime", error: unknown) => void;
|
|
3044
3593
|
/**
|
|
3045
3594
|
* Called when a history snapshot is taken.
|
|
3046
3595
|
* @param snapshot - The snapshot that was captured
|
|
@@ -3099,4 +3648,4 @@ interface Plugin<M extends ModuleSchema = ModuleSchema> {
|
|
|
3099
3648
|
onTraceComplete?: (entry: TraceEntry) => void;
|
|
3100
3649
|
}
|
|
3101
3650
|
|
|
3102
|
-
export { type
|
|
3651
|
+
export { type InferSchemaType as $, type AnySystem as A, type BatchConfig as B, type ClauseResult as C, type DefinitionMeta as D, type EffectsDef as E, type FactPredicate as F, type DynamicConstraintDef as G, type DynamicEffectDef as H, type DynamicResolverDef as I, type EffectDef as J, type EventsDef as K, type FactTemplate as L, type ModuleSchema as M, type NamespacedSystem as N, type FactsSnapshot as O, type Plugin as P, type HistoryAPI as Q, type RequirementWithId as R, type SchemaType as S, type TypedDerivationsDef as T, type HistoryOption as U, type HistoryState as V, type InferDerivations as W, type InferEvents as X, type InferFacts as Y, type InferRequirementTypes as Z, type InferRequirements as _, type Facts as a, type FlexibleEventHandler as a$, type InferSelectorState as a0, type KeySelector as a1, type MetaAccessor as a2, type MetaMatch as a3, type ObservationEvent as a4, type OperatorObject as a5, type PatchSpec as a6, type PatchValue as a7, type PayloadRef as a8, type PredicateClause as a9, type FactsStore as aA, type ConstraintState as aB, type ResolverStatus as aC, type FactChange as aD, type ReconcileResult as aE, type RecoveryStrategy as aF, type ErrorSource as aG, type RetryLaterConfig as aH, type BatchItemResult as aI, type BatchResolveResults as aJ, type ConstraintsControl as aK, type CrossModuleDerivationFn as aL, type CrossModuleFactsWithSelf as aM, type DerivationKeys as aN, type DerivationReturnType as aO, type DerivationsControl as aP, type DerivationsSchema as aQ, type DeriveAccessor as aR, type DispatchEventsFromSchema as aS, type EffectCleanup as aT, type EffectsControl as aU, type EventPayloadSchema as aV, type EventsAccessor as aW, type EventsAccessorFromSchema as aX, type EventsSchema as aY, type FactKeys as aZ, type FactReturnType as a_, type PredicateCombinator as aa, type PredicateCombinatorKey as ab, type PredicateObject as ac, type PredicateOp as ad, type ResolverDef as ae, type ResolversDef as af, type RetryPolicy as ag, type Schema as ah, type SchemaTypedResolversDef as ai, type Snapshot as aj, type SourceDef as ak, type SourcePublish as al, type SourceReportError as am, type SourceUnsubscribe as an, type System as ao, type SystemConfig as ap, type SystemDerived as aq, type SystemFacts as ar, type SystemInspection as as, type SystemMode as at, type SystemSnapshot as au, type TraceEntry as av, type TypedConstraintDef as aw, type TypedResolverDef as ax, isNamespacedSystem as ay, isSingleModuleSystem as az, type TypedEventsDef as b, type HistoryConfig as b0, type InferEventPayloadFromSchema as b1, type InferRequirementPayloadFromSchema as b2, type InferSchema as b3, type MutableNamespacedFacts as b4, type NamespacedDerivations as b5, type NamespacedEventsAccessor as b6, type NamespacedFacts as b7, type ObservableKeys as b8, type RequirementExplanation as b9, type RequirementOutput as ba, type RequirementPayloadSchema$1 as bb, type RequirementsSchema as bc, type ResolverContext as bd, type ResolversControl as be, type SnapshotMeta as bf, type SystemEvent as bg, type TraceConfig as bh, type TypedResolverContext as bi, type UnionEvents as bj, type RequirementOutput$1 as bk, type SourcesDef as c, type TypedConstraintsDef as d, type TypedResolversDef as e, type ModuleHooks as f, type CrossModuleDeps as g, type CrossModuleDerivationsDef as h, type CrossModuleEffectsDef as i, type CrossModuleConstraintsDef as j, type ModuleDef as k, type CreateSystemOptionsSingle as l, type SingleModuleSystem as m, type ModulesMap as n, type CreateSystemOptionsNamed as o, type TraceOption as p, type ErrorBoundaryConfig as q, type Requirement as r, type RequirementKeyFn as s, type ConstraintDef as t, type ConstraintsDef as u, type CrossModuleConstraintDef as v, type CrossModuleEffectDef as w, DirectiveError as x, type DistributableSnapshot as y, type DistributableSnapshotOptions as z };
|