@prisma-next/adapter-mongo 0.8.0 → 0.9.0-dev.1

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,9 +1,11 @@
1
+ import { t as createMongoAdapter } from "./mongo-adapter-JuKx_-h9.mjs";
1
2
  import { Db, Document, MongoClient } from "mongodb";
2
3
  import { CollModCommand, CreateCollectionCommand, CreateIndexCommand, DropCollectionCommand, DropIndexCommand, ListCollectionsCommand, ListIndexesCommand, MongoDdlCommandVisitor, MongoInspectionCommandVisitor } from "@prisma-next/mongo-query-ast/control";
3
4
  import { MongoSchemaIR } from "@prisma-next/mongo-schema-ir";
4
- import { MongoRunnerDependencies } from "@prisma-next/target-mongo/control";
5
- import { ControlAdapterDescriptor, ControlDriverInstance, ControlFamilyInstance } from "@prisma-next/framework-components/control";
6
- import { MongoDriver } from "@prisma-next/mongo-lowering";
5
+ import { MongoControlAdapter, MongoControlAdapterDescriptor } from "@prisma-next/family-mongo/control-adapter";
6
+ import { ContractMarkerRecord } from "@prisma-next/contract/types";
7
+ import { ControlDriverInstance, ControlFamilyInstance } from "@prisma-next/framework-components/control";
8
+ import { MongoAdapter, MongoDriver } from "@prisma-next/mongo-lowering";
7
9
 
8
10
  //#region src/core/command-executor.d.ts
9
11
  declare class MongoCommandExecutor implements MongoDdlCommandVisitor<Promise<void>> {
@@ -25,6 +27,92 @@ declare class MongoInspectionExecutor implements MongoInspectionCommandVisitor<P
25
27
  //#region src/core/introspect-schema.d.ts
26
28
  declare function introspectSchema(db: Db): Promise<MongoSchemaIR>;
27
29
  //#endregion
30
+ //#region src/core/marker-ledger.d.ts
31
+ /**
32
+ * Reads the marker document for the given contract space, or returns
33
+ * `null` if no marker has been written for that space yet. Each space
34
+ * owns one row keyed by `_id: <space>` — see ADR 212 for the per-space
35
+ * mechanism this enables.
36
+ */
37
+ declare function readMarker(db: Db, space: string): Promise<ContractMarkerRecord | null>;
38
+ /**
39
+ * Reads every marker doc in the collection (one per contract space)
40
+ * and returns them keyed by `space`. Used by the per-space verifier
41
+ * to detect marker-vs-on-disk drift and orphan marker rows. Returns
42
+ * an empty map when no marker docs have been written yet.
43
+ *
44
+ * Marker docs are keyed by `_id: <space>` (string); ledger entries
45
+ * live in the same collection but use a driver-generated `ObjectId`
46
+ * `_id` plus `type: 'ledger'`. The filter selects string-keyed docs
47
+ * with a `space` field, which excludes ledger entries by construction.
48
+ */
49
+ declare function readAllMarkers(db: Db): Promise<ReadonlyMap<string, ContractMarkerRecord>>;
50
+ declare function initMarker(db: Db, space: string, destination: {
51
+ readonly storageHash: string;
52
+ readonly profileHash: string;
53
+ readonly invariants?: readonly string[];
54
+ }): Promise<void>;
55
+ /**
56
+ * Updates the marker doc for the given space atomically (CAS on
57
+ * `expectedFrom`).
58
+ *
59
+ * `destination.invariants`:
60
+ * - `undefined` → existing field left untouched.
61
+ * - explicit value → merged into the existing field server-side via an
62
+ * aggregation pipeline (`$setUnion + $sortArray`), atomic at the
63
+ * document level. `[]` is a no-op merge.
64
+ */
65
+ declare function updateMarker(db: Db, space: string, expectedFrom: string, destination: {
66
+ readonly storageHash: string;
67
+ readonly profileHash: string;
68
+ readonly invariants?: readonly string[];
69
+ }): Promise<boolean>;
70
+ /**
71
+ * Appends a ledger entry for the given space. Ledger entries co-exist
72
+ * with marker docs in the same collection; marker docs use `_id: <space>`
73
+ * (string), ledger entries use `type: 'ledger'` plus a driver-generated
74
+ * ObjectId. Reads partition the two by filter shape.
75
+ *
76
+ * The same `edgeId` may legitimately recur across different spaces (e.g.
77
+ * a synthetic ∅→head edge on first apply), so the ledger key is
78
+ * `(space, edgeId)` — the doc carries `space` for partitioned reads.
79
+ */
80
+ declare function writeLedgerEntry(db: Db, space: string, entry: {
81
+ readonly edgeId: string;
82
+ readonly from: string;
83
+ readonly to: string;
84
+ }): Promise<void>;
85
+ //#endregion
86
+ //#region src/core/mongo-control-adapter.d.ts
87
+ /**
88
+ * Mongo control adapter for control-plane operations like introspection
89
+ * and marker-ledger CAS. Implements the family-level `MongoControlAdapter`
90
+ * SPI by extracting the underlying `Db` from the framework-shaped driver
91
+ * and forwarding to the wire-level helpers in this package.
92
+ */
93
+ declare class MongoControlAdapterImpl implements MongoControlAdapter<'mongo'> {
94
+ readonly familyId: "mongo";
95
+ readonly targetId: "mongo";
96
+ readMarker(driver: ControlDriverInstance<'mongo', 'mongo'>, space: string): Promise<ContractMarkerRecord | null>;
97
+ readAllMarkers(driver: ControlDriverInstance<'mongo', 'mongo'>): Promise<ReadonlyMap<string, ContractMarkerRecord>>;
98
+ initMarker(driver: ControlDriverInstance<'mongo', 'mongo'>, space: string, destination: {
99
+ readonly storageHash: string;
100
+ readonly profileHash: string;
101
+ readonly invariants?: readonly string[];
102
+ }): Promise<void>;
103
+ updateMarker(driver: ControlDriverInstance<'mongo', 'mongo'>, space: string, expectedFrom: string, destination: {
104
+ readonly storageHash: string;
105
+ readonly profileHash: string;
106
+ readonly invariants?: readonly string[];
107
+ }): Promise<boolean>;
108
+ writeLedgerEntry(driver: ControlDriverInstance<'mongo', 'mongo'>, space: string, entry: {
109
+ readonly edgeId: string;
110
+ readonly from: string;
111
+ readonly to: string;
112
+ }): Promise<void>;
113
+ introspectSchema(driver: ControlDriverInstance<'mongo', 'mongo'>): Promise<MongoSchemaIR>;
114
+ }
115
+ //#endregion
28
116
  //#region src/core/mongo-control-driver.d.ts
29
117
  interface MongoControlDriverInstance extends ControlDriverInstance<'mongo', 'mongo'> {
30
118
  readonly db: Db;
@@ -33,10 +121,51 @@ declare function createMongoControlDriver(db: Db, client: MongoClient): MongoCon
33
121
  //#endregion
34
122
  //#region src/core/runner-deps.d.ts
35
123
  declare function extractDb(driver: ControlDriverInstance<'mongo', 'mongo'>): Db;
36
- declare function createMongoRunnerDeps(controlDriver: ControlDriverInstance<'mongo', 'mongo'>, driver: MongoDriver, family: ControlFamilyInstance<'mongo', MongoSchemaIR>): MongoRunnerDependencies;
124
+ /**
125
+ * Marker / ledger operations the Mongo runner depends on. Every method
126
+ * takes a `space` parameter so each loaded contract space addresses its
127
+ * own marker row independently — see ADR 212 for the per-space
128
+ * mechanism.
129
+ */
130
+ interface MarkerOperations {
131
+ readMarker(space: string): Promise<ContractMarkerRecord | null>;
132
+ initMarker(space: string, destination: {
133
+ readonly storageHash: string;
134
+ readonly profileHash: string;
135
+ readonly invariants?: readonly string[];
136
+ }): Promise<void>;
137
+ updateMarker(space: string, expectedFrom: string, destination: {
138
+ readonly storageHash: string;
139
+ readonly profileHash: string;
140
+ readonly invariants?: readonly string[];
141
+ }): Promise<boolean>;
142
+ writeLedgerEntry(space: string, entry: {
143
+ readonly edgeId: string;
144
+ readonly from: string;
145
+ readonly to: string;
146
+ }): Promise<void>;
147
+ }
148
+ interface MongoRunnerDependencies {
149
+ readonly commandExecutor: MongoDdlCommandVisitor<Promise<void>>;
150
+ readonly inspectionExecutor: MongoInspectionCommandVisitor<Promise<Record<string, unknown>[]>>;
151
+ readonly adapter: MongoAdapter;
152
+ readonly driver: MongoDriver;
153
+ readonly markerOps: MarkerOperations;
154
+ readonly introspectSchema: () => Promise<MongoSchemaIR>;
155
+ }
156
+ /**
157
+ * Build the runner-dependencies envelope. `controlAdapter` is the
158
+ * dispatch surface for wire-level Mongo CAS operations (marker reads,
159
+ * marker advances, ledger appends, introspection); the envelope's
160
+ * `markerOps` shim simply forwards each call through it. When the
161
+ * caller already has a `MongoControlAdapter` on the control stack it
162
+ * can pass it in; otherwise a default `MongoControlAdapterImpl` is
163
+ * constructed locally.
164
+ */
165
+ declare function createMongoRunnerDeps(controlDriver: ControlDriverInstance<'mongo', 'mongo'>, driver: MongoDriver, _family: ControlFamilyInstance<'mongo', MongoSchemaIR>, controlAdapter?: MongoControlAdapter<'mongo'>): MongoRunnerDependencies;
37
166
  //#endregion
38
167
  //#region src/exports/control.d.ts
39
- declare const mongoAdapterDescriptor: ControlAdapterDescriptor<'mongo', 'mongo'>;
168
+ declare const mongoAdapterDescriptor: MongoControlAdapterDescriptor<'mongo'>;
40
169
  //#endregion
41
- export { MongoCommandExecutor, type MongoControlDriverInstance, MongoInspectionExecutor, createMongoControlDriver, createMongoRunnerDeps, mongoAdapterDescriptor as default, extractDb, introspectSchema };
170
+ export { type MarkerOperations, MongoCommandExecutor, MongoControlAdapterImpl, type MongoControlDriverInstance, MongoInspectionExecutor, type MongoRunnerDependencies, createMongoAdapter, createMongoControlDriver, createMongoRunnerDeps, mongoAdapterDescriptor as default, mongoAdapterDescriptor, extractDb, initMarker, introspectSchema, readAllMarkers, readMarker, updateMarker, writeLedgerEntry };
42
171
  //# sourceMappingURL=control.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"control.d.mts","names":[],"sources":["../src/core/command-executor.ts","../src/core/introspect-schema.ts","../src/core/mongo-control-driver.ts","../src/core/runner-deps.ts","../src/exports/control.ts"],"mappings":";;;;;;;;cAca,oBAAA,YAAgC,sBAAA,CAAuB,OAAA;EAAA,iBACrC,EAAA;cAAA,EAAA,EAAI,EAAA;EAE3B,WAAA,CAAY,GAAA,EAAK,kBAAA,GAAqB,OAAA;EAmBtC,SAAA,CAAU,GAAA,EAAK,gBAAA,GAAmB,OAAA;EAIlC,gBAAA,CAAiB,GAAA,EAAK,uBAAA,GAA0B,OAAA;EAgBhD,cAAA,CAAe,GAAA,EAAK,qBAAA,GAAwB,OAAA;EAI5C,OAAA,CAAQ,GAAA,EAAK,cAAA,GAAiB,OAAA;AAAA;AAAA,cAWzB,uBAAA,YAAmC,6BAAA,CAA8B,OAAA,CAAQ,QAAA;EAAA,iBACvD,EAAA;cAAA,EAAA,EAAI,EAAA;EAE3B,WAAA,CAAY,GAAA,EAAK,kBAAA,GAAqB,OAAA,CAAQ,QAAA;EAW9C,eAAA,CAAgB,IAAA,EAAM,sBAAA,GAAyB,OAAA,CAAQ,QAAA;AAAA;;;iBCMzC,gBAAA,CAAiB,EAAA,EAAI,EAAA,GAAK,OAAA,CAAQ,aAAA;;;UCxFvC,0BAAA,SAAmC,qBAAA;EAAA,SACzC,EAAA,EAAI,EAAA;AAAA;AAAA,iBAuBC,wBAAA,CAAyB,EAAA,EAAI,EAAA,EAAI,MAAA,EAAQ,WAAA,GAAc,0BAAA;;;iBCVvD,SAAA,CAAU,MAAA,EAAQ,qBAAA,qBAA0C,EAAA;AAAA,iBAW5D,qBAAA,CACd,aAAA,EAAe,qBAAA,oBACf,MAAA,EAAQ,WAAA,EACR,MAAA,EAAQ,qBAAA,UAA+B,aAAA,IACtC,uBAAA;;;cCpBG,sBAAA,EAAwB,wBAAA"}
1
+ {"version":3,"file":"control.d.mts","names":[],"sources":["../src/core/command-executor.ts","../src/core/introspect-schema.ts","../src/core/marker-ledger.ts","../src/core/mongo-control-adapter.ts","../src/core/mongo-control-driver.ts","../src/core/runner-deps.ts","../src/exports/control.ts"],"mappings":"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"}
package/dist/control.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  import { r as mongoCodecDescriptors, t as createMongoAdapter } from "./mongo-adapter-DfCmEYHR.mjs";
2
+ import { RawAggregateCommand, RawFindOneAndUpdateCommand, RawInsertOneCommand } from "@prisma-next/mongo-query-ast/execution";
2
3
  import { MongoServerError } from "mongodb";
3
4
  import { keysToKeySpec } from "@prisma-next/mongo-query-ast/control";
4
5
  import { MongoSchemaCollection, MongoSchemaCollectionOptions, MongoSchemaIR, MongoSchemaIndex, MongoSchemaValidator } from "@prisma-next/mongo-schema-ir";
5
- import { initMarker, readMarker, updateMarker, writeLedgerEntry } from "@prisma-next/target-mongo/control";
6
+ import { type } from "arktype";
6
7
  //#region src/core/command-executor.ts
7
8
  var MongoCommandExecutor = class {
8
9
  db;
@@ -162,6 +163,219 @@ async function introspectSchema(db) {
162
163
  return new MongoSchemaIR(collections);
163
164
  }
164
165
  //#endregion
166
+ //#region src/core/marker-ledger.ts
167
+ const COLLECTION = "_prisma_migrations";
168
+ /**
169
+ * Marker doc shape.
170
+ *
171
+ * Same fields as the SQL marker row but camelCase + Mongo-native types:
172
+ * `Date` is BSON-hydrated, `meta` is a native object (not JSON-stringified),
173
+ * `_id` and any extension fields are tolerated. `invariants?` is optional —
174
+ * absent reads as `[]` (schemaless default); present-but-malformed throws.
175
+ *
176
+ * `space` is required: every marker doc is keyed by its space id (`_id`)
177
+ * and stamped with a matching `space` field for partitioned reads.
178
+ */
179
+ const MongoMarkerDocSchema = type({
180
+ space: "string",
181
+ storageHash: "string",
182
+ profileHash: "string",
183
+ "contractJson?": "unknown | null",
184
+ "canonicalVersion?": "number | null",
185
+ "updatedAt?": "Date",
186
+ "appTag?": "string | null",
187
+ "meta?": type({ "[string]": "unknown" }).or("null"),
188
+ "invariants?": type("string").array(),
189
+ "+": "delete"
190
+ });
191
+ function parseMongoMarkerDoc(doc) {
192
+ const result = MongoMarkerDocSchema(doc);
193
+ if (result instanceof type.errors) throw new Error(`Invalid marker doc on ${COLLECTION}: ${result.summary}`);
194
+ return {
195
+ storageHash: result.storageHash,
196
+ profileHash: result.profileHash,
197
+ contractJson: result.contractJson ?? null,
198
+ canonicalVersion: result.canonicalVersion ?? null,
199
+ updatedAt: result.updatedAt ?? /* @__PURE__ */ new Date(),
200
+ appTag: result.appTag ?? null,
201
+ meta: result.meta ?? {},
202
+ invariants: result.invariants ?? []
203
+ };
204
+ }
205
+ async function executeAggregate(db, cmd) {
206
+ return db.collection(cmd.collection).aggregate(cmd.pipeline).toArray();
207
+ }
208
+ async function executeInsertOne(db, cmd) {
209
+ await db.collection(cmd.collection).insertOne(cmd.document);
210
+ }
211
+ async function executeFindOneAndUpdate(db, cmd) {
212
+ return db.collection(cmd.collection).findOneAndUpdate(cmd.filter, cmd.update, { upsert: cmd.upsert });
213
+ }
214
+ /**
215
+ * Reads the marker document for the given contract space, or returns
216
+ * `null` if no marker has been written for that space yet. Each space
217
+ * owns one row keyed by `_id: <space>` — see ADR 212 for the per-space
218
+ * mechanism this enables.
219
+ */
220
+ async function readMarker(db, space) {
221
+ const doc = (await executeAggregate(db, new RawAggregateCommand(COLLECTION, [{ $match: {
222
+ _id: space,
223
+ space
224
+ } }, { $limit: 1 }])))[0];
225
+ if (!doc) return null;
226
+ return parseMongoMarkerDoc(doc);
227
+ }
228
+ /**
229
+ * Reads every marker doc in the collection (one per contract space)
230
+ * and returns them keyed by `space`. Used by the per-space verifier
231
+ * to detect marker-vs-on-disk drift and orphan marker rows. Returns
232
+ * an empty map when no marker docs have been written yet.
233
+ *
234
+ * Marker docs are keyed by `_id: <space>` (string); ledger entries
235
+ * live in the same collection but use a driver-generated `ObjectId`
236
+ * `_id` plus `type: 'ledger'`. The filter selects string-keyed docs
237
+ * with a `space` field, which excludes ledger entries by construction.
238
+ */
239
+ async function readAllMarkers(db) {
240
+ const docs = await executeAggregate(db, new RawAggregateCommand(COLLECTION, [{ $match: {
241
+ _id: { $type: "string" },
242
+ space: { $type: "string" },
243
+ $expr: { $eq: ["$_id", "$space"] }
244
+ } }]));
245
+ const out = /* @__PURE__ */ new Map();
246
+ for (const doc of docs) {
247
+ const space = doc["space"];
248
+ /* v8 ignore next -- @preserve type-narrowing guard: the $match stage above filters on `space: { $type: 'string' }`, so this branch is unreachable at runtime. The check exists so the `out.set(space, ...)` call below can accept `string`. */
249
+ if (typeof space !== "string") continue;
250
+ out.set(space, parseMongoMarkerDoc(doc));
251
+ }
252
+ return out;
253
+ }
254
+ async function initMarker(db, space, destination) {
255
+ await executeInsertOne(db, new RawInsertOneCommand(COLLECTION, {
256
+ _id: space,
257
+ space,
258
+ storageHash: destination.storageHash,
259
+ profileHash: destination.profileHash,
260
+ contractJson: null,
261
+ canonicalVersion: null,
262
+ updatedAt: /* @__PURE__ */ new Date(),
263
+ appTag: null,
264
+ meta: {},
265
+ invariants: destination.invariants ?? []
266
+ }));
267
+ }
268
+ /**
269
+ * Updates the marker doc for the given space atomically (CAS on
270
+ * `expectedFrom`).
271
+ *
272
+ * `destination.invariants`:
273
+ * - `undefined` → existing field left untouched.
274
+ * - explicit value → merged into the existing field server-side via an
275
+ * aggregation pipeline (`$setUnion + $sortArray`), atomic at the
276
+ * document level. `[]` is a no-op merge.
277
+ */
278
+ async function updateMarker(db, space, expectedFrom, destination) {
279
+ const setBase = {
280
+ storageHash: destination.storageHash,
281
+ profileHash: destination.profileHash,
282
+ updatedAt: /* @__PURE__ */ new Date()
283
+ };
284
+ const update = destination.invariants === void 0 ? { $set: setBase } : [{ $set: {
285
+ ...setBase,
286
+ invariants: { $sortArray: {
287
+ input: { $setUnion: [{ $ifNull: ["$invariants", []] }, destination.invariants] },
288
+ sortBy: 1
289
+ } }
290
+ } }];
291
+ return await executeFindOneAndUpdate(db, new RawFindOneAndUpdateCommand(COLLECTION, {
292
+ _id: space,
293
+ space,
294
+ storageHash: expectedFrom
295
+ }, update, false)) !== null;
296
+ }
297
+ /**
298
+ * Appends a ledger entry for the given space. Ledger entries co-exist
299
+ * with marker docs in the same collection; marker docs use `_id: <space>`
300
+ * (string), ledger entries use `type: 'ledger'` plus a driver-generated
301
+ * ObjectId. Reads partition the two by filter shape.
302
+ *
303
+ * The same `edgeId` may legitimately recur across different spaces (e.g.
304
+ * a synthetic ∅→head edge on first apply), so the ledger key is
305
+ * `(space, edgeId)` — the doc carries `space` for partitioned reads.
306
+ */
307
+ async function writeLedgerEntry(db, space, entry) {
308
+ await executeInsertOne(db, new RawInsertOneCommand(COLLECTION, {
309
+ type: "ledger",
310
+ space,
311
+ edgeId: entry.edgeId,
312
+ from: entry.from,
313
+ to: entry.to,
314
+ appliedAt: /* @__PURE__ */ new Date()
315
+ }));
316
+ }
317
+ //#endregion
318
+ //#region src/core/runner-deps.ts
319
+ function extractDb(driver) {
320
+ const mongoDriver = driver;
321
+ if (!mongoDriver.db) throw new Error("Mongo control driver does not expose a db property. Use mongoControlDriver.create() from `@prisma-next/driver-mongo/control`.");
322
+ return mongoDriver.db;
323
+ }
324
+ /**
325
+ * Build the runner-dependencies envelope. `controlAdapter` is the
326
+ * dispatch surface for wire-level Mongo CAS operations (marker reads,
327
+ * marker advances, ledger appends, introspection); the envelope's
328
+ * `markerOps` shim simply forwards each call through it. When the
329
+ * caller already has a `MongoControlAdapter` on the control stack it
330
+ * can pass it in; otherwise a default `MongoControlAdapterImpl` is
331
+ * constructed locally.
332
+ */
333
+ function createMongoRunnerDeps(controlDriver, driver, _family, controlAdapter = new MongoControlAdapterImpl()) {
334
+ return {
335
+ commandExecutor: new MongoCommandExecutor(extractDb(controlDriver)),
336
+ inspectionExecutor: new MongoInspectionExecutor(extractDb(controlDriver)),
337
+ adapter: createMongoAdapter(),
338
+ driver,
339
+ markerOps: {
340
+ readMarker: (space) => controlAdapter.readMarker(controlDriver, space),
341
+ initMarker: (space, dest) => controlAdapter.initMarker(controlDriver, space, dest),
342
+ updateMarker: (space, expectedFrom, dest) => controlAdapter.updateMarker(controlDriver, space, expectedFrom, dest),
343
+ writeLedgerEntry: (space, entry) => controlAdapter.writeLedgerEntry(controlDriver, space, entry)
344
+ },
345
+ introspectSchema: () => controlAdapter.introspectSchema(controlDriver)
346
+ };
347
+ }
348
+ //#endregion
349
+ //#region src/core/mongo-control-adapter.ts
350
+ /**
351
+ * Mongo control adapter for control-plane operations like introspection
352
+ * and marker-ledger CAS. Implements the family-level `MongoControlAdapter`
353
+ * SPI by extracting the underlying `Db` from the framework-shaped driver
354
+ * and forwarding to the wire-level helpers in this package.
355
+ */
356
+ var MongoControlAdapterImpl = class {
357
+ familyId = "mongo";
358
+ targetId = "mongo";
359
+ async readMarker(driver, space) {
360
+ return readMarker(extractDb(driver), space);
361
+ }
362
+ async readAllMarkers(driver) {
363
+ return readAllMarkers(extractDb(driver));
364
+ }
365
+ async initMarker(driver, space, destination) {
366
+ await initMarker(extractDb(driver), space, destination);
367
+ }
368
+ async updateMarker(driver, space, expectedFrom, destination) {
369
+ return updateMarker(extractDb(driver), space, expectedFrom, destination);
370
+ }
371
+ async writeLedgerEntry(driver, space, entry) {
372
+ await writeLedgerEntry(extractDb(driver), space, entry);
373
+ }
374
+ async introspectSchema(driver) {
375
+ return introspectSchema(extractDb(driver));
376
+ }
377
+ };
378
+ //#endregion
165
379
  //#region src/core/mongo-control-driver.ts
166
380
  var MongoControlDriverImpl = class {
167
381
  familyId = "mongo";
@@ -183,29 +397,6 @@ function createMongoControlDriver(db, client) {
183
397
  return new MongoControlDriverImpl(db, client);
184
398
  }
185
399
  //#endregion
186
- //#region src/core/runner-deps.ts
187
- function extractDb(driver) {
188
- const mongoDriver = driver;
189
- if (!mongoDriver.db) throw new Error("Mongo control driver does not expose a db property. Use mongoControlDriver.create() from `@prisma-next/driver-mongo/control`.");
190
- return mongoDriver.db;
191
- }
192
- function createMongoRunnerDeps(controlDriver, driver, family) {
193
- const db = extractDb(controlDriver);
194
- return {
195
- commandExecutor: new MongoCommandExecutor(db),
196
- inspectionExecutor: new MongoInspectionExecutor(db),
197
- adapter: createMongoAdapter(),
198
- driver,
199
- markerOps: {
200
- readMarker: (space) => readMarker(db, space),
201
- initMarker: (space, dest) => initMarker(db, space, dest),
202
- updateMarker: (space, expectedFrom, dest) => updateMarker(db, space, expectedFrom, dest),
203
- writeLedgerEntry: (space, entry) => writeLedgerEntry(db, space, entry)
204
- },
205
- introspectSchema: () => family.introspect({ driver: controlDriver })
206
- };
207
- }
208
- //#endregion
209
400
  //#region src/exports/control.ts
210
401
  const mongoAdapterDescriptor = {
211
402
  kind: "adapter",
@@ -235,13 +426,10 @@ const mongoAdapterDescriptor = {
235
426
  }]
236
427
  } },
237
428
  create(_stack) {
238
- return {
239
- familyId: "mongo",
240
- targetId: "mongo"
241
- };
429
+ return new MongoControlAdapterImpl();
242
430
  }
243
431
  };
244
432
  //#endregion
245
- export { MongoCommandExecutor, MongoInspectionExecutor, createMongoControlDriver, createMongoRunnerDeps, mongoAdapterDescriptor as default, extractDb, introspectSchema };
433
+ export { MongoCommandExecutor, MongoControlAdapterImpl, MongoInspectionExecutor, createMongoAdapter, createMongoControlDriver, createMongoRunnerDeps, mongoAdapterDescriptor as default, mongoAdapterDescriptor, extractDb, initMarker, introspectSchema, readAllMarkers, readMarker, updateMarker, writeLedgerEntry };
246
434
 
247
435
  //# sourceMappingURL=control.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"control.mjs","names":["#client"],"sources":["../src/core/command-executor.ts","../src/core/introspect-schema.ts","../src/core/mongo-control-driver.ts","../src/core/runner-deps.ts","../src/exports/control.ts"],"sourcesContent":["import type {\n CollModCommand,\n CreateCollectionCommand,\n CreateIndexCommand,\n DropCollectionCommand,\n DropIndexCommand,\n ListCollectionsCommand,\n ListIndexesCommand,\n MongoDdlCommandVisitor,\n MongoInspectionCommandVisitor,\n} from '@prisma-next/mongo-query-ast/control';\nimport { keysToKeySpec } from '@prisma-next/mongo-query-ast/control';\nimport { type Db, type Document, MongoServerError } from 'mongodb';\n\nexport class MongoCommandExecutor implements MongoDdlCommandVisitor<Promise<void>> {\n constructor(private readonly db: Db) {}\n\n async createIndex(cmd: CreateIndexCommand): Promise<void> {\n const keySpec: Document = keysToKeySpec(cmd.keys);\n const options: Record<string, unknown> = {};\n if (cmd.unique !== undefined) options['unique'] = cmd.unique;\n if (cmd.sparse !== undefined) options['sparse'] = cmd.sparse;\n if (cmd.expireAfterSeconds !== undefined)\n options['expireAfterSeconds'] = cmd.expireAfterSeconds;\n if (cmd.partialFilterExpression !== undefined)\n options['partialFilterExpression'] = cmd.partialFilterExpression;\n if (cmd.name !== undefined) options['name'] = cmd.name;\n if (cmd.wildcardProjection !== undefined)\n options['wildcardProjection'] = cmd.wildcardProjection;\n if (cmd.collation !== undefined) options['collation'] = cmd.collation;\n if (cmd.weights !== undefined) options['weights'] = cmd.weights;\n if (cmd.default_language !== undefined) options['default_language'] = cmd.default_language;\n if (cmd.language_override !== undefined) options['language_override'] = cmd.language_override;\n await this.db.collection(cmd.collection).createIndex(keySpec, options);\n }\n\n async dropIndex(cmd: DropIndexCommand): Promise<void> {\n await this.db.collection(cmd.collection).dropIndex(cmd.name);\n }\n\n async createCollection(cmd: CreateCollectionCommand): Promise<void> {\n const options: Record<string, unknown> = {};\n if (cmd.capped !== undefined) options['capped'] = cmd.capped;\n if (cmd.size !== undefined) options['size'] = cmd.size;\n if (cmd.max !== undefined) options['max'] = cmd.max;\n if (cmd.timeseries !== undefined) options['timeseries'] = cmd.timeseries;\n if (cmd.collation !== undefined) options['collation'] = cmd.collation;\n if (cmd.clusteredIndex !== undefined) options['clusteredIndex'] = cmd.clusteredIndex;\n if (cmd.validator !== undefined) options['validator'] = cmd.validator;\n if (cmd.validationLevel !== undefined) options['validationLevel'] = cmd.validationLevel;\n if (cmd.validationAction !== undefined) options['validationAction'] = cmd.validationAction;\n if (cmd.changeStreamPreAndPostImages !== undefined)\n options['changeStreamPreAndPostImages'] = cmd.changeStreamPreAndPostImages;\n await this.db.createCollection(cmd.collection, options);\n }\n\n async dropCollection(cmd: DropCollectionCommand): Promise<void> {\n await this.db.collection(cmd.collection).drop();\n }\n\n async collMod(cmd: CollModCommand): Promise<void> {\n const command: Record<string, unknown> = { collMod: cmd.collection };\n if (cmd.validator !== undefined) command['validator'] = cmd.validator;\n if (cmd.validationLevel !== undefined) command['validationLevel'] = cmd.validationLevel;\n if (cmd.validationAction !== undefined) command['validationAction'] = cmd.validationAction;\n if (cmd.changeStreamPreAndPostImages !== undefined)\n command['changeStreamPreAndPostImages'] = cmd.changeStreamPreAndPostImages;\n await this.db.command(command);\n }\n}\n\nexport class MongoInspectionExecutor implements MongoInspectionCommandVisitor<Promise<Document[]>> {\n constructor(private readonly db: Db) {}\n\n async listIndexes(cmd: ListIndexesCommand): Promise<Document[]> {\n try {\n return await this.db.collection(cmd.collection).listIndexes().toArray();\n } catch (error: unknown) {\n if (error instanceof MongoServerError && error.code === 26) {\n return [];\n }\n throw error;\n }\n }\n\n async listCollections(_cmd: ListCollectionsCommand): Promise<Document[]> {\n return this.db.listCollections().toArray();\n }\n}\n","import type { MongoIndexKey, MongoIndexKeyDirection } from '@prisma-next/mongo-contract';\nimport {\n MongoSchemaCollection,\n MongoSchemaCollectionOptions,\n MongoSchemaIndex,\n MongoSchemaIR,\n MongoSchemaValidator,\n} from '@prisma-next/mongo-schema-ir';\nimport type { Db, Document } from 'mongodb';\n\nconst PRISMA_MIGRATIONS_COLLECTION = '_prisma_migrations';\n\nfunction parseIndexKeys(keySpec: Record<string, unknown>): MongoIndexKey[] {\n const keys: MongoIndexKey[] = [];\n for (const [field, direction] of Object.entries(keySpec)) {\n keys.push({ field, direction: direction as MongoIndexKeyDirection });\n }\n return keys;\n}\n\n/**\n * Exported for unit tests to exercise the defensive `!key` guard; not part of\n * the public API. Callers in this package use it via the `introspectSchema`\n * pipeline only.\n */\nexport function isDefaultIdIndex(doc: Document): boolean {\n const key = doc['key'] as Record<string, unknown> | undefined;\n if (!key) return false;\n const entries = Object.entries(key);\n return entries.length === 1 && entries[0]?.[0] === '_id' && entries[0]?.[1] === 1;\n}\n\nfunction parseIndex(doc: Document): MongoSchemaIndex {\n const keySpec = doc['key'] as Record<string, unknown>;\n return new MongoSchemaIndex({\n keys: parseIndexKeys(keySpec),\n unique: doc['unique'] as boolean | undefined,\n sparse: doc['sparse'] as boolean | undefined,\n expireAfterSeconds: doc['expireAfterSeconds'] as number | undefined,\n partialFilterExpression: doc['partialFilterExpression'] as Record<string, unknown> | undefined,\n wildcardProjection: doc['wildcardProjection'] as Record<string, 0 | 1> | undefined,\n collation: doc['collation'] as Record<string, unknown> | undefined,\n weights: doc['weights'] as Record<string, number> | undefined,\n default_language: doc['default_language'] as string | undefined,\n language_override: doc['language_override'] as string | undefined,\n });\n}\n\nfunction parseValidator(options: Document): MongoSchemaValidator | undefined {\n const validator = options['validator'] as Record<string, unknown> | undefined;\n if (!validator) return undefined;\n\n const jsonSchema = validator['$jsonSchema'] as Record<string, unknown> | undefined;\n if (!jsonSchema) return undefined;\n\n return new MongoSchemaValidator({\n jsonSchema,\n validationLevel: (options['validationLevel'] as 'strict' | 'moderate') ?? 'strict',\n validationAction: (options['validationAction'] as 'error' | 'warn') ?? 'error',\n });\n}\n\nfunction parseCollectionOptions(info: Document): MongoSchemaCollectionOptions | undefined {\n const options = info['options'] as Record<string, unknown> | undefined;\n if (!options) return undefined;\n\n const capped = options['capped'] as boolean | undefined;\n const size = options['size'] as number | undefined;\n const max = options['max'] as number | undefined;\n const timeseries = options['timeseries'] as\n | { timeField: string; metaField?: string; granularity?: 'seconds' | 'minutes' | 'hours' }\n | undefined;\n const collation = options['collation'] as Record<string, unknown> | undefined;\n const changeStreamPreAndPostImages = options['changeStreamPreAndPostImages'] as\n | { enabled: boolean }\n | undefined;\n const clusteredIndex = options['clusteredIndex'] as { name?: string } | undefined;\n\n const hasMeaningfulOptions =\n capped || timeseries || collation || changeStreamPreAndPostImages || clusteredIndex;\n if (!hasMeaningfulOptions) return undefined;\n\n return new MongoSchemaCollectionOptions({\n ...(capped ? { capped: { size: size ?? 0, ...(max != null ? { max } : {}) } } : {}),\n ...(timeseries ? { timeseries } : {}),\n ...(collation ? { collation } : {}),\n ...(changeStreamPreAndPostImages ? { changeStreamPreAndPostImages } : {}),\n ...(clusteredIndex ? { clusteredIndex } : {}),\n });\n}\n\nexport async function introspectSchema(db: Db): Promise<MongoSchemaIR> {\n const collectionInfos = await db.listCollections().toArray();\n\n const collections: MongoSchemaCollection[] = [];\n\n for (const info of collectionInfos) {\n const name = info['name'] as string;\n const type = info['type'] as string | undefined;\n\n if (name === PRISMA_MIGRATIONS_COLLECTION) continue;\n if (name.startsWith('system.')) continue;\n if (type === 'view') continue;\n\n const indexDocs = await db.collection(name).listIndexes().toArray();\n const indexes = indexDocs.filter((doc) => !isDefaultIdIndex(doc)).map(parseIndex);\n\n const infoOptions = 'options' in info ? (info['options'] as Record<string, unknown>) : {};\n const validator = parseValidator(infoOptions);\n const options = parseCollectionOptions(info);\n\n collections.push(\n new MongoSchemaCollection({\n name,\n indexes,\n ...(validator ? { validator } : {}),\n ...(options ? { options } : {}),\n }),\n );\n }\n\n return new MongoSchemaIR(collections);\n}\n","import type { ControlDriverInstance } from '@prisma-next/framework-components/control';\nimport type { Db, MongoClient } from 'mongodb';\n\nexport interface MongoControlDriverInstance extends ControlDriverInstance<'mongo', 'mongo'> {\n readonly db: Db;\n}\n\nclass MongoControlDriverImpl implements MongoControlDriverInstance {\n readonly familyId = 'mongo' as const;\n readonly targetId = 'mongo' as const;\n readonly db: Db;\n readonly #client: MongoClient;\n\n constructor(db: Db, client: MongoClient) {\n this.db = db;\n this.#client = client;\n }\n\n query(): Promise<never> {\n throw new Error('MongoDB control driver does not support SQL queries');\n }\n\n async close(): Promise<void> {\n await this.#client.close();\n }\n}\n\nexport function createMongoControlDriver(db: Db, client: MongoClient): MongoControlDriverInstance {\n return new MongoControlDriverImpl(db, client);\n}\n","import type {\n ControlDriverInstance,\n ControlFamilyInstance,\n} from '@prisma-next/framework-components/control';\nimport type { MongoDriver } from '@prisma-next/mongo-lowering';\nimport type { MongoSchemaIR } from '@prisma-next/mongo-schema-ir';\nimport {\n initMarker,\n type MongoRunnerDependencies,\n readMarker,\n updateMarker,\n writeLedgerEntry,\n} from '@prisma-next/target-mongo/control';\nimport type { Db } from 'mongodb';\nimport { createMongoAdapter } from '../mongo-adapter';\nimport { MongoCommandExecutor, MongoInspectionExecutor } from './command-executor';\n\nexport function extractDb(driver: ControlDriverInstance<'mongo', 'mongo'>): Db {\n const mongoDriver = driver as ControlDriverInstance<'mongo', 'mongo'> & { db?: Db };\n if (!mongoDriver.db) {\n throw new Error(\n 'Mongo control driver does not expose a db property. ' +\n 'Use mongoControlDriver.create() from `@prisma-next/driver-mongo/control`.',\n );\n }\n return mongoDriver.db;\n}\n\nexport function createMongoRunnerDeps(\n controlDriver: ControlDriverInstance<'mongo', 'mongo'>,\n driver: MongoDriver,\n family: ControlFamilyInstance<'mongo', MongoSchemaIR>,\n): MongoRunnerDependencies {\n const db = extractDb(controlDriver);\n return {\n commandExecutor: new MongoCommandExecutor(db),\n inspectionExecutor: new MongoInspectionExecutor(db),\n adapter: createMongoAdapter(),\n driver,\n markerOps: {\n readMarker: (space) => readMarker(db, space),\n initMarker: (space, dest) => initMarker(db, space, dest),\n updateMarker: (space, expectedFrom, dest) => updateMarker(db, space, expectedFrom, dest),\n writeLedgerEntry: (space, entry) => writeLedgerEntry(db, space, entry),\n },\n introspectSchema: () => family.introspect({ driver: controlDriver }),\n };\n}\n","import type { ControlAdapterDescriptor } from '@prisma-next/framework-components/control';\n\nexport { MongoCommandExecutor, MongoInspectionExecutor } from '../core/command-executor';\nexport { introspectSchema } from '../core/introspect-schema';\nexport {\n createMongoControlDriver,\n type MongoControlDriverInstance,\n} from '../core/mongo-control-driver';\nexport { createMongoRunnerDeps, extractDb } from '../core/runner-deps';\n\nimport { mongoCodecDescriptors } from '../core/codecs';\n\nconst mongoAdapterDescriptor: ControlAdapterDescriptor<'mongo', 'mongo'> = {\n kind: 'adapter',\n id: 'mongo',\n familyId: 'mongo',\n targetId: 'mongo',\n version: '0.0.1',\n scalarTypeDescriptors: new Map([\n ['String', 'mongo/string@1'],\n ['Int', 'mongo/int32@1'],\n ['Boolean', 'mongo/bool@1'],\n ['DateTime', 'mongo/date@1'],\n ['ObjectId', 'mongo/objectId@1'],\n ['Float', 'mongo/double@1'],\n ]),\n types: {\n codecTypes: {\n codecDescriptors: mongoCodecDescriptors,\n import: {\n package: '@prisma-next/adapter-mongo/codec-types',\n named: 'CodecTypes',\n alias: 'MongoCodecTypes',\n },\n typeImports: [\n {\n package: '@prisma-next/adapter-mongo/codec-types',\n named: 'Vector',\n alias: 'Vector',\n },\n ],\n },\n },\n create(_stack) {\n return { familyId: 'mongo' as const, targetId: 'mongo' as const };\n },\n};\n\nexport default mongoAdapterDescriptor;\n"],"mappings":"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"}
1
+ {"version":3,"file":"control.mjs","names":["#client"],"sources":["../src/core/command-executor.ts","../src/core/introspect-schema.ts","../src/core/marker-ledger.ts","../src/core/runner-deps.ts","../src/core/mongo-control-adapter.ts","../src/core/mongo-control-driver.ts","../src/exports/control.ts"],"sourcesContent":["import type {\n CollModCommand,\n CreateCollectionCommand,\n CreateIndexCommand,\n DropCollectionCommand,\n DropIndexCommand,\n ListCollectionsCommand,\n ListIndexesCommand,\n MongoDdlCommandVisitor,\n MongoInspectionCommandVisitor,\n} from '@prisma-next/mongo-query-ast/control';\nimport { keysToKeySpec } from '@prisma-next/mongo-query-ast/control';\nimport { type Db, type Document, MongoServerError } from 'mongodb';\n\nexport class MongoCommandExecutor implements MongoDdlCommandVisitor<Promise<void>> {\n constructor(private readonly db: Db) {}\n\n async createIndex(cmd: CreateIndexCommand): Promise<void> {\n const keySpec: Document = keysToKeySpec(cmd.keys);\n const options: Record<string, unknown> = {};\n if (cmd.unique !== undefined) options['unique'] = cmd.unique;\n if (cmd.sparse !== undefined) options['sparse'] = cmd.sparse;\n if (cmd.expireAfterSeconds !== undefined)\n options['expireAfterSeconds'] = cmd.expireAfterSeconds;\n if (cmd.partialFilterExpression !== undefined)\n options['partialFilterExpression'] = cmd.partialFilterExpression;\n if (cmd.name !== undefined) options['name'] = cmd.name;\n if (cmd.wildcardProjection !== undefined)\n options['wildcardProjection'] = cmd.wildcardProjection;\n if (cmd.collation !== undefined) options['collation'] = cmd.collation;\n if (cmd.weights !== undefined) options['weights'] = cmd.weights;\n if (cmd.default_language !== undefined) options['default_language'] = cmd.default_language;\n if (cmd.language_override !== undefined) options['language_override'] = cmd.language_override;\n await this.db.collection(cmd.collection).createIndex(keySpec, options);\n }\n\n async dropIndex(cmd: DropIndexCommand): Promise<void> {\n await this.db.collection(cmd.collection).dropIndex(cmd.name);\n }\n\n async createCollection(cmd: CreateCollectionCommand): Promise<void> {\n const options: Record<string, unknown> = {};\n if (cmd.capped !== undefined) options['capped'] = cmd.capped;\n if (cmd.size !== undefined) options['size'] = cmd.size;\n if (cmd.max !== undefined) options['max'] = cmd.max;\n if (cmd.timeseries !== undefined) options['timeseries'] = cmd.timeseries;\n if (cmd.collation !== undefined) options['collation'] = cmd.collation;\n if (cmd.clusteredIndex !== undefined) options['clusteredIndex'] = cmd.clusteredIndex;\n if (cmd.validator !== undefined) options['validator'] = cmd.validator;\n if (cmd.validationLevel !== undefined) options['validationLevel'] = cmd.validationLevel;\n if (cmd.validationAction !== undefined) options['validationAction'] = cmd.validationAction;\n if (cmd.changeStreamPreAndPostImages !== undefined)\n options['changeStreamPreAndPostImages'] = cmd.changeStreamPreAndPostImages;\n await this.db.createCollection(cmd.collection, options);\n }\n\n async dropCollection(cmd: DropCollectionCommand): Promise<void> {\n await this.db.collection(cmd.collection).drop();\n }\n\n async collMod(cmd: CollModCommand): Promise<void> {\n const command: Record<string, unknown> = { collMod: cmd.collection };\n if (cmd.validator !== undefined) command['validator'] = cmd.validator;\n if (cmd.validationLevel !== undefined) command['validationLevel'] = cmd.validationLevel;\n if (cmd.validationAction !== undefined) command['validationAction'] = cmd.validationAction;\n if (cmd.changeStreamPreAndPostImages !== undefined)\n command['changeStreamPreAndPostImages'] = cmd.changeStreamPreAndPostImages;\n await this.db.command(command);\n }\n}\n\nexport class MongoInspectionExecutor implements MongoInspectionCommandVisitor<Promise<Document[]>> {\n constructor(private readonly db: Db) {}\n\n async listIndexes(cmd: ListIndexesCommand): Promise<Document[]> {\n try {\n return await this.db.collection(cmd.collection).listIndexes().toArray();\n } catch (error: unknown) {\n if (error instanceof MongoServerError && error.code === 26) {\n return [];\n }\n throw error;\n }\n }\n\n async listCollections(_cmd: ListCollectionsCommand): Promise<Document[]> {\n return this.db.listCollections().toArray();\n }\n}\n","import type { MongoIndexKey, MongoIndexKeyDirection } from '@prisma-next/mongo-contract';\nimport {\n MongoSchemaCollection,\n MongoSchemaCollectionOptions,\n MongoSchemaIndex,\n MongoSchemaIR,\n MongoSchemaValidator,\n} from '@prisma-next/mongo-schema-ir';\nimport type { Db, Document } from 'mongodb';\n\nconst PRISMA_MIGRATIONS_COLLECTION = '_prisma_migrations';\n\nfunction parseIndexKeys(keySpec: Record<string, unknown>): MongoIndexKey[] {\n const keys: MongoIndexKey[] = [];\n for (const [field, direction] of Object.entries(keySpec)) {\n keys.push({ field, direction: direction as MongoIndexKeyDirection });\n }\n return keys;\n}\n\n/**\n * Exported for unit tests to exercise the defensive `!key` guard; not part of\n * the public API. Callers in this package use it via the `introspectSchema`\n * pipeline only.\n */\nexport function isDefaultIdIndex(doc: Document): boolean {\n const key = doc['key'] as Record<string, unknown> | undefined;\n if (!key) return false;\n const entries = Object.entries(key);\n return entries.length === 1 && entries[0]?.[0] === '_id' && entries[0]?.[1] === 1;\n}\n\nfunction parseIndex(doc: Document): MongoSchemaIndex {\n const keySpec = doc['key'] as Record<string, unknown>;\n return new MongoSchemaIndex({\n keys: parseIndexKeys(keySpec),\n unique: doc['unique'] as boolean | undefined,\n sparse: doc['sparse'] as boolean | undefined,\n expireAfterSeconds: doc['expireAfterSeconds'] as number | undefined,\n partialFilterExpression: doc['partialFilterExpression'] as Record<string, unknown> | undefined,\n wildcardProjection: doc['wildcardProjection'] as Record<string, 0 | 1> | undefined,\n collation: doc['collation'] as Record<string, unknown> | undefined,\n weights: doc['weights'] as Record<string, number> | undefined,\n default_language: doc['default_language'] as string | undefined,\n language_override: doc['language_override'] as string | undefined,\n });\n}\n\nfunction parseValidator(options: Document): MongoSchemaValidator | undefined {\n const validator = options['validator'] as Record<string, unknown> | undefined;\n if (!validator) return undefined;\n\n const jsonSchema = validator['$jsonSchema'] as Record<string, unknown> | undefined;\n if (!jsonSchema) return undefined;\n\n return new MongoSchemaValidator({\n jsonSchema,\n validationLevel: (options['validationLevel'] as 'strict' | 'moderate') ?? 'strict',\n validationAction: (options['validationAction'] as 'error' | 'warn') ?? 'error',\n });\n}\n\nfunction parseCollectionOptions(info: Document): MongoSchemaCollectionOptions | undefined {\n const options = info['options'] as Record<string, unknown> | undefined;\n if (!options) return undefined;\n\n const capped = options['capped'] as boolean | undefined;\n const size = options['size'] as number | undefined;\n const max = options['max'] as number | undefined;\n const timeseries = options['timeseries'] as\n | { timeField: string; metaField?: string; granularity?: 'seconds' | 'minutes' | 'hours' }\n | undefined;\n const collation = options['collation'] as Record<string, unknown> | undefined;\n const changeStreamPreAndPostImages = options['changeStreamPreAndPostImages'] as\n | { enabled: boolean }\n | undefined;\n const clusteredIndex = options['clusteredIndex'] as { name?: string } | undefined;\n\n const hasMeaningfulOptions =\n capped || timeseries || collation || changeStreamPreAndPostImages || clusteredIndex;\n if (!hasMeaningfulOptions) return undefined;\n\n return new MongoSchemaCollectionOptions({\n ...(capped ? { capped: { size: size ?? 0, ...(max != null ? { max } : {}) } } : {}),\n ...(timeseries ? { timeseries } : {}),\n ...(collation ? { collation } : {}),\n ...(changeStreamPreAndPostImages ? { changeStreamPreAndPostImages } : {}),\n ...(clusteredIndex ? { clusteredIndex } : {}),\n });\n}\n\nexport async function introspectSchema(db: Db): Promise<MongoSchemaIR> {\n const collectionInfos = await db.listCollections().toArray();\n\n const collections: MongoSchemaCollection[] = [];\n\n for (const info of collectionInfos) {\n const name = info['name'] as string;\n const type = info['type'] as string | undefined;\n\n if (name === PRISMA_MIGRATIONS_COLLECTION) continue;\n if (name.startsWith('system.')) continue;\n if (type === 'view') continue;\n\n const indexDocs = await db.collection(name).listIndexes().toArray();\n const indexes = indexDocs.filter((doc) => !isDefaultIdIndex(doc)).map(parseIndex);\n\n const infoOptions = 'options' in info ? (info['options'] as Record<string, unknown>) : {};\n const validator = parseValidator(infoOptions);\n const options = parseCollectionOptions(info);\n\n collections.push(\n new MongoSchemaCollection({\n name,\n indexes,\n ...(validator ? { validator } : {}),\n ...(options ? { options } : {}),\n }),\n );\n }\n\n return new MongoSchemaIR(collections);\n}\n","import type { ContractMarkerRecord } from '@prisma-next/contract/types';\nimport {\n RawAggregateCommand,\n RawFindOneAndUpdateCommand,\n RawInsertOneCommand,\n} from '@prisma-next/mongo-query-ast/execution';\nimport { type } from 'arktype';\nimport type { Db, Document, UpdateFilter } from 'mongodb';\n\nconst COLLECTION = '_prisma_migrations';\n\n/**\n * Marker doc shape.\n *\n * Same fields as the SQL marker row but camelCase + Mongo-native types:\n * `Date` is BSON-hydrated, `meta` is a native object (not JSON-stringified),\n * `_id` and any extension fields are tolerated. `invariants?` is optional —\n * absent reads as `[]` (schemaless default); present-but-malformed throws.\n *\n * `space` is required: every marker doc is keyed by its space id (`_id`)\n * and stamped with a matching `space` field for partitioned reads.\n */\nconst MongoMarkerDocSchema = type({\n space: 'string',\n storageHash: 'string',\n profileHash: 'string',\n 'contractJson?': 'unknown | null',\n 'canonicalVersion?': 'number | null',\n 'updatedAt?': 'Date',\n 'appTag?': 'string | null',\n 'meta?': type({ '[string]': 'unknown' }).or('null'),\n 'invariants?': type('string').array(),\n '+': 'delete',\n});\n\nfunction parseMongoMarkerDoc(doc: unknown): ContractMarkerRecord {\n const result = MongoMarkerDocSchema(doc);\n if (result instanceof type.errors) {\n throw new Error(`Invalid marker doc on ${COLLECTION}: ${result.summary}`);\n }\n return {\n storageHash: result.storageHash,\n profileHash: result.profileHash,\n contractJson: result.contractJson ?? null,\n canonicalVersion: result.canonicalVersion ?? null,\n updatedAt: result.updatedAt ?? new Date(),\n appTag: result.appTag ?? null,\n meta: (result.meta as Record<string, unknown> | null) ?? {},\n invariants: result.invariants ?? [],\n };\n}\n\nasync function executeAggregate(db: Db, cmd: RawAggregateCommand): Promise<Document[]> {\n return db\n .collection(cmd.collection)\n .aggregate(cmd.pipeline as Record<string, unknown>[])\n .toArray();\n}\n\nasync function executeInsertOne(db: Db, cmd: RawInsertOneCommand): Promise<void> {\n await db.collection(cmd.collection).insertOne(cmd.document);\n}\n\nasync function executeFindOneAndUpdate(\n db: Db,\n cmd: RawFindOneAndUpdateCommand,\n): Promise<Document | null> {\n // `cmd.update` is `Document | ReadonlyArray<Document>` per the AST. The\n // MongoDB driver's `findOneAndUpdate` accepts the same shape under the\n // type `UpdateFilter<T> | Document[]`. The driver's runtime path handles\n // both forms identically — pipelines (array) and update docs (object).\n // One cast to that union keeps the call single-arm.\n return db\n .collection(cmd.collection)\n .findOneAndUpdate(cmd.filter, cmd.update as UpdateFilter<Document> | Document[], {\n upsert: cmd.upsert,\n });\n}\n\n/**\n * Reads the marker document for the given contract space, or returns\n * `null` if no marker has been written for that space yet. Each space\n * owns one row keyed by `_id: <space>` — see ADR 212 for the per-space\n * mechanism this enables.\n */\nexport async function readMarker(db: Db, space: string): Promise<ContractMarkerRecord | null> {\n const cmd = new RawAggregateCommand(COLLECTION, [\n { $match: { _id: space, space } },\n { $limit: 1 },\n ]);\n const docs = await executeAggregate(db, cmd);\n const doc = docs[0];\n if (!doc) return null;\n return parseMongoMarkerDoc(doc);\n}\n\n/**\n * Reads every marker doc in the collection (one per contract space)\n * and returns them keyed by `space`. Used by the per-space verifier\n * to detect marker-vs-on-disk drift and orphan marker rows. Returns\n * an empty map when no marker docs have been written yet.\n *\n * Marker docs are keyed by `_id: <space>` (string); ledger entries\n * live in the same collection but use a driver-generated `ObjectId`\n * `_id` plus `type: 'ledger'`. The filter selects string-keyed docs\n * with a `space` field, which excludes ledger entries by construction.\n */\nexport async function readAllMarkers(db: Db): Promise<ReadonlyMap<string, ContractMarkerRecord>> {\n const cmd = new RawAggregateCommand(COLLECTION, [\n {\n $match: {\n _id: { $type: 'string' },\n space: { $type: 'string' },\n $expr: { $eq: ['$_id', '$space'] },\n },\n },\n ]);\n const docs = await executeAggregate(db, cmd);\n const out = new Map<string, ContractMarkerRecord>();\n for (const doc of docs) {\n const space = doc['space'];\n /* v8 ignore next -- @preserve type-narrowing guard: the $match stage above filters on `space: { $type: 'string' }`, so this branch is unreachable at runtime. The check exists so the `out.set(space, ...)` call below can accept `string`. */\n if (typeof space !== 'string') continue;\n out.set(space, parseMongoMarkerDoc(doc));\n }\n return out;\n}\n\nexport async function initMarker(\n db: Db,\n space: string,\n destination: {\n readonly storageHash: string;\n readonly profileHash: string;\n readonly invariants?: readonly string[];\n },\n): Promise<void> {\n const cmd = new RawInsertOneCommand(COLLECTION, {\n _id: space,\n space,\n storageHash: destination.storageHash,\n profileHash: destination.profileHash,\n contractJson: null,\n canonicalVersion: null,\n updatedAt: new Date(),\n appTag: null,\n meta: {},\n invariants: destination.invariants ?? [],\n });\n await executeInsertOne(db, cmd);\n}\n\n/**\n * Updates the marker doc for the given space atomically (CAS on\n * `expectedFrom`).\n *\n * `destination.invariants`:\n * - `undefined` → existing field left untouched.\n * - explicit value → merged into the existing field server-side via an\n * aggregation pipeline (`$setUnion + $sortArray`), atomic at the\n * document level. `[]` is a no-op merge.\n */\nexport async function updateMarker(\n db: Db,\n space: string,\n expectedFrom: string,\n destination: {\n readonly storageHash: string;\n readonly profileHash: string;\n readonly invariants?: readonly string[];\n },\n): Promise<boolean> {\n const setBase: Record<string, unknown> = {\n storageHash: destination.storageHash,\n profileHash: destination.profileHash,\n updatedAt: new Date(),\n };\n // When invariants is supplied, use an aggregation pipeline so the\n // merge runs server-side against the doc's current value (atomic, no\n // read-then-write window). When omitted, a regular update doc keeps\n // the field untouched.\n const update: Document | Document[] =\n destination.invariants === undefined\n ? { $set: setBase }\n : [\n {\n $set: {\n ...setBase,\n invariants: {\n $sortArray: {\n input: { $setUnion: [{ $ifNull: ['$invariants', []] }, destination.invariants] },\n sortBy: 1,\n },\n },\n },\n },\n ];\n const cmd = new RawFindOneAndUpdateCommand(\n COLLECTION,\n { _id: space, space, storageHash: expectedFrom },\n update,\n false,\n );\n const result = await executeFindOneAndUpdate(db, cmd);\n return result !== null;\n}\n\n/**\n * Appends a ledger entry for the given space. Ledger entries co-exist\n * with marker docs in the same collection; marker docs use `_id: <space>`\n * (string), ledger entries use `type: 'ledger'` plus a driver-generated\n * ObjectId. Reads partition the two by filter shape.\n *\n * The same `edgeId` may legitimately recur across different spaces (e.g.\n * a synthetic ∅→head edge on first apply), so the ledger key is\n * `(space, edgeId)` — the doc carries `space` for partitioned reads.\n */\nexport async function writeLedgerEntry(\n db: Db,\n space: string,\n entry: { readonly edgeId: string; readonly from: string; readonly to: string },\n): Promise<void> {\n const cmd = new RawInsertOneCommand(COLLECTION, {\n type: 'ledger',\n space,\n edgeId: entry.edgeId,\n from: entry.from,\n to: entry.to,\n appliedAt: new Date(),\n });\n await executeInsertOne(db, cmd);\n}\n","import type { ContractMarkerRecord } from '@prisma-next/contract/types';\nimport type { MongoControlAdapter } from '@prisma-next/family-mongo/control-adapter';\nimport type {\n ControlDriverInstance,\n ControlFamilyInstance,\n} from '@prisma-next/framework-components/control';\nimport type { MongoAdapter, MongoDriver } from '@prisma-next/mongo-lowering';\nimport type {\n MongoDdlCommandVisitor,\n MongoInspectionCommandVisitor,\n} from '@prisma-next/mongo-query-ast/control';\nimport type { MongoSchemaIR } from '@prisma-next/mongo-schema-ir';\nimport type { Db } from 'mongodb';\nimport { createMongoAdapter } from '../mongo-adapter';\nimport { MongoCommandExecutor, MongoInspectionExecutor } from './command-executor';\nimport { MongoControlAdapterImpl } from './mongo-control-adapter';\n\nexport function extractDb(driver: ControlDriverInstance<'mongo', 'mongo'>): Db {\n const mongoDriver = driver as ControlDriverInstance<'mongo', 'mongo'> & { db?: Db };\n if (!mongoDriver.db) {\n throw new Error(\n 'Mongo control driver does not expose a db property. ' +\n 'Use mongoControlDriver.create() from `@prisma-next/driver-mongo/control`.',\n );\n }\n return mongoDriver.db;\n}\n\n/**\n * Marker / ledger operations the Mongo runner depends on. Every method\n * takes a `space` parameter so each loaded contract space addresses its\n * own marker row independently — see ADR 212 for the per-space\n * mechanism.\n */\nexport interface MarkerOperations {\n readMarker(space: string): Promise<ContractMarkerRecord | null>;\n initMarker(\n space: string,\n destination: {\n readonly storageHash: string;\n readonly profileHash: string;\n readonly invariants?: readonly string[];\n },\n ): Promise<void>;\n updateMarker(\n space: string,\n expectedFrom: string,\n destination: {\n readonly storageHash: string;\n readonly profileHash: string;\n readonly invariants?: readonly string[];\n },\n ): Promise<boolean>;\n writeLedgerEntry(\n space: string,\n entry: {\n readonly edgeId: string;\n readonly from: string;\n readonly to: string;\n },\n ): Promise<void>;\n}\n\nexport interface MongoRunnerDependencies {\n readonly commandExecutor: MongoDdlCommandVisitor<Promise<void>>;\n readonly inspectionExecutor: MongoInspectionCommandVisitor<Promise<Record<string, unknown>[]>>;\n readonly adapter: MongoAdapter;\n readonly driver: MongoDriver;\n readonly markerOps: MarkerOperations;\n readonly introspectSchema: () => Promise<MongoSchemaIR>;\n}\n\n/**\n * Build the runner-dependencies envelope. `controlAdapter` is the\n * dispatch surface for wire-level Mongo CAS operations (marker reads,\n * marker advances, ledger appends, introspection); the envelope's\n * `markerOps` shim simply forwards each call through it. When the\n * caller already has a `MongoControlAdapter` on the control stack it\n * can pass it in; otherwise a default `MongoControlAdapterImpl` is\n * constructed locally.\n */\nexport function createMongoRunnerDeps(\n controlDriver: ControlDriverInstance<'mongo', 'mongo'>,\n driver: MongoDriver,\n // Vestigial after the M2.5 family→adapter SPI dispatch refactor: the runner\n // dependencies now route every wire-level call through `controlAdapter`, so\n // the `family` instance is no longer consulted. Kept on the signature to\n // avoid rippling through ~14 call sites mid-orchestration; a follow-up that\n // already touches this factory should drop the parameter outright.\n _family: ControlFamilyInstance<'mongo', MongoSchemaIR>,\n controlAdapter: MongoControlAdapter<'mongo'> = new MongoControlAdapterImpl(),\n): MongoRunnerDependencies {\n return {\n commandExecutor: new MongoCommandExecutor(extractDb(controlDriver)),\n inspectionExecutor: new MongoInspectionExecutor(extractDb(controlDriver)),\n adapter: createMongoAdapter(),\n driver,\n markerOps: {\n readMarker: (space) => controlAdapter.readMarker(controlDriver, space),\n initMarker: (space, dest) => controlAdapter.initMarker(controlDriver, space, dest),\n updateMarker: (space, expectedFrom, dest) =>\n controlAdapter.updateMarker(controlDriver, space, expectedFrom, dest),\n writeLedgerEntry: (space, entry) =>\n controlAdapter.writeLedgerEntry(controlDriver, space, entry),\n },\n introspectSchema: () => controlAdapter.introspectSchema(controlDriver),\n };\n}\n","import type { ContractMarkerRecord } from '@prisma-next/contract/types';\nimport type { MongoControlAdapter } from '@prisma-next/family-mongo/control-adapter';\nimport type { ControlDriverInstance } from '@prisma-next/framework-components/control';\nimport type { MongoSchemaIR } from '@prisma-next/mongo-schema-ir';\nimport { introspectSchema } from './introspect-schema';\nimport {\n initMarker,\n readAllMarkers,\n readMarker,\n updateMarker,\n writeLedgerEntry,\n} from './marker-ledger';\nimport { extractDb } from './runner-deps';\n\n/**\n * Mongo control adapter for control-plane operations like introspection\n * and marker-ledger CAS. Implements the family-level `MongoControlAdapter`\n * SPI by extracting the underlying `Db` from the framework-shaped driver\n * and forwarding to the wire-level helpers in this package.\n */\nexport class MongoControlAdapterImpl implements MongoControlAdapter<'mongo'> {\n readonly familyId = 'mongo' as const;\n readonly targetId = 'mongo' as const;\n\n async readMarker(\n driver: ControlDriverInstance<'mongo', 'mongo'>,\n space: string,\n ): Promise<ContractMarkerRecord | null> {\n return readMarker(extractDb(driver), space);\n }\n\n async readAllMarkers(\n driver: ControlDriverInstance<'mongo', 'mongo'>,\n ): Promise<ReadonlyMap<string, ContractMarkerRecord>> {\n return readAllMarkers(extractDb(driver));\n }\n\n async initMarker(\n driver: ControlDriverInstance<'mongo', 'mongo'>,\n space: string,\n destination: {\n readonly storageHash: string;\n readonly profileHash: string;\n readonly invariants?: readonly string[];\n },\n ): Promise<void> {\n await initMarker(extractDb(driver), space, destination);\n }\n\n async updateMarker(\n driver: ControlDriverInstance<'mongo', 'mongo'>,\n space: string,\n expectedFrom: string,\n destination: {\n readonly storageHash: string;\n readonly profileHash: string;\n readonly invariants?: readonly string[];\n },\n ): Promise<boolean> {\n return updateMarker(extractDb(driver), space, expectedFrom, destination);\n }\n\n async writeLedgerEntry(\n driver: ControlDriverInstance<'mongo', 'mongo'>,\n space: string,\n entry: { readonly edgeId: string; readonly from: string; readonly to: string },\n ): Promise<void> {\n await writeLedgerEntry(extractDb(driver), space, entry);\n }\n\n async introspectSchema(driver: ControlDriverInstance<'mongo', 'mongo'>): Promise<MongoSchemaIR> {\n return introspectSchema(extractDb(driver));\n }\n}\n","import type { ControlDriverInstance } from '@prisma-next/framework-components/control';\nimport type { Db, MongoClient } from 'mongodb';\n\nexport interface MongoControlDriverInstance extends ControlDriverInstance<'mongo', 'mongo'> {\n readonly db: Db;\n}\n\nclass MongoControlDriverImpl implements MongoControlDriverInstance {\n readonly familyId = 'mongo' as const;\n readonly targetId = 'mongo' as const;\n readonly db: Db;\n readonly #client: MongoClient;\n\n constructor(db: Db, client: MongoClient) {\n this.db = db;\n this.#client = client;\n }\n\n query(): Promise<never> {\n throw new Error('MongoDB control driver does not support SQL queries');\n }\n\n async close(): Promise<void> {\n await this.#client.close();\n }\n}\n\nexport function createMongoControlDriver(db: Db, client: MongoClient): MongoControlDriverInstance {\n return new MongoControlDriverImpl(db, client);\n}\n","import type { MongoControlAdapterDescriptor } from '@prisma-next/family-mongo/control-adapter';\n\nexport { MongoCommandExecutor, MongoInspectionExecutor } from '../core/command-executor';\nexport { introspectSchema } from '../core/introspect-schema';\nexport {\n initMarker,\n readAllMarkers,\n readMarker,\n updateMarker,\n writeLedgerEntry,\n} from '../core/marker-ledger';\nexport { MongoControlAdapterImpl } from '../core/mongo-control-adapter';\nexport {\n createMongoControlDriver,\n type MongoControlDriverInstance,\n} from '../core/mongo-control-driver';\nexport {\n createMongoRunnerDeps,\n extractDb,\n type MarkerOperations,\n type MongoRunnerDependencies,\n} from '../core/runner-deps';\nexport { createMongoAdapter } from '../mongo-adapter';\n\nimport { mongoCodecDescriptors } from '../core/codecs';\nimport { MongoControlAdapterImpl } from '../core/mongo-control-adapter';\n\nexport const mongoAdapterDescriptor: MongoControlAdapterDescriptor<'mongo'> = {\n kind: 'adapter',\n id: 'mongo',\n familyId: 'mongo',\n targetId: 'mongo',\n version: '0.0.1',\n scalarTypeDescriptors: new Map([\n ['String', 'mongo/string@1'],\n ['Int', 'mongo/int32@1'],\n ['Boolean', 'mongo/bool@1'],\n ['DateTime', 'mongo/date@1'],\n ['ObjectId', 'mongo/objectId@1'],\n ['Float', 'mongo/double@1'],\n ]),\n types: {\n codecTypes: {\n codecDescriptors: mongoCodecDescriptors,\n import: {\n package: '@prisma-next/adapter-mongo/codec-types',\n named: 'CodecTypes',\n alias: 'MongoCodecTypes',\n },\n typeImports: [\n {\n package: '@prisma-next/adapter-mongo/codec-types',\n named: 'Vector',\n alias: 'Vector',\n },\n ],\n },\n },\n create(_stack) {\n return new MongoControlAdapterImpl();\n },\n};\n\nexport default mongoAdapterDescriptor;\n"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -1,8 +1,8 @@
1
+ import { t as createMongoAdapter } from "./mongo-adapter-JuKx_-h9.mjs";
1
2
  import { MongoAggExpr, MongoFilterExpr, MongoPipelineStage } from "@prisma-next/mongo-query-ast/execution";
2
3
  import { Document } from "@prisma-next/mongo-value";
3
4
  import { CodecCallContext } from "@prisma-next/framework-components/codec";
4
5
  import { MongoCodecRegistry } from "@prisma-next/mongo-codec";
5
- import { MongoAdapter } from "@prisma-next/mongo-lowering";
6
6
  import { OperationDescriptor, OperationDescriptors } from "@prisma-next/operations";
7
7
 
8
8
  //#region src/core/operations.d.ts
@@ -15,17 +15,5 @@ declare function lowerFilter(filter: MongoFilterExpr, codecs: MongoCodecRegistry
15
15
  declare function lowerStage(stage: MongoPipelineStage, codecs: MongoCodecRegistry, ctx: CodecCallContext): Promise<Record<string, unknown>>;
16
16
  declare function lowerPipeline(stages: ReadonlyArray<MongoPipelineStage>, codecs: MongoCodecRegistry, ctx: CodecCallContext): Promise<Array<Record<string, unknown>>>;
17
17
  //#endregion
18
- //#region src/mongo-adapter.d.ts
19
- /**
20
- * Construct a Mongo adapter with the standard wire-type codecs registered
21
- * for encode-side dispatch (`MongoParamRef.codecId` lookups).
22
- *
23
- * The runtime-side codec registry the runtime decodes against is composed
24
- * separately by `createMongoExecutionContext`. This factory exists for
25
- * direct adapter use (the runtime descriptor's `create(stack)` calls
26
- * through it). User code should compose a stack/context instead.
27
- */
28
- declare function createMongoAdapter(): MongoAdapter;
29
- //#endregion
30
18
  export { createMongoAdapter, lowerAggExpr, lowerFilter, lowerPipeline, lowerStage, mongoVectorNearOperation, mongoVectorOperationDescriptors };
31
19
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/core/operations.ts","../src/lowering.ts","../src/mongo-adapter.ts"],"mappings":";;;;;;;;cAGa,wBAAA,EAA0B,mBAAA;AAAA,cAK1B,+BAAA,EAAiC,oBAAA;;;iBCgI9B,YAAA,CAAa,IAAA,EAAM,YAAA;AAAA,iBAIb,WAAA,CACpB,MAAA,EAAQ,eAAA,EACR,MAAA,EAAQ,kBAAA,EACR,GAAA,EAAK,gBAAA,GACJ,OAAA,CAAQ,QAAA;AAAA,iBA8DW,UAAA,CACpB,KAAA,EAAO,kBAAA,EACP,MAAA,EAAQ,kBAAA,EACR,GAAA,EAAK,gBAAA,GACJ,OAAA,CAAQ,MAAA;AAAA,iBAsNW,aAAA,CACpB,MAAA,EAAQ,aAAA,CAAc,kBAAA,GACtB,MAAA,EAAQ,kBAAA,EACR,GAAA,EAAK,gBAAA,GACJ,OAAA,CAAQ,KAAA,CAAM,MAAA;;;;;;;;;ADzajB;;;iBEqKgB,kBAAA,CAAA,GAAsB,YAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/core/operations.ts","../src/lowering.ts"],"mappings":";;;;;;;;cAGa,wBAAA,EAA0B,mBAAA;AAAA,cAK1B,+BAAA,EAAiC,oBAAA;;;iBCgI9B,YAAA,CAAa,IAAA,EAAM,YAAA;AAAA,iBAIb,WAAA,CACpB,MAAA,EAAQ,eAAA,EACR,MAAA,EAAQ,kBAAA,EACR,GAAA,EAAK,gBAAA,GACJ,OAAA,CAAQ,QAAA;AAAA,iBA8DW,UAAA,CACpB,KAAA,EAAO,kBAAA,EACP,MAAA,EAAQ,kBAAA,EACR,GAAA,EAAK,gBAAA,GACJ,OAAA,CAAQ,MAAA;AAAA,iBAsNW,aAAA,CACpB,MAAA,EAAQ,aAAA,CAAc,kBAAA,GACtB,MAAA,EAAQ,kBAAA,EACR,GAAA,EAAK,gBAAA,GACJ,OAAA,CAAQ,KAAA,CAAM,MAAA"}
@@ -0,0 +1,17 @@
1
+ import { MongoCodecRegistry } from "@prisma-next/mongo-codec";
2
+ import { MongoAdapter } from "@prisma-next/mongo-lowering";
3
+
4
+ //#region src/mongo-adapter.d.ts
5
+ /**
6
+ * Construct a Mongo adapter with the standard wire-type codecs registered
7
+ * for encode-side dispatch (`MongoParamRef.codecId` lookups).
8
+ *
9
+ * The runtime-side codec registry the runtime decodes against is composed
10
+ * separately by `createMongoExecutionContext`. This factory exists for
11
+ * direct adapter use (the runtime descriptor's `create(stack)` calls
12
+ * through it). User code should compose a stack/context instead.
13
+ */
14
+ declare function createMongoAdapter(): MongoAdapter;
15
+ //#endregion
16
+ export { createMongoAdapter as t };
17
+ //# sourceMappingURL=mongo-adapter-JuKx_-h9.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mongo-adapter-JuKx_-h9.d.mts","names":[],"sources":["../src/mongo-adapter.ts"],"mappings":";;;;;;AAwKA;;;;;;;iBAAgB,kBAAA,CAAA,GAAsB,YAAA"}
package/package.json CHANGED
@@ -1,34 +1,34 @@
1
1
  {
2
2
  "name": "@prisma-next/adapter-mongo",
3
- "version": "0.8.0",
3
+ "version": "0.9.0-dev.1",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "description": "MongoDB adapter for Prisma Next (lowers commands to wire format)",
8
8
  "dependencies": {
9
- "@prisma-next/config": "0.8.0",
10
- "@prisma-next/contract": "0.8.0",
11
- "@prisma-next/target-mongo": "0.8.0",
12
- "@prisma-next/framework-components": "0.8.0",
13
- "@prisma-next/mongo-codec": "0.8.0",
14
- "@prisma-next/mongo-contract": "0.8.0",
15
- "@prisma-next/mongo-lowering": "0.8.0",
16
- "@prisma-next/mongo-query-ast": "0.8.0",
17
- "@prisma-next/mongo-schema-ir": "0.8.0",
18
- "@prisma-next/mongo-value": "0.8.0",
19
- "@prisma-next/mongo-wire": "0.8.0",
20
- "@prisma-next/operations": "0.8.0",
21
- "@prisma-next/utils": "0.8.0",
9
+ "@prisma-next/config": "0.9.0-dev.1",
10
+ "@prisma-next/contract": "0.9.0-dev.1",
11
+ "@prisma-next/family-mongo": "0.9.0-dev.1",
12
+ "@prisma-next/framework-components": "0.9.0-dev.1",
13
+ "@prisma-next/mongo-codec": "0.9.0-dev.1",
14
+ "@prisma-next/mongo-contract": "0.9.0-dev.1",
15
+ "@prisma-next/mongo-lowering": "0.9.0-dev.1",
16
+ "@prisma-next/mongo-query-ast": "0.9.0-dev.1",
17
+ "@prisma-next/mongo-schema-ir": "0.9.0-dev.1",
18
+ "@prisma-next/mongo-value": "0.9.0-dev.1",
19
+ "@prisma-next/mongo-wire": "0.9.0-dev.1",
20
+ "@prisma-next/operations": "0.9.0-dev.1",
21
+ "@prisma-next/utils": "0.9.0-dev.1",
22
22
  "arktype": "^2.1.29",
23
23
  "mongodb": "^6.16.0"
24
24
  },
25
25
  "devDependencies": {
26
- "@prisma-next/driver-mongo": "0.8.0",
27
- "@prisma-next/mongo-contract-psl": "0.8.0",
28
- "@prisma-next/psl-parser": "0.8.0",
29
- "@prisma-next/test-utils": "0.8.0",
30
- "@prisma-next/tsconfig": "0.8.0",
31
- "@prisma-next/tsdown": "0.8.0",
26
+ "@prisma-next/driver-mongo": "0.9.0-dev.1",
27
+ "@prisma-next/mongo-contract-psl": "0.9.0-dev.1",
28
+ "@prisma-next/psl-parser": "0.9.0-dev.1",
29
+ "@prisma-next/test-utils": "0.9.0-dev.1",
30
+ "@prisma-next/tsconfig": "0.9.0-dev.1",
31
+ "@prisma-next/tsdown": "0.9.0-dev.1",
32
32
  "mongodb-memory-server": "11.0.1",
33
33
  "tsdown": "0.22.0",
34
34
  "typescript": "5.9.3",
@@ -45,8 +45,6 @@
45
45
  "./runtime": "./dist/runtime.mjs",
46
46
  "./package.json": "./package.json"
47
47
  },
48
- "main": "./dist/index.mjs",
49
- "module": "./dist/index.mjs",
50
48
  "types": "./dist/index.d.mts",
51
49
  "repository": {
52
50
  "type": "git",
@@ -0,0 +1,232 @@
1
+ import type { ContractMarkerRecord } from '@prisma-next/contract/types';
2
+ import {
3
+ RawAggregateCommand,
4
+ RawFindOneAndUpdateCommand,
5
+ RawInsertOneCommand,
6
+ } from '@prisma-next/mongo-query-ast/execution';
7
+ import { type } from 'arktype';
8
+ import type { Db, Document, UpdateFilter } from 'mongodb';
9
+
10
+ const COLLECTION = '_prisma_migrations';
11
+
12
+ /**
13
+ * Marker doc shape.
14
+ *
15
+ * Same fields as the SQL marker row but camelCase + Mongo-native types:
16
+ * `Date` is BSON-hydrated, `meta` is a native object (not JSON-stringified),
17
+ * `_id` and any extension fields are tolerated. `invariants?` is optional —
18
+ * absent reads as `[]` (schemaless default); present-but-malformed throws.
19
+ *
20
+ * `space` is required: every marker doc is keyed by its space id (`_id`)
21
+ * and stamped with a matching `space` field for partitioned reads.
22
+ */
23
+ const MongoMarkerDocSchema = type({
24
+ space: 'string',
25
+ storageHash: 'string',
26
+ profileHash: 'string',
27
+ 'contractJson?': 'unknown | null',
28
+ 'canonicalVersion?': 'number | null',
29
+ 'updatedAt?': 'Date',
30
+ 'appTag?': 'string | null',
31
+ 'meta?': type({ '[string]': 'unknown' }).or('null'),
32
+ 'invariants?': type('string').array(),
33
+ '+': 'delete',
34
+ });
35
+
36
+ function parseMongoMarkerDoc(doc: unknown): ContractMarkerRecord {
37
+ const result = MongoMarkerDocSchema(doc);
38
+ if (result instanceof type.errors) {
39
+ throw new Error(`Invalid marker doc on ${COLLECTION}: ${result.summary}`);
40
+ }
41
+ return {
42
+ storageHash: result.storageHash,
43
+ profileHash: result.profileHash,
44
+ contractJson: result.contractJson ?? null,
45
+ canonicalVersion: result.canonicalVersion ?? null,
46
+ updatedAt: result.updatedAt ?? new Date(),
47
+ appTag: result.appTag ?? null,
48
+ meta: (result.meta as Record<string, unknown> | null) ?? {},
49
+ invariants: result.invariants ?? [],
50
+ };
51
+ }
52
+
53
+ async function executeAggregate(db: Db, cmd: RawAggregateCommand): Promise<Document[]> {
54
+ return db
55
+ .collection(cmd.collection)
56
+ .aggregate(cmd.pipeline as Record<string, unknown>[])
57
+ .toArray();
58
+ }
59
+
60
+ async function executeInsertOne(db: Db, cmd: RawInsertOneCommand): Promise<void> {
61
+ await db.collection(cmd.collection).insertOne(cmd.document);
62
+ }
63
+
64
+ async function executeFindOneAndUpdate(
65
+ db: Db,
66
+ cmd: RawFindOneAndUpdateCommand,
67
+ ): Promise<Document | null> {
68
+ // `cmd.update` is `Document | ReadonlyArray<Document>` per the AST. The
69
+ // MongoDB driver's `findOneAndUpdate` accepts the same shape under the
70
+ // type `UpdateFilter<T> | Document[]`. The driver's runtime path handles
71
+ // both forms identically — pipelines (array) and update docs (object).
72
+ // One cast to that union keeps the call single-arm.
73
+ return db
74
+ .collection(cmd.collection)
75
+ .findOneAndUpdate(cmd.filter, cmd.update as UpdateFilter<Document> | Document[], {
76
+ upsert: cmd.upsert,
77
+ });
78
+ }
79
+
80
+ /**
81
+ * Reads the marker document for the given contract space, or returns
82
+ * `null` if no marker has been written for that space yet. Each space
83
+ * owns one row keyed by `_id: <space>` — see ADR 212 for the per-space
84
+ * mechanism this enables.
85
+ */
86
+ export async function readMarker(db: Db, space: string): Promise<ContractMarkerRecord | null> {
87
+ const cmd = new RawAggregateCommand(COLLECTION, [
88
+ { $match: { _id: space, space } },
89
+ { $limit: 1 },
90
+ ]);
91
+ const docs = await executeAggregate(db, cmd);
92
+ const doc = docs[0];
93
+ if (!doc) return null;
94
+ return parseMongoMarkerDoc(doc);
95
+ }
96
+
97
+ /**
98
+ * Reads every marker doc in the collection (one per contract space)
99
+ * and returns them keyed by `space`. Used by the per-space verifier
100
+ * to detect marker-vs-on-disk drift and orphan marker rows. Returns
101
+ * an empty map when no marker docs have been written yet.
102
+ *
103
+ * Marker docs are keyed by `_id: <space>` (string); ledger entries
104
+ * live in the same collection but use a driver-generated `ObjectId`
105
+ * `_id` plus `type: 'ledger'`. The filter selects string-keyed docs
106
+ * with a `space` field, which excludes ledger entries by construction.
107
+ */
108
+ export async function readAllMarkers(db: Db): Promise<ReadonlyMap<string, ContractMarkerRecord>> {
109
+ const cmd = new RawAggregateCommand(COLLECTION, [
110
+ {
111
+ $match: {
112
+ _id: { $type: 'string' },
113
+ space: { $type: 'string' },
114
+ $expr: { $eq: ['$_id', '$space'] },
115
+ },
116
+ },
117
+ ]);
118
+ const docs = await executeAggregate(db, cmd);
119
+ const out = new Map<string, ContractMarkerRecord>();
120
+ for (const doc of docs) {
121
+ const space = doc['space'];
122
+ /* v8 ignore next -- @preserve type-narrowing guard: the $match stage above filters on `space: { $type: 'string' }`, so this branch is unreachable at runtime. The check exists so the `out.set(space, ...)` call below can accept `string`. */
123
+ if (typeof space !== 'string') continue;
124
+ out.set(space, parseMongoMarkerDoc(doc));
125
+ }
126
+ return out;
127
+ }
128
+
129
+ export async function initMarker(
130
+ db: Db,
131
+ space: string,
132
+ destination: {
133
+ readonly storageHash: string;
134
+ readonly profileHash: string;
135
+ readonly invariants?: readonly string[];
136
+ },
137
+ ): Promise<void> {
138
+ const cmd = new RawInsertOneCommand(COLLECTION, {
139
+ _id: space,
140
+ space,
141
+ storageHash: destination.storageHash,
142
+ profileHash: destination.profileHash,
143
+ contractJson: null,
144
+ canonicalVersion: null,
145
+ updatedAt: new Date(),
146
+ appTag: null,
147
+ meta: {},
148
+ invariants: destination.invariants ?? [],
149
+ });
150
+ await executeInsertOne(db, cmd);
151
+ }
152
+
153
+ /**
154
+ * Updates the marker doc for the given space atomically (CAS on
155
+ * `expectedFrom`).
156
+ *
157
+ * `destination.invariants`:
158
+ * - `undefined` → existing field left untouched.
159
+ * - explicit value → merged into the existing field server-side via an
160
+ * aggregation pipeline (`$setUnion + $sortArray`), atomic at the
161
+ * document level. `[]` is a no-op merge.
162
+ */
163
+ export async function updateMarker(
164
+ db: Db,
165
+ space: string,
166
+ expectedFrom: string,
167
+ destination: {
168
+ readonly storageHash: string;
169
+ readonly profileHash: string;
170
+ readonly invariants?: readonly string[];
171
+ },
172
+ ): Promise<boolean> {
173
+ const setBase: Record<string, unknown> = {
174
+ storageHash: destination.storageHash,
175
+ profileHash: destination.profileHash,
176
+ updatedAt: new Date(),
177
+ };
178
+ // When invariants is supplied, use an aggregation pipeline so the
179
+ // merge runs server-side against the doc's current value (atomic, no
180
+ // read-then-write window). When omitted, a regular update doc keeps
181
+ // the field untouched.
182
+ const update: Document | Document[] =
183
+ destination.invariants === undefined
184
+ ? { $set: setBase }
185
+ : [
186
+ {
187
+ $set: {
188
+ ...setBase,
189
+ invariants: {
190
+ $sortArray: {
191
+ input: { $setUnion: [{ $ifNull: ['$invariants', []] }, destination.invariants] },
192
+ sortBy: 1,
193
+ },
194
+ },
195
+ },
196
+ },
197
+ ];
198
+ const cmd = new RawFindOneAndUpdateCommand(
199
+ COLLECTION,
200
+ { _id: space, space, storageHash: expectedFrom },
201
+ update,
202
+ false,
203
+ );
204
+ const result = await executeFindOneAndUpdate(db, cmd);
205
+ return result !== null;
206
+ }
207
+
208
+ /**
209
+ * Appends a ledger entry for the given space. Ledger entries co-exist
210
+ * with marker docs in the same collection; marker docs use `_id: <space>`
211
+ * (string), ledger entries use `type: 'ledger'` plus a driver-generated
212
+ * ObjectId. Reads partition the two by filter shape.
213
+ *
214
+ * The same `edgeId` may legitimately recur across different spaces (e.g.
215
+ * a synthetic ∅→head edge on first apply), so the ledger key is
216
+ * `(space, edgeId)` — the doc carries `space` for partitioned reads.
217
+ */
218
+ export async function writeLedgerEntry(
219
+ db: Db,
220
+ space: string,
221
+ entry: { readonly edgeId: string; readonly from: string; readonly to: string },
222
+ ): Promise<void> {
223
+ const cmd = new RawInsertOneCommand(COLLECTION, {
224
+ type: 'ledger',
225
+ space,
226
+ edgeId: entry.edgeId,
227
+ from: entry.from,
228
+ to: entry.to,
229
+ appliedAt: new Date(),
230
+ });
231
+ await executeInsertOne(db, cmd);
232
+ }
@@ -0,0 +1,74 @@
1
+ import type { ContractMarkerRecord } from '@prisma-next/contract/types';
2
+ import type { MongoControlAdapter } from '@prisma-next/family-mongo/control-adapter';
3
+ import type { ControlDriverInstance } from '@prisma-next/framework-components/control';
4
+ import type { MongoSchemaIR } from '@prisma-next/mongo-schema-ir';
5
+ import { introspectSchema } from './introspect-schema';
6
+ import {
7
+ initMarker,
8
+ readAllMarkers,
9
+ readMarker,
10
+ updateMarker,
11
+ writeLedgerEntry,
12
+ } from './marker-ledger';
13
+ import { extractDb } from './runner-deps';
14
+
15
+ /**
16
+ * Mongo control adapter for control-plane operations like introspection
17
+ * and marker-ledger CAS. Implements the family-level `MongoControlAdapter`
18
+ * SPI by extracting the underlying `Db` from the framework-shaped driver
19
+ * and forwarding to the wire-level helpers in this package.
20
+ */
21
+ export class MongoControlAdapterImpl implements MongoControlAdapter<'mongo'> {
22
+ readonly familyId = 'mongo' as const;
23
+ readonly targetId = 'mongo' as const;
24
+
25
+ async readMarker(
26
+ driver: ControlDriverInstance<'mongo', 'mongo'>,
27
+ space: string,
28
+ ): Promise<ContractMarkerRecord | null> {
29
+ return readMarker(extractDb(driver), space);
30
+ }
31
+
32
+ async readAllMarkers(
33
+ driver: ControlDriverInstance<'mongo', 'mongo'>,
34
+ ): Promise<ReadonlyMap<string, ContractMarkerRecord>> {
35
+ return readAllMarkers(extractDb(driver));
36
+ }
37
+
38
+ async initMarker(
39
+ driver: ControlDriverInstance<'mongo', 'mongo'>,
40
+ space: string,
41
+ destination: {
42
+ readonly storageHash: string;
43
+ readonly profileHash: string;
44
+ readonly invariants?: readonly string[];
45
+ },
46
+ ): Promise<void> {
47
+ await initMarker(extractDb(driver), space, destination);
48
+ }
49
+
50
+ async updateMarker(
51
+ driver: ControlDriverInstance<'mongo', 'mongo'>,
52
+ space: string,
53
+ expectedFrom: string,
54
+ destination: {
55
+ readonly storageHash: string;
56
+ readonly profileHash: string;
57
+ readonly invariants?: readonly string[];
58
+ },
59
+ ): Promise<boolean> {
60
+ return updateMarker(extractDb(driver), space, expectedFrom, destination);
61
+ }
62
+
63
+ async writeLedgerEntry(
64
+ driver: ControlDriverInstance<'mongo', 'mongo'>,
65
+ space: string,
66
+ entry: { readonly edgeId: string; readonly from: string; readonly to: string },
67
+ ): Promise<void> {
68
+ await writeLedgerEntry(extractDb(driver), space, entry);
69
+ }
70
+
71
+ async introspectSchema(driver: ControlDriverInstance<'mongo', 'mongo'>): Promise<MongoSchemaIR> {
72
+ return introspectSchema(extractDb(driver));
73
+ }
74
+ }
@@ -1,19 +1,19 @@
1
+ import type { ContractMarkerRecord } from '@prisma-next/contract/types';
2
+ import type { MongoControlAdapter } from '@prisma-next/family-mongo/control-adapter';
1
3
  import type {
2
4
  ControlDriverInstance,
3
5
  ControlFamilyInstance,
4
6
  } from '@prisma-next/framework-components/control';
5
- import type { MongoDriver } from '@prisma-next/mongo-lowering';
7
+ import type { MongoAdapter, MongoDriver } from '@prisma-next/mongo-lowering';
8
+ import type {
9
+ MongoDdlCommandVisitor,
10
+ MongoInspectionCommandVisitor,
11
+ } from '@prisma-next/mongo-query-ast/control';
6
12
  import type { MongoSchemaIR } from '@prisma-next/mongo-schema-ir';
7
- import {
8
- initMarker,
9
- type MongoRunnerDependencies,
10
- readMarker,
11
- updateMarker,
12
- writeLedgerEntry,
13
- } from '@prisma-next/target-mongo/control';
14
13
  import type { Db } from 'mongodb';
15
14
  import { createMongoAdapter } from '../mongo-adapter';
16
15
  import { MongoCommandExecutor, MongoInspectionExecutor } from './command-executor';
16
+ import { MongoControlAdapterImpl } from './mongo-control-adapter';
17
17
 
18
18
  export function extractDb(driver: ControlDriverInstance<'mongo', 'mongo'>): Db {
19
19
  const mongoDriver = driver as ControlDriverInstance<'mongo', 'mongo'> & { db?: Db };
@@ -26,23 +26,83 @@ export function extractDb(driver: ControlDriverInstance<'mongo', 'mongo'>): Db {
26
26
  return mongoDriver.db;
27
27
  }
28
28
 
29
+ /**
30
+ * Marker / ledger operations the Mongo runner depends on. Every method
31
+ * takes a `space` parameter so each loaded contract space addresses its
32
+ * own marker row independently — see ADR 212 for the per-space
33
+ * mechanism.
34
+ */
35
+ export interface MarkerOperations {
36
+ readMarker(space: string): Promise<ContractMarkerRecord | null>;
37
+ initMarker(
38
+ space: string,
39
+ destination: {
40
+ readonly storageHash: string;
41
+ readonly profileHash: string;
42
+ readonly invariants?: readonly string[];
43
+ },
44
+ ): Promise<void>;
45
+ updateMarker(
46
+ space: string,
47
+ expectedFrom: string,
48
+ destination: {
49
+ readonly storageHash: string;
50
+ readonly profileHash: string;
51
+ readonly invariants?: readonly string[];
52
+ },
53
+ ): Promise<boolean>;
54
+ writeLedgerEntry(
55
+ space: string,
56
+ entry: {
57
+ readonly edgeId: string;
58
+ readonly from: string;
59
+ readonly to: string;
60
+ },
61
+ ): Promise<void>;
62
+ }
63
+
64
+ export interface MongoRunnerDependencies {
65
+ readonly commandExecutor: MongoDdlCommandVisitor<Promise<void>>;
66
+ readonly inspectionExecutor: MongoInspectionCommandVisitor<Promise<Record<string, unknown>[]>>;
67
+ readonly adapter: MongoAdapter;
68
+ readonly driver: MongoDriver;
69
+ readonly markerOps: MarkerOperations;
70
+ readonly introspectSchema: () => Promise<MongoSchemaIR>;
71
+ }
72
+
73
+ /**
74
+ * Build the runner-dependencies envelope. `controlAdapter` is the
75
+ * dispatch surface for wire-level Mongo CAS operations (marker reads,
76
+ * marker advances, ledger appends, introspection); the envelope's
77
+ * `markerOps` shim simply forwards each call through it. When the
78
+ * caller already has a `MongoControlAdapter` on the control stack it
79
+ * can pass it in; otherwise a default `MongoControlAdapterImpl` is
80
+ * constructed locally.
81
+ */
29
82
  export function createMongoRunnerDeps(
30
83
  controlDriver: ControlDriverInstance<'mongo', 'mongo'>,
31
84
  driver: MongoDriver,
32
- family: ControlFamilyInstance<'mongo', MongoSchemaIR>,
85
+ // Vestigial after the M2.5 family→adapter SPI dispatch refactor: the runner
86
+ // dependencies now route every wire-level call through `controlAdapter`, so
87
+ // the `family` instance is no longer consulted. Kept on the signature to
88
+ // avoid rippling through ~14 call sites mid-orchestration; a follow-up that
89
+ // already touches this factory should drop the parameter outright.
90
+ _family: ControlFamilyInstance<'mongo', MongoSchemaIR>,
91
+ controlAdapter: MongoControlAdapter<'mongo'> = new MongoControlAdapterImpl(),
33
92
  ): MongoRunnerDependencies {
34
- const db = extractDb(controlDriver);
35
93
  return {
36
- commandExecutor: new MongoCommandExecutor(db),
37
- inspectionExecutor: new MongoInspectionExecutor(db),
94
+ commandExecutor: new MongoCommandExecutor(extractDb(controlDriver)),
95
+ inspectionExecutor: new MongoInspectionExecutor(extractDb(controlDriver)),
38
96
  adapter: createMongoAdapter(),
39
97
  driver,
40
98
  markerOps: {
41
- readMarker: (space) => readMarker(db, space),
42
- initMarker: (space, dest) => initMarker(db, space, dest),
43
- updateMarker: (space, expectedFrom, dest) => updateMarker(db, space, expectedFrom, dest),
44
- writeLedgerEntry: (space, entry) => writeLedgerEntry(db, space, entry),
99
+ readMarker: (space) => controlAdapter.readMarker(controlDriver, space),
100
+ initMarker: (space, dest) => controlAdapter.initMarker(controlDriver, space, dest),
101
+ updateMarker: (space, expectedFrom, dest) =>
102
+ controlAdapter.updateMarker(controlDriver, space, expectedFrom, dest),
103
+ writeLedgerEntry: (space, entry) =>
104
+ controlAdapter.writeLedgerEntry(controlDriver, space, entry),
45
105
  },
46
- introspectSchema: () => family.introspect({ driver: controlDriver }),
106
+ introspectSchema: () => controlAdapter.introspectSchema(controlDriver),
47
107
  };
48
108
  }
@@ -1,16 +1,31 @@
1
- import type { ControlAdapterDescriptor } from '@prisma-next/framework-components/control';
1
+ import type { MongoControlAdapterDescriptor } from '@prisma-next/family-mongo/control-adapter';
2
2
 
3
3
  export { MongoCommandExecutor, MongoInspectionExecutor } from '../core/command-executor';
4
4
  export { introspectSchema } from '../core/introspect-schema';
5
+ export {
6
+ initMarker,
7
+ readAllMarkers,
8
+ readMarker,
9
+ updateMarker,
10
+ writeLedgerEntry,
11
+ } from '../core/marker-ledger';
12
+ export { MongoControlAdapterImpl } from '../core/mongo-control-adapter';
5
13
  export {
6
14
  createMongoControlDriver,
7
15
  type MongoControlDriverInstance,
8
16
  } from '../core/mongo-control-driver';
9
- export { createMongoRunnerDeps, extractDb } from '../core/runner-deps';
17
+ export {
18
+ createMongoRunnerDeps,
19
+ extractDb,
20
+ type MarkerOperations,
21
+ type MongoRunnerDependencies,
22
+ } from '../core/runner-deps';
23
+ export { createMongoAdapter } from '../mongo-adapter';
10
24
 
11
25
  import { mongoCodecDescriptors } from '../core/codecs';
26
+ import { MongoControlAdapterImpl } from '../core/mongo-control-adapter';
12
27
 
13
- const mongoAdapterDescriptor: ControlAdapterDescriptor<'mongo', 'mongo'> = {
28
+ export const mongoAdapterDescriptor: MongoControlAdapterDescriptor<'mongo'> = {
14
29
  kind: 'adapter',
15
30
  id: 'mongo',
16
31
  familyId: 'mongo',
@@ -42,7 +57,7 @@ const mongoAdapterDescriptor: ControlAdapterDescriptor<'mongo', 'mongo'> = {
42
57
  },
43
58
  },
44
59
  create(_stack) {
45
- return { familyId: 'mongo' as const, targetId: 'mongo' as const };
60
+ return new MongoControlAdapterImpl();
46
61
  },
47
62
  };
48
63