@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.
- package/dist/{chunk-4VPELMZN.mjs → chunk-5ZJO35LB.mjs} +460 -330
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +40 -11
- package/dist/runtime/index.d.ts +92 -28
- package/dist/runtime/index.mjs +1 -1
- package/package.json +40 -23
|
@@ -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 {
|
|
122
|
-
import {
|
|
123
|
-
import {
|
|
124
|
-
import {
|
|
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 {
|
|
142
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
564
|
-
|
|
565
|
-
|
|
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
|
-
|
|
569
|
-
|
|
570
|
-
return
|
|
640
|
+
...context,
|
|
641
|
+
getRequestCookie(name) {
|
|
642
|
+
return resolveRequestContext().getRequestCookie?.(name);
|
|
571
643
|
},
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
return runtime.user();
|
|
644
|
+
getRequestHeader(name) {
|
|
645
|
+
return resolveRequestContext().getRequestHeader?.(name);
|
|
575
646
|
},
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
return runtime.refreshUser();
|
|
647
|
+
appendResponseCookie(cookie) {
|
|
648
|
+
return resolveRequestContext().appendResponseCookie?.(cookie);
|
|
579
649
|
},
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
return runtime.id();
|
|
650
|
+
redirectResponse(url, status) {
|
|
651
|
+
return resolveRequestContext().redirectResponse?.(url, status);
|
|
583
652
|
},
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
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.
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
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
|
-
|
|
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
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
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
|
-
|
|
1356
|
-
"
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
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
|
-
|
|
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
|
|
1704
|
+
return normalizeRow(row, {
|
|
1705
|
+
provider,
|
|
1615
1706
|
providerUserId: String(row.provider_user_id),
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
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
|
-
|
|
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
|
|
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 () =>
|
|
2355
|
-
resolveGuardActor: async (guardName) =>
|
|
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
|
|
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,
|