Skip to content

Commit

Permalink
mixed pure/fx object chain test
Browse files Browse the repository at this point in the history
  • Loading branch information
mccraigmccraig committed Nov 15, 2023
1 parent b9d296c commit 7dfa83b
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 0 deletions.
52 changes: 52 additions & 0 deletions src/object_chain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Effect } from "effect"
import { Tagged, Tag } from "./tagged.ts"
import { ChainObjectSteps } from "./object_builders.ts"
import { UPObjectStepSpec, ObjectStepsDepsU, ObjectStepsErrorsU, ChainObjectStepsReturn, chainObjectStepsProg } from "./object_builders.ts"

// an ObjectChain defines a series of steps to build an Object
export type ObjectChain<Obj extends Tagged,
Steps extends [...UPObjectStepSpec[]]> = {
readonly tag: Tag<Obj>
readonly tagStr: Obj['tag']
readonly steps: ChainObjectSteps<Steps, Obj> extends readonly [...Steps] ? readonly [...Steps] : ChainObjectSteps<Steps, Obj>
readonly program: (obj: Obj) => Effect.Effect<ObjectStepsDepsU<Steps>,
ObjectStepsErrorsU<Steps>,
ChainObjectStepsReturn<Steps, Obj>>
}

// an unparameterised version of ObjectChain for typing tuples
export type UPObjectChain = {
readonly tagStr: string
readonly steps: [...UPObjectStepSpec[]]
// deno-lint-ignore no-explicit-any
readonly program: (obj: any) => Effect.Effect<any, any, any>
}

// build an ObjectChain from Steps
export function objectChain<Obj extends Tagged>() {
return function <Steps extends [...UPObjectStepSpec[]]>
(tag: Tag<Obj>,
steps: ChainObjectSteps<Steps, Obj> extends readonly [...Steps] ? readonly [...Steps] : ChainObjectSteps<Steps, Obj>) {

return {
tag: tag,
tagStr: tag.tag,
steps: steps,
program: chainObjectStepsProg<Obj>()(steps)
} as ObjectChain<Obj, Steps>
}
}

export function addSteps<Obj extends Tagged,
Steps extends [...UPObjectStepSpec[]],
AdditionalSteps extends [...UPObjectStepSpec[]]>
(chain: ObjectChain<Obj, Steps>,
additionalSteps: ChainObjectSteps<AdditionalSteps, ChainObjectStepsReturn<Steps, Obj>> extends readonly [...AdditionalSteps[]]
? readonly [...AdditionalSteps[]]
: ChainObjectSteps<AdditionalSteps, ChainObjectStepsReturn<Steps, Obj>>) {

const newSteps = [...chain.steps, ...additionalSteps]

// deno-lint-ignore no-explicit-any
return objectChain<Obj>()(chain.tag, newSteps as any) as ObjectChain<Obj, [...Steps, ...AdditionalSteps]>
}
81 changes: 81 additions & 0 deletions src/object_chain_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { assertEquals } from "assert"
import { Effect, Context } from "effect"
import { tag } from "./tagged.ts"
import { objectChain } from "./object_chain.ts"
import { Org, OrgService, getOrgByNick, User, UserService, getUserByIds, PushNotificationService, sendPush } from "./test_services.ts"

const getOrgObjectStepSpec /* : ObjectStepSpec<"org", { data: { org_nick: string } }, string, OrgService, never, Org> */ =
{
k: "org" as const,
inFn: (d: { data: { org_nick: string } }) => d.data.org_nick,
fxFn: getOrgByNick
}
const getUserObjectStepSpec /* : ObjectStepSpec<"user", { data: { user_id: string }, org: Org }, {org_id: string, user_id: string}, UserService, never, User> */ =
{
k: "user" as const,
// note that this fn depends on the output of an OrgServiceI.getBy* step
inFn: (d: { data: { user_id: string }, org: Org }) => { return { org_id: d.org.id, user_id: d.data.user_id } },
fxFn: getUserByIds
}

const pureFormatPushNotificationStepSpec = {
k: "formatPushNotification" as const,
pureFn: (d: { org: Org, user: User }) => {
return "Welcome " + d.user.name + " of " + d.org.name
}
}

const sendPusnNotificationStepSpec =
{
k: "sendPush" as const,
inFn: (d: { user: User, formatPushNotification: string }) => {
return {
user_id: d.user.id,
message: d.formatPushNotification
}
},
fxFn: sendPush
}


// a simple context with an OrgService and a UserService which echo data back
const echoContext = Context.empty().pipe(
Context.add(OrgService, OrgService.of({
getById: (id: string) => Effect.succeed({ id: id, name: "Foo" }),
getByNick: (nick: string) => Effect.succeed({ id: nick, name: "Foo" })
})),
Context.add(UserService, UserService.of({
getByIds: (d: { org_id: string, user_id: string }) => Effect.succeed({ id: d.user_id, name: "Bar" })
})),
Context.add(PushNotificationService, PushNotificationService.of({
sendPush: (d: { user_id: string, message: string }) => Effect.succeed("push sent OK: " + d.message)
})))

Deno.test("objectChain", () => {
type Input = { tag: "sendPushNotification", data: { org_nick: string, user_id: string } }
const InputTag = tag<Input>("sendPushNotification")

const steps = [getOrgObjectStepSpec,
getUserObjectStepSpec,
pureFormatPushNotificationStepSpec,
sendPusnNotificationStepSpec] as const

const prog = objectChain<Input>()(InputTag, steps)

const input: Input = {
tag: "sendPushNotification",
data: {org_nick: "foo", user_id: "bar"}
}
const effect = prog.program(input)
const runnable = Effect.provide(effect, echoContext)
const r = Effect.runSync(runnable)

assertEquals(r, {
...input,
org: { id: "foo", name: "Foo" },
user: {id: "bar", name: "Bar" },
formatPushNotification: "Welcome Bar of Foo",
sendPush: "push sent OK: Welcome Bar of Foo"
})

})

0 comments on commit 7dfa83b

Please sign in to comment.