codehooks-js 1.3.11 → 1.3.13
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 +203 -7
- package/index.js +5 -1
- package/package.json +1 -1
- package/types/index.d.ts +99 -32
- package/webserver.mjs +29 -1
- package/workflow/engine.mjs +20 -62
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,199 @@ 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. **Datastore** with MongoDB-like query syntax, key-value operations, and queue management
|
|
321
|
+
9. **Security & Authentication** with JWKS support and custom auth middleware for secure API endpoints
|
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {agg} from './aggregation/index.mjs';
|
|
2
2
|
import {crudlify as crud} from './crudlify/index.mjs';
|
|
3
|
-
import {serveStatic as ws, render as renderView} from './webserver.mjs';
|
|
3
|
+
import {serveStatic as ws, render as renderView, internalFetch} from './webserver.mjs';
|
|
4
4
|
import Workflow from './workflow/engine.mjs';
|
|
5
5
|
|
|
6
6
|
function createRoute(str) {
|
|
@@ -116,6 +116,10 @@ class Codehooks {
|
|
|
116
116
|
res.status(200).json({...data});
|
|
117
117
|
})
|
|
118
118
|
}
|
|
119
|
+
|
|
120
|
+
internalFetch = (url, options = {}) => {
|
|
121
|
+
return internalFetch(url, options);
|
|
122
|
+
}
|
|
119
123
|
|
|
120
124
|
createWorkflow = (name, description, steps, options={}) => {
|
|
121
125
|
const wf = new Workflow(name, description, steps, options);
|
package/package.json
CHANGED
package/types/index.d.ts
CHANGED
|
@@ -1048,6 +1048,13 @@ export class Codehooks {
|
|
|
1048
1048
|
* @returns Promise with the registered workflow name
|
|
1049
1049
|
*/
|
|
1050
1050
|
registerWorkflow: (name: string, description: string, steps: WorkflowDefinition) => Promise<string>;
|
|
1051
|
+
/**
|
|
1052
|
+
* Fetch data from another Codehooks API
|
|
1053
|
+
* @param url - URL to fetch from, e.g. http://myapi-ffee.codehooks.io/dev/api/myroute
|
|
1054
|
+
* @param options - Fetch options
|
|
1055
|
+
* @returns Promise with the fetched data
|
|
1056
|
+
*/
|
|
1057
|
+
internalFetch: (url: string, options?: any) => Promise<any>;
|
|
1051
1058
|
}
|
|
1052
1059
|
declare const _coho: Codehooks;
|
|
1053
1060
|
|
|
@@ -1080,10 +1087,10 @@ export type WorkflowEvents = {
|
|
|
1080
1087
|
'stateUpdated': { workflowName: string; state: any; instanceId: string };
|
|
1081
1088
|
|
|
1082
1089
|
/**
|
|
1083
|
-
* Emitted when a step is
|
|
1090
|
+
* Emitted when a step is enqueued for execution
|
|
1084
1091
|
* @event
|
|
1085
1092
|
*/
|
|
1086
|
-
'
|
|
1093
|
+
'stepEnqueued': { workflowName: string; step: string; state: any; instanceId: string };
|
|
1087
1094
|
|
|
1088
1095
|
/**
|
|
1089
1096
|
* Emitted when a workflow instance is continued after waiting
|
|
@@ -1145,20 +1152,6 @@ export type WorkflowConfig = {
|
|
|
1145
1152
|
steps?: Record<string, StepOptions>;
|
|
1146
1153
|
};
|
|
1147
1154
|
|
|
1148
|
-
/**
|
|
1149
|
-
* Engine for managing workflow-based applications
|
|
1150
|
-
* @extends Workflow
|
|
1151
|
-
*/
|
|
1152
|
-
export type WorkflowEngine = Workflow & {
|
|
1153
|
-
/**
|
|
1154
|
-
* Register a new workflow
|
|
1155
|
-
* @param name - Unique identifier for the workflow
|
|
1156
|
-
* @param description - Human-readable description
|
|
1157
|
-
* @param steps - Step definitions
|
|
1158
|
-
*/
|
|
1159
|
-
register: (name: string, description: string, steps: WorkflowDefinition) => Promise<string>;
|
|
1160
|
-
};
|
|
1161
|
-
|
|
1162
1155
|
/**
|
|
1163
1156
|
* Workflow engine for managing step-based applications
|
|
1164
1157
|
* @extends EventEmitter
|
|
@@ -1189,48 +1182,118 @@ export type Workflow = {
|
|
|
1189
1182
|
configure: (options: WorkflowConfig) => void;
|
|
1190
1183
|
|
|
1191
1184
|
/**
|
|
1192
|
-
*
|
|
1185
|
+
* Get the collection name for storing workflow data
|
|
1186
|
+
* @returns The collection name
|
|
1187
|
+
*/
|
|
1188
|
+
getCollectionName: () => string;
|
|
1189
|
+
|
|
1190
|
+
/**
|
|
1191
|
+
* Set the collection name for storing workflow data
|
|
1192
|
+
* @param name - Collection name
|
|
1193
|
+
*/
|
|
1194
|
+
setCollectionName: (name: string) => void;
|
|
1195
|
+
|
|
1196
|
+
/**
|
|
1197
|
+
* Get the queue prefix for workflow jobs
|
|
1198
|
+
* @returns The queue prefix
|
|
1199
|
+
*/
|
|
1200
|
+
getQueuePrefix: () => string;
|
|
1201
|
+
|
|
1202
|
+
/**
|
|
1203
|
+
* Set the queue prefix for workflow jobs
|
|
1204
|
+
* @param prefix - Queue prefix
|
|
1205
|
+
*/
|
|
1206
|
+
setQueuePrefix: (prefix: string) => void;
|
|
1207
|
+
|
|
1208
|
+
/**
|
|
1209
|
+
* Get the timeout for workflow steps
|
|
1210
|
+
* @returns The timeout in milliseconds
|
|
1211
|
+
*/
|
|
1212
|
+
getTimeout: () => number;
|
|
1213
|
+
|
|
1214
|
+
/**
|
|
1215
|
+
* Set the timeout for workflow steps
|
|
1216
|
+
* @param timeout - Timeout in milliseconds
|
|
1217
|
+
*/
|
|
1218
|
+
setTimeout: (timeout: number) => void;
|
|
1219
|
+
|
|
1220
|
+
/**
|
|
1221
|
+
* Get the maximum step count
|
|
1222
|
+
* @returns The maximum step count
|
|
1223
|
+
*/
|
|
1224
|
+
getMaxStepCount: () => number;
|
|
1225
|
+
|
|
1226
|
+
/**
|
|
1227
|
+
* Set the maximum step count
|
|
1228
|
+
* @param maxStepCount - Maximum step count
|
|
1229
|
+
*/
|
|
1230
|
+
setMaxStepCount: (maxStepCount: number) => void;
|
|
1231
|
+
|
|
1232
|
+
/**
|
|
1233
|
+
* Get the steps configuration
|
|
1234
|
+
* @returns The steps configuration
|
|
1235
|
+
*/
|
|
1236
|
+
getStepsConfig: () => Record<string, StepOptions>;
|
|
1237
|
+
|
|
1238
|
+
/**
|
|
1239
|
+
* Set the steps configuration
|
|
1240
|
+
* @param steps - Steps configuration
|
|
1241
|
+
*/
|
|
1242
|
+
setStepsConfig: (steps: Record<string, StepOptions>) => void;
|
|
1243
|
+
|
|
1244
|
+
/**
|
|
1245
|
+
* Get the step definition for a specific step
|
|
1246
|
+
* @param workflowName - Name of the workflow
|
|
1247
|
+
* @param stepName - Name of the step
|
|
1248
|
+
* @returns The step function
|
|
1249
|
+
*/
|
|
1250
|
+
getDefinition: (workflowName: string, stepName: string) => Function;
|
|
1251
|
+
|
|
1252
|
+
/**
|
|
1253
|
+
* Register a new workflow with a Codehooks application
|
|
1193
1254
|
* @param app - Codehooks application instance
|
|
1194
|
-
* @param name - Unique identifier for the workflow
|
|
1195
|
-
* @param description - Human-readable description
|
|
1196
|
-
* @param definition - Object containing step definitions
|
|
1197
1255
|
* @returns Promise with the registered workflow name
|
|
1198
1256
|
*/
|
|
1199
|
-
register: (app: Codehooks
|
|
1257
|
+
register: (app: Codehooks) => Promise<string>;
|
|
1200
1258
|
|
|
1201
1259
|
/**
|
|
1202
1260
|
* Start a new workflow instance
|
|
1203
|
-
* @param name - Name of the workflow to start
|
|
1204
1261
|
* @param initialState - Initial state for the workflow instance
|
|
1205
|
-
* @returns Promise with the workflow instance
|
|
1262
|
+
* @returns Promise with the workflow instance
|
|
1206
1263
|
*/
|
|
1207
|
-
start: (
|
|
1264
|
+
start: (initialState: any) => Promise<any>;
|
|
1208
1265
|
|
|
1209
1266
|
/**
|
|
1210
1267
|
* Update the state of a workflow instance
|
|
1211
|
-
* @param workflowName - Name of the workflow
|
|
1212
1268
|
* @param instanceId - ID of the workflow instance
|
|
1213
1269
|
* @param state - New state to update with
|
|
1214
1270
|
* @param options - Options for the update, { continue: false } to avoid continuing the step
|
|
1215
1271
|
* @returns Promise with the updated state
|
|
1216
1272
|
*/
|
|
1217
|
-
updateState: (
|
|
1273
|
+
updateState: (instanceId: string, state: any, options?: UpdateOptions) => Promise<any>;
|
|
1274
|
+
|
|
1275
|
+
/**
|
|
1276
|
+
* Set the complete state of a workflow instance
|
|
1277
|
+
* @param instanceId - ID of the workflow instance
|
|
1278
|
+
* @param stateData - Object containing _id and state
|
|
1279
|
+
* @returns Promise<void>
|
|
1280
|
+
*/
|
|
1281
|
+
setState: (instanceId: string, stateData: { _id: string; state: any }) => Promise<void>;
|
|
1218
1282
|
|
|
1219
1283
|
/**
|
|
1220
1284
|
* Continue a paused workflow instance
|
|
1221
|
-
* @param workflowName - Name of the workflow
|
|
1222
1285
|
* @param instanceId - ID of the workflow instance
|
|
1223
1286
|
* @param reset - Whether to reset all step counts (true) or just the current step (false)
|
|
1224
1287
|
* @returns Promise with the queue ID for the continued step
|
|
1225
1288
|
*/
|
|
1226
|
-
continue: (
|
|
1289
|
+
continue: (instanceId: string, reset?: boolean) => Promise<{ instanceId: string }>;
|
|
1227
1290
|
|
|
1228
1291
|
/**
|
|
1229
1292
|
* Get the status of a workflow instance
|
|
1230
1293
|
* @param id - ID of the workflow instance
|
|
1231
1294
|
* @returns Promise with the workflow status
|
|
1232
1295
|
*/
|
|
1233
|
-
|
|
1296
|
+
getStepsStatus: (id: string) => Promise<any>;
|
|
1234
1297
|
|
|
1235
1298
|
/**
|
|
1236
1299
|
* Get all workflow instances matching a filter
|
|
@@ -1244,7 +1307,7 @@ export type Workflow = {
|
|
|
1244
1307
|
* @param id - ID of the workflow instance to cancel
|
|
1245
1308
|
* @returns Promise with the cancellation result
|
|
1246
1309
|
*/
|
|
1247
|
-
|
|
1310
|
+
cancelSteps: (id: string) => Promise<any>;
|
|
1248
1311
|
|
|
1249
1312
|
/**
|
|
1250
1313
|
* Register an event listener
|
|
@@ -1282,7 +1345,7 @@ export type Workflow = {
|
|
|
1282
1345
|
* Continue all timed out workflow instances
|
|
1283
1346
|
* @returns Promise with array of results containing queue IDs for continued workflows
|
|
1284
1347
|
*/
|
|
1285
|
-
continueAllTimedOut: () => Promise<Array<{
|
|
1348
|
+
continueAllTimedOut: () => Promise<Array<{instanceId: string}>>;
|
|
1286
1349
|
|
|
1287
1350
|
/**
|
|
1288
1351
|
* Check if a specific step in a workflow instance has timed out
|
|
@@ -1301,6 +1364,7 @@ export type Workflow = {
|
|
|
1301
1364
|
* // finishTime?: string, // if step has finished
|
|
1302
1365
|
* // currentTime?: string, // if step is still running
|
|
1303
1366
|
* // timeoutSource: 'stepConfig' | 'defaultOptions' | 'globalTimeout'
|
|
1367
|
+
* // reason?: string // if step not found
|
|
1304
1368
|
* // }
|
|
1305
1369
|
*/
|
|
1306
1370
|
isStepTimedOut: (workflow: any) => {
|
|
@@ -1378,11 +1442,14 @@ export type WorkflowEvent =
|
|
|
1378
1442
|
* Data structure for workflow events
|
|
1379
1443
|
*/
|
|
1380
1444
|
export type WorkflowEventData = {
|
|
1381
|
-
workflowName
|
|
1445
|
+
workflowName?: string;
|
|
1446
|
+
name?: string;
|
|
1447
|
+
description?: string;
|
|
1382
1448
|
step?: string;
|
|
1383
1449
|
state?: any;
|
|
1384
1450
|
instanceId?: string;
|
|
1385
1451
|
error?: Error;
|
|
1452
|
+
id?: string;
|
|
1386
1453
|
[key: string]: any;
|
|
1387
1454
|
};
|
|
1388
1455
|
|
package/webserver.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import mime from 'mime';
|
|
2
|
+
import fetch from 'node-fetch';
|
|
3
|
+
import { URL } from 'url';
|
|
2
4
|
|
|
3
5
|
// old and new style support
|
|
4
6
|
function promisify(cb, fn) {
|
|
@@ -222,4 +224,30 @@ async function handlebars(viewFile, data, settings, cb) {
|
|
|
222
224
|
console.error('Handle bars engine error:', err)
|
|
223
225
|
cb(err);
|
|
224
226
|
}
|
|
225
|
-
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Generic fetch wrapper that routes through apigateway-service
|
|
230
|
+
export const internalFetch = (url, options = {}) => {
|
|
231
|
+
// Parse the original URL to extract domain and path
|
|
232
|
+
|
|
233
|
+
const urlObj = new URL(url);
|
|
234
|
+
const originalHost = urlObj.host;
|
|
235
|
+
const pathAndQuery = urlObj.pathname + urlObj.search;
|
|
236
|
+
|
|
237
|
+
// Replace domain with apigateway-service
|
|
238
|
+
const proxyUrl = `http://apigateway-service:8888${pathAndQuery}`;
|
|
239
|
+
|
|
240
|
+
// Merge headers with original host
|
|
241
|
+
const headers = {
|
|
242
|
+
...options.headers,
|
|
243
|
+
'host': originalHost
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// Create new options with updated headers
|
|
247
|
+
const newOptions = {
|
|
248
|
+
...options,
|
|
249
|
+
headers
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
return fetch(proxyUrl, newOptions);
|
|
253
|
+
};
|
package/workflow/engine.mjs
CHANGED
|
@@ -423,17 +423,7 @@ class Workflow extends EventEmitter {
|
|
|
423
423
|
* @private
|
|
424
424
|
*/
|
|
425
425
|
async registerWithApp(app, name, description, definition) {
|
|
426
|
-
this.emit('workflowCreated', { name, description });
|
|
427
|
-
|
|
428
|
-
// Log initial state of definitions Map
|
|
429
|
-
console.debug('Before registration - Current definitions Map:', {
|
|
430
|
-
size: this.#definitions.size,
|
|
431
|
-
keys: Array.from(this.#definitions.keys()),
|
|
432
|
-
entries: Array.from(this.#definitions.entries()).map(([key, value]) => ({
|
|
433
|
-
key,
|
|
434
|
-
stepNames: Object.keys(value)
|
|
435
|
-
}))
|
|
436
|
-
});
|
|
426
|
+
this.emit('workflowCreated', { name, description });
|
|
437
427
|
|
|
438
428
|
// Validate each step in the definition
|
|
439
429
|
for (const [stepName, step] of Object.entries(definition)) {
|
|
@@ -468,63 +458,33 @@ class Workflow extends EventEmitter {
|
|
|
468
458
|
}
|
|
469
459
|
|
|
470
460
|
// Store the definition
|
|
471
|
-
this.#definitions.set(name, definition);
|
|
472
|
-
|
|
473
|
-
// Log state after registration
|
|
474
|
-
console.debug('After registration - Updated definitions Map:', {
|
|
475
|
-
size: this.#definitions.size,
|
|
476
|
-
keys: Array.from(this.#definitions.keys()),
|
|
477
|
-
entries: Array.from(this.#definitions.entries()).map(([key, value]) => ({
|
|
478
|
-
key,
|
|
479
|
-
stepNames: Object.keys(value)
|
|
480
|
-
}))
|
|
481
|
-
});
|
|
461
|
+
this.#definitions.set(name, definition);
|
|
482
462
|
|
|
483
463
|
return name;
|
|
484
464
|
}
|
|
485
465
|
|
|
486
466
|
/**
|
|
487
467
|
* Start a new steps instance
|
|
488
|
-
* @param {string} name - Name of the steps workflow to start
|
|
489
468
|
* @param {Object} initialState - Initial state for the steps instance
|
|
490
469
|
* @returns {Promise<{id: string}>} The steps instance ID
|
|
491
470
|
* @throws {Error} If starting steps fails
|
|
492
471
|
*/
|
|
493
|
-
async start(
|
|
494
|
-
this.emit('workflowStarted', { name, initialState });
|
|
495
|
-
|
|
496
|
-
// Log definitions Map state at start
|
|
497
|
-
console.debug('At workflow start - Current definitions Map:', {
|
|
498
|
-
size: this.#definitions.size,
|
|
499
|
-
keys: Array.from(this.#definitions.keys()),
|
|
500
|
-
entries: Array.from(this.#definitions.entries()).map(([key, value]) => ({
|
|
501
|
-
key,
|
|
502
|
-
stepNames: Object.keys(value)
|
|
503
|
-
}))
|
|
504
|
-
});
|
|
472
|
+
async start(initialState) {
|
|
473
|
+
this.emit('workflowStarted', { name: this.#name, initialState });
|
|
505
474
|
|
|
506
475
|
return new Promise(async (resolve, reject) => {
|
|
507
476
|
try {
|
|
508
|
-
console.debug('Starting workflow', name);
|
|
509
|
-
const funcs = this.#definitions.get(name);
|
|
510
|
-
|
|
511
|
-
exists: !!funcs,
|
|
512
|
-
stepNames: funcs ? Object.keys(funcs) : []
|
|
513
|
-
});
|
|
514
|
-
|
|
477
|
+
console.debug('Starting workflow', this.#name);
|
|
478
|
+
const funcs = this.#definitions.get(this.#name);
|
|
479
|
+
|
|
515
480
|
if (!funcs) {
|
|
516
|
-
reject(new Error(`No workflow definition found for: ${name}`));
|
|
481
|
+
reject(new Error(`No workflow definition found for: ${this.#name}`));
|
|
517
482
|
return;
|
|
518
483
|
}
|
|
519
484
|
|
|
520
485
|
const firstStepName = Object.keys(funcs)[0];
|
|
521
486
|
const firstStep = funcs[firstStepName];
|
|
522
|
-
|
|
523
|
-
name: firstStepName,
|
|
524
|
-
exists: !!firstStep,
|
|
525
|
-
type: firstStep ? typeof firstStep : 'undefined'
|
|
526
|
-
});
|
|
527
|
-
|
|
487
|
+
|
|
528
488
|
if (!firstStep) {
|
|
529
489
|
reject(new Error('No start step defined in workflow'));
|
|
530
490
|
return;
|
|
@@ -533,9 +493,9 @@ class Workflow extends EventEmitter {
|
|
|
533
493
|
const connection = await Datastore.open();
|
|
534
494
|
// Create a new workflow state in the database
|
|
535
495
|
const newState = await connection.insertOne(this.#collectionName,
|
|
536
|
-
{ ...initialState, nextStep: firstStepName, createdAt: new Date().toISOString(), workflowName: name, stepCount: { } });
|
|
537
|
-
const { _id: ID } = await connection.enqueue(`${this.#queuePrefix}_${name}_${firstStepName}`, {
|
|
538
|
-
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,
|
|
539
499
|
goto: firstStepName,
|
|
540
500
|
state: newState,
|
|
541
501
|
instanceId: newState._id,
|
|
@@ -551,21 +511,20 @@ class Workflow extends EventEmitter {
|
|
|
551
511
|
|
|
552
512
|
/**
|
|
553
513
|
* Update the state of a workflow instance
|
|
554
|
-
* @param {string} stepsName - Name of the workflow
|
|
555
514
|
* @param {string} instanceId - ID of the workflow instance
|
|
556
515
|
* @param {Object} state - New state to update with
|
|
557
516
|
* @param {Object} options - Options for the update, { continue: false } to avoid continuing the the step
|
|
558
517
|
* @returns {Promise<Object>} The updated state
|
|
559
518
|
*/
|
|
560
|
-
async updateState(
|
|
561
|
-
this.emit('stepsStateUpdating', {
|
|
519
|
+
async updateState(instanceId, state, options={continue: true}) {
|
|
520
|
+
this.emit('stepsStateUpdating', { name: this.#name, instanceId, state });
|
|
562
521
|
const connection = await Datastore.open();
|
|
563
522
|
return new Promise(async (resolve, reject) => {
|
|
564
523
|
const doc = await connection.updateOne(this.#collectionName,
|
|
565
524
|
{ _id: instanceId },
|
|
566
525
|
{ $set: { ...state, updatedAt: new Date().toISOString() } });
|
|
567
526
|
if (options.continue) {
|
|
568
|
-
await this.continue(
|
|
527
|
+
await this.continue(instanceId, false);
|
|
569
528
|
}
|
|
570
529
|
resolve({ ...doc });
|
|
571
530
|
});
|
|
@@ -584,12 +543,11 @@ class Workflow extends EventEmitter {
|
|
|
584
543
|
|
|
585
544
|
/**
|
|
586
545
|
* Continue a paused steps instance
|
|
587
|
-
* @param {string} stepsName - Name of the steps workflow
|
|
588
546
|
* @param {string} instanceId - ID of the steps instance
|
|
589
547
|
* @returns {Promise<{qId: string}>} Queue ID for the continued step
|
|
590
548
|
* @throws {Error} If steps instance not found
|
|
591
549
|
*/
|
|
592
|
-
async continue(
|
|
550
|
+
async continue(instanceId, reset=false) {
|
|
593
551
|
const connection = await Datastore.open();
|
|
594
552
|
const state = await connection.findOne(this.#collectionName, { _id: instanceId });
|
|
595
553
|
if (!state) {
|
|
@@ -608,11 +566,11 @@ class Workflow extends EventEmitter {
|
|
|
608
566
|
|
|
609
567
|
await connection.updateOne(this.#collectionName, { _id: instanceId }, { $set: { stepCount: state.stepCount } });
|
|
610
568
|
console.debug('continue state', state);
|
|
611
|
-
this.emit('workflowContinued', {
|
|
569
|
+
this.emit('workflowContinued', { name: this.#name, step: state.nextStep, instanceId });
|
|
612
570
|
|
|
613
571
|
return new Promise(async (resolve, reject) => {
|
|
614
|
-
const { _id: ID } = await connection.enqueue(`${this.#queuePrefix}_${
|
|
615
|
-
stepsName,
|
|
572
|
+
const { _id: ID } = await connection.enqueue(`${this.#queuePrefix}_${this.#name}_${state.nextStep}`, {
|
|
573
|
+
stepsName: this.#name,
|
|
616
574
|
goto: state.nextStep,
|
|
617
575
|
state: state,
|
|
618
576
|
options: {},
|
|
@@ -638,7 +596,7 @@ class Workflow extends EventEmitter {
|
|
|
638
596
|
if (diffMillis > this.#timeout) {
|
|
639
597
|
const diffMinutes = diffMillis / (1000 * 60);
|
|
640
598
|
console.log('Timed out:', workflow._id, workflow.nextStep, `(${diffMinutes.toFixed(1)} minutes old)`);
|
|
641
|
-
const result = await this.continue(workflow.
|
|
599
|
+
const result = await this.continue(workflow._id, true);
|
|
642
600
|
console.log('Continued:', result._id);
|
|
643
601
|
results.push(result);
|
|
644
602
|
}
|