@getstrata/bootstrap 0.2.8 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bootstrap/membershipService.d.ts +3 -0
- package/dist/bootstrap/public-api.d.ts +3 -1
- package/dist/bootstrap/queue/defaultJobs.d.ts +2 -0
- package/dist/core/auth/membershipService.d.ts +1 -2
- package/dist/core/queue/createAppQueue.d.ts +3 -3
- package/dist/entries/context.js +689 -686
- package/dist/entries/createWebRoutes.js +1 -0
- package/dist/entries/membershipService.js +480 -0
- package/dist/entries/providers/view.js +1 -0
- package/dist/entries/providers.js +1938 -1935
- package/dist/entries/queue/defaultJobs.js +549 -0
- package/dist/index.js +288 -123
- package/package.json +13 -3
package/dist/entries/context.js
CHANGED
|
@@ -306,6 +306,7 @@ var db = new Proxy(function database() {}, {
|
|
|
306
306
|
return typeof value === "function" ? value.bind(connection) : value;
|
|
307
307
|
}
|
|
308
308
|
});
|
|
309
|
+
var connection_default = db;
|
|
309
310
|
|
|
310
311
|
// ../../src/modules/user/apiTokenTable.ts
|
|
311
312
|
import { defineTable } from "@getstrata/core/database";
|
|
@@ -1441,590 +1442,283 @@ class InvalidateCacheTagsJob extends Job {
|
|
|
1441
1442
|
}
|
|
1442
1443
|
var invalidateCacheTagsJob_default = InvalidateCacheTagsJob;
|
|
1443
1444
|
|
|
1444
|
-
// ../../src/core/
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1445
|
+
// ../../src/core/pagination/index.ts
|
|
1446
|
+
function buildPaginationMeta(input) {
|
|
1447
|
+
const lastPage = Math.max(1, Math.ceil(input.total / input.perPage));
|
|
1448
|
+
return {
|
|
1449
|
+
page: input.page,
|
|
1450
|
+
per_page: input.perPage,
|
|
1451
|
+
total: input.total,
|
|
1452
|
+
last_page: lastPage
|
|
1453
|
+
};
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
// ../../src/core/database/errors.ts
|
|
1457
|
+
function isPostgresError(error) {
|
|
1458
|
+
return typeof error === "object" && error !== null && (("errno" in error) || ("code" in error));
|
|
1459
|
+
}
|
|
1460
|
+
function getPostgresSqlState(error) {
|
|
1461
|
+
if (typeof error.errno === "string" && /^\d{5}$/.test(error.errno)) {
|
|
1462
|
+
return error.errno;
|
|
1449
1463
|
}
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
level,
|
|
1453
|
-
channel: this.channel,
|
|
1454
|
-
message,
|
|
1455
|
-
timestamp: new Date().toISOString(),
|
|
1456
|
-
...context
|
|
1457
|
-
};
|
|
1458
|
-
const line = JSON.stringify(entry);
|
|
1459
|
-
if (level === "error") {
|
|
1460
|
-
console.error(line);
|
|
1461
|
-
return;
|
|
1462
|
-
}
|
|
1463
|
-
console.log(line);
|
|
1464
|
+
if (typeof error.errno === "number") {
|
|
1465
|
+
return String(error.errno).padStart(5, "0");
|
|
1464
1466
|
}
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
+
if (typeof error.code === "string" && /^\d{5}$/.test(error.code)) {
|
|
1468
|
+
return error.code;
|
|
1467
1469
|
}
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
+
return;
|
|
1471
|
+
}
|
|
1472
|
+
function mapDatabaseError(error) {
|
|
1473
|
+
if (error instanceof HttpError) {
|
|
1474
|
+
return error;
|
|
1470
1475
|
}
|
|
1471
|
-
|
|
1472
|
-
|
|
1476
|
+
if (!isPostgresError(error)) {
|
|
1477
|
+
const message = error instanceof Error ? error.message : "Database operation failed.";
|
|
1478
|
+
return new BadRequestError(message);
|
|
1473
1479
|
}
|
|
1474
|
-
error
|
|
1475
|
-
|
|
1480
|
+
const sqlState = getPostgresSqlState(error);
|
|
1481
|
+
switch (sqlState) {
|
|
1482
|
+
case "23505":
|
|
1483
|
+
return new ConflictError(error.detail ?? "A record with these values already exists.", {
|
|
1484
|
+
constraint: error.constraint
|
|
1485
|
+
});
|
|
1486
|
+
case "23503":
|
|
1487
|
+
return new UnprocessableEntityError(error.detail ?? "Referenced record does not exist.", {
|
|
1488
|
+
constraint: error.constraint
|
|
1489
|
+
});
|
|
1490
|
+
case "23502":
|
|
1491
|
+
return new BadRequestError(error.detail ?? "Required field is missing.", {
|
|
1492
|
+
constraint: error.constraint
|
|
1493
|
+
});
|
|
1494
|
+
case "23514":
|
|
1495
|
+
return new BadRequestError(error.detail ?? "Value violates a database constraint.", {
|
|
1496
|
+
constraint: error.constraint
|
|
1497
|
+
});
|
|
1498
|
+
default:
|
|
1499
|
+
return new BadRequestError(error.message ?? "Database operation failed.", {
|
|
1500
|
+
code: error.code,
|
|
1501
|
+
sqlState
|
|
1502
|
+
});
|
|
1476
1503
|
}
|
|
1477
1504
|
}
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
activeContext = context;
|
|
1484
|
-
}
|
|
1485
|
-
function requireActiveApplicationContext() {
|
|
1486
|
-
if (!activeContext) {
|
|
1487
|
-
throw new Error("The application context has not been bootstrapped.");
|
|
1505
|
+
async function withDatabaseErrorHandling(operation) {
|
|
1506
|
+
try {
|
|
1507
|
+
return await operation();
|
|
1508
|
+
} catch (error) {
|
|
1509
|
+
throw mapDatabaseError(error);
|
|
1488
1510
|
}
|
|
1489
|
-
return activeContext;
|
|
1490
1511
|
}
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
return requireActiveApplicationContext().container.resolve(CORE_AUTH_TOKEN);
|
|
1512
|
+
|
|
1513
|
+
// ../../src/core/database/query.ts
|
|
1514
|
+
function quoteIdentifier(identifier) {
|
|
1515
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(identifier)) {
|
|
1516
|
+
throw new Error(`Invalid SQL identifier: ${identifier}`);
|
|
1517
|
+
}
|
|
1518
|
+
return `"${identifier}"`;
|
|
1499
1519
|
}
|
|
1500
|
-
function
|
|
1501
|
-
return
|
|
1520
|
+
function qualifyColumn(tableName, column) {
|
|
1521
|
+
return `${quoteIdentifier(tableName)}.${quoteIdentifier(column)}`;
|
|
1502
1522
|
}
|
|
1503
|
-
function
|
|
1504
|
-
|
|
1523
|
+
function resolveQualifiedColumn(defaultTable, columnName) {
|
|
1524
|
+
if (columnName.includes(".")) {
|
|
1525
|
+
const [table, column] = columnName.split(".", 2);
|
|
1526
|
+
if (!table || !column) {
|
|
1527
|
+
throw new Error(`Invalid qualified column: ${columnName}`);
|
|
1528
|
+
}
|
|
1529
|
+
return qualifyColumn(table, column);
|
|
1530
|
+
}
|
|
1531
|
+
return qualifyColumn(defaultTable, columnName);
|
|
1505
1532
|
}
|
|
1506
|
-
function
|
|
1507
|
-
|
|
1533
|
+
function parseQualifiedColumn(reference) {
|
|
1534
|
+
const [table, column] = reference.split(".", 2);
|
|
1535
|
+
if (!table || !column) {
|
|
1536
|
+
throw new Error(`Join columns must be qualified as table.column: ${reference}`);
|
|
1537
|
+
}
|
|
1538
|
+
return { table, column };
|
|
1508
1539
|
}
|
|
1509
|
-
function
|
|
1510
|
-
return
|
|
1540
|
+
function normalizeDirection(direction = "ASC") {
|
|
1541
|
+
return direction.toUpperCase() === "DESC" ? "DESC" : "ASC";
|
|
1511
1542
|
}
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
import { createHmac as createHmac2 } from "crypto";
|
|
1515
|
-
|
|
1516
|
-
// ../../src/core/database/boundConnection.ts
|
|
1517
|
-
var boundConnectionHolder = {
|
|
1518
|
-
connection: null
|
|
1519
|
-
};
|
|
1520
|
-
function getBoundDatabaseConnection() {
|
|
1521
|
-
return boundConnectionHolder.connection;
|
|
1543
|
+
function isQueryOperator(value) {
|
|
1544
|
+
return value !== null && !Array.isArray(value) && !(value instanceof Date) && typeof value === "object";
|
|
1522
1545
|
}
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
return getBoundDatabaseConnection() ?? getDefaultDatabaseQuery();
|
|
1546
|
+
function pushParam(values, value) {
|
|
1547
|
+
values.push(value);
|
|
1548
|
+
return `$${values.length}`;
|
|
1527
1549
|
}
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
return
|
|
1531
|
-
},
|
|
1532
|
-
get(_target, property) {
|
|
1533
|
-
const connection = resolveRepositoryConnection();
|
|
1534
|
-
const value = connection[property];
|
|
1535
|
-
return typeof value === "function" ? value.bind(connection) : value;
|
|
1536
|
-
}
|
|
1537
|
-
});
|
|
1538
|
-
|
|
1539
|
-
// ../../src/core/security/safeUrl.ts
|
|
1540
|
-
import { lookup as dnsLookupImpl } from "dns/promises";
|
|
1541
|
-
var dnsLookup = dnsLookupImpl;
|
|
1542
|
-
var BLOCKED_HOSTNAMES = new Set([
|
|
1543
|
-
"localhost",
|
|
1544
|
-
"127.0.0.1",
|
|
1545
|
-
"0.0.0.0",
|
|
1546
|
-
"::1",
|
|
1547
|
-
"metadata.google.internal"
|
|
1548
|
-
]);
|
|
1549
|
-
function isPrivateIpv4(hostname) {
|
|
1550
|
-
const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
|
|
1551
|
-
if (!match) {
|
|
1552
|
-
return false;
|
|
1553
|
-
}
|
|
1554
|
-
const octets = match.slice(1, 5).map((part) => Number.parseInt(part, 10));
|
|
1555
|
-
if (octets.some((octet) => octet < 0 || octet > 255)) {
|
|
1556
|
-
return true;
|
|
1550
|
+
function buildInClause(column, values, params) {
|
|
1551
|
+
if (values.length === 0) {
|
|
1552
|
+
return "1 = 0";
|
|
1557
1553
|
}
|
|
1558
|
-
const
|
|
1559
|
-
|
|
1560
|
-
|
|
1554
|
+
const placeholders = values.map((value) => pushParam(params, value)).join(", ");
|
|
1555
|
+
return `${column} IN (${placeholders})`;
|
|
1556
|
+
}
|
|
1557
|
+
function buildOperatorClauses(column, operator, params) {
|
|
1558
|
+
const clauses = [];
|
|
1559
|
+
if (operator.isNull === true) {
|
|
1560
|
+
clauses.push(`${column} IS NULL`);
|
|
1561
1561
|
}
|
|
1562
|
-
if (
|
|
1563
|
-
|
|
1562
|
+
if (operator.isNull === false) {
|
|
1563
|
+
clauses.push(`${column} IS NOT NULL`);
|
|
1564
1564
|
}
|
|
1565
|
-
if (
|
|
1566
|
-
|
|
1565
|
+
if (operator.eq !== undefined) {
|
|
1566
|
+
if (operator.eq === null) {
|
|
1567
|
+
clauses.push(`${column} IS NULL`);
|
|
1568
|
+
} else {
|
|
1569
|
+
clauses.push(`${column} = ${pushParam(params, operator.eq)}`);
|
|
1570
|
+
}
|
|
1567
1571
|
}
|
|
1568
|
-
if (
|
|
1569
|
-
|
|
1572
|
+
if (operator.in !== undefined) {
|
|
1573
|
+
clauses.push(buildInClause(column, operator.in, params));
|
|
1570
1574
|
}
|
|
1571
|
-
if (
|
|
1572
|
-
|
|
1575
|
+
if (operator.gt !== undefined) {
|
|
1576
|
+
clauses.push(`${column} > ${pushParam(params, operator.gt)}`);
|
|
1573
1577
|
}
|
|
1574
|
-
if (
|
|
1575
|
-
|
|
1578
|
+
if (operator.gte !== undefined) {
|
|
1579
|
+
clauses.push(`${column} >= ${pushParam(params, operator.gte)}`);
|
|
1576
1580
|
}
|
|
1577
|
-
|
|
1578
|
-
}
|
|
1579
|
-
function isBlockedHostname(hostname) {
|
|
1580
|
-
const normalized = hostname.trim().toLowerCase();
|
|
1581
|
-
if (normalized.length === 0) {
|
|
1582
|
-
return true;
|
|
1581
|
+
if (operator.lt !== undefined) {
|
|
1582
|
+
clauses.push(`${column} < ${pushParam(params, operator.lt)}`);
|
|
1583
1583
|
}
|
|
1584
|
-
if (
|
|
1585
|
-
|
|
1584
|
+
if (operator.lte !== undefined) {
|
|
1585
|
+
clauses.push(`${column} <= ${pushParam(params, operator.lte)}`);
|
|
1586
1586
|
}
|
|
1587
|
-
if (
|
|
1588
|
-
|
|
1587
|
+
if (operator.ilike !== undefined) {
|
|
1588
|
+
clauses.push(`${column} ILIKE ${pushParam(params, `%${operator.ilike}%`)}`);
|
|
1589
1589
|
}
|
|
1590
|
-
if (
|
|
1591
|
-
|
|
1590
|
+
if (operator.tsMatch !== undefined) {
|
|
1591
|
+
clauses.push(`${column} @@ plainto_tsquery('english', ${pushParam(params, operator.tsMatch)})`);
|
|
1592
1592
|
}
|
|
1593
|
-
return
|
|
1593
|
+
return clauses;
|
|
1594
1594
|
}
|
|
1595
|
-
function
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1595
|
+
function appendWhereParts(tableName, where, params) {
|
|
1596
|
+
const clauses = [];
|
|
1597
|
+
for (const [columnName, filterValue] of Object.entries(where)) {
|
|
1598
|
+
if (filterValue === undefined) {
|
|
1599
|
+
continue;
|
|
1600
|
+
}
|
|
1601
|
+
const column = resolveQualifiedColumn(tableName, columnName);
|
|
1602
|
+
if (Array.isArray(filterValue)) {
|
|
1603
|
+
clauses.push(buildInClause(column, filterValue, params));
|
|
1604
|
+
continue;
|
|
1605
|
+
}
|
|
1606
|
+
if (isQueryOperator(filterValue)) {
|
|
1607
|
+
clauses.push(...buildOperatorClauses(column, filterValue, params));
|
|
1608
|
+
continue;
|
|
1609
|
+
}
|
|
1610
|
+
if (filterValue === null) {
|
|
1611
|
+
clauses.push(`${column} IS NULL`);
|
|
1612
|
+
continue;
|
|
1613
|
+
}
|
|
1614
|
+
clauses.push(`${column} = ${pushParam(params, filterValue)}`);
|
|
1610
1615
|
}
|
|
1611
|
-
return
|
|
1612
|
-
}
|
|
1613
|
-
function isBlockedIpAddress(address) {
|
|
1614
|
-
return isBlockedHostname(address.trim().toLowerCase());
|
|
1616
|
+
return clauses.join(" AND ");
|
|
1615
1617
|
}
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
return parsed;
|
|
1618
|
+
function buildWhereNodeClause(tableName, node, params) {
|
|
1619
|
+
if ("where" in node) {
|
|
1620
|
+
return appendWhereParts(tableName, node.where, params);
|
|
1620
1621
|
}
|
|
1621
|
-
const
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
throw new BadRequestError("Webhook URL targets a blocked host.");
|
|
1622
|
+
const grouped = buildWhereGroupClause(tableName, node.group, params);
|
|
1623
|
+
if (!grouped) {
|
|
1624
|
+
return "";
|
|
1625
1625
|
}
|
|
1626
|
-
return
|
|
1626
|
+
return grouped.includes(" OR ") ? `(${grouped})` : grouped;
|
|
1627
1627
|
}
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
const resolveDns = options.resolveDns ?? appConfig.env === "production";
|
|
1635
|
-
const urlOptions = { allowHttp: options.allowHttp, resolveDns };
|
|
1636
|
-
const controller = new AbortController;
|
|
1637
|
-
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
1638
|
-
try {
|
|
1639
|
-
let currentUrl = (await assertSafeOutboundUrlResolved(input, urlOptions)).toString();
|
|
1640
|
-
let redirectCount = 0;
|
|
1641
|
-
while (true) {
|
|
1642
|
-
const response = await fetch(currentUrl, {
|
|
1643
|
-
...init,
|
|
1644
|
-
signal: controller.signal,
|
|
1645
|
-
redirect: "manual"
|
|
1646
|
-
});
|
|
1647
|
-
if (response.status >= 300 && response.status < 400) {
|
|
1648
|
-
const location = response.headers.get("location");
|
|
1649
|
-
if (!location || redirectCount >= maxRedirects) {
|
|
1650
|
-
return response;
|
|
1651
|
-
}
|
|
1652
|
-
currentUrl = (await assertSafeOutboundUrlResolved(new URL(location, currentUrl).toString(), urlOptions)).toString();
|
|
1653
|
-
redirectCount += 1;
|
|
1654
|
-
continue;
|
|
1655
|
-
}
|
|
1656
|
-
return response;
|
|
1628
|
+
function buildWhereGroupClause(tableName, nodes, params) {
|
|
1629
|
+
let result = "";
|
|
1630
|
+
for (const node of nodes) {
|
|
1631
|
+
const part = buildWhereNodeClause(tableName, node, params);
|
|
1632
|
+
if (!part) {
|
|
1633
|
+
continue;
|
|
1657
1634
|
}
|
|
1658
|
-
|
|
1659
|
-
|
|
1635
|
+
if (!result) {
|
|
1636
|
+
result = part;
|
|
1637
|
+
continue;
|
|
1638
|
+
}
|
|
1639
|
+
result = node.kind === "or" ? `${result} OR ${part}` : `${result} AND ${part}`;
|
|
1640
|
+
}
|
|
1641
|
+
if (!result) {
|
|
1642
|
+
return "";
|
|
1660
1643
|
}
|
|
1644
|
+
return result;
|
|
1661
1645
|
}
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
backoffMs = 2000;
|
|
1667
|
-
async handle(payload) {
|
|
1668
|
-
const rows = await repositoryConnection`
|
|
1669
|
-
SELECT id, url, secret
|
|
1670
|
-
FROM webhook
|
|
1671
|
-
WHERE id = ${payload.webhookId} AND active = TRUE
|
|
1672
|
-
LIMIT 1
|
|
1673
|
-
`;
|
|
1674
|
-
const webhook = rows[0];
|
|
1675
|
-
if (!webhook) {
|
|
1676
|
-
return;
|
|
1677
|
-
}
|
|
1678
|
-
const body = JSON.stringify({ event: payload.event, payload: payload.payload });
|
|
1679
|
-
const signature = createHmac2("sha256", webhook.secret).update(body).digest("hex");
|
|
1680
|
-
assertSafeOutboundUrl(webhook.url, { allowHttp: appConfig.env !== "production" });
|
|
1681
|
-
let responseStatus = null;
|
|
1682
|
-
let errorMessage = null;
|
|
1683
|
-
try {
|
|
1684
|
-
const response = await safeFetch(webhook.url, {
|
|
1685
|
-
method: "POST",
|
|
1686
|
-
headers: {
|
|
1687
|
-
"content-type": "application/json",
|
|
1688
|
-
"x-workhub-signature": signature
|
|
1689
|
-
},
|
|
1690
|
-
body
|
|
1691
|
-
}, { allowHttp: appConfig.env !== "production" });
|
|
1692
|
-
responseStatus = response.status;
|
|
1693
|
-
if (!response.ok) {
|
|
1694
|
-
throw new Error(`Webhook delivery failed with status ${response.status}.`);
|
|
1695
|
-
}
|
|
1696
|
-
} catch (error) {
|
|
1697
|
-
errorMessage = error instanceof Error ? error.message : String(error);
|
|
1698
|
-
await repositoryConnection`
|
|
1699
|
-
INSERT INTO webhook_delivery (webhook_id, event, payload, response_status, error)
|
|
1700
|
-
VALUES (
|
|
1701
|
-
${webhook.id},
|
|
1702
|
-
${payload.event},
|
|
1703
|
-
${JSON.stringify(payload.payload)}::jsonb,
|
|
1704
|
-
${responseStatus},
|
|
1705
|
-
${errorMessage}
|
|
1706
|
-
)
|
|
1707
|
-
`;
|
|
1708
|
-
throw error instanceof Error ? error : new Error(errorMessage);
|
|
1709
|
-
}
|
|
1710
|
-
await repositoryConnection`
|
|
1711
|
-
INSERT INTO webhook_delivery (webhook_id, event, payload, response_status)
|
|
1712
|
-
VALUES (
|
|
1713
|
-
${webhook.id},
|
|
1714
|
-
${payload.event},
|
|
1715
|
-
${JSON.stringify(payload.payload)}::jsonb,
|
|
1716
|
-
${responseStatus}
|
|
1717
|
-
)
|
|
1718
|
-
`;
|
|
1646
|
+
function buildAdvancedWhereClause(tableName, where = {}, whereNodes = [], params = []) {
|
|
1647
|
+
const nodes = [];
|
|
1648
|
+
if (Object.keys(where).length > 0) {
|
|
1649
|
+
nodes.push({ kind: "and", where });
|
|
1719
1650
|
}
|
|
1651
|
+
nodes.push(...whereNodes);
|
|
1652
|
+
const combined = buildWhereGroupClause(tableName, nodes, params);
|
|
1653
|
+
return {
|
|
1654
|
+
clause: combined ? ` WHERE ${combined}` : "",
|
|
1655
|
+
params
|
|
1656
|
+
};
|
|
1720
1657
|
}
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
class JobRegistry {
|
|
1725
|
-
constructor() {}
|
|
1726
|
-
factories = new Map;
|
|
1727
|
-
instances = new WeakMap;
|
|
1728
|
-
register(name, factory) {
|
|
1729
|
-
this.factories.set(name, factory);
|
|
1658
|
+
function resolveSoftDeleteColumn(table) {
|
|
1659
|
+
if (!table.softDeletes) {
|
|
1660
|
+
return null;
|
|
1730
1661
|
}
|
|
1731
|
-
|
|
1732
|
-
return
|
|
1662
|
+
if (table.softDeletes === true) {
|
|
1663
|
+
return "deleted_at";
|
|
1733
1664
|
}
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1665
|
+
return table.softDeletes.column ?? "deleted_at";
|
|
1666
|
+
}
|
|
1667
|
+
function appendSoftDeleteScope(table, options, clauses) {
|
|
1668
|
+
const column = resolveSoftDeleteColumn(table);
|
|
1669
|
+
if (!column) {
|
|
1670
|
+
return;
|
|
1737
1671
|
}
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
}
|
|
1743
|
-
return factory();
|
|
1672
|
+
const qualifiedColumn = qualifyColumn(table.name, column);
|
|
1673
|
+
if (options.onlyTrashed) {
|
|
1674
|
+
clauses.push(`${qualifiedColumn} IS NOT NULL`);
|
|
1675
|
+
return;
|
|
1744
1676
|
}
|
|
1745
|
-
|
|
1746
|
-
|
|
1677
|
+
if (!options.withTrashed) {
|
|
1678
|
+
clauses.push(`${qualifiedColumn} IS NULL`);
|
|
1747
1679
|
}
|
|
1748
1680
|
}
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1681
|
+
function buildQueryWhereClause(table, options = {}, whereNodes = [], params = []) {
|
|
1682
|
+
const { clause, params: whereParams } = buildAdvancedWhereClause(table.name, options.where ?? {}, whereNodes, params);
|
|
1683
|
+
const softDeleteClauses = [];
|
|
1684
|
+
appendSoftDeleteScope(table, options, softDeleteClauses);
|
|
1685
|
+
if (softDeleteClauses.length === 0) {
|
|
1686
|
+
return { clause, params: whereParams };
|
|
1687
|
+
}
|
|
1688
|
+
const base = clause.replace(/^ WHERE /, "");
|
|
1689
|
+
const scope = softDeleteClauses.join(" AND ");
|
|
1754
1690
|
return {
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
total: input.total,
|
|
1758
|
-
last_page: lastPage
|
|
1691
|
+
clause: base ? ` WHERE (${base}) AND ${scope}` : ` WHERE ${scope}`,
|
|
1692
|
+
params: whereParams
|
|
1759
1693
|
};
|
|
1760
1694
|
}
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
function isPostgresError(error) {
|
|
1764
|
-
return typeof error === "object" && error !== null && (("errno" in error) || ("code" in error));
|
|
1695
|
+
function isQueryOrder(value) {
|
|
1696
|
+
return "column" in value;
|
|
1765
1697
|
}
|
|
1766
|
-
function
|
|
1767
|
-
if (
|
|
1768
|
-
return
|
|
1698
|
+
function normalizeOrderBy(orderBy) {
|
|
1699
|
+
if (!orderBy) {
|
|
1700
|
+
return [];
|
|
1769
1701
|
}
|
|
1770
|
-
if (
|
|
1771
|
-
return
|
|
1702
|
+
if (Array.isArray(orderBy)) {
|
|
1703
|
+
return orderBy;
|
|
1772
1704
|
}
|
|
1773
|
-
if (
|
|
1774
|
-
return
|
|
1705
|
+
if (isQueryOrder(orderBy)) {
|
|
1706
|
+
return [orderBy];
|
|
1775
1707
|
}
|
|
1776
|
-
return
|
|
1708
|
+
return Object.entries(orderBy).map(([column, direction]) => ({
|
|
1709
|
+
column,
|
|
1710
|
+
direction
|
|
1711
|
+
}));
|
|
1777
1712
|
}
|
|
1778
|
-
function
|
|
1779
|
-
|
|
1780
|
-
return
|
|
1781
|
-
}
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
switch (sqlState) {
|
|
1788
|
-
case "23505":
|
|
1789
|
-
return new ConflictError(error.detail ?? "A record with these values already exists.", {
|
|
1790
|
-
constraint: error.constraint
|
|
1791
|
-
});
|
|
1792
|
-
case "23503":
|
|
1793
|
-
return new UnprocessableEntityError(error.detail ?? "Referenced record does not exist.", {
|
|
1794
|
-
constraint: error.constraint
|
|
1795
|
-
});
|
|
1796
|
-
case "23502":
|
|
1797
|
-
return new BadRequestError(error.detail ?? "Required field is missing.", {
|
|
1798
|
-
constraint: error.constraint
|
|
1799
|
-
});
|
|
1800
|
-
case "23514":
|
|
1801
|
-
return new BadRequestError(error.detail ?? "Value violates a database constraint.", {
|
|
1802
|
-
constraint: error.constraint
|
|
1803
|
-
});
|
|
1804
|
-
default:
|
|
1805
|
-
return new BadRequestError(error.message ?? "Database operation failed.", {
|
|
1806
|
-
code: error.code,
|
|
1807
|
-
sqlState
|
|
1808
|
-
});
|
|
1809
|
-
}
|
|
1810
|
-
}
|
|
1811
|
-
async function withDatabaseErrorHandling(operation) {
|
|
1812
|
-
try {
|
|
1813
|
-
return await operation();
|
|
1814
|
-
} catch (error) {
|
|
1815
|
-
throw mapDatabaseError(error);
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
|
-
|
|
1819
|
-
// ../../src/core/database/query.ts
|
|
1820
|
-
function quoteIdentifier(identifier) {
|
|
1821
|
-
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(identifier)) {
|
|
1822
|
-
throw new Error(`Invalid SQL identifier: ${identifier}`);
|
|
1823
|
-
}
|
|
1824
|
-
return `"${identifier}"`;
|
|
1825
|
-
}
|
|
1826
|
-
function qualifyColumn(tableName, column) {
|
|
1827
|
-
return `${quoteIdentifier(tableName)}.${quoteIdentifier(column)}`;
|
|
1828
|
-
}
|
|
1829
|
-
function resolveQualifiedColumn(defaultTable, columnName) {
|
|
1830
|
-
if (columnName.includes(".")) {
|
|
1831
|
-
const [table, column] = columnName.split(".", 2);
|
|
1832
|
-
if (!table || !column) {
|
|
1833
|
-
throw new Error(`Invalid qualified column: ${columnName}`);
|
|
1834
|
-
}
|
|
1835
|
-
return qualifyColumn(table, column);
|
|
1836
|
-
}
|
|
1837
|
-
return qualifyColumn(defaultTable, columnName);
|
|
1838
|
-
}
|
|
1839
|
-
function parseQualifiedColumn(reference) {
|
|
1840
|
-
const [table, column] = reference.split(".", 2);
|
|
1841
|
-
if (!table || !column) {
|
|
1842
|
-
throw new Error(`Join columns must be qualified as table.column: ${reference}`);
|
|
1843
|
-
}
|
|
1844
|
-
return { table, column };
|
|
1845
|
-
}
|
|
1846
|
-
function normalizeDirection(direction = "ASC") {
|
|
1847
|
-
return direction.toUpperCase() === "DESC" ? "DESC" : "ASC";
|
|
1848
|
-
}
|
|
1849
|
-
function isQueryOperator(value) {
|
|
1850
|
-
return value !== null && !Array.isArray(value) && !(value instanceof Date) && typeof value === "object";
|
|
1851
|
-
}
|
|
1852
|
-
function pushParam(values, value) {
|
|
1853
|
-
values.push(value);
|
|
1854
|
-
return `$${values.length}`;
|
|
1855
|
-
}
|
|
1856
|
-
function buildInClause(column, values, params) {
|
|
1857
|
-
if (values.length === 0) {
|
|
1858
|
-
return "1 = 0";
|
|
1859
|
-
}
|
|
1860
|
-
const placeholders = values.map((value) => pushParam(params, value)).join(", ");
|
|
1861
|
-
return `${column} IN (${placeholders})`;
|
|
1862
|
-
}
|
|
1863
|
-
function buildOperatorClauses(column, operator, params) {
|
|
1864
|
-
const clauses = [];
|
|
1865
|
-
if (operator.isNull === true) {
|
|
1866
|
-
clauses.push(`${column} IS NULL`);
|
|
1867
|
-
}
|
|
1868
|
-
if (operator.isNull === false) {
|
|
1869
|
-
clauses.push(`${column} IS NOT NULL`);
|
|
1870
|
-
}
|
|
1871
|
-
if (operator.eq !== undefined) {
|
|
1872
|
-
if (operator.eq === null) {
|
|
1873
|
-
clauses.push(`${column} IS NULL`);
|
|
1874
|
-
} else {
|
|
1875
|
-
clauses.push(`${column} = ${pushParam(params, operator.eq)}`);
|
|
1876
|
-
}
|
|
1877
|
-
}
|
|
1878
|
-
if (operator.in !== undefined) {
|
|
1879
|
-
clauses.push(buildInClause(column, operator.in, params));
|
|
1880
|
-
}
|
|
1881
|
-
if (operator.gt !== undefined) {
|
|
1882
|
-
clauses.push(`${column} > ${pushParam(params, operator.gt)}`);
|
|
1883
|
-
}
|
|
1884
|
-
if (operator.gte !== undefined) {
|
|
1885
|
-
clauses.push(`${column} >= ${pushParam(params, operator.gte)}`);
|
|
1886
|
-
}
|
|
1887
|
-
if (operator.lt !== undefined) {
|
|
1888
|
-
clauses.push(`${column} < ${pushParam(params, operator.lt)}`);
|
|
1889
|
-
}
|
|
1890
|
-
if (operator.lte !== undefined) {
|
|
1891
|
-
clauses.push(`${column} <= ${pushParam(params, operator.lte)}`);
|
|
1892
|
-
}
|
|
1893
|
-
if (operator.ilike !== undefined) {
|
|
1894
|
-
clauses.push(`${column} ILIKE ${pushParam(params, `%${operator.ilike}%`)}`);
|
|
1895
|
-
}
|
|
1896
|
-
if (operator.tsMatch !== undefined) {
|
|
1897
|
-
clauses.push(`${column} @@ plainto_tsquery('english', ${pushParam(params, operator.tsMatch)})`);
|
|
1898
|
-
}
|
|
1899
|
-
return clauses;
|
|
1900
|
-
}
|
|
1901
|
-
function appendWhereParts(tableName, where, params) {
|
|
1902
|
-
const clauses = [];
|
|
1903
|
-
for (const [columnName, filterValue] of Object.entries(where)) {
|
|
1904
|
-
if (filterValue === undefined) {
|
|
1905
|
-
continue;
|
|
1906
|
-
}
|
|
1907
|
-
const column = resolveQualifiedColumn(tableName, columnName);
|
|
1908
|
-
if (Array.isArray(filterValue)) {
|
|
1909
|
-
clauses.push(buildInClause(column, filterValue, params));
|
|
1910
|
-
continue;
|
|
1911
|
-
}
|
|
1912
|
-
if (isQueryOperator(filterValue)) {
|
|
1913
|
-
clauses.push(...buildOperatorClauses(column, filterValue, params));
|
|
1914
|
-
continue;
|
|
1915
|
-
}
|
|
1916
|
-
if (filterValue === null) {
|
|
1917
|
-
clauses.push(`${column} IS NULL`);
|
|
1918
|
-
continue;
|
|
1919
|
-
}
|
|
1920
|
-
clauses.push(`${column} = ${pushParam(params, filterValue)}`);
|
|
1921
|
-
}
|
|
1922
|
-
return clauses.join(" AND ");
|
|
1923
|
-
}
|
|
1924
|
-
function buildWhereNodeClause(tableName, node, params) {
|
|
1925
|
-
if ("where" in node) {
|
|
1926
|
-
return appendWhereParts(tableName, node.where, params);
|
|
1927
|
-
}
|
|
1928
|
-
const grouped = buildWhereGroupClause(tableName, node.group, params);
|
|
1929
|
-
if (!grouped) {
|
|
1930
|
-
return "";
|
|
1931
|
-
}
|
|
1932
|
-
return grouped.includes(" OR ") ? `(${grouped})` : grouped;
|
|
1933
|
-
}
|
|
1934
|
-
function buildWhereGroupClause(tableName, nodes, params) {
|
|
1935
|
-
let result = "";
|
|
1936
|
-
for (const node of nodes) {
|
|
1937
|
-
const part = buildWhereNodeClause(tableName, node, params);
|
|
1938
|
-
if (!part) {
|
|
1939
|
-
continue;
|
|
1940
|
-
}
|
|
1941
|
-
if (!result) {
|
|
1942
|
-
result = part;
|
|
1943
|
-
continue;
|
|
1944
|
-
}
|
|
1945
|
-
result = node.kind === "or" ? `${result} OR ${part}` : `${result} AND ${part}`;
|
|
1946
|
-
}
|
|
1947
|
-
if (!result) {
|
|
1948
|
-
return "";
|
|
1949
|
-
}
|
|
1950
|
-
return result;
|
|
1951
|
-
}
|
|
1952
|
-
function buildAdvancedWhereClause(tableName, where = {}, whereNodes = [], params = []) {
|
|
1953
|
-
const nodes = [];
|
|
1954
|
-
if (Object.keys(where).length > 0) {
|
|
1955
|
-
nodes.push({ kind: "and", where });
|
|
1956
|
-
}
|
|
1957
|
-
nodes.push(...whereNodes);
|
|
1958
|
-
const combined = buildWhereGroupClause(tableName, nodes, params);
|
|
1959
|
-
return {
|
|
1960
|
-
clause: combined ? ` WHERE ${combined}` : "",
|
|
1961
|
-
params
|
|
1962
|
-
};
|
|
1963
|
-
}
|
|
1964
|
-
function resolveSoftDeleteColumn(table) {
|
|
1965
|
-
if (!table.softDeletes) {
|
|
1966
|
-
return null;
|
|
1967
|
-
}
|
|
1968
|
-
if (table.softDeletes === true) {
|
|
1969
|
-
return "deleted_at";
|
|
1970
|
-
}
|
|
1971
|
-
return table.softDeletes.column ?? "deleted_at";
|
|
1972
|
-
}
|
|
1973
|
-
function appendSoftDeleteScope(table, options, clauses) {
|
|
1974
|
-
const column = resolveSoftDeleteColumn(table);
|
|
1975
|
-
if (!column) {
|
|
1976
|
-
return;
|
|
1977
|
-
}
|
|
1978
|
-
const qualifiedColumn = qualifyColumn(table.name, column);
|
|
1979
|
-
if (options.onlyTrashed) {
|
|
1980
|
-
clauses.push(`${qualifiedColumn} IS NOT NULL`);
|
|
1981
|
-
return;
|
|
1982
|
-
}
|
|
1983
|
-
if (!options.withTrashed) {
|
|
1984
|
-
clauses.push(`${qualifiedColumn} IS NULL`);
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
1987
|
-
function buildQueryWhereClause(table, options = {}, whereNodes = [], params = []) {
|
|
1988
|
-
const { clause, params: whereParams } = buildAdvancedWhereClause(table.name, options.where ?? {}, whereNodes, params);
|
|
1989
|
-
const softDeleteClauses = [];
|
|
1990
|
-
appendSoftDeleteScope(table, options, softDeleteClauses);
|
|
1991
|
-
if (softDeleteClauses.length === 0) {
|
|
1992
|
-
return { clause, params: whereParams };
|
|
1993
|
-
}
|
|
1994
|
-
const base = clause.replace(/^ WHERE /, "");
|
|
1995
|
-
const scope = softDeleteClauses.join(" AND ");
|
|
1996
|
-
return {
|
|
1997
|
-
clause: base ? ` WHERE (${base}) AND ${scope}` : ` WHERE ${scope}`,
|
|
1998
|
-
params: whereParams
|
|
1999
|
-
};
|
|
2000
|
-
}
|
|
2001
|
-
function isQueryOrder(value) {
|
|
2002
|
-
return "column" in value;
|
|
2003
|
-
}
|
|
2004
|
-
function normalizeOrderBy(orderBy) {
|
|
2005
|
-
if (!orderBy) {
|
|
2006
|
-
return [];
|
|
2007
|
-
}
|
|
2008
|
-
if (Array.isArray(orderBy)) {
|
|
2009
|
-
return orderBy;
|
|
2010
|
-
}
|
|
2011
|
-
if (isQueryOrder(orderBy)) {
|
|
2012
|
-
return [orderBy];
|
|
2013
|
-
}
|
|
2014
|
-
return Object.entries(orderBy).map(([column, direction]) => ({
|
|
2015
|
-
column,
|
|
2016
|
-
direction
|
|
2017
|
-
}));
|
|
2018
|
-
}
|
|
2019
|
-
function buildOrderByClause(tableName, orderBy) {
|
|
2020
|
-
const parts = normalizeOrderBy(orderBy).map(({ column, direction }) => {
|
|
2021
|
-
return `${resolveQualifiedColumn(tableName, column)} ${normalizeDirection(direction)}`;
|
|
2022
|
-
});
|
|
2023
|
-
return parts.length > 0 ? ` ORDER BY ${parts.join(", ")}` : "";
|
|
2024
|
-
}
|
|
2025
|
-
function buildGroupByClause(tableName, groupBy) {
|
|
2026
|
-
if (!groupBy) {
|
|
2027
|
-
return "";
|
|
1713
|
+
function buildOrderByClause(tableName, orderBy) {
|
|
1714
|
+
const parts = normalizeOrderBy(orderBy).map(({ column, direction }) => {
|
|
1715
|
+
return `${resolveQualifiedColumn(tableName, column)} ${normalizeDirection(direction)}`;
|
|
1716
|
+
});
|
|
1717
|
+
return parts.length > 0 ? ` ORDER BY ${parts.join(", ")}` : "";
|
|
1718
|
+
}
|
|
1719
|
+
function buildGroupByClause(tableName, groupBy) {
|
|
1720
|
+
if (!groupBy) {
|
|
1721
|
+
return "";
|
|
2028
1722
|
}
|
|
2029
1723
|
const columns = (Array.isArray(groupBy) ? groupBy : [groupBy]).map((column) => String(column));
|
|
2030
1724
|
const parts = columns.map((column) => resolveQualifiedColumn(tableName, column));
|
|
@@ -2287,17 +1981,40 @@ function indexMorphToRelation(children, parentsByType, relation) {
|
|
|
2287
1981
|
return result;
|
|
2288
1982
|
}
|
|
2289
1983
|
|
|
2290
|
-
// ../../src/core/database/
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
1984
|
+
// ../../src/core/database/boundConnection.ts
|
|
1985
|
+
var boundConnectionHolder = {
|
|
1986
|
+
connection: null
|
|
1987
|
+
};
|
|
1988
|
+
function getBoundDatabaseConnection() {
|
|
1989
|
+
return boundConnectionHolder.connection;
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
// ../../src/core/database/repositoryConnection.ts
|
|
1993
|
+
function resolveRepositoryConnection() {
|
|
1994
|
+
return getBoundDatabaseConnection() ?? getDefaultDatabaseQuery();
|
|
1995
|
+
}
|
|
1996
|
+
var repositoryConnection = new Proxy(function repositoryConnection2() {}, {
|
|
1997
|
+
apply(_target, _thisArg, args) {
|
|
1998
|
+
return resolveRepositoryConnection()(...args);
|
|
1999
|
+
},
|
|
2000
|
+
get(_target, property) {
|
|
2001
|
+
const connection = resolveRepositoryConnection();
|
|
2002
|
+
const value = connection[property];
|
|
2003
|
+
return typeof value === "function" ? value.bind(connection) : value;
|
|
2004
|
+
}
|
|
2005
|
+
});
|
|
2006
|
+
|
|
2007
|
+
// ../../src/core/database/whereBuilder.ts
|
|
2008
|
+
class WhereBuilder {
|
|
2009
|
+
nodes = [];
|
|
2010
|
+
where(where) {
|
|
2011
|
+
this.nodes.push({ kind: "and", where });
|
|
2012
|
+
return this;
|
|
2013
|
+
}
|
|
2014
|
+
orWhere(where) {
|
|
2015
|
+
this.nodes.push({ kind: "or", where });
|
|
2016
|
+
return this;
|
|
2017
|
+
}
|
|
2301
2018
|
whereGroup(fn) {
|
|
2302
2019
|
const nested = new WhereBuilder;
|
|
2303
2020
|
fn(nested);
|
|
@@ -3429,179 +3146,465 @@ class FailedJobRepository extends baseRepository_default {
|
|
|
3429
3146
|
super(failedJobTable);
|
|
3430
3147
|
}
|
|
3431
3148
|
}
|
|
3432
|
-
var failedJobRepository_default = FailedJobRepository;
|
|
3149
|
+
var failedJobRepository_default = FailedJobRepository;
|
|
3150
|
+
|
|
3151
|
+
// ../../src/core/queue/failedJobService.ts
|
|
3152
|
+
class FailedJobService {
|
|
3153
|
+
repository;
|
|
3154
|
+
constructor(repository) {
|
|
3155
|
+
this.repository = repository;
|
|
3156
|
+
}
|
|
3157
|
+
async recordFailure(input) {
|
|
3158
|
+
return await this.repository.create({
|
|
3159
|
+
job_name: input.jobName,
|
|
3160
|
+
payload: input.payload,
|
|
3161
|
+
exception: input.exception,
|
|
3162
|
+
failed_at: new Date
|
|
3163
|
+
});
|
|
3164
|
+
}
|
|
3165
|
+
listRecent(limit = 50) {
|
|
3166
|
+
return this.repository.findAll({
|
|
3167
|
+
limit,
|
|
3168
|
+
orderBy: { column: "failed_at", direction: "DESC" }
|
|
3169
|
+
});
|
|
3170
|
+
}
|
|
3171
|
+
async retry(id) {
|
|
3172
|
+
const failedJob = await this.repository.findByIdOrThrow(id, (jobId) => new Error(`Failed job ${jobId} not found.`));
|
|
3173
|
+
await this.repository.deleteById(id);
|
|
3174
|
+
return failedJob;
|
|
3175
|
+
}
|
|
3176
|
+
async delete(id) {
|
|
3177
|
+
const deleted = await this.repository.deleteById(id);
|
|
3178
|
+
if (!deleted) {
|
|
3179
|
+
throw new Error(`Failed job ${id} not found.`);
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
async flush() {
|
|
3183
|
+
const jobs = await this.repository.findAll();
|
|
3184
|
+
let deleted = 0;
|
|
3185
|
+
for (const job of jobs) {
|
|
3186
|
+
if (await this.repository.deleteById(job.id)) {
|
|
3187
|
+
deleted += 1;
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
return deleted;
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
var failedJobService_default = FailedJobService;
|
|
3194
|
+
|
|
3195
|
+
// ../../src/core/queue/jobRegistry.ts
|
|
3196
|
+
class JobRegistry {
|
|
3197
|
+
constructor() {}
|
|
3198
|
+
factories = new Map;
|
|
3199
|
+
instances = new WeakMap;
|
|
3200
|
+
register(name, factory) {
|
|
3201
|
+
this.factories.set(name, factory);
|
|
3202
|
+
}
|
|
3203
|
+
resolveName(job) {
|
|
3204
|
+
return this.instances.get(job);
|
|
3205
|
+
}
|
|
3206
|
+
track(name, job) {
|
|
3207
|
+
this.instances.set(job, name);
|
|
3208
|
+
return job;
|
|
3209
|
+
}
|
|
3210
|
+
create(name) {
|
|
3211
|
+
const factory = this.factories.get(name);
|
|
3212
|
+
if (!factory) {
|
|
3213
|
+
return;
|
|
3214
|
+
}
|
|
3215
|
+
return factory();
|
|
3216
|
+
}
|
|
3217
|
+
names() {
|
|
3218
|
+
return [...this.factories.keys()];
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
var jobRegistry = new JobRegistry;
|
|
3222
|
+
|
|
3223
|
+
// ../../src/core/queue/jobRunner.ts
|
|
3224
|
+
async function runQueueJob(envelope, failedJobs) {
|
|
3225
|
+
const job = jobRegistry.create(envelope.name);
|
|
3226
|
+
if (!job) {
|
|
3227
|
+
throw new Error(`Unknown job "${envelope.name}".`);
|
|
3228
|
+
}
|
|
3229
|
+
const attempts = envelope.attempts ?? 0;
|
|
3230
|
+
try {
|
|
3231
|
+
await job.handle(envelope.payload);
|
|
3232
|
+
} catch (error) {
|
|
3233
|
+
const nextAttempt = attempts + 1;
|
|
3234
|
+
const maxAttempts = job.maxAttempts ?? queueConfig.maxAttempts;
|
|
3235
|
+
if (nextAttempt < maxAttempts) {
|
|
3236
|
+
const backoffMs = job.backoffMs ?? queueConfig.backoffMs;
|
|
3237
|
+
await new Promise((resolve) => setTimeout(resolve, backoffMs * nextAttempt));
|
|
3238
|
+
await runQueueJob({
|
|
3239
|
+
...envelope,
|
|
3240
|
+
attempts: nextAttempt
|
|
3241
|
+
}, failedJobs);
|
|
3242
|
+
return;
|
|
3243
|
+
}
|
|
3244
|
+
await failedJobs.recordFailure({
|
|
3245
|
+
jobName: envelope.name,
|
|
3246
|
+
payload: envelope.payload,
|
|
3247
|
+
exception: error instanceof Error ? error.stack ?? error.message : String(error)
|
|
3248
|
+
});
|
|
3249
|
+
throw error;
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
|
|
3253
|
+
// ../../src/core/queue/redisQueue.ts
|
|
3254
|
+
var {RedisClient: RedisClient2 } = globalThis.Bun;
|
|
3255
|
+
var QUEUE_LIST_KEY = "workhub:queue:default";
|
|
3256
|
+
var QUEUE_HIGH_KEY = "workhub:queue:high";
|
|
3257
|
+
var QUEUE_LOW_KEY = "workhub:queue:low";
|
|
3258
|
+
function queueKeyForPriority(priority = "default") {
|
|
3259
|
+
switch (priority) {
|
|
3260
|
+
case "high":
|
|
3261
|
+
return QUEUE_HIGH_KEY;
|
|
3262
|
+
case "low":
|
|
3263
|
+
return QUEUE_LOW_KEY;
|
|
3264
|
+
default:
|
|
3265
|
+
return QUEUE_LIST_KEY;
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
class RedisQueue {
|
|
3269
|
+
client;
|
|
3270
|
+
constructor(redisUrl) {
|
|
3271
|
+
this.client = new RedisClient2(redisUrl);
|
|
3272
|
+
}
|
|
3273
|
+
async dispatch(job, payload) {
|
|
3274
|
+
const name = jobRegistry.resolveName(job);
|
|
3275
|
+
if (!name) {
|
|
3276
|
+
throw new Error("Job is not registered with the queue worker registry.");
|
|
3277
|
+
}
|
|
3278
|
+
const envelope = {
|
|
3279
|
+
name,
|
|
3280
|
+
payload,
|
|
3281
|
+
attempts: 0
|
|
3282
|
+
};
|
|
3283
|
+
const queueKey = queueKeyForPriority(job.priority);
|
|
3284
|
+
await this.client.lpush(queueKey, JSON.stringify(envelope));
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
|
|
3288
|
+
// ../../src/core/queue/resilientQueue.ts
|
|
3289
|
+
class ResilientQueue {
|
|
3290
|
+
failedJobs;
|
|
3291
|
+
asyncDispatch;
|
|
3292
|
+
constructor(failedJobs, asyncDispatch = false) {
|
|
3293
|
+
this.failedJobs = failedJobs;
|
|
3294
|
+
this.asyncDispatch = asyncDispatch;
|
|
3295
|
+
}
|
|
3296
|
+
async dispatch(job, payload) {
|
|
3297
|
+
const name = jobRegistry.resolveName(job);
|
|
3298
|
+
if (!name) {
|
|
3299
|
+
throw new Error("Job is not registered with the queue worker registry.");
|
|
3300
|
+
}
|
|
3301
|
+
const envelope = {
|
|
3302
|
+
name,
|
|
3303
|
+
payload,
|
|
3304
|
+
attempts: 0
|
|
3305
|
+
};
|
|
3306
|
+
if (this.asyncDispatch) {
|
|
3307
|
+
setTimeout(() => {
|
|
3308
|
+
runQueueJob(envelope, this.failedJobs).catch((error) => {
|
|
3309
|
+
console.error("[ResilientQueue] Job failed:", error);
|
|
3310
|
+
});
|
|
3311
|
+
}, 0);
|
|
3312
|
+
return;
|
|
3313
|
+
}
|
|
3314
|
+
await runQueueJob(envelope, this.failedJobs);
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
|
|
3318
|
+
// ../../src/core/queue/publicQueue.ts
|
|
3319
|
+
function createFailedJobService() {
|
|
3320
|
+
return new failedJobService_default(new failedJobRepository_default);
|
|
3321
|
+
}
|
|
3322
|
+
function createTrackedJob(name, job) {
|
|
3323
|
+
return jobRegistry.track(name, job);
|
|
3324
|
+
}
|
|
3325
|
+
function createProductionQueue(driver, options = {}) {
|
|
3326
|
+
options.registerJobs?.();
|
|
3327
|
+
const failedJobs = options.failedJobs ?? createFailedJobService();
|
|
3328
|
+
if (driver === "redis") {
|
|
3329
|
+
if (!options.redisUrl) {
|
|
3330
|
+
throw new Error('QUEUE_DRIVER="redis" requires REDIS_URL to be set.');
|
|
3331
|
+
}
|
|
3332
|
+
return new RedisQueue(options.redisUrl);
|
|
3333
|
+
}
|
|
3334
|
+
return new ResilientQueue(failedJobs, driver === "async");
|
|
3335
|
+
}
|
|
3336
|
+
|
|
3337
|
+
// ../../src/core/jobs/dispatchWebhookJob.ts
|
|
3338
|
+
import { createHmac as createHmac2 } from "crypto";
|
|
3339
|
+
|
|
3340
|
+
// ../../src/core/security/safeUrl.ts
|
|
3341
|
+
import { lookup as dnsLookupImpl } from "dns/promises";
|
|
3342
|
+
var dnsLookup = dnsLookupImpl;
|
|
3343
|
+
var BLOCKED_HOSTNAMES = new Set([
|
|
3344
|
+
"localhost",
|
|
3345
|
+
"127.0.0.1",
|
|
3346
|
+
"0.0.0.0",
|
|
3347
|
+
"::1",
|
|
3348
|
+
"metadata.google.internal"
|
|
3349
|
+
]);
|
|
3350
|
+
function isPrivateIpv4(hostname) {
|
|
3351
|
+
const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
|
|
3352
|
+
if (!match) {
|
|
3353
|
+
return false;
|
|
3354
|
+
}
|
|
3355
|
+
const octets = match.slice(1, 5).map((part) => Number.parseInt(part, 10));
|
|
3356
|
+
if (octets.some((octet) => octet < 0 || octet > 255)) {
|
|
3357
|
+
return true;
|
|
3358
|
+
}
|
|
3359
|
+
const [a = 0, b = 0] = octets;
|
|
3360
|
+
if (a === 10) {
|
|
3361
|
+
return true;
|
|
3362
|
+
}
|
|
3363
|
+
if (a === 127) {
|
|
3364
|
+
return true;
|
|
3365
|
+
}
|
|
3366
|
+
if (a === 0) {
|
|
3367
|
+
return true;
|
|
3368
|
+
}
|
|
3369
|
+
if (a === 169 && b === 254) {
|
|
3370
|
+
return true;
|
|
3371
|
+
}
|
|
3372
|
+
if (a === 172 && b >= 16 && b <= 31) {
|
|
3373
|
+
return true;
|
|
3374
|
+
}
|
|
3375
|
+
if (a === 192 && b === 168) {
|
|
3376
|
+
return true;
|
|
3377
|
+
}
|
|
3378
|
+
return false;
|
|
3379
|
+
}
|
|
3380
|
+
function isBlockedHostname(hostname) {
|
|
3381
|
+
const normalized = hostname.trim().toLowerCase();
|
|
3382
|
+
if (normalized.length === 0) {
|
|
3383
|
+
return true;
|
|
3384
|
+
}
|
|
3385
|
+
if (BLOCKED_HOSTNAMES.has(normalized)) {
|
|
3386
|
+
return true;
|
|
3387
|
+
}
|
|
3388
|
+
if (normalized.endsWith(".local") || normalized.endsWith(".internal")) {
|
|
3389
|
+
return true;
|
|
3390
|
+
}
|
|
3391
|
+
if (normalized.includes(":")) {
|
|
3392
|
+
return true;
|
|
3393
|
+
}
|
|
3394
|
+
return isPrivateIpv4(normalized);
|
|
3395
|
+
}
|
|
3396
|
+
function assertSafeOutboundUrl(rawUrl, options = {}) {
|
|
3397
|
+
let parsed;
|
|
3398
|
+
try {
|
|
3399
|
+
parsed = new URL(rawUrl);
|
|
3400
|
+
} catch {
|
|
3401
|
+
throw new BadRequestError("Webhook URL is invalid.");
|
|
3402
|
+
}
|
|
3403
|
+
if (parsed.protocol !== "https:" && !(options.allowHttp && parsed.protocol === "http:")) {
|
|
3404
|
+
throw new BadRequestError("Webhook URL must use HTTPS.");
|
|
3405
|
+
}
|
|
3406
|
+
if (parsed.username || parsed.password) {
|
|
3407
|
+
throw new BadRequestError("Webhook URL must not include credentials.");
|
|
3408
|
+
}
|
|
3409
|
+
if (isBlockedHostname(parsed.hostname)) {
|
|
3410
|
+
throw new BadRequestError("Webhook URL targets a blocked host.");
|
|
3411
|
+
}
|
|
3412
|
+
return parsed;
|
|
3413
|
+
}
|
|
3414
|
+
function isBlockedIpAddress(address) {
|
|
3415
|
+
return isBlockedHostname(address.trim().toLowerCase());
|
|
3416
|
+
}
|
|
3417
|
+
async function assertSafeOutboundUrlResolved(rawUrl, options = {}) {
|
|
3418
|
+
const parsed = assertSafeOutboundUrl(rawUrl, options);
|
|
3419
|
+
if (options.resolveDns === false) {
|
|
3420
|
+
return parsed;
|
|
3421
|
+
}
|
|
3422
|
+
const hostname = parsed.hostname.trim().toLowerCase();
|
|
3423
|
+
const results = await dnsLookup(hostname, { all: true, verbatim: true });
|
|
3424
|
+
if (results.some((result) => isBlockedIpAddress(result.address))) {
|
|
3425
|
+
throw new BadRequestError("Webhook URL targets a blocked host.");
|
|
3426
|
+
}
|
|
3427
|
+
return parsed;
|
|
3428
|
+
}
|
|
3429
|
+
|
|
3430
|
+
// ../../src/core/security/safeFetch.ts
|
|
3431
|
+
var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
|
|
3432
|
+
async function safeFetch(input, init = {}, options = {}) {
|
|
3433
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
|
|
3434
|
+
const maxRedirects = options.maxRedirects ?? 0;
|
|
3435
|
+
const resolveDns = options.resolveDns ?? appConfig.env === "production";
|
|
3436
|
+
const urlOptions = { allowHttp: options.allowHttp, resolveDns };
|
|
3437
|
+
const controller = new AbortController;
|
|
3438
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
3439
|
+
try {
|
|
3440
|
+
let currentUrl = (await assertSafeOutboundUrlResolved(input, urlOptions)).toString();
|
|
3441
|
+
let redirectCount = 0;
|
|
3442
|
+
while (true) {
|
|
3443
|
+
const response = await fetch(currentUrl, {
|
|
3444
|
+
...init,
|
|
3445
|
+
signal: controller.signal,
|
|
3446
|
+
redirect: "manual"
|
|
3447
|
+
});
|
|
3448
|
+
if (response.status >= 300 && response.status < 400) {
|
|
3449
|
+
const location = response.headers.get("location");
|
|
3450
|
+
if (!location || redirectCount >= maxRedirects) {
|
|
3451
|
+
return response;
|
|
3452
|
+
}
|
|
3453
|
+
currentUrl = (await assertSafeOutboundUrlResolved(new URL(location, currentUrl).toString(), urlOptions)).toString();
|
|
3454
|
+
redirectCount += 1;
|
|
3455
|
+
continue;
|
|
3456
|
+
}
|
|
3457
|
+
return response;
|
|
3458
|
+
}
|
|
3459
|
+
} finally {
|
|
3460
|
+
clearTimeout(timeout);
|
|
3461
|
+
}
|
|
3462
|
+
}
|
|
3433
3463
|
|
|
3434
|
-
// ../../src/core/
|
|
3435
|
-
class
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
listRecent(limit = 50) {
|
|
3449
|
-
return this.repository.findAll({
|
|
3450
|
-
limit,
|
|
3451
|
-
orderBy: { column: "failed_at", direction: "DESC" }
|
|
3452
|
-
});
|
|
3453
|
-
}
|
|
3454
|
-
async retry(id) {
|
|
3455
|
-
const failedJob = await this.repository.findByIdOrThrow(id, (jobId) => new Error(`Failed job ${jobId} not found.`));
|
|
3456
|
-
await this.repository.deleteById(id);
|
|
3457
|
-
return failedJob;
|
|
3458
|
-
}
|
|
3459
|
-
async delete(id) {
|
|
3460
|
-
const deleted = await this.repository.deleteById(id);
|
|
3461
|
-
if (!deleted) {
|
|
3462
|
-
throw new Error(`Failed job ${id} not found.`);
|
|
3464
|
+
// ../../src/core/jobs/dispatchWebhookJob.ts
|
|
3465
|
+
class DispatchWebhookJob extends Job {
|
|
3466
|
+
maxAttempts = 3;
|
|
3467
|
+
backoffMs = 2000;
|
|
3468
|
+
async handle(payload) {
|
|
3469
|
+
const rows = await repositoryConnection`
|
|
3470
|
+
SELECT id, url, secret
|
|
3471
|
+
FROM webhook
|
|
3472
|
+
WHERE id = ${payload.webhookId} AND active = TRUE
|
|
3473
|
+
LIMIT 1
|
|
3474
|
+
`;
|
|
3475
|
+
const webhook = rows[0];
|
|
3476
|
+
if (!webhook) {
|
|
3477
|
+
return;
|
|
3463
3478
|
}
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
let
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3479
|
+
const body = JSON.stringify({ event: payload.event, payload: payload.payload });
|
|
3480
|
+
const signature = createHmac2("sha256", webhook.secret).update(body).digest("hex");
|
|
3481
|
+
assertSafeOutboundUrl(webhook.url, { allowHttp: appConfig.env !== "production" });
|
|
3482
|
+
let responseStatus = null;
|
|
3483
|
+
let errorMessage = null;
|
|
3484
|
+
try {
|
|
3485
|
+
const response = await safeFetch(webhook.url, {
|
|
3486
|
+
method: "POST",
|
|
3487
|
+
headers: {
|
|
3488
|
+
"content-type": "application/json",
|
|
3489
|
+
"x-workhub-signature": signature
|
|
3490
|
+
},
|
|
3491
|
+
body
|
|
3492
|
+
}, { allowHttp: appConfig.env !== "production" });
|
|
3493
|
+
responseStatus = response.status;
|
|
3494
|
+
if (!response.ok) {
|
|
3495
|
+
throw new Error(`Webhook delivery failed with status ${response.status}.`);
|
|
3471
3496
|
}
|
|
3497
|
+
} catch (error) {
|
|
3498
|
+
errorMessage = error instanceof Error ? error.message : String(error);
|
|
3499
|
+
await repositoryConnection`
|
|
3500
|
+
INSERT INTO webhook_delivery (webhook_id, event, payload, response_status, error)
|
|
3501
|
+
VALUES (
|
|
3502
|
+
${webhook.id},
|
|
3503
|
+
${payload.event},
|
|
3504
|
+
${JSON.stringify(payload.payload)}::jsonb,
|
|
3505
|
+
${responseStatus},
|
|
3506
|
+
${errorMessage}
|
|
3507
|
+
)
|
|
3508
|
+
`;
|
|
3509
|
+
throw error instanceof Error ? error : new Error(errorMessage);
|
|
3472
3510
|
}
|
|
3473
|
-
|
|
3511
|
+
await repositoryConnection`
|
|
3512
|
+
INSERT INTO webhook_delivery (webhook_id, event, payload, response_status)
|
|
3513
|
+
VALUES (
|
|
3514
|
+
${webhook.id},
|
|
3515
|
+
${payload.event},
|
|
3516
|
+
${JSON.stringify(payload.payload)}::jsonb,
|
|
3517
|
+
${responseStatus}
|
|
3518
|
+
)
|
|
3519
|
+
`;
|
|
3474
3520
|
}
|
|
3475
3521
|
}
|
|
3476
|
-
var
|
|
3522
|
+
var dispatchWebhookJob_default = DispatchWebhookJob;
|
|
3477
3523
|
|
|
3478
|
-
// ../../src/core/
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3524
|
+
// ../../src/core/logging/logger.ts
|
|
3525
|
+
class Logger {
|
|
3526
|
+
channel;
|
|
3527
|
+
constructor(channel = "app") {
|
|
3528
|
+
this.channel = channel;
|
|
3483
3529
|
}
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
attempts: nextAttempt
|
|
3496
|
-
}, failedJobs);
|
|
3530
|
+
write(level, message, context = {}) {
|
|
3531
|
+
const entry = {
|
|
3532
|
+
level,
|
|
3533
|
+
channel: this.channel,
|
|
3534
|
+
message,
|
|
3535
|
+
timestamp: new Date().toISOString(),
|
|
3536
|
+
...context
|
|
3537
|
+
};
|
|
3538
|
+
const line = JSON.stringify(entry);
|
|
3539
|
+
if (level === "error") {
|
|
3540
|
+
console.error(line);
|
|
3497
3541
|
return;
|
|
3498
3542
|
}
|
|
3499
|
-
|
|
3500
|
-
jobName: envelope.name,
|
|
3501
|
-
payload: envelope.payload,
|
|
3502
|
-
exception: error instanceof Error ? error.stack ?? error.message : String(error)
|
|
3503
|
-
});
|
|
3504
|
-
throw error;
|
|
3543
|
+
console.log(line);
|
|
3505
3544
|
}
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
// ../../src/core/queue/redisQueue.ts
|
|
3509
|
-
var {RedisClient: RedisClient2 } = globalThis.Bun;
|
|
3510
|
-
var QUEUE_LIST_KEY = "workhub:queue:default";
|
|
3511
|
-
var QUEUE_HIGH_KEY = "workhub:queue:high";
|
|
3512
|
-
var QUEUE_LOW_KEY = "workhub:queue:low";
|
|
3513
|
-
function queueKeyForPriority(priority = "default") {
|
|
3514
|
-
switch (priority) {
|
|
3515
|
-
case "high":
|
|
3516
|
-
return QUEUE_HIGH_KEY;
|
|
3517
|
-
case "low":
|
|
3518
|
-
return QUEUE_LOW_KEY;
|
|
3519
|
-
default:
|
|
3520
|
-
return QUEUE_LIST_KEY;
|
|
3545
|
+
debug(message, context) {
|
|
3546
|
+
this.write("debug", message, context);
|
|
3521
3547
|
}
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
client;
|
|
3525
|
-
constructor(redisUrl) {
|
|
3526
|
-
this.client = new RedisClient2(redisUrl);
|
|
3548
|
+
info(message, context) {
|
|
3549
|
+
this.write("info", message, context);
|
|
3527
3550
|
}
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
const envelope = {
|
|
3534
|
-
name,
|
|
3535
|
-
payload,
|
|
3536
|
-
attempts: 0
|
|
3537
|
-
};
|
|
3538
|
-
const queueKey = queueKeyForPriority(job.priority);
|
|
3539
|
-
await this.client.lpush(queueKey, JSON.stringify(envelope));
|
|
3551
|
+
warn(message, context) {
|
|
3552
|
+
this.write("warn", message, context);
|
|
3553
|
+
}
|
|
3554
|
+
error(message, context) {
|
|
3555
|
+
this.write("error", message, context);
|
|
3540
3556
|
}
|
|
3541
3557
|
}
|
|
3558
|
+
var appLogger = new Logger("app");
|
|
3542
3559
|
|
|
3543
|
-
// ../../src/
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
async dispatch(job, payload) {
|
|
3552
|
-
const name = jobRegistry.resolveName(job);
|
|
3553
|
-
if (!name) {
|
|
3554
|
-
throw new Error("Job is not registered with the queue worker registry.");
|
|
3555
|
-
}
|
|
3556
|
-
const envelope = {
|
|
3557
|
-
name,
|
|
3558
|
-
payload,
|
|
3559
|
-
attempts: 0
|
|
3560
|
-
};
|
|
3561
|
-
if (this.asyncDispatch) {
|
|
3562
|
-
setTimeout(() => {
|
|
3563
|
-
runQueueJob(envelope, this.failedJobs).catch((error) => {
|
|
3564
|
-
console.error("[ResilientQueue] Job failed:", error);
|
|
3565
|
-
});
|
|
3566
|
-
}, 0);
|
|
3567
|
-
return;
|
|
3568
|
-
}
|
|
3569
|
-
await runQueueJob(envelope, this.failedJobs);
|
|
3560
|
+
// ../../src/bootstrap/applicationRegistry.ts
|
|
3561
|
+
var activeContext;
|
|
3562
|
+
function setActiveApplicationContext(context) {
|
|
3563
|
+
activeContext = context;
|
|
3564
|
+
}
|
|
3565
|
+
function requireActiveApplicationContext() {
|
|
3566
|
+
if (!activeContext) {
|
|
3567
|
+
throw new Error("The application context has not been bootstrapped.");
|
|
3570
3568
|
}
|
|
3569
|
+
return activeContext;
|
|
3571
3570
|
}
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
function createFailedJobService() {
|
|
3575
|
-
return new failedJobService_default(new failedJobRepository_default);
|
|
3571
|
+
function resolveApplicationCache() {
|
|
3572
|
+
return getRequiredDependency(requireActiveApplicationContext().dependencies, "cache");
|
|
3576
3573
|
}
|
|
3577
|
-
function
|
|
3578
|
-
return
|
|
3574
|
+
function resolveApplicationQueue() {
|
|
3575
|
+
return requireActiveApplicationContext().container.resolve(CORE_QUEUE_TOKEN);
|
|
3579
3576
|
}
|
|
3580
|
-
function
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3577
|
+
function resolveApplicationAuth() {
|
|
3578
|
+
return requireActiveApplicationContext().container.resolve(CORE_AUTH_TOKEN);
|
|
3579
|
+
}
|
|
3580
|
+
function resolveApplicationPolicyGate() {
|
|
3581
|
+
return requireActiveApplicationContext().container.resolve(CORE_POLICY_GATE_TOKEN);
|
|
3582
|
+
}
|
|
3583
|
+
function resolveApplicationConfig() {
|
|
3584
|
+
return requireActiveApplicationContext().config;
|
|
3585
|
+
}
|
|
3586
|
+
function resolveApplicationLogger() {
|
|
3587
|
+
return appLogger;
|
|
3588
|
+
}
|
|
3589
|
+
function resolveApplicationDependencies() {
|
|
3590
|
+
return requireActiveApplicationContext().dependencies;
|
|
3590
3591
|
}
|
|
3591
3592
|
|
|
3592
|
-
// ../../src/
|
|
3593
|
-
var FAILED_JOB_SERVICE_TOKEN = "core.failedJobs";
|
|
3593
|
+
// ../../src/bootstrap/queue/defaultJobs.ts
|
|
3594
3594
|
function registerDefaultJobs() {
|
|
3595
3595
|
jobRegistry.register("cache.invalidate-tags", () => {
|
|
3596
3596
|
return new invalidateCacheTagsJob_default(resolveApplicationCache());
|
|
3597
3597
|
});
|
|
3598
3598
|
jobRegistry.register("webhook.dispatch", () => new dispatchWebhookJob_default);
|
|
3599
3599
|
}
|
|
3600
|
-
|
|
3600
|
+
|
|
3601
|
+
// ../../src/core/queue/createAppQueue.ts
|
|
3602
|
+
var FAILED_JOB_SERVICE_TOKEN = "core.failedJobs";
|
|
3603
|
+
function createAppQueue(driver, redisUrl, failedJobs = createFailedJobService(), registerJobs) {
|
|
3601
3604
|
return createProductionQueue(driver, {
|
|
3602
3605
|
redisUrl,
|
|
3603
3606
|
failedJobs,
|
|
3604
|
-
registerJobs
|
|
3607
|
+
registerJobs
|
|
3605
3608
|
});
|
|
3606
3609
|
}
|
|
3607
3610
|
|
|
@@ -3709,7 +3712,7 @@ var queueProvider = {
|
|
|
3709
3712
|
config.set("queue.driver", driver);
|
|
3710
3713
|
const failedJobs = createFailedJobService();
|
|
3711
3714
|
container.set(FAILED_JOB_SERVICE_TOKEN, failedJobs);
|
|
3712
|
-
container.set(CORE_QUEUE_TOKEN, createAppQueue(driver, config.get(REDIS_URL_CONFIG_KEY) ?? process.env.REDIS_URL, failedJobs));
|
|
3715
|
+
container.set(CORE_QUEUE_TOKEN, createAppQueue(driver, config.get(REDIS_URL_CONFIG_KEY) ?? process.env.REDIS_URL, failedJobs, registerDefaultJobs));
|
|
3713
3716
|
}
|
|
3714
3717
|
};
|
|
3715
3718
|
var queue_default = queueProvider;
|