@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.
- package/CHANGELOG.md +67 -32
- package/README.md +16 -35
- package/dist/core/auth/abilityCatalog.d.ts +2 -2
- package/dist/core/auth/basicAuthGuard.d.ts +9 -0
- package/dist/core/auth/guard.d.ts +6 -0
- package/dist/core/auth/jwt.d.ts +19 -0
- package/dist/core/auth/jwtGuard.d.ts +14 -0
- package/dist/core/auth/tokenAbilityChecker.d.ts +5 -0
- package/dist/core/cache/tags.d.ts +6 -0
- package/dist/core/contracts/authUserDirectory.d.ts +4 -0
- package/dist/core/database/dialect.d.ts +18 -0
- package/dist/core/database/factory.d.ts +1 -0
- package/dist/core/database/index.d.ts +8 -0
- package/dist/core/database/mysqlConnection.d.ts +12 -0
- package/dist/core/database/namedConnections.d.ts +15 -0
- package/dist/core/database/repositoryQuery.d.ts +1 -0
- package/dist/core/database/sqliteConnection.d.ts +7 -0
- package/dist/core/http/loginThrottleMiddleware.d.ts +5 -2
- package/dist/core/http/resources.d.ts +2 -2
- package/dist/core/http/response.d.ts +2 -1
- package/dist/core/http/statelessAuth.d.ts +8 -0
- package/dist/core/http/throttleResponse.d.ts +2 -0
- package/dist/core/runtime/frontendMode.d.ts +10 -2
- package/dist/entries/auth/basicAuthGuard.js +137 -0
- package/dist/entries/auth/jwt.js +135 -0
- package/dist/entries/auth/jwtGuard.js +203 -0
- package/dist/entries/auth/sessionGuard.js +3 -21
- package/dist/entries/auth/tokenAbilityChecker.js +24 -0
- package/dist/entries/cache/tags.js +7 -1
- package/dist/entries/database/connectionContext.js +1 -0
- package/dist/entries/database/dialect.js +1 -0
- package/dist/entries/database/factory.js +5 -4
- package/dist/entries/database/model.js +49 -32
- package/dist/entries/database/mysqlConnection.js +35 -0
- package/dist/entries/database/namedConnections.js +1 -0
- package/dist/entries/database/query.js +28 -15
- package/dist/entries/database/relationships.js +14 -6
- package/dist/entries/database/repositoryQuery.js +96 -81
- package/dist/entries/database/schema.js +28 -15
- package/dist/entries/database/sqliteConnection.js +34 -0
- package/dist/entries/facades.js +1 -1
- package/dist/entries/http/contentNegotiation.js +5 -2
- package/dist/entries/http/csrfMiddleware.js +45 -0
- package/dist/entries/http/loginThrottleMiddleware.js +246 -7
- package/dist/entries/http/memoryThrottleMiddleware.js +208 -6
- package/dist/entries/http/requireAbilityMiddleware.js +35 -11
- package/dist/entries/http/requirePasswordConfirmMiddleware.js +5 -2
- package/dist/entries/http/requireVerifiedMiddleware.js +5 -2
- package/dist/entries/http/requireWebAuthMiddleware.js +12 -3
- package/dist/entries/http/resources.js +4 -1
- package/dist/entries/http/response.js +46 -12
- package/dist/entries/http/statelessAuth.js +48 -0
- package/dist/entries/http/throttleMiddleware.js +208 -6
- package/dist/entries/http/webErrorResponse.js +35 -11
- package/dist/entries/http/webFormRequest.js +5 -2
- package/dist/entries/mail/mailer.js +1 -1
- package/dist/entries/openapi/generator.js +26 -3
- package/dist/entries/runtime/frontendMode.js +39 -10
- package/dist/framework/public-api.d.ts +11 -1
- package/dist/index.js +873 -270
- 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
|
-
"
|
|
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
|
|
296
|
-
if (!
|
|
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
|
|
314
|
-
if (!
|
|
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
|
|
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
|
|
772
|
-
|
|
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
|
-
|
|
1023
|
+
boundConnectionState().connection = connection;
|
|
776
1024
|
}
|
|
777
1025
|
function getBoundDatabaseConnection() {
|
|
778
|
-
return
|
|
1026
|
+
return boundConnectionState().connection;
|
|
779
1027
|
}
|
|
780
1028
|
function resetBoundDatabaseConnection() {
|
|
781
|
-
|
|
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
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
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
|
-
|
|
828
|
-
|
|
1080
|
+
const holder = defaultConnectionState();
|
|
1081
|
+
holder.pool = connection;
|
|
1082
|
+
holder.query = createDatabaseQueryProxy(connection);
|
|
829
1083
|
}
|
|
830
1084
|
function getDefaultDatabasePool() {
|
|
831
|
-
|
|
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
|
|
1089
|
+
return pool;
|
|
835
1090
|
}
|
|
836
1091
|
function getDefaultDatabaseQuery() {
|
|
837
|
-
|
|
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
|
|
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
|
|
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/
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
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
|
|
1016
|
-
return
|
|
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/
|
|
1648
|
-
function
|
|
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
|
|
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
|
|
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}
|
|
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)}
|
|
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})
|
|
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}
|
|
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)} = $
|
|
2041
|
-
params
|
|
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)} = $
|
|
2052
|
-
params
|
|
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)} = $
|
|
2058
|
-
params
|
|
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
|
-
|
|
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
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
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
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
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
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
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
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
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
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
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
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
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
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
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
|
-
|
|
3366
|
+
await Promise.all([...idsByType.entries()].map(async ([morphType, ids]) => {
|
|
2957
3367
|
const repository = repositoriesByType.get(morphType);
|
|
2958
3368
|
if (!repository) {
|
|
2959
|
-
|
|
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
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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 ? {
|
|
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
|
-
|
|
6009
|
-
if (frontendMode === "server-htmx") {
|
|
6500
|
+
if (isViewsEnabled()) {
|
|
6010
6501
|
return cloneDirectives(HTMX_HTML_DIRECTIVES);
|
|
6011
6502
|
}
|
|
6012
|
-
if (
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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 (!
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
7894
|
-
globalThis[JOB_REGISTRY_KEY] =
|
|
7895
|
-
return
|
|
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,
|