1- import type { AnyFlow , FlowShape } from '@pgflow/dsl' ;
1+ import type { AnyFlow } from '@pgflow/dsl' ;
22import { compileFlow } from '@pgflow/dsl' ;
3- import { isLocalSupabase } from '../shared/localDetection.ts' ;
43
54/**
65 * Response type for the /flows/:slug endpoint
@@ -18,38 +17,6 @@ export interface ErrorResponse {
1817 message : string ;
1918}
2019
21- /**
22- * Response type for the /flows/:slug/ensure-compiled endpoint
23- */
24- export interface EnsureCompiledResponse {
25- status : 'compiled' | 'verified' | 'recompiled' | 'mismatch' ;
26- differences : string [ ] ;
27- mode : 'development' | 'production' ;
28- }
29-
30- /**
31- * Request body for the /flows/:slug/ensure-compiled endpoint
32- */
33- export interface EnsureCompiledRequest {
34- shape : FlowShape ;
35- }
36-
37- /**
38- * SQL function interface for database operations
39- * Compatible with the postgres library's tagged template interface
40- */
41- // eslint-disable-next-line @typescript-eslint/no-explicit-any
42- // deno-lint-ignore no-explicit-any
43- export type SqlFunction = ( strings : TemplateStringsArray , ...values : any [ ] ) => Promise < any [ ] > ;
44-
45- /**
46- * Options for configuring the ControlPlane handler
47- */
48- export interface ControlPlaneOptions {
49- /** SQL function for database operations (required for ensure-compiled endpoint) */
50- sql ?: SqlFunction ;
51- }
52-
5320/**
5421 * Input type for flow registration - accepts array or object (for namespace imports)
5522 */
@@ -87,13 +54,9 @@ function buildFlowRegistry(flows: AnyFlow[]): Map<string, AnyFlow> {
8754/**
8855 * Creates a request handler for the ControlPlane HTTP API
8956 * @param flowsInput Array or object of flow definitions to register
90- * @param options Optional configuration for database and authentication
9157 * @returns Request handler function
9258 */
93- export function createControlPlaneHandler (
94- flowsInput : FlowInput ,
95- options ?: ControlPlaneOptions
96- ) {
59+ export function createControlPlaneHandler ( flowsInput : FlowInput ) {
9760 const flows = normalizeFlowInput ( flowsInput ) ;
9861 const registry = buildFlowRegistry ( flows ) ;
9962
@@ -112,15 +75,6 @@ export function createControlPlaneHandler(
11275 return handleGetFlow ( registry , slug ) ;
11376 }
11477
115- // Handle POST /flows/:slug/ensure-compiled
116- const ensureCompiledMatch = pathname . match (
117- / ^ \/ f l o w s \/ ( [ a - z A - Z 0 - 9 _ ] + ) \/ e n s u r e - c o m p i l e d $ /
118- ) ;
119- if ( ensureCompiledMatch && req . method === 'POST' ) {
120- const slug = ensureCompiledMatch [ 1 ] ;
121- return handleEnsureCompiled ( req , slug , options ) ;
122- }
123-
12478 // 404 for unknown routes
12579 return jsonResponse (
12680 {
@@ -192,121 +146,3 @@ function jsonResponse(data: unknown, status: number): Response {
192146 } ,
193147 } ) ;
194148}
195-
196- /**
197- * Verifies authentication using apikey header against SUPABASE_SERVICE_ROLE_KEY env var
198- */
199- function verifyAuth ( request : Request ) : boolean {
200- const serviceRoleKey = Deno . env . get ( 'SUPABASE_SERVICE_ROLE_KEY' ) ;
201- if ( ! serviceRoleKey ) return false ;
202- const apikey = request . headers . get ( 'apikey' ) ;
203- return apikey === serviceRoleKey ;
204- }
205-
206- /**
207- * Validates the ensure-compiled request body
208- */
209- function validateEnsureCompiledBody (
210- body : unknown
211- ) : { valid : true ; data : EnsureCompiledRequest } | { valid : false ; error : string } {
212- if ( ! body || typeof body !== 'object' ) {
213- return { valid : false , error : 'Request body must be an object' } ;
214- }
215-
216- const { shape } = body as Record < string , unknown > ;
217-
218- if ( ! shape || typeof shape !== 'object' ) {
219- return { valid : false , error : 'Missing or invalid shape in request body' } ;
220- }
221-
222- return { valid : true , data : { shape : shape as FlowShape } } ;
223- }
224-
225- /**
226- * Handles POST /flows/:slug/ensure-compiled requests
227- */
228- async function handleEnsureCompiled (
229- request : Request ,
230- flowSlug : string ,
231- options ?: ControlPlaneOptions
232- ) : Promise < Response > {
233- // Check if SQL is configured
234- if ( ! options ?. sql ) {
235- return jsonResponse (
236- {
237- error : 'Not Found' ,
238- message : 'ensure-compiled endpoint requires SQL configuration' ,
239- } ,
240- 404
241- ) ;
242- }
243-
244- // Verify authentication
245- if ( ! verifyAuth ( request ) ) {
246- return jsonResponse (
247- {
248- error : 'Unauthorized' ,
249- message : 'Invalid or missing apikey header' ,
250- } ,
251- 401
252- ) ;
253- }
254-
255- // Parse and validate request body
256- let body : unknown ;
257- try {
258- body = await request . json ( ) ;
259- } catch {
260- return jsonResponse (
261- {
262- error : 'Bad Request' ,
263- message : 'Invalid JSON in request body' ,
264- } ,
265- 400
266- ) ;
267- }
268-
269- const validation = validateEnsureCompiledBody ( body ) ;
270- if ( ! validation . valid ) {
271- return jsonResponse (
272- {
273- error : 'Bad Request' ,
274- message : validation . error ,
275- } ,
276- 400
277- ) ;
278- }
279-
280- const { shape } = validation . data ;
281-
282- // Auto-detect mode based on environment
283- const mode = isLocalSupabase ( ) ? 'development' : 'production' ;
284-
285- // Call SQL function
286- try {
287- const [ result ] = await options . sql `
288- SELECT pgflow.ensure_flow_compiled(
289- ${ flowSlug } ,
290- ${ JSON . stringify ( shape ) } ::jsonb,
291- ${ mode }
292- ) as result
293- ` ;
294-
295- // Include detected mode in response for transparency
296- const response : EnsureCompiledResponse = {
297- ...result . result ,
298- mode,
299- } ;
300-
301- return jsonResponse ( response , response . status === 'mismatch' ? 409 : 200 ) ;
302- } catch ( error ) {
303- console . error ( 'Error calling ensure_flow_compiled:' , error ) ;
304- return jsonResponse (
305- {
306- error : 'Database Error' ,
307- message : error instanceof Error ? error . message : 'Unknown error' ,
308- } ,
309- 500
310- ) ;
311- }
312- }
0 commit comments