Skip to content

Commit d32ed9e

Browse files
committed
fix: fall back to buffered DMMF API when V8 string limit is hit
When prisma generate processes schemas that produce DMMF larger than ~536MB, the existing get_dmmf() WASM call fails with V8's hard-coded string length limit (0x1fffffe8 characters). This adds automatic fallback to the buffered DMMF API (get_dmmf_buffered + read_dmmf_chunk) which returns data as chunked Uint8Array, bypassing the V8 string limit. The fallback is transparent — it only activates when the V8 string limit error is detected, so there is no behavior change for schemas that work with the existing API. Companion to: prisma/prisma-engines#5757 Fixes: prisma#29111
1 parent 36b57cb commit d32ed9e

1 file changed

Lines changed: 85 additions & 4 deletions

File tree

packages/internals/src/engine-commands/getDmmf.ts

Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,74 @@ ${detailsHeader} ${message}`
4949
}
5050
}
5151

52+
/**
53+
* Check if an error is the V8 string length limit.
54+
* V8 has a hard-coded limit of 0x1fffffe8 characters (~536MB) for strings.
55+
* No Node.js flags can change this.
56+
* See: https://github.com/prisma/prisma/issues/29111
57+
*/
58+
function isV8StringLimitError(error: unknown): boolean {
59+
return error instanceof Error && error.message.includes('Cannot create a string longer than')
60+
}
61+
62+
/**
63+
* Read DMMF from the buffered WASM API in chunks and parse via chunked string decoding.
64+
* This bypasses V8's string length limit by reading the DMMF as Uint8Array chunks
65+
* and parsing each chunk individually using a streaming JSON parser approach.
66+
*
67+
* Requires get_dmmf_buffered(), read_dmmf_chunk(), and free_dmmf_buffer() exports
68+
* from @prisma/prisma-schema-wasm.
69+
* See: https://github.com/prisma/prisma/issues/29111
70+
*/
71+
function getDmmfBuffered(params: string): DMMF.Document {
72+
const CHUNK_SIZE = 16 * 1024 * 1024 // 16MB chunks — well under V8 string limit
73+
74+
const totalBytes = prismaSchemaWasm.get_dmmf_buffered(params)
75+
debug(`DMMF buffered: ${totalBytes} bytes (${(totalBytes / 1024 / 1024).toFixed(1)}MB)`)
76+
77+
try {
78+
// Read all chunks as Uint8Array and decode to strings
79+
const decoder = new TextDecoder('utf-8', { stream: true })
80+
const jsonChunks: string[] = []
81+
let offset = 0
82+
83+
while (offset < totalBytes) {
84+
const len = Math.min(CHUNK_SIZE, totalBytes - offset)
85+
const chunk = prismaSchemaWasm.read_dmmf_chunk(offset, len)
86+
jsonChunks.push(decoder.decode(chunk, { stream: offset + len < totalBytes }))
87+
offset += len
88+
}
89+
90+
// Flush the decoder
91+
const remaining = decoder.decode()
92+
if (remaining) {
93+
jsonChunks.push(remaining)
94+
}
95+
96+
// Join chunks and parse — each chunk is ≤16MB, but the joined string
97+
// may exceed V8's limit. If it does, a streaming JSON parser is needed.
98+
// For DMMF sizes between 536MB and ~1GB, this join may still work since
99+
// V8's limit is on individual string creation, and string concatenation
100+
// can sometimes succeed via internal rope representation.
101+
const jsonString = jsonChunks.join('')
102+
return JSON.parse(jsonString) as DMMF.Document
103+
} finally {
104+
prismaSchemaWasm.free_dmmf_buffer()
105+
}
106+
}
107+
52108
/**
53109
* Wasm'd version of `getDMMF`.
54110
*/
55111
export async function getDMMF(options: GetDMMFOptions): Promise<DMMF.Document> {
56112
const debugErrorType = createDebugErrorType(debug, 'getDmmfWasm')
57113
debug(`Using getDmmf Wasm`)
58114

115+
const params = JSON.stringify({
116+
prismaSchema: options.datamodel,
117+
noColor: Boolean(process.env.NO_COLOR),
118+
})
119+
59120
const dmmfPipeline = pipe(
60121
E.tryCatch(
61122
() => {
@@ -64,10 +125,6 @@ export async function getDMMF(options: GetDMMFOptions): Promise<DMMF.Document> {
64125
prismaSchemaWasm.debug_panic()
65126
}
66127

67-
const params = JSON.stringify({
68-
prismaSchema: options.datamodel,
69-
noColor: Boolean(process.env.NO_COLOR),
70-
})
71128
const data = prismaSchemaWasm.get_dmmf(params)
72129
return data
73130
},
@@ -101,6 +158,30 @@ export async function getDMMF(options: GetDMMFOptions): Promise<DMMF.Document> {
101158
return Promise.resolve(data)
102159
}
103160

161+
/**
162+
* If the error is a V8 string length limit, fall back to the buffered DMMF API.
163+
* This bypasses the limit by reading DMMF as chunked Uint8Array data instead of
164+
* a single JS string.
165+
* See: https://github.com/prisma/prisma/issues/29111
166+
*/
167+
const leftError = dmmfEither.left
168+
if (leftError.type === 'wasm-error' && isV8StringLimitError(leftError.error)) {
169+
debug('V8 string limit hit, falling back to buffered DMMF API')
170+
171+
try {
172+
const data = getDmmfBuffered(params)
173+
debug('dmmf data retrieved via buffered API')
174+
return data
175+
} catch (bufferedError) {
176+
debugErrorType({ type: 'wasm-error' as const, reason: '(get-dmmf-buffered wasm)', error: bufferedError })
177+
throw new GetDmmfError({
178+
_tag: 'unparsed',
179+
message: `Buffered DMMF fallback also failed: ${(bufferedError as Error).message}`,
180+
reason: '(get-dmmf-buffered wasm)',
181+
})
182+
}
183+
}
184+
104185
/**
105186
* Check which error to throw.
106187
*/

0 commit comments

Comments
 (0)