@getstrata/core 2.2.0 → 2.2.2
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 +11 -0
- package/dist/core/contracts/serviceTokens.d.ts +3 -2
- package/dist/core/database/baseRepository.d.ts +5 -0
- package/dist/core/database/model.d.ts +3 -1
- package/dist/core/database/query.d.ts +6 -1
- package/dist/core/database/repositoryQuery.d.ts +7 -1
- package/dist/core/database/schema/blueprint.d.ts +2 -0
- package/dist/core/database/schema/columnDefinition.d.ts +1 -1
- package/dist/core/database/types.d.ts +10 -1
- package/dist/core/http/requestMetaContext.d.ts +2 -0
- package/dist/core/queue/createAppQueue.d.ts +2 -1
- package/dist/core/queue/failedJobService.d.ts +3 -1
- package/dist/core/queue/index.d.ts +10 -2
- package/dist/core/queue/jobRunner.d.ts +7 -1
- package/dist/core/queue/publicQueue.d.ts +2 -1
- package/dist/core/queue/queueConfig.d.ts +3 -0
- package/dist/core/queue/redisQueue.d.ts +5 -7
- package/dist/core/queue/redisQueueKeys.d.ts +8 -0
- package/dist/core/queue/redisStreams.d.ts +73 -0
- package/dist/core/queue/types.d.ts +1 -0
- package/dist/core/tenant/publicTenantHost.d.ts +11 -0
- package/dist/core/tenant/tenantMiddleware.d.ts +4 -0
- package/dist/entries/auth/basicAuthGuard.js +1 -0
- package/dist/entries/auth/jwtGuard.js +1 -0
- package/dist/entries/auth/sessionGuard.js +1 -0
- package/dist/entries/contracts/serviceTokens.js +2 -0
- package/dist/entries/database/query.js +70 -1
- package/dist/entries/database/repositoryQuery.js +99 -1
- package/dist/entries/database/schema.js +9 -0
- package/dist/entries/database/sqliteConnection.js +1 -1
- package/dist/entries/queue/createAppQueue.js +681 -66
- package/dist/entries/queue/failedJobRepository.js +1 -1
- package/dist/entries/queue/failedJobService.js +5 -1
- package/dist/entries/queue/jobRunner.js +67 -2
- package/dist/entries/queue/publicQueue.js +679 -65
- package/dist/entries/queue/queueMetrics.js +694 -72
- package/dist/entries/queue/redisQueue.js +765 -148
- package/dist/framework/public-api.d.ts +2 -2
- package/dist/index.js +855 -69
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@ import { defineTable } from "@getstrata/core/database/table";
|
|
|
7
7
|
var failedJobTable = defineTable({
|
|
8
8
|
name: "failed_job",
|
|
9
9
|
primaryKey: "id",
|
|
10
|
-
columns: ["id", "job_name", "payload", "exception", "failed_at"],
|
|
10
|
+
columns: ["id", "job_name", "job_id", "payload", "exception", "failed_at"],
|
|
11
11
|
defaultOrderBy: { column: "failed_at", direction: "DESC" }
|
|
12
12
|
});
|
|
13
13
|
|
|
@@ -8,6 +8,7 @@ class FailedJobService {
|
|
|
8
8
|
async recordFailure(input) {
|
|
9
9
|
return await this.repository.create({
|
|
10
10
|
job_name: input.jobName,
|
|
11
|
+
job_id: input.jobId ?? null,
|
|
11
12
|
payload: input.payload,
|
|
12
13
|
exception: input.exception,
|
|
13
14
|
failed_at: new Date
|
|
@@ -19,8 +20,11 @@ class FailedJobService {
|
|
|
19
20
|
orderBy: { column: "failed_at", direction: "DESC" }
|
|
20
21
|
});
|
|
21
22
|
}
|
|
22
|
-
async retry(id) {
|
|
23
|
+
async retry(id, enqueue) {
|
|
24
|
+
if (typeof enqueue !== "function")
|
|
25
|
+
throw new Error("Failed-job retry requires an admission callback.");
|
|
23
26
|
const failedJob = await this.repository.findByIdOrThrow(id, (jobId) => new Error(`Failed job ${jobId} not found.`));
|
|
27
|
+
await enqueue(failedJob);
|
|
24
28
|
await this.repository.deleteById(id);
|
|
25
29
|
return failedJob;
|
|
26
30
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
// ../../src/core/queue/jobRunner.ts
|
|
3
|
+
import { setTimeout as delay } from "timers/promises";
|
|
4
|
+
|
|
2
5
|
// ../../src/core/queue/jobRegistry.ts
|
|
3
6
|
class JobRegistry {
|
|
4
7
|
factories = new Map;
|
|
@@ -58,22 +61,82 @@ var queueConfig = {
|
|
|
58
61
|
return resolveQueueConfig().backoffMs;
|
|
59
62
|
}
|
|
60
63
|
};
|
|
64
|
+
function resolveRedisQueueTransport() {
|
|
65
|
+
const transport = process.env.QUEUE_REDIS_TRANSPORT ?? "lists";
|
|
66
|
+
if (transport !== "lists" && transport !== "streams")
|
|
67
|
+
throw new Error('QUEUE_REDIS_TRANSPORT must be "lists" or "streams".');
|
|
68
|
+
return transport;
|
|
69
|
+
}
|
|
70
|
+
function readQueueVisibilityMs() {
|
|
71
|
+
const value = Number(process.env.QUEUE_VISIBILITY_MS ?? "60000");
|
|
72
|
+
if (!Number.isSafeInteger(value) || value < 30)
|
|
73
|
+
throw new Error("QUEUE_VISIBILITY_MS must be an integer of at least 30 milliseconds.");
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
61
76
|
|
|
62
77
|
// ../../src/core/queue/jobRunner.ts
|
|
78
|
+
function parseQueueJobEnvelope(rawPayload) {
|
|
79
|
+
let parsed;
|
|
80
|
+
try {
|
|
81
|
+
parsed = JSON.parse(rawPayload);
|
|
82
|
+
} catch {
|
|
83
|
+
console.error("[QueueWorker] Ignoring malformed queue payload");
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
if (!parsed || typeof parsed !== "object") {
|
|
87
|
+
console.error("[QueueWorker] Ignoring non-object queue payload");
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
const envelope = parsed;
|
|
91
|
+
if (typeof envelope.name !== "string" || envelope.name.length === 0) {
|
|
92
|
+
console.error("[QueueWorker] Ignoring queue payload without job name");
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
if (!jobRegistry.create(envelope.name)) {
|
|
96
|
+
console.error(`[QueueWorker] Ignoring unknown job name: ${envelope.name}`);
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
if (envelope.payload !== undefined && (typeof envelope.payload !== "object" || envelope.payload === null)) {
|
|
100
|
+
console.error("[QueueWorker] Ignoring queue payload with invalid payload object");
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
if (envelope.jobId !== undefined && (typeof envelope.jobId !== "string" || !envelope.jobId.length))
|
|
104
|
+
return null;
|
|
105
|
+
if (envelope.attempts !== undefined && (!Number.isSafeInteger(envelope.attempts) || envelope.attempts < 0))
|
|
106
|
+
return null;
|
|
107
|
+
if (envelope.deadlineAtMs !== undefined && (!Number.isSafeInteger(envelope.deadlineAtMs) || envelope.deadlineAtMs <= 0))
|
|
108
|
+
return null;
|
|
109
|
+
if (envelope.cancellable !== undefined && (typeof envelope.cancellable !== "boolean" || envelope.cancellable && !envelope.jobId))
|
|
110
|
+
return null;
|
|
111
|
+
return {
|
|
112
|
+
...envelope.cancellable ? { cancellable: true } : {},
|
|
113
|
+
...envelope.deadlineAtMs === undefined ? {} : { deadlineAtMs: envelope.deadlineAtMs },
|
|
114
|
+
jobId: envelope.jobId,
|
|
115
|
+
name: envelope.name,
|
|
116
|
+
payload: envelope.payload ?? {},
|
|
117
|
+
attempts: typeof envelope.attempts === "number" ? envelope.attempts : 0
|
|
118
|
+
};
|
|
119
|
+
}
|
|
63
120
|
async function runQueueJob(envelope, failedJobs, options = {}) {
|
|
121
|
+
options.context?.signal.throwIfAborted();
|
|
64
122
|
const job = jobRegistry.create(envelope.name);
|
|
65
123
|
if (!job) {
|
|
66
124
|
throw new Error(`Unknown job "${envelope.name}".`);
|
|
67
125
|
}
|
|
68
126
|
const attempts = envelope.attempts ?? 0;
|
|
69
127
|
try {
|
|
70
|
-
await job.handle(envelope.payload);
|
|
128
|
+
await job.handle(envelope.payload, options.context);
|
|
71
129
|
} catch (error) {
|
|
130
|
+
options.context?.signal.throwIfAborted();
|
|
72
131
|
const nextAttempt = attempts + 1;
|
|
73
132
|
const maxAttempts = job.maxAttempts ?? queueConfig.maxAttempts;
|
|
74
133
|
if (nextAttempt < maxAttempts) {
|
|
75
134
|
const backoffMs = job.backoffMs ?? queueConfig.backoffMs;
|
|
76
|
-
|
|
135
|
+
if (options.deferRetry) {
|
|
136
|
+
await options.deferRetry({ ...envelope, attempts: nextAttempt }, backoffMs * nextAttempt);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
await delay(backoffMs * nextAttempt, undefined, { signal: options.context?.signal });
|
|
77
140
|
await runQueueJob({
|
|
78
141
|
...envelope,
|
|
79
142
|
attempts: nextAttempt
|
|
@@ -82,6 +145,7 @@ async function runQueueJob(envelope, failedJobs, options = {}) {
|
|
|
82
145
|
}
|
|
83
146
|
await failedJobs.recordFailure({
|
|
84
147
|
jobName: envelope.name,
|
|
148
|
+
jobId: options.context?.jobId ?? envelope.jobId,
|
|
85
149
|
payload: envelope.payload,
|
|
86
150
|
exception: error instanceof Error ? error.stack ?? error.message : String(error)
|
|
87
151
|
});
|
|
@@ -90,5 +154,6 @@ async function runQueueJob(envelope, failedJobs, options = {}) {
|
|
|
90
154
|
}
|
|
91
155
|
}
|
|
92
156
|
export {
|
|
157
|
+
parseQueueJobEnvelope,
|
|
93
158
|
runQueueJob
|
|
94
159
|
};
|