@getstrata/core 0.5.101 → 0.7.3

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 +63 -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 +839 -252
  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");
@@ -841,14 +1081,14 @@ function getDefaultDatabaseQuery() {
841
1081
  }
842
1082
 
843
1083
  // ../../src/core/security/timingSafeCompare.ts
844
- import { timingSafeEqual } from "crypto";
1084
+ import { timingSafeEqual as timingSafeEqual2 } from "crypto";
845
1085
  function timingSafeCompareString(left, right) {
846
1086
  const leftBuffer = Buffer.from(left);
847
1087
  const rightBuffer = Buffer.from(right);
848
1088
  if (leftBuffer.length !== rightBuffer.length) {
849
1089
  return false;
850
1090
  }
851
- return timingSafeEqual(leftBuffer, rightBuffer);
1091
+ return timingSafeEqual2(leftBuffer, rightBuffer);
852
1092
  }
853
1093
 
854
1094
  // ../../src/core/security/scimTenantTokens.ts
@@ -988,35 +1228,26 @@ function jsonScimError(detail, status) {
988
1228
  headers: { "content-type": "application/scim+json" }
989
1229
  });
990
1230
  }
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`;
1231
+ // ../../src/core/auth/tokenAbilityChecker.ts
1232
+ function tokenCan(user, ability) {
1233
+ if (!user) {
1234
+ return false;
1235
+ }
1236
+ const abilities = user.abilities ?? [];
1237
+ return abilities.includes("*") || abilities.includes(ability);
1014
1238
  }
1015
- function appEnv() {
1016
- return process.env.APP_ENV?.trim() || "local";
1239
+ function createTokenAbilityChecker() {
1240
+ return {
1241
+ tokenCan,
1242
+ requireAbility(user, ability) {
1243
+ if (!tokenCan(user, ability)) {
1244
+ throw new ForbiddenError(`Missing ability: ${ability}`);
1245
+ }
1246
+ }
1247
+ };
1017
1248
  }
1018
-
1019
1249
  // ../../src/core/cache/redisCacheStore.ts
1250
+ var {RedisClient } = globalThis.Bun;
1020
1251
  function cacheKeyPrefix() {
1021
1252
  return namespacedRedisKey("cache:");
1022
1253
  }
@@ -1455,7 +1686,13 @@ var CACHE_TAGS = {
1455
1686
  tasks: "tasks",
1456
1687
  comments: "comments",
1457
1688
  attachments: "attachments",
1458
- reports: "reports"
1689
+ reports: "reports",
1690
+ users: "users",
1691
+ departments: "departments",
1692
+ positions: "positions",
1693
+ applications: "applications",
1694
+ careers: "careers",
1695
+ offers: "offers"
1459
1696
  };
1460
1697
  // ../../src/core/contracts/container.ts
1461
1698
  class ServiceContainer {
@@ -1644,12 +1881,155 @@ async function withDatabaseErrorHandling(operation) {
1644
1881
  }
1645
1882
  }
1646
1883
 
1647
- // ../../src/core/database/query.ts
1648
- function quoteIdentifier(identifier) {
1884
+ // ../../src/core/database/schema/driver.ts
1885
+ function normalizeConnectionName(connection) {
1886
+ const normalized = connection.trim().toLowerCase();
1887
+ if (normalized === "pgsql" || normalized === "postgres" || normalized === "postgresql") {
1888
+ return "pgsql";
1889
+ }
1890
+ if (normalized === "mysql" || normalized === "mariadb") {
1891
+ return "mysql";
1892
+ }
1893
+ if (normalized === "sqlite") {
1894
+ return "sqlite";
1895
+ }
1896
+ throw new Error(`Unsupported DB_CONNECTION: ${connection}`);
1897
+ }
1898
+ function resolveDriverFromUrl(url) {
1899
+ const normalized = url.trim().toLowerCase();
1900
+ if (normalized.startsWith("postgres://") || normalized.startsWith("postgresql://") || normalized.startsWith("postgres:")) {
1901
+ return "pgsql";
1902
+ }
1903
+ if (normalized.startsWith("mysql://") || normalized.startsWith("mysql:")) {
1904
+ return "mysql";
1905
+ }
1906
+ if (normalized.startsWith("sqlite:")) {
1907
+ return "sqlite";
1908
+ }
1909
+ return null;
1910
+ }
1911
+ function resolveDatabaseDriver(options = {}) {
1912
+ const connection = options.connection ?? process.env.DB_CONNECTION;
1913
+ if (connection) {
1914
+ return normalizeConnectionName(connection);
1915
+ }
1916
+ const url = options.url ?? process.env.DATABASE_URL ?? "";
1917
+ const fromUrl = resolveDriverFromUrl(url);
1918
+ if (fromUrl) {
1919
+ return fromUrl;
1920
+ }
1921
+ return "pgsql";
1922
+ }
1923
+
1924
+ // ../../src/core/database/dialect.ts
1925
+ function assertSafeIdentifier(identifier) {
1649
1926
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(identifier)) {
1650
1927
  throw new Error(`Invalid SQL identifier: ${identifier}`);
1651
1928
  }
1652
- return `"${identifier}"`;
1929
+ return identifier;
1930
+ }
1931
+ var postgresDialect = {
1932
+ driver: "pgsql",
1933
+ placeholder(index) {
1934
+ return `$${index}`;
1935
+ },
1936
+ quoteIdentifier(identifier) {
1937
+ return `"${assertSafeIdentifier(identifier)}"`;
1938
+ },
1939
+ nowExpression() {
1940
+ return "NOW()";
1941
+ },
1942
+ returningClause(columns) {
1943
+ return ` RETURNING ${columns}`;
1944
+ },
1945
+ ilikeOperator() {
1946
+ return "ILIKE";
1947
+ },
1948
+ nullsLastSuffix() {
1949
+ return " NULLS LAST";
1950
+ },
1951
+ castToText(expression) {
1952
+ return `${expression}::text`;
1953
+ }
1954
+ };
1955
+ var mysqlDialect = {
1956
+ driver: "mysql",
1957
+ placeholder() {
1958
+ return "?";
1959
+ },
1960
+ quoteIdentifier(identifier) {
1961
+ return `\`${assertSafeIdentifier(identifier)}\``;
1962
+ },
1963
+ nowExpression() {
1964
+ return "CURRENT_TIMESTAMP";
1965
+ },
1966
+ returningClause() {
1967
+ return "";
1968
+ },
1969
+ ilikeOperator() {
1970
+ return "LIKE";
1971
+ },
1972
+ nullsLastSuffix() {
1973
+ return "";
1974
+ },
1975
+ castToText(expression) {
1976
+ return `CAST(${expression} AS CHAR)`;
1977
+ }
1978
+ };
1979
+ var sqliteDialect = {
1980
+ driver: "sqlite",
1981
+ placeholder() {
1982
+ return "?";
1983
+ },
1984
+ quoteIdentifier(identifier) {
1985
+ return `"${assertSafeIdentifier(identifier)}"`;
1986
+ },
1987
+ nowExpression() {
1988
+ return "CURRENT_TIMESTAMP";
1989
+ },
1990
+ returningClause(columns) {
1991
+ return ` RETURNING ${columns}`;
1992
+ },
1993
+ ilikeOperator() {
1994
+ return "LIKE";
1995
+ },
1996
+ nullsLastSuffix() {
1997
+ return "";
1998
+ },
1999
+ castToText(expression) {
2000
+ return `CAST(${expression} AS TEXT)`;
2001
+ }
2002
+ };
2003
+ var dialects = {
2004
+ pgsql: postgresDialect,
2005
+ mysql: mysqlDialect,
2006
+ sqlite: sqliteDialect
2007
+ };
2008
+ var dialectContext = createAsyncContextStore("@getstrata/sqlDialect");
2009
+ var dialectOverride = null;
2010
+ function dialectFor(driver) {
2011
+ return dialects[driver];
2012
+ }
2013
+ function currentSqlDialect() {
2014
+ return dialectContext.getStore() ?? dialectOverride ?? dialectFor(resolveDatabaseDriver());
2015
+ }
2016
+ function useSqlDialect(driver) {
2017
+ dialectOverride = dialectFor(driver);
2018
+ return dialectOverride;
2019
+ }
2020
+ function resetSqlDialect() {
2021
+ dialectOverride = null;
2022
+ }
2023
+ function runWithSqlDialect(driver, callback) {
2024
+ return dialectContext.run(dialectFor(driver), callback);
2025
+ }
2026
+
2027
+ // ../../src/core/database/query.ts
2028
+ function quoteIdentifier(identifier) {
2029
+ return currentSqlDialect().quoteIdentifier(identifier);
2030
+ }
2031
+ function returningSuffix(columns) {
2032
+ return currentSqlDialect().returningClause(columns);
1653
2033
  }
1654
2034
  function qualifyColumn(tableName, column) {
1655
2035
  return `${quoteIdentifier(tableName)}.${quoteIdentifier(column)}`;
@@ -1679,7 +2059,7 @@ function isQueryOperator(value) {
1679
2059
  }
1680
2060
  function pushParam(values, value) {
1681
2061
  values.push(value);
1682
- return `$${values.length}`;
2062
+ return currentSqlDialect().placeholder(values.length);
1683
2063
  }
1684
2064
  function buildInClause(column, values, params) {
1685
2065
  if (values.length === 0) {
@@ -1719,9 +2099,12 @@ function buildOperatorClauses(column, operator, params) {
1719
2099
  clauses.push(`${column} <= ${pushParam(params, operator.lte)}`);
1720
2100
  }
1721
2101
  if (operator.ilike !== undefined) {
1722
- clauses.push(`${column} ILIKE ${pushParam(params, operator.ilike)}`);
2102
+ clauses.push(`${column} ${currentSqlDialect().ilikeOperator()} ${pushParam(params, operator.ilike)}`);
1723
2103
  }
1724
2104
  if (operator.tsMatch !== undefined) {
2105
+ if (currentSqlDialect().driver !== "pgsql") {
2106
+ throw new Error("Full-text search (tsMatch) is only available on PostgreSQL.");
2107
+ }
1725
2108
  clauses.push(`${column} @@ plainto_tsquery('english', ${pushParam(params, operator.tsMatch)})`);
1726
2109
  }
1727
2110
  return clauses;
@@ -1912,7 +2295,7 @@ function buildSelectList(table, select, params = []) {
1912
2295
  return item.as ? `${column2} AS ${quoteIdentifier(item.as)}` : column2;
1913
2296
  }
1914
2297
  if (item.kind === "literalText") {
1915
- return `${pushParam(params, item.value)}::text AS ${quoteIdentifier(item.as)}`;
2298
+ return `${currentSqlDialect().castToText(pushParam(params, item.value))} AS ${quoteIdentifier(item.as)}`;
1916
2299
  }
1917
2300
  const column = qualifyColumn(item.table, item.column);
1918
2301
  const placeholder = pushParam(params, item.query);
@@ -2004,7 +2387,7 @@ function buildInsertQuery(table, values) {
2004
2387
  const placeholders = entries.map(([, value]) => pushParam(params, value)).join(", ");
2005
2388
  const returningColumns = buildReturningColumns(table);
2006
2389
  return {
2007
- text: `INSERT INTO ${quoteIdentifier(table.name)} (${columns}) VALUES (${placeholders}) RETURNING ${returningColumns}`,
2390
+ text: `INSERT INTO ${quoteIdentifier(table.name)} (${columns}) VALUES (${placeholders})${returningSuffix(returningColumns)}`,
2008
2391
  params
2009
2392
  };
2010
2393
  }
@@ -2023,7 +2406,7 @@ function buildUpdateQuery(table, id, changes) {
2023
2406
  appendSoftDeleteScope(table, {}, scopeClauses);
2024
2407
  const scopeSuffix = scopeClauses.length > 0 ? ` AND ${scopeClauses.join(" AND ")}` : "";
2025
2408
  return {
2026
- text: `UPDATE ${quoteIdentifier(table.name)} SET ${setClause} WHERE ${quoteIdentifier(table.primaryKey)} = ${primaryKeyPlaceholder}${scopeSuffix} RETURNING ${returningColumns}`,
2409
+ text: `UPDATE ${quoteIdentifier(table.name)} SET ${setClause} WHERE ${quoteIdentifier(table.primaryKey)} = ${primaryKeyPlaceholder}${scopeSuffix}${returningSuffix(returningColumns)}`,
2027
2410
  params
2028
2411
  };
2029
2412
  }
@@ -2036,9 +2419,12 @@ function buildSoftDeleteByIdQuery(table, id, deletedAt) {
2036
2419
  const scopeClauses = [];
2037
2420
  appendSoftDeleteScope(table, {}, scopeClauses);
2038
2421
  const scopeSuffix = scopeClauses.length > 0 ? ` AND ${scopeClauses.join(" AND ")}` : "";
2422
+ const params = [];
2423
+ const deletedAtPlaceholder = pushParam(params, deletedAt);
2424
+ const idPlaceholder = pushParam(params, id);
2039
2425
  return {
2040
- text: `UPDATE ${quoteIdentifier(table.name)} SET ${quoteIdentifier(deletedAtColumn)} = $1 WHERE ${quoteIdentifier(table.primaryKey)} = $2${scopeSuffix} RETURNING ${returningColumns}`,
2041
- params: [deletedAt, id]
2426
+ text: `UPDATE ${quoteIdentifier(table.name)} SET ${quoteIdentifier(deletedAtColumn)} = ${deletedAtPlaceholder} WHERE ${quoteIdentifier(table.primaryKey)} = ${idPlaceholder}${scopeSuffix}${returningSuffix(returningColumns)}`,
2427
+ params
2042
2428
  };
2043
2429
  }
2044
2430
  function buildRestoreByIdQuery(table, id) {
@@ -2047,15 +2433,21 @@ function buildRestoreByIdQuery(table, id) {
2047
2433
  throw new Error(`Table ${table.name} does not support soft deletes.`);
2048
2434
  }
2049
2435
  const returningColumns = buildReturningColumns(table);
2436
+ const params = [];
2437
+ const deletedAtPlaceholder = pushParam(params, null);
2438
+ const idPlaceholder = pushParam(params, id);
2050
2439
  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]
2440
+ text: `UPDATE ${quoteIdentifier(table.name)} SET ${quoteIdentifier(deletedAtColumn)} = ${deletedAtPlaceholder} WHERE ${quoteIdentifier(table.primaryKey)} = ${idPlaceholder} AND ${qualifyColumn(table.name, deletedAtColumn)} IS NOT NULL${returningSuffix(returningColumns)}`,
2441
+ params
2053
2442
  };
2054
2443
  }
2055
2444
  function buildDeleteByIdQuery(table, id) {
2445
+ const params = [];
2446
+ const idPlaceholder = pushParam(params, id);
2447
+ const returning = returningSuffix(`${quoteIdentifier(table.primaryKey)} AS ${quoteIdentifier("deleted_id")}`);
2056
2448
  return {
2057
- text: `DELETE FROM ${quoteIdentifier(table.name)} WHERE ${quoteIdentifier(table.primaryKey)} = $1 RETURNING ${quoteIdentifier(table.primaryKey)} AS ${quoteIdentifier("deleted_id")}`,
2058
- params: [id]
2449
+ text: `DELETE FROM ${quoteIdentifier(table.name)} WHERE ${quoteIdentifier(table.primaryKey)} = ${idPlaceholder}${returning}`,
2450
+ params
2059
2451
  };
2060
2452
  }
2061
2453
 
@@ -2106,6 +2498,19 @@ function relationMatchKey(value) {
2106
2498
  }
2107
2499
  return String(value);
2108
2500
  }
2501
+ var relationLookupCache = new WeakMap;
2502
+ function indexedRelationLookup(map) {
2503
+ const cached = relationLookupCache.get(map);
2504
+ if (cached) {
2505
+ return cached;
2506
+ }
2507
+ const indexed = new Map;
2508
+ for (const [existing, value] of map) {
2509
+ indexed.set(relationMatchKey(existing), value);
2510
+ }
2511
+ relationLookupCache.set(map, indexed);
2512
+ return indexed;
2513
+ }
2109
2514
  function getByRelationKey(map, key) {
2110
2515
  if (map.has(key)) {
2111
2516
  return map.get(key);
@@ -2114,12 +2519,7 @@ function getByRelationKey(map, key) {
2114
2519
  if (want === "") {
2115
2520
  return;
2116
2521
  }
2117
- for (const [existing, value] of map) {
2118
- if (relationMatchKey(existing) === want) {
2119
- return value;
2120
- }
2121
- }
2122
- return;
2522
+ return indexedRelationLookup(map).get(want);
2123
2523
  }
2124
2524
  function indexHasManyRelation(parents, children, relation) {
2125
2525
  const groups = new Map;
@@ -2532,73 +2932,67 @@ class RepositoryQuery {
2532
2932
  return this;
2533
2933
  }
2534
2934
  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;
2935
+ const result = rows.map((row) => ({ ...row }));
2936
+ if (result.length === 0 || this.eagerLoads.length === 0) {
2937
+ return result;
2938
+ }
2939
+ await Promise.all(this.eagerLoads.map((load) => this.hydrateEagerLoad(rows, result, load)));
2940
+ return result;
2941
+ }
2942
+ async hydrateEagerLoad(rows, result, load) {
2943
+ if (load.kind === "hasMany") {
2944
+ const relation2 = load.relation;
2945
+ const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadHasManyForParents(rows, relation2, load.options);
2946
+ for (const row of result) {
2947
+ row[load.as] = getByRelationKey(grouped2, row[relation2.localKey]) ?? [];
2548
2948
  }
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;
2949
+ return;
2950
+ }
2951
+ if (load.kind === "morphMany") {
2952
+ const relation2 = load.relation;
2953
+ const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphManyForParents(rows, relation2, load.options);
2954
+ for (const row of result) {
2955
+ row[load.as] = getByRelationKey(grouped2, row[relation2.localKey]) ?? [];
2557
2956
  }
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;
2957
+ return;
2958
+ }
2959
+ if (load.kind === "morphOne") {
2960
+ const relation2 = load.relation;
2961
+ const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadMorphOneForParents(rows, relation2, load.options);
2962
+ for (const row of result) {
2963
+ row[load.as] = getByRelationKey(grouped2, row[relation2.localKey]);
2566
2964
  }
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;
2965
+ return;
2966
+ }
2967
+ if (load.kind === "hasManyThrough") {
2968
+ const relation2 = load.relation;
2969
+ const grouped2 = await load.repository.withConnection(this.repository.getConnection()).loadHasManyThroughForParents(rows, relation2, load.options);
2970
+ for (const row of result) {
2971
+ row[load.as] = getByRelationKey(grouped2, row[relation2.localKey]) ?? [];
2575
2972
  }
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;
2973
+ return;
2974
+ }
2975
+ if (load.kind === "belongsToMany") {
2976
+ const relation2 = load.relation;
2977
+ const grouped2 = await this.repository.loadBelongsToManyForParents(rows, relation2, load.repository, load.options);
2978
+ for (const row of result) {
2979
+ row[load.as] = getByRelationKey(grouped2, row[relation2.parentKey]) ?? [];
2584
2980
  }
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;
2981
+ return;
2982
+ }
2983
+ if (load.kind === "morphTo") {
2984
+ const relation2 = load.relation;
2985
+ const grouped2 = await this.repository.loadMorphToForChildren(rows, relation2, load.morphRepositories ?? new Map, load.options);
2986
+ for (const row of result) {
2987
+ row[load.as] = getByRelationKey(grouped2, row[relation2.morphIdKey]);
2593
2988
  }
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
- }));
2989
+ return;
2990
+ }
2991
+ const relation = load.relation;
2992
+ const grouped = await this.repository.loadBelongsToForParents(rows, relation, load.repository, load.options);
2993
+ for (const row of result) {
2994
+ row[load.as] = getByRelationKey(grouped, row[relation.foreignKey]);
2600
2995
  }
2601
- return result;
2602
2996
  }
2603
2997
  }
2604
2998
 
@@ -2953,10 +3347,10 @@ class BaseRepository {
2953
3347
  idsByType.set(morphType, ids);
2954
3348
  }
2955
3349
  const parentsByType = new Map;
2956
- for (const [morphType, ids] of idsByType) {
3350
+ await Promise.all([...idsByType.entries()].map(async ([morphType, ids]) => {
2957
3351
  const repository = repositoriesByType.get(morphType);
2958
3352
  if (!repository) {
2959
- continue;
3353
+ return;
2960
3354
  }
2961
3355
  const ownerKey = repository.getTable().primaryKey;
2962
3356
  const parents = await repository.withConnection(this.connection).findWhere({
@@ -2967,7 +3361,7 @@ class BaseRepository {
2967
3361
  indexed.set(relationMatchKey(parent[ownerKey]), parent);
2968
3362
  }
2969
3363
  parentsByType.set(morphType, indexed);
2970
- }
3364
+ }));
2971
3365
  return indexMorphToRelation(children, parentsByType, relation);
2972
3366
  }
2973
3367
  async loadBelongsToManyForParents(parents, relation, relatedRepository, options = {}) {
@@ -3169,10 +3563,11 @@ class Factory {
3169
3563
  return made;
3170
3564
  }
3171
3565
  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
- }
3566
+ return this.persistCreated(this.makeOne(overrides));
3567
+ }
3568
+ async persistCreated(record) {
3569
+ const created = await this.persist(this.insertable(record));
3570
+ await Promise.all(this.children.map((child) => child.factory.for(created, child.foreignKey).create()));
3176
3571
  for (const callback of this.afterCreatingCallbacks) {
3177
3572
  await callback(created);
3178
3573
  }
@@ -3410,11 +3805,7 @@ class HasManyRelationQuery {
3410
3805
  return this.create(related);
3411
3806
  }
3412
3807
  async createMany(records) {
3413
- const created = [];
3414
- for (const attributes of records) {
3415
- created.push(await this.create(attributes));
3416
- }
3417
- return created;
3808
+ return Promise.all(records.map((attributes) => this.create(attributes)));
3418
3809
  }
3419
3810
  }
3420
3811
 
@@ -3920,12 +4311,12 @@ async function eagerLoadOnModels(models, paths) {
3920
4311
  }
3921
4312
  grouped.set(head, existing);
3922
4313
  }
3923
- for (const [head, nested] of grouped) {
4314
+ await Promise.all([...grouped.entries()].map(async ([head, nested]) => {
3924
4315
  const unloaded = models.filter((model) => model.loaded(head) === undefined);
3925
4316
  if (unloaded.length > 0) {
3926
4317
  const first = unloaded[0];
3927
4318
  if (!first) {
3928
- continue;
4319
+ return;
3929
4320
  }
3930
4321
  const method = first[head];
3931
4322
  if (typeof method !== "function") {
@@ -3941,14 +4332,14 @@ async function eagerLoadOnModels(models, paths) {
3941
4332
  }
3942
4333
  }
3943
4334
  if (nested.length === 0) {
3944
- continue;
4335
+ return;
3945
4336
  }
3946
4337
  const children = models.flatMap((model) => {
3947
4338
  const loaded = model.loaded(head);
3948
4339
  return Array.isArray(loaded) ? loaded : loaded ? [loaded] : [];
3949
4340
  });
3950
4341
  await eagerLoadOnModels(children.filter(isLoadableModel), nested);
3951
- }
4342
+ }));
3952
4343
  }
3953
4344
  async function loadNested(model, path) {
3954
4345
  await eagerLoadOnModels([model], [path]);
@@ -4226,12 +4617,12 @@ class ModelQuery {
4226
4617
  const models = [];
4227
4618
  for (const row of rows) {
4228
4619
  const model = statics.newFromRecord(row, true);
4229
- await runObservers(model, "retrieved");
4230
4620
  for (const { name, relationQuery } of this.eager) {
4231
4621
  model.setLoaded(name, relationQuery.hydrateEager(row, name));
4232
4622
  }
4233
4623
  models.push(model);
4234
4624
  }
4625
+ await Promise.all(models.map((model) => runObservers(model, "retrieved")));
4235
4626
  const nested = this.eager.filter((item) => item.path.includes(".")).map((item) => item.path);
4236
4627
  await eagerLoadOnModels(models.filter(isLoadableModel), nested);
4237
4628
  return models;
@@ -4688,7 +5079,7 @@ class Model {
4688
5079
  morphTo(relatedByType, morphName, typeKey, idKey) {
4689
5080
  const resolvedName = morphName ?? inferRelationMethodName("morphTo");
4690
5081
  if (!resolvedName) {
4691
- throw new Error(`${this.constructor.name}.morphTo() needs an explicit morph name (Laravel infers it from the relation method).`);
5082
+ throw new Error(`${this.constructor.name}.morphTo() needs an explicit morph name (the calling method name is not available at runtime).`);
4692
5083
  }
4693
5084
  const resolvedMap = Object.fromEntries(Object.entries(relatedByType).map(([type, related]) => [type, resolveRelated(related)]));
4694
5085
  return new MorphToRelationQuery(this, resolvedMap, morphTo({
@@ -4737,6 +5128,74 @@ function registerModelRepository(model, repository) {
4737
5128
  ensureBooted(model);
4738
5129
  return model;
4739
5130
  }
5131
+ // ../../src/core/database/mysqlConnection.ts
5132
+ import mysql from "mysql2/promise";
5133
+ function rowsFromResult(result) {
5134
+ if (Array.isArray(result)) {
5135
+ return result;
5136
+ }
5137
+ if (result && typeof result === "object") {
5138
+ return [result];
5139
+ }
5140
+ return [];
5141
+ }
5142
+ function createMysqlConnectionFromPool(pool) {
5143
+ return {
5144
+ async unsafe(query, params = []) {
5145
+ const [result] = await pool.execute(query, [...params]);
5146
+ return rowsFromResult(result);
5147
+ },
5148
+ async close() {
5149
+ if (typeof pool.end === "function") {
5150
+ await pool.end();
5151
+ }
5152
+ }
5153
+ };
5154
+ }
5155
+ function createMysqlConnection(url) {
5156
+ if (!url.trim()) {
5157
+ throw new Error("MYSQL_URL is not configured. Set url before creating a MySQL pool.");
5158
+ }
5159
+ return createMysqlConnectionFromPool(mysql.createPool(url));
5160
+ }
5161
+ // ../../src/core/database/namedConnections.ts
5162
+ var REGISTRY_KEY = Symbol.for("@getstrata/namedConnections");
5163
+ function registry() {
5164
+ const globalRecord = globalThis;
5165
+ const existing = globalRecord[REGISTRY_KEY];
5166
+ if (existing) {
5167
+ return existing;
5168
+ }
5169
+ const created = new Map;
5170
+ globalRecord[REGISTRY_KEY] = created;
5171
+ return created;
5172
+ }
5173
+ function registerNamedConnection(name, driver, connection) {
5174
+ if (!name.trim()) {
5175
+ throw new Error("Named database connection requires a non-empty name.");
5176
+ }
5177
+ registry().set(name, { name, driver, connection });
5178
+ }
5179
+ function unregisterNamedConnection(name) {
5180
+ return registry().delete(name);
5181
+ }
5182
+ function hasNamedConnection(name) {
5183
+ return registry().has(name);
5184
+ }
5185
+ function getNamedConnection(name) {
5186
+ const entry = registry().get(name);
5187
+ if (!entry) {
5188
+ throw new Error(`Named database connection "${name}" is not registered.`);
5189
+ }
5190
+ return entry;
5191
+ }
5192
+ function resetNamedConnections() {
5193
+ registry().clear();
5194
+ }
5195
+ function runOnNamedConnection(name, callback) {
5196
+ const entry = getNamedConnection(name);
5197
+ return runWithSqlDialect(entry.driver, () => runWithDatabaseConnection(entry.connection, callback));
5198
+ }
4740
5199
  // ../../src/core/database/schema/columnDefinition.ts
4741
5200
  class ColumnDefinition {
4742
5201
  name;
@@ -4958,45 +5417,6 @@ class Blueprint {
4958
5417
  });
4959
5418
  }
4960
5419
  }
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
5420
  // ../../src/core/database/schema/errors.ts
5001
5421
  class UnsupportedSchemaFeatureError extends Error {
5002
5422
  constructor(feature, driver) {
@@ -5385,6 +5805,36 @@ async function runSeedersFromDirectory(directory, db, options) {
5385
5805
  }
5386
5806
  return seeders.length;
5387
5807
  }
5808
+ // ../../src/core/database/sqliteConnection.ts
5809
+ import { Database } from "bun:sqlite";
5810
+ function isRowReturning(sql) {
5811
+ const upper = sql.replace(/\s+/g, " ").trim().toUpperCase();
5812
+ if (upper.includes(" RETURNING ")) {
5813
+ return true;
5814
+ }
5815
+ return upper.startsWith("SELECT") || upper.startsWith("WITH") || upper.startsWith("PRAGMA") || upper.startsWith("EXPLAIN");
5816
+ }
5817
+ function createSqliteConnection(filename) {
5818
+ if (!filename.trim()) {
5819
+ throw new Error("SQLite path is not configured. Pass a filename or :memory:.");
5820
+ }
5821
+ const db = new Database(filename, { create: true });
5822
+ db.exec("PRAGMA foreign_keys = ON");
5823
+ return {
5824
+ async unsafe(query, params = []) {
5825
+ const statement = db.query(query);
5826
+ const args = [...params];
5827
+ if (isRowReturning(query)) {
5828
+ return statement.all(...args);
5829
+ }
5830
+ statement.run(...args);
5831
+ return [];
5832
+ },
5833
+ close() {
5834
+ db.close();
5835
+ }
5836
+ };
5837
+ }
5388
5838
  // ../../src/core/database/table.ts
5389
5839
  function defineTable(definition) {
5390
5840
  return definition;
@@ -5563,7 +6013,7 @@ class LogMailDriver {
5563
6013
  to: message.to,
5564
6014
  subject: message.subject,
5565
6015
  body: message.body,
5566
- ...message.html ? { html: message.html } : {}
6016
+ ...message.html ? { htmlBytes: Buffer.byteLength(message.html, "utf8") } : {}
5567
6017
  }));
5568
6018
  }
5569
6019
  }
@@ -5918,6 +6368,32 @@ function conditionalJsonResponse(request, data, init = {}) {
5918
6368
  headers
5919
6369
  });
5920
6370
  }
6371
+ // ../../src/core/runtime/frontendMode.ts
6372
+ var FRONTEND_MODES = ["api", "server-htmx", "spa-react", "hybrid"];
6373
+ var FRONTEND_MODE_PATTERN = new RegExp(`^(${FRONTEND_MODES.join("|")})$`);
6374
+ function parseFrontendMode(value) {
6375
+ const mode = (value ?? "api").trim();
6376
+ if (mode === "server-htmx" || mode === "spa-react" || mode === "hybrid") {
6377
+ return mode;
6378
+ }
6379
+ return "api";
6380
+ }
6381
+ function readFrontendMode() {
6382
+ return parseFrontendMode(process.env.FRONTEND_MODE);
6383
+ }
6384
+ function isViewsMode(mode) {
6385
+ return mode === "server-htmx" || mode === "hybrid";
6386
+ }
6387
+ function isSpaMode(mode) {
6388
+ return mode === "spa-react" || mode === "hybrid";
6389
+ }
6390
+ function isViewsEnabled() {
6391
+ return isViewsMode(readFrontendMode());
6392
+ }
6393
+ function isSpaEnabled() {
6394
+ return isSpaMode(readFrontendMode());
6395
+ }
6396
+
5921
6397
  // ../../src/core/http/contentSecurityPolicy.ts
5922
6398
  var HTMX_2_0_4_INDICATOR_STYLE_HASH = "'sha256-bsV5JivYxvGywDAZ22EZJKBFip65Ng9xoJVLbBg7bdo='";
5923
6399
  var API_DIRECTIVES = {
@@ -6005,11 +6481,10 @@ function applyNonce(directives, nonce) {
6005
6481
  return next;
6006
6482
  }
6007
6483
  function htmlBaselineDirectives() {
6008
- const frontendMode = (process.env.FRONTEND_MODE ?? "api").trim();
6009
- if (frontendMode === "server-htmx") {
6484
+ if (isViewsEnabled()) {
6010
6485
  return cloneDirectives(HTMX_HTML_DIRECTIVES);
6011
6486
  }
6012
- if (frontendMode === "spa-react") {
6487
+ if (isSpaEnabled()) {
6013
6488
  return cloneDirectives(SPA_HTML_DIRECTIVES);
6014
6489
  }
6015
6490
  return cloneDirectives(API_DIRECTIVES);
@@ -6124,7 +6599,7 @@ function buildCorsHeaders(request) {
6124
6599
  return headers;
6125
6600
  }
6126
6601
  // ../../src/core/http/csrfToken.ts
6127
- import { timingSafeEqual as timingSafeEqual2 } from "crypto";
6602
+ import { timingSafeEqual as timingSafeEqual3 } from "crypto";
6128
6603
 
6129
6604
  // ../../src/core/http/requestMetaContext.ts
6130
6605
  var requestMetaContext = createAsyncContextStore("@getstrata/requestMetaContext");
@@ -6156,7 +6631,7 @@ function tokensMatch(left, right) {
6156
6631
  if (leftBuffer.length !== rightBuffer.length) {
6157
6632
  return false;
6158
6633
  }
6159
- return timingSafeEqual2(leftBuffer, rightBuffer);
6634
+ return timingSafeEqual3(leftBuffer, rightBuffer);
6160
6635
  }
6161
6636
  function createCsrfTokenCookie() {
6162
6637
  const token = Bun.CSRF.generate(resolveCsrfSecret(), { expiresIn: CSRF_TTL_MS });
@@ -6233,6 +6708,9 @@ function appendSetCookie(response, cookie) {
6233
6708
  }
6234
6709
  function createCsrfMiddleware() {
6235
6710
  return async (request, next) => {
6711
+ if (requestUsesHeaderCredentials(request)) {
6712
+ return await next();
6713
+ }
6236
6714
  const method = request.method.toUpperCase();
6237
6715
  if (!MUTATING_METHODS.has(method)) {
6238
6716
  const csrf = resolveCsrfToken(request);
@@ -6270,7 +6748,7 @@ function createCsrfProtection(secret, options = {}) {
6270
6748
  };
6271
6749
  }
6272
6750
  // ../../src/core/http/flashSession.ts
6273
- import { createHmac, timingSafeEqual as timingSafeEqual3 } from "crypto";
6751
+ import { createHmac as createHmac2, timingSafeEqual as timingSafeEqual4 } from "crypto";
6274
6752
  var FLASH_COOKIE = appCookieName("flash");
6275
6753
  var FLASH_TTL_MS = 60 * 1000;
6276
6754
  function flashCookieName() {
@@ -6280,7 +6758,7 @@ function resolveFlashSecret() {
6280
6758
  return process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || appDevSecret("flash-secret");
6281
6759
  }
6282
6760
  function signFlashPayload(payload, issuedAt) {
6283
- const signature = createHmac("sha256", resolveFlashSecret()).update(`${payload}.${issuedAt}`).digest("hex");
6761
+ const signature = createHmac2("sha256", resolveFlashSecret()).update(`${payload}.${issuedAt}`).digest("hex");
6284
6762
  return `${payload}.${issuedAt}.${signature}`;
6285
6763
  }
6286
6764
  function readFlashCookie(request) {
@@ -6320,7 +6798,7 @@ function parseFlashCookie(cookieValue) {
6320
6798
  if (expectedBuffer.length !== actualBuffer.length) {
6321
6799
  return null;
6322
6800
  }
6323
- if (!timingSafeEqual3(expectedBuffer, actualBuffer)) {
6801
+ if (!timingSafeEqual4(expectedBuffer, actualBuffer)) {
6324
6802
  return null;
6325
6803
  }
6326
6804
  try {
@@ -6630,6 +7108,9 @@ function whenLoaded(model, relation, transform) {
6630
7108
  if (value === undefined) {
6631
7109
  return;
6632
7110
  }
7111
+ if (value === null) {
7112
+ return null;
7113
+ }
6633
7114
  return transform ? transform(value) : value;
6634
7115
  }
6635
7116
 
@@ -6655,7 +7136,7 @@ class JsonResource {
6655
7136
  }
6656
7137
  whenLoaded(relation, transform) {
6657
7138
  const model = this.resource;
6658
- if (typeof model.loaded !== "function") {
7139
+ if (this.resource == null || typeof model.loaded !== "function") {
6659
7140
  return;
6660
7141
  }
6661
7142
  return whenLoaded(model, relation, transform);
@@ -6698,21 +7179,6 @@ function toPaginatedResourceCollection(items, meta, transformer) {
6698
7179
  meta
6699
7180
  };
6700
7181
  }
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
7182
  // ../../src/core/view/htmlResponse.ts
6717
7183
  function withCharset(contentType) {
6718
7184
  return contentType.includes("charset=") ? contentType : `${contentType}; charset=utf-8`;
@@ -6840,6 +7306,10 @@ function requestPrefersJson(request) {
6840
7306
  if (request.headers.get("HX-Request") === "true") {
6841
7307
  return false;
6842
7308
  }
7309
+ const pathname = new URL(request.url).pathname;
7310
+ if (pathname.startsWith("/api/")) {
7311
+ return true;
7312
+ }
6843
7313
  const accept = request.headers.get("accept")?.toLowerCase() ?? "";
6844
7314
  if (accept.includes("text/html")) {
6845
7315
  return false;
@@ -6851,8 +7321,7 @@ function requestPrefersJson(request) {
6851
7321
  if (contentType.includes("application/x-www-form-urlencoded") || contentType.includes("multipart/form-data")) {
6852
7322
  return false;
6853
7323
  }
6854
- const pathname = new URL(request.url).pathname;
6855
- return pathname.startsWith("/api/");
7324
+ return false;
6856
7325
  }
6857
7326
 
6858
7327
  // ../../src/core/http/safeInternalPath.ts
@@ -7059,6 +7528,29 @@ function securedBindRouteModelByKey(param, resolver, authorization, handler) {
7059
7528
  }
7060
7529
  // ../../src/core/http/loginThrottleMiddleware.ts
7061
7530
  var {RedisClient: RedisClient2 } = globalThis.Bun;
7531
+
7532
+ // ../../src/core/http/throttleResponse.ts
7533
+ async function tooManyRequestsResponse(request, message, decaySeconds) {
7534
+ const retryAfter = { "retry-after": String(decaySeconds) };
7535
+ if (requestPrefersJson(request) || !isViewsEnabled()) {
7536
+ return Response.json({ error: message }, {
7537
+ status: 429,
7538
+ headers: retryAfter
7539
+ });
7540
+ }
7541
+ const html = await htmlErrorResponse({
7542
+ status: 429,
7543
+ title: "Too Many Requests",
7544
+ message,
7545
+ request
7546
+ });
7547
+ const headers = new Headers(html.headers);
7548
+ headers.set("retry-after", String(decaySeconds));
7549
+ return new Response(html.body, { status: 429, headers });
7550
+ }
7551
+
7552
+ // ../../src/core/http/loginThrottleMiddleware.ts
7553
+ var memoryLoginBuckets = new Map;
7062
7554
  function resolveLoginIdentity(request) {
7063
7555
  return readClientIp(request) ?? "unknown";
7064
7556
  }
@@ -7076,7 +7568,30 @@ async function resolveLoginEmail(request) {
7076
7568
  return "unknown";
7077
7569
  }
7078
7570
  }
7079
- function createLoginThrottleMiddleware(options) {
7571
+ function consumeMemoryAttempt(key, decaySeconds) {
7572
+ const now = Date.now();
7573
+ const existing = memoryLoginBuckets.get(key);
7574
+ if (!existing || existing.resetAt <= now) {
7575
+ memoryLoginBuckets.set(key, { count: 1, resetAt: now + decaySeconds * 1000 });
7576
+ return 1;
7577
+ }
7578
+ existing.count += 1;
7579
+ return existing.count;
7580
+ }
7581
+ function createMemoryLoginThrottleMiddleware(options) {
7582
+ const prefix = options.keyPrefix ?? namespacedRedisKey("login-throttle:");
7583
+ return async (request, next) => {
7584
+ const identity = resolveLoginIdentity(request);
7585
+ const email = await resolveLoginEmail(request);
7586
+ const throttleKey = `${prefix}${identity}:${email}`;
7587
+ const attempts = consumeMemoryAttempt(throttleKey, options.decaySeconds);
7588
+ if (attempts > options.maxAttempts) {
7589
+ return await tooManyRequestsResponse(request, "Too many login attempts. Try again later.", options.decaySeconds);
7590
+ }
7591
+ return await next();
7592
+ };
7593
+ }
7594
+ function createRedisLoginThrottleMiddleware(options) {
7080
7595
  const client = new RedisClient2(options.redisUrl);
7081
7596
  const prefix = options.keyPrefix ?? namespacedRedisKey("login-throttle:");
7082
7597
  return async (request, next) => {
@@ -7088,16 +7603,18 @@ function createLoginThrottleMiddleware(options) {
7088
7603
  await client.expire(throttleKey, options.decaySeconds);
7089
7604
  }
7090
7605
  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
- });
7606
+ return await tooManyRequestsResponse(request, "Too many login attempts. Try again later.", options.decaySeconds);
7097
7607
  }
7098
7608
  return await next();
7099
7609
  };
7100
7610
  }
7611
+ function createLoginThrottleMiddleware(options) {
7612
+ const redisUrl = options.redisUrl?.trim() ?? "";
7613
+ if (redisUrl) {
7614
+ return createRedisLoginThrottleMiddleware({ ...options, redisUrl });
7615
+ }
7616
+ return createMemoryLoginThrottleMiddleware(options);
7617
+ }
7101
7618
  // ../../src/core/http/memoryThrottleMiddleware.ts
7102
7619
  var throttleBucketRegistries = new Set;
7103
7620
  function createMemoryThrottleMiddleware(options) {
@@ -7116,12 +7633,7 @@ function createMemoryThrottleMiddleware(options) {
7116
7633
  }
7117
7634
  existing.count += 1;
7118
7635
  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
- });
7636
+ return await tooManyRequestsResponse(request, "Too many requests.", options.decaySeconds);
7125
7637
  }
7126
7638
  return await next();
7127
7639
  };
@@ -7267,6 +7779,60 @@ function createRequireGlobalAdminMiddleware() {
7267
7779
  return await next();
7268
7780
  };
7269
7781
  }
7782
+ // ../../src/core/auth/intendedUrlCookie.ts
7783
+ var INTENDED_URL_COOKIE = appCookieName("intended");
7784
+ var DEFAULT_INTENDED_URL_TTL_SECONDS = 60 * 60 * 24;
7785
+ var SKIP_EXACT_PATHS = new Set([
7786
+ "/email/verify",
7787
+ "/verify-email",
7788
+ "/login",
7789
+ "/register",
7790
+ "/logout",
7791
+ "/forgot-password",
7792
+ "/reset-password",
7793
+ "/two-factor-challenge",
7794
+ "/confirm-password",
7795
+ "/api"
7796
+ ]);
7797
+ function intendedUrlCookieName() {
7798
+ return process.env.INTENDED_URL_COOKIE_NAME?.trim() || appCookieName("intended");
7799
+ }
7800
+ function intendedUrlTtlSeconds() {
7801
+ const parsed = Number.parseInt(process.env.INTENDED_URL_TTL_SECONDS ?? "", 10);
7802
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_INTENDED_URL_TTL_SECONDS;
7803
+ }
7804
+ function cookieSecureFlag() {
7805
+ return process.env.APP_ENV === "production" ? "; Secure" : "";
7806
+ }
7807
+ function pathnameOf(path) {
7808
+ const pathname = path.split("?")[0] ?? path;
7809
+ const trimmed = pathname.replace(/\/+$/, "");
7810
+ return trimmed || "/";
7811
+ }
7812
+ function isStashableIntendedPath(path) {
7813
+ if (!path.startsWith("/") || path.startsWith("//")) {
7814
+ return false;
7815
+ }
7816
+ const pathname = pathnameOf(path);
7817
+ if (SKIP_EXACT_PATHS.has(pathname) || pathname.startsWith("/oauth") || pathname.startsWith("/api/")) {
7818
+ return false;
7819
+ }
7820
+ return true;
7821
+ }
7822
+ function createIntendedUrlCookie(path) {
7823
+ const sanitized = sanitizeInternalPath(path, "");
7824
+ if (!sanitized || !isStashableIntendedPath(sanitized)) {
7825
+ return null;
7826
+ }
7827
+ return `${intendedUrlCookieName()}=${encodeURIComponent(sanitized)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${intendedUrlTtlSeconds()}${cookieSecureFlag()}`;
7828
+ }
7829
+ function createIntendedUrlCookieFromRequest(request) {
7830
+ if (request.method !== "GET" && request.method !== "HEAD") {
7831
+ return null;
7832
+ }
7833
+ return createIntendedUrlCookie(safeInternalRedirectPath(request, ""));
7834
+ }
7835
+
7270
7836
  // ../../src/core/http/requireWebAuthMiddleware.ts
7271
7837
  function createRequireWebAuthMiddleware(auth2) {
7272
7838
  return async (request, next) => {
@@ -7277,7 +7843,12 @@ function createRequireWebAuthMiddleware(auth2) {
7277
7843
  if (requestPrefersJson(request)) {
7278
7844
  throw new UnauthorizedError;
7279
7845
  }
7280
- return Response.redirect(loginRedirectLocation(request), 302);
7846
+ const intended = createIntendedUrlCookieFromRequest(request);
7847
+ const headers = new Headers({ Location: loginRedirectLocation(request) });
7848
+ if (intended) {
7849
+ headers.append("Set-Cookie", intended);
7850
+ }
7851
+ return new Response(null, { status: 302, headers });
7281
7852
  };
7282
7853
  }
7283
7854
  // ../../src/core/http/scimThrottleMiddleware.ts
@@ -7350,7 +7921,7 @@ function createSecurityHeadersMiddleware(options = {}) {
7350
7921
  };
7351
7922
  }
7352
7923
  // ../../src/core/http/signedUrl.ts
7353
- import { createHmac as createHmac2 } from "crypto";
7924
+ import { createHmac as createHmac3 } from "crypto";
7354
7925
  function resolveSignedUrlSecret() {
7355
7926
  return process.env.SIGNED_URL_SECRET?.trim() || process.env.SESSION_SECRET?.trim() || process.env.OAUTH_STATE_SECRET?.trim() || appDevSecret("signed-url-secret");
7356
7927
  }
@@ -7372,7 +7943,7 @@ function sortedQueryString(params) {
7372
7943
  return new URLSearchParams(entries).toString();
7373
7944
  }
7374
7945
  function signCanonicalPayload(path, query) {
7375
- return createHmac2("sha256", resolveSignedUrlSecret()).update(`${path}
7946
+ return createHmac3("sha256", resolveSignedUrlSecret()).update(`${path}
7376
7947
  ${query}`).digest("hex");
7377
7948
  }
7378
7949
  function buildSignedSearchParams(path, query = {}, expiresAt) {
@@ -7467,12 +8038,7 @@ function createThrottleMiddleware(options) {
7467
8038
  }
7468
8039
  const maxAttempts = options.maxAttempts * rateLimitMultiplierForPlan(currentTenant()?.plan ?? "free");
7469
8040
  if (attempts > maxAttempts) {
7470
- return Response.json({ error: "Too many requests." }, {
7471
- status: 429,
7472
- headers: {
7473
- "retry-after": String(options.decaySeconds)
7474
- }
7475
- });
8041
+ return await tooManyRequestsResponse(request, "Too many requests.", options.decaySeconds);
7476
8042
  }
7477
8043
  return await next();
7478
8044
  };
@@ -7890,9 +8456,9 @@ function readSharedJobRegistry() {
7890
8456
  if (globalRegistry) {
7891
8457
  return globalRegistry;
7892
8458
  }
7893
- const registry = new JobRegistry;
7894
- globalThis[JOB_REGISTRY_KEY] = registry;
7895
- return registry;
8459
+ const registry2 = new JobRegistry;
8460
+ globalThis[JOB_REGISTRY_KEY] = registry2;
8461
+ return registry2;
7896
8462
  }
7897
8463
  var jobRegistry = readSharedJobRegistry();
7898
8464
 
@@ -8787,6 +9353,7 @@ export {
8787
9353
  AuthManager,
8788
9354
  BadRequestError,
8789
9355
  baseRepository_default as BaseRepository,
9356
+ BasicAuthGuard,
8790
9357
  BelongsToManyRelationQuery,
8791
9358
  BelongsToRelationQuery,
8792
9359
  Blueprint,
@@ -8813,6 +9380,7 @@ export {
8813
9380
  HttpError,
8814
9381
  Job,
8815
9382
  JsonResource,
9383
+ JwtGuard,
8816
9384
  LocalStorageDriver,
8817
9385
  LogMailDriver,
8818
9386
  Mailer,
@@ -8898,6 +9466,8 @@ export {
8898
9466
  createMembershipMiddleware,
8899
9467
  createMemoryThrottleMiddleware,
8900
9468
  createMetricsMiddleware,
9469
+ createMysqlConnection,
9470
+ createMysqlConnectionFromPool,
8901
9471
  createNotificationDispatcher,
8902
9472
  createProductionQueue,
8903
9473
  createQueue,
@@ -8911,9 +9481,11 @@ export {
8911
9481
  createScimAuthMiddleware,
8912
9482
  createScimThrottleMiddleware,
8913
9483
  createSecurityHeadersMiddleware,
9484
+ createSqliteConnection,
8914
9485
  createStorageDriver,
8915
9486
  createTenantMiddleware,
8916
9487
  createThrottleMiddleware,
9488
+ createTokenAbilityChecker,
8917
9489
  createTracingMiddleware,
8918
9490
  createTrackedJob,
8919
9491
  createValidateSignatureMiddleware,
@@ -8922,11 +9494,13 @@ export {
8922
9494
  currentOrgRole,
8923
9495
  currentOrganizationIds,
8924
9496
  currentRequestMeta,
9497
+ currentSqlDialect,
8925
9498
  currentTenant,
8926
9499
  currentTenantId,
8927
9500
  currentTraceId,
8928
9501
  defineTable,
8929
9502
  dehydrateValue,
9503
+ dialectFor,
8930
9504
  emailRule,
8931
9505
  emptyPaginateResult,
8932
9506
  errorTemplateName,
@@ -8943,10 +9517,13 @@ export {
8943
9517
  getDefaultDatabasePool,
8944
9518
  getDefaultDatabaseQuery,
8945
9519
  getMigrationStatus,
9520
+ getNamedConnection,
8946
9521
  grammarForDriver,
8947
9522
  guestCanViewResource,
9523
+ hasActiveDatabaseConnection,
8948
9524
  hasMany,
8949
9525
  hasMinimumOrgRole2 as hasMinimumOrgRole,
9526
+ hasNamedConnection,
8950
9527
  hasOne,
8951
9528
  hasOrgMembership,
8952
9529
  hasValidSignature,
@@ -8971,6 +9548,7 @@ export {
8971
9548
  isTenancyEnabled,
8972
9549
  jobRegistry,
8973
9550
  jsonResponse2 as jsonResponse,
9551
+ jwtTtlSeconds,
8974
9552
  loadMigrationsFromDirectory,
8975
9553
  loadSeedersFromDirectory,
8976
9554
  log,
@@ -9008,6 +9586,7 @@ export {
9008
9586
  registerDefaultDatabasePool,
9009
9587
  registerModelClass,
9010
9588
  registerModelRepository,
9589
+ registerNamedConnection,
9011
9590
  registerShutdownHandler,
9012
9591
  renderKernelErrorChrome,
9013
9592
  renderMarkdownMail,
@@ -9017,6 +9596,8 @@ export {
9017
9596
  resetBoundDatabaseConnection,
9018
9597
  resetDefaultStorage,
9019
9598
  resetMemoryThrottleForTests,
9599
+ resetNamedConnections,
9600
+ resetSqlDialect,
9020
9601
  resolveApplicationAuth,
9021
9602
  resolveApplicationCache,
9022
9603
  resolveApplicationConfig,
@@ -9043,12 +9624,14 @@ export {
9043
9624
  runDueScheduledTasks,
9044
9625
  runGracefulShutdown,
9045
9626
  runInTransaction,
9627
+ runOnNamedConnection,
9046
9628
  runQueueJob,
9047
9629
  runSeedersFromDirectory,
9048
9630
  runWithAuthUser,
9049
9631
  runWithDatabaseConnection,
9050
9632
  runWithMembershipContext,
9051
9633
  runWithRequestMeta,
9634
+ runWithSqlDialect,
9052
9635
  runWithTenant,
9053
9636
  runWithTenantDatabase,
9054
9637
  runWithTraceContext,
@@ -9062,6 +9645,7 @@ export {
9062
9645
  serializeDate,
9063
9646
  serverHtmxContentSecurityPolicy,
9064
9647
  setActiveApplicationContext,
9648
+ signJwt,
9065
9649
  signedUrl,
9066
9650
  singularize,
9067
9651
  spaContentSecurityPolicy,
@@ -9075,8 +9659,11 @@ export {
9075
9659
  toPaginatedResourceCollection,
9076
9660
  toResourceCollection,
9077
9661
  trustForwardedFor,
9662
+ unregisterNamedConnection,
9663
+ useSqlDialect,
9078
9664
  validateObject,
9079
9665
  verifyCsrfToken,
9666
+ verifyJwt,
9080
9667
  whenLoaded,
9081
9668
  withErrorHandling,
9082
9669
  withMiddleware,