@igniter-js/jobs 0.1.13 → 0.1.15

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/AGENTS.md +119 -243
  2. package/README.md +352 -158
  3. package/dist/{adapter-CXZxomI9.d.mts → adapter-DDyMVche.d.mts} +125 -20
  4. package/dist/{adapter-CXZxomI9.d.ts → adapter-DDyMVche.d.ts} +125 -20
  5. package/dist/adapters/bun.d.mts +101 -0
  6. package/dist/adapters/bun.d.ts +101 -0
  7. package/dist/adapters/bun.js +1048 -0
  8. package/dist/adapters/bun.js.map +1 -0
  9. package/dist/adapters/bun.mjs +1046 -0
  10. package/dist/adapters/bun.mjs.map +1 -0
  11. package/dist/adapters/{memory.adapter.d.ts → mock.d.mts} +7 -3
  12. package/dist/adapters/{memory.adapter.d.mts → mock.d.ts} +7 -3
  13. package/dist/adapters/{memory.adapter.js → mock.js} +122 -25
  14. package/dist/adapters/mock.js.map +1 -0
  15. package/dist/adapters/{memory.adapter.mjs → mock.mjs} +122 -25
  16. package/dist/adapters/mock.mjs.map +1 -0
  17. package/dist/adapters/{bullmq.adapter.d.mts → node.d.mts} +8 -3
  18. package/dist/adapters/{bullmq.adapter.d.ts → node.d.ts} +8 -3
  19. package/dist/adapters/{bullmq.adapter.js → node.js} +194 -40
  20. package/dist/adapters/node.js.map +1 -0
  21. package/dist/adapters/{bullmq.adapter.mjs → node.mjs} +194 -40
  22. package/dist/adapters/node.mjs.map +1 -0
  23. package/dist/index.d.mts +41 -38
  24. package/dist/index.d.ts +41 -38
  25. package/dist/index.js +145 -1856
  26. package/dist/index.js.map +1 -1
  27. package/dist/index.mjs +146 -1854
  28. package/dist/index.mjs.map +1 -1
  29. package/package.json +29 -41
  30. package/CHANGELOG.md +0 -13
  31. package/dist/adapters/bullmq.adapter.js.map +0 -1
  32. package/dist/adapters/bullmq.adapter.mjs.map +0 -1
  33. package/dist/adapters/index.d.mts +0 -143
  34. package/dist/adapters/index.d.ts +0 -143
  35. package/dist/adapters/index.js +0 -1891
  36. package/dist/adapters/index.js.map +0 -1
  37. package/dist/adapters/index.mjs +0 -1887
  38. package/dist/adapters/index.mjs.map +0 -1
  39. package/dist/adapters/memory.adapter.js.map +0 -1
  40. package/dist/adapters/memory.adapter.mjs.map +0 -1
package/README.md CHANGED
@@ -25,6 +25,7 @@ Background processing is notoriously hard to keep reliable and observable. `@ign
25
25
  - ✅ **Runtime validation** — Zod or Standard Schema V1 input validation.
26
26
  - ✅ **Scoped jobs** — First-class multi-tenant support via `scope()`.
27
27
  - ✅ **Observability built-in** — Telemetry events and pub/sub job lifecycle events.
28
+ - ✅ **Typed job streams** — Emit live per-job stream events with optional persistence and replay.
28
29
  - ✅ **Adapter-based backends** — In-memory for tests, SQLite for local, BullMQ for production.
29
30
 
30
31
  ---
@@ -57,7 +58,7 @@ bun add @igniter-js/jobs zod
57
58
 
58
59
  ```typescript
59
60
  import { IgniterJobs, IgniterQueue } from "@igniter-js/jobs";
60
- import { IgniterJobsMemoryAdapter } from "@igniter-js/jobs/adapters";
61
+ import { IgniterJobsMemoryAdapter } from "@igniter-js/jobs/adapters/mock";
61
62
  import { z } from "zod";
62
63
 
63
64
  type AppContext = { mailer: { sendWelcome: (email: string) => Promise<void> } };
@@ -109,7 +110,7 @@ await jobs.email.sendWelcome.dispatch({ input: { email: "user@example.com" } });
109
110
 
110
111
  ┌─────────────────────────────────────────────────────────────┐
111
112
  │ Adapter Layer │
112
- │ Memory Adapter • SQLite Adapter • BullMQ Adapter
113
+ │ Memory Adapter • Bun SQLite Adapter • BullMQ Adapter
113
114
  └────────────┬────────────────────────────────────────────────┘
114
115
 
115
116
 
@@ -184,8 +185,8 @@ await jobs.email.sendWelcome.dispatch({ input: { email: "user@example.com" } });
184
185
  48. Graceful shutdown in process
185
186
  49. Global events to analytics
186
187
  50. Event filtering pattern
187
- 51. SQLite adapter usage
188
- 52. SQLite adapter persistence
188
+ 51. Bun SQLite adapter usage
189
+ 52. Bun SQLite adapter persistence
189
190
  53. Memory adapter testing
190
191
  54. BullMQ adapter usage
191
192
  55. Custom adapter skeleton
@@ -229,7 +230,7 @@ const uploadQueue = IgniterQueue.create("uploads")
229
230
 
230
231
  ```typescript
231
232
  import { IgniterJobs } from "@igniter-js/jobs";
232
- import { IgniterJobsMemoryAdapter } from "@igniter-js/jobs/adapters";
233
+ import { IgniterJobsMemoryAdapter } from "@igniter-js/jobs/adapters/mock";
233
234
 
234
235
  const jobs = IgniterJobs.create()
235
236
  .withAdapter(IgniterJobsMemoryAdapter.create())
@@ -402,16 +403,7 @@ await jobs.email.sendWelcome.many(["job-1", "job-2"]).retry();
402
403
  await jobs.email.sendWelcome.many(["job-1", "job-2"]).remove();
403
404
  ```
404
405
 
405
- ### 22) Pause/Resume Job Type
406
-
407
- ```typescript
408
- await jobs.email.sendWelcome.pause();
409
- await jobs.email.sendWelcome.resume();
410
- ```
411
-
412
- > Note: The BullMQ adapter throws `JOBS_QUEUE_OPERATION_FAILED` for pause/resume job type.
413
-
414
- ### 23) Create a Worker
406
+ ### 22) Create a Worker
415
407
 
416
408
  ```typescript
417
409
  const worker = await jobs.worker
@@ -645,7 +637,42 @@ const queue = IgniterQueue.create("import")
645
637
  .build();
646
638
  ```
647
639
 
648
- ### 44) Job Logs Inspection
640
+ ### 44) Typed Job Streams
641
+
642
+ ```typescript
643
+ const queue = IgniterQueue.create("ai")
644
+ .addJob("generate", {
645
+ stream: {
646
+ persistence: { enabled: true, maxEvents: 1000 },
647
+ events: {
648
+ "text-delta": z.string(),
649
+ status: z.object({ phase: z.string() }),
650
+ done: z.object({ finishReason: z.string() }),
651
+ },
652
+ },
653
+ handler: async ({ job }) => {
654
+ await job.stream.emit("status", { phase: "thinking" });
655
+ await job.stream.emit("text-delta", "Hello");
656
+ await job.stream.emit("done", { finishReason: "stop" });
657
+ return { ok: true };
658
+ },
659
+ })
660
+ .build();
661
+
662
+ const jobId = await jobs.ai.generate.dispatch({ input: {} });
663
+
664
+ const off = await jobs.ai.generate
665
+ .get(jobId)
666
+ .stream()
667
+ .subscribe((event) => {
668
+ console.log(event.type, event.data);
669
+ });
670
+
671
+ const history = await jobs.ai.generate.get(jobId).stream().read({ limit: 100 });
672
+ await off();
673
+ ```
674
+
675
+ ### 45) Job Logs Inspection
649
676
 
650
677
  ```typescript
651
678
  const logs = await jobs.email.sendWelcome.get("job-id").logs();
@@ -654,7 +681,7 @@ for (const entry of logs) {
654
681
  }
655
682
  ```
656
683
 
657
- ### 45) Queue List with Paging
684
+ ### 46) Queue List with Paging
658
685
 
659
686
  ```typescript
660
687
  const jobsInQueue = await jobs.email.list({
@@ -664,7 +691,7 @@ const jobsInQueue = await jobs.email.list({
664
691
  });
665
692
  ```
666
693
 
667
- ### 46) Worker Limiter Pattern
694
+ ### 47) Worker Limiter Pattern
668
695
 
669
696
  ```typescript
670
697
  const worker = await jobs.worker
@@ -674,14 +701,14 @@ const worker = await jobs.worker
674
701
  .start();
675
702
  ```
676
703
 
677
- ### 47) Worker Sharding by Queue
704
+ ### 48) Worker Sharding by Queue
678
705
 
679
706
  ```typescript
680
707
  const workerA = await jobs.worker.create().addQueue("email").start();
681
708
  const workerB = await jobs.worker.create().addQueue("analytics").start();
682
709
  ```
683
710
 
684
- ### 48) Graceful Shutdown in Process
711
+ ### 49) Graceful Shutdown in Process
685
712
 
686
713
  ```typescript
687
714
  process.on("SIGINT", async () => {
@@ -712,21 +739,24 @@ const unsubscribe = await jobs.subscribe((event) => {
712
739
  });
713
740
  ```
714
741
 
715
- ### 51) SQLite Adapter Usage
742
+ ### 51) Bun SQLite Adapter Usage
716
743
 
717
744
  ```typescript
718
- const adapter = IgniterJobsSQLiteAdapter.create({
745
+ const adapter = IgniterJobsBunSQLiteAdapter.create({
719
746
  path: "./jobs.sqlite",
720
- pollingInterval: 500,
721
- enableWAL: true,
747
+ durable: true,
722
748
  });
723
749
  ```
724
750
 
725
- ### 52) SQLite Adapter Persistence
751
+ ### 52) Bun SQLite Adapter Persistence
726
752
 
727
753
  ```typescript
728
- const adapter = IgniterJobsSQLiteAdapter.create({ path: "./jobs.sqlite" });
729
- await adapter.dispatch({ queue: "emails", jobName: "send", input: { id: "1" } });
754
+ const adapter = IgniterJobsBunSQLiteAdapter.create({ path: "./jobs.sqlite" });
755
+ await adapter.dispatch({
756
+ queue: "emails",
757
+ jobName: "send",
758
+ input: { id: "1" },
759
+ });
730
760
  await adapter.shutdown();
731
761
  ```
732
762
 
@@ -748,39 +778,120 @@ const adapter = IgniterJobsBullMQAdapter.create({ redis });
748
778
  class CustomAdapter implements IgniterJobsAdapter {
749
779
  readonly client = {};
750
780
  readonly queues = null as any;
751
- async dispatch(params: IgniterJobsAdapterDispatchParams) { return "job-id"; }
752
- async schedule(params: IgniterJobsAdapterScheduleParams) { return "job-id"; }
753
- async getJob(jobId: string) { return null; }
754
- async getJobState(jobId: string) { return null; }
755
- async getJobLogs(jobId: string) { return []; }
756
- async getJobProgress(jobId: string) { return 0; }
757
- async retryJob(jobId: string) { /* ... */ }
758
- async removeJob(jobId: string) { /* ... */ }
759
- async promoteJob(jobId: string) { /* ... */ }
760
- async moveJobToFailed(jobId: string, reason: string) { /* ... */ }
761
- async retryManyJobs(jobIds: string[]) { /* ... */ }
762
- async removeManyJobs(jobIds: string[]) { /* ... */ }
763
- async getQueueInfo(queue: string) { return null; }
764
- async getQueueJobCounts(queue: string) { return { waiting: 0, active: 0, completed: 0, failed: 0, delayed: 0, paused: 0 }; }
765
- async listQueues() { return []; }
766
- async pauseQueue(queue: string) { /* ... */ }
767
- async resumeQueue(queue: string) { /* ... */ }
768
- async drainQueue(queue: string) { return 0; }
769
- async cleanQueue(queue: string, options: IgniterJobsQueueCleanOptions) { return 0; }
770
- async obliterateQueue(queue: string, options?: { force?: boolean }) { /* ... */ }
771
- async retryAllInQueue(queue: string) { return 0; }
772
- async pauseJobType(queue: string, jobName: string) { /* ... */ }
773
- async resumeJobType(queue: string, jobName: string) { /* ... */ }
774
- async searchJobs(filter: Record<string, unknown>) { return []; }
775
- async searchQueues(filter: Record<string, unknown>) { return []; }
776
- async searchWorkers(filter: Record<string, unknown>) { return []; }
777
- async createWorker(config: IgniterJobsWorkerBuilderConfig) { return {} as IgniterJobsWorkerHandle; }
778
- getWorkers() { return new Map(); }
779
- async publishEvent(channel: string, payload: unknown) { /* ... */ }
780
- async subscribeEvent(channel: string, handler: IgniterJobsEventHandler) { return async () => {}; }
781
- registerJob(queueName: string, jobName: string, definition: IgniterJobDefinition<any, any, any>) { /* ... */ }
782
- registerCron(queueName: string, cronName: string, definition: IgniterCronDefinition<any, any>) { /* ... */ }
783
- async shutdown() { /* ... */ }
781
+ async dispatch(params: IgniterJobsAdapterDispatchParams) {
782
+ return "job-id";
783
+ }
784
+ async schedule(params: IgniterJobsAdapterScheduleParams) {
785
+ return "job-id";
786
+ }
787
+ async getJob(jobId: string) {
788
+ return null;
789
+ }
790
+ async getJobState(jobId: string) {
791
+ return null;
792
+ }
793
+ async getJobLogs(jobId: string) {
794
+ return [];
795
+ }
796
+ async getJobProgress(jobId: string) {
797
+ return 0;
798
+ }
799
+ async retryJob(jobId: string) {
800
+ /* ... */
801
+ }
802
+ async removeJob(jobId: string) {
803
+ /* ... */
804
+ }
805
+ async promoteJob(jobId: string) {
806
+ /* ... */
807
+ }
808
+ async moveJobToFailed(jobId: string, reason: string) {
809
+ /* ... */
810
+ }
811
+ async retryManyJobs(jobIds: string[]) {
812
+ /* ... */
813
+ }
814
+ async removeManyJobs(jobIds: string[]) {
815
+ /* ... */
816
+ }
817
+ async getQueueInfo(queue: string) {
818
+ return null;
819
+ }
820
+ async getQueueJobCounts(queue: string) {
821
+ return {
822
+ waiting: 0,
823
+ active: 0,
824
+ completed: 0,
825
+ failed: 0,
826
+ delayed: 0,
827
+ paused: 0,
828
+ };
829
+ }
830
+ async listQueues() {
831
+ return [];
832
+ }
833
+ async pauseQueue(queue: string) {
834
+ /* ... */
835
+ }
836
+ async resumeQueue(queue: string) {
837
+ /* ... */
838
+ }
839
+ async drainQueue(queue: string) {
840
+ return 0;
841
+ }
842
+ async cleanQueue(queue: string, options: IgniterJobsQueueCleanOptions) {
843
+ return 0;
844
+ }
845
+ async obliterateQueue(queue: string, options?: { force?: boolean }) {
846
+ /* ... */
847
+ }
848
+ async retryAllInQueue(queue: string) {
849
+ return 0;
850
+ }
851
+ async pauseJobType(queue: string, jobName: string) {
852
+ /* ... */
853
+ }
854
+ async resumeJobType(queue: string, jobName: string) {
855
+ /* ... */
856
+ }
857
+ async searchJobs(filter: Record<string, unknown>) {
858
+ return [];
859
+ }
860
+ async searchQueues(filter: Record<string, unknown>) {
861
+ return [];
862
+ }
863
+ async searchWorkers(filter: Record<string, unknown>) {
864
+ return [];
865
+ }
866
+ async createWorker(config: IgniterJobsWorkerBuilderConfig) {
867
+ return {} as IgniterJobsWorkerHandle;
868
+ }
869
+ getWorkers() {
870
+ return new Map();
871
+ }
872
+ async publishEvent(channel: string, payload: unknown) {
873
+ /* ... */
874
+ }
875
+ async subscribeEvent(channel: string, handler: IgniterJobsEventHandler) {
876
+ return async () => {};
877
+ }
878
+ registerJob(
879
+ queueName: string,
880
+ jobName: string,
881
+ definition: IgniterJobDefinition<any, any, any>,
882
+ ) {
883
+ /* ... */
884
+ }
885
+ registerCron(
886
+ queueName: string,
887
+ cronName: string,
888
+ definition: IgniterCronDefinition<any, any>,
889
+ ) {
890
+ /* ... */
891
+ }
892
+ async shutdown() {
893
+ /* ... */
894
+ }
784
895
  }
785
896
  ```
786
897
 
@@ -798,7 +909,9 @@ const telemetry = IgniterTelemetry.create()
798
909
 
799
910
  ```typescript
800
911
  export async function POST() {
801
- const id = await jobs.email.sendWelcome.dispatch({ input: { email: "user@example.com" } });
912
+ const id = await jobs.email.sendWelcome.dispatch({
913
+ input: { email: "user@example.com" },
914
+ });
802
915
  return NextResponse.json({ jobId: id });
803
916
  }
804
917
  ```
@@ -807,7 +920,9 @@ export async function POST() {
807
920
 
808
921
  ```typescript
809
922
  app.post("/send", async (_req, res) => {
810
- const jobId = await jobs.email.sendWelcome.dispatch({ input: { email: "user@example.com" } });
923
+ const jobId = await jobs.email.sendWelcome.dispatch({
924
+ input: { email: "user@example.com" },
925
+ });
811
926
  res.json({ jobId });
812
927
  });
813
928
  ```
@@ -816,7 +931,9 @@ app.post("/send", async (_req, res) => {
816
931
 
817
932
  ```typescript
818
933
  app.post("/send", async (_req, res) => {
819
- const jobId = await jobs.email.sendWelcome.dispatch({ input: { email: "user@example.com" } });
934
+ const jobId = await jobs.email.sendWelcome.dispatch({
935
+ input: { email: "user@example.com" },
936
+ });
820
937
  return res.send({ jobId });
821
938
  });
822
939
  ```
@@ -1019,44 +1136,45 @@ const jobs = IgniterJobs.create()
1019
1136
 
1020
1137
  ## 🔌 Adapters
1021
1138
 
1022
- Adapters implement the `IgniterJobsAdapter` interface and are exported via:
1139
+ Adapters implement the `IgniterJobsAdapter` interface and are grouped by runtime:
1023
1140
 
1024
1141
  ```typescript
1025
- import { IgniterJobsMemoryAdapter, IgniterJobsSQLiteAdapter, IgniterJobsBullMQAdapter } from "@igniter-js/jobs/adapters";
1142
+ import { IgniterJobsMemoryAdapter } from "@igniter-js/jobs/adapters/mock";
1143
+ import { IgniterJobsBullMQAdapter } from "@igniter-js/jobs/adapters/node";
1144
+ import { IgniterJobsBunSQLiteAdapter } from "@igniter-js/jobs/adapters/bun";
1026
1145
  ```
1027
1146
 
1028
1147
  ### Adapter Comparison
1029
1148
 
1030
- | Adapter | Persistence | Multi-process | Use Case |
1031
- |--------|-------------|---------------|---------|
1032
- | Memory | ❌ | ❌ | Unit tests, local dev |
1033
- | SQLite | ✅ | ⚠️ (single process) | Desktop, CLI, local |
1034
- | BullMQ | ✅ | ✅ | Production scale |
1149
+ | Adapter | Persistence | Multi-process | Use Case |
1150
+ | ------- | ----------- | ------------------- | --------------------- |
1151
+ | Memory | ❌ | ❌ | Unit tests, local dev |
1152
+ | SQLite | ✅ | ⚠️ (single process) | Desktop, CLI, local |
1153
+ | BullMQ | ✅ | ✅ | Production scale |
1035
1154
 
1036
1155
  ### Memory Adapter
1037
1156
 
1038
1157
  ```typescript
1039
- import { IgniterJobsMemoryAdapter } from "@igniter-js/jobs/adapters";
1158
+ import { IgniterJobsMemoryAdapter } from "@igniter-js/jobs/adapters/mock";
1040
1159
 
1041
1160
  const adapter = IgniterJobsMemoryAdapter.create();
1042
1161
  ```
1043
1162
 
1044
- ### SQLite Adapter
1163
+ ### Bun SQLite Adapter
1045
1164
 
1046
1165
  ```typescript
1047
- import { IgniterJobsSQLiteAdapter } from "@igniter-js/jobs/adapters";
1166
+ import { IgniterJobsBunSQLiteAdapter } from "@igniter-js/jobs/adapters/bun";
1048
1167
 
1049
- const adapter = IgniterJobsSQLiteAdapter.create({
1168
+ const adapter = IgniterJobsBunSQLiteAdapter.create({
1050
1169
  path: "./data/jobs.sqlite",
1051
- pollingInterval: 500,
1052
- enableWAL: true,
1170
+ durable: true,
1053
1171
  });
1054
1172
  ```
1055
1173
 
1056
1174
  ### BullMQ Adapter
1057
1175
 
1058
1176
  ```typescript
1059
- import { IgniterJobsBullMQAdapter } from "@igniter-js/jobs/adapters";
1177
+ import { IgniterJobsBullMQAdapter } from "@igniter-js/jobs/adapters/node";
1060
1178
  import Redis from "ioredis";
1061
1179
 
1062
1180
  const redis = new Redis(process.env.REDIS_URL);
@@ -1067,12 +1185,23 @@ const adapter = IgniterJobsBullMQAdapter.create({ redis });
1067
1185
 
1068
1186
  ## 🧪 Testing
1069
1187
 
1188
+ ### Bun SQLite Adapter
1189
+
1190
+ ```typescript
1191
+ import { IgniterJobsBunSQLiteAdapter } from "@igniter-js/jobs/adapters/bun";
1192
+
1193
+ const adapter = IgniterJobsBunSQLiteAdapter.create({
1194
+ path: "./data/jobs.sqlite",
1195
+ durable: true,
1196
+ });
1197
+ ```
1198
+
1070
1199
  ### Unit Testing with Memory Adapter
1071
1200
 
1072
1201
  ```typescript
1073
1202
  import { describe, it, expect } from "vitest";
1074
1203
  import { IgniterJobs, IgniterQueue } from "@igniter-js/jobs";
1075
- import { IgniterJobsMemoryAdapter } from "@igniter-js/jobs/adapters";
1204
+ import { IgniterJobsMemoryAdapter } from "@igniter-js/jobs/adapters/mock";
1076
1205
 
1077
1206
  describe("jobs", () => {
1078
1207
  it("dispatches and processes a job", async () => {
@@ -1121,22 +1250,21 @@ describe("jobs", () => {
1121
1250
  const adapter = IgniterJobsMemoryAdapter.create({ maxJobHistory: 1000 });
1122
1251
  ```
1123
1252
 
1124
- ### SQLite Adapter
1253
+ ### Bun SQLite Adapter
1125
1254
 
1126
1255
  Use for desktop apps, CLI tools, or local persistence without Redis.
1127
1256
 
1128
1257
  Key traits:
1129
1258
 
1130
1259
  - Persistent storage on disk
1131
- - Single-process (WAL helps but not distributed)
1260
+ - Single-process local runtime (persistent, but not distributed)
1132
1261
  - Polling-based worker loop
1133
1262
  - Great for edge or MCP servers
1134
1263
 
1135
1264
  ```typescript
1136
- const adapter = IgniterJobsSQLiteAdapter.create({
1265
+ const adapter = IgniterJobsBunSQLiteAdapter.create({
1137
1266
  path: "./data/jobs.sqlite",
1138
- pollingInterval: 500,
1139
- enableWAL: true,
1267
+ durable: true,
1140
1268
  });
1141
1269
  ```
1142
1270
 
@@ -1195,10 +1323,10 @@ describe("jobs", () => {
1195
1323
  - Use `withLimiter()` for API-bound workloads.
1196
1324
  - Shard by queue when jobs have very different resource needs.
1197
1325
 
1198
- ### Polling (SQLite)
1326
+ ### Local Bun SQLite Runtime
1199
1327
 
1200
- - Lower `pollingInterval` means faster reaction but higher CPU.
1201
- - Higher `pollingInterval` reduces CPU at the cost of latency.
1328
+ - Use `durable: true` only for jobs that must survive abrupt exits.
1329
+ - Tune `batchSize`, `pollTimeout`, and `heartbeatInterval` for throughput-sensitive workloads.
1202
1330
 
1203
1331
  ---
1204
1332
 
@@ -1705,17 +1833,40 @@ export const IgniterJobs: {
1705
1833
  class IgniterJobsBuilder<TContext, TQueues, TScope> {
1706
1834
  static create(): IgniterJobsBuilder<unknown>;
1707
1835
 
1708
- withAdapter(adapter: IgniterJobsAdapter): IgniterJobsBuilder<TContext, TQueues, TScope>;
1836
+ withAdapter(
1837
+ adapter: IgniterJobsAdapter,
1838
+ ): IgniterJobsBuilder<TContext, TQueues, TScope>;
1709
1839
  withService(service: string): IgniterJobsBuilder<TContext, TQueues, TScope>;
1710
- withEnvironment(environment: string): IgniterJobsBuilder<TContext, TQueues, TScope>;
1711
- withContext<TNewContext>(factory: () => TNewContext | Promise<TNewContext>): IgniterJobsBuilder<TNewContext, {}, TScope>;
1712
- addScope(name: string, options?: IgniterJobsScopeOptions): IgniterJobsBuilder<TContext, TQueues, TScope | string>;
1713
- addQueue(queue: IgniterJobsQueue<TContext, any, any> & { name: string }): IgniterJobsBuilder<TContext, TQueues & Record<string, any>, TScope>;
1714
- withQueueDefaults(defaults: Partial<IgniterJobDefinition<TContext, any, any>>): IgniterJobsBuilder<TContext, TQueues, TScope>;
1715
- withWorkerDefaults(defaults: Partial<IgniterJobsWorkerBuilderConfig>): IgniterJobsBuilder<TContext, TQueues, TScope>;
1716
- withAutoStartWorker(config: { queues: (keyof TQueues)[]; concurrency?: number; limiter?: IgniterJobsLimiter }): IgniterJobsBuilder<TContext, TQueues, TScope>;
1717
- withTelemetry(telemetry: IgniterJobsTelemetry): IgniterJobsBuilder<TContext, TQueues, TScope>;
1718
- withLogger(logger: IgniterLogger): IgniterJobsBuilder<TContext, TQueues, TScope>;
1840
+ withEnvironment(
1841
+ environment: string,
1842
+ ): IgniterJobsBuilder<TContext, TQueues, TScope>;
1843
+ withContext<TNewContext>(
1844
+ factory: () => TNewContext | Promise<TNewContext>,
1845
+ ): IgniterJobsBuilder<TNewContext, {}, TScope>;
1846
+ addScope(
1847
+ name: string,
1848
+ options?: IgniterJobsScopeOptions,
1849
+ ): IgniterJobsBuilder<TContext, TQueues, TScope | string>;
1850
+ addQueue(
1851
+ queue: IgniterJobsQueue<TContext, any, any> & { name: string },
1852
+ ): IgniterJobsBuilder<TContext, TQueues & Record<string, any>, TScope>;
1853
+ withQueueDefaults(
1854
+ defaults: Partial<IgniterJobDefinition<TContext, any, any>>,
1855
+ ): IgniterJobsBuilder<TContext, TQueues, TScope>;
1856
+ withWorkerDefaults(
1857
+ defaults: Partial<IgniterJobsWorkerBuilderConfig>,
1858
+ ): IgniterJobsBuilder<TContext, TQueues, TScope>;
1859
+ withAutoStartWorker(config: {
1860
+ queues: (keyof TQueues)[];
1861
+ concurrency?: number;
1862
+ limiter?: IgniterJobsLimiter;
1863
+ }): IgniterJobsBuilder<TContext, TQueues, TScope>;
1864
+ withTelemetry(
1865
+ telemetry: IgniterJobsTelemetry,
1866
+ ): IgniterJobsBuilder<TContext, TQueues, TScope>;
1867
+ withLogger(
1868
+ logger: IgniterLogger,
1869
+ ): IgniterJobsBuilder<TContext, TQueues, TScope>;
1719
1870
  build(): IgniterJobsRuntime<IgniterJobsConfig<TContext, TQueues, TScope>>;
1720
1871
  }
1721
1872
  ```
@@ -1726,7 +1877,9 @@ class IgniterJobsBuilder<TContext, TQueues, TScope> {
1726
1877
 
1727
1878
  ```typescript
1728
1879
  class IgniterQueue {
1729
- static create<const TName extends string>(name: TName): IgniterQueueBuilder<unknown, {}, {}, TName>;
1880
+ static create<const TName extends string>(
1881
+ name: TName,
1882
+ ): IgniterQueueBuilder<unknown, {}, {}, TName>;
1730
1883
  }
1731
1884
  ```
1732
1885
 
@@ -1736,13 +1889,23 @@ class IgniterQueue {
1736
1889
  class IgniterQueueBuilder<TContext, TJobs, TCron, TName> {
1737
1890
  addJob<TJobName extends string, TInput, TResult>(
1738
1891
  jobName: TJobName,
1739
- definition: IgniterJobDefinition<TContext, TInput, TResult>
1740
- ): IgniterQueueBuilder<TContext, TJobs & Record<TJobName, IgniterJobDefinition<TContext, TInput, TResult>>, TCron, TName>;
1892
+ definition: IgniterJobDefinition<TContext, TInput, TResult>,
1893
+ ): IgniterQueueBuilder<
1894
+ TContext,
1895
+ TJobs & Record<TJobName, IgniterJobDefinition<TContext, TInput, TResult>>,
1896
+ TCron,
1897
+ TName
1898
+ >;
1741
1899
 
1742
1900
  addCron<TCronName extends string, TResult>(
1743
1901
  cronName: TCronName,
1744
- definition: IgniterCronDefinition<TContext, TResult>
1745
- ): IgniterQueueBuilder<TContext, TJobs, TCron & Record<TCronName, IgniterCronDefinition<TContext, TResult>>, TName>;
1902
+ definition: IgniterCronDefinition<TContext, TResult>,
1903
+ ): IgniterQueueBuilder<
1904
+ TContext,
1905
+ TJobs,
1906
+ TCron & Record<TCronName, IgniterCronDefinition<TContext, TResult>>,
1907
+ TName
1908
+ >;
1746
1909
 
1747
1910
  build(): IgniterJobsQueue<TContext, TJobs, TCron> & { name: TName };
1748
1911
  }
@@ -1754,10 +1917,17 @@ class IgniterQueueBuilder<TContext, TJobs, TCron, TName> {
1754
1917
  interface IgniterJobsRuntime<TConfig> {
1755
1918
  config: TConfig;
1756
1919
  subscribe(handler: IgniterJobsEventHandler): Promise<() => Promise<void>>;
1757
- search(target: "jobs" | "queues" | "workers", filter: Record<string, unknown>): Promise<unknown[]>;
1920
+ search(
1921
+ target: "jobs" | "queues" | "workers",
1922
+ filter: Record<string, unknown>,
1923
+ ): Promise<unknown[]>;
1758
1924
  shutdown(): Promise<void>;
1759
1925
  worker: { create(): IgniterWorkerBuilder<keyof TConfig["queues"] & string> };
1760
- scope(type: string, id: string | number, tags?: Record<string, unknown>): IgniterJobsRuntime<TConfig>;
1926
+ scope(
1927
+ type: string,
1928
+ id: string | number,
1929
+ tags?: Record<string, unknown>,
1930
+ ): IgniterJobsRuntime<TConfig>;
1761
1931
 
1762
1932
  // Queue accessors (dynamic)
1763
1933
  [queueName: string]: IgniterJobsQueueAccessor<any>;
@@ -1777,7 +1947,11 @@ interface IgniterJobsQueueAccessor {
1777
1947
  obliterate(options?: { force?: boolean }): Promise<void>;
1778
1948
  retryAll(): Promise<number>;
1779
1949
  };
1780
- list(filter?: { status?: IgniterJobStatus[]; limit?: number; offset?: number }): Promise<IgniterJobSearchResult[]>;
1950
+ list(filter?: {
1951
+ status?: IgniterJobStatus[];
1952
+ limit?: number;
1953
+ offset?: number;
1954
+ }): Promise<IgniterJobSearchResult[]>;
1781
1955
  subscribe(handler: IgniterJobsEventHandler): Promise<() => Promise<void>>;
1782
1956
  jobs: Record<string, IgniterJobsJobAccessor>;
1783
1957
  }
@@ -1800,8 +1974,6 @@ interface IgniterJobsJobAccessor<TInput = unknown> {
1800
1974
  logs(): Promise<IgniterJobsJobLog[]>;
1801
1975
  };
1802
1976
  many(ids: string[]): { retry(): Promise<void>; remove(): Promise<void> };
1803
- pause(): Promise<void>;
1804
- resume(): Promise<void>;
1805
1977
  subscribe(handler: IgniterJobsEventHandler): Promise<() => Promise<void>>;
1806
1978
  }
1807
1979
  ```
@@ -1811,12 +1983,30 @@ interface IgniterJobsJobAccessor<TInput = unknown> {
1811
1983
  ```typescript
1812
1984
  interface IgniterJobsWorkerFluentBuilder<TQueueNames extends string> {
1813
1985
  addQueue(queue: TQueueNames): IgniterJobsWorkerFluentBuilder<TQueueNames>;
1814
- withConcurrency(concurrency: number): IgniterJobsWorkerFluentBuilder<TQueueNames>;
1815
- withLimiter(limiter: IgniterJobsLimiter): IgniterJobsWorkerFluentBuilder<TQueueNames>;
1816
- onActive(handler: (ctx: { job: IgniterJobSearchResult }) => void | Promise<void>): IgniterJobsWorkerFluentBuilder<TQueueNames>;
1817
- onSuccess(handler: (ctx: { job: IgniterJobSearchResult; result: unknown }) => void | Promise<void>): IgniterJobsWorkerFluentBuilder<TQueueNames>;
1818
- onFailure(handler: (ctx: { job: IgniterJobSearchResult; error: Error }) => void | Promise<void>): IgniterJobsWorkerFluentBuilder<TQueueNames>;
1819
- onIdle(handler: () => void | Promise<void>): IgniterJobsWorkerFluentBuilder<TQueueNames>;
1986
+ withConcurrency(
1987
+ concurrency: number,
1988
+ ): IgniterJobsWorkerFluentBuilder<TQueueNames>;
1989
+ withLimiter(
1990
+ limiter: IgniterJobsLimiter,
1991
+ ): IgniterJobsWorkerFluentBuilder<TQueueNames>;
1992
+ onActive(
1993
+ handler: (ctx: { job: IgniterJobSearchResult }) => void | Promise<void>,
1994
+ ): IgniterJobsWorkerFluentBuilder<TQueueNames>;
1995
+ onSuccess(
1996
+ handler: (ctx: {
1997
+ job: IgniterJobSearchResult;
1998
+ result: unknown;
1999
+ }) => void | Promise<void>,
2000
+ ): IgniterJobsWorkerFluentBuilder<TQueueNames>;
2001
+ onFailure(
2002
+ handler: (ctx: {
2003
+ job: IgniterJobSearchResult;
2004
+ error: Error;
2005
+ }) => void | Promise<void>,
2006
+ ): IgniterJobsWorkerFluentBuilder<TQueueNames>;
2007
+ onIdle(
2008
+ handler: () => void | Promise<void>,
2009
+ ): IgniterJobsWorkerFluentBuilder<TQueueNames>;
1820
2010
  start(): Promise<IgniterJobsWorkerHandle>;
1821
2011
  }
1822
2012
  ```
@@ -1825,13 +2015,17 @@ interface IgniterJobsWorkerFluentBuilder<TQueueNames extends string> {
1825
2015
 
1826
2016
  ## ⚙️ Configuration Reference
1827
2017
 
1828
- ### IgniterJobsSQLiteAdapterOptions
2018
+ ### IgniterJobsBunSQLiteAdapterOptions
1829
2019
 
1830
2020
  ```typescript
1831
- interface IgniterJobsSQLiteAdapterOptions {
2021
+ interface IgniterJobsBunSQLiteAdapterOptions {
1832
2022
  path: string;
1833
- pollingInterval?: number; // default: 500
1834
- enableWAL?: boolean; // default: true
2023
+ durable?: boolean;
2024
+ heartbeatInterval?: number;
2025
+ pollTimeout?: number;
2026
+ batchSize?: number;
2027
+ lockDuration?: number;
2028
+ maxStalledCount?: number;
1835
2029
  }
1836
2030
  ```
1837
2031
 
@@ -1857,22 +2051,22 @@ interface IgniterJobsScheduleOptions {
1857
2051
 
1858
2052
  ## ✅ Best Practices
1859
2053
 
1860
- | Do | Why | Example |
1861
- |----|-----|---------|
1862
- | ✅ Use input schemas | Prevent invalid jobs | `input: z.object({ ... })` |
1863
- | ✅ Keep payloads small | Faster serialization | `{ id: "order_1" }` |
1864
- | ✅ Use scopes | Tenant isolation | `jobs.scope("org", "org_1")` |
1865
- | ✅ Use retries | Resilience | `attempts: 5` |
1866
- | ✅ Use worker hooks | Observability | `onFailure(...)` |
2054
+ | Do | Why | Example |
2055
+ | ---------------------- | -------------------- | ---------------------------- |
2056
+ | ✅ Use input schemas | Prevent invalid jobs | `input: z.object({ ... })` |
2057
+ | ✅ Keep payloads small | Faster serialization | `{ id: "order_1" }` |
2058
+ | ✅ Use scopes | Tenant isolation | `jobs.scope("org", "org_1")` |
2059
+ | ✅ Use retries | Resilience | `attempts: 5` |
2060
+ | ✅ Use worker hooks | Observability | `onFailure(...)` |
1867
2061
 
1868
2062
  ### Anti-Patterns
1869
2063
 
1870
- | Don’t | Why | Alternative |
1871
- |------|-----|-------------|
1872
- | ❌ Store PII in metadata | Metadata is observable | Store IDs only |
1873
- | ❌ Use sync I/O in handlers | Blocks workers | Use async I/O |
1874
- | ❌ Dispatch without schema | Runtime surprises | Add `input` schema |
1875
- | ❌ Long-running jobs without progress | No visibility | Use `onProgress` |
2064
+ | Don’t | Why | Alternative |
2065
+ | ------------------------------------- | ---------------------- | ------------------ |
2066
+ | ❌ Store PII in metadata | Metadata is observable | Store IDs only |
2067
+ | ❌ Use sync I/O in handlers | Blocks workers | Use async I/O |
2068
+ | ❌ Dispatch without schema | Runtime surprises | Add `input` schema |
2069
+ | ❌ Long-running jobs without progress | No visibility | Use `onProgress` |
1876
2070
 
1877
2071
  ---
1878
2072
 
@@ -1966,9 +2160,9 @@ Do not import this package in client-side bundles.
1966
2160
  ### Memory → SQLite
1967
2161
 
1968
2162
  ```typescript
1969
- import { IgniterJobsSQLiteAdapter } from "@igniter-js/jobs/adapters";
2163
+ import { IgniterJobsBunSQLiteAdapter } from "@igniter-js/jobs/adapters/bun";
1970
2164
 
1971
- const adapter = IgniterJobsSQLiteAdapter.create({
2165
+ const adapter = IgniterJobsBunSQLiteAdapter.create({
1972
2166
  path: "./jobs.sqlite",
1973
2167
  });
1974
2168
  ```
@@ -1976,7 +2170,7 @@ const adapter = IgniterJobsSQLiteAdapter.create({
1976
2170
  ### SQLite → BullMQ
1977
2171
 
1978
2172
  ```typescript
1979
- import { IgniterJobsBullMQAdapter } from "@igniter-js/jobs/adapters";
2173
+ import { IgniterJobsBullMQAdapter } from "@igniter-js/jobs/adapters/node";
1980
2174
  import Redis from "ioredis";
1981
2175
 
1982
2176
  const adapter = IgniterJobsBullMQAdapter.create({
@@ -2012,7 +2206,7 @@ Yes. The adapter interface is stable and jobs/queues remain unchanged.
2012
2206
 
2013
2207
  ```typescript
2014
2208
  import { IgniterJobs, IgniterQueue } from "@igniter-js/jobs";
2015
- import { IgniterJobsSQLiteAdapter } from "@igniter-js/jobs/adapters";
2209
+ import { IgniterJobsBunSQLiteAdapter } from "@igniter-js/jobs/adapters/bun";
2016
2210
  import { z } from "zod";
2017
2211
 
2018
2212
  type AppContext = { uploads: { process: (id: string) => Promise<void> } };
@@ -2027,7 +2221,7 @@ const queue = IgniterQueue.create("uploads")
2027
2221
  .build();
2028
2222
 
2029
2223
  const jobs = IgniterJobs.create()
2030
- .withAdapter(IgniterJobsSQLiteAdapter.create({ path: "./jobs.sqlite" }))
2224
+ .withAdapter(IgniterJobsBunSQLiteAdapter.create({ path: "./jobs.sqlite" }))
2031
2225
  .withService("uploader")
2032
2226
  .withEnvironment("local")
2033
2227
  .withContext(async () => ({ uploads }))
@@ -2054,14 +2248,14 @@ await jobs.shutdown();
2054
2248
 
2055
2249
  ### Job Events (Runtime)
2056
2250
 
2057
- | Event | When | Payload Keys |
2058
- |------|------|--------------|
2059
- | `enqueued` | After dispatch | `jobId`, `queue`, `jobName` |
2060
- | `scheduled` | After schedule | `jobId`, `queue`, `jobName` |
2061
- | `started` | Before handler | `jobId`, `jobName`, `queue`, `attemptsMade`, `startedAt` |
2062
- | `completed` | After handler | `jobId`, `jobName`, `queue`, `result`, `duration`, `completedAt` |
2063
- | `failed` | On error | `jobId`, `jobName`, `queue`, `error`, `attemptsMade`, `isFinalAttempt`, `duration`, `failedAt` |
2064
- | `progress` | On progress hook | `jobId`, `jobName`, `queue`, `progress`, `message`, `timestamp` |
2251
+ | Event | When | Payload Keys |
2252
+ | ----------- | ---------------- | ---------------------------------------------------------------------------------------------- |
2253
+ | `enqueued` | After dispatch | `jobId`, `queue`, `jobName` |
2254
+ | `scheduled` | After schedule | `jobId`, `queue`, `jobName` |
2255
+ | `started` | Before handler | `jobId`, `jobName`, `queue`, `attemptsMade`, `startedAt` |
2256
+ | `completed` | After handler | `jobId`, `jobName`, `queue`, `result`, `duration`, `completedAt` |
2257
+ | `failed` | On error | `jobId`, `jobName`, `queue`, `error`, `attemptsMade`, `isFinalAttempt`, `duration`, `failedAt` |
2258
+ | `progress` | On progress hook | `jobId`, `jobName`, `queue`, `progress`, `message`, `timestamp` |
2065
2259
 
2066
2260
  ### Telemetry Event Attributes
2067
2261
 
@@ -2104,22 +2298,22 @@ await jobs.shutdown();
2104
2298
 
2105
2299
  ## 📑 Appendix: Adapter API Matrix
2106
2300
 
2107
- | API | Memory | SQLite | BullMQ |
2108
- |-----|--------|--------|--------|
2109
- | `dispatch()` | ✅ | ✅ | ✅ |
2110
- | `schedule()` | ✅ | ✅ | ✅ |
2111
- | `getJob()` | ✅ | ✅ | ✅ |
2112
- | `getJobState()` | ✅ | ✅ | ✅ |
2113
- | `getJobLogs()` | ✅ | ✅ | ✅ |
2114
- | `getJobProgress()` | ✅ | ✅ | ✅ |
2115
- | `pauseJobType()` | ✅ | ✅ | ❌ |
2116
- | `resumeJobType()` | ✅ | ✅ | ❌ |
2117
- | `pauseQueue()` | ✅ | ✅ | ✅ |
2118
- | `resumeQueue()` | ✅ | ✅ | ✅ |
2119
- | `drainQueue()` | ✅ | ✅ | ✅ |
2120
- | `cleanQueue()` | ✅ | ✅ | ✅ |
2121
- | `obliterateQueue()` | ✅ | ✅ | ✅ |
2122
- | `retryAllInQueue()` | ✅ | ✅ | ✅ |
2301
+ | API | Memory | SQLite | BullMQ |
2302
+ | ------------------- | ------ | ------ | ------ |
2303
+ | `dispatch()` | ✅ | ✅ | ✅ |
2304
+ | `schedule()` | ✅ | ✅ | ✅ |
2305
+ | `getJob()` | ✅ | ✅ | ✅ |
2306
+ | `getJobState()` | ✅ | ✅ | ✅ |
2307
+ | `getJobLogs()` | ✅ | ✅ | ✅ |
2308
+ | `getJobProgress()` | ✅ | ✅ | ✅ |
2309
+ | `pauseJobType()` | ✅ | ✅ | ❌ |
2310
+ | `resumeJobType()` | ✅ | ✅ | ❌ |
2311
+ | `pauseQueue()` | ✅ | ✅ | ✅ |
2312
+ | `resumeQueue()` | ✅ | ✅ | ✅ |
2313
+ | `drainQueue()` | ✅ | ✅ | ✅ |
2314
+ | `cleanQueue()` | ✅ | ✅ | ✅ |
2315
+ | `obliterateQueue()` | ✅ | ✅ | ✅ |
2316
+ | `retryAllInQueue()` | ✅ | ✅ | ✅ |
2123
2317
 
2124
2318
  ---
2125
2319