@prisma-next/adapter-mongo 0.12.0-dev.31 → 0.12.0-dev.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,37 +1,37 @@
1
1
  {
2
2
  "name": "@prisma-next/adapter-mongo",
3
- "version": "0.12.0-dev.31",
3
+ "version": "0.12.0-dev.33",
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.12.0-dev.31",
10
- "@prisma-next/contract": "0.12.0-dev.31",
11
- "@prisma-next/errors": "0.12.0-dev.31",
12
- "@prisma-next/family-mongo": "0.12.0-dev.31",
13
- "@prisma-next/framework-components": "0.12.0-dev.31",
14
- "@prisma-next/migration-tools": "0.12.0-dev.31",
15
- "@prisma-next/mongo-codec": "0.12.0-dev.31",
16
- "@prisma-next/mongo-contract": "0.12.0-dev.31",
17
- "@prisma-next/mongo-lowering": "0.12.0-dev.31",
18
- "@prisma-next/mongo-query-ast": "0.12.0-dev.31",
19
- "@prisma-next/mongo-schema-ir": "0.12.0-dev.31",
20
- "@prisma-next/mongo-value": "0.12.0-dev.31",
21
- "@prisma-next/mongo-wire": "0.12.0-dev.31",
22
- "@prisma-next/operations": "0.12.0-dev.31",
23
- "@prisma-next/utils": "0.12.0-dev.31",
9
+ "@prisma-next/config": "0.12.0-dev.33",
10
+ "@prisma-next/contract": "0.12.0-dev.33",
11
+ "@prisma-next/errors": "0.12.0-dev.33",
12
+ "@prisma-next/family-mongo": "0.12.0-dev.33",
13
+ "@prisma-next/framework-components": "0.12.0-dev.33",
14
+ "@prisma-next/migration-tools": "0.12.0-dev.33",
15
+ "@prisma-next/mongo-codec": "0.12.0-dev.33",
16
+ "@prisma-next/mongo-contract": "0.12.0-dev.33",
17
+ "@prisma-next/mongo-lowering": "0.12.0-dev.33",
18
+ "@prisma-next/mongo-query-ast": "0.12.0-dev.33",
19
+ "@prisma-next/mongo-schema-ir": "0.12.0-dev.33",
20
+ "@prisma-next/mongo-value": "0.12.0-dev.33",
21
+ "@prisma-next/mongo-wire": "0.12.0-dev.33",
22
+ "@prisma-next/operations": "0.12.0-dev.33",
23
+ "@prisma-next/utils": "0.12.0-dev.33",
24
24
  "arktype": "^2.2.0"
25
25
  },
26
26
  "devDependencies": {
27
27
  "mongodb": "^7.2.0",
28
- "@prisma-next/driver-mongo": "0.12.0-dev.31",
29
- "@prisma-next/errors": "0.12.0-dev.31",
30
- "@prisma-next/mongo-contract-psl": "0.12.0-dev.31",
31
- "@prisma-next/psl-parser": "0.12.0-dev.31",
32
- "@prisma-next/test-utils": "0.12.0-dev.31",
33
- "@prisma-next/tsconfig": "0.12.0-dev.31",
34
- "@prisma-next/tsdown": "0.12.0-dev.31",
28
+ "@prisma-next/driver-mongo": "0.12.0-dev.33",
29
+ "@prisma-next/errors": "0.12.0-dev.33",
30
+ "@prisma-next/mongo-contract-psl": "0.12.0-dev.33",
31
+ "@prisma-next/psl-parser": "0.12.0-dev.33",
32
+ "@prisma-next/test-utils": "0.12.0-dev.33",
33
+ "@prisma-next/tsconfig": "0.12.0-dev.33",
34
+ "@prisma-next/tsdown": "0.12.0-dev.33",
35
35
  "mongodb-memory-server": "11.1.0",
36
36
  "tsdown": "0.22.0",
37
37
  "typescript": "5.9.3",
@@ -1,7 +1,6 @@
1
- import type { ContractMarkerRecord, LedgerEntryRecord } from '@prisma-next/contract/types';
2
- import { parseMarkerRowSafely, withMarkerReadErrorHandling } from '@prisma-next/errors/execution';
3
- import { ledgerOriginFromStored } from '@prisma-next/migration-tools/ledger-origin';
4
- import {
1
+ import type { ContractMarkerRecord } from '@prisma-next/contract/types';
2
+ import { parseMarkerRowSafely } from '@prisma-next/errors/execution';
3
+ import type {
5
4
  RawAggregateCommand,
6
5
  RawFindOneAndUpdateCommand,
7
6
  RawInsertOneCommand,
@@ -9,21 +8,10 @@ import {
9
8
  import { type } from 'arktype';
10
9
  import type { Db, Document, UpdateFilter } from 'mongodb';
11
10
 
12
- const COLLECTION = '_prisma_migrations';
13
- const MONGO_MARKER_COLLECTION = `_prisma_migrations marker documents in ${COLLECTION}`;
14
- const MONGO_LEDGER_COLLECTION = `_prisma_migrations ledger documents in ${COLLECTION}`;
11
+ export const COLLECTION = '_prisma_migrations';
12
+ export const MONGO_MARKER_COLLECTION = `_prisma_migrations marker documents in ${COLLECTION}`;
13
+ export const MONGO_LEDGER_COLLECTION = `_prisma_migrations ledger documents in ${COLLECTION}`;
15
14
 
16
- /**
17
- * Marker doc shape.
18
- *
19
- * Same fields as the SQL marker row but camelCase + Mongo-native types:
20
- * `Date` is BSON-hydrated, `meta` is a native object (not JSON-stringified),
21
- * `_id` and any extension fields are tolerated. `invariants?` is optional —
22
- * absent reads as `[]` (schemaless default); present-but-malformed throws.
23
- *
24
- * `space` is required: every marker doc is keyed by its space id (`_id`)
25
- * and stamped with a matching `space` field for partitioned reads.
26
- */
27
15
  const MongoMarkerDocSchema = type({
28
16
  space: 'string',
29
17
  storageHash: 'string',
@@ -37,7 +25,7 @@ const MongoMarkerDocSchema = type({
37
25
  '+': 'delete',
38
26
  });
39
27
 
40
- function parseMongoMarkerDoc(doc: unknown): ContractMarkerRecord {
28
+ export function parseMongoMarkerDoc(doc: unknown): ContractMarkerRecord {
41
29
  const result = MongoMarkerDocSchema(doc);
42
30
  if (result instanceof type.errors) {
43
31
  throw new Error(`Invalid marker doc on ${COLLECTION}: ${result.summary}`);
@@ -54,258 +42,31 @@ function parseMongoMarkerDoc(doc: unknown): ContractMarkerRecord {
54
42
  };
55
43
  }
56
44
 
57
- function parseMongoMarkerDocSafely(doc: unknown, space: string): ContractMarkerRecord {
45
+ export function parseMongoMarkerDocSafely(doc: unknown, space: string): ContractMarkerRecord {
58
46
  return parseMarkerRowSafely(doc, parseMongoMarkerDoc, {
59
47
  space,
60
48
  markerLocation: MONGO_MARKER_COLLECTION,
61
49
  });
62
50
  }
63
51
 
64
- async function executeAggregate(db: Db, cmd: RawAggregateCommand): Promise<Document[]> {
52
+ export async function executeAggregate(db: Db, cmd: RawAggregateCommand): Promise<Document[]> {
65
53
  return db
66
54
  .collection(cmd.collection)
67
55
  .aggregate(cmd.pipeline as Record<string, unknown>[])
68
56
  .toArray();
69
57
  }
70
58
 
71
- async function executeInsertOne(db: Db, cmd: RawInsertOneCommand): Promise<void> {
59
+ export async function executeInsertOne(db: Db, cmd: RawInsertOneCommand): Promise<void> {
72
60
  await db.collection(cmd.collection).insertOne(cmd.document);
73
61
  }
74
62
 
75
- async function executeFindOneAndUpdate(
63
+ export async function executeFindOneAndUpdate(
76
64
  db: Db,
77
65
  cmd: RawFindOneAndUpdateCommand,
78
66
  ): Promise<Document | null> {
79
- // `cmd.update` is `Document | ReadonlyArray<Document>` per the AST. The
80
- // MongoDB driver's `findOneAndUpdate` accepts the same shape under the
81
- // type `UpdateFilter<T> | Document[]`. The driver's runtime path handles
82
- // both forms identically — pipelines (array) and update docs (object).
83
- // One cast to that union keeps the call single-arm.
84
67
  return db
85
68
  .collection(cmd.collection)
86
69
  .findOneAndUpdate(cmd.filter, cmd.update as UpdateFilter<Document> | Document[], {
87
70
  upsert: cmd.upsert,
88
71
  });
89
72
  }
90
-
91
- /**
92
- * Reads the marker document for the given contract space, or returns
93
- * `null` if no marker has been written for that space yet. Each space
94
- * owns one row keyed by `_id: <space>` — see ADR 212 for the per-space
95
- * mechanism this enables.
96
- */
97
- export async function readMarker(db: Db, space: string): Promise<ContractMarkerRecord | null> {
98
- const markerContext = { space, markerLocation: MONGO_MARKER_COLLECTION };
99
- const docs = await withMarkerReadErrorHandling(
100
- () =>
101
- executeAggregate(
102
- db,
103
- new RawAggregateCommand(COLLECTION, [{ $match: { _id: space, space } }, { $limit: 1 }]),
104
- ),
105
- markerContext,
106
- );
107
- const doc = docs[0];
108
- if (!doc) return null;
109
- return parseMongoMarkerDocSafely(doc, space);
110
- }
111
-
112
- /**
113
- * Reads every marker doc in the collection (one per contract space)
114
- * and returns them keyed by `space`. Used by the per-space verifier
115
- * to detect marker-vs-on-disk drift and orphan marker rows. Returns
116
- * an empty map when no marker docs have been written yet.
117
- *
118
- * Marker docs are keyed by `_id: <space>` (string); ledger entries
119
- * live in the same collection but use a driver-generated `ObjectId`
120
- * `_id` plus `type: 'ledger'`. The filter selects string-keyed docs
121
- * with a `space` field, which excludes ledger entries by construction.
122
- */
123
- export async function readAllMarkers(db: Db): Promise<ReadonlyMap<string, ContractMarkerRecord>> {
124
- const markerContext = { space: 'app', markerLocation: MONGO_MARKER_COLLECTION };
125
- const docs = await withMarkerReadErrorHandling(
126
- () =>
127
- executeAggregate(
128
- db,
129
- new RawAggregateCommand(COLLECTION, [
130
- {
131
- $match: {
132
- _id: { $type: 'string' },
133
- space: { $type: 'string' },
134
- $expr: { $eq: ['$_id', '$space'] },
135
- },
136
- },
137
- ]),
138
- ),
139
- markerContext,
140
- );
141
- const out = new Map<string, ContractMarkerRecord>();
142
- for (const doc of docs) {
143
- const space = doc['space'];
144
- /* 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`. */
145
- if (typeof space !== 'string') continue;
146
- out.set(space, parseMongoMarkerDocSafely(doc, space));
147
- }
148
- return out;
149
- }
150
-
151
- export async function initMarker(
152
- db: Db,
153
- space: string,
154
- destination: {
155
- readonly storageHash: string;
156
- readonly profileHash: string;
157
- readonly invariants?: readonly string[];
158
- },
159
- ): Promise<void> {
160
- const cmd = new RawInsertOneCommand(COLLECTION, {
161
- _id: space,
162
- space,
163
- storageHash: destination.storageHash,
164
- profileHash: destination.profileHash,
165
- contractJson: null,
166
- canonicalVersion: null,
167
- updatedAt: new Date(),
168
- appTag: null,
169
- meta: {},
170
- invariants: destination.invariants ?? [],
171
- });
172
- await executeInsertOne(db, cmd);
173
- }
174
-
175
- /**
176
- * Updates the marker doc for the given space atomically (CAS on
177
- * `expectedFrom`).
178
- *
179
- * `destination.invariants`:
180
- * - `undefined` → existing field left untouched.
181
- * - explicit value → merged into the existing field server-side via an
182
- * aggregation pipeline (`$setUnion + $sortArray`), atomic at the
183
- * document level. `[]` is a no-op merge.
184
- */
185
- export async function updateMarker(
186
- db: Db,
187
- space: string,
188
- expectedFrom: string,
189
- destination: {
190
- readonly storageHash: string;
191
- readonly profileHash: string;
192
- readonly invariants?: readonly string[];
193
- },
194
- ): Promise<boolean> {
195
- const setBase: Record<string, unknown> = {
196
- storageHash: destination.storageHash,
197
- profileHash: destination.profileHash,
198
- updatedAt: new Date(),
199
- };
200
- // When invariants is supplied, use an aggregation pipeline so the
201
- // merge runs server-side against the doc's current value (atomic, no
202
- // read-then-write window). When omitted, a regular update doc keeps
203
- // the field untouched.
204
- const update: Document | Document[] =
205
- destination.invariants === undefined
206
- ? { $set: setBase }
207
- : [
208
- {
209
- $set: {
210
- ...setBase,
211
- invariants: {
212
- $sortArray: {
213
- input: { $setUnion: [{ $ifNull: ['$invariants', []] }, destination.invariants] },
214
- sortBy: 1,
215
- },
216
- },
217
- },
218
- },
219
- ];
220
- const cmd = new RawFindOneAndUpdateCommand(
221
- COLLECTION,
222
- { _id: space, space, storageHash: expectedFrom },
223
- update,
224
- false,
225
- );
226
- const result = await executeFindOneAndUpdate(db, cmd);
227
- return result !== null;
228
- }
229
-
230
- /**
231
- * Reads per-migration ledger entries in apply order. When `space` is omitted,
232
- * returns rows for every space. Returns `[]` when no ledger documents exist yet.
233
- */
234
- export async function readLedger(db: Db, space?: string): Promise<readonly LedgerEntryRecord[]> {
235
- const ledgerContext = { space: space ?? '*', markerLocation: MONGO_LEDGER_COLLECTION };
236
- const matchStage: Record<string, unknown> = { type: 'ledger' };
237
- if (space !== undefined) {
238
- matchStage['space'] = space;
239
- }
240
- const docs = await withMarkerReadErrorHandling(
241
- () =>
242
- executeAggregate(
243
- db,
244
- new RawAggregateCommand(COLLECTION, [{ $match: matchStage }, { $sort: { _id: 1 } }]),
245
- ),
246
- ledgerContext,
247
- );
248
-
249
- const entries: LedgerEntryRecord[] = [];
250
- for (const doc of docs) {
251
- const migrationName = doc['migrationName'];
252
- const migrationHash = doc['migrationHash'];
253
- const from = doc['from'];
254
- const to = doc['to'];
255
- const docSpace = doc['space'];
256
- if (typeof migrationName !== 'string' || typeof migrationHash !== 'string') {
257
- continue;
258
- }
259
- if (typeof from !== 'string' || typeof to !== 'string') {
260
- continue;
261
- }
262
- if (typeof docSpace !== 'string') {
263
- continue;
264
- }
265
- const appliedAt = doc['appliedAt'];
266
- const appliedAtDate =
267
- appliedAt instanceof Date
268
- ? appliedAt
269
- : appliedAt !== undefined
270
- ? new Date(String(appliedAt))
271
- : new Date();
272
- const operations = doc['operations'];
273
- const opList = Array.isArray(operations) ? operations : [];
274
- entries.push({
275
- space: docSpace,
276
- migrationName,
277
- migrationHash,
278
- from: ledgerOriginFromStored(from),
279
- to,
280
- appliedAt: appliedAtDate,
281
- operationCount: opList.length,
282
- });
283
- }
284
- return entries;
285
- }
286
-
287
- export async function writeLedgerEntry(
288
- db: Db,
289
- space: string,
290
- entry: {
291
- readonly edgeId: string;
292
- readonly from: string;
293
- readonly to: string;
294
- readonly migrationName: string;
295
- readonly migrationHash: string;
296
- readonly operations: readonly unknown[];
297
- },
298
- ): Promise<void> {
299
- const cmd = new RawInsertOneCommand(COLLECTION, {
300
- type: 'ledger',
301
- space,
302
- edgeId: entry.edgeId,
303
- from: entry.from,
304
- to: entry.to,
305
- migrationName: entry.migrationName,
306
- migrationHash: entry.migrationHash,
307
- operations: entry.operations,
308
- appliedAt: new Date(),
309
- });
310
- await executeInsertOne(db, cmd);
311
- }
@@ -1,15 +1,24 @@
1
1
  import type { ContractMarkerRecord, LedgerEntryRecord } from '@prisma-next/contract/types';
2
+ import { withMarkerReadErrorHandling } from '@prisma-next/errors/execution';
2
3
  import type { MongoControlAdapter } from '@prisma-next/family-mongo/control-adapter';
3
4
  import type { ControlDriverInstance } from '@prisma-next/framework-components/control';
5
+ import { ledgerOriginFromStored } from '@prisma-next/migration-tools/ledger-origin';
6
+ import {
7
+ RawAggregateCommand,
8
+ RawFindOneAndUpdateCommand,
9
+ RawInsertOneCommand,
10
+ } from '@prisma-next/mongo-query-ast/execution';
4
11
  import type { MongoSchemaIR } from '@prisma-next/mongo-schema-ir';
12
+ import type { Document } from 'mongodb';
5
13
  import { introspectSchema } from './introspect-schema';
6
14
  import {
7
- initMarker,
8
- readAllMarkers,
9
- readLedger,
10
- readMarker,
11
- updateMarker,
12
- writeLedgerEntry,
15
+ COLLECTION,
16
+ executeAggregate,
17
+ executeFindOneAndUpdate,
18
+ executeInsertOne,
19
+ MONGO_LEDGER_COLLECTION,
20
+ MONGO_MARKER_COLLECTION,
21
+ parseMongoMarkerDocSafely,
13
22
  } from './marker-ledger';
14
23
  import { extractDb } from './runner-deps';
15
24
 
@@ -17,7 +26,7 @@ import { extractDb } from './runner-deps';
17
26
  * Mongo control adapter for control-plane operations like introspection
18
27
  * and marker-ledger CAS. Implements the family-level `MongoControlAdapter`
19
28
  * SPI by extracting the underlying `Db` from the framework-shaped driver
20
- * and forwarding to the wire-level helpers in this package.
29
+ * per call.
21
30
  */
22
31
  export class MongoControlAdapterImpl implements MongoControlAdapter<'mongo'> {
23
32
  readonly familyId = 'mongo' as const;
@@ -27,13 +36,50 @@ export class MongoControlAdapterImpl implements MongoControlAdapter<'mongo'> {
27
36
  driver: ControlDriverInstance<'mongo', 'mongo'>,
28
37
  space: string,
29
38
  ): Promise<ContractMarkerRecord | null> {
30
- return readMarker(extractDb(driver), space);
39
+ const db = extractDb(driver);
40
+ const markerContext = { space, markerLocation: MONGO_MARKER_COLLECTION };
41
+ const docs = await withMarkerReadErrorHandling(
42
+ () =>
43
+ executeAggregate(
44
+ db,
45
+ new RawAggregateCommand(COLLECTION, [{ $match: { _id: space, space } }, { $limit: 1 }]),
46
+ ),
47
+ markerContext,
48
+ );
49
+ const doc = docs[0];
50
+ if (!doc) return null;
51
+ return parseMongoMarkerDocSafely(doc, space);
31
52
  }
32
53
 
33
54
  async readAllMarkers(
34
55
  driver: ControlDriverInstance<'mongo', 'mongo'>,
35
56
  ): Promise<ReadonlyMap<string, ContractMarkerRecord>> {
36
- return readAllMarkers(extractDb(driver));
57
+ const db = extractDb(driver);
58
+ const markerContext = { space: 'app', markerLocation: MONGO_MARKER_COLLECTION };
59
+ const docs = await withMarkerReadErrorHandling(
60
+ () =>
61
+ executeAggregate(
62
+ db,
63
+ new RawAggregateCommand(COLLECTION, [
64
+ {
65
+ $match: {
66
+ _id: { $type: 'string' },
67
+ space: { $type: 'string' },
68
+ $expr: { $eq: ['$_id', '$space'] },
69
+ },
70
+ },
71
+ ]),
72
+ ),
73
+ markerContext,
74
+ );
75
+ const out = new Map<string, ContractMarkerRecord>();
76
+ for (const doc of docs) {
77
+ const space = doc['space'];
78
+ /* 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`. */
79
+ if (typeof space !== 'string') continue;
80
+ out.set(space, parseMongoMarkerDocSafely(doc, space));
81
+ }
82
+ return out;
37
83
  }
38
84
 
39
85
  async initMarker(
@@ -45,7 +91,20 @@ export class MongoControlAdapterImpl implements MongoControlAdapter<'mongo'> {
45
91
  readonly invariants?: readonly string[];
46
92
  },
47
93
  ): Promise<void> {
48
- await initMarker(extractDb(driver), space, destination);
94
+ const db = extractDb(driver);
95
+ const cmd = new RawInsertOneCommand(COLLECTION, {
96
+ _id: space,
97
+ space,
98
+ storageHash: destination.storageHash,
99
+ profileHash: destination.profileHash,
100
+ contractJson: null,
101
+ canonicalVersion: null,
102
+ updatedAt: new Date(),
103
+ appTag: null,
104
+ meta: {},
105
+ invariants: destination.invariants ?? [],
106
+ });
107
+ await executeInsertOne(db, cmd);
49
108
  }
50
109
 
51
110
  async updateMarker(
@@ -58,7 +117,38 @@ export class MongoControlAdapterImpl implements MongoControlAdapter<'mongo'> {
58
117
  readonly invariants?: readonly string[];
59
118
  },
60
119
  ): Promise<boolean> {
61
- return updateMarker(extractDb(driver), space, expectedFrom, destination);
120
+ const db = extractDb(driver);
121
+ const setBase: Record<string, unknown> = {
122
+ storageHash: destination.storageHash,
123
+ profileHash: destination.profileHash,
124
+ updatedAt: new Date(),
125
+ };
126
+ const update: Document | Document[] =
127
+ destination.invariants === undefined
128
+ ? { $set: setBase }
129
+ : [
130
+ {
131
+ $set: {
132
+ ...setBase,
133
+ invariants: {
134
+ $sortArray: {
135
+ input: {
136
+ $setUnion: [{ $ifNull: ['$invariants', []] }, destination.invariants],
137
+ },
138
+ sortBy: 1,
139
+ },
140
+ },
141
+ },
142
+ },
143
+ ];
144
+ const cmd = new RawFindOneAndUpdateCommand(
145
+ COLLECTION,
146
+ { _id: space, space, storageHash: expectedFrom },
147
+ update,
148
+ false,
149
+ );
150
+ const result = await executeFindOneAndUpdate(db, cmd);
151
+ return result !== null;
62
152
  }
63
153
 
64
154
  async writeLedgerEntry(
@@ -73,14 +163,76 @@ export class MongoControlAdapterImpl implements MongoControlAdapter<'mongo'> {
73
163
  readonly operations: readonly unknown[];
74
164
  },
75
165
  ): Promise<void> {
76
- await writeLedgerEntry(extractDb(driver), space, entry);
166
+ const db = extractDb(driver);
167
+ const cmd = new RawInsertOneCommand(COLLECTION, {
168
+ type: 'ledger',
169
+ space,
170
+ edgeId: entry.edgeId,
171
+ from: entry.from,
172
+ to: entry.to,
173
+ migrationName: entry.migrationName,
174
+ migrationHash: entry.migrationHash,
175
+ operations: entry.operations,
176
+ appliedAt: new Date(),
177
+ });
178
+ await executeInsertOne(db, cmd);
77
179
  }
78
180
 
79
181
  async readLedger(
80
182
  driver: ControlDriverInstance<'mongo', 'mongo'>,
81
183
  space?: string,
82
184
  ): Promise<readonly LedgerEntryRecord[]> {
83
- return readLedger(extractDb(driver), space);
185
+ const db = extractDb(driver);
186
+ const ledgerContext = { space: space ?? '*', markerLocation: MONGO_LEDGER_COLLECTION };
187
+ const matchStage: Record<string, unknown> = { type: 'ledger' };
188
+ if (space !== undefined) {
189
+ matchStage['space'] = space;
190
+ }
191
+ const docs = await withMarkerReadErrorHandling(
192
+ () =>
193
+ executeAggregate(
194
+ db,
195
+ new RawAggregateCommand(COLLECTION, [{ $match: matchStage }, { $sort: { _id: 1 } }]),
196
+ ),
197
+ ledgerContext,
198
+ );
199
+
200
+ const entries: LedgerEntryRecord[] = [];
201
+ for (const doc of docs) {
202
+ const migrationName = doc['migrationName'];
203
+ const migrationHash = doc['migrationHash'];
204
+ const from = doc['from'];
205
+ const to = doc['to'];
206
+ const docSpace = doc['space'];
207
+ if (typeof migrationName !== 'string' || typeof migrationHash !== 'string') {
208
+ continue;
209
+ }
210
+ if (typeof from !== 'string' || typeof to !== 'string') {
211
+ continue;
212
+ }
213
+ if (typeof docSpace !== 'string') {
214
+ continue;
215
+ }
216
+ const appliedAt = doc['appliedAt'];
217
+ const appliedAtDate =
218
+ appliedAt instanceof Date
219
+ ? appliedAt
220
+ : appliedAt !== undefined
221
+ ? new Date(String(appliedAt))
222
+ : new Date();
223
+ const operations = doc['operations'];
224
+ const opList = Array.isArray(operations) ? operations : [];
225
+ entries.push({
226
+ space: docSpace,
227
+ migrationName,
228
+ migrationHash,
229
+ from: ledgerOriginFromStored(from),
230
+ to,
231
+ appliedAt: appliedAtDate,
232
+ operationCount: opList.length,
233
+ });
234
+ }
235
+ return entries;
84
236
  }
85
237
 
86
238
  async introspectSchema(driver: ControlDriverInstance<'mongo', 'mongo'>): Promise<MongoSchemaIR> {
@@ -1,15 +1,10 @@
1
+ import type { ContractMarkerRecord, LedgerEntryRecord } from '@prisma-next/contract/types';
1
2
  import type { MongoControlAdapterDescriptor } from '@prisma-next/family-mongo/control-adapter';
3
+ import type { ControlDriverInstance } from '@prisma-next/framework-components/control';
4
+ import type { Db } from 'mongodb';
2
5
 
3
6
  export { MongoCommandExecutor, MongoInspectionExecutor } from '../core/command-executor';
4
7
  export { introspectSchema } from '../core/introspect-schema';
5
- export {
6
- initMarker,
7
- readAllMarkers,
8
- readLedger,
9
- readMarker,
10
- updateMarker,
11
- writeLedgerEntry,
12
- } from '../core/marker-ledger';
13
8
  export { MongoControlAdapterImpl } from '../core/mongo-control-adapter';
14
9
  export {
15
10
  createMongoControlDriver,
@@ -26,6 +21,75 @@ export { createMongoAdapter } from '../mongo-adapter';
26
21
  import { mongoCodecDescriptors } from '../core/codecs';
27
22
  import { MongoControlAdapterImpl } from '../core/mongo-control-adapter';
28
23
 
24
+ const defaultControlAdapter = new MongoControlAdapterImpl();
25
+
26
+ function controlDriverFromDb(db: Db): ControlDriverInstance<'mongo', 'mongo'> & { db: Db } {
27
+ return {
28
+ familyId: 'mongo',
29
+ targetId: 'mongo',
30
+ db,
31
+ query: () => Promise.reject(new Error('MongoDB control driver does not support SQL queries')),
32
+ close: async () => {},
33
+ };
34
+ }
35
+
36
+ export async function readMarker(db: Db, space: string): Promise<ContractMarkerRecord | null> {
37
+ return defaultControlAdapter.readMarker(controlDriverFromDb(db), space);
38
+ }
39
+
40
+ export async function readAllMarkers(db: Db): Promise<ReadonlyMap<string, ContractMarkerRecord>> {
41
+ return defaultControlAdapter.readAllMarkers(controlDriverFromDb(db));
42
+ }
43
+
44
+ export async function initMarker(
45
+ db: Db,
46
+ space: string,
47
+ destination: {
48
+ readonly storageHash: string;
49
+ readonly profileHash: string;
50
+ readonly invariants?: readonly string[];
51
+ },
52
+ ): Promise<void> {
53
+ await defaultControlAdapter.initMarker(controlDriverFromDb(db), space, destination);
54
+ }
55
+
56
+ export async function updateMarker(
57
+ db: Db,
58
+ space: string,
59
+ expectedFrom: string,
60
+ destination: {
61
+ readonly storageHash: string;
62
+ readonly profileHash: string;
63
+ readonly invariants?: readonly string[];
64
+ },
65
+ ): Promise<boolean> {
66
+ return defaultControlAdapter.updateMarker(
67
+ controlDriverFromDb(db),
68
+ space,
69
+ expectedFrom,
70
+ destination,
71
+ );
72
+ }
73
+
74
+ export async function readLedger(db: Db, space?: string): Promise<readonly LedgerEntryRecord[]> {
75
+ return defaultControlAdapter.readLedger(controlDriverFromDb(db), space);
76
+ }
77
+
78
+ export async function writeLedgerEntry(
79
+ db: Db,
80
+ space: string,
81
+ entry: {
82
+ readonly edgeId: string;
83
+ readonly from: string;
84
+ readonly to: string;
85
+ readonly migrationName: string;
86
+ readonly migrationHash: string;
87
+ readonly operations: readonly unknown[];
88
+ },
89
+ ): Promise<void> {
90
+ await defaultControlAdapter.writeLedgerEntry(controlDriverFromDb(db), space, entry);
91
+ }
92
+
29
93
  export const mongoAdapterDescriptor: MongoControlAdapterDescriptor<'mongo'> = {
30
94
  kind: 'adapter',
31
95
  id: 'mongo',