@czap/astro 0.1.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.
Files changed (159) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +19 -0
  3. package/dist/Satellite.d.ts +42 -0
  4. package/dist/Satellite.d.ts.map +1 -0
  5. package/dist/Satellite.js +55 -0
  6. package/dist/Satellite.js.map +1 -0
  7. package/dist/client-directives/gpu.d.ts +3 -0
  8. package/dist/client-directives/gpu.d.ts.map +1 -0
  9. package/dist/client-directives/gpu.js +5 -0
  10. package/dist/client-directives/gpu.js.map +1 -0
  11. package/dist/client-directives/llm.d.ts +3 -0
  12. package/dist/client-directives/llm.d.ts.map +1 -0
  13. package/dist/client-directives/llm.js +5 -0
  14. package/dist/client-directives/llm.js.map +1 -0
  15. package/dist/client-directives/satellite.d.ts +3 -0
  16. package/dist/client-directives/satellite.d.ts.map +1 -0
  17. package/dist/client-directives/satellite.js +5 -0
  18. package/dist/client-directives/satellite.js.map +1 -0
  19. package/dist/client-directives/stream.d.ts +3 -0
  20. package/dist/client-directives/stream.d.ts.map +1 -0
  21. package/dist/client-directives/stream.js +5 -0
  22. package/dist/client-directives/stream.js.map +1 -0
  23. package/dist/client-directives/wasm.d.ts +3 -0
  24. package/dist/client-directives/wasm.d.ts.map +1 -0
  25. package/dist/client-directives/wasm.js +6 -0
  26. package/dist/client-directives/wasm.js.map +1 -0
  27. package/dist/client-directives/worker.d.ts +3 -0
  28. package/dist/client-directives/worker.d.ts.map +1 -0
  29. package/dist/client-directives/worker.js +5 -0
  30. package/dist/client-directives/worker.js.map +1 -0
  31. package/dist/detect-upgrade.d.ts +16 -0
  32. package/dist/detect-upgrade.d.ts.map +1 -0
  33. package/dist/detect-upgrade.js +105 -0
  34. package/dist/detect-upgrade.js.map +1 -0
  35. package/dist/headers.d.ts +45 -0
  36. package/dist/headers.d.ts.map +1 -0
  37. package/dist/headers.js +64 -0
  38. package/dist/headers.js.map +1 -0
  39. package/dist/index.d.ts +30 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +26 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/integration.d.ts +76 -0
  44. package/dist/integration.d.ts.map +1 -0
  45. package/dist/integration.js +240 -0
  46. package/dist/integration.js.map +1 -0
  47. package/dist/middleware.d.ts +69 -0
  48. package/dist/middleware.d.ts.map +1 -0
  49. package/dist/middleware.js +75 -0
  50. package/dist/middleware.js.map +1 -0
  51. package/dist/quantize.d.ts +50 -0
  52. package/dist/quantize.d.ts.map +1 -0
  53. package/dist/quantize.js +122 -0
  54. package/dist/quantize.js.map +1 -0
  55. package/dist/runtime/boundary.d.ts +123 -0
  56. package/dist/runtime/boundary.d.ts.map +1 -0
  57. package/dist/runtime/boundary.js +164 -0
  58. package/dist/runtime/boundary.js.map +1 -0
  59. package/dist/runtime/globals.d.ts +32 -0
  60. package/dist/runtime/globals.d.ts.map +1 -0
  61. package/dist/runtime/globals.js +45 -0
  62. package/dist/runtime/globals.js.map +1 -0
  63. package/dist/runtime/gpu.d.ts +15 -0
  64. package/dist/runtime/gpu.d.ts.map +1 -0
  65. package/dist/runtime/gpu.js +266 -0
  66. package/dist/runtime/gpu.js.map +1 -0
  67. package/dist/runtime/index.d.ts +7 -0
  68. package/dist/runtime/index.d.ts.map +1 -0
  69. package/dist/runtime/index.js +5 -0
  70. package/dist/runtime/index.js.map +1 -0
  71. package/dist/runtime/llm-receipt-tracker.d.ts +21 -0
  72. package/dist/runtime/llm-receipt-tracker.d.ts.map +1 -0
  73. package/dist/runtime/llm-receipt-tracker.js +60 -0
  74. package/dist/runtime/llm-receipt-tracker.js.map +1 -0
  75. package/dist/runtime/llm-render-pipeline.d.ts +89 -0
  76. package/dist/runtime/llm-render-pipeline.d.ts.map +1 -0
  77. package/dist/runtime/llm-render-pipeline.js +241 -0
  78. package/dist/runtime/llm-render-pipeline.js.map +1 -0
  79. package/dist/runtime/llm-session.d.ts +126 -0
  80. package/dist/runtime/llm-session.d.ts.map +1 -0
  81. package/dist/runtime/llm-session.js +385 -0
  82. package/dist/runtime/llm-session.js.map +1 -0
  83. package/dist/runtime/llm.d.ts +16 -0
  84. package/dist/runtime/llm.d.ts.map +1 -0
  85. package/dist/runtime/llm.js +273 -0
  86. package/dist/runtime/llm.js.map +1 -0
  87. package/dist/runtime/policy.d.ts +100 -0
  88. package/dist/runtime/policy.d.ts.map +1 -0
  89. package/dist/runtime/policy.js +147 -0
  90. package/dist/runtime/policy.js.map +1 -0
  91. package/dist/runtime/receipt-chain.d.ts +22 -0
  92. package/dist/runtime/receipt-chain.d.ts.map +1 -0
  93. package/dist/runtime/receipt-chain.js +80 -0
  94. package/dist/runtime/receipt-chain.js.map +1 -0
  95. package/dist/runtime/runtime-session.d.ts +34 -0
  96. package/dist/runtime/runtime-session.d.ts.map +1 -0
  97. package/dist/runtime/runtime-session.js +102 -0
  98. package/dist/runtime/runtime-session.js.map +1 -0
  99. package/dist/runtime/satellite.d.ts +13 -0
  100. package/dist/runtime/satellite.d.ts.map +1 -0
  101. package/dist/runtime/satellite.js +59 -0
  102. package/dist/runtime/satellite.js.map +1 -0
  103. package/dist/runtime/slots.d.ts +34 -0
  104. package/dist/runtime/slots.d.ts.map +1 -0
  105. package/dist/runtime/slots.js +108 -0
  106. package/dist/runtime/slots.js.map +1 -0
  107. package/dist/runtime/stream-session.d.ts +47 -0
  108. package/dist/runtime/stream-session.d.ts.map +1 -0
  109. package/dist/runtime/stream-session.js +82 -0
  110. package/dist/runtime/stream-session.js.map +1 -0
  111. package/dist/runtime/stream.d.ts +9 -0
  112. package/dist/runtime/stream.d.ts.map +1 -0
  113. package/dist/runtime/stream.js +308 -0
  114. package/dist/runtime/stream.js.map +1 -0
  115. package/dist/runtime/url-policy.d.ts +28 -0
  116. package/dist/runtime/url-policy.d.ts.map +1 -0
  117. package/dist/runtime/url-policy.js +87 -0
  118. package/dist/runtime/url-policy.js.map +1 -0
  119. package/dist/runtime/wasm.d.ts +20 -0
  120. package/dist/runtime/wasm.d.ts.map +1 -0
  121. package/dist/runtime/wasm.js +70 -0
  122. package/dist/runtime/wasm.js.map +1 -0
  123. package/dist/runtime/worker.d.ts +11 -0
  124. package/dist/runtime/worker.d.ts.map +1 -0
  125. package/dist/runtime/worker.js +249 -0
  126. package/dist/runtime/worker.js.map +1 -0
  127. package/package.json +106 -0
  128. package/src/Satellite.astro +39 -0
  129. package/src/Satellite.ts +84 -0
  130. package/src/client-directives/gpu.ts +5 -0
  131. package/src/client-directives/llm.ts +5 -0
  132. package/src/client-directives/satellite.ts +5 -0
  133. package/src/client-directives/stream.ts +5 -0
  134. package/src/client-directives/wasm.ts +6 -0
  135. package/src/client-directives/worker.ts +5 -0
  136. package/src/detect-upgrade.ts +105 -0
  137. package/src/headers.ts +84 -0
  138. package/src/index.ts +30 -0
  139. package/src/integration.ts +309 -0
  140. package/src/middleware.ts +133 -0
  141. package/src/quantize.ts +173 -0
  142. package/src/runtime/boundary.ts +263 -0
  143. package/src/runtime/globals.ts +57 -0
  144. package/src/runtime/gpu.ts +291 -0
  145. package/src/runtime/index.ts +12 -0
  146. package/src/runtime/llm-receipt-tracker.ts +88 -0
  147. package/src/runtime/llm-render-pipeline.ts +366 -0
  148. package/src/runtime/llm-session.ts +548 -0
  149. package/src/runtime/llm.ts +344 -0
  150. package/src/runtime/policy.ts +229 -0
  151. package/src/runtime/receipt-chain.ts +106 -0
  152. package/src/runtime/runtime-session.ts +139 -0
  153. package/src/runtime/satellite.ts +80 -0
  154. package/src/runtime/slots.ts +136 -0
  155. package/src/runtime/stream-session.ts +125 -0
  156. package/src/runtime/stream.ts +407 -0
  157. package/src/runtime/url-policy.ts +107 -0
  158. package/src/runtime/wasm.ts +85 -0
  159. package/src/runtime/worker.ts +307 -0
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Generic runtime-session lifecycle used by every `@czap/astro` client
3
+ * directive. Owns a microtask scheduler, a set of pending timers, and
4
+ * a state machine (`idle` / `active` / `reconnecting` / `disposed`)
5
+ * that every concrete session (stream, llm, gpu, ...) composes.
6
+ *
7
+ * @module
8
+ */
9
+
10
+ /** Lifecycle states a runtime session progresses through. */
11
+ export type RuntimeSessionState = 'idle' | 'active' | 'reconnecting' | 'disposed';
12
+
13
+ /**
14
+ * Shared lifecycle surface shared by every runtime-session kind.
15
+ *
16
+ * `schedule` batches callbacks into a single microtask flush;
17
+ * `setTimer` / `clearTimer` mirror `setTimeout` but participate in
18
+ * `dispose()` so no task outlives the session.
19
+ */
20
+ export interface RuntimeSessionShape {
21
+ readonly state: RuntimeSessionState;
22
+ activate(): void;
23
+ beginReconnect(): void;
24
+ schedule(task: () => void): Promise<void>;
25
+ setTimer(callback: () => void, delay: number): ReturnType<typeof setTimeout> | null;
26
+ clearTimer(handle: ReturnType<typeof setTimeout> | null | undefined): null;
27
+ isDisposed(): boolean;
28
+ dispose(): void;
29
+ }
30
+
31
+ /**
32
+ * Build a fresh runtime session. Every session starts in the `idle`
33
+ * state and must be explicitly `activate()`d before tasks are allowed
34
+ * to run.
35
+ */
36
+ export function createRuntimeSession(): RuntimeSessionShape {
37
+ let state: RuntimeSessionState = 'idle';
38
+ let scheduled = false;
39
+ let tasks: Array<() => void> | null = null;
40
+ let resolvers: Array<() => void> | null = null;
41
+ let timers: Set<ReturnType<typeof setTimeout>> | null = null;
42
+ const resolvePending = (pendingResolvers: ReadonlyArray<() => void>): void => {
43
+ for (const resolve of pendingResolvers) {
44
+ resolve();
45
+ }
46
+ };
47
+
48
+ const flush = (): void => {
49
+ scheduled = false;
50
+ const pendingTasks = tasks ?? [];
51
+ tasks = null;
52
+ const pendingResolvers = resolvers ?? [];
53
+ resolvers = null;
54
+
55
+ if (state === 'disposed') {
56
+ resolvePending(pendingResolvers);
57
+ return;
58
+ }
59
+
60
+ for (const task of pendingTasks) {
61
+ task();
62
+ }
63
+
64
+ resolvePending(pendingResolvers);
65
+ };
66
+
67
+ return {
68
+ get state(): RuntimeSessionState {
69
+ return state;
70
+ },
71
+
72
+ activate(): void {
73
+ if (state !== 'disposed') {
74
+ state = 'active';
75
+ }
76
+ },
77
+
78
+ beginReconnect(): void {
79
+ if (state !== 'disposed') {
80
+ state = 'reconnecting';
81
+ }
82
+ },
83
+
84
+ schedule(task: () => void): Promise<void> {
85
+ if (state === 'disposed') {
86
+ return Promise.resolve();
87
+ }
88
+
89
+ (tasks ??= []).push(task);
90
+ if (!scheduled) {
91
+ scheduled = true;
92
+ queueMicrotask(flush);
93
+ }
94
+
95
+ return new Promise<void>((resolve) => {
96
+ (resolvers ??= []).push(resolve);
97
+ });
98
+ },
99
+
100
+ setTimer(callback: () => void, delay: number): ReturnType<typeof setTimeout> | null {
101
+ if (state === 'disposed') {
102
+ return null;
103
+ }
104
+
105
+ const handle = setTimeout(() => {
106
+ timers?.delete(handle);
107
+ callback();
108
+ }, delay);
109
+ (timers ??= new Set()).add(handle);
110
+ return handle;
111
+ },
112
+
113
+ clearTimer(handle: ReturnType<typeof setTimeout> | null | undefined): null {
114
+ if (!handle) {
115
+ return null;
116
+ }
117
+
118
+ clearTimeout(handle);
119
+ timers?.delete(handle);
120
+ return null;
121
+ },
122
+
123
+ isDisposed(): boolean {
124
+ return state === 'disposed';
125
+ },
126
+
127
+ dispose(): void {
128
+ state = 'disposed';
129
+ for (const handle of timers ?? []) {
130
+ clearTimeout(handle);
131
+ }
132
+ timers = null;
133
+ tasks = null;
134
+ const pendingResolvers = resolvers ?? [];
135
+ resolvers = null;
136
+ resolvePending(pendingResolvers);
137
+ },
138
+ };
139
+ }
@@ -0,0 +1,80 @@
1
+ import {
2
+ applyBoundaryState,
3
+ attachViewportObserver,
4
+ evaluateBoundary,
5
+ parseBoundary,
6
+ readSignalValue,
7
+ } from './boundary.js';
8
+
9
+ /**
10
+ * Entry point used by the `client:satellite` directive. Parses the
11
+ * serialised boundary off `element`, attaches a viewport observer
12
+ * (when the signal is viewport-backed), and recomputes
13
+ * `data-czap-state` plus CSS variables whenever the signal crosses a
14
+ * threshold. Honors `czap:reinit` and `czap:dispose` custom events to
15
+ * re-read / tear down without remounting the island.
16
+ *
17
+ * @param load - Dynamic-import factory the directive passes in.
18
+ * @param element - Satellite root carrying `data-czap-boundary`.
19
+ */
20
+ export function initSatelliteDirective(load: () => Promise<unknown>, element: HTMLElement): void {
21
+ let runtimeBoundary = parseBoundary(element.getAttribute('data-czap-boundary'));
22
+ if (!runtimeBoundary) {
23
+ return;
24
+ }
25
+
26
+ let previousState = element.getAttribute('data-czap-state') ?? '';
27
+ let cleanupObserver: (() => void) | null = null;
28
+
29
+ const updateState = (): void => {
30
+ if (!runtimeBoundary) {
31
+ return;
32
+ }
33
+
34
+ const value = readSignalValue(runtimeBoundary.input);
35
+ if (value === undefined) {
36
+ return;
37
+ }
38
+
39
+ const state = evaluateBoundary(runtimeBoundary, value, previousState || undefined);
40
+ if (state === previousState) {
41
+ return;
42
+ }
43
+
44
+ previousState = state;
45
+ applyBoundaryState(
46
+ element,
47
+ runtimeBoundary,
48
+ {
49
+ discrete: { [runtimeBoundary.name]: state },
50
+ },
51
+ 'czap:satellite-state',
52
+ );
53
+ };
54
+
55
+ const cleanup = (): void => {
56
+ cleanupObserver?.();
57
+ cleanupObserver = null;
58
+ };
59
+
60
+ const init = (): void => {
61
+ updateState();
62
+ if (runtimeBoundary) {
63
+ cleanupObserver = attachViewportObserver(runtimeBoundary.input, updateState);
64
+ }
65
+ };
66
+
67
+ element.addEventListener('czap:reinit', () => {
68
+ cleanup();
69
+ runtimeBoundary = parseBoundary(element.getAttribute('data-czap-boundary'));
70
+ previousState = element.getAttribute('data-czap-state') ?? '';
71
+ init();
72
+ });
73
+
74
+ element.addEventListener('czap:dispose', () => {
75
+ cleanup();
76
+ });
77
+
78
+ init();
79
+ load();
80
+ }
@@ -0,0 +1,136 @@
1
+ import { SlotRegistry } from '@czap/web';
2
+ import { readRuntimeGlobal, writeRuntimeGlobal } from './globals.js';
3
+
4
+ interface RuntimeWindow extends Window {
5
+ __CZAP_SLOT_REGISTRY__?: SlotRegistry.Shape;
6
+ __CZAP_SLOT_BOOTSTRAPPED__?: boolean;
7
+ __CZAP_SWAP_REINIT__?: boolean;
8
+ __CZAP_SLOTS__?: {
9
+ readonly registry: SlotRegistry.Shape;
10
+ readonly entries: Record<string, { path: string; mode: string }>;
11
+ };
12
+ }
13
+
14
+ const REINIT_SELECTOR = '[data-czap-boundary],[data-czap-stream-url],[data-czap-llm-url],[data-czap-wasm]';
15
+
16
+ function isSlotRegistryShape(value: unknown): value is SlotRegistry.Shape {
17
+ if (typeof value !== 'object' || value === null) return false;
18
+ if (!('get' in value) || !('register' in value) || !('entries' in value)) return false;
19
+ return typeof value.get === 'function' && typeof value.register === 'function' && typeof value.entries === 'function';
20
+ }
21
+
22
+ function isBoolean(value: unknown): value is boolean {
23
+ return typeof value === 'boolean';
24
+ }
25
+
26
+ function runtimeWindow(): RuntimeWindow | null {
27
+ return typeof window === 'undefined' ? null : (window as RuntimeWindow);
28
+ }
29
+
30
+ /**
31
+ * Return the document-scoped {@link SlotRegistry.Shape}, creating and
32
+ * persisting one on `window.__CZAP_SLOT_REGISTRY__` the first time
33
+ * it's requested. Returns a detached registry under SSR.
34
+ */
35
+ export function getSlotRegistry(): SlotRegistry.Shape {
36
+ const win = runtimeWindow();
37
+ if (!win) {
38
+ return SlotRegistry.create();
39
+ }
40
+
41
+ const existingRegistry = readRuntimeGlobal('__CZAP_SLOT_REGISTRY__', isSlotRegistryShape);
42
+ if (!existingRegistry) {
43
+ return writeRuntimeGlobal('__CZAP_SLOT_REGISTRY__', SlotRegistry.create());
44
+ }
45
+
46
+ return existingRegistry;
47
+ }
48
+
49
+ /**
50
+ * Clear and rebuild the slot registry by scanning `root` for
51
+ * `data-czap-slot` elements. Also writes a serialised
52
+ * `__CZAP_SLOTS__` snapshot for devtools / diagnostics consumers.
53
+ */
54
+ export function rescanSlots(root: ParentNode = document): SlotRegistry.Shape {
55
+ const registry = getSlotRegistry();
56
+ const existingPaths = Array.from(registry.entries().keys());
57
+ for (const path of existingPaths) {
58
+ registry.unregister(path);
59
+ }
60
+
61
+ const scanRoot = root instanceof Element ? root : document.documentElement;
62
+ SlotRegistry.scanDOM(registry, scanRoot);
63
+
64
+ const win = runtimeWindow();
65
+ if (win) {
66
+ writeRuntimeGlobal('__CZAP_SLOTS__', {
67
+ registry,
68
+ entries: Object.fromEntries(
69
+ Array.from(registry.entries().entries()).map(([path, entry]) => [path, { path, mode: entry.mode }]),
70
+ ),
71
+ });
72
+ }
73
+
74
+ return registry;
75
+ }
76
+
77
+ /**
78
+ * One-shot bootstrap: arm a slot-registry scan on
79
+ * `DOMContentLoaded` (or immediately if the document is already
80
+ * ready) and re-scan after every Astro View Transitions `after-swap`
81
+ * event. Idempotent -- subsequent calls return the same registry.
82
+ */
83
+ export function bootstrapSlots(): SlotRegistry.Shape {
84
+ const win = runtimeWindow();
85
+ if (!win) {
86
+ return SlotRegistry.create();
87
+ }
88
+
89
+ const scan = (): void => {
90
+ rescanSlots(document.documentElement);
91
+ };
92
+
93
+ if (!readRuntimeGlobal('__CZAP_SLOT_BOOTSTRAPPED__', isBoolean)) {
94
+ writeRuntimeGlobal('__CZAP_SLOT_BOOTSTRAPPED__', true);
95
+
96
+ if (document.readyState === 'loading') {
97
+ document.addEventListener('DOMContentLoaded', scan, { once: true });
98
+ } else {
99
+ scan();
100
+ }
101
+
102
+ document.addEventListener('astro:after-swap', scan);
103
+ }
104
+
105
+ return getSlotRegistry();
106
+ }
107
+
108
+ /**
109
+ * Dispatch `czap:dispose` + `czap:reinit` on every known directive
110
+ * root. Used after Astro View Transitions `after-swap` so directives
111
+ * can re-read fresh `data-czap-*` attributes without remounting.
112
+ */
113
+ export function reinitializeDirectives(): void {
114
+ document.querySelectorAll<HTMLElement>(REINIT_SELECTOR).forEach((element) => {
115
+ element.dispatchEvent(new CustomEvent('czap:dispose', { bubbles: true }));
116
+ element.dispatchEvent(new CustomEvent('czap:reinit', { bubbles: true }));
117
+ });
118
+ }
119
+
120
+ /**
121
+ * Install a one-time listener that fires
122
+ * {@link reinitializeDirectives} on every Astro `after-swap`.
123
+ * Guarded by `window.__CZAP_SWAP_REINIT__` so repeated module loads
124
+ * do not stack listeners.
125
+ */
126
+ export function installSwapReinit(): void {
127
+ const win = runtimeWindow();
128
+ if (!win || readRuntimeGlobal('__CZAP_SWAP_REINIT__', isBoolean)) {
129
+ return;
130
+ }
131
+
132
+ writeRuntimeGlobal('__CZAP_SWAP_REINIT__', true);
133
+ document.addEventListener('astro:after-swap', () => {
134
+ reinitializeDirectives();
135
+ });
136
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Stream-patch scheduler backing the `client:stream` directive.
3
+ *
4
+ * Batches incoming HTML patches into a single DOM write per microtask
5
+ * and reports whether any of the coalesced patches requires a slot
6
+ * rescan. Delegates lifecycle (activate / dispose / reconnect timer)
7
+ * to a shared {@link createRuntimeSession}.
8
+ *
9
+ * @module
10
+ */
11
+ import { createRuntimeSession, type RuntimeSessionState } from './runtime-session.js';
12
+
13
+ /**
14
+ * A single HTML patch pulled off the stream. `requiresRescan` flags
15
+ * patches that inserted or removed slot-bearing elements.
16
+ */
17
+ export interface StreamPatch {
18
+ readonly html: string;
19
+ readonly requiresRescan: boolean;
20
+ }
21
+
22
+ /** Callbacks the scheduler invokes when it writes a batch. */
23
+ export interface StreamSchedulerConfig {
24
+ /** Write a coalesced HTML patch into the live document. */
25
+ readonly applyHtml: (html: string) => void;
26
+ /** Observer fired after each flush with batch metadata. */
27
+ readonly onFlush: (context: { readonly patchCount: number; readonly requiresRescan: boolean }) => void;
28
+ }
29
+
30
+ /** Host surface of a stream scheduler. */
31
+ export interface StreamSchedulerShape {
32
+ readonly state: RuntimeSessionState;
33
+ activate(): void;
34
+ beginReconnect(): void;
35
+ enqueue(patch: StreamPatch): Promise<void>;
36
+ enqueueBatch(patches: readonly StreamPatch[]): Promise<void>;
37
+ setReconnectTimer(callback: () => void, delay: number): ReturnType<typeof setTimeout> | null;
38
+ clearReconnectTimer(handle: ReturnType<typeof setTimeout> | null | undefined): null;
39
+ dispose(): void;
40
+ }
41
+
42
+ /**
43
+ * Build a new stream scheduler. The scheduler coalesces patches into a
44
+ * single microtask-flushed DOM write and calls `config.onFlush` once
45
+ * per batch.
46
+ */
47
+ export function createStreamScheduler(config: StreamSchedulerConfig): StreamSchedulerShape {
48
+ const runtime = createRuntimeSession();
49
+ let queue: StreamPatch[] = [];
50
+ let resolvers: Array<() => void> = [];
51
+
52
+ const flush = (): void => {
53
+ const patches = queue;
54
+ queue = [];
55
+ const pendingResolvers = resolvers;
56
+ resolvers = [];
57
+
58
+ let requiresRescan = false;
59
+ for (const patch of patches) {
60
+ requiresRescan ||= patch.requiresRescan;
61
+ config.applyHtml(patch.html);
62
+ }
63
+
64
+ if (patches.length > 0) {
65
+ config.onFlush({ patchCount: patches.length, requiresRescan });
66
+ }
67
+
68
+ for (const resolve of pendingResolvers) {
69
+ resolve();
70
+ }
71
+ };
72
+
73
+ const schedule = (): Promise<void> => {
74
+ return new Promise<void>((resolve) => {
75
+ resolvers.push(resolve);
76
+ void runtime.schedule(flush);
77
+ });
78
+ };
79
+
80
+ return {
81
+ get state(): RuntimeSessionState {
82
+ return runtime.state;
83
+ },
84
+
85
+ activate() {
86
+ runtime.activate();
87
+ },
88
+
89
+ beginReconnect() {
90
+ runtime.beginReconnect();
91
+ },
92
+
93
+ enqueue(patch) {
94
+ queue.push(patch);
95
+ return schedule();
96
+ },
97
+
98
+ enqueueBatch(patches) {
99
+ if (patches.length === 0) {
100
+ return Promise.resolve();
101
+ }
102
+
103
+ queue.push(...patches);
104
+ return schedule();
105
+ },
106
+
107
+ setReconnectTimer(callback, delay) {
108
+ return runtime.setTimer(callback, delay);
109
+ },
110
+
111
+ clearReconnectTimer(handle) {
112
+ return runtime.clearTimer(handle);
113
+ },
114
+
115
+ dispose() {
116
+ queue = [];
117
+ const pendingResolvers = resolvers;
118
+ resolvers = [];
119
+ for (const resolve of pendingResolvers) {
120
+ resolve();
121
+ }
122
+ runtime.dispose();
123
+ },
124
+ };
125
+ }