@azure/search-documents
Version:
Azure client library to use AI Search for node.js and browser.
676 lines (675 loc) • 23.2 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var searchClient_exports = {};
__export(searchClient_exports, {
SearchClient: () => SearchClient
});
module.exports = __toCommonJS(searchClient_exports);
var import_core_auth = require("@azure/core-auth");
var import_core_rest_pipeline = require("@azure/core-rest-pipeline");
var import_base64 = require("./base64.js");
var import_searchClient = require("./search/searchClient.js");
var import_indexDocumentsBatch = require("./indexDocumentsBatch.js");
var import_logger = require("./logger.js");
var import_odataMetadataPolicy = require("./odataMetadataPolicy.js");
var import_searchApiKeyCredentialPolicy = require("./searchApiKeyCredentialPolicy.js");
var import_searchAudience = require("./searchAudience.js");
var import_serialization = require("./serialization.js");
var utils = __toESM(require("./serviceUtils.js"));
var import_tracing = require("./tracing.js");
class SearchClient {
/// Maintenance note: when updating supported API versions,
/// the ContinuationToken logic will need to be updated below.
/**
* The service version to use when communicating with the service.
*/
serviceVersion = utils.defaultServiceVersion;
/**
* The API version to use when communicating with the service.
* @deprecated use {@Link serviceVersion} instead
*/
apiVersion = utils.defaultServiceVersion;
/**
* The endpoint of the search service
*/
endpoint;
/**
* The name of the index
*/
indexName;
/**
* @hidden
* A reference to the auto-generated SearchClient
*/
client;
/**
* A reference to the internal HTTP pipeline for use with raw requests
*/
pipeline;
/**
* Creates an instance of SearchClient.
*
* Example usage:
* ```ts snippet:ReadmeSampleSearchClient
* import { SearchClient, AzureKeyCredential } from "@azure/search-documents";
*
* const searchClient = new SearchClient(
* "<endpoint>",
* "<indexName>",
* new AzureKeyCredential("<apiKey>"),
* );
* ```
*
* Optionally, the type of the model can be used to enable strong typing and type hints:
* ```ts snippet:ReadmeSampleSearchClientWithModel
* import { SearchClient, AzureKeyCredential } from "@azure/search-documents";
*
* type TModel = {
* keyName: string;
* field1?: string | null;
* field2?: {
* anotherField?: string | null;
* } | null;
* };
*
* const searchClient = new SearchClient<TModel>(
* "<endpoint>",
* "<indexName>",
* new AzureKeyCredential("<apiKey>"),
* );
* ```
*
* @param endpoint - The endpoint of the search service
* @param indexName - The name of the index
* @param credential - Used to authenticate requests to the service.
* @param options - Used to configure the Search client.
*
* @typeParam TModel - An optional type that represents the documents stored in
* the search index. For the best typing experience, all non-key fields should
* be marked optional and nullable, and the key property should have the
* non-nullable type `string`.
*/
constructor(endpoint, indexName, credential, options = {}) {
this.endpoint = endpoint;
this.indexName = indexName;
const internalClientPipelineOptions = {
...options,
...{
loggingOptions: {
logger: import_logger.logger.info,
additionalAllowedHeaderNames: [
"elapsed-time",
"Location",
"OData-MaxVersion",
"OData-Version",
"Prefer",
"throttle-reason"
]
}
}
};
this.serviceVersion = options.serviceVersion ?? options.apiVersion ?? utils.defaultServiceVersion;
this.apiVersion = this.serviceVersion;
this.client = new import_searchClient.SearchClient(
this.endpoint,
credential,
this.indexName,
internalClientPipelineOptions
);
this.pipeline = this.client.pipeline;
this.pipeline.removePolicy({ name: import_core_rest_pipeline.bearerTokenAuthenticationPolicyName });
if ((0, import_core_auth.isTokenCredential)(credential)) {
const scope = options.audience ? `${options.audience}/.default` : `${import_searchAudience.KnownSearchAudience.AzurePublicCloud}/.default`;
this.client.pipeline.addPolicy(
(0, import_core_rest_pipeline.bearerTokenAuthenticationPolicy)({ credential, scopes: scope })
);
} else {
this.client.pipeline.addPolicy((0, import_searchApiKeyCredentialPolicy.createSearchApiKeyCredentialPolicy)(credential));
}
this.client.pipeline.addPolicy((0, import_odataMetadataPolicy.createOdataMetadataPolicy)("none"));
}
/**
* Retrieves the number of documents in the index.
* @param options - Options to the count operation.
*/
// eslint-disable-next-line @azure/azure-sdk/ts-naming-options
async getDocumentsCount(options = {}) {
return import_tracing.tracingClient.withSpan(
"SearchClient-getDocumentsCount",
options,
async (updatedOptions) => {
const count = await this.client.getDocumentCount(updatedOptions);
return Number(count);
}
);
}
/**
* Based on a partial searchText from the user, return a list of potential completion strings
* based on a specified suggester.
* @param searchText - The search text on which to base autocomplete results.
* @param suggesterName - The name of the suggester as specified in the suggesters collection
* that's part of the index definition.
* @param options - Options to the autocomplete operation.
* @example
* ```ts snippet:ReadmeSampleAutocomplete
* import { SearchClient, AzureKeyCredential, SearchFieldArray } from "@azure/search-documents";
*
* type TModel = {
* key: string;
* azure?: {
* sdk: string | null;
* } | null;
* };
*
* const client = new SearchClient<TModel>(
* "endpoint.azure",
* "indexName",
* new AzureKeyCredential("key"),
* );
*
* const searchFields: SearchFieldArray<TModel> = ["azure/sdk"];
*
* const autocompleteResult = await client.autocomplete("searchText", "suggesterName", {
* searchFields,
* });
* ```
*/
async autocomplete(searchText, suggesterName, options = {}) {
if (!searchText) {
throw new RangeError("searchText must be provided.");
}
if (!suggesterName) {
throw new RangeError("suggesterName must be provided.");
}
const { searchFields, ...restOptions } = options;
return import_tracing.tracingClient.withSpan("SearchClient-autocomplete", options, async (updatedOptions) => {
return this.client.autocompletePost(searchText, suggesterName, {
...updatedOptions,
...restOptions,
// Cast readonly array to mutable - the generated code doesn't mutate it
searchFields
});
});
}
async searchDocuments(searchText, options = {}, nextPageParameters = {}) {
const {
includeTotalCount,
orderBy,
searchFields,
select,
highlightFields,
vectorSearchOptions,
semanticSearchOptions,
debug,
...restOptions
} = options;
const {
semanticFields,
configurationName,
errorMode,
answers,
captions,
debugMode,
...restSemanticOptions
} = semanticSearchOptions ?? {};
const { queries, filterMode, ...restVectorOptions } = vectorSearchOptions ?? {};
const fullOptions = {
...restSemanticOptions,
...restVectorOptions,
...restOptions,
...nextPageParameters,
searchFields: this.convertSearchFields(searchFields),
select: this.convertSelect(select) || "*",
highlightFields: highlightFields?.split(","),
orderBy: this.convertOrderBy(orderBy),
includeTotalCount,
vectorQueries: queries?.map(this.convertVectorQuery.bind(this)),
answers: this.convertQueryAnswers(answers),
captions: this.convertQueryCaptions(captions),
semanticErrorHandling: errorMode,
semanticConfigurationName: configurationName,
debug: debugMode ?? debug,
// Use semanticSearchOptions.debugMode if set, otherwise use top-level debug
vectorFilterMode: filterMode
};
return import_tracing.tracingClient.withSpan(
"SearchClient-searchDocuments",
fullOptions,
async (updatedOptions) => {
const result = await this.client.searchPost({
...updatedOptions,
searchText
});
const {
results,
nextLink,
nextPageParameters: resultNextPageParameters,
semanticPartialResponseReason: semanticErrorReason,
semanticPartialResponseType: semanticSearchResultsType,
facets,
answers: resultAnswers,
...restResult
} = result;
const modifiedResults = utils.generatedSearchResultToPublicSearchResult(
results
);
const converted = {
...restResult,
facets: utils.convertGeneratedFacetsToPublic(facets),
answers: utils.convertGeneratedAnswersToPublic(resultAnswers),
results: modifiedResults,
semanticErrorReason,
semanticSearchResultsType,
continuationToken: this.encodeContinuationToken(nextLink, resultNextPageParameters)
};
return (0, import_serialization.deserialize)(converted);
}
);
}
async *listSearchResultsPage(searchText, options = {}, settings = {}) {
let decodedContinuation = this.decodeContinuationToken(settings.continuationToken);
let result = await this.searchDocuments(
searchText,
options,
decodedContinuation?.nextPageParameters
);
yield result;
while (result.continuationToken) {
decodedContinuation = this.decodeContinuationToken(result.continuationToken);
result = await this.searchDocuments(
searchText,
options,
decodedContinuation?.nextPageParameters
);
yield result;
}
}
async *listSearchResultsAll(firstPage, searchText, options = {}) {
yield* firstPage.results;
if (firstPage.continuationToken) {
for await (const page of this.listSearchResultsPage(searchText, options, {
continuationToken: firstPage.continuationToken
})) {
yield* page.results;
}
}
}
listSearchResults(firstPage, searchText, options = {}) {
const iter = this.listSearchResultsAll(firstPage, searchText, options);
return {
next() {
return iter.next();
},
[Symbol.asyncIterator]() {
return this;
},
byPage: (settings = {}) => {
return this.listSearchResultsPage(searchText, options, settings);
}
};
}
/**
* Performs a search on the current index given
* the specified arguments.
* @param searchText - Text to search
* @param options - Options for the search operation.
* @example
* ```ts snippet:ReadmeSampleSearchTModel
* import { SearchClient, AzureKeyCredential, SearchFieldArray } from "@azure/search-documents";
*
* type TModel = {
* key: string;
* azure?: {
* sdk: string | null;
* } | null;
* };
*
* const client = new SearchClient<TModel>(
* "endpoint.azure",
* "indexName",
* new AzureKeyCredential("key"),
* );
*
* const select = ["azure/sdk"] as const;
* const searchFields: SearchFieldArray<TModel> = ["azure/sdk"];
*
* const searchResult = await client.search("searchText", {
* select,
* searchFields,
* });
* ```
*/
async search(searchText, options = {}) {
return import_tracing.tracingClient.withSpan("SearchClient-search", options, async (updatedOptions) => {
const pageResult = await this.searchDocuments(searchText, updatedOptions);
return {
...pageResult,
results: this.listSearchResults(pageResult, searchText, updatedOptions)
};
});
}
/**
* Returns a short list of suggestions based on the searchText and specified suggester.
* @param searchText - The search text to use to suggest documents. Must be at least 1 character,
* and no more than 100 characters.
* @param suggesterName - The name of the suggester as specified in the suggesters collection
* that's part of the index definition.
* @param options - Options for the suggest operation
* @example
* ```ts snippet:ReadmeSampleSuggest
* import { SearchClient, AzureKeyCredential, SearchFieldArray } from "@azure/search-documents";
*
* type TModel = {
* key: string;
* azure?: {
* sdk: string | null;
* } | null;
* };
*
* const client = new SearchClient<TModel>(
* "endpoint.azure",
* "indexName",
* new AzureKeyCredential("key"),
* );
*
* const select = ["azure/sdk"] as const;
* const searchFields: SearchFieldArray<TModel> = ["azure/sdk"];
*
* const suggestResult = await client.suggest("searchText", "suggesterName", {
* select,
* searchFields,
* });
* ```
*/
async suggest(searchText, suggesterName, options = {}) {
const { select, searchFields, orderBy, ...nonFieldOptions } = options;
const fullOptions = {
// Cast readonly arrays to mutable - the generated code doesn't mutate them
searchFields: this.convertSearchFields(searchFields),
select: this.convertSelect(select),
orderBy: this.convertOrderBy(orderBy),
...nonFieldOptions
};
if (!searchText) {
throw new RangeError("searchText must be provided.");
}
if (!suggesterName) {
throw new RangeError("suggesterName must be provided.");
}
return import_tracing.tracingClient.withSpan("SearchClient-suggest", fullOptions, async (updatedOptions) => {
const result = await this.client.suggestPost(searchText, suggesterName, updatedOptions);
const modifiedResult = utils.generatedSuggestDocumentsResultToPublicSuggestDocumentsResult(result);
return (0, import_serialization.deserialize)(modifiedResult);
});
}
/**
* Retrieve a particular document from the index by key.
* @param key - The primary key value of the document
* @param options - Additional options
*/
async getDocument(key, options = {}) {
return import_tracing.tracingClient.withSpan(
"SearchClient-getDocument",
options,
async (updatedOptions) => {
const result = await this.client.getDocument(key, {
...updatedOptions,
selectedFields: updatedOptions.selectedFields
});
return (0, import_serialization.deserialize)(result.additionalProperties ?? {});
}
);
}
/**
* Perform a set of index modifications (upload, merge, mergeOrUpload, delete)
* for the given set of documents.
* This operation may partially succeed and not all document operations will
* be reflected in the index. If you would like to treat this as an exception,
* set the `throwOnAnyFailure` option to true.
* For more details about how merging works, see: https://learn.microsoft.com/rest/api/searchservice/AddUpdate-or-Delete-Documents
* @param batch - An array of actions to perform on the index.
* @param options - Additional options.
*/
async indexDocuments(batch, options = {}) {
return import_tracing.tracingClient.withSpan(
"SearchClient-indexDocuments",
options,
async (updatedOptions) => {
let status = 0;
const serializedActions = (0, import_serialization.serialize)(batch.actions);
const result = await this.client.index(
{ actions: utils.convertPublicActionsToGeneratedActions(serializedActions) },
{
...updatedOptions,
onResponse: (rawResponse, flatResponse) => {
status = rawResponse.status;
if (updatedOptions.onResponse) {
updatedOptions.onResponse(rawResponse, flatResponse);
}
}
}
);
if (options.throwOnAnyFailure && status === 207) {
throw result;
}
return result;
}
);
}
/**
* Upload an array of documents to the index.
* @param documents - The documents to upload.
* @param options - Additional options.
*/
async uploadDocuments(documents, options = {}) {
return import_tracing.tracingClient.withSpan(
"SearchClient-uploadDocuments",
options,
async (updatedOptions) => {
const batch = new import_indexDocumentsBatch.IndexDocumentsBatch();
batch.upload(documents);
return this.indexDocuments(batch, updatedOptions);
}
);
}
/**
* Update a set of documents in the index.
*
* For more details about how merging works, see
* https://learn.microsoft.com/rest/api/searchservice/AddUpdate-or-Delete-Documents
* @param documents - The updated documents.
* @param options - Additional options.
*/
async mergeDocuments(documents, options = {}) {
return import_tracing.tracingClient.withSpan(
"SearchClient-mergeDocuments",
options,
async (updatedOptions) => {
const batch = new import_indexDocumentsBatch.IndexDocumentsBatch();
batch.merge(documents);
return this.indexDocuments(batch, updatedOptions);
}
);
}
/**
* Update a set of documents in the index or upload them if they don't exist.
*
* For more details about how merging works, see
* https://learn.microsoft.com/rest/api/searchservice/AddUpdate-or-Delete-Documents
* @param documents - The updated documents.
* @param options - Additional options.
*/
async mergeOrUploadDocuments(documents, options = {}) {
return import_tracing.tracingClient.withSpan(
"SearchClient-mergeOrUploadDocuments",
options,
async (updatedOptions) => {
const batch = new import_indexDocumentsBatch.IndexDocumentsBatch();
batch.mergeOrUpload(documents);
return this.indexDocuments(batch, updatedOptions);
}
);
}
async deleteDocuments(keyNameOrDocuments, keyValuesOrOptions, options = {}) {
return import_tracing.tracingClient.withSpan(
"SearchClient-deleteDocuments",
options,
async (updatedOptions) => {
const batch = new import_indexDocumentsBatch.IndexDocumentsBatch();
if (typeof keyNameOrDocuments === "string") {
batch.delete(keyNameOrDocuments, keyValuesOrOptions);
} else {
batch.delete(keyNameOrDocuments);
}
return this.indexDocuments(batch, updatedOptions);
}
);
}
encodeContinuationToken(nextLink, nextPageParameters) {
if (!nextLink || !nextPageParameters) {
return void 0;
}
const payload = JSON.stringify({
apiVersion: this.apiVersion,
nextLink,
nextPageParameters
});
return (0, import_base64.encode)(payload);
}
decodeContinuationToken(token) {
if (!token) {
return void 0;
}
const decodedToken = (0, import_base64.decode)(token);
try {
const result = JSON.parse(decodedToken);
if (result.apiVersion !== this.apiVersion) {
throw new RangeError(`Continuation token uses unsupported apiVersion "${this.apiVersion}"`);
}
return {
nextLink: result.nextLink,
nextPageParameters: result.nextPageParameters
};
} catch (e) {
throw new Error(`Corrupted or invalid continuation token: ${decodedToken}`);
}
}
convertSelect(select) {
if (select) {
return select.join(",");
}
return void 0;
}
convertVectorQueryFields(fields) {
if (fields) {
return fields.join(",");
}
return void 0;
}
convertSearchFields(searchFields) {
if (searchFields) {
return searchFields.join(",");
}
return void 0;
}
convertOrderBy(orderBy) {
if (orderBy) {
return orderBy.join(",");
}
return void 0;
}
convertQueryAnswers(answers) {
if (!answers) {
return void 0;
}
const config = [];
const { answerType: output, count, threshold, maxAnswerLength } = answers;
if (count) {
config.push(`count-${count}`);
}
if (threshold) {
config.push(`threshold-${threshold}`);
}
if (maxAnswerLength) {
config.push(`maxcharlength-${maxAnswerLength}`);
}
if (config.length) {
return output + `|${config.join(",")}`;
}
return output;
}
convertQueryCaptions(captions) {
if (!captions) {
return void 0;
}
const config = [];
const { captionType: output, highlight, maxCaptionLength } = captions;
if (highlight !== void 0) {
config.push(`highlight-${highlight}`);
}
if (maxCaptionLength) {
config.push(`maxcharlength-${maxCaptionLength}`);
}
if (config.length) {
return output + `|${config.join(",")}`;
}
return output;
}
convertVectorQuery(vectorQuery) {
switch (vectorQuery.kind) {
case "text": {
const { fields, ...restFields } = vectorQuery;
return {
...restFields,
fields: this.convertVectorQueryFields(fields)
};
}
case "vector":
case "imageUrl": {
return { ...vectorQuery, fields: this.convertVectorQueryFields(vectorQuery?.fields) };
}
case "imageBinary": {
const { binaryImage, fields, ...rest } = vectorQuery;
return {
...rest,
base64Image: binaryImage,
fields: this.convertVectorQueryFields(fields)
};
}
default: {
import_logger.logger.warning("Unknown vector query kind; sending without serialization");
return vectorQuery;
}
}
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
SearchClient
});
//# sourceMappingURL=searchClient.js.map