@clidey/whodb-sdk 0.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/README.md +81 -0
- package/dist/auth.d.ts +26 -0
- package/dist/auth.js +65 -0
- package/dist/client.d.ts +51 -0
- package/dist/client.js +157 -0
- package/dist/config.d.ts +37 -0
- package/dist/config.js +39 -0
- package/dist/dataset.d.ts +26 -0
- package/dist/dataset.js +45 -0
- package/dist/errors.d.ts +43 -0
- package/dist/errors.js +59 -0
- package/dist/files.d.ts +25 -0
- package/dist/files.js +53 -0
- package/dist/generated/hydration.d.ts +3 -0
- package/dist/generated/hydration.js +32 -0
- package/dist/generated/manifest.d.ts +12 -0
- package/dist/generated/manifest.js +80 -0
- package/dist/generated/operations.d.ts +196 -0
- package/dist/generated/operations.js +145 -0
- package/dist/generated/surface.d.ts +190 -0
- package/dist/generated/surface.js +191 -0
- package/dist/generated/types.d.ts +302 -0
- package/dist/generated/types.js +2 -0
- package/dist/hydrate.d.ts +18 -0
- package/dist/hydrate.js +83 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +9 -0
- package/dist/manifest-check.d.ts +15 -0
- package/dist/manifest-check.js +43 -0
- package/dist/ontology.d.ts +73 -0
- package/dist/ontology.js +240 -0
- package/dist/pagination.d.ts +21 -0
- package/dist/pagination.js +33 -0
- package/dist/source.d.ts +31 -0
- package/dist/source.js +63 -0
- package/dist/transport-http.d.ts +22 -0
- package/dist/transport-http.js +60 -0
- package/dist/transport-ipc.d.ts +31 -0
- package/dist/transport-ipc.js +183 -0
- package/dist/transport.d.ts +9 -0
- package/dist/transport.js +1 -0
- package/dist/version.d.ts +5 -0
- package/dist/version.js +5 -0
- package/package.json +34 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { mapGraphQLErrors, AuthError, PlatformError } from './errors.js';
|
|
2
|
+
import { SDK_VERSION } from './version.js';
|
|
3
|
+
const RETRYABLE_STATUS = new Set([502, 503, 504]);
|
|
4
|
+
/**
|
|
5
|
+
* HttpTransport posts operations to `<host>/api/query` with bearer
|
|
6
|
+
* credentials and workspace headers. It retries once on a transient 5xx and
|
|
7
|
+
* once after refreshing credentials on a 401.
|
|
8
|
+
*/
|
|
9
|
+
export class HttpTransport {
|
|
10
|
+
options;
|
|
11
|
+
constructor(options) {
|
|
12
|
+
this.options = options;
|
|
13
|
+
}
|
|
14
|
+
/** Sets the workspace scope headers used on subsequent requests. */
|
|
15
|
+
setWorkspace(orgId, projectId) {
|
|
16
|
+
this.options.orgId = orgId;
|
|
17
|
+
this.options.projectId = projectId;
|
|
18
|
+
}
|
|
19
|
+
async execute(operationName, document, variables) {
|
|
20
|
+
let response = await this.post(document, variables);
|
|
21
|
+
if (response.status === 401 && this.options.credentials.refresh) {
|
|
22
|
+
await this.options.credentials.refresh();
|
|
23
|
+
response = await this.post(document, variables);
|
|
24
|
+
}
|
|
25
|
+
else if (RETRYABLE_STATUS.has(response.status)) {
|
|
26
|
+
response = await this.post(document, variables);
|
|
27
|
+
}
|
|
28
|
+
if (response.status === 401) {
|
|
29
|
+
throw new AuthError('authentication failed — check your API key or run: whodb login');
|
|
30
|
+
}
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
throw new PlatformError(`platform request failed with HTTP ${response.status}`, `HTTP_${response.status}`);
|
|
33
|
+
}
|
|
34
|
+
const payload = (await response.json());
|
|
35
|
+
if (payload.errors && payload.errors.length > 0) {
|
|
36
|
+
throw mapGraphQLErrors(payload.errors);
|
|
37
|
+
}
|
|
38
|
+
if (!payload.data) {
|
|
39
|
+
throw new PlatformError(`empty response for ${operationName}`, 'EMPTY_RESPONSE');
|
|
40
|
+
}
|
|
41
|
+
return payload.data;
|
|
42
|
+
}
|
|
43
|
+
async post(document, variables) {
|
|
44
|
+
const token = await this.options.credentials.token();
|
|
45
|
+
const headers = {
|
|
46
|
+
'Content-Type': 'application/json',
|
|
47
|
+
Authorization: `Bearer ${token}`,
|
|
48
|
+
'User-Agent': `clidey-whodb-ts/${SDK_VERSION}`,
|
|
49
|
+
};
|
|
50
|
+
if (this.options.orgId)
|
|
51
|
+
headers['X-Whodb-Org-Id'] = this.options.orgId;
|
|
52
|
+
if (this.options.projectId)
|
|
53
|
+
headers['X-Whodb-Project-Id'] = this.options.projectId;
|
|
54
|
+
return fetch(`${this.options.host.replace(/\/$/, '')}/api/query`, {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
headers,
|
|
57
|
+
body: JSON.stringify({ query: document, variables }),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Transport } from './transport.js';
|
|
2
|
+
/**
|
|
3
|
+
* IpcTransport runs the same facades inside the WhoDB Functions runtime,
|
|
4
|
+
* over the in-container IPC server (unix socket in Docker, TCP in K8s).
|
|
5
|
+
*
|
|
6
|
+
* Two impedance differences are absorbed here:
|
|
7
|
+
* - GraphQL operations address entities by ID; IPC endpoints address them by
|
|
8
|
+
* apiName. IDs resolve via a cached /entities call.
|
|
9
|
+
* - GraphQL write mutations carry RecordInput pairs; IPC takes plain objects.
|
|
10
|
+
*
|
|
11
|
+
* Operations outside the ontology surface (datasets, sources, files,
|
|
12
|
+
* workspace) throw TransportCapabilityError in v1.
|
|
13
|
+
*/
|
|
14
|
+
export declare class IpcTransport implements Transport {
|
|
15
|
+
private readonly address;
|
|
16
|
+
private readonly jobId;
|
|
17
|
+
private readonly token;
|
|
18
|
+
private entitiesCache;
|
|
19
|
+
constructor(options?: {
|
|
20
|
+
address?: string;
|
|
21
|
+
jobId?: string;
|
|
22
|
+
token?: string;
|
|
23
|
+
});
|
|
24
|
+
execute(operationName: string, _document: string, variables: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
25
|
+
private post;
|
|
26
|
+
private entities;
|
|
27
|
+
private entityName;
|
|
28
|
+
private entityPk;
|
|
29
|
+
private recordInputsToData;
|
|
30
|
+
private readonly handlers;
|
|
31
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { request as httpRequest } from 'node:http';
|
|
2
|
+
import { NotFoundError, PlatformError, TransportCapabilityError } from './errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* IpcTransport runs the same facades inside the WhoDB Functions runtime,
|
|
5
|
+
* over the in-container IPC server (unix socket in Docker, TCP in K8s).
|
|
6
|
+
*
|
|
7
|
+
* Two impedance differences are absorbed here:
|
|
8
|
+
* - GraphQL operations address entities by ID; IPC endpoints address them by
|
|
9
|
+
* apiName. IDs resolve via a cached /entities call.
|
|
10
|
+
* - GraphQL write mutations carry RecordInput pairs; IPC takes plain objects.
|
|
11
|
+
*
|
|
12
|
+
* Operations outside the ontology surface (datasets, sources, files,
|
|
13
|
+
* workspace) throw TransportCapabilityError in v1.
|
|
14
|
+
*/
|
|
15
|
+
export class IpcTransport {
|
|
16
|
+
address;
|
|
17
|
+
jobId;
|
|
18
|
+
token;
|
|
19
|
+
entitiesCache = null;
|
|
20
|
+
constructor(options = {}) {
|
|
21
|
+
this.address = options.address ?? process.env.WHODB_IPC_ADDRESS ?? '';
|
|
22
|
+
this.jobId = options.jobId ?? process.env.WHODB_JOB_ID ?? '';
|
|
23
|
+
this.token = options.token ?? process.env.WHODB_IPC_TOKEN ?? '';
|
|
24
|
+
}
|
|
25
|
+
async execute(operationName, _document, variables) {
|
|
26
|
+
const handler = this.handlers[operationName];
|
|
27
|
+
if (!handler) {
|
|
28
|
+
throw new TransportCapabilityError(`${operationName} is not available inside the function runtime in v1`);
|
|
29
|
+
}
|
|
30
|
+
return { [operationName]: await handler(variables) };
|
|
31
|
+
}
|
|
32
|
+
post(path, body) {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
const payload = JSON.stringify(body ?? {});
|
|
35
|
+
const isSocket = this.address.startsWith('/');
|
|
36
|
+
const options = {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
path,
|
|
39
|
+
headers: {
|
|
40
|
+
'Content-Type': 'application/json',
|
|
41
|
+
'Content-Length': Buffer.byteLength(payload),
|
|
42
|
+
'X-Job-ID': this.jobId,
|
|
43
|
+
Authorization: this.token,
|
|
44
|
+
},
|
|
45
|
+
...(isSocket
|
|
46
|
+
? { socketPath: this.address }
|
|
47
|
+
: {
|
|
48
|
+
host: this.address.split(':')[0],
|
|
49
|
+
port: Number(this.address.split(':')[1] ?? 80),
|
|
50
|
+
}),
|
|
51
|
+
};
|
|
52
|
+
const req = httpRequest(options, (res) => {
|
|
53
|
+
const chunks = [];
|
|
54
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
55
|
+
res.on('end', () => {
|
|
56
|
+
const text = Buffer.concat(chunks).toString('utf8');
|
|
57
|
+
if ((res.statusCode ?? 500) >= 400) {
|
|
58
|
+
reject(new PlatformError(`IPC request ${path} failed with HTTP ${res.statusCode}`, `IPC_${res.statusCode}`));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
resolve(text ? JSON.parse(text) : null);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
reject(new PlatformError(`IPC request ${path} returned invalid JSON`, 'IPC_INVALID_JSON'));
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
req.on('error', reject);
|
|
70
|
+
req.end(payload);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
async entities() {
|
|
74
|
+
this.entitiesCache ??= (await this.post('/entities', {})) ?? [];
|
|
75
|
+
return this.entitiesCache;
|
|
76
|
+
}
|
|
77
|
+
async entityName(entityId) {
|
|
78
|
+
const entity = (await this.entities()).find(e => e.id === entityId);
|
|
79
|
+
if (!entity)
|
|
80
|
+
throw new NotFoundError(`ontology entity ${entityId} not found in this function's scope`);
|
|
81
|
+
return String(entity.apiName ?? '');
|
|
82
|
+
}
|
|
83
|
+
async entityPk(entityId) {
|
|
84
|
+
const entity = (await this.entities()).find(e => e.id === entityId);
|
|
85
|
+
return String(entity?.primaryKey ?? '');
|
|
86
|
+
}
|
|
87
|
+
recordInputsToData(values) {
|
|
88
|
+
const data = {};
|
|
89
|
+
for (const record of values ?? []) {
|
|
90
|
+
data[record.Key] = record.Value;
|
|
91
|
+
}
|
|
92
|
+
return data;
|
|
93
|
+
}
|
|
94
|
+
handlers = {
|
|
95
|
+
OntologyEntities: async () => this.entities(),
|
|
96
|
+
OntologyQuery: async (variables) => {
|
|
97
|
+
const input = { ...(variables.input ?? {}) };
|
|
98
|
+
const body = {};
|
|
99
|
+
for (const [key, value] of Object.entries(input)) {
|
|
100
|
+
if (value !== null && value !== undefined)
|
|
101
|
+
body[key] = value;
|
|
102
|
+
}
|
|
103
|
+
const whereJson = body.whereJson;
|
|
104
|
+
delete body.whereJson;
|
|
105
|
+
if (whereJson)
|
|
106
|
+
body.where = JSON.parse(whereJson);
|
|
107
|
+
return this.post('/query', body);
|
|
108
|
+
},
|
|
109
|
+
OntologyDescribe: async (variables) => this.post('/describe', variables.input ?? {}),
|
|
110
|
+
OntologyStats: async (variables) => this.post('/stats', {
|
|
111
|
+
entity: await this.entityName(String(variables.id)),
|
|
112
|
+
property: variables.property,
|
|
113
|
+
where: variables.where,
|
|
114
|
+
}),
|
|
115
|
+
OntologySimilar: async (variables) => {
|
|
116
|
+
const input = variables.input ?? {};
|
|
117
|
+
return this.post('/similar', {
|
|
118
|
+
entity: await this.entityName(String(input.entityId)),
|
|
119
|
+
rowId: input.rowId,
|
|
120
|
+
topK: input.topK,
|
|
121
|
+
properties: input.properties,
|
|
122
|
+
where: input.where,
|
|
123
|
+
});
|
|
124
|
+
},
|
|
125
|
+
OntologyAggregate: async (variables) => this.post('/query', {
|
|
126
|
+
entity: await this.entityName(String(variables.id)),
|
|
127
|
+
where: variables.where,
|
|
128
|
+
groupBy: variables.groupBy,
|
|
129
|
+
metrics: variables.metrics,
|
|
130
|
+
sort: variables.sort,
|
|
131
|
+
pageSize: variables.pageSize,
|
|
132
|
+
offset: variables.pageOffset,
|
|
133
|
+
}),
|
|
134
|
+
OntologyAddRow: async (variables) => {
|
|
135
|
+
await this.post('/create', {
|
|
136
|
+
entity: await this.entityName(String(variables.entityId)),
|
|
137
|
+
data: this.recordInputsToData(variables.values),
|
|
138
|
+
});
|
|
139
|
+
return { Status: true };
|
|
140
|
+
},
|
|
141
|
+
OntologyAddRows: async (variables) => {
|
|
142
|
+
const ids = (await this.post('/create_many', {
|
|
143
|
+
entity: await this.entityName(String(variables.entityId)),
|
|
144
|
+
rows: (variables.rows ?? []).map(row => this.recordInputsToData(row.values)),
|
|
145
|
+
idempotencyKey: variables.idempotencyKey,
|
|
146
|
+
})) ?? [];
|
|
147
|
+
return { inserted: ids.length, ids };
|
|
148
|
+
},
|
|
149
|
+
OntologyUpdateRow: async (variables) => {
|
|
150
|
+
const entityId = String(variables.entityId);
|
|
151
|
+
const data = this.recordInputsToData(variables.values);
|
|
152
|
+
const pkKey = await this.entityPk(entityId);
|
|
153
|
+
const pk = pkKey ? String(data[pkKey] ?? '') : '';
|
|
154
|
+
if (pkKey)
|
|
155
|
+
delete data[pkKey];
|
|
156
|
+
await this.post('/update', { entity: await this.entityName(entityId), pk, data });
|
|
157
|
+
return { Status: true };
|
|
158
|
+
},
|
|
159
|
+
OntologyDeleteRow: async (variables) => {
|
|
160
|
+
const entityId = String(variables.entityId);
|
|
161
|
+
const data = this.recordInputsToData(variables.values);
|
|
162
|
+
const pkKey = await this.entityPk(entityId);
|
|
163
|
+
const pk = pkKey ? String(data[pkKey] ?? '') : String(Object.values(data)[0] ?? '');
|
|
164
|
+
await this.post('/delete', { entity: await this.entityName(entityId), pk });
|
|
165
|
+
return { Status: true };
|
|
166
|
+
},
|
|
167
|
+
OntologyFollowLink: async (variables) => this.post('/follow_link', {
|
|
168
|
+
entity: await this.entityName(String(variables.entityId)),
|
|
169
|
+
pk: variables.pk,
|
|
170
|
+
link: variables.linkApiName,
|
|
171
|
+
pageSize: variables.pageSize,
|
|
172
|
+
offset: variables.pageOffset,
|
|
173
|
+
}),
|
|
174
|
+
OntologyFollowIncomingLink: async (variables) => this.post('/follow_incoming_link', {
|
|
175
|
+
entity: await this.entityName(String(variables.entityId)),
|
|
176
|
+
pk: variables.pk,
|
|
177
|
+
sourceEntity: await this.entityName(String(variables.sourceEntityId)),
|
|
178
|
+
link: variables.linkApiName,
|
|
179
|
+
pageSize: variables.pageSize,
|
|
180
|
+
offset: variables.pageOffset,
|
|
181
|
+
}),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport executes one platform operation and returns the GraphQL `data`
|
|
3
|
+
* object. Implementations: HttpTransport (default, GraphQL over HTTP) and
|
|
4
|
+
* IpcTransport (inside the WhoDB Functions runtime). The generated core and
|
|
5
|
+
* the facade never speak HTTP directly — everything routes through this seam.
|
|
6
|
+
*/
|
|
7
|
+
export interface Transport {
|
|
8
|
+
execute(operationName: string, document: string, variables: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
9
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/version.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@clidey/whodb-sdk",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "Official WhoDB platform SDK — ontology, datasets, and sources as in-code function APIs",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=20"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -p tsconfig.json",
|
|
23
|
+
"test": "node --test 'test/*.test.mjs'"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/clidey/whodb.git",
|
|
28
|
+
"directory": "sdk/packages/typescript"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "25.5.2",
|
|
32
|
+
"typescript": "6.0.3"
|
|
33
|
+
}
|
|
34
|
+
}
|