effect-app 4.0.0-beta.30 → 4.0.0-beta.300

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 (365) hide show
  1. package/CHANGELOG.md +1433 -0
  2. package/dist/Array.d.ts +2 -1
  3. package/dist/Array.d.ts.map +1 -1
  4. package/dist/Array.js +4 -4
  5. package/dist/Chunk.d.ts.map +1 -1
  6. package/dist/Config/SecretURL.d.ts +3 -1
  7. package/dist/Config/SecretURL.d.ts.map +1 -1
  8. package/dist/Config/SecretURL.js +3 -6
  9. package/dist/Config/internal/configSecretURL.d.ts.map +1 -1
  10. package/dist/Config/internal/configSecretURL.js +2 -2
  11. package/dist/Config.d.ts +2 -0
  12. package/dist/Config.d.ts.map +1 -0
  13. package/dist/Config.js +8 -0
  14. package/dist/ConfigProvider.d.ts +2 -0
  15. package/dist/ConfigProvider.d.ts.map +1 -0
  16. package/dist/ConfigProvider.js +6 -0
  17. package/dist/{ServiceMap.d.ts → Context.d.ts} +18 -20
  18. package/dist/Context.d.ts.map +1 -0
  19. package/dist/Context.js +67 -0
  20. package/dist/DataDependencies.d.ts +388 -0
  21. package/dist/DataDependencies.d.ts.map +1 -0
  22. package/dist/DataDependencies.js +64 -0
  23. package/dist/Effect.d.ts +10 -9
  24. package/dist/Effect.d.ts.map +1 -1
  25. package/dist/Effect.js +5 -8
  26. package/dist/Emailer.d.ts +51 -0
  27. package/dist/Emailer.d.ts.map +1 -0
  28. package/dist/Emailer.js +7 -0
  29. package/dist/Function.d.ts.map +1 -1
  30. package/dist/Layer.d.ts +10 -6
  31. package/dist/Layer.d.ts.map +1 -1
  32. package/dist/Layer.js +3 -2
  33. package/dist/Model/Repository/Registry.d.ts +22 -0
  34. package/dist/Model/Repository/Registry.d.ts.map +1 -0
  35. package/dist/Model/Repository/Registry.js +18 -0
  36. package/dist/Model/Repository/ext.d.ts +60 -0
  37. package/dist/Model/Repository/ext.d.ts.map +1 -0
  38. package/dist/Model/Repository/ext.js +122 -0
  39. package/dist/Model/Repository/internal/internal.d.ts +63 -0
  40. package/dist/Model/Repository/internal/internal.d.ts.map +1 -0
  41. package/dist/Model/Repository/internal/internal.js +438 -0
  42. package/dist/Model/Repository/legacy.d.ts +21 -0
  43. package/dist/Model/Repository/legacy.d.ts.map +1 -0
  44. package/dist/Model/Repository/legacy.js +2 -0
  45. package/dist/Model/Repository/makeRepo.d.ts +54 -0
  46. package/dist/Model/Repository/makeRepo.d.ts.map +1 -0
  47. package/dist/Model/Repository/makeRepo.js +27 -0
  48. package/dist/Model/Repository/service.d.ts +121 -0
  49. package/dist/Model/Repository/service.d.ts.map +1 -0
  50. package/dist/Model/Repository/service.js +2 -0
  51. package/dist/Model/Repository/validation.d.ts +58 -0
  52. package/dist/Model/Repository/validation.d.ts.map +1 -0
  53. package/dist/Model/Repository/validation.js +32 -0
  54. package/dist/Model/Repository.d.ts +7 -0
  55. package/dist/Model/Repository.d.ts.map +1 -0
  56. package/dist/Model/Repository.js +7 -0
  57. package/dist/Model/dsl.d.ts +33 -0
  58. package/dist/Model/dsl.d.ts.map +1 -0
  59. package/dist/Model/dsl.js +43 -0
  60. package/dist/Model/filter/filterApi.d.ts +30 -0
  61. package/dist/Model/filter/filterApi.d.ts.map +1 -0
  62. package/dist/Model/filter/filterApi.js +2 -0
  63. package/dist/Model/filter/types/errors.d.ts +29 -0
  64. package/dist/Model/filter/types/errors.d.ts.map +1 -0
  65. package/dist/Model/filter/types/errors.js +2 -0
  66. package/dist/Model/filter/types/fields.d.ts +15 -0
  67. package/dist/Model/filter/types/fields.d.ts.map +1 -0
  68. package/dist/Model/filter/types/fields.js +2 -0
  69. package/dist/Model/filter/types/path/common.d.ts +316 -0
  70. package/dist/Model/filter/types/path/common.d.ts.map +1 -0
  71. package/dist/Model/filter/types/path/common.js +2 -0
  72. package/dist/Model/filter/types/path/eager.d.ts +94 -0
  73. package/dist/Model/filter/types/path/eager.d.ts.map +1 -0
  74. package/dist/Model/filter/types/path/eager.js +36 -0
  75. package/dist/Model/filter/types/path/index.d.ts +4 -0
  76. package/dist/Model/filter/types/path/index.d.ts.map +1 -0
  77. package/dist/Model/filter/types/path/index.js +3 -0
  78. package/dist/Model/filter/types/utils.d.ts +79 -0
  79. package/dist/Model/filter/types/utils.d.ts.map +1 -0
  80. package/dist/Model/filter/types/utils.js +2 -0
  81. package/dist/Model/filter/types/validator.d.ts +30 -0
  82. package/dist/Model/filter/types/validator.d.ts.map +1 -0
  83. package/dist/Model/filter/types/validator.js +2 -0
  84. package/dist/Model/filter/types.d.ts +5 -0
  85. package/dist/Model/filter/types.d.ts.map +1 -0
  86. package/dist/Model/filter/types.js +7 -0
  87. package/dist/Model/query/dsl.d.ts +514 -0
  88. package/dist/Model/query/dsl.d.ts.map +1 -0
  89. package/dist/Model/query/dsl.js +376 -0
  90. package/dist/Model/query/new-kid-interpreter.d.ts +132 -0
  91. package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -0
  92. package/dist/Model/query/new-kid-interpreter.js +361 -0
  93. package/dist/Model/query.d.ts +15 -0
  94. package/dist/Model/query.d.ts.map +1 -0
  95. package/dist/Model/query.js +3 -0
  96. package/dist/Model.d.ts +5 -0
  97. package/dist/Model.d.ts.map +1 -0
  98. package/dist/Model.js +5 -0
  99. package/dist/NonEmptySet.d.ts +3 -1
  100. package/dist/NonEmptySet.d.ts.map +1 -1
  101. package/dist/NonEmptySet.js +2 -2
  102. package/dist/Option.d.ts +1 -0
  103. package/dist/Option.d.ts.map +1 -1
  104. package/dist/Option.js +3 -1
  105. package/dist/Pure.d.ts +7 -5
  106. package/dist/Pure.d.ts.map +1 -1
  107. package/dist/Pure.js +7 -4
  108. package/dist/QueueMaker.d.ts +11 -0
  109. package/dist/QueueMaker.d.ts.map +1 -0
  110. package/dist/QueueMaker.js +11 -0
  111. package/dist/RequestContext.d.ts +87 -0
  112. package/dist/RequestContext.d.ts.map +1 -0
  113. package/dist/RequestContext.js +45 -0
  114. package/dist/RequestScopedDependencies.d.ts +21 -0
  115. package/dist/RequestScopedDependencies.d.ts.map +1 -0
  116. package/dist/RequestScopedDependencies.js +16 -0
  117. package/dist/Schema/Class.d.ts +188 -19
  118. package/dist/Schema/Class.d.ts.map +1 -1
  119. package/dist/Schema/Class.js +260 -17
  120. package/dist/Schema/FastCheck.d.ts.map +1 -1
  121. package/dist/Schema/SchemaParser.d.ts +5 -0
  122. package/dist/Schema/SchemaParser.d.ts.map +1 -0
  123. package/dist/Schema/SchemaParser.js +6 -0
  124. package/dist/Schema/SpecialJsonSchema.d.ts +34 -0
  125. package/dist/Schema/SpecialJsonSchema.d.ts.map +1 -0
  126. package/dist/Schema/SpecialJsonSchema.js +118 -0
  127. package/dist/Schema/SpecialOpenApi.d.ts +32 -0
  128. package/dist/Schema/SpecialOpenApi.d.ts.map +1 -0
  129. package/dist/Schema/SpecialOpenApi.js +123 -0
  130. package/dist/Schema/brand.d.ts +4 -2
  131. package/dist/Schema/brand.d.ts.map +1 -1
  132. package/dist/Schema/brand.js +3 -1
  133. package/dist/Schema/email.d.ts.map +1 -1
  134. package/dist/Schema/email.js +7 -4
  135. package/dist/Schema/ext.d.ts +338 -55
  136. package/dist/Schema/ext.d.ts.map +1 -1
  137. package/dist/Schema/ext.js +358 -53
  138. package/dist/Schema/moreStrings.d.ts +82 -36
  139. package/dist/Schema/moreStrings.d.ts.map +1 -1
  140. package/dist/Schema/moreStrings.js +49 -42
  141. package/dist/Schema/numbers.d.ts +34 -21
  142. package/dist/Schema/numbers.d.ts.map +1 -1
  143. package/dist/Schema/numbers.js +55 -12
  144. package/dist/Schema/phoneNumber.d.ts.map +1 -1
  145. package/dist/Schema/phoneNumber.js +6 -3
  146. package/dist/Schema/schema.d.ts +1 -1
  147. package/dist/Schema/schema.d.ts.map +1 -1
  148. package/dist/Schema/schema.js +2 -2
  149. package/dist/Schema/strings.d.ts +18 -4
  150. package/dist/Schema/strings.d.ts.map +1 -1
  151. package/dist/Schema/strings.js +1 -5
  152. package/dist/Schema.d.ts +222 -8
  153. package/dist/Schema.d.ts.map +1 -1
  154. package/dist/Schema.js +191 -12
  155. package/dist/SchemaAST.d.ts +2 -0
  156. package/dist/SchemaAST.d.ts.map +1 -0
  157. package/dist/SchemaAST.js +2 -0
  158. package/dist/Set.d.ts +4 -1
  159. package/dist/Set.d.ts.map +1 -1
  160. package/dist/Set.js +3 -2
  161. package/dist/Store.d.ts +163 -0
  162. package/dist/Store.d.ts.map +1 -0
  163. package/dist/Store.js +113 -0
  164. package/dist/_ext/Array.d.ts +1 -1
  165. package/dist/_ext/Array.d.ts.map +1 -1
  166. package/dist/_ext/Array.js +4 -2
  167. package/dist/_ext/misc.d.ts +4 -1
  168. package/dist/_ext/misc.d.ts.map +1 -1
  169. package/dist/_ext/misc.js +4 -2
  170. package/dist/_ext/ord.ext.d.ts +2 -1
  171. package/dist/_ext/ord.ext.d.ts.map +1 -1
  172. package/dist/_ext/ord.ext.js +2 -2
  173. package/dist/builtin.d.ts +2 -14
  174. package/dist/builtin.d.ts.map +1 -1
  175. package/dist/builtin.js +1 -1
  176. package/dist/client/InvalidationKeys.d.ts +30 -0
  177. package/dist/client/InvalidationKeys.d.ts.map +1 -0
  178. package/dist/client/InvalidationKeys.js +37 -0
  179. package/dist/client/apiClientFactory.d.ts +19 -31
  180. package/dist/client/apiClientFactory.d.ts.map +1 -1
  181. package/dist/client/apiClientFactory.js +155 -35
  182. package/dist/client/clientFor.d.ts +54 -18
  183. package/dist/client/clientFor.d.ts.map +1 -1
  184. package/dist/client/clientFor.js +9 -1
  185. package/dist/client/errors.d.ts +82 -27
  186. package/dist/client/errors.d.ts.map +1 -1
  187. package/dist/client/errors.js +75 -19
  188. package/dist/client/makeClient.d.ts +490 -34
  189. package/dist/client/makeClient.d.ts.map +1 -1
  190. package/dist/client/makeClient.js +62 -28
  191. package/dist/client.d.ts +2 -0
  192. package/dist/client.d.ts.map +1 -1
  193. package/dist/client.js +3 -1
  194. package/dist/faker.d.ts.map +1 -1
  195. package/dist/http/Request.d.ts +1 -1
  196. package/dist/http/Request.d.ts.map +1 -1
  197. package/dist/http/Request.js +2 -2
  198. package/dist/ids.d.ts +42 -14
  199. package/dist/ids.d.ts.map +1 -1
  200. package/dist/ids.js +30 -5
  201. package/dist/index.d.ts +6 -7
  202. package/dist/index.d.ts.map +1 -1
  203. package/dist/index.js +8 -8
  204. package/dist/middleware.d.ts +13 -7
  205. package/dist/middleware.d.ts.map +1 -1
  206. package/dist/middleware.js +14 -8
  207. package/dist/rpc/Invalidation.d.ts +3140 -0
  208. package/dist/rpc/Invalidation.d.ts.map +1 -0
  209. package/dist/rpc/Invalidation.js +178 -0
  210. package/dist/rpc/MiddlewareMaker.d.ts +11 -7
  211. package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
  212. package/dist/rpc/MiddlewareMaker.js +59 -38
  213. package/dist/rpc/RpcContextMap.d.ts +3 -3
  214. package/dist/rpc/RpcContextMap.d.ts.map +1 -1
  215. package/dist/rpc/RpcContextMap.js +4 -4
  216. package/dist/rpc/RpcMiddleware.d.ts +14 -10
  217. package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
  218. package/dist/rpc/RpcMiddleware.js +1 -1
  219. package/dist/rpc.d.ts +1 -1
  220. package/dist/rpc.d.ts.map +1 -1
  221. package/dist/rpc.js +2 -2
  222. package/dist/runtime.d.ts +19 -0
  223. package/dist/runtime.d.ts.map +1 -0
  224. package/dist/runtime.js +40 -0
  225. package/dist/setupRequest.d.ts +20 -0
  226. package/dist/setupRequest.d.ts.map +1 -0
  227. package/dist/setupRequest.js +72 -0
  228. package/dist/toast.d.ts +51 -0
  229. package/dist/toast.d.ts.map +1 -0
  230. package/dist/toast.js +34 -0
  231. package/dist/transform.d.ts +1 -1
  232. package/dist/transform.d.ts.map +1 -1
  233. package/dist/transform.js +4 -5
  234. package/dist/utils/effectify.d.ts +1 -1
  235. package/dist/utils/effectify.d.ts.map +1 -1
  236. package/dist/utils/effectify.js +2 -2
  237. package/dist/utils/extend.d.ts.map +1 -1
  238. package/dist/utils/gen.d.ts +4 -4
  239. package/dist/utils/gen.d.ts.map +1 -1
  240. package/dist/utils/logLevel.d.ts +2 -2
  241. package/dist/utils/logLevel.d.ts.map +1 -1
  242. package/dist/utils/logger.d.ts +4 -3
  243. package/dist/utils/logger.d.ts.map +1 -1
  244. package/dist/utils/logger.js +4 -4
  245. package/dist/utils.d.ts +34 -39
  246. package/dist/utils.d.ts.map +1 -1
  247. package/dist/utils.js +33 -37
  248. package/dist/validation/validators.d.ts.map +1 -1
  249. package/dist/validation.d.ts.map +1 -1
  250. package/dist/withToast.d.ts +30 -0
  251. package/dist/withToast.d.ts.map +1 -0
  252. package/dist/withToast.js +64 -0
  253. package/package.json +27 -251
  254. package/src/Array.ts +5 -5
  255. package/src/Chunk.ts +3 -3
  256. package/src/Config/SecretURL.ts +6 -3
  257. package/src/Config/internal/configSecretURL.ts +2 -2
  258. package/src/Config.ts +7 -0
  259. package/src/ConfigProvider.ts +5 -0
  260. package/src/{ServiceMap.ts → Context.ts} +56 -63
  261. package/src/DataDependencies.ts +114 -0
  262. package/src/Effect.ts +14 -16
  263. package/src/Emailer.ts +51 -0
  264. package/src/Inputify.type.ts +1 -1
  265. package/src/Layer.ts +11 -7
  266. package/src/Model/Repository/Registry.ts +35 -0
  267. package/src/Model/Repository/ext.ts +375 -0
  268. package/src/Model/Repository/internal/internal.ts +755 -0
  269. package/src/Model/Repository/legacy.ts +29 -0
  270. package/src/Model/Repository/makeRepo.ts +145 -0
  271. package/src/Model/Repository/service.ts +676 -0
  272. package/src/Model/Repository/validation.ts +31 -0
  273. package/src/Model/Repository.ts +6 -0
  274. package/src/Model/dsl.ts +129 -0
  275. package/src/Model/filter/filterApi.ts +60 -0
  276. package/src/Model/filter/types/errors.ts +47 -0
  277. package/src/Model/filter/types/fields.ts +50 -0
  278. package/src/Model/filter/types/path/common.ts +404 -0
  279. package/src/Model/filter/types/path/eager.ts +329 -0
  280. package/src/Model/filter/types/path/index.ts +4 -0
  281. package/src/Model/filter/types/utils.ts +128 -0
  282. package/src/Model/filter/types/validator.ts +46 -0
  283. package/src/Model/filter/types.ts +6 -0
  284. package/src/Model/query/dsl.ts +2770 -0
  285. package/src/Model/query/new-kid-interpreter.ts +526 -0
  286. package/src/Model/query.ts +13 -0
  287. package/src/Model.ts +4 -0
  288. package/src/NonEmptySet.ts +6 -4
  289. package/src/Option.ts +2 -0
  290. package/src/Pure.ts +10 -7
  291. package/src/QueueMaker.ts +11 -0
  292. package/src/RequestContext.ts +48 -0
  293. package/src/RequestScopedDependencies.ts +45 -0
  294. package/src/Schema/Class.ts +633 -59
  295. package/src/Schema/SchemaParser.ts +12 -0
  296. package/src/Schema/SpecialJsonSchema.ts +139 -0
  297. package/src/Schema/SpecialOpenApi.ts +130 -0
  298. package/src/Schema/brand.ts +22 -2
  299. package/src/Schema/email.ts +9 -4
  300. package/src/Schema/ext.ts +446 -91
  301. package/src/Schema/moreStrings.ts +147 -68
  302. package/src/Schema/numbers.ts +97 -28
  303. package/src/Schema/phoneNumber.ts +9 -5
  304. package/src/Schema/schema.ts +1 -1
  305. package/src/Schema/strings.ts +23 -14
  306. package/src/Schema.ts +397 -26
  307. package/src/SchemaAST.ts +1 -0
  308. package/src/Set.ts +6 -2
  309. package/src/Store.ts +273 -0
  310. package/src/_ext/Array.ts +4 -2
  311. package/src/_ext/misc.ts +4 -1
  312. package/src/_ext/ord.ext.ts +2 -1
  313. package/src/builtin-json.d.ts +24 -0
  314. package/src/builtin.ts +16 -21
  315. package/src/client/InvalidationKeys.ts +55 -0
  316. package/src/client/apiClientFactory.ts +299 -134
  317. package/src/client/clientFor.ts +107 -34
  318. package/src/client/errors.ts +100 -29
  319. package/src/client/makeClient.ts +594 -78
  320. package/src/client.ts +6 -4
  321. package/src/http/Request.ts +3 -3
  322. package/src/http.ts +1 -1
  323. package/src/ids.ts +33 -6
  324. package/src/index.ts +20 -23
  325. package/src/middleware.ts +13 -9
  326. package/src/rpc/Invalidation.ts +278 -0
  327. package/src/rpc/MiddlewareMaker.ts +88 -80
  328. package/src/rpc/README.md +2 -2
  329. package/src/rpc/RpcContextMap.ts +7 -6
  330. package/src/rpc/RpcMiddleware.ts +19 -13
  331. package/src/rpc.ts +4 -4
  332. package/src/runtime.ts +56 -0
  333. package/src/setupRequest.ts +138 -0
  334. package/src/toast.ts +54 -0
  335. package/src/transform.ts +4 -4
  336. package/src/utils/effectify.ts +1 -1
  337. package/src/utils/gen.ts +8 -8
  338. package/src/utils/logLevel.ts +1 -1
  339. package/src/utils/logger.ts +4 -3
  340. package/src/utils.ts +85 -158
  341. package/src/validation.ts +2 -2
  342. package/src/withToast.ts +133 -0
  343. package/test/dist/rpc.test.d.ts.map +1 -1
  344. package/test/dist/rpcMiddlewareContext.test.d.ts.map +1 -0
  345. package/test/dist/secretURL.test.d.ts.map +1 -0
  346. package/test/dist/special.test.d.ts.map +1 -0
  347. package/test/moreStrings.test.ts +1 -1
  348. package/test/rpc.test.ts +42 -13
  349. package/test/rpcMiddlewareContext.test.ts +63 -0
  350. package/test/schema.test.ts +459 -30
  351. package/test/secretURL.test.ts +160 -0
  352. package/test/special.test.ts +1258 -0
  353. package/test/utils.test.ts +7 -7
  354. package/tsconfig.base.json +6 -5
  355. package/tsconfig.json +3 -1
  356. package/tsconfig.src.json +29 -29
  357. package/tsconfig.test.json +2 -2
  358. package/dist/Operations.d.ts +0 -123
  359. package/dist/Operations.d.ts.map +0 -1
  360. package/dist/Operations.js +0 -29
  361. package/dist/ServiceMap.d.ts.map +0 -1
  362. package/dist/ServiceMap.js +0 -91
  363. package/eslint.config.mjs +0 -26
  364. package/src/Operations.ts +0 -55
  365. package/tsconfig.json.bak +0 -12
@@ -0,0 +1,755 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
3
+ import * as Equivalence from "effect/Equivalence"
4
+ import { flow, pipe } from "effect/Function"
5
+ import * as HashMap from "effect/HashMap"
6
+ import * as HashSet from "effect/HashSet"
7
+ import * as Pipeable from "effect/Pipeable"
8
+ import * as Ref from "effect/Ref"
9
+ import * as Result from "effect/Result"
10
+ import type * as Scope from "effect/Scope"
11
+ import * as Unify from "effect/Unify"
12
+ import * as Array from "../../../Array.ts"
13
+ import type { NonEmptyReadonlyArray } from "../../../Array.ts"
14
+ import { toNonEmptyArray } from "../../../Array.ts"
15
+ import * as Chunk from "../../../Chunk.ts"
16
+ import { NotFoundError } from "../../../client/errors.ts"
17
+ import * as Context from "../../../Context.ts"
18
+ import * as DataDependencies from "../../../DataDependencies.ts"
19
+ import * as Effect from "../../../Effect.ts"
20
+ import { flatMapOption } from "../../../Effect.ts"
21
+ import * as Option from "../../../Option.ts"
22
+ import * as S from "../../../Schema.ts"
23
+ import { type Codec, NonNegativeInt } from "../../../Schema.ts"
24
+ import * as SchemaAST from "../../../SchemaAST.ts"
25
+ import { setupRequestContextFromCurrent } from "../../../setupRequest.ts"
26
+ import { type FilterArgs, getContextMap, type PersistenceModelType, type StoreConfig, storeId, StoreMaker } from "../../../Store.ts"
27
+ import type { FieldValues } from "../../filter/types.ts"
28
+ import * as Q from "../../query.ts"
29
+ import type { ChangeFeed, ChangeFeedEvent, Repository } from "../service.ts"
30
+ import { ValidationError, ValidationResult } from "../validation.ts"
31
+
32
+ const dedupe = Array.dedupeWith(Equivalence.String)
33
+
34
+ /**
35
+ * A base implementation to create a repository.
36
+ */
37
+ export function makeRepoInternal<
38
+ Evt = never
39
+ >() {
40
+ return <
41
+ ItemType extends string,
42
+ R,
43
+ Encoded extends FieldValues,
44
+ T,
45
+ IdKey extends keyof T & keyof Encoded
46
+ >(
47
+ name: ItemType,
48
+ schema: S.Codec<T, Encoded, R>,
49
+ mapFrom: (pm: Encoded) => Encoded,
50
+ mapTo: (e: Encoded, etag: string | undefined) => PersistenceModelType<Encoded>,
51
+ idKey: IdKey
52
+ ) => {
53
+ type PM = PersistenceModelType<Encoded>
54
+ function mapToPersistenceModel(
55
+ e: Encoded,
56
+ getEtag: (id: string) => string | undefined
57
+ ): PM {
58
+ return mapTo(e, getEtag(e[idKey]))
59
+ }
60
+
61
+ function mapReverse(
62
+ { _etag, ...e }: PM,
63
+ setEtag: (id: string, eTag: string | undefined) => void
64
+ ): Encoded {
65
+ setEtag((e as any)[idKey], _etag)
66
+ return mapFrom(e as unknown as Encoded)
67
+ }
68
+
69
+ const mkStore = makeStore<Encoded>()(name, schema, mapTo, idKey)
70
+
71
+ function make<RInitial = never, E = never, RPublish = never, RCtx = never>(
72
+ args: [Evt] extends [never] ? {
73
+ schemaContext?: Context.Context<RCtx>
74
+ makeInitial?: Effect.Effect<readonly T[], E, RInitial> | undefined
75
+ config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
76
+ partitionValue?: (e?: Encoded) => string
77
+ }
78
+ }
79
+ : {
80
+ schemaContext?: Context.Context<RCtx>
81
+ publishEvents: (evt: NonEmptyReadonlyArray<Evt>) => Effect.Effect<void, never, RPublish>
82
+ makeInitial?: Effect.Effect<readonly T[], E, RInitial> | undefined
83
+ config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
84
+ partitionValue?: (e?: Encoded) => string
85
+ }
86
+ }
87
+ ) {
88
+ return Effect
89
+ .gen(function*() {
90
+ const rctx: Context.Context<RCtx> = args.schemaContext ?? Context.empty() as any
91
+ const provideRctx = Effect.provide(rctx)
92
+ const encodeMany = flow(
93
+ S.encodeEffect(S.Array(schema)),
94
+ provideRctx,
95
+ Effect.withSpan("encodeMany", { attributes: { "app.entity": name } }, { captureStackTrace: false })
96
+ )
97
+ const decode = flow(S.decodeEffectConcurrently(schema), provideRctx)
98
+ const decodeMany = flow(
99
+ S.decodeEffectConcurrently(S.Array(schema)),
100
+ provideRctx
101
+ )
102
+
103
+ const store = yield* mkStore(args.makeInitial, args.config)
104
+ const repoDependency = DataDependencies.repo(name)
105
+ const recordRead = DataDependencies.read(repoDependency)
106
+ const recordWrite = DataDependencies.write(repoDependency)
107
+ const cms = Effect.map(getContextMap.pipe(Effect.orDie), (_) => ({
108
+ get: (id: string) => _.get(`${name}.${id}`),
109
+ set: (id: string, etag: string | undefined) => _.set(`${name}.${id}`, etag)
110
+ }))
111
+
112
+ const pub = "publishEvents" in args
113
+ ? args.publishEvents
114
+ : () => Effect.void
115
+
116
+ type ChangeFeedHandler = (evt: ChangeFeedEvent<T>) => Effect.Effect<void>
117
+ const changeFeedByNamespace = yield* Ref.make(HashMap.empty<string, HashSet.HashSet<ChangeFeedHandler>>())
118
+ const changeFeedWildcard = yield* Ref.make(HashSet.empty<ChangeFeedHandler>())
119
+ // clear all handlers when the repository's scope closes
120
+ yield* Effect.addFinalizer(() =>
121
+ Effect.all([
122
+ Ref.set(changeFeedByNamespace, HashMap.empty<string, HashSet.HashSet<ChangeFeedHandler>>()),
123
+ Ref.set(changeFeedWildcard, HashSet.empty<ChangeFeedHandler>())
124
+ ], { discard: true })
125
+ )
126
+ const changeFeed: ChangeFeed<T> = {
127
+ publish: ([items, op]) =>
128
+ Effect.gen(function*() {
129
+ const ns = yield* storeId
130
+ const evt: ChangeFeedEvent<T> = [items, op, ns]
131
+ const map = yield* Ref.get(changeFeedByNamespace)
132
+ const wild = yield* Ref.get(changeFeedWildcard)
133
+ const targeted = Option.getOrElse(HashMap.get(map, ns), () => HashSet.empty<ChangeFeedHandler>())
134
+ const all = HashSet.union(targeted, wild)
135
+ yield* Effect.forEach(all, (h) => h(evt), { concurrency: "unbounded", discard: true })
136
+ }),
137
+ subscribe: (handler, options) => {
138
+ const ns = options?.namespace
139
+ if (ns === undefined) {
140
+ return Effect.acquireRelease(
141
+ Ref.update(changeFeedWildcard, HashSet.add(handler)),
142
+ () => Ref.update(changeFeedWildcard, HashSet.remove(handler))
143
+ )
144
+ }
145
+ return Effect.acquireRelease(
146
+ Ref.update(changeFeedByNamespace, (m) => {
147
+ const cur = Option.getOrElse(HashMap.get(m, ns), () => HashSet.empty<ChangeFeedHandler>())
148
+ return HashMap.set(m, ns, HashSet.add(cur, handler))
149
+ }),
150
+ () =>
151
+ Ref.update(changeFeedByNamespace, (m) => {
152
+ const cur = Option.getOrElse(HashMap.get(m, ns), () => HashSet.empty<ChangeFeedHandler>())
153
+ const next = HashSet.remove(cur, handler)
154
+ return HashSet.size(next) === 0 ? HashMap.remove(m, ns) : HashMap.set(m, ns, next)
155
+ })
156
+ )
157
+ }
158
+ }
159
+
160
+ const allE = cms
161
+ .pipe(Effect.flatMap((cm) => Effect.map(store.all, (_) => _.map((_) => mapReverse(_, cm.set)))))
162
+
163
+ const all = Effect
164
+ .flatMap(
165
+ allE,
166
+ (_) => decodeMany(_).pipe(Effect.orDie)
167
+ )
168
+ .pipe(
169
+ Effect.tap(() => recordRead),
170
+ Effect.map((_) => _ as T[]),
171
+ Effect.withSpan("Repository.all", {
172
+ kind: "client",
173
+ attributes: { "app.entity": name }
174
+ }, { captureStackTrace: false })
175
+ )
176
+
177
+ const fieldsSchema = schema as unknown as { fields: any }
178
+ // assumes the id field never needs a service...
179
+ const i = ("fields" in fieldsSchema ? S.Struct(fieldsSchema["fields"]) as unknown as typeof schema : schema)
180
+ .pipe((_) => {
181
+ let ast = _.ast
182
+ if (ast._tag === "Declaration") ast = ast.typeParameters[0]!
183
+
184
+ const pickIdFromAst = (a: SchemaAST.AST) => {
185
+ // Unwrap Declaration (e.g. TaggedClass) to get the underlying Objects AST
186
+ let inner = a
187
+ if (inner._tag === "Declaration") inner = inner.typeParameters[0]!
188
+ // Pick from the original AST to preserve the full encoding chain (e.g. decodeTo transformations).
189
+ // Using toEncoded would lose transformation info needed to encode Type -> Encoded.
190
+ if (SchemaAST.isObjects(inner)) {
191
+ const field = inner.propertySignatures.find((_) => _.name === idKey)
192
+ if (field) {
193
+ return S.Struct({ [idKey]: S.make(field.type) }) as unknown as Codec<T, Encoded>
194
+ }
195
+ }
196
+ return S.make(a) as unknown as Codec<T, Encoded>
197
+ }
198
+
199
+ return ast._tag === "Union"
200
+ // we need to get the Objects (TypeLiteral), in case of class it has encoding chain...
201
+ ? S.Union(
202
+ ast.types.map((_) => pickIdFromAst(_))
203
+ )
204
+ : pickIdFromAst(ast)
205
+ })
206
+ const encodeId = flow(S.encodeEffect(i), provideRctx)
207
+ const encodeIdOnly = (id: string) =>
208
+ encodeId({ [idKey]: id } as any).pipe(
209
+ Effect.map((_: Record<string, unknown>) => _[idKey as string] as Encoded[IdKey])
210
+ )
211
+ const findEId = Effect.fnUntraced(function*(id: Encoded[IdKey]) {
212
+ yield* Effect.annotateCurrentSpan({ "app.entity.id": id })
213
+
214
+ return yield* Effect.flatMap(
215
+ store.find(id),
216
+ (item) =>
217
+ Effect.gen(function*() {
218
+ const { set } = yield* cms
219
+ return item.pipe(Option.map((_) => mapReverse(_, set)))
220
+ })
221
+ )
222
+ })
223
+ // TODO: select the particular field, instead of as struct
224
+ const findE = Effect.fnUntraced(function*(id: T[IdKey]) {
225
+ yield* Effect.annotateCurrentSpan({ "app.entity.id": id })
226
+
227
+ return yield* pipe(
228
+ encodeId({ [idKey]: id } as any),
229
+ Effect.orDie,
230
+ Effect.map((_) => (_ as any)[idKey]),
231
+ Effect.flatMap(findEId)
232
+ )
233
+ })
234
+
235
+ const find = Effect.fn("Repository.find", {
236
+ kind: "client",
237
+ attributes: { "app.entity": name }
238
+ })(function*(id: T[IdKey]) {
239
+ yield* recordRead
240
+ yield* Effect.annotateCurrentSpan({ "app.entity.id": id })
241
+ return yield* flatMapOption(findE(id), (_) => Effect.orDie(decode(_)))
242
+ })
243
+
244
+ const saveAllE = (a: Iterable<Encoded>) =>
245
+ flatMapOption(
246
+ Effect
247
+ .sync(() => toNonEmptyArray([...a])),
248
+ (a) =>
249
+ Effect.gen(function*() {
250
+ const { get, set } = yield* cms
251
+ const items = a.map((_) => mapToPersistenceModel(_, get))
252
+ const ret = yield* store.batchSet(items)
253
+ ret.forEach((_) => set(_[idKey], _._etag))
254
+ })
255
+ )
256
+ .pipe(Effect.asVoid)
257
+
258
+ const saveAll = (a: Iterable<T>) =>
259
+ encodeMany(Array.fromIterable(a))
260
+ .pipe(
261
+ Effect.orDie,
262
+ Effect.andThen(saveAllE)
263
+ )
264
+
265
+ const saveAndPublish = Effect.fn("Repository.saveAndPublish", { attributes: { "app.entity": name } })(
266
+ function*(items: Iterable<T>, events: Iterable<Evt> = []) {
267
+ yield* recordWrite
268
+ const it = Chunk.fromIterable(items)
269
+ const evts = [...events]
270
+ yield* Effect.annotateCurrentSpan({
271
+ "app.entity.ids": Chunk.map(it, (_) => _[idKey]),
272
+ "app.event.count": evts.length
273
+ })
274
+ return yield* saveAll(it)
275
+ .pipe(
276
+ Effect.andThen(Effect.sync(() => toNonEmptyArray(evts))),
277
+ // TODO: for full consistency the events should be stored within the same database transaction, and then picked up.
278
+ (_) => flatMapOption(_, pub),
279
+ Effect.andThen(changeFeed.publish([Chunk.toArray(it), "save"] as [T[], "save" | "remove"])),
280
+ Effect.asVoid
281
+ )
282
+ }
283
+ )
284
+
285
+ const removeAndPublish = Effect.fn("Repository.removeAndPublish", { attributes: { "app.entity": name } })(
286
+ function*(a: Iterable<T>, events: Iterable<Evt> = []) {
287
+ yield* recordWrite
288
+ const { set } = yield* cms
289
+ const it = [...a]
290
+ const evts = [...events]
291
+ yield* Effect.annotateCurrentSpan({
292
+ "app.entity.ids": it.map((_) => _[idKey]),
293
+ "app.event.count": evts.length
294
+ })
295
+ const items = yield* encodeMany(it).pipe(Effect.orDie)
296
+ if (Array.isReadonlyArrayNonEmpty(items)) {
297
+ yield* store.batchRemove(
298
+ items.map((_) => (_[idKey])),
299
+ args.config?.partitionValue?.(items[0])
300
+ )
301
+ for (const e of items) {
302
+ set(e[idKey], undefined)
303
+ }
304
+ yield* Effect
305
+ .sync(() => toNonEmptyArray(evts))
306
+ // TODO: for full consistency the events should be stored within the same database transaction, and then picked up.
307
+ .pipe((_) => flatMapOption(_, pub))
308
+
309
+ yield* changeFeed.publish([it, "remove"] as [T[], "save" | "remove"])
310
+ }
311
+ }
312
+ )
313
+
314
+ const removeById = Effect.fn("Repository.removeById", { attributes: { "app.entity": name } })(
315
+ function*(idOrIds: T[IdKey] | ReadonlyArray<T[IdKey]>) {
316
+ yield* recordWrite
317
+ const ids = globalThis.Array.isArray(idOrIds)
318
+ ? idOrIds as readonly T[IdKey][]
319
+ : [idOrIds as T[IdKey]]
320
+ if (!Array.isReadonlyArrayNonEmpty(ids)) {
321
+ return
322
+ }
323
+ const { set } = yield* cms
324
+ const eids = yield* Effect.forEach(ids, (_) => encodeIdOnly(_ as any)).pipe(Effect.orDie)
325
+ yield* Effect.annotateCurrentSpan({ "app.entity.ids": eids })
326
+ yield* store.batchRemove(eids)
327
+ for (const id of eids) {
328
+ set(id, undefined)
329
+ }
330
+ yield* changeFeed.publish([[], "remove"] as [T[], "save" | "remove"])
331
+ }
332
+ )
333
+
334
+ const parseMany = Effect.fn("parseMany", {
335
+ attributes: { "app.entity": name, "app.query.mode": "transform" }
336
+ })(
337
+ function*(items: readonly PM[]) {
338
+ const cm = yield* cms
339
+ return yield* decodeMany(items.map((_) => mapReverse(_, cm.set))).pipe(Effect.orDie)
340
+ }
341
+ )
342
+ const decodeManyCache = new WeakMap<
343
+ S.Codec<any, any, any>,
344
+ (i: readonly any[]) => Effect.Effect<any, any, any>
345
+ >()
346
+ const getDecodeMany = (s: S.Codec<any, Encoded, any>) => {
347
+ let dec = decodeManyCache.get(s)
348
+ if (!dec) {
349
+ dec = S.decodeEffectConcurrently(S.Array(s))
350
+ decodeManyCache.set(s, dec)
351
+ }
352
+ return dec
353
+ }
354
+ const parseMany2 = Effect.fn("parseMany", {
355
+ attributes: { "app.entity": name, "app.query.mode": "transform" }
356
+ })(
357
+ function*<A, R>(items: readonly PM[], schema: S.Codec<A, Encoded, R>) {
358
+ const cm = yield* cms
359
+ return yield* getDecodeMany(schema)(items.map((_) => mapReverse(_, cm.set))).pipe(Effect.orDie)
360
+ }
361
+ )
362
+ const filter = <U extends keyof Encoded = keyof Encoded>(args: FilterArgs<Encoded, U>) =>
363
+ store
364
+ .filter(
365
+ // always enforce id and _etag because they are system fields, required for etag tracking etc
366
+ {
367
+ ...args,
368
+ select: args.select
369
+ ? dedupe([...args.select, idKey, "_etag" as any])
370
+ : undefined
371
+ } as typeof args
372
+ )
373
+ .pipe(
374
+ Effect.tap((items) =>
375
+ Effect.map(cms, ({ set }) => items.forEach((_) => set((_ as Encoded)[idKey], (_ as PM)._etag)))
376
+ )
377
+ )
378
+
379
+ // TODO: For raw we should use S.from, and drop the R...
380
+ const query: {
381
+ <A, R, From extends FieldValues>(
382
+ q: Q.QueryProjection<Encoded extends From ? From : never, A, R>
383
+ ): Effect.Effect<readonly A[], S.SchemaError, Exclude<R, RCtx>>
384
+ <A, R, EncodedRefined extends Encoded = Encoded>(
385
+ q: Q.QAll<NoInfer<Encoded>, NoInfer<EncodedRefined>, A, R>
386
+ ): Effect.Effect<readonly A[], never, Exclude<R, RCtx>>
387
+ } = (<A, R, EncodedRefined extends Encoded = Encoded>(q: Q.QAll<Encoded, EncodedRefined, A, R>) => {
388
+ const a = Q.toFilter(q, schema)
389
+ // Mode dispatch — see `Q.project` JSDoc for the contract:
390
+ // aggregate: GROUP BY + aggregate functions at DB level; decode raw rows with schema; SchemaError surfaces.
391
+ // project : decode raw encoded rows with schema; no PM reverse-mapping; SchemaError surfaces.
392
+ // collect : same as project, but schema yields Option and None rows are dropped.
393
+ // transform: PM reverse-map (re-inject _etag/PM state from cms cache) then decode; orDie.
394
+ const eff = a.mode === "aggregate"
395
+ ? store
396
+ // `a.select` contains `{ key, aggregate }` items not expressible in FilterFunc<Encoded, U>'s
397
+ // `U extends keyof Encoded` generic. Cast is unavoidable until FilterFunc supports aggregate mode.
398
+ .filter(a as any)
399
+ // Decode raw aggregate rows directly — no PM reverse-mapping, no id/_etag needed.
400
+ .pipe(
401
+ Effect.andThen(
402
+ flow(
403
+ S.decodeEffectConcurrently(S.Array(a.schema ?? schema)),
404
+ provideRctx,
405
+ Effect.withSpan("parseMany", {
406
+ attributes: { "app.entity": name, "app.query.mode": "aggregate" }
407
+ })
408
+ )
409
+ )
410
+ )
411
+ : a.mode === "project"
412
+ ? filter(a)
413
+ // TODO: mapFrom but need to support per field and dependencies
414
+ .pipe(
415
+ Effect.andThen(
416
+ flow(
417
+ S.decodeEffectConcurrently(S.Array(a.schema ?? schema)),
418
+ provideRctx,
419
+ Effect.withSpan("parseMany", {
420
+ attributes: { "app.entity": name, "app.query.mode": "project" }
421
+ })
422
+ )
423
+ )
424
+ )
425
+ : a.mode === "collect"
426
+ ? filter(a)
427
+ // TODO: mapFrom but need to support per field and dependencies
428
+ .pipe(
429
+ Effect.flatMap(flow(
430
+ S.decodeEffectConcurrently(S.Array(a.schema)),
431
+ Effect.map(Array.getSomes),
432
+ provideRctx,
433
+ Effect.withSpan("parseMany", {
434
+ attributes: { "app.entity": name, "app.query.mode": "collect" }
435
+ })
436
+ ))
437
+ )
438
+ : Effect.flatMap(
439
+ filter(a),
440
+ (_) =>
441
+ Unify.unify(
442
+ a.schema
443
+ // TODO: partial may not match?
444
+ ? parseMany2(_ as any, a.schema as any)
445
+ : parseMany(_ as any)
446
+ )
447
+ )
448
+ return pipe(
449
+ a.ttype === "one"
450
+ ? Effect.flatMap(
451
+ eff,
452
+ flow(
453
+ Array.head,
454
+ Option.match({
455
+ onNone: () => Effect.fail(new NotFoundError({ id: "query", /* TODO */ type: name })),
456
+ onSome: Effect.succeed
457
+ })
458
+ )
459
+ )
460
+ : a.ttype === "count"
461
+ ? Effect
462
+ .map(eff, (_) => NonNegativeInt(_.length))
463
+ .pipe(Effect.catchTag("SchemaError", (e) => Effect.die(e)))
464
+ : eff,
465
+ Effect.tap((r) =>
466
+ Effect.annotateCurrentSpan({
467
+ "app.query.ttype": a.ttype,
468
+ "app.query.mode": a.mode,
469
+ "db.response.returned_rows": Array.isArray(r) ? r.length : 1
470
+ })
471
+ ),
472
+ Effect.tap(() => recordRead),
473
+ Effect.withSpan("Repository.query", {
474
+ kind: "client",
475
+ attributes: { "app.entity": name }
476
+ }, { captureStackTrace: false })
477
+ )
478
+ }) as any
479
+
480
+ const validateSample = Effect.fn("Repository.validateSample", { attributes: { "app.entity": name } })(
481
+ function*(options?: {
482
+ percentage?: number
483
+ maxItems?: number
484
+ }) {
485
+ const percentage = options?.percentage ?? 0.1 // default 10%
486
+ const maxItems = options?.maxItems
487
+
488
+ // 1. get all IDs with projection (bypasses main schema decode)
489
+ const allIds = yield* store
490
+ .filter({
491
+ t: null as unknown as Encoded,
492
+ select: [idKey as keyof Encoded]
493
+ })
494
+ .pipe(Effect.withSpan("Repository.filter", {
495
+ kind: "client",
496
+ attributes: { "app.entity": name }
497
+ }, { captureStackTrace: false }))
498
+
499
+ // 2. random subset
500
+ const shuffled = [...allIds].sort(() => Math.random() - 0.5)
501
+ const sampleSize = Math.min(
502
+ maxItems ?? Infinity,
503
+ Math.ceil(allIds.length * percentage)
504
+ )
505
+ const sample = shuffled.slice(0, sampleSize)
506
+
507
+ // 3. validate each item
508
+ const errors: ValidationError[] = []
509
+
510
+ for (const item of sample) {
511
+ const id = item[idKey]
512
+ const rawResult = yield* store.find(id).pipe(
513
+ Effect.withSpan("Repository.find", {
514
+ kind: "client",
515
+ attributes: { "app.entity": name, "app.entity.id": id }
516
+ }, { captureStackTrace: false })
517
+ )
518
+
519
+ if (Option.isNone(rawResult)) continue
520
+
521
+ const rawData = rawResult.value as Encoded
522
+ const jitMResult = mapFrom(rawData) // apply jitM
523
+
524
+ const decodeResult = yield* S.decodeEffectConcurrently(schema)(jitMResult).pipe(
525
+ Effect.result,
526
+ provideRctx
527
+ )
528
+
529
+ if (Result.isFailure(decodeResult)) {
530
+ errors.push(
531
+ ValidationError.make({
532
+ id,
533
+ rawData,
534
+ jitMResult,
535
+ error: decodeResult.failure
536
+ })
537
+ )
538
+ }
539
+ }
540
+
541
+ return ValidationResult.make({
542
+ total: NonNegativeInt(allIds.length),
543
+ sampled: NonNegativeInt(sample.length),
544
+ valid: NonNegativeInt(sample.length - errors.length),
545
+ errors
546
+ })
547
+ }
548
+ )
549
+
550
+ const r = {
551
+ changeFeed,
552
+ itemType: name,
553
+ idKey,
554
+ find,
555
+ all,
556
+ saveAndPublish,
557
+ removeAndPublish,
558
+ removeById,
559
+ seedNamespace: (namespace: string) => store.seedNamespace(namespace),
560
+ validateSample,
561
+ queryRaw<A, Out, QR>(schema: S.Codec<A, Out, QR>, q: Q.RawQuery<Encoded, Out>) {
562
+ const dec = S.decodeEffectConcurrently(S.Array(schema))
563
+ return store.queryRaw(q).pipe(
564
+ Effect.flatMap(dec),
565
+ Effect.tap(() => recordRead),
566
+ Effect.withSpan("Repository.queryRaw", {
567
+ kind: "client",
568
+ attributes: { "app.entity": name }
569
+ }, { captureStackTrace: false })
570
+ )
571
+ },
572
+ query(q: any) {
573
+ // eslint-disable-next-line prefer-rest-params
574
+ return query(typeof q === "function" ? Pipeable.pipeArguments(Q.make(), arguments) : q) as any
575
+ },
576
+ /**
577
+ * @internal
578
+ */
579
+ mapped: <A, R>(schema: S.Codec<A, any, R>) => {
580
+ const dec = S.decodeEffectConcurrently(schema)
581
+ const encMany = S.encodeEffect(S.Array(schema))
582
+ const decMany = S.decodeEffectConcurrently(S.Array(schema))
583
+ const spanAttrs = { kind: "client" as const, attributes: { "app.entity": name } }
584
+ return {
585
+ all: allE.pipe(
586
+ Effect.flatMap(decMany),
587
+ Effect.tap(() => recordRead),
588
+ Effect.map((_) => _ as any[]),
589
+ Effect.withSpan("Repository.mapped.all", spanAttrs, { captureStackTrace: false })
590
+ ),
591
+ find: (id: T[IdKey]) =>
592
+ flatMapOption(findE(id), dec).pipe(
593
+ Effect.tap(() => recordRead),
594
+ Effect.withSpan("Repository.mapped.find", {
595
+ ...spanAttrs,
596
+ attributes: { ...spanAttrs.attributes, "app.entity.id": id }
597
+ }, { captureStackTrace: false })
598
+ ),
599
+ // query: (q: any) => {
600
+ // const a = Q.toFilter(q)
601
+
602
+ // return filter(a)
603
+ // .pipe(
604
+ // Effect.flatMap(decMany),
605
+ // Effect.map((_) => _ as any[]),
606
+ // Effect.withSpan("Repository.mapped.query [effect-app/infra]", {
607
+ // captureStackTrace: false,
608
+ // attributes: {
609
+ // "repository.model_name": name,
610
+ // query: { ...a, schema: a.schema ? "__SCHEMA__" : a.schema, filter: a.filter.build() }
611
+ // }
612
+ // })
613
+ // )
614
+ // },
615
+ save: (...xes: any[]) =>
616
+ Effect.flatMap(encMany(xes), (_) => saveAllE(_)).pipe(
617
+ Effect.tap(() => recordWrite),
618
+ Effect.withSpan("Repository.mapped.save", spanAttrs, { captureStackTrace: false })
619
+ )
620
+ }
621
+ }
622
+ }
623
+ return r as Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<R, RCtx>, RPublish, RCtx>
624
+ })
625
+ .pipe(Effect
626
+ // .withSpan("Repository.make [effect-app/infra]", { attributes: { "repository.model_name": name } })
627
+ .withLogSpan("Repository.make: " + name))
628
+ }
629
+
630
+ return {
631
+ make,
632
+ Q: Q.make<Encoded>()
633
+ }
634
+ }
635
+ }
636
+
637
+ const pluralize = (s: string) =>
638
+ s.endsWith("s")
639
+ ? s + "es"
640
+ : s.endsWith("y")
641
+ ? s.substring(0, s.length - 1) + "ies"
642
+ : s + "s"
643
+
644
+ export function makeStore<Encoded extends FieldValues>() {
645
+ return <
646
+ ItemType extends string,
647
+ R,
648
+ E,
649
+ T,
650
+ IdKey extends keyof Encoded
651
+ >(
652
+ name: ItemType,
653
+ schema: S.Codec<T, E, R>,
654
+ mapTo: (e: E, etag: string | undefined) => Encoded,
655
+ idKey: IdKey
656
+ ) => {
657
+ function makeStore<RInitial = never, EInitial = never>(
658
+ makeInitial?: Effect.Effect<readonly T[], EInitial, RInitial>,
659
+ config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
660
+ partitionValue?: (e?: Encoded) => string
661
+ }
662
+ ) {
663
+ function encodeToEncoded() {
664
+ const getEtag = () => undefined
665
+ return (t: T) =>
666
+ S.encodeEffect(schema)(t).pipe(
667
+ Effect.orDie,
668
+ Effect.map((_) => mapToPersistenceModel(_, getEtag))
669
+ )
670
+ }
671
+
672
+ function mapToPersistenceModel(
673
+ e: E,
674
+ getEtag: (id: string) => string | undefined
675
+ ): Encoded {
676
+ return mapTo(e, getEtag((e as any)[idKey] as string))
677
+ }
678
+
679
+ return Effect.gen(function*() {
680
+ const { make } = yield* StoreMaker
681
+
682
+ const store = yield* make<IdKey, Encoded, RInitial | R, EInitial>(
683
+ pluralize(name),
684
+ idKey,
685
+ makeInitial
686
+ ? makeInitial
687
+ .pipe(
688
+ Effect.flatMap(Effect.forEach(encodeToEncoded())),
689
+ setupRequestContextFromCurrent("Repository.makeInitial [effect-app/infra]", {
690
+ attributes: { "app.entity": name }
691
+ })
692
+ )
693
+ : undefined,
694
+ {
695
+ ...config,
696
+ partitionValue: config?.partitionValue
697
+ ?? ((_) => "primary") /*(isIntegrationEvent(r) ? r.companyId : r.id*/
698
+ }
699
+ )
700
+
701
+ return store
702
+ })
703
+ }
704
+
705
+ return makeStore
706
+ }
707
+ }
708
+
709
+ export interface Repos<
710
+ T,
711
+ Encoded extends { id: string },
712
+ RSchema,
713
+ Evt,
714
+ ItemType extends string,
715
+ IdKey extends keyof T,
716
+ RPublish
717
+ > {
718
+ make<RInitial = never, E = never, R2 = never>(
719
+ args: [Evt] extends [never] ? {
720
+ makeInitial?: Effect.Effect<readonly T[], E, RInitial> | undefined
721
+ config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
722
+ partitionValue?: (e?: Encoded) => string
723
+ }
724
+ }
725
+ : {
726
+ publishEvents: (evt: NonEmptyReadonlyArray<Evt>) => Effect.Effect<void, never, R2>
727
+ makeInitial?: Effect.Effect<readonly T[], E, RInitial> | undefined
728
+ config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
729
+ partitionValue?: (e?: Encoded) => string
730
+ }
731
+ }
732
+ ): Effect.Effect<
733
+ Repository<T, Encoded, Evt, ItemType, IdKey, RSchema, RPublish>,
734
+ E,
735
+ StoreMaker | RInitial | R2 | Scope.Scope
736
+ >
737
+ makeWith<Out, RInitial = never, E = never, R2 = never>(
738
+ args: [Evt] extends [never] ? {
739
+ makeInitial?: Effect.Effect<readonly T[], E, RInitial> | undefined
740
+ config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
741
+ partitionValue?: (e?: Encoded) => string
742
+ }
743
+ }
744
+ : {
745
+ publishEvents: (evt: NonEmptyReadonlyArray<Evt>) => Effect.Effect<void, never, R2>
746
+ makeInitial?: Effect.Effect<readonly T[], E, RInitial> | undefined
747
+ config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
748
+ partitionValue?: (e?: Encoded) => string
749
+ }
750
+ },
751
+ f: (r: Repository<T, Encoded, Evt, ItemType, IdKey, RSchema, RPublish>) => Out
752
+ ): Effect.Effect<Out, E, StoreMaker | RInitial | R2 | Scope.Scope>
753
+ readonly Q: ReturnType<typeof Q.make<Encoded>>
754
+ readonly type: Repository<T, Encoded, Evt, ItemType, IdKey, RSchema, RPublish>
755
+ }