@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/query.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
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 { signal, effect, computed } from './signal.js';
|
|
21
|
+
import { hasContext, getCurrentComponent } from './context.js';
|
|
22
|
+
import { inject } from './injector.js';
|
|
23
|
+
/**
|
|
24
|
+
* Global query cache. Managed as an auto-singleton via inject().
|
|
25
|
+
* Components use query() which accesses the client internally.
|
|
26
|
+
*/
|
|
27
|
+
export class QueryClient {
|
|
28
|
+
cache = new Map();
|
|
29
|
+
inFlight = new Map();
|
|
30
|
+
/** Check if a cache entry is fresh */
|
|
31
|
+
isFresh(key, staleTime) {
|
|
32
|
+
const entry = this.cache.get(key);
|
|
33
|
+
if (!entry)
|
|
34
|
+
return false;
|
|
35
|
+
return Date.now() - entry.fetchedAt < staleTime;
|
|
36
|
+
}
|
|
37
|
+
/** Get cached data */
|
|
38
|
+
getCached(key) {
|
|
39
|
+
return this.cache.get(key)?.data;
|
|
40
|
+
}
|
|
41
|
+
/** Store data in cache */
|
|
42
|
+
set(key, data, staleTime) {
|
|
43
|
+
this.cache.set(key, { data, fetchedAt: Date.now(), staleTime });
|
|
44
|
+
}
|
|
45
|
+
/** Get in-flight promise for deduplication */
|
|
46
|
+
getInFlight(key) {
|
|
47
|
+
return this.inFlight.get(key);
|
|
48
|
+
}
|
|
49
|
+
/** Register an in-flight request */
|
|
50
|
+
setInFlight(key, promise) {
|
|
51
|
+
// Attach a catch handler to prevent unhandled rejection on the stored promise.
|
|
52
|
+
// Callers who await this promise get the original behavior from their own try/catch.
|
|
53
|
+
const handled = promise.catch(() => { });
|
|
54
|
+
this.inFlight.set(key, promise);
|
|
55
|
+
// Clean up when done
|
|
56
|
+
handled.finally(() => {
|
|
57
|
+
if (this.inFlight.get(key) === promise) {
|
|
58
|
+
this.inFlight.delete(key);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
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) {
|
|
69
|
+
let count = 0;
|
|
70
|
+
for (const [serialized] of this.cache) {
|
|
71
|
+
try {
|
|
72
|
+
const key = JSON.parse(serialized);
|
|
73
|
+
if (Array.isArray(key) && keyPrefix.every((v, i) => JSON.stringify(v) === JSON.stringify(key[i]))) {
|
|
74
|
+
this.cache.delete(serialized);
|
|
75
|
+
count++;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// Skip invalid keys
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return count;
|
|
83
|
+
}
|
|
84
|
+
/** Prefetch and cache data */
|
|
85
|
+
async prefetch(key, fetcher, staleTime = 0) {
|
|
86
|
+
const serialized = serializeKey(key);
|
|
87
|
+
if (this.isFresh(serialized, staleTime))
|
|
88
|
+
return;
|
|
89
|
+
const data = await fetcher();
|
|
90
|
+
this.set(serialized, data, staleTime);
|
|
91
|
+
}
|
|
92
|
+
/** Clear all cache */
|
|
93
|
+
clear() {
|
|
94
|
+
this.cache.clear();
|
|
95
|
+
this.inFlight.clear();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// ── Key serialization ───────────────────────────────────────────────
|
|
99
|
+
function serializeKey(key) {
|
|
100
|
+
return JSON.stringify(key);
|
|
101
|
+
}
|
|
102
|
+
// ── query() — the main API ──────────────────────────────────────────
|
|
103
|
+
/**
|
|
104
|
+
* Declarative async data loading with caching and auto-refetch.
|
|
105
|
+
*
|
|
106
|
+
* @param keyFn - Returns a cache key array. Signal reads are auto-tracked for refetch.
|
|
107
|
+
* @param fetcher - Async function that fetches the data.
|
|
108
|
+
* @param options - Optional configuration (staleTime, retry, enabled, etc.)
|
|
109
|
+
*/
|
|
110
|
+
export function query(keyFn, fetcher, options = {}) {
|
|
111
|
+
const { staleTime = 0, retry = 0, enabled, initialData, onSuccess, onError, } = options;
|
|
112
|
+
const data = signal(initialData);
|
|
113
|
+
const loading = signal(false);
|
|
114
|
+
const error = signal(null);
|
|
115
|
+
// Get or lazily create the QueryClient
|
|
116
|
+
let client;
|
|
117
|
+
try {
|
|
118
|
+
client = inject(QueryClient);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// If no DI context, create a local client
|
|
122
|
+
client = new QueryClient();
|
|
123
|
+
}
|
|
124
|
+
// Track the current fetch generation to handle race conditions
|
|
125
|
+
let fetchGeneration = 0;
|
|
126
|
+
let disposed = false;
|
|
127
|
+
const doFetch = async () => {
|
|
128
|
+
if (disposed)
|
|
129
|
+
return;
|
|
130
|
+
// Check enabled
|
|
131
|
+
if (enabled && !enabled())
|
|
132
|
+
return;
|
|
133
|
+
const key = keyFn();
|
|
134
|
+
const serialized = serializeKey(key);
|
|
135
|
+
const generation = ++fetchGeneration;
|
|
136
|
+
// Check cache freshness
|
|
137
|
+
if (staleTime > 0 && client.isFresh(serialized, staleTime)) {
|
|
138
|
+
const cached = client.getCached(serialized);
|
|
139
|
+
if (cached !== undefined) {
|
|
140
|
+
data.value = cached;
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Check for in-flight deduplication
|
|
145
|
+
const existing = client.getInFlight(serialized);
|
|
146
|
+
if (existing) {
|
|
147
|
+
loading.value = true;
|
|
148
|
+
try {
|
|
149
|
+
const result = await existing;
|
|
150
|
+
if (generation === fetchGeneration && !disposed) {
|
|
151
|
+
data.value = result;
|
|
152
|
+
error.value = null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
if (generation === fetchGeneration && !disposed) {
|
|
157
|
+
error.value = e instanceof Error ? e : new Error(String(e));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
if (generation === fetchGeneration && !disposed) {
|
|
162
|
+
loading.value = false;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
loading.value = true;
|
|
168
|
+
error.value = null;
|
|
169
|
+
let lastError = null;
|
|
170
|
+
const attempts = retry + 1;
|
|
171
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
172
|
+
if (disposed || generation !== fetchGeneration)
|
|
173
|
+
return;
|
|
174
|
+
const promise = fetcher();
|
|
175
|
+
// Prevent unhandled rejection on the shared in-flight reference
|
|
176
|
+
promise.catch(() => { });
|
|
177
|
+
client.setInFlight(serialized, promise);
|
|
178
|
+
try {
|
|
179
|
+
const result = await promise;
|
|
180
|
+
// Guard against stale responses
|
|
181
|
+
if (generation === fetchGeneration && !disposed) {
|
|
182
|
+
data.value = result;
|
|
183
|
+
error.value = null;
|
|
184
|
+
loading.value = false;
|
|
185
|
+
client.set(serialized, result, staleTime);
|
|
186
|
+
onSuccess?.(result);
|
|
187
|
+
}
|
|
188
|
+
return; // success
|
|
189
|
+
}
|
|
190
|
+
catch (e) {
|
|
191
|
+
lastError = e instanceof Error ? e : new Error(String(e));
|
|
192
|
+
// Continue to next retry attempt
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// All retries exhausted
|
|
196
|
+
if (generation === fetchGeneration && !disposed) {
|
|
197
|
+
error.value = lastError;
|
|
198
|
+
loading.value = false;
|
|
199
|
+
if (lastError)
|
|
200
|
+
onError?.(lastError);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
// Auto-fetch on mount and when dependencies change
|
|
204
|
+
const disposeEffect = effect(() => {
|
|
205
|
+
// Reading keyFn() inside the effect tracks signal dependencies
|
|
206
|
+
const _key = keyFn();
|
|
207
|
+
// Also track enabled()
|
|
208
|
+
if (enabled)
|
|
209
|
+
enabled();
|
|
210
|
+
// Schedule the async fetch — catch to prevent unhandled promise rejection
|
|
211
|
+
doFetch().catch(() => { });
|
|
212
|
+
});
|
|
213
|
+
// Register disposal if in component context
|
|
214
|
+
if (hasContext()) {
|
|
215
|
+
getCurrentComponent().addDisposer(() => {
|
|
216
|
+
disposed = true;
|
|
217
|
+
disposeEffect();
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
const refetch = () => {
|
|
221
|
+
fetchGeneration++; // invalidate any in-flight
|
|
222
|
+
doFetch().catch(() => { });
|
|
223
|
+
};
|
|
224
|
+
return {
|
|
225
|
+
data,
|
|
226
|
+
loading: loading,
|
|
227
|
+
error,
|
|
228
|
+
refetch,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.js","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"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"}
|
package/dist/ref.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ref.ts — Element reference primitive for imperative DOM access
|
|
3
|
+
*
|
|
4
|
+
* `ref()` creates a container that receives a DOM element reference
|
|
5
|
+
* after the template is mounted. Use it for focus management, DOM
|
|
6
|
+
* measurement, scroll management, and third-party library init.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* const inputRef = ref<HTMLInputElement>();
|
|
10
|
+
*
|
|
11
|
+
* onMount(() => inputRef.current?.focus());
|
|
12
|
+
*
|
|
13
|
+
* return html`<input ${inputRef} @keydown.enter=${onSubmit} />`;
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* The template engine detects `Ref` objects in expression positions
|
|
17
|
+
* (not inside attribute values) and assigns the nearest parent element.
|
|
18
|
+
*/
|
|
19
|
+
export declare const REF_BRAND: unique symbol;
|
|
20
|
+
export interface Ref<T extends Element = Element> {
|
|
21
|
+
readonly [REF_BRAND]: true;
|
|
22
|
+
/** The DOM element, or null before mount / after unmount */
|
|
23
|
+
current: T | null;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Create a ref container for imperative DOM access.
|
|
27
|
+
*
|
|
28
|
+
* After the template is mounted, `ref.current` holds the element.
|
|
29
|
+
* After dispose, `ref.current` is set back to null.
|
|
30
|
+
*/
|
|
31
|
+
export declare function ref<T extends Element = Element>(): Ref<T>;
|
|
32
|
+
/**
|
|
33
|
+
* Check if a value is a Ref.
|
|
34
|
+
* Used by the template engine to detect refs in expression slots.
|
|
35
|
+
*/
|
|
36
|
+
export declare function isRef(value: unknown): value is Ref;
|
package/dist/ref.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ref.ts — Element reference primitive for imperative DOM access
|
|
3
|
+
*
|
|
4
|
+
* `ref()` creates a container that receives a DOM element reference
|
|
5
|
+
* after the template is mounted. Use it for focus management, DOM
|
|
6
|
+
* measurement, scroll management, and third-party library init.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* const inputRef = ref<HTMLInputElement>();
|
|
10
|
+
*
|
|
11
|
+
* onMount(() => inputRef.current?.focus());
|
|
12
|
+
*
|
|
13
|
+
* return html`<input ${inputRef} @keydown.enter=${onSubmit} />`;
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* The template engine detects `Ref` objects in expression positions
|
|
17
|
+
* (not inside attribute values) and assigns the nearest parent element.
|
|
18
|
+
*/
|
|
19
|
+
// ── Brand for ref detection in templates ─────────────────────────────
|
|
20
|
+
export const REF_BRAND = Symbol.for('backlog.ref');
|
|
21
|
+
// ── Public API ──────────────────────────────────────────────────────
|
|
22
|
+
/**
|
|
23
|
+
* Create a ref container for imperative DOM access.
|
|
24
|
+
*
|
|
25
|
+
* After the template is mounted, `ref.current` holds the element.
|
|
26
|
+
* After dispose, `ref.current` is set back to null.
|
|
27
|
+
*/
|
|
28
|
+
export function ref() {
|
|
29
|
+
return {
|
|
30
|
+
[REF_BRAND]: true,
|
|
31
|
+
current: null,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Check if a value is a Ref.
|
|
36
|
+
* Used by the template engine to detect refs in expression slots.
|
|
37
|
+
*/
|
|
38
|
+
export function isRef(value) {
|
|
39
|
+
return (value !== null &&
|
|
40
|
+
typeof value === 'object' &&
|
|
41
|
+
REF_BRAND in value);
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=ref.js.map
|
package/dist/ref.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ref.js","sourceRoot":"","sources":["../src/ref.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,wEAAwE;AAExE,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;AAUnD,uEAAuE;AAEvE;;;;;GAKG;AACH,MAAM,UAAU,GAAG;IACjB,OAAO;QACL,CAAC,SAAS,CAAC,EAAE,IAAa;QAC1B,OAAO,EAAE,IAAI;KACd,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,KAAK,CAAC,KAAc;IAClC,OAAO,CACL,KAAK,KAAK,IAAI;QACd,OAAO,KAAK,KAAK,QAAQ;QACzB,SAAS,IAAK,KAAiC,CAChD,CAAC;AACJ,CAAC"}
|
package/dist/signal.d.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* signal.ts — Reactive primitives: signal(), computed(), effect()
|
|
3
|
+
*
|
|
4
|
+
* Push-pull hybrid model:
|
|
5
|
+
* - Writes push "dirty" flags up the dependency graph
|
|
6
|
+
* - Reads pull fresh values lazily (computed only recalculates when read)
|
|
7
|
+
* - Multiple synchronous writes coalesce into one microtask flush
|
|
8
|
+
*
|
|
9
|
+
* Dependency tracking is automatic: reading a signal inside a computed
|
|
10
|
+
* or effect registers it as a dependency. Dependencies are re-tracked
|
|
11
|
+
* on every execution (dynamic/conditional tracking).
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Register the context hook. Called by component.ts at module init.
|
|
15
|
+
* The hook returns an `addDisposer` function if inside setup context, null otherwise.
|
|
16
|
+
*/
|
|
17
|
+
export declare function setContextHook(hook: () => ((fn: () => void) => void) | null): void;
|
|
18
|
+
export declare const SIGNAL_BRAND: unique symbol;
|
|
19
|
+
/** Readable reactive container. */
|
|
20
|
+
export interface ReadonlySignal<T> {
|
|
21
|
+
readonly [SIGNAL_BRAND]: true;
|
|
22
|
+
readonly value: T;
|
|
23
|
+
/** Subscribe to value changes. Returns unsubscribe function. */
|
|
24
|
+
subscribe(fn: (value: T) => void): () => void;
|
|
25
|
+
}
|
|
26
|
+
/** Read-write reactive container. */
|
|
27
|
+
export interface Signal<T> extends ReadonlySignal<T> {
|
|
28
|
+
value: T;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Create a writable reactive signal.
|
|
32
|
+
*
|
|
33
|
+
* ```ts
|
|
34
|
+
* const count = signal(0);
|
|
35
|
+
* count.value; // read (auto-tracks in reactive contexts)
|
|
36
|
+
* count.value = 5; // write (notifies dependents)
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare function signal<T>(initialValue: T): Signal<T>;
|
|
40
|
+
/**
|
|
41
|
+
* Create a derived, lazy, cached computed signal.
|
|
42
|
+
* Re-evaluates only when dependencies change AND the value is read.
|
|
43
|
+
*
|
|
44
|
+
* ```ts
|
|
45
|
+
* const doubled = computed(() => count.value * 2);
|
|
46
|
+
* doubled.value; // lazy evaluation
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare function computed<T>(fn: () => T): ReadonlySignal<T>;
|
|
50
|
+
/**
|
|
51
|
+
* Create a side-effect that re-runs when its dependencies change.
|
|
52
|
+
* Returns a dispose function to stop the effect.
|
|
53
|
+
*
|
|
54
|
+
* The effect function may return a cleanup callback that runs
|
|
55
|
+
* before each re-execution and on disposal.
|
|
56
|
+
*
|
|
57
|
+
* ```ts
|
|
58
|
+
* const dispose = effect(() => {
|
|
59
|
+
* console.log('count is', count.value);
|
|
60
|
+
* return () => { // cleanup
|
|
61
|
+
* console.log('cleaning up');
|
|
62
|
+
* };
|
|
63
|
+
* });
|
|
64
|
+
* dispose(); // stop the effect
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export declare function effect(fn: () => void | (() => void)): () => void;
|
|
68
|
+
/**
|
|
69
|
+
* Check if a value is a signal (writable or computed).
|
|
70
|
+
* Used by the template engine to detect signals in expression slots.
|
|
71
|
+
*/
|
|
72
|
+
export declare function isSignal(value: unknown): value is ReadonlySignal<unknown>;
|
|
73
|
+
/**
|
|
74
|
+
* Run a function with activeObserver set to null, preventing any
|
|
75
|
+
* signal reads inside `fn` from being tracked by an outer effect.
|
|
76
|
+
*
|
|
77
|
+
* Used by component.ts to isolate connectedCallback: child component
|
|
78
|
+
* setup signal reads must not leak into a parent effect's dependency
|
|
79
|
+
* set. See ADR 0008 Gap 1.
|
|
80
|
+
*/
|
|
81
|
+
export declare function untrack(fn: () => void): void;
|
|
82
|
+
/**
|
|
83
|
+
* Synchronously execute all pending effects.
|
|
84
|
+
*
|
|
85
|
+
* In normal usage, effects run automatically on the next microtask.
|
|
86
|
+
* Use flush() only when you need effects to have executed before
|
|
87
|
+
* the next line — e.g., imperative DOM measurement after state change.
|
|
88
|
+
*
|
|
89
|
+
* flush() is idempotent: calling it with no pending effects is a no-op.
|
|
90
|
+
*/
|
|
91
|
+
export declare function flush(): void;
|
|
92
|
+
/** Backward compat alias for tests. */
|
|
93
|
+
export { flush as flushEffects };
|