@push.rocks/smartrust 1.1.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 (40) hide show
  1. package/.gitea/workflows/default_nottags.yaml +66 -0
  2. package/.gitea/workflows/default_tags.yaml +124 -0
  3. package/.vscode/launch.json +11 -0
  4. package/.vscode/settings.json +26 -0
  5. package/changelog.md +22 -0
  6. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  7. package/dist_ts/00_commitinfo_data.js +9 -0
  8. package/dist_ts/classes.rustbinarylocator.d.ts +28 -0
  9. package/dist_ts/classes.rustbinarylocator.js +126 -0
  10. package/dist_ts/classes.rustbridge.d.ts +39 -0
  11. package/dist_ts/classes.rustbridge.js +231 -0
  12. package/dist_ts/index.d.ts +3 -0
  13. package/dist_ts/index.js +4 -0
  14. package/dist_ts/interfaces/config.d.ts +40 -0
  15. package/dist_ts/interfaces/config.js +2 -0
  16. package/dist_ts/interfaces/index.d.ts +2 -0
  17. package/dist_ts/interfaces/index.js +3 -0
  18. package/dist_ts/interfaces/ipc.d.ts +36 -0
  19. package/dist_ts/interfaces/ipc.js +2 -0
  20. package/dist_ts/paths.d.ts +1 -0
  21. package/dist_ts/paths.js +3 -0
  22. package/dist_ts/plugins.d.ts +8 -0
  23. package/dist_ts/plugins.js +11 -0
  24. package/npmextra.json +24 -0
  25. package/package.json +25 -0
  26. package/readme.md +5 -0
  27. package/test/helpers/mock-rust-binary.mjs +62 -0
  28. package/test/test.rustbinarylocator.node.ts +98 -0
  29. package/test/test.rustbridge.node.ts +191 -0
  30. package/test/test.ts +12 -0
  31. package/ts/00_commitinfo_data.ts +8 -0
  32. package/ts/classes.rustbinarylocator.ts +140 -0
  33. package/ts/classes.rustbridge.ts +256 -0
  34. package/ts/index.ts +3 -0
  35. package/ts/interfaces/config.ts +42 -0
  36. package/ts/interfaces/index.ts +2 -0
  37. package/ts/interfaces/ipc.ts +40 -0
  38. package/ts/paths.ts +5 -0
  39. package/ts/plugins.ts +13 -0
  40. package/tsconfig.json +12 -0
@@ -0,0 +1,231 @@
1
+ import * as plugins from './plugins.js';
2
+ import { RustBinaryLocator } from './classes.rustbinarylocator.js';
3
+ const defaultLogger = {
4
+ log() { },
5
+ };
6
+ /**
7
+ * Generic bridge between TypeScript and a Rust binary.
8
+ * Communicates via JSON-over-stdin/stdout IPC protocol.
9
+ *
10
+ * @typeParam TCommands - Map of command names to their param/result types
11
+ */
12
+ export class RustBridge extends plugins.events.EventEmitter {
13
+ locator;
14
+ options;
15
+ logger;
16
+ childProcess = null;
17
+ readlineInterface = null;
18
+ pendingRequests = new Map();
19
+ requestCounter = 0;
20
+ isRunning = false;
21
+ binaryPath = null;
22
+ constructor(options) {
23
+ super();
24
+ this.logger = options.logger || defaultLogger;
25
+ this.options = {
26
+ cliArgs: ['--management'],
27
+ requestTimeoutMs: 30000,
28
+ readyTimeoutMs: 10000,
29
+ readyEventName: 'ready',
30
+ ...options,
31
+ };
32
+ this.locator = new RustBinaryLocator(options, this.logger);
33
+ }
34
+ /**
35
+ * Spawn the Rust binary and wait for it to signal readiness.
36
+ * Returns true if the binary was found and spawned successfully.
37
+ */
38
+ async spawn() {
39
+ this.binaryPath = await this.locator.findBinary();
40
+ if (!this.binaryPath) {
41
+ return false;
42
+ }
43
+ return new Promise((resolve) => {
44
+ try {
45
+ const env = this.options.env
46
+ ? { ...process.env, ...this.options.env }
47
+ : { ...process.env };
48
+ this.childProcess = plugins.childProcess.spawn(this.binaryPath, this.options.cliArgs, {
49
+ stdio: ['pipe', 'pipe', 'pipe'],
50
+ env,
51
+ });
52
+ // Handle stderr
53
+ this.childProcess.stderr?.on('data', (data) => {
54
+ const lines = data.toString().split('\n').filter((l) => l.trim());
55
+ for (const line of lines) {
56
+ this.logger.log('debug', `[${this.options.binaryName}] ${line}`);
57
+ this.emit('stderr', line);
58
+ }
59
+ });
60
+ // Handle stdout via readline for line-delimited JSON
61
+ this.readlineInterface = plugins.readline.createInterface({ input: this.childProcess.stdout });
62
+ this.readlineInterface.on('line', (line) => {
63
+ this.handleLine(line.trim());
64
+ });
65
+ // Handle process exit
66
+ this.childProcess.on('exit', (code, signal) => {
67
+ this.logger.log('info', `Process exited (code=${code}, signal=${signal})`);
68
+ this.cleanup();
69
+ this.emit('exit', code, signal);
70
+ });
71
+ this.childProcess.on('error', (err) => {
72
+ this.logger.log('error', `Process error: ${err.message}`);
73
+ this.cleanup();
74
+ resolve(false);
75
+ });
76
+ // Wait for the ready event
77
+ const readyTimeout = setTimeout(() => {
78
+ this.logger.log('error', `Process did not send ready event within ${this.options.readyTimeoutMs}ms`);
79
+ this.kill();
80
+ resolve(false);
81
+ }, this.options.readyTimeoutMs);
82
+ this.once(`management:${this.options.readyEventName}`, () => {
83
+ clearTimeout(readyTimeout);
84
+ this.isRunning = true;
85
+ this.logger.log('info', `Bridge connected to ${this.options.binaryName}`);
86
+ this.emit('ready');
87
+ resolve(true);
88
+ });
89
+ }
90
+ catch (err) {
91
+ this.logger.log('error', `Failed to spawn: ${err.message}`);
92
+ resolve(false);
93
+ }
94
+ });
95
+ }
96
+ /**
97
+ * Send a typed command to the Rust process and wait for the response.
98
+ */
99
+ async sendCommand(method, params) {
100
+ if (!this.childProcess || !this.isRunning) {
101
+ throw new Error(`${this.options.binaryName} bridge is not running`);
102
+ }
103
+ const id = `req_${++this.requestCounter}`;
104
+ const request = { id, method, params };
105
+ return new Promise((resolve, reject) => {
106
+ const timer = setTimeout(() => {
107
+ this.pendingRequests.delete(id);
108
+ reject(new Error(`Command '${method}' timed out after ${this.options.requestTimeoutMs}ms`));
109
+ }, this.options.requestTimeoutMs);
110
+ this.pendingRequests.set(id, { resolve, reject, timer });
111
+ const json = JSON.stringify(request) + '\n';
112
+ this.childProcess.stdin.write(json, (err) => {
113
+ if (err) {
114
+ clearTimeout(timer);
115
+ this.pendingRequests.delete(id);
116
+ reject(new Error(`Failed to write to stdin: ${err.message}`));
117
+ }
118
+ });
119
+ });
120
+ }
121
+ /**
122
+ * Kill the Rust process and clean up all resources.
123
+ */
124
+ kill() {
125
+ if (this.childProcess) {
126
+ const proc = this.childProcess;
127
+ this.childProcess = null;
128
+ this.isRunning = false;
129
+ // Close readline
130
+ if (this.readlineInterface) {
131
+ this.readlineInterface.close();
132
+ this.readlineInterface = null;
133
+ }
134
+ // Reject pending requests
135
+ for (const [, pending] of this.pendingRequests) {
136
+ clearTimeout(pending.timer);
137
+ pending.reject(new Error(`${this.options.binaryName} process killed`));
138
+ }
139
+ this.pendingRequests.clear();
140
+ // Remove all listeners
141
+ proc.removeAllListeners();
142
+ proc.stdout?.removeAllListeners();
143
+ proc.stderr?.removeAllListeners();
144
+ proc.stdin?.removeAllListeners();
145
+ // Kill the process
146
+ try {
147
+ proc.kill('SIGTERM');
148
+ }
149
+ catch { /* already dead */ }
150
+ // Destroy stdio pipes
151
+ try {
152
+ proc.stdin?.destroy();
153
+ }
154
+ catch { /* ignore */ }
155
+ try {
156
+ proc.stdout?.destroy();
157
+ }
158
+ catch { /* ignore */ }
159
+ try {
160
+ proc.stderr?.destroy();
161
+ }
162
+ catch { /* ignore */ }
163
+ // Unref so Node doesn't wait
164
+ try {
165
+ proc.unref();
166
+ }
167
+ catch { /* ignore */ }
168
+ // Force kill after 5 seconds
169
+ setTimeout(() => {
170
+ try {
171
+ proc.kill('SIGKILL');
172
+ }
173
+ catch { /* already dead */ }
174
+ }, 5000).unref();
175
+ }
176
+ }
177
+ /**
178
+ * Whether the bridge is currently running.
179
+ */
180
+ get running() {
181
+ return this.isRunning;
182
+ }
183
+ handleLine(line) {
184
+ if (!line)
185
+ return;
186
+ let parsed;
187
+ try {
188
+ parsed = JSON.parse(line);
189
+ }
190
+ catch {
191
+ this.logger.log('warn', `Non-JSON output: ${line}`);
192
+ return;
193
+ }
194
+ // Check if it's an event (has 'event' field, no 'id')
195
+ if ('event' in parsed && !('id' in parsed)) {
196
+ const event = parsed;
197
+ this.emit(`management:${event.event}`, event.data);
198
+ return;
199
+ }
200
+ // Otherwise it's a response (has 'id' field)
201
+ if ('id' in parsed) {
202
+ const response = parsed;
203
+ const pending = this.pendingRequests.get(response.id);
204
+ if (pending) {
205
+ clearTimeout(pending.timer);
206
+ this.pendingRequests.delete(response.id);
207
+ if (response.success) {
208
+ pending.resolve(response.result);
209
+ }
210
+ else {
211
+ pending.reject(new Error(response.error || 'Unknown error from Rust process'));
212
+ }
213
+ }
214
+ }
215
+ }
216
+ cleanup() {
217
+ this.isRunning = false;
218
+ this.childProcess = null;
219
+ if (this.readlineInterface) {
220
+ this.readlineInterface.close();
221
+ this.readlineInterface = null;
222
+ }
223
+ // Reject all pending requests
224
+ for (const [, pending] of this.pendingRequests) {
225
+ clearTimeout(pending.timer);
226
+ pending.reject(new Error(`${this.options.binaryName} process exited`));
227
+ }
228
+ this.pendingRequests.clear();
229
+ }
230
+ }
231
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5ydXN0YnJpZGdlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5ydXN0YnJpZGdlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBVW5FLE1BQU0sYUFBYSxHQUFzQjtJQUN2QyxHQUFHLEtBQUksQ0FBQztDQUNULENBQUM7QUFFRjs7Ozs7R0FLRztBQUNILE1BQU0sT0FBTyxVQUF3RCxTQUFRLE9BQU8sQ0FBQyxNQUFNLENBQUMsWUFBWTtJQUM5RixPQUFPLENBQW9CO0lBQzNCLE9BQU8sQ0FBZ0k7SUFDdkksTUFBTSxDQUFvQjtJQUMxQixZQUFZLEdBQTZDLElBQUksQ0FBQztJQUM5RCxpQkFBaUIsR0FBc0MsSUFBSSxDQUFDO0lBQzVELGVBQWUsR0FBRyxJQUFJLEdBQUcsRUFJN0IsQ0FBQztJQUNHLGNBQWMsR0FBRyxDQUFDLENBQUM7SUFDbkIsU0FBUyxHQUFHLEtBQUssQ0FBQztJQUNsQixVQUFVLEdBQWtCLElBQUksQ0FBQztJQUV6QyxZQUFZLE9BQTJCO1FBQ3JDLEtBQUssRUFBRSxDQUFDO1FBQ1IsSUFBSSxDQUFDLE1BQU0sR0FBRyxPQUFPLENBQUMsTUFBTSxJQUFJLGFBQWEsQ0FBQztRQUM5QyxJQUFJLENBQUMsT0FBTyxHQUFHO1lBQ2IsT0FBTyxFQUFFLENBQUMsY0FBYyxDQUFDO1lBQ3pCLGdCQUFnQixFQUFFLEtBQUs7WUFDdkIsY0FBYyxFQUFFLEtBQUs7WUFDckIsY0FBYyxFQUFFLE9BQU87WUFDdkIsR0FBRyxPQUFPO1NBQ1gsQ0FBQztRQUNGLElBQUksQ0FBQyxPQUFPLEdBQUcsSUFBSSxpQkFBaUIsQ0FBQyxPQUFPLEVBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQzdELENBQUM7SUFFRDs7O09BR0c7SUFDSSxLQUFLLENBQUMs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
@@ -0,0 +1,3 @@
1
+ export { RustBridge } from './classes.rustbridge.js';
2
+ export { RustBinaryLocator } from './classes.rustbinarylocator.js';
3
+ export * from './interfaces/index.js';
@@ -0,0 +1,4 @@
1
+ export { RustBridge } from './classes.rustbridge.js';
2
+ export { RustBinaryLocator } from './classes.rustbinarylocator.js';
3
+ export * from './interfaces/index.js';
4
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDckQsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sZ0NBQWdDLENBQUM7QUFDbkUsY0FBYyx1QkFBdUIsQ0FBQyJ9
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Minimal logger interface for the bridge.
3
+ */
4
+ export interface IRustBridgeLogger {
5
+ log(level: string, message: string, data?: Record<string, any>): void;
6
+ }
7
+ /**
8
+ * Options for locating a Rust binary.
9
+ */
10
+ export interface IBinaryLocatorOptions {
11
+ /** Name of the binary (e.g., 'rustproxy') */
12
+ binaryName: string;
13
+ /** Environment variable to check for explicit binary path (e.g., 'SMARTPROXY_RUST_BINARY') */
14
+ envVarName?: string;
15
+ /** Prefix for platform-specific npm packages (e.g., '@push.rocks/smartproxy') */
16
+ platformPackagePrefix?: string;
17
+ /** Additional local paths to search (defaults to ./rust/target/release/<binaryName> and ./rust/target/debug/<binaryName>) */
18
+ localPaths?: string[];
19
+ /** Whether to search the system PATH (default: true) */
20
+ searchSystemPath?: boolean;
21
+ /** Explicit binary path override - skips all other search */
22
+ binaryPath?: string;
23
+ }
24
+ /**
25
+ * Options for the RustBridge.
26
+ */
27
+ export interface IRustBridgeOptions extends IBinaryLocatorOptions {
28
+ /** CLI arguments passed to the binary (default: ['--management']) */
29
+ cliArgs?: string[];
30
+ /** Timeout for individual requests in ms (default: 30000) */
31
+ requestTimeoutMs?: number;
32
+ /** Timeout for the ready event during spawn in ms (default: 10000) */
33
+ readyTimeoutMs?: number;
34
+ /** Additional environment variables for the child process */
35
+ env?: Record<string, string>;
36
+ /** Name of the ready event emitted by the Rust binary (default: 'ready') */
37
+ readyEventName?: string;
38
+ /** Optional logger instance */
39
+ logger?: IRustBridgeLogger;
40
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvaW50ZXJmYWNlcy9jb25maWcudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiJ9
@@ -0,0 +1,2 @@
1
+ export * from './ipc.js';
2
+ export * from './config.js';
@@ -0,0 +1,3 @@
1
+ export * from './ipc.js';
2
+ export * from './config.js';
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9pbnRlcmZhY2VzL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGNBQWMsVUFBVSxDQUFDO0FBQ3pCLGNBQWMsYUFBYSxDQUFDIn0=
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Management request sent to the Rust binary via stdin.
3
+ */
4
+ export interface IManagementRequest {
5
+ id: string;
6
+ method: string;
7
+ params: Record<string, any>;
8
+ }
9
+ /**
10
+ * Management response received from the Rust binary via stdout.
11
+ */
12
+ export interface IManagementResponse {
13
+ id: string;
14
+ success: boolean;
15
+ result?: any;
16
+ error?: string;
17
+ }
18
+ /**
19
+ * Management event received from the Rust binary (unsolicited, no id field).
20
+ */
21
+ export interface IManagementEvent {
22
+ event: string;
23
+ data: any;
24
+ }
25
+ /**
26
+ * Definition of a single command supported by a Rust binary.
27
+ */
28
+ export interface ICommandDefinition<TParams = any, TResult = any> {
29
+ params: TParams;
30
+ result: TResult;
31
+ }
32
+ /**
33
+ * Map of command names to their definitions.
34
+ * Used to type-safe the bridge's sendCommand method.
35
+ */
36
+ export type TCommandMap = Record<string, ICommandDefinition>;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaXBjLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHMvaW50ZXJmYWNlcy9pcGMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiJ9
@@ -0,0 +1 @@
1
+ export declare const packageDir: string;
@@ -0,0 +1,3 @@
1
+ import * as plugins from './plugins.js';
2
+ export const packageDir = plugins.path.join(plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url), '../');
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGF0aHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9wYXRocy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGNBQWMsQ0FBQztBQUN4QyxNQUFNLENBQUMsTUFBTSxVQUFVLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQ3pDLE9BQU8sQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLHdCQUF3QixDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLEVBQy9ELEtBQUssQ0FDTixDQUFDIn0=
@@ -0,0 +1,8 @@
1
+ import * as path from 'path';
2
+ import * as fs from 'fs';
3
+ import * as childProcess from 'child_process';
4
+ import * as readline from 'readline';
5
+ import * as events from 'events';
6
+ export { path, fs, childProcess, readline, events };
7
+ import * as smartpath from '@push.rocks/smartpath';
8
+ export { smartpath };
@@ -0,0 +1,11 @@
1
+ // native scope
2
+ import * as path from 'path';
3
+ import * as fs from 'fs';
4
+ import * as childProcess from 'child_process';
5
+ import * as readline from 'readline';
6
+ import * as events from 'events';
7
+ export { path, fs, childProcess, readline, events };
8
+ // @push.rocks scope
9
+ import * as smartpath from '@push.rocks/smartpath';
10
+ export { smartpath };
11
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2lucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3BsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsZUFBZTtBQUNmLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBQzdCLE9BQU8sS0FBSyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQ3pCLE9BQU8sS0FBSyxZQUFZLE1BQU0sZUFBZSxDQUFDO0FBQzlDLE9BQU8sS0FBSyxRQUFRLE1BQU0sVUFBVSxDQUFDO0FBQ3JDLE9BQU8sS0FBSyxNQUFNLE1BQU0sUUFBUSxDQUFDO0FBRWpDLE9BQU8sRUFBRSxJQUFJLEVBQUUsRUFBRSxFQUFFLFlBQVksRUFBRSxRQUFRLEVBQUUsTUFBTSxFQUFFLENBQUM7QUFFcEQsb0JBQW9CO0FBQ3BCLE9BQU8sS0FBSyxTQUFTLE1BQU0sdUJBQXVCLENBQUM7QUFFbkQsT0FBTyxFQUFFLFNBQVMsRUFBRSxDQUFDIn0=
package/npmextra.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "@git.zone/cli": {
3
+ "projectType": "npm",
4
+ "module": {
5
+ "githost": "code.foss.global",
6
+ "gitscope": "push.rocks",
7
+ "gitrepo": "smartrust",
8
+ "description": "a bridge between JS engines and rust",
9
+ "npmPackagename": "@push.rocks/smartrust",
10
+ "license": "MIT",
11
+ "projectDomain": "push.rocks"
12
+ },
13
+ "release": {
14
+ "accessLevel": "public",
15
+ "registries": [
16
+ "https://verdaccio.lossless.digital",
17
+ "https://registry.npmjs.org"
18
+ ]
19
+ }
20
+ },
21
+ "@ship.zone/szci": {
22
+ "npmGlobalTools": []
23
+ }
24
+ }
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@push.rocks/smartrust",
3
+ "version": "1.1.0",
4
+ "private": false,
5
+ "description": "a bridge between JS engines and rust",
6
+ "main": "dist_ts/index.js",
7
+ "typings": "dist_ts/index.d.ts",
8
+ "type": "module",
9
+ "author": "Task Venture Capital GmbH",
10
+ "license": "MIT",
11
+ "scripts": {
12
+ "test": "(tstest test/ --verbose --logfile --timeout 60)",
13
+ "build": "(tsbuild tsfolders --allowimplicitany)",
14
+ "buildDocs": "(tsdoc)"
15
+ },
16
+ "devDependencies": {
17
+ "@git.zone/tsbuild": "^4.1.2",
18
+ "@git.zone/tsrun": "^2.0.1",
19
+ "@git.zone/tstest": "^3.1.8",
20
+ "@types/node": "^25.2.0"
21
+ },
22
+ "dependencies": {
23
+ "@push.rocks/smartpath": "^6.0.0"
24
+ }
25
+ }
package/readme.md ADDED
@@ -0,0 +1,5 @@
1
+ # @push.rocks/smartrust
2
+ a bridge between JS engines and rust
3
+
4
+ ## How to create the docs
5
+ To create docs run gitzone aidoc.
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Mock "Rust binary" for testing the RustBridge IPC protocol.
5
+ * Reads JSON lines from stdin, writes JSON lines to stdout.
6
+ * Emits a ready event on startup.
7
+ */
8
+
9
+ import { createInterface } from 'readline';
10
+
11
+ // Emit ready event
12
+ const readyEvent = JSON.stringify({ event: 'ready', data: { version: '1.0.0' } });
13
+ process.stdout.write(readyEvent + '\n');
14
+
15
+ const rl = createInterface({ input: process.stdin });
16
+
17
+ rl.on('line', (line) => {
18
+ let request;
19
+ try {
20
+ request = JSON.parse(line.trim());
21
+ } catch {
22
+ return;
23
+ }
24
+
25
+ const { id, method, params } = request;
26
+
27
+ if (method === 'echo') {
28
+ // Echo back the params as result
29
+ const response = JSON.stringify({ id, success: true, result: params });
30
+ process.stdout.write(response + '\n');
31
+ } else if (method === 'error') {
32
+ // Return an error
33
+ const response = JSON.stringify({ id, success: false, error: 'Test error message' });
34
+ process.stdout.write(response + '\n');
35
+ } else if (method === 'emitEvent') {
36
+ // Emit a custom event, then respond with success
37
+ const event = JSON.stringify({ event: params.eventName, data: params.eventData });
38
+ process.stdout.write(event + '\n');
39
+ const response = JSON.stringify({ id, success: true, result: null });
40
+ process.stdout.write(response + '\n');
41
+ } else if (method === 'slow') {
42
+ // Respond after a delay
43
+ setTimeout(() => {
44
+ const response = JSON.stringify({ id, success: true, result: { delayed: true } });
45
+ process.stdout.write(response + '\n');
46
+ }, 100);
47
+ } else if (method === 'exit') {
48
+ // Graceful exit
49
+ const response = JSON.stringify({ id, success: true, result: null });
50
+ process.stdout.write(response + '\n');
51
+ process.exit(0);
52
+ } else {
53
+ // Unknown command
54
+ const response = JSON.stringify({ id, success: false, error: `Unknown method: ${method}` });
55
+ process.stdout.write(response + '\n');
56
+ }
57
+ });
58
+
59
+ // Handle SIGTERM gracefully
60
+ process.on('SIGTERM', () => {
61
+ process.exit(0);
62
+ });
@@ -0,0 +1,98 @@
1
+ import { expect, tap } from '@git.zone/tstest/tapbundle';
2
+ import * as path from 'path';
3
+ import * as fs from 'fs';
4
+ import { RustBinaryLocator } from '../ts/classes.rustbinarylocator.js';
5
+
6
+ const testDir = path.resolve(path.dirname(new URL(import.meta.url).pathname));
7
+
8
+ tap.test('should return null when no binary is found', async () => {
9
+ const locator = new RustBinaryLocator({
10
+ binaryName: 'nonexistent-binary-xyz',
11
+ searchSystemPath: false,
12
+ });
13
+ const result = await locator.findBinary();
14
+ expect(result).toBeNull();
15
+ });
16
+
17
+ tap.test('should use explicit binaryPath when provided', async () => {
18
+ const mockBinaryPath = path.join(testDir, 'helpers/mock-rust-binary.mjs');
19
+ const locator = new RustBinaryLocator({
20
+ binaryName: 'mock-rust-binary',
21
+ binaryPath: mockBinaryPath,
22
+ searchSystemPath: false,
23
+ });
24
+ const result = await locator.findBinary();
25
+ expect(result).toEqual(mockBinaryPath);
26
+ });
27
+
28
+ tap.test('should cache the result', async () => {
29
+ const mockBinaryPath = path.join(testDir, 'helpers/mock-rust-binary.mjs');
30
+ const locator = new RustBinaryLocator({
31
+ binaryName: 'mock-rust-binary',
32
+ binaryPath: mockBinaryPath,
33
+ searchSystemPath: false,
34
+ });
35
+
36
+ const first = await locator.findBinary();
37
+ const second = await locator.findBinary();
38
+ expect(first).toEqual(second);
39
+ expect(first).toEqual(mockBinaryPath);
40
+ });
41
+
42
+ tap.test('should clear cache', async () => {
43
+ const mockBinaryPath = path.join(testDir, 'helpers/mock-rust-binary.mjs');
44
+ const locator = new RustBinaryLocator({
45
+ binaryName: 'mock-rust-binary',
46
+ binaryPath: mockBinaryPath,
47
+ searchSystemPath: false,
48
+ });
49
+
50
+ const first = await locator.findBinary();
51
+ expect(first).toEqual(mockBinaryPath);
52
+
53
+ locator.clearCache();
54
+ // After clearing, next call should re-search and still find it
55
+ const second = await locator.findBinary();
56
+ expect(second).toEqual(mockBinaryPath);
57
+ });
58
+
59
+ tap.test('should fall back to env var when binaryPath not set', async () => {
60
+ const mockBinaryPath = path.join(testDir, 'helpers/mock-rust-binary.mjs');
61
+ const envVar = 'TEST_SMARTRUST_BINARY_' + Date.now();
62
+ process.env[envVar] = mockBinaryPath;
63
+
64
+ const locator = new RustBinaryLocator({
65
+ binaryName: 'mock-rust-binary',
66
+ envVarName: envVar,
67
+ searchSystemPath: false,
68
+ });
69
+
70
+ const result = await locator.findBinary();
71
+ expect(result).toEqual(mockBinaryPath);
72
+
73
+ delete process.env[envVar];
74
+ });
75
+
76
+ tap.test('should find binary in local paths', async () => {
77
+ const mockBinaryPath = path.join(testDir, 'helpers/mock-rust-binary.mjs');
78
+ const locator = new RustBinaryLocator({
79
+ binaryName: 'mock-rust-binary',
80
+ localPaths: ['/nonexistent/path/binary', mockBinaryPath],
81
+ searchSystemPath: false,
82
+ });
83
+
84
+ const result = await locator.findBinary();
85
+ expect(result).toEqual(mockBinaryPath);
86
+ });
87
+
88
+ tap.test('should find node in system PATH', async () => {
89
+ const locator = new RustBinaryLocator({
90
+ binaryName: 'node',
91
+ searchSystemPath: true,
92
+ });
93
+
94
+ const result = await locator.findBinary();
95
+ expect(result).not.toBeNull();
96
+ });
97
+
98
+ export default tap.start();