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,170 @@
import type { QueryResult } from '../../driver/database-connection.js';
import type { RootOperationNode } from '../../query-compiler/query-compiler.js';
import type { UnknownRow } from '../../util/type-utils.js';
import type { KyselyPlugin, PluginTransformQueryArgs, PluginTransformResultArgs } from '../kysely-plugin.js';
export interface CamelCasePluginOptions {
/**
* If true, camelCase is transformed into upper case SNAKE_CASE.
* For example `fooBar => FOO_BAR` and `FOO_BAR => fooBar`
*
* Defaults to false.
*/
upperCase?: boolean;
/**
* If true, an underscore is added before each digit when converting
* camelCase to snake_case. For example `foo12Bar => foo_12_bar` and
* `foo_12_bar => foo12Bar`
*
* Defaults to false.
*/
underscoreBeforeDigits?: boolean;
/**
* If true, an underscore is added between consecutive upper case
* letters when converting from camelCase to snake_case. For example
* `fooBAR => foo_b_a_r` and `foo_b_a_r => fooBAR`.
*
* Defaults to false.
*/
underscoreBetweenUppercaseLetters?: boolean;
/**
* If true, nested object's keys will not be converted to camel case.
*
* Defaults to false.
*/
maintainNestedObjectKeys?: boolean;
}
/**
* A plugin that converts snake_case identifiers in the database into
* camelCase in the JavaScript side.
*
* For example let's assume we have a table called `person_table`
* with columns `first_name` and `last_name` in the database. When
* using `CamelCasePlugin` we would setup Kysely like this:
*
* ```ts
* import * as Sqlite from 'better-sqlite3'
* import { CamelCasePlugin, Kysely, SqliteDialect } from 'kysely'
*
* interface CamelCasedDatabase {
* userMetadata: {
* firstName: string
* lastName: string
* }
* }
*
* const db = new Kysely<CamelCasedDatabase>({
* dialect: new SqliteDialect({
* database: new Sqlite(':memory:'),
* }),
* plugins: [new CamelCasePlugin()],
* })
*
* const person = await db.selectFrom('userMetadata')
* .where('firstName', '=', 'Arnold')
* .select(['firstName', 'lastName'])
* .executeTakeFirst()
*
* if (person) {
* console.log(person.firstName)
* }
* ```
*
* The generated SQL (SQLite):
*
* ```sql
* select "first_name", "last_name" from "user_metadata" where "first_name" = ?
* ```
*
* As you can see from the example, __everything__ needs to be defined
* in camelCase in the TypeScript code: table names, columns, schemas,
* __everything__. When using the `CamelCasePlugin` Kysely works as if
* the database was defined in camelCase.
*
* There are various options you can give to the plugin to modify
* the way identifiers are converted. See {@link CamelCasePluginOptions}.
* If those options are not enough, you can override this plugin's
* `snakeCase` and `camelCase` methods to make the conversion exactly
* the way you like:
*
* ```ts
* class MyCamelCasePlugin extends CamelCasePlugin {
* protected override snakeCase(str: string): string {
* // ...
*
* return str
* }
*
* protected override camelCase(str: string): string {
* // ...
*
* return str
* }
* }
* ```
*/
export declare class CamelCasePlugin implements KyselyPlugin {
#private;
readonly opt: CamelCasePluginOptions;
constructor(opt?: CamelCasePluginOptions);
/**
* This is called for each query before it is executed. You can modify the query by
* transforming its {@link OperationNode} tree provided in {@link PluginTransformQueryArgs.node | args.node}
* and returning the transformed tree. You'd usually want to use an {@link OperationNodeTransformer}
* for this.
*
* If you need to pass some query-related data between this method and `transformResult` you
* can use a `WeakMap` with {@link PluginTransformQueryArgs.queryId | args.queryId} as the key:
*
* ```ts
* import type {
* KyselyPlugin,
* QueryResult,
* RootOperationNode,
* UnknownRow
* } from 'kysely'
*
* interface MyData {
* // ...
* }
* const data = new WeakMap<any, MyData>()
*
* const plugin = {
* transformQuery(args: PluginTransformQueryArgs): RootOperationNode {
* const something: MyData = {}
*
* // ...
*
* data.set(args.queryId, something)
*
* // ...
*
* return args.node
* },
*
* async transformResult(args: PluginTransformResultArgs): Promise<QueryResult<UnknownRow>> {
* // ...
*
* const something = data.get(args.queryId)
*
* // ...
*
* return args.result
* }
* } satisfies KyselyPlugin
* ```
*
* You should use a `WeakMap` instead of a `Map` or some other strong references because `transformQuery`
* is not always matched by a call to `transformResult` which would leave orphaned items in the map
* and cause a memory leak.
*/
transformQuery(args: PluginTransformQueryArgs): RootOperationNode;
/**
* This method is called for each query after it has been executed. The result
* of the query can be accessed through {@link PluginTransformResultArgs.result | args.result}.
* You can modify the result and return the modifier result.
*/
transformResult(args: PluginTransformResultArgs): Promise<QueryResult<UnknownRow>>;
protected mapRow(row: UnknownRow): UnknownRow;
protected snakeCase(str: string): string;
protected camelCase(str: string): string;
}

View File

@@ -0,0 +1,119 @@
/// <reference types="./camel-case-plugin.d.ts" />
import { isPlainObject } from '../../util/object-utils.js';
import { SnakeCaseTransformer } from './camel-case-transformer.js';
import { createCamelCaseMapper, createSnakeCaseMapper, } from './camel-case.js';
/**
* A plugin that converts snake_case identifiers in the database into
* camelCase in the JavaScript side.
*
* For example let's assume we have a table called `person_table`
* with columns `first_name` and `last_name` in the database. When
* using `CamelCasePlugin` we would setup Kysely like this:
*
* ```ts
* import * as Sqlite from 'better-sqlite3'
* import { CamelCasePlugin, Kysely, SqliteDialect } from 'kysely'
*
* interface CamelCasedDatabase {
* userMetadata: {
* firstName: string
* lastName: string
* }
* }
*
* const db = new Kysely<CamelCasedDatabase>({
* dialect: new SqliteDialect({
* database: new Sqlite(':memory:'),
* }),
* plugins: [new CamelCasePlugin()],
* })
*
* const person = await db.selectFrom('userMetadata')
* .where('firstName', '=', 'Arnold')
* .select(['firstName', 'lastName'])
* .executeTakeFirst()
*
* if (person) {
* console.log(person.firstName)
* }
* ```
*
* The generated SQL (SQLite):
*
* ```sql
* select "first_name", "last_name" from "user_metadata" where "first_name" = ?
* ```
*
* As you can see from the example, __everything__ needs to be defined
* in camelCase in the TypeScript code: table names, columns, schemas,
* __everything__. When using the `CamelCasePlugin` Kysely works as if
* the database was defined in camelCase.
*
* There are various options you can give to the plugin to modify
* the way identifiers are converted. See {@link CamelCasePluginOptions}.
* If those options are not enough, you can override this plugin's
* `snakeCase` and `camelCase` methods to make the conversion exactly
* the way you like:
*
* ```ts
* class MyCamelCasePlugin extends CamelCasePlugin {
* protected override snakeCase(str: string): string {
* // ...
*
* return str
* }
*
* protected override camelCase(str: string): string {
* // ...
*
* return str
* }
* }
* ```
*/
export class CamelCasePlugin {
opt;
#camelCase;
#snakeCase;
#snakeCaseTransformer;
constructor(opt = {}) {
this.opt = opt;
this.#camelCase = createCamelCaseMapper(opt);
this.#snakeCase = createSnakeCaseMapper(opt);
this.#snakeCaseTransformer = new SnakeCaseTransformer(this.snakeCase.bind(this));
}
transformQuery(args) {
return this.#snakeCaseTransformer.transformNode(args.node, args.queryId);
}
async transformResult(args) {
if (args.result.rows && Array.isArray(args.result.rows)) {
return {
...args.result,
rows: args.result.rows.map((row) => this.mapRow(row)),
};
}
return args.result;
}
mapRow(row) {
return Object.keys(row).reduce((obj, key) => {
let value = row[key];
if (Array.isArray(value)) {
value = value.map((it) => (canMap(it, this.opt) ? this.mapRow(it) : it));
}
else if (canMap(value, this.opt)) {
value = this.mapRow(value);
}
obj[this.camelCase(key)] = value;
return obj;
}, {});
}
snakeCase(str) {
return this.#snakeCase(str);
}
camelCase(str) {
return this.#camelCase(str);
}
}
function canMap(obj, opt) {
return isPlainObject(obj) && !opt?.maintainNestedObjectKeys;
}

View File

@@ -0,0 +1,9 @@
import type { IdentifierNode } from '../../operation-node/identifier-node.js';
import { OperationNodeTransformer } from '../../operation-node/operation-node-transformer.js';
import type { QueryId } from '../../util/query-id.js';
import type { StringMapper } from './camel-case.js';
export declare class SnakeCaseTransformer extends OperationNodeTransformer {
#private;
constructor(snakeCase: StringMapper);
protected transformIdentifier(node: IdentifierNode, queryId: QueryId): IdentifierNode;
}

View File

@@ -0,0 +1,16 @@
/// <reference types="./camel-case-transformer.d.ts" />
import { OperationNodeTransformer } from '../../operation-node/operation-node-transformer.js';
export class SnakeCaseTransformer extends OperationNodeTransformer {
#snakeCase;
constructor(snakeCase) {
super();
this.#snakeCase = snakeCase;
}
transformIdentifier(node, queryId) {
node = super.transformIdentifier(node, queryId);
return {
...node,
name: this.#snakeCase(node.name),
};
}
}

View File

@@ -0,0 +1,15 @@
export type StringMapper = (str: string) => string;
/**
* Creates a function that transforms camel case strings to snake case.
*/
export declare function createSnakeCaseMapper({ upperCase, underscoreBeforeDigits, underscoreBetweenUppercaseLetters, }?: {
upperCase?: boolean | undefined;
underscoreBeforeDigits?: boolean | undefined;
underscoreBetweenUppercaseLetters?: boolean | undefined;
}): StringMapper;
/**
* Creates a function that transforms snake case strings to camel case.
*/
export declare function createCamelCaseMapper({ upperCase, }?: {
upperCase?: boolean | undefined;
}): StringMapper;

View File

@@ -0,0 +1,108 @@
/// <reference types="./camel-case.d.ts" />
/**
* Creates a function that transforms camel case strings to snake case.
*/
export function createSnakeCaseMapper({ upperCase = false, underscoreBeforeDigits = false, underscoreBetweenUppercaseLetters = false, } = {}) {
return memoize((str) => {
if (str.length === 0) {
return str;
}
const upper = str.toUpperCase();
const lower = str.toLowerCase();
let out = lower[0];
for (let i = 1, l = str.length; i < l; ++i) {
const char = str[i];
const prevChar = str[i - 1];
const upperChar = upper[i];
const prevUpperChar = upper[i - 1];
const lowerChar = lower[i];
const prevLowerChar = lower[i - 1];
// If underScoreBeforeDigits is true then, well, insert an underscore
// before digits :). Only the first digit gets an underscore if
// there are multiple.
if (underscoreBeforeDigits &&
isDigit(char) &&
!isDigit(prevChar) &&
!out.endsWith('_')) {
out += '_' + char;
continue;
}
// Test if `char` is an upper-case character and that the character
// actually has different upper and lower case versions.
if (char === upperChar && upperChar !== lowerChar) {
const prevCharacterIsUppercase = prevChar === prevUpperChar && prevUpperChar !== prevLowerChar;
// If underscoreBetweenUppercaseLetters is true, we always place an underscore
// before consecutive uppercase letters (e.g. "fooBAR" becomes "foo_b_a_r").
// Otherwise, we don't (e.g. "fooBAR" becomes "foo_bar").
if (underscoreBetweenUppercaseLetters || !prevCharacterIsUppercase) {
out += '_' + lowerChar;
}
else {
out += lowerChar;
}
}
else {
out += char;
}
}
if (upperCase) {
return out.toUpperCase();
}
else {
return out;
}
});
}
/**
* Creates a function that transforms snake case strings to camel case.
*/
export function createCamelCaseMapper({ upperCase = false, } = {}) {
return memoize((str) => {
if (str.length === 0) {
return str;
}
if (upperCase && isAllUpperCaseSnakeCase(str)) {
// Only convert to lower case if the string is all upper
// case snake_case. This allows camelCase strings to go
// through without changing.
str = str.toLowerCase();
}
let out = str[0];
for (let i = 1, l = str.length; i < l; ++i) {
const char = str[i];
const prevChar = str[i - 1];
if (char !== '_') {
if (prevChar === '_') {
out += char.toUpperCase();
}
else {
out += char;
}
}
}
return out;
});
}
function isAllUpperCaseSnakeCase(str) {
for (let i = 1, l = str.length; i < l; ++i) {
const char = str[i];
if (char !== '_' && char !== char.toUpperCase()) {
return false;
}
}
return true;
}
function isDigit(char) {
return char >= '0' && char <= '9';
}
function memoize(func) {
const cache = new Map();
return (str) => {
let mapped = cache.get(str);
if (!mapped) {
mapped = func(str);
cache.set(str, mapped);
}
return mapped;
};
}