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", | ||||
|   "version": "1.0.0", | ||||
|   "version": "3.0.0-beta", | ||||
|   "description": "Drizzle connector for Sharkitek models.", | ||||
|   "repository": "https://code.zeptotech.net/Sharkitek/Drizzle", | ||||
|   "author": { | ||||
|  | @ -10,7 +10,7 @@ | |||
|   "license": "MIT", | ||||
|   "scripts": { | ||||
|     "build": "tsc && vite build", | ||||
|     "test": "jest" | ||||
|     "test": "vitest" | ||||
|   }, | ||||
|   "type": "module", | ||||
|   "source": "src/index.ts", | ||||
|  | @ -23,17 +23,19 @@ | |||
|     "access": "public" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@types/jest": "^29.5.13", | ||||
|     "@sharkitek/core": "^3.3.0", | ||||
|     "@types/node": "^22.7.4", | ||||
|     "dotenv": "^16.4.5", | ||||
|     "drizzle-orm": "^0.33.0", | ||||
|     "jest": "^29.7.0", | ||||
|     "ts-jest": "^29.2.5", | ||||
|     "postgres": "^3.4.4", | ||||
|     "ts-node": "^10.9.2", | ||||
|     "typescript": "^5.6.2", | ||||
|     "vite": "^5.4.8", | ||||
|     "vite-plugin-dts": "^4.2.3" | ||||
|     "vite-plugin-dts": "^4.2.3", | ||||
|     "vitest": "^2.1.2" | ||||
|   }, | ||||
|   "peerDependencies": { | ||||
|     "@sharkitek/core": "^3.0.0", | ||||
|     "drizzle-orm": "^0.33.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"], | ||||
| 				fileName: "index", | ||||
| 			}, | ||||
| 			rollupOptions: { | ||||
| 				external: ["@sharkitek/core", "drizzle-orm"], | ||||
| 			}, | ||||
| 		}, | ||||
| 
 | ||||
| 		test: { | ||||
| 			root: "./tests", | ||||
| 			setupFiles: ["./tests/setup.ts"], | ||||
| 		}, | ||||
| 
 | ||||
| 		plugins: [ | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue