@kernl-sdk/storage 0.1.11 → 0.1.12
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 +1 -1
- package/.turbo/turbo-check-types.log +4 -0
- package/CHANGELOG.md +7 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/memory/index.d.ts +5 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/index.js +4 -0
- package/dist/memory/schema.d.ts +55 -0
- package/dist/memory/schema.d.ts.map +1 -0
- package/dist/memory/schema.js +74 -0
- package/dist/serde/__tests__/memory.test.d.ts +2 -0
- package/dist/serde/__tests__/memory.test.d.ts.map +1 -0
- package/dist/serde/__tests__/memory.test.js +244 -0
- package/dist/serde/memory.d.ts +68 -0
- package/dist/serde/memory.d.ts.map +1 -0
- package/dist/serde/memory.js +99 -0
- package/dist/table.d.ts +3 -2
- package/dist/table.d.ts.map +1 -1
- package/dist/table.js +6 -1
- package/package.json +3 -3
- package/src/index.ts +2 -0
- package/src/memory/index.ts +9 -0
- package/src/memory/schema.ts +93 -0
- package/src/serde/__tests__/memory.test.ts +277 -0
- package/src/serde/memory.ts +111 -0
- package/src/table.ts +7 -2
- package/tsconfig.tsbuildinfo +1 -0
package/.turbo/turbo-build.log
CHANGED
package/CHANGELOG.md
CHANGED
package/dist/index.d.ts
CHANGED
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;AAEH,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/memory/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,KAAK,cAAc,GACpB,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory table definition and record schema.
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
export declare const TABLE_MEMORIES: import("../table.js").Table<"memories", {
|
|
6
|
+
readonly metadata: import("../table.js").Column<unknown>;
|
|
7
|
+
readonly created_at: import("../table.js").Column<number>;
|
|
8
|
+
readonly updated_at: import("../table.js").Column<number>;
|
|
9
|
+
readonly id: import("../table.js").Column<string>;
|
|
10
|
+
readonly namespace: import("../table.js").Column<string>;
|
|
11
|
+
readonly entity_id: import("../table.js").Column<string>;
|
|
12
|
+
readonly agent_id: import("../table.js").Column<string>;
|
|
13
|
+
readonly kind: import("../table.js").Column<string>;
|
|
14
|
+
readonly collection: import("../table.js").Column<string>;
|
|
15
|
+
readonly content: import("../table.js").Column<unknown>;
|
|
16
|
+
readonly wmem: import("../table.js").Column<boolean>;
|
|
17
|
+
readonly smem_expires_at: import("../table.js").Column<number>;
|
|
18
|
+
readonly timestamp: import("../table.js").Column<number>;
|
|
19
|
+
}>;
|
|
20
|
+
export declare const MemoryDBRecordSchema: z.ZodObject<{
|
|
21
|
+
id: z.ZodString;
|
|
22
|
+
namespace: z.ZodNullable<z.ZodString>;
|
|
23
|
+
entity_id: z.ZodNullable<z.ZodString>;
|
|
24
|
+
agent_id: z.ZodNullable<z.ZodString>;
|
|
25
|
+
kind: z.ZodEnum<{
|
|
26
|
+
episodic: "episodic";
|
|
27
|
+
semantic: "semantic";
|
|
28
|
+
}>;
|
|
29
|
+
collection: z.ZodString;
|
|
30
|
+
content: z.ZodObject<{
|
|
31
|
+
text: z.ZodOptional<z.ZodString>;
|
|
32
|
+
image: z.ZodOptional<z.ZodObject<{
|
|
33
|
+
data: z.ZodUnion<readonly [z.ZodCustom<Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>>, z.ZodString]>;
|
|
34
|
+
mime: z.ZodString;
|
|
35
|
+
alt: z.ZodOptional<z.ZodString>;
|
|
36
|
+
}, z.core.$strip>>;
|
|
37
|
+
audio: z.ZodOptional<z.ZodObject<{
|
|
38
|
+
data: z.ZodUnion<readonly [z.ZodCustom<Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>>, z.ZodString]>;
|
|
39
|
+
mime: z.ZodString;
|
|
40
|
+
}, z.core.$strip>>;
|
|
41
|
+
video: z.ZodOptional<z.ZodObject<{
|
|
42
|
+
data: z.ZodUnion<readonly [z.ZodCustom<Uint8Array<ArrayBufferLike>, Uint8Array<ArrayBufferLike>>, z.ZodString]>;
|
|
43
|
+
mime: z.ZodString;
|
|
44
|
+
}, z.core.$strip>>;
|
|
45
|
+
object: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
46
|
+
}, z.core.$strip>;
|
|
47
|
+
wmem: z.ZodBoolean;
|
|
48
|
+
smem_expires_at: z.ZodNullable<z.ZodCoercedNumber<unknown>>;
|
|
49
|
+
timestamp: z.ZodCoercedNumber<unknown>;
|
|
50
|
+
created_at: z.ZodCoercedNumber<unknown>;
|
|
51
|
+
updated_at: z.ZodCoercedNumber<unknown>;
|
|
52
|
+
metadata: z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
53
|
+
}, z.core.$strip>;
|
|
54
|
+
export type MemoryDBRecord = z.infer<typeof MemoryDBRecordSchema>;
|
|
55
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/memory/schema.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,eAAO,MAAM,cAAc;;;;;;;;;;;;;;EA0B1B,CAAC;AAyCF,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAc/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory table definition and record schema.
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { text, bigint, boolean, jsonb, timestamps, defineTable } from "../table.js";
|
|
6
|
+
/* ---- Table Definition ---- */
|
|
7
|
+
export const TABLE_MEMORIES = defineTable("memories", {
|
|
8
|
+
id: text().primaryKey(),
|
|
9
|
+
namespace: text().nullable(),
|
|
10
|
+
entity_id: text().nullable(),
|
|
11
|
+
agent_id: text().nullable(),
|
|
12
|
+
kind: text(), // "episodic" | "semantic"
|
|
13
|
+
collection: text(),
|
|
14
|
+
content: jsonb(),
|
|
15
|
+
wmem: boolean().default(false),
|
|
16
|
+
smem_expires_at: bigint().nullable(),
|
|
17
|
+
timestamp: bigint(),
|
|
18
|
+
...timestamps,
|
|
19
|
+
metadata: jsonb().nullable(),
|
|
20
|
+
}, [
|
|
21
|
+
{ kind: "index", columns: ["namespace"] },
|
|
22
|
+
{ kind: "index", columns: ["entity_id"] },
|
|
23
|
+
{ kind: "index", columns: ["agent_id"] },
|
|
24
|
+
{ kind: "index", columns: ["kind"] },
|
|
25
|
+
{ kind: "index", columns: ["collection"] },
|
|
26
|
+
{ kind: "index", columns: ["wmem"] },
|
|
27
|
+
{ kind: "index", columns: ["timestamp"] },
|
|
28
|
+
{ kind: "index", columns: ["created_at"] },
|
|
29
|
+
]);
|
|
30
|
+
/* ---- Record Schema ---- */
|
|
31
|
+
const TextByteSchema = z.string();
|
|
32
|
+
// Binary data can be Uint8Array or base64/URI string
|
|
33
|
+
const BinaryDataSchema = z.union([
|
|
34
|
+
z.custom((val) => val instanceof Uint8Array),
|
|
35
|
+
z.string(),
|
|
36
|
+
]);
|
|
37
|
+
const ImageByteSchema = z.object({
|
|
38
|
+
data: BinaryDataSchema,
|
|
39
|
+
mime: z.string(),
|
|
40
|
+
alt: z.string().optional(),
|
|
41
|
+
});
|
|
42
|
+
const AudioByteSchema = z.object({
|
|
43
|
+
data: BinaryDataSchema,
|
|
44
|
+
mime: z.string(),
|
|
45
|
+
});
|
|
46
|
+
const VideoByteSchema = z.object({
|
|
47
|
+
data: BinaryDataSchema,
|
|
48
|
+
mime: z.string(),
|
|
49
|
+
});
|
|
50
|
+
const MemoryByteSchema = z.object({
|
|
51
|
+
text: TextByteSchema.optional(),
|
|
52
|
+
image: ImageByteSchema.optional(),
|
|
53
|
+
audio: AudioByteSchema.optional(),
|
|
54
|
+
video: VideoByteSchema.optional(),
|
|
55
|
+
object: z.record(z.string(), z.unknown()).optional(),
|
|
56
|
+
});
|
|
57
|
+
const MemoryKindSchema = z.enum(["episodic", "semantic"]);
|
|
58
|
+
// pg returns BIGINT as string, so we coerce to number
|
|
59
|
+
const pgBigint = z.coerce.number().int();
|
|
60
|
+
export const MemoryDBRecordSchema = z.object({
|
|
61
|
+
id: z.string(),
|
|
62
|
+
namespace: z.string().nullable(),
|
|
63
|
+
entity_id: z.string().nullable(),
|
|
64
|
+
agent_id: z.string().nullable(),
|
|
65
|
+
kind: MemoryKindSchema,
|
|
66
|
+
collection: z.string(),
|
|
67
|
+
content: MemoryByteSchema,
|
|
68
|
+
wmem: z.boolean(),
|
|
69
|
+
smem_expires_at: pgBigint.nullable(),
|
|
70
|
+
timestamp: pgBigint,
|
|
71
|
+
created_at: pgBigint,
|
|
72
|
+
updated_at: pgBigint,
|
|
73
|
+
metadata: z.record(z.string(), z.unknown()).nullable(),
|
|
74
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.test.d.ts","sourceRoot":"","sources":["../../../src/serde/__tests__/memory.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
2
|
+
import { MemoryRecordCodec, NewMemoryCodec } from "../memory.js";
|
|
3
|
+
describe("NewMemoryCodec", () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
vi.useFakeTimers();
|
|
6
|
+
vi.setSystemTime(new Date("2024-01-01T00:00:00.000Z"));
|
|
7
|
+
});
|
|
8
|
+
describe("encode", () => {
|
|
9
|
+
it("should apply default values", () => {
|
|
10
|
+
const memory = {
|
|
11
|
+
id: "mem-1",
|
|
12
|
+
scope: { namespace: "test" },
|
|
13
|
+
kind: "episodic",
|
|
14
|
+
collection: "facts",
|
|
15
|
+
content: { text: "Hello world" },
|
|
16
|
+
};
|
|
17
|
+
const record = NewMemoryCodec.encode(memory);
|
|
18
|
+
expect(record.wmem).toBe(false);
|
|
19
|
+
expect(record.smem_expires_at).toBeNull();
|
|
20
|
+
expect(record.metadata).toBeNull();
|
|
21
|
+
});
|
|
22
|
+
it("should preserve provided values", () => {
|
|
23
|
+
const memory = {
|
|
24
|
+
id: "mem-2",
|
|
25
|
+
scope: { namespace: "test", entityId: "user-1", agentId: "agent-1" },
|
|
26
|
+
kind: "semantic",
|
|
27
|
+
collection: "preferences",
|
|
28
|
+
content: { object: { key: "value" } },
|
|
29
|
+
wmem: true,
|
|
30
|
+
smem: { expiresAt: 1704153600000 },
|
|
31
|
+
timestamp: 1704067200000,
|
|
32
|
+
metadata: { source: "user" },
|
|
33
|
+
};
|
|
34
|
+
const record = NewMemoryCodec.encode(memory);
|
|
35
|
+
expect(record.namespace).toBe("test");
|
|
36
|
+
expect(record.entity_id).toBe("user-1");
|
|
37
|
+
expect(record.agent_id).toBe("agent-1");
|
|
38
|
+
expect(record.kind).toBe("semantic");
|
|
39
|
+
expect(record.collection).toBe("preferences");
|
|
40
|
+
expect(record.content).toEqual({ object: { key: "value" } });
|
|
41
|
+
expect(record.wmem).toBe(true);
|
|
42
|
+
expect(record.smem_expires_at).toBe(1704153600000);
|
|
43
|
+
expect(record.timestamp).toBe(1704067200000);
|
|
44
|
+
expect(record.metadata).toEqual({ source: "user" });
|
|
45
|
+
});
|
|
46
|
+
it("should apply current timestamp when not provided", () => {
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
const memory = {
|
|
49
|
+
id: "mem-3",
|
|
50
|
+
scope: { namespace: "test" },
|
|
51
|
+
kind: "episodic",
|
|
52
|
+
collection: "facts",
|
|
53
|
+
content: { text: "Test" },
|
|
54
|
+
};
|
|
55
|
+
const record = NewMemoryCodec.encode(memory);
|
|
56
|
+
expect(record.timestamp).toBe(now);
|
|
57
|
+
expect(record.created_at).toBe(now);
|
|
58
|
+
expect(record.updated_at).toBe(now);
|
|
59
|
+
});
|
|
60
|
+
it("should handle null scope fields", () => {
|
|
61
|
+
const memory = {
|
|
62
|
+
id: "mem-4",
|
|
63
|
+
scope: {},
|
|
64
|
+
kind: "episodic",
|
|
65
|
+
collection: "facts",
|
|
66
|
+
content: { text: "Test" },
|
|
67
|
+
};
|
|
68
|
+
const record = NewMemoryCodec.encode(memory);
|
|
69
|
+
expect(record.namespace).toBeNull();
|
|
70
|
+
expect(record.entity_id).toBeNull();
|
|
71
|
+
expect(record.agent_id).toBeNull();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
describe("decode", () => {
|
|
75
|
+
it("should decode MemoryDBRecord to NewMemory", () => {
|
|
76
|
+
const record = {
|
|
77
|
+
id: "mem-1",
|
|
78
|
+
namespace: "test",
|
|
79
|
+
entity_id: "user-1",
|
|
80
|
+
agent_id: "agent-1",
|
|
81
|
+
kind: "episodic",
|
|
82
|
+
collection: "facts",
|
|
83
|
+
content: { text: "Hello" },
|
|
84
|
+
wmem: true,
|
|
85
|
+
smem_expires_at: 1704153600000,
|
|
86
|
+
timestamp: 1704067200000,
|
|
87
|
+
created_at: 1704067200000,
|
|
88
|
+
updated_at: 1704067200000,
|
|
89
|
+
metadata: { key: "value" },
|
|
90
|
+
};
|
|
91
|
+
const memory = NewMemoryCodec.decode(record);
|
|
92
|
+
expect(memory.id).toBe("mem-1");
|
|
93
|
+
expect(memory.scope.namespace).toBe("test");
|
|
94
|
+
expect(memory.scope.entityId).toBe("user-1");
|
|
95
|
+
expect(memory.scope.agentId).toBe("agent-1");
|
|
96
|
+
expect(memory.kind).toBe("episodic");
|
|
97
|
+
expect(memory.collection).toBe("facts");
|
|
98
|
+
expect(memory.content).toEqual({ text: "Hello" });
|
|
99
|
+
expect(memory.wmem).toBe(true);
|
|
100
|
+
expect(memory.smem?.expiresAt).toBe(1704153600000);
|
|
101
|
+
expect(memory.timestamp).toBe(1704067200000);
|
|
102
|
+
expect(memory.metadata).toEqual({ key: "value" });
|
|
103
|
+
});
|
|
104
|
+
it("should handle null values", () => {
|
|
105
|
+
const record = {
|
|
106
|
+
id: "mem-2",
|
|
107
|
+
namespace: null,
|
|
108
|
+
entity_id: null,
|
|
109
|
+
agent_id: null,
|
|
110
|
+
kind: "semantic",
|
|
111
|
+
collection: "facts",
|
|
112
|
+
content: { text: "Test" },
|
|
113
|
+
wmem: false,
|
|
114
|
+
smem_expires_at: null,
|
|
115
|
+
timestamp: 1704067200000,
|
|
116
|
+
created_at: 1704067200000,
|
|
117
|
+
updated_at: 1704067200000,
|
|
118
|
+
metadata: null,
|
|
119
|
+
};
|
|
120
|
+
const memory = NewMemoryCodec.decode(record);
|
|
121
|
+
expect(memory.scope.namespace).toBeUndefined();
|
|
122
|
+
expect(memory.scope.entityId).toBeUndefined();
|
|
123
|
+
expect(memory.scope.agentId).toBeUndefined();
|
|
124
|
+
expect(memory.smem).toBeUndefined();
|
|
125
|
+
expect(memory.metadata).toBeNull();
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
describe("MemoryRecordCodec", () => {
|
|
130
|
+
describe("encode", () => {
|
|
131
|
+
it("should encode MemoryRecord to MemoryDBRecord", () => {
|
|
132
|
+
const record = {
|
|
133
|
+
id: "mem-1",
|
|
134
|
+
scope: { namespace: "test", entityId: "user-1", agentId: "agent-1" },
|
|
135
|
+
kind: "episodic",
|
|
136
|
+
collection: "facts",
|
|
137
|
+
content: { text: "Hello" },
|
|
138
|
+
wmem: true,
|
|
139
|
+
smem: { expiresAt: 1704153600000 },
|
|
140
|
+
timestamp: 1704067200000,
|
|
141
|
+
createdAt: 1704067200000,
|
|
142
|
+
updatedAt: 1704070800000,
|
|
143
|
+
metadata: { source: "test" },
|
|
144
|
+
};
|
|
145
|
+
const dbRecord = MemoryRecordCodec.encode(record);
|
|
146
|
+
expect(dbRecord.id).toBe("mem-1");
|
|
147
|
+
expect(dbRecord.namespace).toBe("test");
|
|
148
|
+
expect(dbRecord.entity_id).toBe("user-1");
|
|
149
|
+
expect(dbRecord.agent_id).toBe("agent-1");
|
|
150
|
+
expect(dbRecord.kind).toBe("episodic");
|
|
151
|
+
expect(dbRecord.collection).toBe("facts");
|
|
152
|
+
expect(dbRecord.content).toEqual({ text: "Hello" });
|
|
153
|
+
expect(dbRecord.wmem).toBe(true);
|
|
154
|
+
expect(dbRecord.smem_expires_at).toBe(1704153600000);
|
|
155
|
+
expect(dbRecord.timestamp).toBe(1704067200000);
|
|
156
|
+
expect(dbRecord.created_at).toBe(1704067200000);
|
|
157
|
+
expect(dbRecord.updated_at).toBe(1704070800000);
|
|
158
|
+
expect(dbRecord.metadata).toEqual({ source: "test" });
|
|
159
|
+
});
|
|
160
|
+
it("should handle undefined scope fields as null", () => {
|
|
161
|
+
const record = {
|
|
162
|
+
id: "mem-2",
|
|
163
|
+
scope: {},
|
|
164
|
+
kind: "semantic",
|
|
165
|
+
collection: "facts",
|
|
166
|
+
content: { text: "Test" },
|
|
167
|
+
wmem: false,
|
|
168
|
+
smem: { expiresAt: null },
|
|
169
|
+
timestamp: 1704067200000,
|
|
170
|
+
createdAt: 1704067200000,
|
|
171
|
+
updatedAt: 1704067200000,
|
|
172
|
+
metadata: null,
|
|
173
|
+
};
|
|
174
|
+
const dbRecord = MemoryRecordCodec.encode(record);
|
|
175
|
+
expect(dbRecord.namespace).toBeNull();
|
|
176
|
+
expect(dbRecord.entity_id).toBeNull();
|
|
177
|
+
expect(dbRecord.agent_id).toBeNull();
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
describe("decode", () => {
|
|
181
|
+
it("should decode MemoryDBRecord to MemoryRecord", () => {
|
|
182
|
+
const dbRecord = {
|
|
183
|
+
id: "mem-1",
|
|
184
|
+
namespace: "test",
|
|
185
|
+
entity_id: "user-1",
|
|
186
|
+
agent_id: "agent-1",
|
|
187
|
+
kind: "semantic",
|
|
188
|
+
collection: "facts",
|
|
189
|
+
content: { object: { foo: "bar" } },
|
|
190
|
+
wmem: false,
|
|
191
|
+
smem_expires_at: null,
|
|
192
|
+
timestamp: 1704067200000,
|
|
193
|
+
created_at: 1704067200000,
|
|
194
|
+
updated_at: 1704070800000,
|
|
195
|
+
metadata: null,
|
|
196
|
+
};
|
|
197
|
+
const record = MemoryRecordCodec.decode(dbRecord);
|
|
198
|
+
expect(record.id).toBe("mem-1");
|
|
199
|
+
expect(record.scope.namespace).toBe("test");
|
|
200
|
+
expect(record.scope.entityId).toBe("user-1");
|
|
201
|
+
expect(record.scope.agentId).toBe("agent-1");
|
|
202
|
+
expect(record.kind).toBe("semantic");
|
|
203
|
+
expect(record.collection).toBe("facts");
|
|
204
|
+
expect(record.content).toEqual({ object: { foo: "bar" } });
|
|
205
|
+
expect(record.wmem).toBe(false);
|
|
206
|
+
expect(record.smem.expiresAt).toBeNull();
|
|
207
|
+
expect(record.timestamp).toBe(1704067200000);
|
|
208
|
+
expect(record.createdAt).toBe(1704067200000);
|
|
209
|
+
expect(record.updatedAt).toBe(1704070800000);
|
|
210
|
+
expect(record.metadata).toBeNull();
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
describe("round-trip encoding/decoding", () => {
|
|
214
|
+
it("should preserve data through encode -> decode cycle", () => {
|
|
215
|
+
const original = {
|
|
216
|
+
id: "mem-rt",
|
|
217
|
+
scope: { namespace: "test", entityId: "user-1" },
|
|
218
|
+
kind: "episodic",
|
|
219
|
+
collection: "conversations",
|
|
220
|
+
content: { text: "Round trip test" },
|
|
221
|
+
wmem: true,
|
|
222
|
+
smem: { expiresAt: 1704153600000 },
|
|
223
|
+
timestamp: 1704067200000,
|
|
224
|
+
createdAt: 1704067200000,
|
|
225
|
+
updatedAt: 1704070800000,
|
|
226
|
+
metadata: { roundTrip: true },
|
|
227
|
+
};
|
|
228
|
+
const encoded = MemoryRecordCodec.encode(original);
|
|
229
|
+
const decoded = MemoryRecordCodec.decode(encoded);
|
|
230
|
+
expect(decoded.id).toBe(original.id);
|
|
231
|
+
expect(decoded.scope.namespace).toBe(original.scope.namespace);
|
|
232
|
+
expect(decoded.scope.entityId).toBe(original.scope.entityId);
|
|
233
|
+
expect(decoded.kind).toBe(original.kind);
|
|
234
|
+
expect(decoded.collection).toBe(original.collection);
|
|
235
|
+
expect(decoded.content).toEqual(original.content);
|
|
236
|
+
expect(decoded.wmem).toBe(original.wmem);
|
|
237
|
+
expect(decoded.smem.expiresAt).toBe(original.smem.expiresAt);
|
|
238
|
+
expect(decoded.timestamp).toBe(original.timestamp);
|
|
239
|
+
expect(decoded.createdAt).toBe(original.createdAt);
|
|
240
|
+
expect(decoded.updatedAt).toBe(original.updatedAt);
|
|
241
|
+
expect(decoded.metadata).toEqual(original.metadata);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory serialization - codecs for converting between domain types and database records.
|
|
3
|
+
*/
|
|
4
|
+
import type { MemoryRecord, NewMemory } from "kernl";
|
|
5
|
+
import { type Codec } from "@kernl-sdk/shared/lib";
|
|
6
|
+
export declare const MemoryRecordCodec: Codec<MemoryRecord, {
|
|
7
|
+
id: string;
|
|
8
|
+
namespace: string | null;
|
|
9
|
+
entity_id: string | null;
|
|
10
|
+
agent_id: string | null;
|
|
11
|
+
kind: "episodic" | "semantic";
|
|
12
|
+
collection: string;
|
|
13
|
+
content: {
|
|
14
|
+
text?: string | undefined;
|
|
15
|
+
image?: {
|
|
16
|
+
data: string | Uint8Array<ArrayBufferLike>;
|
|
17
|
+
mime: string;
|
|
18
|
+
alt?: string | undefined;
|
|
19
|
+
} | undefined;
|
|
20
|
+
audio?: {
|
|
21
|
+
data: string | Uint8Array<ArrayBufferLike>;
|
|
22
|
+
mime: string;
|
|
23
|
+
} | undefined;
|
|
24
|
+
video?: {
|
|
25
|
+
data: string | Uint8Array<ArrayBufferLike>;
|
|
26
|
+
mime: string;
|
|
27
|
+
} | undefined;
|
|
28
|
+
object?: Record<string, unknown> | undefined;
|
|
29
|
+
};
|
|
30
|
+
wmem: boolean;
|
|
31
|
+
smem_expires_at: number | null;
|
|
32
|
+
timestamp: number;
|
|
33
|
+
created_at: number;
|
|
34
|
+
updated_at: number;
|
|
35
|
+
metadata: Record<string, unknown> | null;
|
|
36
|
+
}>;
|
|
37
|
+
export declare const NewMemoryCodec: Codec<NewMemory, {
|
|
38
|
+
id: string;
|
|
39
|
+
namespace: string | null;
|
|
40
|
+
entity_id: string | null;
|
|
41
|
+
agent_id: string | null;
|
|
42
|
+
kind: "episodic" | "semantic";
|
|
43
|
+
collection: string;
|
|
44
|
+
content: {
|
|
45
|
+
text?: string | undefined;
|
|
46
|
+
image?: {
|
|
47
|
+
data: string | Uint8Array<ArrayBufferLike>;
|
|
48
|
+
mime: string;
|
|
49
|
+
alt?: string | undefined;
|
|
50
|
+
} | undefined;
|
|
51
|
+
audio?: {
|
|
52
|
+
data: string | Uint8Array<ArrayBufferLike>;
|
|
53
|
+
mime: string;
|
|
54
|
+
} | undefined;
|
|
55
|
+
video?: {
|
|
56
|
+
data: string | Uint8Array<ArrayBufferLike>;
|
|
57
|
+
mime: string;
|
|
58
|
+
} | undefined;
|
|
59
|
+
object?: Record<string, unknown> | undefined;
|
|
60
|
+
};
|
|
61
|
+
wmem: boolean;
|
|
62
|
+
smem_expires_at: number | null;
|
|
63
|
+
timestamp: number;
|
|
64
|
+
created_at: number;
|
|
65
|
+
updated_at: number;
|
|
66
|
+
metadata: Record<string, unknown> | null;
|
|
67
|
+
}>;
|
|
68
|
+
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/serde/memory.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAA0B,MAAM,OAAO,CAAC;AAE7E,OAAO,EAAE,KAAK,KAAK,EAAmB,MAAM,uBAAuB,CAAC;AAoDpE,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAG5B,CAAC;AA8CH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGzB,CAAC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory serialization - codecs for converting between domain types and database records.
|
|
3
|
+
*/
|
|
4
|
+
import { neapolitanCodec } from "@kernl-sdk/shared/lib";
|
|
5
|
+
import { MemoryDBRecordSchema } from "../memory/schema.js";
|
|
6
|
+
/* ---- Codecs ---- */
|
|
7
|
+
/**
|
|
8
|
+
* MemoryRecord codec - converts between domain MemoryRecord and MemoryDBRecord.
|
|
9
|
+
*/
|
|
10
|
+
const rawMemoryRecordCodec = {
|
|
11
|
+
encode(record) {
|
|
12
|
+
return {
|
|
13
|
+
id: record.id,
|
|
14
|
+
namespace: record.scope.namespace ?? null,
|
|
15
|
+
entity_id: record.scope.entityId ?? null,
|
|
16
|
+
agent_id: record.scope.agentId ?? null,
|
|
17
|
+
kind: record.kind,
|
|
18
|
+
collection: record.collection,
|
|
19
|
+
content: record.content,
|
|
20
|
+
wmem: record.wmem,
|
|
21
|
+
smem_expires_at: record.smem.expiresAt,
|
|
22
|
+
timestamp: record.timestamp,
|
|
23
|
+
created_at: record.createdAt,
|
|
24
|
+
updated_at: record.updatedAt,
|
|
25
|
+
metadata: record.metadata,
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
decode(row) {
|
|
29
|
+
const base = {
|
|
30
|
+
id: row.id,
|
|
31
|
+
scope: {
|
|
32
|
+
namespace: row.namespace ?? undefined,
|
|
33
|
+
entityId: row.entity_id ?? undefined,
|
|
34
|
+
agentId: row.agent_id ?? undefined,
|
|
35
|
+
},
|
|
36
|
+
collection: row.collection,
|
|
37
|
+
content: row.content,
|
|
38
|
+
wmem: row.wmem,
|
|
39
|
+
smem: {
|
|
40
|
+
expiresAt: row.smem_expires_at ? Number(row.smem_expires_at) : null,
|
|
41
|
+
},
|
|
42
|
+
timestamp: Number(row.timestamp),
|
|
43
|
+
createdAt: Number(row.created_at),
|
|
44
|
+
updatedAt: Number(row.updated_at),
|
|
45
|
+
metadata: row.metadata,
|
|
46
|
+
};
|
|
47
|
+
return { ...base, kind: row.kind };
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
export const MemoryRecordCodec = neapolitanCodec({
|
|
51
|
+
codec: rawMemoryRecordCodec,
|
|
52
|
+
schema: MemoryDBRecordSchema,
|
|
53
|
+
});
|
|
54
|
+
/**
|
|
55
|
+
* NewMemory codec - converts NewMemory input to MemoryDBRecord for insertion.
|
|
56
|
+
*/
|
|
57
|
+
const rawNewMemoryCodec = {
|
|
58
|
+
encode(memory) {
|
|
59
|
+
const now = Date.now();
|
|
60
|
+
return {
|
|
61
|
+
id: memory.id,
|
|
62
|
+
namespace: memory.scope.namespace ?? null,
|
|
63
|
+
entity_id: memory.scope.entityId ?? null,
|
|
64
|
+
agent_id: memory.scope.agentId ?? null,
|
|
65
|
+
kind: memory.kind,
|
|
66
|
+
collection: memory.collection,
|
|
67
|
+
content: memory.content,
|
|
68
|
+
wmem: memory.wmem ?? false,
|
|
69
|
+
smem_expires_at: memory.smem?.expiresAt ?? null,
|
|
70
|
+
timestamp: memory.timestamp ?? now,
|
|
71
|
+
created_at: now,
|
|
72
|
+
updated_at: now,
|
|
73
|
+
metadata: memory.metadata ?? null,
|
|
74
|
+
};
|
|
75
|
+
},
|
|
76
|
+
decode(row) {
|
|
77
|
+
return {
|
|
78
|
+
id: row.id,
|
|
79
|
+
scope: {
|
|
80
|
+
namespace: row.namespace ?? undefined,
|
|
81
|
+
entityId: row.entity_id ?? undefined,
|
|
82
|
+
agentId: row.agent_id ?? undefined,
|
|
83
|
+
},
|
|
84
|
+
kind: row.kind,
|
|
85
|
+
collection: row.collection,
|
|
86
|
+
content: row.content,
|
|
87
|
+
wmem: row.wmem,
|
|
88
|
+
smem: row.smem_expires_at
|
|
89
|
+
? { expiresAt: Number(row.smem_expires_at) }
|
|
90
|
+
: undefined,
|
|
91
|
+
timestamp: Number(row.timestamp),
|
|
92
|
+
metadata: row.metadata,
|
|
93
|
+
};
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
export const NewMemoryCodec = neapolitanCodec({
|
|
97
|
+
codec: rawNewMemoryCodec,
|
|
98
|
+
schema: MemoryDBRecordSchema,
|
|
99
|
+
});
|
package/dist/table.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Schema name for all kernl tables.
|
|
3
3
|
*/
|
|
4
|
-
export declare const
|
|
4
|
+
export declare const KERNL_SCHEMA_NAME = "kernl";
|
|
5
5
|
/**
|
|
6
6
|
* Table definition with name, columns, and optional constraints.
|
|
7
7
|
*/
|
|
@@ -14,7 +14,7 @@ export interface Table<Name extends string, Cols extends Record<string, Column>>
|
|
|
14
14
|
* Define a table with name, columns, and optional constraints.
|
|
15
15
|
*/
|
|
16
16
|
export declare function defineTable<const Name extends string, const Cols extends Record<string, Column>>(name: Name, columns: Cols, constraints?: TableConstraint[]): Table<Name, Cols>;
|
|
17
|
-
export type ColumnType = "text" | "integer" | "bigint" | "jsonb";
|
|
17
|
+
export type ColumnType = "text" | "integer" | "bigint" | "boolean" | "jsonb";
|
|
18
18
|
export type OnDeleteAction = "CASCADE" | "SET NULL" | "RESTRICT";
|
|
19
19
|
/**
|
|
20
20
|
* Column definition with type and constraints.
|
|
@@ -45,6 +45,7 @@ export interface Column<T = unknown> {
|
|
|
45
45
|
export declare const text: () => Column<string>;
|
|
46
46
|
export declare const integer: () => Column<number>;
|
|
47
47
|
export declare const bigint: () => Column<number>;
|
|
48
|
+
export declare const boolean: () => Column<boolean>;
|
|
48
49
|
export declare const jsonb: <T = unknown>() => Column<T>;
|
|
49
50
|
export declare const timestamps: {
|
|
50
51
|
created_at: Column<number>;
|
package/dist/table.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../src/table.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,
|
|
1
|
+
{"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../src/table.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,iBAAiB,UAAU,CAAC;AAEzC;;GAEG;AACH,MAAM,WAAW,KAAK,CACpB,IAAI,SAAS,MAAM,EACnB,IAAI,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAEnC,IAAI,EAAE,IAAI,CAAC;IACX,OAAO,EAAE,IAAI,CAAC;IACd,WAAW,CAAC,EAAE,eAAe,EAAE,CAAC;CACjC;AAED;;GAEG;AACH,wBAAgB,WAAW,CACzB,KAAK,CAAC,IAAI,SAAS,MAAM,EACzB,KAAK,CAAC,IAAI,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAEzC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,IAAI,EACb,WAAW,CAAC,EAAE,eAAe,EAAE,GAC9B,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAQnB;AAID,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;AAC7E,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,MAAM,CAAC,CAAC,GAAG,OAAO;IACjC,IAAI,EAAE,UAAU,CAAC;IAEjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IAGjB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,CAAC,CAAC;IACb,GAAG,CAAC,EAAE;QACJ,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,SAAS,CAAC,EAAE,cAAc,CAAC;IAG3B,UAAU,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC;IACxB,QAAQ,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC;IACtB,OAAO,CAAC,GAAG,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IAC3B,MAAM,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC;IACpB,UAAU,CACR,GAAG,EAAE,MAAM,MAAM,EACjB,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,cAAc,CAAA;KAAE,GACnC,MAAM,CAAC,CAAC,CAAC,CAAC;IAGb,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,MAAM,CAAC;IACvB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,CAAC;CACxB;AAED,eAAO,MAAM,IAAI,QAAO,MAAM,CAAC,MAAM,CAAgB,CAAC;AACtD,eAAO,MAAM,OAAO,QAAO,MAAM,CAAC,MAAM,CAAmB,CAAC;AAC5D,eAAO,MAAM,MAAM,QAAO,MAAM,CAAC,MAAM,CAAkB,CAAC;AAC1D,eAAO,MAAM,OAAO,QAAO,MAAM,CAAC,OAAO,CAAmB,CAAC;AAC7D,eAAO,MAAM,KAAK,GAAI,CAAC,GAAG,OAAO,OAAK,MAAM,CAAC,CAAC,CAAiB,CAAC;AAEhE,eAAO,MAAM,UAAU;;;CAGtB,CAAC;AA0EF;;GAEG;AACH,MAAM,MAAM,eAAe,GACvB,gBAAgB,GAChB,oBAAoB,GACpB,oBAAoB,GACpB,eAAe,GACf,eAAe,CAAC;AAEpB,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE;QACV,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC;IACF,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf"}
|
package/dist/table.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Schema name for all kernl tables.
|
|
3
3
|
*/
|
|
4
|
-
export const
|
|
4
|
+
export const KERNL_SCHEMA_NAME = "kernl";
|
|
5
5
|
/**
|
|
6
6
|
* Define a table with name, columns, and optional constraints.
|
|
7
7
|
*/
|
|
@@ -16,6 +16,7 @@ export function defineTable(name, columns, constraints) {
|
|
|
16
16
|
export const text = () => col("text");
|
|
17
17
|
export const integer = () => col("integer");
|
|
18
18
|
export const bigint = () => col("bigint");
|
|
19
|
+
export const boolean = () => col("boolean");
|
|
19
20
|
export const jsonb = () => col("jsonb");
|
|
20
21
|
export const timestamps = {
|
|
21
22
|
created_at: bigint(),
|
|
@@ -60,6 +61,8 @@ function col(type) {
|
|
|
60
61
|
case "integer":
|
|
61
62
|
case "bigint":
|
|
62
63
|
return String(val);
|
|
64
|
+
case "boolean":
|
|
65
|
+
return val ? "TRUE" : "FALSE";
|
|
63
66
|
case "jsonb":
|
|
64
67
|
return `'${JSON.stringify(val)}'`;
|
|
65
68
|
}
|
|
@@ -71,6 +74,8 @@ function col(type) {
|
|
|
71
74
|
case "integer":
|
|
72
75
|
case "bigint":
|
|
73
76
|
return Number.parseInt(val, 10);
|
|
77
|
+
case "boolean":
|
|
78
|
+
return (val === "t" || val === "true" || val === "TRUE");
|
|
74
79
|
case "jsonb":
|
|
75
80
|
return JSON.parse(val);
|
|
76
81
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kernl-sdk/storage",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "Core storage abstractions for kernl",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"kernl",
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"zod": "^4.1.12",
|
|
39
|
-
"kernl": "0.6.2",
|
|
40
39
|
"@kernl-sdk/protocol": "0.2.5",
|
|
41
|
-
"@kernl-sdk/shared": "0.1.6"
|
|
40
|
+
"@kernl-sdk/shared": "0.1.6",
|
|
41
|
+
"kernl": "0.6.3"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"build": "tsc && tsc-alias --resolve-full-paths",
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory table definition and record schema.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { text, bigint, boolean, jsonb, timestamps, defineTable } from "@/table";
|
|
7
|
+
|
|
8
|
+
/* ---- Table Definition ---- */
|
|
9
|
+
|
|
10
|
+
export const TABLE_MEMORIES = defineTable(
|
|
11
|
+
"memories",
|
|
12
|
+
{
|
|
13
|
+
id: text().primaryKey(),
|
|
14
|
+
namespace: text().nullable(),
|
|
15
|
+
entity_id: text().nullable(),
|
|
16
|
+
agent_id: text().nullable(),
|
|
17
|
+
kind: text(), // "episodic" | "semantic"
|
|
18
|
+
collection: text(),
|
|
19
|
+
content: jsonb(),
|
|
20
|
+
wmem: boolean().default(false),
|
|
21
|
+
smem_expires_at: bigint().nullable(),
|
|
22
|
+
timestamp: bigint(),
|
|
23
|
+
...timestamps,
|
|
24
|
+
metadata: jsonb().nullable(),
|
|
25
|
+
},
|
|
26
|
+
[
|
|
27
|
+
{ kind: "index", columns: ["namespace"] },
|
|
28
|
+
{ kind: "index", columns: ["entity_id"] },
|
|
29
|
+
{ kind: "index", columns: ["agent_id"] },
|
|
30
|
+
{ kind: "index", columns: ["kind"] },
|
|
31
|
+
{ kind: "index", columns: ["collection"] },
|
|
32
|
+
{ kind: "index", columns: ["wmem"] },
|
|
33
|
+
{ kind: "index", columns: ["timestamp"] },
|
|
34
|
+
{ kind: "index", columns: ["created_at"] },
|
|
35
|
+
],
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
/* ---- Record Schema ---- */
|
|
39
|
+
|
|
40
|
+
const TextByteSchema = z.string();
|
|
41
|
+
|
|
42
|
+
// Binary data can be Uint8Array or base64/URI string
|
|
43
|
+
const BinaryDataSchema = z.union([
|
|
44
|
+
z.custom<Uint8Array>((val) => val instanceof Uint8Array),
|
|
45
|
+
z.string(),
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
const ImageByteSchema = z.object({
|
|
49
|
+
data: BinaryDataSchema,
|
|
50
|
+
mime: z.string(),
|
|
51
|
+
alt: z.string().optional(),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const AudioByteSchema = z.object({
|
|
55
|
+
data: BinaryDataSchema,
|
|
56
|
+
mime: z.string(),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const VideoByteSchema = z.object({
|
|
60
|
+
data: BinaryDataSchema,
|
|
61
|
+
mime: z.string(),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const MemoryByteSchema = z.object({
|
|
65
|
+
text: TextByteSchema.optional(),
|
|
66
|
+
image: ImageByteSchema.optional(),
|
|
67
|
+
audio: AudioByteSchema.optional(),
|
|
68
|
+
video: VideoByteSchema.optional(),
|
|
69
|
+
object: z.record(z.string(), z.unknown()).optional(),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const MemoryKindSchema = z.enum(["episodic", "semantic"]);
|
|
73
|
+
|
|
74
|
+
// pg returns BIGINT as string, so we coerce to number
|
|
75
|
+
const pgBigint = z.coerce.number().int();
|
|
76
|
+
|
|
77
|
+
export const MemoryDBRecordSchema = z.object({
|
|
78
|
+
id: z.string(),
|
|
79
|
+
namespace: z.string().nullable(),
|
|
80
|
+
entity_id: z.string().nullable(),
|
|
81
|
+
agent_id: z.string().nullable(),
|
|
82
|
+
kind: MemoryKindSchema,
|
|
83
|
+
collection: z.string(),
|
|
84
|
+
content: MemoryByteSchema,
|
|
85
|
+
wmem: z.boolean(),
|
|
86
|
+
smem_expires_at: pgBigint.nullable(),
|
|
87
|
+
timestamp: pgBigint,
|
|
88
|
+
created_at: pgBigint,
|
|
89
|
+
updated_at: pgBigint,
|
|
90
|
+
metadata: z.record(z.string(), z.unknown()).nullable(),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
export type MemoryDBRecord = z.infer<typeof MemoryDBRecordSchema>;
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { MemoryRecordCodec, NewMemoryCodec } from "../memory";
|
|
4
|
+
|
|
5
|
+
describe("NewMemoryCodec", () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
vi.useFakeTimers();
|
|
8
|
+
vi.setSystemTime(new Date("2024-01-01T00:00:00.000Z"));
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe("encode", () => {
|
|
12
|
+
it("should apply default values", () => {
|
|
13
|
+
const memory = {
|
|
14
|
+
id: "mem-1",
|
|
15
|
+
scope: { namespace: "test" },
|
|
16
|
+
kind: "episodic" as const,
|
|
17
|
+
collection: "facts",
|
|
18
|
+
content: { text: "Hello world" },
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const record = NewMemoryCodec.encode(memory);
|
|
22
|
+
|
|
23
|
+
expect(record.wmem).toBe(false);
|
|
24
|
+
expect(record.smem_expires_at).toBeNull();
|
|
25
|
+
expect(record.metadata).toBeNull();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should preserve provided values", () => {
|
|
29
|
+
const memory = {
|
|
30
|
+
id: "mem-2",
|
|
31
|
+
scope: { namespace: "test", entityId: "user-1", agentId: "agent-1" },
|
|
32
|
+
kind: "semantic" as const,
|
|
33
|
+
collection: "preferences",
|
|
34
|
+
content: { object: { key: "value" } },
|
|
35
|
+
wmem: true,
|
|
36
|
+
smem: { expiresAt: 1704153600000 },
|
|
37
|
+
timestamp: 1704067200000,
|
|
38
|
+
metadata: { source: "user" },
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const record = NewMemoryCodec.encode(memory);
|
|
42
|
+
|
|
43
|
+
expect(record.namespace).toBe("test");
|
|
44
|
+
expect(record.entity_id).toBe("user-1");
|
|
45
|
+
expect(record.agent_id).toBe("agent-1");
|
|
46
|
+
expect(record.kind).toBe("semantic");
|
|
47
|
+
expect(record.collection).toBe("preferences");
|
|
48
|
+
expect(record.content).toEqual({ object: { key: "value" } });
|
|
49
|
+
expect(record.wmem).toBe(true);
|
|
50
|
+
expect(record.smem_expires_at).toBe(1704153600000);
|
|
51
|
+
expect(record.timestamp).toBe(1704067200000);
|
|
52
|
+
expect(record.metadata).toEqual({ source: "user" });
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should apply current timestamp when not provided", () => {
|
|
56
|
+
const now = Date.now();
|
|
57
|
+
|
|
58
|
+
const memory = {
|
|
59
|
+
id: "mem-3",
|
|
60
|
+
scope: { namespace: "test" },
|
|
61
|
+
kind: "episodic" as const,
|
|
62
|
+
collection: "facts",
|
|
63
|
+
content: { text: "Test" },
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const record = NewMemoryCodec.encode(memory);
|
|
67
|
+
|
|
68
|
+
expect(record.timestamp).toBe(now);
|
|
69
|
+
expect(record.created_at).toBe(now);
|
|
70
|
+
expect(record.updated_at).toBe(now);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("should handle null scope fields", () => {
|
|
74
|
+
const memory = {
|
|
75
|
+
id: "mem-4",
|
|
76
|
+
scope: {},
|
|
77
|
+
kind: "episodic" as const,
|
|
78
|
+
collection: "facts",
|
|
79
|
+
content: { text: "Test" },
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const record = NewMemoryCodec.encode(memory);
|
|
83
|
+
|
|
84
|
+
expect(record.namespace).toBeNull();
|
|
85
|
+
expect(record.entity_id).toBeNull();
|
|
86
|
+
expect(record.agent_id).toBeNull();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe("decode", () => {
|
|
91
|
+
it("should decode MemoryDBRecord to NewMemory", () => {
|
|
92
|
+
const record = {
|
|
93
|
+
id: "mem-1",
|
|
94
|
+
namespace: "test",
|
|
95
|
+
entity_id: "user-1",
|
|
96
|
+
agent_id: "agent-1",
|
|
97
|
+
kind: "episodic" as const,
|
|
98
|
+
collection: "facts",
|
|
99
|
+
content: { text: "Hello" },
|
|
100
|
+
wmem: true,
|
|
101
|
+
smem_expires_at: 1704153600000,
|
|
102
|
+
timestamp: 1704067200000,
|
|
103
|
+
created_at: 1704067200000,
|
|
104
|
+
updated_at: 1704067200000,
|
|
105
|
+
metadata: { key: "value" },
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const memory = NewMemoryCodec.decode(record);
|
|
109
|
+
|
|
110
|
+
expect(memory.id).toBe("mem-1");
|
|
111
|
+
expect(memory.scope.namespace).toBe("test");
|
|
112
|
+
expect(memory.scope.entityId).toBe("user-1");
|
|
113
|
+
expect(memory.scope.agentId).toBe("agent-1");
|
|
114
|
+
expect(memory.kind).toBe("episodic");
|
|
115
|
+
expect(memory.collection).toBe("facts");
|
|
116
|
+
expect(memory.content).toEqual({ text: "Hello" });
|
|
117
|
+
expect(memory.wmem).toBe(true);
|
|
118
|
+
expect(memory.smem?.expiresAt).toBe(1704153600000);
|
|
119
|
+
expect(memory.timestamp).toBe(1704067200000);
|
|
120
|
+
expect(memory.metadata).toEqual({ key: "value" });
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("should handle null values", () => {
|
|
124
|
+
const record = {
|
|
125
|
+
id: "mem-2",
|
|
126
|
+
namespace: null,
|
|
127
|
+
entity_id: null,
|
|
128
|
+
agent_id: null,
|
|
129
|
+
kind: "semantic" as const,
|
|
130
|
+
collection: "facts",
|
|
131
|
+
content: { text: "Test" },
|
|
132
|
+
wmem: false,
|
|
133
|
+
smem_expires_at: null,
|
|
134
|
+
timestamp: 1704067200000,
|
|
135
|
+
created_at: 1704067200000,
|
|
136
|
+
updated_at: 1704067200000,
|
|
137
|
+
metadata: null,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const memory = NewMemoryCodec.decode(record);
|
|
141
|
+
|
|
142
|
+
expect(memory.scope.namespace).toBeUndefined();
|
|
143
|
+
expect(memory.scope.entityId).toBeUndefined();
|
|
144
|
+
expect(memory.scope.agentId).toBeUndefined();
|
|
145
|
+
expect(memory.smem).toBeUndefined();
|
|
146
|
+
expect(memory.metadata).toBeNull();
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe("MemoryRecordCodec", () => {
|
|
152
|
+
describe("encode", () => {
|
|
153
|
+
it("should encode MemoryRecord to MemoryDBRecord", () => {
|
|
154
|
+
const record = {
|
|
155
|
+
id: "mem-1",
|
|
156
|
+
scope: { namespace: "test", entityId: "user-1", agentId: "agent-1" },
|
|
157
|
+
kind: "episodic" as const,
|
|
158
|
+
collection: "facts",
|
|
159
|
+
content: { text: "Hello" },
|
|
160
|
+
wmem: true,
|
|
161
|
+
smem: { expiresAt: 1704153600000 },
|
|
162
|
+
timestamp: 1704067200000,
|
|
163
|
+
createdAt: 1704067200000,
|
|
164
|
+
updatedAt: 1704070800000,
|
|
165
|
+
metadata: { source: "test" },
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const dbRecord = MemoryRecordCodec.encode(record);
|
|
169
|
+
|
|
170
|
+
expect(dbRecord.id).toBe("mem-1");
|
|
171
|
+
expect(dbRecord.namespace).toBe("test");
|
|
172
|
+
expect(dbRecord.entity_id).toBe("user-1");
|
|
173
|
+
expect(dbRecord.agent_id).toBe("agent-1");
|
|
174
|
+
expect(dbRecord.kind).toBe("episodic");
|
|
175
|
+
expect(dbRecord.collection).toBe("facts");
|
|
176
|
+
expect(dbRecord.content).toEqual({ text: "Hello" });
|
|
177
|
+
expect(dbRecord.wmem).toBe(true);
|
|
178
|
+
expect(dbRecord.smem_expires_at).toBe(1704153600000);
|
|
179
|
+
expect(dbRecord.timestamp).toBe(1704067200000);
|
|
180
|
+
expect(dbRecord.created_at).toBe(1704067200000);
|
|
181
|
+
expect(dbRecord.updated_at).toBe(1704070800000);
|
|
182
|
+
expect(dbRecord.metadata).toEqual({ source: "test" });
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("should handle undefined scope fields as null", () => {
|
|
186
|
+
const record = {
|
|
187
|
+
id: "mem-2",
|
|
188
|
+
scope: {},
|
|
189
|
+
kind: "semantic" as const,
|
|
190
|
+
collection: "facts",
|
|
191
|
+
content: { text: "Test" },
|
|
192
|
+
wmem: false,
|
|
193
|
+
smem: { expiresAt: null },
|
|
194
|
+
timestamp: 1704067200000,
|
|
195
|
+
createdAt: 1704067200000,
|
|
196
|
+
updatedAt: 1704067200000,
|
|
197
|
+
metadata: null,
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const dbRecord = MemoryRecordCodec.encode(record);
|
|
201
|
+
|
|
202
|
+
expect(dbRecord.namespace).toBeNull();
|
|
203
|
+
expect(dbRecord.entity_id).toBeNull();
|
|
204
|
+
expect(dbRecord.agent_id).toBeNull();
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe("decode", () => {
|
|
209
|
+
it("should decode MemoryDBRecord to MemoryRecord", () => {
|
|
210
|
+
const dbRecord = {
|
|
211
|
+
id: "mem-1",
|
|
212
|
+
namespace: "test",
|
|
213
|
+
entity_id: "user-1",
|
|
214
|
+
agent_id: "agent-1",
|
|
215
|
+
kind: "semantic" as const,
|
|
216
|
+
collection: "facts",
|
|
217
|
+
content: { object: { foo: "bar" } },
|
|
218
|
+
wmem: false,
|
|
219
|
+
smem_expires_at: null,
|
|
220
|
+
timestamp: 1704067200000,
|
|
221
|
+
created_at: 1704067200000,
|
|
222
|
+
updated_at: 1704070800000,
|
|
223
|
+
metadata: null,
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const record = MemoryRecordCodec.decode(dbRecord);
|
|
227
|
+
|
|
228
|
+
expect(record.id).toBe("mem-1");
|
|
229
|
+
expect(record.scope.namespace).toBe("test");
|
|
230
|
+
expect(record.scope.entityId).toBe("user-1");
|
|
231
|
+
expect(record.scope.agentId).toBe("agent-1");
|
|
232
|
+
expect(record.kind).toBe("semantic");
|
|
233
|
+
expect(record.collection).toBe("facts");
|
|
234
|
+
expect(record.content).toEqual({ object: { foo: "bar" } });
|
|
235
|
+
expect(record.wmem).toBe(false);
|
|
236
|
+
expect(record.smem.expiresAt).toBeNull();
|
|
237
|
+
expect(record.timestamp).toBe(1704067200000);
|
|
238
|
+
expect(record.createdAt).toBe(1704067200000);
|
|
239
|
+
expect(record.updatedAt).toBe(1704070800000);
|
|
240
|
+
expect(record.metadata).toBeNull();
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe("round-trip encoding/decoding", () => {
|
|
245
|
+
it("should preserve data through encode -> decode cycle", () => {
|
|
246
|
+
const original = {
|
|
247
|
+
id: "mem-rt",
|
|
248
|
+
scope: { namespace: "test", entityId: "user-1" },
|
|
249
|
+
kind: "episodic" as const,
|
|
250
|
+
collection: "conversations",
|
|
251
|
+
content: { text: "Round trip test" },
|
|
252
|
+
wmem: true,
|
|
253
|
+
smem: { expiresAt: 1704153600000 },
|
|
254
|
+
timestamp: 1704067200000,
|
|
255
|
+
createdAt: 1704067200000,
|
|
256
|
+
updatedAt: 1704070800000,
|
|
257
|
+
metadata: { roundTrip: true },
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const encoded = MemoryRecordCodec.encode(original);
|
|
261
|
+
const decoded = MemoryRecordCodec.decode(encoded);
|
|
262
|
+
|
|
263
|
+
expect(decoded.id).toBe(original.id);
|
|
264
|
+
expect(decoded.scope.namespace).toBe(original.scope.namespace);
|
|
265
|
+
expect(decoded.scope.entityId).toBe(original.scope.entityId);
|
|
266
|
+
expect(decoded.kind).toBe(original.kind);
|
|
267
|
+
expect(decoded.collection).toBe(original.collection);
|
|
268
|
+
expect(decoded.content).toEqual(original.content);
|
|
269
|
+
expect(decoded.wmem).toBe(original.wmem);
|
|
270
|
+
expect(decoded.smem.expiresAt).toBe(original.smem.expiresAt);
|
|
271
|
+
expect(decoded.timestamp).toBe(original.timestamp);
|
|
272
|
+
expect(decoded.createdAt).toBe(original.createdAt);
|
|
273
|
+
expect(decoded.updatedAt).toBe(original.updatedAt);
|
|
274
|
+
expect(decoded.metadata).toEqual(original.metadata);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory serialization - codecs for converting between domain types and database records.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { MemoryRecord, NewMemory, MemoryByte, MemoryKind } from "kernl";
|
|
6
|
+
import type { JSONObject } from "@kernl-sdk/protocol";
|
|
7
|
+
import { type Codec, neapolitanCodec } from "@kernl-sdk/shared/lib";
|
|
8
|
+
|
|
9
|
+
import { MemoryDBRecordSchema, type MemoryDBRecord } from "@/memory/schema";
|
|
10
|
+
|
|
11
|
+
/* ---- Codecs ---- */
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* MemoryRecord codec - converts between domain MemoryRecord and MemoryDBRecord.
|
|
15
|
+
*/
|
|
16
|
+
const rawMemoryRecordCodec: Codec<MemoryRecord, MemoryDBRecord> = {
|
|
17
|
+
encode(record: MemoryRecord): MemoryDBRecord {
|
|
18
|
+
return {
|
|
19
|
+
id: record.id,
|
|
20
|
+
namespace: record.scope.namespace ?? null,
|
|
21
|
+
entity_id: record.scope.entityId ?? null,
|
|
22
|
+
agent_id: record.scope.agentId ?? null,
|
|
23
|
+
kind: record.kind,
|
|
24
|
+
collection: record.collection,
|
|
25
|
+
content: record.content,
|
|
26
|
+
wmem: record.wmem,
|
|
27
|
+
smem_expires_at: record.smem.expiresAt,
|
|
28
|
+
timestamp: record.timestamp,
|
|
29
|
+
created_at: record.createdAt,
|
|
30
|
+
updated_at: record.updatedAt,
|
|
31
|
+
metadata: record.metadata,
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
decode(row: MemoryDBRecord): MemoryRecord {
|
|
36
|
+
const base = {
|
|
37
|
+
id: row.id,
|
|
38
|
+
scope: {
|
|
39
|
+
namespace: row.namespace ?? undefined,
|
|
40
|
+
entityId: row.entity_id ?? undefined,
|
|
41
|
+
agentId: row.agent_id ?? undefined,
|
|
42
|
+
},
|
|
43
|
+
collection: row.collection,
|
|
44
|
+
content: row.content as MemoryByte,
|
|
45
|
+
wmem: row.wmem,
|
|
46
|
+
smem: {
|
|
47
|
+
expiresAt: row.smem_expires_at ? Number(row.smem_expires_at) : null,
|
|
48
|
+
},
|
|
49
|
+
timestamp: Number(row.timestamp),
|
|
50
|
+
createdAt: Number(row.created_at),
|
|
51
|
+
updatedAt: Number(row.updated_at),
|
|
52
|
+
metadata: row.metadata as JSONObject | null,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return { ...base, kind: row.kind as MemoryKind } as MemoryRecord;
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const MemoryRecordCodec = neapolitanCodec({
|
|
60
|
+
codec: rawMemoryRecordCodec,
|
|
61
|
+
schema: MemoryDBRecordSchema,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* NewMemory codec - converts NewMemory input to MemoryDBRecord for insertion.
|
|
66
|
+
*/
|
|
67
|
+
const rawNewMemoryCodec: Codec<NewMemory, MemoryDBRecord> = {
|
|
68
|
+
encode(memory: NewMemory): MemoryDBRecord {
|
|
69
|
+
const now = Date.now();
|
|
70
|
+
return {
|
|
71
|
+
id: memory.id,
|
|
72
|
+
namespace: memory.scope.namespace ?? null,
|
|
73
|
+
entity_id: memory.scope.entityId ?? null,
|
|
74
|
+
agent_id: memory.scope.agentId ?? null,
|
|
75
|
+
kind: memory.kind,
|
|
76
|
+
collection: memory.collection,
|
|
77
|
+
content: memory.content,
|
|
78
|
+
wmem: memory.wmem ?? false,
|
|
79
|
+
smem_expires_at: memory.smem?.expiresAt ?? null,
|
|
80
|
+
timestamp: memory.timestamp ?? now,
|
|
81
|
+
created_at: now,
|
|
82
|
+
updated_at: now,
|
|
83
|
+
metadata: memory.metadata ?? null,
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
decode(row: MemoryDBRecord): NewMemory {
|
|
88
|
+
return {
|
|
89
|
+
id: row.id,
|
|
90
|
+
scope: {
|
|
91
|
+
namespace: row.namespace ?? undefined,
|
|
92
|
+
entityId: row.entity_id ?? undefined,
|
|
93
|
+
agentId: row.agent_id ?? undefined,
|
|
94
|
+
},
|
|
95
|
+
kind: row.kind as MemoryKind,
|
|
96
|
+
collection: row.collection,
|
|
97
|
+
content: row.content as MemoryByte,
|
|
98
|
+
wmem: row.wmem,
|
|
99
|
+
smem: row.smem_expires_at
|
|
100
|
+
? { expiresAt: Number(row.smem_expires_at) }
|
|
101
|
+
: undefined,
|
|
102
|
+
timestamp: Number(row.timestamp),
|
|
103
|
+
metadata: row.metadata as JSONObject | null | undefined,
|
|
104
|
+
};
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const NewMemoryCodec = neapolitanCodec({
|
|
109
|
+
codec: rawNewMemoryCodec,
|
|
110
|
+
schema: MemoryDBRecordSchema,
|
|
111
|
+
});
|
package/src/table.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Schema name for all kernl tables.
|
|
3
3
|
*/
|
|
4
|
-
export const
|
|
4
|
+
export const KERNL_SCHEMA_NAME = "kernl";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Table definition with name, columns, and optional constraints.
|
|
@@ -37,7 +37,7 @@ export function defineTable<
|
|
|
37
37
|
|
|
38
38
|
/* ---- Column Types ---- */
|
|
39
39
|
|
|
40
|
-
export type ColumnType = "text" | "integer" | "bigint" | "jsonb";
|
|
40
|
+
export type ColumnType = "text" | "integer" | "bigint" | "boolean" | "jsonb";
|
|
41
41
|
export type OnDeleteAction = "CASCADE" | "SET NULL" | "RESTRICT";
|
|
42
42
|
|
|
43
43
|
/**
|
|
@@ -78,6 +78,7 @@ export interface Column<T = unknown> {
|
|
|
78
78
|
export const text = (): Column<string> => col("text");
|
|
79
79
|
export const integer = (): Column<number> => col("integer");
|
|
80
80
|
export const bigint = (): Column<number> => col("bigint");
|
|
81
|
+
export const boolean = (): Column<boolean> => col("boolean");
|
|
81
82
|
export const jsonb = <T = unknown>(): Column<T> => col("jsonb");
|
|
82
83
|
|
|
83
84
|
export const timestamps = {
|
|
@@ -130,6 +131,8 @@ function col<T>(type: ColumnType): Column<T> {
|
|
|
130
131
|
case "integer":
|
|
131
132
|
case "bigint":
|
|
132
133
|
return String(val);
|
|
134
|
+
case "boolean":
|
|
135
|
+
return val ? "TRUE" : "FALSE";
|
|
133
136
|
case "jsonb":
|
|
134
137
|
return `'${JSON.stringify(val)}'`;
|
|
135
138
|
}
|
|
@@ -142,6 +145,8 @@ function col<T>(type: ColumnType): Column<T> {
|
|
|
142
145
|
case "integer":
|
|
143
146
|
case "bigint":
|
|
144
147
|
return Number.parseInt(val, 10) as T;
|
|
148
|
+
case "boolean":
|
|
149
|
+
return (val === "t" || val === "true" || val === "TRUE") as T;
|
|
145
150
|
case "jsonb":
|
|
146
151
|
return JSON.parse(val) as T;
|
|
147
152
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["./src/base.ts","./src/index.ts","./src/table.ts","./src/types.ts","./src/__tests__/table.test.ts","./src/memory/index.ts","./src/memory/schema.ts","./src/serde/memory.ts","./src/serde/thread.ts","./src/serde/__tests__/memory.test.ts","./src/serde/__tests__/thread.test.ts","./src/thread/index.ts","./src/thread/schema.ts","./src/thread/store.ts","./src/thread/types.ts"],"version":"5.9.2"}
|