44 FieldCreated ,
55 FieldId ,
66 FieldUpdated ,
7+ TableActionTriggerRequested ,
78 TableId ,
89 type IExecutionContext ,
910 type IEventHandler ,
@@ -15,6 +16,16 @@ import { V2ActionTriggerService } from './v2-action-trigger.service';
1516
1617type PresencePayload = Array < { actionKey : string ; payload ?: Record < string , unknown > } > ;
1718
19+ const waitForPresenceFlush = async ( ) => {
20+ await new Promise < void > ( ( resolve ) => {
21+ if ( typeof setImmediate === 'function' ) {
22+ setImmediate ( ( ) => resolve ( ) ) ;
23+ return ;
24+ }
25+ setTimeout ( ( ) => resolve ( ) , 0 ) ;
26+ } ) ;
27+ } ;
28+
1829const fieldUpdateSemantics = {
1930 type : {
2031 realtimePath : [ 'type' ] ,
@@ -102,6 +113,7 @@ describe('V2ActionTriggerService', () => {
102113
103114 const result = await projection ?. handle ( { } as IExecutionContext , event ) ;
104115 expect ( result ?. isOk ( ) ) . toBe ( true ) ;
116+ await waitForPresenceFlush ( ) ;
105117
106118 expect ( channelSubmitted ) . toBe ( getActionTriggerChannel ( tableId . toString ( ) ) ) ;
107119 expect ( submitted ) . toEqual ( [
@@ -170,6 +182,7 @@ describe('V2ActionTriggerService', () => {
170182
171183 const result = await projection ?. handle ( { } as IExecutionContext , event ) ;
172184 expect ( result ?. isOk ( ) ) . toBe ( true ) ;
185+ await waitForPresenceFlush ( ) ;
173186
174187 expect ( channelSubmitted ) . toBe ( getActionTriggerChannel ( tableId . toString ( ) ) ) ;
175188 expect ( submitted ) . toEqual ( [
@@ -250,6 +263,7 @@ describe('V2ActionTriggerService', () => {
250263
251264 const result = await projection ?. handle ( { } as IExecutionContext , event ) ;
252265 expect ( result ?. isOk ( ) ) . toBe ( true ) ;
266+ await waitForPresenceFlush ( ) ;
253267 expect ( submitted ) . toEqual ( [
254268 {
255269 actionKey : 'setField' ,
@@ -319,6 +333,182 @@ describe('V2ActionTriggerService', () => {
319333
320334 const result = await projection ?. handle ( { } as IExecutionContext , event ) ;
321335 expect ( result ?. isOk ( ) ) . toBe ( true ) ;
336+ await waitForPresenceFlush ( ) ;
322337 expect ( submitted ) . toBeUndefined ( ) ;
323338 } ) ;
339+
340+ it ( 'emits requested action trigger payload for schema-driven presence events' , async ( ) => {
341+ let channelSubmitted : string | undefined ;
342+ let submitted : PresencePayload | undefined ;
343+
344+ const shareDbService = {
345+ connect : ( ) => ( {
346+ getPresence : ( channel : string ) => {
347+ channelSubmitted = channel ;
348+ return {
349+ create : ( ) => ( {
350+ submit : ( data : PresencePayload , cb ?: ( error ?: unknown ) => void ) => {
351+ submitted = data ;
352+ cb ?.( ) ;
353+ } ,
354+ } ) ,
355+ } ;
356+ } ,
357+ } ) ,
358+ } as unknown as ShareDbService ;
359+
360+ const registered : Array < { instance : unknown } > = [ ] ;
361+ const container = {
362+ registerInstance : ( _token : unknown , instance : unknown ) => {
363+ registered . push ( { instance } ) ;
364+ return container ;
365+ } ,
366+ } as unknown as DependencyContainer ;
367+
368+ const service = new V2ActionTriggerService ( shareDbService ) ;
369+ service . registerProjections ( container ) ;
370+
371+ const projection = registered . find (
372+ ( item ) =>
373+ ( item . instance as { constructor ?: { name ?: string } } ) . constructor ?. name ===
374+ 'V2TableActionTriggerRequestedProjection'
375+ ) ?. instance as IEventHandler < TableActionTriggerRequested > | undefined ;
376+
377+ expect ( projection ) . toBeDefined ( ) ;
378+
379+ const { baseId, tableId } = createIds ( ) ;
380+ const event = TableActionTriggerRequested . create ( {
381+ baseId,
382+ tableId,
383+ actionKey : 'setRecord' ,
384+ payload : {
385+ tableId : tableId . toString ( ) ,
386+ fieldIds : [ 'fldSource0000000001' , 'fldComputed00000002' ] ,
387+ } ,
388+ } ) ;
389+
390+ const result = await projection ?. handle ( { } as IExecutionContext , event ) ;
391+ expect ( result ?. isOk ( ) ) . toBe ( true ) ;
392+ await waitForPresenceFlush ( ) ;
393+
394+ expect ( channelSubmitted ) . toBe ( getActionTriggerChannel ( tableId . toString ( ) ) ) ;
395+ expect ( submitted ) . toEqual ( [
396+ {
397+ actionKey : 'setRecord' ,
398+ payload : {
399+ tableId : tableId . toString ( ) ,
400+ fieldIds : [ 'fldSource0000000001' , 'fldComputed00000002' ] ,
401+ } ,
402+ } ,
403+ ] ) ;
404+ } ) ;
405+
406+ it ( 'batches setField and setRecord into one presence submit for schema-driven updates' , async ( ) => {
407+ const submissions : PresencePayload [ ] = [ ] ;
408+
409+ const shareDbService = {
410+ connect : ( ) => ( {
411+ getPresence : ( ) => ( {
412+ create : ( ) => ( {
413+ submit : ( data : PresencePayload , cb ?: ( error ?: unknown ) => void ) => {
414+ submissions . push ( data ) ;
415+ cb ?.( ) ;
416+ } ,
417+ } ) ,
418+ } ) ,
419+ } ) ,
420+ } as unknown as ShareDbService ;
421+
422+ const registered : Array < { instance : unknown } > = [ ] ;
423+ const container = {
424+ registerInstance : ( _token : unknown , instance : unknown ) => {
425+ registered . push ( { instance } ) ;
426+ return container ;
427+ } ,
428+ } as unknown as DependencyContainer ;
429+
430+ const service = new V2ActionTriggerService ( shareDbService ) ;
431+ service . registerProjections ( container ) ;
432+
433+ const fieldUpdatedProjection = registered . find (
434+ ( item ) =>
435+ ( item . instance as { constructor ?: { name ?: string } } ) . constructor ?. name ===
436+ 'V2FieldUpdatedActionTriggerProjection'
437+ ) ?. instance as IEventHandler < FieldUpdated > | undefined ;
438+ const actionTriggerProjection = registered . find (
439+ ( item ) =>
440+ ( item . instance as { constructor ?: { name ?: string } } ) . constructor ?. name ===
441+ 'V2TableActionTriggerRequestedProjection'
442+ ) ?. instance as IEventHandler < TableActionTriggerRequested > | undefined ;
443+
444+ expect ( fieldUpdatedProjection ) . toBeDefined ( ) ;
445+ expect ( actionTriggerProjection ) . toBeDefined ( ) ;
446+
447+ const { baseId, tableId, fieldId } = createIds ( ) ;
448+ const fieldUpdatedEvent = FieldUpdated . create ( {
449+ baseId,
450+ tableId,
451+ fieldId,
452+ updatedProperties : [ 'type' , 'options' ] ,
453+ changes : {
454+ type : { oldValue : 'singleLineText' , newValue : 'number' } ,
455+ options : {
456+ oldValue : { showAs : { type : 'number' } } ,
457+ newValue : { formatting : { decimal : 0 } } ,
458+ } ,
459+ } ,
460+ propertySemantics : {
461+ type : fieldUpdateSemantics . type ,
462+ options : fieldUpdateSemantics . options ,
463+ } ,
464+ } ) ;
465+ const setRecordEvent = TableActionTriggerRequested . create ( {
466+ baseId,
467+ tableId,
468+ actionKey : 'setRecord' ,
469+ payload : {
470+ tableId : tableId . toString ( ) ,
471+ fieldIds : [ fieldId . toString ( ) ] ,
472+ } ,
473+ } ) ;
474+
475+ const fieldResult = await fieldUpdatedProjection ?. handle (
476+ { } as IExecutionContext ,
477+ fieldUpdatedEvent
478+ ) ;
479+ const actionResult = await actionTriggerProjection ?. handle (
480+ { } as IExecutionContext ,
481+ setRecordEvent
482+ ) ;
483+ expect ( fieldResult ?. isOk ( ) ) . toBe ( true ) ;
484+ expect ( actionResult ?. isOk ( ) ) . toBe ( true ) ;
485+
486+ await waitForPresenceFlush ( ) ;
487+
488+ expect ( submissions ) . toEqual ( [
489+ [
490+ {
491+ actionKey : 'setField' ,
492+ payload : {
493+ tableId : tableId . toString ( ) ,
494+ field : {
495+ id : fieldId . toString ( ) ,
496+ updatedProperties : [ 'type' , 'options' ] ,
497+ type : 'number' ,
498+ options : {
499+ formatting : { decimal : 0 } ,
500+ } ,
501+ } ,
502+ } ,
503+ } ,
504+ {
505+ actionKey : 'setRecord' ,
506+ payload : {
507+ tableId : tableId . toString ( ) ,
508+ fieldIds : [ fieldId . toString ( ) ] ,
509+ } ,
510+ } ,
511+ ] ,
512+ ] ) ;
513+ } ) ;
324514} ) ;
0 commit comments