@push.rocks/smartrust 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.linescanner.d.ts +13 -1
- package/dist_ts/classes.linescanner.js +57 -24
- package/dist_ts/classes.rustbinarylocator.d.ts +14 -3
- package/dist_ts/classes.rustbinarylocator.js +56 -16
- package/dist_ts/classes.rustbinarylocatorerror.d.ts +15 -3
- package/dist_ts/classes.rustbinarylocatorerror.js +9 -4
- package/dist_ts/classes.rustbridge.d.ts +2 -1
- package/dist_ts/classes.rustbridge.js +3 -2
- package/dist_ts/classes.sockettransport.js +1 -1
- package/dist_ts/classes.stdiotransport.js +1 -1
- package/dist_ts/index.d.ts +1 -1
- package/dist_ts/index.js +1 -1
- package/dist_ts/paths.js +1 -1
- package/dist_ts/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +3 -2
- package/package.json +8 -6
- package/readme.md +22 -4
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.linescanner.ts +62 -25
- package/ts/classes.rustbinarylocator.ts +61 -15
- package/ts/classes.rustbinarylocatorerror.ts +19 -2
- package/ts/classes.rustbridge.ts +2 -1
- package/ts/index.ts +1 -1
- package/ts/plugins.ts +2 -1
|
@@ -4,9 +4,16 @@ import type { IRustBridgeLogger } from './interfaces/index.js';
|
|
|
4
4
|
* Buffer-based newline scanner for streaming binary data.
|
|
5
5
|
* Accumulates chunks and emits complete lines via callback.
|
|
6
6
|
* Used by both StdioTransport and SocketTransport.
|
|
7
|
+
*
|
|
8
|
+
* Each byte is scanned once and copied at most once into its line, so a line that arrives in many
|
|
9
|
+
* chunks costs time linear in its length.
|
|
7
10
|
*/
|
|
8
11
|
export class LineScanner {
|
|
9
|
-
|
|
12
|
+
/** Chunks of the line being received; none of them contains a newline. */
|
|
13
|
+
private pending: Buffer[] = [];
|
|
14
|
+
private pendingLength = 0;
|
|
15
|
+
/** Bytes after a line whose callback threw; they may contain newlines and are scanned first. */
|
|
16
|
+
private unscanned: Buffer = Buffer.alloc(0);
|
|
10
17
|
private maxPayloadSize: number;
|
|
11
18
|
private logger: IRustBridgeLogger;
|
|
12
19
|
|
|
@@ -19,44 +26,74 @@ export class LineScanner {
|
|
|
19
26
|
* Feed a chunk of data. Calls onLine for each complete newline-terminated line found.
|
|
20
27
|
*/
|
|
21
28
|
public push(chunk: Buffer, onLine: (line: string) => void): void {
|
|
22
|
-
this.
|
|
29
|
+
const input = this.unscanned.length > 0 ? Buffer.concat([this.unscanned, chunk]) : chunk;
|
|
30
|
+
this.unscanned = Buffer.alloc(0);
|
|
31
|
+
let start = 0;
|
|
23
32
|
|
|
24
33
|
try {
|
|
25
34
|
let newlineIndex: number;
|
|
26
|
-
while ((newlineIndex =
|
|
27
|
-
const lineBuffer = this.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (lineBuffer.length > this.maxPayloadSize) {
|
|
31
|
-
this.logger.log('error', `Inbound message exceeds maxPayloadSize (${lineBuffer.length} bytes), dropping`);
|
|
35
|
+
while ((newlineIndex = input.indexOf(0x0A, start)) !== -1) {
|
|
36
|
+
const lineBuffer = this.takeLine(input.subarray(start, newlineIndex));
|
|
37
|
+
start = newlineIndex + 1;
|
|
38
|
+
if (!lineBuffer) {
|
|
32
39
|
continue;
|
|
33
40
|
}
|
|
34
|
-
|
|
35
41
|
const line = lineBuffer.toString('utf8').trim();
|
|
36
42
|
if (line) {
|
|
37
43
|
onLine(line);
|
|
38
44
|
}
|
|
39
45
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
} finally {
|
|
47
|
-
// A subarray retains its entire backing allocation, even when empty.
|
|
48
|
-
// Compact once per chunk, including when a callback throws, without
|
|
49
|
-
// repeatedly copying the unread suffix for each parsed line.
|
|
50
|
-
if (this.buffer.length === 0) {
|
|
51
|
-
this.buffer = Buffer.alloc(0);
|
|
52
|
-
} else if (this.buffer.buffer.byteLength > Math.max(Buffer.poolSize, this.buffer.length * 2)) {
|
|
53
|
-
this.buffer = Buffer.from(this.buffer);
|
|
54
|
-
}
|
|
46
|
+
this.appendPartial(input.subarray(start));
|
|
47
|
+
} catch (error) {
|
|
48
|
+
// Keep what follows the failed line for the next push, without retaining what was consumed.
|
|
49
|
+
this.unscanned = LineScanner.compact(input.subarray(start));
|
|
50
|
+
throw error;
|
|
55
51
|
}
|
|
56
52
|
}
|
|
57
53
|
|
|
58
54
|
/** Reset the internal buffer. */
|
|
59
55
|
public clear(): void {
|
|
60
|
-
this.
|
|
56
|
+
this.pending = [];
|
|
57
|
+
this.pendingLength = 0;
|
|
58
|
+
this.unscanned = Buffer.alloc(0);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Joins the pending chunks with the line's last piece; null when the line is too large. */
|
|
62
|
+
private takeLine(tail: Buffer): Buffer | null {
|
|
63
|
+
const length = this.pendingLength + tail.length;
|
|
64
|
+
const pending = this.pending;
|
|
65
|
+
this.pending = [];
|
|
66
|
+
this.pendingLength = 0;
|
|
67
|
+
if (length > this.maxPayloadSize) {
|
|
68
|
+
this.logger.log('error', `Inbound message exceeds maxPayloadSize (${length} bytes), dropping`);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return pending.length === 0 ? tail : Buffer.concat([...pending, tail], length);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private appendPartial(partial: Buffer): void {
|
|
75
|
+
if (partial.length === 0) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// Prevent OOM if sender never sends newline
|
|
79
|
+
if (this.pendingLength + partial.length > this.maxPayloadSize) {
|
|
80
|
+
this.logger.log(
|
|
81
|
+
'error',
|
|
82
|
+
`Buffer exceeded maxPayloadSize (${this.pendingLength + partial.length} bytes) without newline, clearing`,
|
|
83
|
+
);
|
|
84
|
+
this.pending = [];
|
|
85
|
+
this.pendingLength = 0;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
this.pending.push(LineScanner.compact(partial));
|
|
89
|
+
this.pendingLength += partial.length;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** A subarray retains its entire backing allocation; copy a small piece of a large one. */
|
|
93
|
+
private static compact(piece: Buffer): Buffer {
|
|
94
|
+
if (piece.length === 0) {
|
|
95
|
+
return Buffer.alloc(0);
|
|
96
|
+
}
|
|
97
|
+
return piece.buffer.byteLength > Math.max(Buffer.poolSize, piece.length * 2) ? Buffer.from(piece) : piece;
|
|
61
98
|
}
|
|
62
99
|
}
|
|
@@ -10,9 +10,11 @@ const defaultLogger: IRustBridgeLogger = {
|
|
|
10
10
|
* Locates a Rust binary using a priority-ordered search strategy:
|
|
11
11
|
* 1. Explicit binaryPath override
|
|
12
12
|
* 2. Environment variable
|
|
13
|
-
* 3.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* 3. Inside a Node.js single executable application: the sidecar embedded under `binaryName`,
|
|
14
|
+
* and nothing else
|
|
15
|
+
* 4. Platform-specific npm package
|
|
16
|
+
* 5. Local development build paths
|
|
17
|
+
* 6. System PATH
|
|
16
18
|
*/
|
|
17
19
|
export class RustBinaryLocator {
|
|
18
20
|
private options: IBinaryLocatorOptions;
|
|
@@ -37,11 +39,18 @@ export class RustBinaryLocator {
|
|
|
37
39
|
* Find the binary path.
|
|
38
40
|
* Returns null if discovery finds no binary. An unusable explicit path
|
|
39
41
|
* rejects with RustBinaryLocatorError without trying discovery or chmod.
|
|
42
|
+
* Inside a single executable application, a sidecar it cannot provide rejects
|
|
43
|
+
* with RustBinaryLocatorError (`ERR_RUST_BINARY_SEA_SIDECAR_UNAVAILABLE`).
|
|
40
44
|
*/
|
|
41
45
|
public async findBinary(): Promise<string | null> {
|
|
42
46
|
if (this.options.binaryPath !== undefined) {
|
|
43
47
|
return this.validateExplicitPath();
|
|
44
48
|
}
|
|
49
|
+
// Inside a SEA every lookup resolves again: smartsea refreshes the extracted
|
|
50
|
+
// generation's lease and re-extracts a pruned tree, which a cached path would skip.
|
|
51
|
+
if (plugins.smartsea.isSea()) {
|
|
52
|
+
return this.findSeaBinary();
|
|
53
|
+
}
|
|
45
54
|
if (this.cachedPath !== null) {
|
|
46
55
|
if (await this.isExecutable(this.cachedPath)) return this.cachedPath;
|
|
47
56
|
this.cachedPath = null;
|
|
@@ -62,18 +71,12 @@ export class RustBinaryLocator {
|
|
|
62
71
|
const { binaryName } = this.options;
|
|
63
72
|
|
|
64
73
|
// 2. Environment variable override
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (await this.isExecutable(envPath)) {
|
|
69
|
-
this.logger.log('info', `Binary found via ${this.options.envVarName}: ${envPath}`);
|
|
70
|
-
return envPath;
|
|
71
|
-
}
|
|
72
|
-
this.logger.log('warn', `${this.options.envVarName} set but not executable: ${envPath}`);
|
|
73
|
-
}
|
|
74
|
+
const envPath = await this.findEnvOverride();
|
|
75
|
+
if (envPath) {
|
|
76
|
+
return envPath;
|
|
74
77
|
}
|
|
75
78
|
|
|
76
|
-
//
|
|
79
|
+
// 4. Platform-specific npm package
|
|
77
80
|
if (this.options.platformPackagePrefix) {
|
|
78
81
|
const platformBinary = await this.findPlatformPackageBinary();
|
|
79
82
|
if (platformBinary) {
|
|
@@ -82,7 +85,7 @@ export class RustBinaryLocator {
|
|
|
82
85
|
}
|
|
83
86
|
}
|
|
84
87
|
|
|
85
|
-
//
|
|
88
|
+
// 5. Local development build paths
|
|
86
89
|
const localPaths = this.options.localPaths || [
|
|
87
90
|
plugins.path.resolve(process.cwd(), `rust/target/release/${binaryName}`),
|
|
88
91
|
plugins.path.resolve(process.cwd(), `rust/target/debug/${binaryName}`),
|
|
@@ -101,7 +104,7 @@ export class RustBinaryLocator {
|
|
|
101
104
|
}
|
|
102
105
|
}
|
|
103
106
|
|
|
104
|
-
//
|
|
107
|
+
// 6. System PATH
|
|
105
108
|
if (this.options.searchSystemPath !== false) {
|
|
106
109
|
const systemPath = await this.findInPath(binaryName);
|
|
107
110
|
if (systemPath) {
|
|
@@ -114,6 +117,49 @@ export class RustBinaryLocator {
|
|
|
114
117
|
return null;
|
|
115
118
|
}
|
|
116
119
|
|
|
120
|
+
private async findEnvOverride(): Promise<string | null> {
|
|
121
|
+
if (!this.options.envVarName) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const envPath = process.env[this.options.envVarName];
|
|
125
|
+
if (!envPath) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
if (await this.isExecutable(envPath)) {
|
|
129
|
+
this.logger.log('info', `Binary found via ${this.options.envVarName}: ${envPath}`);
|
|
130
|
+
return envPath;
|
|
131
|
+
}
|
|
132
|
+
this.logger.log('warn', `${this.options.envVarName} set but not executable: ${envPath}`);
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Inside a single executable application: the environment override, else the sidecar the
|
|
138
|
+
* executable embeds under `binaryName`. Packages, local paths and PATH belong to the machine,
|
|
139
|
+
* not to the application, and are never searched.
|
|
140
|
+
*/
|
|
141
|
+
private async findSeaBinary(): Promise<string> {
|
|
142
|
+
const envPath = await this.findEnvOverride();
|
|
143
|
+
if (envPath) {
|
|
144
|
+
return envPath;
|
|
145
|
+
}
|
|
146
|
+
const { binaryName } = this.options;
|
|
147
|
+
let sidecarPath: string;
|
|
148
|
+
try {
|
|
149
|
+
sidecarPath = await plugins.smartsea.getSidecarPath(binaryName);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
152
|
+
throw new RustBinaryLocatorError(
|
|
153
|
+
`This single executable application cannot provide its embedded sidecar '${binaryName}': ${reason}`,
|
|
154
|
+
undefined,
|
|
155
|
+
error,
|
|
156
|
+
'ERR_RUST_BINARY_SEA_SIDECAR_UNAVAILABLE',
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
this.logger.log('info', `Binary found as embedded sidecar: ${sidecarPath}`);
|
|
160
|
+
return sidecarPath;
|
|
161
|
+
}
|
|
162
|
+
|
|
117
163
|
private async findPlatformPackageBinary(): Promise<string | null> {
|
|
118
164
|
const { binaryName, platformPackagePrefix } = this.options;
|
|
119
165
|
const platform = process.platform;
|
|
@@ -1,13 +1,30 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Why a binary lookup failed:
|
|
3
|
+
* - `ERR_RUST_BINARY_EXPLICIT_PATH_INVALID`: the caller's explicit `binaryPath` is unusable.
|
|
4
|
+
* - `ERR_RUST_BINARY_SEA_SIDECAR_UNAVAILABLE`: inside a Node.js single executable application,
|
|
5
|
+
* the sidecar embedded under the binary's name could not be provided (not embedded, no usable
|
|
6
|
+
* cache directory, extraction failed, …); `cause` carries the `SmartSeaError`.
|
|
7
|
+
*/
|
|
8
|
+
export type TRustBinaryLocatorErrorCode =
|
|
9
|
+
| 'ERR_RUST_BINARY_EXPLICIT_PATH_INVALID'
|
|
10
|
+
| 'ERR_RUST_BINARY_SEA_SIDECAR_UNAVAILABLE';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A binary lookup that must not continue with other candidates: an unusable explicit
|
|
14
|
+
* `binaryPath`, or an embedded sidecar a single executable application cannot provide.
|
|
15
|
+
* `RustBridge.spawn()` rejects with it instead of resolving `false`.
|
|
16
|
+
*/
|
|
2
17
|
export class RustBinaryLocatorError extends Error {
|
|
3
|
-
public readonly code
|
|
18
|
+
public readonly code: TRustBinaryLocatorErrorCode;
|
|
4
19
|
|
|
5
20
|
constructor(
|
|
6
21
|
message: string,
|
|
7
22
|
public readonly binaryPath: string | undefined,
|
|
8
23
|
cause?: unknown,
|
|
24
|
+
code: TRustBinaryLocatorErrorCode = 'ERR_RUST_BINARY_EXPLICIT_PATH_INVALID',
|
|
9
25
|
) {
|
|
10
26
|
super(message, cause === undefined ? undefined : { cause });
|
|
11
27
|
this.name = 'RustBinaryLocatorError';
|
|
28
|
+
this.code = code;
|
|
12
29
|
}
|
|
13
30
|
}
|
package/ts/classes.rustbridge.ts
CHANGED
|
@@ -155,7 +155,8 @@ export class RustBridge<TCommands extends TCommandMap = TCommandMap> extends plu
|
|
|
155
155
|
/**
|
|
156
156
|
* Spawn the Rust binary and wait for it to signal readiness.
|
|
157
157
|
* Returns true if the binary was found and spawned successfully.
|
|
158
|
-
* Rejects with RustBinaryLocatorError for an invalid explicit binaryPath
|
|
158
|
+
* Rejects with RustBinaryLocatorError for an invalid explicit binaryPath and,
|
|
159
|
+
* inside a single executable application, for an embedded sidecar it cannot provide.
|
|
159
160
|
*/
|
|
160
161
|
public spawn(): Promise<boolean> {
|
|
161
162
|
return this.activate(async (generationArg) => {
|
package/ts/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ export {
|
|
|
7
7
|
type TRustBridgeRequestErrorCode,
|
|
8
8
|
} from './classes.rustbridgerequesterror.js';
|
|
9
9
|
export { RustBinaryLocator } from './classes.rustbinarylocator.js';
|
|
10
|
-
export { RustBinaryLocatorError } from './classes.rustbinarylocatorerror.js';
|
|
10
|
+
export { RustBinaryLocatorError, type TRustBinaryLocatorErrorCode } from './classes.rustbinarylocatorerror.js';
|
|
11
11
|
export { StreamingResponse } from './classes.streamingresponse.js';
|
|
12
12
|
export { StdioTransport } from './classes.stdiotransport.js';
|
|
13
13
|
export { SocketTransport } from './classes.sockettransport.js';
|
package/ts/plugins.ts
CHANGED