Skip to content

Commit

Permalink
Merge pull request #29 from upstash/fix-fetch-namespace-field
Browse files Browse the repository at this point in the history
Fix fetch namespace field
  • Loading branch information
ogzhanolguncu authored May 14, 2024
2 parents 7567b73 + 721b0d7 commit 58d5f90
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 35 deletions.
34 changes: 34 additions & 0 deletions src/commands/client/fetch/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { afterAll, describe, expect, test } from "bun:test";
import { FetchCommand, UpsertCommand } from "@commands/index";
import { newHttpClient, randomID, range, resetIndexes } from "@utils/test-utils";
import { sleep } from "bun";
import { Index } from "../../../../index";

const client = newHttpClient();

Expand Down Expand Up @@ -54,4 +56,36 @@ describe("FETCH", () => {

expect(res).toEqual([mockData]);
});

test("should fetch succesfully by index.fetch", async () => {
const index = new Index({
url: process.env.UPSTASH_VECTOR_REST_URL!,
token: process.env.UPSTASH_VECTOR_REST_TOKEN!,
});

const randomFetch = await index.fetch([randomID()], {
includeMetadata: true,
namespace: "test",
});

expect(randomFetch).toEqual([null]);

const mockData = {
id: randomID(),
vector: range(0, 384),
metadata: { hello: "world" },
};

await index.upsert(mockData, { namespace: "test" });

sleep(4000);

const fetchWithID = await index.fetch([mockData.id], {
includeMetadata: true,
includeVectors: true,
namespace: "test",
});

expect(fetchWithID).toEqual([mockData]);
});
});
11 changes: 5 additions & 6 deletions src/commands/client/fetch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,20 @@ import { Command } from "@commands/command";
type FetchCommandOptions = {
includeMetadata?: boolean;
includeVectors?: boolean;
namespace?: string;
};

export type FetchResult<TMetadata = Dict> = Vector<TMetadata> | null;

type FetchEndpointVariants = `fetch` | `fetch/${NAMESPACE}`;

export class FetchCommand<TMetadata> extends Command<FetchResult<TMetadata>[]> {
constructor(
[ids, opts]: [ids: number[] | string[], opts?: FetchCommandOptions],
options?: { namespace?: string }
) {
constructor([ids, opts]: [ids: number[] | string[], opts?: FetchCommandOptions]) {
let endpoint: FetchEndpointVariants = "fetch";

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

super({ ids, ...opts }, endpoint);
Expand Down
66 changes: 37 additions & 29 deletions src/commands/client/namespace/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,33 +32,6 @@ export class Namespace<TIndexMetadata extends Dict = Dict> {
this.namespace = namespace;
}

/**
* Queries an index namespace with specified parameters.
* This method creates and executes a query command on an index based on the provided arguments.
*
* @example
* ```js
* await index.namespace("ns").query({
* topK: 3,
* vector: [ 0.22, 0.66 ],
* filter: "age >= 23 and (type = \'turtle\' OR type = \'cat\')"
* });
* ```
*
* @param {Object} args - The arguments for the query command.
* @param {number[]} args.vector - An array of numbers representing the feature vector for the query.
* This vector is utilized to find the most relevant items in the index.
* @param {number} args.topK - The desired number of top results to be returned, based on relevance or similarity to the query vector.
* @param {string} [args.filter] - An optional filter string to be used in the query. The filter string is used to narrow down the query results.
* @param {boolean} [args.includeVectors=false] - When set to true, includes the feature vectors of the returned items in the response.
* @param {boolean} [args.includeMetadata=false] - When set to true, includes additional metadata of the returned items in the response.
*
* @returns A promise that resolves with an array of query result objects when the request to query the index is completed.
*/
upsert = <TMetadata extends Dict = TIndexMetadata>(
args: CommandArgs<typeof UpsertCommand<TMetadata>>
) => new UpsertCommand<TMetadata>(args, { namespace: this.namespace }).exec(this.client);

/**
* Upserts (Updates and Inserts) specific items into the index namespace.
* It's used for adding new items to the index namespace or updating existing ones.
Expand All @@ -81,8 +54,9 @@ export class Namespace<TIndexMetadata extends Dict = Dict> {
*
* @returns {string} A promise that resolves with the result of the upsert operation after the command is executed.
*/
fetch = <TMetadata extends Dict = TIndexMetadata>(...args: CommandArgs<typeof FetchCommand>) =>
new FetchCommand<TMetadata>(args, { namespace: this.namespace }).exec(this.client);
upsert = <TMetadata extends Dict = TIndexMetadata>(
args: CommandArgs<typeof UpsertCommand<TMetadata>>
) => new UpsertCommand<TMetadata>(args, { namespace: this.namespace }).exec(this.client);

/**
* It's used for retrieving specific items from the index namespace, optionally including
Expand All @@ -101,9 +75,43 @@ export class Namespace<TIndexMetadata extends Dict = Dict> {
* @param {FetchCommandOptions} args[1] - Options for the fetch operation.
* @param {boolean} [args[1].includeMetadata=false] - Optionally include metadata of the fetched items.
* @param {boolean} [args[1].includeVectors=false] - Optionally include feature vectors of the fetched items.
* @param {string} [args[1].namespace = ""] - The namespace of the index to fetch items from.
*
* @returns {Promise<FetchReturnResponse<TMetadata>[]>} A promise that resolves with an array of fetched items or null if not found, after the command is executed.
*/
fetch = <TMetadata extends Dict = TIndexMetadata>(...args: CommandArgs<typeof FetchCommand>) => {
if (args[1]) {
args[1].namespace = this.namespace;
} else {
args[1] = { namespace: this.namespace };
}

return new FetchCommand<TMetadata>(args).exec(this.client);
};

/**
* Queries an index namespace with specified parameters.
* This method creates and executes a query command on an index based on the provided arguments.
*
* @example
* ```js
* await index.namespace("ns").query({
* topK: 3,
* vector: [ 0.22, 0.66 ],
* filter: "age >= 23 and (type = \'turtle\' OR type = \'cat\')"
* });
* ```
*
* @param {Object} args - The arguments for the query command.
* @param {number[]} args.vector - An array of numbers representing the feature vector for the query.
* This vector is utilized to find the most relevant items in the index.
* @param {number} args.topK - The desired number of top results to be returned, based on relevance or similarity to the query vector.
* @param {string} [args.filter] - An optional filter string to be used in the query. The filter string is used to narrow down the query results.
* @param {boolean} [args.includeVectors=false] - When set to true, includes the feature vectors of the returned items in the response.
* @param {boolean} [args.includeMetadata=false] - When set to true, includes additional metadata of the returned items in the response.
*
* @returns A promise that resolves with an array of query result objects when the request to query the index is completed.
*/
query = <TMetadata extends Dict = TIndexMetadata>(args: CommandArgs<typeof QueryCommand>) =>
new QueryCommand<TMetadata>(args, { namespace: this.namespace }).exec(this.client);

Expand Down
2 changes: 2 additions & 0 deletions src/commands/client/query/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ describe("QUERY", () => {
await new UpsertCommand(initialData).exec(client);
//This is needed for vector index insertion to happen. When run with other tests in parallel this tends to fail without sleep. But, standalone it should work without an issue.
await sleep(2000);

const res = await new QueryCommand<{ hello: "World" }>({
includeVectors: true,
vector: initialVector,
topK: 1,
}).exec(client);

expect(res).toEqual([
{
id: "33",
Expand Down

0 comments on commit 58d5f90

Please sign in to comment.