@kernl-sdk/libsql 0.1.38 → 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.
Files changed (137) hide show
  1. package/.turbo/turbo-build.log +5 -4
  2. package/CHANGELOG.md +8 -0
  3. package/README.md +225 -0
  4. package/dist/__tests__/constraints.test.d.ts +2 -0
  5. package/dist/__tests__/constraints.test.d.ts.map +1 -0
  6. package/dist/__tests__/constraints.test.js +97 -0
  7. package/dist/__tests__/helpers.d.ts +36 -0
  8. package/dist/__tests__/helpers.d.ts.map +1 -0
  9. package/dist/__tests__/helpers.js +80 -0
  10. package/dist/__tests__/memory.create-get.test.d.ts +2 -0
  11. package/dist/__tests__/memory.create-get.test.d.ts.map +1 -0
  12. package/dist/__tests__/memory.create-get.test.js +8 -0
  13. package/dist/__tests__/memory.delete.test.d.ts +2 -0
  14. package/dist/__tests__/memory.delete.test.d.ts.map +1 -0
  15. package/dist/__tests__/memory.delete.test.js +6 -0
  16. package/dist/__tests__/memory.list.test.d.ts +2 -0
  17. package/dist/__tests__/memory.list.test.d.ts.map +1 -0
  18. package/dist/__tests__/memory.list.test.js +8 -0
  19. package/dist/__tests__/memory.update.test.d.ts +2 -0
  20. package/dist/__tests__/memory.update.test.d.ts.map +1 -0
  21. package/dist/__tests__/memory.update.test.js +8 -0
  22. package/dist/__tests__/migrations.test.d.ts +2 -0
  23. package/dist/__tests__/migrations.test.d.ts.map +1 -0
  24. package/dist/__tests__/migrations.test.js +68 -0
  25. package/dist/__tests__/row-codecs.test.d.ts +2 -0
  26. package/dist/__tests__/row-codecs.test.d.ts.map +1 -0
  27. package/dist/__tests__/row-codecs.test.js +175 -0
  28. package/dist/__tests__/sql-utils.test.d.ts +2 -0
  29. package/dist/__tests__/sql-utils.test.d.ts.map +1 -0
  30. package/dist/__tests__/sql-utils.test.js +45 -0
  31. package/dist/__tests__/storage.init.test.d.ts +2 -0
  32. package/dist/__tests__/storage.init.test.d.ts.map +1 -0
  33. package/dist/__tests__/storage.init.test.js +63 -0
  34. package/dist/__tests__/thread.lifecycle.test.d.ts +2 -0
  35. package/dist/__tests__/thread.lifecycle.test.d.ts.map +1 -0
  36. package/dist/__tests__/thread.lifecycle.test.js +172 -0
  37. package/dist/__tests__/transaction.test.d.ts +2 -0
  38. package/dist/__tests__/transaction.test.d.ts.map +1 -0
  39. package/dist/__tests__/transaction.test.js +16 -0
  40. package/dist/__tests__/utils.test.d.ts +2 -0
  41. package/dist/__tests__/utils.test.d.ts.map +1 -0
  42. package/dist/__tests__/utils.test.js +31 -0
  43. package/dist/client.d.ts +46 -0
  44. package/dist/client.d.ts.map +1 -0
  45. package/dist/client.js +46 -0
  46. package/dist/index.d.ts +5 -0
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js +5 -1
  49. package/dist/memory/__tests__/create-get.test.d.ts +2 -0
  50. package/dist/memory/__tests__/create-get.test.d.ts.map +1 -0
  51. package/dist/memory/__tests__/create-get.test.js +126 -0
  52. package/dist/memory/__tests__/delete.test.d.ts +2 -0
  53. package/dist/memory/__tests__/delete.test.d.ts.map +1 -0
  54. package/dist/memory/__tests__/delete.test.js +96 -0
  55. package/dist/memory/__tests__/list.test.d.ts +2 -0
  56. package/dist/memory/__tests__/list.test.d.ts.map +1 -0
  57. package/dist/memory/__tests__/list.test.js +168 -0
  58. package/dist/memory/__tests__/sql.test.d.ts +2 -0
  59. package/dist/memory/__tests__/sql.test.d.ts.map +1 -0
  60. package/dist/memory/__tests__/sql.test.js +159 -0
  61. package/dist/memory/__tests__/update.test.d.ts +2 -0
  62. package/dist/memory/__tests__/update.test.d.ts.map +1 -0
  63. package/dist/memory/__tests__/update.test.js +113 -0
  64. package/dist/memory/row.d.ts +11 -0
  65. package/dist/memory/row.d.ts.map +1 -0
  66. package/dist/memory/row.js +29 -0
  67. package/dist/memory/sql.d.ts +34 -0
  68. package/dist/memory/sql.d.ts.map +1 -0
  69. package/dist/memory/sql.js +109 -0
  70. package/dist/memory/store.d.ts +41 -0
  71. package/dist/memory/store.d.ts.map +1 -0
  72. package/dist/memory/store.js +132 -0
  73. package/dist/migrations.d.ts +32 -0
  74. package/dist/migrations.d.ts.map +1 -0
  75. package/dist/migrations.js +157 -0
  76. package/dist/sql.d.ts +28 -0
  77. package/dist/sql.d.ts.map +1 -0
  78. package/dist/sql.js +22 -0
  79. package/dist/storage.d.ts +75 -0
  80. package/dist/storage.d.ts.map +1 -0
  81. package/dist/storage.js +123 -0
  82. package/dist/thread/__tests__/append.test.d.ts +2 -0
  83. package/dist/thread/__tests__/append.test.d.ts.map +1 -0
  84. package/dist/thread/__tests__/append.test.js +141 -0
  85. package/dist/thread/__tests__/history.test.d.ts +2 -0
  86. package/dist/thread/__tests__/history.test.d.ts.map +1 -0
  87. package/dist/thread/__tests__/history.test.js +146 -0
  88. package/dist/thread/__tests__/sql.test.d.ts +2 -0
  89. package/dist/thread/__tests__/sql.test.d.ts.map +1 -0
  90. package/dist/thread/__tests__/sql.test.js +129 -0
  91. package/dist/thread/__tests__/store.test.d.ts +2 -0
  92. package/dist/thread/__tests__/store.test.d.ts.map +1 -0
  93. package/dist/thread/__tests__/store.test.js +170 -0
  94. package/dist/thread/row.d.ts +19 -0
  95. package/dist/thread/row.d.ts.map +1 -0
  96. package/dist/thread/row.js +65 -0
  97. package/dist/thread/sql.d.ts +33 -0
  98. package/dist/thread/sql.d.ts.map +1 -0
  99. package/dist/thread/sql.js +112 -0
  100. package/dist/thread/store.d.ts +67 -0
  101. package/dist/thread/store.d.ts.map +1 -0
  102. package/dist/thread/store.js +282 -0
  103. package/dist/utils.d.ts +10 -0
  104. package/dist/utils.d.ts.map +1 -0
  105. package/dist/utils.js +21 -0
  106. package/package.json +15 -11
  107. package/src/__tests__/constraints.test.ts +123 -0
  108. package/src/__tests__/helpers.ts +98 -0
  109. package/src/__tests__/migrations.test.ts +114 -0
  110. package/src/__tests__/row-codecs.test.ts +201 -0
  111. package/src/__tests__/sql-utils.test.ts +52 -0
  112. package/src/__tests__/storage.init.test.ts +92 -0
  113. package/src/__tests__/thread.lifecycle.test.ts +234 -0
  114. package/src/__tests__/transaction.test.ts +25 -0
  115. package/src/__tests__/utils.test.ts +38 -0
  116. package/src/client.ts +71 -0
  117. package/src/index.ts +10 -0
  118. package/src/memory/__tests__/create-get.test.ts +161 -0
  119. package/src/memory/__tests__/delete.test.ts +124 -0
  120. package/src/memory/__tests__/list.test.ts +198 -0
  121. package/src/memory/__tests__/sql.test.ts +186 -0
  122. package/src/memory/__tests__/update.test.ts +148 -0
  123. package/src/memory/row.ts +36 -0
  124. package/src/memory/sql.ts +142 -0
  125. package/src/memory/store.ts +173 -0
  126. package/src/migrations.ts +206 -0
  127. package/src/sql.ts +35 -0
  128. package/src/storage.ts +170 -0
  129. package/src/thread/__tests__/append.test.ts +201 -0
  130. package/src/thread/__tests__/history.test.ts +198 -0
  131. package/src/thread/__tests__/sql.test.ts +154 -0
  132. package/src/thread/__tests__/store.test.ts +219 -0
  133. package/src/thread/row.ts +77 -0
  134. package/src/thread/sql.ts +153 -0
  135. package/src/thread/store.ts +381 -0
  136. package/src/utils.ts +20 -0
  137. 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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sql-utils.test.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=storage.init.test.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=thread.lifecycle.test.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=transaction.test.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=utils.test.d.ts.map
@@ -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
+ });
@@ -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
@@ -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";
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=create-get.test.d.ts.map