feat: type payload operation calls with generics

This commit is contained in:
Elliot DeNolf
2021-11-24 14:10:52 -05:00
committed by Elliot DeNolf
parent 57cab22387
commit f258c5904e
18 changed files with 140 additions and 89 deletions

7
.prettierrc.js Normal file
View File

@@ -0,0 +1,7 @@
module.exports = {
printWidth: 85,
parser: 'typescript',
singleQuote: true,
trailingComma: 'all',
arrowParens: 'avoid',
}

View File

@@ -1,4 +1,9 @@
// auto-generated by payload
/* tslint:disable */
/**
* This file was automatically generated by json-schema-to-typescript.
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
* and run json-schema-to-typescript to regenerate this file.
*/
export interface Config {}
/**
@@ -6,6 +11,7 @@ export interface Config {}
* via the `definition` "navigation-array".
*/
export interface NavigationArray {
id: string;
array?: {
text?: string;
textarea?: string;
@@ -17,6 +23,7 @@ export interface NavigationArray {
* via the `definition` "global-with-access".
*/
export interface GlobalWithStrictAccess {
id: string;
title: string;
relationship: (string | LocalizedPost)[];
singleRelationship: string | LocalizedPost;
@@ -26,6 +33,7 @@ export interface GlobalWithStrictAccess {
* via the `definition` "localized-posts".
*/
export interface LocalizedPost {
id: string;
title: string;
summary?: string;
description: string;
@@ -49,7 +57,7 @@ export interface LocalizedPost {
}[];
id?: string;
blockName?: string;
blockType: 'richTextBlock';
blockType: "richTextBlock";
}[];
}
/**
@@ -57,6 +65,7 @@ export interface LocalizedPost {
* via the `definition` "blocks-global".
*/
export interface BlocksGlobal {
id: string;
blocks?: (
| {
author: string | PublicUser;
@@ -64,14 +73,14 @@ export interface BlocksGlobal {
color: string;
id?: string;
blockName?: string;
blockType: 'quote';
blockType: "quote";
}
| {
label: string;
url: string;
id?: string;
blockName?: string;
blockType: 'cta';
blockType: "cta";
}
)[];
}
@@ -80,6 +89,7 @@ export interface BlocksGlobal {
* via the `definition` "public-users".
*/
export interface PublicUser {
id: string;
email?: string;
resetPasswordToken?: string;
resetPasswordExpiration?: string;
@@ -94,6 +104,7 @@ export interface PublicUser {
* via the `definition` "admins".
*/
export interface Admin {
id: string;
email?: string;
resetPasswordToken?: string;
resetPasswordExpiration?: string;
@@ -102,7 +113,7 @@ export interface Admin {
apiKeyIndex?: string;
loginAttempts?: number;
lockUntil?: string;
roles: ('admin' | 'editor' | 'moderator' | 'user' | 'viewer')[];
roles: ("admin" | "editor" | "moderator" | "user" | "viewer")[];
publicUser?: (string | PublicUser)[];
}
/**
@@ -110,15 +121,16 @@ export interface Admin {
* via the `definition` "all-fields".
*/
export interface AllFields {
id: string;
text: string;
descriptionText?: string;
descriptionFunction?: string;
image?: string | Media;
select: 'option-1' | 'option-2' | 'option-3' | 'option-4';
selectMany: ('option-1' | 'option-2' | 'option-3' | 'option-4')[];
select: "option-1" | "option-2" | "option-3" | "option-4";
selectMany: ("option-1" | "option-2" | "option-3" | "option-4")[];
dayOnlyDateFieldExample: string;
timeOnlyDateFieldExample?: string;
radioGroupExample: 'option-1' | 'option-2' | 'option-3';
radioGroupExample: "option-1" | "option-2" | "option-3";
email?: string;
number?: number;
group?: {
@@ -137,13 +149,13 @@ export interface AllFields {
testEmail: string;
id?: string;
blockName?: string;
blockType: 'email';
blockType: "email";
}
| {
testNumber: number;
id?: string;
blockName?: string;
blockType: 'number';
blockType: "number";
}
| {
author: string | PublicUser;
@@ -151,14 +163,14 @@ export interface AllFields {
color: string;
id?: string;
blockName?: string;
blockType: 'quote';
blockType: "quote";
}
| {
label: string;
url: string;
id?: string;
blockName?: string;
blockType: 'cta';
blockType: "cta";
}
)[];
relationship?: string | Conditions;
@@ -166,11 +178,11 @@ export interface AllFields {
relationshipMultipleCollections?:
| {
value: string | LocalizedPost;
relationTo: 'localized-posts';
relationTo: "localized-posts";
}
| {
value: string | Conditions;
relationTo: 'conditions';
relationTo: "conditions";
};
textarea?: string;
richText: {
@@ -185,6 +197,7 @@ export interface AllFields {
* via the `definition` "media".
*/
export interface Media {
id: string;
url?: string;
filename?: string;
mimeType?: string;
@@ -233,6 +246,7 @@ export interface Media {
* via the `definition` "conditions".
*/
export interface Conditions {
id: string;
title: string;
enableTest?: boolean;
number?: number;
@@ -244,13 +258,13 @@ export interface Conditions {
testEmail: string;
id?: string;
blockName?: string;
blockType: 'email';
blockType: "email";
}
| {
testNumber: number;
id?: string;
blockName?: string;
blockType: 'number';
blockType: "number";
}
| {
author: string | PublicUser;
@@ -258,14 +272,14 @@ export interface Conditions {
color: string;
id?: string;
blockName?: string;
blockType: 'quote';
blockType: "quote";
}
| {
label: string;
url: string;
id?: string;
blockName?: string;
blockType: 'cta';
blockType: "cta";
}
)[];
}
@@ -274,6 +288,7 @@ export interface Conditions {
* via the `definition` "auto-label".
*/
export interface AutoLabel {
id: string;
autoLabelField?: string;
noLabel?: string;
labelOverride?: string;
@@ -282,13 +297,13 @@ export interface AutoLabel {
testNumber?: number;
id?: string;
blockName?: string;
blockType: 'number';
blockType: "number";
}[];
noLabelBlock?: {
testNumber?: number;
id?: string;
blockName?: string;
blockType: 'number';
blockType: "number";
}[];
items?: {
itemName?: string;
@@ -304,6 +319,7 @@ export interface AutoLabel {
* via the `definition` "code".
*/
export interface Code {
id: string;
code: string;
}
/**
@@ -311,6 +327,7 @@ export interface Code {
* via the `definition` "custom-components".
*/
export interface CustomComponent {
id: string;
title: string;
description: string;
componentDescription?: string;
@@ -329,7 +346,7 @@ export interface CustomComponent {
* via the `definition` "custom-id".
*/
export interface CustomID {
id?: number;
id: number;
name: string;
}
/**
@@ -337,11 +354,12 @@ export interface CustomID {
* via the `definition` "files".
*/
export interface File {
id: string;
url?: string;
filename?: string;
mimeType?: string;
filesize?: number;
type: 'Type 1' | 'Type 2' | 'Type 3';
type: "Type 1" | "Type 2" | "Type 3";
owner: string | Admin;
}
/**
@@ -349,11 +367,12 @@ export interface File {
* via the `definition` "default-values".
*/
export interface DefaultValueTest {
id: string;
text?: string;
image?: string | Media;
select?: 'option-1' | 'option-2' | 'option-3' | 'option-4';
selectMany?: ('option-1' | 'option-2' | 'option-3' | 'option-4')[];
radioGroupExample?: 'option-1' | 'option-2' | 'option-3';
select?: "option-1" | "option-2" | "option-3" | "option-4";
selectMany?: ("option-1" | "option-2" | "option-3" | "option-4")[];
radioGroupExample?: "option-1" | "option-2" | "option-3";
email?: string;
number?: number;
group?: {
@@ -372,13 +391,13 @@ export interface DefaultValueTest {
testEmail: string;
id?: string;
blockName?: string;
blockType: 'email';
blockType: "email";
}
| {
testNumber: number;
id?: string;
blockName?: string;
blockType: 'number';
blockType: "number";
}
| {
author: string | PublicUser;
@@ -386,14 +405,14 @@ export interface DefaultValueTest {
color: string;
id?: string;
blockName?: string;
blockType: 'quote';
blockType: "quote";
}
| {
label: string;
url: string;
id?: string;
blockName?: string;
blockType: 'cta';
blockType: "cta";
}
)[];
relationship?: string | Conditions;
@@ -401,11 +420,11 @@ export interface DefaultValueTest {
relationshipMultipleCollections?:
| {
value: string | LocalizedPost;
relationTo: 'localized-posts';
relationTo: "localized-posts";
}
| {
value: string | Conditions;
relationTo: 'conditions';
relationTo: "conditions";
};
textarea?: string;
slug?: string;
@@ -419,18 +438,19 @@ export interface DefaultValueTest {
* via the `definition` "blocks".
*/
export interface Blocks {
id: string;
layout: (
| {
testEmail: string;
id?: string;
blockName?: string;
blockType: 'email';
blockType: "email";
}
| {
testNumber: number;
id?: string;
blockName?: string;
blockType: 'number';
blockType: "number";
}
| {
author: string | PublicUser;
@@ -438,14 +458,14 @@ export interface Blocks {
color: string;
id?: string;
blockName?: string;
blockType: 'quote';
blockType: "quote";
}
| {
label: string;
url: string;
id?: string;
blockName?: string;
blockType: 'cta';
blockType: "cta";
}
)[];
nonLocalizedLayout: (
@@ -453,13 +473,13 @@ export interface Blocks {
testEmail: string;
id?: string;
blockName?: string;
blockType: 'email';
blockType: "email";
}
| {
testNumber: number;
id?: string;
blockName?: string;
blockType: 'number';
blockType: "number";
}
| {
author: string | PublicUser;
@@ -467,14 +487,14 @@ export interface Blocks {
color: string;
id?: string;
blockName?: string;
blockType: 'quote';
blockType: "quote";
}
| {
label: string;
url: string;
id?: string;
blockName?: string;
blockType: 'cta';
blockType: "cta";
}
)[];
}
@@ -483,6 +503,7 @@ export interface Blocks {
* via the `definition` "hidden-fields".
*/
export interface HiddenFields {
id: string;
title: string;
hiddenAdmin: string;
hiddenAPI: string;
@@ -492,6 +513,7 @@ export interface HiddenFields {
* via the `definition` "hooks".
*/
export interface Hook {
id: string;
title: string;
description: string;
}
@@ -500,6 +522,7 @@ export interface Hook {
* via the `definition` "localized-arrays".
*/
export interface LocalizedArray {
id: string;
array: {
allowPublicReadability?: boolean;
arrayText1: string;
@@ -513,6 +536,7 @@ export interface LocalizedArray {
* via the `definition` "local-operations".
*/
export interface LocalOperation {
id: string;
title: string;
}
/**
@@ -520,6 +544,7 @@ export interface LocalOperation {
* via the `definition` "nested-arrays".
*/
export interface NestedArray {
id: string;
array: {
parentIdentifier: string;
nestedArray: {
@@ -538,6 +563,7 @@ export interface NestedArray {
* via the `definition` "previewable-post".
*/
export interface PreviewablePost {
id: string;
title: string;
}
/**
@@ -545,25 +571,26 @@ export interface PreviewablePost {
* via the `definition` "relationship-a".
*/
export interface RelationshipA {
id: string;
post?: string | RelationshipB;
LocalizedPost?: (string | LocalizedPost)[];
postLocalizedMultiple?: (
| {
value: string | LocalizedPost;
relationTo: 'localized-posts';
relationTo: "localized-posts";
}
| {
value: string | AllFields;
relationTo: 'all-fields';
relationTo: "all-fields";
}
| {
value: number | CustomID;
relationTo: 'custom-id';
relationTo: "custom-id";
}
)[];
postManyRelationships?: {
value: string | RelationshipB;
relationTo: 'relationship-b';
relationTo: "relationship-b";
};
postMaxDepth?: string | RelationshipB;
customID?: (number | CustomID)[];
@@ -573,25 +600,26 @@ export interface RelationshipA {
* via the `definition` "relationship-b".
*/
export interface RelationshipB {
id: string;
title?: string;
post?: (string | RelationshipA)[];
postManyRelationships?:
| {
value: string | RelationshipA;
relationTo: 'relationship-a';
relationTo: "relationship-a";
}
| {
value: string | Media;
relationTo: 'media';
relationTo: "media";
};
localizedPosts?: (
| {
value: string | LocalizedPost;
relationTo: 'localized-posts';
relationTo: "localized-posts";
}
| {
value: string | PreviewablePost;
relationTo: 'previewable-post';
relationTo: "previewable-post";
}
)[];
strictAccess?: string | StrictAccess;
@@ -601,6 +629,7 @@ export interface RelationshipB {
* via the `definition` "strict-access".
*/
export interface StrictAccess {
id: string;
address: string;
city: string;
state: string;
@@ -611,6 +640,7 @@ export interface StrictAccess {
* via the `definition` "rich-text".
*/
export interface RichText {
id: string;
defaultRichText: {
[k: string]: unknown;
}[];
@@ -623,16 +653,18 @@ export interface RichText {
* via the `definition` "select".
*/
export interface Select {
Select: 'one' | 'two' | 'three';
SelectHasMany: ('one' | 'two' | 'three')[];
SelectJustStrings: ('blue' | 'green' | 'yellow')[];
Radio: 'one' | 'two' | 'three';
id: string;
Select: "one" | "two" | "three";
SelectHasMany: ("one" | "two" | "three")[];
SelectJustStrings: ("blue" | "green" | "yellow")[];
Radio: "one" | "two" | "three";
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "validations".
*/
export interface Validation {
id: string;
text: string;
lessThan10: number;
greaterThan10LessThan50: number;
@@ -650,6 +682,7 @@ export interface Validation {
* via the `definition` "uniques".
*/
export interface Unique {
id: string;
title: string;
description?: string;
}
@@ -658,6 +691,7 @@ export interface Unique {
* via the `definition` "unstored-media".
*/
export interface UnstoredMedia {
id: string;
url?: string;
filename?: string;
mimeType?: string;
@@ -681,6 +715,7 @@ export interface UnstoredMedia {
* via the `definition` "geolocation".
*/
export interface Geolocation {
id: string;
location?: [number, number];
localizedPoint?: [number, number];
}

View File

@@ -38,7 +38,7 @@ export default buildConfig({
cookiePrefix: 'payload',
serverURL: 'http://localhost:3000',
typescript: {
outputFile: path.resolve(__dirname, './generated-types.ts'),
outputFile: path.resolve(__dirname, './payload-types.ts'),
},
admin: {
user: 'admins',

View File

@@ -63,7 +63,7 @@ const RelationshipField: React.FC<Props> = (props) => {
const response = await fetch(`${serverURL}${api}/${relation}?limit=${maxResultsPerRequest}&page=${lastLoadedPageToUse}&depth=0${searchParam}`);
if (response.ok) {
const data: PaginatedDocs = await response.json();
const data: PaginatedDocs<any> = await response.json();
if (data.docs.length > 0) {
resultsFetched += data.docs.length;
addOptions(data, relation);

View File

@@ -20,7 +20,7 @@ type CLEAR = {
type ADD = {
type: 'ADD'
data: PaginatedDocs
data: PaginatedDocs<any>
relation: string
hasMultipleRelations: boolean
collection: SanitizedCollectionConfig

View File

@@ -106,7 +106,7 @@ const Relationship: React.FC<Props> = (props) => {
const response = await fetch(`${serverURL}${api}/${relation}?limit=${maxResultsPerRequest}&page=${lastLoadedPageToUse}&depth=0${searchParam}`);
if (response.ok) {
const data: PaginatedDocs = await response.json();
const data: PaginatedDocs<any> = await response.json();
if (data.docs.length > 0) {
resultsFetched += data.docs.length;
addOptions(data, relation);

View File

@@ -19,7 +19,7 @@ type CLEAR = {
type ADD = {
type: 'ADD'
data: PaginatedDocs
data: PaginatedDocs<any>
relation: string
hasMultipleRelations: boolean
collection: SanitizedCollectionConfig

View File

@@ -3,7 +3,7 @@ import { Column } from '../../../elements/Table/types';
export type Props = {
collection: SanitizedCollectionConfig
data: PaginatedDocs
data: PaginatedDocs<any>
newDocumentURL: string
setListControls: (controls: unknown) => void
setSort: (sort: string) => void

View File

@@ -157,8 +157,8 @@ export type AuthCollection = {
config: SanitizedCollectionConfig;
}
export type PaginatedDocs = {
docs: any[]
export type PaginatedDocs<T extends TypeWithID> = {
docs: T[]
totalDocs: number
limit: number
totalPages: number
@@ -169,3 +169,7 @@ export type PaginatedDocs = {
prevPage: number | null
nextPage: number | null
}
export type TypeWithID = {
id: string | number
}

View File

@@ -2,7 +2,7 @@ import { Where } from '../../types';
import { PayloadRequest } from '../../express/types';
import executeAccess from '../../auth/executeAccess';
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
import { Collection, PaginatedDocs } from '../config/types';
import { Collection, TypeWithID, PaginatedDocs } from '../config/types';
import { hasWhereAccessResult } from '../../auth/types';
import flattenWhereConstraints from '../../utilities/flattenWhereConstraints';
@@ -18,7 +18,7 @@ export type Arguments = {
showHiddenFields?: boolean
}
async function find(incomingArgs: Arguments): Promise<PaginatedDocs> {
async function find<T extends TypeWithID>(incomingArgs: Arguments): Promise<PaginatedDocs<T>> {
let args = incomingArgs;
// /////////////////////////////////////
@@ -145,7 +145,7 @@ async function find(incomingArgs: Arguments): Promise<PaginatedDocs> {
return docRef;
})),
} as PaginatedDocs;
} as PaginatedDocs<T>;
// /////////////////////////////////////
// afterRead - Fields
@@ -195,7 +195,7 @@ async function find(incomingArgs: Arguments): Promise<PaginatedDocs> {
result = {
...result,
docs: result.docs.map((doc) => sanitizeInternalFields(doc)),
docs: result.docs.map((doc) => sanitizeInternalFields<T>(doc)),
};
return result;

View File

@@ -1,11 +1,11 @@
/* eslint-disable no-underscore-dangle */
import memoize from 'micro-memoize';
import { PayloadRequest } from '../../express/types';
import { Collection } from '../config/types';
import { Collection, TypeWithID } from '../config/types';
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields';
import { Forbidden, NotFound } from '../../errors';
import executeAccess from '../../auth/executeAccess';
import { Document, Where } from '../../types';
import { Where } from '../../types';
import { hasWhereAccessResult } from '../../auth/types';
export type Arguments = {
@@ -19,7 +19,7 @@ export type Arguments = {
depth?: number
}
async function findByID(incomingArgs: Arguments): Promise<Document> {
async function findByID<T extends TypeWithID>(incomingArgs: Arguments): Promise<T> {
let args = incomingArgs;
// /////////////////////////////////////

View File

@@ -1,3 +1,4 @@
import { TypeWithID } from '../../config/types';
import { Document } from '../../../types';
export type Options = {
@@ -11,7 +12,7 @@ export type Options = {
showHiddenFields?: boolean
}
export default async function localDelete(options: Options): Promise<Document> {
export default async function localDelete<T extends TypeWithID>(options: Options): Promise<T> {
const {
collection: collectionSlug,
depth,

View File

@@ -1,4 +1,4 @@
import { PaginatedDocs } from '../../config/types';
import { PaginatedDocs, TypeWithID } from '../../config/types';
import { Document, Where } from '../../../types';
export type Options = {
@@ -15,7 +15,7 @@ export type Options = {
where?: Where
}
export default async function find(options: Options): Promise<PaginatedDocs> {
export default async function find<T extends TypeWithID>(options: Options): Promise<PaginatedDocs<T>> {
const {
collection: collectionSlug,
depth,

View File

@@ -1,3 +1,4 @@
import { TypeWithID } from '../../config/types';
import { PayloadRequest } from '../../../express/types';
import { Document } from '../../../types';
@@ -14,7 +15,7 @@ export type Options = {
req?: PayloadRequest
}
export default async function findByID(options: Options): Promise<Document> {
export default async function findByID<T extends TypeWithID>(options: Options): Promise<T> {
const {
collection: collectionSlug,
depth,

View File

@@ -1,3 +1,4 @@
import { TypeWithID } from '../../config/types';
import { Document } from '../../../types';
import getFileByPath from '../../../uploads/getFileByPath';
@@ -15,7 +16,7 @@ export type Options = {
overwriteExistingFiles?: boolean
}
export default async function update(options: Options): Promise<Document> {
export default async function update<T extends TypeWithID>(options: Options): Promise<T> {
const {
collection: collectionSlug,
depth,

View File

@@ -1,9 +1,9 @@
import { Response, NextFunction } from 'express';
import httpStatus from 'http-status';
import { PayloadRequest } from '../../express/types';
import { PaginatedDocs } from '../config/types';
import { PaginatedDocs, TypeWithID } from '../config/types';
export default async function find(req: PayloadRequest, res: Response, next: NextFunction): Promise<Response<PaginatedDocs> | void> {
export default async function find<T extends TypeWithID>(req: PayloadRequest, res: Response, next: NextFunction): Promise<Response<PaginatedDocs<T>> | void> {
try {
let page;

View File

@@ -1,14 +1,14 @@
import express, { Express, Router } from 'express';
import crypto from 'crypto';
import { Document } from 'mongoose';
import {
TypeWithID,
Collection, PaginatedDocs,
} from './collections/config/types';
import {
SanitizedConfig,
EmailOptions,
InitOptions,
} from './config/types';
import {
Collection, PaginatedDocs,
} from './collections/config/types';
import Logger from './utilities/logger';
import bindOperations from './init/bindOperations';
import bindRequestHandlers, { RequestHandlers } from './init/bindRequestHandlers';
@@ -204,7 +204,7 @@ export class Payload {
* @param options
* @returns created document
*/
create = async (options: CreateOptions): Promise<Document> => {
create = async <T>(options: CreateOptions): Promise<T> => {
let { create } = localOperations;
create = create.bind(this);
return create(options);
@@ -215,19 +215,19 @@ export class Payload {
* @param options
* @returns documents satisfying query
*/
find = async (options: FindOptions): Promise<PaginatedDocs> => {
find = async <T extends TypeWithID>(options: FindOptions): Promise<PaginatedDocs<T>> => {
let { find } = localOperations;
find = find.bind(this);
return find(options);
}
findGlobal = async (options): Promise<any> => {
findGlobal = async <T>(options): Promise<T> => {
let { findOne } = localGlobalOperations;
findOne = findOne.bind(this);
return findOne(options);
}
updateGlobal = async (options): Promise<any> => {
updateGlobal = async <T>(options): Promise<T> => {
let { update } = localGlobalOperations;
update = update.bind(this);
return update(options);
@@ -238,10 +238,10 @@ export class Payload {
* @param options
* @returns document with specified ID
*/
findByID = async (options: FindByIDOptions): Promise<Document> => {
findByID = async <T extends TypeWithID>(options: FindByIDOptions): Promise<T> => {
let { findByID } = localOperations;
findByID = findByID.bind(this);
return findByID(options);
return findByID<T>(options);
}
/**
@@ -249,16 +249,16 @@ export class Payload {
* @param options
* @returns Updated document
*/
update = async (options: UpdateOptions): Promise<Document> => {
update = async <T extends TypeWithID>(options: UpdateOptions): Promise<T> => {
let { update } = localOperations;
update = update.bind(this);
return update(options);
return update<T>(options);
}
delete = async (options: DeleteOptions): Promise<Document> => {
delete = async <T extends TypeWithID>(options: DeleteOptions): Promise<T> => {
let { localDelete: deleteOperation } = localOperations;
deleteOperation = deleteOperation.bind(this);
return deleteOperation(options);
return deleteOperation<T>(options);
}
login = async (options): Promise<any> => {

View File

@@ -1,6 +1,8 @@
import { TypeWithID } from '../collections/config/types';
const internalFields = ['__v', 'salt', 'hash'];
const sanitizeInternalFields = (incomingDoc) => Object.entries(incomingDoc).reduce((newDoc, [key, val]) => {
const sanitizeInternalFields = <T extends TypeWithID>(incomingDoc) => Object.entries(incomingDoc).reduce((newDoc, [key, val]): T => {
if (key === '_id') {
return {
...newDoc,
@@ -16,6 +18,6 @@ const sanitizeInternalFields = (incomingDoc) => Object.entries(incomingDoc).redu
...newDoc,
[key]: val,
};
}, {});
}, {} as T);
export default sanitizeInternalFields;