@acidkill/surreal-memory-client 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Surreal-Memory Contributors
4
+ Copyright (c) 2026 Toni Nowak / AI-Flow NOWAK
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # @acidkill/surreal-memory-client
2
+
3
+ TypeScript client for the [Surreal-Memory](https://github.com/acidkill/surreal-memory) REST API.
4
+
5
+ ```bash
6
+ npm install @acidkill/surreal-memory-client
7
+ ```
8
+
9
+ ## Quick start
10
+
11
+ ```ts
12
+ import { SurrealMemoryClient } from "@acidkill/surreal-memory-client"
13
+
14
+ const client = new SurrealMemoryClient({
15
+ baseUrl: "http://localhost:8000",
16
+ brain: "myproject",
17
+ })
18
+
19
+ // Save a memory
20
+ const { fiber_id } = await client.remember({
21
+ content: "Fixed auth bug with null check in login.py:42",
22
+ type: "fix",
23
+ priority: 7,
24
+ tags: ["auth"],
25
+ })
26
+
27
+ // Recall related memories
28
+ const { results } = await client.recall({ query: "auth bug", limit: 5 })
29
+
30
+ for (const { fiber, score } of results) {
31
+ console.log(score, fiber.content)
32
+ }
33
+
34
+ // Pull recent context
35
+ const { fibers } = await client.context({ limit: 20 })
36
+ ```
37
+
38
+ ## API
39
+
40
+ | Method | Endpoint | Description |
41
+ |---|---|---|
42
+ | `remember(req)` | `POST /api/remember` | Save a memory. |
43
+ | `recall(req)` | `POST /api/recall` | Recall by query (spreading activation + optional vector). |
44
+ | `context(req?)` | `GET /api/context` | Recent fibers in the current brain. |
45
+ | `getFiber(id)` | `GET /api/fibers/:id` | Fetch one fiber. |
46
+ | `forget(id)` | `DELETE /api/fibers/:id` | Hard-delete a fiber. |
47
+ | `listBrains()` | `GET /api/brains` | List all brains. |
48
+ | `getBrainStats()` | `GET /api/stats` | Neuron / synapse / fiber counts. |
49
+ | `health()` | `GET /api/health` | Health probe. |
50
+
51
+ Every method accepts an optional `RequestOptions` argument (`brain` override, `timeoutMs`, `AbortSignal`, extra `headers`).
52
+
53
+ ## Configuration
54
+
55
+ ```ts
56
+ new SurrealMemoryClient({
57
+ baseUrl: "https://memory.example.com", // required, no trailing slash
58
+ brain: "default", // optional default brain
59
+ apiKey: process.env.SURREAL_MEMORY_API_KEY, // optional bearer token
60
+ timeoutMs: 30_000, // default 30s
61
+ fetch: customFetch, // optional fetch impl
62
+ headers: { "X-Tenant": "acme" }, // optional default headers
63
+ })
64
+ ```
65
+
66
+ ## Error handling
67
+
68
+ ```ts
69
+ import { ApiError, SurrealMemoryClient } from "@acidkill/surreal-memory-client"
70
+
71
+ try {
72
+ await client.recall({ query: "..." })
73
+ } catch (err: unknown) {
74
+ if (err instanceof ApiError) {
75
+ console.error(err.status, err.message, err.payload)
76
+ } else {
77
+ throw err
78
+ }
79
+ }
80
+ ```
81
+
82
+ ## Server compatibility
83
+
84
+ | Client | Server |
85
+ |---|---|
86
+ | `2.x` | Surreal-Memory `2.x` (REST API at `/api/*`) |
87
+
88
+ ## License
89
+
90
+ MIT — see [LICENSE](https://github.com/acidkill/surreal-memory/blob/main/LICENSE).
package/dist/index.cjs ADDED
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ApiError: () => ApiError,
24
+ SurrealMemoryClient: () => SurrealMemoryClient
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/client.ts
29
+ var ApiError = class extends Error {
30
+ status;
31
+ payload;
32
+ constructor(status, message, payload) {
33
+ super(message);
34
+ this.name = "ApiError";
35
+ this.status = status;
36
+ this.payload = payload;
37
+ }
38
+ };
39
+ var SurrealMemoryClient = class {
40
+ baseUrl;
41
+ defaultBrain;
42
+ apiKey;
43
+ fetchImpl;
44
+ defaultTimeoutMs;
45
+ defaultHeaders;
46
+ constructor(options) {
47
+ if (!options.baseUrl) {
48
+ throw new Error("SurrealMemoryClient: baseUrl is required");
49
+ }
50
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
51
+ this.defaultBrain = options.brain;
52
+ this.apiKey = options.apiKey;
53
+ this.fetchImpl = options.fetch ?? globalThis.fetch;
54
+ this.defaultTimeoutMs = options.timeoutMs ?? 3e4;
55
+ this.defaultHeaders = options.headers ?? {};
56
+ }
57
+ // ── Memory operations ─────────────────────────────────────
58
+ async remember(req, options) {
59
+ return this.request("POST", "/api/remember", req, options);
60
+ }
61
+ async recall(req, options) {
62
+ return this.request("POST", "/api/recall", req, options);
63
+ }
64
+ async context(req, options) {
65
+ const query = req ? this.buildQuery(req) : "";
66
+ return this.request("GET", `/api/context${query}`, void 0, options);
67
+ }
68
+ async getFiber(fiberId, options) {
69
+ return this.request("GET", `/api/fibers/${encodeURIComponent(fiberId)}`, void 0, options);
70
+ }
71
+ async forget(fiberId, options) {
72
+ return this.request("DELETE", `/api/fibers/${encodeURIComponent(fiberId)}`, void 0, options);
73
+ }
74
+ // ── Brain operations ──────────────────────────────────────
75
+ async listBrains(options) {
76
+ return this.request("GET", "/api/brains", void 0, options);
77
+ }
78
+ async getBrainStats(options) {
79
+ return this.request("GET", "/api/stats", void 0, options);
80
+ }
81
+ // ── Health / diagnostics ─────────────────────────────────
82
+ async health(options) {
83
+ return this.request("GET", "/api/health", void 0, options);
84
+ }
85
+ // ── Internal request plumbing ────────────────────────────
86
+ async request(method, path, body, options) {
87
+ const brain = options?.brain ?? this.defaultBrain;
88
+ const timeoutMs = options?.timeoutMs ?? this.defaultTimeoutMs;
89
+ const headers = new Headers(this.defaultHeaders);
90
+ for (const [k, v] of Object.entries(options?.headers ?? {})) {
91
+ headers.set(k, v);
92
+ }
93
+ if (brain) {
94
+ headers.set("X-Brain-ID", brain);
95
+ }
96
+ if (this.apiKey) {
97
+ headers.set("Authorization", `Bearer ${this.apiKey}`);
98
+ }
99
+ if (body !== void 0 && !headers.has("Content-Type")) {
100
+ headers.set("Content-Type", "application/json");
101
+ }
102
+ if (!headers.has("Accept")) {
103
+ headers.set("Accept", "application/json");
104
+ }
105
+ const controller = new AbortController();
106
+ const timeoutHandle = timeoutMs > 0 ? setTimeout(() => controller.abort(new Error("Request timeout")), timeoutMs) : void 0;
107
+ const externalSignal = options?.signal;
108
+ if (externalSignal) {
109
+ if (externalSignal.aborted) {
110
+ controller.abort(externalSignal.reason);
111
+ } else {
112
+ externalSignal.addEventListener("abort", () => controller.abort(externalSignal.reason), {
113
+ once: true
114
+ });
115
+ }
116
+ }
117
+ try {
118
+ const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
119
+ method,
120
+ headers,
121
+ body: body === void 0 ? void 0 : JSON.stringify(body),
122
+ signal: controller.signal
123
+ });
124
+ if (!response.ok) {
125
+ const payload = await this.safeReadErrorPayload(response);
126
+ const message = payload?.detail ?? payload?.error ?? `HTTP ${response.status}`;
127
+ throw new ApiError(response.status, message, payload);
128
+ }
129
+ if (response.status === 204) {
130
+ return void 0;
131
+ }
132
+ return await response.json();
133
+ } finally {
134
+ if (timeoutHandle) {
135
+ clearTimeout(timeoutHandle);
136
+ }
137
+ }
138
+ }
139
+ async safeReadErrorPayload(response) {
140
+ try {
141
+ const contentType = response.headers.get("Content-Type") ?? "";
142
+ if (contentType.includes("application/json")) {
143
+ return await response.json();
144
+ }
145
+ const text = await response.text();
146
+ return { detail: text || void 0 };
147
+ } catch {
148
+ return void 0;
149
+ }
150
+ }
151
+ buildQuery(params) {
152
+ const search = new URLSearchParams();
153
+ for (const [key, value] of Object.entries(params)) {
154
+ if (value === void 0 || value === null) continue;
155
+ if (Array.isArray(value)) {
156
+ for (const v of value) {
157
+ search.append(key, String(v));
158
+ }
159
+ } else {
160
+ search.set(key, String(value));
161
+ }
162
+ }
163
+ const qs = search.toString();
164
+ return qs ? `?${qs}` : "";
165
+ }
166
+ };
167
+ // Annotate the CommonJS export names for ESM import in node:
168
+ 0 && (module.exports = {
169
+ ApiError,
170
+ SurrealMemoryClient
171
+ });
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Type definitions for the Surreal-Memory REST API.
3
+ *
4
+ * Mirrors the server response shapes from `src/surreal_memory/server/`.
5
+ * Only public-API fields are typed here; internal fields are passed through
6
+ * as `unknown` so the SDK does not break when the server adds new fields.
7
+ */
8
+ type MemoryType = "fact" | "decision" | "error" | "insight" | "preference" | "workflow" | "instruction" | "concept" | "context" | "todo";
9
+ type SynapseType = "CAUSED_BY" | "LEADS_TO" | "CONTRADICTS" | "SIMILAR_TO" | "PART_OF" | "USED_BY" | "DEPENDS_ON" | string;
10
+ type NeuronType = "entity" | "concept" | "time" | "action" | "intent" | "state";
11
+ type LifecycleStage = "full" | "summary" | "essence" | "ghost" | "metadata";
12
+ interface Neuron {
13
+ id: string;
14
+ type: NeuronType;
15
+ content: string;
16
+ content_hash?: number;
17
+ created_at: string;
18
+ metadata?: Record<string, unknown>;
19
+ }
20
+ interface Synapse {
21
+ id: string;
22
+ type: SynapseType;
23
+ source_id: string;
24
+ target_id: string;
25
+ weight?: number;
26
+ created_at: string;
27
+ }
28
+ interface Fiber {
29
+ id: string;
30
+ content: string;
31
+ type?: MemoryType;
32
+ priority?: number;
33
+ tags?: string[];
34
+ stage?: LifecycleStage;
35
+ created_at: string;
36
+ updated_at?: string;
37
+ metadata?: Record<string, unknown>;
38
+ }
39
+ interface Brain {
40
+ id: string;
41
+ name: string;
42
+ created_at: string;
43
+ stats?: BrainStats;
44
+ }
45
+ interface BrainStats {
46
+ neurons: number;
47
+ synapses: number;
48
+ fibers: number;
49
+ active_neurons?: number;
50
+ }
51
+ interface RememberRequest {
52
+ content: string;
53
+ type?: MemoryType;
54
+ priority?: number;
55
+ tags?: string[];
56
+ ephemeral?: boolean;
57
+ metadata?: Record<string, unknown>;
58
+ }
59
+ interface RememberResponse {
60
+ fiber_id: string;
61
+ type: MemoryType;
62
+ saved: true;
63
+ }
64
+ interface RecallRequest {
65
+ query: string;
66
+ limit?: number;
67
+ type?: MemoryType;
68
+ tags?: string[];
69
+ min_priority?: number;
70
+ }
71
+ interface RecallResult {
72
+ fiber: Fiber;
73
+ score: number;
74
+ activation?: number;
75
+ }
76
+ interface RecallResponse {
77
+ results: RecallResult[];
78
+ query: string;
79
+ brain: string;
80
+ }
81
+ interface ContextRequest {
82
+ limit?: number;
83
+ since?: string;
84
+ }
85
+ interface ContextResponse {
86
+ fibers: Fiber[];
87
+ brain: string;
88
+ count: number;
89
+ }
90
+ interface HealthResponse {
91
+ status: "ok" | "degraded" | "down";
92
+ version: string;
93
+ storage: string;
94
+ brain?: string;
95
+ }
96
+ interface ApiErrorPayload {
97
+ detail?: string;
98
+ error?: string;
99
+ status?: number;
100
+ }
101
+
102
+ /**
103
+ * SurrealMemoryClient — typed REST client for a running Surreal-Memory server.
104
+ *
105
+ * @example
106
+ * ```ts
107
+ * import { SurrealMemoryClient } from "@acidkill/surreal-memory-client"
108
+ *
109
+ * const client = new SurrealMemoryClient({
110
+ * baseUrl: "http://localhost:8000",
111
+ * brain: "myproject",
112
+ * })
113
+ *
114
+ * const { fiber_id } = await client.remember({
115
+ * content: "Fixed auth bug with null check in login.py:42",
116
+ * type: "fix",
117
+ * priority: 7,
118
+ * tags: ["auth"],
119
+ * })
120
+ *
121
+ * const { results } = await client.recall({ query: "auth bug", limit: 5 })
122
+ * ```
123
+ */
124
+
125
+ interface ClientOptions {
126
+ /** REST server URL, e.g. `http://localhost:8000`. No trailing slash. */
127
+ baseUrl: string;
128
+ /** Default brain name. Can be overridden per-request. Optional. */
129
+ brain?: string;
130
+ /** Optional bearer token sent as `Authorization: Bearer <token>`. */
131
+ apiKey?: string;
132
+ /** Custom `fetch` implementation (defaults to global `fetch`). */
133
+ fetch?: typeof fetch;
134
+ /** Default timeout in ms (defaults to 30000). */
135
+ timeoutMs?: number;
136
+ /** Extra headers attached to every request. */
137
+ headers?: Record<string, string>;
138
+ }
139
+ interface RequestOptions {
140
+ /** Override the brain for this single call. */
141
+ brain?: string;
142
+ /** Override the timeout for this single call. */
143
+ timeoutMs?: number;
144
+ /** AbortSignal for cancellation. */
145
+ signal?: AbortSignal;
146
+ /** Additional headers merged on top of client defaults. */
147
+ headers?: Record<string, string>;
148
+ }
149
+ declare class ApiError extends Error {
150
+ readonly status: number;
151
+ readonly payload: ApiErrorPayload | undefined;
152
+ constructor(status: number, message: string, payload?: ApiErrorPayload);
153
+ }
154
+ declare class SurrealMemoryClient {
155
+ private readonly baseUrl;
156
+ private readonly defaultBrain;
157
+ private readonly apiKey;
158
+ private readonly fetchImpl;
159
+ private readonly defaultTimeoutMs;
160
+ private readonly defaultHeaders;
161
+ constructor(options: ClientOptions);
162
+ remember(req: RememberRequest, options?: RequestOptions): Promise<RememberResponse>;
163
+ recall(req: RecallRequest, options?: RequestOptions): Promise<RecallResponse>;
164
+ context(req?: ContextRequest, options?: RequestOptions): Promise<ContextResponse>;
165
+ getFiber(fiberId: string, options?: RequestOptions): Promise<Fiber>;
166
+ forget(fiberId: string, options?: RequestOptions): Promise<{
167
+ deleted: true;
168
+ }>;
169
+ listBrains(options?: RequestOptions): Promise<Brain[]>;
170
+ getBrainStats(options?: RequestOptions): Promise<BrainStats>;
171
+ health(options?: RequestOptions): Promise<HealthResponse>;
172
+ private request;
173
+ private safeReadErrorPayload;
174
+ private buildQuery;
175
+ }
176
+
177
+ export { ApiError, type ApiErrorPayload, type Brain, type BrainStats, type ClientOptions, type ContextRequest, type ContextResponse, type Fiber, type HealthResponse, type LifecycleStage, type MemoryType, type Neuron, type NeuronType, type RecallRequest, type RecallResponse, type RecallResult, type RememberRequest, type RememberResponse, type RequestOptions, SurrealMemoryClient, type Synapse, type SynapseType };
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Type definitions for the Surreal-Memory REST API.
3
+ *
4
+ * Mirrors the server response shapes from `src/surreal_memory/server/`.
5
+ * Only public-API fields are typed here; internal fields are passed through
6
+ * as `unknown` so the SDK does not break when the server adds new fields.
7
+ */
8
+ type MemoryType = "fact" | "decision" | "error" | "insight" | "preference" | "workflow" | "instruction" | "concept" | "context" | "todo";
9
+ type SynapseType = "CAUSED_BY" | "LEADS_TO" | "CONTRADICTS" | "SIMILAR_TO" | "PART_OF" | "USED_BY" | "DEPENDS_ON" | string;
10
+ type NeuronType = "entity" | "concept" | "time" | "action" | "intent" | "state";
11
+ type LifecycleStage = "full" | "summary" | "essence" | "ghost" | "metadata";
12
+ interface Neuron {
13
+ id: string;
14
+ type: NeuronType;
15
+ content: string;
16
+ content_hash?: number;
17
+ created_at: string;
18
+ metadata?: Record<string, unknown>;
19
+ }
20
+ interface Synapse {
21
+ id: string;
22
+ type: SynapseType;
23
+ source_id: string;
24
+ target_id: string;
25
+ weight?: number;
26
+ created_at: string;
27
+ }
28
+ interface Fiber {
29
+ id: string;
30
+ content: string;
31
+ type?: MemoryType;
32
+ priority?: number;
33
+ tags?: string[];
34
+ stage?: LifecycleStage;
35
+ created_at: string;
36
+ updated_at?: string;
37
+ metadata?: Record<string, unknown>;
38
+ }
39
+ interface Brain {
40
+ id: string;
41
+ name: string;
42
+ created_at: string;
43
+ stats?: BrainStats;
44
+ }
45
+ interface BrainStats {
46
+ neurons: number;
47
+ synapses: number;
48
+ fibers: number;
49
+ active_neurons?: number;
50
+ }
51
+ interface RememberRequest {
52
+ content: string;
53
+ type?: MemoryType;
54
+ priority?: number;
55
+ tags?: string[];
56
+ ephemeral?: boolean;
57
+ metadata?: Record<string, unknown>;
58
+ }
59
+ interface RememberResponse {
60
+ fiber_id: string;
61
+ type: MemoryType;
62
+ saved: true;
63
+ }
64
+ interface RecallRequest {
65
+ query: string;
66
+ limit?: number;
67
+ type?: MemoryType;
68
+ tags?: string[];
69
+ min_priority?: number;
70
+ }
71
+ interface RecallResult {
72
+ fiber: Fiber;
73
+ score: number;
74
+ activation?: number;
75
+ }
76
+ interface RecallResponse {
77
+ results: RecallResult[];
78
+ query: string;
79
+ brain: string;
80
+ }
81
+ interface ContextRequest {
82
+ limit?: number;
83
+ since?: string;
84
+ }
85
+ interface ContextResponse {
86
+ fibers: Fiber[];
87
+ brain: string;
88
+ count: number;
89
+ }
90
+ interface HealthResponse {
91
+ status: "ok" | "degraded" | "down";
92
+ version: string;
93
+ storage: string;
94
+ brain?: string;
95
+ }
96
+ interface ApiErrorPayload {
97
+ detail?: string;
98
+ error?: string;
99
+ status?: number;
100
+ }
101
+
102
+ /**
103
+ * SurrealMemoryClient — typed REST client for a running Surreal-Memory server.
104
+ *
105
+ * @example
106
+ * ```ts
107
+ * import { SurrealMemoryClient } from "@acidkill/surreal-memory-client"
108
+ *
109
+ * const client = new SurrealMemoryClient({
110
+ * baseUrl: "http://localhost:8000",
111
+ * brain: "myproject",
112
+ * })
113
+ *
114
+ * const { fiber_id } = await client.remember({
115
+ * content: "Fixed auth bug with null check in login.py:42",
116
+ * type: "fix",
117
+ * priority: 7,
118
+ * tags: ["auth"],
119
+ * })
120
+ *
121
+ * const { results } = await client.recall({ query: "auth bug", limit: 5 })
122
+ * ```
123
+ */
124
+
125
+ interface ClientOptions {
126
+ /** REST server URL, e.g. `http://localhost:8000`. No trailing slash. */
127
+ baseUrl: string;
128
+ /** Default brain name. Can be overridden per-request. Optional. */
129
+ brain?: string;
130
+ /** Optional bearer token sent as `Authorization: Bearer <token>`. */
131
+ apiKey?: string;
132
+ /** Custom `fetch` implementation (defaults to global `fetch`). */
133
+ fetch?: typeof fetch;
134
+ /** Default timeout in ms (defaults to 30000). */
135
+ timeoutMs?: number;
136
+ /** Extra headers attached to every request. */
137
+ headers?: Record<string, string>;
138
+ }
139
+ interface RequestOptions {
140
+ /** Override the brain for this single call. */
141
+ brain?: string;
142
+ /** Override the timeout for this single call. */
143
+ timeoutMs?: number;
144
+ /** AbortSignal for cancellation. */
145
+ signal?: AbortSignal;
146
+ /** Additional headers merged on top of client defaults. */
147
+ headers?: Record<string, string>;
148
+ }
149
+ declare class ApiError extends Error {
150
+ readonly status: number;
151
+ readonly payload: ApiErrorPayload | undefined;
152
+ constructor(status: number, message: string, payload?: ApiErrorPayload);
153
+ }
154
+ declare class SurrealMemoryClient {
155
+ private readonly baseUrl;
156
+ private readonly defaultBrain;
157
+ private readonly apiKey;
158
+ private readonly fetchImpl;
159
+ private readonly defaultTimeoutMs;
160
+ private readonly defaultHeaders;
161
+ constructor(options: ClientOptions);
162
+ remember(req: RememberRequest, options?: RequestOptions): Promise<RememberResponse>;
163
+ recall(req: RecallRequest, options?: RequestOptions): Promise<RecallResponse>;
164
+ context(req?: ContextRequest, options?: RequestOptions): Promise<ContextResponse>;
165
+ getFiber(fiberId: string, options?: RequestOptions): Promise<Fiber>;
166
+ forget(fiberId: string, options?: RequestOptions): Promise<{
167
+ deleted: true;
168
+ }>;
169
+ listBrains(options?: RequestOptions): Promise<Brain[]>;
170
+ getBrainStats(options?: RequestOptions): Promise<BrainStats>;
171
+ health(options?: RequestOptions): Promise<HealthResponse>;
172
+ private request;
173
+ private safeReadErrorPayload;
174
+ private buildQuery;
175
+ }
176
+
177
+ export { ApiError, type ApiErrorPayload, type Brain, type BrainStats, type ClientOptions, type ContextRequest, type ContextResponse, type Fiber, type HealthResponse, type LifecycleStage, type MemoryType, type Neuron, type NeuronType, type RecallRequest, type RecallResponse, type RecallResult, type RememberRequest, type RememberResponse, type RequestOptions, SurrealMemoryClient, type Synapse, type SynapseType };
package/dist/index.js ADDED
@@ -0,0 +1,143 @@
1
+ // src/client.ts
2
+ var ApiError = class extends Error {
3
+ status;
4
+ payload;
5
+ constructor(status, message, payload) {
6
+ super(message);
7
+ this.name = "ApiError";
8
+ this.status = status;
9
+ this.payload = payload;
10
+ }
11
+ };
12
+ var SurrealMemoryClient = class {
13
+ baseUrl;
14
+ defaultBrain;
15
+ apiKey;
16
+ fetchImpl;
17
+ defaultTimeoutMs;
18
+ defaultHeaders;
19
+ constructor(options) {
20
+ if (!options.baseUrl) {
21
+ throw new Error("SurrealMemoryClient: baseUrl is required");
22
+ }
23
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "");
24
+ this.defaultBrain = options.brain;
25
+ this.apiKey = options.apiKey;
26
+ this.fetchImpl = options.fetch ?? globalThis.fetch;
27
+ this.defaultTimeoutMs = options.timeoutMs ?? 3e4;
28
+ this.defaultHeaders = options.headers ?? {};
29
+ }
30
+ // ── Memory operations ─────────────────────────────────────
31
+ async remember(req, options) {
32
+ return this.request("POST", "/api/remember", req, options);
33
+ }
34
+ async recall(req, options) {
35
+ return this.request("POST", "/api/recall", req, options);
36
+ }
37
+ async context(req, options) {
38
+ const query = req ? this.buildQuery(req) : "";
39
+ return this.request("GET", `/api/context${query}`, void 0, options);
40
+ }
41
+ async getFiber(fiberId, options) {
42
+ return this.request("GET", `/api/fibers/${encodeURIComponent(fiberId)}`, void 0, options);
43
+ }
44
+ async forget(fiberId, options) {
45
+ return this.request("DELETE", `/api/fibers/${encodeURIComponent(fiberId)}`, void 0, options);
46
+ }
47
+ // ── Brain operations ──────────────────────────────────────
48
+ async listBrains(options) {
49
+ return this.request("GET", "/api/brains", void 0, options);
50
+ }
51
+ async getBrainStats(options) {
52
+ return this.request("GET", "/api/stats", void 0, options);
53
+ }
54
+ // ── Health / diagnostics ─────────────────────────────────
55
+ async health(options) {
56
+ return this.request("GET", "/api/health", void 0, options);
57
+ }
58
+ // ── Internal request plumbing ────────────────────────────
59
+ async request(method, path, body, options) {
60
+ const brain = options?.brain ?? this.defaultBrain;
61
+ const timeoutMs = options?.timeoutMs ?? this.defaultTimeoutMs;
62
+ const headers = new Headers(this.defaultHeaders);
63
+ for (const [k, v] of Object.entries(options?.headers ?? {})) {
64
+ headers.set(k, v);
65
+ }
66
+ if (brain) {
67
+ headers.set("X-Brain-ID", brain);
68
+ }
69
+ if (this.apiKey) {
70
+ headers.set("Authorization", `Bearer ${this.apiKey}`);
71
+ }
72
+ if (body !== void 0 && !headers.has("Content-Type")) {
73
+ headers.set("Content-Type", "application/json");
74
+ }
75
+ if (!headers.has("Accept")) {
76
+ headers.set("Accept", "application/json");
77
+ }
78
+ const controller = new AbortController();
79
+ const timeoutHandle = timeoutMs > 0 ? setTimeout(() => controller.abort(new Error("Request timeout")), timeoutMs) : void 0;
80
+ const externalSignal = options?.signal;
81
+ if (externalSignal) {
82
+ if (externalSignal.aborted) {
83
+ controller.abort(externalSignal.reason);
84
+ } else {
85
+ externalSignal.addEventListener("abort", () => controller.abort(externalSignal.reason), {
86
+ once: true
87
+ });
88
+ }
89
+ }
90
+ try {
91
+ const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
92
+ method,
93
+ headers,
94
+ body: body === void 0 ? void 0 : JSON.stringify(body),
95
+ signal: controller.signal
96
+ });
97
+ if (!response.ok) {
98
+ const payload = await this.safeReadErrorPayload(response);
99
+ const message = payload?.detail ?? payload?.error ?? `HTTP ${response.status}`;
100
+ throw new ApiError(response.status, message, payload);
101
+ }
102
+ if (response.status === 204) {
103
+ return void 0;
104
+ }
105
+ return await response.json();
106
+ } finally {
107
+ if (timeoutHandle) {
108
+ clearTimeout(timeoutHandle);
109
+ }
110
+ }
111
+ }
112
+ async safeReadErrorPayload(response) {
113
+ try {
114
+ const contentType = response.headers.get("Content-Type") ?? "";
115
+ if (contentType.includes("application/json")) {
116
+ return await response.json();
117
+ }
118
+ const text = await response.text();
119
+ return { detail: text || void 0 };
120
+ } catch {
121
+ return void 0;
122
+ }
123
+ }
124
+ buildQuery(params) {
125
+ const search = new URLSearchParams();
126
+ for (const [key, value] of Object.entries(params)) {
127
+ if (value === void 0 || value === null) continue;
128
+ if (Array.isArray(value)) {
129
+ for (const v of value) {
130
+ search.append(key, String(v));
131
+ }
132
+ } else {
133
+ search.set(key, String(value));
134
+ }
135
+ }
136
+ const qs = search.toString();
137
+ return qs ? `?${qs}` : "";
138
+ }
139
+ };
140
+ export {
141
+ ApiError,
142
+ SurrealMemoryClient
143
+ };
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@acidkill/surreal-memory-client",
3
+ "version": "2.6.0",
4
+ "description": "TypeScript client for the Surreal-Memory REST API — typed access to brains, neurons, synapses, fibers, recall, and sync.",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist/",
18
+ "src/",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean",
24
+ "typecheck": "tsc --noEmit",
25
+ "test": "vitest run",
26
+ "prepublishOnly": "npm run build"
27
+ },
28
+ "keywords": [
29
+ "surreal-memory",
30
+ "surrealmemory",
31
+ "surrealdb",
32
+ "memory",
33
+ "ai-agent",
34
+ "client",
35
+ "sdk",
36
+ "rest"
37
+ ],
38
+ "author": "Toni Nowak / AI-Flow NOWAK",
39
+ "license": "MIT",
40
+ "homepage": "https://github.com/acidkill/surreal-memory#readme",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "https://github.com/acidkill/surreal-memory.git",
44
+ "directory": "integrations/surreal-memory-client"
45
+ },
46
+ "bugs": {
47
+ "url": "https://github.com/acidkill/surreal-memory/issues"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "engines": {
53
+ "node": ">=18.0.0"
54
+ },
55
+ "devDependencies": {
56
+ "@types/node": "^20.0.0",
57
+ "tsup": "^8.0.0",
58
+ "typescript": "^5.4.0",
59
+ "vitest": "^1.6.0"
60
+ }
61
+ }
package/src/client.ts ADDED
@@ -0,0 +1,234 @@
1
+ /**
2
+ * SurrealMemoryClient — typed REST client for a running Surreal-Memory server.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * import { SurrealMemoryClient } from "@acidkill/surreal-memory-client"
7
+ *
8
+ * const client = new SurrealMemoryClient({
9
+ * baseUrl: "http://localhost:8000",
10
+ * brain: "myproject",
11
+ * })
12
+ *
13
+ * const { fiber_id } = await client.remember({
14
+ * content: "Fixed auth bug with null check in login.py:42",
15
+ * type: "fix",
16
+ * priority: 7,
17
+ * tags: ["auth"],
18
+ * })
19
+ *
20
+ * const { results } = await client.recall({ query: "auth bug", limit: 5 })
21
+ * ```
22
+ */
23
+
24
+ import type {
25
+ ApiErrorPayload,
26
+ Brain,
27
+ BrainStats,
28
+ ContextRequest,
29
+ ContextResponse,
30
+ Fiber,
31
+ HealthResponse,
32
+ RecallRequest,
33
+ RecallResponse,
34
+ RememberRequest,
35
+ RememberResponse,
36
+ } from "./types"
37
+
38
+ export interface ClientOptions {
39
+ /** REST server URL, e.g. `http://localhost:8000`. No trailing slash. */
40
+ baseUrl: string
41
+ /** Default brain name. Can be overridden per-request. Optional. */
42
+ brain?: string
43
+ /** Optional bearer token sent as `Authorization: Bearer <token>`. */
44
+ apiKey?: string
45
+ /** Custom `fetch` implementation (defaults to global `fetch`). */
46
+ fetch?: typeof fetch
47
+ /** Default timeout in ms (defaults to 30000). */
48
+ timeoutMs?: number
49
+ /** Extra headers attached to every request. */
50
+ headers?: Record<string, string>
51
+ }
52
+
53
+ export interface RequestOptions {
54
+ /** Override the brain for this single call. */
55
+ brain?: string
56
+ /** Override the timeout for this single call. */
57
+ timeoutMs?: number
58
+ /** AbortSignal for cancellation. */
59
+ signal?: AbortSignal
60
+ /** Additional headers merged on top of client defaults. */
61
+ headers?: Record<string, string>
62
+ }
63
+
64
+ export class ApiError extends Error {
65
+ public readonly status: number
66
+ public readonly payload: ApiErrorPayload | undefined
67
+
68
+ constructor(status: number, message: string, payload?: ApiErrorPayload) {
69
+ super(message)
70
+ this.name = "ApiError"
71
+ this.status = status
72
+ this.payload = payload
73
+ }
74
+ }
75
+
76
+ export class SurrealMemoryClient {
77
+ private readonly baseUrl: string
78
+ private readonly defaultBrain: string | undefined
79
+ private readonly apiKey: string | undefined
80
+ private readonly fetchImpl: typeof fetch
81
+ private readonly defaultTimeoutMs: number
82
+ private readonly defaultHeaders: Record<string, string>
83
+
84
+ constructor(options: ClientOptions) {
85
+ if (!options.baseUrl) {
86
+ throw new Error("SurrealMemoryClient: baseUrl is required")
87
+ }
88
+ this.baseUrl = options.baseUrl.replace(/\/+$/, "")
89
+ this.defaultBrain = options.brain
90
+ this.apiKey = options.apiKey
91
+ this.fetchImpl = options.fetch ?? globalThis.fetch
92
+ this.defaultTimeoutMs = options.timeoutMs ?? 30_000
93
+ this.defaultHeaders = options.headers ?? {}
94
+ }
95
+
96
+ // ── Memory operations ─────────────────────────────────────
97
+
98
+ async remember(req: RememberRequest, options?: RequestOptions): Promise<RememberResponse> {
99
+ return this.request<RememberResponse>("POST", "/api/remember", req, options)
100
+ }
101
+
102
+ async recall(req: RecallRequest, options?: RequestOptions): Promise<RecallResponse> {
103
+ return this.request<RecallResponse>("POST", "/api/recall", req, options)
104
+ }
105
+
106
+ async context(req?: ContextRequest, options?: RequestOptions): Promise<ContextResponse> {
107
+ const query = req ? this.buildQuery(req as Record<string, unknown>) : ""
108
+ return this.request<ContextResponse>("GET", `/api/context${query}`, undefined, options)
109
+ }
110
+
111
+ async getFiber(fiberId: string, options?: RequestOptions): Promise<Fiber> {
112
+ return this.request<Fiber>("GET", `/api/fibers/${encodeURIComponent(fiberId)}`, undefined, options)
113
+ }
114
+
115
+ async forget(fiberId: string, options?: RequestOptions): Promise<{ deleted: true }> {
116
+ return this.request("DELETE", `/api/fibers/${encodeURIComponent(fiberId)}`, undefined, options)
117
+ }
118
+
119
+ // ── Brain operations ──────────────────────────────────────
120
+
121
+ async listBrains(options?: RequestOptions): Promise<Brain[]> {
122
+ return this.request<Brain[]>("GET", "/api/brains", undefined, options)
123
+ }
124
+
125
+ async getBrainStats(options?: RequestOptions): Promise<BrainStats> {
126
+ return this.request<BrainStats>("GET", "/api/stats", undefined, options)
127
+ }
128
+
129
+ // ── Health / diagnostics ─────────────────────────────────
130
+
131
+ async health(options?: RequestOptions): Promise<HealthResponse> {
132
+ return this.request<HealthResponse>("GET", "/api/health", undefined, options)
133
+ }
134
+
135
+ // ── Internal request plumbing ────────────────────────────
136
+
137
+ private async request<T>(
138
+ method: string,
139
+ path: string,
140
+ body: unknown,
141
+ options?: RequestOptions,
142
+ ): Promise<T> {
143
+ const brain = options?.brain ?? this.defaultBrain
144
+ const timeoutMs = options?.timeoutMs ?? this.defaultTimeoutMs
145
+
146
+ const headers = new Headers(this.defaultHeaders)
147
+ for (const [k, v] of Object.entries(options?.headers ?? {})) {
148
+ headers.set(k, v)
149
+ }
150
+ if (brain) {
151
+ headers.set("X-Brain-ID", brain)
152
+ }
153
+ if (this.apiKey) {
154
+ headers.set("Authorization", `Bearer ${this.apiKey}`)
155
+ }
156
+ if (body !== undefined && !headers.has("Content-Type")) {
157
+ headers.set("Content-Type", "application/json")
158
+ }
159
+ if (!headers.has("Accept")) {
160
+ headers.set("Accept", "application/json")
161
+ }
162
+
163
+ const controller = new AbortController()
164
+ const timeoutHandle =
165
+ timeoutMs > 0
166
+ ? setTimeout(() => controller.abort(new Error("Request timeout")), timeoutMs)
167
+ : undefined
168
+
169
+ const externalSignal = options?.signal
170
+ if (externalSignal) {
171
+ if (externalSignal.aborted) {
172
+ controller.abort(externalSignal.reason)
173
+ } else {
174
+ externalSignal.addEventListener("abort", () => controller.abort(externalSignal.reason), {
175
+ once: true,
176
+ })
177
+ }
178
+ }
179
+
180
+ try {
181
+ const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
182
+ method,
183
+ headers,
184
+ body: body === undefined ? undefined : JSON.stringify(body),
185
+ signal: controller.signal,
186
+ })
187
+
188
+ if (!response.ok) {
189
+ const payload = await this.safeReadErrorPayload(response)
190
+ const message = payload?.detail ?? payload?.error ?? `HTTP ${response.status}`
191
+ throw new ApiError(response.status, message, payload)
192
+ }
193
+
194
+ if (response.status === 204) {
195
+ return undefined as T
196
+ }
197
+
198
+ return (await response.json()) as T
199
+ } finally {
200
+ if (timeoutHandle) {
201
+ clearTimeout(timeoutHandle)
202
+ }
203
+ }
204
+ }
205
+
206
+ private async safeReadErrorPayload(response: Response): Promise<ApiErrorPayload | undefined> {
207
+ try {
208
+ const contentType = response.headers.get("Content-Type") ?? ""
209
+ if (contentType.includes("application/json")) {
210
+ return (await response.json()) as ApiErrorPayload
211
+ }
212
+ const text = await response.text()
213
+ return { detail: text || undefined }
214
+ } catch {
215
+ return undefined
216
+ }
217
+ }
218
+
219
+ private buildQuery(params: Record<string, unknown>): string {
220
+ const search = new URLSearchParams()
221
+ for (const [key, value] of Object.entries(params)) {
222
+ if (value === undefined || value === null) continue
223
+ if (Array.isArray(value)) {
224
+ for (const v of value) {
225
+ search.append(key, String(v))
226
+ }
227
+ } else {
228
+ search.set(key, String(value))
229
+ }
230
+ }
231
+ const qs = search.toString()
232
+ return qs ? `?${qs}` : ""
233
+ }
234
+ }
package/src/index.ts ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @acidkill/surreal-memory-client
3
+ *
4
+ * Typed REST client for Surreal-Memory. See README.md for usage examples.
5
+ */
6
+
7
+ export { SurrealMemoryClient, ApiError } from "./client"
8
+ export type { ClientOptions, RequestOptions } from "./client"
9
+ export type {
10
+ ApiErrorPayload,
11
+ Brain,
12
+ BrainStats,
13
+ ContextRequest,
14
+ ContextResponse,
15
+ Fiber,
16
+ HealthResponse,
17
+ LifecycleStage,
18
+ MemoryType,
19
+ Neuron,
20
+ NeuronType,
21
+ RecallRequest,
22
+ RecallResponse,
23
+ RecallResult,
24
+ RememberRequest,
25
+ RememberResponse,
26
+ Synapse,
27
+ SynapseType,
28
+ } from "./types"
package/src/types.ts ADDED
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Type definitions for the Surreal-Memory REST API.
3
+ *
4
+ * Mirrors the server response shapes from `src/surreal_memory/server/`.
5
+ * Only public-API fields are typed here; internal fields are passed through
6
+ * as `unknown` so the SDK does not break when the server adds new fields.
7
+ */
8
+
9
+ export type MemoryType =
10
+ | "fact"
11
+ | "decision"
12
+ | "error"
13
+ | "insight"
14
+ | "preference"
15
+ | "workflow"
16
+ | "instruction"
17
+ | "concept"
18
+ | "context"
19
+ | "todo"
20
+
21
+ export type SynapseType =
22
+ | "CAUSED_BY"
23
+ | "LEADS_TO"
24
+ | "CONTRADICTS"
25
+ | "SIMILAR_TO"
26
+ | "PART_OF"
27
+ | "USED_BY"
28
+ | "DEPENDS_ON"
29
+ | string
30
+
31
+ export type NeuronType = "entity" | "concept" | "time" | "action" | "intent" | "state"
32
+
33
+ export type LifecycleStage = "full" | "summary" | "essence" | "ghost" | "metadata"
34
+
35
+ export interface Neuron {
36
+ id: string
37
+ type: NeuronType
38
+ content: string
39
+ content_hash?: number
40
+ created_at: string
41
+ metadata?: Record<string, unknown>
42
+ }
43
+
44
+ export interface Synapse {
45
+ id: string
46
+ type: SynapseType
47
+ source_id: string
48
+ target_id: string
49
+ weight?: number
50
+ created_at: string
51
+ }
52
+
53
+ export interface Fiber {
54
+ id: string
55
+ content: string
56
+ type?: MemoryType
57
+ priority?: number
58
+ tags?: string[]
59
+ stage?: LifecycleStage
60
+ created_at: string
61
+ updated_at?: string
62
+ metadata?: Record<string, unknown>
63
+ }
64
+
65
+ export interface Brain {
66
+ id: string
67
+ name: string
68
+ created_at: string
69
+ stats?: BrainStats
70
+ }
71
+
72
+ export interface BrainStats {
73
+ neurons: number
74
+ synapses: number
75
+ fibers: number
76
+ active_neurons?: number
77
+ }
78
+
79
+ export interface RememberRequest {
80
+ content: string
81
+ type?: MemoryType
82
+ priority?: number
83
+ tags?: string[]
84
+ ephemeral?: boolean
85
+ metadata?: Record<string, unknown>
86
+ }
87
+
88
+ export interface RememberResponse {
89
+ fiber_id: string
90
+ type: MemoryType
91
+ saved: true
92
+ }
93
+
94
+ export interface RecallRequest {
95
+ query: string
96
+ limit?: number
97
+ type?: MemoryType
98
+ tags?: string[]
99
+ min_priority?: number
100
+ }
101
+
102
+ export interface RecallResult {
103
+ fiber: Fiber
104
+ score: number
105
+ activation?: number
106
+ }
107
+
108
+ export interface RecallResponse {
109
+ results: RecallResult[]
110
+ query: string
111
+ brain: string
112
+ }
113
+
114
+ export interface ContextRequest {
115
+ limit?: number
116
+ since?: string
117
+ }
118
+
119
+ export interface ContextResponse {
120
+ fibers: Fiber[]
121
+ brain: string
122
+ count: number
123
+ }
124
+
125
+ export interface HealthResponse {
126
+ status: "ok" | "degraded" | "down"
127
+ version: string
128
+ storage: string
129
+ brain?: string
130
+ }
131
+
132
+ export interface ApiErrorPayload {
133
+ detail?: string
134
+ error?: string
135
+ status?: number
136
+ }