@hashgrid/sdk 0.1.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/README.md ADDED
@@ -0,0 +1,88 @@
1
+ # Hashgrid Client
2
+
3
+ TypeScript/JavaScript SDK for the Hashgrid DNA Protocol API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @hashgrid/sdk
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { Hashgrid, Message } from "@hashgrid/sdk";
15
+
16
+ async function main() {
17
+ // Connect to grid
18
+ const grid = await Hashgrid.connect("your-api-key");
19
+
20
+ // Listen for ticks and process messages
21
+ for await (const tick of grid.listen()) {
22
+ for await (const node of grid.nodes()) {
23
+ const messages = await node.recv();
24
+ if (messages.length === 0) {
25
+ continue;
26
+ }
27
+ const replies = messages.map((msg) =>
28
+ new Message(
29
+ msg.peer_id,
30
+ "Hello, fellow grid peer!",
31
+ msg.round,
32
+ 0.9
33
+ )
34
+ );
35
+ await node.send(replies);
36
+ }
37
+ }
38
+ }
39
+
40
+ main();
41
+ ```
42
+
43
+ ## Resources
44
+
45
+ The SDK provides the following resources:
46
+
47
+ - **`Grid`** - Grid connection with `listen()` and `nodes()` methods
48
+ - **`Node`** - Node with `recv()`, `send()`, `update()`, and `delete()` methods
49
+ - **`Edge`** - Edge data model
50
+ - **`User`** - User data model
51
+ - **`Quota`** - Quota data model
52
+ - **`Message`** - Message for recv/send operations
53
+ - **`Status`** - Status response from send operations
54
+
55
+ ## Examples
56
+
57
+ See the `examples/` directory for example implementations.
58
+
59
+ ## Error Handling
60
+
61
+ ```typescript
62
+ import {
63
+ HashgridError,
64
+ HashgridAPIError,
65
+ HashgridAuthenticationError,
66
+ HashgridNotFoundError,
67
+ HashgridValidationError,
68
+ } from "@hashgrid/sdk";
69
+
70
+ try {
71
+ const grid = await Hashgrid.connect("invalid-key");
72
+ } catch (error) {
73
+ if (error instanceof HashgridAuthenticationError) {
74
+ console.log("Authentication failed");
75
+ } else if (error instanceof HashgridAPIError) {
76
+ console.log(`API error: ${error.message}`);
77
+ }
78
+ }
79
+ ```
80
+
81
+ ## API Reference
82
+
83
+ For detailed API documentation, see the official Hashgrid DNA documentation.
84
+
85
+ ## License
86
+
87
+ MIT
88
+
@@ -0,0 +1,12 @@
1
+ /** Main Hashgrid client class. */
2
+ import { Grid } from "./resources";
3
+ export declare class Hashgrid {
4
+ private api_key?;
5
+ private base_url;
6
+ private timeout;
7
+ constructor(api_key?: string, base_url?: string, timeout?: number);
8
+ private _getHeaders;
9
+ _request(method: string, endpoint: string, params?: Record<string, any>, json_data?: any): Promise<any>;
10
+ _handleResponse(response: Response): Promise<any>;
11
+ static connect(api_key?: string, base_url?: string, timeout?: number): Promise<Grid>;
12
+ }
package/dist/client.js ADDED
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ /** Main Hashgrid client class. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.Hashgrid = void 0;
5
+ const exceptions_1 = require("./exceptions");
6
+ const resources_1 = require("./resources");
7
+ class Hashgrid {
8
+ constructor(api_key, base_url = "https://dna.hashgrid.ai", timeout = 30000) {
9
+ this.api_key = api_key;
10
+ this.base_url = base_url.replace(/\/$/, "");
11
+ this.timeout = timeout;
12
+ }
13
+ _getHeaders() {
14
+ const headers = {
15
+ "Content-Type": "application/json",
16
+ Accept: "application/json",
17
+ };
18
+ if (this.api_key) {
19
+ headers["Authorization"] = `Bearer ${this.api_key}`;
20
+ }
21
+ return headers;
22
+ }
23
+ async _request(method, endpoint, params, json_data) {
24
+ let url;
25
+ if (endpoint.startsWith("http://") || endpoint.startsWith("https://")) {
26
+ url = endpoint;
27
+ }
28
+ else {
29
+ const base = this.base_url.endsWith("/") ? this.base_url.slice(0, -1) : this.base_url;
30
+ const path = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
31
+ url = `${base}${path}`;
32
+ }
33
+ if (params) {
34
+ const searchParams = new URLSearchParams();
35
+ Object.entries(params).forEach(([key, value]) => {
36
+ searchParams.append(key, String(value));
37
+ });
38
+ url += `?${searchParams.toString()}`;
39
+ }
40
+ const headers = this._getHeaders();
41
+ const controller = new AbortController();
42
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
43
+ const options = {
44
+ method,
45
+ headers,
46
+ signal: controller.signal,
47
+ };
48
+ if (json_data !== undefined) {
49
+ options.body = JSON.stringify(json_data);
50
+ }
51
+ try {
52
+ const response = await fetch(url, options);
53
+ clearTimeout(timeoutId);
54
+ return await this._handleResponse(response);
55
+ }
56
+ catch (error) {
57
+ clearTimeout(timeoutId);
58
+ if (error.name === "AbortError") {
59
+ throw new exceptions_1.HashgridAPIError(`Request timeout after ${this.timeout}ms`);
60
+ }
61
+ throw new exceptions_1.HashgridAPIError(`Request failed: ${error.message}`);
62
+ }
63
+ }
64
+ async _handleResponse(response) {
65
+ try {
66
+ if (!response.ok) {
67
+ if (response.status === 401) {
68
+ throw new exceptions_1.HashgridAuthenticationError("Authentication failed. Check your API key.", response.status, response);
69
+ }
70
+ else if (response.status === 404) {
71
+ throw new exceptions_1.HashgridNotFoundError("Resource not found", response.status, response);
72
+ }
73
+ else if (response.status === 422) {
74
+ const errorData = response.headers.get("content-type")?.includes("application/json")
75
+ ? await response.json().catch(() => ({}))
76
+ : {};
77
+ throw new exceptions_1.HashgridValidationError(errorData.message || "Validation error", response.status, response);
78
+ }
79
+ else {
80
+ const errorData = response.headers.get("content-type")?.includes("application/json")
81
+ ? await response.json().catch(() => ({}))
82
+ : {};
83
+ throw new exceptions_1.HashgridAPIError(errorData.message || `API error: ${response.status}`, response.status, response);
84
+ }
85
+ }
86
+ if (!response.body) {
87
+ return {};
88
+ }
89
+ const contentType = response.headers.get("content-type");
90
+ if (contentType?.includes("application/json")) {
91
+ return await response.json();
92
+ }
93
+ else {
94
+ return { content: await response.text() };
95
+ }
96
+ }
97
+ catch (error) {
98
+ if (error instanceof exceptions_1.HashgridAPIError ||
99
+ error instanceof exceptions_1.HashgridAuthenticationError ||
100
+ error instanceof exceptions_1.HashgridNotFoundError ||
101
+ error instanceof exceptions_1.HashgridValidationError) {
102
+ throw error;
103
+ }
104
+ throw new exceptions_1.HashgridAPIError(`Response handling failed: ${error}`);
105
+ }
106
+ }
107
+ static async connect(api_key, base_url = "https://dna.hashgrid.ai", timeout = 30000) {
108
+ const client = new Hashgrid(api_key, base_url, timeout);
109
+ const data = await client._request("GET", "/api/v1");
110
+ const grid = new resources_1.Grid(data.name, data.tick, client);
111
+ return grid;
112
+ }
113
+ }
114
+ exports.Hashgrid = Hashgrid;
@@ -0,0 +1,18 @@
1
+ /** Custom exceptions for the Hashgrid client. */
2
+ export declare class HashgridError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ export declare class HashgridAPIError extends HashgridError {
6
+ status_code?: number;
7
+ response?: Response;
8
+ constructor(message: string, status_code?: number, response?: Response);
9
+ }
10
+ export declare class HashgridAuthenticationError extends HashgridAPIError {
11
+ constructor(message: string, status_code?: number, response?: Response);
12
+ }
13
+ export declare class HashgridNotFoundError extends HashgridAPIError {
14
+ constructor(message: string, status_code?: number, response?: Response);
15
+ }
16
+ export declare class HashgridValidationError extends HashgridAPIError {
17
+ constructor(message: string, status_code?: number, response?: Response);
18
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ /** Custom exceptions for the Hashgrid client. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.HashgridValidationError = exports.HashgridNotFoundError = exports.HashgridAuthenticationError = exports.HashgridAPIError = exports.HashgridError = void 0;
5
+ class HashgridError extends Error {
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = "HashgridError";
9
+ Object.setPrototypeOf(this, HashgridError.prototype);
10
+ }
11
+ }
12
+ exports.HashgridError = HashgridError;
13
+ class HashgridAPIError extends HashgridError {
14
+ constructor(message, status_code, response) {
15
+ super(message);
16
+ this.name = "HashgridAPIError";
17
+ this.status_code = status_code;
18
+ this.response = response;
19
+ Object.setPrototypeOf(this, HashgridAPIError.prototype);
20
+ }
21
+ }
22
+ exports.HashgridAPIError = HashgridAPIError;
23
+ class HashgridAuthenticationError extends HashgridAPIError {
24
+ constructor(message, status_code, response) {
25
+ super(message, status_code, response);
26
+ this.name = "HashgridAuthenticationError";
27
+ Object.setPrototypeOf(this, HashgridAuthenticationError.prototype);
28
+ }
29
+ }
30
+ exports.HashgridAuthenticationError = HashgridAuthenticationError;
31
+ class HashgridNotFoundError extends HashgridAPIError {
32
+ constructor(message, status_code, response) {
33
+ super(message, status_code, response);
34
+ this.name = "HashgridNotFoundError";
35
+ Object.setPrototypeOf(this, HashgridNotFoundError.prototype);
36
+ }
37
+ }
38
+ exports.HashgridNotFoundError = HashgridNotFoundError;
39
+ class HashgridValidationError extends HashgridAPIError {
40
+ constructor(message, status_code, response) {
41
+ super(message, status_code, response);
42
+ this.name = "HashgridValidationError";
43
+ Object.setPrototypeOf(this, HashgridValidationError.prototype);
44
+ }
45
+ }
46
+ exports.HashgridValidationError = HashgridValidationError;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Hashgrid Client - TypeScript/JavaScript SDK
3
+ */
4
+ export { Hashgrid } from "./client";
5
+ export { HashgridError, HashgridAPIError, HashgridAuthenticationError, HashgridNotFoundError, HashgridValidationError, } from "./exceptions";
6
+ export { Grid, User, Quota, Node, Edge, Message, Status, } from "./resources";
package/dist/index.js ADDED
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ /**
3
+ * Hashgrid Client - TypeScript/JavaScript SDK
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Status = exports.Message = exports.Edge = exports.Node = exports.Quota = exports.User = exports.Grid = exports.HashgridValidationError = exports.HashgridNotFoundError = exports.HashgridAuthenticationError = exports.HashgridAPIError = exports.HashgridError = exports.Hashgrid = void 0;
7
+ var client_1 = require("./client");
8
+ Object.defineProperty(exports, "Hashgrid", { enumerable: true, get: function () { return client_1.Hashgrid; } });
9
+ var exceptions_1 = require("./exceptions");
10
+ Object.defineProperty(exports, "HashgridError", { enumerable: true, get: function () { return exceptions_1.HashgridError; } });
11
+ Object.defineProperty(exports, "HashgridAPIError", { enumerable: true, get: function () { return exceptions_1.HashgridAPIError; } });
12
+ Object.defineProperty(exports, "HashgridAuthenticationError", { enumerable: true, get: function () { return exceptions_1.HashgridAuthenticationError; } });
13
+ Object.defineProperty(exports, "HashgridNotFoundError", { enumerable: true, get: function () { return exceptions_1.HashgridNotFoundError; } });
14
+ Object.defineProperty(exports, "HashgridValidationError", { enumerable: true, get: function () { return exceptions_1.HashgridValidationError; } });
15
+ var resources_1 = require("./resources");
16
+ Object.defineProperty(exports, "Grid", { enumerable: true, get: function () { return resources_1.Grid; } });
17
+ Object.defineProperty(exports, "User", { enumerable: true, get: function () { return resources_1.User; } });
18
+ Object.defineProperty(exports, "Quota", { enumerable: true, get: function () { return resources_1.Quota; } });
19
+ Object.defineProperty(exports, "Node", { enumerable: true, get: function () { return resources_1.Node; } });
20
+ Object.defineProperty(exports, "Edge", { enumerable: true, get: function () { return resources_1.Edge; } });
21
+ Object.defineProperty(exports, "Message", { enumerable: true, get: function () { return resources_1.Message; } });
22
+ Object.defineProperty(exports, "Status", { enumerable: true, get: function () { return resources_1.Status; } });
@@ -0,0 +1,58 @@
1
+ /** Hashgrid API resources. */
2
+ import { Hashgrid } from "./client";
3
+ export declare class User {
4
+ user_id: string;
5
+ name: string;
6
+ is_superuser: boolean;
7
+ quota_id: string;
8
+ constructor(user_id: string, name: string, is_superuser: boolean, quota_id: string);
9
+ }
10
+ export declare class Quota {
11
+ quota_id: string;
12
+ name: string;
13
+ capacity: number;
14
+ constructor(quota_id: string, name: string, capacity: number);
15
+ }
16
+ export declare class Edge {
17
+ node_id: string;
18
+ peer_id: string;
19
+ recv_message: string;
20
+ send_message: string | null;
21
+ score: number | null;
22
+ round: number;
23
+ constructor(node_id: string, peer_id: string, recv_message: string, send_message: string | null, score: number | null, round: number);
24
+ }
25
+ export declare class Message {
26
+ peer_id: string;
27
+ message: string;
28
+ round: number;
29
+ score: number | null;
30
+ constructor(peer_id: string, message: string, round: number, score?: number | null);
31
+ }
32
+ export declare class Status {
33
+ peer_id: string;
34
+ success: boolean;
35
+ constructor(peer_id: string, success: boolean);
36
+ }
37
+ export declare class Grid {
38
+ name: string;
39
+ tick: number;
40
+ private _client;
41
+ constructor(name: string, tick: number, client: Hashgrid);
42
+ listen(poll_interval?: number): AsyncGenerator<number>;
43
+ nodes(): AsyncGenerator<Node>;
44
+ create_node(name: string, message?: string, capacity?: number): Promise<Node>;
45
+ }
46
+ export declare class Node {
47
+ node_id: string;
48
+ owner_id: string;
49
+ name: string;
50
+ message: string;
51
+ capacity: number;
52
+ private _client;
53
+ constructor(node_id: string, owner_id: string, name: string, message: string, capacity: number, client: Hashgrid);
54
+ recv(): Promise<Message[]>;
55
+ send(replies: Message[]): Promise<Status[]>;
56
+ update(name?: string, message?: string, capacity?: number): Promise<Node>;
57
+ delete(): Promise<void>;
58
+ }
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ /** Hashgrid API resources. */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.Node = exports.Grid = exports.Status = exports.Message = exports.Edge = exports.Quota = exports.User = void 0;
5
+ class User {
6
+ constructor(user_id, name, is_superuser, quota_id) {
7
+ this.user_id = user_id;
8
+ this.name = name;
9
+ this.is_superuser = is_superuser;
10
+ this.quota_id = quota_id;
11
+ }
12
+ }
13
+ exports.User = User;
14
+ class Quota {
15
+ constructor(quota_id, name, capacity) {
16
+ this.quota_id = quota_id;
17
+ this.name = name;
18
+ this.capacity = capacity;
19
+ }
20
+ }
21
+ exports.Quota = Quota;
22
+ class Edge {
23
+ constructor(node_id, peer_id, recv_message, send_message, score, round) {
24
+ this.node_id = node_id;
25
+ this.peer_id = peer_id;
26
+ this.recv_message = recv_message;
27
+ this.send_message = send_message;
28
+ this.score = score;
29
+ this.round = round;
30
+ }
31
+ }
32
+ exports.Edge = Edge;
33
+ class Message {
34
+ constructor(peer_id, message, round, score = null) {
35
+ this.peer_id = peer_id;
36
+ this.message = message;
37
+ this.round = round;
38
+ this.score = score;
39
+ }
40
+ }
41
+ exports.Message = Message;
42
+ class Status {
43
+ constructor(peer_id, success) {
44
+ this.peer_id = peer_id;
45
+ this.success = success;
46
+ }
47
+ }
48
+ exports.Status = Status;
49
+ class Grid {
50
+ constructor(name, tick, client) {
51
+ this.name = name;
52
+ this.tick = tick;
53
+ this._client = client;
54
+ }
55
+ async *listen(poll_interval = 30000) {
56
+ let last_tick = -1;
57
+ while (true) {
58
+ try {
59
+ const data = await this._client._request("GET", "/api/v1");
60
+ this.name = data.name;
61
+ this.tick = data.tick;
62
+ const current_tick = this.tick;
63
+ if (current_tick !== last_tick) {
64
+ yield current_tick;
65
+ last_tick = current_tick;
66
+ }
67
+ await new Promise((resolve) => setTimeout(resolve, poll_interval));
68
+ }
69
+ catch (error) {
70
+ console.warn(`Error while listening for ticks: ${error}`);
71
+ await new Promise((resolve) => setTimeout(resolve, poll_interval * 2));
72
+ }
73
+ }
74
+ }
75
+ async *nodes() {
76
+ const data = await this._client._request("GET", "/api/v1/node");
77
+ for (const item of data) {
78
+ yield new Node(item.node_id, item.owner_id, item.name, item.message, item.capacity, this._client);
79
+ }
80
+ }
81
+ async create_node(name, message = "", capacity = 100) {
82
+ const json_data = { name, message, capacity };
83
+ const data = await this._client._request("POST", "/api/v1/node", undefined, json_data);
84
+ return new Node(data.node_id, data.owner_id, data.name, data.message, data.capacity, this._client);
85
+ }
86
+ }
87
+ exports.Grid = Grid;
88
+ class Node {
89
+ constructor(node_id, owner_id, name, message, capacity, client) {
90
+ this.node_id = node_id;
91
+ this.owner_id = owner_id;
92
+ this.name = name;
93
+ this.message = message;
94
+ this.capacity = capacity;
95
+ this._client = client;
96
+ }
97
+ async recv() {
98
+ const data = await this._client._request("GET", `/api/v1/node/${this.node_id}/recv`);
99
+ return data.map((item) => new Message(item.peer_id, item.message, item.round, item.score ?? null));
100
+ }
101
+ async send(replies) {
102
+ const json_data = replies.map((msg) => {
103
+ const obj = {
104
+ peer_id: msg.peer_id,
105
+ message: msg.message,
106
+ round: msg.round,
107
+ };
108
+ if (msg.score !== null) {
109
+ obj.score = msg.score;
110
+ }
111
+ return obj;
112
+ });
113
+ const data = await this._client._request("POST", `/api/v1/node/${this.node_id}/send`, undefined, json_data);
114
+ return data.map((item) => new Status(item.peer_id, item.success));
115
+ }
116
+ async update(name, message, capacity) {
117
+ const json_data = {};
118
+ if (name !== undefined)
119
+ json_data.name = name;
120
+ if (message !== undefined)
121
+ json_data.message = message;
122
+ if (capacity !== undefined)
123
+ json_data.capacity = capacity;
124
+ if (Object.keys(json_data).length === 0) {
125
+ return this;
126
+ }
127
+ const data = await this._client._request("PUT", `/api/v1/node/${this.node_id}`, undefined, json_data);
128
+ // Update local attributes
129
+ if (data.name !== undefined)
130
+ this.name = data.name;
131
+ if (data.message !== undefined)
132
+ this.message = data.message;
133
+ if (data.capacity !== undefined)
134
+ this.capacity = data.capacity;
135
+ return this;
136
+ }
137
+ async delete() {
138
+ await this._client._request("DELETE", `/api/v1/node/${this.node_id}`);
139
+ }
140
+ }
141
+ exports.Node = Node;
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@hashgrid/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Hashgrid TypeScript/JavaScript SDK",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "browser": {
8
+ "./dist/index.js": "./dist/index.js"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.js",
14
+ "types": "./dist/index.d.ts"
15
+ }
16
+ },
17
+ "scripts": {
18
+ "build": "tsc",
19
+ "prepublishOnly": "npm run build"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/hashgrid-labs/sdk.git"
24
+ },
25
+ "keywords": [
26
+ "hashgrid",
27
+ "client",
28
+ "sdk"
29
+ ],
30
+ "author": "Hashgrid",
31
+ "license": "MIT",
32
+ "bugs": {
33
+ "url": "https://github.com/hashgrid-labs/sdk/issues"
34
+ },
35
+ "homepage": "https://github.com/hashgrid-labs/sdk#readme",
36
+ "dependencies": {},
37
+ "devDependencies": {
38
+ "@types/node": "^20.0.0",
39
+ "typescript": "^5.0.0"
40
+ },
41
+ "engines": {
42
+ "node": ">=18.0.0"
43
+ }
44
+ }
package/src/client.ts ADDED
@@ -0,0 +1,162 @@
1
+ /** Main Hashgrid client class. */
2
+
3
+ import {
4
+ HashgridAPIError,
5
+ HashgridAuthenticationError,
6
+ HashgridNotFoundError,
7
+ HashgridValidationError,
8
+ } from "./exceptions";
9
+ import { Grid } from "./resources";
10
+
11
+ export class Hashgrid {
12
+ private api_key?: string;
13
+ private base_url: string;
14
+ private timeout: number;
15
+
16
+ constructor(
17
+ api_key?: string,
18
+ base_url: string = "https://dna.hashgrid.ai",
19
+ timeout: number = 30000
20
+ ) {
21
+ this.api_key = api_key;
22
+ this.base_url = base_url.replace(/\/$/, "");
23
+ this.timeout = timeout;
24
+ }
25
+
26
+ private _getHeaders(): Record<string, string> {
27
+ const headers: Record<string, string> = {
28
+ "Content-Type": "application/json",
29
+ Accept: "application/json",
30
+ };
31
+ if (this.api_key) {
32
+ headers["Authorization"] = `Bearer ${this.api_key}`;
33
+ }
34
+ return headers;
35
+ }
36
+
37
+ async _request(
38
+ method: string,
39
+ endpoint: string,
40
+ params?: Record<string, any>,
41
+ json_data?: any
42
+ ): Promise<any> {
43
+ let url: string;
44
+ if (endpoint.startsWith("http://") || endpoint.startsWith("https://")) {
45
+ url = endpoint;
46
+ } else {
47
+ const base = this.base_url.endsWith("/") ? this.base_url.slice(0, -1) : this.base_url;
48
+ const path = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
49
+ url = `${base}${path}`;
50
+ }
51
+
52
+ if (params) {
53
+ const searchParams = new URLSearchParams();
54
+ Object.entries(params).forEach(([key, value]) => {
55
+ searchParams.append(key, String(value));
56
+ });
57
+ url += `?${searchParams.toString()}`;
58
+ }
59
+
60
+ const headers = this._getHeaders();
61
+ const controller = new AbortController();
62
+ const timeoutId: ReturnType<typeof setTimeout> = setTimeout(() => controller.abort(), this.timeout);
63
+
64
+ const options: {
65
+ method: string;
66
+ headers: Record<string, string>;
67
+ signal: AbortSignal;
68
+ body?: string;
69
+ } = {
70
+ method,
71
+ headers,
72
+ signal: controller.signal,
73
+ };
74
+
75
+ if (json_data !== undefined) {
76
+ options.body = JSON.stringify(json_data);
77
+ }
78
+
79
+ try {
80
+ const response = await fetch(url, options);
81
+ clearTimeout(timeoutId);
82
+ return await this._handleResponse(response);
83
+ } catch (error: any) {
84
+ clearTimeout(timeoutId);
85
+ if (error.name === "AbortError") {
86
+ throw new HashgridAPIError(`Request timeout after ${this.timeout}ms`);
87
+ }
88
+ throw new HashgridAPIError(`Request failed: ${error.message}`);
89
+ }
90
+ }
91
+
92
+ async _handleResponse(response: Response): Promise<any> {
93
+ try {
94
+ if (!response.ok) {
95
+ if (response.status === 401) {
96
+ throw new HashgridAuthenticationError(
97
+ "Authentication failed. Check your API key.",
98
+ response.status,
99
+ response
100
+ );
101
+ } else if (response.status === 404) {
102
+ throw new HashgridNotFoundError(
103
+ "Resource not found",
104
+ response.status,
105
+ response
106
+ );
107
+ } else if (response.status === 422) {
108
+ const errorData = response.headers.get("content-type")?.includes("application/json")
109
+ ? await response.json().catch(() => ({}))
110
+ : {};
111
+ throw new HashgridValidationError(
112
+ errorData.message || "Validation error",
113
+ response.status,
114
+ response
115
+ );
116
+ } else {
117
+ const errorData = response.headers.get("content-type")?.includes("application/json")
118
+ ? await response.json().catch(() => ({}))
119
+ : {};
120
+ throw new HashgridAPIError(
121
+ errorData.message || `API error: ${response.status}`,
122
+ response.status,
123
+ response
124
+ );
125
+ }
126
+ }
127
+
128
+ if (!response.body) {
129
+ return {};
130
+ }
131
+
132
+ const contentType = response.headers.get("content-type");
133
+ if (contentType?.includes("application/json")) {
134
+ return await response.json();
135
+ } else {
136
+ return { content: await response.text() };
137
+ }
138
+ } catch (error) {
139
+ if (
140
+ error instanceof HashgridAPIError ||
141
+ error instanceof HashgridAuthenticationError ||
142
+ error instanceof HashgridNotFoundError ||
143
+ error instanceof HashgridValidationError
144
+ ) {
145
+ throw error;
146
+ }
147
+ throw new HashgridAPIError(`Response handling failed: ${error}`);
148
+ }
149
+ }
150
+
151
+ static async connect(
152
+ api_key?: string,
153
+ base_url: string = "https://dna.hashgrid.ai",
154
+ timeout: number = 30000
155
+ ): Promise<Grid> {
156
+ const client = new Hashgrid(api_key, base_url, timeout);
157
+ const data = await client._request("GET", "/api/v1");
158
+ const grid = new Grid(data.name, data.tick, client);
159
+ return grid;
160
+ }
161
+ }
162
+
@@ -0,0 +1,47 @@
1
+ /** Custom exceptions for the Hashgrid client. */
2
+
3
+ export class HashgridError extends Error {
4
+ constructor(message: string) {
5
+ super(message);
6
+ this.name = "HashgridError";
7
+ Object.setPrototypeOf(this, HashgridError.prototype);
8
+ }
9
+ }
10
+
11
+ export class HashgridAPIError extends HashgridError {
12
+ status_code?: number;
13
+ response?: Response;
14
+
15
+ constructor(message: string, status_code?: number, response?: Response) {
16
+ super(message);
17
+ this.name = "HashgridAPIError";
18
+ this.status_code = status_code;
19
+ this.response = response;
20
+ Object.setPrototypeOf(this, HashgridAPIError.prototype);
21
+ }
22
+ }
23
+
24
+ export class HashgridAuthenticationError extends HashgridAPIError {
25
+ constructor(message: string, status_code?: number, response?: Response) {
26
+ super(message, status_code, response);
27
+ this.name = "HashgridAuthenticationError";
28
+ Object.setPrototypeOf(this, HashgridAuthenticationError.prototype);
29
+ }
30
+ }
31
+
32
+ export class HashgridNotFoundError extends HashgridAPIError {
33
+ constructor(message: string, status_code?: number, response?: Response) {
34
+ super(message, status_code, response);
35
+ this.name = "HashgridNotFoundError";
36
+ Object.setPrototypeOf(this, HashgridNotFoundError.prototype);
37
+ }
38
+ }
39
+
40
+ export class HashgridValidationError extends HashgridAPIError {
41
+ constructor(message: string, status_code?: number, response?: Response) {
42
+ super(message, status_code, response);
43
+ this.name = "HashgridValidationError";
44
+ Object.setPrototypeOf(this, HashgridValidationError.prototype);
45
+ }
46
+ }
47
+
package/src/index.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Hashgrid Client - TypeScript/JavaScript SDK
3
+ */
4
+
5
+ export { Hashgrid } from "./client";
6
+ export {
7
+ HashgridError,
8
+ HashgridAPIError,
9
+ HashgridAuthenticationError,
10
+ HashgridNotFoundError,
11
+ HashgridValidationError,
12
+ } from "./exceptions";
13
+ export {
14
+ Grid,
15
+ User,
16
+ Quota,
17
+ Node,
18
+ Edge,
19
+ Message,
20
+ Status,
21
+ } from "./resources";
22
+
@@ -0,0 +1,244 @@
1
+ /** Hashgrid API resources. */
2
+
3
+ import { Hashgrid } from "./client";
4
+
5
+ export class User {
6
+ user_id: string;
7
+ name: string;
8
+ is_superuser: boolean;
9
+ quota_id: string;
10
+
11
+ constructor(
12
+ user_id: string,
13
+ name: string,
14
+ is_superuser: boolean,
15
+ quota_id: string
16
+ ) {
17
+ this.user_id = user_id;
18
+ this.name = name;
19
+ this.is_superuser = is_superuser;
20
+ this.quota_id = quota_id;
21
+ }
22
+ }
23
+
24
+ export class Quota {
25
+ quota_id: string;
26
+ name: string;
27
+ capacity: number;
28
+
29
+ constructor(quota_id: string, name: string, capacity: number) {
30
+ this.quota_id = quota_id;
31
+ this.name = name;
32
+ this.capacity = capacity;
33
+ }
34
+ }
35
+
36
+ export class Edge {
37
+ node_id: string;
38
+ peer_id: string;
39
+ recv_message: string;
40
+ send_message: string | null;
41
+ score: number | null;
42
+ round: number;
43
+
44
+ constructor(
45
+ node_id: string,
46
+ peer_id: string,
47
+ recv_message: string,
48
+ send_message: string | null,
49
+ score: number | null,
50
+ round: number
51
+ ) {
52
+ this.node_id = node_id;
53
+ this.peer_id = peer_id;
54
+ this.recv_message = recv_message;
55
+ this.send_message = send_message;
56
+ this.score = score;
57
+ this.round = round;
58
+ }
59
+ }
60
+
61
+ export class Message {
62
+ peer_id: string;
63
+ message: string;
64
+ round: number;
65
+ score: number | null;
66
+
67
+ constructor(
68
+ peer_id: string,
69
+ message: string,
70
+ round: number,
71
+ score: number | null = null
72
+ ) {
73
+ this.peer_id = peer_id;
74
+ this.message = message;
75
+ this.round = round;
76
+ this.score = score;
77
+ }
78
+ }
79
+
80
+ export class Status {
81
+ peer_id: string;
82
+ success: boolean;
83
+
84
+ constructor(peer_id: string, success: boolean) {
85
+ this.peer_id = peer_id;
86
+ this.success = success;
87
+ }
88
+ }
89
+
90
+ export class Grid {
91
+ name: string;
92
+ tick: number;
93
+ private _client: Hashgrid;
94
+
95
+ constructor(name: string, tick: number, client: Hashgrid) {
96
+ this.name = name;
97
+ this.tick = tick;
98
+ this._client = client;
99
+ }
100
+
101
+ async *listen(poll_interval: number = 30000): AsyncGenerator<number> {
102
+ let last_tick = -1;
103
+ while (true) {
104
+ try {
105
+ const data = await this._client._request("GET", "/api/v1");
106
+ this.name = data.name;
107
+ this.tick = data.tick;
108
+ const current_tick = this.tick;
109
+
110
+ if (current_tick !== last_tick) {
111
+ yield current_tick;
112
+ last_tick = current_tick;
113
+ }
114
+
115
+ await new Promise((resolve) => setTimeout(resolve, poll_interval));
116
+ } catch (error) {
117
+ console.warn(`Error while listening for ticks: ${error}`);
118
+ await new Promise((resolve) => setTimeout(resolve, poll_interval * 2));
119
+ }
120
+ }
121
+ }
122
+
123
+ async *nodes(): AsyncGenerator<Node> {
124
+ const data = await this._client._request("GET", "/api/v1/node");
125
+ for (const item of data) {
126
+ yield new Node(
127
+ item.node_id,
128
+ item.owner_id,
129
+ item.name,
130
+ item.message,
131
+ item.capacity,
132
+ this._client
133
+ );
134
+ }
135
+ }
136
+
137
+ async create_node(
138
+ name: string,
139
+ message: string = "",
140
+ capacity: number = 100
141
+ ): Promise<Node> {
142
+ const json_data = { name, message, capacity };
143
+ const data = await this._client._request("POST", "/api/v1/node", undefined, json_data);
144
+ return new Node(
145
+ data.node_id,
146
+ data.owner_id,
147
+ data.name,
148
+ data.message,
149
+ data.capacity,
150
+ this._client
151
+ );
152
+ }
153
+ }
154
+
155
+ export class Node {
156
+ node_id: string;
157
+ owner_id: string;
158
+ name: string;
159
+ message: string;
160
+ capacity: number;
161
+ private _client: Hashgrid;
162
+
163
+ constructor(
164
+ node_id: string,
165
+ owner_id: string,
166
+ name: string,
167
+ message: string,
168
+ capacity: number,
169
+ client: Hashgrid
170
+ ) {
171
+ this.node_id = node_id;
172
+ this.owner_id = owner_id;
173
+ this.name = name;
174
+ this.message = message;
175
+ this.capacity = capacity;
176
+ this._client = client;
177
+ }
178
+
179
+ async recv(): Promise<Message[]> {
180
+ const data = await this._client._request(
181
+ "GET",
182
+ `/api/v1/node/${this.node_id}/recv`
183
+ );
184
+ return data.map(
185
+ (item: any) =>
186
+ new Message(item.peer_id, item.message, item.round, item.score ?? null)
187
+ );
188
+ }
189
+
190
+ async send(replies: Message[]): Promise<Status[]> {
191
+ const json_data = replies.map((msg) => {
192
+ const obj: any = {
193
+ peer_id: msg.peer_id,
194
+ message: msg.message,
195
+ round: msg.round,
196
+ };
197
+ if (msg.score !== null) {
198
+ obj.score = msg.score;
199
+ }
200
+ return obj;
201
+ });
202
+ const data = await this._client._request(
203
+ "POST",
204
+ `/api/v1/node/${this.node_id}/send`,
205
+ undefined,
206
+ json_data
207
+ );
208
+ return data.map((item: any) => new Status(item.peer_id, item.success));
209
+ }
210
+
211
+ async update(
212
+ name?: string,
213
+ message?: string,
214
+ capacity?: number
215
+ ): Promise<Node> {
216
+ const json_data: any = {};
217
+ if (name !== undefined) json_data.name = name;
218
+ if (message !== undefined) json_data.message = message;
219
+ if (capacity !== undefined) json_data.capacity = capacity;
220
+
221
+ if (Object.keys(json_data).length === 0) {
222
+ return this;
223
+ }
224
+
225
+ const data = await this._client._request(
226
+ "PUT",
227
+ `/api/v1/node/${this.node_id}`,
228
+ undefined,
229
+ json_data
230
+ );
231
+
232
+ // Update local attributes
233
+ if (data.name !== undefined) this.name = data.name;
234
+ if (data.message !== undefined) this.message = data.message;
235
+ if (data.capacity !== undefined) this.capacity = data.capacity;
236
+
237
+ return this;
238
+ }
239
+
240
+ async delete(): Promise<void> {
241
+ await this._client._request("DELETE", `/api/v1/node/${this.node_id}`);
242
+ }
243
+ }
244
+
package/tsconfig.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": [
6
+ "ES2020",
7
+ "DOM"
8
+ ],
9
+ "declaration": true,
10
+ "outDir": "./dist",
11
+ "rootDir": "./src",
12
+ "strict": true,
13
+ "esModuleInterop": true,
14
+ "skipLibCheck": true,
15
+ "forceConsistentCasingInFileNames": true,
16
+ "resolveJsonModule": true,
17
+ "moduleResolution": "node"
18
+ },
19
+ "include": [
20
+ "src/**/*"
21
+ ],
22
+ "exclude": [
23
+ "node_modules",
24
+ "dist"
25
+ ]
26
+ }