@fragno-dev/test 0.0.0-canary-20251030115355
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +19 -0
- package/CHANGELOG.md +76 -0
- package/LICENSE.md +16 -0
- package/dist/adapters.d.ts +35 -0
- package/dist/adapters.d.ts.map +1 -0
- package/dist/adapters.js +220 -0
- package/dist/adapters.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +100 -0
- package/dist/index.js.map +1 -0
- package/package.json +68 -0
- package/src/adapters.ts +406 -0
- package/src/index.test.ts +641 -0
- package/src/index.ts +254 -0
- package/tsconfig.json +10 -0
- package/tsdown.config.ts +7 -0
- package/vitest.config.ts +4 -0
package/src/adapters.ts
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { Kysely } from "kysely";
|
|
2
|
+
import { SQLocalKysely } from "sqlocal/kysely";
|
|
3
|
+
import { KyselyPGlite } from "kysely-pglite";
|
|
4
|
+
import { drizzle } from "drizzle-orm/pglite";
|
|
5
|
+
import { PGlite } from "@electric-sql/pglite";
|
|
6
|
+
import { KyselyAdapter } from "@fragno-dev/db/adapters/kysely";
|
|
7
|
+
import { DrizzleAdapter } from "@fragno-dev/db/adapters/drizzle";
|
|
8
|
+
import type { AnySchema } from "@fragno-dev/db/schema";
|
|
9
|
+
import type { DatabaseAdapter } from "@fragno-dev/db/adapters";
|
|
10
|
+
import type { AbstractQuery } from "@fragno-dev/db/query";
|
|
11
|
+
import { createRequire } from "node:module";
|
|
12
|
+
import { mkdir, writeFile, rm } from "node:fs/promises";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { existsSync } from "node:fs";
|
|
15
|
+
|
|
16
|
+
// Adapter configuration types
|
|
17
|
+
export interface KyselySqliteAdapter {
|
|
18
|
+
type: "kysely-sqlite";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface KyselyPgliteAdapter {
|
|
22
|
+
type: "kysely-pglite";
|
|
23
|
+
databasePath?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface DrizzlePgliteAdapter {
|
|
27
|
+
type: "drizzle-pglite";
|
|
28
|
+
databasePath?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type SupportedAdapter = KyselySqliteAdapter | KyselyPgliteAdapter | DrizzlePgliteAdapter;
|
|
32
|
+
|
|
33
|
+
// Conditional return types based on adapter
|
|
34
|
+
export type TestContext<T extends SupportedAdapter> = T extends
|
|
35
|
+
| KyselySqliteAdapter
|
|
36
|
+
| KyselyPgliteAdapter
|
|
37
|
+
? {
|
|
38
|
+
readonly db: AbstractQuery<any>; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
39
|
+
readonly kysely: Kysely<any>; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
40
|
+
readonly adapter: DatabaseAdapter<any>; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
41
|
+
resetDatabase: () => Promise<void>;
|
|
42
|
+
cleanup: () => Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
: T extends DrizzlePgliteAdapter
|
|
45
|
+
? {
|
|
46
|
+
readonly db: AbstractQuery<any>; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
47
|
+
readonly drizzle: ReturnType<typeof drizzle<any>>; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
48
|
+
readonly adapter: DatabaseAdapter<any>; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
49
|
+
resetDatabase: () => Promise<void>;
|
|
50
|
+
cleanup: () => Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
: never;
|
|
53
|
+
|
|
54
|
+
// Factory function return type
|
|
55
|
+
interface AdapterFactoryResult<T extends SupportedAdapter> {
|
|
56
|
+
testContext: TestContext<T>;
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
58
|
+
adapter: DatabaseAdapter<any>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create Kysely + SQLite adapter using SQLocalKysely (always in-memory)
|
|
63
|
+
*/
|
|
64
|
+
export async function createKyselySqliteAdapter(
|
|
65
|
+
_config: KyselySqliteAdapter,
|
|
66
|
+
schema: AnySchema,
|
|
67
|
+
namespace: string,
|
|
68
|
+
migrateToVersion?: number,
|
|
69
|
+
): Promise<AdapterFactoryResult<KyselySqliteAdapter>> {
|
|
70
|
+
// Helper to create a new database instance and run migrations
|
|
71
|
+
const createDatabase = async () => {
|
|
72
|
+
// Create SQLocalKysely instance (always in-memory for tests)
|
|
73
|
+
const { dialect } = new SQLocalKysely(":memory:");
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
75
|
+
const kysely = new Kysely<any>({
|
|
76
|
+
dialect,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Create KyselyAdapter
|
|
80
|
+
const adapter = new KyselyAdapter({
|
|
81
|
+
db: kysely,
|
|
82
|
+
provider: "sqlite",
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Run migrations
|
|
86
|
+
const migrator = adapter.createMigrationEngine(schema, namespace);
|
|
87
|
+
const preparedMigration = migrateToVersion
|
|
88
|
+
? await migrator.prepareMigrationTo(migrateToVersion, {
|
|
89
|
+
updateSettings: false,
|
|
90
|
+
})
|
|
91
|
+
: await migrator.prepareMigration({
|
|
92
|
+
updateSettings: false,
|
|
93
|
+
});
|
|
94
|
+
await preparedMigration.execute();
|
|
95
|
+
|
|
96
|
+
// Create ORM instance
|
|
97
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
98
|
+
const orm = adapter.createQueryEngine(schema, namespace) as AbstractQuery<any>;
|
|
99
|
+
|
|
100
|
+
return { kysely, adapter, orm };
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Create initial database
|
|
104
|
+
let { kysely, adapter, orm } = await createDatabase();
|
|
105
|
+
|
|
106
|
+
// Reset database function - creates a fresh in-memory database and re-runs migrations
|
|
107
|
+
const resetDatabase = async () => {
|
|
108
|
+
// Destroy the old Kysely instance
|
|
109
|
+
await kysely.destroy();
|
|
110
|
+
|
|
111
|
+
// Create a new database instance
|
|
112
|
+
const newDb = await createDatabase();
|
|
113
|
+
kysely = newDb.kysely;
|
|
114
|
+
adapter = newDb.adapter;
|
|
115
|
+
orm = newDb.orm;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Cleanup function - closes connections (no files to delete for in-memory)
|
|
119
|
+
const cleanup = async () => {
|
|
120
|
+
await kysely.destroy();
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
testContext: {
|
|
125
|
+
get db() {
|
|
126
|
+
return orm;
|
|
127
|
+
},
|
|
128
|
+
get kysely() {
|
|
129
|
+
return kysely;
|
|
130
|
+
},
|
|
131
|
+
get adapter() {
|
|
132
|
+
return adapter;
|
|
133
|
+
},
|
|
134
|
+
resetDatabase,
|
|
135
|
+
cleanup,
|
|
136
|
+
},
|
|
137
|
+
get adapter() {
|
|
138
|
+
return adapter;
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Create Kysely + PGLite adapter using kysely-pglite
|
|
145
|
+
*/
|
|
146
|
+
export async function createKyselyPgliteAdapter(
|
|
147
|
+
config: KyselyPgliteAdapter,
|
|
148
|
+
schema: AnySchema,
|
|
149
|
+
namespace: string,
|
|
150
|
+
migrateToVersion?: number,
|
|
151
|
+
): Promise<AdapterFactoryResult<KyselyPgliteAdapter>> {
|
|
152
|
+
const databasePath = config.databasePath;
|
|
153
|
+
|
|
154
|
+
// Helper to create a new database instance and run migrations
|
|
155
|
+
const createDatabase = async () => {
|
|
156
|
+
// Create KyselyPGlite instance
|
|
157
|
+
const kyselyPglite = await KyselyPGlite.create(databasePath);
|
|
158
|
+
|
|
159
|
+
// Create Kysely instance with PGlite dialect
|
|
160
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
161
|
+
const kysely = new Kysely<any>({
|
|
162
|
+
dialect: kyselyPglite.dialect,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Create KyselyAdapter
|
|
166
|
+
const adapter = new KyselyAdapter({
|
|
167
|
+
db: kysely,
|
|
168
|
+
provider: "postgresql",
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Run migrations
|
|
172
|
+
const migrator = adapter.createMigrationEngine(schema, namespace);
|
|
173
|
+
const preparedMigration = migrateToVersion
|
|
174
|
+
? await migrator.prepareMigrationTo(migrateToVersion, {
|
|
175
|
+
updateSettings: false,
|
|
176
|
+
})
|
|
177
|
+
: await migrator.prepareMigration({
|
|
178
|
+
updateSettings: false,
|
|
179
|
+
});
|
|
180
|
+
await preparedMigration.execute();
|
|
181
|
+
|
|
182
|
+
// Create ORM instance
|
|
183
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
184
|
+
const orm = adapter.createQueryEngine(schema, namespace) as AbstractQuery<any>;
|
|
185
|
+
|
|
186
|
+
return { kysely, adapter, kyselyPglite, orm };
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// Create initial database
|
|
190
|
+
let { kysely, adapter, kyselyPglite, orm } = await createDatabase();
|
|
191
|
+
|
|
192
|
+
// Reset database function - creates a fresh database and re-runs migrations
|
|
193
|
+
const resetDatabase = async () => {
|
|
194
|
+
// Close the old instances
|
|
195
|
+
await kysely.destroy();
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
await kyselyPglite.client.close();
|
|
199
|
+
} catch {
|
|
200
|
+
// Ignore if already closed
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Create a new database instance
|
|
204
|
+
const newDb = await createDatabase();
|
|
205
|
+
kysely = newDb.kysely;
|
|
206
|
+
adapter = newDb.adapter;
|
|
207
|
+
kyselyPglite = newDb.kyselyPglite;
|
|
208
|
+
orm = newDb.orm;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// Cleanup function - closes connections and deletes database directory
|
|
212
|
+
const cleanup = async () => {
|
|
213
|
+
await kysely.destroy();
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
await kyselyPglite.client.close();
|
|
217
|
+
} catch {
|
|
218
|
+
// Ignore if already closed
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Delete the database directory if it exists and is a file path
|
|
222
|
+
if (databasePath && databasePath !== ":memory:" && existsSync(databasePath)) {
|
|
223
|
+
await rm(databasePath, { recursive: true, force: true });
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
testContext: {
|
|
229
|
+
get db() {
|
|
230
|
+
return orm;
|
|
231
|
+
},
|
|
232
|
+
get kysely() {
|
|
233
|
+
return kysely;
|
|
234
|
+
},
|
|
235
|
+
get adapter() {
|
|
236
|
+
return adapter;
|
|
237
|
+
},
|
|
238
|
+
resetDatabase,
|
|
239
|
+
cleanup,
|
|
240
|
+
},
|
|
241
|
+
get adapter() {
|
|
242
|
+
return adapter;
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Create Drizzle + PGLite adapter using drizzle-orm/pglite
|
|
249
|
+
*/
|
|
250
|
+
export async function createDrizzlePgliteAdapter(
|
|
251
|
+
config: DrizzlePgliteAdapter,
|
|
252
|
+
schema: AnySchema,
|
|
253
|
+
namespace: string,
|
|
254
|
+
_migrateToVersion?: number,
|
|
255
|
+
): Promise<AdapterFactoryResult<DrizzlePgliteAdapter>> {
|
|
256
|
+
const databasePath = config.databasePath;
|
|
257
|
+
|
|
258
|
+
// Import drizzle-kit for migrations
|
|
259
|
+
const require = createRequire(import.meta.url);
|
|
260
|
+
const { generateDrizzleJson, generateMigration } =
|
|
261
|
+
require("drizzle-kit/api") as typeof import("drizzle-kit/api");
|
|
262
|
+
|
|
263
|
+
// Import generateSchema from the properly exported module
|
|
264
|
+
const { generateSchema } = await import("@fragno-dev/db/adapters/drizzle/generate");
|
|
265
|
+
|
|
266
|
+
// Helper to write schema to file and dynamically import it
|
|
267
|
+
const writeAndLoadSchema = async () => {
|
|
268
|
+
const testDir = join(import.meta.dirname, "_generated", "drizzle-test");
|
|
269
|
+
await mkdir(testDir, { recursive: true }).catch(() => {
|
|
270
|
+
// Ignore error if directory already exists
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const schemaFilePath = join(
|
|
274
|
+
testDir,
|
|
275
|
+
`test-schema-${Date.now()}-${Math.random().toString(36).slice(2, 9)}.ts`,
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
// Generate and write the Drizzle schema to file
|
|
279
|
+
const drizzleSchemaTs = generateSchema([{ namespace: namespace ?? "", schema }], "postgresql");
|
|
280
|
+
await writeFile(schemaFilePath, drizzleSchemaTs, "utf-8");
|
|
281
|
+
|
|
282
|
+
// Dynamically import the generated schema (with cache busting)
|
|
283
|
+
const schemaModule = await import(`${schemaFilePath}?t=${Date.now()}`);
|
|
284
|
+
|
|
285
|
+
const cleanup = async () => {
|
|
286
|
+
await rm(testDir, { recursive: true, force: true });
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
return { schemaModule, cleanup };
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
// Helper to create a new database instance and run migrations
|
|
293
|
+
const createDatabase = async () => {
|
|
294
|
+
// Write schema to file and load it
|
|
295
|
+
const { schemaModule, cleanup } = await writeAndLoadSchema();
|
|
296
|
+
|
|
297
|
+
// Create PGlite instance
|
|
298
|
+
const pglite = new PGlite(databasePath);
|
|
299
|
+
|
|
300
|
+
// Create Drizzle instance with PGlite
|
|
301
|
+
const db = drizzle(pglite, {
|
|
302
|
+
schema: schemaModule,
|
|
303
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
304
|
+
}) as any;
|
|
305
|
+
|
|
306
|
+
// Generate and run migrations
|
|
307
|
+
const migrationStatements = await generateMigration(
|
|
308
|
+
generateDrizzleJson({}), // Empty schema (starting state)
|
|
309
|
+
generateDrizzleJson(schemaModule), // Target schema
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
// Execute migration SQL
|
|
313
|
+
for (const statement of migrationStatements) {
|
|
314
|
+
await db.execute(statement);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Create DrizzleAdapter
|
|
318
|
+
const adapter = new DrizzleAdapter({
|
|
319
|
+
db: () => db,
|
|
320
|
+
provider: "postgresql",
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// Create ORM instance
|
|
324
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
325
|
+
const orm = adapter.createQueryEngine(schema, namespace) as AbstractQuery<any>;
|
|
326
|
+
|
|
327
|
+
return { drizzle: db, adapter, pglite, cleanup, orm };
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
// Create initial database
|
|
331
|
+
let { drizzle: drizzleDb, adapter, pglite, cleanup: schemaCleanup, orm } = await createDatabase();
|
|
332
|
+
|
|
333
|
+
// Reset database function - creates a fresh database and re-runs migrations
|
|
334
|
+
const resetDatabase = async () => {
|
|
335
|
+
// Close the old instances and cleanup
|
|
336
|
+
await pglite.close();
|
|
337
|
+
await schemaCleanup();
|
|
338
|
+
|
|
339
|
+
// Create a new database instance
|
|
340
|
+
const newDb = await createDatabase();
|
|
341
|
+
drizzleDb = newDb.drizzle;
|
|
342
|
+
adapter = newDb.adapter;
|
|
343
|
+
pglite = newDb.pglite;
|
|
344
|
+
schemaCleanup = newDb.cleanup;
|
|
345
|
+
orm = newDb.orm;
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
// Cleanup function - closes connections and deletes generated files and database directory
|
|
349
|
+
const cleanup = async () => {
|
|
350
|
+
await pglite.close();
|
|
351
|
+
await schemaCleanup();
|
|
352
|
+
|
|
353
|
+
// Delete the database directory if it exists and is a file path
|
|
354
|
+
if (databasePath && databasePath !== ":memory:" && existsSync(databasePath)) {
|
|
355
|
+
await rm(databasePath, { recursive: true, force: true });
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
return {
|
|
360
|
+
testContext: {
|
|
361
|
+
get db() {
|
|
362
|
+
return orm;
|
|
363
|
+
},
|
|
364
|
+
get drizzle() {
|
|
365
|
+
return drizzleDb;
|
|
366
|
+
},
|
|
367
|
+
get adapter() {
|
|
368
|
+
return adapter;
|
|
369
|
+
},
|
|
370
|
+
resetDatabase,
|
|
371
|
+
cleanup,
|
|
372
|
+
},
|
|
373
|
+
get adapter() {
|
|
374
|
+
return adapter;
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Create adapter based on configuration
|
|
381
|
+
*/
|
|
382
|
+
export async function createAdapter<T extends SupportedAdapter>(
|
|
383
|
+
adapterConfig: T,
|
|
384
|
+
schema: AnySchema,
|
|
385
|
+
namespace: string,
|
|
386
|
+
migrateToVersion?: number,
|
|
387
|
+
): Promise<AdapterFactoryResult<T>> {
|
|
388
|
+
if (adapterConfig.type === "kysely-sqlite") {
|
|
389
|
+
return createKyselySqliteAdapter(adapterConfig, schema, namespace, migrateToVersion) as Promise<
|
|
390
|
+
AdapterFactoryResult<T>
|
|
391
|
+
>;
|
|
392
|
+
} else if (adapterConfig.type === "kysely-pglite") {
|
|
393
|
+
return createKyselyPgliteAdapter(adapterConfig, schema, namespace, migrateToVersion) as Promise<
|
|
394
|
+
AdapterFactoryResult<T>
|
|
395
|
+
>;
|
|
396
|
+
} else if (adapterConfig.type === "drizzle-pglite") {
|
|
397
|
+
return createDrizzlePgliteAdapter(
|
|
398
|
+
adapterConfig,
|
|
399
|
+
schema,
|
|
400
|
+
namespace,
|
|
401
|
+
migrateToVersion,
|
|
402
|
+
) as Promise<AdapterFactoryResult<T>>;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
throw new Error(`Unsupported adapter type: ${(adapterConfig as SupportedAdapter).type}`);
|
|
406
|
+
}
|