@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.
Files changed (36) hide show
  1. package/dist/src/cli.d.ts.map +1 -1
  2. package/dist/src/cli.js +6 -0
  3. package/dist/src/cli.js.map +1 -1
  4. package/dist/src/commands/create.d.ts +1 -0
  5. package/dist/src/commands/create.d.ts.map +1 -1
  6. package/dist/src/commands/create.js +44 -13
  7. package/dist/src/commands/create.js.map +1 -1
  8. package/dist/src/services/local-template-service.d.ts +52 -0
  9. package/dist/src/services/local-template-service.d.ts.map +1 -0
  10. package/dist/src/services/local-template-service.js +216 -0
  11. package/dist/src/services/local-template-service.js.map +1 -0
  12. package/dist/src/services/remote-template-service.d.ts +41 -0
  13. package/dist/src/services/remote-template-service.d.ts.map +1 -0
  14. package/dist/src/services/remote-template-service.js +232 -0
  15. package/dist/src/services/remote-template-service.js.map +1 -0
  16. package/dist/src/services/template-cache-service.d.ts +44 -0
  17. package/dist/src/services/template-cache-service.d.ts.map +1 -0
  18. package/dist/src/services/template-cache-service.js +193 -0
  19. package/dist/src/services/template-cache-service.js.map +1 -0
  20. package/dist/src/services/template-service.d.ts +25 -31
  21. package/dist/src/services/template-service.d.ts.map +1 -1
  22. package/dist/src/services/template-service.js +136 -132
  23. package/dist/src/services/template-service.js.map +1 -1
  24. package/dist/src/types/index.d.ts +46 -0
  25. package/dist/src/types/index.d.ts.map +1 -1
  26. package/dist/src/types/index.js.map +1 -1
  27. package/dist/src/version.d.ts +1 -1
  28. package/dist/src/version.js +1 -1
  29. package/package.json +5 -1
  30. package/templates/HelloWorld/.claude/skills/ketrics-app/BACKEND_REFERENCE.md +693 -0
  31. package/templates/HelloWorld/.claude/skills/ketrics-app/CONFIG_AND_DEPLOY.md +278 -0
  32. package/templates/HelloWorld/.claude/skills/ketrics-app/FRONTEND_REFERENCE.md +325 -0
  33. package/templates/HelloWorld/.claude/skills/ketrics-app/SKILL.md +348 -0
  34. package/templates/HelloWorld/.env.example +20 -0
  35. package/templates/HelloWorld/.github/workflows/deploy.yml +51 -0
  36. 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
+ ```