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
60 changes: 60 additions & 0 deletions apps/docs/components/mermaid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use client';

import { useEffect, useId, useState } from 'react';
import { useTheme } from 'next-themes';

/**
* Client-side Mermaid renderer. Code fences with the `mermaid` language are
* rewritten to <Mermaid chart="..."/> by the remark plugin in source.config.ts,
* so authors keep writing standard ```mermaid blocks.
*/
export function Mermaid({ chart }: { chart: string }) {
const id = useId();
const [svg, setSvg] = useState('');
const { resolvedTheme } = useTheme();

useEffect(() => {
let cancelled = false;

async function render() {
const { default: mermaid } = await import('mermaid');
mermaid.initialize({
startOnLoad: false,
securityLevel: 'strict',
fontFamily: 'inherit',
theme: resolvedTheme === 'dark' ? 'dark' : 'default',
});
try {
const rendered = await mermaid.render(
// mermaid requires a DOM-safe element id
`mermaid-${id.replace(/[^a-zA-Z0-9]/g, '')}`,
chart,
);
if (!cancelled) setSvg(rendered.svg);
} catch {
// Leave the diagram source visible instead of a blank hole on bad syntax.
if (!cancelled) setSvg('');
}
}

void render();
return () => {
cancelled = true;
};
}, [chart, id, resolvedTheme]);

if (!svg) {
return (
<pre className="overflow-x-auto rounded-lg border p-4 text-sm">
<code>{chart}</code>
</pre>
);
}

return (
<div
className="my-6 flex justify-center overflow-x-auto [&_svg]:max-w-full"
dangerouslySetInnerHTML={{ __html: svg }}
/>
);
}
2 changes: 2 additions & 0 deletions apps/docs/mdx-components.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import defaultMdxComponents from 'fumadocs-ui/mdx';
import type { MDXComponents } from 'mdx/types';
import { Mermaid } from '@/components/mermaid';

export function getMDXComponents(components?: MDXComponents): MDXComponents {
return {
...defaultMdxComponents,
Mermaid,
...components,
};
}
5 changes: 4 additions & 1 deletion apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@
"fumadocs-mdx": "15.0.12",
"fumadocs-ui": "16.10.5",
"lucide-react": "^1.22.0",
"mermaid": "^11.16.0",
"next": "16.2.9",
"next-themes": "^0.4.6",
"react": "^19.2.7",
"react-dom": "^19.2.7",
"tailwind-merge": "^3.6.0"
"tailwind-merge": "^3.6.0",
"unist-util-visit": "^5.1.0"
},
"devDependencies": {
"@objectstack/spec": "workspace:*",
Expand Down
21 changes: 20 additions & 1 deletion apps/docs/source.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,25 @@ import { defineConfig, defineDocs } from 'fumadocs-mdx/config';
import { metaSchema, pageSchema } from 'fumadocs-core/source/schema';
import { z } from 'zod';
import path from 'node:path';
import { visit } from 'unist-util-visit';

/**
* Rewrite ```mermaid code fences into <Mermaid chart="..."/> elements so
* diagrams render as SVG (components/mermaid.tsx) instead of code blocks.
*/
function remarkMermaid() {
return (tree: any) => {
visit(tree, 'code', (node: any, index: number | undefined, parent: any) => {
if (node.lang !== 'mermaid' || !parent || index === undefined) return;
parent.children[index] = {
type: 'mdxJsxFlowElement',
name: 'Mermaid',
attributes: [{ type: 'mdxJsxAttribute', name: 'chart', value: node.value }],
children: [],
};
});
};
}

export const docs = defineDocs({
dir: path.resolve(process.cwd(), '../../content/docs'),
Expand Down Expand Up @@ -31,6 +50,6 @@ export const blog = defineDocs({

export default defineConfig({
mdxOptions: {
// MDX options
remarkPlugins: (v) => [...v, remarkMermaid],
},
});
22 changes: 7 additions & 15 deletions content/docs/ai/actions-as-tools.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -116,21 +116,13 @@ The queue is exposed via four REST endpoints (`GET`, `GET/:id`, `POST/:id/approv

### End-to-end example

Two runnable demos live in `examples/app-todo/test/`:

- **`ai-hitl.test.ts`** — drives the tool registry directly (no LLM dependency). Asserts the full lifecycle: `variant:'danger'` action registered as a tool → invocation returns `pending_approval` without executing → row persisted → `approvePendingAction(id, actor)` re-runs the handler → row transitions to `executed`. Reject path is also covered.

```bash
pnpm --filter @example/app-todo test:hitl
```

- **`ai-hitl-llm.test.ts`** — same scenario but with a real model behind Vercel AI Gateway. The LLM is given the auto-generated tool description and asked to "delete all completed tasks"; the framework returns `pending_approval` so the model summarises the wait instead of retrying. After we call approve from the test, the deletion completes.

```bash
AI_GATEWAY_API_KEY=vck_... pnpm --filter @example/app-todo test:hitl:llm
```

Gated on `AI_GATEWAY_API_KEY` (or `OPENAI_API_KEY`); exits 0 with a notice if no key is provided, so it can be wired into CI without leaking spend.
The full lifecycle: a `variant:'danger'` action registered as a tool →
invocation returns `pending_approval` without executing → row persisted →
`approvePendingAction(id, actor)` re-runs the handler → row transitions to
`executed` (the reject path mirrors it). The todo example's
`delete_completed` action (`examples/app-todo/src/actions/task.actions.ts`)
is authored exactly for this: `variant: 'danger'` + `confirmText` route it
through the approval queue.

A trimmed view of the integration path:

Expand Down
7 changes: 4 additions & 3 deletions content/docs/ai/agents.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ is in — the user never picks from a roster:

Both agents are part of the **cloud / Enterprise** in-UI AI runtime (cloud ADR-0025).
The **open edition** ships neither — it uses `@objectstack/mcp` (BYO-AI) for data
query and source-mode authoring instead (see the note above).
query and source-mode authoring instead (see the callout in the
[AI Overview](/docs/ai)).

Within the cloud / EE runtime there is no per-turn intent classifier and no agent
dropdown: the surface binds the agent (data console → `ask`, Studio → `build`). A
Expand Down Expand Up @@ -64,7 +65,7 @@ them via the chat endpoint.
<Callout type="info">
Agent tools are **references** to existing Actions, Flows, or queries — you do
not define ad-hoc tool names with inline parameter schemas here. See
[Actions as Tools](#actions-as-tools-explicit-opt-in) for how an Action becomes
[Actions as Tools](/docs/ai/actions-as-tools) for how an Action becomes
LLM-callable.
</Callout>

Expand Down Expand Up @@ -219,7 +220,7 @@ Use the data tools to query records and aggregate metrics.`,
},

// Built-in data tools (query_records / get_record / aggregate_data) are
// available to agents automatically once registered — see "Actions as Tools".
// available to agents automatically once registered — see "Natural Language Queries".
tools: [
{ type: 'flow', name: 'analyze_pipeline', description: 'Analyze sales pipeline health' },
],
Expand Down
11 changes: 6 additions & 5 deletions content/docs/ai/chatbot-integration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@ This guide maps every chatbot configuration knob to its framework endpoint
so you can drop the plugin into an app without reverse-engineering the
contract.

## 1. Enable the AI tier on the framework side
## 1. Enable the AI tier on the backend

The `default` and `full` plugin tier presets both include the `ai`
capability, so `objectstack dev` boots the AI services unless you opt out
with `--preset minimal`. Provide a Vercel AI Gateway model and key via env
vars:
On a cloud / EE dev host (where `@objectstack/service-ai` is available —
see the callout above), the `default` and `full` plugin tier presets both
include the `ai` capability, so `objectstack dev` boots the AI services
unless you opt out with `--preset minimal`. Provide a Vercel AI Gateway
model and key via env vars:

```bash
AI_GATEWAY_API_KEY=vck_*** \
Expand Down
5 changes: 4 additions & 1 deletion content/docs/ai/knowledge-rag.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ kernel.use(new KnowledgeMemoryPlugin());
// kernel.use(new KnowledgeRagflowPlugin({ endpoint, apiKey }));
```

Then register the AI tool so agents can call it:
Then register the AI tool so agents can call it. (This step uses
`@objectstack/service-ai`, i.e. the **cloud / Enterprise** in-UI AI runtime —
see the callout in the [AI Overview](/docs/ai). The Knowledge Protocol itself
and the adapter plugins above ship in the open framework.)

```ts
import { registerKnowledgeTools } from '@objectstack/service-ai';
Expand Down
5 changes: 5 additions & 0 deletions content/docs/ai/natural-language-queries.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ description: How agents query live data through the built-in data tools (query_r

Part of the [AI module](/docs/ai) — how natural-language questions become ObjectQL queries at runtime.

The data tools described here ship with `@objectstack/service-ai`, i.e. the
**cloud / Enterprise** in-UI AI runtime (see the callout in the
[AI Overview](/docs/ai)). On the open edition, point your own AI at the same
objects and queries through `@objectstack/mcp` instead.

Agents query your data through the built-in **data tools** —
`query_records`, `get_record`, and `aggregate_data` — which the LLM calls with
structured arguments. These run as ordinary [ObjectQL](/docs/protocol/objectql) queries over
Expand Down
12 changes: 6 additions & 6 deletions content/docs/api/client-sdk.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ The `@objectstack/client` SDK aims to implement the ObjectStack API protocol spe
| **storage** | ✅ | 2 | File upload & download |
| **i18n** | ✅ | 3 | Internationalization |
| **notifications** | 🟡 | 7 | Legacy notification helpers; receipt/inbox cut-over pending |
| **realtime** | ✅ | 6 | WebSocket subscriptions |
| **realtime** | ✅ | 6 | Connection/subscription/presence calls (server transport is plugin-provided) |
| **ai** | ✅ | 3 | AI services (NLQ, suggest, insights) |

<Callout type="info">
Expand Down Expand Up @@ -553,18 +553,18 @@ For detailed information about the client's protocol implementation:

<Cards>
<Card
href="./api-reference"
title="API Reference"
href="/docs/api/data-api"
title="Data API"
description="Complete REST endpoint documentation"
/>
<Card
href="./kernel-services"
href="/docs/kernel/services-checklist"
title="Kernel Services"
description="Service checklist and plugin development guide"
/>
<Card
href="../references/api/protocol"
href="/docs/references/api/protocol"
title="Protocol Types"
description="All 57 Zod protocol schemas"
description="The Zod protocol schemas"
/>
</Cards>
2 changes: 1 addition & 1 deletion content/docs/api/environment-routing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ const client = new ObjectStackClient({

const env = client.project('env_prod');

await env.data.find('customer', { top: 20 });
await env.data.find('customer', { limit: 20 });
await env.meta.getItems('object');
await env.packages.list();
```
Expand Down
4 changes: 2 additions & 2 deletions content/docs/api/error-catalog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Complete reference for all ObjectStack error codes with causes, fix

# Error Code Catalog

ObjectStack uses a structured error system with **9 error categories** and **41+ standardized error codes**. Every error includes a machine-readable code, HTTP status mapping, and retry guidance.
ObjectStack uses a structured error system with **9 error categories** and **51 standardized error codes**. Every error includes a machine-readable code, HTTP status mapping, and retry guidance.

<Callout type="info">
**Source:** `packages/spec/src/api/errors.zod.ts`
Expand Down Expand Up @@ -391,7 +391,7 @@ import type { EnhancedApiError } from '@objectstack/spec/api';

async function handleApiCall() {
try {
const result = await client.records.create('task', { title: 'New Task' });
const result = await client.data.create('task', { title: 'New Task' });
return result;
} catch (error) {
const apiError = error as EnhancedApiError;
Expand Down
2 changes: 1 addition & 1 deletion content/docs/api/error-handling-client.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ description: Best practices for handling ObjectStack errors in client applicatio
This guide covers best practices for handling ObjectStack API errors in client applications — from parsing error responses to displaying validation errors in forms and implementing retry strategies.

<Callout type="info">
**Error Format:** All ObjectStack API errors follow the `ErrorResponseSchema` structure. See [Wire Format](/docs/api/wire-format#7-error-response-format) for the full JSON shape.
**Error Format:** The patterns in this guide target the spec error envelope (`ErrorResponseSchema`). Note that today's kernel REST data routes emit a flatter `{ error, code }` shape — see the [API Overview](/docs/api#error-handling) for the two wire formats in use and [Wire Format](/docs/api/wire-format#7-error-response-format) for full JSON examples; adapt the parser accordingly.
</Callout>

---
Expand Down
12 changes: 6 additions & 6 deletions content/docs/api/error-handling-server.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ object (imported from `@objectstack/spec/data`) with `object`, `events`
(an array of lifecycle events such as `beforeInsert` / `afterUpdate`), and a
`handler: async (ctx) => { ... }`. Errors thrown from a hook handler abort the
operation and propagate to the API layer. The handler receives a `HookContext`:
the pending record lives on `ctx.input.doc`, the pre-change snapshot on
`ctx.previous`, and the persisted result (after hooks) on `ctx.result`.
the pending record's fields are exposed directly on `ctx.input`, the pre-change
snapshot on `ctx.previous`, and the persisted result (after hooks) on `ctx.result`.

### Before Hooks (Validation / Guard)

Expand All @@ -90,7 +90,7 @@ export const ValidateStatusTransition: Hook = {
object: 'task',
events: ['beforeUpdate'],
handler: async (ctx) => {
const changes = ctx.input.doc as Record<string, any>;
const changes = ctx.input as Record<string, any>;
const previous = ctx.previous ?? {};

// Guard: prevent modifying completed tasks
Expand Down Expand Up @@ -182,7 +182,7 @@ export const ValidateTaskData: Hook = {
object: 'task',
events: ['beforeInsert'],
handler: async (ctx) => {
const data = ctx.input.doc as Record<string, any>;
const data = ctx.input as Record<string, any>;
const schema = z.object({
title: z.string().min(1, 'Title is required').max(200),
priority: z.enum(['low', 'medium', 'high', 'critical']),
Expand Down Expand Up @@ -218,7 +218,7 @@ export const TaskWriteWithLogging: Hook = {
onError: 'abort', // failure aborts the write (default); use 'log' to continue
handler: async (ctx) => {
try {
await runBusinessRules(ctx.input.doc);
await runBusinessRules(ctx.input);
} catch (error) {
const httpStatus = (error as AppError).httpStatus ?? 500;
const logEntry = {
Expand Down Expand Up @@ -496,7 +496,7 @@ export const ValidateBusinessRules: Hook = {
object: 'task',
events: ['beforeInsert'],
handler: async (ctx) => {
const result = safeParsePretty(TaskBusinessRules, ctx.input.doc);
const result = safeParsePretty(TaskBusinessRules, ctx.input);
if (!result.success) {
throw new AppError(
'validation_error',
Expand Down
2 changes: 1 addition & 1 deletion content/docs/api/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ ObjectStack exposes a fully typed REST API. All endpoints use JSON request/respo
| Surface | Status in this repo |
| :--- | :--- |
| **REST** | ✅ Auto-generated from the protocol (`@objectstack/rest`) — CRUD, query, batch, metadata, packages |
| **Realtime** | ✅ WebSocket subscriptions plus SSE streaming (`@objectstack/service-realtime`) |
| **Realtime** | ⚠️ In-process pub/sub service (`@objectstack/service-realtime`, single-instance); the `/realtime/*` REST routes and WebSocket/SSE transport are plugin-provided — none ships in the open framework |
| **MCP** | ✅ Objects and opted-in actions exposed as Model Context Protocol tools ([AI module](/docs/ai)) |
| **GraphQL** | ⚠️ Route is wired but **bring-your-own service**: `/graphql` returns 501 unless an implementation of the `IGraphQLService` contract is registered — none ships in the open framework |
| **OData** | ⚠️ Vocabulary only: REST list endpoints accept OData-style operators (e.g. `$top`), but there is no standalone OData endpoint |
Expand Down
10 changes: 6 additions & 4 deletions content/docs/api/plugin-endpoints.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,16 @@ The following endpoints become available when the corresponding plugin is instal
| GET | `/workflow/:object/config` | Get workflow configuration |
| GET | `/workflow/:object/:recordId/state` | Get record's workflow state |
| POST | `/workflow/:object/:recordId/transition` | Execute state transition |
| POST | `/workflow/:object/:recordId/approve` | Approve workflow step |
| POST | `/workflow/:object/:recordId/reject` | Reject workflow step |

Approve/reject are **not** workflow routes (ADR-0019): approval is a flow node, and decisions are recorded on the approvals runtime via `POST /approvals/requests/:id/approve` and `POST /approvals/requests/:id/reject`.

### Automation (`/automation`) — Plugin Required

| Method | Endpoint | Description |
|:-------|:---------|:------------|
| POST | `/automation/trigger/:name` | Trigger an automation flow by name |
| POST | `/automation/:name/trigger` | Trigger an automation flow by name (legacy alias: `/automation/trigger/:name`) |

The automation dispatcher also exposes flow CRUD (`GET`/`POST /automation`, `GET`/`PUT`/`DELETE /automation/:name`) and run observability/resume routes — see [Durable pause & resume](/docs/automation/flows#durable-pause--resume-adr-0019).

### Views (`/ui`) — Plugin Required

Expand All @@ -60,7 +62,7 @@ The following endpoints become available when the corresponding plugin is instal
| POST | `/realtime/disconnect` | Close connection |
| POST | `/realtime/subscribe` | Subscribe to channel |
| POST | `/realtime/unsubscribe` | Unsubscribe |
| POST | `/realtime/presence` | Set presence |
| PUT | `/realtime/presence/:channel` | Set presence |
| GET | `/realtime/presence/:channel` | Get channel presence |

### Notifications (`/notifications`) — Plugin Required
Expand Down
2 changes: 1 addition & 1 deletion content/docs/automation/approvals.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -130,5 +130,5 @@ Separating *who configures* from *who approves* from *as whom it runs* is the sa
## See also

- Decision records: **ADR-0049** (`runAs`), **ADR-0066** (unified authorization).
- Guide: [Business Logic](/docs/automation/hooks) (Flows, Approval Nodes); [Who can see data / automation / interface](/docs/permissions/access-recipes).
- Guide: [Hooks](/docs/automation/hooks) and [Flows](/docs/automation/flows); [Who can see data / automation / interface](/docs/permissions/access-recipes).
- Reference: [Flow](/docs/references/automation/flow), [Approval](/docs/references/automation/approval).
1 change: 1 addition & 0 deletions content/docs/automation/flows.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ Each node performs a specific action in the flow.
| `delete_record` | Delete records |
| `get_record` | Query records |
| `http` | Make an HTTP API call |
| `notify` | Send an outbound notification via the messaging service |
| `script` | Run a custom script action (dispatched by `config.actionType`) |
| `screen` | Display a user form/screen (durable pause) |
| `wait` | Pause for a timer or named signal (durable pause; timers auto-resume) |
Expand Down
2 changes: 1 addition & 1 deletion content/docs/automation/hook-bodies.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ objectstack.config.ts
## See also

- [Formula Reference](/docs/data-modeling/formulas) — the L1 expression engine.
- [Business Logic](/docs/automation/hooks) — broader patterns for hooks, actions, flows.
- [Hooks](/docs/automation/hooks) and [Flows](/docs/automation/flows) — broader patterns for hooks, actions, flows.
- [Cloud Deployment](/docs/deployment) — how artifacts travel from Studio to objectos.
- `packages/spec/src/data/hook-body.zod.ts` — canonical Zod schema.
- `packages/runtime/src/sandbox/script-runner.ts` — engine decision rationale.
Expand Down
Loading