@getstrata/core 0.5.16 → 0.5.17
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/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/queue/createAppQueue.js +484 -484
- package/dist/entries/queue/queueMetrics.js +881 -881
- package/dist/index.js +9 -8
- package/package.json +1 -1
|
@@ -1,222 +1,47 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
// ../../src/core/
|
|
3
|
-
class
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
2
|
+
// ../../src/core/events/eventBus.ts
|
|
3
|
+
class EventBus {
|
|
4
|
+
constructor() {}
|
|
5
|
+
listeners = new Map;
|
|
6
|
+
listen(event, listener) {
|
|
7
|
+
const handlers = this.listeners.get(event) ?? new Set;
|
|
8
|
+
handlers.add(listener);
|
|
9
|
+
this.listeners.set(event, handlers);
|
|
10
|
+
return () => {
|
|
11
|
+
handlers.delete(listener);
|
|
12
|
+
if (handlers.size === 0) {
|
|
13
|
+
this.listeners.delete(event);
|
|
14
|
+
}
|
|
15
15
|
};
|
|
16
|
-
const line = JSON.stringify(entry);
|
|
17
|
-
if (level === "error") {
|
|
18
|
-
console.error(line);
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
console.log(line);
|
|
22
|
-
}
|
|
23
|
-
debug(message, context) {
|
|
24
|
-
this.write("debug", message, context);
|
|
25
|
-
}
|
|
26
|
-
info(message, context) {
|
|
27
|
-
this.write("info", message, context);
|
|
28
|
-
}
|
|
29
|
-
warn(message, context) {
|
|
30
|
-
this.write("warn", message, context);
|
|
31
|
-
}
|
|
32
|
-
error(message, context) {
|
|
33
|
-
this.write("error", message, context);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
var appLogger = new Logger("app");
|
|
37
|
-
|
|
38
|
-
// ../../src/bootstrap/config.ts
|
|
39
|
-
var CORE_TOKEN_SERVICE_TOKEN = "core.tokenService";
|
|
40
|
-
var DEFAULT_QUEUE_DRIVER = "sync";
|
|
41
|
-
|
|
42
|
-
// ../../src/bootstrap/contracts.ts
|
|
43
|
-
class ServiceContainer {
|
|
44
|
-
services = new Map;
|
|
45
|
-
singletonFactories = new Map;
|
|
46
|
-
bindings = new Map;
|
|
47
|
-
set(key, value) {
|
|
48
|
-
this.singletonFactories.delete(key);
|
|
49
|
-
this.bindings.delete(key);
|
|
50
|
-
this.services.set(key, value);
|
|
51
|
-
return value;
|
|
52
|
-
}
|
|
53
|
-
singleton(key, factory) {
|
|
54
|
-
this.bindings.delete(key);
|
|
55
|
-
this.services.delete(key);
|
|
56
|
-
this.singletonFactories.set(key, factory);
|
|
57
|
-
}
|
|
58
|
-
bind(key, factory) {
|
|
59
|
-
this.singletonFactories.delete(key);
|
|
60
|
-
this.services.delete(key);
|
|
61
|
-
this.bindings.set(key, factory);
|
|
62
|
-
}
|
|
63
|
-
get(key) {
|
|
64
|
-
if (this.services.has(key)) {
|
|
65
|
-
return this.services.get(key);
|
|
66
|
-
}
|
|
67
|
-
const singletonFactory = this.singletonFactories.get(key);
|
|
68
|
-
if (singletonFactory) {
|
|
69
|
-
const value = singletonFactory(this);
|
|
70
|
-
this.services.set(key, value);
|
|
71
|
-
return value;
|
|
72
|
-
}
|
|
73
|
-
const binding = this.bindings.get(key);
|
|
74
|
-
if (binding) {
|
|
75
|
-
return binding(this);
|
|
76
|
-
}
|
|
77
|
-
throw new Error(`Service "${key}" is not registered.`);
|
|
78
|
-
}
|
|
79
|
-
resolve(key) {
|
|
80
|
-
return this.get(key);
|
|
81
|
-
}
|
|
82
|
-
has(key) {
|
|
83
|
-
return this.services.has(key) || this.singletonFactories.has(key) || this.bindings.has(key);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
class ConfigStore {
|
|
88
|
-
values = new Map;
|
|
89
|
-
set(key, value) {
|
|
90
|
-
this.values.set(key, value);
|
|
91
|
-
return value;
|
|
92
|
-
}
|
|
93
|
-
get(key) {
|
|
94
|
-
return this.values.get(key);
|
|
95
|
-
}
|
|
96
|
-
require(key) {
|
|
97
|
-
if (!this.values.has(key)) {
|
|
98
|
-
throw new Error(`Config key "${key}" is not defined.`);
|
|
99
|
-
}
|
|
100
|
-
return this.values.get(key);
|
|
101
|
-
}
|
|
102
|
-
has(key) {
|
|
103
|
-
return this.values.has(key);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
function getRequiredDependency(dependencies, key) {
|
|
107
|
-
const dependency = dependencies[key];
|
|
108
|
-
if (dependency === undefined) {
|
|
109
|
-
throw new Error(`Required dependency "${key}" is not registered.`);
|
|
110
|
-
}
|
|
111
|
-
return dependency;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// ../../src/bootstrap/applicationRegistry.ts
|
|
115
|
-
var activeContext;
|
|
116
|
-
function requireActiveApplicationContext() {
|
|
117
|
-
if (!activeContext) {
|
|
118
|
-
throw new Error("The application context has not been bootstrapped.");
|
|
119
|
-
}
|
|
120
|
-
return activeContext;
|
|
121
|
-
}
|
|
122
|
-
function resolveApplicationCache() {
|
|
123
|
-
return getRequiredDependency(requireActiveApplicationContext().dependencies, "cache");
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// ../../src/core/jobs/dispatchWebhookJob.ts
|
|
127
|
-
import { createHmac } from "crypto";
|
|
128
|
-
|
|
129
|
-
// ../../src/config/app.ts
|
|
130
|
-
var appConfig = {
|
|
131
|
-
name: "WorkHub",
|
|
132
|
-
env: process.env.APP_ENV ?? "local",
|
|
133
|
-
debug: (process.env.APP_DEBUG ?? "true") !== "false",
|
|
134
|
-
url: process.env.APP_URL ?? "http://localhost:3000",
|
|
135
|
-
apiPrefix: process.env.API_PREFIX ?? "/api/v1"
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
// ../../src/core/database/boundConnection.ts
|
|
139
|
-
var boundConnectionHolder = {
|
|
140
|
-
connection: null
|
|
141
|
-
};
|
|
142
|
-
function getBoundDatabaseConnection() {
|
|
143
|
-
return boundConnectionHolder.connection;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// ../../src/core/database/connectionContext.ts
|
|
147
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
148
|
-
var activeConnection = new AsyncLocalStorage;
|
|
149
|
-
function getActiveDatabaseConnection(fallback) {
|
|
150
|
-
return activeConnection.getStore() ?? fallback;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// ../../src/core/database/queryProxy.ts
|
|
154
|
-
var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
|
|
155
|
-
function createDatabaseQueryProxy(pool) {
|
|
156
|
-
function resolveDatabase() {
|
|
157
|
-
return getActiveDatabaseConnection(pool);
|
|
158
16
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
17
|
+
async dispatch(event, payload) {
|
|
18
|
+
const handlers = this.listeners.get(event);
|
|
19
|
+
if (!handlers || handlers.size === 0) {
|
|
20
|
+
return;
|
|
162
21
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
return new Proxy(function database() {}, {
|
|
166
|
-
apply(_target, _thisArg, args) {
|
|
167
|
-
return resolveDatabase()(...args);
|
|
168
|
-
},
|
|
169
|
-
get(_target, property) {
|
|
170
|
-
const connection = resolveDatabaseForProperty(property);
|
|
171
|
-
const value = connection[property];
|
|
172
|
-
return typeof value === "function" ? value.bind(connection) : value;
|
|
22
|
+
for (const handler of handlers) {
|
|
23
|
+
await handler(payload);
|
|
173
24
|
}
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// ../../src/core/database/defaultConnection.ts
|
|
178
|
-
var defaultPool = {
|
|
179
|
-
connection: null
|
|
180
|
-
};
|
|
181
|
-
var defaultQuery = {
|
|
182
|
-
connection: null
|
|
183
|
-
};
|
|
184
|
-
function registerDefaultDatabasePool(connection) {
|
|
185
|
-
defaultPool.connection = connection;
|
|
186
|
-
defaultQuery.connection = createDatabaseQueryProxy(connection);
|
|
187
|
-
}
|
|
188
|
-
function getDefaultDatabaseQuery() {
|
|
189
|
-
if (!defaultQuery.connection) {
|
|
190
|
-
throw new Error("Default database query handle is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
|
|
191
25
|
}
|
|
192
|
-
return defaultQuery.connection;
|
|
193
26
|
}
|
|
27
|
+
var eventBus = new EventBus;
|
|
194
28
|
|
|
195
|
-
// ../../src/core/
|
|
196
|
-
function
|
|
197
|
-
return
|
|
29
|
+
// ../../src/core/events/index.ts
|
|
30
|
+
function modelEventName(tableName, action) {
|
|
31
|
+
return `${tableName}.${action}`;
|
|
198
32
|
}
|
|
199
|
-
var repositoryConnection = new Proxy(function repositoryConnection2() {}, {
|
|
200
|
-
apply(_target, _thisArg, args) {
|
|
201
|
-
return resolveRepositoryConnection()(...args);
|
|
202
|
-
},
|
|
203
|
-
get(_target, property) {
|
|
204
|
-
const connection = resolveRepositoryConnection();
|
|
205
|
-
const value = connection[property];
|
|
206
|
-
return typeof value === "function" ? value.bind(connection) : value;
|
|
207
|
-
}
|
|
208
|
-
});
|
|
209
33
|
|
|
210
|
-
// ../../src/core/
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
34
|
+
// ../../src/core/pagination/index.ts
|
|
35
|
+
function buildPaginationMeta(input) {
|
|
36
|
+
const lastPage = Math.max(1, Math.ceil(input.total / input.perPage));
|
|
37
|
+
return {
|
|
38
|
+
page: input.page,
|
|
39
|
+
per_page: input.perPage,
|
|
40
|
+
total: input.total,
|
|
41
|
+
last_page: lastPage
|
|
42
|
+
};
|
|
215
43
|
}
|
|
216
44
|
|
|
217
|
-
// ../../src/core/security/safeUrl.ts
|
|
218
|
-
import { lookup as dnsLookupImpl } from "dns/promises";
|
|
219
|
-
|
|
220
45
|
// ../../src/core/errors/http.ts
|
|
221
46
|
class HttpError extends Error {
|
|
222
47
|
status;
|
|
@@ -283,279 +108,6 @@ class PreconditionFailedError extends HttpError {
|
|
|
283
108
|
}
|
|
284
109
|
}
|
|
285
110
|
|
|
286
|
-
// ../../src/core/security/safeUrl.ts
|
|
287
|
-
var dnsLookup = dnsLookupImpl;
|
|
288
|
-
var BLOCKED_HOSTNAMES = new Set([
|
|
289
|
-
"localhost",
|
|
290
|
-
"127.0.0.1",
|
|
291
|
-
"0.0.0.0",
|
|
292
|
-
"::1",
|
|
293
|
-
"metadata.google.internal"
|
|
294
|
-
]);
|
|
295
|
-
function isPrivateIpv4(hostname) {
|
|
296
|
-
const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
|
|
297
|
-
if (!match) {
|
|
298
|
-
return false;
|
|
299
|
-
}
|
|
300
|
-
const octets = match.slice(1, 5).map((part) => Number.parseInt(part, 10));
|
|
301
|
-
if (octets.some((octet) => octet < 0 || octet > 255)) {
|
|
302
|
-
return true;
|
|
303
|
-
}
|
|
304
|
-
const [a = 0, b = 0] = octets;
|
|
305
|
-
if (a === 10) {
|
|
306
|
-
return true;
|
|
307
|
-
}
|
|
308
|
-
if (a === 127) {
|
|
309
|
-
return true;
|
|
310
|
-
}
|
|
311
|
-
if (a === 0) {
|
|
312
|
-
return true;
|
|
313
|
-
}
|
|
314
|
-
if (a === 169 && b === 254) {
|
|
315
|
-
return true;
|
|
316
|
-
}
|
|
317
|
-
if (a === 172 && b >= 16 && b <= 31) {
|
|
318
|
-
return true;
|
|
319
|
-
}
|
|
320
|
-
if (a === 192 && b === 168) {
|
|
321
|
-
return true;
|
|
322
|
-
}
|
|
323
|
-
return false;
|
|
324
|
-
}
|
|
325
|
-
function isBlockedHostname(hostname) {
|
|
326
|
-
const normalized = hostname.trim().toLowerCase();
|
|
327
|
-
if (normalized.length === 0) {
|
|
328
|
-
return true;
|
|
329
|
-
}
|
|
330
|
-
if (BLOCKED_HOSTNAMES.has(normalized)) {
|
|
331
|
-
return true;
|
|
332
|
-
}
|
|
333
|
-
if (normalized.endsWith(".local") || normalized.endsWith(".internal")) {
|
|
334
|
-
return true;
|
|
335
|
-
}
|
|
336
|
-
if (normalized.includes(":")) {
|
|
337
|
-
return true;
|
|
338
|
-
}
|
|
339
|
-
return isPrivateIpv4(normalized);
|
|
340
|
-
}
|
|
341
|
-
function assertSafeOutboundUrl(rawUrl, options = {}) {
|
|
342
|
-
let parsed;
|
|
343
|
-
try {
|
|
344
|
-
parsed = new URL(rawUrl);
|
|
345
|
-
} catch {
|
|
346
|
-
throw new BadRequestError("Webhook URL is invalid.");
|
|
347
|
-
}
|
|
348
|
-
if (parsed.protocol !== "https:" && !(options.allowHttp && parsed.protocol === "http:")) {
|
|
349
|
-
throw new BadRequestError("Webhook URL must use HTTPS.");
|
|
350
|
-
}
|
|
351
|
-
if (parsed.username || parsed.password) {
|
|
352
|
-
throw new BadRequestError("Webhook URL must not include credentials.");
|
|
353
|
-
}
|
|
354
|
-
if (isBlockedHostname(parsed.hostname)) {
|
|
355
|
-
throw new BadRequestError("Webhook URL targets a blocked host.");
|
|
356
|
-
}
|
|
357
|
-
return parsed;
|
|
358
|
-
}
|
|
359
|
-
function isBlockedIpAddress(address) {
|
|
360
|
-
return isBlockedHostname(address.trim().toLowerCase());
|
|
361
|
-
}
|
|
362
|
-
async function assertSafeOutboundUrlResolved(rawUrl, options = {}) {
|
|
363
|
-
const parsed = assertSafeOutboundUrl(rawUrl, options);
|
|
364
|
-
if (options.resolveDns === false) {
|
|
365
|
-
return parsed;
|
|
366
|
-
}
|
|
367
|
-
const hostname = parsed.hostname.trim().toLowerCase();
|
|
368
|
-
const results = await dnsLookup(hostname, { all: true, verbatim: true });
|
|
369
|
-
if (results.some((result) => isBlockedIpAddress(result.address))) {
|
|
370
|
-
throw new BadRequestError("Webhook URL targets a blocked host.");
|
|
371
|
-
}
|
|
372
|
-
return parsed;
|
|
373
|
-
}
|
|
374
|
-
function setDnsLookupForTests(lookupFn) {
|
|
375
|
-
dnsLookup = lookupFn;
|
|
376
|
-
}
|
|
377
|
-
function resetDnsLookupForTests() {
|
|
378
|
-
dnsLookup = dnsLookupImpl;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// ../../src/core/security/safeFetch.ts
|
|
382
|
-
var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
|
|
383
|
-
async function safeFetch(input, init = {}, options = {}) {
|
|
384
|
-
const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
|
|
385
|
-
const maxRedirects = options.maxRedirects ?? 0;
|
|
386
|
-
const resolveDns = options.resolveDns ?? appConfig.env === "production";
|
|
387
|
-
const urlOptions = { allowHttp: options.allowHttp, resolveDns };
|
|
388
|
-
const controller = new AbortController;
|
|
389
|
-
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
390
|
-
try {
|
|
391
|
-
let currentUrl = (await assertSafeOutboundUrlResolved(input, urlOptions)).toString();
|
|
392
|
-
let redirectCount = 0;
|
|
393
|
-
while (true) {
|
|
394
|
-
const response = await fetch(currentUrl, {
|
|
395
|
-
...init,
|
|
396
|
-
signal: controller.signal,
|
|
397
|
-
redirect: "manual"
|
|
398
|
-
});
|
|
399
|
-
if (response.status >= 300 && response.status < 400) {
|
|
400
|
-
const location = response.headers.get("location");
|
|
401
|
-
if (!location || redirectCount >= maxRedirects) {
|
|
402
|
-
return response;
|
|
403
|
-
}
|
|
404
|
-
currentUrl = (await assertSafeOutboundUrlResolved(new URL(location, currentUrl).toString(), urlOptions)).toString();
|
|
405
|
-
redirectCount += 1;
|
|
406
|
-
continue;
|
|
407
|
-
}
|
|
408
|
-
return response;
|
|
409
|
-
}
|
|
410
|
-
} finally {
|
|
411
|
-
clearTimeout(timeout);
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// ../../src/core/jobs/dispatchWebhookJob.ts
|
|
416
|
-
class DispatchWebhookJob extends Job {
|
|
417
|
-
maxAttempts = 3;
|
|
418
|
-
backoffMs = 2000;
|
|
419
|
-
async handle(payload) {
|
|
420
|
-
const rows = await repositoryConnection`
|
|
421
|
-
SELECT id, url, secret
|
|
422
|
-
FROM webhook
|
|
423
|
-
WHERE id = ${payload.webhookId} AND active = TRUE
|
|
424
|
-
LIMIT 1
|
|
425
|
-
`;
|
|
426
|
-
const webhook = rows[0];
|
|
427
|
-
if (!webhook) {
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
const body = JSON.stringify({ event: payload.event, payload: payload.payload });
|
|
431
|
-
const signature = createHmac("sha256", webhook.secret).update(body).digest("hex");
|
|
432
|
-
assertSafeOutboundUrl(webhook.url, { allowHttp: appConfig.env !== "production" });
|
|
433
|
-
let responseStatus = null;
|
|
434
|
-
let errorMessage = null;
|
|
435
|
-
try {
|
|
436
|
-
const response = await safeFetch(webhook.url, {
|
|
437
|
-
method: "POST",
|
|
438
|
-
headers: {
|
|
439
|
-
"content-type": "application/json",
|
|
440
|
-
"x-workhub-signature": signature
|
|
441
|
-
},
|
|
442
|
-
body
|
|
443
|
-
}, { allowHttp: appConfig.env !== "production" });
|
|
444
|
-
responseStatus = response.status;
|
|
445
|
-
if (!response.ok) {
|
|
446
|
-
throw new Error(`Webhook delivery failed with status ${response.status}.`);
|
|
447
|
-
}
|
|
448
|
-
} catch (error) {
|
|
449
|
-
errorMessage = error instanceof Error ? error.message : String(error);
|
|
450
|
-
await repositoryConnection`
|
|
451
|
-
INSERT INTO webhook_delivery (webhook_id, event, payload, response_status, error)
|
|
452
|
-
VALUES (
|
|
453
|
-
${webhook.id},
|
|
454
|
-
${payload.event},
|
|
455
|
-
${JSON.stringify(payload.payload)}::jsonb,
|
|
456
|
-
${responseStatus},
|
|
457
|
-
${errorMessage}
|
|
458
|
-
)
|
|
459
|
-
`;
|
|
460
|
-
throw error instanceof Error ? error : new Error(errorMessage);
|
|
461
|
-
}
|
|
462
|
-
await repositoryConnection`
|
|
463
|
-
INSERT INTO webhook_delivery (webhook_id, event, payload, response_status)
|
|
464
|
-
VALUES (
|
|
465
|
-
${webhook.id},
|
|
466
|
-
${payload.event},
|
|
467
|
-
${JSON.stringify(payload.payload)}::jsonb,
|
|
468
|
-
${responseStatus}
|
|
469
|
-
)
|
|
470
|
-
`;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
var dispatchWebhookJob_default = DispatchWebhookJob;
|
|
474
|
-
|
|
475
|
-
// ../../src/core/jobs/invalidateCacheTagsJob.ts
|
|
476
|
-
class InvalidateCacheTagsJob extends Job {
|
|
477
|
-
cache;
|
|
478
|
-
constructor(cache) {
|
|
479
|
-
super();
|
|
480
|
-
this.cache = cache;
|
|
481
|
-
}
|
|
482
|
-
async handle(payload) {
|
|
483
|
-
await this.cache.tags(...payload.tags).flush();
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
var invalidateCacheTagsJob_default = InvalidateCacheTagsJob;
|
|
487
|
-
|
|
488
|
-
// ../../src/core/queue/jobRegistry.ts
|
|
489
|
-
class JobRegistry {
|
|
490
|
-
constructor() {}
|
|
491
|
-
factories = new Map;
|
|
492
|
-
instances = new WeakMap;
|
|
493
|
-
register(name, factory) {
|
|
494
|
-
this.factories.set(name, factory);
|
|
495
|
-
}
|
|
496
|
-
resolveName(job) {
|
|
497
|
-
return this.instances.get(job);
|
|
498
|
-
}
|
|
499
|
-
track(name, job) {
|
|
500
|
-
this.instances.set(job, name);
|
|
501
|
-
return job;
|
|
502
|
-
}
|
|
503
|
-
create(name) {
|
|
504
|
-
const factory = this.factories.get(name);
|
|
505
|
-
if (!factory) {
|
|
506
|
-
return;
|
|
507
|
-
}
|
|
508
|
-
return factory();
|
|
509
|
-
}
|
|
510
|
-
names() {
|
|
511
|
-
return [...this.factories.keys()];
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
var jobRegistry = new JobRegistry;
|
|
515
|
-
|
|
516
|
-
// ../../src/core/events/eventBus.ts
|
|
517
|
-
class EventBus {
|
|
518
|
-
constructor() {}
|
|
519
|
-
listeners = new Map;
|
|
520
|
-
listen(event, listener) {
|
|
521
|
-
const handlers = this.listeners.get(event) ?? new Set;
|
|
522
|
-
handlers.add(listener);
|
|
523
|
-
this.listeners.set(event, handlers);
|
|
524
|
-
return () => {
|
|
525
|
-
handlers.delete(listener);
|
|
526
|
-
if (handlers.size === 0) {
|
|
527
|
-
this.listeners.delete(event);
|
|
528
|
-
}
|
|
529
|
-
};
|
|
530
|
-
}
|
|
531
|
-
async dispatch(event, payload) {
|
|
532
|
-
const handlers = this.listeners.get(event);
|
|
533
|
-
if (!handlers || handlers.size === 0) {
|
|
534
|
-
return;
|
|
535
|
-
}
|
|
536
|
-
for (const handler of handlers) {
|
|
537
|
-
await handler(payload);
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
var eventBus = new EventBus;
|
|
542
|
-
|
|
543
|
-
// ../../src/core/events/index.ts
|
|
544
|
-
function modelEventName(tableName, action) {
|
|
545
|
-
return `${tableName}.${action}`;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
// ../../src/core/pagination/index.ts
|
|
549
|
-
function buildPaginationMeta(input) {
|
|
550
|
-
const lastPage = Math.max(1, Math.ceil(input.total / input.perPage));
|
|
551
|
-
return {
|
|
552
|
-
page: input.page,
|
|
553
|
-
per_page: input.perPage,
|
|
554
|
-
total: input.total,
|
|
555
|
-
last_page: lastPage
|
|
556
|
-
};
|
|
557
|
-
}
|
|
558
|
-
|
|
559
111
|
// ../../src/core/database/errors.ts
|
|
560
112
|
function isPostgresError(error) {
|
|
561
113
|
return typeof error === "object" && error !== null && (("errno" in error) || ("code" in error));
|
|
@@ -1084,6 +636,78 @@ function indexMorphToRelation(children, parentsByType, relation) {
|
|
|
1084
636
|
return result;
|
|
1085
637
|
}
|
|
1086
638
|
|
|
639
|
+
// ../../src/core/database/boundConnection.ts
|
|
640
|
+
var boundConnectionHolder = {
|
|
641
|
+
connection: null
|
|
642
|
+
};
|
|
643
|
+
function getBoundDatabaseConnection() {
|
|
644
|
+
return boundConnectionHolder.connection;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// ../../src/core/database/connectionContext.ts
|
|
648
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
649
|
+
var activeConnection = new AsyncLocalStorage;
|
|
650
|
+
function getActiveDatabaseConnection(fallback) {
|
|
651
|
+
return activeConnection.getStore() ?? fallback;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// ../../src/core/database/queryProxy.ts
|
|
655
|
+
var POOL_CONNECTION_METHODS = new Set(["begin", "close", "connect"]);
|
|
656
|
+
function createDatabaseQueryProxy(pool) {
|
|
657
|
+
function resolveDatabase() {
|
|
658
|
+
return getActiveDatabaseConnection(pool);
|
|
659
|
+
}
|
|
660
|
+
function resolveDatabaseForProperty(property) {
|
|
661
|
+
if (typeof property === "string" && POOL_CONNECTION_METHODS.has(property)) {
|
|
662
|
+
return pool;
|
|
663
|
+
}
|
|
664
|
+
return resolveDatabase();
|
|
665
|
+
}
|
|
666
|
+
return new Proxy(function database() {}, {
|
|
667
|
+
apply(_target, _thisArg, args) {
|
|
668
|
+
return resolveDatabase()(...args);
|
|
669
|
+
},
|
|
670
|
+
get(_target, property) {
|
|
671
|
+
const connection = resolveDatabaseForProperty(property);
|
|
672
|
+
const value = connection[property];
|
|
673
|
+
return typeof value === "function" ? value.bind(connection) : value;
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// ../../src/core/database/defaultConnection.ts
|
|
679
|
+
var defaultPool = {
|
|
680
|
+
connection: null
|
|
681
|
+
};
|
|
682
|
+
var defaultQuery = {
|
|
683
|
+
connection: null
|
|
684
|
+
};
|
|
685
|
+
function registerDefaultDatabasePool(connection) {
|
|
686
|
+
defaultPool.connection = connection;
|
|
687
|
+
defaultQuery.connection = createDatabaseQueryProxy(connection);
|
|
688
|
+
}
|
|
689
|
+
function getDefaultDatabaseQuery() {
|
|
690
|
+
if (!defaultQuery.connection) {
|
|
691
|
+
throw new Error("Default database query handle is not registered. Call registerDefaultDatabasePool() during app bootstrap.");
|
|
692
|
+
}
|
|
693
|
+
return defaultQuery.connection;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// ../../src/core/database/repositoryConnection.ts
|
|
697
|
+
function resolveRepositoryConnection() {
|
|
698
|
+
return getBoundDatabaseConnection() ?? getDefaultDatabaseQuery();
|
|
699
|
+
}
|
|
700
|
+
var repositoryConnection = new Proxy(function repositoryConnection2() {}, {
|
|
701
|
+
apply(_target, _thisArg, args) {
|
|
702
|
+
return resolveRepositoryConnection()(...args);
|
|
703
|
+
},
|
|
704
|
+
get(_target, property) {
|
|
705
|
+
const connection = resolveRepositoryConnection();
|
|
706
|
+
const value = connection[property];
|
|
707
|
+
return typeof value === "function" ? value.bind(connection) : value;
|
|
708
|
+
}
|
|
709
|
+
});
|
|
710
|
+
|
|
1087
711
|
// ../../src/core/database/whereBuilder.ts
|
|
1088
712
|
class WhereBuilder {
|
|
1089
713
|
nodes = [];
|
|
@@ -2272,6 +1896,38 @@ class FailedJobService {
|
|
|
2272
1896
|
}
|
|
2273
1897
|
var failedJobService_default = FailedJobService;
|
|
2274
1898
|
|
|
1899
|
+
// ../../src/core/queue/jobRegistry.ts
|
|
1900
|
+
class JobRegistry {
|
|
1901
|
+
constructor() {}
|
|
1902
|
+
factories = new Map;
|
|
1903
|
+
instances = new WeakMap;
|
|
1904
|
+
register(name, factory) {
|
|
1905
|
+
this.factories.set(name, factory);
|
|
1906
|
+
}
|
|
1907
|
+
resolveName(job) {
|
|
1908
|
+
return this.instances.get(job);
|
|
1909
|
+
}
|
|
1910
|
+
track(name, job) {
|
|
1911
|
+
this.instances.set(job, name);
|
|
1912
|
+
return job;
|
|
1913
|
+
}
|
|
1914
|
+
create(name) {
|
|
1915
|
+
const factory = this.factories.get(name);
|
|
1916
|
+
if (!factory) {
|
|
1917
|
+
return;
|
|
1918
|
+
}
|
|
1919
|
+
return factory();
|
|
1920
|
+
}
|
|
1921
|
+
names() {
|
|
1922
|
+
return [...this.factories.keys()];
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
var jobRegistry = new JobRegistry;
|
|
1926
|
+
|
|
1927
|
+
// ../../src/bootstrap/config.ts
|
|
1928
|
+
var CORE_TOKEN_SERVICE_TOKEN = "core.tokenService";
|
|
1929
|
+
var DEFAULT_QUEUE_DRIVER = "sync";
|
|
1930
|
+
|
|
2275
1931
|
// ../../src/config/queue.ts
|
|
2276
1932
|
var queueConfig = {
|
|
2277
1933
|
driver: process.env.QUEUE_DRIVER === "redis" || process.env.QUEUE_DRIVER === "async" || process.env.QUEUE_DRIVER === "sync" ? process.env.QUEUE_DRIVER : DEFAULT_QUEUE_DRIVER,
|
|
@@ -2484,19 +2140,363 @@ function createQueueWorker(redisUrl, failedJobs = createFailedJobService()) {
|
|
|
2484
2140
|
return new QueueWorker(redisUrl, failedJobs);
|
|
2485
2141
|
}
|
|
2486
2142
|
|
|
2487
|
-
// ../../src/core/
|
|
2488
|
-
|
|
2143
|
+
// ../../src/core/jobs/dispatchWebhookJob.ts
|
|
2144
|
+
import { createHmac } from "crypto";
|
|
2145
|
+
|
|
2146
|
+
// ../../src/config/app.ts
|
|
2147
|
+
var appConfig = {
|
|
2148
|
+
name: "WorkHub",
|
|
2149
|
+
env: process.env.APP_ENV ?? "local",
|
|
2150
|
+
debug: (process.env.APP_DEBUG ?? "true") !== "false",
|
|
2151
|
+
url: process.env.APP_URL ?? "http://localhost:3000",
|
|
2152
|
+
apiPrefix: process.env.API_PREFIX ?? "/api/v1"
|
|
2153
|
+
};
|
|
2154
|
+
|
|
2155
|
+
// ../../src/core/queue/index.ts
|
|
2156
|
+
class Job {
|
|
2157
|
+
maxAttempts;
|
|
2158
|
+
backoffMs;
|
|
2159
|
+
priority;
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
// ../../src/core/security/safeUrl.ts
|
|
2163
|
+
import { lookup as dnsLookupImpl } from "dns/promises";
|
|
2164
|
+
var dnsLookup = dnsLookupImpl;
|
|
2165
|
+
var BLOCKED_HOSTNAMES = new Set([
|
|
2166
|
+
"localhost",
|
|
2167
|
+
"127.0.0.1",
|
|
2168
|
+
"0.0.0.0",
|
|
2169
|
+
"::1",
|
|
2170
|
+
"metadata.google.internal"
|
|
2171
|
+
]);
|
|
2172
|
+
function isPrivateIpv4(hostname) {
|
|
2173
|
+
const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
|
|
2174
|
+
if (!match) {
|
|
2175
|
+
return false;
|
|
2176
|
+
}
|
|
2177
|
+
const octets = match.slice(1, 5).map((part) => Number.parseInt(part, 10));
|
|
2178
|
+
if (octets.some((octet) => octet < 0 || octet > 255)) {
|
|
2179
|
+
return true;
|
|
2180
|
+
}
|
|
2181
|
+
const [a = 0, b = 0] = octets;
|
|
2182
|
+
if (a === 10) {
|
|
2183
|
+
return true;
|
|
2184
|
+
}
|
|
2185
|
+
if (a === 127) {
|
|
2186
|
+
return true;
|
|
2187
|
+
}
|
|
2188
|
+
if (a === 0) {
|
|
2189
|
+
return true;
|
|
2190
|
+
}
|
|
2191
|
+
if (a === 169 && b === 254) {
|
|
2192
|
+
return true;
|
|
2193
|
+
}
|
|
2194
|
+
if (a === 172 && b >= 16 && b <= 31) {
|
|
2195
|
+
return true;
|
|
2196
|
+
}
|
|
2197
|
+
if (a === 192 && b === 168) {
|
|
2198
|
+
return true;
|
|
2199
|
+
}
|
|
2200
|
+
return false;
|
|
2201
|
+
}
|
|
2202
|
+
function isBlockedHostname(hostname) {
|
|
2203
|
+
const normalized = hostname.trim().toLowerCase();
|
|
2204
|
+
if (normalized.length === 0) {
|
|
2205
|
+
return true;
|
|
2206
|
+
}
|
|
2207
|
+
if (BLOCKED_HOSTNAMES.has(normalized)) {
|
|
2208
|
+
return true;
|
|
2209
|
+
}
|
|
2210
|
+
if (normalized.endsWith(".local") || normalized.endsWith(".internal")) {
|
|
2211
|
+
return true;
|
|
2212
|
+
}
|
|
2213
|
+
if (normalized.includes(":")) {
|
|
2214
|
+
return true;
|
|
2215
|
+
}
|
|
2216
|
+
return isPrivateIpv4(normalized);
|
|
2217
|
+
}
|
|
2218
|
+
function assertSafeOutboundUrl(rawUrl, options = {}) {
|
|
2219
|
+
let parsed;
|
|
2220
|
+
try {
|
|
2221
|
+
parsed = new URL(rawUrl);
|
|
2222
|
+
} catch {
|
|
2223
|
+
throw new BadRequestError("Webhook URL is invalid.");
|
|
2224
|
+
}
|
|
2225
|
+
if (parsed.protocol !== "https:" && !(options.allowHttp && parsed.protocol === "http:")) {
|
|
2226
|
+
throw new BadRequestError("Webhook URL must use HTTPS.");
|
|
2227
|
+
}
|
|
2228
|
+
if (parsed.username || parsed.password) {
|
|
2229
|
+
throw new BadRequestError("Webhook URL must not include credentials.");
|
|
2230
|
+
}
|
|
2231
|
+
if (isBlockedHostname(parsed.hostname)) {
|
|
2232
|
+
throw new BadRequestError("Webhook URL targets a blocked host.");
|
|
2233
|
+
}
|
|
2234
|
+
return parsed;
|
|
2235
|
+
}
|
|
2236
|
+
function isBlockedIpAddress(address) {
|
|
2237
|
+
return isBlockedHostname(address.trim().toLowerCase());
|
|
2238
|
+
}
|
|
2239
|
+
async function assertSafeOutboundUrlResolved(rawUrl, options = {}) {
|
|
2240
|
+
const parsed = assertSafeOutboundUrl(rawUrl, options);
|
|
2241
|
+
if (options.resolveDns === false) {
|
|
2242
|
+
return parsed;
|
|
2243
|
+
}
|
|
2244
|
+
const hostname = parsed.hostname.trim().toLowerCase();
|
|
2245
|
+
const results = await dnsLookup(hostname, { all: true, verbatim: true });
|
|
2246
|
+
if (results.some((result) => isBlockedIpAddress(result.address))) {
|
|
2247
|
+
throw new BadRequestError("Webhook URL targets a blocked host.");
|
|
2248
|
+
}
|
|
2249
|
+
return parsed;
|
|
2250
|
+
}
|
|
2251
|
+
function setDnsLookupForTests(lookupFn) {
|
|
2252
|
+
dnsLookup = lookupFn;
|
|
2253
|
+
}
|
|
2254
|
+
function resetDnsLookupForTests() {
|
|
2255
|
+
dnsLookup = dnsLookupImpl;
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
// ../../src/core/security/safeFetch.ts
|
|
2259
|
+
var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
|
|
2260
|
+
async function safeFetch(input, init = {}, options = {}) {
|
|
2261
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
|
|
2262
|
+
const maxRedirects = options.maxRedirects ?? 0;
|
|
2263
|
+
const resolveDns = options.resolveDns ?? appConfig.env === "production";
|
|
2264
|
+
const urlOptions = { allowHttp: options.allowHttp, resolveDns };
|
|
2265
|
+
const controller = new AbortController;
|
|
2266
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
2267
|
+
try {
|
|
2268
|
+
let currentUrl = (await assertSafeOutboundUrlResolved(input, urlOptions)).toString();
|
|
2269
|
+
let redirectCount = 0;
|
|
2270
|
+
while (true) {
|
|
2271
|
+
const response = await fetch(currentUrl, {
|
|
2272
|
+
...init,
|
|
2273
|
+
signal: controller.signal,
|
|
2274
|
+
redirect: "manual"
|
|
2275
|
+
});
|
|
2276
|
+
if (response.status >= 300 && response.status < 400) {
|
|
2277
|
+
const location = response.headers.get("location");
|
|
2278
|
+
if (!location || redirectCount >= maxRedirects) {
|
|
2279
|
+
return response;
|
|
2280
|
+
}
|
|
2281
|
+
currentUrl = (await assertSafeOutboundUrlResolved(new URL(location, currentUrl).toString(), urlOptions)).toString();
|
|
2282
|
+
redirectCount += 1;
|
|
2283
|
+
continue;
|
|
2284
|
+
}
|
|
2285
|
+
return response;
|
|
2286
|
+
}
|
|
2287
|
+
} finally {
|
|
2288
|
+
clearTimeout(timeout);
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
// ../../src/core/jobs/dispatchWebhookJob.ts
|
|
2293
|
+
class DispatchWebhookJob extends Job {
|
|
2294
|
+
maxAttempts = 3;
|
|
2295
|
+
backoffMs = 2000;
|
|
2296
|
+
async handle(payload) {
|
|
2297
|
+
const rows = await repositoryConnection`
|
|
2298
|
+
SELECT id, url, secret
|
|
2299
|
+
FROM webhook
|
|
2300
|
+
WHERE id = ${payload.webhookId} AND active = TRUE
|
|
2301
|
+
LIMIT 1
|
|
2302
|
+
`;
|
|
2303
|
+
const webhook = rows[0];
|
|
2304
|
+
if (!webhook) {
|
|
2305
|
+
return;
|
|
2306
|
+
}
|
|
2307
|
+
const body = JSON.stringify({ event: payload.event, payload: payload.payload });
|
|
2308
|
+
const signature = createHmac("sha256", webhook.secret).update(body).digest("hex");
|
|
2309
|
+
assertSafeOutboundUrl(webhook.url, { allowHttp: appConfig.env !== "production" });
|
|
2310
|
+
let responseStatus = null;
|
|
2311
|
+
let errorMessage = null;
|
|
2312
|
+
try {
|
|
2313
|
+
const response = await safeFetch(webhook.url, {
|
|
2314
|
+
method: "POST",
|
|
2315
|
+
headers: {
|
|
2316
|
+
"content-type": "application/json",
|
|
2317
|
+
"x-workhub-signature": signature
|
|
2318
|
+
},
|
|
2319
|
+
body
|
|
2320
|
+
}, { allowHttp: appConfig.env !== "production" });
|
|
2321
|
+
responseStatus = response.status;
|
|
2322
|
+
if (!response.ok) {
|
|
2323
|
+
throw new Error(`Webhook delivery failed with status ${response.status}.`);
|
|
2324
|
+
}
|
|
2325
|
+
} catch (error) {
|
|
2326
|
+
errorMessage = error instanceof Error ? error.message : String(error);
|
|
2327
|
+
await repositoryConnection`
|
|
2328
|
+
INSERT INTO webhook_delivery (webhook_id, event, payload, response_status, error)
|
|
2329
|
+
VALUES (
|
|
2330
|
+
${webhook.id},
|
|
2331
|
+
${payload.event},
|
|
2332
|
+
${JSON.stringify(payload.payload)}::jsonb,
|
|
2333
|
+
${responseStatus},
|
|
2334
|
+
${errorMessage}
|
|
2335
|
+
)
|
|
2336
|
+
`;
|
|
2337
|
+
throw error instanceof Error ? error : new Error(errorMessage);
|
|
2338
|
+
}
|
|
2339
|
+
await repositoryConnection`
|
|
2340
|
+
INSERT INTO webhook_delivery (webhook_id, event, payload, response_status)
|
|
2341
|
+
VALUES (
|
|
2342
|
+
${webhook.id},
|
|
2343
|
+
${payload.event},
|
|
2344
|
+
${JSON.stringify(payload.payload)}::jsonb,
|
|
2345
|
+
${responseStatus}
|
|
2346
|
+
)
|
|
2347
|
+
`;
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
var dispatchWebhookJob_default = DispatchWebhookJob;
|
|
2351
|
+
|
|
2352
|
+
// ../../src/core/jobs/invalidateCacheTagsJob.ts
|
|
2353
|
+
class InvalidateCacheTagsJob extends Job {
|
|
2354
|
+
cache;
|
|
2355
|
+
constructor(cache) {
|
|
2356
|
+
super();
|
|
2357
|
+
this.cache = cache;
|
|
2358
|
+
}
|
|
2359
|
+
async handle(payload) {
|
|
2360
|
+
await this.cache.tags(...payload.tags).flush();
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
var invalidateCacheTagsJob_default = InvalidateCacheTagsJob;
|
|
2364
|
+
|
|
2365
|
+
// ../../src/core/logging/logger.ts
|
|
2366
|
+
class Logger {
|
|
2367
|
+
channel;
|
|
2368
|
+
constructor(channel = "app") {
|
|
2369
|
+
this.channel = channel;
|
|
2370
|
+
}
|
|
2371
|
+
write(level, message, context = {}) {
|
|
2372
|
+
const entry = {
|
|
2373
|
+
level,
|
|
2374
|
+
channel: this.channel,
|
|
2375
|
+
message,
|
|
2376
|
+
timestamp: new Date().toISOString(),
|
|
2377
|
+
...context
|
|
2378
|
+
};
|
|
2379
|
+
const line = JSON.stringify(entry);
|
|
2380
|
+
if (level === "error") {
|
|
2381
|
+
console.error(line);
|
|
2382
|
+
return;
|
|
2383
|
+
}
|
|
2384
|
+
console.log(line);
|
|
2385
|
+
}
|
|
2386
|
+
debug(message, context) {
|
|
2387
|
+
this.write("debug", message, context);
|
|
2388
|
+
}
|
|
2389
|
+
info(message, context) {
|
|
2390
|
+
this.write("info", message, context);
|
|
2391
|
+
}
|
|
2392
|
+
warn(message, context) {
|
|
2393
|
+
this.write("warn", message, context);
|
|
2394
|
+
}
|
|
2395
|
+
error(message, context) {
|
|
2396
|
+
this.write("error", message, context);
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
var appLogger = new Logger("app");
|
|
2400
|
+
|
|
2401
|
+
// ../../src/bootstrap/contracts.ts
|
|
2402
|
+
class ServiceContainer {
|
|
2403
|
+
services = new Map;
|
|
2404
|
+
singletonFactories = new Map;
|
|
2405
|
+
bindings = new Map;
|
|
2406
|
+
set(key, value) {
|
|
2407
|
+
this.singletonFactories.delete(key);
|
|
2408
|
+
this.bindings.delete(key);
|
|
2409
|
+
this.services.set(key, value);
|
|
2410
|
+
return value;
|
|
2411
|
+
}
|
|
2412
|
+
singleton(key, factory) {
|
|
2413
|
+
this.bindings.delete(key);
|
|
2414
|
+
this.services.delete(key);
|
|
2415
|
+
this.singletonFactories.set(key, factory);
|
|
2416
|
+
}
|
|
2417
|
+
bind(key, factory) {
|
|
2418
|
+
this.singletonFactories.delete(key);
|
|
2419
|
+
this.services.delete(key);
|
|
2420
|
+
this.bindings.set(key, factory);
|
|
2421
|
+
}
|
|
2422
|
+
get(key) {
|
|
2423
|
+
if (this.services.has(key)) {
|
|
2424
|
+
return this.services.get(key);
|
|
2425
|
+
}
|
|
2426
|
+
const singletonFactory = this.singletonFactories.get(key);
|
|
2427
|
+
if (singletonFactory) {
|
|
2428
|
+
const value = singletonFactory(this);
|
|
2429
|
+
this.services.set(key, value);
|
|
2430
|
+
return value;
|
|
2431
|
+
}
|
|
2432
|
+
const binding = this.bindings.get(key);
|
|
2433
|
+
if (binding) {
|
|
2434
|
+
return binding(this);
|
|
2435
|
+
}
|
|
2436
|
+
throw new Error(`Service "${key}" is not registered.`);
|
|
2437
|
+
}
|
|
2438
|
+
resolve(key) {
|
|
2439
|
+
return this.get(key);
|
|
2440
|
+
}
|
|
2441
|
+
has(key) {
|
|
2442
|
+
return this.services.has(key) || this.singletonFactories.has(key) || this.bindings.has(key);
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
class ConfigStore {
|
|
2447
|
+
values = new Map;
|
|
2448
|
+
set(key, value) {
|
|
2449
|
+
this.values.set(key, value);
|
|
2450
|
+
return value;
|
|
2451
|
+
}
|
|
2452
|
+
get(key) {
|
|
2453
|
+
return this.values.get(key);
|
|
2454
|
+
}
|
|
2455
|
+
require(key) {
|
|
2456
|
+
if (!this.values.has(key)) {
|
|
2457
|
+
throw new Error(`Config key "${key}" is not defined.`);
|
|
2458
|
+
}
|
|
2459
|
+
return this.values.get(key);
|
|
2460
|
+
}
|
|
2461
|
+
has(key) {
|
|
2462
|
+
return this.values.has(key);
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
function getRequiredDependency(dependencies, key) {
|
|
2466
|
+
const dependency = dependencies[key];
|
|
2467
|
+
if (dependency === undefined) {
|
|
2468
|
+
throw new Error(`Required dependency "${key}" is not registered.`);
|
|
2469
|
+
}
|
|
2470
|
+
return dependency;
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
// ../../src/bootstrap/applicationRegistry.ts
|
|
2474
|
+
var activeContext;
|
|
2475
|
+
function requireActiveApplicationContext() {
|
|
2476
|
+
if (!activeContext) {
|
|
2477
|
+
throw new Error("The application context has not been bootstrapped.");
|
|
2478
|
+
}
|
|
2479
|
+
return activeContext;
|
|
2480
|
+
}
|
|
2481
|
+
function resolveApplicationCache() {
|
|
2482
|
+
return getRequiredDependency(requireActiveApplicationContext().dependencies, "cache");
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2485
|
+
// ../../src/bootstrap/queue/defaultJobs.ts
|
|
2489
2486
|
function registerDefaultJobs() {
|
|
2490
2487
|
jobRegistry.register("cache.invalidate-tags", () => {
|
|
2491
2488
|
return new invalidateCacheTagsJob_default(resolveApplicationCache());
|
|
2492
2489
|
});
|
|
2493
2490
|
jobRegistry.register("webhook.dispatch", () => new dispatchWebhookJob_default);
|
|
2494
2491
|
}
|
|
2495
|
-
|
|
2492
|
+
|
|
2493
|
+
// ../../src/core/queue/createAppQueue.ts
|
|
2494
|
+
var FAILED_JOB_SERVICE_TOKEN = "core.failedJobs";
|
|
2495
|
+
function createAppQueue(driver, redisUrl, failedJobs = createFailedJobService(), registerJobs) {
|
|
2496
2496
|
return createProductionQueue(driver, {
|
|
2497
2497
|
redisUrl,
|
|
2498
2498
|
failedJobs,
|
|
2499
|
-
registerJobs
|
|
2499
|
+
registerJobs
|
|
2500
2500
|
});
|
|
2501
2501
|
}
|
|
2502
2502
|
export {
|