nacos-sdk-rust-binding-node 0.5.3 → 0.7.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/AGENTS.md ADDED
@@ -0,0 +1,148 @@
1
+ # AGENTS.md
2
+
3
+ This file provides guidance to AI coding agents when working with code in this repository.
4
+
5
+ ## Project Overview
6
+
7
+ This is a Node.js native addon providing bindings to the Nacos service discovery and configuration management system. It uses [napi-rs](https://napi.rs/) to bridge Rust (`nacos-sdk-rust`) with Node.js/TypeScript, offering better performance than the official JavaScript SDK which lacks gRPC support.
8
+
9
+ ## Architecture
10
+
11
+ ### Hybrid Rust/Node.js Structure
12
+
13
+ - **Rust Layer** (`src/*.rs`): Core Nacos client implementation using `nacos-sdk-rust`
14
+ - `lib.rs`: Entry point, `ClientOptions` struct
15
+ - `config.rs`: Configuration management (`NacosConfigClient`)
16
+ - `naming.rs`: Service discovery (`NacosNamingClient`)
17
+ - `plugin.rs`: Config filter plugin interface for encryption/decryption
18
+
19
+ - **TypeScript Definitions** (`index.d.ts`): Auto-generated from Rust code via `napi-rs` during build
20
+
21
+ ### Key Data Flows
22
+
23
+ 1. **Config Client**: `getConfig()` → Rust SDK → Nacos Server → async response
24
+ 2. **Naming Client**: `registerInstance()` → Rust SDK → Nacos Server → async response
25
+ 3. **Subscriptions**: Rust SDK maintains long-lived gRPC connections; pushes updates via `ThreadsafeFunction` callbacks to JS
26
+
27
+ ### Type Conventions
28
+
29
+ - Rust `Option<T>` maps to TypeScript `T | undefined | null`
30
+ - Async Rust methods return JavaScript Promises
31
+ - Callbacks use `ThreadsafeFunction` for thread-safe JS invocation from Rust
32
+ - Nacos errors are converted to JS `Error` with reason string
33
+
34
+ ## Build System
35
+
36
+ This project uses:
37
+ - **Cargo**: Rust dependency management and compilation
38
+ - **napi-rs**: Native Node.js addon framework
39
+ - **Yarn 3.4.1**: Package manager (specified in `packageManager` field)
40
+
41
+ ### Common Commands
42
+
43
+ ```bash
44
+ # Install dependencies
45
+ yarn install
46
+
47
+ # Build debug native addon (faster, for development)
48
+ yarn build:debug
49
+
50
+ # Build release native addon (optimized)
51
+ yarn build
52
+
53
+ # Run tests (requires compiled .node file)
54
+ yarn test
55
+
56
+ # Collect build artifacts for distribution
57
+ yarn artifacts
58
+
59
+ # Create universal macOS binary (combines x64 + arm64)
60
+ yarn universal
61
+ ```
62
+
63
+ ### Cross-Compilation Targets
64
+
65
+ The CI builds for multiple platforms (see `.github/workflows/CI.yml`):
66
+ - macOS: `x86_64-apple-darwin`, `aarch64-apple-darwin`, `universal-apple-darwin`
67
+ - Linux: `x86_64-unknown-linux-gnu/musl`, `aarch64-unknown-linux-gnu/musl`, `armv7-unknown-linux-gnueabihf`
68
+ - Windows: `x86_64-pc-windows-msvc`, `i686-pc-windows-msvc`, `aarch64-pc-windows-msvc`
69
+ - Android: `aarch64-linux-android`, `armv7-linux-androideabi`
70
+
71
+ Target-specific builds: `yarn build --target <target-triple>`
72
+
73
+ ### Build Outputs
74
+
75
+ - Compiled native addon: `nacos-sdk-rust-binding-node.<platform>.node`
76
+ - TypeScript definitions: `index.d.ts` (auto-generated)
77
+
78
+ ## Testing
79
+
80
+ Uses [AVA](https://github.com/avajs/ava) test framework with 3-minute timeout:
81
+
82
+ ```bash
83
+ # Run all tests
84
+ yarn test
85
+
86
+ # Run a specific test file
87
+ yarn test __test__/index.spec.mjs
88
+ ```
89
+
90
+ Note: Tests require the native addon to be built first (`index.node` must exist).
91
+
92
+ ## Environment Variables
93
+
94
+ - `NACOS_CLIENT_LOGGER_LEVEL`: Log level (default: `INFO`, logs to `$HOME/logs/nacos/`)
95
+ - `NACOS_CLIENT_COMMON_THREAD_CORES`: Client thread pool size (default: `1`)
96
+ - `NACOS_CLIENT_NAMING_PUSH_EMPTY_PROTECTION`: Protect against empty service list pushes (default: `true`)
97
+
98
+ See [nacos-sdk-rust docs](https://github.com/nacos-group/nacos-sdk-rust) for more environment variables.
99
+
100
+ ## API Usage Patterns
101
+
102
+ ### Config Client
103
+
104
+ ```javascript
105
+ const { NacosConfigClient } = require('nacos-sdk-rust-binding-node');
106
+
107
+ const client = new NacosConfigClient({
108
+ serverAddr: '127.0.0.1:8848',
109
+ namespace: 'my-namespace',
110
+ // Optional auth:
111
+ username: 'user',
112
+ password: 'pass'
113
+ });
114
+
115
+ const content = await client.getConfig('dataId', 'group');
116
+ await client.addListener('dataId', 'group', (err, config) => {
117
+ // Handle push updates
118
+ });
119
+ ```
120
+
121
+ ### Naming Client
122
+
123
+ ```javascript
124
+ const { NacosNamingClient } = require('nacos-sdk-rust-binding-node');
125
+
126
+ const client = new NacosNamingClient({
127
+ serverAddr: '127.0.0.1:8848',
128
+ namespace: 'my-namespace'
129
+ });
130
+
131
+ await client.registerInstance('service', 'group', {
132
+ ip: '192.168.1.1',
133
+ port: 8080
134
+ });
135
+ ```
136
+
137
+ ## Important Implementation Notes
138
+
139
+ - **removeListener/unSubscribe**: These are NOOPs - the underlying Rust SDK doesn't implement removal, but this is typically not problematic as listeners are long-lived
140
+ - **Clients are stateful**: Each client maintains persistent gRPC connections; create once and reuse for the application lifecycle
141
+ - **Auth methods**: Supports HTTP token auth (username/password) or Alibaba Cloud RAM (access_key/access_secret)
142
+ - **Build dependency**: require `nacos-sdk-rust` and `napi`
143
+
144
+ ## Troubleshooting
145
+
146
+ - If build fails, ensure Rust toolchain is up to date: `rustup update stable && cargo update`
147
+ - For cross-compilation issues, refer to the Docker images and setup in CI workflow
148
+ - Native addon loading errors usually indicate platform/architecture mismatch between build target and runtime
package/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "nacos-sdk-rust-binding-node"
3
- version = "0.5.3"
3
+ version = "0.7.0"
4
4
  edition = "2024"
5
5
  license = "Apache-2.0"
6
6
  publish = false
@@ -19,10 +19,11 @@ crate-type = ["cdylib"]
19
19
  napi = { version = "2", default-features = false, features = ["napi4", "async"] }
20
20
  napi-derive = "2"
21
21
 
22
- #nacos-sdk = { version = "0.5.3", features = ["default", "auth-by-aliyun", "tracing-log"] }
23
- nacos-sdk = { git = "https://github.com/nacos-group/nacos-sdk-rust.git", branch = "main", features = ["default", "auth-by-aliyun", "tracing-log"] }
22
+ nacos-sdk = { version = "0.7.0", features = ["default", "auth-by-aliyun", "tracing-log"] }
23
+ #nacos-sdk = { git = "https://github.com/nacos-group/nacos-sdk-rust.git", branch = "main", features = ["default", "auth-by-aliyun", "tracing-log"] }
24
24
 
25
25
  async-trait = "0.1"
26
+ tokio = { version = "1", features = ["rt"] }
26
27
 
27
28
  [build-dependencies]
28
29
  napi-build = "2"
package/README.md CHANGED
@@ -13,7 +13,7 @@ npm 包 -> https://www.npmjs.com/package/nacos-sdk-rust-binding-node
13
13
 
14
14
  环境变量 `NACOS_CLIENT_COMMON_THREAD_CORES=4` 可设置客户端核心线程数,默认 1
15
15
 
16
- 环境变量 `ENV_NACOS_CLIENT_NAMING_PUSH_EMPTY_PROTECTION=false` 可关闭 Naming 防推空保护,默认 true
16
+ 环境变量 `NACOS_CLIENT_NAMING_PUSH_EMPTY_PROTECTION=false` 可关闭 Naming 防推空保护,默认 true
17
17
 
18
18
  更多环境变量请看 `nacos-sdk-rust` 的[文档说明](https://github.com/nacos-group/nacos-sdk-rust)
19
19
 
package/example/config.js CHANGED
@@ -11,17 +11,29 @@ const nacos_config_client = new NacosConfigClient({
11
11
  appName: "binding-node-example-app"
12
12
  });
13
13
 
14
- try {
15
- // If it fails, pay attention to err
16
- nacos_config_client.getConfig('todo-dataid', 'LOVE').then(data => {
14
+ // getConfig - config may not exist, handle error gracefully
15
+ nacos_config_client.getConfig('todo-dataid', 'LOVE')
16
+ .then(data => {
17
17
  console.log('getConfig => ' + data);
18
+ })
19
+ .catch(err => {
20
+ console.log('getConfig error: ' + err.message);
18
21
  });
19
22
 
20
- nacos_config_client.getConfigResp('todo-dataid', 'LOVE').then(data => {
23
+ // getConfigResp - config may not exist, handle error gracefully
24
+ nacos_config_client.getConfigResp('todo-dataid', 'LOVE')
25
+ .then(data => {
21
26
  console.log('getConfigResp => ' + JSON.stringify(data));
27
+ })
28
+ .catch(err => {
29
+ console.log('getConfigResp error: ' + err.message);
22
30
  });
23
- } catch(e) {
24
- console.log(e);
25
- }
26
31
 
27
- nacos_config_client.addListener('todo-dataid', 'LOVE', (err, config_resp) => { console.log(config_resp) });
32
+ // addListener - listener will be called when config is created/changed
33
+ nacos_config_client.addListener('todo-dataid', 'LOVE', (err, config_resp) => {
34
+ if (err) {
35
+ console.log('addListener error: ' + err.message);
36
+ } else {
37
+ console.log('config changed => ' + JSON.stringify(config_resp));
38
+ }
39
+ });
@@ -27,17 +27,29 @@ const nacos_config_client = new NacosConfigClient(
27
27
  }
28
28
  );
29
29
 
30
- try {
31
- // If it fails, pay attention to err
32
- nacos_config_client.getConfig('todo-dataid', 'LOVE').then(data => {
30
+ // getConfig - config may not exist, handle error gracefully
31
+ nacos_config_client.getConfig('todo-dataid', 'LOVE')
32
+ .then(data => {
33
33
  console.log('getConfig => ' + data);
34
+ })
35
+ .catch(err => {
36
+ console.log('getConfig error (config may not exist): ' + err.message);
34
37
  });
35
-
36
- nacos_config_client.getConfigResp('todo-dataid', 'LOVE').then((data) => {
38
+
39
+ // getConfigResp - config may not exist, handle error gracefully
40
+ nacos_config_client.getConfigResp('todo-dataid', 'LOVE')
41
+ .then(data => {
37
42
  console.log('getConfigResp => ' + JSON.stringify(data));
43
+ })
44
+ .catch(err => {
45
+ console.log('getConfigResp error (config may not exist): ' + err.message);
38
46
  });
39
- } catch(e) {
40
- console.log(e);
41
- }
42
47
 
43
- nacos_config_client.addListener('todo-dataid', 'LOVE', (err, config_resp) => { console.log(config_resp) });
48
+ // addListener - listener will be called when config is created/changed
49
+ nacos_config_client.addListener('todo-dataid', 'LOVE', (err, config_resp) => {
50
+ if (err) {
51
+ console.log('addListener error: ' + err.message);
52
+ } else {
53
+ console.log('config changed => ' + JSON.stringify(config_resp));
54
+ }
55
+ });
package/index.d.ts CHANGED
@@ -1,9 +1,99 @@
1
- /* auto-generated by NAPI-RS */
1
+ /* tslint:disable */
2
2
  /* eslint-disable */
3
+
4
+ /* auto-generated by NAPI-RS */
5
+
6
+ export declare function sum(a: number, b: number): number
7
+ export interface ClientOptions {
8
+ /** Server Addr, e.g. address:port[,address:port],...] */
9
+ serverAddr: string
10
+ /** Namespace/Tenant */
11
+ namespace: string
12
+ /** AppName */
13
+ appName?: string
14
+ /** Username for Auth, Login by Http with Token */
15
+ username?: string
16
+ /** Password for Auth, Login by Http with Token */
17
+ password?: string
18
+ /** Access_Key for Auth, Login by Aliyun Ram */
19
+ accessKey?: string
20
+ /** Access_Secret for Auth, Login by Aliyun Ram */
21
+ accessSecret?: string
22
+ /** Signature_Region_Id for Auth, Login by Aliyun Ram */
23
+ signatureRegionId?: string
24
+ /** naming push_empty_protection, default true */
25
+ namingPushEmptyProtection?: boolean
26
+ /** naming load_cache_at_start, default false */
27
+ namingLoadCacheAtStart?: boolean
28
+ /** config load_cache_at_start, default false */
29
+ configLoadCacheAtStart?: boolean
30
+ }
31
+ export interface NacosConfigResponse {
32
+ /** Namespace/Tenant */
33
+ namespace: string
34
+ /** DataId */
35
+ dataId: string
36
+ /** Group */
37
+ group: string
38
+ /** Content */
39
+ content: string
40
+ /** Content's Type; e.g. json,properties,xml,html,text,yaml */
41
+ contentType: string
42
+ /** Content's md5 */
43
+ md5: string
44
+ }
45
+ export interface NacosServiceInstance {
46
+ /** Instance Id */
47
+ instanceId?: string
48
+ /** Ip */
49
+ ip: string
50
+ /** Port */
51
+ port: number
52
+ /** Weight, default 1.0 */
53
+ weight?: number
54
+ /** Healthy or not, default true */
55
+ healthy?: boolean
56
+ /** Enabled ot not, default true */
57
+ enabled?: boolean
58
+ /** Ephemeral or not, default true */
59
+ ephemeral?: boolean
60
+ /** Cluster Name, default 'DEFAULT' */
61
+ clusterName?: string
62
+ /** Service Name */
63
+ serviceName?: string
64
+ /** Metadata, default '{}' */
65
+ metadata?: Record<string, string>
66
+ }
67
+ /** ConfigReq for [`ConfigFilter`] */
68
+ export interface NacosConfigReq {
69
+ /** DataId */
70
+ dataId: string
71
+ /** Group */
72
+ group: string
73
+ /** Namespace/Tenant */
74
+ namespace: string
75
+ /** Content */
76
+ content: string
77
+ /** Content's Encrypted Data Key. */
78
+ encryptedDataKey: string
79
+ }
80
+ /** ConfigResp for [`ConfigFilter`] */
81
+ export interface NacosConfigResp {
82
+ /** DataId */
83
+ dataId: string
84
+ /** Group */
85
+ group: string
86
+ /** Namespace/Tenant */
87
+ namespace: string
88
+ /** Content */
89
+ content: string
90
+ /** Content's Encrypted Data Key. */
91
+ encryptedDataKey: string
92
+ }
3
93
  /** Client api of Nacos Config. */
4
- export declare class NacosConfigClient {
94
+ export class NacosConfigClient {
5
95
  /** Build a Config Client. */
6
- constructor(clientOptions: ClientOptions, configFilter?: (((err: Error | null, arg0?: NacosConfigReq | undefined | null, arg1?: NacosConfigResp | undefined | null) => any)) | undefined | null)
96
+ constructor(clientOptions: ClientOptions, configFilter?: (err: Error | null, arg0?: NacosConfigReq | undefined | null, arg1?: NacosConfigResp | undefined | null) => any | undefined | null)
7
97
  /**
8
98
  * Get config's content.
9
99
  * If it fails, pay attention to err
@@ -28,17 +118,16 @@ export declare class NacosConfigClient {
28
118
  * Add NacosConfigChangeListener callback func, which listen the config change.
29
119
  * If it fails, pay attention to err
30
120
  */
31
- addListener(dataId: string, group: string, listener: ((err: Error | null, arg: NacosConfigResponse) => any)): Promise<void>
121
+ addListener(dataId: string, group: string, listener: (err: Error | null, arg: NacosConfigResponse) => any): Promise<void>
32
122
  /**
33
123
  * Remove NacosConfigChangeListener callback func, but noop....
34
124
  * The logic is not implemented internally, and only APIs are provided as compatibility.
35
125
  * Users maybe do not need it? Not removing the listener is not a big problem, Sorry!
36
126
  */
37
- removeListener(dataId: string, group: string, listener: ((err: Error | null, arg: NacosConfigResponse) => any)): Promise<void>
127
+ removeListener(dataId: string, group: string, listener: (err: Error | null, arg: NacosConfigResponse) => any): Promise<void>
38
128
  }
39
-
40
129
  /** Client api of Nacos Naming. */
41
- export declare class NacosNamingClient {
130
+ export class NacosNamingClient {
42
131
  /** Build a Naming Client. */
43
132
  constructor(clientOptions: ClientOptions)
44
133
  /**
@@ -75,102 +164,11 @@ export declare class NacosNamingClient {
75
164
  * Add NacosNamingEventListener callback func, which listen the instance change.
76
165
  * If it fails, pay attention to err
77
166
  */
78
- subscribe(serviceName: string, group: string, clusters: Array<string> | undefined | null, listener: ((err: Error | null, arg: Array<NacosServiceInstance>) => any)): Promise<void>
167
+ subscribe(serviceName: string, group: string, clusters: Array<string> | undefined | null, listener: (err: Error | null, arg: Array<NacosServiceInstance>) => any): Promise<void>
79
168
  /**
80
169
  * Remove NacosNamingEventListener callback func, but noop....
81
170
  * The logic is not implemented internally, and only APIs are provided as compatibility.
82
171
  * Users maybe do not need it? Not removing the subscription is not a big problem, Sorry!
83
172
  */
84
- unSubscribe(serviceName: string, group: string, clusters: Array<string> | undefined | null, listener: ((err: Error | null, arg: Array<NacosServiceInstance>) => any)): Promise<void>
173
+ unSubscribe(serviceName: string, group: string, clusters: Array<string> | undefined | null, listener: (err: Error | null, arg: Array<NacosServiceInstance>) => any): Promise<void>
85
174
  }
86
-
87
- export interface ClientOptions {
88
- /** Server Addr, e.g. address:port[,address:port],...] */
89
- serverAddr: string
90
- /** Namespace/Tenant */
91
- namespace: string
92
- /** AppName */
93
- appName?: string
94
- /** Username for Auth, Login by Http with Token */
95
- username?: string
96
- /** Password for Auth, Login by Http with Token */
97
- password?: string
98
- /** Access_Key for Auth, Login by Aliyun Ram */
99
- accessKey?: string
100
- /** Access_Secret for Auth, Login by Aliyun Ram */
101
- accessSecret?: string
102
- /** Signature_Region_Id for Auth, Login by Aliyun Ram */
103
- signatureRegionId?: string
104
- /** naming push_empty_protection, default true */
105
- namingPushEmptyProtection?: boolean
106
- /** naming load_cache_at_start, default false */
107
- namingLoadCacheAtStart?: boolean
108
- }
109
-
110
- /** ConfigReq for [`ConfigFilter`] */
111
- export interface NacosConfigReq {
112
- /** DataId */
113
- dataId: string
114
- /** Group */
115
- group: string
116
- /** Namespace/Tenant */
117
- namespace: string
118
- /** Content */
119
- content: string
120
- /** Content's Encrypted Data Key. */
121
- encryptedDataKey: string
122
- }
123
-
124
- /** ConfigResp for [`ConfigFilter`] */
125
- export interface NacosConfigResp {
126
- /** DataId */
127
- dataId: string
128
- /** Group */
129
- group: string
130
- /** Namespace/Tenant */
131
- namespace: string
132
- /** Content */
133
- content: string
134
- /** Content's Encrypted Data Key. */
135
- encryptedDataKey: string
136
- }
137
-
138
- export interface NacosConfigResponse {
139
- /** Namespace/Tenant */
140
- namespace: string
141
- /** DataId */
142
- dataId: string
143
- /** Group */
144
- group: string
145
- /** Content */
146
- content: string
147
- /** Content's Type; e.g. json,properties,xml,html,text,yaml */
148
- contentType: string
149
- /** Content's md5 */
150
- md5: string
151
- }
152
-
153
- export interface NacosServiceInstance {
154
- /** Instance Id */
155
- instanceId?: string
156
- /** Ip */
157
- ip: string
158
- /** Port */
159
- port: number
160
- /** Weight, default 1.0 */
161
- weight?: number
162
- /** Healthy or not, default true */
163
- healthy?: boolean
164
- /** Enabled ot not, default true */
165
- enabled?: boolean
166
- /** Ephemeral or not, default true */
167
- ephemeral?: boolean
168
- /** Cluster Name, default 'DEFAULT' */
169
- clusterName?: string
170
- /** Service Name */
171
- serviceName?: string
172
- /** Metadata, default '{}' */
173
- metadata?: Record<string, string>
174
- }
175
-
176
- export declare function sum(a: number, b: number): number