lulz 1.0.3 → 2.0.1
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/AGENTS.md +609 -0
- package/README.md +242 -140
- package/TODO.md +132 -0
- package/examples.js +169 -197
- package/index.js +164 -14
- package/package.json +16 -17
- package/src/flow.js +362 -215
- package/src/red-lib.js +595 -0
- package/src/rx-lib.js +679 -0
- package/src/utils.js +270 -0
- package/src/workers.js +367 -0
- package/test.js +505 -279
- package/src/nodes.js +0 -520
package/README.md
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
1
|
# lulz
|
|
2
2
|
|
|
3
|
-
A reactive dataflow system
|
|
3
|
+
A reactive dataflow system that makes coders happy. 🎉
|
|
4
|
+
|
|
5
|
+
Inspired by FFmpeg filtergraph notation, Node-RED, and RxJS.
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
import { flow, inject, debug } from 'lulz';
|
|
9
|
+
|
|
10
|
+
const app = flow([
|
|
11
|
+
[inject({ payload: 'Hello!' }), debug({ name: 'out' })],
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
app.start();
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
- **EventEmitter API** - Natural `emit`/`on` for packet injection and interception
|
|
20
|
+
- **Series by Default** - `['in', a, b, c, 'out']` processes sequentially
|
|
21
|
+
- **Explicit Parallel** - `['in', [a, b, c], 'out']` fans out to all
|
|
22
|
+
- **Auto-compose** - Embed flows within flows seamlessly
|
|
23
|
+
- **Worker Pool** - CPU-bound tasks with Worker Threads/Web Workers
|
|
24
|
+
- **RxJS Operators** - `map`, `filter`, `scan`, `combineLatest`, and more
|
|
25
|
+
- **Node-RED Style** - `inject`, `debug`, `func`, `switch`, `template`
|
|
4
26
|
|
|
5
27
|
## Installation
|
|
6
28
|
|
|
@@ -11,275 +33,355 @@ npm install lulz
|
|
|
11
33
|
## Quick Start
|
|
12
34
|
|
|
13
35
|
```javascript
|
|
14
|
-
import { flow,
|
|
36
|
+
import { flow, inject, debug, func } from 'lulz';
|
|
15
37
|
|
|
38
|
+
// Create a flow
|
|
16
39
|
const app = flow([
|
|
17
|
-
[
|
|
40
|
+
[inject({ payload: 'Hello World!' }), 'input'],
|
|
41
|
+
['input', func({ func: msg => ({ ...msg, payload: msg.payload.toUpperCase() }) }), 'output'],
|
|
42
|
+
['output', debug({ name: 'result' })],
|
|
18
43
|
]);
|
|
19
44
|
|
|
45
|
+
// Start producers
|
|
20
46
|
app.start();
|
|
21
|
-
```
|
|
22
47
|
|
|
23
|
-
|
|
48
|
+
// Or inject manually via EventEmitter API
|
|
49
|
+
app.emit('input', { payload: 'Manual injection!' });
|
|
24
50
|
|
|
25
|
-
|
|
51
|
+
// Listen to any pipe
|
|
52
|
+
app.on('output', (packet) => {
|
|
53
|
+
console.log('Received:', packet.payload);
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Processing Modes
|
|
26
58
|
|
|
27
|
-
|
|
59
|
+
### Series (Default)
|
|
28
60
|
|
|
29
|
-
|
|
30
|
-
- **Transforms**: Functions that process packets
|
|
31
|
-
- **Destination**: A string (pipe name) or consumer function
|
|
61
|
+
Functions process sequentially. Output of one becomes input of next.
|
|
32
62
|
|
|
33
63
|
```javascript
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
['out', consumer], // Pipe → consumer
|
|
38
|
-
]);
|
|
64
|
+
['input', validate, transform, save, 'output']
|
|
65
|
+
// ↓ ↓ ↓
|
|
66
|
+
// packet → packet → packet → output
|
|
39
67
|
```
|
|
40
68
|
|
|
41
|
-
###
|
|
69
|
+
### Parallel (Explicit)
|
|
42
70
|
|
|
43
|
-
|
|
71
|
+
Use `[]` or `parallel()` to fan out. All receive the same packet.
|
|
44
72
|
|
|
45
73
|
```javascript
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
74
|
+
['input', [processA, processB, processC], 'output']
|
|
75
|
+
// ↓ ↓ ↓
|
|
76
|
+
// packet packet packet
|
|
77
|
+
// ↓ ↓ ↓
|
|
78
|
+
// └─────────┴─────────┘
|
|
79
|
+
// ↓
|
|
80
|
+
// output (3 packets)
|
|
51
81
|
```
|
|
52
82
|
|
|
53
|
-
|
|
83
|
+
### Helper Functions
|
|
54
84
|
|
|
55
85
|
```javascript
|
|
56
|
-
|
|
86
|
+
import { series, parallel } from 'lulz';
|
|
87
|
+
|
|
88
|
+
// Explicit series (same as default, but clearer)
|
|
89
|
+
['input', series(a, b, c), 'output']
|
|
90
|
+
|
|
91
|
+
// Explicit parallel
|
|
92
|
+
['input', parallel(a, b, c), 'output']
|
|
57
93
|
```
|
|
58
94
|
|
|
59
|
-
|
|
60
|
-
- Regular functions have `prototype` → outer
|
|
61
|
-
- Arrow functions don't → inner
|
|
95
|
+
## EventEmitter API
|
|
62
96
|
|
|
63
|
-
|
|
97
|
+
Every flow is an EventEmitter. Pipes are events.
|
|
64
98
|
|
|
65
99
|
```javascript
|
|
66
|
-
|
|
67
|
-
['
|
|
100
|
+
const app = flow([
|
|
101
|
+
['data', transform, 'result'],
|
|
102
|
+
]);
|
|
103
|
+
|
|
104
|
+
// Inject packets
|
|
105
|
+
app.emit('data', { payload: 42 });
|
|
106
|
+
|
|
107
|
+
// Listen to pipes
|
|
108
|
+
app.on('result', (packet) => {
|
|
109
|
+
console.log(packet.payload);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Also works
|
|
113
|
+
app.inject('data', { payload: 42 });
|
|
68
114
|
```
|
|
69
115
|
|
|
70
|
-
##
|
|
116
|
+
## Function Types
|
|
71
117
|
|
|
72
|
-
###
|
|
118
|
+
### Outer Functions (Factories)
|
|
73
119
|
|
|
74
|
-
|
|
120
|
+
Regular functions that return inner functions. Called with options.
|
|
75
121
|
|
|
76
122
|
```javascript
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
123
|
+
function myTransform(options) {
|
|
124
|
+
const { multiplier = 1 } = options;
|
|
125
|
+
return (send, packet) => {
|
|
126
|
+
send({ ...packet, payload: packet.payload * multiplier });
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Usage
|
|
131
|
+
['input', myTransform({ multiplier: 2 }), 'output'] // Pre-configured
|
|
132
|
+
['input', myTransform, 'output'] // Auto-configured with {}
|
|
84
133
|
```
|
|
85
134
|
|
|
86
|
-
###
|
|
135
|
+
### Inner Functions (Processors)
|
|
87
136
|
|
|
88
|
-
|
|
137
|
+
Arrow functions that process packets. Used directly.
|
|
89
138
|
|
|
90
139
|
```javascript
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
// ↓
|
|
97
|
-
// transformB
|
|
98
|
-
// ↓
|
|
99
|
-
// transformC
|
|
100
|
-
// ↓
|
|
101
|
-
// output (receives 1 packet)
|
|
140
|
+
const double = (send, packet) => {
|
|
141
|
+
send({ ...packet, payload: packet.payload * 2 });
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
['input', double, 'output']
|
|
102
145
|
```
|
|
103
146
|
|
|
104
|
-
|
|
147
|
+
## Subflows
|
|
105
148
|
|
|
106
|
-
|
|
149
|
+
Create reusable flow components.
|
|
107
150
|
|
|
108
151
|
```javascript
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
//
|
|
152
|
+
import { subflow, compose } from 'lulz';
|
|
153
|
+
|
|
154
|
+
// Create reusable component
|
|
155
|
+
const sanitizer = subflow([
|
|
156
|
+
['in', func({ func: msg => ({
|
|
157
|
+
...msg,
|
|
158
|
+
payload: String(msg.payload).trim().toLowerCase()
|
|
159
|
+
})}), 'out'],
|
|
160
|
+
]);
|
|
161
|
+
|
|
162
|
+
// Use via compose
|
|
163
|
+
const pipeline = compose(sanitizer, validator, enricher);
|
|
164
|
+
pipeline.emit('in', { payload: ' HELLO ' });
|
|
165
|
+
|
|
166
|
+
// Or embed in flow (auto-compose)
|
|
167
|
+
const app = flow([
|
|
168
|
+
['input', sanitizer, 'output'],
|
|
169
|
+
]);
|
|
112
170
|
```
|
|
113
171
|
|
|
114
|
-
##
|
|
172
|
+
## Node-RED Style Nodes
|
|
115
173
|
|
|
116
|
-
###
|
|
174
|
+
### inject
|
|
117
175
|
|
|
118
|
-
|
|
176
|
+
Produce packets on schedule.
|
|
119
177
|
|
|
120
178
|
```javascript
|
|
121
|
-
|
|
122
|
-
payload: 'hello',
|
|
179
|
+
inject({
|
|
180
|
+
payload: 'hello', // or () => Date.now()
|
|
123
181
|
topic: 'greeting',
|
|
124
|
-
once: true,
|
|
125
|
-
onceDelay: 100,
|
|
126
|
-
interval: 1000,
|
|
182
|
+
once: true, // Emit once on start
|
|
183
|
+
onceDelay: 100, // Delay before first
|
|
184
|
+
interval: 1000, // Repeat interval (ms)
|
|
127
185
|
})
|
|
128
186
|
```
|
|
129
187
|
|
|
130
|
-
###
|
|
188
|
+
### debug
|
|
131
189
|
|
|
132
|
-
|
|
190
|
+
Log packets.
|
|
133
191
|
|
|
134
192
|
```javascript
|
|
135
|
-
|
|
193
|
+
debug({
|
|
136
194
|
name: 'my-debug',
|
|
137
195
|
active: true,
|
|
138
|
-
complete: false,
|
|
139
|
-
logger: console.log,
|
|
196
|
+
complete: false, // true = full packet
|
|
197
|
+
logger: console.log,
|
|
140
198
|
})
|
|
141
199
|
```
|
|
142
200
|
|
|
143
|
-
###
|
|
201
|
+
### func
|
|
144
202
|
|
|
145
|
-
Execute custom
|
|
203
|
+
Execute custom code.
|
|
146
204
|
|
|
147
205
|
```javascript
|
|
148
|
-
|
|
206
|
+
func({
|
|
149
207
|
func: (msg, context) => {
|
|
150
208
|
return { ...msg, payload: msg.payload.toUpperCase() };
|
|
151
209
|
},
|
|
152
210
|
})
|
|
153
211
|
```
|
|
154
212
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
### Change
|
|
213
|
+
### change
|
|
158
214
|
|
|
159
|
-
Modify
|
|
215
|
+
Modify properties.
|
|
160
216
|
|
|
161
217
|
```javascript
|
|
162
|
-
|
|
218
|
+
change({
|
|
163
219
|
rules: [
|
|
164
220
|
{ type: 'set', prop: 'payload', to: 'new value' },
|
|
165
|
-
{ type: 'set', prop: 'topic', to: (msg) => msg.payload.type },
|
|
166
221
|
{ type: 'change', prop: 'payload', from: /old/, to: 'new' },
|
|
167
|
-
{ type: 'delete', prop: '
|
|
168
|
-
{ type: 'move', prop: '
|
|
222
|
+
{ type: 'delete', prop: 'temp' },
|
|
223
|
+
{ type: 'move', prop: 'data', to: 'payload' },
|
|
169
224
|
]
|
|
170
225
|
})
|
|
171
226
|
```
|
|
172
227
|
|
|
173
|
-
###
|
|
228
|
+
### switchNode
|
|
174
229
|
|
|
175
|
-
Route
|
|
230
|
+
Route by conditions.
|
|
176
231
|
|
|
177
232
|
```javascript
|
|
178
|
-
|
|
233
|
+
switchNode({
|
|
179
234
|
property: 'payload',
|
|
180
235
|
rules: [
|
|
181
|
-
{ type: 'eq', value: 'hello' },
|
|
182
236
|
{ type: 'gt', value: 100 },
|
|
183
237
|
{ type: 'regex', value: /^test/ },
|
|
184
238
|
{ type: 'else' },
|
|
185
239
|
],
|
|
186
|
-
checkall: false, // Stop at first match
|
|
187
240
|
})
|
|
188
241
|
```
|
|
189
242
|
|
|
190
|
-
|
|
243
|
+
### template
|
|
191
244
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
Render mustache templates:
|
|
245
|
+
Render templates.
|
|
195
246
|
|
|
196
247
|
```javascript
|
|
197
|
-
|
|
198
|
-
template: 'Hello {{name}}
|
|
199
|
-
field: 'payload',
|
|
248
|
+
template({
|
|
249
|
+
template: 'Hello {{name}}!',
|
|
250
|
+
field: 'payload',
|
|
200
251
|
})
|
|
201
252
|
```
|
|
202
253
|
|
|
203
|
-
|
|
254
|
+
## RxJS-Style Operators
|
|
204
255
|
|
|
205
|
-
|
|
256
|
+
### Transformation
|
|
206
257
|
|
|
207
258
|
```javascript
|
|
208
|
-
|
|
209
|
-
delay: 1000, // Delay in ms
|
|
210
|
-
rate: 10, // Or rate limit (msgs/sec)
|
|
211
|
-
drop: false, // Drop vs queue when rate limited
|
|
212
|
-
})
|
|
213
|
-
```
|
|
259
|
+
import { map, scan, pluck, pairwise, buffer } from 'lulz';
|
|
214
260
|
|
|
215
|
-
|
|
261
|
+
map({ fn: x => x * 2 })
|
|
262
|
+
scan({ reducer: (acc, x) => acc + x, initial: 0 })
|
|
263
|
+
pluck({ path: 'data.value' })
|
|
264
|
+
pairwise()
|
|
265
|
+
buffer({ count: 5 })
|
|
266
|
+
```
|
|
216
267
|
|
|
217
|
-
|
|
268
|
+
### Filtering
|
|
218
269
|
|
|
219
270
|
```javascript
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
})
|
|
271
|
+
import { filter, take, skip, distinct, distinctUntilChanged } from 'lulz';
|
|
272
|
+
|
|
273
|
+
filter({ predicate: x => x > 0 })
|
|
274
|
+
take({ count: 5 })
|
|
275
|
+
skip({ count: 2 })
|
|
276
|
+
distinct()
|
|
277
|
+
distinctUntilChanged()
|
|
224
278
|
```
|
|
225
279
|
|
|
226
|
-
###
|
|
280
|
+
### Timing
|
|
227
281
|
|
|
228
|
-
|
|
282
|
+
```javascript
|
|
283
|
+
import { debounce, throttle, delay, timeout } from 'lulz';
|
|
284
|
+
|
|
285
|
+
debounce({ time: 300 })
|
|
286
|
+
throttle({ time: 1000 })
|
|
287
|
+
delay({ time: 500 })
|
|
288
|
+
timeout({ time: 5000 })
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Combination
|
|
229
292
|
|
|
230
293
|
```javascript
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
})
|
|
294
|
+
import { combineLatest, merge, zip, withLatestFrom } from 'lulz';
|
|
295
|
+
|
|
296
|
+
combineLatest({ pipes: ['temp', 'humidity'] })
|
|
297
|
+
merge()
|
|
298
|
+
zip({ sources: 2 })
|
|
236
299
|
```
|
|
237
300
|
|
|
238
|
-
##
|
|
301
|
+
## Worker Pool
|
|
239
302
|
|
|
240
|
-
|
|
303
|
+
Process CPU-bound tasks in parallel using Worker Threads.
|
|
241
304
|
|
|
242
305
|
```javascript
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
306
|
+
import { taskQueue, worker, parallelMap } from 'lulz';
|
|
307
|
+
|
|
308
|
+
// Standalone task queue
|
|
309
|
+
const queue = taskQueue({
|
|
310
|
+
workers: 4,
|
|
311
|
+
handler: (data) => heavyComputation(data),
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
queue.on('result', ({ id, result }) => console.log(result));
|
|
315
|
+
queue.submit({ data: 42 });
|
|
316
|
+
await queue.drain();
|
|
317
|
+
|
|
318
|
+
// In a flow
|
|
319
|
+
const app = flow([
|
|
320
|
+
['input', worker({
|
|
321
|
+
workers: 4,
|
|
322
|
+
handler: (data) => data * data,
|
|
323
|
+
}), 'output'],
|
|
248
324
|
]);
|
|
325
|
+
```
|
|
249
326
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
327
|
+
## API Reference
|
|
328
|
+
|
|
329
|
+
### flow(graph, context?)
|
|
330
|
+
|
|
331
|
+
Create a new flow.
|
|
332
|
+
|
|
333
|
+
```javascript
|
|
334
|
+
const app = flow([...], { username: 'alice' });
|
|
335
|
+
|
|
336
|
+
app.start(); // Start producers
|
|
337
|
+
app.stop(); // Stop producers
|
|
338
|
+
app.emit(pipe, packet); // Inject packet
|
|
339
|
+
app.on(pipe, handler); // Listen to pipe
|
|
340
|
+
app.pipe(name); // Get pipe node
|
|
253
341
|
```
|
|
254
342
|
|
|
255
|
-
|
|
343
|
+
### subflow(graph, context?)
|
|
256
344
|
|
|
257
|
-
|
|
345
|
+
Create a reusable flow with `in`/`out` pipes.
|
|
258
346
|
|
|
259
|
-
|
|
347
|
+
### compose(...flows)
|
|
260
348
|
|
|
261
|
-
|
|
262
|
-
- `start()` - Start all producers
|
|
263
|
-
- `stop()` - Stop all producers
|
|
264
|
-
- `inject(pipeName, packet)` - Inject a packet into a pipe
|
|
265
|
-
- `getPipe(name)` - Get a pipe by name
|
|
266
|
-
- `pipes` - Object of all pipes
|
|
349
|
+
Connect flows in sequence.
|
|
267
350
|
|
|
268
|
-
###
|
|
351
|
+
### series(...fns) / parallel(...fns)
|
|
269
352
|
|
|
270
|
-
|
|
353
|
+
Explicit processing mode markers.
|
|
271
354
|
|
|
272
|
-
|
|
355
|
+
## Project Structure
|
|
273
356
|
|
|
274
|
-
|
|
357
|
+
```
|
|
358
|
+
lulz/
|
|
359
|
+
├── index.js # Main exports
|
|
360
|
+
├── src/
|
|
361
|
+
│ ├── flow.js # Core engine
|
|
362
|
+
│ ├── red-lib.js # Node-RED style nodes
|
|
363
|
+
│ ├── rx-lib.js # RxJS operators
|
|
364
|
+
│ ├── workers.js # Worker pool
|
|
365
|
+
│ └── utils.js # Utilities
|
|
366
|
+
├── test.js
|
|
367
|
+
├── examples.js
|
|
368
|
+
├── TODO.md # Future operators
|
|
369
|
+
└── README.md
|
|
370
|
+
```
|
|
275
371
|
|
|
276
372
|
## Running
|
|
277
373
|
|
|
278
374
|
```bash
|
|
279
|
-
npm test
|
|
280
|
-
npm run examples
|
|
375
|
+
npm test # Run tests
|
|
376
|
+
npm run examples # Run examples
|
|
281
377
|
```
|
|
282
378
|
|
|
283
379
|
## License
|
|
284
380
|
|
|
285
381
|
MIT
|
|
382
|
+
|
|
383
|
+
## Links
|
|
384
|
+
|
|
385
|
+
- [GitHub](https://github.com/catpea/lulz)
|
|
386
|
+
- [Node-RED](https://nodered.org/)
|
|
387
|
+
- [RxJS](https://rxjs.dev/)
|
package/TODO.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# lulz - TODO
|
|
2
|
+
|
|
3
|
+
## RxJS Operators to Add Later
|
|
4
|
+
|
|
5
|
+
These operators are less commonly used but might be useful in specific scenarios.
|
|
6
|
+
|
|
7
|
+
### Combination Operators
|
|
8
|
+
|
|
9
|
+
- [ ] `forkJoin` - Wait for all observables to complete, emit last values
|
|
10
|
+
- [ ] `race` - First to emit wins
|
|
11
|
+
- [ ] `combineAll` - Combine all inner observables
|
|
12
|
+
- [ ] `concatAll` - Concat all inner observables
|
|
13
|
+
- [ ] `mergeAll` - Merge all inner observables
|
|
14
|
+
- [ ] `switchAll` - Switch to latest inner observable
|
|
15
|
+
- [ ] `startWith` - Prepend values
|
|
16
|
+
- [ ] `endWith` - Append values
|
|
17
|
+
|
|
18
|
+
### Transformation Operators
|
|
19
|
+
|
|
20
|
+
- [ ] `bufferCount` - Buffer by count
|
|
21
|
+
- [ ] `bufferTime` - Buffer by time
|
|
22
|
+
- [ ] `bufferToggle` - Buffer between open/close signals
|
|
23
|
+
- [ ] `bufferWhen` - Buffer until closing selector
|
|
24
|
+
- [ ] `concatMap` - Map and concat
|
|
25
|
+
- [ ] `exhaustMap` - Ignore new while processing
|
|
26
|
+
- [ ] `expand` - Recursively project
|
|
27
|
+
- [ ] `groupBy` - Group by key
|
|
28
|
+
- [ ] `mapTo` - Map to constant
|
|
29
|
+
- [ ] `mergeMap` / `flatMap` - Map and merge
|
|
30
|
+
- [ ] `mergeScan` - Scan with merge
|
|
31
|
+
- [ ] `partition` - Split into two streams
|
|
32
|
+
- [ ] `switchMap` - Switch to new on each emission
|
|
33
|
+
- [ ] `windowCount` - Window by count
|
|
34
|
+
- [ ] `windowTime` - Window by time
|
|
35
|
+
- [ ] `windowToggle` - Window between signals
|
|
36
|
+
- [ ] `windowWhen` - Window by closing selector
|
|
37
|
+
|
|
38
|
+
### Filtering Operators
|
|
39
|
+
|
|
40
|
+
- [ ] `audit` - Ignore during duration selector
|
|
41
|
+
- [ ] `auditTime` - Ignore during time window
|
|
42
|
+
- [ ] `debounceTime` - (alias for debounce with time)
|
|
43
|
+
- [ ] `elementAt` - Emit only Nth element
|
|
44
|
+
- [ ] `first` - Emit first (or first matching)
|
|
45
|
+
- [ ] `ignoreElements` - Ignore all, only complete/error
|
|
46
|
+
- [ ] `last` - Emit last (or last matching)
|
|
47
|
+
- [ ] `sample` - Sample on signal
|
|
48
|
+
- [ ] `sampleTime` - Sample at interval
|
|
49
|
+
- [ ] `single` - Emit only if single match
|
|
50
|
+
- [ ] `skipLast` - Skip last N
|
|
51
|
+
- [ ] `skipUntil` - Skip until signal
|
|
52
|
+
- [ ] `takeLast` - Take last N
|
|
53
|
+
- [ ] `takeUntil` - Take until signal
|
|
54
|
+
- [ ] `throttleTime` - (alias for throttle)
|
|
55
|
+
|
|
56
|
+
### Utility Operators
|
|
57
|
+
|
|
58
|
+
- [ ] `dematerialize` - Convert notification objects
|
|
59
|
+
- [ ] `materialize` - Wrap in notification objects
|
|
60
|
+
- [ ] `observeOn` - Schedule emissions
|
|
61
|
+
- [ ] `subscribeOn` - Schedule subscription
|
|
62
|
+
- [ ] `timeInterval` - Emit time between values
|
|
63
|
+
- [ ] `finalize` - Execute on complete/error
|
|
64
|
+
- [ ] `repeat` - Repeat N times
|
|
65
|
+
- [ ] `repeatWhen` - Repeat based on notifier
|
|
66
|
+
|
|
67
|
+
### Error Handling
|
|
68
|
+
|
|
69
|
+
- [ ] `retryWhen` - Retry based on notifier
|
|
70
|
+
- [ ] `onErrorResumeNext` - Continue with next on error
|
|
71
|
+
|
|
72
|
+
### Multicasting
|
|
73
|
+
|
|
74
|
+
- [ ] `multicast` - Share with subject
|
|
75
|
+
- [ ] `publish` - Share via publish subject
|
|
76
|
+
- [ ] `publishBehavior` - Share via behavior subject
|
|
77
|
+
- [ ] `publishLast` - Share via async subject
|
|
78
|
+
- [ ] `publishReplay` - Share via replay subject
|
|
79
|
+
- [ ] `refCount` - Auto connect/disconnect
|
|
80
|
+
- [ ] `shareReplay` - Share and replay
|
|
81
|
+
|
|
82
|
+
## Node-RED Nodes to Add
|
|
83
|
+
|
|
84
|
+
### Network
|
|
85
|
+
|
|
86
|
+
- [ ] `http in` - HTTP endpoint
|
|
87
|
+
- [ ] `http request` - HTTP client
|
|
88
|
+
- [ ] `http response` - HTTP response
|
|
89
|
+
- [ ] `websocket in` - WebSocket listener
|
|
90
|
+
- [ ] `websocket out` - WebSocket sender
|
|
91
|
+
- [ ] `tcp in` / `tcp out` / `tcp request` - TCP nodes
|
|
92
|
+
- [ ] `udp in` / `udp out` - UDP nodes
|
|
93
|
+
- [ ] `mqtt in` / `mqtt out` - MQTT nodes
|
|
94
|
+
|
|
95
|
+
### Storage
|
|
96
|
+
|
|
97
|
+
- [ ] `file in` - Read file
|
|
98
|
+
- [ ] `file out` - Write file
|
|
99
|
+
- [ ] `watch` - Watch file/directory
|
|
100
|
+
|
|
101
|
+
### Parsers
|
|
102
|
+
|
|
103
|
+
- [ ] `csv` - Parse/generate CSV
|
|
104
|
+
- [ ] `html` - Parse HTML
|
|
105
|
+
- [ ] `json` - Parse/stringify JSON
|
|
106
|
+
- [ ] `xml` - Parse/generate XML
|
|
107
|
+
- [ ] `yaml` - Parse/generate YAML
|
|
108
|
+
|
|
109
|
+
### Sequence
|
|
110
|
+
|
|
111
|
+
- [ ] `batch` - Batch messages
|
|
112
|
+
- [ ] `sort` - Sort messages
|
|
113
|
+
|
|
114
|
+
## Worker Enhancements
|
|
115
|
+
|
|
116
|
+
- [ ] Browser Web Worker support
|
|
117
|
+
- [ ] SharedArrayBuffer for zero-copy data transfer
|
|
118
|
+
- [ ] Worker pool auto-scaling
|
|
119
|
+
- [ ] Priority queue
|
|
120
|
+
- [ ] Task cancellation
|
|
121
|
+
- [ ] Progress reporting
|
|
122
|
+
|
|
123
|
+
## Core Improvements
|
|
124
|
+
|
|
125
|
+
- [ ] Better TypeScript definitions
|
|
126
|
+
- [ ] Flow visualization / debugging
|
|
127
|
+
- [ ] Hot reloading of flows
|
|
128
|
+
- [ ] Persistence / checkpointing
|
|
129
|
+
- [ ] Metrics / monitoring
|
|
130
|
+
- [ ] Backpressure handling
|
|
131
|
+
- [ ] Dead letter queue for failed messages
|
|
132
|
+
- [ ] Circuit breaker pattern
|