gufi-cli 0.1.50 → 0.1.52

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 (37) hide show
  1. package/dist/commands/docs.js +1 -5
  2. package/dist/index.js +1 -0
  3. package/dist/lib/docs-resolver.d.ts +8 -0
  4. package/dist/lib/docs-resolver.js +27 -0
  5. package/dist/mcp.d.ts +3 -1
  6. package/dist/mcp.js +232 -34
  7. package/docs/dev-guide/1-01-architecture.md +358 -0
  8. package/docs/dev-guide/1-02-multi-tenant.md +415 -0
  9. package/docs/dev-guide/1-03-column-types.md +594 -0
  10. package/docs/dev-guide/1-04-json-config.md +442 -0
  11. package/docs/dev-guide/1-05-authentication.md +427 -0
  12. package/docs/dev-guide/2-01-api-reference.md +564 -0
  13. package/docs/dev-guide/2-02-automations.md +508 -0
  14. package/docs/dev-guide/2-03-gufi-cli.md +568 -0
  15. package/docs/dev-guide/2-04-realtime.md +401 -0
  16. package/docs/dev-guide/2-05-permissions.md +497 -0
  17. package/docs/dev-guide/2-06-integrations-overview.md +104 -0
  18. package/docs/dev-guide/2-07-stripe.md +173 -0
  19. package/docs/dev-guide/2-08-nayax.md +297 -0
  20. package/docs/dev-guide/2-09-ourvend.md +226 -0
  21. package/docs/dev-guide/2-10-tns.md +177 -0
  22. package/docs/dev-guide/2-11-custom-http.md +268 -0
  23. package/docs/dev-guide/3-01-custom-views.md +555 -0
  24. package/docs/dev-guide/3-02-webhooks-api.md +446 -0
  25. package/docs/mcp/00-overview.md +329 -0
  26. package/docs/mcp/01-architecture.md +220 -0
  27. package/docs/mcp/02-modules.md +285 -0
  28. package/docs/mcp/03-fields.md +357 -0
  29. package/docs/mcp/04-views.md +613 -0
  30. package/docs/mcp/05-automations.md +461 -0
  31. package/docs/mcp/06-api.md +480 -0
  32. package/docs/mcp/07-packages.md +246 -0
  33. package/docs/mcp/08-common-errors.md +284 -0
  34. package/docs/mcp/09-examples.md +453 -0
  35. package/docs/mcp/README.md +71 -0
  36. package/docs/mcp/tool-descriptions.json +49 -0
  37. package/package.json +3 -2
@@ -0,0 +1,401 @@
1
+ ---
2
+ id: realtime
3
+ title: "Real-Time & WebSocket"
4
+ description: "Live updates with PostgreSQL LISTEN/NOTIFY"
5
+ icon: Wifi
6
+ category: dev
7
+ part: 2
8
+ ---
9
+
10
+ # Real-Time & WebSocket
11
+
12
+ Live updates with PostgreSQL LISTEN/NOTIFY
13
+
14
+ ## Architecture
15
+
16
+ Gufi uses PostgreSQL's LISTEN/NOTIFY for real-time updates, providing reliable, database-level event propagation.
17
+
18
+ ```
19
+ ┌──────────────────────────────────────────────────────────────┐
20
+ │ Real-Time System │
21
+ ├──────────────────────────────────────────────────────────────┤
22
+ │ │
23
+ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
24
+ │ │ Database │ │ WebSocket │ │ Clients │ │
25
+ │ │ Trigger │────▶│ Server │────▶│ (React) │ │
26
+ │ │ + NOTIFY │ │ :4000 │ │ │ │
27
+ │ └──────────────┘ └──────────────┘ └──────────────┘ │
28
+ │ │
29
+ │ INSERT/UPDATE/DELETE │
30
+ │ │ │
31
+ │ ▼ │
32
+ │ pg_notify('company_146_m360_t4589', '{"type":"INSERT"...}') │
33
+ │ │
34
+ └──────────────────────────────────────────────────────────────┘
35
+ ```
36
+
37
+ ## How It Works
38
+
39
+ ### Database Triggers
40
+
41
+ When data changes, triggers fire NOTIFY:
42
+
43
+ ```sql
44
+ -- Trigger function (created automatically)
45
+ CREATE FUNCTION notify_table_change() RETURNS TRIGGER AS $$
46
+ BEGIN
47
+ PERFORM pg_notify(
48
+ 'company_' || current_setting('app.company_id') || '_' || TG_TABLE_NAME,
49
+ json_build_object(
50
+ 'type', TG_OP,
51
+ 'table', TG_TABLE_NAME,
52
+ 'id', COALESCE(NEW.id, OLD.id),
53
+ 'data', CASE TG_OP
54
+ WHEN 'DELETE' THEN row_to_json(OLD)
55
+ ELSE row_to_json(NEW)
56
+ END
57
+ )::text
58
+ );
59
+ RETURN COALESCE(NEW, OLD);
60
+ END;
61
+ $$ LANGUAGE plpgsql;
62
+ ```
63
+
64
+ ### WebSocket Server
65
+
66
+ Listens to PostgreSQL channels and broadcasts:
67
+
68
+ ```javascript
69
+ // Simplified WebSocket server logic
70
+ const pg = require('pg');
71
+ const WebSocket = require('ws');
72
+
73
+ // Listen to all company channels
74
+ const listenClient = new pg.Client();
75
+ await listenClient.connect();
76
+
77
+ // Subscribe to channels dynamically
78
+ async function subscribeToCompany(companyId, tableId) {
79
+ const channel = `company_${companyId}_${tableId}`;
80
+ await listenClient.query(`LISTEN ${channel}`);
81
+ }
82
+
83
+ // Handle notifications
84
+ listenClient.on('notification', (msg) => {
85
+ const data = JSON.parse(msg.payload);
86
+ const [, companyId, tableId] = msg.channel.split('_');
87
+
88
+ // Broadcast to subscribed clients
89
+ broadcast(companyId, tableId, data);
90
+ });
91
+ ```
92
+
93
+ ### Client Connection
94
+
95
+ ```typescript
96
+ // React hook for WebSocket
97
+ import { useWebSocket } from '@/contexts/WebSocketContext';
98
+
99
+ function MyComponent() {
100
+ const { subscribe, isConnected } = useWebSocket();
101
+
102
+ useEffect(() => {
103
+ const unsubscribe = subscribe('m360_t4589', (event) => {
104
+ if (event.type === 'INSERT') {
105
+ // New record created
106
+ refetch();
107
+ } else if (event.type === 'UPDATE') {
108
+ // Record updated
109
+ updateLocalCache(event.id, event.data);
110
+ } else if (event.type === 'DELETE') {
111
+ // Record deleted
112
+ removeFromCache(event.id);
113
+ }
114
+ });
115
+
116
+ return () => unsubscribe();
117
+ }, []);
118
+ }
119
+ ```
120
+
121
+ ## Using Real-Time in Views
122
+
123
+ ### Basic Subscription
124
+
125
+ ```typescript
126
+ export default function MyView({ gufi }: Props) {
127
+ const { websocket, context } = gufi;
128
+ const [records, setRecords] = useState([]);
129
+
130
+ useEffect(() => {
131
+ const tableId = context.viewSpec?.ordersTable;
132
+ if (!tableId) return;
133
+
134
+ const unsubscribe = websocket.subscribeToTable(tableId, (event) => {
135
+ switch (event.type) {
136
+ case 'INSERT':
137
+ setRecords(prev => [...prev, event.data]);
138
+ break;
139
+ case 'UPDATE':
140
+ setRecords(prev =>
141
+ prev.map(r => r.id === event.id ? event.data : r)
142
+ );
143
+ break;
144
+ case 'DELETE':
145
+ setRecords(prev =>
146
+ prev.filter(r => r.id !== event.id)
147
+ );
148
+ break;
149
+ }
150
+ });
151
+
152
+ return () => unsubscribe();
153
+ }, [context.viewSpec?.ordersTable]);
154
+
155
+ return <RecordsList records={records} />;
156
+ }
157
+ ```
158
+
159
+ ### With SDK Hook
160
+
161
+ ```typescript
162
+ const { data, loading, refetch } = useViewData({
163
+ gufi,
164
+ tableKey: 'ordersTable',
165
+ realtime: true // Enable auto-refresh on changes
166
+ });
167
+ ```
168
+
169
+ ## Event Types
170
+
171
+ ### INSERT
172
+
173
+ ```json
174
+ {
175
+ "type": "INSERT",
176
+ "table": "m360_t4589",
177
+ "id": 123,
178
+ "data": {
179
+ "id": 123,
180
+ "name": "New Product",
181
+ "price": 99.99,
182
+ "created_at": "2024-01-15T10:30:00Z"
183
+ }
184
+ }
185
+ ```
186
+
187
+ ### UPDATE
188
+
189
+ ```json
190
+ {
191
+ "type": "UPDATE",
192
+ "table": "m360_t4589",
193
+ "id": 123,
194
+ "data": {
195
+ "id": 123,
196
+ "name": "Updated Product",
197
+ "price": 89.99,
198
+ "updated_at": "2024-01-15T11:00:00Z"
199
+ }
200
+ }
201
+ ```
202
+
203
+ ### DELETE
204
+
205
+ ```json
206
+ {
207
+ "type": "DELETE",
208
+ "table": "m360_t4589",
209
+ "id": 123,
210
+ "data": {
211
+ "id": 123,
212
+ "name": "Deleted Product"
213
+ }
214
+ }
215
+ ```
216
+
217
+ ## Broadcasting Custom Events
218
+
219
+ ### From Views
220
+
221
+ ```typescript
222
+ // Broadcast to other users viewing the same view
223
+ websocket.broadcastToView('selection-changed', {
224
+ selectedIds: [1, 2, 3]
225
+ });
226
+
227
+ // Listen for broadcasts
228
+ websocket.onViewBroadcast('selection-changed', (data) => {
229
+ console.log('Another user selected:', data.selectedIds);
230
+ });
231
+ ```
232
+
233
+ ### From Backend
234
+
235
+ ```javascript
236
+ // Send custom notification
237
+ const notifyClients = async (companyId, event) => {
238
+ await pool.query(
239
+ `SELECT pg_notify($1, $2)`,
240
+ [`company_${companyId}_custom`, JSON.stringify(event)]
241
+ );
242
+ };
243
+ ```
244
+
245
+ ## Connection Management
246
+
247
+ ### Connection Status
248
+
249
+ ```typescript
250
+ const { isConnected, reconnect } = useWebSocket();
251
+
252
+ if (!isConnected) {
253
+ return (
254
+ <div className="text-yellow-600">
255
+ <AlertCircle /> Connection lost. Reconnecting...
256
+ <button onClick={reconnect}>Retry Now</button>
257
+ </div>
258
+ );
259
+ }
260
+ ```
261
+
262
+ ### Automatic Reconnection
263
+
264
+ WebSocket client handles:
265
+
266
+ - Automatic reconnection on disconnect
267
+ - Exponential backoff (1s, 2s, 4s, 8s...)
268
+ - Re-subscription to channels after reconnect
269
+ - Heartbeat every 30 seconds
270
+
271
+ ### Manual Subscription Management
272
+
273
+ ```typescript
274
+ // Subscribe to multiple tables
275
+ const subscriptions = [
276
+ websocket.subscribeToTable(ordersTable, handleOrders),
277
+ websocket.subscribeToTable(productsTable, handleProducts),
278
+ ];
279
+
280
+ // Cleanup
281
+ return () => {
282
+ subscriptions.forEach(unsub => unsub());
283
+ };
284
+ ```
285
+
286
+ ## Performance Considerations
287
+
288
+ ### Debouncing Updates
289
+
290
+ For high-frequency changes:
291
+
292
+ ```typescript
293
+ const [debouncedData, setDebouncedData] = useState(data);
294
+
295
+ useEffect(() => {
296
+ const timer = setTimeout(() => {
297
+ setDebouncedData(data);
298
+ }, 100); // 100ms debounce
299
+
300
+ return () => clearTimeout(timer);
301
+ }, [data]);
302
+ ```
303
+
304
+ ### Selective Subscriptions
305
+
306
+ Only subscribe to what you need:
307
+
308
+ ```typescript
309
+ // Good: Subscribe to specific table
310
+ websocket.subscribeToTable(ordersTable, callback);
311
+
312
+ // Avoid: Don't subscribe to everything
313
+ // websocket.subscribeToAll(callback);
314
+ ```
315
+
316
+ ### Batching Updates
317
+
318
+ ```typescript
319
+ const pendingUpdates = useRef([]);
320
+
321
+ useEffect(() => {
322
+ const processBatch = setInterval(() => {
323
+ if (pendingUpdates.current.length > 0) {
324
+ setRecords(prev => [...prev, ...pendingUpdates.current]);
325
+ pendingUpdates.current = [];
326
+ }
327
+ }, 500);
328
+
329
+ return () => clearInterval(processBatch);
330
+ }, []);
331
+
332
+ // In websocket handler
333
+ const handleEvent = (event) => {
334
+ pendingUpdates.current.push(event.data);
335
+ };
336
+ ```
337
+
338
+ ## Scaling
339
+
340
+ ### Redis Pub/Sub
341
+
342
+ For horizontal scaling (multiple WebSocket servers):
343
+
344
+ ```
345
+ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
346
+ │ WS Server │ │ WS Server │ │ WS Server │
347
+ │ #1 │ │ #2 │ │ #3 │
348
+ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘
349
+ │ │ │
350
+ └───────────────────┼───────────────────┘
351
+
352
+ ┌──────▼──────┐
353
+ │ Redis │
354
+ │ Pub/Sub │
355
+ └──────┬──────┘
356
+
357
+ ┌──────▼──────┐
358
+ │ PostgreSQL │
359
+ │ NOTIFY │
360
+ └─────────────┘
361
+ ```
362
+
363
+ ### Configuration
364
+
365
+ ```javascript
366
+ // WebSocket server with Redis
367
+ const Redis = require('ioredis');
368
+ const redis = new Redis(process.env.REDIS_URL);
369
+
370
+ // Publish to all servers
371
+ redis.publish('gufi:events', JSON.stringify(event));
372
+
373
+ // Subscribe on each server
374
+ redis.subscribe('gufi:events');
375
+ redis.on('message', (channel, message) => {
376
+ broadcastToLocalClients(JSON.parse(message));
377
+ });
378
+ ```
379
+
380
+ ## Troubleshooting
381
+
382
+ ### No Updates Received
383
+
384
+ 1. Check WebSocket connection status
385
+ 2. Verify table triggers exist
386
+ 3. Check channel name matches
387
+ 4. Look for errors in browser console
388
+
389
+ ### Delayed Updates
390
+
391
+ 1. Check PostgreSQL connection
392
+ 2. Monitor WebSocket server logs
393
+ 3. Verify network latency
394
+ 4. Check for batching/debouncing
395
+
396
+ ### Memory Issues
397
+
398
+ 1. Ensure subscriptions are cleaned up
399
+ 2. Check for memory leaks in callbacks
400
+ 3. Monitor connection count
401
+ 4. Use pagination for large datasets