@eidetic-labs/stigmem-ts 0.9.0-alpha.1
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/dist/client.d.ts +48 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +242 -0
- package/dist/client.js.map +1 -0
- package/dist/generated.d.ts +5049 -0
- package/dist/generated.d.ts.map +1 -0
- package/dist/generated.js +7 -0
- package/dist/generated.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +215 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +22 -0
- package/dist/types.js.map +1 -0
- package/package.json +51 -0
- package/src/client.ts +317 -0
- package/src/generated.ts +5049 -0
- package/src/index.ts +72 -0
- package/src/types.ts +269 -0
package/src/client.ts
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stigmem TypeScript client SDK.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
AssertOptions,
|
|
7
|
+
CardOptions,
|
|
8
|
+
Conflict,
|
|
9
|
+
ConflictListOptions,
|
|
10
|
+
ConflictPage,
|
|
11
|
+
ConflictResolution,
|
|
12
|
+
Fact,
|
|
13
|
+
FactPage,
|
|
14
|
+
FactScope,
|
|
15
|
+
FactValue,
|
|
16
|
+
LintOptions,
|
|
17
|
+
LintResult,
|
|
18
|
+
MemoryCard,
|
|
19
|
+
NodeInfo,
|
|
20
|
+
Peer,
|
|
21
|
+
QueryOptions,
|
|
22
|
+
RecallOptions,
|
|
23
|
+
RecallResponse,
|
|
24
|
+
ResolveOptions,
|
|
25
|
+
SubscribeOptions,
|
|
26
|
+
} from "./types.js";
|
|
27
|
+
import { sv } from "./types.js";
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Errors
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
export class StigmemError extends Error {
|
|
34
|
+
constructor(message: string) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.name = "StigmemError";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class StigmemHTTPError extends StigmemError {
|
|
41
|
+
constructor(
|
|
42
|
+
public readonly statusCode: number,
|
|
43
|
+
public readonly detail: string,
|
|
44
|
+
) {
|
|
45
|
+
super(`HTTP ${statusCode}: ${detail}`);
|
|
46
|
+
this.name = "StigmemHTTPError";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export class StigmemAuthError extends StigmemHTTPError {
|
|
51
|
+
constructor(statusCode: number, detail: string) {
|
|
52
|
+
super(statusCode, detail);
|
|
53
|
+
this.name = "StigmemAuthError";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class StigmemNotFoundError extends StigmemHTTPError {
|
|
58
|
+
constructor(detail: string) {
|
|
59
|
+
super(404, detail);
|
|
60
|
+
this.name = "StigmemNotFoundError";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export class StigmemConflictError extends StigmemHTTPError {
|
|
65
|
+
constructor(detail: string) {
|
|
66
|
+
super(409, detail);
|
|
67
|
+
this.name = "StigmemConflictError";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// HTTP helpers
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
interface ClientOptions {
|
|
76
|
+
url: string;
|
|
77
|
+
apiKey?: string;
|
|
78
|
+
timeoutMs?: number;
|
|
79
|
+
fetch?: typeof fetch;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function raiseForStatus(res: Response): Promise<void> {
|
|
83
|
+
if (res.ok) return;
|
|
84
|
+
let detail: string;
|
|
85
|
+
try {
|
|
86
|
+
const body = await res.json() as { detail?: string };
|
|
87
|
+
detail = body.detail ?? res.statusText;
|
|
88
|
+
} catch {
|
|
89
|
+
detail = res.statusText;
|
|
90
|
+
}
|
|
91
|
+
if (res.status === 401 || res.status === 403) {
|
|
92
|
+
throw new StigmemAuthError(res.status, detail);
|
|
93
|
+
}
|
|
94
|
+
if (res.status === 404) {
|
|
95
|
+
throw new StigmemNotFoundError(detail);
|
|
96
|
+
}
|
|
97
|
+
if (res.status === 409) {
|
|
98
|
+
throw new StigmemConflictError(detail);
|
|
99
|
+
}
|
|
100
|
+
throw new StigmemHTTPError(res.status, detail);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// StigmemClient
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
export class StigmemClient {
|
|
108
|
+
private readonly baseUrl: string;
|
|
109
|
+
private readonly headers: Record<string, string>;
|
|
110
|
+
private readonly fetchFn: typeof fetch;
|
|
111
|
+
|
|
112
|
+
constructor(opts: ClientOptions) {
|
|
113
|
+
this.baseUrl = opts.url.replace(/\/$/, "");
|
|
114
|
+
this.headers = { "Accept": "application/json", "Content-Type": "application/json" };
|
|
115
|
+
if (opts.apiKey) {
|
|
116
|
+
this.headers["Authorization"] = `Bearer ${opts.apiKey}`;
|
|
117
|
+
}
|
|
118
|
+
this.fetchFn = opts.fetch ?? globalThis.fetch;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private async req<T>(
|
|
122
|
+
method: string,
|
|
123
|
+
path: string,
|
|
124
|
+
body?: unknown,
|
|
125
|
+
params?: Record<string, string | number | boolean | undefined>,
|
|
126
|
+
): Promise<T> {
|
|
127
|
+
let url = `${this.baseUrl}${path}`;
|
|
128
|
+
if (params) {
|
|
129
|
+
const qs = Object.entries(params)
|
|
130
|
+
.filter(([, v]) => v !== undefined)
|
|
131
|
+
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)
|
|
132
|
+
.join("&");
|
|
133
|
+
if (qs) url += `?${qs}`;
|
|
134
|
+
}
|
|
135
|
+
const res = await this.fetchFn(url, {
|
|
136
|
+
method,
|
|
137
|
+
headers: this.headers,
|
|
138
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
139
|
+
});
|
|
140
|
+
await raiseForStatus(res);
|
|
141
|
+
return res.json() as Promise<T>;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ------------------------------------------------------------------
|
|
145
|
+
// Node info
|
|
146
|
+
// ------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
async nodeInfo(): Promise<NodeInfo> {
|
|
149
|
+
return this.req<NodeInfo>("GET", "/.well-known/stigmem");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ------------------------------------------------------------------
|
|
153
|
+
// Facts
|
|
154
|
+
// ------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
async assertFact(
|
|
157
|
+
entity: string,
|
|
158
|
+
relation: string,
|
|
159
|
+
value: FactValue,
|
|
160
|
+
source: string,
|
|
161
|
+
opts: AssertOptions = {},
|
|
162
|
+
): Promise<Fact> {
|
|
163
|
+
const body = {
|
|
164
|
+
entity,
|
|
165
|
+
relation,
|
|
166
|
+
value,
|
|
167
|
+
source,
|
|
168
|
+
confidence: opts.confidence ?? 1.0,
|
|
169
|
+
scope: opts.scope ?? "company",
|
|
170
|
+
...(opts.valid_until ? { valid_until: opts.valid_until } : {}),
|
|
171
|
+
};
|
|
172
|
+
return this.req<Fact>("POST", "/v1/facts", body);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async retract(
|
|
176
|
+
entity: string,
|
|
177
|
+
relation: string,
|
|
178
|
+
scope: FactScope,
|
|
179
|
+
source: string,
|
|
180
|
+
value?: FactValue,
|
|
181
|
+
): Promise<Fact> {
|
|
182
|
+
return this.assertFact(
|
|
183
|
+
entity,
|
|
184
|
+
relation,
|
|
185
|
+
value ?? sv("retracted"),
|
|
186
|
+
source,
|
|
187
|
+
{ confidence: 0.0, scope },
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async getFact(factId: string): Promise<Fact> {
|
|
192
|
+
return this.req<Fact>("GET", `/v1/facts/${factId}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async query(opts: QueryOptions = {}): Promise<FactPage> {
|
|
196
|
+
return this.req<FactPage>("GET", "/v1/facts", undefined, {
|
|
197
|
+
entity: opts.entity,
|
|
198
|
+
relation: opts.relation,
|
|
199
|
+
source: opts.source,
|
|
200
|
+
scope: opts.scope,
|
|
201
|
+
min_confidence: opts.min_confidence,
|
|
202
|
+
include_contradicted: opts.include_contradicted,
|
|
203
|
+
include_expired: opts.include_expired,
|
|
204
|
+
cursor: opts.cursor,
|
|
205
|
+
after: opts.after,
|
|
206
|
+
limit: opts.limit ?? 50,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ------------------------------------------------------------------
|
|
211
|
+
// Conflicts
|
|
212
|
+
// ------------------------------------------------------------------
|
|
213
|
+
|
|
214
|
+
async listConflicts(opts: ConflictListOptions = {}): Promise<ConflictPage> {
|
|
215
|
+
return this.req<ConflictPage>("GET", "/v1/conflicts", undefined, {
|
|
216
|
+
status: opts.status ?? "unresolved",
|
|
217
|
+
cursor: opts.cursor,
|
|
218
|
+
limit: opts.limit ?? 50,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async resolveConflict(
|
|
223
|
+
conflictId: string,
|
|
224
|
+
opts: ResolveOptions = {},
|
|
225
|
+
): Promise<ConflictResolution> {
|
|
226
|
+
const body: Record<string, unknown> = {
|
|
227
|
+
resolution_note: opts.resolution_note ?? "",
|
|
228
|
+
};
|
|
229
|
+
if (opts.winning_fact_id !== undefined) {
|
|
230
|
+
body["winning_fact_id"] = opts.winning_fact_id;
|
|
231
|
+
}
|
|
232
|
+
if (opts.new_value !== undefined) {
|
|
233
|
+
body["new_value"] = opts.new_value;
|
|
234
|
+
}
|
|
235
|
+
return this.req<ConflictResolution>("POST", `/v1/conflicts/${conflictId}/resolve`, body);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ------------------------------------------------------------------
|
|
239
|
+
// Lint — v0.7 (spec §14)
|
|
240
|
+
// ------------------------------------------------------------------
|
|
241
|
+
|
|
242
|
+
async lint(scope: FactScope, opts: LintOptions = {}): Promise<LintResult> {
|
|
243
|
+
const body: Record<string, unknown> = { scope };
|
|
244
|
+
if (opts.checks?.length) body["checks"] = opts.checks;
|
|
245
|
+
if (opts.entity !== undefined) body["entity"] = opts.entity;
|
|
246
|
+
if (opts.relation !== undefined) body["relation"] = opts.relation;
|
|
247
|
+
if (opts.stale_lookahead_s !== undefined) body["stale_lookahead_s"] = opts.stale_lookahead_s;
|
|
248
|
+
return this.req<LintResult>("POST", "/v1/lint", body);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ------------------------------------------------------------------
|
|
252
|
+
// Federation
|
|
253
|
+
// ------------------------------------------------------------------
|
|
254
|
+
|
|
255
|
+
async federationStatus(): Promise<Peer[]> {
|
|
256
|
+
const page = await this.req<{ peers: Peer[] }>("GET", "/v1/federation/peers");
|
|
257
|
+
return page.peers;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ------------------------------------------------------------------
|
|
261
|
+
// Recall (Phase 9 — spec §20)
|
|
262
|
+
// ------------------------------------------------------------------
|
|
263
|
+
|
|
264
|
+
async recall(query: string, opts: RecallOptions = {}): Promise<RecallResponse> {
|
|
265
|
+
const body: Record<string, unknown> = {
|
|
266
|
+
query,
|
|
267
|
+
scope: opts.scope ?? "local",
|
|
268
|
+
token_budget: opts.token_budget ?? 4000,
|
|
269
|
+
depth: opts.depth ?? 2,
|
|
270
|
+
min_confidence: opts.min_confidence ?? 0.1,
|
|
271
|
+
include_neighbors: opts.include_neighbors ?? true,
|
|
272
|
+
limit: opts.limit ?? 100,
|
|
273
|
+
};
|
|
274
|
+
if (opts.weights) body["weights"] = opts.weights;
|
|
275
|
+
return this.req<RecallResponse>("POST", "/v1/recall", body);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ------------------------------------------------------------------
|
|
279
|
+
// Memory cards (Phase 9 — spec §20)
|
|
280
|
+
// ------------------------------------------------------------------
|
|
281
|
+
|
|
282
|
+
async getCard(entityUri: string, opts: CardOptions = {}): Promise<MemoryCard> {
|
|
283
|
+
return this.req<MemoryCard>("GET", `/v1/cards/${entityUri}`, undefined, {
|
|
284
|
+
scope: opts.scope ?? "local",
|
|
285
|
+
refresh: opts.refresh ? true : undefined,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ------------------------------------------------------------------
|
|
290
|
+
// Subscribe (async generator, polling)
|
|
291
|
+
// ------------------------------------------------------------------
|
|
292
|
+
|
|
293
|
+
async *subscribeScope(
|
|
294
|
+
scope: FactScope,
|
|
295
|
+
opts: SubscribeOptions = {},
|
|
296
|
+
): AsyncGenerator<Fact[]> {
|
|
297
|
+
const intervalMs = opts.intervalMs ?? 30_000;
|
|
298
|
+
let cursor: string | undefined;
|
|
299
|
+
|
|
300
|
+
while (true) {
|
|
301
|
+
if (opts.signal?.aborted) return;
|
|
302
|
+
|
|
303
|
+
const page = await this.query({ scope, limit: 100, ...(cursor !== undefined ? { cursor } : {}) });
|
|
304
|
+
if (page.facts.length > 0) {
|
|
305
|
+
yield page.facts;
|
|
306
|
+
}
|
|
307
|
+
cursor = page.cursor ?? undefined;
|
|
308
|
+
|
|
309
|
+
if (opts.signal?.aborted) return;
|
|
310
|
+
await new Promise<void>((resolve, reject) => {
|
|
311
|
+
const t = setTimeout(resolve, intervalMs);
|
|
312
|
+
opts.signal?.addEventListener("abort", () => { clearTimeout(t); reject(new Error("aborted")); });
|
|
313
|
+
}).catch(() => { /* aborted */ });
|
|
314
|
+
if (opts.signal?.aborted) return;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|