nmtjs 0.15.0-beta.2 → 0.15.0-beta.21

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