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 SqliteAdapter 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,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SqliteAdapter = void 0;
const dialect_adapter_base_js_1 = require("../dialect-adapter-base.js");
class SqliteAdapter extends dialect_adapter_base_js_1.DialectAdapterBase {
get supportsTransactionalDdl() {
return false;
}
get supportsReturning() {
return true;
}
async acquireMigrationLock(_db, _opt) {
// SQLite only has one connection that's reserved by the migration system
// for the whole time between acquireMigrationLock and releaseMigrationLock.
// We don't need to do anything here.
}
async releaseMigrationLock(_db, _opt) {
// SQLite only has one connection that's reserved by the migration system
// for the whole time between acquireMigrationLock and releaseMigrationLock.
// We don't need to do anything here.
}
}
exports.SqliteAdapter = SqliteAdapter;

View File

@@ -0,0 +1,41 @@
import type { DatabaseConnection } from '../../driver/database-connection.js';
/**
* Config for the SQLite dialect.
*/
export interface SqliteDialectConfig {
/**
* An sqlite Database instance or a function that returns one.
*
* If a function is provided, it's called once when the first query is executed.
*
* https://github.com/JoshuaWise/better-sqlite3/blob/master/docs/api.md#new-databasepath-options
*/
database: SqliteDatabase | (() => Promise<SqliteDatabase>);
/**
* Called once when the first query is executed.
*
* This is a Kysely specific feature and does not come from the `better-sqlite3` module.
*/
onCreateConnection?: (connection: DatabaseConnection) => Promise<void>;
}
/**
* This interface is the subset of better-sqlite3 driver's `Database` class that
* kysely needs.
*
* We don't use the type from `better-sqlite3` here to not have a dependency to it.
*
* https://github.com/JoshuaWise/better-sqlite3/blob/master/docs/api.md#new-databasepath-options
*/
export interface SqliteDatabase {
close(): void;
prepare(sql: string): SqliteStatement;
}
export interface SqliteStatement {
readonly reader: boolean;
all(parameters: ReadonlyArray<unknown>): unknown[];
run(parameters: ReadonlyArray<unknown>): {
changes: number | bigint;
lastInsertRowid: number | bigint;
};
iterate(parameters: ReadonlyArray<unknown>): IterableIterator<unknown>;
}

View File

@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

View File

@@ -0,0 +1,55 @@
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 { SqliteDialectConfig } from './sqlite-dialect-config.js';
/**
* SQLite dialect that uses the [better-sqlite3](https://github.com/JoshuaWise/better-sqlite3) library.
*
* The constructor takes an instance of {@link SqliteDialectConfig}.
*
* ```ts
* import Database from 'better-sqlite3'
*
* new SqliteDialect({
* database: new Database('db.sqlite')
* })
* ```
*
* If you want the pool to only be created once it's first used, `database`
* can be a function:
*
* ```ts
* import Database from 'better-sqlite3'
*
* new SqliteDialect({
* database: async () => new Database('db.sqlite')
* })
* ```
*/
export declare class SqliteDialect implements Dialect {
#private;
constructor(config: SqliteDialectConfig);
/**
* 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,51 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SqliteDialect = void 0;
const sqlite_driver_js_1 = require("./sqlite-driver.js");
const sqlite_query_compiler_js_1 = require("./sqlite-query-compiler.js");
const sqlite_introspector_js_1 = require("./sqlite-introspector.js");
const sqlite_adapter_js_1 = require("./sqlite-adapter.js");
const object_utils_js_1 = require("../../util/object-utils.js");
/**
* SQLite dialect that uses the [better-sqlite3](https://github.com/JoshuaWise/better-sqlite3) library.
*
* The constructor takes an instance of {@link SqliteDialectConfig}.
*
* ```ts
* import Database from 'better-sqlite3'
*
* new SqliteDialect({
* database: new Database('db.sqlite')
* })
* ```
*
* If you want the pool to only be created once it's first used, `database`
* can be a function:
*
* ```ts
* import Database from 'better-sqlite3'
*
* new SqliteDialect({
* database: async () => new Database('db.sqlite')
* })
* ```
*/
class SqliteDialect {
#config;
constructor(config) {
this.#config = (0, object_utils_js_1.freeze)({ ...config });
}
createDriver() {
return new sqlite_driver_js_1.SqliteDriver(this.#config);
}
createQueryCompiler() {
return new sqlite_query_compiler_js_1.SqliteQueryCompiler();
}
createAdapter() {
return new sqlite_adapter_js_1.SqliteAdapter();
}
createIntrospector(db) {
return new sqlite_introspector_js_1.SqliteIntrospector(db);
}
}
exports.SqliteDialect = SqliteDialect;

View File

@@ -0,0 +1,42 @@
import type { DatabaseConnection } from '../../driver/database-connection.js';
import type { Driver } from '../../driver/driver.js';
import type { QueryCompiler } from '../../query-compiler/query-compiler.js';
import type { SqliteDialectConfig } from './sqlite-dialect-config.js';
export declare class SqliteDriver implements Driver {
#private;
constructor(config: SqliteDialectConfig);
/**
* 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): 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(): Promise<void>;
/**
* Destroys the driver and releases all resources.
*/
destroy(): Promise<void>;
}

View File

@@ -0,0 +1,113 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SqliteDriver = void 0;
const select_query_node_js_1 = require("../../operation-node/select-query-node.js");
const savepoint_parser_js_1 = require("../../parser/savepoint-parser.js");
const compiled_query_js_1 = require("../../query-compiler/compiled-query.js");
const object_utils_js_1 = require("../../util/object-utils.js");
const query_id_js_1 = require("../../util/query-id.js");
class SqliteDriver {
#config;
#connectionMutex = new ConnectionMutex();
#db;
#connection;
constructor(config) {
this.#config = (0, object_utils_js_1.freeze)({ ...config });
}
async init() {
this.#db = (0, object_utils_js_1.isFunction)(this.#config.database)
? await this.#config.database()
: this.#config.database;
this.#connection = new SqliteConnection(this.#db);
if (this.#config.onCreateConnection) {
await this.#config.onCreateConnection(this.#connection);
}
}
async acquireConnection() {
// SQLite only has one single connection. We use a mutex here to wait
// until the single connection has been released.
await this.#connectionMutex.lock();
return this.#connection;
}
async beginTransaction(connection) {
await connection.executeQuery(compiled_query_js_1.CompiledQuery.raw('begin'));
}
async commitTransaction(connection) {
await connection.executeQuery(compiled_query_js_1.CompiledQuery.raw('commit'));
}
async rollbackTransaction(connection) {
await connection.executeQuery(compiled_query_js_1.CompiledQuery.raw('rollback'));
}
async savepoint(connection, savepointName, compileQuery) {
await connection.executeQuery(compileQuery((0, savepoint_parser_js_1.parseSavepointCommand)('savepoint', savepointName), (0, query_id_js_1.createQueryId)()));
}
async rollbackToSavepoint(connection, savepointName, compileQuery) {
await connection.executeQuery(compileQuery((0, savepoint_parser_js_1.parseSavepointCommand)('rollback to', savepointName), (0, query_id_js_1.createQueryId)()));
}
async releaseSavepoint(connection, savepointName, compileQuery) {
await connection.executeQuery(compileQuery((0, savepoint_parser_js_1.parseSavepointCommand)('release', savepointName), (0, query_id_js_1.createQueryId)()));
}
async releaseConnection() {
this.#connectionMutex.unlock();
}
async destroy() {
this.#db?.close();
}
}
exports.SqliteDriver = SqliteDriver;
class SqliteConnection {
#db;
constructor(db) {
this.#db = db;
}
executeQuery(compiledQuery) {
const { sql, parameters } = compiledQuery;
const stmt = this.#db.prepare(sql);
if (stmt.reader) {
return Promise.resolve({
rows: stmt.all(parameters),
});
}
const { changes, lastInsertRowid } = stmt.run(parameters);
return Promise.resolve({
numAffectedRows: changes !== undefined && changes !== null ? BigInt(changes) : undefined,
insertId: lastInsertRowid !== undefined && lastInsertRowid !== null
? BigInt(lastInsertRowid)
: undefined,
rows: [],
});
}
async *streamQuery(compiledQuery, _chunkSize) {
const { sql, parameters, query } = compiledQuery;
const stmt = this.#db.prepare(sql);
if (select_query_node_js_1.SelectQueryNode.is(query)) {
const iter = stmt.iterate(parameters);
for (const row of iter) {
yield {
rows: [row],
};
}
}
else {
throw new Error('Sqlite driver only supports streaming of select queries');
}
}
}
class ConnectionMutex {
#promise;
#resolve;
async lock() {
while (this.#promise) {
await this.#promise;
}
this.#promise = new Promise((resolve) => {
this.#resolve = resolve;
});
}
unlock() {
const resolve = this.#resolve;
this.#promise = undefined;
this.#resolve = undefined;
resolve?.();
}
}

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 SqliteIntrospector 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,94 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SqliteIntrospector = void 0;
const migrator_js_1 = require("../../migration/migrator.js");
const sql_js_1 = require("../../raw-builder/sql.js");
class SqliteIntrospector {
#db;
constructor(db) {
this.#db = db;
}
async getSchemas() {
// Sqlite doesn't support schemas.
return [];
}
async getTables(options = { withInternalKyselyTables: false }) {
return await this.#getTableMetadata(options);
}
async getMetadata(options) {
return {
tables: await this.getTables(options),
};
}
#tablesQuery(qb, options) {
let tablesQuery = qb
.selectFrom('sqlite_master')
.where('type', 'in', ['table', 'view'])
.where('name', 'not like', 'sqlite_%')
.select(['name', 'sql', 'type'])
.orderBy('name');
if (!options.withInternalKyselyTables) {
tablesQuery = tablesQuery
.where('name', '!=', migrator_js_1.DEFAULT_MIGRATION_TABLE)
.where('name', '!=', migrator_js_1.DEFAULT_MIGRATION_LOCK_TABLE);
}
return tablesQuery;
}
async #getTableMetadata(options) {
const tablesResult = await this.#tablesQuery(this.#db, options).execute();
const tableMetadata = await this.#db
.with('table_list', (qb) => this.#tablesQuery(qb, options))
.selectFrom([
'table_list as tl',
(0, sql_js_1.sql) `pragma_table_info(tl.name)`.as('p'),
])
.select([
'tl.name as table',
'p.cid',
'p.name',
'p.type',
'p.notnull',
'p.dflt_value',
'p.pk',
])
.orderBy('tl.name')
.orderBy('p.cid')
.execute();
const columnsByTable = {};
for (const row of tableMetadata) {
columnsByTable[row.table] ??= [];
columnsByTable[row.table].push(row);
}
return tablesResult.map(({ name, sql, type }) => {
// // Try to find the name of the column that has `autoincrement` 🤦
let autoIncrementCol = sql
?.split(/[\(\),]/)
?.find((it) => it.toLowerCase().includes('autoincrement'))
?.trimStart()
?.split(/\s+/)?.[0]
?.replace(/["`]/g, '');
const columns = columnsByTable[name] ?? [];
// Otherwise, check for an INTEGER PRIMARY KEY
// https://www.sqlite.org/autoinc.html
if (!autoIncrementCol) {
const pkCols = columns.filter((r) => r.pk > 0);
if (pkCols.length === 1 && pkCols[0].type.toLowerCase() === 'integer') {
autoIncrementCol = pkCols[0].name;
}
}
return {
name: name,
isView: type === 'view',
columns: columns.map((col) => ({
name: col.name,
dataType: col.type,
isNullable: !col.notnull,
isAutoIncrementing: col.name === autoIncrementCol,
hasDefaultValue: col.dflt_value != null,
comment: undefined,
})),
};
});
}
}
exports.SqliteIntrospector = SqliteIntrospector;

View File

@@ -0,0 +1,14 @@
import type { DefaultInsertValueNode } from '../../operation-node/default-insert-value-node.js';
import type { OrActionNode } from '../../operation-node/or-action-node.js';
import { DefaultQueryCompiler } from '../../query-compiler/default-query-compiler.js';
export declare class SqliteQueryCompiler extends DefaultQueryCompiler {
protected visitOrAction(node: OrActionNode): void;
protected getCurrentParameterPlaceholder(): string;
protected getLeftExplainOptionsWrapper(): string;
protected getRightExplainOptionsWrapper(): string;
protected getLeftIdentifierWrapper(): string;
protected getRightIdentifierWrapper(): string;
protected getAutoIncrement(): string;
protected sanitizeIdentifier(identifier: string): string;
protected visitDefaultInsertValue(_: DefaultInsertValueNode): void;
}

View File

@@ -0,0 +1,37 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SqliteQueryCompiler = void 0;
const default_query_compiler_js_1 = require("../../query-compiler/default-query-compiler.js");
const ID_WRAP_REGEX = /"/g;
class SqliteQueryCompiler extends default_query_compiler_js_1.DefaultQueryCompiler {
visitOrAction(node) {
this.append('or ');
this.append(node.action);
}
getCurrentParameterPlaceholder() {
return '?';
}
getLeftExplainOptionsWrapper() {
return '';
}
getRightExplainOptionsWrapper() {
return '';
}
getLeftIdentifierWrapper() {
return '"';
}
getRightIdentifierWrapper() {
return '"';
}
getAutoIncrement() {
return 'autoincrement';
}
sanitizeIdentifier(identifier) {
return identifier.replace(ID_WRAP_REGEX, '""');
}
visitDefaultInsertValue(_) {
// sqlite doesn't support the `default` keyword in inserts.
this.append('null');
}
}
exports.SqliteQueryCompiler = SqliteQueryCompiler;