@holo-js/core 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-AD2ZYGLN.mjs +2741 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.mjs +29 -14
- package/dist/runtime/index.d.ts +805 -3
- package/dist/runtime/index.mjs +13 -1
- package/package.json +59 -7
- package/dist/chunk-SVCNZBIQ.mjs +0 -703
|
@@ -0,0 +1,2741 @@
|
|
|
1
|
+
// src/portable/dbRuntime.ts
|
|
2
|
+
import {
|
|
3
|
+
createAdapter,
|
|
4
|
+
createDialect,
|
|
5
|
+
createRuntimeConnectionOptions,
|
|
6
|
+
createRuntimeLogger,
|
|
7
|
+
isSupportedDatabaseDriver,
|
|
8
|
+
parseDatabaseDriver,
|
|
9
|
+
resolveRuntimeConnectionManagerOptions
|
|
10
|
+
} from "@holo-js/db";
|
|
11
|
+
|
|
12
|
+
// src/portable/registry.ts
|
|
13
|
+
import { readFile } from "fs/promises";
|
|
14
|
+
import { resolve } from "path";
|
|
15
|
+
import { DEFAULT_HOLO_PROJECT_PATHS } from "@holo-js/db";
|
|
16
|
+
function isRecord(value) {
|
|
17
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
18
|
+
}
|
|
19
|
+
function normalizeLegacyGeneratedProjectRegistry(value) {
|
|
20
|
+
const paths = isRecord(value.paths) ? value.paths : void 0;
|
|
21
|
+
if (paths && typeof paths.jobs !== "string") {
|
|
22
|
+
paths.jobs = DEFAULT_HOLO_PROJECT_PATHS.jobs;
|
|
23
|
+
}
|
|
24
|
+
if (paths && typeof paths.events !== "string") {
|
|
25
|
+
paths.events = DEFAULT_HOLO_PROJECT_PATHS.events;
|
|
26
|
+
}
|
|
27
|
+
if (paths && typeof paths.listeners !== "string") {
|
|
28
|
+
paths.listeners = DEFAULT_HOLO_PROJECT_PATHS.listeners;
|
|
29
|
+
}
|
|
30
|
+
if (paths && typeof paths.broadcast !== "string") {
|
|
31
|
+
paths.broadcast = "server/broadcast";
|
|
32
|
+
}
|
|
33
|
+
if (paths && typeof paths.channels !== "string") {
|
|
34
|
+
paths.channels = "server/channels";
|
|
35
|
+
}
|
|
36
|
+
if (paths && typeof paths.authorizationPolicies !== "string") {
|
|
37
|
+
paths.authorizationPolicies = "server/policies";
|
|
38
|
+
}
|
|
39
|
+
if (paths && typeof paths.authorizationAbilities !== "string") {
|
|
40
|
+
paths.authorizationAbilities = "server/abilities";
|
|
41
|
+
}
|
|
42
|
+
if (!Array.isArray(value.jobs)) {
|
|
43
|
+
value.jobs = [];
|
|
44
|
+
}
|
|
45
|
+
if (!Array.isArray(value.events)) {
|
|
46
|
+
value.events = [];
|
|
47
|
+
}
|
|
48
|
+
if (!Array.isArray(value.listeners)) {
|
|
49
|
+
value.listeners = [];
|
|
50
|
+
}
|
|
51
|
+
if (!Array.isArray(value.broadcast)) {
|
|
52
|
+
value.broadcast = [];
|
|
53
|
+
}
|
|
54
|
+
if (!Array.isArray(value.channels)) {
|
|
55
|
+
value.channels = [];
|
|
56
|
+
}
|
|
57
|
+
if (!Array.isArray(value.authorizationPolicies)) {
|
|
58
|
+
value.authorizationPolicies = [];
|
|
59
|
+
}
|
|
60
|
+
if (!Array.isArray(value.authorizationAbilities)) {
|
|
61
|
+
value.authorizationAbilities = [];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function isGeneratedProjectRegistry(value) {
|
|
65
|
+
if (!isRecord(value)) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
normalizeLegacyGeneratedProjectRegistry(value);
|
|
69
|
+
return value.version === 1 && isRecord(value.paths) && Array.isArray(value.models) && Array.isArray(value.migrations) && Array.isArray(value.seeders) && Array.isArray(value.commands) && Array.isArray(value.jobs) && Array.isArray(value.events) && Array.isArray(value.listeners) && Array.isArray(value.broadcast) && Array.isArray(value.channels) && Array.isArray(value.authorizationPolicies) && Array.isArray(value.authorizationAbilities);
|
|
70
|
+
}
|
|
71
|
+
function resolveGeneratedProjectRegistryPath(projectRoot) {
|
|
72
|
+
return resolve(projectRoot, ".holo-js", "generated", "registry.json");
|
|
73
|
+
}
|
|
74
|
+
async function loadGeneratedProjectRegistry(projectRoot) {
|
|
75
|
+
const filePath = resolveGeneratedProjectRegistryPath(projectRoot);
|
|
76
|
+
const contents = await readFile(filePath, "utf8").catch(() => void 0);
|
|
77
|
+
if (!contents) {
|
|
78
|
+
return void 0;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const parsed = JSON.parse(contents);
|
|
82
|
+
return isGeneratedProjectRegistry(parsed) ? parsed : void 0;
|
|
83
|
+
} catch {
|
|
84
|
+
return void 0;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function createGeneratedBroadcastManifest(registry) {
|
|
88
|
+
return Object.freeze({
|
|
89
|
+
version: 1,
|
|
90
|
+
generatedAt: registry.generatedAt,
|
|
91
|
+
events: Object.freeze(registry.broadcast.map((entry) => Object.freeze({
|
|
92
|
+
name: entry.name,
|
|
93
|
+
channels: Object.freeze(entry.channels.map((channel) => Object.freeze({
|
|
94
|
+
type: channel.type,
|
|
95
|
+
pattern: channel.pattern
|
|
96
|
+
})))
|
|
97
|
+
}))),
|
|
98
|
+
channels: Object.freeze(registry.channels.map((entry) => Object.freeze({
|
|
99
|
+
name: entry.pattern,
|
|
100
|
+
pattern: entry.pattern,
|
|
101
|
+
type: entry.type,
|
|
102
|
+
params: Object.freeze([...entry.params]),
|
|
103
|
+
whispers: Object.freeze([...entry.whispers])
|
|
104
|
+
})))
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
async function loadGeneratedBroadcastManifest(projectRoot) {
|
|
108
|
+
const registry = await loadGeneratedProjectRegistry(projectRoot);
|
|
109
|
+
if (!registry) {
|
|
110
|
+
return void 0;
|
|
111
|
+
}
|
|
112
|
+
return createGeneratedBroadcastManifest(registry);
|
|
113
|
+
}
|
|
114
|
+
var registryInternals = {
|
|
115
|
+
createGeneratedBroadcastManifest,
|
|
116
|
+
isGeneratedProjectRegistry,
|
|
117
|
+
normalizeLegacyGeneratedProjectRegistry
|
|
118
|
+
};
|
|
119
|
+
|
|
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";
|
|
125
|
+
import {
|
|
126
|
+
config as globalConfig,
|
|
127
|
+
configureConfigRuntime,
|
|
128
|
+
createConfigAccessors,
|
|
129
|
+
loadConfigDirectory,
|
|
130
|
+
resetConfigRuntime,
|
|
131
|
+
useConfig as globalUseConfig
|
|
132
|
+
} from "@holo-js/config";
|
|
133
|
+
import {
|
|
134
|
+
configureDB,
|
|
135
|
+
DB,
|
|
136
|
+
resetDB
|
|
137
|
+
} from "@holo-js/db";
|
|
138
|
+
|
|
139
|
+
// 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";
|
|
143
|
+
async function importModule(specifier) {
|
|
144
|
+
if (process.env.VITEST) {
|
|
145
|
+
return import(
|
|
146
|
+
/* @vite-ignore */
|
|
147
|
+
specifier
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
return import(
|
|
151
|
+
/* webpackIgnore: true */
|
|
152
|
+
specifier
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
async function pathExists(path) {
|
|
156
|
+
try {
|
|
157
|
+
await stat(path);
|
|
158
|
+
return true;
|
|
159
|
+
} catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async function writeLoaderTsconfig(projectRoot, tempDir) {
|
|
164
|
+
const projectTsconfigPath = join(projectRoot, "tsconfig.json");
|
|
165
|
+
if (await pathExists(projectTsconfigPath)) {
|
|
166
|
+
return projectTsconfigPath;
|
|
167
|
+
}
|
|
168
|
+
const tsconfigPath = join(tempDir, "tsconfig.json");
|
|
169
|
+
const contents = JSON.stringify({
|
|
170
|
+
compilerOptions: {
|
|
171
|
+
baseUrl: projectRoot,
|
|
172
|
+
paths: {
|
|
173
|
+
"~/*": ["./*"],
|
|
174
|
+
"@/*": ["./*"]
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}, null, 2);
|
|
178
|
+
await writeFile(tsconfigPath, `${contents}
|
|
179
|
+
`, "utf8");
|
|
180
|
+
return tsconfigPath;
|
|
181
|
+
}
|
|
182
|
+
async function bundleRuntimeModule(projectRoot, entryPath) {
|
|
183
|
+
const runtimeTempRoot = join(projectRoot, ".holo-js", "runtime");
|
|
184
|
+
await mkdir(runtimeTempRoot, { recursive: true });
|
|
185
|
+
const tempDir = await mkdtemp(join(runtimeTempRoot, "bundle-"));
|
|
186
|
+
const tsconfigPath = await writeLoaderTsconfig(projectRoot, tempDir);
|
|
187
|
+
const outfile = join(tempDir, `${basename(entryPath, extname(entryPath))}.mjs`);
|
|
188
|
+
const cleanup = async () => {
|
|
189
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
190
|
+
};
|
|
191
|
+
try {
|
|
192
|
+
await runtimeModuleInternals.runEsbuild({
|
|
193
|
+
absWorkingDir: projectRoot,
|
|
194
|
+
bundle: true,
|
|
195
|
+
entryPoints: [entryPath],
|
|
196
|
+
outfile,
|
|
197
|
+
format: "esm",
|
|
198
|
+
logLevel: "silent",
|
|
199
|
+
packages: "external",
|
|
200
|
+
platform: "node",
|
|
201
|
+
target: "node20",
|
|
202
|
+
tsconfig: tsconfigPath,
|
|
203
|
+
sourcemap: false
|
|
204
|
+
});
|
|
205
|
+
return {
|
|
206
|
+
path: outfile,
|
|
207
|
+
cleanup
|
|
208
|
+
};
|
|
209
|
+
} catch (error) {
|
|
210
|
+
await cleanup();
|
|
211
|
+
if (error && typeof error === "object" && Array.isArray(error.errors)) {
|
|
212
|
+
const message = error.errors.map((entry) => {
|
|
213
|
+
if (typeof entry.text === "string" && entry.text.trim()) {
|
|
214
|
+
return entry.text;
|
|
215
|
+
}
|
|
216
|
+
if (typeof entry.message === "string" && entry.message.trim()) {
|
|
217
|
+
return entry.message;
|
|
218
|
+
}
|
|
219
|
+
return "Unknown build error.";
|
|
220
|
+
}).join("\n");
|
|
221
|
+
throw new Error(message);
|
|
222
|
+
}
|
|
223
|
+
if (error instanceof Error && error.message) {
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
226
|
+
throw new Error(`Failed to load ${entryPath}.`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
async function importBundledRuntimeModule(projectRoot, entryPath) {
|
|
230
|
+
const bundled = await bundleRuntimeModule(projectRoot, entryPath);
|
|
231
|
+
try {
|
|
232
|
+
return await runtimeModuleInternals.importModule(
|
|
233
|
+
`${pathToFileURL(bundled.path).href}?t=${Date.now()}`
|
|
234
|
+
);
|
|
235
|
+
} finally {
|
|
236
|
+
await bundled.cleanup();
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
async function loadEsbuild() {
|
|
240
|
+
const module = await import(
|
|
241
|
+
/* webpackIgnore: true */
|
|
242
|
+
"esbuild"
|
|
243
|
+
);
|
|
244
|
+
if ("build" in module) {
|
|
245
|
+
return module;
|
|
246
|
+
}
|
|
247
|
+
return module.default;
|
|
248
|
+
}
|
|
249
|
+
async function runEsbuild(options) {
|
|
250
|
+
const esbuild = await runtimeModuleInternals.loadEsbuild();
|
|
251
|
+
return esbuild.build(options);
|
|
252
|
+
}
|
|
253
|
+
var runtimeModuleInternals = {
|
|
254
|
+
bundleRuntimeModule,
|
|
255
|
+
importModule,
|
|
256
|
+
loadEsbuild,
|
|
257
|
+
pathExists,
|
|
258
|
+
runEsbuild,
|
|
259
|
+
writeLoaderTsconfig
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// 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";
|
|
266
|
+
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
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function resolveStorageKeyPath(root, key) {
|
|
290
|
+
const segments = key.split(":").filter(Boolean);
|
|
291
|
+
if (segments.includes("..")) {
|
|
292
|
+
throw new Error('[Holo Storage] Storage paths must not contain ".." segments.');
|
|
293
|
+
}
|
|
294
|
+
return resolve2(root, ...segments);
|
|
295
|
+
}
|
|
296
|
+
function createFileStorageBackend(root) {
|
|
297
|
+
async function listStorageKeys(currentRoot, prefix = "") {
|
|
298
|
+
const entries = await readdir(currentRoot, { withFileTypes: true }).catch(() => []);
|
|
299
|
+
const keys = [];
|
|
300
|
+
for (const entry of entries) {
|
|
301
|
+
const nextPrefix = prefix ? `${prefix}:${entry.name}` : entry.name;
|
|
302
|
+
const entryPath = join2(currentRoot, entry.name);
|
|
303
|
+
if (entry.isDirectory()) {
|
|
304
|
+
keys.push(...await listStorageKeys(entryPath, nextPrefix));
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
if (entry.isFile()) {
|
|
308
|
+
keys.push(nextPrefix);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return keys;
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
async getItem(key) {
|
|
315
|
+
const value = await this.getItemRaw(key);
|
|
316
|
+
if (value === null) {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
const serialized = Buffer.isBuffer(value) ? value.toString("utf8") : value instanceof Uint8Array ? Buffer.from(value).toString("utf8") : value instanceof ArrayBuffer ? Buffer.from(value).toString("utf8") : String(value);
|
|
320
|
+
return JSON.parse(serialized);
|
|
321
|
+
},
|
|
322
|
+
async getItemRaw(key) {
|
|
323
|
+
const targetPath = resolveStorageKeyPath(root, key);
|
|
324
|
+
try {
|
|
325
|
+
return await readFile2(targetPath);
|
|
326
|
+
} catch {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
async setItem(key, value) {
|
|
331
|
+
await this.setItemRaw(key, JSON.stringify(value));
|
|
332
|
+
},
|
|
333
|
+
async setItemRaw(key, value) {
|
|
334
|
+
const targetPath = resolveStorageKeyPath(root, key);
|
|
335
|
+
await mkdir2(dirname(targetPath), { recursive: true });
|
|
336
|
+
await writeFile2(
|
|
337
|
+
targetPath,
|
|
338
|
+
value instanceof ArrayBuffer ? new Uint8Array(value) : value
|
|
339
|
+
);
|
|
340
|
+
},
|
|
341
|
+
async hasItem(key) {
|
|
342
|
+
return await this.getItemRaw(key) !== null;
|
|
343
|
+
},
|
|
344
|
+
async removeItem(key) {
|
|
345
|
+
const targetPath = resolveStorageKeyPath(root, key);
|
|
346
|
+
await rm2(targetPath, { force: true });
|
|
347
|
+
await rm2(`${targetPath}$`, { force: true });
|
|
348
|
+
},
|
|
349
|
+
async getKeys(base = "") {
|
|
350
|
+
const prefix = base.replace(/:+$/, "");
|
|
351
|
+
const keys = await listStorageKeys(root);
|
|
352
|
+
return keys.filter((key) => {
|
|
353
|
+
if (!prefix) {
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
return key === prefix || key.startsWith(`${prefix}:`);
|
|
357
|
+
});
|
|
358
|
+
},
|
|
359
|
+
async getMeta(key) {
|
|
360
|
+
return this.getItem(`${key}$`);
|
|
361
|
+
},
|
|
362
|
+
async setMeta(key, value) {
|
|
363
|
+
await this.setItem(`${key}$`, value);
|
|
364
|
+
},
|
|
365
|
+
async removeMeta(key) {
|
|
366
|
+
await this.removeItem(`${key}$`);
|
|
367
|
+
},
|
|
368
|
+
async clear(base = "") {
|
|
369
|
+
const prefix = base.replace(/:+$/, "");
|
|
370
|
+
const targetPath = prefix ? resolveStorageKeyPath(root, prefix) : root;
|
|
371
|
+
await rm2(targetPath, { recursive: true, force: true });
|
|
372
|
+
if (!prefix) {
|
|
373
|
+
await mkdir2(root, { recursive: true });
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
async function createS3StorageBackend(disk) {
|
|
379
|
+
const storageS3 = await importOptionalModule("@holo-js/storage/runtime/drivers/s3");
|
|
380
|
+
if (!storageS3) {
|
|
381
|
+
throw new Error("[@holo-js/core] Storage config references an s3 disk but @holo-js/storage-s3 is not installed.");
|
|
382
|
+
}
|
|
383
|
+
return storageS3.default({
|
|
384
|
+
bucket: disk.bucket,
|
|
385
|
+
region: disk.region,
|
|
386
|
+
endpoint: disk.endpoint,
|
|
387
|
+
accessKeyId: disk.accessKeyId,
|
|
388
|
+
secretAccessKey: disk.secretAccessKey,
|
|
389
|
+
sessionToken: disk.sessionToken,
|
|
390
|
+
forcePathStyleEndpoint: disk.forcePathStyleEndpoint
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
async function configurePlainNodeStorageRuntime(projectRoot, loadedConfig) {
|
|
394
|
+
const storageModule = await storageRuntimeInternals.importOptionalModule("@holo-js/storage");
|
|
395
|
+
const storageRuntime = await storageRuntimeInternals.importOptionalModule("@holo-js/storage/runtime");
|
|
396
|
+
if (!storageModule || !storageRuntime) {
|
|
397
|
+
throw new Error("[@holo-js/core] Storage is configured but @holo-js/storage is not installed.");
|
|
398
|
+
}
|
|
399
|
+
const normalizedStorage = storageModule.normalizeModuleOptions({
|
|
400
|
+
defaultDisk: loadedConfig.storage.defaultDisk,
|
|
401
|
+
routePrefix: loadedConfig.storage.routePrefix,
|
|
402
|
+
disks: loadedConfig.storage.disks
|
|
403
|
+
});
|
|
404
|
+
const backends = /* @__PURE__ */ new Map();
|
|
405
|
+
for (const [diskName, disk] of Object.entries(normalizedStorage.disks)) {
|
|
406
|
+
const backend = disk.driver === "s3" ? await createS3StorageBackend(disk) : createFileStorageBackend(resolve2(projectRoot, disk.root));
|
|
407
|
+
backends.set(diskName, backend);
|
|
408
|
+
}
|
|
409
|
+
storageRuntime.configureStorageRuntime({
|
|
410
|
+
getRuntimeConfig: () => ({
|
|
411
|
+
holoStorage: normalizedStorage,
|
|
412
|
+
holo: { appUrl: loadedConfig.app.url }
|
|
413
|
+
}),
|
|
414
|
+
getStorage: (base) => {
|
|
415
|
+
const diskName = base.replace(/^holo:/, "");
|
|
416
|
+
const backend = backends.get(diskName);
|
|
417
|
+
if (!backend) {
|
|
418
|
+
throw new Error(`[Holo Storage] Disk "${diskName}" backend is not configured.`);
|
|
419
|
+
}
|
|
420
|
+
return backend;
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
async function resetOptionalStorageRuntime() {
|
|
425
|
+
const storageRuntime = await storageRuntimeInternals.importOptionalModule("@holo-js/storage/runtime");
|
|
426
|
+
storageRuntime?.resetStorageRuntime();
|
|
427
|
+
}
|
|
428
|
+
var storageRuntimeInternals = {
|
|
429
|
+
importOptionalModule
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
// src/portable/holo.ts
|
|
433
|
+
function closeSessionRedisAdapter(adapter) {
|
|
434
|
+
return adapter.disconnect?.() || adapter.close?.();
|
|
435
|
+
}
|
|
436
|
+
var CORE_BROADCAST_PUBLISHER_MARKER = /* @__PURE__ */ Symbol.for("holo-js.core.broadcast.publisher");
|
|
437
|
+
function getRuntimeState() {
|
|
438
|
+
const runtime = globalThis;
|
|
439
|
+
runtime.__holoRuntime__ ??= {};
|
|
440
|
+
return runtime.__holoRuntime__;
|
|
441
|
+
}
|
|
442
|
+
function configureHoloRenderingRuntime(bindings) {
|
|
443
|
+
getRuntimeState().renderView = bindings?.renderView;
|
|
444
|
+
}
|
|
445
|
+
function resetHoloRenderingRuntime() {
|
|
446
|
+
getRuntimeState().renderView = void 0;
|
|
447
|
+
}
|
|
448
|
+
function restoreHoloRenderingRuntime(renderView) {
|
|
449
|
+
if (renderView) {
|
|
450
|
+
configureHoloRenderingRuntime({
|
|
451
|
+
renderView
|
|
452
|
+
});
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
resetHoloRenderingRuntime();
|
|
456
|
+
}
|
|
457
|
+
function snapshotOptionalSubsystemRuntimeBindings() {
|
|
458
|
+
const state = getRuntimeState();
|
|
459
|
+
const runtime = globalThis;
|
|
460
|
+
return Object.freeze({
|
|
461
|
+
...runtime.__holoMailRuntime__?.bindings ? { mail: runtime.__holoMailRuntime__.bindings } : {},
|
|
462
|
+
...runtime.__holoNotificationsRuntime__?.bindings ? { notifications: runtime.__holoNotificationsRuntime__.bindings } : {},
|
|
463
|
+
...runtime.__holoBroadcastRuntime__?.bindings ? { broadcast: runtime.__holoBroadcastRuntime__.bindings } : {},
|
|
464
|
+
/* v8 ignore start -- this snapshot branch only applies when restoring externally managed session adapters across runtime swaps */
|
|
465
|
+
...state.sessionRedisAdapters ? {
|
|
466
|
+
session: Object.freeze({
|
|
467
|
+
sessionRedisAdapters: state.sessionRedisAdapters
|
|
468
|
+
})
|
|
469
|
+
} : {},
|
|
470
|
+
/* v8 ignore stop */
|
|
471
|
+
...runtime.__holoSecurityRuntime__?.bindings || state.securityRedisAdapter || typeof state.securityRateLimitStoreManaged !== "undefined" ? {
|
|
472
|
+
security: Object.freeze({
|
|
473
|
+
...runtime.__holoSecurityRuntime__?.bindings ? { bindings: runtime.__holoSecurityRuntime__.bindings } : {},
|
|
474
|
+
...state.securityRedisAdapter ? { securityRedisAdapter: state.securityRedisAdapter } : {},
|
|
475
|
+
...typeof state.securityRateLimitStoreManaged !== "undefined" ? { securityRateLimitStoreManaged: state.securityRateLimitStoreManaged } : {}
|
|
476
|
+
})
|
|
477
|
+
} : {}
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
function restoreOptionalSubsystemRuntimeBindings(bindings) {
|
|
481
|
+
const state = getRuntimeState();
|
|
482
|
+
const runtime = globalThis;
|
|
483
|
+
if (bindings.mail || runtime.__holoMailRuntime__) {
|
|
484
|
+
runtime.__holoMailRuntime__ ??= {};
|
|
485
|
+
runtime.__holoMailRuntime__.bindings = bindings.mail;
|
|
486
|
+
}
|
|
487
|
+
if (bindings.notifications || runtime.__holoNotificationsRuntime__) {
|
|
488
|
+
runtime.__holoNotificationsRuntime__ ??= {};
|
|
489
|
+
runtime.__holoNotificationsRuntime__.bindings = bindings.notifications;
|
|
490
|
+
}
|
|
491
|
+
if (bindings.broadcast || runtime.__holoBroadcastRuntime__) {
|
|
492
|
+
runtime.__holoBroadcastRuntime__ ??= {};
|
|
493
|
+
runtime.__holoBroadcastRuntime__.bindings = bindings.broadcast;
|
|
494
|
+
}
|
|
495
|
+
state.sessionRedisAdapters = bindings.session?.sessionRedisAdapters;
|
|
496
|
+
if (bindings.security || runtime.__holoSecurityRuntime__) {
|
|
497
|
+
runtime.__holoSecurityRuntime__ ??= {};
|
|
498
|
+
runtime.__holoSecurityRuntime__.bindings = bindings.security?.bindings;
|
|
499
|
+
state.securityRedisAdapter = bindings.security?.securityRedisAdapter;
|
|
500
|
+
state.securityRateLimitStoreManaged = bindings.security?.securityRateLimitStoreManaged;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
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
|
+
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
|
+
}
|
|
531
|
+
}
|
|
532
|
+
var portableRuntimeModuleInternals = {
|
|
533
|
+
importOptionalModule: importOptionalModule2
|
|
534
|
+
};
|
|
535
|
+
function hasLoadedConfigFile(loadedConfig, configName) {
|
|
536
|
+
return loadedConfig.loadedFiles.some((filePath) => {
|
|
537
|
+
const normalizedPath = filePath.replaceAll("\\", "/");
|
|
538
|
+
return normalizedPath.endsWith(`/config/${configName}.ts`) || normalizedPath.endsWith(`/config/${configName}.mts`) || normalizedPath.endsWith(`/config/${configName}.js`) || normalizedPath.endsWith(`/config/${configName}.mjs`) || normalizedPath.endsWith(`/config/${configName}.cts`) || normalizedPath.endsWith(`/config/${configName}.cjs`);
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
function queueConfigUsesDatabaseDriver(loadedConfig) {
|
|
542
|
+
return Object.values(loadedConfig.queue.connections).some((connection) => connection.driver === "database");
|
|
543
|
+
}
|
|
544
|
+
function queueConfigUsesDatabaseBackedFailedStore(loadedConfig) {
|
|
545
|
+
return loadedConfig.queue.failed !== false;
|
|
546
|
+
}
|
|
547
|
+
function registryHasJobs(registry) {
|
|
548
|
+
return (registry?.jobs.length ?? 0) > 0;
|
|
549
|
+
}
|
|
550
|
+
function registryHasEvents(registry) {
|
|
551
|
+
return (registry?.events.length ?? 0) > 0 || (registry?.listeners.length ?? 0) > 0;
|
|
552
|
+
}
|
|
553
|
+
function authConfigUsesSocialProviders(loadedConfig) {
|
|
554
|
+
return Object.keys(loadedConfig.auth.social).length > 0;
|
|
555
|
+
}
|
|
556
|
+
function authConfigUsesWorkosProviders(loadedConfig) {
|
|
557
|
+
return Object.keys(loadedConfig.auth.workos).length > 0;
|
|
558
|
+
}
|
|
559
|
+
function authConfigUsesClerkProviders(loadedConfig) {
|
|
560
|
+
return Object.keys(loadedConfig.auth.clerk).length > 0;
|
|
561
|
+
}
|
|
562
|
+
var HOLO_AUTH_PROVIDER_MARKER = /* @__PURE__ */ Symbol.for("holo-js.auth.provider");
|
|
563
|
+
function bindAuthRuntimeToContext(runtime, authContext) {
|
|
564
|
+
const activate = () => {
|
|
565
|
+
authContext.activate();
|
|
566
|
+
};
|
|
567
|
+
return Object.freeze({
|
|
568
|
+
check() {
|
|
569
|
+
activate();
|
|
570
|
+
return runtime.check();
|
|
571
|
+
},
|
|
572
|
+
user() {
|
|
573
|
+
activate();
|
|
574
|
+
return runtime.user();
|
|
575
|
+
},
|
|
576
|
+
refreshUser() {
|
|
577
|
+
activate();
|
|
578
|
+
return runtime.refreshUser();
|
|
579
|
+
},
|
|
580
|
+
id() {
|
|
581
|
+
activate();
|
|
582
|
+
return runtime.id();
|
|
583
|
+
},
|
|
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
|
+
}
|
|
695
|
+
});
|
|
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
|
+
})
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
async function loadQueueModule(required = false) {
|
|
746
|
+
const queueModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/queue");
|
|
747
|
+
if (!queueModule && required) {
|
|
748
|
+
throw new Error("[@holo-js/core] Queue support requires @holo-js/queue to be installed.");
|
|
749
|
+
}
|
|
750
|
+
return queueModule;
|
|
751
|
+
}
|
|
752
|
+
async function loadQueueDbModule() {
|
|
753
|
+
return portableRuntimeModuleInternals.importOptionalModule("@holo-js/queue-db");
|
|
754
|
+
}
|
|
755
|
+
async function loadCacheModule(required = false, projectRoot) {
|
|
756
|
+
const cacheModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/cache", {
|
|
757
|
+
projectRoot
|
|
758
|
+
});
|
|
759
|
+
if (!cacheModule && required) {
|
|
760
|
+
throw new Error("[@holo-js/core] Cache support requires @holo-js/cache to be installed.");
|
|
761
|
+
}
|
|
762
|
+
return cacheModule;
|
|
763
|
+
}
|
|
764
|
+
function resetCacheRuntimeGlobalsFallback() {
|
|
765
|
+
const runtime = globalThis;
|
|
766
|
+
if (runtime.__holoCacheRuntime__) {
|
|
767
|
+
runtime.__holoCacheRuntime__.bindings = void 0;
|
|
768
|
+
}
|
|
769
|
+
if (runtime.__holoCacheQueryBridge__) {
|
|
770
|
+
runtime.__holoCacheQueryBridge__.dependencyIndex = void 0;
|
|
771
|
+
}
|
|
772
|
+
if (runtime.__holoDbQueryCacheBridge__) {
|
|
773
|
+
runtime.__holoDbQueryCacheBridge__.bridge = void 0;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
async function loadEventsModule(required = false) {
|
|
777
|
+
const eventsModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/events");
|
|
778
|
+
if (!eventsModule && required) {
|
|
779
|
+
throw new Error("[@holo-js/core] Events support requires @holo-js/events to be installed.");
|
|
780
|
+
}
|
|
781
|
+
return eventsModule;
|
|
782
|
+
}
|
|
783
|
+
async function loadSessionModule(required = false) {
|
|
784
|
+
const sessionModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/session");
|
|
785
|
+
if (!sessionModule && required) {
|
|
786
|
+
throw new Error("[@holo-js/core] Session support requires @holo-js/session to be installed.");
|
|
787
|
+
}
|
|
788
|
+
return sessionModule;
|
|
789
|
+
}
|
|
790
|
+
async function loadSecurityModule(required = false) {
|
|
791
|
+
const securityModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/security");
|
|
792
|
+
if (!securityModule && required) {
|
|
793
|
+
throw new Error("[@holo-js/core] Security support requires @holo-js/security to be installed.");
|
|
794
|
+
}
|
|
795
|
+
return securityModule;
|
|
796
|
+
}
|
|
797
|
+
async function loadSecurityRedisAdapterModule(required = false) {
|
|
798
|
+
const securityRedisAdapterModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/security/drivers/redis-adapter");
|
|
799
|
+
if (!securityRedisAdapterModule && required) {
|
|
800
|
+
throw new Error("[@holo-js/core] Redis-backed security rate limits require @holo-js/security/drivers/redis-adapter to be installed.");
|
|
801
|
+
}
|
|
802
|
+
return securityRedisAdapterModule;
|
|
803
|
+
}
|
|
804
|
+
async function loadSessionRedisAdapterModule(required = false) {
|
|
805
|
+
const sessionRedisAdapterModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/session/drivers/redis-adapter");
|
|
806
|
+
if (!sessionRedisAdapterModule && required) {
|
|
807
|
+
throw new Error("[@holo-js/core] Redis-backed session stores require @holo-js/session/drivers/redis-adapter to be installed.");
|
|
808
|
+
}
|
|
809
|
+
return sessionRedisAdapterModule;
|
|
810
|
+
}
|
|
811
|
+
async function loadNotificationsModule(required = false) {
|
|
812
|
+
const notificationsModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/notifications");
|
|
813
|
+
if (!notificationsModule && required) {
|
|
814
|
+
throw new Error("[@holo-js/core] Notifications support requires @holo-js/notifications to be installed.");
|
|
815
|
+
}
|
|
816
|
+
return notificationsModule;
|
|
817
|
+
}
|
|
818
|
+
async function loadBroadcastModule(required = false, projectRoot) {
|
|
819
|
+
const broadcastModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/broadcast", {
|
|
820
|
+
projectRoot
|
|
821
|
+
});
|
|
822
|
+
if (!broadcastModule && required) {
|
|
823
|
+
throw new Error("[@holo-js/core] Broadcast support requires @holo-js/broadcast to be installed.");
|
|
824
|
+
}
|
|
825
|
+
return broadcastModule;
|
|
826
|
+
}
|
|
827
|
+
async function loadMailModule(required = false) {
|
|
828
|
+
const mailModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/mail");
|
|
829
|
+
if (!mailModule && required) {
|
|
830
|
+
throw new Error("[@holo-js/core] Mail support requires @holo-js/mail to be installed.");
|
|
831
|
+
}
|
|
832
|
+
return mailModule;
|
|
833
|
+
}
|
|
834
|
+
async function loadAuthModule(required = false) {
|
|
835
|
+
const authModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/auth");
|
|
836
|
+
if (!authModule && required) {
|
|
837
|
+
throw new Error("[@holo-js/core] Auth support requires @holo-js/auth to be installed.");
|
|
838
|
+
}
|
|
839
|
+
return authModule;
|
|
840
|
+
}
|
|
841
|
+
async function loadAuthorizationModule(required = false) {
|
|
842
|
+
const authorizationModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/authorization");
|
|
843
|
+
if (!authorizationModule && required) {
|
|
844
|
+
throw new Error("[@holo-js/core] Authorization support requires @holo-js/authorization to be installed.");
|
|
845
|
+
}
|
|
846
|
+
return authorizationModule;
|
|
847
|
+
}
|
|
848
|
+
async function loadSocialModule(required = false) {
|
|
849
|
+
const socialModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/auth-social");
|
|
850
|
+
if (!socialModule && required) {
|
|
851
|
+
throw new Error("[@holo-js/core] Social auth config requires @holo-js/auth-social to be installed.");
|
|
852
|
+
}
|
|
853
|
+
return socialModule;
|
|
854
|
+
}
|
|
855
|
+
async function loadWorkosModule(required = false) {
|
|
856
|
+
const workosModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/auth-workos");
|
|
857
|
+
if (!workosModule && required) {
|
|
858
|
+
throw new Error("[@holo-js/core] WorkOS auth config requires @holo-js/auth-workos to be installed.");
|
|
859
|
+
}
|
|
860
|
+
return workosModule;
|
|
861
|
+
}
|
|
862
|
+
async function loadClerkModule(required = false) {
|
|
863
|
+
const clerkModule = await portableRuntimeModuleInternals.importOptionalModule("@holo-js/auth-clerk");
|
|
864
|
+
if (!clerkModule && required) {
|
|
865
|
+
throw new Error("[@holo-js/core] Clerk auth config requires @holo-js/auth-clerk to be installed.");
|
|
866
|
+
}
|
|
867
|
+
return clerkModule;
|
|
868
|
+
}
|
|
869
|
+
function resolveQueueJobExport(queueModule, moduleValue) {
|
|
870
|
+
const exports = moduleValue;
|
|
871
|
+
if (queueModule.isQueueJobDefinition(exports.default)) {
|
|
872
|
+
return exports.default;
|
|
873
|
+
}
|
|
874
|
+
return Object.values(exports).find((value) => queueModule.isQueueJobDefinition(value));
|
|
875
|
+
}
|
|
876
|
+
function resolveAuthorizationDefinitionExport(moduleValue, exportName, matcher) {
|
|
877
|
+
const exports = moduleValue;
|
|
878
|
+
if (exportName && exportName !== "default" && matcher(exports[exportName])) {
|
|
879
|
+
return exports[exportName];
|
|
880
|
+
}
|
|
881
|
+
if (matcher(exports.default)) {
|
|
882
|
+
return exports.default;
|
|
883
|
+
}
|
|
884
|
+
return Object.entries(exports).find(([name, value]) => name !== exportName && matcher(value))?.[1];
|
|
885
|
+
}
|
|
886
|
+
var HOLO_EVENT_DEFINITION_MARKER = /* @__PURE__ */ Symbol.for("holo-js.events.definition");
|
|
887
|
+
var HOLO_LISTENER_DEFINITION_MARKER = /* @__PURE__ */ Symbol.for("holo-js.events.listener");
|
|
888
|
+
function hasEventDefinitionMarker(value) {
|
|
889
|
+
return !!value && typeof value === "object" && HOLO_EVENT_DEFINITION_MARKER in value;
|
|
890
|
+
}
|
|
891
|
+
function hasListenerDefinitionMarker(value) {
|
|
892
|
+
return !!value && typeof value === "object" && HOLO_LISTENER_DEFINITION_MARKER in value;
|
|
893
|
+
}
|
|
894
|
+
function resolveEventExport(moduleValue) {
|
|
895
|
+
const exports = moduleValue;
|
|
896
|
+
if (hasEventDefinitionMarker(exports.default)) {
|
|
897
|
+
return exports.default;
|
|
898
|
+
}
|
|
899
|
+
return Object.values(exports).find((value) => hasEventDefinitionMarker(value));
|
|
900
|
+
}
|
|
901
|
+
function resolveListenerExport(eventsModule, moduleValue) {
|
|
902
|
+
const exports = moduleValue;
|
|
903
|
+
if (hasListenerDefinitionMarker(exports.default) || eventsModule.isListenerDefinition(exports.default)) {
|
|
904
|
+
return exports.default;
|
|
905
|
+
}
|
|
906
|
+
return Object.values(exports).find((value) => hasListenerDefinitionMarker(value) || eventsModule.isListenerDefinition(value));
|
|
907
|
+
}
|
|
908
|
+
function resolveProjectRelativePath(projectRoot, value) {
|
|
909
|
+
return value.startsWith(".") || !value.startsWith("/") ? resolve3(projectRoot, value) : value;
|
|
910
|
+
}
|
|
911
|
+
function normalizeDateLike(value) {
|
|
912
|
+
return value instanceof Date ? value : new Date(String(value));
|
|
913
|
+
}
|
|
914
|
+
function normalizeSessionRecordFromRow(row) {
|
|
915
|
+
const decodedData = (() => {
|
|
916
|
+
if (row.data && typeof row.data === "object") {
|
|
917
|
+
return row.data;
|
|
918
|
+
}
|
|
919
|
+
if (typeof row.data === "string") {
|
|
920
|
+
try {
|
|
921
|
+
const parsed = JSON.parse(row.data);
|
|
922
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
923
|
+
} catch {
|
|
924
|
+
return {};
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
return {};
|
|
928
|
+
})();
|
|
929
|
+
return Object.freeze({
|
|
930
|
+
id: String(row.id),
|
|
931
|
+
/* v8 ignore next -- runtime rows usually carry an explicit store name; this preserves a safe default */
|
|
932
|
+
store: typeof row.store === "string" ? row.store : "database",
|
|
933
|
+
data: Object.freeze(decodedData),
|
|
934
|
+
createdAt: normalizeDateLike(row.created_at),
|
|
935
|
+
lastActivityAt: normalizeDateLike(row.last_activity_at),
|
|
936
|
+
expiresAt: normalizeDateLike(row.expires_at),
|
|
937
|
+
rememberTokenHash: typeof row.remember_token_hash === "string" ? row.remember_token_hash : void 0
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
function serializeSessionRecordForRow(record) {
|
|
941
|
+
return {
|
|
942
|
+
id: record.id,
|
|
943
|
+
store: record.store,
|
|
944
|
+
/* v8 ignore next -- record.data is always present in runtime flows; nullish fallback is purely defensive */
|
|
945
|
+
data: JSON.stringify(record.data ?? {}),
|
|
946
|
+
created_at: record.createdAt.toISOString(),
|
|
947
|
+
last_activity_at: record.lastActivityAt.toISOString(),
|
|
948
|
+
expires_at: record.expiresAt.toISOString(),
|
|
949
|
+
invalidated_at: null,
|
|
950
|
+
remember_token_hash: record.rememberTokenHash ?? null
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
function normalizeNotificationRecordFromRow(row) {
|
|
954
|
+
const decodedData = normalizeJsonValue(row.data);
|
|
955
|
+
return Object.freeze({
|
|
956
|
+
id: String(row.id),
|
|
957
|
+
type: typeof row.type === "string" ? row.type : void 0,
|
|
958
|
+
notifiableType: String(row.notifiable_type),
|
|
959
|
+
notifiableId: typeof row.notifiable_id === "number" ? row.notifiable_id : String(row.notifiable_id),
|
|
960
|
+
data: decodedData,
|
|
961
|
+
readAt: row.read_at ? normalizeDateLike(row.read_at) : null,
|
|
962
|
+
createdAt: normalizeDateLike(row.created_at),
|
|
963
|
+
updatedAt: normalizeDateLike(row.updated_at)
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
function serializeNotificationRecordForRow(record) {
|
|
967
|
+
return {
|
|
968
|
+
id: record.id,
|
|
969
|
+
type: record.type ?? null,
|
|
970
|
+
notifiable_type: record.notifiableType,
|
|
971
|
+
notifiable_id: String(record.notifiableId),
|
|
972
|
+
data: JSON.stringify(record.data ?? null),
|
|
973
|
+
read_at: record.readAt ? record.readAt.toISOString() : null,
|
|
974
|
+
created_at: record.createdAt.toISOString(),
|
|
975
|
+
updated_at: record.updatedAt.toISOString()
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
function getEntityAttributes(value) {
|
|
979
|
+
if (value && typeof value === "object") {
|
|
980
|
+
const candidate = value;
|
|
981
|
+
if (typeof candidate.toAttributes === "function") {
|
|
982
|
+
return candidate.toAttributes();
|
|
983
|
+
}
|
|
984
|
+
if (typeof candidate.toJSON === "function") {
|
|
985
|
+
const serialized = candidate.toJSON();
|
|
986
|
+
if (serialized && typeof serialized === "object") {
|
|
987
|
+
return serialized;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
return value;
|
|
991
|
+
}
|
|
992
|
+
return {};
|
|
993
|
+
}
|
|
994
|
+
function markProviderUser(value, providerName) {
|
|
995
|
+
if (!value || typeof value !== "object") {
|
|
996
|
+
return value;
|
|
997
|
+
}
|
|
998
|
+
try {
|
|
999
|
+
Object.defineProperty(value, HOLO_AUTH_PROVIDER_MARKER, {
|
|
1000
|
+
value: providerName,
|
|
1001
|
+
enumerable: false,
|
|
1002
|
+
configurable: true
|
|
1003
|
+
});
|
|
1004
|
+
} catch {
|
|
1005
|
+
}
|
|
1006
|
+
return value;
|
|
1007
|
+
}
|
|
1008
|
+
async function createCoreManagedSessionStores(projectRoot, loadedConfig, sessionModule) {
|
|
1009
|
+
const stores = {};
|
|
1010
|
+
const redisAdapters = [];
|
|
1011
|
+
for (const [name, config] of Object.entries(loadedConfig.session.stores)) {
|
|
1012
|
+
if (config.driver === "file") {
|
|
1013
|
+
stores[name] = sessionModule.createFileSessionStore(resolveProjectRelativePath(projectRoot, config.path));
|
|
1014
|
+
continue;
|
|
1015
|
+
}
|
|
1016
|
+
if (config.driver === "database") {
|
|
1017
|
+
const connectionName = config.connection === "default" && !(config.connection in loadedConfig.database.connections) ? loadedConfig.database.defaultConnection : config.connection;
|
|
1018
|
+
stores[name] = sessionModule.createDatabaseSessionStore({
|
|
1019
|
+
async read(sessionId) {
|
|
1020
|
+
const row = await DB.table(config.table, connectionName).where("id", sessionId).whereNull("invalidated_at").first();
|
|
1021
|
+
return row ? normalizeSessionRecordFromRow(row) : null;
|
|
1022
|
+
},
|
|
1023
|
+
async write(record) {
|
|
1024
|
+
const normalized = serializeSessionRecordForRow(record);
|
|
1025
|
+
const existing = await DB.table(config.table, connectionName).find(String(normalized.id));
|
|
1026
|
+
if (existing) {
|
|
1027
|
+
await DB.table(config.table, connectionName).where("id", normalized.id).update(normalized);
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
await DB.table(config.table, connectionName).insert(normalized);
|
|
1031
|
+
},
|
|
1032
|
+
async delete(sessionId) {
|
|
1033
|
+
await DB.table(config.table, connectionName).where("id", sessionId).delete();
|
|
1034
|
+
}
|
|
1035
|
+
});
|
|
1036
|
+
continue;
|
|
1037
|
+
}
|
|
1038
|
+
if (config.driver === "redis") {
|
|
1039
|
+
const sessionRedisAdapterModule = await loadSessionRedisAdapterModule(true);
|
|
1040
|
+
const adapter = sessionRedisAdapterModule.createSessionRedisAdapter(config);
|
|
1041
|
+
try {
|
|
1042
|
+
await adapter.connect?.();
|
|
1043
|
+
redisAdapters.push(adapter);
|
|
1044
|
+
const store = sessionModule.createRedisSessionStore(adapter);
|
|
1045
|
+
stores[name] = store;
|
|
1046
|
+
} catch (error) {
|
|
1047
|
+
const originalError = error;
|
|
1048
|
+
const cleanupResults = await Promise.allSettled([
|
|
1049
|
+
closeSessionRedisAdapter(adapter),
|
|
1050
|
+
...redisAdapters.filter((existingAdapter) => existingAdapter !== adapter).map((existingAdapter) => closeSessionRedisAdapter(existingAdapter))
|
|
1051
|
+
]);
|
|
1052
|
+
const cleanupErrors = cleanupResults.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
|
|
1053
|
+
if (cleanupErrors.length > 0 && originalError instanceof Error) {
|
|
1054
|
+
Object.defineProperty(originalError, "cleanupErrors", {
|
|
1055
|
+
value: Object.freeze(cleanupErrors),
|
|
1056
|
+
configurable: true,
|
|
1057
|
+
enumerable: false
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
throw originalError;
|
|
1061
|
+
}
|
|
1062
|
+
continue;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
if (!(loadedConfig.session.driver in stores)) {
|
|
1066
|
+
throw new Error(
|
|
1067
|
+
`[@holo-js/core] Session driver "${loadedConfig.session.driver}" is configured but the runtime cannot boot it automatically.`
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
return Object.freeze({
|
|
1071
|
+
stores: Object.freeze(stores),
|
|
1072
|
+
redisAdapters: Object.freeze(redisAdapters)
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
async function createCoreSessionStores(projectRoot, loadedConfig, sessionModule) {
|
|
1076
|
+
return (await createCoreManagedSessionStores(projectRoot, loadedConfig, sessionModule)).stores;
|
|
1077
|
+
}
|
|
1078
|
+
function createCoreNotificationStore(loadedConfig) {
|
|
1079
|
+
const tableName = loadedConfig.notifications.table;
|
|
1080
|
+
const connectionName = loadedConfig.database.defaultConnection;
|
|
1081
|
+
const store = {
|
|
1082
|
+
async create(record) {
|
|
1083
|
+
await DB.table(tableName, connectionName).insert(serializeNotificationRecordForRow(record));
|
|
1084
|
+
},
|
|
1085
|
+
async list(notifiable) {
|
|
1086
|
+
const rows = await DB.table(tableName, connectionName).where("notifiable_type", notifiable.type).where("notifiable_id", String(notifiable.id)).orderBy("created_at", "desc").get();
|
|
1087
|
+
return Object.freeze(rows.map((row) => normalizeNotificationRecordFromRow(row)));
|
|
1088
|
+
},
|
|
1089
|
+
async unread(notifiable) {
|
|
1090
|
+
const rows = await DB.table(tableName, connectionName).where("notifiable_type", notifiable.type).where("notifiable_id", String(notifiable.id)).whereNull("read_at").orderBy("created_at", "desc").get();
|
|
1091
|
+
return Object.freeze(rows.map((row) => normalizeNotificationRecordFromRow(row)));
|
|
1092
|
+
},
|
|
1093
|
+
async markAsRead(ids) {
|
|
1094
|
+
if (ids.length === 0) {
|
|
1095
|
+
return 0;
|
|
1096
|
+
}
|
|
1097
|
+
const result = await DB.table(tableName, connectionName).whereIn("id", ids).update({
|
|
1098
|
+
read_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1099
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1100
|
+
});
|
|
1101
|
+
return result.affectedRows ?? 0;
|
|
1102
|
+
},
|
|
1103
|
+
async markAsUnread(ids) {
|
|
1104
|
+
if (ids.length === 0) {
|
|
1105
|
+
return 0;
|
|
1106
|
+
}
|
|
1107
|
+
const result = await DB.table(tableName, connectionName).whereIn("id", ids).update({
|
|
1108
|
+
read_at: null,
|
|
1109
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1110
|
+
});
|
|
1111
|
+
return result.affectedRows ?? 0;
|
|
1112
|
+
},
|
|
1113
|
+
async delete(ids) {
|
|
1114
|
+
if (ids.length === 0) {
|
|
1115
|
+
return 0;
|
|
1116
|
+
}
|
|
1117
|
+
const result = await DB.table(tableName, connectionName).whereIn("id", ids).delete();
|
|
1118
|
+
return result.affectedRows ?? 0;
|
|
1119
|
+
}
|
|
1120
|
+
};
|
|
1121
|
+
return Object.freeze(store);
|
|
1122
|
+
}
|
|
1123
|
+
function createAuthNotificationsDeliveryHook(notificationsModule) {
|
|
1124
|
+
return Object.freeze({
|
|
1125
|
+
async sendEmailVerification(input) {
|
|
1126
|
+
const recipientName = typeof input.user?.name === "string" ? input.user.name?.trim() : void 0;
|
|
1127
|
+
const notification = notificationsModule.defineNotification({
|
|
1128
|
+
type: "auth.email-verification",
|
|
1129
|
+
via() {
|
|
1130
|
+
return ["email"];
|
|
1131
|
+
},
|
|
1132
|
+
build: {
|
|
1133
|
+
email() {
|
|
1134
|
+
return {
|
|
1135
|
+
subject: "Verify your email address",
|
|
1136
|
+
...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
|
+
],
|
|
1143
|
+
metadata: {
|
|
1144
|
+
provider: input.provider,
|
|
1145
|
+
tokenId: input.token.id
|
|
1146
|
+
}
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
});
|
|
1151
|
+
await notificationsModule.notifyUsing().channel("email", recipientName ? {
|
|
1152
|
+
email: input.email,
|
|
1153
|
+
name: recipientName
|
|
1154
|
+
} : input.email).notify(notification);
|
|
1155
|
+
},
|
|
1156
|
+
async sendPasswordReset(input) {
|
|
1157
|
+
const notification = notificationsModule.defineNotification({
|
|
1158
|
+
type: "auth.password-reset",
|
|
1159
|
+
via() {
|
|
1160
|
+
return ["email"];
|
|
1161
|
+
},
|
|
1162
|
+
build: {
|
|
1163
|
+
email() {
|
|
1164
|
+
return {
|
|
1165
|
+
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
|
+
],
|
|
1172
|
+
metadata: {
|
|
1173
|
+
provider: input.provider,
|
|
1174
|
+
tokenId: input.token.id
|
|
1175
|
+
}
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
});
|
|
1180
|
+
await notificationsModule.notifyUsing().channel("email", input.email).notify(notification);
|
|
1181
|
+
}
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
function createCoreNotificationBroadcaster(broadcastModule) {
|
|
1185
|
+
const normalizeChannels = (route) => {
|
|
1186
|
+
if (typeof route === "string") {
|
|
1187
|
+
const value = route.trim();
|
|
1188
|
+
if (value) {
|
|
1189
|
+
return Object.freeze([value]);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
if (Array.isArray(route)) {
|
|
1193
|
+
const channels = route.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter(Boolean);
|
|
1194
|
+
if (channels.length > 0) {
|
|
1195
|
+
return Object.freeze(channels);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
if (route && typeof route === "object" && "channels" in route && Array.isArray(route.channels)) {
|
|
1199
|
+
const channels = route.channels.filter((entry) => typeof entry === "string").map((entry) => entry.trim()).filter(Boolean);
|
|
1200
|
+
if (channels.length > 0) {
|
|
1201
|
+
return Object.freeze(channels);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
throw new Error("[@holo-js/core] Broadcast notifications require at least one resolved channel route.");
|
|
1205
|
+
};
|
|
1206
|
+
return Object.freeze({
|
|
1207
|
+
async send(message, context) {
|
|
1208
|
+
const channels = normalizeChannels(context.route);
|
|
1209
|
+
const event = typeof message.event === "string" && message.event.trim() ? message.event.trim() : "notifications.message";
|
|
1210
|
+
await broadcastModule.broadcastRaw({
|
|
1211
|
+
event,
|
|
1212
|
+
channels,
|
|
1213
|
+
payload: Object.freeze({
|
|
1214
|
+
...typeof context.notificationType === "string" && context.notificationType.trim() ? { type: context.notificationType.trim() } : {},
|
|
1215
|
+
data: message.data ?? null
|
|
1216
|
+
})
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
function createCoreBroadcastPublisher(loadedConfig) {
|
|
1222
|
+
const connectionHosts = /* @__PURE__ */ new Set(["holo", "pusher"]);
|
|
1223
|
+
const publish = async (input, context) => {
|
|
1224
|
+
const connection = loadedConfig.connections[input.connection];
|
|
1225
|
+
if (!connection) {
|
|
1226
|
+
throw new Error(`[@holo-js/core] Broadcast connection "${input.connection}" is not configured.`);
|
|
1227
|
+
}
|
|
1228
|
+
if (!("appId" in connection) || !("key" in connection) || !("secret" in connection)) {
|
|
1229
|
+
throw new Error(`[@holo-js/core] Broadcast connection "${input.connection}" cannot be published automatically.`);
|
|
1230
|
+
}
|
|
1231
|
+
if (!connectionHosts.has(connection.driver)) {
|
|
1232
|
+
throw new Error(`[@holo-js/core] Broadcast connection "${input.connection}" cannot be published automatically.`);
|
|
1233
|
+
}
|
|
1234
|
+
const options = connection.options;
|
|
1235
|
+
const protocol = options.scheme === "http" ? "http:" : "https:";
|
|
1236
|
+
const url = new URL(`/apps/${encodeURIComponent(connection.appId)}/events`, `${protocol}//${options.host}`);
|
|
1237
|
+
if (typeof options.port === "number") {
|
|
1238
|
+
url.port = String(options.port);
|
|
1239
|
+
}
|
|
1240
|
+
const body = JSON.stringify({
|
|
1241
|
+
name: input.event,
|
|
1242
|
+
channels: input.channels,
|
|
1243
|
+
data: JSON.stringify(input.payload),
|
|
1244
|
+
/* v8 ignore next -- tests do not pass socketId through the publish binding */
|
|
1245
|
+
...typeof input.socketId === "undefined" ? {} : { socket_id: input.socketId }
|
|
1246
|
+
});
|
|
1247
|
+
const bodyMd5 = createHash("md5").update(body).digest("hex");
|
|
1248
|
+
url.searchParams.set("auth_key", connection.key);
|
|
1249
|
+
url.searchParams.set("auth_timestamp", String(Math.floor(Date.now() / 1e3)));
|
|
1250
|
+
url.searchParams.set("auth_version", "1.0");
|
|
1251
|
+
url.searchParams.set("body_md5", bodyMd5);
|
|
1252
|
+
url.searchParams.set(
|
|
1253
|
+
"auth_signature",
|
|
1254
|
+
createHmac("sha256", connection.secret).update(
|
|
1255
|
+
[
|
|
1256
|
+
"POST",
|
|
1257
|
+
url.pathname,
|
|
1258
|
+
[...url.searchParams.entries()].filter(([key]) => key !== "auth_signature").sort(([left], [right]) => left.localeCompare(right)).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&")
|
|
1259
|
+
].join("\n")
|
|
1260
|
+
).digest("hex")
|
|
1261
|
+
);
|
|
1262
|
+
const controller = new AbortController();
|
|
1263
|
+
const timeout = setTimeout(() => controller.abort(), BROADCAST_PUBLISH_TIMEOUT_MS);
|
|
1264
|
+
let response;
|
|
1265
|
+
try {
|
|
1266
|
+
response = await fetch(url, {
|
|
1267
|
+
method: "POST",
|
|
1268
|
+
headers: {
|
|
1269
|
+
"content-type": "application/json"
|
|
1270
|
+
},
|
|
1271
|
+
body,
|
|
1272
|
+
signal: controller.signal
|
|
1273
|
+
});
|
|
1274
|
+
} catch (error) {
|
|
1275
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
1276
|
+
throw new Error(`[@holo-js/core] Broadcast publish request timed out after ${BROADCAST_PUBLISH_TIMEOUT_MS}ms.`);
|
|
1277
|
+
}
|
|
1278
|
+
throw error;
|
|
1279
|
+
} finally {
|
|
1280
|
+
clearTimeout(timeout);
|
|
1281
|
+
}
|
|
1282
|
+
if (!response.ok) {
|
|
1283
|
+
throw new Error(`[@holo-js/core] Broadcast publish request failed with status ${response.status}.`);
|
|
1284
|
+
}
|
|
1285
|
+
const result = await response.json();
|
|
1286
|
+
return {
|
|
1287
|
+
connection: input.connection,
|
|
1288
|
+
driver: connection.driver,
|
|
1289
|
+
queued: context.queued,
|
|
1290
|
+
publishedChannels: Array.isArray(result.deliveredChannels) ? Object.freeze(result.deliveredChannels.map((value) => String(value))) : Object.freeze([...input.channels])
|
|
1291
|
+
};
|
|
1292
|
+
};
|
|
1293
|
+
Object.defineProperty(publish, CORE_BROADCAST_PUBLISHER_MARKER, {
|
|
1294
|
+
value: true
|
|
1295
|
+
});
|
|
1296
|
+
return publish;
|
|
1297
|
+
}
|
|
1298
|
+
function isCoreBroadcastPublisher(value) {
|
|
1299
|
+
return typeof value === "function" && CORE_BROADCAST_PUBLISHER_MARKER in value;
|
|
1300
|
+
}
|
|
1301
|
+
function createNotificationMailText(message) {
|
|
1302
|
+
const parts = [
|
|
1303
|
+
typeof message.greeting === "string" ? message.greeting.trim() : void 0,
|
|
1304
|
+
...(message.lines ?? []).map((line) => line.trim()).filter(Boolean),
|
|
1305
|
+
message.action ? `${message.action.label}: ${message.action.url}` : void 0
|
|
1306
|
+
].filter((value) => typeof value === "string" && value.length > 0);
|
|
1307
|
+
return parts.length > 0 ? parts.join("\n\n") : void 0;
|
|
1308
|
+
}
|
|
1309
|
+
function createCoreNotificationMailSender(mailModule) {
|
|
1310
|
+
return Object.freeze({
|
|
1311
|
+
async send(message, context) {
|
|
1312
|
+
const route = context.route;
|
|
1313
|
+
if (!route) {
|
|
1314
|
+
throw new Error("[@holo-js/core] Email notifications require a resolved email route before bridging into mail.");
|
|
1315
|
+
}
|
|
1316
|
+
const fallbackText = createNotificationMailText(message);
|
|
1317
|
+
await mailModule.sendMail({
|
|
1318
|
+
to: route,
|
|
1319
|
+
subject: message.subject,
|
|
1320
|
+
...typeof message.html === "string" ? { html: message.html } : {},
|
|
1321
|
+
...typeof (message.text ?? fallbackText) === "string" ? { text: message.text ?? fallbackText } : {},
|
|
1322
|
+
...typeof message.html !== "string" && typeof (message.text ?? fallbackText) === "string" ? { text: message.text ?? fallbackText } : {},
|
|
1323
|
+
...message.metadata ? { metadata: message.metadata } : {}
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
function createAuthMailDeliveryHook(mailModule) {
|
|
1329
|
+
return Object.freeze({
|
|
1330
|
+
async sendEmailVerification(input) {
|
|
1331
|
+
const recipientName = typeof input.user?.name === "string" ? input.user.name?.trim() : void 0;
|
|
1332
|
+
await mailModule.sendMail({
|
|
1333
|
+
to: {
|
|
1334
|
+
email: input.email,
|
|
1335
|
+
...recipientName ? { name: recipientName } : {}
|
|
1336
|
+
},
|
|
1337
|
+
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"),
|
|
1345
|
+
metadata: {
|
|
1346
|
+
provider: input.provider,
|
|
1347
|
+
tokenId: input.token.id
|
|
1348
|
+
}
|
|
1349
|
+
});
|
|
1350
|
+
},
|
|
1351
|
+
async sendPasswordReset(input) {
|
|
1352
|
+
await mailModule.sendMail({
|
|
1353
|
+
to: input.email,
|
|
1354
|
+
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"),
|
|
1361
|
+
metadata: {
|
|
1362
|
+
provider: input.provider,
|
|
1363
|
+
tokenId: input.token.id
|
|
1364
|
+
}
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
async function loadConfiguredSocialProviders(projectRootOrLoadedConfig, maybeLoadedConfig) {
|
|
1370
|
+
const projectRoot = typeof projectRootOrLoadedConfig === "string" ? projectRootOrLoadedConfig : process.cwd();
|
|
1371
|
+
const loadedConfig = typeof projectRootOrLoadedConfig === "string" ? maybeLoadedConfig : projectRootOrLoadedConfig;
|
|
1372
|
+
const socialConfig = loadedConfig?.auth?.social ?? {};
|
|
1373
|
+
const providers = {};
|
|
1374
|
+
for (const providerName of Object.keys(socialConfig)) {
|
|
1375
|
+
const configuredRuntime = socialConfig[providerName]?.runtime?.trim();
|
|
1376
|
+
const packageName = configuredRuntime || `@holo-js/auth-social-${providerName}`;
|
|
1377
|
+
const moduleValue = await portableRuntimeModuleInternals.importOptionalModule(packageName, {
|
|
1378
|
+
projectRoot
|
|
1379
|
+
});
|
|
1380
|
+
if (!moduleValue) {
|
|
1381
|
+
throw new Error(`[@holo-js/core] Social provider "${providerName}" requires ${packageName} to be installed.`);
|
|
1382
|
+
}
|
|
1383
|
+
const runtime = moduleValue.default ?? moduleValue[`${providerName}SocialProvider`] ?? moduleValue.socialProvider;
|
|
1384
|
+
if (!runtime) {
|
|
1385
|
+
throw new Error(`[@holo-js/core] Social provider package "${packageName}" did not export a runtime.`);
|
|
1386
|
+
}
|
|
1387
|
+
providers[providerName] = runtime;
|
|
1388
|
+
}
|
|
1389
|
+
return Object.freeze(providers);
|
|
1390
|
+
}
|
|
1391
|
+
function normalizeDateValue(value) {
|
|
1392
|
+
return value instanceof Date ? value : new Date(String(value));
|
|
1393
|
+
}
|
|
1394
|
+
function normalizeJsonValue(value) {
|
|
1395
|
+
if (typeof value !== "string") {
|
|
1396
|
+
return value;
|
|
1397
|
+
}
|
|
1398
|
+
try {
|
|
1399
|
+
return JSON.parse(value);
|
|
1400
|
+
} catch {
|
|
1401
|
+
return value;
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
function normalizeStoredUserId(value) {
|
|
1405
|
+
return typeof value === "number" ? value : String(value);
|
|
1406
|
+
}
|
|
1407
|
+
function normalizeAccessTokenRecord(row) {
|
|
1408
|
+
const abilities = normalizeJsonValue(row.abilities);
|
|
1409
|
+
return Object.freeze({
|
|
1410
|
+
id: String(row.id),
|
|
1411
|
+
provider: String(row.provider),
|
|
1412
|
+
userId: normalizeStoredUserId(row.user_id),
|
|
1413
|
+
name: String(row.name),
|
|
1414
|
+
abilities: Array.isArray(abilities) ? Object.freeze([...abilities]) : Object.freeze([]),
|
|
1415
|
+
tokenHash: String(row.token_hash),
|
|
1416
|
+
createdAt: normalizeDateValue(row.created_at),
|
|
1417
|
+
lastUsedAt: row.last_used_at ? normalizeDateValue(row.last_used_at) : void 0,
|
|
1418
|
+
expiresAt: row.expires_at ? normalizeDateValue(row.expires_at) : null
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
function serializeAccessTokenRecord(record) {
|
|
1422
|
+
return {
|
|
1423
|
+
id: record.id,
|
|
1424
|
+
provider: record.provider,
|
|
1425
|
+
user_id: String(record.userId),
|
|
1426
|
+
name: record.name,
|
|
1427
|
+
abilities: JSON.stringify(record.abilities),
|
|
1428
|
+
token_hash: record.tokenHash,
|
|
1429
|
+
created_at: record.createdAt.toISOString(),
|
|
1430
|
+
last_used_at: record.lastUsedAt?.toISOString() ?? null,
|
|
1431
|
+
expires_at: record.expiresAt?.toISOString() ?? null,
|
|
1432
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
function normalizeEmailVerificationTokenRecord(row) {
|
|
1436
|
+
return Object.freeze({
|
|
1437
|
+
id: String(row.id),
|
|
1438
|
+
provider: String(row.provider),
|
|
1439
|
+
userId: normalizeStoredUserId(row.user_id),
|
|
1440
|
+
email: String(row.email),
|
|
1441
|
+
tokenHash: String(row.token_hash),
|
|
1442
|
+
createdAt: normalizeDateValue(row.created_at),
|
|
1443
|
+
expiresAt: normalizeDateValue(row.expires_at)
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
function serializeEmailVerificationTokenRecord(record) {
|
|
1447
|
+
return {
|
|
1448
|
+
id: record.id,
|
|
1449
|
+
provider: record.provider,
|
|
1450
|
+
user_id: String(record.userId),
|
|
1451
|
+
email: record.email,
|
|
1452
|
+
token_hash: record.tokenHash,
|
|
1453
|
+
created_at: record.createdAt.toISOString(),
|
|
1454
|
+
expires_at: record.expiresAt.toISOString(),
|
|
1455
|
+
used_at: null,
|
|
1456
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
function normalizePasswordResetTokenRecord(row) {
|
|
1460
|
+
return Object.freeze({
|
|
1461
|
+
id: String(row.id),
|
|
1462
|
+
provider: typeof row.provider === "string" ? row.provider : "users",
|
|
1463
|
+
email: String(row.email),
|
|
1464
|
+
table: typeof row.__holo_table === "string" ? row.__holo_table : void 0,
|
|
1465
|
+
tokenHash: String(row.token_hash),
|
|
1466
|
+
createdAt: normalizeDateValue(row.created_at),
|
|
1467
|
+
expiresAt: normalizeDateValue(row.expires_at)
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
function serializePasswordResetTokenRecord(record) {
|
|
1471
|
+
return {
|
|
1472
|
+
id: record.id,
|
|
1473
|
+
provider: record.provider,
|
|
1474
|
+
email: record.email,
|
|
1475
|
+
token_hash: record.tokenHash,
|
|
1476
|
+
created_at: record.createdAt.toISOString(),
|
|
1477
|
+
expires_at: record.expiresAt.toISOString(),
|
|
1478
|
+
used_at: null,
|
|
1479
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
async function createCoreSocialBindings(projectRootOrLoadedConfig, loadedConfigOrSessionModule, maybeSessionModule) {
|
|
1483
|
+
const projectRoot = typeof projectRootOrLoadedConfig === "string" ? projectRootOrLoadedConfig : process.cwd();
|
|
1484
|
+
const loadedConfig = typeof projectRootOrLoadedConfig === "string" ? loadedConfigOrSessionModule : projectRootOrLoadedConfig;
|
|
1485
|
+
const sessionModule = typeof projectRootOrLoadedConfig === "string" ? maybeSessionModule : loadedConfigOrSessionModule;
|
|
1486
|
+
const providers = await loadConfiguredSocialProviders(projectRoot, loadedConfig);
|
|
1487
|
+
const sessionRuntime = sessionModule.getSessionRuntime();
|
|
1488
|
+
const stateStore = Object.freeze({
|
|
1489
|
+
async create(record) {
|
|
1490
|
+
await sessionRuntime.create({
|
|
1491
|
+
id: `oauth:${record.provider}:${record.state}`,
|
|
1492
|
+
data: {
|
|
1493
|
+
provider: record.provider,
|
|
1494
|
+
state: record.state,
|
|
1495
|
+
codeVerifier: record.codeVerifier,
|
|
1496
|
+
guard: record.guard,
|
|
1497
|
+
createdAt: record.createdAt.toISOString()
|
|
1498
|
+
}
|
|
1499
|
+
});
|
|
1500
|
+
},
|
|
1501
|
+
async read(provider, state) {
|
|
1502
|
+
const record = await sessionRuntime.read(`oauth:${provider}:${state}`);
|
|
1503
|
+
if (!record || typeof record !== "object" || !("data" in record)) {
|
|
1504
|
+
return null;
|
|
1505
|
+
}
|
|
1506
|
+
const data = record.data;
|
|
1507
|
+
if (!data || typeof data.codeVerifier !== "string" || typeof data.guard !== "string") {
|
|
1508
|
+
return null;
|
|
1509
|
+
}
|
|
1510
|
+
return {
|
|
1511
|
+
provider,
|
|
1512
|
+
state,
|
|
1513
|
+
codeVerifier: data.codeVerifier,
|
|
1514
|
+
guard: data.guard,
|
|
1515
|
+
createdAt: normalizeDateValue(data.createdAt ?? /* @__PURE__ */ new Date())
|
|
1516
|
+
};
|
|
1517
|
+
},
|
|
1518
|
+
async delete(provider, state) {
|
|
1519
|
+
await sessionRuntime.invalidate(`oauth:${provider}:${state}`);
|
|
1520
|
+
}
|
|
1521
|
+
});
|
|
1522
|
+
const identityStore = Object.freeze({
|
|
1523
|
+
async findByProviderUserId(provider, providerUserId) {
|
|
1524
|
+
const row = await DB.table("auth_identities").where("provider", provider).where("provider_user_id", providerUserId).first();
|
|
1525
|
+
if (!row) {
|
|
1526
|
+
return null;
|
|
1527
|
+
}
|
|
1528
|
+
return {
|
|
1529
|
+
provider: String(row.provider ?? provider),
|
|
1530
|
+
providerUserId: String(row.provider_user_id ?? providerUserId),
|
|
1531
|
+
guard: String(row.guard ?? loadedConfig.auth.defaults.guard),
|
|
1532
|
+
authProvider: String(
|
|
1533
|
+
row.auth_provider ?? loadedConfig.auth.guards[String(row.guard ?? loadedConfig.auth.defaults.guard)]?.provider ?? loadedConfig.auth.guards[loadedConfig.auth.defaults.guard]?.provider ?? "users"
|
|
1534
|
+
),
|
|
1535
|
+
userId: normalizeStoredUserId(row.user_id),
|
|
1536
|
+
email: typeof row.email === "string" ? row.email : void 0,
|
|
1537
|
+
emailVerified: row.email_verified === true || row.email_verified === 1 || row.email_verified === "1",
|
|
1538
|
+
profile: typeof normalizeJsonValue(row.profile) === "object" && normalizeJsonValue(row.profile) ? normalizeJsonValue(row.profile) : {},
|
|
1539
|
+
tokens: normalizeJsonValue(row.tokens),
|
|
1540
|
+
linkedAt: normalizeDateValue(row.created_at ?? /* @__PURE__ */ new Date()),
|
|
1541
|
+
updatedAt: normalizeDateValue(row.updated_at ?? /* @__PURE__ */ new Date())
|
|
1542
|
+
};
|
|
1543
|
+
},
|
|
1544
|
+
async save(record) {
|
|
1545
|
+
const value = record;
|
|
1546
|
+
const existing = await DB.table("auth_identities").where("provider", value.provider).where("provider_user_id", value.providerUserId).first();
|
|
1547
|
+
const payload = {
|
|
1548
|
+
user_id: String(value.userId),
|
|
1549
|
+
provider: value.provider,
|
|
1550
|
+
provider_user_id: value.providerUserId,
|
|
1551
|
+
guard: value.guard,
|
|
1552
|
+
auth_provider: value.authProvider,
|
|
1553
|
+
email: value.email ?? null,
|
|
1554
|
+
email_verified: value.emailVerified ? 1 : 0,
|
|
1555
|
+
profile: JSON.stringify(value.profile),
|
|
1556
|
+
tokens: JSON.stringify(value.tokens ?? {}),
|
|
1557
|
+
created_at: value.linkedAt.toISOString(),
|
|
1558
|
+
updated_at: value.updatedAt.toISOString()
|
|
1559
|
+
};
|
|
1560
|
+
if (existing && typeof existing.id !== "undefined") {
|
|
1561
|
+
await DB.table("auth_identities").where("id", existing.id).update(payload);
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
await DB.table("auth_identities").insert(payload);
|
|
1565
|
+
}
|
|
1566
|
+
});
|
|
1567
|
+
return Object.freeze({
|
|
1568
|
+
providers,
|
|
1569
|
+
stateStore,
|
|
1570
|
+
identityStore
|
|
1571
|
+
});
|
|
1572
|
+
}
|
|
1573
|
+
function toHostedIdentityProviderValue(namespace, provider) {
|
|
1574
|
+
return `${namespace}:${provider}`;
|
|
1575
|
+
}
|
|
1576
|
+
function fromHostedIdentityProviderValue(namespace, provider) {
|
|
1577
|
+
const prefix = `${namespace}:`;
|
|
1578
|
+
return provider.startsWith(prefix) ? provider.slice(prefix.length) : provider;
|
|
1579
|
+
}
|
|
1580
|
+
function createCoreHostedIdentityStore(namespace) {
|
|
1581
|
+
return Object.freeze({
|
|
1582
|
+
async findByProviderUserId(provider, providerUserId) {
|
|
1583
|
+
const providerValue = toHostedIdentityProviderValue(namespace, provider);
|
|
1584
|
+
const row = await DB.table("auth_identities").where("provider", providerValue).where("provider_user_id", providerUserId).first();
|
|
1585
|
+
if (!row) {
|
|
1586
|
+
return null;
|
|
1587
|
+
}
|
|
1588
|
+
return {
|
|
1589
|
+
provider: fromHostedIdentityProviderValue(namespace, String(row.provider ?? provider)),
|
|
1590
|
+
providerUserId: String(row.provider_user_id ?? providerUserId),
|
|
1591
|
+
guard: String(row.guard ?? "web"),
|
|
1592
|
+
authProvider: String(row.auth_provider ?? "users"),
|
|
1593
|
+
userId: normalizeStoredUserId(row.user_id),
|
|
1594
|
+
email: typeof row.email === "string" ? row.email : void 0,
|
|
1595
|
+
emailVerified: row.email_verified === true || row.email_verified === 1 || row.email_verified === "1",
|
|
1596
|
+
profile: typeof normalizeJsonValue(row.profile) === "object" && normalizeJsonValue(row.profile) ? normalizeJsonValue(row.profile) : {},
|
|
1597
|
+
linkedAt: normalizeDateValue(row.created_at ?? /* @__PURE__ */ new Date()),
|
|
1598
|
+
updatedAt: normalizeDateValue(row.updated_at ?? /* @__PURE__ */ new Date())
|
|
1599
|
+
};
|
|
1600
|
+
},
|
|
1601
|
+
async findByUserId(provider, authProvider, userId) {
|
|
1602
|
+
const providerValue = toHostedIdentityProviderValue(namespace, provider);
|
|
1603
|
+
const row = await DB.table("auth_identities").where("provider", providerValue).where("auth_provider", authProvider).where("user_id", String(userId)).first();
|
|
1604
|
+
if (!row) {
|
|
1605
|
+
return null;
|
|
1606
|
+
}
|
|
1607
|
+
return {
|
|
1608
|
+
provider: fromHostedIdentityProviderValue(namespace, String(row.provider ?? provider)),
|
|
1609
|
+
providerUserId: String(row.provider_user_id),
|
|
1610
|
+
guard: String(row.guard ?? "web"),
|
|
1611
|
+
authProvider: String(row.auth_provider ?? authProvider),
|
|
1612
|
+
userId: normalizeStoredUserId(row.user_id),
|
|
1613
|
+
email: typeof row.email === "string" ? row.email : void 0,
|
|
1614
|
+
emailVerified: row.email_verified === true || row.email_verified === 1 || row.email_verified === "1",
|
|
1615
|
+
profile: typeof normalizeJsonValue(row.profile) === "object" && normalizeJsonValue(row.profile) ? normalizeJsonValue(row.profile) : {},
|
|
1616
|
+
linkedAt: normalizeDateValue(row.created_at ?? /* @__PURE__ */ new Date()),
|
|
1617
|
+
updatedAt: normalizeDateValue(row.updated_at ?? /* @__PURE__ */ new Date())
|
|
1618
|
+
};
|
|
1619
|
+
},
|
|
1620
|
+
async save(record) {
|
|
1621
|
+
const value = record;
|
|
1622
|
+
const providerValue = toHostedIdentityProviderValue(namespace, value.provider);
|
|
1623
|
+
const existing = await DB.table("auth_identities").where("provider", providerValue).where("provider_user_id", value.providerUserId).first();
|
|
1624
|
+
const payload = {
|
|
1625
|
+
user_id: String(value.userId),
|
|
1626
|
+
provider: providerValue,
|
|
1627
|
+
provider_user_id: value.providerUserId,
|
|
1628
|
+
guard: value.guard,
|
|
1629
|
+
auth_provider: value.authProvider,
|
|
1630
|
+
email: value.email ?? null,
|
|
1631
|
+
email_verified: value.emailVerified ? 1 : 0,
|
|
1632
|
+
profile: JSON.stringify(value.profile),
|
|
1633
|
+
created_at: value.linkedAt.toISOString(),
|
|
1634
|
+
updated_at: value.updatedAt.toISOString()
|
|
1635
|
+
};
|
|
1636
|
+
if (existing && typeof existing.id !== "undefined") {
|
|
1637
|
+
await DB.table("auth_identities").where("id", existing.id).update(payload);
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
await DB.table("auth_identities").insert(payload);
|
|
1641
|
+
}
|
|
1642
|
+
});
|
|
1643
|
+
}
|
|
1644
|
+
function createCoreAuthStores(loadedConfig) {
|
|
1645
|
+
return Object.freeze({
|
|
1646
|
+
tokens: Object.freeze({
|
|
1647
|
+
async create(record) {
|
|
1648
|
+
await DB.table("personal_access_tokens").insert(serializeAccessTokenRecord(record));
|
|
1649
|
+
},
|
|
1650
|
+
async findById(id) {
|
|
1651
|
+
const row = await DB.table("personal_access_tokens").find(id);
|
|
1652
|
+
return row ? normalizeAccessTokenRecord(row) : null;
|
|
1653
|
+
},
|
|
1654
|
+
async listByUserId(provider, userId) {
|
|
1655
|
+
const rows = await DB.table("personal_access_tokens").where("provider", provider).where("user_id", String(userId)).get();
|
|
1656
|
+
return Object.freeze(rows.map((row) => normalizeAccessTokenRecord(row)));
|
|
1657
|
+
},
|
|
1658
|
+
async update(record) {
|
|
1659
|
+
const payload = serializeAccessTokenRecord(record);
|
|
1660
|
+
await DB.table("personal_access_tokens").where("id", String(payload.id)).update(payload);
|
|
1661
|
+
},
|
|
1662
|
+
async delete(id) {
|
|
1663
|
+
await DB.table("personal_access_tokens").where("id", id).delete();
|
|
1664
|
+
},
|
|
1665
|
+
async deleteByUserId(provider, userId) {
|
|
1666
|
+
const result = await DB.table("personal_access_tokens").where("provider", provider).where("user_id", String(userId)).delete();
|
|
1667
|
+
return result.affectedRows ?? 0;
|
|
1668
|
+
}
|
|
1669
|
+
}),
|
|
1670
|
+
emailVerificationTokens: Object.freeze({
|
|
1671
|
+
async create(record) {
|
|
1672
|
+
await DB.table("email_verification_tokens").insert(serializeEmailVerificationTokenRecord(record));
|
|
1673
|
+
},
|
|
1674
|
+
async findById(id) {
|
|
1675
|
+
const row = await DB.table("email_verification_tokens").where("id", id).whereNull("used_at").first();
|
|
1676
|
+
return row ? normalizeEmailVerificationTokenRecord(row) : null;
|
|
1677
|
+
},
|
|
1678
|
+
async delete(id) {
|
|
1679
|
+
await DB.table("email_verification_tokens").where("id", id).delete();
|
|
1680
|
+
},
|
|
1681
|
+
async deleteByUserId(provider, userId) {
|
|
1682
|
+
const result = await DB.table("email_verification_tokens").where("provider", provider).where("user_id", String(userId)).delete();
|
|
1683
|
+
return result.affectedRows ?? 0;
|
|
1684
|
+
}
|
|
1685
|
+
}),
|
|
1686
|
+
passwordResetTokens: Object.freeze({
|
|
1687
|
+
async create(record) {
|
|
1688
|
+
const value = record;
|
|
1689
|
+
await DB.table(value.table ?? "password_reset_tokens").insert(serializePasswordResetTokenRecord(value));
|
|
1690
|
+
},
|
|
1691
|
+
async findById(id) {
|
|
1692
|
+
const tables = Array.from(new Set(
|
|
1693
|
+
Object.values(loadedConfig.auth.passwords).map((config) => config.table)
|
|
1694
|
+
));
|
|
1695
|
+
for (const table of tables) {
|
|
1696
|
+
const row = await DB.table(table).where("id", id).whereNull("used_at").first();
|
|
1697
|
+
if (row) {
|
|
1698
|
+
return normalizePasswordResetTokenRecord({
|
|
1699
|
+
...row,
|
|
1700
|
+
__holo_table: table
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
return null;
|
|
1705
|
+
},
|
|
1706
|
+
async findLatestByEmail(provider, email, options) {
|
|
1707
|
+
const table = options?.table ?? "password_reset_tokens";
|
|
1708
|
+
const row = await DB.table(table).where("provider", provider).where("email", email).latest("created_at").first();
|
|
1709
|
+
if (!row) {
|
|
1710
|
+
return null;
|
|
1711
|
+
}
|
|
1712
|
+
return normalizePasswordResetTokenRecord({
|
|
1713
|
+
...row,
|
|
1714
|
+
__holo_table: table
|
|
1715
|
+
});
|
|
1716
|
+
},
|
|
1717
|
+
async delete(id, options) {
|
|
1718
|
+
const table = options?.table ?? "password_reset_tokens";
|
|
1719
|
+
await DB.table(table).where("id", id).delete();
|
|
1720
|
+
},
|
|
1721
|
+
async deleteByEmail(provider, email, options) {
|
|
1722
|
+
const table = options?.table ?? "password_reset_tokens";
|
|
1723
|
+
const result = await DB.table(table).where("provider", provider).where("email", email).delete();
|
|
1724
|
+
return result.affectedRows ?? 0;
|
|
1725
|
+
}
|
|
1726
|
+
})
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1729
|
+
async function resolveAuthProviderRuntime(projectRoot, loadedConfig, modelName) {
|
|
1730
|
+
const modelsRoot = resolve3(projectRoot, loadedConfig.app.paths.models);
|
|
1731
|
+
for (const extension of [".ts", ".mts", ".js", ".mjs", ".cts", ".cjs"]) {
|
|
1732
|
+
const candidate = resolve3(modelsRoot, `${modelName}${extension}`);
|
|
1733
|
+
try {
|
|
1734
|
+
const moduleValue = await importRuntimeModule(projectRoot, candidate);
|
|
1735
|
+
if ("default" in moduleValue) {
|
|
1736
|
+
return moduleValue;
|
|
1737
|
+
}
|
|
1738
|
+
} catch (error) {
|
|
1739
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
1740
|
+
continue;
|
|
1741
|
+
}
|
|
1742
|
+
if (error instanceof Error && /Could not resolve|Cannot find module|ENOENT/.test(error.message)) {
|
|
1743
|
+
const normalizedCandidate = candidate.replaceAll("\\", "/");
|
|
1744
|
+
const normalizedMessage = error.message.replaceAll("\\", "/");
|
|
1745
|
+
const escapedCandidate = normalizedCandidate.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1746
|
+
const missingModulePattern = new RegExp(`(?:Cannot find module|Could not resolve|Failed to load url)\\s+['"]${escapedCandidate}['"]`);
|
|
1747
|
+
const enotentPathMatch = normalizedMessage.match(/ENOENT.*?(?:open|scandir|stat).*?['"]([^'"]+)['"]/);
|
|
1748
|
+
if (missingModulePattern.test(normalizedMessage) || enotentPathMatch?.[1]?.endsWith(normalizedCandidate)) {
|
|
1749
|
+
continue;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
throw error;
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
throw new Error(`[@holo-js/core] Auth provider model "${modelName}" could not be resolved from ${modelsRoot}.`);
|
|
1756
|
+
}
|
|
1757
|
+
async function createCoreAuthProviders(projectRoot, loadedConfig) {
|
|
1758
|
+
const providers = Object.entries(loadedConfig.auth.providers);
|
|
1759
|
+
return Object.freeze(Object.fromEntries(await Promise.all(providers.map(async ([providerName, providerConfig]) => {
|
|
1760
|
+
const resolvedModule = await resolveAuthProviderRuntime(projectRoot, loadedConfig, providerConfig.model);
|
|
1761
|
+
const model = resolvedModule.default;
|
|
1762
|
+
const throwPendingSchema = () => {
|
|
1763
|
+
throw new Error(
|
|
1764
|
+
`[@holo-js/core] Auth provider model "${providerConfig.model}" is pending generated schema output. Run the schema generator before using auth.`
|
|
1765
|
+
);
|
|
1766
|
+
};
|
|
1767
|
+
if (typeof model === "undefined" && resolvedModule.holoModelPendingSchema === true) {
|
|
1768
|
+
const pendingAdapter = {
|
|
1769
|
+
async findById() {
|
|
1770
|
+
throwPendingSchema();
|
|
1771
|
+
},
|
|
1772
|
+
async findByCredentials() {
|
|
1773
|
+
throwPendingSchema();
|
|
1774
|
+
},
|
|
1775
|
+
async create() {
|
|
1776
|
+
throwPendingSchema();
|
|
1777
|
+
},
|
|
1778
|
+
async update() {
|
|
1779
|
+
throwPendingSchema();
|
|
1780
|
+
},
|
|
1781
|
+
matchesUser() {
|
|
1782
|
+
return false;
|
|
1783
|
+
},
|
|
1784
|
+
getId() {
|
|
1785
|
+
throwPendingSchema();
|
|
1786
|
+
},
|
|
1787
|
+
getPasswordHash() {
|
|
1788
|
+
throwPendingSchema();
|
|
1789
|
+
},
|
|
1790
|
+
getEmailVerifiedAt() {
|
|
1791
|
+
throwPendingSchema();
|
|
1792
|
+
},
|
|
1793
|
+
serialize() {
|
|
1794
|
+
throwPendingSchema();
|
|
1795
|
+
}
|
|
1796
|
+
};
|
|
1797
|
+
return [providerName, pendingAdapter];
|
|
1798
|
+
}
|
|
1799
|
+
const sanitizeAuthWriteInput = (input, options = {}) => {
|
|
1800
|
+
const definition = model.definition;
|
|
1801
|
+
const knownColumns = new Set(Object.keys(definition?.table?.columns ?? {}));
|
|
1802
|
+
const fillable = new Set(definition?.fillable ?? []);
|
|
1803
|
+
const guarded = new Set(definition?.guarded ?? []);
|
|
1804
|
+
const hasKnownColumns = knownColumns.size > 0;
|
|
1805
|
+
const enforceFillable = options.enforceFillable !== false;
|
|
1806
|
+
const output = {};
|
|
1807
|
+
for (const [column, value] of Object.entries(input)) {
|
|
1808
|
+
if (hasKnownColumns && !knownColumns.has(column)) {
|
|
1809
|
+
continue;
|
|
1810
|
+
}
|
|
1811
|
+
if (guarded.has("*")) {
|
|
1812
|
+
continue;
|
|
1813
|
+
}
|
|
1814
|
+
const writable = !enforceFillable ? !guarded.has(column) : fillable.has("*") ? !guarded.has(column) : definition?.hasExplicitFillable === true || fillable.size > 0 ? fillable.has(column) && !guarded.has(column) : !guarded.has(column);
|
|
1815
|
+
if (writable) {
|
|
1816
|
+
output[column] = value;
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
return output;
|
|
1820
|
+
};
|
|
1821
|
+
const prepareAuthCreateInput = async (input) => {
|
|
1822
|
+
const sanitizedInput = sanitizeAuthWriteInput(input);
|
|
1823
|
+
if (typeof resolvedModule.prepareAuthCreateInput !== "function") {
|
|
1824
|
+
return sanitizedInput;
|
|
1825
|
+
}
|
|
1826
|
+
return sanitizeAuthWriteInput(await resolvedModule.prepareAuthCreateInput(sanitizedInput), {
|
|
1827
|
+
enforceFillable: false
|
|
1828
|
+
});
|
|
1829
|
+
};
|
|
1830
|
+
const prepareAuthUpdateInput = async (user, input) => {
|
|
1831
|
+
const sanitizedInput = sanitizeAuthWriteInput(input);
|
|
1832
|
+
if (typeof resolvedModule.prepareAuthUpdateInput !== "function") {
|
|
1833
|
+
return sanitizedInput;
|
|
1834
|
+
}
|
|
1835
|
+
return sanitizeAuthWriteInput(await resolvedModule.prepareAuthUpdateInput(user, sanitizedInput), {
|
|
1836
|
+
enforceFillable: false
|
|
1837
|
+
});
|
|
1838
|
+
};
|
|
1839
|
+
const adapter = {
|
|
1840
|
+
async findById(id) {
|
|
1841
|
+
const resolved = await model.find(id);
|
|
1842
|
+
return resolved ? markProviderUser(resolved, providerName) : null;
|
|
1843
|
+
},
|
|
1844
|
+
async findByCredentials(credentials) {
|
|
1845
|
+
const entries = Object.entries(credentials);
|
|
1846
|
+
if (entries.length === 0) {
|
|
1847
|
+
return null;
|
|
1848
|
+
}
|
|
1849
|
+
if (typeof model.query === "function") {
|
|
1850
|
+
let query2 = model.query();
|
|
1851
|
+
for (const [column, value] of entries) {
|
|
1852
|
+
query2 = query2.where(column, value);
|
|
1853
|
+
}
|
|
1854
|
+
const resolved2 = await query2.first();
|
|
1855
|
+
return resolved2 ? markProviderUser(resolved2, providerName) : null;
|
|
1856
|
+
}
|
|
1857
|
+
let query = model.where(entries[0][0], entries[0][1]);
|
|
1858
|
+
for (const [column, value] of entries.slice(1)) {
|
|
1859
|
+
if (typeof query.where !== "function") {
|
|
1860
|
+
break;
|
|
1861
|
+
}
|
|
1862
|
+
query = query.where(column, value);
|
|
1863
|
+
}
|
|
1864
|
+
const resolved = await query.first();
|
|
1865
|
+
return resolved ? markProviderUser(resolved, providerName) : null;
|
|
1866
|
+
},
|
|
1867
|
+
async create(input) {
|
|
1868
|
+
return markProviderUser(await model.create(await prepareAuthCreateInput(input)), providerName);
|
|
1869
|
+
},
|
|
1870
|
+
/* v8 ignore start -- adapter shape mirrors the auth package contract; core tests cover the wired runtime behavior */
|
|
1871
|
+
async update(user, input) {
|
|
1872
|
+
return markProviderUser(
|
|
1873
|
+
await model.update(getEntityAttributes(user).id, await prepareAuthUpdateInput(user, input)),
|
|
1874
|
+
providerName
|
|
1875
|
+
);
|
|
1876
|
+
},
|
|
1877
|
+
matchesUser(user) {
|
|
1878
|
+
if (typeof model === "function" && user instanceof model) {
|
|
1879
|
+
return true;
|
|
1880
|
+
}
|
|
1881
|
+
if (user && typeof user === "object" && user[HOLO_AUTH_PROVIDER_MARKER] === providerName) {
|
|
1882
|
+
return true;
|
|
1883
|
+
}
|
|
1884
|
+
return getEntityAttributes(user)[HOLO_AUTH_PROVIDER_MARKER] === providerName;
|
|
1885
|
+
},
|
|
1886
|
+
getId(user) {
|
|
1887
|
+
return getEntityAttributes(user).id;
|
|
1888
|
+
},
|
|
1889
|
+
getPasswordHash(user) {
|
|
1890
|
+
const value = getEntityAttributes(user).password;
|
|
1891
|
+
return typeof value === "string" ? value : null;
|
|
1892
|
+
},
|
|
1893
|
+
getEmailVerifiedAt(user) {
|
|
1894
|
+
const value = getEntityAttributes(user).email_verified_at;
|
|
1895
|
+
return value instanceof Date || typeof value === "string" ? value : null;
|
|
1896
|
+
},
|
|
1897
|
+
serialize(user) {
|
|
1898
|
+
const serialized = user && typeof user === "object" && typeof user.toJSON === "function" ? user.toJSON() : { ...getEntityAttributes(user) };
|
|
1899
|
+
Object.defineProperty(serialized, HOLO_AUTH_PROVIDER_MARKER, {
|
|
1900
|
+
value: providerName,
|
|
1901
|
+
enumerable: false,
|
|
1902
|
+
configurable: true
|
|
1903
|
+
});
|
|
1904
|
+
return serialized;
|
|
1905
|
+
}
|
|
1906
|
+
/* v8 ignore stop */
|
|
1907
|
+
};
|
|
1908
|
+
return [providerName, adapter];
|
|
1909
|
+
}))));
|
|
1910
|
+
}
|
|
1911
|
+
async function importRuntimeModule(projectRoot, filePath) {
|
|
1912
|
+
return importBundledRuntimeModule(projectRoot, filePath);
|
|
1913
|
+
}
|
|
1914
|
+
async function registerProjectQueueJobs(projectRoot, registry, queueModule) {
|
|
1915
|
+
if (!registry || registry.jobs.length === 0) {
|
|
1916
|
+
return Object.freeze([]);
|
|
1917
|
+
}
|
|
1918
|
+
const registeredJobNames = [];
|
|
1919
|
+
try {
|
|
1920
|
+
for (const entry of registry.jobs) {
|
|
1921
|
+
const existing = queueModule.getRegisteredQueueJob(entry.name);
|
|
1922
|
+
if (existing && !existing.sourcePath) {
|
|
1923
|
+
continue;
|
|
1924
|
+
}
|
|
1925
|
+
const moduleValue = await importRuntimeModule(projectRoot, resolve3(projectRoot, entry.sourcePath));
|
|
1926
|
+
const job = resolveQueueJobExport(queueModule, moduleValue);
|
|
1927
|
+
if (!job) {
|
|
1928
|
+
throw new Error(`Discovered job "${entry.sourcePath}" does not export a Holo job.`);
|
|
1929
|
+
}
|
|
1930
|
+
queueModule.registerQueueJob(queueModule.normalizeQueueJobDefinition(job), {
|
|
1931
|
+
name: entry.name,
|
|
1932
|
+
sourcePath: entry.sourcePath,
|
|
1933
|
+
replaceExisting: !!existing?.sourcePath
|
|
1934
|
+
});
|
|
1935
|
+
registeredJobNames.push(entry.name);
|
|
1936
|
+
}
|
|
1937
|
+
} catch (error) {
|
|
1938
|
+
unregisterProjectQueueJobs(queueModule, registeredJobNames);
|
|
1939
|
+
throw error;
|
|
1940
|
+
}
|
|
1941
|
+
return Object.freeze(registeredJobNames);
|
|
1942
|
+
}
|
|
1943
|
+
function unregisterProjectQueueJobs(queueModule, jobNames) {
|
|
1944
|
+
if (!queueModule) {
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1947
|
+
for (const jobName of jobNames) {
|
|
1948
|
+
queueModule.unregisterQueueJob(jobName);
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
function withCanonicalAuthorizationDefinitionName(definition, name) {
|
|
1952
|
+
if (definition.name === name) {
|
|
1953
|
+
return definition;
|
|
1954
|
+
}
|
|
1955
|
+
return {
|
|
1956
|
+
...definition,
|
|
1957
|
+
name
|
|
1958
|
+
};
|
|
1959
|
+
}
|
|
1960
|
+
function withCanonicalAuthorizationAbilityName(definition, name) {
|
|
1961
|
+
if (definition.name === name) {
|
|
1962
|
+
return definition;
|
|
1963
|
+
}
|
|
1964
|
+
return {
|
|
1965
|
+
...definition,
|
|
1966
|
+
name
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
async function registerProjectAuthorizationDefinitions(projectRoot, registry, authorizationModule) {
|
|
1970
|
+
if (!registry || !registry.authorizationPolicies.length && !registry.authorizationAbilities.length) {
|
|
1971
|
+
return Object.freeze({
|
|
1972
|
+
policyNames: Object.freeze([]),
|
|
1973
|
+
abilityNames: Object.freeze([])
|
|
1974
|
+
});
|
|
1975
|
+
}
|
|
1976
|
+
if (!authorizationModule) {
|
|
1977
|
+
throw new Error("[@holo-js/core] Authorization support requires @holo-js/authorization to be installed.");
|
|
1978
|
+
}
|
|
1979
|
+
const registeredPolicyNames = [];
|
|
1980
|
+
const registeredAbilityNames = [];
|
|
1981
|
+
const previousPolicies = /* @__PURE__ */ new Map();
|
|
1982
|
+
const previousAbilities = /* @__PURE__ */ new Map();
|
|
1983
|
+
try {
|
|
1984
|
+
for (const entry of registry.authorizationPolicies) {
|
|
1985
|
+
const existing = authorizationModule.authorizationInternals.getAuthorizationRuntimeState().policiesByName.get(entry.name);
|
|
1986
|
+
if (existing) {
|
|
1987
|
+
previousPolicies.set(entry.name, existing);
|
|
1988
|
+
authorizationModule.authorizationInternals.unregisterPolicyDefinition(entry.name);
|
|
1989
|
+
}
|
|
1990
|
+
const moduleValue = await importRuntimeModule(projectRoot, resolve3(projectRoot, entry.sourcePath));
|
|
1991
|
+
const policy = resolveAuthorizationDefinitionExport(
|
|
1992
|
+
moduleValue,
|
|
1993
|
+
entry.exportName,
|
|
1994
|
+
(value) => authorizationModule.isAuthorizationPolicyDefinition(value)
|
|
1995
|
+
);
|
|
1996
|
+
if (!policy) {
|
|
1997
|
+
throw new Error(`Discovered policy "${entry.sourcePath}" does not export a Holo policy.`);
|
|
1998
|
+
}
|
|
1999
|
+
const canonicalPolicy = withCanonicalAuthorizationDefinitionName(
|
|
2000
|
+
policy,
|
|
2001
|
+
entry.name
|
|
2002
|
+
);
|
|
2003
|
+
const resolvedPolicyName = policy.name;
|
|
2004
|
+
if (resolvedPolicyName !== entry.name) {
|
|
2005
|
+
authorizationModule.authorizationInternals.unregisterPolicyDefinition(resolvedPolicyName);
|
|
2006
|
+
}
|
|
2007
|
+
if (typeof authorizationModule.authorizationInternals.registerPolicyDefinition === "function" && !authorizationModule.authorizationInternals.getAuthorizationRuntimeState().policiesByName.has(entry.name)) {
|
|
2008
|
+
authorizationModule.authorizationInternals.registerPolicyDefinition(canonicalPolicy);
|
|
2009
|
+
}
|
|
2010
|
+
registeredPolicyNames.push(entry.name);
|
|
2011
|
+
}
|
|
2012
|
+
for (const entry of registry.authorizationAbilities) {
|
|
2013
|
+
const existing = authorizationModule.authorizationInternals.getAuthorizationRuntimeState().abilitiesByName.get(entry.name);
|
|
2014
|
+
if (existing) {
|
|
2015
|
+
previousAbilities.set(entry.name, existing);
|
|
2016
|
+
authorizationModule.authorizationInternals.unregisterAbilityDefinition(entry.name);
|
|
2017
|
+
}
|
|
2018
|
+
const moduleValue = await importRuntimeModule(projectRoot, resolve3(projectRoot, entry.sourcePath));
|
|
2019
|
+
const ability = resolveAuthorizationDefinitionExport(
|
|
2020
|
+
moduleValue,
|
|
2021
|
+
entry.exportName,
|
|
2022
|
+
(value) => authorizationModule.isAuthorizationAbilityDefinition(value)
|
|
2023
|
+
);
|
|
2024
|
+
if (!ability) {
|
|
2025
|
+
throw new Error(`Discovered ability "${entry.sourcePath}" does not export a Holo ability.`);
|
|
2026
|
+
}
|
|
2027
|
+
const canonicalAbility = withCanonicalAuthorizationAbilityName(
|
|
2028
|
+
ability,
|
|
2029
|
+
entry.name
|
|
2030
|
+
);
|
|
2031
|
+
const resolvedAbilityName = ability.name;
|
|
2032
|
+
if (resolvedAbilityName !== entry.name) {
|
|
2033
|
+
authorizationModule.authorizationInternals.unregisterAbilityDefinition(resolvedAbilityName);
|
|
2034
|
+
}
|
|
2035
|
+
if (typeof authorizationModule.authorizationInternals.registerAbilityDefinition === "function" && !authorizationModule.authorizationInternals.getAuthorizationRuntimeState().abilitiesByName.has(entry.name)) {
|
|
2036
|
+
authorizationModule.authorizationInternals.registerAbilityDefinition(canonicalAbility);
|
|
2037
|
+
}
|
|
2038
|
+
registeredAbilityNames.push(entry.name);
|
|
2039
|
+
}
|
|
2040
|
+
} catch (error) {
|
|
2041
|
+
unregisterProjectAuthorizationDefinitions(authorizationModule, registeredPolicyNames, registeredAbilityNames);
|
|
2042
|
+
if (typeof authorizationModule.authorizationInternals.registerPolicyDefinition === "function") {
|
|
2043
|
+
for (const definition of previousPolicies.values()) {
|
|
2044
|
+
authorizationModule.authorizationInternals.registerPolicyDefinition(definition);
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
if (typeof authorizationModule.authorizationInternals.registerAbilityDefinition === "function") {
|
|
2048
|
+
for (const definition of previousAbilities.values()) {
|
|
2049
|
+
authorizationModule.authorizationInternals.registerAbilityDefinition(definition);
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
throw error;
|
|
2053
|
+
}
|
|
2054
|
+
return Object.freeze({
|
|
2055
|
+
policyNames: Object.freeze(registeredPolicyNames),
|
|
2056
|
+
abilityNames: Object.freeze(registeredAbilityNames)
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
2059
|
+
function unregisterProjectAuthorizationDefinitions(authorizationModule, policyNames, abilityNames) {
|
|
2060
|
+
if (!authorizationModule) {
|
|
2061
|
+
return;
|
|
2062
|
+
}
|
|
2063
|
+
for (const policyName of policyNames) {
|
|
2064
|
+
authorizationModule.authorizationInternals.unregisterPolicyDefinition(policyName);
|
|
2065
|
+
}
|
|
2066
|
+
for (const abilityName of abilityNames) {
|
|
2067
|
+
authorizationModule.authorizationInternals.unregisterAbilityDefinition(abilityName);
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
async function registerProjectEventsAndListeners(projectRoot, registry, eventsModule, queueModule) {
|
|
2071
|
+
if (!registry || registry.events.length === 0 && registry.listeners.length === 0) {
|
|
2072
|
+
return Object.freeze({
|
|
2073
|
+
eventNames: Object.freeze([]),
|
|
2074
|
+
listenerIds: Object.freeze([])
|
|
2075
|
+
});
|
|
2076
|
+
}
|
|
2077
|
+
const registeredEventNames = [];
|
|
2078
|
+
const registeredListenerIds = [];
|
|
2079
|
+
let requiresQueuedListeners = false;
|
|
2080
|
+
try {
|
|
2081
|
+
for (const entry of registry.events) {
|
|
2082
|
+
const existing = eventsModule.getRegisteredEvent(entry.name);
|
|
2083
|
+
if (existing && !existing.sourcePath) {
|
|
2084
|
+
continue;
|
|
2085
|
+
}
|
|
2086
|
+
const moduleValue = await importRuntimeModule(projectRoot, resolve3(projectRoot, entry.sourcePath));
|
|
2087
|
+
const event = resolveEventExport(moduleValue);
|
|
2088
|
+
if (!event || !eventsModule.isEventDefinition(event)) {
|
|
2089
|
+
throw new Error(`Discovered event "${entry.sourcePath}" does not export a Holo event.`);
|
|
2090
|
+
}
|
|
2091
|
+
eventsModule.registerEvent(event, {
|
|
2092
|
+
name: entry.name,
|
|
2093
|
+
sourcePath: entry.sourcePath,
|
|
2094
|
+
replaceExisting: !!existing?.sourcePath
|
|
2095
|
+
});
|
|
2096
|
+
registeredEventNames.push(entry.name);
|
|
2097
|
+
}
|
|
2098
|
+
for (const entry of registry.listeners) {
|
|
2099
|
+
const existing = eventsModule.getRegisteredListener(entry.id);
|
|
2100
|
+
if (existing && !existing.sourcePath) {
|
|
2101
|
+
continue;
|
|
2102
|
+
}
|
|
2103
|
+
const moduleValue = await importRuntimeModule(projectRoot, resolve3(projectRoot, entry.sourcePath));
|
|
2104
|
+
const listener = resolveListenerExport(eventsModule, moduleValue);
|
|
2105
|
+
if (!listener) {
|
|
2106
|
+
throw new Error(`Discovered listener "${entry.sourcePath}" does not export a Holo listener.`);
|
|
2107
|
+
}
|
|
2108
|
+
const normalizedListener = eventsModule.normalizeListenerDefinition(listener);
|
|
2109
|
+
if (normalizedListener.queue === true) {
|
|
2110
|
+
requiresQueuedListeners = true;
|
|
2111
|
+
if (!queueModule) {
|
|
2112
|
+
throw new Error("[@holo-js/core] Queued listeners require @holo-js/queue to be installed.");
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
eventsModule.registerListener({
|
|
2116
|
+
...normalizedListener,
|
|
2117
|
+
listensTo: entry.eventNames
|
|
2118
|
+
}, {
|
|
2119
|
+
id: entry.id,
|
|
2120
|
+
sourcePath: entry.sourcePath,
|
|
2121
|
+
replaceExisting: !!existing?.sourcePath
|
|
2122
|
+
});
|
|
2123
|
+
registeredListenerIds.push(entry.id);
|
|
2124
|
+
}
|
|
2125
|
+
if (requiresQueuedListeners) {
|
|
2126
|
+
await eventsModule.ensureEventsQueueJobRegisteredAsync?.();
|
|
2127
|
+
}
|
|
2128
|
+
} catch (error) {
|
|
2129
|
+
unregisterProjectEventsAndListeners(eventsModule, registeredEventNames, registeredListenerIds);
|
|
2130
|
+
throw error;
|
|
2131
|
+
}
|
|
2132
|
+
return Object.freeze({
|
|
2133
|
+
eventNames: Object.freeze(registeredEventNames),
|
|
2134
|
+
listenerIds: Object.freeze(registeredListenerIds)
|
|
2135
|
+
});
|
|
2136
|
+
}
|
|
2137
|
+
function unregisterProjectEventsAndListeners(eventsModule, eventNames, listenerIds) {
|
|
2138
|
+
if (!eventsModule) {
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
2141
|
+
for (const listenerId of listenerIds) {
|
|
2142
|
+
eventsModule.unregisterListener(listenerId);
|
|
2143
|
+
}
|
|
2144
|
+
for (const eventName of eventNames) {
|
|
2145
|
+
eventsModule.unregisterEvent(eventName);
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
async function reconfigureOptionalHoloSubsystems(projectRoot, loadedConfig, options = {}) {
|
|
2149
|
+
const cacheConfigured = hasLoadedConfigFile(loadedConfig, "cache");
|
|
2150
|
+
const cacheModule = await loadCacheModule(cacheConfigured, projectRoot);
|
|
2151
|
+
if (cacheModule) {
|
|
2152
|
+
cacheModule.configureCacheRuntime({
|
|
2153
|
+
config: loadedConfig.cache,
|
|
2154
|
+
databaseConfig: loadedConfig.database,
|
|
2155
|
+
redisConfig: loadedConfig.redis
|
|
2156
|
+
});
|
|
2157
|
+
}
|
|
2158
|
+
const queueConfigured = hasLoadedConfigFile(loadedConfig, "queue");
|
|
2159
|
+
const queueModule = await loadQueueModule(queueConfigured);
|
|
2160
|
+
if (queueModule) {
|
|
2161
|
+
const queueUsesExplicitDatabaseFeatures = queueConfigured && (queueConfigUsesDatabaseDriver(loadedConfig) || queueConfigUsesDatabaseBackedFailedStore(loadedConfig));
|
|
2162
|
+
const queueUsesImplicitDefaultFailedStore = !queueConfigured && queueConfigUsesDatabaseBackedFailedStore(loadedConfig);
|
|
2163
|
+
const queueDbModule = queueUsesExplicitDatabaseFeatures || queueUsesImplicitDefaultFailedStore ? await loadQueueDbModule() : void 0;
|
|
2164
|
+
if (queueUsesExplicitDatabaseFeatures && !queueDbModule) {
|
|
2165
|
+
throw new Error("[@holo-js/core] Database-backed queue features require @holo-js/queue-db to be installed.");
|
|
2166
|
+
}
|
|
2167
|
+
queueModule.configureQueueRuntime({
|
|
2168
|
+
config: loadedConfig.queue,
|
|
2169
|
+
redisConfig: loadedConfig.redis,
|
|
2170
|
+
...queueDbModule?.createQueueDbRuntimeOptions() ?? {}
|
|
2171
|
+
});
|
|
2172
|
+
}
|
|
2173
|
+
const storageConfigured = hasLoadedConfigFile(loadedConfig, "storage");
|
|
2174
|
+
const storageInstalled = !!await portableRuntimeModuleInternals.importOptionalModule("@holo-js/storage");
|
|
2175
|
+
if (!storageInstalled && storageConfigured) {
|
|
2176
|
+
throw new Error("[@holo-js/core] Storage support requires @holo-js/storage to be installed.");
|
|
2177
|
+
}
|
|
2178
|
+
if (storageInstalled) {
|
|
2179
|
+
await configurePlainNodeStorageRuntime(projectRoot, loadedConfig);
|
|
2180
|
+
}
|
|
2181
|
+
const mailConfigured = hasLoadedConfigFile(loadedConfig, "mail");
|
|
2182
|
+
const mailModule = mailConfigured ? await loadMailModule(true) : void 0;
|
|
2183
|
+
if (mailModule) {
|
|
2184
|
+
const existingMailBindings = mailModule.getMailRuntimeBindings();
|
|
2185
|
+
mailModule.configureMailRuntime({
|
|
2186
|
+
...existingMailBindings,
|
|
2187
|
+
config: loadedConfig.mail,
|
|
2188
|
+
...options.renderView ?? getRuntimeState().renderView ? { renderView: options.renderView ?? getRuntimeState().renderView } : {}
|
|
2189
|
+
});
|
|
2190
|
+
}
|
|
2191
|
+
const broadcastConfigured = hasLoadedConfigFile(loadedConfig, "broadcast");
|
|
2192
|
+
const broadcastModule = broadcastConfigured ? await loadBroadcastModule(true, projectRoot) : void 0;
|
|
2193
|
+
if (broadcastModule) {
|
|
2194
|
+
const existingBroadcastBindings = broadcastModule.getBroadcastRuntimeBindings();
|
|
2195
|
+
broadcastModule.configureBroadcastRuntime({
|
|
2196
|
+
...existingBroadcastBindings,
|
|
2197
|
+
config: loadedConfig.broadcast,
|
|
2198
|
+
...!existingBroadcastBindings.publish || isCoreBroadcastPublisher(existingBroadcastBindings.publish) ? {
|
|
2199
|
+
publish: createCoreBroadcastPublisher(loadedConfig.broadcast)
|
|
2200
|
+
} : {}
|
|
2201
|
+
});
|
|
2202
|
+
}
|
|
2203
|
+
const notificationsConfigured = hasLoadedConfigFile(loadedConfig, "notifications");
|
|
2204
|
+
const notificationsModule = notificationsConfigured ? await loadNotificationsModule(true) : void 0;
|
|
2205
|
+
if (notificationsModule) {
|
|
2206
|
+
const existingNotificationsBindings = notificationsModule.getNotificationsRuntimeBindings();
|
|
2207
|
+
notificationsModule.configureNotificationsRuntime({
|
|
2208
|
+
...existingNotificationsBindings,
|
|
2209
|
+
config: loadedConfig.notifications,
|
|
2210
|
+
store: existingNotificationsBindings.store ?? createCoreNotificationStore(loadedConfig),
|
|
2211
|
+
...!existingNotificationsBindings.mailer && mailModule ? { mailer: createCoreNotificationMailSender(mailModule) } : {},
|
|
2212
|
+
...!existingNotificationsBindings.broadcaster && broadcastModule ? { broadcaster: createCoreNotificationBroadcaster(broadcastModule) } : {}
|
|
2213
|
+
});
|
|
2214
|
+
}
|
|
2215
|
+
const notificationsRuntimeBindings = notificationsModule?.getNotificationsRuntimeBindings();
|
|
2216
|
+
const sessionConfigured = hasLoadedConfigFile(loadedConfig, "session") || hasLoadedConfigFile(loadedConfig, "auth");
|
|
2217
|
+
const authConfigured = hasLoadedConfigFile(loadedConfig, "auth");
|
|
2218
|
+
const securityConfigured = hasLoadedConfigFile(loadedConfig, "security");
|
|
2219
|
+
const securityModule = securityConfigured ? await loadSecurityModule(true) : void 0;
|
|
2220
|
+
const existingManagedSecurityRedisAdapter = getRuntimeState().securityRedisAdapter;
|
|
2221
|
+
if (securityModule) {
|
|
2222
|
+
const existingSecurityBindings = securityModule.getSecurityRuntimeBindings();
|
|
2223
|
+
const shouldReuseExistingSecurityStore = !!existingSecurityBindings?.rateLimitStore && !existingManagedSecurityRedisAdapter && getRuntimeState().securityRateLimitStoreManaged !== true;
|
|
2224
|
+
const shouldCloseExistingManagedSecurityStore = !shouldReuseExistingSecurityStore && !!existingSecurityBindings?.rateLimitStore && (!!existingManagedSecurityRedisAdapter || getRuntimeState().securityRateLimitStoreManaged === true);
|
|
2225
|
+
let nextManagedSecurityRedisAdapter;
|
|
2226
|
+
let rateLimitStore;
|
|
2227
|
+
let configuredSecurityRuntime = false;
|
|
2228
|
+
try {
|
|
2229
|
+
if (!shouldReuseExistingSecurityStore && loadedConfig.security.rateLimit.driver === "redis") {
|
|
2230
|
+
const securityRedisAdapterModule = await loadSecurityRedisAdapterModule(true);
|
|
2231
|
+
nextManagedSecurityRedisAdapter = securityRedisAdapterModule.createSecurityRedisAdapter(
|
|
2232
|
+
loadedConfig.security.rateLimit.redis
|
|
2233
|
+
);
|
|
2234
|
+
}
|
|
2235
|
+
rateLimitStore = shouldReuseExistingSecurityStore ? existingSecurityBindings.rateLimitStore : securityModule.createRateLimitStoreFromConfig(loadedConfig.security, {
|
|
2236
|
+
projectRoot,
|
|
2237
|
+
...nextManagedSecurityRedisAdapter ? { redisAdapter: nextManagedSecurityRedisAdapter } : {}
|
|
2238
|
+
});
|
|
2239
|
+
if (shouldCloseExistingManagedSecurityStore && existingSecurityBindings?.rateLimitStore && existingSecurityBindings.rateLimitStore !== rateLimitStore) {
|
|
2240
|
+
await existingSecurityBindings.rateLimitStore.close?.();
|
|
2241
|
+
}
|
|
2242
|
+
if (existingManagedSecurityRedisAdapter && existingManagedSecurityRedisAdapter !== nextManagedSecurityRedisAdapter) {
|
|
2243
|
+
await existingManagedSecurityRedisAdapter.close?.();
|
|
2244
|
+
}
|
|
2245
|
+
getRuntimeState().securityRedisAdapter = nextManagedSecurityRedisAdapter;
|
|
2246
|
+
getRuntimeState().securityRateLimitStoreManaged = !shouldReuseExistingSecurityStore;
|
|
2247
|
+
securityModule.configureSecurityRuntime({
|
|
2248
|
+
config: loadedConfig.security,
|
|
2249
|
+
rateLimitStore,
|
|
2250
|
+
csrfSigningKey: loadedConfig.app.key,
|
|
2251
|
+
defaultKeyResolver: async () => {
|
|
2252
|
+
const authModule2 = await loadAuthModule();
|
|
2253
|
+
if (!authModule2) {
|
|
2254
|
+
return void 0;
|
|
2255
|
+
}
|
|
2256
|
+
try {
|
|
2257
|
+
const authId = await authModule2.getAuthRuntime().id();
|
|
2258
|
+
if (authId !== null && typeof authId !== "undefined") {
|
|
2259
|
+
return `user:${String(authId)}`;
|
|
2260
|
+
}
|
|
2261
|
+
} catch {
|
|
2262
|
+
return void 0;
|
|
2263
|
+
}
|
|
2264
|
+
return void 0;
|
|
2265
|
+
}
|
|
2266
|
+
});
|
|
2267
|
+
configuredSecurityRuntime = true;
|
|
2268
|
+
} catch (error) {
|
|
2269
|
+
if (!configuredSecurityRuntime && rateLimitStore && rateLimitStore !== existingSecurityBindings?.rateLimitStore) {
|
|
2270
|
+
await rateLimitStore.close?.();
|
|
2271
|
+
}
|
|
2272
|
+
if (nextManagedSecurityRedisAdapter && nextManagedSecurityRedisAdapter !== existingManagedSecurityRedisAdapter) {
|
|
2273
|
+
await nextManagedSecurityRedisAdapter.close?.();
|
|
2274
|
+
}
|
|
2275
|
+
throw error;
|
|
2276
|
+
}
|
|
2277
|
+
} else if (existingManagedSecurityRedisAdapter || getRuntimeState().securityRateLimitStoreManaged === true) {
|
|
2278
|
+
const existingSecurityModule = await loadSecurityModule();
|
|
2279
|
+
const existingSecurityBindings = existingSecurityModule?.getSecurityRuntimeBindings();
|
|
2280
|
+
if (getRuntimeState().securityRateLimitStoreManaged === true) {
|
|
2281
|
+
await existingSecurityBindings?.rateLimitStore?.close?.();
|
|
2282
|
+
}
|
|
2283
|
+
await existingManagedSecurityRedisAdapter?.close?.();
|
|
2284
|
+
getRuntimeState().securityRedisAdapter = void 0;
|
|
2285
|
+
getRuntimeState().securityRateLimitStoreManaged = void 0;
|
|
2286
|
+
existingSecurityModule?.resetSecurityRuntime();
|
|
2287
|
+
} else {
|
|
2288
|
+
getRuntimeState().securityRateLimitStoreManaged = void 0;
|
|
2289
|
+
}
|
|
2290
|
+
const sessionModule = sessionConfigured || authConfigured ? await loadSessionModule(true) : void 0;
|
|
2291
|
+
const existingManagedSessionRedisAdapters = getRuntimeState().sessionRedisAdapters;
|
|
2292
|
+
if (authConfigured && !sessionModule) {
|
|
2293
|
+
throw new Error("[@holo-js/core] Auth support requires @holo-js/session to be installed.");
|
|
2294
|
+
}
|
|
2295
|
+
const authModule = await loadAuthModule(authConfigured);
|
|
2296
|
+
const authorizationModule = await loadAuthorizationModule();
|
|
2297
|
+
let authContext;
|
|
2298
|
+
const workosModule = authConfigUsesWorkosProviders(loadedConfig) ? await loadWorkosModule(true) : void 0;
|
|
2299
|
+
const clerkModule = authConfigUsesClerkProviders(loadedConfig) ? await loadClerkModule(true) : void 0;
|
|
2300
|
+
if (sessionModule) {
|
|
2301
|
+
let managedSessionStores;
|
|
2302
|
+
try {
|
|
2303
|
+
managedSessionStores = await createCoreManagedSessionStores(projectRoot, loadedConfig, sessionModule);
|
|
2304
|
+
sessionModule.configureSessionRuntime({
|
|
2305
|
+
config: loadedConfig.session,
|
|
2306
|
+
stores: managedSessionStores.stores
|
|
2307
|
+
});
|
|
2308
|
+
getRuntimeState().sessionRedisAdapters = managedSessionStores.redisAdapters.length > 0 ? managedSessionStores.redisAdapters : void 0;
|
|
2309
|
+
if (existingManagedSessionRedisAdapters) {
|
|
2310
|
+
await Promise.all(existingManagedSessionRedisAdapters.map((adapter) => adapter.close?.()));
|
|
2311
|
+
}
|
|
2312
|
+
} catch (error) {
|
|
2313
|
+
if (managedSessionStores) {
|
|
2314
|
+
await Promise.all(managedSessionStores.redisAdapters.map((adapter) => adapter.close?.()));
|
|
2315
|
+
}
|
|
2316
|
+
throw error;
|
|
2317
|
+
}
|
|
2318
|
+
} else if (existingManagedSessionRedisAdapters) {
|
|
2319
|
+
await Promise.all(existingManagedSessionRedisAdapters.map((adapter) => adapter.close?.()));
|
|
2320
|
+
getRuntimeState().sessionRedisAdapters = void 0;
|
|
2321
|
+
}
|
|
2322
|
+
if (authConfigured) {
|
|
2323
|
+
if (!authModule) {
|
|
2324
|
+
throw new Error("[@holo-js/core] Auth support requires @holo-js/auth to be installed.");
|
|
2325
|
+
}
|
|
2326
|
+
if (!sessionModule) {
|
|
2327
|
+
throw new Error("[@holo-js/core] Auth support requires @holo-js/session to be installed.");
|
|
2328
|
+
}
|
|
2329
|
+
const socialModule = authConfigUsesSocialProviders(loadedConfig) ? await loadSocialModule(true) : void 0;
|
|
2330
|
+
const authStores = createCoreAuthStores(loadedConfig);
|
|
2331
|
+
authContext = authModule.createAsyncAuthContext();
|
|
2332
|
+
authModule.configureAuthRuntime({
|
|
2333
|
+
config: loadedConfig.auth,
|
|
2334
|
+
session: sessionModule.getSessionRuntime(),
|
|
2335
|
+
providers: await createCoreAuthProviders(projectRoot, loadedConfig),
|
|
2336
|
+
tokens: authStores.tokens,
|
|
2337
|
+
emailVerificationTokens: authStores.emailVerificationTokens,
|
|
2338
|
+
passwordResetTokens: authStores.passwordResetTokens,
|
|
2339
|
+
...notificationsModule && (mailModule || notificationsRuntimeBindings?.mailer) ? { delivery: createAuthNotificationsDeliveryHook(notificationsModule) } : mailModule ? { delivery: createAuthMailDeliveryHook(mailModule) } : {},
|
|
2340
|
+
context: authContext
|
|
2341
|
+
});
|
|
2342
|
+
const boundAuthRuntime = bindAuthRuntimeToContext(authModule.getAuthRuntime(), authContext);
|
|
2343
|
+
if (authorizationModule) {
|
|
2344
|
+
authorizationModule.authorizationInternals.configureAuthorizationAuthIntegration({
|
|
2345
|
+
hasGuard(guardName) {
|
|
2346
|
+
return guardName in loadedConfig.auth.guards;
|
|
2347
|
+
},
|
|
2348
|
+
resolveDefaultActor: async () => boundAuthRuntime.user(),
|
|
2349
|
+
resolveGuardActor: async (guardName) => boundAuthRuntime.guard(guardName).user()
|
|
2350
|
+
});
|
|
2351
|
+
}
|
|
2352
|
+
if (socialModule) {
|
|
2353
|
+
socialModule.configureSocialAuthRuntime({
|
|
2354
|
+
...await createCoreSocialBindings(projectRoot, loadedConfig, sessionModule),
|
|
2355
|
+
encryptionKey: loadedConfig.auth.socialEncryptionKey
|
|
2356
|
+
});
|
|
2357
|
+
}
|
|
2358
|
+
if (workosModule) {
|
|
2359
|
+
workosModule.configureWorkosAuthRuntime({
|
|
2360
|
+
identityStore: createCoreHostedIdentityStore("workos")
|
|
2361
|
+
});
|
|
2362
|
+
}
|
|
2363
|
+
if (clerkModule) {
|
|
2364
|
+
clerkModule.configureClerkAuthRuntime({
|
|
2365
|
+
identityStore: createCoreHostedIdentityStore("clerk")
|
|
2366
|
+
});
|
|
2367
|
+
}
|
|
2368
|
+
} else if (authorizationModule) {
|
|
2369
|
+
authorizationModule.authorizationInternals.resetAuthorizationAuthIntegration();
|
|
2370
|
+
}
|
|
2371
|
+
return Object.freeze({
|
|
2372
|
+
/* v8 ignore next -- only toggles shape when queue support is absent */
|
|
2373
|
+
...queueModule ? { queueModule } : {},
|
|
2374
|
+
...sessionModule ? { session: sessionModule.getSessionRuntime() } : {},
|
|
2375
|
+
...authModule && authConfigured ? { auth: authModule.getAuthRuntime() } : {},
|
|
2376
|
+
...authModule && authConfigured ? { authContext } : {}
|
|
2377
|
+
});
|
|
2378
|
+
}
|
|
2379
|
+
async function resetOptionalHoloSubsystems() {
|
|
2380
|
+
const projectRoot = getRuntimeState().current?.projectRoot ?? getRuntimeState().pendingProjectRoot;
|
|
2381
|
+
await resetOptionalStorageRuntime();
|
|
2382
|
+
const cacheModule = await loadCacheModule(false, projectRoot);
|
|
2383
|
+
if (cacheModule) {
|
|
2384
|
+
cacheModule.resetCacheRuntime();
|
|
2385
|
+
} else {
|
|
2386
|
+
resetCacheRuntimeGlobalsFallback();
|
|
2387
|
+
}
|
|
2388
|
+
const queueModule = await loadQueueModule();
|
|
2389
|
+
await queueModule?.shutdownQueueRuntime();
|
|
2390
|
+
const mailModule = await loadMailModule();
|
|
2391
|
+
mailModule?.resetMailRuntime();
|
|
2392
|
+
const notificationsModule = await loadNotificationsModule();
|
|
2393
|
+
notificationsModule?.resetNotificationsRuntime();
|
|
2394
|
+
const broadcastModule = await loadBroadcastModule(false, projectRoot);
|
|
2395
|
+
broadcastModule?.resetBroadcastRuntime();
|
|
2396
|
+
const authModule = await loadAuthModule();
|
|
2397
|
+
authModule?.resetAuthRuntime();
|
|
2398
|
+
const authorizationModule = await loadAuthorizationModule();
|
|
2399
|
+
authorizationModule?.authorizationInternals.resetAuthorizationAuthIntegration();
|
|
2400
|
+
const socialModule = await loadSocialModule();
|
|
2401
|
+
socialModule?.resetSocialAuthRuntime();
|
|
2402
|
+
const workosModule = await loadWorkosModule();
|
|
2403
|
+
workosModule?.resetWorkosAuthRuntime();
|
|
2404
|
+
const clerkModule = await loadClerkModule();
|
|
2405
|
+
clerkModule?.resetClerkAuthRuntime();
|
|
2406
|
+
const sessionModule = await loadSessionModule();
|
|
2407
|
+
sessionModule?.resetSessionRuntime();
|
|
2408
|
+
const managedSessionRedisAdapters = getRuntimeState().sessionRedisAdapters;
|
|
2409
|
+
if (managedSessionRedisAdapters) {
|
|
2410
|
+
await Promise.all(managedSessionRedisAdapters.map((adapter) => adapter.close?.()));
|
|
2411
|
+
getRuntimeState().sessionRedisAdapters = void 0;
|
|
2412
|
+
}
|
|
2413
|
+
const securityModule = await loadSecurityModule();
|
|
2414
|
+
const securityBindings = securityModule?.getSecurityRuntimeBindings();
|
|
2415
|
+
const state = getRuntimeState();
|
|
2416
|
+
const managedSecurityRedisAdapter = state.securityRateLimitStoreManaged === true ? state.securityRedisAdapter : void 0;
|
|
2417
|
+
const managedSecurityRateLimitStore = state.securityRateLimitStoreManaged === true ? securityBindings?.rateLimitStore : void 0;
|
|
2418
|
+
if (managedSecurityRedisAdapter) {
|
|
2419
|
+
await managedSecurityRedisAdapter.close?.();
|
|
2420
|
+
state.securityRedisAdapter = void 0;
|
|
2421
|
+
}
|
|
2422
|
+
if (managedSecurityRateLimitStore) {
|
|
2423
|
+
await managedSecurityRateLimitStore.close?.();
|
|
2424
|
+
}
|
|
2425
|
+
state.securityRateLimitStoreManaged = void 0;
|
|
2426
|
+
securityModule?.resetSecurityRuntime();
|
|
2427
|
+
}
|
|
2428
|
+
async function createHolo(projectRoot, options = {}) {
|
|
2429
|
+
const loadedConfig = await loadConfigDirectory(projectRoot, {
|
|
2430
|
+
envName: options.envName,
|
|
2431
|
+
preferCache: options.preferCache,
|
|
2432
|
+
processEnv: options.processEnv
|
|
2433
|
+
});
|
|
2434
|
+
const runtimeConfig = {
|
|
2435
|
+
db: loadedConfig.database,
|
|
2436
|
+
queue: loadedConfig.queue
|
|
2437
|
+
};
|
|
2438
|
+
const manager = resolveRuntimeConnectionManagerOptions(runtimeConfig);
|
|
2439
|
+
const registry = await loadGeneratedProjectRegistry(projectRoot);
|
|
2440
|
+
const accessors = createConfigAccessors(loadedConfig.all);
|
|
2441
|
+
const runtimeOwnedQueueJobNames = [];
|
|
2442
|
+
const runtimeOwnedEventNames = [];
|
|
2443
|
+
const runtimeOwnedListenerIds = [];
|
|
2444
|
+
const runtimeOwnedAuthorizationPolicyNames = [];
|
|
2445
|
+
const runtimeOwnedAuthorizationAbilityNames = [];
|
|
2446
|
+
let activeQueueModule;
|
|
2447
|
+
let activeEventsModule;
|
|
2448
|
+
let activeAuthorizationModule;
|
|
2449
|
+
let activeSessionRuntime;
|
|
2450
|
+
let activeAuthRuntime;
|
|
2451
|
+
let activeAuthContext;
|
|
2452
|
+
let previousOptionalSubsystemBindings;
|
|
2453
|
+
const previousRenderView = options.renderView ? getRuntimeState().renderView : void 0;
|
|
2454
|
+
const fallbackQueueRuntime = Object.freeze({
|
|
2455
|
+
config: loadedConfig.queue,
|
|
2456
|
+
drivers: /* @__PURE__ */ new Map()
|
|
2457
|
+
});
|
|
2458
|
+
const runtime = {
|
|
2459
|
+
projectRoot,
|
|
2460
|
+
loadedConfig,
|
|
2461
|
+
registry,
|
|
2462
|
+
manager,
|
|
2463
|
+
runtimeConfig,
|
|
2464
|
+
get queue() {
|
|
2465
|
+
return activeQueueModule?.getQueueRuntime() ?? fallbackQueueRuntime;
|
|
2466
|
+
},
|
|
2467
|
+
get session() {
|
|
2468
|
+
return activeSessionRuntime;
|
|
2469
|
+
},
|
|
2470
|
+
get auth() {
|
|
2471
|
+
return activeAuthRuntime && activeAuthContext ? bindAuthRuntimeToContext(activeAuthRuntime, activeAuthContext) : activeAuthRuntime;
|
|
2472
|
+
},
|
|
2473
|
+
initialized: false,
|
|
2474
|
+
useConfig: accessors.useConfig,
|
|
2475
|
+
config: accessors.config,
|
|
2476
|
+
async initialize() {
|
|
2477
|
+
if (runtime.initialized) {
|
|
2478
|
+
throw new Error("Holo runtime is already initialized.");
|
|
2479
|
+
}
|
|
2480
|
+
if (getRuntimeState().current) {
|
|
2481
|
+
throw new Error("A Holo runtime is already initialized for this process.");
|
|
2482
|
+
}
|
|
2483
|
+
configureConfigRuntime(loadedConfig.all);
|
|
2484
|
+
configureDB(manager);
|
|
2485
|
+
previousOptionalSubsystemBindings = snapshotOptionalSubsystemRuntimeBindings();
|
|
2486
|
+
if (options.renderView) {
|
|
2487
|
+
configureHoloRenderingRuntime({
|
|
2488
|
+
renderView: options.renderView
|
|
2489
|
+
});
|
|
2490
|
+
}
|
|
2491
|
+
try {
|
|
2492
|
+
await manager.initializeAll();
|
|
2493
|
+
const optionalSubsystems = await reconfigureOptionalHoloSubsystems(projectRoot, loadedConfig, {
|
|
2494
|
+
renderView: options.renderView
|
|
2495
|
+
});
|
|
2496
|
+
activeQueueModule = optionalSubsystems.queueModule;
|
|
2497
|
+
activeSessionRuntime = optionalSubsystems.session;
|
|
2498
|
+
activeAuthRuntime = optionalSubsystems.auth;
|
|
2499
|
+
activeAuthContext = optionalSubsystems.authContext;
|
|
2500
|
+
const optionalEventsModule = activeQueueModule ? await loadEventsModule() : void 0;
|
|
2501
|
+
if (activeQueueModule && optionalEventsModule) {
|
|
2502
|
+
await optionalEventsModule.ensureEventsQueueJobRegisteredAsync?.();
|
|
2503
|
+
}
|
|
2504
|
+
if (registryHasEvents(registry)) {
|
|
2505
|
+
const eventsModule = await loadEventsModule(true);
|
|
2506
|
+
if (!eventsModule) {
|
|
2507
|
+
throw new Error("[@holo-js/core] Events support requires @holo-js/events to be installed.");
|
|
2508
|
+
}
|
|
2509
|
+
activeEventsModule = eventsModule;
|
|
2510
|
+
const eventRegistration = await registerProjectEventsAndListeners(
|
|
2511
|
+
projectRoot,
|
|
2512
|
+
registry,
|
|
2513
|
+
eventsModule,
|
|
2514
|
+
activeQueueModule
|
|
2515
|
+
);
|
|
2516
|
+
runtimeOwnedEventNames.splice(0, runtimeOwnedEventNames.length, ...eventRegistration.eventNames);
|
|
2517
|
+
runtimeOwnedListenerIds.splice(0, runtimeOwnedListenerIds.length, ...eventRegistration.listenerIds);
|
|
2518
|
+
}
|
|
2519
|
+
activeAuthorizationModule = await loadAuthorizationModule();
|
|
2520
|
+
const authorizationRegistration = await registerProjectAuthorizationDefinitions(
|
|
2521
|
+
projectRoot,
|
|
2522
|
+
registry,
|
|
2523
|
+
activeAuthorizationModule
|
|
2524
|
+
);
|
|
2525
|
+
runtimeOwnedAuthorizationPolicyNames.splice(0, runtimeOwnedAuthorizationPolicyNames.length, ...authorizationRegistration.policyNames);
|
|
2526
|
+
runtimeOwnedAuthorizationAbilityNames.splice(0, runtimeOwnedAuthorizationAbilityNames.length, ...authorizationRegistration.abilityNames);
|
|
2527
|
+
if (options.registerProjectQueueJobs === true && registryHasJobs(registry)) {
|
|
2528
|
+
if (!activeQueueModule) {
|
|
2529
|
+
throw new Error("[@holo-js/core] Project jobs require @holo-js/queue to be installed.");
|
|
2530
|
+
}
|
|
2531
|
+
runtimeOwnedQueueJobNames.splice(0, runtimeOwnedQueueJobNames.length);
|
|
2532
|
+
runtimeOwnedQueueJobNames.push(...await registerProjectQueueJobs(projectRoot, registry, activeQueueModule));
|
|
2533
|
+
}
|
|
2534
|
+
runtime.initialized = true;
|
|
2535
|
+
getRuntimeState().current = runtime;
|
|
2536
|
+
} catch (error) {
|
|
2537
|
+
unregisterProjectEventsAndListeners(activeEventsModule, runtimeOwnedEventNames, runtimeOwnedListenerIds);
|
|
2538
|
+
runtimeOwnedEventNames.splice(0, runtimeOwnedEventNames.length);
|
|
2539
|
+
runtimeOwnedListenerIds.splice(0, runtimeOwnedListenerIds.length);
|
|
2540
|
+
unregisterProjectAuthorizationDefinitions(activeAuthorizationModule, runtimeOwnedAuthorizationPolicyNames, runtimeOwnedAuthorizationAbilityNames);
|
|
2541
|
+
runtimeOwnedAuthorizationPolicyNames.splice(0, runtimeOwnedAuthorizationPolicyNames.length);
|
|
2542
|
+
runtimeOwnedAuthorizationAbilityNames.splice(0, runtimeOwnedAuthorizationAbilityNames.length);
|
|
2543
|
+
unregisterProjectQueueJobs(activeQueueModule, runtimeOwnedQueueJobNames);
|
|
2544
|
+
runtimeOwnedQueueJobNames.splice(0, runtimeOwnedQueueJobNames.length);
|
|
2545
|
+
activeAuthorizationModule = void 0;
|
|
2546
|
+
activeEventsModule = void 0;
|
|
2547
|
+
activeQueueModule = void 0;
|
|
2548
|
+
activeSessionRuntime = void 0;
|
|
2549
|
+
activeAuthRuntime = void 0;
|
|
2550
|
+
activeAuthContext = void 0;
|
|
2551
|
+
await manager.disconnectAll().catch(() => {
|
|
2552
|
+
});
|
|
2553
|
+
resetDB();
|
|
2554
|
+
await resetOptionalHoloSubsystems();
|
|
2555
|
+
if (previousOptionalSubsystemBindings) {
|
|
2556
|
+
restoreOptionalSubsystemRuntimeBindings(previousOptionalSubsystemBindings);
|
|
2557
|
+
}
|
|
2558
|
+
if (options.renderView) {
|
|
2559
|
+
restoreHoloRenderingRuntime(previousRenderView);
|
|
2560
|
+
}
|
|
2561
|
+
resetConfigRuntime();
|
|
2562
|
+
getRuntimeState().current = void 0;
|
|
2563
|
+
throw error;
|
|
2564
|
+
}
|
|
2565
|
+
},
|
|
2566
|
+
async shutdown() {
|
|
2567
|
+
try {
|
|
2568
|
+
if (runtime.initialized) {
|
|
2569
|
+
await manager.disconnectAll();
|
|
2570
|
+
}
|
|
2571
|
+
} finally {
|
|
2572
|
+
runtime.initialized = false;
|
|
2573
|
+
if (getRuntimeState().current === runtime) {
|
|
2574
|
+
getRuntimeState().current = void 0;
|
|
2575
|
+
}
|
|
2576
|
+
unregisterProjectEventsAndListeners(activeEventsModule, runtimeOwnedEventNames, runtimeOwnedListenerIds);
|
|
2577
|
+
runtimeOwnedEventNames.splice(0, runtimeOwnedEventNames.length);
|
|
2578
|
+
runtimeOwnedListenerIds.splice(0, runtimeOwnedListenerIds.length);
|
|
2579
|
+
unregisterProjectAuthorizationDefinitions(activeAuthorizationModule, runtimeOwnedAuthorizationPolicyNames, runtimeOwnedAuthorizationAbilityNames);
|
|
2580
|
+
runtimeOwnedAuthorizationPolicyNames.splice(0, runtimeOwnedAuthorizationPolicyNames.length);
|
|
2581
|
+
runtimeOwnedAuthorizationAbilityNames.splice(0, runtimeOwnedAuthorizationAbilityNames.length);
|
|
2582
|
+
unregisterProjectQueueJobs(activeQueueModule, runtimeOwnedQueueJobNames);
|
|
2583
|
+
runtimeOwnedQueueJobNames.splice(0, runtimeOwnedQueueJobNames.length);
|
|
2584
|
+
activeAuthorizationModule = void 0;
|
|
2585
|
+
activeEventsModule = void 0;
|
|
2586
|
+
activeQueueModule = void 0;
|
|
2587
|
+
activeSessionRuntime = void 0;
|
|
2588
|
+
activeAuthRuntime = void 0;
|
|
2589
|
+
activeAuthContext = void 0;
|
|
2590
|
+
resetDB();
|
|
2591
|
+
await resetOptionalHoloSubsystems();
|
|
2592
|
+
if (previousOptionalSubsystemBindings) {
|
|
2593
|
+
restoreOptionalSubsystemRuntimeBindings(previousOptionalSubsystemBindings);
|
|
2594
|
+
}
|
|
2595
|
+
if (options.renderView) {
|
|
2596
|
+
restoreHoloRenderingRuntime(previousRenderView);
|
|
2597
|
+
}
|
|
2598
|
+
resetConfigRuntime();
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
};
|
|
2602
|
+
return runtime;
|
|
2603
|
+
}
|
|
2604
|
+
async function initializeHolo(projectRoot, options = {}) {
|
|
2605
|
+
const state = getRuntimeState();
|
|
2606
|
+
const resolvedProjectRoot = resolve3(projectRoot);
|
|
2607
|
+
const current = state.current;
|
|
2608
|
+
if (current) {
|
|
2609
|
+
if (resolve3(current.projectRoot) !== resolvedProjectRoot) {
|
|
2610
|
+
throw new Error(`A Holo runtime is already initialized for "${current.projectRoot}".`);
|
|
2611
|
+
}
|
|
2612
|
+
return current;
|
|
2613
|
+
}
|
|
2614
|
+
if (state.pending) {
|
|
2615
|
+
if (state.pendingProjectRoot && resolve3(state.pendingProjectRoot) !== resolvedProjectRoot) {
|
|
2616
|
+
throw new Error(`A Holo runtime is already initializing for "${state.pendingProjectRoot}".`);
|
|
2617
|
+
}
|
|
2618
|
+
return state.pending;
|
|
2619
|
+
}
|
|
2620
|
+
const pending = (async () => {
|
|
2621
|
+
const runtime = await createHolo(projectRoot, options);
|
|
2622
|
+
await runtime.initialize();
|
|
2623
|
+
return runtime;
|
|
2624
|
+
})();
|
|
2625
|
+
state.pending = pending;
|
|
2626
|
+
state.pendingProjectRoot = resolvedProjectRoot;
|
|
2627
|
+
try {
|
|
2628
|
+
return await pending;
|
|
2629
|
+
} finally {
|
|
2630
|
+
if (state.pending === pending) {
|
|
2631
|
+
state.pending = void 0;
|
|
2632
|
+
state.pendingProjectRoot = void 0;
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
}
|
|
2636
|
+
function peekHolo() {
|
|
2637
|
+
return getRuntimeState().current;
|
|
2638
|
+
}
|
|
2639
|
+
async function ensureHolo(projectRoot, options = {}) {
|
|
2640
|
+
const current = peekHolo();
|
|
2641
|
+
if (!current) {
|
|
2642
|
+
return initializeHolo(projectRoot, options);
|
|
2643
|
+
}
|
|
2644
|
+
if (resolve3(current.projectRoot) !== resolve3(projectRoot)) {
|
|
2645
|
+
throw new Error(`A Holo runtime is already initialized for "${current.projectRoot}".`);
|
|
2646
|
+
}
|
|
2647
|
+
return current;
|
|
2648
|
+
}
|
|
2649
|
+
function getHolo() {
|
|
2650
|
+
const current = getRuntimeState().current;
|
|
2651
|
+
if (!current) {
|
|
2652
|
+
throw new Error("Holo runtime is not initialized.");
|
|
2653
|
+
}
|
|
2654
|
+
return current;
|
|
2655
|
+
}
|
|
2656
|
+
async function resetHoloRuntime() {
|
|
2657
|
+
const current = getRuntimeState().current;
|
|
2658
|
+
const projectRoot = current?.projectRoot ?? getRuntimeState().pendingProjectRoot;
|
|
2659
|
+
getRuntimeState().pending = void 0;
|
|
2660
|
+
getRuntimeState().pendingProjectRoot = void 0;
|
|
2661
|
+
if (!current) {
|
|
2662
|
+
resetDB();
|
|
2663
|
+
await resetOptionalHoloSubsystems();
|
|
2664
|
+
resetHoloRenderingRuntime();
|
|
2665
|
+
resetConfigRuntime();
|
|
2666
|
+
return;
|
|
2667
|
+
}
|
|
2668
|
+
await current.shutdown();
|
|
2669
|
+
const mailModule = await loadMailModule();
|
|
2670
|
+
mailModule?.resetMailRuntime();
|
|
2671
|
+
const notificationsModule = await loadNotificationsModule();
|
|
2672
|
+
notificationsModule?.resetNotificationsRuntime();
|
|
2673
|
+
const securityModule = await loadSecurityModule();
|
|
2674
|
+
securityModule?.resetSecurityRuntime();
|
|
2675
|
+
const broadcastModule = await loadBroadcastModule(false, projectRoot);
|
|
2676
|
+
broadcastModule?.resetBroadcastRuntime();
|
|
2677
|
+
resetHoloRenderingRuntime();
|
|
2678
|
+
}
|
|
2679
|
+
function getConfigValue(path) {
|
|
2680
|
+
return globalConfig(path);
|
|
2681
|
+
}
|
|
2682
|
+
function getConfigSection(key) {
|
|
2683
|
+
return globalUseConfig(key);
|
|
2684
|
+
}
|
|
2685
|
+
var holoRuntimeInternals = {
|
|
2686
|
+
createAuthMailDeliveryHook,
|
|
2687
|
+
createAuthNotificationsDeliveryHook,
|
|
2688
|
+
createCoreNotificationBroadcaster,
|
|
2689
|
+
createCoreNotificationMailSender,
|
|
2690
|
+
bindAuthRuntimeToContext,
|
|
2691
|
+
createCoreAuthProviders,
|
|
2692
|
+
createCoreAuthStores,
|
|
2693
|
+
createCoreHostedIdentityStore,
|
|
2694
|
+
createCoreNotificationStore,
|
|
2695
|
+
createNotificationMailText,
|
|
2696
|
+
createCoreSessionStores,
|
|
2697
|
+
registerProjectAuthorizationDefinitions,
|
|
2698
|
+
unregisterProjectAuthorizationDefinitions,
|
|
2699
|
+
resolveAuthorizationDefinitionExport,
|
|
2700
|
+
fromHostedIdentityProviderValue,
|
|
2701
|
+
getConfigSection,
|
|
2702
|
+
getConfigValue,
|
|
2703
|
+
createCoreSocialBindings,
|
|
2704
|
+
normalizeNotificationRecordFromRow,
|
|
2705
|
+
loadConfiguredSocialProviders,
|
|
2706
|
+
loadAuthorizationModule,
|
|
2707
|
+
markProviderUser,
|
|
2708
|
+
normalizeDateValue,
|
|
2709
|
+
normalizeEmailVerificationTokenRecord,
|
|
2710
|
+
normalizeJsonValue,
|
|
2711
|
+
normalizePasswordResetTokenRecord,
|
|
2712
|
+
serializeNotificationRecordForRow,
|
|
2713
|
+
moduleInternals: portableRuntimeModuleInternals
|
|
2714
|
+
};
|
|
2715
|
+
|
|
2716
|
+
export {
|
|
2717
|
+
createAdapter,
|
|
2718
|
+
createDialect,
|
|
2719
|
+
createRuntimeConnectionOptions,
|
|
2720
|
+
createRuntimeLogger,
|
|
2721
|
+
isSupportedDatabaseDriver,
|
|
2722
|
+
parseDatabaseDriver,
|
|
2723
|
+
resolveRuntimeConnectionManagerOptions,
|
|
2724
|
+
resolveGeneratedProjectRegistryPath,
|
|
2725
|
+
loadGeneratedProjectRegistry,
|
|
2726
|
+
createGeneratedBroadcastManifest,
|
|
2727
|
+
loadGeneratedBroadcastManifest,
|
|
2728
|
+
registryInternals,
|
|
2729
|
+
resolveStorageKeyPath,
|
|
2730
|
+
configureHoloRenderingRuntime,
|
|
2731
|
+
resetHoloRenderingRuntime,
|
|
2732
|
+
reconfigureOptionalHoloSubsystems,
|
|
2733
|
+
resetOptionalHoloSubsystems,
|
|
2734
|
+
createHolo,
|
|
2735
|
+
initializeHolo,
|
|
2736
|
+
peekHolo,
|
|
2737
|
+
ensureHolo,
|
|
2738
|
+
getHolo,
|
|
2739
|
+
resetHoloRuntime,
|
|
2740
|
+
holoRuntimeInternals
|
|
2741
|
+
};
|