Skip to content

Commit

Permalink
add serial types
Browse files Browse the repository at this point in the history
  • Loading branch information
tylim88 committed Aug 2, 2023
1 parent b7ce3bb commit c5e9f65
Show file tree
Hide file tree
Showing 17 changed files with 270 additions and 52 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ By design Firelord banned mapped type, this was until version 2.5.10. To underst

### Object Unions Type

Object unions type was banned before v2.6.2 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 the type is no longer unions type but an intersection type: `{a:number} & {b:string}`. The reason I decided to lift the limitation is that I believe we should value functionalities over less practical strict typing. Plus in future update operation Mandatory Field could mitigate this problem.
Object unions type was banned before v2.6.2 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 the type is no longer unions type but an intersection type: `{a:number} & {b:string}`. This limitation is lifted to allow users to fully utilize discriminated unions. Plus in future update operation Mandatory Field could mitigate this problem.

## TO DO

Expand All @@ -256,11 +256,18 @@ Object unions type was banned before v2.6.2 because it brings uncertainty that c

- Narrow read type base on query constraint. For example `where('a', '==', true)` will narrow the read type of field `a` to `true`, it should be able to narrow down complex case like `where('a.b.c', '==', { d:[{e:1}] })`. Expected to support `==` comparator for all types and _possibly_ `!=` comparator for literal type(type filtering for`!=` comparator poses great complexity hence I may not work on it). Update: I decided to give this up because with the introduction of composite query, it will be extremely difficult to implement this. Plus unlike narrowing down write type, narrowing down the read type does not contribute to type safety, it just makes thing slightly simpler(skip exhaustive check).

## TO FIX

1. Bytes type is not working correctly and is unusable.
2. The rule `You can use at most one array-contains or array-contains-any clause per query. You can't combine array-contains with array-contains-any` is not enabled, see https://github.com/tylim88/FirelordJS/releases/tag/2.5.9
3. The type check of composite query clauses value is wrong if the field is `__name__`, example: `query(collectionRef,or(where("__name__","==","something")))` result in false negative as the Firelord will ask for full path but we only need full path if the reference is group collection.

## Trivial

- The name Firelord is a reference to the [Firelord](https://avatar.fandom.com/wiki/Fire_Lord) of Avatar.
- Undocumented releases are README updates.
- [Contributing](https://firelordjs.com/contributing).
- Documentation [Github](https://github.com/tylim88/FirelordJSDoc)

## Related Projects

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": "firelordjs",
"version": "2.6.6",
"version": "2.6.7",
"description": "🔥 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
1 change: 1 addition & 0 deletions src/fieldValues/arrayRemove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ import { ArrayRemoveOrUnionFunction } from '../types'
* @returns The `FieldValue` sentinel for use in a call to `setDoc()` or
* `updateDoc()`
*/
// @ts-expect-error
export const arrayRemove: ArrayRemoveOrUnionFunction = arrayRemove_
1 change: 1 addition & 0 deletions src/fieldValues/arrayUnion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ import { ArrayRemoveOrUnionFunction } from '../types'
* @returns The `FieldValue` sentinel for use in a call to `setDoc()` or
* `updateDoc()`.
*/
// @ts-expect-error
export const arrayUnion: ArrayRemoveOrUnionFunction = arrayUnion_
1 change: 1 addition & 0 deletions src/fieldValues/deleteField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ import { Delete } from '../types'
* Returns a sentinel for use with {@link @firebase/firestore/lite#(updateDoc:1)} or
* {@link @firebase/firestore/lite#(setDoc:1)} with `{merge: true}` to mark a field for deletion.
*/
// @ts-expect-error
export const deleteField: () => Delete = deleteField_
1 change: 1 addition & 0 deletions src/fieldValues/increment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ import { Increment } from '../types'
* @returns The `FieldValue` sentinel for use in a call to `setDoc()` or
* `updateDoc()`
*/
// @ts-expect-error
export const increment: (n: number) => Increment = increment_
1 change: 1 addition & 0 deletions src/fieldValues/serverTimestamp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ import { ServerTimestamp } from '../types'
* Returns a sentinel used with {@link @firebase/firestore/lite#(setDoc:1)} or {@link @firebase/firestore/lite#(updateDoc:1)} to
* include a server-generated timestamp in the written data.
*/
// @ts-expect-error
export const serverTimestamp: () => ServerTimestamp = serverTimestamp_
5 changes: 5 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,9 @@ export type {
FirelordRef,
OnSnapshot,
Unsubscribe,
SerialDate as DateSerial,
SerialServerTimestamp as ServerTimestampPersist,
SerialTimestamp as TimestampPersist,
SerialGeoPoint as GeoPointPersist,
SerialDocumentReference as DocumentReferencePersist,
} from './types'
9 changes: 7 additions & 2 deletions src/types/fieldValues.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { OriFieldValue } from './alias'
import { ErrorArrayFieldValueEmpty } from './error'
import { SerialServerTimestamp } from './serial'

declare const serverTimestampSymbol: unique symbol
declare const deleteFieldSymbol: unique symbol
Expand All @@ -14,7 +15,7 @@ type PossiblyReadAsUndefinedSymbol = typeof possiblyReadAsUndefinedSymbol
type ArraySymbol = typeof arraySymbol

declare class FieldValue<T> {
protected 'Firelord_FieldValue'?: T
protected 'Firelord_FieldValue_Do_Not_Access': T
}
declare class ArrayFieldValue<T> {
protected Firelord_ArrayFieldValue?: T
Expand Down Expand Up @@ -43,4 +44,8 @@ export type ArrayRemoveOrUnionFunction = <Elements extends unknown[]>(

export type UnassignedAbleFieldValue = Increment | ArrayUnionOrRemove<unknown>

export type FieldValues = ServerTimestamp | UnassignedAbleFieldValue | Delete
export type FieldValues =
| ServerTimestamp
| UnassignedAbleFieldValue
| Delete
| SerialServerTimestamp
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ export * from './transaction'
export * from './operations'
export * from './equal'
export * from './firelord'
export * from './serial'
27 changes: 24 additions & 3 deletions src/types/metaTypeCreator/compare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,26 @@ import {
import { DeepValue } from '../objectFlatten'
import { DocumentReference } from '../refs'
import { MetaType } from './metaType'
import {
SerialDate,
SerialGeoPoint,
SerialServerTimestamp,
SerialDocumentReference,
SerialTimestamp,
} from '../serial'

type CompareConverterArray<T, BannedTypes> = NoDirectNestedArray<
T,
T extends (infer A)[]
? readonly CompareConverterArray<A, BannedTypes>[]
: T extends FieldValues
? ErrorFieldValueInArray
: T extends Date | Timestamp
: T extends Date | Timestamp | SerialDate | SerialTimestamp
? Timestamp | Date
: T extends SerialDocumentReference<infer R>
? DocumentReference<R>
: T extends SerialGeoPoint
? GeoPoint
: T extends DocumentReference<MetaType> | Bytes | GeoPoint
? T
: T extends Record<string, unknown>
Expand All @@ -39,9 +50,19 @@ export type CompareConverter<T, BannedTypes> = NoDirectNestedArray<
T,
T extends (infer A)[]
? readonly CompareConverterArray<A, BannedTypes>[]
: T extends ServerTimestamp | Date | Timestamp
: T extends
| ServerTimestamp
| Date
| Timestamp
| SerialDate
| SerialTimestamp
| SerialServerTimestamp
? Timestamp | Date
: T extends DocumentReference<MetaType> | Bytes | GeoPoint
: T extends SerialGeoPoint
? GeoPoint
: T extends SerialDocumentReference<MetaType>
? DocumentReference<MetaType>
: T extends DocumentReference<MetaType> | Bytes | GeoPoint | SerialGeoPoint
? T
: T extends Record<string, unknown>
? {
Expand Down
88 changes: 87 additions & 1 deletion src/types/metaTypeCreator/metaTypeCreator.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { MetaTypeCreator } from './metaTypeCreator'
import { MetaType } from './metaType'
import { Timestamp, Bytes, GeoPoint } from '../alias'
import { ErrorNullBanned, ErrorDirectNested } from '../error'
import {
ErrorNullBanned,
ErrorDirectNested,
ErrorFieldValueInArray,
} from '../error'
import {
ArrayUnionOrRemove,
Increment,
Expand All @@ -13,6 +17,13 @@ import { DocumentReference } from '../refs'
import { IsTrue, IsSame, IsEqual } from '../utils'
import { Parent, User } from '../../utilForTests'
import { __name__Record } from '../fieldPath'
import {
SerialDate,
SerialGeoPoint,
SerialServerTimestamp,
SerialDocumentReference,
SerialTimestamp,
} from '../serial'

describe('test Firelord type', () => {
it('test parents equal', () => {
Expand Down Expand Up @@ -826,4 +837,79 @@ describe('test Firelord type', () => {
IsTrue<IsSame<ExpectWriteMerge, Write>>
IsTrue<IsSame<ExpectCompare, Compare>>
})
it('test persistent type', () => {
type A = MetaTypeCreator<
{
a: SerialTimestamp
b: SerialDate
c: SerialServerTimestamp
d: SerialDocumentReference<MetaType>
e: SerialGeoPoint
f: SerialTimestamp[]
g: SerialDate[]
h: SerialServerTimestamp[]
i: SerialDocumentReference<MetaType>[]
j: SerialGeoPoint[]
},
'Persist'
>

type ExpectRead = A['read']
type ExpectWrite = A['write']
type ExpectWriteFlatten = A['writeFlatten']
type ExpectWriteMerge = A['writeMerge']
type ExpectCompare = A['compare']

type Read = {
a: SerialTimestamp
b: SerialTimestamp
c: SerialTimestamp
d: SerialDocumentReference<MetaType>
e: SerialGeoPoint
f: SerialTimestamp[]
g: SerialTimestamp[]
h: ErrorFieldValueInArray[]
i: SerialDocumentReference<MetaType>[]
j: SerialGeoPoint[]
}

type Write = {
a: Timestamp | Date
b: Timestamp | Date
c: ServerTimestamp
d: DocumentReference<MetaType>
e: GeoPoint
f: readonly (Timestamp | Date)[] | ArrayUnionOrRemove<Timestamp | Date>
g: readonly (Timestamp | Date)[] | ArrayUnionOrRemove<Timestamp | Date>
h:
| readonly ErrorFieldValueInArray[]
| ArrayUnionOrRemove<ErrorFieldValueInArray>
i:
| readonly DocumentReference<MetaType>[]
| ArrayUnionOrRemove<DocumentReference<MetaType>>
j: readonly GeoPoint[] | ArrayUnionOrRemove<GeoPoint>
}

// TODO need better tests
type WriteFlatten = Write
type WriteMerge = Write
type Compare = {
a: Timestamp | Date
b: Timestamp | Date
c: Timestamp | Date
d: DocumentReference<MetaType>
e: GeoPoint
f: readonly (Timestamp | Date)[]
g: readonly (Timestamp | Date)[]
h: readonly ErrorFieldValueInArray[]
i: readonly DocumentReference<MetaType>[]
j: readonly GeoPoint[]
} & __name__Record

IsTrue<IsSame<ExpectRead, Read>>
IsTrue<IsSame<ExpectWrite, Write>>
IsTrue<IsSame<ExpectWriteFlatten, WriteFlatten>>
IsTrue<IsSame<ExpectWriteMerge, WriteMerge>>
IsTrue<IsSame<ExpectCompare, Compare>>
})
})
103 changes: 64 additions & 39 deletions src/types/metaTypeCreator/read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,50 +15,66 @@ import {
} from '../fieldValues'
import { DocumentReference } from '../refs'
import { MetaType } from './metaType'
import {
SerialDate,
SerialGeoPoint,
SerialServerTimestamp,
SerialDocumentReference,
SerialTimestamp,
} from '../serial'

type ReadConverterArray<
T,
allFieldsPossiblyReadAsUndefined,
BannedTypes,
InArray extends boolean
> = NoDirectNestedArray<
T,
T extends (infer A)[]
?
| ReadConverterArray<
A,
allFieldsPossiblyReadAsUndefined,
BannedTypes,
true
>[]
| (InArray extends true ? never : allFieldsPossiblyReadAsUndefined)
: T extends FieldValues
? ErrorFieldValueInArray
: T extends Date | Timestamp
?
| Timestamp
| (InArray extends true ? never : allFieldsPossiblyReadAsUndefined)
: T extends DocumentReference<MetaType> | Bytes | GeoPoint
? T | (InArray extends true ? never : allFieldsPossiblyReadAsUndefined)
: T extends Record<string, unknown>
?
| {
[K in keyof T]-?: ReadConverterArray<
T[K],
allFieldsPossiblyReadAsUndefined,
BannedTypes,
false
>
}
| (InArray extends true ? never : allFieldsPossiblyReadAsUndefined)
: T extends PossiblyReadAsUndefined
? InArray extends true
? ErrorPossiblyUndefinedAsArrayElement
: undefined
:
| NoUndefinedAndBannedTypes<T, BannedTypes>
| allFieldsPossiblyReadAsUndefined
>
> = (
InArray extends true ? never : allFieldsPossiblyReadAsUndefined
) extends infer U
? NoDirectNestedArray<
T,
T extends (infer A)[]
?
| ReadConverterArray<
A,
allFieldsPossiblyReadAsUndefined,
BannedTypes,
true
>[]
| U
: T extends FieldValues
? ErrorFieldValueInArray
: T extends Date | Timestamp
? Timestamp | U
: T extends SerialDate | SerialTimestamp
? SerialTimestamp | U
: T extends
| DocumentReference<MetaType>
| Bytes
| GeoPoint
| SerialGeoPoint
| SerialDocumentReference<MetaType>
? T | U
: T extends Record<string, unknown>
?
| {
[K in keyof T]-?: ReadConverterArray<
T[K],
allFieldsPossiblyReadAsUndefined,
BannedTypes,
false
>
}
| U
: T extends PossiblyReadAsUndefined
? InArray extends true
? ErrorPossiblyUndefinedAsArrayElement
: undefined
:
| NoUndefinedAndBannedTypes<T, BannedTypes>
| allFieldsPossiblyReadAsUndefined
>
: never

export type ReadConverter<T, allFieldsPossiblyReadAsUndefined, BannedTypes> =
NoDirectNestedArray<
Expand All @@ -74,7 +90,16 @@ export type ReadConverter<T, allFieldsPossiblyReadAsUndefined, BannedTypes> =
| allFieldsPossiblyReadAsUndefined
: T extends ServerTimestamp | Date | Timestamp
? Timestamp | allFieldsPossiblyReadAsUndefined
: T extends DocumentReference<MetaType> | Bytes | GeoPoint
: T extends SerialDate | SerialTimestamp | SerialServerTimestamp
? SerialTimestamp
: T extends
| DocumentReference<MetaType>
| Bytes
| GeoPoint
| SerialGeoPoint
| SerialDocumentReference<MetaType>
| SerialDate
| SerialTimestamp
? T | allFieldsPossiblyReadAsUndefined
: T extends Record<string, unknown>
?
Expand Down
Loading

0 comments on commit c5e9f65

Please sign in to comment.