Skip to content

Commit

Permalink
Proparly type two-level deep updates
Browse files Browse the repository at this point in the history
  • Loading branch information
kossnocorp committed Jun 22, 2021
1 parent e7b8486 commit 0c18cc4
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 19 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ test-system-browser-watch:

build:
@rm -rf lib
@${BIN}/tsc
@${BIN}/tsc --project tsconfig.lib.json
@${BIN}/prettier "lib/**/*.[jt]s" --write --loglevel silent
@cp package.json lib
@cp *.md lib
@rsync --archive --prune-empty-dirs --exclude '*.ts' --relative src/./ lib
@${BIN}/tsc --outDir lib/esm --module es2020 --target es2019
@${BIN}/tsc --project tsconfig.lib.json --outDir lib/esm --module es2020 --target es2019
@cp src/adaptor/package.esm.json lib/esm/adaptor/package.json

publish: build
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "typesaurus",
"version": "7.2.0",
"version": "8.0.0-alpha.1",
"description": "Type-safe ODM for Firestore",
"keywords": [
"Firebase",
Expand Down
24 changes: 22 additions & 2 deletions src/field/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
import { UpdateValue } from '../value'

type Whole<Type, Key extends keyof Type> = Type[Key] extends
| infer Value
| undefined
? Value
: never

type PartialValue<
Model,
Key1 extends keyof Model,
Key2 extends keyof Whole<Model, Key1>
> = Partial<Pick<Model, Key1>> extends Pick<Model, Key1>
? Partial<Model[Key1]> extends Model[Key1]
? Whole<Model, Key1>[Key2]
: never
: Whole<Model, Key1>[Key2]

/**
* The field type. It contains path to the property and property value.
*/
Expand All @@ -13,9 +29,13 @@ function field<Model, Key extends keyof Model>(
value: Model[Key] | UpdateValue<Model, Key>
): Field<Model>

function field<Model, Key1 extends keyof Model, Key2 extends keyof Model[Key1]>(
function field<
Model,
Key1 extends keyof Model,
Key2 extends keyof Whole<Model, Key1>
>(
key: [Key1, Key2],
value: Model[Key1][Key2] | UpdateValue<Model[Key1], Key2>
value: PartialValue<Model, Key1, Key2> | UpdateValue<Whole<Model, Key1>, Key2>
): Field<Model>

function field<
Expand Down
34 changes: 32 additions & 2 deletions src/field/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,44 @@ describe('field', () => {
})

it('disallows deleting required fields', () => {
type TestModel = { a: { b: { c: number; d?: string } }; e?: string }
type TestModel = {
a: { b: { c: number; d?: string } }
e?: string
f?: { [key: string]: number | undefined }
}
// @ts-expect-error
assertField<TestModel>(field(['a', 'b', 'c'], value('remove')))
assertField<TestModel>(field(['a', 'b', 'c'], value('increment', 1)))
assertField<TestModel>(field(['a', 'b', 'd'], value('remove')))
assertField<TestModel>(field('e', 'ok'))
assertField<TestModel>(field('e', value('remove')))
assertField<TestModel>(field(['f', 'ok'], value('remove')))
})

it('allows updating fields in optional partial objects', () => {
type TestModel = {
a?: { b?: number; c?: string }
d?: { e: number; f: string }
g?: { [key: string]: number | undefined }
h?: { [key: string]: number }
i: { j: number; k: string }
l?: { [key: string]: string | undefined }
}
assertField<TestModel>(field(['a', 'b'], 123))
assertField<TestModel>(field(['a', 'b'], value('remove')))
// @ts-expect-error
assertField<TestModel>(field(['d', 'e'], 123))
// @ts-expect-error
assertField<TestModel>(field(['d', 'e'], value('remove')))
assertField<TestModel>(field(['g', 'qwe'], 123))
assertField<TestModel>(field(['i', 'j'], 123))
assertField<TestModel>(field(['g', 'ok'], value('increment', 1)))
assertField<TestModel>(field(['h', 'ok'], value('increment', 1)))
// @ts-expect-error
assertField<TestModel>(field(['h', 'nope'], 123))
// @ts-expect-error
assertField<TestModel>(field(['k', 'nope'], value('increment', 1)))
})
})

function assertField<Model>(fields: Field<Model>) {}
function assertField<Model>(_field: Field<Model>) {}
25 changes: 15 additions & 10 deletions src/value/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,22 +60,27 @@ type MaybeValueRemoveOr<Model, Key extends keyof Model, ValueType> = Partial<
type MaybeValueRemove<Model, Key extends keyof Model> = Partial<
Pick<Model, Key>
> extends Pick<Model, Key>
? ValueRemove
: Undefined<Model[Key]> extends Model[Key]
? ValueRemove
: never

type Undefined<T> = T extends undefined ? T : never

/**
* The value types to use for update operation.
*/
export type UpdateValue<
Model,
Key extends keyof Model
> = Model[Key] extends number
? MaybeValueRemoveOr<Model, Key, ValueIncrement>
: Model[Key] extends Array<any>
? MaybeValueRemoveOr<Model, Key, ValueArrayUnion | ValueArrayRemove>
: Model[Key] extends Date
? MaybeValueRemoveOr<Model, Key, ValueServerDate>
: MaybeValueRemove<Model, Key>
export type UpdateValue<Model, Key> = Key extends keyof Model
? Model[Key] extends infer Type
? Type extends number
? MaybeValueRemoveOr<Model, Key, ValueIncrement>
: Type extends Array<any>
? MaybeValueRemoveOr<Model, Key, ValueArrayUnion | ValueArrayRemove>
: Type extends Date
? MaybeValueRemoveOr<Model, Key, ValueServerDate>
: MaybeValueRemove<Model, Key>
: never
: never

/**
* The value types to use for add operation.
Expand Down
3 changes: 1 addition & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@
"sourceMap": true,
"outDir": "lib",
"strict": true,
"noImplicitAny": true,
"esModuleInterop": true,
"resolveJsonModule": true
},
"exclude": ["**/test.ts", "lib", "node_modules", "test"]
"exclude": ["lib", "node_modules"]
}
4 changes: 4 additions & 0 deletions tsconfig.lib.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "./tsconfig",
"exclude": ["lib", "node_modules", "**/test.ts", "test"]
}

0 comments on commit 0c18cc4

Please sign in to comment.