11import { DurableObject } from 'cloudflare:workers' ;
2- import { eq , ne , gt , gte , lt , and , inArray , isNotNull } from 'drizzle-orm' ;
2+ import { eq , ne , gt , gte , lt , and , or , inArray , isNull , isNotNull } from 'drizzle-orm' ;
33import { drizzle , type DrizzleSqliteDODatabase } from 'drizzle-orm/durable-sqlite' ;
44import { migrate } from 'drizzle-orm/durable-sqlite/migrator' ;
55
@@ -86,6 +86,29 @@ const INGEST_META_EXTRACTORS: Array<{
8686
8787type Changes = Array < { name : ExtractableMetaKey ; value : string | null } > ;
8888
89+ export type IngestOrderCursor = { ingestedAt : number | null ; id : number } ;
90+
91+ export function afterIngestOrderCursor ( cursor : IngestOrderCursor ) {
92+ if ( cursor . ingestedAt === null ) {
93+ return or (
94+ and ( isNull ( ingestItems . ingested_at ) , gt ( ingestItems . id , cursor . id ) ) ,
95+ isNotNull ( ingestItems . ingested_at )
96+ ) ;
97+ }
98+
99+ return or (
100+ gt ( ingestItems . ingested_at , cursor . ingestedAt ) ,
101+ and ( eq ( ingestItems . ingested_at , cursor . ingestedAt ) , gt ( ingestItems . id , cursor . id ) )
102+ ) ;
103+ }
104+
105+ export function ingestOrderCursor ( row : {
106+ ingested_at : number | null ;
107+ id : number ;
108+ } ) : IngestOrderCursor {
109+ return { ingestedAt : row . ingested_at , id : row . id } ;
110+ }
111+
89112export class SessionIngestDO extends DurableObject < Env > {
90113 private db : DrizzleSqliteDODatabase ;
91114
@@ -295,25 +318,31 @@ export class SessionIngestDO extends DurableObject<Env> {
295318 // --- messages ---
296319 const CURSOR_BATCH = 10 ;
297320 controller . enqueue ( encoder . encode ( ',"messages":[' ) ) ;
298- let msgCursor = 0 ;
321+ let msgCursor : IngestOrderCursor | undefined ;
299322 let firstMsg = true ;
300323
301324 while ( true ) {
302325 const msgBatch = db
303326 . select ( {
304327 id : ingestItems . id ,
328+ ingested_at : ingestItems . ingested_at ,
305329 item_id : ingestItems . item_id ,
306330 item_data : ingestItems . item_data ,
307331 item_data_r2_key : ingestItems . item_data_r2_key ,
308332 } )
309333 . from ( ingestItems )
310- . where ( and ( eq ( ingestItems . item_type , 'message' ) , gt ( ingestItems . id , msgCursor ) ) )
311- . orderBy ( ingestItems . id )
334+ . where (
335+ and (
336+ eq ( ingestItems . item_type , 'message' ) ,
337+ msgCursor ? afterIngestOrderCursor ( msgCursor ) : undefined
338+ )
339+ )
340+ . orderBy ( ingestItems . ingested_at , ingestItems . id )
312341 . limit ( CURSOR_BATCH )
313342 . all ( ) ;
314343
315344 if ( msgBatch . length === 0 ) break ;
316- msgCursor = msgBatch [ msgBatch . length - 1 ] . id ;
345+ msgCursor = ingestOrderCursor ( msgBatch [ msgBatch . length - 1 ] ) ;
317346
318347 for ( const msgRow of msgBatch ) {
319348 if ( ! firstMsg ) controller . enqueue ( encoder . encode ( ',' ) ) ;
@@ -327,13 +356,14 @@ export class SessionIngestDO extends DurableObject<Env> {
327356 const msgId = msgRow . item_id . slice ( 'message/' . length ) ;
328357 const partRange = getPartItemIdentityRange ( msgId ) ;
329358 controller . enqueue ( encoder . encode ( ',"parts":[' ) ) ;
330- let partCursor = 0 ;
359+ let partCursor : IngestOrderCursor | undefined ;
331360 let firstPart = true ;
332361
333362 while ( true ) {
334363 const partBatch = db
335364 . select ( {
336365 id : ingestItems . id ,
366+ ingested_at : ingestItems . ingested_at ,
337367 item_data : ingestItems . item_data ,
338368 item_data_r2_key : ingestItems . item_data_r2_key ,
339369 } )
@@ -343,15 +373,15 @@ export class SessionIngestDO extends DurableObject<Env> {
343373 eq ( ingestItems . item_type , 'part' ) ,
344374 gte ( ingestItems . item_id , partRange . start ) ,
345375 lt ( ingestItems . item_id , partRange . end ) ,
346- gt ( ingestItems . id , partCursor )
376+ partCursor ? afterIngestOrderCursor ( partCursor ) : undefined
347377 )
348378 )
349- . orderBy ( ingestItems . id )
379+ . orderBy ( ingestItems . ingested_at , ingestItems . id )
350380 . limit ( CURSOR_BATCH )
351381 . all ( ) ;
352382
353383 if ( partBatch . length === 0 ) break ;
354- partCursor = partBatch [ partBatch . length - 1 ] . id ;
384+ partCursor = ingestOrderCursor ( partBatch [ partBatch . length - 1 ] ) ;
355385
356386 for ( const partRow of partBatch ) {
357387 if ( ! firstPart ) controller . enqueue ( encoder . encode ( ',' ) ) ;
@@ -404,7 +434,7 @@ export class SessionIngestDO extends DurableObject<Env> {
404434 } )
405435 . from ( ingestItems )
406436 . where ( ne ( ingestItems . item_type , 'session_diff' ) )
407- . orderBy ( ingestItems . id )
437+ . orderBy ( ingestItems . ingested_at , ingestItems . id )
408438 . all ( ) ;
409439
410440 if ( rows . length === 0 ) {
0 commit comments