@nisli/core 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +100 -0
- package/dist/component.d.ts +75 -0
- package/dist/component.js +260 -0
- package/dist/component.js.map +1 -0
- package/dist/context.d.ts +43 -0
- package/dist/context.js +55 -0
- package/dist/context.js.map +1 -0
- package/dist/emitter.d.ts +51 -0
- package/dist/emitter.js +90 -0
- package/dist/emitter.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/injector.d.ts +65 -0
- package/dist/injector.js +100 -0
- package/dist/injector.js.map +1 -0
- package/dist/lifecycle.d.ts +57 -0
- package/dist/lifecycle.js +107 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/query.d.ts +81 -0
- package/dist/query.js +231 -0
- package/dist/query.js.map +1 -0
- package/dist/ref.d.ts +36 -0
- package/dist/ref.js +43 -0
- package/dist/ref.js.map +1 -0
- package/dist/signal.d.ts +93 -0
- package/dist/signal.js +403 -0
- package/dist/signal.js.map +1 -0
- package/dist/template.d.ts +49 -0
- package/dist/template.js +753 -0
- package/dist/template.js.map +1 -0
- package/package.json +42 -0
package/dist/emitter.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* emitter.ts — Typed pub/sub base class for services.
|
|
3
|
+
*
|
|
4
|
+
* Replaces `document.dispatchEvent(new CustomEvent(...))` with typed,
|
|
5
|
+
* scoped event emitters. Services extend Emitter<T> where T defines
|
|
6
|
+
* the event contract as a record of event names → payload types.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* class NavigationEvents extends Emitter<{
|
|
10
|
+
* select: { id: string };
|
|
11
|
+
* filter: { filter: string };
|
|
12
|
+
* }> {}
|
|
13
|
+
*
|
|
14
|
+
* const nav = inject(NavigationEvents);
|
|
15
|
+
* nav.emit('select', { id: 'TASK-1' }); // typed
|
|
16
|
+
* nav.on('select', ({ id }) => console.log(id)); // typed
|
|
17
|
+
* const selectedId = nav.toSignal('select', e => e.id, null);
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
import { signal } from './signal.js';
|
|
21
|
+
import { hasContext, getCurrentComponent } from './context.js';
|
|
22
|
+
// ── Emitter base class ──────────────────────────────────────────────
|
|
23
|
+
export class Emitter {
|
|
24
|
+
listeners = new Map();
|
|
25
|
+
/**
|
|
26
|
+
* Emit a typed event. All subscribers for this event name are called
|
|
27
|
+
* synchronously. If a subscriber throws, other subscribers still fire.
|
|
28
|
+
*/
|
|
29
|
+
emit(event, payload) {
|
|
30
|
+
const set = this.listeners.get(event);
|
|
31
|
+
if (!set)
|
|
32
|
+
return;
|
|
33
|
+
// Iterate a copy to allow unsubscribe during callback
|
|
34
|
+
for (const fn of [...set]) {
|
|
35
|
+
try {
|
|
36
|
+
fn(payload);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
console.error(`Emitter: subscriber for '${String(event)}' threw:`, err);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Subscribe to a typed event. Returns an unsubscribe function.
|
|
45
|
+
*
|
|
46
|
+
* If called during component setup, the subscription is automatically
|
|
47
|
+
* disposed on disconnectedCallback.
|
|
48
|
+
*/
|
|
49
|
+
on(event, fn) {
|
|
50
|
+
let set = this.listeners.get(event);
|
|
51
|
+
if (!set) {
|
|
52
|
+
set = new Set();
|
|
53
|
+
this.listeners.set(event, set);
|
|
54
|
+
}
|
|
55
|
+
set.add(fn);
|
|
56
|
+
const unsubscribe = () => {
|
|
57
|
+
set.delete(fn);
|
|
58
|
+
if (set.size === 0) {
|
|
59
|
+
this.listeners.delete(event);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
// Auto-dispose if inside component setup context
|
|
63
|
+
if (hasContext()) {
|
|
64
|
+
getCurrentComponent().addDisposer(unsubscribe);
|
|
65
|
+
}
|
|
66
|
+
return unsubscribe;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Bridge an event into the signal system.
|
|
70
|
+
* Returns a signal that updates whenever the event fires.
|
|
71
|
+
*
|
|
72
|
+
* @param event - Event name to subscribe to
|
|
73
|
+
* @param selector - Transform the payload into the signal value
|
|
74
|
+
* @param initial - Initial signal value before any event fires
|
|
75
|
+
*/
|
|
76
|
+
toSignal(event, selector, initial) {
|
|
77
|
+
const s = signal(initial);
|
|
78
|
+
this.on(event, (payload) => {
|
|
79
|
+
s.value = selector(payload);
|
|
80
|
+
});
|
|
81
|
+
return s;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Remove all subscribers. Useful for testing cleanup.
|
|
85
|
+
*/
|
|
86
|
+
clear() {
|
|
87
|
+
this.listeners.clear();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=emitter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emitter.js","sourceRoot":"","sources":["../src/emitter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,MAAM,EAAe,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAO/D,uEAAuE;AAEvE,MAAM,OAAO,OAAO;IACV,SAAS,GAAG,IAAI,GAAG,EAAoC,CAAC;IAEhE;;;OAGG;IACH,IAAI,CAAyB,KAAQ,EAAE,OAAkB;QACvD,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACtC,IAAI,CAAC,GAAG;YAAE,OAAO;QAEjB,sDAAsD;QACtD,KAAK,MAAM,EAAE,IAAI,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC;YAC1B,IAAI,CAAC;gBACH,EAAE,CAAC,OAAO,CAAC,CAAC;YACd,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CAAC,4BAA4B,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;YAC1E,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,EAAE,CAAyB,KAAQ,EAAE,EAAuB;QAC1D,IAAI,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACpC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC;YAChB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACjC,CAAC;QACD,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAEZ,MAAM,WAAW,GAAG,GAAG,EAAE;YACvB,GAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAChB,IAAI,GAAI,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;gBACpB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC/B,CAAC;QACH,CAAC,CAAC;QAEF,iDAAiD;QACjD,IAAI,UAAU,EAAE,EAAE,CAAC;YACjB,mBAAmB,EAAE,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;QACjD,CAAC;QAED,OAAO,WAAW,CAAC;IACrB,CAAC;IAED;;;;;;;OAOG;IACH,QAAQ,CACN,KAAQ,EACR,QAAmC,EACnC,OAAU;QAEV,MAAM,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;QAC1B,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,EAAE;YACzB,CAAC,CAAC,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,CAAC;IACX,CAAC;IAED;;OAEG;IACH,KAAK;QACH,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;IACzB,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* packages/framework/ — Public API barrel export
|
|
3
|
+
*
|
|
4
|
+
* This is the single import point for all framework primitives.
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* import {
|
|
8
|
+
* signal, computed, effect, flush,
|
|
9
|
+
* component, html, when,
|
|
10
|
+
* inject, provide,
|
|
11
|
+
* Emitter,
|
|
12
|
+
* query, QueryClient,
|
|
13
|
+
* } from './framework';
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export { signal, computed, effect, flush, isSignal, flushEffects, untrack, SIGNAL_BRAND, type Signal, type ReadonlySignal, } from './signal.js';
|
|
17
|
+
export { runWithContext, getCurrentComponent, hasContext, type ComponentHost, } from './context.js';
|
|
18
|
+
export { Emitter } from './emitter.js';
|
|
19
|
+
export { inject, provide, createToken, resetInjector, type Constructor, type InjectionToken, } from './injector.js';
|
|
20
|
+
export { component, type ReactiveProps, type SetupFunction, type ComponentFactory, type ComponentOptions, type PropInput, type HostAttrs, } from './component.js';
|
|
21
|
+
export { html, when, each, type TemplateResult, } from './template.js';
|
|
22
|
+
export { ref, isRef, type Ref, } from './ref.js';
|
|
23
|
+
export { onMount, onCleanup, useHostEvent, } from './lifecycle.js';
|
|
24
|
+
export { query, QueryClient, type QueryResult, type QueryOptions, } from './query.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* packages/framework/ — Public API barrel export
|
|
3
|
+
*
|
|
4
|
+
* This is the single import point for all framework primitives.
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* import {
|
|
8
|
+
* signal, computed, effect, flush,
|
|
9
|
+
* component, html, when,
|
|
10
|
+
* inject, provide,
|
|
11
|
+
* Emitter,
|
|
12
|
+
* query, QueryClient,
|
|
13
|
+
* } from './framework';
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
// ── Reactivity ──────────────────────────────────────────────────────
|
|
17
|
+
export { signal, computed, effect, flush, isSignal, flushEffects, untrack, SIGNAL_BRAND, } from './signal.js';
|
|
18
|
+
// ── Setup context ───────────────────────────────────────────────────
|
|
19
|
+
export { runWithContext, getCurrentComponent, hasContext, } from './context.js';
|
|
20
|
+
// ── Typed event emitters ────────────────────────────────────────────
|
|
21
|
+
export { Emitter } from './emitter.js';
|
|
22
|
+
// ── Dependency injection ────────────────────────────────────────────
|
|
23
|
+
export { inject, provide, createToken, resetInjector, } from './injector.js';
|
|
24
|
+
// ── Component model ─────────────────────────────────────────────────
|
|
25
|
+
export { component, } from './component.js';
|
|
26
|
+
// ── Template engine ─────────────────────────────────────────────────
|
|
27
|
+
export { html, when, each, } from './template.js';
|
|
28
|
+
// ── Element refs ────────────────────────────────────────────────────
|
|
29
|
+
export { ref, isRef, } from './ref.js';
|
|
30
|
+
// ── Lifecycle hooks ─────────────────────────────────────────────────
|
|
31
|
+
export { onMount, onCleanup, useHostEvent, } from './lifecycle.js';
|
|
32
|
+
// ── Declarative data loading ────────────────────────────────────────
|
|
33
|
+
export { query, QueryClient, } from './query.js';
|
|
34
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,uEAAuE;AACvE,OAAO,EACL,MAAM,EACN,QAAQ,EACR,MAAM,EACN,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,OAAO,EACP,YAAY,GAGb,MAAM,aAAa,CAAC;AAErB,uEAAuE;AACvE,OAAO,EACL,cAAc,EACd,mBAAmB,EACnB,UAAU,GAEX,MAAM,cAAc,CAAC;AAEtB,uEAAuE;AACvE,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,uEAAuE;AACvE,OAAO,EACL,MAAM,EACN,OAAO,EACP,WAAW,EACX,aAAa,GAGd,MAAM,eAAe,CAAC;AAEvB,uEAAuE;AACvE,OAAO,EACL,SAAS,GAOV,MAAM,gBAAgB,CAAC;AAExB,uEAAuE;AACvE,OAAO,EACL,IAAI,EACJ,IAAI,EACJ,IAAI,GAEL,MAAM,eAAe,CAAC;AAEvB,uEAAuE;AACvE,OAAO,EACL,GAAG,EACH,KAAK,GAEN,MAAM,UAAU,CAAC;AAElB,uEAAuE;AACvE,OAAO,EACL,OAAO,EACP,SAAS,EACT,YAAY,GACb,MAAM,gBAAgB,CAAC;AAExB,uEAAuE;AACvE,OAAO,EACL,KAAK,EACL,WAAW,GAGZ,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* injector.ts — Dependency injection: inject(), provide(), class-as-token
|
|
3
|
+
*
|
|
4
|
+
* Two key insights from Angular's evolution:
|
|
5
|
+
* 1. The class IS the token — no createToken() needed for 99% of cases
|
|
6
|
+
* 2. provide() is optional — inject() auto-creates singletons on first call
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* class BacklogAPI {
|
|
10
|
+
* async getTasks(): Promise<Task[]> { ... }
|
|
11
|
+
* }
|
|
12
|
+
*
|
|
13
|
+
* // In component setup:
|
|
14
|
+
* const api = inject(BacklogAPI); // auto-created singleton
|
|
15
|
+
*
|
|
16
|
+
* // In tests:
|
|
17
|
+
* provide(BacklogAPI, () => new MockAPI());
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
/** Any class constructor — used as both the token and the type. */
|
|
21
|
+
export type Constructor<T = unknown> = new (...args: any[]) => T;
|
|
22
|
+
/** Opaque token for non-class dependencies (config objects, primitives). */
|
|
23
|
+
export interface InjectionToken<T> {
|
|
24
|
+
readonly __brand: 'InjectionToken';
|
|
25
|
+
readonly name: string;
|
|
26
|
+
/** @internal default factory */
|
|
27
|
+
_factory?: () => T;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get or create a singleton instance of the given class.
|
|
31
|
+
*
|
|
32
|
+
* On first call: creates the instance via `new Class()` (or the
|
|
33
|
+
* factory from provide(), if one exists). On subsequent calls:
|
|
34
|
+
* returns the same instance.
|
|
35
|
+
*
|
|
36
|
+
* Can be called inside or outside component setup context.
|
|
37
|
+
* When inside, the inject result is available for the component's lifetime.
|
|
38
|
+
*/
|
|
39
|
+
export declare function inject<T>(token: Constructor<T>): T;
|
|
40
|
+
export declare function inject<T>(token: InjectionToken<T>): T;
|
|
41
|
+
/**
|
|
42
|
+
* Register a factory override for a class or token.
|
|
43
|
+
* The factory will be called on the next inject() if no cached instance exists.
|
|
44
|
+
*
|
|
45
|
+
* Call provide() before the first inject() of that token.
|
|
46
|
+
* To override in tests, call resetInjector() first.
|
|
47
|
+
*/
|
|
48
|
+
export declare function provide<T>(token: Constructor<T>, factory: () => T): void;
|
|
49
|
+
export declare function provide<T>(token: InjectionToken<T>, factory: () => T): void;
|
|
50
|
+
/**
|
|
51
|
+
* Create an injection token for non-class dependencies.
|
|
52
|
+
* Use this for config objects, primitives, or interfaces.
|
|
53
|
+
*
|
|
54
|
+
* ```ts
|
|
55
|
+
* const AppConfig = createToken<{ apiUrl: string }>('AppConfig');
|
|
56
|
+
* provide(AppConfig, () => ({ apiUrl: '/api' }));
|
|
57
|
+
* const config = inject(AppConfig);
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export declare function createToken<T>(name: string, defaultFactory?: () => T): InjectionToken<T>;
|
|
61
|
+
/**
|
|
62
|
+
* Reset the injector — clears all singletons and factory overrides.
|
|
63
|
+
* Used in tests to ensure isolation between test cases.
|
|
64
|
+
*/
|
|
65
|
+
export declare function resetInjector(): void;
|
package/dist/injector.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* injector.ts — Dependency injection: inject(), provide(), class-as-token
|
|
3
|
+
*
|
|
4
|
+
* Two key insights from Angular's evolution:
|
|
5
|
+
* 1. The class IS the token — no createToken() needed for 99% of cases
|
|
6
|
+
* 2. provide() is optional — inject() auto-creates singletons on first call
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* class BacklogAPI {
|
|
10
|
+
* async getTasks(): Promise<Task[]> { ... }
|
|
11
|
+
* }
|
|
12
|
+
*
|
|
13
|
+
* // In component setup:
|
|
14
|
+
* const api = inject(BacklogAPI); // auto-created singleton
|
|
15
|
+
*
|
|
16
|
+
* // In tests:
|
|
17
|
+
* provide(BacklogAPI, () => new MockAPI());
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
import { hasContext, getCurrentComponent } from './context.js';
|
|
21
|
+
// ── Global singleton registry ───────────────────────────────────────
|
|
22
|
+
/** Stores singleton instances keyed by constructor or token. */
|
|
23
|
+
const singletonCache = new Map();
|
|
24
|
+
/** Stores factory overrides from provide(). */
|
|
25
|
+
const factoryOverrides = new Map();
|
|
26
|
+
/** Tracks which constructors are currently being instantiated (circular detection). */
|
|
27
|
+
const instantiating = new Set();
|
|
28
|
+
export function inject(token) {
|
|
29
|
+
// Check cache first
|
|
30
|
+
if (singletonCache.has(token)) {
|
|
31
|
+
return singletonCache.get(token);
|
|
32
|
+
}
|
|
33
|
+
// Circular dependency detection
|
|
34
|
+
if (instantiating.has(token)) {
|
|
35
|
+
const name = typeof token === 'function' ? token.name : token.name;
|
|
36
|
+
throw new Error(`Circular dependency detected when instantiating '${name}'. ` +
|
|
37
|
+
'Check your service constructors for circular inject() calls.');
|
|
38
|
+
}
|
|
39
|
+
instantiating.add(token);
|
|
40
|
+
try {
|
|
41
|
+
let instance;
|
|
42
|
+
// Check for factory override
|
|
43
|
+
const factory = factoryOverrides.get(token);
|
|
44
|
+
if (factory) {
|
|
45
|
+
instance = factory();
|
|
46
|
+
}
|
|
47
|
+
else if (typeof token === 'function') {
|
|
48
|
+
// Class token — auto-create via constructor
|
|
49
|
+
instance = new token();
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
// InjectionToken with default factory
|
|
53
|
+
const injToken = token;
|
|
54
|
+
if (injToken._factory) {
|
|
55
|
+
instance = injToken._factory();
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
throw new Error(`No provider found for token '${injToken.name}'. ` +
|
|
59
|
+
'Use provide() to register a factory before calling inject().');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
singletonCache.set(token, instance);
|
|
63
|
+
return instance;
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
instantiating.delete(token);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export function provide(token, factory) {
|
|
70
|
+
factoryOverrides.set(token, factory);
|
|
71
|
+
// Remove cached instance so next inject() uses the new factory
|
|
72
|
+
singletonCache.delete(token);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Create an injection token for non-class dependencies.
|
|
76
|
+
* Use this for config objects, primitives, or interfaces.
|
|
77
|
+
*
|
|
78
|
+
* ```ts
|
|
79
|
+
* const AppConfig = createToken<{ apiUrl: string }>('AppConfig');
|
|
80
|
+
* provide(AppConfig, () => ({ apiUrl: '/api' }));
|
|
81
|
+
* const config = inject(AppConfig);
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export function createToken(name, defaultFactory) {
|
|
85
|
+
return {
|
|
86
|
+
__brand: 'InjectionToken',
|
|
87
|
+
name,
|
|
88
|
+
_factory: defaultFactory,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Reset the injector — clears all singletons and factory overrides.
|
|
93
|
+
* Used in tests to ensure isolation between test cases.
|
|
94
|
+
*/
|
|
95
|
+
export function resetInjector() {
|
|
96
|
+
singletonCache.clear();
|
|
97
|
+
factoryOverrides.clear();
|
|
98
|
+
instantiating.clear();
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=injector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"injector.js","sourceRoot":"","sources":["../src/injector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAe/D,uEAAuE;AAEvE,gEAAgE;AAChE,MAAM,cAAc,GAAG,IAAI,GAAG,EAAkD,CAAC;AAEjF,+CAA+C;AAC/C,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAwD,CAAC;AAEzF,uFAAuF;AACvF,MAAM,aAAa,GAAG,IAAI,GAAG,EAAyC,CAAC;AAgBvE,MAAM,UAAU,MAAM,CAAI,KAAyC;IACjE,oBAAoB;IACpB,IAAI,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,cAAc,CAAC,GAAG,CAAC,KAAK,CAAM,CAAC;IACxC,CAAC;IAED,gCAAgC;IAChC,IAAI,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAE,KAA2B,CAAC,IAAI,CAAC;QAC1F,MAAM,IAAI,KAAK,CACb,oDAAoD,IAAI,KAAK;YAC7D,8DAA8D,CAC/D,CAAC;IACJ,CAAC;IAED,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACzB,IAAI,CAAC;QACH,IAAI,QAAW,CAAC;QAEhB,6BAA6B;QAC7B,MAAM,OAAO,GAAG,gBAAgB,CAAC,GAAG,CAAC,KAA8C,CAAC,CAAC;QACrF,IAAI,OAAO,EAAE,CAAC;YACZ,QAAQ,GAAG,OAAO,EAAO,CAAC;QAC5B,CAAC;aAAM,IAAI,OAAO,KAAK,KAAK,UAAU,EAAE,CAAC;YACvC,4CAA4C;YAC5C,QAAQ,GAAG,IAAK,KAAwB,EAAE,CAAC;QAC7C,CAAC;aAAM,CAAC;YACN,sCAAsC;YACtC,MAAM,QAAQ,GAAG,KAA0B,CAAC;YAC5C,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;gBACtB,QAAQ,GAAG,QAAQ,CAAC,QAAQ,EAAE,CAAC;YACjC,CAAC;iBAAM,CAAC;gBACN,MAAM,IAAI,KAAK,CACb,gCAAgC,QAAQ,CAAC,IAAI,KAAK;oBAClD,8DAA8D,CAC/D,CAAC;YACJ,CAAC;QACH,CAAC;QAED,cAAc,CAAC,GAAG,CAAC,KAA8C,EAAE,QAAQ,CAAC,CAAC;QAC7E,OAAO,QAAQ,CAAC;IAClB,CAAC;YAAS,CAAC;QACT,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;AACH,CAAC;AAWD,MAAM,UAAU,OAAO,CAAI,KAAyC,EAAE,OAAgB;IACpF,gBAAgB,CAAC,GAAG,CAAC,KAA8C,EAAE,OAAO,CAAC,CAAC;IAC9E,+DAA+D;IAC/D,cAAc,CAAC,MAAM,CAAC,KAA8C,CAAC,CAAC;AACxE,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW,CAAI,IAAY,EAAE,cAAwB;IACnE,OAAO;QACL,OAAO,EAAE,gBAAyB;QAClC,IAAI;QACJ,QAAQ,EAAE,cAAc;KACzB,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa;IAC3B,cAAc,CAAC,KAAK,EAAE,CAAC;IACvB,gBAAgB,CAAC,KAAK,EAAE,CAAC;IACzB,aAAa,CAAC,KAAK,EAAE,CAAC;AACxB,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lifecycle.ts — Post-mount and cleanup lifecycle hooks
|
|
3
|
+
*
|
|
4
|
+
* `onMount()` registers a callback that runs after the component's
|
|
5
|
+
* template is mounted to the DOM. If the callback returns a function,
|
|
6
|
+
* that function runs on disconnect (cleanup).
|
|
7
|
+
*
|
|
8
|
+
* `onCleanup()` registers a standalone cleanup callback that runs
|
|
9
|
+
* on disconnect — for cases where you need cleanup without mount logic.
|
|
10
|
+
*
|
|
11
|
+
* Both must be called synchronously during component setup.
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* component('my-el', (props, host) => {
|
|
15
|
+
* onMount(() => {
|
|
16
|
+
* host.querySelector('input')?.focus();
|
|
17
|
+
* return () => console.log('unmounted');
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* onCleanup(() => clearInterval(timerId));
|
|
21
|
+
*
|
|
22
|
+
* return html`<input />`;
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Register a callback to run after the component's template is mounted.
|
|
28
|
+
* If the callback returns a function, it will be called on disconnect.
|
|
29
|
+
*
|
|
30
|
+
* Must be called synchronously during component setup.
|
|
31
|
+
*/
|
|
32
|
+
export declare function onMount(callback: () => void | (() => void)): void;
|
|
33
|
+
/**
|
|
34
|
+
* Register a cleanup callback to run on disconnect.
|
|
35
|
+
* This is a convenience for cases where you need cleanup without mount logic.
|
|
36
|
+
*
|
|
37
|
+
* Must be called synchronously during component setup.
|
|
38
|
+
*/
|
|
39
|
+
export declare function onCleanup(callback: () => void): void;
|
|
40
|
+
/**
|
|
41
|
+
* Listen for a DOM event on a target and auto-dispose the listener when the
|
|
42
|
+
* component disconnects. Works with any EventTarget (host element, document,
|
|
43
|
+
* window, etc.). Must be called during component setup.
|
|
44
|
+
*
|
|
45
|
+
* ```ts
|
|
46
|
+
* useHostEvent(host, 'click', (e) => {
|
|
47
|
+
* // Runs on each click
|
|
48
|
+
* });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export declare function useHostEvent<E extends Event = Event>(target: EventTarget, eventName: string, handler: (event: E) => void): void;
|
|
52
|
+
/**
|
|
53
|
+
* Run all registered mount callbacks for a component host.
|
|
54
|
+
* Called by component.ts after the template is mounted to the DOM.
|
|
55
|
+
* @internal
|
|
56
|
+
*/
|
|
57
|
+
export declare function runMountCallbacks(comp: object): void;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lifecycle.ts — Post-mount and cleanup lifecycle hooks
|
|
3
|
+
*
|
|
4
|
+
* `onMount()` registers a callback that runs after the component's
|
|
5
|
+
* template is mounted to the DOM. If the callback returns a function,
|
|
6
|
+
* that function runs on disconnect (cleanup).
|
|
7
|
+
*
|
|
8
|
+
* `onCleanup()` registers a standalone cleanup callback that runs
|
|
9
|
+
* on disconnect — for cases where you need cleanup without mount logic.
|
|
10
|
+
*
|
|
11
|
+
* Both must be called synchronously during component setup.
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* component('my-el', (props, host) => {
|
|
15
|
+
* onMount(() => {
|
|
16
|
+
* host.querySelector('input')?.focus();
|
|
17
|
+
* return () => console.log('unmounted');
|
|
18
|
+
* });
|
|
19
|
+
*
|
|
20
|
+
* onCleanup(() => clearInterval(timerId));
|
|
21
|
+
*
|
|
22
|
+
* return html`<input />`;
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
import { hasContext, getCurrentComponent } from './context.js';
|
|
27
|
+
// ── Mount callback storage ──────────────────────────────────────────
|
|
28
|
+
/** Mount callbacks are stored on the component host and called after template mount. */
|
|
29
|
+
const mountCallbacks = new WeakMap();
|
|
30
|
+
/**
|
|
31
|
+
* Register a callback to run after the component's template is mounted.
|
|
32
|
+
* If the callback returns a function, it will be called on disconnect.
|
|
33
|
+
*
|
|
34
|
+
* Must be called synchronously during component setup.
|
|
35
|
+
*/
|
|
36
|
+
export function onMount(callback) {
|
|
37
|
+
if (!hasContext()) {
|
|
38
|
+
throw new Error('onMount() called outside setup(). ' +
|
|
39
|
+
'It can only be called during component initialization.');
|
|
40
|
+
}
|
|
41
|
+
const comp = getCurrentComponent();
|
|
42
|
+
let callbacks = mountCallbacks.get(comp);
|
|
43
|
+
if (!callbacks) {
|
|
44
|
+
callbacks = [];
|
|
45
|
+
mountCallbacks.set(comp, callbacks);
|
|
46
|
+
}
|
|
47
|
+
callbacks.push(callback);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Register a cleanup callback to run on disconnect.
|
|
51
|
+
* This is a convenience for cases where you need cleanup without mount logic.
|
|
52
|
+
*
|
|
53
|
+
* Must be called synchronously during component setup.
|
|
54
|
+
*/
|
|
55
|
+
export function onCleanup(callback) {
|
|
56
|
+
if (!hasContext()) {
|
|
57
|
+
throw new Error('onCleanup() called outside setup(). ' +
|
|
58
|
+
'It can only be called during component initialization.');
|
|
59
|
+
}
|
|
60
|
+
getCurrentComponent().addDisposer(callback);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Listen for a DOM event on a target and auto-dispose the listener when the
|
|
64
|
+
* component disconnects. Works with any EventTarget (host element, document,
|
|
65
|
+
* window, etc.). Must be called during component setup.
|
|
66
|
+
*
|
|
67
|
+
* ```ts
|
|
68
|
+
* useHostEvent(host, 'click', (e) => {
|
|
69
|
+
* // Runs on each click
|
|
70
|
+
* });
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export function useHostEvent(target, eventName, handler) {
|
|
74
|
+
if (!hasContext()) {
|
|
75
|
+
throw new Error('useHostEvent() called outside setup(). ' +
|
|
76
|
+
'It can only be called during component initialization.');
|
|
77
|
+
}
|
|
78
|
+
target.addEventListener(eventName, handler);
|
|
79
|
+
getCurrentComponent().addDisposer(() => {
|
|
80
|
+
target.removeEventListener(eventName, handler);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Run all registered mount callbacks for a component host.
|
|
85
|
+
* Called by component.ts after the template is mounted to the DOM.
|
|
86
|
+
* @internal
|
|
87
|
+
*/
|
|
88
|
+
export function runMountCallbacks(comp) {
|
|
89
|
+
const callbacks = mountCallbacks.get(comp);
|
|
90
|
+
if (!callbacks)
|
|
91
|
+
return;
|
|
92
|
+
for (const cb of callbacks) {
|
|
93
|
+
try {
|
|
94
|
+
const cleanup = cb();
|
|
95
|
+
if (typeof cleanup === 'function') {
|
|
96
|
+
// Register cleanup as a disposer on the component
|
|
97
|
+
comp.addDisposer(cleanup);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
console.error('onMount callback error:', err);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Clear — mount callbacks run once
|
|
105
|
+
mountCallbacks.delete(comp);
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=lifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAE/D,uEAAuE;AAEvE,wFAAwF;AACxF,MAAM,cAAc,GAAG,IAAI,OAAO,EAAyC,CAAC;AAE5E;;;;;GAKG;AACH,MAAM,UAAU,OAAO,CAAC,QAAmC;IACzD,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CACb,oCAAoC;YACpC,wDAAwD,CACzD,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,mBAAmB,EAAE,CAAC;IACnC,IAAI,SAAS,GAAG,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACzC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,SAAS,GAAG,EAAE,CAAC;QACf,cAAc,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IACtC,CAAC;IACD,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC3B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,QAAoB;IAC5C,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CACb,sCAAsC;YACtC,wDAAwD,CACzD,CAAC;IACJ,CAAC;IACD,mBAAmB,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;AAC9C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAC1B,MAAmB,EACnB,SAAiB,EACjB,OAA2B;IAE3B,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CACb,yCAAyC;YACzC,wDAAwD,CACzD,CAAC;IACJ,CAAC;IACD,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,OAAwB,CAAC,CAAC;IAC7D,mBAAmB,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;QACrC,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,OAAwB,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,SAAS,GAAG,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC3C,IAAI,CAAC,SAAS;QAAE,OAAO;IAEvB,KAAK,MAAM,EAAE,IAAI,SAAS,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,EAAE,EAAE,CAAC;YACrB,IAAI,OAAO,OAAO,KAAK,UAAU,EAAE,CAAC;gBAClC,kDAAkD;gBACjD,IAAY,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;YACrC,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAC;QAChD,CAAC;IACH,CAAC;IAED,mCAAmC;IACnC,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC9B,CAAC"}
|
package/dist/query.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* query.ts — Declarative async data loading with cache
|
|
3
|
+
*
|
|
4
|
+
* `query()` absorbs the loading/error/cache/refetch boilerplate that
|
|
5
|
+
* every data-fetching component would otherwise write manually.
|
|
6
|
+
*
|
|
7
|
+
* Inspired by TanStack Query / React Query, but built on signals.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* const tasks = query(
|
|
11
|
+
* () => ['tasks', scopeId.value],
|
|
12
|
+
* () => api.getTasks(scopeId.value),
|
|
13
|
+
* );
|
|
14
|
+
* tasks.data // Signal<Task[] | undefined>
|
|
15
|
+
* tasks.loading // Signal<boolean>
|
|
16
|
+
* tasks.error // Signal<Error | null>
|
|
17
|
+
* tasks.refetch() // manual refetch
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
import { type Signal, type ReadonlySignal } from './signal.js';
|
|
21
|
+
export interface QueryResult<T> {
|
|
22
|
+
/** The resolved data, or undefined if not yet loaded */
|
|
23
|
+
data: Signal<T | undefined>;
|
|
24
|
+
/** True while fetching */
|
|
25
|
+
loading: ReadonlySignal<boolean>;
|
|
26
|
+
/** The error if the fetch failed, null otherwise */
|
|
27
|
+
error: Signal<Error | null>;
|
|
28
|
+
/** Manually trigger a refetch */
|
|
29
|
+
refetch(): void;
|
|
30
|
+
}
|
|
31
|
+
export interface QueryOptions<T> {
|
|
32
|
+
/** Cache freshness duration in ms. Default: 0 (always refetch) */
|
|
33
|
+
staleTime?: number;
|
|
34
|
+
/** Number of retries on failure. Default: 0 */
|
|
35
|
+
retry?: number;
|
|
36
|
+
/** Skip fetch when returns false. Signal reads are tracked. */
|
|
37
|
+
enabled?: () => boolean;
|
|
38
|
+
/** Synchronous initial value before first fetch */
|
|
39
|
+
initialData?: T;
|
|
40
|
+
/** Callback after successful fetch */
|
|
41
|
+
onSuccess?: (data: T) => void;
|
|
42
|
+
/** Callback after failed fetch */
|
|
43
|
+
onError?: (error: Error) => void;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Global query cache. Managed as an auto-singleton via inject().
|
|
47
|
+
* Components use query() which accesses the client internally.
|
|
48
|
+
*/
|
|
49
|
+
export declare class QueryClient {
|
|
50
|
+
private cache;
|
|
51
|
+
private inFlight;
|
|
52
|
+
/** Check if a cache entry is fresh */
|
|
53
|
+
isFresh(key: string, staleTime: number): boolean;
|
|
54
|
+
/** Get cached data */
|
|
55
|
+
getCached<T>(key: string): T | undefined;
|
|
56
|
+
/** Store data in cache */
|
|
57
|
+
set(key: string, data: unknown, staleTime: number): void;
|
|
58
|
+
/** Get in-flight promise for deduplication */
|
|
59
|
+
getInFlight(key: string): Promise<unknown> | undefined;
|
|
60
|
+
/** Register an in-flight request */
|
|
61
|
+
setInFlight(key: string, promise: Promise<unknown>): void;
|
|
62
|
+
/**
|
|
63
|
+
* Invalidate cached entries whose key array starts with the given prefix elements.
|
|
64
|
+
* Returns the number of entries invalidated.
|
|
65
|
+
*
|
|
66
|
+
* invalidate(['tasks']) matches ['tasks'], ['tasks', '1'], ['tasks', '2', 'x']
|
|
67
|
+
*/
|
|
68
|
+
invalidate(keyPrefix: unknown[]): number;
|
|
69
|
+
/** Prefetch and cache data */
|
|
70
|
+
prefetch(key: unknown[], fetcher: () => Promise<unknown>, staleTime?: number): Promise<void>;
|
|
71
|
+
/** Clear all cache */
|
|
72
|
+
clear(): void;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Declarative async data loading with caching and auto-refetch.
|
|
76
|
+
*
|
|
77
|
+
* @param keyFn - Returns a cache key array. Signal reads are auto-tracked for refetch.
|
|
78
|
+
* @param fetcher - Async function that fetches the data.
|
|
79
|
+
* @param options - Optional configuration (staleTime, retry, enabled, etc.)
|
|
80
|
+
*/
|
|
81
|
+
export declare function query<T>(keyFn: () => unknown[], fetcher: () => Promise<T>, options?: QueryOptions<T>): QueryResult<T>;
|