Skip to content
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
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ A TypeScript library implementing RFC 8621 JMAP for Mail using Effect-TS.

1. **Services via Context.Tag**: Services are defined using `Context.Tag`
2. **Layers for dependency injection**: Services are provided via `Layer`
3. **Effect generators**: Use `Effect.gen(function* () { ... })` for async operations
3. **Effect.gen vs Effect.flatMap/pipe**:
- Use `Effect.gen(function* () { ... })` for complex flows with intermediate values or multiple steps
- Use `Effect.flatMap` / `.pipe()` for simple one-step service calls (e.g., `Effect.flatMap(Service, svc => svc.method(args))`)
4. **Schema validation**: Effect Schema for type-safe parsing
5. **Branded types**: Use `Schema.brand()` for type safety (e.g., `Id`, `State`)

Expand Down
43 changes: 33 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,34 @@ pnpm add effect-jmap

## Quick Start

The easiest way to get started is with the `JMAPLive` function, which includes everything you need:
The simplest way to use effect-jmap is with the Promise-based client — no Effect knowledge required:

```typescript
import { createJMAPClient } from 'effect-jmap'

const client = await createJMAPClient(
'https://api.fastmail.com/jmap/session',
'your-bearer-token-here'
)

// Account ID is auto-discovered
const mailboxes = await client.mailbox.getAll()
console.log(`Found ${mailboxes.length} mailboxes`)

// Query emails
const emails = await client.email.query({
accountId: client.accountId,
filter: { inMailbox: 'inbox-id' },
limit: 10,
})

// Clean up when done
await client.dispose()
```

## Using with Effect

For full control using Effect-TS layers and services:

```typescript
import { Effect } from 'effect'
Expand All @@ -27,25 +54,21 @@ import {

// Create a complete layer with one function call
const mainLayer = JMAPLive(
'https://api.fastmail.com/jmap/session', // Your JMAP session URL
'your-bearer-token-here' // Your authentication token
'https://api.fastmail.com/jmap/session',
'your-bearer-token-here'
)

// Use the services
const program = Effect.gen(function* () {
// Get session to find account ID
const jmapClient = yield* JMAPClientService
const sessionInfo = yield* jmapClient.getSession
const accountId = Object.keys(sessionInfo.accounts)[0]

// Use mailbox service
const mailboxService = yield* MailboxService
const mailboxes = yield* mailboxService.getAll(accountId)

console.log(`Found ${mailboxes.length} mailboxes`)
})

// Run the program
Effect.runPromise(program.pipe(Effect.provide(mainLayer)))
```

Expand All @@ -57,7 +80,7 @@ For fine-grained control over the HTTP client, JMAP client configuration, or spe
import { Effect, Layer } from 'effect'
import { NodeHttpClient } from '@effect/platform-node'
import {
createJMAPClient,
createJMAPClientLayer,
defaultConfig,
AppLive,
MailboxService,
Expand All @@ -67,7 +90,7 @@ import {
// Option 1: Manual layer composition
const mainLayer = Layer.mergeAll(
NodeHttpClient.layerUndici,
createJMAPClient(sessionUrl, bearerToken),
createJMAPClientLayer(sessionUrl, bearerToken),
AppLive // Includes all services + IdGenerator
)

Expand All @@ -81,7 +104,7 @@ const customConfig = {

const customLayer = Layer.mergeAll(
NodeHttpClient.layerUndici,
createJMAPClientWithConfig(customConfig),
createJMAPClientLayerWithConfig(customConfig),
AppLive
)

Expand Down
6 changes: 5 additions & 1 deletion src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export interface JMAPClientConfig {
readonly retryDelay?: number
readonly maxBatchSize?: number
readonly enableRequestLogging?: boolean
/** Pre-fetched session to avoid the initial HTTP round-trip to the session endpoint. */
readonly initialSession?: Session
}

/**
Expand Down Expand Up @@ -70,7 +72,9 @@ interface SessionState {
* Live implementation of JMAP Client
*/
const makeJMAPClientLive = (config: JMAPClientConfig): JMAPClientInterface => {
let sessionState: SessionState | null = null
let sessionState: SessionState | null = config.initialSession
? { session: config.initialSession, lastUpdated: new Date() }
: null

const defaultHeaders = {
'Content-Type': 'application/json',
Expand Down
15 changes: 13 additions & 2 deletions src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ export {
// Live layer factory
export {
JMAPClientLive as JMAPClientLiveImpl,
createJMAPClient,
createJMAPClientWithConfig,
createJMAPClientLayer,
createJMAPClientLayerWithConfig,
type JMAPClientConfig as JMAPConfig,
} from './live.ts'

Expand Down Expand Up @@ -52,3 +52,14 @@ export { JMAP_CAPABILITIES, CAPABILITY_SETS, type JMAPCapability, type Capabilit

// Response utilities
export { extractMethodResponse } from './response-utils.ts'

// Promise-based client wrapper
export {
createJMAPClient,
createJMAPClientWithConfig,
createJMAPClientFromLayer,
type JMAPClientWrapper,
type MailboxNamespace,
type EmailNamespace,
type SubmissionNamespace,
} from './wrapper.ts'
4 changes: 2 additions & 2 deletions src/client/live.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ export type { JMAPClientConfig }
/**
* Convenience function to create a live JMAP client layer with default config
*/
export const createJMAPClient = (sessionUrl: string, bearerToken: string): Layer.Layer<JMAPClientService, never, HttpClient.HttpClient> =>
export const createJMAPClientLayer = (sessionUrl: string, bearerToken: string): Layer.Layer<JMAPClientService, never, HttpClient.HttpClient> =>
JMAPClientLiveImpl(defaultConfig(sessionUrl, bearerToken))

/**
* Convenience function to create a live JMAP client layer with custom config
*/
export const createJMAPClientWithConfig = (config: JMAPClientConfig): Layer.Layer<JMAPClientService, never, HttpClient.HttpClient> =>
export const createJMAPClientLayerWithConfig = (config: JMAPClientConfig): Layer.Layer<JMAPClientService, never, HttpClient.HttpClient> =>
JMAPClientLiveImpl(config)
Loading