@bluealba/platform-cli 1.0.1 → 1.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/dist/index.js +278 -15
- package/docs/404.mdx +5 -0
- package/docs/architecture/api-explorer.mdx +478 -0
- package/docs/architecture/architecture-diagrams.mdx +12 -0
- package/docs/architecture/authentication-system.mdx +903 -0
- package/docs/architecture/authorization-system.mdx +886 -0
- package/docs/architecture/bootstrap.mdx +1442 -0
- package/docs/architecture/gateway-architecture.mdx +845 -0
- package/docs/architecture/multi-tenancy.mdx +1150 -0
- package/docs/architecture/overview.mdx +776 -0
- package/docs/architecture/scheduler.mdx +818 -0
- package/docs/architecture/shell.mdx +885 -0
- package/docs/architecture/ui-extension-points.mdx +781 -0
- package/docs/architecture/user-states.mdx +794 -0
- package/docs/development/overview.mdx +21 -0
- package/docs/development/workflow.mdx +914 -0
- package/docs/getting-started/core-concepts.mdx +892 -0
- package/docs/getting-started/installation.mdx +780 -0
- package/docs/getting-started/overview.mdx +83 -0
- package/docs/getting-started/quick-start.mdx +940 -0
- package/docs/guides/adding-documentation-sites.mdx +1367 -0
- package/docs/guides/creating-services.mdx +1736 -0
- package/docs/guides/creating-ui-modules.mdx +1860 -0
- package/docs/guides/identity-providers.mdx +1007 -0
- package/docs/guides/mermaid-diagrams.mdx +212 -0
- package/docs/guides/using-feature-flags.mdx +1059 -0
- package/docs/guides/working-with-rooms.mdx +566 -0
- package/docs/index.mdx +57 -0
- package/docs/platform-cli/commands.mdx +604 -0
- package/docs/platform-cli/overview.mdx +195 -0
- package/package.json +5 -2
- package/skills/ba-platform/platform-cli.skill.md +26 -0
- package/skills/ba-platform/platform.skill.md +35 -0
- package/templates/application-monorepo-template/gitignore +95 -0
- package/templates/bootstrap-service-template/Dockerfile.development +1 -1
- package/templates/bootstrap-service-template/gitignore +57 -0
- package/templates/bootstrap-service-template/package.json +1 -1
- package/templates/bootstrap-service-template/src/main.ts +6 -16
- package/templates/customization-ui-module-template/Dockerfile.development +1 -1
- package/templates/customization-ui-module-template/gitignore +73 -0
- package/templates/nestjs-service-module-template/Dockerfile.development +1 -1
- package/templates/nestjs-service-module-template/gitignore +56 -0
- package/templates/platform-init-template/{{platformName}}-core/gitignore +97 -0
- package/templates/platform-init-template/{{platformName}}-core/local/.env.example +1 -1
- package/templates/platform-init-template/{{platformName}}-core/local/platform-docker-compose.yml +1 -1
- package/templates/platform-init-template/{{platformName}}-core/local/{{platformName}}-core-docker-compose.yml +0 -1
- package/templates/react-ui-module-template/Dockerfile +1 -1
- package/templates/react-ui-module-template/Dockerfile.development +1 -3
- package/templates/react-ui-module-template/caddy/Caddyfile +1 -1
- package/templates/react-ui-module-template/gitignore +72 -0
- package/templates/react-ui-module-template/Dockerfile_nginx +0 -11
- package/templates/react-ui-module-template/nginx/default.conf +0 -23
|
@@ -0,0 +1,794 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: User States
|
|
3
|
+
description: User-specific state storage mechanism for persisting user preferences, configurations, and application settings
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
import { Card, CardGrid, Aside, Tabs, TabItem } from '@astrojs/starlight/components';
|
|
7
|
+
|
|
8
|
+
**User States** provide a key-value storage mechanism for applications to persist user-specific information such as preferences, configurations, and parameters. This enables personalized user experiences that persist across sessions.
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
User States allow applications to store and retrieve user-specific data:
|
|
13
|
+
|
|
14
|
+
<CardGrid stagger>
|
|
15
|
+
<Card title="Key-Value Storage" icon="seti:db">
|
|
16
|
+
Simple key-value pairs scoped to user and application
|
|
17
|
+
</Card>
|
|
18
|
+
|
|
19
|
+
<Card title="Persistent Storage" icon="seti:lock">
|
|
20
|
+
Data persists across sessions and survives browser refreshes
|
|
21
|
+
</Card>
|
|
22
|
+
|
|
23
|
+
<Card title="Application-Scoped" icon="seti:folder">
|
|
24
|
+
Values are isolated per application - no cross-app data leakage
|
|
25
|
+
</Card>
|
|
26
|
+
|
|
27
|
+
<Card title="React Hooks API" icon="seti:react">
|
|
28
|
+
Easy-to-use React hooks for UI applications
|
|
29
|
+
</Card>
|
|
30
|
+
|
|
31
|
+
<Card title="REST API" icon="random">
|
|
32
|
+
RESTful endpoints for backend services and non-React apps
|
|
33
|
+
</Card>
|
|
34
|
+
|
|
35
|
+
<Card title="Hierarchical Keys" icon="seti:text">
|
|
36
|
+
Support for namespaced keys using `::` separator
|
|
37
|
+
</Card>
|
|
38
|
+
</CardGrid>
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Common Use Cases
|
|
43
|
+
|
|
44
|
+
### UI Preferences
|
|
45
|
+
|
|
46
|
+
Store user interface preferences:
|
|
47
|
+
|
|
48
|
+
- **Table views and filters**: Column visibility, sort order, pagination size
|
|
49
|
+
- **Layout preferences**: Sidebar collapsed/expanded, theme selection
|
|
50
|
+
- **View modes**: Grid vs list view, compact vs comfortable density
|
|
51
|
+
- **Recently used items**: Last selected entity, recent searches
|
|
52
|
+
|
|
53
|
+
### Application Configuration
|
|
54
|
+
|
|
55
|
+
Persist application-specific settings:
|
|
56
|
+
|
|
57
|
+
- **Default values**: Default currency, language, timezone
|
|
58
|
+
- **Workflow preferences**: Auto-save frequency, confirmation dialogs
|
|
59
|
+
- **Display options**: Date format, number format
|
|
60
|
+
- **Feature flags**: Beta features enabled by user
|
|
61
|
+
|
|
62
|
+
### User Context
|
|
63
|
+
|
|
64
|
+
Remember user context between sessions:
|
|
65
|
+
|
|
66
|
+
- **Last viewed entity**: Resume where user left off
|
|
67
|
+
- **Active filters**: Restore filters from last session
|
|
68
|
+
- **Draft data**: Auto-saved form inputs
|
|
69
|
+
- **Navigation state**: Remember last visited route per application
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Data Model
|
|
74
|
+
|
|
75
|
+
### User State Structure
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
interface UserState {
|
|
79
|
+
id: number;
|
|
80
|
+
user: string; // User identifier (email/username)
|
|
81
|
+
application: string; // Application name
|
|
82
|
+
name: string; // Key name (supports :: hierarchy)
|
|
83
|
+
value: any; // JSON-serializable value
|
|
84
|
+
createdAt: Date;
|
|
85
|
+
updatedAt: Date;
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**Example**:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"id": 123,
|
|
94
|
+
"user": "john@acme.com",
|
|
95
|
+
"application": "crm",
|
|
96
|
+
"name": "ui::customers::table::columns",
|
|
97
|
+
"value": ["name", "email", "phone", "status"],
|
|
98
|
+
"createdAt": "2025-01-01T10:00:00Z",
|
|
99
|
+
"updatedAt": "2025-01-09T15:30:00Z"
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Hierarchical Keys
|
|
104
|
+
|
|
105
|
+
Keys support a namespace hierarchy using the `::` separator:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
application::feature::component::setting
|
|
109
|
+
|
|
110
|
+
Examples:
|
|
111
|
+
- ui::menu::collapsed
|
|
112
|
+
- ui::customers::table::pageSize
|
|
113
|
+
- ui::customers::table::filters
|
|
114
|
+
- ui::dashboard::widgets::visible
|
|
115
|
+
- ui::reports::default::currency
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Benefits**:
|
|
119
|
+
- Organized, self-documenting keys
|
|
120
|
+
- Wildcard queries (e.g., `ui::customers::*`)
|
|
121
|
+
- Easy to reason about data structure
|
|
122
|
+
- Avoid key collisions
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Client-Side API (React)
|
|
127
|
+
|
|
128
|
+
For React-based micro-frontends, use the `useUserState` hook:
|
|
129
|
+
|
|
130
|
+
### Basic Usage
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { useUserState } from '@bluealba-public/pae-ui-react-core';
|
|
134
|
+
|
|
135
|
+
function MyComponent() {
|
|
136
|
+
const { value, setValue, loading, error } = useUserState<boolean>(
|
|
137
|
+
'ui::menu::collapsed'
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<div>
|
|
142
|
+
<MyMenu collapsed={error ? false : value} />
|
|
143
|
+
<Checkbox
|
|
144
|
+
checked={value}
|
|
145
|
+
onChange={(e) => setValue(e.target.checked)}
|
|
146
|
+
disabled={loading}
|
|
147
|
+
/>
|
|
148
|
+
{error && <span>Error loading preference</span>}
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Hook API**:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
function useUserState<T>(key: string): {
|
|
158
|
+
value: T | null; // Current value (null if unset or loading)
|
|
159
|
+
setValue: (value: T) => Promise<void>; // Update value
|
|
160
|
+
loading: boolean; // True while fetching or updating
|
|
161
|
+
error: Error | null; // Error if fetch/update failed
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Automatic Context Inference
|
|
166
|
+
|
|
167
|
+
The hook automatically infers:
|
|
168
|
+
- **User**: From current authenticated user
|
|
169
|
+
- **Application**: From the micro-frontend's application context
|
|
170
|
+
|
|
171
|
+
No need to manually specify these - the platform handles it!
|
|
172
|
+
|
|
173
|
+
### Type Safety
|
|
174
|
+
|
|
175
|
+
Use TypeScript generics for type-safe values:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
interface TablePreferences {
|
|
179
|
+
pageSize: number;
|
|
180
|
+
sortBy: string;
|
|
181
|
+
sortOrder: 'asc' | 'desc';
|
|
182
|
+
visibleColumns: string[];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function CustomersTable() {
|
|
186
|
+
const { value: prefs, setValue } = useUserState<TablePreferences>(
|
|
187
|
+
'ui::customers::table::preferences'
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// prefs is typed as TablePreferences | null
|
|
191
|
+
const pageSize = prefs?.pageSize ?? 10;
|
|
192
|
+
const sortBy = prefs?.sortBy ?? 'name';
|
|
193
|
+
|
|
194
|
+
const updatePageSize = (newSize: number) => {
|
|
195
|
+
setValue({
|
|
196
|
+
...prefs,
|
|
197
|
+
pageSize: newSize,
|
|
198
|
+
});
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
return <Table pageSize={pageSize} onPageSizeChange={updatePageSize} />;
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Loading States
|
|
206
|
+
|
|
207
|
+
Handle loading and error states:
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
function UserPreferences() {
|
|
211
|
+
const { value, setValue, loading, error } = useUserState<boolean>(
|
|
212
|
+
'ui::darkMode'
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
if (loading) {
|
|
216
|
+
return <Spinner />;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (error) {
|
|
220
|
+
return <ErrorMessage>Failed to load preferences</ErrorMessage>;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return (
|
|
224
|
+
<Switch
|
|
225
|
+
checked={value ?? false}
|
|
226
|
+
onChange={(checked) => setValue(checked)}
|
|
227
|
+
/>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Complex State Example
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
interface DashboardWidgets {
|
|
236
|
+
sales: { visible: boolean; position: number; };
|
|
237
|
+
orders: { visible: boolean; position: number; };
|
|
238
|
+
customers: { visible: boolean; position: number; };
|
|
239
|
+
revenue: { visible: boolean; position: number; };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function DashboardCustomizer() {
|
|
243
|
+
const { value: widgets, setValue } = useUserState<DashboardWidgets>(
|
|
244
|
+
'ui::dashboard::widgets'
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
const toggleWidget = (widgetName: keyof DashboardWidgets) => {
|
|
248
|
+
setValue({
|
|
249
|
+
...widgets,
|
|
250
|
+
[widgetName]: {
|
|
251
|
+
...widgets[widgetName],
|
|
252
|
+
visible: !widgets[widgetName].visible,
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const reorderWidget = (widgetName: keyof DashboardWidgets, newPosition: number) => {
|
|
258
|
+
setValue({
|
|
259
|
+
...widgets,
|
|
260
|
+
[widgetName]: {
|
|
261
|
+
...widgets[widgetName],
|
|
262
|
+
position: newPosition,
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
return (
|
|
268
|
+
<div>
|
|
269
|
+
{Object.entries(widgets ?? {}).map(([name, config]) => (
|
|
270
|
+
<WidgetControl
|
|
271
|
+
key={name}
|
|
272
|
+
name={name}
|
|
273
|
+
visible={config.visible}
|
|
274
|
+
position={config.position}
|
|
275
|
+
onToggle={() => toggleWidget(name as keyof DashboardWidgets)}
|
|
276
|
+
onReorder={(pos) => reorderWidget(name as keyof DashboardWidgets, pos)}
|
|
277
|
+
/>
|
|
278
|
+
))}
|
|
279
|
+
</div>
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Server-Side API (REST)
|
|
287
|
+
|
|
288
|
+
For backend services or non-React frontends, use the REST API:
|
|
289
|
+
|
|
290
|
+
### Endpoints
|
|
291
|
+
|
|
292
|
+
<Tabs>
|
|
293
|
+
<TabItem label="List User States">
|
|
294
|
+
**GET** `/_/user-states/:application`
|
|
295
|
+
|
|
296
|
+
Retrieve all user states for the current user in a specific application.
|
|
297
|
+
|
|
298
|
+
**Path Parameters**:
|
|
299
|
+
- `application` - Application name (required)
|
|
300
|
+
|
|
301
|
+
**Authentication**: User is automatically determined from authenticated session
|
|
302
|
+
|
|
303
|
+
**Response**:
|
|
304
|
+
```json
|
|
305
|
+
[
|
|
306
|
+
{
|
|
307
|
+
"id": 123,
|
|
308
|
+
"user": "john@acme.com",
|
|
309
|
+
"application": "crm",
|
|
310
|
+
"name": "ui::customers::table::columns",
|
|
311
|
+
"value": ["name", "email", "phone"]
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
"id": 124,
|
|
315
|
+
"user": "john@acme.com",
|
|
316
|
+
"application": "crm",
|
|
317
|
+
"name": "ui::customers::table::pageSize",
|
|
318
|
+
"value": 25
|
|
319
|
+
}
|
|
320
|
+
]
|
|
321
|
+
```
|
|
322
|
+
</TabItem>
|
|
323
|
+
|
|
324
|
+
<TabItem label="Get User State">
|
|
325
|
+
**GET** `/_/user-states/:application/:name`
|
|
326
|
+
|
|
327
|
+
Retrieve a specific user state by application and key name.
|
|
328
|
+
|
|
329
|
+
**Path Parameters**:
|
|
330
|
+
- `application` - Application name (required)
|
|
331
|
+
- `name` - State key name (required)
|
|
332
|
+
|
|
333
|
+
**Response**:
|
|
334
|
+
```json
|
|
335
|
+
{
|
|
336
|
+
"id": 123,
|
|
337
|
+
"user": "john@acme.com",
|
|
338
|
+
"application": "crm",
|
|
339
|
+
"name": "ui::menu::collapsed",
|
|
340
|
+
"value": true
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
**Returns `null` if not found**
|
|
345
|
+
</TabItem>
|
|
346
|
+
|
|
347
|
+
<TabItem label="Create User State">
|
|
348
|
+
**POST** `/_/user-states/:application/:name`
|
|
349
|
+
|
|
350
|
+
Create a new user state.
|
|
351
|
+
|
|
352
|
+
**Path Parameters**:
|
|
353
|
+
- `application` - Application name (required)
|
|
354
|
+
- `name` - State key name (required)
|
|
355
|
+
|
|
356
|
+
**Request Body**:
|
|
357
|
+
```json
|
|
358
|
+
{
|
|
359
|
+
"value": true
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
**Response**:
|
|
364
|
+
```json
|
|
365
|
+
{
|
|
366
|
+
"id": 123,
|
|
367
|
+
"user": "john@acme.com",
|
|
368
|
+
"application": "crm",
|
|
369
|
+
"name": "ui::menu::collapsed",
|
|
370
|
+
"value": true
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
**Error Responses**:
|
|
375
|
+
- `409 Conflict` - User state already exists (use PUT to update)
|
|
376
|
+
- `400 Bad Request` - Value is required
|
|
377
|
+
</TabItem>
|
|
378
|
+
|
|
379
|
+
<TabItem label="Update User State">
|
|
380
|
+
**PUT** `/_/user-states/:application/:name`
|
|
381
|
+
|
|
382
|
+
Update an existing user state.
|
|
383
|
+
|
|
384
|
+
**Path Parameters**:
|
|
385
|
+
- `application` - Application name (required)
|
|
386
|
+
- `name` - State key name (required)
|
|
387
|
+
|
|
388
|
+
**Request Body**:
|
|
389
|
+
```json
|
|
390
|
+
{
|
|
391
|
+
"value": false
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
**Response**:
|
|
396
|
+
```json
|
|
397
|
+
{
|
|
398
|
+
"id": 123,
|
|
399
|
+
"user": "john@acme.com",
|
|
400
|
+
"application": "crm",
|
|
401
|
+
"name": "ui::menu::collapsed",
|
|
402
|
+
"value": false
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
**Error Responses**:
|
|
407
|
+
- `404 Not Found` - User state does not exist (use POST to create)
|
|
408
|
+
- `400 Bad Request` - Value is required
|
|
409
|
+
</TabItem>
|
|
410
|
+
|
|
411
|
+
<TabItem label="Delete User State">
|
|
412
|
+
**DELETE** `/_/user-states/:application/:name`
|
|
413
|
+
|
|
414
|
+
Delete a specific user state.
|
|
415
|
+
|
|
416
|
+
**Path Parameters**:
|
|
417
|
+
- `application` - Application name (required)
|
|
418
|
+
- `name` - State key name (required)
|
|
419
|
+
|
|
420
|
+
**Response**: `204 No Content` (empty response on success)
|
|
421
|
+
|
|
422
|
+
**Error Response**:
|
|
423
|
+
- `404 Not Found` - User state does not exist
|
|
424
|
+
</TabItem>
|
|
425
|
+
|
|
426
|
+
<TabItem label="Delete All States for Application">
|
|
427
|
+
**DELETE** `/_/user-states/:application`
|
|
428
|
+
|
|
429
|
+
Delete all user states for the current user in a specific application.
|
|
430
|
+
|
|
431
|
+
**Path Parameters**:
|
|
432
|
+
- `application` - Application name (required)
|
|
433
|
+
|
|
434
|
+
**Response**:
|
|
435
|
+
```json
|
|
436
|
+
{
|
|
437
|
+
"deletedCount": 12
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
</TabItem>
|
|
441
|
+
|
|
442
|
+
<TabItem label="Admin: Delete All States for User">
|
|
443
|
+
**DELETE** `/_/user-states/admin/user/:username`
|
|
444
|
+
|
|
445
|
+
Admin endpoint to delete all user states for a specific user across all applications.
|
|
446
|
+
|
|
447
|
+
**Path Parameters**:
|
|
448
|
+
- `username` - Username (required)
|
|
449
|
+
|
|
450
|
+
**Response**:
|
|
451
|
+
```json
|
|
452
|
+
{
|
|
453
|
+
"deletedCount": 45
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
**Note**: This is an admin-only endpoint. Requires appropriate admin permissions.
|
|
458
|
+
</TabItem>
|
|
459
|
+
</Tabs>
|
|
460
|
+
|
|
461
|
+
### cURL Examples
|
|
462
|
+
|
|
463
|
+
```bash
|
|
464
|
+
# List all states for an application
|
|
465
|
+
curl -X GET "https://platform.com/_/user-states/crm" \
|
|
466
|
+
-H "Cookie: auth_token=..."
|
|
467
|
+
|
|
468
|
+
# Get a specific user state
|
|
469
|
+
curl -X GET "https://platform.com/_/user-states/crm/ui::menu::collapsed" \
|
|
470
|
+
-H "Cookie: auth_token=..."
|
|
471
|
+
|
|
472
|
+
# Create a new user state
|
|
473
|
+
curl -X POST "https://platform.com/_/user-states/crm/ui::menu::collapsed" \
|
|
474
|
+
-H "Content-Type: application/json" \
|
|
475
|
+
-H "Cookie: auth_token=..." \
|
|
476
|
+
-d '{
|
|
477
|
+
"value": true
|
|
478
|
+
}'
|
|
479
|
+
|
|
480
|
+
# Update an existing user state
|
|
481
|
+
curl -X PUT "https://platform.com/_/user-states/crm/ui::menu::collapsed" \
|
|
482
|
+
-H "Content-Type: application/json" \
|
|
483
|
+
-H "Cookie: auth_token=..." \
|
|
484
|
+
-d '{
|
|
485
|
+
"value": false
|
|
486
|
+
}'
|
|
487
|
+
|
|
488
|
+
# Delete a user state
|
|
489
|
+
curl -X DELETE "https://platform.com/_/user-states/crm/ui::menu::collapsed" \
|
|
490
|
+
-H "Cookie: auth_token=..."
|
|
491
|
+
|
|
492
|
+
# Delete all states for an application
|
|
493
|
+
curl -X DELETE "https://platform.com/_/user-states/crm" \
|
|
494
|
+
-H "Cookie: auth_token=..."
|
|
495
|
+
|
|
496
|
+
# Admin: Delete all states for a user
|
|
497
|
+
curl -X DELETE "https://platform.com/_/user-states/admin/user/john@acme.com" \
|
|
498
|
+
-H "Cookie: auth_token=..."
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
---
|
|
502
|
+
|
|
503
|
+
## Advanced Patterns
|
|
504
|
+
|
|
505
|
+
### Typed State Blueprint (Future Enhancement)
|
|
506
|
+
|
|
507
|
+
For large applications with many user states, a typed blueprint approach is planned:
|
|
508
|
+
|
|
509
|
+
```typescript
|
|
510
|
+
// Define complete application user state schema
|
|
511
|
+
interface MyAppUserStateSchema {
|
|
512
|
+
'ui': {
|
|
513
|
+
'menu': {
|
|
514
|
+
'collapsed': boolean;
|
|
515
|
+
};
|
|
516
|
+
'customers': {
|
|
517
|
+
'table': {
|
|
518
|
+
'columns': string[];
|
|
519
|
+
'pageSize': number;
|
|
520
|
+
'filters': {
|
|
521
|
+
field: string;
|
|
522
|
+
operator: string;
|
|
523
|
+
value: any;
|
|
524
|
+
}[];
|
|
525
|
+
};
|
|
526
|
+
};
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Create typed blueprint
|
|
531
|
+
const MyAppUserState = createUserStateBlueprint<MyAppUserStateSchema>();
|
|
532
|
+
|
|
533
|
+
// Use with type safety
|
|
534
|
+
function Component() {
|
|
535
|
+
const { value, setValue } = MyAppUserState.useStateFor('ui::customers::table::filters');
|
|
536
|
+
// value is typed as the filter array
|
|
537
|
+
}
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
<Aside type="note" title="Future Feature">
|
|
541
|
+
The typed blueprint API is planned for a future release and is not yet implemented.
|
|
542
|
+
</Aside>
|
|
543
|
+
|
|
544
|
+
### Filtering States
|
|
545
|
+
|
|
546
|
+
Retrieve all states for an application and filter them client-side:
|
|
547
|
+
|
|
548
|
+
```bash
|
|
549
|
+
# Get all states for an application
|
|
550
|
+
GET /_/user-states/crm
|
|
551
|
+
|
|
552
|
+
# Then filter the results in your code
|
|
553
|
+
const allStates = await fetch('/_/user-states/crm');
|
|
554
|
+
const tableStates = allStates.filter(state =>
|
|
555
|
+
state.name.includes('::table::')
|
|
556
|
+
);
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### Default Values
|
|
560
|
+
|
|
561
|
+
Always provide sensible defaults:
|
|
562
|
+
|
|
563
|
+
```typescript
|
|
564
|
+
function Component() {
|
|
565
|
+
const { value } = useUserState<number>('ui::table::pageSize');
|
|
566
|
+
|
|
567
|
+
// Use default if value is null (not set)
|
|
568
|
+
const pageSize = value ?? 10;
|
|
569
|
+
|
|
570
|
+
return <Table pageSize={pageSize} />;
|
|
571
|
+
}
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
### Optimistic Updates
|
|
575
|
+
|
|
576
|
+
Update UI immediately, sync in background:
|
|
577
|
+
|
|
578
|
+
```typescript
|
|
579
|
+
function Component() {
|
|
580
|
+
const { value, setValue } = useUserState<boolean>('ui::menu::collapsed');
|
|
581
|
+
const [localValue, setLocalValue] = useState(value);
|
|
582
|
+
|
|
583
|
+
const toggle = async () => {
|
|
584
|
+
const newValue = !localValue;
|
|
585
|
+
setLocalValue(newValue); // Update UI immediately
|
|
586
|
+
await setValue(newValue); // Sync to server
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
return <Menu collapsed={localValue} onToggle={toggle} />;
|
|
590
|
+
}
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
---
|
|
594
|
+
|
|
595
|
+
## Best Practices
|
|
596
|
+
|
|
597
|
+
<CardGrid>
|
|
598
|
+
<Card title="Use Hierarchical Keys" icon="seti:text">
|
|
599
|
+
Organize keys with `::` separator for clarity and maintainability
|
|
600
|
+
</Card>
|
|
601
|
+
|
|
602
|
+
<Card title="Provide Defaults" icon="approve-check">
|
|
603
|
+
Always handle null values with sensible defaults
|
|
604
|
+
</Card>
|
|
605
|
+
|
|
606
|
+
<Card title="Type Your Values" icon="seti:typescript">
|
|
607
|
+
Use TypeScript generics for type safety
|
|
608
|
+
</Card>
|
|
609
|
+
|
|
610
|
+
<Card title="Scope Appropriately" icon="seti:folder">
|
|
611
|
+
Use application-specific keys to avoid conflicts
|
|
612
|
+
</Card>
|
|
613
|
+
|
|
614
|
+
<Card title="Handle Errors Gracefully" icon="warning">
|
|
615
|
+
Don't break UX if user state fetch fails
|
|
616
|
+
</Card>
|
|
617
|
+
|
|
618
|
+
<Card title="Document State Keys" icon="document">
|
|
619
|
+
Maintain documentation of all user state keys used by your app
|
|
620
|
+
</Card>
|
|
621
|
+
</CardGrid>
|
|
622
|
+
|
|
623
|
+
---
|
|
624
|
+
|
|
625
|
+
## Security Considerations
|
|
626
|
+
|
|
627
|
+
<Aside type="caution" title="Security Best Practices">
|
|
628
|
+
- **Never store sensitive data**: Passwords, API keys, tokens should NOT be stored in user states
|
|
629
|
+
- **Validate on server**: Don't trust user state values blindly - validate them
|
|
630
|
+
- **Respect tenant boundaries**: User states are tenant-scoped, maintain this isolation
|
|
631
|
+
- **Size limits**: Avoid storing large objects - user states are for configuration, not data storage
|
|
632
|
+
- **Rate limiting**: Be mindful of update frequency to avoid overwhelming the API
|
|
633
|
+
</Aside>
|
|
634
|
+
|
|
635
|
+
---
|
|
636
|
+
|
|
637
|
+
## Performance Optimization
|
|
638
|
+
|
|
639
|
+
### Batch Queries
|
|
640
|
+
|
|
641
|
+
When loading multiple states, fetch in parallel:
|
|
642
|
+
|
|
643
|
+
```typescript
|
|
644
|
+
function Dashboard() {
|
|
645
|
+
const widgets = useUserState<WidgetConfig>('ui::dashboard::widgets');
|
|
646
|
+
const layout = useUserState<LayoutConfig>('ui::dashboard::layout');
|
|
647
|
+
const theme = useUserState<ThemeConfig>('ui::dashboard::theme');
|
|
648
|
+
|
|
649
|
+
// All three queries execute in parallel
|
|
650
|
+
if (widgets.loading || layout.loading || theme.loading) {
|
|
651
|
+
return <Loading />;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
return <DashboardView widgets={widgets.value} layout={layout.value} theme={theme.value} />;
|
|
655
|
+
}
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
### Debounce Updates
|
|
659
|
+
|
|
660
|
+
For frequently changing values, debounce updates:
|
|
661
|
+
|
|
662
|
+
```typescript
|
|
663
|
+
import { useDebouncedCallback } from 'use-debounce';
|
|
664
|
+
|
|
665
|
+
function SearchInput() {
|
|
666
|
+
const { value, setValue } = useUserState<string>('ui::search::lastQuery');
|
|
667
|
+
const [localValue, setLocalValue] = useState(value ?? '');
|
|
668
|
+
|
|
669
|
+
const debouncedSave = useDebouncedCallback((newValue: string) => {
|
|
670
|
+
setValue(newValue);
|
|
671
|
+
}, 1000);
|
|
672
|
+
|
|
673
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
674
|
+
const newValue = e.target.value;
|
|
675
|
+
setLocalValue(newValue);
|
|
676
|
+
debouncedSave(newValue);
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
return <input value={localValue} onChange={handleChange} />;
|
|
680
|
+
}
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
### Memoization
|
|
684
|
+
|
|
685
|
+
Memoize expensive computations based on user state:
|
|
686
|
+
|
|
687
|
+
```typescript
|
|
688
|
+
function DataTable() {
|
|
689
|
+
const { value: columns } = useUserState<string[]>('ui::table::columns');
|
|
690
|
+
|
|
691
|
+
const processedData = useMemo(() => {
|
|
692
|
+
if (!columns) return [];
|
|
693
|
+
return data.map(row =>
|
|
694
|
+
Object.fromEntries(
|
|
695
|
+
Object.entries(row).filter(([key]) => columns.includes(key))
|
|
696
|
+
)
|
|
697
|
+
);
|
|
698
|
+
}, [data, columns]);
|
|
699
|
+
|
|
700
|
+
return <Table data={processedData} />;
|
|
701
|
+
}
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
---
|
|
705
|
+
|
|
706
|
+
## Future Enhancements
|
|
707
|
+
|
|
708
|
+
Planned features for future releases:
|
|
709
|
+
|
|
710
|
+
### Shared States
|
|
711
|
+
|
|
712
|
+
Allow users to share state with other users or groups:
|
|
713
|
+
|
|
714
|
+
- **Shared views**: Share table configurations with team members
|
|
715
|
+
- **Team preferences**: Group-level defaults
|
|
716
|
+
- **Template states**: Pre-configured states for new users
|
|
717
|
+
|
|
718
|
+
### Admin UI
|
|
719
|
+
|
|
720
|
+
Administrative interface for managing user states:
|
|
721
|
+
|
|
722
|
+
- **Global user states view**: See all states across all users
|
|
723
|
+
- **User-specific view**: View and edit states for a specific user
|
|
724
|
+
- **Bulk operations**: Delete or update multiple states at once
|
|
725
|
+
- **Analytics**: Most common preferences, usage patterns
|
|
726
|
+
|
|
727
|
+
### State Migrations
|
|
728
|
+
|
|
729
|
+
Version and migrate user state schemas:
|
|
730
|
+
|
|
731
|
+
- **Data versioning**: Track schema version with state data
|
|
732
|
+
- **Automatic migrations**: Migrate old state formats to new schemas
|
|
733
|
+
- **Migration hooks**: Application-defined migration logic
|
|
734
|
+
- **Rollback support**: Safely rollback breaking changes
|
|
735
|
+
|
|
736
|
+
### State Synchronization
|
|
737
|
+
|
|
738
|
+
Real-time synchronization across devices:
|
|
739
|
+
|
|
740
|
+
- **WebSocket updates**: Push state changes to all user sessions
|
|
741
|
+
- **Conflict resolution**: Handle concurrent updates gracefully
|
|
742
|
+
- **Offline support**: Queue updates when offline, sync when reconnected
|
|
743
|
+
|
|
744
|
+
---
|
|
745
|
+
|
|
746
|
+
## Troubleshooting
|
|
747
|
+
|
|
748
|
+
### State Not Persisting
|
|
749
|
+
|
|
750
|
+
**Check**:
|
|
751
|
+
1. User is authenticated
|
|
752
|
+
2. Application context is set correctly
|
|
753
|
+
3. No network errors (check browser console)
|
|
754
|
+
4. API endpoints are accessible
|
|
755
|
+
|
|
756
|
+
**Debug**:
|
|
757
|
+
```typescript
|
|
758
|
+
const { value, setValue, error } = useUserState('my::key');
|
|
759
|
+
|
|
760
|
+
useEffect(() => {
|
|
761
|
+
console.log('User state:', { value, error });
|
|
762
|
+
}, [value, error]);
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
### State Not Loading
|
|
766
|
+
|
|
767
|
+
**Possible Causes**:
|
|
768
|
+
- User state never created (first time user)
|
|
769
|
+
- Wrong key name
|
|
770
|
+
- Application context mismatch
|
|
771
|
+
|
|
772
|
+
**Solution**: Always provide defaults
|
|
773
|
+
```typescript
|
|
774
|
+
const { value } = useUserState<boolean>('ui::menu::collapsed');
|
|
775
|
+
const collapsed = value ?? false; // Default to false
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
### Type Errors
|
|
779
|
+
|
|
780
|
+
**Cause**: Mismatch between stored value type and TypeScript type.
|
|
781
|
+
|
|
782
|
+
**Solution**: Validate and transform stored values
|
|
783
|
+
```typescript
|
|
784
|
+
const { value } = useUserState<number>('ui::pageSize');
|
|
785
|
+
const pageSize = typeof value === 'number' && value > 0 ? value : 10;
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
---
|
|
789
|
+
|
|
790
|
+
## Next Steps
|
|
791
|
+
|
|
792
|
+
- **[API Explorer](/_/docs/architecture/api-explorer/)** - Testing user state API endpoints
|
|
793
|
+
- **[Shell Architecture](/_/docs/architecture/shell/)** - Using user states for Shell customization
|
|
794
|
+
- **[Getting Started with UI Development](/_/docs/development/ui-development/)** - Building UIs that use user states
|