@open-mercato/core 0.4.2-canary-5f415b8a44 → 0.4.2-canary-d0a025141f

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.4.2-canary-5f415b8a44",
3
+ "version": "0.4.2-canary-d0a025141f",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -207,7 +207,7 @@
207
207
  }
208
208
  },
209
209
  "dependencies": {
210
- "@open-mercato/shared": "0.4.2-canary-5f415b8a44",
210
+ "@open-mercato/shared": "0.4.2-canary-d0a025141f",
211
211
  "@xyflow/react": "^12.6.0",
212
212
  "date-fns": "^4.1.0",
213
213
  "date-fns-tz": "^3.2.0"
@@ -194,11 +194,14 @@ describe('Workflow Instances API', () => {
194
194
  const request = new NextRequest('http://localhost/api/workflows/instances?entityType=order&entityId=order-123')
195
195
  await listInstances(request)
196
196
 
197
+ // The implementation uses JSONB $contains queries for metadata filtering
197
198
  expect(mockEm.findAndCount).toHaveBeenCalledWith(
198
199
  WorkflowInstance,
199
200
  expect.objectContaining({
200
- 'metadata.entityType': 'order',
201
- 'metadata.entityId': 'order-123',
201
+ $and: expect.arrayContaining([
202
+ { metadata: { $contains: { entityType: 'order' } } },
203
+ { metadata: { $contains: { entityId: 'order-123' } } },
204
+ ]),
202
205
  }),
203
206
  expect.any(Object)
204
207
  )
@@ -318,22 +318,24 @@ describe('Activity Executor (Unit Tests)', () => {
318
318
 
319
319
  describe('UPDATE_ENTITY activity', () => {
320
320
  test('should execute UPDATE_ENTITY activity successfully', async () => {
321
- const mockQueryEngine = {
322
- update: jest.fn().mockResolvedValue({ updated: 1 }),
321
+ const mockCommandBus = {
322
+ execute: jest.fn().mockResolvedValue({
323
+ result: { id: 'order-123', status: 'confirmed' },
324
+ logEntry: { id: 'log-123' },
325
+ }),
323
326
  }
324
327
 
325
- mockContainer.resolve.mockReturnValue(mockQueryEngine)
328
+ mockContainer.resolve.mockReturnValue(mockCommandBus)
326
329
 
327
330
  const activity: ActivityDefinition = {
328
331
  activityId: 'activity-8',
329
332
  activityName: 'Update Order Status',
330
333
  activityType: 'UPDATE_ENTITY',
331
334
  config: {
332
- entityType: 'orders',
333
- entityId: 'order-123',
334
- updates: {
335
- status: 'confirmed',
336
- confirmedAt: new Date().toISOString(),
335
+ commandId: 'sales.orders.update',
336
+ input: {
337
+ id: 'order-123',
338
+ statusEntryId: 'status-confirmed-id',
337
339
  },
338
340
  },
339
341
  }
@@ -346,21 +348,22 @@ describe('Activity Executor (Unit Tests)', () => {
346
348
  )
347
349
 
348
350
  expect(result.success).toBe(true)
349
- expect(result.output.updated).toBe(true)
350
- expect(mockQueryEngine.update).toHaveBeenCalledWith({
351
- entity: 'orders',
352
- where: { id: 'order-123' },
353
- data: expect.objectContaining({
354
- status: 'confirmed',
355
- }),
356
- tenantId: testTenantId,
357
- organizationId: testOrgId,
358
- })
351
+ expect(result.output.executed).toBe(true)
352
+ expect(result.output.commandId).toBe('sales.orders.update')
353
+ expect(mockCommandBus.execute).toHaveBeenCalledWith(
354
+ 'sales.orders.update',
355
+ expect.objectContaining({
356
+ input: expect.objectContaining({
357
+ id: 'order-123',
358
+ statusEntryId: 'status-confirmed-id',
359
+ }),
360
+ })
361
+ )
359
362
  })
360
363
 
361
- test('should fail UPDATE_ENTITY if query engine not available', async () => {
364
+ test('should fail UPDATE_ENTITY if command bus not available', async () => {
362
365
  mockContainer.resolve.mockImplementation(() => {
363
- throw new Error('queryEngine not registered')
366
+ throw new Error('commandBus not registered')
364
367
  })
365
368
 
366
369
  const activity: ActivityDefinition = {
@@ -368,9 +371,8 @@ describe('Activity Executor (Unit Tests)', () => {
368
371
  activityName: 'Test Update',
369
372
  activityType: 'UPDATE_ENTITY',
370
373
  config: {
371
- entityType: 'orders',
372
- entityId: 'order-123',
373
- updates: { status: 'confirmed' },
374
+ commandId: 'sales.orders.update',
375
+ input: { id: 'order-123', status: 'confirmed' },
374
376
  },
375
377
  }
376
378
 
@@ -382,23 +384,23 @@ describe('Activity Executor (Unit Tests)', () => {
382
384
  )
383
385
 
384
386
  expect(result.success).toBe(false)
385
- expect(result.error).toContain('queryEngine not registered')
387
+ expect(result.error).toContain('commandBus not registered')
386
388
  })
387
389
 
388
390
  test('should fail UPDATE_ENTITY if missing required fields', async () => {
389
- const mockQueryEngine = {
390
- update: jest.fn().mockResolvedValue({ updated: 1 }),
391
+ const mockCommandBus = {
392
+ execute: jest.fn().mockResolvedValue({ result: {} }),
391
393
  }
392
394
 
393
- mockContainer.resolve.mockReturnValue(mockQueryEngine)
395
+ mockContainer.resolve.mockReturnValue(mockCommandBus)
394
396
 
395
397
  const activity: ActivityDefinition = {
396
398
  activityId: 'activity-10',
397
399
  activityName: 'Invalid Update',
398
400
  activityType: 'UPDATE_ENTITY',
399
401
  config: {
400
- entityType: 'orders',
401
- // Missing entityId and updates
402
+ // Missing commandId
403
+ input: { id: 'order-123' },
402
404
  },
403
405
  }
404
406
 
@@ -410,7 +412,7 @@ describe('Activity Executor (Unit Tests)', () => {
410
412
  )
411
413
 
412
414
  expect(result.success).toBe(false)
413
- expect(result.error).toContain('requires "entityType", "entityId", and "updates"')
415
+ expect(result.error).toContain('requires "commandId"')
414
416
  })
415
417
  })
416
418
 
@@ -1095,13 +1097,16 @@ describe('Activity Executor (Unit Tests)', () => {
1095
1097
  emitEvent: jest.fn().mockResolvedValue(undefined),
1096
1098
  }
1097
1099
 
1098
- const mockQueryEngine = {
1099
- update: jest.fn().mockResolvedValue({ updated: 1 }),
1100
+ const mockCommandBus = {
1101
+ execute: jest.fn().mockResolvedValue({
1102
+ result: { id: 'order-123', status: 'confirmed' },
1103
+ logEntry: { id: 'log-123' },
1104
+ }),
1100
1105
  }
1101
1106
 
1102
1107
  mockContainer.resolve
1103
1108
  .mockReturnValueOnce(mockEventBus) // First activity
1104
- .mockReturnValueOnce(mockQueryEngine) // Second activity
1109
+ .mockReturnValueOnce(mockCommandBus) // Second activity
1105
1110
 
1106
1111
  const activities: ActivityDefinition[] = [
1107
1112
  {
@@ -1118,9 +1123,11 @@ describe('Activity Executor (Unit Tests)', () => {
1118
1123
  activityName: 'Update Entity',
1119
1124
  activityType: 'UPDATE_ENTITY',
1120
1125
  config: {
1121
- entityType: 'orders',
1122
- entityId: 'order-123',
1123
- updates: { status: 'confirmed' },
1126
+ commandId: 'sales.orders.update',
1127
+ input: {
1128
+ id: 'order-123',
1129
+ statusEntryId: 'status-confirmed-id',
1130
+ },
1124
1131
  },
1125
1132
  },
1126
1133
  ]
@@ -462,15 +462,35 @@ describe('Transition Handler (Unit Tests)', () => {
462
462
  })
463
463
 
464
464
  test('should reject transition if pre-conditions fail', async () => {
465
+ // Create a definition with preConditions
466
+ const definitionWithPreConditions = {
467
+ ...mockDefinition,
468
+ definition: {
469
+ ...mockDefinition.definition,
470
+ transitions: [
471
+ { fromStepId: 'start', toStepId: 'step-1' },
472
+ {
473
+ fromStepId: 'step-1',
474
+ toStepId: 'step-2',
475
+ preConditions: [{ ruleId: 'test-guard-rule', required: true }],
476
+ },
477
+ { fromStepId: 'step-2', toStepId: 'end' },
478
+ ],
479
+ },
480
+ }
481
+
465
482
  mockEm.findOne
466
- .mockResolvedValueOnce(mockDefinition) // evaluateTransition
467
- .mockResolvedValueOnce(mockDefinition) // evaluatePreConditions
468
-
469
- ;(ruleEngine.executeRules as jest.Mock).mockResolvedValueOnce({
470
- allowed: false,
471
- executedRules: [],
472
- totalExecutionTime: 10,
473
- errors: ['Pre-condition rule failed'],
483
+ .mockResolvedValueOnce(definitionWithPreConditions) // evaluateTransition
484
+ .mockResolvedValueOnce(definitionWithPreConditions) // evaluatePreConditions
485
+
486
+ ;(ruleEngine.executeRuleByRuleId as jest.Mock).mockResolvedValueOnce({
487
+ success: false,
488
+ ruleId: 'test-guard-rule',
489
+ ruleName: 'Test Guard Rule',
490
+ conditionResult: false,
491
+ actionsExecuted: null,
492
+ executionTime: 10,
493
+ error: undefined,
474
494
  })
475
495
 
476
496
  mockEm.create.mockReturnValue({} as any)
@@ -491,15 +511,35 @@ describe('Transition Handler (Unit Tests)', () => {
491
511
  })
492
512
 
493
513
  test('should log transition rejection event', async () => {
494
- mockEm.findOne
495
- .mockResolvedValueOnce(mockDefinition)
496
- .mockResolvedValueOnce(mockDefinition)
514
+ // Create a definition with preConditions
515
+ const definitionWithPreConditions = {
516
+ ...mockDefinition,
517
+ definition: {
518
+ ...mockDefinition.definition,
519
+ transitions: [
520
+ { fromStepId: 'start', toStepId: 'step-1' },
521
+ {
522
+ fromStepId: 'step-1',
523
+ toStepId: 'step-2',
524
+ preConditions: [{ ruleId: 'test-guard-rule', required: true }],
525
+ },
526
+ { fromStepId: 'step-2', toStepId: 'end' },
527
+ ],
528
+ },
529
+ }
497
530
 
498
- ;(ruleEngine.executeRules as jest.Mock).mockResolvedValueOnce({
499
- allowed: false,
500
- executedRules: [],
501
- totalExecutionTime: 10,
502
- errors: ['Rule XYZ failed'],
531
+ mockEm.findOne
532
+ .mockResolvedValueOnce(definitionWithPreConditions)
533
+ .mockResolvedValueOnce(definitionWithPreConditions)
534
+
535
+ ;(ruleEngine.executeRuleByRuleId as jest.Mock).mockResolvedValueOnce({
536
+ success: false,
537
+ ruleId: 'test-guard-rule',
538
+ ruleName: 'Test Guard Rule',
539
+ conditionResult: false,
540
+ actionsExecuted: null,
541
+ executionTime: 10,
542
+ error: undefined,
503
543
  })
504
544
 
505
545
  mockEm.create.mockReturnValue({} as any)
@@ -527,27 +567,36 @@ describe('Transition Handler (Unit Tests)', () => {
527
567
  })
528
568
 
529
569
  test('should execute transition even if post-conditions fail (warning only)', async () => {
530
- mockEm.findOne.mockReset()
531
- mockEm.findOne
532
- .mockResolvedValueOnce(mockDefinition)
533
- .mockResolvedValueOnce(mockDefinition)
534
- .mockResolvedValueOnce(mockDefinition)
535
- .mockResolvedValueOnce(mockDefinition)
536
- .mockResolvedValueOnce(mockDefinition)
537
- .mockResolvedValue(mockDefinition)
570
+ // Create a definition with postConditions
571
+ const definitionWithPostConditions = {
572
+ ...mockDefinition,
573
+ definition: {
574
+ ...mockDefinition.definition,
575
+ transitions: [
576
+ { fromStepId: 'start', toStepId: 'step-1' },
577
+ {
578
+ fromStepId: 'step-1',
579
+ toStepId: 'step-2',
580
+ postConditions: [{ ruleId: 'test-post-rule', required: true }],
581
+ },
582
+ { fromStepId: 'step-2', toStepId: 'end' },
583
+ ],
584
+ },
585
+ }
538
586
 
539
- ;(ruleEngine.executeRules as jest.Mock)
540
- .mockResolvedValueOnce({
541
- allowed: true,
542
- executedRules: [],
543
- totalExecutionTime: 10,
544
- })
545
- .mockResolvedValueOnce({
546
- allowed: false, // Post-condition failed
547
- executedRules: [],
548
- totalExecutionTime: 5,
549
- errors: ['Post-condition rule failed'],
550
- })
587
+ mockEm.findOne.mockReset()
588
+ mockEm.findOne.mockResolvedValue(definitionWithPostConditions)
589
+
590
+ // Post-condition fails
591
+ ;(ruleEngine.executeRuleByRuleId as jest.Mock).mockResolvedValueOnce({
592
+ success: false,
593
+ ruleId: 'test-post-rule',
594
+ ruleName: 'Test Post Rule',
595
+ conditionResult: false,
596
+ actionsExecuted: null,
597
+ executionTime: 5,
598
+ error: undefined,
599
+ })
551
600
 
552
601
  mockEm.create.mockReturnValue({} as any)
553
602
 
@@ -709,22 +758,33 @@ describe('Transition Handler (Unit Tests)', () => {
709
758
 
710
759
  describe('Business Rules Integration', () => {
711
760
  test('should call rule engine for pre-conditions with correct entityType', async () => {
712
- mockEm.findOne
713
- .mockResolvedValueOnce(mockDefinition)
714
- .mockResolvedValueOnce(mockDefinition)
715
- .mockResolvedValueOnce(mockDefinition)
761
+ // Create a definition with preConditions
762
+ const definitionWithPreConditions = {
763
+ ...mockDefinition,
764
+ definition: {
765
+ ...mockDefinition.definition,
766
+ transitions: [
767
+ { fromStepId: 'start', toStepId: 'step-1' },
768
+ {
769
+ fromStepId: 'step-1',
770
+ toStepId: 'step-2',
771
+ preConditions: [{ ruleId: 'test-guard-rule', required: true }],
772
+ },
773
+ { fromStepId: 'step-2', toStepId: 'end' },
774
+ ],
775
+ },
776
+ }
716
777
 
717
- ;(ruleEngine.executeRules as jest.Mock)
718
- .mockResolvedValueOnce({
719
- allowed: true,
720
- executedRules: [],
721
- totalExecutionTime: 10,
722
- })
723
- .mockResolvedValueOnce({
724
- allowed: true,
725
- executedRules: [],
726
- totalExecutionTime: 5,
727
- })
778
+ mockEm.findOne.mockResolvedValue(definitionWithPreConditions)
779
+
780
+ ;(ruleEngine.executeRuleByRuleId as jest.Mock).mockResolvedValueOnce({
781
+ success: true,
782
+ ruleId: 'test-guard-rule',
783
+ ruleName: 'Test Guard Rule',
784
+ conditionResult: true,
785
+ actionsExecuted: null,
786
+ executionTime: 10,
787
+ })
728
788
 
729
789
  mockEm.create.mockReturnValue({} as any)
730
790
 
@@ -737,9 +797,10 @@ describe('Transition Handler (Unit Tests)', () => {
737
797
  { workflowContext: {} }
738
798
  )
739
799
 
740
- expect(ruleEngine.executeRules).toHaveBeenCalledWith(
800
+ expect(ruleEngine.executeRuleByRuleId).toHaveBeenCalledWith(
741
801
  mockEm,
742
802
  expect.objectContaining({
803
+ ruleId: 'test-guard-rule',
743
804
  entityType: 'workflow:simple-approval:transition',
744
805
  eventType: 'pre_transition',
745
806
  data: expect.objectContaining({
@@ -751,26 +812,34 @@ describe('Transition Handler (Unit Tests)', () => {
751
812
  })
752
813
 
753
814
  test('should call rule engine for post-conditions with correct eventType', async () => {
754
- mockEm.findOne.mockReset()
755
- mockEm.findOne
756
- .mockResolvedValueOnce(mockDefinition)
757
- .mockResolvedValueOnce(mockDefinition)
758
- .mockResolvedValueOnce(mockDefinition)
759
- .mockResolvedValueOnce(mockDefinition)
760
- .mockResolvedValueOnce(mockDefinition)
761
- .mockResolvedValue(mockDefinition)
815
+ // Create a definition with postConditions
816
+ const definitionWithPostConditions = {
817
+ ...mockDefinition,
818
+ definition: {
819
+ ...mockDefinition.definition,
820
+ transitions: [
821
+ { fromStepId: 'start', toStepId: 'step-1' },
822
+ {
823
+ fromStepId: 'step-1',
824
+ toStepId: 'step-2',
825
+ postConditions: [{ ruleId: 'test-post-rule', required: true }],
826
+ },
827
+ { fromStepId: 'step-2', toStepId: 'end' },
828
+ ],
829
+ },
830
+ }
762
831
 
763
- ;(ruleEngine.executeRules as jest.Mock)
764
- .mockResolvedValueOnce({
765
- allowed: true,
766
- executedRules: [],
767
- totalExecutionTime: 10,
768
- })
769
- .mockResolvedValueOnce({
770
- allowed: true,
771
- executedRules: [],
772
- totalExecutionTime: 5,
773
- })
832
+ mockEm.findOne.mockReset()
833
+ mockEm.findOne.mockResolvedValue(definitionWithPostConditions)
834
+
835
+ ;(ruleEngine.executeRuleByRuleId as jest.Mock).mockResolvedValueOnce({
836
+ success: true,
837
+ ruleId: 'test-post-rule',
838
+ ruleName: 'Test Post Rule',
839
+ conditionResult: true,
840
+ actionsExecuted: null,
841
+ executionTime: 5,
842
+ })
774
843
 
775
844
  mockEm.create.mockReturnValue({} as any)
776
845
 
@@ -783,11 +852,11 @@ describe('Transition Handler (Unit Tests)', () => {
783
852
  { workflowContext: {} }
784
853
  )
785
854
 
786
- // Check second call to executeRules (post-conditions)
787
- expect(ruleEngine.executeRules).toHaveBeenNthCalledWith(
788
- 2,
855
+ // Check call to executeRuleByRuleId for post-conditions
856
+ expect(ruleEngine.executeRuleByRuleId).toHaveBeenCalledWith(
789
857
  mockEm,
790
858
  expect.objectContaining({
859
+ ruleId: 'test-post-rule',
791
860
  entityType: 'workflow:simple-approval:transition',
792
861
  eventType: 'post_transition',
793
862
  })
@@ -795,22 +864,33 @@ describe('Transition Handler (Unit Tests)', () => {
795
864
  })
796
865
 
797
866
  test('should pass workflow context and trigger data to rule engine', async () => {
798
- mockEm.findOne
799
- .mockResolvedValueOnce(mockDefinition)
800
- .mockResolvedValueOnce(mockDefinition)
801
- .mockResolvedValueOnce(mockDefinition)
867
+ // Create a definition with preConditions
868
+ const definitionWithPreConditions = {
869
+ ...mockDefinition,
870
+ definition: {
871
+ ...mockDefinition.definition,
872
+ transitions: [
873
+ { fromStepId: 'start', toStepId: 'step-1' },
874
+ {
875
+ fromStepId: 'step-1',
876
+ toStepId: 'step-2',
877
+ preConditions: [{ ruleId: 'test-guard-rule', required: true }],
878
+ },
879
+ { fromStepId: 'step-2', toStepId: 'end' },
880
+ ],
881
+ },
882
+ }
802
883
 
803
- ;(ruleEngine.executeRules as jest.Mock)
804
- .mockResolvedValueOnce({
805
- allowed: true,
806
- executedRules: [],
807
- totalExecutionTime: 10,
808
- })
809
- .mockResolvedValueOnce({
810
- allowed: true,
811
- executedRules: [],
812
- totalExecutionTime: 5,
813
- })
884
+ mockEm.findOne.mockResolvedValue(definitionWithPreConditions)
885
+
886
+ ;(ruleEngine.executeRuleByRuleId as jest.Mock).mockResolvedValueOnce({
887
+ success: true,
888
+ ruleId: 'test-guard-rule',
889
+ ruleName: 'Test Guard Rule',
890
+ conditionResult: true,
891
+ actionsExecuted: null,
892
+ executionTime: 10,
893
+ })
814
894
 
815
895
  mockEm.create.mockReturnValue({} as any)
816
896
 
@@ -830,7 +910,7 @@ describe('Transition Handler (Unit Tests)', () => {
830
910
  }
831
911
  )
832
912
 
833
- expect(ruleEngine.executeRules).toHaveBeenCalledWith(
913
+ expect(ruleEngine.executeRuleByRuleId).toHaveBeenCalledWith(
834
914
  mockEm,
835
915
  expect.objectContaining({
836
916
  data: expect.objectContaining({