@@ -20,8 +20,8 @@ import {
2020import { takeStateCheckpoints } from '../lib/pipeline.js'
2121import { ndjsonResponse } from '@stripe/sync-ts-cli/ndjson'
2222import { logger } from '../logger.js'
23- import { createStripeSource , DEFAULT_MAX_RPS } from '@stripe/sync-source-stripe'
24- import type { RateLimiter } from '@stripe/sync-source-stripe'
23+ import { createStripeSource , DEFAULT_MAX_RPS , fetchById } from '@stripe/sync-source-stripe'
24+ import type { RateLimiter , Config as StripeConfig } from '@stripe/sync-source-stripe'
2525import {
2626 acquire ,
2727 createRateLimiterTable ,
@@ -208,8 +208,12 @@ export function createApp(resolver: ConnectorResolver) {
208208 logger . info ( context , 'Engine API /setup started' )
209209 const engine = await createEngineFromParams ( params . pipeline , resolver , noopStateStore ( ) )
210210 try {
211- await engine . setup ( )
211+ const result = await engine . setup ( )
212212 logger . info ( { ...context , durationMs : Date . now ( ) - startedAt } , 'Engine API /setup completed' )
213+ logger . info ( result , 'Engine API /setup result' )
214+ if ( result && Object . keys ( result ) . length > 0 ) {
215+ return c . json ( result , 200 )
216+ }
213217 return c . body ( null , 204 )
214218 } catch ( error ) {
215219 logger . error (
@@ -276,6 +280,74 @@ export function createApp(resolver: ConnectorResolver) {
276280 )
277281 } )
278282
283+ app . post ( '/write-events' , async ( c ) => {
284+ const params = parseSyncParams ( c )
285+ const context = { path : '/write-events' , ...syncRequestContext ( params ) }
286+ const startedAt = Date . now ( )
287+
288+ // Body: [{stream, id, row_number?}]
289+ const ThinEventSchema = z . array (
290+ z . object ( {
291+ stream : z . string ( ) ,
292+ id : z . string ( ) ,
293+ row_number : z . number ( ) . int ( ) . positive ( ) . optional ( ) ,
294+ } )
295+ )
296+ let events : z . infer < typeof ThinEventSchema >
297+ try {
298+ events = ThinEventSchema . parse ( await c . req . json ( ) )
299+ } catch {
300+ return c . json ( { error : 'Body must be an array of {stream, id, row_number?}' } , 400 )
301+ }
302+ if ( events . length === 0 ) {
303+ return c . json ( { error : 'Events array must not be empty' } , 400 )
304+ }
305+
306+ logger . info ( { ...context , eventCount : events . length } , 'Engine API /write-events started' )
307+
308+ const sourceConfig = params . pipeline . source as unknown as StripeConfig
309+
310+ // Fetch full objects from Stripe concurrently then build record messages
311+ const now = Date . now ( )
312+ const fetched = await Promise . all (
313+ events . map ( async ( event ) => {
314+ const data = await fetchById ( sourceConfig , event . stream , event . id )
315+ return { event, data }
316+ } )
317+ )
318+
319+ // Log any IDs that couldn't be resolved
320+ for ( const { event, data } of fetched ) {
321+ if ( ! data ) {
322+ logger . warn (
323+ { stream : event . stream , id : event . id } ,
324+ 'Engine API /write-events: fetchById returned null — skipping'
325+ )
326+ }
327+ }
328+
329+ const records = fetched . filter ( ( { data } ) => data != null )
330+
331+ async function * recordMessages ( ) : AsyncIterable <
332+ import ( '@stripe/sync-protocol' ) . DestinationInput
333+ > {
334+ for ( const { event, data } of records ) {
335+ yield {
336+ type : 'record' as const ,
337+ stream : event . stream ,
338+ data : data ! ,
339+ emitted_at : now ,
340+ row_number : event . row_number ,
341+ }
342+ }
343+ }
344+
345+ const engine = await createEngineFromParams ( params . pipeline , resolver , noopStateStore ( ) )
346+ return ndjsonResponse (
347+ logApiStream ( 'Engine API /write-events' , engine . write ( recordMessages ( ) ) , context , startedAt )
348+ )
349+ } )
350+
279351 app . post ( '/sync' , async ( c ) => {
280352 const params = parseSyncParams ( c )
281353 const stateStore = await selectStateStore ( params . pipeline )
0 commit comments