# @econ-v1/app-sdk

> **Status: `1.0.0-experimental`** — API surface is frozen pending validation by at least two first-party packages. Breaking changes between `1.0.0-experimental.*` releases are possible. Pin to an exact version in production.

TypeScript / Bun SDK for building **Node-App** plugins for the [Node](https://github.com/econ-v1/node-app-distribution) Lightning-powered marketplace daemon. Apps run as separate Bun processes and talk to the host over a Unix-domain socket using a JSON-newline protocol.

## What it gives you

- An abstract `NodeApp` class — extend it, declare metadata, and implement the hooks you need.
- A declarative `route()` helper — register HTTP handlers with built-in scope checks (defense-in-depth on top of the host's manifest enforcement).
- `invokeCapability()` — call any capability registered with the host's capability router.
- `publishEvent()` — publish namespaced domain events to the host event bus.
- `host.{trace,debug,info,warn,error}` — file-backed structured logging.

## Quick start

```sh
bun add @econ-v1/app-sdk@1.0.0-experimental
```

```typescript
import { NodeApp, runNodeApp, type AppRequest, type AppResponse } from "@econ-v1/app-sdk";

class MyApp extends NodeApp {
  readonly metadata = {
    name: "my-app",
    version: "0.1.0",
    author: "Me",
    description: "Hello-world Node-App",
    capabilities: ["http_handler"],
  };

  async init() {
    this.route("GET", "/hello", { requiredPermissions: [] }, async () =>
      this.json({ hello: "world" }),
    );
  }
}

runNodeApp(new MyApp());
```

The host launches the Bun process, supplies a Unix socket via the `NODE_APP_SOCKET` environment variable, and routes proxied HTTP requests, events, and capability calls over the socket.

## Burger apps

The same package serves apps that run on Burger (`app_type: "burger"`), and
it carries their toolchain, so a Burger app depends on `@econ-v1/app-sdk`
and nothing else from `@econ-v1`:

| Part | How an app uses it |
|---|---|
| `burger-build` | The package's `bin`: `burger-build build src/index.ts --outdir dist` and `burger-build test-prepare …` (see `sdk/burger-build/README.md`). `node-app build` / `node-app test` run it for you. Needs Bun ≥ 1.4.2. The same code is importable as `@econ-v1/app-sdk/burger-build` (`buildApp`, `prepareTests`). |
| `burger-types` | `"types": ["@econ-v1/app-sdk/burger-types"]` in `tsconfig.json`: the `burger:*` modules, their `node:`/`bun:` aliases and the runtime globals. `@econ-v1/app-sdk/burger-types/modules` declares only `burger:*`. |

Both are built from `sdk/burger-build` and `sdk/burger-types` in this
repository by `bun run build` (`scripts/build-burger-toolchain.ts`).

Bundling with `burger-build` selects the `burger` export condition,
which resolves `@econ-v1/app-sdk` to `dist/burger/index.js`: identical API,
but the host channel is `burger:host` (framing and JSON handled by the
runtime), memory samples come from the QuickJS runtime, and there is no
worker-thread detection. Differences an app can observe:

- `openDatabase()` defaults to a 256 KiB page cache (Burger's cap) instead
  of 1 MiB.
- Telemetry stays inactive (`telemetryActive()` is `false`): Burger Phase 1
  has no `fetch` for an OTLP exporter.
- `heap_snapshot_request` is answered with `success: false`.
- A nested `invokeCapability` carries `invocation_context_id` only when it
  is made before the handler's first `await` (Burger Phase 1 has no
  `AsyncLocalStorage`); later calls are classified by the host as background
  work, never as foreground. This is the known Phase 1 limitation recorded
  in Burger Contract C6
  (`docs/superpowers/plans/2026-09-17-burger-00-overview-and-contracts.md`)
  and fixed by `burger-08`.

Burger apps bundle the SDK into `dist/index.js`; nothing is shared at runtime.

## Building for production

`@econ-v1/app-sdk` is itself a **shared dependency** in the Node deb (installed
once at `/usr/lib/node/shared/node_modules/@econ-v1/app-sdk`), so it's *not*
bundled into your app's `dist/index.js`. The build pipeline handles this
automatically — you don't have to do anything special.

Your app's other deps land in one of three buckets:

| Where | When |
|---|---|
| **Bundled** into `dist/index.js` *(default)* | Pure-JS deps; tree-shaken + minified |
| **Shared** at `/usr/lib/node/shared/node_modules/` | Dep is in the curated `SHARED_EXTERNALS` list (LLM SDKs, this SDK) |
| **Private** at `<your-app>/node_modules/` | You opt in via `manifest.json#nodeApp.privateRuntime` (native bindings, version overrides) |

Opt into a private dep by editing your `manifest.json`:

```json
{
  "name": "my-app",
  "version": "0.1.0",
  "entrypoint": "dist/index.js",
  "nodeApp": {
    "privateRuntime": ["better-sqlite3"]
  }
}
```

Use `privateRuntime` only for packages with native bindings or version
conflicts with the shared tree — each entry adds its full installed size to
your app. See the canonical reference at
[`docs/app-development/12-typescript-dependencies.md`](https://github.com/econ-v1/node/blob/main/docs/app-development/12-typescript-dependencies.md)
for the full bundling and resolution model.

### Build command

```sh
# Inside the node monorepo:
bash infra/scripts/build-bun-app.sh modules/my-app dist/build-aarch64/builtin-apps/my-app

# As a downstream app (after publishing):
bunx @econ-v1/app-sdk build .
```

## Defense-in-depth scope checks

```typescript
this.route(
  "POST",
  "/admin/**",
  { requiredPermissions: ["payments:write"] },
  async (req) => this.json({ ok: true }),
);
```

The host already enforces `endpoint_policies` from your manifest before the request reaches the app — `route()` re-checks `request.caller.granted_permissions` as a second line of defense.

## Calling host capabilities

```typescript
import { invokeCapability } from "@econ-v1/app-sdk";

const response = await invokeCapability({
  id: crypto.randomUUID(),
  capability: "core.storage.get",
  payload: { key: "user_pref" },
});
```

## Publishing events

```typescript
import { publishEvent } from "@econ-v1/app-sdk";

publishEvent("my-app.user_created", { userId: 42 });
```

Event names **must** be namespaced with the app name (`my-app.*`); the host rejects un-namespaced events.

## ABI compatibility

The TypeScript SDK targets **Node Host API v1** over IPC. The wire format is independently versioned but tracks the same semantics as the C ABI used by native (Rust / Go / C / Zig) apps. See `core/host-abi-v1/include/node-host-api-v1.h` for the C surface.

## License

Licensed under either of

- Apache License, Version 2.0
- MIT License

at your option.
