@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.
@@ -2,12 +2,12 @@
2
2
  var __jsonParse = (a) => JSON.parse(a);
3
3
 
4
4
  // ../../src/bootstrap/createRoutes.ts
5
- import { jsonResponse as jsonResponse4 } from "@getstrata/core/http";
6
5
  import { applyMiddlewareToRoutes as applyMiddlewareToRoutes3 } from "@getstrata/core/http/middleware";
6
+ import { jsonResponse as jsonResponse4 } from "@getstrata/core/http/response";
7
7
  import { htmlResponse as htmlResponse2 } from "@getstrata/core/view";
8
8
 
9
9
  // ../../index.html
10
- var strata_default = __jsonParse("{\"index\":\"../../index.html\",\"files\":[{\"input\":\"../../index.html\",\"path\":\"./index-sfreg6q3.js\",\"loader\":\"js\",\"isEntry\":true,\"headers\":{\"etag\":\"Oc_YLpCmV6M\",\"content-type\":\"text/javascript;charset=utf-8\"}},{\"input\":\"../../index.html\",\"path\":\"../../index.html\",\"loader\":\"html\",\"isEntry\":true,\"headers\":{\"etag\":\"rWY90LWIcx8\",\"content-type\":\"text/html;charset=utf-8\"}}]}");
10
+ var strata_default = __jsonParse("{\"index\":\"_.._/_.._/index.html\",\"files\":[{\"input\":\"../../index.html\",\"path\":\"./index-sfreg6q3.js\",\"loader\":\"js\",\"isEntry\":true,\"headers\":{\"etag\":\"Oc_YLpCmV6M\",\"content-type\":\"text/javascript;charset=utf-8\"}},{\"input\":\"../../index.html\",\"path\":\"_.._/_.._/index.html\",\"loader\":\"html\",\"isEntry\":true,\"headers\":{\"etag\":\"rWY90LWIcx8\",\"content-type\":\"text/html;charset=utf-8\"}}]}");
11
11
 
12
12
  // ../../src/config/app.ts
13
13
  var appConfig = {
@@ -211,7 +211,7 @@ class HttpKernel {
211
211
  return this.wrap(["api", "authenticated"], handler);
212
212
  }
213
213
  wrapWeb(handler) {
214
- return handler;
214
+ return this.wrap("web", handler);
215
215
  }
216
216
  wrapWebPublicRead(handler) {
217
217
  if (isPublicReadsEnabled()) {
@@ -221,19 +221,19 @@ class HttpKernel {
221
221
  }
222
222
  wrapWebAuthenticated(handler) {
223
223
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
224
- return withMiddleware(createRequireWebAuthMiddleware(auth))(handler);
224
+ return this.wrap("web", withMiddleware(createRequireWebAuthMiddleware(auth))(handler));
225
225
  }
226
226
  wrapWebAbility(ability, handler) {
227
227
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
228
228
  const abilityChecker = this.dependencies.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
229
229
  const requireAbility = createRequireAbilityMiddleware(abilityChecker);
230
230
  const middleware = [createRequireWebAuthMiddleware(auth), requireAbility(ability)];
231
- return withMiddleware(...middleware)(handler);
231
+ return this.wrap("web", withMiddleware(...middleware)(handler));
232
232
  }
233
233
  wrapWebGlobalAdmin(handler) {
234
234
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
235
235
  const middleware = [createRequireWebAuthMiddleware(auth), createRequireGlobalAdminMiddleware()];
236
- return withMiddleware(...middleware)(handler);
236
+ return this.wrap("web", withMiddleware(...middleware)(handler));
237
237
  }
238
238
  wrapAuthenticated(handler) {
239
239
  return this.wrap("authenticated", handler);
@@ -332,7 +332,7 @@ function resolveModulesDirectory(options) {
332
332
  if (state.configuredModulesDir) {
333
333
  return state.configuredModulesDir;
334
334
  }
335
- return join(import.meta.dir, "../modules");
335
+ throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
336
336
  }
337
337
  async function loadDiscoveredModules(options) {
338
338
  const modulesDirectory = resolveModulesDirectory(options);
@@ -366,6 +366,12 @@ async function ensureModulesLoaded(options) {
366
366
  function discoverModules() {
367
367
  return readDiscoverModulesState().appModules;
368
368
  }
369
+ function resetDiscoverModulesForTests() {
370
+ const state = readDiscoverModulesState();
371
+ state.configuredModulesDir = undefined;
372
+ state.appModules.length = 0;
373
+ state.modulesReady = undefined;
374
+ }
369
375
  // ../../src/bootstrap/prefixRouteMap.ts
370
376
  function prefixRouteMap(prefix, routes) {
371
377
  const normalizedPrefix = prefix.replace(/\/$/, "");
@@ -449,7 +455,7 @@ function buildModuleRoutes(dependencies, options = {}) {
449
455
 
450
456
  // ../../src/bootstrap/createSpaRoutes.ts
451
457
  import { join as join2 } from "path";
452
- import { jsonResponse } from "@getstrata/core/http";
458
+ import { jsonResponse } from "@getstrata/core/http/response";
453
459
  var SPA_DIST_DIRECTORY = join2(process.cwd(), "frontend/dist");
454
460
  var SPA_INDEX_FILE = join2(SPA_DIST_DIRECTORY, "index.html");
455
461
  function createSpaRoutes(_dependencies) {
@@ -496,7 +502,7 @@ function buildWebModuleRoutes(dependencies, options = {}) {
496
502
  routeRegistry.clear();
497
503
  }
498
504
  const kernel = createHttpKernel(dependencies);
499
- const middleware = [...kernel.globalMiddleware(), ...kernel.group("web")];
505
+ const middleware = kernel.globalMiddleware();
500
506
  const moduleRoutes = { ...seedRoutes };
501
507
  for (const module of modules) {
502
508
  if (!module.webRoutes) {
@@ -547,7 +553,7 @@ function mergeWebRoutes(dependencies, routes) {
547
553
  }
548
554
 
549
555
  // ../../src/bootstrap/health.ts
550
- import { jsonResponse as jsonResponse2 } from "@getstrata/core/http";
556
+ import { jsonResponse as jsonResponse2 } from "@getstrata/core/http/response";
551
557
  var {RedisClient } = globalThis.Bun;
552
558
 
553
559
  // ../../src/config/database.ts
@@ -731,15 +737,41 @@ function createHealthRoutes(dependencies) {
731
737
  }
732
738
 
733
739
  // ../../src/bootstrap/metricsRoutes.ts
740
+ import { timingSafeEqual } from "crypto";
734
741
  import { prometheusRegistry } from "@getstrata/core/metrics/prometheus";
742
+ function tokensMatch(left, right) {
743
+ const leftBuffer = Buffer.from(left);
744
+ const rightBuffer = Buffer.from(right);
745
+ if (leftBuffer.length !== rightBuffer.length) {
746
+ return false;
747
+ }
748
+ return timingSafeEqual(leftBuffer, rightBuffer);
749
+ }
750
+ function authorizeMetrics(request) {
751
+ const expected = process.env.METRICS_TOKEN?.trim();
752
+ const authorization = request.headers.get("authorization") ?? "";
753
+ const presented = authorization.startsWith("Bearer ") ? authorization.slice("Bearer ".length) : "";
754
+ if (expected) {
755
+ return presented.length > 0 && tokensMatch(presented, expected);
756
+ }
757
+ if ((process.env.APP_ENV ?? "local") === "production") {
758
+ return false;
759
+ }
760
+ return true;
761
+ }
735
762
  function createMetricsRoutes() {
736
763
  return {
737
- "/metrics": async () => new Response(prometheusRegistry.renderMetrics(), {
738
- status: 200,
739
- headers: {
740
- "content-type": "text/plain; version=0.0.4; charset=utf-8"
764
+ "/metrics": async (request) => {
765
+ if (!authorizeMetrics(request)) {
766
+ return new Response("Not Found", { status: 404 });
741
767
  }
742
- })
768
+ return new Response(prometheusRegistry.renderMetrics(), {
769
+ status: 200,
770
+ headers: {
771
+ "content-type": "text/plain; version=0.0.4; charset=utf-8"
772
+ }
773
+ });
774
+ }
743
775
  };
744
776
  }
745
777
 
@@ -749,16 +781,16 @@ import { withMiddleware as withMiddleware2 } from "@getstrata/core/http/routeMid
749
781
  import { createScimThrottleMiddleware } from "@getstrata/core/http/scimThrottleMiddleware";
750
782
 
751
783
  // ../../src/modules/scim/controller.ts
752
- import { withErrorHandling } from "@getstrata/core/http";
784
+ import { withErrorHandling } from "@getstrata/core/http/response";
753
785
 
754
786
  // ../../src/modules/scim/scimResponse.ts
755
- import { jsonResponse as jsonResponse3 } from "@getstrata/core/http";
756
787
  import {
757
788
  applyConditionalGet,
758
789
  assertIfMatch,
759
790
  etagFromResource,
760
791
  isEtagEnabled
761
792
  } from "@getstrata/core/http/etag";
793
+ import { jsonResponse as jsonResponse3 } from "@getstrata/core/http/response";
762
794
  function scimResponse(data, options = {}) {
763
795
  const status = options.status ?? 200;
764
796
  const response = jsonResponse3(data, {
@@ -797,8 +829,6 @@ import { NotFoundError as NotFoundError4 } from "@getstrata/core/errors/http";
797
829
  import { currentTenantId as currentTenantId4 } from "@getstrata/core/tenant/tenantContext";
798
830
 
799
831
  // ../../src/domain/scim.ts
800
- var TEST_SCIM_BEARER_TOKEN = "workhub-scim-test-token";
801
- var DEFAULT_SCIM_BEARER_TOKEN = TEST_SCIM_BEARER_TOKEN;
802
832
  var SCIM_SCHEMAS = {
803
833
  user: "urn:ietf:params:scim:schemas:core:2.0:User",
804
834
  group: "urn:ietf:params:scim:schemas:core:2.0:Group",
@@ -942,7 +972,7 @@ import { resolveDefaultTokenExpiryDays } from "@getstrata/core/security/tokenExp
942
972
  import { verifyTotp } from "@getstrata/core/security/totp";
943
973
  import { currentTenantId as currentTenantId2 } from "@getstrata/core/tenant/tenantContext";
944
974
 
945
- // ../../src/domain/abilities.ts
975
+ // ../../src/core/auth/abilityCatalog.ts
946
976
  var MEMBER_ABILITIES = [
947
977
  "organizations:read",
948
978
  "projects:read",
@@ -972,107 +1002,6 @@ var ADMIN_ABILITIES = [
972
1002
  "webhooks:write",
973
1003
  "audit:read"
974
1004
  ];
975
- var PLATFORM_ADMIN_ABILITIES = ["*"];
976
- function resolveAbilitiesForRole(role) {
977
- if (role === "admin") {
978
- return [...PLATFORM_ADMIN_ABILITIES];
979
- }
980
- return [...MEMBER_ABILITIES];
981
- }
982
-
983
- // ../../src/modules/user/authService.ts
984
- class AuthService {
985
- users;
986
- tokens;
987
- oauthIdentities;
988
- oauthProviders = new Map;
989
- constructor(users, tokens, oauthIdentities) {
990
- this.users = users;
991
- this.tokens = tokens;
992
- this.oauthIdentities = oauthIdentities;
993
- }
994
- registerOAuthProvider(provider) {
995
- this.oauthProviders.set(provider.name, provider);
996
- }
997
- getOAuthProvider(name) {
998
- return this.oauthProviders.get(name);
999
- }
1000
- async loginWithPassword(email, password, options = {}) {
1001
- const user = await this.users.findByEmail(email);
1002
- if (!user?.password_hash) {
1003
- logSecurityEvent("auth_login_failed", { reason: "unknown_user", email });
1004
- throw new UnauthorizedError("Invalid credentials.");
1005
- }
1006
- const valid = await verifyPassword(password, user.password_hash);
1007
- if (!valid) {
1008
- logSecurityEvent("auth_login_failed", { reason: "invalid_password", user_id: user.id });
1009
- throw new UnauthorizedError("Invalid credentials.");
1010
- }
1011
- if (isFeatureEnabled("emailVerification") && !user.email_verified_at) {
1012
- logSecurityEvent("auth_login_blocked", { reason: "email_unverified", user_id: user.id });
1013
- throw new UnauthorizedError("Email address is not verified.");
1014
- }
1015
- if (isFeatureEnabled("mfa") && user.mfa_enabled) {
1016
- const mfaSecret = revealMfaSecret(user.mfa_secret);
1017
- if (!mfaSecret || !options.mfaCode || !verifyTotp(mfaSecret, options.mfaCode)) {
1018
- logSecurityEvent("auth_login_failed", { reason: "invalid_mfa", user_id: user.id });
1019
- throw new UnauthorizedError("Invalid MFA code.");
1020
- }
1021
- }
1022
- logSecurityEvent("auth_login_success", { user_id: user.id, method: "password" });
1023
- return await this.tokens.createToken(user.id, {
1024
- name: "password-login",
1025
- abilities: resolveAbilitiesForRole(user.role),
1026
- expiresInDays: resolveDefaultTokenExpiryDays() ?? undefined
1027
- });
1028
- }
1029
- async loginWithOAuth(providerName, code) {
1030
- const provider = this.oauthProviders.get(providerName);
1031
- if (!provider) {
1032
- throw new UnauthorizedError("Unsupported OAuth provider.");
1033
- }
1034
- const profile = await provider.exchangeCode(code);
1035
- const user = await this.findOrCreateOAuthUser(providerName, profile);
1036
- logSecurityEvent("auth_login_success", { user_id: user.id, method: `oauth:${providerName}` });
1037
- return await this.tokens.createToken(user.id, {
1038
- name: `${providerName}-oauth`,
1039
- abilities: resolveAbilitiesForRole(user.role),
1040
- expiresInDays: resolveDefaultTokenExpiryDays() ?? undefined
1041
- });
1042
- }
1043
- buildOAuthAuthorizationUrl(providerName, state) {
1044
- const provider = this.oauthProviders.get(providerName);
1045
- if (!provider) {
1046
- throw new UnauthorizedError("Unsupported OAuth provider.");
1047
- }
1048
- return provider.getAuthorizationUrl(state);
1049
- }
1050
- async findOrCreateOAuthUser(providerName, profile) {
1051
- const existingIdentity = await this.oauthIdentities.findByProviderUser(providerName, profile.providerUserId);
1052
- if (existingIdentity) {
1053
- return await this.users.findByIdOrThrow(existingIdentity.user_id);
1054
- }
1055
- const existingUser = await this.users.findByEmail(profile.email);
1056
- const user = existingUser ?? await this.users.create({
1057
- name: profile.name,
1058
- email: profile.email,
1059
- role: "member",
1060
- tenant_id: currentTenantId2(),
1061
- email_verified_at: new Date,
1062
- created_at: new Date,
1063
- updated_at: new Date
1064
- });
1065
- await this.oauthIdentities.create({
1066
- user_id: user.id,
1067
- provider: providerName,
1068
- provider_user_id: profile.providerUserId,
1069
- email: profile.email,
1070
- created_at: new Date
1071
- });
1072
- return user;
1073
- }
1074
- }
1075
-
1076
1005
  // ../../src/modules/user/notificationRepository.ts
1077
1006
  import { BaseRepository as BaseRepository3 } from "@getstrata/core/database/baseRepository";
1078
1007
 
@@ -3,7 +3,7 @@ var __jsonParse = (a) => JSON.parse(a);
3
3
 
4
4
  // ../../src/bootstrap/createSpaRoutes.ts
5
5
  import { join } from "path";
6
- import { jsonResponse } from "@getstrata/core/http";
6
+ import { jsonResponse } from "@getstrata/core/http/response";
7
7
 
8
8
  // ../../src/config/frontend.ts
9
9
  function readFrontendMode() {
@@ -58,7 +58,7 @@ function mergeSpaRoutes(dependencies, routes) {
58
58
  };
59
59
  }
60
60
  export {
61
- mergeSpaRoutes,
61
+ SPA_DIST_DIRECTORY,
62
62
  createSpaRoutes,
63
- SPA_DIST_DIRECTORY
63
+ mergeSpaRoutes
64
64
  };
@@ -180,7 +180,7 @@ class HttpKernel {
180
180
  return this.wrap(["api", "authenticated"], handler);
181
181
  }
182
182
  wrapWeb(handler) {
183
- return handler;
183
+ return this.wrap("web", handler);
184
184
  }
185
185
  wrapWebPublicRead(handler) {
186
186
  if (isPublicReadsEnabled()) {
@@ -190,19 +190,19 @@ class HttpKernel {
190
190
  }
191
191
  wrapWebAuthenticated(handler) {
192
192
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
193
- return withMiddleware(createRequireWebAuthMiddleware(auth))(handler);
193
+ return this.wrap("web", withMiddleware(createRequireWebAuthMiddleware(auth))(handler));
194
194
  }
195
195
  wrapWebAbility(ability, handler) {
196
196
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
197
197
  const abilityChecker = this.dependencies.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
198
198
  const requireAbility = createRequireAbilityMiddleware(abilityChecker);
199
199
  const middleware = [createRequireWebAuthMiddleware(auth), requireAbility(ability)];
200
- return withMiddleware(...middleware)(handler);
200
+ return this.wrap("web", withMiddleware(...middleware)(handler));
201
201
  }
202
202
  wrapWebGlobalAdmin(handler) {
203
203
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
204
204
  const middleware = [createRequireWebAuthMiddleware(auth), createRequireGlobalAdminMiddleware()];
205
- return withMiddleware(...middleware)(handler);
205
+ return this.wrap("web", withMiddleware(...middleware)(handler));
206
206
  }
207
207
  wrapAuthenticated(handler) {
208
208
  return this.wrap("authenticated", handler);
@@ -301,7 +301,7 @@ function resolveModulesDirectory(options) {
301
301
  if (state.configuredModulesDir) {
302
302
  return state.configuredModulesDir;
303
303
  }
304
- return join(import.meta.dir, "../modules");
304
+ throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
305
305
  }
306
306
  async function loadDiscoveredModules(options) {
307
307
  const modulesDirectory = resolveModulesDirectory(options);
@@ -335,6 +335,12 @@ async function ensureModulesLoaded(options) {
335
335
  function discoverModules() {
336
336
  return readDiscoverModulesState().appModules;
337
337
  }
338
+ function resetDiscoverModulesForTests() {
339
+ const state = readDiscoverModulesState();
340
+ state.configuredModulesDir = undefined;
341
+ state.appModules.length = 0;
342
+ state.modulesReady = undefined;
343
+ }
338
344
  // ../../src/bootstrap/prefixRouteMap.ts
339
345
  function prefixRouteMap(prefix, routes) {
340
346
  const normalizedPrefix = prefix.replace(/\/$/, "");
@@ -423,7 +429,7 @@ function buildWebModuleRoutes(dependencies, options = {}) {
423
429
  routeRegistry.clear();
424
430
  }
425
431
  const kernel = createHttpKernel(dependencies);
426
- const middleware = [...kernel.globalMiddleware(), ...kernel.group("web")];
432
+ const middleware = kernel.globalMiddleware();
427
433
  const moduleRoutes = { ...seedRoutes };
428
434
  for (const module of modules) {
429
435
  if (!module.webRoutes) {
@@ -473,6 +479,6 @@ function mergeWebRoutes(dependencies, routes) {
473
479
  };
474
480
  }
475
481
  export {
476
- mergeWebRoutes,
477
- createWebRoutes
482
+ createWebRoutes,
483
+ mergeWebRoutes
478
484
  };
@@ -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
@@ -641,6 +623,6 @@ function createAppDependencies() {
641
623
  return createAppContext().dependencies;
642
624
  }
643
625
  export {
644
- createAppDependencies,
645
- createAppContext
626
+ createAppContext,
627
+ createAppDependencies
646
628
  };
@@ -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,8 +60,15 @@ 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
  export {
64
- ensureModulesLoaded,
70
+ configureModulesDirectory,
65
71
  discoverModules,
66
- configureModulesDirectory
72
+ ensureModulesLoaded,
73
+ resetDiscoverModulesForTests
67
74
  };
@@ -2,7 +2,7 @@
2
2
  var __jsonParse = (a) => JSON.parse(a);
3
3
 
4
4
  // ../../src/bootstrap/health.ts
5
- import { jsonResponse } from "@getstrata/core/http";
5
+ import { jsonResponse } from "@getstrata/core/http/response";
6
6
  var {RedisClient } = globalThis.Bun;
7
7
 
8
8
  // ../../src/config/database.ts
@@ -209,7 +209,7 @@ function createHealthRoutes(dependencies) {
209
209
  };
210
210
  }
211
211
  export {
212
- createHealthRoutes,
212
+ checkDatabase,
213
213
  checkRedis,
214
- checkDatabase
214
+ createHealthRoutes
215
215
  };
@@ -7,6 +7,6 @@ import {
7
7
  securedBindRouteModelByKey
8
8
  } from "@getstrata/core/http/securedRouteModelBinding";
9
9
  export {
10
- securedBindRouteModelByKey,
11
- securedBindRouteModel
10
+ securedBindRouteModel,
11
+ securedBindRouteModelByKey
12
12
  };
@@ -169,7 +169,7 @@ class HttpKernel {
169
169
  return this.wrap(["api", "authenticated"], handler);
170
170
  }
171
171
  wrapWeb(handler) {
172
- return handler;
172
+ return this.wrap("web", handler);
173
173
  }
174
174
  wrapWebPublicRead(handler) {
175
175
  if (isPublicReadsEnabled()) {
@@ -179,19 +179,19 @@ class HttpKernel {
179
179
  }
180
180
  wrapWebAuthenticated(handler) {
181
181
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
182
- return withMiddleware(createRequireWebAuthMiddleware(auth))(handler);
182
+ return this.wrap("web", withMiddleware(createRequireWebAuthMiddleware(auth))(handler));
183
183
  }
184
184
  wrapWebAbility(ability, handler) {
185
185
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
186
186
  const abilityChecker = this.dependencies.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
187
187
  const requireAbility = createRequireAbilityMiddleware(abilityChecker);
188
188
  const middleware = [createRequireWebAuthMiddleware(auth), requireAbility(ability)];
189
- return withMiddleware(...middleware)(handler);
189
+ return this.wrap("web", withMiddleware(...middleware)(handler));
190
190
  }
191
191
  wrapWebGlobalAdmin(handler) {
192
192
  const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
193
193
  const middleware = [createRequireWebAuthMiddleware(auth), createRequireGlobalAdminMiddleware()];
194
- return withMiddleware(...middleware)(handler);
194
+ return this.wrap("web", withMiddleware(...middleware)(handler));
195
195
  }
196
196
  wrapAuthenticated(handler) {
197
197
  return this.wrap("authenticated", handler);
@@ -265,6 +265,6 @@ function createHttpKernel(dependencies) {
265
265
  return new HttpKernel(dependencies);
266
266
  }
267
267
  export {
268
- createHttpKernel,
269
- HttpKernel
268
+ HttpKernel,
269
+ createHttpKernel
270
270
  };
@@ -44,7 +44,7 @@ function resolveModulesDirectory(options) {
44
44
  if (state.configuredModulesDir) {
45
45
  return state.configuredModulesDir;
46
46
  }
47
- return join(import.meta.dir, "../modules");
47
+ throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
48
48
  }
49
49
  async function loadDiscoveredModules(options) {
50
50
  const modulesDirectory = resolveModulesDirectory(options);
@@ -78,6 +78,12 @@ async function ensureModulesLoaded(options) {
78
78
  function discoverModules() {
79
79
  return readDiscoverModulesState().appModules;
80
80
  }
81
+ function resetDiscoverModulesForTests() {
82
+ const state = readDiscoverModulesState();
83
+ state.configuredModulesDir = undefined;
84
+ state.appModules.length = 0;
85
+ state.modulesReady = undefined;
86
+ }
81
87
 
82
88
  // ../../src/bootstrap/cache/modelCacheTags.ts
83
89
  function cacheTagsForModelWrite(tableName, action) {