1.2.1 โ€ข Published 5 months ago

parse-sdk-ts v1.2.1

Weekly downloads
-
License
Apache-2.0
Repository
-
Last release
5 months ago

Parse Typescript SDK

A client/server library for typescript that provides type-safety for database classes, queries and cloud functions.

Built on top of Parse-SDK-JS, which is a version agnostic peer dependency.

Tested in SSR environment on Next and Nuxt. ๐Ÿงช

Package info

npm version License

Justification

The Parse-SDK-JS API, is not only prone to spelling errors but is also exceedingly difficult to utilize for developers who are unfamiliar with the names of database keys.

book.set("author", "Tolkien") // ๐Ÿ˜ฐ anything accepted
// is now
book.author.set("Tolkien") // ๐Ÿš€ only string accepted

And more importantly:

book.get("???") // ๐Ÿ˜ฐ
// is now
book.author.get() // ๐Ÿš€ full code completion

Please see example/ folder for complete examples with imports.

Setup

npm install parse-sdk-ts

import { initialize }  from 'parse-sdk-ts'

initialize(server_url, app_id)

Auth

import * as auth from 'parse-sdk-ts/auth'
const user : MyUser = await auth.signIn(MyUser, username, password)

Objects

Data is accessed via attributes instead of via string keys.

console.log(user.username.get())
user.username.set("new name")    // string
user.level.increment()           // number
user.weapons.addUnique("sword")  // array 
user.save()

Queries

The API is essentially the same as the Parse-SDK-JS but with Keys instead of strings.

const users : MyUser[] = new Query(MyUser).ascending(MyUser.keys.username).find()

Classes

To provide typings, all classes on the database must be wrapped with some logic.

export class MyUser extends DbModel {
  static readonly className: string = "_User";

  static readonly keys = Keys.build(MyUser, {
    username: "username",
  });

  readonly username = field(this).required().string(MyUser.keys.username);

  // to make sure only Parse.User can be passed
  constructor(data: Primitive.User) {
    super(data);
  }
}

For a non-user object it's the same

import User from './User'

export class Book extends DbModel {
  static readonly className: string = "Book";

  // client_key: "db_key", //
  static readonly keys = Keys.build(Book, {
    title: "book_title",
    authors: "authors",
    description: "desc",
  });

  readonly title       = field(this).required().string(Book.keys.title);
  readonly authors     = field(this).relation(User, Book.keys.authors);
  readonly description = field(this).string(Book.keys.description);
}

To import the field function:

import { field } from "parse-sdk-ts"
// or just use the quickhand
readonly title = this.field().string(Book.keys.title);

To be able to create a new object without data you may want to add the following static method.

static create() {
  return DbModel.createWithoutData(Book);
}

Or to ensure that Required fields can't be undefined.

static create(title: string, description: string) {
  const b = DbModel.createWithoutData(Book);
  b.title.set(title);
  b.description.set(description);
  return b;
}

Fields

We differentiate between Required and Optional fields.

If we are certain that the field is defined in the database, for example user.username.get(), we can use a Required field so that we get get(): string instead of get(): string | undefined.

Even Required fields can be undefined if the object has not been fetched, or if we fetch it via a query with exclude or select. Therefore we can also specify an fallback value that will be returned if the Required field is undefined.

field(this).required(fallback).string(User.keys.username)

Required Fields

TypeNameMethods
String.required().stringget set
Number.required().numberget set increment decrement
Boolean.required().booleanget set
Date.required().dateget set
Array<T>.required().array<T>get set append addUnique remove

Optional Fields

TypeNameMethods
String \| undefined.stringget set has getOrElse
Number \| undefined.numberget set has getOrElse increment decrement
Boolean \| undefined.booleanget set has getOrElse
Date \| undefined.dateget set has getOrElse
Array<T> \| undefined.array<T>get set has getOrElse append addUnique remove

Special Fields

T denotes the type of the target DbModel class. .required() does not have an effect on these fields.

NameMethodsNote
.pointer<T>get setA reference to a single other object.
.stringPointer<T>get setSame as Pointer but expects DB field to be a string (uuid) instead of Parse-Pointer.
.relation<T>add remove, query, findAllA reference to a group of other objects.
.syntheticRelation<T>query, findAllSynthesizes a relation like field from the fact that the target class has a pointer to this object.

Fallback

If you need to run some code that is not supported by the API, you can access the Parse Object directly.

user.data.increment("reactions." + reactionType)

Cloud Functions

We can also declare typed cloud functions in the following way.

import { Cloud } from "parse-sdk-ts";

export const myFunction = 
Cloud.declare<(arg1: string, arg2: number) => number>(
    "my_function",
    ["arg1", "arg2"] // db expected argument names
)

const result:number = await myFunction("hello", 42)

It is even supported to pass our custom DbModel classes.

import { Cloud, Primitive } from "parse-sdk-ts";

const getAuthor = Cloud.declare<(book: Book) => Primitive.User>(
    "get_author",
    ["book"]
)
const author: User = new User(await getAuthor(book))

Note that it is NOT supported to return custom DbModel classes. Therefore we have to wrap the result ourselves.

Server side rendering

Parse-SDK-TS is designed to work with server side rendering. It will automatically detect if it is running on the server. Note that it will never use the parse/node library but instead use the browser version with mocked localstorage. I think that this works just as fine, though it also means that the server will have access to auth functions which it should not use.

You should set the following environment variable to ignore the warning about this.

// .env
SERVER_RENDERING=true

If the master key is provided, Parse-SDK-TS will automatically load it when using the library on the server during initialization.

// .env.local
READONLY_MASTERKEY=...
// or
MASTERKEY=... (priority)

We use the masterkey by doing the following.

user.save({ useMasterKey: true })
query.find({ useMasterKey: true })
// or
query.asMaster().find()
user.saveAsMaster()

Error meta data

Errors are automatically split on ;;, the left side is put into message and the right side JSON parsed and put into meta.

The reason for this is that I wanted to display one message directly to the user and still get some more information to handle on the client.

type DbError = {
  code: number,
  message: string,
  meta: {}
}

For example

{
  message: 'Objektet kunde inte hittas.',
  code: 101,
  meta: { original: 'Object not found.' }
}

or (with zod)

{
    message: 'One field could not be saved.',
    code: 9001,
    meta: {
      key: 'username',
      code: 'too_big',
      maximum: 15,
      type: 'string',
      message: 'Username may not be longer than 15 characters'
    }
  }

Other

Parse is exported as Primitive from parse-sdk-ts. So Parse.Object is now Primitive.Object. We should never need to use it though.

Other exports are: isServer, useLocalTestServer

1.2.1

5 months ago

1.2.0

6 months ago

1.1.0-3

6 months ago

1.1.0-2

6 months ago

1.1.0

7 months ago

1.0.4

7 months ago

1.0.3

7 months ago

1.0.2

7 months ago

1.0.1

7 months ago

1.0.0

7 months ago