@holo-js/config 0.1.1 → 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/index.d.ts +771 -6
- package/dist/index.mjs +1585 -19
- package/package.json +2 -3
package/dist/index.mjs
CHANGED
|
@@ -3,10 +3,6 @@ import {
|
|
|
3
3
|
DEFAULT_HOLO_PROJECT_PATHS,
|
|
4
4
|
normalizeHoloProjectConfig
|
|
5
5
|
} from "@holo-js/db";
|
|
6
|
-
import {
|
|
7
|
-
normalizeQueueConfig,
|
|
8
|
-
holoQueueDefaults
|
|
9
|
-
} from "@holo-js/queue";
|
|
10
6
|
var DEFAULT_APP_NAME = "Holo";
|
|
11
7
|
var holoAppDefaults = Object.freeze({
|
|
12
8
|
name: DEFAULT_APP_NAME,
|
|
@@ -30,6 +26,23 @@ var holoDatabaseDefaults = Object.freeze({
|
|
|
30
26
|
})
|
|
31
27
|
})
|
|
32
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
|
+
});
|
|
33
46
|
var holoStorageDefaults = Object.freeze({
|
|
34
47
|
defaultDisk: "local",
|
|
35
48
|
routePrefix: "/storage",
|
|
@@ -45,7 +58,715 @@ var holoStorageDefaults = Object.freeze({
|
|
|
45
58
|
})
|
|
46
59
|
})
|
|
47
60
|
});
|
|
48
|
-
var
|
|
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_SECURITY_CSRF_FIELD = "_token";
|
|
226
|
+
var DEFAULT_SECURITY_CSRF_HEADER = "X-CSRF-TOKEN";
|
|
227
|
+
var DEFAULT_SECURITY_CSRF_COOKIE = "XSRF-TOKEN";
|
|
228
|
+
var DEFAULT_SECURITY_RATE_LIMIT_DRIVER = "memory";
|
|
229
|
+
var DEFAULT_SECURITY_RATE_LIMIT_FILE_PATH = "./storage/framework/rate-limits";
|
|
230
|
+
var DEFAULT_SECURITY_RATE_LIMIT_REDIS_CONNECTION = "default";
|
|
231
|
+
var DEFAULT_SECURITY_RATE_LIMIT_REDIS_PREFIX = "holo:rate-limit:";
|
|
232
|
+
var DEFAULT_SECURITY_CSRF_CONFIG = Object.freeze({
|
|
233
|
+
enabled: false,
|
|
234
|
+
field: DEFAULT_SECURITY_CSRF_FIELD,
|
|
235
|
+
header: DEFAULT_SECURITY_CSRF_HEADER,
|
|
236
|
+
cookie: DEFAULT_SECURITY_CSRF_COOKIE,
|
|
237
|
+
except: Object.freeze([])
|
|
238
|
+
});
|
|
239
|
+
var DEFAULT_SECURITY_RATE_LIMIT_CONFIG = Object.freeze({
|
|
240
|
+
driver: DEFAULT_SECURITY_RATE_LIMIT_DRIVER,
|
|
241
|
+
memory: Object.freeze({
|
|
242
|
+
driver: "memory"
|
|
243
|
+
}),
|
|
244
|
+
file: Object.freeze({
|
|
245
|
+
path: DEFAULT_SECURITY_RATE_LIMIT_FILE_PATH
|
|
246
|
+
}),
|
|
247
|
+
redis: Object.freeze({
|
|
248
|
+
host: DEFAULT_REDIS_HOST,
|
|
249
|
+
port: DEFAULT_REDIS_PORT,
|
|
250
|
+
password: void 0,
|
|
251
|
+
username: void 0,
|
|
252
|
+
db: DEFAULT_REDIS_DB,
|
|
253
|
+
connection: DEFAULT_SECURITY_RATE_LIMIT_REDIS_CONNECTION,
|
|
254
|
+
prefix: DEFAULT_SECURITY_RATE_LIMIT_REDIS_PREFIX
|
|
255
|
+
}),
|
|
256
|
+
limiters: Object.freeze({})
|
|
257
|
+
});
|
|
258
|
+
var holoSecurityDefaults = Object.freeze({
|
|
259
|
+
csrf: DEFAULT_SECURITY_CSRF_CONFIG,
|
|
260
|
+
rateLimit: DEFAULT_SECURITY_RATE_LIMIT_CONFIG
|
|
261
|
+
});
|
|
262
|
+
var DEFAULT_AUTH_GUARD = "web";
|
|
263
|
+
var DEFAULT_AUTH_PROVIDER = "users";
|
|
264
|
+
var DEFAULT_AUTH_IDENTIFIERS = Object.freeze(["email"]);
|
|
265
|
+
var DEFAULT_AUTH_PASSWORD_BROKER = "users";
|
|
266
|
+
var DEFAULT_AUTH_PASSWORD_RESET_TABLE = "password_reset_tokens";
|
|
267
|
+
var DEFAULT_AUTH_PASSWORD_EXPIRE = 60;
|
|
268
|
+
var DEFAULT_AUTH_PASSWORD_THROTTLE = 60;
|
|
269
|
+
var DEFAULT_WORKOS_SESSION_COOKIE = "wos-session";
|
|
270
|
+
var DEFAULT_CLERK_SESSION_COOKIE = "__session";
|
|
271
|
+
var holoAuthDefaults = Object.freeze({
|
|
272
|
+
defaults: Object.freeze({
|
|
273
|
+
guard: DEFAULT_AUTH_GUARD,
|
|
274
|
+
passwords: DEFAULT_AUTH_PASSWORD_BROKER
|
|
275
|
+
}),
|
|
276
|
+
guards: Object.freeze({
|
|
277
|
+
web: Object.freeze({
|
|
278
|
+
name: "web",
|
|
279
|
+
driver: "session",
|
|
280
|
+
provider: DEFAULT_AUTH_PROVIDER
|
|
281
|
+
})
|
|
282
|
+
}),
|
|
283
|
+
providers: Object.freeze({
|
|
284
|
+
users: Object.freeze({
|
|
285
|
+
name: "users",
|
|
286
|
+
model: "User",
|
|
287
|
+
identifiers: DEFAULT_AUTH_IDENTIFIERS
|
|
288
|
+
})
|
|
289
|
+
}),
|
|
290
|
+
passwords: Object.freeze({
|
|
291
|
+
users: Object.freeze({
|
|
292
|
+
name: "users",
|
|
293
|
+
provider: DEFAULT_AUTH_PROVIDER,
|
|
294
|
+
table: DEFAULT_AUTH_PASSWORD_RESET_TABLE,
|
|
295
|
+
expire: DEFAULT_AUTH_PASSWORD_EXPIRE,
|
|
296
|
+
throttle: DEFAULT_AUTH_PASSWORD_THROTTLE
|
|
297
|
+
})
|
|
298
|
+
}),
|
|
299
|
+
emailVerification: Object.freeze({
|
|
300
|
+
required: false
|
|
301
|
+
}),
|
|
302
|
+
personalAccessTokens: Object.freeze({
|
|
303
|
+
defaultAbilities: Object.freeze([])
|
|
304
|
+
}),
|
|
305
|
+
socialEncryptionKey: void 0,
|
|
306
|
+
social: Object.freeze({}),
|
|
307
|
+
workos: Object.freeze({}),
|
|
308
|
+
clerk: Object.freeze({})
|
|
309
|
+
});
|
|
310
|
+
var DEFAULT_QUEUE_CONNECTION = "sync";
|
|
311
|
+
var DEFAULT_QUEUE_NAME = "default";
|
|
312
|
+
var DEFAULT_QUEUE_RETRY_AFTER = 90;
|
|
313
|
+
var DEFAULT_QUEUE_BLOCK_FOR = 5;
|
|
314
|
+
var DEFAULT_QUEUE_SLEEP = 1;
|
|
315
|
+
var DEFAULT_FAILED_JOBS_CONNECTION = "default";
|
|
316
|
+
var DEFAULT_FAILED_JOBS_TABLE = "failed_jobs";
|
|
317
|
+
var DEFAULT_DATABASE_QUEUE_TABLE = "jobs";
|
|
318
|
+
var DEFAULT_QUEUE_CONFIG = Object.freeze({
|
|
319
|
+
default: DEFAULT_QUEUE_CONNECTION,
|
|
320
|
+
failed: Object.freeze({
|
|
321
|
+
driver: "database",
|
|
322
|
+
connection: DEFAULT_FAILED_JOBS_CONNECTION,
|
|
323
|
+
table: DEFAULT_FAILED_JOBS_TABLE
|
|
324
|
+
}),
|
|
325
|
+
connections: Object.freeze({
|
|
326
|
+
[DEFAULT_QUEUE_CONNECTION]: Object.freeze({
|
|
327
|
+
name: DEFAULT_QUEUE_CONNECTION,
|
|
328
|
+
driver: "sync",
|
|
329
|
+
queue: DEFAULT_QUEUE_NAME
|
|
330
|
+
})
|
|
331
|
+
})
|
|
332
|
+
});
|
|
333
|
+
function parseInteger(value, fallback, label, options = {}) {
|
|
334
|
+
if (typeof value === "undefined") {
|
|
335
|
+
return fallback;
|
|
336
|
+
}
|
|
337
|
+
const normalized = typeof value === "number" ? value : Number.parseInt(value, 10);
|
|
338
|
+
if (!Number.isInteger(normalized)) {
|
|
339
|
+
throw new Error(`[Holo Queue] ${label} must be an integer.`);
|
|
340
|
+
}
|
|
341
|
+
if (typeof options.minimum === "number" && normalized < options.minimum) {
|
|
342
|
+
throw new Error(`[Holo Queue] ${label} must be greater than or equal to ${options.minimum}.`);
|
|
343
|
+
}
|
|
344
|
+
return normalized;
|
|
345
|
+
}
|
|
346
|
+
function normalizeNonEmptyString(value, label) {
|
|
347
|
+
const normalized = value?.trim();
|
|
348
|
+
if (!normalized) {
|
|
349
|
+
throw new Error(label);
|
|
350
|
+
}
|
|
351
|
+
return normalized;
|
|
352
|
+
}
|
|
353
|
+
function normalizeConnectionName(value, label) {
|
|
354
|
+
const normalized = value?.trim();
|
|
355
|
+
if (!normalized) {
|
|
356
|
+
throw new Error(`[Holo Queue] ${label} must be a non-empty string.`);
|
|
357
|
+
}
|
|
358
|
+
return normalized;
|
|
359
|
+
}
|
|
360
|
+
function normalizeCacheName(value, label) {
|
|
361
|
+
const normalized = value?.trim();
|
|
362
|
+
if (!normalized) {
|
|
363
|
+
throw new Error(`[Holo Cache] ${label} must be a non-empty string.`);
|
|
364
|
+
}
|
|
365
|
+
return normalized;
|
|
366
|
+
}
|
|
367
|
+
function normalizeCacheOptionalString(value) {
|
|
368
|
+
const normalized = value?.trim();
|
|
369
|
+
return normalized || void 0;
|
|
370
|
+
}
|
|
371
|
+
function parseCacheInteger(value, label, options = {}) {
|
|
372
|
+
if (typeof value === "undefined") {
|
|
373
|
+
return void 0;
|
|
374
|
+
}
|
|
375
|
+
const normalized = typeof value === "number" ? value : (() => {
|
|
376
|
+
const trimmed = value.trim();
|
|
377
|
+
if (!trimmed) {
|
|
378
|
+
return Number.NaN;
|
|
379
|
+
}
|
|
380
|
+
return Number(trimmed);
|
|
381
|
+
})();
|
|
382
|
+
if (!Number.isFinite(normalized) || !Number.isInteger(normalized)) {
|
|
383
|
+
throw new Error(`[Holo Cache] ${label} must be an integer.`);
|
|
384
|
+
}
|
|
385
|
+
if (typeof options.minimum === "number" && normalized < options.minimum) {
|
|
386
|
+
throw new Error(`[Holo Cache] ${label} must be greater than or equal to ${options.minimum}.`);
|
|
387
|
+
}
|
|
388
|
+
return normalized;
|
|
389
|
+
}
|
|
390
|
+
function resolveCachePrefix(globalPrefix, localPrefix) {
|
|
391
|
+
return normalizeCacheOptionalString(localPrefix) ?? globalPrefix;
|
|
392
|
+
}
|
|
393
|
+
function normalizeCacheDriverConfig(name, config2, globalPrefix) {
|
|
394
|
+
switch (config2.driver) {
|
|
395
|
+
case "memory": {
|
|
396
|
+
const memoryConfig = config2;
|
|
397
|
+
return Object.freeze({
|
|
398
|
+
name,
|
|
399
|
+
driver: "memory",
|
|
400
|
+
prefix: resolveCachePrefix(globalPrefix, memoryConfig.prefix),
|
|
401
|
+
maxEntries: parseCacheInteger(memoryConfig.maxEntries, `cache driver "${name}" maxEntries`, {
|
|
402
|
+
minimum: 1
|
|
403
|
+
})
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
case "file": {
|
|
407
|
+
const fileConfig = config2;
|
|
408
|
+
return Object.freeze({
|
|
409
|
+
name,
|
|
410
|
+
driver: "file",
|
|
411
|
+
path: normalizeCacheOptionalString(fileConfig.path) || DEFAULT_CACHE_FILE_PATH,
|
|
412
|
+
prefix: resolveCachePrefix(globalPrefix, fileConfig.prefix)
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
case "redis": {
|
|
416
|
+
const redisConfig = config2;
|
|
417
|
+
return Object.freeze({
|
|
418
|
+
name,
|
|
419
|
+
driver: "redis",
|
|
420
|
+
connection: redisConfig.connection,
|
|
421
|
+
prefix: resolveCachePrefix(globalPrefix, redisConfig.prefix)
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
case "database": {
|
|
425
|
+
const databaseConfig = config2;
|
|
426
|
+
return Object.freeze({
|
|
427
|
+
name,
|
|
428
|
+
driver: "database",
|
|
429
|
+
connection: databaseConfig.connection,
|
|
430
|
+
table: normalizeCacheOptionalString(databaseConfig.table) || DEFAULT_CACHE_DATABASE_TABLE,
|
|
431
|
+
lockTable: normalizeCacheOptionalString(databaseConfig.lockTable) || DEFAULT_CACHE_DATABASE_LOCK_TABLE,
|
|
432
|
+
prefix: resolveCachePrefix(globalPrefix, databaseConfig.prefix)
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
default:
|
|
436
|
+
throw new Error(`[Holo Cache] Unsupported cache driver "${String(config2.driver)}" on driver "${name}".`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
function normalizeCacheConfig(config2 = {}, options = {}) {
|
|
440
|
+
const prefix = normalizeCacheOptionalString(config2.prefix) ?? DEFAULT_CACHE_PREFIX;
|
|
441
|
+
const defaultRedisConnection = options.redis?.default ?? DEFAULT_CACHE_REDIS_CONNECTION;
|
|
442
|
+
const defaultDatabaseConnection = options.database?.defaultConnection ?? DEFAULT_CACHE_DATABASE_CONNECTION;
|
|
443
|
+
const driverEntries = !config2.drivers || Object.keys(config2.drivers).length === 0 ? Object.entries(holoCacheDefaults.drivers) : Object.entries(config2.drivers);
|
|
444
|
+
const normalizedDriverEntries = driverEntries.map(([name, driver]) => {
|
|
445
|
+
const normalizedName = normalizeCacheName(name, "Cache driver name");
|
|
446
|
+
const driverConfig = (() => {
|
|
447
|
+
switch (driver.driver) {
|
|
448
|
+
case "redis":
|
|
449
|
+
return {
|
|
450
|
+
...driver,
|
|
451
|
+
connection: normalizeCacheOptionalString(driver.connection) ?? defaultRedisConnection
|
|
452
|
+
};
|
|
453
|
+
case "database":
|
|
454
|
+
return {
|
|
455
|
+
...driver,
|
|
456
|
+
connection: normalizeCacheOptionalString(driver.connection) ?? defaultDatabaseConnection
|
|
457
|
+
};
|
|
458
|
+
default:
|
|
459
|
+
return driver;
|
|
460
|
+
}
|
|
461
|
+
})();
|
|
462
|
+
return [normalizedName, normalizeCacheDriverConfig(normalizedName, driverConfig, prefix)];
|
|
463
|
+
});
|
|
464
|
+
const drivers = Object.freeze(Object.fromEntries(normalizedDriverEntries));
|
|
465
|
+
const configuredDefault = normalizeCacheOptionalString(config2.default);
|
|
466
|
+
if (configuredDefault && !Object.hasOwn(drivers, configuredDefault)) {
|
|
467
|
+
throw new Error(`[Holo Cache] default cache driver "${configuredDefault}" is not configured.`);
|
|
468
|
+
}
|
|
469
|
+
const fallbackDefaultDriver = normalizedDriverEntries.find(([name]) => name === DEFAULT_CACHE_DRIVER)?.[0] ?? normalizedDriverEntries[0][0];
|
|
470
|
+
const defaultDriver = configuredDefault ?? fallbackDefaultDriver;
|
|
471
|
+
return Object.freeze({
|
|
472
|
+
default: defaultDriver,
|
|
473
|
+
prefix,
|
|
474
|
+
drivers
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
function normalizeQueueName(value) {
|
|
478
|
+
return value?.trim() || DEFAULT_QUEUE_NAME;
|
|
479
|
+
}
|
|
480
|
+
function parseRedisInteger(value, fallback, label, options = {}) {
|
|
481
|
+
if (typeof value === "undefined") {
|
|
482
|
+
return fallback;
|
|
483
|
+
}
|
|
484
|
+
const normalized = typeof value === "number" ? value : (() => {
|
|
485
|
+
const trimmed = value.trim();
|
|
486
|
+
if (!trimmed || !/^\d+$/.test(trimmed)) {
|
|
487
|
+
return Number.NaN;
|
|
488
|
+
}
|
|
489
|
+
return Number.parseInt(trimmed, 10);
|
|
490
|
+
})();
|
|
491
|
+
if (!Number.isInteger(normalized)) {
|
|
492
|
+
throw new Error(`[Holo Redis] ${label} must be an integer.`);
|
|
493
|
+
}
|
|
494
|
+
if (typeof options.minimum === "number" && normalized < options.minimum) {
|
|
495
|
+
throw new Error(`[Holo Redis] ${label} must be greater than or equal to ${options.minimum}.`);
|
|
496
|
+
}
|
|
497
|
+
return normalized;
|
|
498
|
+
}
|
|
499
|
+
function normalizeRedisConnectionName(value, label) {
|
|
500
|
+
const normalized = value?.trim();
|
|
501
|
+
if (!normalized) {
|
|
502
|
+
throw new Error(`[Holo Redis] ${label} must be a non-empty string.`);
|
|
503
|
+
}
|
|
504
|
+
return normalized;
|
|
505
|
+
}
|
|
506
|
+
function normalizeOptionalRedisString(value, label) {
|
|
507
|
+
if (typeof value === "undefined") {
|
|
508
|
+
return void 0;
|
|
509
|
+
}
|
|
510
|
+
const normalized = normalizeRedisConnectionName(value, label);
|
|
511
|
+
try {
|
|
512
|
+
const parsed = new URL(normalized);
|
|
513
|
+
if (parsed.protocol !== "redis:" && parsed.protocol !== "rediss:") {
|
|
514
|
+
throw new Error(`[Holo Redis] ${label} must use the redis:// or rediss:// scheme.`);
|
|
515
|
+
}
|
|
516
|
+
} catch (error) {
|
|
517
|
+
if (error instanceof Error && error.message.startsWith("[Holo Redis]")) {
|
|
518
|
+
throw error;
|
|
519
|
+
}
|
|
520
|
+
throw new Error(`[Holo Redis] ${label} must be a valid redis:// or rediss:// URL.`);
|
|
521
|
+
}
|
|
522
|
+
return normalized;
|
|
523
|
+
}
|
|
524
|
+
function normalizeOptionalRedisSocketPath(value, label) {
|
|
525
|
+
if (typeof value === "undefined") {
|
|
526
|
+
return void 0;
|
|
527
|
+
}
|
|
528
|
+
return normalizeRedisConnectionName(value, label);
|
|
529
|
+
}
|
|
530
|
+
function deriveNormalizedRedisSocketPath(socketPath, host) {
|
|
531
|
+
if (socketPath) {
|
|
532
|
+
return socketPath;
|
|
533
|
+
}
|
|
534
|
+
if (typeof host === "string" && (host.startsWith("unix://") || host.startsWith("/"))) {
|
|
535
|
+
return host.startsWith("unix://") ? host.slice("unix://".length) : host;
|
|
536
|
+
}
|
|
537
|
+
return void 0;
|
|
538
|
+
}
|
|
539
|
+
function normalizeRedisClusterNodeConfig(connectionName, index, config2) {
|
|
540
|
+
const label = `redis connection "${connectionName}" cluster node ${index + 1}`;
|
|
541
|
+
const url = normalizeOptionalRedisString(config2.url, `${label} url`);
|
|
542
|
+
const socketPath = normalizeOptionalRedisSocketPath(config2.socketPath, `${label} socketPath`);
|
|
543
|
+
const normalizedSocketPath = deriveNormalizedRedisSocketPath(socketPath, config2.host?.trim());
|
|
544
|
+
if (typeof normalizedSocketPath !== "undefined") {
|
|
545
|
+
throw new Error(`[Holo Redis] ${label} cannot use socketPath in cluster mode.`);
|
|
546
|
+
}
|
|
547
|
+
parseRedisDatabaseFromUrl(url, {
|
|
548
|
+
allowPath: false,
|
|
549
|
+
label: `${label} url`
|
|
550
|
+
});
|
|
551
|
+
const host = config2.host?.trim() || DEFAULT_REDIS_HOST;
|
|
552
|
+
return Object.freeze({
|
|
553
|
+
...typeof url === "undefined" ? {} : { url },
|
|
554
|
+
host,
|
|
555
|
+
port: parseRedisInteger(config2.port, DEFAULT_REDIS_PORT, `${label} port`, {
|
|
556
|
+
minimum: 1
|
|
557
|
+
})
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
function normalizeRedisClusterNodes(connectionName, nodes) {
|
|
561
|
+
if (!nodes || nodes.length === 0) {
|
|
562
|
+
return void 0;
|
|
563
|
+
}
|
|
564
|
+
return Object.freeze(nodes.map((node, index) => normalizeRedisClusterNodeConfig(connectionName, index, node)));
|
|
565
|
+
}
|
|
566
|
+
function parseRedisDatabaseFromUrl(url, options) {
|
|
567
|
+
if (typeof url === "undefined") {
|
|
568
|
+
return void 0;
|
|
569
|
+
}
|
|
570
|
+
try {
|
|
571
|
+
const parsed = new URL(url);
|
|
572
|
+
const pathname = parsed.pathname.replace(/^\/+/, "");
|
|
573
|
+
if (!pathname) {
|
|
574
|
+
return void 0;
|
|
575
|
+
}
|
|
576
|
+
const [databaseSegment] = pathname.split("/");
|
|
577
|
+
const { label } = options;
|
|
578
|
+
if (options.allowPath === false) {
|
|
579
|
+
throw new Error(`[Holo Redis] ${label} cannot include a database path in cluster mode.`);
|
|
580
|
+
}
|
|
581
|
+
if (!databaseSegment || !/^\d+$/.test(databaseSegment) || pathname !== databaseSegment) {
|
|
582
|
+
throw new Error(`[Holo Redis] ${label} database path must be a single integer segment.`);
|
|
583
|
+
}
|
|
584
|
+
return Number.parseInt(databaseSegment, 10);
|
|
585
|
+
} catch (error) {
|
|
586
|
+
if (error instanceof Error && error.message.startsWith("[Holo Redis]")) {
|
|
587
|
+
throw error;
|
|
588
|
+
}
|
|
589
|
+
return void 0;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
function normalizeRedisConnectionConfig(name, config2) {
|
|
593
|
+
const url = normalizeOptionalRedisString(config2.url, `redis connection "${name}" url`);
|
|
594
|
+
const clusters = normalizeRedisClusterNodes(name, config2.clusters);
|
|
595
|
+
const socketPath = normalizeOptionalRedisSocketPath(config2.socketPath, `redis connection "${name}" socketPath`);
|
|
596
|
+
const normalizedSocketPath = deriveNormalizedRedisSocketPath(socketPath, config2.host?.trim());
|
|
597
|
+
const targetModeCount = [url, clusters, normalizedSocketPath].filter((value) => typeof value !== "undefined").length;
|
|
598
|
+
if (targetModeCount > 1) {
|
|
599
|
+
throw new Error(`[Holo Redis] redis connection "${name}" must configure exactly one target mode: url, clusters, or socketPath.`);
|
|
600
|
+
}
|
|
601
|
+
const host = config2.host?.trim() || normalizedSocketPath || DEFAULT_REDIS_HOST;
|
|
602
|
+
const databaseFromUrl = parseRedisDatabaseFromUrl(url, {
|
|
603
|
+
label: `redis connection "${name}" url`
|
|
604
|
+
});
|
|
605
|
+
const database = parseRedisInteger(config2.db ?? databaseFromUrl, DEFAULT_REDIS_DB, `redis connection "${name}" db`, {
|
|
606
|
+
minimum: 0
|
|
607
|
+
});
|
|
608
|
+
if (typeof clusters !== "undefined" && database !== 0) {
|
|
609
|
+
throw new Error(`[Holo Redis] redis connection "${name}" cannot select redis.db=${database} in cluster mode; Redis Cluster only supports database 0.`);
|
|
610
|
+
}
|
|
611
|
+
return Object.freeze({
|
|
612
|
+
name,
|
|
613
|
+
...typeof url === "undefined" ? {} : { url },
|
|
614
|
+
...typeof clusters === "undefined" ? {} : { clusters },
|
|
615
|
+
...typeof normalizedSocketPath === "undefined" ? {} : { socketPath: normalizedSocketPath },
|
|
616
|
+
host,
|
|
617
|
+
port: parseRedisInteger(config2.port, DEFAULT_REDIS_PORT, `redis connection "${name}" port`, {
|
|
618
|
+
minimum: 1
|
|
619
|
+
}),
|
|
620
|
+
username: config2.username?.trim() || void 0,
|
|
621
|
+
password: config2.password?.trim() || void 0,
|
|
622
|
+
db: database
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
function normalizeRedisConnections(connections) {
|
|
626
|
+
if (!connections || Object.keys(connections).length === 0) {
|
|
627
|
+
return holoRedisDefaults.connections;
|
|
628
|
+
}
|
|
629
|
+
return Object.freeze(Object.fromEntries(
|
|
630
|
+
Object.entries(connections).map(([name, config2]) => {
|
|
631
|
+
const normalizedName = normalizeRedisConnectionName(name, "Redis connection name");
|
|
632
|
+
return [normalizedName, normalizeRedisConnectionConfig(normalizedName, config2)];
|
|
633
|
+
})
|
|
634
|
+
));
|
|
635
|
+
}
|
|
636
|
+
function resolveNormalizedRedisConnection(redisConfig, connectionName, label) {
|
|
637
|
+
const { connections } = redisConfig;
|
|
638
|
+
const resolved = connections[connectionName];
|
|
639
|
+
if (!resolved) {
|
|
640
|
+
throw new Error(`[Holo Redis] ${label} "${connectionName}" is not configured.`);
|
|
641
|
+
}
|
|
642
|
+
return resolved;
|
|
643
|
+
}
|
|
644
|
+
function parseSecurityInteger(value, fallback, label, options = {}) {
|
|
645
|
+
const normalized = typeof value === "undefined" ? fallback : typeof value === "number" ? value : (() => {
|
|
646
|
+
const trimmed = value.trim();
|
|
647
|
+
if (!trimmed) {
|
|
648
|
+
return Number.NaN;
|
|
649
|
+
}
|
|
650
|
+
return Number(trimmed);
|
|
651
|
+
})();
|
|
652
|
+
if (!Number.isFinite(normalized) || !Number.isInteger(normalized)) {
|
|
653
|
+
throw new Error(`[Holo Security] ${label} must be an integer.`);
|
|
654
|
+
}
|
|
655
|
+
if (typeof options.minimum === "number" && normalized < options.minimum) {
|
|
656
|
+
throw new Error(`[Holo Security] ${label} must be greater than or equal to ${options.minimum}.`);
|
|
657
|
+
}
|
|
658
|
+
return normalized;
|
|
659
|
+
}
|
|
660
|
+
function normalizeSecurityName(value, label) {
|
|
661
|
+
const normalized = value?.trim();
|
|
662
|
+
if (!normalized) {
|
|
663
|
+
throw new Error(`[Holo Security] ${label} must be a non-empty string.`);
|
|
664
|
+
}
|
|
665
|
+
return normalized;
|
|
666
|
+
}
|
|
667
|
+
function normalizeSecurityOptionalString(value) {
|
|
668
|
+
const normalized = value?.trim();
|
|
669
|
+
return normalized || void 0;
|
|
670
|
+
}
|
|
671
|
+
function normalizeSyncConnection(name, config2) {
|
|
672
|
+
return Object.freeze({
|
|
673
|
+
name,
|
|
674
|
+
driver: "sync",
|
|
675
|
+
queue: normalizeQueueName(config2.queue)
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
function normalizeRedisConnection(name, config2, redisConfig) {
|
|
679
|
+
const explicitConnectionName = config2.connection?.trim();
|
|
680
|
+
const connectionName = explicitConnectionName || redisConfig?.default;
|
|
681
|
+
if (!connectionName) {
|
|
682
|
+
throw new Error(
|
|
683
|
+
`[@holo-js/config] Queue Redis connection "${name}" requires a top-level redis config with a default connection or an explicit connection name.`
|
|
684
|
+
);
|
|
685
|
+
}
|
|
686
|
+
if (!redisConfig) {
|
|
687
|
+
throw new Error(
|
|
688
|
+
`[@holo-js/config] Queue Redis connection "${name}" references shared Redis connection "${connectionName}" but no top-level redis config is loaded.`
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
const resolvedRedisConnection = resolveNormalizedRedisConnection(
|
|
692
|
+
redisConfig,
|
|
693
|
+
connectionName,
|
|
694
|
+
"Queue Redis connection"
|
|
695
|
+
);
|
|
696
|
+
return Object.freeze({
|
|
697
|
+
name,
|
|
698
|
+
driver: "redis",
|
|
699
|
+
connection: resolvedRedisConnection.name,
|
|
700
|
+
queue: normalizeQueueName(config2.queue),
|
|
701
|
+
retryAfter: parseInteger(config2.retryAfter, DEFAULT_QUEUE_RETRY_AFTER, `queue connection "${name}" retryAfter`, {
|
|
702
|
+
minimum: 0
|
|
703
|
+
}),
|
|
704
|
+
blockFor: parseInteger(config2.blockFor, DEFAULT_QUEUE_BLOCK_FOR, `queue connection "${name}" blockFor`, {
|
|
705
|
+
minimum: 0
|
|
706
|
+
}),
|
|
707
|
+
redis: Object.freeze({
|
|
708
|
+
...typeof resolvedRedisConnection.url === "undefined" ? {} : { url: resolvedRedisConnection.url },
|
|
709
|
+
...typeof resolvedRedisConnection.clusters === "undefined" ? {} : { clusters: resolvedRedisConnection.clusters },
|
|
710
|
+
host: resolvedRedisConnection.host,
|
|
711
|
+
port: resolvedRedisConnection.port,
|
|
712
|
+
password: resolvedRedisConnection.password,
|
|
713
|
+
username: resolvedRedisConnection.username,
|
|
714
|
+
db: resolvedRedisConnection.db
|
|
715
|
+
})
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
function normalizeDatabaseConnection(name, config2) {
|
|
719
|
+
return Object.freeze({
|
|
720
|
+
name,
|
|
721
|
+
driver: "database",
|
|
722
|
+
queue: normalizeQueueName(config2.queue),
|
|
723
|
+
retryAfter: parseInteger(config2.retryAfter, DEFAULT_QUEUE_RETRY_AFTER, `queue connection "${name}" retryAfter`, {
|
|
724
|
+
minimum: 0
|
|
725
|
+
}),
|
|
726
|
+
sleep: parseInteger(config2.sleep, DEFAULT_QUEUE_SLEEP, `queue connection "${name}" sleep`, {
|
|
727
|
+
minimum: 0
|
|
728
|
+
}),
|
|
729
|
+
connection: config2.connection?.trim() || DEFAULT_FAILED_JOBS_CONNECTION,
|
|
730
|
+
table: config2.table?.trim() || DEFAULT_DATABASE_QUEUE_TABLE
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
function normalizeConnectionConfig(name, config2, redisConfig) {
|
|
734
|
+
switch (config2.driver) {
|
|
735
|
+
case "sync":
|
|
736
|
+
return normalizeSyncConnection(name, config2);
|
|
737
|
+
case "redis":
|
|
738
|
+
return normalizeRedisConnection(name, config2, redisConfig);
|
|
739
|
+
case "database":
|
|
740
|
+
return normalizeDatabaseConnection(name, config2);
|
|
741
|
+
default:
|
|
742
|
+
throw new Error(`[Holo Queue] Unsupported queue driver "${String(config2.driver)}" on connection "${name}".`);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
function normalizeConnections(connections, redisConfig) {
|
|
746
|
+
if (!connections || Object.keys(connections).length === 0) {
|
|
747
|
+
return DEFAULT_QUEUE_CONFIG.connections;
|
|
748
|
+
}
|
|
749
|
+
const normalizedEntries = Object.entries(connections).map(([name, config2]) => {
|
|
750
|
+
const normalizedName = normalizeConnectionName(name, "Queue connection name");
|
|
751
|
+
return [normalizedName, normalizeConnectionConfig(normalizedName, config2, redisConfig)];
|
|
752
|
+
});
|
|
753
|
+
return Object.freeze(Object.fromEntries(normalizedEntries));
|
|
754
|
+
}
|
|
755
|
+
function normalizeFailedStore(config2) {
|
|
756
|
+
if (config2 === false) {
|
|
757
|
+
return false;
|
|
758
|
+
}
|
|
759
|
+
const normalized = config2 ?? DEFAULT_QUEUE_CONFIG.failed;
|
|
760
|
+
if (normalized.driver && normalized.driver !== "database") {
|
|
761
|
+
throw new Error(`[Holo Queue] Unsupported failed job store driver "${normalized.driver}".`);
|
|
762
|
+
}
|
|
763
|
+
return Object.freeze({
|
|
764
|
+
driver: "database",
|
|
765
|
+
connection: normalized.connection?.trim() || DEFAULT_FAILED_JOBS_CONNECTION,
|
|
766
|
+
table: normalized.table?.trim() || DEFAULT_FAILED_JOBS_TABLE
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
var holoQueueDefaultsNormalized = DEFAULT_QUEUE_CONFIG;
|
|
49
770
|
function normalizeAppEnv(value, fallback = "development") {
|
|
50
771
|
if (!value) {
|
|
51
772
|
return fallback;
|
|
@@ -71,6 +792,376 @@ function normalizeAppConfig(config2 = {}) {
|
|
|
71
792
|
seeders: project.seeders
|
|
72
793
|
});
|
|
73
794
|
}
|
|
795
|
+
function normalizeSessionStoreConfig(name, config2, redisConfig) {
|
|
796
|
+
switch (config2.driver) {
|
|
797
|
+
case "database": {
|
|
798
|
+
const databaseConfig = config2;
|
|
799
|
+
return Object.freeze({
|
|
800
|
+
name,
|
|
801
|
+
driver: "database",
|
|
802
|
+
connection: databaseConfig.connection?.trim() || DEFAULT_SESSION_DATABASE_CONNECTION,
|
|
803
|
+
table: databaseConfig.table?.trim() || DEFAULT_SESSION_DATABASE_TABLE
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
case "file": {
|
|
807
|
+
const fileConfig = config2;
|
|
808
|
+
return Object.freeze({
|
|
809
|
+
name,
|
|
810
|
+
driver: "file",
|
|
811
|
+
path: fileConfig.path?.trim() || DEFAULT_SESSION_FILE_PATH
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
case "redis": {
|
|
815
|
+
const redisStoreConfig = config2;
|
|
816
|
+
const configuredConnection = redisStoreConfig.connection?.trim();
|
|
817
|
+
const connectionName = configuredConnection || redisConfig?.default;
|
|
818
|
+
if (!connectionName) {
|
|
819
|
+
throw new Error(
|
|
820
|
+
`[@holo-js/config] Session Redis store "${name}" requires a top-level redis config with a default connection or an explicit connection name.`
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
if (!redisConfig) {
|
|
824
|
+
throw new Error(
|
|
825
|
+
`[@holo-js/config] Session Redis store "${name}" references shared Redis connection "${connectionName}" but no top-level redis config is loaded.`
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
const resolvedConnection = resolveNormalizedRedisConnection(
|
|
829
|
+
redisConfig,
|
|
830
|
+
connectionName,
|
|
831
|
+
"Session Redis store"
|
|
832
|
+
);
|
|
833
|
+
return Object.freeze({
|
|
834
|
+
name,
|
|
835
|
+
driver: "redis",
|
|
836
|
+
connection: resolvedConnection.name,
|
|
837
|
+
...typeof resolvedConnection.url === "undefined" ? {} : { url: resolvedConnection.url },
|
|
838
|
+
...typeof resolvedConnection.clusters === "undefined" ? {} : { clusters: resolvedConnection.clusters },
|
|
839
|
+
host: resolvedConnection.host,
|
|
840
|
+
port: resolvedConnection.port,
|
|
841
|
+
username: resolvedConnection.username,
|
|
842
|
+
password: resolvedConnection.password,
|
|
843
|
+
db: resolvedConnection.db,
|
|
844
|
+
prefix: redisStoreConfig.prefix?.trim() || ""
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
default:
|
|
848
|
+
throw new Error(`[Holo Session] Unsupported session store driver "${String(config2.driver)}" on store "${name}".`);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
function normalizeSessionConfig(config2 = {}, redisConfig) {
|
|
852
|
+
const stores = !config2.stores || Object.keys(config2.stores).length === 0 ? holoSessionDefaults.stores : Object.freeze(Object.fromEntries(Object.entries(config2.stores).map(([name, store]) => {
|
|
853
|
+
const normalizedName = normalizeConnectionName(name, "Session store name");
|
|
854
|
+
return [normalizedName, normalizeSessionStoreConfig(normalizedName, store, redisConfig)];
|
|
855
|
+
})));
|
|
856
|
+
const configuredDriver = config2.driver?.trim();
|
|
857
|
+
const driver = configuredDriver || (DEFAULT_SESSION_DRIVER in stores ? DEFAULT_SESSION_DRIVER : void 0) || Object.keys(stores)[0] || DEFAULT_SESSION_DRIVER;
|
|
858
|
+
if (!(driver in stores)) {
|
|
859
|
+
throw new Error(`[Holo Session] default session driver "${driver}" is not configured.`);
|
|
860
|
+
}
|
|
861
|
+
const cookie = config2.cookie ?? {};
|
|
862
|
+
const sameSite = cookie.sameSite ?? DEFAULT_SESSION_COOKIE_SAME_SITE;
|
|
863
|
+
if (sameSite !== "lax" && sameSite !== "strict" && sameSite !== "none") {
|
|
864
|
+
throw new Error(`[Holo Session] cookie sameSite must be "lax", "strict", or "none".`);
|
|
865
|
+
}
|
|
866
|
+
const idleTimeout = parseInteger(config2.idleTimeout, DEFAULT_SESSION_IDLE_TIMEOUT, "session idleTimeout", {
|
|
867
|
+
minimum: 0
|
|
868
|
+
});
|
|
869
|
+
const absoluteLifetime = parseInteger(
|
|
870
|
+
config2.absoluteLifetime,
|
|
871
|
+
DEFAULT_SESSION_ABSOLUTE_LIFETIME,
|
|
872
|
+
"session absoluteLifetime",
|
|
873
|
+
{
|
|
874
|
+
minimum: 0
|
|
875
|
+
}
|
|
876
|
+
);
|
|
877
|
+
const rememberMeLifetime = parseInteger(
|
|
878
|
+
config2.rememberMeLifetime,
|
|
879
|
+
DEFAULT_SESSION_REMEMBER_ME_LIFETIME,
|
|
880
|
+
"session rememberMeLifetime",
|
|
881
|
+
{
|
|
882
|
+
minimum: 0
|
|
883
|
+
}
|
|
884
|
+
);
|
|
885
|
+
return Object.freeze({
|
|
886
|
+
driver,
|
|
887
|
+
stores,
|
|
888
|
+
cookie: Object.freeze({
|
|
889
|
+
name: cookie.name?.trim() || DEFAULT_SESSION_COOKIE_NAME,
|
|
890
|
+
path: cookie.path?.trim() || DEFAULT_SESSION_COOKIE_PATH,
|
|
891
|
+
domain: cookie.domain?.trim() || void 0,
|
|
892
|
+
secure: cookie.secure ?? false,
|
|
893
|
+
httpOnly: cookie.httpOnly ?? true,
|
|
894
|
+
sameSite,
|
|
895
|
+
partitioned: cookie.partitioned ?? false,
|
|
896
|
+
maxAge: parseInteger(cookie.maxAge, absoluteLifetime, "session cookie maxAge", {
|
|
897
|
+
minimum: 0
|
|
898
|
+
})
|
|
899
|
+
}),
|
|
900
|
+
idleTimeout,
|
|
901
|
+
absoluteLifetime,
|
|
902
|
+
rememberMeLifetime
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
function normalizeSecurityLimiter(name, config2) {
|
|
906
|
+
const key = typeof config2.key === "function" ? config2.key : void 0;
|
|
907
|
+
if (typeof config2.key !== "undefined" && typeof config2.key !== "function") {
|
|
908
|
+
throw new Error(`[Holo Security] rate limiter "${name}" key resolver must be a function when provided.`);
|
|
909
|
+
}
|
|
910
|
+
return Object.freeze({
|
|
911
|
+
name,
|
|
912
|
+
maxAttempts: parseSecurityInteger(config2.maxAttempts, 0, `rate limiter "${name}" maxAttempts`, {
|
|
913
|
+
minimum: 1
|
|
914
|
+
}),
|
|
915
|
+
decaySeconds: parseSecurityInteger(config2.decaySeconds, 0, `rate limiter "${name}" decaySeconds`, {
|
|
916
|
+
minimum: 1
|
|
917
|
+
}),
|
|
918
|
+
...key ? { key } : {}
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
function normalizeSecurityRateLimitConfig(config2, redisConfig) {
|
|
922
|
+
const driver = normalizeSecurityOptionalString(config2?.driver) || DEFAULT_SECURITY_RATE_LIMIT_DRIVER;
|
|
923
|
+
if (driver !== "memory" && driver !== "file" && driver !== "redis") {
|
|
924
|
+
throw new Error(`[Holo Security] Unsupported rate limit driver "${driver}".`);
|
|
925
|
+
}
|
|
926
|
+
const file = config2?.file ?? {};
|
|
927
|
+
const redis = config2?.redis ?? {};
|
|
928
|
+
const limiters = !config2?.limiters || Object.keys(config2.limiters).length === 0 ? holoSecurityDefaults.rateLimit.limiters : Object.freeze(Object.fromEntries(Object.entries(config2.limiters).map(([name, limiter]) => {
|
|
929
|
+
const normalizedName = normalizeSecurityName(name, "Rate limiter name");
|
|
930
|
+
return [normalizedName, normalizeSecurityLimiter(normalizedName, limiter)];
|
|
931
|
+
})));
|
|
932
|
+
return Object.freeze({
|
|
933
|
+
driver,
|
|
934
|
+
memory: Object.freeze({
|
|
935
|
+
driver: "memory"
|
|
936
|
+
}),
|
|
937
|
+
file: Object.freeze({
|
|
938
|
+
path: normalizeSecurityOptionalString(file.path) || DEFAULT_SECURITY_RATE_LIMIT_FILE_PATH
|
|
939
|
+
}),
|
|
940
|
+
redis: Object.freeze((() => {
|
|
941
|
+
const connectionName = normalizeSecurityOptionalString(redis.connection) || redisConfig?.default || DEFAULT_SECURITY_RATE_LIMIT_REDIS_CONNECTION;
|
|
942
|
+
const resolvedConnection = redisConfig ? resolveNormalizedRedisConnection(
|
|
943
|
+
redisConfig,
|
|
944
|
+
connectionName,
|
|
945
|
+
"Security rate-limit Redis connection"
|
|
946
|
+
) : driver === "redis" ? (() => {
|
|
947
|
+
throw new Error(
|
|
948
|
+
`[@holo-js/config] Security rate-limit Redis config references shared Redis connection "${connectionName}" but no top-level redis config is loaded.`
|
|
949
|
+
);
|
|
950
|
+
})() : {
|
|
951
|
+
name: connectionName,
|
|
952
|
+
host: DEFAULT_REDIS_HOST,
|
|
953
|
+
port: DEFAULT_REDIS_PORT,
|
|
954
|
+
password: void 0,
|
|
955
|
+
username: void 0,
|
|
956
|
+
db: DEFAULT_REDIS_DB
|
|
957
|
+
};
|
|
958
|
+
return {
|
|
959
|
+
...typeof resolvedConnection.url === "undefined" ? {} : { url: resolvedConnection.url },
|
|
960
|
+
...typeof resolvedConnection.clusters === "undefined" ? {} : { clusters: resolvedConnection.clusters },
|
|
961
|
+
host: resolvedConnection.host,
|
|
962
|
+
port: resolvedConnection.port,
|
|
963
|
+
password: resolvedConnection.password,
|
|
964
|
+
username: resolvedConnection.username,
|
|
965
|
+
db: resolvedConnection.db,
|
|
966
|
+
connection: resolvedConnection.name,
|
|
967
|
+
prefix: normalizeSecurityOptionalString(redis.prefix) || DEFAULT_SECURITY_RATE_LIMIT_REDIS_PREFIX
|
|
968
|
+
};
|
|
969
|
+
})()),
|
|
970
|
+
limiters
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
function normalizeSecurityConfig(config2 = {}, redisConfig) {
|
|
974
|
+
const csrf = typeof config2.csrf === "boolean" ? { enabled: config2.csrf } : config2.csrf ?? {};
|
|
975
|
+
const except = csrf.except ? Object.freeze(csrf.except.map((value, index) => {
|
|
976
|
+
const normalized = value.trim();
|
|
977
|
+
if (!normalized) {
|
|
978
|
+
throw new Error(`[Holo Security] csrf except entry at index ${index} must be a non-empty string.`);
|
|
979
|
+
}
|
|
980
|
+
return normalized;
|
|
981
|
+
})) : DEFAULT_SECURITY_CSRF_CONFIG.except;
|
|
982
|
+
return Object.freeze({
|
|
983
|
+
csrf: Object.freeze({
|
|
984
|
+
enabled: csrf.enabled ?? DEFAULT_SECURITY_CSRF_CONFIG.enabled,
|
|
985
|
+
field: normalizeSecurityOptionalString(csrf.field) || DEFAULT_SECURITY_CSRF_FIELD,
|
|
986
|
+
header: normalizeSecurityOptionalString(csrf.header) || DEFAULT_SECURITY_CSRF_HEADER,
|
|
987
|
+
cookie: normalizeSecurityOptionalString(csrf.cookie) || DEFAULT_SECURITY_CSRF_COOKIE,
|
|
988
|
+
except
|
|
989
|
+
}),
|
|
990
|
+
rateLimit: normalizeSecurityRateLimitConfig(config2.rateLimit, redisConfig)
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
function normalizeAuthProvider(name, config2) {
|
|
994
|
+
const identifiers = Object.freeze(
|
|
995
|
+
Array.from(new Set((config2.identifiers ?? DEFAULT_AUTH_IDENTIFIERS).map((value) => normalizeNonEmptyString(value, `[Holo Auth] provider "${name}" identifier entries must be non-empty strings.`))))
|
|
996
|
+
);
|
|
997
|
+
if (identifiers.length === 0) {
|
|
998
|
+
throw new Error(`[Holo Auth] provider "${name}" must declare at least one identifier.`);
|
|
999
|
+
}
|
|
1000
|
+
return Object.freeze({
|
|
1001
|
+
name,
|
|
1002
|
+
model: normalizeNonEmptyString(config2.model, `[Holo Auth] provider "${name}" model must be a non-empty string.`),
|
|
1003
|
+
identifiers
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
function normalizeAuthGuard(name, config2, providers) {
|
|
1007
|
+
const provider = config2.provider?.trim() || DEFAULT_AUTH_PROVIDER;
|
|
1008
|
+
if (!(provider in providers)) {
|
|
1009
|
+
throw new Error(`[Holo Auth] guard "${name}" references unknown provider "${provider}".`);
|
|
1010
|
+
}
|
|
1011
|
+
if (config2.driver !== "session" && config2.driver !== "token") {
|
|
1012
|
+
throw new Error(`[Holo Auth] Unsupported auth guard driver "${String(config2.driver)}" on guard "${name}".`);
|
|
1013
|
+
}
|
|
1014
|
+
return Object.freeze({
|
|
1015
|
+
name,
|
|
1016
|
+
driver: config2.driver,
|
|
1017
|
+
provider
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
function normalizePasswordBroker(name, config2, providers) {
|
|
1021
|
+
const provider = config2.provider?.trim() || DEFAULT_AUTH_PROVIDER;
|
|
1022
|
+
if (!(provider in providers)) {
|
|
1023
|
+
throw new Error(`[Holo Auth] password broker "${name}" references unknown provider "${provider}".`);
|
|
1024
|
+
}
|
|
1025
|
+
return Object.freeze({
|
|
1026
|
+
name,
|
|
1027
|
+
provider,
|
|
1028
|
+
table: config2.table?.trim() || DEFAULT_AUTH_PASSWORD_RESET_TABLE,
|
|
1029
|
+
expire: parseInteger(config2.expire, DEFAULT_AUTH_PASSWORD_EXPIRE, `auth password broker "${name}" expire`, {
|
|
1030
|
+
minimum: 0
|
|
1031
|
+
}),
|
|
1032
|
+
throttle: parseInteger(config2.throttle, DEFAULT_AUTH_PASSWORD_THROTTLE, `auth password broker "${name}" throttle`, {
|
|
1033
|
+
minimum: 0
|
|
1034
|
+
})
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
function normalizeSocialProvider(name, config2, guards, providers) {
|
|
1038
|
+
const guard = config2.guard?.trim();
|
|
1039
|
+
if (guard && !(guard in guards)) {
|
|
1040
|
+
throw new Error(`[Holo Auth] social provider "${name}" references unknown guard "${guard}".`);
|
|
1041
|
+
}
|
|
1042
|
+
const mapToProvider = config2.mapToProvider?.trim();
|
|
1043
|
+
if (mapToProvider && !(mapToProvider in providers)) {
|
|
1044
|
+
throw new Error(`[Holo Auth] social provider "${name}" references unknown provider "${mapToProvider}".`);
|
|
1045
|
+
}
|
|
1046
|
+
return Object.freeze({
|
|
1047
|
+
name,
|
|
1048
|
+
runtime: config2.runtime?.trim() || void 0,
|
|
1049
|
+
clientId: config2.clientId?.trim() || void 0,
|
|
1050
|
+
clientSecret: config2.clientSecret?.trim() || void 0,
|
|
1051
|
+
redirectUri: config2.redirectUri?.trim() || void 0,
|
|
1052
|
+
scopes: Object.freeze([...config2.scopes ?? []]),
|
|
1053
|
+
guard,
|
|
1054
|
+
mapToProvider,
|
|
1055
|
+
encryptTokens: config2.encryptTokens === true
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
function normalizeWorkosProvider(name, config2, guards, providers) {
|
|
1059
|
+
const guard = config2.guard?.trim();
|
|
1060
|
+
if (guard && !(guard in guards)) {
|
|
1061
|
+
throw new Error(`[Holo Auth] WorkOS provider "${name}" references unknown guard "${guard}".`);
|
|
1062
|
+
}
|
|
1063
|
+
const mapToProvider = config2.mapToProvider?.trim();
|
|
1064
|
+
if (mapToProvider && !(mapToProvider in providers)) {
|
|
1065
|
+
throw new Error(`[Holo Auth] WorkOS provider "${name}" references unknown provider "${mapToProvider}".`);
|
|
1066
|
+
}
|
|
1067
|
+
return Object.freeze({
|
|
1068
|
+
name,
|
|
1069
|
+
clientId: config2.clientId?.trim() || void 0,
|
|
1070
|
+
apiKey: config2.apiKey?.trim() || void 0,
|
|
1071
|
+
cookiePassword: config2.cookiePassword?.trim() || void 0,
|
|
1072
|
+
redirectUri: config2.redirectUri?.trim() || void 0,
|
|
1073
|
+
sessionCookie: config2.sessionCookie?.trim() || DEFAULT_WORKOS_SESSION_COOKIE,
|
|
1074
|
+
guard,
|
|
1075
|
+
mapToProvider
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
function normalizeClerkProvider(name, config2, guards, providers) {
|
|
1079
|
+
const guard = config2.guard?.trim();
|
|
1080
|
+
if (guard && !(guard in guards)) {
|
|
1081
|
+
throw new Error(`[Holo Auth] Clerk provider "${name}" references unknown guard "${guard}".`);
|
|
1082
|
+
}
|
|
1083
|
+
const mapToProvider = config2.mapToProvider?.trim();
|
|
1084
|
+
if (mapToProvider && !(mapToProvider in providers)) {
|
|
1085
|
+
throw new Error(`[Holo Auth] Clerk provider "${name}" references unknown provider "${mapToProvider}".`);
|
|
1086
|
+
}
|
|
1087
|
+
return Object.freeze({
|
|
1088
|
+
name,
|
|
1089
|
+
publishableKey: config2.publishableKey?.trim() || void 0,
|
|
1090
|
+
secretKey: config2.secretKey?.trim() || void 0,
|
|
1091
|
+
jwtKey: config2.jwtKey?.trim() || void 0,
|
|
1092
|
+
apiUrl: config2.apiUrl?.trim() || void 0,
|
|
1093
|
+
frontendApi: config2.frontendApi?.trim() || void 0,
|
|
1094
|
+
sessionCookie: config2.sessionCookie?.trim() || DEFAULT_CLERK_SESSION_COOKIE,
|
|
1095
|
+
authorizedParties: Object.freeze((config2.authorizedParties ?? []).map((value) => value.trim()).filter(Boolean)),
|
|
1096
|
+
guard,
|
|
1097
|
+
mapToProvider
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
function normalizeAuthConfig(config2 = {}) {
|
|
1101
|
+
const providers = !config2.providers || Object.keys(config2.providers).length === 0 ? holoAuthDefaults.providers : Object.freeze(Object.fromEntries(Object.entries(config2.providers).map(([name, provider]) => {
|
|
1102
|
+
const normalizedName = normalizeConnectionName(name, "Auth provider name");
|
|
1103
|
+
return [normalizedName, normalizeAuthProvider(normalizedName, provider)];
|
|
1104
|
+
})));
|
|
1105
|
+
const guards = !config2.guards || Object.keys(config2.guards).length === 0 ? Object.freeze({
|
|
1106
|
+
[DEFAULT_AUTH_GUARD]: normalizeAuthGuard(
|
|
1107
|
+
DEFAULT_AUTH_GUARD,
|
|
1108
|
+
holoAuthDefaults.guards[DEFAULT_AUTH_GUARD],
|
|
1109
|
+
providers
|
|
1110
|
+
)
|
|
1111
|
+
}) : Object.freeze(Object.fromEntries(Object.entries(config2.guards).map(([name, guard]) => {
|
|
1112
|
+
const normalizedName = normalizeConnectionName(name, "Auth guard name");
|
|
1113
|
+
return [normalizedName, normalizeAuthGuard(normalizedName, guard, providers)];
|
|
1114
|
+
})));
|
|
1115
|
+
const passwords = !config2.passwords || Object.keys(config2.passwords).length === 0 ? Object.freeze({
|
|
1116
|
+
[DEFAULT_AUTH_PASSWORD_BROKER]: normalizePasswordBroker(
|
|
1117
|
+
DEFAULT_AUTH_PASSWORD_BROKER,
|
|
1118
|
+
holoAuthDefaults.passwords[DEFAULT_AUTH_PASSWORD_BROKER],
|
|
1119
|
+
providers
|
|
1120
|
+
)
|
|
1121
|
+
}) : Object.freeze(Object.fromEntries(Object.entries(config2.passwords).map(([name, broker]) => {
|
|
1122
|
+
const normalizedName = normalizeConnectionName(name, "Auth password broker name");
|
|
1123
|
+
return [normalizedName, normalizePasswordBroker(normalizedName, broker, providers)];
|
|
1124
|
+
})));
|
|
1125
|
+
const defaultGuard = config2.defaults?.guard?.trim() || DEFAULT_AUTH_GUARD;
|
|
1126
|
+
if (!(defaultGuard in guards)) {
|
|
1127
|
+
throw new Error(`[Holo Auth] default auth guard "${defaultGuard}" is not configured.`);
|
|
1128
|
+
}
|
|
1129
|
+
const defaultPasswords = config2.defaults?.passwords?.trim() || DEFAULT_AUTH_PASSWORD_BROKER;
|
|
1130
|
+
if (!(defaultPasswords in passwords)) {
|
|
1131
|
+
throw new Error(`[Holo Auth] default password broker "${defaultPasswords}" is not configured.`);
|
|
1132
|
+
}
|
|
1133
|
+
const social = !config2.social || Object.keys(config2.social).length === 0 ? holoAuthDefaults.social : Object.freeze(Object.fromEntries(Object.entries(config2.social).map(([name, provider]) => {
|
|
1134
|
+
const normalizedName = normalizeConnectionName(name, "Auth social provider name");
|
|
1135
|
+
return [normalizedName, normalizeSocialProvider(normalizedName, provider, guards, providers)];
|
|
1136
|
+
})));
|
|
1137
|
+
const workos = !config2.workos || Object.keys(config2.workos).length === 0 ? holoAuthDefaults.workos : Object.freeze(Object.fromEntries(Object.entries(config2.workos).map(([name, provider]) => {
|
|
1138
|
+
const normalizedName = normalizeConnectionName(name, "Auth WorkOS provider name");
|
|
1139
|
+
return [normalizedName, normalizeWorkosProvider(normalizedName, provider, guards, providers)];
|
|
1140
|
+
})));
|
|
1141
|
+
const clerk = !config2.clerk || Object.keys(config2.clerk).length === 0 ? holoAuthDefaults.clerk : Object.freeze(Object.fromEntries(Object.entries(config2.clerk).map(([name, provider]) => {
|
|
1142
|
+
const normalizedName = normalizeConnectionName(name, "Auth Clerk provider name");
|
|
1143
|
+
return [normalizedName, normalizeClerkProvider(normalizedName, provider, guards, providers)];
|
|
1144
|
+
})));
|
|
1145
|
+
return Object.freeze({
|
|
1146
|
+
defaults: Object.freeze({
|
|
1147
|
+
guard: defaultGuard,
|
|
1148
|
+
passwords: defaultPasswords
|
|
1149
|
+
}),
|
|
1150
|
+
guards,
|
|
1151
|
+
providers,
|
|
1152
|
+
passwords,
|
|
1153
|
+
emailVerification: Object.freeze({
|
|
1154
|
+
required: typeof config2.emailVerification === "boolean" ? config2.emailVerification : config2.emailVerification?.required ?? false
|
|
1155
|
+
}),
|
|
1156
|
+
personalAccessTokens: Object.freeze({
|
|
1157
|
+
defaultAbilities: Object.freeze([...config2.personalAccessTokens?.defaultAbilities ?? []])
|
|
1158
|
+
}),
|
|
1159
|
+
socialEncryptionKey: config2.socialEncryptionKey?.trim() || void 0,
|
|
1160
|
+
social,
|
|
1161
|
+
workos,
|
|
1162
|
+
clerk
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
74
1165
|
function normalizeDatabaseConfig(config2 = {}) {
|
|
75
1166
|
const configuredConnections = config2.connections;
|
|
76
1167
|
const connections = configuredConnections && Object.keys(configuredConnections).length > 0 ? Object.freeze({ ...configuredConnections }) : holoDatabaseDefaults.connections;
|
|
@@ -91,8 +1182,357 @@ function normalizeStorageConfig(config2 = {}) {
|
|
|
91
1182
|
})
|
|
92
1183
|
});
|
|
93
1184
|
}
|
|
94
|
-
function
|
|
95
|
-
|
|
1185
|
+
function normalizeOptionalBroadcastString(value, label) {
|
|
1186
|
+
if (typeof value === "undefined") {
|
|
1187
|
+
return void 0;
|
|
1188
|
+
}
|
|
1189
|
+
const normalized = String(value).trim();
|
|
1190
|
+
if (!normalized) {
|
|
1191
|
+
throw new Error(`[Holo Broadcast] ${label} must be a non-empty string when provided.`);
|
|
1192
|
+
}
|
|
1193
|
+
return normalized;
|
|
1194
|
+
}
|
|
1195
|
+
function normalizeBroadcastPort(value, fallback, label) {
|
|
1196
|
+
const normalized = typeof value === "number" ? value : typeof value === "string" && value.trim() ? Number(value.trim()) : fallback;
|
|
1197
|
+
if (!Number.isInteger(normalized) || normalized <= 0) {
|
|
1198
|
+
throw new Error(`[Holo Broadcast] ${label} must be a positive integer.`);
|
|
1199
|
+
}
|
|
1200
|
+
return normalized;
|
|
1201
|
+
}
|
|
1202
|
+
function normalizeBroadcastScheme(value, fallback, label) {
|
|
1203
|
+
const normalized = normalizeOptionalBroadcastString(value, label)?.toLowerCase();
|
|
1204
|
+
if (typeof normalized === "undefined") {
|
|
1205
|
+
return fallback;
|
|
1206
|
+
}
|
|
1207
|
+
if (normalized !== "http" && normalized !== "https") {
|
|
1208
|
+
throw new Error(`[Holo Broadcast] ${label} must be "http" or "https".`);
|
|
1209
|
+
}
|
|
1210
|
+
return normalized;
|
|
1211
|
+
}
|
|
1212
|
+
function normalizeBroadcastConnectionOptions(options, fallbackHost, label) {
|
|
1213
|
+
const scheme = normalizeBroadcastScheme(
|
|
1214
|
+
options?.scheme,
|
|
1215
|
+
options?.useTLS === false ? "http" : "https",
|
|
1216
|
+
`${label} scheme`
|
|
1217
|
+
);
|
|
1218
|
+
const resolvedFallbackPort = scheme === "http" ? DEFAULT_BROADCAST_HTTP_PORT : DEFAULT_BROADCAST_HTTPS_PORT;
|
|
1219
|
+
return Object.freeze({
|
|
1220
|
+
host: normalizeOptionalBroadcastString(options?.host, `${label} host`) ?? fallbackHost,
|
|
1221
|
+
port: normalizeBroadcastPort(options?.port, resolvedFallbackPort, `${label} port`),
|
|
1222
|
+
scheme,
|
|
1223
|
+
useTLS: options?.useTLS ?? scheme === "https",
|
|
1224
|
+
cluster: normalizeOptionalBroadcastString(options?.cluster, `${label} cluster`) ?? void 0
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1227
|
+
function normalizeBroadcastWorkerConfig(worker) {
|
|
1228
|
+
const scaling = worker?.scaling;
|
|
1229
|
+
const publicScheme = normalizeBroadcastScheme(worker?.publicScheme, "https", "Broadcast worker public scheme");
|
|
1230
|
+
if (scaling && scaling.driver !== "redis") {
|
|
1231
|
+
throw new Error('[Holo Broadcast] Broadcast worker scaling driver must be "redis".');
|
|
1232
|
+
}
|
|
1233
|
+
return Object.freeze({
|
|
1234
|
+
host: normalizeOptionalBroadcastString(worker?.host, "Broadcast worker host") ?? DEFAULT_BROADCAST_WORKER_HOST,
|
|
1235
|
+
port: normalizeBroadcastPort(worker?.port, DEFAULT_BROADCAST_WORKER_PORT, "Broadcast worker port"),
|
|
1236
|
+
path: normalizeOptionalBroadcastString(worker?.path, "Broadcast worker path") ?? DEFAULT_BROADCAST_WORKER_PATH,
|
|
1237
|
+
publicHost: normalizeOptionalBroadcastString(worker?.publicHost, "Broadcast worker public host") ?? void 0,
|
|
1238
|
+
publicPort: typeof worker?.publicPort === "undefined" ? publicScheme === "http" ? DEFAULT_BROADCAST_HTTP_PORT : void 0 : normalizeBroadcastPort(
|
|
1239
|
+
worker.publicPort,
|
|
1240
|
+
/* v8 ignore next -- defensive HTTPS port default; tests only exercise HTTP scheme */
|
|
1241
|
+
publicScheme === "http" ? DEFAULT_BROADCAST_HTTP_PORT : DEFAULT_BROADCAST_HTTPS_PORT,
|
|
1242
|
+
"Broadcast worker public port"
|
|
1243
|
+
),
|
|
1244
|
+
publicScheme,
|
|
1245
|
+
healthPath: normalizeOptionalBroadcastString(worker?.healthPath, "Broadcast worker health path") ?? DEFAULT_BROADCAST_HEALTH_PATH,
|
|
1246
|
+
statsPath: normalizeOptionalBroadcastString(worker?.statsPath, "Broadcast worker stats path") ?? DEFAULT_BROADCAST_STATS_PATH,
|
|
1247
|
+
scaling: scaling && typeof scaling === "object" ? Object.freeze({
|
|
1248
|
+
driver: "redis",
|
|
1249
|
+
connection: normalizeOptionalBroadcastString(scaling.connection, "Broadcast worker scaling connection") ?? "default"
|
|
1250
|
+
}) : holoBroadcastDefaults.worker.scaling
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
function normalizeBroadcastConnection(name, connection) {
|
|
1254
|
+
const normalizedName = normalizeOptionalBroadcastString(name, "Broadcast connection name");
|
|
1255
|
+
const driver = normalizeOptionalBroadcastString(connection.driver, `Broadcast connection "${name}" driver`);
|
|
1256
|
+
if (!normalizedName || !driver) {
|
|
1257
|
+
throw new Error("[Holo Broadcast] Broadcast connections must define a name and driver.");
|
|
1258
|
+
}
|
|
1259
|
+
const clientOptions = Object.freeze({
|
|
1260
|
+
...connection.clientOptions ?? {}
|
|
1261
|
+
});
|
|
1262
|
+
if (driver === "holo") {
|
|
1263
|
+
return Object.freeze({
|
|
1264
|
+
name: normalizedName,
|
|
1265
|
+
driver: "holo",
|
|
1266
|
+
key: normalizeOptionalBroadcastString(connection.key, `Broadcast connection "${name}" key`) ?? (() => {
|
|
1267
|
+
throw new Error(`[Holo Broadcast] Broadcast connection "${name}" must define a key.`);
|
|
1268
|
+
})(),
|
|
1269
|
+
secret: normalizeOptionalBroadcastString(connection.secret, `Broadcast connection "${name}" secret`) ?? (() => {
|
|
1270
|
+
throw new Error(`[Holo Broadcast] Broadcast connection "${name}" must define a secret.`);
|
|
1271
|
+
})(),
|
|
1272
|
+
appId: normalizeOptionalBroadcastString(connection.appId, `Broadcast connection "${name}" appId`) ?? (() => {
|
|
1273
|
+
throw new Error(`[Holo Broadcast] Broadcast connection "${name}" must define an appId.`);
|
|
1274
|
+
})(),
|
|
1275
|
+
options: normalizeBroadcastConnectionOptions(connection.options, DEFAULT_BROADCAST_HOST, `Broadcast connection "${name}" options`),
|
|
1276
|
+
clientOptions
|
|
1277
|
+
});
|
|
1278
|
+
}
|
|
1279
|
+
if (driver === "pusher") {
|
|
1280
|
+
const cluster = normalizeOptionalBroadcastString(connection.options?.cluster, `Broadcast connection "${name}" cluster`) ?? void 0;
|
|
1281
|
+
return Object.freeze({
|
|
1282
|
+
name: normalizedName,
|
|
1283
|
+
driver: "pusher",
|
|
1284
|
+
key: normalizeOptionalBroadcastString(connection.key, `Broadcast connection "${name}" key`) ?? (() => {
|
|
1285
|
+
throw new Error(`[Holo Broadcast] Broadcast connection "${name}" must define a key.`);
|
|
1286
|
+
})(),
|
|
1287
|
+
secret: normalizeOptionalBroadcastString(connection.secret, `Broadcast connection "${name}" secret`) ?? (() => {
|
|
1288
|
+
throw new Error(`[Holo Broadcast] Broadcast connection "${name}" must define a secret.`);
|
|
1289
|
+
})(),
|
|
1290
|
+
appId: normalizeOptionalBroadcastString(connection.appId, `Broadcast connection "${name}" appId`) ?? (() => {
|
|
1291
|
+
throw new Error(`[Holo Broadcast] Broadcast connection "${name}" must define an appId.`);
|
|
1292
|
+
})(),
|
|
1293
|
+
options: normalizeBroadcastConnectionOptions(
|
|
1294
|
+
{
|
|
1295
|
+
...connection.options,
|
|
1296
|
+
cluster
|
|
1297
|
+
},
|
|
1298
|
+
normalizeOptionalBroadcastString(connection.options?.host, `Broadcast connection "${name}" host`) ?? (cluster ? `api-${cluster}.pusher.com` : "api-mt1.pusher.com"),
|
|
1299
|
+
`Broadcast connection "${name}" options`
|
|
1300
|
+
),
|
|
1301
|
+
clientOptions
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
if (driver === "log") {
|
|
1305
|
+
return Object.freeze({
|
|
1306
|
+
name: normalizedName,
|
|
1307
|
+
driver: "log",
|
|
1308
|
+
clientOptions
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
if (driver === "null") {
|
|
1312
|
+
return Object.freeze({
|
|
1313
|
+
name: normalizedName,
|
|
1314
|
+
driver: "null",
|
|
1315
|
+
clientOptions
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
if (driver === "ably") {
|
|
1319
|
+
throw new Error('[Holo Broadcast] Broadcast driver "ably" is not supported yet.');
|
|
1320
|
+
}
|
|
1321
|
+
const {
|
|
1322
|
+
driver: _ignoredDriver,
|
|
1323
|
+
clientOptions: _ignoredClientOptions,
|
|
1324
|
+
...customConfig
|
|
1325
|
+
} = connection;
|
|
1326
|
+
return Object.freeze({
|
|
1327
|
+
driver,
|
|
1328
|
+
clientOptions,
|
|
1329
|
+
...customConfig,
|
|
1330
|
+
name: normalizedName
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
function normalizeBroadcastConfig(config2 = {}) {
|
|
1334
|
+
const normalizedConnections = Object.fromEntries(
|
|
1335
|
+
Object.entries(config2.connections ?? holoBroadcastDefaults.connections).map(([name, connection]) => [name, normalizeBroadcastConnection(name, connection)])
|
|
1336
|
+
);
|
|
1337
|
+
const defaultConnection = normalizeOptionalBroadcastString(config2.default, "Default broadcast connection") ?? holoBroadcastDefaults.default;
|
|
1338
|
+
if (!normalizedConnections[defaultConnection]) {
|
|
1339
|
+
throw new Error(
|
|
1340
|
+
`[Holo Broadcast] default broadcast connection "${defaultConnection}" is not configured. Available connections: ${Object.keys(normalizedConnections).join(", ")}`
|
|
1341
|
+
);
|
|
1342
|
+
}
|
|
1343
|
+
return Object.freeze({
|
|
1344
|
+
default: defaultConnection,
|
|
1345
|
+
connections: Object.freeze(normalizedConnections),
|
|
1346
|
+
worker: normalizeBroadcastWorkerConfig(config2.worker)
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
function normalizeOptionalMailString(value, label) {
|
|
1350
|
+
if (typeof value === "undefined") {
|
|
1351
|
+
return void 0;
|
|
1352
|
+
}
|
|
1353
|
+
const normalized = value.trim();
|
|
1354
|
+
if (!normalized) {
|
|
1355
|
+
throw new Error(`[Holo Mail] ${label} must be a non-empty string when provided.`);
|
|
1356
|
+
}
|
|
1357
|
+
return normalized;
|
|
1358
|
+
}
|
|
1359
|
+
function isValidMailAddress(email) {
|
|
1360
|
+
if (email.includes(" ")) {
|
|
1361
|
+
return false;
|
|
1362
|
+
}
|
|
1363
|
+
const parts = email.split("@");
|
|
1364
|
+
return parts.length === 2 && parts[0].length > 0 && parts[1].length > 0;
|
|
1365
|
+
}
|
|
1366
|
+
function normalizeMailAddress(address, label) {
|
|
1367
|
+
if (!address) {
|
|
1368
|
+
return void 0;
|
|
1369
|
+
}
|
|
1370
|
+
const email = normalizeOptionalMailString(address.email, `${label} email`)?.toLowerCase();
|
|
1371
|
+
if (!email || !isValidMailAddress(email)) {
|
|
1372
|
+
throw new Error(`[Holo Mail] ${label} email must be a valid email address.`);
|
|
1373
|
+
}
|
|
1374
|
+
const name = normalizeOptionalMailString(address.name, `${label} name`);
|
|
1375
|
+
return Object.freeze({
|
|
1376
|
+
email,
|
|
1377
|
+
...name ? { name } : {}
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
function normalizeMailQueueConfig(queue, fallback = holoMailDefaults.queue) {
|
|
1381
|
+
return Object.freeze({
|
|
1382
|
+
queued: queue?.queued ?? fallback.queued,
|
|
1383
|
+
connection: normalizeOptionalMailString(queue?.connection, "Mail queue connection") ?? fallback.connection,
|
|
1384
|
+
queue: normalizeOptionalMailString(queue?.queue, "Mail queue name") ?? fallback.queue,
|
|
1385
|
+
afterCommit: queue?.afterCommit ?? fallback.afterCommit
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
function normalizeMailPreviewEnvironments(environments) {
|
|
1389
|
+
if (typeof environments === "undefined") {
|
|
1390
|
+
return holoMailDefaults.preview.allowedEnvironments;
|
|
1391
|
+
}
|
|
1392
|
+
const normalized = /* @__PURE__ */ new Set();
|
|
1393
|
+
for (const environment of environments) {
|
|
1394
|
+
if (environment !== "development" && environment !== "production" && environment !== "test") {
|
|
1395
|
+
throw new Error("[Holo Mail] Mail preview environments must be development, production, or test.");
|
|
1396
|
+
}
|
|
1397
|
+
normalized.add(environment);
|
|
1398
|
+
}
|
|
1399
|
+
return Object.freeze([...normalized]);
|
|
1400
|
+
}
|
|
1401
|
+
function normalizeMailMailerConfig(name, config2, fallback) {
|
|
1402
|
+
const normalizedName = normalizeOptionalMailString(name, "Mail mailer name");
|
|
1403
|
+
const driver = normalizeOptionalMailString(config2.driver, `Mail mailer "${name}" driver`);
|
|
1404
|
+
if (!normalizedName || !driver) {
|
|
1405
|
+
throw new Error("[Holo Mail] Mailers must define a name and driver.");
|
|
1406
|
+
}
|
|
1407
|
+
const base = {
|
|
1408
|
+
name: normalizedName,
|
|
1409
|
+
driver,
|
|
1410
|
+
from: normalizeMailAddress(config2.from, `Mail mailer "${name}" from`) ?? fallback?.from,
|
|
1411
|
+
replyTo: normalizeMailAddress(config2.replyTo, `Mail mailer "${name}" replyTo`) ?? fallback?.replyTo,
|
|
1412
|
+
queue: normalizeMailQueueConfig(config2.queue, fallback?.queue ?? holoMailDefaults.queue)
|
|
1413
|
+
};
|
|
1414
|
+
if (driver === "preview") {
|
|
1415
|
+
const previewFallback = fallback?.driver === "preview" ? fallback : void 0;
|
|
1416
|
+
return Object.freeze({
|
|
1417
|
+
...base,
|
|
1418
|
+
driver: "preview",
|
|
1419
|
+
path: normalizeOptionalMailString(config2.path, `Mail mailer "${name}" preview path`) ?? previewFallback?.path ?? DEFAULT_MAIL_PREVIEW_PATH
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
if (driver === "log") {
|
|
1423
|
+
const logFallback = fallback?.driver === "log" ? fallback : void 0;
|
|
1424
|
+
return Object.freeze({
|
|
1425
|
+
...base,
|
|
1426
|
+
driver: "log",
|
|
1427
|
+
logBodies: config2.logBodies ?? logFallback?.logBodies ?? false
|
|
1428
|
+
});
|
|
1429
|
+
}
|
|
1430
|
+
if (driver === "fake") {
|
|
1431
|
+
return Object.freeze({
|
|
1432
|
+
...base,
|
|
1433
|
+
driver: "fake"
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
if (driver === "smtp") {
|
|
1437
|
+
const smtpFallback = fallback?.driver === "smtp" ? fallback : void 0;
|
|
1438
|
+
const rawPort = config2.port;
|
|
1439
|
+
const normalizedPort = typeof rawPort === "number" ? rawPort : typeof rawPort === "string" && rawPort.trim() ? Number(rawPort.trim()) : smtpFallback?.port ?? DEFAULT_SMTP_PORT;
|
|
1440
|
+
if (!Number.isFinite(normalizedPort) || normalizedPort <= 0) {
|
|
1441
|
+
throw new Error(`[Holo Mail] Mail mailer "${name}" SMTP port must be a positive number.`);
|
|
1442
|
+
}
|
|
1443
|
+
return Object.freeze({
|
|
1444
|
+
...base,
|
|
1445
|
+
driver: "smtp",
|
|
1446
|
+
host: normalizeOptionalMailString(config2.host, `Mail mailer "${name}" SMTP host`) ?? smtpFallback?.host ?? DEFAULT_SMTP_HOST,
|
|
1447
|
+
port: normalizedPort,
|
|
1448
|
+
secure: config2.secure ?? smtpFallback?.secure ?? false,
|
|
1449
|
+
user: normalizeOptionalMailString(config2.user, `Mail mailer "${name}" SMTP user`) ?? smtpFallback?.user ?? void 0,
|
|
1450
|
+
password: normalizeOptionalMailString(config2.password, `Mail mailer "${name}" SMTP password`) ?? smtpFallback?.password ?? void 0
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
const customFields = Object.fromEntries(
|
|
1454
|
+
Object.entries(config2).filter(([key]) => key !== "driver" && key !== "from" && key !== "replyTo" && key !== "queue")
|
|
1455
|
+
);
|
|
1456
|
+
return Object.freeze({
|
|
1457
|
+
...base,
|
|
1458
|
+
...customFields
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
function normalizeMailConfig(config2 = {}) {
|
|
1462
|
+
const mergedMailers = {
|
|
1463
|
+
...holoMailDefaults.mailers
|
|
1464
|
+
};
|
|
1465
|
+
for (const [name, mailer] of Object.entries(config2.mailers ?? {})) {
|
|
1466
|
+
mergedMailers[name] = normalizeMailMailerConfig(name, mailer, mergedMailers[name]);
|
|
1467
|
+
}
|
|
1468
|
+
const defaultMailer = normalizeOptionalMailString(config2.default, "Default mailer") ?? holoMailDefaults.default;
|
|
1469
|
+
if (!mergedMailers[defaultMailer]) {
|
|
1470
|
+
throw new Error(
|
|
1471
|
+
`[Holo Mail] default mailer "${defaultMailer}" is not configured. Available mailers: ${Object.keys(mergedMailers).join(", ")}`
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
return Object.freeze({
|
|
1475
|
+
default: defaultMailer,
|
|
1476
|
+
from: normalizeMailAddress(config2.from, "Mail from") ?? holoMailDefaults.from,
|
|
1477
|
+
replyTo: normalizeMailAddress(config2.replyTo, "Mail replyTo") ?? holoMailDefaults.replyTo,
|
|
1478
|
+
queue: normalizeMailQueueConfig(config2.queue),
|
|
1479
|
+
preview: Object.freeze({
|
|
1480
|
+
allowedEnvironments: normalizeMailPreviewEnvironments(config2.preview?.allowedEnvironments)
|
|
1481
|
+
}),
|
|
1482
|
+
markdown: Object.freeze({
|
|
1483
|
+
wrapper: normalizeOptionalMailString(config2.markdown?.wrapper, "Mail markdown wrapper") ?? holoMailDefaults.markdown.wrapper
|
|
1484
|
+
}),
|
|
1485
|
+
mailers: Object.freeze(mergedMailers)
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
function normalizeOptionalNotificationsString(value, label) {
|
|
1489
|
+
if (typeof value === "undefined") {
|
|
1490
|
+
return void 0;
|
|
1491
|
+
}
|
|
1492
|
+
const normalized = value.trim();
|
|
1493
|
+
if (!normalized) {
|
|
1494
|
+
throw new Error(`[Holo Notifications] ${label} must be a non-empty string when provided.`);
|
|
1495
|
+
}
|
|
1496
|
+
return normalized;
|
|
1497
|
+
}
|
|
1498
|
+
function normalizeNotificationsConfig(config2 = {}) {
|
|
1499
|
+
return Object.freeze({
|
|
1500
|
+
table: normalizeOptionalNotificationsString(config2.table, "Notifications table") ?? DEFAULT_NOTIFICATIONS_TABLE,
|
|
1501
|
+
queue: Object.freeze({
|
|
1502
|
+
connection: normalizeOptionalNotificationsString(config2.queue?.connection, "Notifications queue connection"),
|
|
1503
|
+
queue: normalizeOptionalNotificationsString(config2.queue?.queue, "Notifications queue name"),
|
|
1504
|
+
afterCommit: config2.queue?.afterCommit === true
|
|
1505
|
+
})
|
|
1506
|
+
});
|
|
1507
|
+
}
|
|
1508
|
+
function normalizeRedisConfig(config2 = {}) {
|
|
1509
|
+
const connections = normalizeRedisConnections(config2.connections);
|
|
1510
|
+
const connectionNames = Object.keys(connections);
|
|
1511
|
+
const defaultConnection = config2.default?.trim() || connectionNames[0];
|
|
1512
|
+
if (!connections[defaultConnection]) {
|
|
1513
|
+
throw new Error(
|
|
1514
|
+
`[Holo Redis] default redis connection "${defaultConnection}" is not configured. Available connections: ${connectionNames.join(", ")}`
|
|
1515
|
+
);
|
|
1516
|
+
}
|
|
1517
|
+
return Object.freeze({
|
|
1518
|
+
default: defaultConnection,
|
|
1519
|
+
connections
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1522
|
+
function normalizeQueueConfigForHolo(config2 = {}, redisConfig) {
|
|
1523
|
+
const connections = normalizeConnections(config2.connections, redisConfig);
|
|
1524
|
+
const connectionNames = Object.keys(connections);
|
|
1525
|
+
const defaultConnection = config2.default?.trim() || connectionNames[0];
|
|
1526
|
+
if (!connections[defaultConnection]) {
|
|
1527
|
+
throw new Error(
|
|
1528
|
+
`[Holo Queue] default queue connection "${defaultConnection}" is not configured. Available connections: ${connectionNames.join(", ")}`
|
|
1529
|
+
);
|
|
1530
|
+
}
|
|
1531
|
+
return Object.freeze({
|
|
1532
|
+
default: defaultConnection,
|
|
1533
|
+
failed: normalizeFailedStore(config2.failed),
|
|
1534
|
+
connections
|
|
1535
|
+
});
|
|
96
1536
|
}
|
|
97
1537
|
|
|
98
1538
|
// src/access.ts
|
|
@@ -312,11 +1752,11 @@ function resolveEnvPlaceholders(value, envValues) {
|
|
|
312
1752
|
|
|
313
1753
|
// src/loader.ts
|
|
314
1754
|
import { mkdir, readFile as readFile2, readdir, rm, writeFile } from "fs/promises";
|
|
315
|
-
import { dirname, extname, join as join2, resolve as resolve2 } from "path";
|
|
1755
|
+
import { basename as basename2, dirname, extname, join as join2, resolve as resolve2 } from "path";
|
|
316
1756
|
import { pathToFileURL } from "url";
|
|
317
1757
|
var CONFIG_EXTENSION_PRIORITY = [".ts", ".mts", ".js", ".mjs", ".cts", ".cjs"];
|
|
318
1758
|
var SUPPORTED_CONFIG_EXTENSIONS = new Set(CONFIG_EXTENSION_PRIORITY);
|
|
319
|
-
var HOLO_CONFIG_CACHE_VERSION =
|
|
1759
|
+
var HOLO_CONFIG_CACHE_VERSION = 3;
|
|
320
1760
|
var HOLO_CONFIG_CACHE_PATH = join2(".holo-js", "generated", "config-cache.json");
|
|
321
1761
|
var TRANSIENT_CONFIG_IMPORT_MARKER = ".__holo_import_";
|
|
322
1762
|
var LEGACY_TRANSIENT_CONFIG_IMPORT_MARKERS = [TRANSIENT_CONFIG_IMPORT_MARKER, ".__native_test__"];
|
|
@@ -411,6 +1851,7 @@ async function collectRawConfig(configDir, environmentValues, options = {}) {
|
|
|
411
1851
|
const rawConfig = {};
|
|
412
1852
|
const loadedFiles = [];
|
|
413
1853
|
const configEntries = await collectConfigEntries(configDir);
|
|
1854
|
+
const allowedConfigNames = options.onlyConfigNames ? new Set(options.onlyConfigNames) : void 0;
|
|
414
1855
|
const previousEnvEntries = /* @__PURE__ */ new Map();
|
|
415
1856
|
try {
|
|
416
1857
|
configureEnvRuntime(environmentValues, {
|
|
@@ -427,6 +1868,9 @@ async function collectRawConfig(configDir, environmentValues, options = {}) {
|
|
|
427
1868
|
Reflect.deleteProperty(process.env, "HOLO_CAPTURE_ENV");
|
|
428
1869
|
}
|
|
429
1870
|
for (const entry of configEntries) {
|
|
1871
|
+
if (allowedConfigNames && !allowedConfigNames.has(entry.configName)) {
|
|
1872
|
+
continue;
|
|
1873
|
+
}
|
|
430
1874
|
rawConfig[entry.configName] = resolveConfigExport(await importConfigModule(entry.filePath));
|
|
431
1875
|
loadedFiles.push(entry.filePath);
|
|
432
1876
|
}
|
|
@@ -445,31 +1889,69 @@ async function collectRawConfig(configDir, environmentValues, options = {}) {
|
|
|
445
1889
|
loadedFiles: Object.freeze([...loadedFiles])
|
|
446
1890
|
};
|
|
447
1891
|
}
|
|
1892
|
+
function mergeLoadedFiles(cachedFiles, liveFiles, deferredConfigNames) {
|
|
1893
|
+
const deferredNames = new Set(deferredConfigNames);
|
|
1894
|
+
const retainedCachedFiles = cachedFiles.filter((filePath) => {
|
|
1895
|
+
return !deferredNames.has(getConfigName(basename2(filePath)));
|
|
1896
|
+
});
|
|
1897
|
+
return Object.freeze([
|
|
1898
|
+
...retainedCachedFiles,
|
|
1899
|
+
...liveFiles
|
|
1900
|
+
]);
|
|
1901
|
+
}
|
|
448
1902
|
function normalizeLoadedConfig(rawConfig, options) {
|
|
449
1903
|
const resolvedRawConfig = resolveEnvPlaceholders(rawConfig, options.environment.values);
|
|
450
1904
|
const app = normalizeAppConfig(resolvedRawConfig.app);
|
|
451
1905
|
const database = normalizeDatabaseConfig(resolvedRawConfig.database);
|
|
1906
|
+
const redis = normalizeRedisConfig(resolvedRawConfig.redis);
|
|
1907
|
+
const resolvedRedisConfig = typeof resolvedRawConfig.redis === "undefined" ? void 0 : redis;
|
|
1908
|
+
const cache = normalizeCacheConfig(resolvedRawConfig.cache, {
|
|
1909
|
+
database,
|
|
1910
|
+
redis: resolvedRedisConfig
|
|
1911
|
+
});
|
|
452
1912
|
const storage = normalizeStorageConfig(resolvedRawConfig.storage);
|
|
453
|
-
const queue = normalizeQueueConfigForHolo(resolvedRawConfig.queue);
|
|
1913
|
+
const queue = normalizeQueueConfigForHolo(resolvedRawConfig.queue, resolvedRedisConfig);
|
|
1914
|
+
const broadcast = normalizeBroadcastConfig(resolvedRawConfig.broadcast);
|
|
1915
|
+
const mail = normalizeMailConfig(resolvedRawConfig.mail);
|
|
1916
|
+
const notifications = normalizeNotificationsConfig(resolvedRawConfig.notifications);
|
|
454
1917
|
const media = Object.freeze({ ...resolvedRawConfig.media ?? {} });
|
|
1918
|
+
const session = normalizeSessionConfig(resolvedRawConfig.session, resolvedRedisConfig);
|
|
1919
|
+
const security = normalizeSecurityConfig(resolvedRawConfig.security, resolvedRedisConfig);
|
|
1920
|
+
const auth = normalizeAuthConfig(resolvedRawConfig.auth);
|
|
455
1921
|
const customEntries = Object.entries(resolvedRawConfig).filter(([key]) => {
|
|
456
|
-
return key !== "app" && key !== "database" && key !== "storage" && key !== "queue" && key !== "media";
|
|
1922
|
+
return key !== "app" && key !== "database" && key !== "redis" && key !== "cache" && key !== "storage" && key !== "queue" && key !== "broadcast" && key !== "mail" && key !== "notifications" && key !== "media" && key !== "session" && key !== "security" && key !== "auth";
|
|
457
1923
|
});
|
|
458
1924
|
const custom = Object.freeze(Object.fromEntries(customEntries));
|
|
459
1925
|
const all = Object.freeze({
|
|
460
1926
|
app,
|
|
461
1927
|
database,
|
|
1928
|
+
redis,
|
|
1929
|
+
cache,
|
|
462
1930
|
storage,
|
|
463
1931
|
queue,
|
|
1932
|
+
broadcast,
|
|
1933
|
+
mail,
|
|
1934
|
+
notifications,
|
|
464
1935
|
media,
|
|
1936
|
+
session,
|
|
1937
|
+
security,
|
|
1938
|
+
auth,
|
|
465
1939
|
...custom
|
|
466
1940
|
});
|
|
467
1941
|
return {
|
|
468
1942
|
app,
|
|
469
1943
|
database,
|
|
1944
|
+
redis,
|
|
1945
|
+
cache,
|
|
470
1946
|
storage,
|
|
471
1947
|
queue,
|
|
1948
|
+
broadcast,
|
|
1949
|
+
mail,
|
|
1950
|
+
notifications,
|
|
472
1951
|
media,
|
|
1952
|
+
session,
|
|
1953
|
+
security,
|
|
1954
|
+
auth,
|
|
473
1955
|
custom,
|
|
474
1956
|
all,
|
|
475
1957
|
environment: options.environment,
|
|
@@ -489,13 +1971,30 @@ function isSerializableConfigValue(value) {
|
|
|
489
1971
|
}
|
|
490
1972
|
return false;
|
|
491
1973
|
}
|
|
492
|
-
function
|
|
493
|
-
|
|
1974
|
+
function splitCacheableConfig(rawConfig) {
|
|
1975
|
+
const cacheableConfig = {};
|
|
1976
|
+
const deferredConfigNames = [];
|
|
1977
|
+
for (const [name, value] of Object.entries(rawConfig)) {
|
|
1978
|
+
if (isSerializableConfigValue(value)) {
|
|
1979
|
+
cacheableConfig[name] = value;
|
|
1980
|
+
continue;
|
|
1981
|
+
}
|
|
1982
|
+
if (name === "security") {
|
|
1983
|
+
deferredConfigNames.push(name);
|
|
1984
|
+
continue;
|
|
1985
|
+
}
|
|
494
1986
|
throw new TypeError("Holo config cache only supports plain JSON-serializable config values.");
|
|
495
1987
|
}
|
|
1988
|
+
return {
|
|
1989
|
+
cacheableConfig,
|
|
1990
|
+
deferredConfigNames: Object.freeze([...deferredConfigNames])
|
|
1991
|
+
};
|
|
1992
|
+
}
|
|
1993
|
+
function getDeferredConfigNames(payload) {
|
|
1994
|
+
return Array.isArray(payload.deferredConfigNames) ? payload.deferredConfigNames : Object.freeze([]);
|
|
496
1995
|
}
|
|
497
1996
|
function isCachePayload(value) {
|
|
498
|
-
return isObject2(value) && 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);
|
|
1997
|
+
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));
|
|
499
1998
|
}
|
|
500
1999
|
function resolveConfigCachePath(projectRoot) {
|
|
501
2000
|
return join2(resolve2(projectRoot), HOLO_CONFIG_CACHE_PATH);
|
|
@@ -524,7 +2023,10 @@ async function writeConfigCache(projectRoot, options = {}) {
|
|
|
524
2023
|
const { rawConfig, loadedFiles } = await collectRawConfig(configDir, environment.values, {
|
|
525
2024
|
captureEnvPlaceholders: true
|
|
526
2025
|
});
|
|
527
|
-
|
|
2026
|
+
const {
|
|
2027
|
+
cacheableConfig,
|
|
2028
|
+
deferredConfigNames
|
|
2029
|
+
} = splitCacheableConfig(rawConfig);
|
|
528
2030
|
const cachePath = resolveConfigCachePath(root);
|
|
529
2031
|
const contents = `${JSON.stringify({
|
|
530
2032
|
version: HOLO_CONFIG_CACHE_VERSION,
|
|
@@ -534,7 +2036,8 @@ async function writeConfigCache(projectRoot, options = {}) {
|
|
|
534
2036
|
warnings: environment.warnings
|
|
535
2037
|
},
|
|
536
2038
|
configFiles: loadedFiles,
|
|
537
|
-
config:
|
|
2039
|
+
config: cacheableConfig,
|
|
2040
|
+
deferredConfigNames
|
|
538
2041
|
}, null, 2)}
|
|
539
2042
|
`;
|
|
540
2043
|
await writeFileIfChanged(cachePath, contents);
|
|
@@ -562,9 +2065,22 @@ async function loadConfigDirectory(projectRoot, options = {}) {
|
|
|
562
2065
|
envName,
|
|
563
2066
|
processEnv: options.processEnv
|
|
564
2067
|
});
|
|
565
|
-
|
|
2068
|
+
const deferredConfigNames = getDeferredConfigNames(cached);
|
|
2069
|
+
let rawConfig2 = cached.config;
|
|
2070
|
+
let loadedFiles2 = cached.configFiles;
|
|
2071
|
+
if (deferredConfigNames.length > 0) {
|
|
2072
|
+
const live = await collectRawConfig(configDir, environment2.values, {
|
|
2073
|
+
onlyConfigNames: deferredConfigNames
|
|
2074
|
+
});
|
|
2075
|
+
rawConfig2 = {
|
|
2076
|
+
...cached.config,
|
|
2077
|
+
...live.rawConfig
|
|
2078
|
+
};
|
|
2079
|
+
loadedFiles2 = mergeLoadedFiles(cached.configFiles, live.loadedFiles, deferredConfigNames);
|
|
2080
|
+
}
|
|
2081
|
+
return normalizeLoadedConfig(rawConfig2, {
|
|
566
2082
|
environment: environment2,
|
|
567
|
-
loadedFiles:
|
|
2083
|
+
loadedFiles: loadedFiles2
|
|
568
2084
|
});
|
|
569
2085
|
}
|
|
570
2086
|
}
|
|
@@ -588,18 +2104,44 @@ function defineAppConfig(config2) {
|
|
|
588
2104
|
function defineDatabaseConfig(config2) {
|
|
589
2105
|
return defineConfig(config2);
|
|
590
2106
|
}
|
|
2107
|
+
function defineRedisConfig(config2) {
|
|
2108
|
+
return defineConfig(config2);
|
|
2109
|
+
}
|
|
2110
|
+
function defineCacheConfig(config2) {
|
|
2111
|
+
return defineConfig(config2);
|
|
2112
|
+
}
|
|
591
2113
|
function defineStorageConfig(config2) {
|
|
592
2114
|
return defineConfig(config2);
|
|
593
2115
|
}
|
|
594
2116
|
function defineQueueConfig(config2) {
|
|
595
2117
|
return defineConfig(config2);
|
|
596
2118
|
}
|
|
2119
|
+
function defineBroadcastConfig(config2) {
|
|
2120
|
+
return defineConfig(config2);
|
|
2121
|
+
}
|
|
2122
|
+
function defineMailConfig(config2) {
|
|
2123
|
+
return defineConfig(config2);
|
|
2124
|
+
}
|
|
2125
|
+
function defineNotificationsConfig(config2) {
|
|
2126
|
+
return defineConfig(config2);
|
|
2127
|
+
}
|
|
597
2128
|
function defineMediaConfig(config2) {
|
|
598
2129
|
return defineConfig(config2);
|
|
599
2130
|
}
|
|
2131
|
+
function defineSessionConfig(config2) {
|
|
2132
|
+
return defineConfig(config2);
|
|
2133
|
+
}
|
|
2134
|
+
function defineSecurityConfig(config2) {
|
|
2135
|
+
return defineConfig(config2);
|
|
2136
|
+
}
|
|
2137
|
+
function defineAuthConfig(config2) {
|
|
2138
|
+
return defineConfig(config2);
|
|
2139
|
+
}
|
|
600
2140
|
var loaderInternals = {
|
|
2141
|
+
getDeferredConfigNames,
|
|
601
2142
|
getConfigExtensionPriority,
|
|
602
|
-
resolveConfigExport
|
|
2143
|
+
resolveConfigExport,
|
|
2144
|
+
splitCacheableConfig
|
|
603
2145
|
};
|
|
604
2146
|
export {
|
|
605
2147
|
DEFAULT_APP_NAME,
|
|
@@ -609,15 +2151,31 @@ export {
|
|
|
609
2151
|
configureEnvRuntime,
|
|
610
2152
|
createConfigAccessors,
|
|
611
2153
|
defineAppConfig,
|
|
2154
|
+
defineAuthConfig,
|
|
2155
|
+
defineBroadcastConfig,
|
|
2156
|
+
defineCacheConfig,
|
|
612
2157
|
defineConfig,
|
|
613
2158
|
defineDatabaseConfig,
|
|
2159
|
+
defineMailConfig,
|
|
614
2160
|
defineMediaConfig,
|
|
2161
|
+
defineNotificationsConfig,
|
|
615
2162
|
defineQueueConfig,
|
|
2163
|
+
defineRedisConfig,
|
|
2164
|
+
defineSecurityConfig,
|
|
2165
|
+
defineSessionConfig,
|
|
616
2166
|
defineStorageConfig,
|
|
617
2167
|
env,
|
|
618
2168
|
holoAppDefaults,
|
|
2169
|
+
holoAuthDefaults,
|
|
2170
|
+
holoBroadcastDefaults,
|
|
2171
|
+
holoCacheDefaults,
|
|
619
2172
|
holoDatabaseDefaults,
|
|
2173
|
+
holoMailDefaults,
|
|
2174
|
+
holoNotificationsDefaults,
|
|
620
2175
|
holoQueueDefaultsNormalized,
|
|
2176
|
+
holoRedisDefaults,
|
|
2177
|
+
holoSecurityDefaults,
|
|
2178
|
+
holoSessionDefaults,
|
|
621
2179
|
holoStorageDefaults,
|
|
622
2180
|
isEnvPlaceholder,
|
|
623
2181
|
loadConfigDirectory,
|
|
@@ -625,8 +2183,16 @@ export {
|
|
|
625
2183
|
loaderInternals,
|
|
626
2184
|
normalizeAppConfig,
|
|
627
2185
|
normalizeAppEnv,
|
|
2186
|
+
normalizeAuthConfig,
|
|
2187
|
+
normalizeBroadcastConfig,
|
|
2188
|
+
normalizeCacheConfig,
|
|
628
2189
|
normalizeDatabaseConfig,
|
|
2190
|
+
normalizeMailConfig,
|
|
2191
|
+
normalizeNotificationsConfig,
|
|
629
2192
|
normalizeQueueConfigForHolo,
|
|
2193
|
+
normalizeRedisConfig,
|
|
2194
|
+
normalizeSecurityConfig,
|
|
2195
|
+
normalizeSessionConfig,
|
|
630
2196
|
normalizeStorageConfig,
|
|
631
2197
|
resetConfigRuntime,
|
|
632
2198
|
resolveAppEnvironment,
|