@holo-js/core 0.1.4 → 0.1.5

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.
@@ -10,8 +10,8 @@ import {
10
10
  } from "@holo-js/db";
11
11
 
12
12
  // src/portable/registry.ts
13
- import { readFile } from "fs/promises";
14
- import { resolve } from "path";
13
+ import { readFile } from "node:fs/promises";
14
+ import { resolve } from "node:path";
15
15
  import { DEFAULT_HOLO_PROJECT_PATHS } from "@holo-js/db";
16
16
  function isRecord(value) {
17
17
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -118,10 +118,10 @@ var registryInternals = {
118
118
  };
119
119
 
120
120
  // src/portable/holo.ts
121
- import { createHash, createHmac } from "crypto";
122
- import { createRequire } from "module";
123
- import { resolve as resolve3 } from "path";
124
- import { pathToFileURL as pathToFileURL2 } from "url";
121
+ import { AsyncLocalStorage } from "node:async_hooks";
122
+ import { existsSync } from "node:fs";
123
+ import { createHash, createHmac } from "node:crypto";
124
+ import { resolve as resolve3 } from "node:path";
125
125
  import {
126
126
  config as globalConfig,
127
127
  configureConfigRuntime,
@@ -133,13 +133,15 @@ import {
133
133
  import {
134
134
  configureDB,
135
135
  DB,
136
+ Entity,
136
137
  resetDB
137
138
  } from "@holo-js/db";
138
139
 
139
140
  // src/runtimeModule.ts
140
- import { mkdtemp, mkdir, rm, stat, writeFile } from "fs/promises";
141
- import { basename, extname, join } from "path";
142
- import { pathToFileURL } from "url";
141
+ import { mkdtemp, mkdir, rm, stat, writeFile } from "node:fs/promises";
142
+ import { createRequire } from "node:module";
143
+ import { basename, extname, join } from "node:path";
144
+ import { fileURLToPath, pathToFileURL } from "node:url";
143
145
  async function importModule(specifier) {
144
146
  if (process.env.VITEST) {
145
147
  return import(
@@ -152,6 +154,66 @@ async function importModule(specifier) {
152
154
  specifier
153
155
  );
154
156
  }
157
+ var runtimeModuleRequire = createRequire(import.meta.url);
158
+ function resolveOptionalImportSpecifier(specifier, projectRoot) {
159
+ if (!projectRoot) {
160
+ return specifier;
161
+ }
162
+ try {
163
+ return pathToFileURL(runtimeModuleRequire.resolve(specifier, {
164
+ paths: [projectRoot]
165
+ })).href;
166
+ } catch {
167
+ return specifier;
168
+ }
169
+ }
170
+ function getErrorMessage(error) {
171
+ return error && typeof error === "object" && "message" in error && typeof error.message === "string" ? error.message : "";
172
+ }
173
+ function getMissingModuleTarget(message) {
174
+ const match = message.match(/Cannot find package '([^']+)'|Cannot find module '([^']+)'|Failed to load url ([^ ]+)|Could not resolve "([^"]+)"/);
175
+ return match?.slice(1).find((value) => typeof value === "string");
176
+ }
177
+ function normalizeImportSpecifier(specifier) {
178
+ return specifier.startsWith("file://") ? fileURLToPath(specifier) : specifier;
179
+ }
180
+ function normalizeImportTarget(value) {
181
+ return normalizeImportSpecifier(value).replace(/\\/g, "/");
182
+ }
183
+ function matchesRelativeImportTarget(failedTarget, specifier) {
184
+ if (!failedTarget || !specifier.startsWith(".")) {
185
+ return false;
186
+ }
187
+ const suffix = specifier.startsWith("./") ? specifier.slice(2) : specifier;
188
+ return normalizeImportTarget(failedTarget).endsWith(`/${suffix}`);
189
+ }
190
+ function isMissingOptionalModule(error, specifier, resolvedSpecifier) {
191
+ if (!error || typeof error !== "object") {
192
+ return false;
193
+ }
194
+ const message = getErrorMessage(error);
195
+ const failedTarget = getMissingModuleTarget(message);
196
+ const expectedTargets = /* @__PURE__ */ new Set([
197
+ specifier,
198
+ resolvedSpecifier,
199
+ normalizeImportTarget(specifier),
200
+ normalizeImportTarget(resolvedSpecifier)
201
+ ]);
202
+ const normalizedFailedTarget = typeof failedTarget === "string" ? normalizeImportTarget(failedTarget) : void 0;
203
+ const matchesRequestedTarget = typeof normalizedFailedTarget === "string" && (expectedTargets.has(normalizedFailedTarget) || matchesRelativeImportTarget(normalizedFailedTarget, specifier));
204
+ 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;
205
+ }
206
+ async function importOptionalRuntimeModule(specifier, options = {}) {
207
+ const resolvedSpecifier = resolveOptionalImportSpecifier(specifier, options.projectRoot);
208
+ try {
209
+ return await importModule(resolvedSpecifier);
210
+ } catch (error) {
211
+ if (isMissingOptionalModule(error, specifier, resolvedSpecifier)) {
212
+ return void 0;
213
+ }
214
+ throw error;
215
+ }
216
+ }
155
217
  async function pathExists(path) {
156
218
  try {
157
219
  await stat(path);
@@ -253,6 +315,7 @@ async function runEsbuild(options) {
253
315
  var runtimeModuleInternals = {
254
316
  bundleRuntimeModule,
255
317
  importModule,
318
+ importOptionalRuntimeModule,
256
319
  loadEsbuild,
257
320
  pathExists,
258
321
  runEsbuild,
@@ -260,38 +323,26 @@ var runtimeModuleInternals = {
260
323
  };
261
324
 
262
325
  // src/storageRuntime.ts
263
- import { mkdir as mkdir2, readFile as readFile2, readdir, rm as rm2, writeFile as writeFile2 } from "fs/promises";
264
- import { dirname, join as join2, resolve as resolve2 } from "path";
265
- import { fileURLToPath } from "url";
326
+ import { mkdir as mkdir2, readFile as readFile2, readdir, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
327
+ import { dirname, isAbsolute, join as join2, relative, resolve as resolve2, sep, win32 } from "node:path";
266
328
  async function importOptionalModule(specifier) {
267
- try {
268
- if (process.env.VITEST) {
269
- return await import(
270
- /* @vite-ignore */
271
- specifier
272
- );
273
- }
274
- return await import(
275
- /* webpackIgnore: true */
276
- specifier
277
- );
278
- } catch (error) {
279
- const message = error && typeof error === "object" && "message" in error && typeof error.message === "string" ? error.message : "";
280
- const resolvedSpecifier = specifier.startsWith("file://") ? fileURLToPath(specifier) : specifier;
281
- const failedTarget = message.match(/Cannot find package '([^']+)'|Cannot find module '([^']+)'|Failed to load url ([^ ]+)/)?.slice(1).find((value) => typeof value === "string");
282
- const matchesRequestedTarget = failedTarget === specifier || failedTarget === resolvedSpecifier;
283
- if (error && typeof error === "object" && ("code" in error && error.code === "ERR_MODULE_NOT_FOUND" || 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)) {
284
- return void 0;
285
- }
286
- throw error;
287
- }
329
+ return importOptionalRuntimeModule(specifier);
288
330
  }
289
331
  function resolveStorageKeyPath(root, key) {
290
- const segments = key.split(":").filter(Boolean);
332
+ if (isAbsolute(key) || win32.isAbsolute(key)) {
333
+ throw new Error("[Holo Storage] Storage paths must not be absolute.");
334
+ }
335
+ const segments = key.replace(/\\/g, "/").split(":").flatMap((segment) => segment.split("/")).filter(Boolean);
291
336
  if (segments.includes("..")) {
292
337
  throw new Error('[Holo Storage] Storage paths must not contain ".." segments.');
293
338
  }
294
- return resolve2(root, ...segments);
339
+ const rootPath = resolve2(root);
340
+ const targetPath = resolve2(rootPath, ...segments);
341
+ const relativeTarget = relative(rootPath, targetPath);
342
+ if (relativeTarget === ".." || relativeTarget.startsWith(`..${sep}`) || isAbsolute(relativeTarget)) {
343
+ throw new Error("[Holo Storage] Storage paths must stay inside the configured disk root.");
344
+ }
345
+ return targetPath;
295
346
  }
296
347
  function createFileStorageBackend(root) {
297
348
  async function listStorageKeys(currentRoot, prefix = "") {
@@ -430,6 +481,40 @@ var storageRuntimeInternals = {
430
481
  };
431
482
 
432
483
  // src/portable/holo.ts
484
+ async function preloadGeneratedSchemaModule(projectRoot, registry) {
485
+ const entry = registry?.paths.generatedSchema;
486
+ if (!entry) {
487
+ return;
488
+ }
489
+ const expectedTarget = resolve3(projectRoot, entry);
490
+ if (!existsSync(expectedTarget)) {
491
+ return;
492
+ }
493
+ try {
494
+ await importBundledRuntimeModule(projectRoot, entry);
495
+ } catch (error) {
496
+ if (error instanceof Error && /Cannot find module|ERR_MODULE_NOT_FOUND|MODULE_NOT_FOUND|Failed to load url|Failed to load /.test(error.message)) {
497
+ const message = error.message;
498
+ const failedTarget = message.match(/Cannot find module '([^']+)'|Cannot find package '([^']+)'|Failed to load url ([^ ]+)|Failed to load ([^ ]+)\./)?.slice(1).find((value) => typeof value === "string");
499
+ if (failedTarget === expectedTarget) {
500
+ return;
501
+ }
502
+ }
503
+ throw error;
504
+ }
505
+ }
506
+ async function preloadDiscoveredModelModules(projectRoot, registry) {
507
+ if (!registry || registry.models.length === 0) {
508
+ return;
509
+ }
510
+ for (const entry of registry.models) {
511
+ const sourcePath = resolve3(projectRoot, entry.sourcePath);
512
+ if (!existsSync(sourcePath)) {
513
+ continue;
514
+ }
515
+ await importBundledRuntimeModule(projectRoot, sourcePath);
516
+ }
517
+ }
433
518
  function closeSessionRedisAdapter(adapter) {
434
519
  return adapter.disconnect?.() || adapter.close?.();
435
520
  }
@@ -501,33 +586,8 @@ function restoreOptionalSubsystemRuntimeBindings(bindings) {
501
586
  }
502
587
  }
503
588
  var BROADCAST_PUBLISH_TIMEOUT_MS = 1e4;
504
- var portableRuntimeRequire = createRequire(import.meta.url);
505
- function resolveOptionalImportSpecifier(specifier, projectRoot) {
506
- if (!projectRoot) {
507
- return specifier;
508
- }
509
- try {
510
- const resolved = portableRuntimeRequire.resolve(specifier, {
511
- paths: [projectRoot]
512
- });
513
- return pathToFileURL2(resolved).href;
514
- } catch {
515
- return specifier;
516
- }
517
- }
518
589
  async function importOptionalModule2(specifier, options = {}) {
519
- const resolvedSpecifier = resolveOptionalImportSpecifier(specifier, options.projectRoot);
520
- try {
521
- return await import(
522
- /* webpackIgnore: true */
523
- resolvedSpecifier
524
- );
525
- } catch (error) {
526
- if (error instanceof Error && (error.message.includes(`Cannot find package '${specifier}'`) || error.message.includes(`Cannot find module '${specifier}'`) || error.message.includes(`Failed to load url ${specifier}`) || error.message.includes(`Could not resolve "${specifier}"`) || error.message.includes(`Cannot find package '${resolvedSpecifier}'`) || error.message.includes(`Cannot find module '${resolvedSpecifier}'`) || error.message.includes(`Failed to load url ${resolvedSpecifier}`) || error.message.includes(`Could not resolve "${resolvedSpecifier}"`))) {
527
- return void 0;
528
- }
529
- throw error;
530
- }
590
+ return importOptionalRuntimeModule(specifier, options);
531
591
  }
532
592
  var portableRuntimeModuleInternals = {
533
593
  importOptionalModule: importOptionalModule2
@@ -554,192 +614,47 @@ function authConfigUsesSocialProviders(loadedConfig) {
554
614
  return Object.keys(loadedConfig.auth.social).length > 0;
555
615
  }
556
616
  function authConfigUsesWorkosProviders(loadedConfig) {
557
- return Object.keys(loadedConfig.auth.workos).length > 0;
617
+ return Object.entries(loadedConfig.auth.workos).some(([name, provider]) => name !== "provider" && name !== "identityStore" && typeof provider === "object" && provider !== null);
558
618
  }
559
619
  function authConfigUsesClerkProviders(loadedConfig) {
560
- return Object.keys(loadedConfig.auth.clerk).length > 0;
620
+ return Object.entries(loadedConfig.auth.clerk).some(([name, provider]) => name !== "provider" && name !== "identityStore" && typeof provider === "object" && provider !== null);
561
621
  }
562
622
  var HOLO_AUTH_PROVIDER_MARKER = /* @__PURE__ */ Symbol.for("holo-js.auth.provider");
563
- function bindAuthRuntimeToContext(runtime, authContext) {
564
- const activate = () => {
565
- authContext.activate();
623
+ function attachAuthRequestAccessors(context, accessors) {
624
+ return Object.freeze({
625
+ ...context,
626
+ getRequestCookie: accessors.getCookie,
627
+ getRequestHeader: accessors.getHeader,
628
+ appendResponseCookie: accessors.appendResponseCookie,
629
+ redirectResponse: accessors.redirectResponse
630
+ });
631
+ }
632
+ function createRequestAwareAuthContext(context, accessors) {
633
+ const requestAccessorStorage = new AsyncLocalStorage();
634
+ const resolveRequestContext = () => {
635
+ const requestAccessors = requestAccessorStorage.getStore();
636
+ const resolvedAccessors = requestAccessors ? requestAccessors.accessors : accessors;
637
+ return resolvedAccessors ? attachAuthRequestAccessors(context, resolvedAccessors) : context;
566
638
  };
567
639
  return Object.freeze({
568
- check() {
569
- activate();
570
- return runtime.check();
640
+ ...context,
641
+ getRequestCookie(name) {
642
+ return resolveRequestContext().getRequestCookie?.(name);
571
643
  },
572
- user() {
573
- activate();
574
- return runtime.user();
644
+ getRequestHeader(name) {
645
+ return resolveRequestContext().getRequestHeader?.(name);
575
646
  },
576
- refreshUser() {
577
- activate();
578
- return runtime.refreshUser();
647
+ appendResponseCookie(cookie) {
648
+ return resolveRequestContext().appendResponseCookie?.(cookie);
579
649
  },
580
- id() {
581
- activate();
582
- return runtime.id();
650
+ redirectResponse(url, status) {
651
+ return resolveRequestContext().redirectResponse?.(url, status);
583
652
  },
584
- currentAccessToken() {
585
- activate();
586
- return runtime.currentAccessToken();
587
- },
588
- hashPassword(password) {
589
- activate();
590
- return runtime.hashPassword(password);
591
- },
592
- verifyPassword(password, digest) {
593
- activate();
594
- return runtime.verifyPassword(password, digest);
595
- },
596
- needsPasswordRehash(digest) {
597
- activate();
598
- return runtime.needsPasswordRehash(digest);
599
- },
600
- login(credentials) {
601
- activate();
602
- return runtime.login(credentials);
603
- },
604
- loginUsing(user, options) {
605
- activate();
606
- return runtime.loginUsing(user, options);
607
- },
608
- loginUsingId(userId, options) {
609
- activate();
610
- return runtime.loginUsingId(userId, options);
611
- },
612
- impersonate(user, options) {
613
- activate();
614
- return runtime.impersonate(user, options);
615
- },
616
- impersonateById(userId, options) {
617
- activate();
618
- return runtime.impersonateById(userId, options);
619
- },
620
- impersonation() {
621
- activate();
622
- return runtime.impersonation();
623
- },
624
- stopImpersonating() {
625
- activate();
626
- return runtime.stopImpersonating();
627
- },
628
- logout() {
629
- activate();
630
- return runtime.logout();
631
- },
632
- register(input) {
633
- activate();
634
- return runtime.register(input);
635
- },
636
- logoutAll(guardName) {
637
- activate();
638
- return runtime.logoutAll(guardName);
639
- },
640
- guard(name) {
641
- const guard = runtime.guard(name);
642
- return Object.freeze({
643
- check() {
644
- activate();
645
- return guard.check();
646
- },
647
- user() {
648
- activate();
649
- return guard.user();
650
- },
651
- refreshUser() {
652
- activate();
653
- return guard.refreshUser();
654
- },
655
- id() {
656
- activate();
657
- return guard.id();
658
- },
659
- currentAccessToken() {
660
- activate();
661
- return guard.currentAccessToken();
662
- },
663
- login(credentials) {
664
- activate();
665
- return guard.login(credentials);
666
- },
667
- loginUsing(user, options) {
668
- activate();
669
- return guard.loginUsing(user, options);
670
- },
671
- loginUsingId(userId, options) {
672
- activate();
673
- return guard.loginUsingId(userId, options);
674
- },
675
- impersonate(user, options) {
676
- activate();
677
- return guard.impersonate(user, options);
678
- },
679
- impersonateById(userId, options) {
680
- activate();
681
- return guard.impersonateById(userId, options);
682
- },
683
- impersonation() {
684
- activate();
685
- return guard.impersonation();
686
- },
687
- stopImpersonating() {
688
- activate();
689
- return guard.stopImpersonating();
690
- },
691
- logout() {
692
- activate();
693
- return guard.logout();
694
- }
653
+ setRequestAccessors(nextAccessors) {
654
+ requestAccessorStorage.enterWith({
655
+ accessors: nextAccessors
695
656
  });
696
- },
697
- tokens: Object.freeze({
698
- create(user, options) {
699
- activate();
700
- return runtime.tokens.create(user, options);
701
- },
702
- list(user, options) {
703
- activate();
704
- return runtime.tokens.list(user, options);
705
- },
706
- revoke(options) {
707
- activate();
708
- return runtime.tokens.revoke(options);
709
- },
710
- revokeAll(user, options) {
711
- activate();
712
- return runtime.tokens.revokeAll(user, options);
713
- },
714
- authenticate(plainTextToken) {
715
- activate();
716
- return runtime.tokens.authenticate(plainTextToken);
717
- },
718
- can(token, ability) {
719
- activate();
720
- return runtime.tokens.can(token, ability);
721
- }
722
- }),
723
- verification: Object.freeze({
724
- create(user, options) {
725
- activate();
726
- return runtime.verification.create(user, options);
727
- },
728
- consume(plainTextToken) {
729
- activate();
730
- return runtime.verification.consume(plainTextToken);
731
- }
732
- }),
733
- passwords: Object.freeze({
734
- request(email, options) {
735
- activate();
736
- return runtime.passwords.request(email, options);
737
- },
738
- consume(input) {
739
- activate();
740
- return runtime.passwords.consume(input);
741
- }
742
- })
657
+ }
743
658
  });
744
659
  }
745
660
  async function loadQueueModule(required = false) {
@@ -1120,11 +1035,88 @@ function createCoreNotificationStore(loadedConfig) {
1120
1035
  };
1121
1036
  return Object.freeze(store);
1122
1037
  }
1123
- function createAuthNotificationsDeliveryHook(notificationsModule) {
1038
+ var authEmailDateFormatter = new Intl.DateTimeFormat("en-US", {
1039
+ dateStyle: "long",
1040
+ timeStyle: "short",
1041
+ timeZone: "UTC"
1042
+ });
1043
+ function formatAuthEmailExpiration(expiresAt) {
1044
+ return `${authEmailDateFormatter.format(expiresAt)} UTC`;
1045
+ }
1046
+ var AUTH_EMAIL_VERIFICATION_NOTIFICATION_PATHS = [
1047
+ "server/notifications/auth/email-verification.ts",
1048
+ "server/notifications/auth/email-verification.mts",
1049
+ "server/notifications/auth/email-verification.js",
1050
+ "server/notifications/auth/email-verification.mjs",
1051
+ "server/notifications/auth/email-verification.cts",
1052
+ "server/notifications/auth/email-verification.cjs"
1053
+ ];
1054
+ var AUTH_PASSWORD_RESET_NOTIFICATION_PATHS = [
1055
+ "server/notifications/auth/password-reset.ts",
1056
+ "server/notifications/auth/password-reset.mts",
1057
+ "server/notifications/auth/password-reset.js",
1058
+ "server/notifications/auth/password-reset.mjs",
1059
+ "server/notifications/auth/password-reset.cts",
1060
+ "server/notifications/auth/password-reset.cjs"
1061
+ ];
1062
+ function resolveExistingProjectFile(projectRoot, candidates) {
1063
+ if (!projectRoot) {
1064
+ return void 0;
1065
+ }
1066
+ return candidates.find((candidate) => existsSync(resolve3(projectRoot, candidate)));
1067
+ }
1068
+ function resolveAuthNotification(module, exportName, filePath) {
1069
+ const notification = module[exportName] ?? module.notification ?? module.default;
1070
+ if (!isAuthNotificationDefinition(notification)) {
1071
+ throw new Error(
1072
+ `[@holo-js/core] Auth notification file "${filePath}" must export a notification definition.`
1073
+ );
1074
+ }
1075
+ return notification;
1076
+ }
1077
+ function isAuthNotificationDefinition(notification) {
1078
+ if (!notification || typeof notification !== "object") {
1079
+ return false;
1080
+ }
1081
+ const candidate = notification;
1082
+ if (typeof candidate.via !== "function" || !candidate.build || typeof candidate.build !== "object") {
1083
+ return false;
1084
+ }
1085
+ return Object.values(candidate.build).some((factory) => typeof factory === "function");
1086
+ }
1087
+ async function loadProjectAuthNotification(projectRoot, candidates, exportName) {
1088
+ const filePath = resolveExistingProjectFile(projectRoot, candidates);
1089
+ if (!filePath) {
1090
+ return void 0;
1091
+ }
1092
+ const module = await importRuntimeModule(projectRoot, filePath);
1093
+ return resolveAuthNotification(module, exportName, filePath);
1094
+ }
1095
+ function createAuthNotificationsDeliveryHook(notificationsModule, appUrl, projectRoot) {
1124
1096
  return Object.freeze({
1125
1097
  async sendEmailVerification(input) {
1126
1098
  const recipientName = typeof input.user?.name === "string" ? input.user.name?.trim() : void 0;
1127
- const notification = notificationsModule.defineNotification({
1099
+ const actionUrl = createAuthActionUrl(appUrl, input.route, input.token.plainTextToken);
1100
+ const authNotification = Object.freeze({
1101
+ email: input.email,
1102
+ ...recipientName ? { name: recipientName } : {},
1103
+ url: actionUrl,
1104
+ expiresAt: input.token.expiresAt
1105
+ });
1106
+ const projectNotification = await loadProjectAuthNotification(
1107
+ projectRoot,
1108
+ AUTH_EMAIL_VERIFICATION_NOTIFICATION_PATHS,
1109
+ "emailVerificationNotification"
1110
+ );
1111
+ const lines = [
1112
+ "Confirm your account to finish signing in.",
1113
+ `This verification link expires at ${formatAuthEmailExpiration(input.token.expiresAt)}.`
1114
+ ];
1115
+ const action = {
1116
+ label: "Verify email address",
1117
+ url: actionUrl
1118
+ };
1119
+ const notification = projectNotification ?? notificationsModule.defineNotification({
1128
1120
  type: "auth.email-verification",
1129
1121
  via() {
1130
1122
  return ["email"];
@@ -1134,12 +1126,14 @@ function createAuthNotificationsDeliveryHook(notificationsModule) {
1134
1126
  return {
1135
1127
  subject: "Verify your email address",
1136
1128
  ...recipientName ? { greeting: `Hello ${recipientName},` } : {},
1137
- lines: [
1138
- "Use this token to verify your email address:",
1139
- input.token.plainTextToken,
1140
- `Provider: ${input.provider}`,
1141
- `Expires at: ${input.token.expiresAt.toISOString()}`
1142
- ],
1129
+ lines,
1130
+ action,
1131
+ html: createAuthEmailHtml({
1132
+ subject: "Verify your email address",
1133
+ ...recipientName ? { greeting: `Hello ${recipientName},` } : {},
1134
+ lines,
1135
+ action
1136
+ }),
1143
1137
  metadata: {
1144
1138
  provider: input.provider,
1145
1139
  tokenId: input.token.id
@@ -1148,13 +1142,29 @@ function createAuthNotificationsDeliveryHook(notificationsModule) {
1148
1142
  }
1149
1143
  }
1150
1144
  });
1151
- await notificationsModule.notifyUsing().channel("email", recipientName ? {
1152
- email: input.email,
1153
- name: recipientName
1154
- } : input.email).notify(notification);
1145
+ await notificationsModule.notify(authNotification, notification);
1155
1146
  },
1156
1147
  async sendPasswordReset(input) {
1157
- const notification = notificationsModule.defineNotification({
1148
+ const actionUrl = createAuthActionUrl(appUrl, input.route, input.token.plainTextToken);
1149
+ const authNotification = Object.freeze({
1150
+ email: input.email,
1151
+ url: actionUrl,
1152
+ expiresAt: input.token.expiresAt
1153
+ });
1154
+ const projectNotification = await loadProjectAuthNotification(
1155
+ projectRoot,
1156
+ AUTH_PASSWORD_RESET_NOTIFICATION_PATHS,
1157
+ "passwordResetNotification"
1158
+ );
1159
+ const lines = [
1160
+ "Click the link below to choose a new password.",
1161
+ `This reset link expires at ${formatAuthEmailExpiration(input.token.expiresAt)}.`
1162
+ ];
1163
+ const action = {
1164
+ label: "Reset password",
1165
+ url: actionUrl
1166
+ };
1167
+ const notification = projectNotification ?? notificationsModule.defineNotification({
1158
1168
  type: "auth.password-reset",
1159
1169
  via() {
1160
1170
  return ["email"];
@@ -1163,12 +1173,13 @@ function createAuthNotificationsDeliveryHook(notificationsModule) {
1163
1173
  email() {
1164
1174
  return {
1165
1175
  subject: "Reset your password",
1166
- lines: [
1167
- "Use this token to reset your password:",
1168
- input.token.plainTextToken,
1169
- `Provider: ${input.provider}`,
1170
- `Expires at: ${input.token.expiresAt.toISOString()}`
1171
- ],
1176
+ lines,
1177
+ action,
1178
+ html: createAuthEmailHtml({
1179
+ subject: "Reset your password",
1180
+ lines,
1181
+ action
1182
+ }),
1172
1183
  metadata: {
1173
1184
  provider: input.provider,
1174
1185
  tokenId: input.token.id
@@ -1177,7 +1188,7 @@ function createAuthNotificationsDeliveryHook(notificationsModule) {
1177
1188
  }
1178
1189
  }
1179
1190
  });
1180
- await notificationsModule.notifyUsing().channel("email", input.email).notify(notification);
1191
+ await notificationsModule.notify(authNotification, notification);
1181
1192
  }
1182
1193
  });
1183
1194
  }
@@ -1306,6 +1317,42 @@ function createNotificationMailText(message) {
1306
1317
  ].filter((value) => typeof value === "string" && value.length > 0);
1307
1318
  return parts.length > 0 ? parts.join("\n\n") : void 0;
1308
1319
  }
1320
+ function createNotificationMailHtml(message) {
1321
+ const greeting = typeof message.greeting === "string" ? message.greeting.trim() : void 0;
1322
+ const lines = (message.lines ?? []).map((line) => line.trim()).filter(Boolean);
1323
+ const sections = [
1324
+ greeting ? `<p style="margin:0 0 16px;">${escapeAuthEmailHtml(greeting)}</p>` : "",
1325
+ ...lines.map((line) => `<p style="margin:0 0 16px;">${escapeAuthEmailHtml(line)}</p>`),
1326
+ 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>` : "",
1327
+ 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>` : ""
1328
+ ].join("");
1329
+ return [
1330
+ "<!doctype html>",
1331
+ '<html><head><meta charset="utf-8">',
1332
+ `<title>${escapeAuthEmailHtml(message.subject)}</title>`,
1333
+ '</head><body style="margin:0;padding:24px;font-family:Arial,sans-serif;color:#0f172a;background:#f8fafc;">',
1334
+ '<div style="max-width:640px;margin:0 auto;background:#ffffff;border:1px solid #e2e8f0;border-radius:12px;padding:32px;">',
1335
+ `<h1 style="margin:0 0 24px;font-size:24px;line-height:1.3;">${escapeAuthEmailHtml(message.subject)}</h1>`,
1336
+ sections,
1337
+ "</div></body></html>"
1338
+ ].join("");
1339
+ }
1340
+ function joinAppUrl(baseUrl, path) {
1341
+ const normalizedBaseUrl = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
1342
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
1343
+ return `${normalizedBaseUrl}${normalizedPath}`;
1344
+ }
1345
+ function createAuthActionUrl(appUrl, path, token) {
1346
+ const url = new URL(joinAppUrl(appUrl, path));
1347
+ url.searchParams.set("token", token);
1348
+ return url.toString();
1349
+ }
1350
+ function escapeAuthEmailHtml(value) {
1351
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1352
+ }
1353
+ function createAuthEmailHtml(message) {
1354
+ return createNotificationMailHtml(message);
1355
+ }
1309
1356
  function createCoreNotificationMailSender(mailModule) {
1310
1357
  return Object.freeze({
1311
1358
  async send(message, context) {
@@ -1314,34 +1361,46 @@ function createCoreNotificationMailSender(mailModule) {
1314
1361
  throw new Error("[@holo-js/core] Email notifications require a resolved email route before bridging into mail.");
1315
1362
  }
1316
1363
  const fallbackText = createNotificationMailText(message);
1364
+ const fallbackHtml = createNotificationMailHtml(message);
1317
1365
  await mailModule.sendMail({
1318
1366
  to: route,
1319
1367
  subject: message.subject,
1320
- ...typeof message.html === "string" ? { html: message.html } : {},
1368
+ html: typeof message.html === "string" ? message.html : fallbackHtml,
1321
1369
  ...typeof (message.text ?? fallbackText) === "string" ? { text: message.text ?? fallbackText } : {},
1322
- ...typeof message.html !== "string" && typeof (message.text ?? fallbackText) === "string" ? { text: message.text ?? fallbackText } : {},
1323
1370
  ...message.metadata ? { metadata: message.metadata } : {}
1324
1371
  });
1325
1372
  }
1326
1373
  });
1327
1374
  }
1328
- function createAuthMailDeliveryHook(mailModule) {
1375
+ function createAuthMailDeliveryHook(mailModule, appUrl) {
1329
1376
  return Object.freeze({
1330
1377
  async sendEmailVerification(input) {
1331
1378
  const recipientName = typeof input.user?.name === "string" ? input.user.name?.trim() : void 0;
1379
+ const lines = [
1380
+ "Confirm your account to finish signing in.",
1381
+ `This verification link expires at ${formatAuthEmailExpiration(input.token.expiresAt)}.`
1382
+ ];
1383
+ const action = {
1384
+ label: "Verify email address",
1385
+ url: createAuthActionUrl(appUrl, input.route, input.token.plainTextToken)
1386
+ };
1332
1387
  await mailModule.sendMail({
1333
1388
  to: {
1334
1389
  email: input.email,
1335
1390
  ...recipientName ? { name: recipientName } : {}
1336
1391
  },
1337
1392
  subject: "Verify your email address",
1338
- text: [
1339
- recipientName ? `Hello ${recipientName},` : void 0,
1340
- "Use this token to verify your email address:",
1341
- input.token.plainTextToken,
1342
- `Provider: ${input.provider}`,
1343
- `Expires at: ${input.token.expiresAt.toISOString()}`
1344
- ].filter((value) => typeof value === "string").join("\n\n"),
1393
+ html: createAuthEmailHtml({
1394
+ subject: "Verify your email address",
1395
+ ...recipientName ? { greeting: `Hello ${recipientName},` } : {},
1396
+ lines,
1397
+ action
1398
+ }),
1399
+ text: createNotificationMailText({
1400
+ ...recipientName ? { greeting: `Hello ${recipientName},` } : {},
1401
+ lines,
1402
+ action
1403
+ }),
1345
1404
  metadata: {
1346
1405
  provider: input.provider,
1347
1406
  tokenId: input.token.id
@@ -1349,15 +1408,26 @@ function createAuthMailDeliveryHook(mailModule) {
1349
1408
  });
1350
1409
  },
1351
1410
  async sendPasswordReset(input) {
1411
+ const lines = [
1412
+ "Click the link below to choose a new password.",
1413
+ `This reset link expires at ${formatAuthEmailExpiration(input.token.expiresAt)}.`
1414
+ ];
1415
+ const action = {
1416
+ label: "Reset password",
1417
+ url: createAuthActionUrl(appUrl, input.route, input.token.plainTextToken)
1418
+ };
1352
1419
  await mailModule.sendMail({
1353
1420
  to: input.email,
1354
1421
  subject: "Reset your password",
1355
- text: [
1356
- "Use this token to reset your password:",
1357
- input.token.plainTextToken,
1358
- `Provider: ${input.provider}`,
1359
- `Expires at: ${input.token.expiresAt.toISOString()}`
1360
- ].join("\n\n"),
1422
+ html: createAuthEmailHtml({
1423
+ subject: "Reset your password",
1424
+ lines,
1425
+ action
1426
+ }),
1427
+ text: createNotificationMailText({
1428
+ lines,
1429
+ action
1430
+ }),
1361
1431
  metadata: {
1362
1432
  provider: input.provider,
1363
1433
  tokenId: input.token.id
@@ -1500,6 +1570,7 @@ async function createCoreSocialBindings(projectRootOrLoadedConfig, loadedConfigO
1500
1570
  state: record.state,
1501
1571
  codeVerifier: record.codeVerifier,
1502
1572
  guard: record.guard,
1573
+ browserBinding: record.browserBinding,
1503
1574
  createdAt: record.createdAt.toISOString()
1504
1575
  }
1505
1576
  });
@@ -1518,6 +1589,7 @@ async function createCoreSocialBindings(projectRootOrLoadedConfig, loadedConfigO
1518
1589
  state,
1519
1590
  codeVerifier: data.codeVerifier,
1520
1591
  guard: data.guard,
1592
+ ...typeof data.browserBinding === "string" ? { browserBinding: data.browserBinding } : {},
1521
1593
  createdAt: normalizeDateValue(data.createdAt ?? /* @__PURE__ */ new Date())
1522
1594
  };
1523
1595
  },
@@ -1584,25 +1656,44 @@ function fromHostedIdentityProviderValue(namespace, provider) {
1584
1656
  return provider.startsWith(prefix) ? provider.slice(prefix.length) : provider;
1585
1657
  }
1586
1658
  function createCoreHostedIdentityStore(namespace) {
1659
+ const normalizeRow = (row, fallback) => {
1660
+ const profile = normalizeJsonValue(row.profile);
1661
+ return {
1662
+ provider: fromHostedIdentityProviderValue(namespace, String(row.provider ?? fallback.provider)),
1663
+ providerUserId: String(row.provider_user_id ?? fallback.providerUserId),
1664
+ guard: String(row.guard ?? "web"),
1665
+ authProvider: String(row.auth_provider ?? fallback.authProvider),
1666
+ userId: normalizeStoredUserId(row.user_id),
1667
+ email: typeof row.email === "string" ? row.email : void 0,
1668
+ emailVerified: row.email_verified === true || row.email_verified === 1 || row.email_verified === "1",
1669
+ profile: typeof profile === "object" && profile ? profile : {},
1670
+ linkedAt: normalizeDateValue(row.created_at ?? /* @__PURE__ */ new Date()),
1671
+ updatedAt: normalizeDateValue(row.updated_at ?? /* @__PURE__ */ new Date())
1672
+ };
1673
+ };
1674
+ const findStoredIdentity = async (provider, providerUserId, authProvider = "users") => {
1675
+ const providerValue = toHostedIdentityProviderValue(namespace, provider);
1676
+ const row = await DB.table("auth_identities").where("provider", providerValue).where("provider_user_id", providerUserId).first();
1677
+ return row ? normalizeRow(row, { provider, providerUserId, authProvider }) : null;
1678
+ };
1679
+ const toPayload = (record) => {
1680
+ const providerValue = toHostedIdentityProviderValue(namespace, record.provider);
1681
+ return {
1682
+ user_id: String(record.userId),
1683
+ provider: providerValue,
1684
+ provider_user_id: record.providerUserId,
1685
+ guard: record.guard,
1686
+ auth_provider: record.authProvider,
1687
+ email: record.email ?? null,
1688
+ email_verified: record.emailVerified ? 1 : 0,
1689
+ profile: JSON.stringify(record.profile),
1690
+ created_at: record.linkedAt.toISOString(),
1691
+ updated_at: record.updatedAt.toISOString()
1692
+ };
1693
+ };
1587
1694
  return Object.freeze({
1588
1695
  async findByProviderUserId(provider, providerUserId) {
1589
- const providerValue = toHostedIdentityProviderValue(namespace, provider);
1590
- const row = await DB.table("auth_identities").where("provider", providerValue).where("provider_user_id", providerUserId).first();
1591
- if (!row) {
1592
- return null;
1593
- }
1594
- return {
1595
- provider: fromHostedIdentityProviderValue(namespace, String(row.provider ?? provider)),
1596
- providerUserId: String(row.provider_user_id ?? providerUserId),
1597
- guard: String(row.guard ?? "web"),
1598
- authProvider: String(row.auth_provider ?? "users"),
1599
- userId: normalizeStoredUserId(row.user_id),
1600
- email: typeof row.email === "string" ? row.email : void 0,
1601
- emailVerified: row.email_verified === true || row.email_verified === 1 || row.email_verified === "1",
1602
- profile: typeof normalizeJsonValue(row.profile) === "object" && normalizeJsonValue(row.profile) ? normalizeJsonValue(row.profile) : {},
1603
- linkedAt: normalizeDateValue(row.created_at ?? /* @__PURE__ */ new Date()),
1604
- updatedAt: normalizeDateValue(row.updated_at ?? /* @__PURE__ */ new Date())
1605
- };
1696
+ return await findStoredIdentity(provider, providerUserId);
1606
1697
  },
1607
1698
  async findByUserId(provider, authProvider, userId) {
1608
1699
  const providerValue = toHostedIdentityProviderValue(namespace, provider);
@@ -1610,35 +1701,26 @@ function createCoreHostedIdentityStore(namespace) {
1610
1701
  if (!row) {
1611
1702
  return null;
1612
1703
  }
1613
- return {
1614
- provider: fromHostedIdentityProviderValue(namespace, String(row.provider ?? provider)),
1704
+ return normalizeRow(row, {
1705
+ provider,
1615
1706
  providerUserId: String(row.provider_user_id),
1616
- guard: String(row.guard ?? "web"),
1617
- authProvider: String(row.auth_provider ?? authProvider),
1618
- userId: normalizeStoredUserId(row.user_id),
1619
- email: typeof row.email === "string" ? row.email : void 0,
1620
- emailVerified: row.email_verified === true || row.email_verified === 1 || row.email_verified === "1",
1621
- profile: typeof normalizeJsonValue(row.profile) === "object" && normalizeJsonValue(row.profile) ? normalizeJsonValue(row.profile) : {},
1622
- linkedAt: normalizeDateValue(row.created_at ?? /* @__PURE__ */ new Date()),
1623
- updatedAt: normalizeDateValue(row.updated_at ?? /* @__PURE__ */ new Date())
1624
- };
1707
+ authProvider
1708
+ });
1709
+ },
1710
+ async claim(record) {
1711
+ const value = record;
1712
+ await DB.table("auth_identities").insertOrIgnore(toPayload(value));
1713
+ const claimed = await findStoredIdentity(value.provider, value.providerUserId, value.authProvider);
1714
+ if (!claimed) {
1715
+ throw new Error("[@holo-js/core] Claimed hosted identity could not be read back from auth_identities.");
1716
+ }
1717
+ return claimed;
1625
1718
  },
1626
1719
  async save(record) {
1627
1720
  const value = record;
1628
1721
  const providerValue = toHostedIdentityProviderValue(namespace, value.provider);
1629
1722
  const existing = await DB.table("auth_identities").where("provider", providerValue).where("provider_user_id", value.providerUserId).first();
1630
- const payload = {
1631
- user_id: String(value.userId),
1632
- provider: providerValue,
1633
- provider_user_id: value.providerUserId,
1634
- guard: value.guard,
1635
- auth_provider: value.authProvider,
1636
- email: value.email ?? null,
1637
- email_verified: value.emailVerified ? 1 : 0,
1638
- profile: JSON.stringify(value.profile),
1639
- created_at: value.linkedAt.toISOString(),
1640
- updated_at: value.updatedAt.toISOString()
1641
- };
1723
+ const payload = toPayload(value);
1642
1724
  if (existing && typeof existing.id !== "undefined") {
1643
1725
  await DB.table("auth_identities").where("id", existing.id).update(payload);
1644
1726
  return;
@@ -1825,7 +1907,9 @@ async function createCoreAuthProviders(projectRoot, loadedConfig) {
1825
1907
  return output;
1826
1908
  };
1827
1909
  const prepareAuthCreateInput = async (input) => {
1828
- const sanitizedInput = sanitizeAuthWriteInput(input);
1910
+ const sanitizedInput = sanitizeAuthWriteInput(input, {
1911
+ enforceFillable: false
1912
+ });
1829
1913
  if (typeof resolvedModule.prepareAuthCreateInput !== "function") {
1830
1914
  return sanitizedInput;
1831
1915
  }
@@ -1834,7 +1918,9 @@ async function createCoreAuthProviders(projectRoot, loadedConfig) {
1834
1918
  });
1835
1919
  };
1836
1920
  const prepareAuthUpdateInput = async (user, input) => {
1837
- const sanitizedInput = sanitizeAuthWriteInput(input);
1921
+ const sanitizedInput = sanitizeAuthWriteInput(input, {
1922
+ enforceFillable: false
1923
+ });
1838
1924
  if (typeof resolvedModule.prepareAuthUpdateInput !== "function") {
1839
1925
  return sanitizedInput;
1840
1926
  }
@@ -1842,6 +1928,15 @@ async function createCoreAuthProviders(projectRoot, loadedConfig) {
1842
1928
  enforceFillable: false
1843
1929
  });
1844
1930
  };
1931
+ const saveAuthEntity = async (entity, values) => {
1932
+ const repository = typeof model.getRepository === "function" ? model.getRepository() : null;
1933
+ if (repository && typeof repository.saveEntity === "function" && entity && typeof entity === "object" && typeof entity.forceFill === "function") {
1934
+ ;
1935
+ entity.forceFill(values);
1936
+ return repository.saveEntity(entity, new Set(Object.keys(values)));
1937
+ }
1938
+ return null;
1939
+ };
1845
1940
  const adapter = {
1846
1941
  async findById(id) {
1847
1942
  const resolved = await model.find(id);
@@ -1871,14 +1966,34 @@ async function createCoreAuthProviders(projectRoot, loadedConfig) {
1871
1966
  return resolved ? markProviderUser(resolved, providerName) : null;
1872
1967
  },
1873
1968
  async create(input) {
1874
- return markProviderUser(await model.create(await prepareAuthCreateInput(input)), providerName);
1969
+ const values = await prepareAuthCreateInput(input);
1970
+ const repository = typeof model.getRepository === "function" ? model.getRepository() : null;
1971
+ const entity = repository && typeof repository.saveEntity === "function" ? new Entity(repository, values, false) : null;
1972
+ const persisted = entity ? await saveAuthEntity(entity, values) : null;
1973
+ return markProviderUser(persisted ?? await model.create(values), providerName);
1974
+ },
1975
+ async delete(id) {
1976
+ const repository = typeof model.getRepository === "function" ? model.getRepository() : null;
1977
+ if (repository && typeof repository.delete === "function") {
1978
+ await repository.delete(id);
1979
+ return;
1980
+ }
1981
+ if (typeof model.delete === "function") {
1982
+ await model.delete(id);
1983
+ return;
1984
+ }
1985
+ const existing = typeof model.find === "function" ? await model.find(id) : null;
1986
+ if (existing && typeof existing === "object" && "delete" in existing && typeof existing.delete === "function") {
1987
+ await existing.delete();
1988
+ }
1875
1989
  },
1876
1990
  /* v8 ignore start -- adapter shape mirrors the auth package contract; core tests cover the wired runtime behavior */
1877
1991
  async update(user, input) {
1878
- return markProviderUser(
1879
- await model.update(getEntityAttributes(user).id, await prepareAuthUpdateInput(user, input)),
1880
- providerName
1881
- );
1992
+ const id = getEntityAttributes(user).id;
1993
+ const values = await prepareAuthUpdateInput(user, input);
1994
+ const existing = typeof model.find === "function" ? await model.find(id) : null;
1995
+ const persisted = existing ? await saveAuthEntity(existing, values) : null;
1996
+ return markProviderUser(persisted ?? await model.update(id, values), providerName);
1882
1997
  },
1883
1998
  matchesUser(user) {
1884
1999
  if (typeof model === "function" && user instanceof model) {
@@ -2252,6 +2367,7 @@ async function reconfigureOptionalHoloSubsystems(projectRoot, loadedConfig, opti
2252
2367
  getRuntimeState().securityRateLimitStoreManaged = !shouldReuseExistingSecurityStore;
2253
2368
  securityModule.configureSecurityRuntime({
2254
2369
  config: loadedConfig.security,
2370
+ cors: loadedConfig.cors,
2255
2371
  rateLimitStore,
2256
2372
  csrfSigningKey: loadedConfig.app.key,
2257
2373
  defaultKeyResolver: async () => {
@@ -2334,7 +2450,8 @@ async function reconfigureOptionalHoloSubsystems(projectRoot, loadedConfig, opti
2334
2450
  }
2335
2451
  const socialModule = authConfigUsesSocialProviders(loadedConfig) ? await loadSocialModule(true) : void 0;
2336
2452
  const authStores = createCoreAuthStores(loadedConfig);
2337
- authContext = authModule.createAsyncAuthContext();
2453
+ const baseAuthContext = authModule.createAsyncAuthContext();
2454
+ authContext = createRequestAwareAuthContext(baseAuthContext, options.authRequest);
2338
2455
  authModule.configureAuthRuntime({
2339
2456
  config: loadedConfig.auth,
2340
2457
  session: sessionModule.getSessionRuntime(),
@@ -2342,17 +2459,18 @@ async function reconfigureOptionalHoloSubsystems(projectRoot, loadedConfig, opti
2342
2459
  tokens: authStores.tokens,
2343
2460
  emailVerificationTokens: authStores.emailVerificationTokens,
2344
2461
  passwordResetTokens: authStores.passwordResetTokens,
2345
- ...notificationsModule && (mailModule || notificationsRuntimeBindings?.mailer) ? { delivery: createAuthNotificationsDeliveryHook(notificationsModule) } : mailModule ? { delivery: createAuthMailDeliveryHook(mailModule) } : {},
2462
+ ...notificationsModule && (mailModule || notificationsRuntimeBindings?.mailer) ? { delivery: createAuthNotificationsDeliveryHook(notificationsModule, loadedConfig.app.url, projectRoot) } : mailModule ? { delivery: createAuthMailDeliveryHook(mailModule, loadedConfig.app.url) } : {},
2346
2463
  context: authContext
2347
2464
  });
2348
- const boundAuthRuntime = bindAuthRuntimeToContext(authModule.getAuthRuntime(), authContext);
2465
+ const authRuntime = authModule.getAuthRuntime();
2349
2466
  if (authorizationModule) {
2350
2467
  authorizationModule.authorizationInternals.configureAuthorizationAuthIntegration({
2351
2468
  hasGuard(guardName) {
2352
2469
  return guardName in loadedConfig.auth.guards;
2353
2470
  },
2354
- resolveDefaultActor: async () => boundAuthRuntime.user(),
2355
- resolveGuardActor: async (guardName) => boundAuthRuntime.guard(guardName).user()
2471
+ resolveDefaultActor: async () => authRuntime.user(),
2472
+ resolveGuardActor: async (guardName) => authRuntime.guard(guardName).user(),
2473
+ ...options.authorizationError?.createError ? { createError: options.authorizationError.createError } : {}
2356
2474
  });
2357
2475
  }
2358
2476
  if (socialModule) {
@@ -2363,12 +2481,12 @@ async function reconfigureOptionalHoloSubsystems(projectRoot, loadedConfig, opti
2363
2481
  }
2364
2482
  if (workosModule) {
2365
2483
  workosModule.configureWorkosAuthRuntime({
2366
- identityStore: createCoreHostedIdentityStore("workos")
2484
+ identityStore: loadedConfig.auth.workos.identityStore ?? createCoreHostedIdentityStore("workos")
2367
2485
  });
2368
2486
  }
2369
2487
  if (clerkModule) {
2370
2488
  clerkModule.configureClerkAuthRuntime({
2371
- identityStore: createCoreHostedIdentityStore("clerk")
2489
+ identityStore: loadedConfig.auth.clerk.identityStore ?? createCoreHostedIdentityStore("clerk")
2372
2490
  });
2373
2491
  }
2374
2492
  } else if (authorizationModule) {
@@ -2474,11 +2592,14 @@ async function createHolo(projectRoot, options = {}) {
2474
2592
  return activeSessionRuntime;
2475
2593
  },
2476
2594
  get auth() {
2477
- return activeAuthRuntime && activeAuthContext ? bindAuthRuntimeToContext(activeAuthRuntime, activeAuthContext) : activeAuthRuntime;
2595
+ return activeAuthRuntime;
2478
2596
  },
2479
2597
  initialized: false,
2480
2598
  useConfig: accessors.useConfig,
2481
2599
  config: accessors.config,
2600
+ setAuthRequestAccessors(authRequest) {
2601
+ activeAuthContext?.setRequestAccessors?.(authRequest);
2602
+ },
2482
2603
  async initialize() {
2483
2604
  if (runtime.initialized) {
2484
2605
  throw new Error("Holo runtime is already initialized.");
@@ -2488,6 +2609,8 @@ async function createHolo(projectRoot, options = {}) {
2488
2609
  }
2489
2610
  configureConfigRuntime(loadedConfig.all);
2490
2611
  configureDB(manager);
2612
+ await preloadGeneratedSchemaModule(projectRoot, registry);
2613
+ await preloadDiscoveredModelModules(projectRoot, registry);
2491
2614
  previousOptionalSubsystemBindings = snapshotOptionalSubsystemRuntimeBindings();
2492
2615
  if (options.renderView) {
2493
2616
  configureHoloRenderingRuntime({
@@ -2497,7 +2620,9 @@ async function createHolo(projectRoot, options = {}) {
2497
2620
  try {
2498
2621
  await manager.initializeAll();
2499
2622
  const optionalSubsystems = await reconfigureOptionalHoloSubsystems(projectRoot, loadedConfig, {
2500
- renderView: options.renderView
2623
+ renderView: options.renderView,
2624
+ authRequest: options.authRequest,
2625
+ authorizationError: options.authorizationError
2501
2626
  });
2502
2627
  activeQueueModule = optionalSubsystems.queueModule;
2503
2628
  activeSessionRuntime = optionalSubsystems.session;
@@ -2615,13 +2740,19 @@ async function initializeHolo(projectRoot, options = {}) {
2615
2740
  if (resolve3(current.projectRoot) !== resolvedProjectRoot) {
2616
2741
  throw new Error(`A Holo runtime is already initialized for "${current.projectRoot}".`);
2617
2742
  }
2743
+ ;
2744
+ current.setAuthRequestAccessors?.(options.authRequest);
2618
2745
  return current;
2619
2746
  }
2620
2747
  if (state.pending) {
2621
2748
  if (state.pendingProjectRoot && resolve3(state.pendingProjectRoot) !== resolvedProjectRoot) {
2622
2749
  throw new Error(`A Holo runtime is already initializing for "${state.pendingProjectRoot}".`);
2623
2750
  }
2624
- return state.pending;
2751
+ return state.pending.then((runtime) => {
2752
+ ;
2753
+ runtime.setAuthRequestAccessors?.(options.authRequest);
2754
+ return runtime;
2755
+ });
2625
2756
  }
2626
2757
  const pending = (async () => {
2627
2758
  const runtime = await createHolo(projectRoot, options);
@@ -2693,7 +2824,6 @@ var holoRuntimeInternals = {
2693
2824
  createAuthNotificationsDeliveryHook,
2694
2825
  createCoreNotificationBroadcaster,
2695
2826
  createCoreNotificationMailSender,
2696
- bindAuthRuntimeToContext,
2697
2827
  createCoreAuthProviders,
2698
2828
  createCoreAuthStores,
2699
2829
  createCoreHostedIdentityStore,