lulz 1.0.0

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/CHICKEN.md ADDED
@@ -0,0 +1,156 @@
1
+ This is the original chiken scratch that started this project
2
+
3
+ ```js
4
+
5
+ const fakeServer = new EventEmitter();
6
+ let counter = 1;
7
+ setInterval(()=>{
8
+ fakeServer.emit('fakeData', counter++);
9
+ },1_000)
10
+
11
+
12
+ // the actual program
13
+
14
+ const context = {
15
+ username: 'alice',
16
+
17
+ };
18
+
19
+ const pipes = {};
20
+
21
+ // PROGRAM EXAMPLE
22
+ const blogBuilder = [
23
+
24
+ [ socket('post'), 'post' ], // monitor web socket for new post announcement
25
+ [ watch('assets'), 'asset' ], // watch assets folder for changes
26
+
27
+ [ 'post', log ], // log whan a new post arrives on post pipe
28
+
29
+ [ 'asset', assets ], // listen to asset pipe and call the asset funcion
30
+ [ 'post', cover, audio, post, 'updated' ], // listen to post pipe and call cover+aufio+post with same data (fan not series like in ffmpeg, series would requre array ['in', [a,b,c], 'out'], where a gets in, sends a out to b, b sends b out to c, and c sends to out pipe (or funcion if a funcion follows the array) this is a todo)
31
+ [ 'updated', pagerizer, log ],
32
+
33
+ ];
34
+
35
+
36
+ const app = flow(blogBuilder, context);
37
+
38
+
39
+ // User Functions (DO NOT IMPLEMENT YET)
40
+
41
+ function log(options){ // outer declaration: allows user to configure the funcion in program array
42
+
43
+ return (send, packet) => { // inner funcion must be arrow as .arguments are used to set inner/outer apart
44
+ console.log( options, this.context, packet );
45
+ send(packet); /* send through */
46
+ }
47
+ }
48
+
49
+ // examples of producers
50
+
51
+ function socket(options){
52
+ return (send)=>{
53
+ fakeServer.on('fakeData', counter=>send(counter))
54
+ }
55
+ }
56
+
57
+ function watch(options){
58
+ return (send)=>{
59
+ fakeServer.on('fakeData', counter=>send(counter))
60
+ }
61
+ }
62
+
63
+
64
+
65
+ // example sink/tranducer, just pass through
66
+ function assets(options){ return (send, packet)=>send(packet)}
67
+ function cover(options){ return (send, packet)=>send(packet)}
68
+ function audio(options){ return (send, packet)=>send(packet)}
69
+ function post(options){ return (send, packet)=>send(packet)}
70
+ function pagerizer(options){ return (send, packet)=>send(packet)}
71
+
72
+
73
+
74
+
75
+
76
+
77
+ // System Functions
78
+
79
+ function flow(graph, context){
80
+
81
+ const directions = {from:0, to:2};
82
+
83
+ const [ inputs, transforms, outputs ] = graph.reduce((a, c)=>{
84
+ let direction = directions.from;
85
+ if(typeof c === 'string'){
86
+ a[direction].push(makePipe(c));
87
+ }else{
88
+ direction = directions.to; // flip direction
89
+ const isOuter = c?.arguments;
90
+ let fn;
91
+ if(isOuter){
92
+ // bind outer funcion
93
+ const bound = c.bind(context)
94
+ fn = bound({}); // apply empty config and retreive inner funcion
95
+ }else{
96
+ // already inner funcion
97
+ // set context to an already configured inner function
98
+ fn.context = context;
99
+ }
100
+
101
+
102
+ a[1].push(makeNode(fn)); // store transform
103
+
104
+ }
105
+ },[[],[],[]]);
106
+
107
+ if(inputs.length){
108
+ for (const input of inputs){
109
+ for( const transform of transforms ){
110
+ input.connect(transform);
111
+ for (const output of outputs){
112
+ transform.connect(output);
113
+ }
114
+ }
115
+ }
116
+ }else{
117
+ for( const transform of transforms ){
118
+ input.connect(transform);
119
+ for (const output of outputs){
120
+ transform.connect(output);
121
+ }
122
+ }
123
+ }
124
+ }
125
+
126
+
127
+
128
+
129
+
130
+
131
+
132
+
133
+
134
+
135
+ function makeNode(name, fn) {
136
+ const outputs = new Set()
137
+ function send(packet) {
138
+ for (const out of outputs) out(packet)
139
+ }
140
+ function input(packet) {
141
+ fn(send, packet)
142
+ }
143
+ input.connect = next => {
144
+ outputs.add(next)
145
+ return next
146
+ }
147
+ input.name = name
148
+ return input
149
+ }
150
+
151
+ makePipe(name){
152
+ if(!pipes[name]) pipes[name] = {type:'pipe', name, through: makeNode(through)};
153
+ return pipes[name];
154
+ }
155
+
156
+ ```
package/README.md ADDED
@@ -0,0 +1,285 @@
1
+ # lulz
2
+
3
+ A reactive dataflow system inspired by FFmpeg filtergraph notation and Node-RED.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install lulz
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```javascript
14
+ const { flow, Inject, Debug } = require('./index');
15
+
16
+ const app = flow([
17
+ [Inject({ payload: 'Hello World!' }), Debug({ name: 'output' })],
18
+ ]);
19
+
20
+ app.start();
21
+ ```
22
+
23
+ ## Core Concepts
24
+
25
+ ### Flow Syntax
26
+
27
+ Each line in a flow is an array: `[source, ...transforms, destination]`
28
+
29
+ - **Source**: A string (pipe name) or producer function
30
+ - **Transforms**: Functions that process packets
31
+ - **Destination**: A string (pipe name) or consumer function
32
+
33
+ ```javascript
34
+ const app = flow([
35
+ [producer, 'pipeName'], // Producer → pipe
36
+ ['pipeName', transform, 'out'], // Pipe → transform → pipe
37
+ ['out', consumer], // Pipe → consumer
38
+ ]);
39
+ ```
40
+
41
+ ### Function Types
42
+
43
+ **Outer functions** (factories) are regular functions that return inner functions:
44
+
45
+ ```javascript
46
+ function myTransform(options) { // Outer - receives config
47
+ return (send, packet) => { // Inner - processes packets
48
+ send({ ...packet, modified: true });
49
+ };
50
+ }
51
+ ```
52
+
53
+ **Inner functions** are arrow functions that do the actual processing:
54
+
55
+ ```javascript
56
+ const passthrough = (send, packet) => send(packet);
57
+ ```
58
+
59
+ The system auto-detects which is which using `fn.hasOwnProperty('prototype')`:
60
+ - Regular functions have `prototype` → outer
61
+ - Arrow functions don't → inner
62
+
63
+ ### Pre-configured vs Auto-configured
64
+
65
+ ```javascript
66
+ ['input', myTransform, 'output'] // Auto-config: called with {}
67
+ ['input', myTransform({ option: 1 }), 'output'] // Pre-configured
68
+ ```
69
+
70
+ ## Processing Modes
71
+
72
+ ### Fan-out (Parallel)
73
+
74
+ All transforms receive the same packet simultaneously:
75
+
76
+ ```javascript
77
+ ['input', transformA, transformB, transformC, 'output']
78
+ // ↓ ↓ ↓
79
+ // packet packet packet
80
+ // ↓ ↓ ↓
81
+ // └───────────┴───────────┘
82
+ // ↓
83
+ // output (receives 3 packets)
84
+ ```
85
+
86
+ ### Series (Sequential)
87
+
88
+ Use `[a, b, c]` syntax for sequential processing:
89
+
90
+ ```javascript
91
+ ['input', [transformA, transformB, transformC], 'output']
92
+ // ↓
93
+ // packet
94
+ // ↓
95
+ // transformA
96
+ // ↓
97
+ // transformB
98
+ // ↓
99
+ // transformC
100
+ // ↓
101
+ // output (receives 1 packet)
102
+ ```
103
+
104
+ ### Mixed
105
+
106
+ Combine both in a single line:
107
+
108
+ ```javascript
109
+ ['input', validate, [enrich, format], notify, 'output']
110
+ // ↓ ↓ ↓
111
+ // fan-out series fan-out
112
+ ```
113
+
114
+ ## Built-in Nodes
115
+
116
+ ### Inject
117
+
118
+ Produces packets on schedule or trigger:
119
+
120
+ ```javascript
121
+ Inject({
122
+ payload: 'hello', // or () => Date.now() for dynamic
123
+ topic: 'greeting',
124
+ once: true, // Emit once on start
125
+ onceDelay: 100, // Delay before first emit (ms)
126
+ interval: 1000, // Repeat interval (ms)
127
+ })
128
+ ```
129
+
130
+ ### Debug
131
+
132
+ Logs packets:
133
+
134
+ ```javascript
135
+ Debug({
136
+ name: 'my-debug',
137
+ active: true,
138
+ complete: false, // true = show full msg, false = payload only
139
+ logger: console.log, // Custom logger function
140
+ })
141
+ ```
142
+
143
+ ### Function
144
+
145
+ Execute custom JavaScript:
146
+
147
+ ```javascript
148
+ Function({
149
+ func: (msg, context) => {
150
+ return { ...msg, payload: msg.payload.toUpperCase() };
151
+ },
152
+ })
153
+ ```
154
+
155
+ Return `null` to drop the message.
156
+
157
+ ### Change
158
+
159
+ Modify message properties:
160
+
161
+ ```javascript
162
+ Change({
163
+ rules: [
164
+ { type: 'set', prop: 'payload', to: 'new value' },
165
+ { type: 'set', prop: 'topic', to: (msg) => msg.payload.type },
166
+ { type: 'change', prop: 'payload', from: /old/, to: 'new' },
167
+ { type: 'delete', prop: 'unwanted' },
168
+ { type: 'move', prop: 'payload', to: 'data' },
169
+ ]
170
+ })
171
+ ```
172
+
173
+ ### Switch
174
+
175
+ Route messages based on conditions:
176
+
177
+ ```javascript
178
+ Switch({
179
+ property: 'payload',
180
+ rules: [
181
+ { type: 'eq', value: 'hello' },
182
+ { type: 'gt', value: 100 },
183
+ { type: 'regex', value: /^test/ },
184
+ { type: 'else' },
185
+ ],
186
+ checkall: false, // Stop at first match
187
+ })
188
+ ```
189
+
190
+ Rule types: `eq`, `neq`, `lt`, `gt`, `lte`, `gte`, `regex`, `true`, `false`, `null`, `nnull`, `else`
191
+
192
+ ### Template
193
+
194
+ Render mustache templates:
195
+
196
+ ```javascript
197
+ Template({
198
+ template: 'Hello {{name}}, you have {{count}} messages!',
199
+ field: 'payload', // Output field
200
+ })
201
+ ```
202
+
203
+ ### Delay
204
+
205
+ Delay or rate-limit messages:
206
+
207
+ ```javascript
208
+ Delay({
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
+ ```
214
+
215
+ ### Split
216
+
217
+ Split arrays/strings into sequences:
218
+
219
+ ```javascript
220
+ Split({
221
+ property: 'payload',
222
+ delimiter: ',', // For strings, or 'array' for arrays
223
+ })
224
+ ```
225
+
226
+ ### Join
227
+
228
+ Join sequences back together:
229
+
230
+ ```javascript
231
+ Join({
232
+ count: 5, // Emit after N messages
233
+ property: 'payload',
234
+ mode: 'manual', // 'auto', 'manual', 'reduce'
235
+ })
236
+ ```
237
+
238
+ ## Subflows
239
+
240
+ Create reusable flow components:
241
+
242
+ ```javascript
243
+ const sanitizer = subflow([
244
+ ['in', Function({ func: (msg) => ({
245
+ ...msg,
246
+ payload: String(msg.payload).trim().toLowerCase()
247
+ })}), 'out'],
248
+ ]);
249
+
250
+ // Wire into main flow
251
+ mainFlow.pipes['input'].connect(sanitizer._input);
252
+ sanitizer._output.connect(mainFlow.pipes['process']);
253
+ ```
254
+
255
+ ## API
256
+
257
+ ### `flow(graph, context)`
258
+
259
+ Create a new flow from a graph definition.
260
+
261
+ Returns:
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
267
+
268
+ ### `subflow(graph, context)`
269
+
270
+ Create a subflow with `in` and `out` pipes.
271
+
272
+ ### `compose(...flows)`
273
+
274
+ Connect multiple flows in sequence.
275
+
276
+ ## Running
277
+
278
+ ```bash
279
+ npm test # Run tests
280
+ npm run examples # Run examples
281
+ ```
282
+
283
+ ## License
284
+
285
+ MIT