Drizzle connector with drizzle model features for plain objects (without relations).
This commit is contained in:
parent
214cb7f1c1
commit
ff793f31f4
13 changed files with 471 additions and 16 deletions
5
.env.test
Normal file
5
.env.test
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
POSTGRES_HOST=localhost
|
||||||
|
POSTGRES_PORT=5432
|
||||||
|
POSTGRES_USERNAME="sharkitek"
|
||||||
|
POSTGRES_PASSWORD="sharkitek"
|
||||||
|
POSTGRES_DATABASE="sharkitek"
|
|
@ -1,10 +0,0 @@
|
||||||
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
|
|
||||||
|
|
||||||
export default {
|
|
||||||
preset: "ts-jest",
|
|
||||||
testEnvironment: "node",
|
|
||||||
|
|
||||||
roots: [
|
|
||||||
"./tests",
|
|
||||||
],
|
|
||||||
};
|
|
14
package.json
14
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@sharkitek/drizzle",
|
"name": "@sharkitek/drizzle",
|
||||||
"version": "1.0.0",
|
"version": "3.0.0-beta",
|
||||||
"description": "Drizzle connector for Sharkitek models.",
|
"description": "Drizzle connector for Sharkitek models.",
|
||||||
"repository": "https://code.zeptotech.net/Sharkitek/Drizzle",
|
"repository": "https://code.zeptotech.net/Sharkitek/Drizzle",
|
||||||
"author": {
|
"author": {
|
||||||
|
@ -10,7 +10,7 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"test": "jest"
|
"test": "vitest"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"source": "src/index.ts",
|
"source": "src/index.ts",
|
||||||
|
@ -23,17 +23,19 @@
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^29.5.13",
|
"@sharkitek/core": "^3.3.0",
|
||||||
"@types/node": "^22.7.4",
|
"@types/node": "^22.7.4",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
"drizzle-orm": "^0.33.0",
|
"drizzle-orm": "^0.33.0",
|
||||||
"jest": "^29.7.0",
|
"postgres": "^3.4.4",
|
||||||
"ts-jest": "^29.2.5",
|
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
"typescript": "^5.6.2",
|
"typescript": "^5.6.2",
|
||||||
"vite": "^5.4.8",
|
"vite": "^5.4.8",
|
||||||
"vite-plugin-dts": "^4.2.3"
|
"vite-plugin-dts": "^4.2.3",
|
||||||
|
"vitest": "^2.1.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
"@sharkitek/core": "^3.0.0",
|
||||||
"drizzle-orm": "^0.33.0"
|
"drizzle-orm": "^0.33.0"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@4.5.0"
|
"packageManager": "yarn@4.5.0"
|
||||||
|
|
151
src/Drizzle.ts
Normal file
151
src/Drizzle.ts
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
import {IdentifierType, Model, ModelShape, SerializedModel} from "@sharkitek/core";
|
||||||
|
import {eq, getTableColumns, Table, TableConfig} from "drizzle-orm";
|
||||||
|
import {PgDatabase} from "drizzle-orm/pg-core";
|
||||||
|
import {PgQueryResultHKT} from "drizzle-orm/pg-core/session";
|
||||||
|
import type {ExtractTablesWithRelations, TablesRelationalConfig} from "drizzle-orm/relations";
|
||||||
|
import {ModelQuery} from "./Query";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialized values converters for database.
|
||||||
|
*/
|
||||||
|
export const serializedToDatabaseTypes: Record<string, (serializedValue: any) => any> = {
|
||||||
|
"date": (serializedValue: string) => new Date(serializedValue),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sharkitek model extension for Drizzle.
|
||||||
|
*/
|
||||||
|
export interface DrizzleExtension<
|
||||||
|
ModelType extends Model<Shape, IdentifierType<Shape, Identifier>>,
|
||||||
|
TableType extends Table<TC>, TC extends TableConfig,
|
||||||
|
TQueryResult extends PgQueryResultHKT, TFullSchema extends Record<string, unknown>,
|
||||||
|
Scopes extends object,
|
||||||
|
Shape extends ModelShape, Identifier extends keyof Shape = any, TSchema extends TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>
|
||||||
|
>
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Drizzle model manager.
|
||||||
|
*/
|
||||||
|
drizzle(): DrizzleModel<ModelType, TableType, TC, TQueryResult, TFullSchema, Scopes, Shape, Identifier, TSchema>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drizzle model manager.
|
||||||
|
*/
|
||||||
|
export class DrizzleModel<
|
||||||
|
ModelType extends Model<Shape, IdentifierType<Shape, Identifier>>,
|
||||||
|
TableType extends Table<TC>, TC extends TableConfig,
|
||||||
|
TQueryResult extends PgQueryResultHKT, TFullSchema extends Record<string, unknown>,
|
||||||
|
Scopes extends object,
|
||||||
|
Shape extends ModelShape, Identifier extends keyof Shape = any, TSchema extends TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>
|
||||||
|
>
|
||||||
|
{
|
||||||
|
constructor(protected model: ModelType,
|
||||||
|
protected database: PgDatabase<TQueryResult, TFullSchema, TSchema>,
|
||||||
|
protected table: TableType,
|
||||||
|
protected scopes: Scopes & ThisType<ModelQuery<ModelType, TableType, TC, TQueryResult, TFullSchema, Shape, Identifier, TSchema>>)
|
||||||
|
{}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Drizzle database.
|
||||||
|
*/
|
||||||
|
getDatabase(): PgDatabase<TQueryResult, TFullSchema, TSchema>
|
||||||
|
{
|
||||||
|
return this.database;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get model Drizzle table.
|
||||||
|
*/
|
||||||
|
getTable(): TableType
|
||||||
|
{
|
||||||
|
return this.table;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get scopes.
|
||||||
|
*/
|
||||||
|
getScopes(): Scopes & ThisType<ModelQuery<ModelType, TableType, TC, TQueryResult, TFullSchema, Shape, Identifier, TSchema>>
|
||||||
|
{
|
||||||
|
return this.scopes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the model in database.
|
||||||
|
*/
|
||||||
|
async save(): Promise<boolean>
|
||||||
|
{
|
||||||
|
// Get serialized model update.
|
||||||
|
const serializedModel = this.model.patch();
|
||||||
|
|
||||||
|
// Get table columns of the model.
|
||||||
|
const tableColumns = getTableColumns(this.table);
|
||||||
|
|
||||||
|
// Create model data for the database.
|
||||||
|
const databaseModelData = Object.fromEntries(
|
||||||
|
Object.entries(serializedModel).map(
|
||||||
|
// Only keep table column values and convert them into database types, if it is required.
|
||||||
|
([key, value]) => [key, tableColumns?.[key] ? (serializedToDatabaseTypes?.[tableColumns?.[key].dataType]?.(value) ?? value) : undefined]
|
||||||
|
)
|
||||||
|
) as Partial<SerializedModel<Shape>>;
|
||||||
|
|
||||||
|
// Initialize query result variable.
|
||||||
|
let result: any;
|
||||||
|
|
||||||
|
if (this.model.isNew())
|
||||||
|
{ // Insert the new model in database and get the inserted row data.
|
||||||
|
result = (await (this.database.insert(this.table)).values(databaseModelData as any).returning() as any)?.[0];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // Update model data in database, and get the updated row data.
|
||||||
|
result = (await (this.database.update(this.table)
|
||||||
|
.set(databaseModelData)
|
||||||
|
.where(eq((this.table as any)?.[this.model.getIdentifierName()], this.model.getIdentifier())))
|
||||||
|
.returning() as any)?.[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update model from inserted or updated row data.
|
||||||
|
this.model.deserialize(result);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async refresh(): Promise<void>
|
||||||
|
{
|
||||||
|
// Update model from up-to-date row data.
|
||||||
|
this.model.deserialize(
|
||||||
|
(( // Retrieve model data from database.
|
||||||
|
await this.database.select().from(this.table)
|
||||||
|
.where(eq((this.table as any)?.[this.model.getIdentifierName()], this.model.getIdentifier()))
|
||||||
|
) as any)[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drizzle extension initializer.
|
||||||
|
* @param database Drizzle database.
|
||||||
|
* @param table Drizzle database table.
|
||||||
|
* @param scopes
|
||||||
|
*/
|
||||||
|
export function drizzle<
|
||||||
|
ModelType extends Model<Shape, IdentifierType<Shape, Identifier>>,
|
||||||
|
TableType extends Table<TC>, TC extends TableConfig,
|
||||||
|
TQueryResult extends PgQueryResultHKT, TFullSchema extends Record<string, unknown>,
|
||||||
|
Scopes extends object,
|
||||||
|
Shape extends ModelShape, Identifier extends keyof Shape = any, TSchema extends TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>,
|
||||||
|
>(
|
||||||
|
database: PgDatabase<TQueryResult, TFullSchema, TSchema>,
|
||||||
|
table: TableType,
|
||||||
|
scopes: Scopes & ThisType<ModelQuery<ModelType, TableType, TC, TQueryResult, TFullSchema, Shape, Identifier, TSchema>>
|
||||||
|
): DrizzleExtension<ModelType, TableType, TC, TQueryResult, TFullSchema, Scopes, Shape, Identifier, TSchema> & ThisType<ModelType>
|
||||||
|
{
|
||||||
|
// Return initialized drizzle extension.
|
||||||
|
return {
|
||||||
|
drizzle(): DrizzleModel<ModelType, TableType, TC, TQueryResult, TFullSchema, Scopes, Shape, Identifier, TSchema>
|
||||||
|
{
|
||||||
|
// Initialize drizzle model manager instance.
|
||||||
|
return new DrizzleModel<ModelType, TableType, TC, TQueryResult, TFullSchema, Scopes, Shape, Identifier, TSchema>(this, database, table, scopes);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
109
src/Query.ts
Normal file
109
src/Query.ts
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
import {AnyModel, IdentifierType, Model, ModelClass, ModelShape, SerializedModel} from "@sharkitek/core";
|
||||||
|
import {eq, getTableName, Table, TableConfig} from "drizzle-orm";
|
||||||
|
import {PgDatabase} from "drizzle-orm/pg-core";
|
||||||
|
import {PgQueryResultHKT} from "drizzle-orm/pg-core/session";
|
||||||
|
import type {DBQueryConfig, ExtractTablesWithRelations, TablesRelationalConfig} from "drizzle-orm/relations";
|
||||||
|
import type {KnownKeysOnly} from "drizzle-orm/utils";
|
||||||
|
import {DrizzleExtension} from "./Drizzle";
|
||||||
|
import {inArray} from "drizzle-orm/sql/expressions/conditions";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model query manager.
|
||||||
|
*/
|
||||||
|
export class ModelQuery<
|
||||||
|
ModelType extends Model<Shape, IdentifierType<Shape, Identifier>>,
|
||||||
|
TableType extends Table<TC>, TC extends TableConfig,
|
||||||
|
TQueryResult extends PgQueryResultHKT, TFullSchema extends Record<string, unknown>,
|
||||||
|
Shape extends ModelShape, Identifier extends keyof Shape = any, TSchema extends TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>
|
||||||
|
>
|
||||||
|
{
|
||||||
|
constructor(protected modelClass: ModelClass<ModelType, Shape, Identifier>, protected modelInstance: ModelType, protected database: PgDatabase<TQueryResult, TFullSchema, TSchema>, protected table: TableType)
|
||||||
|
{}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the given model data to a model.
|
||||||
|
* @param rawModel Raw model.
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected parseModel(rawModel: SerializedModel<Shape>|null): ModelType|null
|
||||||
|
{
|
||||||
|
// The raw model is null or undefined, return NULL.
|
||||||
|
if (!rawModel) return null;
|
||||||
|
// Parse the given raw model to a model.
|
||||||
|
return (new this.modelClass()).deserialize(rawModel) as ModelType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find models matching the given configuration.
|
||||||
|
* @param config Request configuration.
|
||||||
|
*/
|
||||||
|
async get<TConfig extends DBQueryConfig<"many", true, TSchema, TSchema[keyof TSchema]>>(config?: KnownKeysOnly<TConfig, DBQueryConfig<"many", true, TSchema, TSchema[keyof TSchema]>>): Promise<ModelType[]>
|
||||||
|
{
|
||||||
|
// Parse retrieved raw models.
|
||||||
|
return (
|
||||||
|
// Find many models from the given configuration.
|
||||||
|
await ((this.database.query as any)?.[getTableName(this.table)]?.findMany(config) as Promise<SerializedModel<Shape>[]>)
|
||||||
|
).map(rawModel => this.parseModel(rawModel)).filter((model) => !!model); // Only keep non-null models.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the first model matching the given configuration.
|
||||||
|
* @param config Request configuration.
|
||||||
|
*/
|
||||||
|
async first<TSelection extends Omit<DBQueryConfig<"many", true, TSchema, TSchema[keyof TSchema]>, "limit">>(config?: KnownKeysOnly<TSelection, Omit<DBQueryConfig<"many", true, TSchema, TSchema[keyof TSchema]>, "limit">>): Promise<ModelType|null>
|
||||||
|
{
|
||||||
|
// Parse retrieved raw model.
|
||||||
|
return this.parseModel(
|
||||||
|
// Find a model from the given configuration.
|
||||||
|
await ((this.database.query as any)?.[getTableName(this.table)]?.findFirst(config) as Promise<SerializedModel<Shape>>)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a model from its identifier.
|
||||||
|
* @param identifier Model identifier.
|
||||||
|
*/
|
||||||
|
async find(identifier: IdentifierType<Shape, Identifier>): Promise<ModelType|null>
|
||||||
|
{
|
||||||
|
// Find the first model which match the given identifier.
|
||||||
|
return this.first({
|
||||||
|
where: eq((this.table as any)?.[this.modelInstance.getIdentifierName()], identifier),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find many models from their identifier.
|
||||||
|
* @param identifiers Model identifiers.
|
||||||
|
*/
|
||||||
|
async findMany(...identifiers: (IdentifierType<Shape, Identifier>|IdentifierType<Shape, Identifier>[])[]): Promise<ModelType[]>
|
||||||
|
{
|
||||||
|
// Find many models which match the given identifiers.
|
||||||
|
return this.get({
|
||||||
|
where: inArray((this.table as any)?.[this.modelInstance.getIdentifierName()], identifiers.flat()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model query builder.
|
||||||
|
* @param modelClass Model class.
|
||||||
|
*/
|
||||||
|
export function query<
|
||||||
|
ModelType extends Model<Shape, IdentifierType<Shape, Identifier>> & DrizzleExtension<AnyModel, TableType, TC, TQueryResult, TFullSchema, Scopes, any, any, TSchema>,
|
||||||
|
TableType extends Table<TC>, TC extends TableConfig,
|
||||||
|
TQueryResult extends PgQueryResultHKT, TFullSchema extends Record<string, unknown>,
|
||||||
|
Scopes extends object,
|
||||||
|
Shape extends ModelShape, Identifier extends keyof Shape = any, TSchema extends TablesRelationalConfig = ExtractTablesWithRelations<TFullSchema>
|
||||||
|
>(modelClass: ModelClass<ModelType, Shape, Identifier>): ModelQuery<ModelType, TableType, TC, TQueryResult, TFullSchema, Shape, Identifier, TSchema> & Scopes
|
||||||
|
{
|
||||||
|
// Get model drizzle instance.
|
||||||
|
const model = new modelClass();
|
||||||
|
const modelDrizzle = model.drizzle();
|
||||||
|
// Create a model query with the model database and table.
|
||||||
|
return Object.assign(
|
||||||
|
new ModelQuery<ModelType, TableType, TC, TQueryResult, TFullSchema, Shape, Identifier, TSchema>(
|
||||||
|
modelClass, model, modelDrizzle.getDatabase(), modelDrizzle.getTable(),
|
||||||
|
),
|
||||||
|
modelDrizzle.getScopes(),
|
||||||
|
);
|
||||||
|
}
|
3
src/index.ts
Normal file
3
src/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
|
||||||
|
export * from "./Drizzle";
|
||||||
|
export * from "./Query";
|
64
tests/Drizzle.test.ts
Normal file
64
tests/Drizzle.test.ts
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import {test, expect, beforeAll} from "vitest";
|
||||||
|
import {query} from "../src";
|
||||||
|
import {Invoice} from "./Invoice";
|
||||||
|
import {setupDefaultInvoices} from "./database";
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
await setupDefaultInvoices();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("retrieve existing models from database", async () => {
|
||||||
|
const invoice = await query(Invoice).find(1);
|
||||||
|
const invoices = await query(Invoice).findMany(1, 2);
|
||||||
|
|
||||||
|
expect(invoice).not.toBeNull();
|
||||||
|
expect(invoices).toHaveLength(2);
|
||||||
|
|
||||||
|
expect(invoice?.amount).toStrictEqual(5450.12);
|
||||||
|
|
||||||
|
expect(invoices.reduce((total, invoice) => (total + invoice.amount), 0)).toStrictEqual(5450.12 + 1122.54);
|
||||||
|
|
||||||
|
expect(invoices[0].date.getFullYear()).toStrictEqual(1997);
|
||||||
|
})
|
||||||
|
|
||||||
|
test("alter existing model in database", async () => {
|
||||||
|
// Get an invoice to change its date.
|
||||||
|
const invoice = await query(Invoice).find(1) as Invoice;
|
||||||
|
invoice.date.setDate(15);
|
||||||
|
await invoice.drizzle().save();
|
||||||
|
|
||||||
|
// Get the same invoice again, to check that the date has been successfully changed.
|
||||||
|
const updatedInvoice = await query(Invoice).find(1);
|
||||||
|
expect(updatedInvoice?.date?.getDate()).toStrictEqual(15);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("refresh a model from database", async () => {
|
||||||
|
// Get an invoice to change its date.
|
||||||
|
const invoice = await query(Invoice).find(1) as Invoice;
|
||||||
|
invoice.date.setDate(30);
|
||||||
|
// Refresh from database: it should revert the changes.
|
||||||
|
await invoice.drizzle().refresh();
|
||||||
|
|
||||||
|
expect(invoice?.date?.getDate()).toStrictEqual(15);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("insert a new model in database", async () => {
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
const invoice = new Invoice();
|
||||||
|
invoice.date = now;
|
||||||
|
invoice.amount = 12.34;
|
||||||
|
invoice.clientName = "client name";
|
||||||
|
invoice.clientAddress = "any address string";
|
||||||
|
|
||||||
|
await invoice.drizzle().save();
|
||||||
|
|
||||||
|
expect(invoice.serialize()).toStrictEqual({
|
||||||
|
id: 3,
|
||||||
|
date: now.toISOString(),
|
||||||
|
amount: "12.34",
|
||||||
|
clientName: "client name",
|
||||||
|
clientAddress: "any address string",
|
||||||
|
});
|
||||||
|
expect(invoice.serialize()).toStrictEqual((await query(Invoice).find(invoice.id))?.serialize());
|
||||||
|
});
|
19
tests/Invoice.ts
Normal file
19
tests/Invoice.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import {s} from "@sharkitek/core";
|
||||||
|
import {drizzle} from "../src";
|
||||||
|
import {database} from "./database";
|
||||||
|
import {invoices} from "./schema/invoices";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice model class example with Sharkitek and its Drizzle connector.
|
||||||
|
* @see https://code.zeptotech.net/Sharkitek/Core
|
||||||
|
*/
|
||||||
|
export class Invoice extends s.model({
|
||||||
|
id: s.property.numeric(),
|
||||||
|
date: s.property.date(),
|
||||||
|
amount: s.property.decimal(),
|
||||||
|
clientName: s.property.string(),
|
||||||
|
clientAddress: s.property.string(),
|
||||||
|
}, "id").extends(drizzle(database, invoices, {
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
}
|
56
tests/database.ts
Normal file
56
tests/database.ts
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
import postgres from "postgres";
|
||||||
|
import {drizzle} from "drizzle-orm/postgres-js";
|
||||||
|
import {sql} from "drizzle-orm";
|
||||||
|
import * as InvoicesSchema from "./schema/invoices";
|
||||||
|
|
||||||
|
// Load configuration from environment variables.
|
||||||
|
dotenv.config({
|
||||||
|
path: ".env.test",
|
||||||
|
});
|
||||||
|
|
||||||
|
const queryClient = postgres(`postgres://${process.env.POSTGRES_USERNAME}:${process.env.POSTGRES_PASSWORD}@${process.env.POSTGRES_HOST}:${process.env.POSTGRES_PORT}/${process.env.POSTGRES_DATABASE}`);
|
||||||
|
export const database = drizzle(queryClient, {
|
||||||
|
schema: {
|
||||||
|
...InvoicesSchema,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize database schema for tests.
|
||||||
|
*/
|
||||||
|
export async function initializeDatabase(): Promise<void>
|
||||||
|
{
|
||||||
|
await database.execute(sql`
|
||||||
|
DROP TABLE IF EXISTS invoices;
|
||||||
|
CREATE TABLE invoices(
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
date TIMESTAMP WITH TIME ZONE,
|
||||||
|
amount NUMERIC(12, 2),
|
||||||
|
client_name VARCHAR,
|
||||||
|
client_address TEXT
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add default invoices for tests.
|
||||||
|
*/
|
||||||
|
export async function setupDefaultInvoices(): Promise<void>
|
||||||
|
{
|
||||||
|
await database.execute(sql`TRUNCATE invoices RESTART IDENTITY;`);
|
||||||
|
await database.insert(InvoicesSchema.invoices).values([
|
||||||
|
{
|
||||||
|
date: new Date("1997-10-09"),
|
||||||
|
amount: "5450.12",
|
||||||
|
clientName: "test name",
|
||||||
|
clientAddress: "test test test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
date: new Date(),
|
||||||
|
amount: "1122.54",
|
||||||
|
clientName: "another name",
|
||||||
|
clientAddress: "foo bar baz",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
13
tests/schema/invoices.ts
Normal file
13
tests/schema/invoices.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import {numeric, pgTable, serial, text, timestamp, varchar} from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoices example table with Drizzle.
|
||||||
|
* @see https://orm.drizzle.team/docs/sql-schema-declaration
|
||||||
|
*/
|
||||||
|
export const invoices = pgTable("invoices", {
|
||||||
|
id: serial("id").primaryKey(),
|
||||||
|
date: timestamp("date", { withTimezone: true }).notNull(),
|
||||||
|
amount: numeric("amount", { precision: 12, scale: 2 }).notNull(),
|
||||||
|
clientName: varchar("client_name").notNull(),
|
||||||
|
clientAddress: text("client_address"),
|
||||||
|
});
|
3
tests/setup.ts
Normal file
3
tests/setup.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import {initializeDatabase} from "./database";
|
||||||
|
|
||||||
|
initializeDatabase();
|
32
tsconfig.json
Normal file
32
tsconfig.json
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"ts-node": {
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "ESNext",
|
||||||
|
"types": ["node"],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./lib/",
|
||||||
|
"incremental": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"module": "ES6",
|
||||||
|
"target": "ES6",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"lib": [
|
||||||
|
"ESNext",
|
||||||
|
"DOM"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,14 @@ export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
|
||||||
formats: ["es"],
|
formats: ["es"],
|
||||||
fileName: "index",
|
fileName: "index",
|
||||||
},
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
external: ["@sharkitek/core", "drizzle-orm"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
test: {
|
||||||
|
root: "./tests",
|
||||||
|
setupFiles: ["./tests/setup.ts"],
|
||||||
},
|
},
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|
Loading…
Reference in a new issue