@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
package/src/Store/Cosmos.ts
CHANGED
|
@@ -7,10 +7,21 @@ import { dropUndefinedT } from "effect-app/utils"
|
|
|
7
7
|
import { CosmosClient, CosmosClientLayer } from "../adapters/cosmos-client.js"
|
|
8
8
|
import { OptimisticConcurrencyException } from "../errors.js"
|
|
9
9
|
import { InfraLogger } from "../logger.js"
|
|
10
|
+
import type { FieldValues } from "../Model/filter/types.js"
|
|
10
11
|
import { buildWhereCosmosQuery3, logQuery } from "./Cosmos/query.js"
|
|
11
12
|
import { StoreMaker } from "./service.js"
|
|
12
13
|
import type { FilterArgs, PersistenceModelType, StorageConfig, Store, StoreConfig } from "./service.js"
|
|
13
14
|
|
|
15
|
+
const makeMapId =
|
|
16
|
+
<IdKey extends keyof Encoded, Encoded extends FieldValues>(idKey: IdKey) => ({ [idKey]: id, ...e }: Encoded) => ({
|
|
17
|
+
...e,
|
|
18
|
+
id
|
|
19
|
+
})
|
|
20
|
+
const makeReverseMapId =
|
|
21
|
+
<IdKey extends keyof Encoded, Encoded extends FieldValues>(idKey: IdKey) =>
|
|
22
|
+
({ id, ...t }: PersistenceModelType<Omit<Encoded, IdKey> & { id: string }>) =>
|
|
23
|
+
({ ...t, [idKey]: id }) as any as PersistenceModelType<Encoded>
|
|
24
|
+
|
|
14
25
|
class CosmosDbOperationError {
|
|
15
26
|
constructor(readonly message: string) {}
|
|
16
27
|
} // TODO: Retry operation when running into RU limit.
|
|
@@ -19,13 +30,17 @@ function makeCosmosStore({ prefix }: StorageConfig) {
|
|
|
19
30
|
return Effect.gen(function*() {
|
|
20
31
|
const { db } = yield* CosmosClient
|
|
21
32
|
return {
|
|
22
|
-
make: <
|
|
33
|
+
make: <IdKey extends keyof Encoded, Encoded extends FieldValues, R = never, E = never>(
|
|
23
34
|
name: string,
|
|
35
|
+
idKey: IdKey,
|
|
24
36
|
seed?: Effect<Iterable<Encoded>, E, R>,
|
|
25
37
|
config?: StoreConfig<Encoded>
|
|
26
38
|
) =>
|
|
27
39
|
Effect.gen(function*() {
|
|
40
|
+
const mapId = makeMapId<IdKey, Encoded>(idKey)
|
|
41
|
+
const mapReverseId = makeReverseMapId<IdKey, Encoded>(idKey)
|
|
28
42
|
type PM = PersistenceModelType<Encoded>
|
|
43
|
+
type PMCosmos = PersistenceModelType<Omit<Encoded, IdKey> & { id: string }>
|
|
29
44
|
const containerId = `${prefix}${name}`
|
|
30
45
|
yield* Effect.promise(() =>
|
|
31
46
|
db.containers.createIfNotExists(dropUndefinedT({
|
|
@@ -47,34 +62,37 @@ function makeCosmosStore({ prefix }: StorageConfig) {
|
|
|
47
62
|
.gen(function*() {
|
|
48
63
|
// TODO: disable batching if need atomicity
|
|
49
64
|
// we delay and batch to keep low amount of RUs
|
|
50
|
-
const b = [...items]
|
|
51
|
-
(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
65
|
+
const b = [...items]
|
|
66
|
+
.map(
|
|
67
|
+
(x) =>
|
|
68
|
+
[
|
|
69
|
+
x,
|
|
70
|
+
Option.match(Option.fromNullable(x._etag), {
|
|
71
|
+
onNone: () =>
|
|
72
|
+
dropUndefinedT({
|
|
73
|
+
operationType: "Create" as const,
|
|
74
|
+
resourceBody: {
|
|
75
|
+
...Struct.omit(x, "_etag", idKey),
|
|
76
|
+
id: x[idKey],
|
|
77
|
+
_partitionKey: config?.partitionValue(x)
|
|
78
|
+
},
|
|
79
|
+
partitionKey: config?.partitionValue(x)
|
|
80
|
+
}),
|
|
81
|
+
onSome: (eTag) =>
|
|
82
|
+
dropUndefinedT({
|
|
83
|
+
operationType: "Replace" as const,
|
|
84
|
+
id: x[idKey],
|
|
85
|
+
resourceBody: {
|
|
86
|
+
...Struct.omit(x, "_etag", idKey),
|
|
87
|
+
id: x[idKey],
|
|
88
|
+
_partitionKey: config?.partitionValue(x)
|
|
89
|
+
},
|
|
90
|
+
ifMatch: eTag,
|
|
91
|
+
partitionKey: config?.partitionValue(x)
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
] as const
|
|
95
|
+
)
|
|
78
96
|
const batches = Chunk.toReadonlyArray(Array.chunk_(b, config?.maxBulkSize ?? 10))
|
|
79
97
|
|
|
80
98
|
const batchResult = yield* Effect.forEach(
|
|
@@ -135,15 +153,17 @@ function makeCosmosStore({ prefix }: StorageConfig) {
|
|
|
135
153
|
onNone: () => ({
|
|
136
154
|
operationType: "Create" as const,
|
|
137
155
|
resourceBody: {
|
|
138
|
-
...Struct.omit(x, "_etag"),
|
|
156
|
+
...Struct.omit(x, "_etag", idKey),
|
|
157
|
+
id: x[idKey],
|
|
139
158
|
_partitionKey: config?.partitionValue(x)
|
|
140
159
|
}
|
|
141
160
|
}),
|
|
142
161
|
onSome: (eTag) => ({
|
|
143
162
|
operationType: "Replace" as const,
|
|
144
|
-
id: x
|
|
163
|
+
id: x[idKey],
|
|
145
164
|
resourceBody: {
|
|
146
|
-
...Struct.omit(x, "_etag"),
|
|
165
|
+
...Struct.omit(x, "_etag", idKey),
|
|
166
|
+
id: x[idKey],
|
|
147
167
|
_partitionKey: config?.partitionValue(x)
|
|
148
168
|
},
|
|
149
169
|
ifMatch: eTag
|
|
@@ -173,8 +193,9 @@ function makeCosmosStore({ prefix }: StorageConfig) {
|
|
|
173
193
|
)
|
|
174
194
|
}
|
|
175
195
|
|
|
176
|
-
return batch.map(([e], i) => ({
|
|
196
|
+
return batch.map(([{ id, ...e }], i) => ({
|
|
177
197
|
...e,
|
|
198
|
+
[idKey]: id,
|
|
178
199
|
_etag: result[i]?.eTag
|
|
179
200
|
})) as unknown as NonEmptyReadonlyArray<Encoded>
|
|
180
201
|
})
|
|
@@ -187,7 +208,7 @@ function makeCosmosStore({ prefix }: StorageConfig) {
|
|
|
187
208
|
}))
|
|
188
209
|
}
|
|
189
210
|
|
|
190
|
-
const s: Store<
|
|
211
|
+
const s: Store<IdKey, Encoded> = {
|
|
191
212
|
all: Effect
|
|
192
213
|
.sync(() => ({
|
|
193
214
|
query: `SELECT * FROM ${name} f WHERE f.id != @id`,
|
|
@@ -199,9 +220,13 @@ function makeCosmosStore({ prefix }: StorageConfig) {
|
|
|
199
220
|
Effect.promise(() =>
|
|
200
221
|
container
|
|
201
222
|
.items
|
|
202
|
-
.query<
|
|
223
|
+
.query<PMCosmos>(q)
|
|
203
224
|
.fetchAll()
|
|
204
|
-
.then(({ resources }) =>
|
|
225
|
+
.then(({ resources }) =>
|
|
226
|
+
resources.map(
|
|
227
|
+
(_) => ({ ...defaultValues, ...mapReverseId(_) })
|
|
228
|
+
)
|
|
229
|
+
)
|
|
205
230
|
)
|
|
206
231
|
),
|
|
207
232
|
Effect
|
|
@@ -223,6 +248,7 @@ function makeCosmosStore({ prefix }: StorageConfig) {
|
|
|
223
248
|
return Effect
|
|
224
249
|
.sync(() =>
|
|
225
250
|
buildWhereCosmosQuery3(
|
|
251
|
+
idKey,
|
|
226
252
|
filter ?? [],
|
|
227
253
|
name,
|
|
228
254
|
importedMarkerId,
|
|
@@ -244,13 +270,20 @@ function makeCosmosStore({ prefix }: StorageConfig) {
|
|
|
244
270
|
.query<M>(q)
|
|
245
271
|
.fetchAll()
|
|
246
272
|
.then(({ resources }) =>
|
|
247
|
-
resources.map((_) =>
|
|
273
|
+
resources.map((_) =>
|
|
274
|
+
({
|
|
275
|
+
...pipe(defaultValues, Struct.pick(...f.select!)),
|
|
276
|
+
...mapReverseId(_ as any)
|
|
277
|
+
}) as any
|
|
278
|
+
)
|
|
248
279
|
)
|
|
249
280
|
: container
|
|
250
281
|
.items
|
|
251
282
|
.query<{ f: M }>(q)
|
|
252
283
|
.fetchAll()
|
|
253
|
-
.then(({ resources }) =>
|
|
284
|
+
.then(({ resources }) =>
|
|
285
|
+
resources.map(({ f }) => ({ ...defaultValues, ...mapReverseId(f as any) }) as any)
|
|
286
|
+
)
|
|
254
287
|
)
|
|
255
288
|
)
|
|
256
289
|
)
|
|
@@ -263,10 +296,10 @@ function makeCosmosStore({ prefix }: StorageConfig) {
|
|
|
263
296
|
Effect
|
|
264
297
|
.promise(() =>
|
|
265
298
|
container
|
|
266
|
-
.item(id, config?.partitionValue({ id } as Encoded))
|
|
299
|
+
.item(id, config?.partitionValue({ [idKey]: id } as Encoded))
|
|
267
300
|
.read<Encoded>()
|
|
268
301
|
.then(({ resource }) =>
|
|
269
|
-
Option.fromNullable(resource).pipe(Option.map((_) => ({ ...defaultValues, ..._ })))
|
|
302
|
+
Option.fromNullable(resource).pipe(Option.map((_) => ({ ...defaultValues, ...mapReverseId(_) })))
|
|
270
303
|
)
|
|
271
304
|
)
|
|
272
305
|
.pipe(Effect
|
|
@@ -275,7 +308,7 @@ function makeCosmosStore({ prefix }: StorageConfig) {
|
|
|
275
308
|
attributes: {
|
|
276
309
|
"repository.container_id": containerId,
|
|
277
310
|
"repository.model_name": name,
|
|
278
|
-
partitionValue: config?.partitionValue({ id } as Encoded),
|
|
311
|
+
partitionValue: config?.partitionValue({ [idKey]: id } as Encoded),
|
|
279
312
|
id
|
|
280
313
|
}
|
|
281
314
|
})),
|
|
@@ -288,14 +321,14 @@ function makeCosmosStore({ prefix }: StorageConfig) {
|
|
|
288
321
|
onNone: () =>
|
|
289
322
|
Effect.promise(() =>
|
|
290
323
|
container.items.create({
|
|
291
|
-
...e,
|
|
324
|
+
...mapId(e),
|
|
292
325
|
_partitionKey: config?.partitionValue(e)
|
|
293
326
|
})
|
|
294
327
|
),
|
|
295
328
|
onSome: (eTag) =>
|
|
296
329
|
Effect.promise(() =>
|
|
297
|
-
container.item(e
|
|
298
|
-
{ ...e, _partitionKey: config?.partitionValue(e) },
|
|
330
|
+
container.item(e[idKey], config?.partitionValue(e)).replace(
|
|
331
|
+
{ ...mapId(e), _partitionKey: config?.partitionValue(e) },
|
|
299
332
|
{
|
|
300
333
|
accessCondition: {
|
|
301
334
|
type: "IfMatch",
|
|
@@ -310,7 +343,7 @@ function makeCosmosStore({ prefix }: StorageConfig) {
|
|
|
310
343
|
Effect
|
|
311
344
|
.flatMap((x) => {
|
|
312
345
|
if (x.statusCode === 412 || x.statusCode === 404) {
|
|
313
|
-
return new OptimisticConcurrencyException({ type: name, id: e
|
|
346
|
+
return new OptimisticConcurrencyException({ type: name, id: e[idKey] })
|
|
314
347
|
}
|
|
315
348
|
if (x.statusCode > 299 || x.statusCode < 200) {
|
|
316
349
|
return Effect.die(
|
|
@@ -327,18 +360,22 @@ function makeCosmosStore({ prefix }: StorageConfig) {
|
|
|
327
360
|
Effect
|
|
328
361
|
.withSpan("Cosmos.set [effect-app/infra/Store]", {
|
|
329
362
|
captureStackTrace: false,
|
|
330
|
-
attributes: {
|
|
363
|
+
attributes: {
|
|
364
|
+
"repository.container_id": containerId,
|
|
365
|
+
"repository.model_name": name,
|
|
366
|
+
id: e[idKey]
|
|
367
|
+
}
|
|
331
368
|
})
|
|
332
369
|
),
|
|
333
370
|
batchSet,
|
|
334
371
|
bulkSet,
|
|
335
372
|
remove: (e: Encoded) =>
|
|
336
373
|
Effect
|
|
337
|
-
.promise(() => container.item(e
|
|
374
|
+
.promise(() => container.item(e[idKey], config?.partitionValue(e)).delete())
|
|
338
375
|
.pipe(Effect
|
|
339
376
|
.withSpan("Cosmos.remove [effect-app/infra/Store]", {
|
|
340
377
|
captureStackTrace: false,
|
|
341
|
-
attributes: { "repository.container_id": containerId, "repository.model_name": name, id: e
|
|
378
|
+
attributes: { "repository.container_id": containerId, "repository.model_name": name, id: e[idKey] }
|
|
342
379
|
}))
|
|
343
380
|
}
|
|
344
381
|
|
package/src/Store/Disk.ts
CHANGED
|
@@ -4,12 +4,14 @@ import * as fu from "../fileUtil.js"
|
|
|
4
4
|
import fs from "fs"
|
|
5
5
|
|
|
6
6
|
import { Console, Effect, FiberRef, flow } from "effect-app"
|
|
7
|
+
import type { FieldValues } from "../Model/filter/types.js"
|
|
7
8
|
import { makeMemoryStoreInt, storeId } from "./Memory.js"
|
|
8
9
|
import type { PersistenceModelType, StorageConfig, Store, StoreConfig } from "./service.js"
|
|
9
10
|
import { StoreMaker } from "./service.js"
|
|
10
11
|
|
|
11
|
-
function makeDiskStoreInt<
|
|
12
|
+
function makeDiskStoreInt<IdKey extends keyof Encoded, Encoded extends FieldValues, R, E>(
|
|
12
13
|
prefix: string,
|
|
14
|
+
idKey: IdKey,
|
|
13
15
|
namespace: string,
|
|
14
16
|
dir: string,
|
|
15
17
|
name: string,
|
|
@@ -68,8 +70,9 @@ function makeDiskStoreInt<Id extends string, Encoded extends { id: Id }, R, E>(
|
|
|
68
70
|
)
|
|
69
71
|
}
|
|
70
72
|
|
|
71
|
-
const store = yield* makeMemoryStoreInt<
|
|
73
|
+
const store = yield* makeMemoryStoreInt<IdKey, Encoded, R, E>(
|
|
72
74
|
name,
|
|
75
|
+
idKey,
|
|
73
76
|
namespace,
|
|
74
77
|
!fs.existsSync(file)
|
|
75
78
|
? seed
|
|
@@ -107,7 +110,7 @@ function makeDiskStoreInt<Id extends string, Encoded extends { id: Id }, R, E>(
|
|
|
107
110
|
store.remove,
|
|
108
111
|
Effect.tap(flushToDiskInBackground)
|
|
109
112
|
)
|
|
110
|
-
} satisfies Store<
|
|
113
|
+
} satisfies Store<IdKey, Encoded>
|
|
111
114
|
})
|
|
112
115
|
}
|
|
113
116
|
|
|
@@ -121,15 +124,16 @@ export function makeDiskStore({ prefix }: StorageConfig, dir: string) {
|
|
|
121
124
|
fs.mkdirSync(dir)
|
|
122
125
|
}
|
|
123
126
|
return {
|
|
124
|
-
make: <
|
|
127
|
+
make: <IdKey extends keyof Encoded, Encoded extends FieldValues, R, E>(
|
|
125
128
|
name: string,
|
|
129
|
+
idKey: IdKey,
|
|
126
130
|
seed?: Effect<Iterable<Encoded>, E, R>,
|
|
127
131
|
config?: StoreConfig<Encoded>
|
|
128
132
|
) =>
|
|
129
133
|
Effect.gen(function*() {
|
|
130
134
|
const storesSem = Effect.unsafeMakeSemaphore(1)
|
|
131
|
-
const primary = yield* makeDiskStoreInt(prefix, "primary", dir, name, seed, config?.defaultValues)
|
|
132
|
-
const stores = new Map<string, Store<
|
|
135
|
+
const primary = yield* makeDiskStoreInt(prefix, idKey, "primary", dir, name, seed, config?.defaultValues)
|
|
136
|
+
const stores = new Map<string, Store<IdKey, Encoded>>([["primary", primary]])
|
|
133
137
|
const ctx = yield* Effect.context<R>()
|
|
134
138
|
const getStore = !config?.allowNamespace
|
|
135
139
|
? Effect.succeed(primary)
|
|
@@ -145,7 +149,15 @@ export function makeDiskStore({ prefix }: StorageConfig, dir: string) {
|
|
|
145
149
|
Effect.suspend(() => {
|
|
146
150
|
const existing = stores.get(namespace)
|
|
147
151
|
if (existing) return Effect.sync(() => existing)
|
|
148
|
-
return makeDiskStoreInt<
|
|
152
|
+
return makeDiskStoreInt<IdKey, Encoded, R, E>(
|
|
153
|
+
prefix,
|
|
154
|
+
idKey,
|
|
155
|
+
namespace,
|
|
156
|
+
dir,
|
|
157
|
+
name,
|
|
158
|
+
seed,
|
|
159
|
+
config?.defaultValues
|
|
160
|
+
)
|
|
149
161
|
.pipe(
|
|
150
162
|
Effect.orDie,
|
|
151
163
|
Effect.provide(ctx),
|
|
@@ -155,7 +167,7 @@ export function makeDiskStore({ prefix }: StorageConfig, dir: string) {
|
|
|
155
167
|
)
|
|
156
168
|
}))
|
|
157
169
|
|
|
158
|
-
const s: Store<
|
|
170
|
+
const s: Store<IdKey, Encoded> = {
|
|
159
171
|
all: Effect.flatMap(getStore, (_) => _.all),
|
|
160
172
|
find: (...args) => Effect.flatMap(getStore, (_) => _.find(...args)),
|
|
161
173
|
filter: (...args) => Effect.flatMap(getStore, (_) => _.filter(...args)),
|
package/src/Store/Memory.ts
CHANGED
|
@@ -5,12 +5,13 @@ import type { NonEmptyReadonlyArray } from "effect-app"
|
|
|
5
5
|
import { NonEmptyString255 } from "effect-app/Schema"
|
|
6
6
|
import { get } from "effect-app/utils"
|
|
7
7
|
import { InfraLogger } from "../logger.js"
|
|
8
|
+
import type { FieldValues } from "../Model/filter/types.js"
|
|
8
9
|
import { codeFilter } from "./codeFilter.js"
|
|
9
10
|
import type { FilterArgs, PersistenceModelType, Store, StoreConfig } from "./service.js"
|
|
10
11
|
import { StoreMaker } from "./service.js"
|
|
11
12
|
import { makeUpdateETag } from "./utils.js"
|
|
12
13
|
|
|
13
|
-
export function memFilter<T extends
|
|
14
|
+
export function memFilter<T extends FieldValues, U extends keyof T = never>(f: FilterArgs<T, U>) {
|
|
14
15
|
type M = U extends undefined ? T : Pick<T, U>
|
|
15
16
|
return ((c: T[]): M[] => {
|
|
16
17
|
const select = (r: T[]): M[] => (f.select ? r.map(Struct.pick(...f.select)) : r) as any
|
|
@@ -76,8 +77,9 @@ function logQuery(f: FilterArgs<any, any>, defaultValues?: any) {
|
|
|
76
77
|
}))
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
export function makeMemoryStoreInt<
|
|
80
|
+
export function makeMemoryStoreInt<IdKey extends keyof Encoded, Encoded extends FieldValues, R = never, E = never>(
|
|
80
81
|
modelName: string,
|
|
82
|
+
idKey: IdKey,
|
|
81
83
|
namespace: string,
|
|
82
84
|
seed?: Effect<Iterable<Encoded>, E, R>,
|
|
83
85
|
_defaultValues?: Partial<Encoded>
|
|
@@ -88,8 +90,8 @@ export function makeMemoryStoreInt<Id extends string, Encoded extends { id: Id }
|
|
|
88
90
|
const items_ = yield* seed ?? Effect.sync(() => [])
|
|
89
91
|
const defaultValues = _defaultValues ?? {}
|
|
90
92
|
|
|
91
|
-
const items = new Map([...items_].map((_) => [_
|
|
92
|
-
const store = Ref.unsafeMake<ReadonlyMap<
|
|
93
|
+
const items = new Map([...items_].map((_) => [_[idKey], { _etag: undefined, ...defaultValues, ..._ }] as const))
|
|
94
|
+
const store = Ref.unsafeMake<ReadonlyMap<string, PM>>(items)
|
|
93
95
|
const sem = Effect.unsafeMakeSemaphore(1)
|
|
94
96
|
const withPermit = sem.withPermits(1)
|
|
95
97
|
const values = Effect.map(Ref.get(store), (s) => s.values())
|
|
@@ -98,7 +100,7 @@ export function makeMemoryStoreInt<Id extends string, Encoded extends { id: Id }
|
|
|
98
100
|
|
|
99
101
|
const batchSet = (items: NonEmptyReadonlyArray<PM>) =>
|
|
100
102
|
Effect
|
|
101
|
-
.forEach(items, (i) => Effect.flatMap(s.find(i
|
|
103
|
+
.forEach(items, (i) => Effect.flatMap(s.find(i[idKey]), (current) => updateETag(i, idKey, current)))
|
|
102
104
|
.pipe(
|
|
103
105
|
Effect
|
|
104
106
|
.tap((items) =>
|
|
@@ -107,8 +109,8 @@ export function makeMemoryStoreInt<Id extends string, Encoded extends { id: Id }
|
|
|
107
109
|
.pipe(
|
|
108
110
|
Effect
|
|
109
111
|
.map((m) => {
|
|
110
|
-
const mut = m as Map<
|
|
111
|
-
items.forEach((e) => mut.set(e
|
|
112
|
+
const mut = m as Map<string, PM>
|
|
113
|
+
items.forEach((e) => mut.set(e[idKey], e))
|
|
112
114
|
return mut
|
|
113
115
|
}),
|
|
114
116
|
Effect
|
|
@@ -119,7 +121,7 @@ export function makeMemoryStoreInt<Id extends string, Encoded extends { id: Id }
|
|
|
119
121
|
.map((_) => _),
|
|
120
122
|
withPermit
|
|
121
123
|
)
|
|
122
|
-
const s: Store<
|
|
124
|
+
const s: Store<IdKey, Encoded> = {
|
|
123
125
|
all: all.pipe(Effect.withSpan("Memory.all [effect-app/infra/Store]", {
|
|
124
126
|
captureStackTrace: false,
|
|
125
127
|
attributes: {
|
|
@@ -153,13 +155,13 @@ export function makeMemoryStoreInt<Id extends string, Encoded extends { id: Id }
|
|
|
153
155
|
),
|
|
154
156
|
set: (e) =>
|
|
155
157
|
s
|
|
156
|
-
.find(e
|
|
158
|
+
.find(e[idKey])
|
|
157
159
|
.pipe(
|
|
158
|
-
Effect.flatMap((current) => updateETag(e, current)),
|
|
160
|
+
Effect.flatMap((current) => updateETag(e, idKey, current)),
|
|
159
161
|
Effect
|
|
160
162
|
.tap((e) =>
|
|
161
163
|
Ref.get(store).pipe(
|
|
162
|
-
Effect.map((_) => new Map([..._, [e
|
|
164
|
+
Effect.map((_) => new Map([..._, [e[idKey], e]])),
|
|
163
165
|
Effect.flatMap((_) => Ref.set(store, _))
|
|
164
166
|
)
|
|
165
167
|
),
|
|
@@ -197,7 +199,7 @@ export function makeMemoryStoreInt<Id extends string, Encoded extends { id: Id }
|
|
|
197
199
|
Ref
|
|
198
200
|
.get(store)
|
|
199
201
|
.pipe(
|
|
200
|
-
Effect.map((_) => new Map([..._].filter(([_]) => _ !== e
|
|
202
|
+
Effect.map((_) => new Map([..._].filter(([_]) => _ !== e[idKey]))),
|
|
201
203
|
Effect.flatMap((_) => Ref.set(store, _)),
|
|
202
204
|
withPermit,
|
|
203
205
|
Effect.withSpan("Memory.remove [effect-app/infra/Store]", {
|
|
@@ -211,14 +213,21 @@ export function makeMemoryStoreInt<Id extends string, Encoded extends { id: Id }
|
|
|
211
213
|
}
|
|
212
214
|
|
|
213
215
|
export const makeMemoryStore = () => ({
|
|
214
|
-
make: <
|
|
216
|
+
make: <IdKey extends keyof Encoded, Encoded extends FieldValues, R, E>(
|
|
215
217
|
modelName: string,
|
|
218
|
+
idKey: IdKey,
|
|
216
219
|
seed?: Effect<Iterable<Encoded>, E, R>,
|
|
217
220
|
config?: StoreConfig<Encoded>
|
|
218
221
|
) =>
|
|
219
222
|
Effect.gen(function*() {
|
|
220
223
|
const storesSem = Effect.unsafeMakeSemaphore(1)
|
|
221
|
-
const primary = yield* makeMemoryStoreInt<
|
|
224
|
+
const primary = yield* makeMemoryStoreInt<IdKey, Encoded, R, E>(
|
|
225
|
+
modelName,
|
|
226
|
+
idKey,
|
|
227
|
+
"primary",
|
|
228
|
+
seed,
|
|
229
|
+
config?.defaultValues
|
|
230
|
+
)
|
|
222
231
|
const ctx = yield* Effect.context<R>()
|
|
223
232
|
const stores = new Map([["primary", primary]])
|
|
224
233
|
const getStore = !config?.allowNamespace
|
|
@@ -234,7 +243,7 @@ export const makeMemoryStore = () => ({
|
|
|
234
243
|
return storesSem.withPermits(1)(Effect.suspend(() => {
|
|
235
244
|
const store = stores.get(namespace)
|
|
236
245
|
if (store) return Effect.sync(() => store)
|
|
237
|
-
return makeMemoryStoreInt(modelName, namespace, seed, config?.defaultValues)
|
|
246
|
+
return makeMemoryStoreInt(modelName, idKey, namespace, seed, config?.defaultValues)
|
|
238
247
|
.pipe(
|
|
239
248
|
Effect.orDie,
|
|
240
249
|
Effect.provide(ctx),
|
|
@@ -242,7 +251,7 @@ export const makeMemoryStore = () => ({
|
|
|
242
251
|
)
|
|
243
252
|
}))
|
|
244
253
|
}))
|
|
245
|
-
const s: Store<
|
|
254
|
+
const s: Store<IdKey, Encoded> = {
|
|
246
255
|
all: Effect.flatMap(getStore, (_) => _.all),
|
|
247
256
|
find: (...args) => Effect.flatMap(getStore, (_) => _.find(...args)),
|
|
248
257
|
filter: (...args) => Effect.flatMap(getStore, (_) => _.filter(...args)),
|
package/src/Store/codeFilter.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { Array, Option } from "effect-app"
|
|
4
4
|
import { assertUnreachable, get } from "effect-app/utils"
|
|
5
5
|
import type { FilterR, FilterResult } from "../Model/filter/filterApi.js"
|
|
6
|
+
import type { FieldValues } from "../Model/filter/types.js"
|
|
6
7
|
import type { Filter } from "./service.js"
|
|
7
8
|
import { compare, greaterThan, greaterThanExclusive, lowerThan, lowerThanExclusive } from "./utils.js"
|
|
8
9
|
|
|
@@ -120,6 +121,6 @@ export const codeFilter3_ = <E>(state: readonly FilterResult[], sut: E): boolean
|
|
|
120
121
|
return eval(s)
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
export function codeFilter<E extends
|
|
124
|
+
export function codeFilter<E extends FieldValues, NE extends E>(filter: Filter) {
|
|
124
125
|
return (x: E) => codeFilter3_(filter, x) ? Option.some(x as unknown as NE) : Option.none()
|
|
125
126
|
}
|
package/src/Store/service.ts
CHANGED
|
@@ -57,7 +57,7 @@ export interface O<TFieldValues extends FieldValues> {
|
|
|
57
57
|
direction: "ASC" | "DESC"
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
export interface FilterArgs<Encoded extends
|
|
60
|
+
export interface FilterArgs<Encoded extends FieldValues, U extends keyof Encoded = never> {
|
|
61
61
|
t: Encoded
|
|
62
62
|
filter?: Filter | undefined
|
|
63
63
|
select?: NonEmptyReadonlyArray<U> | undefined
|
|
@@ -66,18 +66,18 @@ export interface FilterArgs<Encoded extends { id: string }, U extends keyof Enco
|
|
|
66
66
|
skip?: number | undefined
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
export type FilterFunc<Encoded extends
|
|
69
|
+
export type FilterFunc<Encoded extends FieldValues> = <U extends keyof Encoded = never>(
|
|
70
70
|
args: FilterArgs<Encoded, U>
|
|
71
71
|
) => Effect<(U extends undefined ? Encoded : Pick<Encoded, U>)[]>
|
|
72
72
|
|
|
73
73
|
export interface Store<
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
IdKey extends keyof Encoded,
|
|
75
|
+
Encoded extends FieldValues,
|
|
76
76
|
PM extends PersistenceModelType<Encoded> = PersistenceModelType<Encoded>
|
|
77
77
|
> {
|
|
78
78
|
all: Effect<PM[]>
|
|
79
79
|
filter: FilterFunc<Encoded>
|
|
80
|
-
find: (id:
|
|
80
|
+
find: (id: Encoded[IdKey]) => Effect<Option<PM>>
|
|
81
81
|
set: (e: PM) => Effect<PM, OptimisticConcurrencyException>
|
|
82
82
|
batchSet: (
|
|
83
83
|
items: NonEmptyReadonlyArray<PM>
|
|
@@ -96,11 +96,12 @@ export interface Store<
|
|
|
96
96
|
* @tsplus companion StoreMaker.Ops
|
|
97
97
|
*/
|
|
98
98
|
export class StoreMaker extends Context.TagId("effect-app/StoreMaker")<StoreMaker, {
|
|
99
|
-
make: <
|
|
99
|
+
make: <IdKey extends keyof Encoded, Encoded extends FieldValues, R = never, E = never>(
|
|
100
100
|
name: string,
|
|
101
|
+
idKey: IdKey,
|
|
101
102
|
seed?: Effect<Iterable<Encoded>, E, R>,
|
|
102
103
|
config?: StoreConfig<Encoded>
|
|
103
|
-
) => Effect<Store<
|
|
104
|
+
) => Effect<Store<IdKey, Encoded>, E, R>
|
|
104
105
|
}>() {
|
|
105
106
|
}
|
|
106
107
|
|
package/src/Store/utils.ts
CHANGED
|
@@ -13,18 +13,19 @@ export const makeETag = <E extends PersistenceModelType<{}>>(
|
|
|
13
13
|
}) as any
|
|
14
14
|
|
|
15
15
|
export const makeUpdateETag =
|
|
16
|
-
(type: string) =>
|
|
16
|
+
(type: string) =>
|
|
17
|
+
<IdKey extends keyof E, E extends PersistenceModelType<{}>>(e: E, idKey: IdKey, current: Option<E>) =>
|
|
17
18
|
Effect.gen(function*() {
|
|
18
19
|
if (e._etag) {
|
|
19
20
|
yield* Effect.mapError(
|
|
20
21
|
current,
|
|
21
|
-
() => new OptimisticConcurrencyException({ type, id: e
|
|
22
|
+
() => new OptimisticConcurrencyException({ type, id: e[idKey] as string, current: "", found: e._etag })
|
|
22
23
|
)
|
|
23
24
|
}
|
|
24
25
|
if (Option.isSome(current) && current.value._etag !== e._etag) {
|
|
25
26
|
return yield* new OptimisticConcurrencyException({
|
|
26
27
|
type,
|
|
27
|
-
id: current.value
|
|
28
|
+
id: current.value[idKey] as string,
|
|
28
29
|
current: current.value._etag,
|
|
29
30
|
found: e._etag
|
|
30
31
|
})
|