Skip to content

Commit 0e0c6f6

Browse files
committed
feat: add types for result value from getPublic method
1 parent 255cbfa commit 0e0c6f6

6 files changed

Lines changed: 36 additions & 25 deletions

File tree

packages/node-mongo/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,10 @@ Removes private fields from a document and returns a sanitized version. Private
276276
**Example**
277277

278278
```typescript
279-
const service = db.createService<User>("users", {
280-
privateFields: ['passwordHash', 'signupToken', 'resetPasswordToken'],
279+
const USER_PRIVATE_FIELDS = ['passwordHash', 'signupToken', 'resetPasswordToken'] as const
280+
281+
const service = db.createService<User, typeof USER_PRIVATE_FIELDS>("users", {
282+
privateFields: USER_PRIVATE_FIELDS,
281283
});
282284

283285
const user = await service.findOne({ _id: userId });

packages/node-mongo/src/database.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,11 @@ class Database extends EventEmitter {
8181
await this.client.close();
8282
};
8383

84-
createService<T extends IDocument>(
84+
createService<T extends IDocument, PrivateFields extends ReadonlyArray<keyof T> = []>(
8585
collectionName: string,
86-
options?: ServiceOptions<T> | undefined,
87-
): Service<T> {
88-
return new Service<T>(
86+
options?: ServiceOptions<T, PrivateFields> | undefined,
87+
): Service<T, PrivateFields> {
88+
return new Service<T, PrivateFields>(
8989
collectionName,
9090
this as IDatabase,
9191
options,

packages/node-mongo/src/service.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import PopulateUtil from './utils/populate';
3838

3939
import { inMemoryPublisher } from './events/in-memory';
4040

41-
const defaultOptions: ServiceOptions<IDocument> = {
41+
const defaultOptions: ServiceOptions<IDocument, ReadonlyArray<keyof IDocument>> = {
4242
skipDeletedOnDocs: true,
4343
publishEvents: true,
4444
outbox: false,
@@ -50,14 +50,14 @@ const defaultOptions: ServiceOptions<IDocument> = {
5050

5151
const isDev = process.env.NODE_ENV === 'development';
5252

53-
class Service<T extends IDocument> {
53+
class Service<T extends IDocument, PrivateFields extends ReadonlyArray<keyof T> = []> {
5454
private client?: MongoClient;
5555

5656
private collection: Collection<T> | null;
5757

5858
private _collectionName: string;
5959

60-
private options: ServiceOptions<T>;
60+
private options: ServiceOptions<T, PrivateFields>;
6161

6262
private db;
6363

@@ -68,14 +68,14 @@ class Service<T extends IDocument> {
6868
constructor(
6969
collectionName: string,
7070
db: IDatabase,
71-
options: ServiceOptions<T> = {},
71+
options: ServiceOptions<T, PrivateFields> = {},
7272
) {
7373
this._collectionName = collectionName;
7474
this.db = db;
7575
this.options = {
7676
...defaultOptions,
7777
...options,
78-
};
78+
} as ServiceOptions<T, PrivateFields>;
7979
this.waitForConnection = db.waitForConnection;
8080

8181
if (this.options.outbox) {
@@ -990,9 +990,13 @@ class Service<T extends IDocument> {
990990
}
991991
};
992992

993-
getPublic = <U extends T = T>(doc: U | null): Partial<U> | null => {
994-
return omitPrivateFields<U>(doc, this.options.privateFields || []);
995-
};
993+
getPublic(doc: null): null;
994+
995+
getPublic(doc: T): Omit<T, PrivateFields[number]>;
996+
997+
getPublic(doc: T | null): Omit<T, PrivateFields[number]> | null {
998+
return omitPrivateFields<T, PrivateFields[number]>(doc, this.options.privateFields || []);
999+
}
9961000
}
9971001

9981002
export default Service;

packages/node-mongo/src/tests/service.spec.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ enum AdminPermissions {
3636
EDIT = 'edit',
3737
}
3838

39+
const USER_PRIVATE_FIELDS = ['passwordHash'] as const;
40+
3941
const userSchema = z.object({
4042
_id: z.string(),
4143
createdOn: z.date().optional(),
@@ -67,9 +69,11 @@ const companyService = database.createService<CompanyType>('companies', {
6769
schemaValidator: (obj) => companySchema.parseAsync(obj),
6870
});
6971

70-
const usersServiceWithPrivateFields = database.createService<UserType>('usersWithPrivateFields', {
72+
73+
74+
const usersServiceWithPrivateFields = database.createService<UserType, typeof USER_PRIVATE_FIELDS>('usersWithPrivateFields', {
7175
schemaValidator: (obj) => userSchema.parseAsync(obj),
72-
privateFields: ['passwordHash'],
76+
privateFields: USER_PRIVATE_FIELDS,
7377
});
7478

7579

@@ -1511,7 +1515,9 @@ describe('service.ts', () => {
15111515

15121516
const publicUser = usersServiceWithPrivateFields.getPublic(user);
15131517

1514-
(publicUser?.passwordHash === undefined).should.be.equal(true);
1518+
// @ts-expect-error Property 'passwordHash' does not exist
1519+
publicUser?.passwordHash;
1520+
15151521
publicUser?.fullName?.should.be.equal(userToInsertPayload.fullName);
15161522
publicUser?.age?.should.be.equal(userToInsertPayload.age);
15171523
publicUser?.role?.should.be.equal(userToInsertPayload.role);

packages/node-mongo/src/types/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ interface IDatabase {
118118
) => Promise<TRes>,
119119
}
120120

121-
interface ServiceOptions<T extends IDocument> {
121+
interface ServiceOptions<T extends IDocument, PrivateFields extends ReadonlyArray<keyof T> = []> {
122122
skipDeletedOnDocs?: boolean,
123123
schemaValidator?: (obj: any) => Promise<any>,
124124
publishEvents?: boolean,
@@ -128,7 +128,7 @@ interface ServiceOptions<T extends IDocument> {
128128
collectionOptions?: CollectionOptions;
129129
collectionCreateOptions?: CreateCollectionOptions;
130130
escapeRegExp?: boolean;
131-
privateFields?: Array<keyof T>;
131+
privateFields?: PrivateFields;
132132
}
133133

134134
export type UpdateFilterFunction<U> = (doc: U) => Partial<U>;

packages/node-mongo/src/utils/helpers.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,13 @@ const addUpdatedOnField = <T>(update: UpdateFilter<T>): UpdateFilter<T> => {
4040
} as unknown as UpdateFilter<T>;
4141
};
4242

43-
const omitPrivateFields = <T>(
43+
const omitPrivateFields = <T extends object, K extends keyof T>(
4444
doc: T | null,
45-
privateFields: Array<keyof T>,
46-
): Partial<T> | null => {
47-
if (!doc) return doc;
45+
privateFields: ReadonlyArray<K>,
46+
): Omit<T, K> | null => {
47+
if (!doc) return null;
4848

49-
return _.omit(doc, privateFields || []) as T;
49+
return _.omit<T, K>(doc, privateFields);
5050
};
5151

52-
5352
export { deepCompare, generateId, addUpdatedOnField, omitPrivateFields };

0 commit comments

Comments
 (0)