Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorporate v28 changes #260

Merged
merged 13 commits into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/Typesense/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import Stopwords from "./Stopwords";
import Stopword from "./Stopword";
import Conversations from "./Conversations";
import Conversation from "./Conversation";
import Stemming from "./Stemming";

export default class Client {
configuration: Configuration;
Expand All @@ -32,6 +33,7 @@ export default class Client {
operations: Operations;
multiSearch: MultiSearch;
analytics: Analytics;
stemming: Stemming;
private readonly _collections: Collections;
private readonly individualCollections: Record<string, Collection>;
private readonly _aliases: Aliases;
Expand Down Expand Up @@ -67,6 +69,7 @@ export default class Client {
this._stopwords = new Stopwords(this.apiCall);
this.individualStopwords = {};
this.analytics = new Analytics(this.apiCall);
this.stemming = new Stemming(this.apiCall);
this._conversations = new Conversations(this.apiCall);
this.individualConversations = {};
}
Expand Down
7 changes: 6 additions & 1 deletion src/Typesense/Collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type FieldType =
| "float"
| "bool"
| "geopoint"
| "geopolygon"
| "geopoint[]"
| "string[]"
| "int32[]"
Expand All @@ -27,7 +28,11 @@ export type FieldType =
| "string*"
| "image";

export interface CollectionFieldSchema {
export interface CollectionFieldSchema
extends Pick<
CollectionCreateSchema,
"token_separators" | "symbols_to_index"
> {
name: string;
type: FieldType;
optional?: boolean;
Expand Down
16 changes: 11 additions & 5 deletions src/Typesense/Documents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@ import { ImportError } from "./Errors";
import { SearchOnlyDocuments } from "./SearchOnlyDocuments";

// Todo: use generic to extract filter_by values
export interface DeleteQuery {
filter_by?: string;
batch_size?: number;
ignore_not_found?: boolean;
}
export type DeleteQuery =
| {
truncate?: true;
}
| {
truncate?: never;
filter_by?: string;
batch_size?: number;
ignore_not_found?: boolean;
};

export interface DeleteResponse {
num_deleted: number;
Expand Down Expand Up @@ -93,6 +98,7 @@ export interface SearchParams {
query_by_weights?: string | number[];
prefix?: string | boolean | boolean[]; // default: true
filter_by?: string;
max_filter_by_candidates?: number; // default: 4
enable_synonyms?: boolean; // default: true
enable_analytics?: boolean; // default: true
filter_curated_hits?: boolean; // default: false
Expand Down
2 changes: 2 additions & 0 deletions src/Typesense/MultiSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const RESOURCEPATH = "/multi_search";

export interface MultiSearchRequestSchema extends SearchParams {
collection?: string;
rerank_hybrid_matches?: boolean;
"x-typesense-api-key"?: string;
}

Expand All @@ -23,6 +24,7 @@ export interface MultiSearchRequestWithPresetSchema
}

export interface MultiSearchRequestsSchema {
union?: true;
searches: (MultiSearchRequestSchema | MultiSearchRequestWithPresetSchema)[];
}

Expand Down
11 changes: 8 additions & 3 deletions src/Typesense/Operations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@ export default class Operations {
constructor(private apiCall: ApiCall) {}

async perform(
// eslint-disable-next-line @typescript-eslint/ban-types -- Can't use `object` here, it needs to intersect with `{}`
operationName: "vote" | "snapshot" | "cache/clear" | (string & {}),
operationName:
| "vote"
| "snapshot"
| "cache/clear"
| "schema_changes"
// eslint-disable-next-line @typescript-eslint/ban-types -- Can't use `object` here, it needs to intersect with `{}`
| (string & {}),
queryParameters: Record<string, any> = {},
): Promise<any> {
return this.apiCall.post(
`${RESOURCEPATH}/${operationName}`,
{},
queryParameters
queryParameters,
);
}
}
38 changes: 38 additions & 0 deletions src/Typesense/Stemming.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type ApiCall from "./ApiCall";
import StemmingDictionaries from "./StemmingDictionaries";
import StemmingDictionary from "./StemmingDictionary";

const RESOURCEPATH = "/stemming";

export default class Stemming {
private readonly _stemmingDictionaries: StemmingDictionaries;
private readonly individualStemmingDictionaries: Record<
string,
StemmingDictionary
> = {};

constructor(private readonly apiCall: ApiCall) {
this.apiCall = apiCall;
this._stemmingDictionaries = new StemmingDictionaries(this.apiCall);
}

dictionaries(): StemmingDictionaries;
dictionaries(id: string): StemmingDictionary;
dictionaries(id?: string): StemmingDictionaries | StemmingDictionary {
if (id === undefined) {
return this._stemmingDictionaries;
} else {
if (this.individualStemmingDictionaries[id] === undefined) {
this.individualStemmingDictionaries[id] = new StemmingDictionary(
id,
this.apiCall,
);
}
return this.individualStemmingDictionaries[id];
}
}

static get RESOURCEPATH() {
return RESOURCEPATH;
}
}
59 changes: 59 additions & 0 deletions src/Typesense/StemmingDictionaries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import ApiCall from "./ApiCall";
import type { StemmingDictionaryCreateSchema } from "./StemmingDictionary";

const RESOURCEPATH = "/stemming/dictionaries";

export interface StemmingDictionariesRetrieveSchema {
dictionaries: string[];
}

export default class StemmingDictionaries {
constructor(private readonly apiCall: ApiCall) {
this.apiCall = apiCall;
}

async upsert(
id: string,
wordRootCombinations: StemmingDictionaryCreateSchema[] | string,
): Promise<StemmingDictionaryCreateSchema[] | string> {
const wordRootCombinationsInJSONLFormat = Array.isArray(
wordRootCombinations,
)
? wordRootCombinations.map((combo) => JSON.stringify(combo)).join("\n")
: wordRootCombinations;

const resultsInJSONLFormat = await this.apiCall.performRequest<string>(

"post",
this.endpointPath("import"),
{
queryParameters: {id},
bodyParameters: wordRootCombinationsInJSONLFormat,
additionalHeaders: {"Content-Type": "text/plain"},
skipConnectionTimeout: true,
}
);

return Array.isArray(wordRootCombinations)
? resultsInJSONLFormat
.split("\n")
.map((line) => JSON.parse(line) as StemmingDictionaryCreateSchema)
: resultsInJSONLFormat;
}

async retrieve(): Promise<StemmingDictionariesRetrieveSchema> {
return this.apiCall.get<StemmingDictionariesRetrieveSchema>(
this.endpointPath(),
);
}

private endpointPath(operation?: string): string {
return operation === undefined
? `${StemmingDictionaries.RESOURCEPATH}`
: `${StemmingDictionaries.RESOURCEPATH}/${encodeURIComponent(operation)}`;
}

static get RESOURCEPATH() {
return RESOURCEPATH;
}
}
27 changes: 27 additions & 0 deletions src/Typesense/StemmingDictionary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import ApiCall from "./ApiCall";
import StemmingDictionaries from "./StemmingDictionaries";

export interface StemmingDictionaryCreateSchema {
root: string;
word: string;
}

export interface StemmingDictionarySchema {
id: string;
words: StemmingDictionaryCreateSchema[];
}

export default class StemmingDictionary {
constructor(
private id: string,
private apiCall: ApiCall,
) {}

async retrieve(): Promise<StemmingDictionarySchema> {
return this.apiCall.get<StemmingDictionarySchema>(this.endpointPath());
}

private endpointPath(): string {
return `${StemmingDictionaries.RESOURCEPATH}/${encodeURIComponent(this.id)}`;
}
}
60 changes: 60 additions & 0 deletions test/Typesense/StemmingDictionaries.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import chai from "chai";
import chaiAsPromised from "chai-as-promised";
import { Client as TypesenseClient } from "../../src/Typesense";
import ApiCall from "../../src/Typesense/ApiCall";
import axios from "axios";
import MockAxiosAdapter from "axios-mock-adapter";

let expect = chai.expect;
chai.use(chaiAsPromised);

describe("StemmingDictionaries", function () {
let typesense;
let stemmingDictionaries;
let apiCall;
let mockAxios;

beforeEach(function () {
typesense = new TypesenseClient({
nodes: [
{
host: "node0",
port: "8108",
protocol: "http",
},
],
apiKey: "abcd",
randomizeNodes: false,
});
stemmingDictionaries = typesense.stemming.dictionaries();
apiCall = new ApiCall(typesense.configuration);
mockAxios = new MockAxiosAdapter(axios);
});

describe(".retrieve", function () {
it("retrieves all stemming dictionaries", function (done) {
mockAxios
.onGet(
apiCall.uriFor(
"/stemming/dictionaries",
typesense.configuration.nodes[0],
),
undefined,
{
Accept: "application/json, text/plain, */*",
"Content-Type": "application/json",
"X-TYPESENSE-API-KEY": typesense.configuration.apiKey,
},
)
.reply(200, { dictionaries: ["set1", "set2"] });

let returnData = stemmingDictionaries.retrieve();

expect(returnData)
.to.eventually.deep.equal({
dictionaries: ["set1", "set2"],
})
.notify(done);
});
});
});
64 changes: 64 additions & 0 deletions test/Typesense/StemmingDictionary.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import chai from "chai";
import chaiAsPromised from "chai-as-promised";
import { Client as TypesenseClient } from "../../src/Typesense";
import ApiCall from "../../src/Typesense/ApiCall";
import axios from "axios";
import MockAxiosAdapter from "axios-mock-adapter";

let expect = chai.expect;
chai.use(chaiAsPromised);

describe("StemmingDictionary", function () {
let typesense;
let stemmingDictionary;
let apiCall;
let mockAxios;

beforeEach(function () {
typesense = new TypesenseClient({
nodes: [
{
host: "node0",
port: "8108",
protocol: "http",
},
],
apiKey: "abcd",
randomizeNodes: false,
});
stemmingDictionary = typesense.stemming.dictionaries("set1");
apiCall = new ApiCall(typesense.configuration);
mockAxios = new MockAxiosAdapter(axios);
});

describe(".retrieve", function () {
it("retrieves the dictionary", function (done) {
mockAxios
.onGet(
apiCall.uriFor(
"/stemming/dictionaries/set1",
typesense.configuration.nodes[0],
),
null,
{
Accept: "application/json, text/plain, */*",
"Content-Type": "application/json",
"X-TYPESENSE-API-KEY": typesense.configuration.apiKey,
},
)
.reply(200, {
id: "set1",
words: [{ word: "people", root: "person" }],
});

let returnData = stemmingDictionary.retrieve();

expect(returnData)
.to.eventually.deep.equal({
id: "set1",
words: [{ word: "people", root: "person" }],
})
.notify(done);
});
});
});