Skip to content

Commit

Permalink
Merge pull request #28 from upstash/dx-905-vectorjs-update-command
Browse files Browse the repository at this point in the history
DX-905: Update Command
  • Loading branch information
ogzhanolguncu authored May 24, 2024
2 parents 85dab4f + 0163cb3 commit 3091b0e
Show file tree
Hide file tree
Showing 11 changed files with 207 additions and 40 deletions.
1 change: 1 addition & 0 deletions src/commands/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from "./range";
export * from "./reset";
export * from "./info";
export * from "./namespace";
export * from "./update";
32 changes: 32 additions & 0 deletions src/commands/client/namespace/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,36 @@ describe("NAMESPACE", () => {

expect(res2.length).toEqual(0);
});

test("should update vector in namespace", async () => {
const index = new Index({
url: process.env.UPSTASH_VECTOR_REST_URL!,
token: process.env.UPSTASH_VECTOR_REST_TOKEN!,
});

const namespace = index.namespace("test-namespace-update");

await namespace.upsert([
{
id: "test-1",
vector: range(0, 384),
metadata: { upstash: "test-1-not-updated" },
},
]);

await awaitUntilIndexed(index);

const res = await namespace.update({
id: "test-1",
metadata: { upstash: "test-1-updated" },
});

expect(res).toEqual({ updated: 1 });

await awaitUntilIndexed(index);

const fetchData = await namespace.fetch(["test-1"], { includeMetadata: true });

expect(fetchData[0]?.metadata?.upstash).toBe("test-1-updated");
});
});
26 changes: 26 additions & 0 deletions src/commands/client/namespace/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
QueryCommand,
RangeCommand,
ResetCommand,
UpdateCommand,
UpsertCommand,
} from "@commands/client";
import { Dict } from "@commands/client/types";
Expand Down Expand Up @@ -58,6 +59,31 @@ export class Namespace<TIndexMetadata extends Dict = Dict> {
args: CommandArgs<typeof UpsertCommand<TMetadata>>
) => new UpsertCommand<TMetadata>(args, { namespace: this.namespace }).exec(this.client);

/*
* Updates specific items in the index.
* It's used for updating existing items in the index.
*
* @example
* ```js
* const updateArgs = {
* id: '123',
* metadata: { updatedProperty: 'value1' }
* };
* const updateResult = await index.update(updateArgs);
* console.log(updateResult); // Outputs the result of the update operation
* ```
*
* @param {CommandArgs<typeof UpdateCommand>} args - The arguments for the update command.
* @param {number|string} args.id - The unique identifier for the item being updated.
* @param {number[]} args.vector - The feature vector associated with the item.
* @param {Record<string, unknown>} [args.metadata] - Optional metadata to be associated with the item.
*
* @returns {Promise<{updated: number}>} A promise that returns the number of items successfully updated.
*/
update = <TMetadata extends Dict = TIndexMetadata>(
args: CommandArgs<typeof UpdateCommand<TMetadata>>
) => new UpdateCommand<TMetadata>(args, { namespace: this.namespace }).exec(this.client);

/**
* It's used for retrieving specific items from the index namespace, optionally including
* their metadata and feature vectors.
Expand Down
10 changes: 5 additions & 5 deletions src/commands/client/query/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,15 +126,15 @@ describe("QUERY", () => {
await new UpsertCommand([
{
id: "hello-world",
data: "Test1-2-3-4-5",
data: "testing-plan-text",
metadata: { upstash: "test" },
},
]).exec(embeddingClient);

await awaitUntilIndexed(embeddingClient);

const res = await new QueryCommand({
data: "Test1-2-3-4-5",
data: "testing-plain-text",
topK: 1,
includeVectors: true,
includeMetadata: true,
Expand All @@ -156,20 +156,20 @@ describe("QUERY", () => {
await new UpsertCommand([
{
id: "hello-world",
data: "Test1-2-3-4-5",
data: "testing-bulk-data-original",
metadata: { upstash: "Cookie" },
},
{
id: "hello-world1",
data: "Test1-2-3-4-5-6",
data: "testing-bulk-data-secondary",
metadata: { upstash: "Monster" },
},
]).exec(embeddingClient);

await awaitUntilIndexed(embeddingClient);

const res = await new QueryCommand({
data: "Test1-2-3-4-5",
data: "testing-bulk-data-original",
topK: 1,
includeVectors: true,
includeMetadata: true,
Expand Down
33 changes: 33 additions & 0 deletions src/commands/client/update/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { afterAll, describe, expect, test } from "bun:test";
import { FetchCommand, UpdateCommand, UpsertCommand } from "@commands/index";
import { awaitUntilIndexed, newHttpClient, range, resetIndexes } from "@utils/test-utils";

const client = newHttpClient();

describe("UPDATE", () => {
afterAll(async () => await resetIndexes());

test("should update vector metadata", async () => {
await new UpsertCommand({
id: 1,
vector: range(0, 384),
metadata: { upstash: "test-simple" },
}).exec(client);

const res = await new UpdateCommand({
id: 1,
metadata: { upstash: "test-update" },
}).exec(client);

expect(res).toEqual({ updated: 1 });

await awaitUntilIndexed(client, 5000);

const fetchData = await new FetchCommand<{ upstash: string }>([
["1"],
{ includeMetadata: true },
]).exec(client);

expect(fetchData[0]?.metadata?.upstash).toBe("test-update");
});
});
41 changes: 41 additions & 0 deletions src/commands/client/update/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { NAMESPACE } from "@commands/client/types";
import { Command } from "@commands/command";

type NoInfer<T> = T extends infer U ? U : never;

type MetadataUpdatePayload<TMetadata> = {
id: string | number;
metadata: NoInfer<TMetadata>;
};

type VectorUpdatePayload = {
id: string | number;
vector: number[];
};

type DataUpdatePayload = {
id: string | number;
data: string;
};

type Payload<TMetadata> =
| MetadataUpdatePayload<TMetadata>
| VectorUpdatePayload
| DataUpdatePayload;

type UpdateCommandOptions = { namespace?: string };

type UpdateEndpointVariants = `update` | `update/${NAMESPACE}`;

type UpdateCommandResponse = { updated: number };
export class UpdateCommand<TMetadata> extends Command<UpdateCommandResponse> {
constructor(payload: Payload<TMetadata>, opts?: UpdateCommandOptions) {
let endpoint: UpdateEndpointVariants = "update";

if (opts?.namespace) {
endpoint = `${endpoint}/${opts.namespace}`;
}

super(payload, endpoint);
}
}
29 changes: 26 additions & 3 deletions src/commands/client/upsert/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { afterAll, describe, expect, test } from "bun:test";
import { FetchCommand, UpsertCommand } from "@commands/index";
import { newHttpClient, range, resetIndexes } from "@utils/test-utils";
import { newHttpClient, randomID, range, resetIndexes } from "@utils/test-utils";
import { Index } from "../../../../index";

const client = newHttpClient();

describe("UPSERT", () => {
const index = new Index({
url: process.env.UPSTASH_VECTOR_REST_URL!,
token: process.env.UPSTASH_VECTOR_REST_TOKEN!,
});
afterAll(async () => await resetIndexes());

test("should add record successfully", async () => {
Expand Down Expand Up @@ -73,13 +78,13 @@ describe("UPSERT", () => {
await new UpsertCommand([
{
id: "hello-world",
//@ts-expect-error Mixed usage of vector and data in the same upsert command is not allowed.

data: "Test1-2-3-4-5",
metadata: { upstash: "test" },
},
{
id: "hello-world",

//@ts-expect-error Mixed usage of vector and data in the same upsert command is not allowed.
vector: [1, 2, 3, 4],
metadata: { upstash: "test" },
},
Expand Down Expand Up @@ -110,4 +115,22 @@ describe("UPSERT", () => {

expect(resUpsert).toEqual("Success");
});

test("should run with index.upsert in bulk", async () => {
const upsertData = [
{
id: randomID(),
vector: range(0, 384),
metadata: { upstash: "test-simple-1" },
},
{
id: randomID(),
vector: range(0, 384),
metadata: { upstash: "test-simple-2" },
},
];
const resUpsert = await index.upsert(upsertData, { namespace: "test-namespace" });

expect(resUpsert).toEqual("Success");
});
});
44 changes: 12 additions & 32 deletions src/commands/client/upsert/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,23 @@ import { Command } from "@commands/command";

type NoInfer<T> = T extends infer U ? U : never;

type BasePayload = {
type VectorPayload<TMetadata> = {
id: number | string;
vector: number[];
metadata?: NoInfer<TMetadata>;
};

type ExtendedVectorPayload<TMetadata> = BasePayload &
(
| {
metadata: NoInfer<TMetadata>;
vector?: number[];
data?: never;
}
| {
metadata?: NoInfer<TMetadata>;
vector: number[];
data?: never;
}
);

type ExtendedDataPayload<TMetadata> = BasePayload &
(
| {
metadata: NoInfer<TMetadata>;
data: string;
vector?: never;
}
| {
metadata?: NoInfer<TMetadata>;
data: string;
vector?: never;
}
);
type DataPayload<TMetadata> = {
id: number | string;
data: string;
metadata?: NoInfer<TMetadata>;
};

type Payload<TMetadata> =
| ExtendedDataPayload<TMetadata>
| ExtendedVectorPayload<TMetadata>
| ExtendedDataPayload<TMetadata>[]
| ExtendedVectorPayload<TMetadata>[];
| VectorPayload<TMetadata>
| DataPayload<TMetadata>
| VectorPayload<TMetadata>[]
| DataPayload<TMetadata>[];

type UpsertCommandOptions = { namespace?: string };

Expand Down
1 change: 1 addition & 0 deletions src/commands/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { NAMESPACE } from "./client/types";

const ENDPOINTS = [
"upsert",
"update",
"query",
"delete",
"fetch",
Expand Down
1 change: 1 addition & 0 deletions src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from "./client/delete";
export * from "./client/fetch";
export * from "./client/query";
export * from "./client/upsert";
export * from "./client/update";
export * from "./client/reset";
export * from "./client/range";
export * from "./client/info";
Expand Down
29 changes: 29 additions & 0 deletions src/vector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
QueryCommand,
RangeCommand,
ResetCommand,
UpdateCommand,
UpsertCommand,
} from "@commands/client";
import { Dict } from "@commands/client/types";
Expand Down Expand Up @@ -107,6 +108,34 @@ export class Index<TIndexMetadata extends Dict = Dict> {
options?: { namespace?: string }
) => new UpsertCommand<TMetadata>(args, options).exec(this.client);

/*
* Updates specific items in the index.
* It's used for updating existing items in the index.
*
* @example
* ```js
* const updateArgs = {
* id: '123',
* vector: [0.42, 0.87, ...],
* metadata: { property1: 'value1', property2: 'value2' }
* };
* const updateResult = await index.update(updateArgs);
* console.log(updateResult); // Outputs the result of the update operation
* ```
*
* @param {CommandArgs<typeof UpdateCommand>} args - The arguments for the update command.
* @param {number|string} args.id - The unique identifier for the item being updated.
* @param {number[]} args.vector - The feature vector associated with the item.
* @param {Record<string, unknown>} [args.metadata] - Optional metadata to be associated with the item.
* @param {string} [args.namespace] - The namespace to update the item in.
*
* @returns {Promise<{updated: number}>} A promise that returns the number of items successfully updated.
*/
update = <TMetadata extends Dict = TIndexMetadata>(
args: CommandArgs<typeof UpdateCommand<TMetadata>>,
options?: { namespace?: string }
) => new UpdateCommand<TMetadata>(args, options).exec(this.client);

/**
* It's used for retrieving specific items from the index, optionally including
* their metadata and feature vectors.
Expand Down

0 comments on commit 3091b0e

Please sign in to comment.