@@ -340,6 +340,7 @@ export async function createCheckupReport(params: {
340340/**
341341 * Upload a JSON check result to an existing checkup report.
342342 * Each check (e.g., H001, A003) is uploaded as a separate JSON file.
343+ * For paid users, markdown is auto-generated and returned in the response.
343344 *
344345 * @param params - Configuration for the upload
345346 * @param params.apiKey - PostgresAI API access token
@@ -348,7 +349,7 @@ export async function createCheckupReport(params: {
348349 * @param params.filename - Filename for the uploaded JSON (e.g., "H001.json")
349350 * @param params.checkId - Check identifier (e.g., "H001", "A003")
350351 * @param params.jsonText - JSON content as a string
351- * @returns Promise resolving to the created report chunk ID
352+ * @returns Promise resolving to chunk ID and optional markdown info (for paid users)
352353 * @throws {RpcError } On API failures (4xx/5xx responses)
353354 * @throws {Error } On network errors or unexpected response format
354355 */
@@ -359,7 +360,12 @@ export async function uploadCheckupReportJson(params: {
359360 filename : string ;
360361 checkId : string ;
361362 jsonText : string ;
362- } ) : Promise < { reportChunkId : number } > {
363+ } ) : Promise < {
364+ reportChunkId : number ;
365+ markdownChunkId ?: number ;
366+ markdownChunkIds ?: number [ ] ;
367+ skippedMarkdown ?: boolean ;
368+ } > {
363369 const { apiKey, apiBaseUrl, reportId, filename, checkId, jsonText } = params ;
364370 const bodyObj : Record < string , unknown > = {
365371 access_token : apiKey ,
@@ -382,152 +388,181 @@ export async function uploadCheckupReportJson(params: {
382388 if ( ! Number . isFinite ( chunkId ) || chunkId <= 0 ) {
383389 throw new Error ( `Unexpected checkup_report_file_post response: ${ JSON . stringify ( resp ) } ` ) ;
384390 }
385- return { reportChunkId : chunkId } ;
386- }
387-
388- /**
389- * Status of markdown generation request
390- */
391- export type MarkdownStatus = "pending" | "processing" | "completed" | "failed" ;
392-
393- /**
394- * Response from markdown status check
395- */
396- export interface MarkdownStatusResponse {
397- status : MarkdownStatus ;
398- markdown ?: string ;
399- error ?: string ;
400- }
401-
402- /**
403- * Request markdown generation for an uploaded checkup report.
404- * This initiates async markdown generation on the server.
405- *
406- * @param params - Configuration for the request
407- * @param params.apiKey - PostgresAI API access token
408- * @param params.apiBaseUrl - Base URL of the PostgresAI API
409- * @param params.reportId - ID of the checkup report to generate markdown for
410- * @returns Promise resolving to a request ID for polling status
411- * @throws {RpcError } On API failures (402 for non-paid users, other 4xx/5xx)
412- */
413- export async function requestMarkdownGeneration ( params : {
414- apiKey : string ;
415- apiBaseUrl : string ;
416- reportId : number ;
417- } ) : Promise < { requestId : string } > {
418- const { apiKey, apiBaseUrl, reportId } = params ;
419391
420- const resp = await postRpc < any > ( {
421- apiKey,
422- apiBaseUrl,
423- rpcName : "checkup_markdown_request" ,
424- bodyObj : {
425- access_token : apiKey ,
426- checkup_report_id : reportId ,
427- } ,
428- } ) ;
392+ const result : {
393+ reportChunkId : number ;
394+ markdownChunkId ?: number ;
395+ markdownChunkIds ?: number [ ] ;
396+ skippedMarkdown ?: boolean ;
397+ } = { reportChunkId : chunkId } ;
429398
430- const requestId = resp ?. request_id ;
431- if ( typeof requestId !== "string" || ! requestId ) {
432- throw new Error ( `Unexpected checkup_markdown_request response: ${ JSON . stringify ( resp ) } ` ) ;
399+ // Extract markdown chunk info (for paid users)
400+ if ( resp ?. markdown_chunk_id ) {
401+ result . markdownChunkId = Number ( resp . markdown_chunk_id ) ;
402+ }
403+ if ( Array . isArray ( resp ?. markdown_chunk_ids ) ) {
404+ result . markdownChunkIds = resp . markdown_chunk_ids . map ( Number ) ;
405+ }
406+ if ( resp ?. skipped_markdown ) {
407+ result . skippedMarkdown = true ;
433408 }
434- return { requestId } ;
409+
410+ return result ;
435411}
436412
437413/**
438- * Check the status of a markdown generation request.
414+ * Fetch markdown content by chunk ID(s) from the checkup_report_file_data view.
415+ * Markdown is auto-generated when JSON is uploaded (for paid users).
439416 *
440- * @param params - Configuration for the status check
417+ * @param params - Configuration for fetching markdown
441418 * @param params.apiKey - PostgresAI API access token
442419 * @param params.apiBaseUrl - Base URL of the PostgresAI API
443- * @param params.requestId - Request ID from requestMarkdownGeneration
444- * @returns Promise resolving to the current status and markdown content if completed
420+ * @param params.markdownChunkIds - Array of markdown chunk IDs to fetch
421+ * @returns Promise resolving to concatenated markdown content
445422 * @throws {RpcError } On API failures
423+ * @throws {Error } If no markdown found
446424 */
447- export async function getMarkdownStatus ( params : {
425+ export async function fetchMarkdownContent ( params : {
448426 apiKey : string ;
449427 apiBaseUrl : string ;
450- requestId : string ;
451- } ) : Promise < MarkdownStatusResponse > {
452- const { apiKey, apiBaseUrl, requestId } = params ;
453-
454- const resp = await postRpc < any > ( {
455- apiKey,
456- apiBaseUrl,
457- rpcName : "checkup_markdown_status" ,
458- bodyObj : {
459- access_token : apiKey ,
460- request_id : requestId ,
461- } ,
462- } ) ;
428+ markdownChunkIds : number [ ] ;
429+ } ) : Promise < string > {
430+ const { apiKey, apiBaseUrl, markdownChunkIds } = params ;
463431
464- const status = resp ?. status as MarkdownStatus ;
465- if ( ! [ "pending" , "processing" , "completed" , "failed" ] . includes ( status ) ) {
466- throw new Error ( `Unexpected checkup_markdown_status response: ${ JSON . stringify ( resp ) } ` ) ;
432+ if ( markdownChunkIds . length === 0 ) {
433+ throw new Error ( "No markdown chunk IDs provided" ) ;
467434 }
468435
469- return {
470- status,
471- markdown : typeof resp ?. markdown === "string" ? resp . markdown : undefined ,
472- error : typeof resp ?. error === "string" ? resp . error : undefined ,
473- } ;
436+ const base = normalizeBaseUrl ( apiBaseUrl ) ;
437+ // Query the view for markdown chunks by ID
438+ const idsFilter = markdownChunkIds . map ( ( id ) => `id.eq.${ id } ` ) . join ( "," ) ;
439+ const url = new URL ( `${ base } /checkup_report_file_data?or=(${ idsFilter } )&type=eq.md&select=data,filename,check_id` ) ;
440+
441+ return new Promise ( ( resolve , reject ) => {
442+ const headers : Record < string , string > = {
443+ "access-token" : apiKey ,
444+ Accept : "application/json" ,
445+ } ;
446+
447+ const req = https . request (
448+ url ,
449+ { method : "GET" , headers } ,
450+ ( res ) => {
451+ let data = "" ;
452+ res . on ( "data" , ( chunk ) => ( data += chunk ) ) ;
453+ res . on ( "end" , ( ) => {
454+ if ( res . statusCode && res . statusCode >= 200 && res . statusCode < 300 ) {
455+ try {
456+ const rows = JSON . parse ( data ) ;
457+ if ( ! Array . isArray ( rows ) || rows . length === 0 ) {
458+ reject ( new Error ( "No markdown content found for the specified chunk IDs" ) ) ;
459+ return ;
460+ }
461+ // Concatenate all markdown content, separated by newlines
462+ const markdown = rows
463+ . map ( ( row : { data : string ; check_id ?: string } ) => row . data )
464+ . join ( "\n\n---\n\n" ) ;
465+ resolve ( markdown ) ;
466+ } catch {
467+ reject ( new Error ( `Failed to parse markdown response: ${ data } ` ) ) ;
468+ }
469+ } else {
470+ let payloadJson : any = null ;
471+ try {
472+ payloadJson = JSON . parse ( data ) ;
473+ } catch {
474+ // ignore
475+ }
476+ reject (
477+ new RpcError ( {
478+ rpcName : "checkup_report_file_data" ,
479+ statusCode : res . statusCode || 0 ,
480+ payloadText : data ,
481+ payloadJson,
482+ } )
483+ ) ;
484+ }
485+ } ) ;
486+ res . on ( "error" , reject ) ;
487+ }
488+ ) ;
489+ req . on ( "error" , reject ) ;
490+ req . end ( ) ;
491+ } ) ;
474492}
475493
476494/**
477- * Request markdown generation and poll until complete or timeout.
478- * This is a convenience function that combines requestMarkdownGeneration
479- * and getMarkdownStatus with polling logic.
495+ * Fetch all markdown files for a checkup report by report ID.
480496 *
481497 * @param params - Configuration for fetching markdown
482498 * @param params.apiKey - PostgresAI API access token
483499 * @param params.apiBaseUrl - Base URL of the PostgresAI API
484500 * @param params.reportId - ID of the checkup report
485- * @param params.timeoutMs - Maximum time to wait for generation (default: 120000ms)
486- * @param params.pollIntervalMs - Interval between status checks (default: 2000ms)
487- * @param params.onProgress - Optional callback for progress updates
488- * @returns Promise resolving to the generated markdown content
489- * @throws {RpcError } On API failures (402 for non-paid users)
490- * @throws {Error } On timeout or generation failure
501+ * @returns Promise resolving to markdown content
502+ * @throws {RpcError } On API failures
503+ * @throws {Error } If no markdown found
491504 */
492- export async function fetchMarkdownReport ( params : {
505+ export async function fetchMarkdownByReportId ( params : {
493506 apiKey : string ;
494507 apiBaseUrl : string ;
495508 reportId : number ;
496- timeoutMs ?: number ;
497- pollIntervalMs ?: number ;
498- onProgress ?: ( message : string ) => void ;
499509} ) : Promise < string > {
500- const {
501- apiKey,
502- apiBaseUrl,
503- reportId,
504- timeoutMs = 120_000 ,
505- pollIntervalMs = 2000 ,
506- onProgress,
507- } = params ;
508-
509- // Request markdown generation
510- const { requestId } = await requestMarkdownGeneration ( { apiKey, apiBaseUrl, reportId } ) ;
511-
512- const startTime = Date . now ( ) ;
513-
514- // Poll for completion
515- while ( Date . now ( ) - startTime < timeoutMs ) {
516- const result = await getMarkdownStatus ( { apiKey, apiBaseUrl, requestId } ) ;
517-
518- if ( result . status === "completed" && result . markdown ) {
519- return result . markdown ;
520- }
521-
522- if ( result . status === "failed" ) {
523- throw new Error ( `Markdown generation failed: ${ result . error || "Unknown error" } ` ) ;
524- }
510+ const { apiKey, apiBaseUrl, reportId } = params ;
525511
526- const elapsed = Math . round ( ( Date . now ( ) - startTime ) / 1000 ) ;
527- onProgress ?.( `Generating markdown report... (${ elapsed } s)` ) ;
512+ const base = normalizeBaseUrl ( apiBaseUrl ) ;
513+ const url = new URL (
514+ `${ base } /checkup_report_file_data?checkup_report_id=eq.${ reportId } &type=eq.md&select=data,filename,check_id&order=check_id`
515+ ) ;
528516
529- await new Promise ( ( resolve ) => setTimeout ( resolve , pollIntervalMs ) ) ;
530- }
517+ return new Promise ( ( resolve , reject ) => {
518+ const headers : Record < string , string > = {
519+ "access-token" : apiKey ,
520+ Accept : "application/json" ,
521+ } ;
531522
532- throw new Error ( `Markdown generation timed out after ${ Math . round ( timeoutMs / 1000 ) } seconds` ) ;
523+ const req = https . request (
524+ url ,
525+ { method : "GET" , headers } ,
526+ ( res ) => {
527+ let data = "" ;
528+ res . on ( "data" , ( chunk ) => ( data += chunk ) ) ;
529+ res . on ( "end" , ( ) => {
530+ if ( res . statusCode && res . statusCode >= 200 && res . statusCode < 300 ) {
531+ try {
532+ const rows = JSON . parse ( data ) ;
533+ if ( ! Array . isArray ( rows ) || rows . length === 0 ) {
534+ reject ( new Error ( "No markdown content found for this report" ) ) ;
535+ return ;
536+ }
537+ // Concatenate all markdown content, separated by newlines
538+ const markdown = rows
539+ . map ( ( row : { data : string ; check_id ?: string } ) => row . data )
540+ . join ( "\n\n---\n\n" ) ;
541+ resolve ( markdown ) ;
542+ } catch {
543+ reject ( new Error ( `Failed to parse markdown response: ${ data } ` ) ) ;
544+ }
545+ } else {
546+ let payloadJson : any = null ;
547+ try {
548+ payloadJson = JSON . parse ( data ) ;
549+ } catch {
550+ // ignore
551+ }
552+ reject (
553+ new RpcError ( {
554+ rpcName : "checkup_report_file_data" ,
555+ statusCode : res . statusCode || 0 ,
556+ payloadText : data ,
557+ payloadJson,
558+ } )
559+ ) ;
560+ }
561+ } ) ;
562+ res . on ( "error" , reject ) ;
563+ }
564+ ) ;
565+ req . on ( "error" , reject ) ;
566+ req . end ( ) ;
567+ } ) ;
533568}
0 commit comments