@pxp-nd/flow 1.1.29 → 1.1.31

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 CHANGED
@@ -1 +1,437 @@
1
- # flow-nd
1
+ # Tempo Flows (Workflow Engine)
2
+
3
+ This document describes how automation **flows** work in Tempo: database structure, runtime pipeline, and how to trigger or assemble them.
4
+
5
+ Flows are a **node-graph automation engine**. Definitions and runtime state live in MySQL tables prefixed `twf_*`. Core entities and graph execution come from **`@pxp-nd/flow`**. Tempo adds business orchestration via `EventHq`, `FlowHQ`, and an SQS worker.
6
+
7
+ ---
8
+
9
+ ## High-level architecture
10
+
11
+ ```text
12
+ Business code (controllers / PHP jobs)
13
+ │
14
+ ▼
15
+ EventHq.add({ actionId, descJobName, dataId })
16
+ │
17
+ ├─► twf_event (status: pending)
18
+ └─► SQS FIFO (FLOW_QUEUE_URL)
19
+ │
20
+ ▼
21
+ worker.ts → EventHq.processSingleEvent({ eventId })
22
+ │
23
+ ├─► Find active twf_node rows for actionId
24
+ ├─► Query origin view (origin_name + origin_key = dataId)
25
+ ├─► Vendor match + event_config conditions
26
+ ├─► Create twf_flow_instance
27
+ └─► NodeInstanceController.RecursiveInstance() walks the DAG
28
+ ```
29
+
30
+ ### Key Tempo files
31
+
32
+ | File | Role |
33
+ |------|------|
34
+ | `src/modules/hq-nd/controllers/EventHq.ts` | Enqueue events to SQS; process a single event into flow instances |
35
+ | `src/modules/hq-nd/controllers/Flow.ts` (`FlowHQ`) | Duplicate templates, chained flows, funnel lists/statistics |
36
+ | `src/worker.ts` | SQS consumer (started when `INIT_FLOW_WORKER=Y`) |
37
+ | `src/config.ts` / `ormconfig.js` | Registers `flow-nd` module and merges flow entities |
38
+
39
+ Entities such as `Flow`, `Node`, `Action`, `Event`, `FlowInstance`, `NodeInstance` are **not** redefined in Tempo; they come from `@pxp-nd/flow`.
40
+
41
+ ---
42
+
43
+ ## How execution works (summary)
44
+
45
+ 1. **Fire an event**
46
+ Application code looks up `twf_action` by stable `code`, then calls `EventHq.add` with:
47
+ - `actionId` — PK of the action
48
+ - `dataId` — PK value in the action’s origin view (`origin_key`)
49
+ - `descJobName` — caller context for debugging
50
+
51
+ 2. **Enqueue**
52
+ `EventHq.add` inserts a `twf_event` (`status = pending`) and sends a message to SQS containing `eventId`.
53
+
54
+ 3. **Worker**
55
+ `pollQueue()` in `src/worker.ts` receives the message, deletes it from the queue, then calls `processSingleEvent` with retries (1s / 5s / 10s / 20s). Failures after retries go to Slack (`SLACK_TEMPO_ERROR`).
56
+
57
+ 4. **Match flows**
58
+ For every active `twf_node` with that `action_id`:
59
+ - Load one row: `SELECT * FROM {origin_name} WHERE {origin_key} = {dataId}`
60
+ - Load parent `twf_flow` — must be `is_active = 1` and **`status = 'on'`**
61
+ - Vendor filter: `flow.vendor_id === view.vendor_id` **OR** `flow.vendor_id === 55` (global Qorus flows)
62
+ - Optional `event_config` filters on the action/node
63
+
64
+ 5. **Run the graph**
65
+ Create `twf_flow_instance`, then walk `twf_node` / `twf_node_connection` via `RecursiveInstance()`. Each step creates `twf_node_instance` rows. Errors are stored in `twf_flow_log`.
66
+
67
+ ---
68
+
69
+ ## Database ER diagram
70
+
71
+ ```mermaid
72
+ erDiagram
73
+ twf_action_type ||--o{ twf_action : "categorizes"
74
+ twf_action ||--o{ twf_node : "used by"
75
+ twf_action ||--o{ twf_event : "triggered as"
76
+ twf_action ||--o{ twf_flow_instance : "started by"
77
+ twf_flow ||--o{ twf_node : "contains"
78
+ twf_flow ||--o{ twf_flow_instance : "instantiates"
79
+ twf_node ||--o{ twf_node_connection : "master"
80
+ twf_node ||--o{ twf_node_connection : "child"
81
+ twf_node ||--o{ twf_node_instance : "executes as"
82
+ twf_flow_instance ||--o{ twf_node_instance : "has steps"
83
+ twf_origin_name ||--o{ twf_field_map : "maps fields"
84
+
85
+ twf_action_type {
86
+ int action_type_id PK
87
+ varchar name
88
+ varchar schema_json
89
+ varchar is_start
90
+ varchar is_delay
91
+ varchar controller
92
+ varchar on_duplicate
93
+ tinyint is_active
94
+ }
95
+
96
+ twf_action {
97
+ int action_id PK
98
+ int action_type_id FK
99
+ varchar code "stable contract"
100
+ varchar name
101
+ varchar origin_name "DB view"
102
+ varchar origin_key "PK column in view"
103
+ varchar controller_function
104
+ varchar schema_json
105
+ varchar event_config
106
+ varchar config_json_template
107
+ tinyint is_active
108
+ }
109
+
110
+ twf_flow {
111
+ int flow_id PK
112
+ int vendor_id "0/55 template or vendor"
113
+ varchar code
114
+ varchar name
115
+ varchar type "template|custom"
116
+ varchar template_type "funnel|flow|null"
117
+ varchar status "must be on to run"
118
+ varchar enabled
119
+ tinyint is_active
120
+ varchar publish
121
+ varchar flow_type
122
+ }
123
+
124
+ twf_node {
125
+ int node_id PK
126
+ int flow_id FK
127
+ int action_id FK
128
+ varchar is_init "Y = entry/trigger"
129
+ varchar is_end
130
+ varchar action_config_json "runtime values"
131
+ varchar approval_config
132
+ tinyint is_active
133
+ }
134
+
135
+ twf_node_connection {
136
+ int node_connection_id PK
137
+ int node_id_master "NULL = init edge"
138
+ int node_id_child FK
139
+ varchar condition
140
+ tinyint is_active
141
+ }
142
+
143
+ twf_event {
144
+ int event_id PK
145
+ int action_id FK
146
+ int data_id "origin PK value"
147
+ varchar desc_job_name
148
+ varchar status "pending|processed"
149
+ tinyint is_active
150
+ }
151
+
152
+ twf_flow_instance {
153
+ int flow_instance_id PK
154
+ int flow_id FK
155
+ int data_id
156
+ int action_id
157
+ varchar origin_name
158
+ varchar origin_key
159
+ varchar status
160
+ varchar flag_name
161
+ int flag_value
162
+ tinyint is_active
163
+ }
164
+
165
+ twf_node_instance {
166
+ int node_instance_id PK
167
+ int node_id FK
168
+ int flow_instance_id FK
169
+ timestamp run_time
170
+ timestamp schedule
171
+ varchar status
172
+ json extra_args
173
+ tinyint is_active
174
+ }
175
+
176
+ twf_origin_name {
177
+ int origin_name_id PK
178
+ varchar name "view name"
179
+ varchar originKey
180
+ tinyint is_active
181
+ }
182
+
183
+ twf_field_map {
184
+ int field_map_id PK
185
+ int origin_name_id FK
186
+ varchar name
187
+ varchar alias
188
+ varchar type
189
+ tinyint is_active
190
+ }
191
+
192
+ twf_flow_log {
193
+ int flow_log_id PK
194
+ varchar error_custom_message
195
+ varchar error_message
196
+ text stack_trace
197
+ tinyint is_active
198
+ }
199
+ ```
200
+
201
+ > Note: Some FK relationships are logical (enforced in application / TypeORM) rather than hard MySQL foreign keys on every column.
202
+
203
+ ---
204
+
205
+ ## Runtime pipeline diagram
206
+
207
+ ```mermaid
208
+ flowchart LR
209
+ A[Business code] -->|Action.findOne by code| B[EventHq.add]
210
+ B --> C[(twf_event pending)]
211
+ B --> D[SQS FLOW_QUEUE_URL]
212
+ D --> E[worker.ts pollQueue]
213
+ E --> F[EventHq.processSingleEvent]
214
+ F --> G{Active nodes for actionId}
215
+ G --> H[Query origin view by dataId]
216
+ H --> I{flow status=on<br/>vendor match or vendor 55}
217
+ I -->|yes| J[(twf_flow_instance)]
218
+ J --> K[RecursiveInstance DAG walk]
219
+ K --> L[(twf_node_instance)]
220
+ I -->|no| M[Skip node]
221
+ F -.->|errors| N[(twf_flow_log)]
222
+ ```
223
+
224
+ ---
225
+
226
+ ## Table reference
227
+
228
+ | Table | Role |
229
+ |-------|------|
230
+ | `twf_action_type` | Category of step (EVENT, EMAIL, DELAY, PAGE, CHAINED FLOW, CONTROLLER EXECUTION, …) |
231
+ | `twf_action` | Reusable action / event definition; stable API via `code` |
232
+ | `twf_flow` | Flow definition (template or vendor custom instance) |
233
+ | `twf_node` | One step in a flow; points at one action; holds `action_config_json` |
234
+ | `twf_node_connection` | Directed edges (DAG). Init connection uses `node_id_master = NULL` |
235
+ | `twf_event` | Async event queue row |
236
+ | `twf_flow_instance` | One execution of a flow for a given `dataId` |
237
+ | `twf_node_instance` | Per-step execution record |
238
+ | `twf_origin_name` | Registry of DB views used as event data sources |
239
+ | `twf_field_map` | Column aliases for Harmony builder / templating |
240
+ | `twf_flow_log` | Execution / processing errors |
241
+
242
+ ### Important `twf_flow` fields
243
+
244
+ | Field | Meaning |
245
+ |-------|---------|
246
+ | `type` | `template` (blueprint) or `custom` (vendor instance) |
247
+ | `template_type` | `funnel`, `flow`, or null |
248
+ | `status` | Must be **`on`** for `EventHq` to execute the flow |
249
+ | `vendor_id` | `0` / `55` for global templates; vendor id for instances. **Vendor 55** matches any vendor’s events |
250
+ | `code` | Optional stable identifier (e.g. `NEW_EMAIL_LIST`) |
251
+
252
+ ### Important `twf_action` fields
253
+
254
+ | Field | Meaning |
255
+ |-------|---------|
256
+ | `code` | Unique string — always resolve with `Action.findOne({ code })` |
257
+ | `origin_name` / `origin_key` | View + PK used when resolving `dataId` |
258
+ | `schema_json` | UI form for Harmony flow builder |
259
+ | `event_config` | Optional filters: `{ "filters": [{ "originField", "nodeField" }] }` |
260
+ | `controller_function` | Tempo controller path for CONTROLLER EXECUTION (e.g. `hq-nd/BusinessTypeSF/sendApprovalNotification`) |
261
+
262
+ ### Common `action_type_id` values (active)
263
+
264
+ | ID | Name | Notes |
265
+ |----|------|--------|
266
+ | 9 | EVENT | Trigger / listener node |
267
+ | 10 | EMAIL NOTIFICATION | Send email |
268
+ | 11 | DELAY | Wait step |
269
+ | 16 | PAGE | Opt-in / bridge pages |
270
+ | 24 | CHAINED FLOW | Invoke another flow |
271
+ | 26 | PUSH NOTIFICATION | Mobile push |
272
+ | 28 | CONTROLLER EXECUTION | Call a Tempo controller method |
273
+
274
+ `on_duplicate` on the action type drives post-processing when duplicating a flow (`duplicateEmailTemplate`, `duplicatePageTemplate`, `duplicateChainedFlow`).
275
+
276
+ ---
277
+
278
+ ## JSON configuration layers
279
+
280
+ 1. **`twf_action_type.schema_json`** — shared UI schema for the type
281
+ 2. **`twf_action.schema_json`** — per-action UI overrides
282
+ 3. **`twf_node.action_config_json`** — **actual values at runtime** for that node
283
+
284
+ Examples of `action_config_json`:
285
+
286
+ | Type | Typical keys |
287
+ |------|----------------|
288
+ | PAGE | `templateBuilderId`, `emailListId`, `smsListId`, `title` |
289
+ | EMAIL | `to`, `subject`, `templateId` |
290
+ | DELAY | `quantity`, `type` (`hours` / `minutes` / `days`) |
291
+ | CHAINED / EMAIL_JOURNEY | `flowId` |
292
+ | CONTROLLER / push (SF) | `subject`, `message`, `mobileActionId`, `notificationType` |
293
+
294
+ Templates may use `{{ FIELD }}` placeholders resolved from the origin view row (`config_json_template` / node config).
295
+
296
+ ---
297
+
298
+ ## Triggering a flow from TypeScript
299
+
300
+ ```typescript
301
+ import { Action, Event } from '@pxp-nd/flow';
302
+ import EventHq from './EventHq';
303
+
304
+ const action = await Action.findOne({ code: 'ADD EMAIL LIST ADDRESS' });
305
+ if (action) {
306
+ const eventInstance = new EventHq('hq-nd/EventHq', Event);
307
+ await eventInstance.add({
308
+ actionId: action.actionId,
309
+ descJobName: 'formLegato', // caller context
310
+ dataId: resInsert.emailListAddrId, // PK in action.origin_name
311
+ }, manager);
312
+ }
313
+ ```
314
+
315
+ Rules:
316
+
317
+ - Prefer **`EventHq.add()`** in Tempo (enqueues SQS). Do not bypass the queue for production event firing.
318
+ - Never hardcode `action_id`; always look up by `code`.
319
+ - `dataId` must exist in the action’s origin view.
320
+ - Flow must be `status = 'on'` and `is_active = 1`.
321
+
322
+ Events may also be fired from legacy PHP (e.g. billing jobs calling `hq-nd/EventHq/add`).
323
+
324
+ ---
325
+
326
+ ## Assembling / creating flows
327
+
328
+ ### A. Duplicate a template (preferred for vendor instances)
329
+
330
+ ```typescript
331
+ const flowHq = new FlowHQ('hq-nd/FlowHQ');
332
+ const result = await flowHq.duplicateFlow(
333
+ { flowId: templateFlowId, vendorId, name: 'Bridge Funnel' },
334
+ manager
335
+ );
336
+ // Activate if needed:
337
+ await manager.createQueryBuilder().update('twf_flow')
338
+ .set({ status: 'on' })
339
+ .where('flowId = :flowId', { flowId })
340
+ .execute();
341
+ ```
342
+
343
+ Template IDs often come from env:
344
+
345
+ - `TEMPLATE_BRIDGE_FUNNEL_FLOW_ID` (default `149`)
346
+ - `TEMPLATE_QORD_BRIDGE_FUNNEL_FLOW_ID` (default `3419`)
347
+
348
+ ### B. Patch init node after duplicate
349
+
350
+ ```typescript
351
+ await manager.createQueryBuilder().update('twf_node')
352
+ .set({ actionConfigJson: JSON.stringify({ emailListId }) })
353
+ .where('flowId = :flowId AND isInit = :isInit', { flowId, isInit: 'Y' })
354
+ .execute();
355
+ ```
356
+
357
+ ### C. Chained funnel → email journey
358
+
359
+ Use `FlowHQ.addChainedFlow()` (duplicates child flow, copies `emailListId`, activates).
360
+
361
+ ### D. SQL registration
362
+
363
+ Typical order:
364
+
365
+ 1. Optional `twf_action_type`
366
+ 2. `twf_action` (EVENT + step actions) with `WHERE NOT EXISTS (... code ...)`
367
+ 3. If new origin: `twf_origin_name` + `twf_field_map`
368
+ 4. Optional full graph: `twf_flow` → init `twf_node` → connections → step nodes
369
+ 5. Register in `src/modules/hq-nd/database/jaime-scripts.ts` as `JRR-HQ-[STORY]-[SEQ]`
370
+
371
+ Modern SF-style DML often registers **actions only** and lets the Harmony UI assemble the graph.
372
+
373
+ ---
374
+
375
+ ## Vendor matching (important)
376
+
377
+ When processing an event, Tempo runs a flow if:
378
+
379
+ ```text
380
+ flow.vendorId === viewVendorId OR flow.vendorId === 55
381
+ ```
382
+
383
+ So:
384
+
385
+ - A **vendor-specific** flow only runs for that vendor’s data.
386
+ - A **vendor 55 (Qorus)** flow runs for **any** vendor that fires the matching event (global notifications).
387
+
388
+ ---
389
+
390
+ ## Environment variables
391
+
392
+ | Variable | Purpose |
393
+ |----------|---------|
394
+ | `FLOW_QUEUE_URL` | SQS FIFO queue for async events |
395
+ | `INIT_FLOW_WORKER` | `Y` starts `pollQueue()` from `server.ts` |
396
+ | `TEMPLATE_BRIDGE_FUNNEL_FLOW_ID` | Default bridge funnel template |
397
+ | `TEMPLATE_QORD_BRIDGE_FUNNEL_FLOW_ID` | Qord bridge funnel template |
398
+ | `SLACK_TEMPO_ERROR` | Channel for worker failures after retries |
399
+
400
+ ---
401
+
402
+ ## Naming conventions
403
+
404
+ - Action `code`: UPPER CASE with spaces — e.g. `NEW POSTED NEED`, `BILLING NOT PROCESSED`, `SF SEND COS PUSH`
405
+ - Funnel templates: `type = 'template'`, `template_type = 'funnel'`
406
+ - Email / automation templates: `type = 'template'`, `template_type = 'flow'` (or null)
407
+ - Vendor copies after `duplicateFlow`: `type = 'custom'`
408
+
409
+ ---
410
+
411
+ ## Do / Don’t
412
+
413
+ **Do**
414
+
415
+ - Find actions by `code`
416
+ - Duplicate templates for vendor flows; keep them `status = 'on'` when they should run
417
+ - Register new origins in `twf_origin_name` (+ field maps) before using them in actions
418
+ - Use `EventHq.add` so processing goes through SQS
419
+
420
+ **Don’t**
421
+
422
+ - Hardcode `action_id`
423
+ - Auto-sync `twf_*` schema (`synchronize` stays false)
424
+ - Skip SQS for production event firing
425
+ - Hand-build full vendor graphs in TypeScript when a template + `duplicateFlow` exists
426
+
427
+ ---
428
+
429
+ ## Controllers that commonly fire events
430
+
431
+ Examples in Tempo: `Site`, `Vendor`, `Member`, `Package`, `MemberAccounts`, `EmailList`, `PageTemplate`, `Extra`, `CloudFrontFlow`, mobile `Subscriptions`, `helpers/Site.ts`. Legacy Harmony PHP jobs may also call `EventHq/add` (e.g. billing).
432
+
433
+ ---
434
+
435
+ ## Related Cursor rules
436
+
437
+ Project conventions for flows are also summarized in `.cursorrules` under **Flows (Workflow Engine)**. Keep this document and those rules aligned when changing the event pipeline or `twf_*` contracts.
@@ -9,7 +9,7 @@
9
9
  * @author Favio Figueroa
10
10
  *
11
11
  * Created at : 2021-07-08 12:55:38
12
- * Last modified :
12
+ * Last modified : 2026-11-06 — Rensi Arteaga — @ApiDoc for OpenAPI (spec 04)
13
13
  */
14
14
  import { EntityManager } from 'typeorm';
15
15
  import { Controller } from '@pxp-nd/core';
@@ -10,7 +10,7 @@
10
10
  * @author Favio Figueroa
11
11
  *
12
12
  * Created at : 2021-07-08 12:55:38
13
- * Last modified :
13
+ * Last modified : 2026-11-06 — Rensi Arteaga — @ApiDoc for OpenAPI (spec 04)
14
14
  */
15
15
  var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
16
16
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -30,9 +30,13 @@ const core_1 = require("@pxp-nd/core");
30
30
  const Action_1 = __importDefault(require("../entity/Action"));
31
31
  const FieldMap_1 = __importDefault(require("../entity/FieldMap"));
32
32
  const OriginName_1 = __importDefault(require("../entity/OriginName"));
33
+ const FlowNdApiDocs_1 = require("../helpers/FlowNdApiDocs");
33
34
  let Action = class Action extends core_1.Controller {
34
35
  async getFieldMapByAction(params, manager) {
35
36
  const { originName } = params;
37
+ if (originName == null || String(originName).trim() === '') {
38
+ throw new core_1.PxpError(400, 'originName is required');
39
+ }
36
40
  const fieldMapData = await (0, core_1.__)(manager.createQueryBuilder(FieldMap_1.default, 'fm')
37
41
  .innerJoin(OriginName_1.default, 'on', 'on.originNameId = fm.originNameId')
38
42
  .where("on.name = :n ", { n: originName })
@@ -61,8 +65,9 @@ let Action = class Action extends core_1.Controller {
61
65
  __decorate([
62
66
  (0, core_1.Get)(),
63
67
  (0, core_1.DbSettings)('Orm'),
64
- (0, core_1.ReadOnly)(false),
68
+ (0, core_1.ReadOnly)(true),
65
69
  (0, core_1.Log)(true),
70
+ (0, core_1.ApiDoc)(FlowNdApiDocs_1.actionGetFieldMapByActionDoc),
66
71
  __metadata("design:type", Function),
67
72
  __metadata("design:paramtypes", [Object, typeorm_1.EntityManager]),
68
73
  __metadata("design:returntype", Promise)
@@ -72,6 +77,7 @@ __decorate([
72
77
  (0, core_1.DbSettings)('Orm'),
73
78
  (0, core_1.ReadOnly)(true),
74
79
  (0, core_1.Log)(true),
80
+ (0, core_1.ApiDoc)(FlowNdApiDocs_1.actionGetActionsDoc),
75
81
  __metadata("design:type", Function),
76
82
  __metadata("design:paramtypes", [Object, typeorm_1.EntityManager]),
77
83
  __metadata("design:returntype", Promise)
@@ -19,6 +19,7 @@
19
19
  * 28-Sep-2023 SP06OCT23 Mercedes Zambrana Add Validation when change off status (validateFlow, deleteflow, saveFlow, saveflowName)
20
20
  * 07-Feb-2025 8353064698 Mercedes Zambrana Add validation for opt in page when flow si funnel change
21
21
  * 18-Feb-2025 8501163144 Mercedes Zambrana Add publish filter
22
+ * 06-Nov-2026 Rensi Arteaga @ApiDoc for OpenAPI (spec 04)
22
23
  * ******************************************************************************
23
24
  */
24
25
  import { EntityManager } from 'typeorm';
@@ -20,6 +20,7 @@
20
20
  * 28-Sep-2023 SP06OCT23 Mercedes Zambrana Add Validation when change off status (validateFlow, deleteflow, saveFlow, saveflowName)
21
21
  * 07-Feb-2025 8353064698 Mercedes Zambrana Add validation for opt in page when flow si funnel change
22
22
  * 18-Feb-2025 8501163144 Mercedes Zambrana Add publish filter
23
+ * 06-Nov-2026 Rensi Arteaga @ApiDoc for OpenAPI (spec 04)
23
24
  * ******************************************************************************
24
25
  */
25
26
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
@@ -69,8 +70,13 @@ const FlowInstance_1 = __importDefault(require("../entity/FlowInstance"));
69
70
  const Node_2 = __importDefault(require("./Node"));
70
71
  const axios_1 = __importDefault(require("axios"));
71
72
  //import {configFacebookStrategy} from "./passport-facebook";
73
+ const FlowNdApiDocs_1 = require("../helpers/FlowNdApiDocs");
72
74
  let Flow = class Flow extends core_1.Controller {
73
75
  async get(params) {
76
+ const fid = Number(params.flowId);
77
+ if (!Number.isInteger(fid) || fid < 1) {
78
+ throw new core_1.PxpError(400, 'flowId is required and must be a positive integer');
79
+ }
74
80
  const nodes = await Node_1.default.find({ flowId: params.flowId });
75
81
  const restNodes = [];
76
82
  for (let node of nodes) {
@@ -477,6 +483,10 @@ let Flow = class Flow extends core_1.Controller {
477
483
  }
478
484
  async getFlowRender(params) {
479
485
  var _a;
486
+ const fid = Number(params.flowId);
487
+ if (!Number.isInteger(fid) || fid < 1) {
488
+ throw new core_1.PxpError(400, 'flowId is required and must be a positive integer');
489
+ }
480
490
  const flow = await (0, typeorm_1.getManager)().findOne(Flow_1.default, params.flowId);
481
491
  const totalNodes = await (0, typeorm_1.getManager)()
482
492
  .createQueryBuilder(Node_1.default, "n")
@@ -782,6 +792,15 @@ let Flow = class Flow extends core_1.Controller {
782
792
  const filter = (params === null || params === void 0 ? void 0 : params.filter) ? JSON.parse(params === null || params === void 0 ? void 0 : params.filter) : null;
783
793
  const filters = (filter === null || filter === void 0 ? void 0 : filter.items) || [];
784
794
  const logicOperator = ((_a = filter === null || filter === void 0 ? void 0 : filter.logicOperator) === null || _a === void 0 ? void 0 : _a.toUpperCase()) || 'AND';
795
+ const allowedSort = ['flowId', 'name', 'createdAt', 'modifiedAt'];
796
+ const allowedDir = ['ASC', 'DESC'];
797
+ const sortCol = params.sort != null && params.sort !== '' ? String(params.sort) : 'flowId';
798
+ const dirVal = params.dir != null && params.dir !== ''
799
+ ? String(params.dir).toUpperCase()
800
+ : 'ASC';
801
+ if (!allowedSort.includes(sortCol) || !allowedDir.includes(dirVal)) {
802
+ throw new core_1.PxpError(400, 'sort and dir must be allowed values (whitelist for SQL orderBy)');
803
+ }
785
804
  const queryBuilder = await (0, typeorm_1.getManager)()
786
805
  .createQueryBuilder()
787
806
  .select([
@@ -888,7 +907,7 @@ let Flow = class Flow extends core_1.Controller {
888
907
  // Sorting and pagination
889
908
  //queryBuilder.orderBy(`f.${params.sort}`, params.dir).skip(params.start).take(params.limit);
890
909
  // Sorting and pagination
891
- queryBuilder.orderBy(`f.${params.sort}`, params.dir);
910
+ queryBuilder.orderBy(`f.${sortCol}`, dirVal);
892
911
  // Agrega manualmente la cláusula LIMIT y OFFSET
893
912
  queryBuilder.limit(params.limit).offset(params.start);
894
913
  const data = await queryBuilder.getRawMany();
@@ -929,6 +948,15 @@ let Flow = class Flow extends core_1.Controller {
929
948
  const filters = (filter === null || filter === void 0 ? void 0 : filter.items) || [];
930
949
  const logicOperator = ((_a = filter === null || filter === void 0 ? void 0 : filter.logicOperator) === null || _a === void 0 ? void 0 : _a.toUpperCase()) || "AND";
931
950
  const publish = params._publish;
951
+ const allowedSort = ["flowId", "name", "createdAt", "modifiedAt"];
952
+ const allowedDir = ["ASC", "DESC"];
953
+ const sortCol = params.sort != null && params.sort !== "" ? String(params.sort) : "flowId";
954
+ const dirVal = params.dir != null && params.dir !== ""
955
+ ? String(params.dir).toUpperCase()
956
+ : "ASC";
957
+ if (!allowedSort.includes(sortCol) || !allowedDir.includes(dirVal)) {
958
+ throw new core_1.PxpError(400, "sort and dir must be allowed values (whitelist for SQL orderBy)");
959
+ }
932
960
  const queryBuilder = await (0, typeorm_1.getManager)()
933
961
  .createQueryBuilder()
934
962
  .select([
@@ -1040,7 +1068,7 @@ let Flow = class Flow extends core_1.Controller {
1040
1068
  "f.status",
1041
1069
  ]);
1042
1070
  // Ordenamiento y paginación
1043
- queryBuilder.orderBy(`f.${params.sort || "flowId"}`, params.dir || "ASC");
1071
+ queryBuilder.orderBy(`f.${sortCol}`, dirVal);
1044
1072
  queryBuilder.limit(params.limit || 50).offset(params.start || 0);
1045
1073
  // Obtener los datos
1046
1074
  const data = await queryBuilder.getRawMany();
@@ -1117,6 +1145,7 @@ __decorate([
1117
1145
  (0, core_1.DbSettings)('Orm'),
1118
1146
  (0, core_1.ReadOnly)(true),
1119
1147
  (0, core_1.Log)(true),
1148
+ (0, core_1.ApiDoc)(FlowNdApiDocs_1.flowGetDoc),
1120
1149
  __metadata("design:type", Function),
1121
1150
  __metadata("design:paramtypes", [Object]),
1122
1151
  __metadata("design:returntype", Promise)
@@ -1135,6 +1164,7 @@ __decorate([
1135
1164
  (0, core_1.DbSettings)('Orm'),
1136
1165
  (0, core_1.ReadOnly)(false),
1137
1166
  (0, core_1.Log)(true),
1167
+ (0, core_1.ApiDoc)(FlowNdApiDocs_1.flowDeleteFlowDoc),
1138
1168
  __metadata("design:type", Function),
1139
1169
  __metadata("design:paramtypes", [Object, typeorm_1.EntityManager]),
1140
1170
  __metadata("design:returntype", Promise)
@@ -1144,6 +1174,7 @@ __decorate([
1144
1174
  (0, core_1.DbSettings)('Orm'),
1145
1175
  (0, core_1.ReadOnly)(false),
1146
1176
  (0, core_1.Log)(true),
1177
+ (0, core_1.ApiDoc)(FlowNdApiDocs_1.flowSaveFlowNameDoc),
1147
1178
  __metadata("design:type", Function),
1148
1179
  __metadata("design:paramtypes", [Object, typeorm_1.EntityManager]),
1149
1180
  __metadata("design:returntype", Promise)
@@ -1162,6 +1193,7 @@ __decorate([
1162
1193
  (0, core_1.DbSettings)('Orm'),
1163
1194
  (0, core_1.ReadOnly)(false),
1164
1195
  (0, core_1.Log)(true),
1196
+ (0, core_1.ApiDoc)(FlowNdApiDocs_1.flowSaveFlowDoc),
1165
1197
  __metadata("design:type", Function),
1166
1198
  __metadata("design:paramtypes", [Object, typeorm_1.EntityManager]),
1167
1199
  __metadata("design:returntype", Promise)
@@ -1172,6 +1204,7 @@ __decorate([
1172
1204
  (0, core_1.DbSettings)('Orm'),
1173
1205
  (0, core_1.ReadOnly)(true),
1174
1206
  (0, core_1.Log)(true),
1207
+ (0, core_1.ApiDoc)(FlowNdApiDocs_1.flowGetFlowRenderDoc),
1175
1208
  __metadata("design:type", Function),
1176
1209
  __metadata("design:paramtypes", [Object]),
1177
1210
  __metadata("design:returntype", Promise)
@@ -1181,6 +1214,7 @@ __decorate([
1181
1214
  (0, core_1.DbSettings)('Orm'),
1182
1215
  (0, core_1.ReadOnly)(false),
1183
1216
  (0, core_1.Log)(true),
1217
+ (0, core_1.ApiDoc)(FlowNdApiDocs_1.flowInsertEventFlowDoc),
1184
1218
  __metadata("design:type", Function),
1185
1219
  __metadata("design:paramtypes", [Object, typeorm_1.EntityManager]),
1186
1220
  __metadata("design:returntype", Promise)
@@ -1199,6 +1233,7 @@ __decorate([
1199
1233
  (0, core_1.DbSettings)('Orm'),
1200
1234
  (0, core_1.ReadOnly)(false),
1201
1235
  (0, core_1.Log)(true),
1236
+ (0, core_1.ApiDoc)(FlowNdApiDocs_1.flowChangeStatusFlowDoc),
1202
1237
  __metadata("design:type", Function),
1203
1238
  __metadata("design:paramtypes", [Object, typeorm_1.EntityManager]),
1204
1239
  __metadata("design:returntype", Promise)
@@ -1217,6 +1252,7 @@ __decorate([
1217
1252
  (0, core_1.DbSettings)("Orm"),
1218
1253
  (0, core_1.ReadOnly)(true),
1219
1254
  (0, core_1.Log)(true),
1255
+ (0, core_1.ApiDoc)(FlowNdApiDocs_1.flowListTemplatesDoc),
1220
1256
  __metadata("design:type", Function),
1221
1257
  __metadata("design:paramtypes", [Object]),
1222
1258
  __metadata("design:returntype", Promise)
@@ -1231,6 +1267,7 @@ __decorate([
1231
1267
  __metadata("design:returntype", Promise)
1232
1268
  ], Flow.prototype, "changePublishTemplate", null);
1233
1269
  Flow = __decorate([
1270
+ (0, core_1.ApiDocFor)('add', FlowNdApiDocs_1.flowAddDoc),
1234
1271
  (0, core_1.Model)('flow-nd/Flow')
1235
1272
  ], Flow);
1236
1273
  exports.default = Flow;
@@ -13,6 +13,7 @@
13
13
  * Last modified :
14
14
  * ISSUE DATE NAME DESCRIPTION
15
15
  * 9141239986 16-May-2025 Mercedes Zambrana Save flagName and FlagValue
16
+ * EF-23 08-Oct-2026 Favio Figueroa GET_LOCK instead of flag, skipped=200, axios timeout
16
17
  */
17
18
  import { EntityManager } from 'typeorm';
18
19
  import { Controller } from '@pxp-nd/core';