@synapsedx/sdk 0.0.0-stage → 0.3.0-dev.20261012
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 +35 -2
- package/dist/client.test.d.ts +1 -0
- package/dist/client.test.js +75 -0
- package/dist/example.d.ts +1 -0
- package/dist/example.js +11 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.js +159 -0
- package/package.json +16 -4
- package/src/client.test.ts +82 -0
- package/src/example.ts +10 -0
- package/src/index.ts +165 -0
- package/tsconfig.json +13 -0
package/README.md
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@synapsedx/sdk` — synapsedx SDK for JavaScript/TypeScript
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Apache-2.0. Part of [SynapseDX](../../README.md) Tier-1 PaaS.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @synapsedx/sdk
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { SynapseDXClient } from '@synapsedx/sdk';
|
|
11
|
+
|
|
12
|
+
const c = new SynapseDXClient({ baseUrl: 'http://localhost:8080' });
|
|
13
|
+
const { accessToken, user } = await c.login('demo@acme.local');
|
|
14
|
+
c.setToken(accessToken);
|
|
15
|
+
|
|
16
|
+
await c.createOrg('acme-2', 'ACME 2');
|
|
17
|
+
await c.authorize(user.id, 'workflow:Execute', '*');
|
|
18
|
+
await c.createWorkflowFromTemplate('webhook-to-log', 'demo');
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Auth: `setToken(jwt)` (Bearer) hoặc `setApiKey(key)` (`X-API-Key`).
|
|
22
|
+
`timeoutMs` tuỳ chọn (mặc định 15000).
|
|
23
|
+
Xem thêm `src/example.ts` và [docs/api.md](../../docs/api.md).
|
|
24
|
+
|
|
25
|
+
## Full method coverage (mirrors `docs/api.md`)
|
|
26
|
+
|
|
27
|
+
- Auth: `login`, `refresh`, `logout`, `logoutAll`, `verify`, `me`
|
|
28
|
+
- Orgs/accounts/users: `listOrgs`, `createOrg`, `listAccounts`, `createAccount`,
|
|
29
|
+
`listUsers`, `createUser`, `userPolicies`
|
|
30
|
+
- IAM: `createRole`, `listRoles`, `deleteRole`, `createPolicy`, `listPolicies`,
|
|
31
|
+
`deletePolicy`, `attachPolicy`, `assignRole`, `authorize`, `simulate`,
|
|
32
|
+
`listAudit`, `createApiKey`, `revokeApiKey`, `verifyApiKey`,
|
|
33
|
+
`createServiceAccount`, `listServiceAccounts`, `serviceAccountToken`
|
|
34
|
+
- Workflows: `listWorkflows`, `getWorkflow`, `createWorkflowFromTemplate`,
|
|
35
|
+
`createWorkflow`, `activateWorkflow`, `deactivateWorkflow`, `executeWorkflow`,
|
|
36
|
+
`listTemplates`, `getTemplate`, `listExecutions`, `getExecution`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
/*
|
|
7
|
+
* Copyright 2026 SynapseDX Contributors
|
|
8
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
9
|
+
* Offline SDK tests — local echo server, no platform needed.
|
|
10
|
+
*/
|
|
11
|
+
const node_test_1 = require("node:test");
|
|
12
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
13
|
+
const node_http_1 = __importDefault(require("node:http"));
|
|
14
|
+
const index_1 = require("./index");
|
|
15
|
+
let base = '';
|
|
16
|
+
let lastHeaders = {};
|
|
17
|
+
let lastPath = '';
|
|
18
|
+
let server;
|
|
19
|
+
(0, node_test_1.before)(() => new Promise((resolve) => {
|
|
20
|
+
server = node_http_1.default
|
|
21
|
+
.createServer((req, res) => {
|
|
22
|
+
lastHeaders = { ...req.headers };
|
|
23
|
+
lastPath = req.url || '';
|
|
24
|
+
res.setHeader('Content-Type', 'application/json');
|
|
25
|
+
res.end(JSON.stringify({ ok: true, path: req.url }));
|
|
26
|
+
})
|
|
27
|
+
.listen(0, '127.0.0.1', () => {
|
|
28
|
+
const a = server.address();
|
|
29
|
+
base = `http://127.0.0.1:${typeof a === 'object' && a ? a.port : 0}`;
|
|
30
|
+
resolve();
|
|
31
|
+
});
|
|
32
|
+
}));
|
|
33
|
+
(0, node_test_1.after)(() => new Promise((resolve) => server.close(() => resolve())));
|
|
34
|
+
(0, node_test_1.describe)('SynapseDXClient', () => {
|
|
35
|
+
(0, node_test_1.it)('trims trailing slash', () => {
|
|
36
|
+
const c = new index_1.SynapseDXClient({ baseUrl: 'http://x:8080/' });
|
|
37
|
+
strict_1.default.equal(c.baseUrl, 'http://x:8080');
|
|
38
|
+
});
|
|
39
|
+
(0, node_test_1.it)('sends Bearer token', async () => {
|
|
40
|
+
const c = new index_1.SynapseDXClient({ baseUrl: base });
|
|
41
|
+
c.setToken('tok123');
|
|
42
|
+
await c.listOrgs();
|
|
43
|
+
strict_1.default.equal(lastHeaders['authorization'], 'Bearer tok123');
|
|
44
|
+
strict_1.default.equal(lastPath, '/v1/orgs');
|
|
45
|
+
});
|
|
46
|
+
(0, node_test_1.it)('sends X-API-Key', async () => {
|
|
47
|
+
const c = new index_1.SynapseDXClient({ baseUrl: base, apiKey: 'k1' });
|
|
48
|
+
await c.listTemplates();
|
|
49
|
+
strict_1.default.equal(lastHeaders['x-api-key'], 'k1');
|
|
50
|
+
});
|
|
51
|
+
(0, node_test_1.it)('throws rich error on non-2xx', async () => {
|
|
52
|
+
const bad = new index_1.SynapseDXClient({ baseUrl: 'http://127.0.0.1:1' });
|
|
53
|
+
await strict_1.default.rejects(() => bad.listOrgs(), /synapsedx/);
|
|
54
|
+
});
|
|
55
|
+
(0, node_test_1.it)('encodes ids in paths', async () => {
|
|
56
|
+
const c = new index_1.SynapseDXClient({ baseUrl: base });
|
|
57
|
+
await c.getWorkflow('a/b');
|
|
58
|
+
strict_1.default.equal(lastPath, '/v1/workflows/a%2Fb');
|
|
59
|
+
});
|
|
60
|
+
(0, node_test_1.it)('covers new endpoints', async () => {
|
|
61
|
+
const c = new index_1.SynapseDXClient({ baseUrl: base });
|
|
62
|
+
await c.userPolicies('u1');
|
|
63
|
+
strict_1.default.equal(lastPath, '/v1/users/u1/policies');
|
|
64
|
+
await c.getTemplate('webhook-to-log');
|
|
65
|
+
strict_1.default.equal(lastPath, '/v1/templates/webhook-to-log');
|
|
66
|
+
await c.listExecutions('limit=5');
|
|
67
|
+
strict_1.default.equal(lastPath, '/v1/executions?limit=5');
|
|
68
|
+
await c.executeWorkflow('w1', {});
|
|
69
|
+
strict_1.default.equal(lastPath, '/v1/workflows/w1/execute');
|
|
70
|
+
await c.verifyApiKey('k');
|
|
71
|
+
strict_1.default.equal(lastPath, '/v1/keys/verify');
|
|
72
|
+
await c.deactivateWorkflow('w1');
|
|
73
|
+
strict_1.default.equal(lastPath, '/v1/workflows/w1/deactivate');
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/example.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const index_1 = require("./index");
|
|
4
|
+
async function main() {
|
|
5
|
+
const c = new index_1.SynapseDXClient({ baseUrl: process.env.SYNAPSEDX_BASE_URL || 'http://localhost:8080' });
|
|
6
|
+
const login = await c.login('demo@acme.local');
|
|
7
|
+
console.log('login ok:', login.user?.email);
|
|
8
|
+
c.setToken(login.accessToken);
|
|
9
|
+
console.log(await c.listOrgs());
|
|
10
|
+
}
|
|
11
|
+
main().catch((e) => { console.error(e); process.exit(1); });
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export interface SynapseDXClientOptions {
|
|
2
|
+
baseUrl: string;
|
|
3
|
+
token?: string;
|
|
4
|
+
apiKey?: string;
|
|
5
|
+
timeoutMs?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare class SynapseDXClient {
|
|
8
|
+
private baseUrl;
|
|
9
|
+
private token?;
|
|
10
|
+
private apiKey?;
|
|
11
|
+
private timeoutMs;
|
|
12
|
+
constructor(opts: SynapseDXClientOptions);
|
|
13
|
+
setToken(t: string): void;
|
|
14
|
+
setApiKey(k: string): void;
|
|
15
|
+
private headers;
|
|
16
|
+
private req;
|
|
17
|
+
login(email: string, orgSlug?: string): Promise<{
|
|
18
|
+
accessToken: string;
|
|
19
|
+
refreshToken: string;
|
|
20
|
+
user: any;
|
|
21
|
+
}>;
|
|
22
|
+
refresh(refreshToken: string): Promise<{
|
|
23
|
+
accessToken: string;
|
|
24
|
+
refreshToken: string;
|
|
25
|
+
}>;
|
|
26
|
+
logout(refreshToken: string): Promise<unknown>;
|
|
27
|
+
logoutAll(refreshToken: string): Promise<{
|
|
28
|
+
ok: boolean;
|
|
29
|
+
revoked: number;
|
|
30
|
+
}>;
|
|
31
|
+
verify(): Promise<unknown>;
|
|
32
|
+
me(): Promise<unknown>;
|
|
33
|
+
listOrgs(): Promise<any[]>;
|
|
34
|
+
createOrg(slug: string, displayName: string): Promise<unknown>;
|
|
35
|
+
listAccounts(orgId: string): Promise<unknown>;
|
|
36
|
+
listUsers(orgId: string): Promise<unknown>;
|
|
37
|
+
createUser(orgId: string, email: string, displayName?: string): Promise<unknown>;
|
|
38
|
+
createAccount(orgId: string, slug: string, displayName: string): Promise<unknown>;
|
|
39
|
+
userPolicies(userId: string): Promise<unknown>;
|
|
40
|
+
createRole(accountId: string, slug: string, description?: string): Promise<unknown>;
|
|
41
|
+
listRoles(accountId: string): Promise<unknown>;
|
|
42
|
+
deleteRole(roleId: string): Promise<unknown>;
|
|
43
|
+
listPolicies(accountId: string): Promise<unknown>;
|
|
44
|
+
deletePolicy(policyId: string): Promise<unknown>;
|
|
45
|
+
createPolicy(accountId: string, slug: string, document: any, description?: string): Promise<unknown>;
|
|
46
|
+
authorize(userId: string, action: string, resource: string, context?: Record<string, unknown>): Promise<{
|
|
47
|
+
allowed: boolean;
|
|
48
|
+
}>;
|
|
49
|
+
attachPolicy(roleId: string, policyId: string): Promise<unknown>;
|
|
50
|
+
assignRole(userId: string, roleId: string): Promise<unknown>;
|
|
51
|
+
simulate(documents: any[], action: string, resource: string, context?: Record<string, unknown>): Promise<{
|
|
52
|
+
allowed: boolean;
|
|
53
|
+
}>;
|
|
54
|
+
listAudit(limit?: number, actor?: string): Promise<unknown>;
|
|
55
|
+
createApiKey(accountId: string, name?: string, scopes?: string[]): Promise<{
|
|
56
|
+
apiKey: string;
|
|
57
|
+
}>;
|
|
58
|
+
revokeApiKey(keyId: string): Promise<unknown>;
|
|
59
|
+
verifyApiKey(apiKey: string): Promise<unknown>;
|
|
60
|
+
createServiceAccount(accountId: string, slug: string, description?: string): Promise<unknown>;
|
|
61
|
+
listServiceAccounts(accountId: string): Promise<unknown>;
|
|
62
|
+
serviceAccountToken(saId: string, scopes?: string[]): Promise<{
|
|
63
|
+
accessToken: string;
|
|
64
|
+
}>;
|
|
65
|
+
listWorkflows(): Promise<unknown>;
|
|
66
|
+
getWorkflow(id: string): Promise<unknown>;
|
|
67
|
+
getExecution(id: string): Promise<unknown>;
|
|
68
|
+
listExecutions(query?: string): Promise<unknown>;
|
|
69
|
+
executeWorkflow(id: string, payload?: Record<string, unknown>): Promise<unknown>;
|
|
70
|
+
activateWorkflow(id: string): Promise<unknown>;
|
|
71
|
+
deactivateWorkflow(id: string): Promise<unknown>;
|
|
72
|
+
createWorkflowFromTemplate(template: string, name?: string): Promise<unknown>;
|
|
73
|
+
createWorkflow(name: string, definition: any): Promise<unknown>;
|
|
74
|
+
listTemplates(): Promise<unknown>;
|
|
75
|
+
getTemplate(name: string): Promise<unknown>;
|
|
76
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright 2026 SynapseDX Contributors
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* synapsedx JavaScript SDK — works in Node 18+ and browsers (fetch-based).
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.SynapseDXClient = void 0;
|
|
9
|
+
class SynapseDXClient {
|
|
10
|
+
baseUrl;
|
|
11
|
+
token;
|
|
12
|
+
apiKey;
|
|
13
|
+
timeoutMs;
|
|
14
|
+
constructor(opts) {
|
|
15
|
+
this.baseUrl = opts.baseUrl.replace(/\/$/, '');
|
|
16
|
+
this.token = opts.token;
|
|
17
|
+
this.apiKey = opts.apiKey;
|
|
18
|
+
this.timeoutMs = opts.timeoutMs ?? 15000;
|
|
19
|
+
}
|
|
20
|
+
setToken(t) { this.token = t; }
|
|
21
|
+
setApiKey(k) { this.apiKey = k; }
|
|
22
|
+
headers(extra = {}) {
|
|
23
|
+
const h = { 'Content-Type': 'application/json', ...extra };
|
|
24
|
+
if (this.token)
|
|
25
|
+
h['Authorization'] = `Bearer ${this.token}`;
|
|
26
|
+
if (this.apiKey)
|
|
27
|
+
h['X-API-Key'] = this.apiKey;
|
|
28
|
+
return h;
|
|
29
|
+
}
|
|
30
|
+
async req(path, init) {
|
|
31
|
+
let r;
|
|
32
|
+
const ctl = new AbortController();
|
|
33
|
+
const to = setTimeout(() => ctl.abort(), this.timeoutMs);
|
|
34
|
+
try {
|
|
35
|
+
r = await fetch(`${this.baseUrl}${path}`, {
|
|
36
|
+
...init,
|
|
37
|
+
signal: init?.signal ?? ctl.signal,
|
|
38
|
+
headers: this.headers(init?.headers || {}),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
throw new Error(`synapsedx network ${path}: ${e?.message || e}`);
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
clearTimeout(to);
|
|
46
|
+
}
|
|
47
|
+
if (!r.ok) {
|
|
48
|
+
const text = (await r.text()).slice(0, 2000);
|
|
49
|
+
throw new Error(`synapsedx ${r.status} ${path}: ${text}`);
|
|
50
|
+
}
|
|
51
|
+
const text = await r.text();
|
|
52
|
+
if (!text)
|
|
53
|
+
return null;
|
|
54
|
+
try {
|
|
55
|
+
return JSON.parse(text);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return text;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// ---- auth ----
|
|
62
|
+
login(email, orgSlug = 'acme') {
|
|
63
|
+
return this.req('/v1/auth/login', {
|
|
64
|
+
method: 'POST', body: JSON.stringify({ email, orgSlug }),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
refresh(refreshToken) {
|
|
68
|
+
return this.req('/v1/auth/refresh', {
|
|
69
|
+
method: 'POST', body: JSON.stringify({ refreshToken }),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
logout(refreshToken) {
|
|
73
|
+
return this.req('/v1/auth/logout', { method: 'POST', body: JSON.stringify({ refreshToken }) });
|
|
74
|
+
}
|
|
75
|
+
logoutAll(refreshToken) {
|
|
76
|
+
return this.req('/v1/auth/logout-all', { method: 'POST', body: JSON.stringify({ refreshToken }) });
|
|
77
|
+
}
|
|
78
|
+
verify() { return this.req('/v1/auth/verify'); }
|
|
79
|
+
me() { return this.req('/v1/auth/me'); }
|
|
80
|
+
// ---- orgs/accounts ----
|
|
81
|
+
listOrgs() { return this.req('/v1/orgs'); }
|
|
82
|
+
createOrg(slug, displayName) {
|
|
83
|
+
return this.req('/v1/orgs', { method: 'POST', body: JSON.stringify({ slug, displayName }) });
|
|
84
|
+
}
|
|
85
|
+
listAccounts(orgId) { return this.req(`/v1/orgs/${encodeURIComponent(orgId)}/accounts`); }
|
|
86
|
+
listUsers(orgId) { return this.req(`/v1/orgs/${encodeURIComponent(orgId)}/users`); }
|
|
87
|
+
createUser(orgId, email, displayName) {
|
|
88
|
+
return this.req(`/v1/orgs/${encodeURIComponent(orgId)}/users`, { method: 'POST', body: JSON.stringify({ email, displayName }) });
|
|
89
|
+
}
|
|
90
|
+
createAccount(orgId, slug, displayName) {
|
|
91
|
+
return this.req(`/v1/orgs/${encodeURIComponent(orgId)}/accounts`, { method: 'POST', body: JSON.stringify({ slug, displayName }) });
|
|
92
|
+
}
|
|
93
|
+
userPolicies(userId) { return this.req(`/v1/users/${encodeURIComponent(userId)}/policies`); }
|
|
94
|
+
// ---- iam ----
|
|
95
|
+
createRole(accountId, slug, description = '') {
|
|
96
|
+
return this.req(`/v1/accounts/${encodeURIComponent(accountId)}/roles`, { method: 'POST', body: JSON.stringify({ slug, description }) });
|
|
97
|
+
}
|
|
98
|
+
listRoles(accountId) { return this.req(`/v1/accounts/${encodeURIComponent(accountId)}/roles`); }
|
|
99
|
+
deleteRole(roleId) { return this.req(`/v1/roles/${encodeURIComponent(roleId)}`, { method: 'DELETE' }); }
|
|
100
|
+
listPolicies(accountId) { return this.req(`/v1/accounts/${encodeURIComponent(accountId)}/policies`); }
|
|
101
|
+
deletePolicy(policyId) { return this.req(`/v1/policies/${encodeURIComponent(policyId)}`, { method: 'DELETE' }); }
|
|
102
|
+
createPolicy(accountId, slug, document, description = '') {
|
|
103
|
+
return this.req(`/v1/accounts/${encodeURIComponent(accountId)}/policies`, { method: 'POST', body: JSON.stringify({ slug, document, description }) });
|
|
104
|
+
}
|
|
105
|
+
authorize(userId, action, resource, context) {
|
|
106
|
+
return this.req('/v1/authorize', { method: 'POST', body: JSON.stringify({ userId, action, resource, context }) });
|
|
107
|
+
}
|
|
108
|
+
attachPolicy(roleId, policyId) {
|
|
109
|
+
return this.req(`/v1/roles/${encodeURIComponent(roleId)}/attach`, { method: 'POST', body: JSON.stringify({ policyId }) });
|
|
110
|
+
}
|
|
111
|
+
assignRole(userId, roleId) {
|
|
112
|
+
return this.req(`/v1/users/${encodeURIComponent(userId)}/assign`, { method: 'POST', body: JSON.stringify({ roleId }) });
|
|
113
|
+
}
|
|
114
|
+
simulate(documents, action, resource, context) {
|
|
115
|
+
return this.req('/v1/authorize/dry-run', { method: 'POST', body: JSON.stringify({ documents, action, resource, context }) });
|
|
116
|
+
}
|
|
117
|
+
listAudit(limit = 50, actor) {
|
|
118
|
+
const qs = `?limit=${limit}${actor ? `&actor=${encodeURIComponent(actor)}` : ''}`;
|
|
119
|
+
return this.req(`/v1/audit${qs}`);
|
|
120
|
+
}
|
|
121
|
+
createApiKey(accountId, name = 'default', scopes = ['*']) {
|
|
122
|
+
return this.req(`/v1/accounts/${encodeURIComponent(accountId)}/keys`, { method: 'POST', body: JSON.stringify({ name, scopes }) });
|
|
123
|
+
}
|
|
124
|
+
revokeApiKey(keyId) {
|
|
125
|
+
return this.req(`/v1/keys/${encodeURIComponent(keyId)}`, { method: 'DELETE' });
|
|
126
|
+
}
|
|
127
|
+
verifyApiKey(apiKey) {
|
|
128
|
+
return this.req(`/v1/keys/verify`, { method: 'POST', body: JSON.stringify({ apiKey }) });
|
|
129
|
+
}
|
|
130
|
+
createServiceAccount(accountId, slug, description = '') {
|
|
131
|
+
return this.req(`/v1/accounts/${encodeURIComponent(accountId)}/service-accounts`, { method: 'POST', body: JSON.stringify({ slug, description }) });
|
|
132
|
+
}
|
|
133
|
+
listServiceAccounts(accountId) { return this.req(`/v1/accounts/${encodeURIComponent(accountId)}/service-accounts`); }
|
|
134
|
+
serviceAccountToken(saId, scopes = ['*']) {
|
|
135
|
+
return this.req(`/v1/service-accounts/${encodeURIComponent(saId)}/token`, { method: 'POST', body: JSON.stringify({ scopes }) });
|
|
136
|
+
}
|
|
137
|
+
// ---- workflows (n8n via workflow-service) ----
|
|
138
|
+
listWorkflows() { return this.req('/v1/workflows'); }
|
|
139
|
+
getWorkflow(id) { return this.req(`/v1/workflows/${encodeURIComponent(id)}`); }
|
|
140
|
+
getExecution(id) { return this.req(`/v1/executions/${encodeURIComponent(id)}`); }
|
|
141
|
+
listExecutions(query = '') {
|
|
142
|
+
const q = query ? (query.startsWith('?') ? query : `?${query}`) : '';
|
|
143
|
+
return this.req(`/v1/executions${q}`);
|
|
144
|
+
}
|
|
145
|
+
executeWorkflow(id, payload = {}) {
|
|
146
|
+
return this.req(`/v1/workflows/${encodeURIComponent(id)}/execute`, { method: 'POST', body: JSON.stringify(payload) });
|
|
147
|
+
}
|
|
148
|
+
activateWorkflow(id) { return this.req(`/v1/workflows/${encodeURIComponent(id)}/activate`, { method: 'POST' }); }
|
|
149
|
+
deactivateWorkflow(id) { return this.req(`/v1/workflows/${encodeURIComponent(id)}/deactivate`, { method: 'POST' }); }
|
|
150
|
+
createWorkflowFromTemplate(template, name) {
|
|
151
|
+
return this.req('/v1/workflows', { method: 'POST', body: JSON.stringify({ template, name }) });
|
|
152
|
+
}
|
|
153
|
+
createWorkflow(name, definition) {
|
|
154
|
+
return this.req('/v1/workflows', { method: 'POST', body: JSON.stringify({ name, definition }) });
|
|
155
|
+
}
|
|
156
|
+
listTemplates() { return this.req('/v1/templates'); }
|
|
157
|
+
getTemplate(name) { return this.req(`/v1/templates/${encodeURIComponent(name)}`); }
|
|
158
|
+
}
|
|
159
|
+
exports.SynapseDXClient = SynapseDXClient;
|
package/package.json
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synapsedx/sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "0.3.0-dev.20261012",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"description": "synapsedx SDK for JavaScript/TypeScript",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc -p tsconfig.json",
|
|
10
|
+
"test": "node --test dist/**/*.test.js || echo \"no tests\"",
|
|
11
|
+
"lint": "echo lint-ok"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"typescript": "^5.5.0",
|
|
16
|
+
"@types/node": "^20.14.0"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 SynapseDX Contributors
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* Offline SDK tests — local echo server, no platform needed.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, before, after } from 'node:test';
|
|
7
|
+
import assert from 'node:assert/strict';
|
|
8
|
+
import http from 'node:http';
|
|
9
|
+
import { SynapseDXClient } from './index';
|
|
10
|
+
|
|
11
|
+
let base = '';
|
|
12
|
+
let lastHeaders: Record<string, string | string[] | undefined> = {};
|
|
13
|
+
let lastPath = '';
|
|
14
|
+
let server: http.Server;
|
|
15
|
+
|
|
16
|
+
before(
|
|
17
|
+
() =>
|
|
18
|
+
new Promise<void>((resolve) => {
|
|
19
|
+
server = http
|
|
20
|
+
.createServer((req, res) => {
|
|
21
|
+
lastHeaders = { ...req.headers };
|
|
22
|
+
lastPath = req.url || '';
|
|
23
|
+
res.setHeader('Content-Type', 'application/json');
|
|
24
|
+
res.end(JSON.stringify({ ok: true, path: req.url }));
|
|
25
|
+
})
|
|
26
|
+
.listen(0, '127.0.0.1', () => {
|
|
27
|
+
const a = server.address();
|
|
28
|
+
base = `http://127.0.0.1:${typeof a === 'object' && a ? a.port : 0}`;
|
|
29
|
+
resolve();
|
|
30
|
+
});
|
|
31
|
+
}),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
after(() => new Promise<void>((resolve) => server.close(() => resolve())));
|
|
35
|
+
|
|
36
|
+
describe('SynapseDXClient', () => {
|
|
37
|
+
it('trims trailing slash', () => {
|
|
38
|
+
const c = new SynapseDXClient({ baseUrl: 'http://x:8080/' });
|
|
39
|
+
assert.equal((c as any).baseUrl, 'http://x:8080');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('sends Bearer token', async () => {
|
|
43
|
+
const c = new SynapseDXClient({ baseUrl: base });
|
|
44
|
+
c.setToken('tok123');
|
|
45
|
+
await c.listOrgs();
|
|
46
|
+
assert.equal(lastHeaders['authorization'], 'Bearer tok123');
|
|
47
|
+
assert.equal(lastPath, '/v1/orgs');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('sends X-API-Key', async () => {
|
|
51
|
+
const c = new SynapseDXClient({ baseUrl: base, apiKey: 'k1' });
|
|
52
|
+
await c.listTemplates();
|
|
53
|
+
assert.equal(lastHeaders['x-api-key'], 'k1');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('throws rich error on non-2xx', async () => {
|
|
57
|
+
const bad = new SynapseDXClient({ baseUrl: 'http://127.0.0.1:1' });
|
|
58
|
+
await assert.rejects(() => bad.listOrgs(), /synapsedx/);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('encodes ids in paths', async () => {
|
|
62
|
+
const c = new SynapseDXClient({ baseUrl: base });
|
|
63
|
+
await c.getWorkflow('a/b');
|
|
64
|
+
assert.equal(lastPath, '/v1/workflows/a%2Fb');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('covers new endpoints', async () => {
|
|
68
|
+
const c = new SynapseDXClient({ baseUrl: base });
|
|
69
|
+
await c.userPolicies('u1');
|
|
70
|
+
assert.equal(lastPath, '/v1/users/u1/policies');
|
|
71
|
+
await c.getTemplate('webhook-to-log');
|
|
72
|
+
assert.equal(lastPath, '/v1/templates/webhook-to-log');
|
|
73
|
+
await c.listExecutions('limit=5');
|
|
74
|
+
assert.equal(lastPath, '/v1/executions?limit=5');
|
|
75
|
+
await c.executeWorkflow('w1', {});
|
|
76
|
+
assert.equal(lastPath, '/v1/workflows/w1/execute');
|
|
77
|
+
await c.verifyApiKey('k');
|
|
78
|
+
assert.equal(lastPath, '/v1/keys/verify');
|
|
79
|
+
await c.deactivateWorkflow('w1');
|
|
80
|
+
assert.equal(lastPath, '/v1/workflows/w1/deactivate');
|
|
81
|
+
});
|
|
82
|
+
});
|
package/src/example.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { SynapseDXClient } from './index';
|
|
2
|
+
|
|
3
|
+
async function main() {
|
|
4
|
+
const c = new SynapseDXClient({ baseUrl: process.env.SYNAPSEDX_BASE_URL || 'http://localhost:8080' });
|
|
5
|
+
const login = await c.login('demo@acme.local');
|
|
6
|
+
console.log('login ok:', login.user?.email);
|
|
7
|
+
c.setToken(login.accessToken);
|
|
8
|
+
console.log(await c.listOrgs());
|
|
9
|
+
}
|
|
10
|
+
main().catch((e) => { console.error(e); process.exit(1); });
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 SynapseDX Contributors
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* synapsedx JavaScript SDK — works in Node 18+ and browsers (fetch-based).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface SynapseDXClientOptions {
|
|
8
|
+
baseUrl: string; // gateway, e.g. http://localhost:8080
|
|
9
|
+
token?: string; // Bearer JWT
|
|
10
|
+
apiKey?: string; // X-API-Key
|
|
11
|
+
timeoutMs?: number; // per-request timeout (default 15000)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class SynapseDXClient {
|
|
15
|
+
private baseUrl: string;
|
|
16
|
+
private token?: string;
|
|
17
|
+
private apiKey?: string;
|
|
18
|
+
private timeoutMs: number;
|
|
19
|
+
|
|
20
|
+
constructor(opts: SynapseDXClientOptions) {
|
|
21
|
+
this.baseUrl = opts.baseUrl.replace(/\/$/, '');
|
|
22
|
+
this.token = opts.token;
|
|
23
|
+
this.apiKey = opts.apiKey;
|
|
24
|
+
this.timeoutMs = opts.timeoutMs ?? 15000;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
setToken(t: string) { this.token = t; }
|
|
28
|
+
setApiKey(k: string) { this.apiKey = k; }
|
|
29
|
+
|
|
30
|
+
private headers(extra: Record<string, string> = {}): Record<string, string> {
|
|
31
|
+
const h: Record<string, string> = { 'Content-Type': 'application/json', ...extra };
|
|
32
|
+
if (this.token) h['Authorization'] = `Bearer ${this.token}`;
|
|
33
|
+
if (this.apiKey) h['X-API-Key'] = this.apiKey;
|
|
34
|
+
return h;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private async req<T>(path: string, init?: RequestInit): Promise<T> {
|
|
38
|
+
let r: Response;
|
|
39
|
+
const ctl = new AbortController();
|
|
40
|
+
const to = setTimeout(() => ctl.abort(), this.timeoutMs);
|
|
41
|
+
try {
|
|
42
|
+
r = await fetch(`${this.baseUrl}${path}`, {
|
|
43
|
+
...init,
|
|
44
|
+
signal: init?.signal ?? ctl.signal,
|
|
45
|
+
headers: this.headers((init?.headers as any) || {}),
|
|
46
|
+
});
|
|
47
|
+
} catch (e: any) {
|
|
48
|
+
throw new Error(`synapsedx network ${path}: ${e?.message || e}`);
|
|
49
|
+
} finally {
|
|
50
|
+
clearTimeout(to);
|
|
51
|
+
}
|
|
52
|
+
if (!r.ok) {
|
|
53
|
+
const text = (await r.text()).slice(0, 2000);
|
|
54
|
+
throw new Error(`synapsedx ${r.status} ${path}: ${text}`);
|
|
55
|
+
}
|
|
56
|
+
const text = await r.text();
|
|
57
|
+
if (!text) return null as T;
|
|
58
|
+
try {
|
|
59
|
+
return JSON.parse(text) as T;
|
|
60
|
+
} catch {
|
|
61
|
+
return text as unknown as T;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ---- auth ----
|
|
66
|
+
login(email: string, orgSlug = 'acme') {
|
|
67
|
+
return this.req<{ accessToken: string; refreshToken: string; user: any }>('/v1/auth/login', {
|
|
68
|
+
method: 'POST', body: JSON.stringify({ email, orgSlug }),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
refresh(refreshToken: string) {
|
|
72
|
+
return this.req<{ accessToken: string; refreshToken: string }>('/v1/auth/refresh', {
|
|
73
|
+
method: 'POST', body: JSON.stringify({ refreshToken }),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
logout(refreshToken: string) {
|
|
77
|
+
return this.req('/v1/auth/logout', { method: 'POST', body: JSON.stringify({ refreshToken }) });
|
|
78
|
+
}
|
|
79
|
+
logoutAll(refreshToken: string) {
|
|
80
|
+
return this.req<{ ok: boolean; revoked: number }>('/v1/auth/logout-all', { method: 'POST', body: JSON.stringify({ refreshToken }) });
|
|
81
|
+
}
|
|
82
|
+
verify() { return this.req('/v1/auth/verify'); }
|
|
83
|
+
me() { return this.req('/v1/auth/me'); }
|
|
84
|
+
|
|
85
|
+
// ---- orgs/accounts ----
|
|
86
|
+
listOrgs(): Promise<any[]> { return this.req('/v1/orgs'); }
|
|
87
|
+
createOrg(slug: string, displayName: string) {
|
|
88
|
+
return this.req('/v1/orgs', { method: 'POST', body: JSON.stringify({ slug, displayName }) });
|
|
89
|
+
}
|
|
90
|
+
listAccounts(orgId: string) { return this.req(`/v1/orgs/${encodeURIComponent(orgId)}/accounts`); }
|
|
91
|
+
listUsers(orgId: string) { return this.req(`/v1/orgs/${encodeURIComponent(orgId)}/users`); }
|
|
92
|
+
createUser(orgId: string, email: string, displayName?: string) {
|
|
93
|
+
return this.req(`/v1/orgs/${encodeURIComponent(orgId)}/users`, { method: 'POST', body: JSON.stringify({ email, displayName }) });
|
|
94
|
+
}
|
|
95
|
+
createAccount(orgId: string, slug: string, displayName: string) {
|
|
96
|
+
return this.req(`/v1/orgs/${encodeURIComponent(orgId)}/accounts`, { method: 'POST', body: JSON.stringify({ slug, displayName }) });
|
|
97
|
+
}
|
|
98
|
+
userPolicies(userId: string) { return this.req(`/v1/users/${encodeURIComponent(userId)}/policies`); }
|
|
99
|
+
|
|
100
|
+
// ---- iam ----
|
|
101
|
+
createRole(accountId: string, slug: string, description = '') {
|
|
102
|
+
return this.req(`/v1/accounts/${encodeURIComponent(accountId)}/roles`, { method: 'POST', body: JSON.stringify({ slug, description }) });
|
|
103
|
+
}
|
|
104
|
+
listRoles(accountId: string) { return this.req(`/v1/accounts/${encodeURIComponent(accountId)}/roles`); }
|
|
105
|
+
deleteRole(roleId: string) { return this.req(`/v1/roles/${encodeURIComponent(roleId)}`, { method: 'DELETE' }); }
|
|
106
|
+
listPolicies(accountId: string) { return this.req(`/v1/accounts/${encodeURIComponent(accountId)}/policies`); }
|
|
107
|
+
deletePolicy(policyId: string) { return this.req(`/v1/policies/${encodeURIComponent(policyId)}`, { method: 'DELETE' }); }
|
|
108
|
+
createPolicy(accountId: string, slug: string, document: any, description = '') {
|
|
109
|
+
return this.req(`/v1/accounts/${encodeURIComponent(accountId)}/policies`, { method: 'POST', body: JSON.stringify({ slug, document, description }) });
|
|
110
|
+
}
|
|
111
|
+
authorize(userId: string, action: string, resource: string, context?: Record<string, unknown>) {
|
|
112
|
+
return this.req<{ allowed: boolean }>('/v1/authorize', { method: 'POST', body: JSON.stringify({ userId, action, resource, context }) });
|
|
113
|
+
}
|
|
114
|
+
attachPolicy(roleId: string, policyId: string) {
|
|
115
|
+
return this.req(`/v1/roles/${encodeURIComponent(roleId)}/attach`, { method: 'POST', body: JSON.stringify({ policyId }) });
|
|
116
|
+
}
|
|
117
|
+
assignRole(userId: string, roleId: string) {
|
|
118
|
+
return this.req(`/v1/users/${encodeURIComponent(userId)}/assign`, { method: 'POST', body: JSON.stringify({ roleId }) });
|
|
119
|
+
}
|
|
120
|
+
simulate(documents: any[], action: string, resource: string, context?: Record<string, unknown>) {
|
|
121
|
+
return this.req<{ allowed: boolean }>('/v1/authorize/dry-run', { method: 'POST', body: JSON.stringify({ documents, action, resource, context }) });
|
|
122
|
+
}
|
|
123
|
+
listAudit(limit = 50, actor?: string) {
|
|
124
|
+
const qs = `?limit=${limit}${actor ? `&actor=${encodeURIComponent(actor)}` : ''}`;
|
|
125
|
+
return this.req(`/v1/audit${qs}`);
|
|
126
|
+
}
|
|
127
|
+
createApiKey(accountId: string, name = 'default', scopes: string[] = ['*']) {
|
|
128
|
+
return this.req<{ apiKey: string }>(`/v1/accounts/${encodeURIComponent(accountId)}/keys`, { method: 'POST', body: JSON.stringify({ name, scopes }) });
|
|
129
|
+
}
|
|
130
|
+
revokeApiKey(keyId: string) {
|
|
131
|
+
return this.req(`/v1/keys/${encodeURIComponent(keyId)}`, { method: 'DELETE' });
|
|
132
|
+
}
|
|
133
|
+
verifyApiKey(apiKey: string) {
|
|
134
|
+
return this.req(`/v1/keys/verify`, { method: 'POST', body: JSON.stringify({ apiKey }) });
|
|
135
|
+
}
|
|
136
|
+
createServiceAccount(accountId: string, slug: string, description = '') {
|
|
137
|
+
return this.req(`/v1/accounts/${encodeURIComponent(accountId)}/service-accounts`, { method: 'POST', body: JSON.stringify({ slug, description }) });
|
|
138
|
+
}
|
|
139
|
+
listServiceAccounts(accountId: string) { return this.req(`/v1/accounts/${encodeURIComponent(accountId)}/service-accounts`); }
|
|
140
|
+
serviceAccountToken(saId: string, scopes: string[] = ['*']) {
|
|
141
|
+
return this.req<{ accessToken: string }>(`/v1/service-accounts/${encodeURIComponent(saId)}/token`, { method: 'POST', body: JSON.stringify({ scopes }) });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ---- workflows (n8n via workflow-service) ----
|
|
145
|
+
listWorkflows() { return this.req('/v1/workflows'); }
|
|
146
|
+
getWorkflow(id: string) { return this.req(`/v1/workflows/${encodeURIComponent(id)}`); }
|
|
147
|
+
getExecution(id: string) { return this.req(`/v1/executions/${encodeURIComponent(id)}`); }
|
|
148
|
+
listExecutions(query = '') {
|
|
149
|
+
const q = query ? (query.startsWith('?') ? query : `?${query}`) : '';
|
|
150
|
+
return this.req(`/v1/executions${q}`);
|
|
151
|
+
}
|
|
152
|
+
executeWorkflow(id: string, payload: Record<string, unknown> = {}) {
|
|
153
|
+
return this.req(`/v1/workflows/${encodeURIComponent(id)}/execute`, { method: 'POST', body: JSON.stringify(payload) });
|
|
154
|
+
}
|
|
155
|
+
activateWorkflow(id: string) { return this.req(`/v1/workflows/${encodeURIComponent(id)}/activate`, { method: 'POST' }); }
|
|
156
|
+
deactivateWorkflow(id: string) { return this.req(`/v1/workflows/${encodeURIComponent(id)}/deactivate`, { method: 'POST' }); }
|
|
157
|
+
createWorkflowFromTemplate(template: string, name?: string) {
|
|
158
|
+
return this.req('/v1/workflows', { method: 'POST', body: JSON.stringify({ template, name }) });
|
|
159
|
+
}
|
|
160
|
+
createWorkflow(name: string, definition: any) {
|
|
161
|
+
return this.req('/v1/workflows', { method: 'POST', body: JSON.stringify({ name, definition }) });
|
|
162
|
+
}
|
|
163
|
+
listTemplates() { return this.req('/v1/templates'); }
|
|
164
|
+
getTemplate(name: string) { return this.req(`/v1/templates/${encodeURIComponent(name)}`); }
|
|
165
|
+
}
|
package/tsconfig.json
ADDED