gufi-cli 0.1.50 → 0.1.51
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/commands/docs.js +1 -5
- package/dist/lib/docs-resolver.d.ts +8 -0
- package/dist/lib/docs-resolver.js +27 -0
- package/dist/mcp.js +5 -31
- package/docs/dev-guide/1-01-architecture.md +358 -0
- package/docs/dev-guide/1-02-multi-tenant.md +415 -0
- package/docs/dev-guide/1-03-column-types.md +594 -0
- package/docs/dev-guide/1-04-json-config.md +442 -0
- package/docs/dev-guide/1-05-authentication.md +427 -0
- package/docs/dev-guide/2-01-api-reference.md +564 -0
- package/docs/dev-guide/2-02-automations.md +508 -0
- package/docs/dev-guide/2-03-gufi-cli.md +568 -0
- package/docs/dev-guide/2-04-realtime.md +401 -0
- package/docs/dev-guide/2-05-permissions.md +497 -0
- package/docs/dev-guide/2-06-integrations-overview.md +104 -0
- package/docs/dev-guide/2-07-stripe.md +173 -0
- package/docs/dev-guide/2-08-nayax.md +297 -0
- package/docs/dev-guide/2-09-ourvend.md +226 -0
- package/docs/dev-guide/2-10-tns.md +177 -0
- package/docs/dev-guide/2-11-custom-http.md +268 -0
- package/docs/dev-guide/3-01-custom-views.md +555 -0
- package/docs/dev-guide/3-02-webhooks-api.md +446 -0
- package/docs/mcp/00-overview.md +329 -0
- package/docs/mcp/01-architecture.md +226 -0
- package/docs/mcp/02-modules.md +285 -0
- package/docs/mcp/03-fields.md +357 -0
- package/docs/mcp/04-views.md +613 -0
- package/docs/mcp/05-automations.md +461 -0
- package/docs/mcp/06-api.md +531 -0
- package/docs/mcp/07-packages.md +246 -0
- package/docs/mcp/08-common-errors.md +284 -0
- package/docs/mcp/09-examples.md +453 -0
- package/docs/mcp/README.md +71 -0
- package/docs/mcp/tool-descriptions.json +64 -0
- package/package.json +3 -2
package/dist/commands/docs.js
CHANGED
|
@@ -9,12 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import * as fs from "fs";
|
|
11
11
|
import * as path from "path";
|
|
12
|
-
import { fileURLToPath } from "url";
|
|
13
12
|
import chalk from "chalk";
|
|
14
|
-
|
|
15
|
-
const __dirname = path.dirname(__filename);
|
|
16
|
-
// docs/mcp path relative to CLI
|
|
17
|
-
const DOCS_MCP_PATH = path.resolve(__dirname, "../../../../docs/mcp");
|
|
13
|
+
import { DOCS_MCP_PATH } from "../lib/docs-resolver.js";
|
|
18
14
|
// Topic to file mapping
|
|
19
15
|
const TOPIC_FILES = {
|
|
20
16
|
overview: { file: "00-overview.md", description: "Overview and workflow" },
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves documentation paths for both development (monorepo) and global install.
|
|
3
|
+
*
|
|
4
|
+
* - Global install: docs are bundled at <pkg>/docs/
|
|
5
|
+
* - Development: docs live at <monorepo>/docs/
|
|
6
|
+
*/
|
|
7
|
+
export declare const DOCS_MCP_PATH: string;
|
|
8
|
+
export declare const DOCS_DEV_GUIDE_PATH: string;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves documentation paths for both development (monorepo) and global install.
|
|
3
|
+
*
|
|
4
|
+
* - Global install: docs are bundled at <pkg>/docs/
|
|
5
|
+
* - Development: docs live at <monorepo>/docs/
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from "fs";
|
|
8
|
+
import * as path from "path";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
// Package root: from dist/lib/ go up 2 levels
|
|
13
|
+
const PKG_ROOT = path.resolve(__dirname, "../..");
|
|
14
|
+
function resolveDocsPath(subdir) {
|
|
15
|
+
// 1. Package-local (global install): <pkg>/docs/<subdir>
|
|
16
|
+
const localPath = path.join(PKG_ROOT, "docs", subdir);
|
|
17
|
+
if (fs.existsSync(localPath))
|
|
18
|
+
return localPath;
|
|
19
|
+
// 2. Monorepo (development): <pkg>/../../docs/<subdir>
|
|
20
|
+
const monorepoPath = path.resolve(PKG_ROOT, "../../docs", subdir);
|
|
21
|
+
if (fs.existsSync(monorepoPath))
|
|
22
|
+
return monorepoPath;
|
|
23
|
+
// Fallback to local path (will fail gracefully downstream)
|
|
24
|
+
return localPath;
|
|
25
|
+
}
|
|
26
|
+
export const DOCS_MCP_PATH = resolveDocsPath("mcp");
|
|
27
|
+
export const DOCS_DEV_GUIDE_PATH = resolveDocsPath("dev-guide");
|
package/dist/mcp.js
CHANGED
|
@@ -48,10 +48,8 @@ const COLUMN_TYPE_NAMES = [
|
|
|
48
48
|
// For ES modules __dirname equivalent
|
|
49
49
|
const __filename = fileURLToPath(import.meta.url);
|
|
50
50
|
const __dirname = path.dirname(__filename);
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
// docs/dev-guide path for integration docs
|
|
54
|
-
const DOCS_DEV_GUIDE_PATH = path.resolve(__dirname, "../../../docs/dev-guide");
|
|
51
|
+
// Docs paths - resolved for both global install and monorepo dev
|
|
52
|
+
import { DOCS_MCP_PATH, DOCS_DEV_GUIDE_PATH } from "./lib/docs-resolver.js";
|
|
55
53
|
/**
|
|
56
54
|
* Parse frontmatter from markdown content
|
|
57
55
|
*/
|
|
@@ -1806,11 +1804,6 @@ const toolHandlers = {
|
|
|
1806
1804
|
// 💜 CLI: Save to ~/gufi-dev/company_<id>/view_<id>/
|
|
1807
1805
|
const viewDir = path.join(LOCAL_VIEWS_DIR, `company_${companyId}`, `view_${viewId}`);
|
|
1808
1806
|
fs.mkdirSync(viewDir, { recursive: true });
|
|
1809
|
-
// 💜 Migrate: if old path exists (~/gufi-dev/view_<id>/), remove it
|
|
1810
|
-
const oldViewDir = path.join(LOCAL_VIEWS_DIR, `view_${viewId}`);
|
|
1811
|
-
if (fs.existsSync(oldViewDir)) {
|
|
1812
|
-
fs.rmSync(oldViewDir, { recursive: true, force: true });
|
|
1813
|
-
}
|
|
1814
1807
|
// 💜 Get latest snapshot for version tracking
|
|
1815
1808
|
let latestSnapshot;
|
|
1816
1809
|
try {
|
|
@@ -1890,7 +1883,6 @@ const toolHandlers = {
|
|
|
1890
1883
|
}
|
|
1891
1884
|
// 💜 Try to get company_id from view metadata if not provided
|
|
1892
1885
|
if (!companyId && useLocal) {
|
|
1893
|
-
// Try new path first (company_*/view_*)
|
|
1894
1886
|
const companyDirs = fs.existsSync(LOCAL_VIEWS_DIR)
|
|
1895
1887
|
? fs.readdirSync(LOCAL_VIEWS_DIR).filter(d => d.startsWith("company_"))
|
|
1896
1888
|
: [];
|
|
@@ -1906,18 +1898,6 @@ const toolHandlers = {
|
|
|
1906
1898
|
break;
|
|
1907
1899
|
}
|
|
1908
1900
|
}
|
|
1909
|
-
// Fallback: old path (view_*)
|
|
1910
|
-
if (!companyId) {
|
|
1911
|
-
const oldMetaPath = path.join(LOCAL_VIEWS_DIR, `view_${viewId}`, ".gufi-view.json");
|
|
1912
|
-
if (fs.existsSync(oldMetaPath)) {
|
|
1913
|
-
try {
|
|
1914
|
-
const meta = JSON.parse(fs.readFileSync(oldMetaPath, "utf-8"));
|
|
1915
|
-
if (meta.company_id)
|
|
1916
|
-
companyId = String(meta.company_id);
|
|
1917
|
-
}
|
|
1918
|
-
catch { /* ignore */ }
|
|
1919
|
-
}
|
|
1920
|
-
}
|
|
1921
1901
|
}
|
|
1922
1902
|
if (!companyId) {
|
|
1923
1903
|
throw new Error("company_id is required. Pass it as parameter or pull the view first.");
|
|
@@ -1925,12 +1905,8 @@ const toolHandlers = {
|
|
|
1925
1905
|
let files = [];
|
|
1926
1906
|
let lastPulledSnapshot;
|
|
1927
1907
|
if (useLocal) {
|
|
1928
|
-
// 💜 CLI: Read from ~/gufi-dev/company_<id>/view_<id>/
|
|
1929
|
-
|
|
1930
|
-
if (!fs.existsSync(viewDir)) {
|
|
1931
|
-
// Fallback to old path
|
|
1932
|
-
viewDir = path.join(LOCAL_VIEWS_DIR, `view_${viewId}`);
|
|
1933
|
-
}
|
|
1908
|
+
// 💜 CLI: Read from ~/gufi-dev/company_<id>/view_<id>/
|
|
1909
|
+
const viewDir = path.join(LOCAL_VIEWS_DIR, `company_${companyId}`, `view_${viewId}`);
|
|
1934
1910
|
if (!fs.existsSync(viewDir)) {
|
|
1935
1911
|
throw new Error(`View directory not found. Run gufi_view_pull({ view_id: ${viewId}, company_id: '${companyId}' }) first.`);
|
|
1936
1912
|
}
|
|
@@ -2055,9 +2031,7 @@ const toolHandlers = {
|
|
|
2055
2031
|
}
|
|
2056
2032
|
// 💜 Update local metadata with new snapshot number (for version tracking)
|
|
2057
2033
|
if (useLocal && result.snapshot) {
|
|
2058
|
-
|
|
2059
|
-
if (!fs.existsSync(viewDir))
|
|
2060
|
-
viewDir = path.join(LOCAL_VIEWS_DIR, `view_${viewId}`);
|
|
2034
|
+
const viewDir = path.join(LOCAL_VIEWS_DIR, `company_${companyId}`, `view_${viewId}`);
|
|
2061
2035
|
const metaPath = path.join(viewDir, ".gufi-view.json");
|
|
2062
2036
|
if (fs.existsSync(metaPath)) {
|
|
2063
2037
|
try {
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: architecture
|
|
3
|
+
title: "Architecture Overview"
|
|
4
|
+
description: "How Gufi is built and why"
|
|
5
|
+
icon: Layers
|
|
6
|
+
category: dev
|
|
7
|
+
part: 1
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Architecture Overview
|
|
11
|
+
|
|
12
|
+
How Gufi is built and why
|
|
13
|
+
|
|
14
|
+
## System Architecture
|
|
15
|
+
|
|
16
|
+
Gufi follows a modern microservices architecture optimized for multi-tenancy, real-time updates, and developer experience.
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
20
|
+
│ CLIENTS │
|
|
21
|
+
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
|
22
|
+
│ │ Web App │ │ Mobile │ │ CLI │ │ API │ │
|
|
23
|
+
│ │ (React) │ │ (PWA) │ │ (Node) │ │ Clients │ │
|
|
24
|
+
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
|
|
25
|
+
│ │ │ │ │ │
|
|
26
|
+
└───────┼─────────────┼─────────────┼─────────────┼───────────────┘
|
|
27
|
+
│ │ │ │
|
|
28
|
+
▼ ▼ ▼ ▼
|
|
29
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
30
|
+
│ LOAD BALANCER │
|
|
31
|
+
└───────────────────────────┬─────────────────────────────────────┘
|
|
32
|
+
│
|
|
33
|
+
┌───────────────────┼───────────────────┐
|
|
34
|
+
│ │ │
|
|
35
|
+
▼ ▼ ▼
|
|
36
|
+
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
|
|
37
|
+
│ Backend ERP │ │ Marketplace │ │ WebSocket │
|
|
38
|
+
│ (Express) │ │ Backend │ │ Server │
|
|
39
|
+
│ :3000 │ │ :3003 │ │ :4000 │
|
|
40
|
+
└───────┬───────┘ └───────┬───────┘ └───────┬───────┘
|
|
41
|
+
│ │ │
|
|
42
|
+
└──────────────────┼───────────────────┘
|
|
43
|
+
│
|
|
44
|
+
┌──────────────────┴──────────────────┐
|
|
45
|
+
│ │
|
|
46
|
+
▼ ▼
|
|
47
|
+
┌───────────────┐ ┌───────────────┐
|
|
48
|
+
│ PostgreSQL │ │ Redis │
|
|
49
|
+
│ (Primary DB) │ │ (Cache) │
|
|
50
|
+
└───────────────┘ └───────────────┘
|
|
51
|
+
│
|
|
52
|
+
▼
|
|
53
|
+
┌───────────────┐
|
|
54
|
+
│ Worker │
|
|
55
|
+
│ (pg-boss) │
|
|
56
|
+
└───────────────┘
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Core Services
|
|
60
|
+
|
|
61
|
+
### Backend ERP (Port 3000)
|
|
62
|
+
|
|
63
|
+
The main API server handling:
|
|
64
|
+
|
|
65
|
+
- Authentication and authorization
|
|
66
|
+
- CRUD operations for all entities
|
|
67
|
+
- Module and schema management
|
|
68
|
+
- File uploads and storage
|
|
69
|
+
- Business logic and validations
|
|
70
|
+
|
|
71
|
+
**Tech Stack:**
|
|
72
|
+
- Node.js with Express
|
|
73
|
+
- JWT + HttpOnly cookie authentication
|
|
74
|
+
- Zod for validation
|
|
75
|
+
- pg (node-postgres) for database
|
|
76
|
+
|
|
77
|
+
### Marketplace Backend (Port 3003)
|
|
78
|
+
|
|
79
|
+
Handles marketplace-specific operations:
|
|
80
|
+
|
|
81
|
+
- Package publishing and versioning
|
|
82
|
+
- View code storage and retrieval
|
|
83
|
+
- Developer accounts and billing
|
|
84
|
+
- Cross-company module installations
|
|
85
|
+
|
|
86
|
+
**Key Endpoints:**
|
|
87
|
+
- `/packages` - Package CRUD
|
|
88
|
+
- `/views` - Custom view management
|
|
89
|
+
- `/developer` - Developer portal API
|
|
90
|
+
|
|
91
|
+
### WebSocket Server (Port 4000)
|
|
92
|
+
|
|
93
|
+
Real-time communication:
|
|
94
|
+
|
|
95
|
+
- LISTEN/NOTIFY from PostgreSQL
|
|
96
|
+
- Broadcasts changes to connected clients
|
|
97
|
+
- Room-based subscriptions per company/table
|
|
98
|
+
- Presence tracking
|
|
99
|
+
|
|
100
|
+
**Protocol:**
|
|
101
|
+
- Native WebSocket (not Socket.IO)
|
|
102
|
+
- JSON message format
|
|
103
|
+
- Heartbeat for connection health
|
|
104
|
+
|
|
105
|
+
### Worker (pg-boss)
|
|
106
|
+
|
|
107
|
+
Background job processing:
|
|
108
|
+
|
|
109
|
+
- Scheduled automations
|
|
110
|
+
- Email sending
|
|
111
|
+
- Heavy computations
|
|
112
|
+
- Data synchronization
|
|
113
|
+
- External API calls
|
|
114
|
+
|
|
115
|
+
**Features:**
|
|
116
|
+
- Persistent job queue in PostgreSQL
|
|
117
|
+
- Retry with exponential backoff
|
|
118
|
+
- Dead letter queue for failures
|
|
119
|
+
- Monitoring and alerting
|
|
120
|
+
|
|
121
|
+
## Frontend Architecture
|
|
122
|
+
|
|
123
|
+
### React Application
|
|
124
|
+
|
|
125
|
+
Built with modern React patterns:
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
frontend/src/
|
|
129
|
+
├── features/ # Feature-based organization
|
|
130
|
+
│ ├── auth/ # Login, registration
|
|
131
|
+
│ ├── tables/ # Table views and editing
|
|
132
|
+
│ ├── modules/ # Module management
|
|
133
|
+
│ └── ...
|
|
134
|
+
├── shared/ # Shared components
|
|
135
|
+
│ ├── ui/ # Base UI components
|
|
136
|
+
│ ├── views/ # View system components
|
|
137
|
+
│ ├── dialogs/ # Dialog components
|
|
138
|
+
│ └── hooks/ # Custom hooks
|
|
139
|
+
├── stores/ # Zustand state stores
|
|
140
|
+
├── contexts/ # React contexts
|
|
141
|
+
└── sdk/ # Gufi SDK for views
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### State Management
|
|
145
|
+
|
|
146
|
+
| Store | Purpose |
|
|
147
|
+
|---|---|
|
|
148
|
+
| **authStore** | User session, tokens |
|
|
149
|
+
| **schemaStore** | Company schema cache |
|
|
150
|
+
| **permissionsStore** | Permission cache |
|
|
151
|
+
| **uiStore** | UI state (sidebar, dialogs) |
|
|
152
|
+
|
|
153
|
+
Using Zustand for simple, type-safe state management.
|
|
154
|
+
|
|
155
|
+
### Data Fetching
|
|
156
|
+
|
|
157
|
+
Using Refine framework:
|
|
158
|
+
|
|
159
|
+
- Automatic CRUD hooks
|
|
160
|
+
- Caching and invalidation
|
|
161
|
+
- Optimistic updates
|
|
162
|
+
- Pagination helpers
|
|
163
|
+
|
|
164
|
+
## Database Architecture
|
|
165
|
+
|
|
166
|
+
### PostgreSQL as Core
|
|
167
|
+
|
|
168
|
+
Why PostgreSQL:
|
|
169
|
+
|
|
170
|
+
- JSONB for flexible schemas
|
|
171
|
+
- Row-level security
|
|
172
|
+
- LISTEN/NOTIFY for real-time
|
|
173
|
+
- Excellent performance
|
|
174
|
+
- Strong ACID compliance
|
|
175
|
+
|
|
176
|
+
### Multi-Tenant Schema
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
PostgreSQL Instance
|
|
180
|
+
├── core (schema) # Platform data
|
|
181
|
+
│ ├── users # All users
|
|
182
|
+
│ ├── companies # All companies
|
|
183
|
+
│ ├── modules # Module definitions
|
|
184
|
+
│ ├── entities # Entity definitions
|
|
185
|
+
│ └── automation_scripts # Shared scripts
|
|
186
|
+
│
|
|
187
|
+
├── company_1 (schema) # Company 1 data
|
|
188
|
+
│ ├── m1_t1 # Module 1, Entity 1
|
|
189
|
+
│ ├── m1_t2 # Module 1, Entity 2
|
|
190
|
+
│ ├── __audit_log__ # Audit trail
|
|
191
|
+
│ └── ...
|
|
192
|
+
│
|
|
193
|
+
├── company_2 (schema) # Company 2 data
|
|
194
|
+
│ └── ...
|
|
195
|
+
└── ...
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Table Naming Convention
|
|
199
|
+
|
|
200
|
+
Physical tables use IDs:
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
m{moduleId}_t{entityId}
|
|
204
|
+
|
|
205
|
+
Example: m360_t4589
|
|
206
|
+
└── Module 360, Entity 4589
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Logical names (products, customers) map to physical names via schema.
|
|
210
|
+
|
|
211
|
+
## API Design
|
|
212
|
+
|
|
213
|
+
### RESTful Endpoints
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
GET /api/tables/:tableId # List records
|
|
217
|
+
GET /api/tables/:tableId/:rowId # Get record
|
|
218
|
+
POST /api/tables/:tableId # Create record
|
|
219
|
+
PUT /api/tables/:tableId/:rowId # Update record
|
|
220
|
+
DELETE /api/tables/:tableId/:rowId # Delete record
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Authentication Flow
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
1. Login → POST /api/auth/login
|
|
227
|
+
← Access token (15min) + Refresh cookie (7days)
|
|
228
|
+
|
|
229
|
+
2. API Request → GET /api/... + Authorization: Bearer {token}
|
|
230
|
+
← Data response
|
|
231
|
+
|
|
232
|
+
3. Token Expiring → POST /api/auth/refresh
|
|
233
|
+
← New access token
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Response Format
|
|
237
|
+
|
|
238
|
+
Success:
|
|
239
|
+
```json
|
|
240
|
+
{
|
|
241
|
+
"data": { ... },
|
|
242
|
+
"meta": {
|
|
243
|
+
"total": 100,
|
|
244
|
+
"page": 1,
|
|
245
|
+
"pageSize": 20
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Error:
|
|
251
|
+
```json
|
|
252
|
+
{
|
|
253
|
+
"error": {
|
|
254
|
+
"code": "VALIDATION_ERROR",
|
|
255
|
+
"message": "Price must be positive",
|
|
256
|
+
"details": { "field": "price" }
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Security Model
|
|
262
|
+
|
|
263
|
+
### Layers of Security
|
|
264
|
+
|
|
265
|
+
1. **Network**: HTTPS, WAF, Rate limiting
|
|
266
|
+
2. **Authentication**: JWT, refresh tokens, 2FA
|
|
267
|
+
3. **Authorization**: Role-based, entity-level
|
|
268
|
+
4. **Data**: Row-level security, tenant isolation
|
|
269
|
+
5. **Audit**: Complete change tracking
|
|
270
|
+
|
|
271
|
+
### Tenant Isolation
|
|
272
|
+
|
|
273
|
+
Each request:
|
|
274
|
+
|
|
275
|
+
```
|
|
276
|
+
1. Extract company_id from JWT
|
|
277
|
+
2. SET search_path TO company_{id}
|
|
278
|
+
3. Execute query (sees only company data)
|
|
279
|
+
4. RESET search_path
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
No cross-tenant queries possible at database level.
|
|
283
|
+
|
|
284
|
+
## Caching Strategy
|
|
285
|
+
|
|
286
|
+
### Redis Cache Layers
|
|
287
|
+
|
|
288
|
+
| Layer | TTL | Purpose |
|
|
289
|
+
|---|---|---|
|
|
290
|
+
| **Schema** | 5 min | Company schemas |
|
|
291
|
+
| **Permissions** | 1 min | User permissions |
|
|
292
|
+
| **Queries** | 30 sec | Expensive queries |
|
|
293
|
+
| **Sessions** | 7 days | User sessions |
|
|
294
|
+
|
|
295
|
+
### Cache Invalidation
|
|
296
|
+
|
|
297
|
+
Event-driven invalidation:
|
|
298
|
+
|
|
299
|
+
```
|
|
300
|
+
Record Changed → Invalidate related caches
|
|
301
|
+
→ Notify WebSocket
|
|
302
|
+
→ Clients refetch
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Deployment
|
|
306
|
+
|
|
307
|
+
### Container Architecture
|
|
308
|
+
|
|
309
|
+
```yaml
|
|
310
|
+
services:
|
|
311
|
+
frontend:
|
|
312
|
+
image: gufi/frontend
|
|
313
|
+
ports: [5173, 8080]
|
|
314
|
+
|
|
315
|
+
backend:
|
|
316
|
+
image: gufi/backend
|
|
317
|
+
ports: [3000]
|
|
318
|
+
depends_on: [db, redis]
|
|
319
|
+
|
|
320
|
+
websocket:
|
|
321
|
+
image: gufi/websocket
|
|
322
|
+
ports: [4000]
|
|
323
|
+
depends_on: [db, redis]
|
|
324
|
+
|
|
325
|
+
worker:
|
|
326
|
+
image: gufi/worker
|
|
327
|
+
depends_on: [db, redis]
|
|
328
|
+
|
|
329
|
+
db:
|
|
330
|
+
image: postgres:15
|
|
331
|
+
volumes: [db_data]
|
|
332
|
+
|
|
333
|
+
redis:
|
|
334
|
+
image: redis:7
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Scaling
|
|
338
|
+
|
|
339
|
+
| Component | Scale Strategy |
|
|
340
|
+
|---|---|
|
|
341
|
+
| Frontend | Horizontal (CDN + replicas) |
|
|
342
|
+
| Backend | Horizontal (load balanced) |
|
|
343
|
+
| WebSocket | Horizontal (Redis pub/sub) |
|
|
344
|
+
| Worker | Horizontal (pg-boss handles) |
|
|
345
|
+
| PostgreSQL | Vertical + Read replicas |
|
|
346
|
+
| Redis | Cluster mode |
|
|
347
|
+
|
|
348
|
+
## Performance Targets
|
|
349
|
+
|
|
350
|
+
| Metric | Target |
|
|
351
|
+
|---|---|
|
|
352
|
+
| API Response (P95) | < 100ms |
|
|
353
|
+
| Page Load | < 2s |
|
|
354
|
+
| Time to Interactive | < 3s |
|
|
355
|
+
| WebSocket Latency | < 50ms |
|
|
356
|
+
| Database Query | < 50ms |
|
|
357
|
+
|
|
358
|
+
Monitored continuously with alerting on degradation.
|