From f13f9d54b956889ef93fbf68fade03cad726372c Mon Sep 17 00:00:00 2001 From: Long Tran Date: Tue, 6 Feb 2024 09:16:32 +1100 Subject: [PATCH] test: fail on unknown typed method access --- packages/dynamoose/lib/General.ts | 4 ++-- packages/dynamoose/lib/Item.ts | 6 +++--- packages/dynamoose/lib/Model/index.ts | 2 +- packages/dynamoose/lib/ModelStore.ts | 2 +- packages/dynamoose/lib/Schema.ts | 4 ++-- packages/dynamoose/lib/Serializer.ts | 4 ++-- packages/dynamoose/lib/index.ts | 2 +- .../lib/utils/dynamoose/returnModel.ts | 2 +- packages/dynamoose/test/types/Item.ts | 18 ++++++++---------- packages/dynamoose/test/types/ItemMethods.ts | 12 ++++++++++++ packages/dynamoose/test/types/Model.ts | 11 +++++++---- packages/dynamoose/test/types/ModelMethods.ts | 6 ++++++ 12 files changed, 46 insertions(+), 27 deletions(-) create mode 100644 packages/dynamoose/test/types/ItemMethods.ts create mode 100644 packages/dynamoose/test/types/ModelMethods.ts diff --git a/packages/dynamoose/lib/General.ts b/packages/dynamoose/lib/General.ts index 09142224..f58a6902 100644 --- a/packages/dynamoose/lib/General.ts +++ b/packages/dynamoose/lib/General.ts @@ -15,7 +15,7 @@ export type KeyObject = {[attribute: string]: string | number}; // An item representing a DynamoDB key export type InputKey = string | number | KeyObject; -interface ModelItemConstructor { +interface ModelItemConstructor> { /** * In order to create a new item you just pass in your object into an instance of your model. * @@ -33,7 +33,7 @@ interface ModelItemConstructor { new (object: Flatten>): T & U; Model: Model; } -export type ModelType = T & Model & ModelItemConstructor; +export type ModelType> = T & Model & ModelItemConstructor; // This represents an item array. This is used for the output of functions such as `scan`, `query`, and `batchGet`. These functions can extend this property to add additional properties or functions. However this represents the shared properties/functions for all item arrays. export interface ItemArray extends Array { diff --git a/packages/dynamoose/lib/Item.ts b/packages/dynamoose/lib/Item.ts index ca21bf58..d9e81d26 100644 --- a/packages/dynamoose/lib/Item.ts +++ b/packages/dynamoose/lib/Item.ts @@ -30,9 +30,9 @@ export interface ItemSettings { type?: "fromDynamo" | "toDynamo"; } -export interface ItemMethods { - [key: string]: Function; -} +export type ItemMethods = { + [K in keyof T]: Function +}; interface ItemInternalProperties { originalObject: any; diff --git a/packages/dynamoose/lib/Model/index.ts b/packages/dynamoose/lib/Model/index.ts index fd5d39a5..c9a5cef5 100644 --- a/packages/dynamoose/lib/Model/index.ts +++ b/packages/dynamoose/lib/Model/index.ts @@ -131,7 +131,7 @@ interface ModelInternalProperties { } // Model represents a single entity (ex. User, Movie, Video, Order) -export class Model extends InternalPropertiesClass { +export class Model = {}> extends InternalPropertiesClass { /** * This method is the basic entry point for creating a model in Dynamoose. When you call this method a new model is created, and it returns an item initializer that you can use to create instances of the given model. * diff --git a/packages/dynamoose/lib/ModelStore.ts b/packages/dynamoose/lib/ModelStore.ts index 9b71905c..09044275 100644 --- a/packages/dynamoose/lib/ModelStore.ts +++ b/packages/dynamoose/lib/ModelStore.ts @@ -6,7 +6,7 @@ const {internalProperties} = Internal.General; let models: {[name: string]: Model} = {}; -const returnObject = (input: Model | string): Model | never => { +const returnObject = >(input: Model | string): Model | never => { if (input instanceof Model) { models[input.name] = input; return input; diff --git a/packages/dynamoose/lib/Schema.ts b/packages/dynamoose/lib/Schema.ts index 00a8bfa3..37b6f187 100644 --- a/packages/dynamoose/lib/Schema.ts +++ b/packages/dynamoose/lib/Schema.ts @@ -273,7 +273,7 @@ const attributeTypes: (DynamoDBTypeResult | DynamoDBSetTypeResult)[] = utils.arr type GeneralValueType = string | boolean | number | Buffer | Date; export type ValueType = GeneralValueType | {[key: string]: ValueType} | ValueType[]; -type AttributeType = string | StringConstructor | BooleanConstructor | NumberConstructor | typeof Buffer | DateConstructor | ObjectConstructor | ArrayConstructor | SetConstructor | symbol | Schema | ModelType; +type AttributeType = string | StringConstructor | BooleanConstructor | NumberConstructor | typeof Buffer | DateConstructor | ObjectConstructor | ArrayConstructor | SetConstructor | symbol | Schema | ModelType; export interface TimestampObject { createdAt?: string | string[] | SchemaDefinition; @@ -324,7 +324,7 @@ interface IndexDefinition { } interface AttributeDefinitionTypeSettings { storage?: "milliseconds" | "seconds" | "iso"; - model?: ModelType; + model?: ModelType>; attributes?: string[]; separator?: string; value?: string | boolean | number; diff --git a/packages/dynamoose/lib/Serializer.ts b/packages/dynamoose/lib/Serializer.ts index ccdcf3b4..e0d0ad4f 100644 --- a/packages/dynamoose/lib/Serializer.ts +++ b/packages/dynamoose/lib/Serializer.ts @@ -16,7 +16,7 @@ interface SerializerInternalProperties { serializers: {[key: string]: SerializerOptions}; defaultSerializer?: string; - serializeMany: (itemsArray: ModelType[], nameOrOptions: SerializerOptions | string) => ObjectType[]; + serializeMany: (itemsArray: ModelType[], nameOrOptions: SerializerOptions | string) => ObjectType[]; serialize: (item: ObjectType, nameOrOptions: SerializerOptions | string) => ObjectType; } @@ -32,7 +32,7 @@ export class Serializer extends InternalPropertiesClass ({...original}) } }, - "serializeMany": (itemsArray: ModelType[], nameOrOptions: SerializerOptions | string): ObjectType[] => { + "serializeMany": (itemsArray: ModelType>[], nameOrOptions: SerializerOptions | string): ObjectType[] => { if (!itemsArray || !Array.isArray(itemsArray)) { throw new CustomError.InvalidParameter("itemsArray must be an array of item objects"); } diff --git a/packages/dynamoose/lib/index.ts b/packages/dynamoose/lib/index.ts index d468b9fd..6ffc6110 100644 --- a/packages/dynamoose/lib/index.ts +++ b/packages/dynamoose/lib/index.ts @@ -13,7 +13,7 @@ import {Instance} from "./Instance"; import {custom as TableDefaults} from "./Table/defaults"; import returnModel from "./utils/dynamoose/returnModel"; -const model = (name: string, schema?: Schema | SchemaDefinition | (Schema | SchemaDefinition)[], options?: ModelTableOptions): ModelType => { +const model = = {}>(name: string, schema?: Schema | SchemaDefinition | (Schema | SchemaDefinition)[], options?: ModelTableOptions): ModelType => { let model: Model; let storedSchema: Model; if (name) { diff --git a/packages/dynamoose/lib/utils/dynamoose/returnModel.ts b/packages/dynamoose/lib/utils/dynamoose/returnModel.ts index 050ab643..3d41a8fc 100644 --- a/packages/dynamoose/lib/utils/dynamoose/returnModel.ts +++ b/packages/dynamoose/lib/utils/dynamoose/returnModel.ts @@ -3,7 +3,7 @@ import {ModelType} from "../../General"; import {AnyItem, Item, ItemMethods} from "../../Item"; import {Model} from "../../Model"; -export default (model: Model): ModelType => { +export default = {}>(model: Model): ModelType => { const returnObject: any = model.Item; const keys = utils.array_flatten([ Object.keys(model), diff --git a/packages/dynamoose/test/types/Item.ts b/packages/dynamoose/test/types/Item.ts index 0f8dfefa..b41e3620 100644 --- a/packages/dynamoose/test/types/Item.ts +++ b/packages/dynamoose/test/types/Item.ts @@ -1,15 +1,13 @@ import {UserTypedModel} from "./Model"; -const user = new UserTypedModel({"id": "1", "name": "Jane", "age": 30}); +const typedUser = new UserTypedModel({"id": "1", "name": "Jane", "age": 30}); -const shouldPassSave = user.save(); -const shouldPassSaveWithReturnRequest = user.save({"return": "request"}); -const shouldPassSaveWithReturnItem = user.save({"return": "item"}); -const shouldPassSaveCallback = user.save(() => {}); -const shouldPassSaveWithReturnRequestCallback = user.save({"return": "request"}, () => {}); -const shouldPassSaveWithReturnItemCallback = user.save({"return": "item"}, () => {}); +const shouldPassSave = typedUser.save(); +const shouldPassSaveWithReturnRequest = typedUser.save({"return": "request"}); +const shouldPassSaveWithReturnItem = typedUser.save({"return": "item"}); +const shouldPassSaveCallback = typedUser.save(() => {}); +const shouldPassSaveWithReturnRequestCallback = typedUser.save({"return": "request"}, () => {}); +const shouldPassSaveWithReturnItemCallback = typedUser.save({"return": "item"}, () => {}); // @ts-expect-error -const shouldFailWithInvalidReturnType = user.save({"return": "invalid-return-type"}); - -const shouldPassCustomMethodAccess = user.resetPassword(); +const shouldFailWithInvalidReturnType = typedUser.save({"return": "invalid-return-type"}); diff --git a/packages/dynamoose/test/types/ItemMethods.ts b/packages/dynamoose/test/types/ItemMethods.ts new file mode 100644 index 00000000..9dcbbe44 --- /dev/null +++ b/packages/dynamoose/test/types/ItemMethods.ts @@ -0,0 +1,12 @@ +/* eslint @typescript-eslint/no-unused-vars: 0 */ + +import {UserModel, UserTypedModel} from "./Model"; + +const user = new UserModel({"id": "1", "name": "Jane", "age": 30}); +const typedUser = new UserTypedModel({"id": "1", "name": "Jane", "age": 30}); + +const shouldPassUntypedCustomMethodAccess = user.resetPassword(); + +const shouldPassCustomMethodAccess = typedUser.resetPassword(); +// @ts-expect-error +const shouldFailUnknownCustomMethodAccess = typedUser.updateEmail(); diff --git a/packages/dynamoose/test/types/Model.ts b/packages/dynamoose/test/types/Model.ts index aa4ee38a..727f5d92 100644 --- a/packages/dynamoose/test/types/Model.ts +++ b/packages/dynamoose/test/types/Model.ts @@ -74,10 +74,14 @@ const shouldFailWithInvalidConditionTransaction = model.transaction.condition(0, // Typed Models -export interface UserMethods extends ItemMethods { +export interface UserMethods { resetPassword: () => Promise; } +interface InvalidMethods { + updateEmail: string; +} + export interface User extends Item { id: string; name: string; @@ -99,6 +103,8 @@ export const UserTypedModel = dynamoose.model( "User", userSchema ); +// @ts-expect-error +const shouldFailWithInvalidMethodTypes = dynamoose.model("User", userSchema); export const UserModel = dynamoose.model( "User", @@ -116,6 +122,3 @@ UserTypedModel.update({"id": "foo"}, { UserTypedModel.update({"id": "foo"}, { "$REMOVE":{"age":null} }); - -// @ts-expect-error -const shouldFailItemCustomMethodAccess = UserTypedModel.resetPassword(); diff --git a/packages/dynamoose/test/types/ModelMethods.ts b/packages/dynamoose/test/types/ModelMethods.ts new file mode 100644 index 00000000..8f9a6859 --- /dev/null +++ b/packages/dynamoose/test/types/ModelMethods.ts @@ -0,0 +1,6 @@ +/* eslint @typescript-eslint/no-unused-vars: 0 */ + +import {UserTypedModel} from "./Model"; + +// @ts-expect-error +const shouldFailItemCustomMethodAccess = UserTypedModel.resetPassword();