@skein-js/storage-memory 0.5.0 → 0.6.3

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.
Files changed (2) hide show
  1. package/dist/index.js +123 -3
  2. package/package.json +6 -6
package/dist/index.js CHANGED
@@ -7,6 +7,19 @@ import {
7
7
  } from "@skein-js/core";
8
8
  var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
9
9
  var clone = (value) => structuredClone(value);
10
+ function toVersion(assistant, createdAt) {
11
+ return {
12
+ assistant_id: assistant.assistant_id,
13
+ graph_id: assistant.graph_id,
14
+ config: assistant.config,
15
+ context: assistant.context,
16
+ created_at: createdAt ?? assistant.created_at,
17
+ metadata: assistant.metadata,
18
+ version: assistant.version,
19
+ name: assistant.name,
20
+ description: assistant.description
21
+ };
22
+ }
10
23
  function readOne(map, id) {
11
24
  const found = map.get(id);
12
25
  return found ? clone(found) : null;
@@ -27,8 +40,14 @@ function hasPrefix(namespace, prefix) {
27
40
  function itemKey(namespace, key) {
28
41
  return JSON.stringify([namespace, key]);
29
42
  }
43
+ function versionKey(assistantId, version) {
44
+ return JSON.stringify([assistantId, version]);
45
+ }
30
46
  var MemorySkeinStore = class {
31
47
  #assistants = /* @__PURE__ */ new Map();
48
+ // Immutable version snapshots, keyed by `versionKey(assistant_id, version)`. The live #assistants
49
+ // row always mirrors the currently-active version; this map is the append-only history.
50
+ #assistantVersions = /* @__PURE__ */ new Map();
32
51
  #threads = /* @__PURE__ */ new Map();
33
52
  #runs = /* @__PURE__ */ new Map();
34
53
  // The opaque execution payload lives beside the run row (it is not part of the wire `Run`).
@@ -65,13 +84,48 @@ var MemorySkeinStore = class {
65
84
  this.#itemExpiry.set(id, { ...entry, expiresAt: Date.now() + entry.ttlMinutes * 6e4 });
66
85
  }
67
86
  }
87
+ /** Drop every version snapshot belonging to an assistant (memory has no cascading FK). */
88
+ #deleteVersionsOf(assistantId) {
89
+ for (const [key, version] of this.#assistantVersions) {
90
+ if (version.assistant_id === assistantId) this.#assistantVersions.delete(key);
91
+ }
92
+ }
93
+ /** Highest version number recorded for an assistant, or 0 when it has no history yet. */
94
+ #maxVersionOf(assistantId) {
95
+ let max = 0;
96
+ for (const version of this.#assistantVersions.values()) {
97
+ if (version.assistant_id === assistantId && version.version > max) max = version.version;
98
+ }
99
+ return max;
100
+ }
68
101
  assistants = {
69
102
  list: async () => readAll(this.#assistants),
103
+ search: async (query) => {
104
+ const matched = readAll(this.#assistants).filter(
105
+ (assistant) => (query.graph_id === void 0 || assistant.graph_id === query.graph_id) && (query.name === void 0 || assistant.name === query.name) && isMetadataSubset(assistant.metadata, query.metadata)
106
+ );
107
+ const sortBy = query.sortBy ?? "created_at";
108
+ const direction = query.sortOrder === "asc" ? 1 : -1;
109
+ const compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
110
+ matched.sort((a, b) => {
111
+ const primary = compare(String(a[sortBy] ?? ""), String(b[sortBy] ?? ""));
112
+ const ordered = primary !== 0 ? primary : compare(a.assistant_id, b.assistant_id);
113
+ return direction * ordered;
114
+ });
115
+ const offset = query.offset ?? 0;
116
+ const limit = query.limit ?? matched.length;
117
+ return matched.slice(offset, offset + limit);
118
+ },
119
+ count: async (query) => (await this.assistants.search({ ...query, limit: void 0, offset: void 0 })).length,
70
120
  get: async (assistantId) => readOne(this.#assistants, assistantId),
71
121
  create: async (input) => {
122
+ const assistantId = input.assistant_id ?? randomUUID();
123
+ if (this.#assistants.has(assistantId)) {
124
+ throw SkeinHttpError.conflict(`Assistant "${assistantId}" already exists.`);
125
+ }
72
126
  const at = nowIso();
73
127
  const assistant = {
74
- assistant_id: input.assistant_id ?? randomUUID(),
128
+ assistant_id: assistantId,
75
129
  graph_id: input.graph_id,
76
130
  config: input.config ?? {},
77
131
  context: input.context ?? {},
@@ -82,10 +136,62 @@ var MemorySkeinStore = class {
82
136
  name: input.name ?? input.graph_id,
83
137
  description: input.description
84
138
  };
85
- return write(this.#assistants, assistant.assistant_id, assistant);
139
+ this.#assistantVersions.set(versionKey(assistantId, 1), clone(toVersion(assistant)));
140
+ return write(this.#assistants, assistantId, assistant);
141
+ },
142
+ update: async (assistantId, patch) => {
143
+ const existing = this.#assistants.get(assistantId);
144
+ if (!existing) throw SkeinHttpError.notFound(`Assistant "${assistantId}" not found.`);
145
+ const at = nowIso();
146
+ const next = this.#maxVersionOf(assistantId) + 1;
147
+ const updated = {
148
+ ...existing,
149
+ graph_id: patch.graph_id ?? existing.graph_id,
150
+ name: patch.name ?? existing.name,
151
+ description: patch.description !== void 0 ? patch.description : existing.description,
152
+ config: patch.config ?? existing.config,
153
+ context: patch.context !== void 0 ? patch.context : existing.context,
154
+ // metadata MERGES (shallow), matching LangGraph — patching one key keeps the siblings.
155
+ metadata: patch.metadata !== void 0 ? { ...existing.metadata, ...patch.metadata } : existing.metadata,
156
+ version: next,
157
+ updated_at: at
158
+ };
159
+ this.#assistantVersions.set(versionKey(assistantId, next), clone(toVersion(updated, at)));
160
+ return write(this.#assistants, assistantId, updated);
161
+ },
162
+ listVersions: async (assistantId, query) => {
163
+ const versions = [...this.#assistantVersions.values()].filter(
164
+ (version) => version.assistant_id === assistantId && isMetadataSubset(version.metadata, query?.metadata)
165
+ ).sort((a, b) => b.version - a.version);
166
+ const offset = query?.offset ?? 0;
167
+ const limit = query?.limit ?? versions.length;
168
+ return versions.slice(offset, offset + limit).map(clone);
169
+ },
170
+ setLatest: async (assistantId, version) => {
171
+ const existing = this.#assistants.get(assistantId);
172
+ if (!existing) throw SkeinHttpError.notFound(`Assistant "${assistantId}" not found.`);
173
+ const target = this.#assistantVersions.get(versionKey(assistantId, version));
174
+ if (!target) {
175
+ throw SkeinHttpError.notFound(
176
+ `Version ${version} of assistant "${assistantId}" not found.`
177
+ );
178
+ }
179
+ const updated = {
180
+ ...existing,
181
+ graph_id: target.graph_id,
182
+ name: target.name,
183
+ description: target.description,
184
+ config: target.config,
185
+ context: target.context,
186
+ metadata: target.metadata,
187
+ version: target.version,
188
+ updated_at: nowIso()
189
+ };
190
+ return write(this.#assistants, assistantId, updated);
86
191
  },
87
192
  delete: async (assistantId) => {
88
193
  this.#assistants.delete(assistantId);
194
+ this.#deleteVersionsOf(assistantId);
89
195
  }
90
196
  };
91
197
  threads = {
@@ -196,7 +302,10 @@ var MemorySkeinStore = class {
196
302
  if (run.thread_id === threadId && !isTerminalRunStatus(run.status)) return true;
197
303
  }
198
304
  return false;
199
- }
305
+ },
306
+ listActiveRuns: async (threadId) => readAll(this.#runs).filter(
307
+ (run) => run.thread_id === threadId && !isTerminalRunStatus(run.status)
308
+ )
200
309
  };
201
310
  store = {
202
311
  get: async (namespace, key) => {
@@ -274,6 +383,7 @@ var MemorySkeinStore = class {
274
383
  const entries = (map) => [...map.entries()].map(([id, row]) => [id, clone(row)]);
275
384
  return {
276
385
  assistants: entries(this.#assistants),
386
+ assistantVersions: entries(this.#assistantVersions),
277
387
  threads: entries(this.#threads),
278
388
  runs: entries(this.#runs),
279
389
  runKwargs: entries(this.#runKwargs),
@@ -287,10 +397,19 @@ var MemorySkeinStore = class {
287
397
  for (const [id, row] of rows) map.set(id, clone(row));
288
398
  };
289
399
  fill(this.#assistants, snapshot.assistants);
400
+ fill(this.#assistantVersions, snapshot.assistantVersions ?? []);
290
401
  fill(this.#threads, snapshot.threads);
291
402
  fill(this.#runs, snapshot.runs);
292
403
  fill(this.#runKwargs, snapshot.runKwargs);
293
404
  fill(this.#items, snapshot.items);
405
+ for (const assistant of this.#assistants.values()) {
406
+ if (this.#maxVersionOf(assistant.assistant_id) === 0) {
407
+ this.#assistantVersions.set(
408
+ versionKey(assistant.assistant_id, assistant.version),
409
+ clone(toVersion(assistant))
410
+ );
411
+ }
412
+ }
294
413
  this.#itemExpiry.clear();
295
414
  }
296
415
  /**
@@ -305,6 +424,7 @@ var MemorySkeinStore = class {
305
424
  for (const [id, row] of rows) if (!map.has(id)) map.set(id, clone(row));
306
425
  };
307
426
  add(this.#assistants, snapshot.assistants);
427
+ add(this.#assistantVersions, snapshot.assistantVersions ?? []);
308
428
  add(this.#threads, snapshot.threads);
309
429
  add(this.#runs, snapshot.runs);
310
430
  add(this.#runKwargs, snapshot.runKwargs);
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@skein-js/storage-memory",
3
- "version": "0.5.0",
3
+ "version": "0.6.3",
4
4
  "description": "In-memory SkeinStore + queue driver for development and tests.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Maina Wycliffe <wmmaina7@gmail.com>",
7
- "homepage": "https://github.com/mainawycliffe/skein-js/tree/main/packages/storage-memory#readme",
7
+ "homepage": "https://github.com/skein-js/skein-js/tree/main/packages/storage-memory#readme",
8
8
  "repository": {
9
9
  "type": "git",
10
- "url": "git+https://github.com/mainawycliffe/skein-js.git",
10
+ "url": "git+https://github.com/skein-js/skein-js.git",
11
11
  "directory": "packages/storage-memory"
12
12
  },
13
13
  "bugs": {
14
- "url": "https://github.com/mainawycliffe/skein-js/issues"
14
+ "url": "https://github.com/skein-js/skein-js/issues"
15
15
  },
16
16
  "keywords": [
17
17
  "typescript",
@@ -40,10 +40,10 @@
40
40
  "node": ">=20"
41
41
  },
42
42
  "dependencies": {
43
- "@skein-js/core": "0.5.0"
43
+ "@skein-js/core": "0.6.3"
44
44
  },
45
45
  "devDependencies": {
46
- "@skein-js/test-support": "0.5.0"
46
+ "@skein-js/test-support": "0.6.3"
47
47
  },
48
48
  "publishConfig": {
49
49
  "access": "public"