@getstrata/bootstrap 0.2.47 → 0.2.49

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,10 @@ import {
28
28
  import { createHttpKernel, createAppContext, coreProviders } from "@getstrata/bootstrap";
29
29
  ```
30
30
 
31
+ `createAppContext()` does not call `assertProductionSecrets`. WorkHub calls that from `App.serve()` / `queue:work`. Sibling apps should call it only if they want those WorkHub production gates (API tokens, Stripe, SCIM, CORS).
32
+
33
+ `wrapWeb` applies the web group (CSRF + flash) and `withErrorHandling`, so CSRF `ForbiddenError` becomes an HTML 403 in `FRONTEND_MODE=server-htmx`. `wrapWebLogin` / `wrapWebRegister` include that web group plus throttle — do not wrap them with `wrapWeb` again.
34
+
35
+ 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`).
36
+
31
37
  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,9 +14,9 @@ export declare function wrapSecuredRouteModelByKey<TParams extends Record<string
14
14
  resource: string;
15
15
  action: "view" | "create" | "update" | "delete";
16
16
  }, handler: (request: RouteRequest<TParams>, model: TModel) => Response | Promise<Response>): RouteHandler;
17
- /** Converts kernel login throttle 429 JSON into an HTML response for web forms. */
17
+ /** Web group (CSRF + flash + HTML errors) plus login throttle. Do not wrap with wrapWeb again. */
18
18
  export declare function wrapWebLogin(kernel: HttpKernel, handler: RouteHandler, onThrottled: (request: Request) => Response | Promise<Response>): RouteHandler;
19
- /** Converts kernel register throttle 429 JSON into an HTML response for web forms. */
19
+ /** Web group (CSRF + flash + HTML errors) plus register throttle. Do not wrap with wrapWeb again. */
20
20
  export declare function wrapWebRegister(kernel: HttpKernel, handler: RouteHandler, onThrottled: (request: Request) => Response | Promise<Response>): RouteHandler;
21
21
  /** Convenience alias: HttpKernel is the Laravel-style router middleware wrapper. */
22
22
  export declare function createRouteKernel(dependencies: AppDependencies): HttpKernel;
@@ -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
  };
@@ -21,6 +21,7 @@ import { createRequireAbilityMiddleware } from "@getstrata/core/http/requireAbil
21
21
  import { createRequireAuthMiddleware } from "@getstrata/core/http/requireAuthMiddleware";
22
22
  import { createRequireGlobalAdminMiddleware } from "@getstrata/core/http/requireGlobalAdminMiddleware";
23
23
  import { createRequireWebAuthMiddleware } from "@getstrata/core/http/requireWebAuthMiddleware";
24
+ import { withErrorHandling } from "@getstrata/core/http/response";
24
25
  import { withMiddleware } from "@getstrata/core/http/routeMiddleware";
25
26
  import { createSecurityHeadersMiddleware } from "@getstrata/core/http/securityHeadersMiddleware";
26
27
  import { createThrottleMiddleware } from "@getstrata/core/http/throttleMiddleware";
@@ -173,7 +174,7 @@ class HttpKernel {
173
174
  return this.wrap(["api", "authenticated"], handler);
174
175
  }
175
176
  wrapWeb(handler) {
176
- return handler;
177
+ return withErrorHandling(this.wrap("web", handler));
177
178
  }
178
179
  wrapWebPublicRead(handler) {
179
180
  if (isPublicReadsEnabled()) {
@@ -183,19 +184,19 @@ class HttpKernel {
183
184
  }
184
185
  wrapWebAuthenticated(handler) {
185
186
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
186
- return withMiddleware(createRequireWebAuthMiddleware(auth))(handler);
187
+ return this.wrapWeb(withMiddleware(createRequireWebAuthMiddleware(auth))(handler));
187
188
  }
188
189
  wrapWebAbility(ability, handler) {
189
190
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
190
191
  const abilityChecker = this.dependencies.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
191
192
  const requireAbility = createRequireAbilityMiddleware(abilityChecker);
192
193
  const middleware = [createRequireWebAuthMiddleware(auth), requireAbility(ability)];
193
- return withMiddleware(...middleware)(handler);
194
+ return this.wrapWeb(withMiddleware(...middleware)(handler));
194
195
  }
195
196
  wrapWebGlobalAdmin(handler) {
196
197
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
197
198
  const middleware = [createRequireWebAuthMiddleware(auth), createRequireGlobalAdminMiddleware()];
198
- return withMiddleware(...middleware)(handler);
199
+ return this.wrapWeb(withMiddleware(...middleware)(handler));
199
200
  }
200
201
  wrapAuthenticated(handler) {
201
202
  return this.wrap("authenticated", handler);
@@ -294,7 +295,7 @@ function resolveModulesDirectory(options) {
294
295
  if (state.configuredModulesDir) {
295
296
  return state.configuredModulesDir;
296
297
  }
297
- return join(import.meta.dir, "../modules");
298
+ throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
298
299
  }
299
300
  async function loadDiscoveredModules(options) {
300
301
  const modulesDirectory = resolveModulesDirectory(options);
@@ -328,6 +329,12 @@ async function ensureModulesLoaded(options) {
328
329
  function discoverModules() {
329
330
  return readDiscoverModulesState().appModules;
330
331
  }
332
+ function resetDiscoverModulesForTests() {
333
+ const state = readDiscoverModulesState();
334
+ state.configuredModulesDir = undefined;
335
+ state.appModules.length = 0;
336
+ state.modulesReady = undefined;
337
+ }
331
338
  // ../../src/bootstrap/prefixRouteMap.ts
332
339
  function prefixRouteMap(prefix, routes) {
333
340
  const normalizedPrefix = prefix.replace(/\/$/, "");
@@ -409,6 +416,6 @@ function buildModuleRoutes(dependencies, options = {}) {
409
416
  return applyMiddlewareToRoutes(registerOpenApiRouteMap(prefixedModuleRoutes, ["global", "api"]), middleware);
410
417
  }
411
418
  export {
412
- registerOpenApiRouteMap,
413
- buildModuleRoutes
419
+ buildModuleRoutes,
420
+ registerOpenApiRouteMap
414
421
  };
@@ -24,6 +24,7 @@ import { createRequireAbilityMiddleware } from "@getstrata/core/http/requireAbil
24
24
  import { createRequireAuthMiddleware } from "@getstrata/core/http/requireAuthMiddleware";
25
25
  import { createRequireGlobalAdminMiddleware } from "@getstrata/core/http/requireGlobalAdminMiddleware";
26
26
  import { createRequireWebAuthMiddleware } from "@getstrata/core/http/requireWebAuthMiddleware";
27
+ import { withErrorHandling } from "@getstrata/core/http/response";
27
28
  import { withMiddleware } from "@getstrata/core/http/routeMiddleware";
28
29
  import { createSecurityHeadersMiddleware } from "@getstrata/core/http/securityHeadersMiddleware";
29
30
  import { createThrottleMiddleware } from "@getstrata/core/http/throttleMiddleware";
@@ -176,7 +177,7 @@ class HttpKernel {
176
177
  return this.wrap(["api", "authenticated"], handler);
177
178
  }
178
179
  wrapWeb(handler) {
179
- return handler;
180
+ return withErrorHandling(this.wrap("web", handler));
180
181
  }
181
182
  wrapWebPublicRead(handler) {
182
183
  if (isPublicReadsEnabled()) {
@@ -186,19 +187,19 @@ class HttpKernel {
186
187
  }
187
188
  wrapWebAuthenticated(handler) {
188
189
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
189
- return withMiddleware(createRequireWebAuthMiddleware(auth))(handler);
190
+ return this.wrapWeb(withMiddleware(createRequireWebAuthMiddleware(auth))(handler));
190
191
  }
191
192
  wrapWebAbility(ability, handler) {
192
193
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
193
194
  const abilityChecker = this.dependencies.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
194
195
  const requireAbility = createRequireAbilityMiddleware(abilityChecker);
195
196
  const middleware = [createRequireWebAuthMiddleware(auth), requireAbility(ability)];
196
- return withMiddleware(...middleware)(handler);
197
+ return this.wrapWeb(withMiddleware(...middleware)(handler));
197
198
  }
198
199
  wrapWebGlobalAdmin(handler) {
199
200
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
200
201
  const middleware = [createRequireWebAuthMiddleware(auth), createRequireGlobalAdminMiddleware()];
201
- return withMiddleware(...middleware)(handler);
202
+ return this.wrapWeb(withMiddleware(...middleware)(handler));
202
203
  }
203
204
  wrapAuthenticated(handler) {
204
205
  return this.wrap("authenticated", handler);
@@ -297,7 +298,7 @@ function resolveModulesDirectory(options) {
297
298
  if (state.configuredModulesDir) {
298
299
  return state.configuredModulesDir;
299
300
  }
300
- return join(import.meta.dir, "../modules");
301
+ throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
301
302
  }
302
303
  async function loadDiscoveredModules(options) {
303
304
  const modulesDirectory = resolveModulesDirectory(options);
@@ -331,6 +332,12 @@ async function ensureModulesLoaded(options) {
331
332
  function discoverModules() {
332
333
  return readDiscoverModulesState().appModules;
333
334
  }
335
+ function resetDiscoverModulesForTests() {
336
+ const state = readDiscoverModulesState();
337
+ state.configuredModulesDir = undefined;
338
+ state.appModules.length = 0;
339
+ state.modulesReady = undefined;
340
+ }
334
341
  // ../../src/bootstrap/prefixRouteMap.ts
335
342
  function prefixRouteMap(prefix, routes) {
336
343
  const normalizedPrefix = prefix.replace(/\/$/, "");
@@ -419,7 +426,7 @@ function buildWebModuleRoutes(dependencies, options = {}) {
419
426
  routeRegistry.clear();
420
427
  }
421
428
  const kernel = createHttpKernel(dependencies);
422
- const middleware = [...kernel.globalMiddleware(), ...kernel.group("web")];
429
+ const middleware = kernel.globalMiddleware();
423
430
  const moduleRoutes = { ...seedRoutes };
424
431
  for (const module of modules) {
425
432
  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,92 +506,6 @@ 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
- // ../../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]);
542
- function assertProductionSecrets(env = process.env) {
543
- const appEnv = env.APP_ENV ?? appConfig.env;
544
- if (appEnv !== "production") {
545
- return;
546
- }
547
- const adminToken = env.ADMIN_API_TOKEN ?? TEST_ADMIN_API_TOKEN;
548
- const memberToken = env.MEMBER_API_TOKEN ?? TEST_MEMBER_API_TOKEN;
549
- 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";
552
- if (devHeadersEnabled) {
553
- throw new Error("Production startup blocked: set AUTH_DEV_HEADERS=false to disable development auth headers.");
554
- }
555
- if (DEFAULT_TOKENS.has(adminToken) || DEFAULT_TOKENS.has(memberToken)) {
556
- throw new Error("Production startup blocked: rotate ADMIN_API_TOKEN and MEMBER_API_TOKEN away from default WorkHub test values.");
557
- }
558
- if (DEFAULT_SCIM_TOKENS.has(scimToken)) {
559
- throw new Error("Production startup blocked: rotate SCIM_BEARER_TOKEN away from default WorkHub test values.");
560
- }
561
- if (encryptionEnabled && !env.KMS_ENCRYPTION_KEY?.trim()) {
562
- throw new Error("Production startup blocked: set KMS_ENCRYPTION_KEY when field encryption is enabled.");
563
- }
564
- if (!env.SIEM_EXPORT_URL?.trim() && isFeatureEnabled("siemExport")) {
565
- console.warn("[secrets] SIEM_EXPORT_URL is not configured; audit logs remain database-only.");
566
- }
567
- const billingEnabled = (env.FEATURE_BILLING ?? "true") !== "false";
568
- if (billingEnabled && !env.STRIPE_WEBHOOK_SECRET?.trim()) {
569
- throw new Error("Production startup blocked: set STRIPE_WEBHOOK_SECRET when billing webhooks are enabled.");
570
- }
571
- const corsOrigins = (env.CORS_ALLOWED_ORIGINS ?? "*").split(",").map((origin) => origin.trim());
572
- if (corsOrigins.includes("*")) {
573
- throw new Error("Production startup blocked: set explicit CORS_ALLOWED_ORIGINS instead of wildcard.");
574
- }
575
- if ((env.FEATURE_PUBLIC_READS ?? "true") !== "false") {
576
- throw new Error("Production startup blocked: set FEATURE_PUBLIC_READS=false for authenticated-only reads.");
577
- }
578
- if (!env.OAUTH_STATE_SECRET?.trim()) {
579
- throw new Error("Production startup blocked: set OAUTH_STATE_SECRET for OAuth CSRF protection.");
580
- }
581
- if (!env.TOKEN_HASH_PEPPER?.trim()) {
582
- throw new Error("Production startup blocked: set TOKEN_HASH_PEPPER for API token hashing.");
583
- }
584
- if (!env.API_TOKEN_DEFAULT_EXPIRY_DAYS?.trim()) {
585
- throw new Error("Production startup blocked: set API_TOKEN_DEFAULT_EXPIRY_DAYS to enforce token rotation.");
586
- }
587
- }
588
-
589
509
  // ../../src/bootstrap/context.ts
590
510
  function collectProviders(modules = discoverModules()) {
591
511
  return [...coreProviders, ...modules.flatMap((module) => module.providers ?? [])];
@@ -596,7 +516,6 @@ function runProviderPhase(providers, phase, context) {
596
516
  }
597
517
  }
598
518
  function createAppContext() {
599
- assertProductionSecrets();
600
519
  const container = new ServiceContainer;
601
520
  const config = new ConfigStore;
602
521
  const dependencies = {
@@ -636,8 +555,8 @@ var appContext = {
636
555
  }
637
556
  };
638
557
  export {
639
- runProviderPhase,
640
- createAppContext,
558
+ appContext,
641
559
  collectProviders,
642
- appContext
560
+ createAppContext,
561
+ runProviderPhase
643
562
  };
@@ -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
  };