Strongly typing Parse Classes with Typescript

I am in the process of converting a Parse Server codebase from Javascript to Typescript – learning Typescript as I go. I would welcome any insight on best practices.

I’m wondering what the best way to approach strongly typing Parse Classes in my codebase? (are there any good reference Parse Server projects written in Typescript?)

Currently, I have a classes.ts file in my root src directory that holds all of my Class and Attribute definitions like so:

import { Attributes } from 'parse'

export interface ItemAttributes extends Attributes {
  color: string
  size: number
  enabled: boolean
}

export class Item extends Object<ItemAttributes> {
  constructor (data?: Partial<ItemAttributes>) {
    super('Item', data as ItemAttributes)
  }
}

Parse.Object.registerSubclass('Item', Item)

Has anyone found success with different folder structures than the above? It is getting a bit unwieldy with all of my classes in the one root file.

Also, I am looking to type the getter and setter arguments that currently exist as strings (for example: myItem.get('color')).

I am looking for a solution that doesn’t require an additional enum definition. I find it tedious to have to maintain multiple definitions when updating class attributes.

export enum ItemKeys {
  color = 'color'
  size = 'size'
  enabled = 'enabled'
}

// Usage:
myItem.get(ItemKeys.color)

Any feedback would be appreciated.

@sadortun might have some feedback here.

We have also discussed a typescript example package but no progress just yet.

1 Like

Hi!

We went though this in the past years and we wrote a utility library that allows us to wrap the non-ts ParseJS code into OOP TS code that supports getters and setters and many more useful utilities.

Library is stable and we use it actively in about 10 distinct projects., but there is no explicit docs written (readme,…)

Most of the package also have good code coverage and tests.

It allow us to do 99% of operations using Typescript and IDE are intelligent enough that they will also provide type hints and autocomplete which is so awesome!

const q = Query.create(AssetPrice);

    q.equalTo('symbol', symbol);
    q.descending('recordedAt');
    q.limit(1);

    const ap = await q.find();

In this example, the 'symbol' and 'recordrdAt' can be autocomplete and have type hints that are enforced by Typescript.

You can see how to use it in

import { BaseObject, Query, QueryUtils } from '@goplan-finance/utils';
import { AssetSymbol } from './AssetSymbol';

export class AssetPrice extends BaseObject {
  static className = 'AssetPrice';

  constructor() {
    super(AssetPrice.className);
  }

  get symbol(): AssetSymbol {
    return this.get('symbol');
  }

  set symbol(value: AssetSymbol) {
    this.set('symbol', value);
  }

  get recordedAt(): Date {
    return this.get('recordedAt');
  }
1 Like

This is very helpful, thank you!

This might also help, it allows you to access data via obj.key rather than obj.get("key").


const _internalFields = Object.freeze([
   'objectId',
   'id',
   'className',
   'attributes',
   'createdAt',
   'updatedAt',
   'then',
 ]);
 const proxyHandler = {
   get(target, key, receiver) {
     const value = target[key];
     const reflector = Reflect.get(target, key, receiver);
     if (
       typeof value === 'function' ||
       key.toString().charAt(0) === '_' ||
       _internalFields.includes(key.toString())
     ) {
       return reflector;
     }
     return receiver.get(key) ?? reflector;
   },

   set(target, key, value, receiver) {
     const current = target[key];
     if (
       typeof current !== 'function' &&
       !_internalFields.includes(key.toString()) &&
       key.toString().charAt(0) !== '_'
     ) {
       return receiver.set(key, value);
     }
     return Reflect.set(target, key, value, receiver);
   },
 };

class ParseProxy extends Parse.Object {
  constructor(...args) {
    super(...args);
    return new Proxy(this, proxyHandler);
  }
}

const classes = ["_User", "Profile"];
for (const parseClass of classes) {
  Parse.Object.registerSubclass(parseClass, ParseProxy);
}
1 Like

hi, I can see this is pretty handy and well suite my need. Can I know where to put these codes for archive what you are told?

it allows you to access data via obj.key rather than obj.get("key") .

You can place it at the entry point to your project, just after you run Parse.Initialize on the client, or, on the server, before all your other cloud code

1 Like