Core API — @edv4h/usketch-core

The createApp function and AppInstance interface.

The @edv4h/usketch-core package provides the application bootstrap function.

createApp

import { createApp } from "@edv4h/usketch-core";

const app = await createApp({
  store: myBoardStore,
  plugins: [selectPlugin, panPlugin, rectPlugin, /* ... */],
});

CreateAppOptions

interface CreateAppOptions {
  store: BoardStore;
  plugins: UsketchPlugin[];
  lod?: {
    policy?: LodPolicy;
    initialMode?: RenderMode;
  };
  selectionForeground?: Pick<SelectionForeground, "render"> &
    Partial<Pick<SelectionForeground, "priority" | "order" | "fixed">>;
}
FieldDescription
storeRequired. A BoardStore instance (typically created via @edv4h/usketch-store).
pluginsRequired. Array of plugins to initialize. Order matters — plugins are set up sequentially.
lod?Optional. LOD (level-of-detail) configuration. If omitted, a zoom + shape-count composite policy starting in interactive mode is used.
selectionForeground?Optional. Replace the default selection UI (handles, bounding box, marquee) with a host-provided implementation. Registered internally at priority 100, so it wins over plugin defaults.

AppInstance

The return value of createApp:

interface AppInstance {
  store: BoardStore;
  layers: LayerManager;
  tools: ToolRegistry;
  shapes: ShapeRegistry;
  commands: CommandRegistry;
  shortcuts: ShortcutRegistry;
  events: EventBus;
  transient: TransientRegistry;
  lod: LodController;
  ui: UiRegistry;
  selectionForeground: SelectionForegroundRegistry;
  externalContent: ExternalContentRegistry;
  actions: ActionRegistry;
  services: ServiceRegistry;
  plugins: readonly UsketchPlugin[];
  destroy(): void;
}

All registries are populated after createApp resolves. You can query them to inspect what plugins registered.

destroy()

Call destroy() to tear down the app:

app.destroy();

This runs the teardown closures returned by each plugin’s setup(), in LIFO (reverse registration) order, then destroys the LOD controller and cleans up internal subscriptions — it does not call plugin.teardown() in registration order. Teardowns may be sync or async; destroy() itself stays synchronous and any async teardown is fire-and-forget (errors are logged).

Initialization Flow

createApp({ store, plugins })

  ├─ Create registries
  │   ├─ LayerManager
  │   ├─ ToolRegistry
  │   ├─ ShapeRegistry
  │   ├─ CommandRegistry
  │   ├─ ShortcutRegistry
  │   ├─ EventBus
  │   └─ TransientRegistry

  ├─ Bridge store mutations → EventBus

  ├─ For each plugin:
  │   ├─ Register in PluginRegistry
  │   └─ await plugin.setup(ctx)

  ├─ Register core shortcuts
  │   ├─ Ctrl+Z → undo
  │   └─ Ctrl+Shift+Z → redo

  └─ Return AppInstance

Error Handling

If any plugin’s setup() throws, createApp will:

  1. Run the teardowns already collected (those returned by earlier successfully-set-up plugins) in reverse (LIFO) order, rolling back so partially-initialized state doesn’t leak.
  2. Clean up the store mutation subscription.
  3. Re-throw the error.

So teardowns of already-set-up plugins are called. It’s still good practice to design your plugins to be safe even if partially initialized.

Usage with React

Typically you create the app instance once and provide the registries to React via context:

const app = await createApp({ store, plugins });

function App() {
  return (
    <UsketchProvider app={app}>
      <Canvas />
      <Toolbar />
      <PropertyPanel />
    </UsketchProvider>
  );
}

The UsketchProvider and React integration components are part of apps/web — they are not in the core package.