English | 简体中文

本文是公开 API 的结构化速查。行为语义与设计原因请继续阅读对应专题文档。

1. 安装与运行环境

pnpm add @jason-huang/infinite-canvas-agent
  • Node.js:>=20(构建、SSR 和工具链)。
  • 浏览器 API:Canvas2D/IndexedDB 适配器需要相应浏览器能力。
  • 输出:ESM + CommonJS + .d.ts
  • sideEffects: false,建议使用 subpath import 控制模块图。

ESM:

import { createCanvasRuntime } from '@jason-huang/infinite-canvas-agent'

CommonJS:

const { createCanvasRuntime } = require('@jason-huang/infinite-canvas-agent')

2. Subpath 与可选 peer

Import path 主要导出 额外 peer
包根路径 core、agent、cache、integration、persistence 的公共导出
/core model、camera、document、spatial index、working set、runtime
/integration createCanvasRuntime, CanvasRuntimeKit
/cache ResourcePool, LOD helpers, AssetStore
/agent commands、CommandBus、context、PlannerPort、remote parser
/canvas2d Canvas2DRenderer
/konva KonvaRenderer konva >=10 <11
/rbush RBushSpatialIndex rbush >=4 <5
/persistence persistence ports、memory implementation
/indexeddb IndexedDbPersistence
/react React useCanvasRuntime react >=18 <20
/vue Vue useCanvasRuntime vue >=3.3 <4

只有导入对应 subpath 才需要可选 peer。例如 cache-only 项目不需要安装 React、Vue、Konva 或 RBush。

3. Core model

Types

type NodeId = string
type JsonPrimitive = boolean | number | string | null
type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue }
type JsonObject = { [key: string]: JsonValue }

interface Bounds { minX: number; minY: number; maxX: number; maxY: number }
interface Camera { centerX: number; centerY: number; zoom: number }
interface Viewport { width: number; height: number }
interface ViewState { camera; viewport; exactBounds; expandedBounds }
interface CanvasNode<Payload extends JsonObject = JsonObject> {
  id: string
  kind: string
  bounds: Bounds
  rotation?: number
  zIndex?: number
  assetIds?: string[]
  payload: Payload
}

Functions

Function Result / failure
assertBounds(bounds) 非有限数或反转 bounds 时抛错
assertNode(node) 校验 id、kind、bounds
cloneNode(node) structuredClone
intersects(left, right) bounds 是否相交,接触边界视为相交
clampZoom(zoom, min?, max?) 默认 clamp 到 0.02..5
visibleWorldBounds(camera, viewport) 精确可视世界 bounds
expandBounds(bounds, distance) 四边外扩
createViewState(camera, viewport, options?) clamp camera + exact/expanded bounds
worldToViewport(point, view) 世界坐标转 CSS viewport 坐标
viewportToWorld(point, view) viewport 坐标转世界坐标

4. Document

DocumentStore<Node>

Member 说明
revision 当前非负整数 revision
get(id) 返回节点克隆或 undefined
values() 返回全部节点克隆
snapshot() { revision, nodes }
commit(input) revision + before 校验的原子提交
subscribe(listener) 返回 unsubscribe

MemoryDocumentStore(initialNodes?, initialRevision?) 是默认内存实现。构造时拒绝重复 id。

提交输入与输出:

interface NodeChange<Node> { id: string; before: Node | null; after: Node | null }
interface CommitInput<Node> {
  transactionId: string
  label: string
  expectedRevision: number
  changes: NodeChange<Node>[]
}
interface DocumentCommit<Node> {
  transactionId: string
  label: string
  baseRevision: number
  revision: number
  changes: NodeChange<Node>[]
  committedAt: number
}

5. Spatial index 与 WorkingSet

SpatialIndex<Node>

load, upsert, remove, search, clear, size。内置实现:

  • LinearSpatialIndex:无 peer,O(n) search;
  • RBushSpatialIndex/rbush,适合大文档。

WorkingSet

new WorkingSet({ exitGraceMs?: number }) // 默认 180
workingSet.reconcile(candidates, pinned, now?)
workingSet.remove(id)
workingSet.clear()
workingSet.values()

reconcile 返回:enter, stay, pendingExit, exit, active

6. Renderer 与 Runtime

Renderer ports

Port 新增能力
RendererPort setView/hydrate/update/dehydrate/draw/count/destroy
PreviewRendererPort setPreview(node?)
PngExportRendererPort exportPng(): Promise<Blob>
InteractiveRendererPort 同时包含 preview + PNG

内置 Canvas2DRendererKonvaRenderer 都实现 InteractiveRendererPort

Canvas2D options

Option 默认值
devicePixelRatio globalThis.devicePixelRatio ?? 1
background #f8f8fc
showGrid false
gridColor #d8dbe6
axisColor #b7bdca
selectionColor #ff7a45
style defaultNodeStyle

Konva options

与 Canvas2D 的背景/网格/样式选项相同;另有 exportPixelRatio,默认 DPR。

CanvasRuntime<Node>

const runtime = new CanvasRuntime(document, spatialIndex, renderer, {
  overscanPixels?: number
  minimumZoom?: number
  maximumZoom?: number
  exitGraceMs?: number
})

runtime.setSelection(ids)
runtime.setPinned(ids)
runtime.refresh(camera, viewport, now?)
runtime.destroy()

createCanvasRuntime

推荐使用的组合工厂。输入可包含:

  • renderer:必填;
  • documentinitialSnapshot:二选一;
  • initialHistory:必须与 Document revision 相同;
  • spatialIndex:默认 Linear;
  • runtime:Camera/WorkingSet 参数;
  • history:CommandBus 历史预算。

返回 CanvasRuntimeKitdocument, spatialIndex, renderer, runtime, commands, refresh, destroy

7. Commands 与 Agent

Command types

type CanvasCommand<Node> =
  | { type: 'create'; node: Node }
  | { type: 'update'; id: string; patch: Partial<Omit<Node, 'id'>> }
  | { type: 'delete'; id: string }

interface AgentPlan<Node> {
  actionId: string
  label: string
  expectedRevision: number
  commands: CanvasCommand<Node>[]
}

applyCommandsToSnapshot(initialNodes, commands) 在克隆 Map 上暂存命令,返回 before/after Map, 不修改输入数组。

CommandBus

const bus = new CommandBus(document, {
  maxHistoryTransactions?: number // 默认 100
  maxHistoryBytes?: number        // 默认 8 MiB
})

bus.execute(plan)
bus.undo()
bus.redo()
bus.inspectHistory()
bus.exportHistory()
bus.restoreHistory(snapshot)

Agent helpers

API 作用
buildAgentContext(input) 按 selected/visible/recent/remaining 构建有界上下文
compactNodeForRemoteAgent(node, options?) 明确裁剪允许发送的 payload
parseRemoteAgentPlan(input) JSON、schema、大小、policy、最终快照校验
PlannerPort 宿主 Planner 抽象,不包含实现
RemoteAgentPlanPolicy kinds、patch keys、坐标、size、payload、points、业务校验

详细门禁值见 Agent Integration

8. Cache 与 Asset

ResourcePool<Resource>

const pool = new ResourcePool<Resource>(budgetBytes)
const lease = await pool.acquire(key, loader)
lease.release()
pool.evictUnused(targetBytes?)
pool.inspect()
pool.destroy()

loader 返回 { value, bytes, dispose(value) }。Stats 字段:entries、bytes、budgetBytes、 activeLeases、pendingLoads、hits、misses、evictions、rejections。

LOD

  • DEFAULT_IMAGE_TIERS = [128, 256, 512, 1024, 2048, 4096]
  • selectImageTier(projectedMaxEdge, options?)
  • imageResourceKey(assetId, tier)
  • 默认 downshift ratio 0.45

Asset

  • AssetVariant
  • AssetManifest
  • AssetStore
  • MemoryAssetStore

9. Persistence

API 说明
DocumentPersistence load/save snapshot + append commit
CanvasWorkspacePersistence 同 revision 保存/恢复 Document + History
MemoryDocumentPersistence 测试和无刷新内存实现,可 inspect journal
IndexedDbPersistence /indexeddb,兼具 workspace 和 asset store

IndexedDbPersistence options:databaseName 默认 infinite-canvas-agent,version 默认 2

10. React / Vue lifecycle binding

React:

const runtimeRef = useCanvasRuntime(factory, dependencies)
// event handler 中读取 runtimeRef.current

effect cleanup 会 destroy 当次创建的 kit。factory 应在 DOM ref 可用后返回 kit 或 null。

Vue:

const runtime = useCanvasRuntime(factory)
// 读取 runtime.value

onMounted 创建,onBeforeUnmount destroy。两个 binding 都不管理业务 state、camera 或输入事件。

11. 公共错误

Error code 触发条件 推荐处理
RevisionConflictError revision-conflict expected revision 或 before 过期 重读、重算、再提交
ActionIdConflictError action-id-conflict 同 action id 被不同计划复用 修复调用方 id 生命周期
ResourceBudgetExceededError resource-budget-exceeded 无可淘汰空间或单资源过大 降 LOD/释放 lease/拒绝挂载

其他参数/schema 错误使用 TypeErrorRangeError 或普通 Error。不要靠错误 message 做程序 分支;优先使用公共 error class 或 code

12. 类型扩展示例

interface CardPayload extends JsonObject {
  schemaVersion: 1
  title: string
  color: string
}

interface CardNode extends CanvasNode<CardPayload> {
  kind: 'card'
}

const kit = createCanvasRuntime<CardNode>({ renderer })

当项目有多种节点时,用联合类型并在 Renderer/业务 validator 中按 kind 穷举。核心包不会 注册全局节点类型,也不会阻止应用添加自定义字段之外的 JSON payload。