Skip to content

Commit e8a850c

Browse files
committed
fix(mongo): satisfy cast ratchet and stub adapter types
Replace new bare `as` casts with blindCast or narrowing helpers so lint:casts passes, and extend the mongo-runner stub with structuralLower and resolveParams after the adapter interface split. Signed-off-by: Will Madden <madden@prisma.io>
1 parent ad56a2a commit e8a850c

6 files changed

Lines changed: 104 additions & 44 deletions

File tree

packages/2-mongo-family/7-runtime/src/mongo-runtime.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,12 @@ class MongoRuntimeImpl
9595
log: { info: noop, warn: noop, error: noop },
9696
// ctx is only invoked by runWithMiddleware with execs this runtime lowered;
9797
// the framework parameter type is the cross-family base.
98-
contentHash: (exec) => computeMongoContentHash(exec as MongoExecutionPlan),
98+
contentHash: (exec) =>
99+
computeMongoContentHash(
100+
blindCast<MongoExecutionPlan, 'runWithMiddleware passes execs this runtime lowered'>(
101+
exec,
102+
),
103+
),
99104
// When MongoRuntimeImpl grows connection()/transaction() surfaces,
100105
// derive a scope-narrowed ctx per call (mirror
101106
// SqlRuntimeImpl#executeAgainstQueryable in `sql-runtime.ts`).
@@ -205,7 +210,9 @@ class MongoRuntimeImpl
205210
);
206211
for await (const rawRow of stream) {
207212
if (exec.resultShape === undefined) {
208-
yield rawRow as Row;
213+
yield blindCast<Row, 'driver row matches plan _row phantom when resultShape is absent'>(
214+
rawRow,
215+
);
209216
} else {
210217
// Source the collection from the lowered exec rather than the
211218
// pre-lowering plan: a runBeforeCompile middleware is allowed to
@@ -218,7 +225,7 @@ class MongoRuntimeImpl
218225
exec.command.collection,
219226
codecCtx,
220227
);
221-
yield decoded as Row;
228+
yield blindCast<Row, 'decodeMongoRow output matches plan _row phantom'>(decoded);
222229
}
223230
}
224231
};

packages/2-mongo-family/7-runtime/src/param-ref-mutator.ts

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ParamRefMutator } from '@prisma-next/framework-components/runtime';
22
import type { MongoLoweredDraft } from '@prisma-next/mongo-lowering';
33
import { MongoParamRef } from '@prisma-next/mongo-value';
4+
import { blindCast } from '@prisma-next/utils/casts';
45

56
/**
67
* Phantom brand on {@link MongoParamRefHandle} so handles produced by
@@ -93,6 +94,10 @@ export interface MongoParamRefMutatorInternal<
9394

9495
type AnyMongoHandle = MongoParamRefHandle<string | undefined>;
9596

97+
function isPlainRecord(value: unknown): value is Record<string, unknown> {
98+
return value !== null && typeof value === 'object' && !Array.isArray(value);
99+
}
100+
96101
// ─── Internal tree-walk helpers ────────────────────────────────────────────
97102

98103
function* flattenDraftSlot(value: unknown): Generator<MongoParamRef> {
@@ -106,8 +111,8 @@ function* flattenDraftSlot(value: unknown): Generator<MongoParamRef> {
106111
}
107112
return;
108113
}
109-
if (value !== null && typeof value === 'object') {
110-
for (const v of Object.values(value as Record<string, unknown>)) {
114+
if (isPlainRecord(value)) {
115+
for (const v of Object.values(value)) {
111116
yield* flattenDraftSlot(v);
112117
}
113118
}
@@ -177,9 +182,9 @@ function substituteSlot(value: unknown, overrides: ReadonlyMap<MongoParamRef, un
177182
if (Array.isArray(value)) {
178183
return value.map((item) => substituteSlot(item, overrides));
179184
}
180-
if (value !== null && typeof value === 'object') {
185+
if (isPlainRecord(value)) {
181186
const out: Record<string, unknown> = {};
182-
for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
187+
for (const [k, v] of Object.entries(value)) {
183188
out[k] = substituteSlot(v, overrides);
184189
}
185190
return out;
@@ -191,19 +196,26 @@ function substituteDoc(
191196
doc: Record<string, unknown>,
192197
overrides: ReadonlyMap<MongoParamRef, unknown>,
193198
): Record<string, unknown> {
194-
return substituteSlot(doc, overrides) as Record<string, unknown>;
199+
return blindCast<
200+
Record<string, unknown>,
201+
'substituteSlot preserves plain-object shape for document inputs'
202+
>(substituteSlot(doc, overrides));
203+
}
204+
205+
function isUpdatePipeline(
206+
update: Record<string, unknown> | ReadonlyArray<Record<string, unknown>>,
207+
): update is ReadonlyArray<Record<string, unknown>> {
208+
return Array.isArray(update);
195209
}
196210

197211
function substituteUpdate(
198212
update: Record<string, unknown> | ReadonlyArray<Record<string, unknown>>,
199213
overrides: ReadonlyMap<MongoParamRef, unknown>,
200214
): Record<string, unknown> | Array<Record<string, unknown>> {
201-
if (!Array.isArray(update)) {
202-
return substituteDoc(update as Record<string, unknown>, overrides);
215+
if (isUpdatePipeline(update)) {
216+
return update.map((stage) => substituteDoc(stage, overrides));
203217
}
204-
return (update as ReadonlyArray<Record<string, unknown>>).map((stage) =>
205-
substituteDoc(stage, overrides),
206-
);
218+
return substituteDoc(update, overrides);
207219
}
208220

209221
function buildMutatedDraft(
@@ -363,30 +375,45 @@ export function createMongoParamRefMutator<
363375

364376
function* entries(): IterableIterator<MongoParamRefEntryUnion<TCodecMap>> {
365377
for (const ref of flattenMongoParamRefs(originalDraft)) {
366-
const handle = ref as unknown as MongoParamRefHandle<string | undefined>;
378+
const handle = blindCast<
379+
MongoParamRefHandle<string | undefined>,
380+
'MongoParamRef instance is the runtime handle token'
381+
>(ref);
367382
const value = overrides?.has(ref) ? overrides.get(ref) : ref.value;
368383
const codecId: string | undefined = ref.codecId;
369384
const entry: MongoParamRefEntry<string | undefined> = { ref: handle, value, codecId };
370-
yield entry as MongoParamRefEntryUnion<TCodecMap>;
385+
yield blindCast<
386+
MongoParamRefEntryUnion<TCodecMap>,
387+
'entry codecId widened to TCodecMap union'
388+
>(entry);
371389
}
372390
}
373391

374392
function replaceValue(handle: AnyMongoHandle, newValue: unknown): void {
375-
ensureOverrides().set(handle as unknown as MongoParamRef, newValue);
393+
ensureOverrides().set(
394+
blindCast<MongoParamRef, 'MongoParamRefHandle brand is the underlying ref instance'>(handle),
395+
newValue,
396+
);
376397
}
377398

378399
function replaceValues(
379400
updates: Iterable<{ readonly ref: AnyMongoHandle; readonly newValue: unknown }>,
380401
): void {
381402
const map = ensureOverrides();
382403
for (const { ref, newValue } of updates) {
383-
map.set(ref as unknown as MongoParamRef, newValue);
404+
map.set(
405+
blindCast<MongoParamRef, 'MongoParamRefHandle brand is the underlying ref instance'>(ref),
406+
newValue,
407+
);
384408
}
385409
}
386410

387411
return {
388412
entries,
389-
replaceValue: replaceValue as MongoParamRefMutator<TCodecMap>['replaceValue'],
413+
replaceValue: blindCast<
414+
MongoParamRefMutator<TCodecMap>['replaceValue'],
415+
'replaceValue overloads are enforced at the interface; implementation accepts AnyMongoHandle'
416+
>(replaceValue),
390417
replaceValues,
391418
currentDraft(): MongoLoweredDraft {
392419
if (!overrides || overrides.size === 0) {

packages/3-mongo-target/1-mongo-target/test/mongo-runner.test.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
import type { CodecCallContext } from '@prisma-next/framework-components/codec';
12
import type {
23
MigrationOperationPolicy,
34
MigrationPlan,
45
MigrationRunnerExecutionChecks,
56
} from '@prisma-next/framework-components/control';
67
import type { MongoContract } from '@prisma-next/mongo-contract';
7-
import type { MongoAdapter, MongoDriver } from '@prisma-next/mongo-lowering';
8+
import type { MongoAdapter, MongoDriver, MongoLoweredDraft } from '@prisma-next/mongo-lowering';
89
import type {
910
AnyMongoDdlCommand,
1011
AnyMongoInspectionCommand,
@@ -85,14 +86,29 @@ class StubMongoDriver implements MongoDriver {
8586
class StubMongoAdapter implements MongoAdapter {
8687
readonly loweredPlans: MongoQueryPlan[] = [];
8788

88-
async lower(plan: MongoQueryPlan): Promise<WireCommand> {
89+
structuralLower(plan: MongoQueryPlan): MongoLoweredDraft {
8990
this.loweredPlans.push(plan);
9091
const kind = plan.command.kind;
91-
const wireKind = kind === 'aggregate' || kind === 'rawAggregate' ? 'aggregate' : 'updateMany';
92-
// The stub driver only reads `kind` and `collection`, so a minimal shape
93-
// is sufficient. The double-cast documents that this is a test-only
94-
// mock standing in for the full wire-command class hierarchy.
95-
return { kind: wireKind, collection: plan.collection } as unknown as WireCommand;
92+
if (kind === 'aggregate' || kind === 'rawAggregate') {
93+
return { kind: 'aggregate', collection: plan.collection, pipeline: [] };
94+
}
95+
return {
96+
kind: 'updateMany',
97+
collection: plan.collection,
98+
filter: {},
99+
update: {},
100+
upsert: undefined,
101+
};
102+
}
103+
104+
async resolveParams(draft: MongoLoweredDraft, _ctx: CodecCallContext): Promise<WireCommand> {
105+
const wireKind =
106+
draft.kind === 'aggregate' || draft.kind === 'rawAggregate' ? 'aggregate' : 'updateMany';
107+
return { kind: wireKind, collection: draft.collection } as unknown as WireCommand;
108+
}
109+
110+
lower(plan: MongoQueryPlan, ctx: CodecCallContext): Promise<WireCommand> {
111+
return this.resolveParams(this.structuralLower(plan), ctx);
96112
}
97113
}
98114

packages/3-mongo-target/2-mongo-adapter/src/lowering.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type {
1111
} from '@prisma-next/mongo-query-ast/execution';
1212
import { isExprArray, isRecordArgs } from '@prisma-next/mongo-query-ast/execution';
1313
import type { Document } from '@prisma-next/mongo-value';
14+
import { blindCast } from '@prisma-next/utils/casts';
1415
import { resolveValue } from './resolve-value';
1516

1617
// Biome flags `{ then: ... }` as a thenable object (noThenProperty). Build via Object.fromEntries to avoid.
@@ -161,7 +162,9 @@ export function structuralLowerFilter(filter: MongoFilterExpr): Record<string, u
161162
return { $expr: lowerAggExpr(filter.aggExpr) };
162163
default: {
163164
const _exhaustive: never = filter;
164-
throw new Error(`Unhandled filter kind: ${(_exhaustive as MongoFilterExpr).kind}`);
165+
throw new Error(
166+
`Unhandled filter kind: ${blindCast<MongoFilterExpr, 'exhaustive switch fallback for error message'>(_exhaustive).kind}`,
167+
);
165168
}
166169
}
167170
}
@@ -186,7 +189,9 @@ export async function lowerFilter(
186189
return { $expr: lowerAggExpr(filter.aggExpr) };
187190
default: {
188191
const _exhaustive: never = filter;
189-
throw new Error(`Unhandled filter kind: ${(_exhaustive as MongoFilterExpr).kind}`);
192+
throw new Error(
193+
`Unhandled filter kind: ${blindCast<MongoFilterExpr, 'exhaustive switch fallback for error message'>(_exhaustive).kind}`,
194+
);
190195
}
191196
}
192197
}
@@ -207,9 +212,9 @@ function lowerExprRecord(
207212
const result: Record<string, unknown> = {};
208213
for (const [key, val] of Object.entries(fields)) {
209214
if (Array.isArray(val)) {
210-
result[key] = val.map((v: MongoAggExpr) => lowerAggExpr(v));
215+
result[key] = val.map((v) => lowerAggExpr(v));
211216
} else {
212-
result[key] = lowerAggExpr(val as MongoAggExpr);
217+
result[key] = lowerAggExpr(val);
213218
}
214219
}
215220
return result;
@@ -445,7 +450,9 @@ export async function lowerStage(
445450
}
446451
default: {
447452
const _exhaustive: never = stage;
448-
throw new Error(`Unhandled stage kind: ${(_exhaustive as MongoPipelineStage).kind}`);
453+
throw new Error(
454+
`Unhandled stage kind: ${blindCast<MongoPipelineStage, 'exhaustive switch fallback for error message'>(_exhaustive).kind}`,
455+
);
449456
}
450457
}
451458
}
@@ -660,7 +667,9 @@ export function structuralLowerStage(stage: MongoPipelineStage): Record<string,
660667
}
661668
default: {
662669
const _exhaustive: never = stage;
663-
throw new Error(`Unhandled stage kind: ${(_exhaustive as MongoPipelineStage).kind}`);
670+
throw new Error(
671+
`Unhandled stage kind: ${blindCast<MongoPipelineStage, 'exhaustive switch fallback for error message'>(_exhaustive).kind}`,
672+
);
664673
}
665674
}
666675
}

packages/3-mongo-target/2-mongo-adapter/src/mongo-adapter.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
UpdateManyWireCommand,
1919
UpdateOneWireCommand,
2020
} from '@prisma-next/mongo-wire';
21+
import { blindCast } from '@prisma-next/utils/casts';
2122
import { buildStandardCodecRegistry } from './core/codecs';
2223
import { structuralLowerFilter, structuralLowerPipeline } from './lowering';
2324
import { resolveDraftDoc } from './resolve-value';
@@ -34,13 +35,9 @@ async function resolveUpdate(
3435
ctx: CodecCallContext,
3536
): Promise<Record<string, unknown> | ReadonlyArray<Record<string, unknown>>> {
3637
if (Array.isArray(update)) {
37-
return Promise.all(
38-
(update as ReadonlyArray<Record<string, unknown>>).map((stage) =>
39-
resolveDraftDoc(stage, codecs, ctx),
40-
),
41-
);
38+
return Promise.all(update.map((stage) => resolveDraftDoc(stage, codecs, ctx)));
4239
}
43-
return resolveDraftDoc(update as Record<string, unknown>, codecs, ctx);
40+
return resolveDraftDoc(update, codecs, ctx);
4441
}
4542

4643
class MongoAdapterImpl implements MongoAdapter {
@@ -170,7 +167,9 @@ class MongoAdapterImpl implements MongoAdapter {
170167
// v8 ignore next 4
171168
default: {
172169
const _exhaustive: never = command;
173-
throw new Error(`Unknown command kind: ${(_exhaustive as { kind: string }).kind}`);
170+
throw new Error(
171+
`Unknown command kind: ${blindCast<{ kind: string }, 'exhaustive switch fallback for error message'>(_exhaustive).kind}`,
172+
);
174173
}
175174
}
176175
}
@@ -269,7 +268,9 @@ class MongoAdapterImpl implements MongoAdapter {
269268
// v8 ignore next 4
270269
default: {
271270
const _exhaustive: never = draft;
272-
throw new Error(`Unknown draft kind: ${(_exhaustive as { kind: string }).kind}`);
271+
throw new Error(
272+
`Unknown draft kind: ${blindCast<{ kind: string }, 'exhaustive switch fallback for error message'>(_exhaustive).kind}`,
273+
);
273274
}
274275
}
275276
}

packages/3-mongo-target/2-mongo-adapter/src/resolve-value.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,12 @@ function paramRefLabel(ref: MongoParamRef, codecId: string): string {
152152
return ref.name ?? codecId;
153153
}
154154

155+
function isErrorWithCode(error: unknown): error is Error & { code: unknown } {
156+
return error instanceof Error && 'code' in error;
157+
}
158+
155159
function isAlreadyEncodeFailure(error: unknown): boolean {
156-
return (
157-
error instanceof Error &&
158-
'code' in error &&
159-
(error as Error & { code?: unknown }).code === 'RUNTIME.ENCODE_FAILED'
160-
);
160+
return isErrorWithCode(error) && error.code === 'RUNTIME.ENCODE_FAILED';
161161
}
162162

163163
function wrapEncodeFailure(error: unknown, ref: MongoParamRef, codecId: string): never {

0 commit comments

Comments
 (0)