@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.
Files changed (57) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/MainFiberSet.d.ts +1 -1
  3. package/dist/Operations.d.ts +1 -1
  4. package/dist/QueueMaker/sbqueue.d.ts +5 -6
  5. package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
  6. package/dist/QueueMaker/sbqueue.js +18 -22
  7. package/dist/RequestFiberSet.d.ts +1 -1
  8. package/dist/Store/service.d.ts +1 -1
  9. package/dist/adapters/ServiceBus.d.ts +57 -17
  10. package/dist/adapters/ServiceBus.d.ts.map +1 -1
  11. package/dist/adapters/ServiceBus.js +75 -61
  12. package/dist/adapters/memQueue.d.ts +1 -1
  13. package/dist/api/ContextProvider.d.ts +3 -3
  14. package/dist/api/ContextProvider.d.ts.map +1 -1
  15. package/dist/api/ContextProvider.js +1 -1
  16. package/dist/api/routing/middleware/middleware-api.d.ts +2 -1
  17. package/dist/api/routing/middleware/middleware-api.d.ts.map +1 -1
  18. package/dist/api/routing/middleware/middleware-api.js +1 -1
  19. package/dist/api/routing/middleware.d.ts +0 -1
  20. package/dist/api/routing/middleware.d.ts.map +1 -1
  21. package/dist/api/routing/middleware.js +1 -2
  22. package/dist/api/routing.d.ts +9 -13
  23. package/dist/api/routing.d.ts.map +1 -1
  24. package/dist/api/routing.js +24 -16
  25. package/package.json +2 -6
  26. package/src/QueueMaker/sbqueue.ts +33 -46
  27. package/src/adapters/ServiceBus.ts +141 -93
  28. package/src/api/ContextProvider.ts +9 -7
  29. package/src/api/routing/middleware/middleware-api.ts +2 -1
  30. package/src/api/routing/middleware.ts +0 -1
  31. package/src/api/routing.ts +53 -246
  32. package/test/contextProvider.test.ts +4 -4
  33. package/test/controller.test.ts +14 -10
  34. package/test/dist/controller/test2.test.d.ts.map +1 -0
  35. package/test/dist/controller.legacy2.test.d.ts.map +1 -0
  36. package/test/dist/controller.legacy3.test.d.ts.map +1 -0
  37. package/test/dist/controller.test copy.js +129 -0
  38. package/test/dist/controller.test.d.ts.map +1 -1
  39. package/test/dist/controller5.test.d.ts.map +1 -0
  40. package/test/dist/controller6.test.d.ts.map +1 -0
  41. package/test/dist/controller7.test.d.ts.map +1 -0
  42. package/test/dist/dynamicContext.test.d.ts.map +1 -0
  43. package/test/dist/filterApi.test.d.ts.map +1 -0
  44. package/test/dist/fixtures.d.ts +3 -3
  45. package/test/dist/fixtures.js +2 -2
  46. package/test/dist/middleware-api.test.d.ts.map +1 -0
  47. package/test/dist/requires.d.ts +21 -0
  48. package/test/dist/requires.d.ts.map +1 -0
  49. package/test/dist/requires.js +27 -0
  50. package/test/fixtures.ts +1 -1
  51. package/vitest.config.ts.timestamp-1711724061890-6ecedb0a07fdd.mjs +0 -0
  52. package/vitest.config.ts.timestamp-1711743489537-da8d9e5f66c9f.mjs +0 -0
  53. package/vitest.config.ts.timestamp-1711744615239-dcf257a844e01.mjs +37 -0
  54. package/dist/api/routing/middleware/dynamic-middleware.d.ts +0 -2
  55. package/dist/api/routing/middleware/dynamic-middleware.d.ts.map +0 -1
  56. package/dist/api/routing/middleware/dynamic-middleware.js +0 -2
  57. /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, type ServiceBusSender } from "@azure/service-bus"
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
- const Client = Context.GenericTag<ServiceBusClient>("@services/Client")
22
- export const LiveServiceBusClient = (url: string) => Layer.scoped(Client)(makeClient(url))
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 makeSender(queueName: string) {
25
+ function makeSender_(queueName: string) {
25
26
  return Effect.gen(function*() {
26
- const serviceBusClient = yield* Client
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
- export function LiveSender(queueName: string) {
39
- return Layer
40
- .scoped(Sender, makeSender(queueName))
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
- function makeReceiver(queueName: string, waitTillEmpty: Effect<void>, sessionId?: string) {
44
- return Effect.gen(function*() {
45
- const serviceBusClient = yield* Client
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
- return yield* Effect.acquireRelease(
48
- (sessionId
49
- ? Effect.promise(() => serviceBusClient.acceptSession(queueName, sessionId))
50
- : Effect.sync(() => serviceBusClient.createReceiver(queueName)))
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
- export class ServiceBusReceiverFactory extends Context.TagId(
65
- "ServiceBusReceiverFactory"
66
- )<ServiceBusReceiverFactory, {
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 Live = (queueName: string) =>
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 function sendMessages(
80
- messages: ServiceBusMessage | ServiceBusMessage[] | ServiceBusMessageBatch,
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
- export function subscribe<RMsg, RErr>(hndlr: MessageHandlers<RMsg, RErr>, sessionId?: string) {
90
- return Effect.gen(function*() {
91
- const rf = yield* ServiceBusReceiverFactory
92
- const fs = yield* FiberSet.make()
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 HttpRouter } from "effect-app/http"
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 HttpRouter.Provided - it's not even relevant outside of Http context, while ContextProviders are for anywhere. Only support Scope?
54
- // _R extends HttpRouter.HttpRouter.Provided => the context provided can only have what HttpRouter.Provided provides as requirements
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 [HttpRouter.HttpRouter.Provided] ? TDeps[K]
58
- : `HttpRouter.HttpRouter.Provided is the only requirement ${TDeps[K]["Service"]["_tag"]}'s returned effect can have`
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 [HttpRouter.HttpRouter.Provided] ? TDeps[K]
72
- : `HttpRouter.HttpRouter.Provided is the only requirement ${TDeps[K]["Service"][
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
- missing: {
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"