what-core 0.11.7 → 0.11.8
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/chunk-JIZR6JX5.min.js +1 -0
- package/dist/chunk-PABR63P3.min.js +1 -0
- package/dist/index.min.js +19 -6
- package/dist/render.min.js +1 -1
- package/dist/testing.min.js +1 -1
- package/index.d.ts +152 -11
- package/package.json +1 -1
- package/src/agent-context.js +1 -1
- package/src/components.js +47 -22
- package/src/dom.js +219 -22
- package/src/errors.js +29 -0
- package/src/head.js +35 -5
- package/src/hooks.js +3 -0
- package/src/reactive.js +5 -5
- package/src/render.js +73 -34
- package/dist/chunk-5QCEMXNL.min.js +0 -1
- package/dist/chunk-H67HFVDV.min.js +0 -1
package/index.d.ts
CHANGED
|
@@ -24,7 +24,7 @@ export interface Computed<T> {
|
|
|
24
24
|
_signal: true;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export function signal<T>(initial: T): Signal<T>;
|
|
27
|
+
export function signal<T>(initial: T, debugName?: string): Signal<T>;
|
|
28
28
|
export function computed<T>(fn: () => T): Computed<T>;
|
|
29
29
|
export function effect(fn: () => void | (() => void), opts?: { stable?: boolean }): () => void;
|
|
30
30
|
export function signalMemo<T>(fn: () => T): Computed<T>;
|
|
@@ -33,12 +33,36 @@ export function untrack<T>(fn: () => T): T;
|
|
|
33
33
|
export function flushSync(): void;
|
|
34
34
|
export function createRoot<T>(fn: (dispose: () => void) => T): T;
|
|
35
35
|
|
|
36
|
+
/** Opaque ownership scope handle. Pair with runWithOwner() for async work. */
|
|
37
|
+
export interface Owner {
|
|
38
|
+
disposals: Array<() => void>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getOwner(): Owner | null;
|
|
42
|
+
export function runWithOwner<T>(owner: Owner | null, fn: () => T): T;
|
|
43
|
+
/** Register a cleanup with the current owner/root (root-level onCleanup). */
|
|
44
|
+
export function onRootCleanup(fn: () => void): void;
|
|
45
|
+
|
|
36
46
|
// --- Virtual DOM ---
|
|
37
47
|
|
|
38
48
|
export type PrimitiveChild = string | number | boolean | null | undefined;
|
|
39
49
|
export type VNodeChild = PrimitiveChild | VNode | (() => VNodeChild) | VNodeChild[];
|
|
40
50
|
|
|
41
|
-
|
|
51
|
+
/** A component may legitimately render nothing, so `null` is part of the contract. */
|
|
52
|
+
export type Component<P = {}> = ((props: P & { children?: VNodeChild }) => VNode | null) & {
|
|
53
|
+
/**
|
|
54
|
+
* Opt out of realizing compiled children before the component runs.
|
|
55
|
+
*
|
|
56
|
+
* A component that establishes a scope its children depend on (a context
|
|
57
|
+
* provider, an error or suspense boundary) receives `props.children` as a
|
|
58
|
+
* zero-argument factory instead of built nodes, and the runtime realizes it
|
|
59
|
+
* once that scope exists. `ErrorBoundary`, `Suspense` and `Context.Provider`
|
|
60
|
+
* set this themselves, and the compiler keeps a component that only forwards
|
|
61
|
+
* its children lazy, so this is only needed by a component that both
|
|
62
|
+
* inspects its children and forwards them into a boundary or a provider.
|
|
63
|
+
*/
|
|
64
|
+
_deferChildren?: boolean;
|
|
65
|
+
};
|
|
42
66
|
|
|
43
67
|
export interface VNode<P = Record<string, any>> {
|
|
44
68
|
tag: string | Component<P>;
|
|
@@ -54,15 +78,20 @@ export function h<P extends Record<string, any>>(
|
|
|
54
78
|
...children: VNodeChild[]
|
|
55
79
|
): VNode<P>;
|
|
56
80
|
|
|
57
|
-
export function Fragment(props: { children?: VNodeChild }):
|
|
81
|
+
export function Fragment(props: { children?: VNodeChild }): VNode;
|
|
58
82
|
export function html(strings: TemplateStringsArray, ...values: any[]): VNode | VNode[];
|
|
59
83
|
|
|
60
84
|
// --- DOM ---
|
|
61
85
|
|
|
62
86
|
export function mount(vnode: VNodeChild, container: string | Element): () => void;
|
|
63
87
|
|
|
88
|
+
/** Attach reactive bindings to server-rendered DOM instead of creating it. */
|
|
89
|
+
export function hydrate(vnode: VNodeChild, container: Element): Node | null;
|
|
90
|
+
export function isHydrating(): boolean;
|
|
91
|
+
|
|
64
92
|
// Fine-grained rendering primitives
|
|
65
93
|
export function template(html: string): () => Element;
|
|
94
|
+
export function svgTemplate(html: string): () => Element;
|
|
66
95
|
export function insert(parent: Node, child: any, marker?: Node | null): any;
|
|
67
96
|
export function mapArray<T>(
|
|
68
97
|
source: () => T[],
|
|
@@ -132,18 +161,18 @@ export function Show(props: {
|
|
|
132
161
|
when: boolean | (() => boolean);
|
|
133
162
|
fallback?: VNodeChild;
|
|
134
163
|
children?: VNodeChild;
|
|
135
|
-
}):
|
|
164
|
+
}): VNode;
|
|
136
165
|
|
|
137
166
|
export function For<T>(props: {
|
|
138
167
|
each: T[] | (() => T[]);
|
|
139
168
|
fallback?: VNodeChild;
|
|
140
169
|
children: ((item: T, index: number) => VNodeChild) | VNodeChild;
|
|
141
|
-
}):
|
|
170
|
+
}): VNode;
|
|
142
171
|
|
|
143
172
|
export function Switch(props: {
|
|
144
173
|
fallback?: VNodeChild;
|
|
145
174
|
children?: VNodeChild;
|
|
146
|
-
}):
|
|
175
|
+
}): VNode;
|
|
147
176
|
|
|
148
177
|
export function Match(props: {
|
|
149
178
|
when: boolean | (() => boolean);
|
|
@@ -198,6 +227,37 @@ export function Head(props: {
|
|
|
198
227
|
}): null;
|
|
199
228
|
export function clearHead(): void;
|
|
200
229
|
|
|
230
|
+
/** Per-render head accumulator used by the SSR renderer. */
|
|
231
|
+
export interface HeadSink {
|
|
232
|
+
title: string | null;
|
|
233
|
+
metas: Map<string, Record<string, string>>;
|
|
234
|
+
links: Map<string, Record<string, string>>;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function beginHeadCollection(): HeadSink;
|
|
238
|
+
export function endHeadCollection(sink: HeadSink | null): string;
|
|
239
|
+
|
|
240
|
+
// --- Loader Data ---
|
|
241
|
+
|
|
242
|
+
export function useLoaderData<T = any>(): T | undefined;
|
|
243
|
+
export function getLoaderData<T = any>(): T | undefined;
|
|
244
|
+
export function getResource<T = any>(key: string): T | undefined;
|
|
245
|
+
|
|
246
|
+
// --- Server Context ---
|
|
247
|
+
|
|
248
|
+
export interface ServerContext {
|
|
249
|
+
loaderData?: any;
|
|
250
|
+
head?: HeadSink;
|
|
251
|
+
resources?: Record<string, any>;
|
|
252
|
+
[key: string]: any;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** The active render context, or null on the client / outside a render. */
|
|
256
|
+
export function getServerContext(): ServerContext | null;
|
|
257
|
+
/** Set the active context and return the previous one so callers can restore it. */
|
|
258
|
+
export function setServerContext(ctx: ServerContext | null): ServerContext | null;
|
|
259
|
+
export function runWithServerContext<T>(ctx: ServerContext, fn: () => T): T;
|
|
260
|
+
|
|
201
261
|
// --- Scheduler ---
|
|
202
262
|
|
|
203
263
|
export function scheduleRead(fn: () => void): () => void;
|
|
@@ -334,17 +394,17 @@ export function onKeys(keys: string[], handler: (e: KeyboardEvent) => void): (e:
|
|
|
334
394
|
|
|
335
395
|
// --- Skeleton ---
|
|
336
396
|
|
|
337
|
-
export function Skeleton(props?: Record<string, any>):
|
|
397
|
+
export function Skeleton(props?: Record<string, any>): VNode;
|
|
338
398
|
export function SkeletonText(props?: Record<string, any>): VNode;
|
|
339
|
-
export function SkeletonAvatar(props?: Record<string, any>):
|
|
399
|
+
export function SkeletonAvatar(props?: Record<string, any>): VNode;
|
|
340
400
|
export function SkeletonCard(props?: Record<string, any>): VNode;
|
|
341
401
|
export function SkeletonTable(props?: Record<string, any>): VNode;
|
|
342
|
-
export function IslandSkeleton(props?: Record<string, any>):
|
|
402
|
+
export function IslandSkeleton(props?: Record<string, any>): VNode;
|
|
343
403
|
export function useSkeleton<T>(asyncFn: () => Promise<T> | T, deps?: unknown[]): {
|
|
344
404
|
isLoading: () => boolean;
|
|
345
405
|
data: () => T | null;
|
|
346
406
|
error: () => any;
|
|
347
|
-
Skeleton: (props?: Record<string, any>) =>
|
|
407
|
+
Skeleton: (props?: Record<string, any>) => VNode;
|
|
348
408
|
};
|
|
349
409
|
export function Placeholder(props?: Record<string, any>): VNode;
|
|
350
410
|
export function LoadingDots(props?: Record<string, any>): VNode;
|
|
@@ -504,4 +564,85 @@ export function ErrorMessage(props: {
|
|
|
504
564
|
formState?: FormState;
|
|
505
565
|
errors?: Record<string, FieldError> | (() => Record<string, FieldError>);
|
|
506
566
|
render?: (args: { message?: string; type?: string }) => VNodeChild;
|
|
507
|
-
}):
|
|
567
|
+
}): VNode;
|
|
568
|
+
|
|
569
|
+
// --- Structured Errors ---
|
|
570
|
+
|
|
571
|
+
export interface ErrorCodeDefinition {
|
|
572
|
+
code: string;
|
|
573
|
+
severity: 'error' | 'warning';
|
|
574
|
+
template: string;
|
|
575
|
+
suggestion: string;
|
|
576
|
+
codeExample?: string;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
export const ERROR_CODES: Record<string, ErrorCodeDefinition>;
|
|
580
|
+
|
|
581
|
+
export interface WhatErrorJSON {
|
|
582
|
+
code: string;
|
|
583
|
+
message: string;
|
|
584
|
+
suggestion?: string;
|
|
585
|
+
file?: string;
|
|
586
|
+
line?: number;
|
|
587
|
+
component?: string;
|
|
588
|
+
signal?: string;
|
|
589
|
+
effect?: string;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
export class WhatError extends Error {
|
|
593
|
+
constructor(init: {
|
|
594
|
+
code: string;
|
|
595
|
+
message: string;
|
|
596
|
+
suggestion?: string;
|
|
597
|
+
file?: string;
|
|
598
|
+
line?: number;
|
|
599
|
+
component?: string;
|
|
600
|
+
signal?: string;
|
|
601
|
+
effect?: string;
|
|
602
|
+
});
|
|
603
|
+
code: string;
|
|
604
|
+
suggestion?: string;
|
|
605
|
+
file?: string;
|
|
606
|
+
line?: number;
|
|
607
|
+
component?: string;
|
|
608
|
+
signal?: string;
|
|
609
|
+
effect?: string;
|
|
610
|
+
toJSON(): WhatErrorJSON;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
export function createWhatError(
|
|
614
|
+
errorCode: string | ErrorCodeDefinition,
|
|
615
|
+
context?: Record<string, any>,
|
|
616
|
+
): WhatError;
|
|
617
|
+
export function classifyError(err: unknown, context?: Record<string, any>): WhatError;
|
|
618
|
+
export function collectError(error: WhatError): void;
|
|
619
|
+
export function getCollectedErrors(since?: number): Array<WhatErrorJSON & { timestamp: number }>;
|
|
620
|
+
export function clearCollectedErrors(): void;
|
|
621
|
+
|
|
622
|
+
// --- Guardrails ---
|
|
623
|
+
|
|
624
|
+
export interface GuardrailConfig {
|
|
625
|
+
signalReadDetection: boolean;
|
|
626
|
+
componentNaming: boolean;
|
|
627
|
+
importValidation: boolean;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
export function configureGuardrails(overrides: Partial<GuardrailConfig>): void;
|
|
631
|
+
export function getGuardrailConfig(): GuardrailConfig;
|
|
632
|
+
export function installSignalReadGuardrail<T>(signalFn: T, debugName?: string): T;
|
|
633
|
+
|
|
634
|
+
// --- Agent Context ---
|
|
635
|
+
|
|
636
|
+
export interface HealthReport {
|
|
637
|
+
effectCycleRisk: boolean;
|
|
638
|
+
orphanEffects: number;
|
|
639
|
+
signalLeaks: number;
|
|
640
|
+
memoryPressure: 'low' | 'medium' | 'high';
|
|
641
|
+
recentErrorCount: number;
|
|
642
|
+
totalSignals: number;
|
|
643
|
+
totalComponents: number;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
export function getHealth(): HealthReport;
|
|
647
|
+
/** Expose globalThis.__WHAT_AGENT__ for agent tooling (dev mode only). */
|
|
648
|
+
export function installAgentContext(): void;
|
package/package.json
CHANGED
package/src/agent-context.js
CHANGED
|
@@ -8,7 +8,7 @@ import { getCollectedErrors } from './errors.js';
|
|
|
8
8
|
// --- Version ---
|
|
9
9
|
// Keep in sync with packages/core/package.json (checked by
|
|
10
10
|
// core/test/guardrails.test.js so it can't silently go stale again).
|
|
11
|
-
const VERSION = '0.11.
|
|
11
|
+
const VERSION = '0.11.8';
|
|
12
12
|
|
|
13
13
|
// --- Component Registry ---
|
|
14
14
|
// Tracks mounted components for agent inspection.
|
package/src/components.js
CHANGED
|
@@ -81,19 +81,31 @@ export function lazy(loader) {
|
|
|
81
81
|
export function Suspense({ fallback, children }) {
|
|
82
82
|
const loading = signal(false);
|
|
83
83
|
const pendingPromises = new Set();
|
|
84
|
+
let failed = false;
|
|
84
85
|
|
|
85
86
|
// Suspense boundary marker
|
|
86
87
|
const boundary = {
|
|
87
88
|
_suspense: true,
|
|
88
89
|
onSuspend(promise) {
|
|
90
|
+
if (failed) return;
|
|
89
91
|
loading.set(true);
|
|
90
92
|
pendingPromises.add(promise);
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
// Rejection is handled separately from fulfilment. Clearing the fallback
|
|
94
|
+
// on a rejection re-renders the child, which suspends on the same
|
|
95
|
+
// rejected thenable again, forever. Latch the failure and stay put.
|
|
96
|
+
promise.then(
|
|
97
|
+
() => {
|
|
98
|
+
pendingPromises.delete(promise);
|
|
99
|
+
if (pendingPromises.size === 0) {
|
|
100
|
+
loading.set(false);
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
(err) => {
|
|
104
|
+
failed = true;
|
|
105
|
+
pendingPromises.delete(promise);
|
|
106
|
+
console.error('[what] Suspense: a suspended child rejected:', err);
|
|
107
|
+
},
|
|
108
|
+
);
|
|
97
109
|
},
|
|
98
110
|
};
|
|
99
111
|
|
|
@@ -105,6 +117,10 @@ export function Suspense({ fallback, children }) {
|
|
|
105
117
|
};
|
|
106
118
|
}
|
|
107
119
|
|
|
120
|
+
// The boundary context only exists once createSuspenseBoundary runs, so
|
|
121
|
+
// compiled children must not be built during this call. See createComponent.
|
|
122
|
+
Suspense._deferChildren = true;
|
|
123
|
+
|
|
108
124
|
// --- ErrorBoundary ---
|
|
109
125
|
// Catch errors in children and show fallback.
|
|
110
126
|
// Uses a signal to track error state so it works with reactive rendering.
|
|
@@ -135,6 +151,10 @@ export function ErrorBoundary({ fallback, children, onError }) {
|
|
|
135
151
|
};
|
|
136
152
|
}
|
|
137
153
|
|
|
154
|
+
// The boundary context only exists once createErrorBoundary runs, so compiled
|
|
155
|
+
// children must not be built during this call. See createComponent.
|
|
156
|
+
ErrorBoundary._deferChildren = true;
|
|
157
|
+
|
|
138
158
|
// Helper to report error to nearest boundary
|
|
139
159
|
// Walks the component context tree (not a runtime stack) so async errors are caught
|
|
140
160
|
export function reportError(error, startCtx) {
|
|
@@ -216,33 +236,38 @@ export function For({ each, fallback = null, children }) {
|
|
|
216
236
|
// Multi-condition rendering (like switch statement).
|
|
217
237
|
|
|
218
238
|
export function Switch({ fallback = null, children }) {
|
|
219
|
-
// The Match children
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
//
|
|
239
|
+
// The Match children are static, so resolve them once. The match loop, which
|
|
240
|
+
// reads each Match's reactive `when`, must run inside a reactive thunk (see
|
|
241
|
+
// Show/For above): components run once, so evaluating `when()` in the body
|
|
242
|
+
// here would snapshot the active arm a single time and `<Switch>`/`<Match
|
|
243
|
+
// when={() => sig()}>` would render once and never update.
|
|
244
|
+
// This is the runtime path, for h() and the automatic JSX runtime, where a
|
|
245
|
+
// <Match> arrives as an unexecuted marker vnode. The fine-grained compiler
|
|
246
|
+
// lowers <Switch> to the same conditional thunk and never reaches here; a
|
|
247
|
+
// <Switch> it cannot lower is a build error rather than a call into this.
|
|
226
248
|
const kids = Array.isArray(children) ? children : [children];
|
|
227
249
|
|
|
228
250
|
return () => {
|
|
229
251
|
for (const child of kids) {
|
|
230
252
|
if (child && child.tag === Match) {
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
if (condition) {
|
|
235
|
-
return child.children;
|
|
236
|
-
}
|
|
253
|
+
const when = child.props.when;
|
|
254
|
+
const condition = typeof when === 'function' ? when() : when;
|
|
255
|
+
if (condition) return child.children;
|
|
237
256
|
}
|
|
238
257
|
}
|
|
239
258
|
return fallback;
|
|
240
259
|
};
|
|
241
260
|
}
|
|
242
261
|
|
|
243
|
-
export function Match(
|
|
244
|
-
//
|
|
245
|
-
|
|
262
|
+
export function Match(props) {
|
|
263
|
+
// Executed rather than left as a marker, which is what a lone compiled
|
|
264
|
+
// <Match> does. Returning a `{ tag: Match }` vnode here would send createDOM
|
|
265
|
+
// straight back into Match forever, so return a reactive thunk that renders
|
|
266
|
+
// the arm when it matches.
|
|
267
|
+
return () => {
|
|
268
|
+
const condition = typeof props.when === 'function' ? props.when() : props.when;
|
|
269
|
+
return condition ? props.children : null;
|
|
270
|
+
};
|
|
246
271
|
}
|
|
247
272
|
|
|
248
273
|
// --- Island ---
|