Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 3 additions & 16 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,7 @@
"./node_modules/ultracite/config/oxlint/core/.oxlintrc.json",
"./node_modules/ultracite/config/oxlint/react/.oxlintrc.json"
],
"overrides": [
{
"files": [
"src/index.ts",
"src/schemas/index.ts",
"src/schemas/base/index.ts",
"src/schemas/responses/index.ts",
"src/schemas/requests/index.ts",
"src/errors/index.ts",
"src/app-bridge/index.ts"
],
"rules": {
"oxc/no-barrel-file": "off"
}
}
]
"rules": {
"oxc/no-barrel-file": "off"
}
}
14 changes: 12 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ Pre-commit hook (lefthook) auto-runs `bun x ultracite fix` on staged files.

After any significant code change, always run `bun run lint` and `bun run fix` to ensure lint and formatting pass before committing.

## Documentation Rules

After any significant code change, update the following:

1. **`docs/progress.md`** — mark completed features, update status
2. **`README.md`** — keep usage examples and API docs current
3. **Feature-specific docs** — if README.md grows too large, create docs under `docs/` (e.g. `docs/app-bridge.md`) and link from the main README

Always keep docs in sync with the code. Do not defer documentation to a later step.

## Architecture

`assembly-kit` is a TypeScript-first SDK for the Assembly platform. It is an **ESM-only single package** with 4 entry points, targeting Node.js 18+, Node.js 24+, and Bun.
Expand All @@ -33,7 +43,7 @@ After any significant code change, always run `bun run lint` and `bun run fix` t
| `assembly-kit` | `createClient()`, error classes, token utilities, `paginate()` |
| `assembly-kit/schemas` | All Zod schemas and inferred types (no client dependency) |
| `assembly-kit/app-bridge` | Framework-agnostic `sendToParent()` postMessage utilities |
| `assembly-kit/react` | React hooks wrapping app-bridge (`usePrimaryCta`, `useSecondaryCta`, `useActionsMenu`) |
| `assembly-kit/bridge-ui` | React hooks wrapping app-bridge (`usePrimaryCta`, `useSecondaryCta`, `useActionsMenu`) |

### Source Layer Dependency Order

Expand All @@ -46,7 +56,7 @@ src/pagination/ ← paginate() AsyncIterable cursor helper
src/client/ ← createClient() factory + AssemblyClient class
src/resources/ ← workspace, clients, companies, internalUsers, notifications, customFields, tasks, token
src/app-bridge/ ← parallel track, no dependency on layers above
src/react/ ← depends on app-bridge only
src/bridge-ui/ ← depends on app-bridge only
```

### Zod Version
Expand Down
236 changes: 236 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,242 @@ try {
| `AssemblyResponseParseError` | 500 | API response failed Zod schema validation (`.zodError`) |
| `AssemblyConnectionError` | 503 | Network error reaching the API |

### Schemas

All Zod schemas live under `assembly-kit/schemas`. Each resource has a **base** schema, a **response** schema (wrapping the base for paginated API responses), and optionally a **request** schema for create/update payloads.

```typescript
// Base schemas — the core shape of each resource
import {
ClientSchema,
CompanySchema,
TaskSchema,
TaskStatusSchema,
WorkspaceSchema,
InternalUserSchema,
InvoiceSchema,
CustomFieldSchema,
TokenPayloadSchema,
HexColorSchema,
} from "assembly-kit/schemas";

// TypeScript types inferred from schemas
import type {
Client,
Company,
Task,
TaskStatus,
Workspace,
InternalUser,
} from "assembly-kit/schemas";
```

#### Response schemas

Response schemas wrap the base schemas into the paginated shape returned by the Assembly API:

```typescript
import {
ClientsResponseSchema,
CompaniesResponseSchema,
TasksResponseSchema,
} from "assembly-kit/schemas";

import type {
ClientsResponse,
CompaniesResponse,
TasksResponse,
} from "assembly-kit/schemas";
```

#### Request schemas

Request schemas define the shape of create/update payloads:

```typescript
import {
ClientCreateRequestSchema,
ClientUpdateRequestSchema,
CompanyCreateRequestSchema,
TaskCreateRequestSchema,
} from "assembly-kit/schemas";

import type {
ClientCreateRequest,
ClientUpdateRequest,
} from "assembly-kit/schemas";
```

#### Validating data

```typescript
import { ClientSchema } from "assembly-kit/schemas";

const result = ClientSchema.safeParse(unknownData);

if (result.success) {
console.log(result.data.name);
} else {
console.error(result.error);
}
```

#### Sub-path imports

You can also import from specific schema groups to reduce bundle size:

```typescript
import { ClientSchema } from "assembly-kit/schemas/base";
import { ClientsResponseSchema } from "assembly-kit/schemas/responses";
import { ClientCreateRequestSchema } from "assembly-kit/schemas/requests";
```

### App Bridge

The app-bridge entry point provides framework-agnostic utilities for communicating with the Assembly dashboard from an embedded iframe app. Works in any JavaScript environment — no React dependency required.

#### `sendToParent`

Sends a typed postMessage payload to the Assembly dashboard parent frame:

```typescript
import { sendToParent, Icons } from "assembly-kit/app-bridge";
import type { PrimaryCtaPayload } from "assembly-kit/app-bridge";

// Register a primary CTA button in the dashboard header
const payload: PrimaryCtaPayload = {
type: "header.primaryCta",
label: "Create Invoice",
icon: Icons.Plus,
onClick: "header.primaryCta.onClick",
};

sendToParent(payload);
```

When called without a `portalUrl`, it fans out the message to all known Assembly dashboard domains. Pass a specific origin to restrict:

```typescript
sendToParent(payload, "https://dashboard.assembly.com");
```

`sendToParent` is SSR-safe — it's a no-op when `window` is undefined.

#### Payload types

```typescript
import type {
PrimaryCtaPayload, // { type: "header.primaryCta", label?, icon?, onClick? }
SecondaryCtaPayload, // { type: "header.secondaryCta", label?, icon?, onClick? }
ActionsMenuPayload, // { type: "header.actionsMenu", items: ActionItem[] }
AppBridgePayload, // Discriminated union of all three
ActionItem, // { label, onClick, icon?, color? }
CtaConfig, // { label?, icon?, onClick?(), color? }
BridgeOpts, // { portalUrl?, show? }
} from "assembly-kit/app-bridge";
```

#### Clearing a slot

Send a payload with only the `type` field to remove a button, or an empty items array for the actions menu:

```typescript
sendToParent({ type: "header.primaryCta" });
sendToParent({ type: "header.actionsMenu", items: [] });
```

### Bridge UI (React Hooks)

React hooks that wrap `sendToParent` into a declarative API. They handle setup, cleanup, and `beforeunload` automatically.

Requires `react >= 18` as a peer dependency.

#### `usePrimaryCta`

Registers a primary CTA button in the dashboard header:

```tsx
import { usePrimaryCta } from "assembly-kit/bridge-ui";
import { Icons } from "assembly-kit/app-bridge";

function MyApp() {
usePrimaryCta({
label: "Create Invoice",
icon: Icons.Plus,
onClick: () => {
console.log("Primary CTA clicked");
},
});

return <div>My App</div>;
}
```

#### `useSecondaryCta`

Registers a secondary CTA button. Same API as `usePrimaryCta`:

```tsx
import { useSecondaryCta } from "assembly-kit/bridge-ui";
import { Icons } from "assembly-kit/app-bridge";

function MyApp() {
useSecondaryCta({
label: "Export",
icon: Icons.Download,
onClick: () => {
console.log("Secondary CTA clicked");
},
});

return <div>My App</div>;
}
```

#### `useActionsMenu`

Registers a dropdown actions menu in the dashboard header:

```tsx
import { useActionsMenu } from "assembly-kit/bridge-ui";
import { Icons } from "assembly-kit/app-bridge";

function MyApp() {
useActionsMenu([
{ label: "Archive", onClick: "actions.archive", icon: Icons.Archive },
{
label: "Delete",
onClick: "actions.delete",
icon: Icons.Trash,
color: "red",
},
]);

return <div>My App</div>;
}
```

#### Visibility toggle

All hooks accept an optional second argument to control visibility:

```tsx
usePrimaryCta({ label: "Save", onClick: () => save() }, { show: hasChanges });
```

When `show` is `false`, the slot is cleared in the dashboard header. Defaults to `true`.

#### Portal URL

If your app is embedded in a custom portal, pass the portal origin to restrict postMessage targeting:

```tsx
usePrimaryCta(
{ label: "Save", onClick: () => save() },
{ portalUrl: "https://my-portal.example.com" }
);
```

## Development

```bash
Expand Down
7 changes: 7 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions bunup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const bunupConfig = defineConfig({
"src/schemas/responses/index.ts",
"src/schemas/requests/index.ts",
"src/errors/index.ts",
"src/bridge-ui/index.ts",
],
format: "esm",
minify: true,
Expand Down
Loading