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…
Reference in a new issue