@incodetech/core 2.0.0-alpha.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 (84) hide show
  1. package/package.json +51 -0
  2. package/src/camera/cameraActor.ts +21 -0
  3. package/src/camera/cameraService.test.ts +437 -0
  4. package/src/camera/cameraService.ts +165 -0
  5. package/src/camera/cameraServices.test.ts +66 -0
  6. package/src/camera/cameraServices.ts +26 -0
  7. package/src/camera/cameraStateMachine.test.ts +602 -0
  8. package/src/camera/cameraStateMachine.ts +264 -0
  9. package/src/camera/index.ts +5 -0
  10. package/src/camera/types.ts +17 -0
  11. package/src/device/getBrowser.ts +31 -0
  12. package/src/device/getDeviceClass.ts +29 -0
  13. package/src/device/index.ts +2 -0
  14. package/src/email/__mocks__/emailMocks.ts +59 -0
  15. package/src/email/emailActor.ts +15 -0
  16. package/src/email/emailManager.test.ts +573 -0
  17. package/src/email/emailManager.ts +427 -0
  18. package/src/email/emailServices.ts +66 -0
  19. package/src/email/emailStateMachine.test.ts +741 -0
  20. package/src/email/emailStateMachine.ts +367 -0
  21. package/src/email/index.ts +39 -0
  22. package/src/email/types.ts +60 -0
  23. package/src/events/addEvent.ts +20 -0
  24. package/src/events/types.ts +7 -0
  25. package/src/flow/__mocks__/flowMocks.ts +84 -0
  26. package/src/flow/flowActor.ts +13 -0
  27. package/src/flow/flowAnalyzer.test.ts +266 -0
  28. package/src/flow/flowAnalyzer.ts +37 -0
  29. package/src/flow/flowCompletionService.ts +21 -0
  30. package/src/flow/flowManager.test.ts +560 -0
  31. package/src/flow/flowManager.ts +235 -0
  32. package/src/flow/flowServices.test.ts +109 -0
  33. package/src/flow/flowServices.ts +13 -0
  34. package/src/flow/flowStateMachine.test.ts +334 -0
  35. package/src/flow/flowStateMachine.ts +182 -0
  36. package/src/flow/index.ts +21 -0
  37. package/src/flow/moduleLoader.test.ts +136 -0
  38. package/src/flow/moduleLoader.ts +73 -0
  39. package/src/flow/orchestratedFlowManager.test.ts +240 -0
  40. package/src/flow/orchestratedFlowManager.ts +231 -0
  41. package/src/flow/orchestratedFlowStateMachine.test.ts +199 -0
  42. package/src/flow/orchestratedFlowStateMachine.ts +325 -0
  43. package/src/flow/types.ts +434 -0
  44. package/src/http/__mocks__/api.ts +88 -0
  45. package/src/http/api.test.ts +231 -0
  46. package/src/http/api.ts +90 -0
  47. package/src/http/endpoints.ts +17 -0
  48. package/src/index.ts +33 -0
  49. package/src/permissions/index.ts +2 -0
  50. package/src/permissions/permissionServices.ts +31 -0
  51. package/src/permissions/types.ts +3 -0
  52. package/src/phone/__mocks__/phoneMocks.ts +71 -0
  53. package/src/phone/index.ts +39 -0
  54. package/src/phone/phoneActor.ts +15 -0
  55. package/src/phone/phoneManager.test.ts +393 -0
  56. package/src/phone/phoneManager.ts +458 -0
  57. package/src/phone/phoneServices.ts +98 -0
  58. package/src/phone/phoneStateMachine.test.ts +918 -0
  59. package/src/phone/phoneStateMachine.ts +422 -0
  60. package/src/phone/types.ts +83 -0
  61. package/src/recordings/recordingsRepository.test.ts +87 -0
  62. package/src/recordings/recordingsRepository.ts +48 -0
  63. package/src/recordings/streamingEvents.ts +10 -0
  64. package/src/selfie/__mocks__/selfieMocks.ts +26 -0
  65. package/src/selfie/index.ts +14 -0
  66. package/src/selfie/selfieActor.ts +17 -0
  67. package/src/selfie/selfieErrorUtils.test.ts +116 -0
  68. package/src/selfie/selfieErrorUtils.ts +66 -0
  69. package/src/selfie/selfieManager.test.ts +297 -0
  70. package/src/selfie/selfieManager.ts +301 -0
  71. package/src/selfie/selfieServices.ts +362 -0
  72. package/src/selfie/selfieStateMachine.test.ts +283 -0
  73. package/src/selfie/selfieStateMachine.ts +804 -0
  74. package/src/selfie/selfieUploadService.test.ts +90 -0
  75. package/src/selfie/selfieUploadService.ts +81 -0
  76. package/src/selfie/types.ts +103 -0
  77. package/src/session/index.ts +5 -0
  78. package/src/session/sessionService.ts +78 -0
  79. package/src/setup.test.ts +61 -0
  80. package/src/setup.ts +171 -0
  81. package/tsconfig.json +13 -0
  82. package/tsdown.config.ts +22 -0
  83. package/vitest.config.ts +37 -0
  84. package/vitest.setup.ts +135 -0
@@ -0,0 +1,325 @@
1
+ import {
2
+ type AnyStateMachine,
3
+ assign,
4
+ createActor,
5
+ fromCallback,
6
+ fromPromise,
7
+ setup,
8
+ } from '@incodetech/infra';
9
+ import {
10
+ type GetFinishStatusFn,
11
+ getFinishStatus,
12
+ } from './flowCompletionService';
13
+ import type { Flow } from './types';
14
+
15
+ export type GetFlowFn = (signal: AbortSignal) => Promise<Flow>;
16
+
17
+ export type ModuleRegistry = {
18
+ PHONE?: AnyStateMachine;
19
+ EMAIL?: AnyStateMachine;
20
+ SELFIE?: AnyStateMachine;
21
+ [key: string]: AnyStateMachine | undefined;
22
+ };
23
+
24
+ export type OrchestratedFlowInput = {
25
+ getFlow: GetFlowFn;
26
+ modules: ModuleRegistry;
27
+ getFinishStatus?: GetFinishStatusFn;
28
+ };
29
+
30
+ export type OrchestratedFlowContext = {
31
+ flow: Flow | undefined;
32
+ error: string | undefined;
33
+ steps: string[];
34
+ currentStepIndex: number;
35
+ currentStep: string | undefined;
36
+ config: unknown;
37
+ getFlow: GetFlowFn;
38
+ registeredModules: ModuleRegistry;
39
+ getFinishStatus: GetFinishStatusFn;
40
+ finishStatus:
41
+ | {
42
+ redirectionUrl: string;
43
+ action: 'approved' | 'rejected' | 'none';
44
+ scoreStatus:
45
+ | 'OK'
46
+ | 'WARN'
47
+ | 'MANUAL_OK'
48
+ | 'FAIL'
49
+ | 'UNKNOWN'
50
+ | 'MANUAL_FAIL';
51
+ }
52
+ | undefined;
53
+ };
54
+
55
+ export type OrchestratedFlowEvent =
56
+ | { type: 'LOAD' }
57
+ | { type: 'CANCEL' }
58
+ | { type: 'RESET' }
59
+ | { type: 'MODULE_COMPLETE' }
60
+ | { type: '*'; [key: string]: unknown }
61
+ | { type: string; [key: string]: unknown };
62
+
63
+ export const orchestratedFlowMachine = setup({
64
+ types: {
65
+ context: {} as OrchestratedFlowContext,
66
+ events: {} as OrchestratedFlowEvent,
67
+ input: {} as OrchestratedFlowInput,
68
+ },
69
+ actors: {
70
+ fetchFlow: fromPromise<Flow, { getFlow: GetFlowFn }>(
71
+ async ({ input, signal }) => {
72
+ return input.getFlow(signal);
73
+ },
74
+ ),
75
+ notifyBackend: fromPromise<
76
+ OrchestratedFlowContext['finishStatus'],
77
+ { getFinishStatus: GetFinishStatusFn; flowId: string | undefined }
78
+ >(async ({ input, signal }) => {
79
+ const result = await input.getFinishStatus(input.flowId, signal);
80
+ return result;
81
+ }),
82
+ runChildModule: fromCallback<
83
+ { type: 'MODULE_COMPLETE' } | { type: 'MODULE_ERROR'; error: unknown },
84
+ { machine: AnyStateMachine; config: unknown }
85
+ >(({ sendBack, input }) => {
86
+ const { machine, config } = input;
87
+
88
+ // Create and start the child actor
89
+ const actor = createActor(machine, { input: { config } });
90
+
91
+ // Subscribe to state changes
92
+ const subscription = actor.subscribe({
93
+ complete: () => {
94
+ sendBack({ type: 'MODULE_COMPLETE' });
95
+ },
96
+ error: (err) => {
97
+ sendBack({ type: 'MODULE_ERROR', error: err });
98
+ },
99
+ });
100
+
101
+ actor.start();
102
+
103
+ // Return cleanup function
104
+ return () => {
105
+ subscription.unsubscribe();
106
+ actor.stop();
107
+ };
108
+ }),
109
+ },
110
+ actions: {
111
+ resetContext: assign(({ context }) => ({
112
+ flow: undefined,
113
+ error: undefined,
114
+ steps: [],
115
+ currentStepIndex: -1,
116
+ currentStep: undefined,
117
+ config: undefined,
118
+ getFlow: context.getFlow,
119
+ registeredModules: context.registeredModules,
120
+ getFinishStatus: context.getFinishStatus,
121
+ finishStatus: undefined,
122
+ })),
123
+ setFlowData: assign(({ event }) => {
124
+ const flow = (event as unknown as { output: Flow }).output;
125
+ return {
126
+ flow,
127
+ steps: (flow.flowModules ?? []).map((m) => m.key),
128
+ currentStepIndex: (flow.flowModules ?? []).length > 0 ? 0 : -1,
129
+ currentStep: flow.flowModules?.[0]?.key,
130
+ config: flow.flowModules?.[0]?.configuration,
131
+ };
132
+ }),
133
+ setError: assign(({ event }) => ({
134
+ error: String((event as unknown as { error: unknown }).error),
135
+ })),
136
+ incrementStep: assign(({ context }) => {
137
+ const nextIndex = context.currentStepIndex + 1;
138
+ const module = context.flow?.flowModules?.[nextIndex];
139
+ return {
140
+ currentStepIndex: nextIndex,
141
+ currentStep: module?.key,
142
+ config: module?.configuration,
143
+ };
144
+ }),
145
+ setFinishStatus: assign(({ event }) => ({
146
+ finishStatus: (
147
+ event as unknown as { output: OrchestratedFlowContext['finishStatus'] }
148
+ ).output,
149
+ })),
150
+ },
151
+ guards: {
152
+ isLastStep: ({ context }) =>
153
+ context.currentStepIndex >= 0 &&
154
+ context.currentStepIndex === context.steps.length - 1,
155
+ canGoNext: ({ context }) =>
156
+ context.currentStepIndex >= 0 &&
157
+ context.currentStepIndex < context.steps.length - 1,
158
+ hasModule: ({ context, event }) => {
159
+ // Guard runs before action, so check event output instead of context
160
+ if (!('output' in event)) return false;
161
+ const flow = (event as unknown as { output: Flow }).output;
162
+ const firstModuleKey = flow.flowModules?.[0]?.key;
163
+ if (!firstModuleKey) return false;
164
+ return context.registeredModules[firstModuleKey] != null;
165
+ },
166
+ },
167
+ }).createMachine({
168
+ id: 'orchestratedFlow',
169
+ initial: 'idle',
170
+ context: ({ input }) => ({
171
+ flow: undefined,
172
+ error: undefined,
173
+ steps: [],
174
+ currentStepIndex: -1,
175
+ currentStep: undefined,
176
+ config: undefined,
177
+ getFlow: input.getFlow,
178
+ registeredModules: input.modules,
179
+ getFinishStatus: input.getFinishStatus ?? getFinishStatus,
180
+ finishStatus: undefined,
181
+ }),
182
+ states: {
183
+ idle: {
184
+ on: {
185
+ LOAD: {
186
+ target: 'loading',
187
+ actions: 'resetContext',
188
+ },
189
+ },
190
+ },
191
+
192
+ loading: {
193
+ invoke: {
194
+ id: 'fetchFlow',
195
+ src: 'fetchFlow',
196
+ input: ({ context }) => ({
197
+ getFlow: context.getFlow,
198
+ }),
199
+ onDone: [
200
+ {
201
+ target: 'runningModule',
202
+ guard: 'hasModule',
203
+ actions: 'setFlowData',
204
+ },
205
+ {
206
+ target: 'error',
207
+ actions: assign({
208
+ error: () => 'No registered module found for flow',
209
+ }),
210
+ },
211
+ ],
212
+ onError: {
213
+ target: 'error',
214
+ actions: 'setError',
215
+ },
216
+ },
217
+ on: {
218
+ CANCEL: {
219
+ target: 'idle',
220
+ actions: 'resetContext',
221
+ },
222
+ },
223
+ },
224
+
225
+ runningModule: {
226
+ invoke: {
227
+ id: 'currentModule',
228
+ src: 'runChildModule',
229
+ input: ({ context }: { context: OrchestratedFlowContext }) => {
230
+ if (!context.currentStep) {
231
+ throw new Error('No current step');
232
+ }
233
+ const machine = context.registeredModules[context.currentStep];
234
+ if (!machine) {
235
+ throw new Error(`Module ${context.currentStep} not registered`);
236
+ }
237
+ return { machine, config: context.config };
238
+ },
239
+ onDone: [
240
+ {
241
+ target: 'completing',
242
+ guard: 'isLastStep',
243
+ },
244
+ {
245
+ target: 'runningModule',
246
+ actions: 'incrementStep',
247
+ reenter: true,
248
+ },
249
+ ],
250
+ onError: {
251
+ target: 'error',
252
+ actions: 'setError',
253
+ },
254
+ },
255
+ on: {
256
+ MODULE_COMPLETE: [
257
+ {
258
+ target: 'completing',
259
+ guard: 'isLastStep',
260
+ },
261
+ {
262
+ target: 'runningModule',
263
+ actions: 'incrementStep',
264
+ reenter: true,
265
+ },
266
+ ],
267
+ MODULE_ERROR: {
268
+ target: 'error',
269
+ actions: assign(({ event }) => ({
270
+ error: String((event as unknown as { error: unknown }).error),
271
+ })),
272
+ },
273
+ RESET: {
274
+ target: 'idle',
275
+ actions: 'resetContext',
276
+ },
277
+ },
278
+ },
279
+
280
+ completing: {
281
+ invoke: {
282
+ id: 'notifyBackend',
283
+ src: 'notifyBackend',
284
+ input: ({ context }) => ({
285
+ getFinishStatus: context.getFinishStatus,
286
+ flowId: context.flow?.flowId,
287
+ }),
288
+ onDone: {
289
+ target: 'finished',
290
+ actions: 'setFinishStatus',
291
+ },
292
+ onError: {
293
+ target: 'error',
294
+ actions: 'setError',
295
+ },
296
+ },
297
+ on: {
298
+ RESET: {
299
+ target: 'idle',
300
+ actions: 'resetContext',
301
+ },
302
+ },
303
+ },
304
+
305
+ finished: {
306
+ on: {
307
+ RESET: {
308
+ target: 'idle',
309
+ actions: 'resetContext',
310
+ },
311
+ },
312
+ },
313
+
314
+ error: {
315
+ on: {
316
+ RESET: {
317
+ target: 'idle',
318
+ actions: 'resetContext',
319
+ },
320
+ },
321
+ },
322
+ },
323
+ });
324
+
325
+ export type OrchestratedFlowMachine = typeof orchestratedFlowMachine;