@electric-ax/agents 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/entrypoint.js +474 -737
  2. package/dist/index.cjs +470 -733
  3. package/dist/index.d.cts +68 -35
  4. package/dist/index.d.ts +69 -36
  5. package/dist/index.js +489 -751
  6. package/docs/entities/agents/horton.md +12 -12
  7. package/docs/entities/agents/worker.md +18 -18
  8. package/docs/entities/patterns/blackboard.md +6 -6
  9. package/docs/entities/patterns/dispatcher.md +1 -1
  10. package/docs/entities/patterns/manager-worker.md +1 -1
  11. package/docs/entities/patterns/map-reduce.md +1 -1
  12. package/docs/entities/patterns/pipeline.md +1 -1
  13. package/docs/entities/patterns/reactive-observers.md +2 -2
  14. package/docs/examples/playground.md +42 -26
  15. package/docs/index.md +25 -23
  16. package/docs/quickstart.md +12 -12
  17. package/docs/reference/agent-config.md +20 -12
  18. package/docs/reference/agent-tool.md +1 -1
  19. package/docs/reference/built-in-collections.md +21 -21
  20. package/docs/reference/cli.md +39 -30
  21. package/docs/reference/entity-definition.md +9 -9
  22. package/docs/reference/entity-handle.md +2 -2
  23. package/docs/reference/entity-registry.md +1 -1
  24. package/docs/reference/handler-context.md +34 -18
  25. package/docs/reference/mcp-registry.md +189 -0
  26. package/docs/reference/mcp-server-config.md +226 -0
  27. package/docs/reference/runtime-handler.md +25 -23
  28. package/docs/reference/shared-state-handle.md +7 -7
  29. package/docs/reference/state-collection-proxy.md +1 -1
  30. package/docs/reference/wake-event.md +23 -23
  31. package/docs/usage/app-setup.md +24 -23
  32. package/docs/usage/clients-and-react.md +40 -36
  33. package/docs/usage/configuring-the-agent.md +25 -19
  34. package/docs/usage/context-composition.md +12 -12
  35. package/docs/usage/defining-entities.md +36 -36
  36. package/docs/usage/defining-tools.md +45 -45
  37. package/docs/usage/embedded-builtins.md +54 -43
  38. package/docs/usage/managing-state.md +12 -12
  39. package/docs/usage/mcp-servers.md +354 -0
  40. package/docs/usage/overview.md +50 -45
  41. package/docs/usage/programmatic-runtime-client.md +51 -48
  42. package/docs/usage/shared-state.md +32 -32
  43. package/docs/usage/spawning-and-coordinating.md +9 -9
  44. package/docs/usage/testing.md +14 -14
  45. package/docs/usage/waking-entities.md +13 -13
  46. package/docs/usage/writing-handlers.md +52 -26
  47. package/package.json +9 -4
  48. package/scripts/sync-docs.mjs +42 -0
  49. package/docs/examples/mega-draw.md +0 -106
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Defining tools
3
- titleTemplate: '... - Electric Agents'
3
+ titleTemplate: "... - Electric Agents"
4
4
  description: >-
5
5
  Create stateless, stateful, and handler-scoped tools for the LLM agent loop.
6
6
  outline: [2, 3]
@@ -33,7 +33,7 @@ The return type:
33
33
 
34
34
  ```ts
35
35
  interface AgentToolResult<T = any> {
36
- content: Array<{ type: 'text'; text: string }>
36
+ content: Array<{ type: "text"; text: string }>
37
37
  details: T
38
38
  }
39
39
  ```
@@ -43,11 +43,11 @@ interface AgentToolResult<T = any> {
43
43
  Defined using [TypeBox](https://github.com/sinclairzx81/typebox) (`@sinclair/typebox`). The schema is used for LLM function calling and argument validation.
44
44
 
45
45
  ```ts
46
- import { Type } from '@sinclair/typebox'
46
+ import { Type } from "@sinclair/typebox"
47
47
 
48
48
  parameters: Type.Object({
49
- expression: Type.String({ description: 'Math expression to evaluate' }),
50
- precision: Type.Optional(Type.Number({ description: 'Decimal places' })),
49
+ expression: Type.String({ description: "Math expression to evaluate" }),
50
+ precision: Type.Optional(Type.Number({ description: "Decimal places" })),
51
51
  })
52
52
  ```
53
53
 
@@ -56,21 +56,21 @@ parameters: Type.Object({
56
56
  Pure functions with no side effects beyond what they compute. Define directly as an `AgentTool` object.
57
57
 
58
58
  ```ts
59
- import { Type } from '@sinclair/typebox'
60
- import type { AgentTool } from '@electric-ax/agents-runtime'
59
+ import { Type } from "@sinclair/typebox"
60
+ import type { AgentTool } from "@electric-ax/agents-runtime"
61
61
 
62
62
  const calculatorTool: AgentTool = {
63
- name: 'calculator',
64
- label: 'Calculator',
65
- description: 'Evaluate mathematical expressions.',
63
+ name: "calculator",
64
+ label: "Calculator",
65
+ description: "Evaluate mathematical expressions.",
66
66
  parameters: Type.Object({
67
- expression: Type.String({ description: 'The expression to evaluate' }),
67
+ expression: Type.String({ description: "The expression to evaluate" }),
68
68
  }),
69
69
  execute: async (_toolCallId, params) => {
70
70
  const { expression } = params as { expression: string }
71
71
  const result = evaluate(expression)
72
72
  return {
73
- content: [{ type: 'text', text: String(result) }],
73
+ content: [{ type: "text", text: String(result) }],
74
74
  details: {},
75
75
  }
76
76
  },
@@ -82,20 +82,20 @@ const calculatorTool: AgentTool = {
82
82
  Use a factory function that receives the `HandlerContext`. The state persists across wakes -- it is backed by the entity's durable stream. Reads go through `ctx.db.collections` and writes go through `ctx.db.actions`.
83
83
 
84
84
  ```ts
85
- import { Type } from '@sinclair/typebox'
86
- import type { AgentTool, HandlerContext } from '@electric-ax/agents-runtime'
85
+ import { Type } from "@sinclair/typebox"
86
+ import type { AgentTool, HandlerContext } from "@electric-ax/agents-runtime"
87
87
 
88
88
  function createMemoryStoreTool(ctx: HandlerContext): AgentTool {
89
89
  return {
90
- name: 'memory_store',
91
- label: 'Memory Store',
92
- description: 'Persistent key-value store.',
90
+ name: "memory_store",
91
+ label: "Memory Store",
92
+ description: "Persistent key-value store.",
93
93
  parameters: Type.Object({
94
94
  operation: Type.Union([
95
- Type.Literal('get'),
96
- Type.Literal('set'),
97
- Type.Literal('delete'),
98
- Type.Literal('list'),
95
+ Type.Literal("get"),
96
+ Type.Literal("set"),
97
+ Type.Literal("delete"),
98
+ Type.Literal("list"),
99
99
  ]),
100
100
  key: Type.Optional(Type.String()),
101
101
  value: Type.Optional(Type.String()),
@@ -106,7 +106,7 @@ function createMemoryStoreTool(ctx: HandlerContext): AgentTool {
106
106
  key?: string
107
107
  value?: string
108
108
  }
109
- if (operation === 'set') {
109
+ if (operation === "set") {
110
110
  const existing = ctx.db.collections.kv?.get(key!)
111
111
  if (existing) {
112
112
  ctx.db.actions.kv_update({
@@ -119,27 +119,27 @@ function createMemoryStoreTool(ctx: HandlerContext): AgentTool {
119
119
  ctx.db.actions.kv_insert({ row: { key: key!, value: value! } })
120
120
  }
121
121
  return {
122
- content: [{ type: 'text', text: `Stored "${key}"` }],
122
+ content: [{ type: "text", text: `Stored "${key}"` }],
123
123
  details: {},
124
124
  }
125
125
  }
126
- if (operation === 'get') {
126
+ if (operation === "get") {
127
127
  const entry = ctx.db.collections.kv?.get(key!)
128
128
  const text = entry ? entry.value : `No value found for "${key}"`
129
- return { content: [{ type: 'text', text }], details: {} }
129
+ return { content: [{ type: "text", text }], details: {} }
130
130
  }
131
- if (operation === 'delete') {
131
+ if (operation === "delete") {
132
132
  ctx.db.actions.kv_delete({ key: key! })
133
133
  return {
134
- content: [{ type: 'text', text: `Deleted "${key}"` }],
134
+ content: [{ type: "text", text: `Deleted "${key}"` }],
135
135
  details: {},
136
136
  }
137
137
  }
138
138
  // list
139
139
  const entries = ctx.db.collections.kv?.toArray ?? []
140
- const text = entries.map((e) => `${e.key}: ${e.value}`).join('\n')
140
+ const text = entries.map((e) => `${e.key}: ${e.value}`).join("\n")
141
141
  return {
142
- content: [{ type: 'text', text: text || '(empty)' }],
142
+ content: [{ type: "text", text: text || "(empty)" }],
143
143
  details: {},
144
144
  }
145
145
  },
@@ -162,18 +162,18 @@ The entity state API:
162
162
  Use a factory that receives the `HandlerContext`. These tools can spawn entities, observe streams, send messages, and use any other `ctx` primitive.
163
163
 
164
164
  ```ts
165
- import { Type } from '@sinclair/typebox'
166
- import type { AgentTool, HandlerContext } from '@electric-ax/agents-runtime'
165
+ import { Type } from "@sinclair/typebox"
166
+ import type { AgentTool, HandlerContext } from "@electric-ax/agents-runtime"
167
167
 
168
168
  function createDispatchTool(ctx: HandlerContext): AgentTool {
169
169
  return {
170
- name: 'dispatch',
171
- label: 'Dispatch',
172
- description: 'Spawn a child agent and wait for its response.',
170
+ name: "dispatch",
171
+ label: "Dispatch",
172
+ description: "Spawn a child agent and wait for its response.",
173
173
  parameters: Type.Object({
174
- type: Type.String({ description: 'Entity type to spawn' }),
175
- systemPrompt: Type.String({ description: 'System prompt for the child' }),
176
- task: Type.String({ description: 'Task to send to the child' }),
174
+ type: Type.String({ description: "Entity type to spawn" }),
175
+ systemPrompt: Type.String({ description: "System prompt for the child" }),
176
+ task: Type.String({ description: "Task to send to the child" }),
177
177
  }),
178
178
  execute: async (_, params) => {
179
179
  const { type, systemPrompt, task } = params as {
@@ -187,12 +187,12 @@ function createDispatchTool(ctx: HandlerContext): AgentTool {
187
187
  { systemPrompt },
188
188
  {
189
189
  initialMessage: task,
190
- wake: 'runFinished',
190
+ wake: "runFinished",
191
191
  }
192
192
  )
193
- const text = (await child.text()).join('\n\n')
193
+ const text = (await child.text()).join("\n\n")
194
194
  return {
195
- content: [{ type: 'text', text }],
195
+ content: [{ type: "text", text }],
196
196
  details: {},
197
197
  }
198
198
  },
@@ -207,18 +207,18 @@ function createDispatchTool(ctx: HandlerContext): AgentTool {
207
207
  Tools are constructed in the handler and passed to `useAgent`. Include `ctx.electricTools` when your runtime host provides runtime-level tools that the LLM should be able to call:
208
208
 
209
209
  ```ts
210
- registry.define('assistant', {
211
- description: 'An assistant with memory and delegation',
210
+ registry.define("assistant", {
211
+ description: "An assistant with memory and delegation",
212
212
  state: {
213
- kv: { primaryKey: 'key' },
213
+ kv: { primaryKey: "key" },
214
214
  },
215
215
  async handler(ctx) {
216
216
  const memoryTool = createMemoryStoreTool(ctx)
217
217
  const dispatchTool = createDispatchTool(ctx)
218
218
 
219
219
  ctx.useAgent({
220
- systemPrompt: 'You are a helpful assistant with persistent memory.',
221
- model: 'claude-sonnet-4-5-20250929',
220
+ systemPrompt: "You are a helpful assistant with persistent memory.",
221
+ model: "claude-sonnet-4-5-20250929",
222
222
  tools: [...ctx.electricTools, memoryTool, dispatchTool, calculatorTool],
223
223
  })
224
224
  await ctx.agent.run()
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Embedded built-ins
3
- titleTemplate: '... - Electric Agents'
3
+ titleTemplate: "... - Electric Agents"
4
4
  description: >-
5
5
  Embed the built-in Horton and worker runtime in your own process using
6
6
  @electric-ax/agents, BuiltinAgentsServer, or the entrypoint helpers.
@@ -16,10 +16,10 @@ The CLI commands `electric agents start-builtin` and `electric agents quickstart
16
16
  `BuiltinAgentsServer` starts an HTTP webhook server, registers `horton` and `worker`, and forwards Electric Agents webhook wakes to the built-in handler.
17
17
 
18
18
  ```ts
19
- import { BuiltinAgentsServer } from '@electric-ax/agents'
19
+ import { BuiltinAgentsServer } from "@electric-ax/agents"
20
20
 
21
21
  const server = new BuiltinAgentsServer({
22
- agentServerUrl: 'http://localhost:4437',
22
+ agentServerUrl: "http://localhost:4437",
23
23
  port: 4448,
24
24
  workingDirectory: process.cwd(),
25
25
  })
@@ -36,9 +36,9 @@ await server.stop()
36
36
  ### Options
37
37
 
38
38
  ```ts
39
- import type { RuntimeRouterConfig } from '@electric-ax/agents-runtime'
39
+ import type { RuntimeRouterConfig } from "@electric-ax/agents-runtime"
40
40
 
41
- type CreateElectricTools = RuntimeRouterConfig['createElectricTools']
41
+ type CreateElectricTools = RuntimeRouterConfig["createElectricTools"]
42
42
 
43
43
  interface BuiltinAgentsServerOptions {
44
44
  agentServerUrl: string
@@ -49,19 +49,30 @@ interface BuiltinAgentsServerOptions {
49
49
  mockStreamFn?: StreamFn
50
50
  webhookPath?: string
51
51
  createElectricTools?: CreateElectricTools
52
+ // MCP integration
53
+ extraMcpServers?: ReadonlyArray<McpServerConfig>
54
+ loadProjectMcpConfig?: boolean
55
+ mcpOAuthRedirectBase?: string
56
+ openAuthorizeUrl?: (url: string, server: string) => void
57
+ onConfigError?: (error: unknown) => void
52
58
  }
53
59
  ```
54
60
 
55
- | Field | Description |
56
- | --------------------- | ---------------------------------------------------------------------------- |
57
- | `agentServerUrl` | Electric Agents coordinator server URL. |
58
- | `baseUrl` | Public base URL used when registering the webhook. Defaults to local URL. |
59
- | `port` | Local webhook server port. |
60
- | `host` | Bind host. Defaults to `127.0.0.1`. |
61
- | `workingDirectory` | Directory used by Horton and worker file tools. Defaults to `process.cwd()`. |
62
- | `mockStreamFn` | Optional test stream function. Lets you run without `ANTHROPIC_API_KEY`. |
63
- | `webhookPath` | Webhook path. Defaults to `/_electric/builtin-agent-handler`. |
64
- | `createElectricTools` | Optional factory for extra tools injected into built-in agent handlers. |
61
+ | Field | Description |
62
+ | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
63
+ | `agentServerUrl` | Electric Agents coordinator server URL. |
64
+ | `baseUrl` | Public base URL used when registering the webhook. Defaults to local URL. |
65
+ | `port` | Local webhook server port. |
66
+ | `host` | Bind host. Defaults to `127.0.0.1`. |
67
+ | `workingDirectory` | Directory used by Horton and worker file tools. Defaults to `process.cwd()`. |
68
+ | `mockStreamFn` | Optional test stream function. Lets you run without `ANTHROPIC_API_KEY`. |
69
+ | `webhookPath` | Webhook path. Defaults to `/_electric/builtin-agent-handler`. |
70
+ | `createElectricTools` | Optional factory for extra tools injected into built-in agent handlers. |
71
+ | `extraMcpServers` | MCP servers contributed by the embedder. On name conflict with `mcp.json`, `mcp.json` wins. `authorizationCode` servers are auto-wired with `keychainPersistence`. |
72
+ | `loadProjectMcpConfig` | Load `<workingDirectory>/mcp.json` (and watch it). Off by default — stdio MCP servers can spawn local commands, so the embedder must opt in. The Electron desktop and `electric-ax` CLI opt in. |
73
+ | `mcpOAuthRedirectBase` | Base for OAuth redirect URIs (full URI is `<base>/oauth/callback/<server-name>`). MUST be stable across restarts so DCR client info stays valid; required when listening on `port: 0`. The runtime never listens at this URI — the embedder intercepts the redirect. |
74
+ | `openAuthorizeUrl` | Hook invoked when an `authorizationCode` MCP server first needs user consent. Receives the SDK-generated authorize URL. The desktop opens it in a sandboxed `BrowserWindow`; headless embedders can read the URL from the `authenticating` envelope of `addServer` and surface it themselves. |
75
+ | `onConfigError` | Invoked when applying an MCP config (initial boot or watcher reload) fails. Errors are always logged; this hook is for surfacing them programmatically. |
65
76
 
66
77
  Without `mockStreamFn`, `ANTHROPIC_API_KEY` must be present before the built-in handler starts.
67
78
 
@@ -73,16 +84,16 @@ Use `createBuiltinAgentHandler()` when you already have an HTTP server and only
73
84
  import {
74
85
  createBuiltinAgentHandler,
75
86
  registerBuiltinAgentTypes,
76
- } from '@electric-ax/agents'
87
+ } from "@electric-ax/agents"
77
88
 
78
89
  const bootstrap = await createBuiltinAgentHandler({
79
- agentServerUrl: 'http://localhost:4437',
80
- serveEndpoint: 'https://example.com/_electric/builtin-agent-handler',
90
+ agentServerUrl: "http://localhost:4437",
91
+ serveEndpoint: "https://example.com/_electric/builtin-agent-handler",
81
92
  workingDirectory: process.cwd(),
82
93
  })
83
94
 
84
95
  if (!bootstrap) {
85
- throw new Error('ANTHROPIC_API_KEY is required for built-in agents')
96
+ throw new Error("ANTHROPIC_API_KEY is required for built-in agents")
86
97
  }
87
98
 
88
99
  await registerBuiltinAgentTypes(bootstrap)
@@ -108,27 +119,27 @@ interface AgentHandlerResult {
108
119
  Both `BuiltinAgentsServer` and `createBuiltinAgentHandler()` accept `createElectricTools`. The factory receives the same context shape as `RuntimeRouterConfig.createElectricTools` and can add host-specific tools to Horton.
109
120
 
110
121
  ```ts
111
- import { Type } from '@sinclair/typebox'
122
+ import { Type } from "@sinclair/typebox"
112
123
 
113
124
  const server = new BuiltinAgentsServer({
114
- agentServerUrl: 'http://localhost:4437',
125
+ agentServerUrl: "http://localhost:4437",
115
126
  port: 4448,
116
127
  createElectricTools: ({ entityUrl, upsertCronSchedule }) => [
117
128
  {
118
- name: 'schedule_daily_summary',
119
- label: 'Schedule daily summary',
120
- description: 'Schedule a daily summary wake for this entity.',
129
+ name: "schedule_daily_summary",
130
+ label: "Schedule daily summary",
131
+ description: "Schedule a daily summary wake for this entity.",
121
132
  parameters: Type.Object({
122
133
  hour: Type.Number(),
123
134
  }),
124
135
  execute: async (_id, params) => {
125
136
  const { hour } = params as { hour: number }
126
137
  await upsertCronSchedule({
127
- id: 'daily-summary',
138
+ id: "daily-summary",
128
139
  expression: `0 ${hour} * * *`,
129
140
  payload: `Run daily summary for ${entityUrl}`,
130
141
  })
131
- return { content: [{ type: 'text', text: 'Scheduled.' }], details: {} }
142
+ return { content: [{ type: "text", text: "Scheduled." }], details: {} }
132
143
  },
133
144
  },
134
145
  ],
@@ -143,7 +154,7 @@ const server = new BuiltinAgentsServer({
143
154
  import {
144
155
  resolveBuiltinAgentsEntrypointOptions,
145
156
  runBuiltinAgentsEntrypoint,
146
- } from '@electric-ax/agents'
157
+ } from "@electric-ax/agents"
147
158
 
148
159
  const options = resolveBuiltinAgentsEntrypointOptions(process.env)
149
160
  const { server, url } = await runBuiltinAgentsEntrypoint()
@@ -154,27 +165,27 @@ await server.stop()
154
165
 
155
166
  Environment variables:
156
167
 
157
- | Variable | Description |
158
- | ----------------------------------- | ------------------------------------------------ |
159
- | `ELECTRIC_AGENTS_SERVER_URL` | Required coordinator server URL. |
160
- | `ELECTRIC_AGENTS_BUILTIN_BASE_URL` | Public webhook base URL for the built-in server. |
161
- | `ELECTRIC_AGENTS_BUILTIN_HOST` | Bind host. |
162
- | `ELECTRIC_AGENTS_BUILTIN_PORT` | Built-in server port. Defaults to `4448`. |
163
- | `ELECTRIC_AGENTS_WORKING_DIRECTORY` | Working directory for file tools. |
168
+ | Variable | Description |
169
+ | -------------------------------- | ----------------------------------------------------- |
170
+ | `ELECTRIC_AGENTS_SERVER_URL` | Required coordinator server URL. |
171
+ | `ELECTRIC_AGENTS_BUILTIN_BASE_URL` | Public webhook base URL for the built-in server. |
172
+ | `ELECTRIC_AGENTS_BUILTIN_HOST` | Bind host. |
173
+ | `ELECTRIC_AGENTS_BUILTIN_PORT` | Built-in server port. Defaults to `4448`. |
174
+ | `ELECTRIC_AGENTS_WORKING_DIRECTORY` | Working directory for file tools. |
164
175
 
165
176
  ## Built-in Agent APIs
166
177
 
167
178
  The built-in agent exports are also available if you want to compose your own runtime:
168
179
 
169
- | Export | Purpose |
170
- | --------------------------- | ----------------------------------------------------- |
171
- | `registerHorton()` | Register the `horton` type on an `EntityRegistry`. |
172
- | `registerWorker()` | Register the `worker` type on an `EntityRegistry`. |
173
- | `HORTON_MODEL` | Default model id used by Horton and worker. |
180
+ | Export | Purpose |
181
+ | ------------------------- | --------------------------------------------------- |
182
+ | `registerHorton()` | Register the `horton` type on an `EntityRegistry`. |
183
+ | `registerWorker()` | Register the `worker` type on an `EntityRegistry`. |
184
+ | `HORTON_MODEL` | Default model id used by Horton and worker. |
174
185
  | `buildHortonSystemPrompt()` | Build Horton's system prompt for a working directory. |
175
- | `createHortonTools()` | Create Horton's base shell/file/search/worker tools. |
176
- | `createSpawnWorkerTool()` | Create the `spawn_worker` tool for another agent. |
177
- | `WORKER_TOOL_NAMES` | Valid primitive tool names for workers. |
178
- | `createHortonDocsSupport()` | Create Horton's docs knowledge-base support. |
186
+ | `createHortonTools()` | Create Horton's base shell/file/search/worker tools. |
187
+ | `createSpawnWorkerTool()` | Create the `spawn_worker` tool for another agent. |
188
+ | `WORKER_TOOL_NAMES` | Valid primitive tool names for workers. |
189
+ | `createHortonDocsSupport()` | Create Horton's docs knowledge-base support. |
179
190
 
180
191
  For the behavior of `horton` and `worker`, see [Horton](../entities/agents/horton) and [Worker](../entities/agents/worker).
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Managing state
3
- titleTemplate: '... - Electric Agents'
3
+ titleTemplate: "... - Electric Agents"
4
4
  description: >-
5
5
  Declare and manage persistent entity state using custom collections with typed CRUD operations.
6
6
  outline: [2, 3]
@@ -15,16 +15,16 @@ Entities can declare custom persistent collections. The convenience API is `ctx.
15
15
  Define collections in the `state` field of the entity definition:
16
16
 
17
17
  ```ts
18
- registry.define('my-entity', {
18
+ registry.define("my-entity", {
19
19
  state: {
20
- status: { primaryKey: 'key' },
20
+ status: { primaryKey: "key" },
21
21
  items: {
22
22
  schema: z.object({
23
23
  key: z.string(),
24
24
  name: z.string(),
25
25
  count: z.number(),
26
26
  }),
27
- primaryKey: 'key',
27
+ primaryKey: "key",
28
28
  },
29
29
  },
30
30
  async handler(ctx) {
@@ -59,33 +59,33 @@ Write helpers return a Transaction. Reads query the underlying TanStack DB colle
59
59
 
60
60
  ```ts
61
61
  // Convenience API
62
- ctx.state.items.insert({ key: 'item-1', name: 'Widget', count: 5 })
63
- const itemViaState = ctx.state.items.get('item-1')
62
+ ctx.state.items.insert({ key: "item-1", name: "Widget", count: 5 })
63
+ const itemViaState = ctx.state.items.get("item-1")
64
64
  const allViaState = ctx.state.items.toArray
65
- ctx.state.items.update('item-1', (draft) => {
65
+ ctx.state.items.update("item-1", (draft) => {
66
66
  draft.count += 1
67
67
  })
68
- ctx.state.items.delete('item-1')
68
+ ctx.state.items.delete("item-1")
69
69
 
70
70
  // Lower-level insert
71
71
  ctx.db.actions.items_insert({
72
- row: { key: 'item-1', name: 'Widget', count: 5 },
72
+ row: { key: "item-1", name: "Widget", count: 5 },
73
73
  })
74
74
 
75
75
  // Lower-level read
76
- const item = ctx.db.collections.items?.get('item-1')
76
+ const item = ctx.db.collections.items?.get("item-1")
77
77
  const all = ctx.db.collections.items?.toArray
78
78
 
79
79
  // Lower-level update (Immer-style draft)
80
80
  ctx.db.actions.items_update({
81
- key: 'item-1',
81
+ key: "item-1",
82
82
  updater: (draft) => {
83
83
  draft.count += 1
84
84
  },
85
85
  })
86
86
 
87
87
  // Lower-level delete
88
- ctx.db.actions.items_delete({ key: 'item-1' })
88
+ ctx.db.actions.items_delete({ key: "item-1" })
89
89
  ```
90
90
 
91
91
  ## Built-in collections