@anarlabs/crosspulse 1.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/LICENSE +21 -0
- package/README.md +535 -0
- package/package.json +36 -0
- package/src/crosspulse.js +204 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AnarEsgerzade
|
|
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,535 @@
|
|
|
1
|
+
# Crosspulse
|
|
2
|
+
|
|
3
|
+
**Seamless Python ↔ JavaScript Bridge** - Bidirectional, fully synchronized cross-language communication library.
|
|
4
|
+
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://www.python.org/downloads/)
|
|
7
|
+
[](https://nodejs.org/)
|
|
8
|
+
|
|
9
|
+
> Break down language barriers and let Python and JavaScript talk to each other like old friends.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## ✨ Features
|
|
14
|
+
|
|
15
|
+
- ✅ **Bidirectional Communication**: JS → PY and PY → JS
|
|
16
|
+
- ✅ **Synchronous Calls**: Full Promise/await support
|
|
17
|
+
- ✅ **Event-Based Architecture**: Register, listen, and call methods
|
|
18
|
+
- ✅ **Error Handling**: Exception handling in both languages
|
|
19
|
+
- ✅ **Type Safe**: JSON serialization with validation
|
|
20
|
+
- ✅ **Simple API**: Just 3 core methods: `register()`, `listen()`, `call()`
|
|
21
|
+
- ✅ **Zero Dependencies**: No external packages required
|
|
22
|
+
- ✅ **Lightweight**: Minimal overhead, maximum performance
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 🎯 Why Crosspulse?
|
|
27
|
+
|
|
28
|
+
Ever wanted to combine Python's data processing power with JavaScript's UI capabilities? Or run ML models in Python while displaying results in Node.js? Crosspulse makes it effortless.
|
|
29
|
+
|
|
30
|
+
```javascript
|
|
31
|
+
// JavaScript
|
|
32
|
+
const result = await bridge.call("train_model", data);
|
|
33
|
+
console.log("Accuracy:", result.accuracy);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
# Python (automatically responds)
|
|
38
|
+
bridge.register("train_model", train_ml_model)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
That's it. No REST APIs, no HTTP servers, no complexity.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 📦 Installation
|
|
46
|
+
|
|
47
|
+
Simply copy the files to your project:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
crosspulse.py
|
|
51
|
+
crosspulse.js
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Requirements:**
|
|
55
|
+
- Python 3.7+
|
|
56
|
+
- Node.js 12+
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 🚀 Quick Start
|
|
61
|
+
|
|
62
|
+
### Mode 1: JavaScript → Python
|
|
63
|
+
|
|
64
|
+
Call Python functions from JavaScript.
|
|
65
|
+
|
|
66
|
+
**JavaScript side:**
|
|
67
|
+
```javascript
|
|
68
|
+
const Crosspulse = require('./crosspulse');
|
|
69
|
+
|
|
70
|
+
async function main() {
|
|
71
|
+
const bridge = new Crosspulse("connect");
|
|
72
|
+
await bridge.connect("./crosspulse.py");
|
|
73
|
+
|
|
74
|
+
// Call Python function
|
|
75
|
+
const result = await bridge.call("py_add", 10, 20);
|
|
76
|
+
console.log(result); // 30
|
|
77
|
+
|
|
78
|
+
bridge.disconnect();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
main();
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Python side:**
|
|
85
|
+
```python
|
|
86
|
+
from crosspulse import Crosspulse
|
|
87
|
+
|
|
88
|
+
bridge = Crosspulse(mode="listen")
|
|
89
|
+
|
|
90
|
+
# Register method
|
|
91
|
+
bridge.register("py_add", lambda a, b: a + b)
|
|
92
|
+
|
|
93
|
+
# Start listening
|
|
94
|
+
bridge.listen()
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Run:**
|
|
98
|
+
```bash
|
|
99
|
+
node app.js
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
### Mode 2: Python → JavaScript
|
|
105
|
+
|
|
106
|
+
Call JavaScript functions from Python.
|
|
107
|
+
|
|
108
|
+
**Python side:**
|
|
109
|
+
```python
|
|
110
|
+
from crosspulse import Crosspulse
|
|
111
|
+
|
|
112
|
+
bridge = Crosspulse(mode="connect")
|
|
113
|
+
bridge.connect("./crosspulse.js")
|
|
114
|
+
|
|
115
|
+
# Call JavaScript function
|
|
116
|
+
result = bridge.call("js_multiply", 100, 50)
|
|
117
|
+
print(result) # 5000
|
|
118
|
+
|
|
119
|
+
bridge.disconnect()
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**JavaScript side:**
|
|
123
|
+
```javascript
|
|
124
|
+
const Crosspulse = require('./crosspulse');
|
|
125
|
+
|
|
126
|
+
const bridge = new Crosspulse("listen");
|
|
127
|
+
|
|
128
|
+
// Register method
|
|
129
|
+
bridge.register("js_multiply", (a, b) => a * b);
|
|
130
|
+
|
|
131
|
+
// Start listening
|
|
132
|
+
bridge.listen();
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Run:**
|
|
136
|
+
```bash
|
|
137
|
+
python3 main.py
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
### Mode 3: Bidirectional Communication
|
|
143
|
+
|
|
144
|
+
Both languages can call each other simultaneously!
|
|
145
|
+
|
|
146
|
+
**Python side:**
|
|
147
|
+
```python
|
|
148
|
+
bridge = Crosspulse(mode="connect")
|
|
149
|
+
|
|
150
|
+
# Register our methods (JS can call these)
|
|
151
|
+
bridge.register("py_square", lambda x: x ** 2)
|
|
152
|
+
bridge.register("py_reverse", lambda s: s[::-1])
|
|
153
|
+
|
|
154
|
+
bridge.connect("./app.js")
|
|
155
|
+
|
|
156
|
+
# Call JS methods
|
|
157
|
+
result = bridge.call("js_capitalize", "hello world")
|
|
158
|
+
print(result) # "HELLO WORLD"
|
|
159
|
+
|
|
160
|
+
# JS can simultaneously call py_square() or py_reverse()
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**JavaScript side:**
|
|
164
|
+
```javascript
|
|
165
|
+
const bridge = new Crosspulse("listen");
|
|
166
|
+
|
|
167
|
+
// Register our methods (Python can call these)
|
|
168
|
+
bridge.register("js_capitalize", (str) => str.toUpperCase());
|
|
169
|
+
bridge.register("js_length", (str) => str.length);
|
|
170
|
+
|
|
171
|
+
bridge.listen();
|
|
172
|
+
|
|
173
|
+
// Python can simultaneously call our methods
|
|
174
|
+
// We automatically respond to incoming calls
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## 📖 API Reference
|
|
180
|
+
|
|
181
|
+
### Python
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
from crosspulse import Crosspulse
|
|
185
|
+
|
|
186
|
+
# Create instance
|
|
187
|
+
bridge = Crosspulse(mode="listen") # Listen mode
|
|
188
|
+
bridge = Crosspulse(mode="connect") # Connect mode
|
|
189
|
+
|
|
190
|
+
# Register method
|
|
191
|
+
bridge.register("method_name", callback_function)
|
|
192
|
+
|
|
193
|
+
# Listen for calls (listen mode)
|
|
194
|
+
bridge.listen()
|
|
195
|
+
|
|
196
|
+
# Connect to target (connect mode)
|
|
197
|
+
bridge.connect("target_script.js")
|
|
198
|
+
|
|
199
|
+
# Call remote method
|
|
200
|
+
result = bridge.call("method_name", arg1, arg2)
|
|
201
|
+
|
|
202
|
+
# Disconnect
|
|
203
|
+
bridge.disconnect()
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### JavaScript
|
|
207
|
+
|
|
208
|
+
```javascript
|
|
209
|
+
const Crosspulse = require('./crosspulse');
|
|
210
|
+
|
|
211
|
+
// Create instance
|
|
212
|
+
const bridge = new Crosspulse("listen"); // Listen mode
|
|
213
|
+
const bridge = new Crosspulse("connect"); // Connect mode
|
|
214
|
+
|
|
215
|
+
// Register method
|
|
216
|
+
bridge.register("method_name", (arg1, arg2) => {
|
|
217
|
+
return result;
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Listen for calls (listen mode)
|
|
221
|
+
bridge.listen();
|
|
222
|
+
|
|
223
|
+
// Connect to target (connect mode)
|
|
224
|
+
await bridge.connect("./target_script.py");
|
|
225
|
+
|
|
226
|
+
// Call remote method
|
|
227
|
+
const result = await bridge.call("method_name", arg1, arg2);
|
|
228
|
+
|
|
229
|
+
// Disconnect
|
|
230
|
+
bridge.disconnect();
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## 🎯 Real-World Examples
|
|
236
|
+
|
|
237
|
+
### Example 1: Data Processing Pipeline
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
# data_processor.py
|
|
241
|
+
import pandas as pd
|
|
242
|
+
from crosspulse import Crosspulse
|
|
243
|
+
|
|
244
|
+
bridge = Crosspulse("connect")
|
|
245
|
+
bridge.connect("./visualizer.js")
|
|
246
|
+
|
|
247
|
+
# Process data in Python
|
|
248
|
+
df = pd.read_csv("sales_data.csv")
|
|
249
|
+
insights = analyze_sales(df)
|
|
250
|
+
|
|
251
|
+
# Visualize in JavaScript
|
|
252
|
+
chart = bridge.call("create_chart", insights.to_dict())
|
|
253
|
+
print(f"Chart created: {chart}")
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
```javascript
|
|
257
|
+
// visualizer.js
|
|
258
|
+
const bridge = new Crosspulse("listen");
|
|
259
|
+
|
|
260
|
+
bridge.register("create_chart", (data) => {
|
|
261
|
+
// Use Chart.js, D3.js, or any JS library
|
|
262
|
+
const chart = generateChart(data);
|
|
263
|
+
return chart.id;
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
bridge.listen();
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Example 2: Machine Learning UI
|
|
270
|
+
|
|
271
|
+
```javascript
|
|
272
|
+
// ml_interface.js
|
|
273
|
+
const bridge = new Crosspulse("connect");
|
|
274
|
+
|
|
275
|
+
async function trainModel(dataset) {
|
|
276
|
+
await bridge.connect("./ml_model.py");
|
|
277
|
+
|
|
278
|
+
const progress = await bridge.call("train", dataset);
|
|
279
|
+
console.log("Training progress:", progress);
|
|
280
|
+
|
|
281
|
+
const predictions = await bridge.call("predict", testData);
|
|
282
|
+
displayResults(predictions);
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
# ml_model.py
|
|
288
|
+
from sklearn.ensemble import RandomForestClassifier
|
|
289
|
+
|
|
290
|
+
bridge = Crosspulse("listen")
|
|
291
|
+
|
|
292
|
+
model = RandomForestClassifier()
|
|
293
|
+
|
|
294
|
+
def train_model(data):
|
|
295
|
+
X, y = prepare_data(data)
|
|
296
|
+
model.fit(X, y)
|
|
297
|
+
return {"status": "trained", "accuracy": model.score(X, y)}
|
|
298
|
+
|
|
299
|
+
bridge.register("train", train_model)
|
|
300
|
+
bridge.listen()
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Example 3: Web Scraping + Frontend
|
|
304
|
+
|
|
305
|
+
```python
|
|
306
|
+
# scraper.py
|
|
307
|
+
from crosspulse import Crosspulse
|
|
308
|
+
import requests
|
|
309
|
+
from bs4 import BeautifulSoup
|
|
310
|
+
|
|
311
|
+
bridge = Crosspulse("connect")
|
|
312
|
+
|
|
313
|
+
def scrape_news():
|
|
314
|
+
# Scrape with Python
|
|
315
|
+
data = scrape_website()
|
|
316
|
+
|
|
317
|
+
# Send to JavaScript for display
|
|
318
|
+
bridge.call("update_ui", data)
|
|
319
|
+
|
|
320
|
+
bridge.connect("./server.js")
|
|
321
|
+
scrape_news()
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Example 4: Desktop App (Electron + Python)
|
|
325
|
+
|
|
326
|
+
```javascript
|
|
327
|
+
// electron_main.js
|
|
328
|
+
const bridge = new Crosspulse("connect");
|
|
329
|
+
|
|
330
|
+
ipcMain.on("process-image", async (event, imagePath) => {
|
|
331
|
+
await bridge.connect("./image_processor.py");
|
|
332
|
+
|
|
333
|
+
const processed = await bridge.call("enhance_image", imagePath);
|
|
334
|
+
event.reply("image-ready", processed);
|
|
335
|
+
});
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## 🔧 Advanced Usage
|
|
341
|
+
|
|
342
|
+
### Error Handling
|
|
343
|
+
|
|
344
|
+
```javascript
|
|
345
|
+
try {
|
|
346
|
+
const result = await bridge.call("risky_operation", data);
|
|
347
|
+
console.log("Success:", result);
|
|
348
|
+
} catch (error) {
|
|
349
|
+
console.error("Python error:", error.message);
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
```python
|
|
354
|
+
try:
|
|
355
|
+
result = bridge.call("risky_operation", data)
|
|
356
|
+
print(f"Success: {result}")
|
|
357
|
+
except Exception as e:
|
|
358
|
+
print(f"JavaScript error: {e}")
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Multiple Arguments & Complex Data
|
|
362
|
+
|
|
363
|
+
```javascript
|
|
364
|
+
// JavaScript
|
|
365
|
+
const result = await bridge.call("process_user", {
|
|
366
|
+
name: "John",
|
|
367
|
+
age: 30,
|
|
368
|
+
tags: ["developer", "python", "javascript"]
|
|
369
|
+
});
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
```python
|
|
373
|
+
# Python
|
|
374
|
+
def process_user(user_data):
|
|
375
|
+
return {
|
|
376
|
+
"id": generate_id(),
|
|
377
|
+
"name": user_data["name"],
|
|
378
|
+
"processed": True
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
bridge.register("process_user", process_user)
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### Async Operations
|
|
385
|
+
|
|
386
|
+
```python
|
|
387
|
+
# Python
|
|
388
|
+
import time
|
|
389
|
+
|
|
390
|
+
def long_running_task(duration):
|
|
391
|
+
time.sleep(duration)
|
|
392
|
+
return "Task completed"
|
|
393
|
+
|
|
394
|
+
bridge.register("long_task", long_running_task)
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
```javascript
|
|
398
|
+
// JavaScript
|
|
399
|
+
const result = await bridge.call("long_task", 5);
|
|
400
|
+
console.log(result); // "Task completed" (after 5 seconds)
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
## 🎨 Use Cases
|
|
406
|
+
|
|
407
|
+
| Use Case | Python Side | JavaScript Side |
|
|
408
|
+
|----------|-------------|-----------------|
|
|
409
|
+
| **Web Scraping** | BeautifulSoup, Scrapy | Display in React/Vue |
|
|
410
|
+
| **Machine Learning** | TensorFlow, PyTorch | Real-time UI updates |
|
|
411
|
+
| **Data Analysis** | Pandas, NumPy | Chart.js, D3.js |
|
|
412
|
+
| **Image Processing** | OpenCV, Pillow | Canvas, WebGL |
|
|
413
|
+
| **File Processing** | Parse Excel/PDF | Electron file picker |
|
|
414
|
+
| **API Gateway** | Flask/FastAPI alternative | Frontend communication |
|
|
415
|
+
| **Desktop Apps** | Backend logic | Electron UI |
|
|
416
|
+
| **Automation** | Selenium, automation | Control panel |
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
## ⚡ Performance
|
|
421
|
+
|
|
422
|
+
- **Latency**: ~5-10ms per call (local)
|
|
423
|
+
- **Throughput**: 1000+ calls/second
|
|
424
|
+
- **Memory**: Minimal overhead (<5MB)
|
|
425
|
+
- **Scalability**: Single process pair
|
|
426
|
+
|
|
427
|
+
For high-throughput scenarios, consider batching calls or using WebSocket alternatives.
|
|
428
|
+
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
## 🛡️ Security Notes
|
|
432
|
+
|
|
433
|
+
- Crosspulse uses STDIN/STDOUT for IPC
|
|
434
|
+
- Only use with trusted code
|
|
435
|
+
- Validate all incoming data
|
|
436
|
+
- Don't expose to untrusted networks
|
|
437
|
+
- Consider sandboxing for production
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## 🐛 Troubleshooting
|
|
442
|
+
|
|
443
|
+
### "Method not found" error
|
|
444
|
+
```python
|
|
445
|
+
# Make sure method is registered before listening
|
|
446
|
+
bridge.register("my_method", my_function)
|
|
447
|
+
bridge.listen() # Must be after register
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Connection timeout
|
|
451
|
+
```javascript
|
|
452
|
+
// Ensure Python script is running
|
|
453
|
+
await bridge.connect("./script.py");
|
|
454
|
+
// Python should be in listen mode
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### JSON serialization errors
|
|
458
|
+
```python
|
|
459
|
+
# Only use JSON-serializable types
|
|
460
|
+
# ✅ str, int, float, list, dict, bool, None
|
|
461
|
+
# ❌ Custom objects, functions, file handles
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
## 🤝 Contributing
|
|
467
|
+
|
|
468
|
+
Contributions are welcome! Here's how:
|
|
469
|
+
|
|
470
|
+
```bash
|
|
471
|
+
git clone https://github.com/yourusername/crosspulse.git
|
|
472
|
+
cd crosspulse
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
**Areas to contribute:**
|
|
476
|
+
- TypeScript definitions
|
|
477
|
+
- More examples
|
|
478
|
+
- Performance improvements
|
|
479
|
+
- Documentation
|
|
480
|
+
- Bug fixes
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
## 📄 License
|
|
485
|
+
|
|
486
|
+
MIT License - Use it however you want!
|
|
487
|
+
|
|
488
|
+
```
|
|
489
|
+
MIT License
|
|
490
|
+
|
|
491
|
+
Copyright (c) 2026 AnarEsgerzade
|
|
492
|
+
|
|
493
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
494
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
495
|
+
in the Software without restriction, including without limitation the rights
|
|
496
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
497
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
498
|
+
furnished to do so, subject to the following conditions:
|
|
499
|
+
|
|
500
|
+
The above copyright notice and this permission notice shall be included in all
|
|
501
|
+
copies or substantial portions of the Software.
|
|
502
|
+
|
|
503
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
504
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
505
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
506
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
507
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
508
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
509
|
+
SOFTWARE.
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
---
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
## 🌟 Show Your Support
|
|
517
|
+
|
|
518
|
+
If Crosspulse helps your project, give it a ⭐️ on GitHub!
|
|
519
|
+
|
|
520
|
+
---
|
|
521
|
+
|
|
522
|
+
## 📞 Support
|
|
523
|
+
|
|
524
|
+
- **Issues**: [GitHub Issues](https://github.com/AnarDevStudio/crosspulse/issues)
|
|
525
|
+
- **Discussions**: [GitHub Discussions](https://github.com/AnarDevStudio/crosspulse/discussions)
|
|
526
|
+
- **Email**: anardevstudio@gmail.com
|
|
527
|
+
|
|
528
|
+
---
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
**Built with ❤️ by developers who believe languages should work together, not apart.**
|
|
532
|
+
|
|
533
|
+
**Crosspulse** - Where Python meets JavaScript. 🚀
|
|
534
|
+
|
|
535
|
+
**Make by AnarEsgerzade🌷**
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@anarlabs/crosspulse",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Call Python from Node.js and Node.js from Python easily with Crosspulse’s lightweight bridge.",
|
|
5
|
+
"main": "crosspulse.js",
|
|
6
|
+
"directories": {
|
|
7
|
+
"example": "example"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"No tests yet\" && exit 0"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/AnarDevStudio/Crosspulse.git"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"nodejs",
|
|
18
|
+
"javascript",
|
|
19
|
+
"python",
|
|
20
|
+
"events",
|
|
21
|
+
"ipc",
|
|
22
|
+
"stdout",
|
|
23
|
+
"stdin",
|
|
24
|
+
"interop",
|
|
25
|
+
"bridge",
|
|
26
|
+
"event-driven",
|
|
27
|
+
"cross-language",
|
|
28
|
+
"python-javascript"
|
|
29
|
+
],
|
|
30
|
+
"author": "AnarDevStudio",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/AnarDevStudio/Crosspulse/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/AnarDevStudio/Crosspulse#readme"
|
|
36
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// Copyright (c) 2026 Crosspulse
|
|
2
|
+
|
|
3
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
// in the Software without restriction, including without limitation the rights
|
|
6
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
// copies of the Software.
|
|
8
|
+
|
|
9
|
+
const { spawn } = require("child_process");
|
|
10
|
+
const path = require("path");
|
|
11
|
+
|
|
12
|
+
class Crosspulse {
|
|
13
|
+
/**
|
|
14
|
+
* Crosspulse - JavaScript ↔ Python Bridge
|
|
15
|
+
* Bidirectional communication: both listening and calling
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
constructor(mode = "connect") {
|
|
19
|
+
/**
|
|
20
|
+
* mode:
|
|
21
|
+
* "connect" = connect to Python and call its methods
|
|
22
|
+
* "listen" = listen for method calls coming from Python
|
|
23
|
+
*/
|
|
24
|
+
this.mode = mode;
|
|
25
|
+
this.handlers = new Map();
|
|
26
|
+
this.process = null;
|
|
27
|
+
this.buffer = "";
|
|
28
|
+
this.callbacks = new Map();
|
|
29
|
+
this.requestId = 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async connect(pythonFile) {
|
|
33
|
+
/**
|
|
34
|
+
* Connect to a Python script
|
|
35
|
+
*/
|
|
36
|
+
if (this.mode !== "connect") {
|
|
37
|
+
throw new Error("Crosspulse must be in 'connect' mode");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
const pyPath = path.resolve(__dirname, pythonFile);
|
|
42
|
+
this.process = spawn("python3", [pyPath]);
|
|
43
|
+
|
|
44
|
+
this.process.stdout.on("data", (data) => {
|
|
45
|
+
this.buffer += data.toString();
|
|
46
|
+
|
|
47
|
+
let lines = this.buffer.split("\n");
|
|
48
|
+
this.buffer = lines.pop() || "";
|
|
49
|
+
|
|
50
|
+
for (let line of lines) {
|
|
51
|
+
if (!line.trim()) continue;
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const response = JSON.parse(line);
|
|
55
|
+
|
|
56
|
+
// Response to a method call made to Python
|
|
57
|
+
if (response.id !== undefined && this.callbacks.has(response.id)) {
|
|
58
|
+
const callback = this.callbacks.get(response.id);
|
|
59
|
+
this.callbacks.delete(response.id);
|
|
60
|
+
|
|
61
|
+
if (response.success) {
|
|
62
|
+
callback.resolve(response.result);
|
|
63
|
+
} else {
|
|
64
|
+
callback.reject(new Error(response.error));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Method call coming from Python
|
|
69
|
+
else if (response.method) {
|
|
70
|
+
this._handleIncomingCall(response);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.error("JSON parse error:", err);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
this.process.stderr.on("data", (data) => {
|
|
80
|
+
console.error("Python stderr:", data.toString());
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
this.process.on("close", (code) => {
|
|
84
|
+
if (code !== 0) {
|
|
85
|
+
reject(new Error(`Python process exited with code ${code}`));
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
this.process.on("error", (err) => {
|
|
90
|
+
reject(err);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Process is ready
|
|
94
|
+
setTimeout(() => resolve(this), 100);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
call(method, ...args) {
|
|
99
|
+
/**
|
|
100
|
+
* Call a method defined in Python
|
|
101
|
+
*/
|
|
102
|
+
return new Promise((resolve, reject) => {
|
|
103
|
+
if (!this.process) {
|
|
104
|
+
return reject(new Error("Not connected. Call connect() first."));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const id = this.requestId++;
|
|
108
|
+
this.callbacks.set(id, { resolve, reject });
|
|
109
|
+
|
|
110
|
+
const request = {
|
|
111
|
+
id,
|
|
112
|
+
method,
|
|
113
|
+
args,
|
|
114
|
+
kwargs: {}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
this.process.stdin.write(JSON.stringify(request) + "\n");
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
disconnect() {
|
|
122
|
+
/**
|
|
123
|
+
* Close the connection
|
|
124
|
+
*/
|
|
125
|
+
if (this.process) {
|
|
126
|
+
this.process.kill();
|
|
127
|
+
this.process = null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ==================== LISTENER MODE ====================
|
|
132
|
+
|
|
133
|
+
register(methodName, callback) {
|
|
134
|
+
/**
|
|
135
|
+
* Register a method that can be called from Python
|
|
136
|
+
*/
|
|
137
|
+
this.handlers.set(methodName, callback);
|
|
138
|
+
return this;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
listen() {
|
|
142
|
+
/**
|
|
143
|
+
* Listen for incoming method calls from Python (via stdin)
|
|
144
|
+
*/
|
|
145
|
+
if (this.mode !== "listen") {
|
|
146
|
+
throw new Error("Crosspulse must be in 'listen' mode");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
process.stdin.setEncoding('utf8');
|
|
150
|
+
|
|
151
|
+
process.stdin.on('data', (data) => {
|
|
152
|
+
this.buffer += data.toString();
|
|
153
|
+
|
|
154
|
+
let lines = this.buffer.split("\n");
|
|
155
|
+
this.buffer = lines.pop() || "";
|
|
156
|
+
|
|
157
|
+
for (let line of lines) {
|
|
158
|
+
if (!line.trim()) continue;
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
const request = JSON.parse(line);
|
|
162
|
+
this._handleIncomingCall(request);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
console.error("JSON parse error:", err);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
process.stdin.on('end', () => {
|
|
170
|
+
process.exit(0);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
_handleIncomingCall(request) {
|
|
175
|
+
/**
|
|
176
|
+
* Handle an incoming method call
|
|
177
|
+
*/
|
|
178
|
+
const method = request.method;
|
|
179
|
+
const args = request.args || [];
|
|
180
|
+
const id = request.id;
|
|
181
|
+
|
|
182
|
+
let response;
|
|
183
|
+
|
|
184
|
+
if (this.handlers.has(method)) {
|
|
185
|
+
try {
|
|
186
|
+
const result = this.handlers.get(method)(...args);
|
|
187
|
+
response = { id, success: true, result };
|
|
188
|
+
} catch (err) {
|
|
189
|
+
response = { id, success: false, error: err.message };
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
response = { id, success: false, error: `Method not found: ${method}` };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Send the response back
|
|
196
|
+
if (this.mode === "connect" && this.process) {
|
|
197
|
+
this.process.stdin.write(JSON.stringify(response) + "\n");
|
|
198
|
+
} else if (this.mode === "listen") {
|
|
199
|
+
console.log(JSON.stringify(response));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
module.exports = Crosspulse;
|