@holo-js/core 0.2.6 → 0.3.1

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
@@ -156,14 +179,18 @@ async function importModule(specifier) {
156
179
  );
157
180
  }
158
181
  var runtimeModuleRequire = createRequire(import.meta.url);
182
+ function resolveRuntimeModuleSearchPaths(projectRoot, runtimeModuleUrl = import.meta.url) {
183
+ const runtimeModulePath = normalizeImportTarget(runtimeModuleUrl);
184
+ return runtimeModulePath.includes("/node_modules/@holo-js/core/") ? void 0 : [projectRoot];
185
+ }
159
186
  function resolveOptionalImportSpecifier(specifier, projectRoot) {
160
187
  if (!projectRoot) {
161
188
  return specifier;
162
189
  }
163
190
  try {
164
- return pathToFileURL(runtimeModuleRequire.resolve(specifier, {
165
- paths: [projectRoot]
166
- })).href;
191
+ const paths = resolveRuntimeModuleSearchPaths(projectRoot);
192
+ const resolved = paths ? runtimeModuleRequire.resolve(specifier, { paths: [...paths] }) : runtimeModuleRequire.resolve(specifier);
193
+ return pathToFileURL(resolved).href;
167
194
  } catch {
168
195
  return specifier;
169
196
  }
@@ -181,6 +208,16 @@ function normalizeImportSpecifier(specifier) {
181
208
  function normalizeImportTarget(value) {
182
209
  return normalizeImportSpecifier(value).replace(/\\/g, "/");
183
210
  }
211
+ function resolvePackageRootSpecifier(specifier) {
212
+ if (specifier.startsWith("@")) {
213
+ const [scope, packageName] = specifier.split("/");
214
+ return scope && packageName ? `${scope}/${packageName}` : void 0;
215
+ }
216
+ if (specifier.startsWith(".") || specifier.startsWith("/") || specifier.includes(":")) {
217
+ return void 0;
218
+ }
219
+ return specifier.split("/")[0];
220
+ }
184
221
  function matchesRelativeImportTarget(failedTarget, specifier) {
185
222
  if (!failedTarget || !specifier.startsWith(".")) {
186
223
  return false;
@@ -200,6 +237,10 @@ function isMissingOptionalModule(error, specifier, resolvedSpecifier) {
200
237
  normalizeImportTarget(specifier),
201
238
  normalizeImportTarget(resolvedSpecifier)
202
239
  ]);
240
+ const packageRootSpecifier = resolvePackageRootSpecifier(specifier);
241
+ if (packageRootSpecifier) {
242
+ expectedTargets.add(packageRootSpecifier);
243
+ }
203
244
  const normalizedFailedTarget = typeof failedTarget === "string" ? normalizeImportTarget(failedTarget) : void 0;
204
245
  const matchesRequestedTarget = typeof normalizedFailedTarget === "string" && (expectedTargets.has(normalizedFailedTarget) || matchesRelativeImportTarget(normalizedFailedTarget, specifier));
205
246
  return "code" in error && error.code === "ERR_MODULE_NOT_FOUND" && matchesRequestedTarget || message.startsWith("Cannot find package '") && matchesRequestedTarget || message.startsWith("Cannot find module '") && matchesRequestedTarget || message.includes("Does the file exist?") && message.startsWith("Failed to load url ") && matchesRequestedTarget || message.startsWith('Could not resolve "') && matchesRequestedTarget;
@@ -317,8 +358,10 @@ var runtimeModuleInternals = {
317
358
  bundleRuntimeModule,
318
359
  importModule,
319
360
  importOptionalRuntimeModule,
361
+ isMissingOptionalModule,
320
362
  loadEsbuild,
321
363
  pathExists,
364
+ resolveRuntimeModuleSearchPaths,
322
365
  runEsbuild,
323
366
  writeLoaderTsconfig
324
367
  };
@@ -326,8 +369,8 @@ var runtimeModuleInternals = {
326
369
  // src/storageRuntime.ts
327
370
  import { mkdir as mkdir2, readFile as readFile2, readdir, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
328
371
  import { dirname, isAbsolute, join as join2, relative, resolve as resolve2, sep, win32 } from "node:path";
329
- async function importOptionalModule(specifier) {
330
- return importOptionalRuntimeModule(specifier);
372
+ async function importOptionalModule(specifier, projectRoot) {
373
+ return importOptionalRuntimeModule(specifier, projectRoot ? { projectRoot } : {});
331
374
  }
332
375
  function resolveStorageKeyPath(root, key) {
333
376
  if (isAbsolute(key) || win32.isAbsolute(key)) {
@@ -427,8 +470,8 @@ function createFileStorageBackend(root) {
427
470
  }
428
471
  };
429
472
  }
430
- async function createS3StorageBackend(disk) {
431
- const storageS3 = await importOptionalModule("@holo-js/storage/runtime/drivers/s3");
473
+ async function createS3StorageBackend(projectRoot, disk) {
474
+ const storageS3 = await importOptionalModule("@holo-js/storage-s3", projectRoot);
432
475
  if (!storageS3) {
433
476
  throw new Error("[@holo-js/core] Storage config references an s3 disk but @holo-js/storage-s3 is not installed.");
434
477
  }
@@ -455,7 +498,7 @@ async function configurePlainNodeStorageRuntime(projectRoot, loadedConfig) {
455
498
  });
456
499
  const backends = /* @__PURE__ */ new Map();
457
500
  for (const [diskName, disk] of Object.entries(normalizedStorage.disks)) {
458
- const backend = disk.driver === "s3" ? await createS3StorageBackend(disk) : createFileStorageBackend(resolve2(projectRoot, disk.root));
501
+ const backend = disk.driver === "s3" ? await createS3StorageBackend(projectRoot, disk) : createFileStorageBackend(resolve2(projectRoot, disk.root));
459
502
  backends.set(diskName, backend);
460
503
  }
461
504
  storageRuntime.configureStorageRuntime({
@@ -481,150 +524,231 @@ var storageRuntimeInternals = {
481
524
  importOptionalModule
482
525
  };
483
526
 
484
- // src/portable/holo.ts
527
+ // src/portable/discoveryRuntime.ts
528
+ import { existsSync } from "node:fs";
529
+ import { resolve as resolve3 } from "node:path";
485
530
  async function preloadGeneratedSchemaModule(projectRoot, registry) {
486
531
  const entry = registry?.paths.generatedSchema;
487
- if (!entry) {
488
- return;
489
- }
532
+ if (!entry) return;
490
533
  const expectedTarget = resolve3(projectRoot, entry);
491
- if (!existsSync(expectedTarget)) {
492
- return;
493
- }
534
+ if (!existsSync(expectedTarget)) return;
494
535
  try {
495
536
  await importBundledRuntimeModule(projectRoot, entry);
496
537
  } catch (error) {
497
538
  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
- }
539
+ 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");
540
+ if (failedTarget === expectedTarget) return;
503
541
  }
504
542
  throw error;
505
543
  }
506
544
  }
507
545
  async function preloadDiscoveredModelModules(projectRoot, registry) {
508
- if (!registry || registry.models.length === 0) {
509
- return;
510
- }
546
+ if (!registry || registry.models.length === 0) return;
511
547
  for (const entry of registry.models) {
512
548
  const sourcePath = resolve3(projectRoot, entry.sourcePath);
513
- if (!existsSync(sourcePath)) {
514
- continue;
515
- }
516
- await importBundledRuntimeModule(projectRoot, sourcePath);
549
+ if (existsSync(sourcePath)) await importBundledRuntimeModule(projectRoot, sourcePath);
517
550
  }
518
551
  }
519
- function closeSessionRedisAdapter(adapter) {
520
- return adapter.disconnect?.() || adapter.close?.();
552
+
553
+ // src/portable/configRuntime.ts
554
+ var featureConfigContributionSpecifiers = [
555
+ "@holo-js/auth/config",
556
+ "@holo-js/broadcast/config",
557
+ "@holo-js/cache/config",
558
+ "@holo-js/mail/config",
559
+ "@holo-js/media/config",
560
+ "@holo-js/notifications/config",
561
+ "@holo-js/queue/config",
562
+ "@holo-js/security/config",
563
+ "@holo-js/session/config",
564
+ "@holo-js/storage/config"
565
+ ];
566
+ async function loadDirectFeatureConfigContribution(specifier) {
567
+ switch (specifier) {
568
+ case "@holo-js/auth/config":
569
+ await import("@holo-js/auth/config");
570
+ break;
571
+ case "@holo-js/broadcast/config":
572
+ await import("@holo-js/broadcast/config");
573
+ break;
574
+ case "@holo-js/cache/config":
575
+ await import("@holo-js/cache/config");
576
+ break;
577
+ case "@holo-js/mail/config":
578
+ await import("@holo-js/mail/config");
579
+ break;
580
+ case "@holo-js/media/config":
581
+ await import("@holo-js/media/config");
582
+ break;
583
+ case "@holo-js/notifications/config":
584
+ await import("@holo-js/notifications/config");
585
+ break;
586
+ case "@holo-js/queue/config":
587
+ await import("@holo-js/queue/config");
588
+ break;
589
+ case "@holo-js/security/config":
590
+ await import("@holo-js/security/config");
591
+ break;
592
+ case "@holo-js/session/config":
593
+ await import("@holo-js/session/config");
594
+ break;
595
+ case "@holo-js/storage/config":
596
+ await import("@holo-js/storage/config");
597
+ break;
598
+ }
599
+ }
600
+ var defaultLoaders = {
601
+ loadDirect: loadDirectFeatureConfigContribution,
602
+ async loadOptional(specifier, projectRoot) {
603
+ return await importOptionalRuntimeModule(specifier, { projectRoot });
604
+ }
605
+ };
606
+ async function loadInstalledFeatureConfigContributions(projectRoot, loaders = defaultLoaders) {
607
+ for (const specifier of featureConfigContributionSpecifiers) {
608
+ try {
609
+ await loaders.loadDirect(specifier);
610
+ } catch {
611
+ const loaded = await loaders.loadOptional(specifier, projectRoot);
612
+ if (!loaded) continue;
613
+ }
614
+ }
521
615
  }
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";
616
+
617
+ // src/portable/authPersistence.ts
618
+ function normalizeDateValue(value) {
619
+ return value instanceof Date ? value : new Date(String(value));
526
620
  }
527
- function getRuntimeState() {
528
- const runtime = globalThis;
529
- runtime.__holoRuntime__ ??= {};
530
- return runtime.__holoRuntime__;
621
+ function normalizeJsonValue(value) {
622
+ if (typeof value !== "string") return value;
623
+ try {
624
+ return JSON.parse(value);
625
+ } catch {
626
+ return value;
627
+ }
531
628
  }
532
- function configureHoloRenderingRuntime(bindings) {
533
- getRuntimeState().renderView = bindings?.renderView;
629
+ function normalizeStoredUserId(value) {
630
+ return typeof value === "number" ? value : String(value);
534
631
  }
535
- function resetHoloRenderingRuntime() {
536
- getRuntimeState().renderView = void 0;
632
+ function normalizeAccessTokenRecord(row) {
633
+ const abilities = normalizeJsonValue(row.abilities);
634
+ return Object.freeze({
635
+ id: String(row.id),
636
+ provider: String(row.provider),
637
+ userId: normalizeStoredUserId(row.user_id),
638
+ name: String(row.name),
639
+ abilities: Array.isArray(abilities) ? Object.freeze([...abilities]) : Object.freeze([]),
640
+ tokenHash: String(row.token_hash),
641
+ createdAt: normalizeDateValue(row.created_at),
642
+ lastUsedAt: row.last_used_at ? normalizeDateValue(row.last_used_at) : void 0,
643
+ expiresAt: row.expires_at ? normalizeDateValue(row.expires_at) : null
644
+ });
537
645
  }
538
- function restoreHoloRenderingRuntime(renderView) {
539
- if (renderView) {
540
- configureHoloRenderingRuntime({
541
- renderView
542
- });
543
- return;
544
- }
545
- resetHoloRenderingRuntime();
646
+ function serializeAccessTokenRecord(record) {
647
+ return {
648
+ id: record.id,
649
+ provider: record.provider,
650
+ user_id: String(record.userId),
651
+ name: record.name,
652
+ abilities: JSON.stringify(record.abilities),
653
+ token_hash: record.tokenHash,
654
+ created_at: record.createdAt.toISOString(),
655
+ last_used_at: record.lastUsedAt?.toISOString() ?? null,
656
+ expires_at: record.expiresAt?.toISOString() ?? null,
657
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
658
+ };
546
659
  }
547
- function snapshotOptionalSubsystemRuntimeBindings() {
548
- const state = getRuntimeState();
549
- const runtime = globalThis;
660
+ function normalizeEmailVerificationTokenRecord(row) {
550
661
  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
- } : {}
662
+ id: String(row.id),
663
+ provider: String(row.provider),
664
+ userId: normalizeStoredUserId(row.user_id),
665
+ email: String(row.email),
666
+ tokenHash: String(row.token_hash),
667
+ createdAt: normalizeDateValue(row.created_at),
668
+ expiresAt: normalizeDateValue(row.expires_at)
568
669
  });
569
670
  }
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);
671
+ function serializeEmailVerificationTokenRecord(record) {
672
+ return {
673
+ id: record.id,
674
+ provider: record.provider,
675
+ user_id: String(record.userId),
676
+ email: record.email,
677
+ token_hash: record.tokenHash,
678
+ created_at: record.createdAt.toISOString(),
679
+ expires_at: record.expiresAt.toISOString(),
680
+ used_at: null,
681
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
682
+ };
596
683
  }
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`);
684
+ function normalizePasswordResetTokenRecord(row) {
685
+ return Object.freeze({
686
+ id: String(row.id),
687
+ provider: typeof row.provider === "string" ? row.provider : "users",
688
+ email: String(row.email),
689
+ table: typeof row.__holo_table === "string" ? row.__holo_table : void 0,
690
+ tokenHash: String(row.token_hash),
691
+ createdAt: normalizeDateValue(row.created_at),
692
+ expiresAt: normalizeDateValue(row.expires_at)
604
693
  });
605
694
  }
606
- function queueConfigUsesDatabaseDriver(loadedConfig) {
607
- return Object.values(loadedConfig.queue.connections).some((connection) => connection.driver === "database");
695
+ function serializePasswordResetTokenRecord(record) {
696
+ return {
697
+ id: record.id,
698
+ provider: record.provider,
699
+ email: record.email,
700
+ token_hash: record.tokenHash,
701
+ created_at: record.createdAt.toISOString(),
702
+ expires_at: record.expiresAt.toISOString(),
703
+ used_at: null,
704
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
705
+ };
608
706
  }
609
- function queueConfigUsesDatabaseBackedFailedStore(loadedConfig) {
610
- return loadedConfig.queue.failed !== false;
707
+
708
+ // src/portable/pluginRuntime.ts
709
+ import { resolve as resolve4 } from "node:path";
710
+ import {
711
+ loadHoloPluginBootModules,
712
+ loadHoloPluginDefinitions
713
+ } from "@holo-js/kernel";
714
+ function isRecord2(value) {
715
+ return !!value && typeof value === "object" && !Array.isArray(value);
611
716
  }
612
- function registryHasJobs(registry) {
613
- return (registry?.jobs.length ?? 0) > 0;
717
+ var bootedHoloPluginModules = /* @__PURE__ */ new Set();
718
+ function resolveHoloPluginBootKey(projectRoot, bootModule) {
719
+ return [resolve4(projectRoot), bootModule.plugin.packageName, bootModule.runtime].join("\0");
614
720
  }
615
- function registryHasEvents(registry) {
616
- return (registry?.events.length ?? 0) > 0 || (registry?.listeners.length ?? 0) > 0;
721
+ function resolveLoadedPluginNames(loadedConfig) {
722
+ return loadedConfig.app.plugins;
617
723
  }
618
- function authConfigUsesSocialProviders(loadedConfig) {
619
- return Object.keys(loadedConfig.auth.social).length > 0;
724
+ async function bootConfiguredHoloPluginModule(projectRoot, loadedConfig, bootModule) {
725
+ const bootKey = resolveHoloPluginBootKey(projectRoot, bootModule);
726
+ if (bootedHoloPluginModules.has(bootKey)) return;
727
+ const candidate = isRecord2(bootModule.module) && typeof bootModule.module.default !== "undefined" ? bootModule.module.default : bootModule.module;
728
+ if (typeof candidate !== "function") return;
729
+ await candidate({ projectRoot, config: loadedConfig });
730
+ bootedHoloPluginModules.add(bootKey);
620
731
  }
621
- function authConfigUsesWorkosProviders(loadedConfig) {
622
- return Object.entries(loadedConfig.auth.workos).some(([name, provider]) => name !== "provider" && name !== "identityStore" && typeof provider === "object" && provider !== null);
732
+ function resetBootedHoloPluginModules() {
733
+ bootedHoloPluginModules.clear();
623
734
  }
624
- function authConfigUsesClerkProviders(loadedConfig) {
625
- return Object.entries(loadedConfig.auth.clerk).some(([name, provider]) => name !== "provider" && name !== "identityStore" && typeof provider === "object" && provider !== null);
735
+ async function loadConfiguredHoloPluginDefinitions(projectRoot, pluginNames) {
736
+ const normalizedNames = [...new Set(pluginNames.map((name) => name.trim()).filter(Boolean))];
737
+ return loadHoloPluginDefinitions(projectRoot, normalizedNames, { moduleVersion: String(Date.now()) });
626
738
  }
627
- var HOLO_AUTH_PROVIDER_MARKER = /* @__PURE__ */ Symbol.for("holo-js.auth.provider");
739
+ async function loadConfiguredHoloPluginBootModules(projectRoot, plugins) {
740
+ return loadHoloPluginBootModules(projectRoot, plugins, { moduleVersion: String(Date.now()) });
741
+ }
742
+ function mergeQueueRuntimeDriverFactories(...sources) {
743
+ const factories = /* @__PURE__ */ new Map();
744
+ for (const source of sources) {
745
+ for (const factory of source ?? []) factories.set(factory.driver, factory);
746
+ }
747
+ return Object.freeze([...factories.values()]);
748
+ }
749
+
750
+ // src/portable/authRequestContext.ts
751
+ import { AsyncLocalStorage } from "node:async_hooks";
628
752
  function attachAuthRequestAccessors(context, accessors) {
629
753
  return Object.freeze({
630
754
  ...context,
@@ -656,141 +780,503 @@ function createRequestAwareAuthContext(context, accessors) {
656
780
  return resolveRequestContext().redirectResponse?.(url, status);
657
781
  },
658
782
  setRequestAccessors(nextAccessors) {
659
- requestAccessorStorage.enterWith({
660
- accessors: nextAccessors
661
- });
783
+ requestAccessorStorage.enterWith({ accessors: nextAccessors });
662
784
  },
663
- async runWithRequestAccessors(nextAccessors, callback) {
664
- return await requestAccessorStorage.run({
665
- accessors: nextAccessors
666
- }, callback);
785
+ runWithRequestAccessors(nextAccessors, callback) {
786
+ return requestAccessorStorage.run(
787
+ { accessors: nextAccessors },
788
+ () => context.run ? context.run(callback) : callback()
789
+ );
667
790
  }
668
791
  });
669
792
  }
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");
793
+
794
+ // src/portable/recordPersistence.ts
795
+ function normalizeDateLike(value) {
796
+ return value instanceof Date ? value : new Date(String(value));
679
797
  }
680
- async function loadCacheModule(required = false, projectRoot) {
681
- const cacheModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/cache", {
682
- projectRoot
798
+ function normalizeSessionRecordFromRow(row) {
799
+ const decodedData = (() => {
800
+ if (row.data && typeof row.data === "object") return row.data;
801
+ if (typeof row.data !== "string") return {};
802
+ try {
803
+ const parsed = JSON.parse(row.data);
804
+ return parsed && typeof parsed === "object" ? parsed : {};
805
+ } catch {
806
+ return {};
807
+ }
808
+ })();
809
+ return Object.freeze({
810
+ id: String(row.id),
811
+ store: typeof row.store === "string" ? row.store : "database",
812
+ data: Object.freeze(decodedData),
813
+ createdAt: normalizeDateLike(row.created_at),
814
+ lastActivityAt: normalizeDateLike(row.last_activity_at),
815
+ expiresAt: normalizeDateLike(row.expires_at),
816
+ rememberTokenHash: typeof row.remember_token_hash === "string" ? row.remember_token_hash : void 0
683
817
  });
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
818
  }
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;
819
+ function serializeSessionRecordForRow(record) {
820
+ return {
821
+ id: record.id,
822
+ store: record.store,
823
+ data: JSON.stringify(record.data ?? {}),
824
+ created_at: record.createdAt.toISOString(),
825
+ last_activity_at: record.lastActivityAt.toISOString(),
826
+ expires_at: record.expiresAt.toISOString(),
827
+ invalidated_at: null,
828
+ remember_token_hash: record.rememberTokenHash ?? null
829
+ };
742
830
  }
743
- async function loadBroadcastModule(required = false, projectRoot) {
744
- const broadcastModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/broadcast", {
745
- projectRoot
831
+ function normalizeNotificationRecordFromRow(row) {
832
+ const decodedData = normalizeJsonValue(row.data);
833
+ return Object.freeze({
834
+ id: String(row.id),
835
+ type: typeof row.type === "string" ? row.type : void 0,
836
+ notifiableType: String(row.notifiable_type),
837
+ notifiableId: typeof row.notifiable_id === "number" ? row.notifiable_id : String(row.notifiable_id),
838
+ data: decodedData,
839
+ readAt: row.read_at ? normalizeDateLike(row.read_at) : null,
840
+ createdAt: normalizeDateLike(row.created_at),
841
+ updatedAt: normalizeDateLike(row.updated_at)
746
842
  });
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
- }
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
843
  }
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;
765
- }
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;
772
- }
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.");
777
- }
778
- return socialModule;
779
- }
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.");
784
- }
785
- return workosModule;
844
+ function serializeNotificationRecordForRow(record) {
845
+ return {
846
+ id: record.id,
847
+ type: record.type ?? null,
848
+ notifiable_type: record.notifiableType,
849
+ notifiable_id: String(record.notifiableId),
850
+ data: JSON.stringify(record.data ?? null),
851
+ read_at: record.readAt ? record.readAt.toISOString() : null,
852
+ created_at: record.createdAt.toISOString(),
853
+ updated_at: record.updatedAt.toISOString()
854
+ };
855
+ }
856
+
857
+ // src/portable/notificationPersistence.ts
858
+ import { DB } from "@holo-js/db";
859
+ function createCoreNotificationStore(loadedConfig) {
860
+ const tableName = loadedConfig.notifications.table;
861
+ const connectionName = loadedConfig.database.defaultConnection;
862
+ return Object.freeze({
863
+ async create(record) {
864
+ await DB.table(tableName, connectionName).insert(serializeNotificationRecordForRow(record));
865
+ },
866
+ async list(notifiable) {
867
+ const rows = await DB.table(tableName, connectionName).where("notifiable_type", notifiable.type).where("notifiable_id", String(notifiable.id)).orderBy("created_at", "desc").get();
868
+ return Object.freeze(rows.map((row) => normalizeNotificationRecordFromRow(row)));
869
+ },
870
+ async unread(notifiable) {
871
+ 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();
872
+ return Object.freeze(rows.map((row) => normalizeNotificationRecordFromRow(row)));
873
+ },
874
+ async markAsRead(ids) {
875
+ if (ids.length === 0) return 0;
876
+ const now = (/* @__PURE__ */ new Date()).toISOString();
877
+ const result = await DB.table(tableName, connectionName).whereIn("id", ids).update({ read_at: now, updated_at: now });
878
+ return result.affectedRows ?? 0;
879
+ },
880
+ async markAsUnread(ids) {
881
+ if (ids.length === 0) return 0;
882
+ const result = await DB.table(tableName, connectionName).whereIn("id", ids).update({ read_at: null, updated_at: (/* @__PURE__ */ new Date()).toISOString() });
883
+ return result.affectedRows ?? 0;
884
+ },
885
+ async delete(ids) {
886
+ if (ids.length === 0) return 0;
887
+ const result = await DB.table(tableName, connectionName).whereIn("id", ids).delete();
888
+ return result.affectedRows ?? 0;
889
+ }
890
+ });
891
+ }
892
+
893
+ // src/portable/authMailDelivery.ts
894
+ var authEmailDateFormatter = new Intl.DateTimeFormat("en-US", {
895
+ dateStyle: "long",
896
+ timeStyle: "short",
897
+ timeZone: "UTC"
898
+ });
899
+ function formatAuthEmailExpiration(expiresAt) {
900
+ return `${authEmailDateFormatter.format(expiresAt)} UTC`;
901
+ }
902
+ function escapeAuthEmailHtml(value) {
903
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
904
+ }
905
+ function createNotificationMailText(message) {
906
+ const parts = [
907
+ typeof message.greeting === "string" ? message.greeting.trim() : void 0,
908
+ ...(message.lines ?? []).map((line) => line.trim()).filter(Boolean),
909
+ message.action ? `${message.action.label}: ${message.action.url}` : void 0
910
+ ].filter((value) => typeof value === "string" && value.length > 0);
911
+ return parts.length > 0 ? parts.join("\n\n") : void 0;
912
+ }
913
+ function createNotificationMailHtml(message) {
914
+ const greeting = typeof message.greeting === "string" ? message.greeting.trim() : void 0;
915
+ const lines = (message.lines ?? []).map((line) => line.trim()).filter(Boolean);
916
+ const sections = [
917
+ greeting ? `<p style="margin:0 0 16px;">${escapeAuthEmailHtml(greeting)}</p>` : "",
918
+ ...lines.map((line) => `<p style="margin:0 0 16px;">${escapeAuthEmailHtml(line)}</p>`),
919
+ 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>` : "",
920
+ 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>` : ""
921
+ ].join("");
922
+ return [
923
+ "<!doctype html>",
924
+ '<html><head><meta charset="utf-8">',
925
+ `<title>${escapeAuthEmailHtml(message.subject)}</title>`,
926
+ '</head><body style="margin:0;padding:24px;font-family:Arial,sans-serif;color:#0f172a;background:#f8fafc;">',
927
+ '<div style="max-width:640px;margin:0 auto;background:#ffffff;border:1px solid #e2e8f0;border-radius:12px;padding:32px;">',
928
+ `<h1 style="margin:0 0 24px;font-size:24px;line-height:1.3;">${escapeAuthEmailHtml(message.subject)}</h1>`,
929
+ sections,
930
+ "</div></body></html>"
931
+ ].join("");
932
+ }
933
+ function createAuthActionUrl(appUrl, path, token) {
934
+ const normalizedBaseUrl = appUrl.endsWith("/") ? appUrl.slice(0, -1) : appUrl;
935
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
936
+ const url = new URL(`${normalizedBaseUrl}${normalizedPath}`);
937
+ url.searchParams.set("token", token);
938
+ return url.toString();
939
+ }
940
+ function createAuthEmailHtml(message) {
941
+ return createNotificationMailHtml(message);
942
+ }
943
+ function createCoreNotificationMailSender(mailModule) {
944
+ return Object.freeze({
945
+ async send(message, context) {
946
+ if (!context.route) {
947
+ throw new Error("[@holo-js/core] Email notifications require a resolved email route before bridging into mail.");
948
+ }
949
+ const fallbackText = createNotificationMailText(message);
950
+ await mailModule.sendMail({
951
+ to: context.route,
952
+ subject: message.subject,
953
+ html: typeof message.html === "string" ? message.html : createNotificationMailHtml(message),
954
+ ...typeof (message.text ?? fallbackText) === "string" ? { text: message.text ?? fallbackText } : {},
955
+ ...message.metadata ? { metadata: message.metadata } : {}
956
+ });
957
+ }
958
+ });
959
+ }
960
+ function createAuthMailDeliveryHook(mailModule, appUrl) {
961
+ return Object.freeze({
962
+ async sendEmailVerification(input) {
963
+ const recipientName = typeof input.user?.name === "string" ? input.user.name?.trim() : void 0;
964
+ const lines = [
965
+ "Confirm your account to finish signing in.",
966
+ `This verification link expires at ${formatAuthEmailExpiration(input.token.expiresAt)}.`
967
+ ];
968
+ const action = {
969
+ label: "Verify email address",
970
+ url: createAuthActionUrl(appUrl, input.route, input.token.plainTextToken)
971
+ };
972
+ await mailModule.sendMail({
973
+ to: { email: input.email, ...recipientName ? { name: recipientName } : {} },
974
+ subject: "Verify your email address",
975
+ html: createAuthEmailHtml({
976
+ subject: "Verify your email address",
977
+ ...recipientName ? { greeting: `Hello ${recipientName},` } : {},
978
+ lines,
979
+ action
980
+ }),
981
+ text: createNotificationMailText({
982
+ ...recipientName ? { greeting: `Hello ${recipientName},` } : {},
983
+ lines,
984
+ action
985
+ }),
986
+ metadata: { provider: input.provider, tokenId: input.token.id }
987
+ });
988
+ },
989
+ async sendPasswordReset(input) {
990
+ const lines = [
991
+ "Click the link below to choose a new password.",
992
+ `This reset link expires at ${formatAuthEmailExpiration(input.token.expiresAt)}.`
993
+ ];
994
+ const action = {
995
+ label: "Reset password",
996
+ url: createAuthActionUrl(appUrl, input.route, input.token.plainTextToken)
997
+ };
998
+ await mailModule.sendMail({
999
+ to: input.email,
1000
+ subject: "Reset your password",
1001
+ html: createAuthEmailHtml({ subject: "Reset your password", lines, action }),
1002
+ text: createNotificationMailText({ lines, action }),
1003
+ metadata: { provider: input.provider, tokenId: input.token.id }
1004
+ });
1005
+ }
1006
+ });
1007
+ }
1008
+
1009
+ // src/portable/optionalFeatureLoader.ts
1010
+ function createOptionalFeatureModuleLoader(importer, specifier, missingPackageMessage) {
1011
+ async function load(required = false, options = {}) {
1012
+ const module = await importer(specifier, options);
1013
+ if (!module && required) throw new Error(missingPackageMessage);
1014
+ return module;
1015
+ }
1016
+ return load;
1017
+ }
1018
+
1019
+ // src/portable/featureDetection.ts
1020
+ function hasLoadedConfigFile(loadedConfig, configName) {
1021
+ return loadedConfig.loadedFiles.some((filePath) => {
1022
+ const normalizedPath = filePath.replaceAll("\\", "/");
1023
+ return ["ts", "mts", "js", "mjs", "cts", "cjs"].some((extension) => normalizedPath.endsWith(`/config/${configName}.${extension}`));
1024
+ });
1025
+ }
1026
+ function queueConfigUsesDatabaseDriver(loadedConfig) {
1027
+ if (!loadedConfig.queue) return false;
1028
+ return Object.values(loadedConfig.queue.connections).some((connection) => connection.driver === "database");
1029
+ }
1030
+ function queueConfigUsesRedisDriver(loadedConfig) {
1031
+ if (!loadedConfig.queue) return false;
1032
+ return Object.values(loadedConfig.queue.connections).some((connection) => connection.driver === "redis");
1033
+ }
1034
+ function queueConfigUsesDatabaseBackedFailedStore(loadedConfig) {
1035
+ if (!loadedConfig.queue) return false;
1036
+ return loadedConfig.queue.failed !== false;
1037
+ }
1038
+ function registryHasJobs(registry) {
1039
+ return (registry?.jobs.length ?? 0) > 0;
1040
+ }
1041
+ function registryHasEvents(registry) {
1042
+ return (registry?.events.length ?? 0) > 0 || (registry?.listeners.length ?? 0) > 0;
1043
+ }
1044
+ function authConfigUsesSocialProviders(loadedConfig) {
1045
+ if (!loadedConfig.auth) return false;
1046
+ return Object.keys(loadedConfig.auth.social).length > 0;
1047
+ }
1048
+ function authConfigUsesWorkosProviders(loadedConfig) {
1049
+ if (!loadedConfig.auth) return false;
1050
+ return Object.entries(loadedConfig.auth.workos).some(([name, provider]) => name !== "provider" && name !== "identityStore" && typeof provider === "object" && provider !== null);
1051
+ }
1052
+ function authConfigUsesClerkProviders(loadedConfig) {
1053
+ if (!loadedConfig.auth) return false;
1054
+ return Object.entries(loadedConfig.auth.clerk).some(([name, provider]) => name !== "provider" && name !== "identityStore" && typeof provider === "object" && provider !== null);
1055
+ }
1056
+
1057
+ // src/portable/runtimeState.ts
1058
+ function createRuntimeStateAccessors() {
1059
+ const getRuntimeState2 = () => {
1060
+ const runtime = globalThis;
1061
+ runtime.__holoRuntime__ ??= {};
1062
+ return runtime.__holoRuntime__;
1063
+ };
1064
+ const snapshotOptionalSubsystemRuntimeBindings2 = () => {
1065
+ const state = getRuntimeState2();
1066
+ const runtime = globalThis;
1067
+ return Object.freeze({
1068
+ ...runtime.__holoMailRuntime__?.bindings ? { mail: runtime.__holoMailRuntime__.bindings } : {},
1069
+ ...runtime.__holoNotificationsRuntime__?.bindings ? { notifications: runtime.__holoNotificationsRuntime__.bindings } : {},
1070
+ ...runtime.__holoBroadcastRuntime__?.bindings ? { broadcast: runtime.__holoBroadcastRuntime__.bindings } : {},
1071
+ ...state.sessionRedisAdapters ? { session: Object.freeze({ sessionRedisAdapters: state.sessionRedisAdapters }) } : {},
1072
+ ...runtime.__holoSecurityRuntime__?.bindings || state.securityRedisAdapter || typeof state.securityRateLimitStoreManaged !== "undefined" ? {
1073
+ security: Object.freeze({
1074
+ ...runtime.__holoSecurityRuntime__?.bindings ? { bindings: runtime.__holoSecurityRuntime__.bindings } : {},
1075
+ ...state.securityRedisAdapter ? { securityRedisAdapter: state.securityRedisAdapter } : {},
1076
+ ...typeof state.securityRateLimitStoreManaged !== "undefined" ? { securityRateLimitStoreManaged: state.securityRateLimitStoreManaged } : {}
1077
+ })
1078
+ } : {}
1079
+ });
1080
+ };
1081
+ const restoreOptionalSubsystemRuntimeBindings2 = (bindings) => {
1082
+ const state = getRuntimeState2();
1083
+ const runtime = globalThis;
1084
+ if (bindings.mail || runtime.__holoMailRuntime__) {
1085
+ runtime.__holoMailRuntime__ ??= {};
1086
+ runtime.__holoMailRuntime__.bindings = bindings.mail;
1087
+ }
1088
+ if (bindings.notifications || runtime.__holoNotificationsRuntime__) {
1089
+ runtime.__holoNotificationsRuntime__ ??= {};
1090
+ runtime.__holoNotificationsRuntime__.bindings = bindings.notifications;
1091
+ }
1092
+ if (bindings.broadcast || runtime.__holoBroadcastRuntime__) {
1093
+ runtime.__holoBroadcastRuntime__ ??= {};
1094
+ runtime.__holoBroadcastRuntime__.bindings = bindings.broadcast;
1095
+ }
1096
+ state.sessionRedisAdapters = bindings.session?.sessionRedisAdapters;
1097
+ if (bindings.security || runtime.__holoSecurityRuntime__) {
1098
+ runtime.__holoSecurityRuntime__ ??= {};
1099
+ runtime.__holoSecurityRuntime__.bindings = bindings.security?.bindings;
1100
+ state.securityRedisAdapter = bindings.security?.securityRedisAdapter;
1101
+ state.securityRateLimitStoreManaged = bindings.security?.securityRateLimitStoreManaged;
1102
+ }
1103
+ };
1104
+ return {
1105
+ getRuntimeState: getRuntimeState2,
1106
+ restoreOptionalSubsystemRuntimeBindings: restoreOptionalSubsystemRuntimeBindings2,
1107
+ snapshotOptionalSubsystemRuntimeBindings: snapshotOptionalSubsystemRuntimeBindings2
1108
+ };
1109
+ }
1110
+
1111
+ // src/portable/holo.ts
1112
+ function closeSessionRedisAdapter(adapter) {
1113
+ return adapter.disconnect?.() || adapter.close?.();
1114
+ }
1115
+ var CORE_BROADCAST_PUBLISHER_MARKER = /* @__PURE__ */ Symbol.for("holo-js.core.broadcast.publisher");
1116
+ var frameworkBuildEnvKey = "HOLO_INTERNAL_FRAMEWORK_BUILD";
1117
+ function shouldBootRuntimeServices(processEnv = process.env) {
1118
+ return processEnv[frameworkBuildEnvKey] !== "1";
1119
+ }
1120
+ var {
1121
+ getRuntimeState,
1122
+ restoreOptionalSubsystemRuntimeBindings,
1123
+ snapshotOptionalSubsystemRuntimeBindings
1124
+ } = createRuntimeStateAccessors();
1125
+ var BROADCAST_PUBLISH_TIMEOUT_MS = 1e4;
1126
+ async function importOptionalModule2(specifier, options = {}) {
1127
+ return importOptionalRuntimeModule(specifier, options);
1128
+ }
1129
+ var portableRuntimeModuleInternals = {
1130
+ importOptionalModule: importOptionalModule2
1131
+ };
1132
+ var HOLO_AUTH_PROVIDER_MARKER = /* @__PURE__ */ Symbol.for("holo-js.auth.provider");
1133
+ var importOptionalFeature = (specifier, options) => portableRuntimeModuleInternals.importOptionalModule(specifier, options);
1134
+ var loadQueueModule = createOptionalFeatureModuleLoader(
1135
+ importOptionalFeature,
1136
+ "@holo-js/queue",
1137
+ "[@holo-js/core] Queue support requires @holo-js/queue to be installed."
1138
+ );
1139
+ async function loadConfiguredDatabaseDrivers(projectRoot, loadedConfig) {
1140
+ const packageByDriver = {
1141
+ sqlite: { packageName: "@holo-js/db-sqlite", factoryExport: "sqliteDatabaseDriverFactory" },
1142
+ postgres: { packageName: "@holo-js/db-postgres", factoryExport: "postgresDatabaseDriverFactory" },
1143
+ mysql: { packageName: "@holo-js/db-mysql", factoryExport: "mysqlDatabaseDriverFactory" }
1144
+ };
1145
+ const drivers = new Set(Object.values(loadedConfig.database.connections).map((connection) => {
1146
+ if (typeof connection === "string") {
1147
+ if (connection.startsWith("postgres://") || connection.startsWith("postgresql://")) return "postgres";
1148
+ if (connection.startsWith("mysql://") || connection.startsWith("mysql2://")) return "mysql";
1149
+ return "sqlite";
1150
+ }
1151
+ return connection.driver ?? "sqlite";
1152
+ }));
1153
+ const factories = [];
1154
+ for (const driver of drivers) {
1155
+ const contribution = packageByDriver[driver];
1156
+ const { packageName, factoryExport } = contribution;
1157
+ const module = await portableRuntimeModuleInternals.importOptionalModule(packageName, { projectRoot });
1158
+ if (!module) throw new Error(`[@holo-js/core] Database driver "${driver}" requires ${packageName} to be installed.`);
1159
+ const factory = module[factoryExport];
1160
+ if (!factory) throw new Error(`[@holo-js/core] Database driver package ${packageName} does not export ${factoryExport}.`);
1161
+ factories.push(factory);
1162
+ }
1163
+ return factories;
1164
+ }
1165
+ async function loadQueueDbModule(projectRoot) {
1166
+ return createOptionalFeatureModuleLoader(
1167
+ importOptionalFeature,
1168
+ "@holo-js/queue-db",
1169
+ "[@holo-js/core] Database queues require @holo-js/queue-db to be installed."
1170
+ )(false, { projectRoot });
1171
+ }
1172
+ async function loadQueueRedisModule(projectRoot) {
1173
+ return createOptionalFeatureModuleLoader(
1174
+ importOptionalFeature,
1175
+ "@holo-js/queue-redis",
1176
+ "[@holo-js/core] Redis queues require @holo-js/queue-redis to be installed."
1177
+ )(false, { projectRoot });
1178
+ }
1179
+ async function loadCacheModule(required = false, projectRoot) {
1180
+ const loader = createOptionalFeatureModuleLoader(
1181
+ importOptionalFeature,
1182
+ "@holo-js/cache",
1183
+ "[@holo-js/core] Cache support requires @holo-js/cache to be installed."
1184
+ );
1185
+ return required ? loader(true, { projectRoot }) : loader(false, { projectRoot });
1186
+ }
1187
+ async function loadConfiguredCacheDrivers(projectRoot, cacheConfig) {
1188
+ const packageByDriver = {
1189
+ database: "@holo-js/cache-db",
1190
+ redis: "@holo-js/cache-redis"
1191
+ };
1192
+ const drivers = new Set(Object.values(cacheConfig.drivers).map((driver) => driver.driver));
1193
+ for (const driver of drivers) {
1194
+ if (driver !== "database" && driver !== "redis") continue;
1195
+ const packageName = packageByDriver[driver];
1196
+ const module = await portableRuntimeModuleInternals.importOptionalModule(packageName, { projectRoot });
1197
+ if (!module) throw new Error(`[@holo-js/core] Cache driver "${driver}" requires ${packageName} to be installed.`);
1198
+ }
786
1199
  }
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.");
1200
+ function resetCacheRuntimeGlobalsFallback() {
1201
+ const runtime = globalThis;
1202
+ if (runtime.__holoCacheRuntime__) {
1203
+ runtime.__holoCacheRuntime__.bindings = void 0;
1204
+ }
1205
+ if (runtime.__holoCacheQueryBridge__) {
1206
+ runtime.__holoCacheQueryBridge__.dependencyIndex = void 0;
1207
+ }
1208
+ if (runtime.__holoDbQueryCacheBridge__) {
1209
+ runtime.__holoDbQueryCacheBridge__.bridge = void 0;
791
1210
  }
792
- return clerkModule;
793
1211
  }
1212
+ var loadEventsModule = createOptionalFeatureModuleLoader(
1213
+ importOptionalFeature,
1214
+ "@holo-js/events",
1215
+ "[@holo-js/core] Events support requires @holo-js/events to be installed."
1216
+ );
1217
+ var loadSessionModule = createOptionalFeatureModuleLoader(
1218
+ importOptionalFeature,
1219
+ "@holo-js/session",
1220
+ "[@holo-js/core] Session support requires @holo-js/session to be installed."
1221
+ );
1222
+ var loadSecurityModule = createOptionalFeatureModuleLoader(
1223
+ importOptionalFeature,
1224
+ "@holo-js/security",
1225
+ "[@holo-js/core] Security support requires @holo-js/security to be installed."
1226
+ );
1227
+ var loadSecurityRedisAdapterModule = createOptionalFeatureModuleLoader(
1228
+ importOptionalFeature,
1229
+ "@holo-js/security/drivers/redis-adapter",
1230
+ "[@holo-js/core] Redis-backed security rate limits require @holo-js/security/drivers/redis-adapter to be installed."
1231
+ );
1232
+ var loadSessionRedisAdapterModule = createOptionalFeatureModuleLoader(
1233
+ importOptionalFeature,
1234
+ "@holo-js/session/drivers/redis-adapter",
1235
+ "[@holo-js/core] Redis-backed session stores require @holo-js/session/drivers/redis-adapter to be installed."
1236
+ );
1237
+ var loadNotificationsModule = createOptionalFeatureModuleLoader(
1238
+ importOptionalFeature,
1239
+ "@holo-js/notifications",
1240
+ "[@holo-js/core] Notifications support requires @holo-js/notifications to be installed."
1241
+ );
1242
+ async function loadBroadcastModule(required = false, projectRoot) {
1243
+ const loader = createOptionalFeatureModuleLoader(
1244
+ importOptionalFeature,
1245
+ "@holo-js/broadcast",
1246
+ "[@holo-js/core] Broadcast support requires @holo-js/broadcast to be installed."
1247
+ );
1248
+ return required ? loader(true, { projectRoot }) : loader(false, { projectRoot });
1249
+ }
1250
+ var loadMailModule = createOptionalFeatureModuleLoader(
1251
+ importOptionalFeature,
1252
+ "@holo-js/mail",
1253
+ "[@holo-js/core] Mail support requires @holo-js/mail to be installed."
1254
+ );
1255
+ var loadAuthModule = createOptionalFeatureModuleLoader(
1256
+ importOptionalFeature,
1257
+ "@holo-js/auth",
1258
+ "[@holo-js/core] Auth support requires @holo-js/auth to be installed."
1259
+ );
1260
+ var loadAuthorizationModule = createOptionalFeatureModuleLoader(
1261
+ importOptionalFeature,
1262
+ "@holo-js/authorization",
1263
+ "[@holo-js/core] Authorization support requires @holo-js/authorization to be installed."
1264
+ );
1265
+ var loadSocialModule = createOptionalFeatureModuleLoader(
1266
+ importOptionalFeature,
1267
+ "@holo-js/auth-social",
1268
+ "[@holo-js/core] Social auth config requires @holo-js/auth-social to be installed."
1269
+ );
1270
+ var loadWorkosModule = createOptionalFeatureModuleLoader(
1271
+ importOptionalFeature,
1272
+ "@holo-js/auth-workos",
1273
+ "[@holo-js/core] WorkOS auth config requires @holo-js/auth-workos to be installed."
1274
+ );
1275
+ var loadClerkModule = createOptionalFeatureModuleLoader(
1276
+ importOptionalFeature,
1277
+ "@holo-js/auth-clerk",
1278
+ "[@holo-js/core] Clerk auth config requires @holo-js/auth-clerk to be installed."
1279
+ );
794
1280
  function resolveQueueJobExport(queueModule, moduleValue) {
795
1281
  const exports = moduleValue;
796
1282
  if (queueModule.isQueueJobDefinition(exports.default)) {
@@ -831,74 +1317,7 @@ function resolveListenerExport(eventsModule, moduleValue) {
831
1317
  return Object.values(exports).find((value) => hasListenerDefinitionMarker(value) || eventsModule.isListenerDefinition(value));
832
1318
  }
833
1319
  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
- };
1320
+ return value.startsWith(".") || !value.startsWith("/") ? resolve5(projectRoot, value) : value;
902
1321
  }
903
1322
  function getEntityAttributes(value) {
904
1323
  if (value && typeof value === "object") {
@@ -942,20 +1361,20 @@ async function createCoreManagedSessionStores(projectRoot, loadedConfig, session
942
1361
  const connectionName = config.connection === "default" && !(config.connection in loadedConfig.database.connections) ? loadedConfig.database.defaultConnection : config.connection;
943
1362
  stores[name] = sessionModule.createDatabaseSessionStore({
944
1363
  async read(sessionId) {
945
- const row = await DB.table(config.table, connectionName).where("id", sessionId).whereNull("invalidated_at").first();
1364
+ const row = await DB2.table(config.table, connectionName).where("id", sessionId).whereNull("invalidated_at").first();
946
1365
  return row ? normalizeSessionRecordFromRow(row) : null;
947
1366
  },
948
1367
  async write(record) {
949
1368
  const normalized = serializeSessionRecordForRow(record);
950
- const existing = await DB.table(config.table, connectionName).find(String(normalized.id));
1369
+ const existing = await DB2.table(config.table, connectionName).find(String(normalized.id));
951
1370
  if (existing) {
952
- await DB.table(config.table, connectionName).where("id", normalized.id).update(normalized);
1371
+ await DB2.table(config.table, connectionName).where("id", normalized.id).update(normalized);
953
1372
  return;
954
1373
  }
955
- await DB.table(config.table, connectionName).insert(normalized);
1374
+ await DB2.table(config.table, connectionName).insert(normalized);
956
1375
  },
957
1376
  async delete(sessionId) {
958
- await DB.table(config.table, connectionName).where("id", sessionId).delete();
1377
+ await DB2.table(config.table, connectionName).where("id", sessionId).delete();
959
1378
  }
960
1379
  });
961
1380
  continue;
@@ -1000,59 +1419,6 @@ async function createCoreManagedSessionStores(projectRoot, loadedConfig, session
1000
1419
  async function createCoreSessionStores(projectRoot, loadedConfig, sessionModule) {
1001
1420
  return (await createCoreManagedSessionStores(projectRoot, loadedConfig, sessionModule)).stores;
1002
1421
  }
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
1422
  var AUTH_EMAIL_VERIFICATION_NOTIFICATION_PATHS = [
1057
1423
  "server/notifications/auth/email-verification.ts",
1058
1424
  "server/notifications/auth/email-verification.mts",
@@ -1073,7 +1439,7 @@ function resolveExistingProjectFile(projectRoot, candidates) {
1073
1439
  if (!projectRoot) {
1074
1440
  return void 0;
1075
1441
  }
1076
- return candidates.find((candidate) => existsSync(resolve3(projectRoot, candidate)));
1442
+ return candidates.find((candidate) => existsSync2(resolve5(projectRoot, candidate)));
1077
1443
  }
1078
1444
  function resolveAuthNotification(module, exportName, filePath) {
1079
1445
  const notification = module[exportName] ?? module.notification ?? module.default;
@@ -1232,219 +1598,92 @@ function createCoreNotificationBroadcaster(broadcastModule) {
1232
1598
  event,
1233
1599
  channels,
1234
1600
  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
- }
1601
+ ...typeof context.notificationType === "string" && context.notificationType.trim() ? { type: context.notificationType.trim() } : {},
1602
+ data: message.data ?? null
1603
+ })
1418
1604
  });
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
- }
1605
+ }
1606
+ });
1607
+ }
1608
+ function createCoreBroadcastPublisher(loadedConfig) {
1609
+ const connectionHosts = /* @__PURE__ */ new Set(["holo", "pusher"]);
1610
+ const publish = async (input, context) => {
1611
+ const connection = loadedConfig.connections[input.connection];
1612
+ if (!connection) {
1613
+ throw new Error(`[@holo-js/core] Broadcast connection "${input.connection}" is not configured.`);
1614
+ }
1615
+ if (!("appId" in connection) || !("key" in connection) || !("secret" in connection)) {
1616
+ throw new Error(`[@holo-js/core] Broadcast connection "${input.connection}" cannot be published automatically.`);
1617
+ }
1618
+ if (!connectionHosts.has(connection.driver)) {
1619
+ throw new Error(`[@holo-js/core] Broadcast connection "${input.connection}" cannot be published automatically.`);
1620
+ }
1621
+ const options = connection.options;
1622
+ const protocol = options.scheme === "http" ? "http:" : "https:";
1623
+ const url = new URL(`/apps/${encodeURIComponent(connection.appId)}/events`, `${protocol}//${options.host}`);
1624
+ if (typeof options.port === "number") {
1625
+ url.port = String(options.port);
1626
+ }
1627
+ const body = JSON.stringify({
1628
+ name: input.event,
1629
+ channels: input.channels,
1630
+ data: JSON.stringify(input.payload),
1631
+ /* v8 ignore next -- tests do not pass socketId through the publish binding */
1632
+ ...typeof input.socketId === "undefined" ? {} : { socket_id: input.socketId }
1633
+ });
1634
+ const bodyMd5 = createHash("md5").update(body).digest("hex");
1635
+ url.searchParams.set("auth_key", connection.key);
1636
+ url.searchParams.set("auth_timestamp", String(Math.floor(Date.now() / 1e3)));
1637
+ url.searchParams.set("auth_version", "1.0");
1638
+ url.searchParams.set("body_md5", bodyMd5);
1639
+ url.searchParams.set(
1640
+ "auth_signature",
1641
+ createHmac("sha256", connection.secret).update(
1642
+ [
1643
+ "POST",
1644
+ url.pathname,
1645
+ [...url.searchParams.entries()].filter(([key]) => key !== "auth_signature").sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&")
1646
+ ].join("\n")
1647
+ ).digest("hex")
1648
+ );
1649
+ const controller = new AbortController();
1650
+ const timeout = setTimeout(() => controller.abort(), BROADCAST_PUBLISH_TIMEOUT_MS);
1651
+ let response;
1652
+ try {
1653
+ response = await fetch(url, {
1654
+ method: "POST",
1655
+ headers: {
1656
+ "content-type": "application/json"
1657
+ },
1658
+ body,
1659
+ signal: controller.signal
1445
1660
  });
1661
+ } catch (error) {
1662
+ if (error instanceof Error && error.name === "AbortError") {
1663
+ throw new Error(`[@holo-js/core] Broadcast publish request timed out after ${BROADCAST_PUBLISH_TIMEOUT_MS}ms.`);
1664
+ }
1665
+ throw error;
1666
+ } finally {
1667
+ clearTimeout(timeout);
1668
+ }
1669
+ if (!response.ok) {
1670
+ throw new Error(`[@holo-js/core] Broadcast publish request failed with status ${response.status}.`);
1446
1671
  }
1672
+ const result = await response.json();
1673
+ return {
1674
+ connection: input.connection,
1675
+ driver: connection.driver,
1676
+ queued: context.queued,
1677
+ publishedChannels: Array.isArray(result.deliveredChannels) ? Object.freeze(result.deliveredChannels.map((value) => String(value))) : Object.freeze([...input.channels])
1678
+ };
1679
+ };
1680
+ Object.defineProperty(publish, CORE_BROADCAST_PUBLISHER_MARKER, {
1681
+ value: true
1447
1682
  });
1683
+ return publish;
1684
+ }
1685
+ function isCoreBroadcastPublisher(value) {
1686
+ return typeof value === "function" && CORE_BROADCAST_PUBLISHER_MARKER in value;
1448
1687
  }
1449
1688
  async function loadConfiguredSocialProviders(projectRootOrLoadedConfig, maybeLoadedConfig) {
1450
1689
  const projectRoot = typeof projectRootOrLoadedConfig === "string" ? projectRootOrLoadedConfig : (
@@ -1471,97 +1710,6 @@ async function loadConfiguredSocialProviders(projectRootOrLoadedConfig, maybeLoa
1471
1710
  }
1472
1711
  return Object.freeze(providers);
1473
1712
  }
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
1713
  async function createCoreSocialBindings(projectRootOrLoadedConfig, loadedConfigOrSessionModule, maybeSessionModule) {
1566
1714
  const projectRoot = typeof projectRootOrLoadedConfig === "string" ? projectRootOrLoadedConfig : (
1567
1715
  /* turbopackIgnore: true */
@@ -1609,7 +1757,7 @@ async function createCoreSocialBindings(projectRootOrLoadedConfig, loadedConfigO
1609
1757
  });
1610
1758
  const identityStore = Object.freeze({
1611
1759
  async findByProviderUserId(provider, providerUserId) {
1612
- const row = await DB.table("auth_identities").where("provider", provider).where("provider_user_id", providerUserId).first();
1760
+ const row = await DB2.table("auth_identities").where("provider", provider).where("provider_user_id", providerUserId).first();
1613
1761
  if (!row) {
1614
1762
  return null;
1615
1763
  }
@@ -1631,7 +1779,7 @@ async function createCoreSocialBindings(projectRootOrLoadedConfig, loadedConfigO
1631
1779
  },
1632
1780
  async save(record) {
1633
1781
  const value = record;
1634
- const existing = await DB.table("auth_identities").where("provider", value.provider).where("provider_user_id", value.providerUserId).first();
1782
+ const existing = await DB2.table("auth_identities").where("provider", value.provider).where("provider_user_id", value.providerUserId).first();
1635
1783
  const payload = {
1636
1784
  user_id: String(value.userId),
1637
1785
  provider: value.provider,
@@ -1646,10 +1794,10 @@ async function createCoreSocialBindings(projectRootOrLoadedConfig, loadedConfigO
1646
1794
  updated_at: value.updatedAt.toISOString()
1647
1795
  };
1648
1796
  if (existing && typeof existing.id !== "undefined") {
1649
- await DB.table("auth_identities").where("id", existing.id).update(payload);
1797
+ await DB2.table("auth_identities").where("id", existing.id).update(payload);
1650
1798
  return;
1651
1799
  }
1652
- await DB.table("auth_identities").insert(payload);
1800
+ await DB2.table("auth_identities").insert(payload);
1653
1801
  }
1654
1802
  });
1655
1803
  return Object.freeze({
@@ -1683,7 +1831,7 @@ function createCoreHostedIdentityStore(namespace) {
1683
1831
  };
1684
1832
  const findStoredIdentity = async (provider, providerUserId, authProvider = "users") => {
1685
1833
  const providerValue = toHostedIdentityProviderValue(namespace, provider);
1686
- const row = await DB.table("auth_identities").where("provider", providerValue).where("provider_user_id", providerUserId).first();
1834
+ const row = await DB2.table("auth_identities").where("provider", providerValue).where("provider_user_id", providerUserId).first();
1687
1835
  return row ? normalizeRow(row, { provider, providerUserId, authProvider }) : null;
1688
1836
  };
1689
1837
  const toPayload = (record) => {
@@ -1707,7 +1855,7 @@ function createCoreHostedIdentityStore(namespace) {
1707
1855
  },
1708
1856
  async findByUserId(provider, authProvider, userId) {
1709
1857
  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();
1858
+ const row = await DB2.table("auth_identities").where("provider", providerValue).where("auth_provider", authProvider).where("user_id", String(userId)).first();
1711
1859
  if (!row) {
1712
1860
  return null;
1713
1861
  }
@@ -1719,7 +1867,7 @@ function createCoreHostedIdentityStore(namespace) {
1719
1867
  },
1720
1868
  async claim(record) {
1721
1869
  const value = record;
1722
- await DB.table("auth_identities").insertOrIgnore(toPayload(value));
1870
+ await DB2.table("auth_identities").insertOrIgnore(toPayload(value));
1723
1871
  const claimed = await findStoredIdentity(value.provider, value.providerUserId, value.authProvider);
1724
1872
  if (!claimed) {
1725
1873
  throw new Error("[@holo-js/core] Claimed hosted identity could not be read back from auth_identities.");
@@ -1729,13 +1877,13 @@ function createCoreHostedIdentityStore(namespace) {
1729
1877
  async save(record) {
1730
1878
  const value = record;
1731
1879
  const providerValue = toHostedIdentityProviderValue(namespace, value.provider);
1732
- const existing = await DB.table("auth_identities").where("provider", providerValue).where("provider_user_id", value.providerUserId).first();
1880
+ const existing = await DB2.table("auth_identities").where("provider", providerValue).where("provider_user_id", value.providerUserId).first();
1733
1881
  const payload = toPayload(value);
1734
1882
  if (existing && typeof existing.id !== "undefined") {
1735
- await DB.table("auth_identities").where("id", existing.id).update(payload);
1883
+ await DB2.table("auth_identities").where("id", existing.id).update(payload);
1736
1884
  return;
1737
1885
  }
1738
- await DB.table("auth_identities").insert(payload);
1886
+ await DB2.table("auth_identities").insert(payload);
1739
1887
  }
1740
1888
  });
1741
1889
  }
@@ -1743,55 +1891,55 @@ function createCoreAuthStores(loadedConfig) {
1743
1891
  return Object.freeze({
1744
1892
  tokens: Object.freeze({
1745
1893
  async create(record) {
1746
- await DB.table("personal_access_tokens").insert(serializeAccessTokenRecord(record));
1894
+ await DB2.table("personal_access_tokens").insert(serializeAccessTokenRecord(record));
1747
1895
  },
1748
1896
  async findById(id) {
1749
- const row = await DB.table("personal_access_tokens").find(id);
1897
+ const row = await DB2.table("personal_access_tokens").find(id);
1750
1898
  return row ? normalizeAccessTokenRecord(row) : null;
1751
1899
  },
1752
1900
  async listByUserId(provider, userId) {
1753
- const rows = await DB.table("personal_access_tokens").where("provider", provider).where("user_id", String(userId)).get();
1901
+ const rows = await DB2.table("personal_access_tokens").where("provider", provider).where("user_id", String(userId)).get();
1754
1902
  return Object.freeze(rows.map((row) => normalizeAccessTokenRecord(row)));
1755
1903
  },
1756
1904
  async update(record) {
1757
1905
  const payload = serializeAccessTokenRecord(record);
1758
- await DB.table("personal_access_tokens").where("id", String(payload.id)).update(payload);
1906
+ await DB2.table("personal_access_tokens").where("id", String(payload.id)).update(payload);
1759
1907
  },
1760
1908
  async delete(id) {
1761
- await DB.table("personal_access_tokens").where("id", id).delete();
1909
+ await DB2.table("personal_access_tokens").where("id", id).delete();
1762
1910
  },
1763
1911
  async deleteByUserId(provider, userId) {
1764
- const result = await DB.table("personal_access_tokens").where("provider", provider).where("user_id", String(userId)).delete();
1912
+ const result = await DB2.table("personal_access_tokens").where("provider", provider).where("user_id", String(userId)).delete();
1765
1913
  return result.affectedRows ?? 0;
1766
1914
  }
1767
1915
  }),
1768
1916
  emailVerificationTokens: Object.freeze({
1769
1917
  async create(record) {
1770
- await DB.table("email_verification_tokens").insert(serializeEmailVerificationTokenRecord(record));
1918
+ await DB2.table("email_verification_tokens").insert(serializeEmailVerificationTokenRecord(record));
1771
1919
  },
1772
1920
  async findById(id) {
1773
- const row = await DB.table("email_verification_tokens").where("id", id).whereNull("used_at").first();
1921
+ const row = await DB2.table("email_verification_tokens").where("id", id).whereNull("used_at").first();
1774
1922
  return row ? normalizeEmailVerificationTokenRecord(row) : null;
1775
1923
  },
1776
1924
  async delete(id) {
1777
- await DB.table("email_verification_tokens").where("id", id).delete();
1925
+ await DB2.table("email_verification_tokens").where("id", id).delete();
1778
1926
  },
1779
1927
  async deleteByUserId(provider, userId) {
1780
- const result = await DB.table("email_verification_tokens").where("provider", provider).where("user_id", String(userId)).delete();
1928
+ const result = await DB2.table("email_verification_tokens").where("provider", provider).where("user_id", String(userId)).delete();
1781
1929
  return result.affectedRows ?? 0;
1782
1930
  }
1783
1931
  }),
1784
1932
  passwordResetTokens: Object.freeze({
1785
1933
  async create(record) {
1786
1934
  const value = record;
1787
- await DB.table(value.table ?? "password_reset_tokens").insert(serializePasswordResetTokenRecord(value));
1935
+ await DB2.table(value.table ?? "password_reset_tokens").insert(serializePasswordResetTokenRecord(value));
1788
1936
  },
1789
1937
  async findById(id) {
1790
1938
  const tables = Array.from(new Set(
1791
1939
  Object.values(loadedConfig.auth.passwords).map((config) => config.table)
1792
1940
  ));
1793
1941
  for (const table of tables) {
1794
- const row = await DB.table(table).where("id", id).whereNull("used_at").first();
1942
+ const row = await DB2.table(table).where("id", id).whereNull("used_at").first();
1795
1943
  if (row) {
1796
1944
  return normalizePasswordResetTokenRecord({
1797
1945
  ...row,
@@ -1803,7 +1951,7 @@ function createCoreAuthStores(loadedConfig) {
1803
1951
  },
1804
1952
  async findLatestByEmail(provider, email, options) {
1805
1953
  const table = options?.table ?? "password_reset_tokens";
1806
- const row = await DB.table(table).where("provider", provider).where("email", email).latest("created_at").first();
1954
+ const row = await DB2.table(table).where("provider", provider).where("email", email).latest("created_at").first();
1807
1955
  if (!row) {
1808
1956
  return null;
1809
1957
  }
@@ -1814,20 +1962,20 @@ function createCoreAuthStores(loadedConfig) {
1814
1962
  },
1815
1963
  async delete(id, options) {
1816
1964
  const table = options?.table ?? "password_reset_tokens";
1817
- await DB.table(table).where("id", id).delete();
1965
+ await DB2.table(table).where("id", id).delete();
1818
1966
  },
1819
1967
  async deleteByEmail(provider, email, options) {
1820
1968
  const table = options?.table ?? "password_reset_tokens";
1821
- const result = await DB.table(table).where("provider", provider).where("email", email).delete();
1969
+ const result = await DB2.table(table).where("provider", provider).where("email", email).delete();
1822
1970
  return result.affectedRows ?? 0;
1823
1971
  }
1824
1972
  })
1825
1973
  });
1826
1974
  }
1827
1975
  async function resolveAuthProviderRuntime(projectRoot, loadedConfig, modelName) {
1828
- const modelsRoot = resolve3(projectRoot, loadedConfig.app.paths.models);
1976
+ const modelsRoot = resolve5(projectRoot, loadedConfig.app.paths.models);
1829
1977
  for (const extension of [".ts", ".mts", ".js", ".mjs", ".cts", ".cjs"]) {
1830
- const candidate = resolve3(modelsRoot, `${modelName}${extension}`);
1978
+ const candidate = resolve5(modelsRoot, `${modelName}${extension}`);
1831
1979
  try {
1832
1980
  const moduleValue = await importRuntimeModule(projectRoot, candidate);
1833
1981
  if ("default" in moduleValue) {
@@ -2053,7 +2201,7 @@ async function registerProjectQueueJobs(projectRoot, registry, queueModule) {
2053
2201
  if (existing && !existing.sourcePath) {
2054
2202
  continue;
2055
2203
  }
2056
- const moduleValue = await importRuntimeModule(projectRoot, resolve3(projectRoot, entry.sourcePath));
2204
+ const moduleValue = await importRuntimeModule(projectRoot, resolve5(projectRoot, entry.sourcePath));
2057
2205
  const job = resolveQueueJobExport(queueModule, moduleValue);
2058
2206
  if (!job) {
2059
2207
  throw new Error(`Discovered job "${entry.sourcePath}" does not export a Holo job.`);
@@ -2118,7 +2266,7 @@ async function registerProjectAuthorizationDefinitions(projectRoot, registry, au
2118
2266
  previousPolicies.set(entry.name, existing);
2119
2267
  authorizationModule.authorizationInternals.unregisterPolicyDefinition(entry.name);
2120
2268
  }
2121
- const moduleValue = await importRuntimeModule(projectRoot, resolve3(projectRoot, entry.sourcePath));
2269
+ const moduleValue = await importRuntimeModule(projectRoot, resolve5(projectRoot, entry.sourcePath));
2122
2270
  const policy = resolveAuthorizationDefinitionExport(
2123
2271
  moduleValue,
2124
2272
  entry.exportName,
@@ -2146,7 +2294,7 @@ async function registerProjectAuthorizationDefinitions(projectRoot, registry, au
2146
2294
  previousAbilities.set(entry.name, existing);
2147
2295
  authorizationModule.authorizationInternals.unregisterAbilityDefinition(entry.name);
2148
2296
  }
2149
- const moduleValue = await importRuntimeModule(projectRoot, resolve3(projectRoot, entry.sourcePath));
2297
+ const moduleValue = await importRuntimeModule(projectRoot, resolve5(projectRoot, entry.sourcePath));
2150
2298
  const ability = resolveAuthorizationDefinitionExport(
2151
2299
  moduleValue,
2152
2300
  entry.exportName,
@@ -2214,7 +2362,7 @@ async function registerProjectEventsAndListeners(projectRoot, registry, eventsMo
2214
2362
  if (existing && !existing.sourcePath) {
2215
2363
  continue;
2216
2364
  }
2217
- const moduleValue = await importRuntimeModule(projectRoot, resolve3(projectRoot, entry.sourcePath));
2365
+ const moduleValue = await importRuntimeModule(projectRoot, resolve5(projectRoot, entry.sourcePath));
2218
2366
  const event = resolveEventExport(moduleValue);
2219
2367
  if (!event || !eventsModule.isEventDefinition(event)) {
2220
2368
  throw new Error(`Discovered event "${entry.sourcePath}" does not export a Holo event.`);
@@ -2231,7 +2379,7 @@ async function registerProjectEventsAndListeners(projectRoot, registry, eventsMo
2231
2379
  if (existing && !existing.sourcePath) {
2232
2380
  continue;
2233
2381
  }
2234
- const moduleValue = await importRuntimeModule(projectRoot, resolve3(projectRoot, entry.sourcePath));
2382
+ const moduleValue = await importRuntimeModule(projectRoot, resolve5(projectRoot, entry.sourcePath));
2235
2383
  const listener = resolveListenerExport(eventsModule, moduleValue);
2236
2384
  if (!listener) {
2237
2385
  throw new Error(`Discovered listener "${entry.sourcePath}" does not export a Holo listener.`);
@@ -2277,13 +2425,27 @@ function unregisterProjectEventsAndListeners(eventsModule, eventNames, listenerI
2277
2425
  }
2278
2426
  }
2279
2427
  async function reconfigureOptionalHoloSubsystems(projectRoot, loadedConfig, options = {}) {
2428
+ const pluginDefinitions = await loadConfiguredHoloPluginDefinitions(projectRoot, resolveLoadedPluginNames(loadedConfig));
2280
2429
  const cacheConfigured = hasLoadedConfigFile(loadedConfig, "cache");
2281
2430
  const cacheModule = await loadCacheModule(cacheConfigured, projectRoot);
2282
- if (cacheModule) {
2431
+ const cacheConfig = loadedConfig.cache;
2432
+ if (cacheModule && cacheConfig) {
2433
+ await loadConfiguredCacheDrivers(projectRoot, cacheConfig);
2434
+ const configuredPluginDrivers = Object.entries(cacheConfig.drivers).flatMap(([name, driver]) => {
2435
+ if (driver.driver === "memory" || driver.driver === "file" || driver.driver === "redis" || driver.driver === "database") return [];
2436
+ return [{ ...driver, name, driver: driver.driver }];
2437
+ });
2438
+ const loadedPluginDrivers = await cacheModule.loadConfiguredCachePluginDriverContracts(
2439
+ projectRoot,
2440
+ resolveLoadedPluginNames(loadedConfig),
2441
+ configuredPluginDrivers
2442
+ );
2443
+ const cachePluginDrivers = new Map(loadedPluginDrivers.map((driver) => [driver.name, driver]));
2283
2444
  cacheModule.configureCacheRuntime({
2284
- config: loadedConfig.cache,
2445
+ config: cacheConfig,
2285
2446
  databaseConfig: loadedConfig.database,
2286
- redisConfig: loadedConfig.redis
2447
+ redisConfig: loadedConfig.redis,
2448
+ ...cachePluginDrivers.size > 0 ? { drivers: cachePluginDrivers } : {}
2287
2449
  });
2288
2450
  }
2289
2451
  const queueConfigured = hasLoadedConfigFile(loadedConfig, "queue");
@@ -2291,14 +2453,30 @@ async function reconfigureOptionalHoloSubsystems(projectRoot, loadedConfig, opti
2291
2453
  if (queueModule) {
2292
2454
  const queueUsesExplicitDatabaseFeatures = queueConfigured && (queueConfigUsesDatabaseDriver(loadedConfig) || queueConfigUsesDatabaseBackedFailedStore(loadedConfig));
2293
2455
  const queueUsesImplicitDefaultFailedStore = !queueConfigured && queueConfigUsesDatabaseBackedFailedStore(loadedConfig);
2294
- const queueDbModule = queueUsesExplicitDatabaseFeatures || queueUsesImplicitDefaultFailedStore ? await loadQueueDbModule() : void 0;
2456
+ const queueDbModule = queueUsesExplicitDatabaseFeatures || queueUsesImplicitDefaultFailedStore ? await loadQueueDbModule(projectRoot) : void 0;
2457
+ const queueUsesRedis = queueConfigUsesRedisDriver(loadedConfig);
2458
+ const queueRedisModule = queueUsesRedis ? await loadQueueRedisModule(projectRoot) : void 0;
2295
2459
  if (queueUsesExplicitDatabaseFeatures && !queueDbModule) {
2296
2460
  throw new Error("[@holo-js/core] Database-backed queue features require @holo-js/queue-db to be installed.");
2297
2461
  }
2462
+ if (queueUsesRedis && !queueRedisModule) {
2463
+ throw new Error("[@holo-js/core] Redis-backed queue connections require @holo-js/queue-redis to be installed.");
2464
+ }
2465
+ const queuePluginDriverFactories = await queueModule.loadQueuePluginDriverFactories(
2466
+ projectRoot,
2467
+ resolveLoadedPluginNames(loadedConfig)
2468
+ );
2469
+ const queueDbRuntimeOptions = queueDbModule?.createQueueDbRuntimeOptions() ?? {};
2470
+ const queueDriverFactories = mergeQueueRuntimeDriverFactories(
2471
+ queuePluginDriverFactories,
2472
+ queueDbRuntimeOptions.driverFactories,
2473
+ queueRedisModule ? [queueRedisModule.redisQueueDriverFactory] : void 0
2474
+ );
2298
2475
  queueModule.configureQueueRuntime({
2299
2476
  config: loadedConfig.queue,
2300
2477
  redisConfig: loadedConfig.redis,
2301
- ...queueDbModule?.createQueueDbRuntimeOptions() ?? {}
2478
+ ...queueDbRuntimeOptions,
2479
+ ...queueDriverFactories.length > 0 ? { driverFactories: queueDriverFactories } : {}
2302
2480
  });
2303
2481
  }
2304
2482
  const storageConfigured = hasLoadedConfigFile(loadedConfig, "storage");
@@ -2306,7 +2484,7 @@ async function reconfigureOptionalHoloSubsystems(projectRoot, loadedConfig, opti
2306
2484
  if (!storageInstalled && storageConfigured) {
2307
2485
  throw new Error("[@holo-js/core] Storage support requires @holo-js/storage to be installed.");
2308
2486
  }
2309
- if (storageInstalled) {
2487
+ if (storageInstalled && loadedConfig.storage) {
2310
2488
  await configurePlainNodeStorageRuntime(projectRoot, loadedConfig);
2311
2489
  }
2312
2490
  const mailConfigured = hasLoadedConfigFile(loadedConfig, "mail");
@@ -2316,7 +2494,9 @@ async function reconfigureOptionalHoloSubsystems(projectRoot, loadedConfig, opti
2316
2494
  mailModule.configureMailRuntime({
2317
2495
  ...existingMailBindings,
2318
2496
  config: loadedConfig.mail,
2319
- ...options.renderView ?? getRuntimeState().renderView ? { renderView: options.renderView ?? getRuntimeState().renderView } : {}
2497
+ projectRoot,
2498
+ plugins: loadedConfig.app.plugins,
2499
+ ...options.renderView ?? getHoloRenderingRuntime().renderView ? { renderView: options.renderView ?? getHoloRenderingRuntime().renderView } : {}
2320
2500
  });
2321
2501
  }
2322
2502
  const broadcastConfigured = hasLoadedConfigFile(loadedConfig, "broadcast");
@@ -2326,6 +2506,8 @@ async function reconfigureOptionalHoloSubsystems(projectRoot, loadedConfig, opti
2326
2506
  broadcastModule.configureBroadcastRuntime({
2327
2507
  ...existingBroadcastBindings,
2328
2508
  config: loadedConfig.broadcast,
2509
+ projectRoot,
2510
+ plugins: loadedConfig.app.plugins,
2329
2511
  ...!existingBroadcastBindings.publish || isCoreBroadcastPublisher(existingBroadcastBindings.publish) ? {
2330
2512
  publish: createCoreBroadcastPublisher(loadedConfig.broadcast)
2331
2513
  } : {}
@@ -2338,6 +2520,16 @@ async function reconfigureOptionalHoloSubsystems(projectRoot, loadedConfig, opti
2338
2520
  notificationsModule.configureNotificationsRuntime({
2339
2521
  ...existingNotificationsBindings,
2340
2522
  config: loadedConfig.notifications,
2523
+ deferAfterCommit(callback) {
2524
+ const connection = connectionAsyncContext.getActive()?.connection;
2525
+ if (!connection || connection.getScope().kind === "root") {
2526
+ return false;
2527
+ }
2528
+ connection.afterCommit(callback);
2529
+ return true;
2530
+ },
2531
+ projectRoot,
2532
+ plugins: loadedConfig.app.plugins,
2341
2533
  store: existingNotificationsBindings.store ?? createCoreNotificationStore(loadedConfig),
2342
2534
  ...!existingNotificationsBindings.mailer && mailModule ? { mailer: createCoreNotificationMailSender(mailModule) } : {},
2343
2535
  ...!existingNotificationsBindings.broadcaster && broadcastModule ? { broadcaster: createCoreNotificationBroadcaster(broadcastModule) } : {}
@@ -2509,6 +2701,9 @@ async function reconfigureOptionalHoloSubsystems(projectRoot, loadedConfig, opti
2509
2701
  } else if (authorizationModule) {
2510
2702
  authorizationModule.authorizationInternals.resetAuthorizationAuthIntegration();
2511
2703
  }
2704
+ for (const bootModule of await loadConfiguredHoloPluginBootModules(projectRoot, pluginDefinitions)) {
2705
+ await bootConfiguredHoloPluginModule(projectRoot, loadedConfig, bootModule);
2706
+ }
2512
2707
  return Object.freeze({
2513
2708
  /* v8 ignore next -- only toggles shape when queue support is absent */
2514
2709
  ...queueModule ? { queueModule } : {},
@@ -2518,6 +2713,7 @@ async function reconfigureOptionalHoloSubsystems(projectRoot, loadedConfig, opti
2518
2713
  });
2519
2714
  }
2520
2715
  async function resetOptionalHoloSubsystems() {
2716
+ resetBootedHoloPluginModules();
2521
2717
  const projectRoot = getRuntimeState().current?.projectRoot ?? getRuntimeState().pendingProjectRoot;
2522
2718
  await resetOptionalStorageRuntime();
2523
2719
  const cacheModule = await loadCacheModule(false, projectRoot);
@@ -2527,7 +2723,10 @@ async function resetOptionalHoloSubsystems() {
2527
2723
  resetCacheRuntimeGlobalsFallback();
2528
2724
  }
2529
2725
  const queueModule = await loadQueueModule();
2530
- await queueModule?.shutdownQueueRuntime();
2726
+ if (queueModule) {
2727
+ await queueModule.shutdownQueueRuntime();
2728
+ queueModule.resetQueueRuntime?.();
2729
+ }
2531
2730
  const mailModule = await loadMailModule();
2532
2731
  mailModule?.resetMailRuntime();
2533
2732
  const notificationsModule = await loadNotificationsModule();
@@ -2567,15 +2766,32 @@ async function resetOptionalHoloSubsystems() {
2567
2766
  securityModule?.resetSecurityRuntime();
2568
2767
  }
2569
2768
  async function createHolo(projectRoot, options = {}) {
2769
+ await loadInstalledFeatureConfigContributions(projectRoot);
2570
2770
  const loadedConfig = await loadConfigDirectory(projectRoot, {
2571
2771
  envName: options.envName,
2572
2772
  preferCache: options.preferCache,
2573
2773
  processEnv: options.processEnv
2574
2774
  });
2775
+ const fallbackQueueConfig = Object.freeze({
2776
+ default: "sync",
2777
+ failed: Object.freeze({
2778
+ driver: "database",
2779
+ connection: "default",
2780
+ table: "failed_jobs"
2781
+ }),
2782
+ connections: Object.freeze({
2783
+ sync: Object.freeze({
2784
+ driver: "sync",
2785
+ queue: "default"
2786
+ })
2787
+ })
2788
+ });
2789
+ const queueConfig = loadedConfig.queue ?? fallbackQueueConfig;
2575
2790
  const runtimeConfig = {
2576
2791
  db: loadedConfig.database,
2577
- queue: loadedConfig.queue
2792
+ queue: queueConfig
2578
2793
  };
2794
+ const databaseDriverFactories = await loadConfiguredDatabaseDrivers(projectRoot, loadedConfig);
2579
2795
  const manager = resolveRuntimeConnectionManagerOptions(runtimeConfig);
2580
2796
  const registry = await loadGeneratedProjectRegistry(projectRoot);
2581
2797
  const accessors = createConfigAccessors(loadedConfig.all);
@@ -2591,11 +2807,105 @@ async function createHolo(projectRoot, options = {}) {
2591
2807
  let activeAuthRuntime;
2592
2808
  let activeAuthContext;
2593
2809
  let previousOptionalSubsystemBindings;
2594
- const previousRenderView = options.renderView ? getRuntimeState().renderView : void 0;
2810
+ const previousRenderView = options.renderView ? getHoloRenderingRuntime().renderView : void 0;
2595
2811
  const fallbackQueueRuntime = Object.freeze({
2596
- config: loadedConfig.queue,
2812
+ config: queueConfig,
2597
2813
  drivers: /* @__PURE__ */ new Map()
2598
2814
  });
2815
+ const unregisterRuntimeContributions = () => {
2816
+ unregisterProjectEventsAndListeners(activeEventsModule, runtimeOwnedEventNames, runtimeOwnedListenerIds);
2817
+ runtimeOwnedEventNames.splice(0);
2818
+ runtimeOwnedListenerIds.splice(0);
2819
+ unregisterProjectAuthorizationDefinitions(activeAuthorizationModule, runtimeOwnedAuthorizationPolicyNames, runtimeOwnedAuthorizationAbilityNames);
2820
+ runtimeOwnedAuthorizationPolicyNames.splice(0);
2821
+ runtimeOwnedAuthorizationAbilityNames.splice(0);
2822
+ unregisterProjectQueueJobs(activeQueueModule, runtimeOwnedQueueJobNames);
2823
+ runtimeOwnedQueueJobNames.splice(0);
2824
+ activeAuthorizationModule = void 0;
2825
+ activeEventsModule = void 0;
2826
+ activeQueueModule = void 0;
2827
+ activeSessionRuntime = void 0;
2828
+ activeAuthRuntime = void 0;
2829
+ activeAuthContext = void 0;
2830
+ };
2831
+ const initializeRuntimeServices = async () => {
2832
+ if (!shouldBootRuntimeServices(options.processEnv)) return;
2833
+ const optionalSubsystems = await reconfigureOptionalHoloSubsystems(projectRoot, loadedConfig, {
2834
+ renderView: options.renderView,
2835
+ authRequest: options.authRequest,
2836
+ authorizationError: options.authorizationError
2837
+ });
2838
+ activeQueueModule = optionalSubsystems.queueModule;
2839
+ activeSessionRuntime = optionalSubsystems.session;
2840
+ activeAuthRuntime = optionalSubsystems.auth;
2841
+ activeAuthContext = optionalSubsystems.authContext;
2842
+ const optionalEventsModule = activeQueueModule ? await loadEventsModule() : void 0;
2843
+ if (activeQueueModule && optionalEventsModule) {
2844
+ await optionalEventsModule.ensureEventsQueueJobRegisteredAsync?.();
2845
+ }
2846
+ if (registryHasEvents(registry)) {
2847
+ const eventsModule = await loadEventsModule(true);
2848
+ if (!eventsModule) throw new Error("[@holo-js/core] Events support requires @holo-js/events to be installed.");
2849
+ activeEventsModule = eventsModule;
2850
+ const eventRegistration = await registerProjectEventsAndListeners(projectRoot, registry, eventsModule, activeQueueModule);
2851
+ runtimeOwnedEventNames.push(...eventRegistration.eventNames);
2852
+ runtimeOwnedListenerIds.push(...eventRegistration.listenerIds);
2853
+ }
2854
+ activeAuthorizationModule = await loadAuthorizationModule();
2855
+ const authorizationRegistration = await registerProjectAuthorizationDefinitions(projectRoot, registry, activeAuthorizationModule);
2856
+ runtimeOwnedAuthorizationPolicyNames.push(...authorizationRegistration.policyNames);
2857
+ runtimeOwnedAuthorizationAbilityNames.push(...authorizationRegistration.abilityNames);
2858
+ if (options.registerProjectQueueJobs !== false && registryHasJobs(registry)) {
2859
+ if (!activeQueueModule) throw new Error("[@holo-js/core] Project jobs require @holo-js/queue to be installed.");
2860
+ runtimeOwnedQueueJobNames.push(...await registerProjectQueueJobs(projectRoot, registry, activeQueueModule));
2861
+ }
2862
+ };
2863
+ const runtimeLifecycle = createRuntimeLifecycle([
2864
+ {
2865
+ name: "configuration",
2866
+ async initialize() {
2867
+ configureConfigRuntime(loadedConfig.all);
2868
+ await preloadGeneratedSchemaModule(projectRoot, registry);
2869
+ await preloadDiscoveredModelModules(projectRoot, registry);
2870
+ previousOptionalSubsystemBindings = snapshotOptionalSubsystemRuntimeBindings();
2871
+ if (options.renderView) configureHoloRenderingRuntime({ renderView: options.renderView });
2872
+ },
2873
+ dispose() {
2874
+ if (options.renderView) restoreHoloRenderingRuntime(previousRenderView);
2875
+ resetConfigRuntime();
2876
+ }
2877
+ },
2878
+ {
2879
+ name: "database",
2880
+ dependsOn: ["configuration"],
2881
+ async initialize() {
2882
+ for (const factory of databaseDriverFactories) registerDatabaseDriverFactory(factory);
2883
+ configureDB(manager);
2884
+ if (shouldBootRuntimeServices(options.processEnv)) await manager.initializeAll();
2885
+ },
2886
+ async dispose() {
2887
+ try {
2888
+ await manager.disconnectAll();
2889
+ } finally {
2890
+ resetDB();
2891
+ for (const factory of [...databaseDriverFactories].reverse()) unregisterDatabaseDriverFactory(factory);
2892
+ }
2893
+ }
2894
+ },
2895
+ {
2896
+ name: "optional-subsystems",
2897
+ dependsOn: ["database"],
2898
+ initialize: initializeRuntimeServices,
2899
+ async dispose() {
2900
+ unregisterRuntimeContributions();
2901
+ try {
2902
+ await resetOptionalHoloSubsystems();
2903
+ } finally {
2904
+ if (previousOptionalSubsystemBindings) restoreOptionalSubsystemRuntimeBindings(previousOptionalSubsystemBindings);
2905
+ }
2906
+ }
2907
+ }
2908
+ ]);
2599
2909
  const runtime = {
2600
2910
  projectRoot,
2601
2911
  loadedConfig,
@@ -2617,150 +2927,31 @@ async function createHolo(projectRoot, options = {}) {
2617
2927
  setAuthRequestAccessors(authRequest) {
2618
2928
  activeAuthContext?.setRequestAccessors?.(authRequest);
2619
2929
  },
2620
- async runWithAuthRequestAccessors(authRequest, callback) {
2930
+ runWithAuthRequestAccessors(authRequest, callback) {
2621
2931
  const runner = activeAuthContext?.runWithRequestAccessors;
2622
- return runner ? await runner(authRequest, callback) : await callback();
2932
+ return runner ? runner(authRequest, callback) : callback();
2623
2933
  },
2624
2934
  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
- }
2935
+ if (runtime.initialized) throw new Error("Holo runtime is already initialized.");
2936
+ if (getRuntimeState().current) throw new Error("A Holo runtime is already initialized for this process.");
2937
+ await runtimeLifecycle.initialize({ projectRoot });
2938
+ runtime.initialized = true;
2939
+ getRuntimeState().current = runtime;
2719
2940
  },
2720
2941
  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
- }
2942
+ runtime.initialized = false;
2943
+ if (getRuntimeState().current === runtime) getRuntimeState().current = void 0;
2944
+ await runtimeLifecycle.dispose({ projectRoot });
2754
2945
  }
2755
2946
  };
2756
2947
  return runtime;
2757
2948
  }
2758
2949
  async function initializeHolo(projectRoot, options = {}) {
2759
2950
  const state = getRuntimeState();
2760
- const resolvedProjectRoot = resolve3(projectRoot);
2951
+ const resolvedProjectRoot = resolve5(projectRoot);
2761
2952
  const current = state.current;
2762
2953
  if (current) {
2763
- if (resolve3(current.projectRoot) !== resolvedProjectRoot) {
2954
+ if (resolve5(current.projectRoot) !== resolvedProjectRoot) {
2764
2955
  throw new Error(`A Holo runtime is already initialized for "${current.projectRoot}".`);
2765
2956
  }
2766
2957
  ;
@@ -2768,7 +2959,7 @@ async function initializeHolo(projectRoot, options = {}) {
2768
2959
  return current;
2769
2960
  }
2770
2961
  if (state.pending) {
2771
- if (state.pendingProjectRoot && resolve3(state.pendingProjectRoot) !== resolvedProjectRoot) {
2962
+ if (state.pendingProjectRoot && resolve5(state.pendingProjectRoot) !== resolvedProjectRoot) {
2772
2963
  throw new Error(`A Holo runtime is already initializing for "${state.pendingProjectRoot}".`);
2773
2964
  }
2774
2965
  return state.pending.then((runtime) => {
@@ -2801,7 +2992,7 @@ async function ensureHolo(projectRoot, options = {}) {
2801
2992
  if (!current) {
2802
2993
  return initializeHolo(projectRoot, options);
2803
2994
  }
2804
- if (resolve3(current.projectRoot) !== resolve3(projectRoot)) {
2995
+ if (resolve5(current.projectRoot) !== resolve5(projectRoot)) {
2805
2996
  throw new Error(`A Holo runtime is already initialized for "${current.projectRoot}".`);
2806
2997
  }
2807
2998
  return current;