@effect-app/infra 2.11.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.
Files changed (65) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/_cjs/Model/Repository/ext.cjs +1 -1
  3. package/_cjs/Model/Repository/ext.cjs.map +1 -1
  4. package/_cjs/Model/Repository/internal/internal.cjs +18 -20
  5. package/_cjs/Model/Repository/internal/internal.cjs.map +1 -1
  6. package/_cjs/Model/Repository/makeRepo.cjs.map +1 -1
  7. package/_cjs/Store/Cosmos/query.cjs +9 -2
  8. package/_cjs/Store/Cosmos/query.cjs.map +1 -1
  9. package/_cjs/Store/Cosmos.cjs +49 -23
  10. package/_cjs/Store/Cosmos.cjs.map +1 -1
  11. package/_cjs/Store/Disk.cjs +5 -5
  12. package/_cjs/Store/Disk.cjs.map +1 -1
  13. package/_cjs/Store/Memory.cjs +9 -9
  14. package/_cjs/Store/Memory.cjs.map +1 -1
  15. package/_cjs/Store/codeFilter.cjs.map +1 -1
  16. package/_cjs/Store/service.cjs.map +1 -1
  17. package/_cjs/Store/utils.cjs +3 -3
  18. package/_cjs/Store/utils.cjs.map +1 -1
  19. package/dist/Model/Repository/ext.d.ts +6 -9
  20. package/dist/Model/Repository/ext.d.ts.map +1 -1
  21. package/dist/Model/Repository/ext.js +2 -2
  22. package/dist/Model/Repository/internal/internal.d.ts +4 -9
  23. package/dist/Model/Repository/internal/internal.d.ts.map +1 -1
  24. package/dist/Model/Repository/internal/internal.js +19 -21
  25. package/dist/Model/Repository/legacy.d.ts +2 -6
  26. package/dist/Model/Repository/legacy.d.ts.map +1 -1
  27. package/dist/Model/Repository/makeRepo.d.ts +4 -9
  28. package/dist/Model/Repository/makeRepo.d.ts.map +1 -1
  29. package/dist/Model/Repository/makeRepo.js +1 -1
  30. package/dist/Model/Repository/service.d.ts +1 -3
  31. package/dist/Model/Repository/service.d.ts.map +1 -1
  32. package/dist/QueueMaker/sbqueue.d.ts +2 -2
  33. package/dist/Store/Cosmos/query.d.ts +1 -1
  34. package/dist/Store/Cosmos/query.d.ts.map +1 -1
  35. package/dist/Store/Cosmos/query.js +7 -3
  36. package/dist/Store/Cosmos.d.ts.map +1 -1
  37. package/dist/Store/Cosmos.js +43 -23
  38. package/dist/Store/Disk.d.ts +2 -3
  39. package/dist/Store/Disk.d.ts.map +1 -1
  40. package/dist/Store/Disk.js +6 -6
  41. package/dist/Store/Memory.d.ts +4 -9
  42. package/dist/Store/Memory.d.ts.map +1 -1
  43. package/dist/Store/Memory.js +12 -12
  44. package/dist/Store/codeFilter.d.ts +2 -3
  45. package/dist/Store/codeFilter.d.ts.map +1 -1
  46. package/dist/Store/codeFilter.js +1 -1
  47. package/dist/Store/service.d.ts +9 -23
  48. package/dist/Store/service.d.ts.map +1 -1
  49. package/dist/Store/service.js +1 -1
  50. package/dist/Store/utils.d.ts +1 -3
  51. package/dist/Store/utils.d.ts.map +1 -1
  52. package/dist/Store/utils.js +4 -4
  53. package/package.json +1 -1
  54. package/src/Model/Repository/ext.ts +6 -5
  55. package/src/Model/Repository/internal/internal.ts +37 -36
  56. package/src/Model/Repository/legacy.ts +2 -2
  57. package/src/Model/Repository/makeRepo.ts +8 -9
  58. package/src/Model/Repository/service.ts +1 -1
  59. package/src/Store/Cosmos/query.ts +6 -1
  60. package/src/Store/Cosmos.ts +85 -48
  61. package/src/Store/Disk.ts +20 -8
  62. package/src/Store/Memory.ts +25 -16
  63. package/src/Store/codeFilter.ts +2 -1
  64. package/src/Store/service.ts +8 -7
  65. package/src/Store/utils.ts +4 -3
@@ -1,3 +1,4 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
1
2
  import type {} from "effect/Equal"
2
3
  import type {} from "effect/Hash"
3
4
  import type { NonEmptyReadonlyArray } from "effect-app"
@@ -26,9 +27,9 @@ export function makeRepoInternal<
26
27
  return <
27
28
  ItemType extends string,
28
29
  R,
29
- Encoded extends { id: string },
30
+ Encoded extends FieldValues,
30
31
  T,
31
- IdKey extends keyof T
32
+ IdKey extends keyof T & keyof Encoded
32
33
  >(
33
34
  name: ItemType,
34
35
  schema: S.Schema<T, Encoded, R>,
@@ -41,18 +42,18 @@ export function makeRepoInternal<
41
42
  e: Encoded,
42
43
  getEtag: (id: string) => string | undefined
43
44
  ): PM {
44
- return mapTo(e, getEtag(e.id))
45
+ return mapTo(e, getEtag(e[idKey]))
45
46
  }
46
47
 
47
48
  function mapReverse(
48
49
  { _etag, ...e }: PM,
49
50
  setEtag: (id: string, eTag: string | undefined) => void
50
51
  ): Encoded {
51
- setEtag(e.id, _etag)
52
+ setEtag((e as any)[idKey], _etag)
52
53
  return mapFrom(e as unknown as Encoded)
53
54
  }
54
55
 
55
- const mkStore = makeStore<Encoded>()(name, schema, mapTo)
56
+ const mkStore = makeStore<Encoded>()(name, schema, mapTo, idKey)
56
57
 
57
58
  function make<RInitial = never, E = never, RPublish = never, RCtx = never>(
58
59
  args: [Evt] extends [never] ? {
@@ -128,7 +129,7 @@ export function makeRepoInternal<
128
129
  : s.pipe(S.pick(idKey as any))
129
130
  })
130
131
  const encodeId = flow(S.encode(i), provideRctx)
131
- function findEId(id: Encoded["id"]) {
132
+ function findEId(id: Encoded[IdKey]) {
132
133
  return Effect.flatMap(
133
134
  store.find(id),
134
135
  (item) =>
@@ -138,13 +139,12 @@ export function makeRepoInternal<
138
139
  })
139
140
  )
140
141
  }
142
+ // TODO: select the particular field, instead of as struct
141
143
  function findE(id: T[IdKey]) {
142
144
  return pipe(
143
145
  encodeId({ [idKey]: id } as any),
144
146
  Effect.orDie,
145
- // we will have idKey because the transform is undone again by the encode schema mumbo jumbo above
146
- // TODO: make reliable. (Security: isin: PrimaryKey(ISIN), idKey: "isin", does end up with "id")
147
- Effect.map((_) => (_ as any)[idKey] ?? (_ as any).id),
147
+ Effect.map((_) => (_ as any)[idKey]),
148
148
  Effect.flatMap(findEId)
149
149
  )
150
150
  }
@@ -163,7 +163,7 @@ export function makeRepoInternal<
163
163
  const { get, set } = yield* cms
164
164
  const items = a.map((_) => mapToPersistenceModel(_, get))
165
165
  const ret = yield* store.batchSet(items)
166
- ret.forEach((_) => set(_.id, _._etag))
166
+ ret.forEach((_) => set(_[idKey], _._etag))
167
167
  })
168
168
  )
169
169
  .pipe(Effect.asVoid)
@@ -199,7 +199,7 @@ export function makeRepoInternal<
199
199
  // TODO: we should have a batchRemove on store so the adapter can actually batch...
200
200
  for (const e of items) {
201
201
  yield* store.remove(mapToPersistenceModel(e, get))
202
- set(e.id, undefined)
202
+ set(e[idKey], undefined)
203
203
  }
204
204
  yield* Effect
205
205
  .sync(() => toNonEmptyArray([...events]))
@@ -233,13 +233,13 @@ export function makeRepoInternal<
233
233
  {
234
234
  ...args,
235
235
  select: args.select
236
- ? dedupe([...args.select, "id", "_etag" as any])
236
+ ? dedupe([...args.select, idKey, "_etag" as any])
237
237
  : undefined
238
238
  } as typeof args
239
239
  )
240
240
  .pipe(
241
241
  Effect.tap((items) =>
242
- Effect.map(cms, ({ set }) => items.forEach((_) => set((_ as Encoded).id, (_ as PM)._etag)))
242
+ Effect.map(cms, ({ set }) => items.forEach((_) => set((_ as Encoded)[idKey], (_ as PM)._etag)))
243
243
  )
244
244
  )
245
245
 
@@ -372,46 +372,47 @@ const pluralize = (s: string) =>
372
372
  ? s.substring(0, s.length - 1) + "ies"
373
373
  : s + "s"
374
374
 
375
- export function makeStore<
376
- Encoded extends { id: string }
377
- >() {
375
+ export function makeStore<Encoded extends FieldValues>() {
378
376
  return <
379
377
  ItemType extends string,
380
378
  R,
381
- E extends { id: string },
382
- T
379
+ E,
380
+ T,
381
+ IdKey extends keyof Encoded
383
382
  >(
384
383
  name: ItemType,
385
384
  schema: S.Schema<T, E, R>,
386
- mapTo: (e: E, etag: string | undefined) => Encoded
385
+ mapTo: (e: E, etag: string | undefined) => Encoded,
386
+ idKey: IdKey
387
387
  ) => {
388
- function encodeToEncoded() {
389
- const getEtag = () => undefined
390
- return (t: T) =>
391
- S.encode(schema)(t).pipe(
392
- Effect.orDie,
393
- Effect.map((_) => mapToPersistenceModel(_, getEtag))
394
- )
395
- }
396
-
397
- function mapToPersistenceModel(
398
- e: E,
399
- getEtag: (id: string) => string | undefined
400
- ): Encoded {
401
- return mapTo(e, getEtag(e.id))
402
- }
403
-
404
388
  function makeStore<RInitial = never, EInitial = never>(
405
389
  makeInitial?: Effect<readonly T[], EInitial, RInitial>,
406
390
  config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
407
391
  partitionValue?: (a: Encoded) => string
408
392
  }
409
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
410
  return Effect.gen(function*() {
411
411
  const { make } = yield* StoreMaker
412
412
 
413
- const store = yield* make<Encoded, string, RInitial | R, EInitial>(
413
+ const store = yield* make<IdKey, Encoded, RInitial | R, EInitial>(
414
414
  pluralize(name),
415
+ idKey,
415
416
  makeInitial
416
417
  ? makeInitial
417
418
  .pipe(
@@ -14,13 +14,13 @@ export interface Mapped2<A, R> {
14
14
  all: Effect<A[], ParseResult.ParseError, R>
15
15
  }
16
16
 
17
- export interface Mapped<Encoded extends { id: string }> {
17
+ export interface Mapped<Encoded> {
18
18
  <A, R, IdKey extends keyof A>(schema: S.Schema<A, Encoded, R>): Mapped1<A, IdKey, R>
19
19
  // TODO: constrain on Encoded2 having to contain only fields that fit Encoded
20
20
  <A, Encoded2, R>(schema: S.Schema<A, Encoded2, R>): Mapped2<A, R>
21
21
  }
22
22
 
23
- export interface MM<Repo, Encoded extends { id: string }> {
23
+ export interface MM<Repo, Encoded> {
24
24
  <A, R, IdKey extends keyof A>(schema: S.Schema<A, Encoded, R>): Effect<Mapped1<A, IdKey, R>, never, Repo>
25
25
  // TODO: constrain on Encoded2 having to contain only fields that fit Encoded
26
26
  <A, Encoded2, R>(schema: S.Schema<A, Encoded2, R>): Effect<Mapped2<A, R>, never, Repo>
@@ -10,15 +10,14 @@ import type {} from "effect/Hash"
10
10
  import type { Context, NonEmptyReadonlyArray, S } from "effect-app"
11
11
  import { Effect } from "effect-app"
12
12
  import type { StoreConfig, StoreMaker } from "../../Store.js"
13
+ import type { FieldValues } from "../filter/types.js"
13
14
  import type { ExtendedRepository } from "./ext.js"
14
15
  import { extendRepo } from "./ext.js"
15
16
  import { makeRepoInternal } from "./internal/internal.js"
16
17
 
17
18
  export interface RepositoryOptions<
18
- IdKey extends keyof T,
19
- Encoded extends {
20
- id: string
21
- },
19
+ IdKey extends keyof T & keyof Encoded,
20
+ Encoded,
22
21
  T,
23
22
  Evt = never,
24
23
  RPublish = never,
@@ -66,9 +65,9 @@ export const makeRepo: {
66
65
  <
67
66
  ItemType extends string,
68
67
  RSchema,
69
- Encoded extends { id: string },
68
+ Encoded extends FieldValues,
70
69
  T,
71
- IdKey extends keyof T,
70
+ IdKey extends keyof T & keyof Encoded,
72
71
  E = never,
73
72
  Evt = never,
74
73
  RInitial = never,
@@ -86,7 +85,7 @@ export const makeRepo: {
86
85
  <
87
86
  ItemType extends string,
88
87
  RSchema,
89
- Encoded extends { id: string },
88
+ Encoded extends FieldValues,
90
89
  T extends { id: unknown },
91
90
  E = never,
92
91
  Evt = never,
@@ -105,9 +104,9 @@ export const makeRepo: {
105
104
  } = <
106
105
  ItemType extends string,
107
106
  R,
108
- Encoded extends { id: string },
107
+ Encoded extends FieldValues,
109
108
  T,
110
- IdKey extends keyof T,
109
+ IdKey extends keyof T & keyof Encoded,
111
110
  E = never,
112
111
  RInitial = never,
113
112
  RPublish = never,
@@ -11,7 +11,7 @@ import type { Mapped } from "./legacy.js"
11
11
  */
12
12
  export interface Repository<
13
13
  T,
14
- Encoded extends { id: string },
14
+ Encoded extends FieldValues,
15
15
  Evt,
16
16
  ItemType extends string,
17
17
  IdKey extends keyof T,
@@ -32,6 +32,7 @@ const arrayContains = (v: any[]) => v.map((_) => JSON.stringify(_)).join(", ")
32
32
  const vAsArr = (v: string) => v as unknown as any[]
33
33
 
34
34
  export function buildWhereCosmosQuery3(
35
+ idKey: PropertyKey,
35
36
  filter: readonly FilterResult[],
36
37
  name: string,
37
38
  importedMarkerId: string,
@@ -42,10 +43,14 @@ export function buildWhereCosmosQuery3(
42
43
  limit?: number
43
44
  ) {
44
45
  const statement = (x: FilterR, i: number) => {
46
+ if (x.path === idKey) {
47
+ x = { ...x, path: "id" }
48
+ }
45
49
  let k = x.path.includes(".-1.")
46
50
  ? `${x.path.split(".-1.")[0]}.${x.path.split(".-1.")[1]!}`
47
51
  : `f.${x.path}`
48
52
 
53
+ // would have to map id, but shouldnt allow id in defaultValues anyway..
49
54
  k = x.path in defaultValues ? `(${k} ?? ${JSON.stringify(defaultValues[x.path])})` : k
50
55
 
51
56
  const v = "@v" + i
@@ -180,7 +185,7 @@ export function buildWhereCosmosQuery3(
180
185
  query: `
181
186
  SELECT ${
182
187
  select
183
- ? `${select.map((_) => `f.${_}`).join(", ")}`
188
+ ? `${select.map((_) => (_ as any) === idKey ? "id" : _).map((_) => `f.${_}`).join(", ")}`
184
189
  : "f"
185
190
  }
186
191
  FROM ${name} f
@@ -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: <Id extends string, Encoded extends Record<string, any> & { id: Id }, R = never, E = never>(
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].map(
51
- (x) =>
52
- [
53
- x,
54
- Option.match(Option.fromNullable(x._etag), {
55
- onNone: () =>
56
- dropUndefinedT({
57
- operationType: "Create" as const,
58
- resourceBody: {
59
- ...Struct.omit(x, "_etag"),
60
- _partitionKey: config?.partitionValue(x)
61
- },
62
- partitionKey: config?.partitionValue(x)
63
- }),
64
- onSome: (eTag) =>
65
- dropUndefinedT({
66
- operationType: "Replace" as const,
67
- id: x.id,
68
- resourceBody: {
69
- ...Struct.omit(x, "_etag"),
70
- _partitionKey: config?.partitionValue(x)
71
- },
72
- ifMatch: eTag,
73
- partitionKey: config?.partitionValue(x)
74
- })
75
- })
76
- ] as const
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.id,
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<Encoded, Id> = {
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<PM>(q)
223
+ .query<PMCosmos>(q)
203
224
  .fetchAll()
204
- .then(({ resources }) => resources.map((_) => ({ ...defaultValues, ..._ })))
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((_) => ({ ...pipe(defaultValues, Struct.pick(...f.select!)), ..._ }))
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 }) => resources.map((_) => ({ ...defaultValues, ..._.f })))
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.id, config?.partitionValue(e)).replace(
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.id })
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: { "repository.container_id": containerId, "repository.model_name": name, id: e.id }
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.id, config?.partitionValue(e)).delete())
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.id }
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<Id extends string, Encoded extends { id: Id }, R, E>(
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<Id, Encoded, R, E>(
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<Encoded, Id>
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: <Id extends string, Encoded extends { id: Id }, R, E>(
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<Encoded, Id>>([["primary", primary]])
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<Id, Encoded, R, E>(prefix, namespace, dir, name, seed, config?.defaultValues)
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<Encoded, Id> = {
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)),