@skein-js/storage-memory 0.5.0 → 0.7.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/dist/index.js +123 -3
- 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:
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "0.7.0",
|
|
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/
|
|
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/
|
|
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/
|
|
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.
|
|
43
|
+
"@skein-js/core": "0.7.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@skein-js/test-support": "0.
|
|
46
|
+
"@skein-js/test-support": "0.7.0"
|
|
47
47
|
},
|
|
48
48
|
"publishConfig": {
|
|
49
49
|
"access": "public"
|