@holo-js/config 0.2.6 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,8 +1,15 @@
1
+ import {
2
+ composeRegisteredConfig,
3
+ configRegistryInternals,
4
+ registerConfigNormalizer,
5
+ resetConfigNormalizers
6
+ } from "./chunk-SAQGYMAD.mjs";
7
+
1
8
  // src/defaults.ts
2
9
  import {
3
10
  DEFAULT_HOLO_PROJECT_PATHS,
4
11
  normalizeHoloProjectConfig
5
- } from "@holo-js/db";
12
+ } from "@holo-js/kernel";
6
13
  var DEFAULT_APP_NAME = "Holo";
7
14
  var holoAppDefaults = Object.freeze({
8
15
  name: DEFAULT_APP_NAME,
@@ -10,785 +17,12 @@ var holoAppDefaults = Object.freeze({
10
17
  url: "http://localhost:3000",
11
18
  debug: true,
12
19
  env: "development",
20
+ plugins: Object.freeze([]),
13
21
  paths: Object.freeze({ ...DEFAULT_HOLO_PROJECT_PATHS }),
14
22
  models: Object.freeze([]),
15
23
  migrations: Object.freeze([]),
16
24
  seeders: Object.freeze([])
17
25
  });
18
- var holoDatabaseDefaults = Object.freeze({
19
- defaultConnection: "default",
20
- connections: Object.freeze({
21
- default: Object.freeze({
22
- driver: "sqlite",
23
- url: "./data/database.sqlite",
24
- schema: "public",
25
- logging: false
26
- })
27
- })
28
- });
29
- var DEFAULT_REDIS_CONNECTION = "default";
30
- var DEFAULT_REDIS_HOST = "127.0.0.1";
31
- var DEFAULT_REDIS_PORT = 6379;
32
- var DEFAULT_REDIS_DB = 0;
33
- var holoRedisDefaults = Object.freeze({
34
- default: DEFAULT_REDIS_CONNECTION,
35
- connections: Object.freeze({
36
- [DEFAULT_REDIS_CONNECTION]: Object.freeze({
37
- name: DEFAULT_REDIS_CONNECTION,
38
- host: DEFAULT_REDIS_HOST,
39
- port: DEFAULT_REDIS_PORT,
40
- username: void 0,
41
- password: void 0,
42
- db: DEFAULT_REDIS_DB
43
- })
44
- })
45
- });
46
- var holoStorageDefaults = Object.freeze({
47
- defaultDisk: "local",
48
- routePrefix: "/storage",
49
- disks: Object.freeze({
50
- local: Object.freeze({
51
- driver: "local",
52
- root: "./storage/app"
53
- }),
54
- public: Object.freeze({
55
- driver: "public",
56
- root: "./storage/app/public",
57
- visibility: "public"
58
- })
59
- })
60
- });
61
- var DEFAULT_NOTIFICATIONS_TABLE = "notifications";
62
- var DEFAULT_BROADCAST_CONNECTION = "null";
63
- var DEFAULT_BROADCAST_HOST = "127.0.0.1";
64
- var DEFAULT_BROADCAST_HTTP_PORT = 80;
65
- var DEFAULT_BROADCAST_HTTPS_PORT = 443;
66
- var DEFAULT_BROADCAST_WORKER_HOST = "0.0.0.0";
67
- var DEFAULT_BROADCAST_WORKER_PORT = 8080;
68
- var DEFAULT_BROADCAST_WORKER_PATH = "/app";
69
- var DEFAULT_BROADCAST_HEALTH_PATH = "/health";
70
- var DEFAULT_BROADCAST_STATS_PATH = "/stats";
71
- var holoNotificationsDefaults = Object.freeze({
72
- table: DEFAULT_NOTIFICATIONS_TABLE,
73
- queue: Object.freeze({
74
- connection: void 0,
75
- queue: void 0,
76
- afterCommit: false
77
- })
78
- });
79
- var holoBroadcastDefaults = Object.freeze({
80
- default: DEFAULT_BROADCAST_CONNECTION,
81
- connections: Object.freeze({
82
- log: Object.freeze({
83
- name: "log",
84
- driver: "log",
85
- clientOptions: Object.freeze({})
86
- }),
87
- null: Object.freeze({
88
- name: "null",
89
- driver: "null",
90
- clientOptions: Object.freeze({})
91
- })
92
- }),
93
- worker: Object.freeze({
94
- host: DEFAULT_BROADCAST_WORKER_HOST,
95
- port: DEFAULT_BROADCAST_WORKER_PORT,
96
- path: DEFAULT_BROADCAST_WORKER_PATH,
97
- publicHost: void 0,
98
- publicPort: void 0,
99
- publicScheme: "https",
100
- healthPath: DEFAULT_BROADCAST_HEALTH_PATH,
101
- statsPath: DEFAULT_BROADCAST_STATS_PATH,
102
- scaling: false
103
- })
104
- });
105
- var DEFAULT_CACHE_DRIVER = "file";
106
- var DEFAULT_CACHE_PREFIX = "";
107
- var DEFAULT_CACHE_FILE_PATH = "./storage/framework/cache/data";
108
- var DEFAULT_CACHE_REDIS_CONNECTION = "default";
109
- var DEFAULT_CACHE_DATABASE_CONNECTION = "default";
110
- var DEFAULT_CACHE_DATABASE_TABLE = "cache";
111
- var DEFAULT_CACHE_DATABASE_LOCK_TABLE = "cache_locks";
112
- var holoCacheDefaults = Object.freeze({
113
- default: DEFAULT_CACHE_DRIVER,
114
- prefix: DEFAULT_CACHE_PREFIX,
115
- drivers: Object.freeze({
116
- file: Object.freeze({
117
- name: "file",
118
- driver: "file",
119
- path: DEFAULT_CACHE_FILE_PATH,
120
- prefix: DEFAULT_CACHE_PREFIX
121
- }),
122
- memory: Object.freeze({
123
- name: "memory",
124
- driver: "memory",
125
- maxEntries: void 0,
126
- prefix: DEFAULT_CACHE_PREFIX
127
- })
128
- })
129
- });
130
- var DEFAULT_MAILER_NAME = "preview";
131
- var DEFAULT_MAIL_PREVIEW_PATH = ".holo-js/runtime/mail-preview";
132
- var DEFAULT_SMTP_HOST = "127.0.0.1";
133
- var DEFAULT_SMTP_PORT = 1025;
134
- var DEFAULT_MAIL_QUEUE_CONFIG = Object.freeze({
135
- queued: false,
136
- connection: void 0,
137
- queue: void 0,
138
- afterCommit: false
139
- });
140
- var holoMailDefaults = Object.freeze({
141
- default: DEFAULT_MAILER_NAME,
142
- from: void 0,
143
- replyTo: void 0,
144
- queue: DEFAULT_MAIL_QUEUE_CONFIG,
145
- preview: Object.freeze({
146
- allowedEnvironments: Object.freeze(["development"])
147
- }),
148
- markdown: Object.freeze({
149
- wrapper: void 0
150
- }),
151
- mailers: Object.freeze({
152
- preview: Object.freeze({
153
- name: "preview",
154
- driver: "preview",
155
- from: void 0,
156
- replyTo: void 0,
157
- queue: DEFAULT_MAIL_QUEUE_CONFIG,
158
- path: DEFAULT_MAIL_PREVIEW_PATH
159
- }),
160
- log: Object.freeze({
161
- name: "log",
162
- driver: "log",
163
- from: void 0,
164
- replyTo: void 0,
165
- queue: DEFAULT_MAIL_QUEUE_CONFIG,
166
- logBodies: false
167
- }),
168
- fake: Object.freeze({
169
- name: "fake",
170
- driver: "fake",
171
- from: void 0,
172
- replyTo: void 0,
173
- queue: DEFAULT_MAIL_QUEUE_CONFIG
174
- }),
175
- smtp: Object.freeze({
176
- name: "smtp",
177
- driver: "smtp",
178
- from: void 0,
179
- replyTo: void 0,
180
- queue: DEFAULT_MAIL_QUEUE_CONFIG,
181
- host: DEFAULT_SMTP_HOST,
182
- port: DEFAULT_SMTP_PORT,
183
- secure: false
184
- })
185
- })
186
- });
187
- var DEFAULT_SESSION_DRIVER = "file";
188
- var DEFAULT_SESSION_IDLE_TIMEOUT = 120;
189
- var DEFAULT_SESSION_ABSOLUTE_LIFETIME = 120;
190
- var DEFAULT_SESSION_REMEMBER_ME_LIFETIME = 43200;
191
- var DEFAULT_SESSION_COOKIE_NAME = "holo_session";
192
- var DEFAULT_SESSION_COOKIE_PATH = "/";
193
- var DEFAULT_SESSION_COOKIE_SAME_SITE = "lax";
194
- var DEFAULT_SESSION_DATABASE_CONNECTION = "default";
195
- var DEFAULT_SESSION_DATABASE_TABLE = "sessions";
196
- var DEFAULT_SESSION_FILE_PATH = "./storage/framework/sessions";
197
- var holoSessionDefaults = Object.freeze({
198
- driver: DEFAULT_SESSION_DRIVER,
199
- stores: Object.freeze({
200
- database: Object.freeze({
201
- name: "database",
202
- driver: "database",
203
- connection: DEFAULT_SESSION_DATABASE_CONNECTION,
204
- table: DEFAULT_SESSION_DATABASE_TABLE
205
- }),
206
- file: Object.freeze({
207
- name: "file",
208
- driver: "file",
209
- path: DEFAULT_SESSION_FILE_PATH
210
- })
211
- }),
212
- cookie: Object.freeze({
213
- name: DEFAULT_SESSION_COOKIE_NAME,
214
- path: DEFAULT_SESSION_COOKIE_PATH,
215
- secure: false,
216
- httpOnly: true,
217
- sameSite: DEFAULT_SESSION_COOKIE_SAME_SITE,
218
- partitioned: false,
219
- maxAge: DEFAULT_SESSION_ABSOLUTE_LIFETIME
220
- }),
221
- idleTimeout: DEFAULT_SESSION_IDLE_TIMEOUT,
222
- absoluteLifetime: DEFAULT_SESSION_ABSOLUTE_LIFETIME,
223
- rememberMeLifetime: DEFAULT_SESSION_REMEMBER_ME_LIFETIME
224
- });
225
- var DEFAULT_CORS_PATHS = Object.freeze(["/api/*"]);
226
- var DEFAULT_CORS_METHODS = Object.freeze(["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]);
227
- var DEFAULT_CORS_HEADERS = Object.freeze(["Content-Type", "Authorization", "X-CSRF-TOKEN", "X-Requested-With"]);
228
- var DEFAULT_CORS_MAX_AGE = 7200;
229
- var holoCorsDefaults = Object.freeze({
230
- paths: DEFAULT_CORS_PATHS,
231
- origins: Object.freeze([]),
232
- methods: DEFAULT_CORS_METHODS,
233
- headers: DEFAULT_CORS_HEADERS,
234
- credentials: false,
235
- maxAge: DEFAULT_CORS_MAX_AGE,
236
- statefulDomains: Object.freeze([])
237
- });
238
- var DEFAULT_SECURITY_CSRF_FIELD = "_token";
239
- var DEFAULT_SECURITY_CSRF_HEADER = "X-CSRF-TOKEN";
240
- var DEFAULT_SECURITY_CSRF_COOKIE = "XSRF-TOKEN";
241
- var DEFAULT_SECURITY_RATE_LIMIT_DRIVER = "memory";
242
- var DEFAULT_SECURITY_RATE_LIMIT_FILE_PATH = "./storage/framework/rate-limits";
243
- var DEFAULT_SECURITY_RATE_LIMIT_REDIS_CONNECTION = "default";
244
- var DEFAULT_SECURITY_RATE_LIMIT_REDIS_PREFIX = "holo:rate-limit:";
245
- var DEFAULT_SECURITY_CSRF_CONFIG = Object.freeze({
246
- enabled: false,
247
- field: DEFAULT_SECURITY_CSRF_FIELD,
248
- header: DEFAULT_SECURITY_CSRF_HEADER,
249
- cookie: DEFAULT_SECURITY_CSRF_COOKIE,
250
- except: Object.freeze([])
251
- });
252
- var DEFAULT_SECURITY_RATE_LIMIT_CONFIG = Object.freeze({
253
- driver: DEFAULT_SECURITY_RATE_LIMIT_DRIVER,
254
- memory: Object.freeze({
255
- driver: "memory"
256
- }),
257
- file: Object.freeze({
258
- path: DEFAULT_SECURITY_RATE_LIMIT_FILE_PATH
259
- }),
260
- redis: Object.freeze({
261
- host: DEFAULT_REDIS_HOST,
262
- port: DEFAULT_REDIS_PORT,
263
- password: void 0,
264
- username: void 0,
265
- db: DEFAULT_REDIS_DB,
266
- connection: DEFAULT_SECURITY_RATE_LIMIT_REDIS_CONNECTION,
267
- prefix: DEFAULT_SECURITY_RATE_LIMIT_REDIS_PREFIX
268
- }),
269
- limiters: Object.freeze({})
270
- });
271
- var holoSecurityDefaults = Object.freeze({
272
- csrf: DEFAULT_SECURITY_CSRF_CONFIG,
273
- rateLimit: DEFAULT_SECURITY_RATE_LIMIT_CONFIG
274
- });
275
- var DEFAULT_AUTH_GUARD = "web";
276
- var DEFAULT_AUTH_PROVIDER = "users";
277
- var DEFAULT_AUTH_IDENTIFIERS = Object.freeze(["email"]);
278
- var DEFAULT_AUTH_PASSWORD_BROKER = "users";
279
- var DEFAULT_AUTH_PASSWORD_RESET_TABLE = "password_reset_tokens";
280
- var DEFAULT_AUTH_PASSWORD_EXPIRE = 60;
281
- var DEFAULT_AUTH_PASSWORD_THROTTLE = 60;
282
- var DEFAULT_AUTH_PASSWORD_RESET_ROUTE = "/reset-password";
283
- var DEFAULT_AUTH_EMAIL_VERIFICATION_ROUTE = "/verify-email";
284
- var DEFAULT_WORKOS_SESSION_COOKIE = "wos-session";
285
- var DEFAULT_CLERK_SESSION_COOKIE = "__session";
286
- var holoAuthDefaults = Object.freeze({
287
- defaults: Object.freeze({
288
- guard: DEFAULT_AUTH_GUARD,
289
- passwords: DEFAULT_AUTH_PASSWORD_BROKER
290
- }),
291
- guards: Object.freeze({
292
- web: Object.freeze({
293
- name: "web",
294
- driver: "session",
295
- provider: DEFAULT_AUTH_PROVIDER
296
- })
297
- }),
298
- providers: Object.freeze({
299
- users: Object.freeze({
300
- name: "users",
301
- model: "User",
302
- identifiers: DEFAULT_AUTH_IDENTIFIERS
303
- })
304
- }),
305
- passwords: Object.freeze({
306
- users: Object.freeze({
307
- name: "users",
308
- provider: DEFAULT_AUTH_PROVIDER,
309
- table: DEFAULT_AUTH_PASSWORD_RESET_TABLE,
310
- expire: DEFAULT_AUTH_PASSWORD_EXPIRE,
311
- throttle: DEFAULT_AUTH_PASSWORD_THROTTLE,
312
- route: DEFAULT_AUTH_PASSWORD_RESET_ROUTE
313
- })
314
- }),
315
- emailVerification: Object.freeze({
316
- required: false,
317
- route: DEFAULT_AUTH_EMAIL_VERIFICATION_ROUTE
318
- }),
319
- personalAccessTokens: Object.freeze({
320
- defaultAbilities: Object.freeze(["*"])
321
- }),
322
- socialEncryptionKey: void 0,
323
- social: Object.freeze({}),
324
- workos: Object.freeze({}),
325
- clerk: Object.freeze({})
326
- });
327
- var DEFAULT_QUEUE_CONNECTION = "sync";
328
- var DEFAULT_QUEUE_NAME = "default";
329
- var DEFAULT_QUEUE_RETRY_AFTER = 90;
330
- var DEFAULT_QUEUE_BLOCK_FOR = 5;
331
- var DEFAULT_QUEUE_SLEEP = 1;
332
- var DEFAULT_FAILED_JOBS_CONNECTION = "default";
333
- var DEFAULT_FAILED_JOBS_TABLE = "failed_jobs";
334
- var DEFAULT_DATABASE_QUEUE_TABLE = "jobs";
335
- var DEFAULT_QUEUE_CONFIG = Object.freeze({
336
- default: DEFAULT_QUEUE_CONNECTION,
337
- failed: Object.freeze({
338
- driver: "database",
339
- connection: DEFAULT_FAILED_JOBS_CONNECTION,
340
- table: DEFAULT_FAILED_JOBS_TABLE
341
- }),
342
- connections: Object.freeze({
343
- [DEFAULT_QUEUE_CONNECTION]: Object.freeze({
344
- name: DEFAULT_QUEUE_CONNECTION,
345
- driver: "sync",
346
- queue: DEFAULT_QUEUE_NAME
347
- })
348
- })
349
- });
350
- function parseIntegerCandidate(value, parser) {
351
- if (typeof value === "number") {
352
- return value;
353
- }
354
- const trimmed = value.trim();
355
- if (!trimmed) {
356
- return Number.NaN;
357
- }
358
- if (parser === "parse-int") {
359
- return Number.parseInt(value, 10);
360
- }
361
- if (parser === "digits") {
362
- return /^\d+$/.test(trimmed) ? Number.parseInt(trimmed, 10) : Number.NaN;
363
- }
364
- return Number(trimmed);
365
- }
366
- function parseScopedInteger(value, fallback, namespace, label, options = {}) {
367
- const normalized = typeof value === "undefined" ? fallback : parseIntegerCandidate(value, options.parser ?? "number");
368
- if (!Number.isFinite(normalized) || !Number.isInteger(normalized)) {
369
- throw new Error(`[${namespace}] ${label} must be an integer.`);
370
- }
371
- if (typeof options.minimum === "number" && normalized < options.minimum) {
372
- throw new Error(`[${namespace}] ${label} must be greater than or equal to ${options.minimum}.`);
373
- }
374
- return normalized;
375
- }
376
- function parseScopedOptionalInteger(value, namespace, label, options = {}) {
377
- if (typeof value === "undefined") {
378
- return void 0;
379
- }
380
- return parseScopedInteger(value, Number.NaN, namespace, label, options);
381
- }
382
- function parseInteger(value, fallback, namespace, label, options = {}) {
383
- return parseScopedInteger(value, fallback, namespace, label, {
384
- ...options,
385
- parser: "parse-int"
386
- });
387
- }
388
- function normalizeNonEmptyString(value, label) {
389
- const normalized = value?.trim();
390
- if (!normalized) {
391
- throw new Error(label);
392
- }
393
- return normalized;
394
- }
395
- function normalizeScopedName(value, namespace, label) {
396
- const normalized = value?.trim();
397
- if (!normalized) {
398
- throw new Error(`[${namespace}] ${label} must be a non-empty string.`);
399
- }
400
- return normalized;
401
- }
402
- function normalizeConnectionName(value, namespace, label) {
403
- return normalizeScopedName(value, namespace, label);
404
- }
405
- function normalizeCacheName(value, label) {
406
- return normalizeScopedName(value, "Holo Cache", label);
407
- }
408
- function normalizeCacheOptionalString(value) {
409
- const normalized = value?.trim();
410
- return normalized || void 0;
411
- }
412
- function parseCacheInteger(value, label, options = {}) {
413
- return parseScopedOptionalInteger(value, "Holo Cache", label, options);
414
- }
415
- function resolveCachePrefix(globalPrefix, localPrefix) {
416
- return normalizeCacheOptionalString(localPrefix) ?? globalPrefix;
417
- }
418
- function normalizeCacheDriverConfig(name, config2, globalPrefix) {
419
- switch (config2.driver) {
420
- case "memory": {
421
- const memoryConfig = config2;
422
- return Object.freeze({
423
- name,
424
- driver: "memory",
425
- prefix: resolveCachePrefix(globalPrefix, memoryConfig.prefix),
426
- maxEntries: parseCacheInteger(memoryConfig.maxEntries, `cache driver "${name}" maxEntries`, {
427
- minimum: 1
428
- })
429
- });
430
- }
431
- case "file": {
432
- const fileConfig = config2;
433
- return Object.freeze({
434
- name,
435
- driver: "file",
436
- path: normalizeCacheOptionalString(fileConfig.path) || DEFAULT_CACHE_FILE_PATH,
437
- prefix: resolveCachePrefix(globalPrefix, fileConfig.prefix)
438
- });
439
- }
440
- case "redis": {
441
- const redisConfig = config2;
442
- return Object.freeze({
443
- name,
444
- driver: "redis",
445
- connection: redisConfig.connection,
446
- prefix: resolveCachePrefix(globalPrefix, redisConfig.prefix)
447
- });
448
- }
449
- case "database": {
450
- const databaseConfig = config2;
451
- return Object.freeze({
452
- name,
453
- driver: "database",
454
- connection: databaseConfig.connection,
455
- table: normalizeCacheOptionalString(databaseConfig.table) || DEFAULT_CACHE_DATABASE_TABLE,
456
- lockTable: normalizeCacheOptionalString(databaseConfig.lockTable) || DEFAULT_CACHE_DATABASE_LOCK_TABLE,
457
- prefix: resolveCachePrefix(globalPrefix, databaseConfig.prefix)
458
- });
459
- }
460
- default:
461
- throw new Error(`[Holo Cache] Unsupported cache driver "${String(config2.driver)}" on driver "${name}".`);
462
- }
463
- }
464
- function normalizeCacheConfig(config2 = {}, options = {}) {
465
- const prefix = normalizeCacheOptionalString(config2.prefix) ?? DEFAULT_CACHE_PREFIX;
466
- const defaultRedisConnection = options.redis?.default ?? DEFAULT_CACHE_REDIS_CONNECTION;
467
- const defaultDatabaseConnection = options.database?.defaultConnection ?? DEFAULT_CACHE_DATABASE_CONNECTION;
468
- const driverEntries = !config2.drivers || Object.keys(config2.drivers).length === 0 ? Object.entries(holoCacheDefaults.drivers) : Object.entries(config2.drivers);
469
- const normalizedDriverEntries = driverEntries.map(([name, driver]) => {
470
- const normalizedName = normalizeCacheName(name, "Cache driver name");
471
- const driverConfig = (() => {
472
- switch (driver.driver) {
473
- case "redis":
474
- return {
475
- ...driver,
476
- connection: normalizeCacheOptionalString(driver.connection) ?? defaultRedisConnection
477
- };
478
- case "database":
479
- return {
480
- ...driver,
481
- connection: normalizeCacheOptionalString(driver.connection) ?? defaultDatabaseConnection
482
- };
483
- default:
484
- return driver;
485
- }
486
- })();
487
- return [normalizedName, normalizeCacheDriverConfig(normalizedName, driverConfig, prefix)];
488
- });
489
- const drivers = Object.freeze(Object.fromEntries(normalizedDriverEntries));
490
- const configuredDefault = normalizeCacheOptionalString(config2.default);
491
- if (configuredDefault && !Object.hasOwn(drivers, configuredDefault)) {
492
- throw new Error(`[Holo Cache] default cache driver "${configuredDefault}" is not configured.`);
493
- }
494
- const fallbackDefaultDriver = normalizedDriverEntries.find(([name]) => name === DEFAULT_CACHE_DRIVER)?.[0] ?? normalizedDriverEntries[0][0];
495
- const defaultDriver = configuredDefault ?? fallbackDefaultDriver;
496
- return Object.freeze({
497
- default: defaultDriver,
498
- prefix,
499
- drivers
500
- });
501
- }
502
- function normalizeQueueName(value) {
503
- return value?.trim() || DEFAULT_QUEUE_NAME;
504
- }
505
- function parseRedisInteger(value, fallback, label, options = {}) {
506
- return parseScopedInteger(value, fallback, "Holo Redis", label, {
507
- ...options,
508
- parser: "digits"
509
- });
510
- }
511
- function normalizeRedisConnectionName(value, label) {
512
- return normalizeScopedName(value, "Holo Redis", label);
513
- }
514
- function normalizeOptionalRedisString(value, label) {
515
- if (typeof value === "undefined") {
516
- return void 0;
517
- }
518
- const normalized = normalizeRedisConnectionName(value, label);
519
- try {
520
- const parsed = new URL(normalized);
521
- if (parsed.protocol !== "redis:" && parsed.protocol !== "rediss:") {
522
- throw new Error(`[Holo Redis] ${label} must use the redis:// or rediss:// scheme.`);
523
- }
524
- } catch (error) {
525
- if (error instanceof Error && error.message.startsWith("[Holo Redis]")) {
526
- throw error;
527
- }
528
- throw new Error(`[Holo Redis] ${label} must be a valid redis:// or rediss:// URL.`);
529
- }
530
- return normalized;
531
- }
532
- function normalizeOptionalRedisSocketPath(value, label) {
533
- if (typeof value === "undefined") {
534
- return void 0;
535
- }
536
- return normalizeRedisConnectionName(value, label);
537
- }
538
- function deriveNormalizedRedisSocketPath(socketPath, host) {
539
- if (socketPath) {
540
- return socketPath;
541
- }
542
- if (typeof host === "string" && (host.startsWith("unix://") || host.startsWith("/"))) {
543
- return host.startsWith("unix://") ? host.slice("unix://".length) : host;
544
- }
545
- return void 0;
546
- }
547
- function normalizeRedisClusterNodeConfig(connectionName, index, config2) {
548
- const label = `redis connection "${connectionName}" cluster node ${index + 1}`;
549
- const url = normalizeOptionalRedisString(config2.url, `${label} url`);
550
- const socketPath = normalizeOptionalRedisSocketPath(config2.socketPath, `${label} socketPath`);
551
- const normalizedSocketPath = deriveNormalizedRedisSocketPath(socketPath, config2.host?.trim());
552
- if (typeof normalizedSocketPath !== "undefined") {
553
- throw new Error(`[Holo Redis] ${label} cannot use socketPath in cluster mode.`);
554
- }
555
- parseRedisDatabaseFromUrl(url, {
556
- allowPath: false,
557
- label: `${label} url`
558
- });
559
- const host = config2.host?.trim() || DEFAULT_REDIS_HOST;
560
- return Object.freeze({
561
- ...typeof url === "undefined" ? {} : { url },
562
- host,
563
- port: parseRedisInteger(config2.port, DEFAULT_REDIS_PORT, `${label} port`, {
564
- minimum: 1
565
- })
566
- });
567
- }
568
- function normalizeRedisClusterNodes(connectionName, nodes) {
569
- if (!nodes || nodes.length === 0) {
570
- return void 0;
571
- }
572
- return Object.freeze(nodes.map((node, index) => normalizeRedisClusterNodeConfig(connectionName, index, node)));
573
- }
574
- function parseRedisDatabaseFromUrl(url, options) {
575
- if (typeof url === "undefined") {
576
- return void 0;
577
- }
578
- try {
579
- const parsed = new URL(url);
580
- const pathname = parsed.pathname.replace(/^\/+/, "");
581
- if (!pathname) {
582
- return void 0;
583
- }
584
- const [databaseSegment] = pathname.split("/");
585
- const { label } = options;
586
- if (options.allowPath === false) {
587
- throw new Error(`[Holo Redis] ${label} cannot include a database path in cluster mode.`);
588
- }
589
- if (!databaseSegment || !/^\d+$/.test(databaseSegment) || pathname !== databaseSegment) {
590
- throw new Error(`[Holo Redis] ${label} database path must be a single integer segment.`);
591
- }
592
- return Number.parseInt(databaseSegment, 10);
593
- } catch (error) {
594
- if (error instanceof Error && error.message.startsWith("[Holo Redis]")) {
595
- throw error;
596
- }
597
- return void 0;
598
- }
599
- }
600
- function normalizeRedisConnectionConfig(name, config2) {
601
- const url = normalizeOptionalRedisString(config2.url, `redis connection "${name}" url`);
602
- const clusters = normalizeRedisClusterNodes(name, config2.clusters);
603
- const socketPath = normalizeOptionalRedisSocketPath(config2.socketPath, `redis connection "${name}" socketPath`);
604
- const normalizedSocketPath = deriveNormalizedRedisSocketPath(socketPath, config2.host?.trim());
605
- const targetModeCount = [url, clusters, normalizedSocketPath].filter((value) => typeof value !== "undefined").length;
606
- if (targetModeCount > 1) {
607
- throw new Error(`[Holo Redis] redis connection "${name}" must configure exactly one target mode: url, clusters, or socketPath.`);
608
- }
609
- const host = config2.host?.trim() || normalizedSocketPath || DEFAULT_REDIS_HOST;
610
- const databaseFromUrl = parseRedisDatabaseFromUrl(url, {
611
- label: `redis connection "${name}" url`
612
- });
613
- const database = parseRedisInteger(config2.db ?? databaseFromUrl, DEFAULT_REDIS_DB, `redis connection "${name}" db`, {
614
- minimum: 0
615
- });
616
- if (typeof clusters !== "undefined" && database !== 0) {
617
- throw new Error(`[Holo Redis] redis connection "${name}" cannot select redis.db=${database} in cluster mode; Redis Cluster only supports database 0.`);
618
- }
619
- return Object.freeze({
620
- name,
621
- ...typeof url === "undefined" ? {} : { url },
622
- ...typeof clusters === "undefined" ? {} : { clusters },
623
- ...typeof normalizedSocketPath === "undefined" ? {} : { socketPath: normalizedSocketPath },
624
- host,
625
- port: parseRedisInteger(config2.port, DEFAULT_REDIS_PORT, `redis connection "${name}" port`, {
626
- minimum: 1
627
- }),
628
- username: config2.username?.trim() || void 0,
629
- password: config2.password?.trim() || void 0,
630
- db: database
631
- });
632
- }
633
- function normalizeRedisConnections(connections) {
634
- if (!connections || Object.keys(connections).length === 0) {
635
- return holoRedisDefaults.connections;
636
- }
637
- return Object.freeze(Object.fromEntries(
638
- Object.entries(connections).map(([name, config2]) => {
639
- const normalizedName = normalizeRedisConnectionName(name, "Redis connection name");
640
- return [normalizedName, normalizeRedisConnectionConfig(normalizedName, config2)];
641
- })
642
- ));
643
- }
644
- function resolveNormalizedRedisConnection(redisConfig, connectionName, label) {
645
- const { connections } = redisConfig;
646
- const resolved = connections[connectionName];
647
- if (!resolved) {
648
- throw new Error(`[Holo Redis] ${label} "${connectionName}" is not configured.`);
649
- }
650
- return resolved;
651
- }
652
- function parseSecurityInteger(value, fallback, label, options = {}) {
653
- return parseScopedInteger(value, fallback, "Holo Security", label, options);
654
- }
655
- function normalizeSecurityName(value, label) {
656
- return normalizeScopedName(value, "Holo Security", label);
657
- }
658
- function normalizeSecurityOptionalString(value) {
659
- const normalized = value?.trim();
660
- return normalized || void 0;
661
- }
662
- function normalizeCorsStringList(values, defaults, label) {
663
- if (!values) {
664
- return defaults;
665
- }
666
- return Object.freeze(values.map((value, index) => {
667
- const normalized = value.trim();
668
- if (!normalized) {
669
- throw new Error(`[Holo CORS] ${label} entry at index ${index} must be a non-empty string.`);
670
- }
671
- return normalized;
672
- }));
673
- }
674
- function normalizeCorsMethods(values) {
675
- return Object.freeze(normalizeCorsStringList(values, DEFAULT_CORS_METHODS, "methods").map((value) => value.toUpperCase()));
676
- }
677
- function normalizeCorsMaxAge(value) {
678
- return parseSecurityInteger(value ?? DEFAULT_CORS_MAX_AGE, DEFAULT_CORS_MAX_AGE, "cors maxAge", {
679
- minimum: 0
680
- });
681
- }
682
- function normalizeCorsConfig(config2 = {}) {
683
- return Object.freeze({
684
- paths: normalizeCorsStringList(config2.paths, DEFAULT_CORS_PATHS, "paths"),
685
- origins: normalizeCorsStringList(config2.origins, holoCorsDefaults.origins, "origins"),
686
- methods: normalizeCorsMethods(config2.methods),
687
- headers: normalizeCorsStringList(config2.headers, DEFAULT_CORS_HEADERS, "headers"),
688
- credentials: config2.credentials ?? holoCorsDefaults.credentials,
689
- maxAge: normalizeCorsMaxAge(config2.maxAge),
690
- statefulDomains: normalizeCorsStringList(config2.statefulDomains, holoCorsDefaults.statefulDomains, "statefulDomains")
691
- });
692
- }
693
- function normalizeSyncConnection(name, config2) {
694
- return Object.freeze({
695
- name,
696
- driver: "sync",
697
- queue: normalizeQueueName(config2.queue)
698
- });
699
- }
700
- function normalizeRedisConnection(name, config2, redisConfig) {
701
- const explicitConnectionName = config2.connection?.trim();
702
- const connectionName = explicitConnectionName || redisConfig?.default;
703
- if (!connectionName) {
704
- throw new Error(
705
- `[@holo-js/config] Queue Redis connection "${name}" requires a top-level redis config with a default connection or an explicit connection name.`
706
- );
707
- }
708
- if (!redisConfig) {
709
- throw new Error(
710
- `[@holo-js/config] Queue Redis connection "${name}" references shared Redis connection "${connectionName}" but no top-level redis config is loaded.`
711
- );
712
- }
713
- const resolvedRedisConnection = resolveNormalizedRedisConnection(
714
- redisConfig,
715
- connectionName,
716
- "Queue Redis connection"
717
- );
718
- return Object.freeze({
719
- name,
720
- driver: "redis",
721
- connection: resolvedRedisConnection.name,
722
- queue: normalizeQueueName(config2.queue),
723
- retryAfter: parseInteger(config2.retryAfter, DEFAULT_QUEUE_RETRY_AFTER, "Holo Queue", `queue connection "${name}" retryAfter`, {
724
- minimum: 0
725
- }),
726
- blockFor: parseInteger(config2.blockFor, DEFAULT_QUEUE_BLOCK_FOR, "Holo Queue", `queue connection "${name}" blockFor`, {
727
- minimum: 0
728
- }),
729
- redis: Object.freeze({
730
- ...typeof resolvedRedisConnection.url === "undefined" ? {} : { url: resolvedRedisConnection.url },
731
- ...typeof resolvedRedisConnection.clusters === "undefined" ? {} : { clusters: resolvedRedisConnection.clusters },
732
- host: resolvedRedisConnection.host,
733
- port: resolvedRedisConnection.port,
734
- password: resolvedRedisConnection.password,
735
- username: resolvedRedisConnection.username,
736
- db: resolvedRedisConnection.db
737
- })
738
- });
739
- }
740
- function normalizeDatabaseConnection(name, config2) {
741
- return Object.freeze({
742
- name,
743
- driver: "database",
744
- queue: normalizeQueueName(config2.queue),
745
- retryAfter: parseInteger(config2.retryAfter, DEFAULT_QUEUE_RETRY_AFTER, "Holo Queue", `queue connection "${name}" retryAfter`, {
746
- minimum: 0
747
- }),
748
- sleep: parseInteger(config2.sleep, DEFAULT_QUEUE_SLEEP, "Holo Queue", `queue connection "${name}" sleep`, {
749
- minimum: 0
750
- }),
751
- connection: config2.connection?.trim() || DEFAULT_FAILED_JOBS_CONNECTION,
752
- table: config2.table?.trim() || DEFAULT_DATABASE_QUEUE_TABLE
753
- });
754
- }
755
- function normalizeConnectionConfig(name, config2, redisConfig) {
756
- switch (config2.driver) {
757
- case "sync":
758
- return normalizeSyncConnection(name, config2);
759
- case "redis":
760
- return normalizeRedisConnection(name, config2, redisConfig);
761
- case "database":
762
- return normalizeDatabaseConnection(name, config2);
763
- default:
764
- throw new Error(`[Holo Queue] Unsupported queue driver "${String(config2.driver)}" on connection "${name}".`);
765
- }
766
- }
767
- function normalizeConnections(connections, redisConfig) {
768
- if (!connections || Object.keys(connections).length === 0) {
769
- return DEFAULT_QUEUE_CONFIG.connections;
770
- }
771
- const normalizedEntries = Object.entries(connections).map(([name, config2]) => {
772
- const normalizedName = normalizeConnectionName(name, "Holo Queue", "Queue connection name");
773
- return [normalizedName, normalizeConnectionConfig(normalizedName, config2, redisConfig)];
774
- });
775
- return Object.freeze(Object.fromEntries(normalizedEntries));
776
- }
777
- function normalizeFailedStore(config2) {
778
- if (config2 === false) {
779
- return false;
780
- }
781
- const normalized = config2 ?? DEFAULT_QUEUE_CONFIG.failed;
782
- if (normalized.driver && normalized.driver !== "database") {
783
- throw new Error(`[Holo Queue] Unsupported failed job store driver "${normalized.driver}".`);
784
- }
785
- return Object.freeze({
786
- driver: "database",
787
- connection: normalized.connection?.trim() || DEFAULT_FAILED_JOBS_CONNECTION,
788
- table: normalized.table?.trim() || DEFAULT_FAILED_JOBS_TABLE
789
- });
790
- }
791
- var holoQueueDefaultsNormalized = DEFAULT_QUEUE_CONFIG;
792
26
  function normalizeAppEnv(value, fallback = "development") {
793
27
  if (!value) {
794
28
  return fallback;
@@ -808,893 +42,13 @@ function normalizeAppConfig(config2 = {}) {
808
42
  url: config2.url ?? holoAppDefaults.url,
809
43
  debug: debug ?? holoAppDefaults.debug,
810
44
  env: normalizeAppEnv(config2.env, holoAppDefaults.env),
45
+ plugins: Object.freeze([...new Set((config2.plugins ?? []).map((plugin) => plugin.trim()).filter((plugin) => plugin.length > 0))]),
811
46
  paths: project.paths,
812
47
  models: project.models,
813
48
  migrations: project.migrations,
814
49
  seeders: project.seeders
815
50
  });
816
51
  }
817
- function normalizeSessionStoreConfig(name, config2, redisConfig) {
818
- switch (config2.driver) {
819
- case "database": {
820
- const databaseConfig = config2;
821
- return Object.freeze({
822
- name,
823
- driver: "database",
824
- connection: databaseConfig.connection?.trim() || DEFAULT_SESSION_DATABASE_CONNECTION,
825
- table: databaseConfig.table?.trim() || DEFAULT_SESSION_DATABASE_TABLE
826
- });
827
- }
828
- case "file": {
829
- const fileConfig = config2;
830
- return Object.freeze({
831
- name,
832
- driver: "file",
833
- path: fileConfig.path?.trim() || DEFAULT_SESSION_FILE_PATH
834
- });
835
- }
836
- case "redis": {
837
- const redisStoreConfig = config2;
838
- const configuredConnection = redisStoreConfig.connection?.trim();
839
- const connectionName = configuredConnection || redisConfig?.default;
840
- if (!connectionName) {
841
- throw new Error(
842
- `[@holo-js/config] Session Redis store "${name}" requires a top-level redis config with a default connection or an explicit connection name.`
843
- );
844
- }
845
- if (!redisConfig) {
846
- throw new Error(
847
- `[@holo-js/config] Session Redis store "${name}" references shared Redis connection "${connectionName}" but no top-level redis config is loaded.`
848
- );
849
- }
850
- const resolvedConnection = resolveNormalizedRedisConnection(
851
- redisConfig,
852
- connectionName,
853
- "Session Redis store"
854
- );
855
- return Object.freeze({
856
- name,
857
- driver: "redis",
858
- connection: resolvedConnection.name,
859
- ...typeof resolvedConnection.url === "undefined" ? {} : { url: resolvedConnection.url },
860
- ...typeof resolvedConnection.clusters === "undefined" ? {} : { clusters: resolvedConnection.clusters },
861
- host: resolvedConnection.host,
862
- port: resolvedConnection.port,
863
- username: resolvedConnection.username,
864
- password: resolvedConnection.password,
865
- db: resolvedConnection.db,
866
- prefix: redisStoreConfig.prefix?.trim() || ""
867
- });
868
- }
869
- default:
870
- throw new Error(`[Holo Session] Unsupported session store driver "${String(config2.driver)}" on store "${name}".`);
871
- }
872
- }
873
- function normalizeSessionConfig(config2 = {}, redisConfig) {
874
- const stores = !config2.stores || Object.keys(config2.stores).length === 0 ? holoSessionDefaults.stores : Object.freeze(Object.fromEntries(Object.entries(config2.stores).map(([name, store]) => {
875
- const normalizedName = normalizeConnectionName(name, "Holo Session", "Session store name");
876
- return [normalizedName, normalizeSessionStoreConfig(normalizedName, store, redisConfig)];
877
- })));
878
- const configuredDriver = config2.driver?.trim();
879
- const driver = configuredDriver || (DEFAULT_SESSION_DRIVER in stores ? DEFAULT_SESSION_DRIVER : void 0) || Object.keys(stores)[0] || DEFAULT_SESSION_DRIVER;
880
- if (!(driver in stores)) {
881
- throw new Error(`[Holo Session] default session driver "${driver}" is not configured.`);
882
- }
883
- const cookie = config2.cookie ?? {};
884
- const sameSite = cookie.sameSite ?? DEFAULT_SESSION_COOKIE_SAME_SITE;
885
- if (sameSite !== "lax" && sameSite !== "strict" && sameSite !== "none") {
886
- throw new Error(`[Holo Session] cookie sameSite must be "lax", "strict", or "none".`);
887
- }
888
- const idleTimeout = parseInteger(config2.idleTimeout, DEFAULT_SESSION_IDLE_TIMEOUT, "Holo Session", "session idleTimeout", {
889
- minimum: 0
890
- });
891
- const absoluteLifetime = parseInteger(
892
- config2.absoluteLifetime,
893
- DEFAULT_SESSION_ABSOLUTE_LIFETIME,
894
- "Holo Session",
895
- "session absoluteLifetime",
896
- {
897
- minimum: 0
898
- }
899
- );
900
- const rememberMeLifetime = parseInteger(
901
- config2.rememberMeLifetime,
902
- DEFAULT_SESSION_REMEMBER_ME_LIFETIME,
903
- "Holo Session",
904
- "session rememberMeLifetime",
905
- {
906
- minimum: 0
907
- }
908
- );
909
- return Object.freeze({
910
- driver,
911
- stores,
912
- cookie: Object.freeze({
913
- name: cookie.name?.trim() || DEFAULT_SESSION_COOKIE_NAME,
914
- path: cookie.path?.trim() || DEFAULT_SESSION_COOKIE_PATH,
915
- domain: cookie.domain?.trim() || void 0,
916
- secure: cookie.secure ?? false,
917
- httpOnly: cookie.httpOnly ?? true,
918
- sameSite,
919
- partitioned: cookie.partitioned ?? false,
920
- maxAge: parseInteger(cookie.maxAge, absoluteLifetime, "Holo Session", "session cookie maxAge", {
921
- minimum: 0
922
- })
923
- }),
924
- idleTimeout,
925
- absoluteLifetime,
926
- rememberMeLifetime
927
- });
928
- }
929
- function normalizeSecurityLimiter(name, config2) {
930
- const key = typeof config2.key === "function" ? config2.key : void 0;
931
- if (typeof config2.key !== "undefined" && typeof config2.key !== "function") {
932
- throw new Error(`[Holo Security] rate limiter "${name}" key resolver must be a function when provided.`);
933
- }
934
- return Object.freeze({
935
- name,
936
- maxAttempts: parseSecurityInteger(config2.maxAttempts, 0, `rate limiter "${name}" maxAttempts`, {
937
- minimum: 1
938
- }),
939
- decaySeconds: parseSecurityInteger(config2.decaySeconds, 0, `rate limiter "${name}" decaySeconds`, {
940
- minimum: 1
941
- }),
942
- ...key ? { key } : {}
943
- });
944
- }
945
- function normalizeSecurityRateLimitConfig(config2, redisConfig) {
946
- const driver = normalizeSecurityOptionalString(config2?.driver) || DEFAULT_SECURITY_RATE_LIMIT_DRIVER;
947
- if (driver !== "memory" && driver !== "file" && driver !== "redis") {
948
- throw new Error(`[Holo Security] Unsupported rate limit driver "${driver}".`);
949
- }
950
- const file = config2?.file ?? {};
951
- const redis = config2?.redis ?? {};
952
- const limiters = !config2?.limiters || Object.keys(config2.limiters).length === 0 ? holoSecurityDefaults.rateLimit.limiters : Object.freeze(Object.fromEntries(Object.entries(config2.limiters).map(([name, limiter]) => {
953
- const normalizedName = normalizeSecurityName(name, "Rate limiter name");
954
- return [normalizedName, normalizeSecurityLimiter(normalizedName, limiter)];
955
- })));
956
- return Object.freeze({
957
- driver,
958
- memory: Object.freeze({
959
- driver: "memory"
960
- }),
961
- file: Object.freeze({
962
- path: normalizeSecurityOptionalString(file.path) || DEFAULT_SECURITY_RATE_LIMIT_FILE_PATH
963
- }),
964
- redis: Object.freeze((() => {
965
- const connectionName = normalizeSecurityOptionalString(redis.connection) || redisConfig?.default || DEFAULT_SECURITY_RATE_LIMIT_REDIS_CONNECTION;
966
- const resolvedConnection = redisConfig ? resolveNormalizedRedisConnection(
967
- redisConfig,
968
- connectionName,
969
- "Security rate-limit Redis connection"
970
- ) : driver === "redis" ? (() => {
971
- throw new Error(
972
- `[@holo-js/config] Security rate-limit Redis config references shared Redis connection "${connectionName}" but no top-level redis config is loaded.`
973
- );
974
- })() : {
975
- name: connectionName,
976
- host: DEFAULT_REDIS_HOST,
977
- port: DEFAULT_REDIS_PORT,
978
- password: void 0,
979
- username: void 0,
980
- db: DEFAULT_REDIS_DB
981
- };
982
- return {
983
- ...typeof resolvedConnection.url === "undefined" ? {} : { url: resolvedConnection.url },
984
- ...typeof resolvedConnection.clusters === "undefined" ? {} : { clusters: resolvedConnection.clusters },
985
- host: resolvedConnection.host,
986
- port: resolvedConnection.port,
987
- password: resolvedConnection.password,
988
- username: resolvedConnection.username,
989
- db: resolvedConnection.db,
990
- connection: resolvedConnection.name,
991
- prefix: normalizeSecurityOptionalString(redis.prefix) || DEFAULT_SECURITY_RATE_LIMIT_REDIS_PREFIX
992
- };
993
- })()),
994
- limiters
995
- });
996
- }
997
- function normalizeSecurityConfig(config2 = {}, redisConfig) {
998
- const csrf = typeof config2.csrf === "boolean" ? { enabled: config2.csrf } : config2.csrf ?? {};
999
- const except = csrf.except ? Object.freeze(csrf.except.map((value, index) => {
1000
- const normalized = value.trim();
1001
- if (!normalized) {
1002
- throw new Error(`[Holo Security] csrf except entry at index ${index} must be a non-empty string.`);
1003
- }
1004
- return normalized;
1005
- })) : DEFAULT_SECURITY_CSRF_CONFIG.except;
1006
- return Object.freeze({
1007
- csrf: Object.freeze({
1008
- enabled: csrf.enabled ?? DEFAULT_SECURITY_CSRF_CONFIG.enabled,
1009
- field: normalizeSecurityOptionalString(csrf.field) || DEFAULT_SECURITY_CSRF_FIELD,
1010
- header: normalizeSecurityOptionalString(csrf.header) || DEFAULT_SECURITY_CSRF_HEADER,
1011
- cookie: normalizeSecurityOptionalString(csrf.cookie) || DEFAULT_SECURITY_CSRF_COOKIE,
1012
- except
1013
- }),
1014
- rateLimit: normalizeSecurityRateLimitConfig(config2.rateLimit, redisConfig)
1015
- });
1016
- }
1017
- function normalizeAuthProvider(name, config2) {
1018
- const identifiers = Object.freeze(
1019
- Array.from(new Set((config2.identifiers ?? DEFAULT_AUTH_IDENTIFIERS).map((value) => normalizeNonEmptyString(value, `[Holo Auth] provider "${name}" identifier entries must be non-empty strings.`))))
1020
- );
1021
- if (identifiers.length === 0) {
1022
- throw new Error(`[Holo Auth] provider "${name}" must declare at least one identifier.`);
1023
- }
1024
- return Object.freeze({
1025
- name,
1026
- model: normalizeNonEmptyString(config2.model, `[Holo Auth] provider "${name}" model must be a non-empty string.`),
1027
- identifiers
1028
- });
1029
- }
1030
- function normalizeAuthGuard(name, config2, providers) {
1031
- const provider = config2.provider?.trim() || DEFAULT_AUTH_PROVIDER;
1032
- if (!(provider in providers)) {
1033
- throw new Error(`[Holo Auth] guard "${name}" references unknown provider "${provider}".`);
1034
- }
1035
- if (config2.driver !== "session" && config2.driver !== "token") {
1036
- throw new Error(`[Holo Auth] Unsupported auth guard driver "${String(config2.driver)}" on guard "${name}".`);
1037
- }
1038
- return Object.freeze({
1039
- name,
1040
- driver: config2.driver,
1041
- provider
1042
- });
1043
- }
1044
- function normalizePasswordBroker(name, config2, providers) {
1045
- const provider = config2.provider?.trim() || DEFAULT_AUTH_PROVIDER;
1046
- if (!(provider in providers)) {
1047
- throw new Error(`[Holo Auth] password broker "${name}" references unknown provider "${provider}".`);
1048
- }
1049
- return Object.freeze({
1050
- name,
1051
- provider,
1052
- table: config2.table?.trim() || DEFAULT_AUTH_PASSWORD_RESET_TABLE,
1053
- expire: parseInteger(config2.expire, DEFAULT_AUTH_PASSWORD_EXPIRE, "Holo Auth", `auth password broker "${name}" expire`, {
1054
- minimum: 0
1055
- }),
1056
- throttle: parseInteger(config2.throttle, DEFAULT_AUTH_PASSWORD_THROTTLE, "Holo Auth", `auth password broker "${name}" throttle`, {
1057
- minimum: 0
1058
- }),
1059
- route: config2.route?.trim() || DEFAULT_AUTH_PASSWORD_RESET_ROUTE
1060
- });
1061
- }
1062
- function normalizeSocialProvider(name, config2, guards, providers) {
1063
- const guard = config2.guard?.trim();
1064
- if (guard && !(guard in guards)) {
1065
- throw new Error(`[Holo Auth] social provider "${name}" references unknown guard "${guard}".`);
1066
- }
1067
- const mapToProvider = config2.mapToProvider?.trim();
1068
- if (mapToProvider && !(mapToProvider in providers)) {
1069
- throw new Error(`[Holo Auth] social provider "${name}" references unknown provider "${mapToProvider}".`);
1070
- }
1071
- return Object.freeze({
1072
- name,
1073
- runtime: config2.runtime?.trim() || void 0,
1074
- clientId: config2.clientId?.trim() || void 0,
1075
- clientSecret: config2.clientSecret?.trim() || void 0,
1076
- redirectUri: config2.redirectUri?.trim() || void 0,
1077
- scopes: Object.freeze([...config2.scopes ?? []]),
1078
- guard,
1079
- mapToProvider,
1080
- encryptTokens: config2.encryptTokens === true
1081
- });
1082
- }
1083
- function normalizeWorkosProvider(name, config2, guards, providers) {
1084
- const guard = config2.guard?.trim();
1085
- if (guard && !(guard in guards)) {
1086
- throw new Error(`[Holo Auth] WorkOS provider "${name}" references unknown guard "${guard}".`);
1087
- }
1088
- const mapToProvider = config2.mapToProvider?.trim();
1089
- if (mapToProvider && !(mapToProvider in providers)) {
1090
- throw new Error(`[Holo Auth] WorkOS provider "${name}" references unknown provider "${mapToProvider}".`);
1091
- }
1092
- return Object.freeze({
1093
- name,
1094
- clientId: config2.clientId?.trim() || void 0,
1095
- apiKey: config2.apiKey?.trim() || void 0,
1096
- redirectUri: config2.redirectUri?.trim() || void 0,
1097
- sessionCookie: DEFAULT_WORKOS_SESSION_COOKIE,
1098
- guard,
1099
- mapToProvider
1100
- });
1101
- }
1102
- function isHostedIdentityStore(value) {
1103
- return Boolean(value) && typeof value === "object" && typeof value.findByProviderUserId === "function" && typeof value.findByUserId === "function" && typeof value.save === "function";
1104
- }
1105
- function isWorkosProviderConfig(value) {
1106
- return typeof value === "object" && value !== null && !isHostedIdentityStore(value);
1107
- }
1108
- function getWorkosProviderEntries(config2) {
1109
- if (!config2) {
1110
- return Object.freeze([]);
1111
- }
1112
- const entries = [];
1113
- for (const [name, value] of Object.entries(config2)) {
1114
- if (name === "provider") {
1115
- if (typeof value !== "undefined" && typeof value !== "string") {
1116
- throw new Error('[Holo Auth] WorkOS provider key "provider" is reserved for the default provider name.');
1117
- }
1118
- continue;
1119
- }
1120
- if (name === "identityStore") {
1121
- if (typeof value !== "undefined" && !isHostedIdentityStore(value)) {
1122
- throw new Error("[Holo Auth] WorkOS identityStore must implement the hosted identity store contract.");
1123
- }
1124
- continue;
1125
- }
1126
- if (!isWorkosProviderConfig(value)) {
1127
- throw new Error(`[Holo Auth] WorkOS provider "${name}" must be an object.`);
1128
- }
1129
- entries.push([name, value]);
1130
- }
1131
- return Object.freeze(entries);
1132
- }
1133
- function normalizeWorkosConfig(config2, guards, providers) {
1134
- const providerEntries = getWorkosProviderEntries(config2);
1135
- const provider = typeof config2?.provider === "string" ? config2.provider.trim() || void 0 : void 0;
1136
- if (providerEntries.length === 0) {
1137
- if (provider) {
1138
- throw new Error(`[Holo Auth] WorkOS provider "${provider}" is not configured.`);
1139
- }
1140
- if (config2?.identityStore) {
1141
- return Object.freeze({
1142
- identityStore: config2.identityStore
1143
- });
1144
- }
1145
- return holoAuthDefaults.workos;
1146
- }
1147
- const normalizedEntries = providerEntries.map(([name, providerConfig]) => {
1148
- const normalizedName = normalizeConnectionName(name, "Holo Auth", "Auth WorkOS provider name");
1149
- return [normalizedName, providerConfig];
1150
- });
1151
- if (provider && !normalizedEntries.some(([name]) => name === provider)) {
1152
- throw new Error(`[Holo Auth] WorkOS provider "${provider}" is not configured.`);
1153
- }
1154
- return Object.freeze({
1155
- ...typeof provider === "undefined" ? {} : { provider },
1156
- ...config2?.identityStore ? { identityStore: config2.identityStore } : {},
1157
- ...Object.fromEntries(normalizedEntries.map(([name, providerConfig]) => [
1158
- name,
1159
- normalizeWorkosProvider(name, providerConfig, guards, providers)
1160
- ]))
1161
- });
1162
- }
1163
- function normalizeClerkProvider(name, config2, guards, providers) {
1164
- const guard = config2.guard?.trim();
1165
- if (guard && !(guard in guards)) {
1166
- throw new Error(`[Holo Auth] Clerk provider "${name}" references unknown guard "${guard}".`);
1167
- }
1168
- const mapToProvider = config2.mapToProvider?.trim();
1169
- if (mapToProvider && !(mapToProvider in providers)) {
1170
- throw new Error(`[Holo Auth] Clerk provider "${name}" references unknown provider "${mapToProvider}".`);
1171
- }
1172
- const redirectUri = config2.redirectUri?.trim();
1173
- if (redirectUri) {
1174
- try {
1175
- new URL(redirectUri);
1176
- } catch {
1177
- throw new Error(`Invalid redirectUri in Clerk provider "${name}": ${redirectUri}`);
1178
- }
1179
- }
1180
- return Object.freeze({
1181
- name,
1182
- publishableKey: config2.publishableKey?.trim() || void 0,
1183
- secretKey: config2.secretKey?.trim() || void 0,
1184
- apiUrl: config2.apiUrl?.trim() || void 0,
1185
- frontendApi: config2.frontendApi?.trim() || void 0,
1186
- redirectUri: redirectUri || void 0,
1187
- sessionCookie: config2.sessionCookie?.trim() || DEFAULT_CLERK_SESSION_COOKIE,
1188
- authorizedParties: Object.freeze((config2.authorizedParties ?? []).map((value) => value.trim()).filter(Boolean)),
1189
- guard,
1190
- mapToProvider
1191
- });
1192
- }
1193
- function isClerkProviderConfig(value) {
1194
- if (!value || typeof value !== "object" || Array.isArray(value)) {
1195
- return false;
1196
- }
1197
- const candidate = value;
1198
- for (const key of ["publishableKey", "secretKey", "apiUrl", "frontendApi", "redirectUri", "sessionCookie", "guard", "mapToProvider"]) {
1199
- const field = candidate[key];
1200
- if (typeof field !== "undefined" && typeof field !== "string") {
1201
- return false;
1202
- }
1203
- }
1204
- return typeof candidate.authorizedParties === "undefined" || Array.isArray(candidate.authorizedParties) && candidate.authorizedParties.every((value2) => typeof value2 === "string");
1205
- }
1206
- function getClerkProviderEntries(config2) {
1207
- if (!config2) {
1208
- return Object.freeze([]);
1209
- }
1210
- const entries = [];
1211
- for (const [name, value] of Object.entries(config2)) {
1212
- if (name === "provider") {
1213
- if (typeof value !== "undefined" && typeof value !== "string") {
1214
- throw new Error('[Holo Auth] Clerk provider key "provider" is reserved for the default provider name.');
1215
- }
1216
- continue;
1217
- }
1218
- if (name === "identityStore") {
1219
- if (typeof value !== "undefined" && !isHostedIdentityStore(value)) {
1220
- throw new Error("[Holo Auth] Clerk identityStore must implement the hosted identity store contract.");
1221
- }
1222
- continue;
1223
- }
1224
- if (!isClerkProviderConfig(value)) {
1225
- throw new Error(`[Holo Auth] Clerk provider "${name}" must be a Clerk provider config object.`);
1226
- }
1227
- entries.push([name, value]);
1228
- }
1229
- return Object.freeze(entries);
1230
- }
1231
- function normalizeClerkConfig(config2, guards, providers) {
1232
- const providerEntries = getClerkProviderEntries(config2);
1233
- const provider = typeof config2?.provider === "string" ? config2.provider.trim() || void 0 : void 0;
1234
- if (providerEntries.length === 0) {
1235
- if (provider) {
1236
- throw new Error(`[Holo Auth] Clerk provider "${provider}" is not configured.`);
1237
- }
1238
- if (config2?.identityStore) {
1239
- return Object.freeze({
1240
- identityStore: config2.identityStore
1241
- });
1242
- }
1243
- return holoAuthDefaults.clerk;
1244
- }
1245
- const normalizedEntries = providerEntries.map(([name, providerConfig]) => {
1246
- const normalizedName = normalizeConnectionName(name, "Holo Auth", "Auth Clerk provider name");
1247
- return [normalizedName, providerConfig];
1248
- });
1249
- if (provider && !normalizedEntries.some(([name]) => name === provider)) {
1250
- throw new Error(`[Holo Auth] Clerk provider "${provider}" is not configured.`);
1251
- }
1252
- return Object.freeze({
1253
- ...typeof provider === "undefined" ? {} : { provider },
1254
- ...config2?.identityStore ? { identityStore: config2.identityStore } : {},
1255
- ...Object.fromEntries(normalizedEntries.map(([name, providerConfig]) => [
1256
- name,
1257
- normalizeClerkProvider(name, providerConfig, guards, providers)
1258
- ]))
1259
- });
1260
- }
1261
- function normalizeAuthConfig(config2 = {}, _options = {}) {
1262
- const providers = !config2.providers || Object.keys(config2.providers).length === 0 ? holoAuthDefaults.providers : Object.freeze(Object.fromEntries(Object.entries(config2.providers).map(([name, provider]) => {
1263
- const normalizedName = normalizeConnectionName(name, "Holo Auth", "Auth provider name");
1264
- return [normalizedName, normalizeAuthProvider(normalizedName, provider)];
1265
- })));
1266
- const guards = !config2.guards || Object.keys(config2.guards).length === 0 ? Object.freeze({
1267
- [DEFAULT_AUTH_GUARD]: normalizeAuthGuard(
1268
- DEFAULT_AUTH_GUARD,
1269
- holoAuthDefaults.guards[DEFAULT_AUTH_GUARD],
1270
- providers
1271
- )
1272
- }) : Object.freeze(Object.fromEntries(Object.entries(config2.guards).map(([name, guard]) => {
1273
- const normalizedName = normalizeConnectionName(name, "Holo Auth", "Auth guard name");
1274
- return [normalizedName, normalizeAuthGuard(normalizedName, guard, providers)];
1275
- })));
1276
- const passwords = !config2.passwords || Object.keys(config2.passwords).length === 0 ? Object.freeze({
1277
- [DEFAULT_AUTH_PASSWORD_BROKER]: normalizePasswordBroker(
1278
- DEFAULT_AUTH_PASSWORD_BROKER,
1279
- holoAuthDefaults.passwords[DEFAULT_AUTH_PASSWORD_BROKER],
1280
- providers
1281
- )
1282
- }) : Object.freeze(Object.fromEntries(Object.entries(config2.passwords).map(([name, broker]) => {
1283
- const normalizedName = normalizeConnectionName(name, "Holo Auth", "Auth password broker name");
1284
- return [normalizedName, normalizePasswordBroker(normalizedName, broker, providers)];
1285
- })));
1286
- const defaultGuard = config2.defaults?.guard?.trim() || DEFAULT_AUTH_GUARD;
1287
- if (!(defaultGuard in guards)) {
1288
- throw new Error(`[Holo Auth] default auth guard "${defaultGuard}" is not configured.`);
1289
- }
1290
- const defaultPasswords = config2.defaults?.passwords?.trim() || DEFAULT_AUTH_PASSWORD_BROKER;
1291
- if (!(defaultPasswords in passwords)) {
1292
- throw new Error(`[Holo Auth] default password broker "${defaultPasswords}" is not configured.`);
1293
- }
1294
- const social = !config2.social || Object.keys(config2.social).length === 0 ? holoAuthDefaults.social : Object.freeze(Object.fromEntries(Object.entries(config2.social).map(([name, provider]) => {
1295
- const normalizedName = normalizeConnectionName(name, "Holo Auth", "Auth social provider name");
1296
- return [normalizedName, normalizeSocialProvider(normalizedName, provider, guards, providers)];
1297
- })));
1298
- const workos = normalizeWorkosConfig(config2.workos, guards, providers);
1299
- const clerk = normalizeClerkConfig(config2.clerk, guards, providers);
1300
- return Object.freeze({
1301
- defaults: Object.freeze({
1302
- guard: defaultGuard,
1303
- passwords: defaultPasswords
1304
- }),
1305
- guards,
1306
- providers,
1307
- passwords,
1308
- emailVerification: Object.freeze({
1309
- required: typeof config2.emailVerification === "boolean" ? config2.emailVerification : config2.emailVerification?.required ?? false,
1310
- route: typeof config2.emailVerification === "boolean" ? DEFAULT_AUTH_EMAIL_VERIFICATION_ROUTE : config2.emailVerification?.route?.trim() || DEFAULT_AUTH_EMAIL_VERIFICATION_ROUTE
1311
- }),
1312
- personalAccessTokens: Object.freeze({
1313
- defaultAbilities: Object.freeze([...config2.personalAccessTokens?.defaultAbilities ?? holoAuthDefaults.personalAccessTokens.defaultAbilities])
1314
- }),
1315
- socialEncryptionKey: config2.socialEncryptionKey?.trim() || _options.appKey?.trim() || void 0,
1316
- social,
1317
- workos,
1318
- clerk
1319
- });
1320
- }
1321
- function normalizeDatabaseConfig(config2 = {}) {
1322
- const configuredConnections = config2.connections;
1323
- const connections = configuredConnections && Object.keys(configuredConnections).length > 0 ? Object.freeze({ ...configuredConnections }) : holoDatabaseDefaults.connections;
1324
- const defaultConnection = config2.defaultConnection ?? Object.keys(connections)[0];
1325
- return Object.freeze({
1326
- /* v8 ignore next */
1327
- defaultConnection: defaultConnection ?? "default",
1328
- connections
1329
- });
1330
- }
1331
- function normalizeStorageConfig(config2 = {}) {
1332
- return Object.freeze({
1333
- defaultDisk: config2.defaultDisk ?? holoStorageDefaults.defaultDisk,
1334
- routePrefix: config2.routePrefix ?? holoStorageDefaults.routePrefix,
1335
- disks: Object.freeze({
1336
- ...holoStorageDefaults.disks,
1337
- ...config2.disks ?? {}
1338
- })
1339
- });
1340
- }
1341
- function normalizeOptionalBroadcastString(value, label) {
1342
- if (typeof value === "undefined") {
1343
- return void 0;
1344
- }
1345
- const normalized = String(value).trim();
1346
- if (!normalized) {
1347
- throw new Error(`[Holo Broadcast] ${label} must be a non-empty string when provided.`);
1348
- }
1349
- return normalized;
1350
- }
1351
- function normalizeBroadcastPort(value, fallback, label) {
1352
- const normalized = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value.trim()) : fallback;
1353
- if (!Number.isInteger(normalized) || normalized <= 0) {
1354
- throw new Error(`[Holo Broadcast] ${label} must be a positive integer.`);
1355
- }
1356
- return normalized;
1357
- }
1358
- function normalizeBroadcastScheme(value, fallback, label) {
1359
- const normalized = normalizeOptionalBroadcastString(value, label)?.toLowerCase();
1360
- if (typeof normalized === "undefined") {
1361
- return fallback;
1362
- }
1363
- if (normalized !== "http" && normalized !== "https") {
1364
- throw new Error(`[Holo Broadcast] ${label} must be "http" or "https".`);
1365
- }
1366
- return normalized;
1367
- }
1368
- function normalizeBroadcastConnectionOptions(options, fallbackHost, label) {
1369
- const scheme = normalizeBroadcastScheme(
1370
- options?.scheme,
1371
- options?.useTLS === false ? "http" : "https",
1372
- `${label} scheme`
1373
- );
1374
- const resolvedFallbackPort = scheme === "http" ? DEFAULT_BROADCAST_HTTP_PORT : DEFAULT_BROADCAST_HTTPS_PORT;
1375
- return Object.freeze({
1376
- host: normalizeOptionalBroadcastString(options?.host, `${label} host`) ?? fallbackHost,
1377
- port: normalizeBroadcastPort(options?.port, resolvedFallbackPort, `${label} port`),
1378
- scheme,
1379
- useTLS: options?.useTLS ?? scheme === "https",
1380
- cluster: normalizeOptionalBroadcastString(options?.cluster, `${label} cluster`) ?? void 0
1381
- });
1382
- }
1383
- function normalizeBroadcastWorkerConfig(worker, connectionOptions, configuredConnectionPort) {
1384
- const scaling = worker?.scaling;
1385
- const publicScheme = normalizeBroadcastScheme(worker?.publicScheme, connectionOptions?.scheme ?? "https", "Broadcast worker public scheme");
1386
- const fallbackPort = typeof configuredConnectionPort === "undefined" ? DEFAULT_BROADCAST_WORKER_PORT : normalizeBroadcastPort(configuredConnectionPort, DEFAULT_BROADCAST_WORKER_PORT, "Broadcast worker port");
1387
- if (scaling && scaling.driver !== "redis") {
1388
- throw new Error('[Holo Broadcast] Broadcast worker scaling driver must be "redis".');
1389
- }
1390
- return Object.freeze({
1391
- host: normalizeOptionalBroadcastString(worker?.host, "Broadcast worker host") ?? DEFAULT_BROADCAST_WORKER_HOST,
1392
- port: normalizeBroadcastPort(worker?.port, fallbackPort, "Broadcast worker port"),
1393
- path: normalizeOptionalBroadcastString(worker?.path, "Broadcast worker path") ?? DEFAULT_BROADCAST_WORKER_PATH,
1394
- publicHost: normalizeOptionalBroadcastString(worker?.publicHost, "Broadcast worker public host") ?? void 0,
1395
- publicPort: typeof worker?.publicPort === "undefined" ? publicScheme === "http" ? DEFAULT_BROADCAST_HTTP_PORT : void 0 : normalizeBroadcastPort(
1396
- worker.publicPort,
1397
- /* v8 ignore next -- defensive HTTPS port default; tests only exercise HTTP scheme */
1398
- publicScheme === "http" ? DEFAULT_BROADCAST_HTTP_PORT : DEFAULT_BROADCAST_HTTPS_PORT,
1399
- "Broadcast worker public port"
1400
- ),
1401
- publicScheme,
1402
- healthPath: normalizeOptionalBroadcastString(worker?.healthPath, "Broadcast worker health path") ?? DEFAULT_BROADCAST_HEALTH_PATH,
1403
- statsPath: normalizeOptionalBroadcastString(worker?.statsPath, "Broadcast worker stats path") ?? DEFAULT_BROADCAST_STATS_PATH,
1404
- scaling: scaling && typeof scaling === "object" ? Object.freeze({
1405
- driver: "redis",
1406
- connection: normalizeOptionalBroadcastString(scaling.connection, "Broadcast worker scaling connection") ?? "default"
1407
- }) : holoBroadcastDefaults.worker.scaling
1408
- });
1409
- }
1410
- function normalizeBroadcastConnection(name, connection) {
1411
- const normalizedName = normalizeOptionalBroadcastString(name, "Broadcast connection name");
1412
- const driver = normalizeOptionalBroadcastString(connection.driver, `Broadcast connection "${name}" driver`);
1413
- if (!normalizedName || !driver) {
1414
- throw new Error("[Holo Broadcast] Broadcast connections must define a name and driver.");
1415
- }
1416
- const clientOptions = Object.freeze({
1417
- ...connection.clientOptions ?? {}
1418
- });
1419
- if (driver === "holo") {
1420
- return Object.freeze({
1421
- name: normalizedName,
1422
- driver: "holo",
1423
- key: normalizeOptionalBroadcastString(connection.key, `Broadcast connection "${name}" key`) ?? (() => {
1424
- throw new Error(`[Holo Broadcast] Broadcast connection "${name}" must define a key.`);
1425
- })(),
1426
- secret: normalizeOptionalBroadcastString(connection.secret, `Broadcast connection "${name}" secret`) ?? (() => {
1427
- throw new Error(`[Holo Broadcast] Broadcast connection "${name}" must define a secret.`);
1428
- })(),
1429
- appId: normalizeOptionalBroadcastString(connection.appId, `Broadcast connection "${name}" appId`) ?? (() => {
1430
- throw new Error(`[Holo Broadcast] Broadcast connection "${name}" must define an appId.`);
1431
- })(),
1432
- options: normalizeBroadcastConnectionOptions(connection.options, DEFAULT_BROADCAST_HOST, `Broadcast connection "${name}" options`),
1433
- clientOptions
1434
- });
1435
- }
1436
- if (driver === "pusher") {
1437
- const cluster = normalizeOptionalBroadcastString(connection.options?.cluster, `Broadcast connection "${name}" cluster`) ?? void 0;
1438
- return Object.freeze({
1439
- name: normalizedName,
1440
- driver: "pusher",
1441
- key: normalizeOptionalBroadcastString(connection.key, `Broadcast connection "${name}" key`) ?? (() => {
1442
- throw new Error(`[Holo Broadcast] Broadcast connection "${name}" must define a key.`);
1443
- })(),
1444
- secret: normalizeOptionalBroadcastString(connection.secret, `Broadcast connection "${name}" secret`) ?? (() => {
1445
- throw new Error(`[Holo Broadcast] Broadcast connection "${name}" must define a secret.`);
1446
- })(),
1447
- appId: normalizeOptionalBroadcastString(connection.appId, `Broadcast connection "${name}" appId`) ?? (() => {
1448
- throw new Error(`[Holo Broadcast] Broadcast connection "${name}" must define an appId.`);
1449
- })(),
1450
- options: normalizeBroadcastConnectionOptions(
1451
- {
1452
- ...connection.options,
1453
- cluster
1454
- },
1455
- normalizeOptionalBroadcastString(connection.options?.host, `Broadcast connection "${name}" host`) ?? (cluster ? `api-${cluster}.pusher.com` : "api-mt1.pusher.com"),
1456
- `Broadcast connection "${name}" options`
1457
- ),
1458
- clientOptions
1459
- });
1460
- }
1461
- if (driver === "log") {
1462
- return Object.freeze({
1463
- name: normalizedName,
1464
- driver: "log",
1465
- clientOptions
1466
- });
1467
- }
1468
- if (driver === "null") {
1469
- return Object.freeze({
1470
- name: normalizedName,
1471
- driver: "null",
1472
- clientOptions
1473
- });
1474
- }
1475
- if (driver === "ably") {
1476
- throw new Error('[Holo Broadcast] Broadcast driver "ably" is not supported yet.');
1477
- }
1478
- const {
1479
- driver: _ignoredDriver,
1480
- clientOptions: _ignoredClientOptions,
1481
- ...customConfig
1482
- } = connection;
1483
- return Object.freeze({
1484
- driver,
1485
- clientOptions,
1486
- ...customConfig,
1487
- name: normalizedName
1488
- });
1489
- }
1490
- function normalizeBroadcastConfig(config2 = {}) {
1491
- const normalizedConnections = Object.fromEntries(
1492
- Object.entries(config2.connections ?? holoBroadcastDefaults.connections).map(([name, connection]) => [name, normalizeBroadcastConnection(name, connection)])
1493
- );
1494
- const defaultConnection = normalizeOptionalBroadcastString(config2.default, "Default broadcast connection") ?? holoBroadcastDefaults.default;
1495
- if (!normalizedConnections[defaultConnection]) {
1496
- throw new Error(
1497
- `[Holo Broadcast] default broadcast connection "${defaultConnection}" is not configured. Available connections: ${Object.keys(normalizedConnections).join(", ")}`
1498
- );
1499
- }
1500
- const defaultBroadcastConnection = normalizedConnections[defaultConnection];
1501
- const defaultHoloConnectionOptions = defaultBroadcastConnection && defaultBroadcastConnection.driver === "holo" && "options" in defaultBroadcastConnection ? defaultBroadcastConnection.options : void 0;
1502
- const configuredDefaultConnection = config2.connections?.[defaultConnection];
1503
- const configuredDefaultHoloConnectionPort = configuredDefaultConnection?.driver === "holo" ? configuredDefaultConnection.options?.port : void 0;
1504
- return Object.freeze({
1505
- default: defaultConnection,
1506
- connections: Object.freeze(normalizedConnections),
1507
- worker: normalizeBroadcastWorkerConfig(config2.worker, defaultHoloConnectionOptions, configuredDefaultHoloConnectionPort)
1508
- });
1509
- }
1510
- function normalizeOptionalMailString(value, label) {
1511
- if (typeof value === "undefined") {
1512
- return void 0;
1513
- }
1514
- const normalized = value.trim();
1515
- if (!normalized) {
1516
- throw new Error(`[Holo Mail] ${label} must be a non-empty string when provided.`);
1517
- }
1518
- return normalized;
1519
- }
1520
- function isValidMailAddress(email) {
1521
- if (email.includes(" ")) {
1522
- return false;
1523
- }
1524
- const parts = email.split("@");
1525
- return parts.length === 2 && parts[0].length > 0 && parts[1].length > 0;
1526
- }
1527
- function normalizeMailAddress(address, label) {
1528
- if (!address) {
1529
- return void 0;
1530
- }
1531
- const email = normalizeOptionalMailString(address.email, `${label} email`)?.toLowerCase();
1532
- if (!email || !isValidMailAddress(email)) {
1533
- throw new Error(`[Holo Mail] ${label} email must be a valid email address.`);
1534
- }
1535
- const name = normalizeOptionalMailString(address.name, `${label} name`);
1536
- return Object.freeze({
1537
- email,
1538
- ...name ? { name } : {}
1539
- });
1540
- }
1541
- function normalizeMailQueueConfig(queue, fallback = holoMailDefaults.queue) {
1542
- return Object.freeze({
1543
- queued: queue?.queued ?? fallback.queued,
1544
- connection: normalizeOptionalMailString(queue?.connection, "Mail queue connection") ?? fallback.connection,
1545
- queue: normalizeOptionalMailString(queue?.queue, "Mail queue name") ?? fallback.queue,
1546
- afterCommit: queue?.afterCommit ?? fallback.afterCommit
1547
- });
1548
- }
1549
- function normalizeMailPreviewEnvironments(environments) {
1550
- if (typeof environments === "undefined") {
1551
- return holoMailDefaults.preview.allowedEnvironments;
1552
- }
1553
- const normalized = /* @__PURE__ */ new Set();
1554
- for (const environment of environments) {
1555
- if (environment !== "development" && environment !== "production" && environment !== "test") {
1556
- throw new Error("[Holo Mail] Mail preview environments must be development, production, or test.");
1557
- }
1558
- normalized.add(environment);
1559
- }
1560
- return Object.freeze([...normalized]);
1561
- }
1562
- function normalizeMailMailerConfig(name, config2, fallback) {
1563
- const normalizedName = normalizeOptionalMailString(name, "Mail mailer name");
1564
- const driver = normalizeOptionalMailString(config2.driver, `Mail mailer "${name}" driver`);
1565
- if (!normalizedName || !driver) {
1566
- throw new Error("[Holo Mail] Mailers must define a name and driver.");
1567
- }
1568
- const base = {
1569
- name: normalizedName,
1570
- driver,
1571
- from: normalizeMailAddress(config2.from, `Mail mailer "${name}" from`) ?? fallback?.from,
1572
- replyTo: normalizeMailAddress(config2.replyTo, `Mail mailer "${name}" replyTo`) ?? fallback?.replyTo,
1573
- queue: normalizeMailQueueConfig(config2.queue, fallback?.queue ?? holoMailDefaults.queue)
1574
- };
1575
- if (driver === "preview") {
1576
- const previewFallback = fallback?.driver === "preview" ? fallback : void 0;
1577
- return Object.freeze({
1578
- ...base,
1579
- driver: "preview",
1580
- path: normalizeOptionalMailString(config2.path, `Mail mailer "${name}" preview path`) ?? previewFallback?.path ?? DEFAULT_MAIL_PREVIEW_PATH
1581
- });
1582
- }
1583
- if (driver === "log") {
1584
- const logFallback = fallback?.driver === "log" ? fallback : void 0;
1585
- return Object.freeze({
1586
- ...base,
1587
- driver: "log",
1588
- logBodies: config2.logBodies ?? logFallback?.logBodies ?? false
1589
- });
1590
- }
1591
- if (driver === "fake") {
1592
- return Object.freeze({
1593
- ...base,
1594
- driver: "fake"
1595
- });
1596
- }
1597
- if (driver === "smtp") {
1598
- const smtpFallback = fallback?.driver === "smtp" ? fallback : void 0;
1599
- const rawPort = config2.port;
1600
- const normalizedPort = typeof rawPort === "number" ? rawPort : typeof rawPort === "string" && rawPort.trim() ? Number(rawPort.trim()) : smtpFallback?.port ?? DEFAULT_SMTP_PORT;
1601
- if (!Number.isFinite(normalizedPort) || normalizedPort <= 0) {
1602
- throw new Error(`[Holo Mail] Mail mailer "${name}" SMTP port must be a positive number.`);
1603
- }
1604
- return Object.freeze({
1605
- ...base,
1606
- driver: "smtp",
1607
- host: normalizeOptionalMailString(config2.host, `Mail mailer "${name}" SMTP host`) ?? smtpFallback?.host ?? DEFAULT_SMTP_HOST,
1608
- port: normalizedPort,
1609
- secure: config2.secure ?? smtpFallback?.secure ?? false,
1610
- user: normalizeOptionalMailString(config2.user, `Mail mailer "${name}" SMTP user`) ?? smtpFallback?.user ?? void 0,
1611
- password: normalizeOptionalMailString(config2.password, `Mail mailer "${name}" SMTP password`) ?? smtpFallback?.password ?? void 0
1612
- });
1613
- }
1614
- const customFields = Object.fromEntries(
1615
- Object.entries(config2).filter(([key]) => key !== "driver" && key !== "from" && key !== "replyTo" && key !== "queue")
1616
- );
1617
- return Object.freeze({
1618
- ...base,
1619
- ...customFields
1620
- });
1621
- }
1622
- function normalizeMailConfig(config2 = {}) {
1623
- const mergedMailers = {
1624
- ...holoMailDefaults.mailers
1625
- };
1626
- for (const [name, mailer] of Object.entries(config2.mailers ?? {})) {
1627
- mergedMailers[name] = normalizeMailMailerConfig(name, mailer, mergedMailers[name]);
1628
- }
1629
- const defaultMailer = normalizeOptionalMailString(config2.default, "Default mailer") ?? holoMailDefaults.default;
1630
- if (!mergedMailers[defaultMailer]) {
1631
- throw new Error(
1632
- `[Holo Mail] default mailer "${defaultMailer}" is not configured. Available mailers: ${Object.keys(mergedMailers).join(", ")}`
1633
- );
1634
- }
1635
- return Object.freeze({
1636
- default: defaultMailer,
1637
- from: normalizeMailAddress(config2.from, "Mail from") ?? holoMailDefaults.from,
1638
- replyTo: normalizeMailAddress(config2.replyTo, "Mail replyTo") ?? holoMailDefaults.replyTo,
1639
- queue: normalizeMailQueueConfig(config2.queue),
1640
- preview: Object.freeze({
1641
- allowedEnvironments: normalizeMailPreviewEnvironments(config2.preview?.allowedEnvironments)
1642
- }),
1643
- markdown: Object.freeze({
1644
- wrapper: normalizeOptionalMailString(config2.markdown?.wrapper, "Mail markdown wrapper") ?? holoMailDefaults.markdown.wrapper
1645
- }),
1646
- mailers: Object.freeze(mergedMailers)
1647
- });
1648
- }
1649
- function normalizeOptionalNotificationsString(value, label) {
1650
- if (typeof value === "undefined") {
1651
- return void 0;
1652
- }
1653
- const normalized = value.trim();
1654
- if (!normalized) {
1655
- throw new Error(`[Holo Notifications] ${label} must be a non-empty string when provided.`);
1656
- }
1657
- return normalized;
1658
- }
1659
- function normalizeNotificationsConfig(config2 = {}) {
1660
- return Object.freeze({
1661
- table: normalizeOptionalNotificationsString(config2.table, "Notifications table") ?? DEFAULT_NOTIFICATIONS_TABLE,
1662
- queue: Object.freeze({
1663
- connection: normalizeOptionalNotificationsString(config2.queue?.connection, "Notifications queue connection"),
1664
- queue: normalizeOptionalNotificationsString(config2.queue?.queue, "Notifications queue name"),
1665
- afterCommit: config2.queue?.afterCommit === true
1666
- })
1667
- });
1668
- }
1669
- function normalizeRedisConfig(config2 = {}) {
1670
- const connections = normalizeRedisConnections(config2.connections);
1671
- const connectionNames = Object.keys(connections);
1672
- const defaultConnection = config2.default?.trim() || connectionNames[0];
1673
- if (!connections[defaultConnection]) {
1674
- throw new Error(
1675
- `[Holo Redis] default redis connection "${defaultConnection}" is not configured. Available connections: ${connectionNames.join(", ")}`
1676
- );
1677
- }
1678
- return Object.freeze({
1679
- default: defaultConnection,
1680
- connections
1681
- });
1682
- }
1683
- function normalizeQueueConfigForHolo(config2 = {}, redisConfig) {
1684
- const connections = normalizeConnections(config2.connections, redisConfig);
1685
- const connectionNames = Object.keys(connections);
1686
- const defaultConnection = config2.default?.trim() || connectionNames[0];
1687
- if (!connections[defaultConnection]) {
1688
- throw new Error(
1689
- `[Holo Queue] default queue connection "${defaultConnection}" is not configured. Available connections: ${connectionNames.join(", ")}`
1690
- );
1691
- }
1692
- return Object.freeze({
1693
- default: defaultConnection,
1694
- failed: normalizeFailedStore(config2.failed),
1695
- connections
1696
- });
1697
- }
1698
52
 
1699
53
  // src/access.ts
1700
54
  function isObject(value) {
@@ -1918,6 +272,9 @@ function resolveEnvPlaceholders(value, envValues) {
1918
272
  import { mkdir, readFile as readFile2, readdir, rm, writeFile } from "fs/promises";
1919
273
  import { basename as basename2, dirname, extname, join as join2, resolve as resolve2 } from "path";
1920
274
  import { pathToFileURL } from "url";
275
+ import {
276
+ normalizeRedisConfig
277
+ } from "@holo-js/kernel";
1921
278
  var CONFIG_EXTENSION_PRIORITY = [".ts", ".mts", ".js", ".mjs", ".cts", ".cjs"];
1922
279
  var SUPPORTED_CONFIG_EXTENSIONS = new Set(CONFIG_EXTENSION_PRIORITY);
1923
280
  var HOLO_CONFIG_CACHE_VERSION = 3;
@@ -2066,41 +423,11 @@ function mergeLoadedFiles(cachedFiles, liveFiles, deferredConfigNames) {
2066
423
  function normalizeLoadedConfig(rawConfig, options) {
2067
424
  const resolvedRawConfig = resolveEnvPlaceholders(rawConfig, options.environment.values);
2068
425
  const app = normalizeAppConfig(resolvedRawConfig.app);
2069
- const database = normalizeDatabaseConfig(resolvedRawConfig.database);
2070
426
  const redis = normalizeRedisConfig(resolvedRawConfig.redis);
2071
- const resolvedRedisConfig = typeof resolvedRawConfig.redis === "undefined" ? void 0 : redis;
2072
- const cache = normalizeCacheConfig(resolvedRawConfig.cache, {
2073
- database,
2074
- redis: resolvedRedisConfig
2075
- });
2076
- const cors = normalizeCorsConfig(resolvedRawConfig.cors);
2077
- const storage = normalizeStorageConfig(resolvedRawConfig.storage);
2078
- const queue = normalizeQueueConfigForHolo(resolvedRawConfig.queue, resolvedRedisConfig);
2079
- const broadcast = normalizeBroadcastConfig(resolvedRawConfig.broadcast);
2080
- const mail = normalizeMailConfig(resolvedRawConfig.mail);
2081
- const notifications = normalizeNotificationsConfig(resolvedRawConfig.notifications);
2082
- const media = Object.freeze({ ...resolvedRawConfig.media ?? {} });
2083
- const session = normalizeSessionConfig(resolvedRawConfig.session, resolvedRedisConfig);
2084
- const security = normalizeSecurityConfig(resolvedRawConfig.security, resolvedRedisConfig);
2085
- const auth = normalizeAuthConfig(resolvedRawConfig.auth, {
2086
- appKey: app.key
2087
- });
2088
- const firstPartyConfig = {
427
+ const firstPartyConfig = composeRegisteredConfig(resolvedRawConfig, {
2089
428
  app,
2090
- database,
2091
- redis,
2092
- cache,
2093
- cors,
2094
- storage,
2095
- queue,
2096
- broadcast,
2097
- mail,
2098
- notifications,
2099
- media,
2100
- session,
2101
- security,
2102
- auth
2103
- };
429
+ redis
430
+ });
2104
431
  const firstPartyConfigKeys = new Set(Object.keys(firstPartyConfig));
2105
432
  const customEntries = Object.entries(resolvedRawConfig).filter(([key]) => {
2106
433
  return !firstPartyConfigKeys.has(key);
@@ -2153,8 +480,11 @@ function splitCacheableConfig(rawConfig) {
2153
480
  function getDeferredConfigNames(payload) {
2154
481
  return Array.isArray(payload.deferredConfigNames) ? payload.deferredConfigNames : Object.freeze([]);
2155
482
  }
483
+ function getNormalizerConfigNames(payload) {
484
+ return payload.normalizerConfigNames ?? Object.freeze([]);
485
+ }
2156
486
  function isCachePayload(value) {
2157
- return isObject2(value) && (value.version === 1 || value.version === HOLO_CONFIG_CACHE_VERSION) && isObject2(value.environment) && typeof value.environment.name === "string" && Array.isArray(value.environment.loadedFiles) && Array.isArray(value.environment.warnings) && Array.isArray(value.configFiles) && isObject2(value.config) && (typeof value.deferredConfigNames === "undefined" || Array.isArray(value.deferredConfigNames));
487
+ return isObject2(value) && (value.version === 1 || value.version === HOLO_CONFIG_CACHE_VERSION) && isObject2(value.environment) && typeof value.environment.name === "string" && Array.isArray(value.environment.loadedFiles) && Array.isArray(value.environment.warnings) && Array.isArray(value.configFiles) && isObject2(value.config) && (typeof value.deferredConfigNames === "undefined" || Array.isArray(value.deferredConfigNames)) && (typeof value.normalizerConfigNames === "undefined" || Array.isArray(value.normalizerConfigNames));
2158
488
  }
2159
489
  function resolveConfigCachePath(projectRoot) {
2160
490
  return join2(resolve2(projectRoot), HOLO_CONFIG_CACHE_PATH);
@@ -2197,7 +527,8 @@ async function writeConfigCache(projectRoot, options = {}) {
2197
527
  },
2198
528
  configFiles: loadedFiles,
2199
529
  config: cacheableConfig,
2200
- deferredConfigNames
530
+ deferredConfigNames,
531
+ normalizerConfigNames: configRegistryInternals.getRegisteredNames().filter((name) => Object.hasOwn(rawConfig, name))
2201
532
  }, null, 2)}
2202
533
  `;
2203
534
  await writeFileIfChanged(cachePath, contents);
@@ -2228,6 +559,12 @@ async function loadConfigDirectory(projectRoot, options = {}) {
2228
559
  const deferredConfigNames = getDeferredConfigNames(cached);
2229
560
  let rawConfig2 = cached.config;
2230
561
  let loadedFiles2 = cached.configFiles;
562
+ const cachedConfigNames = getNormalizerConfigNames(cached).filter((name) => !deferredConfigNames.includes(name));
563
+ if (cachedConfigNames.length > 0) {
564
+ await collectRawConfig(configDir, environment2.values, {
565
+ onlyConfigNames: cachedConfigNames
566
+ });
567
+ }
2231
568
  if (deferredConfigNames.length > 0) {
2232
569
  const live = await collectRawConfig(configDir, environment2.values, {
2233
570
  onlyConfigNames: deferredConfigNames
@@ -2261,106 +598,34 @@ function defineConfig(config2) {
2261
598
  function defineAppConfig(config2) {
2262
599
  return defineConfig(config2);
2263
600
  }
2264
- function defineDatabaseConfig(config2) {
2265
- return defineConfig(config2);
2266
- }
2267
- function defineRedisConfig(config2) {
2268
- return defineConfig(config2);
2269
- }
2270
- function defineCacheConfig(config2) {
2271
- return defineConfig(config2);
2272
- }
2273
- function defineStorageConfig(config2) {
2274
- return defineConfig(config2);
2275
- }
2276
- function defineQueueConfig(config2) {
2277
- return defineConfig(config2);
2278
- }
2279
- function defineBroadcastConfig(config2) {
2280
- return defineConfig(config2);
2281
- }
2282
- function defineMailConfig(config2) {
2283
- return defineConfig(config2);
2284
- }
2285
- function defineNotificationsConfig(config2) {
2286
- return defineConfig(config2);
2287
- }
2288
- function defineMediaConfig(config2) {
2289
- return defineConfig(config2);
2290
- }
2291
- function defineSessionConfig(config2) {
2292
- return defineConfig(config2);
2293
- }
2294
- function defineSecurityConfig(config2) {
2295
- return defineConfig(config2);
2296
- }
2297
- function defineCorsConfig(config2) {
2298
- return defineConfig(config2);
2299
- }
2300
- function defineAuthConfig(config2) {
2301
- return defineConfig(config2);
2302
- }
2303
601
  var loaderInternals = {
2304
602
  getDeferredConfigNames,
603
+ getNormalizerConfigNames,
2305
604
  getConfigExtensionPriority,
2306
605
  resolveConfigExport,
2307
606
  splitCacheableConfig
2308
607
  };
2309
608
  export {
2310
609
  DEFAULT_APP_NAME,
2311
- DEFAULT_SESSION_COOKIE_NAME,
2312
610
  clearConfigCache,
611
+ composeRegisteredConfig,
2313
612
  config,
613
+ configRegistryInternals,
2314
614
  configureConfigRuntime,
2315
615
  configureEnvRuntime,
2316
616
  createConfigAccessors,
2317
617
  defineAppConfig,
2318
- defineAuthConfig,
2319
- defineBroadcastConfig,
2320
- defineCacheConfig,
2321
618
  defineConfig,
2322
- defineCorsConfig,
2323
- defineDatabaseConfig,
2324
- defineMailConfig,
2325
- defineMediaConfig,
2326
- defineNotificationsConfig,
2327
- defineQueueConfig,
2328
- defineRedisConfig,
2329
- defineSecurityConfig,
2330
- defineSessionConfig,
2331
- defineStorageConfig,
2332
619
  env,
2333
620
  holoAppDefaults,
2334
- holoAuthDefaults,
2335
- holoBroadcastDefaults,
2336
- holoCacheDefaults,
2337
- holoCorsDefaults,
2338
- holoDatabaseDefaults,
2339
- holoMailDefaults,
2340
- holoNotificationsDefaults,
2341
- holoQueueDefaultsNormalized,
2342
- holoRedisDefaults,
2343
- holoSecurityDefaults,
2344
- holoSessionDefaults,
2345
- holoStorageDefaults,
2346
621
  isEnvPlaceholder,
2347
622
  loadConfigDirectory,
2348
623
  loadEnvironment,
2349
624
  loaderInternals,
2350
625
  normalizeAppConfig,
2351
626
  normalizeAppEnv,
2352
- normalizeAuthConfig,
2353
- normalizeBroadcastConfig,
2354
- normalizeCacheConfig,
2355
- normalizeCorsConfig,
2356
- normalizeDatabaseConfig,
2357
- normalizeMailConfig,
2358
- normalizeNotificationsConfig,
2359
- normalizeQueueConfigForHolo,
2360
- normalizeRedisConfig,
2361
- normalizeSecurityConfig,
2362
- normalizeSessionConfig,
2363
- normalizeStorageConfig,
627
+ registerConfigNormalizer,
628
+ resetConfigNormalizers,
2364
629
  resetConfigRuntime,
2365
630
  resolveAppEnvironment,
2366
631
  resolveConfigCachePath,