Skip to content

Commit 668b075

Browse files
committed
feat(@sirutils/wizard): middleware
1 parent f5b2a55 commit 668b075

File tree

5 files changed

+202
-13
lines changed

5 files changed

+202
-13
lines changed

packages/wizard/src/definitions/wizard.ts

+60-5
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ declare global {
2626
}
2727

2828
interface WizardServices {}
29+
interface WizardMiddlewares {}
2930

3031
namespace Wizard {
3132
interface Options {
@@ -51,6 +52,7 @@ declare global {
5152
interface BaseApi {
5253
broker: ServiceBroker
5354
gateway: MoleculerService
55+
middleware: MoleculerService
5456
}
5557

5658
interface ServiceOptions<
@@ -67,7 +69,7 @@ declare global {
6769

6870
actions?: R
6971

70-
created?: <B, P, Q>(ctx: Sirutils.Wizard.ActionContext<B, P, Q>) => BlobType
72+
created?: <B, P, Q, S>(ctx: Sirutils.Wizard.ActionContext<B, P, Q, S>) => BlobType
7173
}
7274

7375
type ActionNames = {
@@ -97,6 +99,14 @@ declare global {
9799
: never
98100
: never
99101

102+
type ExtractShareContent<M> = M extends keyof Sirutils.WizardMiddlewares
103+
? Sirutils.WizardMiddlewares[M] extends Sirutils.Wizard.MiddlewareSchema<infer S, BlobType>
104+
? S
105+
: never
106+
: M extends Sirutils.Wizard.MiddlewareSchema<infer S, BlobType>
107+
? S
108+
: never
109+
100110
interface ServiceApi {
101111
service: <
102112
const T extends string,
@@ -136,7 +146,7 @@ declare global {
136146
},
137147
]
138148

139-
interface ActionContext<B, P, Q> {
149+
interface ActionContext<B, P, Q, S> {
140150
logger: Moleculer.LoggerInstance
141151
body: Simplify<
142152
(B extends Sirutils.Schema.ValidationSchema<BlobType>
@@ -154,23 +164,41 @@ declare global {
154164
res?: ServerResponse
155165
streams?: Sirutils.Wizard.StreamData[]
156166
raw?: MoleculerContext
167+
share?: Partial<Pick<ContextShare, S extends keyof ContextShare ? S : never>>
157168
}
169+
interface MiddlewareContext<B, P, Q, S> extends Omit<ActionContext<B, P, Q, S>, 'share'> {
170+
share: Pick<ContextShare, S extends keyof ContextShare ? S : never>
171+
}
172+
173+
interface ContextShare {}
158174

159175
interface ActionSchema<B, P, Q, R> extends MoleculerActionSchema {}
176+
interface MiddlewareSchema<S extends keyof ContextShare, R> {
177+
logger: unknown
178+
share: S[]
179+
handler: Sirutils.Wizard.MiddlewareHandler<S, R>
180+
}
160181

161182
type ActionList = Record<
162183
string,
163184
Sirutils.Wizard.ActionSchema<BlobType, BlobType, BlobType, BlobType>
164185
>
165186

166-
type ActionHandler<B, P, Q, R> = (ctx: Sirutils.Wizard.ActionContext<B, P, Q>) => R
187+
type ActionHandler<B, P, Q, S, R> = (ctx: Sirutils.Wizard.ActionContext<B, P, Q, S>) => R
188+
type MiddlewareHandler<S, R> = (
189+
ctx: Sirutils.Wizard.MiddlewareContext<BlobType, BlobType, BlobType, S>,
190+
next: unknown
191+
) => R
167192

168193
interface ActionApi {
169194
createAction: <
170195
const B extends Sirutils.Schema.ValidationSchema<BlobType>,
171196
const P extends Sirutils.Schema.ValidationSchema<BlobType>,
172197
const Q extends Sirutils.Schema.ValidationSchema<BlobType>,
173198
Hr,
199+
const M extends
200+
| keyof Sirutils.WizardMiddlewares
201+
| Sirutils.Wizard.MiddlewareSchema<keyof Sirutils.Wizard.ContextShare, Hr> = never,
174202
>(
175203
meta: {
176204
body?: B
@@ -179,9 +207,16 @@ declare global {
179207
rest?: true | string | string[]
180208
cache?: boolean | CacherOptions
181209
stream?: boolean
210+
middlewares?: M[]
182211
multipart?: formidable.Options | boolean
183212
},
184-
handler: Sirutils.Wizard.ActionHandler<NoInfer<B>, NoInfer<P>, NoInfer<Q>, Hr>
213+
handler: Sirutils.Wizard.ActionHandler<
214+
NoInfer<B>,
215+
NoInfer<P>,
216+
NoInfer<Q>,
217+
ExtractShareContent<M>,
218+
Hr
219+
>
185220
) => (
186221
serviceOptions: Sirutils.Wizard.ServiceOptions<BlobType, BlobType, BlobType>,
187222
actionName: string
@@ -199,9 +234,29 @@ declare global {
199234
>
200235
}
201236

237+
interface MiddlewareApi {
238+
createMiddleware: <Hr, const S extends keyof ContextShare = never>(
239+
meta: {
240+
name?: keyof WizardMiddlewares
241+
share?: S[]
242+
},
243+
handler: Sirutils.Wizard.MiddlewareHandler<S, Hr>
244+
) => Sirutils.Wizard.MiddlewareSchema<S, Hr>
245+
processMiddlewares: (
246+
ctx: Sirutils.Wizard.ActionContext<BlobType, BlobType, BlobType, BlobType>,
247+
middlewares: (
248+
| keyof WizardMiddlewares
249+
| Sirutils.Wizard.MiddlewareSchema<keyof ContextShare, BlobType>
250+
)[]
251+
) => Promise<{ contiune: true } | { contiune: false; returnedData: BlobType }>
252+
}
253+
202254
type Context = Sirutils.PluginSystem.Context<
203255
Sirutils.Wizard.Options,
204-
Sirutils.Wizard.BaseApi & Sirutils.Wizard.ServiceApi & Sirutils.Wizard.ActionApi
256+
Sirutils.Wizard.BaseApi &
257+
Sirutils.Wizard.ServiceApi &
258+
Sirutils.Wizard.ActionApi &
259+
Sirutils.Wizard.MiddlewareApi
205260
>
206261
}
207262
}

packages/wizard/src/tag.ts

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const wizardTags = {
88
plugin: createTag('plugin'),
99
service: createTag('service'),
1010
action: createTag('action'),
11+
middleware: createTag('middleware'),
1112
httpMixin: createTag('http-mixin'),
1213
getDetails: createTag('get-details'),
1314

packages/wizard/src/utils/create.ts

+5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { wizardTags } from '../tag'
99
import { actionActions } from './internals/action'
1010
import { WizardRegenerator } from './internals/error'
1111
import { WizardLogger } from './internals/logger'
12+
import { middlewareActions } from './internals/middleware'
1213
import { serviceActions } from './internals/service'
1314

1415
export const createWizard = createPlugin<Sirutils.Wizard.Options, Sirutils.Wizard.BaseApi>(
@@ -149,6 +150,9 @@ export const createWizard = createPlugin<Sirutils.Wizard.Options, Sirutils.Wizar
149150
},
150151
},
151152
}),
153+
middleware: broker.createService({
154+
name: 'middleware',
155+
}),
152156
methods: {
153157
reformatError(err: BlobType) {
154158
return err
@@ -160,4 +164,5 @@ export const createWizard = createPlugin<Sirutils.Wizard.Options, Sirutils.Wizar
160164
)
161165
.register(serviceActions)
162166
.register(actionActions)
167+
.register(middlewareActions)
163168
.lock()

packages/wizard/src/utils/internals/action.ts

+26-8
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,12 @@ export const actionActions = createActions(
5252
ctx.params.req.method === 'DELETE'
5353

5454
// @ts-ignore
55-
const subctx: Sirutils.Wizard.ActionContext<BlobType, BlobType, BlobType> = {
55+
const subctx: Sirutils.Wizard.ActionContext<
56+
BlobType,
57+
BlobType,
58+
BlobType,
59+
BlobType
60+
> = {
5661
body: ctx.params.req.body ?? ({} as BlobType),
5762
req: ctx.params.req,
5863
res: ctx.params.res,
@@ -150,7 +155,13 @@ export const actionActions = createActions(
150155
unwrap(await bodySchema(subctx.body as BlobType), wizardTags.invalidBody)
151156
}
152157

153-
return rawHandler(subctx)
158+
const middlewaresResult = await context.api.processMiddlewares(
159+
subctx,
160+
meta.middlewares ?? []
161+
)
162+
return middlewaresResult.contiune
163+
? rawHandler(subctx)
164+
: middlewaresResult.returnedData
154165
}
155166

156167
if (meta.rest) {
@@ -176,11 +187,12 @@ export const actionActions = createActions(
176187
)
177188
}
178189

179-
const subctx: Sirutils.Wizard.ActionContext<BlobType, BlobType, BlobType> = {
180-
body: isParamsStream ? ctx.meta : ctx.params,
181-
logger: serviceLogger,
182-
raw: ctx,
183-
}
190+
const subctx: Sirutils.Wizard.ActionContext<BlobType, BlobType, BlobType, BlobType> =
191+
{
192+
body: isParamsStream ? ctx.meta : ctx.params,
193+
logger: serviceLogger,
194+
raw: ctx,
195+
}
184196

185197
if (isParamsStream) {
186198
subctx.streams = isArray(ctx.params)
@@ -191,7 +203,13 @@ export const actionActions = createActions(
191203
: [ctx.params, getDetails(ctx.params, ctx.meta.$params)]
192204
}
193205

194-
return rawHandler(subctx)
206+
const middlewaresResult = await context.api.processMiddlewares(
207+
subctx,
208+
meta.middlewares ?? []
209+
)
210+
return middlewaresResult.contiune
211+
? rawHandler(subctx)
212+
: middlewaresResult.returnedData
195213
},
196214
`${wizardTags.action}#createAction.handler.${serviceOptions.name}@${serviceOptions.version}#${actionName}` as Sirutils.ErrorValues,
197215
context.$cause
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// import fs from 'node:fs'
2+
import { type BlobType, capsule, createActions } from '@sirutils/core'
3+
4+
import { logger } from '../../internal/logger'
5+
import { createTag } from '../../internal/tag'
6+
import { wizardTags } from '../../tag'
7+
8+
import type { LoggerInstance, ServiceAction } from 'moleculer'
9+
10+
export const middlewareActions = createActions(
11+
(context: Sirutils.Wizard.Context): Sirutils.Wizard.MiddlewareApi => ({
12+
createMiddleware(meta, rawHandler) {
13+
const middlewareLogger = logger.create({
14+
defaults: {
15+
tag: createTag(`middleware.${meta.name}`),
16+
},
17+
})
18+
19+
const handler = capsule(
20+
rawHandler,
21+
`${wizardTags.middleware}#createMiddleware.handler.${meta.name}` as Sirutils.ErrorValues,
22+
context.$cause
23+
)
24+
25+
const result: Sirutils.Wizard.MiddlewareSchema<keyof Sirutils.Wizard.ContextShare, BlobType> =
26+
{
27+
logger: middlewareLogger,
28+
share: Array.from(new Set(meta.share ? meta.share : [])),
29+
handler,
30+
}
31+
32+
const settings = context.api.middleware.settings
33+
34+
if (meta.name) {
35+
if (settings.middlewareSchemas === undefined) {
36+
settings.middlewareSchemas = {}
37+
}
38+
39+
settings.middlewareSchemas[meta.name] = result
40+
41+
context.api.middleware.actions[meta.name] = handler as ServiceAction
42+
}
43+
44+
return result
45+
},
46+
async processMiddlewares(actionCtx, middlewares) {
47+
if (middlewares.length === 0) {
48+
return { contiune: true }
49+
}
50+
51+
let willContiune = true
52+
let returnedData: BlobType
53+
54+
const share: Record<string, BlobType> = {}
55+
const interCtx = {
56+
...actionCtx,
57+
share,
58+
}
59+
60+
const nextSymbol = Symbol('next-middleware')
61+
const settings = context.api.middleware.settings
62+
63+
const shareKeys: string[] = middlewares.flatMap(middleware => {
64+
if (typeof middleware === 'string') {
65+
return settings.middlewareSchemas[middleware].share
66+
}
67+
return middleware.share
68+
})
69+
70+
//biome-ignore lint/complexity/noForEach: <explanation>
71+
shareKeys.forEach(key => {
72+
if (!Object.hasOwn(share, key)) {
73+
Object.defineProperty(share, key, { writable: true })
74+
}
75+
})
76+
77+
for (const middleware of middlewares) {
78+
if (typeof middleware === 'string') {
79+
const middlewareSchema = settings.middlewareSchemas[
80+
middleware
81+
] as Sirutils.Wizard.MiddlewareSchema<keyof Sirutils.Wizard.ContextShare, BlobType>
82+
83+
interCtx.logger = middlewareSchema.logger as LoggerInstance
84+
returnedData = await middlewareSchema.handler(interCtx, nextSymbol)
85+
} else {
86+
interCtx.logger = middleware.logger as LoggerInstance
87+
returnedData = await middleware.handler(interCtx, nextSymbol)
88+
}
89+
90+
if (returnedData !== nextSymbol) {
91+
willContiune = false
92+
break
93+
}
94+
}
95+
96+
actionCtx.share = shareKeys.reduce(
97+
(acc, key) => {
98+
if (share[key] !== undefined) {
99+
acc[key] = share[key]
100+
}
101+
return acc
102+
},
103+
{} as Record<string, BlobType>
104+
)
105+
106+
return willContiune ? { contiune: willContiune } : { contiune: willContiune, returnedData }
107+
},
108+
}),
109+
wizardTags.middleware
110+
)

0 commit comments

Comments
 (0)