@igniter-js/jobs 0.1.1 → 0.1.12

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 (42) hide show
  1. package/AGENTS.md +1118 -96
  2. package/CHANGELOG.md +8 -0
  3. package/README.md +2146 -93
  4. package/dist/{adapter-PiDCQWQd.d.mts → adapter-CXZxomI9.d.mts} +2 -2
  5. package/dist/{adapter-PiDCQWQd.d.ts → adapter-CXZxomI9.d.ts} +2 -2
  6. package/dist/adapters/bullmq.adapter.d.mts +2 -2
  7. package/dist/adapters/bullmq.adapter.d.ts +2 -2
  8. package/dist/adapters/bullmq.adapter.js +2 -2
  9. package/dist/adapters/bullmq.adapter.js.map +1 -1
  10. package/dist/adapters/bullmq.adapter.mjs +1 -1
  11. package/dist/adapters/bullmq.adapter.mjs.map +1 -1
  12. package/dist/adapters/index.d.mts +140 -2
  13. package/dist/adapters/index.d.ts +140 -2
  14. package/dist/adapters/index.js +864 -31
  15. package/dist/adapters/index.js.map +1 -1
  16. package/dist/adapters/index.mjs +863 -31
  17. package/dist/adapters/index.mjs.map +1 -1
  18. package/dist/adapters/memory.adapter.d.mts +2 -2
  19. package/dist/adapters/memory.adapter.d.ts +2 -2
  20. package/dist/adapters/memory.adapter.js +122 -30
  21. package/dist/adapters/memory.adapter.js.map +1 -1
  22. package/dist/adapters/memory.adapter.mjs +121 -29
  23. package/dist/adapters/memory.adapter.mjs.map +1 -1
  24. package/dist/index.d.mts +452 -342
  25. package/dist/index.d.ts +452 -342
  26. package/dist/index.js +1923 -1002
  27. package/dist/index.js.map +1 -1
  28. package/dist/index.mjs +1921 -1001
  29. package/dist/index.mjs.map +1 -1
  30. package/dist/shim.d.mts +36 -0
  31. package/dist/shim.d.ts +36 -0
  32. package/dist/shim.js +75 -0
  33. package/dist/shim.js.map +1 -0
  34. package/dist/shim.mjs +67 -0
  35. package/dist/shim.mjs.map +1 -0
  36. package/dist/telemetry/index.d.mts +281 -0
  37. package/dist/telemetry/index.d.ts +281 -0
  38. package/dist/telemetry/index.js +97 -0
  39. package/dist/telemetry/index.js.map +1 -0
  40. package/dist/telemetry/index.mjs +95 -0
  41. package/dist/telemetry/index.mjs.map +1 -0
  42. package/package.json +44 -11
@@ -1,9 +1,14 @@
1
1
  'use strict';
2
2
 
3
3
  var adapterBullmq = require('@igniter-js/adapter-bullmq');
4
- var core = require('@igniter-js/core');
4
+ var common = require('@igniter-js/common');
5
5
 
6
- // src/adapters/bullmq.adapter.ts
6
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
7
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
8
+ }) : x)(function(x) {
9
+ if (typeof require !== "undefined") return require.apply(this, arguments);
10
+ throw Error('Dynamic require of "' + x + '" is not supported');
11
+ });
7
12
 
8
13
  // src/utils/prefix.ts
9
14
  var _IgniterJobsPrefix = class _IgniterJobsPrefix {
@@ -33,7 +38,7 @@ var _IgniterJobsPrefix = class _IgniterJobsPrefix {
33
38
  };
34
39
  _IgniterJobsPrefix.BASE_PREFIX = "igniter:jobs";
35
40
  var IgniterJobsPrefix = _IgniterJobsPrefix;
36
- var IgniterJobsError = class extends core.IgniterError {
41
+ var IgniterJobsError = class extends common.IgniterError {
37
42
  constructor(options) {
38
43
  super(options);
39
44
  }
@@ -655,10 +660,16 @@ var IgniterJobsMemoryAdapter = class _IgniterJobsMemoryAdapter {
655
660
  async retryJob(jobId, queue) {
656
661
  const job = this.jobsById.get(jobId);
657
662
  if (!job) {
658
- throw new IgniterJobsError({ code: "JOBS_NOT_FOUND", message: `Job "${jobId}" not found.` });
663
+ throw new IgniterJobsError({
664
+ code: "JOBS_NOT_FOUND",
665
+ message: `Job "${jobId}" not found.`
666
+ });
659
667
  }
660
668
  if (queue && job.queue !== queue) {
661
- throw new IgniterJobsError({ code: "JOBS_NOT_FOUND", message: `Job "${jobId}" not found in queue "${queue}".` });
669
+ throw new IgniterJobsError({
670
+ code: "JOBS_NOT_FOUND",
671
+ message: `Job "${jobId}" not found in queue "${queue}".`
672
+ });
662
673
  }
663
674
  job.status = "waiting";
664
675
  job.error = void 0;
@@ -672,15 +683,25 @@ var IgniterJobsMemoryAdapter = class _IgniterJobsMemoryAdapter {
672
683
  if (queue && job.queue !== queue) return;
673
684
  this.jobsById.delete(jobId);
674
685
  const list = this.jobsByQueue.get(job.queue);
675
- if (list) this.jobsByQueue.set(job.queue, list.filter((id) => id !== jobId));
686
+ if (list)
687
+ this.jobsByQueue.set(
688
+ job.queue,
689
+ list.filter((id) => id !== jobId)
690
+ );
676
691
  }
677
692
  async promoteJob(jobId, queue) {
678
693
  const job = this.jobsById.get(jobId);
679
694
  if (!job) {
680
- throw new IgniterJobsError({ code: "JOBS_NOT_FOUND", message: `Job "${jobId}" not found.` });
695
+ throw new IgniterJobsError({
696
+ code: "JOBS_NOT_FOUND",
697
+ message: `Job "${jobId}" not found.`
698
+ });
681
699
  }
682
700
  if (queue && job.queue !== queue) {
683
- throw new IgniterJobsError({ code: "JOBS_NOT_FOUND", message: `Job "${jobId}" not found in queue "${queue}".` });
701
+ throw new IgniterJobsError({
702
+ code: "JOBS_NOT_FOUND",
703
+ message: `Job "${jobId}" not found in queue "${queue}".`
704
+ });
684
705
  }
685
706
  if (job.status === "delayed" || job.status === "paused") {
686
707
  job.status = this.pausedQueues.has(job.queue) ? "paused" : "waiting";
@@ -690,10 +711,16 @@ var IgniterJobsMemoryAdapter = class _IgniterJobsMemoryAdapter {
690
711
  async moveJobToFailed(jobId, reason, queue) {
691
712
  const job = this.jobsById.get(jobId);
692
713
  if (!job) {
693
- throw new IgniterJobsError({ code: "JOBS_NOT_FOUND", message: `Job "${jobId}" not found.` });
714
+ throw new IgniterJobsError({
715
+ code: "JOBS_NOT_FOUND",
716
+ message: `Job "${jobId}" not found.`
717
+ });
694
718
  }
695
719
  if (queue && job.queue !== queue) {
696
- throw new IgniterJobsError({ code: "JOBS_NOT_FOUND", message: `Job "${jobId}" not found in queue "${queue}".` });
720
+ throw new IgniterJobsError({
721
+ code: "JOBS_NOT_FOUND",
722
+ message: `Job "${jobId}" not found in queue "${queue}".`
723
+ });
697
724
  }
698
725
  job.status = "failed";
699
726
  job.error = reason;
@@ -733,7 +760,13 @@ var IgniterJobsMemoryAdapter = class _IgniterJobsMemoryAdapter {
733
760
  return counts;
734
761
  }
735
762
  async listQueues() {
736
- const queues = Array.from(/* @__PURE__ */ new Set([...this.jobsByQueue.keys(), ...this.registeredJobs.keys(), ...this.registeredCrons.keys()]));
763
+ const queues = Array.from(
764
+ /* @__PURE__ */ new Set([
765
+ ...this.jobsByQueue.keys(),
766
+ ...this.registeredJobs.keys(),
767
+ ...this.registeredCrons.keys()
768
+ ])
769
+ );
737
770
  const result = [];
738
771
  for (const q of queues) {
739
772
  result.push(await this.getQueueInfo(q));
@@ -770,7 +803,10 @@ var IgniterJobsMemoryAdapter = class _IgniterJobsMemoryAdapter {
770
803
  removed++;
771
804
  }
772
805
  }
773
- this.jobsByQueue.set(queue, jobIds.filter((id) => this.jobsById.has(id)));
806
+ this.jobsByQueue.set(
807
+ queue,
808
+ jobIds.filter((id) => this.jobsById.has(id))
809
+ );
774
810
  return removed;
775
811
  }
776
812
  async cleanQueue(queue, options) {
@@ -790,7 +826,10 @@ var IgniterJobsMemoryAdapter = class _IgniterJobsMemoryAdapter {
790
826
  this.jobsById.delete(id);
791
827
  cleaned++;
792
828
  }
793
- this.jobsByQueue.set(queue, jobIds.filter((id) => this.jobsById.has(id)));
829
+ this.jobsByQueue.set(
830
+ queue,
831
+ jobIds.filter((id) => this.jobsById.has(id))
832
+ );
794
833
  return cleaned;
795
834
  }
796
835
  async obliterateQueue(queue, options) {
@@ -819,7 +858,8 @@ var IgniterJobsMemoryAdapter = class _IgniterJobsMemoryAdapter {
819
858
  for (const id of jobIds) {
820
859
  const job = this.jobsById.get(id);
821
860
  if (!job) continue;
822
- if (job.name === jobName && job.status === "waiting") job.status = "paused";
861
+ if (job.name === jobName && job.status === "waiting")
862
+ job.status = "paused";
823
863
  }
824
864
  }
825
865
  async resumeJobType(queue, jobName) {
@@ -827,7 +867,8 @@ var IgniterJobsMemoryAdapter = class _IgniterJobsMemoryAdapter {
827
867
  for (const id of jobIds) {
828
868
  const job = this.jobsById.get(id);
829
869
  if (!job) continue;
830
- if (job.name === jobName && job.status === "paused") job.status = "waiting";
870
+ if (job.name === jobName && job.status === "paused")
871
+ job.status = "waiting";
831
872
  }
832
873
  void this.kickWorkers(queue);
833
874
  }
@@ -836,19 +877,25 @@ var IgniterJobsMemoryAdapter = class _IgniterJobsMemoryAdapter {
836
877
  const statuses = filter?.status;
837
878
  const limit = filter?.limit ?? 100;
838
879
  const offset = filter?.offset ?? 0;
839
- const all = Array.from(this.jobsById.values()).filter((j) => queue ? j.queue === queue : true).filter((j) => statuses ? statuses.includes(j.status) : true).sort((a, b) => b.priority - a.priority || a.createdAt.getTime() - b.createdAt.getTime());
880
+ const all = Array.from(this.jobsById.values()).filter((j) => queue ? j.queue === queue : true).filter((j) => statuses ? statuses.includes(j.status) : true).sort(
881
+ (a, b) => b.priority - a.priority || a.createdAt.getTime() - b.createdAt.getTime()
882
+ );
840
883
  return all.slice(offset, offset + limit).map((j) => this.toSearchResult(j));
841
884
  }
842
885
  async searchQueues(filter) {
843
886
  const name = filter?.name;
844
887
  const isPaused = filter?.isPaused;
845
888
  const all = await this.listQueues();
846
- return all.filter((q) => name ? q.name.includes(name) : true).filter((q) => typeof isPaused === "boolean" ? q.isPaused === isPaused : true);
889
+ return all.filter((q) => name ? q.name.includes(name) : true).filter(
890
+ (q) => typeof isPaused === "boolean" ? q.isPaused === isPaused : true
891
+ );
847
892
  }
848
893
  async searchWorkers(filter) {
849
894
  const queue = filter?.queue;
850
895
  const isRunning = filter?.isRunning;
851
- return Array.from(this.workers.values()).filter((w) => queue ? w.queues.includes(queue) : true).filter((w) => typeof isRunning === "boolean" ? isRunning ? !w.closed : w.closed : true).map((w) => this.toWorkerHandle(w));
896
+ return Array.from(this.workers.values()).filter((w) => queue ? w.queues.includes(queue) : true).filter(
897
+ (w) => typeof isRunning === "boolean" ? isRunning ? !w.closed : w.closed : true
898
+ ).map((w) => this.toWorkerHandle(w));
852
899
  }
853
900
  async createWorker(config) {
854
901
  const workerId = IgniterJobsIdGenerator.generate("worker");
@@ -868,7 +915,8 @@ var IgniterJobsMemoryAdapter = class _IgniterJobsMemoryAdapter {
868
915
  }
869
916
  getWorkers() {
870
917
  const out = /* @__PURE__ */ new Map();
871
- for (const [id, state] of this.workers) out.set(id, this.toWorkerHandle(state));
918
+ for (const [id, state] of this.workers)
919
+ out.set(id, this.toWorkerHandle(state));
872
920
  return out;
873
921
  }
874
922
  async publishEvent(channel, payload) {
@@ -943,7 +991,9 @@ var IgniterJobsMemoryAdapter = class _IgniterJobsMemoryAdapter {
943
991
  }
944
992
  async kickWorkers(queue) {
945
993
  if (this.pausedQueues.has(queue)) return;
946
- const relevant = Array.from(this.workers.values()).filter((w) => !w.closed && !w.paused && (w.queues.length === 0 || w.queues.includes(queue)));
994
+ const relevant = Array.from(this.workers.values()).filter(
995
+ (w) => !w.closed && !w.paused && (w.queues.length === 0 || w.queues.includes(queue))
996
+ );
947
997
  if (relevant.length === 0) return;
948
998
  for (const w of relevant) {
949
999
  void this.processLoop(w, queue);
@@ -968,7 +1018,9 @@ var IgniterJobsMemoryAdapter = class _IgniterJobsMemoryAdapter {
968
1018
  }
969
1019
  nextJob(queue) {
970
1020
  const ids = this.jobsByQueue.get(queue) ?? [];
971
- const candidates = ids.map((id) => this.jobsById.get(id)).filter((j) => Boolean(j)).filter((j) => j.status === "waiting").sort((a, b) => b.priority - a.priority || a.createdAt.getTime() - b.createdAt.getTime());
1021
+ const candidates = ids.map((id) => this.jobsById.get(id)).filter((j) => Boolean(j)).filter((j) => j.status === "waiting").sort(
1022
+ (a, b) => b.priority - a.priority || a.createdAt.getTime() - b.createdAt.getTime()
1023
+ );
972
1024
  return candidates[0] ?? null;
973
1025
  }
974
1026
  async processJob(worker, job) {
@@ -979,8 +1031,13 @@ var IgniterJobsMemoryAdapter = class _IgniterJobsMemoryAdapter {
979
1031
  job.status = "active";
980
1032
  job.startedAt = /* @__PURE__ */ new Date();
981
1033
  job.attemptsMade += 1;
982
- job.logs.push({ timestamp: /* @__PURE__ */ new Date(), level: "info", message: "Job started" });
983
- if (worker.handlers?.onActive) await worker.handlers.onActive({ job: this.toSearchResult(job) });
1034
+ job.logs.push({
1035
+ timestamp: /* @__PURE__ */ new Date(),
1036
+ level: "info",
1037
+ message: "Job started"
1038
+ });
1039
+ if (worker.handlers?.onActive)
1040
+ await worker.handlers.onActive({ job: this.toSearchResult(job) });
984
1041
  const start = Date.now();
985
1042
  try {
986
1043
  const definition = this.registeredJobs.get(job.queue)?.get(job.name);
@@ -994,7 +1051,13 @@ var IgniterJobsMemoryAdapter = class _IgniterJobsMemoryAdapter {
994
1051
  await definition.onStart({
995
1052
  input: job.input,
996
1053
  context: {},
997
- job: { id: job.id, name: job.name, queue: job.queue, attemptsMade: job.attemptsMade, metadata: job.metadata },
1054
+ job: {
1055
+ id: job.id,
1056
+ name: job.name,
1057
+ queue: job.queue,
1058
+ attemptsMade: job.attemptsMade,
1059
+ metadata: job.metadata
1060
+ },
998
1061
  scope: job.scope,
999
1062
  startedAt: job.startedAt
1000
1063
  });
@@ -1002,7 +1065,13 @@ var IgniterJobsMemoryAdapter = class _IgniterJobsMemoryAdapter {
1002
1065
  const result = await definition.handler({
1003
1066
  input: job.input,
1004
1067
  context: {},
1005
- job: { id: job.id, name: job.name, queue: job.queue, attemptsMade: job.attemptsMade, metadata: job.metadata },
1068
+ job: {
1069
+ id: job.id,
1070
+ name: job.name,
1071
+ queue: job.queue,
1072
+ attemptsMade: job.attemptsMade,
1073
+ metadata: job.metadata
1074
+ },
1006
1075
  scope: job.scope
1007
1076
  });
1008
1077
  const duration = Date.now() - start;
@@ -1010,23 +1079,41 @@ var IgniterJobsMemoryAdapter = class _IgniterJobsMemoryAdapter {
1010
1079
  job.completedAt = /* @__PURE__ */ new Date();
1011
1080
  job.result = result;
1012
1081
  job.progress = 100;
1013
- job.logs.push({ timestamp: /* @__PURE__ */ new Date(), level: "info", message: `Job completed in ${duration}ms` });
1082
+ job.logs.push({
1083
+ timestamp: /* @__PURE__ */ new Date(),
1084
+ level: "info",
1085
+ message: `Job completed in ${duration}ms`
1086
+ });
1014
1087
  worker.metrics.processed += 1;
1015
1088
  worker.metrics.totalDuration += duration;
1016
1089
  if (definition.onSuccess) {
1017
1090
  await definition.onSuccess({
1018
1091
  input: job.input,
1019
1092
  context: {},
1020
- job: { id: job.id, name: job.name, queue: job.queue, attemptsMade: job.attemptsMade, metadata: job.metadata },
1093
+ job: {
1094
+ id: job.id,
1095
+ name: job.name,
1096
+ queue: job.queue,
1097
+ attemptsMade: job.attemptsMade,
1098
+ metadata: job.metadata
1099
+ },
1021
1100
  scope: job.scope,
1022
1101
  result,
1023
1102
  duration
1024
1103
  });
1025
1104
  }
1026
- if (worker.handlers?.onSuccess) await worker.handlers.onSuccess({ job: this.toSearchResult(job), result });
1105
+ if (worker.handlers?.onSuccess)
1106
+ await worker.handlers.onSuccess({
1107
+ job: this.toSearchResult(job),
1108
+ result
1109
+ });
1027
1110
  } catch (error) {
1028
1111
  job.error = error?.message ?? String(error);
1029
- job.logs.push({ timestamp: /* @__PURE__ */ new Date(), level: "error", message: job.error ?? "Unknown error" });
1112
+ job.logs.push({
1113
+ timestamp: /* @__PURE__ */ new Date(),
1114
+ level: "error",
1115
+ message: job.error ?? "Unknown error"
1116
+ });
1030
1117
  const isFinalAttempt = job.attemptsMade >= job.maxAttempts;
1031
1118
  if (isFinalAttempt) {
1032
1119
  job.status = "failed";
@@ -1037,13 +1124,23 @@ var IgniterJobsMemoryAdapter = class _IgniterJobsMemoryAdapter {
1037
1124
  await definition.onFailure({
1038
1125
  input: job.input,
1039
1126
  context: {},
1040
- job: { id: job.id, name: job.name, queue: job.queue, attemptsMade: job.attemptsMade, metadata: job.metadata },
1127
+ job: {
1128
+ id: job.id,
1129
+ name: job.name,
1130
+ queue: job.queue,
1131
+ attemptsMade: job.attemptsMade,
1132
+ metadata: job.metadata
1133
+ },
1041
1134
  scope: job.scope,
1042
1135
  error,
1043
1136
  isFinalAttempt: true
1044
1137
  });
1045
1138
  }
1046
- if (worker.handlers?.onFailure) await worker.handlers.onFailure({ job: this.toSearchResult(job), error });
1139
+ if (worker.handlers?.onFailure)
1140
+ await worker.handlers.onFailure({
1141
+ job: this.toSearchResult(job),
1142
+ error
1143
+ });
1047
1144
  } else {
1048
1145
  job.status = "waiting";
1049
1146
  void this.kickWorkers(job.queue);
@@ -1052,7 +1149,743 @@ var IgniterJobsMemoryAdapter = class _IgniterJobsMemoryAdapter {
1052
1149
  }
1053
1150
  };
1054
1151
 
1152
+ // src/adapters/sqlite.adapter.ts
1153
+ var IgniterJobsSQLiteAdapter = class _IgniterJobsSQLiteAdapter {
1154
+ constructor(options) {
1155
+ this.registeredJobs = /* @__PURE__ */ new Map();
1156
+ this.registeredCrons = /* @__PURE__ */ new Map();
1157
+ this.workers = /* @__PURE__ */ new Map();
1158
+ this.subscribers = /* @__PURE__ */ new Map();
1159
+ this.pausedQueues = /* @__PURE__ */ new Set();
1160
+ this.queues = {
1161
+ list: async () => this.listQueues(),
1162
+ get: async (name) => this.getQueueInfo(name),
1163
+ getJobCounts: async (name) => this.getQueueJobCounts(name),
1164
+ getJobs: async (name, filter) => {
1165
+ const statuses = filter?.status;
1166
+ const limit = filter?.limit ?? 100;
1167
+ const offset = filter?.offset ?? 0;
1168
+ const results = await this.searchJobs({
1169
+ queue: name,
1170
+ status: statuses,
1171
+ limit,
1172
+ offset
1173
+ });
1174
+ return results;
1175
+ },
1176
+ pause: async (name) => this.pauseQueue(name),
1177
+ resume: async (name) => this.resumeQueue(name),
1178
+ isPaused: async (name) => {
1179
+ const info = await this.getQueueInfo(name);
1180
+ return info?.isPaused ?? false;
1181
+ },
1182
+ drain: async (name) => this.drainQueue(name),
1183
+ clean: async (name, options) => this.cleanQueue(name, options),
1184
+ obliterate: async (name, options) => this.obliterateQueue(name, options)
1185
+ };
1186
+ this.options = {
1187
+ path: options.path,
1188
+ pollingInterval: options.pollingInterval ?? 500,
1189
+ enableWAL: options.enableWAL ?? true
1190
+ };
1191
+ this.client = {
1192
+ type: "sqlite",
1193
+ path: this.options.path
1194
+ };
1195
+ const Database = __require("better-sqlite3");
1196
+ this.db = new Database(this.options.path);
1197
+ this.initializeSchema();
1198
+ }
1199
+ /**
1200
+ * Creates a new SQLite adapter instance.
1201
+ *
1202
+ * @param options - Configuration options
1203
+ * @returns A new adapter instance
1204
+ *
1205
+ * @example
1206
+ * ```ts
1207
+ * // File-based database (persistent)
1208
+ * const adapter = IgniterJobsSQLiteAdapter.create({
1209
+ * path: './data/jobs.sqlite'
1210
+ * });
1211
+ *
1212
+ * // In-memory database (for testing)
1213
+ * const testAdapter = IgniterJobsSQLiteAdapter.create({
1214
+ * path: ':memory:'
1215
+ * });
1216
+ * ```
1217
+ */
1218
+ static create(options) {
1219
+ return new _IgniterJobsSQLiteAdapter(options);
1220
+ }
1221
+ initializeSchema() {
1222
+ if (this.options.enableWAL) {
1223
+ this.db.exec("PRAGMA journal_mode = WAL;");
1224
+ }
1225
+ this.db.exec(`
1226
+ CREATE TABLE IF NOT EXISTS jobs (
1227
+ id TEXT PRIMARY KEY,
1228
+ name TEXT NOT NULL,
1229
+ queue TEXT NOT NULL,
1230
+ input TEXT NOT NULL,
1231
+ status TEXT NOT NULL DEFAULT 'waiting',
1232
+ progress REAL NOT NULL DEFAULT 0,
1233
+ attempts_made INTEGER NOT NULL DEFAULT 0,
1234
+ max_attempts INTEGER NOT NULL DEFAULT 1,
1235
+ priority INTEGER NOT NULL DEFAULT 0,
1236
+ created_at TEXT NOT NULL,
1237
+ started_at TEXT,
1238
+ completed_at TEXT,
1239
+ scheduled_at TEXT,
1240
+ result TEXT,
1241
+ error TEXT,
1242
+ metadata TEXT,
1243
+ scope TEXT
1244
+ );
1245
+
1246
+ CREATE INDEX IF NOT EXISTS idx_jobs_queue_status ON jobs(queue, status);
1247
+ CREATE INDEX IF NOT EXISTS idx_jobs_status ON jobs(status);
1248
+ CREATE INDEX IF NOT EXISTS idx_jobs_scheduled_at ON jobs(scheduled_at);
1249
+ CREATE INDEX IF NOT EXISTS idx_jobs_priority ON jobs(priority DESC, created_at ASC);
1250
+ `);
1251
+ this.db.exec(`
1252
+ CREATE TABLE IF NOT EXISTS job_logs (
1253
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1254
+ job_id TEXT NOT NULL,
1255
+ timestamp TEXT NOT NULL,
1256
+ level TEXT NOT NULL,
1257
+ message TEXT NOT NULL,
1258
+ FOREIGN KEY (job_id) REFERENCES jobs(id) ON DELETE CASCADE
1259
+ );
1260
+
1261
+ CREATE INDEX IF NOT EXISTS idx_job_logs_job_id ON job_logs(job_id);
1262
+ `);
1263
+ this.db.exec(`
1264
+ CREATE TABLE IF NOT EXISTS paused_queues (
1265
+ name TEXT PRIMARY KEY
1266
+ );
1267
+ `);
1268
+ const pausedRows = this.db.prepare(
1269
+ "SELECT name FROM paused_queues"
1270
+ ).all();
1271
+ for (const row of pausedRows) {
1272
+ this.pausedQueues.add(row.name);
1273
+ }
1274
+ }
1275
+ registerJob(queueName, jobName, definition) {
1276
+ const queueJobs = this.registeredJobs.get(queueName) ?? /* @__PURE__ */ new Map();
1277
+ if (queueJobs.has(jobName)) {
1278
+ throw new IgniterJobsError({
1279
+ code: "JOBS_DUPLICATE_JOB",
1280
+ message: `Job "${jobName}" already registered for queue "${queueName}".`
1281
+ });
1282
+ }
1283
+ queueJobs.set(jobName, definition);
1284
+ this.registeredJobs.set(queueName, queueJobs);
1285
+ }
1286
+ registerCron(queueName, cronName, definition) {
1287
+ const queueCrons = this.registeredCrons.get(queueName) ?? /* @__PURE__ */ new Map();
1288
+ if (queueCrons.has(cronName)) {
1289
+ throw new IgniterJobsError({
1290
+ code: "JOBS_INVALID_CRON",
1291
+ message: `Cron "${cronName}" already registered for queue "${queueName}".`
1292
+ });
1293
+ }
1294
+ queueCrons.set(cronName, definition);
1295
+ this.registeredCrons.set(queueName, queueCrons);
1296
+ }
1297
+ async dispatch(params) {
1298
+ const jobId = params.jobId ?? IgniterJobsIdGenerator.generate("job");
1299
+ const maxAttempts = params.attempts ?? 1;
1300
+ const now = /* @__PURE__ */ new Date();
1301
+ let status = "waiting";
1302
+ let scheduledAt = null;
1303
+ if (this.pausedQueues.has(params.queue)) {
1304
+ status = "paused";
1305
+ } else if (params.delay && params.delay > 0) {
1306
+ status = "delayed";
1307
+ scheduledAt = new Date(now.getTime() + params.delay);
1308
+ }
1309
+ const stmt = this.db.prepare(`
1310
+ INSERT INTO jobs (
1311
+ id, name, queue, input, status, progress, attempts_made, max_attempts,
1312
+ priority, created_at, scheduled_at, metadata, scope
1313
+ ) VALUES (?, ?, ?, ?, ?, 0, 0, ?, ?, ?, ?, ?, ?)
1314
+ `);
1315
+ stmt.run(
1316
+ jobId,
1317
+ params.jobName,
1318
+ params.queue,
1319
+ JSON.stringify(params.input ?? {}),
1320
+ status,
1321
+ maxAttempts,
1322
+ params.priority ?? 0,
1323
+ now.toISOString(),
1324
+ scheduledAt?.toISOString() ?? null,
1325
+ params.metadata ? JSON.stringify(params.metadata) : null,
1326
+ params.scope ? JSON.stringify(params.scope) : null
1327
+ );
1328
+ if (params.delay && params.delay > 0) {
1329
+ setTimeout(() => {
1330
+ this.promoteDelayedJob(jobId, params.queue);
1331
+ }, params.delay);
1332
+ }
1333
+ return jobId;
1334
+ }
1335
+ promoteDelayedJob(jobId, queue) {
1336
+ if (this.pausedQueues.has(queue)) return;
1337
+ const stmt = this.db.prepare(`
1338
+ UPDATE jobs SET status = 'waiting', scheduled_at = NULL
1339
+ WHERE id = ? AND status = 'delayed'
1340
+ `);
1341
+ stmt.run(jobId);
1342
+ }
1343
+ async schedule(params) {
1344
+ if (params.at) {
1345
+ const delay = params.at.getTime() - Date.now();
1346
+ if (delay <= 0) {
1347
+ throw new IgniterJobsError({
1348
+ code: "JOBS_INVALID_SCHEDULE",
1349
+ message: "Scheduled time must be in the future."
1350
+ });
1351
+ }
1352
+ return this.dispatch({ ...params, delay });
1353
+ }
1354
+ if (params.cron || params.every) {
1355
+ return this.dispatch({ ...params, delay: params.delay ?? 0 });
1356
+ }
1357
+ return this.dispatch(params);
1358
+ }
1359
+ async getJob(jobId, queue) {
1360
+ let sql = "SELECT * FROM jobs WHERE id = ?";
1361
+ const params = [jobId];
1362
+ if (queue) {
1363
+ sql += " AND queue = ?";
1364
+ params.push(queue);
1365
+ }
1366
+ const row = this.db.prepare(sql).get(...params);
1367
+ if (!row) return null;
1368
+ return this.rowToSearchResult(row);
1369
+ }
1370
+ async getJobState(jobId, queue) {
1371
+ let sql = "SELECT status FROM jobs WHERE id = ?";
1372
+ const params = [jobId];
1373
+ if (queue) {
1374
+ sql += " AND queue = ?";
1375
+ params.push(queue);
1376
+ }
1377
+ const row = this.db.prepare(sql).get(...params);
1378
+ return row?.status ?? null;
1379
+ }
1380
+ async getJobLogs(jobId, queue) {
1381
+ if (queue) {
1382
+ const job = await this.getJob(jobId, queue);
1383
+ if (!job) return [];
1384
+ }
1385
+ const rows = this.db.prepare("SELECT * FROM job_logs WHERE job_id = ? ORDER BY timestamp ASC").all(jobId);
1386
+ return rows.map((row) => ({
1387
+ timestamp: new Date(row.timestamp),
1388
+ level: row.level,
1389
+ message: row.message
1390
+ }));
1391
+ }
1392
+ async getJobProgress(jobId, queue) {
1393
+ let sql = "SELECT progress FROM jobs WHERE id = ?";
1394
+ const params = [jobId];
1395
+ if (queue) {
1396
+ sql += " AND queue = ?";
1397
+ params.push(queue);
1398
+ }
1399
+ const row = this.db.prepare(sql).get(...params);
1400
+ return row?.progress ?? 0;
1401
+ }
1402
+ async retryJob(jobId, queue) {
1403
+ let sql = "SELECT id FROM jobs WHERE id = ?";
1404
+ const checkParams = [jobId];
1405
+ if (queue) {
1406
+ sql += " AND queue = ?";
1407
+ checkParams.push(queue);
1408
+ }
1409
+ const exists = this.db.prepare(sql).get(...checkParams);
1410
+ if (!exists) {
1411
+ throw new IgniterJobsError({
1412
+ code: "JOBS_NOT_FOUND",
1413
+ message: `Job "${jobId}" not found${queue ? ` in queue "${queue}"` : ""}.`
1414
+ });
1415
+ }
1416
+ let updateSql = "UPDATE jobs SET status = 'waiting', error = NULL, completed_at = NULL, progress = 0 WHERE id = ?";
1417
+ const updateParams = [jobId];
1418
+ if (queue) {
1419
+ updateSql = updateSql.replace("WHERE id = ?", "WHERE id = ? AND queue = ?");
1420
+ updateParams.push(queue);
1421
+ }
1422
+ this.db.prepare(updateSql).run(...updateParams);
1423
+ }
1424
+ async removeJob(jobId, queue) {
1425
+ let sql = "DELETE FROM jobs WHERE id = ?";
1426
+ const params = [jobId];
1427
+ if (queue) {
1428
+ sql += " AND queue = ?";
1429
+ params.push(queue);
1430
+ }
1431
+ this.db.prepare(sql).run(...params);
1432
+ }
1433
+ async promoteJob(jobId, queue) {
1434
+ let sql = "SELECT id, status, queue FROM jobs WHERE id = ?";
1435
+ const checkParams = [jobId];
1436
+ if (queue) {
1437
+ sql += " AND queue = ?";
1438
+ checkParams.push(queue);
1439
+ }
1440
+ const row = this.db.prepare(sql).get(...checkParams);
1441
+ if (!row) {
1442
+ throw new IgniterJobsError({
1443
+ code: "JOBS_NOT_FOUND",
1444
+ message: `Job "${jobId}" not found${queue ? ` in queue "${queue}"` : ""}.`
1445
+ });
1446
+ }
1447
+ if (row.status === "delayed" || row.status === "paused") {
1448
+ const newStatus = this.pausedQueues.has(row.queue) ? "paused" : "waiting";
1449
+ this.db.prepare("UPDATE jobs SET status = ?, scheduled_at = NULL WHERE id = ?").run(newStatus, jobId);
1450
+ }
1451
+ }
1452
+ async moveJobToFailed(jobId, reason, queue) {
1453
+ let sql = "SELECT id FROM jobs WHERE id = ?";
1454
+ const checkParams = [jobId];
1455
+ if (queue) {
1456
+ sql += " AND queue = ?";
1457
+ checkParams.push(queue);
1458
+ }
1459
+ const exists = this.db.prepare(sql).get(...checkParams);
1460
+ if (!exists) {
1461
+ throw new IgniterJobsError({
1462
+ code: "JOBS_NOT_FOUND",
1463
+ message: `Job "${jobId}" not found${queue ? ` in queue "${queue}"` : ""}.`
1464
+ });
1465
+ }
1466
+ this.db.prepare(`
1467
+ UPDATE jobs SET status = 'failed', error = ?, completed_at = ?
1468
+ WHERE id = ?
1469
+ `).run(reason, (/* @__PURE__ */ new Date()).toISOString(), jobId);
1470
+ }
1471
+ async retryManyJobs(jobIds, queue) {
1472
+ await Promise.all(jobIds.map((id) => this.retryJob(id, queue)));
1473
+ }
1474
+ async removeManyJobs(jobIds, queue) {
1475
+ await Promise.all(jobIds.map((id) => this.removeJob(id, queue)));
1476
+ }
1477
+ async getQueueInfo(queue) {
1478
+ const counts = await this.getQueueJobCounts(queue);
1479
+ return {
1480
+ name: queue,
1481
+ isPaused: this.pausedQueues.has(queue),
1482
+ jobCounts: counts
1483
+ };
1484
+ }
1485
+ async getQueueJobCounts(queue) {
1486
+ const counts = {
1487
+ waiting: 0,
1488
+ active: 0,
1489
+ completed: 0,
1490
+ failed: 0,
1491
+ delayed: 0,
1492
+ paused: 0
1493
+ };
1494
+ const rows = this.db.prepare(
1495
+ "SELECT status, COUNT(*) as count FROM jobs WHERE queue = ? GROUP BY status"
1496
+ ).all(queue);
1497
+ for (const row of rows) {
1498
+ if (row.status in counts) {
1499
+ counts[row.status] = row.count;
1500
+ }
1501
+ }
1502
+ return counts;
1503
+ }
1504
+ async listQueues() {
1505
+ const jobQueues = this.db.prepare("SELECT DISTINCT queue FROM jobs").all().map((r) => r.queue);
1506
+ const allQueues = /* @__PURE__ */ new Set([
1507
+ ...jobQueues,
1508
+ ...this.registeredJobs.keys(),
1509
+ ...this.registeredCrons.keys()
1510
+ ]);
1511
+ const result = [];
1512
+ for (const q of allQueues) {
1513
+ const info = await this.getQueueInfo(q);
1514
+ if (info) result.push(info);
1515
+ }
1516
+ return result;
1517
+ }
1518
+ async pauseQueue(queue) {
1519
+ this.pausedQueues.add(queue);
1520
+ this.db.prepare("INSERT OR IGNORE INTO paused_queues (name) VALUES (?)").run(queue);
1521
+ this.db.prepare(
1522
+ "UPDATE jobs SET status = 'paused' WHERE queue = ? AND status = 'waiting'"
1523
+ ).run(queue);
1524
+ }
1525
+ async resumeQueue(queue) {
1526
+ this.pausedQueues.delete(queue);
1527
+ this.db.prepare("DELETE FROM paused_queues WHERE name = ?").run(queue);
1528
+ this.db.prepare(
1529
+ "UPDATE jobs SET status = 'waiting' WHERE queue = ? AND status = 'paused'"
1530
+ ).run(queue);
1531
+ }
1532
+ async drainQueue(queue) {
1533
+ const result = this.db.prepare(
1534
+ "DELETE FROM jobs WHERE queue = ? AND status IN ('waiting', 'paused')"
1535
+ ).run(queue);
1536
+ return result.changes;
1537
+ }
1538
+ async cleanQueue(queue, options) {
1539
+ const statuses = Array.isArray(options.status) ? options.status : [options.status];
1540
+ const olderThan = options.olderThan ?? 0;
1541
+ const limit = options.limit ?? Number.POSITIVE_INFINITY;
1542
+ const cutoffTime = new Date(Date.now() - olderThan).toISOString();
1543
+ const statusPlaceholders = statuses.map(() => "?").join(", ");
1544
+ let sql = `
1545
+ DELETE FROM jobs WHERE id IN (
1546
+ SELECT id FROM jobs
1547
+ WHERE queue = ? AND status IN (${statusPlaceholders}) AND created_at < ?
1548
+ ORDER BY created_at ASC
1549
+ LIMIT ?
1550
+ )
1551
+ `;
1552
+ const result = this.db.prepare(sql).run(
1553
+ queue,
1554
+ ...statuses,
1555
+ cutoffTime,
1556
+ limit === Number.POSITIVE_INFINITY ? -1 : limit
1557
+ );
1558
+ return result.changes;
1559
+ }
1560
+ async obliterateQueue(queue, _options) {
1561
+ this.db.prepare("DELETE FROM jobs WHERE queue = ?").run(queue);
1562
+ this.registeredJobs.delete(queue);
1563
+ this.registeredCrons.delete(queue);
1564
+ this.pausedQueues.delete(queue);
1565
+ this.db.prepare("DELETE FROM paused_queues WHERE name = ?").run(queue);
1566
+ }
1567
+ async retryAllInQueue(queue) {
1568
+ const result = this.db.prepare(`
1569
+ UPDATE jobs SET status = 'waiting', error = NULL, completed_at = NULL, progress = 0
1570
+ WHERE queue = ? AND status = 'failed'
1571
+ `).run(queue);
1572
+ return result.changes;
1573
+ }
1574
+ async pauseJobType(queue, jobName) {
1575
+ this.db.prepare(
1576
+ "UPDATE jobs SET status = 'paused' WHERE queue = ? AND name = ? AND status = 'waiting'"
1577
+ ).run(queue, jobName);
1578
+ }
1579
+ async resumeJobType(queue, jobName) {
1580
+ this.db.prepare(
1581
+ "UPDATE jobs SET status = 'waiting' WHERE queue = ? AND name = ? AND status = 'paused'"
1582
+ ).run(queue, jobName);
1583
+ }
1584
+ async searchJobs(filter) {
1585
+ const queue = filter?.queue;
1586
+ const statuses = filter?.status;
1587
+ const limit = filter?.limit ?? 100;
1588
+ const offset = filter?.offset ?? 0;
1589
+ let sql = "SELECT * FROM jobs WHERE 1=1";
1590
+ const params = [];
1591
+ if (queue) {
1592
+ sql += " AND queue = ?";
1593
+ params.push(queue);
1594
+ }
1595
+ if (statuses && statuses.length > 0) {
1596
+ const placeholders = statuses.map(() => "?").join(", ");
1597
+ sql += ` AND status IN (${placeholders})`;
1598
+ params.push(...statuses);
1599
+ }
1600
+ sql += " ORDER BY priority DESC, created_at ASC LIMIT ? OFFSET ?";
1601
+ params.push(limit, offset);
1602
+ const rows = this.db.prepare(sql).all(...params);
1603
+ return rows.map((row) => this.rowToSearchResult(row));
1604
+ }
1605
+ async searchQueues(filter) {
1606
+ const name = filter?.name;
1607
+ const isPaused = filter?.isPaused;
1608
+ const all = await this.listQueues();
1609
+ return all.filter((q) => name ? q.name.includes(name) : true).filter(
1610
+ (q) => typeof isPaused === "boolean" ? q.isPaused === isPaused : true
1611
+ );
1612
+ }
1613
+ async searchWorkers(filter) {
1614
+ const queue = filter?.queue;
1615
+ const isRunning = filter?.isRunning;
1616
+ return Array.from(this.workers.values()).filter((w) => queue ? w.queues.includes(queue) : true).filter(
1617
+ (w) => typeof isRunning === "boolean" ? isRunning ? !w.closed : w.closed : true
1618
+ ).map((w) => this.toWorkerHandle(w));
1619
+ }
1620
+ async createWorker(config) {
1621
+ const workerId = IgniterJobsIdGenerator.generate("worker");
1622
+ const state = {
1623
+ id: workerId,
1624
+ queues: config.queues ?? [],
1625
+ concurrency: config.concurrency ?? 1,
1626
+ paused: false,
1627
+ closed: false,
1628
+ startedAt: /* @__PURE__ */ new Date(),
1629
+ metrics: { processed: 0, failed: 0, totalDuration: 0 },
1630
+ handlers: config.handlers,
1631
+ activeJobs: 0
1632
+ };
1633
+ this.workers.set(workerId, state);
1634
+ this.startPollingLoop(state);
1635
+ return this.toWorkerHandle(state);
1636
+ }
1637
+ getWorkers() {
1638
+ const out = /* @__PURE__ */ new Map();
1639
+ for (const [id, state] of this.workers) {
1640
+ out.set(id, this.toWorkerHandle(state));
1641
+ }
1642
+ return out;
1643
+ }
1644
+ async publishEvent(channel, payload) {
1645
+ const handlers = this.subscribers.get(channel);
1646
+ if (!handlers) return;
1647
+ await Promise.all(Array.from(handlers).map(async (h) => h(payload)));
1648
+ }
1649
+ async subscribeEvent(channel, handler) {
1650
+ const set = this.subscribers.get(channel) ?? /* @__PURE__ */ new Set();
1651
+ set.add(handler);
1652
+ this.subscribers.set(channel, set);
1653
+ return async () => {
1654
+ const current = this.subscribers.get(channel);
1655
+ if (!current) return;
1656
+ current.delete(handler);
1657
+ if (current.size === 0) this.subscribers.delete(channel);
1658
+ };
1659
+ }
1660
+ async shutdown() {
1661
+ for (const worker of this.workers.values()) {
1662
+ worker.closed = true;
1663
+ if (worker.pollingTimer) {
1664
+ clearInterval(worker.pollingTimer);
1665
+ }
1666
+ }
1667
+ this.workers.clear();
1668
+ this.subscribers.clear();
1669
+ this.db.close();
1670
+ }
1671
+ // ─────────────────────────────────────────────────────────────────────────────
1672
+ // Private helpers
1673
+ // ─────────────────────────────────────────────────────────────────────────────
1674
+ rowToSearchResult(row) {
1675
+ return {
1676
+ id: row.id,
1677
+ name: row.name,
1678
+ queue: row.queue,
1679
+ status: row.status,
1680
+ input: JSON.parse(row.input),
1681
+ result: row.result ? JSON.parse(row.result) : void 0,
1682
+ error: row.error ?? void 0,
1683
+ progress: row.progress,
1684
+ attemptsMade: row.attempts_made,
1685
+ priority: row.priority,
1686
+ createdAt: new Date(row.created_at),
1687
+ startedAt: row.started_at ? new Date(row.started_at) : void 0,
1688
+ completedAt: row.completed_at ? new Date(row.completed_at) : void 0,
1689
+ metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
1690
+ scope: row.scope ? JSON.parse(row.scope) : void 0
1691
+ };
1692
+ }
1693
+ toWorkerHandle(worker) {
1694
+ return {
1695
+ id: worker.id,
1696
+ queues: worker.queues,
1697
+ pause: async () => {
1698
+ worker.paused = true;
1699
+ },
1700
+ resume: async () => {
1701
+ worker.paused = false;
1702
+ },
1703
+ close: async () => {
1704
+ worker.closed = true;
1705
+ if (worker.pollingTimer) {
1706
+ clearInterval(worker.pollingTimer);
1707
+ }
1708
+ },
1709
+ isRunning: () => !worker.closed && !worker.paused,
1710
+ isPaused: () => worker.paused,
1711
+ isClosed: () => worker.closed,
1712
+ getMetrics: async () => this.toWorkerMetrics(worker)
1713
+ };
1714
+ }
1715
+ toWorkerMetrics(worker) {
1716
+ const uptime = Date.now() - worker.startedAt.getTime();
1717
+ const processed = worker.metrics.processed;
1718
+ return {
1719
+ processed,
1720
+ failed: worker.metrics.failed,
1721
+ avgDuration: processed > 0 ? worker.metrics.totalDuration / processed : 0,
1722
+ concurrency: worker.concurrency,
1723
+ uptime
1724
+ };
1725
+ }
1726
+ startPollingLoop(worker) {
1727
+ const poll = () => {
1728
+ if (worker.closed || worker.paused) return;
1729
+ void this.processNextJobs(worker);
1730
+ };
1731
+ poll();
1732
+ worker.pollingTimer = setInterval(poll, this.options.pollingInterval);
1733
+ }
1734
+ async processNextJobs(worker) {
1735
+ if (worker.closed || worker.paused) return;
1736
+ const availableSlots = worker.concurrency - worker.activeJobs;
1737
+ if (availableSlots <= 0) return;
1738
+ const queueFilter = worker.queues.length > 0 ? `queue IN (${worker.queues.map(() => "?").join(", ")})` : "1=1";
1739
+ const params = worker.queues.length > 0 ? [...worker.queues, availableSlots] : [availableSlots];
1740
+ const rows = this.db.prepare(`
1741
+ SELECT * FROM jobs
1742
+ WHERE status = 'waiting' AND ${queueFilter}
1743
+ ORDER BY priority DESC, created_at ASC
1744
+ LIMIT ?
1745
+ `).all(...params);
1746
+ for (const row of rows) {
1747
+ if (worker.closed || worker.paused) break;
1748
+ if (worker.activeJobs >= worker.concurrency) break;
1749
+ const claimed = this.db.prepare(`
1750
+ UPDATE jobs SET status = 'active', started_at = ?
1751
+ WHERE id = ? AND status = 'waiting'
1752
+ `).run((/* @__PURE__ */ new Date()).toISOString(), row.id);
1753
+ if (claimed.changes === 0) continue;
1754
+ worker.activeJobs++;
1755
+ void this.processJob(worker, row.id).finally(() => {
1756
+ worker.activeJobs--;
1757
+ if (worker.handlers?.onIdle && worker.activeJobs === 0) {
1758
+ void worker.handlers.onIdle();
1759
+ }
1760
+ });
1761
+ }
1762
+ }
1763
+ async processJob(worker, jobId) {
1764
+ const row = this.db.prepare("SELECT * FROM jobs WHERE id = ?").get(jobId);
1765
+ if (!row) return;
1766
+ const job = this.rowToSearchResult(row);
1767
+ this.addJobLog(jobId, "info", "Job started");
1768
+ if (worker.handlers?.onActive) {
1769
+ await worker.handlers.onActive({ job });
1770
+ }
1771
+ const start = Date.now();
1772
+ try {
1773
+ const definition = this.registeredJobs.get(row.queue)?.get(row.name);
1774
+ if (!definition) {
1775
+ throw new IgniterJobsError({
1776
+ code: "JOBS_NOT_REGISTERED",
1777
+ message: `Job "${row.name}" is not registered for queue "${row.queue}".`
1778
+ });
1779
+ }
1780
+ this.db.prepare("UPDATE jobs SET attempts_made = attempts_made + 1 WHERE id = ?").run(jobId);
1781
+ if (definition.onStart) {
1782
+ await definition.onStart({
1783
+ input: job.input,
1784
+ context: {},
1785
+ job: {
1786
+ id: job.id,
1787
+ name: job.name,
1788
+ queue: job.queue,
1789
+ attemptsMade: job.attemptsMade + 1,
1790
+ metadata: job.metadata
1791
+ },
1792
+ scope: job.scope,
1793
+ startedAt: /* @__PURE__ */ new Date()
1794
+ });
1795
+ }
1796
+ const result = await definition.handler({
1797
+ input: job.input,
1798
+ context: {},
1799
+ job: {
1800
+ id: job.id,
1801
+ name: job.name,
1802
+ queue: job.queue,
1803
+ attemptsMade: job.attemptsMade + 1,
1804
+ metadata: job.metadata
1805
+ },
1806
+ scope: job.scope
1807
+ });
1808
+ const duration = Date.now() - start;
1809
+ this.db.prepare(`
1810
+ UPDATE jobs SET status = 'completed', completed_at = ?, result = ?, progress = 100
1811
+ WHERE id = ?
1812
+ `).run((/* @__PURE__ */ new Date()).toISOString(), JSON.stringify(result), jobId);
1813
+ this.addJobLog(jobId, "info", `Job completed in ${duration}ms`);
1814
+ worker.metrics.processed++;
1815
+ worker.metrics.totalDuration += duration;
1816
+ if (definition.onSuccess) {
1817
+ await definition.onSuccess({
1818
+ input: job.input,
1819
+ context: {},
1820
+ job: {
1821
+ id: job.id,
1822
+ name: job.name,
1823
+ queue: job.queue,
1824
+ attemptsMade: job.attemptsMade + 1,
1825
+ metadata: job.metadata
1826
+ },
1827
+ scope: job.scope,
1828
+ result,
1829
+ duration
1830
+ });
1831
+ }
1832
+ if (worker.handlers?.onSuccess) {
1833
+ const updatedJob = await this.getJob(jobId);
1834
+ if (updatedJob) {
1835
+ await worker.handlers.onSuccess({ job: updatedJob, result });
1836
+ }
1837
+ }
1838
+ } catch (error) {
1839
+ const errorMessage = error?.message ?? String(error);
1840
+ this.addJobLog(jobId, "error", errorMessage);
1841
+ const current = this.db.prepare(
1842
+ "SELECT attempts_made, max_attempts FROM jobs WHERE id = ?"
1843
+ ).get(jobId);
1844
+ const isFinalAttempt = (current?.attempts_made ?? 0) >= (current?.max_attempts ?? 1);
1845
+ if (isFinalAttempt) {
1846
+ this.db.prepare(`
1847
+ UPDATE jobs SET status = 'failed', error = ?, completed_at = ?
1848
+ WHERE id = ?
1849
+ `).run(errorMessage, (/* @__PURE__ */ new Date()).toISOString(), jobId);
1850
+ worker.metrics.failed++;
1851
+ const definition = this.registeredJobs.get(row.queue)?.get(row.name);
1852
+ if (definition?.onFailure) {
1853
+ await definition.onFailure({
1854
+ input: job.input,
1855
+ context: {},
1856
+ job: {
1857
+ id: job.id,
1858
+ name: job.name,
1859
+ queue: job.queue,
1860
+ attemptsMade: current?.attempts_made ?? 1,
1861
+ metadata: job.metadata
1862
+ },
1863
+ scope: job.scope,
1864
+ error,
1865
+ isFinalAttempt: true
1866
+ });
1867
+ }
1868
+ if (worker.handlers?.onFailure) {
1869
+ const updatedJob = await this.getJob(jobId);
1870
+ if (updatedJob) {
1871
+ await worker.handlers.onFailure({ job: updatedJob, error });
1872
+ }
1873
+ }
1874
+ } else {
1875
+ this.db.prepare("UPDATE jobs SET status = 'waiting' WHERE id = ?").run(jobId);
1876
+ }
1877
+ }
1878
+ }
1879
+ addJobLog(jobId, level, message) {
1880
+ this.db.prepare(`
1881
+ INSERT INTO job_logs (job_id, timestamp, level, message)
1882
+ VALUES (?, ?, ?, ?)
1883
+ `).run(jobId, (/* @__PURE__ */ new Date()).toISOString(), level, message);
1884
+ }
1885
+ };
1886
+
1055
1887
  exports.IgniterJobsBullMQAdapter = IgniterJobsBullMQAdapter;
1056
1888
  exports.IgniterJobsMemoryAdapter = IgniterJobsMemoryAdapter;
1889
+ exports.IgniterJobsSQLiteAdapter = IgniterJobsSQLiteAdapter;
1057
1890
  //# sourceMappingURL=index.js.map
1058
1891
  //# sourceMappingURL=index.js.map