@holo-js/core 0.2.5 → 0.3.0

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.
@@ -12,7 +12,7 @@ import {
12
12
  // src/portable/registry.ts
13
13
  import { readFile } from "node:fs/promises";
14
14
  import { resolve } from "node:path";
15
- import { DEFAULT_HOLO_PROJECT_PATHS } from "@holo-js/db";
15
+ import { DEFAULT_HOLO_PROJECT_PATHS } from "@holo-js/kernel";
16
16
  function isRecord(value) {
17
17
  return !!value && typeof value === "object" && !Array.isArray(value);
18
18
  }
@@ -118,11 +118,31 @@ var registryInternals = {
118
118
  normalizeLegacyGeneratedProjectRegistry
119
119
  };
120
120
 
121
+ // src/portable/renderingRuntime.ts
122
+ function getHoloRenderingRuntime() {
123
+ const runtime = globalThis;
124
+ runtime.__holoRenderingRuntime__ ??= {};
125
+ return runtime.__holoRenderingRuntime__;
126
+ }
127
+ function configureHoloRenderingRuntime(bindings) {
128
+ getHoloRenderingRuntime().renderView = bindings?.renderView;
129
+ }
130
+ function resetHoloRenderingRuntime() {
131
+ getHoloRenderingRuntime().renderView = void 0;
132
+ }
133
+ function restoreHoloRenderingRuntime(renderView) {
134
+ if (renderView) {
135
+ configureHoloRenderingRuntime({ renderView });
136
+ return;
137
+ }
138
+ resetHoloRenderingRuntime();
139
+ }
140
+
121
141
  // src/portable/holo.ts
122
- import { AsyncLocalStorage } from "node:async_hooks";
123
- import { existsSync } from "node:fs";
142
+ import { existsSync as existsSync2 } from "node:fs";
124
143
  import { createHash, createHmac } from "node:crypto";
125
- import { resolve as resolve3 } from "node:path";
144
+ import { resolve as resolve5 } from "node:path";
145
+ import { createRuntimeLifecycle } from "@holo-js/kernel";
126
146
  import {
127
147
  config as globalConfig,
128
148
  configureConfigRuntime,
@@ -132,10 +152,13 @@ import {
132
152
  useConfig as globalUseConfig
133
153
  } from "@holo-js/config";
134
154
  import {
155
+ connectionAsyncContext,
135
156
  configureDB,
136
- DB,
157
+ DB as DB2,
137
158
  Entity,
138
- resetDB
159
+ registerDatabaseDriverFactory,
160
+ resetDB,
161
+ unregisterDatabaseDriverFactory
139
162
  } from "@holo-js/db";
140
163
 
141
164
  // src/runtimeModule.ts
@@ -326,8 +349,8 @@ var runtimeModuleInternals = {
326
349
  // src/storageRuntime.ts
327
350
  import { mkdir as mkdir2, readFile as readFile2, readdir, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
328
351
  import { dirname, isAbsolute, join as join2, relative, resolve as resolve2, sep, win32 } from "node:path";
329
- async function importOptionalModule(specifier) {
330
- return importOptionalRuntimeModule(specifier);
352
+ async function importOptionalModule(specifier, projectRoot) {
353
+ return importOptionalRuntimeModule(specifier, projectRoot ? { projectRoot } : {});
331
354
  }
332
355
  function resolveStorageKeyPath(root, key) {
333
356
  if (isAbsolute(key) || win32.isAbsolute(key)) {
@@ -427,8 +450,8 @@ function createFileStorageBackend(root) {
427
450
  }
428
451
  };
429
452
  }
430
- async function createS3StorageBackend(disk) {
431
- const storageS3 = await importOptionalModule("@holo-js/storage/runtime/drivers/s3");
453
+ async function createS3StorageBackend(projectRoot, disk) {
454
+ const storageS3 = await importOptionalModule("@holo-js/storage-s3", projectRoot);
432
455
  if (!storageS3) {
433
456
  throw new Error("[@holo-js/core] Storage config references an s3 disk but @holo-js/storage-s3 is not installed.");
434
457
  }
@@ -455,7 +478,7 @@ async function configurePlainNodeStorageRuntime(projectRoot, loadedConfig) {
455
478
  });
456
479
  const backends = /* @__PURE__ */ new Map();
457
480
  for (const [diskName, disk] of Object.entries(normalizedStorage.disks)) {
458
- const backend = disk.driver === "s3" ? await createS3StorageBackend(disk) : createFileStorageBackend(resolve2(projectRoot, disk.root));
481
+ const backend = disk.driver === "s3" ? await createS3StorageBackend(projectRoot, disk) : createFileStorageBackend(resolve2(projectRoot, disk.root));
459
482
  backends.set(diskName, backend);
460
483
  }
461
484
  storageRuntime.configureStorageRuntime({
@@ -481,150 +504,231 @@ var storageRuntimeInternals = {
481
504
  importOptionalModule
482
505
  };
483
506
 
484
- // src/portable/holo.ts
507
+ // src/portable/discoveryRuntime.ts
508
+ import { existsSync } from "node:fs";
509
+ import { resolve as resolve3 } from "node:path";
485
510
  async function preloadGeneratedSchemaModule(projectRoot, registry) {
486
511
  const entry = registry?.paths.generatedSchema;
487
- if (!entry) {
488
- return;
489
- }
512
+ if (!entry) return;
490
513
  const expectedTarget = resolve3(projectRoot, entry);
491
- if (!existsSync(expectedTarget)) {
492
- return;
493
- }
514
+ if (!existsSync(expectedTarget)) return;
494
515
  try {
495
516
  await importBundledRuntimeModule(projectRoot, entry);
496
517
  } catch (error) {
497
518
  if (error instanceof Error && /Cannot find module|ERR_MODULE_NOT_FOUND|MODULE_NOT_FOUND|Failed to load url|Failed to load /.test(error.message)) {
498
- const message = error.message;
499
- const failedTarget = message.match(/Cannot find module '([^']+)'|Cannot find package '([^']+)'|Failed to load url ([^ ]+)|Failed to load ([^ ]+)\./)?.slice(1).find((value) => typeof value === "string");
500
- if (failedTarget === expectedTarget) {
501
- return;
502
- }
519
+ const failedTarget = error.message.match(/Cannot find module '([^']+)'|Cannot find package '([^']+)'|Failed to load url ([^ ]+)|Failed to load ([^ ]+)\./)?.slice(1).find((value) => typeof value === "string");
520
+ if (failedTarget === expectedTarget) return;
503
521
  }
504
522
  throw error;
505
523
  }
506
524
  }
507
525
  async function preloadDiscoveredModelModules(projectRoot, registry) {
508
- if (!registry || registry.models.length === 0) {
509
- return;
510
- }
526
+ if (!registry || registry.models.length === 0) return;
511
527
  for (const entry of registry.models) {
512
528
  const sourcePath = resolve3(projectRoot, entry.sourcePath);
513
- if (!existsSync(sourcePath)) {
514
- continue;
515
- }
516
- await importBundledRuntimeModule(projectRoot, sourcePath);
529
+ if (existsSync(sourcePath)) await importBundledRuntimeModule(projectRoot, sourcePath);
517
530
  }
518
531
  }
519
- function closeSessionRedisAdapter(adapter) {
520
- return adapter.disconnect?.() || adapter.close?.();
532
+
533
+ // src/portable/configRuntime.ts
534
+ var featureConfigContributionSpecifiers = [
535
+ "@holo-js/auth/config",
536
+ "@holo-js/broadcast/config",
537
+ "@holo-js/cache/config",
538
+ "@holo-js/mail/config",
539
+ "@holo-js/media/config",
540
+ "@holo-js/notifications/config",
541
+ "@holo-js/queue/config",
542
+ "@holo-js/security/config",
543
+ "@holo-js/session/config",
544
+ "@holo-js/storage/config"
545
+ ];
546
+ async function loadDirectFeatureConfigContribution(specifier) {
547
+ switch (specifier) {
548
+ case "@holo-js/auth/config":
549
+ await import("@holo-js/auth/config");
550
+ break;
551
+ case "@holo-js/broadcast/config":
552
+ await import("@holo-js/broadcast/config");
553
+ break;
554
+ case "@holo-js/cache/config":
555
+ await import("@holo-js/cache/config");
556
+ break;
557
+ case "@holo-js/mail/config":
558
+ await import("@holo-js/mail/config");
559
+ break;
560
+ case "@holo-js/media/config":
561
+ await import("@holo-js/media/config");
562
+ break;
563
+ case "@holo-js/notifications/config":
564
+ await import("@holo-js/notifications/config");
565
+ break;
566
+ case "@holo-js/queue/config":
567
+ await import("@holo-js/queue/config");
568
+ break;
569
+ case "@holo-js/security/config":
570
+ await import("@holo-js/security/config");
571
+ break;
572
+ case "@holo-js/session/config":
573
+ await import("@holo-js/session/config");
574
+ break;
575
+ case "@holo-js/storage/config":
576
+ await import("@holo-js/storage/config");
577
+ break;
578
+ }
579
+ }
580
+ var defaultLoaders = {
581
+ loadDirect: loadDirectFeatureConfigContribution,
582
+ async loadOptional(specifier, projectRoot) {
583
+ return await importOptionalRuntimeModule(specifier, { projectRoot });
584
+ }
585
+ };
586
+ async function loadInstalledFeatureConfigContributions(projectRoot, loaders = defaultLoaders) {
587
+ for (const specifier of featureConfigContributionSpecifiers) {
588
+ try {
589
+ await loaders.loadDirect(specifier);
590
+ } catch {
591
+ const loaded = await loaders.loadOptional(specifier, projectRoot);
592
+ if (!loaded) continue;
593
+ }
594
+ }
521
595
  }
522
- var CORE_BROADCAST_PUBLISHER_MARKER = /* @__PURE__ */ Symbol.for("holo-js.core.broadcast.publisher");
523
- var frameworkBuildEnvKey = "HOLO_INTERNAL_FRAMEWORK_BUILD";
524
- function shouldBootRuntimeServices(processEnv = process.env) {
525
- return processEnv[frameworkBuildEnvKey] !== "1";
596
+
597
+ // src/portable/authPersistence.ts
598
+ function normalizeDateValue(value) {
599
+ return value instanceof Date ? value : new Date(String(value));
526
600
  }
527
- function getRuntimeState() {
528
- const runtime = globalThis;
529
- runtime.__holoRuntime__ ??= {};
530
- return runtime.__holoRuntime__;
601
+ function normalizeJsonValue(value) {
602
+ if (typeof value !== "string") return value;
603
+ try {
604
+ return JSON.parse(value);
605
+ } catch {
606
+ return value;
607
+ }
531
608
  }
532
- function configureHoloRenderingRuntime(bindings) {
533
- getRuntimeState().renderView = bindings?.renderView;
609
+ function normalizeStoredUserId(value) {
610
+ return typeof value === "number" ? value : String(value);
534
611
  }
535
- function resetHoloRenderingRuntime() {
536
- getRuntimeState().renderView = void 0;
612
+ function normalizeAccessTokenRecord(row) {
613
+ const abilities = normalizeJsonValue(row.abilities);
614
+ return Object.freeze({
615
+ id: String(row.id),
616
+ provider: String(row.provider),
617
+ userId: normalizeStoredUserId(row.user_id),
618
+ name: String(row.name),
619
+ abilities: Array.isArray(abilities) ? Object.freeze([...abilities]) : Object.freeze([]),
620
+ tokenHash: String(row.token_hash),
621
+ createdAt: normalizeDateValue(row.created_at),
622
+ lastUsedAt: row.last_used_at ? normalizeDateValue(row.last_used_at) : void 0,
623
+ expiresAt: row.expires_at ? normalizeDateValue(row.expires_at) : null
624
+ });
537
625
  }
538
- function restoreHoloRenderingRuntime(renderView) {
539
- if (renderView) {
540
- configureHoloRenderingRuntime({
541
- renderView
542
- });
543
- return;
544
- }
545
- resetHoloRenderingRuntime();
626
+ function serializeAccessTokenRecord(record) {
627
+ return {
628
+ id: record.id,
629
+ provider: record.provider,
630
+ user_id: String(record.userId),
631
+ name: record.name,
632
+ abilities: JSON.stringify(record.abilities),
633
+ token_hash: record.tokenHash,
634
+ created_at: record.createdAt.toISOString(),
635
+ last_used_at: record.lastUsedAt?.toISOString() ?? null,
636
+ expires_at: record.expiresAt?.toISOString() ?? null,
637
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
638
+ };
546
639
  }
547
- function snapshotOptionalSubsystemRuntimeBindings() {
548
- const state = getRuntimeState();
549
- const runtime = globalThis;
640
+ function normalizeEmailVerificationTokenRecord(row) {
550
641
  return Object.freeze({
551
- ...runtime.__holoMailRuntime__?.bindings ? { mail: runtime.__holoMailRuntime__.bindings } : {},
552
- ...runtime.__holoNotificationsRuntime__?.bindings ? { notifications: runtime.__holoNotificationsRuntime__.bindings } : {},
553
- ...runtime.__holoBroadcastRuntime__?.bindings ? { broadcast: runtime.__holoBroadcastRuntime__.bindings } : {},
554
- /* v8 ignore start -- this snapshot branch only applies when restoring externally managed session adapters across runtime swaps */
555
- ...state.sessionRedisAdapters ? {
556
- session: Object.freeze({
557
- sessionRedisAdapters: state.sessionRedisAdapters
558
- })
559
- } : {},
560
- /* v8 ignore stop */
561
- ...runtime.__holoSecurityRuntime__?.bindings || state.securityRedisAdapter || typeof state.securityRateLimitStoreManaged !== "undefined" ? {
562
- security: Object.freeze({
563
- ...runtime.__holoSecurityRuntime__?.bindings ? { bindings: runtime.__holoSecurityRuntime__.bindings } : {},
564
- ...state.securityRedisAdapter ? { securityRedisAdapter: state.securityRedisAdapter } : {},
565
- ...typeof state.securityRateLimitStoreManaged !== "undefined" ? { securityRateLimitStoreManaged: state.securityRateLimitStoreManaged } : {}
566
- })
567
- } : {}
642
+ id: String(row.id),
643
+ provider: String(row.provider),
644
+ userId: normalizeStoredUserId(row.user_id),
645
+ email: String(row.email),
646
+ tokenHash: String(row.token_hash),
647
+ createdAt: normalizeDateValue(row.created_at),
648
+ expiresAt: normalizeDateValue(row.expires_at)
568
649
  });
569
650
  }
570
- function restoreOptionalSubsystemRuntimeBindings(bindings) {
571
- const state = getRuntimeState();
572
- const runtime = globalThis;
573
- if (bindings.mail || runtime.__holoMailRuntime__) {
574
- runtime.__holoMailRuntime__ ??= {};
575
- runtime.__holoMailRuntime__.bindings = bindings.mail;
576
- }
577
- if (bindings.notifications || runtime.__holoNotificationsRuntime__) {
578
- runtime.__holoNotificationsRuntime__ ??= {};
579
- runtime.__holoNotificationsRuntime__.bindings = bindings.notifications;
580
- }
581
- if (bindings.broadcast || runtime.__holoBroadcastRuntime__) {
582
- runtime.__holoBroadcastRuntime__ ??= {};
583
- runtime.__holoBroadcastRuntime__.bindings = bindings.broadcast;
584
- }
585
- state.sessionRedisAdapters = bindings.session?.sessionRedisAdapters;
586
- if (bindings.security || runtime.__holoSecurityRuntime__) {
587
- runtime.__holoSecurityRuntime__ ??= {};
588
- runtime.__holoSecurityRuntime__.bindings = bindings.security?.bindings;
589
- state.securityRedisAdapter = bindings.security?.securityRedisAdapter;
590
- state.securityRateLimitStoreManaged = bindings.security?.securityRateLimitStoreManaged;
591
- }
592
- }
593
- var BROADCAST_PUBLISH_TIMEOUT_MS = 1e4;
594
- async function importOptionalModule2(specifier, options = {}) {
595
- return importOptionalRuntimeModule(specifier, options);
651
+ function serializeEmailVerificationTokenRecord(record) {
652
+ return {
653
+ id: record.id,
654
+ provider: record.provider,
655
+ user_id: String(record.userId),
656
+ email: record.email,
657
+ token_hash: record.tokenHash,
658
+ created_at: record.createdAt.toISOString(),
659
+ expires_at: record.expiresAt.toISOString(),
660
+ used_at: null,
661
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
662
+ };
596
663
  }
597
- var portableRuntimeModuleInternals = {
598
- importOptionalModule: importOptionalModule2
599
- };
600
- function hasLoadedConfigFile(loadedConfig, configName) {
601
- return loadedConfig.loadedFiles.some((filePath) => {
602
- const normalizedPath = filePath.replaceAll("\\", "/");
603
- return normalizedPath.endsWith(`/config/${configName}.ts`) || normalizedPath.endsWith(`/config/${configName}.mts`) || normalizedPath.endsWith(`/config/${configName}.js`) || normalizedPath.endsWith(`/config/${configName}.mjs`) || normalizedPath.endsWith(`/config/${configName}.cts`) || normalizedPath.endsWith(`/config/${configName}.cjs`);
664
+ function normalizePasswordResetTokenRecord(row) {
665
+ return Object.freeze({
666
+ id: String(row.id),
667
+ provider: typeof row.provider === "string" ? row.provider : "users",
668
+ email: String(row.email),
669
+ table: typeof row.__holo_table === "string" ? row.__holo_table : void 0,
670
+ tokenHash: String(row.token_hash),
671
+ createdAt: normalizeDateValue(row.created_at),
672
+ expiresAt: normalizeDateValue(row.expires_at)
604
673
  });
605
674
  }
606
- function queueConfigUsesDatabaseDriver(loadedConfig) {
607
- return Object.values(loadedConfig.queue.connections).some((connection) => connection.driver === "database");
675
+ function serializePasswordResetTokenRecord(record) {
676
+ return {
677
+ id: record.id,
678
+ provider: record.provider,
679
+ email: record.email,
680
+ token_hash: record.tokenHash,
681
+ created_at: record.createdAt.toISOString(),
682
+ expires_at: record.expiresAt.toISOString(),
683
+ used_at: null,
684
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
685
+ };
608
686
  }
609
- function queueConfigUsesDatabaseBackedFailedStore(loadedConfig) {
610
- return loadedConfig.queue.failed !== false;
687
+
688
+ // src/portable/pluginRuntime.ts
689
+ import { resolve as resolve4 } from "node:path";
690
+ import {
691
+ loadHoloPluginBootModules,
692
+ loadHoloPluginDefinitions
693
+ } from "@holo-js/kernel";
694
+ function isRecord2(value) {
695
+ return !!value && typeof value === "object" && !Array.isArray(value);
611
696
  }
612
- function registryHasJobs(registry) {
613
- return (registry?.jobs.length ?? 0) > 0;
697
+ var bootedHoloPluginModules = /* @__PURE__ */ new Set();
698
+ function resolveHoloPluginBootKey(projectRoot, bootModule) {
699
+ return [resolve4(projectRoot), bootModule.plugin.packageName, bootModule.runtime].join("\0");
614
700
  }
615
- function registryHasEvents(registry) {
616
- return (registry?.events.length ?? 0) > 0 || (registry?.listeners.length ?? 0) > 0;
701
+ function resolveLoadedPluginNames(loadedConfig) {
702
+ return loadedConfig.app.plugins;
617
703
  }
618
- function authConfigUsesSocialProviders(loadedConfig) {
619
- return Object.keys(loadedConfig.auth.social).length > 0;
704
+ async function bootConfiguredHoloPluginModule(projectRoot, loadedConfig, bootModule) {
705
+ const bootKey = resolveHoloPluginBootKey(projectRoot, bootModule);
706
+ if (bootedHoloPluginModules.has(bootKey)) return;
707
+ const candidate = isRecord2(bootModule.module) && typeof bootModule.module.default !== "undefined" ? bootModule.module.default : bootModule.module;
708
+ if (typeof candidate !== "function") return;
709
+ await candidate({ projectRoot, config: loadedConfig });
710
+ bootedHoloPluginModules.add(bootKey);
620
711
  }
621
- function authConfigUsesWorkosProviders(loadedConfig) {
622
- return Object.entries(loadedConfig.auth.workos).some(([name, provider]) => name !== "provider" && name !== "identityStore" && typeof provider === "object" && provider !== null);
712
+ function resetBootedHoloPluginModules() {
713
+ bootedHoloPluginModules.clear();
623
714
  }
624
- function authConfigUsesClerkProviders(loadedConfig) {
625
- return Object.entries(loadedConfig.auth.clerk).some(([name, provider]) => name !== "provider" && name !== "identityStore" && typeof provider === "object" && provider !== null);
715
+ async function loadConfiguredHoloPluginDefinitions(projectRoot, pluginNames) {
716
+ const normalizedNames = [...new Set(pluginNames.map((name) => name.trim()).filter(Boolean))];
717
+ return loadHoloPluginDefinitions(projectRoot, normalizedNames, { moduleVersion: String(Date.now()) });
626
718
  }
627
- var HOLO_AUTH_PROVIDER_MARKER = /* @__PURE__ */ Symbol.for("holo-js.auth.provider");
719
+ async function loadConfiguredHoloPluginBootModules(projectRoot, plugins) {
720
+ return loadHoloPluginBootModules(projectRoot, plugins, { moduleVersion: String(Date.now()) });
721
+ }
722
+ function mergeQueueRuntimeDriverFactories(...sources) {
723
+ const factories = /* @__PURE__ */ new Map();
724
+ for (const source of sources) {
725
+ for (const factory of source ?? []) factories.set(factory.driver, factory);
726
+ }
727
+ return Object.freeze([...factories.values()]);
728
+ }
729
+
730
+ // src/portable/authRequestContext.ts
731
+ import { AsyncLocalStorage } from "node:async_hooks";
628
732
  function attachAuthRequestAccessors(context, accessors) {
629
733
  return Object.freeze({
630
734
  ...context,
@@ -656,141 +760,503 @@ function createRequestAwareAuthContext(context, accessors) {
656
760
  return resolveRequestContext().redirectResponse?.(url, status);
657
761
  },
658
762
  setRequestAccessors(nextAccessors) {
659
- requestAccessorStorage.enterWith({
660
- accessors: nextAccessors
661
- });
763
+ requestAccessorStorage.enterWith({ accessors: nextAccessors });
662
764
  },
663
- async runWithRequestAccessors(nextAccessors, callback) {
664
- return await requestAccessorStorage.run({
665
- accessors: nextAccessors
666
- }, callback);
765
+ runWithRequestAccessors(nextAccessors, callback) {
766
+ return requestAccessorStorage.run(
767
+ { accessors: nextAccessors },
768
+ () => context.run ? context.run(callback) : callback()
769
+ );
667
770
  }
668
771
  });
669
772
  }
670
- async function loadQueueModule(required = false) {
671
- const queueModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/queue");
672
- if (!queueModule && required) {
673
- throw new Error("[@holo-js/core] Queue support requires @holo-js/queue to be installed.");
674
- }
675
- return queueModule;
676
- }
677
- async function loadQueueDbModule() {
678
- return portableRuntimeModuleInternals.importOptionalModule("@holo-js/queue-db");
773
+
774
+ // src/portable/recordPersistence.ts
775
+ function normalizeDateLike(value) {
776
+ return value instanceof Date ? value : new Date(String(value));
679
777
  }
680
- async function loadCacheModule(required = false, projectRoot) {
681
- const cacheModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/cache", {
682
- projectRoot
778
+ function normalizeSessionRecordFromRow(row) {
779
+ const decodedData = (() => {
780
+ if (row.data && typeof row.data === "object") return row.data;
781
+ if (typeof row.data !== "string") return {};
782
+ try {
783
+ const parsed = JSON.parse(row.data);
784
+ return parsed && typeof parsed === "object" ? parsed : {};
785
+ } catch {
786
+ return {};
787
+ }
788
+ })();
789
+ return Object.freeze({
790
+ id: String(row.id),
791
+ store: typeof row.store === "string" ? row.store : "database",
792
+ data: Object.freeze(decodedData),
793
+ createdAt: normalizeDateLike(row.created_at),
794
+ lastActivityAt: normalizeDateLike(row.last_activity_at),
795
+ expiresAt: normalizeDateLike(row.expires_at),
796
+ rememberTokenHash: typeof row.remember_token_hash === "string" ? row.remember_token_hash : void 0
683
797
  });
684
- if (!cacheModule && required) {
685
- throw new Error("[@holo-js/core] Cache support requires @holo-js/cache to be installed.");
686
- }
687
- return cacheModule;
688
- }
689
- function resetCacheRuntimeGlobalsFallback() {
690
- const runtime = globalThis;
691
- if (runtime.__holoCacheRuntime__) {
692
- runtime.__holoCacheRuntime__.bindings = void 0;
693
- }
694
- if (runtime.__holoCacheQueryBridge__) {
695
- runtime.__holoCacheQueryBridge__.dependencyIndex = void 0;
696
- }
697
- if (runtime.__holoDbQueryCacheBridge__) {
698
- runtime.__holoDbQueryCacheBridge__.bridge = void 0;
699
- }
700
- }
701
- async function loadEventsModule(required = false) {
702
- const eventsModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/events");
703
- if (!eventsModule && required) {
704
- throw new Error("[@holo-js/core] Events support requires @holo-js/events to be installed.");
705
- }
706
- return eventsModule;
707
- }
708
- async function loadSessionModule(required = false) {
709
- const sessionModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/session");
710
- if (!sessionModule && required) {
711
- throw new Error("[@holo-js/core] Session support requires @holo-js/session to be installed.");
712
- }
713
- return sessionModule;
714
- }
715
- async function loadSecurityModule(required = false) {
716
- const securityModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/security");
717
- if (!securityModule && required) {
718
- throw new Error("[@holo-js/core] Security support requires @holo-js/security to be installed.");
719
- }
720
- return securityModule;
721
- }
722
- async function loadSecurityRedisAdapterModule(required = false) {
723
- const securityRedisAdapterModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/security/drivers/redis-adapter");
724
- if (!securityRedisAdapterModule && required) {
725
- throw new Error("[@holo-js/core] Redis-backed security rate limits require @holo-js/security/drivers/redis-adapter to be installed.");
726
- }
727
- return securityRedisAdapterModule;
728
- }
729
- async function loadSessionRedisAdapterModule(required = false) {
730
- const sessionRedisAdapterModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/session/drivers/redis-adapter");
731
- if (!sessionRedisAdapterModule && required) {
732
- throw new Error("[@holo-js/core] Redis-backed session stores require @holo-js/session/drivers/redis-adapter to be installed.");
733
- }
734
- return sessionRedisAdapterModule;
735
798
  }
736
- async function loadNotificationsModule(required = false) {
737
- const notificationsModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/notifications");
738
- if (!notificationsModule && required) {
739
- throw new Error("[@holo-js/core] Notifications support requires @holo-js/notifications to be installed.");
740
- }
741
- return notificationsModule;
799
+ function serializeSessionRecordForRow(record) {
800
+ return {
801
+ id: record.id,
802
+ store: record.store,
803
+ data: JSON.stringify(record.data ?? {}),
804
+ created_at: record.createdAt.toISOString(),
805
+ last_activity_at: record.lastActivityAt.toISOString(),
806
+ expires_at: record.expiresAt.toISOString(),
807
+ invalidated_at: null,
808
+ remember_token_hash: record.rememberTokenHash ?? null
809
+ };
742
810
  }
743
- async function loadBroadcastModule(required = false, projectRoot) {
744
- const broadcastModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/broadcast", {
745
- projectRoot
811
+ function normalizeNotificationRecordFromRow(row) {
812
+ const decodedData = normalizeJsonValue(row.data);
813
+ return Object.freeze({
814
+ id: String(row.id),
815
+ type: typeof row.type === "string" ? row.type : void 0,
816
+ notifiableType: String(row.notifiable_type),
817
+ notifiableId: typeof row.notifiable_id === "number" ? row.notifiable_id : String(row.notifiable_id),
818
+ data: decodedData,
819
+ readAt: row.read_at ? normalizeDateLike(row.read_at) : null,
820
+ createdAt: normalizeDateLike(row.created_at),
821
+ updatedAt: normalizeDateLike(row.updated_at)
746
822
  });
747
- if (!broadcastModule && required) {
748
- throw new Error("[@holo-js/core] Broadcast support requires @holo-js/broadcast to be installed.");
749
- }
750
- return broadcastModule;
751
823
  }
752
- async function loadMailModule(required = false) {
753
- const mailModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/mail");
754
- if (!mailModule && required) {
755
- throw new Error("[@holo-js/core] Mail support requires @holo-js/mail to be installed.");
756
- }
757
- return mailModule;
758
- }
759
- async function loadAuthModule(required = false) {
760
- const authModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/auth");
761
- if (!authModule && required) {
762
- throw new Error("[@holo-js/core] Auth support requires @holo-js/auth to be installed.");
763
- }
764
- return authModule;
824
+ function serializeNotificationRecordForRow(record) {
825
+ return {
826
+ id: record.id,
827
+ type: record.type ?? null,
828
+ notifiable_type: record.notifiableType,
829
+ notifiable_id: String(record.notifiableId),
830
+ data: JSON.stringify(record.data ?? null),
831
+ read_at: record.readAt ? record.readAt.toISOString() : null,
832
+ created_at: record.createdAt.toISOString(),
833
+ updated_at: record.updatedAt.toISOString()
834
+ };
765
835
  }
766
- async function loadAuthorizationModule(required = false) {
767
- const authorizationModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/authorization");
768
- if (!authorizationModule && required) {
769
- throw new Error("[@holo-js/core] Authorization support requires @holo-js/authorization to be installed.");
770
- }
771
- return authorizationModule;
836
+
837
+ // src/portable/notificationPersistence.ts
838
+ import { DB } from "@holo-js/db";
839
+ function createCoreNotificationStore(loadedConfig) {
840
+ const tableName = loadedConfig.notifications.table;
841
+ const connectionName = loadedConfig.database.defaultConnection;
842
+ return Object.freeze({
843
+ async create(record) {
844
+ await DB.table(tableName, connectionName).insert(serializeNotificationRecordForRow(record));
845
+ },
846
+ async list(notifiable) {
847
+ const rows = await DB.table(tableName, connectionName).where("notifiable_type", notifiable.type).where("notifiable_id", String(notifiable.id)).orderBy("created_at", "desc").get();
848
+ return Object.freeze(rows.map((row) => normalizeNotificationRecordFromRow(row)));
849
+ },
850
+ async unread(notifiable) {
851
+ const rows = await DB.table(tableName, connectionName).where("notifiable_type", notifiable.type).where("notifiable_id", String(notifiable.id)).whereNull("read_at").orderBy("created_at", "desc").get();
852
+ return Object.freeze(rows.map((row) => normalizeNotificationRecordFromRow(row)));
853
+ },
854
+ async markAsRead(ids) {
855
+ if (ids.length === 0) return 0;
856
+ const now = (/* @__PURE__ */ new Date()).toISOString();
857
+ const result = await DB.table(tableName, connectionName).whereIn("id", ids).update({ read_at: now, updated_at: now });
858
+ return result.affectedRows ?? 0;
859
+ },
860
+ async markAsUnread(ids) {
861
+ if (ids.length === 0) return 0;
862
+ const result = await DB.table(tableName, connectionName).whereIn("id", ids).update({ read_at: null, updated_at: (/* @__PURE__ */ new Date()).toISOString() });
863
+ return result.affectedRows ?? 0;
864
+ },
865
+ async delete(ids) {
866
+ if (ids.length === 0) return 0;
867
+ const result = await DB.table(tableName, connectionName).whereIn("id", ids).delete();
868
+ return result.affectedRows ?? 0;
869
+ }
870
+ });
871
+ }
872
+
873
+ // src/portable/authMailDelivery.ts
874
+ var authEmailDateFormatter = new Intl.DateTimeFormat("en-US", {
875
+ dateStyle: "long",
876
+ timeStyle: "short",
877
+ timeZone: "UTC"
878
+ });
879
+ function formatAuthEmailExpiration(expiresAt) {
880
+ return `${authEmailDateFormatter.format(expiresAt)} UTC`;
881
+ }
882
+ function escapeAuthEmailHtml(value) {
883
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
884
+ }
885
+ function createNotificationMailText(message) {
886
+ const parts = [
887
+ typeof message.greeting === "string" ? message.greeting.trim() : void 0,
888
+ ...(message.lines ?? []).map((line) => line.trim()).filter(Boolean),
889
+ message.action ? `${message.action.label}: ${message.action.url}` : void 0
890
+ ].filter((value) => typeof value === "string" && value.length > 0);
891
+ return parts.length > 0 ? parts.join("\n\n") : void 0;
892
+ }
893
+ function createNotificationMailHtml(message) {
894
+ const greeting = typeof message.greeting === "string" ? message.greeting.trim() : void 0;
895
+ const lines = (message.lines ?? []).map((line) => line.trim()).filter(Boolean);
896
+ const sections = [
897
+ greeting ? `<p style="margin:0 0 16px;">${escapeAuthEmailHtml(greeting)}</p>` : "",
898
+ ...lines.map((line) => `<p style="margin:0 0 16px;">${escapeAuthEmailHtml(line)}</p>`),
899
+ message.action ? `<p style="margin:24px 0;"><a href="${escapeAuthEmailHtml(message.action.url)}" style="display:inline-block;padding:12px 18px;background:#111827;color:#ffffff;text-decoration:none;border-radius:8px;font-weight:600;">${escapeAuthEmailHtml(message.action.label)}</a></p>` : "",
900
+ message.action ? `<p style="margin:0;color:#475569;font-size:14px;">If the button does not work, open this link: <a href="${escapeAuthEmailHtml(message.action.url)}">${escapeAuthEmailHtml(message.action.url)}</a></p>` : ""
901
+ ].join("");
902
+ return [
903
+ "<!doctype html>",
904
+ '<html><head><meta charset="utf-8">',
905
+ `<title>${escapeAuthEmailHtml(message.subject)}</title>`,
906
+ '</head><body style="margin:0;padding:24px;font-family:Arial,sans-serif;color:#0f172a;background:#f8fafc;">',
907
+ '<div style="max-width:640px;margin:0 auto;background:#ffffff;border:1px solid #e2e8f0;border-radius:12px;padding:32px;">',
908
+ `<h1 style="margin:0 0 24px;font-size:24px;line-height:1.3;">${escapeAuthEmailHtml(message.subject)}</h1>`,
909
+ sections,
910
+ "</div></body></html>"
911
+ ].join("");
912
+ }
913
+ function createAuthActionUrl(appUrl, path, token) {
914
+ const normalizedBaseUrl = appUrl.endsWith("/") ? appUrl.slice(0, -1) : appUrl;
915
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
916
+ const url = new URL(`${normalizedBaseUrl}${normalizedPath}`);
917
+ url.searchParams.set("token", token);
918
+ return url.toString();
919
+ }
920
+ function createAuthEmailHtml(message) {
921
+ return createNotificationMailHtml(message);
922
+ }
923
+ function createCoreNotificationMailSender(mailModule) {
924
+ return Object.freeze({
925
+ async send(message, context) {
926
+ if (!context.route) {
927
+ throw new Error("[@holo-js/core] Email notifications require a resolved email route before bridging into mail.");
928
+ }
929
+ const fallbackText = createNotificationMailText(message);
930
+ await mailModule.sendMail({
931
+ to: context.route,
932
+ subject: message.subject,
933
+ html: typeof message.html === "string" ? message.html : createNotificationMailHtml(message),
934
+ ...typeof (message.text ?? fallbackText) === "string" ? { text: message.text ?? fallbackText } : {},
935
+ ...message.metadata ? { metadata: message.metadata } : {}
936
+ });
937
+ }
938
+ });
939
+ }
940
+ function createAuthMailDeliveryHook(mailModule, appUrl) {
941
+ return Object.freeze({
942
+ async sendEmailVerification(input) {
943
+ const recipientName = typeof input.user?.name === "string" ? input.user.name?.trim() : void 0;
944
+ const lines = [
945
+ "Confirm your account to finish signing in.",
946
+ `This verification link expires at ${formatAuthEmailExpiration(input.token.expiresAt)}.`
947
+ ];
948
+ const action = {
949
+ label: "Verify email address",
950
+ url: createAuthActionUrl(appUrl, input.route, input.token.plainTextToken)
951
+ };
952
+ await mailModule.sendMail({
953
+ to: { email: input.email, ...recipientName ? { name: recipientName } : {} },
954
+ subject: "Verify your email address",
955
+ html: createAuthEmailHtml({
956
+ subject: "Verify your email address",
957
+ ...recipientName ? { greeting: `Hello ${recipientName},` } : {},
958
+ lines,
959
+ action
960
+ }),
961
+ text: createNotificationMailText({
962
+ ...recipientName ? { greeting: `Hello ${recipientName},` } : {},
963
+ lines,
964
+ action
965
+ }),
966
+ metadata: { provider: input.provider, tokenId: input.token.id }
967
+ });
968
+ },
969
+ async sendPasswordReset(input) {
970
+ const lines = [
971
+ "Click the link below to choose a new password.",
972
+ `This reset link expires at ${formatAuthEmailExpiration(input.token.expiresAt)}.`
973
+ ];
974
+ const action = {
975
+ label: "Reset password",
976
+ url: createAuthActionUrl(appUrl, input.route, input.token.plainTextToken)
977
+ };
978
+ await mailModule.sendMail({
979
+ to: input.email,
980
+ subject: "Reset your password",
981
+ html: createAuthEmailHtml({ subject: "Reset your password", lines, action }),
982
+ text: createNotificationMailText({ lines, action }),
983
+ metadata: { provider: input.provider, tokenId: input.token.id }
984
+ });
985
+ }
986
+ });
772
987
  }
773
- async function loadSocialModule(required = false) {
774
- const socialModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/auth-social");
775
- if (!socialModule && required) {
776
- throw new Error("[@holo-js/core] Social auth config requires @holo-js/auth-social to be installed.");
988
+
989
+ // src/portable/optionalFeatureLoader.ts
990
+ function createOptionalFeatureModuleLoader(importer, specifier, missingPackageMessage) {
991
+ async function load(required = false, options = {}) {
992
+ const module = await importer(specifier, options);
993
+ if (!module && required) throw new Error(missingPackageMessage);
994
+ return module;
777
995
  }
778
- return socialModule;
996
+ return load;
997
+ }
998
+
999
+ // src/portable/featureDetection.ts
1000
+ function hasLoadedConfigFile(loadedConfig, configName) {
1001
+ return loadedConfig.loadedFiles.some((filePath) => {
1002
+ const normalizedPath = filePath.replaceAll("\\", "/");
1003
+ return ["ts", "mts", "js", "mjs", "cts", "cjs"].some((extension) => normalizedPath.endsWith(`/config/${configName}.${extension}`));
1004
+ });
1005
+ }
1006
+ function queueConfigUsesDatabaseDriver(loadedConfig) {
1007
+ if (!loadedConfig.queue) return false;
1008
+ return Object.values(loadedConfig.queue.connections).some((connection) => connection.driver === "database");
1009
+ }
1010
+ function queueConfigUsesRedisDriver(loadedConfig) {
1011
+ if (!loadedConfig.queue) return false;
1012
+ return Object.values(loadedConfig.queue.connections).some((connection) => connection.driver === "redis");
1013
+ }
1014
+ function queueConfigUsesDatabaseBackedFailedStore(loadedConfig) {
1015
+ if (!loadedConfig.queue) return false;
1016
+ return loadedConfig.queue.failed !== false;
1017
+ }
1018
+ function registryHasJobs(registry) {
1019
+ return (registry?.jobs.length ?? 0) > 0;
1020
+ }
1021
+ function registryHasEvents(registry) {
1022
+ return (registry?.events.length ?? 0) > 0 || (registry?.listeners.length ?? 0) > 0;
1023
+ }
1024
+ function authConfigUsesSocialProviders(loadedConfig) {
1025
+ if (!loadedConfig.auth) return false;
1026
+ return Object.keys(loadedConfig.auth.social).length > 0;
1027
+ }
1028
+ function authConfigUsesWorkosProviders(loadedConfig) {
1029
+ if (!loadedConfig.auth) return false;
1030
+ return Object.entries(loadedConfig.auth.workos).some(([name, provider]) => name !== "provider" && name !== "identityStore" && typeof provider === "object" && provider !== null);
1031
+ }
1032
+ function authConfigUsesClerkProviders(loadedConfig) {
1033
+ if (!loadedConfig.auth) return false;
1034
+ return Object.entries(loadedConfig.auth.clerk).some(([name, provider]) => name !== "provider" && name !== "identityStore" && typeof provider === "object" && provider !== null);
1035
+ }
1036
+
1037
+ // src/portable/runtimeState.ts
1038
+ function createRuntimeStateAccessors() {
1039
+ const getRuntimeState2 = () => {
1040
+ const runtime = globalThis;
1041
+ runtime.__holoRuntime__ ??= {};
1042
+ return runtime.__holoRuntime__;
1043
+ };
1044
+ const snapshotOptionalSubsystemRuntimeBindings2 = () => {
1045
+ const state = getRuntimeState2();
1046
+ const runtime = globalThis;
1047
+ return Object.freeze({
1048
+ ...runtime.__holoMailRuntime__?.bindings ? { mail: runtime.__holoMailRuntime__.bindings } : {},
1049
+ ...runtime.__holoNotificationsRuntime__?.bindings ? { notifications: runtime.__holoNotificationsRuntime__.bindings } : {},
1050
+ ...runtime.__holoBroadcastRuntime__?.bindings ? { broadcast: runtime.__holoBroadcastRuntime__.bindings } : {},
1051
+ ...state.sessionRedisAdapters ? { session: Object.freeze({ sessionRedisAdapters: state.sessionRedisAdapters }) } : {},
1052
+ ...runtime.__holoSecurityRuntime__?.bindings || state.securityRedisAdapter || typeof state.securityRateLimitStoreManaged !== "undefined" ? {
1053
+ security: Object.freeze({
1054
+ ...runtime.__holoSecurityRuntime__?.bindings ? { bindings: runtime.__holoSecurityRuntime__.bindings } : {},
1055
+ ...state.securityRedisAdapter ? { securityRedisAdapter: state.securityRedisAdapter } : {},
1056
+ ...typeof state.securityRateLimitStoreManaged !== "undefined" ? { securityRateLimitStoreManaged: state.securityRateLimitStoreManaged } : {}
1057
+ })
1058
+ } : {}
1059
+ });
1060
+ };
1061
+ const restoreOptionalSubsystemRuntimeBindings2 = (bindings) => {
1062
+ const state = getRuntimeState2();
1063
+ const runtime = globalThis;
1064
+ if (bindings.mail || runtime.__holoMailRuntime__) {
1065
+ runtime.__holoMailRuntime__ ??= {};
1066
+ runtime.__holoMailRuntime__.bindings = bindings.mail;
1067
+ }
1068
+ if (bindings.notifications || runtime.__holoNotificationsRuntime__) {
1069
+ runtime.__holoNotificationsRuntime__ ??= {};
1070
+ runtime.__holoNotificationsRuntime__.bindings = bindings.notifications;
1071
+ }
1072
+ if (bindings.broadcast || runtime.__holoBroadcastRuntime__) {
1073
+ runtime.__holoBroadcastRuntime__ ??= {};
1074
+ runtime.__holoBroadcastRuntime__.bindings = bindings.broadcast;
1075
+ }
1076
+ state.sessionRedisAdapters = bindings.session?.sessionRedisAdapters;
1077
+ if (bindings.security || runtime.__holoSecurityRuntime__) {
1078
+ runtime.__holoSecurityRuntime__ ??= {};
1079
+ runtime.__holoSecurityRuntime__.bindings = bindings.security?.bindings;
1080
+ state.securityRedisAdapter = bindings.security?.securityRedisAdapter;
1081
+ state.securityRateLimitStoreManaged = bindings.security?.securityRateLimitStoreManaged;
1082
+ }
1083
+ };
1084
+ return {
1085
+ getRuntimeState: getRuntimeState2,
1086
+ restoreOptionalSubsystemRuntimeBindings: restoreOptionalSubsystemRuntimeBindings2,
1087
+ snapshotOptionalSubsystemRuntimeBindings: snapshotOptionalSubsystemRuntimeBindings2
1088
+ };
1089
+ }
1090
+
1091
+ // src/portable/holo.ts
1092
+ function closeSessionRedisAdapter(adapter) {
1093
+ return adapter.disconnect?.() || adapter.close?.();
1094
+ }
1095
+ var CORE_BROADCAST_PUBLISHER_MARKER = /* @__PURE__ */ Symbol.for("holo-js.core.broadcast.publisher");
1096
+ var frameworkBuildEnvKey = "HOLO_INTERNAL_FRAMEWORK_BUILD";
1097
+ function shouldBootRuntimeServices(processEnv = process.env) {
1098
+ return processEnv[frameworkBuildEnvKey] !== "1";
1099
+ }
1100
+ var {
1101
+ getRuntimeState,
1102
+ restoreOptionalSubsystemRuntimeBindings,
1103
+ snapshotOptionalSubsystemRuntimeBindings
1104
+ } = createRuntimeStateAccessors();
1105
+ var BROADCAST_PUBLISH_TIMEOUT_MS = 1e4;
1106
+ async function importOptionalModule2(specifier, options = {}) {
1107
+ return importOptionalRuntimeModule(specifier, options);
1108
+ }
1109
+ var portableRuntimeModuleInternals = {
1110
+ importOptionalModule: importOptionalModule2
1111
+ };
1112
+ var HOLO_AUTH_PROVIDER_MARKER = /* @__PURE__ */ Symbol.for("holo-js.auth.provider");
1113
+ var importOptionalFeature = (specifier, options) => portableRuntimeModuleInternals.importOptionalModule(specifier, options);
1114
+ var loadQueueModule = createOptionalFeatureModuleLoader(
1115
+ importOptionalFeature,
1116
+ "@holo-js/queue",
1117
+ "[@holo-js/core] Queue support requires @holo-js/queue to be installed."
1118
+ );
1119
+ async function loadConfiguredDatabaseDrivers(projectRoot, loadedConfig) {
1120
+ const packageByDriver = {
1121
+ sqlite: { packageName: "@holo-js/db-sqlite", factoryExport: "sqliteDatabaseDriverFactory" },
1122
+ postgres: { packageName: "@holo-js/db-postgres", factoryExport: "postgresDatabaseDriverFactory" },
1123
+ mysql: { packageName: "@holo-js/db-mysql", factoryExport: "mysqlDatabaseDriverFactory" }
1124
+ };
1125
+ const drivers = new Set(Object.values(loadedConfig.database.connections).map((connection) => {
1126
+ if (typeof connection === "string") {
1127
+ if (connection.startsWith("postgres://") || connection.startsWith("postgresql://")) return "postgres";
1128
+ if (connection.startsWith("mysql://") || connection.startsWith("mysql2://")) return "mysql";
1129
+ return "sqlite";
1130
+ }
1131
+ return connection.driver ?? "sqlite";
1132
+ }));
1133
+ const factories = [];
1134
+ for (const driver of drivers) {
1135
+ const contribution = packageByDriver[driver];
1136
+ const { packageName, factoryExport } = contribution;
1137
+ const module = await portableRuntimeModuleInternals.importOptionalModule(packageName, { projectRoot });
1138
+ if (!module) throw new Error(`[@holo-js/core] Database driver "${driver}" requires ${packageName} to be installed.`);
1139
+ const factory = module[factoryExport];
1140
+ if (!factory) throw new Error(`[@holo-js/core] Database driver package ${packageName} does not export ${factoryExport}.`);
1141
+ factories.push(factory);
1142
+ }
1143
+ return factories;
1144
+ }
1145
+ async function loadQueueDbModule(projectRoot) {
1146
+ return createOptionalFeatureModuleLoader(
1147
+ importOptionalFeature,
1148
+ "@holo-js/queue-db",
1149
+ "[@holo-js/core] Database queues require @holo-js/queue-db to be installed."
1150
+ )(false, { projectRoot });
1151
+ }
1152
+ async function loadQueueRedisModule(projectRoot) {
1153
+ return createOptionalFeatureModuleLoader(
1154
+ importOptionalFeature,
1155
+ "@holo-js/queue-redis",
1156
+ "[@holo-js/core] Redis queues require @holo-js/queue-redis to be installed."
1157
+ )(false, { projectRoot });
1158
+ }
1159
+ async function loadCacheModule(required = false, projectRoot) {
1160
+ const loader = createOptionalFeatureModuleLoader(
1161
+ importOptionalFeature,
1162
+ "@holo-js/cache",
1163
+ "[@holo-js/core] Cache support requires @holo-js/cache to be installed."
1164
+ );
1165
+ return required ? loader(true, { projectRoot }) : loader(false, { projectRoot });
779
1166
  }
780
- async function loadWorkosModule(required = false) {
781
- const workosModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/auth-workos");
782
- if (!workosModule && required) {
783
- throw new Error("[@holo-js/core] WorkOS auth config requires @holo-js/auth-workos to be installed.");
1167
+ async function loadConfiguredCacheDrivers(projectRoot, cacheConfig) {
1168
+ const packageByDriver = {
1169
+ database: "@holo-js/cache-db",
1170
+ redis: "@holo-js/cache-redis"
1171
+ };
1172
+ const drivers = new Set(Object.values(cacheConfig.drivers).map((driver) => driver.driver));
1173
+ for (const driver of drivers) {
1174
+ if (driver !== "database" && driver !== "redis") continue;
1175
+ const packageName = packageByDriver[driver];
1176
+ const module = await portableRuntimeModuleInternals.importOptionalModule(packageName, { projectRoot });
1177
+ if (!module) throw new Error(`[@holo-js/core] Cache driver "${driver}" requires ${packageName} to be installed.`);
784
1178
  }
785
- return workosModule;
786
1179
  }
787
- async function loadClerkModule(required = false) {
788
- const clerkModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/auth-clerk");
789
- if (!clerkModule && required) {
790
- throw new Error("[@holo-js/core] Clerk auth config requires @holo-js/auth-clerk to be installed.");
1180
+ function resetCacheRuntimeGlobalsFallback() {
1181
+ const runtime = globalThis;
1182
+ if (runtime.__holoCacheRuntime__) {
1183
+ runtime.__holoCacheRuntime__.bindings = void 0;
1184
+ }
1185
+ if (runtime.__holoCacheQueryBridge__) {
1186
+ runtime.__holoCacheQueryBridge__.dependencyIndex = void 0;
1187
+ }
1188
+ if (runtime.__holoDbQueryCacheBridge__) {
1189
+ runtime.__holoDbQueryCacheBridge__.bridge = void 0;
791
1190
  }
792
- return clerkModule;
793
1191
  }
1192
+ var loadEventsModule = createOptionalFeatureModuleLoader(
1193
+ importOptionalFeature,
1194
+ "@holo-js/events",
1195
+ "[@holo-js/core] Events support requires @holo-js/events to be installed."
1196
+ );
1197
+ var loadSessionModule = createOptionalFeatureModuleLoader(
1198
+ importOptionalFeature,
1199
+ "@holo-js/session",
1200
+ "[@holo-js/core] Session support requires @holo-js/session to be installed."
1201
+ );
1202
+ var loadSecurityModule = createOptionalFeatureModuleLoader(
1203
+ importOptionalFeature,
1204
+ "@holo-js/security",
1205
+ "[@holo-js/core] Security support requires @holo-js/security to be installed."
1206
+ );
1207
+ var loadSecurityRedisAdapterModule = createOptionalFeatureModuleLoader(
1208
+ importOptionalFeature,
1209
+ "@holo-js/security/drivers/redis-adapter",
1210
+ "[@holo-js/core] Redis-backed security rate limits require @holo-js/security/drivers/redis-adapter to be installed."
1211
+ );
1212
+ var loadSessionRedisAdapterModule = createOptionalFeatureModuleLoader(
1213
+ importOptionalFeature,
1214
+ "@holo-js/session/drivers/redis-adapter",
1215
+ "[@holo-js/core] Redis-backed session stores require @holo-js/session/drivers/redis-adapter to be installed."
1216
+ );
1217
+ var loadNotificationsModule = createOptionalFeatureModuleLoader(
1218
+ importOptionalFeature,
1219
+ "@holo-js/notifications",
1220
+ "[@holo-js/core] Notifications support requires @holo-js/notifications to be installed."
1221
+ );
1222
+ async function loadBroadcastModule(required = false, projectRoot) {
1223
+ const loader = createOptionalFeatureModuleLoader(
1224
+ importOptionalFeature,
1225
+ "@holo-js/broadcast",
1226
+ "[@holo-js/core] Broadcast support requires @holo-js/broadcast to be installed."
1227
+ );
1228
+ return required ? loader(true, { projectRoot }) : loader(false, { projectRoot });
1229
+ }
1230
+ var loadMailModule = createOptionalFeatureModuleLoader(
1231
+ importOptionalFeature,
1232
+ "@holo-js/mail",
1233
+ "[@holo-js/core] Mail support requires @holo-js/mail to be installed."
1234
+ );
1235
+ var loadAuthModule = createOptionalFeatureModuleLoader(
1236
+ importOptionalFeature,
1237
+ "@holo-js/auth",
1238
+ "[@holo-js/core] Auth support requires @holo-js/auth to be installed."
1239
+ );
1240
+ var loadAuthorizationModule = createOptionalFeatureModuleLoader(
1241
+ importOptionalFeature,
1242
+ "@holo-js/authorization",
1243
+ "[@holo-js/core] Authorization support requires @holo-js/authorization to be installed."
1244
+ );
1245
+ var loadSocialModule = createOptionalFeatureModuleLoader(
1246
+ importOptionalFeature,
1247
+ "@holo-js/auth-social",
1248
+ "[@holo-js/core] Social auth config requires @holo-js/auth-social to be installed."
1249
+ );
1250
+ var loadWorkosModule = createOptionalFeatureModuleLoader(
1251
+ importOptionalFeature,
1252
+ "@holo-js/auth-workos",
1253
+ "[@holo-js/core] WorkOS auth config requires @holo-js/auth-workos to be installed."
1254
+ );
1255
+ var loadClerkModule = createOptionalFeatureModuleLoader(
1256
+ importOptionalFeature,
1257
+ "@holo-js/auth-clerk",
1258
+ "[@holo-js/core] Clerk auth config requires @holo-js/auth-clerk to be installed."
1259
+ );
794
1260
  function resolveQueueJobExport(queueModule, moduleValue) {
795
1261
  const exports = moduleValue;
796
1262
  if (queueModule.isQueueJobDefinition(exports.default)) {
@@ -831,74 +1297,7 @@ function resolveListenerExport(eventsModule, moduleValue) {
831
1297
  return Object.values(exports).find((value) => hasListenerDefinitionMarker(value) || eventsModule.isListenerDefinition(value));
832
1298
  }
833
1299
  function resolveProjectRelativePath(projectRoot, value) {
834
- return value.startsWith(".") || !value.startsWith("/") ? resolve3(projectRoot, value) : value;
835
- }
836
- function normalizeDateLike(value) {
837
- return value instanceof Date ? value : new Date(String(value));
838
- }
839
- function normalizeSessionRecordFromRow(row) {
840
- const decodedData = (() => {
841
- if (row.data && typeof row.data === "object") {
842
- return row.data;
843
- }
844
- if (typeof row.data === "string") {
845
- try {
846
- const parsed = JSON.parse(row.data);
847
- return parsed && typeof parsed === "object" ? parsed : {};
848
- } catch {
849
- return {};
850
- }
851
- }
852
- return {};
853
- })();
854
- return Object.freeze({
855
- id: String(row.id),
856
- /* v8 ignore next -- runtime rows usually carry an explicit store name; this preserves a safe default */
857
- store: typeof row.store === "string" ? row.store : "database",
858
- data: Object.freeze(decodedData),
859
- createdAt: normalizeDateLike(row.created_at),
860
- lastActivityAt: normalizeDateLike(row.last_activity_at),
861
- expiresAt: normalizeDateLike(row.expires_at),
862
- rememberTokenHash: typeof row.remember_token_hash === "string" ? row.remember_token_hash : void 0
863
- });
864
- }
865
- function serializeSessionRecordForRow(record) {
866
- return {
867
- id: record.id,
868
- store: record.store,
869
- /* v8 ignore next -- record.data is always present in runtime flows; nullish fallback is purely defensive */
870
- data: JSON.stringify(record.data ?? {}),
871
- created_at: record.createdAt.toISOString(),
872
- last_activity_at: record.lastActivityAt.toISOString(),
873
- expires_at: record.expiresAt.toISOString(),
874
- invalidated_at: null,
875
- remember_token_hash: record.rememberTokenHash ?? null
876
- };
877
- }
878
- function normalizeNotificationRecordFromRow(row) {
879
- const decodedData = normalizeJsonValue(row.data);
880
- return Object.freeze({
881
- id: String(row.id),
882
- type: typeof row.type === "string" ? row.type : void 0,
883
- notifiableType: String(row.notifiable_type),
884
- notifiableId: typeof row.notifiable_id === "number" ? row.notifiable_id : String(row.notifiable_id),
885
- data: decodedData,
886
- readAt: row.read_at ? normalizeDateLike(row.read_at) : null,
887
- createdAt: normalizeDateLike(row.created_at),
888
- updatedAt: normalizeDateLike(row.updated_at)
889
- });
890
- }
891
- function serializeNotificationRecordForRow(record) {
892
- return {
893
- id: record.id,
894
- type: record.type ?? null,
895
- notifiable_type: record.notifiableType,
896
- notifiable_id: String(record.notifiableId),
897
- data: JSON.stringify(record.data ?? null),
898
- read_at: record.readAt ? record.readAt.toISOString() : null,
899
- created_at: record.createdAt.toISOString(),
900
- updated_at: record.updatedAt.toISOString()
901
- };
1300
+ return value.startsWith(".") || !value.startsWith("/") ? resolve5(projectRoot, value) : value;
902
1301
  }
903
1302
  function getEntityAttributes(value) {
904
1303
  if (value && typeof value === "object") {
@@ -942,20 +1341,20 @@ async function createCoreManagedSessionStores(projectRoot, loadedConfig, session
942
1341
  const connectionName = config.connection === "default" && !(config.connection in loadedConfig.database.connections) ? loadedConfig.database.defaultConnection : config.connection;
943
1342
  stores[name] = sessionModule.createDatabaseSessionStore({
944
1343
  async read(sessionId) {
945
- const row = await DB.table(config.table, connectionName).where("id", sessionId).whereNull("invalidated_at").first();
1344
+ const row = await DB2.table(config.table, connectionName).where("id", sessionId).whereNull("invalidated_at").first();
946
1345
  return row ? normalizeSessionRecordFromRow(row) : null;
947
1346
  },
948
1347
  async write(record) {
949
1348
  const normalized = serializeSessionRecordForRow(record);
950
- const existing = await DB.table(config.table, connectionName).find(String(normalized.id));
1349
+ const existing = await DB2.table(config.table, connectionName).find(String(normalized.id));
951
1350
  if (existing) {
952
- await DB.table(config.table, connectionName).where("id", normalized.id).update(normalized);
1351
+ await DB2.table(config.table, connectionName).where("id", normalized.id).update(normalized);
953
1352
  return;
954
1353
  }
955
- await DB.table(config.table, connectionName).insert(normalized);
1354
+ await DB2.table(config.table, connectionName).insert(normalized);
956
1355
  },
957
1356
  async delete(sessionId) {
958
- await DB.table(config.table, connectionName).where("id", sessionId).delete();
1357
+ await DB2.table(config.table, connectionName).where("id", sessionId).delete();
959
1358
  }
960
1359
  });
961
1360
  continue;
@@ -1000,59 +1399,6 @@ async function createCoreManagedSessionStores(projectRoot, loadedConfig, session
1000
1399
  async function createCoreSessionStores(projectRoot, loadedConfig, sessionModule) {
1001
1400
  return (await createCoreManagedSessionStores(projectRoot, loadedConfig, sessionModule)).stores;
1002
1401
  }
1003
- function createCoreNotificationStore(loadedConfig) {
1004
- const tableName = loadedConfig.notifications.table;
1005
- const connectionName = loadedConfig.database.defaultConnection;
1006
- const store = {
1007
- async create(record) {
1008
- await DB.table(tableName, connectionName).insert(serializeNotificationRecordForRow(record));
1009
- },
1010
- async list(notifiable) {
1011
- const rows = await DB.table(tableName, connectionName).where("notifiable_type", notifiable.type).where("notifiable_id", String(notifiable.id)).orderBy("created_at", "desc").get();
1012
- return Object.freeze(rows.map((row) => normalizeNotificationRecordFromRow(row)));
1013
- },
1014
- async unread(notifiable) {
1015
- const rows = await DB.table(tableName, connectionName).where("notifiable_type", notifiable.type).where("notifiable_id", String(notifiable.id)).whereNull("read_at").orderBy("created_at", "desc").get();
1016
- return Object.freeze(rows.map((row) => normalizeNotificationRecordFromRow(row)));
1017
- },
1018
- async markAsRead(ids) {
1019
- if (ids.length === 0) {
1020
- return 0;
1021
- }
1022
- const result = await DB.table(tableName, connectionName).whereIn("id", ids).update({
1023
- read_at: (/* @__PURE__ */ new Date()).toISOString(),
1024
- updated_at: (/* @__PURE__ */ new Date()).toISOString()
1025
- });
1026
- return result.affectedRows ?? 0;
1027
- },
1028
- async markAsUnread(ids) {
1029
- if (ids.length === 0) {
1030
- return 0;
1031
- }
1032
- const result = await DB.table(tableName, connectionName).whereIn("id", ids).update({
1033
- read_at: null,
1034
- updated_at: (/* @__PURE__ */ new Date()).toISOString()
1035
- });
1036
- return result.affectedRows ?? 0;
1037
- },
1038
- async delete(ids) {
1039
- if (ids.length === 0) {
1040
- return 0;
1041
- }
1042
- const result = await DB.table(tableName, connectionName).whereIn("id", ids).delete();
1043
- return result.affectedRows ?? 0;
1044
- }
1045
- };
1046
- return Object.freeze(store);
1047
- }
1048
- var authEmailDateFormatter = new Intl.DateTimeFormat("en-US", {
1049
- dateStyle: "long",
1050
- timeStyle: "short",
1051
- timeZone: "UTC"
1052
- });
1053
- function formatAuthEmailExpiration(expiresAt) {
1054
- return `${authEmailDateFormatter.format(expiresAt)} UTC`;
1055
- }
1056
1402
  var AUTH_EMAIL_VERIFICATION_NOTIFICATION_PATHS = [
1057
1403
  "server/notifications/auth/email-verification.ts",
1058
1404
  "server/notifications/auth/email-verification.mts",
@@ -1073,7 +1419,7 @@ function resolveExistingProjectFile(projectRoot, candidates) {
1073
1419
  if (!projectRoot) {
1074
1420
  return void 0;
1075
1421
  }
1076
- return candidates.find((candidate) => existsSync(resolve3(projectRoot, candidate)));
1422
+ return candidates.find((candidate) => existsSync2(resolve5(projectRoot, candidate)));
1077
1423
  }
1078
1424
  function resolveAuthNotification(module, exportName, filePath) {
1079
1425
  const notification = module[exportName] ?? module.notification ?? module.default;
@@ -1232,219 +1578,92 @@ function createCoreNotificationBroadcaster(broadcastModule) {
1232
1578
  event,
1233
1579
  channels,
1234
1580
  payload: Object.freeze({
1235
- ...typeof context.notificationType === "string" && context.notificationType.trim() ? { type: context.notificationType.trim() } : {},
1236
- data: message.data ?? null
1237
- })
1238
- });
1239
- }
1240
- });
1241
- }
1242
- function createCoreBroadcastPublisher(loadedConfig) {
1243
- const connectionHosts = /* @__PURE__ */ new Set(["holo", "pusher"]);
1244
- const publish = async (input, context) => {
1245
- const connection = loadedConfig.connections[input.connection];
1246
- if (!connection) {
1247
- throw new Error(`[@holo-js/core] Broadcast connection "${input.connection}" is not configured.`);
1248
- }
1249
- if (!("appId" in connection) || !("key" in connection) || !("secret" in connection)) {
1250
- throw new Error(`[@holo-js/core] Broadcast connection "${input.connection}" cannot be published automatically.`);
1251
- }
1252
- if (!connectionHosts.has(connection.driver)) {
1253
- throw new Error(`[@holo-js/core] Broadcast connection "${input.connection}" cannot be published automatically.`);
1254
- }
1255
- const options = connection.options;
1256
- const protocol = options.scheme === "http" ? "http:" : "https:";
1257
- const url = new URL(`/apps/${encodeURIComponent(connection.appId)}/events`, `${protocol}//${options.host}`);
1258
- if (typeof options.port === "number") {
1259
- url.port = String(options.port);
1260
- }
1261
- const body = JSON.stringify({
1262
- name: input.event,
1263
- channels: input.channels,
1264
- data: JSON.stringify(input.payload),
1265
- /* v8 ignore next -- tests do not pass socketId through the publish binding */
1266
- ...typeof input.socketId === "undefined" ? {} : { socket_id: input.socketId }
1267
- });
1268
- const bodyMd5 = createHash("md5").update(body).digest("hex");
1269
- url.searchParams.set("auth_key", connection.key);
1270
- url.searchParams.set("auth_timestamp", String(Math.floor(Date.now() / 1e3)));
1271
- url.searchParams.set("auth_version", "1.0");
1272
- url.searchParams.set("body_md5", bodyMd5);
1273
- url.searchParams.set(
1274
- "auth_signature",
1275
- createHmac("sha256", connection.secret).update(
1276
- [
1277
- "POST",
1278
- url.pathname,
1279
- [...url.searchParams.entries()].filter(([key]) => key !== "auth_signature").sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&")
1280
- ].join("\n")
1281
- ).digest("hex")
1282
- );
1283
- const controller = new AbortController();
1284
- const timeout = setTimeout(() => controller.abort(), BROADCAST_PUBLISH_TIMEOUT_MS);
1285
- let response;
1286
- try {
1287
- response = await fetch(url, {
1288
- method: "POST",
1289
- headers: {
1290
- "content-type": "application/json"
1291
- },
1292
- body,
1293
- signal: controller.signal
1294
- });
1295
- } catch (error) {
1296
- if (error instanceof Error && error.name === "AbortError") {
1297
- throw new Error(`[@holo-js/core] Broadcast publish request timed out after ${BROADCAST_PUBLISH_TIMEOUT_MS}ms.`);
1298
- }
1299
- throw error;
1300
- } finally {
1301
- clearTimeout(timeout);
1302
- }
1303
- if (!response.ok) {
1304
- throw new Error(`[@holo-js/core] Broadcast publish request failed with status ${response.status}.`);
1305
- }
1306
- const result = await response.json();
1307
- return {
1308
- connection: input.connection,
1309
- driver: connection.driver,
1310
- queued: context.queued,
1311
- publishedChannels: Array.isArray(result.deliveredChannels) ? Object.freeze(result.deliveredChannels.map((value) => String(value))) : Object.freeze([...input.channels])
1312
- };
1313
- };
1314
- Object.defineProperty(publish, CORE_BROADCAST_PUBLISHER_MARKER, {
1315
- value: true
1316
- });
1317
- return publish;
1318
- }
1319
- function isCoreBroadcastPublisher(value) {
1320
- return typeof value === "function" && CORE_BROADCAST_PUBLISHER_MARKER in value;
1321
- }
1322
- function createNotificationMailText(message) {
1323
- const parts = [
1324
- typeof message.greeting === "string" ? message.greeting.trim() : void 0,
1325
- ...(message.lines ?? []).map((line) => line.trim()).filter(Boolean),
1326
- message.action ? `${message.action.label}: ${message.action.url}` : void 0
1327
- ].filter((value) => typeof value === "string" && value.length > 0);
1328
- return parts.length > 0 ? parts.join("\n\n") : void 0;
1329
- }
1330
- function createNotificationMailHtml(message) {
1331
- const greeting = typeof message.greeting === "string" ? message.greeting.trim() : void 0;
1332
- const lines = (message.lines ?? []).map((line) => line.trim()).filter(Boolean);
1333
- const sections = [
1334
- greeting ? `<p style="margin:0 0 16px;">${escapeAuthEmailHtml(greeting)}</p>` : "",
1335
- ...lines.map((line) => `<p style="margin:0 0 16px;">${escapeAuthEmailHtml(line)}</p>`),
1336
- message.action ? `<p style="margin:24px 0;"><a href="${escapeAuthEmailHtml(message.action.url)}" style="display:inline-block;padding:12px 18px;background:#111827;color:#ffffff;text-decoration:none;border-radius:8px;font-weight:600;">${escapeAuthEmailHtml(message.action.label)}</a></p>` : "",
1337
- message.action ? `<p style="margin:0;color:#475569;font-size:14px;">If the button does not work, open this link: <a href="${escapeAuthEmailHtml(message.action.url)}">${escapeAuthEmailHtml(message.action.url)}</a></p>` : ""
1338
- ].join("");
1339
- return [
1340
- "<!doctype html>",
1341
- '<html><head><meta charset="utf-8">',
1342
- `<title>${escapeAuthEmailHtml(message.subject)}</title>`,
1343
- '</head><body style="margin:0;padding:24px;font-family:Arial,sans-serif;color:#0f172a;background:#f8fafc;">',
1344
- '<div style="max-width:640px;margin:0 auto;background:#ffffff;border:1px solid #e2e8f0;border-radius:12px;padding:32px;">',
1345
- `<h1 style="margin:0 0 24px;font-size:24px;line-height:1.3;">${escapeAuthEmailHtml(message.subject)}</h1>`,
1346
- sections,
1347
- "</div></body></html>"
1348
- ].join("");
1349
- }
1350
- function joinAppUrl(baseUrl, path) {
1351
- const normalizedBaseUrl = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
1352
- const normalizedPath = path.startsWith("/") ? path : `/${path}`;
1353
- return `${normalizedBaseUrl}${normalizedPath}`;
1354
- }
1355
- function createAuthActionUrl(appUrl, path, token) {
1356
- const url = new URL(joinAppUrl(appUrl, path));
1357
- url.searchParams.set("token", token);
1358
- return url.toString();
1359
- }
1360
- function escapeAuthEmailHtml(value) {
1361
- return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1362
- }
1363
- function createAuthEmailHtml(message) {
1364
- return createNotificationMailHtml(message);
1365
- }
1366
- function createCoreNotificationMailSender(mailModule) {
1367
- return Object.freeze({
1368
- async send(message, context) {
1369
- const route = context.route;
1370
- if (!route) {
1371
- throw new Error("[@holo-js/core] Email notifications require a resolved email route before bridging into mail.");
1372
- }
1373
- const fallbackText = createNotificationMailText(message);
1374
- const fallbackHtml = createNotificationMailHtml(message);
1375
- await mailModule.sendMail({
1376
- to: route,
1377
- subject: message.subject,
1378
- html: typeof message.html === "string" ? message.html : fallbackHtml,
1379
- ...typeof (message.text ?? fallbackText) === "string" ? { text: message.text ?? fallbackText } : {},
1380
- ...message.metadata ? { metadata: message.metadata } : {}
1381
- });
1382
- }
1383
- });
1384
- }
1385
- function createAuthMailDeliveryHook(mailModule, appUrl) {
1386
- return Object.freeze({
1387
- async sendEmailVerification(input) {
1388
- const recipientName = typeof input.user?.name === "string" ? input.user.name?.trim() : void 0;
1389
- const lines = [
1390
- "Confirm your account to finish signing in.",
1391
- `This verification link expires at ${formatAuthEmailExpiration(input.token.expiresAt)}.`
1392
- ];
1393
- const action = {
1394
- label: "Verify email address",
1395
- url: createAuthActionUrl(appUrl, input.route, input.token.plainTextToken)
1396
- };
1397
- await mailModule.sendMail({
1398
- to: {
1399
- email: input.email,
1400
- ...recipientName ? { name: recipientName } : {}
1401
- },
1402
- subject: "Verify your email address",
1403
- html: createAuthEmailHtml({
1404
- subject: "Verify your email address",
1405
- ...recipientName ? { greeting: `Hello ${recipientName},` } : {},
1406
- lines,
1407
- action
1408
- }),
1409
- text: createNotificationMailText({
1410
- ...recipientName ? { greeting: `Hello ${recipientName},` } : {},
1411
- lines,
1412
- action
1413
- }),
1414
- metadata: {
1415
- provider: input.provider,
1416
- tokenId: input.token.id
1417
- }
1581
+ ...typeof context.notificationType === "string" && context.notificationType.trim() ? { type: context.notificationType.trim() } : {},
1582
+ data: message.data ?? null
1583
+ })
1418
1584
  });
1419
- },
1420
- async sendPasswordReset(input) {
1421
- const lines = [
1422
- "Click the link below to choose a new password.",
1423
- `This reset link expires at ${formatAuthEmailExpiration(input.token.expiresAt)}.`
1424
- ];
1425
- const action = {
1426
- label: "Reset password",
1427
- url: createAuthActionUrl(appUrl, input.route, input.token.plainTextToken)
1428
- };
1429
- await mailModule.sendMail({
1430
- to: input.email,
1431
- subject: "Reset your password",
1432
- html: createAuthEmailHtml({
1433
- subject: "Reset your password",
1434
- lines,
1435
- action
1436
- }),
1437
- text: createNotificationMailText({
1438
- lines,
1439
- action
1440
- }),
1441
- metadata: {
1442
- provider: input.provider,
1443
- tokenId: input.token.id
1444
- }
1585
+ }
1586
+ });
1587
+ }
1588
+ function createCoreBroadcastPublisher(loadedConfig) {
1589
+ const connectionHosts = /* @__PURE__ */ new Set(["holo", "pusher"]);
1590
+ const publish = async (input, context) => {
1591
+ const connection = loadedConfig.connections[input.connection];
1592
+ if (!connection) {
1593
+ throw new Error(`[@holo-js/core] Broadcast connection "${input.connection}" is not configured.`);
1594
+ }
1595
+ if (!("appId" in connection) || !("key" in connection) || !("secret" in connection)) {
1596
+ throw new Error(`[@holo-js/core] Broadcast connection "${input.connection}" cannot be published automatically.`);
1597
+ }
1598
+ if (!connectionHosts.has(connection.driver)) {
1599
+ throw new Error(`[@holo-js/core] Broadcast connection "${input.connection}" cannot be published automatically.`);
1600
+ }
1601
+ const options = connection.options;
1602
+ const protocol = options.scheme === "http" ? "http:" : "https:";
1603
+ const url = new URL(`/apps/${encodeURIComponent(connection.appId)}/events`, `${protocol}//${options.host}`);
1604
+ if (typeof options.port === "number") {
1605
+ url.port = String(options.port);
1606
+ }
1607
+ const body = JSON.stringify({
1608
+ name: input.event,
1609
+ channels: input.channels,
1610
+ data: JSON.stringify(input.payload),
1611
+ /* v8 ignore next -- tests do not pass socketId through the publish binding */
1612
+ ...typeof input.socketId === "undefined" ? {} : { socket_id: input.socketId }
1613
+ });
1614
+ const bodyMd5 = createHash("md5").update(body).digest("hex");
1615
+ url.searchParams.set("auth_key", connection.key);
1616
+ url.searchParams.set("auth_timestamp", String(Math.floor(Date.now() / 1e3)));
1617
+ url.searchParams.set("auth_version", "1.0");
1618
+ url.searchParams.set("body_md5", bodyMd5);
1619
+ url.searchParams.set(
1620
+ "auth_signature",
1621
+ createHmac("sha256", connection.secret).update(
1622
+ [
1623
+ "POST",
1624
+ url.pathname,
1625
+ [...url.searchParams.entries()].filter(([key]) => key !== "auth_signature").sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&")
1626
+ ].join("\n")
1627
+ ).digest("hex")
1628
+ );
1629
+ const controller = new AbortController();
1630
+ const timeout = setTimeout(() => controller.abort(), BROADCAST_PUBLISH_TIMEOUT_MS);
1631
+ let response;
1632
+ try {
1633
+ response = await fetch(url, {
1634
+ method: "POST",
1635
+ headers: {
1636
+ "content-type": "application/json"
1637
+ },
1638
+ body,
1639
+ signal: controller.signal
1445
1640
  });
1641
+ } catch (error) {
1642
+ if (error instanceof Error && error.name === "AbortError") {
1643
+ throw new Error(`[@holo-js/core] Broadcast publish request timed out after ${BROADCAST_PUBLISH_TIMEOUT_MS}ms.`);
1644
+ }
1645
+ throw error;
1646
+ } finally {
1647
+ clearTimeout(timeout);
1648
+ }
1649
+ if (!response.ok) {
1650
+ throw new Error(`[@holo-js/core] Broadcast publish request failed with status ${response.status}.`);
1446
1651
  }
1652
+ const result = await response.json();
1653
+ return {
1654
+ connection: input.connection,
1655
+ driver: connection.driver,
1656
+ queued: context.queued,
1657
+ publishedChannels: Array.isArray(result.deliveredChannels) ? Object.freeze(result.deliveredChannels.map((value) => String(value))) : Object.freeze([...input.channels])
1658
+ };
1659
+ };
1660
+ Object.defineProperty(publish, CORE_BROADCAST_PUBLISHER_MARKER, {
1661
+ value: true
1447
1662
  });
1663
+ return publish;
1664
+ }
1665
+ function isCoreBroadcastPublisher(value) {
1666
+ return typeof value === "function" && CORE_BROADCAST_PUBLISHER_MARKER in value;
1448
1667
  }
1449
1668
  async function loadConfiguredSocialProviders(projectRootOrLoadedConfig, maybeLoadedConfig) {
1450
1669
  const projectRoot = typeof projectRootOrLoadedConfig === "string" ? projectRootOrLoadedConfig : (
@@ -1471,97 +1690,6 @@ async function loadConfiguredSocialProviders(projectRootOrLoadedConfig, maybeLoa
1471
1690
  }
1472
1691
  return Object.freeze(providers);
1473
1692
  }
1474
- function normalizeDateValue(value) {
1475
- return value instanceof Date ? value : new Date(String(value));
1476
- }
1477
- function normalizeJsonValue(value) {
1478
- if (typeof value !== "string") {
1479
- return value;
1480
- }
1481
- try {
1482
- return JSON.parse(value);
1483
- } catch {
1484
- return value;
1485
- }
1486
- }
1487
- function normalizeStoredUserId(value) {
1488
- return typeof value === "number" ? value : String(value);
1489
- }
1490
- function normalizeAccessTokenRecord(row) {
1491
- const abilities = normalizeJsonValue(row.abilities);
1492
- return Object.freeze({
1493
- id: String(row.id),
1494
- provider: String(row.provider),
1495
- userId: normalizeStoredUserId(row.user_id),
1496
- name: String(row.name),
1497
- abilities: Array.isArray(abilities) ? Object.freeze([...abilities]) : Object.freeze([]),
1498
- tokenHash: String(row.token_hash),
1499
- createdAt: normalizeDateValue(row.created_at),
1500
- lastUsedAt: row.last_used_at ? normalizeDateValue(row.last_used_at) : void 0,
1501
- expiresAt: row.expires_at ? normalizeDateValue(row.expires_at) : null
1502
- });
1503
- }
1504
- function serializeAccessTokenRecord(record) {
1505
- return {
1506
- id: record.id,
1507
- provider: record.provider,
1508
- user_id: String(record.userId),
1509
- name: record.name,
1510
- abilities: JSON.stringify(record.abilities),
1511
- token_hash: record.tokenHash,
1512
- created_at: record.createdAt.toISOString(),
1513
- last_used_at: record.lastUsedAt?.toISOString() ?? null,
1514
- expires_at: record.expiresAt?.toISOString() ?? null,
1515
- updated_at: (/* @__PURE__ */ new Date()).toISOString()
1516
- };
1517
- }
1518
- function normalizeEmailVerificationTokenRecord(row) {
1519
- return Object.freeze({
1520
- id: String(row.id),
1521
- provider: String(row.provider),
1522
- userId: normalizeStoredUserId(row.user_id),
1523
- email: String(row.email),
1524
- tokenHash: String(row.token_hash),
1525
- createdAt: normalizeDateValue(row.created_at),
1526
- expiresAt: normalizeDateValue(row.expires_at)
1527
- });
1528
- }
1529
- function serializeEmailVerificationTokenRecord(record) {
1530
- return {
1531
- id: record.id,
1532
- provider: record.provider,
1533
- user_id: String(record.userId),
1534
- email: record.email,
1535
- token_hash: record.tokenHash,
1536
- created_at: record.createdAt.toISOString(),
1537
- expires_at: record.expiresAt.toISOString(),
1538
- used_at: null,
1539
- updated_at: (/* @__PURE__ */ new Date()).toISOString()
1540
- };
1541
- }
1542
- function normalizePasswordResetTokenRecord(row) {
1543
- return Object.freeze({
1544
- id: String(row.id),
1545
- provider: typeof row.provider === "string" ? row.provider : "users",
1546
- email: String(row.email),
1547
- table: typeof row.__holo_table === "string" ? row.__holo_table : void 0,
1548
- tokenHash: String(row.token_hash),
1549
- createdAt: normalizeDateValue(row.created_at),
1550
- expiresAt: normalizeDateValue(row.expires_at)
1551
- });
1552
- }
1553
- function serializePasswordResetTokenRecord(record) {
1554
- return {
1555
- id: record.id,
1556
- provider: record.provider,
1557
- email: record.email,
1558
- token_hash: record.tokenHash,
1559
- created_at: record.createdAt.toISOString(),
1560
- expires_at: record.expiresAt.toISOString(),
1561
- used_at: null,
1562
- updated_at: (/* @__PURE__ */ new Date()).toISOString()
1563
- };
1564
- }
1565
1693
  async function createCoreSocialBindings(projectRootOrLoadedConfig, loadedConfigOrSessionModule, maybeSessionModule) {
1566
1694
  const projectRoot = typeof projectRootOrLoadedConfig === "string" ? projectRootOrLoadedConfig : (
1567
1695
  /* turbopackIgnore: true */
@@ -1609,7 +1737,7 @@ async function createCoreSocialBindings(projectRootOrLoadedConfig, loadedConfigO
1609
1737
  });
1610
1738
  const identityStore = Object.freeze({
1611
1739
  async findByProviderUserId(provider, providerUserId) {
1612
- const row = await DB.table("auth_identities").where("provider", provider).where("provider_user_id", providerUserId).first();
1740
+ const row = await DB2.table("auth_identities").where("provider", provider).where("provider_user_id", providerUserId).first();
1613
1741
  if (!row) {
1614
1742
  return null;
1615
1743
  }
@@ -1631,7 +1759,7 @@ async function createCoreSocialBindings(projectRootOrLoadedConfig, loadedConfigO
1631
1759
  },
1632
1760
  async save(record) {
1633
1761
  const value = record;
1634
- const existing = await DB.table("auth_identities").where("provider", value.provider).where("provider_user_id", value.providerUserId).first();
1762
+ const existing = await DB2.table("auth_identities").where("provider", value.provider).where("provider_user_id", value.providerUserId).first();
1635
1763
  const payload = {
1636
1764
  user_id: String(value.userId),
1637
1765
  provider: value.provider,
@@ -1646,10 +1774,10 @@ async function createCoreSocialBindings(projectRootOrLoadedConfig, loadedConfigO
1646
1774
  updated_at: value.updatedAt.toISOString()
1647
1775
  };
1648
1776
  if (existing && typeof existing.id !== "undefined") {
1649
- await DB.table("auth_identities").where("id", existing.id).update(payload);
1777
+ await DB2.table("auth_identities").where("id", existing.id).update(payload);
1650
1778
  return;
1651
1779
  }
1652
- await DB.table("auth_identities").insert(payload);
1780
+ await DB2.table("auth_identities").insert(payload);
1653
1781
  }
1654
1782
  });
1655
1783
  return Object.freeze({
@@ -1683,7 +1811,7 @@ function createCoreHostedIdentityStore(namespace) {
1683
1811
  };
1684
1812
  const findStoredIdentity = async (provider, providerUserId, authProvider = "users") => {
1685
1813
  const providerValue = toHostedIdentityProviderValue(namespace, provider);
1686
- const row = await DB.table("auth_identities").where("provider", providerValue).where("provider_user_id", providerUserId).first();
1814
+ const row = await DB2.table("auth_identities").where("provider", providerValue).where("provider_user_id", providerUserId).first();
1687
1815
  return row ? normalizeRow(row, { provider, providerUserId, authProvider }) : null;
1688
1816
  };
1689
1817
  const toPayload = (record) => {
@@ -1707,7 +1835,7 @@ function createCoreHostedIdentityStore(namespace) {
1707
1835
  },
1708
1836
  async findByUserId(provider, authProvider, userId) {
1709
1837
  const providerValue = toHostedIdentityProviderValue(namespace, provider);
1710
- const row = await DB.table("auth_identities").where("provider", providerValue).where("auth_provider", authProvider).where("user_id", String(userId)).first();
1838
+ const row = await DB2.table("auth_identities").where("provider", providerValue).where("auth_provider", authProvider).where("user_id", String(userId)).first();
1711
1839
  if (!row) {
1712
1840
  return null;
1713
1841
  }
@@ -1719,7 +1847,7 @@ function createCoreHostedIdentityStore(namespace) {
1719
1847
  },
1720
1848
  async claim(record) {
1721
1849
  const value = record;
1722
- await DB.table("auth_identities").insertOrIgnore(toPayload(value));
1850
+ await DB2.table("auth_identities").insertOrIgnore(toPayload(value));
1723
1851
  const claimed = await findStoredIdentity(value.provider, value.providerUserId, value.authProvider);
1724
1852
  if (!claimed) {
1725
1853
  throw new Error("[@holo-js/core] Claimed hosted identity could not be read back from auth_identities.");
@@ -1729,13 +1857,13 @@ function createCoreHostedIdentityStore(namespace) {
1729
1857
  async save(record) {
1730
1858
  const value = record;
1731
1859
  const providerValue = toHostedIdentityProviderValue(namespace, value.provider);
1732
- const existing = await DB.table("auth_identities").where("provider", providerValue).where("provider_user_id", value.providerUserId).first();
1860
+ const existing = await DB2.table("auth_identities").where("provider", providerValue).where("provider_user_id", value.providerUserId).first();
1733
1861
  const payload = toPayload(value);
1734
1862
  if (existing && typeof existing.id !== "undefined") {
1735
- await DB.table("auth_identities").where("id", existing.id).update(payload);
1863
+ await DB2.table("auth_identities").where("id", existing.id).update(payload);
1736
1864
  return;
1737
1865
  }
1738
- await DB.table("auth_identities").insert(payload);
1866
+ await DB2.table("auth_identities").insert(payload);
1739
1867
  }
1740
1868
  });
1741
1869
  }
@@ -1743,55 +1871,55 @@ function createCoreAuthStores(loadedConfig) {
1743
1871
  return Object.freeze({
1744
1872
  tokens: Object.freeze({
1745
1873
  async create(record) {
1746
- await DB.table("personal_access_tokens").insert(serializeAccessTokenRecord(record));
1874
+ await DB2.table("personal_access_tokens").insert(serializeAccessTokenRecord(record));
1747
1875
  },
1748
1876
  async findById(id) {
1749
- const row = await DB.table("personal_access_tokens").find(id);
1877
+ const row = await DB2.table("personal_access_tokens").find(id);
1750
1878
  return row ? normalizeAccessTokenRecord(row) : null;
1751
1879
  },
1752
1880
  async listByUserId(provider, userId) {
1753
- const rows = await DB.table("personal_access_tokens").where("provider", provider).where("user_id", String(userId)).get();
1881
+ const rows = await DB2.table("personal_access_tokens").where("provider", provider).where("user_id", String(userId)).get();
1754
1882
  return Object.freeze(rows.map((row) => normalizeAccessTokenRecord(row)));
1755
1883
  },
1756
1884
  async update(record) {
1757
1885
  const payload = serializeAccessTokenRecord(record);
1758
- await DB.table("personal_access_tokens").where("id", String(payload.id)).update(payload);
1886
+ await DB2.table("personal_access_tokens").where("id", String(payload.id)).update(payload);
1759
1887
  },
1760
1888
  async delete(id) {
1761
- await DB.table("personal_access_tokens").where("id", id).delete();
1889
+ await DB2.table("personal_access_tokens").where("id", id).delete();
1762
1890
  },
1763
1891
  async deleteByUserId(provider, userId) {
1764
- const result = await DB.table("personal_access_tokens").where("provider", provider).where("user_id", String(userId)).delete();
1892
+ const result = await DB2.table("personal_access_tokens").where("provider", provider).where("user_id", String(userId)).delete();
1765
1893
  return result.affectedRows ?? 0;
1766
1894
  }
1767
1895
  }),
1768
1896
  emailVerificationTokens: Object.freeze({
1769
1897
  async create(record) {
1770
- await DB.table("email_verification_tokens").insert(serializeEmailVerificationTokenRecord(record));
1898
+ await DB2.table("email_verification_tokens").insert(serializeEmailVerificationTokenRecord(record));
1771
1899
  },
1772
1900
  async findById(id) {
1773
- const row = await DB.table("email_verification_tokens").where("id", id).whereNull("used_at").first();
1901
+ const row = await DB2.table("email_verification_tokens").where("id", id).whereNull("used_at").first();
1774
1902
  return row ? normalizeEmailVerificationTokenRecord(row) : null;
1775
1903
  },
1776
1904
  async delete(id) {
1777
- await DB.table("email_verification_tokens").where("id", id).delete();
1905
+ await DB2.table("email_verification_tokens").where("id", id).delete();
1778
1906
  },
1779
1907
  async deleteByUserId(provider, userId) {
1780
- const result = await DB.table("email_verification_tokens").where("provider", provider).where("user_id", String(userId)).delete();
1908
+ const result = await DB2.table("email_verification_tokens").where("provider", provider).where("user_id", String(userId)).delete();
1781
1909
  return result.affectedRows ?? 0;
1782
1910
  }
1783
1911
  }),
1784
1912
  passwordResetTokens: Object.freeze({
1785
1913
  async create(record) {
1786
1914
  const value = record;
1787
- await DB.table(value.table ?? "password_reset_tokens").insert(serializePasswordResetTokenRecord(value));
1915
+ await DB2.table(value.table ?? "password_reset_tokens").insert(serializePasswordResetTokenRecord(value));
1788
1916
  },
1789
1917
  async findById(id) {
1790
1918
  const tables = Array.from(new Set(
1791
1919
  Object.values(loadedConfig.auth.passwords).map((config) => config.table)
1792
1920
  ));
1793
1921
  for (const table of tables) {
1794
- const row = await DB.table(table).where("id", id).whereNull("used_at").first();
1922
+ const row = await DB2.table(table).where("id", id).whereNull("used_at").first();
1795
1923
  if (row) {
1796
1924
  return normalizePasswordResetTokenRecord({
1797
1925
  ...row,
@@ -1803,7 +1931,7 @@ function createCoreAuthStores(loadedConfig) {
1803
1931
  },
1804
1932
  async findLatestByEmail(provider, email, options) {
1805
1933
  const table = options?.table ?? "password_reset_tokens";
1806
- const row = await DB.table(table).where("provider", provider).where("email", email).latest("created_at").first();
1934
+ const row = await DB2.table(table).where("provider", provider).where("email", email).latest("created_at").first();
1807
1935
  if (!row) {
1808
1936
  return null;
1809
1937
  }
@@ -1814,20 +1942,20 @@ function createCoreAuthStores(loadedConfig) {
1814
1942
  },
1815
1943
  async delete(id, options) {
1816
1944
  const table = options?.table ?? "password_reset_tokens";
1817
- await DB.table(table).where("id", id).delete();
1945
+ await DB2.table(table).where("id", id).delete();
1818
1946
  },
1819
1947
  async deleteByEmail(provider, email, options) {
1820
1948
  const table = options?.table ?? "password_reset_tokens";
1821
- const result = await DB.table(table).where("provider", provider).where("email", email).delete();
1949
+ const result = await DB2.table(table).where("provider", provider).where("email", email).delete();
1822
1950
  return result.affectedRows ?? 0;
1823
1951
  }
1824
1952
  })
1825
1953
  });
1826
1954
  }
1827
1955
  async function resolveAuthProviderRuntime(projectRoot, loadedConfig, modelName) {
1828
- const modelsRoot = resolve3(projectRoot, loadedConfig.app.paths.models);
1956
+ const modelsRoot = resolve5(projectRoot, loadedConfig.app.paths.models);
1829
1957
  for (const extension of [".ts", ".mts", ".js", ".mjs", ".cts", ".cjs"]) {
1830
- const candidate = resolve3(modelsRoot, `${modelName}${extension}`);
1958
+ const candidate = resolve5(modelsRoot, `${modelName}${extension}`);
1831
1959
  try {
1832
1960
  const moduleValue = await importRuntimeModule(projectRoot, candidate);
1833
1961
  if ("default" in moduleValue) {
@@ -2053,7 +2181,7 @@ async function registerProjectQueueJobs(projectRoot, registry, queueModule) {
2053
2181
  if (existing && !existing.sourcePath) {
2054
2182
  continue;
2055
2183
  }
2056
- const moduleValue = await importRuntimeModule(projectRoot, resolve3(projectRoot, entry.sourcePath));
2184
+ const moduleValue = await importRuntimeModule(projectRoot, resolve5(projectRoot, entry.sourcePath));
2057
2185
  const job = resolveQueueJobExport(queueModule, moduleValue);
2058
2186
  if (!job) {
2059
2187
  throw new Error(`Discovered job "${entry.sourcePath}" does not export a Holo job.`);
@@ -2118,7 +2246,7 @@ async function registerProjectAuthorizationDefinitions(projectRoot, registry, au
2118
2246
  previousPolicies.set(entry.name, existing);
2119
2247
  authorizationModule.authorizationInternals.unregisterPolicyDefinition(entry.name);
2120
2248
  }
2121
- const moduleValue = await importRuntimeModule(projectRoot, resolve3(projectRoot, entry.sourcePath));
2249
+ const moduleValue = await importRuntimeModule(projectRoot, resolve5(projectRoot, entry.sourcePath));
2122
2250
  const policy = resolveAuthorizationDefinitionExport(
2123
2251
  moduleValue,
2124
2252
  entry.exportName,
@@ -2146,7 +2274,7 @@ async function registerProjectAuthorizationDefinitions(projectRoot, registry, au
2146
2274
  previousAbilities.set(entry.name, existing);
2147
2275
  authorizationModule.authorizationInternals.unregisterAbilityDefinition(entry.name);
2148
2276
  }
2149
- const moduleValue = await importRuntimeModule(projectRoot, resolve3(projectRoot, entry.sourcePath));
2277
+ const moduleValue = await importRuntimeModule(projectRoot, resolve5(projectRoot, entry.sourcePath));
2150
2278
  const ability = resolveAuthorizationDefinitionExport(
2151
2279
  moduleValue,
2152
2280
  entry.exportName,
@@ -2214,7 +2342,7 @@ async function registerProjectEventsAndListeners(projectRoot, registry, eventsMo
2214
2342
  if (existing && !existing.sourcePath) {
2215
2343
  continue;
2216
2344
  }
2217
- const moduleValue = await importRuntimeModule(projectRoot, resolve3(projectRoot, entry.sourcePath));
2345
+ const moduleValue = await importRuntimeModule(projectRoot, resolve5(projectRoot, entry.sourcePath));
2218
2346
  const event = resolveEventExport(moduleValue);
2219
2347
  if (!event || !eventsModule.isEventDefinition(event)) {
2220
2348
  throw new Error(`Discovered event "${entry.sourcePath}" does not export a Holo event.`);
@@ -2231,7 +2359,7 @@ async function registerProjectEventsAndListeners(projectRoot, registry, eventsMo
2231
2359
  if (existing && !existing.sourcePath) {
2232
2360
  continue;
2233
2361
  }
2234
- const moduleValue = await importRuntimeModule(projectRoot, resolve3(projectRoot, entry.sourcePath));
2362
+ const moduleValue = await importRuntimeModule(projectRoot, resolve5(projectRoot, entry.sourcePath));
2235
2363
  const listener = resolveListenerExport(eventsModule, moduleValue);
2236
2364
  if (!listener) {
2237
2365
  throw new Error(`Discovered listener "${entry.sourcePath}" does not export a Holo listener.`);
@@ -2277,13 +2405,27 @@ function unregisterProjectEventsAndListeners(eventsModule, eventNames, listenerI
2277
2405
  }
2278
2406
  }
2279
2407
  async function reconfigureOptionalHoloSubsystems(projectRoot, loadedConfig, options = {}) {
2408
+ const pluginDefinitions = await loadConfiguredHoloPluginDefinitions(projectRoot, resolveLoadedPluginNames(loadedConfig));
2280
2409
  const cacheConfigured = hasLoadedConfigFile(loadedConfig, "cache");
2281
2410
  const cacheModule = await loadCacheModule(cacheConfigured, projectRoot);
2282
- if (cacheModule) {
2411
+ const cacheConfig = loadedConfig.cache;
2412
+ if (cacheModule && cacheConfig) {
2413
+ await loadConfiguredCacheDrivers(projectRoot, cacheConfig);
2414
+ const configuredPluginDrivers = Object.entries(cacheConfig.drivers).flatMap(([name, driver]) => {
2415
+ if (driver.driver === "memory" || driver.driver === "file" || driver.driver === "redis" || driver.driver === "database") return [];
2416
+ return [{ ...driver, name, driver: driver.driver }];
2417
+ });
2418
+ const loadedPluginDrivers = await cacheModule.loadConfiguredCachePluginDriverContracts(
2419
+ projectRoot,
2420
+ resolveLoadedPluginNames(loadedConfig),
2421
+ configuredPluginDrivers
2422
+ );
2423
+ const cachePluginDrivers = new Map(loadedPluginDrivers.map((driver) => [driver.name, driver]));
2283
2424
  cacheModule.configureCacheRuntime({
2284
- config: loadedConfig.cache,
2425
+ config: cacheConfig,
2285
2426
  databaseConfig: loadedConfig.database,
2286
- redisConfig: loadedConfig.redis
2427
+ redisConfig: loadedConfig.redis,
2428
+ ...cachePluginDrivers.size > 0 ? { drivers: cachePluginDrivers } : {}
2287
2429
  });
2288
2430
  }
2289
2431
  const queueConfigured = hasLoadedConfigFile(loadedConfig, "queue");
@@ -2291,14 +2433,30 @@ async function reconfigureOptionalHoloSubsystems(projectRoot, loadedConfig, opti
2291
2433
  if (queueModule) {
2292
2434
  const queueUsesExplicitDatabaseFeatures = queueConfigured && (queueConfigUsesDatabaseDriver(loadedConfig) || queueConfigUsesDatabaseBackedFailedStore(loadedConfig));
2293
2435
  const queueUsesImplicitDefaultFailedStore = !queueConfigured && queueConfigUsesDatabaseBackedFailedStore(loadedConfig);
2294
- const queueDbModule = queueUsesExplicitDatabaseFeatures || queueUsesImplicitDefaultFailedStore ? await loadQueueDbModule() : void 0;
2436
+ const queueDbModule = queueUsesExplicitDatabaseFeatures || queueUsesImplicitDefaultFailedStore ? await loadQueueDbModule(projectRoot) : void 0;
2437
+ const queueUsesRedis = queueConfigUsesRedisDriver(loadedConfig);
2438
+ const queueRedisModule = queueUsesRedis ? await loadQueueRedisModule(projectRoot) : void 0;
2295
2439
  if (queueUsesExplicitDatabaseFeatures && !queueDbModule) {
2296
2440
  throw new Error("[@holo-js/core] Database-backed queue features require @holo-js/queue-db to be installed.");
2297
2441
  }
2442
+ if (queueUsesRedis && !queueRedisModule) {
2443
+ throw new Error("[@holo-js/core] Redis-backed queue connections require @holo-js/queue-redis to be installed.");
2444
+ }
2445
+ const queuePluginDriverFactories = await queueModule.loadQueuePluginDriverFactories(
2446
+ projectRoot,
2447
+ resolveLoadedPluginNames(loadedConfig)
2448
+ );
2449
+ const queueDbRuntimeOptions = queueDbModule?.createQueueDbRuntimeOptions() ?? {};
2450
+ const queueDriverFactories = mergeQueueRuntimeDriverFactories(
2451
+ queuePluginDriverFactories,
2452
+ queueDbRuntimeOptions.driverFactories,
2453
+ queueRedisModule ? [queueRedisModule.redisQueueDriverFactory] : void 0
2454
+ );
2298
2455
  queueModule.configureQueueRuntime({
2299
2456
  config: loadedConfig.queue,
2300
2457
  redisConfig: loadedConfig.redis,
2301
- ...queueDbModule?.createQueueDbRuntimeOptions() ?? {}
2458
+ ...queueDbRuntimeOptions,
2459
+ ...queueDriverFactories.length > 0 ? { driverFactories: queueDriverFactories } : {}
2302
2460
  });
2303
2461
  }
2304
2462
  const storageConfigured = hasLoadedConfigFile(loadedConfig, "storage");
@@ -2306,7 +2464,7 @@ async function reconfigureOptionalHoloSubsystems(projectRoot, loadedConfig, opti
2306
2464
  if (!storageInstalled && storageConfigured) {
2307
2465
  throw new Error("[@holo-js/core] Storage support requires @holo-js/storage to be installed.");
2308
2466
  }
2309
- if (storageInstalled) {
2467
+ if (storageInstalled && loadedConfig.storage) {
2310
2468
  await configurePlainNodeStorageRuntime(projectRoot, loadedConfig);
2311
2469
  }
2312
2470
  const mailConfigured = hasLoadedConfigFile(loadedConfig, "mail");
@@ -2316,7 +2474,9 @@ async function reconfigureOptionalHoloSubsystems(projectRoot, loadedConfig, opti
2316
2474
  mailModule.configureMailRuntime({
2317
2475
  ...existingMailBindings,
2318
2476
  config: loadedConfig.mail,
2319
- ...options.renderView ?? getRuntimeState().renderView ? { renderView: options.renderView ?? getRuntimeState().renderView } : {}
2477
+ projectRoot,
2478
+ plugins: loadedConfig.app.plugins,
2479
+ ...options.renderView ?? getHoloRenderingRuntime().renderView ? { renderView: options.renderView ?? getHoloRenderingRuntime().renderView } : {}
2320
2480
  });
2321
2481
  }
2322
2482
  const broadcastConfigured = hasLoadedConfigFile(loadedConfig, "broadcast");
@@ -2326,6 +2486,8 @@ async function reconfigureOptionalHoloSubsystems(projectRoot, loadedConfig, opti
2326
2486
  broadcastModule.configureBroadcastRuntime({
2327
2487
  ...existingBroadcastBindings,
2328
2488
  config: loadedConfig.broadcast,
2489
+ projectRoot,
2490
+ plugins: loadedConfig.app.plugins,
2329
2491
  ...!existingBroadcastBindings.publish || isCoreBroadcastPublisher(existingBroadcastBindings.publish) ? {
2330
2492
  publish: createCoreBroadcastPublisher(loadedConfig.broadcast)
2331
2493
  } : {}
@@ -2338,6 +2500,16 @@ async function reconfigureOptionalHoloSubsystems(projectRoot, loadedConfig, opti
2338
2500
  notificationsModule.configureNotificationsRuntime({
2339
2501
  ...existingNotificationsBindings,
2340
2502
  config: loadedConfig.notifications,
2503
+ deferAfterCommit(callback) {
2504
+ const connection = connectionAsyncContext.getActive()?.connection;
2505
+ if (!connection || connection.getScope().kind === "root") {
2506
+ return false;
2507
+ }
2508
+ connection.afterCommit(callback);
2509
+ return true;
2510
+ },
2511
+ projectRoot,
2512
+ plugins: loadedConfig.app.plugins,
2341
2513
  store: existingNotificationsBindings.store ?? createCoreNotificationStore(loadedConfig),
2342
2514
  ...!existingNotificationsBindings.mailer && mailModule ? { mailer: createCoreNotificationMailSender(mailModule) } : {},
2343
2515
  ...!existingNotificationsBindings.broadcaster && broadcastModule ? { broadcaster: createCoreNotificationBroadcaster(broadcastModule) } : {}
@@ -2509,6 +2681,9 @@ async function reconfigureOptionalHoloSubsystems(projectRoot, loadedConfig, opti
2509
2681
  } else if (authorizationModule) {
2510
2682
  authorizationModule.authorizationInternals.resetAuthorizationAuthIntegration();
2511
2683
  }
2684
+ for (const bootModule of await loadConfiguredHoloPluginBootModules(projectRoot, pluginDefinitions)) {
2685
+ await bootConfiguredHoloPluginModule(projectRoot, loadedConfig, bootModule);
2686
+ }
2512
2687
  return Object.freeze({
2513
2688
  /* v8 ignore next -- only toggles shape when queue support is absent */
2514
2689
  ...queueModule ? { queueModule } : {},
@@ -2518,6 +2693,7 @@ async function reconfigureOptionalHoloSubsystems(projectRoot, loadedConfig, opti
2518
2693
  });
2519
2694
  }
2520
2695
  async function resetOptionalHoloSubsystems() {
2696
+ resetBootedHoloPluginModules();
2521
2697
  const projectRoot = getRuntimeState().current?.projectRoot ?? getRuntimeState().pendingProjectRoot;
2522
2698
  await resetOptionalStorageRuntime();
2523
2699
  const cacheModule = await loadCacheModule(false, projectRoot);
@@ -2527,7 +2703,10 @@ async function resetOptionalHoloSubsystems() {
2527
2703
  resetCacheRuntimeGlobalsFallback();
2528
2704
  }
2529
2705
  const queueModule = await loadQueueModule();
2530
- await queueModule?.shutdownQueueRuntime();
2706
+ if (queueModule) {
2707
+ await queueModule.shutdownQueueRuntime();
2708
+ queueModule.resetQueueRuntime?.();
2709
+ }
2531
2710
  const mailModule = await loadMailModule();
2532
2711
  mailModule?.resetMailRuntime();
2533
2712
  const notificationsModule = await loadNotificationsModule();
@@ -2567,15 +2746,32 @@ async function resetOptionalHoloSubsystems() {
2567
2746
  securityModule?.resetSecurityRuntime();
2568
2747
  }
2569
2748
  async function createHolo(projectRoot, options = {}) {
2749
+ await loadInstalledFeatureConfigContributions(projectRoot);
2570
2750
  const loadedConfig = await loadConfigDirectory(projectRoot, {
2571
2751
  envName: options.envName,
2572
2752
  preferCache: options.preferCache,
2573
2753
  processEnv: options.processEnv
2574
2754
  });
2755
+ const fallbackQueueConfig = Object.freeze({
2756
+ default: "sync",
2757
+ failed: Object.freeze({
2758
+ driver: "database",
2759
+ connection: "default",
2760
+ table: "failed_jobs"
2761
+ }),
2762
+ connections: Object.freeze({
2763
+ sync: Object.freeze({
2764
+ driver: "sync",
2765
+ queue: "default"
2766
+ })
2767
+ })
2768
+ });
2769
+ const queueConfig = loadedConfig.queue ?? fallbackQueueConfig;
2575
2770
  const runtimeConfig = {
2576
2771
  db: loadedConfig.database,
2577
- queue: loadedConfig.queue
2772
+ queue: queueConfig
2578
2773
  };
2774
+ const databaseDriverFactories = await loadConfiguredDatabaseDrivers(projectRoot, loadedConfig);
2579
2775
  const manager = resolveRuntimeConnectionManagerOptions(runtimeConfig);
2580
2776
  const registry = await loadGeneratedProjectRegistry(projectRoot);
2581
2777
  const accessors = createConfigAccessors(loadedConfig.all);
@@ -2591,11 +2787,105 @@ async function createHolo(projectRoot, options = {}) {
2591
2787
  let activeAuthRuntime;
2592
2788
  let activeAuthContext;
2593
2789
  let previousOptionalSubsystemBindings;
2594
- const previousRenderView = options.renderView ? getRuntimeState().renderView : void 0;
2790
+ const previousRenderView = options.renderView ? getHoloRenderingRuntime().renderView : void 0;
2595
2791
  const fallbackQueueRuntime = Object.freeze({
2596
- config: loadedConfig.queue,
2792
+ config: queueConfig,
2597
2793
  drivers: /* @__PURE__ */ new Map()
2598
2794
  });
2795
+ const unregisterRuntimeContributions = () => {
2796
+ unregisterProjectEventsAndListeners(activeEventsModule, runtimeOwnedEventNames, runtimeOwnedListenerIds);
2797
+ runtimeOwnedEventNames.splice(0);
2798
+ runtimeOwnedListenerIds.splice(0);
2799
+ unregisterProjectAuthorizationDefinitions(activeAuthorizationModule, runtimeOwnedAuthorizationPolicyNames, runtimeOwnedAuthorizationAbilityNames);
2800
+ runtimeOwnedAuthorizationPolicyNames.splice(0);
2801
+ runtimeOwnedAuthorizationAbilityNames.splice(0);
2802
+ unregisterProjectQueueJobs(activeQueueModule, runtimeOwnedQueueJobNames);
2803
+ runtimeOwnedQueueJobNames.splice(0);
2804
+ activeAuthorizationModule = void 0;
2805
+ activeEventsModule = void 0;
2806
+ activeQueueModule = void 0;
2807
+ activeSessionRuntime = void 0;
2808
+ activeAuthRuntime = void 0;
2809
+ activeAuthContext = void 0;
2810
+ };
2811
+ const initializeRuntimeServices = async () => {
2812
+ if (!shouldBootRuntimeServices(options.processEnv)) return;
2813
+ const optionalSubsystems = await reconfigureOptionalHoloSubsystems(projectRoot, loadedConfig, {
2814
+ renderView: options.renderView,
2815
+ authRequest: options.authRequest,
2816
+ authorizationError: options.authorizationError
2817
+ });
2818
+ activeQueueModule = optionalSubsystems.queueModule;
2819
+ activeSessionRuntime = optionalSubsystems.session;
2820
+ activeAuthRuntime = optionalSubsystems.auth;
2821
+ activeAuthContext = optionalSubsystems.authContext;
2822
+ const optionalEventsModule = activeQueueModule ? await loadEventsModule() : void 0;
2823
+ if (activeQueueModule && optionalEventsModule) {
2824
+ await optionalEventsModule.ensureEventsQueueJobRegisteredAsync?.();
2825
+ }
2826
+ if (registryHasEvents(registry)) {
2827
+ const eventsModule = await loadEventsModule(true);
2828
+ if (!eventsModule) throw new Error("[@holo-js/core] Events support requires @holo-js/events to be installed.");
2829
+ activeEventsModule = eventsModule;
2830
+ const eventRegistration = await registerProjectEventsAndListeners(projectRoot, registry, eventsModule, activeQueueModule);
2831
+ runtimeOwnedEventNames.push(...eventRegistration.eventNames);
2832
+ runtimeOwnedListenerIds.push(...eventRegistration.listenerIds);
2833
+ }
2834
+ activeAuthorizationModule = await loadAuthorizationModule();
2835
+ const authorizationRegistration = await registerProjectAuthorizationDefinitions(projectRoot, registry, activeAuthorizationModule);
2836
+ runtimeOwnedAuthorizationPolicyNames.push(...authorizationRegistration.policyNames);
2837
+ runtimeOwnedAuthorizationAbilityNames.push(...authorizationRegistration.abilityNames);
2838
+ if (options.registerProjectQueueJobs !== false && registryHasJobs(registry)) {
2839
+ if (!activeQueueModule) throw new Error("[@holo-js/core] Project jobs require @holo-js/queue to be installed.");
2840
+ runtimeOwnedQueueJobNames.push(...await registerProjectQueueJobs(projectRoot, registry, activeQueueModule));
2841
+ }
2842
+ };
2843
+ const runtimeLifecycle = createRuntimeLifecycle([
2844
+ {
2845
+ name: "configuration",
2846
+ async initialize() {
2847
+ configureConfigRuntime(loadedConfig.all);
2848
+ await preloadGeneratedSchemaModule(projectRoot, registry);
2849
+ await preloadDiscoveredModelModules(projectRoot, registry);
2850
+ previousOptionalSubsystemBindings = snapshotOptionalSubsystemRuntimeBindings();
2851
+ if (options.renderView) configureHoloRenderingRuntime({ renderView: options.renderView });
2852
+ },
2853
+ dispose() {
2854
+ if (options.renderView) restoreHoloRenderingRuntime(previousRenderView);
2855
+ resetConfigRuntime();
2856
+ }
2857
+ },
2858
+ {
2859
+ name: "database",
2860
+ dependsOn: ["configuration"],
2861
+ async initialize() {
2862
+ for (const factory of databaseDriverFactories) registerDatabaseDriverFactory(factory);
2863
+ configureDB(manager);
2864
+ if (shouldBootRuntimeServices(options.processEnv)) await manager.initializeAll();
2865
+ },
2866
+ async dispose() {
2867
+ try {
2868
+ await manager.disconnectAll();
2869
+ } finally {
2870
+ resetDB();
2871
+ for (const factory of [...databaseDriverFactories].reverse()) unregisterDatabaseDriverFactory(factory);
2872
+ }
2873
+ }
2874
+ },
2875
+ {
2876
+ name: "optional-subsystems",
2877
+ dependsOn: ["database"],
2878
+ initialize: initializeRuntimeServices,
2879
+ async dispose() {
2880
+ unregisterRuntimeContributions();
2881
+ try {
2882
+ await resetOptionalHoloSubsystems();
2883
+ } finally {
2884
+ if (previousOptionalSubsystemBindings) restoreOptionalSubsystemRuntimeBindings(previousOptionalSubsystemBindings);
2885
+ }
2886
+ }
2887
+ }
2888
+ ]);
2599
2889
  const runtime = {
2600
2890
  projectRoot,
2601
2891
  loadedConfig,
@@ -2617,150 +2907,31 @@ async function createHolo(projectRoot, options = {}) {
2617
2907
  setAuthRequestAccessors(authRequest) {
2618
2908
  activeAuthContext?.setRequestAccessors?.(authRequest);
2619
2909
  },
2620
- async runWithAuthRequestAccessors(authRequest, callback) {
2910
+ runWithAuthRequestAccessors(authRequest, callback) {
2621
2911
  const runner = activeAuthContext?.runWithRequestAccessors;
2622
- return runner ? await runner(authRequest, callback) : await callback();
2912
+ return runner ? runner(authRequest, callback) : callback();
2623
2913
  },
2624
2914
  async initialize() {
2625
- if (runtime.initialized) {
2626
- throw new Error("Holo runtime is already initialized.");
2627
- }
2628
- if (getRuntimeState().current) {
2629
- throw new Error("A Holo runtime is already initialized for this process.");
2630
- }
2631
- configureConfigRuntime(loadedConfig.all);
2632
- configureDB(manager);
2633
- await preloadGeneratedSchemaModule(projectRoot, registry);
2634
- await preloadDiscoveredModelModules(projectRoot, registry);
2635
- previousOptionalSubsystemBindings = snapshotOptionalSubsystemRuntimeBindings();
2636
- if (options.renderView) {
2637
- configureHoloRenderingRuntime({
2638
- renderView: options.renderView
2639
- });
2640
- }
2641
- try {
2642
- if (shouldBootRuntimeServices(options.processEnv)) {
2643
- await manager.initializeAll();
2644
- const optionalSubsystems = await reconfigureOptionalHoloSubsystems(projectRoot, loadedConfig, {
2645
- renderView: options.renderView,
2646
- authRequest: options.authRequest,
2647
- authorizationError: options.authorizationError
2648
- });
2649
- activeQueueModule = optionalSubsystems.queueModule;
2650
- activeSessionRuntime = optionalSubsystems.session;
2651
- activeAuthRuntime = optionalSubsystems.auth;
2652
- activeAuthContext = optionalSubsystems.authContext;
2653
- const optionalEventsModule = activeQueueModule ? await loadEventsModule() : void 0;
2654
- if (activeQueueModule && optionalEventsModule) {
2655
- await optionalEventsModule.ensureEventsQueueJobRegisteredAsync?.();
2656
- }
2657
- if (registryHasEvents(registry)) {
2658
- const eventsModule = await loadEventsModule(true);
2659
- if (!eventsModule) {
2660
- throw new Error("[@holo-js/core] Events support requires @holo-js/events to be installed.");
2661
- }
2662
- activeEventsModule = eventsModule;
2663
- const eventRegistration = await registerProjectEventsAndListeners(
2664
- projectRoot,
2665
- registry,
2666
- eventsModule,
2667
- activeQueueModule
2668
- );
2669
- runtimeOwnedEventNames.splice(0, runtimeOwnedEventNames.length, ...eventRegistration.eventNames);
2670
- runtimeOwnedListenerIds.splice(0, runtimeOwnedListenerIds.length, ...eventRegistration.listenerIds);
2671
- }
2672
- activeAuthorizationModule = await loadAuthorizationModule();
2673
- const authorizationRegistration = await registerProjectAuthorizationDefinitions(
2674
- projectRoot,
2675
- registry,
2676
- activeAuthorizationModule
2677
- );
2678
- runtimeOwnedAuthorizationPolicyNames.splice(0, runtimeOwnedAuthorizationPolicyNames.length, ...authorizationRegistration.policyNames);
2679
- runtimeOwnedAuthorizationAbilityNames.splice(0, runtimeOwnedAuthorizationAbilityNames.length, ...authorizationRegistration.abilityNames);
2680
- if (options.registerProjectQueueJobs !== false && registryHasJobs(registry)) {
2681
- if (!activeQueueModule) {
2682
- throw new Error("[@holo-js/core] Project jobs require @holo-js/queue to be installed.");
2683
- }
2684
- runtimeOwnedQueueJobNames.splice(0, runtimeOwnedQueueJobNames.length);
2685
- runtimeOwnedQueueJobNames.push(...await registerProjectQueueJobs(projectRoot, registry, activeQueueModule));
2686
- }
2687
- }
2688
- runtime.initialized = true;
2689
- getRuntimeState().current = runtime;
2690
- } catch (error) {
2691
- unregisterProjectEventsAndListeners(activeEventsModule, runtimeOwnedEventNames, runtimeOwnedListenerIds);
2692
- runtimeOwnedEventNames.splice(0, runtimeOwnedEventNames.length);
2693
- runtimeOwnedListenerIds.splice(0, runtimeOwnedListenerIds.length);
2694
- unregisterProjectAuthorizationDefinitions(activeAuthorizationModule, runtimeOwnedAuthorizationPolicyNames, runtimeOwnedAuthorizationAbilityNames);
2695
- runtimeOwnedAuthorizationPolicyNames.splice(0, runtimeOwnedAuthorizationPolicyNames.length);
2696
- runtimeOwnedAuthorizationAbilityNames.splice(0, runtimeOwnedAuthorizationAbilityNames.length);
2697
- unregisterProjectQueueJobs(activeQueueModule, runtimeOwnedQueueJobNames);
2698
- runtimeOwnedQueueJobNames.splice(0, runtimeOwnedQueueJobNames.length);
2699
- activeAuthorizationModule = void 0;
2700
- activeEventsModule = void 0;
2701
- activeQueueModule = void 0;
2702
- activeSessionRuntime = void 0;
2703
- activeAuthRuntime = void 0;
2704
- activeAuthContext = void 0;
2705
- await manager.disconnectAll().catch(() => {
2706
- });
2707
- resetDB();
2708
- await resetOptionalHoloSubsystems();
2709
- if (previousOptionalSubsystemBindings) {
2710
- restoreOptionalSubsystemRuntimeBindings(previousOptionalSubsystemBindings);
2711
- }
2712
- if (options.renderView) {
2713
- restoreHoloRenderingRuntime(previousRenderView);
2714
- }
2715
- resetConfigRuntime();
2716
- getRuntimeState().current = void 0;
2717
- throw error;
2718
- }
2915
+ if (runtime.initialized) throw new Error("Holo runtime is already initialized.");
2916
+ if (getRuntimeState().current) throw new Error("A Holo runtime is already initialized for this process.");
2917
+ await runtimeLifecycle.initialize({ projectRoot });
2918
+ runtime.initialized = true;
2919
+ getRuntimeState().current = runtime;
2719
2920
  },
2720
2921
  async shutdown() {
2721
- try {
2722
- if (runtime.initialized) {
2723
- await manager.disconnectAll();
2724
- }
2725
- } finally {
2726
- runtime.initialized = false;
2727
- if (getRuntimeState().current === runtime) {
2728
- getRuntimeState().current = void 0;
2729
- }
2730
- unregisterProjectEventsAndListeners(activeEventsModule, runtimeOwnedEventNames, runtimeOwnedListenerIds);
2731
- runtimeOwnedEventNames.splice(0, runtimeOwnedEventNames.length);
2732
- runtimeOwnedListenerIds.splice(0, runtimeOwnedListenerIds.length);
2733
- unregisterProjectAuthorizationDefinitions(activeAuthorizationModule, runtimeOwnedAuthorizationPolicyNames, runtimeOwnedAuthorizationAbilityNames);
2734
- runtimeOwnedAuthorizationPolicyNames.splice(0, runtimeOwnedAuthorizationPolicyNames.length);
2735
- runtimeOwnedAuthorizationAbilityNames.splice(0, runtimeOwnedAuthorizationAbilityNames.length);
2736
- unregisterProjectQueueJobs(activeQueueModule, runtimeOwnedQueueJobNames);
2737
- runtimeOwnedQueueJobNames.splice(0, runtimeOwnedQueueJobNames.length);
2738
- activeAuthorizationModule = void 0;
2739
- activeEventsModule = void 0;
2740
- activeQueueModule = void 0;
2741
- activeSessionRuntime = void 0;
2742
- activeAuthRuntime = void 0;
2743
- activeAuthContext = void 0;
2744
- resetDB();
2745
- await resetOptionalHoloSubsystems();
2746
- if (previousOptionalSubsystemBindings) {
2747
- restoreOptionalSubsystemRuntimeBindings(previousOptionalSubsystemBindings);
2748
- }
2749
- if (options.renderView) {
2750
- restoreHoloRenderingRuntime(previousRenderView);
2751
- }
2752
- resetConfigRuntime();
2753
- }
2922
+ runtime.initialized = false;
2923
+ if (getRuntimeState().current === runtime) getRuntimeState().current = void 0;
2924
+ await runtimeLifecycle.dispose({ projectRoot });
2754
2925
  }
2755
2926
  };
2756
2927
  return runtime;
2757
2928
  }
2758
2929
  async function initializeHolo(projectRoot, options = {}) {
2759
2930
  const state = getRuntimeState();
2760
- const resolvedProjectRoot = resolve3(projectRoot);
2931
+ const resolvedProjectRoot = resolve5(projectRoot);
2761
2932
  const current = state.current;
2762
2933
  if (current) {
2763
- if (resolve3(current.projectRoot) !== resolvedProjectRoot) {
2934
+ if (resolve5(current.projectRoot) !== resolvedProjectRoot) {
2764
2935
  throw new Error(`A Holo runtime is already initialized for "${current.projectRoot}".`);
2765
2936
  }
2766
2937
  ;
@@ -2768,7 +2939,7 @@ async function initializeHolo(projectRoot, options = {}) {
2768
2939
  return current;
2769
2940
  }
2770
2941
  if (state.pending) {
2771
- if (state.pendingProjectRoot && resolve3(state.pendingProjectRoot) !== resolvedProjectRoot) {
2942
+ if (state.pendingProjectRoot && resolve5(state.pendingProjectRoot) !== resolvedProjectRoot) {
2772
2943
  throw new Error(`A Holo runtime is already initializing for "${state.pendingProjectRoot}".`);
2773
2944
  }
2774
2945
  return state.pending.then((runtime) => {
@@ -2801,7 +2972,7 @@ async function ensureHolo(projectRoot, options = {}) {
2801
2972
  if (!current) {
2802
2973
  return initializeHolo(projectRoot, options);
2803
2974
  }
2804
- if (resolve3(current.projectRoot) !== resolve3(projectRoot)) {
2975
+ if (resolve5(current.projectRoot) !== resolve5(projectRoot)) {
2805
2976
  throw new Error(`A Holo runtime is already initialized for "${current.projectRoot}".`);
2806
2977
  }
2807
2978
  return current;