@getstrata/core 0.5.63 → 0.5.64
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/CHANGELOG.md +5 -0
- package/README.md +1 -1
- package/dist/core/jobs/dispatchWebhookJob.d.ts +1 -16
- package/dist/core/queue/redisQueue.d.ts +3 -3
- package/dist/core/runtime/appKeyPrefix.d.ts +3 -0
- package/dist/entries/cache/createCacheStore.js +24 -10
- package/dist/entries/http/loginThrottleMiddleware.js +9 -1
- package/dist/entries/http/memoryThrottleMiddleware.js +9 -1
- package/dist/entries/http/scimThrottleMiddleware.js +9 -1
- package/dist/entries/http/throttleMiddleware.js +9 -1
- package/dist/entries/jobs/dispatchWebhookJob.js +9 -265
- package/dist/entries/queue/createAppQueue.js +25 -6
- package/dist/entries/queue/publicQueue.js +25 -6
- package/dist/entries/queue/queueMetrics.js +25 -6
- package/dist/entries/queue/redisQueue.js +23 -6
- package/dist/index.js +43 -20
- package/dist/modules/webhook/dispatchWebhookJob.d.ts +16 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# @getstrata/core changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.64
|
|
4
|
+
|
|
5
|
+
- `APP_KEY_PREFIX` (default `workhub`) namespaces Redis cache, queue, and throttle keys so sibling apps do not share WorkHub’s keyspace.
|
|
6
|
+
- `DispatchWebhookJob` implementation lives in the WorkHub webhook module. `@getstrata/core/jobs/dispatchWebhookJob` remains a compatibility re-export.
|
|
7
|
+
|
|
3
8
|
## 0.5.63
|
|
4
9
|
|
|
5
10
|
- Flash cookies honor `FLASH_COOKIE_NAME` (default `workhub_flash`) so sibling apps do not inherit WorkHub’s cookie name.
|
package/README.md
CHANGED
|
@@ -82,7 +82,7 @@ import type { Migration } from "@getstrata/core/database/migrations/types";
|
|
|
82
82
|
Package name: **`@getstrata/core`** (npm org [`@getstrata`](https://www.npmjs.com/org/getstrata)).
|
|
83
83
|
|
|
84
84
|
1. Add `NPM_TOKEN` to GitHub repository secrets.
|
|
85
|
-
2. Tag a release: `git tag v0.5.
|
|
85
|
+
2. Tag a release: `git tag v0.5.66 && git push origin v0.5.66`
|
|
86
86
|
3. [Release workflow](../../.github/workflows/release.yml) builds and runs `npm publish --access public`.
|
|
87
87
|
|
|
88
88
|
Previously published as `@eyk-workhub/framework@0.1.0`, deprecated in favor of this package.
|
|
@@ -1,16 +1 @@
|
|
|
1
|
-
|
|
2
|
-
interface DispatchWebhookPayload {
|
|
3
|
-
webhookId: number;
|
|
4
|
-
tenantId: number;
|
|
5
|
-
event: string;
|
|
6
|
-
payload: Record<string, unknown>;
|
|
7
|
-
}
|
|
8
|
-
declare class DispatchWebhookJob extends Job<DispatchWebhookPayload> {
|
|
9
|
-
readonly maxAttempts = 3;
|
|
10
|
-
readonly backoffMs = 2000;
|
|
11
|
-
handle(payload: DispatchWebhookPayload): Promise<void>;
|
|
12
|
-
private deliver;
|
|
13
|
-
}
|
|
14
|
-
export { DispatchWebhookJob };
|
|
15
|
-
export default DispatchWebhookJob;
|
|
16
|
-
export type { DispatchWebhookPayload };
|
|
1
|
+
export { DispatchWebhookJob, type DispatchWebhookPayload, default, } from "../../modules/webhook/dispatchWebhookJob";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type FailedJobService from "./failedJobService";
|
|
2
2
|
import type { Job, Queue, QueuePriority } from "./index";
|
|
3
3
|
import { type QueueJobEnvelope } from "./jobRunner";
|
|
4
|
-
declare const QUEUE_LIST_KEY
|
|
5
|
-
declare const QUEUE_HIGH_KEY
|
|
6
|
-
declare const QUEUE_LOW_KEY
|
|
4
|
+
declare const QUEUE_LIST_KEY: string;
|
|
5
|
+
declare const QUEUE_HIGH_KEY: string;
|
|
6
|
+
declare const QUEUE_LOW_KEY: string;
|
|
7
7
|
declare function queueKeyForPriority(priority?: QueuePriority): string;
|
|
8
8
|
declare function parseQueueJobEnvelope(rawPayload: string): QueueJobEnvelope | null;
|
|
9
9
|
declare class RedisQueue implements Queue {
|
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// ../../src/core/cache/redisCacheStore.ts
|
|
3
3
|
var {RedisClient } = globalThis.Bun;
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
|
|
5
|
+
// ../../src/core/runtime/appKeyPrefix.ts
|
|
6
|
+
function appKeyPrefix() {
|
|
7
|
+
return process.env.APP_KEY_PREFIX?.trim() || "workhub";
|
|
8
|
+
}
|
|
9
|
+
function namespacedRedisKey(kind) {
|
|
10
|
+
return `${appKeyPrefix()}:${kind}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// ../../src/core/cache/redisCacheStore.ts
|
|
14
|
+
function cacheKeyPrefix() {
|
|
15
|
+
return namespacedRedisKey("cache:");
|
|
16
|
+
}
|
|
17
|
+
function cacheTagPrefix() {
|
|
18
|
+
return namespacedRedisKey("cache:tag:");
|
|
19
|
+
}
|
|
6
20
|
|
|
7
21
|
class RedisCacheStore {
|
|
8
22
|
ttlMs;
|
|
@@ -89,10 +103,10 @@ class RedisCacheStore {
|
|
|
89
103
|
return deleted > 0;
|
|
90
104
|
}
|
|
91
105
|
async invalidateByPrefix(prefix) {
|
|
92
|
-
const keys = await this.client.keys(`${
|
|
106
|
+
const keys = await this.client.keys(`${cacheKeyPrefix()}*`);
|
|
93
107
|
let removed = 0;
|
|
94
108
|
for (const storageKey of keys) {
|
|
95
|
-
const key = storageKey.slice(
|
|
109
|
+
const key = storageKey.slice(cacheKeyPrefix().length);
|
|
96
110
|
if (key === prefix || key.startsWith(`${prefix}?`)) {
|
|
97
111
|
if (await this.invalidate(key)) {
|
|
98
112
|
removed += 1;
|
|
@@ -102,11 +116,11 @@ class RedisCacheStore {
|
|
|
102
116
|
return removed;
|
|
103
117
|
}
|
|
104
118
|
async clear() {
|
|
105
|
-
const keys = await this.client.keys(`${
|
|
119
|
+
const keys = await this.client.keys(`${cacheKeyPrefix()}*`);
|
|
106
120
|
if (keys.length > 0) {
|
|
107
121
|
await this.client.del(...keys);
|
|
108
122
|
}
|
|
109
|
-
const tagKeys = await this.client.keys(`${
|
|
123
|
+
const tagKeys = await this.client.keys(`${cacheTagPrefix()}*`);
|
|
110
124
|
if (tagKeys.length > 0) {
|
|
111
125
|
await this.client.del(...tagKeys);
|
|
112
126
|
}
|
|
@@ -114,14 +128,14 @@ class RedisCacheStore {
|
|
|
114
128
|
this.keyTags.clear();
|
|
115
129
|
}
|
|
116
130
|
async size() {
|
|
117
|
-
const keys = await this.client.keys(`${
|
|
131
|
+
const keys = await this.client.keys(`${cacheKeyPrefix()}*`);
|
|
118
132
|
return keys.length;
|
|
119
133
|
}
|
|
120
134
|
storageKey(key) {
|
|
121
|
-
return `${
|
|
135
|
+
return `${cacheKeyPrefix()}${key}`;
|
|
122
136
|
}
|
|
123
137
|
tagKey(tag) {
|
|
124
|
-
return `${
|
|
138
|
+
return `${cacheTagPrefix()}${tag}`;
|
|
125
139
|
}
|
|
126
140
|
async detachKeyFromTags(key) {
|
|
127
141
|
const tags = this.keyTags.get(key);
|
|
@@ -134,7 +148,7 @@ class RedisCacheStore {
|
|
|
134
148
|
this.keyTags.delete(key);
|
|
135
149
|
}
|
|
136
150
|
async enforceMaxEntries() {
|
|
137
|
-
const keys = await this.client.keys(`${
|
|
151
|
+
const keys = await this.client.keys(`${cacheKeyPrefix()}*`);
|
|
138
152
|
if (keys.length <= this.maxEntries) {
|
|
139
153
|
return;
|
|
140
154
|
}
|
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
// ../../src/core/http/loginThrottleMiddleware.ts
|
|
3
3
|
var {RedisClient } = globalThis.Bun;
|
|
4
4
|
|
|
5
|
+
// ../../src/core/runtime/appKeyPrefix.ts
|
|
6
|
+
function appKeyPrefix() {
|
|
7
|
+
return process.env.APP_KEY_PREFIX?.trim() || "workhub";
|
|
8
|
+
}
|
|
9
|
+
function namespacedRedisKey(kind) {
|
|
10
|
+
return `${appKeyPrefix()}:${kind}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
5
13
|
// ../../src/core/http/clientIp.ts
|
|
6
14
|
function trustForwardedFor(env = process.env) {
|
|
7
15
|
return (env.TRUST_FORWARDED_FOR ?? "false") === "true";
|
|
@@ -37,7 +45,7 @@ async function resolveLoginEmail(request) {
|
|
|
37
45
|
}
|
|
38
46
|
function createLoginThrottleMiddleware(options) {
|
|
39
47
|
const client = new RedisClient(options.redisUrl);
|
|
40
|
-
const prefix = options.keyPrefix ?? "
|
|
48
|
+
const prefix = options.keyPrefix ?? namespacedRedisKey("login-throttle:");
|
|
41
49
|
return async (request, next) => {
|
|
42
50
|
const identity = resolveLoginIdentity(request);
|
|
43
51
|
const email = await resolveLoginEmail(request);
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
// ../../src/core/runtime/appKeyPrefix.ts
|
|
3
|
+
function appKeyPrefix() {
|
|
4
|
+
return process.env.APP_KEY_PREFIX?.trim() || "workhub";
|
|
5
|
+
}
|
|
6
|
+
function namespacedRedisKey(kind) {
|
|
7
|
+
return `${appKeyPrefix()}:${kind}`;
|
|
8
|
+
}
|
|
9
|
+
|
|
2
10
|
// ../../src/core/http/clientIp.ts
|
|
3
11
|
function trustForwardedFor(env = process.env) {
|
|
4
12
|
return (env.TRUST_FORWARDED_FOR ?? "false") === "true";
|
|
@@ -17,7 +25,7 @@ function readClientIp(request, env = process.env) {
|
|
|
17
25
|
// ../../src/core/http/memoryThrottleMiddleware.ts
|
|
18
26
|
var throttleBucketRegistries = new Set;
|
|
19
27
|
function createMemoryThrottleMiddleware(options) {
|
|
20
|
-
const prefix = options.keyPrefix ?? "
|
|
28
|
+
const prefix = options.keyPrefix ?? namespacedRedisKey("memory-throttle:");
|
|
21
29
|
const buckets = new Map;
|
|
22
30
|
throttleBucketRegistries.add(buckets);
|
|
23
31
|
return async (request, next) => {
|
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
// ../../src/core/http/scimThrottleMiddleware.ts
|
|
3
3
|
var {RedisClient } = globalThis.Bun;
|
|
4
4
|
|
|
5
|
+
// ../../src/core/runtime/appKeyPrefix.ts
|
|
6
|
+
function appKeyPrefix() {
|
|
7
|
+
return process.env.APP_KEY_PREFIX?.trim() || "workhub";
|
|
8
|
+
}
|
|
9
|
+
function namespacedRedisKey(kind) {
|
|
10
|
+
return `${appKeyPrefix()}:${kind}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
5
13
|
// ../../src/core/http/clientIp.ts
|
|
6
14
|
function trustForwardedFor(env = process.env) {
|
|
7
15
|
return (env.TRUST_FORWARDED_FOR ?? "false") === "true";
|
|
@@ -39,7 +47,7 @@ function createScimThrottleMiddleware(options) {
|
|
|
39
47
|
const client = options.redisUrl ? new RedisClient(options.redisUrl) : null;
|
|
40
48
|
return async (request, next) => {
|
|
41
49
|
const identity = resolveScimIdentity(request);
|
|
42
|
-
const key =
|
|
50
|
+
const key = `${namespacedRedisKey("scim-throttle:")}${identity}`;
|
|
43
51
|
if (client) {
|
|
44
52
|
const attempts = Number(await client.incr(key));
|
|
45
53
|
if (attempts === 1) {
|
|
@@ -4,6 +4,14 @@ import { currentAuthUser } from "@getstrata/core/auth/authContext";
|
|
|
4
4
|
import { currentTenant, rateLimitMultiplierForPlan } from "@getstrata/core/tenant/tenantContext";
|
|
5
5
|
var {RedisClient } = globalThis.Bun;
|
|
6
6
|
|
|
7
|
+
// ../../src/core/runtime/appKeyPrefix.ts
|
|
8
|
+
function appKeyPrefix() {
|
|
9
|
+
return process.env.APP_KEY_PREFIX?.trim() || "workhub";
|
|
10
|
+
}
|
|
11
|
+
function namespacedRedisKey(kind) {
|
|
12
|
+
return `${appKeyPrefix()}:${kind}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
7
15
|
// ../../src/core/http/clientIp.ts
|
|
8
16
|
function trustForwardedFor(env = process.env) {
|
|
9
17
|
return (env.TRUST_FORWARDED_FOR ?? "false") === "true";
|
|
@@ -32,7 +40,7 @@ function resolveThrottleIdentity(request) {
|
|
|
32
40
|
}
|
|
33
41
|
function createThrottleMiddleware(options) {
|
|
34
42
|
const client = new RedisClient(options.redisUrl);
|
|
35
|
-
const prefix = options.keyPrefix ?? "
|
|
43
|
+
const prefix = options.keyPrefix ?? namespacedRedisKey("throttle:");
|
|
36
44
|
return async (request, next) => {
|
|
37
45
|
const identity = resolveThrottleIdentity(request);
|
|
38
46
|
const path = new URL(request.url).pathname;
|
|
@@ -1,269 +1,13 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
// ../../src/
|
|
2
|
+
// ../../src/modules/webhook/dispatchWebhookJob.ts
|
|
3
3
|
import { createHmac } from "crypto";
|
|
4
|
-
import { repositoryConnection as db2 } from "@getstrata/core/database/repositoryConnection";
|
|
5
|
-
|
|
6
|
-
// ../../src/core/queue/index.ts
|
|
7
|
-
class Job {
|
|
8
|
-
maxAttempts;
|
|
9
|
-
backoffMs;
|
|
10
|
-
priority;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
class SyncQueue {
|
|
14
|
-
async dispatch(job, payload) {
|
|
15
|
-
await job.handle(payload);
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
class AsyncQueue {
|
|
20
|
-
async dispatch(job, payload) {
|
|
21
|
-
setTimeout(() => {
|
|
22
|
-
job.handle(payload).catch((error) => {
|
|
23
|
-
console.error("[AsyncQueue] Job failed:", error);
|
|
24
|
-
});
|
|
25
|
-
}, 0);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
function createQueue(driver) {
|
|
29
|
-
return driver === "async" ? new AsyncQueue : new SyncQueue;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// ../../src/config/app.ts
|
|
33
|
-
var appConfig = {
|
|
34
|
-
name: process.env.APP_NAME?.trim() || "WorkHub",
|
|
35
|
-
env: process.env.APP_ENV ?? "local",
|
|
36
|
-
debug: (process.env.APP_DEBUG ?? "true") !== "false",
|
|
37
|
-
url: process.env.APP_URL ?? "http://localhost:3000",
|
|
38
|
-
apiPrefix: process.env.API_PREFIX ?? "/api/v1"
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
// ../../src/core/security/safeUrl.ts
|
|
42
|
-
import { lookup as dnsLookupImpl } from "dns/promises";
|
|
43
|
-
import { BadRequestError } from "@getstrata/core/errors/http";
|
|
44
|
-
var dnsLookup = dnsLookupImpl;
|
|
45
|
-
var BLOCKED_HOSTNAMES = new Set([
|
|
46
|
-
"localhost",
|
|
47
|
-
"127.0.0.1",
|
|
48
|
-
"0.0.0.0",
|
|
49
|
-
"::1",
|
|
50
|
-
"metadata.google.internal"
|
|
51
|
-
]);
|
|
52
|
-
function isPrivateIpv4(hostname) {
|
|
53
|
-
const match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
|
|
54
|
-
if (!match) {
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
const octets = match.slice(1, 5).map((part) => Number.parseInt(part, 10));
|
|
58
|
-
if (octets.some((octet) => octet < 0 || octet > 255)) {
|
|
59
|
-
return true;
|
|
60
|
-
}
|
|
61
|
-
const [a = 0, b = 0] = octets;
|
|
62
|
-
if (a === 10) {
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
if (a === 127) {
|
|
66
|
-
return true;
|
|
67
|
-
}
|
|
68
|
-
if (a === 0) {
|
|
69
|
-
return true;
|
|
70
|
-
}
|
|
71
|
-
if (a === 169 && b === 254) {
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
if (a === 172 && b >= 16 && b <= 31) {
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
if (a === 192 && b === 168) {
|
|
78
|
-
return true;
|
|
79
|
-
}
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
function isBlockedHostname(hostname) {
|
|
83
|
-
const normalized = hostname.trim().toLowerCase();
|
|
84
|
-
if (normalized.length === 0) {
|
|
85
|
-
return true;
|
|
86
|
-
}
|
|
87
|
-
if (BLOCKED_HOSTNAMES.has(normalized)) {
|
|
88
|
-
return true;
|
|
89
|
-
}
|
|
90
|
-
if (normalized.endsWith(".local") || normalized.endsWith(".internal")) {
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
if (normalized.includes(":")) {
|
|
94
|
-
return true;
|
|
95
|
-
}
|
|
96
|
-
return isPrivateIpv4(normalized);
|
|
97
|
-
}
|
|
98
|
-
function assertSafeOutboundUrl(rawUrl, options = {}) {
|
|
99
|
-
let parsed;
|
|
100
|
-
try {
|
|
101
|
-
parsed = new URL(rawUrl);
|
|
102
|
-
} catch {
|
|
103
|
-
throw new BadRequestError("Webhook URL is invalid.");
|
|
104
|
-
}
|
|
105
|
-
if (parsed.protocol !== "https:" && !(options.allowHttp && parsed.protocol === "http:")) {
|
|
106
|
-
throw new BadRequestError("Webhook URL must use HTTPS.");
|
|
107
|
-
}
|
|
108
|
-
if (parsed.username || parsed.password) {
|
|
109
|
-
throw new BadRequestError("Webhook URL must not include credentials.");
|
|
110
|
-
}
|
|
111
|
-
if (isBlockedHostname(parsed.hostname)) {
|
|
112
|
-
throw new BadRequestError("Webhook URL targets a blocked host.");
|
|
113
|
-
}
|
|
114
|
-
return parsed;
|
|
115
|
-
}
|
|
116
|
-
function isBlockedIpAddress(address) {
|
|
117
|
-
return isBlockedHostname(address.trim().toLowerCase());
|
|
118
|
-
}
|
|
119
|
-
async function assertSafeOutboundUrlResolved(rawUrl, options = {}) {
|
|
120
|
-
const parsed = assertSafeOutboundUrl(rawUrl, options);
|
|
121
|
-
if (options.resolveDns === false) {
|
|
122
|
-
return parsed;
|
|
123
|
-
}
|
|
124
|
-
const hostname = parsed.hostname.trim().toLowerCase();
|
|
125
|
-
const results = await dnsLookup(hostname, { all: true, verbatim: true });
|
|
126
|
-
if (results.some((result) => isBlockedIpAddress(result.address))) {
|
|
127
|
-
throw new BadRequestError("Webhook URL targets a blocked host.");
|
|
128
|
-
}
|
|
129
|
-
return parsed;
|
|
130
|
-
}
|
|
131
|
-
function setDnsLookupForTests(lookupFn) {
|
|
132
|
-
dnsLookup = lookupFn;
|
|
133
|
-
}
|
|
134
|
-
function resetDnsLookupForTests() {
|
|
135
|
-
dnsLookup = dnsLookupImpl;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// ../../src/core/security/safeFetch.ts
|
|
139
|
-
var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
|
|
140
|
-
async function safeFetch(input, init = {}, options = {}) {
|
|
141
|
-
const timeoutMs = options.timeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS;
|
|
142
|
-
const maxRedirects = options.maxRedirects ?? 0;
|
|
143
|
-
const resolveDns = options.resolveDns ?? appConfig.env === "production";
|
|
144
|
-
const urlOptions = { allowHttp: options.allowHttp, resolveDns };
|
|
145
|
-
const controller = new AbortController;
|
|
146
|
-
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
147
|
-
try {
|
|
148
|
-
let currentUrl = (await assertSafeOutboundUrlResolved(input, urlOptions)).toString();
|
|
149
|
-
let redirectCount = 0;
|
|
150
|
-
while (true) {
|
|
151
|
-
const response = await fetch(currentUrl, {
|
|
152
|
-
...init,
|
|
153
|
-
signal: controller.signal,
|
|
154
|
-
redirect: "manual"
|
|
155
|
-
});
|
|
156
|
-
if (response.status >= 300 && response.status < 400) {
|
|
157
|
-
const location = response.headers.get("location");
|
|
158
|
-
if (!location || redirectCount >= maxRedirects) {
|
|
159
|
-
return response;
|
|
160
|
-
}
|
|
161
|
-
currentUrl = (await assertSafeOutboundUrlResolved(new URL(location, currentUrl).toString(), urlOptions)).toString();
|
|
162
|
-
redirectCount += 1;
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
return response;
|
|
166
|
-
}
|
|
167
|
-
} finally {
|
|
168
|
-
clearTimeout(timeout);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// ../../src/core/tenant/resolveTenant.ts
|
|
173
4
|
import { repositoryConnection as db } from "@getstrata/core/database/repositoryConnection";
|
|
5
|
+
import { Job } from "@getstrata/core/queue";
|
|
6
|
+
import { safeFetch } from "@getstrata/core/security/safeFetch";
|
|
7
|
+
import { assertSafeOutboundUrl } from "@getstrata/core/security/safeUrl";
|
|
8
|
+
import { resolveTenant } from "@getstrata/core/tenant/resolveTenant";
|
|
9
|
+
import { runWithTenantDatabase } from "@getstrata/core/tenant/tenantDatabaseScope";
|
|
174
10
|
|
|
175
|
-
// ../../src/core/tenant/tenancyConfig.ts
|
|
176
|
-
function readTenancyDriver(env = process.env) {
|
|
177
|
-
return env.TENANCY_DRIVER === "none" ? "none" : "rls";
|
|
178
|
-
}
|
|
179
|
-
function isTenancyEnabled(env = process.env) {
|
|
180
|
-
return readTenancyDriver(env) !== "none";
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// ../../src/core/tenant/resolveTenant.ts
|
|
184
|
-
async function resolveTenant(tenantId) {
|
|
185
|
-
if (!isTenancyEnabled()) {
|
|
186
|
-
return {
|
|
187
|
-
id: tenantId,
|
|
188
|
-
slug: "default",
|
|
189
|
-
plan: "free",
|
|
190
|
-
region: "eu"
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
const rows = await db`
|
|
194
|
-
SELECT id, slug, plan, region
|
|
195
|
-
FROM tenant
|
|
196
|
-
WHERE id = ${tenantId}
|
|
197
|
-
LIMIT 1
|
|
198
|
-
`;
|
|
199
|
-
const row = rows[0];
|
|
200
|
-
return row ? { id: row.id, slug: row.slug, plan: row.plan, region: row.region ?? "eu" } : null;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// ../../src/core/tenant/tenantDatabaseScope.ts
|
|
204
|
-
import { getDefaultDatabasePool } from "@getstrata/core/database/defaultConnection";
|
|
205
|
-
|
|
206
|
-
// ../../src/core/runtime/asyncContextStore.ts
|
|
207
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
208
|
-
function createAsyncContextStore(key) {
|
|
209
|
-
const symbol = Symbol.for(key);
|
|
210
|
-
const globalRecord = globalThis;
|
|
211
|
-
const existing = globalRecord[symbol];
|
|
212
|
-
if (existing) {
|
|
213
|
-
return existing;
|
|
214
|
-
}
|
|
215
|
-
const store = new AsyncLocalStorage;
|
|
216
|
-
globalRecord[symbol] = store;
|
|
217
|
-
return store;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// ../../src/core/database/connectionContext.ts
|
|
221
|
-
var activeConnection = createAsyncContextStore("@getstrata/databaseConnectionContext");
|
|
222
|
-
function runWithDatabaseConnection(connection, callback) {
|
|
223
|
-
return activeConnection.run(connection, callback);
|
|
224
|
-
}
|
|
225
|
-
function getActiveDatabaseConnection(fallback) {
|
|
226
|
-
return activeConnection.getStore() ?? fallback;
|
|
227
|
-
}
|
|
228
|
-
function hasActiveDatabaseConnection() {
|
|
229
|
-
return activeConnection.getStore() !== undefined;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// ../../src/core/tenant/tenantContext.ts
|
|
233
|
-
var tenantContext = createAsyncContextStore("@getstrata/tenantContext");
|
|
234
|
-
function runWithTenant(tenant, callback) {
|
|
235
|
-
return tenantContext.run(tenant, callback);
|
|
236
|
-
}
|
|
237
|
-
function currentTenant() {
|
|
238
|
-
return tenantContext.getStore() ?? null;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// ../../src/core/tenant/tenantDatabaseScope.ts
|
|
242
|
-
async function applyTenantContextToTransaction(transaction, tenantId) {
|
|
243
|
-
await transaction.unsafe(`SELECT set_config('app.tenant_id', $1, true)`, [String(tenantId)]);
|
|
244
|
-
await transaction.unsafe(`SELECT set_config('app.bypass_rls', $1, true)`, ["false"]);
|
|
245
|
-
}
|
|
246
|
-
async function runWithTenantDatabase(tenant, callback) {
|
|
247
|
-
if (!isTenancyEnabled()) {
|
|
248
|
-
return await runWithTenant(tenant, callback);
|
|
249
|
-
}
|
|
250
|
-
if (hasActiveDatabaseConnection()) {
|
|
251
|
-
const activeConnection2 = getActiveDatabaseConnection(getDefaultDatabasePool());
|
|
252
|
-
await applyTenantContextToTransaction(activeConnection2, tenant.id);
|
|
253
|
-
return await runWithTenant(tenant, callback);
|
|
254
|
-
}
|
|
255
|
-
return await getDefaultDatabasePool().begin(async (transaction) => {
|
|
256
|
-
await applyTenantContextToTransaction(transaction, tenant.id);
|
|
257
|
-
return await runWithDatabaseConnection(transaction, async () => {
|
|
258
|
-
return await runWithTenant(tenant, callback);
|
|
259
|
-
});
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
function isInsideTenantDatabaseScope(tenantId = currentTenant()?.id) {
|
|
263
|
-
return hasActiveDatabaseConnection() && currentTenant()?.id === tenantId;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// ../../src/core/jobs/dispatchWebhookJob.ts
|
|
267
11
|
class DispatchWebhookJob extends Job {
|
|
268
12
|
maxAttempts = 3;
|
|
269
13
|
backoffMs = 2000;
|
|
@@ -280,7 +24,7 @@ class DispatchWebhookJob extends Job {
|
|
|
280
24
|
}
|
|
281
25
|
}
|
|
282
26
|
async deliver(payload) {
|
|
283
|
-
const rows = await
|
|
27
|
+
const rows = await db`
|
|
284
28
|
SELECT id, url, secret
|
|
285
29
|
FROM webhook
|
|
286
30
|
WHERE id = ${payload.webhookId} AND active = TRUE
|
|
@@ -312,7 +56,7 @@ class DispatchWebhookJob extends Job {
|
|
|
312
56
|
}
|
|
313
57
|
} catch (error) {
|
|
314
58
|
errorMessage = error instanceof Error ? error.message : String(error);
|
|
315
|
-
await
|
|
59
|
+
await db`
|
|
316
60
|
INSERT INTO webhook_delivery (webhook_id, event, payload, response_status, error)
|
|
317
61
|
VALUES (
|
|
318
62
|
${webhook.id},
|
|
@@ -324,7 +68,7 @@ class DispatchWebhookJob extends Job {
|
|
|
324
68
|
`;
|
|
325
69
|
return error instanceof Error ? error : new Error(errorMessage);
|
|
326
70
|
}
|
|
327
|
-
await
|
|
71
|
+
await db`
|
|
328
72
|
INSERT INTO webhook_delivery (webhook_id, event, payload, response_status)
|
|
329
73
|
VALUES (
|
|
330
74
|
${webhook.id},
|
|
@@ -153,18 +153,37 @@ async function runQueueJob(envelope, failedJobs) {
|
|
|
153
153
|
|
|
154
154
|
// ../../src/core/queue/redisQueue.ts
|
|
155
155
|
var {RedisClient } = globalThis.Bun;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
|
|
157
|
+
// ../../src/core/runtime/appKeyPrefix.ts
|
|
158
|
+
function appKeyPrefix() {
|
|
159
|
+
return process.env.APP_KEY_PREFIX?.trim() || "workhub";
|
|
160
|
+
}
|
|
161
|
+
function namespacedRedisKey(kind) {
|
|
162
|
+
return `${appKeyPrefix()}:${kind}`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ../../src/core/queue/redisQueue.ts
|
|
166
|
+
function queueListKey() {
|
|
167
|
+
return namespacedRedisKey("queue:default");
|
|
168
|
+
}
|
|
169
|
+
function queueHighKey() {
|
|
170
|
+
return namespacedRedisKey("queue:high");
|
|
171
|
+
}
|
|
172
|
+
function queueLowKey() {
|
|
173
|
+
return namespacedRedisKey("queue:low");
|
|
174
|
+
}
|
|
175
|
+
var QUEUE_LIST_KEY = queueListKey();
|
|
176
|
+
var QUEUE_HIGH_KEY = queueHighKey();
|
|
177
|
+
var QUEUE_LOW_KEY = queueLowKey();
|
|
159
178
|
var QUEUE_KEYS = [QUEUE_HIGH_KEY, QUEUE_LIST_KEY, QUEUE_LOW_KEY];
|
|
160
179
|
function queueKeyForPriority(priority = "default") {
|
|
161
180
|
switch (priority) {
|
|
162
181
|
case "high":
|
|
163
|
-
return
|
|
182
|
+
return queueHighKey();
|
|
164
183
|
case "low":
|
|
165
|
-
return
|
|
184
|
+
return queueLowKey();
|
|
166
185
|
default:
|
|
167
|
-
return
|
|
186
|
+
return queueListKey();
|
|
168
187
|
}
|
|
169
188
|
}
|
|
170
189
|
function parseQueueJobEnvelope(rawPayload) {
|
|
@@ -153,18 +153,37 @@ async function runQueueJob(envelope, failedJobs) {
|
|
|
153
153
|
|
|
154
154
|
// ../../src/core/queue/redisQueue.ts
|
|
155
155
|
var {RedisClient } = globalThis.Bun;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
|
|
157
|
+
// ../../src/core/runtime/appKeyPrefix.ts
|
|
158
|
+
function appKeyPrefix() {
|
|
159
|
+
return process.env.APP_KEY_PREFIX?.trim() || "workhub";
|
|
160
|
+
}
|
|
161
|
+
function namespacedRedisKey(kind) {
|
|
162
|
+
return `${appKeyPrefix()}:${kind}`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ../../src/core/queue/redisQueue.ts
|
|
166
|
+
function queueListKey() {
|
|
167
|
+
return namespacedRedisKey("queue:default");
|
|
168
|
+
}
|
|
169
|
+
function queueHighKey() {
|
|
170
|
+
return namespacedRedisKey("queue:high");
|
|
171
|
+
}
|
|
172
|
+
function queueLowKey() {
|
|
173
|
+
return namespacedRedisKey("queue:low");
|
|
174
|
+
}
|
|
175
|
+
var QUEUE_LIST_KEY = queueListKey();
|
|
176
|
+
var QUEUE_HIGH_KEY = queueHighKey();
|
|
177
|
+
var QUEUE_LOW_KEY = queueLowKey();
|
|
159
178
|
var QUEUE_KEYS = [QUEUE_HIGH_KEY, QUEUE_LIST_KEY, QUEUE_LOW_KEY];
|
|
160
179
|
function queueKeyForPriority(priority = "default") {
|
|
161
180
|
switch (priority) {
|
|
162
181
|
case "high":
|
|
163
|
-
return
|
|
182
|
+
return queueHighKey();
|
|
164
183
|
case "low":
|
|
165
|
-
return
|
|
184
|
+
return queueLowKey();
|
|
166
185
|
default:
|
|
167
|
-
return
|
|
186
|
+
return queueListKey();
|
|
168
187
|
}
|
|
169
188
|
}
|
|
170
189
|
function parseQueueJobEnvelope(rawPayload) {
|
|
@@ -156,18 +156,37 @@ async function runQueueJob(envelope, failedJobs) {
|
|
|
156
156
|
|
|
157
157
|
// ../../src/core/queue/redisQueue.ts
|
|
158
158
|
var {RedisClient } = globalThis.Bun;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
|
|
160
|
+
// ../../src/core/runtime/appKeyPrefix.ts
|
|
161
|
+
function appKeyPrefix() {
|
|
162
|
+
return process.env.APP_KEY_PREFIX?.trim() || "workhub";
|
|
163
|
+
}
|
|
164
|
+
function namespacedRedisKey(kind) {
|
|
165
|
+
return `${appKeyPrefix()}:${kind}`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ../../src/core/queue/redisQueue.ts
|
|
169
|
+
function queueListKey() {
|
|
170
|
+
return namespacedRedisKey("queue:default");
|
|
171
|
+
}
|
|
172
|
+
function queueHighKey() {
|
|
173
|
+
return namespacedRedisKey("queue:high");
|
|
174
|
+
}
|
|
175
|
+
function queueLowKey() {
|
|
176
|
+
return namespacedRedisKey("queue:low");
|
|
177
|
+
}
|
|
178
|
+
var QUEUE_LIST_KEY = queueListKey();
|
|
179
|
+
var QUEUE_HIGH_KEY = queueHighKey();
|
|
180
|
+
var QUEUE_LOW_KEY = queueLowKey();
|
|
162
181
|
var QUEUE_KEYS = [QUEUE_HIGH_KEY, QUEUE_LIST_KEY, QUEUE_LOW_KEY];
|
|
163
182
|
function queueKeyForPriority(priority = "default") {
|
|
164
183
|
switch (priority) {
|
|
165
184
|
case "high":
|
|
166
|
-
return
|
|
185
|
+
return queueHighKey();
|
|
167
186
|
case "low":
|
|
168
|
-
return
|
|
187
|
+
return queueLowKey();
|
|
169
188
|
default:
|
|
170
|
-
return
|
|
189
|
+
return queueListKey();
|
|
171
190
|
}
|
|
172
191
|
}
|
|
173
192
|
function parseQueueJobEnvelope(rawPayload) {
|
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
// ../../src/core/queue/redisQueue.ts
|
|
3
3
|
var {RedisClient } = globalThis.Bun;
|
|
4
4
|
|
|
5
|
+
// ../../src/core/runtime/appKeyPrefix.ts
|
|
6
|
+
function appKeyPrefix() {
|
|
7
|
+
return process.env.APP_KEY_PREFIX?.trim() || "workhub";
|
|
8
|
+
}
|
|
9
|
+
function namespacedRedisKey(kind) {
|
|
10
|
+
return `${appKeyPrefix()}:${kind}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
5
13
|
// ../../src/core/queue/jobRegistry.ts
|
|
6
14
|
class JobRegistry {
|
|
7
15
|
factories = new Map;
|
|
@@ -91,18 +99,27 @@ async function runQueueJob(envelope, failedJobs) {
|
|
|
91
99
|
}
|
|
92
100
|
|
|
93
101
|
// ../../src/core/queue/redisQueue.ts
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
102
|
+
function queueListKey() {
|
|
103
|
+
return namespacedRedisKey("queue:default");
|
|
104
|
+
}
|
|
105
|
+
function queueHighKey() {
|
|
106
|
+
return namespacedRedisKey("queue:high");
|
|
107
|
+
}
|
|
108
|
+
function queueLowKey() {
|
|
109
|
+
return namespacedRedisKey("queue:low");
|
|
110
|
+
}
|
|
111
|
+
var QUEUE_LIST_KEY = queueListKey();
|
|
112
|
+
var QUEUE_HIGH_KEY = queueHighKey();
|
|
113
|
+
var QUEUE_LOW_KEY = queueLowKey();
|
|
97
114
|
var QUEUE_KEYS = [QUEUE_HIGH_KEY, QUEUE_LIST_KEY, QUEUE_LOW_KEY];
|
|
98
115
|
function queueKeyForPriority(priority = "default") {
|
|
99
116
|
switch (priority) {
|
|
100
117
|
case "high":
|
|
101
|
-
return
|
|
118
|
+
return queueHighKey();
|
|
102
119
|
case "low":
|
|
103
|
-
return
|
|
120
|
+
return queueLowKey();
|
|
104
121
|
default:
|
|
105
|
-
return
|
|
122
|
+
return queueListKey();
|
|
106
123
|
}
|
|
107
124
|
}
|
|
108
125
|
function parseQueueJobEnvelope(rawPayload) {
|
package/dist/index.js
CHANGED
|
@@ -966,8 +966,22 @@ function jsonScimError(detail, status) {
|
|
|
966
966
|
}
|
|
967
967
|
// ../../src/core/cache/redisCacheStore.ts
|
|
968
968
|
var {RedisClient } = globalThis.Bun;
|
|
969
|
-
|
|
970
|
-
|
|
969
|
+
|
|
970
|
+
// ../../src/core/runtime/appKeyPrefix.ts
|
|
971
|
+
function appKeyPrefix() {
|
|
972
|
+
return process.env.APP_KEY_PREFIX?.trim() || "workhub";
|
|
973
|
+
}
|
|
974
|
+
function namespacedRedisKey(kind) {
|
|
975
|
+
return `${appKeyPrefix()}:${kind}`;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// ../../src/core/cache/redisCacheStore.ts
|
|
979
|
+
function cacheKeyPrefix() {
|
|
980
|
+
return namespacedRedisKey("cache:");
|
|
981
|
+
}
|
|
982
|
+
function cacheTagPrefix() {
|
|
983
|
+
return namespacedRedisKey("cache:tag:");
|
|
984
|
+
}
|
|
971
985
|
|
|
972
986
|
class RedisCacheStore {
|
|
973
987
|
ttlMs;
|
|
@@ -1054,10 +1068,10 @@ class RedisCacheStore {
|
|
|
1054
1068
|
return deleted > 0;
|
|
1055
1069
|
}
|
|
1056
1070
|
async invalidateByPrefix(prefix) {
|
|
1057
|
-
const keys = await this.client.keys(`${
|
|
1071
|
+
const keys = await this.client.keys(`${cacheKeyPrefix()}*`);
|
|
1058
1072
|
let removed = 0;
|
|
1059
1073
|
for (const storageKey of keys) {
|
|
1060
|
-
const key = storageKey.slice(
|
|
1074
|
+
const key = storageKey.slice(cacheKeyPrefix().length);
|
|
1061
1075
|
if (key === prefix || key.startsWith(`${prefix}?`)) {
|
|
1062
1076
|
if (await this.invalidate(key)) {
|
|
1063
1077
|
removed += 1;
|
|
@@ -1067,11 +1081,11 @@ class RedisCacheStore {
|
|
|
1067
1081
|
return removed;
|
|
1068
1082
|
}
|
|
1069
1083
|
async clear() {
|
|
1070
|
-
const keys = await this.client.keys(`${
|
|
1084
|
+
const keys = await this.client.keys(`${cacheKeyPrefix()}*`);
|
|
1071
1085
|
if (keys.length > 0) {
|
|
1072
1086
|
await this.client.del(...keys);
|
|
1073
1087
|
}
|
|
1074
|
-
const tagKeys = await this.client.keys(`${
|
|
1088
|
+
const tagKeys = await this.client.keys(`${cacheTagPrefix()}*`);
|
|
1075
1089
|
if (tagKeys.length > 0) {
|
|
1076
1090
|
await this.client.del(...tagKeys);
|
|
1077
1091
|
}
|
|
@@ -1079,14 +1093,14 @@ class RedisCacheStore {
|
|
|
1079
1093
|
this.keyTags.clear();
|
|
1080
1094
|
}
|
|
1081
1095
|
async size() {
|
|
1082
|
-
const keys = await this.client.keys(`${
|
|
1096
|
+
const keys = await this.client.keys(`${cacheKeyPrefix()}*`);
|
|
1083
1097
|
return keys.length;
|
|
1084
1098
|
}
|
|
1085
1099
|
storageKey(key) {
|
|
1086
|
-
return `${
|
|
1100
|
+
return `${cacheKeyPrefix()}${key}`;
|
|
1087
1101
|
}
|
|
1088
1102
|
tagKey(tag) {
|
|
1089
|
-
return `${
|
|
1103
|
+
return `${cacheTagPrefix()}${tag}`;
|
|
1090
1104
|
}
|
|
1091
1105
|
async detachKeyFromTags(key) {
|
|
1092
1106
|
const tags = this.keyTags.get(key);
|
|
@@ -1099,7 +1113,7 @@ class RedisCacheStore {
|
|
|
1099
1113
|
this.keyTags.delete(key);
|
|
1100
1114
|
}
|
|
1101
1115
|
async enforceMaxEntries() {
|
|
1102
|
-
const keys = await this.client.keys(`${
|
|
1116
|
+
const keys = await this.client.keys(`${cacheKeyPrefix()}*`);
|
|
1103
1117
|
if (keys.length <= this.maxEntries) {
|
|
1104
1118
|
return;
|
|
1105
1119
|
}
|
|
@@ -5401,7 +5415,7 @@ async function resolveLoginEmail(request) {
|
|
|
5401
5415
|
}
|
|
5402
5416
|
function createLoginThrottleMiddleware(options) {
|
|
5403
5417
|
const client = new RedisClient2(options.redisUrl);
|
|
5404
|
-
const prefix = options.keyPrefix ?? "
|
|
5418
|
+
const prefix = options.keyPrefix ?? namespacedRedisKey("login-throttle:");
|
|
5405
5419
|
return async (request, next) => {
|
|
5406
5420
|
const identity = resolveLoginIdentity(request);
|
|
5407
5421
|
const email = await resolveLoginEmail(request);
|
|
@@ -5424,7 +5438,7 @@ function createLoginThrottleMiddleware(options) {
|
|
|
5424
5438
|
// ../../src/core/http/memoryThrottleMiddleware.ts
|
|
5425
5439
|
var throttleBucketRegistries = new Set;
|
|
5426
5440
|
function createMemoryThrottleMiddleware(options) {
|
|
5427
|
-
const prefix = options.keyPrefix ?? "
|
|
5441
|
+
const prefix = options.keyPrefix ?? namespacedRedisKey("memory-throttle:");
|
|
5428
5442
|
const buckets = new Map;
|
|
5429
5443
|
throttleBucketRegistries.add(buckets);
|
|
5430
5444
|
return async (request, next) => {
|
|
@@ -5626,7 +5640,7 @@ function createScimThrottleMiddleware(options) {
|
|
|
5626
5640
|
const client = options.redisUrl ? new RedisClient3(options.redisUrl) : null;
|
|
5627
5641
|
return async (request, next) => {
|
|
5628
5642
|
const identity = resolveScimIdentity(request);
|
|
5629
|
-
const key =
|
|
5643
|
+
const key = `${namespacedRedisKey("scim-throttle:")}${identity}`;
|
|
5630
5644
|
if (client) {
|
|
5631
5645
|
const attempts = Number(await client.incr(key));
|
|
5632
5646
|
if (attempts === 1) {
|
|
@@ -5782,7 +5796,7 @@ function resolveThrottleIdentity(request) {
|
|
|
5782
5796
|
}
|
|
5783
5797
|
function createThrottleMiddleware(options) {
|
|
5784
5798
|
const client = new RedisClient4(options.redisUrl);
|
|
5785
|
-
const prefix = options.keyPrefix ?? "
|
|
5799
|
+
const prefix = options.keyPrefix ?? namespacedRedisKey("throttle:");
|
|
5786
5800
|
return async (request, next) => {
|
|
5787
5801
|
const identity = resolveThrottleIdentity(request);
|
|
5788
5802
|
const path = new URL(request.url).pathname;
|
|
@@ -6263,18 +6277,27 @@ async function runQueueJob(envelope, failedJobs) {
|
|
|
6263
6277
|
|
|
6264
6278
|
// ../../src/core/queue/redisQueue.ts
|
|
6265
6279
|
var {RedisClient: RedisClient5 } = globalThis.Bun;
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6280
|
+
function queueListKey() {
|
|
6281
|
+
return namespacedRedisKey("queue:default");
|
|
6282
|
+
}
|
|
6283
|
+
function queueHighKey() {
|
|
6284
|
+
return namespacedRedisKey("queue:high");
|
|
6285
|
+
}
|
|
6286
|
+
function queueLowKey() {
|
|
6287
|
+
return namespacedRedisKey("queue:low");
|
|
6288
|
+
}
|
|
6289
|
+
var QUEUE_LIST_KEY = queueListKey();
|
|
6290
|
+
var QUEUE_HIGH_KEY = queueHighKey();
|
|
6291
|
+
var QUEUE_LOW_KEY = queueLowKey();
|
|
6269
6292
|
var QUEUE_KEYS = [QUEUE_HIGH_KEY, QUEUE_LIST_KEY, QUEUE_LOW_KEY];
|
|
6270
6293
|
function queueKeyForPriority(priority = "default") {
|
|
6271
6294
|
switch (priority) {
|
|
6272
6295
|
case "high":
|
|
6273
|
-
return
|
|
6296
|
+
return queueHighKey();
|
|
6274
6297
|
case "low":
|
|
6275
|
-
return
|
|
6298
|
+
return queueLowKey();
|
|
6276
6299
|
default:
|
|
6277
|
-
return
|
|
6300
|
+
return queueListKey();
|
|
6278
6301
|
}
|
|
6279
6302
|
}
|
|
6280
6303
|
function parseQueueJobEnvelope(rawPayload) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Job } from "@getstrata/core/queue";
|
|
2
|
+
interface DispatchWebhookPayload {
|
|
3
|
+
webhookId: number;
|
|
4
|
+
tenantId: number;
|
|
5
|
+
event: string;
|
|
6
|
+
payload: Record<string, unknown>;
|
|
7
|
+
}
|
|
8
|
+
declare class DispatchWebhookJob extends Job<DispatchWebhookPayload> {
|
|
9
|
+
readonly maxAttempts = 3;
|
|
10
|
+
readonly backoffMs = 2000;
|
|
11
|
+
handle(payload: DispatchWebhookPayload): Promise<void>;
|
|
12
|
+
private deliver;
|
|
13
|
+
}
|
|
14
|
+
export { DispatchWebhookJob };
|
|
15
|
+
export default DispatchWebhookJob;
|
|
16
|
+
export type { DispatchWebhookPayload };
|