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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (310) hide show
  1. package/CHANGELOG.md +1640 -0
  2. package/_check.sh +1 -1
  3. package/dist/CUPS.d.ts +7 -7
  4. package/dist/CUPS.d.ts.map +1 -1
  5. package/dist/CUPS.js +10 -12
  6. package/dist/Emailer/Sendgrid.d.ts +14 -14
  7. package/dist/Emailer/Sendgrid.d.ts.map +1 -1
  8. package/dist/Emailer/Sendgrid.js +16 -15
  9. package/dist/Emailer/fake.d.ts +1 -1
  10. package/dist/Emailer/service.d.ts +10 -4
  11. package/dist/Emailer/service.d.ts.map +1 -1
  12. package/dist/Emailer/service.js +3 -3
  13. package/dist/Emailer.d.ts +1 -1
  14. package/dist/MainFiberSet.d.ts +9 -9
  15. package/dist/MainFiberSet.d.ts.map +1 -1
  16. package/dist/MainFiberSet.js +3 -3
  17. package/dist/Model/Repository/Registry.d.ts +20 -0
  18. package/dist/Model/Repository/Registry.d.ts.map +1 -0
  19. package/dist/Model/Repository/Registry.js +17 -0
  20. package/dist/Model/Repository/ext.d.ts +33 -15
  21. package/dist/Model/Repository/ext.d.ts.map +1 -1
  22. package/dist/Model/Repository/ext.js +54 -2
  23. package/dist/Model/Repository/internal/internal.d.ts +6 -6
  24. package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
  25. package/dist/Model/Repository/internal/internal.js +103 -51
  26. package/dist/Model/Repository/legacy.d.ts +1 -1
  27. package/dist/Model/Repository/makeRepo.d.ts +7 -6
  28. package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
  29. package/dist/Model/Repository/makeRepo.js +5 -1
  30. package/dist/Model/Repository/service.d.ts +28 -23
  31. package/dist/Model/Repository/service.d.ts.map +1 -1
  32. package/dist/Model/Repository/validation.d.ts +46 -17
  33. package/dist/Model/Repository/validation.d.ts.map +1 -1
  34. package/dist/Model/Repository/validation.js +5 -5
  35. package/dist/Model/Repository.d.ts +2 -1
  36. package/dist/Model/Repository.d.ts.map +1 -1
  37. package/dist/Model/Repository.js +2 -1
  38. package/dist/Model/dsl.d.ts +4 -4
  39. package/dist/Model/dsl.d.ts.map +1 -1
  40. package/dist/Model/filter/filterApi.d.ts +5 -5
  41. package/dist/Model/filter/filterApi.d.ts.map +1 -1
  42. package/dist/Model/filter/types/errors.d.ts +1 -1
  43. package/dist/Model/filter/types/fields.d.ts +1 -1
  44. package/dist/Model/filter/types/path/common.d.ts +1 -1
  45. package/dist/Model/filter/types/path/eager.d.ts +1 -1
  46. package/dist/Model/filter/types/path/eager.d.ts.map +1 -1
  47. package/dist/Model/filter/types/path/eager.js +1 -1
  48. package/dist/Model/filter/types/path/index.d.ts +1 -1
  49. package/dist/Model/filter/types/utils.d.ts +1 -1
  50. package/dist/Model/filter/types/validator.d.ts +1 -1
  51. package/dist/Model/filter/types.d.ts +1 -1
  52. package/dist/Model/query/dsl.d.ts +139 -16
  53. package/dist/Model/query/dsl.d.ts.map +1 -1
  54. package/dist/Model/query/dsl.js +187 -1
  55. package/dist/Model/query/new-kid-interpreter.d.ts +76 -7
  56. package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -1
  57. package/dist/Model/query/new-kid-interpreter.js +122 -6
  58. package/dist/Model/query.d.ts +1 -1
  59. package/dist/Model.d.ts +2 -1
  60. package/dist/Model.d.ts.map +1 -1
  61. package/dist/Model.js +2 -1
  62. package/dist/QueueMaker/SQLQueue.d.ts +5 -7
  63. package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
  64. package/dist/QueueMaker/SQLQueue.js +130 -116
  65. package/dist/QueueMaker/errors.d.ts +2 -2
  66. package/dist/QueueMaker/errors.d.ts.map +1 -1
  67. package/dist/QueueMaker/memQueue.d.ts +7 -4
  68. package/dist/QueueMaker/memQueue.d.ts.map +1 -1
  69. package/dist/QueueMaker/memQueue.js +75 -63
  70. package/dist/QueueMaker/sbqueue.d.ts +6 -3
  71. package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
  72. package/dist/QueueMaker/sbqueue.js +52 -53
  73. package/dist/QueueMaker/service.d.ts +1 -1
  74. package/dist/RequestContext.d.ts +74 -35
  75. package/dist/RequestContext.d.ts.map +1 -1
  76. package/dist/RequestContext.js +13 -14
  77. package/dist/RequestFiberSet.d.ts +7 -7
  78. package/dist/RequestFiberSet.d.ts.map +1 -1
  79. package/dist/RequestFiberSet.js +3 -3
  80. package/dist/Store/ContextMapContainer.d.ts +19 -3
  81. package/dist/Store/ContextMapContainer.d.ts.map +1 -1
  82. package/dist/Store/ContextMapContainer.js +13 -3
  83. package/dist/Store/Cosmos/query.d.ts +5 -1
  84. package/dist/Store/Cosmos/query.d.ts.map +1 -1
  85. package/dist/Store/Cosmos/query.js +113 -34
  86. package/dist/Store/Cosmos.d.ts +1 -1
  87. package/dist/Store/Cosmos.d.ts.map +1 -1
  88. package/dist/Store/Cosmos.js +335 -243
  89. package/dist/Store/Disk.d.ts +2 -2
  90. package/dist/Store/Disk.d.ts.map +1 -1
  91. package/dist/Store/Disk.js +72 -35
  92. package/dist/Store/Memory.d.ts +6 -4
  93. package/dist/Store/Memory.d.ts.map +1 -1
  94. package/dist/Store/Memory.js +242 -58
  95. package/dist/Store/SQL/Pg.d.ts +4 -0
  96. package/dist/Store/SQL/Pg.d.ts.map +1 -0
  97. package/dist/Store/SQL/Pg.js +231 -0
  98. package/dist/Store/SQL/query.d.ts +42 -0
  99. package/dist/Store/SQL/query.d.ts.map +1 -0
  100. package/dist/Store/SQL/query.js +479 -0
  101. package/dist/Store/SQL.d.ts +20 -0
  102. package/dist/Store/SQL.d.ts.map +1 -0
  103. package/dist/Store/SQL.js +446 -0
  104. package/dist/Store/codeFilter.d.ts +1 -1
  105. package/dist/Store/codeFilter.d.ts.map +1 -1
  106. package/dist/Store/codeFilter.js +4 -2
  107. package/dist/Store/index.d.ts +5 -2
  108. package/dist/Store/index.d.ts.map +1 -1
  109. package/dist/Store/index.js +15 -3
  110. package/dist/Store/service.d.ts +22 -8
  111. package/dist/Store/service.d.ts.map +1 -1
  112. package/dist/Store/service.js +24 -6
  113. package/dist/Store/utils.d.ts +1 -1
  114. package/dist/Store/utils.d.ts.map +1 -1
  115. package/dist/Store/utils.js +3 -4
  116. package/dist/Store.d.ts +1 -1
  117. package/dist/adapters/SQL/Model.d.ts +31 -42
  118. package/dist/adapters/SQL/Model.d.ts.map +1 -1
  119. package/dist/adapters/SQL/Model.js +29 -38
  120. package/dist/adapters/SQL.d.ts +1 -1
  121. package/dist/adapters/ServiceBus.d.ts +11 -11
  122. package/dist/adapters/ServiceBus.d.ts.map +1 -1
  123. package/dist/adapters/ServiceBus.js +25 -21
  124. package/dist/adapters/cosmos-client.d.ts +3 -3
  125. package/dist/adapters/cosmos-client.d.ts.map +1 -1
  126. package/dist/adapters/cosmos-client.js +3 -3
  127. package/dist/adapters/index.d.ts +8 -2
  128. package/dist/adapters/index.d.ts.map +1 -1
  129. package/dist/adapters/index.js +8 -2
  130. package/dist/adapters/logger.d.ts +1 -1
  131. package/dist/adapters/logger.d.ts.map +1 -1
  132. package/dist/adapters/memQueue.d.ts +3 -3
  133. package/dist/adapters/memQueue.d.ts.map +1 -1
  134. package/dist/adapters/memQueue.js +3 -3
  135. package/dist/adapters/mongo-client.d.ts +3 -3
  136. package/dist/adapters/mongo-client.d.ts.map +1 -1
  137. package/dist/adapters/mongo-client.js +3 -3
  138. package/dist/adapters/redis-client.d.ts +3 -3
  139. package/dist/adapters/redis-client.d.ts.map +1 -1
  140. package/dist/adapters/redis-client.js +3 -3
  141. package/dist/api/ContextProvider.d.ts +8 -8
  142. package/dist/api/ContextProvider.d.ts.map +1 -1
  143. package/dist/api/ContextProvider.js +6 -6
  144. package/dist/api/codec.d.ts +1 -1
  145. package/dist/api/internal/RequestContextMiddleware.d.ts +2 -2
  146. package/dist/api/internal/RequestContextMiddleware.d.ts.map +1 -1
  147. package/dist/api/internal/RequestContextMiddleware.js +9 -6
  148. package/dist/api/internal/auth.d.ts +44 -6
  149. package/dist/api/internal/auth.d.ts.map +1 -1
  150. package/dist/api/internal/auth.js +160 -29
  151. package/dist/api/internal/events.d.ts +3 -3
  152. package/dist/api/internal/events.d.ts.map +1 -1
  153. package/dist/api/internal/events.js +10 -8
  154. package/dist/api/internal/health.d.ts +1 -1
  155. package/dist/api/layerUtils.d.ts +6 -6
  156. package/dist/api/layerUtils.d.ts.map +1 -1
  157. package/dist/api/layerUtils.js +5 -5
  158. package/dist/api/middlewares.d.ts +1 -1
  159. package/dist/api/reportError.d.ts +1 -1
  160. package/dist/api/routing/middleware/RouterMiddleware.d.ts +4 -4
  161. package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -1
  162. package/dist/api/routing/middleware/middleware.d.ts +39 -3
  163. package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
  164. package/dist/api/routing/middleware/middleware.js +48 -16
  165. package/dist/api/routing/middleware.d.ts +1 -2
  166. package/dist/api/routing/middleware.d.ts.map +1 -1
  167. package/dist/api/routing/middleware.js +1 -2
  168. package/dist/api/routing/schema/jwt.d.ts +1 -1
  169. package/dist/api/routing/schema/jwt.d.ts.map +1 -1
  170. package/dist/api/routing/tsort.d.ts +1 -1
  171. package/dist/api/routing/tsort.d.ts.map +1 -1
  172. package/dist/api/routing/utils.d.ts +3 -3
  173. package/dist/api/routing/utils.d.ts.map +1 -1
  174. package/dist/api/routing.d.ts +80 -37
  175. package/dist/api/routing.d.ts.map +1 -1
  176. package/dist/api/routing.js +109 -41
  177. package/dist/api/setupRequest.d.ts +8 -5
  178. package/dist/api/setupRequest.d.ts.map +1 -1
  179. package/dist/api/setupRequest.js +12 -7
  180. package/dist/api/util.d.ts +1 -1
  181. package/dist/arbs.d.ts +1 -1
  182. package/dist/arbs.d.ts.map +1 -1
  183. package/dist/arbs.js +5 -3
  184. package/dist/errorReporter.d.ts +4 -4
  185. package/dist/errorReporter.d.ts.map +1 -1
  186. package/dist/errorReporter.js +20 -25
  187. package/dist/errors.d.ts +1 -1
  188. package/dist/fileUtil.d.ts +1 -1
  189. package/dist/fileUtil.d.ts.map +1 -1
  190. package/dist/index.d.ts +1 -1
  191. package/dist/logger/jsonLogger.d.ts +1 -1
  192. package/dist/logger/logFmtLogger.d.ts +1 -1
  193. package/dist/logger/shared.d.ts +1 -1
  194. package/dist/logger/shared.js +2 -2
  195. package/dist/logger.d.ts +1 -1
  196. package/dist/logger.d.ts.map +1 -1
  197. package/dist/otel.d.ts +75 -0
  198. package/dist/otel.d.ts.map +1 -0
  199. package/dist/otel.js +65 -0
  200. package/dist/rateLimit.d.ts +9 -3
  201. package/dist/rateLimit.d.ts.map +1 -1
  202. package/dist/rateLimit.js +5 -11
  203. package/dist/test.d.ts +2 -2
  204. package/dist/test.d.ts.map +1 -1
  205. package/dist/test.js +1 -1
  206. package/dist/vitest.d.ts +1 -1
  207. package/examples/query.ts +42 -38
  208. package/package.json +46 -37
  209. package/src/CUPS.ts +9 -11
  210. package/src/Emailer/Sendgrid.ts +17 -14
  211. package/src/Emailer/service.ts +9 -3
  212. package/src/MainFiberSet.ts +5 -6
  213. package/src/Model/Repository/Registry.ts +33 -0
  214. package/src/Model/Repository/ext.ts +96 -10
  215. package/src/Model/Repository/internal/internal.ts +218 -149
  216. package/src/Model/Repository/makeRepo.ts +12 -10
  217. package/src/Model/Repository/service.ts +31 -22
  218. package/src/Model/Repository/validation.ts +4 -4
  219. package/src/Model/Repository.ts +1 -0
  220. package/src/Model/dsl.ts +3 -3
  221. package/src/Model/filter/types/path/eager.ts +1 -2
  222. package/src/Model/query/dsl.ts +348 -18
  223. package/src/Model/query/new-kid-interpreter.ts +206 -6
  224. package/src/Model.ts +1 -0
  225. package/src/QueueMaker/SQLQueue.ts +144 -152
  226. package/src/QueueMaker/memQueue.ts +104 -103
  227. package/src/QueueMaker/sbqueue.ts +70 -86
  228. package/src/RequestContext.ts +14 -16
  229. package/src/RequestFiberSet.ts +2 -2
  230. package/src/Store/ContextMapContainer.ts +41 -2
  231. package/src/Store/Cosmos/query.ts +140 -43
  232. package/src/Store/Cosmos.ts +482 -349
  233. package/src/Store/Disk.ts +102 -65
  234. package/src/Store/Memory.ts +275 -87
  235. package/src/Store/SQL/Pg.ts +361 -0
  236. package/src/Store/SQL/query.ts +539 -0
  237. package/src/Store/SQL.ts +731 -0
  238. package/src/Store/codeFilter.ts +3 -1
  239. package/src/Store/index.ts +17 -2
  240. package/src/Store/service.ts +41 -10
  241. package/src/Store/utils.ts +23 -22
  242. package/src/adapters/SQL/Model.ts +41 -40
  243. package/src/adapters/ServiceBus.ts +125 -121
  244. package/src/adapters/cosmos-client.ts +2 -2
  245. package/src/adapters/index.ts +7 -0
  246. package/src/adapters/memQueue.ts +2 -2
  247. package/src/adapters/mongo-client.ts +2 -2
  248. package/src/adapters/redis-client.ts +2 -2
  249. package/src/api/ContextProvider.ts +12 -13
  250. package/src/api/internal/RequestContextMiddleware.ts +15 -5
  251. package/src/api/internal/auth.ts +246 -44
  252. package/src/api/internal/events.ts +13 -9
  253. package/src/api/layerUtils.ts +8 -8
  254. package/src/api/routing/middleware/RouterMiddleware.ts +4 -4
  255. package/src/api/routing/middleware/middleware.ts +55 -14
  256. package/src/api/routing/middleware.ts +0 -2
  257. package/src/api/routing.ts +296 -131
  258. package/src/api/setupRequest.ts +28 -8
  259. package/src/arbs.ts +4 -2
  260. package/src/errorReporter.ts +62 -74
  261. package/src/logger/shared.ts +1 -1
  262. package/src/otel.ts +152 -0
  263. package/src/rateLimit.ts +30 -22
  264. package/src/test.ts +1 -1
  265. package/test/auth.test.ts +101 -0
  266. package/test/contextProvider.test.ts +11 -11
  267. package/test/controller.test.ts +21 -30
  268. package/test/dist/auth.test.d.ts.map +1 -0
  269. package/test/dist/contextProvider.test.d.ts.map +1 -1
  270. package/test/dist/controller.test.d.ts.map +1 -1
  271. package/test/dist/date-query.test.d.ts.map +1 -0
  272. package/test/dist/fixtures.d.ts +26 -12
  273. package/test/dist/fixtures.d.ts.map +1 -1
  274. package/test/dist/fixtures.js +12 -10
  275. package/test/dist/query.test.d.ts.map +1 -1
  276. package/test/dist/rawQuery.test.d.ts.map +1 -1
  277. package/test/dist/repository-ext.test.d.ts.map +1 -0
  278. package/test/dist/requires.test.d.ts.map +1 -1
  279. package/test/dist/router-generator.test.d.ts.map +1 -0
  280. package/test/dist/routing-interruptibility.test.d.ts.map +1 -0
  281. package/test/dist/rpc-e2e-invalidation.test.d.ts.map +1 -0
  282. package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
  283. package/test/dist/rpc-stream-fullstack.test.d.ts.map +1 -0
  284. package/test/dist/sql-store.test.d.ts.map +1 -0
  285. package/test/fixtures.ts +11 -9
  286. package/test/query.test.ts +813 -38
  287. package/test/rawQuery.test.ts +301 -20
  288. package/test/repository-ext.test.ts +60 -0
  289. package/test/requires.test.ts +6 -6
  290. package/test/router-generator.test.ts +183 -0
  291. package/test/routing-interruptibility.test.ts +63 -0
  292. package/test/rpc-e2e-invalidation.test.ts +251 -0
  293. package/test/rpc-multi-middleware.test.ts +78 -9
  294. package/test/rpc-stream-fullstack.test.ts +300 -0
  295. package/test/sql-store.test.ts +1592 -0
  296. package/test/validateSample.test.ts +15 -12
  297. package/tsconfig.examples.json +1 -1
  298. package/tsconfig.json +0 -1
  299. package/tsconfig.json.bak +2 -2
  300. package/tsconfig.src.json +35 -35
  301. package/tsconfig.test.json +2 -2
  302. package/dist/Operations.d.ts +0 -55
  303. package/dist/Operations.d.ts.map +0 -1
  304. package/dist/Operations.js +0 -102
  305. package/dist/OperationsRepo.d.ts +0 -41
  306. package/dist/OperationsRepo.d.ts.map +0 -1
  307. package/dist/OperationsRepo.js +0 -14
  308. package/eslint.config.mjs +0 -24
  309. package/src/Operations.ts +0 -235
  310. package/src/OperationsRepo.ts +0 -16
@@ -1,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,16 +71,16 @@ 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: { "app.entity": name } }, { captureStackTrace: false })
81
80
  )
82
- const decode = flow(S.decodeEffect(schema), provideRctx)
81
+ const decode = flow(S.decodeEffectConcurrently(schema), provideRctx)
83
82
  const decodeMany = flow(
84
- S.decodeEffect(S.Array(schema)),
83
+ S.decodeEffectConcurrently(S.Array(schema)),
85
84
  provideRctx
86
85
  )
87
86
 
@@ -104,7 +103,13 @@ export function makeRepoInternal<
104
103
  allE,
105
104
  (_) => decodeMany(_).pipe(Effect.orDie)
106
105
  )
107
- .pipe(Effect.map((_) => _ as T[]))
106
+ .pipe(
107
+ Effect.map((_) => _ as T[]),
108
+ Effect.withSpan("Repository.all", {
109
+ kind: "client",
110
+ attributes: { "app.entity": name }
111
+ }, { captureStackTrace: false })
112
+ )
108
113
 
109
114
  const fieldsSchema = schema as unknown as { fields: any }
110
115
  // assumes the id field never needs a service...
@@ -113,11 +118,14 @@ export function makeRepoInternal<
113
118
  let ast = _.ast
114
119
  if (ast._tag === "Declaration") ast = ast.typeParameters[0]!
115
120
 
116
- // In v4, to get the encoded (from) side of a schema, use SchemaAST.toEncoded
117
121
  const pickIdFromAst = (a: SchemaAST.AST) => {
118
- const encoded = SchemaAST.toEncoded(a)
119
- if (SchemaAST.isObjects(encoded)) {
120
- const field = encoded.propertySignatures.find((_) => _.name === idKey)
122
+ // Unwrap Declaration (e.g. TaggedClass) to get the underlying Objects AST
123
+ let inner = a
124
+ if (inner._tag === "Declaration") inner = inner.typeParameters[0]!
125
+ // Pick from the original AST to preserve the full encoding chain (e.g. decodeTo transformations).
126
+ // Using toEncoded would lose transformation info needed to encode Type -> Encoded.
127
+ if (SchemaAST.isObjects(inner)) {
128
+ const field = inner.propertySignatures.find((_) => _.name === idKey)
121
129
  if (field) {
122
130
  return S.Struct({ [idKey]: S.make(field.type) }) as unknown as Codec<T, Encoded>
123
131
  }
@@ -138,7 +146,7 @@ export function makeRepoInternal<
138
146
  Effect.map((_: Record<string, unknown>) => _[idKey as string] as Encoded[IdKey])
139
147
  )
140
148
  const findEId = Effect.fnUntraced(function*(id: Encoded[IdKey]) {
141
- yield* Effect.annotateCurrentSpan({ itemId: id })
149
+ yield* Effect.annotateCurrentSpan({ "app.entity.id": id })
142
150
 
143
151
  return yield* Effect.flatMap(
144
152
  store.find(id),
@@ -151,7 +159,7 @@ export function makeRepoInternal<
151
159
  })
152
160
  // TODO: select the particular field, instead of as struct
153
161
  const findE = Effect.fnUntraced(function*(id: T[IdKey]) {
154
- yield* Effect.annotateCurrentSpan({ itemId: id })
162
+ yield* Effect.annotateCurrentSpan({ "app.entity.id": id })
155
163
 
156
164
  return yield* pipe(
157
165
  encodeId({ [idKey]: id } as any),
@@ -161,9 +169,11 @@ export function makeRepoInternal<
161
169
  )
162
170
  })
163
171
 
164
- const find = Effect.fn("find")(function*(id: T[IdKey]) {
165
- yield* Effect.annotateCurrentSpan({ itemId: id })
166
-
172
+ const find = Effect.fn("Repository.find", {
173
+ kind: "client",
174
+ attributes: { "app.entity": name }
175
+ })(function*(id: T[IdKey]) {
176
+ yield* Effect.annotateCurrentSpan({ "app.entity.id": id })
167
177
  return yield* flatMapOption(findE(id), (_) => Effect.orDie(decode(_)))
168
178
  })
169
179
 
@@ -188,73 +198,96 @@ export function makeRepoInternal<
188
198
  Effect.andThen(saveAllE)
189
199
  )
190
200
 
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
- })
201
+ const saveAndPublish = Effect.fn("Repository.saveAndPublish", { attributes: { "app.entity": name } })(
202
+ function*(items: Iterable<T>, events: Iterable<Evt> = []) {
203
+ const it = Chunk.fromIterable(items)
204
+ const evts = [...events]
205
+ yield* Effect.annotateCurrentSpan({
206
+ "app.entity.ids": Chunk.map(it, (_) => _[idKey]),
207
+ "app.event.count": evts.length
208
+ })
209
+ return yield* saveAll(it)
210
+ .pipe(
211
+ Effect.andThen(Effect.sync(() => toNonEmptyArray(evts))),
212
+ // TODO: for full consistency the events should be stored within the same database transaction, and then picked up.
213
+ (_) => flatMapOption(_, pub),
214
+ Effect.andThen(PubSub.publish(changeFeed, [Chunk.toArray(it), "save"] as [T[], "save" | "remove"])),
215
+ Effect.asVoid
216
+ )
217
+ }
218
+ )
204
219
 
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)
220
+ const removeAndPublish = Effect.fn("Repository.removeAndPublish", { attributes: { "app.entity": name } })(
221
+ function*(a: Iterable<T>, events: Iterable<Evt> = []) {
222
+ const { set } = yield* cms
223
+ const it = [...a]
224
+ const evts = [...events]
225
+ yield* Effect.annotateCurrentSpan({
226
+ "app.entity.ids": it.map((_) => _[idKey]),
227
+ "app.event.count": evts.length
228
+ })
229
+ const items = yield* encodeMany(it).pipe(Effect.orDie)
230
+ if (Array.isReadonlyArrayNonEmpty(items)) {
231
+ yield* store.batchRemove(
232
+ items.map((_) => (_[idKey])),
233
+ args.config?.partitionValue?.(items[0])
234
+ )
235
+ for (const e of items) {
236
+ set(e[idKey], undefined)
237
+ }
238
+ yield* Effect
239
+ .sync(() => toNonEmptyArray(evts))
240
+ // TODO: for full consistency the events should be stored within the same database transaction, and then picked up.
241
+ .pipe((_) => flatMapOption(_, pub))
242
+
243
+ yield* PubSub.publish(changeFeed, [it, "remove"] as [T[], "save" | "remove"])
218
244
  }
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))
245
+ }
246
+ )
223
247
 
224
- yield* PubSub.publish(changeFeed, [it, "remove"] as [T[], "save" | "remove"])
248
+ const removeById = Effect.fn("Repository.removeById", { attributes: { "app.entity": name } })(
249
+ function*(idOrIds: T[IdKey] | ReadonlyArray<T[IdKey]>) {
250
+ const ids = globalThis.Array.isArray(idOrIds)
251
+ ? idOrIds as readonly T[IdKey][]
252
+ : [idOrIds as T[IdKey]]
253
+ if (!Array.isReadonlyArrayNonEmpty(ids)) {
254
+ return
255
+ }
256
+ const { set } = yield* cms
257
+ const eids = yield* Effect.forEach(ids, (_) => encodeIdOnly(_ as any)).pipe(Effect.orDie)
258
+ yield* Effect.annotateCurrentSpan({ "app.entity.ids": eids })
259
+ yield* store.batchRemove(eids)
260
+ for (const id of eids) {
261
+ set(id, undefined)
262
+ }
263
+ yield* PubSub.publish(changeFeed, [[], "remove"] as [T[], "save" | "remove"])
225
264
  }
226
- })
265
+ )
227
266
 
228
- const removeById = Effect.fn("removeById")(function*(...ids: readonly T[IdKey][]) {
229
- if (!Array.isReadonlyArrayNonEmpty(ids)) {
230
- return
267
+ const parseMany = Effect.fn("parseMany", { attributes: { "app.entity": name } })(
268
+ function*(items: readonly PM[]) {
269
+ const cm = yield* cms
270
+ return yield* decodeMany(items.map((_) => mapReverse(_, cm.set))).pipe(Effect.orDie)
231
271
  }
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)
272
+ )
273
+ const decodeManyCache = new WeakMap<
274
+ S.Codec<any, any, any>,
275
+ (i: readonly any[]) => Effect.Effect<any, any, any>
276
+ >()
277
+ const getDecodeMany = (s: S.Codec<any, Encoded, any>) => {
278
+ let dec = decodeManyCache.get(s)
279
+ if (!dec) {
280
+ dec = S.decodeEffectConcurrently(S.Array(s))
281
+ decodeManyCache.set(s, dec)
238
282
  }
239
- yield* PubSub.publish(changeFeed, [[], "remove"] as [T[], "save" | "remove"])
240
- })
241
-
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 })))
283
+ return dec
284
+ }
285
+ const parseMany2 = Effect.fn("parseMany2", { attributes: { "app.entity": name } })(
286
+ function*<A, R>(items: readonly PM[], schema: S.Codec<A, Encoded, R>) {
287
+ const cm = yield* cms
288
+ return yield* getDecodeMany(schema)(items.map((_) => mapReverse(_, cm.set))).pipe(Effect.orDie)
289
+ }
290
+ )
258
291
  const filter = <U extends keyof Encoded = keyof Encoded>(args: FilterArgs<Encoded, U>) =>
259
292
  store
260
293
  .filter(
@@ -276,24 +309,28 @@ export function makeRepoInternal<
276
309
  const query: {
277
310
  <A, R, From extends FieldValues>(
278
311
  q: Q.QueryProjection<Encoded extends From ? From : never, A, R>
279
- ): Effect.Effect<readonly A[], S.SchemaError, R>
312
+ ): Effect.Effect<readonly A[], S.SchemaError, Exclude<R, RCtx>>
280
313
  <A, R, EncodedRefined extends Encoded = Encoded>(
281
314
  q: Q.QAll<NoInfer<Encoded>, NoInfer<EncodedRefined>, A, R>
282
- ): Effect.Effect<readonly A[], never, R>
315
+ ): Effect.Effect<readonly A[], never, Exclude<R, RCtx>>
283
316
  } = (<A, R, EncodedRefined extends Encoded = Encoded>(q: Q.QAll<Encoded, EncodedRefined, A, R>) => {
284
- const a = Q.toFilter(q)
317
+ const a = Q.toFilter(q, schema)
318
+ // Mode dispatch — see `Q.project` JSDoc for the contract:
319
+ // project : decode raw encoded rows with schema; no PM reverse-mapping; SchemaError surfaces.
320
+ // collect : same as project, but schema yields Option and None rows are dropped.
321
+ // transform: PM reverse-map (re-inject _etag/PM state from cms cache) then decode; orDie.
285
322
  const eff = a.mode === "project"
286
323
  ? filter(a)
287
324
  // TODO: mapFrom but need to support per field and dependencies
288
325
  .pipe(
289
- Effect.andThen(flow(S.decodeEffect(S.Array(a.schema ?? schema)), provideRctx))
326
+ Effect.andThen(flow(S.decodeEffectConcurrently(S.Array(a.schema ?? schema)), provideRctx))
290
327
  )
291
328
  : a.mode === "collect"
292
329
  ? filter(a)
293
330
  // TODO: mapFrom but need to support per field and dependencies
294
331
  .pipe(
295
332
  Effect.flatMap(flow(
296
- S.decodeEffect(S.Array(a.schema)),
333
+ S.decodeEffectConcurrently(S.Array(a.schema)),
297
334
  Effect.map(Array.getSomes),
298
335
  provideRctx
299
336
  ))
@@ -325,72 +362,89 @@ export function makeRepoInternal<
325
362
  .map(eff, (_) => NonNegativeInt(_.length))
326
363
  .pipe(Effect.catchTag("SchemaError", (e) => Effect.die(e)))
327
364
  : eff,
328
- Effect.withSpan("Repository.query [effect-app/infra]", {
329
- attributes: {
330
- "repository.model_name": name,
331
- query: { ...a, schema: a.schema ? "__SCHEMA__" : a.schema, filter: a.filter }
332
- }
365
+ Effect.tap((r) =>
366
+ Effect.annotateCurrentSpan({
367
+ "app.query.ttype": a.ttype,
368
+ "app.query.mode": a.mode,
369
+ "db.response.returned_rows": Array.isArray(r) ? r.length : 1
370
+ })
371
+ ),
372
+ Effect.withSpan("Repository.query", {
373
+ kind: "client",
374
+ attributes: { "app.entity": name }
333
375
  }, { captureStackTrace: false })
334
376
  )
335
377
  }) as any
336
378
 
337
- const validateSample = Effect.fn("validateSample")(function*(options?: {
338
- percentage?: number
339
- maxItems?: number
340
- }) {
341
- const percentage = options?.percentage ?? 0.1 // default 10%
342
- const maxItems = options?.maxItems
343
-
344
- // 1. get all IDs with projection (bypasses main schema decode)
345
- const allIds = yield* store.filter({
346
- t: null as unknown as Encoded,
347
- select: [idKey as keyof Encoded]
348
- })
349
-
350
- // 2. random subset
351
- const shuffled = [...allIds].sort(() => Math.random() - 0.5)
352
- const sampleSize = Math.min(
353
- maxItems ?? Infinity,
354
- Math.ceil(allIds.length * percentage)
355
- )
356
- const sample = shuffled.slice(0, sampleSize)
357
-
358
- // 3. validate each item
359
- const errors: ValidationError[] = []
360
-
361
- for (const item of sample) {
362
- const id = item[idKey]
363
- const rawResult = yield* store.find(id)
364
-
365
- if (Option.isNone(rawResult)) continue
379
+ const validateSample = Effect.fn("Repository.validateSample", { attributes: { "app.entity": name } })(
380
+ function*(options?: {
381
+ percentage?: number
382
+ maxItems?: number
383
+ }) {
384
+ const percentage = options?.percentage ?? 0.1 // default 10%
385
+ const maxItems = options?.maxItems
386
+
387
+ // 1. get all IDs with projection (bypasses main schema decode)
388
+ const allIds = yield* store
389
+ .filter({
390
+ t: null as unknown as Encoded,
391
+ select: [idKey as keyof Encoded]
392
+ })
393
+ .pipe(Effect.withSpan("Repository.filter", {
394
+ kind: "client",
395
+ attributes: { "app.entity": name }
396
+ }, { captureStackTrace: false }))
397
+
398
+ // 2. random subset
399
+ const shuffled = [...allIds].sort(() => Math.random() - 0.5)
400
+ const sampleSize = Math.min(
401
+ maxItems ?? Infinity,
402
+ Math.ceil(allIds.length * percentage)
403
+ )
404
+ const sample = shuffled.slice(0, sampleSize)
405
+
406
+ // 3. validate each item
407
+ const errors: ValidationError[] = []
408
+
409
+ for (const item of sample) {
410
+ const id = item[idKey]
411
+ const rawResult = yield* store.find(id).pipe(
412
+ Effect.withSpan("Repository.find", {
413
+ kind: "client",
414
+ attributes: { "app.entity": name, "app.entity.id": id }
415
+ }, { captureStackTrace: false })
416
+ )
366
417
 
367
- const rawData = rawResult.value as Encoded
368
- const jitMResult = mapFrom(rawData) // apply jitM
418
+ if (Option.isNone(rawResult)) continue
369
419
 
370
- const decodeResult = yield* S.decodeEffect(schema)(jitMResult).pipe(
371
- Effect.result,
372
- provideRctx
373
- )
420
+ const rawData = rawResult.value as Encoded
421
+ const jitMResult = mapFrom(rawData) // apply jitM
374
422
 
375
- if (Result.isFailure(decodeResult)) {
376
- errors.push(
377
- new ValidationError({
378
- id,
379
- rawData,
380
- jitMResult,
381
- error: decodeResult.failure
382
- })
423
+ const decodeResult = yield* S.decodeEffectConcurrently(schema)(jitMResult).pipe(
424
+ Effect.result,
425
+ provideRctx
383
426
  )
427
+
428
+ if (Result.isFailure(decodeResult)) {
429
+ errors.push(
430
+ ValidationError.make({
431
+ id,
432
+ rawData,
433
+ jitMResult,
434
+ error: decodeResult.failure
435
+ })
436
+ )
437
+ }
384
438
  }
385
- }
386
439
 
387
- return new ValidationResult({
388
- total: NonNegativeInt(allIds.length),
389
- sampled: NonNegativeInt(sample.length),
390
- valid: NonNegativeInt(sample.length - errors.length),
391
- errors
392
- })
393
- })
440
+ return ValidationResult.make({
441
+ total: NonNegativeInt(allIds.length),
442
+ sampled: NonNegativeInt(sample.length),
443
+ valid: NonNegativeInt(sample.length - errors.length),
444
+ errors
445
+ })
446
+ }
447
+ )
394
448
 
395
449
  const r = {
396
450
  changeFeed,
@@ -401,10 +455,17 @@ export function makeRepoInternal<
401
455
  saveAndPublish,
402
456
  removeAndPublish,
403
457
  removeById,
458
+ seedNamespace: (namespace: string) => store.seedNamespace(namespace),
404
459
  validateSample,
405
460
  queryRaw<A, Out, QR>(schema: S.Codec<A, Out, QR>, q: Q.RawQuery<Encoded, Out>) {
406
- const dec = S.decodeEffect(S.Array(schema))
407
- return store.queryRaw(q).pipe(Effect.flatMap(dec))
461
+ const dec = S.decodeEffectConcurrently(S.Array(schema))
462
+ return store.queryRaw(q).pipe(
463
+ Effect.flatMap(dec),
464
+ Effect.withSpan("Repository.queryRaw", {
465
+ kind: "client",
466
+ attributes: { "app.entity": name }
467
+ }, { captureStackTrace: false })
468
+ )
408
469
  },
409
470
  query(q: any) {
410
471
  // eslint-disable-next-line prefer-rest-params
@@ -414,15 +475,23 @@ export function makeRepoInternal<
414
475
  * @internal
415
476
  */
416
477
  mapped: <A, R>(schema: S.Codec<A, any, R>) => {
417
- const dec = S.decodeEffect(schema)
478
+ const dec = S.decodeEffectConcurrently(schema)
418
479
  const encMany = S.encodeEffect(S.Array(schema))
419
- const decMany = S.decodeEffect(S.Array(schema))
480
+ const decMany = S.decodeEffectConcurrently(S.Array(schema))
481
+ const spanAttrs = { kind: "client" as const, attributes: { "app.entity": name } }
420
482
  return {
421
483
  all: allE.pipe(
422
484
  Effect.flatMap(decMany),
423
- Effect.map((_) => _ as any[])
485
+ Effect.map((_) => _ as any[]),
486
+ Effect.withSpan("Repository.mapped.all", spanAttrs, { captureStackTrace: false })
424
487
  ),
425
- find: (id: T[IdKey]) => flatMapOption(findE(id), dec),
488
+ find: (id: T[IdKey]) =>
489
+ flatMapOption(findE(id), dec).pipe(
490
+ Effect.withSpan("Repository.mapped.find", {
491
+ ...spanAttrs,
492
+ attributes: { ...spanAttrs.attributes, "app.entity.id": id }
493
+ }, { captureStackTrace: false })
494
+ ),
426
495
  // query: (q: any) => {
427
496
  // const a = Q.toFilter(q)
428
497
 
@@ -441,12 +510,12 @@ export function makeRepoInternal<
441
510
  // },
442
511
  save: (...xes: any[]) =>
443
512
  Effect.flatMap(encMany(xes), (_) => saveAllE(_)).pipe(
444
- Effect.withSpan("mapped.save", {}, { captureStackTrace: false })
513
+ Effect.withSpan("Repository.mapped.save", spanAttrs, { captureStackTrace: false })
445
514
  )
446
515
  }
447
516
  }
448
517
  }
449
- return r as Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<R, RCtx>, RPublish>
518
+ return r as Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<R, RCtx>, RPublish, RCtx>
450
519
  })
451
520
  .pipe(Effect
452
521
  // .withSpan("Repository.make [effect-app/infra]", { attributes: { "repository.model_name": name } })
@@ -513,7 +582,7 @@ export function makeStore<Encoded extends FieldValues>() {
513
582
  .pipe(
514
583
  Effect.flatMap(Effect.forEach(encodeToEncoded())),
515
584
  setupRequestContextFromCurrent("Repository.makeInitial [effect-app/infra]", {
516
- attributes: { "repository.model_name": name }
585
+ attributes: { "app.entity": name }
517
586
  })
518
587
  )
519
588
  : undefined,
@@ -5,13 +5,13 @@
5
5
  /* eslint-disable @typescript-eslint/no-explicit-any */
6
6
 
7
7
  // import type { ParserEnv } from "effect-app/Schema/custom/Parser"
8
- import type {} from "effect/Equal"
9
- import type {} from "effect/Hash"
10
- import { Effect, type NonEmptyReadonlyArray, type S, type ServiceMap } from "effect-app"
8
+
9
+ import { type Context, Effect, type NonEmptyReadonlyArray, type S } from "effect-app"
11
10
  import type { StoreConfig, StoreMaker } from "../../Store.js"
12
11
  import type { FieldValues } from "../filter/types.js"
13
12
  import { type ExtendedRepository, extendRepo } from "./ext.js"
14
13
  import { makeRepoInternal } from "./internal/internal.js"
14
+ import { RepositoryRegistry } from "./Registry.js"
15
15
  import type { Repository } from "./service.js"
16
16
 
17
17
  export interface RepositoryOptions<
@@ -52,11 +52,11 @@ export interface RepositoryOptions<
52
52
  * Optional context to be provided to Schema decode/encode.
53
53
  * Useful for effectful transformations like XWithItems, where items is a transformation retrieving elements from another database table or other source.
54
54
  */
55
- schemaContext?: ServiceMap.ServiceMap<RCtx>
55
+ schemaContext?: Context.Context<RCtx>
56
56
 
57
57
  overrides?: (
58
- repo: Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<RSchema, RCtx>, RPublish>
59
- ) => Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<RSchema, RCtx>, RPublish>
58
+ repo: Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<RSchema, RCtx>, RPublish, RCtx>
59
+ ) => Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<RSchema, RCtx>, RPublish, RCtx>
60
60
  }
61
61
 
62
62
  /**
@@ -83,9 +83,9 @@ export const makeRepo: {
83
83
  schema: S.Codec<T, Encoded, RSchema>,
84
84
  options: RepositoryOptions<IdKey, Encoded, T, ItemType, Evt, RPublish, E, RInitial, RCtx, RSchema>
85
85
  ): Effect.Effect<
86
- ExtendedRepository<T, Encoded, Evt, ItemType, IdKey, Exclude<RSchema, RCtx>, RPublish>,
86
+ ExtendedRepository<T, Encoded, Evt, ItemType, IdKey, Exclude<RSchema, RCtx>, RPublish, RCtx>,
87
87
  E,
88
- RInitial | StoreMaker
88
+ RInitial | StoreMaker | RepositoryRegistry
89
89
  >
90
90
  <
91
91
  ItemType extends string,
@@ -102,9 +102,9 @@ export const makeRepo: {
102
102
  schema: S.Codec<T, Encoded, RSchema>,
103
103
  options: Omit<RepositoryOptions<"id", Encoded, T, ItemType, Evt, RPublish, E, RInitial, RCtx, RSchema>, "idKey">
104
104
  ): Effect.Effect<
105
- ExtendedRepository<T, Encoded, Evt, ItemType, "id", Exclude<RSchema, RCtx>, RPublish>,
105
+ ExtendedRepository<T, Encoded, Evt, ItemType, "id", Exclude<RSchema, RCtx>, RPublish, RCtx>,
106
106
  E,
107
- RInitial | StoreMaker
107
+ RInitial | StoreMaker | RepositoryRegistry
108
108
  >
109
109
  } = <
110
110
  ItemType extends string,
@@ -135,5 +135,7 @@ export const makeRepo: {
135
135
  let r = yield* mkRepo.make<RInitial, E, RPublish, RCtx>(options as any)
136
136
  if (options.overrides) r = options.overrides(r)
137
137
  const repo = extendRepo(r)
138
+ const registry = yield* RepositoryRegistry
139
+ registry.register(itemType, repo)
138
140
  return repo
139
141
  })