@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.
Files changed (40) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/core/contracts/serviceTokens.d.ts +3 -2
  3. package/dist/core/database/baseRepository.d.ts +5 -0
  4. package/dist/core/database/model.d.ts +3 -1
  5. package/dist/core/database/query.d.ts +6 -1
  6. package/dist/core/database/repositoryQuery.d.ts +7 -1
  7. package/dist/core/database/schema/blueprint.d.ts +2 -0
  8. package/dist/core/database/schema/columnDefinition.d.ts +1 -1
  9. package/dist/core/database/types.d.ts +10 -1
  10. package/dist/core/http/requestMetaContext.d.ts +2 -0
  11. package/dist/core/queue/createAppQueue.d.ts +2 -1
  12. package/dist/core/queue/failedJobService.d.ts +3 -1
  13. package/dist/core/queue/index.d.ts +10 -2
  14. package/dist/core/queue/jobRunner.d.ts +7 -1
  15. package/dist/core/queue/publicQueue.d.ts +2 -1
  16. package/dist/core/queue/queueConfig.d.ts +3 -0
  17. package/dist/core/queue/redisQueue.d.ts +5 -7
  18. package/dist/core/queue/redisQueueKeys.d.ts +8 -0
  19. package/dist/core/queue/redisStreams.d.ts +73 -0
  20. package/dist/core/queue/types.d.ts +1 -0
  21. package/dist/core/tenant/publicTenantHost.d.ts +11 -0
  22. package/dist/core/tenant/tenantMiddleware.d.ts +4 -0
  23. package/dist/entries/auth/basicAuthGuard.js +1 -0
  24. package/dist/entries/auth/jwtGuard.js +1 -0
  25. package/dist/entries/auth/sessionGuard.js +1 -0
  26. package/dist/entries/contracts/serviceTokens.js +2 -0
  27. package/dist/entries/database/query.js +70 -1
  28. package/dist/entries/database/repositoryQuery.js +99 -1
  29. package/dist/entries/database/schema.js +9 -0
  30. package/dist/entries/database/sqliteConnection.js +1 -1
  31. package/dist/entries/queue/createAppQueue.js +681 -66
  32. package/dist/entries/queue/failedJobRepository.js +1 -1
  33. package/dist/entries/queue/failedJobService.js +5 -1
  34. package/dist/entries/queue/jobRunner.js +67 -2
  35. package/dist/entries/queue/publicQueue.js +679 -65
  36. package/dist/entries/queue/queueMetrics.js +694 -72
  37. package/dist/entries/queue/redisQueue.js +765 -148
  38. package/dist/framework/public-api.d.ts +2 -2
  39. package/dist/index.js +855 -69
  40. 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
- await new Promise((resolve) => setTimeout(resolve, backoffMs * nextAttempt));
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
  };