nmtjs 0.15.0-beta.2 → 0.15.0-beta.20

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 (254) hide show
  1. package/dist/cli.d.ts +2 -0
  2. package/dist/cli.js +3 -2
  3. package/dist/cli.js.map +1 -0
  4. package/dist/config.d.ts +51 -0
  5. package/dist/config.js +1 -0
  6. package/dist/config.js.map +1 -0
  7. package/dist/entrypoints/cli.d.ts +1 -0
  8. package/dist/entrypoints/cli.js +1 -0
  9. package/dist/entrypoints/cli.js.map +1 -0
  10. package/dist/entrypoints/main.d.ts +5 -0
  11. package/dist/entrypoints/main.js +83 -15
  12. package/dist/entrypoints/main.js.map +1 -0
  13. package/dist/entrypoints/thread.d.ts +14 -0
  14. package/dist/entrypoints/thread.js +130 -24
  15. package/dist/entrypoints/thread.js.map +1 -0
  16. package/dist/entrypoints/worker.d.ts +3 -0
  17. package/dist/entrypoints/worker.js +4 -3
  18. package/dist/entrypoints/worker.js.map +1 -0
  19. package/dist/index.d.ts +69 -0
  20. package/dist/{_exports/index.js → index.js} +9 -5
  21. package/dist/index.js.map +1 -0
  22. package/dist/resolver.d.ts +2 -0
  23. package/dist/resolver.js +1 -0
  24. package/dist/resolver.js.map +1 -0
  25. package/dist/runtime/application/api/api.d.ts +49 -0
  26. package/dist/runtime/application/api/api.js +193 -0
  27. package/dist/runtime/application/api/api.js.map +1 -0
  28. package/dist/runtime/application/api/constants.d.ts +14 -0
  29. package/dist/runtime/application/api/constants.js +8 -0
  30. package/dist/runtime/application/api/constants.js.map +1 -0
  31. package/dist/runtime/application/api/filters.d.ts +14 -0
  32. package/dist/runtime/application/api/filters.js +11 -0
  33. package/dist/runtime/application/api/filters.js.map +1 -0
  34. package/dist/runtime/application/api/guards.d.ts +13 -0
  35. package/dist/runtime/application/api/guards.js +8 -0
  36. package/dist/runtime/application/api/guards.js.map +1 -0
  37. package/dist/runtime/application/api/index.d.ts +8 -0
  38. package/dist/runtime/application/api/index.js +9 -0
  39. package/dist/runtime/application/api/index.js.map +1 -0
  40. package/dist/runtime/application/api/middlewares.d.ts +14 -0
  41. package/dist/runtime/application/api/middlewares.js +12 -0
  42. package/dist/runtime/application/api/middlewares.js.map +1 -0
  43. package/dist/runtime/application/api/procedure.d.ts +67 -0
  44. package/dist/runtime/application/api/procedure.js +50 -0
  45. package/dist/runtime/application/api/procedure.js.map +1 -0
  46. package/dist/runtime/application/api/router.d.ts +71 -0
  47. package/dist/runtime/application/api/router.js +51 -0
  48. package/dist/runtime/application/api/router.js.map +1 -0
  49. package/dist/runtime/application/api/types.d.ts +32 -0
  50. package/dist/runtime/application/api/types.js +2 -0
  51. package/dist/runtime/application/api/types.js.map +1 -0
  52. package/dist/runtime/application/config.d.ts +26 -0
  53. package/dist/runtime/application/config.js +21 -0
  54. package/dist/runtime/application/config.js.map +1 -0
  55. package/dist/runtime/application/constants.d.ts +2 -0
  56. package/dist/runtime/application/constants.js +2 -0
  57. package/dist/runtime/application/constants.js.map +1 -0
  58. package/dist/runtime/application/hook.d.ts +19 -0
  59. package/dist/runtime/application/hook.js +11 -0
  60. package/dist/runtime/application/hook.js.map +1 -0
  61. package/dist/runtime/application/hooks.d.ts +3 -0
  62. package/dist/runtime/application/hooks.js +4 -0
  63. package/dist/runtime/application/hooks.js.map +1 -0
  64. package/dist/runtime/application/index.d.ts +5 -0
  65. package/dist/runtime/application/index.js +6 -0
  66. package/dist/runtime/application/index.js.map +1 -0
  67. package/dist/runtime/constants.d.ts +8 -0
  68. package/dist/runtime/constants.js +5 -0
  69. package/dist/runtime/constants.js.map +1 -0
  70. package/dist/runtime/core/hooks.d.ts +4 -0
  71. package/dist/runtime/core/hooks.js +4 -0
  72. package/dist/runtime/core/hooks.js.map +1 -0
  73. package/dist/runtime/core/plugin.d.ts +8 -0
  74. package/dist/runtime/core/plugin.js +4 -0
  75. package/dist/runtime/core/plugin.js.map +1 -0
  76. package/dist/runtime/core/runtime.d.ts +27 -0
  77. package/dist/runtime/core/runtime.js +81 -0
  78. package/dist/runtime/core/runtime.js.map +1 -0
  79. package/dist/runtime/enums.d.ts +21 -0
  80. package/dist/runtime/enums.js +26 -0
  81. package/dist/runtime/enums.js.map +1 -0
  82. package/dist/runtime/index.d.ts +21 -0
  83. package/dist/runtime/index.js +22 -0
  84. package/dist/runtime/index.js.map +1 -0
  85. package/dist/runtime/injectables.d.ts +23 -0
  86. package/dist/runtime/injectables.js +20 -0
  87. package/dist/runtime/injectables.js.map +1 -0
  88. package/dist/runtime/jobs/job.d.ts +132 -0
  89. package/dist/runtime/jobs/job.js +68 -0
  90. package/dist/runtime/jobs/job.js.map +1 -0
  91. package/dist/runtime/jobs/manager.d.ts +103 -0
  92. package/dist/runtime/jobs/manager.js +200 -0
  93. package/dist/runtime/jobs/manager.js.map +1 -0
  94. package/dist/runtime/jobs/router.d.ts +251 -0
  95. package/dist/runtime/jobs/router.js +396 -0
  96. package/dist/runtime/jobs/router.js.map +1 -0
  97. package/dist/runtime/jobs/runner.d.ts +64 -0
  98. package/dist/runtime/jobs/runner.js +256 -0
  99. package/dist/runtime/jobs/runner.js.map +1 -0
  100. package/dist/runtime/jobs/step.d.ts +22 -0
  101. package/dist/runtime/jobs/step.js +18 -0
  102. package/dist/runtime/jobs/step.js.map +1 -0
  103. package/dist/runtime/jobs/ui.d.ts +3 -0
  104. package/dist/runtime/jobs/ui.js +17 -0
  105. package/dist/runtime/jobs/ui.js.map +1 -0
  106. package/dist/runtime/pubsub/manager.d.ts +48 -0
  107. package/dist/runtime/pubsub/manager.js +119 -0
  108. package/dist/runtime/pubsub/manager.js.map +1 -0
  109. package/dist/runtime/pubsub/redis.d.ts +16 -0
  110. package/dist/runtime/pubsub/redis.js +98 -0
  111. package/dist/runtime/pubsub/redis.js.map +1 -0
  112. package/dist/runtime/scheduler/index.d.ts +22 -0
  113. package/dist/runtime/scheduler/index.js +20 -0
  114. package/dist/runtime/scheduler/index.js.map +1 -0
  115. package/dist/runtime/server/applications.d.ts +52 -0
  116. package/dist/runtime/server/applications.js +133 -0
  117. package/dist/runtime/server/applications.js.map +1 -0
  118. package/dist/runtime/server/config.d.ts +121 -0
  119. package/dist/runtime/server/config.js +33 -0
  120. package/dist/runtime/server/config.js.map +1 -0
  121. package/dist/runtime/server/jobs.d.ts +41 -0
  122. package/dist/runtime/server/jobs.js +181 -0
  123. package/dist/runtime/server/jobs.js.map +1 -0
  124. package/dist/runtime/server/pool.d.ts +54 -0
  125. package/dist/runtime/server/pool.js +194 -0
  126. package/dist/runtime/server/pool.js.map +1 -0
  127. package/dist/runtime/server/proxy.d.ts +21 -0
  128. package/dist/runtime/server/proxy.js +79 -0
  129. package/dist/runtime/server/proxy.js.map +1 -0
  130. package/dist/runtime/server/server.d.ts +53 -0
  131. package/dist/runtime/server/server.js +90 -0
  132. package/dist/runtime/server/server.js.map +1 -0
  133. package/dist/runtime/store/index.d.ts +3 -0
  134. package/dist/runtime/store/index.js +23 -0
  135. package/dist/runtime/store/index.js.map +1 -0
  136. package/dist/runtime/types.d.ts +103 -0
  137. package/dist/runtime/types.js +2 -0
  138. package/dist/runtime/types.js.map +1 -0
  139. package/dist/runtime/workers/application.d.ts +47 -0
  140. package/dist/runtime/workers/application.js +162 -0
  141. package/dist/runtime/workers/application.js.map +1 -0
  142. package/dist/runtime/workers/base.d.ts +16 -0
  143. package/dist/runtime/workers/base.js +46 -0
  144. package/dist/runtime/workers/base.js.map +1 -0
  145. package/dist/runtime/workers/cli.d.ts +1 -0
  146. package/dist/runtime/workers/cli.js +2 -0
  147. package/dist/runtime/workers/cli.js.map +1 -0
  148. package/dist/runtime/workers/job.d.ts +20 -0
  149. package/dist/runtime/workers/job.js +172 -0
  150. package/dist/runtime/workers/job.js.map +1 -0
  151. package/dist/typings.d.ts +5 -0
  152. package/dist/typings.js +4 -3
  153. package/dist/typings.js.map +1 -0
  154. package/dist/vite/builder.d.ts +5 -0
  155. package/dist/vite/builder.js +5 -1
  156. package/dist/vite/builder.js.map +1 -0
  157. package/dist/vite/config.d.ts +28 -0
  158. package/dist/vite/config.js +1 -0
  159. package/dist/vite/config.js.map +1 -0
  160. package/dist/vite/plugins.d.ts +2 -0
  161. package/dist/vite/plugins.js +1 -0
  162. package/dist/vite/plugins.js.map +1 -0
  163. package/dist/vite/runners/worker.d.ts +4 -0
  164. package/dist/vite/runners/worker.js +1 -0
  165. package/dist/vite/runners/worker.js.map +1 -0
  166. package/dist/vite/server.d.ts +3 -0
  167. package/dist/vite/server.js +6 -1
  168. package/dist/vite/server.js.map +1 -0
  169. package/dist/vite/servers/main.d.ts +8 -0
  170. package/dist/vite/servers/main.js +1 -0
  171. package/dist/vite/servers/main.js.map +1 -0
  172. package/dist/vite/servers/worker.d.ts +11 -0
  173. package/dist/vite/servers/worker.js +28 -0
  174. package/dist/vite/servers/worker.js.map +1 -0
  175. package/package.json +31 -18
  176. package/src/cli.ts +144 -0
  177. package/src/config.ts +64 -0
  178. package/src/entrypoints/cli.ts +13 -0
  179. package/src/entrypoints/main.ts +200 -0
  180. package/src/entrypoints/thread.ts +184 -0
  181. package/src/entrypoints/worker.ts +48 -0
  182. package/src/index.ts +82 -0
  183. package/src/resolver.ts +16 -0
  184. package/src/runtime/application/api/api.ts +265 -0
  185. package/src/runtime/application/api/constants.ts +22 -0
  186. package/src/runtime/application/api/filters.ts +39 -0
  187. package/src/runtime/application/api/guards.ts +29 -0
  188. package/src/runtime/application/api/index.ts +8 -0
  189. package/src/runtime/application/api/middlewares.ts +37 -0
  190. package/src/runtime/application/api/procedure.ts +229 -0
  191. package/src/runtime/application/api/router.ts +193 -0
  192. package/src/runtime/application/api/types.ts +124 -0
  193. package/src/runtime/application/config.ts +69 -0
  194. package/src/runtime/application/constants.ts +4 -0
  195. package/src/runtime/application/hook.ts +51 -0
  196. package/src/runtime/application/hooks.ts +3 -0
  197. package/src/runtime/application/index.ts +5 -0
  198. package/src/runtime/constants.ts +13 -0
  199. package/src/runtime/core/hooks.ts +5 -0
  200. package/src/runtime/core/plugin.ts +13 -0
  201. package/src/runtime/core/runtime.ts +109 -0
  202. package/src/runtime/enums.ts +24 -0
  203. package/src/runtime/index.ts +21 -0
  204. package/src/runtime/injectables.ts +61 -0
  205. package/src/runtime/jobs/job.ts +370 -0
  206. package/src/runtime/jobs/manager.ts +332 -0
  207. package/src/runtime/jobs/router.ts +835 -0
  208. package/src/runtime/jobs/runner.ts +320 -0
  209. package/src/runtime/jobs/step.ts +65 -0
  210. package/src/runtime/jobs/ui.ts +21 -0
  211. package/src/runtime/pubsub/manager.ts +211 -0
  212. package/src/runtime/pubsub/redis.ts +108 -0
  213. package/src/runtime/scheduler/index.ts +39 -0
  214. package/src/runtime/server/applications.ts +210 -0
  215. package/src/runtime/server/config.ts +158 -0
  216. package/src/runtime/server/jobs.ts +250 -0
  217. package/src/runtime/server/pool.ts +260 -0
  218. package/src/runtime/server/proxy.ts +118 -0
  219. package/src/runtime/server/server.ts +155 -0
  220. package/src/runtime/store/index.ts +30 -0
  221. package/src/runtime/types.ts +93 -0
  222. package/src/runtime/workers/application.ts +209 -0
  223. package/src/runtime/workers/base.ts +68 -0
  224. package/src/runtime/workers/cli.ts +0 -0
  225. package/src/runtime/workers/job.ts +153 -0
  226. package/src/typings.ts +30 -0
  227. package/src/vite/builder.ts +122 -0
  228. package/src/vite/config.ts +45 -0
  229. package/src/vite/plugins.ts +26 -0
  230. package/src/vite/runners/worker.ts +57 -0
  231. package/src/vite/server.ts +39 -0
  232. package/src/vite/servers/main.ts +34 -0
  233. package/src/vite/servers/worker.ts +143 -0
  234. package/dist/_exports/application.js +0 -1
  235. package/dist/_exports/common.js +0 -1
  236. package/dist/_exports/contract.js +0 -2
  237. package/dist/_exports/core.js +0 -1
  238. package/dist/_exports/gateway.js +0 -1
  239. package/dist/_exports/http-transport/bun.js +0 -1
  240. package/dist/_exports/http-transport/deno.js +0 -1
  241. package/dist/_exports/http-transport/node.js +0 -1
  242. package/dist/_exports/http-transport.js +0 -1
  243. package/dist/_exports/json-format.js +0 -1
  244. package/dist/_exports/protocol/client.js +0 -1
  245. package/dist/_exports/protocol/server.js +0 -1
  246. package/dist/_exports/protocol.js +0 -1
  247. package/dist/_exports/runtime/types.js +0 -1
  248. package/dist/_exports/runtime.js +0 -1
  249. package/dist/_exports/type.js +0 -2
  250. package/dist/_exports/ws-transport/bun.js +0 -1
  251. package/dist/_exports/ws-transport/deno.js +0 -1
  252. package/dist/_exports/ws-transport/node.js +0 -1
  253. package/dist/_exports/ws-transport.js +0 -1
  254. package/dist/command.js +0 -30
@@ -0,0 +1,835 @@
1
+ import type { MaybePromise } from '@nmtjs/common'
2
+ import type { TProcedureContract, TRouterContract } from '@nmtjs/contract'
3
+ import type { Dependencies, DependencyContext, Metadata } from '@nmtjs/core'
4
+ import type { NullableType, OptionalType } from '@nmtjs/type'
5
+ import type { NeverType } from '@nmtjs/type/never'
6
+ import type { JobState } from 'bullmq'
7
+ import { CoreInjectables } from '@nmtjs/core'
8
+ import { t } from '@nmtjs/type'
9
+
10
+ import type { AnyGuard } from '../application/api/guards.ts'
11
+ import type { AnyMiddleware } from '../application/api/middlewares.ts'
12
+ import type { AnyProcedure } from '../application/api/procedure.ts'
13
+ import type { AnyRouter, Router } from '../application/api/router.ts'
14
+ import type { AnyJob } from './job.ts'
15
+ import { createProcedure } from '../application/api/procedure.ts'
16
+ import { createRouter } from '../application/api/router.ts'
17
+ import { jobManager } from '../injectables.ts'
18
+
19
+ // ============================================================================
20
+ // Configuration Types
21
+ // ============================================================================
22
+
23
+ /** Base operation config shared by all operations */
24
+ export type BaseOperationConfig<Deps extends Dependencies = {}> = {
25
+ dependencies?: Deps
26
+ guards?: AnyGuard[]
27
+ middlewares?: AnyMiddleware[]
28
+ metadata?: Metadata[]
29
+ timeout?: number
30
+ }
31
+
32
+ /** List operation config (read-only, no hooks) */
33
+ export type ListOperationConfig<Deps extends Dependencies = {}> =
34
+ BaseOperationConfig<Deps>
35
+
36
+ /** Get operation config (read-only, no hooks) */
37
+ export type GetOperationConfig<Deps extends Dependencies = {}> =
38
+ BaseOperationConfig<Deps>
39
+
40
+ /** Add queue options */
41
+ export type AddQueueOptions = {
42
+ priority?: number
43
+ attempts?: number
44
+ delay?: number
45
+ }
46
+
47
+ /** Add operation config with before/after hooks */
48
+ export type AddOperationConfig<
49
+ T extends AnyJob = AnyJob,
50
+ Deps extends Dependencies = {},
51
+ > = BaseOperationConfig<Deps> & {
52
+ beforeAdd?: (
53
+ ctx: DependencyContext<Deps>,
54
+ input: T['_']['input'],
55
+ ) => MaybePromise<T['_']['input']>
56
+ afterAdd?: (
57
+ ctx: DependencyContext<Deps>,
58
+ result: { id: string; name: string },
59
+ input: T['_']['input'],
60
+ ) => MaybePromise<void>
61
+ options?: AddQueueOptions
62
+ }
63
+
64
+ /** Remove operation config with before/after hooks */
65
+ export type RemoveOperationConfig<Deps extends Dependencies = {}> =
66
+ BaseOperationConfig<Deps> & {
67
+ beforeRemove?: (
68
+ ctx: DependencyContext<Deps>,
69
+ params: { id: string },
70
+ ) => MaybePromise<void>
71
+ afterRemove?: (
72
+ ctx: DependencyContext<Deps>,
73
+ params: { id: string },
74
+ ) => MaybePromise<void>
75
+ }
76
+
77
+ /** Retry operation config with before/after hooks */
78
+ export type RetryOperationConfig<Deps extends Dependencies = {}> =
79
+ BaseOperationConfig<Deps> & {
80
+ clearState?: boolean
81
+ beforeRetry?: (
82
+ ctx: DependencyContext<Deps>,
83
+ params: { id: string; clearState?: boolean },
84
+ ) => MaybePromise<void>
85
+ afterRetry?: (
86
+ ctx: DependencyContext<Deps>,
87
+ params: { id: string; clearState?: boolean },
88
+ ) => MaybePromise<void>
89
+ }
90
+
91
+ /** Cancel operation config with before/after hooks */
92
+ export type CancelOperationConfig<Deps extends Dependencies = {}> =
93
+ BaseOperationConfig<Deps> & {
94
+ beforeCancel?: (
95
+ ctx: DependencyContext<Deps>,
96
+ params: { id: string },
97
+ ) => MaybePromise<void>
98
+ afterCancel?: (
99
+ ctx: DependencyContext<Deps>,
100
+ params: { id: string },
101
+ ) => MaybePromise<void>
102
+ }
103
+
104
+ /** All operations for a job (false = disabled) */
105
+ export type JobOperations<T extends AnyJob = AnyJob> = {
106
+ list?: ListOperationConfig<any> | false
107
+ get?: GetOperationConfig<any> | false
108
+ add?: AddOperationConfig<T, any> | false
109
+ retry?: RetryOperationConfig<any> | false
110
+ cancel?: CancelOperationConfig<any> | false
111
+ remove?: RemoveOperationConfig<any> | false
112
+ }
113
+
114
+ /** Default operations config */
115
+ export type DefaultOperations = {
116
+ list?: ListOperationConfig<any> | false
117
+ get?: GetOperationConfig<any> | false
118
+ add?: AddOperationConfig<AnyJob, any> | false
119
+ retry?: RetryOperationConfig<any> | false
120
+ cancel?: CancelOperationConfig<any> | false
121
+ remove?: RemoveOperationConfig<any> | false
122
+ }
123
+
124
+ /** Options for createJobsRouter */
125
+ export type CreateJobsRouterOptions<Jobs extends Record<string, AnyJob>> = {
126
+ jobs: Jobs
127
+ guards?: AnyGuard[]
128
+ middlewares?: AnyMiddleware[]
129
+ defaults?: DefaultOperations
130
+ overrides?: {
131
+ [K in keyof Jobs]?: JobOperations<Jobs[K]>
132
+ }
133
+ }
134
+
135
+ // ============================================================================
136
+ // Router Contract Types
137
+ // ============================================================================
138
+
139
+ /** Type-level representation of createJobItemSchema output */
140
+ type JobItemSchemaType<T extends AnyJob> = t.ObjectType<{
141
+ id: t.StringType
142
+ queueName: t.StringType
143
+ priority: OptionalType<t.NumberType>
144
+ progress: t.AnyType
145
+ name: t.StringType
146
+ data: T['input']
147
+ returnvalue: OptionalType<T['output']>
148
+ attemptsMade: t.NumberType
149
+ processedOn: OptionalType<t.NumberType>
150
+ finishedOn: OptionalType<t.NumberType>
151
+ failedReason: OptionalType<t.StringType>
152
+ stacktrace: OptionalType<t.ArrayType<t.StringType>>
153
+ status: t.StringType
154
+ }>
155
+
156
+ /** Type-level representation of createListOutputSchema output */
157
+ type ListOutputSchemaType<T extends AnyJob> = t.ObjectType<{
158
+ items: t.ArrayType<JobItemSchemaType<T>>
159
+ page: t.NumberType
160
+ limit: t.NumberType
161
+ pages: t.NumberType
162
+ total: t.NumberType
163
+ }>
164
+
165
+ /** Type-level representation of createGetOutputSchema output */
166
+ type GetOutputSchemaType<T extends AnyJob> = NullableType<JobItemSchemaType<T>>
167
+
168
+ /** Type-level representation of createAddInputSchema output */
169
+ type AddInputSchemaType<T extends AnyJob> = t.ObjectType<{
170
+ data: T['input']
171
+ jobId: OptionalType<t.StringType>
172
+ priority: OptionalType<t.NumberType>
173
+ delay: OptionalType<t.NumberType>
174
+ }>
175
+
176
+ /** Operations contract for a single job - now properly typed per job */
177
+ type JobOperationsProcedures<T extends AnyJob> = {
178
+ list: TProcedureContract<typeof listInputSchema, ListOutputSchemaType<T>>
179
+ get: TProcedureContract<typeof getInputSchema, GetOutputSchemaType<T>>
180
+ add: TProcedureContract<AddInputSchemaType<T>, typeof addOutputSchema>
181
+ retry: TProcedureContract<typeof retryInputSchema, NeverType>
182
+ cancel: TProcedureContract<typeof idInputSchema, NeverType>
183
+ remove: TProcedureContract<typeof idInputSchema, NeverType>
184
+ }
185
+
186
+ /** Router contract for a single job's operations */
187
+ type JobRouterContract<T extends AnyJob> = TRouterContract<
188
+ JobOperationsProcedures<T>
189
+ >
190
+
191
+ /** Full jobs router contract mapping job names to their operation routers */
192
+ type JobsRouterContract<Jobs extends Record<string, AnyJob>> = TRouterContract<{
193
+ [K in keyof Jobs]: JobRouterContract<Jobs[K]>
194
+ }>
195
+
196
+ /** Return type for createJobsRouter */
197
+ export type JobsRouter<Jobs extends Record<string, AnyJob>> = Router<
198
+ JobsRouterContract<Jobs>
199
+ >
200
+
201
+ // ============================================================================
202
+ // Schemas
203
+ // ============================================================================
204
+
205
+ /** Input schema for list operation */
206
+ const listInputSchema = t.object({
207
+ page: t.number().optional(),
208
+ limit: t.number().optional(),
209
+ state: t.array(t.string()).optional(),
210
+ })
211
+
212
+ /** Input schema for get operation */
213
+ const getInputSchema = t.object({ id: t.string() })
214
+
215
+ /** Output schema for add operation */
216
+ const addOutputSchema = t.object({ id: t.string(), name: t.string() })
217
+
218
+ /** Input schema for retry operation */
219
+ const retryInputSchema = t.object({
220
+ id: t.string(),
221
+ clearState: t.boolean().optional(),
222
+ })
223
+
224
+ /** Input schema for cancel/remove operations */
225
+ const idInputSchema = t.object({ id: t.string() })
226
+
227
+ /** JobItem schema for list/get responses - typed per job */
228
+ function createJobItemSchema<T extends AnyJob>(job: T): JobItemSchemaType<T> {
229
+ return t.object({
230
+ id: t.string(),
231
+ queueName: t.string(),
232
+ priority: t.number().optional(),
233
+ progress: t.any(),
234
+ name: t.string(),
235
+ data: job.input,
236
+ returnvalue: job.output.optional(),
237
+ attemptsMade: t.number(),
238
+ processedOn: t.number().optional(),
239
+ finishedOn: t.number().optional(),
240
+ failedReason: t.string().optional(),
241
+ stacktrace: t.array(t.string()).optional(),
242
+ status: t.string(),
243
+ })
244
+ }
245
+
246
+ /** Creates list output schema for a specific job */
247
+ function createListOutputSchema<T extends AnyJob>(
248
+ job: T,
249
+ ): ListOutputSchemaType<T> {
250
+ return t.object({
251
+ items: t.array(createJobItemSchema(job)),
252
+ page: t.number(),
253
+ limit: t.number(),
254
+ pages: t.number(),
255
+ total: t.number(),
256
+ })
257
+ }
258
+
259
+ /** Creates get output schema for a specific job */
260
+ function createGetOutputSchema<T extends AnyJob>(
261
+ job: T,
262
+ ): GetOutputSchemaType<T> {
263
+ return createJobItemSchema(job).nullable()
264
+ }
265
+
266
+ /** Creates add input schema for a specific job */
267
+ function createAddInputSchema<T extends AnyJob>(job: T): AddInputSchemaType<T> {
268
+ return t.object({
269
+ data: job.input,
270
+ jobId: t.string().optional(),
271
+ priority: t.number().optional(),
272
+ delay: t.number().optional(),
273
+ })
274
+ }
275
+
276
+ // ============================================================================
277
+ // Helpers
278
+ // ============================================================================
279
+
280
+ /**
281
+ * Helper function to create a type-safe operation config with dependencies.
282
+ * Use this when you need custom dependencies for hooks.
283
+ *
284
+ * @example
285
+ * ```ts
286
+ * add: jobOperation({
287
+ * dependencies: { userService, auditLog },
288
+ * beforeAdd: async (ctx, input) => {
289
+ * return { ...input, createdBy: ctx.userService.getCurrentId() }
290
+ * },
291
+ * })
292
+ * ```
293
+ */
294
+ export function jobOperation<
295
+ Deps extends Dependencies,
296
+ T extends AnyJob = AnyJob,
297
+ >(
298
+ config: BaseOperationConfig<Deps> & {
299
+ // Add hooks
300
+ beforeAdd?: (
301
+ ctx: DependencyContext<Deps>,
302
+ input: T['_']['input'],
303
+ ) => MaybePromise<T['_']['input']>
304
+ afterAdd?: (
305
+ ctx: DependencyContext<Deps>,
306
+ result: { id: string; name: string },
307
+ input: T['_']['input'],
308
+ ) => MaybePromise<void>
309
+ options?: AddQueueOptions
310
+
311
+ // Remove hooks
312
+ beforeRemove?: (
313
+ ctx: DependencyContext<Deps>,
314
+ params: { id: string },
315
+ ) => MaybePromise<void>
316
+ afterRemove?: (
317
+ ctx: DependencyContext<Deps>,
318
+ params: { id: string },
319
+ ) => MaybePromise<void>
320
+
321
+ // Retry hooks
322
+ clearState?: boolean
323
+ beforeRetry?: (
324
+ ctx: DependencyContext<Deps>,
325
+ params: { id: string; clearState?: boolean },
326
+ ) => MaybePromise<void>
327
+ afterRetry?: (
328
+ ctx: DependencyContext<Deps>,
329
+ params: { id: string; clearState?: boolean },
330
+ ) => MaybePromise<void>
331
+
332
+ // Cancel hooks
333
+ beforeCancel?: (
334
+ ctx: DependencyContext<Deps>,
335
+ params: { id: string },
336
+ ) => MaybePromise<void>
337
+ afterCancel?: (
338
+ ctx: DependencyContext<Deps>,
339
+ params: { id: string },
340
+ ) => MaybePromise<void>
341
+ },
342
+ ): typeof config {
343
+ return config
344
+ }
345
+
346
+ // ============================================================================
347
+ // Implementation
348
+ // ============================================================================
349
+
350
+ type JobManagerDeps = {
351
+ jobManager: typeof jobManager
352
+ logger: typeof CoreInjectables.logger
353
+ }
354
+
355
+ function createListProcedure(
356
+ job: AnyJob,
357
+ config: ListOperationConfig<any> = {},
358
+ shared: { guards?: AnyGuard[]; middlewares?: AnyMiddleware[] },
359
+ ): AnyProcedure {
360
+ const allGuards = [...(shared.guards ?? []), ...(config.guards ?? [])]
361
+ const allMiddlewares = [
362
+ ...(shared.middlewares ?? []),
363
+ ...(config.middlewares ?? []),
364
+ ]
365
+
366
+ const deps: JobManagerDeps = { jobManager, logger: CoreInjectables.logger }
367
+
368
+ return createProcedure({
369
+ input: listInputSchema,
370
+ output: createListOutputSchema(job),
371
+ dependencies: { ...deps, ...(config.dependencies ?? {}) },
372
+ guards: allGuards,
373
+ middlewares: allMiddlewares,
374
+ metadata: config.metadata,
375
+ timeout: config.timeout,
376
+ handler: async (ctx: DependencyContext<JobManagerDeps>, input) => {
377
+ ctx.logger.debug(
378
+ {
379
+ jobName: job.options.name,
380
+ page: input.page,
381
+ limit: input.limit,
382
+ state: input.state,
383
+ },
384
+ 'Listing jobs',
385
+ )
386
+ const result = await ctx.jobManager.list(job, {
387
+ page: input.page,
388
+ limit: input.limit,
389
+ state: input.state as JobState[],
390
+ })
391
+ ctx.logger.debug(
392
+ { jobName: job.options.name, total: result.total, pages: result.pages },
393
+ 'Jobs listed',
394
+ )
395
+ return result
396
+ },
397
+ })
398
+ }
399
+
400
+ function createGetProcedure(
401
+ job: AnyJob,
402
+ config: GetOperationConfig<any> = {},
403
+ shared: { guards?: AnyGuard[]; middlewares?: AnyMiddleware[] },
404
+ ): AnyProcedure {
405
+ const allGuards = [...(shared.guards ?? []), ...(config.guards ?? [])]
406
+ const allMiddlewares = [
407
+ ...(shared.middlewares ?? []),
408
+ ...(config.middlewares ?? []),
409
+ ]
410
+
411
+ const deps: JobManagerDeps = { jobManager, logger: CoreInjectables.logger }
412
+
413
+ return createProcedure({
414
+ input: getInputSchema,
415
+ output: createGetOutputSchema(job),
416
+ dependencies: { ...deps, ...(config.dependencies ?? {}) },
417
+ guards: allGuards,
418
+ middlewares: allMiddlewares,
419
+ metadata: config.metadata,
420
+ timeout: config.timeout,
421
+ handler: async (ctx: DependencyContext<JobManagerDeps>, input) => {
422
+ ctx.logger.trace(
423
+ { jobName: job.options.name, id: input.id },
424
+ 'Getting job',
425
+ )
426
+ const result = await ctx.jobManager.get(job, input.id)
427
+ ctx.logger.trace(
428
+ { jobName: job.options.name, id: input.id, found: result !== null },
429
+ 'Job retrieved',
430
+ )
431
+ return result
432
+ },
433
+ })
434
+ }
435
+
436
+ function createAddProcedure(
437
+ job: AnyJob,
438
+ config: AddOperationConfig<AnyJob, any> = {},
439
+ shared: { guards?: AnyGuard[]; middlewares?: AnyMiddleware[] },
440
+ ): AnyProcedure {
441
+ const allGuards = [...(shared.guards ?? []), ...(config.guards ?? [])]
442
+ const allMiddlewares = [
443
+ ...(shared.middlewares ?? []),
444
+ ...(config.middlewares ?? []),
445
+ ]
446
+
447
+ const deps: JobManagerDeps = { jobManager, logger: CoreInjectables.logger }
448
+
449
+ return createProcedure({
450
+ input: createAddInputSchema(job),
451
+ output: addOutputSchema,
452
+ dependencies: { ...deps, ...(config.dependencies ?? {}) },
453
+ guards: allGuards,
454
+ middlewares: allMiddlewares,
455
+ metadata: config.metadata,
456
+ timeout: config.timeout,
457
+ handler: async (ctx: DependencyContext<JobManagerDeps>, input) => {
458
+ let jobData = input.data
459
+
460
+ ctx.logger.debug(
461
+ {
462
+ jobName: job.options.name,
463
+ jobId: input.jobId,
464
+ priority: input.priority,
465
+ },
466
+ 'Adding job',
467
+ )
468
+
469
+ // Call beforeAdd hook if provided
470
+ if (config.beforeAdd) {
471
+ ctx.logger.debug(
472
+ { jobName: job.options.name },
473
+ 'Running beforeAdd hook',
474
+ )
475
+ jobData = await config.beforeAdd(ctx as any, jobData)
476
+ }
477
+
478
+ const queueResult = await ctx.jobManager.add(job, jobData, {
479
+ jobId: input.jobId,
480
+ priority: input.priority ?? config.options?.priority,
481
+ delay: input.delay ?? config.options?.delay,
482
+ attempts: config.options?.attempts,
483
+ })
484
+
485
+ const result = { id: queueResult.id!, name: queueResult.name }
486
+
487
+ ctx.logger.info({ jobName: job.options.name, id: result.id }, 'Job added')
488
+
489
+ // Call afterAdd hook if provided
490
+ if (config.afterAdd) {
491
+ ctx.logger.trace(
492
+ { jobName: job.options.name, id: result.id },
493
+ 'Running afterAdd hook',
494
+ )
495
+ await config.afterAdd(ctx as any, result, jobData)
496
+ }
497
+
498
+ return result
499
+ },
500
+ })
501
+ }
502
+
503
+ function createRetryProcedure(
504
+ job: AnyJob,
505
+ config: RetryOperationConfig<any> = {},
506
+ shared: { guards?: AnyGuard[]; middlewares?: AnyMiddleware[] },
507
+ ): AnyProcedure {
508
+ const allGuards = [...(shared.guards ?? []), ...(config.guards ?? [])]
509
+ const allMiddlewares = [
510
+ ...(shared.middlewares ?? []),
511
+ ...(config.middlewares ?? []),
512
+ ]
513
+
514
+ const deps: JobManagerDeps = { jobManager, logger: CoreInjectables.logger }
515
+
516
+ return createProcedure({
517
+ input: retryInputSchema,
518
+ dependencies: { ...deps, ...(config.dependencies ?? {}) },
519
+ guards: allGuards,
520
+ middlewares: allMiddlewares,
521
+ metadata: config.metadata,
522
+ timeout: config.timeout,
523
+ handler: async (ctx: DependencyContext<JobManagerDeps>, input) => {
524
+ const clearState = input.clearState ?? config.clearState
525
+
526
+ ctx.logger.debug(
527
+ { jobName: job.options.name, id: input.id, clearState },
528
+ 'Retrying job',
529
+ )
530
+
531
+ // Call beforeRetry hook if provided
532
+ if (config.beforeRetry) {
533
+ ctx.logger.trace(
534
+ { jobName: job.options.name, id: input.id },
535
+ 'Running beforeRetry hook',
536
+ )
537
+ await config.beforeRetry(ctx as any, { id: input.id, clearState })
538
+ }
539
+
540
+ await ctx.jobManager.retry(job, input.id, { clearState })
541
+
542
+ ctx.logger.info(
543
+ { jobName: job.options.name, id: input.id },
544
+ 'Job retried',
545
+ )
546
+
547
+ // Call afterRetry hook if provided
548
+ if (config.afterRetry) {
549
+ ctx.logger.trace(
550
+ { jobName: job.options.name, id: input.id },
551
+ 'Running afterRetry hook',
552
+ )
553
+ await config.afterRetry(ctx as any, { id: input.id, clearState })
554
+ }
555
+ },
556
+ })
557
+ }
558
+
559
+ function createCancelProcedure(
560
+ job: AnyJob,
561
+ config: CancelOperationConfig<any> = {},
562
+ shared: { guards?: AnyGuard[]; middlewares?: AnyMiddleware[] },
563
+ ): AnyProcedure {
564
+ const allGuards = [...(shared.guards ?? []), ...(config.guards ?? [])]
565
+ const allMiddlewares = [
566
+ ...(shared.middlewares ?? []),
567
+ ...(config.middlewares ?? []),
568
+ ]
569
+
570
+ const deps: JobManagerDeps = { jobManager, logger: CoreInjectables.logger }
571
+
572
+ return createProcedure({
573
+ input: idInputSchema,
574
+ dependencies: { ...deps, ...(config.dependencies ?? {}) },
575
+ guards: allGuards,
576
+ middlewares: allMiddlewares,
577
+ metadata: config.metadata,
578
+ timeout: config.timeout,
579
+ handler: async (ctx: DependencyContext<JobManagerDeps>, input) => {
580
+ ctx.logger.debug(
581
+ { jobName: job.options.name, id: input.id },
582
+ 'Canceling job',
583
+ )
584
+
585
+ // Call beforeCancel hook if provided
586
+ if (config.beforeCancel) {
587
+ ctx.logger.trace(
588
+ { jobName: job.options.name, id: input.id },
589
+ 'Running beforeCancel hook',
590
+ )
591
+ await config.beforeCancel(ctx as any, { id: input.id })
592
+ }
593
+
594
+ await ctx.jobManager.cancel(job, input.id)
595
+
596
+ ctx.logger.info(
597
+ { jobName: job.options.name, id: input.id },
598
+ 'Job canceled',
599
+ )
600
+
601
+ // Call afterCancel hook if provided
602
+ if (config.afterCancel) {
603
+ ctx.logger.trace(
604
+ { jobName: job.options.name, id: input.id },
605
+ 'Running afterCancel hook',
606
+ )
607
+ await config.afterCancel(ctx as any, { id: input.id })
608
+ }
609
+ },
610
+ })
611
+ }
612
+
613
+ function createRemoveProcedure(
614
+ job: AnyJob,
615
+ config: RemoveOperationConfig<any> = {},
616
+ shared: { guards?: AnyGuard[]; middlewares?: AnyMiddleware[] },
617
+ ): AnyProcedure {
618
+ const allGuards = [...(shared.guards ?? []), ...(config.guards ?? [])]
619
+ const allMiddlewares = [
620
+ ...(shared.middlewares ?? []),
621
+ ...(config.middlewares ?? []),
622
+ ]
623
+
624
+ const deps: JobManagerDeps = { jobManager, logger: CoreInjectables.logger }
625
+
626
+ return createProcedure({
627
+ input: idInputSchema,
628
+ dependencies: { ...deps, ...(config.dependencies ?? {}) },
629
+ guards: allGuards,
630
+ middlewares: allMiddlewares,
631
+ metadata: config.metadata,
632
+ timeout: config.timeout,
633
+ handler: async (ctx: DependencyContext<JobManagerDeps>, input) => {
634
+ ctx.logger.debug(
635
+ { jobName: job.options.name, id: input.id },
636
+ 'Removing job',
637
+ )
638
+
639
+ // Call beforeRemove hook if provided
640
+ if (config.beforeRemove) {
641
+ ctx.logger.trace(
642
+ { jobName: job.options.name, id: input.id },
643
+ 'Running beforeRemove hook',
644
+ )
645
+ await config.beforeRemove(ctx as any, { id: input.id })
646
+ }
647
+
648
+ await ctx.jobManager.remove(job, input.id)
649
+
650
+ ctx.logger.info(
651
+ { jobName: job.options.name, id: input.id },
652
+ 'Job removed',
653
+ )
654
+
655
+ // Call afterRemove hook if provided
656
+ if (config.afterRemove) {
657
+ ctx.logger.trace(
658
+ { jobName: job.options.name, id: input.id },
659
+ 'Running afterRemove hook',
660
+ )
661
+ await config.afterRemove(ctx as any, { id: input.id })
662
+ }
663
+ },
664
+ })
665
+ }
666
+
667
+ // ============================================================================
668
+ // Main router factory
669
+ // ============================================================================
670
+
671
+ /**
672
+ * Merge default operations with job-specific overrides
673
+ */
674
+ function mergeOperations(
675
+ defaults: DefaultOperations = {},
676
+ overrides: JobOperations = {},
677
+ ): JobOperations {
678
+ const result: JobOperations = {}
679
+
680
+ const ops = ['list', 'get', 'add', 'retry', 'cancel', 'remove'] as const
681
+
682
+ for (const op of ops) {
683
+ const override = overrides[op]
684
+ const defaultOp = defaults[op]
685
+
686
+ if (override === false) {
687
+ result[op] = false
688
+ } else if (override !== undefined) {
689
+ // Override provided - use it (merged with default base config if both are objects)
690
+ if (
691
+ defaultOp &&
692
+ (defaultOp as unknown) !== false &&
693
+ typeof override === 'object'
694
+ ) {
695
+ result[op] = {
696
+ ...(defaultOp as object),
697
+ ...(override as object),
698
+ guards: [
699
+ ...((defaultOp as any).guards ?? []),
700
+ ...((override as any).guards ?? []),
701
+ ],
702
+ middlewares: [
703
+ ...((defaultOp as any).middlewares ?? []),
704
+ ...((override as any).middlewares ?? []),
705
+ ],
706
+ } as any
707
+ } else {
708
+ result[op] = override as any
709
+ }
710
+ } else if (defaultOp !== undefined) {
711
+ result[op] = defaultOp as any
712
+ }
713
+ // else: undefined = use default behavior (enabled with empty config)
714
+ }
715
+
716
+ return result
717
+ }
718
+
719
+ /**
720
+ * Creates a router with CRUD-like operations for multiple jobs.
721
+ *
722
+ * @example
723
+ * ```ts
724
+ * const jobsRouter = createJobsRouter({
725
+ * jobs: [userJob, emailJob] as const,
726
+ * guards: [authGuard],
727
+ * defaults: {
728
+ * list: {},
729
+ * get: {},
730
+ * add: {},
731
+ * retry: { guards: [adminGuard] },
732
+ * cancel: { guards: [adminGuard] },
733
+ * remove: false,
734
+ * },
735
+ * overrides: {
736
+ * userProcessing: {
737
+ * add: jobOperation({
738
+ * dependencies: { userService },
739
+ * beforeAdd: async (ctx, input) => ({
740
+ * ...input,
741
+ * userId: ctx.userService.getCurrentId(),
742
+ * }),
743
+ * }),
744
+ * },
745
+ * },
746
+ * })
747
+ *
748
+ * // Use in your router
749
+ * createRouter({
750
+ * routes: {
751
+ * jobs: jobsRouter,
752
+ * }
753
+ * })
754
+ * ```
755
+ */
756
+ export function createJobsRouter<const Jobs extends Record<string, AnyJob>>(
757
+ options: CreateJobsRouterOptions<Jobs>,
758
+ ): JobsRouter<Jobs> {
759
+ const {
760
+ jobs,
761
+ guards: sharedGuards = [],
762
+ middlewares: sharedMiddlewares = [],
763
+ defaults = {},
764
+ overrides = {},
765
+ } = options
766
+
767
+ const routes: Record<string, AnyRouter> = {}
768
+ const shared = { guards: sharedGuards, middlewares: sharedMiddlewares }
769
+
770
+ for (const jobName in jobs) {
771
+ const job = jobs[jobName]
772
+ const jobOverrides =
773
+ (overrides as Record<string, JobOperations>)[jobName] ?? {}
774
+
775
+ // Merge defaults with job-specific overrides
776
+ const operations = mergeOperations(defaults, jobOverrides)
777
+
778
+ const jobRoutes: Record<string, AnyProcedure> = {}
779
+
780
+ // Generate each enabled operation
781
+ if (operations.list !== false) {
782
+ jobRoutes.list = createListProcedure(
783
+ job,
784
+ operations.list as ListOperationConfig,
785
+ shared,
786
+ )
787
+ }
788
+
789
+ if (operations.get !== false) {
790
+ jobRoutes.get = createGetProcedure(
791
+ job,
792
+ operations.get as GetOperationConfig,
793
+ shared,
794
+ )
795
+ }
796
+
797
+ if (operations.add !== false) {
798
+ jobRoutes.add = createAddProcedure(
799
+ job,
800
+ operations.add as AddOperationConfig,
801
+ shared,
802
+ )
803
+ }
804
+
805
+ if (operations.retry !== false) {
806
+ jobRoutes.retry = createRetryProcedure(
807
+ job,
808
+ operations.retry as RetryOperationConfig,
809
+ shared,
810
+ )
811
+ }
812
+
813
+ if (operations.cancel !== false) {
814
+ jobRoutes.cancel = createCancelProcedure(
815
+ job,
816
+ operations.cancel as CancelOperationConfig,
817
+ shared,
818
+ )
819
+ }
820
+
821
+ if (operations.remove !== false) {
822
+ jobRoutes.remove = createRemoveProcedure(
823
+ job,
824
+ operations.remove as RemoveOperationConfig,
825
+ shared,
826
+ )
827
+ }
828
+
829
+ // Create router for this job (named by job name)
830
+ routes[jobName] = createRouter({ routes: jobRoutes })
831
+ }
832
+
833
+ // Return router containing all job routers
834
+ return createRouter({ routes }) as JobsRouter<Jobs>
835
+ }