Skip to content

Commit

Permalink
R/idkey-in-driver (#93)
Browse files Browse the repository at this point in the history
* prepare

* wrap up

* progress

* nice

* fix typo

* cleanup

* fix cosmos query replace idKey

* doc

* add changeset
  • Loading branch information
patroza authored Oct 26, 2024
1 parent 5c6fcd7 commit 23c8ca5
Show file tree
Hide file tree
Showing 13 changed files with 209 additions and 137 deletions.
5 changes: 5 additions & 0 deletions .changeset/kind-chefs-try.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect-app/infra": minor
---

pushed idKey mapping down to the driver instead of the repo
11 changes: 6 additions & 5 deletions packages/infra/src/Model/Repository/ext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@ import { NotFoundError } from "effect-app/client"
import type { FixEnv, PureEnv } from "effect-app/Pure"
import { runTerm } from "effect-app/Pure"
import { AnyPureDSL } from "../dsl.js"
import type { FieldValues } from "../filter/types.js"
import type { Query, QueryEnd, QueryWhere } from "../query.js"
import * as Q from "../query.js"
import type { Repository } from "./service.js"

export const extendRepo = <
T,
Encoded extends { id: string },
Encoded extends FieldValues,
Evt,
ItemType extends string,
IdKey extends keyof T,
IdKey extends keyof T & keyof Encoded,
RSchema,
RPublish
>(
Expand Down Expand Up @@ -212,7 +213,7 @@ export const extendRepo = <
.makeBatched((
requests: NonEmptyReadonlyArray<Req>
) =>
(repo.query(Q.where("id", "in", requests.map((_) => _.id)) as any) as Effect<readonly T[], never>)
(repo.query(Q.where(repo.idKey as any, "in", requests.map((_) => _.id)) as any) as Effect<readonly T[], never>)
// TODO
.pipe(
Effect.andThen((items) =>
Expand Down Expand Up @@ -272,10 +273,10 @@ export const extendRepo = <
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
export interface ExtendedRepository<
T,
Encoded extends { id: string },
Encoded extends FieldValues,
Evt,
ItemType extends string,
IdKey extends keyof T,
IdKey extends keyof T & keyof Encoded,
RSchema,
RPublish
> extends ReturnType<typeof extendRepo<T, Encoded, Evt, ItemType, IdKey, RSchema, RPublish>> {}
73 changes: 37 additions & 36 deletions packages/infra/src/Model/Repository/internal/internal.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type {} from "effect/Equal"
import type {} from "effect/Hash"
import type { NonEmptyReadonlyArray } from "effect-app"
Expand Down Expand Up @@ -26,9 +27,9 @@ export function makeRepoInternal<
return <
ItemType extends string,
R,
Encoded extends { id: string },
Encoded extends FieldValues,
T,
IdKey extends keyof T
IdKey extends keyof T & keyof Encoded
>(
name: ItemType,
schema: S.Schema<T, Encoded, R>,
Expand All @@ -41,18 +42,18 @@ export function makeRepoInternal<
e: Encoded,
getEtag: (id: string) => string | undefined
): PM {
return mapTo(e, getEtag(e.id))
return mapTo(e, getEtag(e[idKey]))
}

function mapReverse(
{ _etag, ...e }: PM,
setEtag: (id: string, eTag: string | undefined) => void
): Encoded {
setEtag(e.id, _etag)
setEtag((e as any)[idKey], _etag)
return mapFrom(e as unknown as Encoded)
}

const mkStore = makeStore<Encoded>()(name, schema, mapTo)
const mkStore = makeStore<Encoded>()(name, schema, mapTo, idKey)

function make<RInitial = never, E = never, RPublish = never, RCtx = never>(
args: [Evt] extends [never] ? {
Expand Down Expand Up @@ -128,7 +129,7 @@ export function makeRepoInternal<
: s.pipe(S.pick(idKey as any))
})
const encodeId = flow(S.encode(i), provideRctx)
function findEId(id: Encoded["id"]) {
function findEId(id: Encoded[IdKey]) {
return Effect.flatMap(
store.find(id),
(item) =>
Expand All @@ -138,13 +139,12 @@ export function makeRepoInternal<
})
)
}
// TODO: select the particular field, instead of as struct
function findE(id: T[IdKey]) {
return pipe(
encodeId({ [idKey]: id } as any),
Effect.orDie,
// we will have idKey because the transform is undone again by the encode schema mumbo jumbo above
// TODO: make reliable. (Security: isin: PrimaryKey(ISIN), idKey: "isin", does end up with "id")
Effect.map((_) => (_ as any)[idKey] ?? (_ as any).id),
Effect.map((_) => (_ as any)[idKey]),
Effect.flatMap(findEId)
)
}
Expand All @@ -163,7 +163,7 @@ export function makeRepoInternal<
const { get, set } = yield* cms
const items = a.map((_) => mapToPersistenceModel(_, get))
const ret = yield* store.batchSet(items)
ret.forEach((_) => set(_.id, _._etag))
ret.forEach((_) => set(_[idKey], _._etag))
})
)
.pipe(Effect.asVoid)
Expand Down Expand Up @@ -199,7 +199,7 @@ export function makeRepoInternal<
// TODO: we should have a batchRemove on store so the adapter can actually batch...
for (const e of items) {
yield* store.remove(mapToPersistenceModel(e, get))
set(e.id, undefined)
set(e[idKey], undefined)
}
yield* Effect
.sync(() => toNonEmptyArray([...events]))
Expand Down Expand Up @@ -233,13 +233,13 @@ export function makeRepoInternal<
{
...args,
select: args.select
? dedupe([...args.select, "id", "_etag" as any])
? dedupe([...args.select, idKey, "_etag" as any])
: undefined
} as typeof args
)
.pipe(
Effect.tap((items) =>
Effect.map(cms, ({ set }) => items.forEach((_) => set((_ as Encoded).id, (_ as PM)._etag)))
Effect.map(cms, ({ set }) => items.forEach((_) => set((_ as Encoded)[idKey], (_ as PM)._etag)))
)
)

Expand Down Expand Up @@ -372,46 +372,47 @@ const pluralize = (s: string) =>
? s.substring(0, s.length - 1) + "ies"
: s + "s"

export function makeStore<
Encoded extends { id: string }
>() {
export function makeStore<Encoded extends FieldValues>() {
return <
ItemType extends string,
R,
E extends { id: string },
T
E,
T,
IdKey extends keyof Encoded
>(
name: ItemType,
schema: S.Schema<T, E, R>,
mapTo: (e: E, etag: string | undefined) => Encoded
mapTo: (e: E, etag: string | undefined) => Encoded,
idKey: IdKey
) => {
function encodeToEncoded() {
const getEtag = () => undefined
return (t: T) =>
S.encode(schema)(t).pipe(
Effect.orDie,
Effect.map((_) => mapToPersistenceModel(_, getEtag))
)
}

function mapToPersistenceModel(
e: E,
getEtag: (id: string) => string | undefined
): Encoded {
return mapTo(e, getEtag(e.id))
}

function makeStore<RInitial = never, EInitial = never>(
makeInitial?: Effect<readonly T[], EInitial, RInitial>,
config?: Omit<StoreConfig<Encoded>, "partitionValue"> & {
partitionValue?: (a: Encoded) => string
}
) {
function encodeToEncoded() {
const getEtag = () => undefined
return (t: T) =>
S.encode(schema)(t).pipe(
Effect.orDie,
Effect.map((_) => mapToPersistenceModel(_, getEtag))
)
}

function mapToPersistenceModel(
e: E,
getEtag: (id: string) => string | undefined
): Encoded {
return mapTo(e, getEtag((e as any)[idKey] as string))
}

return Effect.gen(function*() {
const { make } = yield* StoreMaker

const store = yield* make<Encoded, string, RInitial | R, EInitial>(
const store = yield* make<IdKey, Encoded, RInitial | R, EInitial>(
pluralize(name),
idKey,
makeInitial
? makeInitial
.pipe(
Expand Down
4 changes: 2 additions & 2 deletions packages/infra/src/Model/Repository/legacy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ export interface Mapped2<A, R> {
all: Effect<A[], ParseResult.ParseError, R>
}

export interface Mapped<Encoded extends { id: string }> {
export interface Mapped<Encoded> {
<A, R, IdKey extends keyof A>(schema: S.Schema<A, Encoded, R>): Mapped1<A, IdKey, R>
// TODO: constrain on Encoded2 having to contain only fields that fit Encoded
<A, Encoded2, R>(schema: S.Schema<A, Encoded2, R>): Mapped2<A, R>
}

export interface MM<Repo, Encoded extends { id: string }> {
export interface MM<Repo, Encoded> {
<A, R, IdKey extends keyof A>(schema: S.Schema<A, Encoded, R>): Effect<Mapped1<A, IdKey, R>, never, Repo>
// TODO: constrain on Encoded2 having to contain only fields that fit Encoded
<A, Encoded2, R>(schema: S.Schema<A, Encoded2, R>): Effect<Mapped2<A, R>, never, Repo>
Expand Down
17 changes: 8 additions & 9 deletions packages/infra/src/Model/Repository/makeRepo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,14 @@ import type {} from "effect/Hash"
import type { Context, NonEmptyReadonlyArray, S } from "effect-app"
import { Effect } from "effect-app"
import type { StoreConfig, StoreMaker } from "../../Store.js"
import type { FieldValues } from "../filter/types.js"
import type { ExtendedRepository } from "./ext.js"
import { extendRepo } from "./ext.js"
import { makeRepoInternal } from "./internal/internal.js"

export interface RepositoryOptions<
IdKey extends keyof T,
Encoded extends {
id: string
},
IdKey extends keyof T & keyof Encoded,
Encoded,
T,
Evt = never,
RPublish = never,
Expand Down Expand Up @@ -66,9 +65,9 @@ export const makeRepo: {
<
ItemType extends string,
RSchema,
Encoded extends { id: string },
Encoded extends FieldValues,
T,
IdKey extends keyof T,
IdKey extends keyof T & keyof Encoded,
E = never,
Evt = never,
RInitial = never,
Expand All @@ -86,7 +85,7 @@ export const makeRepo: {
<
ItemType extends string,
RSchema,
Encoded extends { id: string },
Encoded extends FieldValues,
T extends { id: unknown },
E = never,
Evt = never,
Expand All @@ -105,9 +104,9 @@ export const makeRepo: {
} = <
ItemType extends string,
R,
Encoded extends { id: string },
Encoded extends FieldValues,
T,
IdKey extends keyof T,
IdKey extends keyof T & keyof Encoded,
E = never,
RInitial = never,
RPublish = never,
Expand Down
2 changes: 1 addition & 1 deletion packages/infra/src/Model/Repository/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { Mapped } from "./legacy.js"
*/
export interface Repository<
T,
Encoded extends { id: string },
Encoded extends FieldValues,
Evt,
ItemType extends string,
IdKey extends keyof T,
Expand Down
Loading

0 comments on commit 23c8ca5

Please sign in to comment.