@getstrata/core 2.0.2 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/core/database/dialect.d.ts +4 -0
  3. package/dist/core/database/mysqlConnection.d.ts +12 -2
  4. package/dist/core/database/sqliteConnection.d.ts +2 -1
  5. package/dist/core/database/transaction.d.ts +2 -2
  6. package/dist/core/database/transactionControl.d.ts +2 -1
  7. package/dist/core/events/outbox/index.d.ts +54 -0
  8. package/dist/core/events/outbox/migration.d.ts +7 -0
  9. package/dist/core/http/requestMetaContext.d.ts +2 -0
  10. package/dist/core/http/response.d.ts +2 -1
  11. package/dist/core/security/safeFetch.d.ts +1 -0
  12. package/dist/entries/audit/exportAuditLogs.js +119 -17
  13. package/dist/entries/auth/basicAuthGuard.js +18 -0
  14. package/dist/entries/auth/oauth/oidcProvider.js +100 -17
  15. package/dist/entries/auth/oauth/providers.js +100 -17
  16. package/dist/entries/auth/oauth/samlProvider.js +18 -0
  17. package/dist/entries/auth/oneTimeToken.js +18 -0
  18. package/dist/entries/auth/passwordLogin.js +18 -0
  19. package/dist/entries/auth/saml/samlServiceProvider.js +18 -0
  20. package/dist/entries/auth/scimAuthMiddleware.js +19 -0
  21. package/dist/entries/database/migrations.js +18 -0
  22. package/dist/entries/database/sqliteConnection.js +101 -7
  23. package/dist/entries/events/outbox.js +1 -0
  24. package/dist/entries/http/response.js +39 -5
  25. package/dist/entries/jobs/exportAuditLogsJob.js +119 -17
  26. package/dist/entries/security/safeFetch.js +100 -17
  27. package/dist/entries/tenant/databaseTenantContext.js +19 -0
  28. package/dist/entries/tenant/tenantDatabaseScope.js +19 -0
  29. package/dist/entries/tracing/tracingMiddleware.js +100 -17
  30. package/dist/framework/public-api.d.ts +3 -1
  31. package/dist/index.js +591 -50
  32. package/package.json +6 -1
@@ -164,6 +164,7 @@ async function runWithTransactionScope(callback) {
164
164
  var TransactionRollback, transactionScopes;
165
165
  var init_transactionControl = __esm(() => {
166
166
  init_asyncContextStore();
167
+ init_connectionContext();
167
168
  TransactionRollback = class TransactionRollback extends Error {
168
169
  result;
169
170
  constructor(result) {
@@ -452,6 +453,12 @@ var init_dialect = __esm(() => {
452
453
  nowExpression() {
453
454
  return "NOW()";
454
455
  },
456
+ epochMillisecondsExpression() {
457
+ return "CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)";
458
+ },
459
+ rowLockClause(skipLocked = false) {
460
+ return ` FOR UPDATE${skipLocked ? " SKIP LOCKED" : ""}`;
461
+ },
455
462
  timestampValue(value) {
456
463
  return value.toISOString();
457
464
  },
@@ -490,6 +497,12 @@ var init_dialect = __esm(() => {
490
497
  nowExpression() {
491
498
  return "CURRENT_TIMESTAMP";
492
499
  },
500
+ epochMillisecondsExpression() {
501
+ return "CAST(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000 AS UNSIGNED)";
502
+ },
503
+ rowLockClause(skipLocked = false) {
504
+ return ` FOR UPDATE${skipLocked ? " SKIP LOCKED" : ""}`;
505
+ },
493
506
  timestampValue(value) {
494
507
  return value.toISOString().slice(0, 19).replace("T", " ");
495
508
  },
@@ -528,6 +541,12 @@ var init_dialect = __esm(() => {
528
541
  nowExpression() {
529
542
  return "strftime('%Y-%m-%dT%H:%M:%fZ', 'now')";
530
543
  },
544
+ epochMillisecondsExpression() {
545
+ return "CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)";
546
+ },
547
+ rowLockClause() {
548
+ return "";
549
+ },
531
550
  timestampValue(value) {
532
551
  return value.toISOString();
533
552
  },
@@ -1077,50 +1096,133 @@ async function resolveSafeOutboundTarget(rawUrl, options = {}) {
1077
1096
 
1078
1097
  // ../../src/core/security/safeFetch.ts
1079
1098
  var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
1099
+ function untilAborted(work, signal) {
1100
+ signal.throwIfAborted();
1101
+ return new Promise((resolve, reject) => {
1102
+ const onAbort = () => reject(signal.reason);
1103
+ signal.addEventListener("abort", onAbort, { once: true });
1104
+ work.then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
1105
+ });
1106
+ }
1107
+ function preserveResponseMetadata(response, original) {
1108
+ Object.defineProperties(response, {
1109
+ url: { value: original.url },
1110
+ redirected: { value: original.redirected },
1111
+ type: { value: original.type }
1112
+ });
1113
+ const clone = response.clone.bind(response);
1114
+ response.clone = () => preserveResponseMetadata(clone(), original);
1115
+ return response;
1116
+ }
1117
+ function withBodyDeadline(response, signal, release) {
1118
+ if (!response.body) {
1119
+ release();
1120
+ return response;
1121
+ }
1122
+ const reader = response.body.getReader();
1123
+ let stopped = false;
1124
+ let bodyController;
1125
+ const stop = (reason, errorBody) => {
1126
+ if (stopped)
1127
+ return;
1128
+ stopped = true;
1129
+ release();
1130
+ signal.removeEventListener("abort", onAbort);
1131
+ if (errorBody)
1132
+ bodyController.error(reason);
1133
+ reader.cancel(reason).catch(() => {}).finally(() => reader.releaseLock());
1134
+ };
1135
+ const onAbort = () => stop(signal.reason, true);
1136
+ const body = new ReadableStream({
1137
+ start(controller) {
1138
+ bodyController = controller;
1139
+ signal.addEventListener("abort", onAbort, { once: true });
1140
+ if (signal.aborted)
1141
+ onAbort();
1142
+ },
1143
+ async pull(controller) {
1144
+ try {
1145
+ const result = await untilAborted(reader.read(), signal);
1146
+ if (stopped)
1147
+ return;
1148
+ if (result.done) {
1149
+ stopped = true;
1150
+ release();
1151
+ signal.removeEventListener("abort", onAbort);
1152
+ reader.releaseLock();
1153
+ controller.close();
1154
+ } else {
1155
+ controller.enqueue(result.value);
1156
+ }
1157
+ } catch (error) {
1158
+ stop(error, true);
1159
+ }
1160
+ },
1161
+ cancel(reason) {
1162
+ stop(reason, false);
1163
+ }
1164
+ }, { highWaterMark: 0 });
1165
+ return preserveResponseMetadata(new Response(body, {
1166
+ status: response.status,
1167
+ statusText: response.statusText,
1168
+ headers: response.headers
1169
+ }), response);
1170
+ }
1080
1171
  async function safeFetch(input, init = {}, options = {}) {
1172
+ init.signal?.throwIfAborted();
1081
1173
  const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
1174
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
1175
+ throw new RangeError("safeFetch timeoutMs must be a positive finite number.");
1176
+ }
1082
1177
  const maxRedirects = options.maxRedirects ?? 0;
1083
- const resolveDns = options.resolveDns ?? true;
1084
1178
  const urlOptions = {
1085
1179
  allowHttp: options.allowHttp,
1086
- resolveDns,
1180
+ resolveDns: options.resolveDns ?? true,
1087
1181
  allowPrivate: options.allowPrivate
1088
1182
  };
1089
1183
  const controller = new AbortController;
1090
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
1184
+ const forwardAbort = () => controller.abort(init.signal?.reason);
1185
+ init.signal?.addEventListener("abort", forwardAbort, { once: true });
1186
+ const timeout = setTimeout(() => controller.abort(new DOMException("The operation timed out.", "TimeoutError")), timeoutMs);
1187
+ const release = () => {
1188
+ clearTimeout(timeout);
1189
+ init.signal?.removeEventListener("abort", forwardAbort);
1190
+ };
1091
1191
  try {
1092
- let current = await resolveSafeOutboundTarget(input, urlOptions);
1192
+ let current = await untilAborted(resolveSafeOutboundTarget(input, urlOptions), controller.signal);
1093
1193
  let redirectCount = 0;
1094
1194
  while (true) {
1195
+ controller.signal.throwIfAborted();
1095
1196
  const address = current.addresses[0];
1096
1197
  const fetchUrl = address ? pinUrlToAddress(current.url, address).toString() : current.url.toString();
1097
1198
  const headers = new Headers(init.headers);
1098
- if (address && !headers.has("host")) {
1199
+ if (address && !headers.has("host"))
1099
1200
  headers.set("Host", current.url.host);
1100
- }
1101
1201
  const fetchInit = {
1102
1202
  ...init,
1103
1203
  headers,
1104
1204
  signal: controller.signal,
1105
1205
  redirect: "manual"
1106
1206
  };
1107
- if (address) {
1207
+ if (address)
1108
1208
  fetchInit.tls = { serverName: current.url.hostname };
1109
- }
1110
- const response = await fetch(fetchUrl, fetchInit);
1209
+ const response = await untilAborted(fetch(fetchUrl, fetchInit), controller.signal);
1111
1210
  if (response.status >= 300 && response.status < 400) {
1112
1211
  const location = response.headers.get("location");
1113
- if (!location || redirectCount >= maxRedirects) {
1114
- return response;
1212
+ if (location && redirectCount < maxRedirects) {
1213
+ if (response.body)
1214
+ await untilAborted(response.body.cancel(), controller.signal);
1215
+ current = await untilAborted(resolveSafeOutboundTarget(new URL(location, current.url).toString(), urlOptions), controller.signal);
1216
+ redirectCount++;
1217
+ continue;
1115
1218
  }
1116
- current = await resolveSafeOutboundTarget(new URL(location, current.url).toString(), urlOptions);
1117
- redirectCount += 1;
1118
- continue;
1119
1219
  }
1120
- return response;
1220
+ return withBodyDeadline(response, controller.signal, release);
1121
1221
  }
1122
- } finally {
1123
- clearTimeout(timeout);
1222
+ } catch (error) {
1223
+ controller.abort(error);
1224
+ release();
1225
+ throw error;
1124
1226
  }
1125
1227
  }
1126
1228
 
@@ -261,50 +261,133 @@ async function resolveSafeOutboundTarget(rawUrl, options = {}) {
261
261
 
262
262
  // ../../src/core/security/safeFetch.ts
263
263
  var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
264
+ function untilAborted(work, signal) {
265
+ signal.throwIfAborted();
266
+ return new Promise((resolve, reject) => {
267
+ const onAbort = () => reject(signal.reason);
268
+ signal.addEventListener("abort", onAbort, { once: true });
269
+ work.then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
270
+ });
271
+ }
272
+ function preserveResponseMetadata(response, original) {
273
+ Object.defineProperties(response, {
274
+ url: { value: original.url },
275
+ redirected: { value: original.redirected },
276
+ type: { value: original.type }
277
+ });
278
+ const clone = response.clone.bind(response);
279
+ response.clone = () => preserveResponseMetadata(clone(), original);
280
+ return response;
281
+ }
282
+ function withBodyDeadline(response, signal, release) {
283
+ if (!response.body) {
284
+ release();
285
+ return response;
286
+ }
287
+ const reader = response.body.getReader();
288
+ let stopped = false;
289
+ let bodyController;
290
+ const stop = (reason, errorBody) => {
291
+ if (stopped)
292
+ return;
293
+ stopped = true;
294
+ release();
295
+ signal.removeEventListener("abort", onAbort);
296
+ if (errorBody)
297
+ bodyController.error(reason);
298
+ reader.cancel(reason).catch(() => {}).finally(() => reader.releaseLock());
299
+ };
300
+ const onAbort = () => stop(signal.reason, true);
301
+ const body = new ReadableStream({
302
+ start(controller) {
303
+ bodyController = controller;
304
+ signal.addEventListener("abort", onAbort, { once: true });
305
+ if (signal.aborted)
306
+ onAbort();
307
+ },
308
+ async pull(controller) {
309
+ try {
310
+ const result = await untilAborted(reader.read(), signal);
311
+ if (stopped)
312
+ return;
313
+ if (result.done) {
314
+ stopped = true;
315
+ release();
316
+ signal.removeEventListener("abort", onAbort);
317
+ reader.releaseLock();
318
+ controller.close();
319
+ } else {
320
+ controller.enqueue(result.value);
321
+ }
322
+ } catch (error) {
323
+ stop(error, true);
324
+ }
325
+ },
326
+ cancel(reason) {
327
+ stop(reason, false);
328
+ }
329
+ }, { highWaterMark: 0 });
330
+ return preserveResponseMetadata(new Response(body, {
331
+ status: response.status,
332
+ statusText: response.statusText,
333
+ headers: response.headers
334
+ }), response);
335
+ }
264
336
  async function safeFetch(input, init = {}, options = {}) {
337
+ init.signal?.throwIfAborted();
265
338
  const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
339
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
340
+ throw new RangeError("safeFetch timeoutMs must be a positive finite number.");
341
+ }
266
342
  const maxRedirects = options.maxRedirects ?? 0;
267
- const resolveDns = options.resolveDns ?? true;
268
343
  const urlOptions = {
269
344
  allowHttp: options.allowHttp,
270
- resolveDns,
345
+ resolveDns: options.resolveDns ?? true,
271
346
  allowPrivate: options.allowPrivate
272
347
  };
273
348
  const controller = new AbortController;
274
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
349
+ const forwardAbort = () => controller.abort(init.signal?.reason);
350
+ init.signal?.addEventListener("abort", forwardAbort, { once: true });
351
+ const timeout = setTimeout(() => controller.abort(new DOMException("The operation timed out.", "TimeoutError")), timeoutMs);
352
+ const release = () => {
353
+ clearTimeout(timeout);
354
+ init.signal?.removeEventListener("abort", forwardAbort);
355
+ };
275
356
  try {
276
- let current = await resolveSafeOutboundTarget(input, urlOptions);
357
+ let current = await untilAborted(resolveSafeOutboundTarget(input, urlOptions), controller.signal);
277
358
  let redirectCount = 0;
278
359
  while (true) {
360
+ controller.signal.throwIfAborted();
279
361
  const address = current.addresses[0];
280
362
  const fetchUrl = address ? pinUrlToAddress(current.url, address).toString() : current.url.toString();
281
363
  const headers = new Headers(init.headers);
282
- if (address && !headers.has("host")) {
364
+ if (address && !headers.has("host"))
283
365
  headers.set("Host", current.url.host);
284
- }
285
366
  const fetchInit = {
286
367
  ...init,
287
368
  headers,
288
369
  signal: controller.signal,
289
370
  redirect: "manual"
290
371
  };
291
- if (address) {
372
+ if (address)
292
373
  fetchInit.tls = { serverName: current.url.hostname };
293
- }
294
- const response = await fetch(fetchUrl, fetchInit);
374
+ const response = await untilAborted(fetch(fetchUrl, fetchInit), controller.signal);
295
375
  if (response.status >= 300 && response.status < 400) {
296
376
  const location = response.headers.get("location");
297
- if (!location || redirectCount >= maxRedirects) {
298
- return response;
377
+ if (location && redirectCount < maxRedirects) {
378
+ if (response.body)
379
+ await untilAborted(response.body.cancel(), controller.signal);
380
+ current = await untilAborted(resolveSafeOutboundTarget(new URL(location, current.url).toString(), urlOptions), controller.signal);
381
+ redirectCount++;
382
+ continue;
299
383
  }
300
- current = await resolveSafeOutboundTarget(new URL(location, current.url).toString(), urlOptions);
301
- redirectCount += 1;
302
- continue;
303
384
  }
304
- return response;
385
+ return withBodyDeadline(response, controller.signal, release);
305
386
  }
306
- } finally {
307
- clearTimeout(timeout);
387
+ } catch (error) {
388
+ controller.abort(error);
389
+ release();
390
+ throw error;
308
391
  }
309
392
  }
310
393
  export {
@@ -164,6 +164,7 @@ async function runWithTransactionScope(callback) {
164
164
  var TransactionRollback, transactionScopes;
165
165
  var init_transactionControl = __esm(() => {
166
166
  init_asyncContextStore();
167
+ init_connectionContext();
167
168
  TransactionRollback = class TransactionRollback extends Error {
168
169
  result;
169
170
  constructor(result) {
@@ -452,6 +453,12 @@ var init_dialect = __esm(() => {
452
453
  nowExpression() {
453
454
  return "NOW()";
454
455
  },
456
+ epochMillisecondsExpression() {
457
+ return "CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)";
458
+ },
459
+ rowLockClause(skipLocked = false) {
460
+ return ` FOR UPDATE${skipLocked ? " SKIP LOCKED" : ""}`;
461
+ },
455
462
  timestampValue(value) {
456
463
  return value.toISOString();
457
464
  },
@@ -490,6 +497,12 @@ var init_dialect = __esm(() => {
490
497
  nowExpression() {
491
498
  return "CURRENT_TIMESTAMP";
492
499
  },
500
+ epochMillisecondsExpression() {
501
+ return "CAST(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000 AS UNSIGNED)";
502
+ },
503
+ rowLockClause(skipLocked = false) {
504
+ return ` FOR UPDATE${skipLocked ? " SKIP LOCKED" : ""}`;
505
+ },
493
506
  timestampValue(value) {
494
507
  return value.toISOString().slice(0, 19).replace("T", " ");
495
508
  },
@@ -528,6 +541,12 @@ var init_dialect = __esm(() => {
528
541
  nowExpression() {
529
542
  return "strftime('%Y-%m-%dT%H:%M:%fZ', 'now')";
530
543
  },
544
+ epochMillisecondsExpression() {
545
+ return "CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)";
546
+ },
547
+ rowLockClause() {
548
+ return "";
549
+ },
531
550
  timestampValue(value) {
532
551
  return value.toISOString();
533
552
  },
@@ -105,6 +105,7 @@ async function runWithTransactionScope(callback) {
105
105
  var TransactionRollback, transactionScopes;
106
106
  var init_transactionControl = __esm(() => {
107
107
  init_asyncContextStore();
108
+ init_connectionContext();
108
109
  TransactionRollback = class TransactionRollback extends Error {
109
110
  result;
110
111
  constructor(result) {
@@ -386,6 +387,12 @@ var init_dialect = __esm(() => {
386
387
  nowExpression() {
387
388
  return "NOW()";
388
389
  },
390
+ epochMillisecondsExpression() {
391
+ return "CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)";
392
+ },
393
+ rowLockClause(skipLocked = false) {
394
+ return ` FOR UPDATE${skipLocked ? " SKIP LOCKED" : ""}`;
395
+ },
389
396
  timestampValue(value) {
390
397
  return value.toISOString();
391
398
  },
@@ -424,6 +431,12 @@ var init_dialect = __esm(() => {
424
431
  nowExpression() {
425
432
  return "CURRENT_TIMESTAMP";
426
433
  },
434
+ epochMillisecondsExpression() {
435
+ return "CAST(UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000 AS UNSIGNED)";
436
+ },
437
+ rowLockClause(skipLocked = false) {
438
+ return ` FOR UPDATE${skipLocked ? " SKIP LOCKED" : ""}`;
439
+ },
427
440
  timestampValue(value) {
428
441
  return value.toISOString().slice(0, 19).replace("T", " ");
429
442
  },
@@ -462,6 +475,12 @@ var init_dialect = __esm(() => {
462
475
  nowExpression() {
463
476
  return "strftime('%Y-%m-%dT%H:%M:%fZ', 'now')";
464
477
  },
478
+ epochMillisecondsExpression() {
479
+ return "CAST((julianday('now') - 2440587.5) * 86400000 AS INTEGER)";
480
+ },
481
+ rowLockClause() {
482
+ return "";
483
+ },
465
484
  timestampValue(value) {
466
485
  return value.toISOString();
467
486
  },
@@ -355,50 +355,133 @@ async function resolveSafeOutboundTarget(rawUrl, options = {}) {
355
355
 
356
356
  // ../../src/core/security/safeFetch.ts
357
357
  var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
358
+ function untilAborted(work, signal) {
359
+ signal.throwIfAborted();
360
+ return new Promise((resolve, reject) => {
361
+ const onAbort = () => reject(signal.reason);
362
+ signal.addEventListener("abort", onAbort, { once: true });
363
+ work.then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
364
+ });
365
+ }
366
+ function preserveResponseMetadata(response, original) {
367
+ Object.defineProperties(response, {
368
+ url: { value: original.url },
369
+ redirected: { value: original.redirected },
370
+ type: { value: original.type }
371
+ });
372
+ const clone = response.clone.bind(response);
373
+ response.clone = () => preserveResponseMetadata(clone(), original);
374
+ return response;
375
+ }
376
+ function withBodyDeadline(response, signal, release) {
377
+ if (!response.body) {
378
+ release();
379
+ return response;
380
+ }
381
+ const reader = response.body.getReader();
382
+ let stopped = false;
383
+ let bodyController;
384
+ const stop = (reason, errorBody) => {
385
+ if (stopped)
386
+ return;
387
+ stopped = true;
388
+ release();
389
+ signal.removeEventListener("abort", onAbort);
390
+ if (errorBody)
391
+ bodyController.error(reason);
392
+ reader.cancel(reason).catch(() => {}).finally(() => reader.releaseLock());
393
+ };
394
+ const onAbort = () => stop(signal.reason, true);
395
+ const body = new ReadableStream({
396
+ start(controller) {
397
+ bodyController = controller;
398
+ signal.addEventListener("abort", onAbort, { once: true });
399
+ if (signal.aborted)
400
+ onAbort();
401
+ },
402
+ async pull(controller) {
403
+ try {
404
+ const result = await untilAborted(reader.read(), signal);
405
+ if (stopped)
406
+ return;
407
+ if (result.done) {
408
+ stopped = true;
409
+ release();
410
+ signal.removeEventListener("abort", onAbort);
411
+ reader.releaseLock();
412
+ controller.close();
413
+ } else {
414
+ controller.enqueue(result.value);
415
+ }
416
+ } catch (error) {
417
+ stop(error, true);
418
+ }
419
+ },
420
+ cancel(reason) {
421
+ stop(reason, false);
422
+ }
423
+ }, { highWaterMark: 0 });
424
+ return preserveResponseMetadata(new Response(body, {
425
+ status: response.status,
426
+ statusText: response.statusText,
427
+ headers: response.headers
428
+ }), response);
429
+ }
358
430
  async function safeFetch(input, init = {}, options = {}) {
431
+ init.signal?.throwIfAborted();
359
432
  const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
433
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
434
+ throw new RangeError("safeFetch timeoutMs must be a positive finite number.");
435
+ }
360
436
  const maxRedirects = options.maxRedirects ?? 0;
361
- const resolveDns = options.resolveDns ?? true;
362
437
  const urlOptions = {
363
438
  allowHttp: options.allowHttp,
364
- resolveDns,
439
+ resolveDns: options.resolveDns ?? true,
365
440
  allowPrivate: options.allowPrivate
366
441
  };
367
442
  const controller = new AbortController;
368
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
443
+ const forwardAbort = () => controller.abort(init.signal?.reason);
444
+ init.signal?.addEventListener("abort", forwardAbort, { once: true });
445
+ const timeout = setTimeout(() => controller.abort(new DOMException("The operation timed out.", "TimeoutError")), timeoutMs);
446
+ const release = () => {
447
+ clearTimeout(timeout);
448
+ init.signal?.removeEventListener("abort", forwardAbort);
449
+ };
369
450
  try {
370
- let current = await resolveSafeOutboundTarget(input, urlOptions);
451
+ let current = await untilAborted(resolveSafeOutboundTarget(input, urlOptions), controller.signal);
371
452
  let redirectCount = 0;
372
453
  while (true) {
454
+ controller.signal.throwIfAborted();
373
455
  const address = current.addresses[0];
374
456
  const fetchUrl = address ? pinUrlToAddress(current.url, address).toString() : current.url.toString();
375
457
  const headers = new Headers(init.headers);
376
- if (address && !headers.has("host")) {
458
+ if (address && !headers.has("host"))
377
459
  headers.set("Host", current.url.host);
378
- }
379
460
  const fetchInit = {
380
461
  ...init,
381
462
  headers,
382
463
  signal: controller.signal,
383
464
  redirect: "manual"
384
465
  };
385
- if (address) {
466
+ if (address)
386
467
  fetchInit.tls = { serverName: current.url.hostname };
387
- }
388
- const response = await fetch(fetchUrl, fetchInit);
468
+ const response = await untilAborted(fetch(fetchUrl, fetchInit), controller.signal);
389
469
  if (response.status >= 300 && response.status < 400) {
390
470
  const location = response.headers.get("location");
391
- if (!location || redirectCount >= maxRedirects) {
392
- return response;
471
+ if (location && redirectCount < maxRedirects) {
472
+ if (response.body)
473
+ await untilAborted(response.body.cancel(), controller.signal);
474
+ current = await untilAborted(resolveSafeOutboundTarget(new URL(location, current.url).toString(), urlOptions), controller.signal);
475
+ redirectCount++;
476
+ continue;
393
477
  }
394
- current = await resolveSafeOutboundTarget(new URL(location, current.url).toString(), urlOptions);
395
- redirectCount += 1;
396
- continue;
397
478
  }
398
- return response;
479
+ return withBodyDeadline(response, controller.signal, release);
399
480
  }
400
- } finally {
401
- clearTimeout(timeout);
481
+ } catch (error) {
482
+ controller.abort(error);
483
+ release();
484
+ throw error;
402
485
  }
403
486
  }
404
487
 
@@ -64,7 +64,7 @@ export { loadSeedersFromDirectory, runSeedersFromDirectory, } from "../core/data
64
64
  export type { Seeder, SeederDatabase } from "../core/database/seeders/types.ts";
65
65
  export { createSqliteConnection } from "../core/database/sqliteConnection.ts";
66
66
  export { defineTable } from "../core/database/table.ts";
67
- export { requestTransactionRollback, runInTransaction } from "../core/database/transaction.ts";
67
+ export { hasActiveTransaction, requestTransactionRollback, runInTransaction, } from "../core/database/transaction.ts";
68
68
  export type { QueryJoin, QueryJoinOn, QueryOptions, QueryOrder, QuerySelectItem, QueryWhere, } from "../core/database/types.ts";
69
69
  export type { WhereNode } from "../core/database/whereBuilder.ts";
70
70
  export { WhereBuilder } from "../core/database/whereBuilder.ts";
@@ -72,6 +72,8 @@ export { BadRequestError, ConflictError, ForbiddenError, HttpError, InternalServ
72
72
  export type { EventListener } from "../core/events/eventBus.ts";
73
73
  export { EventBus, eventBus, readSharedEventBus } from "../core/events/eventBus.ts";
74
74
  export { dispatchModelEvent, modelEventName, runWithDeferredModelEvents, } from "../core/events/index.ts";
75
+ export type { DurableEvent, DurableListener, SqlOutboxOptions, } from "../core/events/outbox/index.ts";
76
+ export { createOutboxMigration, SqlOutbox } from "../core/events/outbox/index.ts";
75
77
  export { auth, cache, config, events, log, mail, policyGate, queue, storage, } from "../core/facades/index.ts";
76
78
  export { createBodySizeLimitMiddleware } from "../core/http/bodySizeLimitMiddleware.ts";
77
79
  export { readClientIp, trustForwardedFor } from "../core/http/clientIp.ts";