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:
@@ -337,11 +337,14 @@ export class RedisDB implements VectorStore {
|
||||
const id = ids[idx];
|
||||
|
||||
// Create entry with required fields
|
||||
const createdAt = payload.created_at
|
||||
? new Date(payload.created_at).getTime()
|
||||
: 0;
|
||||
const entry: Record<string, any> = {
|
||||
memory_id: id,
|
||||
hash: payload.hash,
|
||||
memory: payload.data,
|
||||
created_at: new Date(payload.created_at).getTime(),
|
||||
hash: payload.hash ?? "",
|
||||
memory: payload.data ?? "",
|
||||
created_at: createdAt,
|
||||
embedding: new Float32Array(vector).buffer,
|
||||
};
|
||||
|
||||
@@ -561,12 +564,18 @@ export class RedisDB implements VectorStore {
|
||||
payload: Record<string, any>,
|
||||
): Promise<void> {
|
||||
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> = {
|
||||
memory_id: vectorId,
|
||||
hash: snakePayload.hash,
|
||||
memory: snakePayload.data,
|
||||
created_at: new Date(snakePayload.created_at).getTime(),
|
||||
updated_at: new Date(snakePayload.updated_at).getTime(),
|
||||
hash: snakePayload.hash ?? "",
|
||||
memory: snakePayload.data ?? "",
|
||||
created_at: createdAt,
|
||||
updated_at: updatedAt,
|
||||
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