# The Deco App Pattern

Every Deco app follows the same structural pattern. This document describes each component.

## Directory Layout

```
{app-name}/
├── mod.ts               # App factory function
├── manifest.gen.ts      # Auto-generated block registry
├── runtime.ts           # Client-side invoke proxy
├── middleware.ts         # Per-request middleware (optional)
├── actions/             # Server-side mutations
├── loaders/             # Server-side data fetching
├── hooks/               # Client-side hooks (Preact signals)
│   └── context.ts       # Shared reactive state
├── sections/            # CMS-editable UI sections
├── handlers/            # HTTP request handlers
├── components/          # Shared components
├── utils/               # Internal utilities
│   ├── types.ts         # API types
│   ├── client.ts        # Typed HTTP client interface
│   └── transform.ts     # API → schema.org mapping
├── workflows/           # Background jobs
└── preview/             # Admin preview UI
```

## `mod.ts` — App Factory

The entry point. Exports a function that receives `Props` and returns an `App`.

```typescript
import manifest, { Manifest } from "./manifest.gen.ts";
import { middleware } from "./middleware.ts";
import { type App, type AppContext as AC } from "@deco/deco";

export type AppContext = AC<ReturnType<typeof MyApp>>;

export interface Props {
  account: string;
  apiKey?: Secret;
  salesChannel?: string;
  platform: "myplatform";
}

export default function MyApp(props: Props) {
  // Create typed HTTP clients
  const api = createHttpClient<MyAPI>({ base: `https://${props.account}.api.com` });
  const gql = createGraphqlClient({ endpoint: `https://${props.account}.api.com/graphql` });

  const state = { ...props, api, gql };

  const app: App<Manifest, typeof state> = {
    state,
    manifest,
    middleware,         // Optional
    dependencies: [],   // Other apps this depends on
  };

  return app;
}
```

### Key Concepts:
- **`state`** is available to all loaders/actions via `ctx` (AppContext)
- **`manifest`** registers all blocks (auto-generated)
- **`middleware`** runs before every request
- **`dependencies`** compose other apps (e.g., `website`, `workflows`)

## `manifest.gen.ts` — Block Registry

Auto-generated by `deno task start`. Registers all discoverable blocks:

```typescript
// DO NOT EDIT
import * as $$$0 from "./actions/cart/addItems.ts";
import * as $$$1 from "./loaders/productDetailsPage.ts";
import * as $$$$0 from "./sections/Analytics/Vtex.tsx";

const manifest = {
  "actions": { "myapp/actions/cart/addItems.ts": $$$0 },
  "loaders": { "myapp/loaders/productDetailsPage.ts": $$$1 },
  "sections": { "myapp/sections/Analytics/Vtex.tsx": $$$$0 },
  "name": "myapp",
  "baseUrl": import.meta.url,
};
export type Manifest = typeof manifest;
export default manifest;
```

## `runtime.ts` — Client Invoke Proxy

Provides typed client-side invocations:

```typescript
import { Manifest } from "./manifest.gen.ts";
import { proxy } from "@deco/deco/web";
export const invoke = proxy<Manifest>();

// Usage (client-side):
const cart = await invoke.myapp.loaders.cart();
const result = await invoke.myapp.actions.cart.addItems({ items: [...] });
```

## Actions Pattern

Server-side write operations. Each action is a single function:

```typescript
// actions/cart/addItems.ts
import { AppContext } from "../../mod.ts";

export interface Props {
  items: Array<{ id: string; quantity: number; seller: string }>;
}

const action = async (props: Props, req: Request, ctx: AppContext): Promise<OrderForm> => {
  const { vcsDeprecated } = ctx;
  // ... call VTEX API
  return orderForm;
};

export default action;
```

### Action conventions:
- Receives `(props, req, ctx)` — Props are CMS-configurable
- Returns the modified resource
- Placed in `actions/{domain}/{operation}.ts`
- Cart actions return `OrderForm`
- Auth actions return `AuthResponse`

## Loaders Pattern

Server-side read operations:

```typescript
// loaders/productDetailsPage.ts
import { AppContext } from "../mod.ts";
import { ProductDetailsPage } from "../../commerce/types.ts";

export interface Props {
  slug: string;
}

const loader = async (props: Props, req: Request, ctx: AppContext): Promise<ProductDetailsPage | null> => {
  // Fetch from API, transform to schema.org
  return { "@type": "ProductDetailsPage", product, breadcrumbList, seo };
};

export default loader;
```

### Loader conventions:
- Receives `(props, req, ctx)`
- Returns `null` for 404 cases
- Commerce loaders return `schema.org` types
- Placed in `loaders/{domain}/{operation}.ts`

## Hooks Pattern (Preact/Signals)

Client-side reactive state using `@preact/signals`:

### `context.ts` — Central State

```typescript
import { IS_BROWSER } from "$fresh/runtime.ts";
import { signal } from "@preact/signals";
import { invoke } from "../runtime.ts";

const loading = signal<boolean>(true);
const context = {
  cart: IS_BROWSER ? signal<OrderForm | null>(null) : { value: null },
  user: IS_BROWSER ? signal<Person | null>(null) : { value: null },
};

// Serial queue with abort support
let queue = Promise.resolve();
let abort = () => {};
const enqueue = (cb: (signal: AbortSignal) => Promise<Partial<Context>>) => {
  abort();
  loading.value = true;
  const controller = new AbortController();
  queue = queue.then(async () => {
    const result = await cb(controller.signal);
    context.cart.value = result.cart || context.cart.value;
    loading.value = false;
  });
  abort = () => controller.abort();
  return queue;
};

// Initial load
if (IS_BROWSER) {
  enqueue((signal) => invoke({ cart: invoke.myapp.loaders.cart() }, { signal }));
}

export const state = { ...context, loading, enqueue };
```

### `useCart.ts` — Domain Hook

```typescript
import { state as storeState } from "./context.ts";
import { invoke } from "../runtime.ts";

const { cart, loading } = storeState;

const enqueue = (key) => (props) =>
  storeState.enqueue((signal) =>
    invoke({ cart: { key, props } }, { signal })
  );

const state = {
  cart,
  loading,
  addItems: enqueue("myapp/actions/cart/addItems.ts"),
  updateItems: enqueue("myapp/actions/cart/updateItems.ts"),
  // ...
};

export const useCart = () => state;
```

### Key Hook Concepts:
- **Serial queue** — mutations execute one at a time, latest aborts previous
- **`enqueue`** pattern — wraps `invoke` with abort control
- **Signals** — reactive primitives from `@preact/signals`
- All state flows through `context.ts`

## Middleware Pattern

Runs before every request. Sets up shared context:

```typescript
// middleware.ts
import { AppMiddlewareContext } from "./mod.ts";

export const middleware = (_props: unknown, req: Request, ctx: AppMiddlewareContext) => {
  // Parse cookies, set segment in bag, etc.
  const cookies = getCookies(req.headers);
  setSegmentBag(cookies, req, ctx);
  setISCookiesBag(cookies, ctx);
  return ctx.next!();
};
```

Uses `ctx.bag` for per-request shared state (segment, cookies, orderFormId).

## Sections Pattern

CMS-renderable Preact components:

```typescript
// sections/Analytics/Vtex.tsx
import { AppContext } from "../../mod.ts";

export interface Props {
  trackingId?: string;
}

export default function VtexAnalytics(props: Props) {
  return <script dangerouslySetInnerHTML={{ __html: `...` }} />;
}
```

## OpenAPI Codegen

For apps with OpenAPI specs:

1. Place `{name}.openapi.json` in `utils/openapi/`
2. Run `deno task start`
3. Generated types appear in `{name}.openapi.gen.ts`
4. Use with `createHttpClient<GeneratedType>()`

## Commerce App Checklist

When building a new commerce integration:

- [ ] `utils/types.ts` — Define all API response types
- [ ] `utils/client.ts` — Define typed HTTP interface
- [ ] `utils/transform.ts` — Map API types → schema.org types
- [ ] `loaders/` — PDP, PLP, ProductList, Suggestions, Cart, User, Wishlist
- [ ] `actions/cart/` — addItems, updateItems, removeItems, updateCoupons
- [ ] `actions/authentication/` — signIn, logout, etc.
- [ ] `hooks/` — useCart, useUser, useWishlist, context
- [ ] `middleware.ts` — Cookie/segment handling
- [ ] `mod.ts` — App factory with clients
