@push.rocks/smartrust 1.3.2 → 1.4.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/.smartconfig.json +42 -0
- package/dist_ts/00_commitinfo_data.js +3 -3
- package/dist_ts/classes.rustbridge.d.ts +5 -2
- package/dist_ts/classes.rustbridge.js +158 -40
- package/dist_ts/classes.rustbridgerequesterror.d.ts +23 -0
- package/dist_ts/classes.rustbridgerequesterror.js +20 -0
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +2 -1
- package/dist_ts/interfaces/config.d.ts +9 -0
- package/license +19 -0
- package/package.json +41 -12
- package/readme.md +58 -6
- package/ts/00_commitinfo_data.ts +2 -2
- package/ts/classes.rustbridge.ts +204 -44
- package/ts/classes.rustbridgerequesterror.ts +37 -0
- package/ts/index.ts +5 -0
- package/ts/interfaces/config.ts +10 -0
- package/.gitea/workflows/default_nottags.yaml +0 -66
- package/.gitea/workflows/default_tags.yaml +0 -124
- package/.vscode/launch.json +0 -11
- package/.vscode/settings.json +0 -26
- package/changelog.md +0 -84
- package/npmextra.json +0 -24
- package/test/helpers/mock-rust-binary.mjs +0 -104
- package/test/helpers/mock-socket-server.mjs +0 -124
- package/test/test.linescanner.node.ts +0 -89
- package/test/test.rustbinarylocator.node.ts +0 -98
- package/test/test.rustbridge.node.ts +0 -440
- package/test/test.sockettransport.node.ts +0 -312
- package/test/test.ts +0 -12
- package/tsconfig.json +0 -12
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
name: Default (tags)
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
tags:
|
|
6
|
-
- '*'
|
|
7
|
-
|
|
8
|
-
env:
|
|
9
|
-
IMAGE: code.foss.global/host.today/ht-docker-node:npmci
|
|
10
|
-
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@code.foss.global/${{gitea.repository}}.git
|
|
11
|
-
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
|
12
|
-
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
|
13
|
-
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
|
14
|
-
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
|
|
15
|
-
|
|
16
|
-
jobs:
|
|
17
|
-
security:
|
|
18
|
-
runs-on: ubuntu-latest
|
|
19
|
-
continue-on-error: true
|
|
20
|
-
container:
|
|
21
|
-
image: ${{ env.IMAGE }}
|
|
22
|
-
|
|
23
|
-
steps:
|
|
24
|
-
- uses: actions/checkout@v3
|
|
25
|
-
|
|
26
|
-
- name: Prepare
|
|
27
|
-
run: |
|
|
28
|
-
pnpm install -g pnpm
|
|
29
|
-
pnpm install -g @ship.zone/npmci
|
|
30
|
-
npmci npm prepare
|
|
31
|
-
|
|
32
|
-
- name: Audit production dependencies
|
|
33
|
-
run: |
|
|
34
|
-
npmci command npm config set registry https://registry.npmjs.org
|
|
35
|
-
npmci command pnpm audit --audit-level=high --prod
|
|
36
|
-
continue-on-error: true
|
|
37
|
-
|
|
38
|
-
- name: Audit development dependencies
|
|
39
|
-
run: |
|
|
40
|
-
npmci command npm config set registry https://registry.npmjs.org
|
|
41
|
-
npmci command pnpm audit --audit-level=high --dev
|
|
42
|
-
continue-on-error: true
|
|
43
|
-
|
|
44
|
-
test:
|
|
45
|
-
if: ${{ always() }}
|
|
46
|
-
needs: security
|
|
47
|
-
runs-on: ubuntu-latest
|
|
48
|
-
container:
|
|
49
|
-
image: ${{ env.IMAGE }}
|
|
50
|
-
|
|
51
|
-
steps:
|
|
52
|
-
- uses: actions/checkout@v3
|
|
53
|
-
|
|
54
|
-
- name: Prepare
|
|
55
|
-
run: |
|
|
56
|
-
pnpm install -g pnpm
|
|
57
|
-
pnpm install -g @ship.zone/npmci
|
|
58
|
-
npmci npm prepare
|
|
59
|
-
|
|
60
|
-
- name: Test stable
|
|
61
|
-
run: |
|
|
62
|
-
npmci node install stable
|
|
63
|
-
npmci npm install
|
|
64
|
-
npmci npm test
|
|
65
|
-
|
|
66
|
-
- name: Test build
|
|
67
|
-
run: |
|
|
68
|
-
npmci node install stable
|
|
69
|
-
npmci npm install
|
|
70
|
-
npmci npm build
|
|
71
|
-
|
|
72
|
-
release:
|
|
73
|
-
needs: test
|
|
74
|
-
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
|
75
|
-
runs-on: ubuntu-latest
|
|
76
|
-
container:
|
|
77
|
-
image: ${{ env.IMAGE }}
|
|
78
|
-
|
|
79
|
-
steps:
|
|
80
|
-
- uses: actions/checkout@v3
|
|
81
|
-
|
|
82
|
-
- name: Prepare
|
|
83
|
-
run: |
|
|
84
|
-
pnpm install -g pnpm
|
|
85
|
-
pnpm install -g @ship.zone/npmci
|
|
86
|
-
npmci npm prepare
|
|
87
|
-
|
|
88
|
-
- name: Release
|
|
89
|
-
run: |
|
|
90
|
-
npmci node install stable
|
|
91
|
-
npmci npm publish
|
|
92
|
-
|
|
93
|
-
metadata:
|
|
94
|
-
needs: test
|
|
95
|
-
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
|
96
|
-
runs-on: ubuntu-latest
|
|
97
|
-
container:
|
|
98
|
-
image: ${{ env.IMAGE }}
|
|
99
|
-
continue-on-error: true
|
|
100
|
-
|
|
101
|
-
steps:
|
|
102
|
-
- uses: actions/checkout@v3
|
|
103
|
-
|
|
104
|
-
- name: Prepare
|
|
105
|
-
run: |
|
|
106
|
-
pnpm install -g pnpm
|
|
107
|
-
pnpm install -g @ship.zone/npmci
|
|
108
|
-
npmci npm prepare
|
|
109
|
-
|
|
110
|
-
- name: Code quality
|
|
111
|
-
run: |
|
|
112
|
-
npmci command npm install -g typescript
|
|
113
|
-
npmci npm install
|
|
114
|
-
|
|
115
|
-
- name: Trigger
|
|
116
|
-
run: npmci trigger
|
|
117
|
-
|
|
118
|
-
- name: Build docs and upload artifacts
|
|
119
|
-
run: |
|
|
120
|
-
npmci node install stable
|
|
121
|
-
npmci npm install
|
|
122
|
-
pnpm install -g @git.zone/tsdoc
|
|
123
|
-
npmci command tsdoc
|
|
124
|
-
continue-on-error: true
|
package/.vscode/launch.json
DELETED
package/.vscode/settings.json
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"json.schemas": [
|
|
3
|
-
{
|
|
4
|
-
"fileMatch": ["/npmextra.json"],
|
|
5
|
-
"schema": {
|
|
6
|
-
"type": "object",
|
|
7
|
-
"properties": {
|
|
8
|
-
"npmci": {
|
|
9
|
-
"type": "object",
|
|
10
|
-
"description": "settings for npmci"
|
|
11
|
-
},
|
|
12
|
-
"gitzone": {
|
|
13
|
-
"type": "object",
|
|
14
|
-
"description": "settings for gitzone",
|
|
15
|
-
"properties": {
|
|
16
|
-
"projectType": {
|
|
17
|
-
"type": "string",
|
|
18
|
-
"enum": ["website", "element", "service", "npm", "wcc"]
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
]
|
|
26
|
-
}
|
package/changelog.md
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 2026-03-14 - 1.3.2 - fix(rustbinarylocator)
|
|
4
|
-
support resolving platform-suffixed local Rust binaries
|
|
5
|
-
|
|
6
|
-
- Checks local Rust build paths for binaries with platform suffixes such as _linux_amd64 in addition to unsuffixed names
|
|
7
|
-
- Adds platform and architecture suffix mapping for linux, darwin, windows, x64, and arm64
|
|
8
|
-
|
|
9
|
-
## 2026-02-26 - 1.3.1 - fix(readme)
|
|
10
|
-
document socket transport and clarify stdio/socket differences in README
|
|
11
|
-
|
|
12
|
-
- Add 'Two Transport Modes' section documenting stdio (spawn) and socket (connect) modes
|
|
13
|
-
- Add examples for connect(), socket usage, and auto-reconnect with exponential backoff
|
|
14
|
-
- Clarify protocol is transport-agnostic and update ready/stream/event descriptions
|
|
15
|
-
- Update event docs: mark stderr as stdio-only and add 'reconnected' event for socket transports
|
|
16
|
-
- Clarify kill() behavior for both stdio and socket transports
|
|
17
|
-
- Add API reference entries for SocketTransport, StdioTransport, ISocketConnectOptions, IRustTransport, and LineScanner
|
|
18
|
-
- Add platform notes, architecture diagram, and minimal Rust/socket usage guidance
|
|
19
|
-
|
|
20
|
-
## 2026-02-26 - 1.3.0 - feat(transport)
|
|
21
|
-
introduce transport abstraction and socket-mode support for RustBridge
|
|
22
|
-
|
|
23
|
-
- Add IRustTransport interface and two transport implementations: StdioTransport (spawns child process and uses stdin/stdout) and SocketTransport (connects to Unix socket / Windows named pipe).
|
|
24
|
-
- Refactor RustBridge to use a transport abstraction (connectWithTransport) and add connect(socketPath) to attach to an existing daemon via socket.
|
|
25
|
-
- Introduce LineScanner: a buffer-based newline scanner used by both transports to handle large/newline-delimited messages and avoid OOMs.
|
|
26
|
-
- Add socket connection options (autoReconnect, reconnectBaseDelayMs, reconnectMaxDelayMs, maxReconnectAttempts) and implement auto-reconnect/backoff behavior in SocketTransport.
|
|
27
|
-
- Implement backpressure-aware write semantics and proper disconnect/cleanup for both transports; RustBridge.kill() now disconnects the transport instead of directly managing processes.
|
|
28
|
-
- Add tests and tooling: socket transport tests, line scanner tests, and a mock-socket-server.mjs helper script for testing socket mode.
|
|
29
|
-
- Export new symbols (StdioTransport, SocketTransport, LineScanner) and update plugins to expose net; update interfaces to export transport types.
|
|
30
|
-
|
|
31
|
-
## 2026-02-12 - 1.2.1 - fix(rust-binary-locator)
|
|
32
|
-
auto-fix missing execute permission for located Rust binaries
|
|
33
|
-
|
|
34
|
-
- If a located binary exists but lacks the execute bit, attempt to chmod it to 0o755 and treat it as executable.
|
|
35
|
-
- Logs an info message when the auto-fix is applied: 'Auto-fixed missing execute permission on: <filePath>'.
|
|
36
|
-
- Addresses cases where npm/pnpm installs remove the execute permission from bundled binaries.
|
|
37
|
-
|
|
38
|
-
## 2026-02-11 - 1.2.0 - feat(rustbridge)
|
|
39
|
-
add streaming responses and robust large-payload/backpressure handling to RustBridge
|
|
40
|
-
|
|
41
|
-
- Introduce StreamingResponse type and export it (for-await-of iterator + .result promise)
|
|
42
|
-
- Add sendCommandStreaming API to send streaming commands and receive chunks + final result
|
|
43
|
-
- Implement buffer-based stdout newline scanner to handle large messages and avoid readline limits
|
|
44
|
-
- Add backpressure-aware writeToStdin to wait for drain when writing large outbound payloads
|
|
45
|
-
- Add maxPayloadSize option and enforce outbound/inbound size checks to prevent OOMs
|
|
46
|
-
- Add streamTimeoutMs (inactivity timeout) and reset timeout on each received chunk
|
|
47
|
-
- Improve stderr handling (cross-chunk buffering and trimmed emits)
|
|
48
|
-
- Update mock test binary and extensive tests for streaming, large payloads, concurrency, and error cases
|
|
49
|
-
- Add TypeScript types for streaming commands (TStreamingCommandKeys, TExtractChunk, IManagementStreamChunk)
|
|
50
|
-
|
|
51
|
-
## 2026-02-10 - 1.1.2 - fix(rust-binary-locator)
|
|
52
|
-
use import.meta.resolve and url.fileURLToPath to locate bundled Rust binary in ESM environments
|
|
53
|
-
|
|
54
|
-
- Replace require.resolve with import.meta.resolve to support ESM module resolution
|
|
55
|
-
- Convert resolved file URL to a filesystem path using url.fileURLToPath
|
|
56
|
-
- Export the url module from ts/plugins to provide fileURLToPath
|
|
57
|
-
|
|
58
|
-
## 2026-02-10 - 1.1.1 - fix(readme)
|
|
59
|
-
update README with comprehensive documentation, usage examples, API reference, installation instructions, and legal/company information
|
|
60
|
-
|
|
61
|
-
- Rewrote readme.md (≈ +298 −3 lines) to add detailed install, overview, IPC protocol, command definition examples, usage, API reference, issue reporting & security guidance, and legal/trademark/company information.
|
|
62
|
-
- Documentation-only change — no source code modified.
|
|
63
|
-
- Current package version is 1.1.0; recommend a patch release
|
|
64
|
-
|
|
65
|
-
## 2026-02-10 - 1.1.0 - feat(rustbridge)
|
|
66
|
-
add RustBridge and RustBinaryLocator with typed IPC interfaces, plugins, tests and mock runner; export from index; add npm registries
|
|
67
|
-
|
|
68
|
-
- Introduce RustBridge: spawn and manage a child binary, JSON-over-stdin/stdout request/response handling, events, timeouts, pending request tracking, kill/cleanup logic.
|
|
69
|
-
- Introduce RustBinaryLocator: multi-strategy binary discovery (explicit path, env var, platform-specific package, local build paths, system PATH) with caching and logger hooks.
|
|
70
|
-
- Add IPC and config TypeScript interfaces (IManagementRequest/Response/Event, ICommandDefinition, IBinaryLocatorOptions, IRustBridgeOptions) and re-export via interfaces/index.ts.
|
|
71
|
-
- Update ts/plugins.ts to export fs, child_process, readline and events for easier native integration.
|
|
72
|
-
- Add tests for RustBridge and RustBinaryLocator plus a test helper mock-rust-binary.mjs to simulate the IPC protocol and exercise commands, events, timeouts and locator behaviors.
|
|
73
|
-
- Update ts/index.ts to export RustBridge and RustBinaryLocator and export interfaces; update npmextra.json to include internal Verdaccio registry alongside npmjs.org.
|
|
74
|
-
|
|
75
|
-
## 2026-02-08 - 1.0.2 - fix()
|
|
76
|
-
no changes
|
|
77
|
-
|
|
78
|
-
- No changes detected in git diff; no release necessary.
|
|
79
|
-
|
|
80
|
-
## 2026-02-08 - 1.0.1 - initial release
|
|
81
|
-
Initial release of the project.
|
|
82
|
-
|
|
83
|
-
- Initial commit creating the project repository and baseline files.
|
|
84
|
-
- Tagged as version 1.0.1.
|
package/npmextra.json
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Mock "Rust binary" for testing the RustBridge IPC protocol.
|
|
5
|
-
* Reads JSON lines from stdin via Buffer-based scanner, writes JSON lines to stdout.
|
|
6
|
-
* Emits a ready event on startup.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
// Emit ready event
|
|
10
|
-
const readyEvent = JSON.stringify({ event: 'ready', data: { version: '1.0.0' } });
|
|
11
|
-
process.stdout.write(readyEvent + '\n');
|
|
12
|
-
|
|
13
|
-
// Buffer-based newline scanner for stdin (mirrors the RustBridge approach)
|
|
14
|
-
let stdinBuffer = Buffer.alloc(0);
|
|
15
|
-
|
|
16
|
-
process.stdin.on('data', (chunk) => {
|
|
17
|
-
stdinBuffer = Buffer.concat([stdinBuffer, chunk]);
|
|
18
|
-
|
|
19
|
-
let newlineIndex;
|
|
20
|
-
while ((newlineIndex = stdinBuffer.indexOf(0x0A)) !== -1) {
|
|
21
|
-
const lineBuffer = stdinBuffer.subarray(0, newlineIndex);
|
|
22
|
-
stdinBuffer = stdinBuffer.subarray(newlineIndex + 1);
|
|
23
|
-
const line = lineBuffer.toString('utf8').trim();
|
|
24
|
-
if (line) {
|
|
25
|
-
handleLine(line);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Backpressure-aware write to stdout.
|
|
32
|
-
*/
|
|
33
|
-
function writeResponse(data) {
|
|
34
|
-
const json = JSON.stringify(data) + '\n';
|
|
35
|
-
if (!process.stdout.write(json)) {
|
|
36
|
-
// Wait for drain before continuing
|
|
37
|
-
process.stdout.once('drain', () => {});
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function handleLine(line) {
|
|
42
|
-
let request;
|
|
43
|
-
try {
|
|
44
|
-
request = JSON.parse(line);
|
|
45
|
-
} catch {
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const { id, method, params } = request;
|
|
50
|
-
|
|
51
|
-
if (method === 'echo') {
|
|
52
|
-
// Echo back the params as result
|
|
53
|
-
writeResponse({ id, success: true, result: params });
|
|
54
|
-
} else if (method === 'largeEcho') {
|
|
55
|
-
// Echo back params (same as echo, named distinctly for large payload tests)
|
|
56
|
-
writeResponse({ id, success: true, result: params });
|
|
57
|
-
} else if (method === 'error') {
|
|
58
|
-
// Return an error
|
|
59
|
-
writeResponse({ id, success: false, error: 'Test error message' });
|
|
60
|
-
} else if (method === 'emitEvent') {
|
|
61
|
-
// Emit a custom event, then respond with success
|
|
62
|
-
writeResponse({ event: params.eventName, data: params.eventData });
|
|
63
|
-
writeResponse({ id, success: true, result: null });
|
|
64
|
-
} else if (method === 'slow') {
|
|
65
|
-
// Respond after a delay
|
|
66
|
-
setTimeout(() => {
|
|
67
|
-
writeResponse({ id, success: true, result: { delayed: true } });
|
|
68
|
-
}, 100);
|
|
69
|
-
} else if (method === 'streamEcho') {
|
|
70
|
-
// Send params.count stream chunks, then final response
|
|
71
|
-
const count = params.count || 0;
|
|
72
|
-
let sent = 0;
|
|
73
|
-
const interval = setInterval(() => {
|
|
74
|
-
if (sent < count) {
|
|
75
|
-
writeResponse({ id, stream: true, data: { index: sent, value: `chunk_${sent}` } });
|
|
76
|
-
sent++;
|
|
77
|
-
} else {
|
|
78
|
-
clearInterval(interval);
|
|
79
|
-
writeResponse({ id, success: true, result: { totalChunks: count } });
|
|
80
|
-
}
|
|
81
|
-
}, 10);
|
|
82
|
-
} else if (method === 'streamError') {
|
|
83
|
-
// Send 1 chunk, then error
|
|
84
|
-
writeResponse({ id, stream: true, data: { index: 0, value: 'before_error' } });
|
|
85
|
-
setTimeout(() => {
|
|
86
|
-
writeResponse({ id, success: false, error: 'Stream error after chunk' });
|
|
87
|
-
}, 20);
|
|
88
|
-
} else if (method === 'streamEmpty') {
|
|
89
|
-
// Zero chunks, immediate final response
|
|
90
|
-
writeResponse({ id, success: true, result: { totalChunks: 0 } });
|
|
91
|
-
} else if (method === 'exit') {
|
|
92
|
-
// Graceful exit
|
|
93
|
-
writeResponse({ id, success: true, result: null });
|
|
94
|
-
process.exit(0);
|
|
95
|
-
} else {
|
|
96
|
-
// Unknown command
|
|
97
|
-
writeResponse({ id, success: false, error: `Unknown method: ${method}` });
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Handle SIGTERM gracefully
|
|
102
|
-
process.on('SIGTERM', () => {
|
|
103
|
-
process.exit(0);
|
|
104
|
-
});
|
|
@@ -1,124 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,89 +0,0 @@
|
|
|
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();
|
|
@@ -1,98 +0,0 @@
|
|
|
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();
|