@antelopejs/dms-frontend 0.3.1 → 0.3.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.
@@ -299,6 +299,24 @@ interface DmsPluginRegistration {
299
299
  clientOnly: boolean;
300
300
  }
301
301
 
302
+ /**
303
+ * What the frontend modules registered through their SDK. Modules are set up
304
+ * by descending priority, so the first one to claim a name keeps it.
305
+ */
306
+ interface DmsFrontendRegistry {
307
+ components: Map<string, DmsComponentRegistration>;
308
+ pages: Map<string, DmsFrontendEntry>;
309
+ dynamicPages: Map<string, DmsFrontendEntry>;
310
+ layouts: Map<string, DmsFrontendEntry>;
311
+ errorPage?: DmsFrontendEntry;
312
+ plugins: Plugin[];
313
+ pluginSetups: DmsPluginRegistration[];
314
+ middleware: DmsMiddleware[];
315
+ namedMiddleware: Map<string, DmsMiddleware>;
316
+ accessRedirects: Map<string, string>;
317
+ injections: Map<string | symbol, unknown>;
318
+ }
319
+
302
320
  export interface DmsFrontendRuntime {
303
321
  sharedState: Map<string, Ref<unknown>>;
304
322
  asyncData: Map<string, DmsAsyncData<unknown>>;
@@ -324,17 +342,25 @@ export interface DmsFrontendRuntime {
324
342
  scope: EffectScope;
325
343
  }
326
344
 
327
- const components = new Map<string, DmsComponentRegistration>();
328
- const pages = new Map<string, DmsFrontendEntry>();
329
- const dynamicPages = new Map<string, DmsFrontendEntry>();
330
- const layouts = new Map<string, DmsFrontendEntry>();
331
- let errorPage: DmsFrontendEntry | undefined;
332
- const plugins: Plugin[] = [];
333
- const pluginSetups: DmsPluginRegistration[] = [];
334
- const middleware: DmsMiddleware[] = [];
335
- const namedMiddleware = new Map<string, DmsMiddleware>();
336
- const accessRedirects = new Map<string, string>();
337
- const injections = new Map<string | symbol, unknown>();
345
+ /** Creates the empty registry one setup of the frontend modules fills. */
346
+ function createDmsFrontendRegistry(): DmsFrontendRegistry {
347
+ return {
348
+ components: new Map(),
349
+ pages: new Map(),
350
+ dynamicPages: new Map(),
351
+ layouts: new Map(),
352
+ plugins: [],
353
+ pluginSetups: [],
354
+ middleware: [],
355
+ namedMiddleware: new Map(),
356
+ accessRedirects: new Map(),
357
+ injections: new Map(),
358
+ };
359
+ }
360
+
361
+ // Replaced, never refilled, by each setup of the modules: see
362
+ // `setupFrontendModules`.
363
+ let registry = createDmsFrontendRegistry();
338
364
  const runtimeConfig = ref<DmsRuntimeConfig>({
339
365
  public: {},
340
366
  } as DmsRuntimeConfig);
@@ -506,9 +532,9 @@ async function runMiddleware(
506
532
  const declared = Array.isArray(to.meta.middleware) ? to.meta.middleware : [];
507
533
  const metadataNames = Object.keys(to.meta).filter((name) => to.meta[name]);
508
534
  const declaredHandlers = [...declared, ...metadataNames]
509
- .map((name) => namedMiddleware.get(String(name)))
535
+ .map((name) => registry.namedMiddleware.get(String(name)))
510
536
  .filter((handler): handler is DmsMiddleware => !!handler);
511
- for (const handler of [...middleware, ...declaredHandlers]) {
537
+ for (const handler of [...registry.middleware, ...declaredHandlers]) {
512
538
  const runWithContext = runtimeContexts.get(runtime);
513
539
  const result = await (runWithContext
514
540
  ? runWithContext(() => handler(to, runtime.route))
@@ -913,8 +939,8 @@ export function addDmsMiddleware(
913
939
  name?: string,
914
940
  isGlobal = false,
915
941
  ): void {
916
- if (isGlobal) middleware.push(handler);
917
- else if (name) namedMiddleware.set(name, handler);
942
+ if (isGlobal) registry.middleware.push(handler);
943
+ else if (name) registry.namedMiddleware.set(name, handler);
918
944
  }
919
945
  export const useHead = useUnhead;
920
946
  export const useSeoMeta = useUnheadSeoMeta;
@@ -1158,13 +1184,20 @@ export function useUserSession<
1158
1184
  };
1159
1185
  }
1160
1186
 
1187
+ function registerDmsComponentEntry(
1188
+ entries: Map<string, DmsComponentRegistration>,
1189
+ name: string,
1190
+ component: Component,
1191
+ ): void {
1192
+ const key = normalizeDmsName(name);
1193
+ if (!entries.has(key)) entries.set(key, { name, component });
1194
+ }
1161
1195
  /** Registers a component unless a higher-priority module already owns its name. */
1162
1196
  export function registerDmsComponent(name: string, component: Component): void {
1163
- const key = normalizeDmsName(name);
1164
- if (!components.has(key)) components.set(key, { name, component });
1197
+ registerDmsComponentEntry(registry.components, name, component);
1165
1198
  }
1166
1199
  export function resolveDmsComponent(name: string): Component | undefined {
1167
- return components.get(normalizeDmsName(name))?.component;
1200
+ return registry.components.get(normalizeDmsName(name))?.component;
1168
1201
  }
1169
1202
  export const getDmsComponent = resolveDmsComponent;
1170
1203
 
@@ -1177,7 +1210,7 @@ export const getDmsComponent = resolveDmsComponent;
1177
1210
  */
1178
1211
  export function trackDmsAsyncComponents(app: App): Set<string> {
1179
1212
  const names = new Map<Component, string>();
1180
- components.forEach(({ component, name }) => {
1213
+ registry.components.forEach(({ component, name }) => {
1181
1214
  if ((component as DmsAsyncComponent).__asyncLoader)
1182
1215
  names.set(component, name);
1183
1216
  });
@@ -1201,12 +1234,17 @@ export function trackDmsAsyncComponents(app: App): Set<string> {
1201
1234
  * a process-wide hook registry and only unregisters there, so every server
1202
1235
  * render of the dashboard sidebar stayed reachable, its whole component tree
1203
1236
  * and page props with it, until the server ran out of heap.
1237
+ *
1238
+ * The component's scope is read through `getCurrentScope`: Vue makes it the
1239
+ * active scope before it calls `beforeCreate`, and the instance's own `scope`
1240
+ * field is internal, absent from the typings a generated workspace checks.
1204
1241
  */
1205
1242
  export function trackDmsServerScopes(app: App): () => void {
1206
1243
  const scopes: EffectScope[] = [];
1207
1244
  app.mixin({
1208
- beforeCreate(this: ComponentPublicInstance) {
1209
- scopes.push(this.$.scope);
1245
+ beforeCreate() {
1246
+ const scope = getCurrentScope();
1247
+ if (scope) scopes.push(scope);
1210
1248
  },
1211
1249
  });
1212
1250
  return () => {
@@ -1288,30 +1326,35 @@ function findDmsPageEntry(props: DmsPageProps): DmsFrontendEntry | undefined {
1288
1326
  props.page.componentName,
1289
1327
  props.page.route?.fullSlug,
1290
1328
  props.path,
1291
- ].find((candidate) => candidate && pages.has(normalizeDmsPageKey(candidate)));
1329
+ ].find(
1330
+ (candidate) =>
1331
+ candidate && registry.pages.has(normalizeDmsPageKey(candidate)),
1332
+ );
1292
1333
  return name
1293
- ? pages.get(normalizeDmsPageKey(name))
1294
- : dynamicPages.get(CATCH_ALL_PAGE_KEY);
1334
+ ? registry.pages.get(normalizeDmsPageKey(name))
1335
+ : registry.dynamicPages.get(CATCH_ALL_PAGE_KEY);
1295
1336
  }
1296
1337
 
1297
1338
  /** The path a frontend module registered for a typed backend refusal. */
1298
1339
  export function resolveDmsAccessRedirect(code: string): string | undefined {
1299
- return accessRedirects.get(code);
1340
+ return registry.accessRedirects.get(code);
1300
1341
  }
1301
1342
  export function hasDmsPage(name: string): boolean {
1302
- return pages.has(normalizeDmsPageKey(name));
1343
+ return registry.pages.has(normalizeDmsPageKey(name));
1303
1344
  }
1304
1345
  export function getDmsPage(props: DmsPageProps): Component | undefined {
1305
- if (props.error) return errorPage?.component;
1346
+ if (props.error) return registry.errorPage?.component;
1306
1347
  return findDmsPageEntry(props)?.component;
1307
1348
  }
1308
1349
  export function getDmsDynamicPage(name = "default"): Component | undefined {
1309
- return dynamicPages.get(name)?.component;
1350
+ return registry.dynamicPages.get(name)?.component;
1310
1351
  }
1311
1352
  export function getDmsLayout(props: DmsPageProps): Component | undefined {
1312
1353
  const layout = props.page.layout;
1313
1354
  const name = layout?.layout?.componentName ?? layout?.componentName;
1314
- return name ? layouts.get(normalizeDmsName(name))?.component : undefined;
1355
+ return name
1356
+ ? registry.layouts.get(normalizeDmsName(name))?.component
1357
+ : undefined;
1315
1358
  }
1316
1359
  export function getDmsLayoutProps(
1317
1360
  props: DmsPageProps,
@@ -1357,18 +1400,18 @@ export function hydrateDmsPageProps(props: DmsPageProps, url?: string): void {
1357
1400
  layoutUrl && props.page.layout ? { [layoutUrl]: props.page.layout } : {};
1358
1401
  }
1359
1402
  export function getDmsErrorPage(): Component | undefined {
1360
- return errorPage?.component;
1403
+ return registry.errorPage?.component;
1361
1404
  }
1362
1405
 
1363
1406
  export async function preloadDmsPage(props: DmsPageProps): Promise<void> {
1364
1407
  const layoutName =
1365
1408
  props.page.layout?.layout?.componentName ??
1366
1409
  props.page.layout?.componentName;
1367
- const page = findDmsPageEntry(props) ?? dynamicPages.get("default");
1410
+ const page = findDmsPageEntry(props) ?? registry.dynamicPages.get("default");
1368
1411
  const layout = layoutName
1369
- ? layouts.get(normalizeDmsName(layoutName))
1412
+ ? registry.layouts.get(normalizeDmsName(layoutName))
1370
1413
  : undefined;
1371
- const entries = props.error ? [errorPage] : [page, layout];
1414
+ const entries = props.error ? [registry.errorPage] : [page, layout];
1372
1415
  // `preload` only warms the module; the async wrapper stays unresolved until
1373
1416
  // its own loader runs. Hydrating an unresolved wrapper defers it, and the
1374
1417
  // first parent render (Inertia's initial swap) then makes Vue drop the
@@ -1389,58 +1432,85 @@ export function useDmsInjection<T>(
1389
1432
  }
1390
1433
 
1391
1434
  function registerDmsFrontendEntry(
1392
- registry: Map<string, DmsFrontendEntry>,
1435
+ entries: Map<string, DmsFrontendEntry>,
1393
1436
  name: string,
1394
1437
  component: Component,
1395
1438
  preload?: DmsComponentPreloader,
1396
1439
  ): void {
1397
- if (!registry.has(name)) registry.set(name, { component, preload });
1440
+ if (!entries.has(name)) entries.set(name, { component, preload });
1398
1441
  }
1399
1442
 
1400
- function createSdk(options: DmsModuleOptions): DmsFrontendSdk {
1443
+ function createSdk(
1444
+ target: DmsFrontendRegistry,
1445
+ options: DmsModuleOptions,
1446
+ ): DmsFrontendSdk {
1401
1447
  return {
1402
1448
  options,
1403
- registerComponent: registerDmsComponent,
1449
+ registerComponent: (name, component) =>
1450
+ registerDmsComponentEntry(target.components, name, component),
1404
1451
  registerPage: (name, component, preload) => {
1405
1452
  const key = normalizeDmsPageKey(name);
1406
- registerDmsFrontendEntry(pages, key, component, preload);
1453
+ registerDmsFrontendEntry(target.pages, key, component, preload);
1407
1454
  },
1408
1455
  registerDynamicPage: (name, component, preload) =>
1409
- registerDmsFrontendEntry(dynamicPages, name, component, preload),
1456
+ registerDmsFrontendEntry(target.dynamicPages, name, component, preload),
1410
1457
  registerLayout: (name, component, preload) => {
1411
1458
  const key = normalizeDmsName(name);
1412
- registerDmsFrontendEntry(layouts, key, component, preload);
1459
+ registerDmsFrontendEntry(target.layouts, key, component, preload);
1413
1460
  },
1414
1461
  registerErrorPage: (component, preload) => {
1415
- errorPage ??= { component, preload };
1462
+ target.errorPage ??= { component, preload };
1416
1463
  },
1417
1464
  registerPlugin: (setup, options = {}) =>
1418
- pluginSetups.push({ setup, clientOnly: options.clientOnly ?? false }),
1465
+ target.pluginSetups.push({
1466
+ setup,
1467
+ clientOnly: options.clientOnly ?? false,
1468
+ }),
1419
1469
  registerMiddleware: (name, handler, options = {}) => {
1420
- if (!namedMiddleware.has(name)) namedMiddleware.set(name, handler);
1421
- if (options.global) middleware.push(handler);
1470
+ if (!target.namedMiddleware.has(name))
1471
+ target.namedMiddleware.set(name, handler);
1472
+ if (options.global) target.middleware.push(handler);
1422
1473
  },
1423
1474
  registerAccessRedirect: (code, path) => {
1424
- if (!accessRedirects.has(code)) accessRedirects.set(code, path);
1475
+ if (!target.accessRedirects.has(code))
1476
+ target.accessRedirects.set(code, path);
1425
1477
  },
1426
1478
  provide: (key, value) => {
1427
- if (!injections.has(key)) injections.set(key, value);
1479
+ if (!target.injections.has(key)) target.injections.set(key, value);
1428
1480
  },
1429
- use: (plugin) => plugins.push(plugin),
1481
+ use: (plugin) => target.plugins.push(plugin),
1430
1482
  };
1431
1483
  }
1432
1484
 
1433
- /** Initializes frontend adapters in their generated priority order. */
1485
+ /**
1486
+ * Sets the frontend modules up in their generated priority order, into a
1487
+ * registry of their own that replaces the previous one once complete.
1488
+ *
1489
+ * The browser and a production server set the modules up once. A development
1490
+ * server sets them up again each time Vite evaluates the server renderer anew
1491
+ * after a layer changes, but keeps this module and its state: registering into
1492
+ * the previous registry, the new components lost their names to the ones they
1493
+ * replace, so the server kept rendering the old ones, and every list gained the
1494
+ * same entries once more. A render still in progress keeps reading a complete
1495
+ * registry: the new one is only published once every module is set up.
1496
+ */
1434
1497
  export async function setupFrontendModules(
1435
1498
  registrations: DmsFrontendModuleRegistration[],
1436
1499
  ): Promise<void> {
1500
+ const next = createDmsFrontendRegistry();
1501
+ // Modules read and extend these two while they are set up (the DMS layout
1502
+ // merges its app config into `useDmsAppConfig()`), so they restart empty
1503
+ // here rather than being published with the registry.
1504
+ runtimeConfig.value.public = {} as PublicRuntimeConfig;
1505
+ appConfig.value = {};
1437
1506
  for (const registration of registrations) {
1438
1507
  runtimeConfig.value.public = defu(
1439
1508
  runtimeConfig.value.public,
1440
1509
  registration.options.public,
1441
1510
  );
1442
- await registration.module.setup(createSdk(registration.options));
1511
+ await registration.module.setup(createSdk(next, registration.options));
1443
1512
  }
1513
+ registry = next;
1444
1514
  }
1445
1515
 
1446
1516
  export async function installDmsPlugins(
@@ -1497,16 +1567,16 @@ export async function installDmsPlugins(
1497
1567
  );
1498
1568
  app.component("DmsLink", DmsLink);
1499
1569
  app.component("DmsClientOnly", DmsClientOnly);
1500
- components.forEach(({ component, name }) => {
1570
+ registry.components.forEach(({ component, name }) => {
1501
1571
  app.component(name, component);
1502
1572
  });
1503
- injections.forEach((value, key) => {
1573
+ registry.injections.forEach((value, key) => {
1504
1574
  app.provide(key, value);
1505
1575
  });
1506
- plugins.forEach((plugin) => {
1576
+ registry.plugins.forEach((plugin) => {
1507
1577
  app.use(plugin);
1508
1578
  });
1509
- for (const registration of pluginSetups) {
1579
+ for (const registration of registry.pluginSetups) {
1510
1580
  if (registration.clientOnly && typeof window === "undefined") continue;
1511
1581
  await runtime.scope.run(() =>
1512
1582
  app.runWithContext(() => registration.setup(appContext)),