@effect-app/infra 2.10.0 → 2.12.0
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.
- package/CHANGELOG.md +12 -0
- package/_cjs/Model/Repository/ext.cjs +1 -1
- package/_cjs/Model/Repository/ext.cjs.map +1 -1
- package/_cjs/Model/Repository/internal/internal.cjs +245 -0
- package/_cjs/Model/Repository/internal/internal.cjs.map +1 -0
- package/_cjs/Model/Repository/legacy.cjs +0 -139
- package/_cjs/Model/Repository/legacy.cjs.map +1 -1
- package/_cjs/Model/Repository/makeRepo.cjs +2 -241
- package/_cjs/Model/Repository/makeRepo.cjs.map +1 -1
- package/_cjs/Store/Cosmos/query.cjs +9 -2
- package/_cjs/Store/Cosmos/query.cjs.map +1 -1
- package/_cjs/Store/Cosmos.cjs +49 -23
- package/_cjs/Store/Cosmos.cjs.map +1 -1
- package/_cjs/Store/Disk.cjs +5 -5
- package/_cjs/Store/Disk.cjs.map +1 -1
- package/_cjs/Store/Memory.cjs +9 -9
- package/_cjs/Store/Memory.cjs.map +1 -1
- package/_cjs/Store/codeFilter.cjs.map +1 -1
- package/_cjs/Store/service.cjs.map +1 -1
- package/_cjs/Store/utils.cjs +3 -3
- package/_cjs/Store/utils.cjs.map +1 -1
- package/dist/Model/Repository/ext.d.ts +6 -9
- package/dist/Model/Repository/ext.d.ts.map +1 -1
- package/dist/Model/Repository/ext.js +2 -2
- package/dist/Model/Repository/internal/internal.d.ts +61 -0
- package/dist/Model/Repository/internal/internal.d.ts.map +1 -0
- package/dist/Model/Repository/internal/internal.js +245 -0
- package/dist/Model/Repository/legacy.d.ts +4 -192
- package/dist/Model/Repository/legacy.d.ts.map +1 -1
- package/dist/Model/Repository/legacy.js +2 -123
- package/dist/Model/Repository/makeRepo.d.ts +7 -77
- package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
- package/dist/Model/Repository/makeRepo.js +3 -247
- package/dist/Model/Repository/service.d.ts +1 -3
- package/dist/Model/Repository/service.d.ts.map +1 -1
- package/dist/Operations.d.ts +3 -3
- package/dist/QueueMaker/sbqueue.d.ts +2 -2
- package/dist/Store/Cosmos/query.d.ts +1 -1
- package/dist/Store/Cosmos/query.d.ts.map +1 -1
- package/dist/Store/Cosmos/query.js +7 -3
- package/dist/Store/Cosmos.d.ts.map +1 -1
- package/dist/Store/Cosmos.js +43 -23
- package/dist/Store/Disk.d.ts +2 -3
- package/dist/Store/Disk.d.ts.map +1 -1
- package/dist/Store/Disk.js +6 -6
- package/dist/Store/Memory.d.ts +4 -9
- package/dist/Store/Memory.d.ts.map +1 -1
- package/dist/Store/Memory.js +12 -12
- package/dist/Store/codeFilter.d.ts +2 -3
- package/dist/Store/codeFilter.d.ts.map +1 -1
- package/dist/Store/codeFilter.js +1 -1
- package/dist/Store/service.d.ts +9 -23
- package/dist/Store/service.d.ts.map +1 -1
- package/dist/Store/service.js +1 -1
- package/dist/Store/utils.d.ts +1 -3
- package/dist/Store/utils.d.ts.map +1 -1
- package/dist/Store/utils.js +4 -4
- package/package.json +1 -1
- package/src/Model/Repository/ext.ts +6 -5
- package/src/Model/Repository/internal/internal.ts +482 -0
- package/src/Model/Repository/legacy.ts +4 -538
- package/src/Model/Repository/makeRepo.ts +11 -489
- package/src/Model/Repository/service.ts +1 -1
- package/src/Store/Cosmos/query.ts +6 -1
- package/src/Store/Cosmos.ts +85 -48
- package/src/Store/Disk.ts +20 -8
- package/src/Store/Memory.ts +25 -16
- package/src/Store/codeFilter.ts +2 -1
- package/src/Store/service.ts +8 -7
- package/src/Store/utils.ts +4 -3
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import type {} from "effect/Equal"
|
|
3
|
+
import type {} from "effect/Hash"
|
|
4
|
+
import type { NonEmptyReadonlyArray } from "effect-app"
|
|
5
|
+
import { Array, Chunk, Context, Effect, Equivalence, flow, Option, pipe, Pipeable, PubSub, S, Unify } from "effect-app"
|
|
6
|
+
import { toNonEmptyArray } from "effect-app/Array"
|
|
7
|
+
import { NotFoundError } from "effect-app/client"
|
|
8
|
+
import { flatMapOption } from "effect-app/Effect"
|
|
9
|
+
import type { Schema } from "effect-app/Schema"
|
|
10
|
+
import { NonNegativeInt } from "effect-app/Schema"
|
|
11
|
+
import { setupRequestContextFromCurrent } from "../../../api/setupRequest.js"
|
|
12
|
+
import type { FilterArgs, PersistenceModelType, StoreConfig } from "../../../Store.js"
|
|
13
|
+
import { StoreMaker } from "../../../Store.js"
|
|
14
|
+
import { getContextMap } from "../../../Store/ContextMapContainer.js"
|
|
15
|
+
import type { FieldValues } from "../../filter/types.js"
|
|
16
|
+
import * as Q from "../../query.js"
|
|
17
|
+
import type { Repository } from "../service.js"
|
|
18
|
+
|
|
19
|
+
const dedupe = Array.dedupeWith(Equivalence.string)
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A base implementation to create a repository.
|
|
23
|
+
*/
|
|
24
|
+
export function makeRepoInternal<
|
|
25
|
+
Evt = never
|
|
26
|
+
>() {
|
|
27
|
+
return <
|
|
28
|
+
ItemType extends string,
|
|
29
|
+
R,
|
|
30
|
+
Encoded extends FieldValues,
|
|
31
|
+
T,
|
|
32
|
+
IdKey extends keyof T & keyof Encoded
|
|
33
|
+
>(
|
|
34
|
+
name: ItemType,
|
|
35
|
+
schema: S.Schema<T, Encoded, R>,
|
|
36
|
+
mapFrom: (pm: Encoded) => Encoded,
|
|
37
|
+
mapTo: (e: Encoded, etag: string | undefined) => PersistenceModelType<Encoded>,
|
|
38
|
+
idKey: IdKey
|
|
39
|
+
) => {
|
|
40
|
+
type PM = PersistenceModelType<Encoded>
|
|
41
|
+
function mapToPersistenceModel(
|
|
42
|
+
e: Encoded,
|
|
43
|
+
getEtag: (id: string) => string | undefined
|
|
44
|
+
): PM {
|
|
45
|
+
return mapTo(e, getEtag(e[idKey]))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function mapReverse(
|
|
49
|
+
{ _etag, ...e }: PM,
|
|
50
|
+
setEtag: (id: string, eTag: string | undefined) => void
|
|
51
|
+
): Encoded {
|
|
52
|
+
setEtag((e as any)[idKey], _etag)
|
|
53
|
+
return mapFrom(e as unknown as Encoded)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const mkStore = makeStore<Encoded>()(name, schema, mapTo, idKey)
|
|
57
|
+
|
|
58
|
+
function make<RInitial = never, E = never, RPublish = never, RCtx = never>(
|
|
59
|
+
args: [Evt] extends [never] ? {
|
|
60
|
+
schemaContext?: Context.Context<RCtx>
|
|
61
|
+
makeInitial?: Effect<readonly T[], E, RInitial>
|
|
62
|
+
config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
|
|
63
|
+
partitionValue?: (a: Encoded) => string
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
: {
|
|
67
|
+
schemaContext?: Context.Context<RCtx>
|
|
68
|
+
publishEvents: (evt: NonEmptyReadonlyArray<Evt>) => Effect<void, never, RPublish>
|
|
69
|
+
makeInitial?: Effect<readonly T[], E, RInitial>
|
|
70
|
+
config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
|
|
71
|
+
partitionValue?: (a: Encoded) => string
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
) {
|
|
75
|
+
return Effect
|
|
76
|
+
.gen(function*() {
|
|
77
|
+
const rctx: Context<RCtx> = args.schemaContext ?? Context.empty() as any
|
|
78
|
+
const provideRctx = Effect.provide(rctx)
|
|
79
|
+
const encodeMany = flow(
|
|
80
|
+
S.encode(S.Array(schema)),
|
|
81
|
+
provideRctx,
|
|
82
|
+
Effect.withSpan("encodeMany", { captureStackTrace: false })
|
|
83
|
+
)
|
|
84
|
+
const decode = flow(S.decode(schema), provideRctx)
|
|
85
|
+
const decodeMany = flow(
|
|
86
|
+
S.decode(S.Array(schema)),
|
|
87
|
+
provideRctx,
|
|
88
|
+
Effect.withSpan("decodeMany", { captureStackTrace: false })
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
const store = yield* mkStore(args.makeInitial, args.config)
|
|
92
|
+
const cms = Effect.andThen(getContextMap.pipe(Effect.orDie), (_) => ({
|
|
93
|
+
get: (id: string) => _.get(`${name}.${id}`),
|
|
94
|
+
set: (id: string, etag: string | undefined) => _.set(`${name}.${id}`, etag)
|
|
95
|
+
}))
|
|
96
|
+
|
|
97
|
+
const pub = "publishEvents" in args
|
|
98
|
+
? args.publishEvents
|
|
99
|
+
: () => Effect.void
|
|
100
|
+
const changeFeed = yield* PubSub.unbounded<[T[], "save" | "remove"]>()
|
|
101
|
+
|
|
102
|
+
const allE = cms
|
|
103
|
+
.pipe(Effect.flatMap((cm) => Effect.map(store.all, (_) => _.map((_) => mapReverse(_, cm.set)))))
|
|
104
|
+
|
|
105
|
+
const all = Effect
|
|
106
|
+
.flatMap(
|
|
107
|
+
allE,
|
|
108
|
+
(_) => decodeMany(_).pipe(Effect.orDie)
|
|
109
|
+
)
|
|
110
|
+
.pipe(Effect.map((_) => _ as T[]))
|
|
111
|
+
|
|
112
|
+
const fieldsSchema = schema as unknown as { fields: any }
|
|
113
|
+
// assumes the id field never needs a service...
|
|
114
|
+
const i = ("fields" in fieldsSchema ? S.Struct(fieldsSchema["fields"]) as unknown as typeof schema : schema)
|
|
115
|
+
.pipe((_) => {
|
|
116
|
+
let ast = _.ast
|
|
117
|
+
if (ast._tag === "Declaration") ast = ast.typeParameters[0]!
|
|
118
|
+
|
|
119
|
+
const s = S.make(ast) as unknown as Schema<T, Encoded, R>
|
|
120
|
+
|
|
121
|
+
return ast._tag === "Union"
|
|
122
|
+
// we need to get the TypeLiteral, incase of class it's behind a transform...
|
|
123
|
+
? S.Union(
|
|
124
|
+
...ast.types.map((_) =>
|
|
125
|
+
(S.make(_._tag === "Transformation" ? _.from : _) as unknown as Schema<T, Encoded>)
|
|
126
|
+
.pipe(S.pick(idKey as any))
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
: s.pipe(S.pick(idKey as any))
|
|
130
|
+
})
|
|
131
|
+
const encodeId = flow(S.encode(i), provideRctx)
|
|
132
|
+
function findEId(id: Encoded[IdKey]) {
|
|
133
|
+
return Effect.flatMap(
|
|
134
|
+
store.find(id),
|
|
135
|
+
(item) =>
|
|
136
|
+
Effect.gen(function*() {
|
|
137
|
+
const { set } = yield* cms
|
|
138
|
+
return item.pipe(Option.map((_) => mapReverse(_, set)))
|
|
139
|
+
})
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
// TODO: select the particular field, instead of as struct
|
|
143
|
+
function findE(id: T[IdKey]) {
|
|
144
|
+
return pipe(
|
|
145
|
+
encodeId({ [idKey]: id } as any),
|
|
146
|
+
Effect.orDie,
|
|
147
|
+
Effect.map((_) => (_ as any)[idKey]),
|
|
148
|
+
Effect.flatMap(findEId)
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function find(id: T[IdKey]) {
|
|
153
|
+
return Effect.flatMapOption(findE(id), (_) => Effect.orDie(decode(_)))
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const saveAllE = (a: Iterable<Encoded>) =>
|
|
157
|
+
Effect
|
|
158
|
+
.flatMapOption(
|
|
159
|
+
Effect
|
|
160
|
+
.sync(() => toNonEmptyArray([...a])),
|
|
161
|
+
(a) =>
|
|
162
|
+
Effect.gen(function*() {
|
|
163
|
+
const { get, set } = yield* cms
|
|
164
|
+
const items = a.map((_) => mapToPersistenceModel(_, get))
|
|
165
|
+
const ret = yield* store.batchSet(items)
|
|
166
|
+
ret.forEach((_) => set(_[idKey], _._etag))
|
|
167
|
+
})
|
|
168
|
+
)
|
|
169
|
+
.pipe(Effect.asVoid)
|
|
170
|
+
|
|
171
|
+
const saveAll = (a: Iterable<T>) =>
|
|
172
|
+
encodeMany(Array.fromIterable(a))
|
|
173
|
+
.pipe(
|
|
174
|
+
Effect.orDie,
|
|
175
|
+
Effect.andThen(saveAllE)
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
const saveAndPublish = (items: Iterable<T>, events: Iterable<Evt> = []) => {
|
|
179
|
+
return Effect
|
|
180
|
+
.suspend(() => {
|
|
181
|
+
const it = Chunk.fromIterable(items)
|
|
182
|
+
return saveAll(it)
|
|
183
|
+
.pipe(
|
|
184
|
+
Effect.andThen(Effect.sync(() => toNonEmptyArray([...events]))),
|
|
185
|
+
// TODO: for full consistency the events should be stored within the same database transaction, and then picked up.
|
|
186
|
+
(_) => Effect.flatMapOption(_, pub),
|
|
187
|
+
Effect.andThen(changeFeed.publish([Chunk.toArray(it), "save"])),
|
|
188
|
+
Effect.asVoid
|
|
189
|
+
)
|
|
190
|
+
})
|
|
191
|
+
.pipe(Effect.withSpan("saveAndPublish", { captureStackTrace: false }))
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function removeAndPublish(a: Iterable<T>, events: Iterable<Evt> = []) {
|
|
195
|
+
return Effect.gen(function*() {
|
|
196
|
+
const { get, set } = yield* cms
|
|
197
|
+
const it = [...a]
|
|
198
|
+
const items = yield* encodeMany(it).pipe(Effect.orDie)
|
|
199
|
+
// TODO: we should have a batchRemove on store so the adapter can actually batch...
|
|
200
|
+
for (const e of items) {
|
|
201
|
+
yield* store.remove(mapToPersistenceModel(e, get))
|
|
202
|
+
set(e[idKey], undefined)
|
|
203
|
+
}
|
|
204
|
+
yield* Effect
|
|
205
|
+
.sync(() => toNonEmptyArray([...events]))
|
|
206
|
+
// TODO: for full consistency the events should be stored within the same database transaction, and then picked up.
|
|
207
|
+
.pipe((_) => Effect.flatMapOption(_, pub))
|
|
208
|
+
|
|
209
|
+
yield* changeFeed.publish([it, "remove"])
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const parseMany = (items: readonly PM[]) =>
|
|
214
|
+
Effect
|
|
215
|
+
.flatMap(cms, (cm) =>
|
|
216
|
+
decodeMany(items.map((_) => mapReverse(_, cm.set)))
|
|
217
|
+
.pipe(Effect.orDie, Effect.withSpan("parseMany", { captureStackTrace: false })))
|
|
218
|
+
const parseMany2 = <A, R>(
|
|
219
|
+
items: readonly PM[],
|
|
220
|
+
schema: S.Schema<A, Encoded, R>
|
|
221
|
+
) =>
|
|
222
|
+
Effect
|
|
223
|
+
.flatMap(cms, (cm) =>
|
|
224
|
+
S
|
|
225
|
+
.decode(S.Array(schema))(
|
|
226
|
+
items.map((_) => mapReverse(_, cm.set))
|
|
227
|
+
)
|
|
228
|
+
.pipe(Effect.orDie, Effect.withSpan("parseMany2", { captureStackTrace: false })))
|
|
229
|
+
const filter = <U extends keyof Encoded = keyof Encoded>(args: FilterArgs<Encoded, U>) =>
|
|
230
|
+
store
|
|
231
|
+
.filter(
|
|
232
|
+
// always enforce id and _etag because they are system fields, required for etag tracking etc
|
|
233
|
+
{
|
|
234
|
+
...args,
|
|
235
|
+
select: args.select
|
|
236
|
+
? dedupe([...args.select, idKey, "_etag" as any])
|
|
237
|
+
: undefined
|
|
238
|
+
} as typeof args
|
|
239
|
+
)
|
|
240
|
+
.pipe(
|
|
241
|
+
Effect.tap((items) =>
|
|
242
|
+
Effect.map(cms, ({ set }) => items.forEach((_) => set((_ as Encoded)[idKey], (_ as PM)._etag)))
|
|
243
|
+
)
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
// TODO: For raw we should use S.from, and drop the R...
|
|
247
|
+
const query: {
|
|
248
|
+
<A, R, From extends FieldValues>(
|
|
249
|
+
q: Q.QueryProjection<Encoded extends From ? From : never, A, R>
|
|
250
|
+
): Effect.Effect<readonly A[], S.ParseResult.ParseError, R>
|
|
251
|
+
<A, R, EncodedRefined extends Encoded = Encoded>(
|
|
252
|
+
q: Q.QAll<NoInfer<Encoded>, NoInfer<EncodedRefined>, A, R>
|
|
253
|
+
): Effect.Effect<readonly A[], never, R>
|
|
254
|
+
} = (<A, R, EncodedRefined extends Encoded = Encoded>(q: Q.QAll<Encoded, EncodedRefined, A, R>) => {
|
|
255
|
+
const a = Q.toFilter(q)
|
|
256
|
+
const eff = a.mode === "project"
|
|
257
|
+
? filter(a)
|
|
258
|
+
// TODO: mapFrom but need to support per field and dependencies
|
|
259
|
+
.pipe(
|
|
260
|
+
Effect.andThen(flow(S.decode(S.Array(a.schema ?? schema)), provideRctx))
|
|
261
|
+
)
|
|
262
|
+
: a.mode === "collect"
|
|
263
|
+
? filter(a)
|
|
264
|
+
// TODO: mapFrom but need to support per field and dependencies
|
|
265
|
+
.pipe(
|
|
266
|
+
Effect.flatMap(flow(
|
|
267
|
+
S.decode(S.Array(a.schema)),
|
|
268
|
+
Effect.map(Array.getSomes),
|
|
269
|
+
provideRctx
|
|
270
|
+
))
|
|
271
|
+
)
|
|
272
|
+
: Effect.flatMap(
|
|
273
|
+
filter(a),
|
|
274
|
+
(_) =>
|
|
275
|
+
Unify.unify(
|
|
276
|
+
a.schema
|
|
277
|
+
// TODO: partial may not match?
|
|
278
|
+
? parseMany2(_ as any, a.schema as any)
|
|
279
|
+
: parseMany(_ as any)
|
|
280
|
+
)
|
|
281
|
+
)
|
|
282
|
+
return pipe(
|
|
283
|
+
a.ttype === "one"
|
|
284
|
+
? Effect.andThen(
|
|
285
|
+
eff,
|
|
286
|
+
flow(
|
|
287
|
+
Array.head,
|
|
288
|
+
Effect.mapError(() => new NotFoundError({ id: "query", /* TODO */ type: name }))
|
|
289
|
+
)
|
|
290
|
+
)
|
|
291
|
+
: a.ttype === "count"
|
|
292
|
+
? Effect
|
|
293
|
+
.andThen(eff, (_) => NonNegativeInt(_.length))
|
|
294
|
+
.pipe(Effect.catchTag("ParseError", (e) => Effect.die(e)))
|
|
295
|
+
: eff,
|
|
296
|
+
Effect.withSpan("Repository.query [effect-app/infra]", {
|
|
297
|
+
captureStackTrace: false,
|
|
298
|
+
attributes: {
|
|
299
|
+
"repository.model_name": name,
|
|
300
|
+
query: { ...a, schema: a.schema ? "__SCHEMA__" : a.schema, filter: a.filter }
|
|
301
|
+
}
|
|
302
|
+
})
|
|
303
|
+
)
|
|
304
|
+
}) as any
|
|
305
|
+
|
|
306
|
+
const r: Repository<T, Encoded, Evt, ItemType, IdKey, Exclude<R, RCtx>, RPublish> = {
|
|
307
|
+
changeFeed,
|
|
308
|
+
itemType: name,
|
|
309
|
+
idKey,
|
|
310
|
+
find,
|
|
311
|
+
all,
|
|
312
|
+
saveAndPublish,
|
|
313
|
+
removeAndPublish,
|
|
314
|
+
query(q: any) {
|
|
315
|
+
// eslint-disable-next-line prefer-rest-params
|
|
316
|
+
return query(typeof q === "function" ? Pipeable.pipeArguments(Q.make(), arguments) : q) as any
|
|
317
|
+
},
|
|
318
|
+
/**
|
|
319
|
+
* @internal
|
|
320
|
+
*/
|
|
321
|
+
mapped: <A, R>(schema: S.Schema<A, any, R>) => {
|
|
322
|
+
const dec = S.decode(schema)
|
|
323
|
+
const encMany = S.encode(S.Array(schema))
|
|
324
|
+
const decMany = S.decode(S.Array(schema))
|
|
325
|
+
return {
|
|
326
|
+
all: allE.pipe(
|
|
327
|
+
Effect.flatMap(decMany),
|
|
328
|
+
Effect.map((_) => _ as any[])
|
|
329
|
+
),
|
|
330
|
+
find: (id: T[IdKey]) => flatMapOption(findE(id), dec),
|
|
331
|
+
// query: (q: any) => {
|
|
332
|
+
// const a = Q.toFilter(q)
|
|
333
|
+
|
|
334
|
+
// return filter(a)
|
|
335
|
+
// .pipe(
|
|
336
|
+
// Effect.flatMap(decMany),
|
|
337
|
+
// Effect.map((_) => _ as any[]),
|
|
338
|
+
// Effect.withSpan("Repository.mapped.query [effect-app/infra]", {
|
|
339
|
+
// captureStackTrace: false,
|
|
340
|
+
// attributes: {
|
|
341
|
+
// "repository.model_name": name,
|
|
342
|
+
// query: { ...a, schema: a.schema ? "__SCHEMA__" : a.schema, filter: a.filter.build() }
|
|
343
|
+
// }
|
|
344
|
+
// })
|
|
345
|
+
// )
|
|
346
|
+
// },
|
|
347
|
+
save: (...xes: any[]) =>
|
|
348
|
+
Effect.flatMap(encMany(xes), (_) => saveAllE(_)).pipe(
|
|
349
|
+
Effect.withSpan("mapped.save", { captureStackTrace: false })
|
|
350
|
+
)
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return r
|
|
355
|
+
})
|
|
356
|
+
.pipe(Effect
|
|
357
|
+
// .withSpan("Repository.make [effect-app/infra]", { attributes: { "repository.model_name": name } })
|
|
358
|
+
.withLogSpan("Repository.make: " + name))
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
make,
|
|
363
|
+
Q: Q.make<Encoded>()
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const pluralize = (s: string) =>
|
|
369
|
+
s.endsWith("s")
|
|
370
|
+
? s + "es"
|
|
371
|
+
: s.endsWith("y")
|
|
372
|
+
? s.substring(0, s.length - 1) + "ies"
|
|
373
|
+
: s + "s"
|
|
374
|
+
|
|
375
|
+
export function makeStore<Encoded extends FieldValues>() {
|
|
376
|
+
return <
|
|
377
|
+
ItemType extends string,
|
|
378
|
+
R,
|
|
379
|
+
E,
|
|
380
|
+
T,
|
|
381
|
+
IdKey extends keyof Encoded
|
|
382
|
+
>(
|
|
383
|
+
name: ItemType,
|
|
384
|
+
schema: S.Schema<T, E, R>,
|
|
385
|
+
mapTo: (e: E, etag: string | undefined) => Encoded,
|
|
386
|
+
idKey: IdKey
|
|
387
|
+
) => {
|
|
388
|
+
function makeStore<RInitial = never, EInitial = never>(
|
|
389
|
+
makeInitial?: Effect<readonly T[], EInitial, RInitial>,
|
|
390
|
+
config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
|
|
391
|
+
partitionValue?: (a: Encoded) => string
|
|
392
|
+
}
|
|
393
|
+
) {
|
|
394
|
+
function encodeToEncoded() {
|
|
395
|
+
const getEtag = () => undefined
|
|
396
|
+
return (t: T) =>
|
|
397
|
+
S.encode(schema)(t).pipe(
|
|
398
|
+
Effect.orDie,
|
|
399
|
+
Effect.map((_) => mapToPersistenceModel(_, getEtag))
|
|
400
|
+
)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function mapToPersistenceModel(
|
|
404
|
+
e: E,
|
|
405
|
+
getEtag: (id: string) => string | undefined
|
|
406
|
+
): Encoded {
|
|
407
|
+
return mapTo(e, getEtag((e as any)[idKey] as string))
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return Effect.gen(function*() {
|
|
411
|
+
const { make } = yield* StoreMaker
|
|
412
|
+
|
|
413
|
+
const store = yield* make<IdKey, Encoded, RInitial | R, EInitial>(
|
|
414
|
+
pluralize(name),
|
|
415
|
+
idKey,
|
|
416
|
+
makeInitial
|
|
417
|
+
? makeInitial
|
|
418
|
+
.pipe(
|
|
419
|
+
Effect.flatMap(Effect.forEach(encodeToEncoded())),
|
|
420
|
+
setupRequestContextFromCurrent("Repository.makeInitial [effect-app/infra]", {
|
|
421
|
+
attributes: { "repository.model_name": name }
|
|
422
|
+
})
|
|
423
|
+
)
|
|
424
|
+
: undefined,
|
|
425
|
+
{
|
|
426
|
+
...config,
|
|
427
|
+
partitionValue: config?.partitionValue
|
|
428
|
+
?? ((_) => "primary") /*(isIntegrationEvent(r) ? r.companyId : r.id*/
|
|
429
|
+
}
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
return store
|
|
433
|
+
})
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return makeStore
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export interface Repos<
|
|
441
|
+
T,
|
|
442
|
+
Encoded extends { id: string },
|
|
443
|
+
RSchema,
|
|
444
|
+
Evt,
|
|
445
|
+
ItemType extends string,
|
|
446
|
+
IdKey extends keyof T,
|
|
447
|
+
RPublish
|
|
448
|
+
> {
|
|
449
|
+
make<RInitial = never, E = never, R2 = never>(
|
|
450
|
+
args: [Evt] extends [never] ? {
|
|
451
|
+
makeInitial?: Effect<readonly T[], E, RInitial>
|
|
452
|
+
config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
|
|
453
|
+
partitionValue?: (a: Encoded) => string
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
: {
|
|
457
|
+
publishEvents: (evt: NonEmptyReadonlyArray<Evt>) => Effect<void, never, R2>
|
|
458
|
+
makeInitial?: Effect<readonly T[], E, RInitial>
|
|
459
|
+
config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
|
|
460
|
+
partitionValue?: (a: Encoded) => string
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
): Effect<Repository<T, Encoded, Evt, ItemType, IdKey, RSchema, RPublish>, E, StoreMaker | RInitial | R2>
|
|
464
|
+
makeWith<Out, RInitial = never, E = never, R2 = never>(
|
|
465
|
+
args: [Evt] extends [never] ? {
|
|
466
|
+
makeInitial?: Effect<readonly T[], E, RInitial>
|
|
467
|
+
config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
|
|
468
|
+
partitionValue?: (a: Encoded) => string
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
: {
|
|
472
|
+
publishEvents: (evt: NonEmptyReadonlyArray<Evt>) => Effect<void, never, R2>
|
|
473
|
+
makeInitial?: Effect<readonly T[], E, RInitial>
|
|
474
|
+
config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
|
|
475
|
+
partitionValue?: (a: Encoded) => string
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
f: (r: Repository<T, Encoded, Evt, ItemType, IdKey, RSchema, RPublish>) => Out
|
|
479
|
+
): Effect<Out, E, StoreMaker | RInitial | R2>
|
|
480
|
+
readonly Q: ReturnType<typeof Q.make<Encoded>>
|
|
481
|
+
readonly type: Repository<T, Encoded, Evt, ItemType, IdKey, RSchema, RPublish>
|
|
482
|
+
}
|