@ketrics/ketrics-cli 0.5.0 → 0.6.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/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +6 -0
- package/dist/src/cli.js.map +1 -1
- package/dist/src/commands/create.d.ts +1 -0
- package/dist/src/commands/create.d.ts.map +1 -1
- package/dist/src/commands/create.js +44 -13
- package/dist/src/commands/create.js.map +1 -1
- package/dist/src/services/local-template-service.d.ts +52 -0
- package/dist/src/services/local-template-service.d.ts.map +1 -0
- package/dist/src/services/local-template-service.js +216 -0
- package/dist/src/services/local-template-service.js.map +1 -0
- package/dist/src/services/remote-template-service.d.ts +41 -0
- package/dist/src/services/remote-template-service.d.ts.map +1 -0
- package/dist/src/services/remote-template-service.js +232 -0
- package/dist/src/services/remote-template-service.js.map +1 -0
- package/dist/src/services/template-cache-service.d.ts +44 -0
- package/dist/src/services/template-cache-service.d.ts.map +1 -0
- package/dist/src/services/template-cache-service.js +193 -0
- package/dist/src/services/template-cache-service.js.map +1 -0
- package/dist/src/services/template-service.d.ts +25 -31
- package/dist/src/services/template-service.d.ts.map +1 -1
- package/dist/src/services/template-service.js +136 -132
- package/dist/src/services/template-service.js.map +1 -1
- package/dist/src/types/index.d.ts +46 -0
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/types/index.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/package.json +5 -1
- package/templates/HelloWorld/.claude/skills/ketrics-app/BACKEND_REFERENCE.md +693 -0
- package/templates/HelloWorld/.claude/skills/ketrics-app/CONFIG_AND_DEPLOY.md +278 -0
- package/templates/HelloWorld/.claude/skills/ketrics-app/FRONTEND_REFERENCE.md +325 -0
- package/templates/HelloWorld/.claude/skills/ketrics-app/SKILL.md +348 -0
- package/templates/HelloWorld/.env.example +20 -0
- package/templates/HelloWorld/.github/workflows/deploy.yml +51 -0
- package/templates/HelloWorld/backend/package.json +1 -1
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# Configuration and Deployment
|
|
2
|
+
|
|
3
|
+
## ketrics.config.json
|
|
4
|
+
|
|
5
|
+
The config file at the project root declares the application to the Ketrics platform.
|
|
6
|
+
|
|
7
|
+
### Full schema
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"name": "my-ketrics-app",
|
|
12
|
+
"version": "1.0.0",
|
|
13
|
+
"description": "Description of the application",
|
|
14
|
+
"runtime": "nodejs18",
|
|
15
|
+
"actions": [
|
|
16
|
+
"handlerName1",
|
|
17
|
+
"handlerName2"
|
|
18
|
+
],
|
|
19
|
+
"entry": "dist/index.js",
|
|
20
|
+
"include": ["dist/**/*"],
|
|
21
|
+
"exclude": ["node_modules", "*.test.js", "*.spec.js"],
|
|
22
|
+
"resources": {
|
|
23
|
+
"documentdb": [
|
|
24
|
+
{
|
|
25
|
+
"code": "resource-code",
|
|
26
|
+
"description": "What this DocumentDB stores"
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"volume": [
|
|
30
|
+
{
|
|
31
|
+
"code": "volume-code",
|
|
32
|
+
"description": "What files this volume stores"
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Field descriptions
|
|
40
|
+
|
|
41
|
+
| Field | Required | Description |
|
|
42
|
+
|-------|----------|-------------|
|
|
43
|
+
| `name` | Yes | Application identifier (lowercase, hyphens) |
|
|
44
|
+
| `version` | Yes | Semver version string |
|
|
45
|
+
| `description` | No | Human-readable description |
|
|
46
|
+
| `runtime` | Yes | Runtime environment. Use `"nodejs18"` |
|
|
47
|
+
| `actions` | Yes | Array of handler function names. MUST match exports in `backend/src/index.ts` |
|
|
48
|
+
| `entry` | Yes | Path to the built backend bundle (relative to `backend/`) |
|
|
49
|
+
| `include` | No | Glob patterns for files to include in deployment |
|
|
50
|
+
| `exclude` | No | Glob patterns for files to exclude |
|
|
51
|
+
| `resources` | No | Declares DocumentDB collections and Volumes the app uses |
|
|
52
|
+
|
|
53
|
+
### Actions sync rule
|
|
54
|
+
|
|
55
|
+
The `actions` array MUST list every exported handler name from `backend/src/index.ts`. If a handler is exported but not in the actions array, it won't be callable from the frontend. If a name is in actions but not exported, deployment will reference a non-existent function.
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
backend/src/index.ts exports <--> ketrics.config.json actions
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Always keep these in sync when adding or removing handlers.
|
|
62
|
+
|
|
63
|
+
### Resource declarations
|
|
64
|
+
|
|
65
|
+
**DocumentDB**: NoSQL document stores. The `code` is used in `ketrics.DocumentDb.connect(code)`.
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
"documentdb": [
|
|
69
|
+
{ "code": "app-data", "description": "Main application data" },
|
|
70
|
+
{ "code": "audit-log", "description": "Audit trail entries" }
|
|
71
|
+
]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Volume**: File storage buckets. The `code` is used in `ketrics.Volume.connect(code)`.
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
"volume": [
|
|
78
|
+
{ "code": "exports", "description": "Excel and PDF exports" },
|
|
79
|
+
{ "code": "uploads", "description": "User-uploaded files" }
|
|
80
|
+
]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Backend build
|
|
84
|
+
|
|
85
|
+
Uses esbuild to bundle the single `backend/src/index.ts` into `backend/dist/index.js`.
|
|
86
|
+
|
|
87
|
+
### `backend/package.json`
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"name": "my-ketrics-app-backend",
|
|
92
|
+
"version": "1.0.0",
|
|
93
|
+
"main": "dist/index.js",
|
|
94
|
+
"scripts": {
|
|
95
|
+
"build": "esbuild src/index.ts --bundle --platform=node --target=es2020 --outfile=dist/index.js",
|
|
96
|
+
"clean": "rm -rf dist"
|
|
97
|
+
},
|
|
98
|
+
"devDependencies": {
|
|
99
|
+
"@ketrics/sdk-backend": "0.11.0",
|
|
100
|
+
"@types/node": ">=24.0.0",
|
|
101
|
+
"esbuild": "^0.27.2",
|
|
102
|
+
"typescript": "^5.3.3"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### `backend/tsconfig.json`
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"compilerOptions": {
|
|
112
|
+
"target": "ES2020",
|
|
113
|
+
"module": "ESNext",
|
|
114
|
+
"moduleResolution": "bundler",
|
|
115
|
+
"strict": true,
|
|
116
|
+
"esModuleInterop": true,
|
|
117
|
+
"skipLibCheck": true,
|
|
118
|
+
"outDir": "dist",
|
|
119
|
+
"rootDir": "src",
|
|
120
|
+
"types": ["@ketrics/sdk-backend"]
|
|
121
|
+
},
|
|
122
|
+
"include": ["src/**/*"]
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Build command
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
cd backend && npm run build
|
|
130
|
+
# Outputs: backend/dist/index.js
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Frontend build
|
|
134
|
+
|
|
135
|
+
Uses tsc for type-checking and Vite for bundling.
|
|
136
|
+
|
|
137
|
+
### Build command
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
cd frontend && npm run build
|
|
141
|
+
# Outputs: frontend/dist/ (static files)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Development
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
cd frontend && npm run dev
|
|
148
|
+
# Starts Vite dev server with mock handlers (no backend needed)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Environment variables
|
|
152
|
+
|
|
153
|
+
Environment variables are configured in the Ketrics dashboard (not in code). Access them at runtime via `ketrics.environment["VAR_NAME"]` in backend handlers.
|
|
154
|
+
|
|
155
|
+
Common variables to document for your app:
|
|
156
|
+
|
|
157
|
+
| Variable | Type | Description |
|
|
158
|
+
|----------|------|-------------|
|
|
159
|
+
| `DB_CONNECTIONS` | JSON string | Array of `{code, name}` for database connections |
|
|
160
|
+
| `DOCDB_*` | String | DocumentDB resource code overrides |
|
|
161
|
+
| `EXPORTS_VOLUME` | String | Volume code for file exports |
|
|
162
|
+
| Custom variables | String | App-specific configuration |
|
|
163
|
+
|
|
164
|
+
## GitHub Actions deployment
|
|
165
|
+
|
|
166
|
+
### `.github/workflows/deploy.yml`
|
|
167
|
+
|
|
168
|
+
```yaml
|
|
169
|
+
name: Deploy to Ketrics
|
|
170
|
+
|
|
171
|
+
on:
|
|
172
|
+
push:
|
|
173
|
+
branches: [master]
|
|
174
|
+
|
|
175
|
+
env:
|
|
176
|
+
KETRICS_API_URL: https://api.ketrics.io/api/v1
|
|
177
|
+
KETRICS_RUNTIME_URL: https://runtime.ketrics.io
|
|
178
|
+
KETRICS_TENANT_ID: <your-tenant-id>
|
|
179
|
+
KETRICS_APPLICATION_ID: <your-application-id>
|
|
180
|
+
|
|
181
|
+
jobs:
|
|
182
|
+
deploy:
|
|
183
|
+
runs-on: ubuntu-latest
|
|
184
|
+
environment: prod
|
|
185
|
+
steps:
|
|
186
|
+
- uses: actions/checkout@v4
|
|
187
|
+
|
|
188
|
+
- uses: actions/setup-node@v4
|
|
189
|
+
with:
|
|
190
|
+
node-version: 18
|
|
191
|
+
|
|
192
|
+
# Install Ketrics CLI
|
|
193
|
+
- run: npm install -g @ketrics/ketrics-cli
|
|
194
|
+
|
|
195
|
+
# Generate .env file (only KETRICS_TOKEN is a secret)
|
|
196
|
+
- name: Generate .env file
|
|
197
|
+
run: |
|
|
198
|
+
cat <<EOF > .env
|
|
199
|
+
KETRICS_API_URL=$KETRICS_API_URL
|
|
200
|
+
KETRICS_RUNTIME_URL=$KETRICS_RUNTIME_URL
|
|
201
|
+
KETRICS_TENANT_ID=$KETRICS_TENANT_ID
|
|
202
|
+
KETRICS_APPLICATION_ID=$KETRICS_APPLICATION_ID
|
|
203
|
+
KETRICS_TOKEN=${{ secrets.KETRICS_TOKEN }}
|
|
204
|
+
EOF
|
|
205
|
+
|
|
206
|
+
# Backend: install + build
|
|
207
|
+
- run: npm ci
|
|
208
|
+
working-directory: backend
|
|
209
|
+
- run: npm run build
|
|
210
|
+
working-directory: backend
|
|
211
|
+
|
|
212
|
+
# Frontend: install + build
|
|
213
|
+
- run: npm ci
|
|
214
|
+
working-directory: frontend
|
|
215
|
+
- run: npm run build
|
|
216
|
+
working-directory: frontend
|
|
217
|
+
|
|
218
|
+
# Deploy
|
|
219
|
+
- run: ketrics deploy --env .env
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Required secrets
|
|
223
|
+
|
|
224
|
+
| Secret | Where to set | Description |
|
|
225
|
+
|--------|-------------|-------------|
|
|
226
|
+
| `KETRICS_TOKEN` | GitHub repo > Settings > Secrets | API token from Ketrics dashboard |
|
|
227
|
+
|
|
228
|
+
### Required environment variables (in workflow)
|
|
229
|
+
|
|
230
|
+
| Variable | Description |
|
|
231
|
+
|----------|-------------|
|
|
232
|
+
| `KETRICS_API_URL` | Ketrics API endpoint |
|
|
233
|
+
| `KETRICS_RUNTIME_URL` | Ketrics runtime endpoint |
|
|
234
|
+
| `KETRICS_TENANT_ID` | Your tenant UUID |
|
|
235
|
+
| `KETRICS_APPLICATION_ID` | Your application UUID |
|
|
236
|
+
|
|
237
|
+
## Manual deployment
|
|
238
|
+
|
|
239
|
+
For local deployment without CI/CD:
|
|
240
|
+
|
|
241
|
+
1. Create a `.env` file at the project root:
|
|
242
|
+
|
|
243
|
+
```
|
|
244
|
+
KETRICS_API_URL=https://api.ketrics.io/api/v1
|
|
245
|
+
KETRICS_RUNTIME_URL=https://runtime.ketrics.io
|
|
246
|
+
KETRICS_TENANT_ID=<your-tenant-id>
|
|
247
|
+
KETRICS_APPLICATION_ID=<your-application-id>
|
|
248
|
+
KETRICS_TOKEN=<your-token>
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
2. Build and deploy:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
cd backend && npm run build && cd ..
|
|
255
|
+
cd frontend && npm run build && cd ..
|
|
256
|
+
ketrics deploy --env .env
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## SDK versions
|
|
260
|
+
|
|
261
|
+
| Package | Version | Purpose |
|
|
262
|
+
|---------|---------|---------|
|
|
263
|
+
| `@ketrics/sdk-backend` | 0.11.0 | Backend global types (devDependency) |
|
|
264
|
+
| `@ketrics/sdk-frontend` | ^0.1.1 | Frontend auth manager (dependency) |
|
|
265
|
+
| `@ketrics/ketrics-cli` | latest | CLI for deployment |
|
|
266
|
+
|
|
267
|
+
## Deployment checklist
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
- [ ] ketrics.config.json actions match all backend exports
|
|
271
|
+
- [ ] ketrics.config.json resources list all DocumentDB and Volume codes used
|
|
272
|
+
- [ ] Backend builds successfully: cd backend && npm run build
|
|
273
|
+
- [ ] Frontend builds successfully: cd frontend && npm run build
|
|
274
|
+
- [ ] .env file has all required variables (or GitHub secrets configured)
|
|
275
|
+
- [ ] Environment variables configured in Ketrics dashboard
|
|
276
|
+
- [ ] KETRICS_TOKEN secret set in GitHub repo settings
|
|
277
|
+
- [ ] GitHub Actions workflow file in .github/workflows/deploy.yml
|
|
278
|
+
```
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# Frontend Reference
|
|
2
|
+
|
|
3
|
+
Patterns for the React frontend of a Ketrics tenant application. The frontend is built with Vite + React 18 + TypeScript and embedded as an iframe in the Ketrics platform.
|
|
4
|
+
|
|
5
|
+
## Service layer
|
|
6
|
+
|
|
7
|
+
The service layer provides a single `callFunction<T>(fnName, payload)` interface between frontend and backend. In dev mode, calls route to mock handlers. In production, calls go to the Ketrics Runtime API with JWT auth.
|
|
8
|
+
|
|
9
|
+
### `frontend/src/services/index.ts`
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { createAuthManager } from "@ketrics/sdk-frontend";
|
|
13
|
+
|
|
14
|
+
const auth = createAuthManager();
|
|
15
|
+
auth.initAutoRefresh({ refreshBuffer: 60, onTokenUpdated: () => {} });
|
|
16
|
+
|
|
17
|
+
export async function callFunction<T = unknown>(
|
|
18
|
+
fnName: string,
|
|
19
|
+
payload?: unknown
|
|
20
|
+
): Promise<{ result: T }> {
|
|
21
|
+
if (import.meta.env.DEV) {
|
|
22
|
+
const { handlers } = await import("../mocks/handlers");
|
|
23
|
+
const handler = handlers[fnName];
|
|
24
|
+
if (!handler) {
|
|
25
|
+
throw new Error(`[Mock] No handler for "${fnName}"`);
|
|
26
|
+
}
|
|
27
|
+
await new Promise((r) => setTimeout(r, 200)); // Simulate network delay
|
|
28
|
+
const result = await handler(payload);
|
|
29
|
+
console.log(`[Mock] ${fnName}`, { payload, result });
|
|
30
|
+
return { result: result as T };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const runtimeApiUrl = auth.getRuntimeApiUrl();
|
|
34
|
+
const tenantId = auth.getTenantId();
|
|
35
|
+
const applicationId = auth.getApplicationId();
|
|
36
|
+
const accessToken = auth.getAccessToken();
|
|
37
|
+
|
|
38
|
+
if (!runtimeApiUrl || !tenantId || !applicationId || !accessToken) {
|
|
39
|
+
throw new Error("Missing authentication context");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const response = await fetch(
|
|
43
|
+
`${runtimeApiUrl}/tenants/${tenantId}/applications/${applicationId}/functions/${fnName}`,
|
|
44
|
+
{
|
|
45
|
+
method: "POST",
|
|
46
|
+
headers: {
|
|
47
|
+
Authorization: `Bearer ${accessToken}`,
|
|
48
|
+
"Content-Type": "application/json",
|
|
49
|
+
},
|
|
50
|
+
body: payload !== undefined ? JSON.stringify({ payload }) : undefined,
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
if (response.status === 401) auth.requestRefresh();
|
|
56
|
+
const errorBody = await response.text();
|
|
57
|
+
throw new Error(`Error API (${response.status}): ${errorBody}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return response.json() as Promise<{ result: T }>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export { auth };
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Key points
|
|
67
|
+
|
|
68
|
+
- `import.meta.env.DEV` — Vite's built-in env flag for dev mode detection
|
|
69
|
+
- `createAuthManager()` — Returns an auth manager that handles JWT tokens in the iframe context
|
|
70
|
+
- `auth.initAutoRefresh()` — Keeps the token fresh; `refreshBuffer: 60` means refresh 60s before expiry
|
|
71
|
+
- `auth.requestRefresh()` — Called on 401 to force a token refresh
|
|
72
|
+
- Handler names must match exactly between `callFunction("handlerName", ...)` and `backend/src/index.ts` exports
|
|
73
|
+
|
|
74
|
+
## Calling backend handlers
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { callFunction } from "../services";
|
|
78
|
+
|
|
79
|
+
// Simple call (no payload)
|
|
80
|
+
const { result } = await callFunction<{ connections: Connection[] }>("getConnections");
|
|
81
|
+
|
|
82
|
+
// Call with payload
|
|
83
|
+
const { result } = await callFunction<{ item: Item }>("createItem", {
|
|
84
|
+
name: "My Item",
|
|
85
|
+
description: "Item description",
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Call with typed response
|
|
89
|
+
interface QueryResult {
|
|
90
|
+
columns: string[];
|
|
91
|
+
rows: Record<string, unknown>[];
|
|
92
|
+
rowCount: number;
|
|
93
|
+
executionTime: number;
|
|
94
|
+
}
|
|
95
|
+
const { result } = await callFunction<QueryResult>("executeQuery", {
|
|
96
|
+
connectionCode: "main-db",
|
|
97
|
+
sql: "SELECT * FROM users",
|
|
98
|
+
params: [],
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Mock handlers
|
|
103
|
+
|
|
104
|
+
Create mock handlers for local development. These let you run the frontend without a backend.
|
|
105
|
+
|
|
106
|
+
### `frontend/src/mocks/handlers.ts`
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
type MockHandler = (payload?: unknown) => unknown | Promise<unknown>;
|
|
110
|
+
|
|
111
|
+
// In-memory stores
|
|
112
|
+
let itemsStore: Item[] = [
|
|
113
|
+
{ id: "1", name: "Sample Item", createdBy: "mock-user", createdAt: "2024-01-01T00:00:00Z", updatedAt: "2024-01-01T00:00:00Z" },
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
const handlers: Record<string, MockHandler> = {
|
|
117
|
+
listItems: () => ({
|
|
118
|
+
items: itemsStore,
|
|
119
|
+
}),
|
|
120
|
+
|
|
121
|
+
getItem: (payload: unknown) => {
|
|
122
|
+
const { id } = payload as { id: string };
|
|
123
|
+
const item = itemsStore.find(i => i.id === id);
|
|
124
|
+
if (!item) throw new Error("Not found");
|
|
125
|
+
return { item };
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
createItem: (payload: unknown) => {
|
|
129
|
+
const { name, description } = payload as { name: string; description?: string };
|
|
130
|
+
const now = new Date().toISOString();
|
|
131
|
+
const newItem: Item = {
|
|
132
|
+
id: crypto.randomUUID(),
|
|
133
|
+
name,
|
|
134
|
+
description: description || "",
|
|
135
|
+
createdBy: "mock-user",
|
|
136
|
+
createdAt: now,
|
|
137
|
+
updatedAt: now,
|
|
138
|
+
};
|
|
139
|
+
itemsStore.push(newItem);
|
|
140
|
+
return { item: newItem };
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
updateItem: (payload: unknown) => {
|
|
144
|
+
const { id, name, description } = payload as { id: string; name: string; description?: string };
|
|
145
|
+
const idx = itemsStore.findIndex(i => i.id === id);
|
|
146
|
+
if (idx === -1) throw new Error("Not found");
|
|
147
|
+
itemsStore[idx] = { ...itemsStore[idx], name, description: description || "", updatedAt: new Date().toISOString() };
|
|
148
|
+
return { item: itemsStore[idx] };
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
deleteItem: (payload: unknown) => {
|
|
152
|
+
const { id } = payload as { id: string };
|
|
153
|
+
itemsStore = itemsStore.filter(i => i.id !== id);
|
|
154
|
+
return { success: true };
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
getPermissions: () => ({
|
|
158
|
+
isEditor: true,
|
|
159
|
+
userId: "mock-user",
|
|
160
|
+
userName: "Mock User",
|
|
161
|
+
}),
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export { handlers };
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Mock handler rules
|
|
168
|
+
|
|
169
|
+
- Handler names MUST match the backend exports exactly
|
|
170
|
+
- Use in-memory arrays/objects for state (persists only during the dev session)
|
|
171
|
+
- Cast `payload` to the expected type (mock handlers receive `unknown`)
|
|
172
|
+
- Simulate realistic response shapes matching what the backend returns
|
|
173
|
+
|
|
174
|
+
## TypeScript types
|
|
175
|
+
|
|
176
|
+
Define shared interfaces in `frontend/src/types.ts`. These mirror the backend types and define the shape of API responses.
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
// Common pattern: one full interface + one summary for list views
|
|
180
|
+
export interface Item {
|
|
181
|
+
id: string;
|
|
182
|
+
name: string;
|
|
183
|
+
description: string;
|
|
184
|
+
createdBy: string;
|
|
185
|
+
createdAt: string;
|
|
186
|
+
updatedAt: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface ItemSummary {
|
|
190
|
+
id: string;
|
|
191
|
+
name: string;
|
|
192
|
+
description: string;
|
|
193
|
+
createdAt: string;
|
|
194
|
+
updatedAt: string;
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Vite configuration
|
|
199
|
+
|
|
200
|
+
### `frontend/vite.config.ts`
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
import { defineConfig } from "vite";
|
|
204
|
+
import react from "@vitejs/plugin-react";
|
|
205
|
+
|
|
206
|
+
export default defineConfig({
|
|
207
|
+
plugins: [react()],
|
|
208
|
+
base: "./", // Important for iframe embedding
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### `frontend/index.html`
|
|
213
|
+
|
|
214
|
+
```html
|
|
215
|
+
<!DOCTYPE html>
|
|
216
|
+
<html lang="en">
|
|
217
|
+
<head>
|
|
218
|
+
<meta charset="UTF-8" />
|
|
219
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
220
|
+
<title>My Ketrics App</title>
|
|
221
|
+
</head>
|
|
222
|
+
<body>
|
|
223
|
+
<div id="root"></div>
|
|
224
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
225
|
+
</body>
|
|
226
|
+
</html>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### `frontend/src/main.tsx`
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
import React from "react";
|
|
233
|
+
import ReactDOM from "react-dom/client";
|
|
234
|
+
import App from "./App";
|
|
235
|
+
|
|
236
|
+
ReactDOM.createRoot(document.getElementById("root")!).render(
|
|
237
|
+
<React.StrictMode>
|
|
238
|
+
<App />
|
|
239
|
+
</React.StrictMode>
|
|
240
|
+
);
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Frontend package.json
|
|
244
|
+
|
|
245
|
+
```json
|
|
246
|
+
{
|
|
247
|
+
"name": "my-ketrics-app",
|
|
248
|
+
"version": "1.0.0",
|
|
249
|
+
"type": "module",
|
|
250
|
+
"scripts": {
|
|
251
|
+
"dev": "vite",
|
|
252
|
+
"build": "tsc && vite build",
|
|
253
|
+
"preview": "vite preview"
|
|
254
|
+
},
|
|
255
|
+
"dependencies": {
|
|
256
|
+
"@ketrics/sdk-frontend": "^0.1.1",
|
|
257
|
+
"react": "^18.2.0",
|
|
258
|
+
"react-dom": "^18.2.0"
|
|
259
|
+
},
|
|
260
|
+
"devDependencies": {
|
|
261
|
+
"@types/react": "^18.2.0",
|
|
262
|
+
"@types/react-dom": "^18.2.0",
|
|
263
|
+
"@vitejs/plugin-react": "^4.2.0",
|
|
264
|
+
"typescript": "^5.3.3",
|
|
265
|
+
"vite": "^5.0.0"
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Common frontend patterns
|
|
271
|
+
|
|
272
|
+
### Loading and error states
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
const [loading, setLoading] = useState(false);
|
|
276
|
+
const [error, setError] = useState<string | null>(null);
|
|
277
|
+
|
|
278
|
+
const fetchData = async () => {
|
|
279
|
+
setLoading(true);
|
|
280
|
+
setError(null);
|
|
281
|
+
try {
|
|
282
|
+
const { result } = await callFunction<{ items: Item[] }>("listItems");
|
|
283
|
+
setItems(result.items);
|
|
284
|
+
} catch (err) {
|
|
285
|
+
setError(err instanceof Error ? err.message : "An error occurred");
|
|
286
|
+
} finally {
|
|
287
|
+
setLoading(false);
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### File download from presigned URL
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
const handleExport = async () => {
|
|
296
|
+
try {
|
|
297
|
+
const { result } = await callFunction<{ url: string; filename: string }>(
|
|
298
|
+
"exportData",
|
|
299
|
+
{ /* payload */ }
|
|
300
|
+
);
|
|
301
|
+
// Trigger browser download
|
|
302
|
+
const a = document.createElement("a");
|
|
303
|
+
a.href = result.url;
|
|
304
|
+
a.download = result.filename;
|
|
305
|
+
a.click();
|
|
306
|
+
} catch (err) {
|
|
307
|
+
setError(err instanceof Error ? err.message : "Export failed");
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Permission-based UI
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
const [isEditor, setIsEditor] = useState(false);
|
|
316
|
+
|
|
317
|
+
useEffect(() => {
|
|
318
|
+
callFunction<{ isEditor: boolean }>("getPermissions")
|
|
319
|
+
.then(({ result }) => setIsEditor(result.isEditor))
|
|
320
|
+
.catch(() => setIsEditor(false));
|
|
321
|
+
}, []);
|
|
322
|
+
|
|
323
|
+
// In JSX
|
|
324
|
+
{isEditor && <button onClick={handleDelete}>Delete</button>}
|
|
325
|
+
```
|