Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: add getStore method #58

Merged
merged 19 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ module.exports = {
'max-lines-per-function': 'off',
'max-statements': 'off',
'node/no-missing-import': 'off',
'import/no-unresolved': 'off',
'n/no-missing-import': 'off',
'no-magic-numbers': 'off',
'no-shadow': 'off',
'no-use-before-define': 'off',
Expand Down
190 changes: 140 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,73 +13,163 @@ You can install `@netlify/blobs` via npm:
npm install @netlify/blobs
```

### Requirements

- Deno 1.30 and above or Node.js 16.0.0 and above
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we still have to support node 14? @eduardoboucas

- `fetch` in the global scope with a [fetch-compatible](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)
interface

## Usage

To use the blob store, import the module and create an instance of the `Blobs` class. The constructor accepts an object
with the following properties:
To start reading and writing data, you must first get a reference to a store using the `getStore` method.

| Property | Description | Required |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `authentication` | An object containing authentication credentials (see [Authentication](#authentication)) | **Yes** |
| `siteID` | The Netlify site ID | **Yes** |
| `context` | The [deploy context](https://docs.netlify.com/site-deploys/overview/#deploy-contexts) to use (defaults to `production`) | No |
| `fetcher` | An implementation of a [fetch-compatible](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) module for making HTTP requests (defaults to `globalThis.fetch`) | No |
This method takes an options object that lets you configure the store for different access modes.

### Example
### API access

```javascript
import assert from 'node:assert'
import { Blobs } from '@netlify/blobs'

const store = new Blobs({
authentication: {
token: 'YOUR_NETLIFY_AUTH_TOKEN',
},
siteID: 'YOUR_NETLIFY_SITE_ID',
})
You can interact with the blob store through the [Netlify API](https://docs.netlify.com/api/get-started). This is the
recommended method if you're looking for a strong-consistency way of accessing data, where latency is not mission
critical (since requests will always go to a non-distributed origin).

Create a store for API access by calling `getStore` with the following parameters:

await store.set('some-key', 'Hello!')
- `name` (string): Name of the store
- `siteID` (string): ID of the Netlify site
- `token` (string): [Personal access token](https://docs.netlify.com/api/get-started/#authentication) to access the
Netlify API
- `apiURL` (string): URL of the Netlify API (optional, defaults to `https://api.netlify.com`)

const item = await store.get('some-key')
```ts
import { getStore } from '@netlify/blobs'

assert.strictEqual(item, 'Hello!')
const store = getStore({
name: 'my-store',
siteID: 'MY_SITE_ID',
token: 'MY_TOKEN',
})

console.log(await store.get('some-key'))
```

### Authentication
### Edge access

You can also interact with the blob store using a distributed network that caches entries at the edge. This is the
recommended method if you're looking for fast reads across multiple locations, knowing that reads will be
eventually-consistent with a drift of up to 60 seconds.

Create a store for edge access by calling `getStore` with the following parameters:

Authentication with the blob storage is done in one of two ways:
- `name` (string): Name of the store
- `siteID` (string): ID of the Netlify site
- `token` (string): [Personal access token](https://docs.netlify.com/api/get-started/#authentication) to access the
Netlify API
- `edgeURL` (string): URL of the edge endpoint

- Using a [Netlify API token](https://docs.netlify.com/api/get-started/#authentication)
```ts
import { Buffer } from 'node:buffer'

```javascript
import { Blobs } from '@netlify/blobs'
import { getStore } from '@netlify/blobs'

const store = new Blobs({
authentication: {
token: 'YOUR_NETLIFY_API_TOKEN',
},
siteID: 'YOUR_NETLIFY_SITE_ID',
// Serverless function using the Lambda compatibility mode
export const handler = async (event, context) => {
const rawData = Buffer.from(context.clientContext.custom.blobs, 'base64')
const data = JSON.parse(rawData.toString('ascii'))
const store = getStore({
edgeURL: data.url,
name: 'my-store',
token: data.token,
siteID: 'MY_SITE_ID',
})
```

- Using a context object injected in Netlify Functions

```javascript
import { Blobs } from '@netlify/blobs'
import type { Handler, HandlerEvent, HandlerContext } from '@netlify/functions'

export const handler: Handler = async (event: HandlerEvent, context: HandlerContext) => {
const store = new Blobs({
authentication: {
contextURL: context.blobs.url,
token: context.blobs.token,
},
siteID: 'YOUR_NETLIFY_SITE_ID',
})
const item = await store.get('some-key')

return {
statusCode: 200,
body: item,
}
```
}
```

### Environment-based configuration
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move this first


Rather than explicitly passing the configuration context to the `getStore` method, it can be read from the execution
environment. This is particularly useful for setups where the configuration data is held by one system and the data
needs to be accessed in another system, with no direct communication between the two.

To do this, the system that holds the configuration data should set an environment variable called
`NETLIFY_BLOBS_CONTEXT` with a Base64-encoded, JSON-stringified representation of an object with the following
properties:

- `apiURL` (optional) or `edgeURL`: URL of the Netlify API (for [API access](#api-access)) or the edge endpoint (for
[Edge access](#edge-access))
- `token`: Access token for the corresponding access mode
- `siteID`: ID of the Netlify site

This data is automatically populated by Netlify in the execution environment for both serverless and edge functions.

With this in place, the `getStore` method can be called just with the store name. No configuration object is required,
since it'll be read from the environment.

```ts
import { getStore } from '@netlify/blobs'

const store = getStore('my-store')

console.log(await store.get('my-key'))
```

### Deploy scope

By default, stores exist at the site level, which means that data can be read and written across different deploys and
deploy contexts. Users are responsible for managing that data, since the platform doesn't have enough information to
know whether an item is still relevant or safe to delete.

But sometimes it's useful to have data pegged to a specific deploy, and shift to the platform the responsibility of
managing that data — keep it as long as the deploy is around, and wipe it if the deploy is deleted.

You can opt-in to this behavior by supplying a `deployID` instead of a `name` to the `getStore` method.

```ts
import { assert } from 'node:assert'

import { getStore } from '@netlify/blobs'

// Using API access
const store1 = getStore({
deployID: 'MY_DEPLOY_ID',
token: 'MY_API_TOKEN',
})

await store1.set('my-key', 'my value')

// Using environment-based configuration
const store2 = getStore({
deployID: 'MY_DEPLOY_ID',
})

assert.equal(await store2.get('my-key'), 'my value')
```

### Custom `fetch`

The client uses [the web platform `fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) to make HTTP
calls. By default, it will use any globally-defined instance of `fetch`, but you can choose to provide your own.

You can do this by supplying a `fetch` property to the `getStore` method.

```ts
import { fetch } from 'whatwg-fetch'

import { getStore } from '@netlify/blobs'

const store = getStore({
fetch,
name: 'my-store',
})

console.log(await store.get('my-key'))
```

## API
## Store API reference

### `get(key: string, { type: string }): Promise<any>`

Expand Down
59 changes: 0 additions & 59 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,7 @@
"esbuild": "^0.19.0",
"husky": "^8.0.0",
"node-fetch": "^3.3.1",
"p-map": "^6.0.0",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yay 🥇

"semver": "^7.5.3",
"tmp-promise": "^3.0.3",
"tsup": "^7.2.0",
"typescript": "^5.0.0",
"vitest": "^0.34.0"
Expand Down
Loading