Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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