@zeyos/client 0.1.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.
- package/CHANGELOG.md +31 -0
- package/LICENSE +21 -0
- package/README.md +458 -0
- package/agents/README.md +66 -0
- package/agents/shared/business-app-benchmarks.md +111 -0
- package/agents/shared/zeyos-entity-map.md +142 -0
- package/agents/shared/zeyos-entity-reference.md +570 -0
- package/agents/shared/zeyos-query-patterns.md +89 -0
- package/agents/zeyos-account-intelligence/SKILL.md +34 -0
- package/agents/zeyos-account-intelligence/agents/openai.yaml +4 -0
- package/agents/zeyos-account-intelligence/references/workflows.md +84 -0
- package/agents/zeyos-billing-insights/SKILL.md +41 -0
- package/agents/zeyos-billing-insights/agents/openai.yaml +4 -0
- package/agents/zeyos-billing-insights/references/workflows.md +106 -0
- package/agents/zeyos-campaign-and-outreach/SKILL.md +44 -0
- package/agents/zeyos-campaign-and-outreach/agents/openai.yaml +4 -0
- package/agents/zeyos-campaign-and-outreach/references/workflows.md +100 -0
- package/agents/zeyos-collaboration-and-activity/SKILL.md +37 -0
- package/agents/zeyos-collaboration-and-activity/agents/openai.yaml +4 -0
- package/agents/zeyos-collaboration-and-activity/references/workflows.md +104 -0
- package/agents/zeyos-collections-and-dunning/SKILL.md +46 -0
- package/agents/zeyos-collections-and-dunning/agents/openai.yaml +4 -0
- package/agents/zeyos-collections-and-dunning/references/workflows.md +132 -0
- package/agents/zeyos-commerce-and-inventory/SKILL.md +38 -0
- package/agents/zeyos-commerce-and-inventory/agents/openai.yaml +4 -0
- package/agents/zeyos-commerce-and-inventory/references/workflows.md +101 -0
- package/agents/zeyos-mail-operations/SKILL.md +35 -0
- package/agents/zeyos-mail-operations/agents/openai.yaml +4 -0
- package/agents/zeyos-mail-operations/references/workflows.md +110 -0
- package/agents/zeyos-notes-and-sops/SKILL.md +31 -0
- package/agents/zeyos-notes-and-sops/agents/openai.yaml +4 -0
- package/agents/zeyos-notes-and-sops/references/workflows.md +85 -0
- package/agents/zeyos-platform-and-schema/SKILL.md +37 -0
- package/agents/zeyos-platform-and-schema/agents/openai.yaml +4 -0
- package/agents/zeyos-platform-and-schema/references/workflows.md +97 -0
- package/agents/zeyos-work-management/SKILL.md +45 -0
- package/agents/zeyos-work-management/agents/openai.yaml +4 -0
- package/agents/zeyos-work-management/references/workflows.md +148 -0
- package/docs/01-api-reference/01-data-retrieval.md +601 -0
- package/docs/01-api-reference/02-authentication.md +288 -0
- package/docs/01-api-reference/03-resources.md +270 -0
- package/docs/01-api-reference/04-schema.md +539 -0
- package/docs/01-api-reference/_category_.json +9 -0
- package/docs/02-javascript-client/01-getting-started.md +146 -0
- package/docs/02-javascript-client/02-authentication.md +287 -0
- package/docs/02-javascript-client/03-making-requests.md +572 -0
- package/docs/02-javascript-client/04-practical-guide.md +348 -0
- package/docs/02-javascript-client/_category_.json +9 -0
- package/docs/03-cli/01-getting-started.md +219 -0
- package/docs/03-cli/02-commands.md +407 -0
- package/docs/03-cli/03-configuration.md +220 -0
- package/docs/03-cli/_category_.json +9 -0
- package/docs/04-agent-workflows/00-coding-agents.md +35 -0
- package/docs/04-agent-workflows/01-agent-quickstart.md +147 -0
- package/docs/04-agent-workflows/02-agent-recipes.md +109 -0
- package/docs/04-agent-workflows/03-cli-coverage-and-escalation.md +65 -0
- package/docs/04-agent-workflows/_category_.json +9 -0
- package/docs/04-sample-apps/01-kanban.md +89 -0
- package/docs/04-sample-apps/02-crm.md +81 -0
- package/docs/04-sample-apps/03-dashboard.md +80 -0
- package/docs/04-sample-apps/_category_.json +9 -0
- package/docs/05-tutorials/00-application-developers.md +43 -0
- package/docs/05-tutorials/01-integration-architecture.md +60 -0
- package/docs/05-tutorials/02-build-your-own-zeyos-frontend.md +517 -0
- package/docs/05-tutorials/03-server-side-integrations.md +185 -0
- package/docs/05-tutorials/_category_.json +9 -0
- package/docs/intro.md +197 -0
- package/openapi/api.json +24308 -0
- package/openapi/auth.json +415 -0
- package/openapi/dbref.json +56223 -0
- package/openapi/oauth2.json +781 -0
- package/openapi/sdk.json +949 -0
- package/openapi/views.txt +642 -0
- package/package.json +49 -0
- package/samples/crm/README.md +28 -0
- package/samples/crm/index.html +327 -0
- package/samples/crm/js/api.js +208 -0
- package/samples/crm/js/auth.js +61 -0
- package/samples/crm/js/main.js +545 -0
- package/samples/crm/js/state.js +90 -0
- package/samples/crm/js/ui.js +51 -0
- package/samples/dashboard/README.md +28 -0
- package/samples/dashboard/index.html +280 -0
- package/samples/dashboard/js/api.js +197 -0
- package/samples/dashboard/js/auth.js +59 -0
- package/samples/dashboard/js/main.js +382 -0
- package/samples/dashboard/js/state.js +81 -0
- package/samples/dashboard/js/ui.js +48 -0
- package/samples/kanban/README.md +28 -0
- package/samples/kanban/index.html +263 -0
- package/samples/kanban/js/api.js +152 -0
- package/samples/kanban/js/auth.js +59 -0
- package/samples/kanban/js/constants.js +40 -0
- package/samples/kanban/js/kanban.js +246 -0
- package/samples/kanban/js/main.js +362 -0
- package/samples/kanban/js/modals.js +474 -0
- package/samples/kanban/js/settings.js +82 -0
- package/samples/kanban/js/state.js +118 -0
- package/samples/kanban/js/ui.js +49 -0
- package/scripts/generate-client.mjs +344 -0
- package/src/generated/operations.js +9772 -0
- package/src/generated/schema.js +8982 -0
- package/src/index.js +85 -0
- package/src/runtime/client.js +1208 -0
- package/src/runtime/error.js +29 -0
- package/src/runtime/http.js +174 -0
- package/src/runtime/request-shape.js +35 -0
- package/src/runtime/schema.js +206 -0
- package/src/runtime/suggest.js +74 -0
- package/src/runtime/token-store.js +105 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 2
|
|
3
|
+
sidebar_label: Integration Architecture
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Integration Architecture
|
|
7
|
+
|
|
8
|
+
Choose the integration model before you choose the framework. In ZeyOS projects, the main design decision is usually where authentication happens and where long-lived business operations run.
|
|
9
|
+
|
|
10
|
+
## Comparison
|
|
11
|
+
|
|
12
|
+
| Model | Where code runs | Auth model | Best for | Main constraint |
|
|
13
|
+
|-------|-----------------|------------|----------|-----------------|
|
|
14
|
+
| Browser session mode | Browser | Existing `ZEYOSID` session cookie | Internal tools on the same origin, embedded UIs, operator-facing apps | Requires browser session access and compatible credentialed requests |
|
|
15
|
+
| Browser token mode | Browser | Pre-obtained OAuth tokens | Development, controlled demos, pre-provisioned browser apps | Do not embed a client secret in production browser code |
|
|
16
|
+
| Server token mode | Server, worker, cron, API backend | OAuth tokens stored server-side | Integrations, sync jobs, scheduled tasks, multi-step business logic | Requires token persistence and refresh handling |
|
|
17
|
+
|
|
18
|
+
## Recommended Choices
|
|
19
|
+
|
|
20
|
+
### Use browser session mode when:
|
|
21
|
+
|
|
22
|
+
- the user is already logged into ZeyOS
|
|
23
|
+
- the UI runs on the same origin or an allowed credentialed origin
|
|
24
|
+
- you want the simplest browser auth flow with no token storage in the page
|
|
25
|
+
|
|
26
|
+
### Use browser token mode when:
|
|
27
|
+
|
|
28
|
+
- you already have tokens from a trusted flow
|
|
29
|
+
- you are building a local demo or internal prototype
|
|
30
|
+
- you can avoid embedding client secrets in shipped browser code
|
|
31
|
+
|
|
32
|
+
### Use server token mode when:
|
|
33
|
+
|
|
34
|
+
- the application runs without a live browser session
|
|
35
|
+
- you need retries, scheduling, background work, or webhook handling
|
|
36
|
+
- you want one place to centralize token refresh and request logging
|
|
37
|
+
|
|
38
|
+
Browser token mode is intentionally limited. Use it for pre-obtained access tokens and controlled demos. If you need authorization-code exchange or automatic refresh, move that responsibility to server token mode because the current client helpers require `clientId` and `clientSecret`.
|
|
39
|
+
|
|
40
|
+
## Interface Selection
|
|
41
|
+
|
|
42
|
+
| Need | Interface |
|
|
43
|
+
|------|-----------|
|
|
44
|
+
| JavaScript application code with full API coverage | `@zeyos/client` |
|
|
45
|
+
| Shell-driven operational workflows | `zeyos` CLI |
|
|
46
|
+
| Another language or custom SDK | REST/OpenAPI |
|
|
47
|
+
|
|
48
|
+
## Cross-Cutting Rules
|
|
49
|
+
|
|
50
|
+
- List operations are `POST` requests in ZeyOS, even though they behave like queries.
|
|
51
|
+
- Prefer `filters` in client code for consistent handling of scalar and foreign-key fields.
|
|
52
|
+
- Include `visibility: 0` in normal list queries.
|
|
53
|
+
- Use `body: { ... }` for updates that also pass `ID`.
|
|
54
|
+
- Treat count-enabled responses defensively. Different endpoints or client layers may return either a count wrapper or a list wrapper with count metadata.
|
|
55
|
+
|
|
56
|
+
## Recommended Reading Order
|
|
57
|
+
|
|
58
|
+
1. [Browser UI Playbook](./02-build-your-own-zeyos-frontend.md) for user-facing frontends
|
|
59
|
+
2. [Server-Side Integrations](./03-server-side-integrations.md) for services, workers, and scheduled jobs
|
|
60
|
+
3. [Making Requests](../02-javascript-client/03-making-requests.md) for the full request model
|
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 3
|
|
3
|
+
sidebar_label: Browser UI Playbook
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Browser UI Playbook
|
|
7
|
+
|
|
8
|
+
This playbook walks through building a browser-based interface on top of the ZeyOS REST API using the `@zeyos/client` JavaScript library. By the end, you will have a working single-page application that authenticates, queries data, renders a UI, and writes changes back with patterns you can reuse in any ZeyOS-connected browser app.
|
|
9
|
+
|
|
10
|
+
We will build a minimal **Ticket Dashboard** step by step. No application framework or build step is required -- just ES modules, the ZeyOS client, and a browser. The starter HTML below uses the Tailwind CDN only for concise demo styling; remove it or self-host your CSS for production or stricter security environments.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Prerequisites
|
|
15
|
+
|
|
16
|
+
- A **ZeyOS instance** with some ticket data (e.g. `https://cloud.zeyos.com/demo/`)
|
|
17
|
+
- An **access token** (obtain one via the [CLI](../03-cli/01-getting-started.md) or the ZeyOS OAuth2 flow)
|
|
18
|
+
- A **local HTTP server** to serve static files (e.g. `python3 -m http.server 8080` or `npx serve .`)
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Step 1: Project Setup
|
|
23
|
+
|
|
24
|
+
Create a project folder with this structure:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
my-zeyos-app/
|
|
28
|
+
index.html
|
|
29
|
+
app.js
|
|
30
|
+
zeyos-client/ # symlink or copy of the @zeyos/client src/ directory
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Link the ZeyOS client source so your browser can import it:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Symlink (Linux/macOS)
|
|
37
|
+
ln -s /path/to/zeyos/client/src ./zeyos-client
|
|
38
|
+
|
|
39
|
+
# Or copy
|
|
40
|
+
cp -r /path/to/zeyos/client/src ./zeyos-client
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Create a minimal `index.html`:
|
|
44
|
+
|
|
45
|
+
```html
|
|
46
|
+
<!DOCTYPE html>
|
|
47
|
+
<html lang="en">
|
|
48
|
+
<head>
|
|
49
|
+
<meta charset="UTF-8">
|
|
50
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
51
|
+
<title>My ZeyOS App</title>
|
|
52
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
53
|
+
</head>
|
|
54
|
+
<body class="bg-gray-50 min-h-screen font-sans p-8">
|
|
55
|
+
|
|
56
|
+
<div id="app">
|
|
57
|
+
<h1 class="text-2xl font-bold mb-6">My ZeyOS Ticket Dashboard</h1>
|
|
58
|
+
<div id="content">Loading...</div>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<script type="module" src="./app.js"></script>
|
|
62
|
+
</body>
|
|
63
|
+
</html>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Step 2: Initialize the Client
|
|
69
|
+
|
|
70
|
+
Create `app.js` and set up the ZeyOS client. You have two authentication options:
|
|
71
|
+
|
|
72
|
+
### Option A: Token Mode
|
|
73
|
+
|
|
74
|
+
Use this when you already have an OAuth access token. This is most useful for development and controlled demos:
|
|
75
|
+
|
|
76
|
+
```js
|
|
77
|
+
import { createZeyosClient, MemoryTokenStore, normalizeListResult } from './zeyos-client/index.js';
|
|
78
|
+
|
|
79
|
+
const client = createZeyosClient({
|
|
80
|
+
platform: 'https://cloud.zeyos.com/demo/',
|
|
81
|
+
auth: {
|
|
82
|
+
mode: 'oauth',
|
|
83
|
+
oauth: {
|
|
84
|
+
tokenStore: new MemoryTokenStore({
|
|
85
|
+
accessToken: 'YOUR_ACCESS_TOKEN',
|
|
86
|
+
}),
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Use session mode or a backend token broker for long-lived browser apps. Do not embed `clientSecret` in shipped browser code just to enable OAuth refresh.
|
|
93
|
+
|
|
94
|
+
### Option B: Session Mode
|
|
95
|
+
|
|
96
|
+
Use this when you are already logged into ZeyOS in the same browser:
|
|
97
|
+
|
|
98
|
+
```js
|
|
99
|
+
import { createZeyosClient, normalizeListResult } from './zeyos-client/index.js';
|
|
100
|
+
|
|
101
|
+
const client = createZeyosClient({
|
|
102
|
+
platform: 'https://cloud.zeyos.com/demo/',
|
|
103
|
+
auth: {
|
|
104
|
+
mode: 'session',
|
|
105
|
+
session: { enabled: true, credentials: 'include' },
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
:::tip
|
|
111
|
+
For a real application, never hardcode tokens in source files. Persist them only if you control the environment, or prefer session mode or a backend-assisted token flow. See the [Kanban sample](../04-sample-apps/01-kanban.md) for a reusable config pattern.
|
|
112
|
+
:::
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Step 3: Fetch and Display Data
|
|
117
|
+
|
|
118
|
+
Add a function to load tickets and render them as a table:
|
|
119
|
+
|
|
120
|
+
```js
|
|
121
|
+
async function loadTickets() {
|
|
122
|
+
const result = await client.api.listTickets({
|
|
123
|
+
fields: ['ID', 'ticketnum', 'name', 'status', 'priority', 'duedate', 'assigneduser'],
|
|
124
|
+
filters: { visibility: 0 },
|
|
125
|
+
sort: ['-lastmodified'],
|
|
126
|
+
limit: 50,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const { data: tickets } = normalizeListResult(result);
|
|
130
|
+
return tickets;
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Key things to note:
|
|
135
|
+
|
|
136
|
+
- **List operations are POST requests.** The client handles this; you just pass an object.
|
|
137
|
+
- **Always include `visibility: 0`** to exclude archived/deleted records.
|
|
138
|
+
- **Use `filters` (plural)** for best compatibility across all field types. This handles both simple equality filters and GIN-indexed foreign key fields.
|
|
139
|
+
- **Always specify `fields`** to keep payloads small. Without it, every field on every record is returned.
|
|
140
|
+
- **Normalise the response.** Use `normalizeListResult()` so list calls share one array/object-wrapper handling path. Use `normalizeCountResult()` for count-only requests.
|
|
141
|
+
|
|
142
|
+
Now render it:
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
const STATUS_LABELS = {
|
|
146
|
+
0: 'Not Started', 1: 'Awaiting Acceptance', 2: 'Accepted',
|
|
147
|
+
3: 'Rejected', 4: 'Active', 5: 'Inactive',
|
|
148
|
+
6: 'Feedback Required', 7: 'Testing', 8: 'Cancelled',
|
|
149
|
+
9: 'Completed', 10: 'Failed', 11: 'Booked',
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const PRIORITY_LABELS = {
|
|
153
|
+
0: 'Lowest', 1: 'Low', 2: 'Medium', 3: 'High', 4: 'Highest',
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
function formatDate(unix) {
|
|
157
|
+
if (!unix) return '';
|
|
158
|
+
return new Date(unix * 1000).toLocaleDateString();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function renderTickets(tickets) {
|
|
162
|
+
if (tickets.length === 0) {
|
|
163
|
+
return '<p class="text-gray-500">No tickets found.</p>';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const rows = tickets.map(t => `
|
|
167
|
+
<tr class="border-t hover:bg-gray-50">
|
|
168
|
+
<td class="py-2 px-3 font-mono text-sm text-gray-400">${t.ticketnum ?? t.ID}</td>
|
|
169
|
+
<td class="py-2 px-3 font-medium">${esc(t.name ?? '')}</td>
|
|
170
|
+
<td class="py-2 px-3 text-sm">${STATUS_LABELS[t.status] ?? t.status}</td>
|
|
171
|
+
<td class="py-2 px-3 text-sm">${PRIORITY_LABELS[t.priority] ?? t.priority}</td>
|
|
172
|
+
<td class="py-2 px-3 text-sm">${formatDate(t.duedate)}</td>
|
|
173
|
+
<td class="py-2 px-3 text-sm">${esc(t.assigneduser ?? '')}</td>
|
|
174
|
+
</tr>
|
|
175
|
+
`).join('');
|
|
176
|
+
|
|
177
|
+
return `
|
|
178
|
+
<table class="w-full bg-white rounded-lg shadow text-left">
|
|
179
|
+
<thead>
|
|
180
|
+
<tr class="text-xs uppercase text-gray-500 border-b">
|
|
181
|
+
<th class="py-2 px-3">#</th>
|
|
182
|
+
<th class="py-2 px-3">Name</th>
|
|
183
|
+
<th class="py-2 px-3">Status</th>
|
|
184
|
+
<th class="py-2 px-3">Priority</th>
|
|
185
|
+
<th class="py-2 px-3">Due</th>
|
|
186
|
+
<th class="py-2 px-3">Assigned</th>
|
|
187
|
+
</tr>
|
|
188
|
+
</thead>
|
|
189
|
+
<tbody>${rows}</tbody>
|
|
190
|
+
</table>`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function esc(str) {
|
|
194
|
+
return String(str).replace(/&/g, '&').replace(/</g, '<')
|
|
195
|
+
.replace(/>/g, '>').replace(/"/g, '"');
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Wire it up:
|
|
200
|
+
|
|
201
|
+
```js
|
|
202
|
+
async function boot() {
|
|
203
|
+
try {
|
|
204
|
+
const tickets = await loadTickets();
|
|
205
|
+
document.getElementById('content').innerHTML = renderTickets(tickets);
|
|
206
|
+
} catch (err) {
|
|
207
|
+
document.getElementById('content').innerHTML =
|
|
208
|
+
`<p class="text-red-500">Error: ${esc(err.message)}</p>`;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
boot();
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Start your server and open the page -- you should see a table of tickets.
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Step 4: Filter by Project
|
|
220
|
+
|
|
221
|
+
Add a project selector above the table. First, load the available projects:
|
|
222
|
+
|
|
223
|
+
```js
|
|
224
|
+
async function loadProjects() {
|
|
225
|
+
try {
|
|
226
|
+
const result = await client.api.listProjects({
|
|
227
|
+
fields: ['ID', 'name'],
|
|
228
|
+
filters: { visibility: 0 },
|
|
229
|
+
sort: ['+name'],
|
|
230
|
+
limit: 500,
|
|
231
|
+
});
|
|
232
|
+
return normalizeListResult(result).data;
|
|
233
|
+
} catch {
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Then build a `<select>` and reload tickets when the selection changes:
|
|
240
|
+
|
|
241
|
+
```js
|
|
242
|
+
function renderProjectFilter(projects, onSelect) {
|
|
243
|
+
const options = projects.map(p =>
|
|
244
|
+
`<option value="${p.ID}">${esc(p.name)}</option>`
|
|
245
|
+
).join('');
|
|
246
|
+
|
|
247
|
+
return `
|
|
248
|
+
<select id="project-filter"
|
|
249
|
+
class="border rounded-lg px-3 py-2 text-sm mb-4">
|
|
250
|
+
<option value="">All Projects</option>
|
|
251
|
+
${options}
|
|
252
|
+
</select>`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function boot() {
|
|
256
|
+
const projects = await loadProjects();
|
|
257
|
+
const app = document.getElementById('content');
|
|
258
|
+
|
|
259
|
+
app.innerHTML = renderProjectFilter(projects) + '<div id="tickets">Loading...</div>';
|
|
260
|
+
|
|
261
|
+
const loadAndRender = async (projectId) => {
|
|
262
|
+
const filters = { visibility: 0 };
|
|
263
|
+
if (projectId) filters.project = Number(projectId);
|
|
264
|
+
|
|
265
|
+
const result = await client.api.listTickets({
|
|
266
|
+
fields: ['ID', 'ticketnum', 'name', 'status', 'priority', 'duedate', 'assigneduser'],
|
|
267
|
+
filters,
|
|
268
|
+
sort: ['-lastmodified'],
|
|
269
|
+
limit: 50,
|
|
270
|
+
});
|
|
271
|
+
const { data: tickets } = normalizeListResult(result);
|
|
272
|
+
document.getElementById('tickets').innerHTML = renderTickets(tickets);
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
document.getElementById('project-filter').addEventListener('change', e => {
|
|
276
|
+
loadAndRender(e.target.value);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
await loadAndRender('');
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Step 5: Create a New Ticket
|
|
286
|
+
|
|
287
|
+
Add a simple form and use the `createTicket` API. Note: **create operations use PUT**, not POST.
|
|
288
|
+
|
|
289
|
+
```js
|
|
290
|
+
function renderCreateForm() {
|
|
291
|
+
return `
|
|
292
|
+
<form id="create-form" class="bg-white rounded-lg shadow p-4 mb-6 flex gap-3 items-end">
|
|
293
|
+
<div class="flex-1">
|
|
294
|
+
<label class="block text-xs font-medium text-gray-600 mb-1">Ticket Name</label>
|
|
295
|
+
<input id="f-name" type="text" placeholder="What needs to be done?"
|
|
296
|
+
class="w-full border rounded-lg px-3 py-2 text-sm" required>
|
|
297
|
+
</div>
|
|
298
|
+
<div>
|
|
299
|
+
<label class="block text-xs font-medium text-gray-600 mb-1">Priority</label>
|
|
300
|
+
<select id="f-priority" class="border rounded-lg px-3 py-2 text-sm">
|
|
301
|
+
<option value="0">Lowest</option>
|
|
302
|
+
<option value="1">Low</option>
|
|
303
|
+
<option value="2" selected>Medium</option>
|
|
304
|
+
<option value="3">High</option>
|
|
305
|
+
<option value="4">Highest</option>
|
|
306
|
+
</select>
|
|
307
|
+
</div>
|
|
308
|
+
<button type="submit"
|
|
309
|
+
class="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-700">
|
|
310
|
+
Create
|
|
311
|
+
</button>
|
|
312
|
+
</form>`;
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Handle form submission:
|
|
317
|
+
|
|
318
|
+
```js
|
|
319
|
+
document.getElementById('create-form').addEventListener('submit', async e => {
|
|
320
|
+
e.preventDefault();
|
|
321
|
+
const name = document.getElementById('f-name').value.trim();
|
|
322
|
+
if (!name) return;
|
|
323
|
+
|
|
324
|
+
try {
|
|
325
|
+
await client.api.createTicket({
|
|
326
|
+
name,
|
|
327
|
+
priority: Number(document.getElementById('f-priority').value),
|
|
328
|
+
status: 0,
|
|
329
|
+
visibility: 0,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
document.getElementById('f-name').value = '';
|
|
333
|
+
await loadAndRender(''); // Refresh the list
|
|
334
|
+
} catch (err) {
|
|
335
|
+
alert(`Create failed: ${err.message}`);
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
For generated create and update operations, the flat input style works for normal record fields.
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## Step 6: Update a Ticket
|
|
345
|
+
|
|
346
|
+
Update a record by passing the record ID and the changed fields:
|
|
347
|
+
|
|
348
|
+
```js
|
|
349
|
+
async function updateTicketStatus(ticketId, newStatus) {
|
|
350
|
+
const updated = await client.api.updateTicket({
|
|
351
|
+
ID: ticketId,
|
|
352
|
+
status: newStatus,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// The response contains the full updated record -- use it to
|
|
356
|
+
// confirm the change was applied
|
|
357
|
+
return updated;
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
If you prefer explicit separation, `body` and `data` are also supported:
|
|
362
|
+
|
|
363
|
+
```js
|
|
364
|
+
await client.api.updateTicket({ ID: ticketId, body: { status: newStatus } });
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## Step 7: Delete a Ticket
|
|
370
|
+
|
|
371
|
+
Deletion is straightforward:
|
|
372
|
+
|
|
373
|
+
```js
|
|
374
|
+
async function removeTicket(ticketId) {
|
|
375
|
+
await client.api.deleteTicket({ ID: ticketId });
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Always confirm with the user before deleting -- there is no undo.
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## Step 8: Fetch Related Data
|
|
384
|
+
|
|
385
|
+
Most ZeyOS entities are related to each other. For example, tickets can have tasks. To load tasks for a specific ticket:
|
|
386
|
+
|
|
387
|
+
```js
|
|
388
|
+
async function loadTasks(ticketId) {
|
|
389
|
+
const result = await client.api.listTasks({
|
|
390
|
+
fields: ['ID', 'tasknum', 'name', 'status', 'duedate', 'assigneduser'],
|
|
391
|
+
filters: { ticket: ticketId, visibility: 0 },
|
|
392
|
+
sort: ['+name'],
|
|
393
|
+
limit: 200,
|
|
394
|
+
});
|
|
395
|
+
return normalizeListResult(result).data;
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
You can also use **dot-notation joins** to pull fields from related records in a single request:
|
|
400
|
+
|
|
401
|
+
```js
|
|
402
|
+
const tickets = await client.api.listTickets({
|
|
403
|
+
fields: {
|
|
404
|
+
Id: 'ID',
|
|
405
|
+
Name: 'name',
|
|
406
|
+
ProjectName: 'project.name',
|
|
407
|
+
AssignedTo: 'assigneduser.name',
|
|
408
|
+
ContactCity: 'contact.city',
|
|
409
|
+
},
|
|
410
|
+
filters: { visibility: 0 },
|
|
411
|
+
limit: 50,
|
|
412
|
+
});
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
This returns flattened objects like `{ Id: 42, Name: '...', ProjectName: 'Acme', AssignedTo: 'Jane', ContactCity: 'Berlin' }` -- no extra API calls needed.
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
## Step 9: Handle Errors Gracefully
|
|
420
|
+
|
|
421
|
+
Every API error throws a `ZeyosApiError` with rich context:
|
|
422
|
+
|
|
423
|
+
```js
|
|
424
|
+
import { ZeyosApiError } from './zeyos-client/index.js';
|
|
425
|
+
|
|
426
|
+
try {
|
|
427
|
+
await client.api.getTicket({ ID: 999999 });
|
|
428
|
+
} catch (err) {
|
|
429
|
+
if (err instanceof ZeyosApiError) {
|
|
430
|
+
switch (err.status) {
|
|
431
|
+
case 401:
|
|
432
|
+
// Token expired and auto-refresh failed
|
|
433
|
+
showLoginScreen();
|
|
434
|
+
break;
|
|
435
|
+
case 403:
|
|
436
|
+
showMessage('You do not have permission to view this record.');
|
|
437
|
+
break;
|
|
438
|
+
case 404:
|
|
439
|
+
showMessage('Record not found.');
|
|
440
|
+
break;
|
|
441
|
+
default:
|
|
442
|
+
showMessage(`Error ${err.status}: ${err.body ?? err.message}`);
|
|
443
|
+
}
|
|
444
|
+
} else {
|
|
445
|
+
// Network error, timeout, etc.
|
|
446
|
+
showMessage('Network error. Please check your connection.');
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
## Step 10: Working with Dates
|
|
454
|
+
|
|
455
|
+
All ZeyOS date fields are **Unix timestamps in seconds** (not milliseconds). This catches most JavaScript developers off guard since `Date.now()` returns milliseconds.
|
|
456
|
+
|
|
457
|
+
```js
|
|
458
|
+
// Reading: multiply by 1000
|
|
459
|
+
const jsDate = new Date(ticket.duedate * 1000);
|
|
460
|
+
|
|
461
|
+
// Writing: divide by 1000
|
|
462
|
+
const duedate = Math.floor(new Date('2026-06-15').getTime() / 1000);
|
|
463
|
+
await client.api.updateTicket({ ID: id, body: { duedate } });
|
|
464
|
+
|
|
465
|
+
// Checking overdue
|
|
466
|
+
const isOverdue = ticket.duedate * 1000 < Date.now();
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
---
|
|
470
|
+
|
|
471
|
+
## Summary: Quick Reference Card
|
|
472
|
+
|
|
473
|
+
| Task | Code |
|
|
474
|
+
|------|------|
|
|
475
|
+
| List records | `client.api.listTickets({ fields: [...], filters: {...}, sort: [...], limit: N })` |
|
|
476
|
+
| Get one record | `client.api.getTicket({ ID: 42 })` |
|
|
477
|
+
| Create | `client.api.createTicket({ name: '...', status: 0, visibility: 0 })` |
|
|
478
|
+
| Update | `client.api.updateTicket({ ID: 42, body: { status: 4 } })` |
|
|
479
|
+
| Delete | `client.api.deleteTicket({ ID: 42 })` |
|
|
480
|
+
| Related data | `client.api.listTasks({ filters: { ticket: 42 } })` |
|
|
481
|
+
| Dot-notation join | `fields: { City: 'contact.city', Agent: 'assigneduser.name' }` |
|
|
482
|
+
| Date read | `new Date(record.duedate * 1000)` |
|
|
483
|
+
| Date write | `Math.floor(date.getTime() / 1000)` |
|
|
484
|
+
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
## Where to Go Next
|
|
488
|
+
|
|
489
|
+
- **[Practical Guide](../02-javascript-client/04-practical-guide.md)** -- deeper coverage of `filter` vs `filters`, token persistence, and optimistic UI patterns
|
|
490
|
+
- **[Making Requests](../02-javascript-client/03-making-requests.md)** -- full reference for field selection, sorting, pagination, extended data, and error handling
|
|
491
|
+
- **[Data Retrieval](../01-api-reference/01-data-retrieval.md)** -- the complete REST query language with advanced filter operators, full-text search, and composite expressions
|
|
492
|
+
- **[Kanban Sample](../04-sample-apps/01-kanban.md)** -- a reusable sample browser app with drag-and-drop, modals, and session detection
|
|
493
|
+
- **[Authentication](../02-javascript-client/02-authentication.md)** -- OAuth 2.0 flows, session mode, legacy auth, and token management
|
|
494
|
+
|
|
495
|
+
---
|
|
496
|
+
|
|
497
|
+
## Available Resources
|
|
498
|
+
|
|
499
|
+
The ZeyOS API provides access to over 50 resource types. Here are the ones most commonly used when building custom frontends:
|
|
500
|
+
|
|
501
|
+
| Resource | Operations | Use case |
|
|
502
|
+
|----------|-----------|----------|
|
|
503
|
+
| `Tickets` | list, get, create, update, delete | Issue tracking, support, project work |
|
|
504
|
+
| `Tasks` | list, get, create, update, delete | Task management within tickets/projects |
|
|
505
|
+
| `Accounts` | list, get, create, update, delete | CRM contacts and companies |
|
|
506
|
+
| `Projects` | list, get, create, update, delete | Project organisation |
|
|
507
|
+
| `Contacts` | list, get, create, update, delete | Contact details (addresses, phone, email) |
|
|
508
|
+
| `Appointments` | list, get, create, update, delete | Calendar and scheduling |
|
|
509
|
+
| `Transactions` | list, get, create, update, delete | Invoices, quotes, orders |
|
|
510
|
+
| `Items` | list, get, create, update, delete | Products and services |
|
|
511
|
+
| `Documents` | list, get, create, update, delete | File management |
|
|
512
|
+
| `Notes` | list, get, create, update, delete | Notes attached to any entity |
|
|
513
|
+
| `Messages` | list, get, create, update, delete | Email and messaging |
|
|
514
|
+
| `Opportunities` | list, get, create, update, delete | Sales pipeline |
|
|
515
|
+
| `Users` | list, get | Team members and permissions |
|
|
516
|
+
|
|
517
|
+
Every resource follows the same API patterns shown in this guide. Once you know how to work with tickets, you can work with any resource.
|