@getstrata/core 0.5.101 → 0.7.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 (61) hide show
  1. package/CHANGELOG.md +67 -32
  2. package/README.md +16 -35
  3. package/dist/core/auth/abilityCatalog.d.ts +2 -2
  4. package/dist/core/auth/basicAuthGuard.d.ts +9 -0
  5. package/dist/core/auth/guard.d.ts +6 -0
  6. package/dist/core/auth/jwt.d.ts +19 -0
  7. package/dist/core/auth/jwtGuard.d.ts +14 -0
  8. package/dist/core/auth/tokenAbilityChecker.d.ts +5 -0
  9. package/dist/core/cache/tags.d.ts +6 -0
  10. package/dist/core/contracts/authUserDirectory.d.ts +4 -0
  11. package/dist/core/database/dialect.d.ts +18 -0
  12. package/dist/core/database/factory.d.ts +1 -0
  13. package/dist/core/database/index.d.ts +8 -0
  14. package/dist/core/database/mysqlConnection.d.ts +12 -0
  15. package/dist/core/database/namedConnections.d.ts +15 -0
  16. package/dist/core/database/repositoryQuery.d.ts +1 -0
  17. package/dist/core/database/sqliteConnection.d.ts +7 -0
  18. package/dist/core/http/loginThrottleMiddleware.d.ts +5 -2
  19. package/dist/core/http/resources.d.ts +2 -2
  20. package/dist/core/http/response.d.ts +2 -1
  21. package/dist/core/http/statelessAuth.d.ts +8 -0
  22. package/dist/core/http/throttleResponse.d.ts +2 -0
  23. package/dist/core/runtime/frontendMode.d.ts +10 -2
  24. package/dist/entries/auth/basicAuthGuard.js +137 -0
  25. package/dist/entries/auth/jwt.js +135 -0
  26. package/dist/entries/auth/jwtGuard.js +203 -0
  27. package/dist/entries/auth/sessionGuard.js +3 -21
  28. package/dist/entries/auth/tokenAbilityChecker.js +24 -0
  29. package/dist/entries/cache/tags.js +7 -1
  30. package/dist/entries/database/connectionContext.js +1 -0
  31. package/dist/entries/database/dialect.js +1 -0
  32. package/dist/entries/database/factory.js +5 -4
  33. package/dist/entries/database/model.js +49 -32
  34. package/dist/entries/database/mysqlConnection.js +35 -0
  35. package/dist/entries/database/namedConnections.js +1 -0
  36. package/dist/entries/database/query.js +28 -15
  37. package/dist/entries/database/relationships.js +14 -6
  38. package/dist/entries/database/repositoryQuery.js +96 -81
  39. package/dist/entries/database/schema.js +28 -15
  40. package/dist/entries/database/sqliteConnection.js +34 -0
  41. package/dist/entries/facades.js +1 -1
  42. package/dist/entries/http/contentNegotiation.js +5 -2
  43. package/dist/entries/http/csrfMiddleware.js +45 -0
  44. package/dist/entries/http/loginThrottleMiddleware.js +246 -7
  45. package/dist/entries/http/memoryThrottleMiddleware.js +208 -6
  46. package/dist/entries/http/requireAbilityMiddleware.js +35 -11
  47. package/dist/entries/http/requirePasswordConfirmMiddleware.js +5 -2
  48. package/dist/entries/http/requireVerifiedMiddleware.js +5 -2
  49. package/dist/entries/http/requireWebAuthMiddleware.js +12 -3
  50. package/dist/entries/http/resources.js +4 -1
  51. package/dist/entries/http/response.js +46 -12
  52. package/dist/entries/http/statelessAuth.js +48 -0
  53. package/dist/entries/http/throttleMiddleware.js +208 -6
  54. package/dist/entries/http/webErrorResponse.js +35 -11
  55. package/dist/entries/http/webFormRequest.js +5 -2
  56. package/dist/entries/mail/mailer.js +1 -1
  57. package/dist/entries/openapi/generator.js +26 -3
  58. package/dist/entries/runtime/frontendMode.js +39 -10
  59. package/dist/framework/public-api.d.ts +11 -1
  60. package/dist/index.js +873 -270
  61. package/package.json +56 -5
package/dist/index.js CHANGED
@@ -211,37 +211,101 @@ function resolveAuthUserDirectory(container) {
211
211
  return null;
212
212
  }
213
213
 
214
+ // ../../src/core/http/statelessAuth.ts
215
+ function authorizationScheme(request) {
216
+ const header = request.headers.get("authorization")?.trim() ?? "";
217
+ const scheme = header.split(/\s+/, 1)[0];
218
+ return scheme ? scheme.toLowerCase() : "";
219
+ }
220
+ function requestUsesHeaderCredentials(request) {
221
+ const scheme = authorizationScheme(request);
222
+ return scheme === "bearer" || scheme === "basic";
223
+ }
224
+ function readBearerToken(request) {
225
+ const header = request.headers.get("authorization")?.trim() ?? "";
226
+ if (!header.toLowerCase().startsWith("bearer ")) {
227
+ return null;
228
+ }
229
+ const token = header.slice("Bearer ".length).trim();
230
+ return token.length > 0 ? token : null;
231
+ }
232
+ function readBasicCredentials(request) {
233
+ const header = request.headers.get("authorization")?.trim() ?? "";
234
+ if (!header.toLowerCase().startsWith("basic ")) {
235
+ return null;
236
+ }
237
+ const encoded = header.slice("Basic ".length).trim();
238
+ if (!encoded) {
239
+ return null;
240
+ }
241
+ try {
242
+ const decoded = Buffer.from(encoded, "base64").toString("utf8");
243
+ const separator = decoded.indexOf(":");
244
+ if (separator < 0) {
245
+ return null;
246
+ }
247
+ return {
248
+ username: decoded.slice(0, separator),
249
+ password: decoded.slice(separator + 1)
250
+ };
251
+ } catch {
252
+ return null;
253
+ }
254
+ }
255
+
256
+ // ../../src/core/auth/password.ts
257
+ async function verifyPassword(password, passwordHash) {
258
+ return await Bun.password.verify(password, passwordHash);
259
+ }
260
+
261
+ // ../../src/core/auth/basicAuthGuard.ts
262
+ class BasicAuthGuard {
263
+ container;
264
+ constructor(container) {
265
+ this.container = container;
266
+ }
267
+ async resolve(request) {
268
+ const credentials = readBasicCredentials(request);
269
+ if (!credentials) {
270
+ return null;
271
+ }
272
+ const directory = resolveAuthUserDirectory(this.container);
273
+ if (!directory) {
274
+ return null;
275
+ }
276
+ if (typeof directory.verifyCredentials === "function") {
277
+ return await directory.verifyCredentials(credentials.username, credentials.password);
278
+ }
279
+ if (typeof directory.findByEmail !== "function") {
280
+ return null;
281
+ }
282
+ const record = await directory.findByEmail(credentials.username);
283
+ if (!record?.password) {
284
+ return null;
285
+ }
286
+ if (!await verifyPassword(credentials.password, record.password)) {
287
+ return null;
288
+ }
289
+ return {
290
+ id: record.id,
291
+ role: record.role,
292
+ emailVerifiedAt: record.email_verified_at ?? null
293
+ };
294
+ }
295
+ }
214
296
  // ../../src/core/auth/abilityCatalog.ts
215
297
  var MEMBER_ABILITIES = [
216
- "organizations:read",
217
- "organizations:create",
218
- "projects:read",
219
- "projects:create",
220
- "tasks:read",
221
- "tasks:create",
222
- "comments:read",
223
- "comments:create",
224
- "attachments:read",
225
- "attachments:create",
298
+ "profile:read",
226
299
  "auth:tokens:read",
227
300
  "auth:tokens:write",
228
301
  "auth:tokens:delete"
229
302
  ];
230
303
  var ADMIN_ABILITIES = [
231
304
  ...MEMBER_ABILITIES,
232
- "organizations:create",
233
- "organizations:update",
234
- "organizations:delete",
235
- "projects:update",
236
- "projects:delete",
237
- "tasks:update",
238
- "tasks:delete",
239
- "comments:update",
240
- "comments:delete",
241
- "attachments:delete",
242
305
  "webhooks:read",
243
306
  "webhooks:write",
244
- "audit:read"
307
+ "audit:read",
308
+ "audit:export"
245
309
  ];
246
310
  var PLATFORM_ADMIN_ABILITIES = ["*"];
247
311
  function resolveAbilitiesForRole(role) {
@@ -292,12 +356,8 @@ class ApiTokenGuard {
292
356
  this.options = options;
293
357
  }
294
358
  resolve(request) {
295
- const authorization = request.headers.get("authorization");
296
- if (!authorization?.startsWith("Bearer ")) {
297
- return null;
298
- }
299
- const token = authorization.slice("Bearer ".length).trim();
300
- if (token !== this.options.token) {
359
+ const token = readBearerToken(request);
360
+ if (!token || token !== this.options.token) {
301
361
  return null;
302
362
  }
303
363
  return this.options.user;
@@ -310,12 +370,8 @@ class DatabaseTokenGuard {
310
370
  this.container = container;
311
371
  }
312
372
  async resolve(request) {
313
- const authorization = request.headers.get("authorization");
314
- if (!authorization?.startsWith("Bearer ")) {
315
- return null;
316
- }
317
- const token = authorization.slice("Bearer ".length).trim();
318
- if (!token) {
373
+ const token = readBearerToken(request);
374
+ if (!token || token.split(".").length === 3) {
319
375
  return null;
320
376
  }
321
377
  const tokenService = resolveAuthUserDirectory(this.container);
@@ -341,15 +397,38 @@ class CompositeGuard {
341
397
  return null;
342
398
  }
343
399
  }
400
+ var BEARER_GUARD_NAMES = ["api", "access_token", "token", "jwt"];
401
+ var BASIC_GUARD_NAMES = ["basic"];
402
+ var SESSION_GUARD_NAMES = ["web", "session", "default"];
344
403
 
345
404
  class AuthManager {
346
405
  guard;
406
+ namedGuards = new Map;
347
407
  constructor(guard) {
348
408
  this.guard = guard;
409
+ this.namedGuards.set("default", guard);
410
+ }
411
+ registerGuard(name, next) {
412
+ const trimmed = name.trim();
413
+ if (!trimmed) {
414
+ throw new Error("Auth guard name must not be empty.");
415
+ }
416
+ this.namedGuards.set(trimmed, next);
417
+ return this;
418
+ }
419
+ use(name = "default") {
420
+ const found = this.namedGuards.get(name);
421
+ if (!found) {
422
+ throw new Error(`Unknown auth guard "${name}".`);
423
+ }
424
+ return found;
425
+ }
426
+ guardNames() {
427
+ return [...this.namedGuards.keys()];
349
428
  }
350
429
  async resolve(request) {
351
430
  if (request) {
352
- return await Promise.resolve(this.guard.resolve(request));
431
+ return await this.authenticateRequest(request);
353
432
  }
354
433
  return currentAuthUser();
355
434
  }
@@ -366,6 +445,167 @@ class AuthManager {
366
445
  }
367
446
  return user;
368
447
  }
448
+ async authenticateRequest(request) {
449
+ const scheme = authorizationScheme(request);
450
+ if (scheme === "bearer") {
451
+ const fromBearer = await this.tryNamedGuards(request, BEARER_GUARD_NAMES);
452
+ if (fromBearer) {
453
+ return fromBearer;
454
+ }
455
+ } else if (scheme === "basic") {
456
+ const fromBasic = await this.tryNamedGuards(request, BASIC_GUARD_NAMES);
457
+ if (fromBasic) {
458
+ return fromBasic;
459
+ }
460
+ } else {
461
+ const fromSession = await this.tryNamedGuards(request, SESSION_GUARD_NAMES);
462
+ if (fromSession) {
463
+ return fromSession;
464
+ }
465
+ }
466
+ return await Promise.resolve(this.guard.resolve(request));
467
+ }
468
+ async tryNamedGuards(request, names) {
469
+ const seen = new Set;
470
+ for (const name of names) {
471
+ const named = this.namedGuards.get(name);
472
+ if (!named || seen.has(named)) {
473
+ continue;
474
+ }
475
+ seen.add(named);
476
+ const user = await Promise.resolve(named.resolve(request));
477
+ if (user) {
478
+ return user;
479
+ }
480
+ }
481
+ return null;
482
+ }
483
+ }
484
+ // ../../src/core/auth/jwt.ts
485
+ import { createHmac, timingSafeEqual } from "crypto";
486
+
487
+ // ../../src/core/runtime/appKeyPrefix.ts
488
+ function appKeyPrefix() {
489
+ return process.env.APP_KEY_PREFIX?.trim() || "strata";
490
+ }
491
+ function appCookieName(kind) {
492
+ return `${appKeyPrefix()}_${kind}`;
493
+ }
494
+ function appDevSecret(kind) {
495
+ return `${appKeyPrefix()}-dev-${kind}`;
496
+ }
497
+ function namespacedRedisKey(kind) {
498
+ return `${appKeyPrefix()}:${kind}`;
499
+ }
500
+ function smtpEhloHost() {
501
+ const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
502
+ const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
503
+ return safe || "strata.local";
504
+ }
505
+ function otelServiceName() {
506
+ return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
507
+ }
508
+ function appEnv() {
509
+ return process.env.APP_ENV?.trim() || "local";
510
+ }
511
+
512
+ // ../../src/core/auth/jwt.ts
513
+ function resolveJwtSecret(secret) {
514
+ return secret?.trim() || process.env.JWT_SECRET?.trim() || process.env.SESSION_SECRET?.trim() || appDevSecret("jwt-secret");
515
+ }
516
+ function jwtTtlSeconds(override) {
517
+ if (typeof override === "number" && Number.isInteger(override) && override > 0) {
518
+ return override;
519
+ }
520
+ const parsed = Number.parseInt(process.env.JWT_TTL_SECONDS ?? "", 10);
521
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : 3600;
522
+ }
523
+ function encodeJson(value) {
524
+ return Buffer.from(JSON.stringify(value)).toString("base64url");
525
+ }
526
+ function decodeJson(value) {
527
+ try {
528
+ return JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
529
+ } catch {
530
+ return null;
531
+ }
532
+ }
533
+ function signPart(headerAndPayload, secret) {
534
+ return createHmac("sha256", secret).update(headerAndPayload).digest("base64url");
535
+ }
536
+ function signaturesMatch(left, right) {
537
+ const leftBuffer = Buffer.from(left);
538
+ const rightBuffer = Buffer.from(right);
539
+ if (leftBuffer.length !== rightBuffer.length) {
540
+ return false;
541
+ }
542
+ return timingSafeEqual(leftBuffer, rightBuffer);
543
+ }
544
+ function signJwt(payload, options = {}) {
545
+ const now = Math.floor(Date.now() / 1000);
546
+ const body = {
547
+ ...payload,
548
+ iat: now,
549
+ exp: now + jwtTtlSeconds(options.ttlSeconds)
550
+ };
551
+ const header = encodeJson({ alg: "HS256", typ: "JWT" });
552
+ const data = encodeJson(body);
553
+ const unsigned = `${header}.${data}`;
554
+ const signature = signPart(unsigned, resolveJwtSecret(options.secret));
555
+ return `${unsigned}.${signature}`;
556
+ }
557
+ function verifyJwt(token, secret) {
558
+ const parts = token.split(".");
559
+ if (parts.length !== 3) {
560
+ return null;
561
+ }
562
+ const [header, data, signature] = parts;
563
+ if (!header || !data || !signature) {
564
+ return null;
565
+ }
566
+ const expected = signPart(`${header}.${data}`, resolveJwtSecret(secret));
567
+ if (!signaturesMatch(signature, expected)) {
568
+ return null;
569
+ }
570
+ const parsedHeader = decodeJson(header);
571
+ if (parsedHeader?.alg !== "HS256") {
572
+ return null;
573
+ }
574
+ const payload = decodeJson(data);
575
+ if (!payload || payload.sub === undefined || payload.sub === null) {
576
+ return null;
577
+ }
578
+ if (typeof payload.exp === "number" && payload.exp * 1000 <= Date.now()) {
579
+ return null;
580
+ }
581
+ return payload;
582
+ }
583
+ // ../../src/core/auth/jwtGuard.ts
584
+ function authUserFromJwt(payload) {
585
+ return {
586
+ id: payload.sub,
587
+ ...payload.role ? { role: String(payload.role) } : {},
588
+ ...Array.isArray(payload.abilities) ? { abilities: payload.abilities.map(String) } : {},
589
+ ...payload.emailVerifiedAt !== undefined ? { emailVerifiedAt: payload.emailVerifiedAt } : {}
590
+ };
591
+ }
592
+
593
+ class JwtGuard {
594
+ options;
595
+ constructor(options = {}) {
596
+ this.options = options;
597
+ }
598
+ resolve(request) {
599
+ const token = readBearerToken(request);
600
+ if (token?.split(".").length !== 3) {
601
+ return null;
602
+ }
603
+ const payload = verifyJwt(token, this.options.secret);
604
+ if (!payload) {
605
+ return null;
606
+ }
607
+ return authUserFromJwt(payload);
608
+ }
369
609
  }
370
610
  // ../../src/core/auth/membershipContext.ts
371
611
  var membershipContext = createAsyncContextStore("@getstrata/membershipContext");
@@ -768,17 +1008,25 @@ class PolicyGate {
768
1008
  }
769
1009
  }
770
1010
  // ../../src/core/database/boundConnection.ts
771
- var boundConnectionHolder = {
772
- connection: null
773
- };
1011
+ var BOUND_CONNECTION_KEY = Symbol.for("@getstrata/boundDatabaseConnection");
1012
+ function boundConnectionState() {
1013
+ const globalRecord = globalThis;
1014
+ const existing = globalRecord[BOUND_CONNECTION_KEY];
1015
+ if (existing) {
1016
+ return existing;
1017
+ }
1018
+ const created = { connection: null };
1019
+ globalRecord[BOUND_CONNECTION_KEY] = created;
1020
+ return created;
1021
+ }
774
1022
  function bindDatabaseConnection(connection) {
775
- boundConnectionHolder.connection = connection;
1023
+ boundConnectionState().connection = connection;
776
1024
  }
777
1025
  function getBoundDatabaseConnection() {
778
- return boundConnectionHolder.connection;
1026
+ return boundConnectionState().connection;
779
1027
  }
780
1028
  function resetBoundDatabaseConnection() {
781
- boundConnectionHolder.connection = null;
1029
+ boundConnectionState().connection = null;
782
1030
  }
783
1031
  // ../../src/core/database/connectionContext.ts
784
1032
  var activeConnection = createAsyncContextStore("@getstrata/databaseConnectionContext");
@@ -817,38 +1065,46 @@ function createDatabaseQueryProxy(pool) {
817
1065
  }
818
1066
 
819
1067
  // ../../src/core/database/defaultConnection.ts
820
- var defaultPool = {
821
- connection: null
822
- };
823
- var defaultQuery = {
824
- connection: null
825
- };
1068
+ var DEFAULT_CONNECTION_KEY = Symbol.for("@getstrata/defaultDatabaseConnection");
1069
+ function defaultConnectionState() {
1070
+ const globalRecord = globalThis;
1071
+ const existing = globalRecord[DEFAULT_CONNECTION_KEY];
1072
+ if (existing) {
1073
+ return existing;
1074
+ }
1075
+ const created = { pool: null, query: null };
1076
+ globalRecord[DEFAULT_CONNECTION_KEY] = created;
1077
+ return created;
1078
+ }
826
1079
  function registerDefaultDatabasePool(connection) {
827
- defaultPool.connection = connection;
828
- defaultQuery.connection = createDatabaseQueryProxy(connection);
1080
+ const holder = defaultConnectionState();
1081
+ holder.pool = connection;
1082
+ holder.query = createDatabaseQueryProxy(connection);
829
1083
  }
830
1084
  function getDefaultDatabasePool() {
831
- if (!defaultPool.connection) {
1085
+ const pool = defaultConnectionState().pool;
1086
+ if (!pool) {
832
1087
  throw new Error("Default database pool is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
833
1088
  }
834
- return defaultPool.connection;
1089
+ return pool;
835
1090
  }
836
1091
  function getDefaultDatabaseQuery() {
837
- if (!defaultQuery.connection) {
1092
+ const query = defaultConnectionState().query;
1093
+ if (!query) {
838
1094
  throw new Error("Default database query handle is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
839
1095
  }
840
- return defaultQuery.connection;
1096
+ return query;
841
1097
  }
842
1098
 
843
1099
  // ../../src/core/security/timingSafeCompare.ts
844
- import { timingSafeEqual } from "crypto";
1100
+ import { timingSafeEqual as timingSafeEqual2 } from "crypto";
845
1101
  function timingSafeCompareString(left, right) {
846
1102
  const leftBuffer = Buffer.from(left);
847
1103
  const rightBuffer = Buffer.from(right);
848
1104
  if (leftBuffer.length !== rightBuffer.length) {
849
1105
  return false;
850
1106
  }
851
- return timingSafeEqual(leftBuffer, rightBuffer);
1107
+ return timingSafeEqual2(leftBuffer, rightBuffer);
852
1108
  }
853
1109
 
854
1110
  // ../../src/core/security/scimTenantTokens.ts
@@ -988,35 +1244,26 @@ function jsonScimError(detail, status) {
988
1244
  headers: { "content-type": "application/scim+json" }
989
1245
  });
990
1246
  }
991
- // ../../src/core/cache/redisCacheStore.ts
992
- var {RedisClient } = globalThis.Bun;
993
-
994
- // ../../src/core/runtime/appKeyPrefix.ts
995
- function appKeyPrefix() {
996
- return process.env.APP_KEY_PREFIX?.trim() || "strata";
997
- }
998
- function appCookieName(kind) {
999
- return `${appKeyPrefix()}_${kind}`;
1000
- }
1001
- function appDevSecret(kind) {
1002
- return `${appKeyPrefix()}-dev-${kind}`;
1003
- }
1004
- function namespacedRedisKey(kind) {
1005
- return `${appKeyPrefix()}:${kind}`;
1006
- }
1007
- function smtpEhloHost() {
1008
- const raw = process.env.MAIL_EHLO?.trim() || `${appKeyPrefix()}.local`;
1009
- const safe = raw.replace(/[^a-zA-Z0-9.-]/g, "");
1010
- return safe || "strata.local";
1011
- }
1012
- function otelServiceName() {
1013
- return process.env.OTEL_SERVICE_NAME?.trim() || `${appKeyPrefix()}-api`;
1247
+ // ../../src/core/auth/tokenAbilityChecker.ts
1248
+ function tokenCan(user, ability) {
1249
+ if (!user) {
1250
+ return false;
1251
+ }
1252
+ const abilities = user.abilities ?? [];
1253
+ return abilities.includes("*") || abilities.includes(ability);
1014
1254
  }
1015
- function appEnv() {
1016
- return process.env.APP_ENV?.trim() || "local";
1255
+ function createTokenAbilityChecker() {
1256
+ return {
1257
+ tokenCan,
1258
+ requireAbility(user, ability) {
1259
+ if (!tokenCan(user, ability)) {
1260
+ throw new ForbiddenError(`Missing ability: ${ability}`);
1261
+ }
1262
+ }
1263
+ };
1017
1264
  }
1018
-
1019
1265
  // ../../src/core/cache/redisCacheStore.ts
1266
+ var {RedisClient } = globalThis.Bun;
1020
1267
  function cacheKeyPrefix() {
1021
1268
  return namespacedRedisKey("cache:");
1022
1269
  }
@@ -1455,7 +1702,13 @@ var CACHE_TAGS = {
1455
1702
  tasks: "tasks",
1456
1703
  comments: "comments",
1457
1704
  attachments: "attachments",
1458
- reports: "reports"
1705
+ reports: "reports",
1706
+ users: "users",
1707
+ departments: "departments",
1708
+ positions: "positions",
1709
+ applications: "applications",
1710
+ careers: "careers",
1711
+ offers: "offers"
1459
1712
  };
1460
1713
  // ../../src/core/contracts/container.ts
1461
1714
  class ServiceContainer {
@@ -1644,12 +1897,155 @@ async function withDatabaseErrorHandling(operation) {
1644
1897
  }
1645
1898
  }
1646
1899
 
1647
- // ../../src/core/database/query.ts
1648
- function quoteIdentifier(identifier) {
1900
+ // ../../src/core/database/schema/driver.ts
1901
+ function normalizeConnectionName(connection) {
1902
+ const normalized = connection.trim().toLowerCase();
1903
+ if (normalized === "pgsql" || normalized === "postgres" || normalized === "postgresql") {
1904
+ return "pgsql";
1905
+ }
1906
+ if (normalized === "mysql" || normalized === "mariadb") {
1907
+ return "mysql";
1908
+ }
1909
+ if (normalized === "sqlite") {
1910
+ return "sqlite";
1911
+ }
1912
+ throw new Error(`Unsupported DB_CONNECTION: ${connection}`);
1913
+ }
1914
+ function resolveDriverFromUrl(url) {
1915
+ const normalized = url.trim().toLowerCase();
1916
+ if (normalized.startsWith("postgres://") || normalized.startsWith("postgresql://") || normalized.startsWith("postgres:")) {
1917
+ return "pgsql";
1918
+ }
1919
+ if (normalized.startsWith("mysql://") || normalized.startsWith("mysql:")) {
1920
+ return "mysql";
1921
+ }
1922
+ if (normalized.startsWith("sqlite:")) {
1923
+ return "sqlite";
1924
+ }
1925
+ return null;
1926
+ }
1927
+ function resolveDatabaseDriver(options = {}) {
1928
+ const connection = options.connection ?? process.env.DB_CONNECTION;
1929
+ if (connection) {
1930
+ return normalizeConnectionName(connection);
1931
+ }
1932
+ const url = options.url ?? process.env.DATABASE_URL ?? "";
1933
+ const fromUrl = resolveDriverFromUrl(url);
1934
+ if (fromUrl) {
1935
+ return fromUrl;
1936
+ }
1937
+ return "pgsql";
1938
+ }
1939
+
1940
+ // ../../src/core/database/dialect.ts
1941
+ function assertSafeIdentifier(identifier) {
1649
1942
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(identifier)) {
1650
1943
  throw new Error(`Invalid SQL identifier: ${identifier}`);
1651
1944
  }
1652
- return `"${identifier}"`;
1945
+ return identifier;
1946
+ }
1947
+ var postgresDialect = {
1948
+ driver: "pgsql",
1949
+ placeholder(index) {
1950
+ return `$${index}`;
1951
+ },
1952
+ quoteIdentifier(identifier) {
1953
+ return `"${assertSafeIdentifier(identifier)}"`;
1954
+ },
1955
+ nowExpression() {
1956
+ return "NOW()";
1957
+ },
1958
+ returningClause(columns) {
1959
+ return ` RETURNING ${columns}`;
1960
+ },
1961
+ ilikeOperator() {
1962
+ return "ILIKE";
1963
+ },
1964
+ nullsLastSuffix() {
1965
+ return " NULLS LAST";
1966
+ },
1967
+ castToText(expression) {
1968
+ return `${expression}::text`;
1969
+ }
1970
+ };
1971
+ var mysqlDialect = {
1972
+ driver: "mysql",
1973
+ placeholder() {
1974
+ return "?";
1975
+ },
1976
+ quoteIdentifier(identifier) {
1977
+ return `\`${assertSafeIdentifier(identifier)}\``;
1978
+ },
1979
+ nowExpression() {
1980
+ return "CURRENT_TIMESTAMP";
1981
+ },
1982
+ returningClause() {
1983
+ return "";
1984
+ },
1985
+ ilikeOperator() {
1986
+ return "LIKE";
1987
+ },
1988
+ nullsLastSuffix() {
1989
+ return "";
1990
+ },
1991
+ castToText(expression) {
1992
+ return `CAST(${expression} AS CHAR)`;
1993
+ }
1994
+ };
1995
+ var sqliteDialect = {
1996
+ driver: "sqlite",
1997
+ placeholder() {
1998
+ return "?";
1999
+ },
2000
+ quoteIdentifier(identifier) {
2001
+ return `"${assertSafeIdentifier(identifier)}"`;
2002
+ },
2003
+ nowExpression() {
2004
+ return "CURRENT_TIMESTAMP";
2005
+ },
2006
+ returningClause(columns) {
2007
+ return ` RETURNING ${columns}`;
2008
+ },
2009
+ ilikeOperator() {
2010
+ return "LIKE";
2011
+ },
2012
+ nullsLastSuffix() {
2013
+ return "";
2014
+ },
2015
+ castToText(expression) {
2016
+ return `CAST(${expression} AS TEXT)`;
2017
+ }
2018
+ };
2019
+ var dialects = {
2020
+ pgsql: postgresDialect,
2021
+ mysql: mysqlDialect,
2022
+ sqlite: sqliteDialect
2023
+ };
2024
+ var dialectContext = createAsyncContextStore("@getstrata/sqlDialect");
2025
+ var dialectOverride = null;
2026
+ function dialectFor(driver) {
2027
+ return dialects[driver];
2028
+ }
2029
+ function currentSqlDialect() {
2030
+ return dialectContext.getStore() ?? dialectOverride ?? dialectFor(resolveDatabaseDriver());
2031
+ }
2032
+ function useSqlDialect(driver) {
2033
+ dialectOverride = dialectFor(driver);
2034
+ return dialectOverride;
2035
+ }
2036
+ function resetSqlDialect() {
2037
+ dialectOverride = null;
2038
+ }
2039
+ function runWithSqlDialect(driver, callback) {
2040
+ return dialectContext.run(dialectFor(driver), callback);
2041
+ }
2042
+
2043
+ // ../../src/core/database/query.ts
2044
+ function quoteIdentifier(identifier) {
2045
+ return currentSqlDialect().quoteIdentifier(identifier);
2046
+ }
2047
+ function returningSuffix(columns) {
2048
+ return currentSqlDialect().returningClause(columns);
1653
2049
  }
1654
2050
  function qualifyColumn(tableName, column) {
1655
2051
  return `${quoteIdentifier(tableName)}.${quoteIdentifier(column)}`;
@@ -1679,7 +2075,7 @@ function isQueryOperator(value) {
1679
2075
  }
1680
2076
  function pushParam(values, value) {
1681
2077
  values.push(value);
1682
- return `$${values.length}`;
2078
+ return currentSqlDialect().placeholder(values.length);
1683
2079
  }
1684
2080
  function buildInClause(column, values, params) {
1685
2081
  if (values.length === 0) {
@@ -1719,9 +2115,12 @@ function buildOperatorClauses(column, operator, params) {
1719
2115
  clauses.push(`${column} <= ${pushParam(params, operator.lte)}`);
1720
2116
  }
1721
2117
  if (operator.ilike !== undefined) {
1722
- clauses.push(`${column} ILIKE ${pushParam(params, operator.ilike)}`);
2118
+ clauses.push(`${column} ${currentSqlDialect().ilikeOperator()} ${pushParam(params, operator.ilike)}`);
1723
2119
  }
1724
2120
  if (operator.tsMatch !== undefined) {
2121
+ if (currentSqlDialect().driver !== "pgsql") {
2122
+ throw new Error("Full-text search (tsMatch) is only available on PostgreSQL.");
2123
+ }
1725
2124
  clauses.push(`${column} @@ plainto_tsquery('english', ${pushParam(params, operator.tsMatch)})`);
1726
2125
  }
1727
2126
  return clauses;
@@ -1912,7 +2311,7 @@ function buildSelectList(table, select, params = []) {
1912
2311
  return item.as ? `${column2} AS ${quoteIdentifier(item.as)}` : column2;
1913
2312
  }
1914
2313
  if (item.kind === "literalText") {
1915
- return `${pushParam(params, item.value)}::text AS ${quoteIdentifier(item.as)}`;
2314
+ return `${currentSqlDialect().castToText(pushParam(params, item.value))} AS ${quoteIdentifier(item.as)}`;
1916
2315
  }
1917
2316
  const column = qualifyColumn(item.table, item.column);
1918
2317
  const placeholder = pushParam(params, item.query);
@@ -2004,7 +2403,7 @@ function buildInsertQuery(table, values) {
2004
2403
  const placeholders = entries.map(([, value]) => pushParam(params, value)).join(", ");
2005
2404
  const returningColumns = buildReturningColumns(table);
2006
2405
  return {
2007
- text: `INSERT INTO ${quoteIdentifier(table.name)} (${columns}) VALUES (${placeholders}) RETURNING ${returningColumns}`,
2406
+ text: `INSERT INTO ${quoteIdentifier(table.name)} (${columns}) VALUES (${placeholders})${returningSuffix(returningColumns)}`,
2008
2407
  params
2009
2408
  };
2010
2409
  }
@@ -2023,7 +2422,7 @@ function buildUpdateQuery(table, id, changes) {
2023
2422
  appendSoftDeleteScope(table, {}, scopeClauses);
2024
2423
  const scopeSuffix = scopeClauses.length > 0 ? ` AND ${scopeClauses.join(" AND ")}` : "";
2025
2424
  return {
2026
- text: `UPDATE ${quoteIdentifier(table.name)} SET ${setClause} WHERE ${quoteIdentifier(table.primaryKey)} = ${primaryKeyPlaceholder}${scopeSuffix} RETURNING ${returningColumns}`,
2425
+ text: `UPDATE ${quoteIdentifier(table.name)} SET ${setClause} WHERE ${quoteIdentifier(table.primaryKey)} = ${primaryKeyPlaceholder}${scopeSuffix}${returningSuffix(returningColumns)}`,
2027
2426
  params
2028
2427
  };
2029
2428
  }
@@ -2036,9 +2435,12 @@ function buildSoftDeleteByIdQuery(table, id, deletedAt) {
2036
2435
  const scopeClauses = [];
2037
2436
  appendSoftDeleteScope(table, {}, scopeClauses);
2038
2437
  const scopeSuffix = scopeClauses.length > 0 ? ` AND ${scopeClauses.join(" AND ")}` : "";
2438
+ const params = [];
2439
+ const deletedAtPlaceholder = pushParam(params, deletedAt);
2440
+ const idPlaceholder = pushParam(params, id);
2039
2441
  return {
2040
- text: `UPDATE ${quoteIdentifier(table.name)} SET ${quoteIdentifier(deletedAtColumn)} = $1 WHERE ${quoteIdentifier(table.primaryKey)} = $2${scopeSuffix} RETURNING ${returningColumns}`,
2041
- params: [deletedAt, id]
2442
+ text: `UPDATE ${quoteIdentifier(table.name)} SET ${quoteIdentifier(deletedAtColumn)} = ${deletedAtPlaceholder} WHERE ${quoteIdentifier(table.primaryKey)} = ${idPlaceholder}${scopeSuffix}${returningSuffix(returningColumns)}`,
2443
+ params
2042
2444
  };
2043
2445
  }
2044
2446
  function buildRestoreByIdQuery(table, id) {
@@ -2047,15 +2449,21 @@ function buildRestoreByIdQuery(table, id) {
2047
2449
  throw new Error(`Table ${table.name} does not support soft deletes.`);
2048
2450
  }
2049
2451
  const returningColumns = buildReturningColumns(table);
2452
+ const params = [];
2453
+ const deletedAtPlaceholder = pushParam(params, null);
2454
+ const idPlaceholder = pushParam(params, id);
2050
2455
  return {
2051
- text: `UPDATE ${quoteIdentifier(table.name)} SET ${quoteIdentifier(deletedAtColumn)} = $1 WHERE ${quoteIdentifier(table.primaryKey)} = $2 AND ${qualifyColumn(table.name, deletedAtColumn)} IS NOT NULL RETURNING ${returningColumns}`,
2052
- params: [null, id]
2456
+ text: `UPDATE ${quoteIdentifier(table.name)} SET ${quoteIdentifier(deletedAtColumn)} = ${deletedAtPlaceholder} WHERE ${quoteIdentifier(table.primaryKey)} = ${idPlaceholder} AND ${qualifyColumn(table.name, deletedAtColumn)} IS NOT NULL${returningSuffix(returningColumns)}`,
2457
+ params
2053
2458
  };
2054
2459
  }
2055
2460
  function buildDeleteByIdQuery(table, id) {
2461
+ const params = [];
2462
+ const idPlaceholder = pushParam(params, id);
2463
+ const returning = returningSuffix(`${quoteIdentifier(table.primaryKey)} AS ${quoteIdentifier("deleted_id")}`);
2056
2464
  return {
2057
- text: `DELETE FROM ${quoteIdentifier(table.name)} WHERE ${quoteIdentifier(table.primaryKey)} = $1 RETURNING ${quoteIdentifier(table.primaryKey)} AS ${quoteIdentifier("deleted_id")}`,
2058
- params: [id]
2465
+ text: `DELETE FROM ${quoteIdentifier(table.name)} WHERE ${quoteIdentifier(table.primaryKey)} = ${idPlaceholder}${returning}`,
2466
+ params
2059
2467
  };
2060
2468
  }
2061
2469
 
@@ -2106,6 +2514,19 @@ function relationMatchKey(value) {
2106
2514
  }
2107
2515
  return String(value);
2108
2516
  }
2517
+ var relationLookupCache = new WeakMap;
2518
+ function indexedRelationLookup(map) {
2519
+ const cached = relationLookupCache.get(map);
2520
+ if (cached) {
2521
+ return cached;
2522
+ }
2523
+ const indexed = new Map;
2524
+ for (const [existing, value] of map) {
2525
+ indexed.set(relationMatchKey(existing), value);
2526
+ }
2527
+ relationLookupCache.set(map, indexed);
2528
+ return indexed;
2529
+ }
2109
2530
  function getByRelationKey(map, key) {
2110
2531
  if (map.has(key)) {
2111
2532
  return map.get(key);
@@ -2114,12 +2535,7 @@ function getByRelationKey(map, key) {
2114
2535
  if (want === "") {
2115
2536
  return;
2116
2537
  }
2117
- for (const [existing, value] of map) {
2118
- if (relationMatchKey(existing) === want) {
2119
- return value;
2120
- }
2121
- }
2122
- return;
2538
+ return indexedRelationLookup(map).get(want);
2123
2539
  }
2124
2540
  function indexHasManyRelation(parents, children, relation) {
2125
2541
  const groups = new Map;
@@ -2532,73 +2948,67 @@ class RepositoryQuery {
2532
2948
  return this;
2533
2949
  }
2534
2950
  async attach(rows) {
2535
- if (rows.length === 0 || this.eagerLoads.length === 0) {
2536
- return rows.map((row) => ({ ...row }));
2537
- }
2538
- let result = rows.map((row) => ({ ...row }));
2539
- for (const load of this.eagerLoads) {
2540
- if (load.kind === "hasMany") {
2541
- const relation2 = load.relation;
2542
- const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadHasManyForParents(rows, relation2, load.options);
2543
- result = result.map((row) => ({
2544
- ...row,
2545
- [load.as]: getByRelationKey(grouped2, row[relation2.localKey]) ?? []
2546
- }));
2547
- continue;
2951
+ const result = rows.map((row) => ({ ...row }));
2952
+ if (result.length === 0 || this.eagerLoads.length === 0) {
2953
+ return result;
2954
+ }
2955
+ await Promise.all(this.eagerLoads.map((load) => this.hydrateEagerLoad(rows, result, load)));
2956
+ return result;
2957
+ }
2958
+ async hydrateEagerLoad(rows, result, load) {
2959
+ if (load.kind === "hasMany") {
2960
+ const relation2 = load.relation;
2961
+ const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadHasManyForParents(rows, relation2, load.options);
2962
+ for (const row of result) {
2963
+ row[load.as] = getByRelationKey(grouped2, row[relation2.localKey]) ?? [];
2548
2964
  }
2549
- if (load.kind === "morphMany") {
2550
- const relation2 = load.relation;
2551
- const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphManyForParents(rows, relation2, load.options);
2552
- result = result.map((row) => ({
2553
- ...row,
2554
- [load.as]: getByRelationKey(grouped2, row[relation2.localKey]) ?? []
2555
- }));
2556
- continue;
2965
+ return;
2966
+ }
2967
+ if (load.kind === "morphMany") {
2968
+ const relation2 = load.relation;
2969
+ const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphManyForParents(rows, relation2, load.options);
2970
+ for (const row of result) {
2971
+ row[load.as] = getByRelationKey(grouped2, row[relation2.localKey]) ?? [];
2557
2972
  }
2558
- if (load.kind === "morphOne") {
2559
- const relation2 = load.relation;
2560
- const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphOneForParents(rows, relation2, load.options);
2561
- result = result.map((row) => ({
2562
- ...row,
2563
- [load.as]: getByRelationKey(grouped2, row[relation2.localKey])
2564
- }));
2565
- continue;
2973
+ return;
2974
+ }
2975
+ if (load.kind === "morphOne") {
2976
+ const relation2 = load.relation;
2977
+ const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphOneForParents(rows, relation2, load.options);
2978
+ for (const row of result) {
2979
+ row[load.as] = getByRelationKey(grouped2, row[relation2.localKey]);
2566
2980
  }
2567
- if (load.kind === "hasManyThrough") {
2568
- const relation2 = load.relation;
2569
- const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadHasManyThroughForParents(rows, relation2, load.options);
2570
- result = result.map((row) => ({
2571
- ...row,
2572
- [load.as]: getByRelationKey(grouped2, row[relation2.localKey]) ?? []
2573
- }));
2574
- continue;
2981
+ return;
2982
+ }
2983
+ if (load.kind === "hasManyThrough") {
2984
+ const relation2 = load.relation;
2985
+ const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadHasManyThroughForParents(rows, relation2, load.options);
2986
+ for (const row of result) {
2987
+ row[load.as] = getByRelationKey(grouped2, row[relation2.localKey]) ?? [];
2575
2988
  }
2576
- if (load.kind === "belongsToMany") {
2577
- const relation2 = load.relation;
2578
- const grouped2 = await this.repository.loadBelongsToManyForParents(rows, relation2, load.repository, load.options);
2579
- result = result.map((row) => ({
2580
- ...row,
2581
- [load.as]: getByRelationKey(grouped2, row[relation2.parentKey]) ?? []
2582
- }));
2583
- continue;
2989
+ return;
2990
+ }
2991
+ if (load.kind === "belongsToMany") {
2992
+ const relation2 = load.relation;
2993
+ const grouped2 = await this.repository.loadBelongsToManyForParents(rows, relation2, load.repository, load.options);
2994
+ for (const row of result) {
2995
+ row[load.as] = getByRelationKey(grouped2, row[relation2.parentKey]) ?? [];
2584
2996
  }
2585
- if (load.kind === "morphTo") {
2586
- const relation2 = load.relation;
2587
- const grouped2 = await this.repository.loadMorphToForChildren(rows, relation2, load.morphRepositories ?? new Map, load.options);
2588
- result = result.map((row) => ({
2589
- ...row,
2590
- [load.as]: getByRelationKey(grouped2, row[relation2.morphIdKey])
2591
- }));
2592
- continue;
2997
+ return;
2998
+ }
2999
+ if (load.kind === "morphTo") {
3000
+ const relation2 = load.relation;
3001
+ const grouped2 = await this.repository.loadMorphToForChildren(rows, relation2, load.morphRepositories ?? new Map, load.options);
3002
+ for (const row of result) {
3003
+ row[load.as] = getByRelationKey(grouped2, row[relation2.morphIdKey]);
2593
3004
  }
2594
- const relation = load.relation;
2595
- const grouped = await this.repository.loadBelongsToForParents(rows, relation, load.repository, load.options);
2596
- result = result.map((row) => ({
2597
- ...row,
2598
- [load.as]: getByRelationKey(grouped, row[relation.foreignKey])
2599
- }));
3005
+ return;
3006
+ }
3007
+ const relation = load.relation;
3008
+ const grouped = await this.repository.loadBelongsToForParents(rows, relation, load.repository, load.options);
3009
+ for (const row of result) {
3010
+ row[load.as] = getByRelationKey(grouped, row[relation.foreignKey]);
2600
3011
  }
2601
- return result;
2602
3012
  }
2603
3013
  }
2604
3014
 
@@ -2953,10 +3363,10 @@ class BaseRepository {
2953
3363
  idsByType.set(morphType, ids);
2954
3364
  }
2955
3365
  const parentsByType = new Map;
2956
- for (const [morphType, ids] of idsByType) {
3366
+ await Promise.all([...idsByType.entries()].map(async ([morphType, ids]) => {
2957
3367
  const repository = repositoriesByType.get(morphType);
2958
3368
  if (!repository) {
2959
- continue;
3369
+ return;
2960
3370
  }
2961
3371
  const ownerKey = repository.getTable().primaryKey;
2962
3372
  const parents = await repository.withConnection(this.connection).findWhere({
@@ -2967,7 +3377,7 @@ class BaseRepository {
2967
3377
  indexed.set(relationMatchKey(parent[ownerKey]), parent);
2968
3378
  }
2969
3379
  parentsByType.set(morphType, indexed);
2970
- }
3380
+ }));
2971
3381
  return indexMorphToRelation(children, parentsByType, relation);
2972
3382
  }
2973
3383
  async loadBelongsToManyForParents(parents, relation, relatedRepository, options = {}) {
@@ -3169,10 +3579,11 @@ class Factory {
3169
3579
  return made;
3170
3580
  }
3171
3581
  async createOne(overrides = {}) {
3172
- const created = await this.persist(this.insertable(this.makeOne(overrides)));
3173
- for (const child of this.children) {
3174
- await child.factory.for(created, child.foreignKey).create();
3175
- }
3582
+ return this.persistCreated(this.makeOne(overrides));
3583
+ }
3584
+ async persistCreated(record) {
3585
+ const created = await this.persist(this.insertable(record));
3586
+ await Promise.all(this.children.map((child) => child.factory.for(created, child.foreignKey).create()));
3176
3587
  for (const callback of this.afterCreatingCallbacks) {
3177
3588
  await callback(created);
3178
3589
  }
@@ -3410,11 +3821,7 @@ class HasManyRelationQuery {
3410
3821
  return this.create(related);
3411
3822
  }
3412
3823
  async createMany(records) {
3413
- const created = [];
3414
- for (const attributes of records) {
3415
- created.push(await this.create(attributes));
3416
- }
3417
- return created;
3824
+ return Promise.all(records.map((attributes) => this.create(attributes)));
3418
3825
  }
3419
3826
  }
3420
3827
 
@@ -3920,12 +4327,12 @@ async function eagerLoadOnModels(models, paths) {
3920
4327
  }
3921
4328
  grouped.set(head, existing);
3922
4329
  }
3923
- for (const [head, nested] of grouped) {
4330
+ await Promise.all([...grouped.entries()].map(async ([head, nested]) => {
3924
4331
  const unloaded = models.filter((model) => model.loaded(head) === undefined);
3925
4332
  if (unloaded.length > 0) {
3926
4333
  const first = unloaded[0];
3927
4334
  if (!first) {
3928
- continue;
4335
+ return;
3929
4336
  }
3930
4337
  const method = first[head];
3931
4338
  if (typeof method !== "function") {
@@ -3941,14 +4348,14 @@ async function eagerLoadOnModels(models, paths) {
3941
4348
  }
3942
4349
  }
3943
4350
  if (nested.length === 0) {
3944
- continue;
4351
+ return;
3945
4352
  }
3946
4353
  const children = models.flatMap((model) => {
3947
4354
  const loaded = model.loaded(head);
3948
4355
  return Array.isArray(loaded) ? loaded : loaded ? [loaded] : [];
3949
4356
  });
3950
4357
  await eagerLoadOnModels(children.filter(isLoadableModel), nested);
3951
- }
4358
+ }));
3952
4359
  }
3953
4360
  async function loadNested(model, path) {
3954
4361
  await eagerLoadOnModels([model], [path]);
@@ -4226,12 +4633,12 @@ class ModelQuery {
4226
4633
  const models = [];
4227
4634
  for (const row of rows) {
4228
4635
  const model = statics.newFromRecord(row, true);
4229
- await runObservers(model, "retrieved");
4230
4636
  for (const { name, relationQuery } of this.eager) {
4231
4637
  model.setLoaded(name, relationQuery.hydrateEager(row, name));
4232
4638
  }
4233
4639
  models.push(model);
4234
4640
  }
4641
+ await Promise.all(models.map((model) => runObservers(model, "retrieved")));
4235
4642
  const nested = this.eager.filter((item) => item.path.includes(".")).map((item) => item.path);
4236
4643
  await eagerLoadOnModels(models.filter(isLoadableModel), nested);
4237
4644
  return models;
@@ -4688,7 +5095,7 @@ class Model {
4688
5095
  morphTo(relatedByType, morphName, typeKey, idKey) {
4689
5096
  const resolvedName = morphName ?? inferRelationMethodName("morphTo");
4690
5097
  if (!resolvedName) {
4691
- throw new Error(`${this.constructor.name}.morphTo() needs an explicit morph name (Laravel infers it from the relation method).`);
5098
+ throw new Error(`${this.constructor.name}.morphTo() needs an explicit morph name (the calling method name is not available at runtime).`);
4692
5099
  }
4693
5100
  const resolvedMap = Object.fromEntries(Object.entries(relatedByType).map(([type, related]) => [type, resolveRelated(related)]));
4694
5101
  return new MorphToRelationQuery(this, resolvedMap, morphTo({
@@ -4737,6 +5144,74 @@ function registerModelRepository(model, repository) {
4737
5144
  ensureBooted(model);
4738
5145
  return model;
4739
5146
  }
5147
+ // ../../src/core/database/mysqlConnection.ts
5148
+ import mysql from "mysql2/promise";
5149
+ function rowsFromResult(result) {
5150
+ if (Array.isArray(result)) {
5151
+ return result;
5152
+ }
5153
+ if (result && typeof result === "object") {
5154
+ return [result];
5155
+ }
5156
+ return [];
5157
+ }
5158
+ function createMysqlConnectionFromPool(pool) {
5159
+ return {
5160
+ async unsafe(query, params = []) {
5161
+ const [result] = await pool.execute(query, [...params]);
5162
+ return rowsFromResult(result);
5163
+ },
5164
+ async close() {
5165
+ if (typeof pool.end === "function") {
5166
+ await pool.end();
5167
+ }
5168
+ }
5169
+ };
5170
+ }
5171
+ function createMysqlConnection(url) {
5172
+ if (!url.trim()) {
5173
+ throw new Error("MYSQL_URL is not configured. Set url before creating a MySQL pool.");
5174
+ }
5175
+ return createMysqlConnectionFromPool(mysql.createPool(url));
5176
+ }
5177
+ // ../../src/core/database/namedConnections.ts
5178
+ var REGISTRY_KEY = Symbol.for("@getstrata/namedConnections");
5179
+ function registry() {
5180
+ const globalRecord = globalThis;
5181
+ const existing = globalRecord[REGISTRY_KEY];
5182
+ if (existing) {
5183
+ return existing;
5184
+ }
5185
+ const created = new Map;
5186
+ globalRecord[REGISTRY_KEY] = created;
5187
+ return created;
5188
+ }
5189
+ function registerNamedConnection(name, driver, connection) {
5190
+ if (!name.trim()) {
5191
+ throw new Error("Named database connection requires a non-empty name.");
5192
+ }
5193
+ registry().set(name, { name, driver, connection });
5194
+ }
5195
+ function unregisterNamedConnection(name) {
5196
+ return registry().delete(name);
5197
+ }
5198
+ function hasNamedConnection(name) {
5199
+ return registry().has(name);
5200
+ }
5201
+ function getNamedConnection(name) {
5202
+ const entry = registry().get(name);
5203
+ if (!entry) {
5204
+ throw new Error(`Named database connection "${name}" is not registered.`);
5205
+ }
5206
+ return entry;
5207
+ }
5208
+ function resetNamedConnections() {
5209
+ registry().clear();
5210
+ }
5211
+ function runOnNamedConnection(name, callback) {
5212
+ const entry = getNamedConnection(name);
5213
+ return runWithSqlDialect(entry.driver, () => runWithDatabaseConnection(entry.connection, callback));
5214
+ }
4740
5215
  // ../../src/core/database/schema/columnDefinition.ts
4741
5216
  class ColumnDefinition {
4742
5217
  name;
@@ -4958,45 +5433,6 @@ class Blueprint {
4958
5433
  });
4959
5434
  }
4960
5435
  }
4961
- // ../../src/core/database/schema/driver.ts
4962
- function normalizeConnectionName(connection) {
4963
- const normalized = connection.trim().toLowerCase();
4964
- if (normalized === "pgsql" || normalized === "postgres" || normalized === "postgresql") {
4965
- return "pgsql";
4966
- }
4967
- if (normalized === "mysql" || normalized === "mariadb") {
4968
- return "mysql";
4969
- }
4970
- if (normalized === "sqlite") {
4971
- return "sqlite";
4972
- }
4973
- throw new Error(`Unsupported DB_CONNECTION: ${connection}`);
4974
- }
4975
- function resolveDriverFromUrl(url) {
4976
- const normalized = url.trim().toLowerCase();
4977
- if (normalized.startsWith("postgres://") || normalized.startsWith("postgresql://") || normalized.startsWith("postgres:")) {
4978
- return "pgsql";
4979
- }
4980
- if (normalized.startsWith("mysql://") || normalized.startsWith("mysql:")) {
4981
- return "mysql";
4982
- }
4983
- if (normalized.startsWith("sqlite:")) {
4984
- return "sqlite";
4985
- }
4986
- return null;
4987
- }
4988
- function resolveDatabaseDriver(options = {}) {
4989
- const connection = options.connection ?? process.env.DB_CONNECTION;
4990
- if (connection) {
4991
- return normalizeConnectionName(connection);
4992
- }
4993
- const url = options.url ?? process.env.DATABASE_URL ?? "";
4994
- const fromUrl = resolveDriverFromUrl(url);
4995
- if (fromUrl) {
4996
- return fromUrl;
4997
- }
4998
- return "pgsql";
4999
- }
5000
5436
  // ../../src/core/database/schema/errors.ts
5001
5437
  class UnsupportedSchemaFeatureError extends Error {
5002
5438
  constructor(feature, driver) {
@@ -5385,6 +5821,36 @@ async function runSeedersFromDirectory(directory, db, options) {
5385
5821
  }
5386
5822
  return seeders.length;
5387
5823
  }
5824
+ // ../../src/core/database/sqliteConnection.ts
5825
+ import { Database } from "bun:sqlite";
5826
+ function isRowReturning(sql) {
5827
+ const upper = sql.replace(/\s+/g, " ").trim().toUpperCase();
5828
+ if (upper.includes(" RETURNING ")) {
5829
+ return true;
5830
+ }
5831
+ return upper.startsWith("SELECT") || upper.startsWith("WITH") || upper.startsWith("PRAGMA") || upper.startsWith("EXPLAIN");
5832
+ }
5833
+ function createSqliteConnection(filename) {
5834
+ if (!filename.trim()) {
5835
+ throw new Error("SQLite path is not configured. Pass a filename or :memory:.");
5836
+ }
5837
+ const db = new Database(filename, { create: true });
5838
+ db.exec("PRAGMA foreign_keys = ON");
5839
+ return {
5840
+ async unsafe(query, params = []) {
5841
+ const statement = db.query(query);
5842
+ const args = [...params];
5843
+ if (isRowReturning(query)) {
5844
+ return statement.all(...args);
5845
+ }
5846
+ statement.run(...args);
5847
+ return [];
5848
+ },
5849
+ close() {
5850
+ db.close();
5851
+ }
5852
+ };
5853
+ }
5388
5854
  // ../../src/core/database/table.ts
5389
5855
  function defineTable(definition) {
5390
5856
  return definition;
@@ -5563,7 +6029,7 @@ class LogMailDriver {
5563
6029
  to: message.to,
5564
6030
  subject: message.subject,
5565
6031
  body: message.body,
5566
- ...message.html ? { html: message.html } : {}
6032
+ ...message.html ? { htmlBytes: Buffer.byteLength(message.html, "utf8") } : {}
5567
6033
  }));
5568
6034
  }
5569
6035
  }
@@ -5918,6 +6384,32 @@ function conditionalJsonResponse(request, data, init = {}) {
5918
6384
  headers
5919
6385
  });
5920
6386
  }
6387
+ // ../../src/core/runtime/frontendMode.ts
6388
+ var FRONTEND_MODES = ["api", "server-htmx", "spa-react", "hybrid"];
6389
+ var FRONTEND_MODE_PATTERN = new RegExp(`^(${FRONTEND_MODES.join("|")})$`);
6390
+ function parseFrontendMode(value) {
6391
+ const mode = (value ?? "api").trim();
6392
+ if (mode === "server-htmx" || mode === "spa-react" || mode === "hybrid") {
6393
+ return mode;
6394
+ }
6395
+ return "api";
6396
+ }
6397
+ function readFrontendMode() {
6398
+ return parseFrontendMode(process.env.FRONTEND_MODE);
6399
+ }
6400
+ function isViewsMode(mode) {
6401
+ return mode === "server-htmx" || mode === "hybrid";
6402
+ }
6403
+ function isSpaMode(mode) {
6404
+ return mode === "spa-react" || mode === "hybrid";
6405
+ }
6406
+ function isViewsEnabled() {
6407
+ return isViewsMode(readFrontendMode());
6408
+ }
6409
+ function isSpaEnabled() {
6410
+ return isSpaMode(readFrontendMode());
6411
+ }
6412
+
5921
6413
  // ../../src/core/http/contentSecurityPolicy.ts
5922
6414
  var HTMX_2_0_4_INDICATOR_STYLE_HASH = "'sha256-bsV5JivYxvGywDAZ22EZJKBFip65Ng9xoJVLbBg7bdo='";
5923
6415
  var API_DIRECTIVES = {
@@ -6005,11 +6497,10 @@ function applyNonce(directives, nonce) {
6005
6497
  return next;
6006
6498
  }
6007
6499
  function htmlBaselineDirectives() {
6008
- const frontendMode = (process.env.FRONTEND_MODE ?? "api").trim();
6009
- if (frontendMode === "server-htmx") {
6500
+ if (isViewsEnabled()) {
6010
6501
  return cloneDirectives(HTMX_HTML_DIRECTIVES);
6011
6502
  }
6012
- if (frontendMode === "spa-react") {
6503
+ if (isSpaEnabled()) {
6013
6504
  return cloneDirectives(SPA_HTML_DIRECTIVES);
6014
6505
  }
6015
6506
  return cloneDirectives(API_DIRECTIVES);
@@ -6124,7 +6615,7 @@ function buildCorsHeaders(request) {
6124
6615
  return headers;
6125
6616
  }
6126
6617
  // ../../src/core/http/csrfToken.ts
6127
- import { timingSafeEqual as timingSafeEqual2 } from "crypto";
6618
+ import { timingSafeEqual as timingSafeEqual3 } from "crypto";
6128
6619
 
6129
6620
  // ../../src/core/http/requestMetaContext.ts
6130
6621
  var requestMetaContext = createAsyncContextStore("@getstrata/requestMetaContext");
@@ -6156,7 +6647,7 @@ function tokensMatch(left, right) {
6156
6647
  if (leftBuffer.length !== rightBuffer.length) {
6157
6648
  return false;
6158
6649
  }
6159
- return timingSafeEqual2(leftBuffer, rightBuffer);
6650
+ return timingSafeEqual3(leftBuffer, rightBuffer);
6160
6651
  }
6161
6652
  function createCsrfTokenCookie() {
6162
6653
  const token = Bun.CSRF.generate(resolveCsrfSecret(), { expiresIn: CSRF_TTL_MS });
@@ -6233,6 +6724,9 @@ function appendSetCookie(response, cookie) {
6233
6724
  }
6234
6725
  function createCsrfMiddleware() {
6235
6726
  return async (request, next) => {
6727
+ if (requestUsesHeaderCredentials(request)) {
6728
+ return await next();
6729
+ }
6236
6730
  const method = request.method.toUpperCase();
6237
6731
  if (!MUTATING_METHODS.has(method)) {
6238
6732
  const csrf = resolveCsrfToken(request);
@@ -6270,7 +6764,7 @@ function createCsrfProtection(secret, options = {}) {
6270
6764
  };
6271
6765
  }
6272
6766
  // ../../src/core/http/flashSession.ts
6273
- import { createHmac, timingSafeEqual as timingSafeEqual3 } from "crypto";
6767
+ import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual4 } from "crypto";
6274
6768
  var FLASH_COOKIE = appCookieName("flash");
6275
6769
  var FLASH_TTL_MS = 60 * 1000;
6276
6770
  function flashCookieName() {
@@ -6280,7 +6774,7 @@ function resolveFlashSecret() {
6280
6774
  return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || appDevSecret("flash-secret");
6281
6775
  }
6282
6776
  function signFlashPayload(payload, issuedAt) {
6283
- const signature = createHmac("sha256", resolveFlashSecret()).update(`${payload}.${issuedAt}`).digest("hex");
6777
+ const signature = createHmac2("sha256", resolveFlashSecret()).update(`${payload}.${issuedAt}`).digest("hex");
6284
6778
  return `${payload}.${issuedAt}.${signature}`;
6285
6779
  }
6286
6780
  function readFlashCookie(request) {
@@ -6320,7 +6814,7 @@ function parseFlashCookie(cookieValue) {
6320
6814
  if (expectedBuffer.length !== actualBuffer.length) {
6321
6815
  return null;
6322
6816
  }
6323
- if (!timingSafeEqual3(expectedBuffer, actualBuffer)) {
6817
+ if (!timingSafeEqual4(expectedBuffer, actualBuffer)) {
6324
6818
  return null;
6325
6819
  }
6326
6820
  try {
@@ -6630,6 +7124,9 @@ function whenLoaded(model, relation, transform) {
6630
7124
  if (value === undefined) {
6631
7125
  return;
6632
7126
  }
7127
+ if (value === null) {
7128
+ return null;
7129
+ }
6633
7130
  return transform ? transform(value) : value;
6634
7131
  }
6635
7132
 
@@ -6655,7 +7152,7 @@ class JsonResource {
6655
7152
  }
6656
7153
  whenLoaded(relation, transform) {
6657
7154
  const model = this.resource;
6658
- if (typeof model.loaded !== "function") {
7155
+ if (this.resource == null || typeof model.loaded !== "function") {
6659
7156
  return;
6660
7157
  }
6661
7158
  return whenLoaded(model, relation, transform);
@@ -6698,21 +7195,6 @@ function toPaginatedResourceCollection(items, meta, transformer) {
6698
7195
  meta
6699
7196
  };
6700
7197
  }
6701
- // ../../src/core/runtime/frontendMode.ts
6702
- function readFrontendMode() {
6703
- const mode = (process.env.FRONTEND_MODE ?? "api").trim();
6704
- if (mode === "server-htmx") {
6705
- return "server-htmx";
6706
- }
6707
- if (mode === "spa-react") {
6708
- return "spa-react";
6709
- }
6710
- return "api";
6711
- }
6712
- function isViewsEnabled() {
6713
- return readFrontendMode() === "server-htmx";
6714
- }
6715
-
6716
7198
  // ../../src/core/view/htmlResponse.ts
6717
7199
  function withCharset(contentType) {
6718
7200
  return contentType.includes("charset=") ? contentType : `${contentType}; charset=utf-8`;
@@ -6840,6 +7322,10 @@ function requestPrefersJson(request) {
6840
7322
  if (request.headers.get("HX-Request") === "true") {
6841
7323
  return false;
6842
7324
  }
7325
+ const pathname = new URL(request.url).pathname;
7326
+ if (pathname.startsWith("/api/")) {
7327
+ return true;
7328
+ }
6843
7329
  const accept = request.headers.get("accept")?.toLowerCase() ?? "";
6844
7330
  if (accept.includes("text/html")) {
6845
7331
  return false;
@@ -6851,8 +7337,7 @@ function requestPrefersJson(request) {
6851
7337
  if (contentType.includes("application/x-www-form-urlencoded") || contentType.includes("multipart/form-data")) {
6852
7338
  return false;
6853
7339
  }
6854
- const pathname = new URL(request.url).pathname;
6855
- return pathname.startsWith("/api/");
7340
+ return false;
6856
7341
  }
6857
7342
 
6858
7343
  // ../../src/core/http/safeInternalPath.ts
@@ -7059,6 +7544,29 @@ function securedBindRouteModelByKey(param, resolver, authorization, handler) {
7059
7544
  }
7060
7545
  // ../../src/core/http/loginThrottleMiddleware.ts
7061
7546
  var {RedisClient: RedisClient2 } = globalThis.Bun;
7547
+
7548
+ // ../../src/core/http/throttleResponse.ts
7549
+ async function tooManyRequestsResponse(request, message, decaySeconds) {
7550
+ const retryAfter = { "retry-after": String(decaySeconds) };
7551
+ if (requestPrefersJson(request) || !isViewsEnabled()) {
7552
+ return Response.json({ error: message }, {
7553
+ status: 429,
7554
+ headers: retryAfter
7555
+ });
7556
+ }
7557
+ const html = await htmlErrorResponse({
7558
+ status: 429,
7559
+ title: "Too Many Requests",
7560
+ message,
7561
+ request
7562
+ });
7563
+ const headers = new Headers(html.headers);
7564
+ headers.set("retry-after", String(decaySeconds));
7565
+ return new Response(html.body, { status: 429, headers });
7566
+ }
7567
+
7568
+ // ../../src/core/http/loginThrottleMiddleware.ts
7569
+ var memoryLoginBuckets = new Map;
7062
7570
  function resolveLoginIdentity(request) {
7063
7571
  return readClientIp(request) ?? "unknown";
7064
7572
  }
@@ -7076,7 +7584,30 @@ async function resolveLoginEmail(request) {
7076
7584
  return "unknown";
7077
7585
  }
7078
7586
  }
7079
- function createLoginThrottleMiddleware(options) {
7587
+ function consumeMemoryAttempt(key, decaySeconds) {
7588
+ const now = Date.now();
7589
+ const existing = memoryLoginBuckets.get(key);
7590
+ if (!existing || existing.resetAt <= now) {
7591
+ memoryLoginBuckets.set(key, { count: 1, resetAt: now + decaySeconds * 1000 });
7592
+ return 1;
7593
+ }
7594
+ existing.count += 1;
7595
+ return existing.count;
7596
+ }
7597
+ function createMemoryLoginThrottleMiddleware(options) {
7598
+ const prefix = options.keyPrefix ?? namespacedRedisKey("login-throttle:");
7599
+ return async (request, next) => {
7600
+ const identity = resolveLoginIdentity(request);
7601
+ const email = await resolveLoginEmail(request);
7602
+ const throttleKey = `${prefix}${identity}:${email}`;
7603
+ const attempts = consumeMemoryAttempt(throttleKey, options.decaySeconds);
7604
+ if (attempts > options.maxAttempts) {
7605
+ return await tooManyRequestsResponse(request, "Too many login attempts. Try again later.", options.decaySeconds);
7606
+ }
7607
+ return await next();
7608
+ };
7609
+ }
7610
+ function createRedisLoginThrottleMiddleware(options) {
7080
7611
  const client = new RedisClient2(options.redisUrl);
7081
7612
  const prefix = options.keyPrefix ?? namespacedRedisKey("login-throttle:");
7082
7613
  return async (request, next) => {
@@ -7088,16 +7619,18 @@ function createLoginThrottleMiddleware(options) {
7088
7619
  await client.expire(throttleKey, options.decaySeconds);
7089
7620
  }
7090
7621
  if (attempts > options.maxAttempts) {
7091
- return Response.json({ error: "Too many login attempts. Try again later." }, {
7092
- status: 429,
7093
- headers: {
7094
- "retry-after": String(options.decaySeconds)
7095
- }
7096
- });
7622
+ return await tooManyRequestsResponse(request, "Too many login attempts. Try again later.", options.decaySeconds);
7097
7623
  }
7098
7624
  return await next();
7099
7625
  };
7100
7626
  }
7627
+ function createLoginThrottleMiddleware(options) {
7628
+ const redisUrl = options.redisUrl?.trim() ?? "";
7629
+ if (redisUrl) {
7630
+ return createRedisLoginThrottleMiddleware({ ...options, redisUrl });
7631
+ }
7632
+ return createMemoryLoginThrottleMiddleware(options);
7633
+ }
7101
7634
  // ../../src/core/http/memoryThrottleMiddleware.ts
7102
7635
  var throttleBucketRegistries = new Set;
7103
7636
  function createMemoryThrottleMiddleware(options) {
@@ -7116,12 +7649,7 @@ function createMemoryThrottleMiddleware(options) {
7116
7649
  }
7117
7650
  existing.count += 1;
7118
7651
  if (existing.count > options.maxAttempts) {
7119
- return Response.json({ error: "Too many requests." }, {
7120
- status: 429,
7121
- headers: {
7122
- "retry-after": String(options.decaySeconds)
7123
- }
7124
- });
7652
+ return await tooManyRequestsResponse(request, "Too many requests.", options.decaySeconds);
7125
7653
  }
7126
7654
  return await next();
7127
7655
  };
@@ -7267,6 +7795,60 @@ function createRequireGlobalAdminMiddleware() {
7267
7795
  return await next();
7268
7796
  };
7269
7797
  }
7798
+ // ../../src/core/auth/intendedUrlCookie.ts
7799
+ var INTENDED_URL_COOKIE = appCookieName("intended");
7800
+ var DEFAULT_INTENDED_URL_TTL_SECONDS = 60 * 60 * 24;
7801
+ var SKIP_EXACT_PATHS = new Set([
7802
+ "/email/verify",
7803
+ "/verify-email",
7804
+ "/login",
7805
+ "/register",
7806
+ "/logout",
7807
+ "/forgot-password",
7808
+ "/reset-password",
7809
+ "/two-factor-challenge",
7810
+ "/confirm-password",
7811
+ "/api"
7812
+ ]);
7813
+ function intendedUrlCookieName() {
7814
+ return process.env.INTENDED_URL_COOKIE_NAME?.trim() || appCookieName("intended");
7815
+ }
7816
+ function intendedUrlTtlSeconds() {
7817
+ const parsed = Number.parseInt(process.env.INTENDED_URL_TTL_SECONDS ?? "", 10);
7818
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_INTENDED_URL_TTL_SECONDS;
7819
+ }
7820
+ function cookieSecureFlag() {
7821
+ return process.env.APP_ENV === "production" ? "; Secure" : "";
7822
+ }
7823
+ function pathnameOf(path) {
7824
+ const pathname = path.split("?")[0] ?? path;
7825
+ const trimmed = pathname.replace(/\/+$/, "");
7826
+ return trimmed || "/";
7827
+ }
7828
+ function isStashableIntendedPath(path) {
7829
+ if (!path.startsWith("/") || path.startsWith("//")) {
7830
+ return false;
7831
+ }
7832
+ const pathname = pathnameOf(path);
7833
+ if (SKIP_EXACT_PATHS.has(pathname) || pathname.startsWith("/oauth") || pathname.startsWith("/api/")) {
7834
+ return false;
7835
+ }
7836
+ return true;
7837
+ }
7838
+ function createIntendedUrlCookie(path) {
7839
+ const sanitized = sanitizeInternalPath(path, "");
7840
+ if (!sanitized || !isStashableIntendedPath(sanitized)) {
7841
+ return null;
7842
+ }
7843
+ return `${intendedUrlCookieName()}=${encodeURIComponent(sanitized)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${intendedUrlTtlSeconds()}${cookieSecureFlag()}`;
7844
+ }
7845
+ function createIntendedUrlCookieFromRequest(request) {
7846
+ if (request.method !== "GET" && request.method !== "HEAD") {
7847
+ return null;
7848
+ }
7849
+ return createIntendedUrlCookie(safeInternalRedirectPath(request, ""));
7850
+ }
7851
+
7270
7852
  // ../../src/core/http/requireWebAuthMiddleware.ts
7271
7853
  function createRequireWebAuthMiddleware(auth2) {
7272
7854
  return async (request, next) => {
@@ -7277,7 +7859,12 @@ function createRequireWebAuthMiddleware(auth2) {
7277
7859
  if (requestPrefersJson(request)) {
7278
7860
  throw new UnauthorizedError;
7279
7861
  }
7280
- return Response.redirect(loginRedirectLocation(request), 302);
7862
+ const intended = createIntendedUrlCookieFromRequest(request);
7863
+ const headers = new Headers({ Location: loginRedirectLocation(request) });
7864
+ if (intended) {
7865
+ headers.append("Set-Cookie", intended);
7866
+ }
7867
+ return new Response(null, { status: 302, headers });
7281
7868
  };
7282
7869
  }
7283
7870
  // ../../src/core/http/scimThrottleMiddleware.ts
@@ -7350,7 +7937,7 @@ function createSecurityHeadersMiddleware(options = {}) {
7350
7937
  };
7351
7938
  }
7352
7939
  // ../../src/core/http/signedUrl.ts
7353
- import { createHmac as createHmac2 } from "crypto";
7940
+ import { createHmac as createHmac3 } from "crypto";
7354
7941
  function resolveSignedUrlSecret() {
7355
7942
  return process.env.SIGNED_URL_SECRET?.trim() || process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || appDevSecret("signed-url-secret");
7356
7943
  }
@@ -7372,7 +7959,7 @@ function sortedQueryString(params) {
7372
7959
  return new URLSearchParams(entries).toString();
7373
7960
  }
7374
7961
  function signCanonicalPayload(path, query) {
7375
- return createHmac2("sha256", resolveSignedUrlSecret()).update(`${path}
7962
+ return createHmac3("sha256", resolveSignedUrlSecret()).update(`${path}
7376
7963
  ${query}`).digest("hex");
7377
7964
  }
7378
7965
  function buildSignedSearchParams(path, query = {}, expiresAt) {
@@ -7467,12 +8054,7 @@ function createThrottleMiddleware(options) {
7467
8054
  }
7468
8055
  const maxAttempts = options.maxAttempts * rateLimitMultiplierForPlan(currentTenant()?.plan ?? "free");
7469
8056
  if (attempts > maxAttempts) {
7470
- return Response.json({ error: "Too many requests." }, {
7471
- status: 429,
7472
- headers: {
7473
- "retry-after": String(options.decaySeconds)
7474
- }
7475
- });
8057
+ return await tooManyRequestsResponse(request, "Too many requests.", options.decaySeconds);
7476
8058
  }
7477
8059
  return await next();
7478
8060
  };
@@ -7890,9 +8472,9 @@ function readSharedJobRegistry() {
7890
8472
  if (globalRegistry) {
7891
8473
  return globalRegistry;
7892
8474
  }
7893
- const registry = new JobRegistry;
7894
- globalThis[JOB_REGISTRY_KEY] = registry;
7895
- return registry;
8475
+ const registry2 = new JobRegistry;
8476
+ globalThis[JOB_REGISTRY_KEY] = registry2;
8477
+ return registry2;
7896
8478
  }
7897
8479
  var jobRegistry = readSharedJobRegistry();
7898
8480
 
@@ -8787,6 +9369,7 @@ export {
8787
9369
  AuthManager,
8788
9370
  BadRequestError,
8789
9371
  baseRepository_default as BaseRepository,
9372
+ BasicAuthGuard,
8790
9373
  BelongsToManyRelationQuery,
8791
9374
  BelongsToRelationQuery,
8792
9375
  Blueprint,
@@ -8813,6 +9396,7 @@ export {
8813
9396
  HttpError,
8814
9397
  Job,
8815
9398
  JsonResource,
9399
+ JwtGuard,
8816
9400
  LocalStorageDriver,
8817
9401
  LogMailDriver,
8818
9402
  Mailer,
@@ -8898,6 +9482,8 @@ export {
8898
9482
  createMembershipMiddleware,
8899
9483
  createMemoryThrottleMiddleware,
8900
9484
  createMetricsMiddleware,
9485
+ createMysqlConnection,
9486
+ createMysqlConnectionFromPool,
8901
9487
  createNotificationDispatcher,
8902
9488
  createProductionQueue,
8903
9489
  createQueue,
@@ -8911,9 +9497,11 @@ export {
8911
9497
  createScimAuthMiddleware,
8912
9498
  createScimThrottleMiddleware,
8913
9499
  createSecurityHeadersMiddleware,
9500
+ createSqliteConnection,
8914
9501
  createStorageDriver,
8915
9502
  createTenantMiddleware,
8916
9503
  createThrottleMiddleware,
9504
+ createTokenAbilityChecker,
8917
9505
  createTracingMiddleware,
8918
9506
  createTrackedJob,
8919
9507
  createValidateSignatureMiddleware,
@@ -8922,11 +9510,13 @@ export {
8922
9510
  currentOrgRole,
8923
9511
  currentOrganizationIds,
8924
9512
  currentRequestMeta,
9513
+ currentSqlDialect,
8925
9514
  currentTenant,
8926
9515
  currentTenantId,
8927
9516
  currentTraceId,
8928
9517
  defineTable,
8929
9518
  dehydrateValue,
9519
+ dialectFor,
8930
9520
  emailRule,
8931
9521
  emptyPaginateResult,
8932
9522
  errorTemplateName,
@@ -8943,10 +9533,13 @@ export {
8943
9533
  getDefaultDatabasePool,
8944
9534
  getDefaultDatabaseQuery,
8945
9535
  getMigrationStatus,
9536
+ getNamedConnection,
8946
9537
  grammarForDriver,
8947
9538
  guestCanViewResource,
9539
+ hasActiveDatabaseConnection,
8948
9540
  hasMany,
8949
9541
  hasMinimumOrgRole2 as hasMinimumOrgRole,
9542
+ hasNamedConnection,
8950
9543
  hasOne,
8951
9544
  hasOrgMembership,
8952
9545
  hasValidSignature,
@@ -8971,6 +9564,7 @@ export {
8971
9564
  isTenancyEnabled,
8972
9565
  jobRegistry,
8973
9566
  jsonResponse2 as jsonResponse,
9567
+ jwtTtlSeconds,
8974
9568
  loadMigrationsFromDirectory,
8975
9569
  loadSeedersFromDirectory,
8976
9570
  log,
@@ -9008,6 +9602,7 @@ export {
9008
9602
  registerDefaultDatabasePool,
9009
9603
  registerModelClass,
9010
9604
  registerModelRepository,
9605
+ registerNamedConnection,
9011
9606
  registerShutdownHandler,
9012
9607
  renderKernelErrorChrome,
9013
9608
  renderMarkdownMail,
@@ -9017,6 +9612,8 @@ export {
9017
9612
  resetBoundDatabaseConnection,
9018
9613
  resetDefaultStorage,
9019
9614
  resetMemoryThrottleForTests,
9615
+ resetNamedConnections,
9616
+ resetSqlDialect,
9020
9617
  resolveApplicationAuth,
9021
9618
  resolveApplicationCache,
9022
9619
  resolveApplicationConfig,
@@ -9043,12 +9640,14 @@ export {
9043
9640
  runDueScheduledTasks,
9044
9641
  runGracefulShutdown,
9045
9642
  runInTransaction,
9643
+ runOnNamedConnection,
9046
9644
  runQueueJob,
9047
9645
  runSeedersFromDirectory,
9048
9646
  runWithAuthUser,
9049
9647
  runWithDatabaseConnection,
9050
9648
  runWithMembershipContext,
9051
9649
  runWithRequestMeta,
9650
+ runWithSqlDialect,
9052
9651
  runWithTenant,
9053
9652
  runWithTenantDatabase,
9054
9653
  runWithTraceContext,
@@ -9062,6 +9661,7 @@ export {
9062
9661
  serializeDate,
9063
9662
  serverHtmxContentSecurityPolicy,
9064
9663
  setActiveApplicationContext,
9664
+ signJwt,
9065
9665
  signedUrl,
9066
9666
  singularize,
9067
9667
  spaContentSecurityPolicy,
@@ -9075,8 +9675,11 @@ export {
9075
9675
  toPaginatedResourceCollection,
9076
9676
  toResourceCollection,
9077
9677
  trustForwardedFor,
9678
+ unregisterNamedConnection,
9679
+ useSqlDialect,
9078
9680
  validateObject,
9079
9681
  verifyCsrfToken,
9682
+ verifyJwt,
9080
9683
  whenLoaded,
9081
9684
  withErrorHandling,
9082
9685
  withMiddleware,