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

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 (310) hide show
  1. package/CHANGELOG.md +1640 -0
  2. package/_check.sh +1 -1
  3. package/dist/CUPS.d.ts +7 -7
  4. package/dist/CUPS.d.ts.map +1 -1
  5. package/dist/CUPS.js +10 -12
  6. package/dist/Emailer/Sendgrid.d.ts +14 -14
  7. package/dist/Emailer/Sendgrid.d.ts.map +1 -1
  8. package/dist/Emailer/Sendgrid.js +16 -15
  9. package/dist/Emailer/fake.d.ts +1 -1
  10. package/dist/Emailer/service.d.ts +10 -4
  11. package/dist/Emailer/service.d.ts.map +1 -1
  12. package/dist/Emailer/service.js +3 -3
  13. package/dist/Emailer.d.ts +1 -1
  14. package/dist/MainFiberSet.d.ts +9 -9
  15. package/dist/MainFiberSet.d.ts.map +1 -1
  16. package/dist/MainFiberSet.js +3 -3
  17. package/dist/Model/Repository/Registry.d.ts +20 -0
  18. package/dist/Model/Repository/Registry.d.ts.map +1 -0
  19. package/dist/Model/Repository/Registry.js +17 -0
  20. package/dist/Model/Repository/ext.d.ts +33 -15
  21. package/dist/Model/Repository/ext.d.ts.map +1 -1
  22. package/dist/Model/Repository/ext.js +54 -2
  23. package/dist/Model/Repository/internal/internal.d.ts +6 -6
  24. package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
  25. package/dist/Model/Repository/internal/internal.js +103 -51
  26. package/dist/Model/Repository/legacy.d.ts +1 -1
  27. package/dist/Model/Repository/makeRepo.d.ts +7 -6
  28. package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
  29. package/dist/Model/Repository/makeRepo.js +5 -1
  30. package/dist/Model/Repository/service.d.ts +28 -23
  31. package/dist/Model/Repository/service.d.ts.map +1 -1
  32. package/dist/Model/Repository/validation.d.ts +46 -17
  33. package/dist/Model/Repository/validation.d.ts.map +1 -1
  34. package/dist/Model/Repository/validation.js +5 -5
  35. package/dist/Model/Repository.d.ts +2 -1
  36. package/dist/Model/Repository.d.ts.map +1 -1
  37. package/dist/Model/Repository.js +2 -1
  38. package/dist/Model/dsl.d.ts +4 -4
  39. package/dist/Model/dsl.d.ts.map +1 -1
  40. package/dist/Model/filter/filterApi.d.ts +5 -5
  41. package/dist/Model/filter/filterApi.d.ts.map +1 -1
  42. package/dist/Model/filter/types/errors.d.ts +1 -1
  43. package/dist/Model/filter/types/fields.d.ts +1 -1
  44. package/dist/Model/filter/types/path/common.d.ts +1 -1
  45. package/dist/Model/filter/types/path/eager.d.ts +1 -1
  46. package/dist/Model/filter/types/path/eager.d.ts.map +1 -1
  47. package/dist/Model/filter/types/path/eager.js +1 -1
  48. package/dist/Model/filter/types/path/index.d.ts +1 -1
  49. package/dist/Model/filter/types/utils.d.ts +1 -1
  50. package/dist/Model/filter/types/validator.d.ts +1 -1
  51. package/dist/Model/filter/types.d.ts +1 -1
  52. package/dist/Model/query/dsl.d.ts +139 -16
  53. package/dist/Model/query/dsl.d.ts.map +1 -1
  54. package/dist/Model/query/dsl.js +187 -1
  55. package/dist/Model/query/new-kid-interpreter.d.ts +76 -7
  56. package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -1
  57. package/dist/Model/query/new-kid-interpreter.js +122 -6
  58. package/dist/Model/query.d.ts +1 -1
  59. package/dist/Model.d.ts +2 -1
  60. package/dist/Model.d.ts.map +1 -1
  61. package/dist/Model.js +2 -1
  62. package/dist/QueueMaker/SQLQueue.d.ts +5 -7
  63. package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
  64. package/dist/QueueMaker/SQLQueue.js +130 -116
  65. package/dist/QueueMaker/errors.d.ts +2 -2
  66. package/dist/QueueMaker/errors.d.ts.map +1 -1
  67. package/dist/QueueMaker/memQueue.d.ts +7 -4
  68. package/dist/QueueMaker/memQueue.d.ts.map +1 -1
  69. package/dist/QueueMaker/memQueue.js +75 -63
  70. package/dist/QueueMaker/sbqueue.d.ts +6 -3
  71. package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
  72. package/dist/QueueMaker/sbqueue.js +52 -53
  73. package/dist/QueueMaker/service.d.ts +1 -1
  74. package/dist/RequestContext.d.ts +74 -35
  75. package/dist/RequestContext.d.ts.map +1 -1
  76. package/dist/RequestContext.js +13 -14
  77. package/dist/RequestFiberSet.d.ts +7 -7
  78. package/dist/RequestFiberSet.d.ts.map +1 -1
  79. package/dist/RequestFiberSet.js +3 -3
  80. package/dist/Store/ContextMapContainer.d.ts +19 -3
  81. package/dist/Store/ContextMapContainer.d.ts.map +1 -1
  82. package/dist/Store/ContextMapContainer.js +13 -3
  83. package/dist/Store/Cosmos/query.d.ts +5 -1
  84. package/dist/Store/Cosmos/query.d.ts.map +1 -1
  85. package/dist/Store/Cosmos/query.js +113 -34
  86. package/dist/Store/Cosmos.d.ts +1 -1
  87. package/dist/Store/Cosmos.d.ts.map +1 -1
  88. package/dist/Store/Cosmos.js +335 -243
  89. package/dist/Store/Disk.d.ts +2 -2
  90. package/dist/Store/Disk.d.ts.map +1 -1
  91. package/dist/Store/Disk.js +72 -35
  92. package/dist/Store/Memory.d.ts +6 -4
  93. package/dist/Store/Memory.d.ts.map +1 -1
  94. package/dist/Store/Memory.js +242 -58
  95. package/dist/Store/SQL/Pg.d.ts +4 -0
  96. package/dist/Store/SQL/Pg.d.ts.map +1 -0
  97. package/dist/Store/SQL/Pg.js +231 -0
  98. package/dist/Store/SQL/query.d.ts +42 -0
  99. package/dist/Store/SQL/query.d.ts.map +1 -0
  100. package/dist/Store/SQL/query.js +479 -0
  101. package/dist/Store/SQL.d.ts +20 -0
  102. package/dist/Store/SQL.d.ts.map +1 -0
  103. package/dist/Store/SQL.js +446 -0
  104. package/dist/Store/codeFilter.d.ts +1 -1
  105. package/dist/Store/codeFilter.d.ts.map +1 -1
  106. package/dist/Store/codeFilter.js +4 -2
  107. package/dist/Store/index.d.ts +5 -2
  108. package/dist/Store/index.d.ts.map +1 -1
  109. package/dist/Store/index.js +15 -3
  110. package/dist/Store/service.d.ts +22 -8
  111. package/dist/Store/service.d.ts.map +1 -1
  112. package/dist/Store/service.js +24 -6
  113. package/dist/Store/utils.d.ts +1 -1
  114. package/dist/Store/utils.d.ts.map +1 -1
  115. package/dist/Store/utils.js +3 -4
  116. package/dist/Store.d.ts +1 -1
  117. package/dist/adapters/SQL/Model.d.ts +31 -42
  118. package/dist/adapters/SQL/Model.d.ts.map +1 -1
  119. package/dist/adapters/SQL/Model.js +29 -38
  120. package/dist/adapters/SQL.d.ts +1 -1
  121. package/dist/adapters/ServiceBus.d.ts +11 -11
  122. package/dist/adapters/ServiceBus.d.ts.map +1 -1
  123. package/dist/adapters/ServiceBus.js +25 -21
  124. package/dist/adapters/cosmos-client.d.ts +3 -3
  125. package/dist/adapters/cosmos-client.d.ts.map +1 -1
  126. package/dist/adapters/cosmos-client.js +3 -3
  127. package/dist/adapters/index.d.ts +8 -2
  128. package/dist/adapters/index.d.ts.map +1 -1
  129. package/dist/adapters/index.js +8 -2
  130. package/dist/adapters/logger.d.ts +1 -1
  131. package/dist/adapters/logger.d.ts.map +1 -1
  132. package/dist/adapters/memQueue.d.ts +3 -3
  133. package/dist/adapters/memQueue.d.ts.map +1 -1
  134. package/dist/adapters/memQueue.js +3 -3
  135. package/dist/adapters/mongo-client.d.ts +3 -3
  136. package/dist/adapters/mongo-client.d.ts.map +1 -1
  137. package/dist/adapters/mongo-client.js +3 -3
  138. package/dist/adapters/redis-client.d.ts +3 -3
  139. package/dist/adapters/redis-client.d.ts.map +1 -1
  140. package/dist/adapters/redis-client.js +3 -3
  141. package/dist/api/ContextProvider.d.ts +8 -8
  142. package/dist/api/ContextProvider.d.ts.map +1 -1
  143. package/dist/api/ContextProvider.js +6 -6
  144. package/dist/api/codec.d.ts +1 -1
  145. package/dist/api/internal/RequestContextMiddleware.d.ts +2 -2
  146. package/dist/api/internal/RequestContextMiddleware.d.ts.map +1 -1
  147. package/dist/api/internal/RequestContextMiddleware.js +9 -6
  148. package/dist/api/internal/auth.d.ts +44 -6
  149. package/dist/api/internal/auth.d.ts.map +1 -1
  150. package/dist/api/internal/auth.js +160 -29
  151. package/dist/api/internal/events.d.ts +3 -3
  152. package/dist/api/internal/events.d.ts.map +1 -1
  153. package/dist/api/internal/events.js +10 -8
  154. package/dist/api/internal/health.d.ts +1 -1
  155. package/dist/api/layerUtils.d.ts +6 -6
  156. package/dist/api/layerUtils.d.ts.map +1 -1
  157. package/dist/api/layerUtils.js +5 -5
  158. package/dist/api/middlewares.d.ts +1 -1
  159. package/dist/api/reportError.d.ts +1 -1
  160. package/dist/api/routing/middleware/RouterMiddleware.d.ts +4 -4
  161. package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -1
  162. package/dist/api/routing/middleware/middleware.d.ts +39 -3
  163. package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
  164. package/dist/api/routing/middleware/middleware.js +48 -16
  165. package/dist/api/routing/middleware.d.ts +1 -2
  166. package/dist/api/routing/middleware.d.ts.map +1 -1
  167. package/dist/api/routing/middleware.js +1 -2
  168. package/dist/api/routing/schema/jwt.d.ts +1 -1
  169. package/dist/api/routing/schema/jwt.d.ts.map +1 -1
  170. package/dist/api/routing/tsort.d.ts +1 -1
  171. package/dist/api/routing/tsort.d.ts.map +1 -1
  172. package/dist/api/routing/utils.d.ts +3 -3
  173. package/dist/api/routing/utils.d.ts.map +1 -1
  174. package/dist/api/routing.d.ts +80 -37
  175. package/dist/api/routing.d.ts.map +1 -1
  176. package/dist/api/routing.js +109 -41
  177. package/dist/api/setupRequest.d.ts +8 -5
  178. package/dist/api/setupRequest.d.ts.map +1 -1
  179. package/dist/api/setupRequest.js +12 -7
  180. package/dist/api/util.d.ts +1 -1
  181. package/dist/arbs.d.ts +1 -1
  182. package/dist/arbs.d.ts.map +1 -1
  183. package/dist/arbs.js +5 -3
  184. package/dist/errorReporter.d.ts +4 -4
  185. package/dist/errorReporter.d.ts.map +1 -1
  186. package/dist/errorReporter.js +20 -25
  187. package/dist/errors.d.ts +1 -1
  188. package/dist/fileUtil.d.ts +1 -1
  189. package/dist/fileUtil.d.ts.map +1 -1
  190. package/dist/index.d.ts +1 -1
  191. package/dist/logger/jsonLogger.d.ts +1 -1
  192. package/dist/logger/logFmtLogger.d.ts +1 -1
  193. package/dist/logger/shared.d.ts +1 -1
  194. package/dist/logger/shared.js +2 -2
  195. package/dist/logger.d.ts +1 -1
  196. package/dist/logger.d.ts.map +1 -1
  197. package/dist/otel.d.ts +75 -0
  198. package/dist/otel.d.ts.map +1 -0
  199. package/dist/otel.js +65 -0
  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 +2 -2
  204. package/dist/test.d.ts.map +1 -1
  205. package/dist/test.js +1 -1
  206. package/dist/vitest.d.ts +1 -1
  207. package/examples/query.ts +42 -38
  208. package/package.json +46 -37
  209. package/src/CUPS.ts +9 -11
  210. package/src/Emailer/Sendgrid.ts +17 -14
  211. package/src/Emailer/service.ts +9 -3
  212. package/src/MainFiberSet.ts +5 -6
  213. package/src/Model/Repository/Registry.ts +33 -0
  214. package/src/Model/Repository/ext.ts +96 -10
  215. package/src/Model/Repository/internal/internal.ts +218 -149
  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/filter/types/path/eager.ts +1 -2
  222. package/src/Model/query/dsl.ts +348 -18
  223. package/src/Model/query/new-kid-interpreter.ts +206 -6
  224. package/src/Model.ts +1 -0
  225. package/src/QueueMaker/SQLQueue.ts +144 -152
  226. package/src/QueueMaker/memQueue.ts +104 -103
  227. package/src/QueueMaker/sbqueue.ts +70 -86
  228. package/src/RequestContext.ts +14 -16
  229. package/src/RequestFiberSet.ts +2 -2
  230. package/src/Store/ContextMapContainer.ts +41 -2
  231. package/src/Store/Cosmos/query.ts +140 -43
  232. package/src/Store/Cosmos.ts +482 -349
  233. package/src/Store/Disk.ts +102 -65
  234. package/src/Store/Memory.ts +275 -87
  235. package/src/Store/SQL/Pg.ts +361 -0
  236. package/src/Store/SQL/query.ts +539 -0
  237. package/src/Store/SQL.ts +731 -0
  238. package/src/Store/codeFilter.ts +3 -1
  239. package/src/Store/index.ts +17 -2
  240. package/src/Store/service.ts +41 -10
  241. package/src/Store/utils.ts +23 -22
  242. package/src/adapters/SQL/Model.ts +41 -40
  243. package/src/adapters/ServiceBus.ts +125 -121
  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 +12 -13
  250. package/src/api/internal/RequestContextMiddleware.ts +15 -5
  251. package/src/api/internal/auth.ts +246 -44
  252. package/src/api/internal/events.ts +13 -9
  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 +55 -14
  256. package/src/api/routing/middleware.ts +0 -2
  257. package/src/api/routing.ts +296 -131
  258. package/src/api/setupRequest.ts +28 -8
  259. package/src/arbs.ts +4 -2
  260. package/src/errorReporter.ts +62 -74
  261. package/src/logger/shared.ts +1 -1
  262. package/src/otel.ts +152 -0
  263. package/src/rateLimit.ts +30 -22
  264. package/src/test.ts +1 -1
  265. package/test/auth.test.ts +101 -0
  266. package/test/contextProvider.test.ts +11 -11
  267. package/test/controller.test.ts +21 -30
  268. package/test/dist/auth.test.d.ts.map +1 -0
  269. package/test/dist/contextProvider.test.d.ts.map +1 -1
  270. package/test/dist/controller.test.d.ts.map +1 -1
  271. package/test/dist/date-query.test.d.ts.map +1 -0
  272. package/test/dist/fixtures.d.ts +26 -12
  273. package/test/dist/fixtures.d.ts.map +1 -1
  274. package/test/dist/fixtures.js +12 -10
  275. package/test/dist/query.test.d.ts.map +1 -1
  276. package/test/dist/rawQuery.test.d.ts.map +1 -1
  277. package/test/dist/repository-ext.test.d.ts.map +1 -0
  278. package/test/dist/requires.test.d.ts.map +1 -1
  279. package/test/dist/router-generator.test.d.ts.map +1 -0
  280. package/test/dist/routing-interruptibility.test.d.ts.map +1 -0
  281. package/test/dist/rpc-e2e-invalidation.test.d.ts.map +1 -0
  282. package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
  283. package/test/dist/rpc-stream-fullstack.test.d.ts.map +1 -0
  284. package/test/dist/sql-store.test.d.ts.map +1 -0
  285. package/test/fixtures.ts +11 -9
  286. package/test/query.test.ts +813 -38
  287. package/test/rawQuery.test.ts +301 -20
  288. package/test/repository-ext.test.ts +60 -0
  289. package/test/requires.test.ts +6 -6
  290. package/test/router-generator.test.ts +183 -0
  291. package/test/routing-interruptibility.test.ts +63 -0
  292. package/test/rpc-e2e-invalidation.test.ts +251 -0
  293. package/test/rpc-multi-middleware.test.ts +78 -9
  294. package/test/rpc-stream-fullstack.test.ts +300 -0
  295. package/test/sql-store.test.ts +1592 -0
  296. package/test/validateSample.test.ts +15 -12
  297. package/tsconfig.examples.json +1 -1
  298. package/tsconfig.json +0 -1
  299. package/tsconfig.json.bak +2 -2
  300. package/tsconfig.src.json +35 -35
  301. package/tsconfig.test.json +2 -2
  302. package/dist/Operations.d.ts +0 -55
  303. package/dist/Operations.d.ts.map +0 -1
  304. package/dist/Operations.js +0 -102
  305. package/dist/OperationsRepo.d.ts +0 -41
  306. package/dist/OperationsRepo.d.ts.map +0 -1
  307. package/dist/OperationsRepo.js +0 -14
  308. package/eslint.config.mjs +0 -24
  309. package/src/Operations.ts +0 -235
  310. package/src/OperationsRepo.ts +0 -16
@@ -1,14 +1,164 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
 
3
- import { Array, Effect, flow, type NonEmptyReadonlyArray, Option, Order, pipe, Ref, Result, Semaphore, ServiceMap, Struct } from "effect-app"
3
+ import { Array, Context, Effect, flow, type NonEmptyReadonlyArray, Option, Order, pipe, Ref, Result, Semaphore, Struct } from "effect-app"
4
4
  import { NonEmptyString255 } from "effect-app/Schema"
5
- import { get } from "effect-app/utils"
5
+ import { assertUnreachable } from "effect-app/utils"
6
6
  import { InfraLogger } from "../logger.js"
7
+ import type { FilterResult } from "../Model/filter/filterApi.js"
7
8
  import type { FieldValues } from "../Model/filter/types.js"
9
+ import type { ComputedProjectionIrExpression, ComputedProjectionMathIrExpression } from "../Model/query.js"
10
+ import { annotateDb } from "../otel.js"
8
11
  import { codeFilter, codeFilter3_ } from "./codeFilter.js"
9
12
  import { type FilterArgs, type PersistenceModelType, type Store, type StoreConfig, StoreMaker } from "./service.js"
10
13
  import { makeUpdateETag } from "./utils.js"
11
14
 
15
+ /** Traverse an object by a dot-separated path string, e.g. `"a.b.c"`. */
16
+ export function get(obj: any, path: string): any {
17
+ return path.split(".").reduce((res: any, key: string) => (res != null ? res[key] : res), obj)
18
+ }
19
+
20
+ const stripRelationFilterPaths = (state: readonly FilterResult[], relationPath: string): readonly FilterResult[] => {
21
+ const prefix = `${relationPath}.-1.`
22
+ return state.map((entry) =>
23
+ "path" in entry
24
+ ? {
25
+ ...entry,
26
+ path: entry.path.startsWith(prefix) ? entry.path.slice(prefix.length) : entry.path
27
+ }
28
+ : {
29
+ ...entry,
30
+ result: stripRelationFilterPaths(entry.result, relationPath)
31
+ }
32
+ )
33
+ }
34
+
35
+ const emptyValueFor = (tag: ComputedProjectionIrExpression["_tag"]) => {
36
+ switch (tag) {
37
+ case "relation-count":
38
+ case "relation-distinct-count":
39
+ case "relation-sum":
40
+ case "relation-sum-expr":
41
+ case "relation-sum-expr-normalized":
42
+ return 0
43
+ case "relation-sum-expr-by":
44
+ return [] as unknown[]
45
+ case "relation-any":
46
+ return false
47
+ case "relation-every":
48
+ return true
49
+ case "relation-collect":
50
+ return [] as unknown[]
51
+ case "relation-collect-fields":
52
+ return [] as unknown[]
53
+ default:
54
+ return assertUnreachable(tag)
55
+ }
56
+ }
57
+
58
+ const computeProjectionValue = (
59
+ row: FieldValues,
60
+ computed: ComputedProjectionIrExpression
61
+ ) => {
62
+ const relation = get(row, computed.path)
63
+ if (!Array.isArray(relation)) {
64
+ return emptyValueFor(computed._tag)
65
+ }
66
+ const filter = stripRelationFilterPaths(computed.filter, computed.path)
67
+ // empty filter = unconditional match (codeFilter3_ uses eval on a built
68
+ // string and chokes on `( )`, so short-circuit before invoking it).
69
+ const matches = filter.length === 0
70
+ ? (_value: unknown) => true
71
+ : (value: unknown) => codeFilter3_(filter, value)
72
+ const evalExpr = (value: unknown, expression: ComputedProjectionMathIrExpression): number => {
73
+ switch (expression._tag) {
74
+ case "field": {
75
+ const v = get(value, expression.field)
76
+ return typeof v === "number" ? v : Number(v) || 0
77
+ }
78
+ case "mul":
79
+ return evalExpr(value, expression.left) * evalExpr(value, expression.right)
80
+ default:
81
+ return assertUnreachable(expression)
82
+ }
83
+ }
84
+ switch (computed._tag) {
85
+ case "relation-count":
86
+ return relation.reduce<number>((acc, value) => matches(value) ? acc + 1 : acc, 0)
87
+ case "relation-any":
88
+ return relation.some(matches)
89
+ case "relation-every":
90
+ return relation.every(matches)
91
+ case "relation-distinct-count": {
92
+ const seen = new Set<unknown>()
93
+ for (const value of relation) {
94
+ if (matches(value)) seen.add(get(value, computed.field))
95
+ }
96
+ return seen.size
97
+ }
98
+ case "relation-sum":
99
+ return relation.reduce<number>((acc, value) => {
100
+ if (!matches(value)) return acc
101
+ const v = get(value, computed.field)
102
+ return acc + (typeof v === "number" ? v : Number(v) || 0)
103
+ }, 0)
104
+ case "relation-sum-expr":
105
+ return relation.reduce<number>((acc, value) => {
106
+ if (!matches(value)) return acc
107
+ return acc + evalExpr(value, computed.expression)
108
+ }, 0)
109
+ case "relation-sum-expr-by": {
110
+ const totals = new Map<unknown, number>()
111
+ for (const value of relation) {
112
+ if (!matches(value)) continue
113
+ const unit = get(value, computed.unit)
114
+ const current = totals.get(unit) ?? 0
115
+ totals.set(unit, current + evalExpr(value, computed.expression))
116
+ }
117
+ return [...totals.entries()].map(([unit, total]) => ({ unit, total }))
118
+ }
119
+ case "relation-sum-expr-normalized":
120
+ return relation.reduce<number>((acc, value) => {
121
+ if (!matches(value)) return acc
122
+ const unit = get(value, computed.unit)
123
+ const factor = unit === computed.toBase ? 1 : computed.factors[String(unit)]
124
+ if (factor === undefined || !Number.isFinite(factor)) return acc
125
+ return acc + evalExpr(value, computed.expression) * factor
126
+ }, 0)
127
+ case "relation-collect": {
128
+ const out: unknown[] = []
129
+ const seen = computed.distinct ? new Set<unknown>() : undefined
130
+ for (const value of relation) {
131
+ if (!matches(value)) continue
132
+ const v = get(value, computed.field)
133
+ if (seen) {
134
+ if (seen.has(v)) continue
135
+ seen.add(v)
136
+ }
137
+ out.push(v)
138
+ }
139
+ return out
140
+ }
141
+ case "relation-collect-fields": {
142
+ const out: unknown[] = []
143
+ const seen = computed.distinct ? new Set<unknown>() : undefined
144
+ for (const value of relation) {
145
+ if (!matches(value)) continue
146
+ for (const field of computed.fields) {
147
+ const v = get(value, field)
148
+ if (seen) {
149
+ if (seen.has(v)) continue
150
+ seen.add(v)
151
+ }
152
+ out.push(v)
153
+ }
154
+ }
155
+ return out
156
+ }
157
+ default:
158
+ return assertUnreachable(computed)
159
+ }
160
+ }
161
+
12
162
  export function memFilter<T extends FieldValues, U extends keyof T = never>(f: FilterArgs<T, U>) {
13
163
  type M = U extends undefined ? T : Pick<T, U>
14
164
  return ((c: T[]): M[] => {
@@ -16,18 +166,26 @@ export function memFilter<T extends FieldValues, U extends keyof T = never>(f: F
16
166
  const sel = f.select
17
167
  if (!sel) return r as M[]
18
168
  return r.map((i) => {
19
- const [keys, subKeys] = pipe(
169
+ const [keys, entries] = pipe(
20
170
  sel,
21
- Array.partition((r) =>
22
- typeof r === "string" ? Result.fail(String(r)) : Result.succeed(r as { key: string; subKeys: string[] })
23
- )
171
+ Array.partition((entry) => typeof entry === "string" ? Result.fail(String(entry)) : Result.succeed(entry))
172
+ )
173
+ const subKeys = entries.filter((entry): entry is { key: string; subKeys: readonly string[] } =>
174
+ typeof entry === "object" && entry !== null && "subKeys" in entry
24
175
  )
176
+ const computedKeys = entries.filter((entry): entry is {
177
+ key: string
178
+ computed: ComputedProjectionIrExpression
179
+ } => typeof entry === "object" && entry !== null && "computed" in entry)
25
180
  const n = Struct.pick(i, keys)
26
181
  subKeys.forEach((subKey) => {
27
182
  n[subKey.key] = i[subKey.key]!.map(Struct.pick(subKey.subKeys as never[]))
28
183
  })
184
+ computedKeys.forEach((entry) => {
185
+ ;(n as Record<string, unknown>)[entry.key] = computeProjectionValue(i, entry.computed)
186
+ })
29
187
  return n as M
30
- }) as any
188
+ })
31
189
  }
32
190
  const skip = f?.skip
33
191
  const limit = f?.limit
@@ -72,7 +230,7 @@ export function memFilter<T extends FieldValues, U extends keyof T = never>(f: F
72
230
  }
73
231
 
74
232
  const defaultNs: NonEmptyString255 = NonEmptyString255("primary")
75
- export class storeId extends ServiceMap.Reference("StoreId", { defaultValue: (): NonEmptyString255 => defaultNs }) {}
233
+ export class storeId extends Context.Reference("StoreId", { defaultValue: (): NonEmptyString255 => defaultNs }) {}
76
234
 
77
235
  function logQuery(f: FilterArgs<any, any>, defaultValues?: any) {
78
236
  return InfraLogger
@@ -151,43 +309,55 @@ export function makeMemoryStoreInt<IdKey extends keyof Encoded, Encoded extends
151
309
  withPermit
152
310
  )
153
311
  const s: Store<IdKey, Encoded> = {
312
+ seedNamespace: () => Effect.void,
313
+
154
314
  queryRaw: (query) =>
155
315
  all
156
316
  .pipe(
157
317
  // Effect.tap(() => logQuery(query, defaultValues)),
158
318
  Effect.map(query.memory),
159
- Effect.withSpan("Memory.queryRaw [effect-app/infra/Store]", {
160
- attributes: { "repository.model_name": modelName, "repository.namespace": namespace }
161
- }, { captureStackTrace: false })
319
+ annotateDb({
320
+ operation: "queryRaw",
321
+ system: "memory",
322
+ collection: modelName,
323
+ namespace,
324
+ entity: modelName
325
+ })
162
326
  ),
163
327
 
164
- all: all.pipe(Effect.withSpan("Memory.all [effect-app/infra/Store]", {
165
- attributes: {
166
- modelName,
167
- namespace
168
- }
169
- }, { captureStackTrace: false })),
328
+ all: all.pipe(annotateDb({
329
+ operation: "all",
330
+ system: "memory",
331
+ collection: modelName,
332
+ namespace,
333
+ entity: modelName
334
+ })),
170
335
  find: (id) =>
171
336
  Ref
172
337
  .get(store)
173
338
  .pipe(
174
339
  Effect.map((_) => Option.fromNullishOr(_.get(id))),
175
- Effect
176
- .withSpan("Memory.find [effect-app/infra/Store]", {
177
- attributes: {
178
- modelName,
179
- namespace
180
- }
181
- }, { captureStackTrace: false })
340
+ annotateDb({
341
+ operation: "find",
342
+ system: "memory",
343
+ collection: modelName,
344
+ namespace,
345
+ entity: modelName,
346
+ extra: { "app.entity.id": id as unknown }
347
+ })
182
348
  ),
183
349
  filter: (f) =>
184
350
  all
185
351
  .pipe(
186
352
  Effect.tap(() => logQuery(f, defaultValues)),
187
353
  Effect.map(memFilter(f)),
188
- Effect.withSpan("Memory.filter [effect-app/infra/Store]", {
189
- attributes: { "repository.model_name": modelName, "repository.namespace": namespace }
190
- }, { captureStackTrace: false })
354
+ annotateDb({
355
+ operation: "filter",
356
+ system: "memory",
357
+ collection: modelName,
358
+ namespace,
359
+ entity: modelName
360
+ })
191
361
  ),
192
362
  set: (e) =>
193
363
  s
@@ -202,10 +372,14 @@ export function makeMemoryStoreInt<IdKey extends keyof Encoded, Encoded extends
202
372
  )
203
373
  ),
204
374
  withPermit,
205
- Effect
206
- .withSpan("Memory.set [effect-app/infra/Store]", {
207
- attributes: { "repository.model_name": modelName, "repository.namespace": namespace }
208
- }, { captureStackTrace: false })
375
+ annotateDb({
376
+ operation: "set",
377
+ system: "memory",
378
+ collection: modelName,
379
+ namespace,
380
+ entity: modelName,
381
+ extra: { "app.entity.id": e[idKey] as unknown }
382
+ })
209
383
  ),
210
384
  batchRemove: (items: NonEmptyReadonlyArray<Encoded[IdKey]>) =>
211
385
  pipe(
@@ -216,10 +390,13 @@ export function makeMemoryStoreInt<IdKey extends keyof Encoded, Encoded extends
216
390
  Effect.filterOrFail((_) => _.length <= 100, () => "BatchRemove: a batch may not exceed 100 items"),
217
391
  Effect.orDie,
218
392
  Effect.andThen(batchRemove),
219
- Effect
220
- .withSpan("Memory.batchRemove [effect-app/infra/Store]", {
221
- attributes: { "repository.model_name": modelName, "repository.namespace": namespace }
222
- }, { captureStackTrace: false })
393
+ annotateDb({
394
+ operation: "batchRemove",
395
+ system: "memory",
396
+ collection: modelName,
397
+ namespace,
398
+ entity: modelName
399
+ })
223
400
  )
224
401
  ),
225
402
  batchSet: (items: readonly [PM, ...PM[]]) =>
@@ -231,18 +408,25 @@ export function makeMemoryStoreInt<IdKey extends keyof Encoded, Encoded extends
231
408
  Effect.filterOrFail((_) => _.length <= 100, () => "BatchSet: a batch may not exceed 100 items"),
232
409
  Effect.orDie,
233
410
  Effect.andThen(batchSet),
234
- Effect
235
- .withSpan("Memory.batchSet [effect-app/infra/Store]", {
236
- attributes: { "repository.model_name": modelName, "repository.namespace": namespace }
237
- }, { captureStackTrace: false })
411
+ annotateDb({
412
+ operation: "batchSet",
413
+ system: "memory",
414
+ collection: modelName,
415
+ namespace,
416
+ entity: modelName
417
+ })
238
418
  )
239
419
  ),
240
420
  bulkSet: flow(
241
421
  batchSet,
242
422
  (_) =>
243
- _.pipe(Effect.withSpan("Memory.bulkSet [effect-app/infra/Store]", {
244
- attributes: { "repository.model_name": modelName, "repository.namespace": namespace }
245
- }, { captureStackTrace: false }))
423
+ _.pipe(annotateDb({
424
+ operation: "bulkSet",
425
+ system: "memory",
426
+ collection: modelName,
427
+ namespace,
428
+ entity: modelName
429
+ }))
246
430
  )
247
431
  }
248
432
  return s
@@ -250,56 +434,60 @@ export function makeMemoryStoreInt<IdKey extends keyof Encoded, Encoded extends
250
434
  }
251
435
 
252
436
  export const makeMemoryStore = () => ({
253
- make: <IdKey extends keyof Encoded, Encoded extends FieldValues, R, E>(
437
+ make: Effect.fnUntraced(function*<IdKey extends keyof Encoded, Encoded extends FieldValues, R, E>(
254
438
  modelName: string,
255
439
  idKey: IdKey,
256
440
  seed?: Effect.Effect<Iterable<Encoded>, E, R>,
257
441
  config?: StoreConfig<Encoded>
258
- ) =>
259
- Effect.gen(function*() {
260
- const storesSem = Semaphore.makeUnsafe(1)
261
- const primary = yield* makeMemoryStoreInt<IdKey, Encoded, R, E>(
262
- modelName,
263
- idKey,
264
- "primary",
265
- seed,
266
- config?.defaultValues
267
- )
268
- const ctx = yield* Effect.services<R>()
269
- const stores = new Map([["primary", primary]])
270
- const getStore = !config?.allowNamespace
271
- ? Effect.succeed(primary)
272
- : storeId.asEffect().pipe(Effect.flatMap((namespace) => {
273
- const store = stores.get(namespace)
274
- if (store) {
275
- return Effect.succeed(store)
276
- }
277
- if (!config.allowNamespace!(namespace)) {
278
- throw new Error(`Namespace ${namespace} not allowed!`)
279
- }
280
- return storesSem.withPermits(1)(Effect.suspend(() => {
281
- const store = stores.get(namespace)
282
- if (store) return Effect.sync(() => store)
283
- return makeMemoryStoreInt(modelName, idKey, namespace, seed, config?.defaultValues)
284
- .pipe(
285
- Effect.orDie,
286
- Effect.provide(ctx),
287
- Effect.tap((store) => Effect.sync(() => stores.set(namespace, store)))
288
- )
289
- }))
290
- }))
291
- const s: Store<IdKey, Encoded> = {
292
- all: Effect.flatMap(getStore, (_) => _.all),
293
- queryRaw: (...args) => Effect.flatMap(getStore, (_) => _.queryRaw(...args)),
294
- find: (...args) => Effect.flatMap(getStore, (_) => _.find(...args)),
295
- filter: (...args) => Effect.flatMap(getStore, (_) => _.filter(...args)),
296
- set: (...args) => Effect.flatMap(getStore, (_) => _.set(...args)),
297
- batchSet: (...args) => Effect.flatMap(getStore, (_) => _.batchSet(...args)),
298
- bulkSet: (...args) => Effect.flatMap(getStore, (_) => _.bulkSet(...args)),
299
- batchRemove: (...args) => Effect.flatMap(getStore, (_) => _.batchRemove(...args))
442
+ ) {
443
+ const primary = yield* makeMemoryStoreInt<IdKey, Encoded, R, E>(
444
+ modelName,
445
+ idKey,
446
+ "primary",
447
+ seed,
448
+ config?.defaultValues
449
+ )
450
+ const ctx = yield* Effect.context<R>()
451
+ const stores = new Map([["primary", primary]])
452
+ const semaphores = new Map<string, Semaphore.Semaphore>()
453
+ const getSem = (ns: string) => {
454
+ let sem = semaphores.get(ns)
455
+ if (!sem) {
456
+ sem = Semaphore.makeUnsafe(1)
457
+ semaphores.set(ns, sem)
300
458
  }
301
- return s
302
- })
459
+ return sem
460
+ }
461
+ const ensureStore = (namespace: string) =>
462
+ getSem(namespace).withPermits(1)(Effect.suspend(() => {
463
+ const store = stores.get(namespace)
464
+ if (store) return Effect.succeed(store)
465
+ if (config?.allowNamespace && !config.allowNamespace(namespace)) {
466
+ throw new Error(`Namespace ${namespace} not allowed!`)
467
+ }
468
+ return makeMemoryStoreInt(modelName, idKey, namespace, seed, config?.defaultValues)
469
+ .pipe(
470
+ Effect.orDie,
471
+ Effect.provide(ctx),
472
+ Effect.tap((store) => Effect.sync(() => stores.set(namespace, store)))
473
+ )
474
+ }))
475
+ const getStore = !config?.allowNamespace
476
+ ? Effect.succeed(primary)
477
+ : storeId.asEffect().pipe(Effect.flatMap((namespace) => ensureStore(namespace)))
478
+ const s: Store<IdKey, Encoded> = {
479
+ seedNamespace: (namespace) => ensureStore(namespace).pipe(Effect.asVoid),
480
+ all: Effect.flatMap(getStore, (_) => _.all),
481
+ queryRaw: (...args) => Effect.flatMap(getStore, (_) => _.queryRaw(...args)),
482
+ find: (...args) => Effect.flatMap(getStore, (_) => _.find(...args)),
483
+ filter: (...args) => Effect.flatMap(getStore, (_) => _.filter(...args)),
484
+ set: (...args) => Effect.flatMap(getStore, (_) => _.set(...args)),
485
+ batchSet: (...args) => Effect.flatMap(getStore, (_) => _.batchSet(...args)),
486
+ bulkSet: (...args) => Effect.flatMap(getStore, (_) => _.bulkSet(...args)),
487
+ batchRemove: (...args) => Effect.flatMap(getStore, (_) => _.batchRemove(...args))
488
+ }
489
+ return s
490
+ })
303
491
  })
304
492
 
305
493
  export const MemoryStoreLive = StoreMaker.toLayer(Effect.sync(() => makeMemoryStore()))