@effect-app/infra 0.43.1 → 0.44.1
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 +15 -0
- package/_cjs/api/codec.cjs +3 -11
- package/_cjs/api/codec.cjs.map +1 -1
- package/_cjs/services/RepositoryBase.cjs +201 -0
- package/_cjs/services/RepositoryBase.cjs.map +1 -0
- package/_cjs/services/Store/Cosmos.cjs +1 -1
- package/_cjs/services/Store/Cosmos.cjs.map +1 -1
- package/_cjs/services/Store/Disk.cjs +1 -1
- package/_cjs/services/Store/Disk.cjs.map +1 -1
- package/_cjs/services/Store/Memory.cjs +2 -1
- package/_cjs/services/Store/Memory.cjs.map +1 -1
- package/_cjs/services/Store/Redis.cjs +2 -2
- package/_cjs/services/Store/Redis.cjs.map +1 -1
- package/_src/api/codec.ts +3 -15
- package/_src/services/RepositoryBase.ts +366 -0
- package/_src/services/Store/Cosmos.ts +3 -3
- package/_src/services/Store/Disk.ts +8 -8
- package/_src/services/Store/Memory.ts +7 -6
- package/_src/services/Store/Redis.ts +4 -4
- package/_src/services/Store/service.ts +4 -4
- package/dist/api/codec.d.ts +2 -3
- package/dist/api/codec.d.ts.map +1 -1
- package/dist/api/codec.js +4 -12
- package/dist/services/RepositoryBase.d.ts +240 -0
- package/dist/services/RepositoryBase.d.ts.map +1 -0
- package/dist/services/RepositoryBase.js +175 -0
- package/dist/services/Store/Cosmos.d.ts +1 -1
- package/dist/services/Store/Cosmos.d.ts.map +1 -1
- package/dist/services/Store/Cosmos.js +2 -2
- package/dist/services/Store/Disk.d.ts +1 -1
- package/dist/services/Store/Disk.d.ts.map +1 -1
- package/dist/services/Store/Disk.js +3 -3
- package/dist/services/Store/Memory.d.ts +2 -2
- package/dist/services/Store/Memory.d.ts.map +1 -1
- package/dist/services/Store/Memory.js +3 -2
- package/dist/services/Store/Redis.d.ts +1 -1
- package/dist/services/Store/Redis.d.ts.map +1 -1
- package/dist/services/Store/Redis.js +3 -3
- package/dist/services/Store/service.d.ts +1 -1
- package/dist/services/Store/service.d.ts.map +1 -1
- package/package.json +15 -5
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import type { ParserEnv } from "@effect-app/schema/custom/Parser"
|
|
2
|
+
import type { Repository } from "./Repository.js"
|
|
3
|
+
import type { RequestContextContainer } from "./RequestContextContainer.js"
|
|
4
|
+
import { ContextMap, StoreMaker } from "./Store.js"
|
|
5
|
+
import type { Filter, StoreConfig, Where } from "./Store.js"
|
|
6
|
+
import type {} from "@effect/data/Equal"
|
|
7
|
+
import type {} from "@effect/data/Hash"
|
|
8
|
+
import type { Opt } from "@effect-app/core/Option"
|
|
9
|
+
import { makeCodec } from "@effect-app/infra/api/codec"
|
|
10
|
+
import { makeFilters } from "@effect-app/infra/filter"
|
|
11
|
+
import type { Chunk, Schema } from "@effect-app/prelude"
|
|
12
|
+
import { EParserFor } from "@effect-app/prelude/schema"
|
|
13
|
+
import type { InvalidStateError, OptimisticConcurrencyException } from "../errors.js"
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* A base for creating an abstract class usable as Tag, Companion and interface to create your own implementation.
|
|
17
|
+
*/
|
|
18
|
+
export const RepositoryBase = <Service>() => {
|
|
19
|
+
return <T extends { id: string }, PM extends { id: string; _etag: string | undefined }, Evt, ItemType extends string>(
|
|
20
|
+
itemType: ItemType
|
|
21
|
+
) => {
|
|
22
|
+
abstract class RepositoryBaseC implements Repository<T, PM, Evt, ItemType> {
|
|
23
|
+
itemType: ItemType = itemType
|
|
24
|
+
abstract find: (id: T["id"]) => Effect<ContextMap | RequestContextContainer, never, Opt<T>>
|
|
25
|
+
abstract all: Effect<ContextMap, never, Chunk<T>>
|
|
26
|
+
abstract saveAndPublish: (
|
|
27
|
+
items: Iterable<T>,
|
|
28
|
+
events?: Iterable<Evt>
|
|
29
|
+
) => Effect<ContextMap | RequestContextContainer, InvalidStateError | OptimisticConcurrencyException, void>
|
|
30
|
+
abstract utils: {
|
|
31
|
+
mapReverse: (
|
|
32
|
+
pm: PM,
|
|
33
|
+
setEtag: (id: string, eTag: string | undefined) => void
|
|
34
|
+
) => unknown // TODO
|
|
35
|
+
parse: (a: unknown, env?: ParserEnv | undefined) => T
|
|
36
|
+
all: Effect<ContextMap, never, Chunk<PM>>
|
|
37
|
+
filter: (filter: Filter<PM>, cursor?: { limit?: number; skip?: number }) => Effect<ContextMap, never, Chunk<PM>>
|
|
38
|
+
}
|
|
39
|
+
abstract remove: (item: T) => Effect<ContextMap, never, void>
|
|
40
|
+
static where = makeWhere<PM>()
|
|
41
|
+
static flatMap<R1, E1, B>(f: (a: Service) => Effect<R1, E1, B>): Effect<Service | R1, E1, B> {
|
|
42
|
+
return Effect.flatMap(this as unknown as Tag<Service, Service>, f)
|
|
43
|
+
}
|
|
44
|
+
static map<B>(f: (a: Service) => B): Effect<Service, never, B> {
|
|
45
|
+
return Effect.map(this as unknown as Tag<Service, Service>, f)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return assignTag<Service>()(RepositoryBaseC)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A base implementation to create a repository.
|
|
54
|
+
*/
|
|
55
|
+
export function makeRepo<
|
|
56
|
+
PM extends { id: string; _etag: string | undefined },
|
|
57
|
+
Evt = never
|
|
58
|
+
>() {
|
|
59
|
+
return <
|
|
60
|
+
ItemType extends string,
|
|
61
|
+
T extends { id: string },
|
|
62
|
+
ConstructorInput,
|
|
63
|
+
Api,
|
|
64
|
+
E extends { id: string }
|
|
65
|
+
>(
|
|
66
|
+
name: ItemType,
|
|
67
|
+
schema: Schema.Schema<unknown, T, ConstructorInput, E, Api>,
|
|
68
|
+
mapFrom: (pm: Omit<PM, "_etag">) => E,
|
|
69
|
+
mapTo: (e: E, etag: string | undefined) => PM
|
|
70
|
+
) => {
|
|
71
|
+
const where = makeWhere<PM>()
|
|
72
|
+
|
|
73
|
+
function mapToPersistenceModel(
|
|
74
|
+
e: E,
|
|
75
|
+
getEtag: (id: string) => string | undefined
|
|
76
|
+
): PM {
|
|
77
|
+
return mapTo(e, getEtag(e.id))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function mapReverse(
|
|
81
|
+
{ _etag, ...e }: PM,
|
|
82
|
+
setEtag: (id: string, eTag: string | undefined) => void
|
|
83
|
+
): E {
|
|
84
|
+
setEtag(e.id, _etag)
|
|
85
|
+
return mapFrom(e)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const mkStore = makeStore<PM>()(name, schema, mapTo)
|
|
89
|
+
|
|
90
|
+
function make(
|
|
91
|
+
publishEvents: (evt: NonEmptyReadonlyArray<Evt>) => Effect<RequestContextContainer, never, void>,
|
|
92
|
+
makeInitial?: Effect<never, never, readonly T[]>,
|
|
93
|
+
config?: Omit<StoreConfig<PM>, "partitionValue"> & {
|
|
94
|
+
partitionValue?: (a: PM) => string
|
|
95
|
+
}
|
|
96
|
+
) {
|
|
97
|
+
return Do($ => {
|
|
98
|
+
const store = $(mkStore(makeInitial, config))
|
|
99
|
+
|
|
100
|
+
const allE = store.all.flatMap(items =>
|
|
101
|
+
Do($ => {
|
|
102
|
+
const { set } = $(ContextMap)
|
|
103
|
+
return items.map(_ => mapReverse(_, set))
|
|
104
|
+
})
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
const all = allE.flatMap(_ => _.forEachEffect(EParserFor(schema).condemnDie))
|
|
108
|
+
|
|
109
|
+
function findE(id: T["id"]) {
|
|
110
|
+
return store.find(id)
|
|
111
|
+
.flatMap(items =>
|
|
112
|
+
Do($ => {
|
|
113
|
+
const { set } = $(ContextMap)
|
|
114
|
+
return items.map(_ => mapReverse(_, set))
|
|
115
|
+
})
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function find(id: T["id"]) {
|
|
120
|
+
return findE(id).flatMapOpt(EParserFor(schema).condemnDie)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const saveAllE = (a: Iterable<E>) =>
|
|
124
|
+
Effect(a.toNonEmptyArray)
|
|
125
|
+
.flatMapOpt(a =>
|
|
126
|
+
Do($ => {
|
|
127
|
+
const { get, set } = $(ContextMap)
|
|
128
|
+
const items = a.mapNonEmpty(_ => mapToPersistenceModel(_, get))
|
|
129
|
+
const ret = $(store.batchSet(items))
|
|
130
|
+
ret.forEach(_ => set(_.id, _._etag))
|
|
131
|
+
})
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
const saveAll = (a: Iterable<T>) => saveAllE(a.toChunk.map(Encoder.for(schema)))
|
|
135
|
+
|
|
136
|
+
const saveAndPublish = (items: Iterable<T>, events: Iterable<Evt> = []) =>
|
|
137
|
+
saveAll(items)
|
|
138
|
+
> Effect(events.toNonEmptyArray)
|
|
139
|
+
// TODO: for full consistency the events should be stored within the same database transaction, and then picked up.
|
|
140
|
+
.flatMapOpt(publishEvents)
|
|
141
|
+
|
|
142
|
+
const encode = Encoder.for(schema)
|
|
143
|
+
function remove(item: T) {
|
|
144
|
+
return Do($ => {
|
|
145
|
+
const { get, set } = $(ContextMap)
|
|
146
|
+
const e = encode(item)
|
|
147
|
+
$(store.remove(mapToPersistenceModel(e, get)))
|
|
148
|
+
set(item.id, undefined)
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const r: Repository<T, PM, Evt, ItemType> = {
|
|
153
|
+
/**
|
|
154
|
+
* @internal
|
|
155
|
+
*/
|
|
156
|
+
utils: {
|
|
157
|
+
mapReverse,
|
|
158
|
+
parse: Parser.for(schema).unsafe,
|
|
159
|
+
filter: store.filter
|
|
160
|
+
.flow(_ => _.tap(items => ContextMap.map(({ set }) => items.forEach(_ => set(_.id, _._etag))))),
|
|
161
|
+
all: store.all.tap(items => ContextMap.map(({ set }) => items.forEach(_ => set(_.id, _._etag))))
|
|
162
|
+
},
|
|
163
|
+
itemType: name,
|
|
164
|
+
find,
|
|
165
|
+
all,
|
|
166
|
+
saveAndPublish,
|
|
167
|
+
remove
|
|
168
|
+
}
|
|
169
|
+
return r
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
make,
|
|
175
|
+
where
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* @tsplus fluent Repository removeById
|
|
182
|
+
*/
|
|
183
|
+
export function removeById<
|
|
184
|
+
T extends { id: string },
|
|
185
|
+
PM extends { id: string },
|
|
186
|
+
Evt,
|
|
187
|
+
ItemType extends string
|
|
188
|
+
>(
|
|
189
|
+
self: Repository<T, PM, Evt, ItemType>,
|
|
190
|
+
id: T["id"]
|
|
191
|
+
) {
|
|
192
|
+
return self.get(id).flatMap(_ => self.remove(_))
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function makeWhere<PM extends { id: string; _etag: string | undefined }>() {
|
|
196
|
+
const f_ = makeFilters<PM>()
|
|
197
|
+
type WhereFilter = typeof f_
|
|
198
|
+
|
|
199
|
+
function makeFilter_(filter: (f: WhereFilter) => Filter<PM>) {
|
|
200
|
+
return filter(f_)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function where(
|
|
204
|
+
makeWhere: (
|
|
205
|
+
f: WhereFilter
|
|
206
|
+
) => Where | readonly [Where, ...Where[]],
|
|
207
|
+
mode?: "or" | "and"
|
|
208
|
+
) {
|
|
209
|
+
return makeFilter_(f => {
|
|
210
|
+
const m = makeWhere ? makeWhere(f) : []
|
|
211
|
+
return ({
|
|
212
|
+
mode,
|
|
213
|
+
where: (Array.isArray(m) ? m as unknown as [Where, ...Where[]] : [m]) as readonly [Where, ...Where[]]
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
return where
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const pluralize = (s: string) =>
|
|
221
|
+
s.endsWith("s")
|
|
222
|
+
? s + "es"
|
|
223
|
+
: s.endsWith("y")
|
|
224
|
+
? s.substring(0, s.length - 1) + "ies"
|
|
225
|
+
: s + "s"
|
|
226
|
+
|
|
227
|
+
export function makeStore<
|
|
228
|
+
PM extends { id: string; _etag: string | undefined }
|
|
229
|
+
>() {
|
|
230
|
+
return <
|
|
231
|
+
ItemType extends string,
|
|
232
|
+
T extends { id: string },
|
|
233
|
+
ConstructorInput,
|
|
234
|
+
Api,
|
|
235
|
+
E extends { id: string }
|
|
236
|
+
>(
|
|
237
|
+
name: ItemType,
|
|
238
|
+
schema: Schema.Schema<unknown, T, ConstructorInput, E, Api>,
|
|
239
|
+
mapTo: (e: E, etag: string | undefined) => PM
|
|
240
|
+
) => {
|
|
241
|
+
const [_dec, encode] = makeCodec(schema)
|
|
242
|
+
function encodeToPM() {
|
|
243
|
+
const getEtag = () => undefined
|
|
244
|
+
return flow(encode, v => mapToPersistenceModel(v, getEtag))
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function mapToPersistenceModel(
|
|
248
|
+
e: E,
|
|
249
|
+
getEtag: (id: string) => string | undefined
|
|
250
|
+
): PM {
|
|
251
|
+
return mapTo(e, getEtag(e.id))
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function makeStore(
|
|
255
|
+
makeInitial?: Effect<never, never, readonly T[]>,
|
|
256
|
+
config?: Omit<StoreConfig<PM>, "partitionValue"> & {
|
|
257
|
+
partitionValue?: (a: PM) => string
|
|
258
|
+
}
|
|
259
|
+
) {
|
|
260
|
+
return Do($ => {
|
|
261
|
+
const { make } = $(StoreMaker)
|
|
262
|
+
|
|
263
|
+
const store = $(
|
|
264
|
+
make<PM, string>(
|
|
265
|
+
pluralize(name),
|
|
266
|
+
makeInitial
|
|
267
|
+
? makeInitial
|
|
268
|
+
.map(_ => _.map(encodeToPM()))
|
|
269
|
+
: undefined,
|
|
270
|
+
{
|
|
271
|
+
...config,
|
|
272
|
+
partitionValue: config?.partitionValue ?? (_ => "primary") /*(isIntegrationEvent(r) ? r.companyId : r.id*/
|
|
273
|
+
}
|
|
274
|
+
)
|
|
275
|
+
)
|
|
276
|
+
return store
|
|
277
|
+
})
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return makeStore
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export const RepositoryBaseImpl = <Service>() => {
|
|
285
|
+
return <
|
|
286
|
+
PM extends { id: string; _etag: string | undefined },
|
|
287
|
+
Evt = never
|
|
288
|
+
>() =>
|
|
289
|
+
<ItemType extends string, T extends { id: string }, ConstructorInput, Api, E extends { id: string }>(
|
|
290
|
+
itemType: ItemType,
|
|
291
|
+
schema: Schema.Schema<unknown, T, ConstructorInput, E, Api>,
|
|
292
|
+
mapFrom: (pm: Omit<PM, "_etag">) => E,
|
|
293
|
+
mapTo: (e: E, etag: string | undefined) => PM
|
|
294
|
+
): (abstract new() => Repository<T, PM, Evt, ItemType>) & Tag<Service, Service> & {
|
|
295
|
+
make(
|
|
296
|
+
publishEvents: (evt: NonEmptyReadonlyArray<Evt>) => Effect<RequestContextContainer, never, void>,
|
|
297
|
+
makeInitial?: Effect<never, never, readonly T[]>,
|
|
298
|
+
config?: Omit<StoreConfig<PM>, "partitionValue"> & {
|
|
299
|
+
partitionValue?: (a: PM) => string
|
|
300
|
+
}
|
|
301
|
+
): Effect<StoreMaker, never, Repository<T, PM, Evt, ItemType>>
|
|
302
|
+
where: ReturnType<typeof makeWhere<PM>>
|
|
303
|
+
flatMap: <R1, E1, B>(f: (a: Service) => Effect<R1, E1, B>) => Effect<Service | R1, E1, B>
|
|
304
|
+
map: <B>(f: (a: Service) => B) => Effect<Service, never, B>
|
|
305
|
+
} => {
|
|
306
|
+
const mkRepo = makeRepo<PM, Evt>()(itemType, schema, mapFrom, mapTo)
|
|
307
|
+
abstract class Cls extends RepositoryBase<Service>()<T, PM, Evt, ItemType>(itemType) {
|
|
308
|
+
static readonly make = mkRepo.make
|
|
309
|
+
}
|
|
310
|
+
return Cls
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export const RepositoryDefaultImpl = <Service>() => {
|
|
315
|
+
return <
|
|
316
|
+
PM extends { id: string; _etag: string | undefined },
|
|
317
|
+
Evt = never
|
|
318
|
+
>() =>
|
|
319
|
+
<ItemType extends string, T extends { id: string }, ConstructorInput, Api, E extends { id: string }>(
|
|
320
|
+
itemType: ItemType,
|
|
321
|
+
schema: Schema.Schema<unknown, T, ConstructorInput, E, Api>,
|
|
322
|
+
mapFrom: (pm: Omit<PM, "_etag">) => E,
|
|
323
|
+
mapTo: (e: E, etag: string | undefined) => PM
|
|
324
|
+
): Tag<Service, Service> & {
|
|
325
|
+
new(
|
|
326
|
+
impl: Repository<T, PM, Evt, ItemType>
|
|
327
|
+
): Repository<T, PM, Evt, ItemType>
|
|
328
|
+
make(
|
|
329
|
+
publishEvents: (evt: NonEmptyReadonlyArray<Evt>) => Effect<RequestContextContainer, never, void>,
|
|
330
|
+
makeInitial?: Effect<never, never, readonly T[]>,
|
|
331
|
+
config?: Omit<StoreConfig<PM>, "partitionValue"> & {
|
|
332
|
+
partitionValue?: (a: PM) => string
|
|
333
|
+
}
|
|
334
|
+
): Effect<StoreMaker, never, Repository<T, PM, Evt, ItemType>>
|
|
335
|
+
toLayer(
|
|
336
|
+
publishEvents: (evt: NonEmptyReadonlyArray<Evt>) => Effect<RequestContextContainer, never, void>,
|
|
337
|
+
makeInitial?: Effect<never, never, readonly T[]>,
|
|
338
|
+
config?: Omit<StoreConfig<PM>, "partitionValue"> & {
|
|
339
|
+
partitionValue?: (a: PM) => string
|
|
340
|
+
}
|
|
341
|
+
): Layer<StoreMaker, never, Service>
|
|
342
|
+
where: ReturnType<typeof makeWhere<PM>>
|
|
343
|
+
flatMap: <R1, E1, B>(f: (a: Service) => Effect<R1, E1, B>) => Effect<Service | R1, E1, B>
|
|
344
|
+
map: <B>(f: (a: Service) => B) => Effect<Service, never, B>
|
|
345
|
+
repo: Repository<T, PM, Evt, ItemType> // just a helper to type the constructor
|
|
346
|
+
} => {
|
|
347
|
+
return class extends RepositoryBaseImpl<Service>()<PM, Evt>()(itemType, schema, mapFrom, mapTo) {
|
|
348
|
+
static toLayer(
|
|
349
|
+
publishEvents: (evt: NonEmptyReadonlyArray<Evt>) => Effect<RequestContextContainer, never, void>,
|
|
350
|
+
makeInitial?: Effect<never, never, readonly T[]>,
|
|
351
|
+
config?: Omit<StoreConfig<PM>, "partitionValue"> & {
|
|
352
|
+
partitionValue?: (a: PM) => string
|
|
353
|
+
}
|
|
354
|
+
) {
|
|
355
|
+
return this.make(publishEvents, makeInitial, config).map(impl => new this(impl) as any as Service).toLayer(this)
|
|
356
|
+
}
|
|
357
|
+
static repo: any
|
|
358
|
+
constructor(
|
|
359
|
+
impl: Repository<T, PM, Evt, ItemType>
|
|
360
|
+
) {
|
|
361
|
+
super()
|
|
362
|
+
Object.assign(this, impl)
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
@@ -25,9 +25,9 @@ export function makeCosmosStore({ prefix }: StorageConfig) {
|
|
|
25
25
|
return Effect.gen(function*($) {
|
|
26
26
|
const { db } = yield* $(CosmosClient.CosmosClient)
|
|
27
27
|
return {
|
|
28
|
-
make: <Id extends string, PM extends PersistenceModelType<Id
|
|
28
|
+
make: <Id extends string, PM extends PersistenceModelType<Id>>(
|
|
29
29
|
name: string,
|
|
30
|
-
seed?: Effect<never, never,
|
|
30
|
+
seed?: Effect<never, never, Iterable<PM>>,
|
|
31
31
|
config?: StoreConfig<PM>
|
|
32
32
|
) =>
|
|
33
33
|
Effect.gen(function*($) {
|
|
@@ -337,7 +337,7 @@ export function makeCosmosStore({ prefix }: StorageConfig) {
|
|
|
337
337
|
if (seed) {
|
|
338
338
|
const m = yield* $(seed)
|
|
339
339
|
yield* $(
|
|
340
|
-
Effect(
|
|
340
|
+
Effect(m.toNonEmptyArray)
|
|
341
341
|
.flatMapOpt(a =>
|
|
342
342
|
s
|
|
343
343
|
.bulkSet(a)
|
|
@@ -7,12 +7,12 @@ import { makeMemoryStoreInt, storeId } from "./Memory.js"
|
|
|
7
7
|
import type { PersistenceModelType, StorageConfig, Store, StoreConfig } from "./service.js"
|
|
8
8
|
import { StoreMaker } from "./service.js"
|
|
9
9
|
|
|
10
|
-
function makeDiskStoreInt<Id extends string,
|
|
10
|
+
function makeDiskStoreInt<Id extends string, PM extends PersistenceModelType<Id>>(
|
|
11
11
|
prefix: string,
|
|
12
12
|
namespace: string,
|
|
13
13
|
dir: string,
|
|
14
14
|
name: string,
|
|
15
|
-
seed?: Effect<never, never,
|
|
15
|
+
seed?: Effect<never, never, Iterable<PM>>
|
|
16
16
|
) {
|
|
17
17
|
return Effect.gen(function*($) {
|
|
18
18
|
const file = dir + "/" + prefix + name + (namespace === "primary" ? "" : "-" + namespace) + ".json"
|
|
@@ -25,11 +25,11 @@ function makeDiskStoreInt<Id extends string, Id2 extends Id, PM extends Persiste
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const store = yield* $(
|
|
28
|
-
makeMemoryStoreInt<Id,
|
|
28
|
+
makeMemoryStoreInt<Id, PM>(
|
|
29
29
|
name,
|
|
30
30
|
!fs.existsSync(file)
|
|
31
|
-
? seed
|
|
32
|
-
: fsStore.get
|
|
31
|
+
? seed
|
|
32
|
+
: fsStore.get
|
|
33
33
|
)
|
|
34
34
|
)
|
|
35
35
|
|
|
@@ -70,9 +70,9 @@ export function makeDiskStore({ prefix }: StorageConfig, dir: string) {
|
|
|
70
70
|
fs.mkdirSync(dir)
|
|
71
71
|
}
|
|
72
72
|
return {
|
|
73
|
-
make: <Id extends string,
|
|
73
|
+
make: <Id extends string, PM extends PersistenceModelType<Id>>(
|
|
74
74
|
name: string,
|
|
75
|
-
seed?: Effect<never, never,
|
|
75
|
+
seed?: Effect<never, never, Iterable<PM>>,
|
|
76
76
|
config?: StoreConfig<PM>
|
|
77
77
|
) =>
|
|
78
78
|
Effect.gen(function*($) {
|
|
@@ -91,7 +91,7 @@ export function makeDiskStore({ prefix }: StorageConfig, dir: string) {
|
|
|
91
91
|
Effect.suspend(() => {
|
|
92
92
|
const existing = stores.get(namespace)
|
|
93
93
|
if (existing) return Effect(existing)
|
|
94
|
-
return makeDiskStoreInt<Id,
|
|
94
|
+
return makeDiskStoreInt<Id, PM>(prefix, namespace, dir, name, seed).tap(store =>
|
|
95
95
|
Effect.sync(() => stores.set(namespace, store))
|
|
96
96
|
)
|
|
97
97
|
})
|
|
@@ -28,13 +28,14 @@ export const restoreFromRequestContext = RequestContextContainer.get.flatMap(ctx
|
|
|
28
28
|
storeId.set(ctx.namespace ?? "primary")
|
|
29
29
|
)
|
|
30
30
|
|
|
31
|
-
export function makeMemoryStoreInt<Id extends string,
|
|
31
|
+
export function makeMemoryStoreInt<Id extends string, PM extends PersistenceModelType<Id>>(
|
|
32
32
|
name: string,
|
|
33
|
-
seed?: Effect<never, never,
|
|
33
|
+
seed?: Effect<never, never, Iterable<PM>>
|
|
34
34
|
) {
|
|
35
35
|
return Effect.gen(function*($) {
|
|
36
36
|
const updateETag = makeUpdateETag(name)
|
|
37
|
-
const
|
|
37
|
+
const items_ = yield* $(seed ?? Effect([]))
|
|
38
|
+
const items = new Map(items_.toChunk.map(_ => [_.id, _] as const))
|
|
38
39
|
const makeStore = (): ReadonlyMap<Id, PM> => new Map([...items.entries()].map(([id, e]) => [id, makeETag(e)]))
|
|
39
40
|
const store = Ref.unsafeMake(makeStore())
|
|
40
41
|
const sem = Semaphore.unsafeMake(1)
|
|
@@ -78,14 +79,14 @@ export function makeMemoryStoreInt<Id extends string, Id2 extends Id, PM extends
|
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
export const makeMemoryStore = () => ({
|
|
81
|
-
make: <Id extends string,
|
|
82
|
+
make: <Id extends string, PM extends PersistenceModelType<Id>>(
|
|
82
83
|
name: string,
|
|
83
|
-
seed?: Effect<never, never,
|
|
84
|
+
seed?: Effect<never, never, Iterable<PM>>,
|
|
84
85
|
config?: StoreConfig<PM>
|
|
85
86
|
) =>
|
|
86
87
|
Effect.gen(function*($) {
|
|
87
88
|
const storesSem = Semaphore.unsafeMake(1)
|
|
88
|
-
const primary = yield* $(makeMemoryStoreInt<Id,
|
|
89
|
+
const primary = yield* $(makeMemoryStoreInt<Id, PM>(name, seed))
|
|
89
90
|
const stores = new Map([["primary", primary]])
|
|
90
91
|
const getStore = !config?.allowNamespace ? Effect.succeed(primary) : storeId.get.flatMap(namespace => {
|
|
91
92
|
const store = stores.get(namespace)
|
|
@@ -11,9 +11,9 @@ export function makeRedisStore({ prefix }: StorageConfig) {
|
|
|
11
11
|
return Effect.gen(function*($) {
|
|
12
12
|
const redis = yield* $(RedisClient.RedisClient)
|
|
13
13
|
return {
|
|
14
|
-
make: <Id extends string, PM extends PersistenceModelType<Id
|
|
14
|
+
make: <Id extends string, PM extends PersistenceModelType<Id>>(
|
|
15
15
|
name: string,
|
|
16
|
-
seed?: Effect<never, never,
|
|
16
|
+
seed?: Effect<never, never, Iterable<PM>>,
|
|
17
17
|
_config?: StoreConfig<PM>
|
|
18
18
|
) =>
|
|
19
19
|
Effect.gen(function*($) {
|
|
@@ -22,9 +22,9 @@ export function makeRedisStore({ prefix }: StorageConfig) {
|
|
|
22
22
|
const key = `${prefix}${name}`
|
|
23
23
|
const current = yield* $(RedisClient.get(key).orDie.provideService(RedisClient.RedisClient, redis))
|
|
24
24
|
if (!current.isSome()) {
|
|
25
|
-
const m = yield* $(seed ?? Effect(
|
|
25
|
+
const m = yield* $(seed ?? Effect([]))
|
|
26
26
|
yield* $(
|
|
27
|
-
RedisClient.set(key, JSON.stringify({ data: [...m
|
|
27
|
+
RedisClient.set(key, JSON.stringify({ data: [...m].map(e => makeETag(e)) }))
|
|
28
28
|
.orDie
|
|
29
29
|
.provideService(RedisClient.RedisClient, redis)
|
|
30
30
|
)
|
|
@@ -84,11 +84,11 @@ export interface Store<PM extends PersistenceModelType<Id>, Id extends string> {
|
|
|
84
84
|
* @tsplus companion StoreMaker.Ops
|
|
85
85
|
*/
|
|
86
86
|
export abstract class StoreMaker extends TagClass<StoreMaker>() {
|
|
87
|
-
abstract make: <
|
|
87
|
+
abstract make: <PM extends PersistenceModelType<Id>, Id extends string>(
|
|
88
88
|
name: string,
|
|
89
|
-
seed?: Effect<never, never,
|
|
90
|
-
config?: StoreConfig<
|
|
91
|
-
) => Effect<never, never, Store<
|
|
89
|
+
seed?: Effect<never, never, Iterable<PM>>,
|
|
90
|
+
config?: StoreConfig<PM>
|
|
91
|
+
) => Effect<never, never, Store<PM, Id>>
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
/**
|
package/dist/api/codec.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { Effect } from "@effect/io/Effect";
|
|
2
1
|
import * as MO from "@effect-app/schema";
|
|
3
|
-
import
|
|
2
|
+
import { Encoder, Parser } from "@effect-app/schema";
|
|
4
3
|
export declare function makeCodec<ParsedShape extends {
|
|
5
4
|
id: Id;
|
|
6
|
-
}, ConstructorInput, Encoded, Api, Id>(self: MO.Schema<unknown, ParsedShape, ConstructorInput, Encoded, Api>): readonly [(
|
|
5
|
+
}, ConstructorInput, Encoded, Api, Id>(self: MO.Schema<unknown, ParsedShape, ConstructorInput, Encoded, Api>): readonly [(a: unknown, env?: Parser.ParserEnv | undefined) => import("@effect/io/Effect").Effect<never, never, ParsedShape>, Encoder.Encoder<ParsedShape, Encoded>];
|
|
7
6
|
//# sourceMappingURL=codec.d.ts.map
|
package/dist/api/codec.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codec.d.ts","sourceRoot":"","sources":["../../_src/api/codec.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"codec.d.ts","sourceRoot":"","sources":["../../_src/api/codec.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,oBAAoB,CAAA;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAEpD,wBAAgB,SAAS,CACvB,WAAW,SAAS;IAAE,EAAE,EAAE,EAAE,CAAA;CAAE,EAC9B,gBAAgB,EAChB,OAAO,EACP,GAAG,EACH,EAAE,EACF,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,OAAO,EAAE,GAAG,CAAC,uKAKtE"}
|
package/dist/api/codec.js
CHANGED
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
import * as tsplus_module_1 from "@effect/io/Effect";
|
|
2
|
-
import * as tsplus_module_2 from "@effect-app/core/_ext/Iterable";
|
|
3
1
|
import * as MO from "@effect-app/schema";
|
|
4
2
|
import { Encoder, Parser } from "@effect-app/schema";
|
|
5
3
|
export function makeCodec(self) {
|
|
6
|
-
const
|
|
4
|
+
const decode = MO.condemnDie(Parser.for(self));
|
|
7
5
|
// TODO: strict
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
const encode = (u) => tsplus_module_1.sync(() => enc(u));
|
|
11
|
-
const encodeToMap = toMap(encode);
|
|
12
|
-
return [decode, encode, encodeToMap];
|
|
6
|
+
const encode = Encoder.for(self);
|
|
7
|
+
return [decode, encode];
|
|
13
8
|
}
|
|
14
|
-
|
|
15
|
-
return (a) => tsplus_module_1.map(tsplus_module_2.ext_collectAll(a.map(task => tsplus_module_1.all(tsplus_module_1.sync(() => task.id), encode(task)))), _ => new Map(_));
|
|
16
|
-
}
|
|
17
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29kZWMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9fc3JjL2FwaS9jb2RlYy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLE9BQU8sS0FBSyxFQUFFLE1BQU0sb0JBQW9CLENBQUE7QUFDeEMsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQTtBQUdwRCxNQUFNLFVBQVUsU0FBUyxDQU12QixJQUFxRTtJQUNyRSxNQUFNLEtBQUssR0FBRyxFQUFFLENBQUMsVUFBVSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQTtJQUM3QyxlQUFlO0lBQ2YsTUFBTSxNQUFNLEdBQUcsQ0FBQyxDQUFVLEVBQUUsR0FBZSxFQUFFLEVBQUUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLEdBQUcsQ0FBQyxDQUFBO0lBQzdELE1BQU0sR0FBRyxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUE7SUFFN0IsTUFBTSxNQUFNLEdBQUcsQ0FBQyxDQUFjLEVBQUUsRUFBRSxDQUFDLDJCQUFPLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFBO0lBQ2pELE1BQU0sV0FBVyxHQUFHLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQTtJQUNqQyxPQUFPLENBQUMsTUFBTSxFQUFFLE1BQU0sRUFBRSxXQUFXLENBQVUsQ0FBQTtBQUMvQyxDQUFDO0FBRUQsU0FBUyxLQUFLLENBQThCLE1BQXlDO0lBQ25GLE9BQU8sQ0FBQyxDQUFtQixFQUFFLEVBQUUsQ0FDN0Isb0JBQUEsK0JBQUEsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLG9CQUFXLDJCQUFPLElBQUksQ0FBQyxFQUFhLENBQUMsRUFBRSxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUNwRCxFQUNSLENBQUMsQ0FBQyxFQUFFLENBQUMsSUFBSSxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQTtBQUMzQixDQUFDIn0=
|
|
9
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29kZWMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9fc3JjL2FwaS9jb2RlYy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxNQUFNLG9CQUFvQixDQUFBO0FBQ3hDLE9BQU8sRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sb0JBQW9CLENBQUE7QUFFcEQsTUFBTSxVQUFVLFNBQVMsQ0FNdkIsSUFBcUU7SUFDckUsTUFBTSxNQUFNLEdBQUcsRUFBRSxDQUFDLFVBQVUsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUE7SUFDOUMsZUFBZTtJQUNmLE1BQU0sTUFBTSxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUE7SUFDaEMsT0FBTyxDQUFDLE1BQU0sRUFBRSxNQUFNLENBQVUsQ0FBQTtBQUNsQyxDQUFDIn0=
|