Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Type safe custom methods on models and items #1647

Open
4 tasks done
tranhl opened this issue Dec 21, 2023 · 2 comments
Open
4 tasks done

[FEATURE] Type safe custom methods on models and items #1647

tranhl opened this issue Dec 21, 2023 · 2 comments

Comments

@tranhl
Copy link

tranhl commented Dec 21, 2023

Summary:

Currently, there is no way to type custom methods set on models and items. See this discussion for context.

Having the ability to provide type information to Dynamoose around these methods will increase type safety while using the library.

For model methods, a straightforward solution would be to introduce a second generic type parameter to the model that allows us to strongly type the model's methods. However, for item methods, there are three options. One is to add a generic to the Item itself. The second is to instruct consumers to supply custom methods while extending the Item type. The third is to add another generic to the model factory function.

@fishcharlie Keen to hear your perspective on which opens would be best in terms of DX and feasibility. My team is pretty keen on having this as a feature, so we might make PR if we can come to an agreement on how the enhancement should look. It would also be nice to be able to ship this in the next minor v3 release so that we aren't stuck waiting until v4 lands.

Code sample:

Model Methods

import { Schema, model } from 'dynamoose';
import { Item } from 'dynamoose/dist/Item';

type UserItem = Item & {
  id: string;
  email: string;
};

type ModelMethods = {
  getByEmail: () => Promise<UserItem>; // 👈 Define type for model method
};

export const User = model<UserItem, ModelMethods>( // 👈 Pass it to model factory to apply type
  'User',
  new Schema({
    id: { type: String, hashKey: true },
    email: { type: String, required: true }
  })
);

User.methods.set('getByEmail', function () {});

Item Methods (Generic on Item type)

import { Schema, model } from 'dynamoose';
import { Item } from 'dynamoose/dist/Item';

type UserItem = Item<{
  resetPassword: () => Promise<void>; // 👈 Item method defined as part of defining item type
}> & {
  id: string;
  email: string;
};

type ModelMethods = {
  getByEmail: () => Promise<UserItem>;
};

export const User = model<UserItem, ModelMethods>(
  'User',
  new Schema({
    id: { type: String, hashKey: true },
    email: { type: String, required: true }
  })
);

User.methods.set('getByEmail', function () {});
User.methods.item.set('resetPassword', function() {});

Item Methods (Extend Item type)

import { Schema, model } from 'dynamoose';
import { Item } from 'dynamoose/dist/Item';

type UserItem = Item & {
  id: string;
  email: string;
} & {
  resetPassword: () => Promise<void>; // 👈 Same as previous example, except the method is defined as an intersection instead
};

type ModelMethods = {
  getByEmail: () => Promise<UserItem>;
};

export const User = model<UserItem, ModelMethods>(
  'User',
  new Schema({
    id: { type: String, hashKey: true },
    email: { type: String, required: true }
  })
);

User.methods.set('getByEmail', function () {});
User.methods.item.set('resetPassword', function () {});

Item Methods (Extend Item type)

import { Schema, model } from 'dynamoose';
import { Item } from 'dynamoose/dist/Item';

type UserItem = Item & {
  id: string;
  email: string;
};

type ModelMethods = {
  getByEmail: () => Promise<void>;
};

type ItemMethods = {
  resetPassword: () => boolean; // 👈 Item methods defined as separate type
}

export const User = model<UserItem, ModelMethods, ItemMethods>( // 👈 Type is then passed to model factory as generic parameter
  'User',
  new Schema(
    {
      id: { type: String, hashKey: true },
      email: { type: String, required: true }
    }
  )
);

User.methods.set('getByEmail', function() {});
User.methods.item.set('resetPassword', function() {});

General

import { User } from './models/User'

const user = await User.getByEmail('foo@bar.com') // ✅
await user.resetPassword() // ✅

const user = await User.getActiveUsers() // ❌ Type error
await user.isValidEmail() // ❌ Type error

Environment:

Operating System: MacOS
Operating System Version: Sonoma 14.2.1
Node.js version (node -v): v18.19.0
NPM version: (npm -v): 10.2.3
Dynamoose version: v3.2.1

Other:

  • I have read through the Dynamoose documentation before posting this issue
  • I have searched through the GitHub issues (including closed issues) and pull requests to ensure this feature has not already been suggested before
  • I have filled out all fields above
  • I am running the latest version of Dynamoose
@tranhl
Copy link
Author

tranhl commented Jan 16, 2024

@fishcharlie My team is still pretty keen on getting this to release, we'd be happy to make a contribution for it. Would you be able to give your 2c on the options presented for Model & Item method types?

@fishcharlie
Copy link
Member

@tranhl Let's keep it consistent with Mongoose: https://mongoosejs.com/docs/typescript/statics-and-methods.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants