Skip to content

Commit c429262

Browse files
committed
refactor: replace static buffer with handle-based DmmfBuffer API
Address review feedback from @jacek-prisma: replace the `static DMMF_BUFFER: Mutex<Vec<u8>>` global state with a caller-owned `DmmfBuffer` struct exported via `#[wasm_bindgen]`. The new API: - `get_dmmf_buffered(params)` → returns a `DmmfBuffer` handle - `buffer.len()` → total byte count - `buffer.read_chunk(offset, length)` → `Uint8Array` chunk - `buffer.free()` → release WASM memory (auto-provided by wasm-bindgen) Benefits: - No implicit global state — each call returns an independent buffer - Multiple concurrent buffers work correctly - `FinalizationRegistry` provides automatic cleanup as safety net - `Symbol.dispose` enables `using` syntax in modern JS/TS Tested with Node.js integration test: 7/7 tests pass including concurrent buffers, use-after-free detection, and OOB bounds checking.
1 parent c451c54 commit c429262

File tree

1 file changed

+50
-32
lines changed

1 file changed

+50
-32
lines changed

prisma-schema-wasm/src/lib.rs

Lines changed: 50 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11
use std::panic;
2-
use std::sync::Mutex;
32
use wasm_bindgen::prelude::*;
43

5-
/// Global buffer for storing DMMF bytes when using the buffered API.
6-
/// This allows JS to read the DMMF in chunks via Uint8Array, bypassing
7-
/// V8's string length limit of ~536MB.
8-
/// See: https://github.com/prisma/prisma/issues/29111
9-
static DMMF_BUFFER: Mutex<Vec<u8>> = Mutex::new(Vec::new());
10-
114
#[wasm_bindgen]
125
extern "C" {
136
/// This function registers the reason for a Wasm panic via the
@@ -139,44 +132,69 @@ pub fn hover(schema_files: String, params: String) -> String {
139132
prisma_fmt::hover(schema_files, &params)
140133
}
141134

142-
/// Serialize DMMF to an internal buffer and return the total byte count.
143-
/// Use `read_dmmf_chunk()` to read portions as Uint8Array, then `free_dmmf_buffer()` to release.
135+
/// Handle-based DMMF buffer that holds serialized DMMF JSON as bytes.
144136
///
145137
/// This bypasses V8's string length limit (~536MB / 0x1fffffe8 chars) by keeping the
146-
/// serialized JSON as bytes in WASM linear memory. The JS side reads chunks as Uint8Array
147-
/// (which has no V8 string limit) and can use a streaming JSON parser.
138+
/// serialized JSON as bytes in WASM linear memory. The JS side reads chunks as `Uint8Array`
139+
/// (which has no V8 string limit) and can reassemble them with a streaming JSON parser.
140+
///
141+
/// Usage from JS:
142+
/// ```js
143+
/// const buffer = get_dmmf_buffered(params);
144+
/// const totalLen = buffer.len();
145+
/// const chunks = [];
146+
/// for (let offset = 0; offset < totalLen; offset += CHUNK_SIZE) {
147+
/// chunks.push(buffer.read_chunk(offset, CHUNK_SIZE));
148+
/// }
149+
/// buffer.free(); // release WASM memory
150+
/// ```
148151
///
149152
/// See: https://github.com/prisma/prisma/issues/29111
150153
#[wasm_bindgen]
151-
pub fn get_dmmf_buffered(params: String) -> Result<usize, JsError> {
152-
register_panic_hook();
153-
let bytes = prisma_fmt::get_dmmf_bytes(params).map_err(|e| JsError::new(&e))?;
154-
let len = bytes.len();
155-
let mut buf = DMMF_BUFFER.lock().unwrap();
156-
*buf = bytes;
157-
Ok(len)
154+
pub struct DmmfBuffer {
155+
data: Vec<u8>,
158156
}
159157

160-
/// Read a chunk of the DMMF buffer as Uint8Array.
161-
/// `offset` is the byte offset, `length` is the number of bytes to read.
162-
/// Returns a Vec<u8> which wasm-bindgen converts to Uint8Array on the JS side.
163158
#[wasm_bindgen]
164-
pub fn read_dmmf_chunk(offset: usize, length: usize) -> Result<Vec<u8>, JsError> {
165-
register_panic_hook();
166-
let buf = DMMF_BUFFER.lock().unwrap();
167-
if offset >= buf.len() {
168-
return Err(JsError::new("Offset beyond buffer length"));
159+
impl DmmfBuffer {
160+
/// Returns the total byte length of the serialized DMMF JSON.
161+
pub fn len(&self) -> usize {
162+
self.data.len()
163+
}
164+
165+
/// Returns `true` if the buffer is empty.
166+
pub fn is_empty(&self) -> bool {
167+
self.data.is_empty()
168+
}
169+
170+
/// Read a chunk of the buffer as `Uint8Array`.
171+
/// `offset` is the byte offset, `length` is the max number of bytes to read.
172+
/// Returns a `Vec<u8>` which wasm-bindgen converts to `Uint8Array` on the JS side.
173+
pub fn read_chunk(&self, offset: usize, length: usize) -> Result<Vec<u8>, JsError> {
174+
if offset >= self.data.len() {
175+
return Err(JsError::new("Offset beyond buffer length"));
176+
}
177+
if length == 0 {
178+
return Ok(Vec::new());
179+
}
180+
// Use saturating_add to avoid overflow on wasm32 (usize is 32-bit)
181+
let end = std::cmp::min(offset.saturating_add(length), self.data.len());
182+
Ok(self.data[offset..end].to_vec())
169183
}
170-
let end = std::cmp::min(offset + length, buf.len());
171-
Ok(buf[offset..end].to_vec())
172184
}
173185

174-
/// Free the internal DMMF buffer. Call this after reading all chunks.
186+
/// Serialize DMMF to a caller-owned buffer and return it as a handle.
187+
/// Use `DmmfBuffer.read_chunk()` to read portions as `Uint8Array`, then
188+
/// `DmmfBuffer.free()` (auto-provided by wasm-bindgen) to release WASM memory.
189+
///
190+
/// This avoids implicit global state — each call returns an independent buffer.
191+
///
192+
/// See: https://github.com/prisma/prisma/issues/29111
175193
#[wasm_bindgen]
176-
pub fn free_dmmf_buffer() {
194+
pub fn get_dmmf_buffered(params: String) -> Result<DmmfBuffer, JsError> {
177195
register_panic_hook();
178-
let mut buf = DMMF_BUFFER.lock().unwrap();
179-
*buf = Vec::new();
196+
let data = prisma_fmt::get_dmmf_bytes(params).map_err(|e| JsError::new(&e))?;
197+
Ok(DmmfBuffer { data })
180198
}
181199

182200
/// Trigger a panic inside the wasm module. This is only useful in development for testing panic

0 commit comments

Comments
 (0)