@prisma-next/adapter-mongo 0.12.0-dev.5 → 0.12.0-dev.50

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,14 +1,24 @@
1
- import type { ContractMarkerRecord } from '@prisma-next/contract/types';
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
- readMarker,
10
- updateMarker,
11
- writeLedgerEntry,
15
+ COLLECTION,
16
+ executeAggregate,
17
+ executeFindOneAndUpdate,
18
+ executeInsertOne,
19
+ MONGO_LEDGER_COLLECTION,
20
+ MONGO_MARKER_COLLECTION,
21
+ parseMongoMarkerDocSafely,
12
22
  } from './marker-ledger';
13
23
  import { extractDb } from './runner-deps';
14
24
 
@@ -16,7 +26,7 @@ import { extractDb } from './runner-deps';
16
26
  * Mongo control adapter for control-plane operations like introspection
17
27
  * and marker-ledger CAS. Implements the family-level `MongoControlAdapter`
18
28
  * SPI by extracting the underlying `Db` from the framework-shaped driver
19
- * and forwarding to the wire-level helpers in this package.
29
+ * per call.
20
30
  */
21
31
  export class MongoControlAdapterImpl implements MongoControlAdapter<'mongo'> {
22
32
  readonly familyId = 'mongo' as const;
@@ -26,13 +36,50 @@ export class MongoControlAdapterImpl implements MongoControlAdapter<'mongo'> {
26
36
  driver: ControlDriverInstance<'mongo', 'mongo'>,
27
37
  space: string,
28
38
  ): Promise<ContractMarkerRecord | null> {
29
- 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);
30
52
  }
31
53
 
32
54
  async readAllMarkers(
33
55
  driver: ControlDriverInstance<'mongo', 'mongo'>,
34
56
  ): Promise<ReadonlyMap<string, ContractMarkerRecord>> {
35
- 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;
36
83
  }
37
84
 
38
85
  async initMarker(
@@ -44,7 +91,20 @@ export class MongoControlAdapterImpl implements MongoControlAdapter<'mongo'> {
44
91
  readonly invariants?: readonly string[];
45
92
  },
46
93
  ): Promise<void> {
47
- 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);
48
108
  }
49
109
 
50
110
  async updateMarker(
@@ -57,15 +117,122 @@ export class MongoControlAdapterImpl implements MongoControlAdapter<'mongo'> {
57
117
  readonly invariants?: readonly string[];
58
118
  },
59
119
  ): Promise<boolean> {
60
- 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;
61
152
  }
62
153
 
63
154
  async writeLedgerEntry(
64
155
  driver: ControlDriverInstance<'mongo', 'mongo'>,
65
156
  space: string,
66
- entry: { readonly edgeId: string; readonly from: string; readonly to: string },
157
+ entry: {
158
+ readonly edgeId: string;
159
+ readonly from: string;
160
+ readonly to: string;
161
+ readonly migrationName: string;
162
+ readonly migrationHash: string;
163
+ readonly operations: readonly unknown[];
164
+ },
67
165
  ): Promise<void> {
68
- 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);
179
+ }
180
+
181
+ async readLedger(
182
+ driver: ControlDriverInstance<'mongo', 'mongo'>,
183
+ space?: string,
184
+ ): Promise<readonly LedgerEntryRecord[]> {
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;
69
236
  }
70
237
 
71
238
  async introspectSchema(driver: ControlDriverInstance<'mongo', 'mongo'>): Promise<MongoSchemaIR> {
@@ -1,30 +1,8 @@
1
1
  import type { ControlDriverInstance } from '@prisma-next/framework-components/control';
2
- import type { Db, MongoClient } from 'mongodb';
2
+ import type { MongoControlDriverInstance } from '@prisma-next/mongo-lowering';
3
3
 
4
- export interface MongoControlDriverInstance extends ControlDriverInstance<'mongo', 'mongo'> {
5
- readonly db: Db;
6
- }
7
-
8
- class MongoControlDriverImpl implements MongoControlDriverInstance {
9
- readonly familyId = 'mongo' as const;
10
- readonly targetId = 'mongo' as const;
11
- readonly db: Db;
12
- readonly #client: MongoClient;
13
-
14
- constructor(db: Db, client: MongoClient) {
15
- this.db = db;
16
- this.#client = client;
17
- }
18
-
19
- query(): Promise<never> {
20
- throw new Error('MongoDB control driver does not support SQL queries');
21
- }
22
-
23
- async close(): Promise<void> {
24
- await this.#client.close();
25
- }
26
- }
27
-
28
- export function createMongoControlDriver(db: Db, client: MongoClient): MongoControlDriverInstance {
29
- return new MongoControlDriverImpl(db, client);
4
+ export function isMongoControlDriver(
5
+ driver: ControlDriverInstance<'mongo', string>,
6
+ ): driver is MongoControlDriverInstance {
7
+ return driver.familyId === 'mongo' && driver.targetId === 'mongo';
30
8
  }
@@ -14,16 +14,16 @@ import type { Db } from 'mongodb';
14
14
  import { createMongoAdapter } from '../mongo-adapter';
15
15
  import { MongoCommandExecutor, MongoInspectionExecutor } from './command-executor';
16
16
  import { MongoControlAdapterImpl } from './mongo-control-adapter';
17
+ import { isMongoControlDriver } from './mongo-control-driver';
17
18
 
18
19
  export function extractDb(driver: ControlDriverInstance<'mongo', 'mongo'>): Db {
19
- const mongoDriver = driver as ControlDriverInstance<'mongo', 'mongo'> & { db?: Db };
20
- if (!mongoDriver.db) {
20
+ if (!isMongoControlDriver(driver)) {
21
21
  throw new Error(
22
- 'Mongo control driver does not expose a db property. ' +
23
- 'Use mongoControlDriver.create() from `@prisma-next/driver-mongo/control`.',
22
+ 'Expected a Mongo control driver created by ' +
23
+ 'mongoControlDriver.create() from `@prisma-next/driver-mongo/control`.',
24
24
  );
25
25
  }
26
- return mongoDriver.db;
26
+ return driver.db;
27
27
  }
28
28
 
29
29
  /**
@@ -57,6 +57,9 @@ export interface MarkerOperations {
57
57
  readonly edgeId: string;
58
58
  readonly from: string;
59
59
  readonly to: string;
60
+ readonly migrationName: string;
61
+ readonly migrationHash: string;
62
+ readonly operations: readonly unknown[];
60
63
  },
61
64
  ): Promise<void>;
62
65
  }
@@ -82,11 +85,11 @@ export interface MongoRunnerDependencies {
82
85
  export function createMongoRunnerDeps(
83
86
  controlDriver: ControlDriverInstance<'mongo', 'mongo'>,
84
87
  driver: MongoDriver,
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.
88
+ // Vestigial after the family→adapter SPI refactor: the runner dependencies
89
+ // now route every wire-level call through `controlAdapter`, so the `family`
90
+ // instance is no longer consulted. Kept on the signature to avoid rippling
91
+ // through ~14 call sites; a follow-up that already touches this factory
92
+ // should drop the parameter outright.
90
93
  _family: ControlFamilyInstance<'mongo', MongoSchemaIR>,
91
94
  controlAdapter: MongoControlAdapter<'mongo'> = new MongoControlAdapterImpl(),
92
95
  ): MongoRunnerDependencies {
@@ -1,19 +1,13 @@
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
 
6
+ export type { MongoControlDriverInstance } from '@prisma-next/mongo-lowering';
3
7
  export { MongoCommandExecutor, MongoInspectionExecutor } from '../core/command-executor';
4
8
  export { introspectSchema } from '../core/introspect-schema';
5
- export {
6
- initMarker,
7
- readAllMarkers,
8
- readMarker,
9
- updateMarker,
10
- writeLedgerEntry,
11
- } from '../core/marker-ledger';
12
9
  export { MongoControlAdapterImpl } from '../core/mongo-control-adapter';
13
- export {
14
- createMongoControlDriver,
15
- type MongoControlDriverInstance,
16
- } from '../core/mongo-control-driver';
10
+ export { isMongoControlDriver } from '../core/mongo-control-driver';
17
11
  export {
18
12
  createMongoRunnerDeps,
19
13
  extractDb,
@@ -25,6 +19,74 @@ export { createMongoAdapter } from '../mongo-adapter';
25
19
  import { mongoCodecDescriptors } from '../core/codecs';
26
20
  import { MongoControlAdapterImpl } from '../core/mongo-control-adapter';
27
21
 
22
+ const defaultControlAdapter = new MongoControlAdapterImpl();
23
+
24
+ function controlDriverFromDb(db: Db): ControlDriverInstance<'mongo', 'mongo'> & { db: Db } {
25
+ return {
26
+ familyId: 'mongo',
27
+ targetId: 'mongo',
28
+ db,
29
+ close: async () => {},
30
+ };
31
+ }
32
+
33
+ export async function readMarker(db: Db, space: string): Promise<ContractMarkerRecord | null> {
34
+ return defaultControlAdapter.readMarker(controlDriverFromDb(db), space);
35
+ }
36
+
37
+ export async function readAllMarkers(db: Db): Promise<ReadonlyMap<string, ContractMarkerRecord>> {
38
+ return defaultControlAdapter.readAllMarkers(controlDriverFromDb(db));
39
+ }
40
+
41
+ export async function initMarker(
42
+ db: Db,
43
+ space: string,
44
+ destination: {
45
+ readonly storageHash: string;
46
+ readonly profileHash: string;
47
+ readonly invariants?: readonly string[];
48
+ },
49
+ ): Promise<void> {
50
+ await defaultControlAdapter.initMarker(controlDriverFromDb(db), space, destination);
51
+ }
52
+
53
+ export async function updateMarker(
54
+ db: Db,
55
+ space: string,
56
+ expectedFrom: string,
57
+ destination: {
58
+ readonly storageHash: string;
59
+ readonly profileHash: string;
60
+ readonly invariants?: readonly string[];
61
+ },
62
+ ): Promise<boolean> {
63
+ return defaultControlAdapter.updateMarker(
64
+ controlDriverFromDb(db),
65
+ space,
66
+ expectedFrom,
67
+ destination,
68
+ );
69
+ }
70
+
71
+ export async function readLedger(db: Db, space?: string): Promise<readonly LedgerEntryRecord[]> {
72
+ return defaultControlAdapter.readLedger(controlDriverFromDb(db), space);
73
+ }
74
+
75
+ export async function writeLedgerEntry(
76
+ db: Db,
77
+ space: string,
78
+ entry: {
79
+ readonly edgeId: string;
80
+ readonly from: string;
81
+ readonly to: string;
82
+ readonly migrationName: string;
83
+ readonly migrationHash: string;
84
+ readonly operations: readonly unknown[];
85
+ },
86
+ ): Promise<void> {
87
+ await defaultControlAdapter.writeLedgerEntry(controlDriverFromDb(db), space, entry);
88
+ }
89
+
28
90
  export const mongoAdapterDescriptor: MongoControlAdapterDescriptor<'mongo'> = {
29
91
  kind: 'adapter',
30
92
  id: 'mongo',