@@ -2,7 +2,7 @@ import type { ColumnMeta, Env, Footer, TableMeta, DatasetMeta, AppendResult } fr
22import { parseFooter , parseColumnMetaFromProtobuf , FOOTER_SIZE } from "./footer.js" ;
33import { parseManifest } from "./manifest.js" ;
44import { detectFormat , getParquetFooterLength , parseParquetFooter , parquetMetaToTableMeta } from "./parquet.js" ;
5- import { bigIntReplacer } from "./decode .js" ;
5+ import type { QueryDORpc } from "./types .js" ;
66import { instantiateWasm , type WasmEngine } from "./wasm-engine.js" ;
77import wasmModule from "./wasm-module.js" ;
88
@@ -15,25 +15,9 @@ export class MasterDO extends DurableObject<Env> {
1515 super ( ctx , env ) ;
1616 }
1717
18- private json ( body : unknown , status = 200 ) : Response {
19- return new Response ( JSON . stringify ( body ) , {
20- status, headers : { "content-type" : "application/json" } ,
21- } ) ;
22- }
23-
24- async fetch ( request : Request ) : Promise < Response > {
25- switch ( new URL ( request . url ) . pathname ) {
26- case "/register" : return this . handleRegister ( request ) ;
27- case "/write" : return this . handleWrite ( request ) ;
28- case "/append" : return this . handleAppend ( request ) ;
29- case "/refresh" : return this . handleRefresh ( request ) ;
30- case "/tables" : return this . handleListTables ( ) ;
31- default : return new Response ( "Not found" , { status : 404 } ) ;
32- }
33- }
18+ // ── RPC methods ────────────────────────────────────────────────────────
3419
35- private async handleRegister ( request : Request ) : Promise < Response > {
36- const { queryDoId, region } = ( await request . json ( ) ) as { queryDoId : string ; region : string } ;
20+ async registerRpc ( queryDoId : string , region : string ) : Promise < { registered : boolean ; region : string ; tableVersions ?: Record < string , { r2Key : string ; updatedAt : number } > } > {
3721 const regions = ( await this . ctx . storage . get < Record < string , string > > ( "regions" ) ) ?? { } ;
3822 regions [ region ] = queryDoId ;
3923 await this . ctx . storage . put ( "regions" , regions ) ;
@@ -45,22 +29,22 @@ export class MasterDO extends DurableObject<Env> {
4529 const name = key . replace ( "table:" , "" ) ;
4630 tableVersions [ name ] = { r2Key : meta . r2Key ?? name , updatedAt : meta . updatedAt ?? 0 } ;
4731 }
48- return this . json ( { registered : true , region, tableVersions } ) ;
32+ return { registered : true , region, tableVersions } ;
4933 }
5034
51- private async handleWrite ( request : Request ) : Promise < Response > {
52- const { r2Key } = ( await request . json ( ) ) as { r2Key : string } ;
35+ async writeRpc ( body : unknown ) : Promise < unknown > {
36+ const { r2Key } = body as { r2Key : string } ;
5337 if ( ! r2Key || typeof r2Key !== "string" || r2Key . includes ( ".." ) ) {
54- return this . json ( { error : "Invalid r2Key" } , 400 ) ;
38+ throw new Error ( "Invalid r2Key" ) ;
5539 }
5640
5741 // Check if this is a dataset directory (ends with / or .lance/)
5842 if ( r2Key . endsWith ( "/" ) || r2Key . endsWith ( ".lance/" ) ) {
59- return this . handleDatasetWrite ( r2Key ) ;
43+ return this . executeDatasetWrite ( r2Key ) ;
6044 }
6145
6246 const result = await this . readFooterAndColumns ( r2Key ) ;
63- if ( ! result ) return this . json ( { error : "Failed to read footer" } , 500 ) ;
47+ if ( ! result ) throw new Error ( "Failed to read footer" ) ;
6448
6549 const tableName = r2Key . replace ( / \. ( l a n c e | p a r q u e t ) $ / , "" ) . split ( "/" ) . pop ( ) ?? r2Key ;
6650 const totalRows = result . columns [ 0 ] ?. pages . reduce ( ( s , p ) => s + p . rowCount , 0 ) ?? 0 ;
@@ -70,26 +54,26 @@ export class MasterDO extends DurableObject<Env> {
7054 } ;
7155 await this . ctx . storage . put ( `table:${ tableName } ` , meta ) ;
7256 await this . broadcast ( tableName , r2Key , result , { totalRows } ) ;
73- return this . json ( { success : true , table : tableName } ) ;
57+ return { success : true , table : tableName } ;
7458 }
7559
76- /** Handle write notification for a multi-fragment dataset directory. */
77- private async handleDatasetWrite ( r2Prefix : string ) : Promise < Response > {
60+ /** Write notification for a multi-fragment dataset directory. */
61+ private async executeDatasetWrite ( r2Prefix : string ) : Promise < unknown > {
7862 const tableName = r2Prefix . replace ( / \/ $ / , "" ) . replace ( / \. l a n c e $ / , "" ) . split ( "/" ) . pop ( ) ?? r2Prefix ;
7963
8064 // Find latest manifest
8165 const listed = await this . env . DATA_BUCKET . list ( { prefix : `${ r2Prefix } _versions/` , limit : 100 } ) ;
8266 const manifestKeys = listed . objects
8367 . filter ( o => o . key . endsWith ( ".manifest" ) )
8468 . sort ( ( a , b ) => a . key . localeCompare ( b . key ) ) ;
85- if ( manifestKeys . length === 0 ) return this . json ( { error : "No manifests found" } , 404 ) ;
69+ if ( manifestKeys . length === 0 ) throw new Error ( "No manifests found" ) ;
8670
8771 const latestKey = manifestKeys [ manifestKeys . length - 1 ] . key ;
8872 const manifestObj = await this . env . DATA_BUCKET . get ( latestKey ) ;
89- if ( ! manifestObj ) return this . json ( { error : "Failed to read manifest" } , 500 ) ;
73+ if ( ! manifestObj ) throw new Error ( "Failed to read manifest" ) ;
9074
9175 const manifest = parseManifest ( await manifestObj . arrayBuffer ( ) ) ;
92- if ( ! manifest ) return this . json ( { error : "Failed to parse manifest" } , 500 ) ;
76+ if ( ! manifest ) throw new Error ( "Failed to parse manifest" ) ;
9377
9478 // Read first fragment's footer to broadcast (Query DOs will discover the rest)
9579 if ( manifest . fragments . length > 0 ) {
@@ -110,7 +94,7 @@ export class MasterDO extends DurableObject<Env> {
11094 name : tableName , r2Prefix, manifest, totalRows : manifest . totalRows , updatedAt : Date . now ( ) ,
11195 } ) ;
11296
113- return this . json ( { success : true , table : tableName , fragments : manifest . fragments . length , totalRows : manifest . totalRows } ) ;
97+ return { success : true , table : tableName , fragments : manifest . fragments . length , totalRows : manifest . totalRows } ;
11498 }
11599
116100 private async getWasm ( ) : Promise < WasmEngine > {
@@ -119,29 +103,7 @@ export class MasterDO extends DurableObject<Env> {
119103 return this . wasmEngine ;
120104 }
121105
122- /** Append rows to a table using CAS coordination.
123- * 1. Build Lance fragment from row data via WASM
124- * 2. PUT data file to R2 (unique name, no conflict)
125- * 3. CAS loop: read _latest → build new manifest → PUT with ETag match
126- */
127- private async handleAppend ( request : Request ) : Promise < Response > {
128- const { table, rows } = ( await request . json ( ) ) as {
129- table : string ;
130- rows : Record < string , unknown > [ ] ;
131- } ;
132-
133- if ( ! rows ?. length ) return this . json ( { error : "No rows provided" } , 400 ) ;
134- try {
135- const result = await this . executeAppend ( table , rows ) ;
136- return this . json ( result ) ;
137- } catch ( err ) {
138- const msg = err instanceof Error ? err . message : String ( err ) ;
139- const status = msg . includes ( "CAS failed" ) ? 409 : 400 ;
140- return this . json ( { error : msg } , status ) ;
141- }
142- }
143-
144- /** Core append logic shared by HTTP fetch handler and RPC. */
106+ /** Core append logic. */
145107 private async executeAppend ( table : string , rows : Record < string , unknown > [ ] ) : Promise < AppendResult > {
146108 if ( ! rows ?. length ) throw new Error ( "No rows provided" ) ;
147109
@@ -355,22 +317,22 @@ export class MasterDO extends DurableObject<Env> {
355317 return new Uint8Array ( bytes ) ;
356318 }
357319
358- private async handleRefresh ( request : Request ) : Promise < Response > {
359- const { r2Key } = ( await request . json ( ) ) as { r2Key : string } ;
320+ async refreshRpc ( body : unknown ) : Promise < unknown > {
321+ const { r2Key } = body as { r2Key : string } ;
360322 if ( ! r2Key || typeof r2Key !== "string" || r2Key . includes ( ".." ) ) {
361- return this . json ( { error : "Invalid r2Key" } , 400 ) ;
323+ throw new Error ( "Invalid r2Key" ) ;
362324 }
363325 const result = await this . readFooterAndColumns ( r2Key ) ;
364- if ( ! result ) return this . json ( { error : "Failed to read footer" } , 500 ) ;
326+ if ( ! result ) throw new Error ( "Failed to read footer" ) ;
365327
366328 const tableName = r2Key . replace ( / \. ( l a n c e | p a r q u e t ) $ / , "" ) . split ( "/" ) . pop ( ) ?? r2Key ;
367329 await this . broadcast ( tableName , r2Key , result ) ;
368- return this . json ( { refreshed : true , table : tableName } ) ;
330+ return { refreshed : true , table : tableName } ;
369331 }
370332
371- private async handleListTables ( ) : Promise < Response > {
333+ async listTablesRpc ( ) : Promise < { tables : string [ ] } > {
372334 const tables = await this . ctx . storage . list < TableMeta > ( { prefix : "table:" } ) ;
373- return this . json ( { tables : [ ...tables . keys ( ) ] . map ( k => k . replace ( "table:" , "" ) ) } ) ;
335+ return { tables : [ ...tables . keys ( ) ] . map ( k => k . replace ( "table:" , "" ) ) } ;
374336 }
375337
376338 /** Read footer + column metadata from R2 (2 range reads, done once by Master). */
@@ -427,28 +389,26 @@ export class MasterDO extends DurableObject<Env> {
427389 return { parsed, raw, fileSize, columns, format : "lance" } ;
428390 }
429391
430- /** Broadcast invalidation with pre-parsed columns to all Query DOs. */
392+ /** Broadcast invalidation with pre-parsed columns to all Query DOs via RPC . */
431393 private async broadcast (
432394 table : string , r2Key : string ,
433395 footer : { raw : ArrayBuffer ; fileSize : bigint ; columns : ColumnMeta [ ] ; format ?: "lance" | "parquet" } ,
434396 opts ?: { totalRows ?: number ; r2Prefix ?: string } ,
435397 ) : Promise < void > {
436398 const regions = ( await this . ctx . storage . get < Record < string , string > > ( "regions" ) ) ?? { } ;
437- const payload = JSON . stringify ( {
399+ const payload = {
438400 table, r2Key, columns : footer . columns , format : footer . format ?? "lance" ,
439401 footerBytes : Array . from ( new Uint8Array ( footer . raw ) ) ,
440402 fileSize : footer . fileSize . toString ( ) , timestamp : Date . now ( ) ,
441403 ...( opts ?. totalRows != null ? { totalRows : opts . totalRows } : { } ) ,
442404 ...( opts ?. r2Prefix != null ? { r2Prefix : opts . r2Prefix } : { } ) ,
443- } , bigIntReplacer ) ;
405+ } ;
444406
445407 const deadRegions : string [ ] = [ ] ;
446408 await Promise . allSettled ( Object . entries ( regions ) . map ( async ( [ region , doId ] ) => {
447409 try {
448- const queryDo = this . env . QUERY_DO . get ( this . env . QUERY_DO . idFromString ( doId ) ) ;
449- await queryDo . fetch ( new Request ( "http://internal/invalidate" , {
450- method : "POST" , body : payload , headers : { "content-type" : "application/json" } ,
451- } ) ) ;
410+ const queryDo = this . env . QUERY_DO . get ( this . env . QUERY_DO . idFromString ( doId ) ) as unknown as QueryDORpc ;
411+ await queryDo . invalidateRpc ( payload ) ;
452412 this . broadcastFailures . delete ( region ) ;
453413 } catch {
454414 const count = ( this . broadcastFailures . get ( region ) ?? 0 ) + 1 ;
0 commit comments