@effect-app/infra 2.11.0 → 2.12.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.
Files changed (69) hide show
  1. package/CHANGELOG.md +12 -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 +44 -22
  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 +3 -6
  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 +5 -10
  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/Operations.d.ts +3 -3
  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 +41 -22
  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 +8 -22
  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 +83 -47
  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
  66. package/vitest.config.ts.timestamp-1711656440838-19c636fe320df.mjs +0 -0
  67. package/vitest.config.ts.timestamp-1711724061890-6ecedb0a07fdd.mjs +0 -0
  68. package/vitest.config.ts.timestamp-1711743489537-da8d9e5f66c9f.mjs +0 -0
  69. package/vitest.config.ts.timestamp-1711744615239-dcf257a844e01.mjs +0 -37
@@ -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
@@ -187,7 +207,7 @@ function makeCosmosStore({ prefix }: StorageConfig) {
187
207
  }))
188
208
  }
189
209
 
190
- const s: Store<Encoded, Id> = {
210
+ const s: Store<IdKey, Encoded> = {
191
211
  all: Effect
192
212
  .sync(() => ({
193
213
  query: `SELECT * FROM ${name} f WHERE f.id != @id`,
@@ -199,9 +219,13 @@ function makeCosmosStore({ prefix }: StorageConfig) {
199
219
  Effect.promise(() =>
200
220
  container
201
221
  .items
202
- .query<PM>(q)
222
+ .query<PMCosmos>(q)
203
223
  .fetchAll()
204
- .then(({ resources }) => resources.map((_) => ({ ...defaultValues, ..._ })))
224
+ .then(({ resources }) =>
225
+ resources.map(
226
+ (_) => ({ ...defaultValues, ...mapReverseId(_) })
227
+ )
228
+ )
205
229
  )
206
230
  ),
207
231
  Effect
@@ -223,6 +247,7 @@ function makeCosmosStore({ prefix }: StorageConfig) {
223
247
  return Effect
224
248
  .sync(() =>
225
249
  buildWhereCosmosQuery3(
250
+ idKey,
226
251
  filter ?? [],
227
252
  name,
228
253
  importedMarkerId,
@@ -244,13 +269,20 @@ function makeCosmosStore({ prefix }: StorageConfig) {
244
269
  .query<M>(q)
245
270
  .fetchAll()
246
271
  .then(({ resources }) =>
247
- resources.map((_) => ({ ...pipe(defaultValues, Struct.pick(...f.select!)), ..._ }))
272
+ resources.map((_) =>
273
+ ({
274
+ ...pipe(defaultValues, Struct.pick(...f.select!)),
275
+ ...mapReverseId(_ as any)
276
+ }) as any
277
+ )
248
278
  )
249
279
  : container
250
280
  .items
251
281
  .query<{ f: M }>(q)
252
282
  .fetchAll()
253
- .then(({ resources }) => resources.map((_) => ({ ...defaultValues, ..._.f })))
283
+ .then(({ resources }) =>
284
+ resources.map(({ f }) => ({ ...defaultValues, ...mapReverseId(f as any) }) as any)
285
+ )
254
286
  )
255
287
  )
256
288
  )
@@ -263,10 +295,10 @@ function makeCosmosStore({ prefix }: StorageConfig) {
263
295
  Effect
264
296
  .promise(() =>
265
297
  container
266
- .item(id, config?.partitionValue({ id } as Encoded))
298
+ .item(id, config?.partitionValue({ [idKey]: id } as Encoded))
267
299
  .read<Encoded>()
268
300
  .then(({ resource }) =>
269
- Option.fromNullable(resource).pipe(Option.map((_) => ({ ...defaultValues, ..._ })))
301
+ Option.fromNullable(resource).pipe(Option.map((_) => ({ ...defaultValues, ...mapReverseId(_) })))
270
302
  )
271
303
  )
272
304
  .pipe(Effect
@@ -275,7 +307,7 @@ function makeCosmosStore({ prefix }: StorageConfig) {
275
307
  attributes: {
276
308
  "repository.container_id": containerId,
277
309
  "repository.model_name": name,
278
- partitionValue: config?.partitionValue({ id } as Encoded),
310
+ partitionValue: config?.partitionValue({ [idKey]: id } as Encoded),
279
311
  id
280
312
  }
281
313
  })),
@@ -288,14 +320,14 @@ function makeCosmosStore({ prefix }: StorageConfig) {
288
320
  onNone: () =>
289
321
  Effect.promise(() =>
290
322
  container.items.create({
291
- ...e,
323
+ ...mapId(e),
292
324
  _partitionKey: config?.partitionValue(e)
293
325
  })
294
326
  ),
295
327
  onSome: (eTag) =>
296
328
  Effect.promise(() =>
297
- container.item(e.id, config?.partitionValue(e)).replace(
298
- { ...e, _partitionKey: config?.partitionValue(e) },
329
+ container.item(e[idKey], config?.partitionValue(e)).replace(
330
+ { ...mapId(e), _partitionKey: config?.partitionValue(e) },
299
331
  {
300
332
  accessCondition: {
301
333
  type: "IfMatch",
@@ -310,7 +342,7 @@ function makeCosmosStore({ prefix }: StorageConfig) {
310
342
  Effect
311
343
  .flatMap((x) => {
312
344
  if (x.statusCode === 412 || x.statusCode === 404) {
313
- return new OptimisticConcurrencyException({ type: name, id: e.id })
345
+ return new OptimisticConcurrencyException({ type: name, id: e[idKey] })
314
346
  }
315
347
  if (x.statusCode > 299 || x.statusCode < 200) {
316
348
  return Effect.die(
@@ -327,18 +359,22 @@ function makeCosmosStore({ prefix }: StorageConfig) {
327
359
  Effect
328
360
  .withSpan("Cosmos.set [effect-app/infra/Store]", {
329
361
  captureStackTrace: false,
330
- attributes: { "repository.container_id": containerId, "repository.model_name": name, id: e.id }
362
+ attributes: {
363
+ "repository.container_id": containerId,
364
+ "repository.model_name": name,
365
+ id: e[idKey]
366
+ }
331
367
  })
332
368
  ),
333
369
  batchSet,
334
370
  bulkSet,
335
371
  remove: (e: Encoded) =>
336
372
  Effect
337
- .promise(() => container.item(e.id, config?.partitionValue(e)).delete())
373
+ .promise(() => container.item(e[idKey], config?.partitionValue(e)).delete())
338
374
  .pipe(Effect
339
375
  .withSpan("Cosmos.remove [effect-app/infra/Store]", {
340
376
  captureStackTrace: false,
341
- attributes: { "repository.container_id": containerId, "repository.model_name": name, id: e.id }
377
+ attributes: { "repository.container_id": containerId, "repository.model_name": name, id: e[idKey] }
342
378
  }))
343
379
  }
344
380
 
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)),