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.
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +3 -2
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +51 -0
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -0
- package/dist/entrypoints/cli.d.ts +1 -0
- package/dist/entrypoints/cli.js +1 -0
- package/dist/entrypoints/cli.js.map +1 -0
- package/dist/entrypoints/main.d.ts +5 -0
- package/dist/entrypoints/main.js +83 -15
- package/dist/entrypoints/main.js.map +1 -0
- package/dist/entrypoints/thread.d.ts +14 -0
- package/dist/entrypoints/thread.js +130 -24
- package/dist/entrypoints/thread.js.map +1 -0
- package/dist/entrypoints/worker.d.ts +3 -0
- package/dist/entrypoints/worker.js +4 -3
- package/dist/entrypoints/worker.js.map +1 -0
- package/dist/index.d.ts +69 -0
- package/dist/{_exports/index.js → index.js} +9 -5
- package/dist/index.js.map +1 -0
- package/dist/resolver.d.ts +2 -0
- package/dist/resolver.js +1 -0
- package/dist/resolver.js.map +1 -0
- package/dist/runtime/application/api/api.d.ts +49 -0
- package/dist/runtime/application/api/api.js +193 -0
- package/dist/runtime/application/api/api.js.map +1 -0
- package/dist/runtime/application/api/constants.d.ts +14 -0
- package/dist/runtime/application/api/constants.js +8 -0
- package/dist/runtime/application/api/constants.js.map +1 -0
- package/dist/runtime/application/api/filters.d.ts +14 -0
- package/dist/runtime/application/api/filters.js +11 -0
- package/dist/runtime/application/api/filters.js.map +1 -0
- package/dist/runtime/application/api/guards.d.ts +13 -0
- package/dist/runtime/application/api/guards.js +8 -0
- package/dist/runtime/application/api/guards.js.map +1 -0
- package/dist/runtime/application/api/index.d.ts +8 -0
- package/dist/runtime/application/api/index.js +9 -0
- package/dist/runtime/application/api/index.js.map +1 -0
- package/dist/runtime/application/api/middlewares.d.ts +14 -0
- package/dist/runtime/application/api/middlewares.js +12 -0
- package/dist/runtime/application/api/middlewares.js.map +1 -0
- package/dist/runtime/application/api/procedure.d.ts +67 -0
- package/dist/runtime/application/api/procedure.js +50 -0
- package/dist/runtime/application/api/procedure.js.map +1 -0
- package/dist/runtime/application/api/router.d.ts +71 -0
- package/dist/runtime/application/api/router.js +51 -0
- package/dist/runtime/application/api/router.js.map +1 -0
- package/dist/runtime/application/api/types.d.ts +32 -0
- package/dist/runtime/application/api/types.js +2 -0
- package/dist/runtime/application/api/types.js.map +1 -0
- package/dist/runtime/application/config.d.ts +26 -0
- package/dist/runtime/application/config.js +21 -0
- package/dist/runtime/application/config.js.map +1 -0
- package/dist/runtime/application/constants.d.ts +2 -0
- package/dist/runtime/application/constants.js +2 -0
- package/dist/runtime/application/constants.js.map +1 -0
- package/dist/runtime/application/hook.d.ts +19 -0
- package/dist/runtime/application/hook.js +11 -0
- package/dist/runtime/application/hook.js.map +1 -0
- package/dist/runtime/application/hooks.d.ts +3 -0
- package/dist/runtime/application/hooks.js +4 -0
- package/dist/runtime/application/hooks.js.map +1 -0
- package/dist/runtime/application/index.d.ts +5 -0
- package/dist/runtime/application/index.js +6 -0
- package/dist/runtime/application/index.js.map +1 -0
- package/dist/runtime/constants.d.ts +8 -0
- package/dist/runtime/constants.js +5 -0
- package/dist/runtime/constants.js.map +1 -0
- package/dist/runtime/core/hooks.d.ts +4 -0
- package/dist/runtime/core/hooks.js +4 -0
- package/dist/runtime/core/hooks.js.map +1 -0
- package/dist/runtime/core/plugin.d.ts +8 -0
- package/dist/runtime/core/plugin.js +4 -0
- package/dist/runtime/core/plugin.js.map +1 -0
- package/dist/runtime/core/runtime.d.ts +27 -0
- package/dist/runtime/core/runtime.js +81 -0
- package/dist/runtime/core/runtime.js.map +1 -0
- package/dist/runtime/enums.d.ts +21 -0
- package/dist/runtime/enums.js +26 -0
- package/dist/runtime/enums.js.map +1 -0
- package/dist/runtime/index.d.ts +21 -0
- package/dist/runtime/index.js +22 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/injectables.d.ts +23 -0
- package/dist/runtime/injectables.js +20 -0
- package/dist/runtime/injectables.js.map +1 -0
- package/dist/runtime/jobs/job.d.ts +132 -0
- package/dist/runtime/jobs/job.js +68 -0
- package/dist/runtime/jobs/job.js.map +1 -0
- package/dist/runtime/jobs/manager.d.ts +113 -0
- package/dist/runtime/jobs/manager.js +210 -0
- package/dist/runtime/jobs/manager.js.map +1 -0
- package/dist/runtime/jobs/router.d.ts +266 -0
- package/dist/runtime/jobs/router.js +432 -0
- package/dist/runtime/jobs/router.js.map +1 -0
- package/dist/runtime/jobs/runner.d.ts +64 -0
- package/dist/runtime/jobs/runner.js +256 -0
- package/dist/runtime/jobs/runner.js.map +1 -0
- package/dist/runtime/jobs/step.d.ts +23 -0
- package/dist/runtime/jobs/step.js +18 -0
- package/dist/runtime/jobs/step.js.map +1 -0
- package/dist/runtime/jobs/ui.d.ts +3 -0
- package/dist/runtime/jobs/ui.js +17 -0
- package/dist/runtime/jobs/ui.js.map +1 -0
- package/dist/runtime/pubsub/manager.d.ts +48 -0
- package/dist/runtime/pubsub/manager.js +119 -0
- package/dist/runtime/pubsub/manager.js.map +1 -0
- package/dist/runtime/pubsub/redis.d.ts +16 -0
- package/dist/runtime/pubsub/redis.js +98 -0
- package/dist/runtime/pubsub/redis.js.map +1 -0
- package/dist/runtime/scheduler/index.d.ts +22 -0
- package/dist/runtime/scheduler/index.js +20 -0
- package/dist/runtime/scheduler/index.js.map +1 -0
- package/dist/runtime/server/applications.d.ts +52 -0
- package/dist/runtime/server/applications.js +133 -0
- package/dist/runtime/server/applications.js.map +1 -0
- package/dist/runtime/server/config.d.ts +121 -0
- package/dist/runtime/server/config.js +33 -0
- package/dist/runtime/server/config.js.map +1 -0
- package/dist/runtime/server/jobs.d.ts +41 -0
- package/dist/runtime/server/jobs.js +181 -0
- package/dist/runtime/server/jobs.js.map +1 -0
- package/dist/runtime/server/pool.d.ts +54 -0
- package/dist/runtime/server/pool.js +194 -0
- package/dist/runtime/server/pool.js.map +1 -0
- package/dist/runtime/server/proxy.d.ts +21 -0
- package/dist/runtime/server/proxy.js +79 -0
- package/dist/runtime/server/proxy.js.map +1 -0
- package/dist/runtime/server/server.d.ts +53 -0
- package/dist/runtime/server/server.js +90 -0
- package/dist/runtime/server/server.js.map +1 -0
- package/dist/runtime/store/index.d.ts +3 -0
- package/dist/runtime/store/index.js +23 -0
- package/dist/runtime/store/index.js.map +1 -0
- package/dist/runtime/types.d.ts +103 -0
- package/dist/runtime/types.js +2 -0
- package/dist/runtime/types.js.map +1 -0
- package/dist/runtime/workers/application.d.ts +47 -0
- package/dist/runtime/workers/application.js +162 -0
- package/dist/runtime/workers/application.js.map +1 -0
- package/dist/runtime/workers/base.d.ts +16 -0
- package/dist/runtime/workers/base.js +46 -0
- package/dist/runtime/workers/base.js.map +1 -0
- package/dist/runtime/workers/cli.d.ts +1 -0
- package/dist/runtime/workers/cli.js +2 -0
- package/dist/runtime/workers/cli.js.map +1 -0
- package/dist/runtime/workers/job.d.ts +20 -0
- package/dist/runtime/workers/job.js +172 -0
- package/dist/runtime/workers/job.js.map +1 -0
- package/dist/typings.d.ts +5 -0
- package/dist/typings.js +4 -3
- package/dist/typings.js.map +1 -0
- package/dist/vite/builder.d.ts +5 -0
- package/dist/vite/builder.js +5 -1
- package/dist/vite/builder.js.map +1 -0
- package/dist/vite/config.d.ts +28 -0
- package/dist/vite/config.js +1 -0
- package/dist/vite/config.js.map +1 -0
- package/dist/vite/plugins.d.ts +2 -0
- package/dist/vite/plugins.js +1 -0
- package/dist/vite/plugins.js.map +1 -0
- package/dist/vite/runners/worker.d.ts +4 -0
- package/dist/vite/runners/worker.js +1 -0
- package/dist/vite/runners/worker.js.map +1 -0
- package/dist/vite/server.d.ts +3 -0
- package/dist/vite/server.js +6 -1
- package/dist/vite/server.js.map +1 -0
- package/dist/vite/servers/main.d.ts +8 -0
- package/dist/vite/servers/main.js +1 -0
- package/dist/vite/servers/main.js.map +1 -0
- package/dist/vite/servers/worker.d.ts +11 -0
- package/dist/vite/servers/worker.js +28 -0
- package/dist/vite/servers/worker.js.map +1 -0
- package/package.json +31 -18
- package/src/cli.ts +144 -0
- package/src/config.ts +64 -0
- package/src/entrypoints/cli.ts +13 -0
- package/src/entrypoints/main.ts +200 -0
- package/src/entrypoints/thread.ts +184 -0
- package/src/entrypoints/worker.ts +48 -0
- package/src/index.ts +82 -0
- package/src/resolver.ts +16 -0
- package/src/runtime/application/api/api.ts +265 -0
- package/src/runtime/application/api/constants.ts +22 -0
- package/src/runtime/application/api/filters.ts +39 -0
- package/src/runtime/application/api/guards.ts +29 -0
- package/src/runtime/application/api/index.ts +8 -0
- package/src/runtime/application/api/middlewares.ts +37 -0
- package/src/runtime/application/api/procedure.ts +229 -0
- package/src/runtime/application/api/router.ts +193 -0
- package/src/runtime/application/api/types.ts +124 -0
- package/src/runtime/application/config.ts +69 -0
- package/src/runtime/application/constants.ts +4 -0
- package/src/runtime/application/hook.ts +51 -0
- package/src/runtime/application/hooks.ts +3 -0
- package/src/runtime/application/index.ts +5 -0
- package/src/runtime/constants.ts +13 -0
- package/src/runtime/core/hooks.ts +5 -0
- package/src/runtime/core/plugin.ts +13 -0
- package/src/runtime/core/runtime.ts +109 -0
- package/src/runtime/enums.ts +24 -0
- package/src/runtime/index.ts +21 -0
- package/src/runtime/injectables.ts +61 -0
- package/src/runtime/jobs/job.ts +370 -0
- package/src/runtime/jobs/manager.ts +348 -0
- package/src/runtime/jobs/router.ts +896 -0
- package/src/runtime/jobs/runner.ts +320 -0
- package/src/runtime/jobs/step.ts +66 -0
- package/src/runtime/jobs/ui.ts +21 -0
- package/src/runtime/pubsub/manager.ts +211 -0
- package/src/runtime/pubsub/redis.ts +108 -0
- package/src/runtime/scheduler/index.ts +39 -0
- package/src/runtime/server/applications.ts +210 -0
- package/src/runtime/server/config.ts +158 -0
- package/src/runtime/server/jobs.ts +250 -0
- package/src/runtime/server/pool.ts +260 -0
- package/src/runtime/server/proxy.ts +118 -0
- package/src/runtime/server/server.ts +155 -0
- package/src/runtime/store/index.ts +30 -0
- package/src/runtime/types.ts +93 -0
- package/src/runtime/workers/application.ts +209 -0
- package/src/runtime/workers/base.ts +68 -0
- package/src/runtime/workers/cli.ts +0 -0
- package/src/runtime/workers/job.ts +153 -0
- package/src/typings.ts +30 -0
- package/src/vite/builder.ts +122 -0
- package/src/vite/config.ts +45 -0
- package/src/vite/plugins.ts +26 -0
- package/src/vite/runners/worker.ts +57 -0
- package/src/vite/server.ts +39 -0
- package/src/vite/servers/main.ts +34 -0
- package/src/vite/servers/worker.ts +143 -0
- package/dist/_exports/application.js +0 -1
- package/dist/_exports/common.js +0 -1
- package/dist/_exports/contract.js +0 -2
- package/dist/_exports/core.js +0 -1
- package/dist/_exports/gateway.js +0 -1
- package/dist/_exports/http-transport/bun.js +0 -1
- package/dist/_exports/http-transport/deno.js +0 -1
- package/dist/_exports/http-transport/node.js +0 -1
- package/dist/_exports/http-transport.js +0 -1
- package/dist/_exports/json-format.js +0 -1
- package/dist/_exports/protocol/client.js +0 -1
- package/dist/_exports/protocol/server.js +0 -1
- package/dist/_exports/protocol.js +0 -1
- package/dist/_exports/runtime/types.js +0 -1
- package/dist/_exports/runtime.js +0 -1
- package/dist/_exports/type.js +0 -2
- package/dist/_exports/ws-transport/bun.js +0 -1
- package/dist/_exports/ws-transport/deno.js +0 -1
- package/dist/_exports/ws-transport/node.js +0 -1
- package/dist/_exports/ws-transport.js +0 -1
- 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
|
+
}
|