@shipfox/client-triggers 4.0.0 → 6.0.1

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.
Files changed (96) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +49 -0
  3. package/dist/components/events-filter-bar.d.ts +3 -4
  4. package/dist/components/events-filter-bar.d.ts.map +1 -1
  5. package/dist/components/events-filter-bar.js +8 -16
  6. package/dist/components/events-filter-bar.js.map +1 -1
  7. package/dist/components/events-list.d.ts.map +1 -1
  8. package/dist/components/events-list.js +2 -4
  9. package/dist/components/events-list.js.map +1 -1
  10. package/dist/components/events-list.stories.d.ts +1 -16
  11. package/dist/components/events-list.stories.d.ts.map +1 -1
  12. package/dist/components/events-list.stories.js +2 -1
  13. package/dist/components/events-list.stories.js.map +1 -1
  14. package/dist/components/trigger-event-detail.d.ts +2 -2
  15. package/dist/components/trigger-event-detail.d.ts.map +1 -1
  16. package/dist/components/trigger-event-detail.js +7 -7
  17. package/dist/components/trigger-event-detail.js.map +1 -1
  18. package/dist/components/trigger-event-detail.stories.d.ts +1 -36
  19. package/dist/components/trigger-event-detail.stories.d.ts.map +1 -1
  20. package/dist/components/trigger-event-detail.stories.js +6 -3
  21. package/dist/components/trigger-event-detail.stories.js.map +1 -1
  22. package/dist/components/trigger-event-result.d.ts +2 -2
  23. package/dist/components/trigger-event-result.d.ts.map +1 -1
  24. package/dist/components/trigger-event-result.js +8 -16
  25. package/dist/components/trigger-event-result.js.map +1 -1
  26. package/dist/components/trigger-event-row.d.ts +2 -2
  27. package/dist/components/trigger-event-row.d.ts.map +1 -1
  28. package/dist/components/trigger-event-row.js +1 -1
  29. package/dist/components/trigger-event-row.js.map +1 -1
  30. package/dist/components/trigger-source-icon.d.ts +8 -6
  31. package/dist/components/trigger-source-icon.d.ts.map +1 -1
  32. package/dist/components/trigger-source-icon.js +4 -3
  33. package/dist/components/trigger-source-icon.js.map +1 -1
  34. package/dist/components/types.d.ts +3 -4
  35. package/dist/components/types.d.ts.map +1 -1
  36. package/dist/components/types.js.map +1 -1
  37. package/dist/core/trigger-event.d.ts +78 -0
  38. package/dist/core/trigger-event.d.ts.map +1 -0
  39. package/dist/core/trigger-event.js +71 -0
  40. package/dist/core/trigger-event.js.map +1 -0
  41. package/dist/feature.d.ts +23 -0
  42. package/dist/feature.d.ts.map +1 -0
  43. package/dist/feature.js +23 -0
  44. package/dist/feature.js.map +1 -0
  45. package/dist/hooks/api/trigger-event-mapper.d.ts +7 -0
  46. package/dist/hooks/api/trigger-event-mapper.d.ts.map +1 -0
  47. package/dist/hooks/api/trigger-event-mapper.js +57 -0
  48. package/dist/hooks/api/trigger-event-mapper.js.map +1 -0
  49. package/dist/hooks/api/trigger-events.d.ts +15 -138
  50. package/dist/hooks/api/trigger-events.d.ts.map +1 -1
  51. package/dist/hooks/api/trigger-events.js +44 -47
  52. package/dist/hooks/api/trigger-events.js.map +1 -1
  53. package/dist/index.d.ts +1 -0
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +1 -0
  56. package/dist/index.js.map +1 -1
  57. package/dist/pages/events-page.d.ts +1 -1
  58. package/dist/pages/events-page.d.ts.map +1 -1
  59. package/dist/pages/events-page.js +1 -1
  60. package/dist/pages/events-page.js.map +1 -1
  61. package/dist/routes/events-settings.d.ts +8 -0
  62. package/dist/routes/events-settings.d.ts.map +1 -0
  63. package/dist/routes/events-settings.js +37 -0
  64. package/dist/routes/events-settings.js.map +1 -0
  65. package/dist/search.d.ts +3 -3
  66. package/dist/search.d.ts.map +1 -1
  67. package/dist/search.js +3 -4
  68. package/dist/search.js.map +1 -1
  69. package/dist/tsconfig.test.tsbuildinfo +1 -1
  70. package/package.json +18 -9
  71. package/src/components/events-filter-bar.tsx +16 -11
  72. package/src/components/events-list.stories.tsx +3 -2
  73. package/src/components/events-list.test.tsx +4 -3
  74. package/src/components/events-list.tsx +2 -12
  75. package/src/components/trigger-event-detail.stories.tsx +7 -3
  76. package/src/components/trigger-event-detail.test.tsx +6 -1
  77. package/src/components/trigger-event-detail.tsx +21 -18
  78. package/src/components/trigger-event-result.test.ts +6 -6
  79. package/src/components/trigger-event-result.ts +9 -13
  80. package/src/components/trigger-event-row.tsx +5 -5
  81. package/src/components/trigger-source-icon.tsx +9 -7
  82. package/src/components/types.ts +7 -7
  83. package/src/core/trigger-event.test.ts +40 -0
  84. package/src/core/trigger-event.ts +126 -0
  85. package/src/feature.ts +17 -0
  86. package/src/hooks/api/trigger-event-mapper.test.ts +88 -0
  87. package/src/hooks/api/trigger-event-mapper.ts +76 -0
  88. package/src/hooks/api/trigger-events.test.ts +49 -12
  89. package/src/hooks/api/trigger-events.ts +96 -56
  90. package/src/index.ts +1 -0
  91. package/src/pages/events-page.test.tsx +4 -4
  92. package/src/pages/events-page.tsx +3 -4
  93. package/src/routes/events-settings.tsx +30 -0
  94. package/src/search.ts +4 -5
  95. package/tsconfig.build.tsbuildinfo +1 -1
  96. package/vercel.json +5 -0
@@ -0,0 +1,126 @@
1
+ export type TriggerEventOrigin = 'integration' | 'manual' | 'cron';
2
+
3
+ export type TriggerEventOutcome = 'received' | 'routed' | 'discarded' | 'failed' | 'errored';
4
+
5
+ export const triggerEventOutcomes = [
6
+ 'received',
7
+ 'routed',
8
+ 'discarded',
9
+ 'failed',
10
+ 'errored',
11
+ ] as const satisfies readonly TriggerEventOutcome[];
12
+
13
+ export type TriggerEventDecisionOutcome = 'triggered' | 'filter-error' | 'dispatch-error';
14
+
15
+ export interface TriggerEventSource {
16
+ provider: string | null;
17
+ source: string;
18
+ }
19
+
20
+ export interface TriggerEventSummary extends TriggerEventSource {
21
+ id: string;
22
+ eventRef: string;
23
+ origin: TriggerEventOrigin;
24
+ workspaceId: string;
25
+ event: string;
26
+ deliveryId: string | null;
27
+ connectionId: string | null;
28
+ outcome: TriggerEventOutcome;
29
+ matchedCount: number;
30
+ receivedAt: string;
31
+ processedAt: string | null;
32
+ createdAt: string;
33
+ }
34
+
35
+ export interface TriggerEventMatchedWorkflowResult {
36
+ id: string;
37
+ subscriptionName: string;
38
+ decision: TriggerEventDecisionOutcome;
39
+ projectId: string | null;
40
+ runId: string | null;
41
+ runName: string | null;
42
+ reason: string | null;
43
+ }
44
+
45
+ export interface TriggerEventDetail extends TriggerEventSummary {
46
+ connectionName: string | null;
47
+ payload: Record<string, unknown> | null;
48
+ decisions: TriggerEventMatchedWorkflowResult[];
49
+ }
50
+
51
+ export interface TriggerEventFacetItem {
52
+ value: string;
53
+ count: number;
54
+ }
55
+
56
+ export interface TriggerEventFacets {
57
+ sources: TriggerEventFacetItem[];
58
+ events: TriggerEventFacetItem[];
59
+ }
60
+
61
+ export interface TriggerEventListPage {
62
+ events: TriggerEventSummary[];
63
+ nextCursor: string | null;
64
+ }
65
+
66
+ export interface TriggerEventFilters {
67
+ source?: string[] | undefined;
68
+ event?: string[] | undefined;
69
+ outcome?: TriggerEventOutcome[] | undefined;
70
+ from?: string | undefined;
71
+ to?: string | undefined;
72
+ }
73
+
74
+ export function normalizeTriggerEventFilterValues(
75
+ values: readonly string[] | undefined,
76
+ ): string[] | null {
77
+ return values && values.length > 0 ? [...new Set(values)].sort() : null;
78
+ }
79
+
80
+ export function normalizeTriggerEventFilters(filters: TriggerEventFilters) {
81
+ return {
82
+ source: normalizeTriggerEventFilterValues(filters.source),
83
+ event: normalizeTriggerEventFilterValues(filters.event),
84
+ outcome: normalizeTriggerEventFilterValues(filters.outcome),
85
+ from: filters.from ?? null,
86
+ to: filters.to ?? null,
87
+ };
88
+ }
89
+
90
+ export function hasTriggerEventFilters(filters: TriggerEventFilters): boolean {
91
+ const normalized = normalizeTriggerEventFilters(filters);
92
+ return Boolean(
93
+ normalized.source || normalized.event || normalized.outcome || normalized.from || normalized.to,
94
+ );
95
+ }
96
+
97
+ export type TriggerEventResultKind = 'triggered' | 'no-match' | 'failed' | 'evaluating';
98
+
99
+ export interface TriggerEventResult {
100
+ kind: TriggerEventResultKind;
101
+ matchedWorkflowCount: number;
102
+ isFailure: boolean;
103
+ }
104
+
105
+ export function getTriggerEventResult(
106
+ event: Pick<TriggerEventSummary, 'outcome' | 'matchedCount'>,
107
+ ): TriggerEventResult {
108
+ switch (event.outcome) {
109
+ case 'routed':
110
+ return {kind: 'triggered', matchedWorkflowCount: event.matchedCount, isFailure: false};
111
+ case 'discarded':
112
+ return {kind: 'no-match', matchedWorkflowCount: 0, isFailure: false};
113
+ case 'failed':
114
+ case 'errored':
115
+ return {kind: 'failed', matchedWorkflowCount: event.matchedCount, isFailure: true};
116
+ case 'received':
117
+ return {kind: 'evaluating', matchedWorkflowCount: 0, isFailure: false};
118
+ }
119
+ }
120
+
121
+ export const triggerEventResultFilterOutcomes = {
122
+ triggered: ['routed'],
123
+ 'no-match': ['discarded'],
124
+ failed: ['failed', 'errored'],
125
+ evaluating: ['received'],
126
+ } as const satisfies Record<TriggerEventResultKind, readonly TriggerEventOutcome[]>;
package/src/feature.ts ADDED
@@ -0,0 +1,17 @@
1
+ import {defineClientFeature, type SettingsSectionEntry} from '@shipfox/client-shell';
2
+
3
+ export const triggersSettingsSections = [
4
+ {id: 'settings.events', pathSegment: 'events', label: 'Events', icon: 'pulseLine', order: 800},
5
+ ] as const satisfies readonly SettingsSectionEntry[];
6
+
7
+ export const triggersFeature = defineClientFeature({
8
+ id: 'shipfox.triggers',
9
+ routes: [
10
+ {
11
+ path: '/workspaces/$wid/settings/events',
12
+ parent: 'workspaceSettings',
13
+ impl: '@shipfox/client-triggers/routes/events-settings',
14
+ },
15
+ ],
16
+ settingsSections: triggersSettingsSections,
17
+ });
@@ -0,0 +1,88 @@
1
+ import type {
2
+ TriggerEventDetailResponseDto,
3
+ TriggerEventListItemDto,
4
+ } from '@shipfox/api-triggers-dto';
5
+ import {toTriggerEventDetail, toTriggerEventListPage} from './trigger-event-mapper.js';
6
+
7
+ const EVENT_ID = '22222222-2222-4222-8222-222222222222';
8
+ const WORKSPACE_ID = '11111111-1111-4111-8111-111111111111';
9
+
10
+ function triggerEventDto(
11
+ overrides: Partial<TriggerEventListItemDto> = {},
12
+ ): TriggerEventListItemDto {
13
+ return {
14
+ id: EVENT_ID,
15
+ event_ref: 'github:delivery-1:push',
16
+ origin: 'integration' as const,
17
+ workspace_id: WORKSPACE_ID,
18
+ provider: 'github',
19
+ source: 'github',
20
+ event: 'push',
21
+ delivery_id: 'delivery-1',
22
+ connection_id: '33333333-3333-4333-8333-333333333333',
23
+ outcome: 'routed' as const,
24
+ matched_count: 1,
25
+ received_at: '2026-06-01T00:00:00.000Z',
26
+ processed_at: '2026-06-01T00:00:01.000Z',
27
+ created_at: '2026-06-01T00:00:00.000Z',
28
+ ...overrides,
29
+ };
30
+ }
31
+
32
+ describe('trigger event mapper', () => {
33
+ test('maps a transport list page before it enters the cache', () => {
34
+ const page = toTriggerEventListPage({trigger_events: [triggerEventDto()], next_cursor: 'next'});
35
+
36
+ expect(page).toEqual({
37
+ events: [
38
+ expect.objectContaining({
39
+ eventRef: 'github:delivery-1:push',
40
+ workspaceId: WORKSPACE_ID,
41
+ matchedCount: 1,
42
+ receivedAt: '2026-06-01T00:00:00.000Z',
43
+ }),
44
+ ],
45
+ nextCursor: 'next',
46
+ });
47
+ });
48
+
49
+ test('maps matched workflow results into their domain contract', () => {
50
+ const detail = toTriggerEventDetail({
51
+ ...triggerEventDto(),
52
+ connection_name: 'Shipfox',
53
+ payload: {ref: 'main'},
54
+ decisions: [
55
+ {
56
+ id: '44444444-4444-4444-8444-444444444444',
57
+ received_event_id: EVENT_ID,
58
+ subscription_kind: 'trigger',
59
+ subscription_id: '55555555-5555-4555-8555-555555555555',
60
+ subscription_name: 'Deploy',
61
+ workflow_definition_id: '66666666-6666-4666-8666-666666666666',
62
+ project_id: '77777777-7777-4777-8777-777777777777',
63
+ workflow_run_id: null,
64
+ job_id: null,
65
+ matcher_kind: null,
66
+ matcher_ordinal: null,
67
+ decision: 'triggered',
68
+ run_id: '88888888-8888-4888-8888-888888888888',
69
+ run_name: 'deploy #1',
70
+ reason: null,
71
+ created_at: '2026-06-01T00:00:01.000Z',
72
+ },
73
+ ],
74
+ } satisfies TriggerEventDetailResponseDto);
75
+
76
+ expect(detail.decisions).toEqual([
77
+ {
78
+ id: '44444444-4444-4444-8444-444444444444',
79
+ subscriptionName: 'Deploy',
80
+ decision: 'triggered',
81
+ projectId: '77777777-7777-4777-8777-777777777777',
82
+ runId: '88888888-8888-4888-8888-888888888888',
83
+ runName: 'deploy #1',
84
+ reason: null,
85
+ },
86
+ ]);
87
+ });
88
+ });
@@ -0,0 +1,76 @@
1
+ import type {
2
+ TriggerDecisionDto,
3
+ TriggerEventDetailResponseDto,
4
+ TriggerEventFacetItemDto,
5
+ TriggerEventFacetsResponseDto,
6
+ TriggerEventListItemDto,
7
+ TriggerEventListResponseDto,
8
+ } from '@shipfox/api-triggers-dto';
9
+ import type {
10
+ TriggerEventDetail,
11
+ TriggerEventFacetItem,
12
+ TriggerEventFacets,
13
+ TriggerEventListPage,
14
+ TriggerEventMatchedWorkflowResult,
15
+ TriggerEventSummary,
16
+ } from '#core/trigger-event.js';
17
+
18
+ export function toTriggerEventSummary(event: TriggerEventListItemDto): TriggerEventSummary {
19
+ return {
20
+ id: event.id,
21
+ eventRef: event.event_ref,
22
+ origin: event.origin,
23
+ workspaceId: event.workspace_id,
24
+ provider: event.provider,
25
+ source: event.source,
26
+ event: event.event,
27
+ deliveryId: event.delivery_id,
28
+ connectionId: event.connection_id,
29
+ outcome: event.outcome,
30
+ matchedCount: event.matched_count,
31
+ receivedAt: event.received_at,
32
+ processedAt: event.processed_at,
33
+ createdAt: event.created_at,
34
+ };
35
+ }
36
+
37
+ export function toTriggerEventListPage(
38
+ response: TriggerEventListResponseDto,
39
+ ): TriggerEventListPage {
40
+ return {
41
+ events: response.trigger_events.map(toTriggerEventSummary),
42
+ nextCursor: response.next_cursor,
43
+ };
44
+ }
45
+
46
+ function toMatchedWorkflowResult(decision: TriggerDecisionDto): TriggerEventMatchedWorkflowResult {
47
+ return {
48
+ id: decision.id,
49
+ subscriptionName: decision.subscription_name,
50
+ decision: decision.decision,
51
+ projectId: decision.project_id,
52
+ runId: decision.run_id,
53
+ runName: decision.run_name,
54
+ reason: decision.reason,
55
+ };
56
+ }
57
+
58
+ export function toTriggerEventDetail(response: TriggerEventDetailResponseDto): TriggerEventDetail {
59
+ return {
60
+ ...toTriggerEventSummary(response),
61
+ connectionName: response.connection_name,
62
+ payload: response.payload,
63
+ decisions: response.decisions.map(toMatchedWorkflowResult),
64
+ };
65
+ }
66
+
67
+ function toTriggerEventFacetItem(item: TriggerEventFacetItemDto): TriggerEventFacetItem {
68
+ return {value: item.value, count: item.count};
69
+ }
70
+
71
+ export function toTriggerEventFacets(response: TriggerEventFacetsResponseDto): TriggerEventFacets {
72
+ return {
73
+ sources: response.sources.map(toTriggerEventFacetItem),
74
+ events: response.events.map(toTriggerEventFacetItem),
75
+ };
76
+ }
@@ -18,9 +18,32 @@ function requestFrom(fetchImpl: ReturnType<typeof vi.fn>): Request {
18
18
  return fetchImpl.mock.calls[0]?.[0] as Request;
19
19
  }
20
20
 
21
+ const EVENT_ID = '22222222-2222-4222-8222-222222222222';
22
+ const WORKSPACE_ID = '11111111-1111-4111-8111-111111111111';
23
+
24
+ function triggerEventDto(overrides: Record<string, unknown> = {}) {
25
+ return {
26
+ id: EVENT_ID,
27
+ event_ref: 'github:delivery-1:push',
28
+ origin: 'integration',
29
+ workspace_id: WORKSPACE_ID,
30
+ provider: 'github',
31
+ source: 'github',
32
+ event: 'push',
33
+ delivery_id: 'delivery-1',
34
+ connection_id: '33333333-3333-4333-8333-333333333333',
35
+ outcome: 'routed',
36
+ matched_count: 1,
37
+ received_at: '2026-06-01T00:00:00.000Z',
38
+ processed_at: '2026-06-01T00:00:01.000Z',
39
+ created_at: '2026-06-01T00:00:00.000Z',
40
+ ...overrides,
41
+ };
42
+ }
43
+
21
44
  describe('triggerEventsQueryKeys', () => {
22
45
  test('list key nests under the workspace lists key', () => {
23
- const workspaceId = '11111111-1111-4111-8111-111111111111';
46
+ const workspaceId = WORKSPACE_ID;
24
47
 
25
48
  const key = triggerEventsQueryKeys.list(workspaceId, {});
26
49
 
@@ -35,7 +58,7 @@ describe('triggerEventsQueryKeys', () => {
35
58
  });
36
59
 
37
60
  test('keeps distinct page limits on distinct keys', () => {
38
- const workspaceId = '11111111-1111-4111-8111-111111111111';
61
+ const workspaceId = WORKSPACE_ID;
39
62
 
40
63
  const defaultLimit = triggerEventsQueryKeys.list(workspaceId, {});
41
64
  const compactLimit = triggerEventsQueryKeys.list(workspaceId, {}, 25);
@@ -46,7 +69,7 @@ describe('triggerEventsQueryKeys', () => {
46
69
  });
47
70
 
48
71
  test('normalizes absent filter fields to null so empty variants share a key', () => {
49
- const workspaceId = '11111111-1111-4111-8111-111111111111';
72
+ const workspaceId = WORKSPACE_ID;
50
73
 
51
74
  const empty = triggerEventsQueryKeys.list(workspaceId, {});
52
75
  const allUndefined = triggerEventsQueryKeys.list(workspaceId, {
@@ -61,7 +84,7 @@ describe('triggerEventsQueryKeys', () => {
61
84
  });
62
85
 
63
86
  test('treats an empty outcome array as no outcome filter', () => {
64
- const workspaceId = '11111111-1111-4111-8111-111111111111';
87
+ const workspaceId = WORKSPACE_ID;
65
88
 
66
89
  const key = triggerEventsQueryKeys.list(workspaceId, {outcome: []});
67
90
 
@@ -69,7 +92,7 @@ describe('triggerEventsQueryKeys', () => {
69
92
  });
70
93
 
71
94
  test('treats empty source and event arrays as no filter', () => {
72
- const workspaceId = '11111111-1111-4111-8111-111111111111';
95
+ const workspaceId = WORKSPACE_ID;
73
96
 
74
97
  const key = triggerEventsQueryKeys.list(workspaceId, {source: [], event: []});
75
98
 
@@ -77,7 +100,7 @@ describe('triggerEventsQueryKeys', () => {
77
100
  });
78
101
 
79
102
  test('sorts and de-duplicates outcome so filter order does not split the cache', () => {
80
- const workspaceId = '11111111-1111-4111-8111-111111111111';
103
+ const workspaceId = WORKSPACE_ID;
81
104
 
82
105
  const a = triggerEventsQueryKeys.list(workspaceId, {outcome: ['routed', 'failed']});
83
106
  const b = triggerEventsQueryKeys.list(workspaceId, {outcome: ['failed', 'routed', 'failed']});
@@ -87,7 +110,7 @@ describe('triggerEventsQueryKeys', () => {
87
110
  });
88
111
 
89
112
  test('sorts and de-duplicates source and event filters so order does not split the cache', () => {
90
- const workspaceId = '11111111-1111-4111-8111-111111111111';
113
+ const workspaceId = WORKSPACE_ID;
91
114
 
92
115
  const a = triggerEventsQueryKeys.list(workspaceId, {
93
116
  source: ['github', 'gitea'],
@@ -203,14 +226,23 @@ describe('listTriggerEvents', () => {
203
226
 
204
227
  test('returns the parsed list response', async () => {
205
228
  const page = {
206
- trigger_events: [],
229
+ trigger_events: [triggerEventDto()],
207
230
  next_cursor: 'next-page',
208
231
  };
209
232
  configureApiClient({fetchImpl: vi.fn(async () => jsonResponse(page))});
210
233
 
211
234
  const result = await listTriggerEvents({workspaceId});
212
235
 
213
- expect(result).toEqual(page);
236
+ expect(result).toEqual({
237
+ events: [
238
+ expect.objectContaining({
239
+ id: EVENT_ID,
240
+ eventRef: 'github:delivery-1:push',
241
+ matchedCount: 1,
242
+ }),
243
+ ],
244
+ nextCursor: 'next-page',
245
+ });
214
246
  });
215
247
  });
216
248
 
@@ -220,8 +252,13 @@ describe('getTriggerEvent', () => {
220
252
  });
221
253
 
222
254
  test('requests the event detail by id', async () => {
223
- const id = '22222222-2222-4222-8222-222222222222';
224
- const fetchImpl = vi.fn(async () => jsonResponse({id, decisions: []}));
255
+ const id = EVENT_ID;
256
+ const fetchImpl = vi.fn(async () =>
257
+ jsonResponse({
258
+ ...triggerEventDto({id, connection_name: null, payload: null}),
259
+ decisions: [],
260
+ }),
261
+ );
225
262
  configureApiClient({fetchImpl});
226
263
 
227
264
  await getTriggerEvent({id});
@@ -238,7 +275,7 @@ describe('getTriggerEventFacets', () => {
238
275
  });
239
276
 
240
277
  test('requests the workspace facets', async () => {
241
- const workspaceId = '11111111-1111-4111-8111-111111111111';
278
+ const workspaceId = WORKSPACE_ID;
242
279
  const fetchImpl = vi.fn(async () => jsonResponse({sources: [], events: []}));
243
280
  configureApiClient({fetchImpl});
244
281
 
@@ -1,40 +1,60 @@
1
- import type {
2
- TriggerEventDetailResponseDto,
3
- TriggerEventFacetsResponseDto,
4
- TriggerEventListResponseDto,
5
- TriggerEventOutcomeDto,
1
+ import {
2
+ triggerEventDetailResponseSchema,
3
+ triggerEventFacetsResponseSchema,
4
+ triggerEventListResponseSchema,
6
5
  } from '@shipfox/api-triggers-dto';
7
- import {apiRequest} from '@shipfox/client-api';
8
- import {keepPreviousData, useInfiniteQuery, useQuery} from '@tanstack/react-query';
9
-
10
- export interface TriggerEventFilters {
11
- source?: string[] | undefined;
12
- event?: string[] | undefined;
13
- outcome?: TriggerEventOutcomeDto[] | undefined;
14
- from?: string | undefined;
15
- to?: string | undefined;
16
- }
6
+ import {checkedApiRequest} from '@shipfox/client-api';
7
+ import {
8
+ type FetchQueryOptions,
9
+ type InfiniteData,
10
+ infiniteQueryOptions,
11
+ keepPreviousData,
12
+ queryOptions,
13
+ type UseInfiniteQueryOptions,
14
+ useInfiniteQuery,
15
+ useQuery,
16
+ } from '@tanstack/react-query';
17
+ import {
18
+ normalizeTriggerEventFilters,
19
+ normalizeTriggerEventFilterValues,
20
+ type TriggerEventFilters,
21
+ } from '#core/trigger-event.js';
22
+ import {
23
+ toTriggerEventDetail,
24
+ toTriggerEventFacets,
25
+ toTriggerEventListPage,
26
+ } from './trigger-event-mapper.js';
17
27
 
18
- function normalizeStringFilter(values: readonly string[] | undefined) {
19
- return values && values.length > 0 ? [...new Set(values)].sort() : null;
20
- }
28
+ export type {TriggerEventFilters} from '#core/trigger-event.js';
21
29
 
22
- function normalizeTriggerEventFiltersForQueryKey(filters: TriggerEventFilters) {
23
- return {
24
- source: normalizeStringFilter(filters.source),
25
- event: normalizeStringFilter(filters.event),
26
- outcome: normalizeStringFilter(filters.outcome),
27
- from: filters.from ?? null,
28
- to: filters.to ?? null,
29
- };
30
- }
30
+ type TriggerEventsInfiniteQueryOptions = UseInfiniteQueryOptions<
31
+ import('#core/trigger-event.js').TriggerEventListPage,
32
+ Error,
33
+ InfiniteData<import('#core/trigger-event.js').TriggerEventListPage, string | undefined>,
34
+ ReturnType<typeof triggerEventsQueryKeys.list>,
35
+ string | undefined
36
+ >;
37
+
38
+ type TriggerEventQueryOptions = FetchQueryOptions<
39
+ import('#core/trigger-event.js').TriggerEventDetail,
40
+ Error,
41
+ import('#core/trigger-event.js').TriggerEventDetail,
42
+ ReturnType<typeof triggerEventsQueryKeys.detail>
43
+ >;
44
+
45
+ type TriggerEventFacetsQueryOptions = FetchQueryOptions<
46
+ import('#core/trigger-event.js').TriggerEventFacets,
47
+ Error,
48
+ import('#core/trigger-event.js').TriggerEventFacets,
49
+ ReturnType<typeof triggerEventsQueryKeys.facets>
50
+ >;
31
51
 
32
52
  function setListParam(
33
53
  params: URLSearchParams,
34
54
  name: string,
35
55
  values: readonly string[] | undefined,
36
56
  ) {
37
- const normalized = normalizeStringFilter(values);
57
+ const normalized = normalizeTriggerEventFilterValues(values);
38
58
  if (normalized) params.set(name, normalized.join(','));
39
59
  }
40
60
 
@@ -45,7 +65,7 @@ export const triggerEventsQueryKeys = {
45
65
  [
46
66
  ...triggerEventsQueryKeys.lists(workspaceId),
47
67
  limit,
48
- normalizeTriggerEventFiltersForQueryKey(filters),
68
+ normalizeTriggerEventFilters(filters),
49
69
  ] as const,
50
70
  detail: (id: string) => [...triggerEventsQueryKeys.all, 'detail', id] as const,
51
71
  facets: (workspaceId: string) => [...triggerEventsQueryKeys.all, 'facets', workspaceId] as const,
@@ -72,16 +92,36 @@ export async function listTriggerEvents({
72
92
  if (filters.to) params.set('to', filters.to);
73
93
  setListParam(params, 'outcome', filters.outcome);
74
94
 
75
- return await apiRequest<TriggerEventListResponseDto>(`/trigger-events?${params.toString()}`, {
76
- signal,
77
- });
95
+ const response = await checkedApiRequest(
96
+ triggerEventListResponseSchema,
97
+ `/trigger-events?${params.toString()}`,
98
+ {signal},
99
+ );
100
+ return toTriggerEventListPage(response);
78
101
  }
79
102
 
80
103
  export async function getTriggerEvent({id, signal}: {id: string; signal?: AbortSignal}) {
81
- return await apiRequest<TriggerEventDetailResponseDto>(
104
+ const response = await checkedApiRequest(
105
+ triggerEventDetailResponseSchema,
82
106
  `/trigger-events/${encodeURIComponent(id)}`,
83
107
  {signal},
84
108
  );
109
+ return toTriggerEventDetail(response);
110
+ }
111
+
112
+ export function triggerEventsInfiniteQueryOptions(
113
+ workspaceId: string,
114
+ filters: TriggerEventFilters = {},
115
+ limit = 50,
116
+ ): TriggerEventsInfiniteQueryOptions {
117
+ return infiniteQueryOptions({
118
+ queryKey: triggerEventsQueryKeys.list(workspaceId, filters, limit),
119
+ initialPageParam: undefined as string | undefined,
120
+ queryFn: ({pageParam, signal}) =>
121
+ listTriggerEvents({workspaceId, filters, limit, cursor: pageParam, signal}),
122
+ getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
123
+ placeholderData: keepPreviousData,
124
+ });
85
125
  }
86
126
 
87
127
  export function useTriggerEventsInfiniteQuery(
@@ -90,29 +130,22 @@ export function useTriggerEventsInfiniteQuery(
90
130
  limit = 50,
91
131
  ) {
92
132
  return useInfiniteQuery({
93
- queryKey: workspaceId
94
- ? triggerEventsQueryKeys.list(workspaceId, filters, limit)
95
- : [...triggerEventsQueryKeys.all, 'list'],
133
+ ...triggerEventsInfiniteQueryOptions(workspaceId ?? '', filters, limit),
96
134
  enabled: Boolean(workspaceId),
97
- initialPageParam: undefined as string | undefined,
98
- queryFn: ({pageParam, signal}) =>
99
- listTriggerEvents({
100
- workspaceId: workspaceId ?? '',
101
- filters,
102
- limit,
103
- cursor: pageParam,
104
- signal,
105
- }),
106
- getNextPageParam: (lastPage) => lastPage.next_cursor ?? undefined,
107
- placeholderData: keepPreviousData,
135
+ });
136
+ }
137
+
138
+ export function triggerEventQueryOptions(id: string): TriggerEventQueryOptions {
139
+ return queryOptions({
140
+ queryKey: triggerEventsQueryKeys.detail(id),
141
+ queryFn: ({signal}) => getTriggerEvent({id, signal}),
108
142
  });
109
143
  }
110
144
 
111
145
  export function useTriggerEventQuery(id: string | undefined) {
112
146
  return useQuery({
113
- queryKey: id ? triggerEventsQueryKeys.detail(id) : [...triggerEventsQueryKeys.all, 'detail'],
147
+ ...triggerEventQueryOptions(id ?? ''),
114
148
  enabled: Boolean(id),
115
- queryFn: ({signal}) => getTriggerEvent({id: id ?? '', signal}),
116
149
  });
117
150
  }
118
151
 
@@ -124,20 +157,27 @@ export async function getTriggerEventFacets({
124
157
  signal?: AbortSignal;
125
158
  }) {
126
159
  const params = new URLSearchParams({workspace_id: workspaceId});
127
- return await apiRequest<TriggerEventFacetsResponseDto>(
160
+ const response = await checkedApiRequest(
161
+ triggerEventFacetsResponseSchema,
128
162
  `/trigger-events/facets?${params.toString()}`,
129
163
  {signal},
130
164
  );
165
+ return toTriggerEventFacets(response);
166
+ }
167
+
168
+ export function triggerEventFacetsQueryOptions(
169
+ workspaceId: string,
170
+ ): TriggerEventFacetsQueryOptions {
171
+ return queryOptions({
172
+ queryKey: triggerEventsQueryKeys.facets(workspaceId),
173
+ queryFn: ({signal}) => getTriggerEventFacets({workspaceId, signal}),
174
+ staleTime: 5 * 60 * 1000,
175
+ });
131
176
  }
132
177
 
133
178
  export function useTriggerEventFacetsQuery(workspaceId: string | undefined) {
134
179
  return useQuery({
135
- queryKey: workspaceId
136
- ? triggerEventsQueryKeys.facets(workspaceId)
137
- : [...triggerEventsQueryKeys.all, 'facets'],
180
+ ...triggerEventFacetsQueryOptions(workspaceId ?? ''),
138
181
  enabled: Boolean(workspaceId),
139
- queryFn: ({signal}) => getTriggerEventFacets({workspaceId: workspaceId ?? '', signal}),
140
- // Distinct values change slowly; avoid refetching on every page mount.
141
- staleTime: 5 * 60 * 1000,
142
182
  });
143
183
  }
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from '#core/trigger-event.js';
1
2
  export * from './components/trigger-source-icon.js';
2
3
  export * from './hooks/api/trigger-events.js';
3
4
  export * from './pages/events-page.js';