( db: Database, path: string, source: ReadableStream<Uint8Array>, options: WriteFileOptions, now: () => number, )
| 138 | // Failure mid-stream leaves blob rows behind; gc() reaps orphans on |
| 139 | // its next pass since no node references them. |
| 140 | async function writeFileStreaming( |
| 141 | db: Database, |
| 142 | path: string, |
| 143 | source: ReadableStream<Uint8Array>, |
| 144 | options: WriteFileOptions, |
| 145 | now: () => number, |
| 146 | ): Promise<void> { |
| 147 | const { parts, path: canonical } = canonicalizePath(path); |
| 148 | if (parts.length === 0) { |
| 149 | throw createWorkspaceError("EISDIR", "cannot write to the root directory", canonical); |
| 150 | } |
| 151 | // Reject before we stage any blob bytes so known failures do not grow |
| 152 | // orphan blob rows that gc() then has to reap. |
| 153 | assertNotReadOnly(db, canonical); |
| 154 | if (options.exclusive) { |
| 155 | const parentInode = resolveParent(db, parts, canonical); |
| 156 | const existing = db.one( |
| 157 | "SELECT 1 FROM vfs_dirents WHERE parent_inode = ? AND name = ?", |
| 158 | parentInode, |
| 159 | parts[parts.length - 1], |
| 160 | ); |
| 161 | if (existing !== undefined) { |
| 162 | throw createWorkspaceError("EEXIST", `path exists: ${canonical}`, canonical); |
| 163 | } |
| 164 | } |
| 165 | const mode = (options.mode ?? 0o644) & 0o7777; |
| 166 | const mtime = now(); |
| 167 | |
| 168 | const chunkRefs: Array<{ hash: Uint8Array; size: number }> = []; |
| 169 | // Carry-over buffer: bytes left over from the previous source chunk |
| 170 | // that didn't fill a CHUNK_SIZE window. |
| 171 | let carry: Uint8Array | undefined; |
| 172 | |
| 173 | const flush = (chunk: Uint8Array): void => { |
| 174 | const hash = sha256(chunk); |
| 175 | stageBlob(db, hash, chunk, mtime); |
| 176 | chunkRefs.push({ hash, size: chunk.byteLength }); |
| 177 | }; |
| 178 | |
| 179 | const reader = source.getReader(); |
| 180 | try { |
| 181 | while (true) { |
| 182 | const { value, done } = await reader.read(); |
| 183 | if (done) break; |
| 184 | if (value === undefined || value.byteLength === 0) continue; |
| 185 | let input = value; |
| 186 | if (carry !== undefined) { |
| 187 | // Splice carry-over onto the front of this source chunk so |
| 188 | // we can re-window cleanly. |
| 189 | const merged = new Uint8Array(carry.byteLength + input.byteLength); |
| 190 | merged.set(carry, 0); |
| 191 | merged.set(input, carry.byteLength); |
| 192 | input = merged; |
| 193 | carry = undefined; |
| 194 | } |
| 195 | let offset = 0; |
| 196 | while (input.byteLength - offset >= CHUNK_SIZE) { |
| 197 | // Copy the window so the staged blob doesn't alias a |
no test coverage detected