@effect-app/infra 2.92.3 → 2.94.0
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/CHANGELOG.md +22 -0
- package/dist/MainFiberSet.d.ts +1 -1
- package/dist/Operations.d.ts +1 -1
- package/dist/QueueMaker/sbqueue.d.ts +5 -6
- package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
- package/dist/QueueMaker/sbqueue.js +18 -22
- package/dist/RequestFiberSet.d.ts +1 -1
- package/dist/Store/service.d.ts +1 -1
- package/dist/adapters/ServiceBus.d.ts +57 -17
- package/dist/adapters/ServiceBus.d.ts.map +1 -1
- package/dist/adapters/ServiceBus.js +75 -61
- package/dist/adapters/memQueue.d.ts +1 -1
- package/dist/api/ContextProvider.d.ts +3 -3
- package/dist/api/ContextProvider.d.ts.map +1 -1
- package/dist/api/ContextProvider.js +1 -1
- package/dist/api/routing/middleware/middleware-api.d.ts +2 -1
- package/dist/api/routing/middleware/middleware-api.d.ts.map +1 -1
- package/dist/api/routing/middleware/middleware-api.js +1 -1
- package/dist/api/routing/middleware.d.ts +0 -1
- package/dist/api/routing/middleware.d.ts.map +1 -1
- package/dist/api/routing/middleware.js +1 -2
- package/dist/api/routing.d.ts +9 -13
- package/dist/api/routing.d.ts.map +1 -1
- package/dist/api/routing.js +24 -16
- package/package.json +2 -6
- package/src/QueueMaker/sbqueue.ts +33 -46
- package/src/adapters/ServiceBus.ts +141 -93
- package/src/api/ContextProvider.ts +9 -7
- package/src/api/routing/middleware/middleware-api.ts +2 -1
- package/src/api/routing/middleware.ts +0 -1
- package/src/api/routing.ts +53 -246
- package/test/contextProvider.test.ts +4 -4
- package/test/controller.test.ts +14 -10
- package/test/dist/controller/test2.test.d.ts.map +1 -0
- package/test/dist/controller.legacy2.test.d.ts.map +1 -0
- package/test/dist/controller.legacy3.test.d.ts.map +1 -0
- package/test/dist/controller.test copy.js +129 -0
- package/test/dist/controller.test.d.ts.map +1 -1
- package/test/dist/controller5.test.d.ts.map +1 -0
- package/test/dist/controller6.test.d.ts.map +1 -0
- package/test/dist/controller7.test.d.ts.map +1 -0
- package/test/dist/dynamicContext.test.d.ts.map +1 -0
- package/test/dist/filterApi.test.d.ts.map +1 -0
- package/test/dist/fixtures.d.ts +3 -3
- package/test/dist/fixtures.js +2 -2
- package/test/dist/middleware-api.test.d.ts.map +1 -0
- package/test/dist/requires.d.ts +21 -0
- package/test/dist/requires.d.ts.map +1 -0
- package/test/dist/requires.js +27 -0
- package/test/fixtures.ts +1 -1
- package/vitest.config.ts.timestamp-1711724061890-6ecedb0a07fdd.mjs +0 -0
- package/vitest.config.ts.timestamp-1711743489537-da8d9e5f66c9f.mjs +0 -0
- package/vitest.config.ts.timestamp-1711744615239-dcf257a844e01.mjs +37 -0
- package/dist/api/routing/middleware/dynamic-middleware.d.ts +0 -2
- package/dist/api/routing/middleware/dynamic-middleware.d.ts.map +0 -1
- package/dist/api/routing/middleware/dynamic-middleware.js +0 -2
- /package/{src/api/routing/middleware/dynamic-middleware.ts → vitest.config.ts.timestamp-1711656440838-19c636fe320df.mjs} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/prefer-promise-reject-errors */
|
|
2
|
-
import { type OperationOptionsBase, type ProcessErrorArgs, ServiceBusClient, type ServiceBusMessage, type ServiceBusMessageBatch, type ServiceBusReceivedMessage, type ServiceBusReceiver
|
|
2
|
+
import { type OperationOptionsBase, type ProcessErrorArgs, ServiceBusClient, type ServiceBusMessage, type ServiceBusMessageBatch, type ServiceBusReceivedMessage, type ServiceBusReceiver } from "@azure/service-bus"
|
|
3
3
|
import { Cause, Context, Effect, Exit, FiberSet, Layer, type Scope } from "effect-app"
|
|
4
4
|
import { InfraLogger } from "../logger.js"
|
|
5
5
|
|
|
@@ -18,12 +18,13 @@ function makeClient(url: string) {
|
|
|
18
18
|
)
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
export class ServiceBusClientTag extends Context.Tag("@services/Client")<ServiceBusClientTag, ServiceBusClient>() {
|
|
22
|
+
static readonly layer = (url: string) => Layer.scoped(this, makeClient(url))
|
|
23
|
+
}
|
|
23
24
|
|
|
24
|
-
function
|
|
25
|
+
function makeSender_(queueName: string) {
|
|
25
26
|
return Effect.gen(function*() {
|
|
26
|
-
const serviceBusClient = yield*
|
|
27
|
+
const serviceBusClient = yield* ServiceBusClientTag
|
|
27
28
|
|
|
28
29
|
return yield* Effect.acquireRelease(
|
|
29
30
|
Effect.sync(() => serviceBusClient.createSender(queueName)).pipe(
|
|
@@ -33,112 +34,159 @@ function makeSender(queueName: string) {
|
|
|
33
34
|
)
|
|
34
35
|
})
|
|
35
36
|
}
|
|
36
|
-
export const Sender = Context.GenericTag<ServiceBusSender>("@services/Sender")
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
const makeSender = (name: string) =>
|
|
39
|
+
Effect.gen(function*() {
|
|
40
|
+
const sender = yield* makeSender_(name)
|
|
41
|
+
const sendMessages = Effect.fnUntraced(function*(
|
|
42
|
+
messages: ServiceBusMessage | ServiceBusMessage[] | ServiceBusMessageBatch,
|
|
43
|
+
options?: Omit<OperationOptionsBase, "abortSignal">
|
|
44
|
+
) {
|
|
45
|
+
return yield* Effect.promise((abortSignal) => sender.sendMessages(messages, { ...options, abortSignal }))
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
return { name, sendMessages }
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
export class Sender extends Context.TagId("Sender")<Sender, {
|
|
52
|
+
name: string
|
|
53
|
+
sendMessages: (
|
|
54
|
+
messages: ServiceBusMessage | ServiceBusMessage[] | ServiceBusMessageBatch,
|
|
55
|
+
options?: Omit<OperationOptionsBase, "abortSignal"> | undefined
|
|
56
|
+
) => Effect.Effect<void, never, never>
|
|
57
|
+
}>() {
|
|
58
|
+
static readonly layer = (name: string) => this.toLayerScoped(makeSender(name))
|
|
41
59
|
}
|
|
42
60
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
61
|
+
export const SenderTag = <Id>() => <Key extends string>(queueName: Key) => {
|
|
62
|
+
const tag = Context.Tag(`ServiceBus.Sender.${queueName}`)<
|
|
63
|
+
Id,
|
|
64
|
+
Sender
|
|
65
|
+
>()
|
|
46
66
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
.pipe(withSpanAndLog(`ServiceBus.receiver.create ${queueName}.${sessionId}`)),
|
|
52
|
-
(r) =>
|
|
53
|
-
waitTillEmpty.pipe(
|
|
54
|
-
withSpanAndLog(`ServiceBus.receiver.waitTillEmpty ${queueName}.${sessionId}`),
|
|
55
|
-
Effect.andThen(
|
|
56
|
-
Effect.promise(() => r.close()).pipe(withSpanAndLog(`ServiceBus.receiver.close ${queueName}.${sessionId}`))
|
|
57
|
-
),
|
|
58
|
-
withSpanAndLog(`ServiceBus.receiver.release ${queueName}.${sessionId}`)
|
|
59
|
-
)
|
|
67
|
+
return Object.assign(tag, {
|
|
68
|
+
layer: Layer.scoped(
|
|
69
|
+
tag,
|
|
70
|
+
makeSender(queueName).pipe(Effect.map((_) => Sender.of(_)))
|
|
60
71
|
)
|
|
61
72
|
})
|
|
62
73
|
}
|
|
63
74
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
75
|
+
const makeReceiver = (name: string) =>
|
|
76
|
+
Effect.gen(function*() {
|
|
77
|
+
const serviceBusClient = yield* ServiceBusClientTag
|
|
78
|
+
|
|
79
|
+
const makeReceiver = Effect.fnUntraced(
|
|
80
|
+
function*(queueName: string, waitTillEmpty: Effect<void>, sessionId?: string) {
|
|
81
|
+
return yield* Effect.acquireRelease(
|
|
82
|
+
(sessionId
|
|
83
|
+
? Effect.promise(() => serviceBusClient.acceptSession(queueName, sessionId))
|
|
84
|
+
: Effect.sync(() => serviceBusClient.createReceiver(queueName)))
|
|
85
|
+
.pipe(withSpanAndLog(`ServiceBus.receiver.create ${queueName}.${sessionId}`)),
|
|
86
|
+
(r) =>
|
|
87
|
+
waitTillEmpty.pipe(
|
|
88
|
+
withSpanAndLog(`ServiceBus.receiver.waitTillEmpty ${queueName}.${sessionId}`),
|
|
89
|
+
Effect.andThen(
|
|
90
|
+
Effect.promise(() => r.close()).pipe(
|
|
91
|
+
withSpanAndLog(`ServiceBus.receiver.close ${queueName}.${sessionId}`)
|
|
92
|
+
)
|
|
93
|
+
),
|
|
94
|
+
withSpanAndLog(`ServiceBus.receiver.release ${queueName}.${sessionId}`)
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
const make = (waitTillEmpty: Effect<void>) => makeReceiver(name, waitTillEmpty)
|
|
101
|
+
|
|
102
|
+
const makeSession = (sessionId: string, waitTillEmpty: Effect<void>) => makeReceiver(name, waitTillEmpty, sessionId)
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
name,
|
|
106
|
+
make,
|
|
107
|
+
makeSession,
|
|
108
|
+
subscribe: Effect.fnUntraced(function*<RMsg, RErr>(hndlr: MessageHandlers<RMsg, RErr>, sessionId?: string) {
|
|
109
|
+
const fs = yield* FiberSet.make()
|
|
110
|
+
const fr = yield* FiberSet.runtime(fs)<RMsg | RErr>()
|
|
111
|
+
const wait = Effect
|
|
112
|
+
.gen(function*() {
|
|
113
|
+
if ((yield* FiberSet.size(fs)) > 0) {
|
|
114
|
+
yield* InfraLogger.logDebug("Waiting ServiceBusFiberSet to be empty: " + (yield* FiberSet.size(fs)))
|
|
115
|
+
}
|
|
116
|
+
while ((yield* FiberSet.size(fs)) > 0) yield* Effect.sleep("250 millis")
|
|
117
|
+
})
|
|
118
|
+
const r = yield* sessionId
|
|
119
|
+
? makeSession(
|
|
120
|
+
sessionId,
|
|
121
|
+
wait
|
|
122
|
+
)
|
|
123
|
+
: make(wait)
|
|
124
|
+
|
|
125
|
+
const runEffect = <E>(effect: Effect<void, E, RMsg | RErr>) =>
|
|
126
|
+
new Promise<void>((resolve, reject) =>
|
|
127
|
+
fr(effect)
|
|
128
|
+
.addObserver((exit) => {
|
|
129
|
+
if (Exit.isSuccess(exit)) {
|
|
130
|
+
resolve(exit.value)
|
|
131
|
+
} else {
|
|
132
|
+
// disable @typescript-eslint/prefer-promise-reject-errors
|
|
133
|
+
reject(Cause.pretty(exit.cause, { renderErrorCause: true }))
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
)
|
|
137
|
+
yield* Effect.acquireRelease(
|
|
138
|
+
Effect
|
|
139
|
+
.sync(() =>
|
|
140
|
+
r
|
|
141
|
+
.subscribe({
|
|
142
|
+
processError: (err) =>
|
|
143
|
+
runEffect(
|
|
144
|
+
hndlr
|
|
145
|
+
.processError(err)
|
|
146
|
+
.pipe(
|
|
147
|
+
Effect.catchAllCause((cause) => Effect.logError(`ServiceBus Error ${sessionId}`, cause))
|
|
148
|
+
)
|
|
149
|
+
),
|
|
150
|
+
processMessage: (msg) => runEffect(hndlr.processMessage(msg))
|
|
151
|
+
// DO NOT CATCH ERRORS here as they should return to the queue!
|
|
152
|
+
})
|
|
153
|
+
)
|
|
154
|
+
.pipe(withSpanAndLog(`ServiceBus.subscription.create ${sessionId}`)),
|
|
155
|
+
(subscription) =>
|
|
156
|
+
Effect.promise(() => subscription.close()).pipe(
|
|
157
|
+
withSpanAndLog(`ServiceBus.subscription.close ${sessionId}`)
|
|
158
|
+
)
|
|
159
|
+
)
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
export class Receiver extends Context.TagId("Receiver")<Receiver, {
|
|
165
|
+
name: string
|
|
67
166
|
make: (waitTillEmpty: Effect<void>) => Effect<ServiceBusReceiver, never, Scope>
|
|
68
167
|
makeSession: (sessionId: string, waitTillEmpty: Effect<void>) => Effect<ServiceBusReceiver, never, Scope>
|
|
168
|
+
subscribe<RMsg, RErr>(
|
|
169
|
+
hndlr: MessageHandlers<RMsg, RErr>,
|
|
170
|
+
sessionId?: string
|
|
171
|
+
): Effect.Effect<void, never, Scope.Scope | RMsg | RErr>
|
|
69
172
|
}>() {
|
|
70
|
-
static readonly
|
|
71
|
-
this.toLayer(Client.pipe(Effect.andThen((cl) => ({
|
|
72
|
-
make: (waitTillEmpty: Effect<void>) =>
|
|
73
|
-
makeReceiver(queueName, waitTillEmpty).pipe(Effect.provideService(Client, cl)),
|
|
74
|
-
makeSession: (sessionId: string, waitTillEmpty: Effect<void>) =>
|
|
75
|
-
makeReceiver(queueName, waitTillEmpty, sessionId).pipe(Effect.provideService(Client, cl))
|
|
76
|
-
}))))
|
|
173
|
+
static readonly layer = (name: string) => this.toLayer(makeReceiver(name))
|
|
77
174
|
}
|
|
78
175
|
|
|
79
|
-
export
|
|
80
|
-
|
|
81
|
-
options?: OperationOptionsBase
|
|
82
|
-
) {
|
|
83
|
-
return Effect.gen(function*() {
|
|
84
|
-
const s = yield* Sender
|
|
85
|
-
return yield* Effect.promise(() => s.sendMessages(messages, options))
|
|
86
|
-
})
|
|
87
|
-
}
|
|
176
|
+
export const ReceiverTag = <Id>() => <Key extends string>(queueName: Key) => {
|
|
177
|
+
const tag = Context.Tag(`ServiceBus.Receiver.${queueName}`)<Id, Receiver>()
|
|
88
178
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const fr = yield* FiberSet.runtime(fs)<RMsg | RErr>()
|
|
94
|
-
const wait = Effect
|
|
95
|
-
.gen(function*() {
|
|
96
|
-
if ((yield* FiberSet.size(fs)) > 0) {
|
|
97
|
-
yield* InfraLogger.logDebug("Waiting ServiceBusFiberSet to be empty: " + (yield* FiberSet.size(fs)))
|
|
98
|
-
}
|
|
99
|
-
while ((yield* FiberSet.size(fs)) > 0) yield* Effect.sleep("250 millis")
|
|
100
|
-
})
|
|
101
|
-
const r = yield* sessionId
|
|
102
|
-
? rf.makeSession(
|
|
103
|
-
sessionId,
|
|
104
|
-
wait
|
|
105
|
-
)
|
|
106
|
-
: rf.make(wait)
|
|
107
|
-
|
|
108
|
-
const runEffect = <E>(effect: Effect<void, E, RMsg | RErr>) =>
|
|
109
|
-
new Promise<void>((resolve, reject) =>
|
|
110
|
-
fr(effect)
|
|
111
|
-
.addObserver((exit) => {
|
|
112
|
-
if (Exit.isSuccess(exit)) {
|
|
113
|
-
resolve(exit.value)
|
|
114
|
-
} else {
|
|
115
|
-
// disable @typescript-eslint/prefer-promise-reject-errors
|
|
116
|
-
reject(Cause.pretty(exit.cause, { renderErrorCause: true }))
|
|
117
|
-
}
|
|
118
|
-
})
|
|
119
|
-
)
|
|
120
|
-
yield* Effect.acquireRelease(
|
|
121
|
-
Effect
|
|
122
|
-
.sync(() =>
|
|
123
|
-
r
|
|
124
|
-
.subscribe({
|
|
125
|
-
processError: (err) =>
|
|
126
|
-
runEffect(
|
|
127
|
-
hndlr
|
|
128
|
-
.processError(err)
|
|
129
|
-
.pipe(Effect.catchAllCause((cause) => Effect.logError(`ServiceBus Error ${sessionId}`, cause)))
|
|
130
|
-
),
|
|
131
|
-
processMessage: (msg) => runEffect(hndlr.processMessage(msg))
|
|
132
|
-
// DO NOT CATCH ERRORS here as they should return to the queue!
|
|
133
|
-
})
|
|
134
|
-
)
|
|
135
|
-
.pipe(withSpanAndLog(`ServiceBus.subscription.create ${sessionId}`)),
|
|
136
|
-
(subscription) =>
|
|
137
|
-
Effect.promise(() => subscription.close()).pipe(withSpanAndLog(`ServiceBus.subscription.close ${sessionId}`))
|
|
179
|
+
return Object.assign(tag, {
|
|
180
|
+
layer: Layer.effect(
|
|
181
|
+
tag,
|
|
182
|
+
makeReceiver(queueName).pipe(Effect.map((_) => Receiver.of(_)))
|
|
138
183
|
)
|
|
139
184
|
})
|
|
140
185
|
}
|
|
141
186
|
|
|
187
|
+
export const SenderReceiver = (queue: string, queueDrain?: string) =>
|
|
188
|
+
Layer.mergeAll(Sender.layer(queue), Receiver.layer(queueDrain ?? queue))
|
|
189
|
+
|
|
142
190
|
export interface MessageHandlers<RMsg, RErr> {
|
|
143
191
|
/**
|
|
144
192
|
* Handler that processes messages from service bus.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import { Context, Effect, Layer, type NonEmptyReadonlyArray, pipe, type Scope } from "effect-app"
|
|
3
3
|
|
|
4
|
-
import { type
|
|
4
|
+
import { type HttpLayerRouter } from "effect-app/http"
|
|
5
5
|
import { type Tag } from "effect/Context"
|
|
6
6
|
import { type YieldWrap } from "effect/Utils"
|
|
7
7
|
import { type ContextTagWithDefault, type GetContext, type LayerUtils, mergeContexts } from "./layerUtils.js"
|
|
@@ -50,12 +50,14 @@ type TDepsArr<TDeps extends ReadonlyArray<any>> = {
|
|
|
50
50
|
// actual type in that position, I just wanna set the overall structure
|
|
51
51
|
[K in keyof TDeps]: TDeps[K] extends //
|
|
52
52
|
// E = never => the context provided cannot trigger errors
|
|
53
|
-
// TODO: remove
|
|
54
|
-
// _R extends
|
|
53
|
+
// TODO: remove HttpLayerRouter.Provided - it's not even relevant outside of Http context, while ContextProviders are for anywhere. Only support Scope?
|
|
54
|
+
// _R extends HttpLayerRouter.Provided => the context provided can only have what HttpLayerRouter.Provided provides as requirements
|
|
55
55
|
(
|
|
56
56
|
ContextTagWithDefault.Base<Effect<Context.Context<infer _1>, never, infer _R> & { _tag: infer _2 }>
|
|
57
|
-
) ? [_R] extends [
|
|
58
|
-
: `
|
|
57
|
+
) ? [_R] extends [HttpLayerRouter.Provided] ? TDeps[K]
|
|
58
|
+
: `HttpLayerRouter.Provided is the only requirement ${TDeps[K]["Service"][
|
|
59
|
+
"_tag"
|
|
60
|
+
]}'s returned effect can have`
|
|
59
61
|
: TDeps[K] extends (
|
|
60
62
|
ContextTagWithDefault.Base<
|
|
61
63
|
& (() => Generator<
|
|
@@ -68,8 +70,8 @@ type TDepsArr<TDeps extends ReadonlyArray<any>> = {
|
|
|
68
70
|
) // [_YW] extends [never] if no yield* is used and just some context is returned
|
|
69
71
|
? [_YW] extends [never] ? TDeps[K]
|
|
70
72
|
: [_YW] extends [YieldWrap<Effect<infer _2, never, infer _R>>]
|
|
71
|
-
? [_R] extends [
|
|
72
|
-
: `
|
|
73
|
+
? [_R] extends [HttpLayerRouter.Provided] ? TDeps[K]
|
|
74
|
+
: `HttpLayerRouter.Provided is the only requirement ${TDeps[K]["Service"][
|
|
73
75
|
"_tag"
|
|
74
76
|
]}'s returned effect can have`
|
|
75
77
|
: "WTF are you yielding man?"
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { type AnyWithProps } from "@effect/rpc/Rpc"
|
|
3
3
|
import { Context, type Effect, type NonEmptyArray, type NonEmptyReadonlyArray, S } from "effect-app"
|
|
4
4
|
import { type GetContextConfig, type RPCContextMap } from "effect-app/client"
|
|
5
|
+
import { type TypeTestId } from "../../routing.js"
|
|
5
6
|
import { type MiddlewareMaker, middlewareMaker } from "./generic-middleware.js"
|
|
6
7
|
import { type AnyDynamic, type RpcDynamic, Tag, type TagClassAny } from "./RpcMiddleware.js"
|
|
7
8
|
|
|
@@ -103,7 +104,7 @@ export interface BuildingMiddleware<
|
|
|
103
104
|
: never
|
|
104
105
|
|
|
105
106
|
// helps debugging what are the missing requirements (type only)
|
|
106
|
-
|
|
107
|
+
readonly [TypeTestId]: {
|
|
107
108
|
missingDynamicMiddlewares: Exclude<keyof RequestContextMap, Provided>
|
|
108
109
|
missingContext: MiddlewareR
|
|
109
110
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// codegen:start {preset: barrel, include: ./middleware/*.ts, nodir: false }
|
|
2
|
-
export * from "./middleware/dynamic-middleware.js"
|
|
3
2
|
export * from "./middleware/generic-middleware.js"
|
|
4
3
|
export * from "./middleware/middleware-api.js"
|
|
5
4
|
export * from "./middleware/middleware.js"
|