@effect-app/infra 4.0.0-beta.17 → 4.0.0-beta.171

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 (295) hide show
  1. package/CHANGELOG.md +1123 -0
  2. package/dist/CUPS.d.ts +15 -7
  3. package/dist/CUPS.d.ts.map +1 -1
  4. package/dist/CUPS.js +10 -12
  5. package/dist/Emailer/Sendgrid.d.ts +14 -14
  6. package/dist/Emailer/Sendgrid.d.ts.map +1 -1
  7. package/dist/Emailer/Sendgrid.js +16 -15
  8. package/dist/Emailer/fake.d.ts +1 -1
  9. package/dist/Emailer/service.d.ts +9 -3
  10. package/dist/Emailer/service.d.ts.map +1 -1
  11. package/dist/Emailer/service.js +3 -3
  12. package/dist/Emailer.d.ts +1 -1
  13. package/dist/MainFiberSet.d.ts +5 -5
  14. package/dist/MainFiberSet.d.ts.map +1 -1
  15. package/dist/MainFiberSet.js +3 -3
  16. package/dist/Model/Repository/Registry.d.ts +20 -0
  17. package/dist/Model/Repository/Registry.d.ts.map +1 -0
  18. package/dist/Model/Repository/Registry.js +17 -0
  19. package/dist/Model/Repository/ext.d.ts +33 -15
  20. package/dist/Model/Repository/ext.d.ts.map +1 -1
  21. package/dist/Model/Repository/ext.js +54 -2
  22. package/dist/Model/Repository/internal/internal.d.ts +6 -6
  23. package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
  24. package/dist/Model/Repository/internal/internal.js +43 -32
  25. package/dist/Model/Repository/legacy.d.ts +1 -1
  26. package/dist/Model/Repository/makeRepo.d.ts +7 -6
  27. package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
  28. package/dist/Model/Repository/makeRepo.js +5 -1
  29. package/dist/Model/Repository/service.d.ts +28 -23
  30. package/dist/Model/Repository/service.d.ts.map +1 -1
  31. package/dist/Model/Repository/validation.d.ts +142 -17
  32. package/dist/Model/Repository/validation.d.ts.map +1 -1
  33. package/dist/Model/Repository/validation.js +5 -5
  34. package/dist/Model/Repository.d.ts +2 -1
  35. package/dist/Model/Repository.d.ts.map +1 -1
  36. package/dist/Model/Repository.js +2 -1
  37. package/dist/Model/dsl.d.ts +4 -4
  38. package/dist/Model/dsl.d.ts.map +1 -1
  39. package/dist/Model/filter/filterApi.d.ts +5 -5
  40. package/dist/Model/filter/filterApi.d.ts.map +1 -1
  41. package/dist/Model/filter/types/errors.d.ts +1 -1
  42. package/dist/Model/filter/types/fields.d.ts +1 -1
  43. package/dist/Model/filter/types/path/common.d.ts +1 -1
  44. package/dist/Model/filter/types/path/eager.d.ts +1 -1
  45. package/dist/Model/filter/types/path/eager.d.ts.map +1 -1
  46. package/dist/Model/filter/types/path/index.d.ts +1 -1
  47. package/dist/Model/filter/types/utils.d.ts +1 -1
  48. package/dist/Model/filter/types/validator.d.ts +1 -1
  49. package/dist/Model/filter/types.d.ts +1 -1
  50. package/dist/Model/query/dsl.d.ts +1 -1
  51. package/dist/Model/query/dsl.d.ts.map +1 -1
  52. package/dist/Model/query/new-kid-interpreter.d.ts +6 -6
  53. package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -1
  54. package/dist/Model/query/new-kid-interpreter.js +3 -3
  55. package/dist/Model/query.d.ts +1 -1
  56. package/dist/Model.d.ts +2 -1
  57. package/dist/Model.d.ts.map +1 -1
  58. package/dist/Model.js +2 -1
  59. package/dist/Operations.d.ts +6 -6
  60. package/dist/Operations.d.ts.map +1 -1
  61. package/dist/Operations.js +56 -59
  62. package/dist/OperationsRepo.d.ts +11 -29
  63. package/dist/OperationsRepo.d.ts.map +1 -1
  64. package/dist/OperationsRepo.js +3 -3
  65. package/dist/QueueMaker/SQLQueue.d.ts +5 -7
  66. package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
  67. package/dist/QueueMaker/SQLQueue.js +105 -114
  68. package/dist/QueueMaker/errors.d.ts +2 -2
  69. package/dist/QueueMaker/errors.d.ts.map +1 -1
  70. package/dist/QueueMaker/memQueue.d.ts +7 -4
  71. package/dist/QueueMaker/memQueue.d.ts.map +1 -1
  72. package/dist/QueueMaker/memQueue.js +51 -62
  73. package/dist/QueueMaker/sbqueue.d.ts +6 -3
  74. package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
  75. package/dist/QueueMaker/sbqueue.js +37 -53
  76. package/dist/QueueMaker/service.d.ts +1 -1
  77. package/dist/RequestContext.d.ts +114 -26
  78. package/dist/RequestContext.d.ts.map +1 -1
  79. package/dist/RequestContext.js +7 -7
  80. package/dist/RequestFiberSet.d.ts +7 -7
  81. package/dist/RequestFiberSet.d.ts.map +1 -1
  82. package/dist/RequestFiberSet.js +5 -5
  83. package/dist/Store/ContextMapContainer.d.ts +19 -3
  84. package/dist/Store/ContextMapContainer.d.ts.map +1 -1
  85. package/dist/Store/ContextMapContainer.js +13 -3
  86. package/dist/Store/Cosmos/query.d.ts +1 -1
  87. package/dist/Store/Cosmos/query.d.ts.map +1 -1
  88. package/dist/Store/Cosmos/query.js +8 -10
  89. package/dist/Store/Cosmos.d.ts +1 -1
  90. package/dist/Store/Cosmos.d.ts.map +1 -1
  91. package/dist/Store/Cosmos.js +308 -242
  92. package/dist/Store/Disk.d.ts +2 -2
  93. package/dist/Store/Disk.d.ts.map +1 -1
  94. package/dist/Store/Disk.js +25 -22
  95. package/dist/Store/Memory.d.ts +4 -4
  96. package/dist/Store/Memory.d.ts.map +1 -1
  97. package/dist/Store/Memory.js +27 -22
  98. package/dist/Store/SQL/Pg.d.ts +4 -0
  99. package/dist/Store/SQL/Pg.d.ts.map +1 -0
  100. package/dist/Store/SQL/Pg.js +189 -0
  101. package/dist/Store/SQL/query.d.ts +38 -0
  102. package/dist/Store/SQL/query.d.ts.map +1 -0
  103. package/dist/Store/SQL/query.js +367 -0
  104. package/dist/Store/SQL.d.ts +20 -0
  105. package/dist/Store/SQL.d.ts.map +1 -0
  106. package/dist/Store/SQL.js +381 -0
  107. package/dist/Store/codeFilter.d.ts +1 -1
  108. package/dist/Store/codeFilter.d.ts.map +1 -1
  109. package/dist/Store/codeFilter.js +2 -1
  110. package/dist/Store/index.d.ts +5 -2
  111. package/dist/Store/index.d.ts.map +1 -1
  112. package/dist/Store/index.js +15 -3
  113. package/dist/Store/service.d.ts +17 -6
  114. package/dist/Store/service.d.ts.map +1 -1
  115. package/dist/Store/service.js +24 -6
  116. package/dist/Store/utils.d.ts +1 -1
  117. package/dist/Store/utils.d.ts.map +1 -1
  118. package/dist/Store/utils.js +3 -4
  119. package/dist/Store.d.ts +1 -1
  120. package/dist/adapters/SQL/Model.d.ts +28 -42
  121. package/dist/adapters/SQL/Model.d.ts.map +1 -1
  122. package/dist/adapters/SQL/Model.js +2 -2
  123. package/dist/adapters/SQL.d.ts +1 -1
  124. package/dist/adapters/ServiceBus.d.ts +9 -9
  125. package/dist/adapters/ServiceBus.d.ts.map +1 -1
  126. package/dist/adapters/ServiceBus.js +13 -15
  127. package/dist/adapters/cosmos-client.d.ts +3 -3
  128. package/dist/adapters/cosmos-client.d.ts.map +1 -1
  129. package/dist/adapters/cosmos-client.js +3 -3
  130. package/dist/adapters/index.d.ts +8 -2
  131. package/dist/adapters/index.d.ts.map +1 -1
  132. package/dist/adapters/index.js +8 -2
  133. package/dist/adapters/logger.d.ts +1 -1
  134. package/dist/adapters/logger.d.ts.map +1 -1
  135. package/dist/adapters/memQueue.d.ts +3 -3
  136. package/dist/adapters/memQueue.d.ts.map +1 -1
  137. package/dist/adapters/memQueue.js +3 -3
  138. package/dist/adapters/mongo-client.d.ts +3 -3
  139. package/dist/adapters/mongo-client.d.ts.map +1 -1
  140. package/dist/adapters/mongo-client.js +3 -3
  141. package/dist/adapters/redis-client.d.ts +3 -3
  142. package/dist/adapters/redis-client.d.ts.map +1 -1
  143. package/dist/adapters/redis-client.js +3 -3
  144. package/dist/api/ContextProvider.d.ts +7 -7
  145. package/dist/api/ContextProvider.d.ts.map +1 -1
  146. package/dist/api/ContextProvider.js +6 -6
  147. package/dist/api/codec.d.ts +1 -1
  148. package/dist/api/internal/RequestContextMiddleware.d.ts +2 -2
  149. package/dist/api/internal/RequestContextMiddleware.d.ts.map +1 -1
  150. package/dist/api/internal/RequestContextMiddleware.js +2 -2
  151. package/dist/api/internal/auth.d.ts +44 -6
  152. package/dist/api/internal/auth.d.ts.map +1 -1
  153. package/dist/api/internal/auth.js +160 -29
  154. package/dist/api/internal/events.d.ts +3 -3
  155. package/dist/api/internal/events.d.ts.map +1 -1
  156. package/dist/api/internal/events.js +9 -7
  157. package/dist/api/internal/health.d.ts +1 -1
  158. package/dist/api/layerUtils.d.ts +6 -6
  159. package/dist/api/layerUtils.d.ts.map +1 -1
  160. package/dist/api/layerUtils.js +5 -5
  161. package/dist/api/middlewares.d.ts +1 -1
  162. package/dist/api/reportError.d.ts +1 -1
  163. package/dist/api/routing/middleware/RouterMiddleware.d.ts +4 -4
  164. package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -1
  165. package/dist/api/routing/middleware/middleware.d.ts +39 -3
  166. package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
  167. package/dist/api/routing/middleware/middleware.js +45 -14
  168. package/dist/api/routing/middleware.d.ts +1 -2
  169. package/dist/api/routing/middleware.d.ts.map +1 -1
  170. package/dist/api/routing/middleware.js +1 -2
  171. package/dist/api/routing/schema/jwt.d.ts +1 -1
  172. package/dist/api/routing/schema/jwt.d.ts.map +1 -1
  173. package/dist/api/routing/tsort.d.ts +1 -1
  174. package/dist/api/routing/tsort.d.ts.map +1 -1
  175. package/dist/api/routing/utils.d.ts +3 -3
  176. package/dist/api/routing/utils.d.ts.map +1 -1
  177. package/dist/api/routing.d.ts +12 -14
  178. package/dist/api/routing.d.ts.map +1 -1
  179. package/dist/api/routing.js +17 -6
  180. package/dist/api/setupRequest.d.ts +8 -5
  181. package/dist/api/setupRequest.d.ts.map +1 -1
  182. package/dist/api/setupRequest.js +12 -7
  183. package/dist/api/util.d.ts +1 -1
  184. package/dist/arbs.d.ts +1 -1
  185. package/dist/arbs.d.ts.map +1 -1
  186. package/dist/arbs.js +5 -3
  187. package/dist/errorReporter.d.ts +4 -4
  188. package/dist/errorReporter.d.ts.map +1 -1
  189. package/dist/errorReporter.js +16 -23
  190. package/dist/errors.d.ts +1 -1
  191. package/dist/fileUtil.d.ts +1 -1
  192. package/dist/fileUtil.d.ts.map +1 -1
  193. package/dist/index.d.ts +1 -1
  194. package/dist/logger/jsonLogger.d.ts +1 -1
  195. package/dist/logger/logFmtLogger.d.ts +1 -1
  196. package/dist/logger/shared.d.ts +1 -1
  197. package/dist/logger/shared.js +2 -2
  198. package/dist/logger.d.ts +1 -1
  199. package/dist/logger.d.ts.map +1 -1
  200. package/dist/rateLimit.d.ts +9 -3
  201. package/dist/rateLimit.d.ts.map +1 -1
  202. package/dist/rateLimit.js +5 -11
  203. package/dist/test.d.ts +1 -1
  204. package/dist/test.d.ts.map +1 -1
  205. package/dist/vitest.d.ts +1 -1
  206. package/eslint.config.mjs +3 -3
  207. package/examples/query.ts +39 -35
  208. package/package.json +42 -28
  209. package/src/CUPS.ts +9 -11
  210. package/src/Emailer/Sendgrid.ts +17 -14
  211. package/src/Emailer/service.ts +8 -2
  212. package/src/MainFiberSet.ts +3 -3
  213. package/src/Model/Repository/Registry.ts +33 -0
  214. package/src/Model/Repository/ext.ts +93 -6
  215. package/src/Model/Repository/internal/internal.ts +97 -88
  216. package/src/Model/Repository/makeRepo.ts +12 -10
  217. package/src/Model/Repository/service.ts +31 -22
  218. package/src/Model/Repository/validation.ts +4 -4
  219. package/src/Model/Repository.ts +1 -0
  220. package/src/Model/dsl.ts +3 -3
  221. package/src/Model/query/new-kid-interpreter.ts +2 -2
  222. package/src/Model.ts +1 -0
  223. package/src/Operations.ts +78 -113
  224. package/src/OperationsRepo.ts +2 -2
  225. package/src/QueueMaker/SQLQueue.ts +121 -151
  226. package/src/QueueMaker/memQueue.ts +82 -103
  227. package/src/QueueMaker/sbqueue.ts +56 -86
  228. package/src/RequestContext.ts +8 -8
  229. package/src/RequestFiberSet.ts +4 -4
  230. package/src/Store/ContextMapContainer.ts +41 -2
  231. package/src/Store/Cosmos/query.ts +9 -11
  232. package/src/Store/Cosmos.ts +437 -343
  233. package/src/Store/Disk.ts +52 -49
  234. package/src/Store/Memory.ts +54 -48
  235. package/src/Store/SQL/Pg.ts +318 -0
  236. package/src/Store/SQL/query.ts +409 -0
  237. package/src/Store/SQL.ts +668 -0
  238. package/src/Store/codeFilter.ts +1 -0
  239. package/src/Store/index.ts +17 -2
  240. package/src/Store/service.ts +31 -7
  241. package/src/Store/utils.ts +23 -22
  242. package/src/adapters/SQL/Model.ts +10 -4
  243. package/src/adapters/ServiceBus.ts +111 -115
  244. package/src/adapters/cosmos-client.ts +2 -2
  245. package/src/adapters/index.ts +7 -0
  246. package/src/adapters/memQueue.ts +2 -2
  247. package/src/adapters/mongo-client.ts +2 -2
  248. package/src/adapters/redis-client.ts +2 -2
  249. package/src/api/ContextProvider.ts +11 -11
  250. package/src/api/internal/RequestContextMiddleware.ts +1 -1
  251. package/src/api/internal/auth.ts +246 -44
  252. package/src/api/internal/events.ts +12 -8
  253. package/src/api/layerUtils.ts +8 -8
  254. package/src/api/routing/middleware/RouterMiddleware.ts +4 -4
  255. package/src/api/routing/middleware/middleware.ts +52 -12
  256. package/src/api/routing/middleware.ts +0 -2
  257. package/src/api/routing.ts +21 -7
  258. package/src/api/setupRequest.ts +28 -8
  259. package/src/arbs.ts +4 -2
  260. package/src/errorReporter.ts +58 -72
  261. package/src/logger/shared.ts +1 -1
  262. package/src/rateLimit.ts +30 -22
  263. package/test/auth.test.ts +101 -0
  264. package/test/contextProvider.test.ts +11 -11
  265. package/test/controller.test.ts +18 -14
  266. package/test/dist/auth.test.d.ts.map +1 -0
  267. package/test/dist/contextProvider.test.d.ts.map +1 -1
  268. package/test/dist/controller.test.d.ts.map +1 -1
  269. package/test/dist/date-query.test.d.ts.map +1 -0
  270. package/test/dist/fixtures.d.ts +26 -12
  271. package/test/dist/fixtures.d.ts.map +1 -1
  272. package/test/dist/fixtures.js +12 -10
  273. package/test/dist/query.test.d.ts.map +1 -1
  274. package/test/dist/rawQuery.test.d.ts.map +1 -1
  275. package/test/dist/repository-ext.test.d.ts.map +1 -0
  276. package/test/dist/requires.test.d.ts.map +1 -1
  277. package/test/dist/router-generator.test.d.ts.map +1 -0
  278. package/test/dist/routing-interruptibility.test.d.ts.map +1 -0
  279. package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
  280. package/test/dist/sql-store.test.d.ts.map +1 -0
  281. package/test/fixtures.ts +11 -9
  282. package/test/query.test.ts +216 -34
  283. package/test/rawQuery.test.ts +23 -19
  284. package/test/repository-ext.test.ts +60 -0
  285. package/test/requires.test.ts +6 -6
  286. package/test/router-generator.test.ts +180 -0
  287. package/test/routing-interruptibility.test.ts +63 -0
  288. package/test/rpc-multi-middleware.test.ts +78 -9
  289. package/test/sql-store.test.ts +1064 -0
  290. package/test/validateSample.test.ts +15 -12
  291. package/tsconfig.examples.json +1 -1
  292. package/tsconfig.json +0 -1
  293. package/tsconfig.json.bak +2 -2
  294. package/tsconfig.src.json +35 -35
  295. package/tsconfig.test.json +2 -2
@@ -1,42 +1,244 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- /* eslint-disable unused-imports/no-unused-vars */
3
- import { Data, Effect } from "effect-app"
1
+ import { Data, Effect, Option } from "effect-app"
4
2
  import { HttpHeaders, HttpMiddleware, HttpServerRequest, HttpServerResponse } from "effect-app/http"
5
- import { auth, InsufficientScopeError, InvalidRequestError, InvalidTokenError, UnauthorizedError } from "express-oauth2-jwt-bearer"
3
+ import { createRemoteJWKSet, jwtVerify } from "jose"
6
4
 
7
- // // Authorization middleware. When used, the Access Token must
8
- // // exist and be verified against the Auth0 JSON Web Key Set.
5
+ const getHeaders = (error: string, description: string, scopes?: ReadonlyArray<string>) => ({
6
+ "WWW-Authenticate": `Bearer realm="api", error="${error}", error_description="${description.replace(/"/g, "'")}"${
7
+ scopes ? `, scope="${scopes.join(" ")}"` : ""
8
+ }`
9
+ })
9
10
 
10
- // type Errors = InsufficientScopeError | InvalidRequestError | InvalidTokenError | UnauthorizedError
11
- type Config = Parameters<typeof auth>[0]
12
- export const checkJWTI = (config: Config) => {
13
- const mw = auth(config)
14
- return Effect.fnUntraced(function*(headers: HttpHeaders.Headers) {
15
- return yield* Effect.callback<
16
- void,
17
- InsufficientScopeError | InvalidRequestError | InvalidTokenError | UnauthorizedError
18
- >(
19
- (resume) => {
20
- const next = (err?: unknown) => {
21
- if (!err) return resume(Effect.void)
22
- if (
23
- err instanceof InsufficientScopeError
24
- || err instanceof InvalidRequestError
25
- || err instanceof InvalidTokenError
26
- || err instanceof UnauthorizedError
27
- ) {
28
- return resume(Effect.fail(err))
11
+ export class UnauthorizedError extends Error {
12
+ readonly status: number = 401
13
+ readonly statusCode: number = 401
14
+ headers = { "WWW-Authenticate": "Bearer realm=\"api\"" }
15
+
16
+ constructor(message = "Unauthorized") {
17
+ super(message)
18
+ this.name = this.constructor.name
19
+ }
20
+ }
21
+
22
+ export class InvalidRequestError extends UnauthorizedError {
23
+ readonly code: string
24
+ override readonly status = 400
25
+ override readonly statusCode = 400
26
+
27
+ constructor(message = "Invalid Request", useErrorCode = true) {
28
+ super(message)
29
+ this.code = useErrorCode ? "invalid_request" : ""
30
+ if (useErrorCode) {
31
+ this.headers = getHeaders(this.code, this.message)
32
+ }
33
+ }
34
+ }
35
+
36
+ export class InvalidTokenError extends UnauthorizedError {
37
+ readonly code = "invalid_token"
38
+
39
+ constructor(message = "Invalid Token") {
40
+ super(message)
41
+ this.headers = getHeaders(this.code, this.message)
42
+ }
43
+ }
44
+
45
+ export class InsufficientScopeError extends UnauthorizedError {
46
+ readonly code = "insufficient_scope"
47
+ override readonly status = 403
48
+ override readonly statusCode = 403
49
+
50
+ constructor(scopes?: ReadonlyArray<string>, message = "Insufficient Scope") {
51
+ super(message)
52
+ this.headers = getHeaders(this.code, this.message, scopes)
53
+ }
54
+ }
55
+
56
+ export interface JwtVerifierOptions {
57
+ readonly audience?: string | Array<string> | ReadonlyArray<string>
58
+ readonly clockTolerance?: number
59
+ readonly issuer?: string
60
+ readonly issuerBaseURL?: string
61
+ readonly jwksUri?: string
62
+ readonly maxTokenAge?: number
63
+ readonly secret?: string
64
+ readonly strict?: boolean
65
+ readonly tokenSigningAlg?: string
66
+ }
67
+
68
+ export interface AuthOptions extends JwtVerifierOptions {
69
+ readonly authRequired?: boolean
70
+ }
71
+
72
+ type Config = AuthOptions
73
+
74
+ type JwtError = InsufficientScopeError | InvalidRequestError | InvalidTokenError | UnauthorizedError
75
+
76
+ type ResolvedConfigBase = {
77
+ readonly audience: string | Array<string> | undefined
78
+ readonly clockTolerance: number
79
+ readonly issuer: string | undefined
80
+ readonly maxTokenAge: number | undefined
81
+ readonly strict: boolean
82
+ readonly tokenSigningAlg: string | undefined
83
+ }
84
+
85
+ type ResolvedConfig =
86
+ & ResolvedConfigBase
87
+ & (
88
+ | {
89
+ readonly key: ReturnType<typeof createRemoteJWKSet>
90
+ readonly keyType: "jwks"
91
+ }
92
+ | {
93
+ readonly key: Uint8Array
94
+ readonly keyType: "secret"
95
+ }
96
+ )
97
+
98
+ const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null
99
+
100
+ const getErrorMessage = (error: unknown) => error instanceof Error ? error.message : String(error)
101
+
102
+ const normalizeAudience = (audience: Config["audience"]): string | Array<string> | undefined =>
103
+ Array.isArray(audience) ? Array.from(audience) : audience as string | undefined
104
+
105
+ const buildDiscoveryUrl = (issuerBaseURL: string) => {
106
+ const url = new URL(issuerBaseURL)
107
+ if (!url.pathname.includes("/.well-known/")) {
108
+ url.pathname = url.pathname.endsWith("/")
109
+ ? `${url.pathname}.well-known/openid-configuration`
110
+ : `${url.pathname}/.well-known/openid-configuration`
111
+ }
112
+ url.search = ""
113
+ url.hash = ""
114
+ return url
115
+ }
116
+
117
+ const fetchDiscoveryDocumentPromise = async (issuerBaseURL: string) => {
118
+ const response = await fetch(buildDiscoveryUrl(issuerBaseURL))
119
+ if (!response.ok) {
120
+ throw new Error(`Failed to fetch authorization server metadata: ${response.status}`)
121
+ }
122
+ const json = await response.json()
123
+ if (!isRecord(json) || typeof json["issuer"] !== "string" || typeof json["jwks_uri"] !== "string") {
124
+ throw new Error("Invalid authorization server metadata")
125
+ }
126
+ return { issuer: json["issuer"], jwksUri: json["jwks_uri"] }
127
+ }
128
+
129
+ const getAuthorizationToken = (headers: HttpHeaders.Headers, authRequired: boolean) => {
130
+ const authorization = HttpHeaders.get(headers, "authorization")
131
+ if (Option.isNone(authorization)) {
132
+ return authRequired ? Effect.fail(new UnauthorizedError()) : Effect.succeed(Option.none<string>())
133
+ }
134
+
135
+ const [scheme, token] = authorization.value.split(" ")
136
+ if (!scheme || !token || scheme.toLowerCase() !== "bearer") {
137
+ return Effect.fail(new InvalidRequestError("", false))
138
+ }
139
+
140
+ return Effect.succeed(Option.some(token))
141
+ }
142
+
143
+ const makeResolveConfig = (config: Config) => {
144
+ let cached: Promise<ResolvedConfig> | undefined
145
+
146
+ return Effect.tryPromise({
147
+ try: () => {
148
+ if (!cached) {
149
+ cached = (async (): Promise<ResolvedConfig> => {
150
+ const discovery = config.issuerBaseURL
151
+ ? await fetchDiscoveryDocumentPromise(config.issuerBaseURL)
152
+ : undefined
153
+
154
+ const issuer = config.issuer ?? discovery?.issuer
155
+ const jwksUri = config.jwksUri ?? discovery?.jwksUri
156
+ const secret = config.secret
157
+ const base = {
158
+ audience: normalizeAudience(config.audience),
159
+ clockTolerance: config.clockTolerance ?? 5,
160
+ issuer,
161
+ maxTokenAge: config.maxTokenAge,
162
+ strict: config.strict ?? false,
163
+ tokenSigningAlg: config.tokenSigningAlg
164
+ } satisfies ResolvedConfigBase
165
+
166
+ if (!issuer && !secret) {
167
+ throw new InvalidRequestError("JWT config requires 'issuer', 'issuerBaseURL', or 'secret'")
29
168
  }
30
- return resume(Effect.die(err))
31
- }
32
- const r = { headers, query: {}, body: {}, is: () => false, method: "POST" } // is("urlencoded")
33
- try {
34
- mw(r as any, {} as any, next)
35
- } catch (e) {
36
- return resume(Effect.die(e))
37
- }
169
+
170
+ if (!secret) {
171
+ if (!jwksUri) {
172
+ throw new InvalidRequestError("JWT config requires 'jwksUri', 'issuerBaseURL', or 'secret'")
173
+ }
174
+
175
+ return {
176
+ ...base,
177
+ key: createRemoteJWKSet(new URL(jwksUri)),
178
+ keyType: "jwks"
179
+ }
180
+ }
181
+
182
+ return {
183
+ ...base,
184
+ key: new TextEncoder().encode(secret),
185
+ keyType: "secret"
186
+ }
187
+ })()
38
188
  }
189
+
190
+ return cached
191
+ },
192
+ catch: (error) =>
193
+ error instanceof InvalidRequestError || error instanceof InvalidTokenError
194
+ ? error
195
+ : new InvalidTokenError(getErrorMessage(error))
196
+ })
197
+ }
198
+
199
+ const verifyToken =
200
+ (resolveConfig: Effect.Effect<ResolvedConfig, InvalidRequestError | InvalidTokenError>) => (token: string) =>
201
+ resolveConfig.pipe(
202
+ Effect.flatMap((config) => {
203
+ const options = {
204
+ clockTolerance: config.clockTolerance,
205
+ ...(config.tokenSigningAlg ? { algorithms: [config.tokenSigningAlg] } : {}),
206
+ ...(config.audience !== undefined ? { audience: config.audience } : {}),
207
+ ...(config.issuer !== undefined ? { issuer: config.issuer } : {}),
208
+ ...(config.maxTokenAge !== undefined ? { maxTokenAge: config.maxTokenAge } : {})
209
+ }
210
+ const verified = config.keyType === "jwks"
211
+ ? Effect.tryPromise({
212
+ try: () => jwtVerify(token, config.key, options).then(({ protectedHeader }) => ({ protectedHeader })),
213
+ catch: (error) => new InvalidTokenError(getErrorMessage(error))
214
+ })
215
+ : Effect.tryPromise({
216
+ try: () => jwtVerify(token, config.key, options).then(({ protectedHeader }) => ({ protectedHeader })),
217
+ catch: (error) => new InvalidTokenError(getErrorMessage(error))
218
+ })
219
+
220
+ return verified.pipe(
221
+ Effect.flatMap(({ protectedHeader }) => {
222
+ const typ = protectedHeader.typ?.toLowerCase().replace(/^application\//, "")
223
+ return config.strict && typ !== "at+jwt"
224
+ ? Effect.fail(new InvalidTokenError("Unexpected 'typ' value"))
225
+ : Effect.void
226
+ })
227
+ )
228
+ })
39
229
  )
230
+
231
+ export const checkJWTI = (config: Config) => {
232
+ const resolveConfig = makeResolveConfig(config)
233
+ const verify = verifyToken(resolveConfig)
234
+
235
+ return Effect.fnUntraced(function*(headers: HttpHeaders.Headers) {
236
+ const token = yield* getAuthorizationToken(headers, config.authRequired !== false)
237
+ if (Option.isNone(token)) {
238
+ return
239
+ }
240
+
241
+ yield* verify(token.value)
40
242
  })
41
243
  }
42
244
 
@@ -45,24 +247,24 @@ export const checkJwt = (config: Config) => {
45
247
  return HttpMiddleware.make((app) =>
46
248
  Effect.gen(function*() {
47
249
  const req = yield* HttpServerRequest.HttpServerRequest
48
- const response = yield* check(req.headers).pipe(Effect.catch((e) =>
49
- HttpServerResponse.json({ message: e.message }, {
50
- status: e.status,
51
- headers: HttpHeaders.fromInput(e.headers)
52
- })
53
- ))
250
+ const response = yield* check(req.headers).pipe(
251
+ Effect.catch((error: JwtError) =>
252
+ HttpServerResponse.json({ message: error.message }, {
253
+ status: error.status,
254
+ headers: HttpHeaders.fromInput(error.headers)
255
+ })
256
+ )
257
+ )
258
+
54
259
  if (response) {
55
260
  return response
56
261
  }
262
+
57
263
  return yield* app
58
264
  })
59
265
  )
60
266
  }
61
267
 
62
268
  export class JWTError extends Data.TaggedClass("JWTError")<{
63
- error:
64
- | InsufficientScopeError
65
- | InvalidRequestError
66
- | InvalidTokenError
67
- | UnauthorizedError
269
+ error: JwtError
68
270
  }> {}
@@ -1,6 +1,7 @@
1
1
  import { Duration, Effect, pipe, S, Schedule, Stream } from "effect-app"
2
2
  import { HttpHeaders, HttpServerResponse } from "effect-app/http"
3
3
  import { reportError } from "../../errorReporter.js"
4
+ import { storeId } from "../../Store/Memory.js"
4
5
  import { setupRequestContextFromCurrent } from "../setupRequest.js"
5
6
 
6
7
  // Tell the client to retry every 10 seconds if connectivity is lost
@@ -9,29 +10,32 @@ const keepAlive = Stream.fromEffectSchedule(Effect.succeed(":keep-alive"), Sched
9
10
 
10
11
  let connId = BigInt(0)
11
12
 
12
- export const makeSSE = <A extends { id: any }, SI, SR>(
13
- schema: S.Codec<A, SI, SR>
13
+ export const makeSSE = <A extends { id: any }, SI, SRD, SRE>(
14
+ schema: S.Codec<A, SI, SRD, SRE>
14
15
  ) =>
15
16
  <E, R>(events: Stream.Stream<{ evt: A; namespace: string }, E, R>) =>
16
17
  Effect
17
18
  .gen(function*() {
18
19
  const id = connId++
19
- const ctx = yield* Effect.services<R | SR>()
20
+ const ctx = yield* Effect.context<R | SRD | SRE>()
20
21
  const res = HttpServerResponse.stream(
21
22
  // workaround for different scoped behaviour for streams in Bun
22
23
  // https://discord.com/channels/795981131316985866/1098177242598756412/1389646879675125861
23
24
  Effect
24
25
  .gen(function*() {
26
+ const ns = yield* storeId
25
27
  yield* Effect.annotateCurrentSpan({ connectionId: id.toString() })
26
- yield* Effect.logInfo("$ start listening to events, id: " + id.toString())
27
- yield* Effect.addFinalizer(() => Effect.logInfo("$ end listening to events, id: " + id.toString()))
28
+ yield* Effect.logInfo("$ start listening to events, id: " + id.toString() + ", ns: " + ns)
29
+ yield* Effect.addFinalizer(() =>
30
+ Effect.logInfo("$ end listening to events, id: " + id.toString() + ", ns: " + ns)
31
+ )
28
32
 
29
33
  const enc = new TextEncoder()
30
34
 
31
- const encode = S.encodeEffect(S.fromJsonString(schema))
35
+ const encode = S.encodeEffect(S.fromJsonString(S.toCodecJson(schema)))
32
36
 
33
37
  const eventStream = Stream.mapEffect(
34
- events,
38
+ Stream.filter(events, (_) => _.namespace === ns),
35
39
  (_) =>
36
40
  encode(_.evt)
37
41
  .pipe(Effect.map((data) => `id: ${_.evt.id}\ndata: ${data}`))
@@ -42,7 +46,7 @@ export const makeSSE = <A extends { id: any }, SI, SR>(
42
46
  Stream.merge(keepAlive),
43
47
  // Keep this unary so pipe receives a function, not a Stream value.
44
48
  (self) => Stream.merge(self, eventStream, { haltStrategy: "either" }),
45
- Stream.tapCause((cause) => Effect.logError("SSE error", cause)),
49
+ Stream.tapCause((cause) => Effect.logError("SSE error, id: " + id.toString() + ", ns: " + ns, cause)),
46
50
  Stream.map((_) => enc.encode(_ + "\n\n"))
47
51
  )
48
52
 
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { Effect, type Layer, type NonEmptyReadonlyArray, Option, ServiceMap } from "effect-app"
2
+ import { Context, Effect, type Layer, type NonEmptyReadonlyArray, Option } from "effect-app"
3
3
  import { InfraLogger } from "../logger.js"
4
4
 
5
5
  // TODO: These LayerUtils are flaky, like in dependencies as a readonly array, it breaks when there are two entries
@@ -27,7 +27,7 @@ export namespace LayerUtils {
27
27
  }
28
28
 
29
29
  export type ContextTagWithDefault<Id, A, LayerE, LayerR> =
30
- & ServiceMap.Service<Id, A>
30
+ & Context.Service<Id, A>
31
31
  & {
32
32
  Default: Layer.Layer<Id, LayerE, LayerR>
33
33
  }
@@ -36,29 +36,29 @@ export namespace ContextTagWithDefault {
36
36
  export type Base<A> = ContextTagWithDefault<any, A, any, any>
37
37
  }
38
38
 
39
- export type GetContext<T> = T extends ServiceMap.ServiceMap<infer Y> ? Y : never
39
+ export type GetContext<T> = T extends Context.Context<infer Y> ? Y : never
40
40
 
41
41
  export const mergeContexts = Effect.fnUntraced(
42
42
  function*<
43
43
  T extends readonly {
44
44
  maker: any
45
- handle: Effect.Effect<ServiceMap.ServiceMap<any> | Option.Option<ServiceMap.ServiceMap<any>>>
45
+ handle: Effect.Effect<Context.Context<any> | Option.Option<Context.Context<any>>>
46
46
  }[]
47
47
  >(
48
48
  makers: T
49
49
  ) {
50
- let context = ServiceMap.empty()
50
+ let context = Context.empty()
51
51
  for (const mw of makers) {
52
52
  const ctx = yield* mw.handle.pipe(Effect.provide(context))
53
- const moreContext = ServiceMap.isServiceMap(ctx) ? Option.some(ctx) : ctx
53
+ const moreContext = Context.isContext(ctx) ? Option.some(ctx) : ctx
54
54
  yield* InfraLogger.logDebug(
55
55
  "Built dynamic context for middleware" + (mw.maker.key ?? mw.maker),
56
56
  Option.map(moreContext, (c) => (c as any).toJSON().services)
57
57
  )
58
58
  if (moreContext.value) {
59
- context = ServiceMap.merge(context, moreContext.value)
59
+ context = Context.merge(context, moreContext.value)
60
60
  }
61
61
  }
62
- return context as ServiceMap.ServiceMap<Effect.Success<T[number]["handle"]>>
62
+ return context as Context.Context<Effect.Success<T[number]["handle"]>>
63
63
  }
64
64
  )
@@ -1,13 +1,13 @@
1
1
  /* eslint-disable @typescript-eslint/no-unsafe-assignment */
2
2
  /* eslint-disable @typescript-eslint/no-unsafe-return */
3
3
  /* eslint-disable @typescript-eslint/no-explicit-any */
4
- import { type Layer, type ServiceMap } from "effect-app"
4
+ import { type Context, type Layer } from "effect-app"
5
5
  import { type GetContextConfig, type RpcContextMap } from "effect-app/rpc/RpcContextMap"
6
6
  import { type RpcMiddlewareV4 } from "effect-app/rpc/RpcMiddleware"
7
7
  // module:
8
8
  //
9
9
 
10
- // v4: middleware tags are ServiceMap.Service (not Effect) — they carry the RpcMiddlewareV4 as their service Shape
10
+ // v4: middleware tags are Context.Service (not Effect) — they carry the RpcMiddlewareV4 as their service Shape
11
11
  export type RouterMiddleware<
12
12
  Self,
13
13
  RequestContextMap extends Record<string, RpcContextMap.Any>, // what services will the middlware provide dynamically to the next, or raise errors.
@@ -18,9 +18,9 @@ export type RouterMiddleware<
18
18
  _ContextProviderR, // what the context provider requires
19
19
  RequestContextId
20
20
  > =
21
- & ServiceMap.Service<Self, RpcMiddlewareV4<ContextProviderA, ContextProviderE, never>>
21
+ & Context.Service<Self, RpcMiddlewareV4<ContextProviderA, ContextProviderE, never>>
22
22
  & {
23
23
  readonly Default: Layer.Layer<Self, MakeMiddlewareE, MakeMiddlewareR>
24
- readonly requestContext: ServiceMap.Service<RequestContextId, GetContextConfig<RequestContextMap>>
24
+ readonly requestContext: Context.Service<RequestContextId, GetContextConfig<RequestContextMap>>
25
25
  readonly requestContextMap: RequestContextMap
26
26
  }
@@ -1,10 +1,13 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  import { Cause, Config, Effect, Layer, Schema } from "effect"
3
3
  import { ConfigureInterruptibilityMiddleware, DevMode, DevModeMiddleware, LoggerMiddleware, RequestCacheMiddleware } from "effect-app/middleware"
4
+ import { RpcContextMap, type RpcMiddleware } from "effect-app/rpc"
4
5
  import { pretty } from "effect-app/utils"
6
+ import * as Context from "effect/Context"
7
+ import { type Rpc } from "effect/unstable/rpc"
5
8
  import { logError, reportError } from "../../../errorReporter.js"
6
9
  import { InfraLogger } from "../../../logger.js"
7
- import { determineMethod, isCommand } from "../utils.js"
10
+ import { WithNsTransaction } from "../../../Store/SQL.js"
8
11
 
9
12
  const logRequestError = logError("Request")
10
13
  const reportRequestError = reportError("Request")
@@ -26,22 +29,20 @@ export const RequestCacheMiddlewareLive = Layer.succeed(
26
29
  const isOptimisticConcurrencyException = (input: unknown) =>
27
30
  typeof input === "object" && input !== null && "_tag" in input && input._tag === "OptimisticConcurrencyException"
28
31
 
32
+ export const RequestType = Context.Reference<"command" | "query">(
33
+ "@effect-app/infra/api/routing/RequestType",
34
+ { defaultValue: () => "query" }
35
+ )
36
+
29
37
  export const ConfigureInterruptibilityMiddlewareLive = Layer.effect(
30
38
  ConfigureInterruptibilityMiddleware,
31
39
  Effect.gen(function*() {
32
- const cache = new Map()
33
- const getCached = (key: string, schema: Schema.Top) => {
34
- const existing = cache.get(key)
35
- if (existing) return existing
36
- const n = determineMethod(key, schema)
37
- cache.set(key, n)
38
- return n
39
- }
40
40
  return (effect, { rpc }) => {
41
- const method = getCached(rpc._tag, rpc.payloadSchema)
41
+ const requestType = Context.get(rpc.annotations, RequestType)
42
+ const isCommand = requestType === "command"
42
43
 
43
- effect = isCommand(method)
44
- ? Effect.retry(Effect.uninterruptible(effect), { times: 1, while: isOptimisticConcurrencyException })
44
+ effect = isCommand
45
+ ? Effect.retry(effect, { times: 1, while: isOptimisticConcurrencyException })
45
46
  : Effect.interruptible(effect)
46
47
 
47
48
  return effect
@@ -126,3 +127,42 @@ export const DefaultGenericMiddlewaresLive = Layer.mergeAll(
126
127
  LoggerMiddlewareLive,
127
128
  DevModeMiddlewareLive
128
129
  )
130
+
131
+ /**
132
+ * Config entry for `RequestContextMap` that controls per-RPC transaction wrapping.
133
+ * Defaults to `false` (no transaction). Set `requiresTransaction: true` on a route to enable.
134
+ *
135
+ * @example
136
+ * ```ts
137
+ * class RequestContextMap extends RpcContextMap.makeMap({
138
+ * requiresTransaction: requiresTransactionConfig,
139
+ * // ...
140
+ * }) {}
141
+ * ```
142
+ */
143
+ export const requiresTransactionConfig = RpcContextMap.makeCustom()(Schema.Never, false)
144
+
145
+ /**
146
+ * Creates the middleware Effect for SQL transaction wrapping.
147
+ * Requires `WithNsTransaction` service.
148
+ * Reads `requiresTransaction` from the RPC config; defaults to `false`.
149
+ *
150
+ * @example
151
+ * ```ts
152
+ * const SqlTransactionMiddlewareLive = Layer.effect(
153
+ * SqlTransactionMiddleware,
154
+ * makeSqlTransactionMiddleware(RequestContextMap)
155
+ * )
156
+ * ```
157
+ */
158
+ export const makeSqlTransactionMiddleware = Effect.fnUntraced(function*(
159
+ rcm: { getConfig: (rpc: Rpc.AnyWithProps) => { readonly requiresTransaction?: boolean } }
160
+ ) {
161
+ const withTx = yield* WithNsTransaction
162
+ const mw: RpcMiddleware.RpcMiddlewareV4<never, never, never> = (effect, { rpc }) => {
163
+ const { requiresTransaction } = rcm.getConfig(rpc)
164
+ if (requiresTransaction !== true) return effect
165
+ return withTx(effect)
166
+ }
167
+ return mw
168
+ })
@@ -2,5 +2,3 @@
2
2
  export * from "./middleware/middleware.js"
3
3
  export * from "./middleware/RouterMiddleware.js"
4
4
  // codegen:end
5
-
6
- export * as Middleware from "./middleware.js"
@@ -3,6 +3,7 @@
3
3
  /* eslint-disable @typescript-eslint/no-empty-object-type */
4
4
  /* eslint-disable @typescript-eslint/no-explicit-any */
5
5
  import { Config, Effect, Layer, type NonEmptyReadonlyArray, Predicate, S, type Scope } from "effect-app"
6
+ import { getMeta } from "effect-app/client"
6
7
  import { type HttpHeaders } from "effect-app/http"
7
8
  import { type GetEffectContext, type GetEffectError, type RpcContextMap } from "effect-app/rpc/RpcContextMap"
8
9
  import { type TypeTestId } from "effect-app/TypeTest"
@@ -10,14 +11,20 @@ import { typedKeysOf, typedValuesOf } from "effect-app/utils"
10
11
  import { type Yieldable } from "effect/Effect"
11
12
  import { Rpc, RpcGroup, type RpcSerialization, RpcServer } from "effect/unstable/rpc"
12
13
  import { type LayerUtils } from "./layerUtils.js"
13
- import { type RouterMiddleware } from "./routing/middleware.js"
14
+ import { RequestType as RequestTypeAnnotation, type RouterMiddleware } from "./routing/middleware.js"
14
15
 
15
16
  export * from "./routing/middleware.js"
16
17
 
18
+ export const applyRequestTypeInterruptibility = <A, E, R>(
19
+ requestType: "command" | "query",
20
+ effect: Effect.Effect<A, E, R>
21
+ ) => requestType === "command" ? Rpc.uninterruptible(effect) : effect
22
+
17
23
  // it's the result of extending S.Req setting success, config
18
24
  // it's a schema plus some metadata
19
25
  export type AnyRequestModule = S.Top & {
20
26
  _tag: string // unique identifier for the request module
27
+ type: "command" | "query"
21
28
  config: any // ?
22
29
  success: S.Top // validates the success response
23
30
  error: S.Top // validates the failure response
@@ -182,10 +189,9 @@ export const makeRouter = <
182
189
  * if `check` is provided, the router will only be created if the effect succeeds with true
183
190
  */
184
191
  function matchFor<
185
- const ModuleName extends string,
186
192
  const Resource extends Record<string, any>
187
193
  >(
188
- rsc: Resource & { meta: { moduleName: ModuleName } },
194
+ rsc: Resource,
189
195
  options?: { check?: Effect.Effect<boolean> }
190
196
  ) {
191
197
  type HandlerWithInputGen<
@@ -244,12 +250,12 @@ export const makeRouter = <
244
250
 
245
251
  type AnyHandlers<Action extends AnyRequestModule> = HandlersRaw<Action> | HandlersDecoded<Action>
246
252
 
247
- const { meta } = rsc
253
+ const meta = getMeta(rsc)
248
254
 
249
255
  type RequestModules = FilterRequestModules<Resource>
250
256
  const requestModules = typedKeysOf(rsc).reduce((acc, cur) => {
251
257
  if (Predicate.isObjectKeyword(rsc[cur]) && rsc[cur]["success"]) {
252
- acc[cur as keyof RequestModules] = rsc[cur] as RequestModules[keyof RequestModules]
258
+ acc[cur as keyof RequestModules] = rsc[cur]
253
259
  }
254
260
  return acc
255
261
  }, {} as RequestModules)
@@ -261,12 +267,14 @@ export const makeRouter = <
261
267
  if (handlerImpl[Symbol.toStringTag] === "GeneratorFunction") handlerImpl = Effect.fnUntraced(handlerImpl)
262
268
  const stack = new Error().stack?.split("\n").slice(2).join("\n")
263
269
  return Effect.isEffect(handlerImpl)
270
+ // oxlint-disable-next-line typescript/no-extraneous-class
264
271
  ? class {
265
272
  static request = rsc[cur]
266
273
  static stack = stack
267
274
  static _tag = RequestTypes.DECODED
268
275
  static handler = () => handlerImpl
269
276
  }
277
+ // oxlint-disable-next-line typescript/no-extraneous-class
270
278
  : class {
271
279
  static request = rsc[cur]
272
280
  static stack = stack
@@ -284,12 +292,14 @@ export const makeRouter = <
284
292
  if (handlerImpl[Symbol.toStringTag] === "GeneratorFunction") handlerImpl = Effect.fnUntraced(handlerImpl)
285
293
  const stack = new Error().stack?.split("\n").slice(2).join("\n")
286
294
  return Effect.isEffect(handlerImpl)
295
+ // oxlint-disable-next-line typescript/no-extraneous-class
287
296
  ? class {
288
297
  static request = rsc[cur]
289
298
  static stack = stack
290
299
  static _tag = RequestTypes.RAW
291
300
  static handler = () => handlerImpl
292
301
  }
302
+ // oxlint-disable-next-line typescript/no-extraneous-class
293
303
  : class {
294
304
  static request = rsc[cur]
295
305
  static stack = stack
@@ -386,12 +396,15 @@ export const makeRouter = <
386
396
  static success = S.toEncoded(resource.success)
387
397
  } as any
388
398
  : resource,
389
- (payload: any, headers: any) =>
390
- (handler.handler(payload, headers) as Effect.Effect<unknown, unknown, unknown>).pipe(
399
+ (payload: any, headers: any) => {
400
+ const effect = (handler.handler(payload, headers) as Effect.Effect<unknown, unknown, unknown>).pipe(
391
401
  Effect.withSpan(`Request.${meta.moduleName}.${resource._tag}`, {}, {
392
402
  captureStackTrace: () => handler.stack // capturing the handler stack is the main reason why we are doing the span here
393
403
  })
394
404
  )
405
+
406
+ return applyRequestTypeInterruptibility(resource.type, effect)
407
+ }
395
408
  ] as const
396
409
  return acc
397
410
  }, {} as any) as {
@@ -418,6 +431,7 @@ export const makeRouter = <
418
431
  return Rpc
419
432
  .make(resource._tag, { payload: resource, success: resource.success, error: resource.error })
420
433
  .annotate(middleware.requestContext, resource.config ?? {})
434
+ .annotate(RequestTypeAnnotation, resource.type)
421
435
  })
422
436
  )
423
437
  .prefix(`${meta.moduleName}.`)