@netlify/agent-runner-cli 1.69.2 → 1.70.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/bin-local.js +36 -36
- package/dist/bin.js +38 -38
- package/dist/index.js +39 -39
- package/dist/skills/netlify-blobs/SKILL.md +396 -0
- package/dist/skills/netlify-edge-functions/SKILL.md +478 -0
- package/dist/skills/netlify-image-cdn/SKILL.md +244 -0
- package/dist/skills/netlify-inference/SKILL.md +1 -33
- package/dist/skills/netlify-serverless/SKILL.md +478 -0
- package/package.json +1 -1
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: netlify-blobs
|
|
3
|
+
description: Store and retrieve data using Netlify Blobs key-value storage. Use when implementing file storage, JSON persistence, caching, or any data that needs to persist between requests without an external database.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Netlify Blobs
|
|
7
|
+
|
|
8
|
+
Netlify Blobs is a zero-configuration key-value store optimized for frequent reads and infrequent writes. It provides
|
|
9
|
+
persistent storage scoped to your site with automatic provisioning and access control. The API will be the same across
|
|
10
|
+
all compute types (serverless functions, edge functions, build plugins, etc.).
|
|
11
|
+
|
|
12
|
+
Requires Fetch API support (Node.js 18+ recommended).
|
|
13
|
+
|
|
14
|
+
Prefer using Netlify Blobs over creating a new database unless the user needs relational structures, complex search, or
|
|
15
|
+
explicitly asks for a different tool.
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
Install the package and use `getStore` to start storing data:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @netlify/blobs
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { getStore } from '@netlify/blobs'
|
|
27
|
+
|
|
28
|
+
const store = getStore('my-store')
|
|
29
|
+
|
|
30
|
+
// Write
|
|
31
|
+
await store.set('greeting', 'Hello, world!')
|
|
32
|
+
await store.setJSON('config', { theme: 'dark', lang: 'en' })
|
|
33
|
+
|
|
34
|
+
// Read
|
|
35
|
+
const greeting = await store.get('greeting', { type: 'text' })
|
|
36
|
+
const config = await store.get('config', { type: 'json' })
|
|
37
|
+
|
|
38
|
+
// Delete
|
|
39
|
+
await store.delete('greeting')
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## API Reference
|
|
43
|
+
|
|
44
|
+
### Global Functions
|
|
45
|
+
|
|
46
|
+
#### `getStore(name: string | StoreOptions): Store`
|
|
47
|
+
|
|
48
|
+
Returns a store instance for a site-level namespace. Data persists across deploys.
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { getStore } from '@netlify/blobs'
|
|
52
|
+
|
|
53
|
+
// Simple usage
|
|
54
|
+
const store = getStore('my-store')
|
|
55
|
+
|
|
56
|
+
// With options
|
|
57
|
+
const store = getStore({
|
|
58
|
+
name: 'my-store',
|
|
59
|
+
consistency: 'strong',
|
|
60
|
+
})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**`StoreOptions`:**
|
|
64
|
+
|
|
65
|
+
| Property | Type | Description |
|
|
66
|
+
|----------|------|-------------|
|
|
67
|
+
| `name` | `string` | Store namespace (required) |
|
|
68
|
+
| `consistency` | `'strong' \| 'eventual'` | Consistency model (default: `'eventual'`) |
|
|
69
|
+
|
|
70
|
+
ONLY add the options argument if the user needs strong consistency.
|
|
71
|
+
|
|
72
|
+
#### `getDeployStore(options?: DeployStoreOptions): Store`
|
|
73
|
+
|
|
74
|
+
Returns a store scoped to a specific deploy. Data is isolated per deploy and immutable after deploy finishes.
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { getDeployStore } from '@netlify/blobs'
|
|
78
|
+
|
|
79
|
+
const store = getDeployStore()
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Use deploy stores for data that should be tied to a specific deploy snapshot (e.g., pre-rendered page data, build
|
|
83
|
+
artifacts). ONLY add the options argument if the user needs strong consistency.
|
|
84
|
+
|
|
85
|
+
#### `listStores(options?: ListStoresOptions): Promise<{ stores: string[] }>`
|
|
86
|
+
|
|
87
|
+
Lists all site-level store names. Supports pagination for sites with many stores.
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { listStores } from '@netlify/blobs'
|
|
91
|
+
|
|
92
|
+
const { stores } = await listStores()
|
|
93
|
+
console.log(stores) // ['my-store', 'uploads', ...]
|
|
94
|
+
|
|
95
|
+
// Paginated iteration
|
|
96
|
+
for await (const page of listStores({ paginate: true })) {
|
|
97
|
+
console.log(page.stores)
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
DO NOT pass options unless paginating.
|
|
102
|
+
|
|
103
|
+
### Store Methods
|
|
104
|
+
|
|
105
|
+
THESE ARE THE ONLY STORE METHODS. DO NOT MAKE UP NEW ONES.
|
|
106
|
+
|
|
107
|
+
#### `set(key: string, value: string | ArrayBuffer | Blob, options?: SetOptions): Promise<void>`
|
|
108
|
+
|
|
109
|
+
Stores a value. Supports strings, binary data (ArrayBuffer), and Blob objects.
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
await store.set('my-key', 'my-value')
|
|
113
|
+
await store.set('binary-key', new Uint8Array([1, 2, 3]))
|
|
114
|
+
await store.set('with-meta', 'value', {
|
|
115
|
+
metadata: { contentType: 'text/plain', author: 'system' },
|
|
116
|
+
})
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**`SetOptions`:**
|
|
120
|
+
|
|
121
|
+
| Property | Type | Description |
|
|
122
|
+
|----------|------|-------------|
|
|
123
|
+
| `metadata` | `Record<string, string>` | Custom metadata (max 64 KB total) |
|
|
124
|
+
|
|
125
|
+
NEVER add metadata unless the user explicitly instructs you to.
|
|
126
|
+
|
|
127
|
+
#### `setJSON(key: string, value: any, options?: SetOptions): Promise<void>`
|
|
128
|
+
|
|
129
|
+
Stores a JSON-serializable value. Automatically serializes with `JSON.stringify`.
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
await store.setJSON('config', { theme: 'dark', lang: 'en' })
|
|
133
|
+
await store.setJSON('users/123', { name: 'Alice', role: 'admin' })
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
NEVER add metadata unless the user explicitly instructs you to.
|
|
137
|
+
|
|
138
|
+
#### `get(key: string, options?: GetOptions): Promise<string | Blob | object | ArrayBuffer | null>`
|
|
139
|
+
|
|
140
|
+
Retrieves a value by key. Returns `null` if the key does not exist.
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// As text (default)
|
|
144
|
+
const text = await store.get('my-key', { type: 'text' })
|
|
145
|
+
|
|
146
|
+
// As JSON
|
|
147
|
+
const data = await store.get('config', { type: 'json' })
|
|
148
|
+
|
|
149
|
+
// As ArrayBuffer
|
|
150
|
+
const binary = await store.get('binary-key', { type: 'arrayBuffer' })
|
|
151
|
+
|
|
152
|
+
// As Blob
|
|
153
|
+
const blob = await store.get('image', { type: 'blob' })
|
|
154
|
+
|
|
155
|
+
// As ReadableStream
|
|
156
|
+
const stream = await store.get('large-file', { type: 'stream' })
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**`GetOptions.type`:** `'text'` | `'json'` | `'arrayBuffer'` | `'blob'` | `'stream'`
|
|
160
|
+
|
|
161
|
+
ALWAYS use `store.get('key', { type: 'json' })` instead of `JSON.parse(await store.get('key'))`. NEVER add the options
|
|
162
|
+
argument unless you need a specific type.
|
|
163
|
+
|
|
164
|
+
#### `getWithMetadata(key: string, options?: GetOptions): Promise<{ data, metadata } | null>`
|
|
165
|
+
|
|
166
|
+
Retrieves a value along with its custom metadata.
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
const result = await store.getWithMetadata('my-key', { type: 'text' })
|
|
170
|
+
if (result) {
|
|
171
|
+
console.log(result.data) // the value
|
|
172
|
+
console.log(result.metadata) // { contentType: '...', ... }
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
NEVER add the second getOpts arg unless you need an explicit type or have an etag to check against. AVOID adding it
|
|
177
|
+
unless it's reliably available but IF an etag is provided, it will only return the blob if the etag is different than
|
|
178
|
+
what's stored.
|
|
179
|
+
|
|
180
|
+
#### `getMetadata(key: string): Promise<{ metadata: Record<string, string> } | null>`
|
|
181
|
+
|
|
182
|
+
Retrieves only the metadata for a key, without downloading the value. Useful for checking existence or reading metadata
|
|
183
|
+
on large blobs.
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
const result = await store.getMetadata('my-key')
|
|
187
|
+
if (result) {
|
|
188
|
+
console.log(result.metadata)
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
NEVER add the second getOpts arg unless you need an explicit type or have an etag to check against. AVOID adding it
|
|
193
|
+
unless it's reliably available but IF an etag is provided, it will only return the blob if the etag is different than
|
|
194
|
+
what's stored.
|
|
195
|
+
|
|
196
|
+
#### `list(options?: ListOptions): Promise<{ blobs: ListEntry[], directories: string[] }>`
|
|
197
|
+
|
|
198
|
+
Lists entries in the store. By default retrieves all pages. Use `paginate: true` for an `AsyncIterable`.
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
// List all
|
|
202
|
+
const { blobs } = await store.list()
|
|
203
|
+
|
|
204
|
+
// With prefix filter
|
|
205
|
+
const { blobs } = await store.list({ prefix: 'users/' })
|
|
206
|
+
|
|
207
|
+
// With hierarchical browsing
|
|
208
|
+
const { blobs, directories } = await store.list({
|
|
209
|
+
prefix: 'uploads/',
|
|
210
|
+
directories: true,
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
// Paginated iteration
|
|
214
|
+
for await (const page of store.list({ paginate: true })) {
|
|
215
|
+
for (const blob of page.blobs) {
|
|
216
|
+
console.log(blob.key)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**`ListOptions`:**
|
|
222
|
+
|
|
223
|
+
| Property | Type | Description |
|
|
224
|
+
|----------|------|-------------|
|
|
225
|
+
| `prefix` | `string` | Filter keys by prefix |
|
|
226
|
+
| `directories` | `boolean` | Group by `/` delimiter |
|
|
227
|
+
| `paginate` | `boolean` | Return `AsyncIterable` for page-by-page iteration |
|
|
228
|
+
| `cursor` | `string` | Pagination cursor from previous response |
|
|
229
|
+
| `limit` | `number` | Max entries per page |
|
|
230
|
+
|
|
231
|
+
**`ListEntry`:**
|
|
232
|
+
|
|
233
|
+
| Property | Type | Description |
|
|
234
|
+
|----------|------|-------------|
|
|
235
|
+
| `key` | `string` | Blob key |
|
|
236
|
+
| `etag` | `string` | Content hash |
|
|
237
|
+
|
|
238
|
+
#### `delete(key: string): Promise<void>`
|
|
239
|
+
|
|
240
|
+
Deletes a single blob. No error if the key does not exist.
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
await store.delete('my-key')
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Storage Scopes
|
|
247
|
+
|
|
248
|
+
### Site-Level Stores (`getStore`)
|
|
249
|
+
|
|
250
|
+
Data persists across all deploys and is shared by all functions and builds for the site. Use for user data, application
|
|
251
|
+
state, and any data that should survive redeployments.
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
import { getStore } from '@netlify/blobs'
|
|
255
|
+
|
|
256
|
+
const store = getStore('user-uploads')
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Deploy-Specific Stores (`getDeployStore`)
|
|
260
|
+
|
|
261
|
+
Data is scoped to a single deploy. Use for build-generated data, pre-rendered content, or data that should be
|
|
262
|
+
immutable after deploy. Build plugins and file-based uploads must write to deploy-specific stores.
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import { getDeployStore } from '@netlify/blobs'
|
|
266
|
+
|
|
267
|
+
const store = getDeployStore()
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**Production isolation pattern:** Use a global store in production and deploy store otherwise to prevent non-production
|
|
271
|
+
data from polluting global stores:
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
import { getStore, getDeployStore } from '@netlify/blobs'
|
|
275
|
+
|
|
276
|
+
function getBlobStore(...storeOptions) {
|
|
277
|
+
if (Netlify.context?.deploy.context === 'production') {
|
|
278
|
+
return getStore(...storeOptions)
|
|
279
|
+
}
|
|
280
|
+
return getDeployStore(...storeOptions)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const store = getBlobStore('app-data')
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## Consistency Models
|
|
287
|
+
|
|
288
|
+
| Model | Default | Propagation | Use Case |
|
|
289
|
+
|-------|---------|-------------|----------|
|
|
290
|
+
| `eventual` | Yes | Up to 60 seconds | High-read workloads, caching |
|
|
291
|
+
| `strong` | No | Immediate | Critical data, counters |
|
|
292
|
+
|
|
293
|
+
Last-write-wins: concurrent writes to the same key result in the final write persisting. Add object-locking mechanisms
|
|
294
|
+
if you need concurrency guarantees.
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
// Strong consistency when you need immediate read-after-write
|
|
298
|
+
const store = getStore({ name: 'counters', consistency: 'strong' })
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## File-Based Uploads
|
|
302
|
+
|
|
303
|
+
Deploy blobs by placing files in the `.netlify/blobs/deploy/` directory at build time. These are written to a
|
|
304
|
+
deploy-specific store automatically.
|
|
305
|
+
|
|
306
|
+
```
|
|
307
|
+
.netlify/blobs/deploy/
|
|
308
|
+
my-key.txt # Value stored with key "my-key.txt"
|
|
309
|
+
$my-key.txt.json # Optional metadata for "my-key.txt"
|
|
310
|
+
nested/path/data.json # Key is "nested/path/data.json"
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**Metadata pattern:** Create a `$filename.json` sibling file:
|
|
314
|
+
|
|
315
|
+
```json
|
|
316
|
+
{
|
|
317
|
+
"contentType": "text/plain",
|
|
318
|
+
"author": "build-pipeline"
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Read file-based blobs in functions using `getDeployStore`:
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
import { getDeployStore } from '@netlify/blobs'
|
|
326
|
+
|
|
327
|
+
const store = getDeployStore()
|
|
328
|
+
const data = await store.get('my-key.txt', { type: 'text' })
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## Limits
|
|
332
|
+
|
|
333
|
+
| Resource | Limit |
|
|
334
|
+
|----------|-------|
|
|
335
|
+
| Store name | 64 bytes |
|
|
336
|
+
| Key | 600 bytes |
|
|
337
|
+
| Object size | 5 GB |
|
|
338
|
+
| Metadata per object | 64 KB |
|
|
339
|
+
| Stores per site | Unlimited |
|
|
340
|
+
| Objects per store | Unlimited |
|
|
341
|
+
|
|
342
|
+
## Common Errors & Solutions
|
|
343
|
+
|
|
344
|
+
### "Store not found" or empty reads
|
|
345
|
+
|
|
346
|
+
**Cause:** Store name mismatch or reading from wrong scope.
|
|
347
|
+
|
|
348
|
+
**Fix:**
|
|
349
|
+
|
|
350
|
+
1. Verify the store name matches exactly (case-sensitive)
|
|
351
|
+
2. Use `listStores()` to confirm the store exists
|
|
352
|
+
3. Ensure you're using `getStore` for site-level data and `getDeployStore` for deploy-scoped data
|
|
353
|
+
|
|
354
|
+
### "The environment has not been configured to use Netlify Blobs"
|
|
355
|
+
|
|
356
|
+
**Cause:** Missing Blobs environment configuration in local development.
|
|
357
|
+
|
|
358
|
+
**Fix:**
|
|
359
|
+
|
|
360
|
+
1. Install the framework plugin (`@netlify/vite-plugin`, `@netlify/nuxt`, or `@netlify/vite-plugin-tanstack-start`)
|
|
361
|
+
2. Deploy to Netlify or use the Vite plugin for local env configuration
|
|
362
|
+
3. Ensure the site has at least one production deploy
|
|
363
|
+
|
|
364
|
+
This does NOT apply to legacy V1 functions which require manual siteID/token configuration.
|
|
365
|
+
|
|
366
|
+
### "Unauthorized" or permission errors
|
|
367
|
+
|
|
368
|
+
**Cause:** Function is not running in a Netlify environment.
|
|
369
|
+
|
|
370
|
+
**Fix:**
|
|
371
|
+
|
|
372
|
+
1. Deploy to Netlify
|
|
373
|
+
2. Ensure the site has at least one production deploy
|
|
374
|
+
3. Confirm the function is deployed to Netlify (not a third-party host)
|
|
375
|
+
|
|
376
|
+
### Stale reads after writing
|
|
377
|
+
|
|
378
|
+
**Cause:** Default eventual consistency model (up to 60s propagation).
|
|
379
|
+
|
|
380
|
+
**Fix:**
|
|
381
|
+
|
|
382
|
+
1. Use strong consistency if immediate reads are required:
|
|
383
|
+
```typescript
|
|
384
|
+
const store = getStore({ name: 'my-store', consistency: 'strong' })
|
|
385
|
+
```
|
|
386
|
+
2. Note that strong consistency has higher latency
|
|
387
|
+
|
|
388
|
+
### Large object upload timeouts
|
|
389
|
+
|
|
390
|
+
**Cause:** Object exceeds function timeout or network limits.
|
|
391
|
+
|
|
392
|
+
**Fix:**
|
|
393
|
+
|
|
394
|
+
1. Verify object is under 5 GB
|
|
395
|
+
2. For large uploads, use file-based uploads via `.netlify/blobs/deploy/` at build time
|
|
396
|
+
3. For function uploads, ensure function timeout is sufficient (background functions have 15-min timeout)
|