node-red-contrib-join-wait 0.6.1 → 0.6.2
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/CHANGELOG.md +156 -160
- package/README.md +5 -0
- package/join-wait.html +274 -86
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,178 +4,174 @@ All notable changes to this project are documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [0.6.2] - 2026-05-09
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **Open example flows button** was a no-op. Now invokes
|
|
12
|
+
`core:show-examples-import-dialog` directly, opening the dialog as a
|
|
13
|
+
modal above the edit tray.
|
|
14
|
+
- **Wait paths editableList overlapped the form rows below it** for
|
|
15
|
+
long lists. Resized via the canonical Node-RED pattern (numeric
|
|
16
|
+
height + `oneditresize` filling the tray, no upper clamp); re-flows
|
|
17
|
+
on the Advanced `<details>` toggle.
|
|
18
|
+
- **Persist store admin call** now prefixes `RED.settings.apiRootUrl`,
|
|
19
|
+
so it works under a custom `httpAdminRoot` or behind a reverse
|
|
20
|
+
proxy.
|
|
21
|
+
- **Help text** no longer references the file-based persistence path
|
|
22
|
+
removed in 0.6.0; corrected to describe the context-store backend.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **Bulk paste in path lists** — paste newline-separated text into any
|
|
27
|
+
Wait paths or Reset paths row to fill the row + append one row per
|
|
28
|
+
remaining line.
|
|
29
|
+
- **Richer inline validation** — output preview reflects
|
|
30
|
+
`pathTopicType` and shows repeat counts as `path_1 (×2): …`;
|
|
31
|
+
duplicate Reset paths and overlap between Wait/Reset paths are
|
|
32
|
+
flagged inline; Timeout accepts fractional values.
|
|
33
|
+
- Tests for the `/join-wait/stores` admin endpoint and drift fences
|
|
34
|
+
in `editor_spec.js` so the help text never re-acquires legacy keys.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- Persist store row hides when Preserve queue is off; output-preview
|
|
39
|
+
and Advanced summary use Node-RED CSS variables (theme-aware);
|
|
40
|
+
Timeout field widths cleaned up.
|
|
41
|
+
|
|
42
|
+
## [0.6.1] - 2026-05-08
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- Shipped example flows wouldn't import — inject nodes were missing
|
|
47
|
+
the standard top-level `topic`, `payload`, `repeat`, `crontab`,
|
|
48
|
+
`once`, and `onceDelay` fields. All five examples regenerated in
|
|
49
|
+
the canonical inject format.
|
|
50
|
+
- First attempt at fixing the **Open example flows** button (closed
|
|
51
|
+
the edit tray before invoking the import dialog) — didn't reliably
|
|
52
|
+
surface; see 0.6.2 for the working fix.
|
|
53
|
+
|
|
7
54
|
## [0.6.0] - 2026-05-08
|
|
8
55
|
|
|
9
56
|
### Added
|
|
10
57
|
|
|
11
|
-
- **Modern
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
- **
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
(`title` attributes) for every form row.
|
|
33
|
-
- **Inline output-shape preview.** Under Wait paths, a small monospace
|
|
34
|
-
line shows what the merged success message looks like
|
|
35
|
-
(`→ msg.topic = { path_1: …, path_2: … }`), updating as the list
|
|
36
|
-
changes.
|
|
37
|
-
- **Smart timeout warning.** When the resolved timeout is under 50 ms,
|
|
38
|
-
a yellow tip surfaces the README guidance about padding evaluation
|
|
39
|
-
overhead.
|
|
40
|
-
- **Inline jsonata validation on Group by.** When the field type is
|
|
41
|
-
`jsonata`, the expression is parsed at edit-time and the field marks
|
|
42
|
-
invalid before the first message even arrives.
|
|
43
|
-
- **Quick "Open example flows" button** in the editor opens the import
|
|
44
|
-
dialog directly.
|
|
45
|
-
- Path-row placeholders changed from generic `path name` to a worked
|
|
46
|
-
example (`e.g. sensor_1`, `e.g. abort`) so empty rows hint at intent.
|
|
47
|
-
- **Status-indicator error states.** Validation, regex compile, and
|
|
48
|
-
correlator-evaluation failures now show a red ring on the node with a
|
|
49
|
-
short reason in addition to the usual `node.error` log entry.
|
|
50
|
-
- **Unhandled-rejection guard** around the async input handler so a future
|
|
51
|
-
bug or upstream throw can't escape without being logged.
|
|
52
|
-
- End-to-end smoke test for **all five** shipped example flows, plus a
|
|
53
|
-
static config-shape sanity test on `join-wait.html` to catch drift
|
|
54
|
-
between the editor defaults and the runtime fields.
|
|
55
|
-
- **Advanced settings** are collapsed under a single `<details>` group so
|
|
56
|
-
the editor presents the common options first.
|
|
57
|
-
- **`msg.reset`** — sending `msg.reset = true` silently drains the queue
|
|
58
|
-
for the current correlation group with no output.
|
|
59
|
-
- **`node.status()`** indicator showing the number of queued messages.
|
|
60
|
-
- **Examples** — the package now ships three ready-made flows
|
|
61
|
-
(`Quickstart`, `Correlation`, `Reset paths`) discoverable via
|
|
62
|
-
**Menu → Import → Examples → join-wait**.
|
|
63
|
-
- **Per-instance persistence store** — new `Persist store` field selects a
|
|
64
|
-
named context store from `settings.js` for restart-survival.
|
|
65
|
-
- Unit-test specs for the `lib/config`, `lib/matcher`, and `lib/persist`
|
|
66
|
-
modules.
|
|
58
|
+
- **Modern `(msg, send, done)` input handler** — proper async-message
|
|
59
|
+
tracking and graceful shutdown (the old auto-completion fired
|
|
60
|
+
before awaited work resolved).
|
|
61
|
+
- **Editor UX overhaul** — Wait paths and Reset paths now use
|
|
62
|
+
`editableList` (add/remove/reorder rows; inline validation for
|
|
63
|
+
empty rows + invalid regex; Enter adds the next). Inline output
|
|
64
|
+
preview, smart short-timeout warning, jsonata-validity check on
|
|
65
|
+
Group by, tooltips on every label, an **Open example flows**
|
|
66
|
+
shortcut, and an **Advanced** collapsible. Field labels renamed
|
|
67
|
+
for clarity.
|
|
68
|
+
- **Persist store dropdown** — new `/join-wait/stores` admin route
|
|
69
|
+
exposes the configured context stores; the editor renders a typo-proof
|
|
70
|
+
`<select>`.
|
|
71
|
+
- **Smarter `node.status()`** — single-group nodes show progress
|
|
72
|
+
(`2/3 received`); validation / regex-compile / correlator-evaluation
|
|
73
|
+
failures show a red ring with a short reason.
|
|
74
|
+
- **Five shipped example flows** — Quickstart, Correlation, Reset,
|
|
75
|
+
Regex, Exact order — discoverable via **Menu → Import → Examples
|
|
76
|
+
→ join-wait**.
|
|
77
|
+
- **`msg.reset`** silently drains the queue for the current
|
|
78
|
+
correlation group.
|
|
67
79
|
|
|
68
80
|
### Changed
|
|
69
81
|
|
|
70
|
-
- **
|
|
71
|
-
|
|
72
|
-
`
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
-
|
|
84
|
-
`
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
-
|
|
88
|
-
|
|
89
|
-
Removes a class of subtle defaults-bug for non-editor-built flows.
|
|
90
|
-
- **`countPathsAnyOrder`** now uses a `Set` to track used indices
|
|
91
|
-
(O(1) check vs the previous O(n) `Array.indexOf`).
|
|
92
|
-
- **Comment around `flushOnMsgComplete`** documents the deliberately
|
|
93
|
-
partial behavior: `msg.complete` short-circuits a partial queue but
|
|
94
|
-
never overrides a successful match.
|
|
95
|
-
- **`node:` prefix on core imports** in test files (`node:fs`,
|
|
96
|
-
`node:os`, `node:path`).
|
|
97
|
-
- **Expired output now uses the configured `Path field`** for the merged
|
|
98
|
-
data (matching the success output). Previously hardcoded to `msg.paths`
|
|
99
|
-
even when a different `pathTopic` was configured. Existing flows that
|
|
100
|
-
use the default `paths` setting see no change.
|
|
101
|
-
- **Persistence rewritten on top of Node-RED's context store** (replacing
|
|
102
|
-
the `node-persist` singleton, which raced when multiple `join-wait` nodes
|
|
103
|
-
were deployed in the same flow). Default in-memory store keeps queues
|
|
104
|
-
across deploys; a configured persistent store keeps them across full
|
|
105
|
-
restarts.
|
|
106
|
-
- **`Preserve queue` defaults to on** for new nodes. Partial joins now
|
|
107
|
-
survive a redeploy out of the box. Existing 0.5.x flows keep their
|
|
108
|
-
original `persistOnRestart: false` until re-saved.
|
|
109
|
-
- **`Persist store` is no longer required** for restart persistence.
|
|
110
|
-
If `localfilesystem` (or similar) is set as the **default** context
|
|
111
|
-
store in `settings.js`, every join-wait node uses it automatically.
|
|
112
|
-
- **Auto-pick of a persistent named store.** When `Preserve queue` is
|
|
113
|
-
on, `Persist store` is empty, and the configured default store is
|
|
114
|
-
memory, the node auto-selects the first non-memory named store from
|
|
115
|
-
`settings.js` (with a `node.log` line so it's discoverable). If the
|
|
116
|
-
user has both `default: memory` and (say) `file: localfilesystem`
|
|
117
|
-
configured, they get restart persistence for free without setting
|
|
118
|
-
`Persist store` on every node. Setting `Persist store` explicitly
|
|
119
|
-
always wins.
|
|
120
|
-
- **British → American spellings.** `normalisePaths` → `normalizePaths`,
|
|
121
|
-
`serialisable` → `serializable`, `behaviour` → `behavior` in code +
|
|
122
|
-
docs for consistency with the wider JS / npm ecosystem.
|
|
123
|
-
- **`msg.pathsToWait`, `msg.pathsToExpire`, `msg.useRegex` are now one-shot
|
|
124
|
-
overrides** — they apply only to the current message instead of
|
|
125
|
-
permanently mutating the node's stored config.
|
|
126
|
-
- **Code split** into `lib/config.js`, `lib/matcher.js`, and `lib/persist.js`
|
|
127
|
-
— `join-wait.js` is now a thin orchestrator. The input handler is broken
|
|
128
|
-
into discrete named phases for readability.
|
|
129
|
-
- **`mapPayload` no longer mutates the caller's `pathTopic` object** —
|
|
130
|
-
incoming messages are left untouched.
|
|
131
|
-
- **jsonata bumped to v2** (`evaluate()` is now properly awaited).
|
|
132
|
-
- **Node-RED bumped to ≥ 3.0**, **Node.js ≥ 18**.
|
|
133
|
-
- **`engines.node`, `files`** added to `package.json`; deprecated
|
|
134
|
-
`licenses` array removed.
|
|
135
|
-
- Source labels in the editor renamed for clarity:
|
|
136
|
-
- "Paths topic" → **Path field**
|
|
137
|
-
- "Paths (Wait)" / "Paths (Expire)" → **Wait paths** / **Reset paths**
|
|
138
|
-
- "Sequence order" → **Match order**
|
|
139
|
-
- "Base message" → **Output base**
|
|
140
|
-
- "Merged data" → **Merge values**
|
|
141
|
-
- README rewritten — quickstart first, technical details after.
|
|
82
|
+
- **Persistence rewritten on top of Node-RED's context store**,
|
|
83
|
+
replacing the `node-persist` singleton (which raced when multiple
|
|
84
|
+
`join-wait` nodes shared the flow). Default in-memory store keeps
|
|
85
|
+
queues across deploys; a configured persistent store keeps them
|
|
86
|
+
across full restarts. **Preserve queue** defaults to on; the node
|
|
87
|
+
auto-picks the first persistent named store when the default is
|
|
88
|
+
memory.
|
|
89
|
+
- **`msg.pathsToWait`, `msg.pathsToExpire`, `msg.useRegex`** are now
|
|
90
|
+
one-shot overrides — they apply only to the current message
|
|
91
|
+
instead of permanently mutating the node's stored config.
|
|
92
|
+
- **Expired output uses the configured Path field** for the merged
|
|
93
|
+
data (matched the success output). Previously hardcoded to
|
|
94
|
+
`msg.paths`.
|
|
95
|
+
- **Code split** into `lib/config.js`, `lib/matcher.js`,
|
|
96
|
+
`lib/persist.js`; `findAllPaths{AnyOrder,ExactOrder}` return a
|
|
97
|
+
`{matched, keep}` shape; `drainQueue` takes options; internal
|
|
98
|
+
renames for clarity (config keys unchanged for back-compat).
|
|
99
|
+
- jsonata bumped to v2 (proper async `evaluate()`); Node-RED ≥ 3.0,
|
|
100
|
+
Node.js ≥ 20.
|
|
142
101
|
|
|
143
102
|
### Fixed
|
|
144
103
|
|
|
145
|
-
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
Node-RED shutdown.
|
|
152
|
-
- Empty `<b></b>` tag in the help text.
|
|
153
|
-
- Stale `ignoreUnmatched` references in older example flows.
|
|
104
|
+
- `node.error` / `node.warn` correctly pass the originating `msg`
|
|
105
|
+
(previously passed as `[msg, null]` since 0.5.x — Catch nodes
|
|
106
|
+
downstream now receive the real message).
|
|
107
|
+
- Close handler always calls `done` even when the context-store
|
|
108
|
+
write rejects, so persistence failures don't hold up shutdown.
|
|
109
|
+
- `mapPayload` no longer mutates the caller's `pathTopic` object.
|
|
154
110
|
- Possible race when multiple `join-wait` nodes shared the global
|
|
155
|
-
`node-persist` singleton (now
|
|
111
|
+
`node-persist` singleton (now an isolated context-store entry per
|
|
156
112
|
node).
|
|
157
113
|
|
|
158
114
|
### Tooling
|
|
159
115
|
|
|
160
|
-
- GitHub Actions CI replaces Travis (matrix on Node 20/22/24
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
-
|
|
173
|
-
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
## [0.5.
|
|
180
|
-
|
|
181
|
-
|
|
116
|
+
- GitHub Actions CI replaces Travis (matrix on Node 20/22/24);
|
|
117
|
+
publish job gated on lint + format + spellcheck + test matrix.
|
|
118
|
+
ESLint v9 (flat config), Prettier v3, c8 (replaces nyc), cspell
|
|
119
|
+
(replaces `markdown-spellcheck`); Dependabot for monthly npm + GHA
|
|
120
|
+
updates. Test suite expanded from 32 to 102 cases.
|
|
121
|
+
|
|
122
|
+
## [0.5.3] - 2021-10-31
|
|
123
|
+
|
|
124
|
+
- Update dependencies.
|
|
125
|
+
|
|
126
|
+
## [0.5.2] - 2021-02-02
|
|
127
|
+
|
|
128
|
+
- Retain state across restart
|
|
129
|
+
([#7](https://github.com/dxdc/node-red-contrib-join-wait/issues/7)).
|
|
130
|
+
|
|
131
|
+
## [0.5.1] - 2021-01-05
|
|
132
|
+
|
|
133
|
+
- More automated testing and code coverage.
|
|
134
|
+
|
|
135
|
+
## [0.5.0] - 2020-12-17
|
|
136
|
+
|
|
137
|
+
- Expire earlier for "any order" mode
|
|
138
|
+
([#4](https://github.com/dxdc/node-red-contrib-join-wait/issues/4)).
|
|
139
|
+
|
|
140
|
+
## [0.4.5] - 2020-12-15
|
|
141
|
+
|
|
142
|
+
- Add automated tests; address several minor issues uncovered by
|
|
143
|
+
testing; refactoring and readability changes.
|
|
144
|
+
|
|
145
|
+
## [0.4.0] - 2020-12-14
|
|
146
|
+
|
|
147
|
+
- Reworked logic for exact-order mode
|
|
148
|
+
([#1](https://github.com/dxdc/node-red-contrib-join-wait/issues/1)).
|
|
149
|
+
- Support for duplicate path names
|
|
150
|
+
([#2](https://github.com/dxdc/node-red-contrib-join-wait/issues/2)).
|
|
151
|
+
- Show all unmatched paths with individual warnings.
|
|
152
|
+
- Minor changes to config UI and defaults.
|
|
153
|
+
|
|
154
|
+
## [0.3.5] - 2020-05-26
|
|
155
|
+
|
|
156
|
+
- Enforce unique path names.
|
|
157
|
+
- Regex-based path matching.
|
|
158
|
+
|
|
159
|
+
## [0.3.4] - 2020-01-17
|
|
160
|
+
|
|
161
|
+
- Don't reuse the `msg` variable.
|
|
162
|
+
|
|
163
|
+
## [0.3.3] - 2020-01-17
|
|
164
|
+
|
|
165
|
+
- Fix bug with paths / `clearTimeout`.
|
|
166
|
+
- Minor code optimizations; add Example 3 to documentation.
|
|
167
|
+
|
|
168
|
+
## [0.3.2] - 2020-01-16
|
|
169
|
+
|
|
170
|
+
- `msg.complete` handling.
|
|
171
|
+
- `timeoutUnits` in settings.
|
|
172
|
+
- `ignoreUnmatched` paths option in settings.
|
|
173
|
+
- Updated documentation.
|
|
174
|
+
|
|
175
|
+
## [0.3.0] - 2020-01-14
|
|
176
|
+
|
|
177
|
+
- Initial release.
|
package/README.md
CHANGED
|
@@ -61,6 +61,11 @@ you want to wait for. That's it.
|
|
|
61
61
|
> Looking for a working flow? After install, open
|
|
62
62
|
> **Menu → Import → Examples → join-wait** for ready-made flows.
|
|
63
63
|
|
|
64
|
+
> **Tip:** in the **Wait paths** / **Reset paths** lists you can press
|
|
65
|
+
> <kbd>Enter</kbd> in a row to add the next one, and paste newline-separated
|
|
66
|
+
> text into any row to fill the current row and append the rest as new
|
|
67
|
+
> rows — handy for seeding a list from a spreadsheet column or a debug log.
|
|
68
|
+
|
|
64
69
|
## How it works
|
|
65
70
|
|
|
66
71
|
```
|
package/join-wait.html
CHANGED
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
or an object with multiple keys.
|
|
16
16
|
</div>
|
|
17
17
|
|
|
18
|
-
<div class="form-row">
|
|
18
|
+
<div class="form-row" style="margin-bottom:4px">
|
|
19
19
|
<label title="Path names to wait for; repeat an entry to require it n times."><i class="fa fa-code-fork"></i> Wait paths</label>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="form-row node-input-paths-container-row">
|
|
20
22
|
<ol id="node-input-paths-container"></ol>
|
|
21
23
|
</div>
|
|
22
24
|
<div class="form-tips" style="margin-bottom:6px">
|
|
@@ -24,12 +26,12 @@
|
|
|
24
26
|
merged success output. Press <kbd>Enter</kbd> in a row to add the
|
|
25
27
|
next one. Repeat an entry to require n-of-the-same.
|
|
26
28
|
</div>
|
|
27
|
-
<div class="form-tips" id="join-wait-output-preview" style="margin-bottom:12px; font-family:monospace
|
|
29
|
+
<div class="form-tips" id="join-wait-output-preview" style="margin-bottom:12px; font-family:var(--red-ui-monospace-font, monospace)"></div>
|
|
28
30
|
|
|
29
31
|
<div class="form-row">
|
|
30
32
|
<label for="node-input-timeout" title="How long all wait paths have to arrive before the queue is expired."><i class="fa fa-clock-o"></i> Timeout</label>
|
|
31
|
-
<input type="text" id="node-input-timeout" style="text-align:end; width:
|
|
32
|
-
<select id="node-input-timeoutUnits" style="width:
|
|
33
|
+
<input type="text" id="node-input-timeout" style="text-align:end; width:90px" placeholder="15">
|
|
34
|
+
<select id="node-input-timeoutUnits" style="width:160px; margin-left:5px">
|
|
33
35
|
<option value="1">Milliseconds</option>
|
|
34
36
|
<option value="1000">Seconds</option>
|
|
35
37
|
<option value="60000">Minutes</option>
|
|
@@ -37,11 +39,13 @@
|
|
|
37
39
|
<option value="86400000">Days</option>
|
|
38
40
|
</select>
|
|
39
41
|
</div>
|
|
40
|
-
<div class="form-tips" id="join-wait-timeout-hint" style="margin-bottom:12px; color:#b07b00; display:none"
|
|
42
|
+
<div class="form-tips" id="join-wait-timeout-hint" style="margin-bottom:12px; color:#b07b00; display:none">
|
|
43
|
+
<i class="fa fa-exclamation-triangle"></i> <span></span>
|
|
44
|
+
</div>
|
|
41
45
|
|
|
42
46
|
<div class="form-row">
|
|
43
47
|
<label for="node-input-exactOrder" title="Any order (default) or strict sequence."><i class="fa fa-sort"></i> Match order</label>
|
|
44
|
-
<select id="node-input-exactOrder" style="width:70
|
|
48
|
+
<select id="node-input-exactOrder" style="width:70%">
|
|
45
49
|
<option value="false">Any order</option>
|
|
46
50
|
<option value="true">Exact order</option>
|
|
47
51
|
</select>
|
|
@@ -64,11 +68,13 @@
|
|
|
64
68
|
</button>
|
|
65
69
|
</div>
|
|
66
70
|
|
|
67
|
-
<details style="margin-top:14px">
|
|
68
|
-
<summary style="cursor:pointer; font-weight:bold; padding:6px 0">Advanced</summary>
|
|
71
|
+
<details class="join-wait-advanced" style="margin-top:14px">
|
|
72
|
+
<summary style="cursor:pointer; font-weight:bold; padding:6px 0; color:var(--red-ui-header-text-color, inherit)">Advanced</summary>
|
|
69
73
|
|
|
70
|
-
<div class="form-row" style="margin-top:10px">
|
|
74
|
+
<div class="form-row" style="margin-top:10px; margin-bottom:4px">
|
|
71
75
|
<label title="Paths that immediately drain the queue to the expired output."><i class="fa fa-times-circle-o"></i> Reset paths</label>
|
|
76
|
+
</div>
|
|
77
|
+
<div class="form-row node-input-pathsToExpire-container-row">
|
|
72
78
|
<ol id="node-input-pathsToExpire-container"></ol>
|
|
73
79
|
</div>
|
|
74
80
|
<div class="form-tips" style="margin-bottom:12px">
|
|
@@ -78,7 +84,7 @@
|
|
|
78
84
|
|
|
79
85
|
<div class="form-row">
|
|
80
86
|
<label for="node-input-firstMsg" title="Use the first or last message in the matched window as the base of the merged output."><i class="fa fa-comments-o"></i> Output base</label>
|
|
81
|
-
<select id="node-input-firstMsg" style="width:70
|
|
87
|
+
<select id="node-input-firstMsg" style="width:70%">
|
|
82
88
|
<option value="true">First received message</option>
|
|
83
89
|
<option value="false">Last received message</option>
|
|
84
90
|
</select>
|
|
@@ -86,7 +92,7 @@
|
|
|
86
92
|
|
|
87
93
|
<div class="form-row">
|
|
88
94
|
<label for="node-input-mapPayload" title="Keep the original Path-field values, or overwrite each with that message's payload."><i class="fa fa-arrow-right"></i> Merge values</label>
|
|
89
|
-
<select id="node-input-mapPayload" style="width:70
|
|
95
|
+
<select id="node-input-mapPayload" style="width:70%">
|
|
90
96
|
<option value="false">Original Path field values</option>
|
|
91
97
|
<option value="true">Each msg.payload</option>
|
|
92
98
|
</select>
|
|
@@ -116,13 +122,13 @@
|
|
|
116
122
|
<label for="node-input-persistOnRestart" style="width:auto">Preserve queue across deploys / restarts</label>
|
|
117
123
|
</div>
|
|
118
124
|
|
|
119
|
-
<div class="form-row">
|
|
125
|
+
<div class="form-row" id="join-wait-persist-store-row">
|
|
120
126
|
<label for="node-input-persistStore" title="Override the context store used to persist the queue."><i class="fa fa-database"></i> Persist store</label>
|
|
121
|
-
<select id="node-input-persistStore" style="width:70
|
|
127
|
+
<select id="node-input-persistStore" style="width:70%">
|
|
122
128
|
<option value="">(default context store)</option>
|
|
123
129
|
</select>
|
|
124
130
|
</div>
|
|
125
|
-
<div class="form-tips" style="margin-bottom:12px">
|
|
131
|
+
<div class="form-tips" id="join-wait-persist-store-tip" style="margin-bottom:12px">
|
|
126
132
|
Override the context store this node uses. Leave on default — set
|
|
127
133
|
the default to a persistent store (e.g. <code>localfilesystem</code>)
|
|
128
134
|
in <code>settings.js</code> and every join-wait node automatically
|
|
@@ -133,6 +139,48 @@
|
|
|
133
139
|
|
|
134
140
|
<script type="text/javascript">
|
|
135
141
|
(function () {
|
|
142
|
+
// Form-element selectors gathered in one place. Keeps the rest
|
|
143
|
+
// of the IIFE free of stringly-typed `#node-input-*` lookups
|
|
144
|
+
// and makes the editor surface auditable at a glance — every
|
|
145
|
+
// hook into the dialog is listed here.
|
|
146
|
+
var SEL = {
|
|
147
|
+
pathField: '#node-input-pathTopic',
|
|
148
|
+
pathFieldType: '#node-input-pathTopicType',
|
|
149
|
+
correlator: '#node-input-correlationTopic',
|
|
150
|
+
correlatorType: '#node-input-correlationTopicType',
|
|
151
|
+
timeout: '#node-input-timeout',
|
|
152
|
+
timeoutUnits: '#node-input-timeoutUnits',
|
|
153
|
+
useRegex: '#node-input-useRegex',
|
|
154
|
+
persistOnRestart: '#node-input-persistOnRestart',
|
|
155
|
+
persistStore: '#node-input-persistStore',
|
|
156
|
+
outputPreview: '#join-wait-output-preview',
|
|
157
|
+
timeoutHint: '#join-wait-timeout-hint',
|
|
158
|
+
examplesButton: '#join-wait-open-examples',
|
|
159
|
+
persistStoreRow: '#join-wait-persist-store-row',
|
|
160
|
+
persistStoreTip: '#join-wait-persist-store-tip',
|
|
161
|
+
dialogForm: '#dialog-form',
|
|
162
|
+
advancedDetails: '#dialog-form details',
|
|
163
|
+
};
|
|
164
|
+
// editableList wrapper IDs (no leading '#'); consumers either
|
|
165
|
+
// pass them to readEditableList or interpolate `#` themselves.
|
|
166
|
+
var LIST_ID = {
|
|
167
|
+
paths: 'node-input-paths-container',
|
|
168
|
+
expire: 'node-input-pathsToExpire-container',
|
|
169
|
+
};
|
|
170
|
+
function listInputs(id) {
|
|
171
|
+
return $('#' + id + ' input[type=text]');
|
|
172
|
+
}
|
|
173
|
+
// Read the typed-input pair behind the Path field — name + type.
|
|
174
|
+
// Used wherever both halves are needed together (currently the
|
|
175
|
+
// output preview); centralising it avoids drift between the two
|
|
176
|
+
// selectors.
|
|
177
|
+
function readPathField() {
|
|
178
|
+
return {
|
|
179
|
+
name: $(SEL.pathField).val() || 'topic',
|
|
180
|
+
type: $(SEL.pathFieldType).val() || 'msg',
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
136
184
|
function arrayValidator(allowEmpty, requireUnique) {
|
|
137
185
|
return function (v) {
|
|
138
186
|
// Accept legacy JSON-string format too, for back-compat with old flows.
|
|
@@ -191,25 +239,82 @@
|
|
|
191
239
|
input.attr('title', ok ? '' : message || '');
|
|
192
240
|
}
|
|
193
241
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
242
|
+
// Single-row validation: empty / invalid regex. List-level
|
|
243
|
+
// checks (uniqueness, cross-list overlap) live in validateAllRows
|
|
244
|
+
// because they need both lists' current values in scope.
|
|
245
|
+
function validateRow(input, ownValues, otherValues, opts) {
|
|
246
|
+
var requireUnique = opts && opts.requireUnique;
|
|
247
|
+
var otherListLabel = (opts && opts.otherListLabel) || '';
|
|
248
|
+
var value = String(input.val() || '');
|
|
249
|
+
if (value === '') {
|
|
198
250
|
setRowValid(input, false, 'path name cannot be empty');
|
|
199
251
|
return false;
|
|
200
252
|
}
|
|
201
|
-
if ($useRegex.is(':checked') && !isValidRegex(
|
|
253
|
+
if ($(SEL.useRegex).is(':checked') && !isValidRegex(value)) {
|
|
202
254
|
setRowValid(input, false, 'invalid regular expression');
|
|
203
255
|
return false;
|
|
204
256
|
}
|
|
257
|
+
if (
|
|
258
|
+
requireUnique &&
|
|
259
|
+
ownValues &&
|
|
260
|
+
ownValues.filter(function (x) {
|
|
261
|
+
return x === value;
|
|
262
|
+
}).length > 1
|
|
263
|
+
) {
|
|
264
|
+
setRowValid(input, false, 'duplicate entry — must be unique');
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
if (otherValues && otherValues.indexOf(value) !== -1) {
|
|
268
|
+
setRowValid(
|
|
269
|
+
input,
|
|
270
|
+
false,
|
|
271
|
+
'"' + value + '" is also in ' + otherListLabel + " — paths shouldn't overlap",
|
|
272
|
+
);
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
205
275
|
setRowValid(input, true);
|
|
206
276
|
return true;
|
|
207
277
|
}
|
|
208
278
|
|
|
279
|
+
// Re-validate every row in both editableLists. Called whenever
|
|
280
|
+
// any row changes, the regex toggle flips, or rows are added /
|
|
281
|
+
// removed — so duplicate and cross-list overlap warnings are
|
|
282
|
+
// always in sync with the current state.
|
|
283
|
+
function validateAllRows() {
|
|
284
|
+
var $paths = listInputs(LIST_ID.paths);
|
|
285
|
+
var $expire = listInputs(LIST_ID.expire);
|
|
286
|
+
var pathValues = $paths
|
|
287
|
+
.map(function () {
|
|
288
|
+
return String($(this).val() || '');
|
|
289
|
+
})
|
|
290
|
+
.get();
|
|
291
|
+
var expireValues = $expire
|
|
292
|
+
.map(function () {
|
|
293
|
+
return String($(this).val() || '');
|
|
294
|
+
})
|
|
295
|
+
.get();
|
|
296
|
+
$paths.each(function () {
|
|
297
|
+
validateRow($(this), pathValues, expireValues, {
|
|
298
|
+
requireUnique: false,
|
|
299
|
+
otherListLabel: 'Reset paths',
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
$expire.each(function () {
|
|
303
|
+
validateRow($(this), expireValues, pathValues, {
|
|
304
|
+
requireUnique: true,
|
|
305
|
+
otherListLabel: 'Wait paths',
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
209
310
|
function initEditableList(containerId, items, opts) {
|
|
210
311
|
var $container = $('#' + containerId);
|
|
211
|
-
$container.css({ 'min-height': '100px' });
|
|
212
312
|
var placeholder = (opts && opts.placeholder) || 'e.g. sensor_1';
|
|
313
|
+
// A numeric height makes editableList scroll its items
|
|
314
|
+
// internally past the limit — without it (height:'auto'), a
|
|
315
|
+
// long list grows the wrapper and visually overlaps the form
|
|
316
|
+
// rows that follow. The actual height is recomputed in
|
|
317
|
+
// oneditresize to fill the available tray space.
|
|
213
318
|
$container.editableList({
|
|
214
319
|
addItem: function (row, index, data) {
|
|
215
320
|
var value = (data && data.value) || '';
|
|
@@ -220,7 +325,7 @@
|
|
|
220
325
|
style: 'width:100%',
|
|
221
326
|
}).val(value);
|
|
222
327
|
input.on('input change blur', function () {
|
|
223
|
-
|
|
328
|
+
validateAllRows();
|
|
224
329
|
if (opts && opts.onChange) opts.onChange();
|
|
225
330
|
});
|
|
226
331
|
// Pressing Enter in a row adds the next one.
|
|
@@ -234,16 +339,44 @@
|
|
|
234
339
|
}, 0);
|
|
235
340
|
}
|
|
236
341
|
});
|
|
342
|
+
// Bulk-paste: paste newline-separated text into any
|
|
343
|
+
// row to fill the current row + add a new row per
|
|
344
|
+
// remaining line. Whitespace-only and empty lines
|
|
345
|
+
// are dropped. Single-line pastes fall through to
|
|
346
|
+
// the browser's default paste handling.
|
|
347
|
+
input.on('paste', function (e) {
|
|
348
|
+
var clip = (e.originalEvent || e).clipboardData;
|
|
349
|
+
if (!clip) return;
|
|
350
|
+
var text = clip.getData('text');
|
|
351
|
+
if (!text || !/[\r\n]/.test(text)) return;
|
|
352
|
+
var lines = text
|
|
353
|
+
.split(/\r?\n/)
|
|
354
|
+
.map(function (s) {
|
|
355
|
+
return s.trim();
|
|
356
|
+
})
|
|
357
|
+
.filter(function (s) {
|
|
358
|
+
return s.length > 0;
|
|
359
|
+
});
|
|
360
|
+
if (lines.length === 0) return;
|
|
361
|
+
e.preventDefault();
|
|
362
|
+
input.val(lines[0]);
|
|
363
|
+
for (var i = 1; i < lines.length; i++) {
|
|
364
|
+
$container.editableList('addItem', { value: lines[i] });
|
|
365
|
+
}
|
|
366
|
+
validateAllRows();
|
|
367
|
+
if (opts && opts.onChange) opts.onChange();
|
|
368
|
+
});
|
|
237
369
|
row.append(input);
|
|
238
|
-
|
|
370
|
+
validateAllRows();
|
|
239
371
|
if (opts && opts.onChange) opts.onChange();
|
|
240
372
|
},
|
|
241
373
|
removeItem: function () {
|
|
374
|
+
validateAllRows();
|
|
242
375
|
if (opts && opts.onChange) opts.onChange();
|
|
243
376
|
},
|
|
244
377
|
removable: true,
|
|
245
378
|
sortable: true,
|
|
246
|
-
height:
|
|
379
|
+
height: (opts && opts.height) || 200,
|
|
247
380
|
});
|
|
248
381
|
(items || []).forEach(function (v) {
|
|
249
382
|
$container.editableList('addItem', { value: v });
|
|
@@ -252,14 +385,7 @@
|
|
|
252
385
|
|
|
253
386
|
// Re-validate every editableList row when the regex toggle flips.
|
|
254
387
|
function rebindRegexValidation() {
|
|
255
|
-
$(
|
|
256
|
-
$(
|
|
257
|
-
'#node-input-paths-container input[type=text], ' +
|
|
258
|
-
'#node-input-pathsToExpire-container input[type=text]',
|
|
259
|
-
).each(function () {
|
|
260
|
-
validateRow($(this));
|
|
261
|
-
});
|
|
262
|
-
});
|
|
388
|
+
$(SEL.useRegex).on('change', validateAllRows);
|
|
263
389
|
}
|
|
264
390
|
|
|
265
391
|
function readEditableList(containerId) {
|
|
@@ -287,50 +413,58 @@
|
|
|
287
413
|
return [];
|
|
288
414
|
}
|
|
289
415
|
|
|
290
|
-
// Render a tiny preview of what
|
|
291
|
-
// success output, derived from the current Wait paths
|
|
416
|
+
// Render a tiny preview of what the path-field property looks
|
|
417
|
+
// like on the success output, derived from the current Wait paths
|
|
418
|
+
// list. Prefix matches the typed-input type so flow/global don't
|
|
419
|
+
// mis-render as msg. Repeated entries surface as `(×n)` so the
|
|
420
|
+
// n-of-the-same semantic is visible.
|
|
292
421
|
function updateOutputPreview() {
|
|
293
|
-
var paths = readEditableList(
|
|
294
|
-
var
|
|
422
|
+
var paths = readEditableList(LIST_ID.paths);
|
|
423
|
+
var counts = Object.create(null);
|
|
424
|
+
var order = [];
|
|
295
425
|
paths.forEach(function (p) {
|
|
296
|
-
if (
|
|
426
|
+
if (counts[p] === undefined) order.push(p);
|
|
427
|
+
counts[p] = (counts[p] || 0) + 1;
|
|
297
428
|
});
|
|
298
|
-
var
|
|
299
|
-
var $tip = $(
|
|
300
|
-
if (
|
|
429
|
+
var field = readPathField();
|
|
430
|
+
var $tip = $(SEL.outputPreview);
|
|
431
|
+
if (order.length === 0) {
|
|
301
432
|
$tip.text('');
|
|
302
433
|
return;
|
|
303
434
|
}
|
|
304
|
-
var entries =
|
|
305
|
-
return p + ': …';
|
|
435
|
+
var entries = order.slice(0, 4).map(function (p) {
|
|
436
|
+
return counts[p] > 1 ? p + ' (×' + counts[p] + '): …' : p + ': …';
|
|
306
437
|
});
|
|
307
|
-
if (
|
|
308
|
-
$tip.text('→
|
|
438
|
+
if (order.length > 4) entries.push('…');
|
|
439
|
+
$tip.text('→ ' + field.type + '.' + field.name + ' = { ' + entries.join(', ') + ' }');
|
|
309
440
|
}
|
|
310
441
|
|
|
311
442
|
// Warn when the resolved timeout is impractically short (the README
|
|
312
443
|
// recommends padding ~5–10 ms for evaluation overhead).
|
|
313
444
|
function updateTimeoutHint() {
|
|
314
|
-
var t = Number($(
|
|
315
|
-
var u = Number($(
|
|
445
|
+
var t = Number($(SEL.timeout).val()) || 0;
|
|
446
|
+
var u = Number($(SEL.timeoutUnits).val()) || 1;
|
|
316
447
|
var ms = t * u;
|
|
317
|
-
var $hint = $(
|
|
448
|
+
var $hint = $(SEL.timeoutHint);
|
|
318
449
|
if (ms > 0 && ms < 50) {
|
|
319
|
-
$hint.text('Very short — pad ~10 ms to leave room for evaluation overhead.')
|
|
450
|
+
$hint.find('span').text('Very short — pad ~10 ms to leave room for evaluation overhead.');
|
|
451
|
+
$hint.show();
|
|
320
452
|
} else {
|
|
321
453
|
$hint.hide();
|
|
322
454
|
}
|
|
323
455
|
}
|
|
324
456
|
|
|
325
457
|
// Populate the Persist store <select> from the runtime via our
|
|
326
|
-
// /join-wait/stores admin route.
|
|
327
|
-
//
|
|
458
|
+
// /join-wait/stores admin route. apiRootUrl prefixes the call so
|
|
459
|
+
// it resolves correctly under custom httpAdminRoot / reverse
|
|
460
|
+
// proxies; falls back to the bare relative URL on older
|
|
461
|
+
// Node-RED versions where the setting isn't exposed.
|
|
328
462
|
function populatePersistStores(currentValue) {
|
|
329
|
-
var $sel = $(
|
|
330
|
-
|
|
463
|
+
var $sel = $(SEL.persistStore);
|
|
464
|
+
var base = (RED.settings && RED.settings.apiRootUrl) || '';
|
|
465
|
+
$.getJSON(base + 'join-wait/stores')
|
|
331
466
|
.done(function (stores) {
|
|
332
|
-
|
|
333
|
-
stores.forEach(function (s) {
|
|
467
|
+
(Array.isArray(stores) ? stores : []).forEach(function (s) {
|
|
334
468
|
if (s.name === 'default') return; // already represented as the blank option
|
|
335
469
|
var label = s.module ? s.name + ' (' + s.module + ')' : s.name;
|
|
336
470
|
$('<option/>').val(s.name).text(label).appendTo($sel);
|
|
@@ -346,25 +480,52 @@
|
|
|
346
480
|
$sel.val(currentValue || '');
|
|
347
481
|
})
|
|
348
482
|
.fail(function () {
|
|
349
|
-
// Admin route not reachable
|
|
350
|
-
//
|
|
483
|
+
// Admin route not reachable. Preserve the saved
|
|
484
|
+
// value as an explicit option so the dropdown doesn't
|
|
485
|
+
// silently overwrite persistStore with '' on save.
|
|
486
|
+
if (currentValue) {
|
|
487
|
+
$('<option/>')
|
|
488
|
+
.val(currentValue)
|
|
489
|
+
.text(currentValue + ' (stores list unavailable)')
|
|
490
|
+
.appendTo($sel);
|
|
491
|
+
$sel.val(currentValue);
|
|
492
|
+
}
|
|
351
493
|
});
|
|
352
494
|
}
|
|
353
495
|
|
|
496
|
+
// Last `size` handed to oneditresize. Cached so the <details>
|
|
497
|
+
// toggle handler can re-run the resize math without waiting for
|
|
498
|
+
// the next tray drag.
|
|
499
|
+
var lastTraySize = null;
|
|
500
|
+
|
|
501
|
+
// Stretch the Wait paths editableList to fill the tray. Mirrors
|
|
502
|
+
// the pattern in Node-RED core (switch/change/httprequest):
|
|
503
|
+
// subtract every other row's outer height from the tray height
|
|
504
|
+
// and give the remainder to the list. The :visible filter mirrors
|
|
505
|
+
// httprequest so hidden rows (e.g. timeout-hint) are skipped
|
|
506
|
+
// explicitly. No upper clamp — when the user drags the tray
|
|
507
|
+
// taller they want to see more rows.
|
|
508
|
+
function resizePathsList() {
|
|
509
|
+
var $form = $(SEL.dialogForm);
|
|
510
|
+
if (!$form.length || !lastTraySize || !lastTraySize.height) return;
|
|
511
|
+
var height = lastTraySize.height;
|
|
512
|
+
$form.children(':not(.node-input-paths-container-row)').each(function () {
|
|
513
|
+
var $r = $(this);
|
|
514
|
+
if ($r.is(':visible')) height -= $r.outerHeight(true) || 0;
|
|
515
|
+
});
|
|
516
|
+
height -= 12; // breathing room for margins/padding the loop misses
|
|
517
|
+
if (height < 140) height = 140;
|
|
518
|
+
$('#' + LIST_ID.paths).editableList('height', height);
|
|
519
|
+
}
|
|
520
|
+
|
|
354
521
|
function openExamplesDialog() {
|
|
355
|
-
// The
|
|
356
|
-
// can
|
|
357
|
-
//
|
|
358
|
-
//
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
}
|
|
363
|
-
};
|
|
364
|
-
if (RED.tray && typeof RED.tray.close === 'function') {
|
|
365
|
-
RED.tray.close(invoke);
|
|
366
|
-
} else {
|
|
367
|
-
invoke();
|
|
522
|
+
// The import dialog opens as a modal above the edit tray, so
|
|
523
|
+
// the user can pick a flow and on close return to this edit
|
|
524
|
+
// dialog with in-flight edits intact. The earlier approach of
|
|
525
|
+
// closing the tray first was unreliable (close-callback drops
|
|
526
|
+
// when stack races editor teardown).
|
|
527
|
+
if (RED.actions && typeof RED.actions.invoke === 'function') {
|
|
528
|
+
RED.actions.invoke('core:show-examples-import-dialog');
|
|
368
529
|
}
|
|
369
530
|
}
|
|
370
531
|
|
|
@@ -411,14 +572,14 @@
|
|
|
411
572
|
return this.name ? 'node_label_italic' : '';
|
|
412
573
|
},
|
|
413
574
|
oneditprepare: function () {
|
|
414
|
-
$(
|
|
575
|
+
$(SEL.pathField).typedInput({
|
|
415
576
|
default: this.pathTopicType || 'msg',
|
|
416
|
-
typeField: $(
|
|
577
|
+
typeField: $(SEL.pathFieldType),
|
|
417
578
|
types: ['msg', 'flow', 'global'],
|
|
418
579
|
});
|
|
419
|
-
$(
|
|
580
|
+
$(SEL.correlator).typedInput({
|
|
420
581
|
default: this.correlationTopicType || 'undefined',
|
|
421
|
-
typeField: $(
|
|
582
|
+
typeField: $(SEL.correlatorType),
|
|
422
583
|
types: [
|
|
423
584
|
{ value: 'undefined', label: RED._('common.type.undefined'), hasValue: false },
|
|
424
585
|
'msg',
|
|
@@ -427,34 +588,57 @@
|
|
|
427
588
|
'jsonata',
|
|
428
589
|
],
|
|
429
590
|
});
|
|
430
|
-
|
|
591
|
+
// Spinner accepts decimals (1.5 seconds, 0.25 hours, …)
|
|
592
|
+
// — the runtime multiplies the value by the unit factor,
|
|
593
|
+
// so fractional units are valid. step:1 keeps the up/down
|
|
594
|
+
// arrows snappy for the common integer case.
|
|
595
|
+
$(SEL.timeout).spinner({ min: 0.001, step: 1, numberFormat: 'n' });
|
|
431
596
|
|
|
432
|
-
initEditableList(
|
|
597
|
+
initEditableList(LIST_ID.paths, toArray(this.paths), {
|
|
433
598
|
placeholder: 'e.g. sensor_1',
|
|
434
599
|
onChange: updateOutputPreview,
|
|
600
|
+
height: 200,
|
|
435
601
|
});
|
|
436
|
-
initEditableList(
|
|
602
|
+
initEditableList(LIST_ID.expire, toArray(this.pathsToExpire), {
|
|
437
603
|
placeholder: 'e.g. abort',
|
|
604
|
+
height: 140,
|
|
438
605
|
});
|
|
439
606
|
rebindRegexValidation();
|
|
440
607
|
|
|
441
|
-
$(
|
|
608
|
+
$(SEL.pathField).on('change input', updateOutputPreview);
|
|
442
609
|
updateOutputPreview();
|
|
443
610
|
|
|
444
|
-
$('
|
|
611
|
+
$(SEL.timeout + ', ' + SEL.timeoutUnits).on('change input', updateTimeoutHint);
|
|
445
612
|
updateTimeoutHint();
|
|
446
613
|
|
|
447
614
|
populatePersistStores(this.persistStore);
|
|
448
615
|
|
|
449
|
-
|
|
616
|
+
// Hide the Persist store row when Preserve queue is off —
|
|
617
|
+
// the override is meaningless without persistence enabled.
|
|
618
|
+
var $persistRow = $(SEL.persistStoreRow + ', ' + SEL.persistStoreTip);
|
|
619
|
+
function syncPersistStoreVisibility() {
|
|
620
|
+
$persistRow.toggle($(SEL.persistOnRestart).is(':checked'));
|
|
621
|
+
resizePathsList();
|
|
622
|
+
}
|
|
623
|
+
$(SEL.persistOnRestart).on('change', syncPersistStoreVisibility);
|
|
624
|
+
syncPersistStoreVisibility();
|
|
625
|
+
|
|
626
|
+
$(SEL.examplesButton).on('click', openExamplesDialog);
|
|
627
|
+
|
|
628
|
+
// Re-run the editableList resize math when Advanced is
|
|
629
|
+
// expanded/collapsed — oneditresize only fires on tray
|
|
630
|
+
// drag, so without this the Wait paths list keeps the
|
|
631
|
+
// height it had before the toggle and doesn't reclaim
|
|
632
|
+
// (or yield) space.
|
|
633
|
+
$(SEL.advancedDetails).on('toggle', resizePathsList);
|
|
450
634
|
},
|
|
451
635
|
oneditsave: function () {
|
|
452
|
-
this.paths = readEditableList(
|
|
453
|
-
this.pathsToExpire = readEditableList(
|
|
636
|
+
this.paths = readEditableList(LIST_ID.paths);
|
|
637
|
+
this.pathsToExpire = readEditableList(LIST_ID.expire);
|
|
454
638
|
},
|
|
455
|
-
oneditresize: function () {
|
|
456
|
-
|
|
457
|
-
|
|
639
|
+
oneditresize: function (size) {
|
|
640
|
+
lastTraySize = size;
|
|
641
|
+
resizePathsList();
|
|
458
642
|
},
|
|
459
643
|
});
|
|
460
644
|
})();
|
|
@@ -550,8 +734,12 @@
|
|
|
550
734
|
is kept in the merged output.</li>
|
|
551
735
|
<li>Pad <b>Timeout</b> with a small overhead (~5–10 ms) for
|
|
552
736
|
evaluation time when working with very short windows.</li>
|
|
553
|
-
<li>When <b>Preserve queue</b> is on, the queue is
|
|
554
|
-
|
|
555
|
-
|
|
737
|
+
<li>When <b>Preserve queue</b> is on, the queue is written to the
|
|
738
|
+
node's context on close (and reloaded on the next start). With
|
|
739
|
+
the default in-memory store this only survives a redeploy; for
|
|
740
|
+
full restart persistence, point <b>Persist store</b> at a
|
|
741
|
+
store backed by <code>localfilesystem</code> (or any persistent
|
|
742
|
+
module) configured under <code>contextStorage</code> in
|
|
743
|
+
<code>settings.js</code>.</li>
|
|
556
744
|
</ul>
|
|
557
745
|
</script>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-join-wait",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Node-RED node that joins related messages across multiple paths within a time window — with exact-order matching, regex paths, correlation grouping, reset paths, and queue persistence. Coordinate parallel flows, synchronize events, and debounce sensors.",
|
|
5
5
|
"author": "Daniel Caspi <dan@element26.net>",
|
|
6
6
|
"license": "MIT",
|