fix(ts/redis): use nullish coalescing for hash/timestamps in insert/update (#5860)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
08da741a31
commit
f4ccef5157
@@ -337,11 +337,14 @@ export class RedisDB implements VectorStore {
|
|||||||
const id = ids[idx];
|
const id = ids[idx];
|
||||||
|
|
||||||
// Create entry with required fields
|
// Create entry with required fields
|
||||||
|
const createdAt = payload.created_at
|
||||||
|
? new Date(payload.created_at).getTime()
|
||||||
|
: 0;
|
||||||
const entry: Record<string, any> = {
|
const entry: Record<string, any> = {
|
||||||
memory_id: id,
|
memory_id: id,
|
||||||
hash: payload.hash,
|
hash: payload.hash ?? "",
|
||||||
memory: payload.data,
|
memory: payload.data ?? "",
|
||||||
created_at: new Date(payload.created_at).getTime(),
|
created_at: createdAt,
|
||||||
embedding: new Float32Array(vector).buffer,
|
embedding: new Float32Array(vector).buffer,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -561,12 +564,18 @@ export class RedisDB implements VectorStore {
|
|||||||
payload: Record<string, any>,
|
payload: Record<string, any>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const snakePayload = toSnakeCase(payload);
|
const snakePayload = toSnakeCase(payload);
|
||||||
|
const createdAt = snakePayload.created_at
|
||||||
|
? new Date(snakePayload.created_at).getTime()
|
||||||
|
: 0;
|
||||||
|
const updatedAt = snakePayload.updated_at
|
||||||
|
? new Date(snakePayload.updated_at).getTime()
|
||||||
|
: 0;
|
||||||
const entry: Record<string, any> = {
|
const entry: Record<string, any> = {
|
||||||
memory_id: vectorId,
|
memory_id: vectorId,
|
||||||
hash: snakePayload.hash,
|
hash: snakePayload.hash ?? "",
|
||||||
memory: snakePayload.data,
|
memory: snakePayload.data ?? "",
|
||||||
created_at: new Date(snakePayload.created_at).getTime(),
|
created_at: createdAt,
|
||||||
updated_at: new Date(snakePayload.updated_at).getTime(),
|
updated_at: updatedAt,
|
||||||
embedding: Buffer.from(new Float32Array(vector).buffer),
|
embedding: Buffer.from(new Float32Array(vector).buffer),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
import { RedisDB } from "../src/vector_stores/redis";
|
||||||
|
|
||||||
|
jest.mock("redis", () => ({
|
||||||
|
createClient: jest.fn(() => ({
|
||||||
|
connect: jest.fn(),
|
||||||
|
on: jest.fn(),
|
||||||
|
moduleList: jest.fn().mockResolvedValue([{ name: "search", ver: 20800 }]),
|
||||||
|
ft: {
|
||||||
|
create: jest.fn(),
|
||||||
|
search: jest.fn(),
|
||||||
|
info: jest.fn().mockRejectedValue(new Error("Unknown index")),
|
||||||
|
_list: jest.fn().mockResolvedValue([]),
|
||||||
|
},
|
||||||
|
hSet: jest.fn(),
|
||||||
|
hGetAll: jest.fn(),
|
||||||
|
del: jest.fn(),
|
||||||
|
exists: jest.fn(),
|
||||||
|
quit: jest.fn(),
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
function createStore(): RedisDB {
|
||||||
|
return new RedisDB({
|
||||||
|
redisUrl: "redis://localhost:6379",
|
||||||
|
collectionName: "test",
|
||||||
|
embeddingModelDims: 4,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("RedisDB – entity payload handling", () => {
|
||||||
|
let store: RedisDB;
|
||||||
|
let mockClient: any;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
store = createStore();
|
||||||
|
await store.initialize();
|
||||||
|
mockClient = (store as any).client;
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("insert with entity payload (no hash/created_at) does not produce NaN", async () => {
|
||||||
|
const entityPayload = {
|
||||||
|
data: "OpenAI",
|
||||||
|
entityType: "organization",
|
||||||
|
linkedMemoryIds: ["mem-1"],
|
||||||
|
userId: "test_user",
|
||||||
|
};
|
||||||
|
|
||||||
|
await store.insert([[0.1, 0.2, 0.3, 0.4]], ["entity-1"], [entityPayload]);
|
||||||
|
|
||||||
|
expect(mockClient.hSet).toHaveBeenCalledTimes(1);
|
||||||
|
const call = mockClient.hSet.mock.calls[0];
|
||||||
|
const entry = call[1];
|
||||||
|
|
||||||
|
expect(entry.memory_id).toBe("entity-1");
|
||||||
|
expect(entry.memory).toBe("OpenAI");
|
||||||
|
expect(entry.hash).toBe("");
|
||||||
|
expect(entry.created_at).toBe(0);
|
||||||
|
expect(Number.isNaN(entry.created_at)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("update with entity payload (no hash/created_at/updated_at) does not produce NaN", async () => {
|
||||||
|
const entityPayload = {
|
||||||
|
data: "OpenAI",
|
||||||
|
entityType: "organization",
|
||||||
|
linkedMemoryIds: ["mem-1"],
|
||||||
|
userId: "test_user",
|
||||||
|
};
|
||||||
|
|
||||||
|
await store.update("entity-1", [0.1, 0.2, 0.3, 0.4], entityPayload);
|
||||||
|
|
||||||
|
expect(mockClient.hSet).toHaveBeenCalledTimes(1);
|
||||||
|
const call = mockClient.hSet.mock.calls[0];
|
||||||
|
const entry = call[1];
|
||||||
|
|
||||||
|
expect(entry.memory_id).toBe("entity-1");
|
||||||
|
expect(entry.memory).toBe("OpenAI");
|
||||||
|
expect(entry.hash).toBe("");
|
||||||
|
expect(entry.created_at).toBe(0);
|
||||||
|
expect(entry.updated_at).toBe(0);
|
||||||
|
expect(Number.isNaN(entry.created_at)).toBe(false);
|
||||||
|
expect(Number.isNaN(entry.updated_at)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("update with normal payload preserves timestamps", async () => {
|
||||||
|
const normalPayload = {
|
||||||
|
data: "likes coffee",
|
||||||
|
hash: "abc123",
|
||||||
|
createdAt: "2026-06-25T10:00:00.000Z",
|
||||||
|
updatedAt: "2026-06-25T12:00:00.000Z",
|
||||||
|
userId: "test_user",
|
||||||
|
};
|
||||||
|
|
||||||
|
await store.update("mem-1", [0.1, 0.2, 0.3, 0.4], normalPayload);
|
||||||
|
|
||||||
|
const call = mockClient.hSet.mock.calls[0];
|
||||||
|
const entry = call[1];
|
||||||
|
|
||||||
|
expect(entry.hash).toBe("abc123");
|
||||||
|
expect(entry.memory).toBe("likes coffee");
|
||||||
|
expect(entry.created_at).toBeGreaterThan(0);
|
||||||
|
expect(entry.updated_at).toBeGreaterThan(0);
|
||||||
|
expect(Number.isNaN(entry.created_at)).toBe(false);
|
||||||
|
expect(Number.isNaN(entry.updated_at)).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("insert with normal payload preserves timestamp", async () => {
|
||||||
|
const normalPayload = {
|
||||||
|
data: "likes coffee",
|
||||||
|
hash: "abc123",
|
||||||
|
createdAt: "2026-06-25T10:00:00.000Z",
|
||||||
|
userId: "test_user",
|
||||||
|
};
|
||||||
|
|
||||||
|
await store.insert([[0.1, 0.2, 0.3, 0.4]], ["mem-1"], [normalPayload]);
|
||||||
|
|
||||||
|
const call = mockClient.hSet.mock.calls[0];
|
||||||
|
const entry = call[1];
|
||||||
|
|
||||||
|
expect(entry.hash).toBe("abc123");
|
||||||
|
expect(entry.memory).toBe("likes coffee");
|
||||||
|
expect(entry.created_at).toBeGreaterThan(0);
|
||||||
|
expect(Number.isNaN(entry.created_at)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user