n8n-nodes-message-debounce 0.1.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/LICENSE +21 -0
- package/README.md +141 -0
- package/dist/credentials/RedisApi.credentials.d.ts +16 -0
- package/dist/credentials/RedisApi.credentials.d.ts.map +1 -0
- package/dist/credentials/RedisApi.credentials.js +70 -0
- package/dist/credentials/RedisApi.credentials.js.map +1 -0
- package/dist/credentials/redisApi.svg +17 -0
- package/dist/nodes/MessageDebounce/MessageDebounce.description.d.ts +8 -0
- package/dist/nodes/MessageDebounce/MessageDebounce.description.d.ts.map +1 -0
- package/dist/nodes/MessageDebounce/MessageDebounce.description.js +247 -0
- package/dist/nodes/MessageDebounce/MessageDebounce.description.js.map +1 -0
- package/dist/nodes/MessageDebounce/MessageDebounce.node.d.ts +19 -0
- package/dist/nodes/MessageDebounce/MessageDebounce.node.d.ts.map +1 -0
- package/dist/nodes/MessageDebounce/MessageDebounce.node.js +102 -0
- package/dist/nodes/MessageDebounce/MessageDebounce.node.js.map +1 -0
- package/dist/nodes/MessageDebounce/constants.d.ts +17 -0
- package/dist/nodes/MessageDebounce/constants.d.ts.map +1 -0
- package/dist/nodes/MessageDebounce/constants.js +42 -0
- package/dist/nodes/MessageDebounce/constants.js.map +1 -0
- package/dist/nodes/MessageDebounce/execute.d.ts +13 -0
- package/dist/nodes/MessageDebounce/execute.d.ts.map +1 -0
- package/dist/nodes/MessageDebounce/execute.js +217 -0
- package/dist/nodes/MessageDebounce/execute.js.map +1 -0
- package/dist/nodes/MessageDebounce/helpers.d.ts +19 -0
- package/dist/nodes/MessageDebounce/helpers.d.ts.map +1 -0
- package/dist/nodes/MessageDebounce/helpers.js +43 -0
- package/dist/nodes/MessageDebounce/helpers.js.map +1 -0
- package/dist/nodes/MessageDebounce/messageDebounce.svg +17 -0
- package/dist/nodes/MessageDebounce/types.d.ts +30 -0
- package/dist/nodes/MessageDebounce/types.d.ts.map +1 -0
- package/dist/nodes/MessageDebounce/types.js +7 -0
- package/dist/nodes/MessageDebounce/types.js.map +1 -0
- package/dist/nodes/MessageDebounce/utils/RedisClient.d.ts +59 -0
- package/dist/nodes/MessageDebounce/utils/RedisClient.d.ts.map +1 -0
- package/dist/nodes/MessageDebounce/utils/RedisClient.js +270 -0
- package/dist/nodes/MessageDebounce/utils/RedisClient.js.map +1 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Daniel Reis
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# n8n-nodes-message-debounce
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://uaiautomacao.com/logo.png" alt="U.ai Automação" width="200"/>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://www.npmjs.com/package/n8n-nodes-message-debounce"><img src="https://img.shields.io/npm/v/n8n-nodes-message-debounce.svg" alt="npm version"/></a>
|
|
9
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"/></a>
|
|
10
|
+
<a href="https://uaiautomacao.com"><img src="https://img.shields.io/badge/by-U.ai%20Automa%C3%A7%C3%A3o-blue" alt="by U.ai Automação"/></a>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
An n8n community node that groups multiple inputs within a time window before continuing the flow — preventing your automation from reacting to every single message before the user finishes writing.
|
|
16
|
+
|
|
17
|
+
> **Real-world use case:** A user sends "hi", then "how are you", then "I need help with my order". Without debounce, your flow fires three times. With this node, it waits for silence and processes everything as one consolidated message.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## How it works
|
|
22
|
+
|
|
23
|
+
Every time a message arrives, the node:
|
|
24
|
+
|
|
25
|
+
1. Stores the message in Redis under the session's key
|
|
26
|
+
2. Waits for the configured debounce window
|
|
27
|
+
3. After the wait, checks if any new message arrived during that time
|
|
28
|
+
4. If no new messages arrived → flushes everything as a single output
|
|
29
|
+
5. If a new message arrived → silently stops (the newer execution will take over)
|
|
30
|
+
|
|
31
|
+
When the node is still waiting, **it emits nothing** — the flow simply stops there. No need for IF nodes or filters after it.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
In your n8n instance, go to **Settings → Community Nodes** and install:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
n8n-nodes-message-debounce
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
> **Requirement:** A Redis instance must be accessible from your n8n environment. In production (queue mode), Redis is already required by n8n — so no extra setup needed.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Configuration
|
|
48
|
+
|
|
49
|
+
### Required fields
|
|
50
|
+
|
|
51
|
+
| Field | Description |
|
|
52
|
+
|---|---|
|
|
53
|
+
| **Redis Credential** | Your Redis connection configured in n8n credentials |
|
|
54
|
+
| **Session ID** | Unique identifier for the conversation or session (e.g. chat ID, user ID) |
|
|
55
|
+
| **Message** | The content of the incoming message |
|
|
56
|
+
| **Debounce Window** | Seconds to wait for silence before flushing (e.g. `10`) |
|
|
57
|
+
|
|
58
|
+
### Options
|
|
59
|
+
|
|
60
|
+
| Option | Description | Default |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| **Max Messages** | Force flush after N messages, regardless of the timer | — |
|
|
63
|
+
| **Max Wait Time** | Maximum total seconds before forcing flush, even if messages keep arriving | — |
|
|
64
|
+
| **Flush Keywords** | List of words that trigger an immediate flush when detected in the message | — |
|
|
65
|
+
| **On Duplicate Message** | What to do when the same message arrives twice: `ignore`, `include`, or `flush` | `include` |
|
|
66
|
+
| **First Message Behavior** | Special behavior for the first message of a new session: `immediate` flush or `customWindow` with a shorter timer | — |
|
|
67
|
+
| **Session TTL** | Seconds of inactivity before a session is considered expired (child of First Message Behavior). Set to `never` to keep sessions permanently. | — |
|
|
68
|
+
| **Separator** | String used to join multiple messages | `\n` |
|
|
69
|
+
|
|
70
|
+
> ⚠️ **Memory warning:** If Session TTL is set to `never expire`, sessions will accumulate in Redis indefinitely. Monitor your Redis memory usage.
|
|
71
|
+
|
|
72
|
+
> 💡 **Max Messages + Max Wait Time:** If both are set, whichever condition is met first triggers the flush.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Output
|
|
77
|
+
|
|
78
|
+
When the debounce fires, the node outputs a single item:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"fullMessage": "hi\nhow are you\nI need help with my order",
|
|
83
|
+
"messageCount": 3,
|
|
84
|
+
"flushReason": "debounceWindow"
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### flushReason values
|
|
89
|
+
|
|
90
|
+
| Value | Meaning |
|
|
91
|
+
|---|---|
|
|
92
|
+
| `debounceWindow` | Silence window elapsed normally |
|
|
93
|
+
| `maxMessages` | Max message count reached |
|
|
94
|
+
| `maxWaitTime` | Max wait time reached |
|
|
95
|
+
| `keyword` | A flush keyword was detected |
|
|
96
|
+
| `firstMessage` | First message of a new session with immediate flush configured |
|
|
97
|
+
| `duplicate` | Duplicate message triggered flush |
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Example flow
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
Webhook → [your processing nodes] → Message Debounce → AI Agent
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The node fits anywhere in your flow. Process audio transcriptions, extract documents, enrich data — then pass everything into the debounce node. It handles the rest.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Error handling
|
|
112
|
+
|
|
113
|
+
The node throws explicit errors in the following situations:
|
|
114
|
+
|
|
115
|
+
- **`Debounce Window is required`** — field left empty
|
|
116
|
+
- **`Session ID is required`** — field left empty
|
|
117
|
+
- **`Message is required`** — field left empty
|
|
118
|
+
- **`Redis connection failed: <detail>`** — could not connect to Redis
|
|
119
|
+
- **`Redis operation failed: <detail>`** — an operation failed during execution
|
|
120
|
+
|
|
121
|
+
Errors are always explicit and descriptive — never silent.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Technical notes
|
|
126
|
+
|
|
127
|
+
- All Redis operations use atomic Lua scripts to prevent race conditions in high-concurrency scenarios
|
|
128
|
+
- Messages are tracked by unique ID, not by content — so duplicate messages are handled correctly
|
|
129
|
+
- When suppressed (not the last message), the node emits **no items** and the flow stops cleanly
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## About
|
|
134
|
+
|
|
135
|
+
Built by **[U.ai Automação](https://uaiautomacao.com)** — automation solutions for real-world workflows.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## License
|
|
140
|
+
|
|
141
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis API credential for the MessageDebounce node.
|
|
3
|
+
*
|
|
4
|
+
* Redis connections are validated when the node executes (via RedisClient.connect()).
|
|
5
|
+
* A separate HTTP-based test is not applicable for TCP protocol services like Redis,
|
|
6
|
+
* so the credential-test-required rule is disabled here intentionally.
|
|
7
|
+
*/
|
|
8
|
+
import type { ICredentialType, INodeProperties } from 'n8n-workflow';
|
|
9
|
+
export declare class RedisApi implements ICredentialType {
|
|
10
|
+
name: string;
|
|
11
|
+
displayName: string;
|
|
12
|
+
icon: "file:../../nodes/MessageDebounce/messageDebounce.svg";
|
|
13
|
+
documentationUrl: string;
|
|
14
|
+
properties: INodeProperties[];
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=RedisApi.credentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RedisApi.credentials.d.ts","sourceRoot":"","sources":["../../credentials/RedisApi.credentials.ts"],"names":[],"mappings":"AACA;;;;;;GAMG;AACH,OAAO,KAAK,EACR,eAAe,EACf,eAAe,EAClB,MAAM,cAAc,CAAC;AAEtB,qBAAa,QAAS,YAAW,eAAe;IAC5C,IAAI,SAAc;IAElB,WAAW,SAAe;IAE1B,IAAI,yDAAmE;IAEvE,gBAAgB,SAAoC;IAEpD,UAAU,EAAE,eAAe,EAAE,CAwD3B;CACL"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RedisApi = void 0;
|
|
4
|
+
class RedisApi {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.name = 'redisApi';
|
|
7
|
+
this.displayName = 'Redis API';
|
|
8
|
+
this.icon = 'file:../../nodes/MessageDebounce/messageDebounce.svg';
|
|
9
|
+
this.documentationUrl = 'https://redis.io/docs/connect/';
|
|
10
|
+
this.properties = [
|
|
11
|
+
{
|
|
12
|
+
displayName: 'Password',
|
|
13
|
+
name: 'password',
|
|
14
|
+
type: 'string',
|
|
15
|
+
typeOptions: {
|
|
16
|
+
password: true,
|
|
17
|
+
},
|
|
18
|
+
default: '',
|
|
19
|
+
description: 'The Redis password (leave empty if none)',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
displayName: 'User',
|
|
23
|
+
name: 'username',
|
|
24
|
+
type: 'string',
|
|
25
|
+
default: '',
|
|
26
|
+
description: 'Leave blank for password-only auth',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
displayName: 'Host',
|
|
30
|
+
name: 'host',
|
|
31
|
+
type: 'string',
|
|
32
|
+
default: 'localhost',
|
|
33
|
+
description: 'The Redis host to connect to',
|
|
34
|
+
required: true,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
displayName: 'Port',
|
|
38
|
+
name: 'port',
|
|
39
|
+
type: 'number',
|
|
40
|
+
typeOptions: {
|
|
41
|
+
minValue: 1,
|
|
42
|
+
maxValue: 65535,
|
|
43
|
+
},
|
|
44
|
+
default: 6379,
|
|
45
|
+
description: 'The Redis port to connect to',
|
|
46
|
+
required: true,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
displayName: 'Database Number',
|
|
50
|
+
name: 'database',
|
|
51
|
+
type: 'number',
|
|
52
|
+
typeOptions: {
|
|
53
|
+
minValue: 0,
|
|
54
|
+
maxValue: 15,
|
|
55
|
+
},
|
|
56
|
+
default: 0,
|
|
57
|
+
description: 'The Redis database index (0–15)',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
displayName: 'SSL',
|
|
61
|
+
name: 'tls',
|
|
62
|
+
type: 'boolean',
|
|
63
|
+
default: false,
|
|
64
|
+
description: 'Whether to use TLS/SSL for the Redis connection',
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
exports.RedisApi = RedisApi;
|
|
70
|
+
//# sourceMappingURL=RedisApi.credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RedisApi.credentials.js","sourceRoot":"","sources":["../../credentials/RedisApi.credentials.ts"],"names":[],"mappings":";;;AAaA,MAAa,QAAQ;IAArB;QACI,SAAI,GAAG,UAAU,CAAC;QAElB,gBAAW,GAAG,WAAW,CAAC;QAE1B,SAAI,GAAG,sDAA+D,CAAC;QAEvE,qBAAgB,GAAG,gCAAgC,CAAC;QAEpD,eAAU,GAAsB;YAC5B;gBACI,WAAW,EAAE,UAAU;gBACvB,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE;oBACT,QAAQ,EAAE,IAAI;iBACjB;gBACD,OAAO,EAAE,EAAE;gBACX,WAAW,EAAE,0CAA0C;aAC1D;YACD;gBACI,WAAW,EAAE,MAAM;gBACnB,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,EAAE;gBACX,WAAW,EAAE,oCAAoC;aACpD;YACD;gBACI,WAAW,EAAE,MAAM;gBACnB,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,WAAW;gBACpB,WAAW,EAAE,8BAA8B;gBAC3C,QAAQ,EAAE,IAAI;aACjB;YACD;gBACI,WAAW,EAAE,MAAM;gBACnB,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE;oBACT,QAAQ,EAAE,CAAC;oBACX,QAAQ,EAAE,KAAK;iBAClB;gBACD,OAAO,EAAE,IAAI;gBACb,WAAW,EAAE,8BAA8B;gBAC3C,QAAQ,EAAE,IAAI;aACjB;YACD;gBACI,WAAW,EAAE,iBAAiB;gBAC9B,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE;oBACT,QAAQ,EAAE,CAAC;oBACX,QAAQ,EAAE,EAAE;iBACf;gBACD,OAAO,EAAE,CAAC;gBACV,WAAW,EAAE,iCAAiC;aACjD;YACD;gBACI,WAAW,EAAE,KAAK;gBAClB,IAAI,EAAE,KAAK;gBACX,IAAI,EAAE,SAAS;gBACf,OAAO,EAAE,KAAK;gBACd,WAAW,EAAE,iDAAiD;aACjE;SACJ,CAAC;IACN,CAAC;CAAA;AAlED,4BAkEC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60" fill="none">
|
|
2
|
+
<!-- Background circle -->
|
|
3
|
+
<circle cx="30" cy="30" r="28" fill="#FF6B35" opacity="0.12"/>
|
|
4
|
+
<!-- Clock face -->
|
|
5
|
+
<circle cx="30" cy="30" r="20" stroke="#FF6B35" stroke-width="2.5" fill="none"/>
|
|
6
|
+
<!-- Clock hands -->
|
|
7
|
+
<line x1="30" y1="30" x2="30" y2="16" stroke="#FF6B35" stroke-width="2.5" stroke-linecap="round"/>
|
|
8
|
+
<line x1="30" y1="30" x2="40" y2="35" stroke="#FF6B35" stroke-width="2" stroke-linecap="round"/>
|
|
9
|
+
<!-- Center dot -->
|
|
10
|
+
<circle cx="30" cy="30" r="2" fill="#FF6B35"/>
|
|
11
|
+
<!-- Message dots (debounce indicator) -->
|
|
12
|
+
<circle cx="10" cy="48" r="3" fill="#FF6B35" opacity="0.4"/>
|
|
13
|
+
<circle cx="20" cy="52" r="3" fill="#FF6B35" opacity="0.65"/>
|
|
14
|
+
<circle cx="30" cy="54" r="3" fill="#FF6B35" opacity="0.9"/>
|
|
15
|
+
<!-- Arrow suggesting consolidation -->
|
|
16
|
+
<path d="M38 54 L50 54 L46 50 M50 54 L46 58" stroke="#FF6B35" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" opacity="0.8"/>
|
|
17
|
+
</svg>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI definition for the MessageDebounce node.
|
|
3
|
+
* Contains the complete INodeTypeDescription — what the n8n editor renders.
|
|
4
|
+
* Keep this file focused on structure and labels only; no business logic here.
|
|
5
|
+
*/
|
|
6
|
+
import type { INodeTypeDescription } from 'n8n-workflow';
|
|
7
|
+
export declare const description: INodeTypeDescription;
|
|
8
|
+
//# sourceMappingURL=MessageDebounce.description.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MessageDebounce.description.d.ts","sourceRoot":"","sources":["../../../nodes/MessageDebounce/MessageDebounce.description.ts"],"names":[],"mappings":"AACA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEzD,eAAO,MAAM,WAAW,EAAE,oBAsPzB,CAAC"}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
|
|
3
|
+
/**
|
|
4
|
+
* UI definition for the MessageDebounce node.
|
|
5
|
+
* Contains the complete INodeTypeDescription — what the n8n editor renders.
|
|
6
|
+
* Keep this file focused on structure and labels only; no business logic here.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.description = void 0;
|
|
10
|
+
exports.description = {
|
|
11
|
+
displayName: 'Message Debounce',
|
|
12
|
+
name: 'messageDebounce',
|
|
13
|
+
icon: 'file:messageDebounce.svg',
|
|
14
|
+
group: ['transform'],
|
|
15
|
+
version: 1,
|
|
16
|
+
description: 'Groups messages from the same session within a silence window and emits a single consolidated output. Silently stops when a newer message has already arrived.',
|
|
17
|
+
defaults: {
|
|
18
|
+
name: 'Message Debounce',
|
|
19
|
+
},
|
|
20
|
+
usableAsTool: true,
|
|
21
|
+
inputs: ['main'],
|
|
22
|
+
outputs: ['main'],
|
|
23
|
+
credentials: [
|
|
24
|
+
{
|
|
25
|
+
name: 'redisApi',
|
|
26
|
+
required: true,
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
properties: [
|
|
30
|
+
// -----------------------------------------------------------------------
|
|
31
|
+
// Required fields
|
|
32
|
+
// -----------------------------------------------------------------------
|
|
33
|
+
{
|
|
34
|
+
displayName: 'Session ID',
|
|
35
|
+
name: 'sessionId',
|
|
36
|
+
type: 'string',
|
|
37
|
+
default: '',
|
|
38
|
+
placeholder: 'e.g. {{ $json.chatId }}',
|
|
39
|
+
description: 'Unique identifier for the conversation or session. All messages sharing the same Session ID are grouped together.',
|
|
40
|
+
required: true,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
displayName: 'Message',
|
|
44
|
+
name: 'message',
|
|
45
|
+
type: 'string',
|
|
46
|
+
default: '',
|
|
47
|
+
placeholder: 'e.g. {{ $json.message }}',
|
|
48
|
+
description: 'The content of the incoming message to buffer',
|
|
49
|
+
required: true,
|
|
50
|
+
typeOptions: {
|
|
51
|
+
rows: 3,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
displayName: 'Debounce Window (Seconds)',
|
|
56
|
+
name: 'debounceWindow',
|
|
57
|
+
type: 'number',
|
|
58
|
+
typeOptions: {
|
|
59
|
+
minValue: 1,
|
|
60
|
+
},
|
|
61
|
+
default: 10,
|
|
62
|
+
placeholder: 'e.g. 10',
|
|
63
|
+
description: 'Seconds of silence to wait before flushing all buffered messages. The timer resets each time a new message arrives.',
|
|
64
|
+
required: true,
|
|
65
|
+
},
|
|
66
|
+
// -----------------------------------------------------------------------
|
|
67
|
+
// Options collection — items MUST be in alphabetical order by displayName
|
|
68
|
+
// -----------------------------------------------------------------------
|
|
69
|
+
{
|
|
70
|
+
displayName: 'Options',
|
|
71
|
+
name: 'options',
|
|
72
|
+
type: 'collection',
|
|
73
|
+
placeholder: 'Add Option',
|
|
74
|
+
default: {},
|
|
75
|
+
options: [
|
|
76
|
+
// F
|
|
77
|
+
{
|
|
78
|
+
displayName: 'First Message Behavior',
|
|
79
|
+
name: 'firstMessageBehavior',
|
|
80
|
+
type: 'options',
|
|
81
|
+
default: 'none',
|
|
82
|
+
description: 'Special handling for the very first message of a new or expired session',
|
|
83
|
+
options: [
|
|
84
|
+
{
|
|
85
|
+
name: 'None',
|
|
86
|
+
value: 'none',
|
|
87
|
+
description: 'Treat the first message like any other (default)',
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: 'Flush Immediately',
|
|
91
|
+
value: 'immediate',
|
|
92
|
+
description: 'Emit the first message right away without waiting',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'Use Custom Window',
|
|
96
|
+
value: 'customWindow',
|
|
97
|
+
description: 'Apply a shorter or longer silence window for the first message only',
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
displayName: 'First Message Custom Window (Seconds)',
|
|
103
|
+
name: 'firstMessageCustomWindow',
|
|
104
|
+
type: 'number',
|
|
105
|
+
typeOptions: { minValue: 1 },
|
|
106
|
+
default: 3,
|
|
107
|
+
placeholder: 'e.g. 3',
|
|
108
|
+
description: 'Silence window in seconds to use for the first message of a new session',
|
|
109
|
+
displayOptions: {
|
|
110
|
+
show: {
|
|
111
|
+
firstMessageBehavior: ['customWindow'],
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
displayName: 'Flush Keywords',
|
|
117
|
+
name: 'flushKeywords',
|
|
118
|
+
type: 'fixedCollection',
|
|
119
|
+
default: {},
|
|
120
|
+
typeOptions: {
|
|
121
|
+
multipleValues: true,
|
|
122
|
+
},
|
|
123
|
+
description: 'Words that trigger an immediate flush when detected anywhere in the incoming message (case-insensitive)',
|
|
124
|
+
options: [
|
|
125
|
+
{
|
|
126
|
+
name: 'keywords',
|
|
127
|
+
displayName: 'Keywords',
|
|
128
|
+
values: [
|
|
129
|
+
{
|
|
130
|
+
displayName: 'Keyword',
|
|
131
|
+
name: 'keyword',
|
|
132
|
+
type: 'string',
|
|
133
|
+
default: '',
|
|
134
|
+
placeholder: 'e.g. urgent',
|
|
135
|
+
description: 'A word that triggers immediate flush when found in the message',
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
// M
|
|
142
|
+
{
|
|
143
|
+
displayName: 'Max Messages',
|
|
144
|
+
name: 'maxMessages',
|
|
145
|
+
type: 'number',
|
|
146
|
+
typeOptions: { minValue: 1 },
|
|
147
|
+
default: 0,
|
|
148
|
+
placeholder: 'e.g. 10',
|
|
149
|
+
description: 'Force flush after this many messages are buffered, regardless of the silence timer. Leave at 0 to disable.',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
displayName: 'Max Wait Time (Seconds)',
|
|
153
|
+
name: 'maxWaitTime',
|
|
154
|
+
type: 'number',
|
|
155
|
+
typeOptions: { minValue: 1 },
|
|
156
|
+
default: 0,
|
|
157
|
+
placeholder: 'e.g. 60',
|
|
158
|
+
description: 'Maximum total seconds before forcing a flush, even if messages keep arriving. Whichever condition (Debounce Window or Max Wait Time) is met first triggers the flush. Leave at 0 to disable.',
|
|
159
|
+
},
|
|
160
|
+
// O
|
|
161
|
+
{
|
|
162
|
+
displayName: 'On Duplicate Message',
|
|
163
|
+
name: 'onDuplicateMessage',
|
|
164
|
+
type: 'options',
|
|
165
|
+
default: 'include',
|
|
166
|
+
description: 'What to do when the incoming message is identical to the last buffered one',
|
|
167
|
+
options: [
|
|
168
|
+
{
|
|
169
|
+
name: 'Include',
|
|
170
|
+
value: 'include',
|
|
171
|
+
description: 'Buffer the duplicate message normally (default)',
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: 'Ignore',
|
|
175
|
+
value: 'ignore',
|
|
176
|
+
description: 'Silently discard the duplicate — no output emitted',
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: 'Flush',
|
|
180
|
+
value: 'flush',
|
|
181
|
+
description: 'Treat the duplicate as a flush signal and emit immediately',
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
},
|
|
185
|
+
// S
|
|
186
|
+
{
|
|
187
|
+
displayName: 'Separator',
|
|
188
|
+
name: 'separator',
|
|
189
|
+
type: 'string',
|
|
190
|
+
default: '\n',
|
|
191
|
+
placeholder: 'e.g. \\n',
|
|
192
|
+
description: 'String used to join buffered messages in the final output',
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
displayName: 'Session TTL',
|
|
196
|
+
name: 'sessionTtlUnit',
|
|
197
|
+
type: 'options',
|
|
198
|
+
default: 'never',
|
|
199
|
+
description: 'How long a session stays alive in Redis after the last message. When a session expires, the next message is treated as a first message.',
|
|
200
|
+
hint: 'Only applies when "First Message Behavior" is not set to "None".',
|
|
201
|
+
options: [
|
|
202
|
+
{
|
|
203
|
+
name: 'Days',
|
|
204
|
+
value: 'days',
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: 'Hours',
|
|
208
|
+
value: 'hours',
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
name: 'Minutes',
|
|
212
|
+
value: 'minutes',
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: 'Months',
|
|
216
|
+
value: 'months',
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
name: 'Never Expire',
|
|
220
|
+
value: 'never',
|
|
221
|
+
description: 'Sessions are kept in Redis indefinitely. Monitor your Redis memory usage.',
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
name: 'Years',
|
|
225
|
+
value: 'years',
|
|
226
|
+
},
|
|
227
|
+
],
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
displayName: 'Session TTL Value',
|
|
231
|
+
name: 'sessionTtlValue',
|
|
232
|
+
type: 'number',
|
|
233
|
+
typeOptions: { minValue: 1 },
|
|
234
|
+
default: 30,
|
|
235
|
+
placeholder: 'e.g. 30',
|
|
236
|
+
description: 'Amount of time before the session expires in Redis',
|
|
237
|
+
displayOptions: {
|
|
238
|
+
hide: {
|
|
239
|
+
sessionTtlUnit: ['never'],
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
},
|
|
245
|
+
],
|
|
246
|
+
};
|
|
247
|
+
//# sourceMappingURL=MessageDebounce.description.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MessageDebounce.description.js","sourceRoot":"","sources":["../../../nodes/MessageDebounce/MessageDebounce.description.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MessageDebounce n8n community node — entry point.
|
|
3
|
+
*
|
|
4
|
+
* Intentionally thin: this file wires together the separated modules.
|
|
5
|
+
*
|
|
6
|
+
* Module map:
|
|
7
|
+
* description.ts → INodeTypeDescription (UI definition)
|
|
8
|
+
* execute.ts → runDebounce() (core debounce business logic)
|
|
9
|
+
* types.ts → shared interfaces and types
|
|
10
|
+
* constants.ts → Lua script, TTL multipliers
|
|
11
|
+
* helpers.ts → pure utility functions
|
|
12
|
+
* utils/RedisClient.ts → native RESP2 Redis client (zero external deps)
|
|
13
|
+
*/
|
|
14
|
+
import { type IExecuteFunctions, type INodeExecutionData, type INodeType } from 'n8n-workflow';
|
|
15
|
+
export declare class MessageDebounce implements INodeType {
|
|
16
|
+
description: import("n8n-workflow").INodeTypeDescription;
|
|
17
|
+
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=MessageDebounce.node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MessageDebounce.node.d.ts","sourceRoot":"","sources":["../../../nodes/MessageDebounce/MessageDebounce.node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAEH,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,SAAS,EAEjB,MAAM,cAAc,CAAC;AAStB,qBAAa,eAAgB,YAAW,SAAS;IAC7C,WAAW,8CAAe;IAEpB,OAAO,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC;CA2F1E"}
|