dalila 1.9.7 → 1.9.9
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 +2 -0
- package/dist/core/mutation.d.ts +106 -31
- package/dist/core/mutation.js +66 -58
- package/dist/core/query.d.ts +351 -31
- package/dist/core/query.js +431 -54
- package/dist/core/resource.d.ts +91 -9
- package/dist/core/resource.js +134 -22
- package/dist/runtime/bind.js +165 -3
- package/dist/runtime/boundary.d.ts +104 -0
- package/dist/runtime/boundary.js +304 -0
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/lazy.d.ts +109 -0
- package/dist/runtime/lazy.js +254 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,6 +60,8 @@ bind(document.getElementById('app')!, ctx);
|
|
|
60
60
|
|
|
61
61
|
- [Template Binding](./docs/runtime/bind.md) — `bind()`, `mount()`, `configure()`, transitions, portal, text interpolation, events
|
|
62
62
|
- [Components](./docs/runtime/component.md) — `defineComponent`, typed props/emits/refs, slots
|
|
63
|
+
- [Lazy Loading](./docs/runtime/lazy.md) — `createLazyComponent`, `d-lazy`, `createSuspense` wrapper, code splitting
|
|
64
|
+
- [Error Boundary](./docs/runtime/boundary.md) — `createErrorBoundary`, `createErrorBoundaryState`, `withErrorBoundary`, `d-boundary`
|
|
63
65
|
- [FOUC Prevention](./docs/runtime/fouc-prevention.md) — Automatic token hiding
|
|
64
66
|
|
|
65
67
|
### Routing
|
package/dist/core/mutation.d.ts
CHANGED
|
@@ -1,55 +1,130 @@
|
|
|
1
1
|
import { type QueryKey } from "./key.js";
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for creating a mutation (write operation).
|
|
4
|
+
*
|
|
5
|
+
* @template TInput - Type of input data passed to the mutation
|
|
6
|
+
* @template TResult - Type of result returned by the mutation
|
|
7
|
+
* @template TContext - Type of optional context kept between callbacks
|
|
8
|
+
*/
|
|
9
|
+
export interface MutationConfig<TInput, TResult, TContext = unknown> {
|
|
10
|
+
/**
|
|
11
|
+
* Main function that executes the mutation.
|
|
12
|
+
* Receives an AbortSignal for cancellation and the input data.
|
|
13
|
+
* Alias: can also use the 'mutate' property.
|
|
14
|
+
*/
|
|
15
|
+
mutationFn?: (signal: AbortSignal, input: TInput) => Promise<TResult>;
|
|
16
|
+
/**
|
|
17
|
+
* Alias for mutationFn. Useful for API compatibility.
|
|
18
|
+
*/
|
|
19
|
+
mutate?: (signal: AbortSignal, input: TInput) => Promise<TResult>;
|
|
4
20
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* - Keys revalidate a specific cached resource by key.
|
|
21
|
+
* Cache tags to automatically invalidate after success.
|
|
22
|
+
* Useful for updating related queries after mutation.
|
|
8
23
|
*/
|
|
9
24
|
invalidateTags?: readonly string[];
|
|
25
|
+
/**
|
|
26
|
+
* Query keys to automatically invalidate after success.
|
|
27
|
+
*/
|
|
10
28
|
invalidateKeys?: readonly QueryKey[];
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Callback executed before the mutation.
|
|
31
|
+
* Useful for optimistically updating UI state.
|
|
32
|
+
* Returns a context that will be passed to subsequent callbacks.
|
|
33
|
+
*/
|
|
34
|
+
onMutate?: (input: TInput) => Promise<TContext> | TContext;
|
|
35
|
+
/**
|
|
36
|
+
* Callback executed when mutation succeeds.
|
|
37
|
+
* @param result - The result returned by the mutation
|
|
38
|
+
* @param input - The original input data
|
|
39
|
+
* @param context - The context returned by onMutate (or undefined)
|
|
40
|
+
*/
|
|
41
|
+
onSuccess?: (result: TResult, input: TInput, context: TContext | undefined) => void;
|
|
42
|
+
/**
|
|
43
|
+
* Callback executed when mutation fails.
|
|
44
|
+
* @param error - The error that occurred
|
|
45
|
+
* @param input - The original input data
|
|
46
|
+
* @param context - The context returned by onMutate (or undefined)
|
|
47
|
+
*/
|
|
48
|
+
onError?: (error: Error, input: TInput, context: TContext | undefined) => void;
|
|
49
|
+
/**
|
|
50
|
+
* Callback executed after mutation completes (success or error).
|
|
51
|
+
* Always executed, regardless of outcome.
|
|
52
|
+
* @param result - The result (or null if error)
|
|
53
|
+
* @param error - The error (or null if success)
|
|
54
|
+
* @param input - The original input data
|
|
55
|
+
* @param context - The context returned by onMutate (or undefined)
|
|
56
|
+
*/
|
|
57
|
+
onSettled?: (result: TResult | null, error: Error | null, input: TInput, context: TContext | undefined) => void;
|
|
14
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Mutation state, exposing functions to control and access data.
|
|
61
|
+
*
|
|
62
|
+
* @template TInput - Type of input data
|
|
63
|
+
* @template TResult - Type of result
|
|
64
|
+
*/
|
|
15
65
|
export interface MutationState<TInput, TResult> {
|
|
66
|
+
/**
|
|
67
|
+
* Signal accessor for data returned by the mutation.
|
|
68
|
+
* Returns null if mutation hasn't run yet or failed.
|
|
69
|
+
*/
|
|
16
70
|
data: () => TResult | null;
|
|
71
|
+
/**
|
|
72
|
+
* Signal accessor indicating if mutation is in progress.
|
|
73
|
+
*/
|
|
17
74
|
loading: () => boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Signal accessor for the last mutation error (if any).
|
|
77
|
+
*/
|
|
18
78
|
error: () => Error | null;
|
|
19
79
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* -
|
|
80
|
+
* Executes the mutation with the provided input data.
|
|
81
|
+
* @param input - Input data for the mutation
|
|
82
|
+
* @param opts.force - If true, aborts any in-flight mutation and starts a new one
|
|
83
|
+
* @returns Promise that resolves with the result or null on error
|
|
23
84
|
*/
|
|
24
85
|
run: (input: TInput, opts?: {
|
|
25
86
|
force?: boolean;
|
|
26
87
|
}) => Promise<TResult | null>;
|
|
27
88
|
/**
|
|
28
|
-
* Resets
|
|
29
|
-
*
|
|
89
|
+
* Resets the mutation state to initial state.
|
|
90
|
+
* Aborts any in-flight mutation and clears data, loading, and error.
|
|
30
91
|
*/
|
|
31
92
|
reset: () => void;
|
|
32
93
|
}
|
|
33
94
|
/**
|
|
34
|
-
*
|
|
95
|
+
* Creates a mutation - a write operation that executes side effects
|
|
96
|
+
* and can invalidate related queries to maintain cache consistency.
|
|
97
|
+
*
|
|
98
|
+
* Follows the Mutation pattern from React Query/TanStack Query:
|
|
99
|
+
* - Provides lifecycle callbacks (onMutate, onSuccess, onError, onSettled)
|
|
100
|
+
* - Supports automatic cache invalidation by tags or keys
|
|
101
|
+
* - Automatically manages cancellation via AbortSignal
|
|
35
102
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* - React Query-like behavior: keep the last successful `data()` until overwritten or reset.
|
|
103
|
+
* @template TInput - Type of input data
|
|
104
|
+
* @template TResult - Type of expected result
|
|
105
|
+
* @template TContext - Type of context for communication between callbacks
|
|
106
|
+
* @param cfg - Mutation configuration
|
|
107
|
+
* @returns Mutation state with methods to execute and control
|
|
42
108
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```ts
|
|
111
|
+
* const mutation = createMutation({
|
|
112
|
+
* mutationFn: async (signal, data) => {
|
|
113
|
+
* const response = await fetch('/api/items', {
|
|
114
|
+
* method: 'POST',
|
|
115
|
+
* body: JSON.stringify(data),
|
|
116
|
+
* signal
|
|
117
|
+
* });
|
|
118
|
+
* return response.json();
|
|
119
|
+
* },
|
|
120
|
+
* invalidateTags: ['items'],
|
|
121
|
+
* onSuccess: (result) => {
|
|
122
|
+
* console.log('Item created:', result);
|
|
123
|
+
* }
|
|
124
|
+
* });
|
|
49
125
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* - invalidateKeys: revalidates specific cached resources by encoded key.
|
|
126
|
+
* // Execute the mutation
|
|
127
|
+
* mutation.run({ name: 'New Item' });
|
|
128
|
+
* ```
|
|
54
129
|
*/
|
|
55
|
-
export declare function createMutation<TInput, TResult>(cfg: MutationConfig<TInput, TResult>): MutationState<TInput, TResult>;
|
|
130
|
+
export declare function createMutation<TInput, TResult, TContext = unknown>(cfg: MutationConfig<TInput, TResult, TContext>): MutationState<TInput, TResult>;
|
package/dist/core/mutation.js
CHANGED
|
@@ -3,39 +3,47 @@ import { getCurrentScope } from "./scope.js";
|
|
|
3
3
|
import { encodeKey } from "./key.js";
|
|
4
4
|
import { invalidateResourceCache, invalidateResourceTags } from "./resource.js";
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Creates a mutation - a write operation that executes side effects
|
|
7
|
+
* and can invalidate related queries to maintain cache consistency.
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
* -
|
|
10
|
-
* -
|
|
11
|
-
* -
|
|
12
|
-
* - Force re-run: abort the current request and start a new one.
|
|
13
|
-
* - React Query-like behavior: keep the last successful `data()` until overwritten or reset.
|
|
9
|
+
* Follows the Mutation pattern from React Query/TanStack Query:
|
|
10
|
+
* - Provides lifecycle callbacks (onMutate, onSuccess, onError, onSettled)
|
|
11
|
+
* - Supports automatic cache invalidation by tags or keys
|
|
12
|
+
* - Automatically manages cancellation via AbortSignal
|
|
14
13
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* -
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* - and it MUST NOT overwrite state from a newer run.
|
|
14
|
+
* @template TInput - Type of input data
|
|
15
|
+
* @template TResult - Type of expected result
|
|
16
|
+
* @template TContext - Type of context for communication between callbacks
|
|
17
|
+
* @param cfg - Mutation configuration
|
|
18
|
+
* @returns Mutation state with methods to execute and control
|
|
21
19
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```ts
|
|
22
|
+
* const mutation = createMutation({
|
|
23
|
+
* mutationFn: async (signal, data) => {
|
|
24
|
+
* const response = await fetch('/api/items', {
|
|
25
|
+
* method: 'POST',
|
|
26
|
+
* body: JSON.stringify(data),
|
|
27
|
+
* signal
|
|
28
|
+
* });
|
|
29
|
+
* return response.json();
|
|
30
|
+
* },
|
|
31
|
+
* invalidateTags: ['items'],
|
|
32
|
+
* onSuccess: (result) => {
|
|
33
|
+
* console.log('Item created:', result);
|
|
34
|
+
* }
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* // Execute the mutation
|
|
38
|
+
* mutation.run({ name: 'New Item' });
|
|
39
|
+
* ```
|
|
26
40
|
*/
|
|
27
41
|
export function createMutation(cfg) {
|
|
28
42
|
const data = signal(null);
|
|
29
43
|
const loading = signal(false);
|
|
30
44
|
const error = signal(null);
|
|
31
|
-
/** In-flight promise for dedupe (represents the latest started run). */
|
|
32
45
|
let inFlight = null;
|
|
33
|
-
/** AbortController for the latest started run (used for force + scope cleanup). */
|
|
34
46
|
let controller = null;
|
|
35
|
-
/**
|
|
36
|
-
* If created inside a scope, abort the active run when the scope is disposed.
|
|
37
|
-
* This prevents orphan network work and avoids updating dead UI.
|
|
38
|
-
*/
|
|
39
47
|
const scope = getCurrentScope();
|
|
40
48
|
if (scope) {
|
|
41
49
|
scope.onCleanup(() => {
|
|
@@ -44,22 +52,15 @@ export function createMutation(cfg) {
|
|
|
44
52
|
inFlight = null;
|
|
45
53
|
});
|
|
46
54
|
}
|
|
55
|
+
const mutationFn = cfg.mutationFn ?? cfg.mutate;
|
|
56
|
+
if (!mutationFn) {
|
|
57
|
+
throw new Error("createMutation requires mutationFn or mutate");
|
|
58
|
+
}
|
|
47
59
|
async function run(input, opts = {}) {
|
|
48
|
-
/**
|
|
49
|
-
* Dedupe:
|
|
50
|
-
* - If a run is already loading and we're not forcing, await the current promise.
|
|
51
|
-
* - Snapshot `inFlight` to avoid races if a forced run starts mid-await.
|
|
52
|
-
*/
|
|
53
60
|
if (loading() && !opts.force) {
|
|
54
|
-
const
|
|
55
|
-
return (await (
|
|
61
|
+
const pending = inFlight;
|
|
62
|
+
return (await (pending ?? Promise.resolve(null)));
|
|
56
63
|
}
|
|
57
|
-
/**
|
|
58
|
-
* Start a new run:
|
|
59
|
-
* - Abort previous run (if any).
|
|
60
|
-
* - Create a fresh controller/signal for this run.
|
|
61
|
-
* - Capture controller identity so older runs cannot clobber newer state.
|
|
62
|
-
*/
|
|
63
64
|
controller?.abort();
|
|
64
65
|
controller = new AbortController();
|
|
65
66
|
const sig = controller.signal;
|
|
@@ -67,14 +68,35 @@ export function createMutation(cfg) {
|
|
|
67
68
|
loading.set(true);
|
|
68
69
|
error.set(null);
|
|
69
70
|
inFlight = (async () => {
|
|
71
|
+
let context = undefined;
|
|
72
|
+
try {
|
|
73
|
+
if (cfg.onMutate) {
|
|
74
|
+
context = await cfg.onMutate(input);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
if (sig.aborted)
|
|
79
|
+
return null;
|
|
80
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
81
|
+
error.set(err);
|
|
82
|
+
if (controller === localController)
|
|
83
|
+
loading.set(false);
|
|
84
|
+
try {
|
|
85
|
+
cfg.onError?.(err, input, context);
|
|
86
|
+
cfg.onSettled?.(null, err, input, context);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
if (sig.aborted)
|
|
93
|
+
return null;
|
|
70
94
|
try {
|
|
71
|
-
const result = await
|
|
72
|
-
// Aborted runs never commit state or call callbacks.
|
|
95
|
+
const result = await mutationFn(sig, input);
|
|
73
96
|
if (sig.aborted)
|
|
74
97
|
return null;
|
|
75
98
|
data.set(result);
|
|
76
|
-
cfg.onSuccess?.(result, input);
|
|
77
|
-
// Invalidate only after a successful, non-aborted mutation.
|
|
99
|
+
cfg.onSuccess?.(result, input, context);
|
|
78
100
|
if (cfg.invalidateTags && cfg.invalidateTags.length > 0) {
|
|
79
101
|
invalidateResourceTags(cfg.invalidateTags, { revalidate: true, force: true });
|
|
80
102
|
}
|
|
@@ -83,39 +105,25 @@ export function createMutation(cfg) {
|
|
|
83
105
|
invalidateResourceCache(encodeKey(k), { revalidate: true, force: true });
|
|
84
106
|
}
|
|
85
107
|
}
|
|
108
|
+
cfg.onSettled?.(result, null, input, context);
|
|
86
109
|
return result;
|
|
87
110
|
}
|
|
88
111
|
catch (e) {
|
|
89
|
-
// Aborted runs are treated as null (no error state, no callbacks).
|
|
90
112
|
if (sig.aborted)
|
|
91
113
|
return null;
|
|
92
114
|
const err = e instanceof Error ? e : new Error(String(e));
|
|
93
115
|
error.set(err);
|
|
94
|
-
cfg.onError?.(err, input);
|
|
116
|
+
cfg.onError?.(err, input, context);
|
|
117
|
+
cfg.onSettled?.(null, err, input, context);
|
|
95
118
|
return null;
|
|
96
119
|
}
|
|
97
120
|
finally {
|
|
98
|
-
|
|
99
|
-
* Only the latest run is allowed to update `loading`.
|
|
100
|
-
*
|
|
101
|
-
* Why?
|
|
102
|
-
* - If run A is aborted because run B starts, A's finally will still execute.
|
|
103
|
-
* - Without this guard, A could flip loading(false) while B is still running.
|
|
104
|
-
*/
|
|
105
|
-
const stillCurrent = controller === localController;
|
|
106
|
-
if (stillCurrent)
|
|
121
|
+
if (controller === localController)
|
|
107
122
|
loading.set(false);
|
|
108
|
-
// Keep onSettled consistent with onSuccess/onError: never run it for aborted runs.
|
|
109
|
-
if (!sig.aborted)
|
|
110
|
-
cfg.onSettled?.(input);
|
|
111
123
|
}
|
|
112
124
|
})();
|
|
113
125
|
return await inFlight;
|
|
114
126
|
}
|
|
115
|
-
/**
|
|
116
|
-
* Resets local state and aborts any active run.
|
|
117
|
-
* Does not touch the resource/query cache.
|
|
118
|
-
*/
|
|
119
127
|
function reset() {
|
|
120
128
|
controller?.abort();
|
|
121
129
|
controller = null;
|