node-red-contrib-prib-functions 0.23.3 → 0.26.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.
Files changed (41) hide show
  1. package/.github/copilot-instructions.md +36 -0
  2. package/README.md +15 -0
  3. package/columnar/columnar.html +258 -0
  4. package/columnar/columnar.js +1055 -0
  5. package/columnar/icons/columnar.svg +38 -0
  6. package/fileSystem/filesystem.html +299 -0
  7. package/fileSystem/filesystem.js +170 -0
  8. package/gitlab/gitlab.html +191 -0
  9. package/gitlab/gitlab.js +248 -0
  10. package/gitlab/icons/gitlab.svg +17 -0
  11. package/lib/typedInput.js +18 -2
  12. package/logisticRegression/icons/logisticregression.svg +22 -0
  13. package/logisticRegression/logisticRegression.html +136 -0
  14. package/logisticRegression/logisticRegression.js +83 -0
  15. package/package.json +19 -8
  16. package/test/columnar.js +509 -0
  17. package/test/data/.config.nodes.json +114 -70
  18. package/test/data/.config.nodes.json.backup +104 -71
  19. package/test/data/.config.runtime.json +2 -1
  20. package/test/data/.config.runtime.json.backup +2 -1
  21. package/test/data/.config.users.json +3 -2
  22. package/test/data/.config.users.json.backup +3 -2
  23. package/test/data/.flow.json.backup +561 -5
  24. package/test/data/flow.json +571 -2
  25. package/test/data/package-lock.json +1 -1
  26. package/test/data/shares/.config.nodes.json +74 -52
  27. package/test/data/shares/.config.nodes.json.backup +589 -0
  28. package/test/data/shares/.config.runtime.json +2 -1
  29. package/test/data/shares/.config.runtime.json.backup +2 -1
  30. package/test/data/shares/.config.users.json +3 -2
  31. package/test/data/shares/.config.users.json.backup +5 -1
  32. package/test/dataAnalysisExtensions.js +93 -93
  33. package/test/logisticRegression.js +379 -0
  34. package/test/transform.js +11 -11
  35. package/test/transformConfluence.js +4 -2
  36. package/test/transformNumPy.js +3 -1
  37. package/test/transformXLSX.js +4 -2
  38. package/test/transformXML.js +4 -2
  39. package/test-runner.js +400 -0
  40. package/test.parq +0 -0
  41. package/test_select.js +37 -0
package/test-runner.js ADDED
@@ -0,0 +1,400 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Simple unit test for logistic regression node
4
+ const assert = require('assert');
5
+ const LogisticRegression = require('logisticegression');
6
+
7
+ // counters used by both runners
8
+ let testsPassed = 0;
9
+ let testsFailed = 0;
10
+
11
+ // If the CLI includes the word 'transform' proceed to run the
12
+ // transformation node tests rather than the logistic regression
13
+ // suite. This avoids pulling mocha back in while still exercising
14
+ // the existing mocha-based files under test/transform*.js.
15
+ if (process.argv.includes('transform')) {
16
+ runTransformTests();
17
+ return;
18
+ }
19
+
20
+ // If the CLI includes 'dataanalysis' run dataAnalysis tests
21
+ if (process.argv.includes('dataanalysis')) {
22
+ runDataAnalysisTests();
23
+ return;
24
+ }
25
+
26
+ // If the CLI includes 'columnar' run the columnar-format tests
27
+ if (process.argv.includes('columnar')) {
28
+ runColumnarTests();
29
+ return;
30
+ }
31
+
32
+
33
+ function test(description, fn) {
34
+ try {
35
+ fn();
36
+ console.log(`✓ PASS: ${description}`);
37
+ testsPassed++;
38
+ } catch (err) {
39
+ console.error(`✗ FAIL: ${description}`);
40
+ console.error(` Error: ${err.message}`);
41
+ testsFailed++;
42
+ }
43
+ }
44
+
45
+ // ---------------------------------------------------------------------------
46
+ // transform test emulation
47
+ // ---------------------------------------------------------------------------
48
+ function callMaybeAsync(fn) {
49
+ return new Promise((resolve, reject) => {
50
+ try {
51
+ if (fn.length >= 1) {
52
+ fn(err => (err ? reject(err) : resolve()));
53
+ } else {
54
+ Promise.resolve(fn()).then(resolve, reject);
55
+ }
56
+ } catch (err) {
57
+ reject(err);
58
+ }
59
+ });
60
+ }
61
+
62
+ async function runTransformTests() {
63
+ const path = require('path');
64
+ const fs = require('fs');
65
+
66
+ let beforeEachFns = [];
67
+ let afterEachFns = [];
68
+ let tests = [];
69
+ let currentDescribe = '';
70
+
71
+ global.describe = function(desc, fn) {
72
+ currentDescribe = desc;
73
+ fn();
74
+ };
75
+ global.beforeEach = function(fn) {
76
+ beforeEachFns.push(fn);
77
+ };
78
+ global.afterEach = function(fn) {
79
+ afterEachFns.push(fn);
80
+ };
81
+ global.it = function(desc, fn) {
82
+ const full = currentDescribe ? `${currentDescribe} ${desc}` : desc;
83
+ tests.push({desc: full, fn});
84
+ return { timeout: () => {} };
85
+ };
86
+
87
+ console.log("Transform Node Tests\n" + "=".repeat(60));
88
+
89
+ const dir = path.join(__dirname, 'test');
90
+ const files = fs.readdirSync(dir).filter(f => /^transform.*\.js$/.test(f));
91
+ files.forEach(f => require(path.join(dir, f)));
92
+
93
+ for (const t of tests) {
94
+ for (const bf of beforeEachFns) await callMaybeAsync(bf);
95
+ try {
96
+ await callMaybeAsync(t.fn);
97
+ console.log(`✓ PASS: ${t.desc}`);
98
+ testsPassed++;
99
+ } catch (err) {
100
+ console.error(`✗ FAIL: ${t.desc}`);
101
+ console.error(` Error: ${err.message}`);
102
+ testsFailed++;
103
+ }
104
+ for (const af of afterEachFns) {
105
+ try {
106
+ await callMaybeAsync(af);
107
+ } catch (err) {
108
+ // ignore server-not-running errors which happen when
109
+ // cleanup double-closes the Node-RED test helper server.
110
+ if (err && err.code === 'ERR_SERVER_NOT_RUNNING') {
111
+ // no-op
112
+ } else {
113
+ throw err;
114
+ }
115
+ }
116
+ }
117
+ }
118
+
119
+ console.log('\n' + '='.repeat(60));
120
+ console.log(`Tests passed: ${testsPassed}`);
121
+ console.log(`Tests failed: ${testsFailed}`);
122
+ console.log('='.repeat(60));
123
+ process.exit(testsFailed > 0 ? 1 : 0);
124
+ }
125
+
126
+ // ---------------------------------------------------------------------------
127
+ // dataanalysis test emulation
128
+ // ---------------------------------------------------------------------------
129
+ async function runDataAnalysisTests() {
130
+ const path = require('path');
131
+ const fs = require('fs');
132
+
133
+ let beforeEachFns = [];
134
+ let afterEachFns = [];
135
+ let tests = [];
136
+ let currentDescribe = '';
137
+
138
+ global.describe = function(desc, fn) {
139
+ currentDescribe = desc;
140
+ fn();
141
+ };
142
+ global.beforeEach = function(fn) {
143
+ beforeEachFns.push(fn);
144
+ };
145
+ global.afterEach = function(fn) {
146
+ afterEachFns.push(fn);
147
+ };
148
+ global.it = function(desc, fn) {
149
+ const full = currentDescribe ? `${currentDescribe} ${desc}` : desc;
150
+ tests.push({desc: full, fn});
151
+ return { timeout: () => {} };
152
+ };
153
+
154
+ console.log("Data Analysis Extension Tests\n" + "=".repeat(60));
155
+
156
+ const dir = path.join(__dirname, 'test');
157
+ const files = fs.readdirSync(dir).filter(f => /^dataAnalysisE.*\.js$/.test(f));
158
+ files.forEach(f => require(path.join(dir, f)));
159
+
160
+ for (const t of tests) {
161
+ for (const bf of beforeEachFns) await callMaybeAsync(bf);
162
+ try {
163
+ await callMaybeAsync(t.fn);
164
+ console.log(`✓ PASS: ${t.desc}`);
165
+ testsPassed++;
166
+ } catch (err) {
167
+ console.error(`✗ FAIL: ${t.desc}`);
168
+ console.error(` Error: ${err.message}`);
169
+ testsFailed++;
170
+ }
171
+ for (const af of afterEachFns) await callMaybeAsync(af);
172
+ }
173
+
174
+ console.log('\n' + '='.repeat(60));
175
+ console.log(`Tests passed: ${testsPassed}`);
176
+ console.log(`Tests failed: ${testsFailed}`);
177
+ console.log('='.repeat(60));
178
+ process.exit(testsFailed > 0 ? 1 : 0);
179
+ }
180
+
181
+ // ---------------------------------------------------------------------------
182
+ // columnar test emulation
183
+ // ---------------------------------------------------------------------------
184
+ async function runColumnarTests() {
185
+ const path = require('path');
186
+ const fs = require('fs');
187
+
188
+ let beforeEachFns = [];
189
+ let afterEachFns = [];
190
+ let tests = [];
191
+ let currentDescribe = '';
192
+
193
+ global.describe = function(desc, fn) {
194
+ currentDescribe = desc;
195
+ fn();
196
+ };
197
+ global.beforeEach = function(fn) {
198
+ beforeEachFns.push(fn);
199
+ };
200
+ global.afterEach = function(fn) {
201
+ afterEachFns.push(fn);
202
+ };
203
+ global.it = function(desc, fn) {
204
+ const full = currentDescribe ? `${currentDescribe} ${desc}` : desc;
205
+ tests.push({desc: full, fn});
206
+ return { timeout: () => {} };
207
+ };
208
+
209
+ console.log("Columnar File Tests\n" + "=".repeat(60));
210
+
211
+ const dir = path.join(__dirname, 'test');
212
+ const files = fs.readdirSync(dir).filter(f => /^columnar.*\.js$/.test(f));
213
+ files.forEach(f => require(path.join(dir, f)));
214
+
215
+ for (const t of tests) {
216
+ for (const bf of beforeEachFns) await callMaybeAsync(bf);
217
+ try {
218
+ await callMaybeAsync(t.fn);
219
+ console.log(`✓ PASS: ${t.desc}`);
220
+ testsPassed++;
221
+ } catch (err) {
222
+ console.error(`✗ FAIL: ${t.desc}`);
223
+ console.error(` Error: ${err.message}`);
224
+ testsFailed++;
225
+ }
226
+ for (const af of afterEachFns) await callMaybeAsync(af);
227
+ }
228
+
229
+ console.log('\n' + '='.repeat(60));
230
+ console.log(`Tests passed: ${testsPassed}`);
231
+ console.log(`Tests failed: ${testsFailed}`);
232
+ console.log('='.repeat(60));
233
+ process.exit(testsFailed > 0 ? 1 : 0);
234
+ }
235
+
236
+ console.log("Logistic Regression Unit Tests\n" + "=".repeat(60));
237
+
238
+ // Test 1: Model instantiation
239
+ test("Should create a LogisticRegression model instance", () => {
240
+ const model = new LogisticRegression({
241
+ learningRate: 0.1,
242
+ iterations: 100
243
+ });
244
+ assert(model instanceof LogisticRegression);
245
+ assert.strictEqual(model.learningRate, 0.1);
246
+ assert.strictEqual(model.iterations, 100);
247
+ });
248
+
249
+ // Test 2: Model fit
250
+ test("Should fit a model with training data", () => {
251
+ const model = new LogisticRegression({
252
+ learningRate: 0.1,
253
+ iterations: 100
254
+ });
255
+ const X = [[0, 0], [0, 1], [1, 0], [1, 1]];
256
+ const y = [0, 1, 1, 1];
257
+
258
+ const result = model.fit(X, y);
259
+ assert(result instanceof LogisticRegression);
260
+ assert(model.weights !== null);
261
+ assert.strictEqual(model.weights.length, 3); // 2 features + 1 intercept
262
+ });
263
+
264
+ // Test 3: Model predict
265
+ test("Should predict class labels", () => {
266
+ const model = new LogisticRegression({
267
+ learningRate: 0.1,
268
+ iterations: 100
269
+ });
270
+ const X = [[0, 0], [0, 1], [1, 0], [1, 1]];
271
+ const y = [0, 1, 1, 1];
272
+
273
+ model.fit(X, y);
274
+ const predictions = model.predict([[0, 0], [1, 1], [0, 1]]);
275
+
276
+ assert(Array.isArray(predictions));
277
+ assert.strictEqual(predictions.length, 3);
278
+ predictions.forEach(pred => {
279
+ assert([0, 1].includes(pred));
280
+ });
281
+ });
282
+
283
+ // Test 4: Model predictProba
284
+ test("Should predict probabilities", () => {
285
+ const model = new LogisticRegression({
286
+ learningRate: 0.1,
287
+ iterations: 100
288
+ });
289
+ const X = [[0, 0], [0, 1], [1, 0], [1, 1]];
290
+ const y = [0, 1, 1, 1];
291
+
292
+ model.fit(X, y);
293
+ const probs = model.predictProba([[0, 0], [1, 1], [0.5, 0.5]]);
294
+
295
+ assert(Array.isArray(probs));
296
+ assert.strictEqual(probs.length, 3);
297
+ probs.forEach(prob => {
298
+ assert(typeof prob === 'number');
299
+ assert(prob >= 0 && prob <= 1);
300
+ });
301
+ });
302
+
303
+ // Test 5: Model decision function
304
+ test("Should compute decision function (logits)", () => {
305
+ const model = new LogisticRegression({
306
+ learningRate: 0.1,
307
+ iterations: 100
308
+ });
309
+ const X = [[0, 0], [0, 1], [1, 0], [1, 1]];
310
+ const y = [0, 1, 1, 1];
311
+
312
+ model.fit(X, y);
313
+ const decisions = model.decisionFunction([[0, 0], [1, 1]]);
314
+
315
+ assert(Array.isArray(decisions));
316
+ assert.strictEqual(decisions.length, 2);
317
+ decisions.forEach(d => {
318
+ assert(typeof d === 'number');
319
+ });
320
+ });
321
+
322
+ // Test 6: Fit without data
323
+ test("Should throw error when fitting without data", () => {
324
+ const model = new LogisticRegression();
325
+ try {
326
+ model.fit([], []);
327
+ throw new Error("Should have thrown an error");
328
+ } catch (err) {
329
+ assert(err.message.includes("non-empty"));
330
+ }
331
+ });
332
+
333
+ // Test 7: Mismatched dimensions
334
+ test("Should throw error when X and y have different lengths", () => {
335
+ const model = new LogisticRegression();
336
+ try {
337
+ model.fit([[0, 0], [1, 1]], [0, 1, 1]);
338
+ throw new Error("Should have thrown an error");
339
+ } catch (err) {
340
+ assert(err.message.includes("same number"));
341
+ }
342
+ });
343
+
344
+ // Test 8: Predict before fit
345
+ test("Should throw error when predicting before fitting", () => {
346
+ const model = new LogisticRegression();
347
+ try {
348
+ model.predict([[0, 0]]);
349
+ throw new Error("Should have thrown an error");
350
+ } catch (err) {
351
+ assert(err.message.includes("not fitted"));
352
+ }
353
+ });
354
+
355
+ // Test 9: L2 regularization
356
+ test("Should support L2 regularization", () => {
357
+ const model = new LogisticRegression({
358
+ learningRate: 0.1,
359
+ iterations: 100,
360
+ l2: 0.01
361
+ });
362
+ const X = [[0, 0], [0, 1], [1, 0], [1, 1]];
363
+ const y = [0, 1, 1, 1];
364
+
365
+ model.fit(X, y);
366
+ const predictions = model.predict([[0.5, 0.5]]);
367
+
368
+ assert(Array.isArray(predictions));
369
+ assert.strictEqual(predictions.length, 1);
370
+ });
371
+
372
+ // Test 10: Custom threshold
373
+ test("Should use custom threshold for prediction", () => {
374
+ const model = new LogisticRegression({
375
+ learningRate: 0.1,
376
+ iterations: 100
377
+ });
378
+ const X = [[0, 0], [0, 1], [1, 0], [1, 1]];
379
+ const y = [0, 1, 1, 1];
380
+
381
+ model.fit(X, y);
382
+ const predictions = model.predict([[0.5, 0.5]], 0.7);
383
+
384
+ assert(Array.isArray(predictions));
385
+ assert([0, 1].includes(predictions[0]));
386
+ });
387
+
388
+ // Summary
389
+ console.log("\n" + "=".repeat(60));
390
+ console.log(`Tests passed: ${testsPassed}`);
391
+ console.log(`Tests failed: ${testsFailed}`);
392
+ console.log("=".repeat(60));
393
+
394
+ if (testsFailed > 0) {
395
+ console.log("\nSome tests failed!");
396
+ process.exit(1);
397
+ } else {
398
+ console.log("\nAll tests passed! ✓");
399
+ process.exit(0);
400
+ }
package/test.parq ADDED
Binary file
package/test_select.js ADDED
@@ -0,0 +1,37 @@
1
+ const { SimpleColumnarStore } = require('./columnar/columnar.js');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const os = require('os');
5
+
6
+ (async () => {
7
+ const tmp = path.join(os.tmpdir(), 'test-select-issue.columnar');
8
+ if (fs.existsSync(tmp)) fs.unlinkSync(tmp);
9
+
10
+ const records = [
11
+ { id: 1, name: 'Alice', age: 25 },
12
+ { id: 2, name: 'Bob', age: 30 },
13
+ { id: 3, name: 'Charlie', age: 35 },
14
+ { id: 4, name: 'Diana', age: 28 },
15
+ { id: 5, name: 'Eve', age: 32 },
16
+ { id: 6, name: 'Frank', age: 29 },
17
+ ];
18
+
19
+ await SimpleColumnarStore.writeRecords(records, tmp);
20
+
21
+ // Test the exact SQL from Node-RED
22
+ const sql = 'SELECT "a1" as test,:msg.payload as name,COUNT(*) AS cnt1 FROM ? a where name= :msg.payload';
23
+ console.log('Testing SQL:', sql);
24
+
25
+ const result = await SimpleColumnarStore.sqlQuery(
26
+ tmp,
27
+ sql,
28
+ null,
29
+ { msg: { payload: 'Alice' }, flow: {}, global: {} }
30
+ );
31
+
32
+ console.log('Result:', JSON.stringify(result, null, 2));
33
+ console.log('Number of columns in first record:', Object.keys(result[0]).length);
34
+ console.log('Column names:', Object.keys(result[0]));
35
+
36
+ if (fs.existsSync(tmp)) fs.unlinkSync(tmp);
37
+ })();