@stonecrop/utilities 0.13.12 → 0.13.14

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,194 +0,0 @@
1
- /**
2
- * Workflow Scaffolding Utilities
3
- * Generates XState workflow configurations from SQL schema patterns
4
- * @packageDocumentation
5
- */
6
- /**
7
- * Detects status/state columns in SQL table
8
- * @param table - Parsed SQL table
9
- * @returns Status column if found
10
- * @public
11
- */
12
- export function detectStatusColumn(table) {
13
- // Look for columns matching common status patterns
14
- const statusPatterns = [/^status$/i, /^state$/i, /^workflow_state$/i, /^document_status$/i, /_status$/i, /_state$/i];
15
- for (const column of table.columns) {
16
- for (const pattern of statusPatterns) {
17
- if (pattern.test(column.name)) {
18
- // Check if it's an enum type or has check constraints
19
- if (column.enumValues || column.checkConstraint) {
20
- return column;
21
- }
22
- }
23
- }
24
- }
25
- return undefined;
26
- }
27
- /**
28
- * Extracts state values from status column
29
- * @param column - Status column
30
- * @returns Array of state names
31
- * @public
32
- */
33
- export function extractStateValues(column) {
34
- // Use enum values if available
35
- if (column.enumValues && column.enumValues.length > 0) {
36
- return column.enumValues;
37
- }
38
- // Try to extract from CHECK constraint
39
- if (column.checkConstraint) {
40
- // Match patterns like: status IN ('draft', 'pending', 'approved')
41
- const inMatch = column.checkConstraint.match(/IN\s*\(([^)]+)\)/i);
42
- if (inMatch) {
43
- return inMatch[1]
44
- .split(',')
45
- .map(v => v.trim().replace(/^'|'$/g, ''))
46
- .filter(Boolean);
47
- }
48
- // Match patterns like: status = 'draft' OR status = 'pending' OR status = 'approved'
49
- const orMatch = column.checkConstraint.match(/'([^']+)'/g);
50
- if (orMatch) {
51
- return orMatch.map(v => v.replace(/'/g, '')).filter(Boolean);
52
- }
53
- }
54
- // Return common defaults if no values detected
55
- return ['draft', 'pending', 'approved', 'rejected'];
56
- }
57
- /**
58
- * Generates transition event names from state pairs
59
- * @param fromState - Source state
60
- * @param toState - Target state
61
- * @returns Uppercase transition event name
62
- * @public
63
- * @example
64
- * ```typescript
65
- * generateTransitionName('draft', 'pending') // 'SUBMIT'
66
- * generateTransitionName('pending', 'approved') // 'APPROVE'
67
- * generateTransitionName('approved', 'rejected') // 'REJECT'
68
- * ```
69
- */
70
- export function generateTransitionName(fromState, toState) {
71
- // Common transition patterns
72
- const transitionMap = {
73
- 'draft->pending': 'SUBMIT',
74
- 'draft->submitted': 'SUBMIT',
75
- 'pending->approved': 'APPROVE',
76
- 'pending->accepted': 'APPROVE',
77
- 'submitted->approved': 'APPROVE',
78
- 'pending->rejected': 'REJECT',
79
- 'submitted->rejected': 'REJECT',
80
- 'approved->rejected': 'REJECT',
81
- 'approved->closed': 'CLOSE',
82
- 'rejected->draft': 'REOPEN',
83
- 'closed->draft': 'REOPEN',
84
- 'active->inactive': 'DEACTIVATE',
85
- 'inactive->active': 'ACTIVATE',
86
- 'new->draft': 'SAVE',
87
- };
88
- const key = `${fromState.toLowerCase()}->${toState.toLowerCase()}`;
89
- if (transitionMap[key]) {
90
- return transitionMap[key];
91
- }
92
- // Generate uppercase event name from target state
93
- // e.g., 'approved' -> 'APPROVE', 'completed' -> 'COMPLETE'
94
- const verb = toState.toUpperCase();
95
- if (verb.endsWith('ED')) {
96
- return verb.slice(0, -1); // 'APPROVED' -> 'APPROVE'
97
- }
98
- return verb;
99
- }
100
- /**
101
- * Scaffolds a basic workflow from detected states
102
- * @param states - Array of state names
103
- * @param machineId - Machine identifier
104
- * @returns Workflow scaffold with machine config and actions
105
- * @public
106
- */
107
- export function scaffoldWorkflow(states, machineId) {
108
- if (states.length === 0) {
109
- throw new Error('Cannot scaffold workflow: no states provided');
110
- }
111
- // Capitalize state names for consistency
112
- const capitalizedStates = states.map(s => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase());
113
- // Use first state as initial
114
- const initialState = capitalizedStates[0];
115
- // Build state machine configuration
116
- const stateConfig = {};
117
- const actions = {};
118
- for (let i = 0; i < capitalizedStates.length; i++) {
119
- const currentState = capitalizedStates[i];
120
- stateConfig[currentState] = { on: {} };
121
- // Create transitions to subsequent states
122
- for (let j = i + 1; j < capitalizedStates.length; j++) {
123
- const targetState = capitalizedStates[j];
124
- const eventName = generateTransitionName(currentState, targetState);
125
- stateConfig[currentState].on[eventName] = targetState;
126
- // Add stub action for this transition
127
- if (!actions[eventName]) {
128
- actions[eventName] = [];
129
- }
130
- }
131
- // Add a CANCEL transition back to first state (except from first state)
132
- if (i > 0 && currentState !== initialState) {
133
- stateConfig[currentState].on['CANCEL'] = initialState;
134
- if (!actions['CANCEL']) {
135
- actions['CANCEL'] = [];
136
- }
137
- }
138
- }
139
- // Mark last state as final (optional)
140
- const lastState = capitalizedStates[capitalizedStates.length - 1];
141
- if (lastState.toLowerCase().includes('close') ||
142
- lastState.toLowerCase().includes('reject') ||
143
- lastState.toLowerCase().includes('complet')) {
144
- stateConfig[lastState].type = 'final';
145
- }
146
- const workflow = {
147
- id: machineId,
148
- initial: initialState,
149
- states: stateConfig,
150
- };
151
- return {
152
- workflow,
153
- actions,
154
- };
155
- }
156
- /**
157
- * Scaffolds workflow from SQL table
158
- * @param table - Parsed SQL table
159
- * @param machineId - Optional machine ID (defaults to table name)
160
- * @returns Workflow scaffold or undefined if no status column found
161
- * @public
162
- */
163
- export function scaffoldWorkflowFromTable(table, machineId) {
164
- const statusColumn = detectStatusColumn(table);
165
- if (!statusColumn) {
166
- return undefined;
167
- }
168
- const states = extractStateValues(statusColumn);
169
- const scaffold = scaffoldWorkflow(states, machineId || table.name);
170
- return {
171
- ...scaffold,
172
- sourceColumn: statusColumn.name,
173
- };
174
- }
175
- /**
176
- * Generates default workflow layout positions for visual editor
177
- * @param states - Array of state names
178
- * @param horizontal - Whether to layout horizontally (default) or vertically
179
- * @returns Layout object with position coordinates
180
- * @public
181
- */
182
- export function generateWorkflowLayout(states, horizontal = true) {
183
- const layout = {};
184
- const spacing = horizontal ? 250 : 100;
185
- const offsetX = 50;
186
- const offsetY = 50;
187
- for (let i = 0; i < states.length; i++) {
188
- const state = states[i].charAt(0).toUpperCase() + states[i].slice(1).toLowerCase();
189
- layout[state] = {
190
- position: horizontal ? { x: offsetX + i * spacing, y: offsetY } : { x: offsetX, y: offsetY + i * spacing },
191
- };
192
- }
193
- return layout;
194
- }