@kernl-sdk/storage 0.1.9
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 +4 -0
- package/CHANGELOG.md +113 -0
- package/LICENSE +201 -0
- package/dist/__tests__/table.test.d.ts +2 -0
- package/dist/__tests__/table.test.d.ts.map +1 -0
- package/dist/__tests__/table.test.js +112 -0
- package/dist/base.d.ts +6 -0
- package/dist/base.d.ts.map +1 -0
- package/dist/base.js +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/serde/__tests__/thread.test.d.ts +2 -0
- package/dist/serde/__tests__/thread.test.d.ts.map +1 -0
- package/dist/serde/__tests__/thread.test.js +180 -0
- package/dist/serde/thread.d.ts +89 -0
- package/dist/serde/thread.d.ts.map +1 -0
- package/dist/serde/thread.js +139 -0
- package/dist/table.d.ts +87 -0
- package/dist/table.d.ts.map +1 -0
- package/dist/table.js +80 -0
- package/dist/thread/index.d.ts +7 -0
- package/dist/thread/index.d.ts.map +1 -0
- package/dist/thread/index.js +6 -0
- package/dist/thread/schema.d.ts +122 -0
- package/dist/thread/schema.d.ts.map +1 -0
- package/dist/thread/schema.js +141 -0
- package/dist/thread/store.d.ts +6 -0
- package/dist/thread/store.d.ts.map +1 -0
- package/dist/thread/store.js +1 -0
- package/dist/thread/types.d.ts +6 -0
- package/dist/thread/types.d.ts.map +1 -0
- package/dist/thread/types.js +1 -0
- package/dist/types.d.ts +52 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/package.json +51 -0
- package/src/__tests__/table.test.ts +134 -0
- package/src/base.ts +5 -0
- package/src/index.ts +8 -0
- package/src/serde/__tests__/thread.test.ts +218 -0
- package/src/serde/thread.ts +178 -0
- package/src/table.ts +199 -0
- package/src/thread/index.ts +7 -0
- package/src/thread/schema.ts +170 -0
- package/src/thread/store.ts +5 -0
- package/src/thread/types.ts +13 -0
- package/src/types.ts +67 -0
- package/tsconfig.json +15 -0
- package/vitest.config.ts +15 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thread record schemas and table definitions.
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { THREAD_STATES } from "kernl";
|
|
6
|
+
import { text, jsonb, bigint, integer, timestamps, defineTable } from "../table";
|
|
7
|
+
/* ---- Table Definitions ---- */
|
|
8
|
+
/**
|
|
9
|
+
* Threads table schema.
|
|
10
|
+
*/
|
|
11
|
+
export const TABLE_THREADS = defineTable("threads", {
|
|
12
|
+
id: text().primaryKey(),
|
|
13
|
+
namespace: text().default("kernl"),
|
|
14
|
+
agent_id: text(),
|
|
15
|
+
model: text(),
|
|
16
|
+
context: jsonb(),
|
|
17
|
+
parent_task_id: text().nullable(),
|
|
18
|
+
tick: integer().default(0),
|
|
19
|
+
state: text(),
|
|
20
|
+
metadata: jsonb().nullable(),
|
|
21
|
+
...timestamps,
|
|
22
|
+
}, [
|
|
23
|
+
{ kind: "index", columns: ["state"] },
|
|
24
|
+
{ kind: "index", columns: ["namespace"] },
|
|
25
|
+
{ kind: "index", columns: ["agent_id"] },
|
|
26
|
+
{ kind: "index", columns: ["parent_task_id"] },
|
|
27
|
+
{ kind: "index", columns: ["created_at"] },
|
|
28
|
+
{ kind: "index", columns: ["updated_at"] },
|
|
29
|
+
]);
|
|
30
|
+
/**
|
|
31
|
+
* Thread events table schema.
|
|
32
|
+
*/
|
|
33
|
+
export const TABLE_THREAD_EVENTS = defineTable("thread_events", {
|
|
34
|
+
id: text(),
|
|
35
|
+
tid: text().references(() => TABLE_THREADS.columns.id, {
|
|
36
|
+
onDelete: "CASCADE",
|
|
37
|
+
}),
|
|
38
|
+
seq: integer(),
|
|
39
|
+
kind: text(),
|
|
40
|
+
timestamp: bigint(),
|
|
41
|
+
data: jsonb().nullable(),
|
|
42
|
+
metadata: jsonb().nullable(),
|
|
43
|
+
}, [
|
|
44
|
+
{
|
|
45
|
+
kind: "unique",
|
|
46
|
+
columns: ["tid", "id"],
|
|
47
|
+
},
|
|
48
|
+
{ kind: "index", columns: ["tid", "seq"] }, // for ordering events within a thread
|
|
49
|
+
{ kind: "index", columns: ["tid", "kind"] }, // for filtering by thread + kind
|
|
50
|
+
]);
|
|
51
|
+
/**
|
|
52
|
+
* Migrations table.
|
|
53
|
+
*/
|
|
54
|
+
export const TABLE_MIGRATIONS = defineTable("migrations", {
|
|
55
|
+
id: text().primaryKey(),
|
|
56
|
+
applied_at: bigint(),
|
|
57
|
+
});
|
|
58
|
+
/* ---- Record Schemas ---- */
|
|
59
|
+
/**
|
|
60
|
+
* Thread record schema (zod-first).
|
|
61
|
+
*/
|
|
62
|
+
export const ThreadRecordSchema = z.object({
|
|
63
|
+
id: z.string(),
|
|
64
|
+
namespace: z.string(),
|
|
65
|
+
agent_id: z.string(),
|
|
66
|
+
model: z.string(), // composite: "provider/modelId"
|
|
67
|
+
context: z.unknown(), // JSONB - Context<TContext>
|
|
68
|
+
parent_task_id: z.string().nullable(),
|
|
69
|
+
tick: z.number().int().nonnegative(),
|
|
70
|
+
state: z.enum(THREAD_STATES),
|
|
71
|
+
created_at: z.number().int(),
|
|
72
|
+
updated_at: z.number().int(),
|
|
73
|
+
metadata: z.record(z.string(), z.unknown()).nullable(),
|
|
74
|
+
});
|
|
75
|
+
/**
|
|
76
|
+
* Thread event inner data - stored in JSONB column.
|
|
77
|
+
*
|
|
78
|
+
* Always an object (record) for non-system events. Inner data is validated by protocol layer
|
|
79
|
+
* and is already JSON-serializable. The actual structure depends on the event kind:
|
|
80
|
+
* - message: {role, content, ...}
|
|
81
|
+
* - tool-call: {callId, toolId, state, arguments}
|
|
82
|
+
* - tool-result: {callId, toolId, state, result, error}
|
|
83
|
+
* - reasoning: {text}
|
|
84
|
+
* - system: null (handled separately)
|
|
85
|
+
*/
|
|
86
|
+
export const ThreadEventInnerSchema = z.record(z.string(), z.unknown());
|
|
87
|
+
/**
|
|
88
|
+
* Thread event record base schema - common fields for all events.
|
|
89
|
+
*/
|
|
90
|
+
const ThreadEventRecordBaseSchema = z.object({
|
|
91
|
+
id: z.string(),
|
|
92
|
+
tid: z.string(),
|
|
93
|
+
seq: z.number().int().nonnegative(),
|
|
94
|
+
timestamp: z.number().int(), // epoch millis
|
|
95
|
+
metadata: z.record(z.string(), z.unknown()).nullable(),
|
|
96
|
+
});
|
|
97
|
+
/**
|
|
98
|
+
* Message event record (user, assistant, system messages).
|
|
99
|
+
*/
|
|
100
|
+
const ThreadMessageEventRecordSchema = ThreadEventRecordBaseSchema.extend({
|
|
101
|
+
kind: z.literal("message"),
|
|
102
|
+
data: ThreadEventInnerSchema, // Message data: {role, content, ...}
|
|
103
|
+
});
|
|
104
|
+
/**
|
|
105
|
+
* Reasoning event record.
|
|
106
|
+
*/
|
|
107
|
+
const ThreadReasoningEventRecordSchema = ThreadEventRecordBaseSchema.extend({
|
|
108
|
+
kind: z.literal("reasoning"),
|
|
109
|
+
data: ThreadEventInnerSchema, // Reasoning data: {text}
|
|
110
|
+
});
|
|
111
|
+
/**
|
|
112
|
+
* Tool call event record.
|
|
113
|
+
*/
|
|
114
|
+
const ThreadToolCallEventRecordSchema = ThreadEventRecordBaseSchema.extend({
|
|
115
|
+
kind: z.literal("tool-call"),
|
|
116
|
+
data: ThreadEventInnerSchema, // ToolCall data: {callId, toolId, state, arguments}
|
|
117
|
+
});
|
|
118
|
+
/**
|
|
119
|
+
* Tool result event record.
|
|
120
|
+
*/
|
|
121
|
+
const ThreadToolResultEventRecordSchema = ThreadEventRecordBaseSchema.extend({
|
|
122
|
+
kind: z.literal("tool-result"),
|
|
123
|
+
data: ThreadEventInnerSchema, // ToolResult data: {callId, toolId, state, result, error}
|
|
124
|
+
});
|
|
125
|
+
/**
|
|
126
|
+
* System event record - runtime state changes (not sent to model).
|
|
127
|
+
*/
|
|
128
|
+
const ThreadSystemEventRecordSchema = ThreadEventRecordBaseSchema.extend({
|
|
129
|
+
kind: z.literal("system"),
|
|
130
|
+
data: z.null(), // System events have no data
|
|
131
|
+
});
|
|
132
|
+
/**
|
|
133
|
+
* Thread event record schema (discriminated union by kind).
|
|
134
|
+
*/
|
|
135
|
+
export const ThreadEventRecordSchema = z.discriminatedUnion("kind", [
|
|
136
|
+
ThreadMessageEventRecordSchema,
|
|
137
|
+
ThreadReasoningEventRecordSchema,
|
|
138
|
+
ThreadToolCallEventRecordSchema,
|
|
139
|
+
ThreadToolResultEventRecordSchema,
|
|
140
|
+
ThreadSystemEventRecordSchema,
|
|
141
|
+
]);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/thread/store.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,YAAY,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thread storage DTOs are now defined in kernl.
|
|
3
|
+
* Re-export them here for convenience.
|
|
4
|
+
*/
|
|
5
|
+
export type { NewThread, ThreadUpdate, ThreadFilter, ThreadInclude, SortOrder, ThreadListOptions, ThreadHistoryOptions, } from "kernl";
|
|
6
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/thread/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,YAAY,EACV,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,SAAS,EACT,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,OAAO,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core storage abstractions for Kernl.
|
|
3
|
+
*
|
|
4
|
+
* Defines the top-level storage interface and transaction primitives.
|
|
5
|
+
*/
|
|
6
|
+
import type { ThreadStore } from "./thread";
|
|
7
|
+
/**
|
|
8
|
+
* The main storage interface for Kernl.
|
|
9
|
+
*
|
|
10
|
+
* Provides access to system stores (threads, tasks, traces) and transaction support.
|
|
11
|
+
*/
|
|
12
|
+
export interface KernlStorage {
|
|
13
|
+
/**
|
|
14
|
+
* Thread store - manages thread execution records and event history.
|
|
15
|
+
*/
|
|
16
|
+
threads: ThreadStore;
|
|
17
|
+
/**
|
|
18
|
+
* Initialize the storage backend.
|
|
19
|
+
*
|
|
20
|
+
* Connects to the database and ensures all required schemas/tables exist.
|
|
21
|
+
*/
|
|
22
|
+
init(): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Close the storage backend and cleanup resources.
|
|
25
|
+
*/
|
|
26
|
+
close(): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Execute a function within a transaction.
|
|
29
|
+
*
|
|
30
|
+
* All operations performed using the transaction-scoped stores will be
|
|
31
|
+
* committed atomically or rolled back on error.
|
|
32
|
+
*/
|
|
33
|
+
transaction<T>(fn: (tx: Transaction) => Promise<T>): Promise<T>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Transaction context providing transactional access to stores.
|
|
37
|
+
*/
|
|
38
|
+
export interface Transaction {
|
|
39
|
+
/**
|
|
40
|
+
* Thread store within this transaction.
|
|
41
|
+
*/
|
|
42
|
+
threads: ThreadStore;
|
|
43
|
+
/**
|
|
44
|
+
* Commit the transaction.
|
|
45
|
+
*/
|
|
46
|
+
commit(): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Rollback the transaction.
|
|
49
|
+
*/
|
|
50
|
+
rollback(): Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE5C;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,OAAO,EAAE,WAAW,CAAC;IAMrB;;;;OAIG;IACH,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEtB;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvB;;;;;OAKG;IACH,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACjE;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,OAAO,EAAE,WAAW,CAAC;IAMrB;;OAEG;IACH,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAExB;;OAEG;IACH,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B"}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kernl-sdk/storage",
|
|
3
|
+
"version": "0.1.9",
|
|
4
|
+
"description": "Core storage abstractions for kernl",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"kernl",
|
|
7
|
+
"storage",
|
|
8
|
+
"database"
|
|
9
|
+
],
|
|
10
|
+
"author": "dremnik",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/kernl-sdk/kernl.git",
|
|
15
|
+
"directory": "packages/storage/core"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://github.com/kernl-sdk/kernl#readme",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/kernl-sdk/kernl/issues"
|
|
20
|
+
},
|
|
21
|
+
"type": "module",
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^24.10.0",
|
|
33
|
+
"tsc-alias": "^1.8.10",
|
|
34
|
+
"typescript": "5.9.2",
|
|
35
|
+
"vitest": "^4.0.8"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"zod": "^4.1.12",
|
|
39
|
+
"kernl": "0.6.0",
|
|
40
|
+
"@kernl-sdk/shared": "0.1.5",
|
|
41
|
+
"@kernl-sdk/protocol": "0.2.4"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsc && tsc-alias",
|
|
45
|
+
"dev": "tsc --watch",
|
|
46
|
+
"check-types": "tsc --noEmit",
|
|
47
|
+
"test": "vitest",
|
|
48
|
+
"test:watch": "vitest --watch",
|
|
49
|
+
"test:run": "vitest run"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { text, integer, bigint, jsonb } from "../table";
|
|
3
|
+
|
|
4
|
+
describe("Column codec", () => {
|
|
5
|
+
describe("text column", () => {
|
|
6
|
+
it("should encode strings with single quotes", () => {
|
|
7
|
+
const col = text();
|
|
8
|
+
expect(col.encode("hello")).toBe("'hello'");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("should escape single quotes", () => {
|
|
12
|
+
const col = text();
|
|
13
|
+
expect(col.encode("it's")).toBe("'it''s'");
|
|
14
|
+
expect(col.encode("'quoted'")).toBe("'''quoted'''");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("should handle NULL values", () => {
|
|
18
|
+
const col = text();
|
|
19
|
+
expect(col.encode(null as any)).toBe("NULL");
|
|
20
|
+
expect(col.encode(undefined as any)).toBe("NULL");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should decode strings", () => {
|
|
24
|
+
const col = text();
|
|
25
|
+
expect(col.decode("hello")).toBe("hello");
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("integer column", () => {
|
|
30
|
+
it("should encode numbers as strings", () => {
|
|
31
|
+
const col = integer();
|
|
32
|
+
expect(col.encode(42)).toBe("42");
|
|
33
|
+
expect(col.encode(0)).toBe("0");
|
|
34
|
+
expect(col.encode(-100)).toBe("-100");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("should handle NULL values", () => {
|
|
38
|
+
const col = integer();
|
|
39
|
+
expect(col.encode(null as any)).toBe("NULL");
|
|
40
|
+
expect(col.encode(undefined as any)).toBe("NULL");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should decode strings to numbers", () => {
|
|
44
|
+
const col = integer();
|
|
45
|
+
expect(col.decode("42")).toBe(42);
|
|
46
|
+
expect(col.decode("-100")).toBe(-100);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("bigint column", () => {
|
|
51
|
+
it("should encode numbers as strings", () => {
|
|
52
|
+
const col = bigint();
|
|
53
|
+
expect(col.encode(9007199254740991)).toBe("9007199254740991");
|
|
54
|
+
expect(col.encode(0)).toBe("0");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should handle NULL values", () => {
|
|
58
|
+
const col = bigint();
|
|
59
|
+
expect(col.encode(null as any)).toBe("NULL");
|
|
60
|
+
expect(col.encode(undefined as any)).toBe("NULL");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should decode strings to numbers", () => {
|
|
64
|
+
const col = bigint();
|
|
65
|
+
expect(col.decode("9007199254740991")).toBe(9007199254740991);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("jsonb column", () => {
|
|
70
|
+
it("should encode objects as JSON strings", () => {
|
|
71
|
+
const col = jsonb();
|
|
72
|
+
expect(col.encode({ foo: "bar" })).toBe("'{\"foo\":\"bar\"}'");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should encode arrays", () => {
|
|
76
|
+
const col = jsonb();
|
|
77
|
+
expect(col.encode([1, 2, 3])).toBe("'[1,2,3]'");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should encode nested objects", () => {
|
|
81
|
+
const col = jsonb();
|
|
82
|
+
const obj = { user: { name: "Alice", age: 30 } };
|
|
83
|
+
expect(col.encode(obj)).toBe("'{\"user\":{\"name\":\"Alice\",\"age\":30}}'");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should handle NULL values", () => {
|
|
87
|
+
const col = jsonb();
|
|
88
|
+
expect(col.encode(null as any)).toBe("NULL");
|
|
89
|
+
expect(col.encode(undefined as any)).toBe("NULL");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("should decode JSON strings to objects", () => {
|
|
93
|
+
const col = jsonb();
|
|
94
|
+
expect(col.decode('{"foo":"bar"}')).toEqual({ foo: "bar" });
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("should decode JSON arrays", () => {
|
|
98
|
+
const col = jsonb();
|
|
99
|
+
expect(col.decode("[1,2,3]")).toEqual([1, 2, 3]);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("column builders", () => {
|
|
104
|
+
it("should support primaryKey()", () => {
|
|
105
|
+
const col = text().primaryKey();
|
|
106
|
+
expect(col._pk).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("should support nullable()", () => {
|
|
110
|
+
const col = text().nullable();
|
|
111
|
+
expect(col._nullable).toBe(true);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("should support unique()", () => {
|
|
115
|
+
const col = text().unique();
|
|
116
|
+
expect(col._unique).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("should support default() with type safety", () => {
|
|
120
|
+
const textCol = text().default("hello");
|
|
121
|
+
expect(textCol._default).toBe("hello");
|
|
122
|
+
|
|
123
|
+
const intCol = integer().default(42);
|
|
124
|
+
expect(intCol._default).toBe(42);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("should chain builder methods", () => {
|
|
128
|
+
const col = text().unique().nullable().default("test");
|
|
129
|
+
expect(col._unique).toBe(true);
|
|
130
|
+
expect(col._nullable).toBe(true);
|
|
131
|
+
expect(col._default).toBe("test");
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
});
|
package/src/base.ts
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
2
|
+
import { RUNNING, STOPPED } from "@kernl-sdk/protocol";
|
|
3
|
+
|
|
4
|
+
import { NewThreadCodec } from "../thread";
|
|
5
|
+
|
|
6
|
+
describe("NewThreadCodec", () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
vi.useFakeTimers();
|
|
9
|
+
vi.setSystemTime(new Date("2024-01-01T00:00:00.000Z"));
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe("encode", () => {
|
|
13
|
+
it("should apply default values (tick=0, state='stopped')", () => {
|
|
14
|
+
const newThread = {
|
|
15
|
+
id: "thread-1",
|
|
16
|
+
namespace: "kernl",
|
|
17
|
+
agentId: "agent-1",
|
|
18
|
+
model: "openai/gpt-4",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const record = NewThreadCodec.encode(newThread);
|
|
22
|
+
|
|
23
|
+
expect(record.tick).toBe(0);
|
|
24
|
+
expect(record.state).toBe("stopped");
|
|
25
|
+
expect(record.parent_task_id).toBeNull();
|
|
26
|
+
expect(record.metadata).toBeNull();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should preserve provided values", () => {
|
|
30
|
+
const newThread = {
|
|
31
|
+
id: "thread-2",
|
|
32
|
+
namespace: "kernl",
|
|
33
|
+
agentId: "agent-2",
|
|
34
|
+
model: "openai/gpt-4",
|
|
35
|
+
tick: 5,
|
|
36
|
+
state: RUNNING as any,
|
|
37
|
+
metadata: { key: "value" },
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const record = NewThreadCodec.encode(newThread);
|
|
41
|
+
|
|
42
|
+
expect(record.tick).toBe(5);
|
|
43
|
+
expect(record.state).toBe(RUNNING);
|
|
44
|
+
expect(record.metadata).toEqual({ key: "value" });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("should apply current timestamp when not provided", () => {
|
|
48
|
+
const now = Date.now(); // Mocked to 2024-01-01T00:00:00.000Z
|
|
49
|
+
|
|
50
|
+
const newThread = {
|
|
51
|
+
id: "thread-3",
|
|
52
|
+
namespace: "kernl",
|
|
53
|
+
agentId: "agent-3",
|
|
54
|
+
model: "anthropic/claude-3",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const record = NewThreadCodec.encode(newThread);
|
|
58
|
+
|
|
59
|
+
expect(record.created_at).toBe(now);
|
|
60
|
+
expect(record.updated_at).toBe(now);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should preserve provided timestamps", () => {
|
|
64
|
+
const createdAt = new Date("2023-12-01T00:00:00.000Z");
|
|
65
|
+
const updatedAt = new Date("2023-12-15T00:00:00.000Z");
|
|
66
|
+
|
|
67
|
+
const newThread = {
|
|
68
|
+
id: "thread-4",
|
|
69
|
+
namespace: "kernl",
|
|
70
|
+
agentId: "agent-4",
|
|
71
|
+
model: "openai/gpt-4",
|
|
72
|
+
createdAt,
|
|
73
|
+
updatedAt,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const record = NewThreadCodec.encode(newThread);
|
|
77
|
+
|
|
78
|
+
expect(record.created_at).toBe(createdAt.getTime());
|
|
79
|
+
expect(record.updated_at).toBe(updatedAt.getTime());
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("should handle context", () => {
|
|
83
|
+
const context = { foo: "bar", nested: { key: "value" } };
|
|
84
|
+
|
|
85
|
+
const newThread = {
|
|
86
|
+
id: "thread-5",
|
|
87
|
+
namespace: "kernl",
|
|
88
|
+
agentId: "agent-5",
|
|
89
|
+
model: "openai/gpt-4",
|
|
90
|
+
context: context as any,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const record = NewThreadCodec.encode(newThread);
|
|
94
|
+
|
|
95
|
+
expect(record.context).toEqual(context);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should apply empty object as default context", () => {
|
|
99
|
+
const newThread = {
|
|
100
|
+
id: "thread-6",
|
|
101
|
+
namespace: "kernl",
|
|
102
|
+
agentId: "agent-6",
|
|
103
|
+
model: "openai/gpt-4",
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const record = NewThreadCodec.encode(newThread);
|
|
107
|
+
|
|
108
|
+
expect(record.context).toEqual({});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("should map model field correctly", () => {
|
|
112
|
+
const newThread = {
|
|
113
|
+
id: "thread-7",
|
|
114
|
+
namespace: "kernl",
|
|
115
|
+
agentId: "agent-7",
|
|
116
|
+
model: "provider/model-name",
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const record = NewThreadCodec.encode(newThread);
|
|
120
|
+
|
|
121
|
+
expect(record.model).toBe("provider/model-name");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("should handle parentTaskId", () => {
|
|
125
|
+
const newThread = {
|
|
126
|
+
id: "thread-8",
|
|
127
|
+
namespace: "kernl",
|
|
128
|
+
agentId: "agent-8",
|
|
129
|
+
model: "openai/gpt-4",
|
|
130
|
+
parentTaskId: "task-123",
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const record = NewThreadCodec.encode(newThread);
|
|
134
|
+
|
|
135
|
+
expect(record.parent_task_id).toBe("task-123");
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe("decode", () => {
|
|
140
|
+
it("should decode ThreadRecord to NewThread", () => {
|
|
141
|
+
const record = {
|
|
142
|
+
id: "thread-1",
|
|
143
|
+
namespace: "kernl",
|
|
144
|
+
agent_id: "agent-1",
|
|
145
|
+
model: "openai/gpt-4",
|
|
146
|
+
context: { foo: "bar" },
|
|
147
|
+
tick: 5,
|
|
148
|
+
state: RUNNING as any,
|
|
149
|
+
parent_task_id: "task-1",
|
|
150
|
+
metadata: { key: "value" },
|
|
151
|
+
created_at: 1704067200000, // 2024-01-01T00:00:00.000Z
|
|
152
|
+
updated_at: 1704070800000, // 2024-01-01T01:00:00.000Z
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const newThread = NewThreadCodec.decode(record);
|
|
156
|
+
|
|
157
|
+
expect(newThread.id).toBe("thread-1");
|
|
158
|
+
expect(newThread.agentId).toBe("agent-1");
|
|
159
|
+
expect(newThread.model).toBe("openai/gpt-4");
|
|
160
|
+
expect(newThread.context).toEqual({ foo: "bar" });
|
|
161
|
+
expect(newThread.tick).toBe(5);
|
|
162
|
+
expect(newThread.state).toBe(RUNNING);
|
|
163
|
+
expect(newThread.parentTaskId).toBe("task-1");
|
|
164
|
+
expect(newThread.metadata).toEqual({ key: "value" });
|
|
165
|
+
expect(newThread.createdAt).toEqual(new Date(1704067200000));
|
|
166
|
+
expect(newThread.updatedAt).toEqual(new Date(1704070800000));
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("should handle null values", () => {
|
|
170
|
+
const record = {
|
|
171
|
+
id: "thread-2",
|
|
172
|
+
namespace: "kernl",
|
|
173
|
+
agent_id: "agent-2",
|
|
174
|
+
model: "openai/gpt-4",
|
|
175
|
+
context: {},
|
|
176
|
+
tick: 0,
|
|
177
|
+
state: STOPPED as any,
|
|
178
|
+
parent_task_id: null,
|
|
179
|
+
metadata: null,
|
|
180
|
+
created_at: 1704067200000,
|
|
181
|
+
updated_at: 1704067200000,
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const newThread = NewThreadCodec.decode(record);
|
|
185
|
+
|
|
186
|
+
expect(newThread.parentTaskId).toBeNull();
|
|
187
|
+
expect(newThread.metadata).toBeNull();
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe("round-trip encoding/decoding", () => {
|
|
192
|
+
it("should preserve data through encode -> decode cycle", () => {
|
|
193
|
+
const original = {
|
|
194
|
+
id: "thread-rt",
|
|
195
|
+
namespace: "kernl",
|
|
196
|
+
agentId: "agent-rt",
|
|
197
|
+
model: "openai/gpt-4",
|
|
198
|
+
tick: 10,
|
|
199
|
+
state: RUNNING as any,
|
|
200
|
+
context: { test: true } as any,
|
|
201
|
+
parentTaskId: "task-rt",
|
|
202
|
+
metadata: { source: "test" },
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const encoded = NewThreadCodec.encode(original);
|
|
206
|
+
const decoded = NewThreadCodec.decode(encoded);
|
|
207
|
+
|
|
208
|
+
expect(decoded.id).toBe(original.id);
|
|
209
|
+
expect(decoded.agentId).toBe(original.agentId);
|
|
210
|
+
expect(decoded.model).toBe(original.model);
|
|
211
|
+
expect(decoded.tick).toBe(original.tick);
|
|
212
|
+
expect(decoded.state).toBe(original.state);
|
|
213
|
+
expect(decoded.context).toEqual(original.context);
|
|
214
|
+
expect(decoded.parentTaskId).toBe(original.parentTaskId);
|
|
215
|
+
expect(decoded.metadata).toEqual(original.metadata);
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
});
|