Advanced Patterns
Context injection, telemetry, global hooks, multi-pattern files, and custom logging.
Advanced Patterns
Once you've mastered the basics, these patterns unlock the full power of collections for production applications.
Context Injection
Use withContext() to inject dependencies into every hook, view data handler, and view action. The factory runs fresh for every operation — you always get the latest state.
import { IgniterCollections } from '@igniter-js/collections';
interface AppContext {
db: Database;
auth: AuthState;
config: AppConfig;
}
const docs = IgniterCollections.create()
.withAdapter(new NodeFsAdapter())
.withContext(async (): Promise<AppContext> => {
const [db, auth, config] = await Promise.all([
Database.connect(),
getCurrentAuth(),
loadConfig(),
]);
return { db, auth, config };
})
.addCollection(Posts)
.build();
// Access in hooks
Posts.onCreated(async ({ value, context }) => {
const { db, auth } = context as AppContext;
await db.auditLog.create({
userId: auth.userId,
action: 'post:created',
documentId: value.id,
});
return value;
});
// Access in views
const DashboardView = IgniterCollectionView.create('dashboard')
.withData(async ({ manager, context }) => {
const { auth } = context as AppContext;
const posts = auth.isAdmin
? await manager.posts.findMany()
: await manager.posts.findMany({ where: { published: true } });
return { posts };
})
.build();The context factory is called once per operation. For batch operations like findMany, it's called once. Every hook on a single create call shares the same context instance.
Telemetry
Integrate @igniter-js/telemetry for observability — track operation duration, error rates, and throughput.
import { IgniterTelemetry } from '@igniter-js/telemetry';
import { IgniterCollections } from '@igniter-js/collections';
const telemetry = IgniterTelemetry.create()
.withAdapter(telemetryAdapter)
.build();
const docs = IgniterCollections.create()
.withAdapter(new NodeFsAdapter())
.withTelemetry(telemetry)
.addCollection(Posts)
.build();
// Collections automatically reports:
// - Operation duration (create, read, update, delete, list)
// - Error rates and error codes
// - Collection name context
// - Document count per operationGlobal Hooks
Apply hooks to every collection at once — perfect for logging, audit trails, and cross-cutting concerns:
const docs = IgniterCollections.create()
.withAdapter(new NodeFsAdapter())
.withGlobalHooks({
onCreated: async ({ value, collection }) => {
console.log(`[${collection.definition.name}] Created: ${value.id}`);
return value;
},
onUpdated: async ({ newValue, previousValue, collection }) => {
console.log(
`[${collection.definition.name}] Updated: ${newValue.id}`,
Object.keys(newValue.data).filter(
k => newValue.data[k] !== previousValue.data[k]
)
);
return newValue;
},
onDeleted: async ({ value, collection }) => {
console.log(`[${collection.definition.name}] Deleted: ${value.id}`);
return true;
},
onRead: async ({ value, collection }) => {
console.log(`[${collection.definition.name}] Read: ${value.id}`);
return value;
},
onList: async ({ values, collection }) => {
console.log(`[${collection.definition.name}] Listed: ${values.length} docs`);
return values;
},
})
.addCollection(Posts)
.addCollection(Pages)
.build();Global hooks run before collection-specific hooks. If a global hook returns false, the operation cancels and collection hooks never run.
Multi-Pattern Files
Collections supports multiple file patterns per collection. The best-match pattern is used based on available data fields:
const Posts = IgniterCollectionModel.create('posts')
.withPatterns([
// Organized by year/month when metadata is available
'.content/posts/{year}/{month}/{id}.mdx',
// Flat structure as fallback
'.content/posts/{id}.mdx',
// Author-organized for team content
'.content/posts/{author}/{id}.mdx',
])
.withSchema(z.object({
title: z.string(),
author: z.string(),
published: z.boolean(),
}))
.build();The system selects the pattern with the most placeholders that can be resolved from the document data. If your data has year, month, and author, the date-based pattern wins.
Custom Logger
Pass a logger for debug output and operation tracking:
import type { IgniterLogger } from '@igniter-js/common';
const logger: IgniterLogger = {
debug: (msg, meta) => console.debug(`[Collections] ${msg}`, meta),
info: (msg, meta) => console.info(`[Collections] ${msg}`, meta),
warn: (msg, meta) => console.warn(`[Collections] ${msg}`, meta),
error: (msg, meta) => console.error(`[Collections] ${msg}`, meta),
};
const docs = IgniterCollections.create()
.withAdapter(new NodeFsAdapter())
.withLogger(logger)
.addCollection(Posts)
.build();
// Now you'll see:
// [Collections] Document created: posts/a1b2c3d4 { duration: 12 }
// [Collections] Registered collection from schema: pagesMultiple Base Paths
Support content from multiple directories:
const docs = IgniterCollections.create()
.withAdapter(new NodeFsAdapter())
.withBasePath(['.content', '.docs', '.blog']) // Multiple base paths
.addCollection(Posts)
.build();The first path in the array is used as the primary base path. Additional paths can be used for pattern-based resolution.
Sub-Collection Depth
Create deeply nested document structures:
const Projects = IgniterCollectionModel.create('projects')
.withPatterns(['.content/projects/{id}.mdx'])
.withSchema(projectSchema)
.build();
const Tasks = Projects.collections.create('tasks')
.withPatterns(['{parent_id}/tasks/{id}.mdx'])
.withSchema(taskSchema)
.build();
const Comments = Tasks.collections.create('comments')
.withPatterns(['{parent_id}/comments/{id}.mdx'])
.withSchema(commentSchema)
.build();
// Nested files: .content/projects/proj-1/tasks/task-1/comments/comment-1.mdxFull-Text Search Configuration
Fine-tune the search index with field weights and fuzzy matching:
const Posts = IgniterCollectionModel.create('posts')
.withSchema(z.object({
title: z.string(),
description: z.string(),
body: z.string(),
tags: z.array(z.string()),
author: z.string(),
}))
.build();
// Search with weighted fields
const results = await docs.posts.findMany({
where: {
search: {
term: 'TypeScript patterns',
fields: {
title: { weight: 3, fuzzy: true },
description: { weight: 1.5 },
body: { weight: 0.5 },
tags: { weight: 2 },
},
threshold: 0.15,
fuzzy: false,
},
},
});