truenas-client 1.0.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/LICENSE +15 -0
- package/README.md +143 -0
- package/dist/index.cjs +122 -0
- package/dist/index.d.cts +27 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +95 -0
- package/package.json +44 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Daury Guzman <me@dauryguzman.com>
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# truenas-client
|
|
2
|
+
|
|
3
|
+
A lightweight TypeScript client for interacting with the [TrueNAS WebSocket API](https://api.truenas.com/v25.10/jsonrpc.html) using JSON-RPC 2.0.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install truenas-client
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
> **Note:** Requires Node.js >= 21 (for native `WebSocket` support).
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### Setup
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { TrueNASClient } from "truenas-client";
|
|
19
|
+
|
|
20
|
+
const client = new TrueNASClient("wss://your-truenas-host/websocket", "your-api-key");
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Single Request
|
|
24
|
+
|
|
25
|
+
Use `sendRequest` to send a single API call. The client handles authentication automatically before forwarding your request.
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
const response = await client.sendRequest("system.info");
|
|
29
|
+
console.log(response.result);
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
You can also pass parameters:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
const response = await client.sendRequest("pool.dataset.query", [[], { limit: 10 }]);
|
|
36
|
+
console.log(response.result);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Error Handling
|
|
40
|
+
|
|
41
|
+
Each response may include an `error` object instead of a `result`. Always check for errors:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
const response = await client.sendRequest("system.info");
|
|
45
|
+
|
|
46
|
+
if (response.error) {
|
|
47
|
+
console.error(`Error ${response.error.code}: ${response.error.message}`);
|
|
48
|
+
} else {
|
|
49
|
+
console.log(response.result);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Requests will reject with an `Error` in the following cases:
|
|
54
|
+
|
|
55
|
+
- **Authentication failure** — invalid API key or denied access.
|
|
56
|
+
- **Timeout** — no response received within 30 seconds (default).
|
|
57
|
+
- **WebSocket error** — connection-level error.
|
|
58
|
+
- **WebSocket closed** — server closes the connection before a response is received.
|
|
59
|
+
|
|
60
|
+
## API
|
|
61
|
+
|
|
62
|
+
### `new TrueNASClient(wsURL: string, apiKey: string)`
|
|
63
|
+
|
|
64
|
+
Creates a new client instance.
|
|
65
|
+
|
|
66
|
+
| Parameter | Type | Description |
|
|
67
|
+
| --------- | -------- | ------------------------------------------------------------------------------- |
|
|
68
|
+
| `wsURL` | `string` | The WebSocket URL of your TrueNAS server (e.g. `ws://192.168.1.100/websocket`) |
|
|
69
|
+
| `apiKey` | `string` | A TrueNAS API key for authentication |
|
|
70
|
+
|
|
71
|
+
> For TrueNAS servers with HTTPS enabled, use `wss://` instead of `ws://`.
|
|
72
|
+
|
|
73
|
+
### `client.sendRequest(method: string, params?: any, timeoutMs?: number): Promise<JSONRPCResponse>`
|
|
74
|
+
|
|
75
|
+
Sends a single JSON-RPC 2.0 request. Opens a WebSocket connection, authenticates with the provided API key, sends the request, and closes the connection.
|
|
76
|
+
|
|
77
|
+
| Parameter | Type | Default | Description |
|
|
78
|
+
| ----------- | -------- | ------- | -------------------------------------------------------- |
|
|
79
|
+
| `method` | `string` | — | The JSON-RPC method to call (e.g. `"system.info"`) |
|
|
80
|
+
| `params` | `any` | — | Optional parameters to pass with the request |
|
|
81
|
+
| `timeoutMs` | `number` | `30000` | Timeout in milliseconds for each underlying WebSocket call (auth + request) |
|
|
82
|
+
|
|
83
|
+
### Types
|
|
84
|
+
|
|
85
|
+
#### `JSONRPCRequest`
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
interface JSONRPCRequest {
|
|
89
|
+
jsonrpc: "2.0";
|
|
90
|
+
method: string;
|
|
91
|
+
params?: any;
|
|
92
|
+
id: number | string;
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
#### `JSONRPCResponse`
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
interface JSONRPCResponse {
|
|
100
|
+
jsonrpc: "2.0";
|
|
101
|
+
result?: any;
|
|
102
|
+
error?: {
|
|
103
|
+
code: number;
|
|
104
|
+
message: string;
|
|
105
|
+
data?: any;
|
|
106
|
+
};
|
|
107
|
+
id: number | string;
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The `result` field is typed as `any` to match the [TrueNAS JSON-RPC API](https://api.truenas.com/v25.10/jsonrpc.html), which returns varied response shapes depending on the method called.
|
|
112
|
+
|
|
113
|
+
## Security
|
|
114
|
+
|
|
115
|
+
Never hardcode your API key. Use environment variables:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const client = new TrueNASClient(
|
|
119
|
+
process.env.TRUENAS_URL!,
|
|
120
|
+
process.env.TRUENAS_API_KEY!
|
|
121
|
+
);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## How It Works
|
|
125
|
+
|
|
126
|
+
1. A new WebSocket connection is opened to the TrueNAS server.
|
|
127
|
+
2. The client authenticates using `auth.login_with_api_key` with your API key.
|
|
128
|
+
3. If authentication succeeds, your request is sent over the same connection.
|
|
129
|
+
4. The connection is closed after the response is received.
|
|
130
|
+
|
|
131
|
+
Each call to `sendRequest` opens and closes its own WebSocket connection.
|
|
132
|
+
|
|
133
|
+
## Building
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
npm run build
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
This compiles the TypeScript source from `src/` into JavaScript in `dist/` using [tsup](https://tsup.egoist.dev/), producing both ESM and CJS outputs with type declarations.
|
|
140
|
+
|
|
141
|
+
## License
|
|
142
|
+
|
|
143
|
+
[ISC](LICENSE) © Daury Guzman <me@dauryguzman.com>
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
TrueNASClient: () => TrueNASClient,
|
|
24
|
+
default: () => index_default
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/client.ts
|
|
29
|
+
var TrueNASClient = class {
|
|
30
|
+
wsURL;
|
|
31
|
+
apiKey;
|
|
32
|
+
constructor(wsURL, apiKey) {
|
|
33
|
+
const url = new URL(wsURL);
|
|
34
|
+
if (url.protocol !== "wss:" && url.protocol !== "ws:")
|
|
35
|
+
throw new Error("Invalid url protocol. Expect wss: or ws:");
|
|
36
|
+
this.wsURL = wsURL;
|
|
37
|
+
this.apiKey = apiKey;
|
|
38
|
+
}
|
|
39
|
+
async sendRequest(method, params, timeoutMs = 3e4) {
|
|
40
|
+
let id = 0;
|
|
41
|
+
const socket = new WebSocket(this.wsURL);
|
|
42
|
+
try {
|
|
43
|
+
const authRequest = this.request(id++, "auth.login_with_api_key", [this.apiKey]);
|
|
44
|
+
const apiRequest = this.request(id++, method, params);
|
|
45
|
+
const authResponse = await this.call(socket, authRequest, timeoutMs);
|
|
46
|
+
if (authResponse.result !== true) {
|
|
47
|
+
throw new Error("Authentication failed");
|
|
48
|
+
}
|
|
49
|
+
return await this.call(socket, apiRequest, timeoutMs);
|
|
50
|
+
} finally {
|
|
51
|
+
socket.close();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
request(id, method, params) {
|
|
55
|
+
return {
|
|
56
|
+
jsonrpc: "2.0",
|
|
57
|
+
method,
|
|
58
|
+
params: params ?? [],
|
|
59
|
+
id
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
async parseMessageData(data) {
|
|
63
|
+
if (typeof data === "string") return data;
|
|
64
|
+
if (data instanceof Blob) return await data.text();
|
|
65
|
+
if (data instanceof ArrayBuffer) return new TextDecoder().decode(data);
|
|
66
|
+
return String(data);
|
|
67
|
+
}
|
|
68
|
+
call(socket, req, timeoutMs) {
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
const cleanup = () => {
|
|
71
|
+
clearTimeout(timer);
|
|
72
|
+
socket.removeEventListener("message", onMessage);
|
|
73
|
+
socket.removeEventListener("error", onError);
|
|
74
|
+
socket.removeEventListener("close", onClose);
|
|
75
|
+
socket.removeEventListener("open", onOpen);
|
|
76
|
+
};
|
|
77
|
+
const timer = setTimeout(() => {
|
|
78
|
+
cleanup();
|
|
79
|
+
reject(new Error(`Request timed out for id ${req.id}`));
|
|
80
|
+
}, timeoutMs);
|
|
81
|
+
const onMessage = async (event) => {
|
|
82
|
+
try {
|
|
83
|
+
const raw = await this.parseMessageData(event.data);
|
|
84
|
+
const response = JSON.parse(raw);
|
|
85
|
+
if (response.id === req.id) {
|
|
86
|
+
cleanup();
|
|
87
|
+
resolve(response);
|
|
88
|
+
}
|
|
89
|
+
} catch (error) {
|
|
90
|
+
cleanup();
|
|
91
|
+
reject(new Error(`Error parsing response for id ${req.id}: ${String(error)}`));
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const onError = (event) => {
|
|
95
|
+
cleanup();
|
|
96
|
+
reject(new Error(`WebSocket error ${event}`));
|
|
97
|
+
};
|
|
98
|
+
const onClose = (event) => {
|
|
99
|
+
cleanup();
|
|
100
|
+
reject(new Error(`WebSocket closed before response received (code: ${event.code})`));
|
|
101
|
+
};
|
|
102
|
+
const onOpen = () => {
|
|
103
|
+
socket.send(JSON.stringify(req));
|
|
104
|
+
};
|
|
105
|
+
socket.addEventListener("message", onMessage);
|
|
106
|
+
socket.addEventListener("error", onError);
|
|
107
|
+
socket.addEventListener("close", onClose);
|
|
108
|
+
if (socket.readyState === socket.OPEN) {
|
|
109
|
+
socket.send(JSON.stringify(req));
|
|
110
|
+
} else {
|
|
111
|
+
socket.addEventListener("open", onOpen);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// src/index.ts
|
|
118
|
+
var index_default = TrueNASClient;
|
|
119
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
120
|
+
0 && (module.exports = {
|
|
121
|
+
TrueNASClient
|
|
122
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
declare class TrueNASClient {
|
|
2
|
+
private wsURL;
|
|
3
|
+
private apiKey;
|
|
4
|
+
constructor(wsURL: string, apiKey: string);
|
|
5
|
+
sendRequest(method: string, params?: any, timeoutMs?: number): Promise<JSONRPCResponse>;
|
|
6
|
+
private request;
|
|
7
|
+
private parseMessageData;
|
|
8
|
+
private call;
|
|
9
|
+
}
|
|
10
|
+
interface JSONRPCRequest {
|
|
11
|
+
jsonrpc: "2.0";
|
|
12
|
+
method: string;
|
|
13
|
+
params?: any;
|
|
14
|
+
id: number | string;
|
|
15
|
+
}
|
|
16
|
+
interface JSONRPCResponse {
|
|
17
|
+
jsonrpc: "2.0";
|
|
18
|
+
result?: any;
|
|
19
|
+
error?: {
|
|
20
|
+
code: number;
|
|
21
|
+
message: string;
|
|
22
|
+
data?: any;
|
|
23
|
+
};
|
|
24
|
+
id: number | string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { type JSONRPCRequest, type JSONRPCResponse, TrueNASClient, TrueNASClient as default };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
declare class TrueNASClient {
|
|
2
|
+
private wsURL;
|
|
3
|
+
private apiKey;
|
|
4
|
+
constructor(wsURL: string, apiKey: string);
|
|
5
|
+
sendRequest(method: string, params?: any, timeoutMs?: number): Promise<JSONRPCResponse>;
|
|
6
|
+
private request;
|
|
7
|
+
private parseMessageData;
|
|
8
|
+
private call;
|
|
9
|
+
}
|
|
10
|
+
interface JSONRPCRequest {
|
|
11
|
+
jsonrpc: "2.0";
|
|
12
|
+
method: string;
|
|
13
|
+
params?: any;
|
|
14
|
+
id: number | string;
|
|
15
|
+
}
|
|
16
|
+
interface JSONRPCResponse {
|
|
17
|
+
jsonrpc: "2.0";
|
|
18
|
+
result?: any;
|
|
19
|
+
error?: {
|
|
20
|
+
code: number;
|
|
21
|
+
message: string;
|
|
22
|
+
data?: any;
|
|
23
|
+
};
|
|
24
|
+
id: number | string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { type JSONRPCRequest, type JSONRPCResponse, TrueNASClient, TrueNASClient as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
var TrueNASClient = class {
|
|
3
|
+
wsURL;
|
|
4
|
+
apiKey;
|
|
5
|
+
constructor(wsURL, apiKey) {
|
|
6
|
+
const url = new URL(wsURL);
|
|
7
|
+
if (url.protocol !== "wss:" && url.protocol !== "ws:")
|
|
8
|
+
throw new Error("Invalid url protocol. Expect wss: or ws:");
|
|
9
|
+
this.wsURL = wsURL;
|
|
10
|
+
this.apiKey = apiKey;
|
|
11
|
+
}
|
|
12
|
+
async sendRequest(method, params, timeoutMs = 3e4) {
|
|
13
|
+
let id = 0;
|
|
14
|
+
const socket = new WebSocket(this.wsURL);
|
|
15
|
+
try {
|
|
16
|
+
const authRequest = this.request(id++, "auth.login_with_api_key", [this.apiKey]);
|
|
17
|
+
const apiRequest = this.request(id++, method, params);
|
|
18
|
+
const authResponse = await this.call(socket, authRequest, timeoutMs);
|
|
19
|
+
if (authResponse.result !== true) {
|
|
20
|
+
throw new Error("Authentication failed");
|
|
21
|
+
}
|
|
22
|
+
return await this.call(socket, apiRequest, timeoutMs);
|
|
23
|
+
} finally {
|
|
24
|
+
socket.close();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
request(id, method, params) {
|
|
28
|
+
return {
|
|
29
|
+
jsonrpc: "2.0",
|
|
30
|
+
method,
|
|
31
|
+
params: params ?? [],
|
|
32
|
+
id
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async parseMessageData(data) {
|
|
36
|
+
if (typeof data === "string") return data;
|
|
37
|
+
if (data instanceof Blob) return await data.text();
|
|
38
|
+
if (data instanceof ArrayBuffer) return new TextDecoder().decode(data);
|
|
39
|
+
return String(data);
|
|
40
|
+
}
|
|
41
|
+
call(socket, req, timeoutMs) {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
const cleanup = () => {
|
|
44
|
+
clearTimeout(timer);
|
|
45
|
+
socket.removeEventListener("message", onMessage);
|
|
46
|
+
socket.removeEventListener("error", onError);
|
|
47
|
+
socket.removeEventListener("close", onClose);
|
|
48
|
+
socket.removeEventListener("open", onOpen);
|
|
49
|
+
};
|
|
50
|
+
const timer = setTimeout(() => {
|
|
51
|
+
cleanup();
|
|
52
|
+
reject(new Error(`Request timed out for id ${req.id}`));
|
|
53
|
+
}, timeoutMs);
|
|
54
|
+
const onMessage = async (event) => {
|
|
55
|
+
try {
|
|
56
|
+
const raw = await this.parseMessageData(event.data);
|
|
57
|
+
const response = JSON.parse(raw);
|
|
58
|
+
if (response.id === req.id) {
|
|
59
|
+
cleanup();
|
|
60
|
+
resolve(response);
|
|
61
|
+
}
|
|
62
|
+
} catch (error) {
|
|
63
|
+
cleanup();
|
|
64
|
+
reject(new Error(`Error parsing response for id ${req.id}: ${String(error)}`));
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const onError = (event) => {
|
|
68
|
+
cleanup();
|
|
69
|
+
reject(new Error(`WebSocket error ${event}`));
|
|
70
|
+
};
|
|
71
|
+
const onClose = (event) => {
|
|
72
|
+
cleanup();
|
|
73
|
+
reject(new Error(`WebSocket closed before response received (code: ${event.code})`));
|
|
74
|
+
};
|
|
75
|
+
const onOpen = () => {
|
|
76
|
+
socket.send(JSON.stringify(req));
|
|
77
|
+
};
|
|
78
|
+
socket.addEventListener("message", onMessage);
|
|
79
|
+
socket.addEventListener("error", onError);
|
|
80
|
+
socket.addEventListener("close", onClose);
|
|
81
|
+
if (socket.readyState === socket.OPEN) {
|
|
82
|
+
socket.send(JSON.stringify(req));
|
|
83
|
+
} else {
|
|
84
|
+
socket.addEventListener("open", onOpen);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// src/index.ts
|
|
91
|
+
var index_default = TrueNASClient;
|
|
92
|
+
export {
|
|
93
|
+
TrueNASClient,
|
|
94
|
+
index_default as default
|
|
95
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "truenas-client",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A lightweight TypeScript client for the TrueNAS WebSocket API (JSON-RPC 2.0)",
|
|
5
|
+
"main": "dist/index.cjs",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsup src/index.ts --format esm,cjs --dts",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git@github.com:dauryg/truenas-client.git"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"truenas"
|
|
30
|
+
],
|
|
31
|
+
"author": "daury guzman <me@dauryguzman.com>",
|
|
32
|
+
"license": "ISC",
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/dauryg/truenas-client/issues"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/dauryg/truenas-client#readme",
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"tsup": "^8.5.1",
|
|
39
|
+
"typescript": "^5.9.3"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=21"
|
|
43
|
+
}
|
|
44
|
+
}
|