@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.
@@ -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
+ ```