Skip to content

Commit

Permalink
feat: add controller update method
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonkuhrt committed Aug 30, 2022
1 parent 257a946 commit a05d0b0
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 7 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ This is just a taster. Places you can go next:
- [Input Defaults](#input-defaults)
- [Input Transformation](#input-transformation)
- [Input Validation](#input-validation)
- [Update](#update)
- [Metadata](#metadata)
- [Chaining API](#chaining-api)
- [Codecs](#codecs)
Expand Down Expand Up @@ -456,6 +457,14 @@ const circle = circle.create({ radius: -10 })
// throws
```

### Update

You can update records. Updating creates shallow copies of data. The validation, transformations, defaults etc. setup on the zod schema will re-run on the update function ensuring data integrity. Any errors there will be thrown.

```ts
const circleUpdated = circle.update(circle, { radius: 5 })
```

### Metadata

The controller gives you access to metadata about your record:
Expand Down
10 changes: 8 additions & 2 deletions src/record/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,14 @@ export function record<Name extends string>(
return current.schema.passthrough().parse(data) as object
},
//eslint-disable-next-line
is$: (value: unknown) => is(value, symbol),
is: (value: unknown) => is(value, symbol),
is$: (value) => is(value, symbol),
is: (record) => is(record, symbol),
update: (record, changes) => {
return controller.create({
...record,
...changes,
}) as object
},
from: {
json: (json: string) => {
const data = tryOrNull(() => JSON.parse(json) as object)
Expand Down
19 changes: 14 additions & 5 deletions src/record/types/controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SomeSchema, SomeSchemaDef } from '../../core/internal.js'
import { Encoder, SomeName, StoredRecords } from '../../core/types.js'
import { Encoder, OmitTag, SomeName, StoredRecords } from '../../core/types.js'
import { OmitRequired, Rest } from '../../lib/utils.js'
import { z } from '../../lib/z/index.js'
import { SomeDecodeOrThrowJson, SomeDecoderJson, SomeDefaultsProvider, SomeEncoderJson } from './internal.js'
Expand All @@ -24,9 +24,13 @@ export type SomeRecordController = {
}
name: string
schema: SomeSchema
// !! HACK not-using any here results in test type errors that I don't understand yet.
// eslint-disable-next-line
is: (value: any) => boolean
is: (record: any) => boolean
is$: (value: unknown) => boolean
// !! HACK not-using any here results in test type errors that I don't understand yet.
// eslint-disable-next-line
update: (record: any, changes: object) => object
// eslint-disable-next-line
create: (params?: any) => any
from: {
Expand Down Expand Up @@ -92,6 +96,11 @@ export type RecordController<Rs extends StoredRecords, R extends SomeStoredRecor
}
name: R[`name`]
schema: R['schema']
/**
*
* @throws If zod schema violated: bad types, failed validation, throw from a transformer.
*/
update(record: StoredRecord.GetType<R>, changes: Partial<OmitTag<StoredRecord.GetType<R>>>): StoredRecord.GetType<R>
/**
* Decoders for this record. Decoders are used to transform other representations of your record back into a record instance.
*/
Expand All @@ -101,13 +110,13 @@ export type RecordController<Rs extends StoredRecords, R extends SomeStoredRecor
*
* @remarks This is a built in decoder.
*/
json: (value: string) => null | StoredRecord.GetType<R>
json(value: string): null | StoredRecord.GetType<R>
/**
* Decode JSON into this record. Throws if it fails for any reason.
*
* @remarks This is a built in decoder.
*/
jsonOrThrow: (value: string) => StoredRecord.GetType<R>
jsonOrThrow(value: string): StoredRecord.GetType<R>
} & Decoders<R['codec'], R>
// & {
// [I in IndexKeys<V['codec']> as AsString<V['codec'][I]>]: Decoder<V['codec']>,V>
Expand All @@ -121,7 +130,7 @@ export type RecordController<Rs extends StoredRecords, R extends SomeStoredRecor
*
* @remarks This is a built in encoder.
*/
json: (record: StoredRecord.GetType<R>) => string
json(record: StoredRecord.GetType<R>): string
} & Encoders<R['codec'], R>
/**
* Strict predicate/type guard for this record.
Expand Down
32 changes: 32 additions & 0 deletions tests/record/other/update.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Alge } from '../../../src/index.js'
import { A, a } from '../../__helpers__.js'
import { expectType } from 'tsd'
import { z } from 'zod'

it(`updates record by copy`, () => {
// eslint-disable-next-line
expectType<(data: A, changes: Partial<A>) => A>(A.update)
const a_ = A.update(a, { m: `updated` })
expect(a).toMatchObject({ m: `m` })
expect(a_).not.toBe(a)
expect(a_).toEqual({ _tag: `A`, m: `updated`, _: { tag: `A`, symbol: A._.symbol } })
})

it(`updates triggers validate etc.`, () => {
const A = Alge.record(`A`, {
m: z.string().regex(/abc/),
})
const a = A.create({ m: `abc` })
expect(() => A.update(a, { m: `updated` })).toThrowErrorMatchingInlineSnapshot(`
"[
{
"validation": "regex",
"code": "invalid_string",
"message": "Invalid",
"path": [
"m"
]
}
]"
`)
})

0 comments on commit a05d0b0

Please sign in to comment.