@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.
Files changed (103) hide show
  1. package/README.md +9 -1
  2. package/dist/cancellation.js +2 -1
  3. package/dist/config.js +11 -7
  4. package/dist/derived-outputs.js +6 -113
  5. package/dist/dev-modules.js +3 -2
  6. package/dist/discovery.js +19 -12
  7. package/dist/fs-sync.js +1 -2
  8. package/dist/internal/guards.d.ts +5 -0
  9. package/dist/internal/guards.js +22 -0
  10. package/dist/internal/locale-catalogs.d.ts +6 -0
  11. package/dist/internal/locale-catalogs.js +115 -0
  12. package/dist/layer-aliases.js +2 -1
  13. package/dist/layers.js +2 -1
  14. package/dist/locale-files.js +9 -4
  15. package/dist/manifest-secrets.js +11 -4
  16. package/dist/manifest.js +11 -5
  17. package/dist/materialize.js +6 -5
  18. package/dist/module-set.js +2 -4
  19. package/dist/ports.js +8 -1
  20. package/dist/private-values.js +26 -17
  21. package/dist/project-modules.js +17 -11
  22. package/dist/renderer-range.js +2 -1
  23. package/dist/server-process.js +17 -16
  24. package/dist/update-check.js +23 -16
  25. package/dist/verify-source-runner.js +2 -2
  26. package/dist/workspace-setup.js +1 -2
  27. package/dist/workspace.js +11 -10
  28. package/package.json +7 -4
  29. package/skills/dms-frontend/SKILL.md +1 -1
  30. package/skills/dms-frontend/cases.json +77 -0
  31. package/templates/vue/app-runtime.ts +88 -7
  32. package/templates/vue/client-chunking.mjs +0 -5
  33. package/templates/vue/client-server-guard.mjs +10 -13
  34. package/templates/vue/entry-weight.mjs +12 -14
  35. package/templates/vue/frontend-module.ts +159 -3563
  36. package/templates/vue/frontend-runtime/async-data-entry.ts +320 -0
  37. package/templates/vue/frontend-runtime/async-data.ts +404 -0
  38. package/templates/vue/frontend-runtime/components.ts +386 -0
  39. package/templates/vue/frontend-runtime/cookies.ts +125 -0
  40. package/templates/vue/frontend-runtime/data-access.ts +132 -0
  41. package/templates/vue/frontend-runtime/dms-error.ts +11 -0
  42. package/templates/vue/frontend-runtime/errors.ts +145 -0
  43. package/templates/vue/frontend-runtime/fetch.ts +306 -0
  44. package/templates/vue/frontend-runtime/module-registration.ts +247 -0
  45. package/templates/vue/frontend-runtime/paged-data.ts +191 -0
  46. package/templates/vue/frontend-runtime/payload.ts +172 -0
  47. package/templates/vue/frontend-runtime/route.ts +328 -0
  48. package/templates/vue/frontend-runtime/sdk.ts +300 -0
  49. package/templates/vue/frontend-runtime/state.ts +167 -0
  50. package/templates/vue/frontend-runtime/types.ts +361 -0
  51. package/templates/vue/frontend-runtime/ui.ts +247 -0
  52. package/templates/vue/globals.d.ts +5 -0
  53. package/templates/vue/head-order.mjs +11 -13
  54. package/templates/vue/icon-bundle.ts +31 -0
  55. package/templates/vue/message-subset.ts +65 -0
  56. package/templates/vue/network-error.ts +4 -0
  57. package/templates/vue/prepare-types.mjs +0 -8
  58. package/templates/vue/server/auth/backend.mjs +4 -2
  59. package/templates/vue/server/auth/establish-endpoints.mjs +14 -9
  60. package/templates/vue/server/auth/session.mjs +4 -3
  61. package/templates/vue/server/auth-hooks.mjs +6 -6
  62. package/templates/vue/server/backend-proxies.mjs +6 -6
  63. package/templates/vue/server/backend-response.mjs +15 -14
  64. package/templates/vue/server/built-files.mjs +10 -8
  65. package/templates/vue/server/cli-report.mjs +7 -5
  66. package/templates/vue/server/client-error.mjs +11 -8
  67. package/templates/vue/server/client-manifest.mjs +9 -8
  68. package/templates/vue/server/content.mjs +2 -1
  69. package/templates/vue/server/dev-private-files.mjs +20 -15
  70. package/templates/vue/server/dev-styles.mjs +22 -25
  71. package/templates/vue/server/email-bundle-watch.mjs +0 -12
  72. package/templates/vue/server/email.mjs +20 -15
  73. package/templates/vue/server/favicon.mjs +12 -11
  74. package/templates/vue/server/homepage.mjs +8 -10
  75. package/templates/vue/server/icons.mjs +18 -9
  76. package/templates/vue/server/in-place-render.mjs +6 -8
  77. package/templates/vue/server/listen-host.mjs +2 -2
  78. package/templates/vue/server/module-event.mjs +5 -5
  79. package/templates/vue/server/module-routes.mjs +11 -7
  80. package/templates/vue/server/module-server.mjs +8 -7
  81. package/templates/vue/server/module-testing.mjs +7 -7
  82. package/templates/vue/server/page-props.mjs +14 -14
  83. package/templates/vue/server/path-patterns.mjs +4 -4
  84. package/templates/vue/server/proxy-compression.mjs +21 -13
  85. package/templates/vue/server/proxy-headers.mjs +5 -3
  86. package/templates/vue/server/proxy-redirects.mjs +7 -6
  87. package/templates/vue/server/proxy-transfers.mjs +15 -11
  88. package/templates/vue/server/public-endpoints.mjs +27 -19
  89. package/templates/vue/server/public-options.mjs +9 -7
  90. package/templates/vue/server/request-failure.mjs +4 -6
  91. package/templates/vue/server/request-routing.mjs +8 -11
  92. package/templates/vue/server/request-target.mjs +6 -6
  93. package/templates/vue/server/route-rules.mjs +7 -5
  94. package/templates/vue/server/static-files.mjs +19 -10
  95. package/templates/vue/server/web-body.mjs +3 -4
  96. package/templates/vue/server.mjs +32 -19
  97. package/templates/vue/sfc-global-types.mjs +27 -24
  98. package/templates/vue/ssr-renderer.ts +10 -1
  99. package/templates/vue/ui-inertia-page.mjs +7 -7
  100. package/templates/vue/ui-kbd.mjs +10 -10
  101. package/templates/vue/ui-lazy-icons.mjs +44 -0
  102. package/templates/vue/ui-request-state.mjs +9 -9
  103. package/templates/vue/vite.config.ts +2 -0
@@ -0,0 +1,300 @@
1
+ import { router as inertiaRouter } from "@inertiajs/vue3";
2
+ import { defu } from "defu";
3
+ import {
4
+ type App,
5
+ type AppConfig,
6
+ type Component,
7
+ type ComponentInternalInstance,
8
+ camelize,
9
+ capitalize,
10
+ computed,
11
+ getCurrentInstance,
12
+ type InjectionKey,
13
+ inject,
14
+ } from "vue";
15
+ import type { Composer } from "vue-i18n";
16
+ import {
17
+ appConfigChanges,
18
+ assignInPlace,
19
+ mergeLayerAppConfigs,
20
+ withAppConfigChanges,
21
+ } from "../layer-app-config";
22
+ import {
23
+ type DmsFrontendEntry,
24
+ type DmsFrontendModuleRegistration,
25
+ type DmsFrontendRegistry,
26
+ type DmsFrontendRuntime,
27
+ type DmsFrontendSdk,
28
+ type DmsLayerAppConfig,
29
+ PLUGIN_ORDER,
30
+ } from "./module-registration";
31
+ import {
32
+ type DmsAppContext,
33
+ type DmsComponentPreloader,
34
+ type DmsI18n,
35
+ type DmsLocaleLoader,
36
+ type PublicRuntimeConfig,
37
+ } from "./types";
38
+ import {
39
+ normalizeDmsName,
40
+ normalizeDmsPageKey,
41
+ registerDmsComponentEntry,
42
+ } from "./components";
43
+ import {
44
+ appConfig,
45
+ createDmsFrontendRegistry,
46
+ forgetBrowserRuntime,
47
+ registry,
48
+ replaceRegistry,
49
+ runtimeConfig,
50
+ } from "./state";
51
+ import {
52
+ DmsClientOnly,
53
+ DmsLink,
54
+ failPage,
55
+ SETUP_FUNCTION_ERROR,
56
+ withinFailedSetup,
57
+ } from "./ui";
58
+ import { LOCALE_COOKIE, LOCALE_COOKIE_MAX_AGE, writeCookie } from "./cookies";
59
+
60
+ // What the last setup of the modules changed in the layers' app configs, which
61
+ // a development update of those configs keeps: see `updateDmsAppConfig`.
62
+ let appConfigSetupChanges: Record<string, unknown> = {};
63
+
64
+ export function useDmsInjection<T>(
65
+ key: string | InjectionKey<T>,
66
+ ): T | undefined {
67
+ return inject(key as InjectionKey<T>, undefined);
68
+ }
69
+
70
+ function registerDmsFrontendEntry(
71
+ entries: Map<string, DmsFrontendEntry>,
72
+ name: string,
73
+ component: Component,
74
+ preload?: DmsComponentPreloader,
75
+ ): void {
76
+ if (!entries.has(name)) entries.set(name, { component, preload });
77
+ }
78
+
79
+ /** `name` behind `prefix`, as a PascalCase component name. */
80
+ function prefixedComponentName(prefix: string, name: string): string {
81
+ return prefix ? `${prefix}${capitalize(camelize(name))}` : name;
82
+ }
83
+
84
+ function createSdk(
85
+ target: DmsFrontendRegistry,
86
+ {
87
+ name: module,
88
+ module: { componentPrefix = "" },
89
+ options,
90
+ }: DmsFrontendModuleRegistration,
91
+ ): DmsFrontendSdk {
92
+ return {
93
+ options,
94
+ registerComponent: (name, component, { prefix = true } = {}) =>
95
+ registerDmsComponentEntry(target.components, {
96
+ name: prefix ? prefixedComponentName(componentPrefix, name) : name,
97
+ component,
98
+ module,
99
+ unprefixed: !prefix,
100
+ }),
101
+ registerPage: (name, component, preload) => {
102
+ const key = normalizeDmsPageKey(name);
103
+ registerDmsFrontendEntry(target.pages, key, component, preload);
104
+ },
105
+ registerDynamicPage: (name, component, preload) =>
106
+ registerDmsFrontendEntry(target.dynamicPages, name, component, preload),
107
+ registerLayout: (name, component, preload) => {
108
+ const key = normalizeDmsName(name);
109
+ registerDmsFrontendEntry(target.layouts, key, component, preload);
110
+ },
111
+ registerErrorPage: (component, preload) => {
112
+ target.errorPage ??= { component, preload };
113
+ },
114
+ registerPlugin: (setup, options = {}) =>
115
+ target.pluginSetups.push({
116
+ setup,
117
+ clientOnly: options.clientOnly ?? false,
118
+ enforce: options.enforce ?? "default",
119
+ }),
120
+ registerMiddleware: (name, handler, options = {}) => {
121
+ if (!target.namedMiddleware.has(name))
122
+ target.namedMiddleware.set(name, handler);
123
+ if (options.global) target.middleware.push(handler);
124
+ if (options.parallel) target.parallelMiddleware.add(handler);
125
+ },
126
+ registerAccessRedirect: (code, path) => {
127
+ if (!target.accessRedirects.has(code))
128
+ target.accessRedirects.set(code, path);
129
+ },
130
+ provide: (key, value) => {
131
+ if (!target.injections.has(key)) target.injections.set(key, value);
132
+ },
133
+ use: (plugin) => target.plugins.push(plugin),
134
+ };
135
+ }
136
+
137
+ /**
138
+ * Sets the frontend modules up in their generated priority order, into a
139
+ * registry of their own that replaces the previous one once complete.
140
+ *
141
+ * The browser and a production server set the modules up once. A development
142
+ * server sets them up again each time Vite evaluates the server renderer anew
143
+ * after a layer changes, but keeps this module and its state: registering into
144
+ * the previous registry, the new components lost their names to the ones they
145
+ * replace, so the server kept rendering the old ones, and every list gained the
146
+ * same entries once more. A render still in progress keeps reading a complete
147
+ * registry: the new one is only published once every module is set up.
148
+ *
149
+ * `useDmsAppConfig()` holds the layers' app configs merged, highest precedence
150
+ * first, before the first module is set up: a project's layer overrides the
151
+ * DMS's, which fills in the rest. A setup may still read and extend it.
152
+ */
153
+ export async function setupFrontendModules(
154
+ registrations: DmsFrontendModuleRegistration[],
155
+ layerAppConfigs: DmsLayerAppConfig[] = [],
156
+ ): Promise<void> {
157
+ const next = createDmsFrontendRegistry();
158
+ const configs = layerAppConfigs.map(({ config }) => config);
159
+ // Modules read and extend these two while they are set up, so they restart
160
+ // here rather than being published with the registry: the public options
161
+ // empty, the app config from the layers alone.
162
+ runtimeConfig.value.public = {} as PublicRuntimeConfig;
163
+ appConfig.value = mergeLayerAppConfigs(configs);
164
+ for (const registration of registrations) {
165
+ runtimeConfig.value.public = defu(
166
+ runtimeConfig.value.public,
167
+ registration.options.public,
168
+ );
169
+ await registration.module.setup(createSdk(next, registration));
170
+ }
171
+ appConfigSetupChanges = appConfigChanges(
172
+ mergeLayerAppConfigs(configs),
173
+ appConfig.value,
174
+ );
175
+ replaceRegistry(next);
176
+ }
177
+
178
+ /**
179
+ * Development only: applies the layers' app configs, edited while the page
180
+ * is open, to `useDmsAppConfig()`, with what the setup of the modules changed
181
+ * in the previous ones still on top. The object is updated in place, records
182
+ * included, since components hold it: the modules are not set up again.
183
+ */
184
+ export function updateDmsAppConfig(layerAppConfigs: DmsLayerAppConfig[]): void {
185
+ const layered = mergeLayerAppConfigs(
186
+ layerAppConfigs.map(({ config }) => config),
187
+ );
188
+ assignInPlace(
189
+ appConfig.value,
190
+ withAppConfigChanges(layered, appConfigSetupChanges),
191
+ );
192
+ }
193
+
194
+ export async function installDmsPlugins(
195
+ app: App,
196
+ i18n: Composer,
197
+ runtime: DmsFrontendRuntime,
198
+ loadLocaleMessages: DmsLocaleLoader,
199
+ supportedLocales: string[],
200
+ ): Promise<() => Promise<void>> {
201
+ const dmsI18n = i18n as DmsI18n;
202
+ const hooks = new Map<string, Array<() => void | Promise<void>>>();
203
+ const appContext: DmsAppContext = {
204
+ vueApp: app,
205
+ $i18n: dmsI18n,
206
+ provide: (key, value) => {
207
+ app.provide(key, value);
208
+ app.config.globalProperties[`$${key}`] = value;
209
+ },
210
+ runWithContext: (callback) => app.runWithContext(callback),
211
+ hook: (name, callback) =>
212
+ hooks.set(name, [...(hooks.get(name) ?? []), callback]),
213
+ };
214
+ runtime.appContext = appContext;
215
+ if (typeof window !== "undefined" && !runtime.hasNavigationListener) {
216
+ runtime.hasNavigationListener = true;
217
+ const stopNavigationListener = inertiaRouter.on("before", () => {
218
+ runtime.currentError.value = null;
219
+ });
220
+ app.onUnmount(() => {
221
+ stopNavigationListener();
222
+ runtime.hasNavigationListener = false;
223
+ forgetBrowserRuntime(runtime);
224
+ });
225
+ }
226
+ const failedSetups = new WeakSet<ComponentInternalInstance>();
227
+ const warnOutsideFailedSetups: AppConfig["warnHandler"] = (
228
+ message,
229
+ instance,
230
+ trace,
231
+ ) => {
232
+ // The comment Vue renders in place of an `undefined` component has no
233
+ // public instance: the template rendering it is the one that failed.
234
+ const source = instance?.$ ?? getCurrentInstance();
235
+ if (!withinFailedSetup(source, failedSetups))
236
+ console.warn(`[Vue warn]: ${message}${trace ? `\n${trace}` : ""}`);
237
+ };
238
+ app.config.errorHandler = (reason, instance, info) => {
239
+ if (instance && info === SETUP_FUNCTION_ERROR) {
240
+ failedSetups.add(instance.$);
241
+ // From the first failure on only: until then Vue keeps its own output,
242
+ // whose component trace shows the props. An app that already has a
243
+ // handler keeps it. A server renders a single page per app; in the
244
+ // browser the app outlives Inertia visits, so once a page has failed
245
+ // the handler stays for the session and later warnings print as plain
246
+ // text (no inspectable objects), still all of them.
247
+ app.config.warnHandler ??= warnOutsideFailedSetups;
248
+ }
249
+ failPage(runtime, reason);
250
+ };
251
+ app.config.globalProperties.$dms = appContext;
252
+ Reflect.set(app, "$dms", appContext);
253
+ Reflect.set(app, "$i18n", i18n);
254
+ Reflect.set(appContext.vueApp, "$dms", appContext);
255
+ Reflect.set(app.config.globalProperties, "$i18n", i18n);
256
+ dmsI18n.setLocale = async (locale: string) => {
257
+ const messages = await loadLocaleMessages(locale);
258
+ i18n.setLocaleMessage(locale, messages);
259
+ i18n.locale.value = locale;
260
+ if (typeof document !== "undefined" && supportedLocales.includes(locale))
261
+ writeCookie(LOCALE_COOKIE, locale, {
262
+ maxAge: LOCALE_COOKIE_MAX_AGE,
263
+ sameSite: "lax",
264
+ secure: globalThis.location?.protocol === "https:",
265
+ });
266
+ };
267
+ dmsI18n.locales = computed(() =>
268
+ supportedLocales.map((code) => ({
269
+ code,
270
+ name: new Intl.DisplayNames([code], { type: "language" }).of(code),
271
+ })),
272
+ );
273
+ app.component("DmsLink", DmsLink);
274
+ app.component("DmsClientOnly", DmsClientOnly);
275
+ registry.components.forEach(({ component, name }) => {
276
+ app.component(name, component);
277
+ });
278
+ registry.injections.forEach((value, key) => {
279
+ app.provide(key, value);
280
+ });
281
+ registry.plugins.forEach((plugin) => {
282
+ app.use(plugin);
283
+ });
284
+ // Stable: within a group, the modules' priority order stays.
285
+ const pluginSetups = [...registry.pluginSetups].sort(
286
+ (left, right) => PLUGIN_ORDER[left.enforce] - PLUGIN_ORDER[right.enforce],
287
+ );
288
+ for (const registration of pluginSetups) {
289
+ if (registration.clientOnly && typeof window === "undefined") continue;
290
+ await runtime.scope.run(() =>
291
+ app.runWithContext(() => registration.setup(appContext)),
292
+ );
293
+ }
294
+ // What plugins start without awaiting it is still part of the page a
295
+ // server renders: its HTML shows it, so the render waits for it.
296
+ if (runtime.isServer) await Promise.all(runtime.serverPrefetches);
297
+ return async () => {
298
+ for (const callback of hooks.get("app:mounted") ?? []) await callback();
299
+ };
300
+ }
@@ -0,0 +1,167 @@
1
+ import { ofetch } from "ofetch";
2
+ import {
3
+ type App,
4
+ effectScope,
5
+ getCurrentInstance,
6
+ hasInjectionContext,
7
+ type InjectionKey,
8
+ inject,
9
+ onScopeDispose,
10
+ reactive,
11
+ ref,
12
+ shallowReactive,
13
+ } from "vue";
14
+ import {
15
+ type DmsFrontendRegistry,
16
+ type DmsFrontendRuntime,
17
+ type DmsRuntimeHooks,
18
+ } from "./module-registration";
19
+ import {
20
+ type DmsAppContext,
21
+ type DmsRoute,
22
+ type DmsRuntimeConfig,
23
+ } from "./types";
24
+
25
+ /** Creates the empty registry one setup of the frontend modules fills. */
26
+ export function createDmsFrontendRegistry(): DmsFrontendRegistry {
27
+ return {
28
+ components: new Map(),
29
+ pages: new Map(),
30
+ dynamicPages: new Map(),
31
+ layouts: new Map(),
32
+ plugins: [],
33
+ pluginSetups: [],
34
+ middleware: [],
35
+ namedMiddleware: new Map(),
36
+ parallelMiddleware: new WeakSet(),
37
+ accessRedirects: new Map(),
38
+ injections: new Map(),
39
+ };
40
+ }
41
+
42
+ // Replaced, never refilled, by each setup of the modules: see
43
+ // `setupFrontendModules`.
44
+ export let registry = createDmsFrontendRegistry();
45
+ export const runtimeConfig = ref<DmsRuntimeConfig>({
46
+ public: {},
47
+ } as DmsRuntimeConfig);
48
+ export const appConfig = ref<Record<string, unknown>>({});
49
+ const DMS_RUNTIME_KEY: InjectionKey<DmsFrontendRuntime> = Symbol("dms-runtime");
50
+ type DmsRuntimeContext = DmsAppContext["runWithContext"];
51
+ export const runtimeContexts = new WeakMap<
52
+ DmsFrontendRuntime,
53
+ DmsRuntimeContext
54
+ >();
55
+ let serverRuntimeResolver: (() => DmsFrontendRuntime | undefined) | undefined;
56
+ let browserRuntime: DmsFrontendRuntime | undefined;
57
+
58
+ export function replaceRegistry(next: DmsFrontendRegistry): void {
59
+ registry = next;
60
+ }
61
+
62
+ export function forgetBrowserRuntime(runtime: DmsFrontendRuntime): void {
63
+ if (browserRuntime === runtime) browserRuntime = undefined;
64
+ }
65
+
66
+ /** Creates mutable runtime state owned by one Vue application. */
67
+ export function createDmsFrontendRuntime(
68
+ serverFetch?: typeof ofetch,
69
+ hydratedAsyncData?: Record<string, unknown>,
70
+ isServer = false,
71
+ requestCookies?: string,
72
+ ): DmsFrontendRuntime {
73
+ const runtime: DmsFrontendRuntime = {
74
+ requestCookies,
75
+ sharedState: new Map(),
76
+ asyncData: shallowReactive(new Map()),
77
+ hydratedAsyncData: new Map(Object.entries(hydratedAsyncData ?? {})),
78
+ hydratedAsyncErrors: new Map(),
79
+ transferredState: new Set(),
80
+ // A browser handed the server's data, even none, hydrates its HTML.
81
+ hydrating: !isServer && hydratedAsyncData !== undefined,
82
+ serverPrefetches: new Set(),
83
+ asyncDataRuns: new Set(),
84
+ boundAsyncData: new Set(),
85
+ asyncDataClock: 0,
86
+ runtimeHooks: new Map(),
87
+ serverFetch,
88
+ isServer,
89
+ currentError: ref<Error | null>(null),
90
+ route: reactive<DmsRoute>({
91
+ fullPath: "/",
92
+ path: "/",
93
+ query: {},
94
+ params: {},
95
+ meta: {},
96
+ matched: [],
97
+ }),
98
+ hasNavigationListener: false,
99
+ pageLoading: ref(false),
100
+ scope: effectScope(true),
101
+ };
102
+ // A server render stops its scope once it has answered: whatever it still
103
+ // fetches then would reach nobody.
104
+ runtime.scope.run(() =>
105
+ onScopeDispose(() => {
106
+ for (const run of runtime.asyncDataRuns) run.abort();
107
+ }),
108
+ );
109
+ return runtime;
110
+ }
111
+
112
+ /** Makes a runtime available before application plugins are installed. */
113
+ export function provideDmsFrontendRuntime(
114
+ app: App,
115
+ runtime: DmsFrontendRuntime,
116
+ ): void {
117
+ app.provide(DMS_RUNTIME_KEY, runtime);
118
+ runtimeContexts.set(runtime, (callback) => app.runWithContext(callback));
119
+ if (typeof window !== "undefined") browserRuntime = runtime;
120
+ }
121
+
122
+ /** Registers the request-local runtime resolver used by the SSR entry point. */
123
+ export function setDmsServerRuntimeResolver(
124
+ resolver: () => DmsFrontendRuntime | undefined,
125
+ ): void {
126
+ serverRuntimeResolver = resolver;
127
+ }
128
+
129
+ export function useDmsRuntime(): DmsFrontendRuntime {
130
+ const instance = getCurrentInstance();
131
+ const provided = instance?.appContext.provides[DMS_RUNTIME_KEY as symbol] as
132
+ | DmsFrontendRuntime
133
+ | undefined;
134
+ if (provided) return provided;
135
+ const contextual = hasInjectionContext()
136
+ ? inject(DMS_RUNTIME_KEY, undefined)
137
+ : undefined;
138
+ if (contextual) return contextual;
139
+ if (typeof window !== "undefined" && browserRuntime) return browserRuntime;
140
+ const serverRuntime = serverRuntimeResolver?.();
141
+ if (serverRuntime) return serverRuntime;
142
+ throw new Error("DMS runtime is unavailable outside a Vue application");
143
+ }
144
+
145
+ /** Returns lifecycle hooks isolated to the current application runtime. */
146
+ export function useDmsRuntimeHooks(): DmsRuntimeHooks {
147
+ const runtime = useDmsRuntime();
148
+ return {
149
+ hook(name, callback) {
150
+ runtime.runtimeHooks.set(name, [
151
+ ...(runtime.runtimeHooks.get(name) ?? []),
152
+ callback,
153
+ ]);
154
+ return () =>
155
+ runtime.runtimeHooks.set(
156
+ name,
157
+ (runtime.runtimeHooks.get(name) ?? []).filter(
158
+ (entry) => entry !== callback,
159
+ ),
160
+ );
161
+ },
162
+ async callHook(name, ...args) {
163
+ for (const callback of runtime.runtimeHooks.get(name) ?? [])
164
+ await callback(...args);
165
+ },
166
+ };
167
+ }