Giter Club home page Giter Club logo

denorm's Introduction

DenORM

Depedency-limited Deno ORM for PostgreSQL. Allow you to build relativly simple queries. Well-suited for REST apis.

Low chance to let you down in production because of unavailable depedencies.

Only 2 deps:

ORM

What you will need for the ORM

  • Add decorators to your model classes
  • Create DAOs
  • Call initTable() before instanting any of the model classes (or referencing their types)

Add decorators to your model class

import { Entity... } from "https://raw.githubusercontent.com/ninjinskii/denorm/master/mod.ts"

@Entity("my_model")
export class MyModel {
  constructor(
    @PrimaryKey("SERIAL") public myPrimaryKey: number,
    @Field("VARCHAR") public name: string,
    @SizedField("VARCHAR", 255) public comment: string,
    @Field("VARCHAR", NULLABLE.YES) public nullableProperty: string | null,
    @Field("BOOL", NULLABLE.NO, "my_field") public alias: string,
  ) {}
}
@Entity(tableName: string) // Will mark your class to be mapped in your DB, bearing the provided name.
@PrimaryKey(type: string, as?: string) // Will mark the property as your primary key with the provided type. If set, `as` your field will take that name in the database.
@Field(type: string, nullable?: NULLABLE, as?: string) // Will mark the property as a standard field, with the provided type.
@SizedField(type: string, size?: int, nullable?: NULLABLE, as?: string) // Will mark the property as a standard field, with the provided type and size (e.g; VARCHAR(255)).

Create DAOs

DAOs will be your entry point for querying data. You need to pass an instance of PostgresClient to the DAO constructor. See: https://deno-postgres.com/#/?id=connecting-to-your-db for more details on that.

import { Dao, Client, Select } from "https://raw.githubusercontent.com/ninjinskii/denorm/master/mod.ts"

export class WineDao extends Dao {
  @Select("wine") // Pass the table name as argument
  function getAllWines(): Promise<Wine[]> { // Set the return type that coresponds to the fetched data
    throw new Error(""); // The error will never be trigerred, but we throw it to avoid linter complaints.
  }
}

const client = new Client(databaseUrl);
const dao = new WineDao(client);

Call initTables()

IMPORTANT NOTE: Run initTables() before trying to instantiate any of your model objects or even referencing the type, as it will break the annotation system.

import { initTables } from "https://raw.githubusercontent.com/ninjinskii/denorm/master/mod.ts"

await initTables(client, [Wine, MyOtherModel])

Query the database

If you're building a REST api, take a look at the dedicated section below. To do basic queries you can use annotations shorthands:

Shorthands

Shorthands allows you to make the base CRUD queries as easy as an annotation. Some of them can take an optionnal where parameter, which can only check single or multiple fields equality. For more complex queries, use @Query described below.

For the sake of brevity, Dao class is not represented in the next examples. But remember that shorthands decorator needs to be called inside a Dao.

SELECT

// Select all wines
@Select() // Pass the table name as argument
getAllWines(): Promise<Wine[]> { // Set the return type that coresponds to the fetched data
  throw new Error(""); // The error will never be trigerred, but we throw it to avoid linter complaints.
}

You can make some slightly more complex select using the where parameter (allows only the "=" operator):

// Select object by id
@Select(new Where({ id: 1, name: "Riesling" })) // Pass where as argument.
getWineById(_id: number, _name: string): Promise<Wine[]> {
  throw new Error("");
}

Dynamic parameters binding can be done by setting the value "°<one-based index of parameter in function>" to your condition. For instance:

@Select(new Where({ isOrganic: true, name: "°1" })) 
getOrganicWinesForName(_name: string): Promise<Wine[]> {
  throw new Error("");
}

INSERT

@Insert()
insertWines(_wines: Wine[]): Promise<number> { // Returns number of inserted rows
  throw new Error("");
}

UPDATE

@Update()
updateWines(_wines: Wine[]): Promise<number> { // Returns number of rows affected
  throw new Error("");
}

DELETE

@Delete(new Where({ id: 1 })) // Returns number of deleted rows. Where is mandatory for Delete shorthand.
delete(): Promise<number> {
  throw new Error("");
}

@Query()

For more complex queries, see @Query:

@Query("SELECT id, name, SUBSTR(naming, 0, $2) AS naming FROM wine WHERE id = $1")
  getWineById(
    _id: number,  // $1
    _max: number, // $2
  ): Promise<{ id: number; name: string; naming: string }[]> {
    throw new Error();
  }

Note that whatever query you will write inside @Query(), the function will always return an Array. This array will be filled with resutlts in case of a SELECT statement.

Also, note that the parameters will automatically be bounded. Parameters naming in WHERE is not necessary here.

Transactions

To begin a transaction, call transaction() passing the list of DAOs needed inside this transaction, and a function.

import { transaction } from "https://raw.githubusercontent.com/ninjinskii/denorm/master/mod.ts"


const success: boolean = await transaction([dao, dao2], async () => {
  // Place all your queries here, e.g. dao.getAllWines(); dao2.getAllThing();
});

Advices for REST apis

Make an interface that allows you to treat all DAOs as the same entity:

export interface RestDao<T> {
  get(): Promise<T[]>
  getOne(id: number): Promise<T[]>
  put(objects: T[]): Promise<number>
  patch(objects: T[]): Promise<number>
  delete(objects: T[]): Promise<number>
  ...
}

Create one DAO per collection, implementing RestDao.

export class WineDao extends Dao implements RestDao<Wine> {
  @Select("wine")
  get(): Promise<Wine[]> {
    throw new Error("");
  }

  @Select("wine", new Where({ id: "°1" }))
  getOne(id: number): Promise<Wine[]> {
    throw new Error("");
  }

  @Insert("wine")
  put(): Promise<number> {
    throw new Error("");
  }
  ...
}

Then you can create an object to your routes and your DAOs, and have a single function to execute all collections actions in your favorite web framework.

const mapper: { [path: string]: RestDao }  = {
  "/wines": new WineDao(client)
  ...
}

on(get) => mapper[request.path].getAll()

Run the project locally

docker-compose up -d

Run tests

docker-compose up -d
docker-compose exec web deno test --allow-env --allow-net --allow-read tests

denorm's People

Contributors

ninjinskii avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.