codehooks-js 1.3.10 → 1.3.12
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/README.md +204 -7
- package/package.json +1 -1
- package/types/index.d.ts +97 -33
- package/workflow/engine.mjs +34 -64
package/README.md
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
# codehooks-js
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The official JavaScript/TypeScript library for [Codehooks.io](https://codehooks.io) - a serverless backend platform that provides everything you need to build and deploy backend applications.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## What is Codehooks.io?
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Codehooks.io is a complete serverless backend platform that combines:
|
|
8
|
+
- **Database** - NoSQL document storage with MongoDB-like queries
|
|
9
|
+
- **API Server** - Express.js-style routing and middleware
|
|
10
|
+
- **Background Jobs** - Queues, workers, and scheduled tasks
|
|
11
|
+
- **Real-time** - Server-sent events for live updates
|
|
12
|
+
- **File Storage** - Static file serving and blob storage
|
|
13
|
+
- **Workflows** - Step-based application logic
|
|
8
14
|
|
|
9
|
-
|
|
15
|
+
All in one platform with zero infrastructure management.
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
10
18
|
|
|
11
19
|
Install the [Codehooks CLI](https://codehooks.io/docs/cli):
|
|
12
20
|
|
|
13
21
|
```shell
|
|
14
22
|
$ npm install -g codehooks
|
|
15
23
|
```
|
|
16
|
-
|
|
24
|
+
|
|
25
|
+
Install the codehooks-js library in your project:
|
|
17
26
|
|
|
18
27
|
```shell
|
|
19
28
|
npm install codehooks-js
|
|
@@ -114,12 +123,200 @@ Correcting the errors should ultimately show a successfull compile output, ready
|
|
|
114
123
|
$ coho compile
|
|
115
124
|
|
|
116
125
|
OK 🙌
|
|
117
|
-
|
|
118
126
|
```
|
|
119
127
|
|
|
128
|
+
Your backend application is now available at your project's endpoint URL. For example:
|
|
129
|
+
|
|
130
|
+
`https://myproject-ff00.codehooks.io/dev/*`
|
|
131
|
+
|
|
120
132
|
## Deploy
|
|
121
133
|
|
|
122
134
|
From the project directory run:
|
|
123
135
|
```shell
|
|
124
136
|
$ codehooks deploy
|
|
125
|
-
```
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Documentation
|
|
140
|
+
|
|
141
|
+
For complete documentation, visit [https://codehooks.io/docs](https://codehooks.io/docs).
|
|
142
|
+
|
|
143
|
+
## API
|
|
144
|
+
|
|
145
|
+
## Codehooks Class API Reference
|
|
146
|
+
|
|
147
|
+
The Codehooks class provides a comprehensive backend application framework with the following APIs:
|
|
148
|
+
|
|
149
|
+
### **HTTP Routing APIs**
|
|
150
|
+
- **`post(path, ...hook)`** - Register POST route handlers
|
|
151
|
+
- **`get(path, ...hook)`** - Register GET route handlers
|
|
152
|
+
- **`put(path, ...hook)`** - Register PUT route handlers
|
|
153
|
+
- **`patch(path, ...hook)`** - Register PATCH route handlers
|
|
154
|
+
- **`delete(path, ...hook)`** - Register DELETE route handlers
|
|
155
|
+
- **`all(path, ...hook)`** - Register handlers for all HTTP methods
|
|
156
|
+
|
|
157
|
+
### **Middleware & Authentication APIs**
|
|
158
|
+
- **`use(...hook)`** - Register global middleware (supports string paths, RegExp, or function)
|
|
159
|
+
- **`useRoute(route, ...hook)`** - Register route-specific middleware
|
|
160
|
+
- **`auth(path, ...hook)`** - Register authentication middleware for specific paths
|
|
161
|
+
|
|
162
|
+
### **Background Processing APIs**
|
|
163
|
+
- **`queue(topic, ...hook)`** - Register queue handlers for background processing
|
|
164
|
+
- **`worker(name, ...hook)`** - Register worker functions (also adds to queues for legacy support)
|
|
165
|
+
- **`job(cronExpression, ...hook)`** - Register scheduled cron jobs
|
|
166
|
+
|
|
167
|
+
### **Static File Serving APIs**
|
|
168
|
+
- **`static(options, hook)`** - Serve static files from source code directory
|
|
169
|
+
- **`storage(options, hook)`** - Serve files from blob storage directory
|
|
170
|
+
|
|
171
|
+
### **Template & Configuration APIs**
|
|
172
|
+
- **`set(key, val)`** - Set application configuration settings
|
|
173
|
+
- **`render(view, data, cb)`** - Render templates with data
|
|
174
|
+
- **`crudlify(schema, options)`** - Auto-generate CRUD REST API endpoints
|
|
175
|
+
|
|
176
|
+
### **Real-time Communication APIs**
|
|
177
|
+
- **`realtime(path, ...hook)`** - Set up server-sent events (SSE) channels for real-time communication
|
|
178
|
+
|
|
179
|
+
### **Workflow Management APIs**
|
|
180
|
+
- **`createWorkflow(name, description, steps, options)`** - Create and register a new workflow instance
|
|
181
|
+
|
|
182
|
+
### **Application Lifecycle APIs**
|
|
183
|
+
- **`init(hook)`** - Initialize the application and return manifest
|
|
184
|
+
- **`start(hook)`** - Alias for `init()` method
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
The Codehooks class serves as a comprehensive backend application framework that combines HTTP routing, background processing, real-time communication, and workflow management capabilities in a single, cohesive API.
|
|
189
|
+
|
|
190
|
+
## Datastore API Reference
|
|
191
|
+
|
|
192
|
+
The Datastore provides a unified interface for both NoSQL document storage and Key-Value operations. It supports MongoDB-like query syntax and provides streaming capabilities for large datasets.
|
|
193
|
+
|
|
194
|
+
### **Connection & Collection Management**
|
|
195
|
+
- **`open()`** - Connect to the Datastore and return the API interface
|
|
196
|
+
- **`collection(name)`** - Get a NoSQL collection by name for document operations
|
|
197
|
+
|
|
198
|
+
### **NoSQL Document Operations**
|
|
199
|
+
|
|
200
|
+
#### **Read Operations**
|
|
201
|
+
- **`getOne(collection, query)`** - Get a single document by ID or query
|
|
202
|
+
- **`findOne(collection, query)`** - Alias for getOne
|
|
203
|
+
- **`getMany(collection, query?, options?)`** - Get a stream of documents matching query
|
|
204
|
+
- **`find(collection, query?, options?)`** - Alias for getMany
|
|
205
|
+
|
|
206
|
+
#### **Write Operations**
|
|
207
|
+
- **`insertOne(collection, document)`** - Insert a new document into a collection
|
|
208
|
+
- **`updateOne(collection, query, document, updateOperators?, options?)`** - Update one document (patches existing data)
|
|
209
|
+
- **`updateMany(collection, query, document, updateOperators?)`** - Update multiple documents
|
|
210
|
+
- **`replaceOne(collection, query, document, options?)`** - Replace one document completely
|
|
211
|
+
- **`replaceMany(collection, query, document, options?)`** - Replace multiple documents
|
|
212
|
+
|
|
213
|
+
#### **Delete Operations**
|
|
214
|
+
- **`removeOne(collection, query)`** - Remove one document by ID or query
|
|
215
|
+
- **`removeMany(collection, query)`** - Remove multiple documents matching query
|
|
216
|
+
|
|
217
|
+
#### **Schema Management**
|
|
218
|
+
- **`createSchema(collection, schema)`** - Validate collection data against JSON-Schema
|
|
219
|
+
- **`setSchema(collection, schema)`** - Alias for createSchema
|
|
220
|
+
- **`removeSchema(collection)`** - Remove JSON-schema validation for collection
|
|
221
|
+
- **`getSchema(collection)`** - Get JSON-schema for collection
|
|
222
|
+
|
|
223
|
+
#### **Utility Operations**
|
|
224
|
+
- **`count(collection)`** - Count documents in a collection
|
|
225
|
+
|
|
226
|
+
### **Key-Value Operations**
|
|
227
|
+
|
|
228
|
+
#### **Basic Key-Value Operations**
|
|
229
|
+
- **`set(key, value, options?)`** - Set a key-value pair
|
|
230
|
+
- **`get(key, options?)`** - Get a value by key
|
|
231
|
+
- **`getAll(keyPattern, options?)`** - Get all key-value pairs matching pattern
|
|
232
|
+
- **`del(key, value)`** - Delete a key-value pair
|
|
233
|
+
- **`delAll(keyPattern, options?)`** - Delete all key-value pairs matching pattern
|
|
234
|
+
|
|
235
|
+
#### **Numeric Operations**
|
|
236
|
+
- **`incr(key, value, options?)`** - Increment a numeric value
|
|
237
|
+
- **`decr(key, value, options?)`** - Decrement a numeric value
|
|
238
|
+
|
|
239
|
+
### **Queue Operations**
|
|
240
|
+
- **`enqueue(topic, document, options?)`** - Add a job to a queue for background processing
|
|
241
|
+
- **`enqueueFromQuery(collection, query, topic, options?)`** - Queue each item from a database query
|
|
242
|
+
|
|
243
|
+
### **DataStream Interface**
|
|
244
|
+
|
|
245
|
+
When using `getMany()` or `find()`, you get a DataStream object with these methods:
|
|
246
|
+
|
|
247
|
+
- **`on(event, callback)`** - Listen for data events (e.g., `'data'`, `'end'`)
|
|
248
|
+
- **`json(response)`** - Pipe data directly to HTTP response as JSON
|
|
249
|
+
- **`toArray()`** - Convert stream to array of objects
|
|
250
|
+
- **`forEach(callback)`** - Iterate over each object in the stream
|
|
251
|
+
|
|
252
|
+
### **Usage Examples**
|
|
253
|
+
|
|
254
|
+
```javascript
|
|
255
|
+
import { datastore } from 'codehooks-js';
|
|
256
|
+
|
|
257
|
+
// Connect to datastore
|
|
258
|
+
const conn = await datastore.open();
|
|
259
|
+
|
|
260
|
+
// NoSQL operations
|
|
261
|
+
const doc = await conn.insertOne('users', { name: 'John', email: 'john@example.com' });
|
|
262
|
+
const user = await conn.getOne('users', { _id: doc._id });
|
|
263
|
+
const users = await conn.getMany('users', { active: true }).toArray();
|
|
264
|
+
await conn.updateOne('users', { _id: doc._id }, { lastLogin: new Date() });
|
|
265
|
+
|
|
266
|
+
// Key-Value operations
|
|
267
|
+
await conn.set('user:123:session', { token: 'abc123', expires: new Date() });
|
|
268
|
+
const session = await conn.get('user:123:session');
|
|
269
|
+
await conn.incr('visits', 1);
|
|
270
|
+
|
|
271
|
+
// Queue operations
|
|
272
|
+
await conn.enqueue('emailWorker', { to: 'user@example.com', template: 'welcome' });
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### **Query Syntax**
|
|
276
|
+
|
|
277
|
+
The Datastore supports MongoDB-like query syntax:
|
|
278
|
+
|
|
279
|
+
```javascript
|
|
280
|
+
// Simple equality
|
|
281
|
+
{ status: 'active' }
|
|
282
|
+
|
|
283
|
+
// Comparison operators
|
|
284
|
+
{ age: { $gt: 18 } }
|
|
285
|
+
{ price: { $lte: 100 } }
|
|
286
|
+
|
|
287
|
+
// Logical operators
|
|
288
|
+
{ $and: [{ status: 'active' }, { age: { $gte: 18 } }] }
|
|
289
|
+
{ $or: [{ category: 'A' }, { category: 'B' }] }
|
|
290
|
+
|
|
291
|
+
// Array operations
|
|
292
|
+
{ tags: { $in: ['javascript', 'nodejs'] } }
|
|
293
|
+
{ tags: { $all: ['javascript', 'nodejs'] } }
|
|
294
|
+
|
|
295
|
+
// Regular expressions
|
|
296
|
+
{ name: { $regex: /john/i } }
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### **Options**
|
|
300
|
+
|
|
301
|
+
Many operations accept an options object for additional configuration:
|
|
302
|
+
|
|
303
|
+
```javascript
|
|
304
|
+
// Upsert option for updates
|
|
305
|
+
await conn.updateOne('users', { email: 'john@example.com' },
|
|
306
|
+
{ lastLogin: new Date() }, {}, { upsert: true });
|
|
307
|
+
|
|
308
|
+
// Key-Value options
|
|
309
|
+
await conn.set('key', 'value', { ttl: 3600000 }); // 1 hour TTL
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### **Key Features:**
|
|
313
|
+
1. **Express-style routing** with support for all HTTP methods
|
|
314
|
+
2. **Middleware system** with global and route-specific middleware
|
|
315
|
+
3. **Background processing** with queues, workers, and scheduled jobs
|
|
316
|
+
4. **Static file serving** from both source and blob storage
|
|
317
|
+
5. **Real-time communication** via Server-Sent Events
|
|
318
|
+
6. **Workflow engine** for complex step-based applications
|
|
319
|
+
7. **Auto-generated CRUD APIs** with schema validation
|
|
320
|
+
8. **Template rendering** system
|
|
321
|
+
9. **Singleton pattern** for global application state
|
|
322
|
+
10. **Datastore** with MongoDB-like query syntax, key-value operations, and queue management
|
package/package.json
CHANGED
package/types/index.d.ts
CHANGED
|
@@ -1080,10 +1080,10 @@ export type WorkflowEvents = {
|
|
|
1080
1080
|
'stateUpdated': { workflowName: string; state: any; instanceId: string };
|
|
1081
1081
|
|
|
1082
1082
|
/**
|
|
1083
|
-
* Emitted when a step is
|
|
1083
|
+
* Emitted when a step is enqueued for execution
|
|
1084
1084
|
* @event
|
|
1085
1085
|
*/
|
|
1086
|
-
'
|
|
1086
|
+
'stepEnqueued': { workflowName: string; step: string; state: any; instanceId: string };
|
|
1087
1087
|
|
|
1088
1088
|
/**
|
|
1089
1089
|
* Emitted when a workflow instance is continued after waiting
|
|
@@ -1113,7 +1113,11 @@ export type WorkflowEvents = {
|
|
|
1113
1113
|
/**
|
|
1114
1114
|
* Definition of a workflow step function
|
|
1115
1115
|
*/
|
|
1116
|
-
export type WorkflowDefinition = Record<string, (
|
|
1116
|
+
export type WorkflowDefinition = Record<string, (
|
|
1117
|
+
state: any,
|
|
1118
|
+
callback: (nextStep: string | string[] | null, newState: any, options?: any) => void,
|
|
1119
|
+
waiterFunction?: (waiter?: any) => void
|
|
1120
|
+
) => Promise<void>>;
|
|
1117
1121
|
|
|
1118
1122
|
/**
|
|
1119
1123
|
* Configuration options for a workflow step
|
|
@@ -1141,20 +1145,6 @@ export type WorkflowConfig = {
|
|
|
1141
1145
|
steps?: Record<string, StepOptions>;
|
|
1142
1146
|
};
|
|
1143
1147
|
|
|
1144
|
-
/**
|
|
1145
|
-
* Engine for managing workflow-based applications
|
|
1146
|
-
* @extends Workflow
|
|
1147
|
-
*/
|
|
1148
|
-
export type WorkflowEngine = Workflow & {
|
|
1149
|
-
/**
|
|
1150
|
-
* Register a new workflow
|
|
1151
|
-
* @param name - Unique identifier for the workflow
|
|
1152
|
-
* @param description - Human-readable description
|
|
1153
|
-
* @param steps - Step definitions
|
|
1154
|
-
*/
|
|
1155
|
-
register: (name: string, description: string, steps: WorkflowDefinition) => Promise<string>;
|
|
1156
|
-
};
|
|
1157
|
-
|
|
1158
1148
|
/**
|
|
1159
1149
|
* Workflow engine for managing step-based applications
|
|
1160
1150
|
* @extends EventEmitter
|
|
@@ -1185,48 +1175,118 @@ export type Workflow = {
|
|
|
1185
1175
|
configure: (options: WorkflowConfig) => void;
|
|
1186
1176
|
|
|
1187
1177
|
/**
|
|
1188
|
-
*
|
|
1178
|
+
* Get the collection name for storing workflow data
|
|
1179
|
+
* @returns The collection name
|
|
1180
|
+
*/
|
|
1181
|
+
getCollectionName: () => string;
|
|
1182
|
+
|
|
1183
|
+
/**
|
|
1184
|
+
* Set the collection name for storing workflow data
|
|
1185
|
+
* @param name - Collection name
|
|
1186
|
+
*/
|
|
1187
|
+
setCollectionName: (name: string) => void;
|
|
1188
|
+
|
|
1189
|
+
/**
|
|
1190
|
+
* Get the queue prefix for workflow jobs
|
|
1191
|
+
* @returns The queue prefix
|
|
1192
|
+
*/
|
|
1193
|
+
getQueuePrefix: () => string;
|
|
1194
|
+
|
|
1195
|
+
/**
|
|
1196
|
+
* Set the queue prefix for workflow jobs
|
|
1197
|
+
* @param prefix - Queue prefix
|
|
1198
|
+
*/
|
|
1199
|
+
setQueuePrefix: (prefix: string) => void;
|
|
1200
|
+
|
|
1201
|
+
/**
|
|
1202
|
+
* Get the timeout for workflow steps
|
|
1203
|
+
* @returns The timeout in milliseconds
|
|
1204
|
+
*/
|
|
1205
|
+
getTimeout: () => number;
|
|
1206
|
+
|
|
1207
|
+
/**
|
|
1208
|
+
* Set the timeout for workflow steps
|
|
1209
|
+
* @param timeout - Timeout in milliseconds
|
|
1210
|
+
*/
|
|
1211
|
+
setTimeout: (timeout: number) => void;
|
|
1212
|
+
|
|
1213
|
+
/**
|
|
1214
|
+
* Get the maximum step count
|
|
1215
|
+
* @returns The maximum step count
|
|
1216
|
+
*/
|
|
1217
|
+
getMaxStepCount: () => number;
|
|
1218
|
+
|
|
1219
|
+
/**
|
|
1220
|
+
* Set the maximum step count
|
|
1221
|
+
* @param maxStepCount - Maximum step count
|
|
1222
|
+
*/
|
|
1223
|
+
setMaxStepCount: (maxStepCount: number) => void;
|
|
1224
|
+
|
|
1225
|
+
/**
|
|
1226
|
+
* Get the steps configuration
|
|
1227
|
+
* @returns The steps configuration
|
|
1228
|
+
*/
|
|
1229
|
+
getStepsConfig: () => Record<string, StepOptions>;
|
|
1230
|
+
|
|
1231
|
+
/**
|
|
1232
|
+
* Set the steps configuration
|
|
1233
|
+
* @param steps - Steps configuration
|
|
1234
|
+
*/
|
|
1235
|
+
setStepsConfig: (steps: Record<string, StepOptions>) => void;
|
|
1236
|
+
|
|
1237
|
+
/**
|
|
1238
|
+
* Get the step definition for a specific step
|
|
1239
|
+
* @param workflowName - Name of the workflow
|
|
1240
|
+
* @param stepName - Name of the step
|
|
1241
|
+
* @returns The step function
|
|
1242
|
+
*/
|
|
1243
|
+
getDefinition: (workflowName: string, stepName: string) => Function;
|
|
1244
|
+
|
|
1245
|
+
/**
|
|
1246
|
+
* Register a new workflow with a Codehooks application
|
|
1189
1247
|
* @param app - Codehooks application instance
|
|
1190
|
-
* @param name - Unique identifier for the workflow
|
|
1191
|
-
* @param description - Human-readable description
|
|
1192
|
-
* @param definition - Object containing step definitions
|
|
1193
1248
|
* @returns Promise with the registered workflow name
|
|
1194
1249
|
*/
|
|
1195
|
-
register: (app: Codehooks
|
|
1250
|
+
register: (app: Codehooks) => Promise<string>;
|
|
1196
1251
|
|
|
1197
1252
|
/**
|
|
1198
1253
|
* Start a new workflow instance
|
|
1199
|
-
* @param name - Name of the workflow to start
|
|
1200
1254
|
* @param initialState - Initial state for the workflow instance
|
|
1201
|
-
* @returns Promise with the workflow instance
|
|
1255
|
+
* @returns Promise with the workflow instance
|
|
1202
1256
|
*/
|
|
1203
|
-
start: (
|
|
1257
|
+
start: (initialState: any) => Promise<any>;
|
|
1204
1258
|
|
|
1205
1259
|
/**
|
|
1206
1260
|
* Update the state of a workflow instance
|
|
1207
|
-
* @param workflowName - Name of the workflow
|
|
1208
1261
|
* @param instanceId - ID of the workflow instance
|
|
1209
1262
|
* @param state - New state to update with
|
|
1210
1263
|
* @param options - Options for the update, { continue: false } to avoid continuing the step
|
|
1211
1264
|
* @returns Promise with the updated state
|
|
1212
1265
|
*/
|
|
1213
|
-
updateState: (
|
|
1266
|
+
updateState: (instanceId: string, state: any, options?: UpdateOptions) => Promise<any>;
|
|
1267
|
+
|
|
1268
|
+
/**
|
|
1269
|
+
* Set the complete state of a workflow instance
|
|
1270
|
+
* @param instanceId - ID of the workflow instance
|
|
1271
|
+
* @param stateData - Object containing _id and state
|
|
1272
|
+
* @returns Promise<void>
|
|
1273
|
+
*/
|
|
1274
|
+
setState: (instanceId: string, stateData: { _id: string; state: any }) => Promise<void>;
|
|
1214
1275
|
|
|
1215
1276
|
/**
|
|
1216
1277
|
* Continue a paused workflow instance
|
|
1217
|
-
* @param workflowName - Name of the workflow
|
|
1218
1278
|
* @param instanceId - ID of the workflow instance
|
|
1219
1279
|
* @param reset - Whether to reset all step counts (true) or just the current step (false)
|
|
1220
1280
|
* @returns Promise with the queue ID for the continued step
|
|
1221
1281
|
*/
|
|
1222
|
-
continue: (
|
|
1282
|
+
continue: (instanceId: string, reset?: boolean) => Promise<{ instanceId: string }>;
|
|
1223
1283
|
|
|
1224
1284
|
/**
|
|
1225
1285
|
* Get the status of a workflow instance
|
|
1226
1286
|
* @param id - ID of the workflow instance
|
|
1227
1287
|
* @returns Promise with the workflow status
|
|
1228
1288
|
*/
|
|
1229
|
-
|
|
1289
|
+
getStepsStatus: (id: string) => Promise<any>;
|
|
1230
1290
|
|
|
1231
1291
|
/**
|
|
1232
1292
|
* Get all workflow instances matching a filter
|
|
@@ -1240,7 +1300,7 @@ export type Workflow = {
|
|
|
1240
1300
|
* @param id - ID of the workflow instance to cancel
|
|
1241
1301
|
* @returns Promise with the cancellation result
|
|
1242
1302
|
*/
|
|
1243
|
-
|
|
1303
|
+
cancelSteps: (id: string) => Promise<any>;
|
|
1244
1304
|
|
|
1245
1305
|
/**
|
|
1246
1306
|
* Register an event listener
|
|
@@ -1278,7 +1338,7 @@ export type Workflow = {
|
|
|
1278
1338
|
* Continue all timed out workflow instances
|
|
1279
1339
|
* @returns Promise with array of results containing queue IDs for continued workflows
|
|
1280
1340
|
*/
|
|
1281
|
-
continueAllTimedOut: () => Promise<Array<{
|
|
1341
|
+
continueAllTimedOut: () => Promise<Array<{instanceId: string}>>;
|
|
1282
1342
|
|
|
1283
1343
|
/**
|
|
1284
1344
|
* Check if a specific step in a workflow instance has timed out
|
|
@@ -1297,6 +1357,7 @@ export type Workflow = {
|
|
|
1297
1357
|
* // finishTime?: string, // if step has finished
|
|
1298
1358
|
* // currentTime?: string, // if step is still running
|
|
1299
1359
|
* // timeoutSource: 'stepConfig' | 'defaultOptions' | 'globalTimeout'
|
|
1360
|
+
* // reason?: string // if step not found
|
|
1300
1361
|
* // }
|
|
1301
1362
|
*/
|
|
1302
1363
|
isStepTimedOut: (workflow: any) => {
|
|
@@ -1374,11 +1435,14 @@ export type WorkflowEvent =
|
|
|
1374
1435
|
* Data structure for workflow events
|
|
1375
1436
|
*/
|
|
1376
1437
|
export type WorkflowEventData = {
|
|
1377
|
-
workflowName
|
|
1438
|
+
workflowName?: string;
|
|
1439
|
+
name?: string;
|
|
1440
|
+
description?: string;
|
|
1378
1441
|
step?: string;
|
|
1379
1442
|
state?: any;
|
|
1380
1443
|
instanceId?: string;
|
|
1381
1444
|
error?: Error;
|
|
1445
|
+
id?: string;
|
|
1382
1446
|
[key: string]: any;
|
|
1383
1447
|
};
|
|
1384
1448
|
|
package/workflow/engine.mjs
CHANGED
|
@@ -261,6 +261,18 @@ class Workflow extends EventEmitter {
|
|
|
261
261
|
return;
|
|
262
262
|
}
|
|
263
263
|
try {
|
|
264
|
+
const waiterFunction = async (state) => {
|
|
265
|
+
if (state) {
|
|
266
|
+
console.debug('waiter', state);
|
|
267
|
+
await connection.updateOne(this.#collectionName,
|
|
268
|
+
{ _id: instanceId },
|
|
269
|
+
{ $set: { ...state, updatedAt: new Date().toISOString() } });
|
|
270
|
+
resolve();
|
|
271
|
+
return;
|
|
272
|
+
} else {
|
|
273
|
+
resolve(); // no state update
|
|
274
|
+
}
|
|
275
|
+
}
|
|
264
276
|
await func({...newState, instanceId: newState._id}, async (nextStep, userState, options) => {
|
|
265
277
|
try {
|
|
266
278
|
// Protect system-level properties
|
|
@@ -370,8 +382,8 @@ class Workflow extends EventEmitter {
|
|
|
370
382
|
console.error('error', error.message);
|
|
371
383
|
reject(error);
|
|
372
384
|
}
|
|
373
|
-
});
|
|
374
|
-
resolve();
|
|
385
|
+
}, waiterFunction);
|
|
386
|
+
//resolve();
|
|
375
387
|
} catch (error) {
|
|
376
388
|
console.error('Error executing step function:', error);
|
|
377
389
|
reject(error);
|
|
@@ -411,17 +423,7 @@ class Workflow extends EventEmitter {
|
|
|
411
423
|
* @private
|
|
412
424
|
*/
|
|
413
425
|
async registerWithApp(app, name, description, definition) {
|
|
414
|
-
this.emit('workflowCreated', { name, description });
|
|
415
|
-
|
|
416
|
-
// Log initial state of definitions Map
|
|
417
|
-
console.debug('Before registration - Current definitions Map:', {
|
|
418
|
-
size: this.#definitions.size,
|
|
419
|
-
keys: Array.from(this.#definitions.keys()),
|
|
420
|
-
entries: Array.from(this.#definitions.entries()).map(([key, value]) => ({
|
|
421
|
-
key,
|
|
422
|
-
stepNames: Object.keys(value)
|
|
423
|
-
}))
|
|
424
|
-
});
|
|
426
|
+
this.emit('workflowCreated', { name, description });
|
|
425
427
|
|
|
426
428
|
// Validate each step in the definition
|
|
427
429
|
for (const [stepName, step] of Object.entries(definition)) {
|
|
@@ -456,63 +458,33 @@ class Workflow extends EventEmitter {
|
|
|
456
458
|
}
|
|
457
459
|
|
|
458
460
|
// Store the definition
|
|
459
|
-
this.#definitions.set(name, definition);
|
|
460
|
-
|
|
461
|
-
// Log state after registration
|
|
462
|
-
console.debug('After registration - Updated definitions Map:', {
|
|
463
|
-
size: this.#definitions.size,
|
|
464
|
-
keys: Array.from(this.#definitions.keys()),
|
|
465
|
-
entries: Array.from(this.#definitions.entries()).map(([key, value]) => ({
|
|
466
|
-
key,
|
|
467
|
-
stepNames: Object.keys(value)
|
|
468
|
-
}))
|
|
469
|
-
});
|
|
461
|
+
this.#definitions.set(name, definition);
|
|
470
462
|
|
|
471
463
|
return name;
|
|
472
464
|
}
|
|
473
465
|
|
|
474
466
|
/**
|
|
475
467
|
* Start a new steps instance
|
|
476
|
-
* @param {string} name - Name of the steps workflow to start
|
|
477
468
|
* @param {Object} initialState - Initial state for the steps instance
|
|
478
469
|
* @returns {Promise<{id: string}>} The steps instance ID
|
|
479
470
|
* @throws {Error} If starting steps fails
|
|
480
471
|
*/
|
|
481
|
-
async start(
|
|
482
|
-
this.emit('workflowStarted', { name, initialState });
|
|
483
|
-
|
|
484
|
-
// Log definitions Map state at start
|
|
485
|
-
console.debug('At workflow start - Current definitions Map:', {
|
|
486
|
-
size: this.#definitions.size,
|
|
487
|
-
keys: Array.from(this.#definitions.keys()),
|
|
488
|
-
entries: Array.from(this.#definitions.entries()).map(([key, value]) => ({
|
|
489
|
-
key,
|
|
490
|
-
stepNames: Object.keys(value)
|
|
491
|
-
}))
|
|
492
|
-
});
|
|
472
|
+
async start(initialState) {
|
|
473
|
+
this.emit('workflowStarted', { name: this.#name, initialState });
|
|
493
474
|
|
|
494
475
|
return new Promise(async (resolve, reject) => {
|
|
495
476
|
try {
|
|
496
|
-
console.debug('Starting workflow', name);
|
|
497
|
-
const funcs = this.#definitions.get(name);
|
|
498
|
-
|
|
499
|
-
exists: !!funcs,
|
|
500
|
-
stepNames: funcs ? Object.keys(funcs) : []
|
|
501
|
-
});
|
|
502
|
-
|
|
477
|
+
console.debug('Starting workflow', this.#name);
|
|
478
|
+
const funcs = this.#definitions.get(this.#name);
|
|
479
|
+
|
|
503
480
|
if (!funcs) {
|
|
504
|
-
reject(new Error(`No workflow definition found for: ${name}`));
|
|
481
|
+
reject(new Error(`No workflow definition found for: ${this.#name}`));
|
|
505
482
|
return;
|
|
506
483
|
}
|
|
507
484
|
|
|
508
485
|
const firstStepName = Object.keys(funcs)[0];
|
|
509
486
|
const firstStep = funcs[firstStepName];
|
|
510
|
-
|
|
511
|
-
name: firstStepName,
|
|
512
|
-
exists: !!firstStep,
|
|
513
|
-
type: firstStep ? typeof firstStep : 'undefined'
|
|
514
|
-
});
|
|
515
|
-
|
|
487
|
+
|
|
516
488
|
if (!firstStep) {
|
|
517
489
|
reject(new Error('No start step defined in workflow'));
|
|
518
490
|
return;
|
|
@@ -521,9 +493,9 @@ class Workflow extends EventEmitter {
|
|
|
521
493
|
const connection = await Datastore.open();
|
|
522
494
|
// Create a new workflow state in the database
|
|
523
495
|
const newState = await connection.insertOne(this.#collectionName,
|
|
524
|
-
{ ...initialState, nextStep: firstStepName, createdAt: new Date().toISOString(), workflowName: name, stepCount: { } });
|
|
525
|
-
const { _id: ID } = await connection.enqueue(`${this.#queuePrefix}_${name}_${firstStepName}`, {
|
|
526
|
-
stepsName: name,
|
|
496
|
+
{ ...initialState, nextStep: firstStepName, createdAt: new Date().toISOString(), workflowName: this.#name, stepCount: { } });
|
|
497
|
+
const { _id: ID } = await connection.enqueue(`${this.#queuePrefix}_${this.#name}_${firstStepName}`, {
|
|
498
|
+
stepsName: this.#name,
|
|
527
499
|
goto: firstStepName,
|
|
528
500
|
state: newState,
|
|
529
501
|
instanceId: newState._id,
|
|
@@ -539,21 +511,20 @@ class Workflow extends EventEmitter {
|
|
|
539
511
|
|
|
540
512
|
/**
|
|
541
513
|
* Update the state of a workflow instance
|
|
542
|
-
* @param {string} stepsName - Name of the workflow
|
|
543
514
|
* @param {string} instanceId - ID of the workflow instance
|
|
544
515
|
* @param {Object} state - New state to update with
|
|
545
516
|
* @param {Object} options - Options for the update, { continue: false } to avoid continuing the the step
|
|
546
517
|
* @returns {Promise<Object>} The updated state
|
|
547
518
|
*/
|
|
548
|
-
async updateState(
|
|
549
|
-
this.emit('stepsStateUpdating', {
|
|
519
|
+
async updateState(instanceId, state, options={continue: true}) {
|
|
520
|
+
this.emit('stepsStateUpdating', { name: this.#name, instanceId, state });
|
|
550
521
|
const connection = await Datastore.open();
|
|
551
522
|
return new Promise(async (resolve, reject) => {
|
|
552
523
|
const doc = await connection.updateOne(this.#collectionName,
|
|
553
524
|
{ _id: instanceId },
|
|
554
525
|
{ $set: { ...state, updatedAt: new Date().toISOString() } });
|
|
555
526
|
if (options.continue) {
|
|
556
|
-
await this.continue(
|
|
527
|
+
await this.continue(instanceId, false);
|
|
557
528
|
}
|
|
558
529
|
resolve({ ...doc });
|
|
559
530
|
});
|
|
@@ -572,12 +543,11 @@ class Workflow extends EventEmitter {
|
|
|
572
543
|
|
|
573
544
|
/**
|
|
574
545
|
* Continue a paused steps instance
|
|
575
|
-
* @param {string} stepsName - Name of the steps workflow
|
|
576
546
|
* @param {string} instanceId - ID of the steps instance
|
|
577
547
|
* @returns {Promise<{qId: string}>} Queue ID for the continued step
|
|
578
548
|
* @throws {Error} If steps instance not found
|
|
579
549
|
*/
|
|
580
|
-
async continue(
|
|
550
|
+
async continue(instanceId, reset=false) {
|
|
581
551
|
const connection = await Datastore.open();
|
|
582
552
|
const state = await connection.findOne(this.#collectionName, { _id: instanceId });
|
|
583
553
|
if (!state) {
|
|
@@ -596,11 +566,11 @@ class Workflow extends EventEmitter {
|
|
|
596
566
|
|
|
597
567
|
await connection.updateOne(this.#collectionName, { _id: instanceId }, { $set: { stepCount: state.stepCount } });
|
|
598
568
|
console.debug('continue state', state);
|
|
599
|
-
this.emit('workflowContinued', {
|
|
569
|
+
this.emit('workflowContinued', { name: this.#name, step: state.nextStep, instanceId });
|
|
600
570
|
|
|
601
571
|
return new Promise(async (resolve, reject) => {
|
|
602
|
-
const { _id: ID } = await connection.enqueue(`${this.#queuePrefix}_${
|
|
603
|
-
stepsName,
|
|
572
|
+
const { _id: ID } = await connection.enqueue(`${this.#queuePrefix}_${this.#name}_${state.nextStep}`, {
|
|
573
|
+
stepsName: this.#name,
|
|
604
574
|
goto: state.nextStep,
|
|
605
575
|
state: state,
|
|
606
576
|
options: {},
|
|
@@ -626,7 +596,7 @@ class Workflow extends EventEmitter {
|
|
|
626
596
|
if (diffMillis > this.#timeout) {
|
|
627
597
|
const diffMinutes = diffMillis / (1000 * 60);
|
|
628
598
|
console.log('Timed out:', workflow._id, workflow.nextStep, `(${diffMinutes.toFixed(1)} minutes old)`);
|
|
629
|
-
const result = await this.continue(workflow.
|
|
599
|
+
const result = await this.continue(workflow._id, true);
|
|
630
600
|
console.log('Continued:', result._id);
|
|
631
601
|
results.push(result);
|
|
632
602
|
}
|