@@ -12,12 +12,12 @@ import type {
1212 GetTestPostgresDatabase ,
1313 GetTestPostgresDatabaseFactoryOptions ,
1414 GetTestPostgresDatabaseOptions ,
15+ GetTestPostgresDatabaseResult ,
1516} from "./public-types"
1617import { Pool } from "pg"
1718import type { Jsonifiable } from "type-fest"
1819import type { ExecutionContext } from "ava"
19- import { once } from "node:events"
20- import { createBirpc } from "birpc"
20+ import { BirpcReturn , createBirpc } from "birpc"
2121import { ExecResult } from "testcontainers"
2222import isPlainObject from "lodash/isPlainObject"
2323
@@ -105,7 +105,7 @@ export const getTestPostgresDatabaseFactory = <
105105 Params extends Jsonifiable = never
106106> (
107107 options ?: GetTestPostgresDatabaseFactoryOptions < Params >
108- ) => {
108+ ) : GetTestPostgresDatabase < Params > => {
109109 const initialData : InitialWorkerData = {
110110 postgresVersion : options ?. postgresVersion ?? "14" ,
111111 containerOptions : options ?. container ,
@@ -136,57 +136,73 @@ export const getTestPostgresDatabaseFactory = <
136136 }
137137
138138 let rpcCallback : ( data : any ) => void
139- const rpc = createBirpc < SharedWorkerFunctions , TestWorkerFunctions > (
140- {
141- runBeforeTemplateIsBakedHook : async ( connection , params ) => {
142- if ( options ?. beforeTemplateIsBaked ) {
143- const connectionDetails =
144- mapWorkerConnectionDetailsToConnectionDetails ( connection )
145-
146- // Ignore if the pool is terminated by the shared worker
147- // (This happens in CI for some reason even though we drain the pool first.)
148- connectionDetails . pool . on ( "error" , ( error ) => {
149- if (
150- error . message . includes (
151- "terminating connection due to administrator command"
152- )
153- ) {
154- return
155- }
139+ const rpc : BirpcReturn < SharedWorkerFunctions , TestWorkerFunctions > =
140+ createBirpc < SharedWorkerFunctions , TestWorkerFunctions > (
141+ {
142+ runBeforeTemplateIsBakedHook : async ( connection , params ) => {
143+ if ( options ?. beforeTemplateIsBaked ) {
144+ const connectionDetails =
145+ mapWorkerConnectionDetailsToConnectionDetails ( connection )
156146
157- throw error
158- } )
147+ // Ignore if the pool is terminated by the shared worker
148+ // (This happens in CI for some reason even though we drain the pool first.)
149+ connectionDetails . pool . on ( "error" , ( error ) => {
150+ if (
151+ error . message . includes (
152+ "terminating connection due to administrator command"
153+ )
154+ ) {
155+ return
156+ }
159157
160- const hookResult = await options . beforeTemplateIsBaked ( {
161- params : params as any ,
162- connection : connectionDetails ,
163- containerExec : async ( command ) : Promise < ExecResult > =>
164- rpc . execCommandInContainer ( command ) ,
165- } )
158+ throw error
159+ } )
166160
167- await teardownConnection ( connectionDetails )
161+ const hookResult = await options . beforeTemplateIsBaked ( {
162+ params : params as any ,
163+ connection : connectionDetails ,
164+ containerExec : async ( command ) : Promise < ExecResult > =>
165+ rpc . execCommandInContainer ( command ) ,
166+ // This is what allows a consumer to get a "nested" database from within their beforeTemplateIsBaked hook
167+ manuallyBuildAdditionalTemplate : async ( ) => {
168+ const connection =
169+ mapWorkerConnectionDetailsToConnectionDetails (
170+ await rpc . createEmptyDatabase ( )
171+ )
168172
169- if ( hookResult && ! isSerializable ( hookResult ) ) {
170- throw new TypeError (
171- "Return value of beforeTemplateIsBaked() hook could not be serialized. Make sure it returns only JSON-serializable values."
172- )
173- }
173+ return {
174+ connection,
175+ finish : async ( ) => {
176+ await teardownConnection ( connection )
177+ return rpc . convertDatabaseToTemplate ( connection . database )
178+ } ,
179+ }
180+ } ,
181+ } )
174182
175- return hookResult
176- }
177- } ,
178- } ,
179- {
180- post : async ( data ) => {
181- const worker = await workerPromise
182- await worker . available
183- worker . publish ( data )
184- } ,
185- on : ( data ) => {
186- rpcCallback = data
183+ await teardownConnection ( connectionDetails )
184+
185+ if ( hookResult && ! isSerializable ( hookResult ) ) {
186+ throw new TypeError (
187+ "Return value of beforeTemplateIsBaked() hook could not be serialized. Make sure it returns only JSON-serializable values."
188+ )
189+ }
190+
191+ return hookResult
192+ }
193+ } ,
187194 } ,
188- }
189- )
195+ {
196+ post : async ( data ) => {
197+ const worker = await workerPromise
198+ await worker . available
199+ worker . publish ( data )
200+ } ,
201+ on : ( data ) => {
202+ rpcCallback = data
203+ } ,
204+ }
205+ )
190206
191207 // Automatically cleaned up by AVA since each test file runs in a separate worker
192208 const _messageHandlerPromise = ( async ( ) => {
@@ -198,11 +214,11 @@ export const getTestPostgresDatabaseFactory = <
198214 }
199215 } ) ( )
200216
201- const getTestPostgresDatabase : GetTestPostgresDatabase < Params > = async (
217+ const getTestPostgresDatabase = async (
202218 t : ExecutionContext ,
203219 params : any ,
204220 getTestDatabaseOptions ?: GetTestPostgresDatabaseOptions
205- ) => {
221+ ) : Promise < GetTestPostgresDatabaseResult > => {
206222 const testDatabaseConnection = await rpc . getTestDatabase ( {
207223 databaseDedupeKey : getTestDatabaseOptions ?. databaseDedupeKey ,
208224 params,
@@ -223,7 +239,22 @@ export const getTestPostgresDatabaseFactory = <
223239 }
224240 }
225241
226- return getTestPostgresDatabase
242+ getTestPostgresDatabase . fromTemplate = async (
243+ t : ExecutionContext ,
244+ templateName : string
245+ ) => {
246+ const connection = mapWorkerConnectionDetailsToConnectionDetails (
247+ await rpc . createDatabaseFromTemplate ( templateName )
248+ )
249+
250+ t . teardown ( async ( ) => {
251+ await teardownConnection ( connection )
252+ } )
253+
254+ return connection
255+ }
256+
257+ return getTestPostgresDatabase as any
227258}
228259
229260export * from "./public-types"
0 commit comments