Skip to content
Open
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
12 changes: 12 additions & 0 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@
"tsc-esm-fix": "^2.20.26",
},
},
"packages/flipper-cloud": {
"name": "@flippercloud/flipper-cloud",
"version": "0.0.1",
"dependencies": {
"@flippercloud/flipper": "workspace:*",
},
"devDependencies": {
"tsc-esm-fix": "^2.20.26",
},
},
"packages/flipper-redis": {
"name": "@flippercloud/flipper-redis",
"version": "0.0.1",
Expand Down Expand Up @@ -207,6 +217,8 @@

"@flippercloud/flipper-cache": ["@flippercloud/flipper-cache@workspace:packages/flipper-cache"],

"@flippercloud/flipper-cloud": ["@flippercloud/flipper-cloud@workspace:packages/flipper-cloud"],

"@flippercloud/flipper-redis": ["@flippercloud/flipper-redis@workspace:packages/flipper-redis"],

"@flippercloud/flipper-sequelize": ["@flippercloud/flipper-sequelize@workspace:packages/flipper-sequelize"],
Expand Down
1 change: 1 addition & 0 deletions docs/adapters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Flipper TypeScript ships with an in-memory adapter out of the box and provides o
## Available Guides

- [Cache](./cache.md)
- [Cloud](./cloud.md)
- [Redis](./redis.md)
- [Sequelize](./sequelize.md)

Expand Down
267 changes: 267 additions & 0 deletions docs/adapters/cloud.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
# Cloud Adapter

The Flipper Cloud adapter provides seamless synchronization between a local adapter and Flipper Cloud. All reads go to the local adapter for low latency, while writes are dual-written to both local and cloud. A background poller keeps the local adapter in sync with cloud changes.

## Installation

```bash
bun add @flippercloud/flipper-cloud
```

> **Prerequisites:** Node.js ≥ 18, Bun ≥ 1.3.2, and a Flipper Cloud account with an environment token.

> **Note:** The Cloud adapter requires a local adapter for fast reads. By default it uses an in-memory adapter, but you can provide Redis, Sequelize, or any other adapter for distributed systems.

## Quick Start

```typescript
import { FlipperCloud } from '@flippercloud/flipper-cloud'

const flipper = await FlipperCloud({
token: process.env.FLIPPER_CLOUD_TOKEN!,
})

await flipper.enable('new-feature')
const isEnabled = await flipper.isEnabled('new-feature')
console.log(isEnabled) // true
```

## Configuration Options

### Custom URL

Override the Flipper Cloud URL (useful for development):

```typescript
const flipper = await FlipperCloud({
token: process.env.FLIPPER_CLOUD_TOKEN!,
url: 'http://localhost:5000/adapter',
})
```

### Custom Local Adapter

Use Redis or another adapter for the local cache:

```typescript
import { RedisAdapter } from '@flippercloud/flipper-redis'
import Redis from 'ioredis'

const redis = new Redis()
const localAdapter = new RedisAdapter({ client: redis })

const flipper = await FlipperCloud({
token: process.env.FLIPPER_CLOUD_TOKEN!,
localAdapter,
})
```

This is essential for distributed systems where multiple application servers need to share the same local cache.

### Sync Interval

Control how often the background poller syncs with cloud (default: 10 seconds):

```typescript
const flipper = await FlipperCloud({
token: process.env.FLIPPER_CLOUD_TOKEN!,
syncInterval: 30000, // 30 seconds
})
```

Longer intervals reduce network traffic but increase staleness. Shorter intervals keep data fresh but increase load.

### HTTP Timeout

Configure timeouts for HTTP requests to Flipper Cloud (default: 5000ms):

```typescript
const flipper = await FlipperCloud({
token: process.env.FLIPPER_CLOUD_TOKEN!,
timeout: 10000, // 10 seconds
})
```

## Architecture

The Cloud adapter uses three layers:

1. **HttpAdapter** - Communicates with Flipper Cloud API via HTTP
2. **DualWrite** - Writes to both local and cloud, reads from local only
3. **Poller** - Background sync that fetches latest state from cloud

```
┌─────────────┐
│ Flipper │
└──────┬──────┘
┌─────────────────┐
│ DualWrite │
└────┬────────┬───┘
│ │
│ └──────────────┐
│ │
▼ ▼
┌──────────┐ ┌────────────┐
│ Local │◄─────────┤ Poller │
│ Adapter │ sync └─────┬──────┘
└──────────┘ │
┌─────────────┐
│HttpAdapter │
│(Cloud API) │
└─────────────┘
```

**Read path:** Flipper → DualWrite → Local Adapter (fast)
**Write path:** Flipper → DualWrite → Local Adapter + HttpAdapter (dual write)
**Sync path:** Poller → HttpAdapter → Local Adapter (background)

## Advanced Usage

### Manual Sync

Force an immediate sync with cloud:

```typescript
await flipper.adapter.sync()
```

This is useful after important changes or for testing.

### Read-only Mode

Create a read-only cloud adapter for worker processes:

```typescript
const flipper = await FlipperCloud({
token: process.env.FLIPPER_CLOUD_TOKEN!,
readOnly: true,
})

// Reads work
const enabled = await flipper.isEnabled('feature')

// Writes throw
await flipper.enable('feature') // throws ReadOnlyError!
```

Read-only mode still syncs from cloud but prevents writes.

### Stop Polling

Stop the background sync (useful during shutdown):

```typescript
flipper.adapter.stopPolling()
```

The poller will clean up its interval timer and stop syncing.

## Best Practices

### Use a Distributed Local Adapter

For multi-server deployments, use Redis or another distributed adapter as your local cache:

```typescript
import { RedisAdapter } from '@flippercloud/flipper-redis'
import Redis from 'ioredis'

const redis = new Redis(process.env.REDIS_URL!)
const localAdapter = new RedisAdapter({ client: redis })

const flipper = await FlipperCloud({
token: process.env.FLIPPER_CLOUD_TOKEN!,
localAdapter,
syncInterval: 10000, // 10 seconds
})
```

This ensures all servers see the same feature flag state and reduces latency.

### Handle Initial Sync

The `FlipperCloud` function waits for the first sync before returning:

```typescript
try {
const flipper = await FlipperCloud({
token: process.env.FLIPPER_CLOUD_TOKEN!,
timeout: 10000,
})
console.log('Cloud adapter ready!')
} catch (error) {
console.error('Failed to sync with cloud:', error)
// Fall back to local-only mode or exit
}
```

If the initial sync fails, the promise rejects. Consider your fallback strategy.

### Monitor Sync Health

The poller emits sync events that you can monitor:

```typescript
const flipper = await FlipperCloud({
token: process.env.FLIPPER_CLOUD_TOKEN!,
})

// Listen for sync events (if instrumentation is configured)
// Implementation depends on your instrumenter setup
```

### Graceful Shutdown

Stop polling when your app shuts down:

```typescript
process.on('SIGTERM', () => {
flipper.adapter.stopPolling()
process.exit(0)
})
```

## Troubleshooting

### Sync Failures

If the poller fails to sync, it will retry on the next interval. Check:

1. Network connectivity to Flipper Cloud
2. Token validity
3. HTTP timeout settings

### Stale Data

If feature flags seem out of date:

1. Verify `syncInterval` isn't too long
2. Check that the poller is running
3. Force a manual sync: `await flipper.adapter.sync()`

### High Latency

All reads should be sub-millisecond since they hit the local adapter. If you see slow reads:

1. Check local adapter performance (Redis, Sequelize, etc.)
2. Ensure you're not accidentally reading from cloud directly

## Ruby Interoperability

The Cloud adapter is fully compatible with the Ruby Flipper Cloud adapter. Both use:

- Same HTTP API contract
- Same dual-write pattern
- Same polling mechanism
- Same ETag-based caching

You can mix Ruby and TypeScript services pointing to the same Flipper Cloud environment.

## See Also

- [Redis Adapter](./redis.md) - Recommended local adapter for distributed systems
- [Sequelize Adapter](./sequelize.md) - SQL-backed local adapter
- [Cache Adapter](./cache.md) - Layered caching strategies
40 changes: 40 additions & 0 deletions packages/flipper-cloud/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# @flippercloud/flipper-cloud

## 0.0.1

Initial release of Flipper Cloud adapter for TypeScript/JavaScript.

### Features

- **HttpAdapter** - Direct HTTP communication with Flipper Cloud API
- Full CRUD operations for features and gates
- ETag-based caching for efficient `getAll` operations
- Configurable timeouts and custom headers
- Read-only mode support
- Error handling with detailed messages

- **Poller** - Background synchronization mechanism
- Configurable polling interval (minimum 10 seconds)
- Automatic jitter to prevent thundering herd
- Graceful error handling and recovery
- Manual sync support

- **Cloud Integration** - High-level `FlipperCloud` function
- Automatic setup with dual-write pattern
- Local adapter for low-latency reads (Memory, Redis, Sequelize, etc.)
- Background sync keeps local cache up-to-date
- Initial sync before starting poller
- Read-only mode for worker processes

- **Ruby Interoperability** - Compatible with Ruby Flipper Cloud adapter
- Same HTTP API contract
- Same ETag caching behavior
- Same dual-write pattern

### Documentation

- Complete adapter guide in `docs/adapters/cloud.md`
- Quick start examples
- Configuration options reference
- Architecture diagrams
- Best practices for production deployments
21 changes: 21 additions & 0 deletions packages/flipper-cloud/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2025 Flipper Cloud

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
34 changes: 34 additions & 0 deletions packages/flipper-cloud/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# @flippercloud/flipper-cloud

Official Flipper Cloud adapter for TypeScript/JavaScript. This adapter provides seamless synchronization between your local adapter and Flipper Cloud, with low-latency reads and automatic background sync.

## Installation

```bash
bun add @flippercloud/flipper-cloud
```

**Note:** You'll also need a local adapter for fast reads. We recommend the memory adapter (built-in) for simple cases, or Redis/Sequelize for distributed systems.

## Quick usage

```typescript
import Flipper from '@flippercloud/flipper'
import { FlipperCloud } from '@flippercloud/flipper-cloud'

const flipper = await FlipperCloud({
token: 'your-cloud-token',
})

await flipper.enable('new-feature')
const isEnabled = await flipper.isEnabled('new-feature')
console.log(isEnabled) // true
```

## Docs

Looking for configuration options, local adapters, sync strategies, and best practices? See the full guide: [Cloud adapter guide](../../docs/adapters/cloud.md).

## License

MIT
Loading