/model-one

Set of utility classes for Cloudflare D1

Primary LanguageTypeScriptMIT LicenseMIT

Model 1

code style styled with prettier made with lass license npm downloads

Set of utility classes for Cloudflare Workers D1 with validations by Joi inspired by reform.

Note: This package is still considered experimental. Breaking changes should be expected.

Features

  • Basic CRUD Model.
  • UUID by default.
  • Timestamps for created_at and updated_at.
  • Validations by Joi.
  • Raw SQL queries.

Table of Contents

  1. Install
  2. Example
  3. Methods
  4. Extend Methods

Install

npm:

npm install model-one joi

yarn:

yarn add model-one joi

Example

In the following example we are going to define an user with the following fields first_name and last_name.

  1. Create a new database.

Create a local file schema.sql

DROP TABLE IF EXISTS users;

CREATE TABLE users (
  id text PRIMARY KEY,
  first_name text,
  last_name text,
  deleted_at datetime,
  created_at datetime,
  updated_at datetime
);

Creates a new D1 database and provides the binding and UUID that you will put in your wrangler.toml file.

npx wrangler d1 create example-db

Create the tables from schema.sql

npx wrangler d1 execute example-db --file ./schema.sql
  1. We need to import the Model and Schema from 'model-one' and the type SchemaConfigI. Then create a new Schema, define table name and fields
// ./models/User.ts
import { Model, Schema } from 'model-one'
import type { SchemaConfigI } from 'model-one';

const userSchema: SchemaConfigI = new Schema({
  table_name: 'users',
  columns: [
    { name: 'id', type: 'string' },
    { name: 'first_name', type: 'string' },
    { name: 'last_name', type: 'string' }
  ],
})
  1. Then we are going to define the interfaces for our User model.
// ./interfaces/index.ts
export interface UserDataI {
  id?: string
  first_name?: string
  last_name?: string
}

export interface UserI extends Model {
  data: UserDataI
}
  1. Now we are going import the types and extend the User
// ./models/User.ts
import { UserI, UserDataI } from '../interfaces'

export class User extends Model implements UserI {
  data: UserDataI

  constructor(props: UserDataI) {
    super(userSchema, props)
    this.data = props
  }
}
  1. Final result of the User model
// ./models/User.ts
import { Model, Schema } from 'model-one'
import type { SchemaConfigI } from 'model-one';
import { UserI, UserDataI } from '../interfaces'

const userSchema: SchemaConfigI = new Schema({
  table_name: 'users',
  columns: [
    { name: 'id', type: 'string' },
    { name: 'first_name', type: 'string' },
    { name: 'last_name', type: 'string' }
  ],
})

export class User extends Model implements UserI {
  data: UserDataI

  constructor(props: UserDataI) {
    super(userSchema, props)
    this.data = props
  }
}
  1. After creating the User we are going to create the form that handles the validations. And with the help of Joi we are going to define the fields.
// ./forms/UserForm.ts
import { Form } from 'model-one'
import { UserI } from '../interfaces'
import Joi from 'joi'

const schema = Joi.object({
  id: Joi.string(),
  first_name: Joi.string(),
  last_name: Joi.string(),
})

export class UserForm extends Form {
  constructor(data: UserI) {
    super(schema, data)
  }
}

Methods

Create

To insert data we need to import the UserForm and we are going start a new User and insert it inside the UserForm, then we can call the method create.

// ./controllers/UserController.ts
import { UserForm } from '../form/UserForm';
import { User } from '../models/User';

const userForm = new UserForm(new User({ first_name, last_name }))

await User.create(userForm, binding)

Read

By importing the User model will have the following methods to query to D1:

// ./controllers/UserController.ts
import { User } from '../models/User';

await User.all(binding)

await User.findById(id, binding)

await User.findOne(column, value, binding)

await User.findBy(column, value, binding)

Update

Include the ID and the fields you want to update inside the data object.

// ./controllers/UserController.ts
import { User } from '../models/User';

// User.update(data, binding)
await User.update({ id, first_name: 'John' }, binding)

Delete

Delete an User

// ./controllers/UserController.ts

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

await User.delete(id, binding)

Extend Methods

Extend User methods.

// ./models/User.ts
import { Model, Schema, NotFoundError } from 'model-one'
import type { SchemaConfigI } from 'model-one';
import { UserI, UserDataI } from '../interfaces'

const userSchema: SchemaConfigI = new Schema({
  table_name: 'users',
  columns: [
    { name: 'id', type: 'string' },
    { name: 'first_name', type: 'string' },
    { name: 'last_name', type: 'string' }
  ],
})

export class User extends Model implements UserI {
  data: UserDataI

  constructor(props: UserDataI) {
    super(userSchema, props)
    this.data = props
  }

  static async findByFirstName(first_name: string, binding: any) {
    // this.findBy(column, value, binding)
    return await this.findBy('first_name', first_name, binding)
  }

  static async rawAll(binding: any) {
    const { results, success } = await binding.prepare(`SELECT * FROM ${userSchema.table_name};`).all()
    return Boolean(success) ? results : NotFoundError
  }
}

To do:

  • Support JSONB.
  • Soft and hard delete.
  • Tests.
  • Unique values.
  • Associations: belongs_to, has_one, has_many.
  • Complex Forms for multiple Models.

Contributors

Julian Clatro

License

MIT