@kernl-sdk/libsql 0.1.36 → 0.1.39
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/.turbo/turbo-build.log +5 -4
- package/CHANGELOG.md +20 -0
- package/README.md +225 -0
- package/dist/__tests__/constraints.test.d.ts +2 -0
- package/dist/__tests__/constraints.test.d.ts.map +1 -0
- package/dist/__tests__/constraints.test.js +97 -0
- package/dist/__tests__/helpers.d.ts +36 -0
- package/dist/__tests__/helpers.d.ts.map +1 -0
- package/dist/__tests__/helpers.js +80 -0
- package/dist/__tests__/memory.create-get.test.d.ts +2 -0
- package/dist/__tests__/memory.create-get.test.d.ts.map +1 -0
- package/dist/__tests__/memory.create-get.test.js +8 -0
- package/dist/__tests__/memory.delete.test.d.ts +2 -0
- package/dist/__tests__/memory.delete.test.d.ts.map +1 -0
- package/dist/__tests__/memory.delete.test.js +6 -0
- package/dist/__tests__/memory.list.test.d.ts +2 -0
- package/dist/__tests__/memory.list.test.d.ts.map +1 -0
- package/dist/__tests__/memory.list.test.js +8 -0
- package/dist/__tests__/memory.update.test.d.ts +2 -0
- package/dist/__tests__/memory.update.test.d.ts.map +1 -0
- package/dist/__tests__/memory.update.test.js +8 -0
- package/dist/__tests__/migrations.test.d.ts +2 -0
- package/dist/__tests__/migrations.test.d.ts.map +1 -0
- package/dist/__tests__/migrations.test.js +68 -0
- package/dist/__tests__/row-codecs.test.d.ts +2 -0
- package/dist/__tests__/row-codecs.test.d.ts.map +1 -0
- package/dist/__tests__/row-codecs.test.js +175 -0
- package/dist/__tests__/sql-utils.test.d.ts +2 -0
- package/dist/__tests__/sql-utils.test.d.ts.map +1 -0
- package/dist/__tests__/sql-utils.test.js +45 -0
- package/dist/__tests__/storage.init.test.d.ts +2 -0
- package/dist/__tests__/storage.init.test.d.ts.map +1 -0
- package/dist/__tests__/storage.init.test.js +63 -0
- package/dist/__tests__/thread.lifecycle.test.d.ts +2 -0
- package/dist/__tests__/thread.lifecycle.test.d.ts.map +1 -0
- package/dist/__tests__/thread.lifecycle.test.js +172 -0
- package/dist/__tests__/transaction.test.d.ts +2 -0
- package/dist/__tests__/transaction.test.d.ts.map +1 -0
- package/dist/__tests__/transaction.test.js +16 -0
- package/dist/__tests__/utils.test.d.ts +2 -0
- package/dist/__tests__/utils.test.d.ts.map +1 -0
- package/dist/__tests__/utils.test.js +31 -0
- package/dist/client.d.ts +46 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +46 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/memory/__tests__/create-get.test.d.ts +2 -0
- package/dist/memory/__tests__/create-get.test.d.ts.map +1 -0
- package/dist/memory/__tests__/create-get.test.js +126 -0
- package/dist/memory/__tests__/delete.test.d.ts +2 -0
- package/dist/memory/__tests__/delete.test.d.ts.map +1 -0
- package/dist/memory/__tests__/delete.test.js +96 -0
- package/dist/memory/__tests__/list.test.d.ts +2 -0
- package/dist/memory/__tests__/list.test.d.ts.map +1 -0
- package/dist/memory/__tests__/list.test.js +168 -0
- package/dist/memory/__tests__/sql.test.d.ts +2 -0
- package/dist/memory/__tests__/sql.test.d.ts.map +1 -0
- package/dist/memory/__tests__/sql.test.js +159 -0
- package/dist/memory/__tests__/update.test.d.ts +2 -0
- package/dist/memory/__tests__/update.test.d.ts.map +1 -0
- package/dist/memory/__tests__/update.test.js +113 -0
- package/dist/memory/row.d.ts +11 -0
- package/dist/memory/row.d.ts.map +1 -0
- package/dist/memory/row.js +29 -0
- package/dist/memory/sql.d.ts +34 -0
- package/dist/memory/sql.d.ts.map +1 -0
- package/dist/memory/sql.js +109 -0
- package/dist/memory/store.d.ts +41 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +132 -0
- package/dist/migrations.d.ts +32 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +157 -0
- package/dist/sql.d.ts +28 -0
- package/dist/sql.d.ts.map +1 -0
- package/dist/sql.js +22 -0
- package/dist/storage.d.ts +75 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +123 -0
- package/dist/thread/__tests__/append.test.d.ts +2 -0
- package/dist/thread/__tests__/append.test.d.ts.map +1 -0
- package/dist/thread/__tests__/append.test.js +141 -0
- package/dist/thread/__tests__/history.test.d.ts +2 -0
- package/dist/thread/__tests__/history.test.d.ts.map +1 -0
- package/dist/thread/__tests__/history.test.js +146 -0
- package/dist/thread/__tests__/sql.test.d.ts +2 -0
- package/dist/thread/__tests__/sql.test.d.ts.map +1 -0
- package/dist/thread/__tests__/sql.test.js +129 -0
- package/dist/thread/__tests__/store.test.d.ts +2 -0
- package/dist/thread/__tests__/store.test.d.ts.map +1 -0
- package/dist/thread/__tests__/store.test.js +170 -0
- package/dist/thread/row.d.ts +19 -0
- package/dist/thread/row.d.ts.map +1 -0
- package/dist/thread/row.js +65 -0
- package/dist/thread/sql.d.ts +33 -0
- package/dist/thread/sql.d.ts.map +1 -0
- package/dist/thread/sql.js +112 -0
- package/dist/thread/store.d.ts +67 -0
- package/dist/thread/store.d.ts.map +1 -0
- package/dist/thread/store.js +282 -0
- package/dist/utils.d.ts +10 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +21 -0
- package/package.json +15 -11
- package/src/__tests__/constraints.test.ts +123 -0
- package/src/__tests__/helpers.ts +98 -0
- package/src/__tests__/migrations.test.ts +114 -0
- package/src/__tests__/row-codecs.test.ts +201 -0
- package/src/__tests__/sql-utils.test.ts +52 -0
- package/src/__tests__/storage.init.test.ts +92 -0
- package/src/__tests__/thread.lifecycle.test.ts +234 -0
- package/src/__tests__/transaction.test.ts +25 -0
- package/src/__tests__/utils.test.ts +38 -0
- package/src/client.ts +71 -0
- package/src/index.ts +10 -0
- package/src/memory/__tests__/create-get.test.ts +161 -0
- package/src/memory/__tests__/delete.test.ts +124 -0
- package/src/memory/__tests__/list.test.ts +198 -0
- package/src/memory/__tests__/sql.test.ts +186 -0
- package/src/memory/__tests__/update.test.ts +148 -0
- package/src/memory/row.ts +36 -0
- package/src/memory/sql.ts +142 -0
- package/src/memory/store.ts +173 -0
- package/src/migrations.ts +206 -0
- package/src/sql.ts +35 -0
- package/src/storage.ts +170 -0
- package/src/thread/__tests__/append.test.ts +201 -0
- package/src/thread/__tests__/history.test.ts +198 -0
- package/src/thread/__tests__/sql.test.ts +154 -0
- package/src/thread/__tests__/store.test.ts +219 -0
- package/src/thread/row.ts +77 -0
- package/src/thread/sql.ts +153 -0
- package/src/thread/store.ts +381 -0
- package/src/utils.ts +20 -0
- package/LICENSE +0 -201
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { RowToThreadRecord, RowToEventRecord, RowToEventRecordDirect, } from "../thread/row.js";
|
|
3
|
+
import { RowToMemoryRecord } from "../memory/row.js";
|
|
4
|
+
/** Cast test data to Row type (libsql rows are array-like) */
|
|
5
|
+
const asRow = (data) => data;
|
|
6
|
+
describe("LibSQL row codecs", () => {
|
|
7
|
+
describe("RowToThreadRecord", () => {
|
|
8
|
+
it("decodes thread record with JSON fields", () => {
|
|
9
|
+
const row = asRow({
|
|
10
|
+
id: "thread-1",
|
|
11
|
+
namespace: "default",
|
|
12
|
+
agent_id: "agent-1",
|
|
13
|
+
model: "openai/gpt-4",
|
|
14
|
+
context: '{"userId": "user-1"}',
|
|
15
|
+
tick: 5,
|
|
16
|
+
state: "stopped",
|
|
17
|
+
parent_task_id: null,
|
|
18
|
+
metadata: '{"title": "Test"}',
|
|
19
|
+
created_at: 1700000000000,
|
|
20
|
+
updated_at: 1700000001000,
|
|
21
|
+
});
|
|
22
|
+
const result = RowToThreadRecord.encode(row);
|
|
23
|
+
expect(result).toEqual({
|
|
24
|
+
id: "thread-1",
|
|
25
|
+
namespace: "default",
|
|
26
|
+
agent_id: "agent-1",
|
|
27
|
+
model: "openai/gpt-4",
|
|
28
|
+
context: { userId: "user-1" },
|
|
29
|
+
tick: 5,
|
|
30
|
+
state: "stopped",
|
|
31
|
+
parent_task_id: null,
|
|
32
|
+
metadata: { title: "Test" },
|
|
33
|
+
created_at: 1700000000000,
|
|
34
|
+
updated_at: 1700000001000,
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
it("handles null metadata", () => {
|
|
38
|
+
const row = asRow({
|
|
39
|
+
id: "thread-1",
|
|
40
|
+
namespace: "default",
|
|
41
|
+
agent_id: "agent-1",
|
|
42
|
+
model: "openai/gpt-4",
|
|
43
|
+
context: "{}",
|
|
44
|
+
tick: 0,
|
|
45
|
+
state: "stopped",
|
|
46
|
+
parent_task_id: null,
|
|
47
|
+
metadata: null,
|
|
48
|
+
created_at: 1700000000000,
|
|
49
|
+
updated_at: 1700000000000,
|
|
50
|
+
});
|
|
51
|
+
const result = RowToThreadRecord.encode(row);
|
|
52
|
+
expect(result.metadata).toBeNull();
|
|
53
|
+
expect(result.context).toEqual({});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
describe("RowToEventRecord", () => {
|
|
57
|
+
it("decodes event record from join query", () => {
|
|
58
|
+
const row = asRow({
|
|
59
|
+
// thread fields (ignored by this codec)
|
|
60
|
+
id: "thread-1",
|
|
61
|
+
namespace: "default",
|
|
62
|
+
// event fields (with event_ prefix)
|
|
63
|
+
event_id: "evt-1",
|
|
64
|
+
event_tid: "thread-1",
|
|
65
|
+
seq: 1,
|
|
66
|
+
event_kind: "message",
|
|
67
|
+
timestamp: "1700000000000", // BigInt comes as string
|
|
68
|
+
data: '{"role": "user", "content": "Hello"}',
|
|
69
|
+
event_metadata: '{"source": "api"}',
|
|
70
|
+
});
|
|
71
|
+
const result = RowToEventRecord.encode(row);
|
|
72
|
+
expect(result).toEqual({
|
|
73
|
+
id: "evt-1",
|
|
74
|
+
tid: "thread-1",
|
|
75
|
+
seq: 1,
|
|
76
|
+
kind: "message",
|
|
77
|
+
timestamp: 1700000000000,
|
|
78
|
+
data: { role: "user", content: "Hello" },
|
|
79
|
+
metadata: { source: "api" },
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
it("handles null data and metadata", () => {
|
|
83
|
+
const row = asRow({
|
|
84
|
+
event_id: "evt-1",
|
|
85
|
+
event_tid: "thread-1",
|
|
86
|
+
seq: 1,
|
|
87
|
+
event_kind: "tick",
|
|
88
|
+
timestamp: 1700000000000,
|
|
89
|
+
data: null,
|
|
90
|
+
event_metadata: null,
|
|
91
|
+
});
|
|
92
|
+
const result = RowToEventRecord.encode(row);
|
|
93
|
+
expect(result.data).toBeNull();
|
|
94
|
+
expect(result.metadata).toBeNull();
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
describe("RowToEventRecordDirect", () => {
|
|
98
|
+
it("decodes event record from direct query", () => {
|
|
99
|
+
const row = asRow({
|
|
100
|
+
id: "evt-1",
|
|
101
|
+
tid: "thread-1",
|
|
102
|
+
seq: 1,
|
|
103
|
+
kind: "message",
|
|
104
|
+
timestamp: "1700000000000",
|
|
105
|
+
data: '{"role": "assistant"}',
|
|
106
|
+
metadata: null,
|
|
107
|
+
});
|
|
108
|
+
const result = RowToEventRecordDirect.encode(row);
|
|
109
|
+
expect(result).toEqual({
|
|
110
|
+
id: "evt-1",
|
|
111
|
+
tid: "thread-1",
|
|
112
|
+
seq: 1,
|
|
113
|
+
kind: "message",
|
|
114
|
+
timestamp: 1700000000000,
|
|
115
|
+
data: { role: "assistant" },
|
|
116
|
+
metadata: null,
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
describe("RowToMemoryRecord", () => {
|
|
121
|
+
it("decodes memory record with JSON and boolean fields", () => {
|
|
122
|
+
const row = asRow({
|
|
123
|
+
id: "mem-1",
|
|
124
|
+
namespace: "default",
|
|
125
|
+
entity_id: "user-1",
|
|
126
|
+
agent_id: "agent-1",
|
|
127
|
+
kind: "semantic",
|
|
128
|
+
collection: "facts",
|
|
129
|
+
content: '{"text": "User likes coffee"}',
|
|
130
|
+
wmem: 1, // SQLite boolean
|
|
131
|
+
smem_expires_at: 1700100000000,
|
|
132
|
+
timestamp: 1700000000000,
|
|
133
|
+
created_at: 1700000000000,
|
|
134
|
+
updated_at: 1700000001000,
|
|
135
|
+
metadata: '{"confidence": 0.9}',
|
|
136
|
+
});
|
|
137
|
+
const result = RowToMemoryRecord.encode(row);
|
|
138
|
+
expect(result).toEqual({
|
|
139
|
+
id: "mem-1",
|
|
140
|
+
namespace: "default",
|
|
141
|
+
entity_id: "user-1",
|
|
142
|
+
agent_id: "agent-1",
|
|
143
|
+
kind: "semantic",
|
|
144
|
+
collection: "facts",
|
|
145
|
+
content: { text: "User likes coffee" },
|
|
146
|
+
wmem: true,
|
|
147
|
+
smem_expires_at: 1700100000000,
|
|
148
|
+
timestamp: 1700000000000,
|
|
149
|
+
created_at: 1700000000000,
|
|
150
|
+
updated_at: 1700000001000,
|
|
151
|
+
metadata: { confidence: 0.9 },
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
it("converts SQLite 0 to false for wmem", () => {
|
|
155
|
+
const row = asRow({
|
|
156
|
+
id: "mem-1",
|
|
157
|
+
namespace: null,
|
|
158
|
+
entity_id: null,
|
|
159
|
+
agent_id: null,
|
|
160
|
+
kind: "episodic",
|
|
161
|
+
collection: null,
|
|
162
|
+
content: "{}",
|
|
163
|
+
wmem: 0,
|
|
164
|
+
smem_expires_at: null,
|
|
165
|
+
timestamp: 1700000000000,
|
|
166
|
+
created_at: 1700000000000,
|
|
167
|
+
updated_at: 1700000000000,
|
|
168
|
+
metadata: null,
|
|
169
|
+
});
|
|
170
|
+
const result = RowToMemoryRecord.encode(row);
|
|
171
|
+
expect(result.wmem).toBe(false);
|
|
172
|
+
expect(result.metadata).toBeNull();
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sql-utils.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/sql-utils.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { expandarray, SQL_IDENTIFIER_REGEX } from "../sql.js";
|
|
3
|
+
describe("libsql sql utils", () => {
|
|
4
|
+
describe("expandarray", () => {
|
|
5
|
+
it("returns placeholders and params for array", () => {
|
|
6
|
+
const result = expandarray(["a", "b", "c"]);
|
|
7
|
+
expect(result.placeholders).toBe("?, ?, ?");
|
|
8
|
+
expect(result.params).toEqual(["a", "b", "c"]);
|
|
9
|
+
});
|
|
10
|
+
it("handles single element", () => {
|
|
11
|
+
const result = expandarray(["only"]);
|
|
12
|
+
expect(result.placeholders).toBe("?");
|
|
13
|
+
expect(result.params).toEqual(["only"]);
|
|
14
|
+
});
|
|
15
|
+
it("handles empty array", () => {
|
|
16
|
+
const result = expandarray([]);
|
|
17
|
+
expect(result.placeholders).toBe("");
|
|
18
|
+
expect(result.params).toEqual([]);
|
|
19
|
+
});
|
|
20
|
+
it("preserves original values", () => {
|
|
21
|
+
const result = expandarray([1, "two", null, true]);
|
|
22
|
+
expect(result.placeholders).toBe("?, ?, ?, ?");
|
|
23
|
+
expect(result.params).toEqual([1, "two", null, true]);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
describe("SQL_IDENTIFIER_REGEX", () => {
|
|
27
|
+
it("accepts valid identifiers", () => {
|
|
28
|
+
expect(SQL_IDENTIFIER_REGEX.test("users")).toBe(true);
|
|
29
|
+
expect(SQL_IDENTIFIER_REGEX.test("thread_events")).toBe(true);
|
|
30
|
+
expect(SQL_IDENTIFIER_REGEX.test("kernl_threads")).toBe(true);
|
|
31
|
+
expect(SQL_IDENTIFIER_REGEX.test("_private")).toBe(true);
|
|
32
|
+
expect(SQL_IDENTIFIER_REGEX.test("Table1")).toBe(true);
|
|
33
|
+
expect(SQL_IDENTIFIER_REGEX.test("a")).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
it("rejects invalid identifiers", () => {
|
|
36
|
+
expect(SQL_IDENTIFIER_REGEX.test("")).toBe(false);
|
|
37
|
+
expect(SQL_IDENTIFIER_REGEX.test("1starts_with_number")).toBe(false);
|
|
38
|
+
expect(SQL_IDENTIFIER_REGEX.test("has space")).toBe(false);
|
|
39
|
+
expect(SQL_IDENTIFIER_REGEX.test("has-dash")).toBe(false);
|
|
40
|
+
expect(SQL_IDENTIFIER_REGEX.test("has.dot")).toBe(false);
|
|
41
|
+
expect(SQL_IDENTIFIER_REGEX.test("DROP TABLE")).toBe(false);
|
|
42
|
+
expect(SQL_IDENTIFIER_REGEX.test("users; --")).toBe(false);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.init.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/storage.init.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { create_client, create_storage, MIGRATIONS_TABLE, THREADS_TABLE, MEMORIES_TABLE, } from "./helpers.js";
|
|
3
|
+
describe("LibSQLStorage init", () => {
|
|
4
|
+
let client;
|
|
5
|
+
beforeEach(() => {
|
|
6
|
+
client = create_client();
|
|
7
|
+
});
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
client.close();
|
|
10
|
+
});
|
|
11
|
+
it("auto-initializes on first store operation", async () => {
|
|
12
|
+
const storage = create_storage(client);
|
|
13
|
+
// Tables shouldn't exist yet
|
|
14
|
+
const before = await client.execute({
|
|
15
|
+
sql: "SELECT name FROM sqlite_master WHERE type='table' AND name=?",
|
|
16
|
+
args: [THREADS_TABLE],
|
|
17
|
+
});
|
|
18
|
+
expect(before.rows.length).toBe(0);
|
|
19
|
+
// Trigger init via store operation
|
|
20
|
+
await storage.memories.list();
|
|
21
|
+
// Now tables should exist
|
|
22
|
+
const after = await client.execute({
|
|
23
|
+
sql: "SELECT name FROM sqlite_master WHERE type='table' AND name=?",
|
|
24
|
+
args: [THREADS_TABLE],
|
|
25
|
+
});
|
|
26
|
+
expect(after.rows.length).toBe(1);
|
|
27
|
+
});
|
|
28
|
+
it("creates migrations table and applies migrations", async () => {
|
|
29
|
+
const storage = create_storage(client);
|
|
30
|
+
// Trigger init
|
|
31
|
+
await storage.memories.list();
|
|
32
|
+
// Check migrations table exists and has records
|
|
33
|
+
const result = await client.execute(`SELECT * FROM "${MIGRATIONS_TABLE}"`);
|
|
34
|
+
expect(result.rows.length).toBeGreaterThan(0);
|
|
35
|
+
// Verify migration IDs
|
|
36
|
+
const migrationIds = result.rows.map((r) => r.id);
|
|
37
|
+
expect(migrationIds).toContain("001_threads");
|
|
38
|
+
});
|
|
39
|
+
it("is idempotent across multiple init calls", async () => {
|
|
40
|
+
const storage = create_storage(client);
|
|
41
|
+
// Trigger init multiple times
|
|
42
|
+
await storage.memories.list();
|
|
43
|
+
await storage.threads.list();
|
|
44
|
+
await storage.memories.list();
|
|
45
|
+
// Should still work and have same migrations
|
|
46
|
+
const result = await client.execute(`SELECT * FROM "${MIGRATIONS_TABLE}"`);
|
|
47
|
+
const migrationCount = result.rows.length;
|
|
48
|
+
// Trigger again
|
|
49
|
+
await storage.memories.list();
|
|
50
|
+
const result2 = await client.execute(`SELECT * FROM "${MIGRATIONS_TABLE}"`);
|
|
51
|
+
expect(result2.rows.length).toBe(migrationCount);
|
|
52
|
+
});
|
|
53
|
+
it("creates all required tables", async () => {
|
|
54
|
+
const storage = create_storage(client);
|
|
55
|
+
await storage.memories.list();
|
|
56
|
+
const tables = await client.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name");
|
|
57
|
+
const tableNames = tables.rows.map((r) => r.name);
|
|
58
|
+
expect(tableNames).toContain(MIGRATIONS_TABLE);
|
|
59
|
+
expect(tableNames).toContain(THREADS_TABLE);
|
|
60
|
+
expect(tableNames).toContain(MEMORIES_TABLE);
|
|
61
|
+
expect(tableNames).toContain("kernl_thread_events");
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thread.lifecycle.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/thread.lifecycle.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { message, IN_PROGRESS, COMPLETED } from "@kernl-sdk/protocol";
|
|
3
|
+
import { create_client, create_storage, create_mock_registries, THREAD_EVENTS_TABLE, testid, } from "./helpers.js";
|
|
4
|
+
/** Create a ThreadEvent from a message */
|
|
5
|
+
function evt(id, tid, seq, timestamp, role, text) {
|
|
6
|
+
return {
|
|
7
|
+
...message({ role, text }),
|
|
8
|
+
id,
|
|
9
|
+
tid,
|
|
10
|
+
seq,
|
|
11
|
+
timestamp,
|
|
12
|
+
metadata: {},
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/** Create a tool.call ThreadEvent */
|
|
16
|
+
function toolCallEvt(id, tid, seq, timestamp, callId, toolId) {
|
|
17
|
+
return {
|
|
18
|
+
kind: "tool.call",
|
|
19
|
+
id,
|
|
20
|
+
tid,
|
|
21
|
+
seq,
|
|
22
|
+
timestamp,
|
|
23
|
+
callId,
|
|
24
|
+
toolId,
|
|
25
|
+
state: IN_PROGRESS,
|
|
26
|
+
arguments: "{}",
|
|
27
|
+
metadata: {},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** Create a tool.result ThreadEvent */
|
|
31
|
+
function toolResultEvt(id, tid, seq, timestamp, callId, toolId) {
|
|
32
|
+
return {
|
|
33
|
+
kind: "tool.result",
|
|
34
|
+
id,
|
|
35
|
+
tid,
|
|
36
|
+
seq,
|
|
37
|
+
timestamp,
|
|
38
|
+
callId,
|
|
39
|
+
toolId,
|
|
40
|
+
state: COMPLETED,
|
|
41
|
+
result: null,
|
|
42
|
+
error: null,
|
|
43
|
+
metadata: {},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
describe("LibSQL thread lifecycle", () => {
|
|
47
|
+
let client;
|
|
48
|
+
let storage;
|
|
49
|
+
beforeEach(async () => {
|
|
50
|
+
client = create_client();
|
|
51
|
+
storage = create_storage(client);
|
|
52
|
+
storage.bind(create_mock_registries());
|
|
53
|
+
// Initialize tables
|
|
54
|
+
await storage.memories.list();
|
|
55
|
+
});
|
|
56
|
+
afterEach(() => {
|
|
57
|
+
client.close();
|
|
58
|
+
});
|
|
59
|
+
it("persists thread and events for simple run", async () => {
|
|
60
|
+
const tid = testid("thread");
|
|
61
|
+
// Insert thread
|
|
62
|
+
const thread = await storage.threads.insert({
|
|
63
|
+
id: tid,
|
|
64
|
+
namespace: "default",
|
|
65
|
+
agentId: "test-agent",
|
|
66
|
+
model: "test/model",
|
|
67
|
+
context: { userId: "user-1" },
|
|
68
|
+
metadata: { title: "Test Thread" },
|
|
69
|
+
});
|
|
70
|
+
expect(thread.tid).toBe(tid);
|
|
71
|
+
// Append events
|
|
72
|
+
const now = Date.now();
|
|
73
|
+
await storage.threads.append([
|
|
74
|
+
evt("evt-1", tid, 1, new Date(now), "user", "Hello"),
|
|
75
|
+
evt("evt-2", tid, 2, new Date(now + 1), "assistant", "Hi!"),
|
|
76
|
+
]);
|
|
77
|
+
// Verify in database
|
|
78
|
+
const events = await client.execute({
|
|
79
|
+
sql: `SELECT * FROM "${THREAD_EVENTS_TABLE}" WHERE tid = ? ORDER BY seq`,
|
|
80
|
+
args: [tid],
|
|
81
|
+
});
|
|
82
|
+
expect(events.rows.length).toBe(2);
|
|
83
|
+
expect(events.rows[0].kind).toBe("message");
|
|
84
|
+
expect(events.rows[1].seq).toBe(2);
|
|
85
|
+
});
|
|
86
|
+
it("persists tool call events across ticks", async () => {
|
|
87
|
+
const tid = testid("thread");
|
|
88
|
+
await storage.threads.insert({
|
|
89
|
+
id: tid,
|
|
90
|
+
namespace: "default",
|
|
91
|
+
agentId: "test-agent",
|
|
92
|
+
model: "test/model",
|
|
93
|
+
});
|
|
94
|
+
const now = Date.now();
|
|
95
|
+
// Tick 1: user message + assistant with tool call
|
|
96
|
+
await storage.threads.append([
|
|
97
|
+
evt("evt-1", tid, 1, new Date(now), "user", "search for something"),
|
|
98
|
+
toolCallEvt("evt-2", tid, 2, new Date(now + 1), "call-1", "search"),
|
|
99
|
+
]);
|
|
100
|
+
await storage.threads.update(tid, { tick: 1, state: "interruptible" });
|
|
101
|
+
// Tick 2: tool result + final response
|
|
102
|
+
await storage.threads.append([
|
|
103
|
+
toolResultEvt("evt-3", tid, 3, new Date(now + 2), "call-1", "search"),
|
|
104
|
+
evt("evt-4", tid, 4, new Date(now + 3), "assistant", "I found it"),
|
|
105
|
+
]);
|
|
106
|
+
await storage.threads.update(tid, { tick: 2, state: "stopped" });
|
|
107
|
+
// Verify all events persisted
|
|
108
|
+
const history = await storage.threads.history(tid);
|
|
109
|
+
expect(history.length).toBe(4);
|
|
110
|
+
expect(history.map((e) => e.kind)).toEqual([
|
|
111
|
+
"message",
|
|
112
|
+
"tool.call",
|
|
113
|
+
"tool.result",
|
|
114
|
+
"message",
|
|
115
|
+
]);
|
|
116
|
+
});
|
|
117
|
+
it("resumes a thread and appends new events", async () => {
|
|
118
|
+
const tid = testid("thread");
|
|
119
|
+
// Create thread with initial events
|
|
120
|
+
await storage.threads.insert({
|
|
121
|
+
id: tid,
|
|
122
|
+
namespace: "default",
|
|
123
|
+
agentId: "test-agent",
|
|
124
|
+
model: "test/model",
|
|
125
|
+
});
|
|
126
|
+
const now = Date.now();
|
|
127
|
+
await storage.threads.append([
|
|
128
|
+
evt("evt-1", tid, 1, new Date(now), "user", "hello"),
|
|
129
|
+
evt("evt-2", tid, 2, new Date(now + 1), "assistant", "hi"),
|
|
130
|
+
]);
|
|
131
|
+
await storage.threads.update(tid, { tick: 1, state: "stopped" });
|
|
132
|
+
// Resume: get thread with history
|
|
133
|
+
const resumed = await storage.threads.get(tid, { history: true });
|
|
134
|
+
expect(resumed).not.toBeNull();
|
|
135
|
+
// Append more events
|
|
136
|
+
await storage.threads.append([
|
|
137
|
+
evt("evt-3", tid, 3, new Date(now + 100), "user", "continue"),
|
|
138
|
+
evt("evt-4", tid, 4, new Date(now + 101), "assistant", "ok"),
|
|
139
|
+
]);
|
|
140
|
+
await storage.threads.update(tid, { tick: 2 });
|
|
141
|
+
// Verify total history
|
|
142
|
+
const history = await storage.threads.history(tid);
|
|
143
|
+
expect(history.length).toBe(4);
|
|
144
|
+
});
|
|
145
|
+
it("keeps event seq monotonic across runs", async () => {
|
|
146
|
+
const tid = testid("thread");
|
|
147
|
+
await storage.threads.insert({
|
|
148
|
+
id: tid,
|
|
149
|
+
namespace: "default",
|
|
150
|
+
agentId: "test-agent",
|
|
151
|
+
model: "test/model",
|
|
152
|
+
});
|
|
153
|
+
const now = Date.now();
|
|
154
|
+
// Run 1
|
|
155
|
+
await storage.threads.append([
|
|
156
|
+
evt("r1-1", tid, 1, new Date(now), "user", "m1"),
|
|
157
|
+
evt("r1-2", tid, 2, new Date(now + 1), "assistant", "m2"),
|
|
158
|
+
]);
|
|
159
|
+
// Run 2 (continuing sequence)
|
|
160
|
+
await storage.threads.append([
|
|
161
|
+
evt("r2-1", tid, 3, new Date(now + 100), "user", "m3"),
|
|
162
|
+
evt("r2-2", tid, 4, new Date(now + 101), "assistant", "m4"),
|
|
163
|
+
]);
|
|
164
|
+
// Run 3
|
|
165
|
+
await storage.threads.append([
|
|
166
|
+
evt("r3-1", tid, 5, new Date(now + 200), "user", "m5"),
|
|
167
|
+
]);
|
|
168
|
+
const history = await storage.threads.history(tid);
|
|
169
|
+
const seqs = history.map((e) => e.seq);
|
|
170
|
+
expect(seqs).toEqual([1, 2, 3, 4, 5]);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transaction.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/transaction.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { UnimplementedError } from "@kernl-sdk/shared/lib";
|
|
3
|
+
import { create_client, create_storage } from "./helpers.js";
|
|
4
|
+
describe("LibSQLStorage transaction", () => {
|
|
5
|
+
let client;
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
client = create_client();
|
|
8
|
+
});
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
client.close();
|
|
11
|
+
});
|
|
12
|
+
it("throws UnimplementedError until implemented", async () => {
|
|
13
|
+
const storage = create_storage(client);
|
|
14
|
+
await expect(storage.transaction(async () => { })).rejects.toThrow(UnimplementedError);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/utils.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { parsejson } from "../utils.js";
|
|
3
|
+
describe("libsql utils", () => {
|
|
4
|
+
describe("parsejson", () => {
|
|
5
|
+
it("parses JSON strings", () => {
|
|
6
|
+
expect(parsejson('{"foo": "bar"}')).toEqual({ foo: "bar" });
|
|
7
|
+
expect(parsejson("[1, 2, 3]")).toEqual([1, 2, 3]);
|
|
8
|
+
expect(parsejson('"hello"')).toEqual("hello");
|
|
9
|
+
expect(parsejson("42")).toEqual(42);
|
|
10
|
+
expect(parsejson("true")).toEqual(true);
|
|
11
|
+
expect(parsejson("null")).toEqual(null);
|
|
12
|
+
});
|
|
13
|
+
it("returns null on invalid JSON", () => {
|
|
14
|
+
expect(parsejson("{invalid}")).toBeNull();
|
|
15
|
+
expect(parsejson("not json")).toBeNull();
|
|
16
|
+
expect(parsejson("{foo: bar}")).toBeNull();
|
|
17
|
+
});
|
|
18
|
+
it("returns null for null/undefined input", () => {
|
|
19
|
+
expect(parsejson(null)).toBeNull();
|
|
20
|
+
expect(parsejson(undefined)).toBeNull();
|
|
21
|
+
});
|
|
22
|
+
it("passes through non-strings as-is", () => {
|
|
23
|
+
const obj = { foo: "bar" };
|
|
24
|
+
expect(parsejson(obj)).toBe(obj);
|
|
25
|
+
const arr = [1, 2, 3];
|
|
26
|
+
expect(parsejson(arr)).toBe(arr);
|
|
27
|
+
expect(parsejson(42)).toBe(42);
|
|
28
|
+
expect(parsejson(true)).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
});
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LibSQL client configuration and factory.
|
|
3
|
+
*/
|
|
4
|
+
import type { KernlStorage } from "kernl";
|
|
5
|
+
import { type Client } from "@libsql/client";
|
|
6
|
+
/**
|
|
7
|
+
* LibSQL connection configuration.
|
|
8
|
+
*
|
|
9
|
+
* Supports:
|
|
10
|
+
* - Local SQLite files: `file:./data.db` or `file:/path/to/data.db`
|
|
11
|
+
* - In-memory: `:memory:`
|
|
12
|
+
* - Remote Turso: `libsql://your-db.turso.io`
|
|
13
|
+
*/
|
|
14
|
+
export type LibSQLConnectionConfig = {
|
|
15
|
+
client: Client;
|
|
16
|
+
} | {
|
|
17
|
+
url: string;
|
|
18
|
+
authToken?: string;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* LibSQL storage configuration.
|
|
22
|
+
*/
|
|
23
|
+
export type LibSQLConfig = LibSQLConnectionConfig;
|
|
24
|
+
/**
|
|
25
|
+
* Create a LibSQL storage adapter for Kernl.
|
|
26
|
+
*
|
|
27
|
+
* @example Local file
|
|
28
|
+
* ```ts
|
|
29
|
+
* const storage = libsql({ url: 'file:./kernl.db' });
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @example In-memory (for testing)
|
|
33
|
+
* ```ts
|
|
34
|
+
* const storage = libsql({ url: ':memory:' });
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @example Remote Turso
|
|
38
|
+
* ```ts
|
|
39
|
+
* const storage = libsql({
|
|
40
|
+
* url: 'libsql://your-db.turso.io',
|
|
41
|
+
* authToken: process.env.TURSO_AUTH_TOKEN
|
|
42
|
+
* });
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare function libsql(config: LibSQLConfig): KernlStorage;
|
|
46
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,EAAgB,KAAK,MAAM,EAAe,MAAM,gBAAgB,CAAC;AAIxE;;;;;;;GAOG;AACH,MAAM,MAAM,sBAAsB,GAC9B;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAExC;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,sBAAsB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CAIzD"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LibSQL client configuration and factory.
|
|
3
|
+
*/
|
|
4
|
+
import { createClient } from "@libsql/client";
|
|
5
|
+
import { LibSQLStorage } from "./storage.js";
|
|
6
|
+
/**
|
|
7
|
+
* Create a LibSQL storage adapter for Kernl.
|
|
8
|
+
*
|
|
9
|
+
* @example Local file
|
|
10
|
+
* ```ts
|
|
11
|
+
* const storage = libsql({ url: 'file:./kernl.db' });
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* @example In-memory (for testing)
|
|
15
|
+
* ```ts
|
|
16
|
+
* const storage = libsql({ url: ':memory:' });
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @example Remote Turso
|
|
20
|
+
* ```ts
|
|
21
|
+
* const storage = libsql({
|
|
22
|
+
* url: 'libsql://your-db.turso.io',
|
|
23
|
+
* authToken: process.env.TURSO_AUTH_TOKEN
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function libsql(config) {
|
|
28
|
+
const client = createLibSQLClient(config);
|
|
29
|
+
const url = "url" in config ? config.url : undefined;
|
|
30
|
+
return new LibSQLStorage({ client, url });
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create a LibSQL client from configuration.
|
|
34
|
+
*/
|
|
35
|
+
function createLibSQLClient(config) {
|
|
36
|
+
if ("client" in config) {
|
|
37
|
+
return config.client;
|
|
38
|
+
}
|
|
39
|
+
const clientConfig = {
|
|
40
|
+
url: config.url,
|
|
41
|
+
};
|
|
42
|
+
if (config.authToken) {
|
|
43
|
+
clientConfig.authToken = config.authToken;
|
|
44
|
+
}
|
|
45
|
+
return createClient(clientConfig);
|
|
46
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @kernl/libsql - LibSQL storage adapter for Kernl
|
|
3
3
|
*/
|
|
4
|
+
export { LibSQLStorage, type LibSQLStorageConfig } from "./storage.js";
|
|
5
|
+
export { LibSQLThreadStore } from "./thread/store.js";
|
|
6
|
+
export { LibSQLMemoryStore } from "./memory/store.js";
|
|
7
|
+
export { libsql, type LibSQLConfig, type LibSQLConnectionConfig, } from "./client.js";
|
|
8
|
+
export { MIGRATIONS, REQUIRED_SCHEMA_VERSION } from "./migrations.js";
|
|
4
9
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,KAAK,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EACL,MAAM,EACN,KAAK,YAAY,EACjB,KAAK,sBAAsB,GAC5B,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,UAAU,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* @kernl/libsql - LibSQL storage adapter for Kernl
|
|
4
3
|
*/
|
|
4
|
+
export { LibSQLStorage } from "./storage.js";
|
|
5
|
+
export { LibSQLThreadStore } from "./thread/store.js";
|
|
6
|
+
export { LibSQLMemoryStore } from "./memory/store.js";
|
|
7
|
+
export { libsql, } from "./client.js";
|
|
8
|
+
export { MIGRATIONS, REQUIRED_SCHEMA_VERSION } from "./migrations.js";
|