@effect-app/infra 4.0.0-beta.1 → 4.0.0-beta.100

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 (178) hide show
  1. package/CHANGELOG.md +720 -0
  2. package/dist/CUPS.d.ts +3 -3
  3. package/dist/CUPS.d.ts.map +1 -1
  4. package/dist/CUPS.js +3 -3
  5. package/dist/Emailer/Sendgrid.js +1 -1
  6. package/dist/Emailer/service.d.ts +3 -3
  7. package/dist/Emailer/service.d.ts.map +1 -1
  8. package/dist/Emailer/service.js +3 -3
  9. package/dist/MainFiberSet.d.ts +2 -2
  10. package/dist/MainFiberSet.d.ts.map +1 -1
  11. package/dist/MainFiberSet.js +3 -3
  12. package/dist/Model/Repository/internal/internal.d.ts +3 -3
  13. package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
  14. package/dist/Model/Repository/internal/internal.js +11 -7
  15. package/dist/Model/Repository/makeRepo.d.ts +2 -2
  16. package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
  17. package/dist/Model/Repository/makeRepo.js +1 -1
  18. package/dist/Model/Repository/validation.d.ts +5 -4
  19. package/dist/Model/Repository/validation.d.ts.map +1 -1
  20. package/dist/Model/query/dsl.d.ts +9 -9
  21. package/dist/Operations.d.ts +2 -2
  22. package/dist/Operations.d.ts.map +1 -1
  23. package/dist/Operations.js +3 -3
  24. package/dist/OperationsRepo.d.ts +2 -2
  25. package/dist/OperationsRepo.d.ts.map +1 -1
  26. package/dist/OperationsRepo.js +3 -3
  27. package/dist/QueueMaker/SQLQueue.d.ts +3 -5
  28. package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
  29. package/dist/QueueMaker/SQLQueue.js +9 -7
  30. package/dist/QueueMaker/errors.d.ts +1 -1
  31. package/dist/QueueMaker/errors.d.ts.map +1 -1
  32. package/dist/QueueMaker/memQueue.d.ts.map +1 -1
  33. package/dist/QueueMaker/memQueue.js +10 -9
  34. package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
  35. package/dist/QueueMaker/sbqueue.js +11 -9
  36. package/dist/RequestContext.d.ts +24 -19
  37. package/dist/RequestContext.d.ts.map +1 -1
  38. package/dist/RequestContext.js +5 -5
  39. package/dist/RequestFiberSet.d.ts +2 -2
  40. package/dist/RequestFiberSet.d.ts.map +1 -1
  41. package/dist/RequestFiberSet.js +5 -5
  42. package/dist/Store/ContextMapContainer.d.ts +14 -3
  43. package/dist/Store/ContextMapContainer.d.ts.map +1 -1
  44. package/dist/Store/ContextMapContainer.js +64 -3
  45. package/dist/Store/Cosmos.d.ts.map +1 -1
  46. package/dist/Store/Cosmos.js +136 -68
  47. package/dist/Store/Disk.d.ts.map +1 -1
  48. package/dist/Store/Disk.js +3 -4
  49. package/dist/Store/Memory.d.ts +2 -2
  50. package/dist/Store/Memory.d.ts.map +1 -1
  51. package/dist/Store/Memory.js +4 -4
  52. package/dist/Store/SQL/Pg.d.ts +4 -0
  53. package/dist/Store/SQL/Pg.d.ts.map +1 -0
  54. package/dist/Store/SQL/Pg.js +186 -0
  55. package/dist/Store/SQL/query.d.ts +37 -0
  56. package/dist/Store/SQL/query.d.ts.map +1 -0
  57. package/dist/Store/SQL/query.js +362 -0
  58. package/dist/Store/SQL.d.ts +11 -0
  59. package/dist/Store/SQL.d.ts.map +1 -0
  60. package/dist/Store/SQL.js +212 -0
  61. package/dist/Store/index.d.ts +1 -1
  62. package/dist/Store/index.d.ts.map +1 -1
  63. package/dist/Store/index.js +11 -1
  64. package/dist/Store/service.d.ts +8 -5
  65. package/dist/Store/service.d.ts.map +1 -1
  66. package/dist/Store/service.js +14 -6
  67. package/dist/adapters/SQL/Model.d.ts +2 -5
  68. package/dist/adapters/SQL/Model.d.ts.map +1 -1
  69. package/dist/adapters/SQL/Model.js +21 -13
  70. package/dist/adapters/ServiceBus.d.ts +6 -6
  71. package/dist/adapters/ServiceBus.d.ts.map +1 -1
  72. package/dist/adapters/ServiceBus.js +9 -9
  73. package/dist/adapters/cosmos-client.d.ts +2 -2
  74. package/dist/adapters/cosmos-client.d.ts.map +1 -1
  75. package/dist/adapters/cosmos-client.js +3 -3
  76. package/dist/adapters/logger.d.ts.map +1 -1
  77. package/dist/adapters/memQueue.d.ts +2 -2
  78. package/dist/adapters/memQueue.d.ts.map +1 -1
  79. package/dist/adapters/memQueue.js +3 -3
  80. package/dist/adapters/mongo-client.d.ts +2 -2
  81. package/dist/adapters/mongo-client.d.ts.map +1 -1
  82. package/dist/adapters/mongo-client.js +3 -3
  83. package/dist/adapters/redis-client.d.ts +3 -3
  84. package/dist/adapters/redis-client.d.ts.map +1 -1
  85. package/dist/adapters/redis-client.js +3 -3
  86. package/dist/api/ContextProvider.d.ts +6 -6
  87. package/dist/api/ContextProvider.d.ts.map +1 -1
  88. package/dist/api/ContextProvider.js +6 -6
  89. package/dist/api/internal/auth.d.ts +1 -1
  90. package/dist/api/internal/events.d.ts +2 -2
  91. package/dist/api/internal/events.d.ts.map +1 -1
  92. package/dist/api/internal/events.js +7 -5
  93. package/dist/api/layerUtils.d.ts +5 -5
  94. package/dist/api/layerUtils.d.ts.map +1 -1
  95. package/dist/api/layerUtils.js +5 -5
  96. package/dist/api/routing/middleware/RouterMiddleware.d.ts +3 -3
  97. package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -1
  98. package/dist/api/routing/middleware/middleware.d.ts +35 -1
  99. package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
  100. package/dist/api/routing/middleware/middleware.js +39 -1
  101. package/dist/api/routing/schema/jwt.d.ts +1 -1
  102. package/dist/api/routing/schema/jwt.d.ts.map +1 -1
  103. package/dist/api/routing/schema/jwt.js +1 -1
  104. package/dist/api/routing.d.ts +15 -21
  105. package/dist/api/routing.d.ts.map +1 -1
  106. package/dist/api/routing.js +5 -4
  107. package/dist/api/setupRequest.d.ts +6 -3
  108. package/dist/api/setupRequest.d.ts.map +1 -1
  109. package/dist/api/setupRequest.js +13 -8
  110. package/dist/errorReporter.d.ts +1 -1
  111. package/dist/errorReporter.d.ts.map +1 -1
  112. package/dist/errorReporter.js +1 -1
  113. package/dist/fileUtil.d.ts.map +1 -1
  114. package/dist/fileUtil.js +3 -2
  115. package/dist/logger.d.ts.map +1 -1
  116. package/dist/rateLimit.js +1 -1
  117. package/examples/query.ts +30 -26
  118. package/package.json +32 -18
  119. package/src/CUPS.ts +2 -2
  120. package/src/Emailer/Sendgrid.ts +1 -1
  121. package/src/Emailer/service.ts +2 -2
  122. package/src/MainFiberSet.ts +2 -2
  123. package/src/Model/Repository/internal/internal.ts +11 -8
  124. package/src/Model/Repository/makeRepo.ts +2 -2
  125. package/src/Operations.ts +2 -2
  126. package/src/OperationsRepo.ts +2 -2
  127. package/src/QueueMaker/SQLQueue.ts +10 -10
  128. package/src/QueueMaker/memQueue.ts +41 -42
  129. package/src/QueueMaker/sbqueue.ts +65 -62
  130. package/src/RequestContext.ts +4 -4
  131. package/src/RequestFiberSet.ts +4 -4
  132. package/src/Store/ContextMapContainer.ts +98 -2
  133. package/src/Store/Cosmos.ts +352 -253
  134. package/src/Store/Disk.ts +2 -3
  135. package/src/Store/Memory.ts +4 -6
  136. package/src/Store/SQL/Pg.ts +328 -0
  137. package/src/Store/SQL/query.ts +402 -0
  138. package/src/Store/SQL.ts +357 -0
  139. package/src/Store/index.ts +10 -0
  140. package/src/Store/service.ts +16 -7
  141. package/src/adapters/SQL/Model.ts +76 -71
  142. package/src/adapters/ServiceBus.ts +8 -8
  143. package/src/adapters/cosmos-client.ts +2 -2
  144. package/src/adapters/memQueue.ts +2 -2
  145. package/src/adapters/mongo-client.ts +2 -2
  146. package/src/adapters/redis-client.ts +2 -2
  147. package/src/api/ContextProvider.ts +11 -11
  148. package/src/api/internal/events.ts +7 -6
  149. package/src/api/layerUtils.ts +8 -8
  150. package/src/api/routing/middleware/RouterMiddleware.ts +4 -4
  151. package/src/api/routing/middleware/middleware.ts +43 -0
  152. package/src/api/routing/schema/jwt.ts +2 -3
  153. package/src/api/routing.ts +18 -23
  154. package/src/api/setupRequest.ts +29 -9
  155. package/src/errorReporter.ts +1 -1
  156. package/src/fileUtil.ts +2 -1
  157. package/src/rateLimit.ts +2 -2
  158. package/test/contextProvider.test.ts +5 -5
  159. package/test/controller.test.ts +19 -14
  160. package/test/dist/contextProvider.test.d.ts.map +1 -1
  161. package/test/dist/controller.test.d.ts.map +1 -1
  162. package/test/dist/date-query.test.d.ts.map +1 -0
  163. package/test/dist/fixtures.d.ts +19 -9
  164. package/test/dist/fixtures.d.ts.map +1 -1
  165. package/test/dist/fixtures.js +11 -9
  166. package/test/dist/query.test.d.ts.map +1 -1
  167. package/test/dist/rawQuery.test.d.ts.map +1 -1
  168. package/test/dist/requires.test.d.ts.map +1 -1
  169. package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
  170. package/test/dist/sql-store.test.d.ts.map +1 -0
  171. package/test/fixtures.ts +10 -8
  172. package/test/query.test.ts +162 -16
  173. package/test/rawQuery.test.ts +19 -17
  174. package/test/requires.test.ts +6 -5
  175. package/test/rpc-multi-middleware.test.ts +73 -4
  176. package/test/sql-store.test.ts +776 -0
  177. package/test/validateSample.test.ts +1 -1
  178. package/tsconfig.json +0 -1
@@ -1,16 +1,15 @@
1
1
  import { getRequestContext, setupRequestContextWithCustomSpan } from "@effect-app/infra/api/setupRequest"
2
2
  import { reportNonInterruptedFailure } from "@effect-app/infra/QueueMaker/errors"
3
3
  import { type QueueBase, QueueMeta } from "@effect-app/infra/QueueMaker/service"
4
- import { SqlClient } from "effect/unstable/sql"
5
4
  import { subMinutes } from "date-fns"
6
- import { Effect, Fiber, Option, S, Tracer } from "effect-app"
7
- import type { NonEmptyString255 } from "effect-app/Schema"
5
+ import { Effect, Fiber, type NonEmptyReadonlyArray, Option, S, Tracer } from "effect-app"
6
+ import { type NonEmptyString255 } from "effect-app/Schema"
8
7
  import { pretty } from "effect-app/utils"
9
- import type { NonEmptyReadonlyArray } from "effect-app"
8
+ import { SqlClient } from "effect/unstable/sql"
10
9
  import { SQLModel } from "../adapters/SQL.js"
11
10
  import { InfraLogger } from "../logger.js"
12
11
 
13
- export const QueueId = S.Number.pipe(S.brand("QueueId"))
12
+ export const QueueId = S.Finite.pipe(S.brand("QueueId"))
14
13
  export type QueueId = typeof QueueId.Type
15
14
 
16
15
  // TODO: let the model track and Auto Generate versionColumn on every update instead
@@ -79,14 +78,14 @@ export function makeSQLQueue<
79
78
 
80
79
  const q = {
81
80
  offer: Effect.fnUntraced(function*(body: Evt, meta: typeof QueueMeta.Type) {
82
- yield* queueRepo.insertVoid({
81
+ yield* queueRepo.insertVoid(Queue.insert.make({
83
82
  body,
84
83
  meta,
85
84
  name: queueName,
86
85
  processingAt: Option.none(),
87
86
  finishedAt: Option.none(),
88
87
  etag: crypto.randomUUID()
89
- })
88
+ }))
90
89
  }),
91
90
  take: Effect.gen(function*() {
92
91
  while (true) {
@@ -95,15 +94,16 @@ export function makeSQLQueue<
95
94
  const dec = yield* decodeDrain(first)
96
95
  const { createdAt, updatedAt, ...rest } = dec
97
96
  return yield* drainRepo.update(
98
- { ...rest, processingAt: Option.some(new Date()) } // auto in lib , etag: crypto.randomUUID()
97
+ Drain.update.make({ ...rest, processingAt: Option.some(new Date()) }) // auto in lib , etag: crypto.randomUUID()
99
98
  )
100
99
  }
101
100
  if (first) return first
102
101
  yield* Effect.sleep(250)
103
102
  }
104
103
  }),
105
- finish: ({ createdAt, updatedAt, ...q }: Drain) =>
106
- drainRepo.updateVoid({ ...q, finishedAt: Option.some(new Date()) }) // auto in lib , etag: crypto.randomUUID()
104
+ finish: Effect.fn(function*({ createdAt, updatedAt, ...q }: Drain) {
105
+ return yield* drainRepo.updateVoid(Drain.update.make({ ...q, finishedAt: Option.some(new Date()) })) // auto in lib , etag: crypto.randomUUID()
106
+ })
107
107
  }
108
108
  const queue = {
109
109
  publish: (...messages: NonEmptyReadonlyArray<Evt>) =>
@@ -1,12 +1,11 @@
1
1
  import { Cause, Tracer } from "effect"
2
- import { Effect, Fiber, flow, S } from "effect-app"
3
- import * as Q from "effect/Queue"
2
+ import { Effect, Fiber, flow, type NonEmptyReadonlyArray, S } from "effect-app"
4
3
  import { pretty } from "effect-app/utils"
4
+ import * as Q from "effect/Queue"
5
5
  import { MemQueue } from "../adapters/memQueue.js"
6
6
  import { getRequestContext, setupRequestContextWithCustomSpan } from "../api/setupRequest.js"
7
7
  import { InfraLogger } from "../logger.js"
8
8
  import { reportNonInterruptedFailure, reportNonInterruptedFailureCause } from "./errors.js"
9
- import type { NonEmptyReadonlyArray } from "effect-app"
10
9
  import { type QueueBase, QueueMeta } from "./service.js"
11
10
 
12
11
  export function makeMemQueue<
@@ -26,8 +25,12 @@ export function makeMemQueue<
26
25
  const qDrain = yield* mem.getOrCreateQueue(queueDrainName)
27
26
 
28
27
  const wireSchema = S.Struct({ body: schema, meta: QueueMeta })
28
+ const wireSchemaJson = S.fromJsonString(S.toCodecJson(wireSchema))
29
+ const encodePublish = S.encodeEffect(wireSchemaJson)
29
30
  const drainW = S.Struct({ body: drainSchema, meta: QueueMeta })
30
- const parseDrain = flow(S.decodeUnknownEffect(drainW), Effect.orDie)
31
+ const drainWJson = S.fromJsonString(S.toCodecJson(drainW))
32
+
33
+ const parseDrain = flow(S.decodeUnknownEffect(drainWJson), Effect.orDie)
31
34
 
32
35
  const queue = {
33
36
  publish: (...messages: NonEmptyReadonlyArray<Evt>) =>
@@ -37,10 +40,8 @@ export function makeMemQueue<
37
40
  Effect
38
41
  .forEach(messages, (m) =>
39
42
  // we JSON encode, because that is what the wire also does, and it reveals holes in e.g unknown encoders (Date->String)
40
- S.encodeEffect(wireSchema)({ body: m, meta: requestContext }).pipe(
43
+ encodePublish({ body: m, meta: requestContext }).pipe(
41
44
  Effect.orDie,
42
- Effect
43
- .map(JSON.stringify),
44
45
  // .tap((msg) => info("Publishing Mem Message: " + utils.inspect(msg)))
45
46
  Effect.flatMap((_) => Q.offer(q, _))
46
47
  ), { discard: true })
@@ -58,42 +59,40 @@ export function makeMemQueue<
58
59
  const reportError = reportNonInterruptedFailureCause({ name: "MemQueue.drain." + queueDrainName })
59
60
  const processMessage = (msg: string) =>
60
61
  // we JSON parse, because that is what the wire also does, and it reveals holes in e.g unknown encoders (Date->String)
61
- Effect
62
- .sync(() => JSON.parse(msg))
63
- .pipe(
64
- Effect.flatMap(parseDrain),
65
- Effect.orDie,
66
- Effect
67
- .flatMap(({ body, meta }) => {
68
- let effect = InfraLogger
69
- .logDebug(`[${queueDrainName}] Processing incoming message`)
70
- .pipe(
71
- Effect.annotateLogs({ body: pretty(body), meta: pretty(meta) }),
72
- Effect.andThen(handleEvent(body)),
73
- silenceAndReportError,
74
- (_) =>
75
- setupRequestContextWithCustomSpan(
76
- _,
77
- meta,
78
- `queue.drain: ${queueDrainName}.${body._tag}`,
79
- {
80
- captureStackTrace: false,
81
- kind: "consumer",
82
- attributes: {
83
- "queue.name": queueDrainName,
84
- "queue.sessionId": sessionId,
85
- "queue.input": body
86
- }
62
+ parseDrain(msg).pipe(
63
+ Effect.orDie,
64
+ Effect
65
+ .flatMap(({ body, meta }) => {
66
+ let effect = InfraLogger
67
+ .logDebug(`[${queueDrainName}] Processing incoming message`)
68
+ .pipe(
69
+ Effect.annotateLogs({ body: pretty(body), meta: pretty(meta) }),
70
+ Effect.andThen(handleEvent(body)),
71
+ silenceAndReportError,
72
+ (_) =>
73
+ setupRequestContextWithCustomSpan(
74
+ _,
75
+ meta,
76
+ `queue.drain: ${queueDrainName}.${body._tag}`,
77
+ {
78
+ captureStackTrace: false,
79
+ kind: "consumer",
80
+ attributes: {
81
+ "queue.name": queueDrainName,
82
+ "queue.sessionId": sessionId,
83
+ "queue.input": body
87
84
  }
88
- )
89
- )
90
- if (meta.span) {
91
- effect = Effect.withParentSpan(effect, Tracer.externalSpan(meta.span))
92
- }
93
- return effect
94
- })
95
- )
96
- return Q.take(qDrain)
85
+ }
86
+ )
87
+ )
88
+ if (meta.span) {
89
+ effect = Effect.withParentSpan(effect, Tracer.externalSpan(meta.span))
90
+ }
91
+ return effect
92
+ })
93
+ )
94
+ return Q
95
+ .take(qDrain)
97
96
  .pipe(
98
97
  Effect
99
98
  .flatMap((x) =>
@@ -1,12 +1,11 @@
1
1
  import { Tracer } from "effect"
2
- import { Cause, Effect, flow, S } from "effect-app"
2
+ import { Cause, Effect, flow, type NonEmptyReadonlyArray, S } from "effect-app"
3
3
  import type { StringId } from "effect-app/Schema"
4
4
  import { pretty } from "effect-app/utils"
5
5
  import { Receiver, Sender } from "../adapters/ServiceBus.js"
6
6
  import { getRequestContext, setupRequestContextWithCustomSpan } from "../api/setupRequest.js"
7
7
  import { InfraLogger } from "../logger.js"
8
8
  import { reportNonInterruptedFailure, reportNonInterruptedFailureCause, reportQueueError } from "./errors.js"
9
- import type { NonEmptyReadonlyArray } from "effect-app"
10
9
  import { type QueueBase, QueueMeta } from "./service.js"
11
10
 
12
11
  export function makeServiceBusQueue<
@@ -22,8 +21,11 @@ export function makeServiceBusQueue<
22
21
  body: schema,
23
22
  meta: QueueMeta
24
23
  })
24
+ const wireSchemaJson = S.fromJsonString(S.toCodecJson(wireSchema))
25
+ const encodePublish = S.encodeEffect(wireSchemaJson)
25
26
  const drainW = S.Struct({ body: drainSchema, meta: QueueMeta })
26
- const parseDrain = flow(S.decodeUnknownEffect(drainW), Effect.orDie)
27
+ const drainWJson = S.fromJsonString(S.toCodecJson(drainW))
28
+ const parseDrain = flow(S.decodeUnknownEffect(drainWJson), Effect.orDie)
27
29
 
28
30
  return Effect.gen(function*() {
29
31
  const sender = yield* Sender
@@ -40,55 +42,51 @@ export function makeServiceBusQueue<
40
42
  handleEvent: (ks: DrainEvt) => Effect.Effect<void, DrainE, DrainR>,
41
43
  sessionId?: string
42
44
  ) => {
43
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
- function processMessage(messageBody: any) {
45
- return Effect
46
- .sync(() => JSON.parse(messageBody))
47
- .pipe(
48
- Effect.flatMap((x) => parseDrain(x)),
49
- Effect.orDie,
50
- Effect
51
- .flatMap(({ body, meta }) => {
52
- let effect = InfraLogger
53
- .logDebug(`[${receiver.name}] Processing incoming message`)
54
- .pipe(
55
- Effect.annotateLogs({
56
- body: pretty(body),
57
- meta: pretty(meta)
58
- }),
59
- Effect.andThen(handleEvent(body)),
60
- Effect.orDie
61
- )
62
- // we silenceAndReportError here, so that the error is reported, and moves into the Exit.
63
- .pipe(
64
- silenceAndReportError,
65
- (_) =>
66
- setupRequestContextWithCustomSpan(
67
- _,
68
- meta,
69
- `queue.drain: ${receiver.name}${sessionId ? `#${sessionId}` : ""}.${body._tag}`,
70
- {
71
- captureStackTrace: false,
72
- kind: "consumer",
73
- attributes: {
74
- "queue.name": receiver.name,
75
- "queue.sessionId": sessionId,
76
- "queue.input": body
77
- }
45
+ function processMessage(messageBody: unknown) {
46
+ return parseDrain(messageBody).pipe(
47
+ Effect.orDie,
48
+ Effect
49
+ .flatMap(({ body, meta }) => {
50
+ let effect = InfraLogger
51
+ .logDebug(`[${receiver.name}] Processing incoming message`)
52
+ .pipe(
53
+ Effect.annotateLogs({
54
+ body: pretty(body),
55
+ meta: pretty(meta)
56
+ }),
57
+ Effect.andThen(handleEvent(body)),
58
+ Effect.orDie
59
+ )
60
+ // we silenceAndReportError here, so that the error is reported, and moves into the Exit.
61
+ .pipe(
62
+ silenceAndReportError,
63
+ (_) =>
64
+ setupRequestContextWithCustomSpan(
65
+ _,
66
+ meta,
67
+ `queue.drain: ${receiver.name}${sessionId ? `#${sessionId}` : ""}.${body._tag}`,
68
+ {
69
+ captureStackTrace: false,
70
+ kind: "consumer",
71
+ attributes: {
72
+ "queue.name": receiver.name,
73
+ "queue.sessionId": sessionId,
74
+ "queue.input": body
78
75
  }
79
- )
80
- )
81
- if (meta.span) {
82
- effect = Effect.withParentSpan(effect, Tracer.externalSpan(meta.span))
83
- }
84
- return effect
85
- }),
86
- Effect
87
- // we reportError here, so that we report the error only, and keep flowing
88
- .tapCause(reportError),
89
- // we still need to flatten the Exit.
90
- Effect.flatMap((_) => _)
91
- )
76
+ }
77
+ )
78
+ )
79
+ if (meta.span) {
80
+ effect = Effect.withParentSpan(effect, Tracer.externalSpan(meta.span))
81
+ }
82
+ return effect
83
+ }),
84
+ Effect
85
+ // we reportError here, so that we report the error only, and keep flowing
86
+ .tapCause(reportError),
87
+ // we still need to flatten the Exit.
88
+ Effect.flatMap((_) => _)
89
+ )
92
90
  }
93
91
 
94
92
  return receiver
@@ -111,17 +109,22 @@ export function makeServiceBusQueue<
111
109
  getRequestContext
112
110
  .pipe(
113
111
  Effect.flatMap((requestContext) =>
114
- Effect.forEach(messages, (m) =>
115
- S.encodeEffect(wireSchema)({
116
- body: m,
117
- meta: requestContext
118
- }).pipe(Effect.orDie, Effect.map((encoded) => ({
119
- body: JSON.stringify(encoded),
120
- messageId: m.id, /* correllationid: requestId */
121
- contentType: "application/json",
122
- sessionId: "sessionId" in m ? m.sessionId as string : undefined as unknown as string // TODO: optional
123
- }))))
124
- .pipe(Effect.flatMap((msgs) => sender.sendMessages(msgs)))
112
+ Effect
113
+ .forEach(messages, (m) =>
114
+ encodePublish({
115
+ body: m,
116
+ meta: requestContext
117
+ })
118
+ .pipe(
119
+ Effect.orDie,
120
+ Effect.map((body) => ({
121
+ body,
122
+ messageId: m.id, /* correllationid: requestId */
123
+ contentType: "application/json",
124
+ sessionId: "sessionId" in m ? m.sessionId as string : undefined as unknown as string // TODO: optional
125
+ }))
126
+ ))
127
+ .pipe(Effect.flatMap((msgs) => sender.sendMessages(msgs)))
125
128
  ),
126
129
  Effect.withSpan("queue.publish: " + sender.name, {
127
130
  kind: "producer",
@@ -1,11 +1,11 @@
1
- import { S, ServiceMap } from "effect-app"
1
+ import { Context, S } from "effect-app"
2
2
  import { UserProfileId } from "effect-app/ids"
3
3
  import { NonEmptyString255 } from "effect-app/Schema"
4
4
 
5
- export const Locale = S.Literal("en", "de")
5
+ export const Locale = S.Literals(["en", "de"])
6
6
  export type Locale = typeof Locale.Type
7
7
 
8
- export class LocaleRef extends ServiceMap.Reference("Locale", { defaultValue: (): Locale => "en" }) {}
8
+ export class LocaleRef extends Context.Reference("Locale", { defaultValue: (): Locale => "en" }) {}
9
9
 
10
10
  export class RequestContext extends S.ExtendedClass<
11
11
  RequestContext,
@@ -23,7 +23,7 @@ export class RequestContext extends S.ExtendedClass<
23
23
  /** @deprecated */
24
24
  userProfile: S.optional(S.Struct({ sub: UserProfileId })) //
25
25
  }) {
26
- // static Tag = ServiceMap.Tag<RequestContext>()
26
+ // static Tag = Context.Tag<RequestContext>()
27
27
 
28
28
  static toMonitoring(this: void, self: RequestContext) {
29
29
  return {
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { Effect, Fiber, FiberSet, Layer, ServiceMap, type Tracer } from "effect-app"
2
+ import { Context, Effect, Fiber, FiberSet, Layer, Option, type Tracer } from "effect-app"
3
3
  import { reportRequestError, reportUnknownRequestError } from "./api/reportError.js"
4
4
  import { InfraLogger } from "./logger.js"
5
5
 
@@ -8,8 +8,8 @@ const getRootParentSpan = Effect.gen(function*() {
8
8
  Effect.catchTag("NoSuchElementError", () => Effect.succeed(null))
9
9
  )
10
10
  if (!span) return span
11
- while (span._tag === "Span" && span.parent !== undefined) {
12
- span = span.parent
11
+ while (span._tag === "Span" && Option.isSome(span.parent)) {
12
+ span = span.parent.value
13
13
  }
14
14
  return span
15
15
  })
@@ -92,7 +92,7 @@ const make = Effect.gen(function*() {
92
92
  * Whenever you fork a fiber for a Request, and you want to prevent dependent services to close prematurely on interruption,
93
93
  * like the ServiceBus Sender, you should register these fibers in this FiberSet.
94
94
  */
95
- export class RequestFiberSet extends ServiceMap.Service<RequestFiberSet>()("RequestFiberSet", { make }) {
95
+ export class RequestFiberSet extends Context.Service<RequestFiberSet>()("RequestFiberSet", { make }) {
96
96
  static readonly Live = Layer.effect(this, this.make)
97
97
  static readonly register = <A, E, R>(self: Effect.Effect<A, E, R>) =>
98
98
  this.asEffect().pipe(Effect.andThen((_) => _.register(self)))
@@ -1,4 +1,8 @@
1
- import { Data, Effect, Layer, ServiceMap } from "effect-app"
1
+ import { Context, Data, Effect, type Exit, Layer, RequestResolver } from "effect-app"
2
+ import type { NonEmptyArray } from "effect/Array"
3
+ import { dual } from "effect/Function"
4
+ import * as MutableHashMap from "effect/MutableHashMap"
5
+ import type * as Request from "effect/Request"
2
6
  import { ContextMap } from "./service.js"
3
7
 
4
8
  // TODO: we have to create a new contextmap on every request.
@@ -7,7 +11,7 @@ import { ContextMap } from "./service.js"
7
11
  // we can call another start after startup. but it would be even better if we could Die on accessing rootmap
8
12
  // we could also make the ContextMap optional, and when missing, issue a warning instead?
9
13
 
10
- export class ContextMapContainer extends ServiceMap.Reference("ContextMapContainer", {
14
+ export class ContextMapContainer extends Context.Reference("ContextMapContainer", {
11
15
  defaultValue: (): ContextMap | "root" => "root"
12
16
  }) {
13
17
  static readonly layer = Layer.effect(this, ContextMap.make.pipe(Effect.map(ContextMap.of)))
@@ -18,3 +22,95 @@ export class ContextMapNotStartedError extends Data.TaggedError("ContextMapNotSt
18
22
  export const getContextMap = ContextMapContainer.asEffect().pipe(
19
23
  Effect.filterOrFail((_) => _ !== "root", () => new ContextMapNotStartedError())
20
24
  )
25
+
26
+ export const withRequestResolverCache: {
27
+ <A extends Request.Request<any, any>>(options: {
28
+ readonly capacity: number
29
+ readonly strategy?: "lru" | "fifo" | undefined
30
+ }): (self: RequestResolver.RequestResolver<A>) => RequestResolver.RequestResolver<A>
31
+ <A extends Request.Request<any, any>>(
32
+ self: RequestResolver.RequestResolver<A>,
33
+ options: {
34
+ readonly capacity: number
35
+ readonly strategy?: "lru" | "fifo" | undefined
36
+ }
37
+ ): RequestResolver.RequestResolver<A>
38
+ } = dual(2, <A extends Request.Request<any, any>>(
39
+ self: RequestResolver.RequestResolver<A>,
40
+ options: {
41
+ readonly capacity: number
42
+ readonly strategy?: "lru" | "fifo" | undefined
43
+ }
44
+ ): RequestResolver.RequestResolver<A> => {
45
+ const cacheKey = Symbol()
46
+ const strategy = options.strategy ?? "lru"
47
+ type CacheEntry = {
48
+ readonly entry: Request.Entry<A>
49
+ exit: Exit.Exit<Request.Success<A>, Request.Error<A>> | undefined
50
+ }
51
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
52
+ return RequestResolver.makeWith({
53
+ ...(self as any),
54
+ runAll(
55
+ entries: NonEmptyArray<Request.Entry<A>>,
56
+ key: unknown
57
+ ) {
58
+ return Effect.flatMap(
59
+ getContextMap.pipe(Effect.orDie),
60
+ (contextMap) => {
61
+ const cache = contextMap.getOrCreateStore<MutableHashMap.MutableHashMap<A, CacheEntry>>(
62
+ cacheKey,
63
+ () => MutableHashMap.empty()
64
+ )
65
+
66
+ const uncached: Array<Request.Entry<A>> = []
67
+ for (const entry of entries) {
68
+ const ocached = MutableHashMap.get(cache, entry.request)
69
+ if (ocached._tag === "None") {
70
+ const cached: CacheEntry = { entry, exit: undefined }
71
+ MutableHashMap.set(cache, entry.request, cached)
72
+ const prevComplete = entry.completeUnsafe.bind(entry)
73
+ entry.completeUnsafe = (exit) => {
74
+ cached.exit = exit
75
+ prevComplete(exit)
76
+ }
77
+ uncached.push(entry)
78
+ } else {
79
+ const cached = ocached.value
80
+ if (cached.exit) {
81
+ if (strategy === "lru") {
82
+ MutableHashMap.remove(cache, cached.entry.request)
83
+ MutableHashMap.set(cache, cached.entry.request, cached)
84
+ }
85
+ entry.completeUnsafe(cached.exit as any)
86
+ } else {
87
+ cached.entry.uninterruptible = true
88
+ const prevComplete = cached.entry.completeUnsafe.bind(cached.entry)
89
+ cached.entry.completeUnsafe = (exit) => {
90
+ prevComplete(exit)
91
+ entry.completeUnsafe(exit)
92
+ }
93
+ }
94
+ }
95
+ }
96
+
97
+ if (uncached.length === 0) return Effect.void
98
+
99
+ return Effect.onExit(
100
+ (self as any).runAll(uncached, key),
101
+ () => {
102
+ let toRemove = MutableHashMap.size(cache) - options.capacity
103
+ if (toRemove <= 0) return Effect.void
104
+ for (const k of MutableHashMap.keys(cache)) {
105
+ MutableHashMap.remove(cache, k)
106
+ toRemove--
107
+ if (toRemove <= 0) break
108
+ }
109
+ return Effect.void
110
+ }
111
+ )
112
+ }
113
+ )
114
+ }
115
+ })
116
+ })