Skip to content

Commit

Permalink
release v2.3.2, improve how to handle const assertion for which clause
Browse files Browse the repository at this point in the history
  • Loading branch information
tylim88 committed Feb 12, 2023
1 parent 7640fe1 commit ba4809c
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 44 deletions.
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ I am confident it has the best type safe and nothing come close. I put money on

FirelordJS is thoroughly tested, it tests source code, built files and published package.

No mock test, all tests are test against real database and emulator to ensure the highest certainty!
No mock test, all tests test against real database and emulator to ensure the highest certainty!

## Trivial

Expand All @@ -132,20 +132,30 @@ Read [here](https://firelordjs.com/contributing)

## Upcoming V3

V3 focus on codebase overhaul, hopefully with further improved code quality, potential contributor will find it easier to work with.
V3 focus on codebase rewrite, hopefully with further improved code quality, potential contributor will find it easier to work with.

Code Quality Improvements:

- More extensible type logics.
- Simpler type logics.
- Implement latest TS features.
- Better file and folder structure.
- Remove trivial APIs.
- More Tests.
- New Documentation.
- Improved Documentation.(ongoing improvement since v2.3.1)

New Features:

- Auto narrow to literal type, remove the need to [manually assert as const](https://firelordjs.com/guides/7_const)(Most of this issue was resolved in v2.3.1).
- Auto narrow to literal type, remove the need to [manually assert as const](https://firelordjs.com/guides/7_const)(Most of this issue was resolved in v2.3.1 and v2.3.2) Turn out [TS 5.0 const type parameter also solved this problem](https://devblogs.microsoft.com/typescript/announcing-typescript-5-0-beta/#const-type-parameters), However, some cases are complicated, example `[[1]] as const` and `[[1] as const]` do not extend each other, and we still need extra logic to handle case like `[[1] as const]`.

- Narrow read type base on query constraint. For example `where('a', '==', true)` will narrow the read type of field `a` to `true`.

- Mandatory field update. Example, for field like `updatedAt`, it is mandatory to includes it every time you update the document. There are two ways to implement these feature: via Meta Type and via abstraction. With Meta Type(using special field value), it is less flexible because we no longer able to exclude it from all update operations. With abstraction, it is more flexible but require more works from user. I prefer via abstraction due to it does not shut down other use cases despite having lower user experience.
- Support wide numeric key and wide string key (Record<number, unknown> and Record<string, unknown>). It still needs more consideration because this data type is pointless to query(we need to know what the key is first, it would be better to just save the document ID somewhere) and we need to constantly aware of the document size limit. If you don't care about query and you sure that the size limit will not exceed 1 MB, then this is for you. But allowing this also open up for mistake and bad practice for those who are not aware. Most likely I will not implement this.

What will not be implemented:

- Support for wide numeric key and wide string key (Record<number, unknown> and Record<string, unknown>). It still needs more consideration because this data type is pointless to query(we need to know what the key is first, it would be better to just save the document ID somewhere) and we need to constantly aware of the document size limit. If you don't care about query and you sure that the size limit will not exceed 1 MB, then this is for you. But allowing this also open up for mistake and bad practice for those who are not aware. Most likely I will not implement this.

- Support for object unions type. Objects unions type seem to be a good type to have in NoSQL database. However this is not the case because it brings uncertainty that cannot be handled reasonably. For example, with `{a:number}|{b:string}`, you can set `{a:1}` then update `{b:"x"}`, in this case type is no longer unions type but an intersection type `{a:number} & {b:string}`. So I will not implement this feature and will remove it from FireSageJS too. A better way to solve this is to use `PossiblyReadAsUndefined` label instead.

- Support for optional (`?` modifier). Optional is a highly requested feature because of common it is, however because of how Firestore works: it is impossible to query field that is missing, example: it is impossible to query user that has no phone number if phone number field is not exist. Because of this, it is important to make sure every field exists. You may not need the field now, but you may need it later plus adding default value is a simple thing to do, especially with such powerful typing library like Firelord. So in order not to accidentally cripple your query in the future, I will not implement this feature. Yes, set merge basically have the same problem, I discourage you from using set merge, I am also considering removing set merge.
28 changes: 28 additions & 0 deletions codeForDoc/src/highlights/whereFirelord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,31 @@ const arr = (): ('a' | 'b' | 'c')[] => {
return []
} // empty array may result from expression
query(colRef, where('z', 'in', arr())) // impossible to block correctType[]

const fs = getFirestore()

type ABC = MetaTypeCreator<
{
a: 1 | 2 | 3 // literal type
b: ('a' | 'b' | 'c')[] // literal type
},
'ABC'
>

const ColRef = getFirelord<ABC>(db, 'ABC').collection()

// literal type
query(ColRef, where('a', '>', 1)) // ok, not dealing with array
// @ts-expect-error
query(ColRef, where('a', 'in', [1])) // not ok, it is an array AND literal type, need const assertion!
query(ColRef, where('a', 'in', [1 as const])) // ok, const assertion!
query(ColRef, where('a', 'in', [1] as const)) // ok, const assertion!

// literal array type
// @ts-expect-error
query(ColRef, where('b', '==', ['a'])) // not ok, dealing with array AND literal type, need const assertion!
query(ColRef, where('b', '==', ['a' as const])) // ok, const assertion!
query(ColRef, where('b', 'in', [['a' as const]])) // ok, const assertion!
query(ColRef, where('b', '==', ['a'] as const)) // ok, const assertion!
query(ColRef, where('b', 'in', [['a'] as const])) // ok, const assertion!
query(ColRef, where('b', 'in', [['a']] as const)) // ok, const assertion!
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "firelordjs",
"version": "2.3.1",
"version": "2.3.2",
"description": "🔥 Extremely High Precision Typescript Wrapper for Firestore Web, Providing Unparalleled Type Safe and Dev Experience",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
15 changes: 15 additions & 0 deletions src/queryClauses/where.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,21 @@ describe('test query ref', () => {
query(ref, where('a.e', 'in', [['1']]))
})

it('test literal type with const assertion, should pass', () => {
query(ref, where('role', 'in', ['admin'] as const))
query(ref, where('role', 'not-in', ['admin'] as const))
query(ref, where('role', 'in', ['admin' as const]))
query(ref, where('role', 'not-in', ['admin' as const]))
})
it('test literal type without const assertion, should fail', () => {
// @ts-expect-error
query(ref, where('role', 'in', ['admin']))
query(
ref,
// @ts-expect-error
where('role', 'not-in', ['admin'])
)
})
it('test block fresh empty array, negative case', () => {
// @ts-expect-error
query(ref, where('name', 'not-in', []))
Expand Down
5 changes: 4 additions & 1 deletion src/types/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export type ErrorUnknownMember<T> =
export type ErrorWhereDocumentFieldPath =
'If field path is document ID, then value must be string'
export type ErrorWhere__name__ =
"Error: Dont use ( __name__ ) directly as where's field path, use documentId() sentinel field path instead."
"Error: Don't use ( __name__ ) directly as where's field path, use documentId() sentinel field path instead."
export type ErrorCursor__name__ =
'Error: detected type is string, please do const assertion'
export type ErrorArrayFieldValueEmpty =
Expand All @@ -86,6 +86,8 @@ export type ErrorCursorNoArray =
'Error: cursor rest parameter must be tuple type, not array type. Use const assertion to transform array type to tuple type'
export type ErrorAutoIdTypeMustBeWideString<T extends string> =
`Error: Only document with wide 'string' type can generate auto id, the current type is literal type '${T}'`
export type ErrorWhereInOrNotInValueIsNotArray<T extends string> =
`Error: The value provided to 'in' or 'not-in' comparator must be array of type '${T}'`

export type ErrorMsgs =
| ErrorUndefined
Expand Down Expand Up @@ -125,6 +127,7 @@ export type ErrorMsgs =
| ErrorEmptyCursor
| ErrorCursorNoArray
| ErrorAutoIdTypeMustBeWideString<string>
| ErrorWhereInOrNotInValueIsNotArray<string>

export type ReplaceErrorMsgsWithNever<T> = T extends ErrorMsgs ? never : T // ! not yet implemented anywhere

Expand Down
58 changes: 33 additions & 25 deletions src/types/metaTypeCreator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ describe('test Firelord type', () => {
e: false
}
f:
| {
| readonly {
g: Date | Timestamp | null
h: 2
}[]
Expand All @@ -90,7 +90,7 @@ describe('test Firelord type', () => {
e: false
}
f:
| {
| readonly {
g: Timestamp | Date | null
h: 2
}[]
Expand All @@ -109,7 +109,7 @@ describe('test Firelord type', () => {
e: false
}
'b.f':
| {
| readonly {
g: Timestamp | Date | null
h: 2
}[]
Expand All @@ -131,7 +131,7 @@ describe('test Firelord type', () => {
}
'd.e': false
f:
| {
| readonly {
g: Timestamp | Date | null
h: 2
}[]
Expand All @@ -145,7 +145,7 @@ describe('test Firelord type', () => {
e: false
}
'b.f':
| {
| readonly {
g: Timestamp | Date | null
h: 2
}[]
Expand Down Expand Up @@ -214,7 +214,7 @@ describe('test Firelord type', () => {
e: false
}
f:
| {
| readonly {
g: Date | Timestamp | null
h: 2
}[]
Expand All @@ -238,7 +238,7 @@ describe('test Firelord type', () => {
}
'd.e': false
f:
| {
| readonly {
g: Timestamp | Date | null
h: 2
}[]
Expand All @@ -252,7 +252,7 @@ describe('test Firelord type', () => {
h: string | null
i: number | Increment
'b.f':
| {
| readonly {
g: Timestamp | Date | null
h: 2
}[]
Expand All @@ -278,7 +278,7 @@ describe('test Firelord type', () => {
}
'd.e': false
f:
| {
| readonly {
g: Timestamp | Date | null
h: 2
}[]
Expand All @@ -293,7 +293,7 @@ describe('test Firelord type', () => {
}
'b.d.e': false
'b.f':
| {
| readonly {
g: Timestamp | Date | null
h: 2
}[]
Expand Down Expand Up @@ -360,7 +360,7 @@ describe('test Firelord type', () => {
c: 'a' | ErrorNullBanned
d: { e: false | ErrorNullBanned }
f:
| {
| readonly {
g: Date | Timestamp | ErrorNullBanned
h: 2 | ErrorNullBanned
}[]
Expand All @@ -385,7 +385,7 @@ describe('test Firelord type', () => {
'd.e': false | ErrorNullBanned
f:
| ErrorNullBanned
| {
| readonly {
g: Timestamp | Date | ErrorNullBanned
h: 2 | ErrorNullBanned
}[]
Expand All @@ -404,7 +404,7 @@ describe('test Firelord type', () => {
'b.d.e': false | ErrorNullBanned
'b.f':
| ErrorNullBanned
| {
| readonly {
g: Timestamp | Date | ErrorNullBanned
h: 2 | ErrorNullBanned
}[]
Expand All @@ -427,7 +427,7 @@ describe('test Firelord type', () => {
'd.e': false | ErrorNullBanned
f:
| ErrorNullBanned
| {
| readonly {
g: Date | Timestamp | ErrorNullBanned
h: ErrorNullBanned | 2
}[]
Expand All @@ -442,7 +442,7 @@ describe('test Firelord type', () => {
'b.d.e': false | ErrorNullBanned
'b.f':
| ErrorNullBanned
| {
| readonly {
g: Date | Timestamp | ErrorNullBanned
h: ErrorNullBanned | 2
}[]
Expand Down Expand Up @@ -505,7 +505,7 @@ describe('test Firelord type', () => {
c: 'a' | DeleteField
d: ErrorUnionInvolveObjectType
f:
| {
| readonly {
g: Date | Timestamp | null
h: 2
}[]
Expand All @@ -527,7 +527,7 @@ describe('test Firelord type', () => {
c: 'a' | DeleteField
d: ErrorUnionInvolveObjectType
f:
| {
| readonly {
g: Timestamp | Date | null
h: 2
}[]
Expand All @@ -542,7 +542,7 @@ describe('test Firelord type', () => {
h: string | DeleteField
i: number | null | Increment | DeleteField
'b.f':
| {
| readonly {
g: Timestamp | Date | null
h: 2
}[]
Expand All @@ -563,7 +563,7 @@ describe('test Firelord type', () => {
c: 'a'
d: ErrorUnionInvolveObjectType
f:
| {
| readonly {
g: Timestamp | Date | null
h: 2
}[]
Expand All @@ -575,7 +575,7 @@ describe('test Firelord type', () => {
'b.c': 'a'
'b.d': ErrorUnionInvolveObjectType
'b.f':
| {
| readonly {
g: Timestamp | Date | null
h: 2
}[]
Expand Down Expand Up @@ -666,10 +666,12 @@ describe('test Firelord type', () => {

type ExpectedWrite = Z & {
d: Z
e: Z[] | ArrayUnionOrRemove<Z>
f: GeoPoint[] | ArrayUnionOrRemove<GeoPoint>
g: Bytes[] | ArrayUnionOrRemove<Bytes>
h: DocumentReference<User>[] | ArrayUnionOrRemove<DocumentReference<User>>
e: readonly Z[] | ArrayUnionOrRemove<Z>
f: readonly GeoPoint[] | ArrayUnionOrRemove<GeoPoint>
g: readonly Bytes[] | ArrayUnionOrRemove<Bytes>
h:
| readonly DocumentReference<User>[]
| ArrayUnionOrRemove<DocumentReference<User>>
}

type ExpectedWriteFlatten = ExpectedWrite & {
Expand All @@ -678,7 +680,13 @@ describe('test Firelord type', () => {
'd.c': DocumentReference<User>
}

type ExpectedCompare = B & {
type ExpectedCompare = Z & {
d: Z
e: readonly Z[]
f: readonly GeoPoint[]
g: readonly Bytes[]
h: readonly DocumentReference<User>[]
} & {
'd.a': GeoPoint
'd.b': Bytes
'd.c': DocumentReference<User>
Expand Down
8 changes: 4 additions & 4 deletions src/types/metaTypeCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ type ReadConverter<T, allFieldsPossiblyReadAsUndefined, BannedTypes> =
type CompareConverterArray<T, BannedTypes> = NoDirectNestedArray<
T,
T extends (infer A)[]
? CompareConverterArray<A, BannedTypes>[]
? readonly CompareConverterArray<A, BannedTypes>[]
: T extends FieldValues
? ErrorFieldValueInArray
: T extends Date | Timestamp
Expand All @@ -235,7 +235,7 @@ type CompareConverterArray<T, BannedTypes> = NoDirectNestedArray<
type CompareConverter<T, BannedTypes> = NoDirectNestedArray<
T,
T extends (infer A)[]
? CompareConverterArray<A, BannedTypes>[]
? readonly CompareConverterArray<A, BannedTypes>[]
: T extends ServerTimestamp | Date | Timestamp
? Timestamp | Date
: T extends DocumentReference<any> | Bytes | GeoPoint
Expand All @@ -254,7 +254,7 @@ type CompareConverter<T, BannedTypes> = NoDirectNestedArray<
type ArrayWriteConverter<T, BannedTypes> = NoDirectNestedArray<
T,
T extends (infer A)[]
? ArrayWriteConverter<A, BannedTypes>[]
? readonly ArrayWriteConverter<A, BannedTypes>[]
: T extends FieldValues
? ErrorFieldValueInArray
: T extends Timestamp | Date
Expand All @@ -274,7 +274,7 @@ type WriteConverter<T, BannedTypes> = NoDirectNestedArray<
T,
T extends (infer A)[]
?
| ArrayWriteConverter<A, BannedTypes>[]
| readonly ArrayWriteConverter<A, BannedTypes>[]
| ArrayUnionOrRemove<ArrayWriteConverter<A, BannedTypes>>
: T extends
| DocumentReference<any>
Expand Down
Loading

0 comments on commit ba4809c

Please sign in to comment.