Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/tomevault-io/skills-registry --skill fluent-iterable명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SOC 직업 분류 기준
SKILL.md 표시 중
| name | fluent-iterable |
| description | | Use when this capability is needed. |
A comprehensive guide to mastering @codibre/fluent-iterable for elegant, chainable data transformations in TypeScript/NestJS projects.
Do you have data to transform?
├─ Is it an AsyncIterable (stream, generator)?
│ ├─ YES → Use fluentAsync()
│ └─ NO → Go to next
├─ Is it a regular array/Map/iterable?
│ ├─ YES → Use fluent()
│ └─ NO → Check if it's an async generator
└─ Will you chain async operations?
├─ YES → Use .forEachAsync() or .flatMapAsync()
└─ NO → Use normal methods (.forEach(), .flatMap())
Use fluent() with arrays, Map entries, Sets, or any sync iterable.
import { fluent } from '@codibre/fluent-iterable';
// Simple example: map + filter
const numbers = [1, 2, 3, 4, 5];
const result = fluent(numbers)
.filter(x => x > 2)
.map(x => x * 2)
.toArray(); // [6, 8, 10]
Key characteristics:
.forEachAsync() or .flatMapAsync()Use fluentAsync() with streams, async generators, or database cursors.
import { fluentAsync } from '@codibre/fluent-iterable';
// Stream example: filter + map + toSet
const activeUsers = await fluentAsync(
this.userRepository.streamUsers() // AsyncIterable<User>
)
.filter(user => user.isActive)
.map(user => user.id)
.toSet(); // Set<string>
Key characteristics:
Use case: Process 5000+ records from database without loading all in memory
// ✅ CORRECT: Stream filtering via async iterable
const successfulUserIds = await fluentAsync(
this.messageRepository.findByAnnouncementIdStream(announcementId)
)
.filter(msg => msg.sendError === null) // Only successful messages
.map(msg => msg.userId)
.toSet(); // Deduplicated set
Why this works:
Use case: Invert a map (userId → tokens) to (token → userId)
// ✅ CORRECT: Map transformation
const tokenToUserId = fluent(userTokens.entries()) // Map.entries() is iterable
.flatMap(([userId, tokens]) =>
tokens.map(token => [token, userId] as const) // Create [token, userId] pairs
)
.reduce(
(acc, [token, userId]) => {
acc.set(token, userId);
return acc;
},
new Map<string, string>()
);
Why this works:
entries() converts Map to iterable of [key, value] pairsflatMap() flattens nested arraysreduce() with initial accumulator (new Map)Use case: Process 5000 items in chunks of 100 for API limits
// ✅ CORRECT: Chunking
const chunks = fluent(largeList)
.partition(100) // Split into chunks of 100
.map(chunk => ({
items: chunk.toArray(),
count: chunk.count()
}))
.toArray();
// Process chunks
for (const {items, count} of chunks) {
await firebase.sendBatch(items); // Each batch ≤ 100
}
Why this works:
partition() returns iterable of iterablesUse case: Save 1000 messages to DB in parallel, not sequentially
// ✅ CORRECT: forEachAsync for parallel operations
await fluent(payload.recipients)
.filter(recipient => !alreadyProcessed.has(recipient.userId))
.forEachAsync(async (recipient) => {
await this.messageRepository.saveMessage({
recipientId: recipient.userId,
message: this.renderTemplate(recipient),
// ...
});
});
Why this works:
forEachAsync() runs all operations in parallelUse case: Depaginate multiple API calls in parallel
// ✅ CORRECT: flatMapAsync for parallel API calls
const allResellers = await fluent(chunks)
.flatMapAsync(chunk =>
this.api.getResellers({
codes: chunk.codes,
limit: 100
})
)
.toArray();
Why this works:
| Method | Sync | Async | Use Case |
|---|---|---|---|
.map(fn) | ✅ | ✅ | Transform each element |
.map(fieldName) | ✅ | ✅ | Extract field: map('email') = map(x => x.email) |
.filter(predicate) | ✅ | ✅ | Keep matching elements |
.filter() | ✅ | ✅ | Remove falsy (null/undefined/false/0/'') |
.filter(fieldName) | ✅ | ✅ | Filter + type guard: filter('email') removes undefined |
.flatMap(fn) | ✅ | ❌ | Map + flatten (sync) |
.flatten(fn) | ✅ | ❌ | Flatten nested iterables |
.flatMapAsync(fn) | ❌ | ✅ | Map + flatten (async) |
.partition(size) | ✅ | ✅ | Split into chunks (returns iterable of iterables) |
.group(keyFn) | ✅ | ✅ | Group elements by key |
.group(fieldName) | ✅ | ✅ | Group by field: group('category') = group(x => x.category) |
.distinct() | ✅ | ✅ | Remove duplicates (like toSet() but lazy) |
.sort(compareFn?) | ✅ | ✅ | Sort elements |
.reverse() | ✅ | ✅ | Reverse order |
.first() / .firstAsync() | ✅ | ✅ | Get first element (stops iteration) |
.last() / |
| Method | Sync | Async | Effect |
|---|---|---|---|
.forEach(fn) | ✅ | ❌ | Execute for each, sync |
.forEachAsync(fn) | ❌ | ✅ | Execute in parallel, await (runs all concurrently) |
.execute(fn) | ✅ | ✅ | Side effect (logging) without modifying iterable |
.waitAll(promiseFn) | ❌ | ✅ | Convert to promises and await all (parallel) |
.combine(other, keyFn1, keyFn2) | ✅ | ✅ | Inner join two iterables |
| Method | Returns | Use | Memory |
|---|---|---|---|
.toArray() | Array<T> | Final result to array | O(n) |
.toSet() | Set<T> | Deduplicate + set | O(n) |
.toMap(keyFn) | Map<K, T> | Create lookup index | O(n) |
.toMap(keyFn, valueFn) | Map<K, V> | Transform keys and values | O(n) |
.count() | number | Get length (no array allocation) | O(1) or O(n) depending on source |
.any(predicate?) | boolean | Check if any match (short-circuits) | O(1)-O(n) |
.all(predicate?) | boolean | Check if all match (short-circuits) | O(1)-O(n) |
.join(separator) | string | Join to string | O(n) |
Direct usage - no callback needed:
// ✅ Remove all null, undefined, false, 0, ''
const activeIds = await fluentAsync(users)
.filter() // ← No parameter needed!
.map(user => user.id)
.toArray();
// Equivalent to:
const activeIds = await fluentAsync(users)
.filter(user => !!user) // More verbose
.map(user => user.id)
.toArray();
Perfect for streams that might have nulls:
// Stream might yield null on errors
const validRecords = await fluentAsync(asyncGenerator())
.filter() // Automatically removes falsy values
.map(record => record.process());
Use field names instead of callbacks - cleaner and with auto type guards:
// ❌ VERBOSE - write callbacks manually
fluent(users)
.map(user => user.email)
.filter(email => !!email)
// ✅ CLEAN - use string field names
fluent(users)
.map('email') // ← Equivalent to map(x => x.email)
.filter('email') // ← Filters falsy + type guards!
// Type guard example:
interface User {
id: number;
email?: string; // Optional!
name: string;
}
const usersWithEmail = fluent(users)
.filter('email') // ← TypeScript now knows email is NOT undefined
.map('email'); // ← email is string, not string | undefined
// email is now narrowed to string type!
String selector patterns:
// ✅ map() with string selector
fluent(products)
.map('name') // Extract only name field
.toArray() // ['Product A', 'Product B', ...]
// ✅ filter() with string selector (includes type narrowing)
fluent(users)
.filter('email') // Only users WITH email, email now typed as non-null
.filter('isActive') // Only active users (truthy check)
.forEach(user => {
// user.email is now string (not string | undefined)
console.log(user.email.toLowerCase()); // ✅ Safe, no TS error
});
// ✅ Chaining field filters
fluent(orders)
.filter('customerId') // Remove orders with no customer
.filter('total') // Remove orders with total = 0
.map('total')
.sum(); // Sum only valid orders
Why string selectors are powerful:
map('field') vs map(x => x.field)filter('field') narrows type (removes | undefined)filter('email') is clearer than filter(x => x.email)// Real-world: User validation pipeline
const validatedUsers = await fluentAsync(streamUsers())
.filter('id') // Must have ID
.filter('email') // Must have email (now non-null)
.filter('isVerified') // Must be verified
.map('email') // Extract emails
.distinct()
.toArray();
// email is guaranteed to be string (not undefined)
// Result: string[]
Declare type guards with is keyword - fluent-iterable propagates narrowed types!
Type guards are TypeScript's way of narrowing types. When you use is in a filter callback, fluent-iterable understands and propagates the narrowed type through the entire chain.
// Define custom type guard
function isUser(item: User | Admin | null): item is User {
return item !== null && 'email' in item && !('permissions' in item);
}
// Use it with fluent - type is automatically narrowed!
const emails = fluent(users)
.filter(isUser) // ← Type guard!
.map(user => user.email) // user is now narrowed to User, not User | Admin | null
.toArray();
// Equivalent to:
const emails = fluent(users)
.filter((item): item is User =>
item !== null && 'email' in item && !('permissions' in item)
)
.map(user => user.email) // ✅ user is User here
.toArray();
Real-world examples with type guards:
// Discriminated unions
type ApiResponse = { status: 'success'; data: string } | { status: 'error'; error: Error };
function isSuccess(response: ApiResponse): response is { status: 'success'; data: string } {
return response.status === 'success';
}
// Use with fluent
const successData = fluent(responses)
.filter(isSuccess) // ← Type guard narrows to success type
.map(r => r.data) // ✅ data is string (not error)
.toArray();
// Optional fields -> required
interface Product {
id: number;
name: string;
description?: string;
price?: number;
}
function hasPrice(product: Product): product is Product & Required<<, >> {
product. !== && product. > ;
}
priced = (products)
.(hasPrice)
.( p.)
.();
{
= ,
= ,
= ,
}
(): user is { : . } {
user. === .;
}
activeUsers = (allUsers)
.(isActive)
.( {
});
Inline Type Guards - Declare directly in lambda for simpler checks:
No need for separate functions when the guard is simple. Use inline syntax with (param): param is Type =>:
// ✅ Simple inline type guard - no separate function needed
const emails = fluent(items)
.filter((item): item is { email: string } =>
typeof item === 'object' && item !== null && 'email' in item && typeof item.email === 'string'
)
.map(item => item.email) // ✅ email is string
.toArray();
// Discriminated union inline
const successData = fluent(responses)
.filter((r): r is { status: 'success'; data: string } => r.status === 'success')
.map(r => r.data) // ✅ data is string
.toArray();
// Optional to required inline
const productsWithPrice = fluent(products)
.filter((p): p is typeof p & { price: number } => p.price !== undefined && p.price > 0)
.map(p => p.)
.();
= { : ; : ; : } | { : ; : };
updates = (actions)
.((action): action is { : ; : ; : } =>
action. ===
)
.( action.)
.();
When to use inline vs separate function:
// ✅ Use inline for simple, one-off checks
fluent(items).filter((x): x is User => !!x.id && !!x.email)
// ✅ Use separate function for complex logic or reuse
function isValidUser(item: any): item is User {
return (
item !== null &&
typeof item === 'object' &&
'id' in item &&
'email' in item &&
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(item.email) &&
item.id > 0
);
}
fluent(items).filter(isValidUser)
Why inline type guards are powerful:
Why type guards are powerful:**
// Without type guard - broader type
fluent(items)
.filter(item => item.type === 'user')
.map(item => item.name) // ❌ TypeScript doesn't know if name exists
.forEach(name => console.log(name)); // ⚠️ Potential error
// With type guard - narrowed type
function isUser(item: any): item is { name: string; type: 'user' } {
return item.type === 'user' && typeof item.name === 'string';
}
fluent(items)
.filter(isUser)
.map(item => item.name) // ✅ TypeScript knows name exists
.forEach(name => console.log(name)); // ✅ Safe!
Use fluent() for arrays and sync iterables
fluent([1, 2, 3]).map(...).filter(...)
Use fluentAsync() for AsyncIterables
fluentAsync(asyncGenerator()).filter(...).toSet()
Always await fluentAsync results
const result = await fluentAsync(...).toArray();
Use .forEachAsync() for async side effects
await fluent(items).forEachAsync(item => saveAsync(item));
Materialize only at the end
// ✅ CORRECT
fluent(x).map(...).filter(...).toArray()
// ❌ WRONG (materializes too early)
fluent(x).toArray().map(...).filter(...)
Use string selectors for cleaner code
// ✅ CLEAN - automatic type narrowing
fluent(users)
.()
.()
(users)
.( !!user.)
.( user.)
Don't use fluent() with AsyncIterables
// ❌ WRONG - will only iterate first element
fluent(asyncGenerator())
// ✅ CORRECT
fluentAsync(asyncGenerator())
Don't forget await on async operations
// ❌ WRONG
const result = fluentAsync(...).toArray(); // Missing await!
// ✅ CORRECT
const result = await fluentAsync(...).toArray();
Don't use async methods on fluentAsync()
// ❌ WRONG - forEachAsync doesn't exist on fluent()
fluentAsync(array).forEachAsync(...)
// ✅ CORRECT
await fluent(array).forEachAsync(...)
// ✅ Also CORRECT
await fluenAsync(data).forEach(...)
Don't chain multiple .toArray() calls
// ❌ WRONG - .toArray() materializes, can't chain
fluent(x).toArray().map(...).filter(...)
// ✅ CORRECT - chain first, materialize last
fluent(x).map(...).filter(...).toArray()
// Get users already successfully reached
const successfulUserIds = await fluentAsync(
this.messageRepository.findByAnnouncementIdStream(announcementId)
)
.filter(msg => msg.sendError === null)
.map(msg => msg.receiverId)
.toSet();
// Get users still needing processing
const usersToProcess = fluent(payload.receiverIds)
.filter(id => !successfulUserIds.has(id))
.toArray();
// Batch in groups of 100
const batches = fluent(userTokens.entries())
.flatMap(([userId, tokens]) =>
tokens.map(token => ({ userId, token }))
)
.partition(100)
.map(batch => batch.toArray())
.toArray();
// Send all in parallel
await fluent(batches)
.forEachAsync(batch =>
..(batch.( x.))
);
const allData = await fluent(sellers)
.partition(500) // Limit codes per request
.map(chunk => ({
...request,
codes: chunk.toArray()
}))
.toArray()
.flatMapAsync(request => // Note: async chain after toArray()
this.api.getData(request)
);
const stats = fluent(users)
.reduce((acc, user) => {
const key = user.country;
acc.set(key, (acc.get(key) ?? 0) + 1);
return acc;
}, new Map<string, number>());
// stats = Map { 'US' => 45, 'BR' => 32, 'MX' => 18 }
Use case: Process 5000+ items in batches while handling failures per batch (not failing entire operation)
// ✅ CORRECT: Try-catch inside partition loop
const results = [];
for (const batch of fluent(items).partition(500)) {
try {
const tokenBatch = batch.toArray();
const result = await sendBatchToApi(tokenBatch);
results.push(result);
} catch (error) {
// Log error for THIS batch, continue with next batch
console.error(`Batch failed: ${error.message}`);
// Can collect failed items if needed
}
}
// ❌ WRONG: Try-catch wrapping entire loop aborts after first error
try {
for (const batch of fluent(items).partition(500)) {
await sendBatchToApi(batch.toArray());
}
} catch (error) {
// All subsequent batches never execute!
}
Why this works:
Use case: Cross-reference 1000 items with 5000 recipients
// ❌ WRONG: O(n²) - calls .find() inside loop for each item
const userMap = new Map();
for (const recipient of recipients) {
const user = items.find(i => i.id === recipient.userId); // 5000 * 1000 iterations!
userMap.set(recipient.userId, user);
}
// ✅ CORRECT: O(n) - create lookup map first
const userLookup = fluent(items).toMap(i => i.id);
for (const recipient of recipients) {
const user = userLookup.get(recipient.userId); // O(1) lookup
}
// Or more elegantly:
const userLookup = fluent(items).toMap(i => i.id, i => i); // both key and value transformation
Why this works:
Use case: Process millions of database records without loading all into memory
// ✅ CORRECT: Streaming + filtering before materialization
const successCount = await fluentAsync(
this.database.streamLargeDataset() // AsyncIterable, yields one at a time
)
.filter(record => record.status === 'active')
.map(record => record.id)
.count(); // Returns count without creating array
// Memory usage: O(1) relative to dataset size
// ❌ WRONG: Materializes entire dataset first
const allRecords = await fluentAsync(this.database.streamLargeDataset())
.toArray(); // Loads entire dataset into memory!
const successCount = fluent(allRecords)
.filter(r => r.status === 'active')
.count();
Why this works:
Use case: Match orders with customers by ID
const orders = [
{ id: 1, customerId: 10, amount: 100 },
{ id: 2, customerId: 20, amount: 200 },
];
const customers = [
{ id: 10, name: 'Alice' },
{ id: 20, name: 'Bob' },
];
const results = fluent(orders)
.combine(
customers,
(order) => order.customerId, // key from first
(customer) => customer.id // key from second
)
.map(([order, customer]) => ({
orderId: order.id,
customerName: customer.name,
amount: order.amount
}))
.toArray();
// Results: [
// { orderId: 1, customerName: 'Alice', amount: 100 },
// { orderId: 2, customerName: 'Bob', amount: 200 }
// ]
Why this works:
Use case: Fetch paginated API data until done
async function* depaginate<T>(
pageSize: number,
fetcher: (page: number) => Promise<T[]>
): AsyncIterable<T> {
let page = 1;
while (true) {
const items = await fetcher(page);
if (items.length === 0) break;
yield* items;
page++;
}
}
// Use it:
const allUsers = await fluentAsync(depaginate(100, page =>
fetchUserPage(page)
))
.filter(user => user.active)
.map(user => user.id)
.toArray();
Why this works:
Use case: Get unique values while being fast
// ✅ Better than toSet() when you need early exit
const firstUniqueActive = fluent(items)
.distinct() // Remove duplicates lazily
.filter(x => x.active) // Then filter
.first(); // Stop at first match
// ✅ If you need the full deduplicated set later
const uniqueIds = fluent(items)
.map(item => item.id)
.toSet(); // Faster for materializing to Set
Why this works:
.distinct() is lazy (doesn't create set upfront).toSet() faster when you need complete setCause: Using fluentAsync() instead of fluent()
// ❌ WRONG
fluentAsync(data).forEachAsync(...)
// ✅ CORRECT
await fluent(data).forEachAsync(...)
// ✅ Also CORRECT
await fluenAsync(data).forEach(...)
Cause: Trying to await a sync fluent chain
// ❌ WRONG
const result = await fluent([1,2,3]).map(...); // Not async!
// ✅ CORRECT
const result = fluent([1,2,3]).map(...); // Don't await
Cause: Calling .toArray() on entire stream before filtering
// ❌ WRONG - loads all in memory first
fluentAsync(stream).toArray().filter(...)
// ✅ CORRECT - filter before materializing
fluentAsync(stream).filter(...).toArray()
Cause: Using .forEach() with async instead of .forEachAsync()
// ❌ WRONG - sequential, doesn't await
fluent(items).forEach(item => saveAsync(item));
// ✅ CORRECT - parallel with proper await
await fluent(items).forEachAsync(item => saveAsync(item));
fluent-iterable Performance (from official benchmarks):
for...of loopsWhen to optimize:
General optimization order:
.count() instead of .toArray().length.first(), .any()) instead of full iterationFilter early - Remove unwanted items before other operations
fluent(million_items)
.filter(x => x.active) // First: reduce to 50k
.map(x => x.value) // Then: transform remaining
.toArray()
Use partition() for large batches
fluent(items)
.partition(100) // Chunk before processing
.map(chunk => processChunk(chunk))
Avoid nested loops - use flatMap instead
// ❌ WRONG - nested loops
fluent(items).forEach(item => {
fluent(item.children).forEach(child => ...)
})
// ✅ CORRECT - flatten
fluent(items)
.flatMap(item => item.children)
.forEach(child => ...)
For AsyncIterables, filter before reduce
await (stream)
.( (x))
.(aggregate, initial)
✅ Use fluent-iterable when:
❌ Avoid fluent-iterable when:
.toArray() at start is simpler.map() or .filter() once - just use native array methods.reduce() with plain JavaScript// ❌ OVERKILL - tiny array, simple operation
fluent([1, 2, 3]).map(x => x * 2).toArray()
// ✅ JUST USE NATIVE
[1, 2, 3].map(x => x * 2)
// ✅ GOOD USE CASE - complex chain, async
await fluentAsync(largeStream)
.filter(x => x.active)
.map(x => x.id)
.group(x => x.category)
.toMap(g => g.key, g => g.values.count())
| Aspect | fluent-iterable | RxJS |
|---|---|---|
| Purpose | Sequential iteration | Event streaming |
| Execution | Synchronous chain | Async/event-driven |
| Memory | O(1) with streams | Can accumulate events |
| Learning curve | Simple array-like API | Complex (Subjects, Operators) |
| Best for | Batch processing, data transforms | Real-time events, UI reactivity |
→ Use fluent-iterable for data pipelines, RxJS for event handling
| Aspect | fluent-iterable | .map().filter() |
|---|---|---|
| Iterations | 1 pass | Multiple passes per operation |
| Memory | Minimal (lazy) | Array created per operation |
| Performance | ~50% faster on 1k+ items | Faster on tiny arrays |
| Async support | ✅ Built-in | ❌ Need Promise.all() |
| Streaming | ✅ AsyncIterable | ❌ Must load all first |
→ Use fluent-iterable for large data, native methods for small arrays
Source: codibre/fluent-iterable — distributed by TomeVault.
.lastAsync()| ✅ |
| ✅ |
| Get last element (forces full iteration) |
.min() / .max() | ✅ | ✅ | Get minimum/maximum element |
.sum() | ✅ | ✅ | Sum all elements |
.skip(n) | ✅ | ✅ | Skip first n elements |
.take(n) | ✅ | ✅ | Take first n elements |
.skipWhile(predicate) | ✅ | ✅ | Skip while condition true |
.takeWhile(predicate) | ✅ | ✅ | Take while condition true |
Use filter() without params for falsy removal
// ✅ CLEAN - removes null, undefined, false, 0, ''
await fluentAsync(stream).filter().toArray()
// ❌ VERBOSE
await fluentAsync(stream).filter(x => !!x).toArray()
Don't use .forEach() when you need awaits
// ❌ WRONG - doesn't await properly
fluent(items).forEach(item => saveAsync(item));
// ✅ CORRECT
await fluent(items).forEachAsync(item => saveAsync(item));