@bffless/skills 1.7.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/.claude-plugin/marketplace.json +18 -0
- package/LICENSE.md +19 -0
- package/README.md +117 -0
- package/package.json +24 -0
- package/plugins/bffless/.claude-plugin/plugin.json +21 -0
- package/plugins/bffless/skills/authentication/SKILL.md +204 -0
- package/plugins/bffless/skills/authorization/SKILL.md +59 -0
- package/plugins/bffless/skills/bffless/SKILL.md +158 -0
- package/plugins/bffless/skills/cache-and-storage/SKILL.md +151 -0
- package/plugins/bffless/skills/chat/SKILL.md +196 -0
- package/plugins/bffless/skills/pipelines/SKILL.md +162 -0
- package/plugins/bffless/skills/proxy-rules/SKILL.md +110 -0
- package/plugins/bffless/skills/repository/SKILL.md +64 -0
- package/plugins/bffless/skills/share-links/SKILL.md +53 -0
- package/plugins/bffless/skills/traffic-splitting/SKILL.md +63 -0
- package/plugins/bffless/skills/upload-artifact/SKILL.md +195 -0
- package/plugins/bffless/skills/use-bff-state/SKILL.md +174 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: share-links
|
|
3
|
+
description: Token-based sharing for private deployments
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Share Links
|
|
7
|
+
|
|
8
|
+
**Docs**: https://docs.bffless.app/features/share-links/
|
|
9
|
+
|
|
10
|
+
Share links allow sharing private deployments without requiring authentication. Each link contains a unique token that grants temporary access.
|
|
11
|
+
|
|
12
|
+
## Creating Share Links
|
|
13
|
+
|
|
14
|
+
1. Navigate to your deployment in the Repository
|
|
15
|
+
2. Click the **Share** button in the toolbar
|
|
16
|
+
3. Configure options:
|
|
17
|
+
- **Expiration**: Set duration or leave unlimited
|
|
18
|
+
- **Usage limit**: Max number of visits (optional)
|
|
19
|
+
- **Label**: Descriptive name for tracking
|
|
20
|
+
4. Copy the generated URL
|
|
21
|
+
|
|
22
|
+
## Link Management
|
|
23
|
+
|
|
24
|
+
- **View usage**: See visit count and last accessed time
|
|
25
|
+
- **Disable**: Temporarily suspend access without deleting
|
|
26
|
+
- **Regenerate**: Create new token, invalidating the old URL
|
|
27
|
+
- **Delete**: Permanently remove access
|
|
28
|
+
|
|
29
|
+
## Use Cases
|
|
30
|
+
|
|
31
|
+
- **Portfolio sharing**: Send private work to potential clients
|
|
32
|
+
- **Client reviews**: Share staging sites for feedback
|
|
33
|
+
- **Beta testing**: Distribute preview builds to testers
|
|
34
|
+
- **Time-limited access**: Demos that expire after a meeting
|
|
35
|
+
|
|
36
|
+
## Best Practices
|
|
37
|
+
|
|
38
|
+
1. Use descriptive labels to track who has which link
|
|
39
|
+
2. Set expiration dates for sensitive content
|
|
40
|
+
3. Combine with traffic splitting to show different content per link
|
|
41
|
+
4. Regenerate tokens if a link is compromised
|
|
42
|
+
5. Monitor usage to detect unexpected access patterns
|
|
43
|
+
|
|
44
|
+
## Troubleshooting
|
|
45
|
+
|
|
46
|
+
**Link not working?**
|
|
47
|
+
- Check if the link is disabled or expired
|
|
48
|
+
- Verify usage limit hasn't been reached
|
|
49
|
+
- Ensure the deployment still exists
|
|
50
|
+
|
|
51
|
+
**Need to revoke access?**
|
|
52
|
+
- Disable the link immediately, or delete it entirely
|
|
53
|
+
- Regenerate creates a new URL while keeping tracking history
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: traffic-splitting
|
|
3
|
+
description: Distribute traffic across aliases with weights and rules
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Traffic Splitting
|
|
7
|
+
|
|
8
|
+
**Docs**: https://docs.bffless.app/features/traffic-splitting/
|
|
9
|
+
|
|
10
|
+
Traffic splitting distributes requests across multiple deployment aliases. Use it for A/B testing, canary deployments, and personalized experiences.
|
|
11
|
+
|
|
12
|
+
## Configuration
|
|
13
|
+
|
|
14
|
+
### Weights
|
|
15
|
+
|
|
16
|
+
Assign percentage weights to aliases. Weights must sum to 100.
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
production: 90%
|
|
20
|
+
canary: 10%
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Sticky Sessions
|
|
24
|
+
|
|
25
|
+
When enabled, visitors consistently see the same variant. Controlled via cookie. Disable for true random distribution per request.
|
|
26
|
+
|
|
27
|
+
### Traffic Rules
|
|
28
|
+
|
|
29
|
+
Rules override weights based on conditions. Evaluated in order, first match wins.
|
|
30
|
+
|
|
31
|
+
**Rule conditions:**
|
|
32
|
+
- **Headers**: Match request headers (e.g., `X-Beta-User: true`)
|
|
33
|
+
- **Query params**: Match URL parameters (e.g., `?variant=new`)
|
|
34
|
+
- **Cookies**: Match cookie values
|
|
35
|
+
- **Share link**: Route specific share links to specific aliases
|
|
36
|
+
|
|
37
|
+
## Use Cases
|
|
38
|
+
|
|
39
|
+
**A/B Testing**: Split traffic 50/50 between variants, measure conversion
|
|
40
|
+
|
|
41
|
+
**Canary Deployment**: Send 5% to new version, monitor for errors, gradually increase
|
|
42
|
+
|
|
43
|
+
**Beta Features**: Route users with beta header to feature branch
|
|
44
|
+
|
|
45
|
+
**Personalized Demos**: Use share links to show customized content per client
|
|
46
|
+
|
|
47
|
+
## Configuration Tips
|
|
48
|
+
|
|
49
|
+
1. Start canary deployments at 5-10%, increase gradually
|
|
50
|
+
2. Enable sticky sessions for consistent user experience
|
|
51
|
+
3. Use rules for deterministic routing (internal testing, specific clients)
|
|
52
|
+
4. Combine with share links for per-recipient personalization
|
|
53
|
+
|
|
54
|
+
## Troubleshooting
|
|
55
|
+
|
|
56
|
+
**Unexpected routing?**
|
|
57
|
+
- Rules are evaluated before weights; check rule order
|
|
58
|
+
- Clear cookies if sticky session is routing to old variant
|
|
59
|
+
- Verify alias names match exactly (case-sensitive)
|
|
60
|
+
|
|
61
|
+
**Traffic percentages seem off?**
|
|
62
|
+
- Small sample sizes show high variance; need 100+ requests for accuracy
|
|
63
|
+
- Sticky sessions can skew percentages if users have different visit frequencies
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: upload-artifact
|
|
3
|
+
description: GitHub Action for uploading build artifacts to BFFless
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# BFFless Upload Artifact Action
|
|
7
|
+
|
|
8
|
+
The `bffless/upload-artifact` GitHub Action uploads build artifacts from your CI/CD pipeline to a BFFless instance. It's available on the [GitHub Marketplace](https://github.com/bffless/upload-artifact).
|
|
9
|
+
|
|
10
|
+
For full documentation, see the [README on GitHub](https://github.com/bffless/upload-artifact).
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
Only 3 inputs are required:
|
|
15
|
+
|
|
16
|
+
```yaml
|
|
17
|
+
- uses: bffless/upload-artifact@v1
|
|
18
|
+
with:
|
|
19
|
+
path: dist
|
|
20
|
+
api-url: ${{ vars.ASSET_HOST_URL }}
|
|
21
|
+
api-key: ${{ secrets.ASSET_HOST_KEY }}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Common Workflows
|
|
25
|
+
|
|
26
|
+
### PR Preview with Comment
|
|
27
|
+
|
|
28
|
+
```yaml
|
|
29
|
+
jobs:
|
|
30
|
+
build:
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
permissions:
|
|
33
|
+
contents: read
|
|
34
|
+
pull-requests: write # Required for PR comments
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/checkout@v4
|
|
37
|
+
with:
|
|
38
|
+
fetch-depth: 0 # Required for commit timestamp
|
|
39
|
+
|
|
40
|
+
- name: Build
|
|
41
|
+
run: npm run build
|
|
42
|
+
|
|
43
|
+
- uses: bffless/upload-artifact@v1
|
|
44
|
+
with:
|
|
45
|
+
path: dist
|
|
46
|
+
api-url: ${{ vars.ASSET_HOST_URL }}
|
|
47
|
+
api-key: ${{ secrets.ASSET_HOST_KEY }}
|
|
48
|
+
alias: preview
|
|
49
|
+
description: 'PR #${{ github.event.pull_request.number }} preview'
|
|
50
|
+
pr-comment: true
|
|
51
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Production Deploy
|
|
55
|
+
|
|
56
|
+
```yaml
|
|
57
|
+
- uses: bffless/upload-artifact@v1
|
|
58
|
+
id: deploy
|
|
59
|
+
with:
|
|
60
|
+
path: dist
|
|
61
|
+
api-url: ${{ vars.ASSET_HOST_URL }}
|
|
62
|
+
api-key: ${{ secrets.ASSET_HOST_KEY }}
|
|
63
|
+
alias: production
|
|
64
|
+
tags: ${{ needs.release.outputs.version }}
|
|
65
|
+
description: 'Release ${{ needs.release.outputs.version }}'
|
|
66
|
+
|
|
67
|
+
- run: echo "Deployed to ${{ steps.deploy.outputs.sha-url }}"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Multiple Artifacts (Same Workflow)
|
|
71
|
+
|
|
72
|
+
You can upload multiple artifacts in the same workflow:
|
|
73
|
+
|
|
74
|
+
```yaml
|
|
75
|
+
- name: Upload frontend
|
|
76
|
+
uses: bffless/upload-artifact@v1
|
|
77
|
+
with:
|
|
78
|
+
path: apps/frontend/dist
|
|
79
|
+
api-url: ${{ vars.ASSET_HOST_URL }}
|
|
80
|
+
api-key: ${{ secrets.ASSET_HOST_KEY }}
|
|
81
|
+
alias: production
|
|
82
|
+
base-path: /dist
|
|
83
|
+
|
|
84
|
+
- name: Upload skills
|
|
85
|
+
uses: bffless/upload-artifact@v1
|
|
86
|
+
with:
|
|
87
|
+
path: .bffless
|
|
88
|
+
api-url: ${{ vars.ASSET_HOST_URL }}
|
|
89
|
+
api-key: ${{ secrets.ASSET_HOST_KEY }}
|
|
90
|
+
alias: production
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## All Inputs
|
|
94
|
+
|
|
95
|
+
| Input | Required | Default | Description |
|
|
96
|
+
|-------|----------|---------|-------------|
|
|
97
|
+
| `path` | **yes** | -- | Build directory to upload |
|
|
98
|
+
| `api-url` | **yes** | -- | BFFless platform URL |
|
|
99
|
+
| `api-key` | **yes** | -- | API key for authentication |
|
|
100
|
+
| `repository` | no | `github.repository` | Repository in `owner/repo` format |
|
|
101
|
+
| `commit-sha` | no | auto | Git commit SHA |
|
|
102
|
+
| `branch` | no | auto | Branch name |
|
|
103
|
+
| `is-public` | no | `'true'` | Public visibility |
|
|
104
|
+
| `alias` | no | -- | Deployment alias (e.g., `production`) |
|
|
105
|
+
| `base-path` | no | `/<path>` | Path prefix in zip |
|
|
106
|
+
| `committed-at` | no | auto | ISO 8601 commit timestamp |
|
|
107
|
+
| `description` | no | -- | Human-readable description |
|
|
108
|
+
| `proxy-rule-set-name` | no | -- | Proxy rule set name |
|
|
109
|
+
| `proxy-rule-set-id` | no | -- | Proxy rule set ID |
|
|
110
|
+
| `tags` | no | -- | Comma-separated tags |
|
|
111
|
+
| `summary` | no | `'true'` | Write GitHub Step Summary |
|
|
112
|
+
| `summary-title` | no | `'Deployment Summary'` | Summary heading |
|
|
113
|
+
| `working-directory` | no | `'.'` | Working directory |
|
|
114
|
+
| `pr-comment` | no | `'false'` | Post comment on PR |
|
|
115
|
+
| `comment-header` | no | `'🚀 BFFLESS Deployment'` | PR comment header |
|
|
116
|
+
| `github-token` | no | `GITHUB_TOKEN` | Token for PR comments |
|
|
117
|
+
|
|
118
|
+
## Outputs
|
|
119
|
+
|
|
120
|
+
| Output | Description |
|
|
121
|
+
|--------|-------------|
|
|
122
|
+
| `deployment-url` | Primary URL (SHA-based) |
|
|
123
|
+
| `sha-url` | Immutable SHA-based URL |
|
|
124
|
+
| `alias-url` | Alias-based URL |
|
|
125
|
+
| `preview-url` | Preview URL (if basePath provided) |
|
|
126
|
+
| `branch-url` | Branch-based URL |
|
|
127
|
+
| `deployment-id` | API deployment ID |
|
|
128
|
+
| `file-count` | Number of files uploaded |
|
|
129
|
+
| `total-size` | Total bytes |
|
|
130
|
+
| `response` | Raw JSON response |
|
|
131
|
+
|
|
132
|
+
## How It Works
|
|
133
|
+
|
|
134
|
+
1. **Validates** the build directory exists
|
|
135
|
+
2. **Zips** the directory preserving structure
|
|
136
|
+
3. **Uploads** via multipart POST to `/api/deployments/zip`
|
|
137
|
+
4. **Sets outputs** from API response
|
|
138
|
+
5. **Writes Step Summary** with deployment table
|
|
139
|
+
6. **Posts PR comment** (if enabled)
|
|
140
|
+
7. **Cleans up** temporary files
|
|
141
|
+
|
|
142
|
+
## Auto-Detection
|
|
143
|
+
|
|
144
|
+
The action automatically detects:
|
|
145
|
+
|
|
146
|
+
- **Repository**: from `github.repository`
|
|
147
|
+
- **Commit SHA**: PR head SHA or push SHA
|
|
148
|
+
- **Branch**: PR head ref or push ref
|
|
149
|
+
- **Committed At**: via `git log` (requires `fetch-depth: 0`)
|
|
150
|
+
- **Base Path**: derived from `path` input as `/<path>`
|
|
151
|
+
|
|
152
|
+
## PR Comments
|
|
153
|
+
|
|
154
|
+
When `pr-comment: true`, the action posts a formatted comment:
|
|
155
|
+
|
|
156
|
+
> ## 🚀 BFFLESS Deployment
|
|
157
|
+
>
|
|
158
|
+
> **Alias:** `preview`
|
|
159
|
+
>
|
|
160
|
+
> | Property | Value |
|
|
161
|
+
> | ----------- | ---------------------------------- |
|
|
162
|
+
> | **Preview** | [example.com](https://example.com) |
|
|
163
|
+
> | **Commit** | `abc1234` |
|
|
164
|
+
> | **Files** | 42 |
|
|
165
|
+
> | **Size** | 1.2 MB |
|
|
166
|
+
|
|
167
|
+
Comments are automatically updated on subsequent pushes (no duplicates).
|
|
168
|
+
|
|
169
|
+
## Troubleshooting
|
|
170
|
+
|
|
171
|
+
### Missing commit timestamp
|
|
172
|
+
|
|
173
|
+
```yaml
|
|
174
|
+
- uses: actions/checkout@v4
|
|
175
|
+
with:
|
|
176
|
+
fetch-depth: 0 # Full history needed for git log
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### PR comment permission denied
|
|
180
|
+
|
|
181
|
+
```yaml
|
|
182
|
+
permissions:
|
|
183
|
+
pull-requests: write # Required for comments
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Custom base path
|
|
187
|
+
|
|
188
|
+
If your app expects to be served from a subdirectory:
|
|
189
|
+
|
|
190
|
+
```yaml
|
|
191
|
+
- uses: bffless/upload-artifact@v1
|
|
192
|
+
with:
|
|
193
|
+
path: build
|
|
194
|
+
base-path: /docs/build # Served at /docs/build/*
|
|
195
|
+
```
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: use-bff-state
|
|
3
|
+
description: React hook for managing server-side state with BFFless Data Tables and Pipelines, with guest IDs, loading states, and stale refetching
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @bffless/use-bff-state
|
|
7
|
+
|
|
8
|
+
React hook library for managing server-side state with BFFless Data Tables and Pipelines. Enables React apps to synchronize state with a BFFless backend, with automatic guest identification, loading states, error handling, and stale data refetching.
|
|
9
|
+
|
|
10
|
+
- **NPM Package**: `@bffless/use-bff-state`
|
|
11
|
+
- **Docs**: https://docs.bffless.app/recipes/state-management/
|
|
12
|
+
- **Peer Dependency**: React 18 or 19
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @bffless/use-bff-state
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Setup
|
|
21
|
+
|
|
22
|
+
Wrap your app with `BffStateProvider`:
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { BffStateProvider } from '@bffless/use-bff-state';
|
|
26
|
+
|
|
27
|
+
<BffStateProvider
|
|
28
|
+
options={{
|
|
29
|
+
baseUrl: '/api', // URL prefix for all requests
|
|
30
|
+
headers: {}, // Global headers for all requests
|
|
31
|
+
persistence: 'forever', // Guest ID cookie: 'session' | { days: N } | 'forever'
|
|
32
|
+
staleTime: 5000, // ms before refetch on window focus
|
|
33
|
+
}}
|
|
34
|
+
>
|
|
35
|
+
<App />
|
|
36
|
+
</BffStateProvider>;
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
All options are optional. The provider can be used with no options: `<BffStateProvider><App /></BffStateProvider>`.
|
|
40
|
+
|
|
41
|
+
## Core Hook: `useBffState`
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { useBffState } from '@bffless/use-bff-state';
|
|
45
|
+
|
|
46
|
+
const {
|
|
47
|
+
data, // T — current state (initialValue until first fetch)
|
|
48
|
+
guestId, // string — UUID for this browser/session
|
|
49
|
+
loading, // boolean — isFetching OR isUpdating
|
|
50
|
+
error, // Error | null
|
|
51
|
+
update, // (newState: T | (prev: T) => T) => Promise<void>
|
|
52
|
+
refetch, // () => Promise<void>
|
|
53
|
+
isUninitialized, // true before first fetch
|
|
54
|
+
isFetching, // true during GET
|
|
55
|
+
isUpdating, // true during POST
|
|
56
|
+
} = useBffState<T>(path, initialValue, options?);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Hook Options
|
|
60
|
+
|
|
61
|
+
| Option | Type | Default | Description |
|
|
62
|
+
| ---------------------- | ------------------------ | ------- | ---------------------------------------- |
|
|
63
|
+
| `skip` | `boolean` | `false` | Skip initial fetch (conditional loading) |
|
|
64
|
+
| `headers` | `Record<string, string>` | `{}` | Per-hook headers (merged with provider) |
|
|
65
|
+
| `refetchOnWindowFocus` | `boolean` | `true` | Refetch when tab visible if data stale |
|
|
66
|
+
|
|
67
|
+
## Usage Patterns
|
|
68
|
+
|
|
69
|
+
### Basic State
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
const { data, update } = useBffState('/api/preferences', { theme: 'light' });
|
|
73
|
+
|
|
74
|
+
// Direct update
|
|
75
|
+
await update({ theme: 'dark' });
|
|
76
|
+
|
|
77
|
+
// Functional update (uses current state)
|
|
78
|
+
await update((prev) => ({ ...prev, theme: 'dark' }));
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Shopping Cart
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
const {
|
|
85
|
+
data: cart,
|
|
86
|
+
update,
|
|
87
|
+
loading,
|
|
88
|
+
} = useBffState('/api/cart', {
|
|
89
|
+
items: [],
|
|
90
|
+
total: 0,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const addItem = async (item) => {
|
|
94
|
+
await update((prev) => ({
|
|
95
|
+
items: [...prev.items, item],
|
|
96
|
+
total: prev.total + item.price,
|
|
97
|
+
}));
|
|
98
|
+
};
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Conditional Fetching
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
const { data } = useBffState(
|
|
105
|
+
`/api/users/${userId}/prefs`,
|
|
106
|
+
{ theme: 'light' },
|
|
107
|
+
{ skip: !userId }, // Don't fetch until userId exists
|
|
108
|
+
);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Feature Flags
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
const { data: flags, update } = useBffState('/api/feature-flags', {
|
|
115
|
+
darkMode: false,
|
|
116
|
+
betaFeatures: false,
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Other Exports
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
import { useGuestId, useBffStateContext } from '@bffless/use-bff-state';
|
|
124
|
+
|
|
125
|
+
const guestId = useGuestId(); // Get current guest ID
|
|
126
|
+
const context = useBffStateContext(); // Access provider config
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Core Utilities (Advanced)
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
import {
|
|
133
|
+
generateUuid,
|
|
134
|
+
readGuestId,
|
|
135
|
+
writeGuestId,
|
|
136
|
+
getOrCreateGuestId,
|
|
137
|
+
fetchState,
|
|
138
|
+
updateState,
|
|
139
|
+
appendGuestIdParam,
|
|
140
|
+
buildUrl,
|
|
141
|
+
mergeHeaders,
|
|
142
|
+
} from '@bffless/use-bff-state';
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## How It Works
|
|
146
|
+
|
|
147
|
+
- **Guest ID**: UUID v4 stored in `bff-guest-id` cookie. Lazy-created on first use. Appended to all requests as `?_bffGuestId=<uuid>`.
|
|
148
|
+
- **Fetching**: GET request on mount (unless `skip: true`). Response JSON becomes `data`.
|
|
149
|
+
- **Updating**: POST request with new state as JSON body. Response becomes new `data`.
|
|
150
|
+
- **Stale Refetch**: On window focus, refetches if `Date.now() - lastFetch > staleTime`.
|
|
151
|
+
- **Abort**: Requests aborted on unmount or when new fetch starts. AbortError silently ignored.
|
|
152
|
+
- **Credentials**: All requests use `credentials: 'include'` for cookie handling.
|
|
153
|
+
|
|
154
|
+
## Backend API Contract
|
|
155
|
+
|
|
156
|
+
The hook expects these endpoints:
|
|
157
|
+
|
|
158
|
+
**GET `{path}?_bffGuestId=<uuid>`** — Returns JSON state object
|
|
159
|
+
**POST `{path}?_bffGuestId=<uuid>`** — Body: JSON state. Returns confirmed/updated state.
|
|
160
|
+
|
|
161
|
+
These map to BFFless Pipeline proxy rules with `state-get` and `state-set` handler types.
|
|
162
|
+
|
|
163
|
+
## Type Safety
|
|
164
|
+
|
|
165
|
+
Generic type inferred from initial value or explicit:
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
interface CartState {
|
|
169
|
+
items: CartItem[];
|
|
170
|
+
total: number;
|
|
171
|
+
}
|
|
172
|
+
const { data, update } = useBffState<CartState>('/api/cart', { items: [], total: 0 });
|
|
173
|
+
// data is CartState, update expects CartState | (prev: CartState) => CartState
|
|
174
|
+
```
|