@effect-app/infra 4.0.0-beta.13 → 4.0.0-beta.130

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 (201) hide show
  1. package/CHANGELOG.md +857 -0
  2. package/dist/CUPS.d.ts +13 -5
  3. package/dist/CUPS.d.ts.map +1 -1
  4. package/dist/CUPS.js +10 -12
  5. package/dist/Emailer/service.d.ts +2 -2
  6. package/dist/Emailer/service.d.ts.map +1 -1
  7. package/dist/Emailer/service.js +3 -3
  8. package/dist/MainFiberSet.d.ts +2 -2
  9. package/dist/MainFiberSet.d.ts.map +1 -1
  10. package/dist/MainFiberSet.js +3 -3
  11. package/dist/Model/Repository/Registry.d.ts +20 -0
  12. package/dist/Model/Repository/Registry.d.ts.map +1 -0
  13. package/dist/Model/Repository/Registry.js +17 -0
  14. package/dist/Model/Repository/ext.d.ts +21 -3
  15. package/dist/Model/Repository/ext.d.ts.map +1 -1
  16. package/dist/Model/Repository/ext.js +54 -2
  17. package/dist/Model/Repository/internal/internal.d.ts +4 -4
  18. package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
  19. package/dist/Model/Repository/internal/internal.js +31 -21
  20. package/dist/Model/Repository/makeRepo.d.ts +6 -5
  21. package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
  22. package/dist/Model/Repository/makeRepo.js +4 -1
  23. package/dist/Model/Repository/service.d.ts +27 -22
  24. package/dist/Model/Repository/service.d.ts.map +1 -1
  25. package/dist/Model/Repository/validation.d.ts +59 -9
  26. package/dist/Model/Repository/validation.d.ts.map +1 -1
  27. package/dist/Model/Repository.d.ts +1 -0
  28. package/dist/Model/Repository.d.ts.map +1 -1
  29. package/dist/Model/Repository.js +2 -1
  30. package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -1
  31. package/dist/Model/query/new-kid-interpreter.js +3 -3
  32. package/dist/Model.d.ts +1 -0
  33. package/dist/Model.d.ts.map +1 -1
  34. package/dist/Model.js +2 -1
  35. package/dist/Operations.d.ts +4 -4
  36. package/dist/Operations.d.ts.map +1 -1
  37. package/dist/Operations.js +56 -59
  38. package/dist/OperationsRepo.d.ts +4 -4
  39. package/dist/OperationsRepo.d.ts.map +1 -1
  40. package/dist/OperationsRepo.js +3 -3
  41. package/dist/QueueMaker/SQLQueue.d.ts +3 -6
  42. package/dist/QueueMaker/SQLQueue.d.ts.map +1 -1
  43. package/dist/QueueMaker/SQLQueue.js +105 -114
  44. package/dist/QueueMaker/errors.d.ts +1 -1
  45. package/dist/QueueMaker/errors.d.ts.map +1 -1
  46. package/dist/QueueMaker/memQueue.d.ts +6 -3
  47. package/dist/QueueMaker/memQueue.d.ts.map +1 -1
  48. package/dist/QueueMaker/memQueue.js +51 -62
  49. package/dist/QueueMaker/sbqueue.d.ts +5 -2
  50. package/dist/QueueMaker/sbqueue.d.ts.map +1 -1
  51. package/dist/QueueMaker/sbqueue.js +36 -52
  52. package/dist/RequestContext.d.ts +51 -21
  53. package/dist/RequestContext.d.ts.map +1 -1
  54. package/dist/RequestContext.js +5 -5
  55. package/dist/RequestFiberSet.d.ts +2 -2
  56. package/dist/RequestFiberSet.d.ts.map +1 -1
  57. package/dist/RequestFiberSet.js +5 -5
  58. package/dist/Store/ContextMapContainer.d.ts +18 -2
  59. package/dist/Store/ContextMapContainer.d.ts.map +1 -1
  60. package/dist/Store/ContextMapContainer.js +13 -3
  61. package/dist/Store/Cosmos.d.ts.map +1 -1
  62. package/dist/Store/Cosmos.js +308 -242
  63. package/dist/Store/Disk.d.ts +1 -1
  64. package/dist/Store/Disk.d.ts.map +1 -1
  65. package/dist/Store/Disk.js +25 -22
  66. package/dist/Store/Memory.d.ts +3 -3
  67. package/dist/Store/Memory.d.ts.map +1 -1
  68. package/dist/Store/Memory.js +27 -22
  69. package/dist/Store/SQL/Pg.d.ts +4 -0
  70. package/dist/Store/SQL/Pg.d.ts.map +1 -0
  71. package/dist/Store/SQL/Pg.js +189 -0
  72. package/dist/Store/SQL/query.d.ts +38 -0
  73. package/dist/Store/SQL/query.d.ts.map +1 -0
  74. package/dist/Store/SQL/query.js +367 -0
  75. package/dist/Store/SQL.d.ts +20 -0
  76. package/dist/Store/SQL.d.ts.map +1 -0
  77. package/dist/Store/SQL.js +381 -0
  78. package/dist/Store/index.d.ts +4 -1
  79. package/dist/Store/index.d.ts.map +1 -1
  80. package/dist/Store/index.js +15 -3
  81. package/dist/Store/service.d.ts +16 -5
  82. package/dist/Store/service.d.ts.map +1 -1
  83. package/dist/Store/service.js +24 -6
  84. package/dist/Store/utils.d.ts.map +1 -1
  85. package/dist/Store/utils.js +3 -4
  86. package/dist/adapters/ServiceBus.d.ts +6 -6
  87. package/dist/adapters/ServiceBus.d.ts.map +1 -1
  88. package/dist/adapters/ServiceBus.js +13 -15
  89. package/dist/adapters/cosmos-client.d.ts +2 -2
  90. package/dist/adapters/cosmos-client.d.ts.map +1 -1
  91. package/dist/adapters/cosmos-client.js +3 -3
  92. package/dist/adapters/logger.d.ts.map +1 -1
  93. package/dist/adapters/memQueue.d.ts +2 -2
  94. package/dist/adapters/memQueue.d.ts.map +1 -1
  95. package/dist/adapters/memQueue.js +3 -3
  96. package/dist/adapters/mongo-client.d.ts +2 -2
  97. package/dist/adapters/mongo-client.d.ts.map +1 -1
  98. package/dist/adapters/mongo-client.js +3 -3
  99. package/dist/adapters/redis-client.d.ts +2 -2
  100. package/dist/adapters/redis-client.d.ts.map +1 -1
  101. package/dist/adapters/redis-client.js +3 -3
  102. package/dist/api/ContextProvider.d.ts +6 -6
  103. package/dist/api/ContextProvider.d.ts.map +1 -1
  104. package/dist/api/ContextProvider.js +6 -6
  105. package/dist/api/internal/auth.d.ts +42 -4
  106. package/dist/api/internal/auth.d.ts.map +1 -1
  107. package/dist/api/internal/auth.js +160 -29
  108. package/dist/api/internal/events.d.ts +2 -2
  109. package/dist/api/internal/events.d.ts.map +1 -1
  110. package/dist/api/internal/events.js +11 -7
  111. package/dist/api/layerUtils.d.ts +5 -5
  112. package/dist/api/layerUtils.d.ts.map +1 -1
  113. package/dist/api/layerUtils.js +5 -5
  114. package/dist/api/routing/middleware/RouterMiddleware.d.ts +3 -3
  115. package/dist/api/routing/middleware/RouterMiddleware.d.ts.map +1 -1
  116. package/dist/api/routing/middleware/middleware.d.ts +35 -1
  117. package/dist/api/routing/middleware/middleware.d.ts.map +1 -1
  118. package/dist/api/routing/middleware/middleware.js +39 -1
  119. package/dist/api/routing.d.ts +1 -5
  120. package/dist/api/routing.d.ts.map +1 -1
  121. package/dist/api/routing.js +3 -2
  122. package/dist/api/setupRequest.d.ts +6 -3
  123. package/dist/api/setupRequest.d.ts.map +1 -1
  124. package/dist/api/setupRequest.js +11 -6
  125. package/dist/errorReporter.d.ts +3 -3
  126. package/dist/errorReporter.d.ts.map +1 -1
  127. package/dist/errorReporter.js +16 -23
  128. package/dist/logger.d.ts.map +1 -1
  129. package/dist/rateLimit.d.ts +8 -2
  130. package/dist/rateLimit.d.ts.map +1 -1
  131. package/dist/rateLimit.js +5 -11
  132. package/examples/query.ts +30 -26
  133. package/package.json +36 -22
  134. package/src/CUPS.ts +9 -11
  135. package/src/Emailer/service.ts +2 -2
  136. package/src/MainFiberSet.ts +2 -2
  137. package/src/Model/Repository/Registry.ts +33 -0
  138. package/src/Model/Repository/ext.ts +93 -6
  139. package/src/Model/Repository/internal/internal.ts +84 -76
  140. package/src/Model/Repository/makeRepo.ts +11 -8
  141. package/src/Model/Repository/service.ts +31 -22
  142. package/src/Model/Repository.ts +1 -0
  143. package/src/Model/query/new-kid-interpreter.ts +2 -2
  144. package/src/Model.ts +1 -0
  145. package/src/Operations.ts +78 -113
  146. package/src/OperationsRepo.ts +2 -2
  147. package/src/QueueMaker/SQLQueue.ts +121 -151
  148. package/src/QueueMaker/memQueue.ts +82 -103
  149. package/src/QueueMaker/sbqueue.ts +55 -85
  150. package/src/RequestContext.ts +4 -4
  151. package/src/RequestFiberSet.ts +4 -4
  152. package/src/Store/ContextMapContainer.ts +41 -2
  153. package/src/Store/Cosmos.ts +437 -343
  154. package/src/Store/Disk.ts +52 -49
  155. package/src/Store/Memory.ts +54 -48
  156. package/src/Store/SQL/Pg.ts +318 -0
  157. package/src/Store/SQL/query.ts +409 -0
  158. package/src/Store/SQL.ts +668 -0
  159. package/src/Store/index.ts +17 -2
  160. package/src/Store/service.ts +31 -7
  161. package/src/Store/utils.ts +23 -22
  162. package/src/adapters/ServiceBus.ts +111 -115
  163. package/src/adapters/cosmos-client.ts +2 -2
  164. package/src/adapters/memQueue.ts +2 -2
  165. package/src/adapters/mongo-client.ts +2 -2
  166. package/src/adapters/redis-client.ts +2 -2
  167. package/src/api/ContextProvider.ts +11 -11
  168. package/src/api/internal/auth.ts +246 -44
  169. package/src/api/internal/events.ts +14 -9
  170. package/src/api/layerUtils.ts +8 -8
  171. package/src/api/routing/middleware/RouterMiddleware.ts +4 -4
  172. package/src/api/routing/middleware/middleware.ts +42 -0
  173. package/src/api/routing.ts +4 -4
  174. package/src/api/setupRequest.ts +27 -7
  175. package/src/errorReporter.ts +58 -72
  176. package/src/rateLimit.ts +30 -22
  177. package/test/auth.test.ts +101 -0
  178. package/test/contextProvider.test.ts +11 -11
  179. package/test/controller.test.ts +18 -13
  180. package/test/dist/auth.test.d.ts.map +1 -0
  181. package/test/dist/contextProvider.test.d.ts.map +1 -1
  182. package/test/dist/controller.test.d.ts.map +1 -1
  183. package/test/dist/date-query.test.d.ts.map +1 -0
  184. package/test/dist/fixtures.d.ts +19 -9
  185. package/test/dist/fixtures.d.ts.map +1 -1
  186. package/test/dist/fixtures.js +11 -9
  187. package/test/dist/query.test.d.ts.map +1 -1
  188. package/test/dist/rawQuery.test.d.ts.map +1 -1
  189. package/test/dist/repository-ext.test.d.ts.map +1 -0
  190. package/test/dist/requires.test.d.ts.map +1 -1
  191. package/test/dist/rpc-multi-middleware.test.d.ts.map +1 -1
  192. package/test/dist/sql-store.test.d.ts.map +1 -0
  193. package/test/fixtures.ts +10 -8
  194. package/test/query.test.ts +209 -31
  195. package/test/rawQuery.test.ts +23 -19
  196. package/test/repository-ext.test.ts +58 -0
  197. package/test/requires.test.ts +6 -5
  198. package/test/rpc-multi-middleware.test.ts +72 -3
  199. package/test/sql-store.test.ts +1064 -0
  200. package/test/validateSample.test.ts +12 -9
  201. package/tsconfig.json +0 -1
@@ -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,7 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  import type {} from "effect/Equal"
3
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"
4
+ import { Array, Chunk, Context, Effect, Equivalence, flow, type NonEmptyReadonlyArray, Option, pipe, Pipeable, PubSub, Result, S, SchemaAST, Unify } from "effect-app"
5
5
  import { toNonEmptyArray } from "effect-app/Array"
6
6
  import { NotFoundError } from "effect-app/client/errors"
7
7
  import { flatMapOption } from "effect-app/Effect"
@@ -55,14 +55,14 @@ export function makeRepoInternal<
55
55
 
56
56
  function make<RInitial = never, E = never, RPublish = never, RCtx = never>(
57
57
  args: [Evt] extends [never] ? {
58
- schemaContext?: ServiceMap.ServiceMap<RCtx>
58
+ schemaContext?: Context.Context<RCtx>
59
59
  makeInitial?: Effect.Effect<readonly T[], E, RInitial> | undefined
60
60
  config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
61
61
  partitionValue?: (e?: Encoded) => string
62
62
  }
63
63
  }
64
64
  : {
65
- schemaContext?: ServiceMap.ServiceMap<RCtx>
65
+ schemaContext?: Context.Context<RCtx>
66
66
  publishEvents: (evt: NonEmptyReadonlyArray<Evt>) => Effect.Effect<void, never, RPublish>
67
67
  makeInitial?: Effect.Effect<readonly T[], E, RInitial> | undefined
68
68
  config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
@@ -72,12 +72,12 @@ export function makeRepoInternal<
72
72
  ) {
73
73
  return Effect
74
74
  .gen(function*() {
75
- const rctx: ServiceMap.ServiceMap<RCtx> = args.schemaContext ?? ServiceMap.empty() as any
75
+ const rctx: Context.Context<RCtx> = args.schemaContext ?? Context.empty() as any
76
76
  const provideRctx = Effect.provide(rctx)
77
77
  const encodeMany = flow(
78
78
  S.encodeEffect(S.Array(schema)),
79
79
  provideRctx,
80
- Effect.withSpan("encodeMany", {}, { captureStackTrace: false })
80
+ Effect.withSpan("encodeMany", { attributes: { itemType: name } }, { captureStackTrace: false })
81
81
  )
82
82
  const decode = flow(S.decodeEffect(schema), provideRctx)
83
83
  const decodeMany = flow(
@@ -113,11 +113,14 @@ export function makeRepoInternal<
113
113
  let ast = _.ast
114
114
  if (ast._tag === "Declaration") ast = ast.typeParameters[0]!
115
115
 
116
- // In v4, to get the encoded (from) side of a schema, use SchemaAST.toEncoded
117
116
  const pickIdFromAst = (a: SchemaAST.AST) => {
118
- const encoded = SchemaAST.toEncoded(a)
119
- if (SchemaAST.isObjects(encoded)) {
120
- const field = encoded.propertySignatures.find((_) => _.name === idKey)
117
+ // Unwrap Declaration (e.g. TaggedClass) to get the underlying Objects AST
118
+ let inner = a
119
+ if (inner._tag === "Declaration") inner = inner.typeParameters[0]!
120
+ // Pick from the original AST to preserve the full encoding chain (e.g. decodeTo transformations).
121
+ // Using toEncoded would lose transformation info needed to encode Type -> Encoded.
122
+ if (SchemaAST.isObjects(inner)) {
123
+ const field = inner.propertySignatures.find((_) => _.name === idKey)
121
124
  if (field) {
122
125
  return S.Struct({ [idKey]: S.make(field.type) }) as unknown as Codec<T, Encoded>
123
126
  }
@@ -161,7 +164,7 @@ export function makeRepoInternal<
161
164
  )
162
165
  })
163
166
 
164
- const find = Effect.fn("find")(function*(id: T[IdKey]) {
167
+ const find = Effect.fn("find", { attributes: { itemType: name } })(function*(id: T[IdKey]) {
165
168
  yield* Effect.annotateCurrentSpan({ itemId: id })
166
169
 
167
170
  return yield* flatMapOption(findE(id), (_) => Effect.orDie(decode(_)))
@@ -188,73 +191,76 @@ export function makeRepoInternal<
188
191
  Effect.andThen(saveAllE)
189
192
  )
190
193
 
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
- })
194
+ const saveAndPublish = Effect.fn("saveAndPublish", { attributes: { itemType: name } })(
195
+ function*(items: Iterable<T>, events: Iterable<Evt> = []) {
196
+ const it = Chunk.fromIterable(items)
197
+ const evts = [...events]
198
+ yield* Effect.annotateCurrentSpan({ itemIds: [...Chunk.map(it, (_) => _[idKey])], events: evts.length })
199
+ return yield* saveAll(it)
200
+ .pipe(
201
+ Effect.andThen(Effect.sync(() => toNonEmptyArray(evts))),
202
+ // TODO: for full consistency the events should be stored within the same database transaction, and then picked up.
203
+ (_) => flatMapOption(_, pub),
204
+ Effect.andThen(PubSub.publish(changeFeed, [Chunk.toArray(it), "save"] as [T[], "save" | "remove"])),
205
+ Effect.asVoid
206
+ )
207
+ }
208
+ )
204
209
 
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))
210
+ const removeAndPublish = Effect.fn("removeAndPublish", { attributes: { itemType: name } })(
211
+ function*(a: Iterable<T>, events: Iterable<Evt> = []) {
212
+ const { set } = yield* cms
213
+ const it = [...a]
214
+ const evts = [...events]
215
+ yield* Effect.annotateCurrentSpan({ itemIds: it.map((_) => _[idKey]), eventCount: evts.length })
216
+ const items = yield* encodeMany(it).pipe(Effect.orDie)
217
+ if (Array.isReadonlyArrayNonEmpty(items)) {
218
+ yield* store.batchRemove(
219
+ items.map((_) => (_[idKey])),
220
+ args.config?.partitionValue?.(items[0])
221
+ )
222
+ for (const e of items) {
223
+ set(e[idKey], undefined)
224
+ }
225
+ yield* Effect
226
+ .sync(() => toNonEmptyArray(evts))
227
+ // TODO: for full consistency the events should be stored within the same database transaction, and then picked up.
228
+ .pipe((_) => flatMapOption(_, pub))
223
229
 
224
- yield* PubSub.publish(changeFeed, [it, "remove"] as [T[], "save" | "remove"])
230
+ yield* PubSub.publish(changeFeed, [it, "remove"] as [T[], "save" | "remove"])
231
+ }
225
232
  }
226
- })
233
+ )
227
234
 
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)
235
+ const removeById = Effect.fn("removeById", { attributes: { itemType: name } })(
236
+ function*(idOrIds: T[IdKey] | ReadonlyArray<T[IdKey]>) {
237
+ const ids = globalThis.Array.isArray(idOrIds)
238
+ ? idOrIds as readonly T[IdKey][]
239
+ : [idOrIds as T[IdKey]]
240
+ if (!Array.isReadonlyArrayNonEmpty(ids)) {
241
+ return
242
+ }
243
+ const { set } = yield* cms
244
+ const eids = yield* Effect.forEach(ids, (_) => encodeIdOnly(_ as any)).pipe(Effect.orDie)
245
+ yield* Effect.annotateCurrentSpan({ itemIds: eids })
246
+ yield* store.batchRemove(eids)
247
+ for (const id of eids) {
248
+ set(id, undefined)
249
+ }
250
+ yield* PubSub.publish(changeFeed, [[], "remove"] as [T[], "save" | "remove"])
238
251
  }
239
- yield* PubSub.publish(changeFeed, [[], "remove"] as [T[], "save" | "remove"])
240
- })
252
+ )
241
253
 
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 })))
254
+ const parseMany = Effect.fn("parseMany", { attributes: { itemType: name } })(function*(items: readonly PM[]) {
255
+ const cm = yield* cms
256
+ return yield* decodeMany(items.map((_) => mapReverse(_, cm.set))).pipe(Effect.orDie)
257
+ })
258
+ const parseMany2 = Effect.fn("parseMany2", { attributes: { itemType: name } })(
259
+ function*<A, R>(items: readonly PM[], schema: S.Codec<A, Encoded, R>) {
260
+ const cm = yield* cms
261
+ return yield* S.decodeEffect(S.Array(schema))(items.map((_) => mapReverse(_, cm.set))).pipe(Effect.orDie)
262
+ }
263
+ )
258
264
  const filter = <U extends keyof Encoded = keyof Encoded>(args: FilterArgs<Encoded, U>) =>
259
265
  store
260
266
  .filter(
@@ -276,10 +282,10 @@ export function makeRepoInternal<
276
282
  const query: {
277
283
  <A, R, From extends FieldValues>(
278
284
  q: Q.QueryProjection<Encoded extends From ? From : never, A, R>
279
- ): Effect.Effect<readonly A[], S.SchemaError, R>
285
+ ): Effect.Effect<readonly A[], S.SchemaError, Exclude<R, RCtx>>
280
286
  <A, R, EncodedRefined extends Encoded = Encoded>(
281
287
  q: Q.QAll<NoInfer<Encoded>, NoInfer<EncodedRefined>, A, R>
282
- ): Effect.Effect<readonly A[], never, R>
288
+ ): Effect.Effect<readonly A[], never, Exclude<R, RCtx>>
283
289
  } = (<A, R, EncodedRefined extends Encoded = Encoded>(q: Q.QAll<Encoded, EncodedRefined, A, R>) => {
284
290
  const a = Q.toFilter(q)
285
291
  const eff = a.mode === "project"
@@ -327,6 +333,7 @@ export function makeRepoInternal<
327
333
  : eff,
328
334
  Effect.withSpan("Repository.query [effect-app/infra]", {
329
335
  attributes: {
336
+ itemType: name,
330
337
  "repository.model_name": name,
331
338
  query: { ...a, schema: a.schema ? "__SCHEMA__" : a.schema, filter: a.filter }
332
339
  }
@@ -334,7 +341,7 @@ export function makeRepoInternal<
334
341
  )
335
342
  }) as any
336
343
 
337
- const validateSample = Effect.fn("validateSample")(function*(options?: {
344
+ const validateSample = Effect.fn("validateSample", { attributes: { itemType: name } })(function*(options?: {
338
345
  percentage?: number
339
346
  maxItems?: number
340
347
  }) {
@@ -401,6 +408,7 @@ export function makeRepoInternal<
401
408
  saveAndPublish,
402
409
  removeAndPublish,
403
410
  removeById,
411
+ seedNamespace: (namespace: string) => store.seedNamespace(namespace),
404
412
  validateSample,
405
413
  queryRaw<A, Out, QR>(schema: S.Codec<A, Out, QR>, q: Q.RawQuery<Encoded, Out>) {
406
414
  const dec = S.decodeEffect(S.Array(schema))
@@ -441,12 +449,12 @@ export function makeRepoInternal<
441
449
  // },
442
450
  save: (...xes: any[]) =>
443
451
  Effect.flatMap(encMany(xes), (_) => saveAllE(_)).pipe(
444
- Effect.withSpan("mapped.save", {}, { captureStackTrace: false })
452
+ Effect.withSpan("mapped.save", { attributes: { itemType: name } }, { captureStackTrace: false })
445
453
  )
446
454
  }
447
455
  }
448
456
  }
449
- return r as Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<R, RCtx>, RPublish>
457
+ return r as Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<R, RCtx>, RPublish, RCtx>
450
458
  })
451
459
  .pipe(Effect
452
460
  // .withSpan("Repository.make [effect-app/infra]", { attributes: { "repository.model_name": name } })
@@ -7,11 +7,12 @@
7
7
  // import type { ParserEnv } from "effect-app/Schema/custom/Parser"
8
8
  import type {} from "effect/Equal"
9
9
  import type {} from "effect/Hash"
10
- import { Effect, type NonEmptyReadonlyArray, type S, type ServiceMap } from "effect-app"
10
+ import { type Context, Effect, type NonEmptyReadonlyArray, type S } from "effect-app"
11
11
  import type { StoreConfig, StoreMaker } from "../../Store.js"
12
12
  import type { FieldValues } from "../filter/types.js"
13
13
  import { type ExtendedRepository, extendRepo } from "./ext.js"
14
14
  import { makeRepoInternal } from "./internal/internal.js"
15
+ import { RepositoryRegistry } from "./Registry.js"
15
16
  import type { Repository } from "./service.js"
16
17
 
17
18
  export interface RepositoryOptions<
@@ -52,11 +53,11 @@ export interface RepositoryOptions<
52
53
  * Optional context to be provided to Schema decode/encode.
53
54
  * Useful for effectful transformations like XWithItems, where items is a transformation retrieving elements from another database table or other source.
54
55
  */
55
- schemaContext?: ServiceMap.ServiceMap<RCtx>
56
+ schemaContext?: Context.Context<RCtx>
56
57
 
57
58
  overrides?: (
58
- repo: Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<RSchema, RCtx>, RPublish>
59
- ) => Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<RSchema, RCtx>, RPublish>
59
+ repo: Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<RSchema, RCtx>, RPublish, RCtx>
60
+ ) => Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<RSchema, RCtx>, RPublish, RCtx>
60
61
  }
61
62
 
62
63
  /**
@@ -83,9 +84,9 @@ export const makeRepo: {
83
84
  schema: S.Codec<T, Encoded, RSchema>,
84
85
  options: RepositoryOptions<IdKey, Encoded, T, ItemType, Evt, RPublish, E, RInitial, RCtx, RSchema>
85
86
  ): Effect.Effect<
86
- ExtendedRepository<T, Encoded, Evt, ItemType, IdKey, Exclude<RSchema, RCtx>, RPublish>,
87
+ ExtendedRepository<T, Encoded, Evt, ItemType, IdKey, Exclude<RSchema, RCtx>, RPublish, RCtx>,
87
88
  E,
88
- RInitial | StoreMaker
89
+ RInitial | StoreMaker | RepositoryRegistry
89
90
  >
90
91
  <
91
92
  ItemType extends string,
@@ -102,9 +103,9 @@ export const makeRepo: {
102
103
  schema: S.Codec<T, Encoded, RSchema>,
103
104
  options: Omit<RepositoryOptions<"id", Encoded, T, ItemType, Evt, RPublish, E, RInitial, RCtx, RSchema>, "idKey">
104
105
  ): Effect.Effect<
105
- ExtendedRepository<T, Encoded, Evt, ItemType, "id", Exclude<RSchema, RCtx>, RPublish>,
106
+ ExtendedRepository<T, Encoded, Evt, ItemType, "id", Exclude<RSchema, RCtx>, RPublish, RCtx>,
106
107
  E,
107
- RInitial | StoreMaker
108
+ RInitial | StoreMaker | RepositoryRegistry
108
109
  >
109
110
  } = <
110
111
  ItemType extends string,
@@ -135,5 +136,7 @@ export const makeRepo: {
135
136
  let r = yield* mkRepo.make<RInitial, E, RPublish, RCtx>(options as any)
136
137
  if (options.overrides) r = options.overrides(r)
137
138
  const repo = extendRepo(r)
139
+ const registry = yield* RepositoryRegistry
140
+ registry.register(itemType, repo)
138
141
  return repo
139
142
  })