Skip to content

Commit 57f2a86

Browse files
Changing UpdateData<T> to expand support for types with index signatures. (#7318)
Changing UpdateData<T> to expand support for types with index signatures.
1 parent f2fb56f commit 57f2a86

File tree

7 files changed

+116
-18
lines changed

7 files changed

+116
-18
lines changed

.changeset/breezy-flies-exist.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@firebase/firestore": minor
3+
---
4+
5+
Changing UpdateData<T> to expand support for types with index signatures.

common/api-review/firestore-lite.api.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export function addDoc<AppModelType, DbModelType extends DocumentData>(reference
1414

1515
// @public
1616
export type AddPrefixToKeys<Prefix extends string, T extends Record<string, unknown>> = {
17-
[K in keyof T & string as `${Prefix}.${K}`]+?: T[K];
17+
[K in keyof T & string as `${Prefix}.${K}`]+?: string extends K ? any : T[K];
1818
};
1919

2020
// @public

common/api-review/firestore.api.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export function addDoc<AppModelType, DbModelType extends DocumentData>(reference
1414

1515
// @public
1616
export type AddPrefixToKeys<Prefix extends string, T extends Record<string, unknown>> = {
17-
[K in keyof T & string as `${Prefix}.${K}`]+?: T[K];
17+
[K in keyof T & string as `${Prefix}.${K}`]+?: string extends K ? any : T[K];
1818
};
1919

2020
// @public

docs-devsite/firestore_.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -2209,7 +2209,7 @@ Returns a new map where every key is prefixed with the outer key appended to a d
22092209

22102210
```typescript
22112211
export declare type AddPrefixToKeys<Prefix extends string, T extends Record<string, unknown>> = {
2212-
[K in keyof T & string as `${Prefix}.${K}`]+?: T[K];
2212+
[K in keyof T & string as `${Prefix}.${K}`]+?: string extends K ? any : T[K];
22132213
};
22142214
```
22152215

docs-devsite/firestore_lite.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1424,7 +1424,7 @@ Returns a new map where every key is prefixed with the outer key appended to a d
14241424

14251425
```typescript
14261426
export declare type AddPrefixToKeys<Prefix extends string, T extends Record<string, unknown>> = {
1427-
[K in keyof T & string as `${Prefix}.${K}`]+?: T[K];
1427+
[K in keyof T & string as `${Prefix}.${K}`]+?: string extends K ? any : T[K];
14281428
};
14291429
```
14301430

packages/firestore/src/lite-api/types.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,22 @@ export type AddPrefixToKeys<
6666
T extends Record<string, unknown>
6767
> =
6868
// Remap K => Prefix.K. See https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#key-remapping-via-as
69-
{ [K in keyof T & string as `${Prefix}.${K}`]+?: T[K] };
69+
70+
// `string extends K : ...` is used to detect index signatures
71+
// like `{[key: string]: bool}`. We map these properties to type `any`
72+
// because a field path like `foo.[string]` will match `foo.bar` or a
73+
// sub-path `foo.bar.baz`. Because it matches a sub-path, we have to
74+
// make this type `any` to allow for any types of the sub-path property.
75+
// This is a significant downside to using index signatures in types for `T`
76+
// for `UpdateData<T>`.
77+
78+
{
79+
/* eslint-disable @typescript-eslint/no-explicit-any */
80+
[K in keyof T & string as `${Prefix}.${K}`]+?: string extends K
81+
? any
82+
: T[K];
83+
/* eslint-enable @typescript-eslint/no-explicit-any */
84+
};
7085

7186
/**
7287
* Given a union type `U = T1 | T2 | ...`, returns an intersected type

packages/firestore/test/unit/lite-api/types.test.ts

+91-13
Original file line numberDiff line numberDiff line change
@@ -441,23 +441,101 @@ describe('UpdateData - v9', () => {
441441
}
442442
};
443443

444-
// preserves type - failure
445-
_ = {
446-
// @ts-expect-error
447-
'indexed.bar': false,
448-
// @ts-expect-error
449-
'indexed.baz': 'string'
444+
expect(true).to.be.true;
445+
});
446+
});
447+
448+
// v10 tests cover new scenarios that are fixed for v10
449+
describe('UpdateData - v10', () => {
450+
interface MyV10ServerType {
451+
booleanProperty: boolean;
452+
453+
// index signatures nested 1 layer deep
454+
indexed: {
455+
[name: string]: {
456+
booleanProperty: boolean;
457+
numberProperty: number;
458+
};
450459
};
451460

452-
// preserves properties of nested objects - failure
453-
_ = {
454-
'indexed.bar': {
455-
// @ts-expect-error
456-
booleanProperty: 'string'
457-
}
461+
// index signatures nested 2 layers deep
462+
layer: {
463+
indexed: {
464+
[name: string]: {
465+
booleanProperty: boolean;
466+
numberProperty: number;
467+
};
468+
};
458469
};
470+
}
459471

460-
expect(true).to.be.true;
472+
describe('given nested objects with index properties', () => {
473+
it('supports object replacement at each layer (with partial)', () => {
474+
// This unexpectidly fails in v9 when the object has index signature nested
475+
// two layers deep (e.g. layer.indexed.[name]).
476+
const _: UpdateData<MyV10ServerType> = {
477+
indexed: {
478+
bar: {},
479+
baz: {}
480+
}
481+
};
482+
483+
expect(true).to.be.true;
484+
});
485+
486+
it('allows dot notation for nested index types', () => {
487+
let _: UpdateData<MyV10ServerType>;
488+
489+
// v10 allows 3 layers of dot notation
490+
491+
// allows the property
492+
_ = {
493+
'indexed.bar.booleanProperty': true
494+
};
495+
496+
_ = {
497+
'indexed.bar.numberProperty': 1
498+
};
499+
500+
// does not enforce type
501+
_ = {
502+
'indexed.bar.booleanProperty': 'string value is not rejected'
503+
};
504+
505+
_ = {
506+
'indexed.bar.numberProperty': 'string value is not rejected'
507+
};
508+
509+
// rejects properties that don't exist
510+
_ = {
511+
'indexed.bar.unknown': 'string value is not rejected'
512+
};
513+
514+
expect(true).to.be.true;
515+
});
516+
517+
it('allows dot notation for nested index types that are 2 layers deep', () => {
518+
let _: UpdateData<MyV10ServerType>;
519+
520+
// v10 3 layers with dot notation
521+
522+
// allows the property
523+
_ = {
524+
'layer.indexed.bar.booleanProperty': true
525+
};
526+
527+
// allows the property, but does not enforce type
528+
_ = {
529+
'layer.indexed.bar.booleanProperty': 'string value is not rejected'
530+
};
531+
532+
// Allows unknown properties in sub types
533+
_ = {
534+
'layer.indexed.bar.unknownProperty': 'This just allows anything'
535+
};
536+
537+
expect(true).to.be.true;
538+
});
461539
});
462540
});
463541
});

0 commit comments

Comments
 (0)