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
35 changes: 31 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,24 +36,51 @@ npm install garde-fou
```typescript
import { GardeFou } from 'garde-fou';

const guard = new GardeFou({ max_calls: 5, on_violation_max_calls: 'warn' });
const result = guard.call(yourApiFunction, "your", "arguments");
const guard = GardeFou({ max_calls: 5, on_violation_max_calls: 'warn' });

// Two equivalent calling patterns:
// 1. Direct call (Python-like syntax)
const result = guard(yourApiFunction, "your", "arguments");

// 2. Explicit method call
const result2 = guard.call(yourApiFunction, "your", "arguments");

// For async functions
const asyncResult = await guard.callAsync(yourAsyncApiFunction, "args");
```

### Ruby
```bash
gem install garde_fou
```

```ruby
require 'gardefou'

guard = Gardefou::GardeFou.new(max_calls: 5, on_violation_max_calls: 'warn')

# Three equivalent calling patterns:
# 1. Method call
result = guard.call(your_api_method, "your", "arguments")

# 2. Bracket syntax (Ruby callable style)
result = guard[your_api_method, "your", "arguments"]

# 3. Protect method (semantic)
result = guard.protect(your_api_method, "your", "arguments")
```

## Repository Layout

- **[python/](python/)** – ✅ **Ready!** Full Python package published to PyPI
- **[js/](js/)** – ✅ **Ready!** TypeScript/JavaScript package with full type support
- **[ruby/](ruby/)** – 🚧 Ruby gem (in development)
- **[ruby/](ruby/)** – ✅ **Ready!** Ruby gem with multiple calling patterns and mixin support

## Status

- **Python**: ✅ Complete and published to PyPI
- **JavaScript/TypeScript**: ✅ Complete with TypeScript support and comprehensive test suite
- **Ruby**: 🚧 Planned
- **Ruby**: ✅ Complete with Ruby-idiomatic API and comprehensive RSpec test suite

## Contributing

Expand Down
136 changes: 120 additions & 16 deletions js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,47 +27,95 @@ yarn add garde-fou
```typescript
import { GardeFou } from 'garde-fou';

// Protect any function with call limits
const guard = new GardeFou({ max_calls: 5, on_violation_max_calls: 'warn' });
const guard = GardeFou({ max_calls: 5, on_violation_max_calls: 'warn' });

// Instead of: result = expensiveApiCall("query")
// Use: result = guard.call(expensiveApiCall, "query")
const result = guard.call(yourApiFunction, "your", "arguments");
// Two equivalent ways to call your protected function:

// 1. Direct call (Python-like syntax) - RECOMMENDED
const result = guard(yourApiFunction, "your", "arguments");

// 2. Explicit method call
const result2 = guard.call(yourApiFunction, "your", "arguments");

// For async functions, use callAsync method
const asyncResult = await guard.callAsync(yourAsyncApiFunction, "args");
```

## Calling Patterns

garde-fou supports two calling patterns that work identically:

### Pattern 1: Direct Call (Recommended)
```typescript
const guard = GardeFou({ max_calls: 3 });

// Call guard directly like a function (same as Python)
const result = guard(apiFunction, param1, param2);
```

### Pattern 2: Explicit Method Call
```typescript
const guard = GardeFou({ max_calls: 3 });

// Use explicit .call() method
const result = guard.call(apiFunction, param1, param2);
```

**Both patterns:**
- Share the same call count and quota
- Work with duplicate detection
- Have identical behavior and performance

## Usage Examples

### Basic Call Limiting
```typescript
import { GardeFou, QuotaExceededError } from 'garde-fou';

// Create a guard with a 3-call limit
const guard = new GardeFou({ max_calls: 3, on_violation_max_calls: 'raise' });
const guard = GardeFou({ max_calls: 3, on_violation_max_calls: 'raise' });

try {
for (let i = 0; i < 5; i++) {
const result = guard.call(apiCall, `query ${i}`);
}
// Using direct call syntax (recommended)
guard(apiCall, 'query 1');
guard(apiCall, 'query 2');
guard(apiCall, 'query 3');
guard(apiCall, 'query 4'); // This will throw!
} catch (error) {
if (error instanceof QuotaExceededError) {
console.log('Call limit exceeded!');
}
}

// Alternative: using explicit method calls
try {
guard.call(apiCall, 'query 1');
guard.call(apiCall, 'query 2');
guard.call(apiCall, 'query 3');
guard.call(apiCall, 'query 4'); // This will also throw!
} catch (error) {
console.log('Same behavior with .call()');
}
```

### Duplicate Call Detection
```typescript
// Warn on duplicate calls
const guard = new GardeFou({ on_violation_duplicate_call: 'warn' });
const guard = GardeFou({ on_violation_duplicate_call: 'warn' });

guard.call(apiCall, 'hello'); // First call - OK
guard.call(apiCall, 'hello'); // Duplicate - Warning logged
// Direct call syntax
guard(apiCall, 'hello'); // First call - OK
guard(apiCall, 'hello'); // Duplicate - Warning logged
guard(apiCall, 'world'); // Different call - OK

// Explicit method syntax (works identically)
guard.call(apiCall, 'hello'); // Also detected as duplicate!
guard.call(apiCall, 'world'); // Different call - OK
```

### Async Function Protection
```typescript
const guard = new GardeFou({ max_calls: 3, on_violation_max_calls: 'raise' });
const guard = GardeFou({ max_calls: 3, on_violation_max_calls: 'raise' });

try {
const result1 = await guard.callAsync(asyncApiCall, 'query 1');
Expand Down Expand Up @@ -111,10 +159,14 @@ const customHandler = (profile) => {
// Send alert, log to service, etc.
};

const guard = new GardeFou({
const guard = GardeFou({
max_calls: 5,
on_violation_max_calls: customHandler
});

// Both calling styles work with custom handlers
guard(apiCall, 'test'); // Direct call
guard.call(apiCall, 'test'); // Explicit method call
```

## Configuration Options
Expand Down Expand Up @@ -168,6 +220,53 @@ interface ProfileConfig {
}
```

## Real-World Examples

### OpenAI API Protection
```typescript
import OpenAI from 'openai';
import { GardeFou } from 'garde-fou';

const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const guard = GardeFou({
max_calls: 100,
on_violation_max_calls: 'warn',
on_violation_duplicate_call: 'warn'
});

// Before: Direct API call
// const response = await openai.chat.completions.create({
// model: 'gpt-4',
// messages: [{ role: 'user', content: 'Hello!' }]
// });

// After: Protected API call (choose your preferred syntax)

// Option 1: Direct call (Python-like)
const response = await guard.callAsync(openai.chat.completions.create, {
model: 'gpt-4',
messages: [{ role: 'user', content: 'Hello!' }]
});

// Option 2: Explicit method call
const response2 = await guard.callAsync(openai.chat.completions.create, {
model: 'gpt-4',
messages: [{ role: 'user', content: 'Hello!' }]
});
```

### Multiple API Services
```typescript
const guard = GardeFou({ max_calls: 50 });

// Protect different APIs with the same guard
const openaiResult = guard(openai.chat.completions.create, { /* config */ });
const anthropicResult = guard(anthropic.messages.create, { /* config */ });
const cohereResult = guard.call(cohere.generate, { /* config */ });

console.log(`Total API calls made: ${guard.profile.call_count}`);
```

## How It Works

garde-fou works by wrapping your function calls. Instead of calling your API function directly, you call it through the guard:
Expand All @@ -176,8 +275,13 @@ garde-fou works by wrapping your function calls. Instead of calling your API fun
// Before
const result = openai.chat.completions.create({ messages: [...] });

// After
const guard = new GardeFou({ max_calls: 10 });
// After - choose your preferred syntax:
const guard = GardeFou({ max_calls: 10 });

// Option 1: Direct call (recommended)
const result = guard(openai.chat.completions.create, { messages: [...] });

// Option 2: Explicit method call
const result = guard.call(openai.chat.completions.create, { messages: [...] });
```

Expand Down
56 changes: 41 additions & 15 deletions js/examples/example.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ async function expensiveAsyncApiCall(query, model = 'gpt-4') {
async function main() {
console.log('=== GardeFou TypeScript Example Usage ===\n');

// Example 1: Basic usage with max_calls limit
// Example 1: Basic usage with max_calls limit (Python-like syntax!)
console.log('1. Basic usage with call limit:');
const guard = new GardeFou({ max_calls: 3, on_violation_max_calls: 'warn' });
const guard = GardeFou({ max_calls: 3, on_violation_max_calls: 'warn' });

for (let i = 0; i < 5; i++) {
try {
const result = guard.call(expensiveApiCall, `Query ${i + 1}`);
// Now you can call guard directly like in Python!
const result = guard(expensiveApiCall, `Query ${i + 1}`);
console.log(` Result: ${result}`);
} catch (error) {
console.log(` Error: ${error.message}`);
Expand All @@ -35,18 +36,18 @@ async function main() {

// Example 2: Duplicate call detection
console.log('2. Duplicate call detection:');
const guardDup = new GardeFou({ on_violation_duplicate_call: 'warn' });
const guardDup = GardeFou({ on_violation_duplicate_call: 'warn' });

// First call - should work
const result1 = guardDup.call(expensiveApiCall, 'Hello world');
const result1 = guardDup(expensiveApiCall, 'Hello world');
console.log(` First call result: ${result1}`);

// Duplicate call - should warn
const result2 = guardDup.call(expensiveApiCall, 'Hello world');
const result2 = guardDup(expensiveApiCall, 'Hello world');
console.log(` Duplicate call result: ${result2}`);

// Different call - should work
const result3 = guardDup.call(expensiveApiCall, 'Different query');
const result3 = guardDup(expensiveApiCall, 'Different query');
console.log(` Different call result: ${result3}`);

console.log('\n' + '='.repeat(50) + '\n');
Expand All @@ -58,19 +59,19 @@ async function main() {
on_violation_max_calls: 'raise',
on_violation_duplicate_call: 'warn'
});
const guardProfile = new GardeFou({ profile });
const guardProfile = GardeFou({ profile });

try {
// First call
const result = guardProfile.call(expensiveApiCall, 'Profile test 1');
const result = guardProfile(expensiveApiCall, 'Profile test 1');
console.log(` Call 1: ${result}`);

// Second call
const result2 = guardProfile.call(expensiveApiCall, 'Profile test 2');
const result2 = guardProfile(expensiveApiCall, 'Profile test 2');
console.log(` Call 2: ${result2}`);

// Third call - should raise exception
const result3 = guardProfile.call(expensiveApiCall, 'Profile test 3');
const result3 = guardProfile(expensiveApiCall, 'Profile test 3');
console.log(` Call 3: ${result3}`);

} catch (error) {
Expand All @@ -81,7 +82,7 @@ async function main() {

// Example 4: Async function protection
console.log('4. Async function protection:');
const guardAsync = new GardeFou({ max_calls: 2, on_violation_max_calls: 'raise' });
const guardAsync = GardeFou({ max_calls: 2, on_violation_max_calls: 'raise' });

try {
const asyncResult1 = await guardAsync.callAsync(expensiveAsyncApiCall, 'Async test 1');
Expand All @@ -108,15 +109,40 @@ async function main() {
callbackTriggered = true;
};

const guardCallback = new GardeFou({
const guardCallback = GardeFou({
max_calls: 1,
on_violation_max_calls: customHandler
});

guardCallback.call(expensiveApiCall, 'Callback test 1');
guardCallback.call(expensiveApiCall, 'Callback test 2'); // Triggers callback
guardCallback(expensiveApiCall, 'Callback test 1');
guardCallback(expensiveApiCall, 'Callback test 2'); // Triggers callback

console.log(` Callback was triggered: ${callbackTriggered}`);

console.log('\n' + '='.repeat(50) + '\n');

// Example 6: Demonstrating both calling patterns work identically
console.log('6. Both calling patterns (direct vs .call()):');
const guardBoth = GardeFou({ max_calls: 4, on_violation_max_calls: 'warn' });

// Mix both calling patterns - they share the same quota
console.log(` Starting call count: ${guardBoth.profile.call_count}`);

guardBoth(expensiveApiCall, 'Direct call 1');
console.log(` After direct call 1: ${guardBoth.profile.call_count}`);

guardBoth.call(expensiveApiCall, 'Method call 1');
console.log(` After method call 1: ${guardBoth.profile.call_count}`);

guardBoth(expensiveApiCall, 'Direct call 2');
console.log(` After direct call 2: ${guardBoth.profile.call_count}`);

guardBoth.call(expensiveApiCall, 'Method call 2');
console.log(` After method call 2: ${guardBoth.profile.call_count}`);

// This should trigger warning (5th call)
guardBoth(expensiveApiCall, 'Direct call 3 - should warn');
console.log(` After warning call: ${guardBoth.profile.call_count}`);
}

if (require.main === module) {
Expand Down
Loading