@getstrata/core 0.5.66 → 0.5.68

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +1 -1
  3. package/dist/core/http/corsMiddleware.d.ts +8 -1
  4. package/dist/core/openapi/generator.d.ts +2 -2
  5. package/dist/{config/queue.d.ts → core/queue/queueConfig.d.ts} +2 -1
  6. package/dist/core/runtime/appKeyPrefix.d.ts +5 -1
  7. package/dist/entries/audit/exportAuditLogs.js +50 -39
  8. package/dist/entries/audit/siemFormatter.js +20 -0
  9. package/dist/entries/auth/oauth/providers.js +20 -0
  10. package/dist/entries/auth/oauth/samlProvider.js +20 -0
  11. package/dist/entries/cache/createCacheStore.js +20 -0
  12. package/dist/entries/facades.js +20 -0
  13. package/dist/entries/http/corsMiddleware.js +20 -18
  14. package/dist/entries/http/loginThrottleMiddleware.js +20 -0
  15. package/dist/entries/http/memoryThrottleMiddleware.js +20 -0
  16. package/dist/entries/http/parseMultipartUpload.js +1 -1
  17. package/dist/entries/http/requireAbilityMiddleware.js +4 -1
  18. package/dist/entries/http/response.js +4 -1
  19. package/dist/entries/http/scimThrottleMiddleware.js +20 -0
  20. package/dist/entries/http/securityHeadersMiddleware.js +48 -9
  21. package/dist/entries/http/throttleMiddleware.js +20 -0
  22. package/dist/entries/http/webErrorResponse.js +18 -15
  23. package/dist/entries/jobs/exportAuditLogsJob.js +50 -39
  24. package/dist/entries/mail/mailer.js +20 -0
  25. package/dist/entries/openapi/generator.js +58 -19
  26. package/dist/entries/queue/createAppQueue.js +40 -18
  27. package/dist/entries/queue/jobRunner.js +23 -21
  28. package/dist/entries/queue/publicQueue.js +40 -18
  29. package/dist/entries/queue/queueMetrics.js +43 -21
  30. package/dist/entries/queue/redisQueue.js +40 -18
  31. package/dist/entries/runtime/appKeyPrefix.js +24 -0
  32. package/dist/entries/runtime/frontendMode.js +23 -0
  33. package/dist/entries/security/publicReads.js +1 -22
  34. package/dist/entries/security/safeFetch.js +48 -9
  35. package/dist/entries/tracing/tracingMiddleware.js +20 -0
  36. package/dist/index.js +45 -57
  37. package/package.json +7 -2
  38. package/dist/bootstrap/config.d.ts +0 -15
  39. package/dist/config/app.d.ts +0 -10
  40. package/dist/config/cors.d.ts +0 -9
  41. package/dist/config/features.d.ts +0 -18
  42. /package/dist/{config → core/http}/uploads.d.ts +0 -0
  43. /package/dist/{config/frontend.d.ts → core/runtime/frontendMode.d.ts} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @getstrata/core changelog
2
2
 
3
+ ## 0.5.68
4
+
5
+ - Published core no longer imports WorkHub `src/config`. Frontend mode, queue retries, CORS, and upload limits read env (`FRONTEND_MODE`, `QUEUE_*`, `CORS_ALLOWED_ORIGINS`, `MAX_UPLOAD_BYTES`).
6
+ - `@getstrata/core/runtime/frontendMode` exports `readFrontendMode` / `isViewsEnabled` / `isSpaEnabled`.
7
+
8
+ ## 0.5.67
9
+
10
+ - OpenAPI title, server URLs, and generated SDK class name come from `APP_NAME` / `APP_URL` / `API_PREFIX` / `APP_SDK_CLASS` instead of importing WorkHub `src/config/app`.
11
+ - `appEnv()`, `appUrl()`, `apiPrefix()`, and `sdkClientClassName()` live on `@getstrata/core/runtime/appKeyPrefix`. SIEM export, HSTS, and `safeFetch` DNS resolve use `appEnv()` instead of WorkHub `appConfig`.
12
+
3
13
  ## 0.5.66
4
14
 
5
15
  - `createValidateSignatureMiddleware()` on `@getstrata/core/http/signedUrl` is Laravel’s `signed` / `ValidateSignature` middleware. Invalid or expired links throw `ForbiddenError`.
package/README.md CHANGED
@@ -82,7 +82,7 @@ import type { Migration } from "@getstrata/core/database/migrations/types";
82
82
  Package name: **`@getstrata/core`** (npm org [`@getstrata`](https://www.npmjs.com/org/getstrata)).
83
83
 
84
84
  1. Add `NPM_TOKEN` to GitHub repository secrets.
85
- 2. Tag a release: `git tag v0.5.68 && git push origin v0.5.68`
85
+ 2. Tag a release: `git tag v0.5.70 && git push origin v0.5.70`
86
86
  3. [Release workflow](../../.github/workflows/release.yml) builds and runs `npm publish --access public`.
87
87
 
88
88
  Previously published as `@eyk-workhub/framework@0.1.0`, deprecated in favor of this package.
@@ -1,3 +1,10 @@
1
1
  import type { Middleware } from "./middleware";
2
+ interface CorsConfig {
3
+ allowedOrigins: string[];
4
+ allowedMethods: string[];
5
+ allowedHeaders: string[];
6
+ maxAgeSeconds: number;
7
+ }
8
+ declare function resolveCorsConfig(): CorsConfig;
2
9
  declare function createCorsMiddleware(): Middleware;
3
- export { createCorsMiddleware };
10
+ export { createCorsMiddleware, resolveCorsConfig };
@@ -1,4 +1,4 @@
1
- import type { RegisteredRoute } from "../openapi/registeredRoute";
1
+ import type { RegisteredRoute } from "./registeredRoute";
2
2
  interface OpenApiSpec {
3
3
  openapi: string;
4
4
  info: {
@@ -17,6 +17,6 @@ interface OpenApiSpec {
17
17
  }
18
18
  declare function generateOpenApiSpec(routes: RegisteredRoute[]): OpenApiSpec;
19
19
  declare function renderOpenApiDocument(spec: OpenApiSpec): string;
20
- declare function renderTypeScriptSdk(spec: OpenApiSpec, apiPrefix?: string): string;
20
+ declare function renderTypeScriptSdk(spec: OpenApiSpec, prefix?: string): string;
21
21
  export type { OpenApiSpec };
22
22
  export { generateOpenApiSpec, renderOpenApiDocument, renderTypeScriptSdk };
@@ -3,6 +3,7 @@ interface QueueConfig {
3
3
  maxAttempts: number;
4
4
  backoffMs: number;
5
5
  }
6
+ declare function resolveQueueConfig(): QueueConfig;
6
7
  declare const queueConfig: QueueConfig;
7
8
  export type { QueueConfig };
8
- export { queueConfig };
9
+ export { queueConfig, resolveQueueConfig };
@@ -6,4 +6,8 @@ declare function appUserAgent(): string;
6
6
  declare function otelServiceName(): string;
7
7
  declare function webhookSignatureHeader(): string;
8
8
  declare function appDisplayName(): string;
9
- export { appDisplayName, appKeyPrefix, appUserAgent, namespacedRedisKey, otelServiceName, siemEventType, smtpEhloHost, webhookSignatureHeader, };
9
+ declare function appEnv(): string;
10
+ declare function appUrl(): string;
11
+ declare function apiPrefix(): string;
12
+ declare function sdkClientClassName(): string;
13
+ export { apiPrefix, appDisplayName, appEnv, appKeyPrefix, appUrl, appUserAgent, namespacedRedisKey, otelServiceName, sdkClientClassName, siemEventType, smtpEhloHost, webhookSignatureHeader, };
@@ -2,14 +2,53 @@
2
2
  // ../../src/core/audit/exportAuditLogs.ts
3
3
  import { repositoryConnection as db2 } from "@getstrata/core/database/repositoryConnection";
4
4
 
5
- // ../../src/config/app.ts
6
- var appConfig = {
7
- name: process.env.APP_NAME?.trim() || "WorkHub",
8
- env: process.env.APP_ENV ?? "local",
9
- debug: (process.env.APP_DEBUG ?? "true") !== "false",
10
- url: process.env.APP_URL ?? "http://localhost:3000",
11
- apiPrefix: process.env.API_PREFIX ?? "/api/v1"
12
- };
5
+ // ../../src/core/runtime/appKeyPrefix.ts
6
+ function appKeyPrefix() {
7
+ return process.env.APP_KEY_PREFIX?.trim() || "workhub";
8
+ }
9
+ function namespacedRedisKey(kind) {
10
+ return `${appKeyPrefix()}:${kind}`;
11
+ }
12
+ function smtpEhloHost() {
13
+ const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
14
+ const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
15
+ return safe || "strata.local";
16
+ }
17
+ function siemEventType() {
18
+ return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
19
+ }
20
+ function appUserAgent() {
21
+ return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
22
+ }
23
+ function otelServiceName() {
24
+ return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
25
+ }
26
+ function webhookSignatureHeader() {
27
+ return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
28
+ }
29
+ function appDisplayName() {
30
+ return process.env.APP_NAME?.trim() || "WorkHub";
31
+ }
32
+ function appEnv() {
33
+ return process.env.APP_ENV?.trim() || "local";
34
+ }
35
+ function appUrl() {
36
+ return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
37
+ }
38
+ function apiPrefix() {
39
+ const raw = process.env.API_PREFIX?.trim() || "/api/v1";
40
+ const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
41
+ const trimmed = withSlash.replace(/\/+$/, "");
42
+ return trimmed || "/api/v1";
43
+ }
44
+ function sdkClientClassName() {
45
+ const override = process.env.APP_SDK_CLASS?.trim();
46
+ if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
47
+ return override;
48
+ }
49
+ const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
50
+ return fromName ? `${fromName}Client` : "AppClient";
51
+ }
13
52
 
14
53
  // ../../src/core/security/safeUrl.ts
15
54
  import { lookup as dnsLookupImpl } from "dns/promises";
@@ -113,7 +152,7 @@ var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
113
152
  async function safeFetch(input, init = {}, options = {}) {
114
153
  const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
115
154
  const maxRedirects = options.maxRedirects ?? 0;
116
- const resolveDns = options.resolveDns ?? appConfig.env === "production";
155
+ const resolveDns = options.resolveDns ?? appEnv() === "production";
117
156
  const urlOptions = { allowHttp: options.allowHttp, resolveDns };
118
157
  const controller = new AbortController;
119
158
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
@@ -153,34 +192,6 @@ async function runWithMigrationBypass(callback) {
153
192
  }
154
193
  }
155
194
 
156
- // ../../src/core/runtime/appKeyPrefix.ts
157
- function appKeyPrefix() {
158
- return process.env.APP_KEY_PREFIX?.trim() || "workhub";
159
- }
160
- function namespacedRedisKey(kind) {
161
- return `${appKeyPrefix()}:${kind}`;
162
- }
163
- function smtpEhloHost() {
164
- const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
165
- const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
166
- return safe || "strata.local";
167
- }
168
- function siemEventType() {
169
- return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
170
- }
171
- function appUserAgent() {
172
- return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
173
- }
174
- function otelServiceName() {
175
- return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
176
- }
177
- function webhookSignatureHeader() {
178
- return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
179
- }
180
- function appDisplayName() {
181
- return process.env.APP_NAME?.trim() || "WorkHub";
182
- }
183
-
184
195
  // ../../src/core/audit/siemFormatter.ts
185
196
  function formatSiemAuditEvent(input) {
186
197
  return {
@@ -220,7 +231,7 @@ function resolveAuditExportConfig() {
220
231
  if (!endpoint) {
221
232
  return null;
222
233
  }
223
- assertSafeOutboundUrl(endpoint, { allowHttp: appConfig.env !== "production" });
234
+ assertSafeOutboundUrl(endpoint, { allowHttp: appEnv() !== "production" });
224
235
  const batchSize = Number(process.env.SIEM_EXPORT_BATCH_SIZE ?? "100");
225
236
  return {
226
237
  endpoint,
@@ -278,7 +289,7 @@ async function exportPendingAuditLogs() {
278
289
  ...process.env.SIEM_EXPORT_TOKEN ? { authorization: `Bearer ${process.env.SIEM_EXPORT_TOKEN}` } : {}
279
290
  },
280
291
  body
281
- }, { allowHttp: appConfig.env !== "production" });
292
+ }, { allowHttp: appEnv() !== "production" });
282
293
  if (!response.ok) {
283
294
  throw new Error(`SIEM export failed with status ${response.status}.`);
284
295
  }
@@ -26,6 +26,26 @@ function webhookSignatureHeader() {
26
26
  function appDisplayName() {
27
27
  return process.env.APP_NAME?.trim() || "WorkHub";
28
28
  }
29
+ function appEnv() {
30
+ return process.env.APP_ENV?.trim() || "local";
31
+ }
32
+ function appUrl() {
33
+ return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
34
+ }
35
+ function apiPrefix() {
36
+ const raw = process.env.API_PREFIX?.trim() || "/api/v1";
37
+ const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
38
+ const trimmed = withSlash.replace(/\/+$/, "");
39
+ return trimmed || "/api/v1";
40
+ }
41
+ function sdkClientClassName() {
42
+ const override = process.env.APP_SDK_CLASS?.trim();
43
+ if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
44
+ return override;
45
+ }
46
+ const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
47
+ return fromName ? `${fromName}Client` : "AppClient";
48
+ }
29
49
 
30
50
  // ../../src/core/audit/siemFormatter.ts
31
51
  function formatSiemAuditEvent(input) {
@@ -26,6 +26,26 @@ function webhookSignatureHeader() {
26
26
  function appDisplayName() {
27
27
  return process.env.APP_NAME?.trim() || "WorkHub";
28
28
  }
29
+ function appEnv() {
30
+ return process.env.APP_ENV?.trim() || "local";
31
+ }
32
+ function appUrl() {
33
+ return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
34
+ }
35
+ function apiPrefix() {
36
+ const raw = process.env.API_PREFIX?.trim() || "/api/v1";
37
+ const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
38
+ const trimmed = withSlash.replace(/\/+$/, "");
39
+ return trimmed || "/api/v1";
40
+ }
41
+ function sdkClientClassName() {
42
+ const override = process.env.APP_SDK_CLASS?.trim();
43
+ if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
44
+ return override;
45
+ }
46
+ const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
47
+ return fromName ? `${fromName}Client` : "AppClient";
48
+ }
29
49
 
30
50
  // ../../src/core/auth/oauth/providers.ts
31
51
  class GitHubOAuthProvider {
@@ -26,6 +26,26 @@ function webhookSignatureHeader() {
26
26
  function appDisplayName() {
27
27
  return process.env.APP_NAME?.trim() || "WorkHub";
28
28
  }
29
+ function appEnv() {
30
+ return process.env.APP_ENV?.trim() || "local";
31
+ }
32
+ function appUrl() {
33
+ return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
34
+ }
35
+ function apiPrefix() {
36
+ const raw = process.env.API_PREFIX?.trim() || "/api/v1";
37
+ const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
38
+ const trimmed = withSlash.replace(/\/+$/, "");
39
+ return trimmed || "/api/v1";
40
+ }
41
+ function sdkClientClassName() {
42
+ const override = process.env.APP_SDK_CLASS?.trim();
43
+ if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
44
+ return override;
45
+ }
46
+ const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
47
+ return fromName ? `${fromName}Client` : "AppClient";
48
+ }
29
49
 
30
50
  // ../../src/core/auth/oauth/samlProvider.ts
31
51
  class SamlProvider {
@@ -29,6 +29,26 @@ function webhookSignatureHeader() {
29
29
  function appDisplayName() {
30
30
  return process.env.APP_NAME?.trim() || "WorkHub";
31
31
  }
32
+ function appEnv() {
33
+ return process.env.APP_ENV?.trim() || "local";
34
+ }
35
+ function appUrl() {
36
+ return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
37
+ }
38
+ function apiPrefix() {
39
+ const raw = process.env.API_PREFIX?.trim() || "/api/v1";
40
+ const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
41
+ const trimmed = withSlash.replace(/\/+$/, "");
42
+ return trimmed || "/api/v1";
43
+ }
44
+ function sdkClientClassName() {
45
+ const override = process.env.APP_SDK_CLASS?.trim();
46
+ if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
47
+ return override;
48
+ }
49
+ const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
50
+ return fromName ? `${fromName}Client` : "AppClient";
51
+ }
32
52
 
33
53
  // ../../src/core/cache/redisCacheStore.ts
34
54
  function cacheKeyPrefix() {
@@ -38,6 +38,26 @@ function webhookSignatureHeader() {
38
38
  function appDisplayName() {
39
39
  return process.env.APP_NAME?.trim() || "WorkHub";
40
40
  }
41
+ function appEnv() {
42
+ return process.env.APP_ENV?.trim() || "local";
43
+ }
44
+ function appUrl() {
45
+ return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
46
+ }
47
+ function apiPrefix() {
48
+ const raw = process.env.API_PREFIX?.trim() || "/api/v1";
49
+ const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
50
+ const trimmed = withSlash.replace(/\/+$/, "");
51
+ return trimmed || "/api/v1";
52
+ }
53
+ function sdkClientClassName() {
54
+ const override = process.env.APP_SDK_CLASS?.trim();
55
+ if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
56
+ return override;
57
+ }
58
+ const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
59
+ return fromName ? `${fromName}Client` : "AppClient";
60
+ }
41
61
 
42
62
  // ../../src/core/mail/mailer.ts
43
63
  function resolveSmtpConfig() {
@@ -1,22 +1,22 @@
1
1
  // @bun
2
- // ../../src/config/cors.ts
3
- var corsConfig = {
4
- allowedOrigins: (process.env.CORS_ALLOWED_ORIGINS ?? "*").split(",").map((origin) => origin.trim()).filter(Boolean),
5
- allowedMethods: ["GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS"],
6
- allowedHeaders: [
7
- "Authorization",
8
- "Content-Type",
9
- "X-Request-Id",
10
- "X-Tenant-Id",
11
- "X-Authenticated-User-Id",
12
- "X-Authenticated-User-Role",
13
- "If-Match",
14
- "If-None-Match"
15
- ],
16
- maxAgeSeconds: 86400
17
- };
18
-
19
2
  // ../../src/core/http/corsMiddleware.ts
3
+ function resolveCorsConfig() {
4
+ return {
5
+ allowedOrigins: (process.env.CORS_ALLOWED_ORIGINS ?? "*").split(",").map((origin) => origin.trim()).filter(Boolean),
6
+ allowedMethods: ["GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS"],
7
+ allowedHeaders: [
8
+ "Authorization",
9
+ "Content-Type",
10
+ "X-Request-Id",
11
+ "X-Tenant-Id",
12
+ "X-Authenticated-User-Id",
13
+ "X-Authenticated-User-Role",
14
+ "If-Match",
15
+ "If-None-Match"
16
+ ],
17
+ maxAgeSeconds: 86400
18
+ };
19
+ }
20
20
  function createCorsMiddleware() {
21
21
  return async (request, next) => {
22
22
  if (request.method === "OPTIONS") {
@@ -40,6 +40,7 @@ function createCorsMiddleware() {
40
40
  function buildCorsHeaders(request) {
41
41
  const headers = new Headers;
42
42
  const origin = request.headers.get("origin");
43
+ const corsConfig = resolveCorsConfig();
43
44
  const allowedOrigins = corsConfig.allowedOrigins;
44
45
  const allowOrigin = allowedOrigins.includes("*") || origin && allowedOrigins.includes(origin) ? origin ?? "*" : allowedOrigins[0] ?? "*";
45
46
  headers.set("Access-Control-Allow-Origin", allowOrigin);
@@ -50,5 +51,6 @@ function buildCorsHeaders(request) {
50
51
  return headers;
51
52
  }
52
53
  export {
53
- createCorsMiddleware
54
+ createCorsMiddleware,
55
+ resolveCorsConfig
54
56
  };
@@ -29,6 +29,26 @@ function webhookSignatureHeader() {
29
29
  function appDisplayName() {
30
30
  return process.env.APP_NAME?.trim() || "WorkHub";
31
31
  }
32
+ function appEnv() {
33
+ return process.env.APP_ENV?.trim() || "local";
34
+ }
35
+ function appUrl() {
36
+ return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
37
+ }
38
+ function apiPrefix() {
39
+ const raw = process.env.API_PREFIX?.trim() || "/api/v1";
40
+ const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
41
+ const trimmed = withSlash.replace(/\/+$/, "");
42
+ return trimmed || "/api/v1";
43
+ }
44
+ function sdkClientClassName() {
45
+ const override = process.env.APP_SDK_CLASS?.trim();
46
+ if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
47
+ return override;
48
+ }
49
+ const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
50
+ return fromName ? `${fromName}Client` : "AppClient";
51
+ }
32
52
 
33
53
  // ../../src/core/http/clientIp.ts
34
54
  function trustForwardedFor(env = process.env) {
@@ -26,6 +26,26 @@ function webhookSignatureHeader() {
26
26
  function appDisplayName() {
27
27
  return process.env.APP_NAME?.trim() || "WorkHub";
28
28
  }
29
+ function appEnv() {
30
+ return process.env.APP_ENV?.trim() || "local";
31
+ }
32
+ function appUrl() {
33
+ return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
34
+ }
35
+ function apiPrefix() {
36
+ const raw = process.env.API_PREFIX?.trim() || "/api/v1";
37
+ const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
38
+ const trimmed = withSlash.replace(/\/+$/, "");
39
+ return trimmed || "/api/v1";
40
+ }
41
+ function sdkClientClassName() {
42
+ const override = process.env.APP_SDK_CLASS?.trim();
43
+ if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
44
+ return override;
45
+ }
46
+ const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
47
+ return fromName ? `${fromName}Client` : "AppClient";
48
+ }
29
49
 
30
50
  // ../../src/core/http/clientIp.ts
31
51
  function trustForwardedFor(env = process.env) {
@@ -2,7 +2,7 @@
2
2
  // ../../src/core/http/parseMultipartUpload.ts
3
3
  import { BadRequestError, PayloadTooLargeError } from "@getstrata/core/errors/http";
4
4
 
5
- // ../../src/config/uploads.ts
5
+ // ../../src/core/http/uploads.ts
6
6
  var DEFAULT_MAX_UPLOAD_BYTES = 5 * 1024 * 1024;
7
7
  var ALLOWED_UPLOAD_MIME_TYPES = new Set([
8
8
  "application/pdf",
@@ -3,7 +3,7 @@
3
3
  import { currentAuthUser } from "@getstrata/core/auth/authContext";
4
4
  import { ForbiddenError } from "@getstrata/core/errors/http";
5
5
 
6
- // ../../src/config/frontend.ts
6
+ // ../../src/core/runtime/frontendMode.ts
7
7
  function readFrontendMode() {
8
8
  const mode = (process.env.FRONTEND_MODE ?? "api").trim();
9
9
  if (mode === "server-htmx") {
@@ -17,6 +17,9 @@ function readFrontendMode() {
17
17
  function isViewsEnabled() {
18
18
  return readFrontendMode() === "server-htmx";
19
19
  }
20
+ function isSpaEnabled() {
21
+ return readFrontendMode() === "spa-react";
22
+ }
20
23
 
21
24
  // ../../src/core/http/contentNegotiation.ts
22
25
  function requestPrefersJson(request) {
@@ -68,7 +68,7 @@ async function withDatabaseErrorHandling(operation) {
68
68
  // ../../src/core/http/webErrorResponse.ts
69
69
  import { HttpError as HttpError2, UnauthorizedError, ValidationError } from "@getstrata/core/errors/http";
70
70
 
71
- // ../../src/config/frontend.ts
71
+ // ../../src/core/runtime/frontendMode.ts
72
72
  function readFrontendMode() {
73
73
  const mode = (process.env.FRONTEND_MODE ?? "api").trim();
74
74
  if (mode === "server-htmx") {
@@ -82,6 +82,9 @@ function readFrontendMode() {
82
82
  function isViewsEnabled() {
83
83
  return readFrontendMode() === "server-htmx";
84
84
  }
85
+ function isSpaEnabled() {
86
+ return readFrontendMode() === "spa-react";
87
+ }
85
88
 
86
89
  // ../../src/core/view/webErrorView.ts
87
90
  import { currentRequestMeta } from "@getstrata/core/http/requestMetaContext";
@@ -29,6 +29,26 @@ function webhookSignatureHeader() {
29
29
  function appDisplayName() {
30
30
  return process.env.APP_NAME?.trim() || "WorkHub";
31
31
  }
32
+ function appEnv() {
33
+ return process.env.APP_ENV?.trim() || "local";
34
+ }
35
+ function appUrl() {
36
+ return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
37
+ }
38
+ function apiPrefix() {
39
+ const raw = process.env.API_PREFIX?.trim() || "/api/v1";
40
+ const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
41
+ const trimmed = withSlash.replace(/\/+$/, "");
42
+ return trimmed || "/api/v1";
43
+ }
44
+ function sdkClientClassName() {
45
+ const override = process.env.APP_SDK_CLASS?.trim();
46
+ if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
47
+ return override;
48
+ }
49
+ const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
50
+ return fromName ? `${fromName}Client` : "AppClient";
51
+ }
32
52
 
33
53
  // ../../src/core/http/clientIp.ts
34
54
  function trustForwardedFor(env = process.env) {
@@ -7,14 +7,53 @@ import {
7
7
  } from "@getstrata/core/http/contentSecurityPolicy";
8
8
  import { currentRequestMeta, runWithRequestMeta } from "@getstrata/core/http/requestMetaContext";
9
9
 
10
- // ../../src/config/app.ts
11
- var appConfig = {
12
- name: process.env.APP_NAME?.trim() || "WorkHub",
13
- env: process.env.APP_ENV ?? "local",
14
- debug: (process.env.APP_DEBUG ?? "true") !== "false",
15
- url: process.env.APP_URL ?? "http://localhost:3000",
16
- apiPrefix: process.env.API_PREFIX ?? "/api/v1"
17
- };
10
+ // ../../src/core/runtime/appKeyPrefix.ts
11
+ function appKeyPrefix() {
12
+ return process.env.APP_KEY_PREFIX?.trim() || "workhub";
13
+ }
14
+ function namespacedRedisKey(kind) {
15
+ return `${appKeyPrefix()}:${kind}`;
16
+ }
17
+ function smtpEhloHost() {
18
+ const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
19
+ const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
20
+ return safe || "strata.local";
21
+ }
22
+ function siemEventType() {
23
+ return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
24
+ }
25
+ function appUserAgent() {
26
+ return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
27
+ }
28
+ function otelServiceName() {
29
+ return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
30
+ }
31
+ function webhookSignatureHeader() {
32
+ return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
33
+ }
34
+ function appDisplayName() {
35
+ return process.env.APP_NAME?.trim() || "WorkHub";
36
+ }
37
+ function appEnv() {
38
+ return process.env.APP_ENV?.trim() || "local";
39
+ }
40
+ function appUrl() {
41
+ return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
42
+ }
43
+ function apiPrefix() {
44
+ const raw = process.env.API_PREFIX?.trim() || "/api/v1";
45
+ const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
46
+ const trimmed = withSlash.replace(/\/+$/, "");
47
+ return trimmed || "/api/v1";
48
+ }
49
+ function sdkClientClassName() {
50
+ const override = process.env.APP_SDK_CLASS?.trim();
51
+ if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
52
+ return override;
53
+ }
54
+ const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
55
+ return fromName ? `${fromName}Client` : "AppClient";
56
+ }
18
57
 
19
58
  // ../../src/core/http/securityHeadersMiddleware.ts
20
59
  function createSecurityHeadersMiddleware(options = {}) {
@@ -33,7 +72,7 @@ function createSecurityHeadersMiddleware(options = {}) {
33
72
  headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
34
73
  headers.set("X-XSS-Protection", "0");
35
74
  headers.set("Content-Security-Policy", resolveContentSecurityPolicy(response, { ...options, nonce }));
36
- if (appConfig.env === "production") {
75
+ if (appEnv() === "production") {
37
76
  headers.set("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
38
77
  }
39
78
  return new Response(response.body, {
@@ -31,6 +31,26 @@ function webhookSignatureHeader() {
31
31
  function appDisplayName() {
32
32
  return process.env.APP_NAME?.trim() || "WorkHub";
33
33
  }
34
+ function appEnv() {
35
+ return process.env.APP_ENV?.trim() || "local";
36
+ }
37
+ function appUrl() {
38
+ return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
39
+ }
40
+ function apiPrefix() {
41
+ const raw = process.env.API_PREFIX?.trim() || "/api/v1";
42
+ const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
43
+ const trimmed = withSlash.replace(/\/+$/, "");
44
+ return trimmed || "/api/v1";
45
+ }
46
+ function sdkClientClassName() {
47
+ const override = process.env.APP_SDK_CLASS?.trim();
48
+ if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
49
+ return override;
50
+ }
51
+ const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
52
+ return fromName ? `${fromName}Client` : "AppClient";
53
+ }
34
54
 
35
55
  // ../../src/core/http/clientIp.ts
36
56
  function trustForwardedFor(env = process.env) {