|
1 | 1 | use std::panic; |
2 | | -use std::sync::Mutex; |
3 | 2 | use wasm_bindgen::prelude::*; |
4 | 3 |
|
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 | | - |
11 | 4 | #[wasm_bindgen] |
12 | 5 | extern "C" { |
13 | 6 | /// This function registers the reason for a Wasm panic via the |
@@ -139,44 +132,69 @@ pub fn hover(schema_files: String, params: String) -> String { |
139 | 132 | prisma_fmt::hover(schema_files, ¶ms) |
140 | 133 | } |
141 | 134 |
|
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. |
144 | 136 | /// |
145 | 137 | /// 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 | +/// ``` |
148 | 151 | /// |
149 | 152 | /// See: https://github.com/prisma/prisma/issues/29111 |
150 | 153 | #[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>, |
158 | 156 | } |
159 | 157 |
|
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. |
163 | 158 | #[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()) |
169 | 183 | } |
170 | | - let end = std::cmp::min(offset + length, buf.len()); |
171 | | - Ok(buf[offset..end].to_vec()) |
172 | 184 | } |
173 | 185 |
|
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 |
175 | 193 | #[wasm_bindgen] |
176 | | -pub fn free_dmmf_buffer() { |
| 194 | +pub fn get_dmmf_buffered(params: String) -> Result<DmmfBuffer, JsError> { |
177 | 195 | 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 }) |
180 | 198 | } |
181 | 199 |
|
182 | 200 | /// Trigger a panic inside the wasm module. This is only useful in development for testing panic |
|
0 commit comments