@fragno-dev/test 0.1.1 → 0.1.2

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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @fragno-dev/test@0.1.1 build /home/runner/work/fragno/fragno/packages/fragno-test
2
+ > @fragno-dev/test@0.1.2 build /home/runner/work/fragno/fragno/packages/fragno-test
3
3
  > tsdown
4
4
 
5
5
  ℹ tsdown v0.15.10 powered by rolldown v1.0.0-beta.44
@@ -7,9 +7,9 @@
7
7
  ℹ entry: src/index.ts
8
8
  ℹ tsconfig: tsconfig.json
9
9
  ℹ Build start
10
- ℹ dist/index.js 1.64 kB │ gzip: 0.67 kB
11
- ℹ dist/index.js.map 5.90 kB │ gzip: 1.93 kB
12
- ℹ dist/index.d.ts.map 1.21 kB │ gzip: 0.57 kB
13
- ℹ dist/index.d.ts 2.29 kB │ gzip: 0.68 kB
14
- ℹ 4 files, total: 11.04 kB
15
- ✔ Build complete in 11624ms
10
+ ℹ dist/index.js 2.63 kB │ gzip: 0.89 kB
11
+ ℹ dist/index.js.map 8.17 kB │ gzip: 2.51 kB
12
+ ℹ dist/index.d.ts.map 1.28 kB │ gzip: 0.59 kB
13
+ ℹ dist/index.d.ts 2.55 kB │ gzip: 0.80 kB
14
+ ℹ 4 files, total: 14.63 kB
15
+ ✔ Build complete in 4126ms
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @fragno-dev/test
2
2
 
3
+ ## 0.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 6fd2528: feat(testing): add resetDatabase method to test Fragment instance
8
+ - Updated dependencies [70bdcb2]
9
+ - @fragno-dev/db@0.1.6
10
+
3
11
  ## 0.1.1
4
12
 
5
13
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -21,6 +21,11 @@ interface CreateDatabaseFragmentForTestOptions<TConfig, TDeps, TServices, TAddit
21
21
  interface DatabaseFragmentForTest<TConfig, TDeps, TServices, TAdditionalContext extends Record<string, unknown>, TOptions extends FragnoPublicConfig> extends FragmentForTest$1<TConfig, TDeps, TServices, TAdditionalContext, TOptions> {
22
22
  kysely: Kysely<any>;
23
23
  adapter: DatabaseAdapter<any>;
24
+ /**
25
+ * Resets the database by creating a fresh in-memory database instance and re-running migrations.
26
+ * After calling this, you should re-initialize any routes to ensure they use the new database instance.
27
+ */
28
+ resetDatabase: () => Promise<void>;
24
29
  }
25
30
  declare function createDatabaseFragmentForTest<const TConfig, const TDeps, const TServices extends Record<string, unknown>, const TAdditionalContext extends Record<string, unknown>, const TOptions extends FragnoPublicConfig, const TSchema extends AnySchema>(fragmentBuilder: {
26
31
  definition: FragmentDefinition<TConfig, TDeps, TServices, TAdditionalContext>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;AA0BA;;AAKmB,UALF,oCAKE,CAAA,OAAA,EAAA,KAAA,EAAA,SAAA,EAAA,2BADU,MACV,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,iBAAA,kBAAA,CAAA,SACT,IADS,CAEf,8BAFe,CAEc,OAFd,EAEuB,KAFvB,EAE8B,SAF9B,EAEyC,kBAFzC,EAE6D,QAF7D,CAAA,EAAA,QAAA,CAAA,CAAA;EAEc,YAAA,CAAA,EAAA,MAAA;EAAS,gBAAA,CAAA,EAAA,MAAA;EAAO,MAAA,CAAA,EAKtC,OALsC;;;;;AADvC,UAYO,uBAZP,CAAA,OAAA,EAAA,KAAA,EAAA,SAAA,EAAA,2BAgBmB,MAhBnB,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,iBAiBS,kBAjBT,CAAA,SAkBA,iBAlBA,CAkBgB,OAlBhB,EAkByB,KAlBzB,EAkBgC,SAlBhC,EAkB2C,kBAlB3C,EAkB+D,QAlB/D,CAAA,CAAA;EAAI,MAAA,EAoBJ,MApBI,CAAA,GAAA,CAAA;EAYG,OAAA,EAUN,eAVM,CAAA,GAAuB,CAAA;;AAKrB,iBAQG,6BARH,CAAA,aAAA,EAAA,WAAA,EAAA,wBAWO,MAXP,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,iCAYgB,MAZhB,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,uBAaM,kBAbN,EAAA,sBAcK,SAdL,CAAA,CAAA,eAAA,EAAA;EACO,UAAA,EAgBV,kBAhBU,CAgBS,OAhBT,EAgBkB,KAhBlB,EAgByB,SAhBzB,EAgBoC,kBAhBpC,CAAA;EAAS,gBAAA,EAiBb,QAjBa;CAAO,EAAA,OAAA,CAAA,EAmB9B,oCAnB8B,CAoBtC,OApBsC,EAqBtC,KArBsC,EAsBtC,SAtBsC,EAuBtC,kBAvBsC,EAwBtC,QAxBsC,CAAA,CAAA,EA0BvC,OA1BuC,CA0B/B,uBA1B+B,CA0BP,OA1BO,EA0BE,KA1BF,EA0BS,SA1BT,EA0BoB,kBA1BpB,EA0BwC,QA1BxC,CAAA,CAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;AA0BA;;AAKmB,UALF,oCAKE,CAAA,OAAA,EAAA,KAAA,EAAA,SAAA,EAAA,2BADU,MACV,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,iBAAA,kBAAA,CAAA,SACT,IADS,CAEf,8BAFe,CAEc,OAFd,EAEuB,KAFvB,EAE8B,SAF9B,EAEyC,kBAFzC,EAE6D,QAF7D,CAAA,EAAA,QAAA,CAAA,CAAA;EAEc,YAAA,CAAA,EAAA,MAAA;EAAS,gBAAA,CAAA,EAAA,MAAA;EAAO,MAAA,CAAA,EAKtC,OALsC;;;;;AADvC,UAYO,uBAZP,CAAA,OAAA,EAAA,KAAA,EAAA,SAAA,EAAA,2BAgBmB,MAhBnB,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,iBAiBS,kBAjBT,CAAA,SAkBA,iBAlBA,CAkBgB,OAlBhB,EAkByB,KAlBzB,EAkBgC,SAlBhC,EAkB2C,kBAlB3C,EAkB+D,QAlB/D,CAAA,CAAA;EAAI,MAAA,EAoBJ,MApBI,CAAA,GAAA,CAAA;EAYG,OAAA,EAUN,eAVM,CAAA,GAAuB,CAAA;EAIX;;;;EAEa,aAAA,EAAA,GAAA,GASnB,OATmB,CAAA,IAAA,CAAA;;AAA+B,iBAYnD,6BAZmD,CAAA,aAAA,EAAA,WAAA,EAAA,wBAe/C,MAf+C,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,iCAgBtC,MAhBsC,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,uBAiBhD,kBAjBgD,EAAA,sBAkBjD,SAlBiD,CAAA,CAAA,eAAA,EAAA;EAE/D,UAAA,EAmBM,kBAnBN,CAmByB,OAnBzB,EAmBkC,KAnBlC,EAmByC,SAnBzC,EAmBoD,kBAnBpD,CAAA;EAEC,gBAAA,EAkBW,QAlBX;CAKY,EAAA,OAAA,CAAA,EAeX,oCAfW,CAgBnB,OAhBmB,EAiBnB,KAjBmB,EAkBnB,SAlBmB,EAmBnB,kBAnBmB,EAoBnB,QApBmB,CAAA,CAAA,EAsBpB,OAtBoB,CAsBZ,uBAtBY,CAsBY,OAtBZ,EAsBqB,KAtBrB,EAsB4B,SAtB5B,EAsBuC,kBAtBvC,EAsB2D,QAtB3D,CAAA,CAAA"}
package/dist/index.js CHANGED
@@ -6,31 +6,79 @@ import { createFragmentForTest, createFragmentForTest as createFragmentForTest$1
6
6
  //#region src/index.ts
7
7
  async function createDatabaseFragmentForTest(fragmentBuilder, options) {
8
8
  const { databasePath = ":memory:", migrateToVersion, config, options: fragmentOptions, deps, services, additionalContext } = options ?? {};
9
- const { dialect } = new SQLocalKysely(databasePath);
10
- const kysely = new Kysely({ dialect });
11
- const adapter = new KyselyAdapter({
12
- db: kysely,
13
- provider: "sqlite"
14
- });
15
9
  const fragmentAdditionalContext = fragmentBuilder.definition.additionalContext;
16
10
  const schema = fragmentAdditionalContext?.databaseSchema;
17
11
  const namespace = fragmentAdditionalContext?.databaseNamespace ?? "";
18
12
  if (!schema) throw new Error(`Fragment '${fragmentBuilder.definition.name}' does not have a database schema. Make sure you're using defineFragmentWithDatabase().withDatabase(schema).`);
19
- const migrator = adapter.createMigrationEngine(schema, namespace);
20
- await (migrateToVersion ? await migrator.prepareMigrationTo(migrateToVersion, { updateSettings: false }) : await migrator.prepareMigration({ updateSettings: false })).execute();
21
- return {
22
- ...createFragmentForTest$1(fragmentBuilder, {
13
+ const createDatabase = async () => {
14
+ const { dialect } = new SQLocalKysely(databasePath);
15
+ const kysely$1 = new Kysely({ dialect });
16
+ const adapter$1 = new KyselyAdapter({
17
+ db: kysely$1,
18
+ provider: "sqlite"
19
+ });
20
+ const migrator = adapter$1.createMigrationEngine(schema, namespace);
21
+ await (migrateToVersion ? await migrator.prepareMigrationTo(migrateToVersion, { updateSettings: false }) : await migrator.prepareMigration({ updateSettings: false })).execute();
22
+ return {
23
+ kysely: kysely$1,
24
+ adapter: adapter$1
25
+ };
26
+ };
27
+ let { kysely, adapter } = await createDatabase();
28
+ let mergedOptions = {
29
+ ...fragmentOptions,
30
+ databaseAdapter: adapter
31
+ };
32
+ let fragment = createFragmentForTest$1(fragmentBuilder, {
33
+ config,
34
+ options: mergedOptions,
35
+ deps,
36
+ services,
37
+ additionalContext
38
+ });
39
+ const resetDatabase = async () => {
40
+ await kysely.destroy();
41
+ const newDb = await createDatabase();
42
+ kysely = newDb.kysely;
43
+ adapter = newDb.adapter;
44
+ mergedOptions = {
45
+ ...fragmentOptions,
46
+ databaseAdapter: adapter
47
+ };
48
+ fragment = createFragmentForTest$1(fragmentBuilder, {
23
49
  config,
24
- options: {
25
- ...fragmentOptions,
26
- databaseAdapter: adapter
27
- },
50
+ options: mergedOptions,
28
51
  deps,
29
52
  services,
30
53
  additionalContext
31
- }),
32
- kysely,
33
- adapter
54
+ });
55
+ };
56
+ return {
57
+ get services() {
58
+ return fragment.services;
59
+ },
60
+ get initRoutes() {
61
+ return fragment.initRoutes;
62
+ },
63
+ get handler() {
64
+ return fragment.handler;
65
+ },
66
+ get config() {
67
+ return fragment.config;
68
+ },
69
+ get deps() {
70
+ return fragment.deps;
71
+ },
72
+ get additionalContext() {
73
+ return fragment.additionalContext;
74
+ },
75
+ get kysely() {
76
+ return kysely;
77
+ },
78
+ get adapter() {
79
+ return adapter;
80
+ },
81
+ resetDatabase
34
82
  };
35
83
  }
36
84
 
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["createFragmentForTest"],"sources":["../src/index.ts"],"sourcesContent":["import { Kysely } from \"kysely\";\nimport { SQLocalKysely } from \"sqlocal/kysely\";\nimport { KyselyAdapter } from \"@fragno-dev/db/adapters/kysely\";\nimport type { AnySchema } from \"@fragno-dev/db/schema\";\nimport type { DatabaseAdapter } from \"@fragno-dev/db/adapters\";\nimport {\n createFragmentForTest,\n type FragmentForTest,\n type CreateFragmentForTestOptions,\n} from \"@fragno-dev/core/test\";\nimport type { FragnoPublicConfig } from \"@fragno-dev/core/api/fragment-instantiation\";\nimport type { FragmentDefinition } from \"@fragno-dev/core/api/fragment-builder\";\n\n// Re-export utilities from @fragno-dev/core/test\nexport {\n createFragmentForTest,\n type TestResponse,\n type CreateFragmentForTestOptions,\n type RouteHandlerInputOptions,\n type FragmentForTest,\n type InitRoutesOverrides,\n} from \"@fragno-dev/core/test\";\n\n/**\n * Options for creating a database fragment for testing\n */\nexport interface CreateDatabaseFragmentForTestOptions<\n TConfig,\n TDeps,\n TServices,\n TAdditionalContext extends Record<string, unknown>,\n TOptions extends FragnoPublicConfig,\n> extends Omit<\n CreateFragmentForTestOptions<TConfig, TDeps, TServices, TAdditionalContext, TOptions>,\n \"config\"\n > {\n databasePath?: string;\n migrateToVersion?: number;\n config?: TConfig;\n}\n\n/**\n * Extended fragment test instance with database adapter and Kysely instance\n */\nexport interface DatabaseFragmentForTest<\n TConfig,\n TDeps,\n TServices,\n TAdditionalContext extends Record<string, unknown>,\n TOptions extends FragnoPublicConfig,\n> extends FragmentForTest<TConfig, TDeps, TServices, TAdditionalContext, TOptions> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n kysely: Kysely<any>;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n adapter: DatabaseAdapter<any>;\n}\n\nexport async function createDatabaseFragmentForTest<\n const TConfig,\n const TDeps,\n const TServices extends Record<string, unknown>,\n const TAdditionalContext extends Record<string, unknown>,\n const TOptions extends FragnoPublicConfig,\n const TSchema extends AnySchema,\n>(\n fragmentBuilder: {\n definition: FragmentDefinition<TConfig, TDeps, TServices, TAdditionalContext>;\n $requiredOptions: TOptions;\n },\n options?: CreateDatabaseFragmentForTestOptions<\n TConfig,\n TDeps,\n TServices,\n TAdditionalContext,\n TOptions\n >,\n): Promise<DatabaseFragmentForTest<TConfig, TDeps, TServices, TAdditionalContext, TOptions>> {\n const {\n databasePath = \":memory:\",\n migrateToVersion,\n config,\n options: fragmentOptions,\n deps,\n services,\n additionalContext,\n } = options ?? {};\n\n // Create SQLocalKysely instance\n const { dialect } = new SQLocalKysely(databasePath);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const kysely = new Kysely<any>({\n dialect,\n });\n\n // Create KyselyAdapter\n const adapter = new KyselyAdapter({\n db: kysely,\n provider: \"sqlite\",\n });\n\n // Get schema and namespace from fragment definition's additionalContext\n // Safe cast: DatabaseFragmentBuilder adds databaseSchema and databaseNamespace to additionalContext\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const fragmentAdditionalContext = fragmentBuilder.definition.additionalContext as any;\n const schema = fragmentAdditionalContext?.databaseSchema as TSchema | undefined;\n const namespace = (fragmentAdditionalContext?.databaseNamespace as string | undefined) ?? \"\";\n\n if (!schema) {\n throw new Error(\n `Fragment '${fragmentBuilder.definition.name}' does not have a database schema. ` +\n `Make sure you're using defineFragmentWithDatabase().withDatabase(schema).`,\n );\n }\n\n // Run migrations automatically\n const migrator = adapter.createMigrationEngine(schema, namespace);\n const preparedMigration = migrateToVersion\n ? await migrator.prepareMigrationTo(migrateToVersion, {\n updateSettings: false,\n })\n : await migrator.prepareMigration({\n updateSettings: false,\n });\n await preparedMigration.execute();\n\n // Create fragment with database adapter in options\n // Safe cast: We're merging the user's options with the databaseAdapter, which is required by TOptions\n // The user's TOptions is constrained to FragnoPublicConfig (or a subtype), which we extend with databaseAdapter\n const mergedOptions = {\n ...fragmentOptions,\n databaseAdapter: adapter,\n } as unknown as TOptions;\n\n // Safe cast: If config is not provided, we pass undefined as TConfig.\n // The base createFragmentForTest expects config: TConfig, but if TConfig allows undefined\n // or if the fragment doesn't use config in its dependencies function, this will work correctly.\n const fragment = createFragmentForTest(fragmentBuilder, {\n config: config as TConfig,\n options: mergedOptions,\n deps,\n services,\n additionalContext,\n });\n\n return {\n ...fragment,\n kysely,\n adapter,\n };\n}\n"],"mappings":";;;;;;AAyDA,eAAsB,8BAQpB,iBAIA,SAO2F;CAC3F,MAAM,EACJ,eAAe,YACf,kBACA,QACA,SAAS,iBACT,MACA,UACA,sBACE,WAAW,EAAE;CAGjB,MAAM,EAAE,YAAY,IAAI,cAAc,aAAa;CAEnD,MAAM,SAAS,IAAI,OAAY,EAC7B,SACD,CAAC;CAGF,MAAM,UAAU,IAAI,cAAc;EAChC,IAAI;EACJ,UAAU;EACX,CAAC;CAKF,MAAM,4BAA4B,gBAAgB,WAAW;CAC7D,MAAM,SAAS,2BAA2B;CAC1C,MAAM,YAAa,2BAA2B,qBAA4C;AAE1F,KAAI,CAAC,OACH,OAAM,IAAI,MACR,aAAa,gBAAgB,WAAW,KAAK,8GAE9C;CAIH,MAAM,WAAW,QAAQ,sBAAsB,QAAQ,UAAU;AAQjE,QAP0B,mBACtB,MAAM,SAAS,mBAAmB,kBAAkB,EAClD,gBAAgB,OACjB,CAAC,GACF,MAAM,SAAS,iBAAiB,EAC9B,gBAAgB,OACjB,CAAC,EACkB,SAAS;AAqBjC,QAAO;EACL,GATeA,wBAAsB,iBAAiB;GAC9C;GACR,SAVoB;IACpB,GAAG;IACH,iBAAiB;IAClB;GAQC;GACA;GACA;GACD,CAAC;EAIA;EACA;EACD"}
1
+ {"version":3,"file":"index.js","names":["kysely","adapter","createFragmentForTest"],"sources":["../src/index.ts"],"sourcesContent":["import { Kysely } from \"kysely\";\nimport { SQLocalKysely } from \"sqlocal/kysely\";\nimport { KyselyAdapter } from \"@fragno-dev/db/adapters/kysely\";\nimport type { AnySchema } from \"@fragno-dev/db/schema\";\nimport type { DatabaseAdapter } from \"@fragno-dev/db/adapters\";\nimport {\n createFragmentForTest,\n type FragmentForTest,\n type CreateFragmentForTestOptions,\n} from \"@fragno-dev/core/test\";\nimport type { FragnoPublicConfig } from \"@fragno-dev/core/api/fragment-instantiation\";\nimport type { FragmentDefinition } from \"@fragno-dev/core/api/fragment-builder\";\n\n// Re-export utilities from @fragno-dev/core/test\nexport {\n createFragmentForTest,\n type TestResponse,\n type CreateFragmentForTestOptions,\n type RouteHandlerInputOptions,\n type FragmentForTest,\n type InitRoutesOverrides,\n} from \"@fragno-dev/core/test\";\n\n/**\n * Options for creating a database fragment for testing\n */\nexport interface CreateDatabaseFragmentForTestOptions<\n TConfig,\n TDeps,\n TServices,\n TAdditionalContext extends Record<string, unknown>,\n TOptions extends FragnoPublicConfig,\n> extends Omit<\n CreateFragmentForTestOptions<TConfig, TDeps, TServices, TAdditionalContext, TOptions>,\n \"config\"\n > {\n databasePath?: string;\n migrateToVersion?: number;\n config?: TConfig;\n}\n\n/**\n * Extended fragment test instance with database adapter and Kysely instance\n */\nexport interface DatabaseFragmentForTest<\n TConfig,\n TDeps,\n TServices,\n TAdditionalContext extends Record<string, unknown>,\n TOptions extends FragnoPublicConfig,\n> extends FragmentForTest<TConfig, TDeps, TServices, TAdditionalContext, TOptions> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n kysely: Kysely<any>;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n adapter: DatabaseAdapter<any>;\n /**\n * Resets the database by creating a fresh in-memory database instance and re-running migrations.\n * After calling this, you should re-initialize any routes to ensure they use the new database instance.\n */\n resetDatabase: () => Promise<void>;\n}\n\nexport async function createDatabaseFragmentForTest<\n const TConfig,\n const TDeps,\n const TServices extends Record<string, unknown>,\n const TAdditionalContext extends Record<string, unknown>,\n const TOptions extends FragnoPublicConfig,\n const TSchema extends AnySchema,\n>(\n fragmentBuilder: {\n definition: FragmentDefinition<TConfig, TDeps, TServices, TAdditionalContext>;\n $requiredOptions: TOptions;\n },\n options?: CreateDatabaseFragmentForTestOptions<\n TConfig,\n TDeps,\n TServices,\n TAdditionalContext,\n TOptions\n >,\n): Promise<DatabaseFragmentForTest<TConfig, TDeps, TServices, TAdditionalContext, TOptions>> {\n const {\n databasePath = \":memory:\",\n migrateToVersion,\n config,\n options: fragmentOptions,\n deps,\n services,\n additionalContext,\n } = options ?? {};\n\n // Get schema and namespace from fragment definition's additionalContext\n // Safe cast: DatabaseFragmentBuilder adds databaseSchema and databaseNamespace to additionalContext\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const fragmentAdditionalContext = fragmentBuilder.definition.additionalContext as any;\n const schema = fragmentAdditionalContext?.databaseSchema as TSchema | undefined;\n const namespace = (fragmentAdditionalContext?.databaseNamespace as string | undefined) ?? \"\";\n\n if (!schema) {\n throw new Error(\n `Fragment '${fragmentBuilder.definition.name}' does not have a database schema. ` +\n `Make sure you're using defineFragmentWithDatabase().withDatabase(schema).`,\n );\n }\n\n // Helper to create a new database instance and run migrations\n const createDatabase = async () => {\n // Create SQLocalKysely instance\n const { dialect } = new SQLocalKysely(databasePath);\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const kysely = new Kysely<any>({\n dialect,\n });\n\n // Create KyselyAdapter\n const adapter = new KyselyAdapter({\n db: kysely,\n provider: \"sqlite\",\n });\n\n // Run migrations\n const migrator = adapter.createMigrationEngine(schema, namespace);\n const preparedMigration = migrateToVersion\n ? await migrator.prepareMigrationTo(migrateToVersion, {\n updateSettings: false,\n })\n : await migrator.prepareMigration({\n updateSettings: false,\n });\n await preparedMigration.execute();\n\n return { kysely, adapter };\n };\n\n // Create initial database\n let { kysely, adapter } = await createDatabase();\n\n // Create fragment with database adapter in options\n // Safe cast: We're merging the user's options with the databaseAdapter, which is required by TOptions\n // The user's TOptions is constrained to FragnoPublicConfig (or a subtype), which we extend with databaseAdapter\n let mergedOptions = {\n ...fragmentOptions,\n databaseAdapter: adapter,\n } as unknown as TOptions;\n\n // Safe cast: If config is not provided, we pass undefined as TConfig.\n // The base createFragmentForTest expects config: TConfig, but if TConfig allows undefined\n // or if the fragment doesn't use config in its dependencies function, this will work correctly.\n let fragment = createFragmentForTest(fragmentBuilder, {\n config: config as TConfig,\n options: mergedOptions,\n deps,\n services,\n additionalContext,\n });\n\n // Reset database function - creates a fresh in-memory database and re-runs migrations\n const resetDatabase = async () => {\n // Destroy the old Kysely instance\n await kysely.destroy();\n\n // Create a new database instance\n const newDb = await createDatabase();\n kysely = newDb.kysely;\n adapter = newDb.adapter;\n\n // Recreate the fragment with the new adapter\n mergedOptions = {\n ...fragmentOptions,\n databaseAdapter: adapter,\n } as unknown as TOptions;\n\n fragment = createFragmentForTest(fragmentBuilder, {\n config: config as TConfig,\n options: mergedOptions,\n deps,\n services,\n additionalContext,\n });\n };\n\n return {\n get services() {\n return fragment.services;\n },\n get initRoutes() {\n return fragment.initRoutes;\n },\n get handler() {\n return fragment.handler;\n },\n get config() {\n return fragment.config;\n },\n get deps() {\n return fragment.deps;\n },\n get additionalContext() {\n return fragment.additionalContext;\n },\n get kysely() {\n return kysely;\n },\n get adapter() {\n return adapter;\n },\n resetDatabase,\n };\n}\n"],"mappings":";;;;;;AA8DA,eAAsB,8BAQpB,iBAIA,SAO2F;CAC3F,MAAM,EACJ,eAAe,YACf,kBACA,QACA,SAAS,iBACT,MACA,UACA,sBACE,WAAW,EAAE;CAKjB,MAAM,4BAA4B,gBAAgB,WAAW;CAC7D,MAAM,SAAS,2BAA2B;CAC1C,MAAM,YAAa,2BAA2B,qBAA4C;AAE1F,KAAI,CAAC,OACH,OAAM,IAAI,MACR,aAAa,gBAAgB,WAAW,KAAK,8GAE9C;CAIH,MAAM,iBAAiB,YAAY;EAEjC,MAAM,EAAE,YAAY,IAAI,cAAc,aAAa;EAEnD,MAAMA,WAAS,IAAI,OAAY,EAC7B,SACD,CAAC;EAGF,MAAMC,YAAU,IAAI,cAAc;GAChC,IAAID;GACJ,UAAU;GACX,CAAC;EAGF,MAAM,WAAWC,UAAQ,sBAAsB,QAAQ,UAAU;AAQjE,SAP0B,mBACtB,MAAM,SAAS,mBAAmB,kBAAkB,EAClD,gBAAgB,OACjB,CAAC,GACF,MAAM,SAAS,iBAAiB,EAC9B,gBAAgB,OACjB,CAAC,EACkB,SAAS;AAEjC,SAAO;GAAE;GAAQ;GAAS;;CAI5B,IAAI,EAAE,QAAQ,YAAY,MAAM,gBAAgB;CAKhD,IAAI,gBAAgB;EAClB,GAAG;EACH,iBAAiB;EAClB;CAKD,IAAI,WAAWC,wBAAsB,iBAAiB;EAC5C;EACR,SAAS;EACT;EACA;EACA;EACD,CAAC;CAGF,MAAM,gBAAgB,YAAY;AAEhC,QAAM,OAAO,SAAS;EAGtB,MAAM,QAAQ,MAAM,gBAAgB;AACpC,WAAS,MAAM;AACf,YAAU,MAAM;AAGhB,kBAAgB;GACd,GAAG;GACH,iBAAiB;GAClB;AAED,aAAWA,wBAAsB,iBAAiB;GACxC;GACR,SAAS;GACT;GACA;GACA;GACD,CAAC;;AAGJ,QAAO;EACL,IAAI,WAAW;AACb,UAAO,SAAS;;EAElB,IAAI,aAAa;AACf,UAAO,SAAS;;EAElB,IAAI,UAAU;AACZ,UAAO,SAAS;;EAElB,IAAI,SAAS;AACX,UAAO,SAAS;;EAElB,IAAI,OAAO;AACT,UAAO,SAAS;;EAElB,IAAI,oBAAoB;AACtB,UAAO,SAAS;;EAElB,IAAI,SAAS;AACX,UAAO;;EAET,IAAI,UAAU;AACZ,UAAO;;EAET;EACD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fragno-dev/test",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -16,7 +16,7 @@
16
16
  "kysely": "^0.28.7",
17
17
  "sqlocal": "^0.15.2",
18
18
  "@fragno-dev/core": "0.1.3",
19
- "@fragno-dev/db": "0.1.5"
19
+ "@fragno-dev/db": "0.1.6"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/node": "^22",
package/src/index.test.ts CHANGED
@@ -3,6 +3,8 @@ import { column, idColumn, schema } from "@fragno-dev/db/schema";
3
3
  import { defineFragmentWithDatabase } from "@fragno-dev/db/fragment";
4
4
  import { createDatabaseFragmentForTest } from "./index";
5
5
  import { unlinkSync, existsSync } from "node:fs";
6
+ import { defineRoute, defineRoutes } from "@fragno-dev/core";
7
+ import { z } from "zod";
6
8
 
7
9
  // Test schema with multiple versions
8
10
  const testSchema = schema((s) => {
@@ -260,4 +262,143 @@ describe("createDatabaseFragmentForTest", () => {
260
262
  ).rejects.toThrow("Fragment 'non-db-fragment' does not have a database schema");
261
263
  });
262
264
  });
265
+
266
+ describe("route handling with defineRoutes", () => {
267
+ it("should handle route factory with multiple routes", async () => {
268
+ const fragment = await createDatabaseFragmentForTest(testFragmentDef);
269
+
270
+ type Config = {};
271
+ type Deps = {};
272
+ type Services = {
273
+ createUser: (data: { name: string; email: string; age?: number | null }) => Promise<{
274
+ name: string;
275
+ email: string;
276
+ age?: number | null;
277
+ id: string;
278
+ }>;
279
+ getUsers: () => Promise<{ name: string; email: string; age: number | null; id: string }[]>;
280
+ };
281
+
282
+ const routeFactory = defineRoutes<Config, Deps, Services>().create(({ services }) => [
283
+ defineRoute({
284
+ method: "POST",
285
+ path: "/users",
286
+ inputSchema: z.object({
287
+ name: z.string(),
288
+ email: z.string(),
289
+ age: z.number().nullable().optional(),
290
+ }),
291
+ outputSchema: z.object({
292
+ id: z.string(),
293
+ name: z.string(),
294
+ email: z.string(),
295
+ age: z.number().nullable().optional(),
296
+ }),
297
+ handler: async ({ input }, { json }) => {
298
+ if (input) {
299
+ const data = await input.valid();
300
+ const user = await services.createUser(data);
301
+ return json(user);
302
+ }
303
+ return json({ id: "", name: "", email: "", age: null });
304
+ },
305
+ }),
306
+ defineRoute({
307
+ method: "GET",
308
+ path: "/users",
309
+ outputSchema: z.array(
310
+ z.object({
311
+ id: z.string(),
312
+ name: z.string(),
313
+ email: z.string(),
314
+ age: z.number().nullable(),
315
+ }),
316
+ ),
317
+ handler: async (_ctx, { json }) => {
318
+ const users = await services.getUsers();
319
+ return json(users);
320
+ },
321
+ }),
322
+ ]);
323
+
324
+ const routes = [routeFactory] as const;
325
+ const [createUserRoute, getUsersRoute] = fragment.initRoutes(routes);
326
+
327
+ // Test creating a user
328
+ const createResponse = await fragment.handler(createUserRoute, {
329
+ body: { name: "John Doe", email: "john@example.com", age: 30 },
330
+ });
331
+
332
+ expect(createResponse.type).toBe("json");
333
+ if (createResponse.type === "json") {
334
+ expect(createResponse.data).toMatchObject({
335
+ id: expect.any(String),
336
+ name: "John Doe",
337
+ email: "john@example.com",
338
+ age: 30,
339
+ });
340
+ }
341
+
342
+ // Test getting users
343
+ const getUsersResponse = await fragment.handler(getUsersRoute);
344
+
345
+ expect(getUsersResponse.type).toBe("json");
346
+ if (getUsersResponse.type === "json") {
347
+ expect(getUsersResponse.data).toHaveLength(1);
348
+ expect(getUsersResponse.data[0]).toMatchObject({
349
+ id: expect.any(String),
350
+ name: "John Doe",
351
+ email: "john@example.com",
352
+ age: 30,
353
+ });
354
+ }
355
+ });
356
+ });
357
+
358
+ describe("resetDatabase", () => {
359
+ it("should clear all data and recreate a fresh database", async () => {
360
+ const fragment = await createDatabaseFragmentForTest(testFragmentDef);
361
+
362
+ // Create some users
363
+ await fragment.services.createUser({
364
+ name: "User 1",
365
+ email: "user1@example.com",
366
+ age: 25,
367
+ });
368
+ await fragment.services.createUser({
369
+ name: "User 2",
370
+ email: "user2@example.com",
371
+ age: 30,
372
+ });
373
+
374
+ // Verify users exist
375
+ let users = await fragment.services.getUsers();
376
+ expect(users).toHaveLength(2);
377
+
378
+ // Reset the database
379
+ await fragment.resetDatabase();
380
+
381
+ // Verify database is empty
382
+ users = await fragment.services.getUsers();
383
+ expect(users).toHaveLength(0);
384
+
385
+ // Verify we can still create new users after reset
386
+ const newUser = await fragment.services.createUser({
387
+ name: "User After Reset",
388
+ email: "after@example.com",
389
+ age: 35,
390
+ });
391
+
392
+ expect(newUser).toMatchObject({
393
+ id: expect.any(String),
394
+ name: "User After Reset",
395
+ email: "after@example.com",
396
+ age: 35,
397
+ });
398
+
399
+ users = await fragment.services.getUsers();
400
+ expect(users).toHaveLength(1);
401
+ expect(users[0]).toMatchObject(newUser);
402
+ });
403
+ });
263
404
  });
package/src/index.ts CHANGED
@@ -53,6 +53,11 @@ export interface DatabaseFragmentForTest<
53
53
  kysely: Kysely<any>;
54
54
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
55
55
  adapter: DatabaseAdapter<any>;
56
+ /**
57
+ * Resets the database by creating a fresh in-memory database instance and re-running migrations.
58
+ * After calling this, you should re-initialize any routes to ensure they use the new database instance.
59
+ */
60
+ resetDatabase: () => Promise<void>;
56
61
  }
57
62
 
58
63
  export async function createDatabaseFragmentForTest<
@@ -85,19 +90,6 @@ export async function createDatabaseFragmentForTest<
85
90
  additionalContext,
86
91
  } = options ?? {};
87
92
 
88
- // Create SQLocalKysely instance
89
- const { dialect } = new SQLocalKysely(databasePath);
90
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
91
- const kysely = new Kysely<any>({
92
- dialect,
93
- });
94
-
95
- // Create KyselyAdapter
96
- const adapter = new KyselyAdapter({
97
- db: kysely,
98
- provider: "sqlite",
99
- });
100
-
101
93
  // Get schema and namespace from fragment definition's additionalContext
102
94
  // Safe cast: DatabaseFragmentBuilder adds databaseSchema and databaseNamespace to additionalContext
103
95
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -112,21 +104,42 @@ export async function createDatabaseFragmentForTest<
112
104
  );
113
105
  }
114
106
 
115
- // Run migrations automatically
116
- const migrator = adapter.createMigrationEngine(schema, namespace);
117
- const preparedMigration = migrateToVersion
118
- ? await migrator.prepareMigrationTo(migrateToVersion, {
119
- updateSettings: false,
120
- })
121
- : await migrator.prepareMigration({
122
- updateSettings: false,
123
- });
124
- await preparedMigration.execute();
107
+ // Helper to create a new database instance and run migrations
108
+ const createDatabase = async () => {
109
+ // Create SQLocalKysely instance
110
+ const { dialect } = new SQLocalKysely(databasePath);
111
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
112
+ const kysely = new Kysely<any>({
113
+ dialect,
114
+ });
115
+
116
+ // Create KyselyAdapter
117
+ const adapter = new KyselyAdapter({
118
+ db: kysely,
119
+ provider: "sqlite",
120
+ });
121
+
122
+ // Run migrations
123
+ const migrator = adapter.createMigrationEngine(schema, namespace);
124
+ const preparedMigration = migrateToVersion
125
+ ? await migrator.prepareMigrationTo(migrateToVersion, {
126
+ updateSettings: false,
127
+ })
128
+ : await migrator.prepareMigration({
129
+ updateSettings: false,
130
+ });
131
+ await preparedMigration.execute();
132
+
133
+ return { kysely, adapter };
134
+ };
135
+
136
+ // Create initial database
137
+ let { kysely, adapter } = await createDatabase();
125
138
 
126
139
  // Create fragment with database adapter in options
127
140
  // Safe cast: We're merging the user's options with the databaseAdapter, which is required by TOptions
128
141
  // The user's TOptions is constrained to FragnoPublicConfig (or a subtype), which we extend with databaseAdapter
129
- const mergedOptions = {
142
+ let mergedOptions = {
130
143
  ...fragmentOptions,
131
144
  databaseAdapter: adapter,
132
145
  } as unknown as TOptions;
@@ -134,7 +147,7 @@ export async function createDatabaseFragmentForTest<
134
147
  // Safe cast: If config is not provided, we pass undefined as TConfig.
135
148
  // The base createFragmentForTest expects config: TConfig, but if TConfig allows undefined
136
149
  // or if the fragment doesn't use config in its dependencies function, this will work correctly.
137
- const fragment = createFragmentForTest(fragmentBuilder, {
150
+ let fragment = createFragmentForTest(fragmentBuilder, {
138
151
  config: config as TConfig,
139
152
  options: mergedOptions,
140
153
  deps,
@@ -142,9 +155,56 @@ export async function createDatabaseFragmentForTest<
142
155
  additionalContext,
143
156
  });
144
157
 
158
+ // Reset database function - creates a fresh in-memory database and re-runs migrations
159
+ const resetDatabase = async () => {
160
+ // Destroy the old Kysely instance
161
+ await kysely.destroy();
162
+
163
+ // Create a new database instance
164
+ const newDb = await createDatabase();
165
+ kysely = newDb.kysely;
166
+ adapter = newDb.adapter;
167
+
168
+ // Recreate the fragment with the new adapter
169
+ mergedOptions = {
170
+ ...fragmentOptions,
171
+ databaseAdapter: adapter,
172
+ } as unknown as TOptions;
173
+
174
+ fragment = createFragmentForTest(fragmentBuilder, {
175
+ config: config as TConfig,
176
+ options: mergedOptions,
177
+ deps,
178
+ services,
179
+ additionalContext,
180
+ });
181
+ };
182
+
145
183
  return {
146
- ...fragment,
147
- kysely,
148
- adapter,
184
+ get services() {
185
+ return fragment.services;
186
+ },
187
+ get initRoutes() {
188
+ return fragment.initRoutes;
189
+ },
190
+ get handler() {
191
+ return fragment.handler;
192
+ },
193
+ get config() {
194
+ return fragment.config;
195
+ },
196
+ get deps() {
197
+ return fragment.deps;
198
+ },
199
+ get additionalContext() {
200
+ return fragment.additionalContext;
201
+ },
202
+ get kysely() {
203
+ return kysely;
204
+ },
205
+ get adapter() {
206
+ return adapter;
207
+ },
208
+ resetDatabase,
149
209
  };
150
210
  }