@nisli/core 0.56.0 → 0.57.1
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/README.md +70 -9
- package/dist/bundler/component.js +409 -0
- package/dist/bundler/component.js.map +7 -0
- package/dist/bundler/context.js +27 -0
- package/dist/bundler/context.js.map +7 -0
- package/dist/bundler/diagnostics.js +55 -0
- package/dist/bundler/diagnostics.js.map +7 -0
- package/dist/bundler/disposable.js +9 -0
- package/dist/bundler/disposable.js.map +7 -0
- package/dist/bundler/element-context.js +47 -0
- package/dist/bundler/element-context.js.map +7 -0
- package/dist/bundler/emitter.js +70 -0
- package/dist/bundler/emitter.js.map +7 -0
- package/dist/bundler/hmr/registry.js +72 -0
- package/dist/bundler/hmr/registry.js.map +7 -0
- package/dist/bundler/hmr/transform.js +30 -0
- package/dist/bundler/hmr/transform.js.map +7 -0
- package/dist/bundler/index.js +103 -0
- package/dist/bundler/index.js.map +7 -0
- package/dist/bundler/injector.js +75 -0
- package/dist/bundler/injector.js.map +7 -0
- package/dist/bundler/lifecycle.js +56 -0
- package/dist/bundler/lifecycle.js.map +7 -0
- package/dist/bundler/projection.js +92 -0
- package/dist/bundler/projection.js.map +7 -0
- package/dist/bundler/query.js +302 -0
- package/dist/bundler/query.js.map +7 -0
- package/dist/bundler/ref.js +16 -0
- package/dist/bundler/ref.js.map +7 -0
- package/dist/bundler/resource.js +99 -0
- package/dist/bundler/resource.js.map +7 -0
- package/dist/bundler/settle.js +43 -0
- package/dist/bundler/settle.js.map +7 -0
- package/dist/bundler/signal.js +403 -0
- package/dist/bundler/signal.js.map +7 -0
- package/dist/bundler/template-audit.js +162 -0
- package/dist/bundler/template-audit.js.map +7 -0
- package/dist/bundler/template.js +922 -0
- package/dist/bundler/template.js.map +7 -0
- package/dist/bundler/view-transition.js +20 -0
- package/dist/bundler/view-transition.js.map +7 -0
- package/dist/bundler/vite-hmr/plugin.js +32 -0
- package/dist/bundler/vite-hmr/plugin.js.map +7 -0
- package/dist/bundler/vite-hmr/runtime.js +8 -0
- package/dist/bundler/vite-hmr/runtime.js.map +7 -0
- package/dist/index.js +2682 -47
- package/dist/index.js.map +7 -1
- package/dist/index.production.js +2 -0
- package/dist/index.production.js.map +6 -0
- package/dist/{diagnostics.d.ts → types/diagnostics.d.ts} +8 -15
- package/dist/{template-audit.d.ts → types/template-audit.d.ts} +4 -5
- package/dist/{template.d.ts → types/template.d.ts} +2 -1
- package/package.json +15 -10
- package/dist/component.js +0 -633
- package/dist/component.js.map +0 -1
- package/dist/context.js +0 -55
- package/dist/context.js.map +0 -1
- package/dist/diagnostics.js +0 -118
- package/dist/diagnostics.js.map +0 -1
- package/dist/disposable.js +0 -8
- package/dist/disposable.js.map +0 -1
- package/dist/element-context.js +0 -108
- package/dist/element-context.js.map +0 -1
- package/dist/emitter.js +0 -91
- package/dist/emitter.js.map +0 -1
- package/dist/hmr/registry.js +0 -136
- package/dist/hmr/registry.js.map +0 -1
- package/dist/hmr/transform.js +0 -52
- package/dist/hmr/transform.js.map +0 -1
- package/dist/injector.js +0 -118
- package/dist/injector.js.map +0 -1
- package/dist/lifecycle.js +0 -117
- package/dist/lifecycle.js.map +0 -1
- package/dist/projection.js +0 -179
- package/dist/projection.js.map +0 -1
- package/dist/query.js +0 -429
- package/dist/query.js.map +0 -1
- package/dist/ref.js +0 -43
- package/dist/ref.js.map +0 -1
- package/dist/resource.js +0 -129
- package/dist/resource.js.map +0 -1
- package/dist/settle.js +0 -106
- package/dist/settle.js.map +0 -1
- package/dist/signal.js +0 -645
- package/dist/signal.js.map +0 -1
- package/dist/template-audit.js +0 -246
- package/dist/template-audit.js.map +0 -1
- package/dist/template.js +0 -1244
- package/dist/template.js.map +0 -1
- package/dist/view-transition.js +0 -98
- package/dist/view-transition.js.map +0 -1
- package/dist/vite-hmr/plugin.js +0 -62
- package/dist/vite-hmr/plugin.js.map +0 -1
- package/dist/vite-hmr/runtime.js +0 -17
- package/dist/vite-hmr/runtime.js.map +0 -1
- /package/dist/{component.d.ts → types/component.d.ts} +0 -0
- /package/dist/{context.d.ts → types/context.d.ts} +0 -0
- /package/dist/{disposable.d.ts → types/disposable.d.ts} +0 -0
- /package/dist/{element-context.d.ts → types/element-context.d.ts} +0 -0
- /package/dist/{emitter.d.ts → types/emitter.d.ts} +0 -0
- /package/dist/{hmr → types/hmr}/registry.d.ts +0 -0
- /package/dist/{hmr → types/hmr}/transform.d.ts +0 -0
- /package/dist/{index.d.ts → types/index.d.ts} +0 -0
- /package/dist/{injector.d.ts → types/injector.d.ts} +0 -0
- /package/dist/{lifecycle.d.ts → types/lifecycle.d.ts} +0 -0
- /package/dist/{projection.d.ts → types/projection.d.ts} +0 -0
- /package/dist/{query.d.ts → types/query.d.ts} +0 -0
- /package/dist/{ref.d.ts → types/ref.d.ts} +0 -0
- /package/dist/{resource.d.ts → types/resource.d.ts} +0 -0
- /package/dist/{settle.d.ts → types/settle.d.ts} +0 -0
- /package/dist/{signal.d.ts → types/signal.d.ts} +0 -0
- /package/dist/{view-transition.d.ts → types/view-transition.d.ts} +0 -0
- /package/dist/{vite-hmr → types/vite-hmr}/plugin.d.ts +0 -0
- /package/dist/{vite-hmr → types/vite-hmr}/runtime.d.ts +0 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
let currentComponent = null;
|
|
2
|
+
function runWithContext(component, fn) {
|
|
3
|
+
const prev = currentComponent;
|
|
4
|
+
currentComponent = component;
|
|
5
|
+
try {
|
|
6
|
+
fn();
|
|
7
|
+
} finally {
|
|
8
|
+
currentComponent = prev;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function getCurrentComponent() {
|
|
12
|
+
if (!currentComponent) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
"getCurrentComponent() called outside setup(). inject(), effect(), and other context-dependent functions can only be called during component initialization."
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
return currentComponent;
|
|
18
|
+
}
|
|
19
|
+
function hasContext() {
|
|
20
|
+
return currentComponent !== null;
|
|
21
|
+
}
|
|
22
|
+
export {
|
|
23
|
+
getCurrentComponent,
|
|
24
|
+
hasContext,
|
|
25
|
+
runWithContext
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/context.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * context.ts \u2014 Setup context for pure function DI\n *\n * The setup context lets pure functions (inject(), listen(), effect())\n * access the component they belong to without needing `this`.\n *\n * The context exists ONLY during component initialization (setup()).\n * It is NOT a runtime thing \u2014 it's a synchronous initialization-time concept.\n *\n * Same pattern as Angular's inject(), Solid's createSignal(), Vue's setup().\n */\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Minimal interface for a component host. component.ts provides the\n * full implementation; this file only needs the disposal hook.\n */\nexport interface ComponentHost {\n /** Register a cleanup function to run on disconnectedCallback */\n addDisposer(fn: () => void): void;\n /** The raw HTMLElement */\n element: HTMLElement;\n}\n\n// \u2500\u2500 Context state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nlet currentComponent: ComponentHost | null = null;\n\n// \u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Run a function with a component as the active context.\n * Pure functions called during `fn` can access the component via\n * `getCurrentComponent()`.\n *\n * Context is strictly synchronous \u2014 it does NOT survive across\n * await boundaries or microtasks.\n */\nexport function runWithContext(component: ComponentHost, fn: () => void): void {\n const prev = currentComponent;\n currentComponent = component;\n try {\n fn();\n } finally {\n currentComponent = prev;\n }\n}\n\n/**\n * Get the currently active component host.\n * Throws if called outside a setup() context.\n *\n * This is framework-internal \u2014 component authors use inject(), effect(),\n * etc., which call this internally.\n */\nexport function getCurrentComponent(): ComponentHost {\n if (!currentComponent) {\n throw new Error(\n 'getCurrentComponent() called outside setup(). ' +\n 'inject(), effect(), and other context-dependent functions ' +\n 'can only be called during component initialization.'\n );\n }\n return currentComponent;\n}\n\n/**\n * Check if there is an active setup context.\n * Returns true during component setup, false otherwise.\n */\nexport function hasContext(): boolean {\n return currentComponent !== null;\n}\n"],
|
|
5
|
+
"mappings": "AA2BA,IAAI,mBAAyC;AAYtC,SAAS,eAAe,WAA0B,IAAsB;AAC7E,QAAM,OAAO;AACb,qBAAmB;AACnB,MAAI;AACF,OAAG;AAAA,EACL,UAAE;AACA,uBAAmB;AAAA,EACrB;AACF;AASO,SAAS,sBAAqC;AACnD,MAAI,CAAC,kBAAkB;AACrB,UAAM,IAAI;AAAA,MACR;AAAA,IAGF;AAAA,EACF;AACA,SAAO;AACT;AAMO,SAAS,aAAsB;AACpC,SAAO,qBAAqB;AAC9B;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const codes = {
|
|
2
|
+
// Range placeholders — owner module in parentheses.
|
|
3
|
+
N1xx: "template: parse/static-audit diagnostics (template.ts \u2014 template wave)",
|
|
4
|
+
N2xx: "define/props: registration and prop-contract diagnostics (component.ts)",
|
|
5
|
+
N3xx: "reactivity: scheduler/purity diagnostics (signal.ts \u2014 scheduler wave)",
|
|
6
|
+
N4xx: "lifecycle: setup/mount containment (component.ts, lifecycle.ts)",
|
|
7
|
+
N5xx: "dependency injection (injector.ts)",
|
|
8
|
+
N6xx: "async: query/resource/settle diagnostics (query.ts, resource.ts \u2014 async wave)",
|
|
9
|
+
// Assigned codes.
|
|
10
|
+
N201: "duplicate component definition \u2014 tag already defined; first definition stays live",
|
|
11
|
+
N202: "unknown-prop echo \u2014 prop written after mount but never read by setup()",
|
|
12
|
+
N401: 'setup failed \u2014 scope contained, host stamped data-nisli-error="N401"',
|
|
13
|
+
N402: 'onMount failed \u2014 scope contained, host stamped data-nisli-error="N402"',
|
|
14
|
+
N501: "provide() after the token was already instantiated \u2014 resetInjector() first",
|
|
15
|
+
N101: "template audit \u2014 dash-tag never defined (misspelled or unimported component?)",
|
|
16
|
+
N102: "template audit \u2014 unknown @event name",
|
|
17
|
+
N103: "template audit \u2014 unknown event modifier",
|
|
18
|
+
N104: "template audit \u2014 undeclared attribute on a component with declared attrs",
|
|
19
|
+
N105: "template \u2014 remounting a live bound TemplateResult (shared-binding corruption)",
|
|
20
|
+
N106: "template \u2014 html:inner requires a branded value: raw(), sanitized(), or TrustedHTML",
|
|
21
|
+
N107: "template \u2014 html:inner sanitized() has no sink: no native Element.setHTML() and no setSanitizerFallback() hook",
|
|
22
|
+
N301: "reactivity \u2014 effect exceeded the consecutive re-schedule cap (loop); disposed",
|
|
23
|
+
N302: "reactivity \u2014 effect wrote a signal in its own dependency set (attribution)",
|
|
24
|
+
N303: "reactivity \u2014 flush()/tick() iteration cap reached before quiescence",
|
|
25
|
+
N310: "reactivity \u2014 effect callback returned a Promise; use resource() for async",
|
|
26
|
+
N602: "query \u2014 invalid key element (flat primitives only; null is the optional sentinel)",
|
|
27
|
+
N603: "settle() \u2014 no quiescence within the iteration cap; returning anyway"
|
|
28
|
+
// N601 (mixed QueryClients) was retired before release: N501 makes that
|
|
29
|
+
// state unrepresentable at its cause. The number is not reused.
|
|
30
|
+
};
|
|
31
|
+
let override = null;
|
|
32
|
+
function isDev() {
|
|
33
|
+
return process.env.NODE_ENV !== "production" && (override ?? true);
|
|
34
|
+
}
|
|
35
|
+
function setDevMode(on) {
|
|
36
|
+
if (process.env.NODE_ENV !== "production") override = on;
|
|
37
|
+
}
|
|
38
|
+
function formatDiag(code, message) {
|
|
39
|
+
return `[nisli ${code}] ${message}`;
|
|
40
|
+
}
|
|
41
|
+
function diag(code, message, detail) {
|
|
42
|
+
if (detail === void 0) {
|
|
43
|
+
console.error(formatDiag(code, message));
|
|
44
|
+
} else {
|
|
45
|
+
console.error(formatDiag(code, message), detail);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export {
|
|
49
|
+
codes,
|
|
50
|
+
diag,
|
|
51
|
+
formatDiag,
|
|
52
|
+
isDev,
|
|
53
|
+
setDevMode
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=diagnostics.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/diagnostics.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * diagnostics.ts \u2014 coded diagnostics leaf + the dev-mode gate (ADR 0030.2, Wave 1)\n *\n * The shared foundation every core layer reports through. Two rules keep it a\n * LEAF (ADR 0030.2 \u00A77): zero imports from other core modules (so signal.ts,\n * template.ts, component.ts, injector.ts can all use it without cycles), and\n * codes + a minimal payload emitter only \u2014 no docs prose.\n *\n * Contract:\n * - Every diagnostic carries a stable machine code `N<range><nn>` and renders\n * as ONE console.error line: `[nisli CODE] message` (+ structured detail as\n * the second console argument when given). Thrown coded errors reuse the\n * same `[nisli CODE]` prefix via {@link formatDiag} so `grep '\\[nisli '`\n * finds every framework-reported failure, logged or thrown.\n * - `diag()` itself is NOT dev-gated: gating is the CALL SITE's policy. Some\n * diagnostics are production facts (T6 containment N401/N402 \u2014 parity with\n * the pre-T6 unconditional console.error); others are dev-only attribution\n * (N201/N202/N501) and their callers guard with {@link isDev}.\n *\n * Availability belongs to the artifact (ADR 0048). Bundler ESM leaves standard\n * NODE_ENV guards at each optional work site; the native browser artifacts\n * resolve them at publication. No process global is needed in either browser\n * artifact. The runtime override controls available diagnostics only: it cannot\n * resurrect instrumentation removed from a production artifact.\n */\n\n// \u2500\u2500 Code registry \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// The minimal central code table (0030.1 B2, landed as part of T6 per \u00A78).\n// `Nxyz` = concrete code; `Nxxx` range keys document ownership so parallel\n// waves allocate without collision. Ranges owned by other Wave-1 worktrees\n// (template, reactivity, async) are seeded here as placeholders only \u2014 their\n// concrete codes land with their waves.\nexport const codes: Record<string, string> = {\n // Range placeholders \u2014 owner module in parentheses.\n N1xx: 'template: parse/static-audit diagnostics (template.ts \u2014 template wave)',\n N2xx: 'define/props: registration and prop-contract diagnostics (component.ts)',\n N3xx: 'reactivity: scheduler/purity diagnostics (signal.ts \u2014 scheduler wave)',\n N4xx: 'lifecycle: setup/mount containment (component.ts, lifecycle.ts)',\n N5xx: 'dependency injection (injector.ts)',\n N6xx: 'async: query/resource/settle diagnostics (query.ts, resource.ts \u2014 async wave)',\n // Assigned codes.\n N201: 'duplicate component definition \u2014 tag already defined; first definition stays live',\n N202: 'unknown-prop echo \u2014 prop written after mount but never read by setup()',\n N401: 'setup failed \u2014 scope contained, host stamped data-nisli-error=\"N401\"',\n N402: 'onMount failed \u2014 scope contained, host stamped data-nisli-error=\"N402\"',\n N501: 'provide() after the token was already instantiated \u2014 resetInjector() first',\n N101: 'template audit \u2014 dash-tag never defined (misspelled or unimported component?)',\n N102: 'template audit \u2014 unknown @event name',\n N103: 'template audit \u2014 unknown event modifier',\n N104: 'template audit \u2014 undeclared attribute on a component with declared attrs',\n N105: 'template \u2014 remounting a live bound TemplateResult (shared-binding corruption)',\n N106: 'template \u2014 html:inner requires a branded value: raw(), sanitized(), or TrustedHTML',\n N107: 'template \u2014 html:inner sanitized() has no sink: no native Element.setHTML() and no setSanitizerFallback() hook',\n N301: 'reactivity \u2014 effect exceeded the consecutive re-schedule cap (loop); disposed',\n N302: 'reactivity \u2014 effect wrote a signal in its own dependency set (attribution)',\n N303: 'reactivity \u2014 flush()/tick() iteration cap reached before quiescence',\n N310: 'reactivity \u2014 effect callback returned a Promise; use resource() for async',\n N602: 'query \u2014 invalid key element (flat primitives only; null is the optional sentinel)',\n N603: 'settle() \u2014 no quiescence within the iteration cap; returning anyway',\n // N601 (mixed QueryClients) was retired before release: N501 makes that\n // state unrepresentable at its cause. The number is not reused.\n};\n\n// \u2500\u2500 Dev gate \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nlet override: boolean | null = null;\n\n/** Runtime preference within this artifact's development capability. */\nexport function isDev(): boolean {\n return process.env.NODE_ENV !== 'production' && (override ?? true);\n}\n\n/**\n * Enable/suppress available development diagnostics, or restore the artifact's\n * default (`null`). A production artifact cannot re-enable removed diagnostics.\n */\nexport function setDevMode(on: boolean | null): void {\n if (process.env.NODE_ENV !== 'production') override = on;\n}\n\n// \u2500\u2500 Emission \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** The shared `[nisli CODE] message` line, for both logged and thrown paths. */\nexport function formatDiag(code: string, message: string): string {\n return `[nisli ${code}] ${message}`;\n}\n\n/**\n * Emit one coded diagnostic: a single console.error line\n * `[nisli CODE] message`, with `detail` attached as structured payload.\n * NOT dev-gated here \u2014 the caller owns the policy (see module doc).\n */\nexport function diag(code: string, message: string, detail?: Record<string, unknown>): void {\n if (detail === undefined) {\n console.error(formatDiag(code, message));\n } else {\n console.error(formatDiag(code, message), detail);\n }\n}\n"],
|
|
5
|
+
"mappings": "AAiCO,MAAM,QAAgC;AAAA;AAAA,EAE3C,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA;AAAA,EAEN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA;AAAA;AAGR;AAIA,IAAI,WAA2B;AAGxB,SAAS,QAAiB;AAC/B,SAAO,QAAQ,IAAI,aAAa,iBAAiB,YAAY;AAC/D;AAMO,SAAS,WAAW,IAA0B;AACnD,MAAI,QAAQ,IAAI,aAAa,aAAc,YAAW;AACxD;AAKO,SAAS,WAAW,MAAc,SAAyB;AAChE,SAAO,UAAU,IAAI,KAAK,OAAO;AACnC;AAOO,SAAS,KAAK,MAAc,SAAiB,QAAwC;AAC1F,MAAI,WAAW,QAAW;AACxB,YAAQ,MAAM,WAAW,MAAM,OAAO,CAAC;AAAA,EACzC,OAAO;AACL,YAAQ,MAAM,WAAW,MAAM,OAAO,GAAG,MAAM;AAAA,EACjD;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/disposable.ts"],
|
|
4
|
+
"sourcesContent": ["/** A disposal surface that exposes ERM when the consumer's lib supports it. */\ntype DisposeSymbol = typeof Symbol extends {\n readonly dispose: infer Key extends symbol;\n} ? Key : never;\n\nexport type DisposableResource = {\n [Key in DisposeSymbol]: () => void;\n};\n\n/** Callable disposer returned by effects, subscriptions, and emitter handles. */\nexport interface Disposer extends DisposableResource {\n (): void;\n}\n\nconst DISPOSE = Symbol.dispose as typeof Symbol.dispose | undefined;\n\n/** Attach a guarded Symbol.dispose alias without polyfilling the platform. */\nexport function disposable<T extends object>(\n value: T,\n dispose: () => void,\n): T & DisposableResource {\n if (DISPOSE) (value as Record<symbol, () => void>)[DISPOSE] = dispose;\n return value as T & DisposableResource;\n}\n"],
|
|
5
|
+
"mappings": "AAcA,MAAM,UAAU,OAAO;AAGhB,SAAS,WACd,OACA,SACwB;AACxB,MAAI,QAAS,CAAC,MAAqC,OAAO,IAAI;AAC9D,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { getCurrentComponent, hasContext } from "./context.js";
|
|
2
|
+
function createContext(name, options = {}) {
|
|
3
|
+
const { providerTag } = options;
|
|
4
|
+
const KEY = /* @__PURE__ */ Symbol(`nisli.context:${name}`);
|
|
5
|
+
const store = (el) => el;
|
|
6
|
+
const provide = (host, value) => {
|
|
7
|
+
store(host)[KEY] = value;
|
|
8
|
+
};
|
|
9
|
+
const peek = (host) => KEY in host ? store(host)[KEY] : void 0;
|
|
10
|
+
const find = (start) => {
|
|
11
|
+
let el = start;
|
|
12
|
+
while (el) {
|
|
13
|
+
if (KEY in el) return store(el)[KEY];
|
|
14
|
+
el = el.parentElement;
|
|
15
|
+
}
|
|
16
|
+
return void 0;
|
|
17
|
+
};
|
|
18
|
+
const startHost = (host, caller) => {
|
|
19
|
+
if (host) return host;
|
|
20
|
+
if (!hasContext()) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`${caller} for context "${name}" was called without a host and outside component setup. Call it during setup (so the provider is resolved before any portal move), or pass an explicit host.`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return getCurrentComponent().element;
|
|
26
|
+
};
|
|
27
|
+
const inject = ((host) => {
|
|
28
|
+
const start = startHost(host, "inject()");
|
|
29
|
+
const found = find(start);
|
|
30
|
+
if (found === void 0) {
|
|
31
|
+
const injectingTag = start.tagName.toLowerCase();
|
|
32
|
+
throw new Error(
|
|
33
|
+
providerTag ? `Context "${name}": <${injectingTag}> must be used inside <${providerTag}>.` : `Context "${name}" not found: <${injectingTag}> must be rendered inside a provider that calls ${name}Context.provide(host, \u2026).`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return found;
|
|
37
|
+
});
|
|
38
|
+
inject.optional = (host) => {
|
|
39
|
+
if (!host && !hasContext()) return void 0;
|
|
40
|
+
return find(startHost(host, "inject.optional()"));
|
|
41
|
+
};
|
|
42
|
+
return { name, provide, inject, peek };
|
|
43
|
+
}
|
|
44
|
+
export {
|
|
45
|
+
createContext
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=element-context.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/element-context.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * element-context.ts \u2014 subtree-scoped context (provide / inject).\n *\n * The DOM-native equivalent of React/Radix context: a provider publishes a\n * typed value on its host element, and descendant components `inject()` it by\n * walking up the element tree. It replaces the userland `host.__uiX = state` +\n * `host.closest('ui-x')` convention (stringly-typed, invisible to TypeScript,\n * re-implemented per family) with an identity-keyed, end-to-end-typed primitive.\n *\n * ```ts\n * interface TabsState { value: ReadonlySignal<string>; setValue(v: string): void }\n * const TabsContext = createContext<TabsState>('Tabs');\n *\n * // provider setup:\n * TabsContext.provide(host, state);\n *\n * // descendant setup:\n * const tabs = TabsContext.inject(); // throws (error boundary) if absent\n * const maybe = TabsContext.inject.optional(); // TabsState | undefined\n * ```\n *\n * Design (see ADR 0025 \u00A72):\n *\n * - **Typed, no string keys.** Each `createContext` mints a private `Symbol`\n * used as the storage key; consumers reference the context object, never a\n * string. Two contexts never collide even with the same `name` (name is for\n * diagnostics only).\n *\n * - **Capture at setup, not at lookup.** `inject()` resolves during component\n * setup, while the injecting host is still a DOM descendant of its provider.\n * It returns the value *once*; because provider values carry signals,\n * reactivity flows through them with no further tree-walks. This is what makes\n * it **portal-safe**: after a subtree is reparented to `<body>` (dialog, menu,\n * \u2026) the injected value is already held, so the component keeps resolving its\n * ORIGINAL provider \u2014 a later walk from the portaled host would find nothing.\n * `inject()` therefore requires an active setup context when no host is passed.\n *\n * - **Zero cost for non-users.** Providing sets one symbol property on the host;\n * injecting is a `parentElement` walk. Components that use neither pay nothing,\n * and there is no global registry to maintain.\n */\n\nimport { getCurrentComponent, hasContext } from './context.js';\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface Inject<T> {\n /**\n * Resolve the nearest provider's value by walking up from `host` (default:\n * the current component's host, requiring a setup context). Throws if no\n * provider is found \u2014 in a component this surfaces via the error boundary.\n */\n (host?: HTMLElement): T;\n /** Like `inject()` but returns `undefined` instead of throwing when absent. */\n optional(host?: HTMLElement): T | undefined;\n}\n\nexport interface Context<T> {\n /** Diagnostic label (error messages); not an identity key. */\n readonly name: string;\n /** Publish `value` on `host` so descendants can `inject()` it. */\n provide(host: HTMLElement, value: T): void;\n /** Resolve this context from a descendant (see {@link Inject}). */\n readonly inject: Inject<T>;\n /**\n * Read the value provided on THIS host only \u2014 no ancestor walk. `undefined`\n * if `host` is not a provider of this context. The low-level primitive for\n * bespoke walks (e.g. \"nearest provider of context A OR context B\", which\n * `inject` can't express in one pass); prefer `inject`/`inject.optional`\n * for ordinary descendant resolution.\n */\n peek(host: HTMLElement): T | undefined;\n}\n\n// \u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Create a subtree-scoped context. `name` is used only in diagnostics; the\n * context's identity (a private symbol) is what keys storage and lookup.\n *\n * Pass `{ providerTag }` to name the provider element so a missing-provider\n * `inject()` produces an ACTIONABLE, element-language error \u2014\n * `<injecting-tag> must be used inside <provider-tag>.` \u2014 instead of naming the\n * private context object. Set it whenever consumers are DOM elements (the whole\n * registry): it's the difference between a message a plain-HTML user can act on\n * and one they can't.\n */\nexport function createContext<T>(\n name: string,\n options: { providerTag?: string } = {},\n): Context<T> {\n const { providerTag } = options;\n // Private per-context storage key. Not derived from `name`, so same-named\n // contexts stay distinct, and it never appears in userland.\n const KEY = Symbol(`nisli.context:${name}`);\n\n const store = (el: Element): Record<symbol, unknown> =>\n el as unknown as Record<symbol, unknown>;\n\n const provide = (host: HTMLElement, value: T): void => {\n store(host)[KEY] = value;\n };\n\n const peek = (host: HTMLElement): T | undefined =>\n KEY in host ? (store(host)[KEY] as T) : undefined;\n\n // Walk up the element tree (inclusive of `start`) to the nearest provider.\n // `parentElement` transparently crosses `display: contents` hosts.\n const find = (start: HTMLElement): T | undefined => {\n let el: Element | null = start;\n while (el) {\n if (KEY in el) return store(el)[KEY] as T;\n el = el.parentElement;\n }\n return undefined;\n };\n\n const startHost = (host: HTMLElement | undefined, caller: string): HTMLElement | null => {\n if (host) return host;\n if (!hasContext()) {\n throw new Error(\n `${caller} for context \"${name}\" was called without a host and outside component ` +\n `setup. Call it during setup (so the provider is resolved before any portal move), ` +\n `or pass an explicit host.`,\n );\n }\n return getCurrentComponent().element;\n };\n\n const inject = ((host?: HTMLElement): T => {\n const start = startHost(host, 'inject()')!;\n const found = find(start);\n if (found === undefined) {\n const injectingTag = start.tagName.toLowerCase();\n // With a declared provider tag, speak element-language the consumer can\n // act on; otherwise fall back to the generic (provider named by API).\n throw new Error(\n providerTag\n ? `Context \"${name}\": <${injectingTag}> must be used inside <${providerTag}>.`\n : `Context \"${name}\" not found: <${injectingTag}> must be rendered ` +\n `inside a provider that calls ${name}Context.provide(host, \u2026).`,\n );\n }\n return found;\n }) as Inject<T>;\n\n inject.optional = (host?: HTMLElement): T | undefined => {\n // Outside setup with no host: there is nothing to resolve from \u2014 undefined.\n if (!host && !hasContext()) return undefined;\n return find(startHost(host, 'inject.optional()')!);\n };\n\n return { name, provide, inject, peek };\n}\n"],
|
|
5
|
+
"mappings": "AA0CA,SAAS,qBAAqB,kBAAkB;AA6CzC,SAAS,cACd,MACA,UAAoC,CAAC,GACzB;AACZ,QAAM,EAAE,YAAY,IAAI;AAGxB,QAAM,MAAM,uBAAO,iBAAiB,IAAI,EAAE;AAE1C,QAAM,QAAQ,CAAC,OACb;AAEF,QAAM,UAAU,CAAC,MAAmB,UAAmB;AACrD,UAAM,IAAI,EAAE,GAAG,IAAI;AAAA,EACrB;AAEA,QAAM,OAAO,CAAC,SACZ,OAAO,OAAQ,MAAM,IAAI,EAAE,GAAG,IAAU;AAI1C,QAAM,OAAO,CAAC,UAAsC;AAClD,QAAI,KAAqB;AACzB,WAAO,IAAI;AACT,UAAI,OAAO,GAAI,QAAO,MAAM,EAAE,EAAE,GAAG;AACnC,WAAK,GAAG;AAAA,IACV;AACA,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,CAAC,MAA+B,WAAuC;AACvF,QAAI,KAAM,QAAO;AACjB,QAAI,CAAC,WAAW,GAAG;AACjB,YAAM,IAAI;AAAA,QACR,GAAG,MAAM,iBAAiB,IAAI;AAAA,MAGhC;AAAA,IACF;AACA,WAAO,oBAAoB,EAAE;AAAA,EAC/B;AAEA,QAAM,UAAU,CAAC,SAA0B;AACzC,UAAM,QAAQ,UAAU,MAAM,UAAU;AACxC,UAAM,QAAQ,KAAK,KAAK;AACxB,QAAI,UAAU,QAAW;AACvB,YAAM,eAAe,MAAM,QAAQ,YAAY;AAG/C,YAAM,IAAI;AAAA,QACR,cACI,YAAY,IAAI,OAAO,YAAY,0BAA0B,WAAW,OACxE,YAAY,IAAI,iBAAiB,YAAY,mDACX,IAAI;AAAA,MAC5C;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAEA,SAAO,WAAW,CAAC,SAAsC;AAEvD,QAAI,CAAC,QAAQ,CAAC,WAAW,EAAG,QAAO;AACnC,WAAO,KAAK,UAAU,MAAM,mBAAmB,CAAE;AAAA,EACnD;AAEA,SAAO,EAAE,MAAM,SAAS,QAAQ,KAAK;AACvC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { signal } from "./signal.js";
|
|
2
|
+
import { hasContext, getCurrentComponent } from "./context.js";
|
|
3
|
+
import { disposable } from "./disposable.js";
|
|
4
|
+
class Emitter {
|
|
5
|
+
listeners = /* @__PURE__ */ new Map();
|
|
6
|
+
/**
|
|
7
|
+
* Emit a typed event. All subscribers for this event name are called
|
|
8
|
+
* synchronously. If a subscriber throws, other subscribers still fire.
|
|
9
|
+
*/
|
|
10
|
+
emit(event, payload) {
|
|
11
|
+
const set = this.listeners.get(event);
|
|
12
|
+
if (!set) return;
|
|
13
|
+
for (const fn of [...set]) {
|
|
14
|
+
try {
|
|
15
|
+
fn(payload);
|
|
16
|
+
} catch (err) {
|
|
17
|
+
console.error(`Emitter: subscriber for '${String(event)}' threw:`, err);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Subscribe to a typed event. Returns an unsubscribe function.
|
|
23
|
+
*
|
|
24
|
+
* If called during component setup, the subscription is automatically
|
|
25
|
+
* disposed on disconnectedCallback.
|
|
26
|
+
*/
|
|
27
|
+
on(event, fn) {
|
|
28
|
+
let set = this.listeners.get(event);
|
|
29
|
+
if (!set) {
|
|
30
|
+
set = /* @__PURE__ */ new Set();
|
|
31
|
+
this.listeners.set(event, set);
|
|
32
|
+
}
|
|
33
|
+
set.add(fn);
|
|
34
|
+
const unsubscribe = () => {
|
|
35
|
+
set.delete(fn);
|
|
36
|
+
if (set.size === 0) {
|
|
37
|
+
this.listeners.delete(event);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
if (hasContext()) {
|
|
41
|
+
getCurrentComponent().addDisposer(unsubscribe);
|
|
42
|
+
}
|
|
43
|
+
return disposable(unsubscribe, unsubscribe);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Bridge an event into the signal system.
|
|
47
|
+
* Returns a signal that updates whenever the event fires.
|
|
48
|
+
*
|
|
49
|
+
* @param event - Event name to subscribe to
|
|
50
|
+
* @param selector - Transform the payload into the signal value
|
|
51
|
+
* @param initial - Initial signal value before any event fires
|
|
52
|
+
*/
|
|
53
|
+
toSignal(event, selector, initial) {
|
|
54
|
+
const s = signal(initial);
|
|
55
|
+
this.on(event, (payload) => {
|
|
56
|
+
s.value = selector(payload);
|
|
57
|
+
});
|
|
58
|
+
return s;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Remove all subscribers. Useful for testing cleanup.
|
|
62
|
+
*/
|
|
63
|
+
clear() {
|
|
64
|
+
this.listeners.clear();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export {
|
|
68
|
+
Emitter
|
|
69
|
+
};
|
|
70
|
+
//# sourceMappingURL=emitter.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/emitter.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * emitter.ts \u2014 Typed pub/sub base class for services.\n *\n * Replaces `document.dispatchEvent(new CustomEvent(...))` with typed,\n * scoped event emitters. Services extend Emitter<T> where T defines\n * the event contract as a record of event names \u2192 payload types.\n *\n * ```ts\n * class NavigationEvents extends Emitter<{\n * select: { id: string };\n * filter: { filter: string };\n * }> {}\n *\n * const nav = inject(NavigationEvents);\n * nav.emit('select', { id: 'TASK-1' }); // typed\n * nav.on('select', ({ id }) => console.log(id)); // typed\n * const selectedId = nav.toSignal('select', e => e.id, null);\n * ```\n */\n\nimport { signal, type Signal } from './signal.js';\nimport { hasContext, getCurrentComponent } from './context.js';\nimport { disposable, type Disposer } from './disposable.js';\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ntype EventMap = Record<string, unknown>;\ntype Listener<T> = (payload: T) => void;\n\n// \u2500\u2500 Emitter base class \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport class Emitter<Events extends EventMap> {\n private listeners = new Map<keyof Events, Set<Listener<any>>>();\n\n /**\n * Emit a typed event. All subscribers for this event name are called\n * synchronously. If a subscriber throws, other subscribers still fire.\n */\n emit<K extends keyof Events>(event: K, payload: Events[K]): void {\n const set = this.listeners.get(event);\n if (!set) return;\n\n // Iterate a copy to allow unsubscribe during callback\n for (const fn of [...set]) {\n try {\n fn(payload);\n } catch (err) {\n console.error(`Emitter: subscriber for '${String(event)}' threw:`, err);\n }\n }\n }\n\n /**\n * Subscribe to a typed event. Returns an unsubscribe function.\n *\n * If called during component setup, the subscription is automatically\n * disposed on disconnectedCallback.\n */\n on<K extends keyof Events>(event: K, fn: Listener<Events[K]>): Disposer {\n let set = this.listeners.get(event);\n if (!set) {\n set = new Set();\n this.listeners.set(event, set);\n }\n set.add(fn);\n\n const unsubscribe = () => {\n set!.delete(fn);\n if (set!.size === 0) {\n this.listeners.delete(event);\n }\n };\n\n // Auto-dispose if inside component setup context\n if (hasContext()) {\n getCurrentComponent().addDisposer(unsubscribe);\n }\n\n return disposable(unsubscribe, unsubscribe);\n }\n\n /**\n * Bridge an event into the signal system.\n * Returns a signal that updates whenever the event fires.\n *\n * @param event - Event name to subscribe to\n * @param selector - Transform the payload into the signal value\n * @param initial - Initial signal value before any event fires\n */\n toSignal<K extends keyof Events, V>(\n event: K,\n selector: (payload: Events[K]) => V,\n initial: V,\n ): Signal<V> {\n const s = signal(initial);\n this.on(event, (payload) => {\n s.value = selector(payload);\n });\n return s;\n }\n\n /**\n * Remove all subscribers. Useful for testing cleanup.\n */\n clear(): void {\n this.listeners.clear();\n }\n}\n"],
|
|
5
|
+
"mappings": "AAoBA,SAAS,cAA2B;AACpC,SAAS,YAAY,2BAA2B;AAChD,SAAS,kBAAiC;AASnC,MAAM,QAAiC;AAAA,EACpC,YAAY,oBAAI,IAAsC;AAAA;AAAA;AAAA;AAAA;AAAA,EAM9D,KAA6B,OAAU,SAA0B;AAC/D,UAAM,MAAM,KAAK,UAAU,IAAI,KAAK;AACpC,QAAI,CAAC,IAAK;AAGV,eAAW,MAAM,CAAC,GAAG,GAAG,GAAG;AACzB,UAAI;AACF,WAAG,OAAO;AAAA,MACZ,SAAS,KAAK;AACZ,gBAAQ,MAAM,4BAA4B,OAAO,KAAK,CAAC,YAAY,GAAG;AAAA,MACxE;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,GAA2B,OAAU,IAAmC;AACtE,QAAI,MAAM,KAAK,UAAU,IAAI,KAAK;AAClC,QAAI,CAAC,KAAK;AACR,YAAM,oBAAI,IAAI;AACd,WAAK,UAAU,IAAI,OAAO,GAAG;AAAA,IAC/B;AACA,QAAI,IAAI,EAAE;AAEV,UAAM,cAAc,MAAM;AACxB,UAAK,OAAO,EAAE;AACd,UAAI,IAAK,SAAS,GAAG;AACnB,aAAK,UAAU,OAAO,KAAK;AAAA,MAC7B;AAAA,IACF;AAGA,QAAI,WAAW,GAAG;AAChB,0BAAoB,EAAE,YAAY,WAAW;AAAA,IAC/C;AAEA,WAAO,WAAW,aAAa,WAAW;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,SACE,OACA,UACA,SACW;AACX,UAAM,IAAI,OAAO,OAAO;AACxB,SAAK,GAAG,OAAO,CAAC,YAAY;AAC1B,QAAE,QAAQ,SAAS,OAAO;AAAA,IAC5B,CAAC;AACD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,SAAK,UAAU,MAAM;AAAA,EACvB;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const registry = /* @__PURE__ */ new Map();
|
|
2
|
+
const pendingRemounts = /* @__PURE__ */ new Set();
|
|
3
|
+
let remountScheduled = false;
|
|
4
|
+
let suppressAutoDrain = false;
|
|
5
|
+
function beginManualDrain() {
|
|
6
|
+
suppressAutoDrain = true;
|
|
7
|
+
remountScheduled = false;
|
|
8
|
+
}
|
|
9
|
+
function endManualDrain() {
|
|
10
|
+
suppressAutoDrain = false;
|
|
11
|
+
}
|
|
12
|
+
function __register(tag, setup) {
|
|
13
|
+
const prev = registry.get(tag);
|
|
14
|
+
registry.set(tag, setup);
|
|
15
|
+
if (prev !== void 0 && prev !== setup) {
|
|
16
|
+
pendingRemounts.add(tag);
|
|
17
|
+
if (!suppressAutoDrain) scheduleRemount();
|
|
18
|
+
}
|
|
19
|
+
const thunk = (props, host) => {
|
|
20
|
+
const current = registry.get(tag);
|
|
21
|
+
if (!current) throw new Error(`[nisli-hmr] no setup registered for <${tag}>`);
|
|
22
|
+
return current(props, host);
|
|
23
|
+
};
|
|
24
|
+
thunk.__nisliHmr = true;
|
|
25
|
+
return thunk;
|
|
26
|
+
}
|
|
27
|
+
function pendingRemountCount() {
|
|
28
|
+
return pendingRemounts.size;
|
|
29
|
+
}
|
|
30
|
+
function drainRemounts() {
|
|
31
|
+
const tags = [...pendingRemounts];
|
|
32
|
+
pendingRemounts.clear();
|
|
33
|
+
remountScheduled = false;
|
|
34
|
+
for (const tag of tags) remount(tag);
|
|
35
|
+
return tags;
|
|
36
|
+
}
|
|
37
|
+
function scheduleRemount() {
|
|
38
|
+
if (remountScheduled) return;
|
|
39
|
+
remountScheduled = true;
|
|
40
|
+
queueMicrotask(() => {
|
|
41
|
+
if (remountScheduled) drainRemounts();
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function remount(tag) {
|
|
45
|
+
const elements = document.querySelectorAll(tag);
|
|
46
|
+
elements.forEach((node) => {
|
|
47
|
+
const el = node;
|
|
48
|
+
if (el._remount) {
|
|
49
|
+
el._remount();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
el.disconnectedCallback?.();
|
|
53
|
+
el.replaceChildren();
|
|
54
|
+
el.connectedCallback?.();
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function __resetRegistry() {
|
|
58
|
+
registry.clear();
|
|
59
|
+
pendingRemounts.clear();
|
|
60
|
+
remountScheduled = false;
|
|
61
|
+
suppressAutoDrain = false;
|
|
62
|
+
}
|
|
63
|
+
export {
|
|
64
|
+
__register,
|
|
65
|
+
__resetRegistry,
|
|
66
|
+
beginManualDrain,
|
|
67
|
+
drainRemounts,
|
|
68
|
+
endManualDrain,
|
|
69
|
+
pendingRemountCount,
|
|
70
|
+
remount
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/hmr/registry.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * hmr/registry.ts \u2014 Transport-agnostic HMR core for Nisli (ADR 0021, ADR 0110).\n *\n * The tag\u2192setup registry, the stable indirection thunk, and the in-place\n * re-mount through the custom-element lifecycle. This is the part that is\n * IDENTICAL regardless of how change notifications arrive \u2014 change\n * (`../esbuild-hmr/*`) or Vite `import.meta.hot` (`../vite-hmr/*`) both drive\n * the same registry. Keeping it here means one implementation, one set of\n * correctness guarantees (ADR 0008.1 disposal), and no duplication.\n *\n * DEV-ONLY: reachable only via the dev subpaths, never from the `@nisli/core`\n * `.` entry, so it contributes zero production bytes.\n */\n\n// \u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** The author's setup function, kept opaque here (props proxy, host). */\nexport type HmrSetup = (props: unknown, host: HTMLElement) => unknown;\n\ninterface CustomElementLike extends HTMLElement {\n connectedCallback?: () => void;\n disconnectedCallback?: () => void;\n _remount?: () => void;\n}\n\n// \u2500\u2500 Tag \u2192 setup registry (Ruling 2) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst registry = new Map<string, HmrSetup>();\n\n/** Tags whose setup changed since the last drain \u2014 pending a re-mount. */\nconst pendingRemounts = new Set<string>();\nlet remountScheduled = false;\n\n/**\n * When true, `__register` accumulates pending tags but does NOT auto-schedule a\n * microtask drain \u2014 the caller owns the drain so it can wait for a full module\n * re-eval to register ALL changed tags first.\n * The Vite path leaves this false and drains explicitly from its accept hook.\n */\nlet suppressAutoDrain = false;\n\n/**\n * Begin a caller-owned drain window: suppress auto-scheduling AND cancel any\n * pending microtask drain, so the caller can re-evaluate a whole bundle and\n * register ALL changed tags before a single explicit `drainRemounts()`. Used by\n * the Vite path drains per-module.\n */\nexport function beginManualDrain(): void {\n suppressAutoDrain = true;\n remountScheduled = false;\n}\n\n/** End a caller-owned drain window (re-enable auto-scheduling). */\nexport function endManualDrain(): void {\n suppressAutoDrain = false;\n}\n\n/**\n * Register (or update) the setup for a tag and return a STABLE indirection\n * thunk that always reads the *current* setup from the registry.\n *\n * First registration for a tag (initial load) schedules no re-mount. A\n * subsequent registration with a *different* setup reference (a rebuild\n * re-evaluating the module) queues the tag for re-mount.\n *\n * The returned thunk is what `component()` hands to the real\n * `FrameworkComponent`; because it reads `registry.get(tag)` lazily, the live\n * element picks up new setup with no class swap or tag re-definition.\n */\nexport function __register(tag: string, setup: HmrSetup): HmrSetup {\n const prev = registry.get(tag);\n registry.set(tag, setup);\n if (prev !== undefined && prev !== setup) {\n pendingRemounts.add(tag);\n if (!suppressAutoDrain) scheduleRemount();\n }\n const thunk: HmrSetup = (props: unknown, host: HTMLElement) => {\n const current = registry.get(tag);\n if (!current) throw new Error(`[nisli-hmr] no setup registered for <${tag}>`);\n return current(props, host);\n };\n // N201 exemption marker (ADR 0030.2 \u00A73/\u00A78): component() throws a coded dev\n // error on duplicate `define`, EXCEPT when the incoming setup is a registry\n // thunk \u2014 an HMR re-evaluation of the same module re-calling component() is\n // the swap mechanism working, not a second definer. The marker rides the\n // thunk itself so component.ts needs no import from this dev-only module.\n (thunk as HmrSetup & { __nisliHmr?: true }).__nisliHmr = true;\n return thunk;\n}\n\n/** Number of tags awaiting a re-mount (for orchestration/tests). */\nexport function pendingRemountCount(): number {\n return pendingRemounts.size;\n}\n\n/** Drain and re-mount every pending tag. Returns the tags that were remounted. */\nexport function drainRemounts(): string[] {\n const tags = [...pendingRemounts];\n pendingRemounts.clear();\n remountScheduled = false;\n for (const tag of tags) remount(tag);\n return tags;\n}\n\nfunction scheduleRemount(): void {\n if (remountScheduled) return;\n remountScheduled = true;\n // Defer so a full module re-eval can register *all* changed tags first.\n queueMicrotask(() => {\n if (remountScheduled) drainRemounts();\n });\n}\n\n// \u2500\u2500 Re-mount through the existing lifecycle (Ruling 3) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Re-mount every live element of `tag` in place, on the SAME instance:\n *\n * disconnectedCallback() \u2014 resets `_mounted`, disposes effects + template\n * (the ADR 0008.1 disposal contract)\n * replaceChildren() \u2014 clears old DOM, firing nested custom elements'\n * disconnectedCallback so their disposers run too\n * connectedCallback() \u2014 re-runs the NEW setup (via the registry thunk)\n * inside the same `untrack(...)` mount path\n *\n * Props survive because `_propsProxy` is constructor-owned (component.ts), and\n * the element node identity is preserved \u2014 siblings, scroll, route, and global\n * stores are untouched.\n */\nexport function remount(tag: string): void {\n const elements = document.querySelectorAll(tag);\n elements.forEach((node) => {\n const el = node as CustomElementLike;\n if (el._remount) {\n el._remount();\n return;\n }\n // Dispose first: must run BEFORE clearing DOM or effects/subscriptions\n // leak across reloads (ADR 0008.1 hazard).\n el.disconnectedCallback?.();\n // Clear old DOM \u2014 removal fires nested children's disconnectedCallback.\n el.replaceChildren();\n // Re-run setup + re-mount on the same instance.\n el.connectedCallback?.();\n });\n}\n\n/** Test-only: clear the registry and pending state between cases. */\nexport function __resetRegistry(): void {\n registry.clear();\n pendingRemounts.clear();\n remountScheduled = false;\n suppressAutoDrain = false;\n}\n"],
|
|
5
|
+
"mappings": "AA2BA,MAAM,WAAW,oBAAI,IAAsB;AAG3C,MAAM,kBAAkB,oBAAI,IAAY;AACxC,IAAI,mBAAmB;AAQvB,IAAI,oBAAoB;AAQjB,SAAS,mBAAyB;AACvC,sBAAoB;AACpB,qBAAmB;AACrB;AAGO,SAAS,iBAAuB;AACrC,sBAAoB;AACtB;AAcO,SAAS,WAAW,KAAa,OAA2B;AACjE,QAAM,OAAO,SAAS,IAAI,GAAG;AAC7B,WAAS,IAAI,KAAK,KAAK;AACvB,MAAI,SAAS,UAAa,SAAS,OAAO;AACxC,oBAAgB,IAAI,GAAG;AACvB,QAAI,CAAC,kBAAmB,iBAAgB;AAAA,EAC1C;AACA,QAAM,QAAkB,CAAC,OAAgB,SAAsB;AAC7D,UAAM,UAAU,SAAS,IAAI,GAAG;AAChC,QAAI,CAAC,QAAS,OAAM,IAAI,MAAM,wCAAwC,GAAG,GAAG;AAC5E,WAAO,QAAQ,OAAO,IAAI;AAAA,EAC5B;AAMA,EAAC,MAA2C,aAAa;AACzD,SAAO;AACT;AAGO,SAAS,sBAA8B;AAC5C,SAAO,gBAAgB;AACzB;AAGO,SAAS,gBAA0B;AACxC,QAAM,OAAO,CAAC,GAAG,eAAe;AAChC,kBAAgB,MAAM;AACtB,qBAAmB;AACnB,aAAW,OAAO,KAAM,SAAQ,GAAG;AACnC,SAAO;AACT;AAEA,SAAS,kBAAwB;AAC/B,MAAI,iBAAkB;AACtB,qBAAmB;AAEnB,iBAAe,MAAM;AACnB,QAAI,iBAAkB,eAAc;AAAA,EACtC,CAAC;AACH;AAkBO,SAAS,QAAQ,KAAmB;AACzC,QAAM,WAAW,SAAS,iBAAiB,GAAG;AAC9C,WAAS,QAAQ,CAAC,SAAS;AACzB,UAAM,KAAK;AACX,QAAI,GAAG,UAAU;AACf,SAAG,SAAS;AACZ;AAAA,IACF;AAGA,OAAG,uBAAuB;AAE1B,OAAG,gBAAgB;AAEnB,OAAG,oBAAoB;AAAA,EACzB,CAAC;AACH;AAGO,SAAS,kBAAwB;AACtC,WAAS,MAAM;AACf,kBAAgB,MAAM;AACtB,qBAAmB;AACnB,sBAAoB;AACtB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
function escapeRegExp(s) {
|
|
2
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3
|
+
}
|
|
4
|
+
function transformSource(code, coreSpecifier, runtimeSpecifier) {
|
|
5
|
+
const spec = escapeRegExp(coreSpecifier);
|
|
6
|
+
const importRe = new RegExp(`import\\s*\\{([^}]*)\\}\\s*from\\s*['"]${spec}['"]`, "g");
|
|
7
|
+
let importsComponent = false;
|
|
8
|
+
const rewritten = code.replace(importRe, (_full, names) => {
|
|
9
|
+
const parts = names.split(",").map((s) => s.trim()).filter(Boolean);
|
|
10
|
+
const next = parts.map((p) => {
|
|
11
|
+
if (p === "component") {
|
|
12
|
+
importsComponent = true;
|
|
13
|
+
return "component as __nisliRealComponent";
|
|
14
|
+
}
|
|
15
|
+
return p;
|
|
16
|
+
});
|
|
17
|
+
return `import { ${next.join(", ")} } from '${coreSpecifier}'`;
|
|
18
|
+
});
|
|
19
|
+
if (!importsComponent) return code;
|
|
20
|
+
const preamble = `import { __register as __nisliRegister } from '${runtimeSpecifier}';
|
|
21
|
+
function component(tag, setup, opts) {
|
|
22
|
+
return __nisliRealComponent(tag, __nisliRegister(tag, setup), opts);
|
|
23
|
+
}
|
|
24
|
+
`;
|
|
25
|
+
return preamble + rewritten;
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
transformSource
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=transform.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/hmr/transform.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * hmr/transform.ts \u2014 Shared source rewrite for Nisli HMR (ADR 0021, ADR 0110).\n *\n * Wraps the `component` a module imports from `@nisli/core` with the registry\n * indirection so a rebuilt module's `component()` calls re-register their setup\n * and trigger an in-place re-mount. Transport-agnostic and pure \u2014 used by both\n * the Vite `transform` hook.\n */\n\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Rewrite a module's source so the `component` it imports from `coreSpecifier`\n * is wrapped by the registry indirection from `runtimeSpecifier`.\n *\n * Strategy: alias the real import, then shadow `component` with a wrapper that\n * registers the setup and passes a STABLE thunk to the real `component`. Cost:\n * one Map write + one thunk allocation per `component()` call.\n *\n * Returns the original source unchanged if it does not import `component` from\n * the core specifier (nothing to wrap) \u2014 the caller uses identity to decide\n * whether the module is HMR-relevant.\n */\nexport function transformSource(code: string, coreSpecifier: string, runtimeSpecifier: string): string {\n const spec = escapeRegExp(coreSpecifier);\n // Match a named import of `component` (alone or among others) from core.\n const importRe = new RegExp(`import\\\\s*\\\\{([^}]*)\\\\}\\\\s*from\\\\s*['\"]${spec}['\"]`, 'g');\n\n let importsComponent = false;\n const rewritten = code.replace(importRe, (_full: string, names: string) => {\n const parts = names.split(',').map((s) => s.trim()).filter(Boolean);\n const next = parts.map((p) => {\n // Handle `component` and `component as X` \u2014 we only wrap the bare name.\n if (p === 'component') {\n importsComponent = true;\n return 'component as __nisliRealComponent';\n }\n return p;\n });\n return `import { ${next.join(', ')} } from '${coreSpecifier}'`;\n });\n\n if (!importsComponent) return code;\n\n // Prepend the registry-import + wrapper. `__register` returns the STABLE\n // thunk the real component captures; the wrapper preserves the factory's\n // return value and signature.\n const preamble =\n `import { __register as __nisliRegister } from '${runtimeSpecifier}';\\n` +\n `function component(tag, setup, opts) {\\n` +\n ` return __nisliRealComponent(tag, __nisliRegister(tag, setup), opts);\\n` +\n `}\\n`;\n\n return preamble + rewritten;\n}\n"],
|
|
5
|
+
"mappings": "AASA,SAAS,aAAa,GAAmB;AACvC,SAAO,EAAE,QAAQ,uBAAuB,MAAM;AAChD;AAcO,SAAS,gBAAgB,MAAc,eAAuB,kBAAkC;AACrG,QAAM,OAAO,aAAa,aAAa;AAEvC,QAAM,WAAW,IAAI,OAAO,0CAA0C,IAAI,QAAQ,GAAG;AAErF,MAAI,mBAAmB;AACvB,QAAM,YAAY,KAAK,QAAQ,UAAU,CAAC,OAAe,UAAkB;AACzE,UAAM,QAAQ,MAAM,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO;AAClE,UAAM,OAAO,MAAM,IAAI,CAAC,MAAM;AAE5B,UAAI,MAAM,aAAa;AACrB,2BAAmB;AACnB,eAAO;AAAA,MACT;AACA,aAAO;AAAA,IACT,CAAC;AACD,WAAO,YAAY,KAAK,KAAK,IAAI,CAAC,YAAY,aAAa;AAAA,EAC7D,CAAC;AAED,MAAI,CAAC,iBAAkB,QAAO;AAK9B,QAAM,WACJ,kDAAkD,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAKpE,SAAO,WAAW;AACpB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import {
|
|
2
|
+
signal,
|
|
3
|
+
computed,
|
|
4
|
+
effect,
|
|
5
|
+
flush,
|
|
6
|
+
tick,
|
|
7
|
+
isSignal,
|
|
8
|
+
flushEffects,
|
|
9
|
+
untrack,
|
|
10
|
+
SIGNAL_BRAND
|
|
11
|
+
} from "./signal.js";
|
|
12
|
+
import {
|
|
13
|
+
runWithContext,
|
|
14
|
+
getCurrentComponent,
|
|
15
|
+
hasContext
|
|
16
|
+
} from "./context.js";
|
|
17
|
+
import { Emitter } from "./emitter.js";
|
|
18
|
+
import {
|
|
19
|
+
inject,
|
|
20
|
+
provide,
|
|
21
|
+
createToken,
|
|
22
|
+
resetInjector
|
|
23
|
+
} from "./injector.js";
|
|
24
|
+
import {
|
|
25
|
+
createContext
|
|
26
|
+
} from "./element-context.js";
|
|
27
|
+
import {
|
|
28
|
+
component
|
|
29
|
+
} from "./component.js";
|
|
30
|
+
import {
|
|
31
|
+
html,
|
|
32
|
+
el,
|
|
33
|
+
when,
|
|
34
|
+
each,
|
|
35
|
+
raw,
|
|
36
|
+
sanitized,
|
|
37
|
+
setSanitizerFallback
|
|
38
|
+
} from "./template.js";
|
|
39
|
+
import {
|
|
40
|
+
ref,
|
|
41
|
+
isRef
|
|
42
|
+
} from "./ref.js";
|
|
43
|
+
import {
|
|
44
|
+
onMount,
|
|
45
|
+
onCleanup,
|
|
46
|
+
useHostEvent
|
|
47
|
+
} from "./lifecycle.js";
|
|
48
|
+
import {
|
|
49
|
+
children
|
|
50
|
+
} from "./projection.js";
|
|
51
|
+
import {
|
|
52
|
+
query,
|
|
53
|
+
QueryClient
|
|
54
|
+
} from "./query.js";
|
|
55
|
+
import {
|
|
56
|
+
resource
|
|
57
|
+
} from "./resource.js";
|
|
58
|
+
import { settle } from "./settle.js";
|
|
59
|
+
import {
|
|
60
|
+
viewTransition
|
|
61
|
+
} from "./view-transition.js";
|
|
62
|
+
import { setDevMode } from "./diagnostics.js";
|
|
63
|
+
export {
|
|
64
|
+
Emitter,
|
|
65
|
+
QueryClient,
|
|
66
|
+
SIGNAL_BRAND,
|
|
67
|
+
children,
|
|
68
|
+
component,
|
|
69
|
+
computed,
|
|
70
|
+
createContext,
|
|
71
|
+
createToken,
|
|
72
|
+
each,
|
|
73
|
+
effect,
|
|
74
|
+
el,
|
|
75
|
+
flush,
|
|
76
|
+
flushEffects,
|
|
77
|
+
getCurrentComponent,
|
|
78
|
+
hasContext,
|
|
79
|
+
html,
|
|
80
|
+
inject,
|
|
81
|
+
isRef,
|
|
82
|
+
isSignal,
|
|
83
|
+
onCleanup,
|
|
84
|
+
onMount,
|
|
85
|
+
provide,
|
|
86
|
+
query,
|
|
87
|
+
raw,
|
|
88
|
+
ref,
|
|
89
|
+
resetInjector,
|
|
90
|
+
resource,
|
|
91
|
+
runWithContext,
|
|
92
|
+
sanitized,
|
|
93
|
+
setDevMode,
|
|
94
|
+
setSanitizerFallback,
|
|
95
|
+
settle,
|
|
96
|
+
signal,
|
|
97
|
+
tick,
|
|
98
|
+
untrack,
|
|
99
|
+
useHostEvent,
|
|
100
|
+
viewTransition,
|
|
101
|
+
when
|
|
102
|
+
};
|
|
103
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/index.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * packages/framework/ \u2014 Public API barrel export\n *\n * This is the single import point for all framework primitives.\n *\n * ```ts\n * import {\n * signal, computed, effect, flush,\n * component, html, when,\n * inject, provide,\n * Emitter,\n * query, QueryClient,\n * } from './framework';\n * ```\n */\n\n// \u2500\u2500 Reactivity \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport {\n signal,\n computed,\n effect,\n flush,\n tick,\n isSignal,\n flushEffects,\n untrack,\n SIGNAL_BRAND,\n type Signal,\n type ReadonlySignal,\n type Disposer,\n} from './signal.js';\n\n// \u2500\u2500 Setup context \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport {\n runWithContext,\n getCurrentComponent,\n hasContext,\n type ComponentHost,\n} from './context.js';\n\n// \u2500\u2500 Typed event emitters \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport { Emitter } from './emitter.js';\n\n// \u2500\u2500 Dependency injection (app-global) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport {\n inject,\n provide,\n createToken,\n resetInjector,\n type Constructor,\n type InjectionToken,\n} from './injector.js';\n\n// \u2500\u2500 Subtree-scoped context (DOM provide/inject) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport {\n createContext,\n type Context,\n type Inject,\n} from './element-context.js';\n\n// \u2500\u2500 Component model \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport {\n component,\n type ReactiveProps,\n type AttrValueType,\n type SetupFunction,\n type ComponentFactory,\n type ComponentOptions,\n type AttrDecl,\n type ComponentAttrs,\n type PropInput,\n type HostAttrs,\n} from './component.js';\n\n// \u2500\u2500 Template engine \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport {\n html,\n el,\n when,\n each,\n raw,\n sanitized,\n setSanitizerFallback,\n type RawHtml,\n type SanitizedHtml,\n type SanitizerFallback,\n type TemplateResult,\n type TypedEventHandler,\n type ElProps,\n type ElChild,\n} from './template.js';\n\n// \u2500\u2500 Element refs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport {\n ref,\n isRef,\n type Ref,\n} from './ref.js';\n\n// \u2500\u2500 Lifecycle hooks \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport {\n onMount,\n onCleanup,\n useHostEvent,\n} from './lifecycle.js';\n\n// \u2500\u2500 Content projection (ADR 0025 item 1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport {\n children,\n} from './projection.js';\n\n// \u2500\u2500 Declarative data loading (keyed logical-request store) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport {\n query,\n QueryClient,\n type QueryResult,\n type QueryOptions,\n type QueryKey,\n type QueryKeyElement,\n type QueryStatus,\n type QueryFetcher,\n} from './query.js';\n\n// \u2500\u2500 Local async derivations \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport {\n resource,\n type ResourceResult,\n} from './resource.js';\n\n// \u2500\u2500 Async quiescence (test/verify + SSG pre-snapshot barrier) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport { settle } from './settle.js';\n\n// \u2500\u2500 View Transitions (opt-in wrapper over the synchronous flush) \u2500\u2500\u2500\u2500\nexport {\n viewTransition,\n type ViewTransitionOptions,\n} from './view-transition.js';\n\n// \u2500\u2500 Runtime control of available development diagnostics \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport { setDevMode } from './diagnostics.js';\n"],
|
|
5
|
+
"mappings": "AAiBA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAGP,SAAS,eAAe;AAGxB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAGP;AAAA,EACE;AAAA,OAGK;AAGP;AAAA,EACE;AAAA,OAUK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAQK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGP;AAAA,EACE;AAAA,OACK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,OAOK;AAGP;AAAA,EACE;AAAA,OAEK;AAGP,SAAS,cAAc;AAGvB;AAAA,EACE;AAAA,OAEK;AAGP,SAAS,kBAAkB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { hasContext, getCurrentComponent } from "./context.js";
|
|
2
|
+
import { formatDiag, isDev } from "./diagnostics.js";
|
|
3
|
+
const singletonCache = /* @__PURE__ */ new Map();
|
|
4
|
+
const factoryOverrides = /* @__PURE__ */ new Map();
|
|
5
|
+
const instantiating = /* @__PURE__ */ new Set();
|
|
6
|
+
function inject(token) {
|
|
7
|
+
if (singletonCache.has(token)) {
|
|
8
|
+
return singletonCache.get(token);
|
|
9
|
+
}
|
|
10
|
+
if (instantiating.has(token)) {
|
|
11
|
+
const name = typeof token === "function" ? token.name : token.name;
|
|
12
|
+
throw new Error(
|
|
13
|
+
`Circular dependency detected when instantiating '${name}'. Check your service constructors for circular inject() calls.`
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
instantiating.add(token);
|
|
17
|
+
try {
|
|
18
|
+
let instance;
|
|
19
|
+
const factory = factoryOverrides.get(token);
|
|
20
|
+
if (factory) {
|
|
21
|
+
instance = factory();
|
|
22
|
+
} else if (typeof token === "function") {
|
|
23
|
+
instance = new token();
|
|
24
|
+
} else {
|
|
25
|
+
const injToken = token;
|
|
26
|
+
if (injToken._factory) {
|
|
27
|
+
instance = injToken._factory();
|
|
28
|
+
} else {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`No provider found for token '${injToken.name}'. Use provide() to register a factory before calling inject().`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
singletonCache.set(token, instance);
|
|
35
|
+
return instance;
|
|
36
|
+
} finally {
|
|
37
|
+
instantiating.delete(token);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function provide(token, factory) {
|
|
41
|
+
if (process.env.NODE_ENV !== "production" && isDev() && singletonCache.has(token)) {
|
|
42
|
+
const name = typeof token === "function" ? token.name : token.name;
|
|
43
|
+
throw new Error(formatDiag(
|
|
44
|
+
"N501",
|
|
45
|
+
`provide(${name}) after '${name}' was already instantiated \u2014 existing consumers keep the old instance while new inject() calls would get the new one (mixed-instance split). Call resetInjector() first (tests), or provide() before the first inject().`
|
|
46
|
+
));
|
|
47
|
+
}
|
|
48
|
+
factoryOverrides.set(token, factory);
|
|
49
|
+
singletonCache.delete(token);
|
|
50
|
+
}
|
|
51
|
+
function createToken(name, defaultFactory) {
|
|
52
|
+
return {
|
|
53
|
+
__brand: "InjectionToken",
|
|
54
|
+
name,
|
|
55
|
+
_factory: defaultFactory
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const resetHooks = /* @__PURE__ */ new Set();
|
|
59
|
+
function registerResetHook(hook) {
|
|
60
|
+
resetHooks.add(hook);
|
|
61
|
+
}
|
|
62
|
+
function resetInjector() {
|
|
63
|
+
singletonCache.clear();
|
|
64
|
+
factoryOverrides.clear();
|
|
65
|
+
instantiating.clear();
|
|
66
|
+
for (const hook of resetHooks) hook();
|
|
67
|
+
}
|
|
68
|
+
export {
|
|
69
|
+
createToken,
|
|
70
|
+
inject,
|
|
71
|
+
provide,
|
|
72
|
+
registerResetHook,
|
|
73
|
+
resetInjector
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=injector.js.map
|