@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.
@@ -1,4 +1,4 @@
1
1
 
2
- > @kernl-sdk/storage@0.1.10 build /Users/andjones/Documents/projects/kernl/packages/storage/core
2
+ > @kernl-sdk/storage@0.1.11 build /Users/andjones/Documents/projects/kernl/packages/storage/core
3
3
  > tsc && tsc-alias --resolve-full-paths
4
4
 
@@ -0,0 +1,4 @@
1
+
2
+ > @kernl-sdk/storage@0.1.11 check-types /Users/andjones/Documents/projects/kernl/packages/storage/core
3
+ > tsc --noEmit
4
+
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # @kernl/storage
2
2
 
3
+ ## 0.1.12
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [a7d6138]
8
+ - kernl@0.6.3
9
+
3
10
  ## 0.1.11
4
11
 
5
12
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -3,6 +3,8 @@
3
3
  */
4
4
  export * from "./base.js";
5
5
  export * from "./thread/index.js";
6
+ export * from "./memory/index.js";
6
7
  export * from "./serde/thread.js";
8
+ export * from "./serde/memory.js";
7
9
  export * from "./table.js";
8
10
  //# sourceMappingURL=index.d.ts.map
@@ -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
@@ -3,5 +3,7 @@
3
3
  */
4
4
  export * from "./base.js";
5
5
  export * from "./thread/index.js";
6
+ export * from "./memory/index.js";
6
7
  export * from "./serde/thread.js";
8
+ export * from "./serde/memory.js";
7
9
  export * from "./table.js";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Memory storage types and schema.
3
+ */
4
+ export { TABLE_MEMORIES, MemoryDBRecordSchema, type MemoryDBRecord, } from "./schema.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -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,4 @@
1
+ /**
2
+ * Memory storage types and schema.
3
+ */
4
+ export { TABLE_MEMORIES, MemoryDBRecordSchema, } from "./schema.js";
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=memory.test.d.ts.map
@@ -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 SCHEMA_NAME = "kernl";
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>;
@@ -1 +1 @@
1
- {"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../src/table.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../src/table.ts"],"names":[],"mappings":"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"}
package/dist/table.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Schema name for all kernl tables.
3
3
  */
4
- export const SCHEMA_NAME = "kernl";
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.11",
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
@@ -4,5 +4,7 @@
4
4
 
5
5
  export * from "./base";
6
6
  export * from "./thread";
7
+ export * from "./memory";
7
8
  export * from "./serde/thread";
9
+ export * from "./serde/memory";
8
10
  export * from "./table";
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Memory storage types and schema.
3
+ */
4
+
5
+ export {
6
+ TABLE_MEMORIES,
7
+ MemoryDBRecordSchema,
8
+ type MemoryDBRecord,
9
+ } from "./schema";
@@ -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 SCHEMA_NAME = "kernl";
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"}