mirror of
https://github.com/nestriness/nestri.git
synced 2026-09-19 17:25:19 +03:00
feat: Sync to OSS repo
This commit is contained in:
114
packages/core/src/db/index.ts
Normal file
114
packages/core/src/db/index.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import { type ExtractTablesWithRelations } from 'drizzle-orm';
|
||||
import { PgTransaction, type PgTransactionConfig } from 'drizzle-orm/pg-core';
|
||||
import { drizzle } from 'drizzle-orm/postgres-js';
|
||||
import { type PostgresJsQueryResultHKT } from 'drizzle-orm/postgres-js';
|
||||
import postgres from 'postgres';
|
||||
|
||||
import { Context } from '../context.js';
|
||||
import { Env } from '../env.js';
|
||||
|
||||
export namespace Database {
|
||||
export async function ping() {
|
||||
const url = Env.get().DATABASE_URL || process.env.DATABASE_URL;
|
||||
const sql = url
|
||||
? postgres(url, { idle_timeout: 30, connect_timeout: 30 })
|
||||
: postgres({
|
||||
idle_timeout: 30,
|
||||
connect_timeout: 30,
|
||||
host: 'localhost',
|
||||
database: 'nestri',
|
||||
user: 'postgres',
|
||||
password: 'postgres',
|
||||
port: 5432
|
||||
});
|
||||
try {
|
||||
const [result] = await sql`SELECT 1`;
|
||||
return result ? true : false;
|
||||
} finally {
|
||||
await sql.end();
|
||||
}
|
||||
}
|
||||
|
||||
export function client() {
|
||||
const url = Env.get().DATABASE_URL || process.env.DATABASE_URL;
|
||||
const c = url
|
||||
? postgres(url, { idle_timeout: 30, connect_timeout: 30 })
|
||||
: postgres({
|
||||
idle_timeout: 30,
|
||||
connect_timeout: 30,
|
||||
host: 'localhost',
|
||||
database: 'nestri',
|
||||
user: 'postgres',
|
||||
password: 'postgres',
|
||||
port: 5432
|
||||
});
|
||||
return drizzle({ client: c });
|
||||
}
|
||||
|
||||
export type Transaction = PgTransaction<
|
||||
PostgresJsQueryResultHKT,
|
||||
Record<string, never>,
|
||||
ExtractTablesWithRelations<Record<string, never>>
|
||||
>;
|
||||
|
||||
export type TxOrDb = Transaction | ReturnType<typeof client>;
|
||||
|
||||
const TransactionContext = Context.create<{
|
||||
tx: TxOrDb;
|
||||
effects: (() => void | Promise<void>)[];
|
||||
}>();
|
||||
|
||||
export async function use<T>(callback: (trx: TxOrDb) => Promise<T>) {
|
||||
try {
|
||||
const { tx } = TransactionContext.use();
|
||||
return tx.transaction(callback);
|
||||
} catch (err) {
|
||||
if (err instanceof Context.NotFound) {
|
||||
const effects: (() => void | Promise<void>)[] = [];
|
||||
const result = await TransactionContext.provide(
|
||||
{
|
||||
effects,
|
||||
tx: client()
|
||||
},
|
||||
() => callback(client())
|
||||
);
|
||||
await Promise.all(effects.map((x) => x()));
|
||||
return result;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fn<Input, T>(callback: (input: Input, trx: TxOrDb) => Promise<T>) {
|
||||
return (input: Input) => use(async (tx) => callback(input, tx));
|
||||
}
|
||||
|
||||
export async function effect(effect: () => any | Promise<any>) {
|
||||
try {
|
||||
const { effects } = TransactionContext.use();
|
||||
effects.push(effect);
|
||||
} catch {
|
||||
await effect();
|
||||
}
|
||||
}
|
||||
|
||||
export async function transaction<T>(
|
||||
callback: (tx: TxOrDb) => Promise<T>,
|
||||
config?: PgTransactionConfig
|
||||
) {
|
||||
try {
|
||||
const { tx } = TransactionContext.use();
|
||||
return callback(tx);
|
||||
} catch (err) {
|
||||
if (err instanceof Context.NotFound) {
|
||||
const effects: (() => void | Promise<void>)[] = [];
|
||||
const result = await client().transaction(async (tx) => {
|
||||
return TransactionContext.provide({ tx, effects }, () => callback(tx));
|
||||
}, config);
|
||||
await Promise.all(effects.map((x) => x()));
|
||||
return result;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
packages/core/src/db/test.ts
Normal file
22
packages/core/src/db/test.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import postgres from 'postgres';
|
||||
|
||||
/**
|
||||
* Fail-closed test database connection.
|
||||
*
|
||||
* Tests must never silently fall back to an ad-hoc localhost database, so
|
||||
* this throws unless an explicit `TEST_DATABASE_URL` is set. Use an isolated
|
||||
* database for tests, e.g.:
|
||||
*
|
||||
* TEST_DATABASE_URL=postgres://postgres:postgres@localhost:5432/nestri
|
||||
*/
|
||||
export function testDb() {
|
||||
const url = process.env.TEST_DATABASE_URL;
|
||||
if (!url) {
|
||||
throw new Error(
|
||||
'TEST_DATABASE_URL is not set; refusing to run against an unspecified database. ' +
|
||||
'Set it to an isolated test database, e.g. ' +
|
||||
'TEST_DATABASE_URL=postgres://postgres:postgres@localhost:5432/nestri'
|
||||
);
|
||||
}
|
||||
return postgres(url, { idle_timeout: 30, connect_timeout: 30 });
|
||||
}
|
||||
23
packages/core/src/db/types.ts
Normal file
23
packages/core/src/db/types.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { char, timestamp as rawTs } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const ulid = (name: string) => char(name, { length: 26 + 4 });
|
||||
|
||||
export const id = {
|
||||
get id() {
|
||||
return ulid('id').primaryKey().notNull();
|
||||
}
|
||||
};
|
||||
|
||||
export const utc = (name: string) =>
|
||||
rawTs(name, {
|
||||
withTimezone: true
|
||||
});
|
||||
|
||||
export const timestamps = {
|
||||
timeCreated: utc('time_created').notNull().defaultNow(),
|
||||
timeUpdated: utc('time_updated')
|
||||
.notNull()
|
||||
.defaultNow()
|
||||
.$onUpdate(() => new Date()),
|
||||
timeDeleted: utc('time_deleted')
|
||||
};
|
||||
Reference in New Issue
Block a user