effect-app 4.0.0-beta.27 → 4.0.0-beta.271

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