Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial version #1

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: release
on:
release:
types: [published]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
registry-url: https://registry.npmjs.org
node-version: 16
- run: npm install
- run: |
npm install -g json && json -I -f package.json -e '
this.version = "${{ github.ref }}".replace("refs/tags/", "");
this.main = "dist/src/index.js";
this.types = "dist/src/index.d.ts";
'
- run: npm run build
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
25 changes: 25 additions & 0 deletions .github/workflows/release_dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: release_dev
on:
push:
branches:
- master
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
registry-url: https://registry.npmjs.org
node-version: 16
- run: npm install
- run: |
npm install -g json && json -I -f package.json -e '
this.version = "0.0.0-dev.'$(date -u +'%Y%m%d%H%M%S')'";
this.main = "dist/src/index.js";
this.types = "dist/src/index.d.ts";
'
- run: npm run build
- run: npm publish --tag dev
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
38 changes: 38 additions & 0 deletions .github/workflows/spec.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: "spec"
on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node:
- 14
- 16
- 18
postgres:
- 12-alpine
- 13-alpine
- 14-alpine
- 15-alpine
services:
postgres:
image: postgres:${{ matrix.postgres }}
env:
POSTGRES_HOST_AUTH_METHOD: trust
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
- run: npm install
- run: npx tsc -noEmit
- run: npm run eslint:check
- run: npm test
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# `@cubos/kysely-repository`
61 changes: 61 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"name": "@cubos/kysely-repository",
"version": "0.0.0",
"description": "A set of repository classes to make it easier to interact with your database with kysely",
"main": "src/index.ts",
"scripts": {
"test": "jest",
"eslint:fix": "eslint --fix '{src,spec}/**/*.ts'",
"eslint:check": "eslint '{src,spec}/**/*.ts'",
"build": "tsc",
"postgres:start": "docker run -d -p 5432:5432 --name postgres -e POSTGRES_HOST_AUTH_METHOD=trust postgres:15-alpine"
},
"keywords": [],
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/cubos/node-kysely-repository.git"
},
"bugs": {
"url": "https://github.com/cubos/node-kysely-repository/issues"
},
"publishConfig": {
"access": "public"
},
"homepage": "https://github.com/cubos/node-kysely-repository#readme",
"dependencies": {
"kysely": "^0.22.0"
},
"devDependencies": {
"@cubos/eslint-config": "^2.0.664038",
"@types/jest": "^29.2.2",
"@types/lodash": "^4.14.188",
"@types/node": "^18.11.9",
"@types/pg": "^8.6.5",
"jest": "^29.2.2",
"jest-extended": "^3.1.0",
"pg": "^8.8.0",
"ts-jest": "^29.0.3",
"typescript": "^4.8.4"
},
"jest": {
"preset": "ts-jest",
"modulePaths": [
"<rootDir>/src/"
],
"testEnvironment": "node",
"testMatch": [
"**/spec/**/*.ts"
],
"verbose": true,
"testTimeout": 60000,
"collectCoverage": true,
"coverageReporters": [
"text",
"lcov"
],
"setupFilesAfterEnv": [
"jest-extended"
]
}
}
159 changes: 159 additions & 0 deletions spec/BaseRepository.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import { randomBytes } from "crypto";

import { Kysely, PostgresDialect, sql } from "kysely";
import type { PoolConfig } from "pg";
import { Pool } from "pg";

import { BaseRepository } from "../src";
import type { BaseModel } from "../src/BaseRepository";

const poolConfig: PoolConfig = {
database: "postgres",
host: process.env.DB_HOST ?? "localhost",
password: process.env.DB_PASSWORD ?? "postgres",
user: process.env.DB_USER ?? "postgres",
};

type Database = Record<string, BaseModel>;

function randomTableName() {
return `test${randomBytes(8).toString("hex")}`;
}

describe("BaseRepository", () => {
const database = `test_${randomBytes(8).toString("hex")}`;
const masterConn = new Kysely<Database>({ dialect: new PostgresDialect({ pool: new Pool(poolConfig) }) });
const conn = new Kysely<Database>({ dialect: new PostgresDialect({ pool: new Pool({ ...poolConfig, database }) }) });

beforeAll(async () => {
await sql`CREATE DATABASE ${sql.raw(database)}`.execute(masterConn);
});

afterAll(async () => {
await conn.destroy();
await sql`DROP DATABASE ${sql.raw(database)}`.execute(masterConn);
await masterConn.destroy();
});

it("inserts, finds, updates and deletes objects", async () => {
const tableName = randomTableName();

await BaseRepository.createTable(conn, tableName, table => {
return table.addColumn("name", "text");
});

interface Model extends BaseModel {
name: string;
}

type TestDb = Record<string, Model>;

const repo = new BaseRepository<TestDb, string>(conn as Kysely<TestDb>, tableName);

expect(await repo.findAll()).toHaveLength(0);

const inserted = await repo.insert({ name: "foo" });

expect(inserted.name).toBe("foo");
expect(inserted.createdAt).toBeInstanceOf(Date);
expect(inserted.updatedAt).toBeInstanceOf(Date);
expect(inserted.updatedAt.getTime()).toBe(inserted.updatedAt.getTime());
expect(inserted.id).toBeDefined();
expect(await repo.findAll()).toEqual([inserted]);

const updated = await repo.update({ id: inserted.id, name: "bar" });

expect(updated.name).toBe("bar");
expect(updated.id).toBe(inserted.id);
expect(updated.createdAt.getTime()).toBe(inserted.createdAt.getTime());
expect(updated.updatedAt.getTime()).toBeGreaterThan(inserted.updatedAt.getTime());
expect(await repo.findAll()).toEqual([updated]);
expect(await repo.findBy({ name: "bar" })).toEqual([updated]);
expect(await repo.findOneBy({ name: "bar" })).toEqual(updated);
expect(await repo.get(inserted.id)).toEqual(updated);
expect(await repo.findBy({ name: "foo" })).toEqual([]);
expect(await repo.count()).toBe(1);

const deleted = await repo.delete(updated.id);

expect(updated).toEqual(deleted);
expect(await repo.findAll()).toEqual([]);
expect(await repo.count()).toBe(0);

await expect(repo.update({ id: inserted.id, name: "baz" })).rejects.toThrowError("no result");
await expect(repo.delete(updated.id)).rejects.toThrowError("no result");
});

it("allows altering tables", async () => {
const tableName = randomTableName();

await BaseRepository.createTable(conn, tableName, table => {
return table.addColumn("name", "text");
});

interface Model1 extends BaseModel {
name: string;
}

type TestDb1 = Record<string, Model1>;

await new BaseRepository<TestDb1, string>(conn as Kysely<TestDb1>, tableName).insert({ name: "foo" });

await BaseRepository.alterTable(conn, tableName, table => {
return table.addColumn("age", "integer").defaultTo(20);
});

interface Model2 extends BaseModel {
name: string;
age: number;
}

type TestDb2 = Record<string, Model2>;

const [row] = await new BaseRepository<TestDb2, string>(conn as Kysely<TestDb2>, tableName).findAll();

expect(row.name).toBe("foo");
expect(row.age).toBe(20);

await BaseRepository.dropTable(conn, tableName);
});

it("inserts and deletes many", async () => {
const tableName = randomTableName();

await BaseRepository.createTable(conn, tableName, table => {
return table.addColumn("value", "integer");
});

interface Model extends BaseModel {
value: number;
}

type TestDb = Record<string, Model>;

const repo = new BaseRepository<TestDb, string>(conn as Kysely<TestDb>, tableName);

const objectsToInsert = new Array(10).fill(0).map((_, index) => ({ value: index }));

const insertedObjects = await repo.insertAll(objectsToInsert);

expect(insertedObjects.map(x => x.value)).toEqual(objectsToInsert.map(x => x.value));
expect(new Set(insertedObjects.map(x => x.id)).size).toEqual(objectsToInsert.length);
expect(new Set(insertedObjects.map(x => x.createdAt.getTime())).size).toEqual(1);
expect(new Set(insertedObjects.map(x => x.updatedAt.getTime())).size).toEqual(1);

const deletedObjects = await repo.deleteBy(item => item.where("value", ">=", 5));

expect(deletedObjects).toEqual(insertedObjects.slice(5));
expect(await repo.findAll()).toEqual(expect.arrayContaining(insertedObjects.slice(0, 5)));

const moreDeletedObjects = await repo.deleteBy({ value: 0 });

expect(moreDeletedObjects).toEqual([insertedObjects[0]]);
expect(await repo.findAll()).toEqual(expect.arrayContaining(insertedObjects.slice(1, 5)));

await repo.truncate();

expect(await repo.findAll()).toEqual([]);
});
});
Loading