Skip to content

Commit 8b13e01

Browse files
committed
Nullify data in types
1 parent 9e27695 commit 8b13e01

File tree

12 files changed

+459
-81
lines changed

12 files changed

+459
-81
lines changed

Diff for: .gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ firestore-debug.log
77
firebase-debug.log
88
.firebaserc
99
/tmp
10-
/.ts
10+
.ts

Diff for: .vscode/settings.json

+1-18
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,7 @@
11
{
22
"typescript.tsdk": "node_modules/typescript/lib",
33
"workbench.colorCustomizations": {
4-
"activityBar.activeBackground": "#1a8cad",
5-
"activityBar.activeBorder": "#f3ace3",
6-
"activityBar.background": "#1a8cad",
7-
"activityBar.foreground": "#e7e7e7",
8-
"activityBar.inactiveForeground": "#e7e7e799",
9-
"activityBarBadge.background": "#f3ace3",
10-
"activityBarBadge.foreground": "#15202b",
11-
"sash.hoverBorder": "#1a8cad",
12-
"statusBar.background": "#136881",
13-
"statusBar.foreground": "#e7e7e7",
14-
"statusBarItem.hoverBackground": "#1a8cad",
15-
"statusBarItem.remoteBackground": "#136881",
16-
"statusBarItem.remoteForeground": "#e7e7e7",
17-
"titleBar.activeBackground": "#136881",
18-
"titleBar.activeForeground": "#e7e7e7",
19-
"titleBar.inactiveBackground": "#13688199",
20-
"titleBar.inactiveForeground": "#e7e7e799",
21-
"commandCenter.border": "#e7e7e799"
4+
"activityBar.activeBorder": "#f3ace3"
225
},
236
"peacock.remoteColor": "#136881",
247
"editor.formatOnSave": true,

Diff for: src/index.ts

+8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { TypesaurusCore as Core } from "./types/core";
2+
import type { TypesaurusUtils as Utils } from "./types/utils";
23

34
export * from "./transaction";
45
export * from "./groups";
@@ -7,6 +8,13 @@ export * from "./helpers";
78

89
export declare const schema: Core.Function;
910

11+
/**
12+
* Deeply adds null to all undefined values. It's useful for wrapping
13+
* your types when you expect data from Firestore where undefined values turn
14+
* into nulls.
15+
*/
16+
export type Nullify<Type> = Core.Nullify<Type>;
17+
1018
export namespace Typesaurus {
1119
/**
1220
* Infers schema types. Useful to define function arguments that accept

Diff for: src/types/core.ts

+34-1
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,11 @@ export namespace TypesaurusCore {
515515
export type Data<
516516
Model extends ModelObjectType,
517517
DateMissing extends ServerDateMissing,
518+
> = DataNullified<Nullify<Model>, DateMissing>;
519+
520+
export type DataNullified<
521+
Model extends ModelObjectType,
522+
DateMissing extends ServerDateMissing,
518523
> = {
519524
[Key in keyof Model]: DataField<Model[Key], DateMissing>;
520525
};
@@ -608,7 +613,12 @@ export namespace TypesaurusCore {
608613
* Type of the data passed to write functions. It extends the model allowing
609614
* to set special values, sucha as server date, increment, etc.
610615
*/
611-
export type WriteData<Model, Props extends DocProps> = {
616+
export type WriteData<Model, Props extends DocProps> = WriteDataNullified<
617+
Nullify<Model>,
618+
Props
619+
>;
620+
621+
export type WriteDataNullified<Model, Props extends DocProps> = {
612622
[Key in keyof Model]: WriteField<Model, Key, Props>;
613623
};
614624

@@ -1549,4 +1559,27 @@ export namespace TypesaurusCore {
15491559
/** The client app name. It takes priority over the root's app name. */
15501560
app?: string;
15511561
}
1562+
1563+
/**
1564+
* Deeply adds null to all undefined values.
1565+
*/
1566+
export type Nullify<Type> =
1567+
// First we extract null and undefined
1568+
Type extends null | undefined
1569+
? Type | null
1570+
: // Now we extract as-is types
1571+
Type extends string | number | boolean | Date | ServerDate | Ref<any>
1572+
? Type
1573+
: // Now extract array types
1574+
Type extends Array<infer Item>
1575+
? Array<Nullify<Item>>
1576+
: // Now extract object types
1577+
Type extends object
1578+
? {
1579+
[Key in keyof Type]: // If field is optionally undefined, exclude undefined before nullifing the rest
1580+
Utils.ActuallyUndefined<Type, Key> extends true
1581+
? Nullify<Type[Key]>
1582+
: Nullify<Exclude<Type[Key], undefined>>;
1583+
}
1584+
: never;
15521585
}

Diff for: src/types/update.ts

+12-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { TypesaurusUtils as Utils } from "./utils.js";
22
import type { TypesaurusCore as Core } from "./core.js";
3+
import { Nullify } from "../index.js";
34

45
export namespace TypesaurusUpdate {
56
export interface CollectionFunction<Def extends Core.DocDef> {
@@ -79,6 +80,11 @@ export namespace TypesaurusUpdate {
7980
export type Data<
8081
Model extends Core.ModelObjectType,
8182
Props extends Core.DocProps,
83+
> = DataNullified<Core.Nullify<Model>, Props>;
84+
85+
export type DataNullified<
86+
Model extends Core.ModelObjectType,
87+
Props extends Core.DocProps,
8288
> = {
8389
[Key in keyof Model]?: Core.WriteField<Model, Key, Props>;
8490
};
@@ -97,13 +103,15 @@ export namespace TypesaurusUpdate {
97103
export interface Helpers<
98104
Model extends Core.ModelObjectType,
99105
Props extends Core.DocProps,
100-
> extends CommonHelpers<Model, Props, UpdateField<Model>> {}
106+
> extends CommonHelpers<Nullify<Model>, Props, UpdateField<Model>> {}
101107

102108
export interface Builder<Def extends Core.DocDef, Props extends Core.DocProps>
103109
extends CommonHelpers<
104-
Def["Flags"]["Reduced"] extends true
105-
? Core.IntersectVariableModelType<Def["Model"]>
106-
: Core.SharedVariableModelType<Def["WideModel"]>,
110+
Core.Nullify<
111+
Def["Flags"]["Reduced"] extends true
112+
? Core.IntersectVariableModelType<Def["Model"]>
113+
: Core.SharedVariableModelType<Def["WideModel"]>
114+
>,
107115
Props,
108116
void
109117
> {

Diff for: src/types/utils.ts

+19-3
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,10 @@ export namespace TypesaurusUtils {
7070
* and values.
7171
*/
7272
export type AllRequired<Model> = {
73-
[Key in keyof Required<Model>]-?: Exclude<Required<Model>[Key], undefined>;
73+
[Key in keyof Required<Model>]-?: Exclude<
74+
Required<Model>[Key],
75+
undefined | null
76+
>;
7477
};
7578

7679
/**
@@ -85,6 +88,15 @@ export namespace TypesaurusUtils {
8588
: true
8689
: false;
8790

91+
/**
92+
* Resolves true if the passed field is undefined union and not optionally
93+
* undefined.
94+
*/
95+
export type ActuallyUndefined<
96+
Model,
97+
Key extends keyof Model,
98+
> = undefined extends Required<Model>[Key] ? true : false;
99+
88100
/**
89101
* Resolves true if all sibling fields in the passed model are optional.
90102
*/
@@ -2238,7 +2250,11 @@ export namespace TypesaurusUtils {
22382250
export type WholeOrEmpty<Type> = Type | { [Key in keyof Type]?: undefined };
22392251

22402252
/**
2241-
* Resolves true if the strictNullChecks is set to true in the tsconfig.
2253+
* Checks if exactOptionalPropertyTypes is enabled or not
22422254
*/
2243-
export type StrictNullChecksEnabled = true; // null extends undefined ? false : true
2255+
export type ExactOptionalPropertyTypesEnabled = {
2256+
test: undefined;
2257+
} extends { test?: boolean }
2258+
? false
2259+
: true;
22442260
}

Diff for: src/tysts/batch.ts

+16-16
Original file line numberDiff line numberDiff line change
@@ -189,13 +189,13 @@ async function tysts() {
189189
}));
190190

191191
$.accounts.update(db.accounts.id("sasha"), ($) =>
192-
$.field("nested1Optional").set($.remove())
192+
$.field("nested1Optional").set($.remove()),
193193
);
194194

195195
// Single field update
196196

197197
$.accounts.update(db.accounts.id("sasha"), ($) =>
198-
$.field("name").set("Alexander")
198+
$.field("name").set("Alexander"),
199199
);
200200

201201
// Multiple fields update
@@ -208,68 +208,68 @@ async function tysts() {
208208
// Nested fields
209209

210210
$.accounts.update(db.accounts.id("sasha"), ($) =>
211-
$.field("contacts", "phone").set("+65xxxxxxxx")
211+
$.field("contacts", "phone").set("+65xxxxxxxx"),
212212
);
213213

214214
$.accounts.update(db.accounts.id("sasha"), ($) =>
215215
$.field("contacts", "phone")
216216
// @ts-expect-error - wrong type
217-
.set(6500000000)
217+
.set(6500000000),
218218
);
219219

220220
$.accounts.update(db.accounts.id("sasha"), ($) =>
221221
// @ts-expect-error - can't update because emergencyContacts can be undefined
222-
$.field("emergencyContacts", "phone").set("+65xxxxxxxx")
222+
$.field("emergencyContacts", "phone").set("+65xxxxxxxx"),
223223
);
224224

225225
$.accounts.update(db.accounts.id("sasha"), ($) =>
226226
// @ts-expect-error - emergencyContacts must have name and phone
227227
$.field("emergencyContacts").set({
228228
name: "Sasha",
229-
})
229+
}),
230230
);
231231

232232
$.accounts.update(db.accounts.id("sasha"), ($) =>
233233
$.field("emergencyContacts").set({
234234
name: "Sasha",
235235
phone: "+65xxxxxxxx",
236-
})
236+
}),
237237
);
238238

239239
// Deeply nested field corner cases
240240

241241
$.accounts.update(db.accounts.id("sasha"), ($) =>
242242
$.field("nested1Required", "nested12Required").set({
243243
hello: "Hello!",
244-
})
244+
}),
245245
);
246246

247247
$.accounts.update(db.accounts.id("sasha"), ($) =>
248248
$.field("nested1Required", "nested12Required").set({
249249
hello: "Hello!",
250250
world: "World!",
251-
})
251+
}),
252252
);
253253

254254
$.accounts.update(db.accounts.id("sasha"), ($) =>
255255
// @ts-expect-error - can't update without hello
256256
$.field("nested1Required", "nested12Required").set({
257257
world: "World!",
258-
})
258+
}),
259259
);
260260

261261
$.accounts.update(db.accounts.id("sasha"), ($) =>
262262
// @ts-expect-error - should not update because requried12 on nested1Optional is required
263263
$.field("nested1Optional", "nested12Optional").set({
264264
hello: "Hello!",
265-
})
265+
}),
266266
);
267267

268268
$.accounts.update(db.accounts.id("sasha"), ($) =>
269269
// @ts-expect-error - nested1Optional has required12, so can't update
270270
$.field("nested1Optional", "nested12Optional").set({
271271
world: "World!",
272-
})
272+
}),
273273
);
274274

275275
// Updating variable collection
@@ -289,22 +289,22 @@ async function tysts() {
289289

290290
$.content.update(contentId, ($) =>
291291
// @ts-expect-error - can't update non-shared variable model fields
292-
$.field("type").set("text")
292+
$.field("type").set("text"),
293293
);
294294

295295
// Nested fields with records
296296

297297
const postId = Math.random().toString();
298298

299299
$.accounts.update(db.accounts.id("sasha"), ($) =>
300-
$.field("counters").set({ [postId]: { likes: 5 } })
300+
$.field("counters").set({ [postId]: { likes: 5 } }),
301301
);
302302

303303
$.accounts.update(db.accounts.id("sasha"), ($) =>
304-
$.field("counters", postId).set({ likes: 5 })
304+
$.field("counters", postId).set({ likes: 5 }),
305305
);
306306

307307
$.accounts.update(db.accounts.id("sasha"), ($) =>
308-
$.field("counters", postId, "likes").set($.increment(1))
308+
$.field("counters", postId, "likes").set($.increment(1)),
309309
);
310310
}

0 commit comments

Comments
 (0)