@pyreon/core 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.
@@ -0,0 +1,381 @@
1
+ //#region src/types.d.ts
2
+ type VNodeChildAtom = VNode | VNode[] | string | number | boolean | null | undefined;
3
+ type VNodeChild = VNodeChildAtom | (() => VNodeChildAtom);
4
+ interface VNode {
5
+ /** Tag name, component function, or special symbol (Fragment) */
6
+ type: string | ComponentFn | symbol;
7
+ props: Props;
8
+ children: VNodeChild[];
9
+ key: string | number | null;
10
+ }
11
+ type Props = Record<string, unknown>;
12
+ /**
13
+ * A component is a plain function that runs ONCE.
14
+ * It returns a VNode (or null) and may call lifecycle hooks during setup.
15
+ */
16
+ type ComponentFn<P extends Props = Props> = (props: P) => VNode | null;
17
+ /**
18
+ * Internal runtime handle created by the renderer for each mounted component.
19
+ */
20
+ interface ComponentInstance {
21
+ vnode: VNode | null;
22
+ /** Trigger a re-check / patch cycle (called by the renderer) */
23
+ update(): void;
24
+ unmount(): void;
25
+ }
26
+ type CleanupFn = () => void;
27
+ /**
28
+ * Result of createTemplate() — a pre-cloned DOM element with its cleanup.
29
+ * Handled directly by mountFor without going through the VNode reconciler,
30
+ * saving 2 allocations per row vs the VNode wrapper path.
31
+ */
32
+ interface NativeItem {
33
+ readonly __isNative: true;
34
+ el: HTMLElement;
35
+ cleanup: (() => void) | null;
36
+ }
37
+ interface LifecycleHooks {
38
+ mount: (() => CleanupFn | undefined)[];
39
+ unmount: (() => void)[];
40
+ update: (() => void)[];
41
+ /** Error handlers — return true to mark the error as handled (stops propagation). */
42
+ error: ((err: unknown) => boolean | undefined)[];
43
+ }
44
+ //#endregion
45
+ //#region src/component.d.ts
46
+ /**
47
+ * Identity wrapper — marks a function as a Pyreon component and preserves its type.
48
+ * Useful for IDE tooling and future compiler optimisations.
49
+ */
50
+ declare function defineComponent<P extends Props>(fn: ComponentFn<P>): ComponentFn<P>;
51
+ /**
52
+ * Run a component function in a tracked context so that lifecycle hooks
53
+ * registered inside it (onMount, onUnmount, onErrorCaptured, etc.) are captured.
54
+ *
55
+ * Called by the renderer — not intended for user code.
56
+ */
57
+ declare function runWithHooks<P extends Props>(fn: ComponentFn<P>, props: P): {
58
+ vnode: VNode | null;
59
+ hooks: LifecycleHooks;
60
+ };
61
+ /**
62
+ * Walk up error handlers collected during component rendering.
63
+ * Returns true if any handler marked the error as handled.
64
+ */
65
+ declare function propagateError(err: unknown, hooks: LifecycleHooks): boolean;
66
+ /**
67
+ * Dispatch an error to the nearest active ErrorBoundary.
68
+ * Returns true if the boundary handled it, false if none was registered.
69
+ */
70
+ declare function dispatchToErrorBoundary(err: unknown): boolean;
71
+ //#endregion
72
+ //#region src/context.d.ts
73
+ /**
74
+ * Provide / inject — like React context or Vue provide/inject.
75
+ *
76
+ * Values flow down the component tree without prop-drilling.
77
+ * The renderer maintains the context stack as it walks the VNode tree.
78
+ */
79
+ interface Context<T> {
80
+ readonly id: symbol;
81
+ readonly defaultValue: T;
82
+ }
83
+ declare function createContext<T>(defaultValue: T): Context<T>;
84
+ /**
85
+ * Override the context stack provider. Called by @pyreon/runtime-server to
86
+ * inject an AsyncLocalStorage-backed stack that isolates concurrent SSR requests.
87
+ * Has no effect in the browser (CSR always uses the default module-level stack).
88
+ */
89
+ declare function setContextStackProvider(fn: () => Map<symbol, unknown>[]): void;
90
+ declare function pushContext(values: Map<symbol, unknown>): void;
91
+ declare function popContext(): void;
92
+ /**
93
+ * Read the nearest provided value for a context.
94
+ * Falls back to `context.defaultValue` if none found.
95
+ */
96
+ declare function useContext<T>(context: Context<T>): T;
97
+ /**
98
+ * Provide a value for `context` during `fn()`.
99
+ * Used by the renderer when it encounters a `<Provider>` component.
100
+ */
101
+ declare function withContext<T>(context: Context<T>, value: T, fn: () => void): void;
102
+ //#endregion
103
+ //#region src/dynamic.d.ts
104
+ interface DynamicProps extends Props {
105
+ component: ComponentFn | string;
106
+ }
107
+ declare function Dynamic(props: DynamicProps): VNode | null;
108
+ //#endregion
109
+ //#region src/error-boundary.d.ts
110
+ /**
111
+ * ErrorBoundary — catches errors thrown by child components and renders a
112
+ * fallback UI instead of crashing the whole tree.
113
+ *
114
+ * Also reports caught errors to any registered telemetry handlers.
115
+ *
116
+ * How error propagation works:
117
+ * ErrorBoundary pushes a handler onto the module-level boundary stack
118
+ * synchronously during its own setup (before children are mounted).
119
+ * When mountComponent catches a child error, it calls dispatchToErrorBoundary()
120
+ * which invokes the innermost boundary's handler.
121
+ *
122
+ * Usage:
123
+ * h(ErrorBoundary, {
124
+ * fallback: (err) => h("p", null, `Error: ${err}`),
125
+ * children: h(MyComponent, null),
126
+ * })
127
+ *
128
+ * // or with JSX:
129
+ * <ErrorBoundary fallback={(err) => <p>Error: {String(err)}</p>}>
130
+ * <MyComponent />
131
+ * </ErrorBoundary>
132
+ */
133
+ declare function ErrorBoundary(props: {
134
+ /**
135
+ * Rendered when a child throws. Receives the caught error and a `reset`
136
+ * function — calling `reset()` clears the error and re-renders children.
137
+ */
138
+ fallback: (err: unknown, reset: () => void) => VNodeChild;
139
+ children?: VNodeChild;
140
+ }): VNodeChild;
141
+ //#endregion
142
+ //#region src/for.d.ts
143
+ /**
144
+ * Symbol used as the VNode type for a For list — runtime-dom handles it
145
+ * via mountFor, bypassing the generic VNode reconciler.
146
+ */
147
+ declare const ForSymbol: unique symbol;
148
+ interface ForProps<T> {
149
+ each: () => T[];
150
+ by: (item: T) => string | number;
151
+ children: (item: T) => VNode | NativeItem;
152
+ }
153
+ /**
154
+ * Efficient reactive list rendering.
155
+ *
156
+ * Unlike a plain `() => items().map(item => h(...))`, For never re-creates
157
+ * VNodes for existing keys — only new keys invoke `children()`. Structural
158
+ * mutations (swap, sort, filter) are O(n) key scan + O(k) DOM moves where k
159
+ * is the number of actually displaced entries.
160
+ *
161
+ * Usage:
162
+ * <For each={items} by={r => r.id}>{r => <li>...</li>}</For>
163
+ */
164
+ declare function For<T>(props: ForProps<T>): VNode;
165
+ //#endregion
166
+ //#region src/h.d.ts
167
+ /** Marker for fragment nodes — renders children without a wrapper element */
168
+ declare const Fragment: unique symbol;
169
+ /**
170
+ * Hyperscript function — the compiled output of JSX.
171
+ * `<div class="x">hello</div>` → `h("div", { class: "x" }, "hello")`
172
+ *
173
+ * Generic on P so TypeScript validates props match the component's signature
174
+ * at the call site, then stores the result in the loosely-typed VNode.
175
+ */
176
+ /** Shared empty props sentinel — identity-checked in mountElement to skip applyProps. */
177
+ declare const EMPTY_PROPS: Props;
178
+ declare function h<P extends Props>(type: string | ComponentFn<P> | symbol, props: P | null, ...children: VNodeChild[]): VNode;
179
+ //#endregion
180
+ //#region src/suspense.d.ts
181
+ /** Internal marker attached to lazy()-wrapped components */
182
+ type LazyComponent<P extends Props = Props> = ((props: P) => VNode | null) & {
183
+ __loading: () => boolean;
184
+ };
185
+ /**
186
+ * Suspense — shows `fallback` while a lazy child component is still loading.
187
+ *
188
+ * Works in tandem with `lazy()` from `@pyreon/react-compat` (or `@pyreon/core/lazy`).
189
+ * The child VNode's `.type.__loading()` signal drives the switch.
190
+ *
191
+ * Usage:
192
+ * const Page = lazy(() => import("./Page"))
193
+ *
194
+ * h(Suspense, { fallback: h(Spinner, null) }, h(Page, null))
195
+ * // or with JSX:
196
+ * <Suspense fallback={<Spinner />}><Page /></Suspense>
197
+ */
198
+ declare function Suspense(props: {
199
+ fallback: VNodeChild;
200
+ children?: VNodeChild;
201
+ }): VNode;
202
+ //#endregion
203
+ //#region src/lazy.d.ts
204
+ declare function lazy<P extends Props>(load: () => Promise<{
205
+ default: ComponentFn<P>;
206
+ }>): LazyComponent<P>;
207
+ //#endregion
208
+ //#region src/lifecycle.d.ts
209
+ /**
210
+ * Register a callback to run after the component is mounted to the DOM.
211
+ * Optionally return a cleanup function — it will run on unmount.
212
+ */
213
+ declare function onMount(fn: () => CleanupFn | undefined): void;
214
+ /**
215
+ * Register a callback to run when the component is removed from the DOM.
216
+ */
217
+ declare function onUnmount(fn: () => void): void;
218
+ /**
219
+ * Register a callback to run after each reactive update.
220
+ */
221
+ declare function onUpdate(fn: () => void): void;
222
+ /**
223
+ * Register an error handler for this component subtree.
224
+ *
225
+ * When an error is thrown during rendering or in a child component,
226
+ * the nearest `onErrorCaptured` handler is called with the error.
227
+ * Return `true` to mark the error as handled and stop propagation.
228
+ *
229
+ * @example
230
+ * onErrorCaptured((err) => {
231
+ * setError(String(err))
232
+ * return true // handled — don't propagate
233
+ * })
234
+ */
235
+ declare function onErrorCaptured(fn: (err: unknown) => boolean | undefined): void;
236
+ //#endregion
237
+ //#region src/map-array.d.ts
238
+ /**
239
+ * mapArray — keyed reactive list mapping.
240
+ *
241
+ * Creates each mapped item exactly once per key, then reuses it across
242
+ * updates. When the source array is reordered or partially changed, only
243
+ * new keys invoke `map()`; existing entries return the cached result.
244
+ *
245
+ * This makes structural list operations (swap, sort, filter) O(k) in
246
+ * allocations where k is the number of new/removed keys, not O(n).
247
+ *
248
+ * The returned accessor reads `source()` reactively, so it can be passed
249
+ * directly to the keyed-list reconciler.
250
+ */
251
+ declare function mapArray<T, U>(source: () => T[], getKey: (item: T) => string | number, map: (item: T) => U): () => U[];
252
+ //#endregion
253
+ //#region src/portal.d.ts
254
+ /**
255
+ * Symbol used as the VNode type for a Portal — runtime-dom mounts the
256
+ * children into `target` instead of the normal parent.
257
+ */
258
+ declare const PortalSymbol: unique symbol;
259
+ interface PortalProps {
260
+ /** DOM element to render children into (e.g. document.body). */
261
+ target: Element;
262
+ children: VNodeChild;
263
+ }
264
+ /**
265
+ * Portal — renders `children` into a different DOM node than the
266
+ * current parent tree.
267
+ *
268
+ * Useful for modals, tooltips, dropdowns, and any overlay that needs to
269
+ * escape CSS overflow/stacking context restrictions.
270
+ *
271
+ * @example
272
+ * // Render a modal at document.body level regardless of where in the
273
+ * // component tree <Modal> is used:
274
+ * Portal({ target: document.body, children: h(Modal, { onClose }) })
275
+ *
276
+ * // JSX:
277
+ * <Portal target={document.body}>
278
+ * <Modal onClose={close} />
279
+ * </Portal>
280
+ */
281
+ declare function Portal(props: PortalProps): VNode;
282
+ //#endregion
283
+ //#region src/ref.d.ts
284
+ /**
285
+ * createRef — mutable container for a DOM element or component value.
286
+ *
287
+ * Usage:
288
+ * const inputRef = createRef<HTMLInputElement>()
289
+ * onMount(() => { inputRef.current?.focus() })
290
+ * return <input ref={inputRef} />
291
+ *
292
+ * The runtime sets `ref.current` after the element is inserted into the DOM
293
+ * and clears it to `null` when the element is removed.
294
+ */
295
+ interface Ref<T = unknown> {
296
+ current: T | null;
297
+ }
298
+ declare function createRef<T = unknown>(): Ref<T>;
299
+ //#endregion
300
+ //#region src/show.d.ts
301
+ interface ShowProps extends Props {
302
+ /** Accessor — children render when truthy, fallback when falsy. */
303
+ when: () => unknown;
304
+ fallback?: VNodeChild;
305
+ children?: VNodeChild;
306
+ }
307
+ /**
308
+ * Conditionally render children based on a reactive condition.
309
+ *
310
+ * @example
311
+ * h(Show, { when: () => isLoggedIn() },
312
+ * h(Dashboard, null)
313
+ * )
314
+ *
315
+ * // With fallback:
316
+ * h(Show, { when: () => user(), fallback: h(Login, null) },
317
+ * h(Dashboard, null)
318
+ * )
319
+ */
320
+ declare function Show(props: ShowProps): VNode | null;
321
+ interface MatchProps extends Props {
322
+ /** Accessor — this branch renders when truthy. */
323
+ when: () => unknown;
324
+ children?: VNodeChild;
325
+ }
326
+ /**
327
+ * A branch inside `<Switch>`. Renders when `when()` is truthy.
328
+ * Must be used as a direct child of `Switch`.
329
+ *
330
+ * `Match` acts as a pure type/identity marker — Switch identifies it by checking
331
+ * `vnode.type === Match` rather than by the runtime return value.
332
+ */
333
+ declare function Match(_props: MatchProps): VNode | null;
334
+ interface SwitchProps extends Props {
335
+ /** Rendered when no Match branch is truthy. */
336
+ fallback?: VNodeChild;
337
+ children?: VNodeChild | VNodeChild[];
338
+ }
339
+ declare function Switch(props: SwitchProps): VNode | null;
340
+ declare const MatchSymbol: unique symbol;
341
+ //#endregion
342
+ //#region src/telemetry.d.ts
343
+ /**
344
+ * Error telemetry — hook into Pyreon's error reporting for Sentry, Datadog, etc.
345
+ *
346
+ * @example
347
+ * import { registerErrorHandler } from "@pyreon/core"
348
+ * import * as Sentry from "@sentry/browser"
349
+ *
350
+ * registerErrorHandler(ctx => {
351
+ * Sentry.captureException(ctx.error, {
352
+ * extra: { component: ctx.component, phase: ctx.phase },
353
+ * })
354
+ * })
355
+ */
356
+ interface ErrorContext {
357
+ /** Component function name, or "Anonymous" */
358
+ component: string;
359
+ /** Lifecycle phase where the error occurred */
360
+ phase: "setup" | "render" | "mount" | "unmount" | "effect";
361
+ /** The thrown value */
362
+ error: unknown;
363
+ /** Unix timestamp (ms) */
364
+ timestamp: number;
365
+ /** Component props at the time of the error */
366
+ props?: Record<string, unknown>;
367
+ }
368
+ type ErrorHandler = (ctx: ErrorContext) => void;
369
+ /**
370
+ * Register a global error handler. Called whenever a component throws in any
371
+ * lifecycle phase. Returns an unregister function.
372
+ */
373
+ declare function registerErrorHandler(handler: ErrorHandler): () => void;
374
+ /**
375
+ * Internal — called by the runtime whenever a component error is caught.
376
+ * Existing console.error calls are preserved; this is additive.
377
+ */
378
+ declare function reportError(ctx: ErrorContext): void;
379
+ //#endregion
380
+ export { type CleanupFn, type ComponentFn, type ComponentInstance, type Context, Dynamic, type DynamicProps, EMPTY_PROPS, ErrorBoundary, type ErrorContext, type ErrorHandler, For, type ForProps, ForSymbol, Fragment, type LazyComponent, type LifecycleHooks, Match, type MatchProps, MatchSymbol, type NativeItem, Portal, type PortalProps, PortalSymbol, type Props, type Ref, Show, type ShowProps, Suspense, Switch, type SwitchProps, type VNode, type VNodeChild, type VNodeChildAtom, createContext, createRef, defineComponent, dispatchToErrorBoundary, h, lazy, mapArray, onErrorCaptured, onMount, onUnmount, onUpdate, popContext, propagateError, pushContext, registerErrorHandler, reportError, runWithHooks, setContextStackProvider, useContext, withContext };
381
+ //# sourceMappingURL=index2.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index2.d.ts","names":[],"sources":["../../src/types.ts","../../src/component.ts","../../src/context.ts","../../src/dynamic.ts","../../src/error-boundary.ts","../../src/for.ts","../../src/h.ts","../../src/suspense.ts","../../src/lazy.ts","../../src/lifecycle.ts","../../src/map-array.ts","../../src/portal.ts","../../src/ref.ts","../../src/show.ts","../../src/telemetry.ts"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@pyreon/core",
3
+ "version": "0.1.0",
4
+ "description": "Core component model and lifecycle for Pyreon",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/pyreon/pyreon.git",
9
+ "directory": "packages/core"
10
+ },
11
+ "homepage": "https://github.com/pyreon/pyreon/tree/main/packages/core#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/pyreon/pyreon/issues"
14
+ },
15
+ "files": [
16
+ "lib",
17
+ "src",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "sideEffects": false,
22
+ "type": "module",
23
+ "main": "./lib/index.js",
24
+ "module": "./lib/index.js",
25
+ "types": "./lib/types/index.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "bun": "./src/index.ts",
29
+ "import": "./lib/index.js",
30
+ "types": "./lib/types/index.d.ts"
31
+ },
32
+ "./jsx-runtime": {
33
+ "bun": "./src/jsx-runtime.ts",
34
+ "import": "./lib/jsx-runtime.js",
35
+ "types": "./lib/types/jsx-runtime.d.ts"
36
+ },
37
+ "./jsx-dev-runtime": {
38
+ "bun": "./src/jsx-dev-runtime.ts",
39
+ "import": "./lib/jsx-dev-runtime.js",
40
+ "types": "./lib/types/jsx-dev-runtime.d.ts"
41
+ }
42
+ },
43
+ "scripts": {
44
+ "build": "vl_rolldown_build",
45
+ "dev": "vl_rolldown_build-watch",
46
+ "test": "vitest run",
47
+ "typecheck": "tsc --noEmit",
48
+ "prepublishOnly": "bun run build"
49
+ },
50
+ "dependencies": {
51
+ "@pyreon/reactivity": "workspace:*"
52
+ }
53
+ }
@@ -0,0 +1,66 @@
1
+ import { setCurrentHooks } from "./lifecycle"
2
+ import type { ComponentFn, LifecycleHooks, Props, VNode } from "./types"
3
+
4
+ /**
5
+ * Identity wrapper — marks a function as a Pyreon component and preserves its type.
6
+ * Useful for IDE tooling and future compiler optimisations.
7
+ */
8
+ export function defineComponent<P extends Props>(fn: ComponentFn<P>): ComponentFn<P> {
9
+ return fn
10
+ }
11
+
12
+ /**
13
+ * Run a component function in a tracked context so that lifecycle hooks
14
+ * registered inside it (onMount, onUnmount, onErrorCaptured, etc.) are captured.
15
+ *
16
+ * Called by the renderer — not intended for user code.
17
+ */
18
+ export function runWithHooks<P extends Props>(
19
+ fn: ComponentFn<P>,
20
+ props: P,
21
+ ): { vnode: VNode | null; hooks: LifecycleHooks } {
22
+ const hooks: LifecycleHooks = { mount: [], unmount: [], update: [], error: [] }
23
+ setCurrentHooks(hooks)
24
+ let vnode: VNode | null = null
25
+ try {
26
+ vnode = fn(props)
27
+ } finally {
28
+ setCurrentHooks(null)
29
+ }
30
+ return { vnode, hooks }
31
+ }
32
+
33
+ /**
34
+ * Walk up error handlers collected during component rendering.
35
+ * Returns true if any handler marked the error as handled.
36
+ */
37
+ export function propagateError(err: unknown, hooks: LifecycleHooks): boolean {
38
+ for (const handler of hooks.error) {
39
+ if (handler(err) === true) return true
40
+ }
41
+ return false
42
+ }
43
+
44
+ // ─── Error boundary stack ────────────────────────────────────────────────────
45
+ // Module-level stack of active ErrorBoundary handlers (innermost last).
46
+ // ErrorBoundary pushes during its own setup (before children mount) so that
47
+ // any child mountComponent error can dispatch up to the nearest boundary.
48
+
49
+ const _errorBoundaryStack: ((err: unknown) => boolean)[] = []
50
+
51
+ export function pushErrorBoundary(handler: (err: unknown) => boolean): void {
52
+ _errorBoundaryStack.push(handler)
53
+ }
54
+
55
+ export function popErrorBoundary(): void {
56
+ _errorBoundaryStack.pop()
57
+ }
58
+
59
+ /**
60
+ * Dispatch an error to the nearest active ErrorBoundary.
61
+ * Returns true if the boundary handled it, false if none was registered.
62
+ */
63
+ export function dispatchToErrorBoundary(err: unknown): boolean {
64
+ const handler = _errorBoundaryStack[_errorBoundaryStack.length - 1]
65
+ return handler ? handler(err) : false
66
+ }
package/src/context.ts ADDED
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Provide / inject — like React context or Vue provide/inject.
3
+ *
4
+ * Values flow down the component tree without prop-drilling.
5
+ * The renderer maintains the context stack as it walks the VNode tree.
6
+ */
7
+
8
+ export interface Context<T> {
9
+ readonly id: symbol
10
+ readonly defaultValue: T
11
+ }
12
+
13
+ export function createContext<T>(defaultValue: T): Context<T> {
14
+ return { id: Symbol("PyreonContext"), defaultValue }
15
+ }
16
+
17
+ // ─── Runtime context stack (managed by the renderer) ─────────────────────────
18
+
19
+ // Default stack — used for CSR and single-threaded SSR.
20
+ // On Node.js with concurrent requests, @pyreon/runtime-server replaces this with
21
+ // an AsyncLocalStorage-backed provider via setContextStackProvider().
22
+ const _defaultStack: Map<symbol, unknown>[] = []
23
+ let _stackProvider: () => Map<symbol, unknown>[] = () => _defaultStack
24
+
25
+ /**
26
+ * Override the context stack provider. Called by @pyreon/runtime-server to
27
+ * inject an AsyncLocalStorage-backed stack that isolates concurrent SSR requests.
28
+ * Has no effect in the browser (CSR always uses the default module-level stack).
29
+ */
30
+ export function setContextStackProvider(fn: () => Map<symbol, unknown>[]): void {
31
+ _stackProvider = fn
32
+ }
33
+
34
+ function getStack(): Map<symbol, unknown>[] {
35
+ return _stackProvider()
36
+ }
37
+
38
+ const __DEV__ = typeof process !== "undefined" && process.env.NODE_ENV !== "production"
39
+
40
+ export function pushContext(values: Map<symbol, unknown>) {
41
+ getStack().push(values)
42
+ }
43
+
44
+ export function popContext() {
45
+ const stack = getStack()
46
+ if (__DEV__ && stack.length === 0) {
47
+ return
48
+ }
49
+ stack.pop()
50
+ }
51
+
52
+ /**
53
+ * Read the nearest provided value for a context.
54
+ * Falls back to `context.defaultValue` if none found.
55
+ */
56
+ export function useContext<T>(context: Context<T>): T {
57
+ const stack = getStack()
58
+ for (let i = stack.length - 1; i >= 0; i--) {
59
+ const frame = stack[i]
60
+ if (frame?.has(context.id)) {
61
+ return frame.get(context.id) as T
62
+ }
63
+ }
64
+ return context.defaultValue
65
+ }
66
+
67
+ /**
68
+ * Provide a value for `context` during `fn()`.
69
+ * Used by the renderer when it encounters a `<Provider>` component.
70
+ */
71
+ export function withContext<T>(context: Context<T>, value: T, fn: () => void) {
72
+ const frame = new Map<symbol, unknown>([[context.id, value]])
73
+ pushContext(frame)
74
+ try {
75
+ fn()
76
+ } finally {
77
+ popContext()
78
+ }
79
+ }
package/src/dynamic.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { h } from "./h"
2
+ import type { ComponentFn, Props, VNode } from "./types"
3
+
4
+ export interface DynamicProps extends Props {
5
+ component: ComponentFn | string
6
+ }
7
+
8
+ export function Dynamic(props: DynamicProps): VNode | null {
9
+ const { component, ...rest } = props
10
+ if (!component) return null
11
+ return h(component as string | ComponentFn, rest as Props)
12
+ }
@@ -0,0 +1,58 @@
1
+ import { signal } from "@pyreon/reactivity"
2
+ import { popErrorBoundary, pushErrorBoundary } from "./component"
3
+ import { onUnmount } from "./lifecycle"
4
+ import { reportError } from "./telemetry"
5
+ import type { VNodeChild, VNodeChildAtom } from "./types"
6
+
7
+ /**
8
+ * ErrorBoundary — catches errors thrown by child components and renders a
9
+ * fallback UI instead of crashing the whole tree.
10
+ *
11
+ * Also reports caught errors to any registered telemetry handlers.
12
+ *
13
+ * How error propagation works:
14
+ * ErrorBoundary pushes a handler onto the module-level boundary stack
15
+ * synchronously during its own setup (before children are mounted).
16
+ * When mountComponent catches a child error, it calls dispatchToErrorBoundary()
17
+ * which invokes the innermost boundary's handler.
18
+ *
19
+ * Usage:
20
+ * h(ErrorBoundary, {
21
+ * fallback: (err) => h("p", null, `Error: ${err}`),
22
+ * children: h(MyComponent, null),
23
+ * })
24
+ *
25
+ * // or with JSX:
26
+ * <ErrorBoundary fallback={(err) => <p>Error: {String(err)}</p>}>
27
+ * <MyComponent />
28
+ * </ErrorBoundary>
29
+ */
30
+ export function ErrorBoundary(props: {
31
+ /**
32
+ * Rendered when a child throws. Receives the caught error and a `reset`
33
+ * function — calling `reset()` clears the error and re-renders children.
34
+ */
35
+ fallback: (err: unknown, reset: () => void) => VNodeChild
36
+ children?: VNodeChild
37
+ }): VNodeChild {
38
+ const error = signal<unknown>(null)
39
+ const reset = () => error.set(null)
40
+
41
+ const handler = (err: unknown): boolean => {
42
+ if (error.peek() !== null) return false // already in error state — let outer boundary catch it
43
+ error.set(err)
44
+ reportError({ component: "ErrorBoundary", phase: "render", error: err, timestamp: Date.now() })
45
+ return true
46
+ }
47
+
48
+ // Push synchronously — before children are mounted — so child errors see this boundary
49
+ pushErrorBoundary(handler)
50
+ onUnmount(() => popErrorBoundary())
51
+
52
+ return (): VNodeChildAtom => {
53
+ const err = error()
54
+ if (err != null) return props.fallback(err, reset) as VNodeChildAtom
55
+ const ch = props.children
56
+ return (typeof ch === "function" ? ch() : ch) as VNodeChildAtom
57
+ }
58
+ }