@effect-app/infra 4.0.0-beta.11 → 4.0.0-beta.110

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 (157) hide show
  1. package/CHANGELOG.md +721 -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/service.d.ts +3 -3
  6. package/dist/Emailer/service.d.ts.map +1 -1
  7. package/dist/Emailer/service.js +3 -3
  8. package/dist/MainFiberSet.d.ts +2 -2
  9. package/dist/MainFiberSet.d.ts.map +1 -1
  10. package/dist/MainFiberSet.js +3 -3
  11. package/dist/Model/Repository/internal/internal.d.ts +3 -3
  12. package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
  13. package/dist/Model/Repository/internal/internal.js +21 -16
  14. package/dist/Model/Repository/makeRepo.d.ts +2 -2
  15. package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
  16. package/dist/Model/Repository/makeRepo.js +1 -1
  17. package/dist/Model/Repository/validation.d.ts +5 -4
  18. package/dist/Model/Repository/validation.d.ts.map +1 -1
  19. package/dist/Model/query/dsl.d.ts +9 -9
  20. package/dist/Operations.d.ts +2 -2
  21. package/dist/Operations.d.ts.map +1 -1
  22. package/dist/Operations.js +3 -3
  23. package/dist/OperationsRepo.d.ts +2 -2
  24. package/dist/OperationsRepo.d.ts.map +1 -1
  25. package/dist/OperationsRepo.js +3 -3
  26. package/dist/QueueMaker/SQLQueue.d.ts +2 -4
  27. package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
  28. package/dist/QueueMaker/SQLQueue.js +8 -6
  29. package/dist/QueueMaker/errors.d.ts +1 -1
  30. package/dist/QueueMaker/errors.d.ts.map +1 -1
  31. package/dist/QueueMaker/memQueue.js +3 -3
  32. package/dist/QueueMaker/sbqueue.js +3 -3
  33. package/dist/RequestContext.d.ts +19 -14
  34. package/dist/RequestContext.d.ts.map +1 -1
  35. package/dist/RequestContext.js +5 -5
  36. package/dist/RequestFiberSet.d.ts +2 -2
  37. package/dist/RequestFiberSet.d.ts.map +1 -1
  38. package/dist/RequestFiberSet.js +5 -5
  39. package/dist/Store/ContextMapContainer.d.ts +19 -3
  40. package/dist/Store/ContextMapContainer.d.ts.map +1 -1
  41. package/dist/Store/ContextMapContainer.js +13 -3
  42. package/dist/Store/Cosmos.d.ts.map +1 -1
  43. package/dist/Store/Cosmos.js +136 -68
  44. package/dist/Store/Disk.d.ts.map +1 -1
  45. package/dist/Store/Disk.js +3 -4
  46. package/dist/Store/Memory.d.ts +2 -2
  47. package/dist/Store/Memory.d.ts.map +1 -1
  48. package/dist/Store/Memory.js +4 -4
  49. package/dist/Store/SQL/Pg.d.ts +4 -0
  50. package/dist/Store/SQL/Pg.d.ts.map +1 -0
  51. package/dist/Store/SQL/Pg.js +186 -0
  52. package/dist/Store/SQL/query.d.ts +38 -0
  53. package/dist/Store/SQL/query.d.ts.map +1 -0
  54. package/dist/Store/SQL/query.js +367 -0
  55. package/dist/Store/SQL.d.ts +20 -0
  56. package/dist/Store/SQL.d.ts.map +1 -0
  57. package/dist/Store/SQL.js +370 -0
  58. package/dist/Store/index.d.ts +4 -1
  59. package/dist/Store/index.d.ts.map +1 -1
  60. package/dist/Store/index.js +12 -2
  61. package/dist/Store/service.d.ts +11 -5
  62. package/dist/Store/service.d.ts.map +1 -1
  63. package/dist/Store/service.js +24 -6
  64. package/dist/adapters/ServiceBus.d.ts +6 -6
  65. package/dist/adapters/ServiceBus.d.ts.map +1 -1
  66. package/dist/adapters/ServiceBus.js +9 -9
  67. package/dist/adapters/cosmos-client.d.ts +2 -2
  68. package/dist/adapters/cosmos-client.d.ts.map +1 -1
  69. package/dist/adapters/cosmos-client.js +3 -3
  70. package/dist/adapters/logger.d.ts.map +1 -1
  71. package/dist/adapters/memQueue.d.ts +2 -2
  72. package/dist/adapters/memQueue.d.ts.map +1 -1
  73. package/dist/adapters/memQueue.js +3 -3
  74. package/dist/adapters/mongo-client.d.ts +2 -2
  75. package/dist/adapters/mongo-client.d.ts.map +1 -1
  76. package/dist/adapters/mongo-client.js +3 -3
  77. package/dist/adapters/redis-client.d.ts +3 -3
  78. package/dist/adapters/redis-client.d.ts.map +1 -1
  79. package/dist/adapters/redis-client.js +3 -3
  80. package/dist/api/ContextProvider.d.ts +6 -6
  81. package/dist/api/ContextProvider.d.ts.map +1 -1
  82. package/dist/api/ContextProvider.js +6 -6
  83. package/dist/api/internal/auth.d.ts +1 -1
  84. package/dist/api/internal/events.d.ts +2 -2
  85. package/dist/api/internal/events.d.ts.map +1 -1
  86. package/dist/api/internal/events.js +6 -4
  87. package/dist/api/layerUtils.d.ts +5 -5
  88. package/dist/api/layerUtils.d.ts.map +1 -1
  89. package/dist/api/layerUtils.js +5 -5
  90. package/dist/api/routing/middleware/RouterMiddleware.d.ts +3 -3
  91. package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -1
  92. package/dist/api/routing/middleware/middleware.d.ts +35 -1
  93. package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
  94. package/dist/api/routing/middleware/middleware.js +39 -1
  95. package/dist/api/routing.d.ts +1 -5
  96. package/dist/api/routing.d.ts.map +1 -1
  97. package/dist/api/routing.js +3 -2
  98. package/dist/api/setupRequest.d.ts +6 -3
  99. package/dist/api/setupRequest.d.ts.map +1 -1
  100. package/dist/api/setupRequest.js +11 -6
  101. package/dist/logger.d.ts.map +1 -1
  102. package/examples/query.ts +30 -26
  103. package/package.json +32 -18
  104. package/src/CUPS.ts +2 -2
  105. package/src/Emailer/service.ts +2 -2
  106. package/src/MainFiberSet.ts +2 -2
  107. package/src/Model/Repository/internal/internal.ts +75 -59
  108. package/src/Model/Repository/makeRepo.ts +2 -2
  109. package/src/Operations.ts +2 -2
  110. package/src/OperationsRepo.ts +2 -2
  111. package/src/QueueMaker/SQLQueue.ts +8 -7
  112. package/src/QueueMaker/memQueue.ts +2 -2
  113. package/src/QueueMaker/sbqueue.ts +2 -2
  114. package/src/RequestContext.ts +4 -4
  115. package/src/RequestFiberSet.ts +4 -4
  116. package/src/Store/ContextMapContainer.ts +41 -2
  117. package/src/Store/Cosmos.ts +352 -255
  118. package/src/Store/Disk.ts +2 -3
  119. package/src/Store/Memory.ts +4 -4
  120. package/src/Store/SQL/Pg.ts +328 -0
  121. package/src/Store/SQL/query.ts +409 -0
  122. package/src/Store/SQL.ts +686 -0
  123. package/src/Store/index.ts +15 -1
  124. package/src/Store/service.ts +26 -7
  125. package/src/adapters/ServiceBus.ts +8 -8
  126. package/src/adapters/cosmos-client.ts +2 -2
  127. package/src/adapters/memQueue.ts +2 -2
  128. package/src/adapters/mongo-client.ts +2 -2
  129. package/src/adapters/redis-client.ts +2 -2
  130. package/src/api/ContextProvider.ts +11 -11
  131. package/src/api/internal/events.ts +6 -5
  132. package/src/api/layerUtils.ts +8 -8
  133. package/src/api/routing/middleware/RouterMiddleware.ts +4 -4
  134. package/src/api/routing/middleware/middleware.ts +43 -0
  135. package/src/api/routing.ts +3 -3
  136. package/src/api/setupRequest.ts +27 -7
  137. package/test/contextProvider.test.ts +5 -5
  138. package/test/controller.test.ts +12 -9
  139. package/test/dist/contextProvider.test.d.ts.map +1 -1
  140. package/test/dist/controller.test.d.ts.map +1 -1
  141. package/test/dist/date-query.test.d.ts.map +1 -0
  142. package/test/dist/fixtures.d.ts +18 -8
  143. package/test/dist/fixtures.d.ts.map +1 -1
  144. package/test/dist/fixtures.js +11 -9
  145. package/test/dist/query.test.d.ts.map +1 -1
  146. package/test/dist/rawQuery.test.d.ts.map +1 -1
  147. package/test/dist/requires.test.d.ts.map +1 -1
  148. package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
  149. package/test/dist/sql-store.test.d.ts.map +1 -0
  150. package/test/fixtures.ts +10 -8
  151. package/test/query.test.ts +162 -16
  152. package/test/rawQuery.test.ts +19 -17
  153. package/test/requires.test.ts +6 -5
  154. package/test/rpc-multi-middleware.test.ts +72 -3
  155. package/test/sql-store.test.ts +1064 -0
  156. package/test/validateSample.test.ts +1 -1
  157. package/tsconfig.json +0 -1
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  import type {} from "effect/Equal"
3
3
  import type {} from "effect/Hash"
4
- import { Array, Chunk, Effect, Equivalence, flow, type NonEmptyReadonlyArray, Option, pipe, Pipeable, PubSub, Result, S, SchemaAST, ServiceMap, Unify } from "effect-app"
4
+ import { Array, Chunk, Context, Effect, Equivalence, flow, type NonEmptyReadonlyArray, Option, pipe, Pipeable, PubSub, Result, S, SchemaAST, Unify } from "effect-app"
5
5
  import { toNonEmptyArray } from "effect-app/Array"
6
6
  import { NotFoundError } from "effect-app/client/errors"
7
7
  import { flatMapOption } from "effect-app/Effect"
@@ -55,14 +55,14 @@ export function makeRepoInternal<
55
55
 
56
56
  function make<RInitial = never, E = never, RPublish = never, RCtx = never>(
57
57
  args: [Evt] extends [never] ? {
58
- schemaContext?: ServiceMap.ServiceMap<RCtx>
58
+ schemaContext?: Context.Context<RCtx>
59
59
  makeInitial?: Effect.Effect<readonly T[], E, RInitial> | undefined
60
60
  config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
61
61
  partitionValue?: (e?: Encoded) => string
62
62
  }
63
63
  }
64
64
  : {
65
- schemaContext?: ServiceMap.ServiceMap<RCtx>
65
+ schemaContext?: Context.Context<RCtx>
66
66
  publishEvents: (evt: NonEmptyReadonlyArray<Evt>) => Effect.Effect<void, never, RPublish>
67
67
  makeInitial?: Effect.Effect<readonly T[], E, RInitial> | undefined
68
68
  config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
@@ -72,12 +72,12 @@ export function makeRepoInternal<
72
72
  ) {
73
73
  return Effect
74
74
  .gen(function*() {
75
- const rctx: ServiceMap.ServiceMap<RCtx> = args.schemaContext ?? ServiceMap.empty() as any
75
+ const rctx: Context.Context<RCtx> = args.schemaContext ?? Context.empty() as any
76
76
  const provideRctx = Effect.provide(rctx)
77
77
  const encodeMany = flow(
78
78
  S.encodeEffect(S.Array(schema)),
79
79
  provideRctx,
80
- Effect.withSpan("encodeMany", {}, { captureStackTrace: false })
80
+ Effect.withSpan("encodeMany", { attributes: { itemType: name } }, { captureStackTrace: false })
81
81
  )
82
82
  const decode = flow(S.decodeEffect(schema), provideRctx)
83
83
  const decodeMany = flow(
@@ -113,11 +113,14 @@ export function makeRepoInternal<
113
113
  let ast = _.ast
114
114
  if (ast._tag === "Declaration") ast = ast.typeParameters[0]!
115
115
 
116
- // In v4, to get the encoded (from) side of a schema, use SchemaAST.toEncoded
117
116
  const pickIdFromAst = (a: SchemaAST.AST) => {
118
- const encoded = SchemaAST.toEncoded(a)
119
- if (SchemaAST.isObjects(encoded)) {
120
- const field = encoded.propertySignatures.find((_) => _.name === idKey)
117
+ // Unwrap Declaration (e.g. TaggedClass) to get the underlying Objects AST
118
+ let inner = a
119
+ if (inner._tag === "Declaration") inner = inner.typeParameters[0]!
120
+ // Pick from the original AST to preserve the full encoding chain (e.g. decodeTo transformations).
121
+ // Using toEncoded would lose transformation info needed to encode Type -> Encoded.
122
+ if (SchemaAST.isObjects(inner)) {
123
+ const field = inner.propertySignatures.find((_) => _.name === idKey)
121
124
  if (field) {
122
125
  return S.Struct({ [idKey]: S.make(field.type) }) as unknown as Codec<T, Encoded>
123
126
  }
@@ -161,7 +164,7 @@ export function makeRepoInternal<
161
164
  )
162
165
  })
163
166
 
164
- const find = Effect.fn("find")(function*(id: T[IdKey]) {
167
+ const find = Effect.fn("find", { attributes: { itemType: name } })(function*(id: T[IdKey]) {
165
168
  yield* Effect.annotateCurrentSpan({ itemId: id })
166
169
 
167
170
  return yield* flatMapOption(findE(id), (_) => Effect.orDie(decode(_)))
@@ -188,62 +191,71 @@ export function makeRepoInternal<
188
191
  Effect.andThen(saveAllE)
189
192
  )
190
193
 
191
- const saveAndPublish = Effect.fn("saveAndPublish")(function*(items: Iterable<T>, events: Iterable<Evt> = []) {
192
- const it = Chunk.fromIterable(items)
193
- const evts = [...events]
194
- yield* Effect.annotateCurrentSpan({ itemIds: [...Chunk.map(it, (_) => _[idKey])], events: evts.length })
195
- return yield* saveAll(it)
196
- .pipe(
197
- Effect.andThen(Effect.sync(() => toNonEmptyArray(evts))),
198
- // TODO: for full consistency the events should be stored within the same database transaction, and then picked up.
199
- (_) => flatMapOption(_, pub),
200
- Effect.andThen(PubSub.publish(changeFeed, [Chunk.toArray(it), "save"] as [T[], "save" | "remove"])),
201
- Effect.asVoid
202
- )
203
- })
194
+ const saveAndPublish = Effect.fn("saveAndPublish", { attributes: { itemType: name } })(
195
+ function*(items: Iterable<T>, events: Iterable<Evt> = []) {
196
+ const it = Chunk.fromIterable(items)
197
+ const evts = [...events]
198
+ yield* Effect.annotateCurrentSpan({ itemIds: [...Chunk.map(it, (_) => _[idKey])], events: evts.length })
199
+ return yield* saveAll(it)
200
+ .pipe(
201
+ Effect.andThen(Effect.sync(() => toNonEmptyArray(evts))),
202
+ // TODO: for full consistency the events should be stored within the same database transaction, and then picked up.
203
+ (_) => flatMapOption(_, pub),
204
+ Effect.andThen(PubSub.publish(changeFeed, [Chunk.toArray(it), "save"] as [T[], "save" | "remove"])),
205
+ Effect.asVoid
206
+ )
207
+ }
208
+ )
204
209
 
205
- const removeAndPublish = Effect.fn("removeAndPublish")(function*(a: Iterable<T>, events: Iterable<Evt> = []) {
206
- const { set } = yield* cms
207
- const it = [...a]
208
- const evts = [...events]
209
- yield* Effect.annotateCurrentSpan({ itemIds: it.map((_) => _[idKey]), eventCount: evts.length })
210
- const items = yield* encodeMany(it).pipe(Effect.orDie)
211
- if (Array.isReadonlyArrayNonEmpty(items)) {
212
- yield* store.batchRemove(
213
- items.map((_) => (_[idKey])),
214
- args.config?.partitionValue?.(items[0])
215
- )
216
- for (const e of items) {
217
- set(e[idKey], undefined)
218
- }
219
- yield* Effect
220
- .sync(() => toNonEmptyArray(evts))
221
- // TODO: for full consistency the events should be stored within the same database transaction, and then picked up.
222
- .pipe((_) => flatMapOption(_, pub))
210
+ const removeAndPublish = Effect.fn("removeAndPublish", { attributes: { itemType: name } })(
211
+ function*(a: Iterable<T>, events: Iterable<Evt> = []) {
212
+ const { set } = yield* cms
213
+ const it = [...a]
214
+ const evts = [...events]
215
+ yield* Effect.annotateCurrentSpan({ itemIds: it.map((_) => _[idKey]), eventCount: evts.length })
216
+ const items = yield* encodeMany(it).pipe(Effect.orDie)
217
+ if (Array.isReadonlyArrayNonEmpty(items)) {
218
+ yield* store.batchRemove(
219
+ items.map((_) => (_[idKey])),
220
+ args.config?.partitionValue?.(items[0])
221
+ )
222
+ for (const e of items) {
223
+ set(e[idKey], undefined)
224
+ }
225
+ yield* Effect
226
+ .sync(() => toNonEmptyArray(evts))
227
+ // TODO: for full consistency the events should be stored within the same database transaction, and then picked up.
228
+ .pipe((_) => flatMapOption(_, pub))
223
229
 
224
- yield* PubSub.publish(changeFeed, [it, "remove"] as [T[], "save" | "remove"])
230
+ yield* PubSub.publish(changeFeed, [it, "remove"] as [T[], "save" | "remove"])
231
+ }
225
232
  }
226
- })
233
+ )
227
234
 
228
- const removeById = Effect.fn("removeById")(function*(...ids: readonly T[IdKey][]) {
229
- if (!Array.isReadonlyArrayNonEmpty(ids)) {
230
- return
231
- }
232
- const { set } = yield* cms
233
- const eids = yield* Effect.forEach(ids, (_) => encodeIdOnly(_ as any)).pipe(Effect.orDie)
234
- yield* Effect.annotateCurrentSpan({ itemIds: eids })
235
- yield* store.batchRemove(eids)
236
- for (const id of eids) {
237
- set(id, undefined)
235
+ const removeById = Effect.fn("removeById", { attributes: { itemType: name } })(
236
+ function*(...ids: readonly T[IdKey][]) {
237
+ if (!Array.isReadonlyArrayNonEmpty(ids)) {
238
+ return
239
+ }
240
+ const { set } = yield* cms
241
+ const eids = yield* Effect.forEach(ids, (_) => encodeIdOnly(_ as any)).pipe(Effect.orDie)
242
+ yield* Effect.annotateCurrentSpan({ itemIds: eids })
243
+ yield* store.batchRemove(eids)
244
+ for (const id of eids) {
245
+ set(id, undefined)
246
+ }
247
+ yield* PubSub.publish(changeFeed, [[], "remove"] as [T[], "save" | "remove"])
238
248
  }
239
- yield* PubSub.publish(changeFeed, [[], "remove"] as [T[], "save" | "remove"])
240
- })
249
+ )
241
250
 
242
251
  const parseMany = (items: readonly PM[]) =>
243
252
  Effect
244
253
  .flatMap(cms, (cm) =>
245
254
  decodeMany(items.map((_) => mapReverse(_, cm.set)))
246
- .pipe(Effect.orDie, Effect.withSpan("parseMany", {}, { captureStackTrace: false })))
255
+ .pipe(
256
+ Effect.orDie,
257
+ Effect.withSpan("parseMany", { attributes: { itemType: name } }, { captureStackTrace: false })
258
+ ))
247
259
  const parseMany2 = <A, R>(
248
260
  items: readonly PM[],
249
261
  schema: S.Codec<A, Encoded, R>
@@ -254,7 +266,10 @@ export function makeRepoInternal<
254
266
  .decodeEffect(S.Array(schema))(
255
267
  items.map((_) => mapReverse(_, cm.set))
256
268
  )
257
- .pipe(Effect.orDie, Effect.withSpan("parseMany2", {}, { captureStackTrace: false })))
269
+ .pipe(
270
+ Effect.orDie,
271
+ Effect.withSpan("parseMany2", { attributes: { itemType: name } }, { captureStackTrace: false })
272
+ ))
258
273
  const filter = <U extends keyof Encoded = keyof Encoded>(args: FilterArgs<Encoded, U>) =>
259
274
  store
260
275
  .filter(
@@ -327,6 +342,7 @@ export function makeRepoInternal<
327
342
  : eff,
328
343
  Effect.withSpan("Repository.query [effect-app/infra]", {
329
344
  attributes: {
345
+ itemType: name,
330
346
  "repository.model_name": name,
331
347
  query: { ...a, schema: a.schema ? "__SCHEMA__" : a.schema, filter: a.filter }
332
348
  }
@@ -334,7 +350,7 @@ export function makeRepoInternal<
334
350
  )
335
351
  }) as any
336
352
 
337
- const validateSample = Effect.fn("validateSample")(function*(options?: {
353
+ const validateSample = Effect.fn("validateSample", { attributes: { itemType: name } })(function*(options?: {
338
354
  percentage?: number
339
355
  maxItems?: number
340
356
  }) {
@@ -441,7 +457,7 @@ export function makeRepoInternal<
441
457
  // },
442
458
  save: (...xes: any[]) =>
443
459
  Effect.flatMap(encMany(xes), (_) => saveAllE(_)).pipe(
444
- Effect.withSpan("mapped.save", {}, { captureStackTrace: false })
460
+ Effect.withSpan("mapped.save", { attributes: { itemType: name } }, { captureStackTrace: false })
445
461
  )
446
462
  }
447
463
  }
@@ -7,7 +7,7 @@
7
7
  // import type { ParserEnv } from "effect-app/Schema/custom/Parser"
8
8
  import type {} from "effect/Equal"
9
9
  import type {} from "effect/Hash"
10
- import { Effect, type NonEmptyReadonlyArray, type S, type ServiceMap } from "effect-app"
10
+ import { type Context, Effect, type NonEmptyReadonlyArray, type S } from "effect-app"
11
11
  import type { StoreConfig, StoreMaker } from "../../Store.js"
12
12
  import type { FieldValues } from "../filter/types.js"
13
13
  import { type ExtendedRepository, extendRepo } from "./ext.js"
@@ -52,7 +52,7 @@ export interface RepositoryOptions<
52
52
  * Optional context to be provided to Schema decode/encode.
53
53
  * Useful for effectful transformations like XWithItems, where items is a transformation retrieving elements from another database table or other source.
54
54
  */
55
- schemaContext?: ServiceMap.ServiceMap<RCtx>
55
+ schemaContext?: Context.Context<RCtx>
56
56
 
57
57
  overrides?: (
58
58
  repo: Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<RSchema, RCtx>, RPublish>
package/src/Operations.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { reportError } from "@effect-app/infra/errorReporter"
2
2
  import { subHours } from "date-fns"
3
- import { Cause, copy, Duration, Effect, Exit, type Fiber, Layer, Option, S, Schedule, ServiceMap } from "effect-app"
3
+ import { Cause, Context, copy, Duration, Effect, Exit, type Fiber, Layer, Option, S, Schedule } from "effect-app"
4
4
  import { annotateLogscoped } from "effect-app/Effect"
5
5
  import { dual, pipe } from "effect-app/Function"
6
6
  import { Operation, OperationFailure, OperationId, type OperationProgress, OperationSuccess } from "effect-app/Operations"
@@ -189,7 +189,7 @@ const make = Effect.gen(function*() {
189
189
  }
190
190
  })
191
191
 
192
- export class Operations extends ServiceMap.Opaque<Operations>()("effect-app/Operations", { make }) {
192
+ export class Operations extends Context.Opaque<Operations>()("effect-app/Operations", { make }) {
193
193
  private static readonly CleanupLive = this
194
194
  .use((_) =>
195
195
  _.cleanup.pipe(
@@ -1,8 +1,8 @@
1
- import { Effect, ServiceMap } from "effect-app"
1
+ import { Context, Effect } from "effect-app"
2
2
  import { Operation } from "effect-app/Operations"
3
3
  import { makeRepo } from "./Model.js"
4
4
 
5
- export class OperationsRepo extends ServiceMap.Service<OperationsRepo>()(
5
+ export class OperationsRepo extends Context.Service<OperationsRepo>()(
6
6
  "OperationRepo",
7
7
  {
8
8
  make: Effect.gen(function*() {
@@ -3,13 +3,13 @@ import { reportNonInterruptedFailure } from "@effect-app/infra/QueueMaker/errors
3
3
  import { type QueueBase, QueueMeta } from "@effect-app/infra/QueueMaker/service"
4
4
  import { subMinutes } from "date-fns"
5
5
  import { Effect, Fiber, type NonEmptyReadonlyArray, Option, S, Tracer } from "effect-app"
6
- import type { NonEmptyString255 } from "effect-app/Schema"
6
+ import { type NonEmptyString255 } from "effect-app/Schema"
7
7
  import { pretty } from "effect-app/utils"
8
8
  import { SqlClient } from "effect/unstable/sql"
9
9
  import { SQLModel } from "../adapters/SQL.js"
10
10
  import { InfraLogger } from "../logger.js"
11
11
 
12
- export const QueueId = S.Number.pipe(S.brand("QueueId"))
12
+ export const QueueId = S.Finite.pipe(S.brand("QueueId"))
13
13
  export type QueueId = typeof QueueId.Type
14
14
 
15
15
  // TODO: let the model track and Auto Generate versionColumn on every update instead
@@ -78,14 +78,14 @@ export function makeSQLQueue<
78
78
 
79
79
  const q = {
80
80
  offer: Effect.fnUntraced(function*(body: Evt, meta: typeof QueueMeta.Type) {
81
- yield* queueRepo.insertVoid({
81
+ yield* queueRepo.insertVoid(Queue.insert.make({
82
82
  body,
83
83
  meta,
84
84
  name: queueName,
85
85
  processingAt: Option.none(),
86
86
  finishedAt: Option.none(),
87
87
  etag: crypto.randomUUID()
88
- })
88
+ }))
89
89
  }),
90
90
  take: Effect.gen(function*() {
91
91
  while (true) {
@@ -94,15 +94,16 @@ export function makeSQLQueue<
94
94
  const dec = yield* decodeDrain(first)
95
95
  const { createdAt, updatedAt, ...rest } = dec
96
96
  return yield* drainRepo.update(
97
- { ...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()
98
98
  )
99
99
  }
100
100
  if (first) return first
101
101
  yield* Effect.sleep(250)
102
102
  }
103
103
  }),
104
- finish: ({ createdAt, updatedAt, ...q }: Drain) =>
105
- 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
+ })
106
107
  }
107
108
  const queue = {
108
109
  publish: (...messages: NonEmptyReadonlyArray<Evt>) =>
@@ -25,10 +25,10 @@ export function makeMemQueue<
25
25
  const qDrain = yield* mem.getOrCreateQueue(queueDrainName)
26
26
 
27
27
  const wireSchema = S.Struct({ body: schema, meta: QueueMeta })
28
- const wireSchemaJson = S.fromJsonString(wireSchema)
28
+ const wireSchemaJson = S.fromJsonString(S.toCodecJson(wireSchema))
29
29
  const encodePublish = S.encodeEffect(wireSchemaJson)
30
30
  const drainW = S.Struct({ body: drainSchema, meta: QueueMeta })
31
- const drainWJson = S.fromJsonString(drainW)
31
+ const drainWJson = S.fromJsonString(S.toCodecJson(drainW))
32
32
 
33
33
  const parseDrain = flow(S.decodeUnknownEffect(drainWJson), Effect.orDie)
34
34
 
@@ -21,10 +21,10 @@ export function makeServiceBusQueue<
21
21
  body: schema,
22
22
  meta: QueueMeta
23
23
  })
24
- const wireSchemaJson = S.fromJsonString(wireSchema)
24
+ const wireSchemaJson = S.fromJsonString(S.toCodecJson(wireSchema))
25
25
  const encodePublish = S.encodeEffect(wireSchemaJson)
26
26
  const drainW = S.Struct({ body: drainSchema, meta: QueueMeta })
27
- const drainWJson = S.fromJsonString(drainW)
27
+ const drainWJson = S.fromJsonString(S.toCodecJson(drainW))
28
28
  const parseDrain = flow(S.decodeUnknownEffect(drainWJson), Effect.orDie)
29
29
 
30
30
  return Effect.gen(function*() {
@@ -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,6 @@
1
- import { Data, Effect, Layer, ServiceMap } from "effect-app"
1
+ import { Context, Data, Effect, Layer, RequestResolver } from "effect-app"
2
+ import { dual } from "effect/Function"
3
+ import type * as Request from "effect/Request"
2
4
  import { ContextMap } from "./service.js"
3
5
 
4
6
  // TODO: we have to create a new contextmap on every request.
@@ -7,7 +9,7 @@ import { ContextMap } from "./service.js"
7
9
  // we can call another start after startup. but it would be even better if we could Die on accessing rootmap
8
10
  // we could also make the ContextMap optional, and when missing, issue a warning instead?
9
11
 
10
- export class ContextMapContainer extends ServiceMap.Reference("ContextMapContainer", {
12
+ export class ContextMapContainer extends Context.Reference("ContextMapContainer", {
11
13
  defaultValue: (): ContextMap | "root" => "root"
12
14
  }) {
13
15
  static readonly layer = Layer.effect(this, ContextMap.make.pipe(Effect.map(ContextMap.of)))
@@ -18,3 +20,40 @@ export class ContextMapNotStartedError extends Data.TaggedError("ContextMapNotSt
18
20
  export const getContextMap = ContextMapContainer.asEffect().pipe(
19
21
  Effect.filterOrFail((_) => _ !== "root", () => new ContextMapNotStartedError())
20
22
  )
23
+
24
+ /**
25
+ * Uses the official `RequestResolver.withCache` internally,
26
+ * creating one cached resolver per ContextMap (i.e. per request).
27
+ * Uses a shared semaphore in the ContextMap to ensure safe single initialization.
28
+ */
29
+ export const withRequestResolverCache: {
30
+ <A extends Request.Request<any, any>>(options: {
31
+ readonly capacity: number
32
+ readonly strategy?: "lru" | "fifo" | undefined
33
+ }): (
34
+ self: RequestResolver.RequestResolver<A>
35
+ ) => Effect.Effect<RequestResolver.RequestResolver<A>, ContextMapNotStartedError>
36
+ <A extends Request.Request<any, any>>(
37
+ self: RequestResolver.RequestResolver<A>,
38
+ options: {
39
+ readonly capacity: number
40
+ readonly strategy?: "lru" | "fifo" | undefined
41
+ }
42
+ ): Effect.Effect<RequestResolver.RequestResolver<A>, ContextMapNotStartedError>
43
+ } = dual(2, <A extends Request.Request<any, any>>(
44
+ self: RequestResolver.RequestResolver<A>,
45
+ options: {
46
+ readonly capacity: number
47
+ readonly strategy?: "lru" | "fifo" | undefined
48
+ }
49
+ ): Effect.Effect<RequestResolver.RequestResolver<A>, ContextMapNotStartedError> => {
50
+ const cacheKey = Symbol()
51
+ return getContextMap.pipe(
52
+ Effect.flatMap((ctxMap) =>
53
+ ctxMap.getOrCreateStoreEffect(
54
+ cacheKey,
55
+ RequestResolver.withCache(self, options)
56
+ )
57
+ )
58
+ )
59
+ })