@open-mercato/shared 0.6.2-develop.3400.2.d948268f50 → 0.6.2-develop.3406.1.2b403f40da

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.
@@ -1,2 +1,2 @@
1
- [build:shared] found 204 entry points
1
+ [build:shared] found 209 entry points
2
2
  [build:shared] built successfully
@@ -1,4 +1,4 @@
1
- const APP_VERSION = "0.6.2-develop.3400.2.d948268f50";
1
+ const APP_VERSION = "0.6.2-develop.3406.1.2b403f40da";
2
2
  const appVersion = APP_VERSION;
3
3
  export {
4
4
  APP_VERSION,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/version.ts"],
4
- "sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.6.2-develop.3400.2.d948268f50'\nexport const appVersion = APP_VERSION\n"],
4
+ "sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.6.2-develop.3406.1.2b403f40da'\nexport const appVersion = APP_VERSION\n"],
5
5
  "mappings": "AACO,MAAM,cAAc;AACpB,MAAM,aAAa;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,47 @@
1
+ function defineWorkflow(config) {
2
+ const steps = config.steps.map((step) => ({
3
+ stepId: step.stepId,
4
+ stepName: step.stepName,
5
+ stepType: step.stepType,
6
+ ...step.description !== void 0 && { description: step.description },
7
+ ...step.config !== void 0 && { config: step.config },
8
+ ...step.userTaskConfig !== void 0 && { userTaskConfig: step.userTaskConfig },
9
+ ...step.subWorkflowConfig !== void 0 && { subWorkflowConfig: step.subWorkflowConfig },
10
+ ...step.signalConfig !== void 0 && { signalConfig: step.signalConfig },
11
+ ...step.activities !== void 0 && { activities: step.activities },
12
+ ...step.timeout !== void 0 && { timeout: step.timeout },
13
+ ...step.retryPolicy !== void 0 && { retryPolicy: step.retryPolicy },
14
+ ...step.preConditions !== void 0 && { preConditions: step.preConditions }
15
+ }));
16
+ const transitions = config.transitions.map((t) => ({
17
+ transitionId: t.transitionId,
18
+ fromStepId: t.fromStepId,
19
+ toStepId: t.toStepId,
20
+ trigger: t.trigger,
21
+ ...t.transitionName !== void 0 && { transitionName: t.transitionName },
22
+ ...t.preConditions !== void 0 && { preConditions: t.preConditions },
23
+ ...t.postConditions !== void 0 && { postConditions: t.postConditions },
24
+ ...t.activities !== void 0 && { activities: t.activities },
25
+ ...t.continueOnActivityFailure !== void 0 && { continueOnActivityFailure: t.continueOnActivityFailure },
26
+ ...t.priority !== void 0 && { priority: t.priority }
27
+ }));
28
+ return {
29
+ workflowId: config.workflowId,
30
+ workflowName: config.workflowName,
31
+ description: config.description ?? null,
32
+ version: config.version ?? 1,
33
+ enabled: config.enabled ?? true,
34
+ metadata: config.metadata ?? null,
35
+ definition: {
36
+ steps,
37
+ transitions,
38
+ ...config.triggers !== void 0 && { triggers: config.triggers }
39
+ },
40
+ moduleId: ""
41
+ // Set by generator/factory at registration time
42
+ };
43
+ }
44
+ export {
45
+ defineWorkflow
46
+ };
47
+ //# sourceMappingURL=builder.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/workflows/builder.ts"],
4
+ "sourcesContent": ["/**\n * Code-Based Workflow Definitions \u2014 Builder API\n *\n * Provides compile-time type safety for step IDs in transitions.\n * The `const` modifier on TSteps tells TypeScript to infer literal types\n * for stepId values. Transitions constrain fromStepId/toStepId to this union.\n *\n * @example\n * ```typescript\n * const workflow = defineWorkflow({\n * workflowId: 'sales.order-approval',\n * workflowName: 'Order Approval',\n * steps: [\n * { stepId: 'start', stepName: 'Start', stepType: 'START' },\n * { stepId: 'review', stepName: 'Review', stepType: 'USER_TASK' },\n * { stepId: 'end', stepName: 'End', stepType: 'END' },\n * ] as const,\n * transitions: [\n * { transitionId: 'to-review', fromStepId: 'start', toStepId: 'review', trigger: 'auto' },\n * // Compile error: '\"typo\"' is not assignable to '\"start\" | \"review\" | \"end\"'\n * // { transitionId: 'bad', fromStepId: 'typo', toStepId: 'end', trigger: 'auto' },\n * ],\n * })\n * ```\n */\n\nimport type {\n CodeWorkflowDefinition,\n CodeStepDefinition,\n CodeTransitionDefinition,\n CodeTriggerDefinition,\n CodeActivityDefinition,\n WorkflowStepType,\n TransitionTrigger,\n} from './types'\n\n// ============================================================================\n// Builder Config Type (with const generic for type inference)\n// ============================================================================\n\ninterface WorkflowBuilderConfig<TSteps extends readonly StepInput<string>[]> {\n workflowId: string\n workflowName: string\n description?: string\n version?: number\n enabled?: boolean\n metadata?: { tags?: string[]; category?: string; icon?: string }\n steps: TSteps\n transitions: TransitionInput<TSteps[number]['stepId']>[]\n triggers?: CodeTriggerDefinition[]\n}\n\ninterface StepInput<TStepId extends string> {\n stepId: TStepId\n stepName: string\n stepType: WorkflowStepType\n description?: string\n config?: Record<string, unknown>\n userTaskConfig?: CodeStepDefinition['userTaskConfig']\n subWorkflowConfig?: CodeStepDefinition['subWorkflowConfig']\n signalConfig?: CodeStepDefinition['signalConfig']\n activities?: CodeActivityDefinition[]\n timeout?: string\n retryPolicy?: { maxAttempts: number; backoffMs: number }\n preConditions?: CodeStepDefinition['preConditions']\n}\n\ninterface TransitionInput<TStepId extends string> {\n transitionId: string\n fromStepId: TStepId\n toStepId: TStepId\n transitionName?: string\n trigger: TransitionTrigger\n preConditions?: CodeTransitionDefinition['preConditions']\n postConditions?: CodeTransitionDefinition['postConditions']\n activities?: CodeActivityDefinition[]\n continueOnActivityFailure?: boolean\n priority?: number\n}\n\n// ============================================================================\n// defineWorkflow \u2014 Main Builder Function\n// ============================================================================\n\n/**\n * Define a code-based workflow with compile-time step ID safety.\n *\n * Use `as const` on the steps array for full type inference:\n * ```typescript\n * defineWorkflow({\n * steps: [...] as const,\n * transitions: [...], // fromStepId/toStepId auto-constrained\n * })\n * ```\n *\n * The returned `CodeWorkflowDefinition` has `moduleId` set to empty string.\n * The generator sets the correct `moduleId` at registration time.\n */\nexport function defineWorkflow<const TSteps extends readonly StepInput<string>[]>(\n config: WorkflowBuilderConfig<TSteps>,\n): CodeWorkflowDefinition {\n const steps: CodeStepDefinition[] = config.steps.map((step) => ({\n stepId: step.stepId,\n stepName: step.stepName,\n stepType: step.stepType,\n ...(step.description !== undefined && { description: step.description }),\n ...(step.config !== undefined && { config: step.config }),\n ...(step.userTaskConfig !== undefined && { userTaskConfig: step.userTaskConfig }),\n ...(step.subWorkflowConfig !== undefined && { subWorkflowConfig: step.subWorkflowConfig }),\n ...(step.signalConfig !== undefined && { signalConfig: step.signalConfig }),\n ...(step.activities !== undefined && { activities: step.activities }),\n ...(step.timeout !== undefined && { timeout: step.timeout }),\n ...(step.retryPolicy !== undefined && { retryPolicy: step.retryPolicy }),\n ...(step.preConditions !== undefined && { preConditions: step.preConditions }),\n }))\n\n const transitions: CodeTransitionDefinition[] = config.transitions.map((t) => ({\n transitionId: t.transitionId,\n fromStepId: t.fromStepId,\n toStepId: t.toStepId,\n trigger: t.trigger,\n ...(t.transitionName !== undefined && { transitionName: t.transitionName }),\n ...(t.preConditions !== undefined && { preConditions: t.preConditions }),\n ...(t.postConditions !== undefined && { postConditions: t.postConditions }),\n ...(t.activities !== undefined && { activities: t.activities }),\n ...(t.continueOnActivityFailure !== undefined && { continueOnActivityFailure: t.continueOnActivityFailure }),\n ...(t.priority !== undefined && { priority: t.priority }),\n }))\n\n return {\n workflowId: config.workflowId,\n workflowName: config.workflowName,\n description: config.description ?? null,\n version: config.version ?? 1,\n enabled: config.enabled ?? true,\n metadata: config.metadata ?? null,\n definition: {\n steps,\n transitions,\n ...(config.triggers !== undefined && { triggers: config.triggers }),\n },\n moduleId: '', // Set by generator/factory at registration time\n }\n}\n"],
5
+ "mappings": "AAkGO,SAAS,eACd,QACwB;AACxB,QAAM,QAA8B,OAAO,MAAM,IAAI,CAAC,UAAU;AAAA,IAC9D,QAAQ,KAAK;AAAA,IACb,UAAU,KAAK;AAAA,IACf,UAAU,KAAK;AAAA,IACf,GAAI,KAAK,gBAAgB,UAAa,EAAE,aAAa,KAAK,YAAY;AAAA,IACtE,GAAI,KAAK,WAAW,UAAa,EAAE,QAAQ,KAAK,OAAO;AAAA,IACvD,GAAI,KAAK,mBAAmB,UAAa,EAAE,gBAAgB,KAAK,eAAe;AAAA,IAC/E,GAAI,KAAK,sBAAsB,UAAa,EAAE,mBAAmB,KAAK,kBAAkB;AAAA,IACxF,GAAI,KAAK,iBAAiB,UAAa,EAAE,cAAc,KAAK,aAAa;AAAA,IACzE,GAAI,KAAK,eAAe,UAAa,EAAE,YAAY,KAAK,WAAW;AAAA,IACnE,GAAI,KAAK,YAAY,UAAa,EAAE,SAAS,KAAK,QAAQ;AAAA,IAC1D,GAAI,KAAK,gBAAgB,UAAa,EAAE,aAAa,KAAK,YAAY;AAAA,IACtE,GAAI,KAAK,kBAAkB,UAAa,EAAE,eAAe,KAAK,cAAc;AAAA,EAC9E,EAAE;AAEF,QAAM,cAA0C,OAAO,YAAY,IAAI,CAAC,OAAO;AAAA,IAC7E,cAAc,EAAE;AAAA,IAChB,YAAY,EAAE;AAAA,IACd,UAAU,EAAE;AAAA,IACZ,SAAS,EAAE;AAAA,IACX,GAAI,EAAE,mBAAmB,UAAa,EAAE,gBAAgB,EAAE,eAAe;AAAA,IACzE,GAAI,EAAE,kBAAkB,UAAa,EAAE,eAAe,EAAE,cAAc;AAAA,IACtE,GAAI,EAAE,mBAAmB,UAAa,EAAE,gBAAgB,EAAE,eAAe;AAAA,IACzE,GAAI,EAAE,eAAe,UAAa,EAAE,YAAY,EAAE,WAAW;AAAA,IAC7D,GAAI,EAAE,8BAA8B,UAAa,EAAE,2BAA2B,EAAE,0BAA0B;AAAA,IAC1G,GAAI,EAAE,aAAa,UAAa,EAAE,UAAU,EAAE,SAAS;AAAA,EACzD,EAAE;AAEF,SAAO;AAAA,IACL,YAAY,OAAO;AAAA,IACnB,cAAc,OAAO;AAAA,IACrB,aAAa,OAAO,eAAe;AAAA,IACnC,SAAS,OAAO,WAAW;AAAA,IAC3B,SAAS,OAAO,WAAW;AAAA,IAC3B,UAAU,OAAO,YAAY;AAAA,IAC7B,YAAY;AAAA,MACV;AAAA,MACA;AAAA,MACA,GAAI,OAAO,aAAa,UAAa,EAAE,UAAU,OAAO,SAAS;AAAA,IACnE;AAAA,IACA,UAAU;AAAA;AAAA,EACZ;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,14 @@
1
+ function createWorkflowsModuleConfig(options) {
2
+ const workflows = options.workflows.map((wf) => ({
3
+ ...wf,
4
+ moduleId: options.moduleId
5
+ }));
6
+ return {
7
+ moduleId: options.moduleId,
8
+ workflows
9
+ };
10
+ }
11
+ export {
12
+ createWorkflowsModuleConfig
13
+ };
14
+ //# sourceMappingURL=factory.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/workflows/factory.ts"],
4
+ "sourcesContent": ["/**\n * Code-Based Workflow Definitions \u2014 Module Config Factory\n *\n * Analogous to createModuleEvents() for events.\n * Creates a WorkflowsModuleConfig that the generator aggregates.\n */\n\nimport type { CodeWorkflowDefinition, WorkflowsModuleConfig } from './types'\n\nexport function createWorkflowsModuleConfig(options: {\n moduleId: string\n workflows: CodeWorkflowDefinition[]\n}): WorkflowsModuleConfig {\n const workflows = options.workflows.map((wf) => ({\n ...wf,\n moduleId: options.moduleId,\n }))\n\n return {\n moduleId: options.moduleId,\n workflows,\n }\n}\n"],
5
+ "mappings": "AASO,SAAS,4BAA4B,SAGlB;AACxB,QAAM,YAAY,QAAQ,UAAU,IAAI,CAAC,QAAQ;AAAA,IAC/C,GAAG;AAAA,IACH,UAAU,QAAQ;AAAA,EACpB,EAAE;AAEF,SAAO;AAAA,IACL,UAAU,QAAQ;AAAA,IAClB;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,7 @@
1
+ import { defineWorkflow } from "./builder.js";
2
+ import { createWorkflowsModuleConfig } from "./factory.js";
3
+ export {
4
+ createWorkflowsModuleConfig,
5
+ defineWorkflow
6
+ };
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/workflows/index.ts"],
4
+ "sourcesContent": ["export type {\n CodeWorkflowDefinition,\n CodeWorkflowDefinitionData,\n CodeStepDefinition,\n CodeTransitionDefinition,\n CodeTriggerDefinition,\n CodeActivityDefinition,\n WorkflowStepType,\n TransitionTrigger,\n ActivityType,\n WorkflowsModuleConfig,\n} from './types'\n\nexport { defineWorkflow } from './builder'\nexport { createWorkflowsModuleConfig } from './factory'\n"],
5
+ "mappings": "AAaA,SAAS,sBAAsB;AAC/B,SAAS,mCAAmC;",
6
+ "names": []
7
+ }
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": [],
4
+ "sourcesContent": [],
5
+ "mappings": "",
6
+ "names": []
7
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./workflows/index.js";
2
+ //# sourceMappingURL=workflows.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/modules/workflows.ts"],
4
+ "sourcesContent": ["export * from './workflows/index'\n"],
5
+ "mappings": "AAAA,cAAc;",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/shared",
3
- "version": "0.6.2-develop.3400.2.d948268f50",
3
+ "version": "0.6.2-develop.3406.1.2b403f40da",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -92,7 +92,7 @@
92
92
  "@mikro-orm/core": "^7.0.15",
93
93
  "@mikro-orm/decorators": "^7.0.15",
94
94
  "@mikro-orm/postgresql": "^7.0.15",
95
- "@open-mercato/cache": "0.6.2-develop.3400.2.d948268f50",
95
+ "@open-mercato/cache": "0.6.2-develop.3406.1.2b403f40da",
96
96
  "dotenv": "^17.4.2",
97
97
  "rate-limiter-flexible": "^11.1.0",
98
98
  "reflect-metadata": "^0.2.2",
@@ -0,0 +1,394 @@
1
+ import { describe, test, expect } from '@jest/globals'
2
+ import { defineWorkflow } from '../builder'
3
+ import { createWorkflowsModuleConfig } from '../factory'
4
+
5
+ const minimalSteps = [
6
+ { stepId: 'start', stepName: 'Start', stepType: 'START' as const },
7
+ { stepId: 'end', stepName: 'End', stepType: 'END' as const },
8
+ ] as const
9
+
10
+ const minimalTransitions = [
11
+ { transitionId: 'to-end', fromStepId: 'start', toStepId: 'end', trigger: 'auto' as const },
12
+ ]
13
+
14
+ describe('defineWorkflow()', () => {
15
+ test('returns a CodeWorkflowDefinition with correct top-level structure', () => {
16
+ const workflow = defineWorkflow({
17
+ workflowId: 'sales.order-approval',
18
+ workflowName: 'Order Approval',
19
+ steps: minimalSteps,
20
+ transitions: minimalTransitions,
21
+ })
22
+
23
+ expect(workflow.workflowId).toBe('sales.order-approval')
24
+ expect(workflow.workflowName).toBe('Order Approval')
25
+ expect(workflow).toHaveProperty('definition')
26
+ expect(workflow).not.toHaveProperty('steps')
27
+ expect(workflow.definition.steps).toBeDefined()
28
+ expect(workflow.definition.transitions).toBeDefined()
29
+ })
30
+
31
+ test('maps steps with correct stepType, stepName, stepId', () => {
32
+ const workflow = defineWorkflow({
33
+ workflowId: 'test.workflow',
34
+ workflowName: 'Test',
35
+ steps: [
36
+ { stepId: 'start', stepName: 'Start Step', stepType: 'START' as const },
37
+ { stepId: 'review', stepName: 'Review Step', stepType: 'USER_TASK' as const },
38
+ { stepId: 'end', stepName: 'End Step', stepType: 'END' as const },
39
+ ] as const,
40
+ transitions: [
41
+ { transitionId: 't1', fromStepId: 'start', toStepId: 'review', trigger: 'auto' as const },
42
+ { transitionId: 't2', fromStepId: 'review', toStepId: 'end', trigger: 'manual' as const },
43
+ ],
44
+ })
45
+
46
+ expect(workflow.definition.steps).toHaveLength(3)
47
+ expect(workflow.definition.steps[0]).toMatchObject({ stepId: 'start', stepName: 'Start Step', stepType: 'START' })
48
+ expect(workflow.definition.steps[1]).toMatchObject({ stepId: 'review', stepName: 'Review Step', stepType: 'USER_TASK' })
49
+ expect(workflow.definition.steps[2]).toMatchObject({ stepId: 'end', stepName: 'End Step', stepType: 'END' })
50
+ })
51
+
52
+ test('maps transitions with correct fromStepId and toStepId', () => {
53
+ const workflow = defineWorkflow({
54
+ workflowId: 'test.workflow',
55
+ workflowName: 'Test',
56
+ steps: minimalSteps,
57
+ transitions: [
58
+ {
59
+ transitionId: 'start-to-end',
60
+ fromStepId: 'start',
61
+ toStepId: 'end',
62
+ trigger: 'auto' as const,
63
+ transitionName: 'Auto Complete',
64
+ priority: 1,
65
+ },
66
+ ],
67
+ })
68
+
69
+ expect(workflow.definition.transitions).toHaveLength(1)
70
+ expect(workflow.definition.transitions[0]).toMatchObject({
71
+ transitionId: 'start-to-end',
72
+ fromStepId: 'start',
73
+ toStepId: 'end',
74
+ trigger: 'auto',
75
+ transitionName: 'Auto Complete',
76
+ priority: 1,
77
+ })
78
+ })
79
+
80
+ test('sets moduleId to empty string', () => {
81
+ const workflow = defineWorkflow({
82
+ workflowId: 'test.workflow',
83
+ workflowName: 'Test',
84
+ steps: minimalSteps,
85
+ transitions: minimalTransitions,
86
+ })
87
+
88
+ expect(workflow.moduleId).toBe('')
89
+ })
90
+
91
+ test('defaults enabled to true when not specified', () => {
92
+ const workflow = defineWorkflow({
93
+ workflowId: 'test.workflow',
94
+ workflowName: 'Test',
95
+ steps: minimalSteps,
96
+ transitions: minimalTransitions,
97
+ })
98
+
99
+ expect(workflow.enabled).toBe(true)
100
+ })
101
+
102
+ test('respects explicit enabled: false', () => {
103
+ const workflow = defineWorkflow({
104
+ workflowId: 'test.workflow',
105
+ workflowName: 'Test',
106
+ enabled: false,
107
+ steps: minimalSteps,
108
+ transitions: minimalTransitions,
109
+ })
110
+
111
+ expect(workflow.enabled).toBe(false)
112
+ })
113
+
114
+ test('defaults version to 1 when not specified', () => {
115
+ const workflow = defineWorkflow({
116
+ workflowId: 'test.workflow',
117
+ workflowName: 'Test',
118
+ steps: minimalSteps,
119
+ transitions: minimalTransitions,
120
+ })
121
+
122
+ expect(workflow.version).toBe(1)
123
+ })
124
+
125
+ test('sets version correctly when specified', () => {
126
+ const workflow = defineWorkflow({
127
+ workflowId: 'test.workflow',
128
+ workflowName: 'Test',
129
+ version: 3,
130
+ steps: minimalSteps,
131
+ transitions: minimalTransitions,
132
+ })
133
+
134
+ expect(workflow.version).toBe(3)
135
+ })
136
+
137
+ test('defaults description to null when not specified', () => {
138
+ const workflow = defineWorkflow({
139
+ workflowId: 'test.workflow',
140
+ workflowName: 'Test',
141
+ steps: minimalSteps,
142
+ transitions: minimalTransitions,
143
+ })
144
+
145
+ expect(workflow.description).toBeNull()
146
+ })
147
+
148
+ test('sets description when specified', () => {
149
+ const workflow = defineWorkflow({
150
+ workflowId: 'test.workflow',
151
+ workflowName: 'Test',
152
+ description: 'Handles order approvals',
153
+ steps: minimalSteps,
154
+ transitions: minimalTransitions,
155
+ })
156
+
157
+ expect(workflow.description).toBe('Handles order approvals')
158
+ })
159
+
160
+ test('defaults metadata to null when not specified', () => {
161
+ const workflow = defineWorkflow({
162
+ workflowId: 'test.workflow',
163
+ workflowName: 'Test',
164
+ steps: minimalSteps,
165
+ transitions: minimalTransitions,
166
+ })
167
+
168
+ expect(workflow.metadata).toBeNull()
169
+ })
170
+
171
+ test('sets metadata when specified', () => {
172
+ const workflow = defineWorkflow({
173
+ workflowId: 'test.workflow',
174
+ workflowName: 'Test',
175
+ metadata: { tags: ['sales', 'approval'], category: 'sales', icon: 'check' },
176
+ steps: minimalSteps,
177
+ transitions: minimalTransitions,
178
+ })
179
+
180
+ expect(workflow.metadata).toEqual({ tags: ['sales', 'approval'], category: 'sales', icon: 'check' })
181
+ })
182
+
183
+ test('omits triggers from definition when not specified', () => {
184
+ const workflow = defineWorkflow({
185
+ workflowId: 'test.workflow',
186
+ workflowName: 'Test',
187
+ steps: minimalSteps,
188
+ transitions: minimalTransitions,
189
+ })
190
+
191
+ expect(workflow.definition.triggers).toBeUndefined()
192
+ })
193
+
194
+ test('includes triggers in definition when specified', () => {
195
+ const trigger = {
196
+ triggerId: 'on-order-created',
197
+ name: 'Order Created',
198
+ eventPattern: 'sales.order.created',
199
+ enabled: true,
200
+ priority: 0,
201
+ }
202
+
203
+ const workflow = defineWorkflow({
204
+ workflowId: 'test.workflow',
205
+ workflowName: 'Test',
206
+ steps: minimalSteps,
207
+ transitions: minimalTransitions,
208
+ triggers: [trigger],
209
+ })
210
+
211
+ expect(workflow.definition.triggers).toHaveLength(1)
212
+ expect(workflow.definition.triggers![0]).toMatchObject(trigger)
213
+ })
214
+
215
+ test('does not include optional step fields when not provided', () => {
216
+ const workflow = defineWorkflow({
217
+ workflowId: 'test.workflow',
218
+ workflowName: 'Test',
219
+ steps: [{ stepId: 'start', stepName: 'Start', stepType: 'START' as const }] as const,
220
+ transitions: [],
221
+ })
222
+
223
+ const step = workflow.definition.steps[0]
224
+ expect(step).not.toHaveProperty('description')
225
+ expect(step).not.toHaveProperty('config')
226
+ expect(step).not.toHaveProperty('userTaskConfig')
227
+ expect(step).not.toHaveProperty('activities')
228
+ expect(step).not.toHaveProperty('timeout')
229
+ expect(step).not.toHaveProperty('retryPolicy')
230
+ })
231
+
232
+ test('includes optional step fields when provided', () => {
233
+ const workflow = defineWorkflow({
234
+ workflowId: 'test.workflow',
235
+ workflowName: 'Test',
236
+ steps: [
237
+ {
238
+ stepId: 'review',
239
+ stepName: 'Review',
240
+ stepType: 'USER_TASK' as const,
241
+ description: 'Manager reviews the order',
242
+ timeout: 'P1D',
243
+ retryPolicy: { maxAttempts: 3, backoffMs: 1000 },
244
+ userTaskConfig: { assignedTo: 'manager', slaDuration: 'P1D' },
245
+ },
246
+ ] as const,
247
+ transitions: [],
248
+ })
249
+
250
+ const step = workflow.definition.steps[0]
251
+ expect(step.description).toBe('Manager reviews the order')
252
+ expect(step.timeout).toBe('P1D')
253
+ expect(step.retryPolicy).toEqual({ maxAttempts: 3, backoffMs: 1000 })
254
+ expect(step.userTaskConfig).toMatchObject({ assignedTo: 'manager', slaDuration: 'P1D' })
255
+ })
256
+
257
+ test('does not include optional transition fields when not provided', () => {
258
+ const workflow = defineWorkflow({
259
+ workflowId: 'test.workflow',
260
+ workflowName: 'Test',
261
+ steps: minimalSteps,
262
+ transitions: [{ transitionId: 't1', fromStepId: 'start', toStepId: 'end', trigger: 'auto' as const }],
263
+ })
264
+
265
+ const transition = workflow.definition.transitions[0]
266
+ expect(transition).not.toHaveProperty('transitionName')
267
+ expect(transition).not.toHaveProperty('preConditions')
268
+ expect(transition).not.toHaveProperty('postConditions')
269
+ expect(transition).not.toHaveProperty('activities')
270
+ expect(transition).not.toHaveProperty('continueOnActivityFailure')
271
+ expect(transition).not.toHaveProperty('priority')
272
+ })
273
+
274
+ test('works correctly with as const steps (key type safety feature)', () => {
275
+ // TypeScript should infer literal step IDs from `as const`.
276
+ // This test ensures the runtime behavior is correct when the pattern is used.
277
+ const workflow = defineWorkflow({
278
+ workflowId: 'sales.order-approval',
279
+ workflowName: 'Order Approval',
280
+ steps: [
281
+ { stepId: 'start', stepName: 'Start', stepType: 'START' as const },
282
+ { stepId: 'review', stepName: 'Review', stepType: 'USER_TASK' as const },
283
+ { stepId: 'end', stepName: 'End', stepType: 'END' as const },
284
+ ] as const,
285
+ transitions: [
286
+ { transitionId: 'to-review', fromStepId: 'start', toStepId: 'review', trigger: 'auto' as const },
287
+ { transitionId: 'to-end', fromStepId: 'review', toStepId: 'end', trigger: 'manual' as const },
288
+ ],
289
+ })
290
+
291
+ expect(workflow.definition.steps.map((s) => s.stepId)).toEqual(['start', 'review', 'end'])
292
+ expect(workflow.definition.transitions.map((t) => t.fromStepId)).toEqual(['start', 'review'])
293
+ expect(workflow.definition.transitions.map((t) => t.toStepId)).toEqual(['review', 'end'])
294
+ })
295
+ })
296
+
297
+ describe('createWorkflowsModuleConfig()', () => {
298
+ const workflowA = defineWorkflow({
299
+ workflowId: 'test.workflow-a',
300
+ workflowName: 'Workflow A',
301
+ steps: minimalSteps,
302
+ transitions: minimalTransitions,
303
+ })
304
+
305
+ const workflowB = defineWorkflow({
306
+ workflowId: 'test.workflow-b',
307
+ workflowName: 'Workflow B',
308
+ steps: minimalSteps,
309
+ transitions: minimalTransitions,
310
+ })
311
+
312
+ test('returns WorkflowsModuleConfig with correct structure', () => {
313
+ const config = createWorkflowsModuleConfig({
314
+ moduleId: 'sales',
315
+ workflows: [workflowA],
316
+ })
317
+
318
+ expect(config).toHaveProperty('moduleId')
319
+ expect(config).toHaveProperty('workflows')
320
+ expect(Array.isArray(config.workflows)).toBe(true)
321
+ })
322
+
323
+ test('sets moduleId on the config', () => {
324
+ const config = createWorkflowsModuleConfig({
325
+ moduleId: 'sales',
326
+ workflows: [workflowA],
327
+ })
328
+
329
+ expect(config.moduleId).toBe('sales')
330
+ })
331
+
332
+ test('sets moduleId on all workflows', () => {
333
+ const config = createWorkflowsModuleConfig({
334
+ moduleId: 'sales',
335
+ workflows: [workflowA, workflowB],
336
+ })
337
+
338
+ expect(config.workflows[0].moduleId).toBe('sales')
339
+ expect(config.workflows[1].moduleId).toBe('sales')
340
+ })
341
+
342
+ test('preserves workflow properties after moduleId assignment', () => {
343
+ const workflow = defineWorkflow({
344
+ workflowId: 'sales.order-approval',
345
+ workflowName: 'Order Approval',
346
+ description: 'Approves orders',
347
+ version: 2,
348
+ enabled: false,
349
+ metadata: { tags: ['sales'], category: 'approvals' },
350
+ steps: minimalSteps,
351
+ transitions: minimalTransitions,
352
+ })
353
+
354
+ const config = createWorkflowsModuleConfig({
355
+ moduleId: 'sales',
356
+ workflows: [workflow],
357
+ })
358
+
359
+ const result = config.workflows[0]
360
+ expect(result.workflowId).toBe('sales.order-approval')
361
+ expect(result.workflowName).toBe('Order Approval')
362
+ expect(result.description).toBe('Approves orders')
363
+ expect(result.version).toBe(2)
364
+ expect(result.enabled).toBe(false)
365
+ expect(result.metadata).toEqual({ tags: ['sales'], category: 'approvals' })
366
+ expect(result.definition.steps).toHaveLength(2)
367
+ expect(result.definition.transitions).toHaveLength(1)
368
+ })
369
+
370
+ test('does not mutate the original workflow objects', () => {
371
+ const workflow = defineWorkflow({
372
+ workflowId: 'test.workflow',
373
+ workflowName: 'Test',
374
+ steps: minimalSteps,
375
+ transitions: minimalTransitions,
376
+ })
377
+
378
+ expect(workflow.moduleId).toBe('')
379
+
380
+ createWorkflowsModuleConfig({ moduleId: 'sales', workflows: [workflow] })
381
+
382
+ expect(workflow.moduleId).toBe('')
383
+ })
384
+
385
+ test('handles empty workflows array', () => {
386
+ const config = createWorkflowsModuleConfig({
387
+ moduleId: 'sales',
388
+ workflows: [],
389
+ })
390
+
391
+ expect(config.moduleId).toBe('sales')
392
+ expect(config.workflows).toHaveLength(0)
393
+ })
394
+ })
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Code-Based Workflow Definitions — Builder API
3
+ *
4
+ * Provides compile-time type safety for step IDs in transitions.
5
+ * The `const` modifier on TSteps tells TypeScript to infer literal types
6
+ * for stepId values. Transitions constrain fromStepId/toStepId to this union.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const workflow = defineWorkflow({
11
+ * workflowId: 'sales.order-approval',
12
+ * workflowName: 'Order Approval',
13
+ * steps: [
14
+ * { stepId: 'start', stepName: 'Start', stepType: 'START' },
15
+ * { stepId: 'review', stepName: 'Review', stepType: 'USER_TASK' },
16
+ * { stepId: 'end', stepName: 'End', stepType: 'END' },
17
+ * ] as const,
18
+ * transitions: [
19
+ * { transitionId: 'to-review', fromStepId: 'start', toStepId: 'review', trigger: 'auto' },
20
+ * // Compile error: '"typo"' is not assignable to '"start" | "review" | "end"'
21
+ * // { transitionId: 'bad', fromStepId: 'typo', toStepId: 'end', trigger: 'auto' },
22
+ * ],
23
+ * })
24
+ * ```
25
+ */
26
+
27
+ import type {
28
+ CodeWorkflowDefinition,
29
+ CodeStepDefinition,
30
+ CodeTransitionDefinition,
31
+ CodeTriggerDefinition,
32
+ CodeActivityDefinition,
33
+ WorkflowStepType,
34
+ TransitionTrigger,
35
+ } from './types'
36
+
37
+ // ============================================================================
38
+ // Builder Config Type (with const generic for type inference)
39
+ // ============================================================================
40
+
41
+ interface WorkflowBuilderConfig<TSteps extends readonly StepInput<string>[]> {
42
+ workflowId: string
43
+ workflowName: string
44
+ description?: string
45
+ version?: number
46
+ enabled?: boolean
47
+ metadata?: { tags?: string[]; category?: string; icon?: string }
48
+ steps: TSteps
49
+ transitions: TransitionInput<TSteps[number]['stepId']>[]
50
+ triggers?: CodeTriggerDefinition[]
51
+ }
52
+
53
+ interface StepInput<TStepId extends string> {
54
+ stepId: TStepId
55
+ stepName: string
56
+ stepType: WorkflowStepType
57
+ description?: string
58
+ config?: Record<string, unknown>
59
+ userTaskConfig?: CodeStepDefinition['userTaskConfig']
60
+ subWorkflowConfig?: CodeStepDefinition['subWorkflowConfig']
61
+ signalConfig?: CodeStepDefinition['signalConfig']
62
+ activities?: CodeActivityDefinition[]
63
+ timeout?: string
64
+ retryPolicy?: { maxAttempts: number; backoffMs: number }
65
+ preConditions?: CodeStepDefinition['preConditions']
66
+ }
67
+
68
+ interface TransitionInput<TStepId extends string> {
69
+ transitionId: string
70
+ fromStepId: TStepId
71
+ toStepId: TStepId
72
+ transitionName?: string
73
+ trigger: TransitionTrigger
74
+ preConditions?: CodeTransitionDefinition['preConditions']
75
+ postConditions?: CodeTransitionDefinition['postConditions']
76
+ activities?: CodeActivityDefinition[]
77
+ continueOnActivityFailure?: boolean
78
+ priority?: number
79
+ }
80
+
81
+ // ============================================================================
82
+ // defineWorkflow — Main Builder Function
83
+ // ============================================================================
84
+
85
+ /**
86
+ * Define a code-based workflow with compile-time step ID safety.
87
+ *
88
+ * Use `as const` on the steps array for full type inference:
89
+ * ```typescript
90
+ * defineWorkflow({
91
+ * steps: [...] as const,
92
+ * transitions: [...], // fromStepId/toStepId auto-constrained
93
+ * })
94
+ * ```
95
+ *
96
+ * The returned `CodeWorkflowDefinition` has `moduleId` set to empty string.
97
+ * The generator sets the correct `moduleId` at registration time.
98
+ */
99
+ export function defineWorkflow<const TSteps extends readonly StepInput<string>[]>(
100
+ config: WorkflowBuilderConfig<TSteps>,
101
+ ): CodeWorkflowDefinition {
102
+ const steps: CodeStepDefinition[] = config.steps.map((step) => ({
103
+ stepId: step.stepId,
104
+ stepName: step.stepName,
105
+ stepType: step.stepType,
106
+ ...(step.description !== undefined && { description: step.description }),
107
+ ...(step.config !== undefined && { config: step.config }),
108
+ ...(step.userTaskConfig !== undefined && { userTaskConfig: step.userTaskConfig }),
109
+ ...(step.subWorkflowConfig !== undefined && { subWorkflowConfig: step.subWorkflowConfig }),
110
+ ...(step.signalConfig !== undefined && { signalConfig: step.signalConfig }),
111
+ ...(step.activities !== undefined && { activities: step.activities }),
112
+ ...(step.timeout !== undefined && { timeout: step.timeout }),
113
+ ...(step.retryPolicy !== undefined && { retryPolicy: step.retryPolicy }),
114
+ ...(step.preConditions !== undefined && { preConditions: step.preConditions }),
115
+ }))
116
+
117
+ const transitions: CodeTransitionDefinition[] = config.transitions.map((t) => ({
118
+ transitionId: t.transitionId,
119
+ fromStepId: t.fromStepId,
120
+ toStepId: t.toStepId,
121
+ trigger: t.trigger,
122
+ ...(t.transitionName !== undefined && { transitionName: t.transitionName }),
123
+ ...(t.preConditions !== undefined && { preConditions: t.preConditions }),
124
+ ...(t.postConditions !== undefined && { postConditions: t.postConditions }),
125
+ ...(t.activities !== undefined && { activities: t.activities }),
126
+ ...(t.continueOnActivityFailure !== undefined && { continueOnActivityFailure: t.continueOnActivityFailure }),
127
+ ...(t.priority !== undefined && { priority: t.priority }),
128
+ }))
129
+
130
+ return {
131
+ workflowId: config.workflowId,
132
+ workflowName: config.workflowName,
133
+ description: config.description ?? null,
134
+ version: config.version ?? 1,
135
+ enabled: config.enabled ?? true,
136
+ metadata: config.metadata ?? null,
137
+ definition: {
138
+ steps,
139
+ transitions,
140
+ ...(config.triggers !== undefined && { triggers: config.triggers }),
141
+ },
142
+ moduleId: '', // Set by generator/factory at registration time
143
+ }
144
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Code-Based Workflow Definitions — Module Config Factory
3
+ *
4
+ * Analogous to createModuleEvents() for events.
5
+ * Creates a WorkflowsModuleConfig that the generator aggregates.
6
+ */
7
+
8
+ import type { CodeWorkflowDefinition, WorkflowsModuleConfig } from './types'
9
+
10
+ export function createWorkflowsModuleConfig(options: {
11
+ moduleId: string
12
+ workflows: CodeWorkflowDefinition[]
13
+ }): WorkflowsModuleConfig {
14
+ const workflows = options.workflows.map((wf) => ({
15
+ ...wf,
16
+ moduleId: options.moduleId,
17
+ }))
18
+
19
+ return {
20
+ moduleId: options.moduleId,
21
+ workflows,
22
+ }
23
+ }
@@ -0,0 +1,15 @@
1
+ export type {
2
+ CodeWorkflowDefinition,
3
+ CodeWorkflowDefinitionData,
4
+ CodeStepDefinition,
5
+ CodeTransitionDefinition,
6
+ CodeTriggerDefinition,
7
+ CodeActivityDefinition,
8
+ WorkflowStepType,
9
+ TransitionTrigger,
10
+ ActivityType,
11
+ WorkflowsModuleConfig,
12
+ } from './types'
13
+
14
+ export { defineWorkflow } from './builder'
15
+ export { createWorkflowsModuleConfig } from './factory'
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Code-Based Workflow Definitions — Shared Types
3
+ *
4
+ * These types are used by the builder API, generator extension, and in-memory registry.
5
+ * They mirror the JSONB shape stored in workflow_definitions.definition but add
6
+ * generic type safety for step ID references in transitions.
7
+ */
8
+
9
+ // ============================================================================
10
+ // Step Types
11
+ // ============================================================================
12
+
13
+ export type WorkflowStepType =
14
+ | 'START'
15
+ | 'END'
16
+ | 'USER_TASK'
17
+ | 'AUTOMATED'
18
+ | 'PARALLEL_FORK'
19
+ | 'PARALLEL_JOIN'
20
+ | 'SUB_WORKFLOW'
21
+ | 'WAIT_FOR_SIGNAL'
22
+ | 'WAIT_FOR_TIMER'
23
+
24
+ export type TransitionTrigger = 'auto' | 'manual' | 'signal' | 'timer'
25
+
26
+ export type ActivityType =
27
+ | 'SEND_EMAIL'
28
+ | 'CALL_API'
29
+ | 'UPDATE_ENTITY'
30
+ | 'EMIT_EVENT'
31
+ | 'CALL_WEBHOOK'
32
+ | 'EXECUTE_FUNCTION'
33
+ | 'WAIT'
34
+
35
+ // ============================================================================
36
+ // Activity Definition
37
+ // ============================================================================
38
+
39
+ export interface CodeActivityDefinition {
40
+ activityId: string
41
+ activityName: string
42
+ activityType: ActivityType
43
+ config: Record<string, unknown>
44
+ async?: boolean
45
+ retryPolicy?: {
46
+ maxAttempts: number
47
+ initialIntervalMs: number
48
+ backoffCoefficient: number
49
+ maxIntervalMs: number
50
+ }
51
+ timeout?: string
52
+ compensation?: {
53
+ activityId: string
54
+ automatic?: boolean
55
+ }
56
+ }
57
+
58
+ // ============================================================================
59
+ // Step Definition (generic over step IDs)
60
+ // ============================================================================
61
+
62
+ export interface CodeStepDefinition<TStepId extends string = string> {
63
+ stepId: TStepId
64
+ stepName: string
65
+ stepType: WorkflowStepType
66
+ description?: string
67
+ config?: Record<string, unknown>
68
+ userTaskConfig?: {
69
+ formSchema?: Record<string, unknown>
70
+ assignedTo?: string | string[]
71
+ assignmentRule?: string
72
+ slaDuration?: string
73
+ escalationRules?: Array<{
74
+ trigger: 'sla_breach' | 'no_progress' | 'custom'
75
+ action: 'reassign' | 'notify' | 'escalate'
76
+ escalateTo?: string
77
+ notifyUsers?: string[]
78
+ }>
79
+ }
80
+ subWorkflowConfig?: {
81
+ subWorkflowId: string
82
+ version?: number
83
+ inputMapping?: Record<string, string>
84
+ outputMapping?: Record<string, string>
85
+ timeoutMs?: number
86
+ }
87
+ signalConfig?: {
88
+ signalName: string
89
+ timeout?: string
90
+ }
91
+ activities?: CodeActivityDefinition[]
92
+ timeout?: string
93
+ retryPolicy?: {
94
+ maxAttempts: number
95
+ backoffMs: number
96
+ }
97
+ preConditions?: Array<{
98
+ ruleId: string
99
+ required?: boolean
100
+ validationMessage?: Record<string, string>
101
+ }>
102
+ }
103
+
104
+ // ============================================================================
105
+ // Transition Definition (generic over step IDs)
106
+ // ============================================================================
107
+
108
+ export interface CodeTransitionDefinition<TStepId extends string = string> {
109
+ transitionId: string
110
+ fromStepId: TStepId
111
+ toStepId: TStepId
112
+ transitionName?: string
113
+ trigger: TransitionTrigger
114
+ preConditions?: Array<{
115
+ ruleId: string
116
+ required?: boolean
117
+ }>
118
+ postConditions?: Array<{
119
+ ruleId: string
120
+ required?: boolean
121
+ }>
122
+ activities?: CodeActivityDefinition[]
123
+ continueOnActivityFailure?: boolean
124
+ priority?: number
125
+ }
126
+
127
+ // ============================================================================
128
+ // Trigger Definition
129
+ // ============================================================================
130
+
131
+ export interface CodeTriggerDefinition {
132
+ triggerId: string
133
+ name: string
134
+ description?: string | null
135
+ eventPattern: string
136
+ config?: {
137
+ filterConditions?: Array<{
138
+ field: string
139
+ operator: string
140
+ value: unknown
141
+ }>
142
+ contextMapping?: Array<{
143
+ targetKey: string
144
+ sourceExpression: string
145
+ defaultValue?: unknown
146
+ }>
147
+ debounceMs?: number
148
+ maxConcurrentInstances?: number
149
+ entityType?: string
150
+ } | null
151
+ enabled: boolean
152
+ priority: number
153
+ }
154
+
155
+ // ============================================================================
156
+ // Workflow Definition Data (matches JSONB shape)
157
+ // ============================================================================
158
+
159
+ export interface CodeWorkflowDefinitionData {
160
+ steps: CodeStepDefinition[]
161
+ transitions: CodeTransitionDefinition[]
162
+ triggers?: CodeTriggerDefinition[]
163
+ queries?: unknown[]
164
+ signals?: unknown[]
165
+ timers?: unknown[]
166
+ }
167
+
168
+ // ============================================================================
169
+ // Code Workflow Definition (output of defineWorkflow)
170
+ // ============================================================================
171
+
172
+ export interface CodeWorkflowDefinition {
173
+ workflowId: string
174
+ workflowName: string
175
+ description: string | null
176
+ version: number
177
+ enabled: boolean
178
+ metadata: { tags?: string[]; category?: string; icon?: string } | null
179
+ definition: CodeWorkflowDefinitionData
180
+ moduleId: string
181
+ }
182
+
183
+ // ============================================================================
184
+ // Module Config (for generator discovery)
185
+ // ============================================================================
186
+
187
+ export interface WorkflowsModuleConfig {
188
+ moduleId: string
189
+ workflows: CodeWorkflowDefinition[]
190
+ }
@@ -0,0 +1 @@
1
+ export * from './workflows/index'