@fragno-dev/test 0.1.11 → 0.1.13

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.
@@ -0,0 +1,214 @@
1
+ import { createAdapter } from "./adapters.js";
2
+
3
+ //#region src/db-test.ts
4
+ /**
5
+ * Builder for creating multiple database fragments for testing
6
+ */
7
+ var DatabaseFragmentsTestBuilder = class {
8
+ #adapter;
9
+ #fragments = /* @__PURE__ */ new Map();
10
+ /**
11
+ * Set the test adapter configuration
12
+ */
13
+ withTestAdapter(adapter) {
14
+ this.#adapter = adapter;
15
+ return this;
16
+ }
17
+ /**
18
+ * Add a fragment to the test setup
19
+ *
20
+ * @param name - Unique name for the fragment
21
+ * @param builder - Pre-configured instantiation builder
22
+ * @param options - Additional options (optional)
23
+ */
24
+ withFragment(name, builder, options) {
25
+ this.#fragments.set(name, {
26
+ definition: builder.definition,
27
+ builder,
28
+ migrateToVersion: options?.migrateToVersion
29
+ });
30
+ return this;
31
+ }
32
+ /**
33
+ * Build the test setup and return fragments and test context
34
+ */
35
+ async build() {
36
+ if (!this.#adapter) throw new Error("Test adapter must be set using withTestAdapter()");
37
+ if (this.#fragments.size === 0) throw new Error("At least one fragment must be added using withFragment()");
38
+ const adapterConfig = this.#adapter;
39
+ const fragmentNames = Array.from(this.#fragments.keys());
40
+ const fragmentConfigs = Array.from(this.#fragments.values());
41
+ const schemaConfigs = [];
42
+ const builderConfigs = [];
43
+ for (const fragmentConfig of fragmentConfigs) {
44
+ const builder = fragmentConfig.builder;
45
+ const definition = builder.definition;
46
+ let schema;
47
+ const namespace = definition.name + "-db";
48
+ if (definition.dependencies) try {
49
+ const mockAdapter = {
50
+ createQueryEngine: () => ({ schema: null }),
51
+ contextStorage: { run: (_data, fn) => fn() }
52
+ };
53
+ const actualConfig = builder.config ?? {};
54
+ const deps = definition.dependencies({
55
+ config: actualConfig,
56
+ options: { databaseAdapter: mockAdapter }
57
+ });
58
+ if (deps && typeof deps === "object" && "schema" in deps) schema = deps.schema;
59
+ } catch (error) {
60
+ const errorMessage = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
61
+ throw new Error(`Failed to extract schema from fragment '${definition.name}'.\nOriginal error: ${errorMessage}\n\nMake sure the fragment is a database fragment using defineFragment().extend(withDatabase(schema)).`);
62
+ }
63
+ if (!schema) throw new Error(`Fragment '${definition.name}' does not have a database schema. Make sure you're using defineFragment().extend(withDatabase(schema)).`);
64
+ schemaConfigs.push({
65
+ schema,
66
+ namespace,
67
+ migrateToVersion: fragmentConfig.migrateToVersion
68
+ });
69
+ builderConfigs.push({
70
+ config: builder.config ?? {},
71
+ routes: builder.routes ?? [],
72
+ options: builder.options ?? {}
73
+ });
74
+ }
75
+ const { testContext, adapter } = await createAdapter(adapterConfig, schemaConfigs);
76
+ const createFragments = () => {
77
+ const providedServicesByName = {};
78
+ const fragmentResults$1 = [];
79
+ for (let i = 0; i < fragmentConfigs.length; i++) {
80
+ const fragmentConfig = fragmentConfigs[i];
81
+ const builderConfig = builderConfigs[i];
82
+ const namespace = schemaConfigs[i].namespace;
83
+ const schema = schemaConfigs[i].schema;
84
+ const orm = testContext.getOrm(namespace);
85
+ const mergedOptions = {
86
+ ...builderConfig.options,
87
+ databaseAdapter: adapter
88
+ };
89
+ const fragment = fragmentConfig.builder.withOptions(mergedOptions).build();
90
+ for (const [serviceName, serviceImpl] of Object.entries(fragment.services)) providedServicesByName[serviceName] = {
91
+ service: serviceImpl,
92
+ orm
93
+ };
94
+ const deps = fragment.$internal?.deps;
95
+ fragmentResults$1.push({
96
+ fragment,
97
+ services: fragment.services,
98
+ deps: deps || {},
99
+ callRoute: fragment.callRoute.bind(fragment),
100
+ get db() {
101
+ return orm;
102
+ },
103
+ _orm: orm,
104
+ _schema: schema
105
+ });
106
+ }
107
+ for (let i = 0; i < fragmentConfigs.length; i++) {
108
+ const fragmentConfig = fragmentConfigs[i];
109
+ const definition = fragmentConfig.builder.definition;
110
+ const builderConfig = builderConfigs[i];
111
+ const namespace = schemaConfigs[i].namespace;
112
+ const schema = schemaConfigs[i].schema;
113
+ const orm = testContext.getOrm(namespace);
114
+ const serviceImplementations = {};
115
+ const serviceDependencies = definition.serviceDependencies;
116
+ if (serviceDependencies) {
117
+ for (const serviceName of Object.keys(serviceDependencies)) if (providedServicesByName[serviceName]) serviceImplementations[serviceName] = providedServicesByName[serviceName].service;
118
+ }
119
+ const mergedOptions = {
120
+ ...builderConfig.options,
121
+ databaseAdapter: adapter
122
+ };
123
+ const fragment = fragmentConfig.builder.withOptions(mergedOptions).withServices(serviceImplementations).build();
124
+ const deps = fragment.$internal?.deps;
125
+ fragmentResults$1[i] = {
126
+ fragment,
127
+ services: fragment.services,
128
+ deps: deps || {},
129
+ callRoute: fragment.callRoute.bind(fragment),
130
+ get db() {
131
+ return orm;
132
+ },
133
+ _orm: orm,
134
+ _schema: schema
135
+ };
136
+ }
137
+ return fragmentResults$1;
138
+ };
139
+ const fragmentResults = createFragments();
140
+ const originalResetDatabase = testContext.resetDatabase;
141
+ const resetDatabase = async () => {
142
+ await originalResetDatabase();
143
+ createFragments().forEach((newResult, index) => {
144
+ const result = fragmentResults[index];
145
+ result.fragment = newResult.fragment;
146
+ result.services = newResult.services;
147
+ result.deps = newResult.deps;
148
+ result.callRoute = newResult.callRoute;
149
+ result._orm = newResult._orm;
150
+ });
151
+ };
152
+ const firstFragment = fragmentResults[0]?.fragment;
153
+ if (!firstFragment) throw new Error("At least one fragment must be added");
154
+ const finalTestContext = {
155
+ ...testContext,
156
+ resetDatabase,
157
+ adapter,
158
+ inContext: firstFragment.inContext.bind(firstFragment)
159
+ };
160
+ return {
161
+ fragments: Object.fromEntries(fragmentNames.map((name, index) => [name, fragmentResults[index]])),
162
+ test: finalTestContext
163
+ };
164
+ }
165
+ };
166
+ /**
167
+ * Create a builder for setting up multiple database fragments for testing.
168
+ * This is the new builder-based API that works with the new fragment instantiation builders.
169
+ *
170
+ * @example
171
+ * ```typescript
172
+ * const userFragmentDef = defineFragment("user")
173
+ * .extend(withDatabase(userSchema))
174
+ * .withDependencies(...)
175
+ * .build();
176
+ *
177
+ * const postFragmentDef = defineFragment("post")
178
+ * .extend(withDatabase(postSchema))
179
+ * .withDependencies(...)
180
+ * .build();
181
+ *
182
+ * const { fragments, test } = await buildDatabaseFragmentsTest()
183
+ * .withTestAdapter({ type: "kysely-sqlite" })
184
+ * .withFragment("user",
185
+ * instantiate(userFragmentDef)
186
+ * .withConfig({ ... })
187
+ * .withRoutes([...])
188
+ * )
189
+ * .withFragment("post",
190
+ * instantiate(postFragmentDef)
191
+ * .withRoutes([...])
192
+ * )
193
+ * .build();
194
+ *
195
+ * // Access fragments by name
196
+ * await fragments.user.services.createUser(...);
197
+ * await fragments.post.services.createPost(...);
198
+ *
199
+ * // Access dependencies directly
200
+ * const userDeps = fragments.user.deps;
201
+ *
202
+ * // Shared test context
203
+ * await test.resetDatabase();
204
+ * await test.cleanup();
205
+ * const adapter = test.adapter; // Access the database adapter
206
+ * ```
207
+ */
208
+ function buildDatabaseFragmentsTest() {
209
+ return new DatabaseFragmentsTestBuilder();
210
+ }
211
+
212
+ //#endregion
213
+ export { DatabaseFragmentsTestBuilder, buildDatabaseFragmentsTest };
214
+ //# sourceMappingURL=db-test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db-test.js","names":["#adapter","#fragments","schemaConfigs: SchemaConfig[]","builderConfigs: Array<{ config: any; routes: any; options: any }>","schema: AnySchema | undefined","providedServicesByName: Record<string, { service: any; orm: any }>","fragmentResults: any[]","serviceImplementations: Record<string, any>","fragmentResults"],"sources":["../src/db-test.ts"],"sourcesContent":["import type { AnySchema } from \"@fragno-dev/db/schema\";\nimport type {\n RequestThisContext,\n FragnoPublicConfig,\n FragmentInstantiationBuilder,\n FragnoInstantiatedFragment,\n FragmentDefinition,\n} from \"@fragno-dev/core\";\nimport type { AnyRouteOrFactory, FlattenRouteFactories } from \"@fragno-dev/core/route\";\nimport {\n createAdapter,\n type SupportedAdapter,\n type AdapterContext,\n type SchemaConfig,\n} from \"./adapters\";\nimport type { DatabaseAdapter } from \"@fragno-dev/db\";\nimport type { AbstractQuery } from \"@fragno-dev/db/query\";\nimport type { BaseTestContext } from \".\";\n\n// BoundServices is an internal type that strips 'this' parameters from service methods\n// It's used to represent services after they've been bound to a context\ntype BoundServices<T> = {\n [K in keyof T]: T[K] extends (this: any, ...args: infer A) => infer R // eslint-disable-line @typescript-eslint/no-explicit-any\n ? (...args: A) => R\n : T[K] extends Record<string, unknown>\n ? BoundServices<T[K]>\n : T[K];\n};\n\n// Extract the schema type from database fragment dependencies\n// Database fragments have ImplicitDatabaseDependencies<TSchema> which includes `schema: TSchema`\ntype ExtractSchemaFromDeps<TDeps> = TDeps extends { schema: infer TSchema extends AnySchema }\n ? TSchema\n : AnySchema;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyLinkedFragments = Record<string, any>;\n\n// Forward declarations for recursive type references\ninterface FragmentResult<\n TDeps,\n TServices extends Record<string, unknown>,\n TServiceThisContext extends RequestThisContext,\n THandlerThisContext extends RequestThisContext,\n TRequestStorage,\n TRoutes extends readonly any[], // eslint-disable-line @typescript-eslint/no-explicit-any\n TSchema extends AnySchema,\n TLinkedFragments extends AnyLinkedFragments = {},\n> {\n fragment: FragnoInstantiatedFragment<\n TRoutes,\n TDeps,\n TServices,\n TServiceThisContext,\n THandlerThisContext,\n TRequestStorage,\n FragnoPublicConfig,\n TLinkedFragments\n >;\n services: TServices;\n deps: TDeps;\n callRoute: FragnoInstantiatedFragment<\n TRoutes,\n TDeps,\n TServices,\n TServiceThisContext,\n THandlerThisContext,\n TRequestStorage,\n FragnoPublicConfig,\n TLinkedFragments\n >[\"callRoute\"];\n db: AbstractQuery<TSchema>;\n}\n\n// Safe: Catch-all for any fragment result type\ntype AnyFragmentResult = FragmentResult<\n any, // eslint-disable-line @typescript-eslint/no-explicit-any\n any, // eslint-disable-line @typescript-eslint/no-explicit-any\n any, // eslint-disable-line @typescript-eslint/no-explicit-any\n any, // eslint-disable-line @typescript-eslint/no-explicit-any\n any, // eslint-disable-line @typescript-eslint/no-explicit-any\n any, // eslint-disable-line @typescript-eslint/no-explicit-any\n any, // eslint-disable-line @typescript-eslint/no-explicit-any\n any // eslint-disable-line @typescript-eslint/no-explicit-any\n>;\n\n// Safe: Catch-all for any fragment builder config type\ntype AnyFragmentBuilderConfig = FragmentBuilderConfig<\n any, // eslint-disable-line @typescript-eslint/no-explicit-any\n any, // eslint-disable-line @typescript-eslint/no-explicit-any\n any, // eslint-disable-line @typescript-eslint/no-explicit-any\n any, // eslint-disable-line @typescript-eslint/no-explicit-any\n any, // eslint-disable-line @typescript-eslint/no-explicit-any\n any, // eslint-disable-line @typescript-eslint/no-explicit-any\n any, // eslint-disable-line @typescript-eslint/no-explicit-any\n any, // eslint-disable-line @typescript-eslint/no-explicit-any\n any, // eslint-disable-line @typescript-eslint/no-explicit-any\n any, // eslint-disable-line @typescript-eslint/no-explicit-any\n any, // eslint-disable-line @typescript-eslint/no-explicit-any\n any // eslint-disable-line @typescript-eslint/no-explicit-any\n>;\n\n/**\n * Configuration for a single fragment in the test builder\n */\ninterface FragmentBuilderConfig<\n TConfig,\n TOptions extends FragnoPublicConfig,\n TDeps,\n TBaseServices extends Record<string, unknown>,\n TServices extends Record<string, unknown>,\n TServiceDependencies,\n TPrivateServices extends Record<string, unknown>,\n TServiceThisContext extends RequestThisContext,\n THandlerThisContext extends RequestThisContext,\n TRequestStorage,\n TRoutesOrFactories extends readonly AnyRouteOrFactory[],\n TLinkedFragments extends AnyLinkedFragments,\n> {\n definition: FragmentDefinition<\n TConfig,\n TOptions,\n TDeps,\n TBaseServices,\n TServices,\n TServiceDependencies,\n TPrivateServices,\n TServiceThisContext,\n THandlerThisContext,\n TRequestStorage,\n TLinkedFragments\n >;\n builder: FragmentInstantiationBuilder<\n TConfig,\n TOptions,\n TDeps,\n TBaseServices,\n TServices,\n TServiceDependencies,\n TPrivateServices,\n TServiceThisContext,\n THandlerThisContext,\n TRequestStorage,\n TRoutesOrFactories,\n TLinkedFragments\n >;\n migrateToVersion?: number;\n}\n\n/**\n * Test context combining base and adapter-specific functionality\n */\ntype TestContext<\n T extends SupportedAdapter,\n TFirstFragmentThisContext extends RequestThisContext = RequestThisContext,\n> = BaseTestContext &\n AdapterContext<T> & {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n adapter: DatabaseAdapter<any>;\n /**\n * Execute a callback within the first fragment's request context.\n * This is useful for calling services outside of route handlers in tests.\n */\n inContext<TResult>(callback: (this: TFirstFragmentThisContext) => TResult): TResult;\n inContext<TResult>(\n callback: (this: TFirstFragmentThisContext) => Promise<TResult>,\n ): Promise<TResult>;\n };\n\n/**\n * Result of building the database fragments test\n */\ninterface DatabaseFragmentsTestResult<\n TFragments extends Record<string, AnyFragmentResult>,\n TAdapter extends SupportedAdapter,\n TFirstFragmentThisContext extends RequestThisContext = RequestThisContext,\n> {\n fragments: TFragments;\n test: TestContext<TAdapter, TFirstFragmentThisContext>;\n}\n\n/**\n * Internal storage for fragment configurations\n */\ntype FragmentConfigMap = Map<string, AnyFragmentBuilderConfig>;\n\n/**\n * Builder for creating multiple database fragments for testing\n */\nexport class DatabaseFragmentsTestBuilder<\n TFragments extends Record<string, AnyFragmentResult>,\n TAdapter extends SupportedAdapter | undefined = undefined,\n TFirstFragmentThisContext extends RequestThisContext = RequestThisContext,\n> {\n #adapter?: SupportedAdapter;\n #fragments: FragmentConfigMap = new Map();\n\n /**\n * Set the test adapter configuration\n */\n withTestAdapter<TNewAdapter extends SupportedAdapter>(\n adapter: TNewAdapter,\n ): DatabaseFragmentsTestBuilder<TFragments, TNewAdapter, TFirstFragmentThisContext> {\n this.#adapter = adapter;\n return this as any; // eslint-disable-line @typescript-eslint/no-explicit-any\n }\n\n /**\n * Add a fragment to the test setup\n *\n * @param name - Unique name for the fragment\n * @param builder - Pre-configured instantiation builder\n * @param options - Additional options (optional)\n */\n withFragment<\n TName extends string,\n TConfig,\n TOptions extends FragnoPublicConfig,\n TDeps,\n TBaseServices extends Record<string, unknown>,\n TServices extends Record<string, unknown>,\n TServiceDependencies,\n TPrivateServices extends Record<string, unknown>,\n TServiceThisContext extends RequestThisContext,\n THandlerThisContext extends RequestThisContext,\n TRequestStorage,\n TRoutesOrFactories extends readonly AnyRouteOrFactory[],\n TLinkedFragments extends AnyLinkedFragments,\n >(\n name: TName,\n builder: FragmentInstantiationBuilder<\n TConfig,\n TOptions,\n TDeps,\n TBaseServices,\n TServices,\n TServiceDependencies,\n TPrivateServices,\n TServiceThisContext,\n THandlerThisContext,\n TRequestStorage,\n TRoutesOrFactories,\n TLinkedFragments\n >,\n options?: {\n migrateToVersion?: number;\n },\n ): DatabaseFragmentsTestBuilder<\n TFragments & {\n [K in TName]: FragmentResult<\n TDeps,\n BoundServices<TBaseServices & TServices>,\n TServiceThisContext,\n THandlerThisContext,\n TRequestStorage,\n FlattenRouteFactories<TRoutesOrFactories>,\n ExtractSchemaFromDeps<TDeps>, // Extract actual schema type from deps\n TLinkedFragments\n >;\n },\n TAdapter,\n // If this is the first fragment (TFragments is empty {}), use THandlerThisContext; otherwise keep existing\n keyof TFragments extends never ? THandlerThisContext : TFirstFragmentThisContext\n > {\n this.#fragments.set(name, {\n definition: builder.definition,\n builder,\n migrateToVersion: options?.migrateToVersion,\n });\n return this as any; // eslint-disable-line @typescript-eslint/no-explicit-any\n }\n\n /**\n * Build the test setup and return fragments and test context\n */\n async build(): Promise<\n TAdapter extends SupportedAdapter\n ? DatabaseFragmentsTestResult<TFragments, TAdapter, TFirstFragmentThisContext>\n : never\n > {\n if (!this.#adapter) {\n throw new Error(\"Test adapter must be set using withTestAdapter()\");\n }\n\n if (this.#fragments.size === 0) {\n throw new Error(\"At least one fragment must be added using withFragment()\");\n }\n\n const adapterConfig = this.#adapter;\n\n // Extract fragment names and configs\n const fragmentNames = Array.from(this.#fragments.keys());\n const fragmentConfigs = Array.from(this.#fragments.values());\n\n // Extract schemas from definitions and prepare schema configs\n const schemaConfigs: SchemaConfig[] = [];\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const builderConfigs: Array<{ config: any; routes: any; options: any }> = [];\n\n for (const fragmentConfig of fragmentConfigs) {\n const builder = fragmentConfig.builder;\n const definition = builder.definition;\n\n // Extract schema from definition by calling dependencies with a mock adapter\n let schema: AnySchema | undefined;\n const namespace = definition.name + \"-db\";\n\n if (definition.dependencies) {\n try {\n // Create a mock adapter to extract the schema\n const mockAdapter = {\n createQueryEngine: () => ({ schema: null }),\n contextStorage: { run: (_data: unknown, fn: () => unknown) => fn() },\n };\n\n // Use the actual config from the builder instead of an empty mock\n // This ensures dependencies can be properly initialized (e.g., Stripe with API keys)\n const actualConfig = builder.config ?? {};\n\n const deps = definition.dependencies({\n config: actualConfig,\n options: {\n databaseAdapter: mockAdapter as any, // eslint-disable-line @typescript-eslint/no-explicit-any\n } as any, // eslint-disable-line @typescript-eslint/no-explicit-any\n });\n\n // The schema is in deps.schema for database fragments\n if (deps && typeof deps === \"object\" && \"schema\" in deps) {\n schema = (deps as any).schema; // eslint-disable-line @typescript-eslint/no-explicit-any\n }\n } catch (error) {\n // If extraction fails, provide a helpful error message\n const errorMessage =\n error instanceof Error\n ? error.message\n : typeof error === \"string\"\n ? error\n : \"Unknown error\";\n\n throw new Error(\n `Failed to extract schema from fragment '${definition.name}'.\\n` +\n `Original error: ${errorMessage}\\n\\n` +\n `Make sure the fragment is a database fragment using defineFragment().extend(withDatabase(schema)).`,\n );\n }\n }\n\n if (!schema) {\n throw new Error(\n `Fragment '${definition.name}' does not have a database schema. ` +\n `Make sure you're using defineFragment().extend(withDatabase(schema)).`,\n );\n }\n\n schemaConfigs.push({\n schema,\n namespace,\n migrateToVersion: fragmentConfig.migrateToVersion,\n });\n\n // Extract config, routes, and options from builder using public getters\n builderConfigs.push({\n config: builder.config ?? {},\n routes: builder.routes ?? [],\n options: builder.options ?? {},\n });\n }\n\n // Create adapter with all schemas\n const { testContext, adapter } = await createAdapter(adapterConfig, schemaConfigs);\n\n // Helper to create fragments with service wiring\n const createFragments = () => {\n // First pass: create fragments without service dependencies to extract provided services\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const providedServicesByName: Record<string, { service: any; orm: any }> = {};\n const fragmentResults: any[] = []; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n for (let i = 0; i < fragmentConfigs.length; i++) {\n const fragmentConfig = fragmentConfigs[i]!;\n const builderConfig = builderConfigs[i]!;\n const namespace = schemaConfigs[i]!.namespace;\n const schema = schemaConfigs[i]!.schema;\n const orm = testContext.getOrm(namespace);\n\n // Merge builder options with database adapter\n const mergedOptions = {\n ...builderConfig.options,\n databaseAdapter: adapter,\n };\n\n // Instantiate fragment using the builder\n const fragment = fragmentConfig.builder.withOptions(mergedOptions).build();\n\n // Extract provided services based on serviceDependencies metadata\n // Note: serviceDependencies lists services this fragment USES, not provides\n // For provided services, we need to check what's actually in fragment.services\n // and match against other fragments' service dependencies\n\n // Store all services as potentially provided\n for (const [serviceName, serviceImpl] of Object.entries(fragment.services)) {\n providedServicesByName[serviceName] = {\n service: serviceImpl,\n orm,\n };\n }\n\n // Store the fragment result\n const deps = fragment.$internal?.deps;\n\n fragmentResults.push({\n fragment,\n services: fragment.services,\n deps: deps || {},\n callRoute: fragment.callRoute.bind(fragment),\n get db() {\n return orm;\n },\n _orm: orm,\n _schema: schema,\n });\n }\n\n // Second pass: rebuild fragments with service dependencies wired up\n for (let i = 0; i < fragmentConfigs.length; i++) {\n const fragmentConfig = fragmentConfigs[i]!;\n const definition = fragmentConfig.builder.definition;\n const builderConfig = builderConfigs[i]!;\n const namespace = schemaConfigs[i]!.namespace;\n const schema = schemaConfigs[i]!.schema;\n const orm = testContext.getOrm(namespace);\n\n // Build service implementations for services this fragment uses\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const serviceImplementations: Record<string, any> = {};\n const serviceDependencies = definition.serviceDependencies;\n\n if (serviceDependencies) {\n for (const serviceName of Object.keys(serviceDependencies)) {\n if (providedServicesByName[serviceName]) {\n serviceImplementations[serviceName] = providedServicesByName[serviceName]!.service;\n }\n }\n }\n\n // Merge builder options with database adapter\n const mergedOptions = {\n ...builderConfig.options,\n databaseAdapter: adapter,\n };\n\n // Rebuild the fragment with service implementations using the builder\n const fragment = fragmentConfig.builder\n .withOptions(mergedOptions)\n .withServices(serviceImplementations as any) // eslint-disable-line @typescript-eslint/no-explicit-any\n .build();\n\n // Update the result\n // Access deps from the internal property\n const deps = fragment.$internal?.deps;\n\n fragmentResults[i] = {\n fragment,\n services: fragment.services,\n deps: deps || {},\n callRoute: fragment.callRoute.bind(fragment),\n get db() {\n return orm;\n },\n _orm: orm,\n _schema: schema,\n };\n }\n\n return fragmentResults;\n };\n\n const fragmentResults = createFragments();\n\n // Wrap resetDatabase to also recreate all fragments\n const originalResetDatabase = testContext.resetDatabase;\n const resetDatabase = async () => {\n await originalResetDatabase();\n\n // Recreate all fragments with service wiring\n const newFragmentResults = createFragments();\n\n // Update the result objects\n newFragmentResults.forEach((newResult, index) => {\n const result = fragmentResults[index]!;\n result.fragment = newResult.fragment;\n result.services = newResult.services;\n result.deps = newResult.deps;\n result.callRoute = newResult.callRoute;\n result._orm = newResult._orm;\n });\n };\n\n // Get the first fragment's inContext method\n const firstFragment = fragmentResults[0]?.fragment;\n if (!firstFragment) {\n throw new Error(\"At least one fragment must be added\");\n }\n\n const finalTestContext = {\n ...testContext,\n resetDatabase,\n adapter,\n inContext: firstFragment.inContext.bind(firstFragment),\n };\n\n // Build result object with named fragments\n const fragmentsObject = Object.fromEntries(\n fragmentNames.map((name, index) => [name, fragmentResults[index]]),\n );\n\n // Safe cast: We've already validated that adapterConfig is SupportedAdapter at the beginning of build()\n // TypeScript can't infer this through the conditional return type, so we use 'as any'\n return {\n fragments: fragmentsObject as TFragments,\n test: finalTestContext,\n } as any; // eslint-disable-line @typescript-eslint/no-explicit-any\n }\n}\n\n/**\n * Create a builder for setting up multiple database fragments for testing.\n * This is the new builder-based API that works with the new fragment instantiation builders.\n *\n * @example\n * ```typescript\n * const userFragmentDef = defineFragment(\"user\")\n * .extend(withDatabase(userSchema))\n * .withDependencies(...)\n * .build();\n *\n * const postFragmentDef = defineFragment(\"post\")\n * .extend(withDatabase(postSchema))\n * .withDependencies(...)\n * .build();\n *\n * const { fragments, test } = await buildDatabaseFragmentsTest()\n * .withTestAdapter({ type: \"kysely-sqlite\" })\n * .withFragment(\"user\",\n * instantiate(userFragmentDef)\n * .withConfig({ ... })\n * .withRoutes([...])\n * )\n * .withFragment(\"post\",\n * instantiate(postFragmentDef)\n * .withRoutes([...])\n * )\n * .build();\n *\n * // Access fragments by name\n * await fragments.user.services.createUser(...);\n * await fragments.post.services.createPost(...);\n *\n * // Access dependencies directly\n * const userDeps = fragments.user.deps;\n *\n * // Shared test context\n * await test.resetDatabase();\n * await test.cleanup();\n * const adapter = test.adapter; // Access the database adapter\n * ```\n */\nexport function buildDatabaseFragmentsTest(): DatabaseFragmentsTestBuilder<\n {},\n undefined,\n RequestThisContext\n> {\n return new DatabaseFragmentsTestBuilder();\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,40 +1,35 @@
1
- import { DrizzlePgliteAdapter, KyselyPgliteAdapter, KyselySqliteAdapter, SupportedAdapter, TestContext } from "./adapters.js";
2
- import { CreateFragmentForTestOptions, CreateFragmentForTestOptions as CreateFragmentForTestOptions$1, FragmentForTest, FragmentForTest as FragmentForTest$1, RouteHandlerInputOptions, createFragmentForTest } from "@fragno-dev/core/test";
1
+ import { AdapterContext, DrizzlePgliteAdapter, KyselyPgliteAdapter, KyselySqliteAdapter, SupportedAdapter } from "./adapters.js";
2
+ import { DatabaseFragmentsTestBuilder, buildDatabaseFragmentsTest } from "./db-test.js";
3
+ import { CreateFragmentForTestOptions, RouteHandlerInputOptions, createFragmentForTest } from "@fragno-dev/core/test";
3
4
  import { AnySchema } from "@fragno-dev/db/schema";
4
- import { FragnoPublicConfig } from "@fragno-dev/core/api/fragment-instantiation";
5
- import { FragmentDefinition } from "@fragno-dev/core/api/fragment-builder";
6
- import { AnyRouteOrFactory, FlattenRouteFactories } from "@fragno-dev/core/api/route";
7
- import { FragnoRouteConfig } from "@fragno-dev/core";
8
- import { HTTPMethod } from "@fragno-dev/core/api";
9
- import { StandardSchemaV1 } from "@standard-schema/spec";
5
+ import { AbstractQuery } from "@fragno-dev/db/query";
6
+ import { DatabaseAdapter } from "@fragno-dev/db";
10
7
 
11
8
  //#region src/index.d.ts
12
9
 
13
10
  /**
14
- * Options for creating a database fragment for testing
11
+ * Base test context with common functionality across all adapters
15
12
  */
16
- interface CreateDatabaseFragmentForTestOptions<TConfig, TDeps, TServices, TAdditionalContext extends Record<string, unknown>, TOptions extends FragnoPublicConfig, TAdapter extends SupportedAdapter> extends Omit<CreateFragmentForTestOptions$1<TConfig, TDeps, TServices, TAdditionalContext, TOptions>, "config"> {
17
- adapter: TAdapter;
18
- migrateToVersion?: number;
19
- config?: TConfig;
13
+ interface BaseTestContext {
14
+ readonly adapter: DatabaseAdapter<any>;
15
+ resetDatabase: () => Promise<void>;
16
+ cleanup: () => Promise<void>;
20
17
  }
21
18
  /**
22
- * Result of creating a database fragment for testing
23
- * All properties are getters that return the current fragment instance
19
+ * Internal interface with getOrm for adapter implementations
24
20
  */
25
- interface DatabaseFragmentTestResult<TConfig, TDeps, TServices, TAdditionalContext extends Record<string, unknown>, TOptions extends FragnoPublicConfig, TAdapter extends SupportedAdapter, TRoutes extends readonly FragnoRouteConfig<HTTPMethod, string, StandardSchemaV1 | undefined, StandardSchemaV1 | undefined, string, string>[]> {
26
- readonly fragment: FragmentForTest$1<TConfig, TDeps, TServices, TAdditionalContext, TOptions, TRoutes>;
27
- readonly services: TServices;
28
- readonly callRoute: FragmentForTest$1<TConfig, TDeps, TServices, TAdditionalContext, TOptions, TRoutes>["callRoute"];
29
- readonly config: TConfig;
30
- readonly deps: TDeps;
31
- readonly additionalContext: TAdditionalContext;
32
- test: TestContext<TAdapter>;
21
+ interface InternalTestContextMethods {
22
+ getOrm: <TSchema extends AnySchema>(namespace: string) => AbstractQuery<TSchema>;
33
23
  }
34
- 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, const TAdapter extends SupportedAdapter, const TRoutesOrFactories extends readonly AnyRouteOrFactory[]>(fragmentBuilder: {
35
- definition: FragmentDefinition<TConfig, TDeps, TServices, TAdditionalContext>;
36
- $requiredOptions: TOptions;
37
- }, routesOrFactories: TRoutesOrFactories, options: CreateDatabaseFragmentForTestOptions<TConfig, TDeps, TServices, TAdditionalContext, TOptions, TAdapter>): Promise<DatabaseFragmentTestResult<TConfig, TDeps, TServices, TAdditionalContext, TOptions, TAdapter, FlattenRouteFactories<TRoutesOrFactories>>>;
24
+ /**
25
+ * Helper to create common test context methods from an ORM map
26
+ * This is used internally by adapter implementations to avoid code duplication
27
+ */
28
+ declare function createCommonTestContextMethods(ormMap: Map<string, AbstractQuery<any>>): InternalTestContextMethods;
29
+ /**
30
+ * Complete test context combining base and adapter-specific functionality
31
+ */
32
+ type TestContext<T extends SupportedAdapter> = BaseTestContext & AdapterContext<T>;
38
33
  //#endregion
39
- export { CreateDatabaseFragmentForTestOptions, type CreateFragmentForTestOptions, DatabaseFragmentTestResult, type DrizzlePgliteAdapter, type FragmentForTest, type KyselyPgliteAdapter, type KyselySqliteAdapter, type RouteHandlerInputOptions, type SupportedAdapter, type TestContext, createDatabaseFragmentForTest, createFragmentForTest };
34
+ export { type AdapterContext, BaseTestContext, type CreateFragmentForTestOptions, DatabaseFragmentsTestBuilder, type DrizzlePgliteAdapter, InternalTestContextMethods, type KyselyPgliteAdapter, type KyselySqliteAdapter, type RouteHandlerInputOptions, type SupportedAdapter, TestContext, buildDatabaseFragmentsTest, createCommonTestContextMethods, createFragmentForTest };
40
35
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;AAyCA;;;AAMmB,UANF,oCAME,CAAA,OAAA,EAAA,KAAA,EAAA,SAAA,EAAA,2BAFU,MAEV,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,iBADA,kBACA,EAAA,iBAAA,gBAAA,CAAA,SACT,IADS,CAEf,8BAFe,CAEc,OAFd,EAEuB,KAFvB,EAE8B,SAF9B,EAEyC,kBAFzC,EAE6D,QAF7D,CAAA,EAAA,QAAA,CAAA,CAAA;EAEc,OAAA,EAGtB,QAHsB;EAAS,gBAAA,CAAA,EAAA,MAAA;EAAO,MAAA,CAAA,EAKtC,OALsC;;;;;;AADvC,UAaO,0BAbP,CAAA,OAAA,EAAA,KAAA,EAAA,SAAA,EAAA,2BAiBmB,MAjBnB,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,iBAkBS,kBAlBT,EAAA,iBAmBS,gBAnBT,EAAA,gBAAA,SAoBiB,iBApBjB,CAqBN,UArBM,EAAA,MAAA,EAuBN,gBAvBM,GAAA,SAAA,EAwBN,gBAxBM,GAAA,SAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAAA,CAAA,CAAA;EAAI,SAAA,QAAA,EA6BO,iBA7BP,CA8BV,OA9BU,EA+BV,KA/BU,EAgCV,SAhCU,EAiCV,kBAjCU,EAkCV,QAlCU,EAmCV,OAnCU,CAAA;EAaG,SAAA,QAAA,EAwBI,SAxBJ;EAIY,SAAA,SAAA,EAqBP,iBArBO,CAsBzB,OAtByB,EAuBzB,KAvByB,EAwBzB,SAxByB,EAyBzB,kBAzByB,EA0BzB,QA1ByB,EA2BzB,OA3ByB,CAAA,CAAA,WAAA,CAAA;EACV,SAAA,MAAA,EA4BA,OA5BA;EACA,SAAA,IAAA,EA4BF,KA5BE;EAEf,SAAA,iBAAA,EA2B0B,kBA3B1B;EAEA,IAAA,EA0BI,WA1BJ,CA0BgB,QA1BhB,CAAA;;AAHuB,iBAgCL,6BAhCK,CAAA,aAAA,EAAA,WAAA,EAAA,wBAmCD,MAnCC,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,iCAoCQ,MApCR,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,uBAqCF,kBArCE,EAAA,sBAsCH,SAtCG,EAAA,uBAuCF,gBAvCE,EAAA,iCAAA,SAwCiB,iBAxCjB,EAAA,CAAA,CAAA,eAAA,EAAA;EAUvB,UAAA,EAiCY,kBAjCZ,CAiC+B,OAjC/B,EAiCwC,KAjCxC,EAiC+C,SAjC/C,EAiC0D,kBAjC1D,CAAA;EACA,gBAAA,EAiCkB,QAjClB;CACA,EAAA,iBAAA,EAkCiB,kBAlCjB,EAAA,OAAA,EAmCO,oCAnCP,CAoCA,OApCA,EAqCA,KArCA,EAsCA,SAtCA,EAuCA,kBAvCA,EAwCA,QAxCA,EAyCA,QAzCA,CAAA,CAAA,EA2CD,OA3CC,CA4CF,0BA5CE,CA6CA,OA7CA,EA8CA,KA9CA,EA+CA,SA/CA,EAgDA,kBAhDA,EAiDA,QAjDA,EAkDA,QAlDA,EAmDA,qBAnDA,CAmDsB,kBAnDtB,CAAA,CAAA,CAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;AAiCA;;AAGuB,UAHN,eAAA,CAGM;EACN,SAAA,OAAA,EAFG,eAEH,CAAA,GAAA,CAAA;EAAO,aAAA,EAAA,GAAA,GADD,OACC,CAAA,IAAA,CAAA;EAMP,OAAA,EAAA,GAAA,GANA,OAMA,CAAA,IAAA,CAAA;;;;;AAQD,UARC,0BAAA,CAQ6B;EAExB,MAAA,EAAA,CAAA,gBATK,SASL,CAAA,CAAA,SAAA,EAAA,MAAA,EAAA,GATsC,aAStC,CAToD,OASpD,CAAA;;;;AAgBtB;;AAAsD,iBAlBtC,8BAAA,CAkBsC,MAAA,EAhB5C,GAgB4C,CAAA,MAAA,EAhBhC,aAgBgC,CAAA,GAAA,CAAA,CAAA,CAAA,EAfnD,0BAemD;;;;KAA1C,sBAAsB,oBAAoB,kBAAkB,eAAe"}
package/dist/index.js CHANGED
@@ -1,97 +1,19 @@
1
- import { createAdapter } from "./adapters.js";
2
- import { createFragmentForTest, createFragmentForTest as createFragmentForTest$1 } from "@fragno-dev/core/test";
1
+ import { DatabaseFragmentsTestBuilder, buildDatabaseFragmentsTest } from "./db-test.js";
2
+ import { createFragmentForTest } from "@fragno-dev/core/test";
3
3
 
4
4
  //#region src/index.ts
5
- async function createDatabaseFragmentForTest(fragmentBuilder, routesOrFactories, options) {
6
- const { adapter: adapterConfig, migrateToVersion, config, options: fragmentOptions, deps, services, additionalContext } = options;
7
- const fragmentAdditionalContext = fragmentBuilder.definition.additionalContext;
8
- const schema = fragmentAdditionalContext?.databaseSchema;
9
- const namespace = fragmentAdditionalContext?.databaseNamespace ?? "";
10
- if (!schema) throw new Error(`Fragment '${fragmentBuilder.definition.name}' does not have a database schema. Make sure you're using defineFragmentWithDatabase().withDatabase(schema).`);
11
- const { testContext: originalTestContext, adapter } = await createAdapter(adapterConfig, schema, namespace, migrateToVersion);
12
- let mergedOptions = {
13
- ...fragmentOptions,
14
- databaseAdapter: adapter
15
- };
16
- let fragment = createFragmentForTest$1(fragmentBuilder, routesOrFactories, {
17
- config,
18
- options: mergedOptions,
19
- deps,
20
- services,
21
- additionalContext
22
- });
23
- const originalResetDatabase = originalTestContext.resetDatabase;
24
- return {
25
- get fragment() {
26
- return fragment;
27
- },
28
- get services() {
29
- return fragment.services;
30
- },
31
- get callRoute() {
32
- return fragment.callRoute;
33
- },
34
- get config() {
35
- return fragment.config;
36
- },
37
- get deps() {
38
- return fragment.deps;
39
- },
40
- get additionalContext() {
41
- return fragment.additionalContext;
42
- },
43
- test: Object.create(originalTestContext, {
44
- db: {
45
- get() {
46
- return originalTestContext.db;
47
- },
48
- enumerable: true
49
- },
50
- kysely: {
51
- get() {
52
- return originalTestContext.kysely;
53
- },
54
- enumerable: true
55
- },
56
- drizzle: {
57
- get() {
58
- return originalTestContext.drizzle;
59
- },
60
- enumerable: true
61
- },
62
- adapter: {
63
- get() {
64
- return originalTestContext.adapter;
65
- },
66
- enumerable: true
67
- },
68
- resetDatabase: {
69
- value: async () => {
70
- await originalResetDatabase();
71
- mergedOptions = {
72
- ...fragmentOptions,
73
- databaseAdapter: originalTestContext.adapter
74
- };
75
- fragment = createFragmentForTest$1(fragmentBuilder, routesOrFactories, {
76
- config,
77
- options: mergedOptions,
78
- deps,
79
- services,
80
- additionalContext
81
- });
82
- },
83
- enumerable: true
84
- },
85
- cleanup: {
86
- value: async () => {
87
- await originalTestContext.cleanup();
88
- },
89
- enumerable: true
90
- }
91
- })
92
- };
5
+ /**
6
+ * Helper to create common test context methods from an ORM map
7
+ * This is used internally by adapter implementations to avoid code duplication
8
+ */
9
+ function createCommonTestContextMethods(ormMap) {
10
+ return { getOrm: (namespace) => {
11
+ const orm = ormMap.get(namespace);
12
+ if (!orm) throw new Error(`No ORM found for namespace: ${namespace}`);
13
+ return orm;
14
+ } };
93
15
  }
94
16
 
95
17
  //#endregion
96
- export { createDatabaseFragmentForTest, createFragmentForTest };
18
+ export { DatabaseFragmentsTestBuilder, buildDatabaseFragmentsTest, createCommonTestContextMethods, createFragmentForTest };
97
19
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["createFragmentForTest"],"sources":["../src/index.ts"],"sourcesContent":["import type { AnySchema } from \"@fragno-dev/db/schema\";\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\";\nimport type { AnyRouteOrFactory, FlattenRouteFactories } from \"@fragno-dev/core/api/route\";\nimport {\n createAdapter,\n type SupportedAdapter,\n type TestContext,\n type KyselySqliteAdapter,\n type KyselyPgliteAdapter,\n type DrizzlePgliteAdapter,\n} from \"./adapters\";\nimport type { FragnoRouteConfig } from \"@fragno-dev/core\";\nimport type { HTTPMethod } from \"@fragno-dev/core/api\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n// Re-export utilities from @fragno-dev/core/test\nexport {\n createFragmentForTest,\n type CreateFragmentForTestOptions,\n type RouteHandlerInputOptions,\n type FragmentForTest,\n} from \"@fragno-dev/core/test\";\n\n// Re-export adapter types\nexport type {\n SupportedAdapter,\n KyselySqliteAdapter,\n KyselyPgliteAdapter,\n DrizzlePgliteAdapter,\n TestContext,\n} from \"./adapters\";\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 TAdapter extends SupportedAdapter,\n> extends Omit<\n CreateFragmentForTestOptions<TConfig, TDeps, TServices, TAdditionalContext, TOptions>,\n \"config\"\n > {\n adapter: TAdapter;\n migrateToVersion?: number;\n config?: TConfig;\n}\n\n/**\n * Result of creating a database fragment for testing\n * All properties are getters that return the current fragment instance\n */\nexport interface DatabaseFragmentTestResult<\n TConfig,\n TDeps,\n TServices,\n TAdditionalContext extends Record<string, unknown>,\n TOptions extends FragnoPublicConfig,\n TAdapter extends SupportedAdapter,\n TRoutes extends readonly FragnoRouteConfig<\n HTTPMethod,\n string,\n StandardSchemaV1 | undefined,\n StandardSchemaV1 | undefined,\n string,\n string\n >[],\n> {\n readonly fragment: FragmentForTest<\n TConfig,\n TDeps,\n TServices,\n TAdditionalContext,\n TOptions,\n TRoutes\n >;\n readonly services: TServices;\n readonly callRoute: FragmentForTest<\n TConfig,\n TDeps,\n TServices,\n TAdditionalContext,\n TOptions,\n TRoutes\n >[\"callRoute\"];\n readonly config: TConfig;\n readonly deps: TDeps;\n readonly additionalContext: TAdditionalContext;\n test: TestContext<TAdapter>;\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 const TAdapter extends SupportedAdapter,\n const TRoutesOrFactories extends readonly AnyRouteOrFactory[],\n>(\n fragmentBuilder: {\n definition: FragmentDefinition<TConfig, TDeps, TServices, TAdditionalContext>;\n $requiredOptions: TOptions;\n },\n routesOrFactories: TRoutesOrFactories,\n options: CreateDatabaseFragmentForTestOptions<\n TConfig,\n TDeps,\n TServices,\n TAdditionalContext,\n TOptions,\n TAdapter\n >,\n): Promise<\n DatabaseFragmentTestResult<\n TConfig,\n TDeps,\n TServices,\n TAdditionalContext,\n TOptions,\n TAdapter,\n FlattenRouteFactories<TRoutesOrFactories>\n >\n> {\n const {\n adapter: adapterConfig,\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 // Create adapter using the factory\n const { testContext: originalTestContext, adapter } = await createAdapter(\n adapterConfig,\n schema,\n namespace,\n migrateToVersion,\n );\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 let fragment = createFragmentForTest(fragmentBuilder, routesOrFactories, {\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 config: config as TConfig,\n options: mergedOptions,\n deps,\n services,\n additionalContext,\n });\n\n // Wrap resetDatabase to also recreate the fragment with the new adapter\n const originalResetDatabase = originalTestContext.resetDatabase;\n\n // Create test context with getters that always return current values\n // We need to cast to any to avoid TypeScript errors when accessing kysely/drizzle properties\n // that may not exist depending on the adapter type\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const testContext: any = Object.create(originalTestContext, {\n db: {\n get() {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (originalTestContext as any).db;\n },\n enumerable: true,\n },\n kysely: {\n get() {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (originalTestContext as any).kysely;\n },\n enumerable: true,\n },\n drizzle: {\n get() {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (originalTestContext as any).drizzle;\n },\n enumerable: true,\n },\n adapter: {\n get() {\n return originalTestContext.adapter;\n },\n enumerable: true,\n },\n resetDatabase: {\n value: async () => {\n // Call the original reset database function\n await originalResetDatabase();\n\n // Recreate the fragment with the new adapter (which has been updated by the factory's resetDatabase)\n mergedOptions = {\n ...fragmentOptions,\n databaseAdapter: originalTestContext.adapter,\n } as unknown as TOptions;\n\n fragment = createFragmentForTest(fragmentBuilder, routesOrFactories, {\n config: config as TConfig,\n options: mergedOptions,\n deps,\n services,\n additionalContext,\n });\n },\n enumerable: true,\n },\n cleanup: {\n value: async () => {\n await originalTestContext.cleanup();\n },\n enumerable: true,\n },\n });\n\n // Return an object with getters for fragment properties so they always reference the current fragment\n return {\n get fragment() {\n return fragment;\n },\n get services() {\n return fragment.services;\n },\n get callRoute() {\n return fragment.callRoute;\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 test: testContext,\n };\n}\n"],"mappings":";;;;AAoGA,eAAsB,8BAUpB,iBAIA,mBACA,SAkBA;CACA,MAAM,EACJ,SAAS,eACT,kBACA,QACA,SAAS,iBACT,MACA,UACA,sBACE;CAKJ,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,EAAE,aAAa,qBAAqB,YAAY,MAAM,cAC1D,eACA,QACA,WACA,iBACD;CAKD,IAAI,gBAAgB;EAClB,GAAG;EACH,iBAAiB;EAClB;CAED,IAAI,WAAWA,wBAAsB,iBAAiB,mBAAmB;EAI/D;EACR,SAAS;EACT;EACA;EACA;EACD,CAAC;CAGF,MAAM,wBAAwB,oBAAoB;AAgElD,QAAO;EACL,IAAI,WAAW;AACb,UAAO;;EAET,IAAI,WAAW;AACb,UAAO,SAAS;;EAElB,IAAI,YAAY;AACd,UAAO,SAAS;;EAElB,IAAI,SAAS;AACX,UAAO,SAAS;;EAElB,IAAI,OAAO;AACT,UAAO,SAAS;;EAElB,IAAI,oBAAoB;AACtB,UAAO,SAAS;;EAElB,MA7EuB,OAAO,OAAO,qBAAqB;GAC1D,IAAI;IACF,MAAM;AAEJ,YAAQ,oBAA4B;;IAEtC,YAAY;IACb;GACD,QAAQ;IACN,MAAM;AAEJ,YAAQ,oBAA4B;;IAEtC,YAAY;IACb;GACD,SAAS;IACP,MAAM;AAEJ,YAAQ,oBAA4B;;IAEtC,YAAY;IACb;GACD,SAAS;IACP,MAAM;AACJ,YAAO,oBAAoB;;IAE7B,YAAY;IACb;GACD,eAAe;IACb,OAAO,YAAY;AAEjB,WAAM,uBAAuB;AAG7B,qBAAgB;MACd,GAAG;MACH,iBAAiB,oBAAoB;MACtC;AAED,gBAAWA,wBAAsB,iBAAiB,mBAAmB;MAC3D;MACR,SAAS;MACT;MACA;MACA;MACD,CAAC;;IAEJ,YAAY;IACb;GACD,SAAS;IACP,OAAO,YAAY;AACjB,WAAM,oBAAoB,SAAS;;IAErC,YAAY;IACb;GACF,CAAC;EAuBD"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { AnySchema } from \"@fragno-dev/db/schema\";\nimport type {\n SupportedAdapter,\n AdapterContext,\n KyselySqliteAdapter,\n KyselyPgliteAdapter,\n DrizzlePgliteAdapter,\n} from \"./adapters\";\nimport type { DatabaseAdapter } from \"@fragno-dev/db\";\nimport type { AbstractQuery } from \"@fragno-dev/db/query\";\n\n// Re-export utilities from @fragno-dev/core/test\nexport {\n createFragmentForTest,\n type CreateFragmentForTestOptions,\n type RouteHandlerInputOptions,\n} from \"@fragno-dev/core/test\";\n\n// Re-export adapter types\nexport type {\n SupportedAdapter,\n KyselySqliteAdapter,\n KyselyPgliteAdapter,\n DrizzlePgliteAdapter,\n AdapterContext,\n} from \"./adapters\";\n\n// Re-export new builder-based database test utilities\nexport { buildDatabaseFragmentsTest, DatabaseFragmentsTestBuilder } from \"./db-test\";\n\n/**\n * Base test context with common functionality across all adapters\n */\nexport interface BaseTestContext {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readonly adapter: DatabaseAdapter<any>;\n resetDatabase: () => Promise<void>;\n cleanup: () => Promise<void>;\n}\n\n/**\n * Internal interface with getOrm for adapter implementations\n */\nexport interface InternalTestContextMethods {\n getOrm: <TSchema extends AnySchema>(namespace: string) => AbstractQuery<TSchema>;\n}\n\n/**\n * Helper to create common test context methods from an ORM map\n * This is used internally by adapter implementations to avoid code duplication\n */\nexport function createCommonTestContextMethods(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n ormMap: Map<string, AbstractQuery<any>>,\n): InternalTestContextMethods {\n return {\n getOrm: <TSchema extends AnySchema>(namespace: string) => {\n const orm = ormMap.get(namespace);\n if (!orm) {\n throw new Error(`No ORM found for namespace: ${namespace}`);\n }\n return orm as AbstractQuery<TSchema>;\n },\n };\n}\n\n/**\n * Complete test context combining base and adapter-specific functionality\n */\nexport type TestContext<T extends SupportedAdapter> = BaseTestContext & AdapterContext<T>;\n"],"mappings":";;;;;;;;AAmDA,SAAgB,+BAEd,QAC4B;AAC5B,QAAO,EACL,SAAoC,cAAsB;EACxD,MAAM,MAAM,OAAO,IAAI,UAAU;AACjC,MAAI,CAAC,IACH,OAAM,IAAI,MAAM,+BAA+B,YAAY;AAE7D,SAAO;IAEV"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fragno-dev/test",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -15,15 +15,15 @@
15
15
  "dependencies": {
16
16
  "@standard-schema/spec": "^1.0.0",
17
17
  "kysely": "^0.28.7",
18
- "sqlocal": "^0.15.2",
19
- "@fragno-dev/core": "0.1.7",
20
- "@fragno-dev/db": "0.1.13"
18
+ "sqlocal": "^0.15.2"
21
19
  },
22
20
  "peerDependencies": {
23
21
  "@electric-sql/pglite": "^0.3.11",
24
22
  "drizzle-kit": "^0.30.3",
25
23
  "drizzle-orm": "^0.44.7",
26
- "kysely-pglite": "^0.6.1"
24
+ "kysely-pglite": "^0.6.1",
25
+ "@fragno-dev/core": "0.1.9",
26
+ "@fragno-dev/db": "0.1.15"
27
27
  },
28
28
  "peerDependenciesMeta": {
29
29
  "@electric-sql/pglite": {
@@ -49,6 +49,8 @@
49
49
  "kysely-pglite": "^0.6.1",
50
50
  "vitest": "^3.2.4",
51
51
  "zod": "^4.1.12",
52
+ "@fragno-dev/core": "0.1.9",
53
+ "@fragno-dev/db": "0.1.15",
52
54
  "@fragno-private/typescript-config": "0.0.1",
53
55
  "@fragno-private/vitest-config": "0.0.0"
54
56
  },