Skip to content

Commit

Permalink
feat: pass predefined zod object to ADT Builder
Browse files Browse the repository at this point in the history
We previously shipped support for Record Builder accepting Zod object
schemas. This brings that support to the ADT builder. The following
patterns are now psosible:

```ts
const Circle = z.object({ radius: z.number() })
const Square = z.object({ size: z.number() })

const Shape1 = Alge.data('Shape', { Circle, Square })

const Shape2 = Alge.data('shape')
  .record('Circle')
  .schema(CircleSchema)
  .record('Square')
  .schema(SquareSchema)
  .done()
```
  • Loading branch information
jasonkuhrt committed Aug 5, 2022
1 parent 14784f6 commit 91809c7
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 13 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,16 @@ const Shape = Alge.data('Shape', {
})
```

#### Referencing Zod Objects

Existing Zod object schemas are also accepted:

```ts
const Circle = z.object({ radius: z.number() })
const Square = z.object({ size: z.number() })
const Shape = Alge.data('Shape', { Circle, Square })
```

### Construction

The ADT Controller contains one Record Controller for every record defined under a property of that records name. Use it just like you did before:
Expand All @@ -564,6 +574,19 @@ As with records before there is a chaining API for ADTs that is more verbose but
const Shape = Alge.data('shape').record(Circle).record(Square).done()
```

As with the shorthand your existing Zod objects can be passed in:

```ts
const CircleSchema = z.object({ radius: z.number() })
const SquareSchema = z.object({ size: z.number() })
const Shape = Alge.data('shape')
.record('Circle')
.schema(CircleSchema)
.record('Square')
.schema(SquareSchema)
.done()
```

### Identity (`.is`, `.is$`)

Use the `.is` Record Controller method as a TypeScript type guard. It checks if the given value is that record or not:
Expand Down
16 changes: 11 additions & 5 deletions src/data/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import { record } from '../record/runtime.js'
import { SomeRecord, SomeRecordController } from '../record/types/controller.js'
import { SomeDecodeOrThrower, SomeDecoder, SomeEncoder, SomeRecordBuilder } from '../record/types/internal.js'
import { Initial } from './types/Builder.js'
import { DataController, SomeShortHandRecordDefs } from './types/Controller.js'
import {
DataController,
SomeShortHandRecordSchemaDefs,
SomeShortHandRecordSchemas,
} from './types/Controller.js'
import { SomeADT } from './types/internal.js'
import { inspect } from 'util'
import { SomeZodObject } from 'zod'
Expand All @@ -19,15 +23,17 @@ export type SomeAdtMethods = {
}

//prettier-ignore
export function data <Name extends string, ShortHandRecordDefs extends SomeShortHandRecordDefs>(name: Name, shortHandRecordDefs: ShortHandRecordDefs): DataController.createDataControllerFromShortHandRecords<Name, ShortHandRecordDefs>
export function data <Name extends string, ShortHandRecordSchemas extends SomeShortHandRecordSchemas>(name: Name, shortHandRecordSchemas: ShortHandRecordSchemas): DataController.createFromShortHandRecordSchemas<Name, ShortHandRecordSchemas>
//prettier-ignore
export function data <Name extends string, ShortHandRecordDefs extends SomeShortHandRecordSchemaDefs>(name: Name, shortHandRecordSchemaDefinitions: ShortHandRecordDefs): DataController.createFromShortHandRecordSchemaDefs<Name, ShortHandRecordDefs>
/**
* Define an algebraic data type. There must be at least two members. If all members have a parse function then an ADT level parse function will automatically be derived.
*/
// @ts-expect-error empty init tuple
//prettier-ignore
export function data <Name extends string>(name: Name): Initial<{ name: Name }, []>
//eslint-disable-next-line
export function data<Name extends string>(name: Name, shortHandRecordDefinitions?: any) {
export function data<Name extends string>(name: Name, shortHandRecordSchemaDefinitionsOrSchemas?: any) {
// let currentRecord: null | SomeRecordDefinition = null
// const records: SomeRecordDefinition[] = []
let currentRecordBuilder: null | SomeRecordBuilder = null
Expand Down Expand Up @@ -144,10 +150,10 @@ export function data<Name extends string>(name: Name, shortHandRecordDefinitions
},
}

if (shortHandRecordDefinitions) {
if (shortHandRecordSchemaDefinitionsOrSchemas) {
let b = builder
//eslint-disable-next-line
for (const [name, schemaDefinition] of Object.entries(shortHandRecordDefinitions)) {
for (const [name, schemaDefinition] of Object.entries(shortHandRecordSchemaDefinitionsOrSchemas)) {
// @ts-expect-error todo
//eslint-disable-next-line
b = b.record(name).schema(schemaDefinition)
Expand Down
5 changes: 3 additions & 2 deletions src/data/types/Builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* This module is concerned with the static types for the API of building up an ADT.
*/

import { SomeSchemaDef } from '../../core/internal.js'
import { SomeSchema, SomeSchemaDef } from '../../core/internal.js'
import { CodecImplementation, ExtensionsBase, SomeName, StoredRecords } from '../../core/types.js'
import { SomeRecordController } from '../../record/types/controller.js'
import { SomeStoredRecord, StoredRecord } from '../../record/types/StoredRecord.js'
Expand Down Expand Up @@ -33,7 +33,8 @@ export interface RecordRequired<ADT extends StoredADT, Rs extends StoredRecords>
export interface PostRecord<ADT extends StoredADT, R extends SomeStoredRecord, Vs extends StoredRecords>
extends RecordRequired<ADT, [R, ...Vs]>,
Done<ADT, R, Vs> {
schema<Schema extends SomeSchemaDef>(schema: Schema): PostSchema<ADT, StoredRecord.AddSchemaDef<Schema, R>, Vs>
schema<Schema extends SomeSchema>(schema: Schema): PostSchema<ADT, StoredRecord.AddSchema<Schema, R>, Vs>
schema<SchemaDef extends SomeSchemaDef>(schemaDefinition: SchemaDef): PostSchema<ADT, StoredRecord.AddSchemaDef<SchemaDef, R>, Vs>
}

/**
Expand Down
31 changes: 25 additions & 6 deletions src/data/types/Controller.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { SomeSchemaDef } from '../../core/internal.js'
import { SomeSchema, SomeSchemaDef } from '../../core/internal.js'
import { SomeName, StoredRecords } from '../../core/types.js'
import { ObjectValues, OnlyStrings } from '../../lib/utils.js'
import { RecordController } from '../../record/types/controller.js'
import { StoredRecord } from '../../record/types/StoredRecord.js'
import { StoredADT } from './Builder.js'

export type SomeShortHandRecordDefs = Record<string, SomeSchemaDef>
export type SomeShortHandRecordSchemaDefs = Record<string, SomeSchemaDef>

export type SomeShortHandRecordSchemas = Record<string, SomeSchema>

// eslint-disable-next-line
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void
Expand All @@ -24,14 +26,31 @@ type TuplifyUnion<T, L = LastOf<T>, N = [T] extends [never] ? true : false> = tr
: Push<TuplifyUnion<Exclude<T, L>>, L>

export namespace DataController {
export type createDataControllerFromShortHandRecords<
export type createFromShortHandRecordSchemas<
Name extends SomeName,
shortHandRecordDefs extends SomeShortHandRecordSchemas
> = DataController<StoredADT.Create<Name>, StoredRecordsFromShortHandRecordSchemas<shortHandRecordDefs>>

type StoredRecordsFromShortHandRecordSchemas<shortHandRecordDefs extends SomeShortHandRecordSchemas> =
OnlyStoredRecords<
TuplifyUnion<
ObjectValues<{
[Name in OnlyStrings<keyof shortHandRecordDefs>]: StoredRecord.AddSchema<
shortHandRecordDefs[Name],
StoredRecord.Create<Name>
>
}>
>
>

export type createFromShortHandRecordSchemaDefs<
Name extends SomeName,
shortHandRecordDefs extends SomeShortHandRecordDefs
> = DataController<StoredADT.Create<Name>, StoredRecordsFromShortHandRecordDefs<shortHandRecordDefs>>
shortHandRecordDefs extends SomeShortHandRecordSchemaDefs
> = DataController<StoredADT.Create<Name>, StoredRecordsFromShortHandRecordSchemaDefs<shortHandRecordDefs>>

// type x = StoredRecordsFromShortHandRecordDefs<{ a: { n: z.ZodString }; b: { m: z.ZodString } }>

type StoredRecordsFromShortHandRecordDefs<shortHandRecordDefs extends SomeShortHandRecordDefs> =
type StoredRecordsFromShortHandRecordSchemaDefs<shortHandRecordDefs extends SomeShortHandRecordSchemaDefs> =
OnlyStoredRecords<
TuplifyUnion<
ObjectValues<{
Expand Down
1 change: 1 addition & 0 deletions tests/data/builder/data.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ describe(`.data(<record>)`, () => {
expectType<typeof $AB>(AB.name)
expect(AB.name).toBe($AB)
})

it(`can construct records`, () => {
expectType<{ _tag: $A; m: `m` }>(m)
expectType<{ _tag: $B; n: 1 }>(n)
Expand Down
36 changes: 36 additions & 0 deletions tests/data/builder/schema.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Alge } from '../../../src/index.js'
import { $AB } from '../../__helpers__.js'
import { expectType } from 'tsd'
import { z } from 'zod'

it(`zod object schemas can be passed into shorthand builder`, () => {
const A = z.object({ m: z.string() })
const B = z.object({ n: z.number() })
const AB = Alge.data($AB, { A, B })

const AB2 = Alge.data($AB)
.record(`B`)
.schema({ n: z.number() })
.record(`A`)
.schema({ m: z.string() })
.done()

expectType<typeof AB2>(AB)
expect(AB.A.create({ m: `` })).toMatchObject({ _tag: `A`, m: `` })
})

it(`zod object schemas can be passed into chain builder`, () => {
const A = z.object({ m: z.string() })
const B = z.object({ n: z.number() })
const AB = Alge.data($AB).record(`B`).schema(B).record(`A`).schema(A).done()

const AB2 = Alge.data($AB)
.record(`B`)
.schema({ n: z.number() })
.record(`A`)
.schema({ m: z.string() })
.done()

expectType<typeof AB2>(AB)
expect(AB.A.create({ m: `` })).toMatchObject({ _tag: `A`, m: `` })
})

0 comments on commit 91809c7

Please sign in to comment.