@getstrata/bootstrap 0.2.46 → 0.2.48

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/README.md CHANGED
@@ -28,4 +28,8 @@ import {
28
28
  import { createHttpKernel, createAppContext, coreProviders } from "@getstrata/bootstrap";
29
29
  ```
30
30
 
31
+ `assertProductionSecrets` (`@getstrata/bootstrap/secretsGuard`) is env-only. It still rejects the well-known WorkHub test tokens.
32
+
33
+ These subpaths remain WorkHub-oriented and are not a generic starter API: `@getstrata/bootstrap/createRoutes` (includes SCIM), `@getstrata/bootstrap/schedule`, and `@getstrata/bootstrap/createWebRoutes` (redirects `/` to `/organizations`).
34
+
31
35
  See the [strata](https://github.com/EyK-26/strata) monorepo reference app for full module patterns.
@@ -0,0 +1,174 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>WorkHub API</title>
7
+ <style>
8
+ :root {
9
+ color-scheme: dark;
10
+ }
11
+
12
+ body {
13
+ font-family: sans-serif;
14
+ background-color: #000;
15
+ color: #fff;
16
+ margin: 0;
17
+ padding: 2rem;
18
+ line-height: 1.5;
19
+ }
20
+
21
+ h1,
22
+ h2 {
23
+ margin-top: 0;
24
+ }
25
+
26
+ section {
27
+ margin-bottom: 2rem;
28
+ }
29
+
30
+ ul {
31
+ padding-left: 1.25rem;
32
+ }
33
+
34
+ li {
35
+ padding: 0.35rem 0;
36
+ }
37
+
38
+ a {
39
+ color: #fff;
40
+ }
41
+
42
+ code,
43
+ input,
44
+ button {
45
+ font: inherit;
46
+ }
47
+
48
+ form {
49
+ display: flex;
50
+ flex-wrap: wrap;
51
+ gap: 0.5rem;
52
+ margin: 0.75rem 0;
53
+ }
54
+
55
+ input {
56
+ border: 1px solid #555;
57
+ background-color: #111;
58
+ color: #fff;
59
+ padding: 0.5rem 0.75rem;
60
+ border-radius: 0.35rem;
61
+ min-width: 14rem;
62
+ }
63
+
64
+ button {
65
+ border: 1px solid #777;
66
+ background-color: #222;
67
+ color: #fff;
68
+ padding: 0.5rem 0.9rem;
69
+ border-radius: 0.35rem;
70
+ cursor: pointer;
71
+ }
72
+
73
+ .hint {
74
+ color: #bbb;
75
+ font-size: 0.95rem;
76
+ }
77
+ </style>
78
+ <script type="module" crossorigin src="../../index-sfreg6q3.js"></script></head>
79
+
80
+ <body>
81
+ <h1>WorkHub API</h1>
82
+ <p class="hint">
83
+ Framework reference domain for organizations, projects, tasks, and
84
+ comments.
85
+ </p>
86
+
87
+ <section>
88
+ <h2>Collections</h2>
89
+ <ul>
90
+ <li><a href="/organizations">/organizations</a></li>
91
+ <li><a href="/projects?include=organization">/projects</a></li>
92
+ <li><a href="/tasks?include=project">/tasks</a></li>
93
+ <li><a href="/comments">/comments</a></li>
94
+ <li><a href="/reports/summary">/reports/summary</a></li>
95
+ </ul>
96
+ </section>
97
+
98
+ <section>
99
+ <h2>Lookup by ID</h2>
100
+
101
+ <form data-prefix="/organizations/">
102
+ <input
103
+ type="number"
104
+ min="1"
105
+ name="id"
106
+ placeholder="Organization ID"
107
+ required
108
+ />
109
+ <button type="submit">Open /organizations/:id</button>
110
+ </form>
111
+
112
+ <form data-prefix="/projects/">
113
+ <input
114
+ type="number"
115
+ min="1"
116
+ name="id"
117
+ placeholder="Project ID"
118
+ required
119
+ />
120
+ <button type="submit">Open /projects/:id</button>
121
+ </form>
122
+
123
+ <form data-prefix="/tasks/">
124
+ <input
125
+ type="number"
126
+ min="1"
127
+ name="id"
128
+ placeholder="Task ID"
129
+ required
130
+ />
131
+ <button type="submit">Open /tasks/:id</button>
132
+ </form>
133
+
134
+ <form data-prefix="/tasks/" data-suffix="/comments">
135
+ <input
136
+ type="number"
137
+ min="1"
138
+ name="id"
139
+ placeholder="Task ID"
140
+ required
141
+ />
142
+ <button type="submit">Open /tasks/:id/comments</button>
143
+ </form>
144
+
145
+ <form data-prefix="/reports/organizations/">
146
+ <input
147
+ type="number"
148
+ min="1"
149
+ name="id"
150
+ placeholder="Organization ID"
151
+ required
152
+ />
153
+ <button type="submit">Open /reports/organizations/:id</button>
154
+ </form>
155
+ </section>
156
+
157
+ <script>
158
+ for (const form of document.querySelectorAll("form[data-prefix]")) {
159
+ form.addEventListener("submit", (event) => {
160
+ event.preventDefault();
161
+ const prefix = form.getAttribute("data-prefix");
162
+ const suffix = form.getAttribute("data-suffix") ?? "";
163
+ const input = form.querySelector('input[name="id"]');
164
+
165
+ if (!prefix || !input?.value) {
166
+ return;
167
+ }
168
+
169
+ window.location.href = `${prefix}${input.value}${suffix}`;
170
+ });
171
+ }
172
+ </script>
173
+ </body>
174
+ </html>
@@ -5,5 +5,6 @@ interface DiscoverModulesOptions {
5
5
  declare function configureModulesDirectory(modulesDir: string): void;
6
6
  declare function ensureModulesLoaded(options?: DiscoverModulesOptions): Promise<AppModule[]>;
7
7
  declare function discoverModules(): AppModule[];
8
+ declare function resetDiscoverModulesForTests(): void;
8
9
  export type { DiscoverModulesOptions };
9
- export { configureModulesDirectory, discoverModules, ensureModulesLoaded };
10
+ export { configureModulesDirectory, discoverModules, ensureModulesLoaded, resetDiscoverModulesForTests, };
@@ -0,0 +1,4 @@
1
+ import "./schedule";
2
+ declare function startInProcessCronIfEnabled(): void;
3
+ declare function stopInProcessCron(): void;
4
+ export { startInProcessCronIfEnabled, stopInProcessCron };
@@ -1,4 +1,4 @@
1
1
  declare function createMetricsRoutes(): {
2
- "/metrics": () => Promise<Response>;
2
+ "/metrics": (request: Request) => Promise<Response>;
3
3
  };
4
4
  export { createMetricsRoutes };
@@ -3,7 +3,6 @@
3
3
  */
4
4
  export type { ScheduledTask } from "@getstrata/core/scheduler/schedule";
5
5
  export { appSchedule, runDueScheduledTasks, Schedule } from "@getstrata/core/scheduler/schedule";
6
- export { scheduleRunCommand } from "../cli/commands/scheduleRun.ts";
7
6
  export { resolveApplicationAuth, resolveApplicationCache, resolveApplicationConfig, resolveApplicationDependencies, resolveApplicationEventBus, resolveApplicationLogger, resolveApplicationPolicyGate, resolveApplicationQueue, setActiveApplicationContext, } from "./applicationRegistry.ts";
8
7
  export type { BuildModuleRoutesOptions } from "./buildModuleRoutes.ts";
9
8
  export { buildModuleRoutes } from "./buildModuleRoutes.ts";
@@ -14,13 +14,13 @@ import {
14
14
  setActiveApplicationContext
15
15
  } from "@getstrata/core/runtime/applicationRegistry";
16
16
  export {
17
- setActiveApplicationContext,
18
- resolveApplicationQueue,
19
- resolveApplicationPolicyGate,
20
- resolveApplicationLogger,
21
- resolveApplicationEventBus,
22
- resolveApplicationDependencies,
23
- resolveApplicationConfig,
17
+ resolveApplicationAuth,
24
18
  resolveApplicationCache,
25
- resolveApplicationAuth
19
+ resolveApplicationConfig,
20
+ resolveApplicationDependencies,
21
+ resolveApplicationEventBus,
22
+ resolveApplicationLogger,
23
+ resolveApplicationPolicyGate,
24
+ resolveApplicationQueue,
25
+ setActiveApplicationContext
26
26
  };
@@ -173,7 +173,7 @@ class HttpKernel {
173
173
  return this.wrap(["api", "authenticated"], handler);
174
174
  }
175
175
  wrapWeb(handler) {
176
- return handler;
176
+ return this.wrap("web", handler);
177
177
  }
178
178
  wrapWebPublicRead(handler) {
179
179
  if (isPublicReadsEnabled()) {
@@ -183,19 +183,19 @@ class HttpKernel {
183
183
  }
184
184
  wrapWebAuthenticated(handler) {
185
185
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
186
- return withMiddleware(createRequireWebAuthMiddleware(auth))(handler);
186
+ return this.wrap("web", withMiddleware(createRequireWebAuthMiddleware(auth))(handler));
187
187
  }
188
188
  wrapWebAbility(ability, handler) {
189
189
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
190
190
  const abilityChecker = this.dependencies.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
191
191
  const requireAbility = createRequireAbilityMiddleware(abilityChecker);
192
192
  const middleware = [createRequireWebAuthMiddleware(auth), requireAbility(ability)];
193
- return withMiddleware(...middleware)(handler);
193
+ return this.wrap("web", withMiddleware(...middleware)(handler));
194
194
  }
195
195
  wrapWebGlobalAdmin(handler) {
196
196
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
197
197
  const middleware = [createRequireWebAuthMiddleware(auth), createRequireGlobalAdminMiddleware()];
198
- return withMiddleware(...middleware)(handler);
198
+ return this.wrap("web", withMiddleware(...middleware)(handler));
199
199
  }
200
200
  wrapAuthenticated(handler) {
201
201
  return this.wrap("authenticated", handler);
@@ -294,7 +294,7 @@ function resolveModulesDirectory(options) {
294
294
  if (state.configuredModulesDir) {
295
295
  return state.configuredModulesDir;
296
296
  }
297
- return join(import.meta.dir, "../modules");
297
+ throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
298
298
  }
299
299
  async function loadDiscoveredModules(options) {
300
300
  const modulesDirectory = resolveModulesDirectory(options);
@@ -328,6 +328,12 @@ async function ensureModulesLoaded(options) {
328
328
  function discoverModules() {
329
329
  return readDiscoverModulesState().appModules;
330
330
  }
331
+ function resetDiscoverModulesForTests() {
332
+ const state = readDiscoverModulesState();
333
+ state.configuredModulesDir = undefined;
334
+ state.appModules.length = 0;
335
+ state.modulesReady = undefined;
336
+ }
331
337
  // ../../src/bootstrap/prefixRouteMap.ts
332
338
  function prefixRouteMap(prefix, routes) {
333
339
  const normalizedPrefix = prefix.replace(/\/$/, "");
@@ -409,6 +415,6 @@ function buildModuleRoutes(dependencies, options = {}) {
409
415
  return applyMiddlewareToRoutes(registerOpenApiRouteMap(prefixedModuleRoutes, ["global", "api"]), middleware);
410
416
  }
411
417
  export {
412
- registerOpenApiRouteMap,
413
- buildModuleRoutes
418
+ buildModuleRoutes,
419
+ registerOpenApiRouteMap
414
420
  };
@@ -176,7 +176,7 @@ class HttpKernel {
176
176
  return this.wrap(["api", "authenticated"], handler);
177
177
  }
178
178
  wrapWeb(handler) {
179
- return handler;
179
+ return this.wrap("web", handler);
180
180
  }
181
181
  wrapWebPublicRead(handler) {
182
182
  if (isPublicReadsEnabled()) {
@@ -186,19 +186,19 @@ class HttpKernel {
186
186
  }
187
187
  wrapWebAuthenticated(handler) {
188
188
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
189
- return withMiddleware(createRequireWebAuthMiddleware(auth))(handler);
189
+ return this.wrap("web", withMiddleware(createRequireWebAuthMiddleware(auth))(handler));
190
190
  }
191
191
  wrapWebAbility(ability, handler) {
192
192
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
193
193
  const abilityChecker = this.dependencies.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
194
194
  const requireAbility = createRequireAbilityMiddleware(abilityChecker);
195
195
  const middleware = [createRequireWebAuthMiddleware(auth), requireAbility(ability)];
196
- return withMiddleware(...middleware)(handler);
196
+ return this.wrap("web", withMiddleware(...middleware)(handler));
197
197
  }
198
198
  wrapWebGlobalAdmin(handler) {
199
199
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
200
200
  const middleware = [createRequireWebAuthMiddleware(auth), createRequireGlobalAdminMiddleware()];
201
- return withMiddleware(...middleware)(handler);
201
+ return this.wrap("web", withMiddleware(...middleware)(handler));
202
202
  }
203
203
  wrapAuthenticated(handler) {
204
204
  return this.wrap("authenticated", handler);
@@ -297,7 +297,7 @@ function resolveModulesDirectory(options) {
297
297
  if (state.configuredModulesDir) {
298
298
  return state.configuredModulesDir;
299
299
  }
300
- return join(import.meta.dir, "../modules");
300
+ throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
301
301
  }
302
302
  async function loadDiscoveredModules(options) {
303
303
  const modulesDirectory = resolveModulesDirectory(options);
@@ -331,6 +331,12 @@ async function ensureModulesLoaded(options) {
331
331
  function discoverModules() {
332
332
  return readDiscoverModulesState().appModules;
333
333
  }
334
+ function resetDiscoverModulesForTests() {
335
+ const state = readDiscoverModulesState();
336
+ state.configuredModulesDir = undefined;
337
+ state.appModules.length = 0;
338
+ state.modulesReady = undefined;
339
+ }
334
340
  // ../../src/bootstrap/prefixRouteMap.ts
335
341
  function prefixRouteMap(prefix, routes) {
336
342
  const normalizedPrefix = prefix.replace(/\/$/, "");
@@ -419,7 +425,7 @@ function buildWebModuleRoutes(dependencies, options = {}) {
419
425
  routeRegistry.clear();
420
426
  }
421
427
  const kernel = createHttpKernel(dependencies);
422
- const middleware = [...kernel.globalMiddleware(), ...kernel.group("web")];
428
+ const middleware = kernel.globalMiddleware();
423
429
  const moduleRoutes = { ...seedRoutes };
424
430
  for (const module of modules) {
425
431
  if (!module.webRoutes) {
@@ -26,7 +26,7 @@ function resolveModulesDirectory(options) {
26
26
  if (state.configuredModulesDir) {
27
27
  return state.configuredModulesDir;
28
28
  }
29
- return join(import.meta.dir, "../modules");
29
+ throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
30
30
  }
31
31
  async function loadDiscoveredModules(options) {
32
32
  const modulesDirectory = resolveModulesDirectory(options);
@@ -60,6 +60,12 @@ async function ensureModulesLoaded(options) {
60
60
  function discoverModules() {
61
61
  return readDiscoverModulesState().appModules;
62
62
  }
63
+ function resetDiscoverModulesForTests() {
64
+ const state = readDiscoverModulesState();
65
+ state.configuredModulesDir = undefined;
66
+ state.appModules.length = 0;
67
+ state.modulesReady = undefined;
68
+ }
63
69
 
64
70
  // ../../src/bootstrap/cache/modelCacheTags.ts
65
71
  function cacheTagsForModelWrite(tableName, action) {
@@ -73,6 +79,6 @@ function discoverModelTableNames() {
73
79
  return discoverModules().map((module) => module.tableName).filter((tableName) => tableName !== undefined);
74
80
  }
75
81
  export {
76
- discoverModelTableNames,
77
- cacheTagsForModelWrite
82
+ cacheTagsForModelWrite,
83
+ discoverModelTableNames
78
84
  };
@@ -25,24 +25,24 @@ var DEFAULT_CACHE_DRIVER = "array";
25
25
  var DEFAULT_API_TOKEN = "";
26
26
  var DEFAULT_QUEUE_DRIVER = "sync";
27
27
  export {
28
- REDIS_URL_CONFIG_KEY,
29
- DEFAULT_QUEUE_DRIVER,
30
- DEFAULT_CACHE_TTL_MS,
31
- DEFAULT_CACHE_MAX_ENTRIES,
32
- DEFAULT_CACHE_DRIVER,
33
- DEFAULT_APP_PORT,
34
- DEFAULT_API_TOKEN,
35
- DATABASE_URL_CONFIG_KEY,
36
- CORE_TOKEN_SERVICE_TOKEN,
37
- CORE_QUEUE_TOKEN,
38
- CORE_POLICY_GATE_TOKEN,
39
- CORE_EVENT_BUS_TOKEN,
40
- CORE_CONFIG_TOKEN,
41
- CORE_CACHE_TOKEN,
42
- CORE_AUTH_TOKEN,
43
- CACHE_TTL_MS_CONFIG_KEY,
44
- CACHE_MAX_ENTRIES_CONFIG_KEY,
45
- CACHE_DRIVER_CONFIG_KEY,
28
+ APP_PORT_CONFIG_KEY,
46
29
  AUTH_DEV_HEADERS_CONFIG_KEY,
47
- APP_PORT_CONFIG_KEY
30
+ CACHE_DRIVER_CONFIG_KEY,
31
+ CACHE_MAX_ENTRIES_CONFIG_KEY,
32
+ CACHE_TTL_MS_CONFIG_KEY,
33
+ CORE_AUTH_TOKEN,
34
+ CORE_CACHE_TOKEN,
35
+ CORE_CONFIG_TOKEN,
36
+ CORE_EVENT_BUS_TOKEN,
37
+ CORE_POLICY_GATE_TOKEN,
38
+ CORE_QUEUE_TOKEN,
39
+ CORE_TOKEN_SERVICE_TOKEN,
40
+ DATABASE_URL_CONFIG_KEY,
41
+ DEFAULT_API_TOKEN,
42
+ DEFAULT_APP_PORT,
43
+ DEFAULT_CACHE_DRIVER,
44
+ DEFAULT_CACHE_MAX_ENTRIES,
45
+ DEFAULT_CACHE_TTL_MS,
46
+ DEFAULT_QUEUE_DRIVER,
47
+ REDIS_URL_CONFIG_KEY
48
48
  };
@@ -50,7 +50,7 @@ function resolveModulesDirectory(options) {
50
50
  if (state.configuredModulesDir) {
51
51
  return state.configuredModulesDir;
52
52
  }
53
- return join(import.meta.dir, "../modules");
53
+ throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
54
54
  }
55
55
  async function loadDiscoveredModules(options) {
56
56
  const modulesDirectory = resolveModulesDirectory(options);
@@ -84,6 +84,12 @@ async function ensureModulesLoaded(options) {
84
84
  function discoverModules() {
85
85
  return readDiscoverModulesState().appModules;
86
86
  }
87
+ function resetDiscoverModulesForTests() {
88
+ const state = readDiscoverModulesState();
89
+ state.configuredModulesDir = undefined;
90
+ state.appModules.length = 0;
91
+ state.modulesReady = undefined;
92
+ }
87
93
  // ../../src/bootstrap/providers/auth.ts
88
94
  import {
89
95
  AuthManager,
@@ -500,55 +506,28 @@ var coreProviders = [
500
506
  viewProvider
501
507
  ];
502
508
 
503
- // ../../src/config/features.ts
504
- function readFeatureFlags() {
505
- return {
506
- webhooks: (process.env.FEATURE_WEBHOOKS ?? "true") !== "false",
507
- fullTextSearch: (process.env.FEATURE_SEARCH ?? "true") !== "false",
508
- auditLog: (process.env.FEATURE_AUDIT_LOG ?? "true") !== "false",
509
- oauthLogin: (process.env.FEATURE_OAUTH ?? "true") !== "false",
510
- samlLogin: (process.env.FEATURE_SAML ?? "false") === "true",
511
- scim: (process.env.FEATURE_SCIM ?? "true") !== "false",
512
- billing: (process.env.FEATURE_BILLING ?? "true") !== "false",
513
- siemExport: (process.env.FEATURE_SIEM_EXPORT ?? "true") !== "false",
514
- publicReads: (process.env.FEATURE_PUBLIC_READS ?? "true") !== "false",
515
- emailVerification: (process.env.FEATURE_EMAIL_VERIFICATION ?? "false") === "true",
516
- mfa: (process.env.FEATURE_MFA ?? "false") === "true"
517
- };
518
- }
519
- var featureFlags = readFeatureFlags();
520
- function isFeatureEnabled(feature) {
521
- return readFeatureFlags()[feature];
522
- }
523
-
524
- // ../../src/domain/auth.ts
525
- var TEST_ADMIN_API_TOKEN = "workhub-admin-test-token";
526
- var TEST_MEMBER_API_TOKEN = "workhub-member-test-token";
527
-
528
- // ../../src/domain/scim.ts
529
- var TEST_SCIM_BEARER_TOKEN = "workhub-scim-test-token";
530
- var DEFAULT_SCIM_BEARER_TOKEN = TEST_SCIM_BEARER_TOKEN;
531
- var SCIM_SCHEMAS = {
532
- user: "urn:ietf:params:scim:schemas:core:2.0:User",
533
- group: "urn:ietf:params:scim:schemas:core:2.0:Group",
534
- listResponse: "urn:ietf:params:scim:api:messages:2.0:ListResponse",
535
- patchOp: "urn:ietf:params:scim:api:messages:2.0:PatchOp",
536
- serviceProviderConfig: "urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"
537
- };
538
-
539
509
  // ../../src/bootstrap/secretsGuard.ts
540
- var DEFAULT_TOKENS = new Set([TEST_ADMIN_API_TOKEN, TEST_MEMBER_API_TOKEN]);
541
- var DEFAULT_SCIM_TOKENS = new Set([TEST_SCIM_BEARER_TOKEN, DEFAULT_SCIM_BEARER_TOKEN]);
510
+ var DEFAULT_ADMIN_API_TOKEN = "workhub-admin-test-token";
511
+ var DEFAULT_MEMBER_API_TOKEN = "workhub-member-test-token";
512
+ var DEFAULT_SCIM_BEARER_TOKEN = "workhub-scim-test-token";
513
+ var DEFAULT_TOKENS = new Set([DEFAULT_ADMIN_API_TOKEN, DEFAULT_MEMBER_API_TOKEN]);
514
+ var DEFAULT_SCIM_TOKENS = new Set([DEFAULT_SCIM_BEARER_TOKEN]);
515
+ function isEnabled(value, defaultEnabled) {
516
+ if (value === undefined) {
517
+ return defaultEnabled;
518
+ }
519
+ return defaultEnabled ? value !== "false" : value === "true";
520
+ }
542
521
  function assertProductionSecrets(env = process.env) {
543
- const appEnv = env.APP_ENV ?? appConfig.env;
522
+ const appEnv = env.APP_ENV ?? "local";
544
523
  if (appEnv !== "production") {
545
524
  return;
546
525
  }
547
- const adminToken = env.ADMIN_API_TOKEN ?? TEST_ADMIN_API_TOKEN;
548
- const memberToken = env.MEMBER_API_TOKEN ?? TEST_MEMBER_API_TOKEN;
526
+ const adminToken = env.ADMIN_API_TOKEN ?? DEFAULT_ADMIN_API_TOKEN;
527
+ const memberToken = env.MEMBER_API_TOKEN ?? DEFAULT_MEMBER_API_TOKEN;
549
528
  const scimToken = env.SCIM_BEARER_TOKEN ?? DEFAULT_SCIM_BEARER_TOKEN;
550
- const encryptionEnabled = env.FEATURE_FIELD_ENCRYPTION !== "false";
551
- const devHeadersEnabled = (env.AUTH_DEV_HEADERS ?? "true") !== "false";
529
+ const encryptionEnabled = isEnabled(env.FEATURE_FIELD_ENCRYPTION, true);
530
+ const devHeadersEnabled = isEnabled(env.AUTH_DEV_HEADERS, true);
552
531
  if (devHeadersEnabled) {
553
532
  throw new Error("Production startup blocked: set AUTH_DEV_HEADERS=false to disable development auth headers.");
554
533
  }
@@ -561,18 +540,17 @@ function assertProductionSecrets(env = process.env) {
561
540
  if (encryptionEnabled && !env.KMS_ENCRYPTION_KEY?.trim()) {
562
541
  throw new Error("Production startup blocked: set KMS_ENCRYPTION_KEY when field encryption is enabled.");
563
542
  }
564
- if (!env.SIEM_EXPORT_URL?.trim() && isFeatureEnabled("siemExport")) {
543
+ if (!env.SIEM_EXPORT_URL?.trim() && isEnabled(env.FEATURE_SIEM_EXPORT, true)) {
565
544
  console.warn("[secrets] SIEM_EXPORT_URL is not configured; audit logs remain database-only.");
566
545
  }
567
- const billingEnabled = (env.FEATURE_BILLING ?? "true") !== "false";
568
- if (billingEnabled && !env.STRIPE_WEBHOOK_SECRET?.trim()) {
546
+ if (isEnabled(env.FEATURE_BILLING, true) && !env.STRIPE_WEBHOOK_SECRET?.trim()) {
569
547
  throw new Error("Production startup blocked: set STRIPE_WEBHOOK_SECRET when billing webhooks are enabled.");
570
548
  }
571
549
  const corsOrigins = (env.CORS_ALLOWED_ORIGINS ?? "*").split(",").map((origin) => origin.trim());
572
550
  if (corsOrigins.includes("*")) {
573
551
  throw new Error("Production startup blocked: set explicit CORS_ALLOWED_ORIGINS instead of wildcard.");
574
552
  }
575
- if ((env.FEATURE_PUBLIC_READS ?? "true") !== "false") {
553
+ if (isEnabled(env.FEATURE_PUBLIC_READS, true)) {
576
554
  throw new Error("Production startup blocked: set FEATURE_PUBLIC_READS=false for authenticated-only reads.");
577
555
  }
578
556
  if (!env.OAUTH_STATE_SECRET?.trim()) {
@@ -584,6 +562,10 @@ function assertProductionSecrets(env = process.env) {
584
562
  if (!env.API_TOKEN_DEFAULT_EXPIRY_DAYS?.trim()) {
585
563
  throw new Error("Production startup blocked: set API_TOKEN_DEFAULT_EXPIRY_DAYS to enforce token rotation.");
586
564
  }
565
+ const frontendMode = (env.FRONTEND_MODE ?? "api").trim();
566
+ if (frontendMode === "server-htmx" && !env.SESSION_SECRET?.trim()) {
567
+ throw new Error("Production startup blocked: set SESSION_SECRET when FRONTEND_MODE=server-htmx.");
568
+ }
587
569
  }
588
570
 
589
571
  // ../../src/bootstrap/context.ts
@@ -636,8 +618,8 @@ var appContext = {
636
618
  }
637
619
  };
638
620
  export {
639
- runProviderPhase,
640
- createAppContext,
621
+ appContext,
641
622
  collectProviders,
642
- appContext
623
+ createAppContext,
624
+ runProviderPhase
643
625
  };
@@ -12,9 +12,9 @@ import {
12
12
  resolveService
13
13
  } from "@getstrata/core/contracts/di";
14
14
  export {
15
- resolveService,
16
- getRequiredDependency,
17
- assertAppDependenciesComplete,
15
+ ConfigStore,
18
16
  ServiceContainer,
19
- ConfigStore
17
+ assertAppDependenciesComplete,
18
+ getRequiredDependency,
19
+ resolveService
20
20
  };