@getstrata/core 2.0.2 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/core/database/baseRepository.d.ts +2 -0
  3. package/dist/core/database/dialect.d.ts +4 -0
  4. package/dist/core/database/mysqlConnection.d.ts +12 -2
  5. package/dist/core/database/sqliteConnection.d.ts +2 -1
  6. package/dist/core/database/transaction.d.ts +2 -2
  7. package/dist/core/database/transactionControl.d.ts +2 -1
  8. package/dist/core/events/eventBus.d.ts +4 -0
  9. package/dist/core/events/outbox/index.d.ts +55 -0
  10. package/dist/core/events/outbox/migration.d.ts +7 -0
  11. package/dist/core/http/requestMetaContext.d.ts +2 -0
  12. package/dist/core/http/response.d.ts +2 -1
  13. package/dist/core/openapi/registeredRoute.d.ts +40 -1
  14. package/dist/core/security/safeFetch.d.ts +1 -0
  15. package/dist/entries/audit/exportAuditLogs.js +137 -17
  16. package/dist/entries/auth/basicAuthGuard.js +18 -0
  17. package/dist/entries/auth/oauth/oidcProvider.js +100 -17
  18. package/dist/entries/auth/oauth/providers.js +100 -17
  19. package/dist/entries/auth/oauth/samlProvider.js +18 -0
  20. package/dist/entries/auth/oneTimeToken.js +18 -0
  21. package/dist/entries/auth/passwordLogin.js +18 -0
  22. package/dist/entries/auth/saml/samlServiceProvider.js +18 -0
  23. package/dist/entries/auth/scimAuthMiddleware.js +37 -0
  24. package/dist/entries/database/migrations.js +18 -0
  25. package/dist/entries/database/sqliteConnection.js +101 -7
  26. package/dist/entries/events/outbox.js +1 -0
  27. package/dist/entries/http/response.js +39 -5
  28. package/dist/entries/jobs/exportAuditLogsJob.js +137 -17
  29. package/dist/entries/openapi/generator.js +42 -12
  30. package/dist/entries/openapi/validate.js +10 -0
  31. package/dist/entries/security/safeFetch.js +100 -17
  32. package/dist/entries/tenant/databaseTenantContext.js +37 -0
  33. package/dist/entries/tenant/tenantDatabaseScope.js +37 -0
  34. package/dist/entries/tracing/tracingMiddleware.js +100 -17
  35. package/dist/framework/public-api.d.ts +3 -1
  36. package/dist/index.js +649 -60
  37. package/package.json +6 -1
package/dist/index.js CHANGED
@@ -221,6 +221,12 @@ var init_dialect = __esm(() => {
221
221
  nowExpression() {
222
222
  return "NOW()";
223
223
  },
224
+ epochMillisecondsExpression() {
225
+ return "CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)";
226
+ },
227
+ rowLockClause(skipLocked = false) {
228
+ return ` FOR UPDATE${skipLocked ? " SKIP LOCKED" : ""}`;
229
+ },
224
230
  timestampValue(value) {
225
231
  return value.toISOString();
226
232
  },
@@ -259,6 +265,12 @@ var init_dialect = __esm(() => {
259
265
  nowExpression() {
260
266
  return "CURRENT_TIMESTAMP";
261
267
  },
268
+ epochMillisecondsExpression() {
269
+ return "CAST(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000 AS UNSIGNED)";
270
+ },
271
+ rowLockClause(skipLocked = false) {
272
+ return ` FOR UPDATE${skipLocked ? " SKIP LOCKED" : ""}`;
273
+ },
262
274
  timestampValue(value) {
263
275
  return value.toISOString().slice(0, 19).replace("T", " ");
264
276
  },
@@ -297,6 +309,12 @@ var init_dialect = __esm(() => {
297
309
  nowExpression() {
298
310
  return "strftime('%Y-%m-%dT%H:%M:%fZ', 'now')";
299
311
  },
312
+ epochMillisecondsExpression() {
313
+ return "CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)";
314
+ },
315
+ rowLockClause() {
316
+ return "";
317
+ },
300
318
  timestampValue(value) {
301
319
  return value.toISOString();
302
320
  },
@@ -446,6 +464,9 @@ function unwrapTransactionRollback(error, depth = 0) {
446
464
  }
447
465
  return unwrapTransactionRollback(error.cause, depth + 1);
448
466
  }
467
+ function hasActiveTransaction() {
468
+ return hasActiveDatabaseConnection() && (transactionScopes.getStore()?.rollback.length ?? 0) > 0;
469
+ }
449
470
  function requestTransactionRollback() {
450
471
  const state = transactionScopes.getStore();
451
472
  const index = state ? state.rollback.length - 1 : -1;
@@ -499,6 +520,7 @@ async function runWithTransactionScope(callback) {
499
520
  var TransactionRollback, transactionScopes;
500
521
  var init_transactionControl = __esm(() => {
501
522
  init_asyncContextStore();
523
+ init_connectionContext();
502
524
  TransactionRollback = class TransactionRollback extends Error {
503
525
  result;
504
526
  constructor(result) {
@@ -513,6 +535,24 @@ var init_transactionControl = __esm(() => {
513
535
  // ../../src/core/events/eventBus.ts
514
536
  class EventBus {
515
537
  constructor() {}
538
+ transactionalListeners = new Map;
539
+ listenTransactional(event, listener) {
540
+ const handlers = this.transactionalListeners.get(event) ?? new Set;
541
+ handlers.add(listener);
542
+ this.transactionalListeners.set(event, handlers);
543
+ return () => {
544
+ handlers.delete(listener);
545
+ if (!handlers.size)
546
+ this.transactionalListeners.delete(event);
547
+ };
548
+ }
549
+ hasTransactionalListeners(event) {
550
+ return (this.transactionalListeners.get(event)?.size ?? 0) > 0;
551
+ }
552
+ async dispatchTransactional(event, payload) {
553
+ for (const listener of this.transactionalListeners.get(event) ?? [])
554
+ await listener(payload);
555
+ }
516
556
  listeners = new Map;
517
557
  on(event, listener) {
518
558
  return this.listen(event, listener);
@@ -599,6 +639,7 @@ async function dispatchQueuedModelEvents(queued) {
599
639
  }
600
640
  }
601
641
  async function dispatchModelEvent(event, payload) {
642
+ await eventBus.dispatchTransactional(event, payload);
602
643
  const state = deferredModelEvents.getStore();
603
644
  const frame = state?.frames[state.frames.length - 1];
604
645
  if (frame) {
@@ -711,6 +752,11 @@ var init_transaction = __esm(() => {
711
752
  });
712
753
 
713
754
  // ../../src/core/tenant/databaseTenantContext.ts
755
+ async function applyBypassToTransaction(transaction, bypass) {
756
+ await transaction.unsafe(`SELECT set_config('app.bypass_rls', $1, true)`, [
757
+ bypass ? "true" : "false"
758
+ ]);
759
+ }
714
760
  function stringifyBypassIdentifier(identifier) {
715
761
  if (typeof identifier === "number") {
716
762
  return String(identifier);
@@ -763,6 +809,9 @@ async function runWithScopedTenantTransaction(apply, callback) {
763
809
  });
764
810
  }));
765
811
  }
812
+ async function runWithMigrationBypass(callback) {
813
+ return await runWithScopedTenantTransaction((transaction) => applyBypassToTransaction(transaction, true), callback);
814
+ }
766
815
  async function runWithMigrationBypassForIdentifier(identifier, callback) {
767
816
  assertBypassIdentifier(identifier);
768
817
  return await runWithScopedTenantTransaction((transaction) => applyIdentifierToTransaction(transaction, identifier), callback);
@@ -2349,50 +2398,133 @@ function resetDnsLookupForTests() {
2349
2398
 
2350
2399
  // ../../src/core/security/safeFetch.ts
2351
2400
  var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
2401
+ function untilAborted(work, signal) {
2402
+ signal.throwIfAborted();
2403
+ return new Promise((resolve, reject) => {
2404
+ const onAbort = () => reject(signal.reason);
2405
+ signal.addEventListener("abort", onAbort, { once: true });
2406
+ work.then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
2407
+ });
2408
+ }
2409
+ function preserveResponseMetadata(response, original) {
2410
+ Object.defineProperties(response, {
2411
+ url: { value: original.url },
2412
+ redirected: { value: original.redirected },
2413
+ type: { value: original.type }
2414
+ });
2415
+ const clone = response.clone.bind(response);
2416
+ response.clone = () => preserveResponseMetadata(clone(), original);
2417
+ return response;
2418
+ }
2419
+ function withBodyDeadline(response, signal, release) {
2420
+ if (!response.body) {
2421
+ release();
2422
+ return response;
2423
+ }
2424
+ const reader = response.body.getReader();
2425
+ let stopped = false;
2426
+ let bodyController;
2427
+ const stop = (reason, errorBody) => {
2428
+ if (stopped)
2429
+ return;
2430
+ stopped = true;
2431
+ release();
2432
+ signal.removeEventListener("abort", onAbort);
2433
+ if (errorBody)
2434
+ bodyController.error(reason);
2435
+ reader.cancel(reason).catch(() => {}).finally(() => reader.releaseLock());
2436
+ };
2437
+ const onAbort = () => stop(signal.reason, true);
2438
+ const body = new ReadableStream({
2439
+ start(controller) {
2440
+ bodyController = controller;
2441
+ signal.addEventListener("abort", onAbort, { once: true });
2442
+ if (signal.aborted)
2443
+ onAbort();
2444
+ },
2445
+ async pull(controller) {
2446
+ try {
2447
+ const result = await untilAborted(reader.read(), signal);
2448
+ if (stopped)
2449
+ return;
2450
+ if (result.done) {
2451
+ stopped = true;
2452
+ release();
2453
+ signal.removeEventListener("abort", onAbort);
2454
+ reader.releaseLock();
2455
+ controller.close();
2456
+ } else {
2457
+ controller.enqueue(result.value);
2458
+ }
2459
+ } catch (error) {
2460
+ stop(error, true);
2461
+ }
2462
+ },
2463
+ cancel(reason) {
2464
+ stop(reason, false);
2465
+ }
2466
+ }, { highWaterMark: 0 });
2467
+ return preserveResponseMetadata(new Response(body, {
2468
+ status: response.status,
2469
+ statusText: response.statusText,
2470
+ headers: response.headers
2471
+ }), response);
2472
+ }
2352
2473
  async function safeFetch(input, init = {}, options = {}) {
2474
+ init.signal?.throwIfAborted();
2353
2475
  const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
2476
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
2477
+ throw new RangeError("safeFetch timeoutMs must be a positive finite number.");
2478
+ }
2354
2479
  const maxRedirects = options.maxRedirects ?? 0;
2355
- const resolveDns = options.resolveDns ?? true;
2356
2480
  const urlOptions = {
2357
2481
  allowHttp: options.allowHttp,
2358
- resolveDns,
2482
+ resolveDns: options.resolveDns ?? true,
2359
2483
  allowPrivate: options.allowPrivate
2360
2484
  };
2361
2485
  const controller = new AbortController;
2362
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
2486
+ const forwardAbort = () => controller.abort(init.signal?.reason);
2487
+ init.signal?.addEventListener("abort", forwardAbort, { once: true });
2488
+ const timeout = setTimeout(() => controller.abort(new DOMException("The operation timed out.", "TimeoutError")), timeoutMs);
2489
+ const release = () => {
2490
+ clearTimeout(timeout);
2491
+ init.signal?.removeEventListener("abort", forwardAbort);
2492
+ };
2363
2493
  try {
2364
- let current = await resolveSafeOutboundTarget(input, urlOptions);
2494
+ let current = await untilAborted(resolveSafeOutboundTarget(input, urlOptions), controller.signal);
2365
2495
  let redirectCount = 0;
2366
2496
  while (true) {
2497
+ controller.signal.throwIfAborted();
2367
2498
  const address = current.addresses[0];
2368
2499
  const fetchUrl = address ? pinUrlToAddress(current.url, address).toString() : current.url.toString();
2369
2500
  const headers = new Headers(init.headers);
2370
- if (address && !headers.has("host")) {
2501
+ if (address && !headers.has("host"))
2371
2502
  headers.set("Host", current.url.host);
2372
- }
2373
2503
  const fetchInit = {
2374
2504
  ...init,
2375
2505
  headers,
2376
2506
  signal: controller.signal,
2377
2507
  redirect: "manual"
2378
2508
  };
2379
- if (address) {
2509
+ if (address)
2380
2510
  fetchInit.tls = { serverName: current.url.hostname };
2381
- }
2382
- const response = await fetch(fetchUrl, fetchInit);
2511
+ const response = await untilAborted(fetch(fetchUrl, fetchInit), controller.signal);
2383
2512
  if (response.status >= 300 && response.status < 400) {
2384
2513
  const location = response.headers.get("location");
2385
- if (!location || redirectCount >= maxRedirects) {
2386
- return response;
2514
+ if (location && redirectCount < maxRedirects) {
2515
+ if (response.body)
2516
+ await untilAborted(response.body.cancel(), controller.signal);
2517
+ current = await untilAborted(resolveSafeOutboundTarget(new URL(location, current.url).toString(), urlOptions), controller.signal);
2518
+ redirectCount++;
2519
+ continue;
2387
2520
  }
2388
- current = await resolveSafeOutboundTarget(new URL(location, current.url).toString(), urlOptions);
2389
- redirectCount += 1;
2390
- continue;
2391
2521
  }
2392
- return response;
2522
+ return withBodyDeadline(response, controller.signal, release);
2393
2523
  }
2394
- } finally {
2395
- clearTimeout(timeout);
2524
+ } catch (error) {
2525
+ controller.abort(error);
2526
+ release();
2527
+ throw error;
2396
2528
  }
2397
2529
  }
2398
2530
 
@@ -3697,6 +3829,9 @@ class ConfigStore {
3697
3829
  return this.values.has(key);
3698
3830
  }
3699
3831
  }
3832
+ // ../../src/core/database/baseRepository.ts
3833
+ init_eventBus();
3834
+
3700
3835
  // ../../src/core/events/index.ts
3701
3836
  init_deferredModelEvents();
3702
3837
  init_eventBus();
@@ -3715,6 +3850,9 @@ function buildPaginationMeta(input) {
3715
3850
  };
3716
3851
  }
3717
3852
 
3853
+ // ../../src/core/database/baseRepository.ts
3854
+ init_connectionContext();
3855
+
3718
3856
  // ../../src/core/database/query.ts
3719
3857
  init_dialect();
3720
3858
  function quoteIdentifier(identifier) {
@@ -4839,6 +4977,9 @@ class RepositoryQuery {
4839
4977
  }
4840
4978
 
4841
4979
  // ../../src/core/database/baseRepository.ts
4980
+ init_transaction();
4981
+ init_transactionControl();
4982
+
4842
4983
  class BaseRepository {
4843
4984
  table;
4844
4985
  connection;
@@ -4846,6 +4987,17 @@ class BaseRepository {
4846
4987
  this.table = table;
4847
4988
  this.connection = connection;
4848
4989
  }
4990
+ assertTransactionalEvent(action) {
4991
+ if (!eventBus.hasTransactionalListeners(modelEventName(this.table.name, action)))
4992
+ return;
4993
+ if (!hasActiveTransaction() || this.connection !== repositoryConnection && this.connection !== getActiveDatabaseConnection(repositoryConnection))
4994
+ throw new Error("Transactional model listeners require runInTransaction and the framework-bound repository connection before writing.");
4995
+ }
4996
+ async runModelMutation(action, operation) {
4997
+ if (!eventBus.hasTransactionalListeners(modelEventName(this.table.name, action)))
4998
+ return operation();
4999
+ return runInTransaction(operation);
5000
+ }
4849
5001
  async count(options = {}) {
4850
5002
  const { whereNodes, where, ...rest } = options;
4851
5003
  return await this.countWhere(where ?? {}, rest, whereNodes ?? []);
@@ -5003,7 +5155,8 @@ class BaseRepository {
5003
5155
  return await this.incrementById(id, column, -amount, extra);
5004
5156
  }
5005
5157
  async create(values) {
5006
- return await withDatabaseErrorHandling(async () => {
5158
+ this.assertTransactionalEvent("created");
5159
+ return await this.runModelMutation("created", () => withDatabaseErrorHandling(async () => {
5007
5160
  const { text, params } = buildInsertQuery(this.table, values);
5008
5161
  const [record] = await this.connection.unsafe(text, params);
5009
5162
  if (!record) {
@@ -5012,10 +5165,11 @@ class BaseRepository {
5012
5165
  const entity = record;
5013
5166
  await dispatchModelEvent(modelEventName(this.table.name, "created"), entity);
5014
5167
  return entity;
5015
- });
5168
+ }));
5016
5169
  }
5017
5170
  async updateById(id, changes) {
5018
- return await withDatabaseErrorHandling(async () => {
5171
+ this.assertTransactionalEvent("updated");
5172
+ return await this.runModelMutation("updated", () => withDatabaseErrorHandling(async () => {
5019
5173
  const { text, params } = buildUpdateQuery(this.table, id, changes);
5020
5174
  const [record] = await this.connection.unsafe(text, params);
5021
5175
  const entity = record ?? null;
@@ -5023,7 +5177,7 @@ class BaseRepository {
5023
5177
  await dispatchModelEvent(modelEventName(this.table.name, "updated"), entity);
5024
5178
  }
5025
5179
  return entity;
5026
- });
5180
+ }));
5027
5181
  }
5028
5182
  async updateByIdOrThrow(id, changes, errorFactory) {
5029
5183
  const record = await this.updateById(id, changes);
@@ -5039,7 +5193,8 @@ class BaseRepository {
5039
5193
  return await this.forceDeleteById(id);
5040
5194
  }
5041
5195
  async softDeleteById(id) {
5042
- return await withDatabaseErrorHandling(async () => {
5196
+ this.assertTransactionalEvent("deleted");
5197
+ return await this.runModelMutation("deleted", () => withDatabaseErrorHandling(async () => {
5043
5198
  const { text, params } = buildSoftDeleteByIdQuery(this.table, id, new Date);
5044
5199
  const [record] = await this.connection.unsafe(text, params);
5045
5200
  if (!record) {
@@ -5047,10 +5202,11 @@ class BaseRepository {
5047
5202
  }
5048
5203
  await dispatchModelEvent(modelEventName(this.table.name, "deleted"), record);
5049
5204
  return true;
5050
- });
5205
+ }));
5051
5206
  }
5052
5207
  async forceDeleteById(id) {
5053
- return await withDatabaseErrorHandling(async () => {
5208
+ this.assertTransactionalEvent("force-deleted");
5209
+ return await this.runModelMutation("force-deleted", () => withDatabaseErrorHandling(async () => {
5054
5210
  const { text, params } = buildDeleteByIdQuery(this.table, id);
5055
5211
  const [row] = await this.connection.unsafe(text, params);
5056
5212
  if (!row) {
@@ -5060,10 +5216,11 @@ class BaseRepository {
5060
5216
  id
5061
5217
  });
5062
5218
  return true;
5063
- });
5219
+ }));
5064
5220
  }
5065
5221
  async restoreById(id) {
5066
- return await withDatabaseErrorHandling(async () => {
5222
+ this.assertTransactionalEvent("restored");
5223
+ return await this.runModelMutation("restored", () => withDatabaseErrorHandling(async () => {
5067
5224
  const { text, params } = buildRestoreByIdQuery(this.table, id);
5068
5225
  const [record] = await this.connection.unsafe(text, params);
5069
5226
  if (!record) {
@@ -5072,7 +5229,7 @@ class BaseRepository {
5072
5229
  const entity = record;
5073
5230
  await dispatchModelEvent(modelEventName(this.table.name, "restored"), entity);
5074
5231
  return entity;
5075
- });
5232
+ }));
5076
5233
  }
5077
5234
  withConnection(connection) {
5078
5235
  const clone = Object.create(Object.getPrototypeOf(this));
@@ -7269,6 +7426,7 @@ function registerModelRepository(model, repository) {
7269
7426
  return model;
7270
7427
  }
7271
7428
  // ../../src/core/database/mysqlConnection.ts
7429
+ init_connectionContext();
7272
7430
  var MYSQL_SESSION_UTC = "SET time_zone = '+00:00'";
7273
7431
  var mysqlModule;
7274
7432
  var mysqlPending;
@@ -7324,18 +7482,71 @@ function rowsFromResult(result) {
7324
7482
  }
7325
7483
  return [];
7326
7484
  }
7327
- function createMysqlConnectionFromPool(pool) {
7328
- return {
7485
+ async function executeMysql(target, query, params) {
7486
+ const [result] = params.length === 0 && target.query ? await target.query(query) : await target.execute(query, [...params]);
7487
+ return rowsFromResult(result);
7488
+ }
7489
+ function createMysqlAdapter(getPool, close) {
7490
+ const transactions = new WeakSet;
7491
+ const connection = {
7329
7492
  async unsafe(query, params = []) {
7330
- const [result] = await pool.execute(query, [...params]);
7331
- return rowsFromResult(result);
7493
+ const active = getActiveDatabaseConnection(connection);
7494
+ if (transactions.has(active))
7495
+ return active.unsafe(query, params);
7496
+ return executeMysql(await getPool(), query, params);
7332
7497
  },
7333
- async close() {
7334
- if (typeof pool.end === "function") {
7335
- await pool.end();
7498
+ async begin(callback) {
7499
+ if (transactions.has(getActiveDatabaseConnection(connection))) {
7500
+ throw new Error("Use runInTransaction for nested MySQL transactions.");
7336
7501
  }
7337
- }
7502
+ const pool = await getPool();
7503
+ if (!pool.getConnection)
7504
+ throw new Error("MySQL transactions require a pool with getConnection().");
7505
+ const reserved = await pool.getConnection();
7506
+ let open = false;
7507
+ let discard = false;
7508
+ const transaction = {
7509
+ async unsafe(query, params = []) {
7510
+ if (!open)
7511
+ throw new Error("MySQL transaction is no longer active.");
7512
+ return executeMysql(reserved, query, params);
7513
+ }
7514
+ };
7515
+ transactions.add(transaction);
7516
+ try {
7517
+ await reserved.beginTransaction();
7518
+ open = true;
7519
+ const result = await runWithDatabaseConnection(transaction, () => callback(transaction));
7520
+ await reserved.commit();
7521
+ return result;
7522
+ } catch (error) {
7523
+ if (open) {
7524
+ try {
7525
+ await reserved.rollback();
7526
+ } catch (rollbackError) {
7527
+ discard = true;
7528
+ reserved.destroy();
7529
+ throw new AggregateError([error, rollbackError], "MySQL rollback failed; connection discarded.");
7530
+ }
7531
+ } else {
7532
+ discard = true;
7533
+ reserved.destroy();
7534
+ }
7535
+ throw error;
7536
+ } finally {
7537
+ open = false;
7538
+ if (!discard)
7539
+ reserved.release();
7540
+ }
7541
+ },
7542
+ close
7338
7543
  };
7544
+ return connection;
7545
+ }
7546
+ function createMysqlConnectionFromPool(pool) {
7547
+ return createMysqlAdapter(async () => pool, async () => {
7548
+ await pool.end?.();
7549
+ });
7339
7550
  }
7340
7551
  function pinSessionToUtc(connection) {
7341
7552
  connection.query(MYSQL_SESSION_UTC, (error) => {
@@ -7368,23 +7579,16 @@ function createMysqlConnection(url) {
7368
7579
  }
7369
7580
  return poolPending;
7370
7581
  }
7371
- return {
7372
- async unsafe(query, params = []) {
7373
- const [result] = await (await ensurePool()).execute(query, [...params]);
7374
- return rowsFromResult(result);
7375
- },
7376
- async close() {
7377
- if (!poolPending) {
7378
- return;
7379
- }
7380
- const pending = poolPending;
7381
- poolPending = undefined;
7382
- const pool = await pending.catch(() => {
7383
- return;
7384
- });
7385
- await pool?.end();
7386
- }
7387
- };
7582
+ return createMysqlAdapter(ensurePool, async () => {
7583
+ if (!poolPending)
7584
+ return;
7585
+ const pending = poolPending;
7586
+ poolPending = undefined;
7587
+ const pool = await pending.catch(() => {
7588
+ return;
7589
+ });
7590
+ await pool?.end();
7591
+ });
7388
7592
  }
7389
7593
  // ../../src/core/database/namedConnections.ts
7390
7594
  init_connectionContext();
@@ -8046,6 +8250,7 @@ async function runSeedersFromDirectory(directory, db, options) {
8046
8250
  return seeders.length;
8047
8251
  }
8048
8252
  // ../../src/core/database/sqliteConnection.ts
8253
+ init_connectionContext();
8049
8254
  import { Database } from "bun:sqlite";
8050
8255
  function isRowReturning(sql) {
8051
8256
  const upper = sql.replace(/\s+/g, " ").trim().toUpperCase();
@@ -8065,20 +8270,70 @@ function createSqliteConnection(filename) {
8065
8270
  db.exec("PRAGMA journal_mode = WAL");
8066
8271
  db.exec("PRAGMA synchronous = NORMAL");
8067
8272
  }
8068
- return {
8273
+ let tail = Promise.resolve();
8274
+ let pending = 0;
8275
+ let closed = false;
8276
+ const transactions = new WeakSet;
8277
+ function execute(query, params) {
8278
+ const statement = db.query(query);
8279
+ const args = [...params];
8280
+ if (isRowReturning(query))
8281
+ return statement.all(...args);
8282
+ statement.run(...args);
8283
+ return [];
8284
+ }
8285
+ function enqueue(operation) {
8286
+ if (closed)
8287
+ return Promise.reject(new Error("SQLite connection is closed."));
8288
+ pending++;
8289
+ const result = tail.then(operation).finally(() => {
8290
+ pending--;
8291
+ });
8292
+ tail = result.then(() => {}, () => {});
8293
+ return result;
8294
+ }
8295
+ const connection = {
8069
8296
  async unsafe(query, params = []) {
8070
- const statement = db.query(query);
8071
- const args = [...params];
8072
- if (isRowReturning(query)) {
8073
- return statement.all(...args);
8297
+ const active = getActiveDatabaseConnection(connection);
8298
+ if (transactions.has(active))
8299
+ return active.unsafe(query, params);
8300
+ return enqueue(() => execute(query, params));
8301
+ },
8302
+ async begin(callback) {
8303
+ if (transactions.has(getActiveDatabaseConnection(connection))) {
8304
+ throw new Error("Use runInTransaction for nested SQLite transactions.");
8074
8305
  }
8075
- statement.run(...args);
8076
- return [];
8306
+ return enqueue(async () => {
8307
+ db.exec("BEGIN IMMEDIATE");
8308
+ let open = true;
8309
+ const transaction = {
8310
+ async unsafe(query, params = []) {
8311
+ if (!open)
8312
+ throw new Error("SQLite transaction is no longer active.");
8313
+ return execute(query, params);
8314
+ }
8315
+ };
8316
+ transactions.add(transaction);
8317
+ try {
8318
+ const result = await runWithDatabaseConnection(transaction, () => callback(transaction));
8319
+ db.exec("COMMIT");
8320
+ return result;
8321
+ } catch (error) {
8322
+ db.exec("ROLLBACK");
8323
+ throw error;
8324
+ } finally {
8325
+ open = false;
8326
+ }
8327
+ });
8077
8328
  },
8078
8329
  close() {
8330
+ if (pending > 0)
8331
+ throw new Error("Drain SQLite operations before closing the connection.");
8332
+ closed = true;
8079
8333
  db.close();
8080
8334
  }
8081
8335
  };
8336
+ return connection;
8082
8337
  }
8083
8338
  // ../../src/core/database/table.ts
8084
8339
  function defineTable(definition) {
@@ -8089,6 +8344,337 @@ function defineTable(definition) {
8089
8344
  init_transaction();
8090
8345
  init_eventBus();
8091
8346
 
8347
+ // ../../src/core/events/outbox/index.ts
8348
+ init_dialect();
8349
+ init_repositoryConnection();
8350
+ init_transaction();
8351
+ init_databaseTenantContext();
8352
+ init_tenantContext();
8353
+ init_tenantDatabaseScope();
8354
+
8355
+ // ../../src/core/events/outbox/migration.ts
8356
+ function createOutboxMigration(name, driver, options = {}) {
8357
+ const identity = driver === "mysql" ? " CHARACTER SET ascii COLLATE ascii_bin" : "";
8358
+ const payloadType = driver === "mysql" ? "MEDIUMTEXT" : "TEXT";
8359
+ const inlineIndex = driver === "mysql" ? ", INDEX strata_outbox_due (status, available_at, event_id, listener_name), INDEX strata_outbox_expired (status, lease_until, event_id, listener_name)" : "";
8360
+ return {
8361
+ name,
8362
+ async up(db) {
8363
+ await db.unsafe(`CREATE TABLE IF NOT EXISTS strata_outbox_event (
8364
+ id VARCHAR(64)${identity} PRIMARY KEY, name VARCHAR(128)${identity} NOT NULL, version INTEGER NOT NULL CHECK (version > 0),
8365
+ tenant_id BIGINT, payload ${payloadType} NOT NULL, created_at BIGINT NOT NULL,
8366
+ publication_token VARCHAR(64)${identity} NOT NULL)`);
8367
+ await db.unsafe(`CREATE TABLE IF NOT EXISTS strata_outbox_delivery (
8368
+ event_id VARCHAR(64)${identity} NOT NULL,
8369
+ listener_name VARCHAR(128)${identity} NOT NULL, status VARCHAR(16) NOT NULL CHECK (status IN ('pending', 'processing', 'failed', 'completed')),
8370
+ attempts INTEGER NOT NULL DEFAULT 0 CHECK (attempts >= 0), max_attempts INTEGER NOT NULL CHECK (max_attempts > 0),
8371
+ available_at BIGINT NOT NULL, lease_token VARCHAR(64)${identity}, lease_until BIGINT,
8372
+ completed_at BIGINT, error_code VARCHAR(64), PRIMARY KEY (event_id, listener_name)${inlineIndex},
8373
+ FOREIGN KEY (event_id) REFERENCES strata_outbox_event(id) ON DELETE CASCADE)`);
8374
+ if (driver !== "mysql") {
8375
+ await db.unsafe("CREATE INDEX IF NOT EXISTS strata_outbox_due ON strata_outbox_delivery (status, available_at, event_id, listener_name)");
8376
+ await db.unsafe("CREATE INDEX IF NOT EXISTS strata_outbox_expired ON strata_outbox_delivery (status, lease_until, event_id, listener_name)");
8377
+ }
8378
+ if (driver === "pgsql" && options.rls) {
8379
+ await db.unsafe(`ALTER TABLE strata_outbox_event ENABLE ROW LEVEL SECURITY`);
8380
+ await db.unsafe(`ALTER TABLE strata_outbox_event FORCE ROW LEVEL SECURITY`);
8381
+ await db.unsafe(`DROP POLICY IF EXISTS strata_outbox_scope ON strata_outbox_event`);
8382
+ await db.unsafe(`CREATE POLICY strata_outbox_scope ON strata_outbox_event USING (
8383
+ current_setting('app.bypass_rls', true) = 'true' OR
8384
+ tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::bigint OR
8385
+ (tenant_id IS NULL AND NULLIF(current_setting('app.tenant_id', true), '') IS NULL))`);
8386
+ await db.unsafe(`ALTER TABLE strata_outbox_delivery ENABLE ROW LEVEL SECURITY`);
8387
+ await db.unsafe(`ALTER TABLE strata_outbox_delivery FORCE ROW LEVEL SECURITY`);
8388
+ await db.unsafe(`DROP POLICY IF EXISTS strata_outbox_scope ON strata_outbox_delivery`);
8389
+ await db.unsafe(`CREATE POLICY strata_outbox_scope ON strata_outbox_delivery USING (
8390
+ current_setting('app.bypass_rls', true) = 'true' OR EXISTS (
8391
+ SELECT 1 FROM strata_outbox_event WHERE id = strata_outbox_delivery.event_id))`);
8392
+ }
8393
+ },
8394
+ async down(db) {
8395
+ const [row] = await db.unsafe("SELECT COUNT(*) AS pending FROM strata_outbox_delivery WHERE status <> 'completed'");
8396
+ if (Number(row?.pending) > 0)
8397
+ throw new Error("Cannot drop outbox tables while undelivered events remain.");
8398
+ await db.unsafe("DROP TABLE strata_outbox_delivery");
8399
+ await db.unsafe("DROP TABLE strata_outbox_event");
8400
+ }
8401
+ };
8402
+ }
8403
+
8404
+ // ../../src/core/events/outbox/index.ts
8405
+ function positiveInteger(value, name) {
8406
+ if (!Number.isSafeInteger(value) || value < 1)
8407
+ throw new Error(`${name} must be a positive integer.`);
8408
+ return value;
8409
+ }
8410
+ function identifier(value, max) {
8411
+ if (!value || value.length > max || !/^[\w.:-]+$/.test(value))
8412
+ throw new Error("Invalid durable event/listener identifier.");
8413
+ return value;
8414
+ }
8415
+ function placeholders(count) {
8416
+ return Array.from({ length: count }, (_, index) => currentSqlDialect().placeholder(index + 1));
8417
+ }
8418
+ async function clock() {
8419
+ const [row] = await repositoryConnection.unsafe(`SELECT ${currentSqlDialect().epochMillisecondsExpression()} AS now_ms`);
8420
+ const now = Number(row?.now_ms);
8421
+ if (!Number.isSafeInteger(now))
8422
+ throw new Error("Database did not return a valid outbox clock.");
8423
+ return now;
8424
+ }
8425
+ async function privileged(work) {
8426
+ if (hasActiveTransaction())
8427
+ throw new Error("Outbox coordination must run outside business transactions.");
8428
+ for (let attempt = 0;; attempt++) {
8429
+ try {
8430
+ return await runWithMigrationBypass(() => runInTransaction(work));
8431
+ } catch (error) {
8432
+ const failure = error !== null && typeof error === "object" ? error : {};
8433
+ const code = String(failure.errno ?? failure.code);
8434
+ if (attempt >= 4 || !["40001", "40P01", "1213"].includes(code))
8435
+ throw error;
8436
+ await Bun.sleep(10 * (attempt + 1));
8437
+ }
8438
+ }
8439
+ }
8440
+
8441
+ class SqlOutbox {
8442
+ listeners = new Map;
8443
+ leaseMs;
8444
+ maxAttempts;
8445
+ retryDelayMs;
8446
+ maxPayloadBytes;
8447
+ tenantResolver;
8448
+ constructor(options) {
8449
+ this.leaseMs = positiveInteger(options.leaseMs ?? 60000, "leaseMs");
8450
+ if (this.leaseMs < 30)
8451
+ throw new Error("leaseMs must be at least 30 milliseconds.");
8452
+ this.maxAttempts = positiveInteger(options.maxAttempts ?? 5, "maxAttempts");
8453
+ this.retryDelayMs = positiveInteger(options.retryDelayMs ?? 1000, "retryDelayMs");
8454
+ this.maxPayloadBytes = positiveInteger(options.maxPayloadBytes ?? 65536, "maxPayloadBytes");
8455
+ this.tenantResolver = options.resolveTenant ?? resolveTenant;
8456
+ for (const listener of options.listeners) {
8457
+ identifier(listener.name, 128);
8458
+ identifier(listener.event, 128);
8459
+ if (this.listeners.has(listener.name))
8460
+ throw new Error("Durable listener names must be unique.");
8461
+ this.listeners.set(listener.name, listener);
8462
+ }
8463
+ }
8464
+ hasListener(name, event) {
8465
+ const listener = this.listeners.get(name);
8466
+ return listener !== undefined && (event === undefined || listener.event === event);
8467
+ }
8468
+ async publish(name, payload, options = {}) {
8469
+ if (!hasActiveTransaction())
8470
+ throw new Error("Durable publication requires an active business transaction.");
8471
+ return runInTransaction(() => this.persist(name, payload, options));
8472
+ }
8473
+ async persist(name, payload, options) {
8474
+ identifier(name, 128);
8475
+ const id = identifier(options.id ?? crypto.randomUUID(), 64);
8476
+ const version = positiveInteger(options.version ?? 1, "version");
8477
+ const serialized = JSON.stringify(payload);
8478
+ if (serialized === undefined || new TextEncoder().encode(serialized).byteLength > this.maxPayloadBytes)
8479
+ throw new Error("Invalid or oversized durable payload.");
8480
+ const listeners = [...this.listeners.values()].filter((listener) => listener.event === name);
8481
+ if (listeners.length === 0)
8482
+ throw new Error("No durable listeners registered for this event.");
8483
+ const tenantId = currentTenant()?.id ?? null;
8484
+ if (tenantId !== null)
8485
+ positiveInteger(tenantId, "tenantId");
8486
+ const token = crypto.randomUUID();
8487
+ const now = await clock();
8488
+ await repositoryConnection.unsafe(`INSERT INTO strata_outbox_event (id, name, version, tenant_id, payload, created_at, publication_token)
8489
+ VALUES (${placeholders(7).join(", ")})${currentSqlDialect().upsertSuffix(["id"], [])}`, [id, name, version, tenantId, serialized, now, token]);
8490
+ const [existing] = await repositoryConnection.unsafe(`SELECT * FROM strata_outbox_event WHERE id = ${placeholders(1)[0]}`, [id]);
8491
+ if (!existing || existing.name !== name || Number(existing.version) !== version || (existing.tenant_id === null ? null : Number(existing.tenant_id)) !== tenantId || existing.payload !== serialized)
8492
+ throw new Error("Conflicting durable event ID.");
8493
+ if (existing.publication_token !== token)
8494
+ return id;
8495
+ for (const listener of listeners) {
8496
+ await repositoryConnection.unsafe(`INSERT INTO strata_outbox_delivery (event_id, listener_name, status, max_attempts, available_at)
8497
+ VALUES (${placeholders(5).join(", ")})`, [id, listener.name, "pending", this.maxAttempts, now]);
8498
+ }
8499
+ return id;
8500
+ }
8501
+ async claim() {
8502
+ return privileged(async () => {
8503
+ const now = await clock();
8504
+ const p = placeholders(2);
8505
+ const columns = "event_id, listener_name, attempts, max_attempts";
8506
+ const [expired] = await repositoryConnection.unsafe(`SELECT ${columns} FROM strata_outbox_delivery WHERE status = 'processing' AND lease_until <= ${p[0]}
8507
+ ORDER BY lease_until, event_id, listener_name LIMIT 1${currentSqlDialect().rowLockClause(true)}`, [now]);
8508
+ const delivery = expired ?? (await repositoryConnection.unsafe(`SELECT ${columns} FROM strata_outbox_delivery WHERE status = 'pending' AND available_at <= ${p[0]}
8509
+ ORDER BY available_at, event_id, listener_name LIMIT 1${currentSqlDialect().rowLockClause(true)}`, [now]))[0];
8510
+ if (!delivery)
8511
+ return null;
8512
+ const [event] = await repositoryConnection.unsafe(`SELECT * FROM strata_outbox_event WHERE id = ${p[0]}`, [delivery.event_id]);
8513
+ if (!event)
8514
+ throw new Error("Outbox event missing for delivery.");
8515
+ const candidate = { ...event, ...delivery };
8516
+ const token = crypto.randomUUID();
8517
+ const exhausted = Number(candidate.attempts) >= Number(candidate.max_attempts);
8518
+ const q = placeholders(9);
8519
+ await repositoryConnection.unsafe(`UPDATE strata_outbox_delivery SET status = ${q[0]}, lease_token = ${q[1]}, lease_until = ${q[2]},
8520
+ attempts = ${q[3]}, error_code = ${exhausted ? "'attempts_exhausted'" : "error_code"} WHERE event_id = ${q[4]} AND listener_name = ${q[5]} AND
8521
+ ((status = 'pending' AND available_at <= ${q[6]}) OR (status = 'processing' AND lease_until <= ${q[7]})) AND attempts = ${q[8]}`, [
8522
+ exhausted ? "failed" : "processing",
8523
+ token,
8524
+ exhausted ? null : now + this.leaseMs,
8525
+ Number(candidate.attempts) + (exhausted ? 0 : 1),
8526
+ candidate.id,
8527
+ candidate.listener_name,
8528
+ now,
8529
+ now,
8530
+ candidate.attempts
8531
+ ]);
8532
+ const [owner] = await repositoryConnection.unsafe(`SELECT lease_token FROM strata_outbox_delivery WHERE event_id = ${p[0]} AND listener_name = ${p[1]}${currentSqlDialect().rowLockClause()}`, [candidate.id, candidate.listener_name]);
8533
+ if (owner?.lease_token !== token)
8534
+ return null;
8535
+ if (exhausted)
8536
+ return "exhausted";
8537
+ return { ...candidate, attempts: Number(candidate.attempts) + 1, lease_token: token };
8538
+ });
8539
+ }
8540
+ async renew(claim) {
8541
+ return privileged(async () => {
8542
+ const now = await clock();
8543
+ const p = placeholders(5);
8544
+ await repositoryConnection.unsafe(`UPDATE strata_outbox_delivery SET lease_until = ${p[0]} WHERE event_id = ${p[1]}
8545
+ AND listener_name = ${p[2]} AND lease_token = ${p[3]} AND status = 'processing' AND lease_until > ${p[4]}`, [now + this.leaseMs, claim.id, claim.listener_name, claim.lease_token, now]);
8546
+ const q = placeholders(3);
8547
+ const [owner] = await repositoryConnection.unsafe(`SELECT lease_until FROM strata_outbox_delivery WHERE event_id = ${q[0]} AND listener_name = ${q[1]} AND lease_token = ${q[2]} AND status = 'processing'${currentSqlDialect().rowLockClause()}`, [claim.id, claim.listener_name, claim.lease_token]);
8548
+ return Number(owner?.lease_until) > now;
8549
+ });
8550
+ }
8551
+ async finish(claim, success) {
8552
+ await privileged(async () => {
8553
+ const now = await clock();
8554
+ const status = success ? "completed" : claim.attempts >= Number(claim.max_attempts) ? "failed" : "pending";
8555
+ const delay = Math.min(this.retryDelayMs * 2 ** Math.min(claim.attempts - 1, 16), 300000);
8556
+ const p = placeholders(9);
8557
+ await repositoryConnection.unsafe(`UPDATE strata_outbox_delivery SET status = ${p[0]}, available_at = ${p[1]}, completed_at = ${p[2]},
8558
+ error_code = ${p[3]}, lease_until = NULL WHERE event_id = ${p[4]} AND listener_name = ${p[5]}
8559
+ AND lease_token = ${p[6]} AND status = 'processing' AND lease_until > ${p[7]} AND attempts = ${p[8]}`, [
8560
+ status,
8561
+ now + delay,
8562
+ success ? now : null,
8563
+ success ? null : "listener_failed",
8564
+ claim.id,
8565
+ claim.listener_name,
8566
+ claim.lease_token,
8567
+ now,
8568
+ claim.attempts
8569
+ ]);
8570
+ });
8571
+ }
8572
+ async processNext(options = {}) {
8573
+ if (hasActiveTransaction())
8574
+ throw new Error("Outbox workers must run outside business transactions.");
8575
+ options.signal?.throwIfAborted();
8576
+ const claim = await this.claim();
8577
+ if (!claim)
8578
+ return false;
8579
+ if (claim === "exhausted")
8580
+ return true;
8581
+ const controller = new AbortController;
8582
+ const forward = () => controller.abort(options.signal?.reason);
8583
+ options.signal?.addEventListener("abort", forward, { once: true });
8584
+ if (options.signal?.aborted)
8585
+ forward();
8586
+ let renewal;
8587
+ let watchdog = setTimeout(() => controller.abort(new Error("Outbox lease expired.")), this.leaseMs);
8588
+ const heartbeat = setInterval(() => {
8589
+ if (renewal || controller.signal.aborted)
8590
+ return;
8591
+ renewal = (async () => {
8592
+ try {
8593
+ if (!await this.renew(claim))
8594
+ controller.abort(new Error("Outbox ownership lost."));
8595
+ else {
8596
+ clearTimeout(watchdog);
8597
+ watchdog = setTimeout(() => controller.abort(new Error("Outbox lease expired.")), this.leaseMs);
8598
+ }
8599
+ } catch {
8600
+ controller.abort(new Error("Outbox lease renewal failed."));
8601
+ } finally {
8602
+ renewal = undefined;
8603
+ }
8604
+ })();
8605
+ }, Math.max(10, Math.floor(this.leaseMs / 3)));
8606
+ let success = false;
8607
+ try {
8608
+ const listener = this.listeners.get(claim.listener_name);
8609
+ if (!listener || listener.event !== claim.name)
8610
+ throw new Error("Durable listener is not registered.");
8611
+ const event = {
8612
+ id: claim.id,
8613
+ name: claim.name,
8614
+ version: Number(claim.version),
8615
+ tenantId: claim.tenant_id === null ? null : Number(claim.tenant_id),
8616
+ payload: JSON.parse(claim.payload)
8617
+ };
8618
+ const tenant = event.tenantId === null ? null : await privileged(() => this.tenantResolver(Number(event.tenantId)));
8619
+ if (event.tenantId !== null && (!tenant || tenant.id !== event.tenantId))
8620
+ throw new Error("Outbox tenant could not be resolved.");
8621
+ const handle = async () => {
8622
+ controller.signal.throwIfAborted();
8623
+ await listener.handle(event, { signal: controller.signal });
8624
+ controller.signal.throwIfAborted();
8625
+ };
8626
+ if (tenant)
8627
+ await runWithTenantDatabase(tenant, handle);
8628
+ else
8629
+ await handle();
8630
+ success = true;
8631
+ } catch {
8632
+ success = false;
8633
+ } finally {
8634
+ clearInterval(heartbeat);
8635
+ await renewal;
8636
+ clearTimeout(watchdog);
8637
+ options.signal?.removeEventListener("abort", forward);
8638
+ }
8639
+ await this.finish(claim, success && !controller.signal.aborted);
8640
+ return true;
8641
+ }
8642
+ async work(options) {
8643
+ const pollMs = positiveInteger(options.pollMs ?? 1000, "pollMs");
8644
+ while (!options.signal.aborted) {
8645
+ try {
8646
+ if (await this.processNext({ signal: options.signal }))
8647
+ continue;
8648
+ } catch (error) {
8649
+ if (options.signal.aborted)
8650
+ return;
8651
+ throw error;
8652
+ }
8653
+ await new Promise((resolve) => {
8654
+ const done = () => {
8655
+ clearTimeout(timer);
8656
+ options.signal.removeEventListener("abort", done);
8657
+ resolve();
8658
+ };
8659
+ const timer = setTimeout(done, pollMs);
8660
+ options.signal.addEventListener("abort", done, { once: true });
8661
+ if (options.signal.aborted)
8662
+ done();
8663
+ });
8664
+ }
8665
+ }
8666
+ async replay(eventId, listenerName) {
8667
+ identifier(eventId, 64);
8668
+ identifier(listenerName, 128);
8669
+ await privileged(async () => {
8670
+ const now = await clock();
8671
+ const p = placeholders(3);
8672
+ await repositoryConnection.unsafe(`UPDATE strata_outbox_delivery SET status = 'pending', attempts = 0, available_at = ${p[0]},
8673
+ lease_token = NULL, lease_until = NULL, completed_at = NULL, error_code = NULL
8674
+ WHERE event_id = ${p[1]} AND listener_name = ${p[2]} AND status = 'failed'`, [now, eventId, listenerName]);
8675
+ });
8676
+ }
8677
+ }
8092
8678
  // ../../src/core/mail/mailer.ts
8093
8679
  function resolveSmtpConfig() {
8094
8680
  const host = process.env.MAIL_HOST?.trim();
@@ -9895,7 +10481,7 @@ function logServerError(error, mappedError) {
9895
10481
  });
9896
10482
  }
9897
10483
  async function webErrorResponse(error, request) {
9898
- if (!request || !isViewsEnabled() || requestPrefersJson(request)) {
10484
+ if (currentRequestMeta().errorFormat === "json" || !request || !isViewsEnabled() || requestPrefersJson(request)) {
9899
10485
  return null;
9900
10486
  }
9901
10487
  const mappedError = toHttpError(error) ?? mapDatabaseError(error);
@@ -12408,6 +12994,7 @@ export {
12408
12994
  Schedule,
12409
12995
  Schema,
12410
12996
  ServiceContainer,
12997
+ SqlOutbox,
12411
12998
  SqliteGrammar,
12412
12999
  StorageManager,
12413
13000
  SyncQueue,
@@ -12502,6 +13089,7 @@ export {
12502
13089
  createMysqlPool,
12503
13090
  createNotificationDispatcher,
12504
13091
  createOidcHandshake,
13092
+ createOutboxMigration,
12505
13093
  createProductionQueue,
12506
13094
  createQueue,
12507
13095
  createQueueWorker,
@@ -12567,6 +13155,7 @@ export {
12567
13155
  grammarForDriver,
12568
13156
  guestCanViewResource,
12569
13157
  hasActiveDatabaseConnection,
13158
+ hasActiveTransaction,
12570
13159
  hasMany,
12571
13160
  hasManyThrough,
12572
13161
  hasMinimumOrgRole2 as hasMinimumOrgRole,