Initial commit - Event Planner application

This commit is contained in:
mberlin
2026-03-18 14:55:56 -03:00
commit 86d779eb4d
7548 changed files with 1006324 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
import type { Kysely } from '../../kysely.js';
import { DialectAdapterBase } from '../dialect-adapter-base.js';
import type { MigrationLockOptions } from '../dialect-adapter.js';
export declare class PostgresAdapter extends DialectAdapterBase {
/**
* Whether or not this dialect supports transactional DDL.
*
* If this is true, migrations are executed inside a transaction.
*/
get supportsTransactionalDdl(): boolean;
/**
* Whether or not this dialect supports the `returning` in inserts
* updates and deletes.
*/
get supportsReturning(): boolean;
/**
* This method is used to acquire a lock for the migrations so that
* it's not possible for two migration operations to run in parallel.
*
* Most dialects have explicit locks that can be used, like advisory locks
* in PostgreSQL and the get_lock function in MySQL.
*
* If the dialect doesn't have explicit locks the {@link MigrationLockOptions.lockTable}
* created by Kysely can be used instead. You can access it through the `options` object.
* The lock table has two columns `id` and `is_locked` and there's only one row in the table
* whose id is {@link MigrationLockOptions.lockRowId}. `is_locked` is an integer. Kysely
* takes care of creating the lock table and inserting the one single row to it before this
* method is executed. If the dialect supports schemas and the user has specified a custom
* schema in their migration settings, the options object also contains the schema name in
* {@link MigrationLockOptions.lockTableSchema}.
*
* Here's an example of how you might implement this method for a dialect that doesn't
* have explicit locks but supports `FOR UPDATE` row locks and transactional DDL:
*
* ```ts
* import { DialectAdapterBase, type MigrationLockOptions, Kysely } from 'kysely'
*
* export class MyAdapter extends DialectAdapterBase {
* override async acquireMigrationLock(
* db: Kysely<any>,
* options: MigrationLockOptions
* ): Promise<void> {
* const queryDb = options.lockTableSchema
* ? db.withSchema(options.lockTableSchema)
* : db
*
* // Since our imaginary dialect supports transactional DDL and has
* // row locks, we can simply take a row lock here and it will guarantee
* // all subsequent calls to this method from other transactions will
* // wait until this transaction finishes.
* await queryDb
* .selectFrom(options.lockTable)
* .selectAll()
* .where('id', '=', options.lockRowId)
* .forUpdate()
* .execute()
* }
*
* override async releaseMigrationLock() {
* // noop
* }
* }
* ```
*
* If `supportsTransactionalDdl` is `true` then the `db` passed to this method
* is a transaction inside which the migrations will be executed. Otherwise
* `db` is a single connection (session) that will be used to execute the
* migrations.
*/
acquireMigrationLock(db: Kysely<any>, _opt: MigrationLockOptions): Promise<void>;
/**
* Releases the migration lock. See {@link acquireMigrationLock}.
*
* If `supportsTransactionalDdl` is `true` then the `db` passed to this method
* is a transaction inside which the migrations were executed. Otherwise `db`
* is a single connection (session) that was used to execute the migrations
* and the `acquireMigrationLock` call.
*/
releaseMigrationLock(_db: Kysely<any>, _opt: MigrationLockOptions): Promise<void>;
}

View File

@@ -0,0 +1,22 @@
/// <reference types="./postgres-adapter.d.ts" />
import { sql } from '../../raw-builder/sql.js';
import { DialectAdapterBase } from '../dialect-adapter-base.js';
// Random id for our transaction lock.
const LOCK_ID = BigInt('3853314791062309107');
export class PostgresAdapter extends DialectAdapterBase {
get supportsTransactionalDdl() {
return true;
}
get supportsReturning() {
return true;
}
async acquireMigrationLock(db, _opt) {
// Acquire a transaction level advisory lock.
await sql `select pg_advisory_xact_lock(${sql.lit(LOCK_ID)})`.execute(db);
}
async releaseMigrationLock(_db, _opt) {
// Nothing to do here. `pg_advisory_xact_lock` is automatically released at the
// end of the transaction and since `supportsTransactionalDdl` true, we know
// the `db` instance passed to acquireMigrationLock is actually a transaction.
}
}

View File

@@ -0,0 +1,68 @@
import type { DatabaseConnection } from '../../driver/database-connection.js';
/**
* Config for the PostgreSQL dialect.
*/
export interface PostgresDialectConfig {
/**
* A postgres Pool instance or a function that returns one.
*
* If a function is provided, it's called once when the first query is executed.
*
* https://node-postgres.com/apis/pool
*/
pool: PostgresPool | (() => Promise<PostgresPool>);
/**
* https://github.com/brianc/node-postgres/tree/master/packages/pg-cursor
*
* ```ts
* import { PostgresDialect } from 'kysely'
* import { Pool } from 'pg'
* import Cursor from 'pg-cursor'
* // or import * as Cursor from 'pg-cursor'
*
* new PostgresDialect({
* cursor: Cursor,
* pool: new Pool('postgres://localhost:5432/mydb')
* })
* ```
*/
cursor?: PostgresCursorConstructor;
/**
* Called once for each created connection.
*/
onCreateConnection?: (connection: DatabaseConnection) => Promise<void>;
/**
* Called every time a connection is acquired from the pool.
*/
onReserveConnection?: (connection: DatabaseConnection) => Promise<void>;
}
/**
* This interface is the subset of pg driver's `Pool` class that
* kysely needs.
*
* We don't use the type from `pg` here to not have a dependency to it.
*
* https://node-postgres.com/apis/pool
*/
export interface PostgresPool {
connect(): Promise<PostgresPoolClient>;
end(): Promise<void>;
}
export interface PostgresPoolClient {
query<R>(sql: string, parameters: ReadonlyArray<unknown>): Promise<PostgresQueryResult<R>>;
query<R>(cursor: PostgresCursor<R>): PostgresCursor<R>;
release(): void;
}
export interface PostgresCursor<T> {
read(rowsCount: number): Promise<T[]>;
close(): Promise<void>;
}
export type PostgresCursorConstructor = new <T>(sql: string, parameters: unknown[]) => PostgresCursor<T>;
export interface PostgresQueryResult<R> {
command: 'UPDATE' | 'DELETE' | 'INSERT' | 'SELECT' | 'MERGE';
rowCount: number;
rows: R[];
}
export interface PostgresStream<T> {
[Symbol.asyncIterator](): AsyncIterableIterator<T>;
}

View File

@@ -0,0 +1,2 @@
/// <reference types="./postgres-dialect-config.d.ts" />
export {};

View File

@@ -0,0 +1,61 @@
import type { Driver } from '../../driver/driver.js';
import type { Kysely } from '../../kysely.js';
import type { QueryCompiler } from '../../query-compiler/query-compiler.js';
import type { Dialect } from '../dialect.js';
import type { DatabaseIntrospector } from '../database-introspector.js';
import type { DialectAdapter } from '../dialect-adapter.js';
import type { PostgresDialectConfig } from './postgres-dialect-config.js';
/**
* PostgreSQL dialect that uses the [pg](https://node-postgres.com/) library.
*
* The constructor takes an instance of {@link PostgresDialectConfig}.
*
* ```ts
* import { Pool } from 'pg'
*
* new PostgresDialect({
* pool: new Pool({
* database: 'some_db',
* host: 'localhost',
* })
* })
* ```
*
* If you want the pool to only be created once it's first used, `pool`
* can be a function:
*
* ```ts
* import { Pool } from 'pg'
*
* new PostgresDialect({
* pool: async () => new Pool({
* database: 'some_db',
* host: 'localhost',
* })
* })
* ```
*/
export declare class PostgresDialect implements Dialect {
#private;
constructor(config: PostgresDialectConfig);
/**
* Creates a driver for the dialect.
*/
createDriver(): Driver;
/**
* Creates a query compiler for the dialect.
*/
createQueryCompiler(): QueryCompiler;
/**
* Creates an adapter for the dialect.
*/
createAdapter(): DialectAdapter;
/**
* Creates a database introspector that can be used to get database metadata
* such as the table names and column names of those tables.
*
* `db` never has any plugins installed. It's created using
* {@link Kysely.withoutPlugins}.
*/
createIntrospector(db: Kysely<any>): DatabaseIntrospector;
}

View File

@@ -0,0 +1,53 @@
/// <reference types="./postgres-dialect.d.ts" />
import { PostgresDriver } from './postgres-driver.js';
import { PostgresIntrospector } from './postgres-introspector.js';
import { PostgresQueryCompiler } from './postgres-query-compiler.js';
import { PostgresAdapter } from './postgres-adapter.js';
/**
* PostgreSQL dialect that uses the [pg](https://node-postgres.com/) library.
*
* The constructor takes an instance of {@link PostgresDialectConfig}.
*
* ```ts
* import { Pool } from 'pg'
*
* new PostgresDialect({
* pool: new Pool({
* database: 'some_db',
* host: 'localhost',
* })
* })
* ```
*
* If you want the pool to only be created once it's first used, `pool`
* can be a function:
*
* ```ts
* import { Pool } from 'pg'
*
* new PostgresDialect({
* pool: async () => new Pool({
* database: 'some_db',
* host: 'localhost',
* })
* })
* ```
*/
export class PostgresDialect {
#config;
constructor(config) {
this.#config = config;
}
createDriver() {
return new PostgresDriver(this.#config);
}
createQueryCompiler() {
return new PostgresQueryCompiler();
}
createAdapter() {
return new PostgresAdapter();
}
createIntrospector(db) {
return new PostgresIntrospector(db);
}
}

View File

@@ -0,0 +1,55 @@
import type { DatabaseConnection, QueryResult } from '../../driver/database-connection.js';
import type { Driver, TransactionSettings } from '../../driver/driver.js';
import { CompiledQuery } from '../../query-compiler/compiled-query.js';
import type { QueryCompiler } from '../../query-compiler/query-compiler.js';
import type { PostgresCursorConstructor, PostgresDialectConfig, PostgresPoolClient } from './postgres-dialect-config.js';
declare const PRIVATE_RELEASE_METHOD: unique symbol;
export declare class PostgresDriver implements Driver {
#private;
constructor(config: PostgresDialectConfig);
/**
* Initializes the driver.
*
* After calling this method the driver should be usable and `acquireConnection` etc.
* methods should be callable.
*/
init(): Promise<void>;
/**
* Acquires a new connection from the pool.
*/
acquireConnection(): Promise<DatabaseConnection>;
/**
* Begins a transaction.
*/
beginTransaction(connection: DatabaseConnection, settings: TransactionSettings): Promise<void>;
/**
* Commits a transaction.
*/
commitTransaction(connection: DatabaseConnection): Promise<void>;
/**
* Rolls back a transaction.
*/
rollbackTransaction(connection: DatabaseConnection): Promise<void>;
savepoint(connection: DatabaseConnection, savepointName: string, compileQuery: QueryCompiler['compileQuery']): Promise<void>;
rollbackToSavepoint(connection: DatabaseConnection, savepointName: string, compileQuery: QueryCompiler['compileQuery']): Promise<void>;
releaseSavepoint(connection: DatabaseConnection, savepointName: string, compileQuery: QueryCompiler['compileQuery']): Promise<void>;
/**
* Releases a connection back to the pool.
*/
releaseConnection(connection: PostgresConnection): Promise<void>;
/**
* Destroys the driver and releases all resources.
*/
destroy(): Promise<void>;
}
interface PostgresConnectionOptions {
cursor: PostgresCursorConstructor | null;
}
declare class PostgresConnection implements DatabaseConnection {
#private;
constructor(client: PostgresPoolClient, options: PostgresConnectionOptions);
executeQuery<O>(compiledQuery: CompiledQuery): Promise<QueryResult<O>>;
streamQuery<O>(compiledQuery: CompiledQuery, chunkSize: number): AsyncIterableIterator<QueryResult<O>>;
[PRIVATE_RELEASE_METHOD](): void;
}
export {};

View File

@@ -0,0 +1,131 @@
/// <reference types="./postgres-driver.d.ts" />
import { parseSavepointCommand } from '../../parser/savepoint-parser.js';
import { CompiledQuery } from '../../query-compiler/compiled-query.js';
import { isFunction, freeze } from '../../util/object-utils.js';
import { createQueryId } from '../../util/query-id.js';
import { extendStackTrace } from '../../util/stack-trace-utils.js';
const PRIVATE_RELEASE_METHOD = Symbol();
export class PostgresDriver {
#config;
#connections = new WeakMap();
#pool;
constructor(config) {
this.#config = freeze({ ...config });
}
async init() {
this.#pool = isFunction(this.#config.pool)
? await this.#config.pool()
: this.#config.pool;
}
async acquireConnection() {
const client = await this.#pool.connect();
let connection = this.#connections.get(client);
if (!connection) {
connection = new PostgresConnection(client, {
cursor: this.#config.cursor ?? null,
});
this.#connections.set(client, connection);
// The driver must take care of calling `onCreateConnection` when a new
// connection is created. The `pg` module doesn't provide an async hook
// for the connection creation. We need to call the method explicitly.
if (this.#config.onCreateConnection) {
await this.#config.onCreateConnection(connection);
}
}
if (this.#config.onReserveConnection) {
await this.#config.onReserveConnection(connection);
}
return connection;
}
async beginTransaction(connection, settings) {
if (settings.isolationLevel || settings.accessMode) {
let sql = 'start transaction';
if (settings.isolationLevel) {
sql += ` isolation level ${settings.isolationLevel}`;
}
if (settings.accessMode) {
sql += ` ${settings.accessMode}`;
}
await connection.executeQuery(CompiledQuery.raw(sql));
}
else {
await connection.executeQuery(CompiledQuery.raw('begin'));
}
}
async commitTransaction(connection) {
await connection.executeQuery(CompiledQuery.raw('commit'));
}
async rollbackTransaction(connection) {
await connection.executeQuery(CompiledQuery.raw('rollback'));
}
async savepoint(connection, savepointName, compileQuery) {
await connection.executeQuery(compileQuery(parseSavepointCommand('savepoint', savepointName), createQueryId()));
}
async rollbackToSavepoint(connection, savepointName, compileQuery) {
await connection.executeQuery(compileQuery(parseSavepointCommand('rollback to', savepointName), createQueryId()));
}
async releaseSavepoint(connection, savepointName, compileQuery) {
await connection.executeQuery(compileQuery(parseSavepointCommand('release', savepointName), createQueryId()));
}
async releaseConnection(connection) {
connection[PRIVATE_RELEASE_METHOD]();
}
async destroy() {
if (this.#pool) {
const pool = this.#pool;
this.#pool = undefined;
await pool.end();
}
}
}
class PostgresConnection {
#client;
#options;
constructor(client, options) {
this.#client = client;
this.#options = options;
}
async executeQuery(compiledQuery) {
try {
const { command, rowCount, rows } = await this.#client.query(compiledQuery.sql, [...compiledQuery.parameters]);
return {
numAffectedRows: command === 'INSERT' ||
command === 'UPDATE' ||
command === 'DELETE' ||
command === 'MERGE'
? BigInt(rowCount)
: undefined,
rows: rows ?? [],
};
}
catch (err) {
throw extendStackTrace(err, new Error());
}
}
async *streamQuery(compiledQuery, chunkSize) {
if (!this.#options.cursor) {
throw new Error("'cursor' is not present in your postgres dialect config. It's required to make streaming work in postgres.");
}
if (!Number.isInteger(chunkSize) || chunkSize <= 0) {
throw new Error('chunkSize must be a positive integer');
}
const cursor = this.#client.query(new this.#options.cursor(compiledQuery.sql, compiledQuery.parameters.slice()));
try {
while (true) {
const rows = await cursor.read(chunkSize);
if (rows.length === 0) {
break;
}
yield {
rows,
};
}
}
finally {
await cursor.close();
}
}
[PRIVATE_RELEASE_METHOD]() {
this.#client.release();
}
}

View File

@@ -0,0 +1,20 @@
import type { DatabaseIntrospector, DatabaseMetadata, DatabaseMetadataOptions, SchemaMetadata, TableMetadata } from '../database-introspector.js';
import type { Kysely } from '../../kysely.js';
export declare class PostgresIntrospector implements DatabaseIntrospector {
#private;
constructor(db: Kysely<any>);
/**
* Get schema metadata.
*/
getSchemas(): Promise<SchemaMetadata[]>;
/**
* Get tables and views metadata.
*/
getTables(options?: DatabaseMetadataOptions): Promise<TableMetadata[]>;
/**
* Get the database metadata such as table and column names.
*
* @deprecated Use getTables() instead.
*/
getMetadata(options?: DatabaseMetadataOptions): Promise<DatabaseMetadata>;
}

View File

@@ -0,0 +1,97 @@
/// <reference types="./postgres-introspector.d.ts" />
import { DEFAULT_MIGRATION_LOCK_TABLE, DEFAULT_MIGRATION_TABLE, } from '../../migration/migrator.js';
import { freeze } from '../../util/object-utils.js';
import { sql } from '../../raw-builder/sql.js';
export class PostgresIntrospector {
#db;
constructor(db) {
this.#db = db;
}
async getSchemas() {
let rawSchemas = await this.#db
.selectFrom('pg_catalog.pg_namespace')
.select('nspname')
.$castTo()
.execute();
return rawSchemas.map((it) => ({ name: it.nspname }));
}
async getTables(options = { withInternalKyselyTables: false }) {
let query = this.#db
// column
.selectFrom('pg_catalog.pg_attribute as a')
// table
.innerJoin('pg_catalog.pg_class as c', 'a.attrelid', 'c.oid')
// table schema
.innerJoin('pg_catalog.pg_namespace as ns', 'c.relnamespace', 'ns.oid')
// column data type
.innerJoin('pg_catalog.pg_type as typ', 'a.atttypid', 'typ.oid')
// column data type schema
.innerJoin('pg_catalog.pg_namespace as dtns', 'typ.typnamespace', 'dtns.oid')
.select([
'a.attname as column',
'a.attnotnull as not_null',
'a.atthasdef as has_default',
'c.relname as table',
'c.relkind as table_type',
'ns.nspname as schema',
'typ.typname as type',
'dtns.nspname as type_schema',
sql `col_description(a.attrelid, a.attnum)`.as('column_description'),
sql `pg_get_serial_sequence(quote_ident(ns.nspname) || '.' || quote_ident(c.relname), a.attname)`.as('auto_incrementing'),
])
.where('c.relkind', 'in', [
'r' /*regular table*/,
'v' /*view*/,
'p' /*partitioned table*/,
])
.where('ns.nspname', '!~', '^pg_')
.where('ns.nspname', '!=', 'information_schema')
// Filter out internal cockroachdb schema
.where('ns.nspname', '!=', 'crdb_internal')
// Only schemas where we are allowed access
.where(sql `has_schema_privilege(ns.nspname, 'USAGE')`)
// No system columns
.where('a.attnum', '>=', 0)
.where('a.attisdropped', '!=', true)
.orderBy('ns.nspname')
.orderBy('c.relname')
.orderBy('a.attnum')
.$castTo();
if (!options.withInternalKyselyTables) {
query = query
.where('c.relname', '!=', DEFAULT_MIGRATION_TABLE)
.where('c.relname', '!=', DEFAULT_MIGRATION_LOCK_TABLE);
}
const rawColumns = await query.execute();
return this.#parseTableMetadata(rawColumns);
}
async getMetadata(options) {
return {
tables: await this.getTables(options),
};
}
#parseTableMetadata(columns) {
return columns.reduce((tables, it) => {
let table = tables.find((tbl) => tbl.name === it.table && tbl.schema === it.schema);
if (!table) {
table = freeze({
name: it.table,
isView: it.table_type === 'v',
schema: it.schema,
columns: [],
});
tables.push(table);
}
table.columns.push(freeze({
name: it.column,
dataType: it.type,
dataTypeSchema: it.type_schema,
isNullable: !it.not_null,
isAutoIncrementing: it.auto_incrementing !== null,
hasDefaultValue: it.has_default,
comment: it.column_description ?? undefined,
}));
return tables;
}, []);
}
}

View File

@@ -0,0 +1,4 @@
import { DefaultQueryCompiler } from '../../query-compiler/default-query-compiler.js';
export declare class PostgresQueryCompiler extends DefaultQueryCompiler {
protected sanitizeIdentifier(identifier: string): string;
}

View File

@@ -0,0 +1,8 @@
/// <reference types="./postgres-query-compiler.d.ts" />
import { DefaultQueryCompiler } from '../../query-compiler/default-query-compiler.js';
const ID_WRAP_REGEX = /"/g;
export class PostgresQueryCompiler extends DefaultQueryCompiler {
sanitizeIdentifier(identifier) {
return identifier.replace(ID_WRAP_REGEX, '""');
}
}