# Promise-Based API Design Specification

**Date:** 2026-03-25
**Project:** react-zlib-js
**Feature:** Promise-based API with backward compatibility
**Status:** Design Phase

---

## Overview

Add Promise support to all asynchronous convenience methods in react-zlib-js, enabling modern `async/await` patterns while maintaining full backward compatibility with existing callback-based code.

### Goals
- ✅ Enable `async/await` syntax for all async methods
- ✅ Maintain 100% backward compatibility with callback API
- ✅ Follow Node.js conventions for promise/callback detection
- ✅ Provide TypeScript definitions with proper overloads
- ✅ Comprehensive test coverage

---

## Design Approach

### Core Mechanism: Callback Detection

All async convenience methods will detect whether a callback is provided:

- **No callback provided** → Return a Promise
- **Callback provided** → Use callback mode (return undefined)
- **Both provided** → Callback takes precedence, promise ignored

```javascript
// Promise mode
const compressed = await zlib.gzip(data, options);

// Callback mode (backward compatible)
zlib.gzip(data, options, (err, result) => {
  if (err) throw err;
  console.log(result);
});

// Works with or without options
const compressed1 = await zlib.gzip(data);
const compressed2 = await zlib.gzip(data, { level: 9 });

zlib.gzip(data, (err, result) => {});
zlib.gzip(data, { level: 9 }, (err, result) => {});
```

### Error Handling

**Callback mode (existing):**
```javascript
zlib.gzip(data, (err, result) => {
  if (err) console.error(err);
  else console.log(result);
});
```

**Promise mode (new):**
```javascript
try {
  const result = await zlib.gzip(data);
} catch (err) {
  console.error(err);
}
```

---

## Scope

### Methods Receiving Promise Support

**All async convenience methods:**

| Category | Methods |
|----------|---------|
| Gzip | `gzip`, `gunzip` |
| Deflate | `deflate`, `inflate` |
| DeflateRaw | `deflateRaw`, `inflateRaw` |
| Auto-detect | `unzip` |
| Brotli | `brotliCompress`, `brotliDecompress` |

**Total: 10 methods updated**

### Methods NOT Updated

- **Sync methods** (`gzipSync`, `deflateSync`, etc.) — Synchronous by nature, promises not applicable
- **Stream classes** (`Gzip`, `Deflate`, etc.) — Already event-based, can be promisified by users if needed
- **CRC32** (`crc32`) — Simple utility, not async
- **Factory functions** (`createGzip`, etc.) — Return stream instances, not promises

---

## Implementation Details

### Code Pattern

Each async method will be wrapped to detect callback presence:

```javascript
// Original implementation
exports.gzip = function (buffer, opts, callback) {
  if (typeof opts === 'function') {
    callback = opts;
    opts = {};
  }
  return zlibBuffer(new Gzip(opts), buffer, callback);
};

// Updated implementation
exports.gzip = function (buffer, opts, callback) {
  if (typeof opts === 'function') {
    callback = opts;
    opts = {};
  }

  // Return promise if no callback
  if (!callback) {
    return new Promise((resolve, reject) => {
      zlibBuffer(new Gzip(opts), buffer, (err, result) => {
        if (err) reject(err);
        else resolve(result);
      });
    });
  }

  // Use callback mode (existing behavior)
  return zlibBuffer(new Gzip(opts), buffer, callback);
};
```

### Implementation Steps

1. Update all 10 async convenience methods in `src/zlib.js`
2. Add TypeScript definition overloads in `index.d.ts` (new file)
3. Add comprehensive tests in `test.js`
4. Update README with promise examples
5. No changes to core compression logic (`src/binding.js`, `src/brotli-binding.js`)

---

## TypeScript Definitions

New `index.d.ts` file with proper overloads for callback/promise modes.

### Example for `gzip`:

```typescript
// Promise mode
export function gzip(
  buffer: string | Buffer | Uint8Array,
  options?: ZlibOptions
): Promise<Buffer>;

// Callback mode
export function gzip(
  buffer: string | Buffer | Uint8Array,
  callback: (error: Error | null, result?: Buffer) => void
): void;

export function gzip(
  buffer: string | Buffer | Uint8Array,
  options: ZlibOptions,
  callback: (error: Error | null, result?: Buffer) => void
): void;
```

All 10 methods receive similar overloads.

---

## Testing Strategy

### Test Categories

**1. Promise Resolution (8 tests)**
- Each of 8 methods (excluding brotli separately) returns resolved promise with valid compressed data
- Compression output matches `*Sync` equivalent

**2. Promise Rejection (8 tests)**
- Invalid input (null, undefined, wrong type) rejects with appropriate error
- All algorithms handle errors consistently

**3. Options Parameter (3 tests)**
- Promise with options: `await gzip(data, { level: 9 })`
- Promise without options: `await gzip(data)`
- Options object properly passed through

**4. Backward Compatibility (8 tests)**
- Callback mode still works for all methods
- Callback called with `(err, result)`
- Signature: `method(buffer, options, callback)`
- Signature: `method(buffer, callback)`

**5. Brotli Methods (4 tests)**
- `brotliCompress` promise/callback modes
- `brotliDecompress` promise/callback modes
- Brotli-specific options work with promises

**6. Mixed Scenarios (4 tests)**
- Callback provided (promise ignored, no rejection if callback errors)
- No callback, no options: `await method(buffer)`
- Callback without options: `method(buffer, callback)`

**Total: ~35 new tests**

---

## Backward Compatibility

✅ **100% backward compatible**

- Existing callback code: No changes needed, works exactly as before
- Callback detection: `typeof callback === 'function'` check is reliable
- No breaking changes to API surface
- No changes to compression behavior or output

---

## Examples

### Before (callback-only)
```javascript
const zlib = require('react-zlib-js');

zlib.gzip('Hello, World!', (err, compressed) => {
  if (err) throw err;
  zlib.gunzip(compressed, (err, decompressed) => {
    if (err) throw err;
    console.log(decompressed.toString());
  });
});
```

### After (with promises, same as before also still works)
```javascript
const zlib = require('react-zlib-js');

async function compress() {
  try {
    const compressed = await zlib.gzip('Hello, World!');
    const decompressed = await zlib.gunzip(compressed);
    console.log(decompressed.toString());
  } catch (err) {
    console.error('Error:', err.message);
  }
}

compress();
```

### Mixed (both patterns work in same code)
```javascript
// Promise pattern
const compressed = await zlib.gzip(data);

// Callback pattern (still works)
zlib.deflate(data, (err, result) => {
  if (!err) console.log(result);
});

// With options
const fast = await zlib.gzip(data, { level: 1 });
const best = await zlib.gzip(data, { level: 9 });

zlib.brotliCompress(data, { params: { /* ... */ } }, (err, result) => {});
```

---

## Performance Considerations

- **Overhead**: Minimal — one additional `typeof` check per call when no callback
- **Promise creation**: Only when promise mode used (lazy, not on every call)
- **Backward compatibility**: Zero overhead for callback mode

---

## Dependencies

No new dependencies required. Uses native JavaScript `Promise` (available in all target environments: Node.js 10+, modern browsers, React Native).

---

## Files to Modify

| File | Changes |
|------|---------|
| `src/zlib.js` | Add callback detection + promise wrapping to 10 methods |
| `index.d.ts` | New file with TypeScript definitions and overloads |
| `test.js` | Add ~35 new test cases |
| `README.md` | Add promise examples and usage documentation |
| `package.json` | No changes (no new dependencies) |

---

## Migration Path

No migration required. This is a purely additive feature:

- Existing code: Works as-is
- New code: Can use `async/await`
- Mixed codebases: Can use both patterns in same application

---

## Success Criteria

✅ All 10 async methods support promises
✅ 100% backward compatibility maintained
✅ ~35 tests passing (promise and callback modes)
✅ TypeScript definitions provided
✅ Documentation updated with examples
✅ No performance degradation
✅ No new dependencies

---

## Next Steps

1. ✅ Design approval (this document)
2. → Write implementation plan
3. → Implement changes
4. → Run test suite
5. → Code review
6. → Publish v2.1.0

