@prisma-next/mongo 0.5.0-dev.4 → 0.5.0-dev.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,24 +1,95 @@
1
1
  import { MongoContract, MongoContractWithTypeMaps, MongoTypeMaps } from "@prisma-next/mongo-contract";
2
- import { mongoOrm } from "@prisma-next/mongo-orm";
2
+ import { MongoOrmClient } from "@prisma-next/mongo-orm";
3
3
  import { mongoQuery } from "@prisma-next/mongo-query-builder";
4
4
  import { MongoRuntime } from "@prisma-next/mongo-runtime";
5
+ import * as mongodb0 from "mongodb";
6
+ import { MongoClient as MongoClient$1 } from "mongodb";
5
7
 
8
+ //#region src/runtime/binding.d.ts
9
+ type MongoBinding = {
10
+ readonly kind: 'url';
11
+ readonly url: string;
12
+ readonly dbName: string;
13
+ } | {
14
+ readonly kind: 'mongoClient';
15
+ readonly client: MongoClient$1;
16
+ readonly dbName: string;
17
+ };
18
+ type MongoBindingInput = {
19
+ readonly binding: MongoBinding;
20
+ readonly url?: never;
21
+ readonly uri?: never;
22
+ readonly dbName?: never;
23
+ readonly mongoClient?: never;
24
+ } | {
25
+ readonly url: string;
26
+ readonly dbName?: string;
27
+ readonly binding?: never;
28
+ readonly uri?: never;
29
+ readonly mongoClient?: never;
30
+ } | {
31
+ readonly uri: string;
32
+ readonly dbName: string;
33
+ readonly binding?: never;
34
+ readonly url?: never;
35
+ readonly mongoClient?: never;
36
+ } | {
37
+ readonly mongoClient: MongoClient$1;
38
+ readonly dbName: string;
39
+ readonly binding?: never;
40
+ readonly url?: never;
41
+ readonly uri?: never;
42
+ };
43
+ //#endregion
6
44
  //#region src/runtime/mongo.d.ts
7
- interface MongoOptions {
8
- readonly contractJson: unknown;
9
- }
45
+ type MongoTargetId = 'mongo';
10
46
  interface MongoClient<TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>> {
11
- readonly query: ReturnType<typeof mongoQuery<TContract>>;
12
- connect(uri: string, dbName: string): Promise<ConnectedMongoClient<TContract>>;
13
- }
14
- interface ConnectedMongoClient<TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>> {
15
- readonly orm: ReturnType<typeof mongoOrm<TContract>>;
16
- readonly runtime: MongoRuntime;
47
+ readonly orm: MongoOrmClient<TContract>;
17
48
  readonly query: ReturnType<typeof mongoQuery<TContract>>;
18
49
  readonly contract: TContract;
50
+ connect(bindingInput?: MongoBindingInput): Promise<MongoRuntime>;
51
+ runtime(): Promise<MongoRuntime>;
19
52
  close(): Promise<void>;
20
53
  }
21
- declare function mongo<TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>>(options: MongoOptions): MongoClient<TContract>;
54
+ interface MongoOptionsBase {
55
+ readonly mode?: 'strict' | 'permissive';
56
+ }
57
+ interface MongoBindingOptions {
58
+ readonly binding?: MongoBinding;
59
+ readonly url?: string;
60
+ readonly uri?: string;
61
+ readonly dbName?: string;
62
+ readonly mongoClient?: mongodb0.MongoClient;
63
+ }
64
+ type MongoOptionsWithContract<TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>> = MongoBindingOptions & MongoOptionsBase & {
65
+ readonly contract: TContract;
66
+ readonly contractJson?: never;
67
+ };
68
+ /**
69
+ * `TContract` is a phantom parameter that drives explicit-type inference at the
70
+ * call site (e.g. `mongo<Contract>({ contractJson })`). It is not referenced in
71
+ * the type body — the contract value comes through `contractJson` at runtime.
72
+ */
73
+ type MongoOptionsWithContractJson<TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>> = MongoBindingOptions & MongoOptionsBase & {
74
+ readonly contractJson: unknown;
75
+ readonly contract?: never;
76
+ /**
77
+ * @internal phantom field; never set at runtime, only references TContract
78
+ * so that strict tsc/biome don't flag the type parameter as unused.
79
+ */
80
+ readonly __contractPhantom?: TContract;
81
+ };
82
+ type MongoOptions<TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>> = MongoOptionsWithContract<TContract> | MongoOptionsWithContractJson<TContract>;
83
+ /**
84
+ * Creates a lazy Mongo client from either `contractJson` or a TypeScript-authored `contract`.
85
+ * The `orm` and `query` surfaces are available immediately; the underlying driver is connected
86
+ * on the first query (or the first explicit `connect()` call), mirroring the Postgres facade.
87
+ *
88
+ * - No-emit: pass a TypeScript-authored contract. Example: `mongo({ contract, url })`
89
+ * - Emitted: pass `Contract` type explicitly. Example: `mongo<Contract>({ contractJson, url })`
90
+ */
91
+ declare function mongo<TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>>(options: MongoOptionsWithContract<TContract>): MongoClient<TContract>;
92
+ declare function mongo<TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>>(options: MongoOptionsWithContractJson<TContract>): MongoClient<TContract>;
22
93
  //#endregion
23
- export { type ConnectedMongoClient, type MongoClient, type MongoOptions, mongo as default };
94
+ export { type MongoBinding, type MongoBindingInput, type MongoClient, type MongoOptions, type MongoOptionsWithContract, type MongoOptionsWithContractJson, type MongoTargetId, mongo as default };
24
95
  //# sourceMappingURL=runtime.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.mts","names":[],"sources":["../src/runtime/mongo.ts"],"sourcesContent":[],"mappings":";;;;;;UAaiB,YAAA;;AAAjB;AAIiB,UAAA,WAAW,CAAA,kBACR,yBADQ,CACkB,aADlB,EACiC,aADjC,CAAA,CAAA,CAAA;EACkB,SAAA,KAAA,EAE5B,UAF4B,CAAA,OAEV,UAFU,CAEC,SAFD,CAAA,CAAA;EAAe,OAAA,CAAA,GAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAGrB,OAHqB,CAGb,oBAHa,CAGQ,SAHR,CAAA,CAAA;;AAEd,UAI9B,oBAJ8B,CAAA,kBAK3B,yBAL2B,CAKD,aALC,EAKc,aALd,CAAA,CAAA,CAAA;EAAX,SAAA,GAAA,EAOpB,UAPoB,CAAA,OAOF,QAPE,CAOO,SAPP,CAAA,CAAA;EAAlB,SAAA,OAAA,EAQE,YARF;EACmD,SAAA,KAAA,EAQnD,UARmD,CAAA,OAQjC,UARiC,CAQtB,SARsB,CAAA,CAAA;EAArB,SAAA,QAAA,EAS3B,SAT2B;EAAR,KAAA,EAAA,EAU7B,OAV6B,CAAA,IAAA,CAAA;;AAGvB,iBAUO,KAVa,CAAA,kBAWjB,yBAXiB,CAWS,aAXT,EAWwB,aAXxB,CAAA,CAAA,CAAA,OAAA,EAY1B,YAZ0B,CAAA,EAYX,WAZW,CAYC,SAZD,CAAA"}
1
+ {"version":3,"file":"runtime.d.mts","names":[],"sources":["../src/runtime/binding.ts","../src/runtime/mongo.ts"],"sourcesContent":[],"mappings":";;;;;;;;KAEY,YAAA;;;;;;mBAIW;EAJX,SAAA,MAAY,EAAA,MAAA;AAQxB,CAAA;KAAY,iBAAA;oBAEY;;ECSZ,SAAA,GAAA,CAAA,EAAA,KAAa;EAER,SAAA,MAAW,CAAA,EAAA,KAAA;EACkB,SAAA,WAAA,CAAA,EAAA,KAAA;CAAe,GAAA;EAAzC,SAAA,GAAA,EAAA,MAAA;EAEW,SAAA,MAAA,CAAA,EAAA,MAAA;EAAf,SAAA,OAAA,CAAA,EAAA,KAAA;EAC+B,SAAA,GAAA,CAAA,EAAA,KAAA;EAAX,SAAA,WAAA,CAAA,EAAA,KAAA;CAAlB,GAAA;EACG,SAAA,GAAA,EAAA,MAAA;EACI,SAAA,MAAA,EAAA,MAAA;EAA4B,SAAA,OAAA,CAAA,EAAA,KAAA;EAAR,SAAA,GAAA,CAAA,EAAA,KAAA;EACxB,SAAA,WAAA,CAAA,EAAA,KAAA;CAAR,GAAA;EACF,SAAA,WAAA,EDEiB,aCFjB;EAAO,SAAA,MAAA,EAAA,MAAA;EAGD,SAAA,OAAA,CAAA,EAAgB,KAAA;EAIhB,SAAA,GAAA,CAAA,EAAA,KAAA;EAQL,SAAA,GAAA,CAAA,EAAA,KAAA;CACkC;;;KA1BlC,aAAA;UAEK,8BACG,0BAA0B,eAAe;gBAE7C,eAAe;EDxBnB,SAAA,KAAA,ECyBM,UDzBM,CAID,OCqBa,UDrBI,CCqBO,SDrBP,CAAA,CAAA;EAI5B,SAAA,QAAA,ECkBS,SDlBQ;yBCmBJ,oBAAoB,QAAQ;aACxC,QAAQ;WACV;AAVX;AAEiB,UAWA,gBAAA,CAXW;EACkB,SAAA,IAAA,CAAA,EAAA,QAAA,GAAA,YAAA;;AAA1B,UAcH,mBAAA,CAdG;EAEW,SAAA,OAAA,CAAA,EAaV,YAbU;EAAf,SAAA,GAAA,CAAA,EAAA,MAAA;EAC+B,SAAA,GAAA,CAAA,EAAA,MAAA;EAAX,SAAA,MAAA,CAAA,EAAA,MAAA;EAAlB,SAAA,WAAA,CAAA,EAYe,QAAA,CAIU,WAhBzB;;AAEO,KAiBb,wBAjBa,CAAA,kBAkBL,yBAlBK,CAkBqB,aAlBrB,EAkBoC,aAlBpC,CAAA,CAAA,GAmBrB,mBAnBqB,GAoBvB,gBApBuB,GAAA;EAA4B,SAAA,QAAA,EAqB9B,SArB8B;EAAR,SAAA,YAAA,CAAA,EAAA,KAAA;CACxB;;;;AAIrB;AAIA;AAQY,KAaA,4BAbwB,CAAA,kBAchB,yBAdgB,CAcU,aAdV,EAcyB,aAdzB,CAAA,CAAA,GAehC,mBAfgC,GAgBlC,gBAhBkC,GAAA;EACU,SAAA,YAAA,EAAA,OAAA;EAAe,SAAA,QAAA,CAAA,EAAA,KAAA;EAAzC;;;;EAGY,SAAA,iBAAA,CAAA,EAmBC,SAnBD;AAShC,CAAA;AAC8C,KAYlC,YAZkC,CAAA,kBAa1B,yBAb0B,CAaA,aAbA,EAae,aAbf,CAAA,CAAA,GAc1C,wBAd0C,CAcjB,SAdiB,CAAA,GAcJ,4BAdI,CAcyB,SAdzB,CAAA;;;;;;;AAY9C;;AAC6D,iBAwBrC,KAxBqC,CAAA,kBAyBzC,yBAzByC,CAyBf,aAzBe,EAyBA,aAzBA,CAAA,CAAA,CAAA,OAAA,EA0BlD,wBA1BkD,CA0BzB,SA1ByB,CAAA,CAAA,EA0BZ,WA1BY,CA0BA,SA1BA,CAAA;AAAzC,iBA2BI,KA3BJ,CAAA,kBA4BA,yBA5BA,CA4B0B,aA5B1B,EA4ByC,aA5BzC,CAAA,CAAA,CAAA,OAAA,EA6BT,4BA7BS,CA6BoB,SA7BpB,CAAA,CAAA,EA6BiC,WA7BjC,CA6B6C,SA7B7C,CAAA"}
package/dist/runtime.mjs CHANGED
@@ -1,35 +1,147 @@
1
1
  import { createMongoAdapter } from "@prisma-next/adapter-mongo";
2
- import { createMongoDriver } from "@prisma-next/driver-mongo";
2
+ import { MongoDriverImpl } from "@prisma-next/driver-mongo";
3
+ import { AsyncIterableResult } from "@prisma-next/framework-components/runtime";
3
4
  import { validateMongoContract } from "@prisma-next/mongo-contract";
4
5
  import { mongoOrm } from "@prisma-next/mongo-orm";
5
6
  import { mongoQuery } from "@prisma-next/mongo-query-builder";
6
7
  import { createMongoRuntime } from "@prisma-next/mongo-runtime";
7
8
 
9
+ //#region src/runtime/binding.ts
10
+ function validateMongoUrl(url) {
11
+ const trimmed = url.trim();
12
+ if (trimmed.length === 0) throw new Error("Mongo URL must be a non-empty string");
13
+ let parsed;
14
+ try {
15
+ parsed = new URL(trimmed);
16
+ } catch {
17
+ throw new Error("Mongo URL must be a valid URL");
18
+ }
19
+ if (parsed.protocol !== "mongodb:" && parsed.protocol !== "mongodb+srv:") throw new Error("Mongo URL must use mongodb:// or mongodb+srv://");
20
+ return parsed;
21
+ }
22
+ function extractDbNameFromUrl(parsed) {
23
+ const path = parsed.pathname.startsWith("/") ? parsed.pathname.slice(1) : parsed.pathname;
24
+ if (path.length === 0) return;
25
+ const slash = path.indexOf("/");
26
+ return slash === -1 ? path : path.slice(0, slash);
27
+ }
28
+ function resolveMongoBinding(options) {
29
+ if (Number(options.binding !== void 0) + Number(options.url !== void 0) + Number(options.uri !== void 0) + Number(options.mongoClient !== void 0) !== 1) throw new Error("Provide one binding input: binding, url, uri+dbName, or mongoClient+dbName");
30
+ if (options.binding !== void 0) return options.binding;
31
+ if (options.url !== void 0) {
32
+ const parsed = validateMongoUrl(options.url);
33
+ if (options.dbName !== void 0 && options.dbName.trim().length === 0) throw new Error("Mongo binding via { url, dbName } requires a non-empty dbName");
34
+ const explicitDbName = options.dbName?.trim();
35
+ const dbName$1 = explicitDbName !== void 0 && explicitDbName.length > 0 ? explicitDbName : extractDbNameFromUrl(parsed);
36
+ if (dbName$1 === void 0 || dbName$1.length === 0) throw new Error("Mongo URL must include a database name in its path (e.g. mongodb://host:27017/mydb), or pass dbName explicitly");
37
+ return {
38
+ kind: "url",
39
+ url: options.url.trim(),
40
+ dbName: dbName$1
41
+ };
42
+ }
43
+ if (options.uri !== void 0) {
44
+ validateMongoUrl(options.uri);
45
+ const dbName$1 = options.dbName?.trim();
46
+ if (dbName$1 === void 0 || dbName$1.length === 0) throw new Error("Mongo binding via { uri, dbName } requires a non-empty dbName");
47
+ return {
48
+ kind: "url",
49
+ url: options.uri.trim(),
50
+ dbName: dbName$1
51
+ };
52
+ }
53
+ const mongoClient = options.mongoClient;
54
+ if (mongoClient === void 0) throw new Error("Invariant violation: expected mongo binding after validation");
55
+ const dbName = options.dbName?.trim();
56
+ if (dbName === void 0 || dbName.length === 0) throw new Error("Mongo binding via { mongoClient, dbName } requires a non-empty dbName");
57
+ return {
58
+ kind: "mongoClient",
59
+ client: mongoClient,
60
+ dbName
61
+ };
62
+ }
63
+ function resolveOptionalMongoBinding(options) {
64
+ const providedCount = Number(options.binding !== void 0) + Number(options.url !== void 0) + Number(options.uri !== void 0) + Number(options.mongoClient !== void 0);
65
+ if (providedCount === 0) return;
66
+ if (providedCount !== 1) throw new Error("Provide one binding input: binding, url, uri+dbName, or mongoClient+dbName");
67
+ if (options.binding !== void 0) return resolveMongoBinding({ binding: options.binding });
68
+ if (options.url !== void 0) return resolveMongoBinding(options.dbName !== void 0 ? {
69
+ url: options.url,
70
+ dbName: options.dbName
71
+ } : { url: options.url });
72
+ if (options.uri !== void 0) return resolveMongoBinding({
73
+ uri: options.uri,
74
+ dbName: options.dbName ?? ""
75
+ });
76
+ if (options.mongoClient !== void 0) return resolveMongoBinding({
77
+ mongoClient: options.mongoClient,
78
+ dbName: options.dbName ?? ""
79
+ });
80
+ throw new Error("Invariant violation: expected one mongo binding branch");
81
+ }
82
+
83
+ //#endregion
8
84
  //#region src/runtime/mongo.ts
85
+ function hasContractJson(options) {
86
+ return "contractJson" in options;
87
+ }
88
+ function resolveContract(options) {
89
+ return validateMongoContract(hasContractJson(options) ? options.contractJson : options.contract).contract;
90
+ }
9
91
  function mongo(options) {
10
- const { contract } = validateMongoContract(options.contractJson);
92
+ const contract = resolveContract(options);
93
+ let binding = resolveOptionalMongoBinding(options);
11
94
  const query = mongoQuery({ contractJson: contract });
95
+ let runtimePromise;
96
+ let closed = false;
97
+ const buildRuntime = async (resolvedBinding) => {
98
+ return createMongoRuntime({
99
+ adapter: createMongoAdapter(),
100
+ driver: resolvedBinding.kind === "url" ? await MongoDriverImpl.fromConnection(resolvedBinding.url, resolvedBinding.dbName) : MongoDriverImpl.fromDb(resolvedBinding.client.db(resolvedBinding.dbName)),
101
+ contract,
102
+ targetId: "mongo",
103
+ ...options.mode !== void 0 ? { mode: options.mode } : {}
104
+ });
105
+ };
106
+ const getRuntime = () => {
107
+ if (closed) return Promise.reject(/* @__PURE__ */ new Error("Mongo client is closed"));
108
+ if (runtimePromise !== void 0) return runtimePromise;
109
+ if (binding === void 0) return Promise.reject(/* @__PURE__ */ new Error("Mongo binding not configured. Pass url/uri+dbName/mongoClient+dbName/binding to mongo(...) or call db.connect({ ... })."));
110
+ runtimePromise = buildRuntime(binding).catch((err) => {
111
+ runtimePromise = void 0;
112
+ throw err;
113
+ });
114
+ return runtimePromise;
115
+ };
12
116
  return {
13
- query,
14
- async connect(uri, dbName) {
15
- const runtime = createMongoRuntime({
16
- adapter: createMongoAdapter(),
17
- driver: await createMongoDriver(uri, dbName),
18
- contract,
19
- targetId: "mongo"
20
- });
21
- return {
22
- orm: mongoOrm({
23
- contract,
24
- executor: runtime
25
- }),
26
- runtime,
27
- query,
28
- contract,
29
- async close() {
30
- await runtime.close();
117
+ orm: mongoOrm({
118
+ contract,
119
+ executor: { execute(plan) {
120
+ async function* iterate() {
121
+ yield* (await getRuntime()).execute(plan);
31
122
  }
32
- };
123
+ return new AsyncIterableResult(iterate());
124
+ } }
125
+ }),
126
+ query,
127
+ contract,
128
+ async connect(bindingInput) {
129
+ if (closed) throw new Error("Mongo client is closed");
130
+ if (runtimePromise !== void 0) throw new Error("Mongo client already connected");
131
+ if (bindingInput !== void 0) binding = resolveMongoBinding(bindingInput);
132
+ if (binding === void 0) throw new Error("Mongo binding not configured. Pass url/uri+dbName/mongoClient+dbName/binding to mongo(...) or call db.connect({ ... }).");
133
+ return getRuntime();
134
+ },
135
+ runtime() {
136
+ return getRuntime();
137
+ },
138
+ async close() {
139
+ if (closed) return;
140
+ closed = true;
141
+ if (runtimePromise === void 0) return;
142
+ try {
143
+ await (await runtimePromise).close();
144
+ } catch {}
33
145
  }
34
146
  };
35
147
  }
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.mjs","names":[],"sources":["../src/runtime/mongo.ts"],"sourcesContent":["import { createMongoAdapter } from '@prisma-next/adapter-mongo';\nimport { createMongoDriver } from '@prisma-next/driver-mongo';\nimport type {\n MongoContract,\n MongoContractWithTypeMaps,\n MongoTypeMaps,\n} from '@prisma-next/mongo-contract';\nimport { validateMongoContract } from '@prisma-next/mongo-contract';\nimport { mongoOrm } from '@prisma-next/mongo-orm';\nimport { mongoQuery } from '@prisma-next/mongo-query-builder';\nimport type { MongoRuntime } from '@prisma-next/mongo-runtime';\nimport { createMongoRuntime } from '@prisma-next/mongo-runtime';\n\nexport interface MongoOptions {\n readonly contractJson: unknown;\n}\n\nexport interface MongoClient<\n TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>,\n> {\n readonly query: ReturnType<typeof mongoQuery<TContract>>;\n connect(uri: string, dbName: string): Promise<ConnectedMongoClient<TContract>>;\n}\n\nexport interface ConnectedMongoClient<\n TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>,\n> {\n readonly orm: ReturnType<typeof mongoOrm<TContract>>;\n readonly runtime: MongoRuntime;\n readonly query: ReturnType<typeof mongoQuery<TContract>>;\n readonly contract: TContract;\n close(): Promise<void>;\n}\n\nexport default function mongo<\n TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>,\n>(options: MongoOptions): MongoClient<TContract> {\n const { contract } = validateMongoContract<TContract>(options.contractJson);\n const query = mongoQuery<TContract>({ contractJson: contract });\n\n return {\n query,\n async connect(uri: string, dbName: string): Promise<ConnectedMongoClient<TContract>> {\n const adapter = createMongoAdapter();\n const driver = await createMongoDriver(uri, dbName);\n const runtime = createMongoRuntime({ adapter, driver, contract, targetId: 'mongo' });\n const orm = mongoOrm<TContract>({ contract, executor: runtime });\n\n return {\n orm,\n runtime,\n query,\n contract,\n async close() {\n await runtime.close();\n },\n };\n },\n };\n}\n"],"mappings":";;;;;;;;AAkCA,SAAwB,MAEtB,SAA+C;CAC/C,MAAM,EAAE,aAAa,sBAAiC,QAAQ,aAAa;CAC3E,MAAM,QAAQ,WAAsB,EAAE,cAAc,UAAU,CAAC;AAE/D,QAAO;EACL;EACA,MAAM,QAAQ,KAAa,QAA0D;GAGnF,MAAM,UAAU,mBAAmB;IAAE,SAFrB,oBAAoB;IAEU,QAD/B,MAAM,kBAAkB,KAAK,OAAO;IACG;IAAU,UAAU;IAAS,CAAC;AAGpF,UAAO;IACL,KAHU,SAAoB;KAAE;KAAU,UAAU;KAAS,CAAC;IAI9D;IACA;IACA;IACA,MAAM,QAAQ;AACZ,WAAM,QAAQ,OAAO;;IAExB;;EAEJ"}
1
+ {"version":3,"file":"runtime.mjs","names":["parsed: URL","dbName","runtimePromise: Promise<MongoRuntime> | undefined"],"sources":["../src/runtime/binding.ts","../src/runtime/mongo.ts"],"sourcesContent":["import type { MongoClient as MongoDriverClient } from 'mongodb';\n\nexport type MongoBinding =\n | { readonly kind: 'url'; readonly url: string; readonly dbName: string }\n | {\n readonly kind: 'mongoClient';\n readonly client: MongoDriverClient;\n readonly dbName: string;\n };\n\nexport type MongoBindingInput =\n | {\n readonly binding: MongoBinding;\n readonly url?: never;\n readonly uri?: never;\n readonly dbName?: never;\n readonly mongoClient?: never;\n }\n | {\n readonly url: string;\n readonly dbName?: string;\n readonly binding?: never;\n readonly uri?: never;\n readonly mongoClient?: never;\n }\n | {\n readonly uri: string;\n readonly dbName: string;\n readonly binding?: never;\n readonly url?: never;\n readonly mongoClient?: never;\n }\n | {\n readonly mongoClient: MongoDriverClient;\n readonly dbName: string;\n readonly binding?: never;\n readonly url?: never;\n readonly uri?: never;\n };\n\ntype MongoBindingFields = {\n readonly binding?: MongoBinding;\n readonly url?: string;\n readonly uri?: string;\n readonly dbName?: string;\n readonly mongoClient?: MongoDriverClient;\n};\n\nfunction validateMongoUrl(url: string): URL {\n const trimmed = url.trim();\n if (trimmed.length === 0) {\n throw new Error('Mongo URL must be a non-empty string');\n }\n\n let parsed: URL;\n try {\n parsed = new URL(trimmed);\n } catch {\n throw new Error('Mongo URL must be a valid URL');\n }\n\n if (parsed.protocol !== 'mongodb:' && parsed.protocol !== 'mongodb+srv:') {\n throw new Error('Mongo URL must use mongodb:// or mongodb+srv://');\n }\n\n return parsed;\n}\n\nfunction extractDbNameFromUrl(parsed: URL): string | undefined {\n // pathname is \"/dbname\" or \"\" — strip the leading slash. Anything past\n // a second slash is invalid for our purposes (auth-source style paths).\n const path = parsed.pathname.startsWith('/') ? parsed.pathname.slice(1) : parsed.pathname;\n if (path.length === 0) {\n return undefined;\n }\n const slash = path.indexOf('/');\n return slash === -1 ? path : path.slice(0, slash);\n}\n\nexport function resolveMongoBinding(options: MongoBindingInput): MongoBinding {\n const providedCount =\n Number(options.binding !== undefined) +\n Number(options.url !== undefined) +\n Number(options.uri !== undefined) +\n Number(options.mongoClient !== undefined);\n\n if (providedCount !== 1) {\n throw new Error('Provide one binding input: binding, url, uri+dbName, or mongoClient+dbName');\n }\n\n if (options.binding !== undefined) {\n return options.binding;\n }\n\n if (options.url !== undefined) {\n const parsed = validateMongoUrl(options.url);\n // An explicit, whitespace-only `dbName` is a user-error we'd rather\n // surface loudly: silently falling back to the URL path would mask\n // a typo or empty-template-string bug. Treat it the same as the\n // `{ uri, dbName }` and `{ mongoClient, dbName }` paths, where an\n // empty trimmed dbName is already a fast failure.\n if (options.dbName !== undefined && options.dbName.trim().length === 0) {\n throw new Error('Mongo binding via { url, dbName } requires a non-empty dbName');\n }\n const explicitDbName = options.dbName?.trim();\n const dbName =\n explicitDbName !== undefined && explicitDbName.length > 0\n ? explicitDbName\n : extractDbNameFromUrl(parsed);\n if (dbName === undefined || dbName.length === 0) {\n throw new Error(\n 'Mongo URL must include a database name in its path (e.g. mongodb://host:27017/mydb), or pass dbName explicitly',\n );\n }\n return { kind: 'url', url: options.url.trim(), dbName };\n }\n\n if (options.uri !== undefined) {\n validateMongoUrl(options.uri);\n const dbName = options.dbName?.trim();\n if (dbName === undefined || dbName.length === 0) {\n throw new Error('Mongo binding via { uri, dbName } requires a non-empty dbName');\n }\n return { kind: 'url', url: options.uri.trim(), dbName };\n }\n\n const mongoClient = options.mongoClient;\n if (mongoClient === undefined) {\n throw new Error('Invariant violation: expected mongo binding after validation');\n }\n const dbName = options.dbName?.trim();\n if (dbName === undefined || dbName.length === 0) {\n throw new Error('Mongo binding via { mongoClient, dbName } requires a non-empty dbName');\n }\n return { kind: 'mongoClient', client: mongoClient, dbName };\n}\n\nexport function resolveOptionalMongoBinding(options: MongoBindingFields): MongoBinding | undefined {\n const providedCount =\n Number(options.binding !== undefined) +\n Number(options.url !== undefined) +\n Number(options.uri !== undefined) +\n Number(options.mongoClient !== undefined);\n\n if (providedCount === 0) {\n return undefined;\n }\n // Defer the \"exactly one\" enforcement to `resolveMongoBinding`. We call it\n // through a single branch per input field so the matching union member of\n // `MongoBindingInput` is constructed explicitly — the previous\n // `options as MongoBindingInput` cast hid drift between `MongoBindingFields`\n // (any combination of optional inputs) and `MongoBindingInput` (exactly one).\n if (providedCount !== 1) {\n throw new Error('Provide one binding input: binding, url, uri+dbName, or mongoClient+dbName');\n }\n if (options.binding !== undefined) {\n return resolveMongoBinding({ binding: options.binding });\n }\n if (options.url !== undefined) {\n return resolveMongoBinding(\n options.dbName !== undefined\n ? { url: options.url, dbName: options.dbName }\n : { url: options.url },\n );\n }\n if (options.uri !== undefined) {\n return resolveMongoBinding({ uri: options.uri, dbName: options.dbName ?? '' });\n }\n if (options.mongoClient !== undefined) {\n return resolveMongoBinding({\n mongoClient: options.mongoClient,\n dbName: options.dbName ?? '',\n });\n }\n // Unreachable: the `providedCount === 1` guard above plus the four\n // branch checks cover every shape of `MongoBindingFields`. A bare\n // `return undefined` here would silently mask a future\n // `MongoBindingFields` extension that adds a fifth input, so we\n // surface it as an invariant rather than a missing binding.\n throw new Error('Invariant violation: expected one mongo binding branch');\n}\n","import { createMongoAdapter } from '@prisma-next/adapter-mongo';\nimport { MongoDriverImpl } from '@prisma-next/driver-mongo';\nimport { AsyncIterableResult } from '@prisma-next/framework-components/runtime';\nimport type {\n MongoContract,\n MongoContractWithTypeMaps,\n MongoTypeMaps,\n} from '@prisma-next/mongo-contract';\nimport { validateMongoContract } from '@prisma-next/mongo-contract';\nimport type { MongoOrmClient, MongoQueryPlan } from '@prisma-next/mongo-orm';\nimport { mongoOrm } from '@prisma-next/mongo-orm';\nimport { mongoQuery } from '@prisma-next/mongo-query-builder';\nimport type { MongoRuntime } from '@prisma-next/mongo-runtime';\nimport { createMongoRuntime } from '@prisma-next/mongo-runtime';\nimport {\n type MongoBinding,\n type MongoBindingInput,\n resolveMongoBinding,\n resolveOptionalMongoBinding,\n} from './binding';\n\nexport type MongoTargetId = 'mongo';\n\nexport interface MongoClient<\n TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>,\n> {\n readonly orm: MongoOrmClient<TContract>;\n readonly query: ReturnType<typeof mongoQuery<TContract>>;\n readonly contract: TContract;\n connect(bindingInput?: MongoBindingInput): Promise<MongoRuntime>;\n runtime(): Promise<MongoRuntime>;\n close(): Promise<void>;\n}\n\nexport interface MongoOptionsBase {\n readonly mode?: 'strict' | 'permissive';\n}\n\nexport interface MongoBindingOptions {\n readonly binding?: MongoBinding;\n readonly url?: string;\n readonly uri?: string;\n readonly dbName?: string;\n readonly mongoClient?: import('mongodb').MongoClient;\n}\n\nexport type MongoOptionsWithContract<\n TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>,\n> = MongoBindingOptions &\n MongoOptionsBase & {\n readonly contract: TContract;\n readonly contractJson?: never;\n };\n\n/**\n * `TContract` is a phantom parameter that drives explicit-type inference at the\n * call site (e.g. `mongo<Contract>({ contractJson })`). It is not referenced in\n * the type body — the contract value comes through `contractJson` at runtime.\n */\nexport type MongoOptionsWithContractJson<\n TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>,\n> = MongoBindingOptions &\n MongoOptionsBase & {\n readonly contractJson: unknown;\n readonly contract?: never;\n /**\n * @internal phantom field; never set at runtime, only references TContract\n * so that strict tsc/biome don't flag the type parameter as unused.\n */\n readonly __contractPhantom?: TContract;\n };\n\nexport type MongoOptions<\n TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>,\n> = MongoOptionsWithContract<TContract> | MongoOptionsWithContractJson<TContract>;\n\nfunction hasContractJson<TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>>(\n options: MongoOptions<TContract>,\n): options is MongoOptionsWithContractJson<TContract> {\n return 'contractJson' in options;\n}\n\nfunction resolveContract<TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>>(\n options: MongoOptions<TContract>,\n): TContract {\n const contractInput = hasContractJson(options) ? options.contractJson : options.contract;\n return validateMongoContract<TContract>(contractInput).contract;\n}\n\n/**\n * Creates a lazy Mongo client from either `contractJson` or a TypeScript-authored `contract`.\n * The `orm` and `query` surfaces are available immediately; the underlying driver is connected\n * on the first query (or the first explicit `connect()` call), mirroring the Postgres facade.\n *\n * - No-emit: pass a TypeScript-authored contract. Example: `mongo({ contract, url })`\n * - Emitted: pass `Contract` type explicitly. Example: `mongo<Contract>({ contractJson, url })`\n */\nexport default function mongo<\n TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>,\n>(options: MongoOptionsWithContract<TContract>): MongoClient<TContract>;\nexport default function mongo<\n TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>,\n>(options: MongoOptionsWithContractJson<TContract>): MongoClient<TContract>;\nexport default function mongo<\n TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>,\n>(options: MongoOptions<TContract>): MongoClient<TContract> {\n const contract = resolveContract(options);\n let binding = resolveOptionalMongoBinding(options);\n\n // `mongoQuery` calls its parameter `contractJson`, but accepts the validated\n // contract value here (it normalises both internally).\n const query = mongoQuery<TContract>({ contractJson: contract });\n\n // Single source of truth for the lifecycle. `runtimePromise` is the in-flight\n // or settled build; `closed` is the terminal state set by `close()`. A failed\n // build resets `runtimePromise` so a retry is possible (see test).\n let runtimePromise: Promise<MongoRuntime> | undefined;\n let closed = false;\n\n const buildRuntime = async (resolvedBinding: MongoBinding): Promise<MongoRuntime> => {\n const adapter = createMongoAdapter();\n const driver =\n resolvedBinding.kind === 'url'\n ? await MongoDriverImpl.fromConnection(resolvedBinding.url, resolvedBinding.dbName)\n : MongoDriverImpl.fromDb(resolvedBinding.client.db(resolvedBinding.dbName));\n return createMongoRuntime({\n adapter,\n driver,\n contract,\n targetId: 'mongo',\n ...(options.mode !== undefined ? { mode: options.mode } : {}),\n });\n };\n\n const getRuntime = (): Promise<MongoRuntime> => {\n if (closed) {\n return Promise.reject(new Error('Mongo client is closed'));\n }\n if (runtimePromise !== undefined) {\n return runtimePromise;\n }\n if (binding === undefined) {\n return Promise.reject(\n new Error(\n 'Mongo binding not configured. Pass url/uri+dbName/mongoClient+dbName/binding to mongo(...) or call db.connect({ ... }).',\n ),\n );\n }\n runtimePromise = buildRuntime(binding).catch((err) => {\n // Reset so a later connect()/runtime() can retry rather than always\n // re-throwing the cached failure.\n runtimePromise = undefined;\n throw err;\n });\n return runtimePromise;\n };\n\n const orm = mongoOrm<TContract>({\n contract,\n executor: {\n execute<Row>(plan: MongoQueryPlan<Row>) {\n async function* iterate(): AsyncGenerator<Row, void, unknown> {\n const runtime = await getRuntime();\n yield* runtime.execute(plan);\n }\n return new AsyncIterableResult(iterate());\n },\n },\n });\n\n return {\n orm,\n query,\n contract,\n\n async connect(bindingInput?: MongoBindingInput): Promise<MongoRuntime> {\n if (closed) {\n throw new Error('Mongo client is closed');\n }\n if (runtimePromise !== undefined) {\n throw new Error('Mongo client already connected');\n }\n if (bindingInput !== undefined) {\n binding = resolveMongoBinding(bindingInput);\n }\n if (binding === undefined) {\n throw new Error(\n 'Mongo binding not configured. Pass url/uri+dbName/mongoClient+dbName/binding to mongo(...) or call db.connect({ ... }).',\n );\n }\n return getRuntime();\n },\n\n runtime(): Promise<MongoRuntime> {\n return getRuntime();\n },\n\n async close(): Promise<void> {\n if (closed) return;\n closed = true;\n if (runtimePromise === undefined) return;\n // Await whatever the build resolved to. Swallow build failures because\n // the user's intent is \"release any resources we acquired\" — there is\n // nothing to close if the build never produced a runtime.\n try {\n const runtime = await runtimePromise;\n await runtime.close();\n } catch {\n // build failed; nothing to close.\n }\n },\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,26 +1,28 @@
1
1
  {
2
2
  "name": "@prisma-next/mongo",
3
- "version": "0.5.0-dev.4",
3
+ "version": "0.5.0-dev.6",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "One-package Mongo setup for Prisma Next",
7
7
  "dependencies": {
8
+ "mongodb": "^6.16.0",
8
9
  "pathe": "^2.0.3",
9
- "@prisma-next/adapter-mongo": "0.5.0-dev.4",
10
- "@prisma-next/contract": "0.5.0-dev.4",
11
- "@prisma-next/family-mongo": "0.5.0-dev.4",
12
- "@prisma-next/framework-components": "0.5.0-dev.4",
13
- "@prisma-next/driver-mongo": "0.5.0-dev.4",
14
- "@prisma-next/mongo-contract-psl": "0.5.0-dev.4",
15
- "@prisma-next/mongo-contract": "0.5.0-dev.4",
16
- "@prisma-next/config": "0.5.0-dev.4",
17
- "@prisma-next/mongo-contract-ts": "0.5.0-dev.4",
18
- "@prisma-next/mongo-query-builder": "0.5.0-dev.4",
19
- "@prisma-next/mongo-orm": "0.5.0-dev.4",
20
- "@prisma-next/mongo-runtime": "0.5.0-dev.4",
21
- "@prisma-next/target-mongo": "0.5.0-dev.4"
10
+ "@prisma-next/adapter-mongo": "0.5.0-dev.6",
11
+ "@prisma-next/family-mongo": "0.5.0-dev.6",
12
+ "@prisma-next/config": "0.5.0-dev.6",
13
+ "@prisma-next/contract": "0.5.0-dev.6",
14
+ "@prisma-next/driver-mongo": "0.5.0-dev.6",
15
+ "@prisma-next/mongo-contract": "0.5.0-dev.6",
16
+ "@prisma-next/framework-components": "0.5.0-dev.6",
17
+ "@prisma-next/mongo-query-builder": "0.5.0-dev.6",
18
+ "@prisma-next/mongo-orm": "0.5.0-dev.6",
19
+ "@prisma-next/mongo-runtime": "0.5.0-dev.6",
20
+ "@prisma-next/target-mongo": "0.5.0-dev.6",
21
+ "@prisma-next/mongo-contract-psl": "0.5.0-dev.6",
22
+ "@prisma-next/mongo-contract-ts": "0.5.0-dev.6"
22
23
  },
23
24
  "devDependencies": {
25
+ "mongodb-memory-server": "10.4.3",
24
26
  "tsdown": "0.18.4",
25
27
  "typescript": "5.9.3",
26
28
  "vitest": "4.0.17",
@@ -1,2 +1,9 @@
1
- export type { ConnectedMongoClient, MongoClient, MongoOptions } from '../runtime/mongo';
1
+ export type { MongoBinding, MongoBindingInput } from '../runtime/binding';
2
+ export type {
3
+ MongoClient,
4
+ MongoOptions,
5
+ MongoOptionsWithContract,
6
+ MongoOptionsWithContractJson,
7
+ MongoTargetId,
8
+ } from '../runtime/mongo';
2
9
  export { default } from '../runtime/mongo';
@@ -0,0 +1,181 @@
1
+ import type { MongoClient as MongoDriverClient } from 'mongodb';
2
+
3
+ export type MongoBinding =
4
+ | { readonly kind: 'url'; readonly url: string; readonly dbName: string }
5
+ | {
6
+ readonly kind: 'mongoClient';
7
+ readonly client: MongoDriverClient;
8
+ readonly dbName: string;
9
+ };
10
+
11
+ export type MongoBindingInput =
12
+ | {
13
+ readonly binding: MongoBinding;
14
+ readonly url?: never;
15
+ readonly uri?: never;
16
+ readonly dbName?: never;
17
+ readonly mongoClient?: never;
18
+ }
19
+ | {
20
+ readonly url: string;
21
+ readonly dbName?: string;
22
+ readonly binding?: never;
23
+ readonly uri?: never;
24
+ readonly mongoClient?: never;
25
+ }
26
+ | {
27
+ readonly uri: string;
28
+ readonly dbName: string;
29
+ readonly binding?: never;
30
+ readonly url?: never;
31
+ readonly mongoClient?: never;
32
+ }
33
+ | {
34
+ readonly mongoClient: MongoDriverClient;
35
+ readonly dbName: string;
36
+ readonly binding?: never;
37
+ readonly url?: never;
38
+ readonly uri?: never;
39
+ };
40
+
41
+ type MongoBindingFields = {
42
+ readonly binding?: MongoBinding;
43
+ readonly url?: string;
44
+ readonly uri?: string;
45
+ readonly dbName?: string;
46
+ readonly mongoClient?: MongoDriverClient;
47
+ };
48
+
49
+ function validateMongoUrl(url: string): URL {
50
+ const trimmed = url.trim();
51
+ if (trimmed.length === 0) {
52
+ throw new Error('Mongo URL must be a non-empty string');
53
+ }
54
+
55
+ let parsed: URL;
56
+ try {
57
+ parsed = new URL(trimmed);
58
+ } catch {
59
+ throw new Error('Mongo URL must be a valid URL');
60
+ }
61
+
62
+ if (parsed.protocol !== 'mongodb:' && parsed.protocol !== 'mongodb+srv:') {
63
+ throw new Error('Mongo URL must use mongodb:// or mongodb+srv://');
64
+ }
65
+
66
+ return parsed;
67
+ }
68
+
69
+ function extractDbNameFromUrl(parsed: URL): string | undefined {
70
+ // pathname is "/dbname" or "" — strip the leading slash. Anything past
71
+ // a second slash is invalid for our purposes (auth-source style paths).
72
+ const path = parsed.pathname.startsWith('/') ? parsed.pathname.slice(1) : parsed.pathname;
73
+ if (path.length === 0) {
74
+ return undefined;
75
+ }
76
+ const slash = path.indexOf('/');
77
+ return slash === -1 ? path : path.slice(0, slash);
78
+ }
79
+
80
+ export function resolveMongoBinding(options: MongoBindingInput): MongoBinding {
81
+ const providedCount =
82
+ Number(options.binding !== undefined) +
83
+ Number(options.url !== undefined) +
84
+ Number(options.uri !== undefined) +
85
+ Number(options.mongoClient !== undefined);
86
+
87
+ if (providedCount !== 1) {
88
+ throw new Error('Provide one binding input: binding, url, uri+dbName, or mongoClient+dbName');
89
+ }
90
+
91
+ if (options.binding !== undefined) {
92
+ return options.binding;
93
+ }
94
+
95
+ if (options.url !== undefined) {
96
+ const parsed = validateMongoUrl(options.url);
97
+ // An explicit, whitespace-only `dbName` is a user-error we'd rather
98
+ // surface loudly: silently falling back to the URL path would mask
99
+ // a typo or empty-template-string bug. Treat it the same as the
100
+ // `{ uri, dbName }` and `{ mongoClient, dbName }` paths, where an
101
+ // empty trimmed dbName is already a fast failure.
102
+ if (options.dbName !== undefined && options.dbName.trim().length === 0) {
103
+ throw new Error('Mongo binding via { url, dbName } requires a non-empty dbName');
104
+ }
105
+ const explicitDbName = options.dbName?.trim();
106
+ const dbName =
107
+ explicitDbName !== undefined && explicitDbName.length > 0
108
+ ? explicitDbName
109
+ : extractDbNameFromUrl(parsed);
110
+ if (dbName === undefined || dbName.length === 0) {
111
+ throw new Error(
112
+ 'Mongo URL must include a database name in its path (e.g. mongodb://host:27017/mydb), or pass dbName explicitly',
113
+ );
114
+ }
115
+ return { kind: 'url', url: options.url.trim(), dbName };
116
+ }
117
+
118
+ if (options.uri !== undefined) {
119
+ validateMongoUrl(options.uri);
120
+ const dbName = options.dbName?.trim();
121
+ if (dbName === undefined || dbName.length === 0) {
122
+ throw new Error('Mongo binding via { uri, dbName } requires a non-empty dbName');
123
+ }
124
+ return { kind: 'url', url: options.uri.trim(), dbName };
125
+ }
126
+
127
+ const mongoClient = options.mongoClient;
128
+ if (mongoClient === undefined) {
129
+ throw new Error('Invariant violation: expected mongo binding after validation');
130
+ }
131
+ const dbName = options.dbName?.trim();
132
+ if (dbName === undefined || dbName.length === 0) {
133
+ throw new Error('Mongo binding via { mongoClient, dbName } requires a non-empty dbName');
134
+ }
135
+ return { kind: 'mongoClient', client: mongoClient, dbName };
136
+ }
137
+
138
+ export function resolveOptionalMongoBinding(options: MongoBindingFields): MongoBinding | undefined {
139
+ const providedCount =
140
+ Number(options.binding !== undefined) +
141
+ Number(options.url !== undefined) +
142
+ Number(options.uri !== undefined) +
143
+ Number(options.mongoClient !== undefined);
144
+
145
+ if (providedCount === 0) {
146
+ return undefined;
147
+ }
148
+ // Defer the "exactly one" enforcement to `resolveMongoBinding`. We call it
149
+ // through a single branch per input field so the matching union member of
150
+ // `MongoBindingInput` is constructed explicitly — the previous
151
+ // `options as MongoBindingInput` cast hid drift between `MongoBindingFields`
152
+ // (any combination of optional inputs) and `MongoBindingInput` (exactly one).
153
+ if (providedCount !== 1) {
154
+ throw new Error('Provide one binding input: binding, url, uri+dbName, or mongoClient+dbName');
155
+ }
156
+ if (options.binding !== undefined) {
157
+ return resolveMongoBinding({ binding: options.binding });
158
+ }
159
+ if (options.url !== undefined) {
160
+ return resolveMongoBinding(
161
+ options.dbName !== undefined
162
+ ? { url: options.url, dbName: options.dbName }
163
+ : { url: options.url },
164
+ );
165
+ }
166
+ if (options.uri !== undefined) {
167
+ return resolveMongoBinding({ uri: options.uri, dbName: options.dbName ?? '' });
168
+ }
169
+ if (options.mongoClient !== undefined) {
170
+ return resolveMongoBinding({
171
+ mongoClient: options.mongoClient,
172
+ dbName: options.dbName ?? '',
173
+ });
174
+ }
175
+ // Unreachable: the `providedCount === 1` guard above plus the four
176
+ // branch checks cover every shape of `MongoBindingFields`. A bare
177
+ // `return undefined` here would silently mask a future
178
+ // `MongoBindingFields` extension that adds a fifth input, so we
179
+ // surface it as an invariant rather than a missing binding.
180
+ throw new Error('Invariant violation: expected one mongo binding branch');
181
+ }
@@ -1,60 +1,213 @@
1
1
  import { createMongoAdapter } from '@prisma-next/adapter-mongo';
2
- import { createMongoDriver } from '@prisma-next/driver-mongo';
2
+ import { MongoDriverImpl } from '@prisma-next/driver-mongo';
3
+ import { AsyncIterableResult } from '@prisma-next/framework-components/runtime';
3
4
  import type {
4
5
  MongoContract,
5
6
  MongoContractWithTypeMaps,
6
7
  MongoTypeMaps,
7
8
  } from '@prisma-next/mongo-contract';
8
9
  import { validateMongoContract } from '@prisma-next/mongo-contract';
10
+ import type { MongoOrmClient, MongoQueryPlan } from '@prisma-next/mongo-orm';
9
11
  import { mongoOrm } from '@prisma-next/mongo-orm';
10
12
  import { mongoQuery } from '@prisma-next/mongo-query-builder';
11
13
  import type { MongoRuntime } from '@prisma-next/mongo-runtime';
12
14
  import { createMongoRuntime } from '@prisma-next/mongo-runtime';
15
+ import {
16
+ type MongoBinding,
17
+ type MongoBindingInput,
18
+ resolveMongoBinding,
19
+ resolveOptionalMongoBinding,
20
+ } from './binding';
13
21
 
14
- export interface MongoOptions {
15
- readonly contractJson: unknown;
16
- }
22
+ export type MongoTargetId = 'mongo';
17
23
 
18
24
  export interface MongoClient<
19
25
  TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>,
20
26
  > {
27
+ readonly orm: MongoOrmClient<TContract>;
21
28
  readonly query: ReturnType<typeof mongoQuery<TContract>>;
22
- connect(uri: string, dbName: string): Promise<ConnectedMongoClient<TContract>>;
29
+ readonly contract: TContract;
30
+ connect(bindingInput?: MongoBindingInput): Promise<MongoRuntime>;
31
+ runtime(): Promise<MongoRuntime>;
32
+ close(): Promise<void>;
33
+ }
34
+
35
+ export interface MongoOptionsBase {
36
+ readonly mode?: 'strict' | 'permissive';
23
37
  }
24
38
 
25
- export interface ConnectedMongoClient<
39
+ export interface MongoBindingOptions {
40
+ readonly binding?: MongoBinding;
41
+ readonly url?: string;
42
+ readonly uri?: string;
43
+ readonly dbName?: string;
44
+ readonly mongoClient?: import('mongodb').MongoClient;
45
+ }
46
+
47
+ export type MongoOptionsWithContract<
26
48
  TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>,
27
- > {
28
- readonly orm: ReturnType<typeof mongoOrm<TContract>>;
29
- readonly runtime: MongoRuntime;
30
- readonly query: ReturnType<typeof mongoQuery<TContract>>;
31
- readonly contract: TContract;
32
- close(): Promise<void>;
49
+ > = MongoBindingOptions &
50
+ MongoOptionsBase & {
51
+ readonly contract: TContract;
52
+ readonly contractJson?: never;
53
+ };
54
+
55
+ /**
56
+ * `TContract` is a phantom parameter that drives explicit-type inference at the
57
+ * call site (e.g. `mongo<Contract>({ contractJson })`). It is not referenced in
58
+ * the type body — the contract value comes through `contractJson` at runtime.
59
+ */
60
+ export type MongoOptionsWithContractJson<
61
+ TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>,
62
+ > = MongoBindingOptions &
63
+ MongoOptionsBase & {
64
+ readonly contractJson: unknown;
65
+ readonly contract?: never;
66
+ /**
67
+ * @internal phantom field; never set at runtime, only references TContract
68
+ * so that strict tsc/biome don't flag the type parameter as unused.
69
+ */
70
+ readonly __contractPhantom?: TContract;
71
+ };
72
+
73
+ export type MongoOptions<
74
+ TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>,
75
+ > = MongoOptionsWithContract<TContract> | MongoOptionsWithContractJson<TContract>;
76
+
77
+ function hasContractJson<TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>>(
78
+ options: MongoOptions<TContract>,
79
+ ): options is MongoOptionsWithContractJson<TContract> {
80
+ return 'contractJson' in options;
33
81
  }
34
82
 
83
+ function resolveContract<TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>>(
84
+ options: MongoOptions<TContract>,
85
+ ): TContract {
86
+ const contractInput = hasContractJson(options) ? options.contractJson : options.contract;
87
+ return validateMongoContract<TContract>(contractInput).contract;
88
+ }
89
+
90
+ /**
91
+ * Creates a lazy Mongo client from either `contractJson` or a TypeScript-authored `contract`.
92
+ * The `orm` and `query` surfaces are available immediately; the underlying driver is connected
93
+ * on the first query (or the first explicit `connect()` call), mirroring the Postgres facade.
94
+ *
95
+ * - No-emit: pass a TypeScript-authored contract. Example: `mongo({ contract, url })`
96
+ * - Emitted: pass `Contract` type explicitly. Example: `mongo<Contract>({ contractJson, url })`
97
+ */
98
+ export default function mongo<
99
+ TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>,
100
+ >(options: MongoOptionsWithContract<TContract>): MongoClient<TContract>;
101
+ export default function mongo<
102
+ TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>,
103
+ >(options: MongoOptionsWithContractJson<TContract>): MongoClient<TContract>;
35
104
  export default function mongo<
36
105
  TContract extends MongoContractWithTypeMaps<MongoContract, MongoTypeMaps>,
37
- >(options: MongoOptions): MongoClient<TContract> {
38
- const { contract } = validateMongoContract<TContract>(options.contractJson);
106
+ >(options: MongoOptions<TContract>): MongoClient<TContract> {
107
+ const contract = resolveContract(options);
108
+ let binding = resolveOptionalMongoBinding(options);
109
+
110
+ // `mongoQuery` calls its parameter `contractJson`, but accepts the validated
111
+ // contract value here (it normalises both internally).
39
112
  const query = mongoQuery<TContract>({ contractJson: contract });
40
113
 
114
+ // Single source of truth for the lifecycle. `runtimePromise` is the in-flight
115
+ // or settled build; `closed` is the terminal state set by `close()`. A failed
116
+ // build resets `runtimePromise` so a retry is possible (see test).
117
+ let runtimePromise: Promise<MongoRuntime> | undefined;
118
+ let closed = false;
119
+
120
+ const buildRuntime = async (resolvedBinding: MongoBinding): Promise<MongoRuntime> => {
121
+ const adapter = createMongoAdapter();
122
+ const driver =
123
+ resolvedBinding.kind === 'url'
124
+ ? await MongoDriverImpl.fromConnection(resolvedBinding.url, resolvedBinding.dbName)
125
+ : MongoDriverImpl.fromDb(resolvedBinding.client.db(resolvedBinding.dbName));
126
+ return createMongoRuntime({
127
+ adapter,
128
+ driver,
129
+ contract,
130
+ targetId: 'mongo',
131
+ ...(options.mode !== undefined ? { mode: options.mode } : {}),
132
+ });
133
+ };
134
+
135
+ const getRuntime = (): Promise<MongoRuntime> => {
136
+ if (closed) {
137
+ return Promise.reject(new Error('Mongo client is closed'));
138
+ }
139
+ if (runtimePromise !== undefined) {
140
+ return runtimePromise;
141
+ }
142
+ if (binding === undefined) {
143
+ return Promise.reject(
144
+ new Error(
145
+ 'Mongo binding not configured. Pass url/uri+dbName/mongoClient+dbName/binding to mongo(...) or call db.connect({ ... }).',
146
+ ),
147
+ );
148
+ }
149
+ runtimePromise = buildRuntime(binding).catch((err) => {
150
+ // Reset so a later connect()/runtime() can retry rather than always
151
+ // re-throwing the cached failure.
152
+ runtimePromise = undefined;
153
+ throw err;
154
+ });
155
+ return runtimePromise;
156
+ };
157
+
158
+ const orm = mongoOrm<TContract>({
159
+ contract,
160
+ executor: {
161
+ execute<Row>(plan: MongoQueryPlan<Row>) {
162
+ async function* iterate(): AsyncGenerator<Row, void, unknown> {
163
+ const runtime = await getRuntime();
164
+ yield* runtime.execute(plan);
165
+ }
166
+ return new AsyncIterableResult(iterate());
167
+ },
168
+ },
169
+ });
170
+
41
171
  return {
172
+ orm,
42
173
  query,
43
- async connect(uri: string, dbName: string): Promise<ConnectedMongoClient<TContract>> {
44
- const adapter = createMongoAdapter();
45
- const driver = await createMongoDriver(uri, dbName);
46
- const runtime = createMongoRuntime({ adapter, driver, contract, targetId: 'mongo' });
47
- const orm = mongoOrm<TContract>({ contract, executor: runtime });
48
-
49
- return {
50
- orm,
51
- runtime,
52
- query,
53
- contract,
54
- async close() {
55
- await runtime.close();
56
- },
57
- };
174
+ contract,
175
+
176
+ async connect(bindingInput?: MongoBindingInput): Promise<MongoRuntime> {
177
+ if (closed) {
178
+ throw new Error('Mongo client is closed');
179
+ }
180
+ if (runtimePromise !== undefined) {
181
+ throw new Error('Mongo client already connected');
182
+ }
183
+ if (bindingInput !== undefined) {
184
+ binding = resolveMongoBinding(bindingInput);
185
+ }
186
+ if (binding === undefined) {
187
+ throw new Error(
188
+ 'Mongo binding not configured. Pass url/uri+dbName/mongoClient+dbName/binding to mongo(...) or call db.connect({ ... }).',
189
+ );
190
+ }
191
+ return getRuntime();
192
+ },
193
+
194
+ runtime(): Promise<MongoRuntime> {
195
+ return getRuntime();
196
+ },
197
+
198
+ async close(): Promise<void> {
199
+ if (closed) return;
200
+ closed = true;
201
+ if (runtimePromise === undefined) return;
202
+ // Await whatever the build resolved to. Swallow build failures because
203
+ // the user's intent is "release any resources we acquired" — there is
204
+ // nothing to close if the build never produced a runtime.
205
+ try {
206
+ const runtime = await runtimePromise;
207
+ await runtime.close();
208
+ } catch {
209
+ // build failed; nothing to close.
210
+ }
58
211
  },
59
212
  };
60
213
  }