@getstrata/core 2.2.37 → 2.2.39

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.
@@ -11,25 +11,6 @@ var __esm = (fn, res, err) => () => {
11
11
  return res;
12
12
  };
13
13
 
14
- // ../../src/core/database/boundConnection.ts
15
- function boundConnectionState() {
16
- const globalRecord = globalThis;
17
- const existing = globalRecord[BOUND_CONNECTION_KEY];
18
- if (existing) {
19
- return existing;
20
- }
21
- const created = { connection: null };
22
- globalRecord[BOUND_CONNECTION_KEY] = created;
23
- return created;
24
- }
25
- function getBoundDatabaseConnection() {
26
- return boundConnectionState().connection;
27
- }
28
- var BOUND_CONNECTION_KEY;
29
- var init_boundConnection = __esm(() => {
30
- BOUND_CONNECTION_KEY = Symbol.for("@getstrata/boundDatabaseConnection");
31
- });
32
-
33
14
  // ../../src/core/runtime/asyncContextStore.ts
34
15
  import { AsyncLocalStorage } from "async_hooks";
35
16
  function createAsyncContextStore(key) {
@@ -45,60 +26,6 @@ function createAsyncContextStore(key) {
45
26
  }
46
27
  var init_asyncContextStore = () => {};
47
28
 
48
- // ../../src/core/database/connectionContext.ts
49
- function runWithDatabaseConnection(connection, callback) {
50
- return activeConnection.run(connection, callback);
51
- }
52
- function getActiveDatabaseConnection(fallback) {
53
- return activeConnection.getStore() ?? fallback;
54
- }
55
- function hasActiveDatabaseConnection() {
56
- return activeConnection.getStore() !== undefined;
57
- }
58
- var activeConnection;
59
- var init_connectionContext = __esm(() => {
60
- init_asyncContextStore();
61
- activeConnection = createAsyncContextStore("@getstrata/databaseConnectionContext");
62
- });
63
-
64
- // ../../src/core/database/queryProxy.ts
65
- var POOL_CONNECTION_METHODS;
66
- var init_queryProxy = __esm(() => {
67
- init_connectionContext();
68
- POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
69
- });
70
-
71
- // ../../src/core/database/defaultConnection.ts
72
- function defaultConnectionState() {
73
- const globalRecord = globalThis;
74
- const existing = globalRecord[DEFAULT_CONNECTION_KEY];
75
- if (existing) {
76
- return existing;
77
- }
78
- const created = { pool: null, query: null };
79
- globalRecord[DEFAULT_CONNECTION_KEY] = created;
80
- return created;
81
- }
82
- function getDefaultDatabasePool() {
83
- const pool = defaultConnectionState().pool;
84
- if (!pool) {
85
- throw new Error("Default database pool is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
86
- }
87
- return pool;
88
- }
89
- function getDefaultDatabaseQuery() {
90
- const query = defaultConnectionState().query;
91
- if (!query) {
92
- throw new Error("Default database query handle is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
93
- }
94
- return query;
95
- }
96
- var DEFAULT_CONNECTION_KEY;
97
- var init_defaultConnection = __esm(() => {
98
- init_queryProxy();
99
- DEFAULT_CONNECTION_KEY = Symbol.for("@getstrata/defaultDatabaseConnection");
100
- });
101
-
102
29
  // ../../src/core/database/schema/driver.ts
103
30
  function normalizeConnectionName(connection) {
104
31
  const normalized = connection.trim().toLowerCase();
@@ -300,6 +227,685 @@ var init_dialect = __esm(() => {
300
227
  dialectContext = createAsyncContextStore("@getstrata/sqlDialect");
301
228
  });
302
229
 
230
+ // ../../src/core/tenant/tenantContext.ts
231
+ function runWithTenant(tenant, callback) {
232
+ return tenantContext.run(tenant, callback);
233
+ }
234
+ function currentTenant() {
235
+ return tenantContext.getStore() ?? null;
236
+ }
237
+ var tenantContext;
238
+ var init_tenantContext = __esm(() => {
239
+ init_asyncContextStore();
240
+ tenantContext = createAsyncContextStore("@getstrata/tenantContext");
241
+ });
242
+
243
+ // ../../src/core/events/eventBus.ts
244
+ class EventBus {
245
+ constructor() {}
246
+ transactionalListeners = new Map;
247
+ listenTransactional(event, listener) {
248
+ const handlers = this.transactionalListeners.get(event) ?? new Set;
249
+ handlers.add(listener);
250
+ this.transactionalListeners.set(event, handlers);
251
+ return () => {
252
+ handlers.delete(listener);
253
+ if (!handlers.size)
254
+ this.transactionalListeners.delete(event);
255
+ };
256
+ }
257
+ hasTransactionalListeners(event) {
258
+ return (this.transactionalListeners.get(event)?.size ?? 0) > 0;
259
+ }
260
+ async dispatchTransactional(event, payload) {
261
+ for (const listener of this.transactionalListeners.get(event) ?? [])
262
+ await listener(payload);
263
+ }
264
+ listeners = new Map;
265
+ on(event, listener) {
266
+ return this.listen(event, listener);
267
+ }
268
+ listen(event, listener) {
269
+ const handlers = this.listeners.get(event) ?? new Set;
270
+ handlers.add(listener);
271
+ this.listeners.set(event, handlers);
272
+ return () => {
273
+ handlers.delete(listener);
274
+ if (handlers.size === 0) {
275
+ this.listeners.delete(event);
276
+ }
277
+ };
278
+ }
279
+ async emit(event, payload) {
280
+ await this.dispatch(event, payload);
281
+ }
282
+ async dispatch(event, payload) {
283
+ const handlers = this.listeners.get(event);
284
+ if (!handlers || handlers.size === 0) {
285
+ return;
286
+ }
287
+ for (const handler of handlers) {
288
+ await handler(payload);
289
+ }
290
+ }
291
+ }
292
+ function readSharedEventBus() {
293
+ const globalBus = globalThis[EVENT_BUS_KEY];
294
+ if (globalBus) {
295
+ return globalBus;
296
+ }
297
+ const bus = new EventBus;
298
+ globalThis[EVENT_BUS_KEY] = bus;
299
+ return bus;
300
+ }
301
+ var EVENT_BUS_KEY, eventBus;
302
+ var init_eventBus = __esm(() => {
303
+ EVENT_BUS_KEY = Symbol.for("@getstrata/eventBus");
304
+ eventBus = readSharedEventBus();
305
+ });
306
+
307
+ // ../../src/core/database/connectionContext.ts
308
+ function runWithDatabaseConnection(connection, callback) {
309
+ return activeConnection.run(connection, callback);
310
+ }
311
+ function getActiveDatabaseConnection(fallback) {
312
+ return activeConnection.getStore() ?? fallback;
313
+ }
314
+ function hasActiveDatabaseConnection() {
315
+ return activeConnection.getStore() !== undefined;
316
+ }
317
+ var activeConnection;
318
+ var init_connectionContext = __esm(() => {
319
+ init_asyncContextStore();
320
+ activeConnection = createAsyncContextStore("@getstrata/databaseConnectionContext");
321
+ });
322
+
323
+ // ../../src/core/database/transactionControl.ts
324
+ function unwrapTransactionRollback(error, depth = 0) {
325
+ if (error instanceof TransactionRollback) {
326
+ return error;
327
+ }
328
+ if (depth >= 5 || !(error instanceof Error) || error.cause === undefined) {
329
+ return null;
330
+ }
331
+ return unwrapTransactionRollback(error.cause, depth + 1);
332
+ }
333
+ function hasActiveTransaction() {
334
+ return hasActiveDatabaseConnection() && (transactionScopes.getStore()?.rollback.length ?? 0) > 0;
335
+ }
336
+ function requestTransactionRollback() {
337
+ const state = transactionScopes.getStore();
338
+ const index = state ? state.rollback.length - 1 : -1;
339
+ if (!state || index < 0) {
340
+ return;
341
+ }
342
+ const flags = state.rollback;
343
+ const current = flags[index];
344
+ if (current === undefined) {
345
+ return;
346
+ }
347
+ flags[index] = true;
348
+ }
349
+ function scopeRequestsRollback() {
350
+ const state = transactionScopes.getStore();
351
+ if (!state || state.rollback.length === 0) {
352
+ return false;
353
+ }
354
+ return state.rollback[state.rollback.length - 1] === true;
355
+ }
356
+ async function commitOrRollbackScope(result) {
357
+ if (scopeRequestsRollback()) {
358
+ throw new TransactionRollback(result);
359
+ }
360
+ return result;
361
+ }
362
+ async function settleTransaction(work) {
363
+ try {
364
+ return await work();
365
+ } catch (error) {
366
+ const rollback = unwrapTransactionRollback(error);
367
+ if (rollback) {
368
+ return rollback.result;
369
+ }
370
+ throw error;
371
+ }
372
+ }
373
+ async function runWithTransactionScope(callback) {
374
+ const existing = transactionScopes.getStore();
375
+ if (existing) {
376
+ existing.rollback.push(false);
377
+ try {
378
+ return await callback();
379
+ } finally {
380
+ existing.rollback.pop();
381
+ }
382
+ }
383
+ const state = { rollback: [false] };
384
+ return await transactionScopes.run(state, callback);
385
+ }
386
+ var TransactionRollback, transactionScopes;
387
+ var init_transactionControl = __esm(() => {
388
+ init_asyncContextStore();
389
+ init_connectionContext();
390
+ TransactionRollback = class TransactionRollback extends Error {
391
+ result;
392
+ constructor(result) {
393
+ super("Transaction rolled back");
394
+ this.name = "TransactionRollback";
395
+ this.result = result;
396
+ }
397
+ };
398
+ transactionScopes = createAsyncContextStore("@getstrata/transactionScopes");
399
+ });
400
+
401
+ // ../../src/core/tenant/tenancyConfig.ts
402
+ function readTenancyDriver(env = process.env) {
403
+ const raw = env.TENANCY_DRIVER?.trim();
404
+ if (raw === undefined || raw === "") {
405
+ return "rls";
406
+ }
407
+ if (TENANCY_DRIVERS.includes(raw)) {
408
+ return raw;
409
+ }
410
+ throw new Error(`TENANCY_DRIVER must be one of ${TENANCY_DRIVERS.join(", ")}; received "${raw}".`);
411
+ }
412
+ function isTenancyEnabled(env = process.env) {
413
+ return readTenancyDriver(env) !== "none";
414
+ }
415
+ function isRlsTenancy(env = process.env) {
416
+ return readTenancyDriver(env) === "rls";
417
+ }
418
+ var TENANCY_DRIVERS;
419
+ var init_tenancyConfig = __esm(() => {
420
+ TENANCY_DRIVERS = ["rls", "column", "none"];
421
+ });
422
+
423
+ // ../../src/core/tenant/databaseTenantContext.ts
424
+ import { getDefaultDatabasePool } from "@getstrata/core/database/defaultConnection";
425
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
426
+ async function applyBypassToTransaction(transaction, bypass) {
427
+ await transaction.unsafe(`SELECT set_config('app.bypass_rls', $1, true)`, [
428
+ bypass ? "true" : "false"
429
+ ]);
430
+ }
431
+ function stringifyBypassIdentifier(identifier) {
432
+ if (typeof identifier === "number") {
433
+ return String(identifier);
434
+ }
435
+ return identifier.trim();
436
+ }
437
+ async function applyIdentifierToTransaction(transaction, identifier) {
438
+ await transaction.unsafe(`SELECT set_config('app.bypass_identifier', $1, true)`, [
439
+ stringifyBypassIdentifier(identifier)
440
+ ]);
441
+ }
442
+ function assertBypassIdentifier(identifier) {
443
+ if (typeof identifier === "number") {
444
+ if (!Number.isInteger(identifier) || identifier <= 0) {
445
+ throw new Error("RLS bypass requires a caller-supplied identifier that pins the row.");
446
+ }
447
+ return;
448
+ }
449
+ if (typeof identifier === "string" && identifier.trim() !== "") {
450
+ return;
451
+ }
452
+ throw new Error("RLS bypass requires a caller-supplied identifier that pins the row.");
453
+ }
454
+ async function runWithScopedTenantTransaction(apply, callback) {
455
+ if (!isRlsTenancy()) {
456
+ return await callback();
457
+ }
458
+ if (hasActiveDatabaseConnection()) {
459
+ const connection = getActiveDatabaseConnection(getDefaultDatabasePool());
460
+ return await runInTransaction(async () => {
461
+ const [previous] = await connection.unsafe(`SELECT current_setting('app.tenant_id', true) AS tenant_id,
462
+ current_setting('app.bypass_rls', true) AS bypass_rls,
463
+ current_setting('app.bypass_identifier', true) AS bypass_identifier`);
464
+ await apply(connection);
465
+ const result = await callback();
466
+ await connection.unsafe(`SELECT set_config('app.tenant_id', $1, true),
467
+ set_config('app.bypass_rls', $2, true), set_config('app.bypass_identifier', $3, true)`, [previous?.tenant_id ?? "", previous?.bypass_rls ?? "", previous?.bypass_identifier ?? ""]);
468
+ return result;
469
+ });
470
+ }
471
+ const pool = getDefaultDatabasePool();
472
+ const begin = pool.begin;
473
+ if (typeof begin !== "function") {
474
+ throw new Error("RLS migration bypass requires a pool that supports begin(). Session-scoped set_config is not used on pooled connections.");
475
+ }
476
+ return await settleTransaction(() => runWithDeferredModelEvents(async () => {
477
+ return await begin.bind(pool)(AsyncLocalStorage2.bind(async (transaction) => {
478
+ await apply(transaction);
479
+ return await runWithDatabaseConnection(transaction, () => runWithTransactionScope(async () => commitOrRollbackScope(await callback())));
480
+ }));
481
+ }));
482
+ }
483
+ async function runWithMigrationBypass(callback) {
484
+ return await runWithScopedTenantTransaction((transaction) => applyBypassToTransaction(transaction, true), callback);
485
+ }
486
+ async function runWithMigrationBypassForIdentifier(identifier, callback) {
487
+ assertBypassIdentifier(identifier);
488
+ return await runWithScopedTenantTransaction((transaction) => applyIdentifierToTransaction(transaction, identifier), callback);
489
+ }
490
+ var init_databaseTenantContext = __esm(() => {
491
+ init_connectionContext();
492
+ init_transaction();
493
+ init_transactionControl();
494
+ init_deferredModelEvents();
495
+ init_tenancyConfig();
496
+ });
497
+
498
+ // ../../src/core/tenant/tenantDatabaseScope.ts
499
+ import { getDefaultDatabasePool as getDefaultDatabasePool2 } from "@getstrata/core/database/defaultConnection";
500
+ import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
501
+ async function applyTenantContextToTransaction(transaction, tenantId) {
502
+ await transaction.unsafe(`SELECT set_config('app.tenant_id', $1, true)`, [String(tenantId)]);
503
+ await transaction.unsafe(`SELECT set_config('app.bypass_rls', $1, true)`, ["false"]);
504
+ }
505
+ async function runWithTenantDatabase(tenant, callback) {
506
+ const outerTenant = currentTenant();
507
+ if (hasActiveDatabaseConnection() && outerTenant && outerTenant.id !== tenant.id) {
508
+ throw new Error("Cannot switch tenants inside an active database transaction.");
509
+ }
510
+ if (!isTenancyEnabled() || !isRlsTenancy()) {
511
+ return await runWithTenant(tenant, callback);
512
+ }
513
+ if (hasActiveDatabaseConnection()) {
514
+ const activeConnection = getActiveDatabaseConnection(getDefaultDatabasePool2());
515
+ return await runWithScopedTenantTransaction(() => applyTenantContextToTransaction(activeConnection, tenant.id), () => runWithTenant(tenant, callback));
516
+ }
517
+ const pool = getDefaultDatabasePool2();
518
+ const begin = pool.begin;
519
+ if (typeof begin !== "function") {
520
+ throw new Error("RLS tenant scope requires a pool that supports begin(). Session-scoped set_config is not used on pooled connections.");
521
+ }
522
+ return await settleTransaction(() => runWithDeferredModelEvents(async () => {
523
+ return await begin.bind(pool)(AsyncLocalStorage3.bind(async (transaction) => {
524
+ await applyTenantContextToTransaction(transaction, tenant.id);
525
+ return await runWithDatabaseConnection(transaction, async () => {
526
+ return await runWithTransactionScope(async () => commitOrRollbackScope(await runWithTenant(tenant, callback)));
527
+ });
528
+ }));
529
+ }));
530
+ }
531
+ function isInsideTenantDatabaseScope(tenantId = currentTenant()?.id) {
532
+ return hasActiveDatabaseConnection() && currentTenant()?.id === tenantId;
533
+ }
534
+ var init_tenantDatabaseScope = __esm(() => {
535
+ init_connectionContext();
536
+ init_transactionControl();
537
+ init_deferredModelEvents();
538
+ init_databaseTenantContext();
539
+ init_tenancyConfig();
540
+ init_tenantContext();
541
+ });
542
+
543
+ // ../../src/core/events/deferredModelEvents.ts
544
+ function sameTenant(left, right) {
545
+ if (left === null && right === null) {
546
+ return true;
547
+ }
548
+ if (left === null || right === null) {
549
+ return false;
550
+ }
551
+ return left.id === right.id;
552
+ }
553
+ function groupQueuedModelEvents(queued) {
554
+ const groups = [];
555
+ for (const item of queued) {
556
+ const lastGroup = groups[groups.length - 1];
557
+ const last = lastGroup?.[lastGroup.length - 1];
558
+ if (!lastGroup || !last || !sameTenant(last.tenant, item.tenant)) {
559
+ groups.push([item]);
560
+ } else {
561
+ lastGroup.push(item);
562
+ }
563
+ }
564
+ return groups;
565
+ }
566
+ async function dispatchEventBatch(batch) {
567
+ for (const item of batch) {
568
+ await eventBus.dispatch(item.event, item.payload);
569
+ }
570
+ }
571
+ async function dispatchQueuedModelEvents(queued) {
572
+ if (queued.length === 0) {
573
+ return;
574
+ }
575
+ await Promise.resolve().then(() => init_tenantDatabaseScope());
576
+ for (const batch of groupQueuedModelEvents(queued)) {
577
+ const tenant = batch[0]?.tenant ?? null;
578
+ if (tenant) {
579
+ await runWithTenantDatabase(tenant, () => dispatchEventBatch(batch));
580
+ } else {
581
+ await dispatchEventBatch(batch);
582
+ }
583
+ }
584
+ }
585
+ async function dispatchModelEvent(event, payload) {
586
+ await eventBus.dispatchTransactional(event, payload);
587
+ const state = deferredModelEvents.getStore();
588
+ const frame = state?.frames[state.frames.length - 1];
589
+ if (frame) {
590
+ frame.push({
591
+ event,
592
+ payload,
593
+ tenant: currentTenant()
594
+ });
595
+ return;
596
+ }
597
+ await eventBus.dispatch(event, payload);
598
+ }
599
+ async function runWithDeferredModelEvents(callback) {
600
+ const existing = deferredModelEvents.getStore();
601
+ if (existing) {
602
+ existing.frames.push([]);
603
+ existing.rollbackFrames.push([]);
604
+ try {
605
+ const result = await callback();
606
+ const nested = existing.frames.pop() ?? [];
607
+ const rollback = existing.rollbackFrames.pop() ?? [];
608
+ existing.rollbackFrames.at(-1)?.push(...rollback);
609
+ const parent = existing.frames[existing.frames.length - 1];
610
+ if (parent) {
611
+ parent.push(...nested);
612
+ }
613
+ return result;
614
+ } catch (error) {
615
+ existing.frames.pop();
616
+ for (const rollback of (existing.rollbackFrames.pop() ?? []).reverse())
617
+ rollback();
618
+ throw error;
619
+ }
620
+ }
621
+ const state = { frames: [[]], rollbackFrames: [[]] };
622
+ let result;
623
+ try {
624
+ result = await deferredModelEvents.run(state, callback);
625
+ } catch (error) {
626
+ for (const rollback of (state.rollbackFrames[0] ?? []).reverse())
627
+ rollback();
628
+ throw error;
629
+ }
630
+ await dispatchQueuedModelEvents(state.frames[0] ?? []);
631
+ return result;
632
+ }
633
+ var deferredModelEvents;
634
+ var init_deferredModelEvents = __esm(() => {
635
+ init_asyncContextStore();
636
+ init_tenantContext();
637
+ init_eventBus();
638
+ deferredModelEvents = createAsyncContextStore("@getstrata/deferredModelEvents");
639
+ });
640
+
641
+ // ../../src/core/database/acquisitionMetrics.ts
642
+ async function observeTransactionAcquisition(metrics, acquire, signal) {
643
+ const state = states.get(metrics);
644
+ if (!state)
645
+ throw new TypeError("Transaction acquisition metrics must be created by the framework factory.");
646
+ const started = performance.now();
647
+ state.inflight++;
648
+ let outcome = "acquired";
649
+ try {
650
+ return await acquire();
651
+ } catch (error) {
652
+ outcome = signal?.aborted ? "aborted" : "failed";
653
+ throw error;
654
+ } finally {
655
+ state.inflight--;
656
+ const elapsed = Math.max(0, performance.now() - started) / 1000;
657
+ const histogram = state.outcomes[outcomes.indexOf(outcome)];
658
+ if (histogram) {
659
+ histogram.count++;
660
+ histogram.sumSeconds += elapsed;
661
+ for (let index = 0;index < ACQUISITION_BUCKETS_SECONDS.length; index++) {
662
+ const bound = ACQUISITION_BUCKETS_SECONDS[index];
663
+ if (bound !== undefined && elapsed <= bound)
664
+ histogram.buckets[index] = (histogram.buckets[index] ?? 0) + 1;
665
+ }
666
+ }
667
+ }
668
+ }
669
+ var ACQUISITION_BUCKETS_SECONDS, outcomes, metricsBrand, states;
670
+ var init_acquisitionMetrics = __esm(() => {
671
+ ACQUISITION_BUCKETS_SECONDS = [
672
+ 0.001,
673
+ 0.005,
674
+ 0.01,
675
+ 0.025,
676
+ 0.05,
677
+ 0.1,
678
+ 0.25,
679
+ 0.5,
680
+ 1,
681
+ 2.5,
682
+ 5,
683
+ 10
684
+ ];
685
+ outcomes = ["acquired", "failed", "aborted"];
686
+ metricsBrand = Symbol("transaction acquisition metrics");
687
+ states = new WeakMap;
688
+ });
689
+
690
+ // ../../src/core/database/connection.ts
691
+ function createDatabaseConnection(source) {
692
+ return {
693
+ async unsafe(query, params = []) {
694
+ return await source.unsafe(query, params);
695
+ }
696
+ };
697
+ }
698
+ var init_connection = () => {};
699
+
700
+ // ../../src/core/database/boundConnection.ts
701
+ function boundConnectionState() {
702
+ const globalRecord = globalThis;
703
+ const existing = globalRecord[BOUND_CONNECTION_KEY];
704
+ if (existing) {
705
+ return existing;
706
+ }
707
+ const created = { connection: null };
708
+ globalRecord[BOUND_CONNECTION_KEY] = created;
709
+ return created;
710
+ }
711
+ function getBoundDatabaseConnection() {
712
+ return boundConnectionState().connection;
713
+ }
714
+ var BOUND_CONNECTION_KEY;
715
+ var init_boundConnection = __esm(() => {
716
+ BOUND_CONNECTION_KEY = Symbol.for("@getstrata/boundDatabaseConnection");
717
+ });
718
+
719
+ // ../../src/core/database/queryProxy.ts
720
+ var POOL_CONNECTION_METHODS;
721
+ var init_queryProxy = __esm(() => {
722
+ init_connectionContext();
723
+ POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
724
+ });
725
+
726
+ // ../../src/core/database/defaultConnection.ts
727
+ function defaultConnectionState() {
728
+ const globalRecord = globalThis;
729
+ const existing = globalRecord[DEFAULT_CONNECTION_KEY];
730
+ if (existing) {
731
+ return existing;
732
+ }
733
+ const created = { pool: null, query: null };
734
+ globalRecord[DEFAULT_CONNECTION_KEY] = created;
735
+ return created;
736
+ }
737
+ function getDefaultDatabasePool3() {
738
+ const pool = defaultConnectionState().pool;
739
+ if (!pool) {
740
+ throw new Error("Default database pool is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
741
+ }
742
+ return pool;
743
+ }
744
+ function getDefaultDatabaseQuery() {
745
+ const query = defaultConnectionState().query;
746
+ if (!query) {
747
+ throw new Error("Default database query handle is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
748
+ }
749
+ return query;
750
+ }
751
+ var DEFAULT_CONNECTION_KEY;
752
+ var init_defaultConnection = __esm(() => {
753
+ init_queryProxy();
754
+ DEFAULT_CONNECTION_KEY = Symbol.for("@getstrata/defaultDatabaseConnection");
755
+ });
756
+
757
+ // ../../src/core/database/repositoryConnection.ts
758
+ function resolveRepositoryConnection() {
759
+ return getActiveDatabaseConnection(getBoundDatabaseConnection() ?? getDefaultDatabaseQuery());
760
+ }
761
+ function compileTaggedSql(strings, values) {
762
+ const dialect = currentSqlDialect();
763
+ let query = strings[0] ?? "";
764
+ const params = [];
765
+ for (let index = 0;index < values.length; index += 1) {
766
+ params.push(values[index]);
767
+ query += `${dialect.placeholder(params.length)}${strings[index + 1] ?? ""}`;
768
+ }
769
+ return { query, params };
770
+ }
771
+ function isTaggedStrings(value) {
772
+ if (!Array.isArray(value) || typeof value[0] !== "string") {
773
+ return false;
774
+ }
775
+ return typeof value.raw !== "undefined";
776
+ }
777
+ function runOnConnection(connection, args) {
778
+ if (typeof connection === "function") {
779
+ return connection(...args);
780
+ }
781
+ if (typeof connection.unsafe !== "function") {
782
+ throw new Error("Database connection cannot run SQL.");
783
+ }
784
+ const strings = args[0];
785
+ if (!isTaggedStrings(strings)) {
786
+ throw new Error("Database connection cannot run tagged SQL.");
787
+ }
788
+ const compiled = compileTaggedSql(strings, args.slice(1));
789
+ return connection.unsafe(compiled.query, compiled.params);
790
+ }
791
+ var repositoryConnection;
792
+ var init_repositoryConnection = __esm(() => {
793
+ init_boundConnection();
794
+ init_connectionContext();
795
+ init_defaultConnection();
796
+ init_dialect();
797
+ repositoryConnection = new Proxy(function repositoryConnection() {}, {
798
+ apply(_target, _thisArg, args) {
799
+ return runOnConnection(resolveRepositoryConnection(), args);
800
+ },
801
+ get(_target, property) {
802
+ const connection = resolveRepositoryConnection();
803
+ const value = connection[property];
804
+ return typeof value === "function" ? value.bind(connection) : value;
805
+ }
806
+ });
807
+ });
808
+
809
+ // ../../src/core/database/transaction.ts
810
+ import { AsyncLocalStorage as AsyncLocalStorage4 } from "async_hooks";
811
+ function scopedConnection(source) {
812
+ const connection = createDatabaseConnection(source);
813
+ transactionAliases.set(connection, source);
814
+ return connection;
815
+ }
816
+ function isActiveTransactionConnection(connection) {
817
+ if (!hasActiveTransaction() || !hasActiveDatabaseConnection())
818
+ return false;
819
+ const active = getActiveDatabaseConnection(connection);
820
+ return connection === active || transactionAliases.get(connection) === active;
821
+ }
822
+ async function runInSavepoint(connection, operation) {
823
+ const inherited = transactionFrame.getStore();
824
+ let parent = inherited?.connection === connection ? inherited : outerFrames.get(connection);
825
+ if (!parent) {
826
+ parent = { connection, childActive: false };
827
+ outerFrames.set(connection, parent);
828
+ }
829
+ if (parent.childActive) {
830
+ throw new Error("Concurrent nested transactions on one connection are not supported. Await each transaction before starting the next.");
831
+ }
832
+ parent.childActive = true;
833
+ const name = `strata_${crypto.randomUUID().replaceAll("-", "")}`;
834
+ try {
835
+ return await settleTransaction(() => runWithDeferredModelEvents(async () => {
836
+ await connection.unsafe(`SAVEPOINT ${name}`);
837
+ try {
838
+ const result = await transactionFrame.run({ connection, childActive: false }, () => runWithTransactionScope(async () => commitOrRollbackScope(await operation(scopedConnection(connection)))));
839
+ await connection.unsafe(`RELEASE SAVEPOINT ${name}`);
840
+ return result;
841
+ } catch (error) {
842
+ await connection.unsafe(`ROLLBACK TO SAVEPOINT ${name}`);
843
+ await connection.unsafe(`RELEASE SAVEPOINT ${name}`);
844
+ throw error;
845
+ }
846
+ }));
847
+ } finally {
848
+ parent.childActive = false;
849
+ }
850
+ }
851
+ function supportsTransactions(connection) {
852
+ return typeof connection.begin === "function";
853
+ }
854
+ async function runInTransaction(operation, options = {}) {
855
+ const signal = options.acquisitionSignal;
856
+ signal?.throwIfAborted();
857
+ const admittedOperation = (connection) => {
858
+ signal?.throwIfAborted();
859
+ return operation(connection);
860
+ };
861
+ const pool = options.connection ?? resolveRepositoryConnection();
862
+ if (hasActiveDatabaseConnection()) {
863
+ const active = getActiveDatabaseConnection(pool);
864
+ if (options.connection && options.connection !== active && options.connection !== transactionRoots.get(active) && transactionAliases.get(options.connection) !== active)
865
+ throw new Error("Cannot switch database connections within an active transaction.");
866
+ return await runInSavepoint(active, admittedOperation);
867
+ }
868
+ if (!supportsTransactions(pool)) {
869
+ throw new Error("Active database connection does not support transactions. Bind a client with begin() via bindDatabaseConnection.");
870
+ }
871
+ return await settleTransaction(() => runWithDeferredModelEvents(async () => {
872
+ let reserved;
873
+ if (signal || options.acquisitionMetrics) {
874
+ if (!pool.reserve)
875
+ throw new Error(signal ? "Active database connection does not support cancellable transaction acquisition." : "Active database connection does not support observable transaction acquisition.");
876
+ const reserve = pool.reserve.bind(pool);
877
+ reserved = options.acquisitionMetrics ? await observeTransactionAcquisition(options.acquisitionMetrics, () => reserve({ signal }), signal) : await reserve({ signal });
878
+ }
879
+ const connection = reserved ?? pool;
880
+ try {
881
+ signal?.throwIfAborted();
882
+ if (!supportsTransactions(connection))
883
+ throw new Error("Reserved database connection does not support transactions.");
884
+ return await connection.begin(AsyncLocalStorage4.bind(async (transaction) => {
885
+ transactionRoots.set(transaction, pool);
886
+ return await runWithDatabaseConnection(transaction, () => transactionFrame.run({ connection: transaction, childActive: false }, () => runWithTransactionScope(async () => commitOrRollbackScope(await admittedOperation(scopedConnection(transaction))))));
887
+ }));
888
+ } finally {
889
+ await reserved?.release();
890
+ }
891
+ }));
892
+ }
893
+ var transactionFrame, outerFrames, transactionRoots, transactionAliases;
894
+ var init_transaction = __esm(() => {
895
+ init_deferredModelEvents();
896
+ init_asyncContextStore();
897
+ init_acquisitionMetrics();
898
+ init_connection();
899
+ init_connectionContext();
900
+ init_repositoryConnection();
901
+ init_transactionControl();
902
+ init_acquisitionMetrics();
903
+ transactionFrame = createAsyncContextStore("@getstrata/transactionFrame");
904
+ outerFrames = new WeakMap;
905
+ transactionRoots = new WeakMap;
906
+ transactionAliases = new WeakMap;
907
+ });
908
+
303
909
  // ../../src/core/runtime/optionalPeer.ts
304
910
  function missingOptionalPeer(packageName, reason, error) {
305
911
  return new Error(`Install ${packageName} ${reason} (\`bun add ${packageName}\`).`, {
@@ -319,8 +925,6 @@ function timingSafeCompareString(left, right) {
319
925
  }
320
926
 
321
927
  // ../../src/core/auth/saml/samlAssertionReplay.ts
322
- init_boundConnection();
323
- init_defaultConnection();
324
928
  init_dialect();
325
929
 
326
930
  // ../../src/core/database/errors.ts
@@ -443,6 +1047,7 @@ function isUniqueConstraintError(error) {
443
1047
  }
444
1048
 
445
1049
  // ../../src/core/auth/saml/samlAssertionReplay.ts
1050
+ init_transaction();
446
1051
  var REPLAY_RETENTION_MS = 60 * 60 * 1000;
447
1052
 
448
1053
  class InMemorySamlAssertionReplayStore {
@@ -470,17 +1075,18 @@ class InMemorySamlAssertionReplayStore {
470
1075
 
471
1076
  class SqlSamlAssertionReplayStore {
472
1077
  async consume(assertionId) {
473
- const sql = getBoundDatabaseConnection() ?? getDefaultDatabasePool();
474
1078
  const dialect = currentSqlDialect();
475
1079
  try {
476
- await sql.unsafe(`INSERT INTO auth_saml_assertions (assertion_id, consumed_at) VALUES (${dialect.placeholder(1)}, ${dialect.placeholder(2)})`, [assertionId, new Date().toISOString()]);
1080
+ await runInTransaction(async (connection) => {
1081
+ await connection.unsafe(`INSERT INTO auth_saml_assertions (assertion_id, consumed_at) VALUES (${dialect.placeholder(1)}, ${dialect.placeholder(2)})`, [assertionId, dialect.timestampValue(new Date)]);
1082
+ await connection.unsafe(`DELETE FROM auth_saml_assertions WHERE consumed_at < ${dialect.placeholder(1)}`, [dialect.timestampValue(new Date(Date.now() - REPLAY_RETENTION_MS))]);
1083
+ });
477
1084
  } catch (error) {
478
1085
  if (isUniqueConstraintError(error)) {
479
1086
  throw new Error("SAML assertion replay detected.");
480
1087
  }
481
1088
  throw error;
482
1089
  }
483
- await sql.unsafe(`DELETE FROM auth_saml_assertions WHERE consumed_at < ${dialect.placeholder(1)}`, [new Date(Date.now() - REPLAY_RETENTION_MS).toISOString()]);
484
1090
  }
485
1091
  }
486
1092
  var sqlReplayStore = new SqlSamlAssertionReplayStore;
@@ -503,6 +1109,7 @@ async function consumeSamlAssertion(assertionId) {
503
1109
  }
504
1110
 
505
1111
  // ../../src/core/auth/saml/samlServiceProvider.ts
1112
+ var CLOCK_SKEW_MS = 5000;
506
1113
  function readAssertionId(profile) {
507
1114
  if (typeof profile.ID === "string" && profile.ID.trim()) {
508
1115
  return profile.ID.trim();
@@ -511,6 +1118,43 @@ function readAssertionId(profile) {
511
1118
  const assertionId = parsed?.Assertion?.$?.ID;
512
1119
  return typeof assertionId === "string" ? assertionId.trim() : "";
513
1120
  }
1121
+ function assertBearerRecipient(profile, acsUrl) {
1122
+ const subjects = profile.getAssertion?.()?.Assertion?.Subject;
1123
+ const confirmations = Array.isArray(subjects) && subjects.length === 1 ? subjects[0]?.SubjectConfirmation : undefined;
1124
+ const now = Date.now();
1125
+ const matches = Array.isArray(confirmations) && confirmations.some((confirmation) => {
1126
+ if (confirmation?.$?.Method !== "urn:oasis:names:tc:SAML:2.0:cm:bearer")
1127
+ return false;
1128
+ const data = confirmation.SubjectConfirmationData;
1129
+ if (!Array.isArray(data) || data.length !== 1)
1130
+ return false;
1131
+ const attributes = data[0]?.$;
1132
+ if (attributes?.Recipient !== acsUrl || attributes.NotBefore !== undefined)
1133
+ return false;
1134
+ if (typeof attributes.NotOnOrAfter !== "string")
1135
+ return false;
1136
+ const expires = Date.parse(attributes.NotOnOrAfter);
1137
+ return Number.isFinite(expires) && now < expires + CLOCK_SKEW_MS;
1138
+ });
1139
+ if (!matches)
1140
+ throw new Error("SAML assertion bearer recipient is invalid.");
1141
+ }
1142
+ async function assertResponseDestination(profile, acsUrl) {
1143
+ const xml = profile.getSamlResponseXml?.();
1144
+ const invalid = () => new Error("SAML response destination is invalid.");
1145
+ if (typeof xml !== "string" || !xml.trim())
1146
+ throw invalid();
1147
+ const parserModule = "@xmldom/xmldom";
1148
+ const { DOMParser } = await import(parserModule);
1149
+ const document = new DOMParser({
1150
+ errorHandler: () => {
1151
+ throw invalid();
1152
+ }
1153
+ }).parseFromString(xml, "application/xml");
1154
+ const response = document.documentElement;
1155
+ if (document.doctype || response?.localName !== "Response" || response.namespaceURI !== "urn:oasis:names:tc:SAML:2.0:protocol" || response.getAttribute("Destination") !== acsUrl)
1156
+ throw invalid();
1157
+ }
514
1158
  function requireSamlOption(value, name) {
515
1159
  const trimmed = value?.trim() ?? "";
516
1160
  if (!trimmed) {
@@ -575,7 +1219,7 @@ class SamlServiceProvider {
575
1219
  idpCert: this.options.idpCert,
576
1220
  audience: this.options.spEntityId,
577
1221
  idpIssuer: this.options.idpIssuer,
578
- acceptedClockSkewMs: 5000,
1222
+ acceptedClockSkewMs: CLOCK_SKEW_MS,
579
1223
  wantAssertionsSigned: this.options.wantAssertionsSigned ?? true,
580
1224
  wantAuthnResponseSigned: this.options.wantAuthnResponseSigned ?? true,
581
1225
  disableRequestedAuthnContext: this.options.disableRequestedAuthnContext ?? false
@@ -606,6 +1250,8 @@ class SamlServiceProvider {
606
1250
  if (!assertionId) {
607
1251
  throw new Error("SAML assertion did not include an ID.");
608
1252
  }
1253
+ assertBearerRecipient(profile, this.options.acsUrl);
1254
+ await assertResponseDestination(profile, this.options.acsUrl);
609
1255
  await consumeSamlAssertion(assertionId);
610
1256
  const email = profile.email || (typeof profile["urn:oid:0.9.2342.19200300.100.1.3"] === "string" ? profile["urn:oid:0.9.2342.19200300.100.1.3"] : "") || "";
611
1257
  if (!email) {