@antelopejs/dms-frontend 0.9.1 → 0.9.3
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 +9 -1
- package/dist/cancellation.js +2 -1
- package/dist/config.js +11 -7
- package/dist/derived-outputs.js +6 -113
- package/dist/dev-modules.js +3 -2
- package/dist/discovery.js +19 -12
- package/dist/fs-sync.js +1 -2
- package/dist/internal/guards.d.ts +5 -0
- package/dist/internal/guards.js +22 -0
- package/dist/internal/locale-catalogs.d.ts +6 -0
- package/dist/internal/locale-catalogs.js +115 -0
- package/dist/layer-aliases.js +2 -1
- package/dist/layers.js +2 -1
- package/dist/locale-files.js +9 -4
- package/dist/manifest-secrets.js +11 -4
- package/dist/manifest.js +11 -5
- package/dist/materialize.js +6 -5
- package/dist/module-set.js +2 -4
- package/dist/ports.js +8 -1
- package/dist/private-values.js +26 -17
- package/dist/project-modules.js +17 -11
- package/dist/renderer-range.js +2 -1
- package/dist/server-process.js +17 -16
- package/dist/update-check.js +23 -16
- package/dist/verify-source-runner.js +2 -2
- package/dist/workspace-setup.js +1 -2
- package/dist/workspace.js +11 -10
- package/package.json +7 -4
- package/skills/dms-frontend/SKILL.md +1 -1
- package/skills/dms-frontend/cases.json +77 -0
- package/templates/vue/app-runtime.ts +88 -7
- package/templates/vue/client-chunking.mjs +0 -5
- package/templates/vue/client-server-guard.mjs +10 -13
- package/templates/vue/entry-weight.mjs +12 -14
- package/templates/vue/frontend-module.ts +159 -3563
- package/templates/vue/frontend-runtime/async-data-entry.ts +320 -0
- package/templates/vue/frontend-runtime/async-data.ts +404 -0
- package/templates/vue/frontend-runtime/components.ts +386 -0
- package/templates/vue/frontend-runtime/cookies.ts +125 -0
- package/templates/vue/frontend-runtime/data-access.ts +132 -0
- package/templates/vue/frontend-runtime/dms-error.ts +11 -0
- package/templates/vue/frontend-runtime/errors.ts +145 -0
- package/templates/vue/frontend-runtime/fetch.ts +306 -0
- package/templates/vue/frontend-runtime/module-registration.ts +247 -0
- package/templates/vue/frontend-runtime/paged-data.ts +191 -0
- package/templates/vue/frontend-runtime/payload.ts +172 -0
- package/templates/vue/frontend-runtime/route.ts +328 -0
- package/templates/vue/frontend-runtime/sdk.ts +300 -0
- package/templates/vue/frontend-runtime/state.ts +167 -0
- package/templates/vue/frontend-runtime/types.ts +361 -0
- package/templates/vue/frontend-runtime/ui.ts +247 -0
- package/templates/vue/globals.d.ts +5 -0
- package/templates/vue/head-order.mjs +11 -13
- package/templates/vue/icon-bundle.ts +31 -0
- package/templates/vue/message-subset.ts +65 -0
- package/templates/vue/network-error.ts +4 -0
- package/templates/vue/prepare-types.mjs +0 -8
- package/templates/vue/server/auth/backend.mjs +4 -2
- package/templates/vue/server/auth/establish-endpoints.mjs +14 -9
- package/templates/vue/server/auth/session.mjs +4 -3
- package/templates/vue/server/auth-hooks.mjs +6 -6
- package/templates/vue/server/backend-proxies.mjs +6 -6
- package/templates/vue/server/backend-response.mjs +15 -14
- package/templates/vue/server/built-files.mjs +10 -8
- package/templates/vue/server/cli-report.mjs +7 -5
- package/templates/vue/server/client-error.mjs +11 -8
- package/templates/vue/server/client-manifest.mjs +9 -8
- package/templates/vue/server/content.mjs +2 -1
- package/templates/vue/server/dev-private-files.mjs +20 -15
- package/templates/vue/server/dev-styles.mjs +22 -25
- package/templates/vue/server/email-bundle-watch.mjs +0 -12
- package/templates/vue/server/email.mjs +20 -15
- package/templates/vue/server/favicon.mjs +12 -11
- package/templates/vue/server/homepage.mjs +8 -10
- package/templates/vue/server/icons.mjs +18 -9
- package/templates/vue/server/in-place-render.mjs +6 -8
- package/templates/vue/server/listen-host.mjs +2 -2
- package/templates/vue/server/module-event.mjs +5 -5
- package/templates/vue/server/module-routes.mjs +11 -7
- package/templates/vue/server/module-server.mjs +8 -7
- package/templates/vue/server/module-testing.mjs +7 -7
- package/templates/vue/server/page-props.mjs +14 -14
- package/templates/vue/server/path-patterns.mjs +4 -4
- package/templates/vue/server/proxy-compression.mjs +21 -13
- package/templates/vue/server/proxy-headers.mjs +5 -3
- package/templates/vue/server/proxy-redirects.mjs +7 -6
- package/templates/vue/server/proxy-transfers.mjs +15 -11
- package/templates/vue/server/public-endpoints.mjs +27 -19
- package/templates/vue/server/public-options.mjs +9 -7
- package/templates/vue/server/request-failure.mjs +4 -6
- package/templates/vue/server/request-routing.mjs +8 -11
- package/templates/vue/server/request-target.mjs +6 -6
- package/templates/vue/server/route-rules.mjs +7 -5
- package/templates/vue/server/static-files.mjs +19 -10
- package/templates/vue/server/web-body.mjs +3 -4
- package/templates/vue/server.mjs +32 -19
- package/templates/vue/sfc-global-types.mjs +27 -24
- package/templates/vue/ssr-renderer.ts +10 -1
- package/templates/vue/ui-inertia-page.mjs +7 -7
- package/templates/vue/ui-kbd.mjs +10 -10
- package/templates/vue/ui-lazy-icons.mjs +44 -0
- package/templates/vue/ui-request-state.mjs +9 -9
- package/templates/vue/vite.config.ts +2 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computed,
|
|
3
|
+
getCurrentInstance,
|
|
4
|
+
getCurrentScope,
|
|
5
|
+
type MaybeRefOrGetter,
|
|
6
|
+
nextTick,
|
|
7
|
+
onBeforeMount,
|
|
8
|
+
onMounted,
|
|
9
|
+
onScopeDispose,
|
|
10
|
+
onServerPrefetch,
|
|
11
|
+
type Ref,
|
|
12
|
+
type ShallowRef,
|
|
13
|
+
shallowRef,
|
|
14
|
+
toValue,
|
|
15
|
+
watch,
|
|
16
|
+
} from "vue";
|
|
17
|
+
import {
|
|
18
|
+
type DmsAsyncData,
|
|
19
|
+
type DmsAsyncDataHandler,
|
|
20
|
+
type DmsAsyncDataKey,
|
|
21
|
+
type DmsAsyncDataResult,
|
|
22
|
+
type UseAsyncDataOptions,
|
|
23
|
+
} from "./types";
|
|
24
|
+
import { useDmsRuntime } from "./state";
|
|
25
|
+
import { type DmsFrontendRuntime } from "./module-registration";
|
|
26
|
+
import {
|
|
27
|
+
type AsyncDataBindingHandle,
|
|
28
|
+
asyncDataEntry,
|
|
29
|
+
type AsyncDataEntry,
|
|
30
|
+
} from "./async-data-entry";
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Loads data once for every component that shows the same key, with Nuxt's
|
|
34
|
+
* semantics: a server render waits for it before rendering, and transfers it,
|
|
35
|
+
* whether or not the caller awaits it; `lazy` only spares client navigations
|
|
36
|
+
* the wait; `server: false` leaves it to the browser.
|
|
37
|
+
*
|
|
38
|
+
* In the browser, data loaded before is shown at once and read again in the
|
|
39
|
+
* background on each mount and each return to its key (stale while
|
|
40
|
+
* revalidate), except while the page the server rendered hydrates: its data
|
|
41
|
+
* is the server's, just read.
|
|
42
|
+
*
|
|
43
|
+
* A call without a key cannot be transferred: the server leaves it to the
|
|
44
|
+
* browser, as `server: false` does, so the page hydrates with what it shows.
|
|
45
|
+
*/
|
|
46
|
+
export function useDmsAsyncData<T>(
|
|
47
|
+
keyOrHandler: MaybeRefOrGetter<DmsAsyncDataKey> | DmsAsyncDataHandler<T>,
|
|
48
|
+
handler?: DmsAsyncDataHandler<T>,
|
|
49
|
+
callerOptions: UseAsyncDataOptions<T> = {},
|
|
50
|
+
): DmsAsyncDataResult<T> {
|
|
51
|
+
const runtime = useDmsRuntime();
|
|
52
|
+
// A call outside any scope (a plugin past its first await, a callback)
|
|
53
|
+
// belongs to the application: its watchers and its polling live as long.
|
|
54
|
+
if (getCurrentScope() || !runtime.scope.active)
|
|
55
|
+
return createAsyncDataCall(runtime, keyOrHandler, handler, callerOptions);
|
|
56
|
+
return runtime.scope.run(() =>
|
|
57
|
+
createAsyncDataCall(runtime, keyOrHandler, handler, callerOptions),
|
|
58
|
+
)!;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function createAsyncDataCall<T>(
|
|
62
|
+
runtime: DmsFrontendRuntime,
|
|
63
|
+
keyOrHandler: MaybeRefOrGetter<DmsAsyncDataKey> | DmsAsyncDataHandler<T>,
|
|
64
|
+
handler: DmsAsyncDataHandler<T> | undefined,
|
|
65
|
+
callerOptions: UseAsyncDataOptions<T>,
|
|
66
|
+
): DmsAsyncDataResult<T> {
|
|
67
|
+
// With a handler second, the first argument is the key: a string, or a ref
|
|
68
|
+
// or getter whose changes move the caller to the data of another key.
|
|
69
|
+
const keySource = handler
|
|
70
|
+
? (keyOrHandler as MaybeRefOrGetter<DmsAsyncDataKey>)
|
|
71
|
+
: undefined;
|
|
72
|
+
const execute = handler ?? keyOrHandler;
|
|
73
|
+
if (typeof execute !== "function")
|
|
74
|
+
throw new Error("useDmsAsyncData requires a handler");
|
|
75
|
+
const keyless = keySource === undefined;
|
|
76
|
+
if (keyless && runtime.isServer && runtime.devWarnings)
|
|
77
|
+
console.warn(
|
|
78
|
+
"DMS: useDmsAsyncData without a key, or useDmsFetch with a body it cannot key, " +
|
|
79
|
+
"is left to the browser: give it a key for the server to render it.",
|
|
80
|
+
);
|
|
81
|
+
const options: UseAsyncDataOptions<T> = keyless
|
|
82
|
+
? { ...callerOptions, server: false }
|
|
83
|
+
: callerOptions;
|
|
84
|
+
const inComponent = getCurrentInstance() !== null;
|
|
85
|
+
const consumer = shallowRef(
|
|
86
|
+
attachAsyncData(
|
|
87
|
+
runtime,
|
|
88
|
+
keyless ? undefined : (toValue(keySource) ?? null),
|
|
89
|
+
execute as DmsAsyncDataHandler<T>,
|
|
90
|
+
options,
|
|
91
|
+
inComponent,
|
|
92
|
+
),
|
|
93
|
+
);
|
|
94
|
+
if (getCurrentScope()) onScopeDispose(() => consumer.value.unbind());
|
|
95
|
+
if (options.watch && options.watch.length)
|
|
96
|
+
refreshOnWatchedChange(consumer, options.watch);
|
|
97
|
+
// Registered in the browser too, though only a server calls it: Vue makes
|
|
98
|
+
// a component that prefetches an async boundary, which shifts the ids
|
|
99
|
+
// `useId()` gives the components after it, and both sides must agree. It
|
|
100
|
+
// waits for the key the caller is on by then, which its setup may have set.
|
|
101
|
+
let prefetched = false;
|
|
102
|
+
if (inComponent && options.immediate !== false && options.server !== false)
|
|
103
|
+
onServerPrefetch(() => {
|
|
104
|
+
prefetched = true;
|
|
105
|
+
return consumer.value.entry.settled();
|
|
106
|
+
});
|
|
107
|
+
pollAsyncData(runtime, consumer, options);
|
|
108
|
+
const { entry } = consumer.value;
|
|
109
|
+
const waited = startAsyncData(runtime, entry, options, inComponent);
|
|
110
|
+
const result =
|
|
111
|
+
keySource == null || typeof keySource === "string"
|
|
112
|
+
? entry.result
|
|
113
|
+
: followAsyncDataKey(
|
|
114
|
+
runtime,
|
|
115
|
+
consumer,
|
|
116
|
+
keySource,
|
|
117
|
+
execute as DmsAsyncDataHandler<T>,
|
|
118
|
+
options,
|
|
119
|
+
inComponent,
|
|
120
|
+
() => !prefetched,
|
|
121
|
+
);
|
|
122
|
+
return Object.assign(
|
|
123
|
+
Promise.resolve(waited).then(() => result),
|
|
124
|
+
result,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Reads the data again when a watched source changes, but when the caller's
|
|
130
|
+
* key moved in the same turn: the new key's own read already has the change.
|
|
131
|
+
*/
|
|
132
|
+
function refreshOnWatchedChange<T>(
|
|
133
|
+
consumer: ShallowRef<AsyncDataConsumer<T>>,
|
|
134
|
+
sources: unknown[],
|
|
135
|
+
): void {
|
|
136
|
+
let moved = false;
|
|
137
|
+
watch(
|
|
138
|
+
() => consumer.value.entry,
|
|
139
|
+
() => {
|
|
140
|
+
moved = true;
|
|
141
|
+
void nextTick(() => {
|
|
142
|
+
moved = false;
|
|
143
|
+
});
|
|
144
|
+
},
|
|
145
|
+
{ flush: "sync" },
|
|
146
|
+
);
|
|
147
|
+
watch(sources as never[], () => {
|
|
148
|
+
if (!moved) void consumer.value.entry.result.refresh();
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Reads the data again every `refreshInterval`, in the browser, following
|
|
154
|
+
* the interval as it changes: none, `0` or `null` stops it.
|
|
155
|
+
*/
|
|
156
|
+
function pollAsyncData<T>(
|
|
157
|
+
runtime: DmsFrontendRuntime,
|
|
158
|
+
consumer: ShallowRef<AsyncDataConsumer<T>>,
|
|
159
|
+
options: UseAsyncDataOptions<T>,
|
|
160
|
+
): void {
|
|
161
|
+
const interval = options.refreshInterval;
|
|
162
|
+
if (runtime.isServer || interval === undefined) return;
|
|
163
|
+
let timer: ReturnType<typeof setInterval> | undefined;
|
|
164
|
+
const stop = () => {
|
|
165
|
+
clearInterval(timer);
|
|
166
|
+
timer = undefined;
|
|
167
|
+
};
|
|
168
|
+
watch(
|
|
169
|
+
() => toValue(interval),
|
|
170
|
+
(period) => {
|
|
171
|
+
stop();
|
|
172
|
+
if (period && period > 0)
|
|
173
|
+
timer = setInterval(() => pollOnce(consumer, options), period);
|
|
174
|
+
},
|
|
175
|
+
{ immediate: true },
|
|
176
|
+
);
|
|
177
|
+
if (getCurrentScope()) onScopeDispose(stop);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function pollOnce<T>(
|
|
181
|
+
consumer: ShallowRef<AsyncDataConsumer<T>>,
|
|
182
|
+
options: UseAsyncDataOptions<T>,
|
|
183
|
+
): void {
|
|
184
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden")
|
|
185
|
+
return;
|
|
186
|
+
const result = consumer.value.entry.result;
|
|
187
|
+
// Data a caller waits to `execute()` is not read before it is.
|
|
188
|
+
if (result.status.value === "idle" && options.immediate === false) return;
|
|
189
|
+
void result.refresh({ dedupe: "defer" });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Starts the run a caller needs, where its side needs it, and returns what
|
|
194
|
+
* awaiting the call waits for.
|
|
195
|
+
*/
|
|
196
|
+
function startAsyncData<T>(
|
|
197
|
+
runtime: DmsFrontendRuntime,
|
|
198
|
+
entry: AsyncDataEntry<T>,
|
|
199
|
+
options: UseAsyncDataOptions<T>,
|
|
200
|
+
inComponent: boolean,
|
|
201
|
+
): Promise<void> | undefined {
|
|
202
|
+
const immediate = options.immediate !== false;
|
|
203
|
+
const onServer = options.server !== false;
|
|
204
|
+
const idle = entry.result.status.value === "idle";
|
|
205
|
+
if (runtime.isServer) {
|
|
206
|
+
if (!immediate || !onServer) return undefined;
|
|
207
|
+
// A render that already answers a redirect throws its HTML away: what it
|
|
208
|
+
// still has to read would reach nobody.
|
|
209
|
+
if (runtime.serverRedirect) return undefined;
|
|
210
|
+
if (idle && options.lazy) entry.schedule();
|
|
211
|
+
else if (idle) void entry.load();
|
|
212
|
+
const settled = entry.settled();
|
|
213
|
+
if (!inComponent) runtime.serverPrefetches.add(settled);
|
|
214
|
+
return settled;
|
|
215
|
+
}
|
|
216
|
+
// The server's render had the data once the setup awaited it, and before
|
|
217
|
+
// the component rendered: handed over at the same points, it gives what
|
|
218
|
+
// the setup derives from it, and so the HTML, the same values.
|
|
219
|
+
const adoption = entry.adoption();
|
|
220
|
+
if (adoption) {
|
|
221
|
+
if (inComponent) onBeforeMount(entry.adoptNow);
|
|
222
|
+
return adoption;
|
|
223
|
+
}
|
|
224
|
+
if (!immediate) return undefined;
|
|
225
|
+
const waitForData = () =>
|
|
226
|
+
!options.lazy && entry.result.data.value === null
|
|
227
|
+
? entry.settled()
|
|
228
|
+
: undefined;
|
|
229
|
+
if (entry.running()) return waitForData();
|
|
230
|
+
if (!idle) {
|
|
231
|
+
// Loaded before: shown at once and read again in the background, except
|
|
232
|
+
// while the page the server rendered hydrates with the server's data, or
|
|
233
|
+
// when another caller of the page has just read it.
|
|
234
|
+
if (!runtime.hydrating && !entry.justReadForAnother()) void entry.load();
|
|
235
|
+
return waitForData();
|
|
236
|
+
}
|
|
237
|
+
// The server rendered the default in place of this data: the browser's
|
|
238
|
+
// first render must show it too, before the request leaves.
|
|
239
|
+
if (runtime.hydrating && (options.lazy || !onServer)) {
|
|
240
|
+
if (inComponent) onMounted(() => void entry.load());
|
|
241
|
+
else entry.schedule();
|
|
242
|
+
} else if (options.lazy) entry.schedule();
|
|
243
|
+
else void entry.load();
|
|
244
|
+
return !options.lazy && entry.result.pending.value
|
|
245
|
+
? entry.settled()
|
|
246
|
+
: undefined;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
interface AsyncDataConsumer<T> extends AsyncDataBindingHandle {
|
|
250
|
+
entry: AsyncDataEntry<T>;
|
|
251
|
+
/** Whether no other component showed the key when this one came. */
|
|
252
|
+
alone: boolean;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Binds a caller to the data of its key. A `null` key has no data to load:
|
|
257
|
+
* the caller holds its default, and nothing runs until it has a key.
|
|
258
|
+
*/
|
|
259
|
+
function attachAsyncData<T>(
|
|
260
|
+
runtime: DmsFrontendRuntime,
|
|
261
|
+
key: string | null | undefined,
|
|
262
|
+
handler: DmsAsyncDataHandler<T>,
|
|
263
|
+
options: UseAsyncDataOptions<T>,
|
|
264
|
+
inComponent: boolean,
|
|
265
|
+
): AsyncDataConsumer<T> {
|
|
266
|
+
const entry = asyncDataEntry(runtime, key ?? undefined, options, inComponent);
|
|
267
|
+
const alone = entry.unused();
|
|
268
|
+
if (key === null) return { entry, alone, unbind() {} };
|
|
269
|
+
return { entry, alone, ...entry.bind({ handler, options }) };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Moves a caller to the data of each key its reactive key takes, and returns
|
|
274
|
+
* refs that follow it there. Until the new key's data is in, the previous
|
|
275
|
+
* data stays on screen rather than the default flashing in its place: the
|
|
276
|
+
* caller's refs show it, and `placeholder` says so, while the new key's own
|
|
277
|
+
* data stays its own, so a run that fails or that a switch away aborts never
|
|
278
|
+
* leaves it another key's data. A key loaded before shows its own data at
|
|
279
|
+
* once, and is read again in the background.
|
|
280
|
+
*
|
|
281
|
+
* A new key is read where the first one would have been. A server reads it
|
|
282
|
+
* neither for `server: false` nor for `immediate: false`, and otherwise only
|
|
283
|
+
* while its render still waits for the caller: until the prefetch of the
|
|
284
|
+
* caller's component, or, outside a component, as a call made then. Past
|
|
285
|
+
* that point the server renders the default and the browser reads the key. A
|
|
286
|
+
* browser hydrating the server's HTML reads a key the server did not render
|
|
287
|
+
* once the component is mounted, so that its first render matches the HTML.
|
|
288
|
+
*/
|
|
289
|
+
function followAsyncDataKey<T>(
|
|
290
|
+
runtime: DmsFrontendRuntime,
|
|
291
|
+
consumer: ShallowRef<AsyncDataConsumer<T>>,
|
|
292
|
+
keySource: MaybeRefOrGetter<DmsAsyncDataKey>,
|
|
293
|
+
handler: DmsAsyncDataHandler<T>,
|
|
294
|
+
options: UseAsyncDataOptions<T>,
|
|
295
|
+
inComponent: boolean,
|
|
296
|
+
serverWaits: () => boolean,
|
|
297
|
+
): DmsAsyncData<T> {
|
|
298
|
+
// A read held back until the hydrating component is mounted.
|
|
299
|
+
let held = false;
|
|
300
|
+
let mounted = !(inComponent && runtime.hydrating);
|
|
301
|
+
if (!mounted)
|
|
302
|
+
onMounted(() => {
|
|
303
|
+
mounted = true;
|
|
304
|
+
const { entry } = consumer.value;
|
|
305
|
+
if (held && entry.result.status.value === "idle") void entry.load();
|
|
306
|
+
held = false;
|
|
307
|
+
});
|
|
308
|
+
const startOnServer = (entry: AsyncDataEntry<T>) => {
|
|
309
|
+
if (
|
|
310
|
+
options.server === false ||
|
|
311
|
+
options.immediate === false ||
|
|
312
|
+
!serverWaits()
|
|
313
|
+
)
|
|
314
|
+
return;
|
|
315
|
+
void entry.load();
|
|
316
|
+
if (!inComponent) runtime.serverPrefetches.add(entry.settled());
|
|
317
|
+
};
|
|
318
|
+
const current = () => consumer.value.entry.result;
|
|
319
|
+
// The previous key's data, shown until the current key settles.
|
|
320
|
+
const carried = shallowRef<{ value: T | null } | undefined>();
|
|
321
|
+
const settled = (result: DmsAsyncData<T>) =>
|
|
322
|
+
result.status.value === "success" || result.status.value === "error";
|
|
323
|
+
const shownData = (): T | null => {
|
|
324
|
+
const result = current();
|
|
325
|
+
return carried.value && !settled(result)
|
|
326
|
+
? carried.value.value
|
|
327
|
+
: result.data.value;
|
|
328
|
+
};
|
|
329
|
+
watch(
|
|
330
|
+
() => settled(current()),
|
|
331
|
+
(done) => {
|
|
332
|
+
if (done) carried.value = undefined;
|
|
333
|
+
},
|
|
334
|
+
{ flush: "sync" },
|
|
335
|
+
);
|
|
336
|
+
// Data written to the current key from elsewhere (`useDmsData`, another
|
|
337
|
+
// caller of the key) is its own: it replaces what is carried.
|
|
338
|
+
watch(
|
|
339
|
+
() => [current(), current().data.value] as const,
|
|
340
|
+
([result], [previous]) => {
|
|
341
|
+
if (result === previous) carried.value = undefined;
|
|
342
|
+
},
|
|
343
|
+
{ flush: "sync" },
|
|
344
|
+
);
|
|
345
|
+
watch(
|
|
346
|
+
() => toValue(keySource) ?? null,
|
|
347
|
+
(key) => {
|
|
348
|
+
const previous = current();
|
|
349
|
+
const shown = shownData();
|
|
350
|
+
const wasRunning = previous.pending.value;
|
|
351
|
+
// Past the setup, the server's data for the new key, if it rendered
|
|
352
|
+
// any, is taken over at once.
|
|
353
|
+
const next = attachAsyncData(runtime, key, handler, options, false);
|
|
354
|
+
const { entry } = next;
|
|
355
|
+
const carry =
|
|
356
|
+
next.alone && entry.result.status.value === "idle" && shown !== null;
|
|
357
|
+
// Unbound first: the previous key's run, aborted, settles it, which
|
|
358
|
+
// must not drop what is carried to the next key.
|
|
359
|
+
consumer.value.unbind();
|
|
360
|
+
consumer.value = next;
|
|
361
|
+
carried.value = carry ? { value: shown } : undefined;
|
|
362
|
+
held = false;
|
|
363
|
+
// `watch: false` keeps the caller on the key it names, without a run.
|
|
364
|
+
if (key === null || options.watch === false) return;
|
|
365
|
+
if (runtime.isServer) return startOnServer(entry);
|
|
366
|
+
const wanted =
|
|
367
|
+
options.immediate !== false || shown !== null || wasRunning;
|
|
368
|
+
if (!wanted) return;
|
|
369
|
+
if (!mounted) held = true;
|
|
370
|
+
else if (!inComponent && runtime.hydrating) entry.schedule();
|
|
371
|
+
else void entry.load();
|
|
372
|
+
},
|
|
373
|
+
{ flush: "sync" },
|
|
374
|
+
);
|
|
375
|
+
const through = <V>(read: (result: DmsAsyncData<T>) => Ref<V>): Ref<V> =>
|
|
376
|
+
computed({
|
|
377
|
+
get: () => read(current()).value,
|
|
378
|
+
set: (value) => {
|
|
379
|
+
read(current()).value = value;
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
return {
|
|
383
|
+
data: computed({
|
|
384
|
+
get: shownData,
|
|
385
|
+
set: (value) => {
|
|
386
|
+
carried.value = undefined;
|
|
387
|
+
current().data.value = value;
|
|
388
|
+
},
|
|
389
|
+
}),
|
|
390
|
+
key: computed(() => current().key.value),
|
|
391
|
+
placeholder: computed(
|
|
392
|
+
() => carried.value !== undefined && !settled(current()),
|
|
393
|
+
),
|
|
394
|
+
error: through((result) => result.error),
|
|
395
|
+
pending: computed(() => current().pending.value),
|
|
396
|
+
status: through((result) => result.status),
|
|
397
|
+
execute: (refreshOptions) => current().refresh(refreshOptions),
|
|
398
|
+
refresh: (refreshOptions) => current().refresh(refreshOptions),
|
|
399
|
+
clear: () => {
|
|
400
|
+
carried.value = undefined;
|
|
401
|
+
current().clear();
|
|
402
|
+
},
|
|
403
|
+
};
|
|
404
|
+
}
|