@fragno-dev/test 0.1.12 → 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.
- package/.turbo/turbo-build.log +11 -7
- package/CHANGELOG.md +27 -0
- package/dist/db-test.d.ts +129 -0
- package/dist/db-test.d.ts.map +1 -0
- package/dist/db-test.js +214 -0
- package/dist/db-test.js.map +1 -0
- package/dist/index.d.ts +5 -82
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -143
- package/dist/index.js.map +1 -1
- package/package.json +7 -5
- package/src/db-test.test.ts +352 -0
- package/src/db-test.ts +574 -0
- package/src/index.test.ts +94 -94
- package/src/index.ts +10 -398
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @fragno-dev/test@0.1.
|
|
2
|
+
> @fragno-dev/test@0.1.13 build /home/runner/work/fragno/fragno/packages/fragno-test
|
|
3
3
|
> tsdown
|
|
4
4
|
|
|
5
5
|
[34mℹ[39m tsdown [2mv0.15.12[22m powered by rolldown [2mv1.0.0-beta.45[22m
|
|
@@ -7,13 +7,17 @@
|
|
|
7
7
|
[34mℹ[39m entry: [34msrc/index.ts[39m
|
|
8
8
|
[34mℹ[39m tsconfig: [34mtsconfig.json[39m
|
|
9
9
|
[34mℹ[39m Build start
|
|
10
|
-
[34mℹ[39m [2mdist/[22m[1mindex.js[22m [2m
|
|
11
|
-
[34mℹ[39m [2mdist/[
|
|
10
|
+
[34mℹ[39m [2mdist/[22m[1mindex.js[22m [2m 0.71 kB[22m [2m│ gzip: 0.40 kB[22m
|
|
11
|
+
[34mℹ[39m [2mdist/[22mdb-test.js.map [2m23.72 kB[22m [2m│ gzip: 5.83 kB[22m
|
|
12
12
|
[34mℹ[39m [2mdist/[22madapters.js.map [2m18.56 kB[22m [2m│ gzip: 4.04 kB[22m
|
|
13
13
|
[34mℹ[39m [2mdist/[22madapters.js [2m 7.97 kB[22m [2m│ gzip: 1.74 kB[22m
|
|
14
|
-
[34mℹ[39m [2mdist/[
|
|
14
|
+
[34mℹ[39m [2mdist/[22mdb-test.js [2m 7.25 kB[22m [2m│ gzip: 2.14 kB[22m
|
|
15
|
+
[34mℹ[39m [2mdist/[22mdb-test.d.ts.map [2m 3.19 kB[22m [2m│ gzip: 1.47 kB[22m
|
|
16
|
+
[34mℹ[39m [2mdist/[22mindex.js.map [2m 2.44 kB[22m [2m│ gzip: 0.96 kB[22m
|
|
17
|
+
[34mℹ[39m [2mdist/[22mindex.d.ts.map [2m 0.55 kB[22m [2m│ gzip: 0.31 kB[22m
|
|
15
18
|
[34mℹ[39m [2mdist/[22madapters.d.ts.map [2m 0.42 kB[22m [2m│ gzip: 0.27 kB[22m
|
|
16
|
-
[34mℹ[39m [2mdist/[22m[32m[1mindex.d.ts[22m[39m [2m
|
|
19
|
+
[34mℹ[39m [2mdist/[22m[32m[1mindex.d.ts[22m[39m [2m 1.79 kB[22m [2m│ gzip: 0.68 kB[22m
|
|
20
|
+
[34mℹ[39m [2mdist/[22m[32mdb-test.d.ts[39m [2m 6.67 kB[22m [2m│ gzip: 1.80 kB[22m
|
|
17
21
|
[34mℹ[39m [2mdist/[22m[32madapters.d.ts[39m [2m 0.95 kB[22m [2m│ gzip: 0.37 kB[22m
|
|
18
|
-
[34mℹ[39m
|
|
19
|
-
[32m✔[39m Build complete in [
|
|
22
|
+
[34mℹ[39m 12 files, total: 74.22 kB
|
|
23
|
+
[32m✔[39m Build complete in [32m1914ms[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @fragno-dev/test
|
|
2
2
|
|
|
3
|
+
## 0.1.13
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- e848208: feat: restrict Unit of Work in service contexts
|
|
8
|
+
- 0f4c9fe: fix: move core/db dependencies to peerDependencies
|
|
9
|
+
- 7276378: feat: add providesPrivateService method to Fragment definition
|
|
10
|
+
|
|
11
|
+
This allows the Fragment author to define private services that are only accessible within the
|
|
12
|
+
Fragment's own code.
|
|
13
|
+
|
|
14
|
+
- 5ea24d2: refactor: improve Fragment builder and instatiator
|
|
15
|
+
- Updated dependencies [d6a7ff5]
|
|
16
|
+
- Updated dependencies [e848208]
|
|
17
|
+
- Updated dependencies [e9b2e7d]
|
|
18
|
+
- Updated dependencies [5e185bc]
|
|
19
|
+
- Updated dependencies [ec622bc]
|
|
20
|
+
- Updated dependencies [219ce35]
|
|
21
|
+
- Updated dependencies [b34917f]
|
|
22
|
+
- Updated dependencies [7276378]
|
|
23
|
+
- Updated dependencies [462004f]
|
|
24
|
+
- Updated dependencies [5ea24d2]
|
|
25
|
+
- Updated dependencies [f22c503]
|
|
26
|
+
- Updated dependencies [3474006]
|
|
27
|
+
- @fragno-dev/db@0.1.15
|
|
28
|
+
- @fragno-dev/core@0.1.9
|
|
29
|
+
|
|
3
30
|
## 0.1.12
|
|
4
31
|
|
|
5
32
|
### Patch Changes
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { AdapterContext, SupportedAdapter } from "./adapters.js";
|
|
2
|
+
import { BaseTestContext } from "./index.js";
|
|
3
|
+
import { AnySchema } from "@fragno-dev/db/schema";
|
|
4
|
+
import { AbstractQuery } from "@fragno-dev/db/query";
|
|
5
|
+
import { DatabaseAdapter } from "@fragno-dev/db";
|
|
6
|
+
import { FragmentInstantiationBuilder, FragnoInstantiatedFragment, FragnoPublicConfig, RequestThisContext } from "@fragno-dev/core";
|
|
7
|
+
import { AnyRouteOrFactory, FlattenRouteFactories } from "@fragno-dev/core/route";
|
|
8
|
+
|
|
9
|
+
//#region src/db-test.d.ts
|
|
10
|
+
type BoundServices<T> = { [K in keyof T]: T[K] extends ((this: any, ...args: infer A) => infer R) ? (...args: A) => R : T[K] extends Record<string, unknown> ? BoundServices<T[K]> : T[K] };
|
|
11
|
+
type ExtractSchemaFromDeps<TDeps> = TDeps extends {
|
|
12
|
+
schema: infer TSchema extends AnySchema;
|
|
13
|
+
} ? TSchema : AnySchema;
|
|
14
|
+
type AnyLinkedFragments = Record<string, any>;
|
|
15
|
+
interface FragmentResult<TDeps, TServices extends Record<string, unknown>, TServiceThisContext extends RequestThisContext, THandlerThisContext extends RequestThisContext, TRequestStorage, TRoutes extends readonly any[],
|
|
16
|
+
// eslint-disable-line @typescript-eslint/no-explicit-any
|
|
17
|
+
TSchema$1 extends AnySchema, TLinkedFragments extends AnyLinkedFragments = {}> {
|
|
18
|
+
fragment: FragnoInstantiatedFragment<TRoutes, TDeps, TServices, TServiceThisContext, THandlerThisContext, TRequestStorage, FragnoPublicConfig, TLinkedFragments>;
|
|
19
|
+
services: TServices;
|
|
20
|
+
deps: TDeps;
|
|
21
|
+
callRoute: FragnoInstantiatedFragment<TRoutes, TDeps, TServices, TServiceThisContext, THandlerThisContext, TRequestStorage, FragnoPublicConfig, TLinkedFragments>["callRoute"];
|
|
22
|
+
db: AbstractQuery<TSchema$1>;
|
|
23
|
+
}
|
|
24
|
+
type AnyFragmentResult = FragmentResult<any,
|
|
25
|
+
// eslint-disable-line @typescript-eslint/no-explicit-any
|
|
26
|
+
any,
|
|
27
|
+
// eslint-disable-line @typescript-eslint/no-explicit-any
|
|
28
|
+
any,
|
|
29
|
+
// eslint-disable-line @typescript-eslint/no-explicit-any
|
|
30
|
+
any,
|
|
31
|
+
// eslint-disable-line @typescript-eslint/no-explicit-any
|
|
32
|
+
any,
|
|
33
|
+
// eslint-disable-line @typescript-eslint/no-explicit-any
|
|
34
|
+
any,
|
|
35
|
+
// eslint-disable-line @typescript-eslint/no-explicit-any
|
|
36
|
+
any,
|
|
37
|
+
// eslint-disable-line @typescript-eslint/no-explicit-any
|
|
38
|
+
any>;
|
|
39
|
+
/**
|
|
40
|
+
* Test context combining base and adapter-specific functionality
|
|
41
|
+
*/
|
|
42
|
+
type TestContext<T extends SupportedAdapter, TFirstFragmentThisContext extends RequestThisContext = RequestThisContext> = BaseTestContext & AdapterContext<T> & {
|
|
43
|
+
adapter: DatabaseAdapter<any>;
|
|
44
|
+
/**
|
|
45
|
+
* Execute a callback within the first fragment's request context.
|
|
46
|
+
* This is useful for calling services outside of route handlers in tests.
|
|
47
|
+
*/
|
|
48
|
+
inContext<TResult>(callback: (this: TFirstFragmentThisContext) => TResult): TResult;
|
|
49
|
+
inContext<TResult>(callback: (this: TFirstFragmentThisContext) => Promise<TResult>): Promise<TResult>;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Result of building the database fragments test
|
|
53
|
+
*/
|
|
54
|
+
interface DatabaseFragmentsTestResult<TFragments extends Record<string, AnyFragmentResult>, TAdapter extends SupportedAdapter, TFirstFragmentThisContext extends RequestThisContext = RequestThisContext> {
|
|
55
|
+
fragments: TFragments;
|
|
56
|
+
test: TestContext<TAdapter, TFirstFragmentThisContext>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Builder for creating multiple database fragments for testing
|
|
60
|
+
*/
|
|
61
|
+
declare class DatabaseFragmentsTestBuilder<TFragments extends Record<string, AnyFragmentResult>, TAdapter extends SupportedAdapter | undefined = undefined, TFirstFragmentThisContext extends RequestThisContext = RequestThisContext> {
|
|
62
|
+
#private;
|
|
63
|
+
/**
|
|
64
|
+
* Set the test adapter configuration
|
|
65
|
+
*/
|
|
66
|
+
withTestAdapter<TNewAdapter extends SupportedAdapter>(adapter: TNewAdapter): DatabaseFragmentsTestBuilder<TFragments, TNewAdapter, TFirstFragmentThisContext>;
|
|
67
|
+
/**
|
|
68
|
+
* Add a fragment to the test setup
|
|
69
|
+
*
|
|
70
|
+
* @param name - Unique name for the fragment
|
|
71
|
+
* @param builder - Pre-configured instantiation builder
|
|
72
|
+
* @param options - Additional options (optional)
|
|
73
|
+
*/
|
|
74
|
+
withFragment<TName extends string, TConfig, TOptions extends FragnoPublicConfig, TDeps, TBaseServices extends Record<string, unknown>, TServices extends Record<string, unknown>, TServiceDependencies, TPrivateServices extends Record<string, unknown>, TServiceThisContext extends RequestThisContext, THandlerThisContext extends RequestThisContext, TRequestStorage, TRoutesOrFactories extends readonly AnyRouteOrFactory[], TLinkedFragments extends AnyLinkedFragments>(name: TName, builder: FragmentInstantiationBuilder<TConfig, TOptions, TDeps, TBaseServices, TServices, TServiceDependencies, TPrivateServices, TServiceThisContext, THandlerThisContext, TRequestStorage, TRoutesOrFactories, TLinkedFragments>, options?: {
|
|
75
|
+
migrateToVersion?: number;
|
|
76
|
+
}): DatabaseFragmentsTestBuilder<TFragments & { [K in TName]: FragmentResult<TDeps, BoundServices<TBaseServices & TServices>, TServiceThisContext, THandlerThisContext, TRequestStorage, FlattenRouteFactories<TRoutesOrFactories>, ExtractSchemaFromDeps<TDeps>,
|
|
77
|
+
// Extract actual schema type from deps
|
|
78
|
+
TLinkedFragments> }, TAdapter, keyof TFragments extends never ? THandlerThisContext : TFirstFragmentThisContext>;
|
|
79
|
+
/**
|
|
80
|
+
* Build the test setup and return fragments and test context
|
|
81
|
+
*/
|
|
82
|
+
build(): Promise<TAdapter extends SupportedAdapter ? DatabaseFragmentsTestResult<TFragments, TAdapter, TFirstFragmentThisContext> : never>;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Create a builder for setting up multiple database fragments for testing.
|
|
86
|
+
* This is the new builder-based API that works with the new fragment instantiation builders.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* const userFragmentDef = defineFragment("user")
|
|
91
|
+
* .extend(withDatabase(userSchema))
|
|
92
|
+
* .withDependencies(...)
|
|
93
|
+
* .build();
|
|
94
|
+
*
|
|
95
|
+
* const postFragmentDef = defineFragment("post")
|
|
96
|
+
* .extend(withDatabase(postSchema))
|
|
97
|
+
* .withDependencies(...)
|
|
98
|
+
* .build();
|
|
99
|
+
*
|
|
100
|
+
* const { fragments, test } = await buildDatabaseFragmentsTest()
|
|
101
|
+
* .withTestAdapter({ type: "kysely-sqlite" })
|
|
102
|
+
* .withFragment("user",
|
|
103
|
+
* instantiate(userFragmentDef)
|
|
104
|
+
* .withConfig({ ... })
|
|
105
|
+
* .withRoutes([...])
|
|
106
|
+
* )
|
|
107
|
+
* .withFragment("post",
|
|
108
|
+
* instantiate(postFragmentDef)
|
|
109
|
+
* .withRoutes([...])
|
|
110
|
+
* )
|
|
111
|
+
* .build();
|
|
112
|
+
*
|
|
113
|
+
* // Access fragments by name
|
|
114
|
+
* await fragments.user.services.createUser(...);
|
|
115
|
+
* await fragments.post.services.createPost(...);
|
|
116
|
+
*
|
|
117
|
+
* // Access dependencies directly
|
|
118
|
+
* const userDeps = fragments.user.deps;
|
|
119
|
+
*
|
|
120
|
+
* // Shared test context
|
|
121
|
+
* await test.resetDatabase();
|
|
122
|
+
* await test.cleanup();
|
|
123
|
+
* const adapter = test.adapter; // Access the database adapter
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
declare function buildDatabaseFragmentsTest(): DatabaseFragmentsTestBuilder<{}, undefined, RequestThisContext>;
|
|
127
|
+
//#endregion
|
|
128
|
+
export { DatabaseFragmentsTestBuilder, buildDatabaseFragmentsTest };
|
|
129
|
+
//# sourceMappingURL=db-test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-test.d.ts","names":[],"sources":["../src/db-test.ts"],"sourcesContent":[],"mappings":";;;;;;;;;KAqBK,iCACS,IAAI,EAAE,kEACJ,MAAM,IAChB,EAAE,WAAW,0BACX,cAAc,EAAE,MAChB,EAAE,IAT+B;KAcpC,qBATS,CAAA,KAAA,CAAA,GASsB,KATtB,SAAA;EAAI,MAAA,EAAA,KAAA,iBASgE,SAThE;CAAE,GAUhB,OAVgB,GAWhB,SAXgB;KAcf,kBAAA,GAAqB,MAbV,CAAA,MAAA,EAAA,GAAA,CAAA;UAgBN,cAhBY,CAAA,KAAA,EAAA,kBAkBF,MAlBE,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,4BAmBQ,kBAnBR,EAAA,4BAoBQ,kBApBR,EAAA,eAAA,EAAA,gBAAA,SAAA,GAAA,EAAA;AAAA;kBAuBJ,SAtBZ,EAAA,yBAuBqB,kBAvBrB,GAAA,CAAA,CAAA,CAAA,CAAA;EAAE,QAAA,EAyBI,0BAzBJ,CA0BJ,OA1BI,EA2BJ,KA3BI,EA4BJ,SA5BI,EA6BJ,mBA7BI,EA8BJ,mBA9BI,EA+BJ,eA/BI,EAgCJ,kBAhCI,EAiCJ,gBAjCI,CAAA;EAAW,QAAA,EAmCP,SAnCO;EACG,IAAA,EAmCd,KAnCc;EAAE,SAAA,EAoCX,0BApCW,CAqCpB,OArCoB,EAsCpB,KAtCoB,EAuCpB,SAvCoB,EAwCpB,mBAxCoB,EAyCpB,mBAzCoB,EA0CpB,eA1CoB,EA2CpB,kBA3CoB,EA4CpB,gBA5CoB,CAAA,CAAA,WAAA,CAAA;EAAhB,EAAA,EA8CF,aA9CE,CA8CY,SA9CZ,CAAA;;KAkDH,iBAAA,GAAoB,cAjDf,CAAA,GAAA;AAAA;GAAC;AAAA;AAAA,GAAA;AAKN;GAA+B;AAAA;GAA8C;AAAA;GAC9E;AAAA;GACA;AAAA;GAAS,CAAA;AAAA;AAGmB;;KAoH3B,WA9GyB,CAAA,UA+GlB,gBA/GkB,EAAA,kCAgHM,kBAhHN,GAgH2B,kBAhH3B,CAAA,GAiH1B,eAjH0B,GAkH5B,cAlH4B,CAkHb,CAlHa,CAAA,GAAA;EACA,OAAA,EAmHjB,eAnHiB,CAAA,GAAA,CAAA;EAGZ;;;;EAMd,SAAA,CAAA,OAAA,CAAA,CAAA,QAAA,EAAA,CAAA,IAAA,EA+GoC,yBA/GpC,EAAA,GA+GkE,OA/GlE,CAAA,EA+G4E,OA/G5E;EACA,SAAA,CAAA,OAAA,CAAA,CAAA,QAAA,EAAA,CAAA,IAAA,EAgHmB,yBAhHnB,EAAA,GAgHiD,OAhHjD,CAgHyD,OAhHzD,CAAA,CAAA,EAiHG,OAjHH,CAiHW,OAjHX,CAAA;CACA;;;;UAsHM,2BA3HE,CAAA,mBA4HS,MA5HT,CAAA,MAAA,EA4HwB,iBA5HxB,CAAA,EAAA,iBA6HO,gBA7HP,EAAA,kCA8HwB,kBA9HxB,GA8H6C,kBA9H7C,CAAA,CAAA;EAUA,SAAA,EAsHC,UAtHD;EACJ,IAAA,EAsHA,WAtHA,CAsHY,QAtHZ,EAsHsB,yBAtHtB,CAAA;;;;;AAMJ,cA2HS,4BA3HT,CAAA,mBA4HiB,MA5HjB,CAAA,MAAA,EA4HgC,iBA5HhC,CAAA,EAAA,iBA6He,gBA7Hf,GAAA,SAAA,GAAA,SAAA,EAAA,kCA8HgC,kBA9HhC,GA8HqD,kBA9HrD,CAAA,CAAA;EACA,CAAA,OAAA;EACA;;;EAGgB,eAAA,CAAA,oBAiIkB,gBAjIlB,CAAA,CAAA,OAAA,EAkIP,WAlIO,CAAA,EAmIf,4BAnIe,CAmIc,UAnId,EAmI0B,WAnI1B,EAmIuC,yBAnIvC,CAAA;EAAd;;AAAa;AAIoB;;;;EAgFnC,YAAA,CAAA,cAAA,MAAA,EAAA,OAAA,EAAA,iBA8DiB,kBA9DjB,EAAA,KAAA,EAAA,sBAgEsB,MAhEtB,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,kBAiEkB,MAjElB,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,oBAAA,EAAA,yBAmEyB,MAnEzB,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,4BAoE4B,kBApE5B,EAAA,4BAqE4B,kBArE5B,EAAA,eAAA,EAAA,2BAAA,SAuEoC,iBAvEpC,EAAA,EAAA,yBAwEyB,kBAxEzB,CAAA,CAAA,IAAA,EA0EM,KA1EN,EAAA,OAAA,EA2ES,4BA3ET,CA4EE,OA5EF,EA6EE,QA7EF,EA8EE,KA9EF,EA+EE,aA/EF,EAgFE,SAhFF,EAiFE,oBAjFF,EAkFE,gBAlFF,EAmFE,mBAnFF,EAoFE,mBApFF,EAqFE,eArFF,EAsFE,kBAtFF,EAuFE,gBAvFF,CAAA,EAAA,OACF,CADE,EAAA;IACa,gBAAA,CAAA,EAAA,MAAA;EAAf,CAAA,CAAA,EA2FG,4BA3FH,CA4FE,UA5FF,GAAA,QA6FU,KA3FC,GA2FO,cA3FP,CA4FL,KA5FK,EA6FL,aA7FK,CA6FS,aA7FT,GA6FyB,SA7FzB,CAAA,EA8FL,mBA9FK,EA+FL,mBA/FK,EAgGL,eAhGK,EAiGL,qBAjGK,CAiGiB,kBAjGjB,CAAA,EAkGL,qBAlGK,CAkGiB,KAlGjB,CAAA;EAAA;EAmGL,gBA9FgC,CAAA,EAA8B,EAiGlE,QAjGkE,EAAA,MAmG5D,UAnG4D,SAAA,KAAA,GAmGjC,mBAnGiC,GAmGX,yBAnGW,CAAA;EAAU;;;EAE3B,KAAA,CAAA,CAAA,EA8GpC,OA9GoC,CA+GjD,QA/GiD,SA+GhC,gBA/GgC,GAgH7C,2BAhH6C,CAgHjB,UAhHiB,EAgHL,QAhHK,EAgHK,yBAhHL,CAAA,GAAA,KAAA,CAAA;;;;AACvC;;;;;;;;;;;AAuBd;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDM,iBAwUU,0BAAA,CAAA,CAxUV,EAwUwC,4BAxUxC,CAAA,CAAA,CAAA,EAAA,SAAA,EA2UJ,kBA3UI,CAAA"}
|
package/dist/db-test.js
ADDED
|
@@ -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,11 +1,9 @@
|
|
|
1
1
|
import { AdapterContext, DrizzlePgliteAdapter, KyselyPgliteAdapter, KyselySqliteAdapter, SupportedAdapter } from "./adapters.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { DatabaseFragmentsTestBuilder, buildDatabaseFragmentsTest } from "./db-test.js";
|
|
3
|
+
import { CreateFragmentForTestOptions, RouteHandlerInputOptions, createFragmentForTest } from "@fragno-dev/core/test";
|
|
4
4
|
import { AnySchema } from "@fragno-dev/db/schema";
|
|
5
|
-
import { FragnoPublicConfig } from "@fragno-dev/core/api/fragment-instantiation";
|
|
6
|
-
import { FragmentDefinition } from "@fragno-dev/core/api/fragment-builder";
|
|
7
|
-
import { AnyRouteOrFactory, FlattenRouteFactories } from "@fragno-dev/core/api/route";
|
|
8
5
|
import { AbstractQuery } from "@fragno-dev/db/query";
|
|
6
|
+
import { DatabaseAdapter } from "@fragno-dev/db";
|
|
9
7
|
|
|
10
8
|
//#region src/index.d.ts
|
|
11
9
|
|
|
@@ -14,9 +12,6 @@ import { AbstractQuery } from "@fragno-dev/db/query";
|
|
|
14
12
|
*/
|
|
15
13
|
interface BaseTestContext {
|
|
16
14
|
readonly adapter: DatabaseAdapter<any>;
|
|
17
|
-
createUnitOfWork: (name?: string) => IUnitOfWorkBase;
|
|
18
|
-
withUnitOfWork: <T>(fn: (uow: IUnitOfWorkBase) => Promise<T>) => Promise<T>;
|
|
19
|
-
callService: <T>(fn: () => T | Promise<T>) => Promise<T>;
|
|
20
15
|
resetDatabase: () => Promise<void>;
|
|
21
16
|
cleanup: () => Promise<void>;
|
|
22
17
|
}
|
|
@@ -24,10 +19,7 @@ interface BaseTestContext {
|
|
|
24
19
|
* Internal interface with getOrm for adapter implementations
|
|
25
20
|
*/
|
|
26
21
|
interface InternalTestContextMethods {
|
|
27
|
-
getOrm: <TSchema
|
|
28
|
-
createUnitOfWork: (name?: string) => IUnitOfWorkBase;
|
|
29
|
-
withUnitOfWork: <T>(fn: (uow: IUnitOfWorkBase) => Promise<T>) => Promise<T>;
|
|
30
|
-
callService: <T>(fn: () => T | Promise<T>) => Promise<T>;
|
|
22
|
+
getOrm: <TSchema extends AnySchema>(namespace: string) => AbstractQuery<TSchema>;
|
|
31
23
|
}
|
|
32
24
|
/**
|
|
33
25
|
* Helper to create common test context methods from an ORM map
|
|
@@ -38,75 +30,6 @@ declare function createCommonTestContextMethods(ormMap: Map<string, AbstractQuer
|
|
|
38
30
|
* Complete test context combining base and adapter-specific functionality
|
|
39
31
|
*/
|
|
40
32
|
type TestContext<T extends SupportedAdapter> = BaseTestContext & AdapterContext<T>;
|
|
41
|
-
/**
|
|
42
|
-
* Helper type to extract the schema from a fragment definition's additional context
|
|
43
|
-
*/
|
|
44
|
-
type ExtractSchemaFromAdditionalContext<TAdditionalContext> = TAdditionalContext extends {
|
|
45
|
-
databaseSchema?: infer TSchema extends AnySchema;
|
|
46
|
-
} ? TSchema : AnySchema;
|
|
47
|
-
/**
|
|
48
|
-
* Fragment configuration for multi-fragment setup
|
|
49
|
-
*/
|
|
50
|
-
interface FragmentConfig<TDef extends {
|
|
51
|
-
definition: FragmentDefinition<any, any, any, any, any, any>;
|
|
52
|
-
$requiredOptions: any;
|
|
53
|
-
} = {
|
|
54
|
-
definition: FragmentDefinition<any, any, any, any, any, any>;
|
|
55
|
-
$requiredOptions: any;
|
|
56
|
-
}, TRoutes extends readonly AnyRouteOrFactory[] = readonly AnyRouteOrFactory[]> {
|
|
57
|
-
definition: TDef;
|
|
58
|
-
routes: TRoutes;
|
|
59
|
-
config?: TDef["definition"] extends FragmentDefinition<infer TConfig, any, any, any, any, any> ? TConfig : never;
|
|
60
|
-
migrateToVersion?: number;
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* Options for creating multiple database fragments for testing
|
|
64
|
-
*/
|
|
65
|
-
interface MultiFragmentTestOptions<TAdapter extends SupportedAdapter> {
|
|
66
|
-
adapter: TAdapter;
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* Result type for a single fragment in a multi-fragment setup
|
|
70
|
-
*/
|
|
71
|
-
type FragmentResultFromConfig<TConfig$1 extends FragmentConfig> = TConfig$1["definition"] extends {
|
|
72
|
-
definition: FragmentDefinition<infer TConf, infer TDeps, infer TServices, infer TAdditionalCtx, any, infer TProvidedServices>;
|
|
73
|
-
$requiredOptions: infer TOptions extends FragnoPublicConfig;
|
|
74
|
-
} ? {
|
|
75
|
-
fragment: FragmentForTest$1<TConf, TDeps, TServices & TProvidedServices, TAdditionalCtx, TOptions, FlattenRouteFactories<TConfig$1["routes"]>>;
|
|
76
|
-
services: TServices & TProvidedServices;
|
|
77
|
-
callRoute: FragmentForTest$1<TConf, TDeps, TServices & TProvidedServices, TAdditionalCtx, TOptions, FlattenRouteFactories<TConfig$1["routes"]>>["callRoute"];
|
|
78
|
-
config: TConf;
|
|
79
|
-
deps: TDeps;
|
|
80
|
-
additionalContext: TAdditionalCtx;
|
|
81
|
-
db: AbstractQuery<ExtractSchemaFromAdditionalContext<TAdditionalCtx>>;
|
|
82
|
-
} : never;
|
|
83
|
-
interface SingleFragmentTestResult<TFragment extends FragmentConfig, TAdapter extends SupportedAdapter> {
|
|
84
|
-
fragment: FragmentResultFromConfig<TFragment>;
|
|
85
|
-
test: TestContext<TAdapter>;
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* Result of creating multiple database fragments for testing (array input)
|
|
89
|
-
*/
|
|
90
|
-
interface MultiFragmentTestResult<TFragments extends readonly FragmentConfig[], TAdapter extends SupportedAdapter> {
|
|
91
|
-
fragments: { [K in keyof TFragments]: FragmentResultFromConfig<TFragments[K]> };
|
|
92
|
-
test: TestContext<TAdapter>;
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Result of creating multiple database fragments for testing (object input)
|
|
96
|
-
*/
|
|
97
|
-
interface NamedMultiFragmentTestResult<TFragments extends Record<string, FragmentConfig>, TAdapter extends SupportedAdapter> {
|
|
98
|
-
fragments: { [K in keyof TFragments]: FragmentResultFromConfig<TFragments[K]> };
|
|
99
|
-
test: TestContext<TAdapter>;
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Create multiple database fragments for testing with a shared adapter (array input)
|
|
103
|
-
*/
|
|
104
|
-
declare function createDatabaseFragmentsForTest<const TFragments extends readonly FragmentConfig[], const TAdapter extends SupportedAdapter>(fragments: TFragments, options: MultiFragmentTestOptions<TAdapter>): Promise<MultiFragmentTestResult<TFragments, TAdapter>>;
|
|
105
|
-
/**
|
|
106
|
-
* Create multiple database fragments for testing with a shared adapter (object input)
|
|
107
|
-
*/
|
|
108
|
-
declare function createDatabaseFragmentsForTest<const TFragments extends Record<string, FragmentConfig>, const TAdapter extends SupportedAdapter>(fragments: TFragments, options: MultiFragmentTestOptions<TAdapter>): Promise<NamedMultiFragmentTestResult<TFragments, TAdapter>>;
|
|
109
|
-
declare function createDatabaseFragmentForTest<const TFragment extends FragmentConfig, const TAdapter extends SupportedAdapter>(fragment: TFragment, options: MultiFragmentTestOptions<TAdapter>): Promise<SingleFragmentTestResult<TFragment, TAdapter>>;
|
|
110
33
|
//#endregion
|
|
111
|
-
export { type AdapterContext, BaseTestContext, type CreateFragmentForTestOptions,
|
|
34
|
+
export { type AdapterContext, BaseTestContext, type CreateFragmentForTestOptions, DatabaseFragmentsTestBuilder, type DrizzlePgliteAdapter, InternalTestContextMethods, type KyselyPgliteAdapter, type KyselySqliteAdapter, type RouteHandlerInputOptions, type SupportedAdapter, TestContext, buildDatabaseFragmentsTest, createCommonTestContextMethods, createFragmentForTest };
|
|
112
35
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":"
|
|
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"}
|