@getstrata/core 1.0.2 → 1.0.4

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 (55) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +1 -1
  3. package/dist/core/database/index.d.ts +2 -1
  4. package/dist/core/database/mysqlConnection.d.ts +8 -4
  5. package/dist/core/runtime/appEnv.d.ts +16 -2
  6. package/dist/core/runtime/appKeyPrefix.d.ts +8 -1
  7. package/dist/core/runtime/optionalPeer.d.ts +2 -0
  8. package/dist/core/view/etaViewEngine.d.ts +5 -1
  9. package/dist/entries/audit/exportAuditLogs.js +83 -18
  10. package/dist/entries/audit/siemFormatter.js +32 -3
  11. package/dist/entries/auth/intendedUrlCookie.js +33 -4
  12. package/dist/entries/auth/jwt.js +36 -4
  13. package/dist/entries/auth/jwtGuard.js +36 -4
  14. package/dist/entries/auth/oauth/providers.js +32 -3
  15. package/dist/entries/auth/oauth/samlProvider.js +32 -3
  16. package/dist/entries/auth/passwordConfirmCookie.js +35 -6
  17. package/dist/entries/auth/sessionCookie.js +35 -6
  18. package/dist/entries/auth/sessionGuard.js +35 -6
  19. package/dist/entries/auth/tokenHash.js +33 -4
  20. package/dist/entries/cache/createCacheStore.js +32 -3
  21. package/dist/entries/database/mysqlConnection.js +86 -5
  22. package/dist/entries/facades.js +32 -3
  23. package/dist/entries/http/corsMiddleware.js +16 -1
  24. package/dist/entries/http/csrfMiddleware.js +34 -5
  25. package/dist/entries/http/csrfToken.js +34 -5
  26. package/dist/entries/http/flashMiddleware.js +33 -4
  27. package/dist/entries/http/flashSession.js +33 -4
  28. package/dist/entries/http/loginThrottleMiddleware.js +1 -373
  29. package/dist/entries/http/memoryThrottleMiddleware.js +32 -58
  30. package/dist/entries/http/response.js +16 -56
  31. package/dist/entries/http/scimThrottleMiddleware.js +32 -3
  32. package/dist/entries/http/securityHeadersMiddleware.js +32 -3
  33. package/dist/entries/http/signedUrl.js +33 -4
  34. package/dist/entries/http/throttleMiddleware.js +32 -58
  35. package/dist/entries/http/webErrorResponse.js +1 -401
  36. package/dist/entries/jobs/exportAuditLogsJob.js +83 -18
  37. package/dist/entries/lifecycle/gracefulShutdown.js +1 -50
  38. package/dist/entries/mail/mailer.js +32 -3
  39. package/dist/entries/openapi/generator.js +32 -3
  40. package/dist/entries/queue/createAppQueue.js +32 -3
  41. package/dist/entries/queue/publicQueue.js +32 -3
  42. package/dist/entries/queue/queueMetrics.js +32 -3
  43. package/dist/entries/queue/redisQueue.js +32 -3
  44. package/dist/entries/runtime/appEnv.js +18 -1
  45. package/dist/entries/runtime/appKeyPrefix.js +1 -70
  46. package/dist/entries/security/oauthState.js +33 -4
  47. package/dist/entries/security/safeFetch.js +32 -9
  48. package/dist/entries/security/safeUrl.js +1 -105
  49. package/dist/entries/security/totp.js +32 -3
  50. package/dist/entries/tenant/databaseTenantContext.js +48 -6
  51. package/dist/entries/tracing/tracingMiddleware.js +32 -3
  52. package/dist/entries/view.js +1 -748
  53. package/dist/framework/public-api.d.ts +28 -18
  54. package/dist/index.js +555 -68
  55. package/package.json +12 -6
package/dist/index.js CHANGED
@@ -490,6 +490,26 @@ class AuthManager {
490
490
  // ../../src/core/auth/jwt.ts
491
491
  import { createHmac, timingSafeEqual } from "crypto";
492
492
 
493
+ // ../../src/core/runtime/appEnv.ts
494
+ var NON_PRODUCTION_APP_ENVS = new Set(["local", "development", "dev", "test", "testing", "ci"]);
495
+ function normalizeEnvValue(value) {
496
+ return (value ?? "").trim().toLowerCase();
497
+ }
498
+ function isProductionEnv(env = process.env) {
499
+ const appEnv = normalizeEnvValue(env.APP_ENV);
500
+ const nodeEnv = normalizeEnvValue(env.NODE_ENV);
501
+ if (appEnv === "production" || nodeEnv === "production") {
502
+ return true;
503
+ }
504
+ if (appEnv === "") {
505
+ return false;
506
+ }
507
+ return !NON_PRODUCTION_APP_ENVS.has(appEnv);
508
+ }
509
+ function envFlagEnabled(value) {
510
+ return value === "true";
511
+ }
512
+
493
513
  // ../../src/core/runtime/appKeyPrefix.ts
494
514
  function appKeyPrefix() {
495
515
  return process.env.APP_KEY_PREFIX?.trim() || "strata";
@@ -500,6 +520,18 @@ function appCookieName(kind) {
500
520
  function appDevSecret(kind) {
501
521
  return `${appKeyPrefix()}-dev-${kind}`;
502
522
  }
523
+ function requireConfiguredSecret(names, devKind, env = process.env) {
524
+ for (const name of names) {
525
+ const value = env[name]?.trim();
526
+ if (value) {
527
+ return value;
528
+ }
529
+ }
530
+ if (isProductionEnv(env)) {
531
+ throw new Error(`${names[0]} must be set outside development. Do not derive secrets from the app name.`);
532
+ }
533
+ return appDevSecret(devKind);
534
+ }
503
535
  function namespacedRedisKey(kind) {
504
536
  return `${appKeyPrefix()}:${kind}`;
505
537
  }
@@ -508,16 +540,48 @@ function smtpEhloHost() {
508
540
  const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
509
541
  return safe || "strata.local";
510
542
  }
543
+ function siemEventType() {
544
+ return process.env.SIEM_EVENT_TYPE?.trim() || `${appKeyPrefix()}.audit`;
545
+ }
546
+ function appUserAgent() {
547
+ return process.env.APP_USER_AGENT?.trim() || appKeyPrefix();
548
+ }
511
549
  function otelServiceName() {
512
550
  return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
513
551
  }
552
+ function webhookSignatureHeader() {
553
+ return process.env.WEBHOOK_SIGNATURE_HEADER?.trim() || `x-${appKeyPrefix()}-signature`;
554
+ }
555
+ function appDisplayName() {
556
+ return process.env.APP_NAME?.trim() || "Strata";
557
+ }
514
558
  function appEnv() {
515
559
  return process.env.APP_ENV?.trim() || "local";
516
560
  }
561
+ function appUrl() {
562
+ return (process.env.APP_URL?.trim() || "http://localhost:3000").replace(/\/$/, "");
563
+ }
564
+ function apiPrefix() {
565
+ const raw = process.env.API_PREFIX?.trim() || "/api/v1";
566
+ const withSlash = raw.startsWith("/") ? raw : `/${raw}`;
567
+ const trimmed = withSlash.replace(/\/+$/, "");
568
+ return trimmed || "/api/v1";
569
+ }
570
+ function sdkClientClassName() {
571
+ const override = process.env.APP_SDK_CLASS?.trim();
572
+ if (override && /^[A-Za-z_][A-Za-z0-9_]*$/.test(override)) {
573
+ return override;
574
+ }
575
+ const fromName = appDisplayName().replace(/[^A-Za-z0-9]/g, "");
576
+ return fromName ? `${fromName}Client` : "AppClient";
577
+ }
517
578
 
518
579
  // ../../src/core/auth/jwt.ts
519
580
  function resolveJwtSecret(secret) {
520
- return secret?.trim() || process.env.JWT_SECRET?.trim() || process.env.SESSION_SECRET?.trim() || appDevSecret("jwt-secret");
581
+ if (secret?.trim()) {
582
+ return secret.trim();
583
+ }
584
+ return requireConfiguredSecret(["JWT_SECRET", "SESSION_SECRET"], "jwt-secret");
521
585
  }
522
586
  function jwtTtlSeconds(override) {
523
587
  if (typeof override === "number" && Number.isInteger(override) && override > 0) {
@@ -642,6 +706,9 @@ function configureMembershipLookup(lookup) {
642
706
  function resolveMembershipLookup() {
643
707
  return membershipRepository;
644
708
  }
709
+ function resetMembershipLookupForTests() {
710
+ membershipRepository = uninitializedMembershipLookup;
711
+ }
645
712
  async function runWithMembershipContext(callback) {
646
713
  const user = currentAuthUser();
647
714
  if (!user || isGlobalAdmin(user)) {
@@ -1101,6 +1168,11 @@ function getDefaultDatabaseQuery() {
1101
1168
  }
1102
1169
  return query;
1103
1170
  }
1171
+ function resetDefaultDatabasePoolForTests() {
1172
+ const holder = defaultConnectionState();
1173
+ holder.pool = null;
1174
+ holder.query = null;
1175
+ }
1104
1176
 
1105
1177
  // ../../src/core/security/timingSafeCompare.ts
1106
1178
  import { timingSafeEqual as timingSafeEqual2 } from "crypto";
@@ -2215,6 +2287,14 @@ function appendWhereParts(tableName, where, params) {
2215
2287
  }
2216
2288
  return clauses.join(" AND ");
2217
2289
  }
2290
+ function buildWhereClause(tableName, where = {}) {
2291
+ const params = [];
2292
+ const body = appendWhereParts(tableName, where, params);
2293
+ return {
2294
+ clause: body.length > 0 ? ` WHERE ${body}` : "",
2295
+ params
2296
+ };
2297
+ }
2218
2298
  function remapExistsSql(sql, existsParams, params) {
2219
2299
  const offset = params.length;
2220
2300
  params.push(...existsParams);
@@ -5211,8 +5291,55 @@ function registerModelRepository(model, repository) {
5211
5291
  ensureBooted(model);
5212
5292
  return model;
5213
5293
  }
5294
+ // ../../src/core/runtime/optionalPeer.ts
5295
+ function missingOptionalPeer(packageName, reason, error) {
5296
+ return new Error(`Install ${packageName} ${reason} (\`bun add ${packageName}\`).`, {
5297
+ cause: error
5298
+ });
5299
+ }
5300
+
5214
5301
  // ../../src/core/database/mysqlConnection.ts
5215
- import mysql from "mysql2/promise";
5302
+ var MYSQL_SESSION_UTC = "SET time_zone = '+00:00'";
5303
+ var mysqlModule;
5304
+ var mysqlPending;
5305
+ var importMysql = defaultImportMysql;
5306
+ async function defaultImportMysql() {
5307
+ return import("mysql2/promise");
5308
+ }
5309
+ function mysqlApi(mod) {
5310
+ if (typeof mod.createPool === "function") {
5311
+ return mod;
5312
+ }
5313
+ const withDefault = mod;
5314
+ if (typeof withDefault.default?.createPool === "function") {
5315
+ return withDefault.default;
5316
+ }
5317
+ throw new Error("mysql2/promise did not export createPool.");
5318
+ }
5319
+ async function loadMysql() {
5320
+ if (mysqlModule) {
5321
+ return mysqlModule;
5322
+ }
5323
+ if (!mysqlPending) {
5324
+ mysqlPending = (async () => {
5325
+ let mod;
5326
+ try {
5327
+ mod = await importMysql();
5328
+ } catch (error) {
5329
+ mysqlPending = undefined;
5330
+ throw missingOptionalPeer("mysql2", "to open a MySQL connection", error);
5331
+ }
5332
+ try {
5333
+ mysqlModule = mysqlApi(mod);
5334
+ return mysqlModule;
5335
+ } catch (error) {
5336
+ mysqlPending = undefined;
5337
+ throw error;
5338
+ }
5339
+ })();
5340
+ }
5341
+ return mysqlPending;
5342
+ }
5216
5343
  function rowsFromResult(result) {
5217
5344
  if (Array.isArray(result)) {
5218
5345
  return result;
@@ -5235,7 +5362,6 @@ function createMysqlConnectionFromPool(pool) {
5235
5362
  }
5236
5363
  };
5237
5364
  }
5238
- var MYSQL_SESSION_UTC = "SET time_zone = '+00:00'";
5239
5365
  function pinSessionToUtc(connection) {
5240
5366
  connection.query(MYSQL_SESSION_UTC, (error) => {
5241
5367
  if (error) {
@@ -5243,18 +5369,47 @@ function pinSessionToUtc(connection) {
5243
5369
  }
5244
5370
  });
5245
5371
  }
5246
- function createMysqlPool(url) {
5372
+ function createPoolFromModule(mysql, url) {
5247
5373
  const pool = mysql.createPool({ uri: url, timezone: "Z" });
5248
5374
  pool.on("connection", (connection) => {
5249
5375
  pinSessionToUtc(connection);
5250
5376
  });
5251
5377
  return pool;
5252
5378
  }
5379
+ async function createMysqlPool(url) {
5380
+ return createPoolFromModule(await loadMysql(), url);
5381
+ }
5253
5382
  function createMysqlConnection(url) {
5254
5383
  if (!url.trim()) {
5255
5384
  throw new Error("MYSQL_URL is not configured. Set url before creating a MySQL pool.");
5256
5385
  }
5257
- return createMysqlConnectionFromPool(createMysqlPool(url));
5386
+ let poolPending;
5387
+ function ensurePool() {
5388
+ if (!poolPending) {
5389
+ poolPending = createMysqlPool(url).catch((error) => {
5390
+ poolPending = undefined;
5391
+ throw error;
5392
+ });
5393
+ }
5394
+ return poolPending;
5395
+ }
5396
+ return {
5397
+ async unsafe(query, params = []) {
5398
+ const [result] = await (await ensurePool()).execute(query, [...params]);
5399
+ return rowsFromResult(result);
5400
+ },
5401
+ async close() {
5402
+ if (!poolPending) {
5403
+ return;
5404
+ }
5405
+ const pending = poolPending;
5406
+ poolPending = undefined;
5407
+ const pool = await pending.catch(() => {
5408
+ return;
5409
+ });
5410
+ await pool?.end();
5411
+ }
5412
+ };
5258
5413
  }
5259
5414
  // ../../src/core/database/namedConnections.ts
5260
5415
  var REGISTRY_KEY = Symbol.for("@getstrata/namedConnections");
@@ -6568,6 +6723,9 @@ function generateCspNonce() {
6568
6723
  function configureContentSecurityPolicy(options) {
6569
6724
  configuredHtmlOptions = { ...options };
6570
6725
  }
6726
+ function resetContentSecurityPolicyForTests() {
6727
+ configuredHtmlOptions = {};
6728
+ }
6571
6729
  function cloneDirectives(source) {
6572
6730
  const copy = {};
6573
6731
  for (const [directive, values] of Object.entries(source)) {
@@ -6678,11 +6836,6 @@ function readRequestCookie(request, name) {
6678
6836
  function readBunRequestCookie(request, name) {
6679
6837
  return request.cookies.get(name) ?? readRequestCookie(request, name);
6680
6838
  }
6681
- // ../../src/core/runtime/appEnv.ts
6682
- function isProductionEnv(env = process.env) {
6683
- return env.APP_ENV === "production" || env.NODE_ENV === "production";
6684
- }
6685
-
6686
6839
  // ../../src/core/http/corsMiddleware.ts
6687
6840
  function defaultAllowedOrigins() {
6688
6841
  return isProductionEnv() ? "" : "*";
@@ -6748,7 +6901,7 @@ function csrfCookieName() {
6748
6901
  return process.env.CSRF_COOKIE_NAME?.trim() || appCookieName("csrf");
6749
6902
  }
6750
6903
  function resolveCsrfSecret() {
6751
- return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || process.env.ADMIN_API_TOKEN?.trim() || appDevSecret("csrf-secret");
6904
+ return requireConfiguredSecret(["SESSION_SECRET", "OAUTH_STATE_SECRET", "ADMIN_API_TOKEN"], "csrf-secret");
6752
6905
  }
6753
6906
  function csrfVerifyOptions() {
6754
6907
  return { secret: resolveCsrfSecret(), maxAge: CSRF_TTL_MS };
@@ -6763,7 +6916,7 @@ function tokensMatch(left, right) {
6763
6916
  }
6764
6917
  function createCsrfTokenCookie() {
6765
6918
  const token = Bun.CSRF.generate(resolveCsrfSecret(), { expiresIn: CSRF_TTL_MS });
6766
- const secure = process.env.APP_ENV === "production" ? "; Secure" : "";
6919
+ const secure = isProductionEnv() ? "; Secure" : "";
6767
6920
  return {
6768
6921
  token,
6769
6922
  cookie: `${csrfCookieName()}=${encodeURIComponent(token)}; Path=/; SameSite=Lax; Max-Age=${Math.floor(CSRF_TTL_MS / 1000)}${secure}`
@@ -6883,7 +7036,7 @@ function flashCookieName() {
6883
7036
  return process.env.FLASH_COOKIE_NAME?.trim() || appCookieName("flash");
6884
7037
  }
6885
7038
  function resolveFlashSecret() {
6886
- return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || appDevSecret("flash-secret");
7039
+ return requireConfiguredSecret(["SESSION_SECRET", "OAUTH_STATE_SECRET"], "flash-secret");
6887
7040
  }
6888
7041
  function signFlashPayload(payload, issuedAt) {
6889
7042
  const signature = createHmac2("sha256", resolveFlashSecret()).update(`${payload}.${issuedAt}`).digest("hex");
@@ -6993,6 +7146,49 @@ function getQueryParams(request) {
6993
7146
  }
6994
7147
  return new URL(request.url).searchParams;
6995
7148
  }
7149
+ function parseOptionalPositiveIntQueryParam(params, name) {
7150
+ const value = params.get(name);
7151
+ if (value === null || value.trim() === "") {
7152
+ return;
7153
+ }
7154
+ const parsed = Number.parseInt(value, 10);
7155
+ if (!Number.isInteger(parsed) || parsed <= 0) {
7156
+ throw new BadRequestError(`Invalid query parameter "${name}". Expected a positive integer.`);
7157
+ }
7158
+ return parsed;
7159
+ }
7160
+ function parseOptionalBooleanQueryParam(params, name) {
7161
+ const value = params.get(name);
7162
+ if (value === null || value.trim() === "") {
7163
+ return;
7164
+ }
7165
+ switch (value.toLowerCase()) {
7166
+ case "true":
7167
+ case "1":
7168
+ return true;
7169
+ case "false":
7170
+ case "0":
7171
+ return false;
7172
+ default:
7173
+ throw new BadRequestError(`Invalid query parameter "${name}". Expected a boolean.`);
7174
+ }
7175
+ }
7176
+ function parseOptionalEnumQueryParam(params, name, allowedValues) {
7177
+ const value = params.get(name);
7178
+ if (value === null || value.trim() === "") {
7179
+ return;
7180
+ }
7181
+ if (!allowedValues.includes(value)) {
7182
+ throw new BadRequestError(`Invalid query parameter "${name}". Expected one of: ${allowedValues.join(", ")}.`);
7183
+ }
7184
+ return value;
7185
+ }
7186
+ function expectObject(value, label = "request body") {
7187
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
7188
+ throw new BadRequestError(`${label} must be a JSON object.`);
7189
+ }
7190
+ return value;
7191
+ }
6996
7192
  async function parseJsonBody(request, validator) {
6997
7193
  let payload;
6998
7194
  try {
@@ -7002,6 +7198,55 @@ async function parseJsonBody(request, validator) {
7002
7198
  }
7003
7199
  return validator(payload);
7004
7200
  }
7201
+ function readRequiredString(payload, field, options = {}) {
7202
+ const value = payload[field];
7203
+ if (typeof value !== "string" || value.trim() === "") {
7204
+ throw new BadRequestError(`"${field}" is required and must be a string.`);
7205
+ }
7206
+ const trimmed = value.trim();
7207
+ if (options.minLength !== undefined && trimmed.length < options.minLength) {
7208
+ throw new BadRequestError(`"${field}" must be at least ${options.minLength} characters.`);
7209
+ }
7210
+ if (options.maxLength !== undefined && trimmed.length > options.maxLength) {
7211
+ throw new BadRequestError(`"${field}" must be at most ${options.maxLength} characters.`);
7212
+ }
7213
+ if (options.pattern && !options.pattern.test(trimmed)) {
7214
+ throw new BadRequestError(`"${field}" has an invalid format.`);
7215
+ }
7216
+ return trimmed;
7217
+ }
7218
+ function readOptionalString(payload, field, options = {}) {
7219
+ if (!(field in payload) || payload[field] === undefined) {
7220
+ return;
7221
+ }
7222
+ return readRequiredString(payload, field, options);
7223
+ }
7224
+ function readRequiredEnum(payload, field, allowedValues) {
7225
+ const value = readRequiredString(payload, field);
7226
+ if (!allowedValues.includes(value)) {
7227
+ throw new BadRequestError(`"${field}" must be one of: ${allowedValues.join(", ")}.`);
7228
+ }
7229
+ return value;
7230
+ }
7231
+ function readOptionalEnum(payload, field, allowedValues) {
7232
+ if (!(field in payload) || payload[field] === undefined) {
7233
+ return;
7234
+ }
7235
+ return readRequiredEnum(payload, field, allowedValues);
7236
+ }
7237
+ function readRequiredPositiveInt(payload, field) {
7238
+ const value = payload[field];
7239
+ if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
7240
+ throw new BadRequestError(`"${field}" is required and must be a positive integer.`);
7241
+ }
7242
+ return value;
7243
+ }
7244
+ function readOptionalPositiveInt(payload, field) {
7245
+ if (!(field in payload) || payload[field] === undefined) {
7246
+ return;
7247
+ }
7248
+ return readRequiredPositiveInt(payload, field);
7249
+ }
7005
7250
  function parsePositiveIntParam(value, name = "id") {
7006
7251
  const parsed = Number.parseInt(value, 10);
7007
7252
  if (!Number.isInteger(parsed) || parsed <= 0) {
@@ -7022,6 +7267,12 @@ class FormRequest {
7022
7267
  return await parseJsonBody(request, (payload) => this.parse(payload));
7023
7268
  }
7024
7269
  }
7270
+
7271
+ class QueryFormRequest {
7272
+ validate(request) {
7273
+ return this.parseQuery(request);
7274
+ }
7275
+ }
7025
7276
  // ../../src/core/http/authMiddleware.ts
7026
7277
  function createAuthMiddleware(auth) {
7027
7278
  return async (request, next) => {
@@ -7584,6 +7835,10 @@ function withErrorHandling(handler) {
7584
7835
  }
7585
7836
  };
7586
7837
  }
7838
+ // ../../src/core/http/route.ts
7839
+ function getRouteParams(request) {
7840
+ return request.params;
7841
+ }
7587
7842
  // ../../src/core/http/routeMiddleware.ts
7588
7843
  function withMiddleware(...middleware) {
7589
7844
  const wrap = composeMiddleware(...middleware);
@@ -7757,6 +8012,9 @@ function createLoginThrottleMiddleware(options) {
7757
8012
  }
7758
8013
  return createMemoryLoginThrottleMiddleware(options);
7759
8014
  }
8015
+ function resetMemoryLoginThrottleForTests() {
8016
+ memoryLoginBuckets.clear();
8017
+ }
7760
8018
  // ../../src/core/http/memoryThrottleMiddleware.ts
7761
8019
  var throttleBucketRegistries = new Set;
7762
8020
  function createMemoryThrottleMiddleware(options) {
@@ -7944,7 +8202,7 @@ function intendedUrlTtlSeconds() {
7944
8202
  return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_INTENDED_URL_TTL_SECONDS;
7945
8203
  }
7946
8204
  function cookieSecureFlag() {
7947
- return process.env.APP_ENV === "production" ? "; Secure" : "";
8205
+ return isProductionEnv() ? "; Secure" : "";
7948
8206
  }
7949
8207
  function pathnameOf(path) {
7950
8208
  const pathname = path.split("?")[0] ?? path;
@@ -8065,7 +8323,7 @@ function createSecurityHeadersMiddleware(options = {}) {
8065
8323
  // ../../src/core/http/signedUrl.ts
8066
8324
  import { createHmac as createHmac3 } from "crypto";
8067
8325
  function resolveSignedUrlSecret() {
8068
- return process.env.SIGNED_URL_SECRET?.trim() || process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || appDevSecret("signed-url-secret");
8326
+ return requireConfiguredSecret(["SIGNED_URL_SECRET", "SESSION_SECRET", "OAUTH_STATE_SECRET"], "signed-url-secret");
8069
8327
  }
8070
8328
  function resolveSignedUrlOrigin() {
8071
8329
  return (process.env.APP_URL ?? "http://localhost:3000").replace(/\/$/, "");
@@ -8264,6 +8522,11 @@ function installGracefulShutdownSignals(signals = ["SIGINT", "SIGTERM"]) {
8264
8522
  });
8265
8523
  }
8266
8524
  }
8525
+ function resetGracefulShutdownForTests() {
8526
+ shutdownHandlers.clear();
8527
+ shutdownInstalled = false;
8528
+ shuttingDown = false;
8529
+ }
8267
8530
  // ../../src/core/logging/requestLoggingMiddleware.ts
8268
8531
  function createRequestLoggingMiddleware() {
8269
8532
  return async (request, next) => {
@@ -8509,6 +8772,42 @@ class AsyncQueue {
8509
8772
  function createQueue(driver) {
8510
8773
  return driver === "async" ? new AsyncQueue : new SyncQueue;
8511
8774
  }
8775
+ // ../../src/core/queue/jobRegistry.ts
8776
+ class JobRegistry {
8777
+ factories = new Map;
8778
+ instances = new WeakMap;
8779
+ register(name, factory) {
8780
+ this.factories.set(name, factory);
8781
+ }
8782
+ resolveName(job) {
8783
+ return this.instances.get(job);
8784
+ }
8785
+ track(name, job) {
8786
+ this.instances.set(job, name);
8787
+ return job;
8788
+ }
8789
+ create(name) {
8790
+ const factory = this.factories.get(name);
8791
+ if (!factory) {
8792
+ return;
8793
+ }
8794
+ return factory();
8795
+ }
8796
+ names() {
8797
+ return [...this.factories.keys()];
8798
+ }
8799
+ }
8800
+ var JOB_REGISTRY_KEY = Symbol.for("@getstrata/jobRegistry");
8801
+ function readSharedJobRegistry() {
8802
+ const globalRegistry = globalThis[JOB_REGISTRY_KEY];
8803
+ if (globalRegistry) {
8804
+ return globalRegistry;
8805
+ }
8806
+ const registry = new JobRegistry;
8807
+ globalThis[JOB_REGISTRY_KEY] = registry;
8808
+ return registry;
8809
+ }
8810
+ var jobRegistry = readSharedJobRegistry();
8512
8811
  // ../../src/core/queue/failedJobTable.ts
8513
8812
  var failedJobTable = defineTable({
8514
8813
  name: "failed_job",
@@ -8569,43 +8868,6 @@ class FailedJobService {
8569
8868
  }
8570
8869
  var failedJobService_default = FailedJobService;
8571
8870
 
8572
- // ../../src/core/queue/jobRegistry.ts
8573
- class JobRegistry {
8574
- factories = new Map;
8575
- instances = new WeakMap;
8576
- register(name, factory) {
8577
- this.factories.set(name, factory);
8578
- }
8579
- resolveName(job) {
8580
- return this.instances.get(job);
8581
- }
8582
- track(name, job) {
8583
- this.instances.set(job, name);
8584
- return job;
8585
- }
8586
- create(name) {
8587
- const factory = this.factories.get(name);
8588
- if (!factory) {
8589
- return;
8590
- }
8591
- return factory();
8592
- }
8593
- names() {
8594
- return [...this.factories.keys()];
8595
- }
8596
- }
8597
- var JOB_REGISTRY_KEY = Symbol.for("@getstrata/jobRegistry");
8598
- function readSharedJobRegistry() {
8599
- const globalRegistry = globalThis[JOB_REGISTRY_KEY];
8600
- if (globalRegistry) {
8601
- return globalRegistry;
8602
- }
8603
- const registry = new JobRegistry;
8604
- globalThis[JOB_REGISTRY_KEY] = registry;
8605
- return registry;
8606
- }
8607
- var jobRegistry = readSharedJobRegistry();
8608
-
8609
8871
  // ../../src/core/queue/queueConfig.ts
8610
8872
  function resolveQueueConfig() {
8611
8873
  const driver = process.env.QUEUE_DRIVER;
@@ -8952,20 +9214,131 @@ function isPublicReadsEnabled() {
8952
9214
  function guestCanViewResource() {
8953
9215
  return isPublicReadsEnabled();
8954
9216
  }
9217
+ // ../../src/core/security/safeUrl.ts
9218
+ import { lookup as dnsLookupImpl } from "dns/promises";
9219
+ var dnsLookup = dnsLookupImpl;
9220
+ var BLOCKED_HOSTNAMES = new Set([
9221
+ "localhost",
9222
+ "127.0.0.1",
9223
+ "0.0.0.0",
9224
+ "::1",
9225
+ "metadata.google.internal"
9226
+ ]);
9227
+ function isPrivateIpv4(hostname) {
9228
+ const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
9229
+ if (!match) {
9230
+ return false;
9231
+ }
9232
+ const octets = match.slice(1, 5).map((part) => Number.parseInt(part, 10));
9233
+ if (octets.some((octet) => octet < 0 || octet > 255)) {
9234
+ return true;
9235
+ }
9236
+ const [a = 0, b = 0] = octets;
9237
+ if (a === 10) {
9238
+ return true;
9239
+ }
9240
+ if (a === 127) {
9241
+ return true;
9242
+ }
9243
+ if (a === 0) {
9244
+ return true;
9245
+ }
9246
+ if (a === 169 && b === 254) {
9247
+ return true;
9248
+ }
9249
+ if (a === 172 && b >= 16 && b <= 31) {
9250
+ return true;
9251
+ }
9252
+ if (a === 192 && b === 168) {
9253
+ return true;
9254
+ }
9255
+ return false;
9256
+ }
9257
+ function isBlockedHostname(hostname) {
9258
+ const normalized = hostname.trim().toLowerCase();
9259
+ if (normalized.length === 0) {
9260
+ return true;
9261
+ }
9262
+ if (BLOCKED_HOSTNAMES.has(normalized)) {
9263
+ return true;
9264
+ }
9265
+ if (normalized.endsWith(".local") || normalized.endsWith(".internal")) {
9266
+ return true;
9267
+ }
9268
+ if (normalized.includes(":")) {
9269
+ return true;
9270
+ }
9271
+ return isPrivateIpv4(normalized);
9272
+ }
9273
+ function assertSafeOutboundUrl(rawUrl, options = {}) {
9274
+ let parsed;
9275
+ try {
9276
+ parsed = new URL(rawUrl);
9277
+ } catch {
9278
+ throw new BadRequestError("Webhook URL is invalid.");
9279
+ }
9280
+ if (parsed.protocol !== "https:" && !(options.allowHttp && parsed.protocol === "http:")) {
9281
+ throw new BadRequestError("Webhook URL must use HTTPS.");
9282
+ }
9283
+ if (parsed.username || parsed.password) {
9284
+ throw new BadRequestError("Webhook URL must not include credentials.");
9285
+ }
9286
+ if (isBlockedHostname(parsed.hostname)) {
9287
+ throw new BadRequestError("Webhook URL targets a blocked host.");
9288
+ }
9289
+ return parsed;
9290
+ }
9291
+ function isBlockedIpAddress(address) {
9292
+ return isBlockedHostname(address.trim().toLowerCase());
9293
+ }
9294
+ async function assertSafeOutboundUrlResolved(rawUrl, options = {}) {
9295
+ const parsed = assertSafeOutboundUrl(rawUrl, options);
9296
+ if (options.resolveDns === false) {
9297
+ return parsed;
9298
+ }
9299
+ const hostname = parsed.hostname.trim().toLowerCase();
9300
+ const results = await dnsLookup(hostname, { all: true, verbatim: true });
9301
+ if (results.some((result) => isBlockedIpAddress(result.address))) {
9302
+ throw new BadRequestError("Webhook URL targets a blocked host.");
9303
+ }
9304
+ return parsed;
9305
+ }
9306
+ function setDnsLookupForTests(lookupFn) {
9307
+ dnsLookup = lookupFn;
9308
+ }
9309
+ function resetDnsLookupForTests() {
9310
+ dnsLookup = dnsLookupImpl;
9311
+ }
8955
9312
  // ../../src/core/tenant/tenantMiddleware.ts
8956
9313
  import { createHash } from "crypto";
8957
9314
 
8958
9315
  // ../../src/core/tenant/databaseTenantContext.ts
9316
+ async function applyBypassToTransaction(transaction, bypass) {
9317
+ await transaction.unsafe(`SELECT set_config('app.bypass_rls', $1, true)`, [
9318
+ bypass ? "true" : "false"
9319
+ ]);
9320
+ }
8959
9321
  async function runWithMigrationBypass(callback) {
8960
9322
  if (!isRlsTenancy()) {
8961
9323
  return await callback();
8962
9324
  }
8963
- await repositoryConnection`SELECT set_config('app.bypass_rls', 'true', false)`;
8964
- try {
8965
- return await callback();
8966
- } finally {
8967
- await repositoryConnection`SELECT set_config('app.bypass_rls', 'false', false)`;
9325
+ if (hasActiveDatabaseConnection()) {
9326
+ const activeConnection = getActiveDatabaseConnection(getDefaultDatabasePool());
9327
+ await applyBypassToTransaction(activeConnection, true);
9328
+ try {
9329
+ return await callback();
9330
+ } finally {
9331
+ await applyBypassToTransaction(activeConnection, false);
9332
+ }
8968
9333
  }
9334
+ const pool = getDefaultDatabasePool();
9335
+ if (typeof pool.begin !== "function") {
9336
+ throw new Error("RLS migration bypass requires a pool that supports begin(). Session-scoped set_config is not used on pooled connections.");
9337
+ }
9338
+ return await pool.begin(async (transaction) => {
9339
+ await applyBypassToTransaction(transaction, true);
9340
+ return await runWithDatabaseConnection(transaction, callback);
9341
+ });
8969
9342
  }
8970
9343
 
8971
9344
  // ../../src/core/tenant/tenantMiddleware.ts
@@ -9229,7 +9602,6 @@ function validateObject(payload, schema) {
9229
9602
  }
9230
9603
  // ../../src/core/view/etaViewEngine.ts
9231
9604
  import { join as join4, relative } from "path";
9232
- import { Eta } from "eta";
9233
9605
 
9234
9606
  // ../../src/core/view/assertEtaHtmlSource.ts
9235
9607
  var HTML_TAGS = new Set([
@@ -9409,33 +9781,57 @@ var DEFAULT_VIEWS_DIRECTORY = join4(process.cwd(), "resources/views");
9409
9781
  var DEFAULT_LAYOUT = "layouts/app.eta";
9410
9782
 
9411
9783
  class EtaViewEngine {
9412
- eta;
9784
+ eta = null;
9785
+ etaPending = null;
9786
+ viewsDirectory;
9413
9787
  resolveLayoutData;
9414
9788
  constructor(viewsDirectory = DEFAULT_VIEWS_DIRECTORY, resolveLayoutData) {
9415
- this.eta = new Eta({
9416
- views: viewsDirectory,
9417
- autoTrim: false
9418
- });
9789
+ this.viewsDirectory = viewsDirectory;
9419
9790
  this.resolveLayoutData = resolveLayoutData;
9420
- const readFile = this.eta.readFile?.bind(this.eta);
9421
- this.eta.readFile = (path) => {
9791
+ }
9792
+ async getEta() {
9793
+ if (this.eta) {
9794
+ return this.eta;
9795
+ }
9796
+ if (!this.etaPending) {
9797
+ this.etaPending = this.createEta();
9798
+ }
9799
+ return this.etaPending;
9800
+ }
9801
+ async createEta() {
9802
+ let eta;
9803
+ try {
9804
+ const mod = await import("eta");
9805
+ eta = new mod.Eta({
9806
+ views: this.viewsDirectory,
9807
+ autoTrim: false
9808
+ });
9809
+ } catch (error) {
9810
+ this.etaPending = null;
9811
+ throw missingOptionalPeer("eta", "to render HTML views", error);
9812
+ }
9813
+ const readFile = eta.readFile?.bind(eta);
9814
+ eta.readFile = (path) => {
9422
9815
  const source = readFile ? readFile(path) : "";
9423
- assertEtaHtmlSource(relative(viewsDirectory, path) || path, source);
9816
+ assertEtaHtmlSource(relative(this.viewsDirectory, path) || path, source);
9424
9817
  return source;
9425
9818
  };
9819
+ this.eta = eta;
9820
+ return eta;
9426
9821
  }
9427
9822
  async render(name, data = {}, options = {}) {
9428
9823
  const template = name.endsWith(".eta") ? name : `${name}.eta`;
9429
9824
  const request = options.request ?? currentRequestMeta().request;
9430
9825
  const layoutData = this.resolveLayoutData ? await this.resolveLayoutData(request) : {};
9431
9826
  const mergedData = { ...layoutData, ...data };
9432
- const body = await this.eta.renderAsync(template, mergedData);
9827
+ const eta = await this.getEta();
9828
+ const body = await eta.renderAsync(template, mergedData);
9433
9829
  const layout = options.layout ?? DEFAULT_LAYOUT;
9434
9830
  if (layout === false) {
9435
9831
  return body;
9436
9832
  }
9437
9833
  const layoutTemplate = layout.endsWith(".eta") ? layout : `${layout}.eta`;
9438
- return await this.eta.renderAsync(layoutTemplate, {
9834
+ return await eta.renderAsync(layoutTemplate, {
9439
9835
  ...mergedData,
9440
9836
  body
9441
9837
  });
@@ -9510,6 +9906,7 @@ export {
9510
9906
  CompositeGuard,
9511
9907
  ConfigStore,
9512
9908
  ConflictError,
9909
+ DEFAULT_PER_PAGE,
9513
9910
  DEFAULT_TENANT,
9514
9911
  DEFAULT_VIEWS_DIRECTORY,
9515
9912
  DatabaseTokenGuard,
@@ -9522,15 +9919,19 @@ export {
9522
9919
  ForeignIdColumnDefinition,
9523
9920
  FormRequest,
9524
9921
  GuestGuard,
9922
+ HTMX_2_0_4_INDICATOR_STYLE_HASH,
9525
9923
  HasManyRelationQuery,
9924
+ HasManyThroughRelationQuery,
9526
9925
  HasOneRelationQuery,
9527
9926
  HttpError,
9528
9927
  InternalServerError,
9529
9928
  Job,
9929
+ JobRegistry,
9530
9930
  JsonResource,
9531
9931
  JwtGuard,
9532
9932
  LocalStorageDriver,
9533
9933
  LogMailDriver,
9934
+ MAX_PER_PAGE,
9534
9935
  Mailer,
9535
9936
  membershipService_default as MembershipService,
9536
9937
  Model,
@@ -9548,6 +9949,7 @@ export {
9548
9949
  PostgresGrammar,
9549
9950
  PreconditionFailedError,
9550
9951
  PrometheusRegistry,
9952
+ QueryFormRequest,
9551
9953
  QueueWorker,
9552
9954
  RedisQueue,
9553
9955
  RepositoryQuery,
@@ -9566,7 +9968,15 @@ export {
9566
9968
  WebFormRequest,
9567
9969
  WhereBuilder,
9568
9970
  absoluteTemporarySignedUrl,
9971
+ apiPrefix,
9972
+ appCookieName,
9973
+ appDevSecret,
9974
+ appDisplayName,
9975
+ appEnv,
9976
+ appKeyPrefix,
9569
9977
  appSchedule,
9978
+ appUrl,
9979
+ appUserAgent,
9570
9980
  appendOrganizationScope,
9571
9981
  appendProjectScope,
9572
9982
  applyCasts,
@@ -9575,6 +9985,8 @@ export {
9575
9985
  assertIfMatch,
9576
9986
  assertOrganizationReadable,
9577
9987
  assertResourceInCurrentTenant,
9988
+ assertSafeOutboundUrl,
9989
+ assertSafeOutboundUrlResolved,
9578
9990
  assertValidSignature,
9579
9991
  auditChecksum,
9580
9992
  auth,
@@ -9584,13 +9996,29 @@ export {
9584
9996
  bindBunSql,
9585
9997
  bindDatabaseConnection,
9586
9998
  bindRouteModel,
9999
+ buildAdvancedWhereClause,
10000
+ buildCountQuery,
10001
+ buildDeleteByIdQuery,
10002
+ buildGroupedCountQuery,
10003
+ buildInsertQuery,
10004
+ buildJoinClause,
9587
10005
  buildMarkdownMailMessage,
10006
+ buildOrderByClause,
10007
+ buildPaginationMeta,
10008
+ buildProjectionQuery,
10009
+ buildQueryWhereClause,
9588
10010
  buildRequestCacheKey,
10011
+ buildRestoreByIdQuery,
10012
+ buildSelectQuery,
9589
10013
  buildSmtpPayload,
10014
+ buildSoftDeleteByIdQuery,
10015
+ buildUpdateQuery,
10016
+ buildWhereClause,
9590
10017
  cache,
9591
10018
  collectQueueMetrics,
9592
10019
  compileBlueprint,
9593
10020
  composeMiddleware,
10021
+ computeEtagFromJson,
9594
10022
  conditionalJsonResponse,
9595
10023
  config,
9596
10024
  configureContentSecurityPolicy,
@@ -9612,6 +10040,7 @@ export {
9612
10040
  createFlashMiddleware,
9613
10041
  createLoginThrottleMiddleware,
9614
10042
  createMembershipMiddleware,
10043
+ createMemoryLoginThrottleMiddleware,
9615
10044
  createMemoryThrottleMiddleware,
9616
10045
  createMetricsMiddleware,
9617
10046
  createMysqlConnection,
@@ -9652,10 +10081,14 @@ export {
9652
10081
  dialectFor,
9653
10082
  emailRule,
9654
10083
  emptyPaginateResult,
10084
+ envFlagEnabled,
10085
+ errorResponse,
9655
10086
  errorTemplateName,
9656
10087
  etagFromResource,
10088
+ etagValuesMatch,
9657
10089
  eventBus,
9658
10090
  events,
10091
+ expectObject,
9659
10092
  filterMassAssignable,
9660
10093
  foreignKeyFromTable,
9661
10094
  formatAdminValue,
@@ -9667,10 +10100,13 @@ export {
9667
10100
  getDefaultDatabaseQuery,
9668
10101
  getMigrationStatus,
9669
10102
  getNamedConnection,
10103
+ getQueryParams,
10104
+ getRouteParams,
9670
10105
  grammarForDriver,
9671
10106
  guestCanViewResource,
9672
10107
  hasActiveDatabaseConnection,
9673
10108
  hasMany,
10109
+ hasManyThrough,
9674
10110
  hasMinimumOrgRole2 as hasMinimumOrgRole,
9675
10111
  hasNamedConnection,
9676
10112
  hasOne,
@@ -9679,20 +10115,26 @@ export {
9679
10115
  htmlErrorResponse,
9680
10116
  htmlResponse,
9681
10117
  hydrateValue,
10118
+ ifMatchSatisfied,
10119
+ ifNoneMatchSatisfied,
9682
10120
  indexBelongsToManyRelation,
9683
10121
  indexBelongsToRelation,
9684
10122
  indexHasManyRelation,
10123
+ indexHasManyThroughRelation,
9685
10124
  indexHasOneRelation,
9686
10125
  indexMorphManyRelation,
9687
10126
  indexMorphOneRelation,
9688
10127
  indexMorphToRelation,
9689
10128
  inferReferencedTable,
9690
10129
  installGracefulShutdownSignals,
10130
+ isBlockedHostname,
10131
+ isBlockedIpAddress,
9691
10132
  isEtagEnabled,
9692
10133
  isGlobalAdmin,
9693
10134
  isHtmxRequest,
9694
10135
  isHttpErrorLike,
9695
10136
  isInsideTenantDatabaseScope,
10137
+ isProductionEnv,
9696
10138
  isPublicReadsEnabled,
9697
10139
  isRlsTenancy,
9698
10140
  isTenancyEnabled,
@@ -9703,32 +10145,54 @@ export {
9703
10145
  loadSeedersFromDirectory,
9704
10146
  log,
9705
10147
  logSecurityEvent,
10148
+ logServerError,
9706
10149
  loginRedirectLocation,
9707
10150
  mail,
9708
10151
  mailer,
10152
+ mapDatabaseError,
9709
10153
  markdownToHtml,
9710
10154
  maxLength,
10155
+ membershipContext,
10156
+ membershipRepository,
9711
10157
  migrateDatabase,
9712
10158
  minLength,
9713
10159
  modelEventName,
9714
10160
  morphMany,
9715
10161
  morphOne,
9716
10162
  morphTo,
10163
+ namespacedRedisKey,
9717
10164
  noContentResponse,
10165
+ normalizeFieldErrors,
9718
10166
  normalizeMetricPath,
9719
10167
  notFoundHtmlResponse,
10168
+ notModifiedResponse,
10169
+ otelServiceName,
9720
10170
  paginatedResponse,
10171
+ parseJsonBody,
9721
10172
  parseMultipartUpload,
10173
+ parseOptionalBooleanQueryParam,
10174
+ parseOptionalEnumQueryParam,
10175
+ parseOptionalPositiveIntQueryParam,
9722
10176
  parsePaginationQuery,
9723
10177
  parsePositiveIntParam,
10178
+ parseQualifiedColumn,
9724
10179
  pivotTableName,
9725
10180
  policyGate,
9726
10181
  prometheusRegistry,
10182
+ qualifyColumn,
9727
10183
  queue,
10184
+ quoteIdentifier,
9728
10185
  rateLimitMultiplierForPlan,
9729
10186
  readBunRequestCookie,
9730
10187
  readClientIp,
10188
+ readOptionalEnum,
10189
+ readOptionalPositiveInt,
10190
+ readOptionalString,
9731
10191
  readRequestCookie,
10192
+ readRequiredEnum,
10193
+ readRequiredPositiveInt,
10194
+ readRequiredString,
10195
+ readSharedEventBus,
9732
10196
  readSubmittedCsrfToken,
9733
10197
  readSubmittedCsrfTokenFromBody,
9734
10198
  readTenancyDriver,
@@ -9740,11 +10204,20 @@ export {
9740
10204
  registerShutdownHandler,
9741
10205
  renderKernelErrorChrome,
9742
10206
  renderMarkdownMail,
10207
+ renderWebErrorHtml,
9743
10208
  repositoryConnection,
9744
10209
  requestIdMiddleware,
10210
+ requestMetaContext,
10211
+ requireConfiguredSecret,
9745
10212
  required,
9746
10213
  resetBoundDatabaseConnection,
10214
+ resetContentSecurityPolicyForTests,
10215
+ resetDefaultDatabasePoolForTests,
9747
10216
  resetDefaultStorage,
10217
+ resetDnsLookupForTests,
10218
+ resetGracefulShutdownForTests,
10219
+ resetMembershipLookupForTests,
10220
+ resetMemoryLoginThrottleForTests,
9748
10221
  resetMemoryThrottleForTests,
9749
10222
  resetNamedConnections,
9750
10223
  resetSqlDialect,
@@ -9761,11 +10234,15 @@ export {
9761
10234
  resolveCsrfTokenForRequest,
9762
10235
  resolveDatabaseDriver,
9763
10236
  resolveHtmlContentSecurityPolicy,
10237
+ resolveLoginEmail,
10238
+ resolveLoginIdentity,
9764
10239
  resolveMembershipLookup,
9765
10240
  resolveMembershipService,
9766
10241
  resolveOrganizationScope,
10242
+ resolveQualifiedColumn,
9767
10243
  resolveRepositoryConnection,
9768
10244
  resolveService,
10245
+ resolveSoftDeleteColumn,
9769
10246
  resolveUserId,
9770
10247
  resolveUserTenantId,
9771
10248
  resolveWebLayoutData,
@@ -9788,16 +10265,21 @@ export {
9788
10265
  safeInternalRedirectPath,
9789
10266
  sanitizeInternalPath,
9790
10267
  sanitizeMailHtml,
10268
+ sanitizeUploadFileName,
9791
10269
  scopedOrganizationIds,
10270
+ sdkClientClassName,
9792
10271
  securedBindRouteModel,
9793
10272
  securedBindRouteModelByKey,
9794
10273
  sendMarkdownMail,
9795
10274
  serializeDate,
9796
10275
  serverHtmxContentSecurityPolicy,
9797
10276
  setActiveApplicationContext,
10277
+ setDnsLookupForTests,
10278
+ siemEventType,
9798
10279
  signJwt,
9799
10280
  signedUrl,
9800
10281
  singularize,
10282
+ smtpEhloHost,
9801
10283
  spaContentSecurityPolicy,
9802
10284
  sqlTimestamp,
9803
10285
  storageFacade as storage,
@@ -9805,17 +10287,22 @@ export {
9805
10287
  stringRule,
9806
10288
  stripMarkdown,
9807
10289
  temporarySignedUrl,
10290
+ tenantContext,
9808
10291
  textResponse,
9809
10292
  toHttpError,
9810
10293
  toPaginatedResourceCollection,
9811
10294
  toResourceCollection,
10295
+ traceContextStorage,
9812
10296
  trustForwardedFor,
9813
10297
  unregisterNamedConnection,
9814
10298
  useSqlDialect,
9815
10299
  validateObject,
9816
10300
  verifyCsrfToken,
9817
10301
  verifyJwt,
10302
+ webErrorResponse,
10303
+ webhookSignatureHeader,
9818
10304
  whenLoaded,
10305
+ withDatabaseErrorHandling,
9819
10306
  withErrorHandling,
9820
10307
  withMiddleware,
9821
10308
  withMigrationLock,