npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

easy-drizzle

v0.0.4

Published

Utility pack to make drizzle more convenient to use and configure

Downloads

223

Readme

Easy Drizzle

A utility pack to make drizzle more convenient to use and configure:

  • Table schema builder to build basic schemas consistently for each database driver
  • Services to expose common table operations and use across multiple tables
  • Client builders to create database clients for each database supported

This pack currently supports the following databases:

  • Sqlite
  • Postgres
  • Mysql

TOC

-- Installation -- Clients -- Schemas -- Services

Installation

npm install easy-drizzle

See the examples folder and the sample project at poc-drizzle-sqlite-pg-mysql

Clients

Drizzle Clients are used to interact with the database, by using the Drizzle ORM API

Sqlite client

import Database from "better-sqlite3";
const createDb = (dbString: string) => new Database(dbString);
createSqliteClient(createDb, { verbose: true });

MySQL client

import mysql from "mysql2/promise";
const createDb = (dbString: string) => mysql.createPool(dbString);
createMysqlClient(createDb, { verbose: true });

Postgres client

import pg from "pg";
const createDb = (dbString: string) =>
  new pg.Pool({
    connectionString: dbString,
  });

createPostgresClient(createDb, { verbose: true });

Building schemas

A Schema builder can be used to faciliate building consistent schemas across the various drizzle ORM adapters. Each builders exposes an API that is almost identical so that minimal change is required to build a schema for another ORM adapter.

Sample sqlite schema builder methods

// primary key
primary(type: FieldKeyType = "int")

// primitive types
int(name: string, opts: IntOpts = {})
bool(name: string, opts: BoolOpts)
str(name: string, opts?: StrOpts)
text(name: string, opts?: StrOpts)

// time
timestamp(name: string, opts: TimeStampOpts = {})
timestampMs(name: string, opts: TimeStampOpts = {})
dateTime(name: string, opts: DateTimeOpts = {})

// build relationships
relation(table: Table, type: FieldKeyType = "int")
oneToMany(parentTable: Table, childTable: Table, foreignKeyName: string)

// to build indexes for each named field
indexFor(...names: string[])

Sqlite sample using schema builder

import { relations } from "drizzle-orm";
import { sqliteTable } from "drizzle-orm/sqlite-core";

import { SQLiteSchemaBuilder, Time } from "easy-drizzle/sqlite";

const builder = new SQLiteSchemaBuilder("user");

export const usersTable = sqliteTable(
  "users",
  {
    id: builder.primary(),
    firstName: builder.str("first_name"),
    lastName: builder.str("last_name"),
    email: builder.str("email"),
    createdAt: builder.dateTime("created_at", { default: Time.Now }),
  },
  builder.indexFor("first_name", "last_name", "email")
);

export type User = typeof usersTable.$inferSelect;
export type CreateUser = typeof usersTable.$inferInsert;

export const usersRelations = relations(usersTable, ({ many }) => ({
  posts: many(postsTable),
}));

export const postsTable = sqliteTable(
  "posts",
  {
    id: builder.primary(),
    userId: builder.relation(usersTable),
    title: builder.str("title"),
    content: builder.str("content"),
    createdAt: builder.dateTime("created_at", { default: Time.Now }),
  },
  builder.indexFor("id", "user_id", "title", "content")
);

export type Post = typeof postsTable.$inferSelect;
export type CreatePost = typeof postsTable.$inferInsert;

export const postsRelations = builder.oneToMany(usersTable, postsTable);

Services

Each service comes with the following methods:

Data query methods

  async getAll()
  async getOrdered(columns: any, opts: OrderOpts = {})
  async getPage(opts: PaginationOpts)
  async getById({ id }: { id: number })
  async whereMatching(column: any, match: any)
  async whereGreater(column: any, criteria: number)
  async whereLess(column: any, criteria: number)
  async whereGte(column: any, criteria: number)
  async whereLte(column: any, criteria: number)

Data mutation methods

  async insert({ values }: { values: SQLiteInsertValue<TTable> })
  async insertMany({ values }: { values: SQLiteInsertValue<TTable>[] })
  async updateById({ id, values }: { id: number; values: SQLiteUpdateSetSource<TTable> })
  async deleteById({ id }: { id: number })
  async deleteMany({ ids }: { ids: number[] })
  async deleteAll() {

Sample Sqlite service from examples folder

import { desc, eq, gt, gte, inArray, lt, lte } from "drizzle-orm";
import type {
  SQLiteInsertValue,
  SQLiteUpdateSetSource,
} from "drizzle-orm/sqlite-core";

import { envConfig } from "easy-drizzle/env";
import type { OrderOpts, PaginationOpts } from "easy-drizzle/types";

import type { SqliteClient } from "easy-drizzle/sqlite";
import type { SqliteTable } from "./sqlite-schema";

export class SqliteService<TTable extends SqliteTable> {
  public dbClient: SqliteClient;
  public table: TTable;

  constructor({ dbClient, table }: { dbClient: SqliteClient; table: TTable }) {
    this.dbClient = dbClient;
    this.table = table;
  }

  private all() {
    return this.dbClient.select().from(this.table);
  }

  async getAll() {
    const records = await this.all();
    return records;
  }

  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  async getOrdered(columns: any, opts: OrderOpts = {}) {
    const ordered =
      opts.order !== "desc"
        ? this.all().orderBy(columns)
        : this.all().orderBy(desc(columns));
    if (opts.limit) ordered.limit(opts.limit);
    const records = await ordered;
    return records;
  }

  async getPage(opts: PaginationOpts) {
    const all = this.dbClient.select().from(this.table);
    if (opts.limit) {
      all.limit(opts.limit);
    }
    if (opts.offset) {
      all.offset(opts.offset);
    }
    const records = await all;
    return records;
  }

  async getById({ id }: { id: number }) {
    const records = await this.all().where(eq(this.table.id, id));
    return records;
  }

  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  async whereMatching(column: any, match: any) {
    const records = await this.all().where(eq(column, match));
    return records;
  }

  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  async whereGreater(column: any, criteria: number) {
    const records = await this.all().where(gt(column, criteria));
    return records;
  }

  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  async whereLess(column: any, criteria: number) {
    const records = await this.all().where(lt(column, criteria));
    return records;
  }

  async whereLte(column: any, criteria: number) {
    const records = await this.all().where(lte(column, criteria));
    return records;
  }

  async whereGte(column: any, criteria: number) {
    const records = await this.all().where(gte(column, criteria));
    return records;
  }

  async insert({ values }: { values: SQLiteInsertValue<TTable> }) {
    const records = await this.dbClient.insert(this.table).values(values);
    return records;
  }

  async insertMany({ values }: { values: SQLiteInsertValue<TTable>[] }) {
    const records = await this.dbClient.insert(this.table).values(values);
    return records;
  }

  async updateById({
    id,
    values,
  }: {
    id: number;
    values: SQLiteUpdateSetSource<TTable>;
  }) {
    const records = await this.dbClient
      .update(this.table)
      .set(values)
      .where(eq(this.table.id, id));
    return records;
  }

  async deleteById({ id }: { id: number }) {
    const records = await this.dbClient
      .delete(this.table)
      .where(eq(this.table.id, id));
    return records;
  }

  async deleteMany({ ids }: { ids: number[] }) {
    const records = await this.dbClient
      .delete(this.table)
      .where(inArray(this.table.id, ids));
    return records;
  }

  async deleteAll() {
    if (envConfig.STAGE !== "test" || envConfig.SQLITE_DB_NAME !== "test") {
      throw new Error("Delete all records is only allowed in test environment");
    }

    await this.dbClient.delete(this.table);
  }
}

Note that the service is required to reference the table type from the schema file.

The service methods return results as per the drizzle documentation for the particular DB.

An Sqlite service for the Posts table can be created simply by extending the generic SqliteService and passing a reference to the table, such as sqlitePostsTable

export class SqlitePostService extends SqliteService<typeof sqlitePostsTable> {
  constructor({ dbClient }: { dbClient: SqliteClient }) {
    super({
      dbClient,
      table: sqlitePostsTable,
    });
  }
}

export const sqlitePostService = new SqlitePostService({
  dbClient: sqliteClient,
});

Service usage example for Users table

// create 10 fake users
for (let i = 0; i < 10; i++) {
    const user = {
        firstName: faker.person.firstName(),
        lastName: faker.person.lastName(),
        email: faker.internet.email()
    }
    await sqliteUserService.insert(user)
}

const user = await sqlitePostService.getById(1)
console.log('User #1:', user)

// update last name of first record
await sqlitePostService.updateById(1, { lastName: 'unknown' })

// get the first 2 users
const user = await sqlitePostService.async getFirst(2)

// get the last 5 users
const user = await sqlitePostService.getLast(5)

// delete users
await sqlitePostService.deleteById(1)
await sqlitePostService.deleteAll()
```

## <a name='Contribution'></a>Contribution

![NPM](https://img.shields.io/npm/l/@gjuchault/typescript-library-starter)
![NPM](https://img.shields.io/npm/v/@gjuchault/typescript-library-starter)
![GitHub Workflow Status](https://github.com/gjuchault/typescript-library-starter/actions/workflows/typescript-library-starter.yml/badge.svg?branch=main)

Based on [typescript-library-starter](https://github.com/gjuchault/typescript-library-starter)

To enable deployment, you will need to:

1. Set up the `NPM_TOKEN` secret in GitHub Actions ([Settings > Secrets > Actions](https://github.com/gjuchault/typescript-library-starter/settings/secrets/actions))
2. Give `GITHUB_TOKEN` write permissions for GitHub releases ([Settings > Actions > General](https://github.com/gjuchault/typescript-library-starter/settings/actions) > Workflow permissions)

## <a name='Features'></a>Features

### <a name='Node.jsnpmversion'></a>Node.js, npm version

TypeScript Library Starter relies on [Volta](https://volta.sh/) to ensure the Node.js version is consistent across developers. It's also used in the GitHub workflow file.

### <a name='TypeScript'></a>TypeScript

Leverages [esbuild](https://github.com/evanw/esbuild) for blazing-fast builds but keeps `tsc` to generate `.d.ts` files.
Generates a single ESM build.

Commands:

- `build`: runs type checking, then ESM and `d.ts` files in the `build/` directory
- `clean`: removes the `build/` directory
- `type:dts`: only generates `d.ts`
- `type:check`: only runs type checking
- `type:build`: only generates ESM

### <a name='Tests'></a>Tests

TypeScript Library Starter uses [Node.js's native test runner](https://nodejs.org/api/test.html). Coverage is done using [c8](https://github.com/bcoe/c8) but will switch to Node.js's one once out.

Commands:

- `test`: runs test runner
- `test:watch`: runs test runner in watch mode
- `test:coverage`: runs test runner and generates coverage reports

### <a name='Formatlint'></a>Format & lint

This template relies on [Biome](https://biomejs.dev/) to do both formatting & linting in no time.
It also uses [cspell](https://github.com/streetsidesoftware/cspell) to ensure correct spelling.

Commands:

- `format`: runs Prettier with automatic fixing
- `format:check`: runs Prettier without automatic fixing (used in CI)
- `lint`: runs Biome with automatic fixing
- `lint:check`: runs Biome without automatic fixing (used in CI)
- `spell:check`: runs spell checking

### <a name='Releasing'></a>Releasing

Under the hood, this library uses [semantic-release](https://github.com/semantic-release/semantic-release) and [Commitizen](https://github.com/commitizen/cz-cli).
The goal is to avoid manual release processes. Using `semantic-release` will automatically create a GitHub release (hence tags) as well as an npm release.
Based on your commit history, `semantic-release` will automatically create a patch, feature, or breaking release.

Commands:

- `cz`: interactive CLI that helps you generate a proper git commit message, using [Commitizen](https://github.com/commitizen/cz-cli)
- `semantic-release`: triggers a release (used in CI)