@entrinsik/vite-plugin-informer

中文文档
2.14.0发布于 2026/10/23UNLICENSEDTypeScriptES Module近 30 天下载 2103

Vite plugin and deploy tool for Informer App development

原始使用文档
文档目录7 节
  1. Quick start
  2. What a deploy ships
  3. Server compatibility
  4. Embeddings
  5. Dev loop
  6. Upgrading an App that already has an embeddings/ folder
  7. Older Informer releases

@entrinsik/vite-plugin-informer

Vite plugin and deploy tool for Informer App development: a local dev server wired to a live Informer, and a deploy pipeline that assembles your project and ships it to an App's library.

Quick start

npm install -g @entrinsik/informer-cli
inf login <your-informer-url>              # once: the sign-in dev and deploy use
npm create vite@latest my-app -- --template react
cd my-app
npm install -D @entrinsik/vite-plugin-informer
npx informer-init                          # wires vite config, informer.yaml, deploy scripts
npm run dev                                # local dev against the server you signed in to
npm run deploy                             # assemble + upload + deploy (or: inf deploy, which asks first)

No .env is needed: with no credential in the environment or .env, the plugin asks the Informer CLI (inf auth token) for the signed-in server and a current token, and a dev server renews it as it runs. INFORMER_URL with INFORMER_API_KEY (or INFORMER_USER and INFORMER_PASS) in .env still work, and win when set; INFORMER_URL alone picks which of your sign-ins to use.

informer-init leaves an existing @entrinsik/vite-plugin-informer entry alone, and writes one only when the dependency is missing — in which case it uses its own version (2.12.0 and later; before that it wrote ^1.0.0). A project scaffolded earlier still carries ^1.0.0, which resolves to 1.0.5 and predates everything below: bump it by hand.

What a deploy ships

The assembler uploads your built frontend plus these source trees, matching how an App's library is structured in Informer:

FolderBecomes
server/API routes, dispatched under the App's /api surface
webhooks/Publicly callable webhook routes
tools/AI tools, private to the App's agents
mcp/Tools exposed over the App's MCP endpoint
channels/Live channel handlers: join / joined / leave and event-named handlers for channel.send(), or a channel actor (config.actor: one live instance per channel, with start / tick / stop / snapshot); npm run dev runs both locally, and the channels: block in informer.yaml declares relays only (each entry needs on)
migrations/Workspace database migrations, run in order on deploy
embeddings/Declarative embedding use cases (see below)
lib/, shared/Modules importable by the trees above

informer.yaml declares dependencies, raw API access, roles, and (optionally) a minimum Informer version.

Server compatibility

A deploy begins by asking the target server what it is (GET /api/about, one unauthenticated request) and adapts to the answer, so one version of this plugin can drive a fleet on mixed releases.

Everything below needs Informer 2026.1.3: App Channels (channels/, the channels: block, broadcast()), the embedding pump (embeddings/, embed()), staged uploads/downloads (uploads/downloads on the handler bag, __INFORMER__.upload()), and the platform descriptor itself.

Against an older server the CLI:

  • Leaves channels/ and embeddings/ on your disk. Those releases don't recognise the folders as server-side, so anything uploaded there is served to any App viewer — your workspace SQL and channel authorization logic included. The rest of the App deploys normally; the warning names what was skipped and why.
  • Names every feature that would be inert, so a green deploy can't be mistaken for a working one.
  • Enforces requires: { informer: … } itself. Releases before 2026.1.3 ignore the block, so without this an App that declared a floor would deploy clean onto a server that can't run it. The deploy is refused before the App's library is touched.

A server whose version isn't comparable (a dev build, or an /about behind a proxy) is treated as current and warned about — gating it would break deploys to builds made from source.

App Channels phase 2 (plugin ≥ 2.11.0: channel.send(), the joined export and event-named exports in channels/ files, wildcard subscriptions, frame seq and replay, connected) needs a 2026.1.3 server that carries I5-13027. The version probe cannot tell such a server from an earlier 2026.1.3 build, so the deploy is not gated: an earlier build refuses a channels/ file that exports joined or an event name, and accepts a channels: entry without on that a current server rejects. The dev server runs the phase 2 contract regardless; plugin 2.12.0 aligns its channel semantics with the server's (wildcard gating, replay expiry, and the per-user send budget), so on 2.11.0 the dev mirror has the phase 2 surface but not all of its behavior.

Channel actors (plugin ≥ 2.13.0: config.actor files, request.member, __INFORMER__.serverNow()) need a server that reports platform.capabilities.channelActors; on one that does not, the same file runs as an ordinary channel handler, one fresh module per message. Under npm run dev each concrete channel gets its own module instance, calls run one at a time, tick() runs on a timer, and the actor stops idleMs after its last page leaves. snapshot() is kept in memory, so editing a file under channels/, shared/, lib/ or server/ restarts the running actors on the new code from their snapshots with the same pages subscribed: a game in progress survives the edit. The server's cluster, leases, billing and broadcast limits have no dev counterpart.

Feature-detect at runtime with optional chaining, since platform is absent entirely before 2026.1.3:

if (platform?.capabilities?.channels) broadcast('tickets', { … });

A marketplace publish is unaffected: the archive always carries every source tree, because the server that installs it isn't known at publish time.

Embeddings

Apps can maintain vector embeddings over their own workspace data declaratively. You ship an embeddings/ folder with one file per use case; the Informer platform acts as an embedding pump that asks your App what is pending, chunks and embeds the content in billed batches, and hands the vectors back for your App to store in its own workspace tables. You never call an embedding provider yourself, and the platform never keeps a copy of your corpus — your own tables are the ledger.

Each use case file exports GET and POST (a missing handler fails the deploy) and, optionally, a config literal; omitted keys take the defaults below, and a use case with no triggers in its config only runs on deploy and on a manual run:

// embeddings/tickets.js
export const config = { chunking: 'prose', on: ['ticket.created'], cron: '0 * * * *', revision: 1 };

// GET: the rows that still need embedding. The query IS the watermark:
// never embedded, stale, or written under an older revision.
export async function GET({ query, batch }) {
  return await query(`
    SELECT t.id, t.subject || ' ' || t.body AS content
    FROM tickets t
    LEFT JOIN ticket_embeddings e ON e.ticket_id = t.id AND e.seq = 0
    WHERE e.ticket_id IS NULL OR e.embedded_at < t.updated_at OR e.revision <> $2
    ORDER BY t.id
    LIMIT $1
  `, [batch.limit, batch.revision]);
}

// POST: store the finished vectors wherever your migrations put them.
export async function POST({ query, batch }) {
  for (const doc of batch.docs) {
    await query('DELETE FROM ticket_embeddings WHERE ticket_id = $1', [doc.id]);
    for (const c of doc.chunks) {
      await query(
        'INSERT INTO ticket_embeddings (ticket_id, seq, embedding, content, revision) VALUES ($1, $2, $3::vector, $4, $5)',
        [doc.id, c.seq, JSON.stringify(c.embedding), c.content, batch.revision]
      );
    }
  }
  return { stored: batch.docs.length };
}

How it works, briefly:

  • Your storage is the ledger. Because GET anti-joins your own embedding table, the pump is idempotent and crash-tolerant: a run that dies mid-way finds the same rows pending next time.
  • Triggers converge. Deploy pokes each use case (initial backfill), emit() events matching on: poke, cron desugars into a scheduled poke, and the platform sweep runs whatever is poked. A manual run claims the same single-flight lease directly, so nothing double-embeds.
  • Failures are bounded. A run that fails for a reason the next attempt might not hit (a provider blip, a handler timeout) retries with a doubling delay and is parked after five consecutive failures; a run that cannot succeed as-is (a rejected key, an exhausted budget, a contract violation) is parked at once. Every park keeps the error on the use case's status row; the next trigger gives it one more attempt, and a successful run resets the count.
  • revision is the invalidation. The string the pump hands you couples your config (chunking profile, budgets, config.revision) to the resolved platform embedding model. Store it beside each vector and compare it in GET: bumping the config, changing chunking, or an admin repointing the embedding model all surface as pending re-embed work automatically.
  • Unembeddable documents are tombstoned platform-side by content hash and re-reported with skipped: true on every run — stamp them in your own table and exclude them from GET.
  • Workspace pgvector. Apps with the embeddings capability get the vector extension provisioned where the server may create extensions (otherwise the deploy log names the cause and a migration using the vector type fails), so migrations can declare real vector(...) columns; ON DELETE CASCADE foreign keys are the cleanup story.
  • Query time. Every handler bag (server/, webhooks/, tools/, mcp/) gets embed(name, text), which returns { embedding, revision }. name must be a declared use case. Filter on the revision as well as ordering by distance — an admin repointing the embedding model puts your query vectors in a new space while the stored rows are still in the old one, and an unfiltered ORDER BY then returns near-random neighbours with no error:
const { embedding, revision } = await embed('tickets', q);
const hits = await query(
  `SELECT ticket_id, content
     FROM ticket_embeddings
    WHERE revision = $2
    ORDER BY embedding <=> $1::vector
    LIMIT 10`,
  [JSON.stringify(embedding), revision]
);
  • Billing. Embedding calls bill to the App, one usage entry per batch of up to 128 chunks regardless of how many provider requests run underneath; pump compute meters against the App's compute budget.
  • config is read as a literal. Deploy lifts export const config = { … } out of the source and evaluates it with none of the file's imports in scope, so it must be self-contained: no imported constants, no spreads of imported objects, no export { config } re-export. Other modules may import the literal from the use-case file; the reverse fails the deploy. The same self-contained-literal rule applies to export const config, schema, and description in server/, webhooks/, tools/, and mcp/; there a config violation fails the deploy while a schema or description violation is a deploy warning (nothing is published for that export).

Dev loop

npm run deploy uploads embeddings/ like any other source tree — there is no manifest block to add (plugin ≥ 2.10.0; earlier versions never upload the folder, so the use case silently does not exist on the server). npm run dev never runs the pump, and its embed() throws an explanation rather than embedding (the dev mirror reports platform.capabilities.embeddings: false), so a search route is exercised against a deployed App. Plugin ≥ 2.12.0 can opt in: informer({ mock: { platform: { capabilities: { embeddings: true } } } }) binds the dev embed() to the deployed App's _embed route on the configured server (addressed by package.json informer.id; the dev credentials need write access to that App, and each call is billed to it), so the query vector under npm run dev is the deployed one — same model, same revision. The corpus is not: query() still reads the dev workspace datasource, which the pump never writes to, so a search route compares a deployed vector against local rows. Feature-detect with platform?.capabilities?.embeddings — typeof embed === 'function' is true on both, since the binding exists either way. Then, in the App admin panel, the Embeddings tab lists each use case with its pump status (queued, running, up to date, last run, last error, skipped docs) and a Run now action; the same surface exists as API routes (GET /apps/{id}/embeddings, POST /apps/{id}/embeddings/{name}/_run). Deploy, run, read the error, fix, repeat.

Upgrading an App that already has an embeddings/ folder

embeddings/ is a server-side folder from Informer 2026.1.3 on, like server/: its files are uploaded with the library, never served to browsers, and scanned as pump handlers. An App that kept anything else there (assets, data files) loses those files in the browser after redeploying with plugin 2.10.0 or later. A .js file there that exports GET or POST but not both fails the deploy; one that exports neither is never scanned as a use case at all and lands as a warning on an otherwise successful deploy. Move such content elsewhere before upgrading; the deploy names every stray entry as a warning.

Older Informer releases

The embeddings/ folder, embed(), and the pump routes exist only on Informer releases that ship the feature. An App that must also run on older servers feature-detects with optional chaining (platform?.capabilities?.embeddings on the handler bag, window.__INFORMER__.platform?.capabilities?.embeddings in the browser) — releases before 2026.1.3 inject no platform descriptor at all, so a bare platform.capabilities.embeddings throws there instead of reporting false. It gates its vector DDL with a -- requires: embeddings header so the migration is skipped where pgvector is absent, and states a floor in informer.yaml (requires: { informer: '>=…' }) only if it cannot work without the feature.

Full reference: the Embeddings article under Developer → API → App in the Informer docs.

版本与发布记录

@entrinsik/vite-plugin-informer 共发布 25 个版本,首次发布于 2026/01/31;过去 12 个月发布了 25 个版本。

版本发布日期距今
2.14.0latest2026/10/2321 天前
2.14.0-beta.0beta2026/10/121 个月前
2.13.02026/09/221 个月前
2.12.02026/09/122 个月前
2.11.02026/09/092 个月前
2.10.02026/09/092 个月前

关于 @entrinsik/vite-plugin-informer 的常见问题

@entrinsik/vite-plugin-informer 是什么?

Vite plugin and deploy tool for Informer App development

如何安装 @entrinsik/vite-plugin-informer?

在项目目录运行 npm install @entrinsik/vite-plugin-informer,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:vite(>=5.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。它还提供命令行工具 informer-ci、informer-init、informer-deploy、informer-publish、informer-workspace、create-magic-report,可以用 npx informer-ci 直接运行。

@entrinsik/vite-plugin-informer 支持 TypeScript 吗?

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

@entrinsik/vite-plugin-informer 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@entrinsik/vite-plugin-informer 需要什么版本的 Node.js?

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

@entrinsik/vite-plugin-informer 可以商用吗?

@entrinsik/vite-plugin-informer 使用 UNLICENSED 许可。作者声明为 UNLICENSED,即不授予公开使用许可(与公共领域的 Unlicense 不同)。使用前需获得作者授权。以上为概括,不构成法律意见,请以许可原文为准。

@entrinsik/vite-plugin-informer 的下载量有多少?

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

@entrinsik/vite-plugin-informer 还在维护吗?

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