@effect-app/infra 4.0.0-beta.12 → 4.0.0-beta.120

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 (172) hide show
  1. package/CHANGELOG.md +794 -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/Registry.d.ts +20 -0
  12. package/dist/Model/Repository/Registry.d.ts.map +1 -0
  13. package/dist/Model/Repository/Registry.js +17 -0
  14. package/dist/Model/Repository/internal/internal.d.ts +3 -3
  15. package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
  16. package/dist/Model/Repository/internal/internal.js +22 -16
  17. package/dist/Model/Repository/makeRepo.d.ts +5 -4
  18. package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
  19. package/dist/Model/Repository/makeRepo.js +4 -1
  20. package/dist/Model/Repository/service.d.ts +5 -0
  21. package/dist/Model/Repository/service.d.ts.map +1 -1
  22. package/dist/Model/Repository/validation.d.ts +7 -6
  23. package/dist/Model/Repository/validation.d.ts.map +1 -1
  24. package/dist/Model/Repository.d.ts +1 -0
  25. package/dist/Model/Repository.d.ts.map +1 -1
  26. package/dist/Model/Repository.js +2 -1
  27. package/dist/Model/query/dsl.d.ts +9 -9
  28. package/dist/Model.d.ts +1 -0
  29. package/dist/Model.d.ts.map +1 -1
  30. package/dist/Model.js +2 -1
  31. package/dist/Operations.d.ts +2 -2
  32. package/dist/Operations.d.ts.map +1 -1
  33. package/dist/Operations.js +3 -3
  34. package/dist/OperationsRepo.d.ts +3 -3
  35. package/dist/OperationsRepo.d.ts.map +1 -1
  36. package/dist/OperationsRepo.js +3 -3
  37. package/dist/QueueMaker/SQLQueue.d.ts +2 -4
  38. package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
  39. package/dist/QueueMaker/SQLQueue.js +8 -6
  40. package/dist/QueueMaker/errors.d.ts +1 -1
  41. package/dist/QueueMaker/errors.d.ts.map +1 -1
  42. package/dist/QueueMaker/memQueue.js +3 -3
  43. package/dist/QueueMaker/sbqueue.js +3 -3
  44. package/dist/RequestContext.d.ts +22 -17
  45. package/dist/RequestContext.d.ts.map +1 -1
  46. package/dist/RequestContext.js +5 -5
  47. package/dist/RequestFiberSet.d.ts +2 -2
  48. package/dist/RequestFiberSet.d.ts.map +1 -1
  49. package/dist/RequestFiberSet.js +5 -5
  50. package/dist/Store/ContextMapContainer.d.ts +19 -3
  51. package/dist/Store/ContextMapContainer.d.ts.map +1 -1
  52. package/dist/Store/ContextMapContainer.js +13 -3
  53. package/dist/Store/Cosmos.d.ts.map +1 -1
  54. package/dist/Store/Cosmos.js +136 -68
  55. package/dist/Store/Disk.d.ts.map +1 -1
  56. package/dist/Store/Disk.js +24 -21
  57. package/dist/Store/Memory.d.ts +2 -2
  58. package/dist/Store/Memory.d.ts.map +1 -1
  59. package/dist/Store/Memory.js +26 -21
  60. package/dist/Store/SQL/Pg.d.ts +4 -0
  61. package/dist/Store/SQL/Pg.d.ts.map +1 -0
  62. package/dist/Store/SQL/Pg.js +191 -0
  63. package/dist/Store/SQL/query.d.ts +38 -0
  64. package/dist/Store/SQL/query.d.ts.map +1 -0
  65. package/dist/Store/SQL/query.js +367 -0
  66. package/dist/Store/SQL.d.ts +20 -0
  67. package/dist/Store/SQL.d.ts.map +1 -0
  68. package/dist/Store/SQL.js +381 -0
  69. package/dist/Store/index.d.ts +4 -1
  70. package/dist/Store/index.d.ts.map +1 -1
  71. package/dist/Store/index.js +15 -3
  72. package/dist/Store/service.d.ts +16 -5
  73. package/dist/Store/service.d.ts.map +1 -1
  74. package/dist/Store/service.js +24 -6
  75. package/dist/adapters/ServiceBus.d.ts +6 -6
  76. package/dist/adapters/ServiceBus.d.ts.map +1 -1
  77. package/dist/adapters/ServiceBus.js +9 -9
  78. package/dist/adapters/cosmos-client.d.ts +2 -2
  79. package/dist/adapters/cosmos-client.d.ts.map +1 -1
  80. package/dist/adapters/cosmos-client.js +3 -3
  81. package/dist/adapters/logger.d.ts.map +1 -1
  82. package/dist/adapters/memQueue.d.ts +2 -2
  83. package/dist/adapters/memQueue.d.ts.map +1 -1
  84. package/dist/adapters/memQueue.js +3 -3
  85. package/dist/adapters/mongo-client.d.ts +2 -2
  86. package/dist/adapters/mongo-client.d.ts.map +1 -1
  87. package/dist/adapters/mongo-client.js +3 -3
  88. package/dist/adapters/redis-client.d.ts +3 -3
  89. package/dist/adapters/redis-client.d.ts.map +1 -1
  90. package/dist/adapters/redis-client.js +3 -3
  91. package/dist/api/ContextProvider.d.ts +6 -6
  92. package/dist/api/ContextProvider.d.ts.map +1 -1
  93. package/dist/api/ContextProvider.js +6 -6
  94. package/dist/api/internal/auth.d.ts +1 -1
  95. package/dist/api/internal/events.d.ts +2 -2
  96. package/dist/api/internal/events.d.ts.map +1 -1
  97. package/dist/api/internal/events.js +11 -7
  98. package/dist/api/layerUtils.d.ts +5 -5
  99. package/dist/api/layerUtils.d.ts.map +1 -1
  100. package/dist/api/layerUtils.js +5 -5
  101. package/dist/api/routing/middleware/RouterMiddleware.d.ts +3 -3
  102. package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -1
  103. package/dist/api/routing/middleware/middleware.d.ts +35 -1
  104. package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
  105. package/dist/api/routing/middleware/middleware.js +39 -1
  106. package/dist/api/routing.d.ts +1 -5
  107. package/dist/api/routing.d.ts.map +1 -1
  108. package/dist/api/routing.js +3 -2
  109. package/dist/api/setupRequest.d.ts +6 -3
  110. package/dist/api/setupRequest.d.ts.map +1 -1
  111. package/dist/api/setupRequest.js +11 -6
  112. package/dist/logger.d.ts.map +1 -1
  113. package/examples/query.ts +30 -26
  114. package/package.json +36 -18
  115. package/src/CUPS.ts +2 -2
  116. package/src/Emailer/service.ts +2 -2
  117. package/src/MainFiberSet.ts +2 -2
  118. package/src/Model/Repository/Registry.ts +33 -0
  119. package/src/Model/Repository/internal/internal.ts +76 -59
  120. package/src/Model/Repository/makeRepo.ts +7 -4
  121. package/src/Model/Repository/service.ts +6 -0
  122. package/src/Model/Repository.ts +1 -0
  123. package/src/Model.ts +1 -0
  124. package/src/Operations.ts +2 -2
  125. package/src/OperationsRepo.ts +2 -2
  126. package/src/QueueMaker/SQLQueue.ts +8 -7
  127. package/src/QueueMaker/memQueue.ts +2 -2
  128. package/src/QueueMaker/sbqueue.ts +2 -2
  129. package/src/RequestContext.ts +4 -4
  130. package/src/RequestFiberSet.ts +4 -4
  131. package/src/Store/ContextMapContainer.ts +41 -2
  132. package/src/Store/Cosmos.ts +350 -255
  133. package/src/Store/Disk.ts +37 -33
  134. package/src/Store/Memory.ts +29 -22
  135. package/src/Store/SQL/Pg.ts +321 -0
  136. package/src/Store/SQL/query.ts +409 -0
  137. package/src/Store/SQL.ts +674 -0
  138. package/src/Store/index.ts +17 -2
  139. package/src/Store/service.ts +31 -7
  140. package/src/adapters/ServiceBus.ts +8 -8
  141. package/src/adapters/cosmos-client.ts +2 -2
  142. package/src/adapters/memQueue.ts +2 -2
  143. package/src/adapters/mongo-client.ts +2 -2
  144. package/src/adapters/redis-client.ts +2 -2
  145. package/src/api/ContextProvider.ts +11 -11
  146. package/src/api/internal/events.ts +14 -9
  147. package/src/api/layerUtils.ts +8 -8
  148. package/src/api/routing/middleware/RouterMiddleware.ts +4 -4
  149. package/src/api/routing/middleware/middleware.ts +43 -0
  150. package/src/api/routing.ts +3 -3
  151. package/src/api/setupRequest.ts +27 -7
  152. package/test/contextProvider.test.ts +11 -11
  153. package/test/controller.test.ts +12 -9
  154. package/test/dist/contextProvider.test.d.ts.map +1 -1
  155. package/test/dist/controller.test.d.ts.map +1 -1
  156. package/test/dist/date-query.test.d.ts.map +1 -0
  157. package/test/dist/fixtures.d.ts +19 -9
  158. package/test/dist/fixtures.d.ts.map +1 -1
  159. package/test/dist/fixtures.js +11 -9
  160. package/test/dist/query.test.d.ts.map +1 -1
  161. package/test/dist/rawQuery.test.d.ts.map +1 -1
  162. package/test/dist/requires.test.d.ts.map +1 -1
  163. package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
  164. package/test/dist/sql-store.test.d.ts.map +1 -0
  165. package/test/fixtures.ts +10 -8
  166. package/test/query.test.ts +182 -33
  167. package/test/rawQuery.test.ts +22 -18
  168. package/test/requires.test.ts +6 -5
  169. package/test/rpc-multi-middleware.test.ts +72 -3
  170. package/test/sql-store.test.ts +1064 -0
  171. package/test/validateSample.test.ts +12 -9
  172. package/tsconfig.json +0 -1
@@ -1,17 +1,21 @@
1
1
  /* eslint-disable unused-imports/no-unused-vars */
2
2
  /* eslint-disable @typescript-eslint/no-empty-object-type */
3
3
  /* eslint-disable @typescript-eslint/no-explicit-any */
4
- import { Effect, flow, Layer, Option, pipe, S, ServiceMap, Struct } from "effect-app"
4
+ import { SchemaTransformation } from "effect"
5
+ import { Context, Effect, flow, Layer, Option, pipe, S, Struct } from "effect-app"
5
6
  import { inspect } from "util"
6
7
  import { expect, expectTypeOf, it } from "vitest"
7
8
  import { setupRequestContextFromCurrent } from "../src/api/setupRequest.js"
8
9
  import { and, count, make, one, or, order, page, project, type QueryEnd, type QueryProjection, type QueryWhere, toFilter, where } from "../src/Model/query.js"
9
10
  import { makeRepo } from "../src/Model/Repository.js"
11
+ import { RepositoryRegistryLive } from "../src/Model/Repository/Registry.js"
10
12
  import { memFilter, MemoryStoreLive } from "../src/Store/Memory.js"
11
13
  import { SomeService } from "./fixtures.js"
12
14
 
15
+ const TestStoreLive = Layer.merge(MemoryStoreLive, RepositoryRegistryLive)
16
+
13
17
  const str = S.Struct({ _tag: S.Literal("string"), value: S.String })
14
- const num = S.Struct({ _tag: S.Literal("number"), value: S.Number })
18
+ const num = S.Struct({ _tag: S.Literal("number"), value: S.Finite })
15
19
  const someUnion = S.Union([str, num])
16
20
 
17
21
  export class Something extends S.Class<Something>("Something")({
@@ -19,7 +23,7 @@ export class Something extends S.Class<Something>("Something")({
19
23
  displayName: S.NonEmptyString255,
20
24
  name: S.NullOr(S.NonEmptyString255).withDefault,
21
25
  n: S.Date.withDefault,
22
- union: someUnion.pipe(S.withDefaultConstructor(() => ({ _tag: "string" as const, value: "hi" })))
26
+ union: someUnion.pipe(S.withConstructorDefault(Effect.succeed({ _tag: "string" as const, value: "hi" })))
23
27
  }) {}
24
28
  export declare namespace Something {
25
29
  // eslint-disable-next-line @typescript-eslint/no-empty-object-type
@@ -90,8 +94,8 @@ it("works", () => {
90
94
 
91
95
  const processed = memFilter(interpreted)(items.map((_) =>
92
96
  S.encodeUnknownSync(S.Struct({
93
- ...Something.omit("displayName"),
94
- displayName: S.Literal("Verona", "Riley")
97
+ ...Struct.omit(Something.fields, ["displayName"]),
98
+ displayName: S.Literals(["Verona", "Riley"])
95
99
  }))(_)
96
100
  ))
97
101
 
@@ -99,7 +103,7 @@ it("works", () => {
99
103
  })
100
104
 
101
105
  // @effect-diagnostics-next-line missingEffectServiceDependency:off
102
- class SomethingRepo extends ServiceMap.Service<SomethingRepo>()("SomethingRepo", {
106
+ class SomethingRepo extends Context.Service<SomethingRepo>()("SomethingRepo", {
103
107
  make: Effect.gen(function*() {
104
108
  return yield* makeRepo("Something", Something, {})
105
109
  })
@@ -112,7 +116,7 @@ class SomethingRepo extends ServiceMap.Service<SomethingRepo>()("SomethingRepo",
112
116
  })
113
117
  )
114
118
  .pipe(
115
- Layer.provide(MemoryStoreLive)
119
+ Layer.provide(TestStoreLive)
116
120
  )
117
121
  }
118
122
 
@@ -156,8 +160,6 @@ it("works with repo", () =>
156
160
 
157
161
  expectTypeOf(smtArr).toEqualTypeOf<readonly Something[]>()
158
162
 
159
- console.log(" $$$$$$")
160
- console.log(Struct.pick(["id", "displayName"]))
161
163
  expect(q1).toEqual(items.slice(0, 2).toReversed().map(Struct.pick(["id", "displayName"])))
162
164
  expect(q2).toEqual(items.slice(0, 2).toReversed().map(Struct.pick(["displayName"])))
163
165
  })
@@ -281,7 +283,7 @@ it(
281
283
  expect(result).toEqual([])
282
284
  expect(result2).toEqual([])
283
285
  })
284
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
286
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
285
287
  )
286
288
 
287
289
  it(
@@ -467,7 +469,7 @@ it(
467
469
 
468
470
  expect([]).toEqual([])
469
471
  })
470
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
472
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
471
473
  )
472
474
 
473
475
  it(
@@ -510,7 +512,7 @@ it(
510
512
 
511
513
  expect([]).toEqual([])
512
514
  })
513
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
515
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
514
516
  )
515
517
 
516
518
  it(
@@ -521,8 +523,8 @@ it(
521
523
  const schema = S.Struct({
522
524
  id: S.String,
523
525
  createdAt: S.Date.pipe(
524
- S.withDecodingDefault(() => new Date().toISOString()),
525
- S.withConstructorDefault(() => Option.some(new Date()))
526
+ S.withDecodingDefault(Effect.sync(() => new Date().toISOString())),
527
+ S.withConstructorDefault(Effect.sync(() => new Date()))
526
528
  )
527
529
  })
528
530
  const repo = yield* makeRepo(
@@ -534,8 +536,8 @@ it(
534
536
  const outputSchema = S.Struct({
535
537
  id: S.Literal("123"),
536
538
  createdAt: S.Date.pipe(
537
- S.withDecodingDefault(() => new Date().toISOString()),
538
- S.withConstructorDefault(() => Option.some(new Date()))
539
+ S.withDecodingDefault(Effect.sync(() => new Date().toISOString())),
540
+ S.withConstructorDefault(Effect.sync(() => new Date()))
539
541
  )
540
542
  })
541
543
 
@@ -543,7 +545,7 @@ it(
543
545
 
544
546
  expect(result).toEqual([])
545
547
  })
546
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
548
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
547
549
  )
548
550
 
549
551
  it(
@@ -553,7 +555,7 @@ it(
553
555
  .gen(function*() {
554
556
  const schema = S.Struct({
555
557
  id: S.String,
556
- literals: S.Literal("a", "b", "c")
558
+ literals: S.Literals(["a", "b", "c"])
557
559
  })
558
560
 
559
561
  type Schema = typeof schema.Type
@@ -573,7 +575,7 @@ it(
573
575
 
574
576
  expect(result).toEqual([])
575
577
  })
576
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
578
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
577
579
  )
578
580
 
579
581
  it(
@@ -583,7 +585,7 @@ it(
583
585
  .gen(function*() {
584
586
  const schema = S.Struct({
585
587
  id: S.String,
586
- literals: S.Union([S.Literal("a", "b", "c"), S.Null])
588
+ literals: S.Union([S.Literals(["a", "b", "c"]), S.Null])
587
589
  })
588
590
 
589
591
  type Schema = typeof schema.Type
@@ -617,7 +619,7 @@ it(
617
619
 
618
620
  expect(result).toEqual([])
619
621
  })
620
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
622
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
621
623
  )
622
624
 
623
625
  it(
@@ -661,7 +663,7 @@ it(
661
663
 
662
664
  expect(result).toEqual([])
663
665
  })
664
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
666
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise)
665
667
  )
666
668
 
667
669
  it("remove null from one constituent of a tagged union", () =>
@@ -674,7 +676,7 @@ it("remove null from one constituent of a tagged union", () =>
674
676
 
675
677
  class BB extends S.Class<BB>("BB")({
676
678
  id: S.Literal("BB"),
677
- b: S.NullOr(S.Number)
679
+ b: S.NullOr(S.Finite)
678
680
  }) {}
679
681
 
680
682
  type Union = AA | BB
@@ -710,7 +712,7 @@ it("remove null from one constituent of a tagged union", () =>
710
712
  })[]
711
713
  >()
712
714
  })
713
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
715
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
714
716
 
715
717
  it("refine 3", () =>
716
718
  Effect
@@ -748,7 +750,7 @@ it("refine 3", () =>
748
750
  const resQuer1 = yield* repo.query(where("id", "AA"))
749
751
  expectTypeOf(resQuer1).toEqualTypeOf<readonly AA[]>()
750
752
  })
751
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
753
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
752
754
 
753
755
  it("my test", () =>
754
756
  Effect
@@ -766,7 +768,7 @@ it("my test", () =>
766
768
  )
767
769
  expectTypeOf(resQuer1).toEqualTypeOf<readonly AA[]>()
768
770
  })
769
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
771
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
770
772
 
771
773
  it("refine inner without imposing a projection", () =>
772
774
  Effect
@@ -810,7 +812,7 @@ it("refine inner without imposing a projection", () =>
810
812
  where("union._tag", "AA"),
811
813
  // But if I wanna the whole Data as output ignoring the inner refinement
812
814
  // I wanna be able to do so
813
- project(S.Struct(Data.pick("union")))
815
+ project(Data.mapFields(Struct.pick(["union"])))
814
816
  )
815
817
 
816
818
  expectTypeOf(query2).toEqualTypeOf<
@@ -841,7 +843,7 @@ it("refine inner without imposing a projection", () =>
841
843
  }[]
842
844
  >()
843
845
  })
844
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
846
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
845
847
 
846
848
  it("does not allow string queries on arrays", () =>
847
849
  Effect
@@ -876,7 +878,7 @@ it("does not allow string queries on arrays", () =>
876
878
  expectTypeOf(good3).toEqualTypeOf<QueryWhere<Some, Some>>()
877
879
  expectTypeOf(good4).toEqualTypeOf<QueryWhere<Some, Some>>()
878
880
  })
879
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
881
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
880
882
 
881
883
  it("test array.length", () =>
882
884
  Effect
@@ -917,7 +919,7 @@ it("test array.length", () =>
917
919
  QueryWhere<Something, Something>
918
920
  >()
919
921
  })
920
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
922
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
921
923
 
922
924
  it("distribution over union", () =>
923
925
  Effect
@@ -941,7 +943,7 @@ it("distribution over union", () =>
941
943
  })[]
942
944
  >()
943
945
  })
944
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
946
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
945
947
 
946
948
  it("refine nested union", () =>
947
949
  Effect
@@ -982,7 +984,154 @@ it("refine nested union", () =>
982
984
  }[]
983
985
  >()
984
986
  })
985
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
987
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
988
+
989
+ it("find with transformed id", () =>
990
+ Effect
991
+ .gen(function*() {
992
+ const ConfiguratorId = S.NonEmptyString255
993
+
994
+ class PreconfigurationId extends S.Class<PreconfigurationId>("PreconfigurationId")({
995
+ configuratorId: ConfiguratorId,
996
+ label: S.NonEmptyString50
997
+ }) {}
998
+
999
+ const PreconfigurationIdFromString = S.NonEmptyString255.pipe(
1000
+ S.decodeTo(
1001
+ S.toType(PreconfigurationId),
1002
+ SchemaTransformation.transformOrFail({
1003
+ decode: Effect.fnUntraced(function*(value) {
1004
+ const values = value.split("_")
1005
+ const label = yield* S.SchemaParser.decodeUnknownEffect(S.NonEmptyString50)(values.pop())
1006
+ const configuratorId = yield* S.SchemaParser.decodeUnknownEffect(ConfiguratorId)(values.join("_"))
1007
+ return new PreconfigurationId({ configuratorId, label })
1008
+ }),
1009
+ encode: (id) => Effect.succeed(S.NonEmptyString255(`${id.configuratorId}_${id.label}`))
1010
+ })
1011
+ ),
1012
+ S.revealCodec
1013
+ )
1014
+
1015
+ const Preconfiguration = S.Struct({
1016
+ id: PreconfigurationIdFromString,
1017
+ name: S.String
1018
+ })
1019
+
1020
+ const repo = yield* makeRepo("Preconfiguration", Preconfiguration, { idKey: "id" as const })
1021
+
1022
+ const id = new PreconfigurationId({
1023
+ configuratorId: S.NonEmptyString255("myConfigurator"),
1024
+ label: S.NonEmptyString50("myLabel")
1025
+ })
1026
+ const item = { id, name: "test preconfig" }
1027
+
1028
+ yield* repo.saveAndPublish([item])
1029
+
1030
+ const found = yield* repo.find(id)
1031
+ expect(Option.isSome(found)).toBe(true)
1032
+ expect(Option.getOrThrow(found).name).toBe("test preconfig")
1033
+ expect(Option.getOrThrow(found).id).toEqual(id)
1034
+
1035
+ const notFound = yield* repo.find(
1036
+ new PreconfigurationId({
1037
+ configuratorId: S.NonEmptyString255("other"),
1038
+ label: S.NonEmptyString50("nope")
1039
+ })
1040
+ )
1041
+ expect(Option.isNone(notFound)).toBe(true)
1042
+ })
1043
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1044
+
1045
+ it("find with transformed id in tagged union", () =>
1046
+ Effect
1047
+ .gen(function*() {
1048
+ const ConfiguratorId = S.NonEmptyString255
1049
+
1050
+ class PreconfigurationId extends S.Class<PreconfigurationId>("PreconfigurationId")({
1051
+ configuratorId: ConfiguratorId,
1052
+ label: S.NonEmptyString50
1053
+ }) {}
1054
+
1055
+ const PreconfigurationIdFromString = S.NonEmptyString255.pipe(
1056
+ S.decodeTo(
1057
+ S.toType(PreconfigurationId),
1058
+ SchemaTransformation.transformOrFail({
1059
+ decode: Effect.fnUntraced(function*(value) {
1060
+ const values = value.split("_")
1061
+ const label = yield* S.SchemaParser.decodeUnknownEffect(S.NonEmptyString50)(values.pop())
1062
+ const configuratorId = yield* S.SchemaParser.decodeUnknownEffect(ConfiguratorId)(values.join("_"))
1063
+ return new PreconfigurationId({ configuratorId, label })
1064
+ }),
1065
+ encode: (id) => Effect.succeed(S.NonEmptyString255(`${id.configuratorId}_${id.label}`))
1066
+ })
1067
+ ),
1068
+ S.revealCodec
1069
+ )
1070
+
1071
+ class Draft extends S.TaggedClass<Draft>()("Draft", {
1072
+ id: PreconfigurationIdFromString,
1073
+ name: S.String
1074
+ }) {}
1075
+
1076
+ class Published extends S.TaggedClass<Published>()("Published", {
1077
+ id: PreconfigurationIdFromString,
1078
+ name: S.String,
1079
+ publishedAt: S.String
1080
+ }) {}
1081
+
1082
+ class Archived extends S.TaggedClass<Archived>()("Archived", {
1083
+ id: PreconfigurationIdFromString,
1084
+ name: S.String,
1085
+ archivedAt: S.String
1086
+ }) {}
1087
+
1088
+ const Preconfiguration = S.Union([Draft, Published, Archived])
1089
+
1090
+ const repo = yield* makeRepo("Preconfiguration", Preconfiguration, {})
1091
+
1092
+ const id1 = new PreconfigurationId({
1093
+ configuratorId: S.NonEmptyString255("conf1"),
1094
+ label: S.NonEmptyString50("draft1")
1095
+ })
1096
+ const id2 = new PreconfigurationId({
1097
+ configuratorId: S.NonEmptyString255("conf2"),
1098
+ label: S.NonEmptyString50("pub1")
1099
+ })
1100
+ const id3 = new PreconfigurationId({
1101
+ configuratorId: S.NonEmptyString255("conf3"),
1102
+ label: S.NonEmptyString50("arch1")
1103
+ })
1104
+
1105
+ const draft = new Draft({ id: id1, name: "my draft" })
1106
+ const published = new Published({ id: id2, name: "my published", publishedAt: "2024-01-01" })
1107
+ const archived = new Archived({ id: id3, name: "my archived", archivedAt: "2024-06-01" })
1108
+
1109
+ yield* repo.saveAndPublish([draft, published, archived])
1110
+
1111
+ // find each by their PreconfigurationId instance
1112
+ const foundDraft = yield* repo.find(id1)
1113
+ expect(Option.isSome(foundDraft)).toBe(true)
1114
+ expect(Option.getOrThrow(foundDraft)._tag).toBe("Draft")
1115
+ expect(Option.getOrThrow(foundDraft).name).toBe("my draft")
1116
+
1117
+ const foundPublished = yield* repo.find(id2)
1118
+ expect(Option.isSome(foundPublished)).toBe(true)
1119
+ expect(Option.getOrThrow(foundPublished)._tag).toBe("Published")
1120
+
1121
+ const foundArchived = yield* repo.find(id3)
1122
+ expect(Option.isSome(foundArchived)).toBe(true)
1123
+ expect(Option.getOrThrow(foundArchived)._tag).toBe("Archived")
1124
+
1125
+ // not found
1126
+ const notFound = yield* repo.find(
1127
+ new PreconfigurationId({
1128
+ configuratorId: S.NonEmptyString255("nope"),
1129
+ label: S.NonEmptyString50("nope")
1130
+ })
1131
+ )
1132
+ expect(Option.isNone(notFound)).toBe(true)
1133
+ })
1134
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
986
1135
 
987
1136
  it("refine union with nested union", () =>
988
1137
  Effect
@@ -1097,4 +1246,4 @@ it("refine union with nested union", () =>
1097
1246
  })[]
1098
1247
  >()
1099
1248
  })
1100
- .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1249
+ .pipe(Effect.provide(TestStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
@@ -1,9 +1,10 @@
1
1
  import { describe, expect, it } from "@effect/vitest"
2
- import { Array, Config, Effect, flow, Layer, ManagedRuntime, Redacted, References, Result, S, ServiceMap } from "effect-app"
2
+ import { Array, Config, Context, Effect, flow, Layer, ManagedRuntime, Redacted, References, Result, S } from "effect-app"
3
3
  import { LogLevels } from "effect-app/utils"
4
4
  import { setupRequestContextFromCurrent } from "../src/api/setupRequest.js"
5
5
  import { and, or, project, where, whereEvery, whereSome } from "../src/Model/query.js"
6
6
  import { makeRepo } from "../src/Model/Repository/makeRepo.js"
7
+ import { RepositoryRegistryLive } from "../src/Model/Repository/Registry.js"
7
8
  import { CosmosStoreLayer } from "../src/Store/Cosmos.js"
8
9
  import { MemoryStoreLive } from "../src/Store/Memory.js"
9
10
 
@@ -24,7 +25,7 @@ class Something extends S.Class<Something>("Something")({
24
25
  id: S.String,
25
26
  name: S.String,
26
27
  description: S.String,
27
- items: S.Array(S.Struct({ id: S.String, value: S.Number, description: S.String }))
28
+ items: S.Array(S.Struct({ id: S.String, value: S.Finite, description: S.String }))
28
29
  }) {}
29
30
 
30
31
  const items = [
@@ -49,7 +50,7 @@ const items = [
49
50
  ]
50
51
 
51
52
  // @effect-diagnostics-next-line missingEffectServiceDependency:off
52
- class SomethingRepo extends ServiceMap.Service<SomethingRepo>()(
53
+ class SomethingRepo extends Context.Service<SomethingRepo>()(
53
54
  "SomethingRepo",
54
55
  {
55
56
  make: Effect.gen(function*() {
@@ -76,28 +77,31 @@ class SomethingRepo extends ServiceMap.Service<SomethingRepo>()(
76
77
  static readonly Test = this
77
78
  .layer
78
79
  .pipe(
79
- Layer.provide(MemoryStoreLive)
80
+ Layer.provide(Layer.merge(MemoryStoreLive, RepositoryRegistryLive))
80
81
  )
81
82
 
82
83
  static readonly TestCosmos = this
83
84
  .layer
84
85
  .pipe(
85
86
  Layer.provide(
86
- Effect.gen(function*() {
87
- const url = yield* Config.redacted("STORAGE_URL").pipe(
88
- Config.withDefault(
89
- Redacted.make(
90
- // the emulator doesn't implement array projections :/ so you need an actual cloud instance!
91
- "AccountEndpoint=http://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="
87
+ Effect
88
+ .gen(function*() {
89
+ const url = yield* Config.redacted("STORAGE_URL").pipe(
90
+ Config.withDefault(
91
+ Redacted.make(
92
+ // the emulator doesn't implement array projections :/ so you need an actual cloud instance!
93
+ "AccountEndpoint=http://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="
94
+ )
92
95
  )
93
96
  )
94
- )
95
- return CosmosStoreLayer({
96
- dbName: "test",
97
- prefix: "",
98
- url
97
+ return CosmosStoreLayer({
98
+ dbName: "test",
99
+ prefix: "",
100
+ url
101
+ })
102
+ .pipe(Layer.merge(RepositoryRegistryLive))
99
103
  })
100
- }).pipe(Layer.unwrap)
104
+ .pipe(Layer.unwrap)
101
105
  )
102
106
  )
103
107
  }
@@ -107,7 +111,7 @@ describe("select first-level array fields", () => {
107
111
  .gen(function*() {
108
112
  const repo = yield* SomethingRepo
109
113
 
110
- const projected = S.Struct({ name: S.String, items: S.Array(S.Struct({ id: S.String, value: S.Number })) })
114
+ const projected = S.Struct({ name: S.String, items: S.Array(S.Struct({ id: S.String, value: S.Finite })) })
111
115
 
112
116
  // ok crazy lol, "value" is a reserved word in CosmosDB, so we have to use t["value"] as a field name instead of t.value
113
117
  const items = yield* repo.queryRaw(projected, {
@@ -159,7 +163,7 @@ describe("select first-level array fields", () => {
159
163
  .pipe(Effect.provide(SomethingRepo.Test), rt.runPromise))
160
164
  })
161
165
 
162
- const projected = S.Struct({ name: S.String, items: S.Array(S.Struct({ id: S.String, value: S.Number })) })
166
+ const projected = S.Struct({ name: S.String, items: S.Array(S.Struct({ id: S.String, value: S.Finite })) })
163
167
 
164
168
  const expected = [
165
169
  {
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, expectTypeOf, it } from "@effect/vitest"
2
- import { Effect, Layer, Result, S, ServiceMap } from "effect-app"
2
+ import { Context, Effect, Layer, Result, S } from "effect-app"
3
3
  import { NotLoggedInError, UnauthorizedError } from "effect-app/client"
4
4
  import { HttpHeaders } from "effect-app/http"
5
5
  import * as RpcX from "effect-app/rpc"
@@ -63,11 +63,12 @@ const testSuite = (_mw: typeof middleware3) =>
63
63
  "works",
64
64
  Effect.fn(function*() {
65
65
  const defaultOpts = {
66
+ client: null as any, // TODO?
66
67
  headers: HttpHeaders.fromRecordUnsafe({}),
67
68
  payload: { _tag: "Test" },
68
69
  clientId: 0,
69
70
  requestId: "test-id" as any,
70
- rpc: { ...TestRpc, annotations: ServiceMap.make(_mw.requestContext, {}) }
71
+ rpc: { ...TestRpc, annotations: Context.make(_mw.requestContext, {}) }
71
72
  }
72
73
  const next = Effect.void as unknown as Effect.Effect<SuccessValue, unhandled, never>
73
74
  const layer = _mw.layer.pipe(
@@ -89,7 +90,7 @@ const testSuite = (_mw: typeof middleware3) =>
89
90
  headers: HttpHeaders.fromRecordUnsafe({ "x-user": "test-user", "x-is-manager": "true" }),
90
91
  rpc: {
91
92
  ...defaultOpts.rpc,
92
- annotations: ServiceMap.make(_mw.requestContext, { requireRoles: ["manager"] })
93
+ annotations: Context.make(_mw.requestContext, { requireRoles: ["manager"] })
93
94
  }
94
95
  })
95
96
  )
@@ -127,7 +128,7 @@ const testSuite = (_mw: typeof middleware3) =>
127
128
  Object.assign({ ...defaultOpts }, {
128
129
  rpc: {
129
130
  ...defaultOpts.rpc,
130
- annotations: ServiceMap.make(_mw.requestContext, { requireRoles: ["manager"] })
131
+ annotations: Context.make(_mw.requestContext, { requireRoles: ["manager"] })
131
132
  }
132
133
  })
133
134
  )
@@ -153,7 +154,7 @@ const testSuite = (_mw: typeof middleware3) =>
153
154
  {
154
155
  rpc: {
155
156
  ...defaultOpts.rpc,
156
- annotations: ServiceMap.make(_mw.requestContext, { requireRoles: ["manager"] })
157
+ annotations: Context.make(_mw.requestContext, { requireRoles: ["manager"] })
157
158
  }
158
159
  }
159
160
  )
@@ -1,14 +1,14 @@
1
1
  import { NodeHttpServer } from "@effect/platform-node"
2
2
  import { expect, expectTypeOf, it } from "@effect/vitest"
3
- import { Console, Effect, Layer, Result } from "effect"
4
- import { S } from "effect-app"
3
+ import { Console, Effect, Layer, Ref, Result } from "effect"
4
+ import { Context, S } from "effect-app"
5
5
  import { NotLoggedInError } from "effect-app/client"
6
6
  import { HttpRouter } from "effect-app/http"
7
7
  import { DefaultGenericMiddlewares } from "effect-app/middleware"
8
8
  import { MiddlewareMaker } from "effect-app/rpc"
9
9
  import { middlewareGroup } from "effect-app/rpc/MiddlewareMaker"
10
10
  import { FetchHttpClient } from "effect/unstable/http"
11
- import { RpcClient, RpcGroup, RpcSerialization, RpcServer, RpcTest } from "effect/unstable/rpc"
11
+ import { Rpc, RpcClient, RpcGroup, RpcSerialization, RpcServer, RpcTest } from "effect/unstable/rpc"
12
12
  import { createServer } from "http"
13
13
  import { DefaultGenericMiddlewaresLive } from "../src/api/routing.js"
14
14
  import { AllowAnonymous, AllowAnonymousLive, RequestContextMap, RequireRoles, RequireRolesLive, Some, SomeElseMiddleware, SomeElseMiddlewareLive, SomeMiddleware, SomeMiddlewareLive, SomeService, Test, TestLive, UserProfile } from "./fixtures.js"
@@ -136,3 +136,72 @@ it.live(
136
136
  Effect.provide(RpcTestLayer)
137
137
  )
138
138
  )
139
+
140
+ // Per-request service isolation test
141
+
142
+ class PerRequestCounter extends Context.Service<PerRequestCounter>()(
143
+ "PerRequestCounter",
144
+ { make: Effect.sync(() => ({ a: 0 })) }
145
+ ) {
146
+ static Default = Layer.effect(this, this.make)
147
+ }
148
+
149
+ class GlobalCounter extends Context.Service<GlobalCounter, {
150
+ readonly ref: Ref.Ref<number>
151
+ }>()("GlobalCounter") {}
152
+
153
+ const CounterRpcs = RpcGroup.make(
154
+ Rpc.make("incrementA", {
155
+ success: S.Number
156
+ }),
157
+ Rpc.make("incrementB", {
158
+ success: S.Number
159
+ })
160
+ )
161
+
162
+ const counterImpl = CounterRpcs
163
+ .toLayer({
164
+ incrementA: Effect.fn(function*() {
165
+ const counter = yield* PerRequestCounter
166
+ counter.a++
167
+ const global = yield* GlobalCounter
168
+ yield* Ref.update(global.ref, (n) => n + 1)
169
+ return counter.a
170
+ }, Effect.provide(PerRequestCounter.Default)),
171
+ incrementB: Effect.fn(function*() {
172
+ const counter = yield* PerRequestCounter
173
+ counter.a++
174
+ const global = yield* GlobalCounter
175
+ yield* Ref.update(global.ref, (n) => n + 1)
176
+ return counter.a
177
+ }, Effect.provide(PerRequestCounter.Default))
178
+ })
179
+
180
+ const GlobalCounterLive = Layer.effect(
181
+ GlobalCounter,
182
+ Ref.make(0).pipe(Effect.map((ref) => ({ ref })))
183
+ )
184
+
185
+ const CounterTestLayer = counterImpl.pipe(Layer.provideMerge(GlobalCounterLive))
186
+
187
+ it.live(
188
+ "per-request service isolation with shared global counter",
189
+ Effect.fnUntraced(
190
+ function*() {
191
+ const client = yield* RpcTest.makeClient(CounterRpcs)
192
+ const global = yield* GlobalCounter
193
+
194
+ const r1 = yield* client.incrementA()
195
+ const r2 = yield* client.incrementB()
196
+
197
+ // per-request counter is fresh each time → both return 1
198
+ expect(r1).toBe(1)
199
+ expect(r2).toBe(1)
200
+
201
+ // global counter is shared across requests → accumulates to 2
202
+ const globalCount = yield* Ref.get(global.ref)
203
+ expect(globalCount).toBe(2)
204
+ },
205
+ Effect.provide(CounterTestLayer)
206
+ )
207
+ )