This commit is contained in:
qpismont 2024-12-30 19:48:29 +00:00
commit 0a2742755d
16 changed files with 477 additions and 0 deletions

5
.devcontainer/Dockerfile Normal file
View file

@ -0,0 +1,5 @@
FROM debian:12
RUN apt update &&\
apt install git curl unzip -y &&\
curl -fsSL https://bun.sh/install | bash

View file

@ -0,0 +1,13 @@
{
"workspaceFolder": "/workspace",
"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,Z",
"build": {
"dockerfile": "Dockerfile"
},
"customizations": {
"vscode": {
"extensions": ["oven.bun-vscode", "biomejs.biome"]
}
},
"forwardPorts": [8080]
}

175
.gitignore vendored Normal file
View file

@ -0,0 +1,175 @@
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
# Logs
logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Caches
.cache
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
pids
_.pid
_.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store

8
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,8 @@
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "biomejs.biome",
"typescript.tsdk": "node_modules/typescript/lib",
"editor.codeActionsOnSave": {
"source.organizeImports.biome": "explicit"
}
}

9
.woodpecker/.build.yml Normal file
View file

@ -0,0 +1,9 @@
when:
event: [push]
steps:
build:
image: oven/bun:1.1.42-slim
commands:
- bun install
- bun run build

10
.woodpecker/.lint.yml Normal file
View file

@ -0,0 +1,10 @@
when:
event: [push]
steps:
lint:
image: oven/bun:1.1.42-slim
commands:
- bun install
- bun run ci

30
.woodpecker/.test.yml Normal file
View file

@ -0,0 +1,30 @@
when:
event: [push]
steps:
test:
image: oven/bun:${BUN_VERSION}-slim
commands:
- sleep 15
- bun install
- bun run test
environment:
- DB_HOST: pg
- DB_PORT: 5432
- DB_USER: dev
- DB_PASSWORD: dev
- DB_NAME: trepa
services:
- name: pg
image: postgres:16-alpine
environment:
- POSTGRES_USER: dev
- POSTGRES_PASSWORD: dev
- POSTGRES_DB: trepa
matrix:
BUN_VERSION:
- 1.1.42
- 1.1.41
- 1.1.40

15
README.md Normal file
View file

@ -0,0 +1,15 @@
# trepa
To install dependencies:
```bash
bun install
```
To run:
```bash
bun run app.ts
```
This project was created using `bun init` in bun v1.1.27. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.

16
biome.json Normal file
View file

@ -0,0 +1,16 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"formatter": {
"enabled": true,
"indentStyle": "tab"
},
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
}
}

BIN
bun.lockb Executable file

Binary file not shown.

22
package.json Normal file
View file

@ -0,0 +1,22 @@
{
"name": "trepa",
"module": "app.ts",
"type": "module",
"scripts": {
"build": "bun build ./src/app.ts --compile --outfile=./dist/app",
"ci": "biome ci . --error-on-warnings",
"test": "bun test"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@types/bun": "latest",
"@types/pg": "^8.11.10"
},
"peerDependencies": {
"typescript": "^5.6.2"
},
"dependencies": {
"hono": "^4.6.15",
"pg": "^8.13.1"
}
}

0
src/app.ts Normal file
View file

View file

@ -0,0 +1,8 @@
export type SqlParams = Record<string, unknown>;
export default interface IDatabase {
fetch(sql: string, params: SqlParams): Promise<unknown | undefined>;
fetchAll(sql: string, params: SqlParams): Promise<unknown[]>;
insert(sql: string, params: SqlParams): Promise<number>;
query(sql: string, params: SqlParams): Promise<void>;
}

View file

@ -0,0 +1,71 @@
import { Client, Pool, type PoolClient, type QueryResult } from "pg";
import type { SqlParams } from "./IDatabase";
import type IDatabase from "./IDatabase";
export interface PgConnectionOptions {
host: string;
port: number;
database: string;
user: string;
password: string;
}
export default class PgDatabase implements IDatabase {
private pool: Pool;
constructor(connectionOptions: PgConnectionOptions) {
this.pool = new Pool({
user: connectionOptions.user,
password: connectionOptions.password,
host: connectionOptions.host,
port: connectionOptions.port,
database: connectionOptions.database,
});
}
async query(sql: string, params: SqlParams = {}): Promise<void> {
await this.internalQuery(sql, params);
}
async insert(sql: string, params: SqlParams): Promise<number> {
const res = await this.internalQuery(sql, params);
const id = res.rows[0]?.id;
if (!id) {
throw new Error("id not found in sql str");
}
return id;
}
async fetch(
sql: string,
params: SqlParams = {},
): Promise<unknown | undefined> {
const rows = await this.fetchAll(sql, params);
return rows[0];
}
async fetchAll(sql: string, params: SqlParams = {}): Promise<unknown[]> {
const res = await this.internalQuery(sql, params);
return res.rows;
}
private async internalQuery(
sql: string,
params: SqlParams,
): Promise<QueryResult> {
let client: PoolClient | null = null;
try {
client = await this.pool.connect();
const res = await client.query(sql, Object.values(params));
return res;
} finally {
client?.release();
}
}
}

View file

@ -0,0 +1,68 @@
import { afterEach, beforeEach, expect, test } from "bun:test";
import PgDatabase from "../../database/PgDatabase";
let db: PgDatabase;
const host = Bun.env.DB_HOST;
const port = Bun.env.DB_PORT;
const database = Bun.env.DB_NAME;
const user = Bun.env.DB_USER;
const password = Bun.env.DB_PASSWORD;
beforeEach(async () => {
db = new PgDatabase({
host: host || "",
port: Number.parseInt(port || "0"),
database: database || "",
user: user || "",
password: password || "",
});
await db.query(`
CREATE TABLE public.test_table
(
id integer NOT NULL GENERATED ALWAYS AS IDENTITY,
name text NOT NULL,
PRIMARY KEY (id)
);
INSERT INTO test_table(name) VALUES('Quentin');
INSERT INTO test_table(name) VALUES('Thomas');
`);
});
test("test insert", async () => {
const id = await db.insert(
"INSERT INTO test_table(name) VALUES($1) RETURNING id",
{
name: "Francis",
},
);
expect(id).toBe(3);
});
test("test error insert no id", async () => {
expect(
db.insert("INSERT INTO test_table(name) VALUES($1)", {
name: "Francis",
}),
).rejects.toBeInstanceOf(Error);
});
test("test fetch", async () => {
const row = (await db.fetch("SELECT * FROM test_table WHERE id = 1")) as {
id: number;
};
expect(row.id).toEqual(1);
});
test("test fetch all", async () => {
const rows = await db.fetchAll("SELECT * FROM test_table");
expect(rows.length).toEqual(2);
});
afterEach(async () => {
await db.query("DROP TABLE test_table");
});

27
tsconfig.json Normal file
View file

@ -0,0 +1,27 @@
{
"compilerOptions": {
// Enable latest features
"lib": ["ESNext", "DOM"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
}
}