@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,320 @@
|
|
|
1
|
+
import { computed, type Ref, ref, shallowRef } from "vue";
|
|
2
|
+
import {
|
|
3
|
+
type DmsAsyncData,
|
|
4
|
+
type DmsAsyncDataDedupe,
|
|
5
|
+
type DmsAsyncDataHandler,
|
|
6
|
+
type DmsAsyncDataRefreshOptions,
|
|
7
|
+
type DmsAsyncDataStatus,
|
|
8
|
+
type UseAsyncDataOptions,
|
|
9
|
+
} from "./types";
|
|
10
|
+
import { type DmsFrontendRuntime } from "./module-registration";
|
|
11
|
+
import { createDmsError } from "./dms-error";
|
|
12
|
+
|
|
13
|
+
function pickValue<T>(value: unknown, keys?: string[]): T {
|
|
14
|
+
if (!keys || typeof value !== "object" || value === null) return value as T;
|
|
15
|
+
return Object.fromEntries(
|
|
16
|
+
keys
|
|
17
|
+
.filter((key) => key in value)
|
|
18
|
+
.map((key) => [key, Reflect.get(value, key)]),
|
|
19
|
+
) as T;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface AsyncDataBinding<T> {
|
|
23
|
+
handler: DmsAsyncDataHandler<T>;
|
|
24
|
+
options: UseAsyncDataOptions<T>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface AsyncDataRun {
|
|
28
|
+
promise: Promise<void>;
|
|
29
|
+
controller: AbortController;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface AsyncDataBindingHandle {
|
|
33
|
+
unbind(): void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** What a key holds before any run of the browser's own. */
|
|
37
|
+
interface AsyncDataInitialState<T> {
|
|
38
|
+
data: T | null;
|
|
39
|
+
error: unknown;
|
|
40
|
+
status: DmsAsyncDataStatus;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** What the components showing one key share: its data and its runs. */
|
|
44
|
+
export interface AsyncDataEntry<T> {
|
|
45
|
+
result: DmsAsyncData<T>;
|
|
46
|
+
bind(binding: AsyncDataBinding<T>): AsyncDataBindingHandle;
|
|
47
|
+
/** A run: the one in flight, or a new one. */
|
|
48
|
+
load(): Promise<void>;
|
|
49
|
+
/** The first run, once the current turn is over. */
|
|
50
|
+
schedule(): void;
|
|
51
|
+
/** Resolves once no run is in flight, including the runs that replace it. */
|
|
52
|
+
settled(): Promise<void>;
|
|
53
|
+
unused(): boolean;
|
|
54
|
+
/** When a caller last stopped showing the data (`asyncDataClock`). */
|
|
55
|
+
lastShown(): number;
|
|
56
|
+
/**
|
|
57
|
+
* Whether another caller shows the data and its last run answered within
|
|
58
|
+
* `JOIN_WITHOUT_READ_MS`: a caller joining it then reads nothing again.
|
|
59
|
+
*/
|
|
60
|
+
justReadForAnother(): boolean;
|
|
61
|
+
running(): boolean;
|
|
62
|
+
/** `refreshDmsPageData`'s run, for data a caller shows and opted into. */
|
|
63
|
+
pageRefresh(): Promise<void> | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* Holds the server's state back, as pending, until the next turn or
|
|
66
|
+
* `adoptNow`, whichever comes first.
|
|
67
|
+
*/
|
|
68
|
+
adoptLater(state: AsyncDataInitialState<T>): void;
|
|
69
|
+
adoptNow(): void;
|
|
70
|
+
/** Resolves once the state held back is adopted; nothing when none is. */
|
|
71
|
+
adoption(): Promise<void> | undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const DEFAULT_DEDUPE: DmsAsyncDataDedupe = "cancel";
|
|
75
|
+
/**
|
|
76
|
+
* How many keys no component shows any more a browser keeps, the most
|
|
77
|
+
* recently shown, for a return to them to show their data at once.
|
|
78
|
+
*/
|
|
79
|
+
export const MAX_UNUSED_ASYNC_DATA = 50;
|
|
80
|
+
/**
|
|
81
|
+
* How long after a run answered a caller that joins a key another caller
|
|
82
|
+
* shows takes the data as it is, instead of reading it again: two components
|
|
83
|
+
* of one page sharing a read mount a moment apart.
|
|
84
|
+
*/
|
|
85
|
+
const JOIN_WITHOUT_READ_MS = 2000;
|
|
86
|
+
|
|
87
|
+
export const asyncDataEntries = new WeakMap<
|
|
88
|
+
DmsAsyncData<unknown>,
|
|
89
|
+
AsyncDataEntry<unknown>
|
|
90
|
+
>();
|
|
91
|
+
|
|
92
|
+
function createAsyncDataEntry<T>(
|
|
93
|
+
runtime: DmsFrontendRuntime,
|
|
94
|
+
key: string | undefined,
|
|
95
|
+
initial: AsyncDataInitialState<T>,
|
|
96
|
+
deep: boolean,
|
|
97
|
+
): AsyncDataEntry<T> {
|
|
98
|
+
const data = (
|
|
99
|
+
deep ? ref(initial.data) : shallowRef(initial.data)
|
|
100
|
+
) as Ref<T | null>;
|
|
101
|
+
const error = ref<unknown>(initial.error);
|
|
102
|
+
const status = ref<DmsAsyncDataStatus>(initial.status);
|
|
103
|
+
const pending = computed(() => status.value === "pending");
|
|
104
|
+
const bindings: AsyncDataBinding<T>[] = [];
|
|
105
|
+
let run: AsyncDataRun | undefined;
|
|
106
|
+
let scheduled: Promise<void> | undefined;
|
|
107
|
+
let heldBack: AsyncDataInitialState<T> | undefined;
|
|
108
|
+
let adoption: Promise<void> | undefined;
|
|
109
|
+
let lastShown = 0;
|
|
110
|
+
// When the last run answered, to spare a caller joining at once a second read.
|
|
111
|
+
let answeredAt = 0;
|
|
112
|
+
const adoptNow = () => {
|
|
113
|
+
if (!heldBack) return;
|
|
114
|
+
data.value = heldBack.data;
|
|
115
|
+
error.value = heldBack.error;
|
|
116
|
+
status.value = heldBack.status;
|
|
117
|
+
heldBack = undefined;
|
|
118
|
+
};
|
|
119
|
+
const handle = (binding: AsyncDataBinding<T>, signal: AbortSignal) => {
|
|
120
|
+
const call = () => binding.handler({ signal });
|
|
121
|
+
return runtime.runAsyncDataHandler
|
|
122
|
+
? runtime.runAsyncDataHandler(call)
|
|
123
|
+
: call();
|
|
124
|
+
};
|
|
125
|
+
const start = (binding: AsyncDataBinding<T>): Promise<void> => {
|
|
126
|
+
heldBack = undefined;
|
|
127
|
+
run?.controller.abort();
|
|
128
|
+
const controller = new AbortController();
|
|
129
|
+
const current: AsyncDataRun = { controller, promise: Promise.resolve() };
|
|
130
|
+
run = current;
|
|
131
|
+
runtime.asyncDataRuns.add(controller);
|
|
132
|
+
// The last failure stays until this run answers, as in Nuxt.
|
|
133
|
+
status.value = "pending";
|
|
134
|
+
// A run another one replaced, or that nobody waits for any more, settles
|
|
135
|
+
// nothing, even when its handler ignores the signal.
|
|
136
|
+
current.promise = (async () => {
|
|
137
|
+
try {
|
|
138
|
+
const value = await handle(binding, controller.signal);
|
|
139
|
+
const transformed = binding.options.transform
|
|
140
|
+
? await binding.options.transform(value, { key })
|
|
141
|
+
: value;
|
|
142
|
+
if (run !== current) return;
|
|
143
|
+
data.value = pickValue<T>(transformed, binding.options.pick);
|
|
144
|
+
error.value = null;
|
|
145
|
+
status.value = "success";
|
|
146
|
+
answeredAt = Date.now();
|
|
147
|
+
} catch (reason) {
|
|
148
|
+
if (run !== current) return;
|
|
149
|
+
// The data of the last success stays on screen next to the failure.
|
|
150
|
+
error.value = reason;
|
|
151
|
+
status.value = "error";
|
|
152
|
+
} finally {
|
|
153
|
+
runtime.asyncDataRuns.delete(controller);
|
|
154
|
+
if (run === current) run = undefined;
|
|
155
|
+
}
|
|
156
|
+
})();
|
|
157
|
+
return current.promise;
|
|
158
|
+
};
|
|
159
|
+
const refresh = (options: DmsAsyncDataRefreshOptions = {}) => {
|
|
160
|
+
const binding = bindings.at(-1);
|
|
161
|
+
if (!binding) return Promise.resolve();
|
|
162
|
+
const dedupe = options.dedupe ?? binding.options.dedupe ?? DEFAULT_DEDUPE;
|
|
163
|
+
return run && dedupe === "defer" ? run.promise : start(binding);
|
|
164
|
+
};
|
|
165
|
+
const load = (): Promise<void> => {
|
|
166
|
+
if (run) return run.promise;
|
|
167
|
+
const binding = bindings.at(-1);
|
|
168
|
+
return binding ? start(binding) : Promise.resolve();
|
|
169
|
+
};
|
|
170
|
+
const result: DmsAsyncData<T> = {
|
|
171
|
+
data,
|
|
172
|
+
key: computed(() => key),
|
|
173
|
+
placeholder: computed(() => false),
|
|
174
|
+
error,
|
|
175
|
+
pending,
|
|
176
|
+
status,
|
|
177
|
+
execute: refresh,
|
|
178
|
+
refresh,
|
|
179
|
+
clear() {
|
|
180
|
+
heldBack = undefined;
|
|
181
|
+
run?.controller.abort();
|
|
182
|
+
run = undefined;
|
|
183
|
+
data.value = bindings.at(-1)?.options.default?.() ?? null;
|
|
184
|
+
error.value = null;
|
|
185
|
+
status.value = "idle";
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
const entry: AsyncDataEntry<T> = {
|
|
189
|
+
result,
|
|
190
|
+
bind(binding) {
|
|
191
|
+
bindings.push(binding);
|
|
192
|
+
runtime.boundAsyncData.add(entry);
|
|
193
|
+
const unbind = () => {
|
|
194
|
+
const index = bindings.indexOf(binding);
|
|
195
|
+
if (index === -1) return;
|
|
196
|
+
bindings.splice(index, 1);
|
|
197
|
+
if (bindings.length > 0) return;
|
|
198
|
+
lastShown = ++runtime.asyncDataClock;
|
|
199
|
+
runtime.boundAsyncData.delete(entry);
|
|
200
|
+
if (!run) return;
|
|
201
|
+
run.controller.abort();
|
|
202
|
+
run = undefined;
|
|
203
|
+
// Back to what the last answer left.
|
|
204
|
+
status.value =
|
|
205
|
+
error.value != null
|
|
206
|
+
? "error"
|
|
207
|
+
: data.value === null
|
|
208
|
+
? "idle"
|
|
209
|
+
: "success";
|
|
210
|
+
};
|
|
211
|
+
return { unbind };
|
|
212
|
+
},
|
|
213
|
+
load,
|
|
214
|
+
schedule() {
|
|
215
|
+
// Started after the caller's turn, an explicit refresh in that turn
|
|
216
|
+
// replaces this first run instead of cancelling it mid-request.
|
|
217
|
+
scheduled ??= Promise.resolve().then(() => {
|
|
218
|
+
scheduled = undefined;
|
|
219
|
+
return load();
|
|
220
|
+
});
|
|
221
|
+
},
|
|
222
|
+
async settled() {
|
|
223
|
+
await scheduled;
|
|
224
|
+
await adoption;
|
|
225
|
+
while (run) await run.promise;
|
|
226
|
+
},
|
|
227
|
+
unused: () => bindings.length === 0,
|
|
228
|
+
lastShown: () => lastShown,
|
|
229
|
+
justReadForAnother: () =>
|
|
230
|
+
bindings.length > 1 && Date.now() - answeredAt < JOIN_WITHOUT_READ_MS,
|
|
231
|
+
running: () => run !== undefined,
|
|
232
|
+
pageRefresh() {
|
|
233
|
+
const wanted = bindings.some(
|
|
234
|
+
(binding) => binding.options.refreshOnPageRefresh !== false,
|
|
235
|
+
);
|
|
236
|
+
// Data a caller waits to `execute()` stays unread.
|
|
237
|
+
if (!wanted || (status.value === "idle" && !run)) return undefined;
|
|
238
|
+
return refresh();
|
|
239
|
+
},
|
|
240
|
+
adoptLater(state) {
|
|
241
|
+
heldBack = state;
|
|
242
|
+
status.value = "pending";
|
|
243
|
+
adoption = Promise.resolve().then(adoptNow);
|
|
244
|
+
},
|
|
245
|
+
adoptNow,
|
|
246
|
+
adoption: () => (heldBack ? adoption : undefined),
|
|
247
|
+
};
|
|
248
|
+
return entry;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* The state the server's render left a key in, if it rendered the key: its
|
|
253
|
+
* data, or its failure next to the default it rendered.
|
|
254
|
+
*/
|
|
255
|
+
function serverAsyncDataState<T>(
|
|
256
|
+
runtime: DmsFrontendRuntime,
|
|
257
|
+
key: string,
|
|
258
|
+
fallback: AsyncDataInitialState<T>,
|
|
259
|
+
): AsyncDataInitialState<T> | undefined {
|
|
260
|
+
if (runtime.hydratedAsyncData.has(key))
|
|
261
|
+
return {
|
|
262
|
+
data: runtime.hydratedAsyncData.get(key) as T,
|
|
263
|
+
error: null,
|
|
264
|
+
status: "success",
|
|
265
|
+
};
|
|
266
|
+
const failure = runtime.hydratedAsyncErrors.get(key);
|
|
267
|
+
if (!failure) return undefined;
|
|
268
|
+
return {
|
|
269
|
+
data: fallback.data,
|
|
270
|
+
error: createDmsError(failure),
|
|
271
|
+
status: "error",
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* The key an entry is stored and transferred under: the caller's key, in
|
|
277
|
+
* the runtime's scope (`setDmsAsyncDataScope`).
|
|
278
|
+
*/
|
|
279
|
+
export function scopedKey(runtime: DmsFrontendRuntime, key: string): string {
|
|
280
|
+
return runtime.asyncDataScope === undefined
|
|
281
|
+
? key
|
|
282
|
+
: `${runtime.asyncDataScope}|${key}`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function asyncDataEntry<T>(
|
|
286
|
+
runtime: DmsFrontendRuntime,
|
|
287
|
+
key: string | undefined,
|
|
288
|
+
options: UseAsyncDataOptions<T>,
|
|
289
|
+
inComponent: boolean,
|
|
290
|
+
): AsyncDataEntry<T> {
|
|
291
|
+
const stored = key === undefined ? undefined : scopedKey(runtime, key);
|
|
292
|
+
const shared =
|
|
293
|
+
stored === undefined ? undefined : runtime.asyncData.get(stored);
|
|
294
|
+
if (shared) return asyncDataEntries.get(shared) as AsyncDataEntry<T>;
|
|
295
|
+
const fallback: AsyncDataInitialState<T> = {
|
|
296
|
+
data: options.default?.() ?? null,
|
|
297
|
+
error: null,
|
|
298
|
+
status: "idle",
|
|
299
|
+
};
|
|
300
|
+
const server =
|
|
301
|
+
stored === undefined
|
|
302
|
+
? undefined
|
|
303
|
+
: serverAsyncDataState<T>(runtime, stored, fallback);
|
|
304
|
+
const entry = createAsyncDataEntry<T>(
|
|
305
|
+
runtime,
|
|
306
|
+
key,
|
|
307
|
+
server && !inComponent ? server : fallback,
|
|
308
|
+
options.deep === true,
|
|
309
|
+
);
|
|
310
|
+
// A component's setup saw the server's run in flight, not its result: the
|
|
311
|
+
// browser holds the result back as long, see `startAsyncData`.
|
|
312
|
+
if (server && inComponent) entry.adoptLater(server);
|
|
313
|
+
if (stored === undefined) return entry;
|
|
314
|
+
runtime.hydratedAsyncData.delete(stored);
|
|
315
|
+
runtime.hydratedAsyncErrors.delete(stored);
|
|
316
|
+
const result = entry.result as DmsAsyncData<unknown>;
|
|
317
|
+
runtime.asyncData.set(stored, result);
|
|
318
|
+
asyncDataEntries.set(result, entry as AsyncDataEntry<unknown>);
|
|
319
|
+
return entry;
|
|
320
|
+
}
|