Repository: Quramy/prisma-fabbrica (ts)

Issue:
Multiple databases support
Thanks for building such a great library!

We have multiple databases on the same application and would like to initialize prisma-fabbrica multiple times with different PrismaClient to connect them.

But since fabbrica currently stores PrismaClient instance [as a singleton](https://github.com/Quramy/prisma-fabbrica/blob/f9da749a1ba35153ac3644dc73c12cd78880fc1f/packages/prisma-fabbrica/src/clientHolder.ts), every initialization overrides previous one.

Any chance to support multiple databases?

Pull request description:
Isolate PrismaClient instance for each fabbrica file
It closes #246 

Patch:
diff --git a/examples/example-prj/src/__generated__/fabbrica/index.d.ts b/examples/example-prj/src/__generated__/fabbrica/index.d.ts
index fdb8526..7d39807 100644
--- a/examples/example-prj/src/__generated__/fabbrica/index.d.ts
+++ b/examples/example-prj/src/__generated__/fabbrica/index.d.ts
@@ -4,10 +4,11 @@ import type { Comment } from "@prisma/client";
 import type { Category } from "@prisma/client";
 import { Prisma } from "@prisma/client";
 import { Resolver } from "@quramy/prisma-fabbrica/lib/internal";
-export { initialize, resetSequence, registerScalarFieldValueGenerator, resetScalarFieldValueGenerator } from "@quramy/prisma-fabbrica/lib/internal";
+export { resetSequence, registerScalarFieldValueGenerator, resetScalarFieldValueGenerator } from "@quramy/prisma-fabbrica/lib/internal";
 type BuildDataOptions = {
     readonly seq: number;
 };
+export declare const initialize: (options: import("@quramy/prisma-fabbrica/lib/initialize").InitializeOptions) => void;
 declare const factoryFor: unique symbol;
 type UserFactoryDefineInput = {
     id?: string;
diff --git a/examples/example-prj/src/__generated__/fabbrica/index.js b/examples/example-prj/src/__generated__/fabbrica/index.js
index 8042c9f..85551f6 100644
--- a/examples/example-prj/src/__generated__/fabbrica/index.js
+++ b/examples/example-prj/src/__generated__/fabbrica/index.js
@@ -1,12 +1,14 @@
 "use strict";
 Object.defineProperty(exports, "__esModule", { value: true });
-exports.defineCategoryFactory = exports.defineCommentFactory = exports.definePostFactory = exports.defineUserFactory = exports.resetScalarFieldValueGenerator = exports.registerScalarFieldValueGenerator = exports.resetSequence = exports.initialize = void 0;
+exports.defineCategoryFactory = exports.defineCommentFactory = exports.definePostFactory = exports.defineUserFactory = exports.initialize = exports.resetScalarFieldValueGenerator = exports.registerScalarFieldValueGenerator = exports.resetSequence = void 0;
 const internal_1 = require("@quramy/prisma-fabbrica/lib/internal");
 var internal_2 = require("@quramy/prisma-fabbrica/lib/internal");
-Object.defineProperty(exports, "initialize", { enumerable: true, get: function () { return internal_2.initialize; } });
 Object.defineProperty(exports, "resetSequence", { enumerable: true, get: function () { return internal_2.resetSequence; } });
 Object.defineProperty(exports, "registerScalarFieldValueGenerator", { enumerable: true, get: function () { return internal_2.registerScalarFieldValueGenerator; } });
 Object.defineProperty(exports, "resetScalarFieldValueGenerator", { enumerable: true, get: function () { return internal_2.resetScalarFieldValueGenerator; } });
+const initializer = (0, internal_1.createInitializer)();
+const { getClient } = initializer;
+exports.initialize = initializer.initialize;
 const factoryFor = Symbol("factoryFor");
 const modelFieldDefinitions = [{
         name: "User",
@@ -88,7 +90,7 @@ function defineUserFactoryInternal({ defaultData: defaultDataResolver, traits: t
         });
         const create = async (inputData = {}) => {
             const data = await build(inputData).then(screen);
-            return await (0, internal_1.getClient)().user.create({ data });
+            return await getClient().user.create({ data });
         };
         const createList = (inputData) => Promise.all((0, internal_1.normalizeList)(inputData).map(data => create(data)));
         const createForConnect = (inputData = {}) => create(inputData).then(pickForConnect);
@@ -163,7 +165,7 @@ function definePostFactoryInternal({ defaultData: defaultDataResolver, traits: t
         });
         const create = async (inputData = {}) => {
             const data = await build(inputData).then(screen);
-            return await (0, internal_1.getClient)().post.create({ data });
+            return await getClient().post.create({ data });
         };
         const createList = (inputData) => Promise.all((0, internal_1.normalizeList)(inputData).map(data => create(data)));
         const createForConnect = (inputData = {}) => create(inputData).then(pickForConnect);
@@ -244,7 +246,7 @@ function defineCommentFactoryInternal({ defaultData: defaultDataResolver, traits
         });
         const create = async (inputData = {}) => {
             const data = await build(inputData).then(screen);
-            return await (0, internal_1.getClient)().comment.create({ data });
+            return await getClient().comment.create({ data });
         };
         const createList = (inputData) => Promise.all((0, internal_1.normalizeList)(inputData).map(data => create(data)));
         const createForConnect = (inputData = {}) => create(inputData).then(pickForConnect);
@@ -312,7 +314,7 @@ function defineCategoryFactoryInternal({ defaultData: defaultDataResolver, trait
         });
         const create = async (inputData = {}) => {
             const data = await build(inputData).then(screen);
-            return await (0, internal_1.getClient)().category.create({ data });
+            return await getClient().category.create({ data });
         };
         const createList = (inputData) => Promise.all((0, internal_1.normalizeList)(inputData).map(data => create(data)));
         const createForConnect = (inputData = {}) => create(inputData).then(pickForConnect);
diff --git a/packages/prisma-fabbrica/src/clientHolder.ts b/packages/prisma-fabbrica/src/clientHolder.ts
index 521d1f3..cdb9e07 100644
--- a/packages/prisma-fabbrica/src/clientHolder.ts
+++ b/packages/prisma-fabbrica/src/clientHolder.ts
@@ -3,18 +3,20 @@ export type PrismaClientLike = {
   $disconnect: () => PromiseLike<unknown>;
 };
 
-let _client: undefined | (() => PrismaClientLike);
+const DEFAULT_CLIENT_MAP_KEY = Symbol("scope:singleton");
+const clientMap: Map<unknown, () => PrismaClientLike> = new Map();
 
-export function resetClient() {
-  _client = undefined;
+export function resetClient(key: unknown = DEFAULT_CLIENT_MAP_KEY) {
+  clientMap.delete(key);
 }
 
-export function setClient<T extends PrismaClientLike>(client: T | (() => T)) {
-  _client = typeof client === "function" ? client : () => client;
+export function setClient<T extends PrismaClientLike>(client: T | (() => T), key: unknown = DEFAULT_CLIENT_MAP_KEY) {
+  const value = typeof client === "function" ? client : () => client;
+  clientMap.set(key, value);
 }
 
-export function getClient<T extends PrismaClientLike>() {
-  const client = _client?.();
+export function getClient<T extends PrismaClientLike>(key?: unknown) {
+  const client = (clientMap.get(key) ?? clientMap.get(DEFAULT_CLIENT_MAP_KEY))?.();
   if (!client) {
     throw new Error("No prisma client");
   }
diff --git a/packages/prisma-fabbrica/src/initialize.ts b/packages/prisma-fabbrica/src/initialize.ts
index c48a7fc..1bceb30 100644
--- a/packages/prisma-fabbrica/src/initialize.ts
+++ b/packages/prisma-fabbrica/src/initialize.ts
@@ -1,4 +1,4 @@
-import { resetClient, setClient, PrismaClientLike } from "./clientHolder";
+import { resetClient, getClient as getClientDelegate, setClient, PrismaClientLike } from "./clientHolder";
 import { resetSequence } from "./helpers";
 import { resetScalarFieldValueGenerator } from "./internal";
 export { resetSequence } from "./helpers";
@@ -8,13 +8,59 @@ export type InitializeOptions = {
   readonly prisma: PrismaClientLike | (() => PrismaClientLike);
 };
 
-export function reset() {
-  resetClient();
-  resetSequence();
-  resetScalarFieldValueGenerator();
+/**
+ *
+ * Create functions to initialize PrismaClient instance.
+ *
+ * This function creates "separated" initializer.
+ *
+ * ```ts
+ * // fabbrica_a.ts
+ * import { createInitializer } from "prisma-fabbrica/lib/internal";
+ *
+ * const initializer = createInitializer();
+ * const { getClient } = initializer;
+ * export const { initialize } = initializer;
+ * ```
+ *
+ * The above `initialize` function does not affect other fabbrica files.
+ *
+ */
+export function createInitializer() {
+  const initializerKey = Symbol("scope:createInitializer");
+
+  const initialize = (options: InitializeOptions) => {
+    setClient(options.prisma, initializerKey);
+    resetSequence();
+  };
+
+  const getClient = <T extends PrismaClientLike>() => {
+    return getClientDelegate<T>(initializerKey);
+  };
+
+  const reset = () => {
+    resetClient(initializerKey);
+    resetSequence();
+    resetScalarFieldValueGenerator();
+  };
+
+  return {
+    initialize,
+    reset,
+    getClient,
+  };
 }
 
+// NOTE:
+//
+// This static initialize and reset functions are exported only for scripts/jest-prisma/setup.ts
 export function initialize(options: InitializeOptions) {
   setClient(options.prisma);
   resetSequence();
 }
+
+export function reset() {
+  resetClient();
+  resetSequence();
+  resetScalarFieldValueGenerator();
+}
diff --git a/packages/prisma-fabbrica/src/internal.ts b/packages/prisma-fabbrica/src/internal.ts
index 97c00f3..88212b0 100644
--- a/packages/prisma-fabbrica/src/internal.ts
+++ b/packages/prisma-fabbrica/src/internal.ts
@@ -1,7 +1,7 @@
 export { getClient } from "./clientHolder";
 export { ModelWithFields, createScreener } from "./relations/screen";
 export { Resolver, normalizeResolver, normalizeList, getSequenceCounter } from "./helpers";
-export { initialize, resetSequence } from "./initialize";
+export { createInitializer, initialize, resetSequence } from "./initialize";
 export {
   getScalarFieldValueGenerator,
   registerScalarFieldValueGenerator,
diff --git a/packages/prisma-fabbrica/src/templates/index.ts b/packages/prisma-fabbrica/src/templates/index.ts
index cb91e13..68a194e 100644
--- a/packages/prisma-fabbrica/src/templates/index.ts
+++ b/packages/prisma-fabbrica/src/templates/index.ts
@@ -81,7 +81,7 @@ export const header = (prismaClientModuleSpecifier: string) =>
     import { Prisma } from ${() => ast.stringLiteral(prismaClientModuleSpecifier)};
     import type { PrismaClient } from ${() => ast.stringLiteral(prismaClientModuleSpecifier)};
     import {
-      getClient,
+      createInitializer,
       ModelWithFields,
       createScreener,
       getScalarFieldValueGenerator,
@@ -90,7 +90,7 @@ export const header = (prismaClientModuleSpecifier: string) =>
       normalizeList,
       getSequenceCounter,
     } from "@quramy/prisma-fabbrica/lib/internal";
-    export { initialize, resetSequence, registerScalarFieldValueGenerator, resetScalarFieldValueGenerator } from "@quramy/prisma-fabbrica/lib/internal";
+    export { resetSequence, registerScalarFieldValueGenerator, resetScalarFieldValueGenerator } from "@quramy/prisma-fabbrica/lib/internal";
   `();
 
 export const buildDataOptions = () =>
@@ -105,6 +105,13 @@ export const importStatement = (specifier: string, prismaClientModuleSpecifier:
     import type { ${() => ast.identifier(specifier)} } from ${() => ast.stringLiteral(prismaClientModuleSpecifier)};
   `();
 
+export const initializer = () =>
+  template.sourceFile`
+    const initializer = createInitializer();
+    const { getClient } = initializer;
+    export const { initialize } = initializer;
+  `();
+
 export const symbols = () =>
   template.sourceFile`
     const factoryFor = Symbol("factoryFor");
@@ -513,6 +520,7 @@ export function getSourceFile({
     ...modelEnums.map(enumName => importStatement(enumName, prismaClientModuleSpecifier)),
     ...header(prismaClientModuleSpecifier).statements,
     insertLeadingBreakMarker(buildDataOptions()),
+    ...insertLeadingBreakMarker(initializer().statements),
     ...insertLeadingBreakMarker(symbols().statements),
     insertLeadingBreakMarker(modelFieldDefinitions(document.datamodel.models)),
     ...document.datamodel.models


Test patch:
diff --git a/packages/artifact-testing/fixtures/field-variation/__generated__/fabbrica/index.ts b/packages/artifact-testing/fixtures/field-variation/__generated__/fabbrica/index.ts
index 4b49c9e..95e6d48 100644
--- a/packages/artifact-testing/fixtures/field-variation/__generated__/fabbrica/index.ts
+++ b/packages/artifact-testing/fixtures/field-variation/__generated__/fabbrica/index.ts
@@ -6,13 +6,19 @@ import type { Role } from "../client";
 import type { Status } from "../client";
 import { Prisma } from "../client";
 import type { PrismaClient } from "../client";
-import { getClient, ModelWithFields, createScreener, getScalarFieldValueGenerator, Resolver, normalizeResolver, normalizeList, getSequenceCounter, } from "@quramy/prisma-fabbrica/lib/internal";
-export { initialize, resetSequence, registerScalarFieldValueGenerator, resetScalarFieldValueGenerator } from "@quramy/prisma-fabbrica/lib/internal";
+import { createInitializer, ModelWithFields, createScreener, getScalarFieldValueGenerator, Resolver, normalizeResolver, normalizeList, getSequenceCounter, } from "@quramy/prisma-fabbrica/lib/internal";
+export { resetSequence, registerScalarFieldValueGenerator, resetScalarFieldValueGenerator } from "@quramy/prisma-fabbrica/lib/internal";
 
 type BuildDataOptions = {
     readonly seq: number;
 };
 
+const initializer = createInitializer();
+
+const { getClient } = initializer;
+
+export const { initialize } = initializer;
+
 const factoryFor = Symbol("factoryFor");
 
 const modelFieldDefinitions: ModelWithFields[] = [{
diff --git a/packages/artifact-testing/fixtures/relations-many-to-many/__generated__/fabbrica/index.ts b/packages/artifact-testing/fixtures/relations-many-to-many/__generated__/fabbrica/index.ts
index f7a7436..b1a45aa 100644
--- a/packages/artifact-testing/fixtures/relations-many-to-many/__generated__/fabbrica/index.ts
+++ b/packages/artifact-testing/fixtures/relations-many-to-many/__generated__/fabbrica/index.ts
@@ -2,13 +2,19 @@ import type { Post } from "../client";
 import type { Category } from "../client";
 import { Prisma } from "../client";
 import type { PrismaClient } from "../client";
-import { getClient, ModelWithFields, createScreener, getScalarFieldValueGenerator, Resolver, normalizeResolver, normalizeList, getSequenceCounter, } from "@quramy/prisma-fabbrica/lib/internal";
-export { initialize, resetSequence, registerScalarFieldValueGenerator, resetScalarFieldValueGenerator } from "@quramy/prisma-fabbrica/lib/internal";
+import { createInitializer, ModelWithFields, createScreener, getScalarFieldValueGenerator, Resolver, normalizeResolver, normalizeList, getSequenceCounter, } from "@quramy/prisma-fabbrica/lib/internal";
+export { resetSequence, registerScalarFieldValueGenerator, resetScalarFieldValueGenerator } from "@quramy/prisma-fabbrica/lib/internal";
 
 type BuildDataOptions = {
     readonly seq: number;
 };
 
+const initializer = createInitializer();
+
+const { getClient } = initializer;
+
+export const { initialize } = initializer;
+
 const factoryFor = Symbol("factoryFor");
 
 const modelFieldDefinitions: ModelWithFields[] = [{
diff --git a/packages/artifact-testing/fixtures/relations-one-to-many/__generated__/fabbrica/index.ts b/packages/artifact-testing/fixtures/relations-one-to-many/__generated__/fabbrica/index.ts
index f74c006..e6e1f52 100644
--- a/packages/artifact-testing/fixtures/relations-one-to-many/__generated__/fabbrica/index.ts
+++ b/packages/artifact-testing/fixtures/relations-one-to-many/__generated__/fabbrica/index.ts
@@ -3,13 +3,19 @@ import type { Post } from "../client";
 import type { Review } from "../client";
 import { Prisma } from "../client";
 import type { PrismaClient } from "../client";
-import { getClient, ModelWithFields, createScreener, getScalarFieldValueGenerator, Resolver, normalizeResolver, normalizeList, getSequenceCounter, } from "@quramy/prisma-fabbrica/lib/internal";
-export { initialize, resetSequence, registerScalarFieldValueGenerator, resetScalarFieldValueGenerator } from "@quramy/prisma-fabbrica/lib/internal";
+import { createInitializer, ModelWithFields, createScreener, getScalarFieldValueGenerator, Resolver, normalizeResolver, normalizeList, getSequenceCounter, } from "@quramy/prisma-fabbrica/lib/internal";
+export { resetSequence, registerScalarFieldValueGenerator, resetScalarFieldValueGenerator } from "@quramy/prisma-fabbrica/lib/internal";
 
 type BuildDataOptions = {
     readonly seq: number;
 };
 
+const initializer = createInitializer();
+
+const { getClient } = initializer;
+
+export const { initialize } = initializer;
+
 const factoryFor = Symbol("factoryFor");
 
 const modelFieldDefinitions: ModelWithFields[] = [{
diff --git a/packages/artifact-testing/fixtures/relations-one-to-one/__generated__/fabbrica/index.ts b/packages/artifact-testing/fixtures/relations-one-to-one/__generated__/fabbrica/index.ts
index b87e245..f9f589b 100644
--- a/packages/artifact-testing/fixtures/relations-one-to-one/__generated__/fabbrica/index.ts
+++ b/packages/artifact-testing/fixtures/relations-one-to-one/__generated__/fabbrica/index.ts
@@ -2,13 +2,19 @@ import type { User } from "../client";
 import type { Profile } from "../client";
 import { Prisma } from "../client";
 import type { PrismaClient } from "../client";
-import { getClient, ModelWithFields, createScreener, getScalarFieldValueGenerator, Resolver, normalizeResolver, normalizeList, getSequenceCounter, } from "@quramy/prisma-fabbrica/lib/internal";
-export { initialize, resetSequence, registerScalarFieldValueGenerator, resetScalarFieldValueGenerator } from "@quramy/prisma-fabbrica/lib/internal";
+import { createInitializer, ModelWithFields, createScreener, getScalarFieldValueGenerator, Resolver, normalizeResolver, normalizeList, getSequenceCounter, } from "@quramy/prisma-fabbrica/lib/internal";
+export { resetSequence, registerScalarFieldValueGenerator, resetScalarFieldValueGenerator } from "@quramy/prisma-fabbrica/lib/internal";
 
 type BuildDataOptions = {
     readonly seq: number;
 };
 
+const initializer = createInitializer();
+
+const { getClient } = initializer;
+
+export const { initialize } = initializer;
+
 const factoryFor = Symbol("factoryFor");
 
 const modelFieldDefinitions: ModelWithFields[] = [{
diff --git a/packages/artifact-testing/fixtures/simple-model/__generated__/fabbrica/index.ts b/packages/artifact-testing/fixtures/simple-model/__generated__/fabbrica/index.ts
index 75fb819..8962e01 100644
--- a/packages/artifact-testing/fixtures/simple-model/__generated__/fabbrica/index.ts
+++ b/packages/artifact-testing/fixtures/simple-model/__generated__/fabbrica/index.ts
@@ -1,13 +1,19 @@
 import type { User } from "../client";
 import { Prisma } from "../client";
 import type { PrismaClient } from "../client";
-import { getClient, ModelWithFields, createScreener, getScalarFieldValueGenerator, Resolver, normalizeResolver, normalizeList, getSequenceCounter, } from "@quramy/prisma-fabbrica/lib/internal";
-export { initialize, resetSequence, registerScalarFieldValueGenerator, resetScalarFieldValueGenerator } from "@quramy/prisma-fabbrica/lib/internal";
+import { createInitializer, ModelWithFields, createScreener, getScalarFieldValueGenerator, Resolver, normalizeResolver, normalizeList, getSequenceCounter, } from "@quramy/prisma-fabbrica/lib/internal";
+export { resetSequence, registerScalarFieldValueGenerator, resetScalarFieldValueGenerator } from "@quramy/prisma-fabbrica/lib/internal";
 
 type BuildDataOptions = {
     readonly seq: number;
 };
 
+const initializer = createInitializer();
+
+const { getClient } = initializer;
+
+export const { initialize } = initializer;
+
 const factoryFor = Symbol("factoryFor");
 
 const modelFieldDefinitions: ModelWithFields[] = [{
diff --git a/packages/artifact-testing/fixtures/simple-model/multipleClients.test.ts b/packages/artifact-testing/fixtures/simple-model/multipleClients.test.ts
new file mode 100644
index 0000000..b9627c9
--- /dev/null
+++ b/packages/artifact-testing/fixtures/simple-model/multipleClients.test.ts
@@ -0,0 +1,50 @@
+import type { PrismaClient } from "./__generated__/client";
+import { initialize, defineUserFactory } from "./__generated__/fabbrica";
+
+import type { PrismaClient as PrismaClientAlt } from "./__generated__/client";
+import {
+  initialize as initializeAlt,
+  defineUserFactory as defineAltUserFactory,
+} from "../field-variation/__generated__/fabbrica";
+
+describe("initialize function", () => {
+  describe("initialize function binds prisma instance for each fabbrica script", () => {
+    const prismaClientMock = {
+      user: {
+        create: jest.fn(),
+      },
+    };
+    const prismaClientMockAlt = {
+      user: {
+        create: jest.fn(),
+      },
+    };
+    const UserFactory = defineUserFactory();
+    const AltUserFactory = defineAltUserFactory();
+
+    beforeAll(() => {
+      initialize({ prisma: prismaClientMock as unknown as PrismaClient });
+      initializeAlt({ prisma: prismaClientMockAlt as unknown as PrismaClientAlt });
+    });
+
+    beforeEach(() => {
+      prismaClientMock.user.create.mockReset();
+      prismaClientMockAlt.user.create.mockReset();
+    });
+
+    it("should be called multiple clients called when multiple fabbrica are executed", async () => {
+      await UserFactory.create();
+      await AltUserFactory.create();
+
+      expect(prismaClientMock.user.create).toHaveBeenCalledTimes(1);
+      expect(prismaClientMockAlt.user.create).toHaveBeenCalledTimes(1);
+    });
+
+    it("should not be called a client unless the corresponding fabbrica is executed", async () => {
+      await UserFactory.create();
+
+      expect(prismaClientMock.user.create).toHaveBeenCalledTimes(1);
+      expect(prismaClientMockAlt.user.create).not.toHaveBeenCalled();
+    });
+  });
+});
diff --git a/packages/prisma-fabbrica/src/templates/__snapshots__/getSourceFile.test.ts.snap b/packages/prisma-fabbrica/src/templates/__snapshots__/getSourceFile.test.ts.snap
index d9f1c57..0c5d744 100644
--- a/packages/prisma-fabbrica/src/templates/__snapshots__/getSourceFile.test.ts.snap
+++ b/packages/prisma-fabbrica/src/templates/__snapshots__/getSourceFile.test.ts.snap
@@ -4,13 +4,19 @@ exports[`getSourceFile generates TypeScript AST 1`] = `
 "import type { User } from "@prisma/client";
 import { Prisma } from "@prisma/client";
 import type { PrismaClient } from "@prisma/client";
-import { getClient, ModelWithFields, createScreener, getScalarFieldValueGenerator, Resolver, normalizeResolver, normalizeList, getSequenceCounter, } from "@quramy/prisma-fabbrica/lib/internal";
-export { initialize, resetSequence, registerScalarFieldValueGenerator, resetScalarFieldValueGenerator } from "@quramy/prisma-fabbrica/lib/internal";
+import { createInitializer, ModelWithFields, createScreener, getScalarFieldValueGenerator, Resolver, normalizeResolver, normalizeList, getSequenceCounter, } from "@quramy/prisma-fabbrica/lib/internal";
+export { resetSequence, registerScalarFieldValueGenerator, resetScalarFieldValueGenerator } from "@quramy/prisma-fabbrica/lib/internal";
 
 type BuildDataOptions = {
     readonly seq: number;
 };
 
+const initializer = createInitializer();
+
+const { getClient } = initializer;
+
+export const { initialize } = initializer;
+
 const factoryFor = Symbol("factoryFor");
 
 const modelFieldDefinitions: ModelWithFields[] = [{