UNPKG

@azure/search-documents

Version:
676 lines (675 loc) 23.2 kB
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