@getstrata/bootstrap 0.2.8 → 0.2.10

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.
@@ -106,9 +106,22 @@ var databaseConfig = {
106
106
  connectionTimeoutSeconds: readInteger("DB_CONNECTION_TIMEOUT", 10)
107
107
  };
108
108
 
109
- // ../../src/core/database/connectionContext.ts
109
+ // ../../src/core/runtime/asyncContextStore.ts
110
110
  import { AsyncLocalStorage } from "async_hooks";
111
- var activeConnection = new AsyncLocalStorage;
111
+ function createAsyncContextStore(key) {
112
+ const symbol = Symbol.for(key);
113
+ const globalRecord = globalThis;
114
+ const existing = globalRecord[symbol];
115
+ if (existing) {
116
+ return existing;
117
+ }
118
+ const store = new AsyncLocalStorage;
119
+ globalRecord[symbol] = store;
120
+ return store;
121
+ }
122
+
123
+ // ../../src/core/database/connectionContext.ts
124
+ var activeConnection = createAsyncContextStore("@getstrata/databaseConnectionContext");
112
125
  function getActiveDatabaseConnection(fallback) {
113
126
  return activeConnection.getStore() ?? fallback;
114
127
  }
@@ -195,6 +208,7 @@ var db = new Proxy(function database() {}, {
195
208
  return typeof value === "function" ? value.bind(connection) : value;
196
209
  }
197
210
  });
211
+ var connection_default = db;
198
212
 
199
213
  // ../../src/modules/user/apiTokenTable.ts
200
214
  import { defineTable } from "@getstrata/core/database";
@@ -423,8 +437,7 @@ class PreconditionFailedError extends HttpError {
423
437
  }
424
438
 
425
439
  // ../../src/core/auth/authContext.ts
426
- import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
427
- var authContext = new AsyncLocalStorage2;
440
+ var authContext = createAsyncContextStore("@getstrata/authContext");
428
441
  function currentAuthUser() {
429
442
  return authContext.getStore() ?? null;
430
443
  }
@@ -1298,6 +1311,9 @@ function discoverListeners() {
1298
1311
  return appListeners;
1299
1312
  }
1300
1313
 
1314
+ // ../../src/bootstrap/listeners/invalidateCacheOnModelWrite.ts
1315
+ import { resolveApplicationCache as resolveApplicationCache2, resolveApplicationQueue } from "@getstrata/core";
1316
+
1301
1317
  // ../../src/bootstrap/discoverModules.ts
1302
1318
  import { readdirSync as readdirSync2 } from "fs";
1303
1319
  import { join as join2 } from "path";
@@ -1340,410 +1356,18 @@ class Job {
1340
1356
  priority;
1341
1357
  }
1342
1358
 
1343
- // ../../src/core/jobs/invalidateCacheTagsJob.ts
1344
- class InvalidateCacheTagsJob extends Job {
1345
- cache;
1346
- constructor(cache) {
1347
- super();
1348
- this.cache = cache;
1349
- }
1350
- async handle(payload) {
1351
- await this.cache.tags(...payload.tags).flush();
1352
- }
1353
- }
1354
- var invalidateCacheTagsJob_default = InvalidateCacheTagsJob;
1355
-
1356
- // ../../src/core/logging/logger.ts
1357
- class Logger {
1358
- channel;
1359
- constructor(channel = "app") {
1360
- this.channel = channel;
1361
- }
1362
- write(level, message, context = {}) {
1363
- const entry = {
1364
- level,
1365
- channel: this.channel,
1366
- message,
1367
- timestamp: new Date().toISOString(),
1368
- ...context
1369
- };
1370
- const line = JSON.stringify(entry);
1371
- if (level === "error") {
1372
- console.error(line);
1373
- return;
1374
- }
1375
- console.log(line);
1376
- }
1377
- debug(message, context) {
1378
- this.write("debug", message, context);
1379
- }
1380
- info(message, context) {
1381
- this.write("info", message, context);
1382
- }
1383
- warn(message, context) {
1384
- this.write("warn", message, context);
1385
- }
1386
- error(message, context) {
1387
- this.write("error", message, context);
1388
- }
1389
- }
1390
- var appLogger = new Logger("app");
1391
-
1392
- // ../../src/bootstrap/contracts.ts
1393
- class ServiceContainer {
1394
- services = new Map;
1395
- singletonFactories = new Map;
1396
- bindings = new Map;
1397
- set(key, value) {
1398
- this.singletonFactories.delete(key);
1399
- this.bindings.delete(key);
1400
- this.services.set(key, value);
1401
- return value;
1402
- }
1403
- singleton(key, factory) {
1404
- this.bindings.delete(key);
1405
- this.services.delete(key);
1406
- this.singletonFactories.set(key, factory);
1407
- }
1408
- bind(key, factory) {
1409
- this.singletonFactories.delete(key);
1410
- this.services.delete(key);
1411
- this.bindings.set(key, factory);
1412
- }
1413
- get(key) {
1414
- if (this.services.has(key)) {
1415
- return this.services.get(key);
1416
- }
1417
- const singletonFactory = this.singletonFactories.get(key);
1418
- if (singletonFactory) {
1419
- const value = singletonFactory(this);
1420
- this.services.set(key, value);
1421
- return value;
1422
- }
1423
- const binding = this.bindings.get(key);
1424
- if (binding) {
1425
- return binding(this);
1426
- }
1427
- throw new Error(`Service "${key}" is not registered.`);
1428
- }
1429
- resolve(key) {
1430
- return this.get(key);
1431
- }
1432
- has(key) {
1433
- return this.services.has(key) || this.singletonFactories.has(key) || this.bindings.has(key);
1434
- }
1435
- }
1436
-
1437
- class ConfigStore {
1438
- values = new Map;
1439
- set(key, value) {
1440
- this.values.set(key, value);
1441
- return value;
1442
- }
1443
- get(key) {
1444
- return this.values.get(key);
1445
- }
1446
- require(key) {
1447
- if (!this.values.has(key)) {
1448
- throw new Error(`Config key "${key}" is not defined.`);
1449
- }
1450
- return this.values.get(key);
1451
- }
1452
- has(key) {
1453
- return this.values.has(key);
1454
- }
1455
- }
1456
- var requiredDependencyKeys = [
1457
- "container",
1458
- "cache",
1459
- "storage"
1460
- ];
1461
- function getRequiredDependency(dependencies, key) {
1462
- const dependency = dependencies[key];
1463
- if (dependency === undefined) {
1464
- throw new Error(`Required dependency "${key}" is not registered.`);
1465
- }
1466
- return dependency;
1467
- }
1468
- function assertAppDependenciesComplete(dependencies) {
1469
- for (const key of requiredDependencyKeys) {
1470
- getRequiredDependency(dependencies, key);
1471
- }
1472
- }
1473
- function resolveService(dependencies, token) {
1474
- return dependencies.container.resolve(token);
1475
- }
1476
-
1477
- // ../../src/bootstrap/applicationRegistry.ts
1478
- var activeContext;
1479
- function setActiveApplicationContext(context) {
1480
- activeContext = context;
1481
- }
1482
- function requireActiveApplicationContext() {
1483
- if (!activeContext) {
1484
- throw new Error("The application context has not been bootstrapped.");
1485
- }
1486
- return activeContext;
1487
- }
1488
- function resolveApplicationCache() {
1489
- return getRequiredDependency(requireActiveApplicationContext().dependencies, "cache");
1490
- }
1491
- function resolveApplicationQueue() {
1492
- return requireActiveApplicationContext().container.resolve(CORE_QUEUE_TOKEN);
1493
- }
1494
- function resolveApplicationAuth() {
1495
- return requireActiveApplicationContext().container.resolve(CORE_AUTH_TOKEN);
1496
- }
1497
- function resolveApplicationPolicyGate() {
1498
- return requireActiveApplicationContext().container.resolve(CORE_POLICY_GATE_TOKEN);
1499
- }
1500
- function resolveApplicationConfig() {
1501
- return requireActiveApplicationContext().config;
1502
- }
1503
- function resolveApplicationLogger() {
1504
- return appLogger;
1505
- }
1506
- function resolveApplicationDependencies() {
1507
- return requireActiveApplicationContext().dependencies;
1508
- }
1509
-
1510
- // ../../src/core/jobs/dispatchWebhookJob.ts
1511
- import { createHmac as createHmac2 } from "crypto";
1512
-
1513
- // ../../src/core/database/boundConnection.ts
1514
- var boundConnectionHolder = {
1515
- connection: null
1516
- };
1517
- function getBoundDatabaseConnection() {
1518
- return boundConnectionHolder.connection;
1519
- }
1520
-
1521
- // ../../src/core/database/repositoryConnection.ts
1522
- function resolveRepositoryConnection() {
1523
- return getBoundDatabaseConnection() ?? getDefaultDatabaseQuery();
1524
- }
1525
- var repositoryConnection = new Proxy(function repositoryConnection2() {}, {
1526
- apply(_target, _thisArg, args) {
1527
- return resolveRepositoryConnection()(...args);
1528
- },
1529
- get(_target, property) {
1530
- const connection = resolveRepositoryConnection();
1531
- const value = connection[property];
1532
- return typeof value === "function" ? value.bind(connection) : value;
1533
- }
1534
- });
1535
-
1536
- // ../../src/core/security/safeUrl.ts
1537
- import { lookup as dnsLookupImpl } from "dns/promises";
1538
- var dnsLookup = dnsLookupImpl;
1539
- var BLOCKED_HOSTNAMES = new Set([
1540
- "localhost",
1541
- "127.0.0.1",
1542
- "0.0.0.0",
1543
- "::1",
1544
- "metadata.google.internal"
1545
- ]);
1546
- function isPrivateIpv4(hostname) {
1547
- const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
1548
- if (!match) {
1549
- return false;
1550
- }
1551
- const octets = match.slice(1, 5).map((part) => Number.parseInt(part, 10));
1552
- if (octets.some((octet) => octet < 0 || octet > 255)) {
1553
- return true;
1554
- }
1555
- const [a = 0, b = 0] = octets;
1556
- if (a === 10) {
1557
- return true;
1558
- }
1559
- if (a === 127) {
1560
- return true;
1561
- }
1562
- if (a === 0) {
1563
- return true;
1564
- }
1565
- if (a === 169 && b === 254) {
1566
- return true;
1567
- }
1568
- if (a === 172 && b >= 16 && b <= 31) {
1569
- return true;
1570
- }
1571
- if (a === 192 && b === 168) {
1572
- return true;
1573
- }
1574
- return false;
1575
- }
1576
- function isBlockedHostname(hostname) {
1577
- const normalized = hostname.trim().toLowerCase();
1578
- if (normalized.length === 0) {
1579
- return true;
1580
- }
1581
- if (BLOCKED_HOSTNAMES.has(normalized)) {
1582
- return true;
1583
- }
1584
- if (normalized.endsWith(".local") || normalized.endsWith(".internal")) {
1585
- return true;
1586
- }
1587
- if (normalized.includes(":")) {
1588
- return true;
1589
- }
1590
- return isPrivateIpv4(normalized);
1591
- }
1592
- function assertSafeOutboundUrl(rawUrl, options = {}) {
1593
- let parsed;
1594
- try {
1595
- parsed = new URL(rawUrl);
1596
- } catch {
1597
- throw new BadRequestError("Webhook URL is invalid.");
1598
- }
1599
- if (parsed.protocol !== "https:" && !(options.allowHttp && parsed.protocol === "http:")) {
1600
- throw new BadRequestError("Webhook URL must use HTTPS.");
1601
- }
1602
- if (parsed.username || parsed.password) {
1603
- throw new BadRequestError("Webhook URL must not include credentials.");
1604
- }
1605
- if (isBlockedHostname(parsed.hostname)) {
1606
- throw new BadRequestError("Webhook URL targets a blocked host.");
1607
- }
1608
- return parsed;
1609
- }
1610
- function isBlockedIpAddress(address) {
1611
- return isBlockedHostname(address.trim().toLowerCase());
1612
- }
1613
- async function assertSafeOutboundUrlResolved(rawUrl, options = {}) {
1614
- const parsed = assertSafeOutboundUrl(rawUrl, options);
1615
- if (options.resolveDns === false) {
1616
- return parsed;
1617
- }
1618
- const hostname = parsed.hostname.trim().toLowerCase();
1619
- const results = await dnsLookup(hostname, { all: true, verbatim: true });
1620
- if (results.some((result) => isBlockedIpAddress(result.address))) {
1621
- throw new BadRequestError("Webhook URL targets a blocked host.");
1622
- }
1623
- return parsed;
1624
- }
1625
-
1626
- // ../../src/core/security/safeFetch.ts
1627
- var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
1628
- async function safeFetch(input, init = {}, options = {}) {
1629
- const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
1630
- const maxRedirects = options.maxRedirects ?? 0;
1631
- const resolveDns = options.resolveDns ?? appConfig.env === "production";
1632
- const urlOptions = { allowHttp: options.allowHttp, resolveDns };
1633
- const controller = new AbortController;
1634
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
1635
- try {
1636
- let currentUrl = (await assertSafeOutboundUrlResolved(input, urlOptions)).toString();
1637
- let redirectCount = 0;
1638
- while (true) {
1639
- const response = await fetch(currentUrl, {
1640
- ...init,
1641
- signal: controller.signal,
1642
- redirect: "manual"
1643
- });
1644
- if (response.status >= 300 && response.status < 400) {
1645
- const location = response.headers.get("location");
1646
- if (!location || redirectCount >= maxRedirects) {
1647
- return response;
1648
- }
1649
- currentUrl = (await assertSafeOutboundUrlResolved(new URL(location, currentUrl).toString(), urlOptions)).toString();
1650
- redirectCount += 1;
1651
- continue;
1652
- }
1653
- return response;
1654
- }
1655
- } finally {
1656
- clearTimeout(timeout);
1657
- }
1658
- }
1659
-
1660
- // ../../src/core/jobs/dispatchWebhookJob.ts
1661
- class DispatchWebhookJob extends Job {
1662
- maxAttempts = 3;
1663
- backoffMs = 2000;
1664
- async handle(payload) {
1665
- const rows = await repositoryConnection`
1666
- SELECT id, url, secret
1667
- FROM webhook
1668
- WHERE id = ${payload.webhookId} AND active = TRUE
1669
- LIMIT 1
1670
- `;
1671
- const webhook = rows[0];
1672
- if (!webhook) {
1673
- return;
1674
- }
1675
- const body = JSON.stringify({ event: payload.event, payload: payload.payload });
1676
- const signature = createHmac2("sha256", webhook.secret).update(body).digest("hex");
1677
- assertSafeOutboundUrl(webhook.url, { allowHttp: appConfig.env !== "production" });
1678
- let responseStatus = null;
1679
- let errorMessage = null;
1680
- try {
1681
- const response = await safeFetch(webhook.url, {
1682
- method: "POST",
1683
- headers: {
1684
- "content-type": "application/json",
1685
- "x-workhub-signature": signature
1686
- },
1687
- body
1688
- }, { allowHttp: appConfig.env !== "production" });
1689
- responseStatus = response.status;
1690
- if (!response.ok) {
1691
- throw new Error(`Webhook delivery failed with status ${response.status}.`);
1692
- }
1693
- } catch (error) {
1694
- errorMessage = error instanceof Error ? error.message : String(error);
1695
- await repositoryConnection`
1696
- INSERT INTO webhook_delivery (webhook_id, event, payload, response_status, error)
1697
- VALUES (
1698
- ${webhook.id},
1699
- ${payload.event},
1700
- ${JSON.stringify(payload.payload)}::jsonb,
1701
- ${responseStatus},
1702
- ${errorMessage}
1703
- )
1704
- `;
1705
- throw error instanceof Error ? error : new Error(errorMessage);
1706
- }
1707
- await repositoryConnection`
1708
- INSERT INTO webhook_delivery (webhook_id, event, payload, response_status)
1709
- VALUES (
1710
- ${webhook.id},
1711
- ${payload.event},
1712
- ${JSON.stringify(payload.payload)}::jsonb,
1713
- ${responseStatus}
1714
- )
1715
- `;
1716
- }
1717
- }
1718
- var dispatchWebhookJob_default = DispatchWebhookJob;
1719
-
1720
- // ../../src/core/queue/jobRegistry.ts
1721
- class JobRegistry {
1722
- constructor() {}
1723
- factories = new Map;
1724
- instances = new WeakMap;
1725
- register(name, factory) {
1726
- this.factories.set(name, factory);
1727
- }
1728
- resolveName(job) {
1729
- return this.instances.get(job);
1730
- }
1731
- track(name, job) {
1732
- this.instances.set(job, name);
1733
- return job;
1734
- }
1735
- create(name) {
1736
- const factory = this.factories.get(name);
1737
- if (!factory) {
1738
- return;
1739
- }
1740
- return factory();
1359
+ // ../../src/core/jobs/invalidateCacheTagsJob.ts
1360
+ class InvalidateCacheTagsJob extends Job {
1361
+ cache;
1362
+ constructor(cache) {
1363
+ super();
1364
+ this.cache = cache;
1741
1365
  }
1742
- names() {
1743
- return [...this.factories.keys()];
1366
+ async handle(payload) {
1367
+ await this.cache.tags(...payload.tags).flush();
1744
1368
  }
1745
1369
  }
1746
- var jobRegistry = new JobRegistry;
1370
+ var invalidateCacheTagsJob_default = InvalidateCacheTagsJob;
1747
1371
 
1748
1372
  // ../../src/core/pagination/index.ts
1749
1373
  function buildPaginationMeta(input) {
@@ -2284,6 +1908,29 @@ function indexMorphToRelation(children, parentsByType, relation) {
2284
1908
  return result;
2285
1909
  }
2286
1910
 
1911
+ // ../../src/core/database/boundConnection.ts
1912
+ var boundConnectionHolder = {
1913
+ connection: null
1914
+ };
1915
+ function getBoundDatabaseConnection() {
1916
+ return boundConnectionHolder.connection;
1917
+ }
1918
+
1919
+ // ../../src/core/database/repositoryConnection.ts
1920
+ function resolveRepositoryConnection() {
1921
+ return getBoundDatabaseConnection() ?? getDefaultDatabaseQuery();
1922
+ }
1923
+ var repositoryConnection = new Proxy(function repositoryConnection2() {}, {
1924
+ apply(_target, _thisArg, args) {
1925
+ return resolveRepositoryConnection()(...args);
1926
+ },
1927
+ get(_target, property) {
1928
+ const connection = resolveRepositoryConnection();
1929
+ const value = connection[property];
1930
+ return typeof value === "function" ? value.bind(connection) : value;
1931
+ }
1932
+ });
1933
+
2287
1934
  // ../../src/core/database/whereBuilder.ts
2288
1935
  class WhereBuilder {
2289
1936
  nodes = [];
@@ -3472,6 +3119,34 @@ class FailedJobService {
3472
3119
  }
3473
3120
  var failedJobService_default = FailedJobService;
3474
3121
 
3122
+ // ../../src/core/queue/jobRegistry.ts
3123
+ class JobRegistry {
3124
+ constructor() {}
3125
+ factories = new Map;
3126
+ instances = new WeakMap;
3127
+ register(name, factory) {
3128
+ this.factories.set(name, factory);
3129
+ }
3130
+ resolveName(job) {
3131
+ return this.instances.get(job);
3132
+ }
3133
+ track(name, job) {
3134
+ this.instances.set(job, name);
3135
+ return job;
3136
+ }
3137
+ create(name) {
3138
+ const factory = this.factories.get(name);
3139
+ if (!factory) {
3140
+ return;
3141
+ }
3142
+ return factory();
3143
+ }
3144
+ names() {
3145
+ return [...this.factories.keys()];
3146
+ }
3147
+ }
3148
+ var jobRegistry = new JobRegistry;
3149
+
3475
3150
  // ../../src/core/queue/jobRunner.ts
3476
3151
  async function runQueueJob(envelope, failedJobs) {
3477
3152
  const job = jobRegistry.create(envelope.name);
@@ -3586,19 +3261,211 @@ function createProductionQueue(driver, options = {}) {
3586
3261
  return new ResilientQueue(failedJobs, driver === "async");
3587
3262
  }
3588
3263
 
3589
- // ../../src/core/queue/createAppQueue.ts
3590
- var FAILED_JOB_SERVICE_TOKEN = "core.failedJobs";
3264
+ // ../../src/bootstrap/queue/defaultJobs.ts
3265
+ import { resolveApplicationCache } from "@getstrata/core";
3266
+
3267
+ // ../../src/core/jobs/dispatchWebhookJob.ts
3268
+ import { createHmac as createHmac2 } from "crypto";
3269
+
3270
+ // ../../src/core/security/safeUrl.ts
3271
+ import { lookup as dnsLookupImpl } from "dns/promises";
3272
+ var dnsLookup = dnsLookupImpl;
3273
+ var BLOCKED_HOSTNAMES = new Set([
3274
+ "localhost",
3275
+ "127.0.0.1",
3276
+ "0.0.0.0",
3277
+ "::1",
3278
+ "metadata.google.internal"
3279
+ ]);
3280
+ function isPrivateIpv4(hostname) {
3281
+ const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
3282
+ if (!match) {
3283
+ return false;
3284
+ }
3285
+ const octets = match.slice(1, 5).map((part) => Number.parseInt(part, 10));
3286
+ if (octets.some((octet) => octet < 0 || octet > 255)) {
3287
+ return true;
3288
+ }
3289
+ const [a = 0, b = 0] = octets;
3290
+ if (a === 10) {
3291
+ return true;
3292
+ }
3293
+ if (a === 127) {
3294
+ return true;
3295
+ }
3296
+ if (a === 0) {
3297
+ return true;
3298
+ }
3299
+ if (a === 169 && b === 254) {
3300
+ return true;
3301
+ }
3302
+ if (a === 172 && b >= 16 && b <= 31) {
3303
+ return true;
3304
+ }
3305
+ if (a === 192 && b === 168) {
3306
+ return true;
3307
+ }
3308
+ return false;
3309
+ }
3310
+ function isBlockedHostname(hostname) {
3311
+ const normalized = hostname.trim().toLowerCase();
3312
+ if (normalized.length === 0) {
3313
+ return true;
3314
+ }
3315
+ if (BLOCKED_HOSTNAMES.has(normalized)) {
3316
+ return true;
3317
+ }
3318
+ if (normalized.endsWith(".local") || normalized.endsWith(".internal")) {
3319
+ return true;
3320
+ }
3321
+ if (normalized.includes(":")) {
3322
+ return true;
3323
+ }
3324
+ return isPrivateIpv4(normalized);
3325
+ }
3326
+ function assertSafeOutboundUrl(rawUrl, options = {}) {
3327
+ let parsed;
3328
+ try {
3329
+ parsed = new URL(rawUrl);
3330
+ } catch {
3331
+ throw new BadRequestError("Webhook URL is invalid.");
3332
+ }
3333
+ if (parsed.protocol !== "https:" && !(options.allowHttp && parsed.protocol === "http:")) {
3334
+ throw new BadRequestError("Webhook URL must use HTTPS.");
3335
+ }
3336
+ if (parsed.username || parsed.password) {
3337
+ throw new BadRequestError("Webhook URL must not include credentials.");
3338
+ }
3339
+ if (isBlockedHostname(parsed.hostname)) {
3340
+ throw new BadRequestError("Webhook URL targets a blocked host.");
3341
+ }
3342
+ return parsed;
3343
+ }
3344
+ function isBlockedIpAddress(address) {
3345
+ return isBlockedHostname(address.trim().toLowerCase());
3346
+ }
3347
+ async function assertSafeOutboundUrlResolved(rawUrl, options = {}) {
3348
+ const parsed = assertSafeOutboundUrl(rawUrl, options);
3349
+ if (options.resolveDns === false) {
3350
+ return parsed;
3351
+ }
3352
+ const hostname = parsed.hostname.trim().toLowerCase();
3353
+ const results = await dnsLookup(hostname, { all: true, verbatim: true });
3354
+ if (results.some((result) => isBlockedIpAddress(result.address))) {
3355
+ throw new BadRequestError("Webhook URL targets a blocked host.");
3356
+ }
3357
+ return parsed;
3358
+ }
3359
+
3360
+ // ../../src/core/security/safeFetch.ts
3361
+ var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
3362
+ async function safeFetch(input, init = {}, options = {}) {
3363
+ const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
3364
+ const maxRedirects = options.maxRedirects ?? 0;
3365
+ const resolveDns = options.resolveDns ?? appConfig.env === "production";
3366
+ const urlOptions = { allowHttp: options.allowHttp, resolveDns };
3367
+ const controller = new AbortController;
3368
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
3369
+ try {
3370
+ let currentUrl = (await assertSafeOutboundUrlResolved(input, urlOptions)).toString();
3371
+ let redirectCount = 0;
3372
+ while (true) {
3373
+ const response = await fetch(currentUrl, {
3374
+ ...init,
3375
+ signal: controller.signal,
3376
+ redirect: "manual"
3377
+ });
3378
+ if (response.status >= 300 && response.status < 400) {
3379
+ const location = response.headers.get("location");
3380
+ if (!location || redirectCount >= maxRedirects) {
3381
+ return response;
3382
+ }
3383
+ currentUrl = (await assertSafeOutboundUrlResolved(new URL(location, currentUrl).toString(), urlOptions)).toString();
3384
+ redirectCount += 1;
3385
+ continue;
3386
+ }
3387
+ return response;
3388
+ }
3389
+ } finally {
3390
+ clearTimeout(timeout);
3391
+ }
3392
+ }
3393
+
3394
+ // ../../src/core/jobs/dispatchWebhookJob.ts
3395
+ class DispatchWebhookJob extends Job {
3396
+ maxAttempts = 3;
3397
+ backoffMs = 2000;
3398
+ async handle(payload) {
3399
+ const rows = await repositoryConnection`
3400
+ SELECT id, url, secret
3401
+ FROM webhook
3402
+ WHERE id = ${payload.webhookId} AND active = TRUE
3403
+ LIMIT 1
3404
+ `;
3405
+ const webhook = rows[0];
3406
+ if (!webhook) {
3407
+ return;
3408
+ }
3409
+ const body = JSON.stringify({ event: payload.event, payload: payload.payload });
3410
+ const signature = createHmac2("sha256", webhook.secret).update(body).digest("hex");
3411
+ assertSafeOutboundUrl(webhook.url, { allowHttp: appConfig.env !== "production" });
3412
+ let responseStatus = null;
3413
+ let errorMessage = null;
3414
+ try {
3415
+ const response = await safeFetch(webhook.url, {
3416
+ method: "POST",
3417
+ headers: {
3418
+ "content-type": "application/json",
3419
+ "x-workhub-signature": signature
3420
+ },
3421
+ body
3422
+ }, { allowHttp: appConfig.env !== "production" });
3423
+ responseStatus = response.status;
3424
+ if (!response.ok) {
3425
+ throw new Error(`Webhook delivery failed with status ${response.status}.`);
3426
+ }
3427
+ } catch (error) {
3428
+ errorMessage = error instanceof Error ? error.message : String(error);
3429
+ await repositoryConnection`
3430
+ INSERT INTO webhook_delivery (webhook_id, event, payload, response_status, error)
3431
+ VALUES (
3432
+ ${webhook.id},
3433
+ ${payload.event},
3434
+ ${JSON.stringify(payload.payload)}::jsonb,
3435
+ ${responseStatus},
3436
+ ${errorMessage}
3437
+ )
3438
+ `;
3439
+ throw error instanceof Error ? error : new Error(errorMessage);
3440
+ }
3441
+ await repositoryConnection`
3442
+ INSERT INTO webhook_delivery (webhook_id, event, payload, response_status)
3443
+ VALUES (
3444
+ ${webhook.id},
3445
+ ${payload.event},
3446
+ ${JSON.stringify(payload.payload)}::jsonb,
3447
+ ${responseStatus}
3448
+ )
3449
+ `;
3450
+ }
3451
+ }
3452
+ var dispatchWebhookJob_default = DispatchWebhookJob;
3453
+
3454
+ // ../../src/bootstrap/queue/defaultJobs.ts
3591
3455
  function registerDefaultJobs() {
3592
3456
  jobRegistry.register("cache.invalidate-tags", () => {
3593
3457
  return new invalidateCacheTagsJob_default(resolveApplicationCache());
3594
3458
  });
3595
3459
  jobRegistry.register("webhook.dispatch", () => new dispatchWebhookJob_default);
3596
3460
  }
3597
- function createAppQueue(driver, redisUrl, failedJobs = createFailedJobService()) {
3461
+
3462
+ // ../../src/core/queue/createAppQueue.ts
3463
+ var FAILED_JOB_SERVICE_TOKEN = "core.failedJobs";
3464
+ function createAppQueue(driver, redisUrl, failedJobs = createFailedJobService(), registerJobs) {
3598
3465
  return createProductionQueue(driver, {
3599
3466
  redisUrl,
3600
3467
  failedJobs,
3601
- registerJobs: registerDefaultJobs
3468
+ registerJobs
3602
3469
  });
3603
3470
  }
3604
3471
 
@@ -3615,7 +3482,7 @@ function registerInvalidateCacheOnModelWriteListeners(bus = eventBus) {
3615
3482
  let cache;
3616
3483
  let queue;
3617
3484
  try {
3618
- cache = resolveApplicationCache();
3485
+ cache = resolveApplicationCache2();
3619
3486
  queue = resolveApplicationQueue();
3620
3487
  } catch {
3621
3488
  return;
@@ -3706,7 +3573,7 @@ var queueProvider = {
3706
3573
  config.set("queue.driver", driver);
3707
3574
  const failedJobs = createFailedJobService();
3708
3575
  container.set(FAILED_JOB_SERVICE_TOKEN, failedJobs);
3709
- container.set(CORE_QUEUE_TOKEN, createAppQueue(driver, config.get(REDIS_URL_CONFIG_KEY) ?? process.env.REDIS_URL, failedJobs));
3576
+ container.set(CORE_QUEUE_TOKEN, createAppQueue(driver, config.get(REDIS_URL_CONFIG_KEY) ?? process.env.REDIS_URL, failedJobs, registerDefaultJobs));
3710
3577
  }
3711
3578
  };
3712
3579
  var queue_default = queueProvider;
@@ -3849,8 +3716,7 @@ function isViewsEnabled() {
3849
3716
  }
3850
3717
 
3851
3718
  // ../../src/core/http/requestMetaContext.ts
3852
- import { AsyncLocalStorage as AsyncLocalStorage3 } from "async_hooks";
3853
- var requestMetaContext = new AsyncLocalStorage3;
3719
+ var requestMetaContext = createAsyncContextStore("@getstrata/requestMetaContext");
3854
3720
  function currentRequestMeta() {
3855
3721
  return requestMetaContext.getStore() ?? {
3856
3722
  ipAddress: null,