@findlane/server

中文文档
0.39.0发布于 2026/11/08MITTypeScriptESM + CJS零依赖近 30 天下载 5583

Findlane server SDK: upload catalogs, manage index settings, and search with a secret workspace API key.

原始使用文档
文档目录9 节
  1. Install
  2. Quick start
  3. Searching for a visitor
  4. An index's methods
  5. Settings
  6. Browser search
  7. Signed keys
  8. Errors
  9. License

@findlane/server

The Findlane server SDK: upload and update catalogs, manage index settings, and search, with a secret workspace API key. It runs wherever fetch does: Node.js 18+, Bun, Deno, and edge runtimes. Never bundle it, or its key, into browser code; use @findlane/browser there.

Docs: https://findlane.dev/docs/server-sdk

Install

npm install @findlane/server

Quick start

import { SearchServerClient } from "@findlane/server"

const client = new SearchServerClient({
  workspaceId: process.env.FINDLANE_WORKSPACE_ID!,
  apiKey: process.env.FINDLANE_API_KEY!,
})
const products = client.index("products")

await products.upsert([{ objectID: "sku-1", title: "Trail shoe", brand: { name: "Acme" }, price: 12900 }])
const results = await products.search({ q: "trail shoe", limit: 10 })
console.log(results.total, results.hits[0]?.record.title)

The workspace ID is under Settings in the dashboard, and keys are under API keys. A key has one or both scopes:

  • search: search().
  • write: record writes, settings, public search restrictions, and indexing status.

Searching for a visitor

When your server searches for someone, such as a shopper loading a server-rendered search page, pass the visitor. The index's limits and bot rules then apply to them as they do to browser search, and searches from verified crawlers like Googlebot are free:

const results = await products.search({ q }, { visitor: { ip: req.ip, userAgent: req.get("user-agent") } })

suggest() and recommendations() take the same option. Without it, a search counts as your server's own.

An index's methods

client.index<T>(name) returns these; T types your records, which each need a string objectID.

MethodWhat it does
search(request, options?)Search with a query, filters, facets, a sort, paging, and highlighting. explain: true says why each hit matched and what ordered it, and appliedRules lists the index's query rules the query fired. options takes signal and visitor (below).
querySuggestions({ q, limit? }), popularSearches({ limit? })Whole searches to offer as a visitor types, [{ q, typed }], and the index's popular searches for an empty box. Not counted as searches.
upsert(records)Add or update up to 1,000 records by objectID.
replaceAll(records)Replace the whole catalog in one request (up to 100,000 records).
replaceAllStaged(records, options)Replace a larger catalog in batches under one stage ID, switched in one commit. Retrying with the same stageId resumes. abortReplacement(stageId) drops a stage.
delete(objectID)Delete a record.
getRecord(objectID)A record and its version.
upsertOne(record, { expectedVersion }), deleteOne(objectID, { expectedVersion })Write only if the record is still at expectedVersion (0 for one that never existed); retried with one idempotency key.
getSettings(), replaceSettings(settings), waitForSettings()Read and change searchable fields, facets, filters, sorts, custom ranking, synonyms, languages, typo tolerance, and text normalization.
compareSettings(settings, { queries, searched }?)Compare settings with the index's own without saving them: its search tests and most searched queries, each searched with both. Says which tests would fail and whose results change.
getSearchTests(), setSearchTests(tests)Searches that must keep working, saved apart from the settings, such as a record in the top 3 or at least 100 results. compareSettings checks them.
getIndexingStatus(), waitUntilIndexed(revision)Writes return an accepted revision and are searchable when accepted, so waitUntilIndexed returns at once.
getPublicSearchAccess(), setPublicSearchPolicy(policy), usePublicIndexDefaults()Restrict what browser search may read and query.
events.send(events, { visitorId, visitId, abTest }?), events.importOrders(orders), events.cancelOrder(orderID)Orders and other events for the index's Events page and Bought together; the key needs the events or write scope. Your own anonymous visitor IDs link a shopper's events, when the index takes them. abTest, from the shopper's search answer, puts them in their A/B test group.
abTests.get(), abTests.estimate({ share }), analytics.abTest()The index's A/B tests of its settings, with their results, and what a test would take. Owners start and end them in the dashboard.
events.forget(ids)Deletes the events sent with these visitor or visit IDs, within a week.
events.proxy(request), events.relay(body, visitor)A route on your own site for the browser SDK's events (its eventsUrl), which ad blockers let through. See Events.
analytics.searches(), .queries({ list, limit }), .filters(), .events(), .products(), .productCounts(), .product(objectID)The index's analytics, for your own admin: searches over time, top queries with their clicks, queries without results or clicks, the filters used, events, and each product's views, add-to-carts and orders. The key needs the analytics scope, and works only from a server. See Analytics.
writes.get({ range }), writes.log({ problems, before, limit })What has come into the index: when data last arrived and last changed anything, by source, and writes refused with their reasons. The key needs the analytics scope, and works only from a server. See Updates.

Settings

replaceSettings sends the full settings; fetch them first to keep the other fields. Its applied says how the change took effect:

  • instant: query-time settings, and removing or reordering searchable fields.
  • fields: facet, filter, sort, and ranking fields, filled in place.
  • searchable: new searchable fields, filled in.
  • rebuild: text normalization; the index is rebuilt.

A large searchable or rebuild change continues in the background, with its progress in rebuild (also returned by getSettings). Searches use the previous settings until it finishes, and waitForSettings() waits for it.

Before saving a change to rules, synonyms, typo tolerance, or ranking, compareSettings shows what it does, and {} runs the search tests with the current settings:

const comparison = await products.compareSettings({ rules: nextRules })
const failing = comparison.tests.filter((test) => test.before && !test.after)
if (!failing.length) await products.replaceSettings({ ...(await products.getSettings()), rules: nextRules })

Only upload fields you intend to make public if browser search is enabled. getPublicSearchAccess() shows the current mode. setPublicSearchPolicy({ returnFields, searchFields, filterFields, facetFields, sortIds, defaultSort }) restricts the index at the API, and usePublicIndexDefaults() removes the restrictions. Only a workspace owner can change allowed browser origins, in the dashboard.

Signed keys

For a logged-in person's browser searches, sign a short-lived key with a signing key from the dashboard's API keys page. It makes no request, and its restrictions replace the index's public settings:

import { signSearchKey } from "@findlane/server"

const signedKey = await signSearchKey(process.env.FINDLANE_SIGNING_KEY!, {
  validUntil: new Date(Date.now() + 60 * 60 * 1000),
  indexes: ["products"],
  filters: { customerGroup: user.group },
  userToken: user.id,
})

The browser SDK takes it as signedKey. See Logged-in visitors.

Errors

Failed requests throw a FindlaneError with the HTTP status and the API's message. A 402 means a plan limit, such as records or monthly searches. replaceAllStaged throws a StagedReplacementError with the stageId to retry. Every method takes an optional AbortSignal.

License

MIT

版本与发布记录

@findlane/server 共发布 41 个版本,首次发布于 2026/09/30;过去 12 个月发布了 41 个版本。

版本发布日期距今
0.39.0latest2026/11/086 天前
0.38.02026/11/068 天前
0.37.02026/11/059 天前
0.36.02026/11/059 天前
0.35.02026/11/059 天前
0.34.02026/11/0410 天前

关于 @findlane/server 的常见问题

@findlane/server 是什么?

Findlane server SDK: upload catalogs, manage index settings, and search with a secret workspace API key.

如何安装 @findlane/server?

在项目目录运行 npm install @findlane/server,或使用 pnpm add / yarn add / bun add。

@findlane/server 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@findlane/server 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@findlane/server 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@findlane/server 可以商用吗?

@findlane/server 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@findlane/server 的下载量有多少?

根据 npm 官方下载统计,@findlane/server 在 2026/10/15 – 2026/11/13 这 30 天内被下载 5,583 次,日均约 186 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@findlane/server 还在维护吗?

最新版本 0.39.0 发布于 2026/11/08(6 天前);至今共发布 41 个版本,其中过去 12 个月发布了 41 个;首次发布于 2026/09/30。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。