All files / src/core/bindings AiSearchNamespaceBinding.ts

100% Statements 11/11
100% Branches 0/0
100% Functions 7/7
100% Lines 10/10

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141              28x 28x                                                                         28x               9x   9x                                     3x                       1x                                   1x                     1x                                           1x      
/**
 * @module
 *
 * @author Bayu Dwiyan Satria
 * @version 1.0.0
 * @since 1.3.0
 */
import { Binding } from '@/core/bindings/Binding'
import { lazySettings } from '@/utils/lazySettings'
import { AiSearchNamespaceSettings } from '@/types/AiSearchNamespaceSettings'
 
import type { CloudflareEnv } from '@/types/CloudflareEnv'
 
/**
 * Accessor for a namespace-scoped AI Search binding.
 *
 * @remarks
 * The other half of AI Search, and a genuinely different binding rather than a
 * convenience over {@link AiSearchBinding}: `ai_search` names one instance at
 * deploy time, while `ai_search_namespaces` opens a namespace and lets a
 * request name the instance. That is what a per-tenant corpus needs, since the
 * set of instances is not known when the Worker is deployed.
 *
 * Which one to bind follows from that. One corpus, fixed at deploy — the
 * single-instance binding, and a Worker that cannot ask for the wrong one. A
 * corpus per customer, created as customers arrive — this one.
 *
 * Nothing stops a Worker binding both, and the default names here differ
 * (`AI_SEARCH` and `AI_SEARCH_NAMESPACE`) so that it can. Two bindings cannot
 * share a name on one `env` regardless of what this package defaults to.
 *
 * @example
 * ```ts
 * const corpora = new AiSearchNamespaceBinding()
 *
 * const tenant = corpora.instance(env, `tenant-${id}`)
 * const { chunks } = await tenant.search({ query })
 * ```
 *
 * @class
 *
 * @author Bayu Dwiyan Satria
 * @version 1.0.0
 * @since 1.3.0
 */
export class AiSearchNamespaceBinding<E extends CloudflareEnv = CloudflareEnv> extends Binding<AiSearchNamespace, E> {
  /**
   * Constructs an AiSearchNamespaceBinding instance.
   *
   * @param settings Resolved settings. Defaults to `resolve('aiSearchNamespace')`, so
   *   callers normally construct this with no arguments at all.
   */
  constructor(settings?: AiSearchNamespaceSettings) {
    const settle = lazySettings<AiSearchNamespaceSettings>('aiSearchNamespace', settings)
 
    super(() => settle().binding)
  }
 
  /**
   * Opens one instance within the namespace.
   *
   * @remarks
   * Synchronous and local — this names an instance rather than fetching it, so
   * an instance that does not exist fails on the first operation rather than
   * here. The returned handle is the platform's own `AiSearchInstance`, so
   * `search`, `chatCompletions`, `items` and `jobs` are all reachable on it
   * without this package standing in the way.
   *
   * @param env The Worker environment.
   * @param name The instance name within the bound namespace.
   * @returns The instance handle.
   * @throws {@link MissingBindingError} When the namespace binding is absent.
   */
  public instance(env: E, name: string): AiSearchInstance {
    return this.resolve(env).get(name)
  }
 
  /**
   * Lists the instances in the namespace.
   *
   * @param env The Worker environment.
   * @param params Optional pagination, search, and ordering.
   * @returns The instances and their pagination cursor.
   * @throws {@link MissingBindingError} When the namespace binding is absent.
   */
  public async list(env: E, params?: AiSearchListInstancesParams): Promise<AiSearchListResponse> {
    return await this.resolve(env).list(params)
  }
 
  /**
   * Creates an instance in the namespace.
   *
   * @remarks
   * Provisioning from the request path is unusual, and it is here because the
   * namespace binding's reason to exist is the case where it is not: a corpus
   * created when a customer signs up cannot be declared in `wrangler.json`.
   * Passing only `id` creates one with built-in storage, to upload items into.
   *
   * @param env The Worker environment.
   * @param config The instance configuration. Only `id` is required.
   * @returns A handle to the new instance.
   * @throws {@link MissingBindingError} When the namespace binding is absent.
   */
  public async create(env: E, config: AiSearchConfig): Promise<AiSearchInstance> {
    return await this.resolve(env).create(config)
  }
 
  /**
   * Deletes an instance from the namespace.
   *
   * @param env The Worker environment.
   * @param name The instance name to delete.
   * @throws {@link MissingBindingError} When the namespace binding is absent.
   */
  public async remove(env: E, name: string): Promise<void> {
    await this.resolve(env).delete(name)
  }
 
  /**
   * Searches across several instances at once.
   *
   * @remarks
   * A fan-out, and it reports partial failure rather than hiding it: chunks
   * come back tagged with the instance they came from, and an `errors` array
   * names the instances that did not answer. A caller that ignores `errors` is
   * reading a result set that silently lost a corpus.
   *
   * `ai_search_options.instance_ids` is required, which is why nothing is
   * defaulted into this request — which instances to search is the question
   * being asked.
   *
   * @param env The Worker environment.
   * @param params The multi-instance search request.
   * @returns Merged chunks, tagged by instance, plus any partial failures.
   * @throws {@link MissingBindingError} When the namespace binding is absent.
   */
  public async search(env: E, params: AiSearchMultiSearchRequest): Promise<AiSearchMultiSearchResponse> {
    return await this.resolve(env).search(params)
  }
}