@effect-app/infra 4.0.0-beta.22 → 4.0.0-beta.221

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