@effect-app/infra 4.0.0-beta.15 → 4.0.0-beta.151

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 (291) hide show
  1. package/CHANGELOG.md +992 -0
  2. package/dist/CUPS.d.ts +15 -7
  3. package/dist/CUPS.d.ts.map +1 -1
  4. package/dist/CUPS.js +10 -12
  5. package/dist/Emailer/Sendgrid.d.ts +14 -14
  6. package/dist/Emailer/Sendgrid.d.ts.map +1 -1
  7. package/dist/Emailer/Sendgrid.js +16 -15
  8. package/dist/Emailer/fake.d.ts +1 -1
  9. package/dist/Emailer/service.d.ts +9 -3
  10. package/dist/Emailer/service.d.ts.map +1 -1
  11. package/dist/Emailer/service.js +3 -3
  12. package/dist/Emailer.d.ts +1 -1
  13. package/dist/MainFiberSet.d.ts +5 -5
  14. package/dist/MainFiberSet.d.ts.map +1 -1
  15. package/dist/MainFiberSet.js +3 -3
  16. package/dist/Model/Repository/Registry.d.ts +20 -0
  17. package/dist/Model/Repository/Registry.d.ts.map +1 -0
  18. package/dist/Model/Repository/Registry.js +17 -0
  19. package/dist/Model/Repository/ext.d.ts +33 -15
  20. package/dist/Model/Repository/ext.d.ts.map +1 -1
  21. package/dist/Model/Repository/ext.js +54 -2
  22. package/dist/Model/Repository/internal/internal.d.ts +6 -6
  23. package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
  24. package/dist/Model/Repository/internal/internal.js +35 -24
  25. package/dist/Model/Repository/legacy.d.ts +1 -1
  26. package/dist/Model/Repository/makeRepo.d.ts +7 -6
  27. package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
  28. package/dist/Model/Repository/makeRepo.js +5 -1
  29. package/dist/Model/Repository/service.d.ts +28 -23
  30. package/dist/Model/Repository/service.d.ts.map +1 -1
  31. package/dist/Model/Repository/validation.d.ts +142 -17
  32. package/dist/Model/Repository/validation.d.ts.map +1 -1
  33. package/dist/Model/Repository/validation.js +5 -5
  34. package/dist/Model/Repository.d.ts +2 -1
  35. package/dist/Model/Repository.d.ts.map +1 -1
  36. package/dist/Model/Repository.js +2 -1
  37. package/dist/Model/dsl.d.ts +4 -4
  38. package/dist/Model/dsl.d.ts.map +1 -1
  39. package/dist/Model/filter/filterApi.d.ts +5 -5
  40. package/dist/Model/filter/filterApi.d.ts.map +1 -1
  41. package/dist/Model/filter/types/errors.d.ts +1 -1
  42. package/dist/Model/filter/types/fields.d.ts +1 -1
  43. package/dist/Model/filter/types/path/common.d.ts +1 -1
  44. package/dist/Model/filter/types/path/eager.d.ts +1 -1
  45. package/dist/Model/filter/types/path/eager.d.ts.map +1 -1
  46. package/dist/Model/filter/types/path/index.d.ts +1 -1
  47. package/dist/Model/filter/types/utils.d.ts +1 -1
  48. package/dist/Model/filter/types/validator.d.ts +1 -1
  49. package/dist/Model/filter/types.d.ts +1 -1
  50. package/dist/Model/query/dsl.d.ts +1 -1
  51. package/dist/Model/query/dsl.d.ts.map +1 -1
  52. package/dist/Model/query/new-kid-interpreter.d.ts +6 -6
  53. package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -1
  54. package/dist/Model/query/new-kid-interpreter.js +3 -3
  55. package/dist/Model/query.d.ts +1 -1
  56. package/dist/Model.d.ts +2 -1
  57. package/dist/Model.d.ts.map +1 -1
  58. package/dist/Model.js +2 -1
  59. package/dist/Operations.d.ts +6 -6
  60. package/dist/Operations.d.ts.map +1 -1
  61. package/dist/Operations.js +56 -59
  62. package/dist/OperationsRepo.d.ts +11 -29
  63. package/dist/OperationsRepo.d.ts.map +1 -1
  64. package/dist/OperationsRepo.js +3 -3
  65. package/dist/QueueMaker/SQLQueue.d.ts +5 -7
  66. package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
  67. package/dist/QueueMaker/SQLQueue.js +105 -114
  68. package/dist/QueueMaker/errors.d.ts +2 -2
  69. package/dist/QueueMaker/errors.d.ts.map +1 -1
  70. package/dist/QueueMaker/memQueue.d.ts +7 -4
  71. package/dist/QueueMaker/memQueue.d.ts.map +1 -1
  72. package/dist/QueueMaker/memQueue.js +51 -62
  73. package/dist/QueueMaker/sbqueue.d.ts +6 -3
  74. package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
  75. package/dist/QueueMaker/sbqueue.js +36 -52
  76. package/dist/QueueMaker/service.d.ts +1 -1
  77. package/dist/RequestContext.d.ts +114 -26
  78. package/dist/RequestContext.d.ts.map +1 -1
  79. package/dist/RequestContext.js +7 -7
  80. package/dist/RequestFiberSet.d.ts +7 -7
  81. package/dist/RequestFiberSet.d.ts.map +1 -1
  82. package/dist/RequestFiberSet.js +5 -5
  83. package/dist/Store/ContextMapContainer.d.ts +19 -3
  84. package/dist/Store/ContextMapContainer.d.ts.map +1 -1
  85. package/dist/Store/ContextMapContainer.js +13 -3
  86. package/dist/Store/Cosmos/query.d.ts +1 -1
  87. package/dist/Store/Cosmos/query.d.ts.map +1 -1
  88. package/dist/Store/Cosmos/query.js +8 -10
  89. package/dist/Store/Cosmos.d.ts +1 -1
  90. package/dist/Store/Cosmos.d.ts.map +1 -1
  91. package/dist/Store/Cosmos.js +308 -242
  92. package/dist/Store/Disk.d.ts +2 -2
  93. package/dist/Store/Disk.d.ts.map +1 -1
  94. package/dist/Store/Disk.js +25 -22
  95. package/dist/Store/Memory.d.ts +4 -4
  96. package/dist/Store/Memory.d.ts.map +1 -1
  97. package/dist/Store/Memory.js +27 -22
  98. package/dist/Store/SQL/Pg.d.ts +4 -0
  99. package/dist/Store/SQL/Pg.d.ts.map +1 -0
  100. package/dist/Store/SQL/Pg.js +189 -0
  101. package/dist/Store/SQL/query.d.ts +38 -0
  102. package/dist/Store/SQL/query.d.ts.map +1 -0
  103. package/dist/Store/SQL/query.js +367 -0
  104. package/dist/Store/SQL.d.ts +20 -0
  105. package/dist/Store/SQL.d.ts.map +1 -0
  106. package/dist/Store/SQL.js +381 -0
  107. package/dist/Store/codeFilter.d.ts +1 -1
  108. package/dist/Store/codeFilter.d.ts.map +1 -1
  109. package/dist/Store/codeFilter.js +2 -1
  110. package/dist/Store/index.d.ts +5 -2
  111. package/dist/Store/index.d.ts.map +1 -1
  112. package/dist/Store/index.js +15 -3
  113. package/dist/Store/service.d.ts +17 -6
  114. package/dist/Store/service.d.ts.map +1 -1
  115. package/dist/Store/service.js +24 -6
  116. package/dist/Store/utils.d.ts +1 -1
  117. package/dist/Store/utils.d.ts.map +1 -1
  118. package/dist/Store/utils.js +3 -4
  119. package/dist/Store.d.ts +1 -1
  120. package/dist/adapters/SQL/Model.d.ts +28 -42
  121. package/dist/adapters/SQL/Model.d.ts.map +1 -1
  122. package/dist/adapters/SQL/Model.js +2 -2
  123. package/dist/adapters/SQL.d.ts +1 -1
  124. package/dist/adapters/ServiceBus.d.ts +9 -9
  125. package/dist/adapters/ServiceBus.d.ts.map +1 -1
  126. package/dist/adapters/ServiceBus.js +13 -15
  127. package/dist/adapters/cosmos-client.d.ts +3 -3
  128. package/dist/adapters/cosmos-client.d.ts.map +1 -1
  129. package/dist/adapters/cosmos-client.js +3 -3
  130. package/dist/adapters/index.d.ts +8 -2
  131. package/dist/adapters/index.d.ts.map +1 -1
  132. package/dist/adapters/index.js +8 -2
  133. package/dist/adapters/logger.d.ts +1 -1
  134. package/dist/adapters/logger.d.ts.map +1 -1
  135. package/dist/adapters/memQueue.d.ts +3 -3
  136. package/dist/adapters/memQueue.d.ts.map +1 -1
  137. package/dist/adapters/memQueue.js +3 -3
  138. package/dist/adapters/mongo-client.d.ts +3 -3
  139. package/dist/adapters/mongo-client.d.ts.map +1 -1
  140. package/dist/adapters/mongo-client.js +3 -3
  141. package/dist/adapters/redis-client.d.ts +3 -3
  142. package/dist/adapters/redis-client.d.ts.map +1 -1
  143. package/dist/adapters/redis-client.js +3 -3
  144. package/dist/api/ContextProvider.d.ts +7 -7
  145. package/dist/api/ContextProvider.d.ts.map +1 -1
  146. package/dist/api/ContextProvider.js +6 -6
  147. package/dist/api/codec.d.ts +1 -1
  148. package/dist/api/internal/RequestContextMiddleware.d.ts +2 -2
  149. package/dist/api/internal/RequestContextMiddleware.d.ts.map +1 -1
  150. package/dist/api/internal/RequestContextMiddleware.js +2 -2
  151. package/dist/api/internal/auth.d.ts +44 -6
  152. package/dist/api/internal/auth.d.ts.map +1 -1
  153. package/dist/api/internal/auth.js +160 -29
  154. package/dist/api/internal/events.d.ts +3 -3
  155. package/dist/api/internal/events.d.ts.map +1 -1
  156. package/dist/api/internal/events.js +9 -7
  157. package/dist/api/internal/health.d.ts +1 -1
  158. package/dist/api/layerUtils.d.ts +6 -6
  159. package/dist/api/layerUtils.d.ts.map +1 -1
  160. package/dist/api/layerUtils.js +5 -5
  161. package/dist/api/middlewares.d.ts +1 -1
  162. package/dist/api/reportError.d.ts +1 -1
  163. package/dist/api/routing/middleware/RouterMiddleware.d.ts +4 -4
  164. package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -1
  165. package/dist/api/routing/middleware/middleware.d.ts +39 -3
  166. package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
  167. package/dist/api/routing/middleware/middleware.js +45 -14
  168. package/dist/api/routing/middleware.d.ts +1 -2
  169. package/dist/api/routing/middleware.d.ts.map +1 -1
  170. package/dist/api/routing/middleware.js +1 -2
  171. package/dist/api/routing/schema/jwt.d.ts +1 -1
  172. package/dist/api/routing/schema/jwt.d.ts.map +1 -1
  173. package/dist/api/routing/tsort.d.ts +1 -1
  174. package/dist/api/routing/tsort.d.ts.map +1 -1
  175. package/dist/api/routing/utils.d.ts +3 -3
  176. package/dist/api/routing/utils.d.ts.map +1 -1
  177. package/dist/api/routing.d.ts +12 -14
  178. package/dist/api/routing.d.ts.map +1 -1
  179. package/dist/api/routing.js +17 -6
  180. package/dist/api/setupRequest.d.ts +8 -5
  181. package/dist/api/setupRequest.d.ts.map +1 -1
  182. package/dist/api/setupRequest.js +12 -7
  183. package/dist/api/util.d.ts +1 -1
  184. package/dist/arbs.d.ts +1 -1
  185. package/dist/arbs.d.ts.map +1 -1
  186. package/dist/arbs.js +5 -3
  187. package/dist/errorReporter.d.ts +4 -4
  188. package/dist/errorReporter.d.ts.map +1 -1
  189. package/dist/errorReporter.js +16 -23
  190. package/dist/errors.d.ts +1 -1
  191. package/dist/fileUtil.d.ts +1 -1
  192. package/dist/fileUtil.d.ts.map +1 -1
  193. package/dist/index.d.ts +1 -1
  194. package/dist/logger/jsonLogger.d.ts +1 -1
  195. package/dist/logger/logFmtLogger.d.ts +1 -1
  196. package/dist/logger/shared.d.ts +1 -1
  197. package/dist/logger/shared.js +2 -2
  198. package/dist/logger.d.ts +1 -1
  199. package/dist/logger.d.ts.map +1 -1
  200. package/dist/rateLimit.d.ts +9 -3
  201. package/dist/rateLimit.d.ts.map +1 -1
  202. package/dist/rateLimit.js +5 -11
  203. package/dist/test.d.ts +1 -1
  204. package/dist/test.d.ts.map +1 -1
  205. package/dist/vitest.d.ts +1 -1
  206. package/eslint.config.mjs +1 -1
  207. package/examples/query.ts +39 -35
  208. package/package.json +42 -28
  209. package/src/CUPS.ts +9 -11
  210. package/src/Emailer/Sendgrid.ts +17 -14
  211. package/src/Emailer/service.ts +8 -2
  212. package/src/MainFiberSet.ts +3 -3
  213. package/src/Model/Repository/Registry.ts +33 -0
  214. package/src/Model/Repository/ext.ts +93 -6
  215. package/src/Model/Repository/internal/internal.ts +87 -80
  216. package/src/Model/Repository/makeRepo.ts +12 -10
  217. package/src/Model/Repository/service.ts +31 -22
  218. package/src/Model/Repository/validation.ts +4 -4
  219. package/src/Model/Repository.ts +1 -0
  220. package/src/Model/dsl.ts +3 -3
  221. package/src/Model/query/new-kid-interpreter.ts +2 -2
  222. package/src/Model.ts +1 -0
  223. package/src/Operations.ts +78 -113
  224. package/src/OperationsRepo.ts +2 -2
  225. package/src/QueueMaker/SQLQueue.ts +121 -151
  226. package/src/QueueMaker/memQueue.ts +82 -103
  227. package/src/QueueMaker/sbqueue.ts +55 -85
  228. package/src/RequestContext.ts +7 -7
  229. package/src/RequestFiberSet.ts +4 -4
  230. package/src/Store/ContextMapContainer.ts +41 -2
  231. package/src/Store/Cosmos/query.ts +9 -11
  232. package/src/Store/Cosmos.ts +437 -343
  233. package/src/Store/Disk.ts +52 -49
  234. package/src/Store/Memory.ts +54 -48
  235. package/src/Store/SQL/Pg.ts +318 -0
  236. package/src/Store/SQL/query.ts +409 -0
  237. package/src/Store/SQL.ts +668 -0
  238. package/src/Store/codeFilter.ts +1 -0
  239. package/src/Store/index.ts +17 -2
  240. package/src/Store/service.ts +31 -7
  241. package/src/Store/utils.ts +23 -22
  242. package/src/adapters/SQL/Model.ts +10 -4
  243. package/src/adapters/ServiceBus.ts +111 -115
  244. package/src/adapters/cosmos-client.ts +2 -2
  245. package/src/adapters/index.ts +7 -0
  246. package/src/adapters/memQueue.ts +2 -2
  247. package/src/adapters/mongo-client.ts +2 -2
  248. package/src/adapters/redis-client.ts +2 -2
  249. package/src/api/ContextProvider.ts +11 -11
  250. package/src/api/internal/RequestContextMiddleware.ts +1 -1
  251. package/src/api/internal/auth.ts +246 -44
  252. package/src/api/internal/events.ts +12 -8
  253. package/src/api/layerUtils.ts +8 -8
  254. package/src/api/routing/middleware/RouterMiddleware.ts +4 -4
  255. package/src/api/routing/middleware/middleware.ts +52 -12
  256. package/src/api/routing/middleware.ts +0 -2
  257. package/src/api/routing.ts +21 -7
  258. package/src/api/setupRequest.ts +28 -8
  259. package/src/arbs.ts +4 -2
  260. package/src/errorReporter.ts +58 -72
  261. package/src/logger/shared.ts +1 -1
  262. package/src/rateLimit.ts +30 -22
  263. package/test/auth.test.ts +101 -0
  264. package/test/contextProvider.test.ts +11 -11
  265. package/test/controller.test.ts +18 -14
  266. package/test/dist/auth.test.d.ts.map +1 -0
  267. package/test/dist/contextProvider.test.d.ts.map +1 -1
  268. package/test/dist/controller.test.d.ts.map +1 -1
  269. package/test/dist/date-query.test.d.ts.map +1 -0
  270. package/test/dist/fixtures.d.ts +26 -12
  271. package/test/dist/fixtures.d.ts.map +1 -1
  272. package/test/dist/fixtures.js +12 -10
  273. package/test/dist/query.test.d.ts.map +1 -1
  274. package/test/dist/rawQuery.test.d.ts.map +1 -1
  275. package/test/dist/repository-ext.test.d.ts.map +1 -0
  276. package/test/dist/requires.test.d.ts.map +1 -1
  277. package/test/dist/router-generator.test.d.ts.map +1 -0
  278. package/test/dist/routing-interruptibility.test.d.ts.map +1 -0
  279. package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
  280. package/test/dist/sql-store.test.d.ts.map +1 -0
  281. package/test/fixtures.ts +11 -9
  282. package/test/query.test.ts +212 -34
  283. package/test/rawQuery.test.ts +23 -19
  284. package/test/repository-ext.test.ts +58 -0
  285. package/test/requires.test.ts +6 -6
  286. package/test/router-generator.test.ts +180 -0
  287. package/test/routing-interruptibility.test.ts +63 -0
  288. package/test/rpc-multi-middleware.test.ts +78 -9
  289. package/test/sql-store.test.ts +1064 -0
  290. package/test/validateSample.test.ts +15 -12
  291. package/tsconfig.json +0 -1
package/src/CUPS.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type FileOptions, tempFile } from "@effect-app/infra/fileUtil"
2
2
  import cp from "child_process"
3
- import { Config, Effect, Layer, Option, Predicate, S, ServiceMap } from "effect-app"
3
+ import { Config, Context, Effect, Layer, Option, Predicate, S } from "effect-app"
4
4
  import { pretty } from "effect-app/utils"
5
5
  import fs from "fs"
6
6
  import os from "os"
@@ -74,15 +74,13 @@ function printBuffer(printer: PrinterConfig, options: string[]) {
74
74
  )
75
75
  }
76
76
 
77
- function getAvailablePrinters(host?: string) {
78
- return Effect.gen(function*() {
79
- const { stdout } = yield* exec(["lpstat", ...buildListArgs({ host }), "-s"].join(" "))
80
- return [...stdout.matchAll(/device for (\w+):/g)]
81
- .map((_) => _[1])
82
- .filter(Predicate.isNotNullish)
83
- .map((_) => S.NonEmptyString255(_))
84
- })
85
- }
77
+ const getAvailablePrinters = Effect.fnUntraced(function*(host?: string) {
78
+ const { stdout } = yield* exec(["lpstat", ...buildListArgs({ host }), "-s"].join(" "))
79
+ return [...stdout.matchAll(/device for (\w+):/g)]
80
+ .map((_) => _[1])
81
+ .filter(Predicate.isNotNullish)
82
+ .map((_) => S.NonEmptyString255(_))
83
+ })
86
84
 
87
85
  function* buildListArgs(config?: { host?: string | undefined }) {
88
86
  if (config?.host) {
@@ -100,7 +98,7 @@ export const CUPSConfig = Config.all({
100
98
  )
101
99
  })
102
100
 
103
- export class CUPS extends ServiceMap.Service<CUPS>()("effect-app/CUPS", {
101
+ export class CUPS extends Context.Service<CUPS>()("effect-app/CUPS", {
104
102
  make: Effect.gen(function*() {
105
103
  const config = yield* CUPSConfig
106
104
  const serverUrl = Option.getOrUndefined(config.server)
@@ -7,7 +7,9 @@ import { inspect } from "util"
7
7
  import { InfraLogger } from "../logger.js"
8
8
  import { Emailer, type EmailMsg, type EmailMsgOptionalFrom, type SendgridConfig, SendMailError } from "./service.js"
9
9
 
10
- const makeSendgrid = ({ apiKey, defaultFrom, defaultReplyTo, realMail, subjectPrefix }: SendgridConfig) =>
10
+ const makeSendgrid = (
11
+ { apiKey, defaultFrom, defaultReplyTo, fakeMailAddress, realMail, subjectPrefix }: SendgridConfig
12
+ ) =>
11
13
  Effect.sync(() => {
12
14
  sgMail.setApiKey(Redacted.value(apiKey))
13
15
 
@@ -18,7 +20,7 @@ const makeSendgrid = ({ apiKey, defaultFrom, defaultReplyTo, realMail, subjectPr
18
20
  from: msg_.from ?? defaultFrom,
19
21
  replyTo: msg_.replyTo ?? (msg_.from ? undefined : defaultReplyTo)
20
22
  })
21
- const render = renderMessage(!realMail)
23
+ const render = renderMessage(!realMail, fakeMailAddress)
22
24
 
23
25
  const renderedMsg_ = render(msg)
24
26
  const renderedMsg = {
@@ -68,23 +70,24 @@ export function Sendgrid(config: SendgridConfig) {
68
70
  /**
69
71
  * @hidden
70
72
  */
71
- export function renderMessage(forceFake: boolean) {
73
+ export function renderMessage(forceFake: boolean, fakeMailAddress: string) {
72
74
  let i = 0
73
75
  const makeId = () => i++
76
+ const makeFakeEmail = () => fakeMailAddress.replace("{i}", String(makeId()))
74
77
  return forceFake
75
78
  ? (msg: EmailMsg) =>
76
79
  dropUndefinedT({
77
80
  ...msg,
78
- to: msg.to && renderFake(msg.to, makeId),
79
- cc: msg.cc && renderFake(msg.cc, makeId),
80
- bcc: msg.bcc && renderFake(msg.bcc, makeId)
81
+ to: msg.to && renderFake(msg.to, makeFakeEmail),
82
+ cc: msg.cc && renderFake(msg.cc, makeFakeEmail),
83
+ bcc: msg.bcc && renderFake(msg.bcc, makeFakeEmail)
81
84
  })
82
85
  : (msg: EmailMsg) =>
83
86
  dropUndefinedT({
84
87
  ...msg,
85
- to: msg.to && renderFakeIfTest(msg.to, makeId),
86
- cc: msg.cc && renderFakeIfTest(msg.cc, makeId),
87
- bcc: msg.bcc && renderFakeIfTest(msg.bcc, makeId)
88
+ to: msg.to && renderFakeIfTest(msg.to, makeFakeEmail),
89
+ cc: msg.cc && renderFakeIfTest(msg.cc, makeFakeEmail),
90
+ bcc: msg.bcc && renderFakeIfTest(msg.bcc, makeFakeEmail)
88
91
  })
89
92
  }
90
93
 
@@ -100,10 +103,10 @@ export function isTestAddress(to: EmailData) {
100
103
  )
101
104
  }
102
105
 
103
- function renderFake(addr: EmailData | readonly EmailData[], makeId: () => number) {
106
+ function renderFake(addr: EmailData | readonly EmailData[], makeEmail: () => string) {
104
107
  return {
105
108
  name: renderMailData(addr),
106
- email: `test+${makeId()}@nomizz.com`
109
+ email: makeEmail()
107
110
  }
108
111
  }
109
112
  const eq = Equivalence.mapInput(
@@ -117,14 +120,14 @@ function isEmailDataArray(md: EmailData | readonly EmailData[]): md is readonly
117
120
 
118
121
  // TODO: should just not add any already added email address
119
122
  // https://stackoverflow.com/a/53603076/11595834
120
- function renderFakeIfTest(addr: EmailData | readonly EmailData[], makeId: () => number) {
123
+ function renderFakeIfTest(addr: EmailData | readonly EmailData[], makeEmail: () => string) {
121
124
  if (isEmailDataArray(addr)) {
122
125
  return Array.dedupeWith(
123
- addr.map((x) => (isTestAddress(x) ? renderFake(x, makeId) : x)),
126
+ addr.map((x) => (isTestAddress(x) ? renderFake(x, makeEmail) : x)),
124
127
  eq
125
128
  )
126
129
  }
127
- return isTestAddress(addr) ? renderFake(addr, makeId) : addr
130
+ return isTestAddress(addr) ? renderFake(addr, makeEmail) : addr
128
131
  }
129
132
 
130
133
  function renderMailData(md: EmailData | readonly EmailData[]): string {
@@ -1,13 +1,13 @@
1
1
  import type { MailContent, MailData } from "@sendgrid/helpers/classes/mail.js"
2
2
  import type { ResponseError } from "@sendgrid/mail"
3
- import { Data, type Effect, type NonEmptyReadonlyArray, type Redacted, ServiceMap } from "effect-app"
3
+ import { Context, Data, type Effect, type NonEmptyReadonlyArray, type Redacted } from "effect-app"
4
4
  import type { Email } from "effect-app/Schema"
5
5
 
6
6
  export class SendMailError extends Data.TaggedError("SendMailError")<{
7
7
  readonly raw: Error | ResponseError
8
8
  }> {}
9
9
 
10
- export class Emailer extends ServiceMap.Opaque<Emailer, {
10
+ export class Emailer extends Context.Opaque<Emailer, {
11
11
  sendMail: (msg: EmailMsgOptionalFrom) => Effect.Effect<void, SendMailError>
12
12
  }>()("effect-app/Emailer") {}
13
13
 
@@ -22,6 +22,12 @@ export interface SendgridConfig {
22
22
  realMail: boolean
23
23
  defaultFrom: EmailData
24
24
  apiKey: Redacted.Redacted<string>
25
+ /**
26
+ * Email address used for fake/test recipients. Use `{i}` as a placeholder for an auto-incrementing index to ensure uniqueness.
27
+ *
28
+ * @example "test+{i}@example.com"
29
+ */
30
+ fakeMailAddress: string
25
31
  }
26
32
  export type EmailTemplateMsg = MailData & { templateId: string }
27
33
 
@@ -1,5 +1,5 @@
1
- import { Effect, Fiber, FiberSet, Layer, ServiceMap } from "effect-app"
2
- import type {} from "effect/Scope"
1
+ import { Context, Effect, Fiber, FiberSet, Layer } from "effect-app"
2
+
3
3
  import { InfraLogger } from "./logger.js"
4
4
  import { reportNonInterruptedFailureCause } from "./QueueMaker/errors.js"
5
5
  import { setRootParentSpan } from "./RequestFiberSet.js"
@@ -62,7 +62,7 @@ const make = Effect.gen(function*() {
62
62
  * you should register these long running fibers in a FiberSet, and join them at the end of your main program.
63
63
  * This way any errors will blow up the main program instead of fibers dying unknowingly.
64
64
  */
65
- export class MainFiberSet extends ServiceMap.Service<MainFiberSet>()("MainFiberSet", { make }) {
65
+ export class MainFiberSet extends Context.Service<MainFiberSet>()("MainFiberSet", { make }) {
66
66
  static readonly Live = Layer.effect(this, this.make)
67
67
  static readonly JoinLive = this.asEffect().pipe(
68
68
  Effect.andThen((_) => _.join),
@@ -0,0 +1,33 @@
1
+ import { Context, Effect } from "effect-app"
2
+
3
+ export interface RegisteredRepository {
4
+ readonly seedNamespace: (namespace: string) => Effect.Effect<void>
5
+ }
6
+
7
+ const make = Effect.sync(() => {
8
+ const repos = new Map<string, RegisteredRepository>()
9
+ return {
10
+ register(modelName: string, repo: RegisteredRepository) {
11
+ repos.set(modelName, repo)
12
+ },
13
+ seedNamespace: (namespace: string) =>
14
+ Effect.suspend(() =>
15
+ Effect.forEach(
16
+ repos.values(),
17
+ (r) => r.seedNamespace(namespace),
18
+ { concurrency: "unbounded", discard: true }
19
+ )
20
+ ),
21
+ get entries(): ReadonlyMap<string, RegisteredRepository> {
22
+ return repos
23
+ }
24
+ }
25
+ })
26
+
27
+ export class RepositoryRegistry extends Context.Opaque<RepositoryRegistry, {
28
+ readonly register: (modelName: string, repo: RegisteredRepository) => void
29
+ readonly seedNamespace: (namespace: string) => Effect.Effect<void>
30
+ readonly entries: ReadonlyMap<string, RegisteredRepository>
31
+ }>()("effect-app/RepositoryRegistry", { make }) {}
32
+
33
+ export const RepositoryRegistryLive = RepositoryRegistry.toLayer(RepositoryRegistry.make)
@@ -9,6 +9,22 @@ import type { Query, QueryEnd, QueryWhere } from "../query.js"
9
9
  import * as Q from "../query.js"
10
10
  import type { Repository } from "./service.js"
11
11
 
12
+ interface BatchOptions {
13
+ readonly batch?: true | number
14
+ }
15
+
16
+ const asReadonlyArray = <T>(itemOrItems: T | ReadonlyArray<T>): ReadonlyArray<T> =>
17
+ globalThis.Array.isArray(itemOrItems)
18
+ ? itemOrItems as ReadonlyArray<T>
19
+ : [itemOrItems as T]
20
+
21
+ const getBatchSize = (batch?: true | number) =>
22
+ batch === true
23
+ ? 100
24
+ : typeof batch === "number" && Number.isFinite(batch) && batch > 0
25
+ ? Math.floor(batch)
26
+ : undefined
27
+
12
28
  export const extendRepo = <
13
29
  T,
14
30
  Encoded extends FieldValues,
@@ -16,9 +32,10 @@ export const extendRepo = <
16
32
  ItemType extends string,
17
33
  IdKey extends keyof T & keyof Encoded,
18
34
  RSchema,
19
- RPublish
35
+ RPublish,
36
+ RProvided = never
20
37
  >(
21
- repo: Repository<T, Encoded, Evt, ItemType, IdKey, RSchema, RPublish>
38
+ repo: Repository<T, Encoded, Evt, ItemType, IdKey, RSchema, RPublish, RProvided>
22
39
  ) => {
23
40
  const get = (id: T[IdKey]) =>
24
41
  repo.find(id).pipe(
@@ -244,8 +261,77 @@ export const extendRepo = <
244
261
  request: (id: T[IdKey]) => Effect.request(_request({ id }), requestResolver),
245
262
  get,
246
263
  log: (evt: Evt) => AnyPureDSL.log(evt),
247
- save: (...items: NonEmptyArray<T>) => repo.saveAndPublish(items),
264
+ /**
265
+ * Enables chunked writes for large batches via `options.batch`.
266
+ * Note: batching breaks transactional properties because chunks are saved independently.
267
+ */
268
+ save: ((itemOrItems: T | ReadonlyArray<T>, options?: BatchOptions) => {
269
+ const items = asReadonlyArray(itemOrItems)
270
+ if (!Array.isReadonlyArrayNonEmpty(items)) {
271
+ return Effect.void
272
+ }
273
+ const batchSize = getBatchSize(options?.batch)
274
+ if (batchSize === undefined) {
275
+ return repo.saveAndPublish(items)
276
+ }
277
+ return Effect.forEach(
278
+ Array.chunksOf(items, batchSize),
279
+ (batch) => repo.saveAndPublish(batch),
280
+ { discard: true }
281
+ )
282
+ }) as (
283
+ itemOrItems: T | ReadonlyArray<T>,
284
+ options?: BatchOptions
285
+ ) => Effect.Effect<
286
+ void,
287
+ InvalidStateError | OptimisticConcurrencyException,
288
+ RSchema | RPublish
289
+ >,
248
290
  saveWithEvents: (events: Iterable<Evt>) => (...items: NonEmptyArray<T>) => repo.saveAndPublish(items, events),
291
+ /**
292
+ * Enables chunked deletes for large batches via `options.batch`.
293
+ * Note: batching breaks transactional properties because chunks are removed independently.
294
+ */
295
+ remove: ((itemOrItems: T | ReadonlyArray<T>, options?: BatchOptions) => {
296
+ const items = asReadonlyArray(itemOrItems)
297
+ if (!Array.isReadonlyArrayNonEmpty(items)) {
298
+ return Effect.void
299
+ }
300
+ const batchSize = getBatchSize(options?.batch)
301
+ if (batchSize === undefined) {
302
+ return repo.removeAndPublish(items)
303
+ }
304
+ return Effect.forEach(
305
+ Array.chunksOf(items, batchSize),
306
+ (batch) => repo.removeAndPublish(batch),
307
+ { discard: true }
308
+ )
309
+ }) as (
310
+ itemOrItems: T | ReadonlyArray<T>,
311
+ options?: BatchOptions
312
+ ) => Effect.Effect<void, never, RSchema | RPublish>,
313
+ /**
314
+ * Enables chunked deletes for large batches via `options.batch`.
315
+ * Note: batching breaks transactional properties because chunks are removed independently.
316
+ */
317
+ removeById: ((idOrIds: T[IdKey] | ReadonlyArray<T[IdKey]>, options?: BatchOptions) => {
318
+ const ids = asReadonlyArray(idOrIds)
319
+ if (!Array.isReadonlyArrayNonEmpty(ids)) {
320
+ return Effect.void
321
+ }
322
+ const batchSize = getBatchSize(options?.batch)
323
+ if (batchSize === undefined) {
324
+ return repo.removeById(ids)
325
+ }
326
+ return Effect.forEach(
327
+ Array.chunksOf(ids, batchSize),
328
+ (batch) => repo.removeById(batch),
329
+ { discard: true }
330
+ )
331
+ }) as (
332
+ idOrIds: T[IdKey] | ReadonlyArray<T[IdKey]>,
333
+ options?: BatchOptions
334
+ ) => Effect.Effect<void, never, RSchema>,
249
335
  queryAndSavePure,
250
336
  saveManyWithPure,
251
337
  byIdAndSaveWithPure,
@@ -268,7 +354,7 @@ export const extendRepo = <
268
354
  return {
269
355
  ...repo,
270
356
  ...exts
271
- } as Repository<T, Encoded, Evt, ItemType, IdKey, RSchema, RPublish> & typeof exts
357
+ } as Repository<T, Encoded, Evt, ItemType, IdKey, RSchema, RPublish, RProvided> & typeof exts
272
358
  }
273
359
 
274
360
  // eslint-disable-next-line @typescript-eslint/no-empty-object-type
@@ -279,5 +365,6 @@ export interface ExtendedRepository<
279
365
  ItemType extends string,
280
366
  IdKey extends keyof T & keyof Encoded,
281
367
  RSchema,
282
- RPublish
283
- > extends ReturnType<typeof extendRepo<T, Encoded, Evt, ItemType, IdKey, RSchema, RPublish>> {}
368
+ RPublish,
369
+ RProvided = never
370
+ > extends ReturnType<typeof extendRepo<T, Encoded, Evt, ItemType, IdKey, RSchema, RPublish, RProvided>> {}
@@ -1,7 +1,6 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import type {} from "effect/Equal"
3
- import type {} from "effect/Hash"
4
- import { Array, Chunk, Effect, Equivalence, flow, type NonEmptyReadonlyArray, Option, pipe, Pipeable, PubSub, Result, S, SchemaAST, ServiceMap, Unify } from "effect-app"
2
+
3
+ import { Array, Chunk, Context, Effect, Equivalence, flow, type NonEmptyReadonlyArray, Option, pipe, Pipeable, PubSub, Result, S, SchemaAST, Unify } from "effect-app"
5
4
  import { toNonEmptyArray } from "effect-app/Array"
6
5
  import { NotFoundError } from "effect-app/client/errors"
7
6
  import { flatMapOption } from "effect-app/Effect"
@@ -55,14 +54,14 @@ export function makeRepoInternal<
55
54
 
56
55
  function make<RInitial = never, E = never, RPublish = never, RCtx = never>(
57
56
  args: [Evt] extends [never] ? {
58
- schemaContext?: ServiceMap.ServiceMap<RCtx>
57
+ schemaContext?: Context.Context<RCtx>
59
58
  makeInitial?: Effect.Effect<readonly T[], E, RInitial> | undefined
60
59
  config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
61
60
  partitionValue?: (e?: Encoded) => string
62
61
  }
63
62
  }
64
63
  : {
65
- schemaContext?: ServiceMap.ServiceMap<RCtx>
64
+ schemaContext?: Context.Context<RCtx>
66
65
  publishEvents: (evt: NonEmptyReadonlyArray<Evt>) => Effect.Effect<void, never, RPublish>
67
66
  makeInitial?: Effect.Effect<readonly T[], E, RInitial> | undefined
68
67
  config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
@@ -72,12 +71,12 @@ export function makeRepoInternal<
72
71
  ) {
73
72
  return Effect
74
73
  .gen(function*() {
75
- const rctx: ServiceMap.ServiceMap<RCtx> = args.schemaContext ?? ServiceMap.empty() as any
74
+ const rctx: Context.Context<RCtx> = args.schemaContext ?? Context.empty() as any
76
75
  const provideRctx = Effect.provide(rctx)
77
76
  const encodeMany = flow(
78
77
  S.encodeEffect(S.Array(schema)),
79
78
  provideRctx,
80
- Effect.withSpan("encodeMany", {}, { captureStackTrace: false })
79
+ Effect.withSpan("encodeMany", { attributes: { itemType: name } }, { captureStackTrace: false })
81
80
  )
82
81
  const decode = flow(S.decodeEffect(schema), provideRctx)
83
82
  const decodeMany = flow(
@@ -113,11 +112,14 @@ export function makeRepoInternal<
113
112
  let ast = _.ast
114
113
  if (ast._tag === "Declaration") ast = ast.typeParameters[0]!
115
114
 
116
- // In v4, to get the encoded (from) side of a schema, use SchemaAST.toEncoded
117
115
  const pickIdFromAst = (a: SchemaAST.AST) => {
118
- const encoded = SchemaAST.toEncoded(a)
119
- if (SchemaAST.isObjects(encoded)) {
120
- const field = encoded.propertySignatures.find((_) => _.name === idKey)
116
+ // Unwrap Declaration (e.g. TaggedClass) to get the underlying Objects AST
117
+ let inner = a
118
+ if (inner._tag === "Declaration") inner = inner.typeParameters[0]!
119
+ // Pick from the original AST to preserve the full encoding chain (e.g. decodeTo transformations).
120
+ // Using toEncoded would lose transformation info needed to encode Type -> Encoded.
121
+ if (SchemaAST.isObjects(inner)) {
122
+ const field = inner.propertySignatures.find((_) => _.name === idKey)
121
123
  if (field) {
122
124
  return S.Struct({ [idKey]: S.make(field.type) }) as unknown as Codec<T, Encoded>
123
125
  }
@@ -161,7 +163,7 @@ export function makeRepoInternal<
161
163
  )
162
164
  })
163
165
 
164
- const find = Effect.fn("find")(function*(id: T[IdKey]) {
166
+ const find = Effect.fn("find", { attributes: { itemType: name } })(function*(id: T[IdKey]) {
165
167
  yield* Effect.annotateCurrentSpan({ itemId: id })
166
168
 
167
169
  return yield* flatMapOption(findE(id), (_) => Effect.orDie(decode(_)))
@@ -188,73 +190,76 @@ export function makeRepoInternal<
188
190
  Effect.andThen(saveAllE)
189
191
  )
190
192
 
191
- const saveAndPublish = Effect.fn("saveAndPublish")(function*(items: Iterable<T>, events: Iterable<Evt> = []) {
192
- const it = Chunk.fromIterable(items)
193
- const evts = [...events]
194
- yield* Effect.annotateCurrentSpan({ itemIds: [...Chunk.map(it, (_) => _[idKey])], events: evts.length })
195
- return yield* saveAll(it)
196
- .pipe(
197
- Effect.andThen(Effect.sync(() => toNonEmptyArray(evts))),
198
- // TODO: for full consistency the events should be stored within the same database transaction, and then picked up.
199
- (_) => flatMapOption(_, pub),
200
- Effect.andThen(PubSub.publish(changeFeed, [Chunk.toArray(it), "save"] as [T[], "save" | "remove"])),
201
- Effect.asVoid
202
- )
203
- })
193
+ const saveAndPublish = Effect.fn("saveAndPublish", { attributes: { itemType: name } })(
194
+ function*(items: Iterable<T>, events: Iterable<Evt> = []) {
195
+ const it = Chunk.fromIterable(items)
196
+ const evts = [...events]
197
+ yield* Effect.annotateCurrentSpan({ itemIds: Chunk.map(it, (_) => _[idKey]), events: evts.length })
198
+ return yield* saveAll(it)
199
+ .pipe(
200
+ Effect.andThen(Effect.sync(() => toNonEmptyArray(evts))),
201
+ // TODO: for full consistency the events should be stored within the same database transaction, and then picked up.
202
+ (_) => flatMapOption(_, pub),
203
+ Effect.andThen(PubSub.publish(changeFeed, [Chunk.toArray(it), "save"] as [T[], "save" | "remove"])),
204
+ Effect.asVoid
205
+ )
206
+ }
207
+ )
204
208
 
205
- const removeAndPublish = Effect.fn("removeAndPublish")(function*(a: Iterable<T>, events: Iterable<Evt> = []) {
206
- const { set } = yield* cms
207
- const it = [...a]
208
- const evts = [...events]
209
- yield* Effect.annotateCurrentSpan({ itemIds: it.map((_) => _[idKey]), eventCount: evts.length })
210
- const items = yield* encodeMany(it).pipe(Effect.orDie)
211
- if (Array.isReadonlyArrayNonEmpty(items)) {
212
- yield* store.batchRemove(
213
- items.map((_) => (_[idKey])),
214
- args.config?.partitionValue?.(items[0])
215
- )
216
- for (const e of items) {
217
- set(e[idKey], undefined)
218
- }
219
- yield* Effect
220
- .sync(() => toNonEmptyArray(evts))
221
- // TODO: for full consistency the events should be stored within the same database transaction, and then picked up.
222
- .pipe((_) => flatMapOption(_, pub))
209
+ const removeAndPublish = Effect.fn("removeAndPublish", { attributes: { itemType: name } })(
210
+ function*(a: Iterable<T>, events: Iterable<Evt> = []) {
211
+ const { set } = yield* cms
212
+ const it = [...a]
213
+ const evts = [...events]
214
+ yield* Effect.annotateCurrentSpan({ itemIds: it.map((_) => _[idKey]), eventCount: evts.length })
215
+ const items = yield* encodeMany(it).pipe(Effect.orDie)
216
+ if (Array.isReadonlyArrayNonEmpty(items)) {
217
+ yield* store.batchRemove(
218
+ items.map((_) => (_[idKey])),
219
+ args.config?.partitionValue?.(items[0])
220
+ )
221
+ for (const e of items) {
222
+ set(e[idKey], undefined)
223
+ }
224
+ yield* Effect
225
+ .sync(() => toNonEmptyArray(evts))
226
+ // TODO: for full consistency the events should be stored within the same database transaction, and then picked up.
227
+ .pipe((_) => flatMapOption(_, pub))
223
228
 
224
- yield* PubSub.publish(changeFeed, [it, "remove"] as [T[], "save" | "remove"])
229
+ yield* PubSub.publish(changeFeed, [it, "remove"] as [T[], "save" | "remove"])
230
+ }
225
231
  }
226
- })
232
+ )
227
233
 
228
- const removeById = Effect.fn("removeById")(function*(...ids: readonly T[IdKey][]) {
229
- if (!Array.isReadonlyArrayNonEmpty(ids)) {
230
- return
231
- }
232
- const { set } = yield* cms
233
- const eids = yield* Effect.forEach(ids, (_) => encodeIdOnly(_ as any)).pipe(Effect.orDie)
234
- yield* Effect.annotateCurrentSpan({ itemIds: eids })
235
- yield* store.batchRemove(eids)
236
- for (const id of eids) {
237
- set(id, undefined)
234
+ const removeById = Effect.fn("removeById", { attributes: { itemType: name } })(
235
+ function*(idOrIds: T[IdKey] | ReadonlyArray<T[IdKey]>) {
236
+ const ids = globalThis.Array.isArray(idOrIds)
237
+ ? idOrIds as readonly T[IdKey][]
238
+ : [idOrIds as T[IdKey]]
239
+ if (!Array.isReadonlyArrayNonEmpty(ids)) {
240
+ return
241
+ }
242
+ const { set } = yield* cms
243
+ const eids = yield* Effect.forEach(ids, (_) => encodeIdOnly(_ as any)).pipe(Effect.orDie)
244
+ yield* Effect.annotateCurrentSpan({ itemIds: eids })
245
+ yield* store.batchRemove(eids)
246
+ for (const id of eids) {
247
+ set(id, undefined)
248
+ }
249
+ yield* PubSub.publish(changeFeed, [[], "remove"] as [T[], "save" | "remove"])
238
250
  }
239
- yield* PubSub.publish(changeFeed, [[], "remove"] as [T[], "save" | "remove"])
240
- })
251
+ )
241
252
 
242
- const parseMany = (items: readonly PM[]) =>
243
- Effect
244
- .flatMap(cms, (cm) =>
245
- decodeMany(items.map((_) => mapReverse(_, cm.set)))
246
- .pipe(Effect.orDie, Effect.withSpan("parseMany", {}, { captureStackTrace: false })))
247
- const parseMany2 = <A, R>(
248
- items: readonly PM[],
249
- schema: S.Codec<A, Encoded, R>
250
- ) =>
251
- Effect
252
- .flatMap(cms, (cm) =>
253
- S
254
- .decodeEffect(S.Array(schema))(
255
- items.map((_) => mapReverse(_, cm.set))
256
- )
257
- .pipe(Effect.orDie, Effect.withSpan("parseMany2", {}, { captureStackTrace: false })))
253
+ const parseMany = Effect.fn("parseMany", { attributes: { itemType: name } })(function*(items: readonly PM[]) {
254
+ const cm = yield* cms
255
+ return yield* decodeMany(items.map((_) => mapReverse(_, cm.set))).pipe(Effect.orDie)
256
+ })
257
+ const parseMany2 = Effect.fn("parseMany2", { attributes: { itemType: name } })(
258
+ function*<A, R>(items: readonly PM[], schema: S.Codec<A, Encoded, R>) {
259
+ const cm = yield* cms
260
+ return yield* S.decodeEffect(S.Array(schema))(items.map((_) => mapReverse(_, cm.set))).pipe(Effect.orDie)
261
+ }
262
+ )
258
263
  const filter = <U extends keyof Encoded = keyof Encoded>(args: FilterArgs<Encoded, U>) =>
259
264
  store
260
265
  .filter(
@@ -276,10 +281,10 @@ export function makeRepoInternal<
276
281
  const query: {
277
282
  <A, R, From extends FieldValues>(
278
283
  q: Q.QueryProjection<Encoded extends From ? From : never, A, R>
279
- ): Effect.Effect<readonly A[], S.SchemaError, R>
284
+ ): Effect.Effect<readonly A[], S.SchemaError, Exclude<R, RCtx>>
280
285
  <A, R, EncodedRefined extends Encoded = Encoded>(
281
286
  q: Q.QAll<NoInfer<Encoded>, NoInfer<EncodedRefined>, A, R>
282
- ): Effect.Effect<readonly A[], never, R>
287
+ ): Effect.Effect<readonly A[], never, Exclude<R, RCtx>>
283
288
  } = (<A, R, EncodedRefined extends Encoded = Encoded>(q: Q.QAll<Encoded, EncodedRefined, A, R>) => {
284
289
  const a = Q.toFilter(q)
285
290
  const eff = a.mode === "project"
@@ -327,6 +332,7 @@ export function makeRepoInternal<
327
332
  : eff,
328
333
  Effect.withSpan("Repository.query [effect-app/infra]", {
329
334
  attributes: {
335
+ itemType: name,
330
336
  "repository.model_name": name,
331
337
  query: { ...a, schema: a.schema ? "__SCHEMA__" : a.schema, filter: a.filter }
332
338
  }
@@ -334,7 +340,7 @@ export function makeRepoInternal<
334
340
  )
335
341
  }) as any
336
342
 
337
- const validateSample = Effect.fn("validateSample")(function*(options?: {
343
+ const validateSample = Effect.fn("validateSample", { attributes: { itemType: name } })(function*(options?: {
338
344
  percentage?: number
339
345
  maxItems?: number
340
346
  }) {
@@ -374,7 +380,7 @@ export function makeRepoInternal<
374
380
 
375
381
  if (Result.isFailure(decodeResult)) {
376
382
  errors.push(
377
- new ValidationError({
383
+ ValidationError.make({
378
384
  id,
379
385
  rawData,
380
386
  jitMResult,
@@ -384,7 +390,7 @@ export function makeRepoInternal<
384
390
  }
385
391
  }
386
392
 
387
- return new ValidationResult({
393
+ return ValidationResult.make({
388
394
  total: NonNegativeInt(allIds.length),
389
395
  sampled: NonNegativeInt(sample.length),
390
396
  valid: NonNegativeInt(sample.length - errors.length),
@@ -401,6 +407,7 @@ export function makeRepoInternal<
401
407
  saveAndPublish,
402
408
  removeAndPublish,
403
409
  removeById,
410
+ seedNamespace: (namespace: string) => store.seedNamespace(namespace),
404
411
  validateSample,
405
412
  queryRaw<A, Out, QR>(schema: S.Codec<A, Out, QR>, q: Q.RawQuery<Encoded, Out>) {
406
413
  const dec = S.decodeEffect(S.Array(schema))
@@ -441,12 +448,12 @@ export function makeRepoInternal<
441
448
  // },
442
449
  save: (...xes: any[]) =>
443
450
  Effect.flatMap(encMany(xes), (_) => saveAllE(_)).pipe(
444
- Effect.withSpan("mapped.save", {}, { captureStackTrace: false })
451
+ Effect.withSpan("mapped.save", { attributes: { itemType: name } }, { captureStackTrace: false })
445
452
  )
446
453
  }
447
454
  }
448
455
  }
449
- return r as Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<R, RCtx>, RPublish>
456
+ return r as Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<R, RCtx>, RPublish, RCtx>
450
457
  })
451
458
  .pipe(Effect
452
459
  // .withSpan("Repository.make [effect-app/infra]", { attributes: { "repository.model_name": name } })