@effect-app/infra 4.0.0-beta.7 → 4.0.0-beta.70

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 (142) hide show
  1. package/CHANGELOG.md +447 -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/query/dsl.d.ts +9 -9
  19. package/dist/Operations.d.ts +2 -2
  20. package/dist/Operations.d.ts.map +1 -1
  21. package/dist/Operations.js +3 -3
  22. package/dist/OperationsRepo.d.ts +2 -2
  23. package/dist/OperationsRepo.d.ts.map +1 -1
  24. package/dist/OperationsRepo.js +3 -3
  25. package/dist/QueueMaker/SQLQueue.d.ts +3 -5
  26. package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
  27. package/dist/QueueMaker/SQLQueue.js +9 -7
  28. package/dist/QueueMaker/memQueue.d.ts.map +1 -1
  29. package/dist/QueueMaker/memQueue.js +10 -9
  30. package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
  31. package/dist/QueueMaker/sbqueue.js +11 -9
  32. package/dist/RequestContext.d.ts +5 -5
  33. package/dist/RequestContext.d.ts.map +1 -1
  34. package/dist/RequestContext.js +4 -4
  35. package/dist/RequestFiberSet.d.ts +2 -2
  36. package/dist/RequestFiberSet.d.ts.map +1 -1
  37. package/dist/RequestFiberSet.js +5 -5
  38. package/dist/Store/ContextMapContainer.d.ts +3 -3
  39. package/dist/Store/ContextMapContainer.d.ts.map +1 -1
  40. package/dist/Store/ContextMapContainer.js +3 -3
  41. package/dist/Store/Cosmos.js +1 -1
  42. package/dist/Store/Disk.d.ts.map +1 -1
  43. package/dist/Store/Disk.js +3 -4
  44. package/dist/Store/Memory.d.ts +2 -2
  45. package/dist/Store/Memory.d.ts.map +1 -1
  46. package/dist/Store/Memory.js +4 -4
  47. package/dist/Store/service.d.ts +3 -3
  48. package/dist/Store/service.d.ts.map +1 -1
  49. package/dist/Store/service.js +4 -4
  50. package/dist/adapters/SQL/Model.d.ts +2 -5
  51. package/dist/adapters/SQL/Model.d.ts.map +1 -1
  52. package/dist/adapters/SQL/Model.js +21 -13
  53. package/dist/adapters/ServiceBus.d.ts +6 -6
  54. package/dist/adapters/ServiceBus.d.ts.map +1 -1
  55. package/dist/adapters/ServiceBus.js +9 -9
  56. package/dist/adapters/cosmos-client.d.ts +2 -2
  57. package/dist/adapters/cosmos-client.d.ts.map +1 -1
  58. package/dist/adapters/cosmos-client.js +3 -3
  59. package/dist/adapters/logger.d.ts.map +1 -1
  60. package/dist/adapters/memQueue.d.ts +2 -2
  61. package/dist/adapters/memQueue.d.ts.map +1 -1
  62. package/dist/adapters/memQueue.js +3 -3
  63. package/dist/adapters/mongo-client.d.ts +2 -2
  64. package/dist/adapters/mongo-client.d.ts.map +1 -1
  65. package/dist/adapters/mongo-client.js +3 -3
  66. package/dist/adapters/redis-client.d.ts +3 -3
  67. package/dist/adapters/redis-client.d.ts.map +1 -1
  68. package/dist/adapters/redis-client.js +3 -3
  69. package/dist/api/ContextProvider.d.ts +6 -6
  70. package/dist/api/ContextProvider.d.ts.map +1 -1
  71. package/dist/api/ContextProvider.js +6 -6
  72. package/dist/api/internal/auth.d.ts +1 -1
  73. package/dist/api/internal/events.d.ts.map +1 -1
  74. package/dist/api/internal/events.js +7 -5
  75. package/dist/api/layerUtils.d.ts +5 -5
  76. package/dist/api/layerUtils.d.ts.map +1 -1
  77. package/dist/api/layerUtils.js +5 -5
  78. package/dist/api/routing/middleware/RouterMiddleware.d.ts +3 -3
  79. package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -1
  80. package/dist/api/routing/schema/jwt.d.ts +1 -1
  81. package/dist/api/routing/schema/jwt.d.ts.map +1 -1
  82. package/dist/api/routing/schema/jwt.js +1 -1
  83. package/dist/api/routing.d.ts.map +1 -1
  84. package/dist/api/routing.js +1 -1
  85. package/dist/errorReporter.d.ts +1 -1
  86. package/dist/errorReporter.d.ts.map +1 -1
  87. package/dist/errorReporter.js +1 -1
  88. package/dist/fileUtil.js +1 -1
  89. package/dist/logger.d.ts.map +1 -1
  90. package/dist/rateLimit.js +1 -1
  91. package/examples/query.ts +28 -24
  92. package/package.json +16 -16
  93. package/src/CUPS.ts +2 -2
  94. package/src/Emailer/Sendgrid.ts +1 -1
  95. package/src/Emailer/service.ts +2 -2
  96. package/src/MainFiberSet.ts +2 -2
  97. package/src/Model/Repository/internal/internal.ts +11 -8
  98. package/src/Model/Repository/makeRepo.ts +2 -2
  99. package/src/Operations.ts +2 -2
  100. package/src/OperationsRepo.ts +2 -2
  101. package/src/QueueMaker/SQLQueue.ts +10 -10
  102. package/src/QueueMaker/memQueue.ts +41 -42
  103. package/src/QueueMaker/sbqueue.ts +65 -62
  104. package/src/RequestContext.ts +3 -3
  105. package/src/RequestFiberSet.ts +4 -4
  106. package/src/Store/ContextMapContainer.ts +2 -2
  107. package/src/Store/Cosmos.ts +10 -10
  108. package/src/Store/Disk.ts +2 -3
  109. package/src/Store/Memory.ts +4 -6
  110. package/src/Store/service.ts +3 -3
  111. package/src/adapters/SQL/Model.ts +76 -71
  112. package/src/adapters/ServiceBus.ts +8 -8
  113. package/src/adapters/cosmos-client.ts +2 -2
  114. package/src/adapters/memQueue.ts +2 -2
  115. package/src/adapters/mongo-client.ts +2 -2
  116. package/src/adapters/redis-client.ts +2 -2
  117. package/src/api/ContextProvider.ts +11 -11
  118. package/src/api/internal/events.ts +5 -4
  119. package/src/api/layerUtils.ts +8 -8
  120. package/src/api/routing/middleware/RouterMiddleware.ts +4 -4
  121. package/src/api/routing/schema/jwt.ts +2 -3
  122. package/src/api/routing.ts +4 -3
  123. package/src/errorReporter.ts +1 -1
  124. package/src/fileUtil.ts +1 -1
  125. package/src/rateLimit.ts +2 -2
  126. package/test/contextProvider.test.ts +5 -5
  127. package/test/controller.test.ts +9 -7
  128. package/test/dist/contextProvider.test.d.ts.map +1 -1
  129. package/test/dist/controller.test.d.ts.map +1 -1
  130. package/test/dist/fixtures.d.ts +16 -7
  131. package/test/dist/fixtures.d.ts.map +1 -1
  132. package/test/dist/fixtures.js +11 -9
  133. package/test/dist/query.test.d.ts.map +1 -1
  134. package/test/dist/rawQuery.test.d.ts.map +1 -1
  135. package/test/dist/requires.test.d.ts.map +1 -1
  136. package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
  137. package/test/fixtures.ts +10 -8
  138. package/test/query.test.ts +156 -10
  139. package/test/rawQuery.test.ts +19 -17
  140. package/test/requires.test.ts +6 -5
  141. package/test/rpc-multi-middleware.test.ts +73 -4
  142. package/tsconfig.json +0 -1
@@ -1,7 +1,8 @@
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"
@@ -11,7 +12,7 @@ import { memFilter, MemoryStoreLive } from "../src/Store/Memory.js"
11
12
  import { SomeService } from "./fixtures.js"
12
13
 
13
14
  const str = S.Struct({ _tag: S.Literal("string"), value: S.String })
14
- const num = S.Struct({ _tag: S.Literal("number"), value: S.Number })
15
+ const num = S.Struct({ _tag: S.Literal("number"), value: S.Finite })
15
16
  const someUnion = S.Union([str, num])
16
17
 
17
18
  export class Something extends S.Class<Something>("Something")({
@@ -99,7 +100,7 @@ it("works", () => {
99
100
  })
100
101
 
101
102
  // @effect-diagnostics-next-line missingEffectServiceDependency:off
102
- class SomethingRepo extends ServiceMap.Service<SomethingRepo>()("SomethingRepo", {
103
+ class SomethingRepo extends Context.Service<SomethingRepo>()("SomethingRepo", {
103
104
  make: Effect.gen(function*() {
104
105
  return yield* makeRepo("Something", Something, {})
105
106
  })
@@ -156,8 +157,6 @@ it("works with repo", () =>
156
157
 
157
158
  expectTypeOf(smtArr).toEqualTypeOf<readonly Something[]>()
158
159
 
159
- console.log(" $$$$$$")
160
- console.log(Struct.pick(["id", "displayName"]))
161
160
  expect(q1).toEqual(items.slice(0, 2).toReversed().map(Struct.pick(["id", "displayName"])))
162
161
  expect(q2).toEqual(items.slice(0, 2).toReversed().map(Struct.pick(["displayName"])))
163
162
  })
@@ -521,8 +520,8 @@ it(
521
520
  const schema = S.Struct({
522
521
  id: S.String,
523
522
  createdAt: S.Date.pipe(
524
- S.withDecodingDefault(() => new Date().toISOString()),
525
- S.withConstructorDefault(() => Option.some(new Date()))
523
+ S.withDecodingDefault(Effect.sync(() => new Date().toISOString())),
524
+ S.withConstructorDefault(Effect.sync(() => new Date()))
526
525
  )
527
526
  })
528
527
  const repo = yield* makeRepo(
@@ -534,8 +533,8 @@ it(
534
533
  const outputSchema = S.Struct({
535
534
  id: S.Literal("123"),
536
535
  createdAt: S.Date.pipe(
537
- S.withDecodingDefault(() => new Date().toISOString()),
538
- S.withConstructorDefault(() => Option.some(new Date()))
536
+ S.withDecodingDefault(Effect.sync(() => new Date().toISOString())),
537
+ S.withConstructorDefault(Effect.sync(() => new Date()))
539
538
  )
540
539
  })
541
540
 
@@ -674,7 +673,7 @@ it("remove null from one constituent of a tagged union", () =>
674
673
 
675
674
  class BB extends S.Class<BB>("BB")({
676
675
  id: S.Literal("BB"),
677
- b: S.NullOr(S.Number)
676
+ b: S.NullOr(S.Finite)
678
677
  }) {}
679
678
 
680
679
  type Union = AA | BB
@@ -984,6 +983,153 @@ it("refine nested union", () =>
984
983
  })
985
984
  .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
986
985
 
986
+ it("find with transformed id", () =>
987
+ Effect
988
+ .gen(function*() {
989
+ const ConfiguratorId = S.NonEmptyString255
990
+
991
+ class PreconfigurationId extends S.Class<PreconfigurationId>("PreconfigurationId")({
992
+ configuratorId: ConfiguratorId,
993
+ label: S.NonEmptyString50
994
+ }) {}
995
+
996
+ const PreconfigurationIdFromString = S.NonEmptyString255.pipe(
997
+ S.decodeTo(
998
+ S.toType(PreconfigurationId),
999
+ SchemaTransformation.transformOrFail({
1000
+ decode: Effect.fnUntraced(function*(value) {
1001
+ const values = value.split("_")
1002
+ const label = yield* S.SchemaParser.decodeUnknownEffect(S.NonEmptyString50)(values.pop())
1003
+ const configuratorId = yield* S.SchemaParser.decodeUnknownEffect(ConfiguratorId)(values.join("_"))
1004
+ return new PreconfigurationId({ configuratorId, label })
1005
+ }),
1006
+ encode: (id) => Effect.succeed(S.NonEmptyString255(`${id.configuratorId}_${id.label}`))
1007
+ })
1008
+ ),
1009
+ S.revealCodec
1010
+ )
1011
+
1012
+ const Preconfiguration = S.Struct({
1013
+ id: PreconfigurationIdFromString,
1014
+ name: S.String
1015
+ })
1016
+
1017
+ const repo = yield* makeRepo("Preconfiguration", Preconfiguration, { idKey: "id" as const })
1018
+
1019
+ const id = new PreconfigurationId({
1020
+ configuratorId: S.NonEmptyString255("myConfigurator"),
1021
+ label: S.NonEmptyString50("myLabel")
1022
+ })
1023
+ const item = { id, name: "test preconfig" }
1024
+
1025
+ yield* repo.saveAndPublish([item])
1026
+
1027
+ const found = yield* repo.find(id)
1028
+ expect(Option.isSome(found)).toBe(true)
1029
+ expect(Option.getOrThrow(found).name).toBe("test preconfig")
1030
+ expect(Option.getOrThrow(found).id).toEqual(id)
1031
+
1032
+ const notFound = yield* repo.find(
1033
+ new PreconfigurationId({
1034
+ configuratorId: S.NonEmptyString255("other"),
1035
+ label: S.NonEmptyString50("nope")
1036
+ })
1037
+ )
1038
+ expect(Option.isNone(notFound)).toBe(true)
1039
+ })
1040
+ .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1041
+
1042
+ it("find with transformed id in tagged union", () =>
1043
+ Effect
1044
+ .gen(function*() {
1045
+ const ConfiguratorId = S.NonEmptyString255
1046
+
1047
+ class PreconfigurationId extends S.Class<PreconfigurationId>("PreconfigurationId")({
1048
+ configuratorId: ConfiguratorId,
1049
+ label: S.NonEmptyString50
1050
+ }) {}
1051
+
1052
+ const PreconfigurationIdFromString = S.NonEmptyString255.pipe(
1053
+ S.decodeTo(
1054
+ S.toType(PreconfigurationId),
1055
+ SchemaTransformation.transformOrFail({
1056
+ decode: Effect.fnUntraced(function*(value) {
1057
+ const values = value.split("_")
1058
+ const label = yield* S.SchemaParser.decodeUnknownEffect(S.NonEmptyString50)(values.pop())
1059
+ const configuratorId = yield* S.SchemaParser.decodeUnknownEffect(ConfiguratorId)(values.join("_"))
1060
+ return new PreconfigurationId({ configuratorId, label })
1061
+ }),
1062
+ encode: (id) => Effect.succeed(S.NonEmptyString255(`${id.configuratorId}_${id.label}`))
1063
+ })
1064
+ ),
1065
+ S.revealCodec
1066
+ )
1067
+
1068
+ class Draft extends S.TaggedClass<Draft>()("Draft", {
1069
+ id: PreconfigurationIdFromString,
1070
+ name: S.String
1071
+ }) {}
1072
+
1073
+ class Published extends S.TaggedClass<Published>()("Published", {
1074
+ id: PreconfigurationIdFromString,
1075
+ name: S.String,
1076
+ publishedAt: S.String
1077
+ }) {}
1078
+
1079
+ class Archived extends S.TaggedClass<Archived>()("Archived", {
1080
+ id: PreconfigurationIdFromString,
1081
+ name: S.String,
1082
+ archivedAt: S.String
1083
+ }) {}
1084
+
1085
+ const Preconfiguration = S.Union([Draft, Published, Archived])
1086
+
1087
+ const repo = yield* makeRepo("Preconfiguration", Preconfiguration, {})
1088
+
1089
+ const id1 = new PreconfigurationId({
1090
+ configuratorId: S.NonEmptyString255("conf1"),
1091
+ label: S.NonEmptyString50("draft1")
1092
+ })
1093
+ const id2 = new PreconfigurationId({
1094
+ configuratorId: S.NonEmptyString255("conf2"),
1095
+ label: S.NonEmptyString50("pub1")
1096
+ })
1097
+ const id3 = new PreconfigurationId({
1098
+ configuratorId: S.NonEmptyString255("conf3"),
1099
+ label: S.NonEmptyString50("arch1")
1100
+ })
1101
+
1102
+ const draft = new Draft({ id: id1, name: "my draft" })
1103
+ const published = new Published({ id: id2, name: "my published", publishedAt: "2024-01-01" })
1104
+ const archived = new Archived({ id: id3, name: "my archived", archivedAt: "2024-06-01" })
1105
+
1106
+ yield* repo.saveAndPublish([draft, published, archived])
1107
+
1108
+ // find each by their PreconfigurationId instance
1109
+ const foundDraft = yield* repo.find(id1)
1110
+ expect(Option.isSome(foundDraft)).toBe(true)
1111
+ expect(Option.getOrThrow(foundDraft)._tag).toBe("Draft")
1112
+ expect(Option.getOrThrow(foundDraft).name).toBe("my draft")
1113
+
1114
+ const foundPublished = yield* repo.find(id2)
1115
+ expect(Option.isSome(foundPublished)).toBe(true)
1116
+ expect(Option.getOrThrow(foundPublished)._tag).toBe("Published")
1117
+
1118
+ const foundArchived = yield* repo.find(id3)
1119
+ expect(Option.isSome(foundArchived)).toBe(true)
1120
+ expect(Option.getOrThrow(foundArchived)._tag).toBe("Archived")
1121
+
1122
+ // not found
1123
+ const notFound = yield* repo.find(
1124
+ new PreconfigurationId({
1125
+ configuratorId: S.NonEmptyString255("nope"),
1126
+ label: S.NonEmptyString50("nope")
1127
+ })
1128
+ )
1129
+ expect(Option.isNone(notFound)).toBe(true)
1130
+ })
1131
+ .pipe(Effect.provide(MemoryStoreLive), setupRequestContextFromCurrent(), Effect.runPromise))
1132
+
987
1133
  it("refine union with nested union", () =>
988
1134
  Effect
989
1135
  .gen(function*() {
@@ -1,5 +1,5 @@
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, Effect, flow, Layer, ManagedRuntime, Redacted, References, Result, S, Context } 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"
@@ -24,7 +24,7 @@ class Something extends S.Class<Something>("Something")({
24
24
  id: S.String,
25
25
  name: S.String,
26
26
  description: S.String,
27
- items: S.Array(S.Struct({ id: S.String, value: S.Number, description: S.String }))
27
+ items: S.Array(S.Struct({ id: S.String, value: S.Finite, description: S.String }))
28
28
  }) {}
29
29
 
30
30
  const items = [
@@ -49,7 +49,7 @@ const items = [
49
49
  ]
50
50
 
51
51
  // @effect-diagnostics-next-line missingEffectServiceDependency:off
52
- class SomethingRepo extends ServiceMap.Service<SomethingRepo>()(
52
+ class SomethingRepo extends Context.Service<SomethingRepo>()(
53
53
  "SomethingRepo",
54
54
  {
55
55
  make: Effect.gen(function*() {
@@ -83,21 +83,23 @@ class SomethingRepo extends ServiceMap.Service<SomethingRepo>()(
83
83
  .layer
84
84
  .pipe(
85
85
  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=="
86
+ Effect
87
+ .gen(function*() {
88
+ const url = yield* Config.redacted("STORAGE_URL").pipe(
89
+ Config.withDefault(
90
+ Redacted.make(
91
+ // the emulator doesn't implement array projections :/ so you need an actual cloud instance!
92
+ "AccountEndpoint=http://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="
93
+ )
92
94
  )
93
95
  )
94
- )
95
- return CosmosStoreLayer({
96
- dbName: "test",
97
- prefix: "",
98
- url
96
+ return CosmosStoreLayer({
97
+ dbName: "test",
98
+ prefix: "",
99
+ url
100
+ })
99
101
  })
100
- }).pipe(Layer.unwrap)
102
+ .pipe(Layer.unwrap)
101
103
  )
102
104
  )
103
105
  }
@@ -107,7 +109,7 @@ describe("select first-level array fields", () => {
107
109
  .gen(function*() {
108
110
  const repo = yield* SomethingRepo
109
111
 
110
- const projected = S.Struct({ name: S.String, items: S.Array(S.Struct({ id: S.String, value: S.Number })) })
112
+ const projected = S.Struct({ name: S.String, items: S.Array(S.Struct({ id: S.String, value: S.Finite })) })
111
113
 
112
114
  // 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
115
  const items = yield* repo.queryRaw(projected, {
@@ -159,7 +161,7 @@ describe("select first-level array fields", () => {
159
161
  .pipe(Effect.provide(SomethingRepo.Test), rt.runPromise))
160
162
  })
161
163
 
162
- const projected = S.Struct({ name: S.String, items: S.Array(S.Struct({ id: S.String, value: S.Number })) })
164
+ const projected = S.Struct({ name: S.String, items: S.Array(S.Struct({ id: S.String, value: S.Finite })) })
163
165
 
164
166
  const expected = [
165
167
  {
@@ -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"
@@ -109,7 +109,7 @@ export const RpcRealLayer = Layer
109
109
  Layer.provide(FetchHttpClient.layer)
110
110
  )
111
111
  )
112
- .pipe(Layer.provide(RpcSerialization.layerJson))
112
+ .pipe(Layer.provide(RpcSerialization.layerNdjson))
113
113
 
114
114
  it.live(
115
115
  "require login",
@@ -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
+ )
package/tsconfig.json CHANGED
@@ -32,7 +32,6 @@
32
32
  "outDir": "build/dist",
33
33
  "resolveJsonModule": true,
34
34
  "moduleResolution": "Node16",
35
- "downlevelIteration": true,
36
35
  "noErrorTruncation": true,
37
36
  "forceConsistentCasingInFileNames": true,
38
37
  "types": [