@@ -48,7 +48,7 @@ export default function DataPanel() {
4848 // that drives the Variables inputs and the Pipeline Debug — see
4949 // useEditorSnapshot. recomputeEditorState() is called on every editor edit and
5050 // on placeholder changes.
51- const [ editorState , recomputeEditorState ] = useEditorSnapshot ( editorRef , pipeline . computeEditorState ) ;
51+ const [ editorState , recomputeEditorState ] = useEditorSnapshot ( editorRef , pipeline . computeEditorStateWithTypes ) ;
5252
5353 const collection = selectedCollection . value ;
5454
@@ -95,9 +95,10 @@ export default function DataPanel() {
9595 async function runQuery ( ) {
9696 if ( ! collection || ! editorRef . current ) return ;
9797 const rawText = editorRef . current . getValue ( ) ;
98- const result = await query . runQuery ( collection , rawText , pipeline . substitutePlaceholders ) ;
98+ await pipeline . ensureFieldTypes ( collection , pipeline . referencedFields ( rawText ) ) ;
99+ const result = await query . runQuery ( collection , rawText , pipeline . substituteWithTypes ) ;
99100 if ( result ) {
100- addToHistory ( collection , rawText , { ...pipeline . placeholderValues . value } ) ;
101+ addToHistory ( collection , rawText , { ...pipeline . placeholderValues . value } , { ... pipeline . placeholderTypes . value } ) ;
101102 }
102103 }
103104
@@ -119,6 +120,7 @@ export default function DataPanel() {
119120 if ( external && external . collection === collection ) {
120121 pendingPipelineLoad . value = null ;
121122 if ( external . variables ) pipeline . placeholderValues . value = { ...external . variables } ;
123+ if ( external . placeholderTypes ) pipeline . placeholderTypes . value = { ...external . placeholderTypes } ;
122124 setTimeout ( ( ) => {
123125 if ( ! editorRef . current ) return ;
124126 pipeline . suppressSync . value = true ;
@@ -133,6 +135,7 @@ export default function DataPanel() {
133135 if ( pending ) {
134136 pendingLoadRef . current = null ;
135137 if ( pending . variables ) pipeline . placeholderValues . value = { ...pending . variables } ;
138+ if ( pending . placeholderTypes ) pipeline . placeholderTypes . value = { ...pending . placeholderTypes } ;
136139 setTimeout ( ( ) => {
137140 if ( ! editorRef . current ) return ;
138141 pipeline . suppressSync . value = true ;
@@ -148,6 +151,7 @@ export default function DataPanel() {
148151 if ( saved ) {
149152 skip . value = saved . skip || 0 ;
150153 if ( saved . variables ) pipeline . placeholderValues . value = { ...saved . variables } ;
154+ if ( saved . placeholderTypes ) pipeline . placeholderTypes . value = { ...saved . placeholderTypes } ;
151155 setTimeout ( ( ) => {
152156 if ( ! editorRef . current ) return ;
153157 pipeline . suppressSync . value = true ;
@@ -172,11 +176,18 @@ export default function DataPanel() {
172176 return ( ) => { saveStateForCleanup ( collection ) ; } ;
173177 } , [ collection ] ) ;
174178
179+ useEffect ( ( ) => {
180+ if ( ! collection || ! editorRef . current ) return ;
181+ pipeline . ensureFieldTypes ( collection , pipeline . referencedFields ( editorState . text ) )
182+ . then ( ( changed ) => { if ( changed ) recomputeEditorState ( ) ; } ) ;
183+ } , [ editorState . text , collection ] ) ;
184+
175185 function saveStateForCleanup ( col ) {
176186 if ( ! editorRef . current ) return ;
177187 savePipelineState ( col , {
178188 pipelineText : editorRef . current . getValue ( ) ,
179189 variables : { ...pipeline . placeholderValues . value } ,
190+ placeholderTypes : { ...pipeline . placeholderTypes . value } ,
180191 skip : skip . value ,
181192 } ) ;
182193 }
@@ -197,7 +208,7 @@ export default function DataPanel() {
197208 // Falls back to {} when the pipeline has no $match or is unparseable.
198209 if ( ! editorRef . current ) return { } ;
199210 try {
200- const text = pipeline . substitutePlaceholders ( editorRef . current . getValue ( ) ) ;
211+ const text = pipeline . substituteWithTypes ( editorRef . current . getValue ( ) ) ;
201212 const parsed = JSON5 . parse ( text ) ;
202213 if ( Array . isArray ( parsed ) ) {
203214 const match = parsed . find ( ( s ) => s && typeof s === 'object' && s . $match ) ;
@@ -318,7 +329,7 @@ export default function DataPanel() {
318329 clearTimeout ( persistTimerRef . current ) ;
319330 persistTimerRef . current = setTimeout ( ( ) => {
320331 if ( ! editorRef . current ) return ;
321- saveLastPipeline ( editorRef . current . getValue ( ) , pipeline . placeholderValues . value ) ;
332+ saveLastPipeline ( editorRef . current . getValue ( ) , pipeline . placeholderValues . value , pipeline . placeholderTypes . value ) ;
322333 } , 400 ) ;
323334 }
324335
@@ -338,16 +349,17 @@ export default function DataPanel() {
338349 }
339350 }
340351
341- function handleLoadPipeline ( pipelineText , col , variables ) {
352+ function handleLoadPipeline ( pipelineText , col , variables , placeholderTypes ) {
342353 if ( col && col !== collection ) {
343354 // Defer to the [collection] effect — it will apply the pipeline and variables
344355 // after reset() instead of racing the default path.
345- pendingLoadRef . current = { pipelineText, variables } ;
356+ pendingLoadRef . current = { pipelineText, variables, placeholderTypes } ;
346357 selectedCollection . value = col ;
347358 return ;
348359 }
349360 if ( selectionMode . value ) selectionPipelineDirty . value = true ;
350361 if ( variables ) pipeline . placeholderValues . value = { ...variables } ;
362+ if ( placeholderTypes ) pipeline . placeholderTypes . value = { ...placeholderTypes } ;
351363 if ( editorRef . current ) {
352364 pipeline . suppressSync . value = true ;
353365 editorRef . current . setValue ( pipelineText ) ;
@@ -427,6 +439,14 @@ export default function DataPanel() {
427439 handleSetPlaceholder . _timer = setTimeout ( runQuery , 400 ) ;
428440 }
429441
442+ function handleSetPlaceholderType ( name , type ) {
443+ pipeline . setPlaceholderType ( name , type ) ;
444+ persistLastPipeline ( ) ;
445+ recomputeEditorState ( ) ;
446+ clearTimeout ( handleSetPlaceholder . _timer ) ;
447+ handleSetPlaceholder . _timer = setTimeout ( runQuery , 400 ) ;
448+ }
449+
430450 async function downloadAll ( ) {
431451 const tc = pagination . totalCount . value ;
432452 if ( tc !== null && tc > 10_000 ) {
@@ -455,7 +475,7 @@ export default function DataPanel() {
455475
456476 let pipelineStages ;
457477 try {
458- const text = pipeline . substitutePlaceholders ( editorRef . current . getValue ( ) ) ;
478+ const text = pipeline . substituteWithTypes ( editorRef . current . getValue ( ) ) ;
459479 const parsed = JSON5 . parse ( text ) ;
460480 if ( ! Array . isArray ( parsed ) ) throw new Error ( 'pipeline must be a JSON array' ) ;
461481 pipelineStages = stripPaginationStages ( parsed ) ;
@@ -533,7 +553,7 @@ export default function DataPanel() {
533553 if ( ! editorRef . current ) return ;
534554 let pipelineStages ;
535555 try {
536- const text = pipeline . substitutePlaceholders ( editorRef . current . getValue ( ) ) ;
556+ const text = pipeline . substituteWithTypes ( editorRef . current . getValue ( ) ) ;
537557 const parsed = JSON5 . parse ( text ) ;
538558 if ( ! Array . isArray ( parsed ) ) throw new Error ( 'pipeline must be a JSON array' ) ;
539559 pipelineStages = stripPaginationStages ( parsed ) ;
@@ -614,7 +634,7 @@ export default function DataPanel() {
614634 if ( ! editorRef . current ) return ;
615635 let pipelineStages ;
616636 try {
617- const text = pipeline . substitutePlaceholders ( editorRef . current . getValue ( ) ) ;
637+ const text = pipeline . substituteWithTypes ( editorRef . current . getValue ( ) ) ;
618638 const parsed = JSON5 . parse ( text ) ;
619639 if ( ! Array . isArray ( parsed ) ) throw new Error ( 'pipeline must be a JSON array' ) ;
620640 pipelineStages = stripPaginationStages ( parsed ) ;
@@ -706,7 +726,7 @@ export default function DataPanel() {
706726 if ( ! editorRef . current ) return ;
707727 let pipelineStages ;
708728 try {
709- const text = pipeline . substitutePlaceholders ( editorRef . current . getValue ( ) ) ;
729+ const text = pipeline . substituteWithTypes ( editorRef . current . getValue ( ) ) ;
710730 const parsed = JSON5 . parse ( text ) ;
711731 if ( ! Array . isArray ( parsed ) ) throw new Error ( 'pipeline must be a JSON array' ) ;
712732 pipelineStages = stripPaginationStages ( parsed ) ;
@@ -847,8 +867,11 @@ export default function DataPanel() {
847867 < PlaceholderInputs
848868 names = { placeholderNames }
849869 values = { pipeline . placeholderValues . value }
870+ types = { pipeline . placeholderTypes . value }
850871 onSetValue = { handleSetPlaceholder }
872+ onSetType = { handleSetPlaceholderType }
851873 onRunQuery = { runQuery }
874+ resolvedTypeFor = { ( name ) => pipeline . resolvedTypeForName ( name , editorState . fieldMap || { } , editorState . parsed != null ) }
852875 />
853876 < PipelineDebug pipeline = { editorState . parsed } />
854877 </ div >
0 commit comments