Package Exports
- creatorsarea-ts
Readme
creatorsarea-ts
Unofficial TypeScript/JavaScript SDK for the CreatorsArea.fr job marketplace API
⚠️ Note: This is an unofficial SDK. It is not affiliated with, endorsed by, or officially connected to CreatorsArea.fr.
Features
✅ Full TypeScript support with complete type definitions
✅ Fluent Query Builder API for complex queries
✅ Modern async/await API
✅ Tree-shakeable ESM and CJS builds
✅ Zero dependencies (uses native fetch)
✅ Lightweight (~16KB minified)
✅ Browser and Node.js compatible (Node 18+)
✅ Complete API coverage (jobs, tags, pagination)
✅ Rate limit protection with automatic delays
✅ Memory-efficient streaming for large datasets
Installation
# pnpm (recommended)
pnpm add creatorsarea-ts
# npm
npm install creatorsarea-ts
# yarn
yarn add creatorsarea-ts
# bun
bun add creatorsarea-tsQuick Start
import { CreatorsAreaClient } from 'creatorsarea-ts';
const client = new CreatorsAreaClient();
// Method 1: Direct API call
const { results, pagination } = await client.getJobs({
category: 'DESIGNER',
});
// Method 2: Query Builder (recommended for complex queries)
const jobs = await client
.query()
.category('DESIGNER')
.volunteer(true)
.status(JobStatus.ACTIVE)
.execute();
console.log(`Found ${jobs.results.length} jobs`);
console.log(`Page ${jobs.pagination.page + 1} of ${jobs.pagination.totalPages}`);Usage
Basic Example
import { CreatorsAreaClient, JobKind } from 'creatorsarea-ts';
const client = new CreatorsAreaClient();
// Fetch designer jobs
const { results: jobs, pagination } = await client.getJobs({
category: 'DESIGNER',
kind: JobKind.DESIGNER,
});
jobs.forEach(job => {
console.log(`${job.title} - ${job.pricing.volunteer ? 'Volunteer' : `${job.pricing.value}€`}`);
console.log(`URL: ${client.getJobUrl(job)}`);
});
console.log(`Total jobs: ${pagination.totalItems}`);Get All Available Tags
import { CreatorsAreaClient } from 'creatorsarea-ts';
const client = new CreatorsAreaClient();
// Get all tags
const tags = await client.getTags();
tags.forEach(tag => {
console.log(`${tag.name} - ${tag.image}`);
});Filter by Tags
import { CreatorsAreaClient } from 'creatorsarea-ts';
const client = new CreatorsAreaClient();
// Get all tags first
const tags = await client.getTags();
// Find specific tag
const minecraftTag = tags.find(t => t.name.includes('Minecraft'));
if (minecraftTag) {
// Get jobs with this tag
const { results } = await client.getJobs({
tags: [minecraftTag._id],
});
console.log(`Found ${results.length} Minecraft jobs`);
}Pagination
import { CreatorsAreaClient } from 'creatorsarea-ts';
const client = new CreatorsAreaClient();
// Get first page (page is 0-indexed)
const page1 = await client.getJobs({ page: 0 });
console.log(`Page 1: ${page1.results.length} jobs`);
// Get second page
const page2 = await client.getJobs({ page: 1 });
console.log(`Page 2: ${page2.results.length} jobs`);
// Get all pages
async function getAllJobs() {
const allJobs = [];
let page = 0;
let hasMore = true;
while (hasMore) {
const { results, pagination } = await client.getJobs({ page });
allJobs.push(...results);
hasMore = page < pagination.totalPages - 1;
page++;
}
return allJobs;
}Advanced Example
import { CreatorsAreaClient, JobKind, JobStatus, type Job } from 'creatorsarea-ts';
// Custom configuration
const client = new CreatorsAreaClient({
timeout: 15000, // 15 seconds
userAgent: 'MyApp/1.0',
});
try {
// Get active team jobs
const { results, pagination } = await client.getJobs({
kind: JobKind.TEAM,
status: JobStatus.ACTIVE,
});
// Filter paid jobs
const paidJobs = results.filter((job: Job) => !job.pricing.volunteer);
// Get specific job by ID
const job = await client.getJobById('691f3358bab478f06fd144d3');
if (job) {
console.log(`Title: ${job.title}`);
console.log(`Content: ${job.content}`);
console.log(`Tags: ${job._tags.map(t => t.name).join(', ')}`);
console.log(`Author Discord: ${job.author.discord_id}`);
}
} catch (error) {
if (error instanceof APIError) {
console.error('API Error:', error.statusCode);
}
}React Hook Example
import { useQuery } from '@tanstack/react-query';
import { CreatorsAreaClient, type GetJobsOptions } from 'creatorsarea-ts';
const client = new CreatorsAreaClient();
function useJobs(options?: GetJobsOptions) {
return useQuery({
queryKey: ['creatorsarea-jobs', options],
queryFn: () => client.getJobs(options),
});
}
// In your component
function JobsList() {
const { data, isLoading } = useJobs({ volunteer: true });
if (isLoading) return <div>Loading...</div>;
return (
<div>
<p>Page {data.pagination.page + 1} of {data.pagination.totalPages}</p>
<ul>
{data.results.map(job => (
<li key={job._id}>
<a href={client.getJobUrl(job)}>{job.title}</a>
<span>{job._tags.map(t => t.name).join(', ')}</span>
</li>
))}
</ul>
</div>
);
}Query Builder API
The query builder provides a fluent interface for constructing complex queries:
Basic Usage
const { results, pagination } = await client
.query()
.category('DESIGNER')
.volunteer(false)
.status(JobStatus.ACTIVE)
.page(0)
.execute();Available Methods
Filtering Methods
// Filter by category
.category('DEVELOPER' | 'DESIGNER' | 'EDITOR' | 'TEAM')
// Filter by volunteer status
.volunteer(true | false)
// Filter by job status
.status(JobStatus.ACTIVE | JobStatus.CLOSED | JobStatus.DRAFT)
// Filter by job kind
.kind(JobKind.TEAM | JobKind.DEVELOPER | JobKind.DESIGNER | JobKind.EDITOR)
// Filter by tags
.tags(['tagId1', 'tagId2'])
.addTag('tagId') // Add single tag
// Set page number
.page(0) // 0-indexedExecution Methods
// Execute and get results + pagination
const { results, pagination } = await query.execute();
// Execute and get only results array
const jobs = await query.executeAndGetResults();
// Count total matching jobs without fetching all
const count = await query.count();
// Get all jobs from all pages (with rate limit protection)
const allJobs = await query.executeAll(delayMs?);
// Stream jobs for memory-efficient processing
for await (const job of query.stream(delayMs?)) {
console.log(job.title);
}Utility Methods
// Build options without executing
const options = query.build();
// Reset all filters
query.reset();Query Builder Examples
import { CreatorsAreaClient, JobStatus } from 'creatorsarea-ts';
const client = new CreatorsAreaClient();
// Example 1: Count jobs without fetching
const designerCount = await client
.query()
.category('DESIGNER')
.volunteer(false)
.count();
console.log(`${designerCount} paid designer jobs available`);
// Example 2: Get all jobs from all pages
const allDesignerJobs = await client
.query()
.category('DESIGNER')
.executeAll(1000); // 1 second delay between requests
console.log(`Retrieved ${allDesignerJobs.length} total jobs`);
// Example 3: Stream jobs efficiently
for await (const job of client.query().volunteer(true).stream()) {
console.log(`${job.title} - ${job.pricing.value}€`);
// Process each job as it comes
}
// Example 4: Dynamic tag filtering
const tags = await client.getTags();
const webTag = tags.find(t => t.name.includes('Site web'));
const jobs = await client
.query()
.category('DEVELOPER')
.addTag(webTag._id)
.executeAndGetResults();
// Example 5: Reuse and reset
const builder = client.query().category('DESIGNER');
const paidCount = await builder.volunteer(false).count();
builder.reset();
const volunteerCount = await builder.category('DESIGNER').volunteer(true).count();API Reference
CreatorsAreaClient
Constructor
new CreatorsAreaClient(config?: ClientConfig)Options:
baseUrl?: string- API base URL (default:https://creatorsarea.fr/api)userAgent?: string- Custom User-Agent headertimeout?: number- Request timeout in ms (default:10000)
Methods
getTags(): Promise<Tag[]>
Fetch all available tags/categories.
Returns: Promise<Tag[]>
Throws:
APIError- API request failedNetworkError- Network errorValidationError- Invalid response
Example:
const tags = await client.getTags();getJobs(options?): Promise<{ results: Job[], pagination: Pagination }>
Fetch jobs from CreatorsArea with pagination.
Parameters:
options.volunteer?: boolean- Filter by volunteer jobs onlyoptions.page?: number- Page number (0-indexed)options.tags?: string | string[]- Filter by tag ID(s)options.kind?: JobKind- Filter by job kind (TEAM, DEVELOPER, DESIGNER, EDITOR)options.status?: JobStatus- Filter by job status (ACTIVE, CLOSED, DRAFT)options.category?: 'DEVELOPER' | 'DESIGNER' | 'EDITOR' | 'TEAM'- Filter by category
Returns: Promise<{ results: Job[], pagination: Pagination }>
Throws:
APIError- API request failedNetworkError- Network errorValidationError- Invalid response
Example:
const { results, pagination } = await client.getJobs({
category: 'DESIGNER',
volunteer: false,
page: 0,
});getJobById(jobId): Promise<Job | null>
Get a single job by ID.
Parameters:
jobId: string- Job ID (MongoDB ObjectId)
Returns: Promise<Job | null>
Example:
const job = await client.getJobById('691f3358bab478f06fd144d3');getJobUrl(job): string
Build the full URL for a job listing.
Parameters:
job: Job | string- Job object or slug
Returns: string
Example:
const url = client.getJobUrl(job);
// Returns: https://creatorsarea.fr/offres/associatif-responsable-communication-hfTypes
Job
interface Job {
_id: string; // MongoDB ObjectId
title: string; // Job title
slug: string; // URL-friendly slug
content: string; // Job description
author: Author; // Author information
pricing: Pricing; // Pricing information
kind: JobKind; // TEAM or COMMISSION
status: JobStatus; // ACTIVE, CLOSED, or DRAFT
deadline: string | null; // Application deadline
_tags: Tag[]; // Associated tags
threads: string[]; // Thread IDs
thread?: string; // Discord thread ID
alerts: Alert[]; // Alert information
createdAt: string; // Creation timestamp
updatedAt: string; // Update timestamp
}Tag
interface Tag {
_id: string; // Tag ID
name: string; // Display name
image: string; // Image URL
isActive?: boolean; // Whether active
createdAt?: string; // Creation date
updatedAt?: string; // Update date
}Pricing
interface Pricing {
value: number; // Price (0 if volunteer)
volunteer: boolean; // Is volunteer work
negotiable: boolean; // Is price negotiable
}Author
interface Author {
_id: string; // User ID
avatar: string; // Avatar hash
discord_id: string; // Discord user ID
}Pagination
interface Pagination {
totalItems: number; // Total number of items
totalPages: number; // Total number of pages
page: number; // Current page (0-indexed)
}JobKind
enum JobKind {
TEAM = 'TEAM', // Team recruitment
DEVELOPER = 'DEVELOPER', // Developer category
DESIGNER = 'DESIGNER', // Designer category
EDITOR = 'EDITOR', // Editor category
}JobStatus
enum JobStatus {
ACTIVE = 'ACTIVE', // Active job
CLOSED = 'CLOSED', // Closed job
DRAFT = 'DRAFT', // Draft job
}Exceptions
import {
CreatorsAreaError, // Base exception
APIError, // API request failed
NetworkError, // Network error
ValidationError, // Invalid response
} from 'creatorsarea-ts';Browser Support
Works in all modern browsers with fetch API support:
- Chrome/Edge 90+
- Firefox 90+
- Safari 14+
- Node.js 18+ (native fetch)
For older browsers, use a fetch polyfill like whatwg-fetch.
Framework Examples
Next.js (App Router)
// app/jobs/page.tsx
import { CreatorsAreaClient } from 'creatorsarea-ts';
export default async function JobsPage() {
const client = new CreatorsAreaClient();
const { results: jobs } = await client.getJobs({ volunteer: true });
return (
<ul>
{jobs.map(job => (
<li key={job._id}>
<a href={client.getJobUrl(job)}>{job.title}</a>
</li>
))}
</ul>
);
}Express.js
import express from 'express';
import { CreatorsAreaClient } from 'creatorsarea-ts';
const app = express();
const client = new CreatorsAreaClient();
app.get('/api/jobs', async (req, res) => {
try {
const data = await client.getJobs();
res.json(data);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch jobs' });
}
});Development
# Install dependencies
pnpm install
# Type check
pnpm typecheck
# Build the package
pnpm build
# Run examples
pnpm tsx examples/basic.ts
pnpm tsx examples/advanced.ts
pnpm tsx examples/query-builder-simple.tsPackage Structure
src/
├── client.ts # Main API client
├── query-builder.ts # Query builder implementation
├── types.ts # TypeScript type definitions
├── exceptions.ts # Custom error classes
└── index.ts # Public API exports
examples/
├── basic.ts # Basic usage examples
├── advanced.ts # Advanced features demo
└── query-builder-simple.ts # Query builder demoBest Practices
Rate Limiting
The API has rate limits. Use the built-in delays in query builder methods:
// Fetch all pages with 1 second delay
const allJobs = await client.query()
.category('DESIGNER')
.executeAll(1000);
// Stream with automatic delays
for await (const job of client.query().volunteer(true).stream(800)) {
// Process job
}Memory Efficiency
For large datasets, prefer streaming over loading all at once:
// ❌ Not recommended for large datasets
const allJobs = await client.query().executeAll();
// ✅ Recommended - memory efficient
for await (const job of client.query().stream()) {
processJob(job);
}Error Handling
Always handle API errors appropriately:
import { APIError, NetworkError, ValidationError } from 'creatorsarea-ts';
try {
const jobs = await client.getJobs();
} catch (error) {
if (error instanceof APIError) {
console.error(`API Error ${error.statusCode}:`, error.message);
} else if (error instanceof NetworkError) {
console.error('Network error:', error.message);
} else if (error instanceof ValidationError) {
console.error('Invalid data:', error.message);
}
}API Reference Summary
Client Methods
| Method | Description |
|---|---|
getJobs(options?) |
Fetch jobs with optional filters |
getTags() |
Get all available tags |
getJobById(id) |
Get a specific job by ID |
getJobUrl(job) |
Build job URL from job or slug |
query() |
Create a query builder |
Query Builder Methods
| Method | Description |
|---|---|
category(cat) |
Filter by category |
volunteer(bool) |
Filter by volunteer status |
status(status) |
Filter by job status |
kind(kind) |
Filter by job kind |
tags(ids) |
Filter by tag IDs |
addTag(id) |
Add a single tag filter |
page(num) |
Set page number |
execute() |
Execute query |
executeAndGetResults() |
Get only results array |
executeAll(delay?) |
Get all pages |
stream(delay?) |
Stream results |
count() |
Count matching jobs |
build() |
Build options object |
reset() |
Reset all filters |
Contributing
Contributions are welcome! Please open an issue or PR.
License
MIT © Sycatle
Links
Made with ❤️ for the freelance community