effect-app 4.0.0-beta.247 → 4.0.0-beta.249

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 (140) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/Emailer.d.ts +51 -0
  3. package/dist/Emailer.d.ts.map +1 -0
  4. package/dist/Emailer.js +7 -0
  5. package/dist/Model/Repository/Registry.d.ts +21 -0
  6. package/dist/Model/Repository/Registry.d.ts.map +1 -0
  7. package/dist/Model/Repository/Registry.js +18 -0
  8. package/dist/Model/Repository/ext.d.ts +60 -0
  9. package/dist/Model/Repository/ext.d.ts.map +1 -0
  10. package/dist/Model/Repository/ext.js +122 -0
  11. package/dist/Model/Repository/internal/internal.d.ts +62 -0
  12. package/dist/Model/Repository/internal/internal.d.ts.map +1 -0
  13. package/dist/Model/Repository/internal/internal.js +413 -0
  14. package/dist/Model/Repository/legacy.d.ts +21 -0
  15. package/dist/Model/Repository/legacy.d.ts.map +1 -0
  16. package/dist/Model/Repository/legacy.js +2 -0
  17. package/dist/Model/Repository/makeRepo.d.ts +53 -0
  18. package/dist/Model/Repository/makeRepo.d.ts.map +1 -0
  19. package/dist/Model/Repository/makeRepo.js +27 -0
  20. package/dist/Model/Repository/service.d.ts +97 -0
  21. package/dist/Model/Repository/service.d.ts.map +1 -0
  22. package/dist/Model/Repository/service.js +2 -0
  23. package/dist/Model/Repository/validation.d.ts +71 -0
  24. package/dist/Model/Repository/validation.d.ts.map +1 -0
  25. package/dist/Model/Repository/validation.js +32 -0
  26. package/dist/Model/Repository.d.ts +7 -0
  27. package/dist/Model/Repository.d.ts.map +1 -0
  28. package/dist/Model/Repository.js +7 -0
  29. package/dist/Model/dsl.d.ts +33 -0
  30. package/dist/Model/dsl.d.ts.map +1 -0
  31. package/dist/Model/dsl.js +43 -0
  32. package/dist/Model/filter/filterApi.d.ts +30 -0
  33. package/dist/Model/filter/filterApi.d.ts.map +1 -0
  34. package/dist/Model/filter/filterApi.js +2 -0
  35. package/dist/Model/filter/types/errors.d.ts +29 -0
  36. package/dist/Model/filter/types/errors.d.ts.map +1 -0
  37. package/dist/Model/filter/types/errors.js +2 -0
  38. package/dist/Model/filter/types/fields.d.ts +15 -0
  39. package/dist/Model/filter/types/fields.d.ts.map +1 -0
  40. package/dist/Model/filter/types/fields.js +2 -0
  41. package/dist/Model/filter/types/path/common.d.ts +316 -0
  42. package/dist/Model/filter/types/path/common.d.ts.map +1 -0
  43. package/dist/Model/filter/types/path/common.js +2 -0
  44. package/dist/Model/filter/types/path/eager.d.ts +95 -0
  45. package/dist/Model/filter/types/path/eager.d.ts.map +1 -0
  46. package/dist/Model/filter/types/path/eager.js +31 -0
  47. package/dist/Model/filter/types/path/index.d.ts +4 -0
  48. package/dist/Model/filter/types/path/index.d.ts.map +1 -0
  49. package/dist/Model/filter/types/path/index.js +3 -0
  50. package/dist/Model/filter/types/utils.d.ts +79 -0
  51. package/dist/Model/filter/types/utils.d.ts.map +1 -0
  52. package/dist/Model/filter/types/utils.js +2 -0
  53. package/dist/Model/filter/types/validator.d.ts +30 -0
  54. package/dist/Model/filter/types/validator.d.ts.map +1 -0
  55. package/dist/Model/filter/types/validator.js +2 -0
  56. package/dist/Model/filter/types.d.ts +5 -0
  57. package/dist/Model/filter/types.d.ts.map +1 -0
  58. package/dist/Model/filter/types.js +7 -0
  59. package/dist/Model/query/dsl.d.ts +446 -0
  60. package/dist/Model/query/dsl.d.ts.map +1 -0
  61. package/dist/Model/query/dsl.js +342 -0
  62. package/dist/Model/query/new-kid-interpreter.d.ts +136 -0
  63. package/dist/Model/query/new-kid-interpreter.d.ts.map +1 -0
  64. package/dist/Model/query/new-kid-interpreter.js +336 -0
  65. package/dist/Model/query.d.ts +15 -0
  66. package/dist/Model/query.d.ts.map +1 -0
  67. package/dist/Model/query.js +3 -0
  68. package/dist/Model.d.ts +5 -0
  69. package/dist/Model.d.ts.map +1 -0
  70. package/dist/Model.js +5 -0
  71. package/dist/QueueMaker.d.ts +13 -0
  72. package/dist/QueueMaker.d.ts.map +1 -0
  73. package/dist/QueueMaker.js +4 -0
  74. package/dist/RequestContext.d.ts +103 -0
  75. package/dist/RequestContext.d.ts.map +1 -0
  76. package/dist/RequestContext.js +49 -0
  77. package/dist/Schema/ext.d.ts +9 -9
  78. package/dist/Schema/ext.d.ts.map +1 -1
  79. package/dist/Schema/ext.js +1 -1
  80. package/dist/Store.d.ts +147 -0
  81. package/dist/Store.d.ts.map +1 -0
  82. package/dist/Store.js +95 -0
  83. package/dist/client/apiClientFactory.d.ts +1 -1
  84. package/dist/client/clientFor.d.ts +5 -5
  85. package/dist/client/clientFor.d.ts.map +1 -1
  86. package/dist/client/makeClient.d.ts +36 -36
  87. package/dist/client/makeClient.d.ts.map +1 -1
  88. package/dist/index.d.ts +3 -1
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js +3 -1
  91. package/dist/rpc/MiddlewareMaker.d.ts +2 -2
  92. package/dist/rpc/MiddlewareMaker.d.ts.map +1 -1
  93. package/dist/rpc/RpcMiddleware.d.ts +2 -2
  94. package/dist/rpc/RpcMiddleware.d.ts.map +1 -1
  95. package/dist/runtime.d.ts +19 -0
  96. package/dist/runtime.d.ts.map +1 -0
  97. package/dist/runtime.js +40 -0
  98. package/dist/toast.d.ts +51 -0
  99. package/dist/toast.d.ts.map +1 -0
  100. package/dist/toast.js +34 -0
  101. package/dist/withToast.d.ts +30 -0
  102. package/dist/withToast.d.ts.map +1 -0
  103. package/dist/withToast.js +64 -0
  104. package/package.json +113 -1
  105. package/src/Emailer.ts +51 -0
  106. package/src/Model/Repository/Registry.ts +34 -0
  107. package/src/Model/Repository/ext.ts +375 -0
  108. package/src/Model/Repository/internal/internal.ts +708 -0
  109. package/src/Model/Repository/legacy.ts +29 -0
  110. package/src/Model/Repository/makeRepo.ts +144 -0
  111. package/src/Model/Repository/service.ts +639 -0
  112. package/src/Model/Repository/validation.ts +31 -0
  113. package/src/Model/Repository.ts +6 -0
  114. package/src/Model/dsl.ts +129 -0
  115. package/src/Model/filter/filterApi.ts +60 -0
  116. package/src/Model/filter/types/errors.ts +47 -0
  117. package/src/Model/filter/types/fields.ts +50 -0
  118. package/src/Model/filter/types/path/common.ts +404 -0
  119. package/src/Model/filter/types/path/eager.ts +297 -0
  120. package/src/Model/filter/types/path/index.ts +4 -0
  121. package/src/Model/filter/types/utils.ts +128 -0
  122. package/src/Model/filter/types/validator.ts +46 -0
  123. package/src/Model/filter/types.ts +6 -0
  124. package/src/Model/query/dsl.ts +2546 -0
  125. package/src/Model/query/new-kid-interpreter.ts +484 -0
  126. package/src/Model/query.ts +13 -0
  127. package/src/Model.ts +4 -0
  128. package/src/QueueMaker.ts +19 -0
  129. package/src/RequestContext.ts +62 -0
  130. package/src/Schema/ext.ts +6 -6
  131. package/src/Store.ts +243 -0
  132. package/src/client/clientFor.ts +8 -8
  133. package/src/client/makeClient.ts +11 -11
  134. package/src/index.ts +2 -0
  135. package/src/rpc/MiddlewareMaker.ts +1 -1
  136. package/src/rpc/RpcMiddleware.ts +1 -1
  137. package/src/runtime.ts +56 -0
  138. package/src/toast.ts +54 -0
  139. package/src/withToast.ts +133 -0
  140. package/test/dist/rpc-dynamic-middleware.test.d.ts.map +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-app",
3
- "version": "4.0.0-beta.247",
3
+ "version": "4.0.0-beta.249",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "dependencies": {
@@ -66,6 +66,10 @@
66
66
  "types": "./dist/Effect.d.ts",
67
67
  "default": "./dist/Effect.js"
68
68
  },
69
+ "./Emailer": {
70
+ "types": "./dist/Emailer.d.ts",
71
+ "default": "./dist/Emailer.js"
72
+ },
69
73
  "./Function": {
70
74
  "types": "./dist/Function.d.ts",
71
75
  "default": "./dist/Function.js"
@@ -78,6 +82,90 @@
78
82
  "types": "./dist/Layer.d.ts",
79
83
  "default": "./dist/Layer.js"
80
84
  },
85
+ "./Model": {
86
+ "types": "./dist/Model.d.ts",
87
+ "default": "./dist/Model.js"
88
+ },
89
+ "./Model/Repository": {
90
+ "types": "./dist/Model/Repository.d.ts",
91
+ "default": "./dist/Model/Repository.js"
92
+ },
93
+ "./Model/Repository/Registry": {
94
+ "types": "./dist/Model/Repository/Registry.d.ts",
95
+ "default": "./dist/Model/Repository/Registry.js"
96
+ },
97
+ "./Model/Repository/ext": {
98
+ "types": "./dist/Model/Repository/ext.d.ts",
99
+ "default": "./dist/Model/Repository/ext.js"
100
+ },
101
+ "./Model/Repository/legacy": {
102
+ "types": "./dist/Model/Repository/legacy.d.ts",
103
+ "default": "./dist/Model/Repository/legacy.js"
104
+ },
105
+ "./Model/Repository/makeRepo": {
106
+ "types": "./dist/Model/Repository/makeRepo.d.ts",
107
+ "default": "./dist/Model/Repository/makeRepo.js"
108
+ },
109
+ "./Model/Repository/service": {
110
+ "types": "./dist/Model/Repository/service.d.ts",
111
+ "default": "./dist/Model/Repository/service.js"
112
+ },
113
+ "./Model/Repository/validation": {
114
+ "types": "./dist/Model/Repository/validation.d.ts",
115
+ "default": "./dist/Model/Repository/validation.js"
116
+ },
117
+ "./Model/dsl": {
118
+ "types": "./dist/Model/dsl.d.ts",
119
+ "default": "./dist/Model/dsl.js"
120
+ },
121
+ "./Model/filter/filterApi": {
122
+ "types": "./dist/Model/filter/filterApi.d.ts",
123
+ "default": "./dist/Model/filter/filterApi.js"
124
+ },
125
+ "./Model/filter/types": {
126
+ "types": "./dist/Model/filter/types.d.ts",
127
+ "default": "./dist/Model/filter/types.js"
128
+ },
129
+ "./Model/filter/types/errors": {
130
+ "types": "./dist/Model/filter/types/errors.d.ts",
131
+ "default": "./dist/Model/filter/types/errors.js"
132
+ },
133
+ "./Model/filter/types/fields": {
134
+ "types": "./dist/Model/filter/types/fields.d.ts",
135
+ "default": "./dist/Model/filter/types/fields.js"
136
+ },
137
+ "./Model/filter/types/path/common": {
138
+ "types": "./dist/Model/filter/types/path/common.d.ts",
139
+ "default": "./dist/Model/filter/types/path/common.js"
140
+ },
141
+ "./Model/filter/types/path/eager": {
142
+ "types": "./dist/Model/filter/types/path/eager.d.ts",
143
+ "default": "./dist/Model/filter/types/path/eager.js"
144
+ },
145
+ "./Model/filter/types/path/index": {
146
+ "types": "./dist/Model/filter/types/path/index.d.ts",
147
+ "default": "./dist/Model/filter/types/path/index.js"
148
+ },
149
+ "./Model/filter/types/utils": {
150
+ "types": "./dist/Model/filter/types/utils.d.ts",
151
+ "default": "./dist/Model/filter/types/utils.js"
152
+ },
153
+ "./Model/filter/types/validator": {
154
+ "types": "./dist/Model/filter/types/validator.d.ts",
155
+ "default": "./dist/Model/filter/types/validator.js"
156
+ },
157
+ "./Model/query": {
158
+ "types": "./dist/Model/query.d.ts",
159
+ "default": "./dist/Model/query.js"
160
+ },
161
+ "./Model/query/dsl": {
162
+ "types": "./dist/Model/query/dsl.d.ts",
163
+ "default": "./dist/Model/query/dsl.js"
164
+ },
165
+ "./Model/query/new-kid-interpreter": {
166
+ "types": "./dist/Model/query/new-kid-interpreter.d.ts",
167
+ "default": "./dist/Model/query/new-kid-interpreter.js"
168
+ },
81
169
  "./NonEmptySet": {
82
170
  "types": "./dist/NonEmptySet.d.ts",
83
171
  "default": "./dist/NonEmptySet.js"
@@ -90,6 +178,14 @@
90
178
  "types": "./dist/Pure.d.ts",
91
179
  "default": "./dist/Pure.js"
92
180
  },
181
+ "./QueueMaker": {
182
+ "types": "./dist/QueueMaker.d.ts",
183
+ "default": "./dist/QueueMaker.js"
184
+ },
185
+ "./RequestContext": {
186
+ "types": "./dist/RequestContext.d.ts",
187
+ "default": "./dist/RequestContext.js"
188
+ },
93
189
  "./Schema": {
94
190
  "types": "./dist/Schema.d.ts",
95
191
  "default": "./dist/Schema.js"
@@ -154,6 +250,10 @@
154
250
  "types": "./dist/Set.d.ts",
155
251
  "default": "./dist/Set.js"
156
252
  },
253
+ "./Store": {
254
+ "types": "./dist/Store.d.ts",
255
+ "default": "./dist/Store.js"
256
+ },
157
257
  "./TypeTest": {
158
258
  "types": "./dist/TypeTest.d.ts",
159
259
  "default": "./dist/TypeTest.js"
@@ -254,6 +354,14 @@
254
354
  "types": "./dist/rpc/RpcMiddleware.d.ts",
255
355
  "default": "./dist/rpc/RpcMiddleware.js"
256
356
  },
357
+ "./runtime": {
358
+ "types": "./dist/runtime.d.ts",
359
+ "default": "./dist/runtime.js"
360
+ },
361
+ "./toast": {
362
+ "types": "./dist/toast.d.ts",
363
+ "default": "./dist/toast.js"
364
+ },
257
365
  "./transform": {
258
366
  "types": "./dist/transform.d.ts",
259
367
  "default": "./dist/transform.js"
@@ -289,6 +397,10 @@
289
397
  "./validation/validators": {
290
398
  "types": "./dist/validation/validators.d.ts",
291
399
  "default": "./dist/validation/validators.js"
400
+ },
401
+ "./withToast": {
402
+ "types": "./dist/withToast.d.ts",
403
+ "default": "./dist/withToast.js"
292
404
  }
293
405
  },
294
406
  "gitHead": "bd8e27eea3eff97db8739d577d67e7336c078d28",
package/src/Emailer.ts ADDED
@@ -0,0 +1,51 @@
1
+ import * as Data from "effect/Data"
2
+ import type { NonEmptyReadonlyArray } from "./Array.js"
3
+ import * as Context from "./Context.js"
4
+ import type * as Effect from "./Effect.js"
5
+ import type { Email } from "./Schema.js"
6
+
7
+ export class SendMailError extends Data.TaggedError("SendMailError")<{
8
+ readonly raw: Error
9
+ }> {}
10
+
11
+ export class Emailer extends Context.Opaque<Emailer, {
12
+ sendMail: (msg: EmailMsgOptionalFrom) => Effect.Effect<void, SendMailError>
13
+ }>()("effect-app/Emailer") {}
14
+
15
+ export type EmailData = Email | {
16
+ name?: string
17
+ email: Email
18
+ }
19
+
20
+ export interface EmailContentPart {
21
+ type: string
22
+ value: string
23
+ }
24
+
25
+ export type EmailRecipients = EmailData | NonEmptyReadonlyArray<EmailData>
26
+
27
+ export interface EmailMsgBase {
28
+ readonly to: EmailRecipients
29
+ readonly cc?: EmailRecipients
30
+ readonly bcc?: EmailRecipients
31
+ readonly from: EmailData
32
+ readonly replyTo?: EmailData
33
+ readonly subject?: string
34
+ /**
35
+ * should multiple `to` addresess be considered multiple emails?
36
+ * defaults to `true`, not to leak email addresses
37
+ */
38
+ readonly isMultiple?: boolean
39
+ }
40
+
41
+ export type EmailContent =
42
+ | { text: string }
43
+ | { html: string }
44
+ | { templateId: string }
45
+ | { content: NonEmptyReadonlyArray<EmailContentPart> }
46
+
47
+ export type EmailMsg =
48
+ & EmailMsgBase
49
+ & EmailContent
50
+
51
+ export type EmailMsgOptionalFrom = Omit<EmailMsgBase, "from"> & Partial<Pick<EmailMsg, "from">> & EmailContent
@@ -0,0 +1,34 @@
1
+ import * as Context from "../../Context.js"
2
+ import * as Effect from "../../Effect.js"
3
+
4
+ export interface RegisteredRepository {
5
+ readonly seedNamespace: (namespace: string) => Effect.Effect<void>
6
+ }
7
+
8
+ const make = Effect.sync(() => {
9
+ const repos = new Map<string, RegisteredRepository>()
10
+ return {
11
+ register(modelName: string, repo: RegisteredRepository) {
12
+ repos.set(modelName, repo)
13
+ },
14
+ seedNamespace: (namespace: string) =>
15
+ Effect.suspend(() =>
16
+ Effect.forEach(
17
+ repos.values(),
18
+ (r) => r.seedNamespace(namespace),
19
+ { concurrency: "unbounded", discard: true }
20
+ )
21
+ ),
22
+ get entries(): ReadonlyMap<string, RegisteredRepository> {
23
+ return repos
24
+ }
25
+ }
26
+ })
27
+
28
+ export class RepositoryRegistry extends Context.Opaque<RepositoryRegistry, {
29
+ readonly register: (modelName: string, repo: RegisteredRepository) => void
30
+ readonly seedNamespace: (namespace: string) => Effect.Effect<void>
31
+ readonly entries: ReadonlyMap<string, RegisteredRepository>
32
+ }>()("effect-app/RepositoryRegistry", { make }) {}
33
+
34
+ export const RepositoryRegistryLive = RepositoryRegistry.toLayer(RepositoryRegistry.make)
@@ -0,0 +1,375 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /* eslint-disable @typescript-eslint/no-unsafe-return */
3
+ import * as Exit from "effect/Exit"
4
+ import * as Request from "effect/Request"
5
+ import * as RequestResolver from "effect/RequestResolver"
6
+ import * as Array from "../../Array.js"
7
+ import type { NonEmptyArray } from "../../Array.js"
8
+ import { type InvalidStateError, NotFoundError, type OptimisticConcurrencyException } from "../../client/errors.js"
9
+ import * as Effect from "../../Effect.js"
10
+ import * as Option from "../../Option.js"
11
+ import { type FixEnv, type PureEnv, runTerm } from "../../Pure.js"
12
+ import { AnyPureDSL } from "../dsl.js"
13
+ import type { FieldValues } from "../filter/types.js"
14
+ import type { Query, QueryEnd, QueryWhere } from "../query.js"
15
+ import * as Q from "../query.js"
16
+ import type { Repository } from "./service.js"
17
+
18
+ interface BatchOptions {
19
+ readonly batch?: true | number
20
+ }
21
+
22
+ const asReadonlyArray = <T>(itemOrItems: T | ReadonlyArray<T>): ReadonlyArray<T> =>
23
+ globalThis.Array.isArray(itemOrItems)
24
+ ? itemOrItems as ReadonlyArray<T>
25
+ : [itemOrItems as T]
26
+
27
+ const getBatchSize = (batch?: true | number) =>
28
+ batch === true
29
+ ? 100
30
+ : typeof batch === "number" && Number.isFinite(batch) && batch > 0
31
+ ? Math.floor(batch)
32
+ : undefined
33
+
34
+ export const extendRepo = <
35
+ T,
36
+ Encoded extends FieldValues,
37
+ Evt,
38
+ ItemType extends string,
39
+ IdKey extends keyof T & keyof Encoded,
40
+ RSchema,
41
+ RPublish,
42
+ RProvided = never
43
+ >(
44
+ repo: Repository<T, Encoded, Evt, ItemType, IdKey, RSchema, RPublish, RProvided>
45
+ ) => {
46
+ const get = (id: T[IdKey]) =>
47
+ repo.find(id).pipe(
48
+ Effect.flatMap(Option.match({
49
+ onNone: () => Effect.fail(new NotFoundError<ItemType>({ type: repo.itemType, id })),
50
+ onSome: Effect.succeed
51
+ }))
52
+ )
53
+ function saveManyWithPure_<
54
+ R,
55
+ A,
56
+ E,
57
+ S1 extends T,
58
+ S2 extends T
59
+ >(
60
+ items: Iterable<S1>,
61
+ pure: Effect.Effect<A, E, FixEnv<R, Evt, readonly S1[], readonly S2[]>>
62
+ ) {
63
+ return saveAllWithEffectInt(
64
+ runTerm(pure, [...items])
65
+ )
66
+ }
67
+
68
+ function saveWithPure_<
69
+ R,
70
+ A,
71
+ E,
72
+ S1 extends T,
73
+ S2 extends T
74
+ >(
75
+ item: S1,
76
+ pure: Effect.Effect<A, E, FixEnv<R, Evt, S1, S2>>
77
+ ) {
78
+ return saveAllWithEffectInt(
79
+ runTerm(pure, item)
80
+ .pipe(Effect
81
+ .map(([item, events, a]) => [[item], events, a]))
82
+ )
83
+ }
84
+
85
+ function saveAllWithEffectInt<
86
+ P extends T,
87
+ R,
88
+ E,
89
+ A
90
+ >(
91
+ gen: Effect.Effect<readonly [Iterable<P>, Iterable<Evt>, A], E, R>
92
+ ) {
93
+ return Effect.flatMap(gen, ([items, events, a]) => repo.saveAndPublish(items, events).pipe(Effect.map(() => a)))
94
+ }
95
+
96
+ function saveManyWithPureBatched_<
97
+ R,
98
+ A,
99
+ E,
100
+ S1 extends T,
101
+ S2 extends T
102
+ >(
103
+ items: Iterable<S1>,
104
+ pure: Effect.Effect<A, E, FixEnv<R, Evt, readonly S1[], readonly S2[]>>,
105
+ batchSize = 100
106
+ ) {
107
+ return Effect.forEach(
108
+ Array.chunksOf(items, batchSize),
109
+ (batch) =>
110
+ saveAllWithEffectInt(
111
+ runTerm(pure, batch)
112
+ )
113
+ )
114
+ }
115
+
116
+ const queryAndSavePure: {
117
+ <A, E2, R2, T2 extends T>(
118
+ q: (
119
+ q: Query<Encoded>
120
+ ) => QueryEnd<Encoded, "one">,
121
+ pure: Effect.Effect<A, E2, FixEnv<R2, Evt, T, T2>>
122
+ ): Effect.Effect<
123
+ A,
124
+ InvalidStateError | OptimisticConcurrencyException | NotFoundError<ItemType> | E2,
125
+ Exclude<R2, {
126
+ env: PureEnv<Evt, T, T2>
127
+ }>
128
+ >
129
+ <A, E2, R2, T2 extends T>(
130
+ q: (
131
+ q: Query<Encoded>
132
+ ) =>
133
+ | Query<Encoded>
134
+ | QueryWhere<Encoded>
135
+ | QueryEnd<Encoded>,
136
+ pure: Effect.Effect<A, E2, FixEnv<R2, Evt, readonly T[], readonly T2[]>>
137
+ ): Effect.Effect<
138
+ A,
139
+ InvalidStateError | OptimisticConcurrencyException | E2,
140
+ | RSchema
141
+ | RPublish
142
+ | Exclude<R2, {
143
+ env: PureEnv<Evt, readonly T[], readonly T2[]>
144
+ }>
145
+ >
146
+ <A, E2, R2, T2 extends T>(
147
+ q: (
148
+ q: Query<Encoded>
149
+ ) =>
150
+ | Query<Encoded>
151
+ | QueryWhere<Encoded>
152
+ | QueryEnd<Encoded>,
153
+ pure: Effect.Effect<A, E2, FixEnv<R2, Evt, readonly T[], readonly T2[]>>,
154
+ batch: "batched" | number
155
+ ): Effect.Effect<
156
+ A[],
157
+ InvalidStateError | OptimisticConcurrencyException | E2,
158
+ | RSchema
159
+ | RPublish
160
+ | Exclude<R2, {
161
+ env: PureEnv<Evt, readonly T[], readonly T2[]>
162
+ }>
163
+ >
164
+ } = (q, pure, batch?: "batched" | number) =>
165
+ repo.query(q).pipe(
166
+ Effect.andThen((_) =>
167
+ Array.isArray(_)
168
+ ? batch === undefined
169
+ ? saveManyWithPure_(_ as any, pure as any)
170
+ : saveManyWithPureBatched_(_ as any, pure as any, batch === "batched" ? 100 : batch)
171
+ : saveWithPure_(_ as any, pure as any)
172
+ )
173
+ ) as any
174
+
175
+ const saveManyWithPure: {
176
+ <R, A, E, S1 extends T, S2 extends T>(
177
+ items: Iterable<S1>,
178
+ pure: Effect.Effect<A, E, FixEnv<R, Evt, readonly S1[], readonly S2[]>>
179
+ ): Effect.Effect<
180
+ A,
181
+ InvalidStateError | OptimisticConcurrencyException | E,
182
+ | RSchema
183
+ | RPublish
184
+ | Exclude<R, {
185
+ env: PureEnv<Evt, readonly S1[], readonly S2[]>
186
+ }>
187
+ >
188
+ <R, A, E, S1 extends T, S2 extends T>(
189
+ items: Iterable<S1>,
190
+ pure: Effect.Effect<A, E, FixEnv<R, Evt, readonly S1[], readonly S2[]>>,
191
+ batch: "batched" | number
192
+ ): Effect.Effect<
193
+ A[],
194
+ InvalidStateError | OptimisticConcurrencyException | E,
195
+ | RSchema
196
+ | RPublish
197
+ | Exclude<R, {
198
+ env: PureEnv<Evt, readonly S1[], readonly S2[]>
199
+ }>
200
+ >
201
+ } = (items, pure, batch?: "batched" | number) =>
202
+ batch
203
+ ? Effect.forEach(
204
+ Array.chunksOf(items, batch === "batched" ? 100 : batch),
205
+ (batch) =>
206
+ saveAllWithEffectInt(
207
+ runTerm(pure, batch)
208
+ )
209
+ )
210
+ : saveAllWithEffectInt(
211
+ runTerm(pure, [...items])
212
+ )
213
+
214
+ const byIdAndSaveWithPure: {
215
+ <R, A, E, S2 extends T>(
216
+ id: T[IdKey],
217
+ pure: Effect.Effect<A, E, FixEnv<R, Evt, T, S2>>
218
+ ): Effect.Effect<
219
+ A,
220
+ InvalidStateError | OptimisticConcurrencyException | NotFoundError<ItemType> | E,
221
+ | RSchema
222
+ | RPublish
223
+ | Exclude<R, {
224
+ env: PureEnv<Evt, T, S2>
225
+ }>
226
+ >
227
+ } = (id, pure): any => get(id).pipe(Effect.flatMap((item) => saveWithPure_(item, pure)))
228
+
229
+ type Req =
230
+ & Request.Request<T, NotFoundError<ItemType>>
231
+ & { _tag: `Get${ItemType}`; id: T[IdKey] }
232
+ const _request = Request.tagged<Req>(`Get${repo.itemType}`)
233
+
234
+ const requestResolver = RequestResolver
235
+ .make((
236
+ entries: NonEmptyArray<Request.Entry<Req>>,
237
+ _key: unknown
238
+ ) =>
239
+ (repo.query(Q.where(repo.idKey as any, "in" as any, entries.map((_) => _.request.id)) as any) as Effect.Effect<
240
+ readonly T[]
241
+ >)
242
+ // TODO
243
+ .pipe(
244
+ Effect.andThen((items) =>
245
+ Effect.forEach(entries, (entry) =>
246
+ Request.complete(
247
+ Array
248
+ .findFirst(items, (_) => _[repo.idKey] === entry.request.id)
249
+ .pipe(Option.match({
250
+ onNone: () => Exit.fail(new NotFoundError({ type: repo.itemType, id: entry.request.id })),
251
+ onSome: Exit.succeed
252
+ }))
253
+ )(entry), { discard: true })
254
+ ),
255
+ Effect
256
+ .catchCause((cause) =>
257
+ Effect.forEach(entries, (entry) => Request.complete(Exit.failCause(cause))(entry), { discard: true })
258
+ )
259
+ )
260
+ )
261
+ .pipe(
262
+ RequestResolver.batchN(20)
263
+ )
264
+
265
+ const exts = {
266
+ request: (id: T[IdKey]) => Effect.request(_request({ id }), requestResolver),
267
+ get,
268
+ log: (evt: Evt) => AnyPureDSL.log(evt),
269
+ /**
270
+ * Enables chunked writes for large batches via `options.batch`.
271
+ * Note: batching breaks transactional properties because chunks are saved independently.
272
+ */
273
+ save: ((itemOrItems: T | ReadonlyArray<T>, options?: BatchOptions) => {
274
+ const items = asReadonlyArray(itemOrItems)
275
+ if (!Array.isReadonlyArrayNonEmpty(items)) {
276
+ return Effect.void
277
+ }
278
+ const batchSize = getBatchSize(options?.batch)
279
+ if (batchSize === undefined) {
280
+ return repo.saveAndPublish(items)
281
+ }
282
+ return Effect.forEach(
283
+ Array.chunksOf(items, batchSize),
284
+ (batch) => repo.saveAndPublish(batch),
285
+ { discard: true }
286
+ )
287
+ }) as (
288
+ itemOrItems: T | ReadonlyArray<T>,
289
+ options?: BatchOptions
290
+ ) => Effect.Effect<
291
+ void,
292
+ InvalidStateError | OptimisticConcurrencyException,
293
+ RSchema | RPublish
294
+ >,
295
+ saveWithEvents: (events: Iterable<Evt>) => (...items: NonEmptyArray<T>) => repo.saveAndPublish(items, events),
296
+ /**
297
+ * Enables chunked deletes for large batches via `options.batch`.
298
+ * Note: batching breaks transactional properties because chunks are removed independently.
299
+ */
300
+ remove: ((itemOrItems: T | ReadonlyArray<T>, options?: BatchOptions) => {
301
+ const items = asReadonlyArray(itemOrItems)
302
+ if (!Array.isReadonlyArrayNonEmpty(items)) {
303
+ return Effect.void
304
+ }
305
+ const batchSize = getBatchSize(options?.batch)
306
+ if (batchSize === undefined) {
307
+ return repo.removeAndPublish(items)
308
+ }
309
+ return Effect.forEach(
310
+ Array.chunksOf(items, batchSize),
311
+ (batch) => repo.removeAndPublish(batch),
312
+ { discard: true }
313
+ )
314
+ }) as (
315
+ itemOrItems: T | ReadonlyArray<T>,
316
+ options?: BatchOptions
317
+ ) => Effect.Effect<void, never, RSchema | RPublish>,
318
+ /**
319
+ * Enables chunked deletes for large batches via `options.batch`.
320
+ * Note: batching breaks transactional properties because chunks are removed independently.
321
+ */
322
+ removeById: ((idOrIds: T[IdKey] | ReadonlyArray<T[IdKey]>, options?: BatchOptions) => {
323
+ const ids = asReadonlyArray(idOrIds)
324
+ if (!Array.isReadonlyArrayNonEmpty(ids)) {
325
+ return Effect.void
326
+ }
327
+ const batchSize = getBatchSize(options?.batch)
328
+ if (batchSize === undefined) {
329
+ return repo.removeById(ids)
330
+ }
331
+ return Effect.forEach(
332
+ Array.chunksOf(ids, batchSize),
333
+ (batch) => repo.removeById(batch),
334
+ { discard: true }
335
+ )
336
+ }) as (
337
+ idOrIds: T[IdKey] | ReadonlyArray<T[IdKey]>,
338
+ options?: BatchOptions
339
+ ) => Effect.Effect<void, never, RSchema>,
340
+ queryAndSavePure,
341
+ saveManyWithPure,
342
+ byIdAndSaveWithPure,
343
+ saveWithPure: <
344
+ R,
345
+ A,
346
+ E,
347
+ S1 extends T,
348
+ S2 extends T
349
+ >(
350
+ item: S1,
351
+ pure: Effect.Effect<A, E, FixEnv<R, Evt, S1, S2>>
352
+ ) =>
353
+ saveAllWithEffectInt(
354
+ runTerm(pure, item)
355
+ .pipe(Effect.map(([item, events, a]) => [[item], events, a]))
356
+ )
357
+ }
358
+
359
+ return {
360
+ ...repo,
361
+ ...exts
362
+ } as Repository<T, Encoded, Evt, ItemType, IdKey, RSchema, RPublish, RProvided> & typeof exts
363
+ }
364
+
365
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type
366
+ export interface ExtendedRepository<
367
+ T,
368
+ Encoded extends FieldValues,
369
+ Evt,
370
+ ItemType extends string,
371
+ IdKey extends keyof T & keyof Encoded,
372
+ RSchema,
373
+ RPublish,
374
+ RProvided = never
375
+ > extends ReturnType<typeof extendRepo<T, Encoded, Evt, ItemType, IdKey, RSchema, RPublish, RProvided>> {}