async-xenapi 1.0.0 → 1.0.3

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # async-xenapi (JavaScript / TypeScript)
2
2
 
3
- An async TypeScript library for [XenAPI](https://xapi-project.github.io/xen-api)
3
+ An async JavaScript/TypeScript library for [XenAPI](https://xapi-project.github.io/xen-api).
4
4
 
5
5
  ## Install
6
6
 
@@ -8,7 +8,7 @@ An async TypeScript library for [XenAPI](https://xapi-project.github.io/xen-api)
8
8
  npm install async-xenapi
9
9
  ```
10
10
 
11
- Requires **Node.js 18+** (uses the global `fetch` API).
11
+ Requires **Node.js 24+** (uses the global `fetch` API).
12
12
 
13
13
  ## Usage
14
14
 
@@ -22,27 +22,99 @@ async function main() {
22
22
  const hosts = await session.xenapi.host.get_all();
23
23
  console.log(hosts);
24
24
  } finally {
25
- await session.xenapi.session.logout();
25
+ await session.logout();
26
26
  }
27
27
  }
28
28
 
29
29
  main();
30
30
  ```
31
31
 
32
- The API mirrors the synchronous Python XenAPI SDK — any dotted method path under `session.xenapi` is translated directly to the corresponding JSON-RPC call. See the [XenAPI Reference](https://xapi-project.github.io/xen-api) for all available classes and fields.
32
+ The API mirrors the Python XenAPI SDK — any dotted method path under `session.xenapi` is translated directly to the corresponding JSON-RPC call. See the [XenAPI Reference](https://xapi-project.github.io/xen-api) for all available classes and fields.
33
33
 
34
- ## Run tests
34
+ ## Best Practices
35
+
36
+ ### 1. Use `get_all_records()` instead of N+1 queries
37
+
38
+ The single biggest performance win. Instead of fetching a list of refs then querying each one individually, fetch everything in one call:
39
+
40
+ ```typescript
41
+ // SLOW — N+1 round-trips (1 for get_all + N for each getter)
42
+ const vms = await session.xenapi.VM.get_all();
43
+ for (const vm of vms as string[]) {
44
+ const name = await session.xenapi.VM.get_name_label(vm);
45
+ console.log(name);
46
+ }
47
+
48
+ // FAST — 1 round-trip, returns { ref: { field: value, ... }, ... }
49
+ const records = await session.xenapi.VM.get_all_records() as Record<string, Record<string, unknown>>;
50
+ for (const rec of Object.values(records)) {
51
+ if (!rec.is_a_template && !rec.is_a_snapshot) {
52
+ console.log(`${rec.name_label} (${rec.power_state})`);
53
+ }
54
+ }
55
+ ```
56
+
57
+ This applies to every XenAPI class: `host`, `SR`, `network`, `VM`, `pool`, etc.
58
+
59
+ ### 2. Use `Promise.all()` for independent calls
60
+
61
+ When you need results from multiple independent API calls, run them concurrently:
62
+
63
+ ```typescript
64
+ // SLOW — sequential, each awaits the previous
65
+ const major = await session.xenapi.host.get_API_version_major(host);
66
+ const minor = await session.xenapi.host.get_API_version_minor(host);
67
+
68
+ // FAST — concurrent, both requests in flight at the same time
69
+ const [major, minor] = await Promise.all([
70
+ session.xenapi.host.get_API_version_major(host),
71
+ session.xenapi.host.get_API_version_minor(host),
72
+ ]);
73
+ ```
74
+
75
+ You can also gather across different classes:
76
+
77
+ ```typescript
78
+ const [hosts, vms, networks] = await Promise.all([
79
+ session.xenapi.host.get_all_records(),
80
+ session.xenapi.VM.get_all_records(),
81
+ session.xenapi.network.get_all_records(),
82
+ ]);
83
+ ```
84
+
85
+ ### 3. Always clean up the session
86
+
87
+ Use `try/finally` to ensure `logout()` is called, which closes the server-side session:
88
+
89
+ ```typescript
90
+ const session = new AsyncXenAPISession("https://xen-host");
91
+ await session.login_with_password("root", "password");
92
+ try {
93
+ // ... your code ...
94
+ } finally {
95
+ await session.logout();
96
+ }
97
+ ```
98
+
99
+ ### Key Takeaways
100
+
101
+ | Pattern | Calls | Approach |
102
+ |------------------------------|--------------|----------------------|
103
+ | List objects with fields | 1 | `get_all_records()` |
104
+ | Multiple independent values | N concurrent | `Promise.all()` |
105
+ | Lookup one field for one ref | 1 | `get_<field>(ref)` |
106
+
107
+ ## Run Tests
35
108
 
36
109
  ```
37
110
  git clone git@github.com:acefei/async-xenapi.git
38
- cd async-xenapi/javascript
111
+ cd async-xenapi
112
+ cp .env.example .env # then edit .env with your credentials
113
+ cd javascript
39
114
  npm install
40
- echo "HOST_URL=https://<xen-host>" >> .env
41
- echo "USERNAME=root" >> .env
42
- echo "PASSWORD=<password>" >> .env
43
- npm test tests/getXapiVersion.test.ts
115
+ npm test
44
116
  ```
45
117
 
46
118
  ## License
47
119
 
48
- GPL-2.0-only
120
+ LGPL-2.1-only
package/dist/XenAPI.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Async XenAPI session via JSON-RPC (built-in fetch + crypto only, Node 18+)
2
+ * Async XenAPI session via JSON-RPC
3
3
  *
4
- * Usage mirrors the Python AsyncXenAPISession:
4
+ * Usage mirrors the Python XenAPI:
5
5
  *
6
6
  * const session = new AsyncXenAPISession("https://host-ip");
7
7
  * await session.login_with_password("root", "password");
@@ -31,5 +31,3 @@ export declare class AsyncXenAPISession {
31
31
  logout(): Promise<void>;
32
32
  _call(method: string, params?: unknown[]): Promise<unknown>;
33
33
  }
34
- /** Convenience factory — equivalent to `new AsyncXenAPISession(url)` */
35
- export declare function xapi_client(url: string | undefined): AsyncXenAPISession;
package/dist/XenAPI.js CHANGED
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  /**
3
- * Async XenAPI session via JSON-RPC (built-in fetch + crypto only, Node 18+)
3
+ * Async XenAPI session via JSON-RPC
4
4
  *
5
- * Usage mirrors the Python AsyncXenAPISession:
5
+ * Usage mirrors the Python XenAPI:
6
6
  *
7
7
  * const session = new AsyncXenAPISession("https://host-ip");
8
8
  * await session.login_with_password("root", "password");
@@ -15,33 +15,27 @@
15
15
  *
16
16
  * await session.logout();
17
17
  */
18
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
19
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
20
- return new (P || (P = Promise))(function (resolve, reject) {
21
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
22
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
23
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
24
- step((generator = generator.apply(thisArg, _arguments || [])).next());
25
- });
26
- };
27
18
  Object.defineProperty(exports, "__esModule", { value: true });
28
19
  exports.AsyncXenAPISession = void 0;
29
- exports.xapi_client = xapi_client;
20
+ // ---------------------------------------------------------------------------
21
+ // JSON-RPC helpers
22
+ // ---------------------------------------------------------------------------
23
+ // XenServer typically uses self-signed certificates — skip TLS verification
24
+ // (mirrors Python's ssl.CERT_NONE behaviour).
25
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
30
26
  function _jsonrpcReq(method, params) {
31
27
  return { jsonrpc: "2.0", method, params, id: crypto.randomUUID() };
32
28
  }
33
- function _post(url, payload) {
34
- return __awaiter(this, void 0, void 0, function* () {
35
- const response = yield fetch(url, {
36
- method: "POST",
37
- headers: { "content-type": "application/json" },
38
- body: JSON.stringify(payload),
39
- });
40
- if (!response.ok) {
41
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
42
- }
43
- return response.json();
29
+ async function _post(url, payload) {
30
+ const response = await fetch(url, {
31
+ method: "POST",
32
+ headers: { "content-type": "application/json" },
33
+ body: JSON.stringify(payload),
44
34
  });
35
+ if (!response.ok) {
36
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
37
+ }
38
+ return response.json();
45
39
  }
46
40
  // Accumulates dotted property access (e.g. xenapi.VM.get_all) and dispatches
47
41
  // the final call as an authenticated JSON-RPC request.
@@ -56,42 +50,35 @@ function _xenApiNamespace(session, path = []) {
56
50
  });
57
51
  }
58
52
  class AsyncXenAPISession {
53
+ _url;
54
+ _sessionRef;
55
+ xenapi;
59
56
  constructor(url) {
60
57
  this._url = `${url}/jsonrpc`;
61
58
  this.xenapi = _xenApiNamespace(this);
62
59
  }
63
- login_with_password(user, password) {
64
- return __awaiter(this, void 0, void 0, function* () {
65
- const ret = yield _post(this._url, _jsonrpcReq("session.login_with_password", [user, password, "version", "originator"]));
66
- if (ret.error)
67
- throw new Error(`Login failed: ${JSON.stringify(ret.error)}`);
68
- this._sessionRef = ret.result;
69
- return this._sessionRef;
70
- });
71
- }
72
- logout() {
73
- return __awaiter(this, void 0, void 0, function* () {
74
- if (this._sessionRef) {
75
- const ret = yield _post(this._url, _jsonrpcReq("session.logout", [this._sessionRef]));
76
- if (ret.error)
77
- throw new Error(`Logout failed: ${JSON.stringify(ret.error)}`);
78
- this._sessionRef = undefined;
79
- }
80
- });
60
+ async login_with_password(user, password) {
61
+ const ret = await _post(this._url, _jsonrpcReq("session.login_with_password", [user, password, "version", "originator"]));
62
+ if (ret.error)
63
+ throw new Error(`Login failed: ${JSON.stringify(ret.error)}`);
64
+ this._sessionRef = ret.result;
65
+ return this._sessionRef;
81
66
  }
82
- _call(method_1) {
83
- return __awaiter(this, arguments, void 0, function* (method, params = []) {
84
- if (!this._sessionRef)
85
- throw new Error("Not logged in");
86
- const ret = yield _post(this._url, _jsonrpcReq(method, [this._sessionRef, ...params]));
67
+ async logout() {
68
+ if (this._sessionRef) {
69
+ const ret = await _post(this._url, _jsonrpcReq("session.logout", [this._sessionRef]));
87
70
  if (ret.error)
88
- throw new Error(`XAPI ${method} failed: ${JSON.stringify(ret.error)}`);
89
- return ret.result;
90
- });
71
+ throw new Error(`Logout failed: ${JSON.stringify(ret.error)}`);
72
+ this._sessionRef = undefined;
73
+ }
74
+ }
75
+ async _call(method, params = []) {
76
+ if (!this._sessionRef)
77
+ throw new Error("Not logged in");
78
+ const ret = await _post(this._url, _jsonrpcReq(method, [this._sessionRef, ...params]));
79
+ if (ret.error)
80
+ throw new Error(`XAPI ${method} failed: ${JSON.stringify(ret.error)}`);
81
+ return ret.result;
91
82
  }
92
83
  }
93
84
  exports.AsyncXenAPISession = AsyncXenAPISession;
94
- /** Convenience factory — equivalent to `new AsyncXenAPISession(url)` */
95
- function xapi_client(url) {
96
- return new AsyncXenAPISession(url);
97
- }
package/package.json CHANGED
@@ -1,26 +1,41 @@
1
1
  {
2
2
  "name": "async-xenapi",
3
- "version": "1.0.0",
4
- "description": "An async TypeScript library for Xen API",
3
+ "version": "1.0.3",
4
+ "description": "Async XenAPI session via JSON-RPC",
5
+ "homepage": "https://github.com/acefei/async-xenapi",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/acefei/async-xenapi.git"
9
+ },
10
+ "bugs": {
11
+ "url": "https://github.com/acefei/async-xenapi/issues"
12
+ },
5
13
  "main": "dist/XenAPI.js",
6
14
  "types": "dist/XenAPI.d.ts",
7
- "files": ["/dist"],
15
+ "files": [
16
+ "/dist"
17
+ ],
8
18
  "scripts": {
9
- "test": "node -r ts-node/register -r dotenv/config ",
19
+ "test": "vitest run",
10
20
  "build": "tsc",
11
21
  "lint": "biome check src/ tests/",
12
22
  "fmt": "biome check --write src/ tests/",
13
23
  "lint:ci": "biome check --diagnostic-level=error src/ tests/"
14
24
  },
15
- "keywords": ["xenserver", "xen-api", "typescript", "async"],
16
- "author": "Su Fei <fei.su@cloud.com>",
17
- "license": "GPL-2.0-only",
25
+ "keywords": [
26
+ "xenserver",
27
+ "xen-api",
28
+ "typescript",
29
+ "async"
30
+ ],
31
+ "author": "Su Fei <acefei@163.com>",
32
+ "license": "LGPL-2.1-only",
18
33
  "devDependencies": {
19
34
  "@biomejs/biome": "^1.9.0",
20
35
  "@types/node": "^20.8.9",
21
36
  "axios": "^1.6.0",
22
37
  "dotenv": "^16.3.1",
23
- "ts-node": "^10.9.1",
38
+ "vitest": "^3.0.0",
24
39
  "typescript": "^5.2.2"
25
40
  },
26
41
  "dependencies": {}