@effect-app/infra 4.0.0-beta.13 → 4.0.0-beta.131

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 (203) hide show
  1. package/CHANGELOG.md +864 -0
  2. package/dist/CUPS.d.ts +13 -5
  3. package/dist/CUPS.d.ts.map +1 -1
  4. package/dist/CUPS.js +10 -12
  5. package/dist/Emailer/service.d.ts +2 -2
  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/ext.d.ts +21 -3
  15. package/dist/Model/Repository/ext.d.ts.map +1 -1
  16. package/dist/Model/Repository/ext.js +54 -2
  17. package/dist/Model/Repository/internal/internal.d.ts +4 -4
  18. package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
  19. package/dist/Model/Repository/internal/internal.js +31 -21
  20. package/dist/Model/Repository/makeRepo.d.ts +6 -5
  21. package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
  22. package/dist/Model/Repository/makeRepo.js +4 -1
  23. package/dist/Model/Repository/service.d.ts +27 -22
  24. package/dist/Model/Repository/service.d.ts.map +1 -1
  25. package/dist/Model/Repository/validation.d.ts +59 -9
  26. package/dist/Model/Repository/validation.d.ts.map +1 -1
  27. package/dist/Model/Repository.d.ts +1 -0
  28. package/dist/Model/Repository.d.ts.map +1 -1
  29. package/dist/Model/Repository.js +2 -1
  30. package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -1
  31. package/dist/Model/query/new-kid-interpreter.js +3 -3
  32. package/dist/Model.d.ts +1 -0
  33. package/dist/Model.d.ts.map +1 -1
  34. package/dist/Model.js +2 -1
  35. package/dist/Operations.d.ts +4 -4
  36. package/dist/Operations.d.ts.map +1 -1
  37. package/dist/Operations.js +56 -59
  38. package/dist/OperationsRepo.d.ts +4 -4
  39. package/dist/OperationsRepo.d.ts.map +1 -1
  40. package/dist/OperationsRepo.js +3 -3
  41. package/dist/QueueMaker/SQLQueue.d.ts +3 -6
  42. package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
  43. package/dist/QueueMaker/SQLQueue.js +105 -114
  44. package/dist/QueueMaker/errors.d.ts +1 -1
  45. package/dist/QueueMaker/errors.d.ts.map +1 -1
  46. package/dist/QueueMaker/memQueue.d.ts +6 -3
  47. package/dist/QueueMaker/memQueue.d.ts.map +1 -1
  48. package/dist/QueueMaker/memQueue.js +51 -62
  49. package/dist/QueueMaker/sbqueue.d.ts +5 -2
  50. package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
  51. package/dist/QueueMaker/sbqueue.js +36 -52
  52. package/dist/RequestContext.d.ts +51 -21
  53. package/dist/RequestContext.d.ts.map +1 -1
  54. package/dist/RequestContext.js +5 -5
  55. package/dist/RequestFiberSet.d.ts +2 -2
  56. package/dist/RequestFiberSet.d.ts.map +1 -1
  57. package/dist/RequestFiberSet.js +5 -5
  58. package/dist/Store/ContextMapContainer.d.ts +18 -2
  59. package/dist/Store/ContextMapContainer.d.ts.map +1 -1
  60. package/dist/Store/ContextMapContainer.js +13 -3
  61. package/dist/Store/Cosmos.d.ts.map +1 -1
  62. package/dist/Store/Cosmos.js +308 -242
  63. package/dist/Store/Disk.d.ts +1 -1
  64. package/dist/Store/Disk.d.ts.map +1 -1
  65. package/dist/Store/Disk.js +25 -22
  66. package/dist/Store/Memory.d.ts +3 -3
  67. package/dist/Store/Memory.d.ts.map +1 -1
  68. package/dist/Store/Memory.js +27 -22
  69. package/dist/Store/SQL/Pg.d.ts +4 -0
  70. package/dist/Store/SQL/Pg.d.ts.map +1 -0
  71. package/dist/Store/SQL/Pg.js +189 -0
  72. package/dist/Store/SQL/query.d.ts +38 -0
  73. package/dist/Store/SQL/query.d.ts.map +1 -0
  74. package/dist/Store/SQL/query.js +367 -0
  75. package/dist/Store/SQL.d.ts +20 -0
  76. package/dist/Store/SQL.d.ts.map +1 -0
  77. package/dist/Store/SQL.js +381 -0
  78. package/dist/Store/index.d.ts +4 -1
  79. package/dist/Store/index.d.ts.map +1 -1
  80. package/dist/Store/index.js +15 -3
  81. package/dist/Store/service.d.ts +16 -5
  82. package/dist/Store/service.d.ts.map +1 -1
  83. package/dist/Store/service.js +24 -6
  84. package/dist/Store/utils.d.ts.map +1 -1
  85. package/dist/Store/utils.js +3 -4
  86. package/dist/adapters/ServiceBus.d.ts +6 -6
  87. package/dist/adapters/ServiceBus.d.ts.map +1 -1
  88. package/dist/adapters/ServiceBus.js +13 -15
  89. package/dist/adapters/cosmos-client.d.ts +2 -2
  90. package/dist/adapters/cosmos-client.d.ts.map +1 -1
  91. package/dist/adapters/cosmos-client.js +3 -3
  92. package/dist/adapters/logger.d.ts.map +1 -1
  93. package/dist/adapters/memQueue.d.ts +2 -2
  94. package/dist/adapters/memQueue.d.ts.map +1 -1
  95. package/dist/adapters/memQueue.js +3 -3
  96. package/dist/adapters/mongo-client.d.ts +2 -2
  97. package/dist/adapters/mongo-client.d.ts.map +1 -1
  98. package/dist/adapters/mongo-client.js +3 -3
  99. package/dist/adapters/redis-client.d.ts +2 -2
  100. package/dist/adapters/redis-client.d.ts.map +1 -1
  101. package/dist/adapters/redis-client.js +3 -3
  102. package/dist/api/ContextProvider.d.ts +6 -6
  103. package/dist/api/ContextProvider.d.ts.map +1 -1
  104. package/dist/api/ContextProvider.js +6 -6
  105. package/dist/api/internal/auth.d.ts +42 -4
  106. package/dist/api/internal/auth.d.ts.map +1 -1
  107. package/dist/api/internal/auth.js +160 -29
  108. package/dist/api/internal/events.d.ts +2 -2
  109. package/dist/api/internal/events.d.ts.map +1 -1
  110. package/dist/api/internal/events.js +11 -7
  111. package/dist/api/layerUtils.d.ts +5 -5
  112. package/dist/api/layerUtils.d.ts.map +1 -1
  113. package/dist/api/layerUtils.js +5 -5
  114. package/dist/api/routing/middleware/RouterMiddleware.d.ts +3 -3
  115. package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -1
  116. package/dist/api/routing/middleware/middleware.d.ts +37 -1
  117. package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
  118. package/dist/api/routing/middleware/middleware.js +45 -14
  119. package/dist/api/routing.d.ts +4 -6
  120. package/dist/api/routing.d.ts.map +1 -1
  121. package/dist/api/routing.js +13 -6
  122. package/dist/api/setupRequest.d.ts +6 -3
  123. package/dist/api/setupRequest.d.ts.map +1 -1
  124. package/dist/api/setupRequest.js +11 -6
  125. package/dist/errorReporter.d.ts +3 -3
  126. package/dist/errorReporter.d.ts.map +1 -1
  127. package/dist/errorReporter.js +16 -23
  128. package/dist/logger.d.ts.map +1 -1
  129. package/dist/rateLimit.d.ts +8 -2
  130. package/dist/rateLimit.d.ts.map +1 -1
  131. package/dist/rateLimit.js +5 -11
  132. package/examples/query.ts +30 -26
  133. package/package.json +36 -22
  134. package/src/CUPS.ts +9 -11
  135. package/src/Emailer/service.ts +2 -2
  136. package/src/MainFiberSet.ts +2 -2
  137. package/src/Model/Repository/Registry.ts +33 -0
  138. package/src/Model/Repository/ext.ts +93 -6
  139. package/src/Model/Repository/internal/internal.ts +84 -76
  140. package/src/Model/Repository/makeRepo.ts +11 -8
  141. package/src/Model/Repository/service.ts +31 -22
  142. package/src/Model/Repository.ts +1 -0
  143. package/src/Model/query/new-kid-interpreter.ts +2 -2
  144. package/src/Model.ts +1 -0
  145. package/src/Operations.ts +78 -113
  146. package/src/OperationsRepo.ts +2 -2
  147. package/src/QueueMaker/SQLQueue.ts +121 -151
  148. package/src/QueueMaker/memQueue.ts +82 -103
  149. package/src/QueueMaker/sbqueue.ts +55 -85
  150. package/src/RequestContext.ts +4 -4
  151. package/src/RequestFiberSet.ts +4 -4
  152. package/src/Store/ContextMapContainer.ts +41 -2
  153. package/src/Store/Cosmos.ts +437 -343
  154. package/src/Store/Disk.ts +52 -49
  155. package/src/Store/Memory.ts +54 -48
  156. package/src/Store/SQL/Pg.ts +318 -0
  157. package/src/Store/SQL/query.ts +409 -0
  158. package/src/Store/SQL.ts +668 -0
  159. package/src/Store/index.ts +17 -2
  160. package/src/Store/service.ts +31 -7
  161. package/src/Store/utils.ts +23 -22
  162. package/src/adapters/ServiceBus.ts +111 -115
  163. package/src/adapters/cosmos-client.ts +2 -2
  164. package/src/adapters/memQueue.ts +2 -2
  165. package/src/adapters/mongo-client.ts +2 -2
  166. package/src/adapters/redis-client.ts +2 -2
  167. package/src/api/ContextProvider.ts +11 -11
  168. package/src/api/internal/auth.ts +246 -44
  169. package/src/api/internal/events.ts +14 -9
  170. package/src/api/layerUtils.ts +8 -8
  171. package/src/api/routing/middleware/RouterMiddleware.ts +4 -4
  172. package/src/api/routing/middleware/middleware.ts +52 -12
  173. package/src/api/routing.ts +17 -7
  174. package/src/api/setupRequest.ts +27 -7
  175. package/src/errorReporter.ts +58 -72
  176. package/src/rateLimit.ts +30 -22
  177. package/test/auth.test.ts +101 -0
  178. package/test/contextProvider.test.ts +11 -11
  179. package/test/controller.test.ts +18 -13
  180. package/test/dist/auth.test.d.ts.map +1 -0
  181. package/test/dist/contextProvider.test.d.ts.map +1 -1
  182. package/test/dist/controller.test.d.ts.map +1 -1
  183. package/test/dist/date-query.test.d.ts.map +1 -0
  184. package/test/dist/fixtures.d.ts +19 -9
  185. package/test/dist/fixtures.d.ts.map +1 -1
  186. package/test/dist/fixtures.js +11 -9
  187. package/test/dist/query.test.d.ts.map +1 -1
  188. package/test/dist/rawQuery.test.d.ts.map +1 -1
  189. package/test/dist/repository-ext.test.d.ts.map +1 -0
  190. package/test/dist/requires.test.d.ts.map +1 -1
  191. package/test/dist/routing-interruptibility.test.d.ts.map +1 -0
  192. package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
  193. package/test/dist/sql-store.test.d.ts.map +1 -0
  194. package/test/fixtures.ts +10 -8
  195. package/test/query.test.ts +209 -31
  196. package/test/rawQuery.test.ts +23 -19
  197. package/test/repository-ext.test.ts +58 -0
  198. package/test/requires.test.ts +6 -5
  199. package/test/routing-interruptibility.test.ts +63 -0
  200. package/test/rpc-multi-middleware.test.ts +72 -3
  201. package/test/sql-store.test.ts +1064 -0
  202. package/test/validateSample.test.ts +12 -9
  203. package/tsconfig.json +0 -1
@@ -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
  {
@@ -405,7 +409,7 @@ describe("removeByIds", () => {
405
409
 
406
410
  yield* repo.saveAndPublish(items)
407
411
  const itemsAfterSave = yield* repo.all
408
- yield* repo.removeById(...items.slice(0, 2).map((_) => _.id))
412
+ yield* repo.removeById([items[0]!.id, items[1]!.id])
409
413
 
410
414
  const items2 = yield* repo.all
411
415
 
@@ -0,0 +1,58 @@
1
+ import { describe, expect, it } from "@effect/vitest"
2
+ import { Effect, Layer, S } from "effect-app"
3
+ import { setupRequestContextFromCurrent } from "../src/api/setupRequest.js"
4
+ import { makeRepo } from "../src/Model/Repository.js"
5
+ import { RepositoryRegistryLive } from "../src/Model/Repository/Registry.js"
6
+ import { MemoryStoreLive } from "../src/Store/Memory.js"
7
+
8
+ class BatchItem extends S.Class<BatchItem>("BatchItem")({
9
+ id: S.String,
10
+ label: S.String
11
+ }) {}
12
+
13
+ const TestStoreLive = Layer.merge(MemoryStoreLive, RepositoryRegistryLive)
14
+
15
+ describe("repository ext save/remove batching", () => {
16
+ it.effect("supports save batching overload", () =>
17
+ Effect.gen(function*() {
18
+ const repo = yield* makeRepo("BatchItem", BatchItem, {})
19
+ const items = [
20
+ new BatchItem({ id: "1", label: "one" }),
21
+ new BatchItem({ id: "2", label: "two" }),
22
+ new BatchItem({ id: "3", label: "three" }),
23
+ new BatchItem({ id: "4", label: "four" })
24
+ ] as const
25
+
26
+ yield* repo.save(items, { batch: 2 })
27
+
28
+ const all = yield* repo.all
29
+ expect(all).toHaveLength(4)
30
+ expect(all.map((_) => _.id).toSorted()).toEqual(["1", "2", "3", "4"])
31
+ }).pipe(
32
+ setupRequestContextFromCurrent(),
33
+ Effect.provide(TestStoreLive)
34
+ )
35
+ )
36
+
37
+ it.effect("supports remove batching overload", () =>
38
+ Effect.gen(function*() {
39
+ const repo = yield* makeRepo("BatchItem", BatchItem, {})
40
+ const items = [
41
+ new BatchItem({ id: "1", label: "one" }),
42
+ new BatchItem({ id: "2", label: "two" }),
43
+ new BatchItem({ id: "3", label: "three" }),
44
+ new BatchItem({ id: "4", label: "four" })
45
+ ] as const
46
+
47
+ yield* repo.save(items)
48
+ yield* repo.remove([items[0], items[1], items[2]], { batch: true })
49
+
50
+ const all = yield* repo.all
51
+ expect(all).toHaveLength(1)
52
+ expect(all[0]?.id).toBe("4")
53
+ }).pipe(
54
+ setupRequestContextFromCurrent(),
55
+ Effect.provide(TestStoreLive)
56
+ )
57
+ )
58
+ })
@@ -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
  )
@@ -0,0 +1,63 @@
1
+ import { describe, expect, it } from "@effect/vitest"
2
+ import { Effect, Fiber, Layer, Ref } from "effect"
3
+ import { S } from "effect-app"
4
+ import { ConfigureInterruptibilityMiddleware } from "effect-app/middleware"
5
+ import { Rpc, RpcGroup, RpcTest } from "effect/unstable/rpc"
6
+ import { applyRequestTypeInterruptibility } from "../src/api/routing.js"
7
+ import { ConfigureInterruptibilityMiddlewareLive, RequestType } from "../src/api/routing/middleware.js"
8
+
9
+ const InterruptibilityRpcs = RpcGroup.make(
10
+ Rpc
11
+ .make("doCommand", { success: S.Void })
12
+ .annotate(RequestType, "command")
13
+ .middleware(ConfigureInterruptibilityMiddleware),
14
+ Rpc
15
+ .make("doQuery", { success: S.Void })
16
+ .annotate(RequestType, "query")
17
+ .middleware(ConfigureInterruptibilityMiddleware)
18
+ )
19
+
20
+ const makeImplLayer = (commandDone: Ref.Ref<boolean>, queryDone: Ref.Ref<boolean>) =>
21
+ InterruptibilityRpcs.toLayer({
22
+ doCommand: () =>
23
+ applyRequestTypeInterruptibility(
24
+ "command",
25
+ Effect.sleep("120 millis").pipe(Effect.andThen(Ref.set(commandDone, true)))
26
+ ),
27
+ doQuery: () =>
28
+ applyRequestTypeInterruptibility(
29
+ "query",
30
+ Effect.sleep("120 millis").pipe(Effect.andThen(Ref.set(queryDone, true)))
31
+ )
32
+ })
33
+
34
+ describe("routing interruptibility", () => {
35
+ it.live(
36
+ "e2e: command continues after client interrupt, query does not",
37
+ () =>
38
+ Effect.gen(function*() {
39
+ const commandDone = yield* Ref.make(false)
40
+ const queryDone = yield* Ref.make(false)
41
+
42
+ const client = yield* RpcTest
43
+ .makeClient(InterruptibilityRpcs)
44
+ .pipe(
45
+ Effect.provide(
46
+ Layer.mergeAll(makeImplLayer(commandDone, queryDone), ConfigureInterruptibilityMiddlewareLive)
47
+ )
48
+ )
49
+
50
+ const commandFiber = yield* Effect.forkDetach(client.doCommand())
51
+ yield* Effect.sleep("20 millis")
52
+ yield* Fiber.interrupt(commandFiber)
53
+ yield* Effect.sleep("180 millis")
54
+ expect(yield* Ref.get(commandDone)).toBe(true)
55
+
56
+ const queryFiber = yield* Effect.forkDetach(client.doQuery())
57
+ yield* Effect.sleep("20 millis")
58
+ yield* Fiber.interrupt(queryFiber)
59
+ yield* Effect.sleep("180 millis")
60
+ expect(yield* Ref.get(queryDone)).toBe(false)
61
+ })
62
+ )
63
+ })
@@ -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
+ )