@push.rocks/smartrust 1.2.1 → 1.3.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.
@@ -0,0 +1,138 @@
1
+ import * as plugins from './plugins.js';
2
+ import { LineScanner } from './classes.linescanner.js';
3
+ /**
4
+ * Transport that connects to an already-running process via Unix socket or Windows named pipe.
5
+ * The JSON-over-newline protocol is identical to stdio; only the transport changes.
6
+ */
7
+ export class SocketTransport extends plugins.events.EventEmitter {
8
+ options;
9
+ socket = null;
10
+ lineScanner;
11
+ _connected = false;
12
+ intentionalDisconnect = false;
13
+ reconnectAttempts = 0;
14
+ reconnectTimer = null;
15
+ constructor(options) {
16
+ super();
17
+ this.options = {
18
+ autoReconnect: false,
19
+ reconnectBaseDelayMs: 100,
20
+ reconnectMaxDelayMs: 30000,
21
+ maxReconnectAttempts: 10,
22
+ ...options,
23
+ };
24
+ this.lineScanner = new LineScanner(options.maxPayloadSize, options.logger);
25
+ }
26
+ get connected() {
27
+ return this._connected;
28
+ }
29
+ /**
30
+ * Connect to the socket. Resolves when the TCP/Unix connection is established.
31
+ */
32
+ async connect() {
33
+ this.intentionalDisconnect = false;
34
+ this.reconnectAttempts = 0;
35
+ return this.doConnect();
36
+ }
37
+ doConnect() {
38
+ return new Promise((resolve, reject) => {
39
+ let settled = false;
40
+ this.socket = plugins.net.connect({ path: this.options.socketPath });
41
+ this.socket.on('connect', () => {
42
+ if (!settled) {
43
+ settled = true;
44
+ this._connected = true;
45
+ this.reconnectAttempts = 0;
46
+ resolve();
47
+ }
48
+ });
49
+ this.socket.on('data', (chunk) => {
50
+ this.lineScanner.push(chunk, (line) => {
51
+ this.emit('line', line);
52
+ });
53
+ });
54
+ this.socket.on('close', () => {
55
+ const wasConnected = this._connected;
56
+ this._connected = false;
57
+ this.lineScanner.clear();
58
+ if (!this.intentionalDisconnect && wasConnected && this.options.autoReconnect) {
59
+ this.attemptReconnect();
60
+ }
61
+ this.emit('close');
62
+ });
63
+ this.socket.on('error', (err) => {
64
+ this._connected = false;
65
+ if (!settled) {
66
+ settled = true;
67
+ reject(err);
68
+ }
69
+ else if (!this.intentionalDisconnect) {
70
+ this.emit('error', err);
71
+ }
72
+ });
73
+ });
74
+ }
75
+ attemptReconnect() {
76
+ if (this.reconnectAttempts >= this.options.maxReconnectAttempts) {
77
+ this.options.logger.log('error', `Max reconnect attempts (${this.options.maxReconnectAttempts}) reached`);
78
+ this.emit('reconnect_failed');
79
+ return;
80
+ }
81
+ const delay = Math.min(this.options.reconnectBaseDelayMs * Math.pow(2, this.reconnectAttempts), this.options.reconnectMaxDelayMs);
82
+ this.reconnectAttempts++;
83
+ this.options.logger.log('info', `Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.options.maxReconnectAttempts})`);
84
+ this.reconnectTimer = setTimeout(async () => {
85
+ this.reconnectTimer = null;
86
+ try {
87
+ await this.doConnect();
88
+ this.emit('reconnected');
89
+ }
90
+ catch {
91
+ // doConnect rejected — the 'close' handler on the new socket will trigger another attempt
92
+ }
93
+ }, delay);
94
+ }
95
+ /**
96
+ * Write data to the socket with backpressure support.
97
+ */
98
+ async write(data) {
99
+ return new Promise((resolve, reject) => {
100
+ if (!this.socket || !this._connected) {
101
+ reject(new Error('Socket not connected'));
102
+ return;
103
+ }
104
+ const canContinue = this.socket.write(data, 'utf8', (err) => {
105
+ if (err) {
106
+ reject(err);
107
+ }
108
+ });
109
+ if (canContinue) {
110
+ resolve();
111
+ }
112
+ else {
113
+ this.socket.once('drain', () => {
114
+ resolve();
115
+ });
116
+ }
117
+ });
118
+ }
119
+ /**
120
+ * Close the socket connection. Does NOT kill the remote daemon.
121
+ */
122
+ disconnect() {
123
+ this.intentionalDisconnect = true;
124
+ if (this.reconnectTimer) {
125
+ clearTimeout(this.reconnectTimer);
126
+ this.reconnectTimer = null;
127
+ }
128
+ if (this.socket) {
129
+ const sock = this.socket;
130
+ this.socket = null;
131
+ this._connected = false;
132
+ this.lineScanner.clear();
133
+ sock.removeAllListeners();
134
+ sock.destroy();
135
+ }
136
+ }
137
+ }
138
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5zb2NrZXR0cmFuc3BvcnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9jbGFzc2VzLnNvY2tldHRyYW5zcG9ydC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUN4QyxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUErQnZEOzs7R0FHRztBQUNILE1BQU0sT0FBTyxlQUFnQixTQUFRLE9BQU8sQ0FBQyxNQUFNLENBQUMsWUFBWTtJQUN0RCxPQUFPLENBQWtDO0lBQ3pDLE1BQU0sR0FBOEIsSUFBSSxDQUFDO0lBQ3pDLFdBQVcsQ0FBYztJQUN6QixVQUFVLEdBQVksS0FBSyxDQUFDO0lBQzVCLHFCQUFxQixHQUFZLEtBQUssQ0FBQztJQUN2QyxpQkFBaUIsR0FBVyxDQUFDLENBQUM7SUFDOUIsY0FBYyxHQUF5QyxJQUFJLENBQUM7SUFFcEUsWUFBWSxPQUFnQztRQUMxQyxLQUFLLEVBQUUsQ0FBQztRQUNSLElBQUksQ0FBQyxPQUFPLEdBQUc7WUFDYixhQUFhLEVBQUUsS0FBSztZQUNwQixvQkFBb0IsRUFBRSxHQUFHO1lBQ3pCLG1CQUFtQixFQUFFLEtBQUs7WUFDMUIsb0JBQW9CLEVBQUUsRUFBRTtZQUN4QixHQUFHLE9BQU87U0FDWCxDQUFDO1FBQ0YsSUFBSSxDQUFDLFdBQVcsR0FBRyxJQUFJLFdBQVcsQ0FBQyxPQUFPLENBQUMsY0FBYyxFQUFFLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUM3RSxDQUFDO0lBRUQsSUFBVyxTQUFTO1FBQ2xCLE9BQU8sSUFBSSxDQUFDLFVBQVUsQ0FBQztJQUN6QixDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsT0FBTztRQUNsQixJQUFJLENBQUMscUJBQXFCLEdBQUcsS0FBSyxDQUFDO1FBQ25DLElBQUksQ0FBQyxpQkFBaUIsR0FBRyxDQUFDLENBQUM7UUFDM0IsT0FBTyxJQUFJ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
@@ -0,0 +1,35 @@
1
+ import * as plugins from './plugins.js';
2
+ import type { IRustBridgeLogger } from './interfaces/index.js';
3
+ import type { IRustTransport } from './interfaces/transport.js';
4
+ export interface IStdioTransportOptions {
5
+ binaryPath: string;
6
+ cliArgs: string[];
7
+ env?: Record<string, string>;
8
+ maxPayloadSize: number;
9
+ logger: IRustBridgeLogger;
10
+ }
11
+ /**
12
+ * Transport that spawns a child process and communicates via stdin/stdout.
13
+ * Extracted from the original RustBridge process management logic.
14
+ */
15
+ export declare class StdioTransport extends plugins.events.EventEmitter implements IRustTransport {
16
+ private options;
17
+ private childProcess;
18
+ private lineScanner;
19
+ private stderrRemainder;
20
+ private _connected;
21
+ constructor(options: IStdioTransportOptions);
22
+ get connected(): boolean;
23
+ /**
24
+ * Spawn the child process. Resolves when the process is running (not necessarily ready).
25
+ */
26
+ connect(): Promise<void>;
27
+ /**
28
+ * Write data to stdin with backpressure support.
29
+ */
30
+ write(data: string): Promise<void>;
31
+ /**
32
+ * Kill the child process. Sends SIGTERM, then SIGKILL after 5s.
33
+ */
34
+ disconnect(): void;
35
+ }
@@ -0,0 +1,136 @@
1
+ import * as plugins from './plugins.js';
2
+ import { LineScanner } from './classes.linescanner.js';
3
+ /**
4
+ * Transport that spawns a child process and communicates via stdin/stdout.
5
+ * Extracted from the original RustBridge process management logic.
6
+ */
7
+ export class StdioTransport extends plugins.events.EventEmitter {
8
+ options;
9
+ childProcess = null;
10
+ lineScanner;
11
+ stderrRemainder = '';
12
+ _connected = false;
13
+ constructor(options) {
14
+ super();
15
+ this.options = options;
16
+ this.lineScanner = new LineScanner(options.maxPayloadSize, options.logger);
17
+ }
18
+ get connected() {
19
+ return this._connected;
20
+ }
21
+ /**
22
+ * Spawn the child process. Resolves when the process is running (not necessarily ready).
23
+ */
24
+ async connect() {
25
+ const env = this.options.env
26
+ ? { ...process.env, ...this.options.env }
27
+ : { ...process.env };
28
+ this.childProcess = plugins.childProcess.spawn(this.options.binaryPath, this.options.cliArgs, { stdio: ['pipe', 'pipe', 'pipe'], env });
29
+ // Handle stderr with cross-chunk buffering
30
+ this.childProcess.stderr?.on('data', (data) => {
31
+ this.stderrRemainder += data.toString();
32
+ const lines = this.stderrRemainder.split('\n');
33
+ this.stderrRemainder = lines.pop();
34
+ for (const line of lines) {
35
+ const trimmed = line.trim();
36
+ if (trimmed) {
37
+ this.emit('stderr', trimmed);
38
+ }
39
+ }
40
+ });
41
+ // Handle stdout via LineScanner
42
+ this.childProcess.stdout.on('data', (chunk) => {
43
+ this.lineScanner.push(chunk, (line) => {
44
+ this.emit('line', line);
45
+ });
46
+ });
47
+ // Handle process exit
48
+ this.childProcess.on('exit', (code, signal) => {
49
+ // Flush remaining stderr
50
+ if (this.stderrRemainder.trim()) {
51
+ this.emit('stderr', this.stderrRemainder.trim());
52
+ }
53
+ this._connected = false;
54
+ this.lineScanner.clear();
55
+ this.stderrRemainder = '';
56
+ this.emit('close', code, signal);
57
+ });
58
+ this.childProcess.on('error', (err) => {
59
+ this._connected = false;
60
+ this.emit('error', err);
61
+ });
62
+ this._connected = true;
63
+ }
64
+ /**
65
+ * Write data to stdin with backpressure support.
66
+ */
67
+ async write(data) {
68
+ return new Promise((resolve, reject) => {
69
+ if (!this.childProcess?.stdin) {
70
+ reject(new Error('stdin not available'));
71
+ return;
72
+ }
73
+ const canContinue = this.childProcess.stdin.write(data, 'utf8', (err) => {
74
+ if (err) {
75
+ reject(err);
76
+ }
77
+ });
78
+ if (canContinue) {
79
+ resolve();
80
+ }
81
+ else {
82
+ this.childProcess.stdin.once('drain', () => {
83
+ resolve();
84
+ });
85
+ }
86
+ });
87
+ }
88
+ /**
89
+ * Kill the child process. Sends SIGTERM, then SIGKILL after 5s.
90
+ */
91
+ disconnect() {
92
+ if (!this.childProcess)
93
+ return;
94
+ const proc = this.childProcess;
95
+ this.childProcess = null;
96
+ this._connected = false;
97
+ this.lineScanner.clear();
98
+ this.stderrRemainder = '';
99
+ // Remove all listeners
100
+ proc.removeAllListeners();
101
+ proc.stdout?.removeAllListeners();
102
+ proc.stderr?.removeAllListeners();
103
+ proc.stdin?.removeAllListeners();
104
+ // Kill the process
105
+ try {
106
+ proc.kill('SIGTERM');
107
+ }
108
+ catch { /* already dead */ }
109
+ // Destroy stdio pipes
110
+ try {
111
+ proc.stdin?.destroy();
112
+ }
113
+ catch { /* ignore */ }
114
+ try {
115
+ proc.stdout?.destroy();
116
+ }
117
+ catch { /* ignore */ }
118
+ try {
119
+ proc.stderr?.destroy();
120
+ }
121
+ catch { /* ignore */ }
122
+ // Unref so Node doesn't wait
123
+ try {
124
+ proc.unref();
125
+ }
126
+ catch { /* ignore */ }
127
+ // Force kill after 5 seconds
128
+ setTimeout(() => {
129
+ try {
130
+ proc.kill('SIGKILL');
131
+ }
132
+ catch { /* already dead */ }
133
+ }, 5000).unref();
134
+ }
135
+ }
136
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5zdGRpb3RyYW5zcG9ydC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2NsYXNzZXMuc3RkaW90cmFuc3BvcnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFDeEMsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBWXZEOzs7R0FHRztBQUNILE1BQU0sT0FBTyxjQUFlLFNBQVEsT0FBTyxDQUFDLE1BQU0sQ0FBQyxZQUFZO0lBQ3JELE9BQU8sQ0FBeUI7SUFDaEMsWUFBWSxHQUE2QyxJQUFJLENBQUM7SUFDOUQsV0FBVyxDQUFjO0lBQ3pCLGVBQWUsR0FBVyxFQUFFLENBQUM7SUFDN0IsVUFBVSxHQUFZLEtBQUssQ0FBQztJQUVwQyxZQUFZLE9BQStCO1FBQ3pDLEtBQUssRUFBRSxDQUFDO1FBQ1IsSUFBSSxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUM7UUFDdkIsSUFBSSxDQUFDLFdBQVcsR0FBRyxJQUFJLFdBQVcsQ0FBQyxPQUFPLENBQUMsY0FBYyxFQUFFLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUM3RSxDQUFDO0lBRUQsSUFBVyxTQUFTO1FBQ2xCLE9BQU8sSUFBSSxDQUFDLFVBQVUsQ0FBQztJQUN6QixDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsT0FBTztRQUNsQixNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUc7WUFDMUIsQ0FBQyxDQUFDLEVBQUUsR0FBRyxPQUFPLENBQUMsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxHQUFHLEVBQUU7WUFDekMsQ0FBQyxDQUFDLEVBQUUsR0FBRyxPQUFPLENBQUMsR0FBRyxFQUFFLENBQUM7UUFFdkIsSUFBSSxDQUFDLFlBQVksR0FBRyxPQUFPLENBQUMsWUFBWSxDQUFDLEtBQUssQ0FDNUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxV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
@@ -1,4 +1,7 @@
1
1
  export { RustBridge } from './classes.rustbridge.js';
2
2
  export { RustBinaryLocator } from './classes.rustbinarylocator.js';
3
3
  export { StreamingResponse } from './classes.streamingresponse.js';
4
+ export { StdioTransport } from './classes.stdiotransport.js';
5
+ export { SocketTransport } from './classes.sockettransport.js';
6
+ export { LineScanner } from './classes.linescanner.js';
4
7
  export * from './interfaces/index.js';
package/dist_ts/index.js CHANGED
@@ -1,5 +1,8 @@
1
1
  export { RustBridge } from './classes.rustbridge.js';
2
2
  export { RustBinaryLocator } from './classes.rustbinarylocator.js';
3
3
  export { StreamingResponse } from './classes.streamingresponse.js';
4
+ export { StdioTransport } from './classes.stdiotransport.js';
5
+ export { SocketTransport } from './classes.sockettransport.js';
6
+ export { LineScanner } from './classes.linescanner.js';
4
7
  export * from './interfaces/index.js';
5
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDckQsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFDbkUsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFDbkUsY0FBYyx1QkFBdUIsQ0FBQyJ9
8
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDckQsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFDbkUsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFDbkUsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLDZCQUE2QixDQUFDO0FBQzdELE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUMvRCxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDdkQsY0FBYyx1QkFBdUIsQ0FBQyJ9
@@ -43,3 +43,16 @@ export interface IRustBridgeOptions extends IBinaryLocatorOptions {
43
43
  * Resets on each chunk received. */
44
44
  streamTimeoutMs?: number;
45
45
  }
46
+ /**
47
+ * Options for connecting to an already-running daemon via Unix socket or named pipe.
48
+ */
49
+ export interface ISocketConnectOptions {
50
+ /** Enable auto-reconnect on unexpected disconnect (default: false) */
51
+ autoReconnect?: boolean;
52
+ /** Initial delay between reconnect attempts in ms (default: 100) */
53
+ reconnectBaseDelayMs?: number;
54
+ /** Maximum delay between reconnect attempts in ms (default: 30000) */
55
+ reconnectMaxDelayMs?: number;
56
+ /** Maximum number of reconnect attempts before giving up (default: 10) */
57
+ maxReconnectAttempts?: number;
58
+ }
@@ -1,2 +1,3 @@
1
1
  export * from './ipc.js';
2
2
  export * from './config.js';
3
+ export * from './transport.js';
@@ -1,3 +1,4 @@
1
1
  export * from './ipc.js';
2
2
  export * from './config.js';
3
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9pbnRlcmZhY2VzL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGNBQWMsVUFBVSxDQUFDO0FBQ3pCLGNBQWMsYUFBYSxDQUFDIn0=
3
+ export * from './transport.js';
4
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9pbnRlcmZhY2VzL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGNBQWMsVUFBVSxDQUFDO0FBQ3pCLGNBQWMsYUFBYSxDQUFDO0FBQzVCLGNBQWMsZ0JBQWdCLENBQUMifQ==
@@ -0,0 +1,22 @@
1
+ import type * as plugins from '../plugins.js';
2
+ /**
3
+ * Abstract transport for communicating with a Rust process.
4
+ * Both stdio and socket transports implement this interface.
5
+ *
6
+ * Events emitted:
7
+ * - 'line' (line: string) — a complete newline-terminated JSON line received
8
+ * - 'error' (err: Error) — transport-level error
9
+ * - 'close' (...args: any[]) — transport has closed/disconnected
10
+ * - 'stderr' (line: string) — stderr output (stdio transport only)
11
+ * - 'reconnected' () — transport reconnected after unexpected disconnect (socket only)
12
+ */
13
+ export interface IRustTransport extends plugins.events.EventEmitter {
14
+ /** Connect the transport (spawn process or connect to socket). Resolves when I/O channel is open. */
15
+ connect(): Promise<void>;
16
+ /** Write a string to the transport. Handles backpressure. */
17
+ write(data: string): Promise<void>;
18
+ /** Disconnect the transport. For stdio: kills the process. For socket: closes the connection. */
19
+ disconnect(): void;
20
+ /** Whether the transport is currently connected and writable. */
21
+ readonly connected: boolean;
22
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHJhbnNwb3J0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvaW50ZXJmYWNlcy90cmFuc3BvcnQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiJ9
@@ -3,6 +3,7 @@ import * as fs from 'fs';
3
3
  import * as childProcess from 'child_process';
4
4
  import * as events from 'events';
5
5
  import * as url from 'url';
6
- export { path, fs, childProcess, events, url };
6
+ import * as net from 'net';
7
+ export { path, fs, childProcess, events, url, net };
7
8
  import * as smartpath from '@push.rocks/smartpath';
8
9
  export { smartpath };
@@ -4,8 +4,9 @@ import * as fs from 'fs';
4
4
  import * as childProcess from 'child_process';
5
5
  import * as events from 'events';
6
6
  import * as url from 'url';
7
- export { path, fs, childProcess, events, url };
7
+ import * as net from 'net';
8
+ export { path, fs, childProcess, events, url, net };
8
9
  // @push.rocks scope
9
10
  import * as smartpath from '@push.rocks/smartpath';
10
11
  export { smartpath };
11
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3BsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsZUFBZTtBQUNmLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBQzdCLE9BQU8sS0FBSyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQ3pCLE9BQU8sS0FBSyxZQUFZLE1BQU0sZUFBZSxDQUFDO0FBQzlDLE9BQU8sS0FBSyxNQUFNLE1BQU0sUUFBUSxDQUFDO0FBQ2pDLE9BQU8sS0FBSyxHQUFHLE1BQU0sS0FBSyxDQUFDO0FBRTNCLE9BQU8sRUFBRSxJQUFJLEVBQUUsRUFBRSxFQUFFLFlBQVksRUFBRSxNQUFNLEVBQUUsR0FBRyxFQUFFLENBQUM7QUFFL0Msb0JBQW9CO0FBQ3BCLE9BQU8sS0FBSyxTQUFTLE1BQU0sdUJBQXVCLENBQUM7QUFFbkQsT0FBTyxFQUFFLFNBQVMsRUFBRSxDQUFDIn0=
12
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3BsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsZUFBZTtBQUNmLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBQzdCLE9BQU8sS0FBSyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQ3pCLE9BQU8sS0FBSyxZQUFZLE1BQU0sZUFBZSxDQUFDO0FBQzlDLE9BQU8sS0FBSyxNQUFNLE1BQU0sUUFBUSxDQUFDO0FBQ2pDLE9BQU8sS0FBSyxHQUFHLE1BQU0sS0FBSyxDQUFDO0FBQzNCLE9BQU8sS0FBSyxHQUFHLE1BQU0sS0FBSyxDQUFDO0FBRTNCLE9BQU8sRUFBRSxJQUFJLEVBQUUsRUFBRSxFQUFFLFlBQVksRUFBRSxNQUFNLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxDQUFDO0FBRXBELG9CQUFvQjtBQUNwQixPQUFPLEtBQUssU0FBUyxNQUFNLHVCQUF1QixDQUFDO0FBRW5ELE9BQU8sRUFBRSxTQUFTLEVBQUUsQ0FBQyJ9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartrust",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "private": false,
5
5
  "description": "a bridge between JS engines and rust",
6
6
  "main": "dist_ts/index.js",
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Mock "Rust daemon" for testing the SocketTransport and RustBridge socket mode.
5
+ * Creates a Unix socket server, accepts connections, and speaks the same
6
+ * JSON-over-newline IPC protocol as mock-rust-binary.mjs.
7
+ *
8
+ * Usage: node mock-socket-server.mjs <socket-path>
9
+ * Signals readiness by writing a JSON line to stdout: {"socketPath":"...","ready":true}
10
+ */
11
+
12
+ import * as net from 'net';
13
+ import * as fs from 'fs';
14
+
15
+ const socketPath = process.argv[2];
16
+ if (!socketPath) {
17
+ process.stderr.write('Usage: mock-socket-server.mjs <socket-path>\n');
18
+ process.exit(1);
19
+ }
20
+
21
+ // Remove stale socket file
22
+ try { fs.unlinkSync(socketPath); } catch { /* ignore */ }
23
+
24
+ /**
25
+ * Backpressure-aware write to a socket.
26
+ */
27
+ function writeResponse(conn, data) {
28
+ const json = JSON.stringify(data) + '\n';
29
+ if (!conn.write(json)) {
30
+ conn.once('drain', () => {});
31
+ }
32
+ }
33
+
34
+ function handleLine(line, conn) {
35
+ let request;
36
+ try {
37
+ request = JSON.parse(line);
38
+ } catch {
39
+ return;
40
+ }
41
+
42
+ const { id, method, params } = request;
43
+
44
+ if (method === 'echo') {
45
+ writeResponse(conn, { id, success: true, result: params });
46
+ } else if (method === 'largeEcho') {
47
+ writeResponse(conn, { id, success: true, result: params });
48
+ } else if (method === 'error') {
49
+ writeResponse(conn, { id, success: false, error: 'Test error message' });
50
+ } else if (method === 'emitEvent') {
51
+ writeResponse(conn, { event: params.eventName, data: params.eventData });
52
+ writeResponse(conn, { id, success: true, result: null });
53
+ } else if (method === 'slow') {
54
+ setTimeout(() => {
55
+ writeResponse(conn, { id, success: true, result: { delayed: true } });
56
+ }, 100);
57
+ } else if (method === 'streamEcho') {
58
+ const count = params.count || 0;
59
+ let sent = 0;
60
+ const interval = setInterval(() => {
61
+ if (sent < count) {
62
+ writeResponse(conn, { id, stream: true, data: { index: sent, value: `chunk_${sent}` } });
63
+ sent++;
64
+ } else {
65
+ clearInterval(interval);
66
+ writeResponse(conn, { id, success: true, result: { totalChunks: count } });
67
+ }
68
+ }, 10);
69
+ } else if (method === 'streamError') {
70
+ writeResponse(conn, { id, stream: true, data: { index: 0, value: 'before_error' } });
71
+ setTimeout(() => {
72
+ writeResponse(conn, { id, success: false, error: 'Stream error after chunk' });
73
+ }, 20);
74
+ } else if (method === 'streamEmpty') {
75
+ writeResponse(conn, { id, success: true, result: { totalChunks: 0 } });
76
+ } else if (method === 'exit') {
77
+ writeResponse(conn, { id, success: true, result: null });
78
+ // In socket mode, 'exit' just closes this connection, not the server
79
+ setTimeout(() => conn.end(), 50);
80
+ } else {
81
+ writeResponse(conn, { id, success: false, error: `Unknown method: ${method}` });
82
+ }
83
+ }
84
+
85
+ const server = net.createServer((conn) => {
86
+ // Send ready event on each new connection
87
+ writeResponse(conn, { event: 'ready', data: { version: '1.0.0' } });
88
+
89
+ // Buffer-based newline scanner for incoming data
90
+ let buffer = Buffer.alloc(0);
91
+ conn.on('data', (chunk) => {
92
+ buffer = Buffer.concat([buffer, chunk]);
93
+ let idx;
94
+ while ((idx = buffer.indexOf(0x0A)) !== -1) {
95
+ const lineBuffer = buffer.subarray(0, idx);
96
+ buffer = buffer.subarray(idx + 1);
97
+ const line = lineBuffer.toString('utf8').trim();
98
+ if (line) {
99
+ handleLine(line, conn);
100
+ }
101
+ }
102
+ });
103
+
104
+ conn.on('error', () => { /* ignore client errors */ });
105
+ });
106
+
107
+ server.listen(socketPath, () => {
108
+ // Signal to parent that the server is ready
109
+ process.stdout.write(JSON.stringify({ socketPath, ready: true }) + '\n');
110
+ });
111
+
112
+ // Handle SIGTERM gracefully
113
+ process.on('SIGTERM', () => {
114
+ server.close();
115
+ try { fs.unlinkSync(socketPath); } catch { /* ignore */ }
116
+ process.exit(0);
117
+ });
118
+
119
+ // Handle SIGINT
120
+ process.on('SIGINT', () => {
121
+ server.close();
122
+ try { fs.unlinkSync(socketPath); } catch { /* ignore */ }
123
+ process.exit(0);
124
+ });
@@ -0,0 +1,89 @@
1
+ import { expect, tap } from '@git.zone/tstest/tapbundle';
2
+ import { LineScanner } from '../ts/classes.linescanner.js';
3
+
4
+ const noopLogger = { log() {} };
5
+
6
+ tap.test('should parse a single complete line', async () => {
7
+ const scanner = new LineScanner(1024 * 1024, noopLogger);
8
+ const lines: string[] = [];
9
+ scanner.push(Buffer.from('{"hello":"world"}\n'), (line) => lines.push(line));
10
+ expect(lines.length).toEqual(1);
11
+ expect(lines[0]).toEqual('{"hello":"world"}');
12
+ });
13
+
14
+ tap.test('should parse multiple lines in one chunk', async () => {
15
+ const scanner = new LineScanner(1024 * 1024, noopLogger);
16
+ const lines: string[] = [];
17
+ scanner.push(Buffer.from('{"a":1}\n{"b":2}\n{"c":3}\n'), (line) => lines.push(line));
18
+ expect(lines.length).toEqual(3);
19
+ expect(lines[0]).toEqual('{"a":1}');
20
+ expect(lines[1]).toEqual('{"b":2}');
21
+ expect(lines[2]).toEqual('{"c":3}');
22
+ });
23
+
24
+ tap.test('should handle a line split across chunks', async () => {
25
+ const scanner = new LineScanner(1024 * 1024, noopLogger);
26
+ const lines: string[] = [];
27
+ scanner.push(Buffer.from('{"hel'), (line) => lines.push(line));
28
+ expect(lines.length).toEqual(0);
29
+ scanner.push(Buffer.from('lo":"world"}\n'), (line) => lines.push(line));
30
+ expect(lines.length).toEqual(1);
31
+ expect(lines[0]).toEqual('{"hello":"world"}');
32
+ });
33
+
34
+ tap.test('should drop oversized lines', async () => {
35
+ const scanner = new LineScanner(100, noopLogger);
36
+ const lines: string[] = [];
37
+ // Line is 200 chars + newline, exceeds maxPayloadSize of 100
38
+ const oversized = 'x'.repeat(200) + '\n';
39
+ scanner.push(Buffer.from(oversized), (line) => lines.push(line));
40
+ expect(lines.length).toEqual(0);
41
+ });
42
+
43
+ tap.test('should clear buffer on OOM (no newline, exceeds max)', async () => {
44
+ const scanner = new LineScanner(100, noopLogger);
45
+ const lines: string[] = [];
46
+ // Push 200 bytes without any newline — exceeds maxPayloadSize
47
+ scanner.push(Buffer.from('x'.repeat(200)), (line) => lines.push(line));
48
+ expect(lines.length).toEqual(0);
49
+ // After clearing, should work normally again
50
+ scanner.push(Buffer.from('{"ok":true}\n'), (line) => lines.push(line));
51
+ expect(lines.length).toEqual(1);
52
+ expect(lines[0]).toEqual('{"ok":true}');
53
+ });
54
+
55
+ tap.test('should skip empty lines', async () => {
56
+ const scanner = new LineScanner(1024 * 1024, noopLogger);
57
+ const lines: string[] = [];
58
+ scanner.push(Buffer.from('\n\n{"a":1}\n\n'), (line) => lines.push(line));
59
+ expect(lines.length).toEqual(1);
60
+ expect(lines[0]).toEqual('{"a":1}');
61
+ });
62
+
63
+ tap.test('should handle mixed complete and partial lines', async () => {
64
+ const scanner = new LineScanner(1024 * 1024, noopLogger);
65
+ const lines: string[] = [];
66
+ // First chunk: one complete line + start of second line
67
+ scanner.push(Buffer.from('{"first":1}\n{"sec'), (line) => lines.push(line));
68
+ expect(lines.length).toEqual(1);
69
+ // Second chunk: end of second line + complete third line
70
+ scanner.push(Buffer.from('ond":2}\n{"third":3}\n'), (line) => lines.push(line));
71
+ expect(lines.length).toEqual(3);
72
+ expect(lines[1]).toEqual('{"second":2}');
73
+ expect(lines[2]).toEqual('{"third":3}');
74
+ });
75
+
76
+ tap.test('clear should reset the buffer', async () => {
77
+ const scanner = new LineScanner(1024 * 1024, noopLogger);
78
+ const lines: string[] = [];
79
+ // Push partial data
80
+ scanner.push(Buffer.from('{"partial":'), (line) => lines.push(line));
81
+ // Clear
82
+ scanner.clear();
83
+ // Now push a complete new line — old partial should not affect it
84
+ scanner.push(Buffer.from('{"fresh":true}\n'), (line) => lines.push(line));
85
+ expect(lines.length).toEqual(1);
86
+ expect(lines[0]).toEqual('{"fresh":true}');
87
+ });
88
+
89
+ export default tap.start();