penguin-beta-version-1.5 #3

Merged
anonpenguin merged 30 commits from sotiris-beta-version-1.5 into main 2025-07-05 02:53:30 +00:00
49 changed files with 17276 additions and 0 deletions
Showing only changes of commit 067e462339 - Show all commits

20
docs/.gitignore vendored Normal file
View File

@ -0,0 +1,20 @@
# Dependencies
/node_modules
# Production
/build
# Generated files
.docusaurus
.cache-loader
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

41
docs/README.md Normal file
View File

@ -0,0 +1,41 @@
# Website
This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
## Installation
```bash
yarn
```
## Local Development
```bash
yarn start
```
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
## Build
```bash
yarn build
```
This command generates static content into the `build` directory and can be served using any static contents hosting service.
## Deployment
Using SSH:
```bash
USE_SSH=true yarn deploy
```
Not using SSH:
```bash
GIT_USER=<Your GitHub username> yarn deploy
```
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.

View File

@ -0,0 +1,12 @@
---
slug: first-blog-post
title: First Blog Post
authors: [slorber, yangshun]
tags: [hola, docusaurus]
---
Lorem ipsum dolor sit amet...
<!-- truncate -->
...consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

View File

@ -0,0 +1,44 @@
---
slug: long-blog-post
title: Long Blog Post
authors: yangshun
tags: [hello, docusaurus]
---
This is the summary of a very long blog post,
Use a `<!--` `truncate` `-->` comment to limit blog post size in the list view.
<!-- truncate -->
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet

View File

@ -0,0 +1,24 @@
---
slug: mdx-blog-post
title: MDX Blog Post
authors: [slorber]
tags: [docusaurus]
---
Blog posts support [Docusaurus Markdown features](https://docusaurus.io/docs/markdown-features), such as [MDX](https://mdxjs.com/).
:::tip
Use the power of React to create interactive blog posts.
:::
{/* truncate */}
For example, use JSX to create an interactive button:
```js
<button onClick={() => alert('button clicked!')}>Click me!</button>
```
<button onClick={() => alert('button clicked!')}>Click me!</button>

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

View File

@ -0,0 +1,29 @@
---
slug: welcome
title: Welcome
authors: [slorber, yangshun]
tags: [facebook, hello, docusaurus]
---
[Docusaurus blogging features](https://docusaurus.io/docs/blog) are powered by the [blog plugin](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-blog).
Here are a few tips you might find useful.
<!-- truncate -->
Simply add Markdown files (or folders) to the `blog` directory.
Regular blog authors can be added to `authors.yml`.
The blog post date can be extracted from filenames, such as:
- `2019-05-30-welcome.md`
- `2019-05-30-welcome/index.md`
A blog post folder can be convenient to co-locate blog post images:
![Docusaurus Plushie](./docusaurus-plushie-banner.jpeg)
The blog supports tags as well!
**And if you don't want a blog**: just delete this directory, and use `blog: false` in your Docusaurus config.

25
docs/blog/authors.yml Normal file
View File

@ -0,0 +1,25 @@
yangshun:
name: Yangshun Tay
title: Ex-Meta Staff Engineer, Co-founder GreatFrontEnd
url: https://linkedin.com/in/yangshun
image_url: https://github.com/yangshun.png
page: true
socials:
x: yangshunz
linkedin: yangshun
github: yangshun
newsletter: https://www.greatfrontend.com
slorber:
name: Sébastien Lorber
title: Docusaurus maintainer
url: https://sebastienlorber.com
image_url: https://github.com/slorber.png
page:
# customize the url of the author page at /blog/authors/<permalink>
permalink: '/all-sebastien-lorber-articles'
socials:
x: sebastienlorber
linkedin: sebastienlorber
github: slorber
newsletter: https://thisweekinreact.com

19
docs/blog/tags.yml Normal file
View File

@ -0,0 +1,19 @@
facebook:
label: Facebook
permalink: /facebook
description: Facebook tag description
hello:
label: Hello
permalink: /hello
description: Hello tag description
docusaurus:
label: Docusaurus
permalink: /docusaurus
description: Docusaurus tag description
hola:
label: Hola
permalink: /hola
description: Hola tag description

View File

@ -0,0 +1,629 @@
---
sidebar_position: 2
---
# DebrosFramework Class
The `DebrosFramework` class is the main entry point for the framework. It handles initialization, configuration, and lifecycle management of all framework components.
## Class Definition
```typescript
class DebrosFramework {
constructor(config?: Partial<DebrosFrameworkConfig>);
// Initialization
async initialize(
orbitDBService?: any,
ipfsService?: any,
overrideConfig?: Partial<DebrosFrameworkConfig>,
): Promise<void>;
// Lifecycle management
async start(): Promise<void>;
async stop(): Promise<void>;
// Component access
getDatabaseManager(): DatabaseManager;
getShardManager(): ShardManager;
getQueryExecutor(): QueryExecutor;
getRelationshipManager(): RelationshipManager;
getMigrationManager(): MigrationManager;
// Configuration
getConfig(): DebrosFrameworkConfig;
updateConfig(config: Partial<DebrosFrameworkConfig>): void;
// Status
isInitialized(): boolean;
isRunning(): boolean;
getStatus(): FrameworkStatus;
}
```
## Constructor
### new DebrosFramework(config?)
Creates a new instance of the DebrosFramework.
**Parameters:**
- `config` (optional): Partial framework configuration
**Example:**
```typescript
import { DebrosFramework } from 'debros-framework';
// Default configuration
const framework = new DebrosFramework();
// Custom configuration
const framework = new DebrosFramework({
cache: {
enabled: true,
maxSize: 5000,
ttl: 600000, // 10 minutes
},
queryOptimization: {
enabled: true,
cacheQueries: true,
parallelExecution: true,
},
development: {
logLevel: 'debug',
enableMetrics: true,
},
});
```
## Initialization Methods
### initialize(orbitDBService?, ipfsService?, overrideConfig?)
Initializes the framework with OrbitDB and IPFS services.
**Parameters:**
- `orbitDBService` (optional): OrbitDB service instance
- `ipfsService` (optional): IPFS service instance
- `overrideConfig` (optional): Configuration overrides
**Returns:** `Promise<void>`
**Throws:** `DebrosFrameworkError` if initialization fails
**Example:**
```typescript
import { DebrosFramework } from 'debros-framework';
import { setupOrbitDB, setupIPFS } from './services';
async function initializeFramework() {
const framework = new DebrosFramework();
// Setup external services
const orbitDBService = await setupOrbitDB();
const ipfsService = await setupIPFS();
// Initialize framework
await framework.initialize(orbitDBService, ipfsService, {
cache: { enabled: true },
development: { logLevel: 'info' },
});
console.log('Framework initialized successfully');
return framework;
}
```
**With existing services:**
```typescript
async function initializeWithExistingServices() {
const framework = new DebrosFramework();
// Use existing services from your application
const existingOrbitDB = app.orbitDB;
const existingIPFS = app.ipfs;
await framework.initialize(existingOrbitDB, existingIPFS);
return framework;
}
```
**Environment-specific configuration:**
```typescript
async function initializeForEnvironment(env: 'development' | 'production' | 'test') {
const framework = new DebrosFramework();
const configs = {
development: {
development: { logLevel: 'debug', enableMetrics: true },
cache: { enabled: true, maxSize: 1000 },
},
production: {
development: { logLevel: 'error', enableMetrics: false },
cache: { enabled: true, maxSize: 10000 },
queryOptimization: { enabled: true, parallelExecution: true },
},
test: {
development: { logLevel: 'warn', enableMetrics: false },
cache: { enabled: false },
},
};
await framework.initialize(await setupOrbitDB(env), await setupIPFS(env), configs[env]);
return framework;
}
```
## Lifecycle Methods
### start()
Starts the framework and all its components.
**Returns:** `Promise<void>`
**Example:**
```typescript
const framework = new DebrosFramework();
await framework.initialize(orbitDBService, ipfsService);
await framework.start();
console.log('Framework is now running');
```
### stop()
Gracefully stops the framework and cleans up resources.
**Returns:** `Promise<void>`
**Example:**
```typescript
// Graceful shutdown
process.on('SIGINT', async () => {
console.log('Shutting down framework...');
await framework.stop();
console.log('Framework stopped');
process.exit(0);
});
// Manual stop
await framework.stop();
```
## Component Access Methods
### getDatabaseManager()
Returns the database manager instance.
**Returns:** `DatabaseManager`
**Example:**
```typescript
const databaseManager = framework.getDatabaseManager();
// Get user database
const userDB = await databaseManager.getUserDatabase('user123', 'Post');
// Get global database
const globalDB = await databaseManager.getGlobalDatabase('User');
```
### getShardManager()
Returns the shard manager instance.
**Returns:** `ShardManager`
**Example:**
```typescript
const shardManager = framework.getShardManager();
// Get shard for data
const shard = shardManager.getShardForData('Post', { userId: 'user123' });
// Get all shards for model
const shards = shardManager.getShardsForModel('Post');
```
### getQueryExecutor()
Returns the query executor instance.
**Returns:** `QueryExecutor`
**Example:**
```typescript
const queryExecutor = framework.getQueryExecutor();
// Execute custom query
const results = await queryExecutor.execute(queryBuilder, {
timeout: 10000,
useCache: true,
});
```
### getRelationshipManager()
Returns the relationship manager instance.
**Returns:** `RelationshipManager`
**Example:**
```typescript
const relationshipManager = framework.getRelationshipManager();
// Load relationship
const posts = await relationshipManager.loadRelationship(user, 'posts', { eager: true });
```
### getMigrationManager()
Returns the migration manager instance.
**Returns:** `MigrationManager`
**Example:**
```typescript
const migrationManager = framework.getMigrationManager();
// Run pending migrations
await migrationManager.runPendingMigrations();
// Get migration status
const status = migrationManager.getActiveMigrations();
```
## Configuration Methods
### getConfig()
Returns the current framework configuration.
**Returns:** `DebrosFrameworkConfig`
**Example:**
```typescript
const config = framework.getConfig();
console.log('Cache enabled:', config.cache?.enabled);
console.log('Log level:', config.development?.logLevel);
```
### updateConfig(config)
Updates the framework configuration.
**Parameters:**
- `config`: Partial configuration to merge
**Example:**
```typescript
// Update cache settings
framework.updateConfig({
cache: {
enabled: true,
maxSize: 2000,
ttl: 300000,
},
});
// Update development settings
framework.updateConfig({
development: {
logLevel: 'debug',
},
});
```
## Status Methods
### isInitialized()
Checks if the framework has been initialized.
**Returns:** `boolean`
**Example:**
```typescript
if (!framework.isInitialized()) {
await framework.initialize(orbitDBService, ipfsService);
}
```
### isRunning()
Checks if the framework is currently running.
**Returns:** `boolean`
**Example:**
```typescript
if (framework.isRunning()) {
console.log('Framework is active');
} else {
await framework.start();
}
```
### getStatus()
Returns detailed framework status information.
**Returns:** `FrameworkStatus`
**Example:**
```typescript
const status = framework.getStatus();
console.log('Framework Status:', {
initialized: status.initialized,
running: status.running,
uptime: status.uptime,
version: status.version,
components: status.components,
metrics: status.metrics,
});
```
## Configuration Interface
### DebrosFrameworkConfig
```typescript
interface DebrosFrameworkConfig {
// Cache configuration
cache?: {
enabled?: boolean; // Enable/disable caching
maxSize?: number; // Maximum cache entries
ttl?: number; // Time to live in milliseconds
cleanupInterval?: number; // Cleanup interval in milliseconds
};
// Query optimization
queryOptimization?: {
enabled?: boolean; // Enable query optimization
cacheQueries?: boolean; // Cache query results
parallelExecution?: boolean; // Execute queries in parallel
maxConcurrent?: number; // Max concurrent queries
timeout?: number; // Query timeout in milliseconds
};
// Automatic pinning
automaticPinning?: {
enabled?: boolean; // Enable automatic pinning
strategy?: 'popularity' | 'fixed' | 'tiered'; // Pinning strategy
factor?: number; // Pinning factor
maxPins?: number; // Maximum pins
evaluationInterval?: number; // Evaluation interval in milliseconds
};
// PubSub configuration
pubsub?: {
enabled?: boolean; // Enable PubSub
bufferSize?: number; // Event buffer size
maxRetries?: number; // Max retry attempts
retryDelay?: number; // Retry delay in milliseconds
};
// Sharding configuration
sharding?: {
defaultStrategy?: 'hash' | 'range' | 'user'; // Default sharding strategy
defaultCount?: number; // Default shard count
maxShards?: number; // Maximum shards per model
};
// Development settings
development?: {
logLevel?: 'debug' | 'info' | 'warn' | 'error'; // Logging level
enableMetrics?: boolean; // Enable metrics collection
enableProfiling?: boolean; // Enable performance profiling
mockMode?: boolean; // Enable mock mode for testing
};
// Network configuration
network?: {
connectionTimeout?: number; // Connection timeout in milliseconds
retryAttempts?: number; // Number of retry attempts
retryDelay?: number; // Delay between retries in milliseconds
};
}
```
### FrameworkStatus
```typescript
interface FrameworkStatus {
initialized: boolean;
running: boolean;
version: string;
uptime: number;
components: {
databaseManager: ComponentStatus;
shardManager: ComponentStatus;
queryExecutor: ComponentStatus;
relationshipManager: ComponentStatus;
migrationManager: ComponentStatus;
pinningManager: ComponentStatus;
pubsubManager: ComponentStatus;
};
metrics?: {
queriesExecuted: number;
cacheHits: number;
cacheMisses: number;
averageQueryTime: number;
activeConnections: number;
memoryUsage: number;
};
}
interface ComponentStatus {
status: 'active' | 'inactive' | 'error';
lastActivity?: number;
errorCount?: number;
lastError?: string;
}
```
## Error Handling
### Common Errors
```typescript
try {
await framework.initialize(orbitDBService, ipfsService);
} catch (error) {
if (error instanceof DebrosFrameworkError) {
switch (error.code) {
case 'INITIALIZATION_FAILED':
console.error('Framework initialization failed:', error.message);
break;
case 'SERVICE_NOT_AVAILABLE':
console.error('Required service not available:', error.details);
break;
case 'CONFIGURATION_ERROR':
console.error('Configuration error:', error.message);
break;
default:
console.error('Unknown framework error:', error);
}
} else {
console.error('Unexpected error:', error);
}
}
```
## Complete Example
### Production Application Setup
```typescript
import { DebrosFramework } from 'debros-framework';
import { setupOrbitDB, setupIPFS } from './services';
import { User, Post, Comment } from './models';
class Application {
private framework: DebrosFramework;
async initialize() {
// Create framework with production configuration
this.framework = new DebrosFramework({
cache: {
enabled: true,
maxSize: 10000,
ttl: 600000, // 10 minutes
},
queryOptimization: {
enabled: true,
cacheQueries: true,
parallelExecution: true,
maxConcurrent: 20,
timeout: 30000,
},
automaticPinning: {
enabled: true,
strategy: 'popularity',
factor: 3,
maxPins: 1000,
},
development: {
logLevel: 'info',
enableMetrics: true,
},
});
// Setup services
const orbitDBService = await setupOrbitDB();
const ipfsService = await setupIPFS();
// Initialize framework
await this.framework.initialize(orbitDBService, ipfsService);
await this.framework.start();
console.log('Application initialized successfully');
// Log status
const status = this.framework.getStatus();
console.log('Framework status:', status);
}
async shutdown() {
if (this.framework) {
await this.framework.stop();
console.log('Application shutdown complete');
}
}
getFramework(): DebrosFramework {
return this.framework;
}
}
// Usage
const app = new Application();
async function main() {
try {
await app.initialize();
// Your application logic here
const framework = app.getFramework();
// Create some data
const user = await User.create({
username: 'alice',
email: 'alice@example.com',
});
const post = await Post.create({
title: 'Hello DebrosFramework',
content: 'This is my first post!',
authorId: user.id,
});
console.log('Created user and post successfully');
} catch (error) {
console.error('Application failed:', error);
}
}
// Graceful shutdown
process.on('SIGINT', async () => {
console.log('Received SIGINT, shutting down gracefully...');
await app.shutdown();
process.exit(0);
});
process.on('SIGTERM', async () => {
console.log('Received SIGTERM, shutting down gracefully...');
await app.shutdown();
process.exit(0);
});
main().catch(console.error);
```
This comprehensive API reference for the DebrosFramework class covers all public methods, configuration options, and usage patterns for initializing and managing the framework in your applications.

453
docs/docs/api/overview.md Normal file
View File

@ -0,0 +1,453 @@
---
sidebar_position: 1
---
# API Reference Overview
The DebrosFramework API provides a comprehensive set of classes, methods, and interfaces for building decentralized applications. This reference covers all public APIs, their parameters, return types, and usage examples.
## Core Framework Classes
### Primary Classes
| Class | Description | Key Features |
| ----------------------------------------------- | ---------------------------- | ------------------------------------ |
| [`DebrosFramework`](./debros-framework) | Main framework class | Initialization, lifecycle management |
| [`BaseModel`](./base-model) | Abstract base for all models | CRUD operations, validation, hooks |
| [`DatabaseManager`](./database-manager) | Database management | User/global databases, lifecycle |
| [`ShardManager`](./shard-manager) | Data sharding | Distribution strategies, routing |
| [`QueryBuilder`](./query-builder) | Query construction | Fluent API, type safety |
| [`QueryExecutor`](./query-executor) | Query execution | Optimization, caching |
| [`RelationshipManager`](./relationship-manager) | Relationship handling | Lazy/eager loading, caching |
| [`MigrationManager`](./migration-manager) | Schema migrations | Version control, rollbacks |
| [`MigrationBuilder`](./migration-builder) | Migration creation | Fluent API, validation |
### Utility Classes
| Class | Description | Use Case |
| ------------------- | ------------------------ | ------------------------------ |
| `ModelRegistry` | Model registration | Framework initialization |
| `ConfigManager` | Configuration management | Environment-specific settings |
| `PinningManager` | Automatic pinning | Data availability optimization |
| `PubSubManager` | Event publishing | Real-time features |
| `QueryCache` | Query result caching | Performance optimization |
| `QueryOptimizer` | Query optimization | Automatic performance tuning |
| `RelationshipCache` | Relationship caching | Relationship performance |
| `LazyLoader` | Lazy loading | On-demand data loading |
## Decorators
### Model Decorators
| Decorator | Purpose | Usage |
| ------------------------------ | -------------------------- | ------------------ |
| [`@Model`](./decorators/model) | Define model configuration | Class decorator |
| [`@Field`](./decorators/field) | Define field properties | Property decorator |
### Relationship Decorators
| Decorator | Relationship Type | Usage |
| ------------------------------------------------------ | ----------------- | ------------------ |
| [`@BelongsTo`](./decorators/relationships#belongsto) | Many-to-one | Property decorator |
| [`@HasMany`](./decorators/relationships#hasmany) | One-to-many | Property decorator |
| [`@HasOne`](./decorators/relationships#hasone) | One-to-one | Property decorator |
| [`@ManyToMany`](./decorators/relationships#manytomany) | Many-to-many | Property decorator |
### Hook Decorators
| Decorator | Trigger | Usage |
| -------------------------------------------------- | --------------------------- | ---------------- |
| [`@BeforeCreate`](./decorators/hooks#beforecreate) | Before creating record | Method decorator |
| [`@AfterCreate`](./decorators/hooks#aftercreate) | After creating record | Method decorator |
| [`@BeforeUpdate`](./decorators/hooks#beforeupdate) | Before updating record | Method decorator |
| [`@AfterUpdate`](./decorators/hooks#afterupdate) | After updating record | Method decorator |
| [`@BeforeDelete`](./decorators/hooks#beforedelete) | Before deleting record | Method decorator |
| [`@AfterDelete`](./decorators/hooks#afterdelete) | After deleting record | Method decorator |
| [`@BeforeSave`](./decorators/hooks#beforesave) | Before save (create/update) | Method decorator |
| [`@AfterSave`](./decorators/hooks#aftersave) | After save (create/update) | Method decorator |
## Type Definitions
### Core Types
```typescript
// Model configuration
interface ModelConfig {
scope: 'user' | 'global';
type: StoreType;
sharding?: ShardingConfig;
pinning?: PinningConfig;
pubsub?: PubSubConfig;
validation?: ValidationConfig;
}
// Field configuration
interface FieldConfig {
type: FieldType;
required?: boolean;
unique?: boolean;
default?: any | (() => any);
validate?: (value: any) => boolean;
transform?: (value: any) => any;
serialize?: boolean;
index?: boolean;
virtual?: boolean;
}
// Query types
interface QueryOptions {
where?: WhereClause[];
orderBy?: OrderByClause[];
limit?: number;
offset?: number;
with?: string[];
cache?: boolean | number;
}
```
### Enum Types
```typescript
// Store types
type StoreType = 'docstore' | 'eventlog' | 'keyvalue' | 'counter' | 'feed';
// Field types
type FieldType = 'string' | 'number' | 'boolean' | 'array' | 'object' | 'date';
// Sharding strategies
type ShardingStrategy = 'hash' | 'range' | 'user';
// Query operators
type QueryOperator =
| 'eq'
| 'ne'
| 'gt'
| 'gte'
| 'lt'
| 'lte'
| 'in'
| 'not in'
| 'like'
| 'regex'
| 'is null'
| 'is not null'
| 'includes'
| 'includes any'
| 'includes all';
```
## Configuration Interfaces
### Framework Configuration
```typescript
interface DebrosFrameworkConfig {
// Cache configuration
cache?: {
enabled?: boolean;
maxSize?: number;
ttl?: number;
};
// Query optimization
queryOptimization?: {
enabled?: boolean;
cacheQueries?: boolean;
parallelExecution?: boolean;
};
// Automatic features
automaticPinning?: {
enabled?: boolean;
strategy?: 'popularity' | 'fixed' | 'tiered';
};
// PubSub configuration
pubsub?: {
enabled?: boolean;
bufferSize?: number;
};
// Development settings
development?: {
logLevel?: 'debug' | 'info' | 'warn' | 'error';
enableMetrics?: boolean;
};
}
```
### Sharding Configuration
```typescript
interface ShardingConfig {
strategy: ShardingStrategy;
count: number;
key: string;
ranges?: ShardRange[];
}
interface ShardRange {
min: any;
max: any;
shard: number;
}
```
### Pinning Configuration
```typescript
interface PinningConfig {
strategy: 'fixed' | 'popularity' | 'tiered';
factor?: number;
maxPins?: number;
ttl?: number;
}
```
## Error Types
### Framework Errors
```typescript
class DebrosFrameworkError extends Error {
code: string;
details?: any;
}
class ValidationError extends DebrosFrameworkError {
field: string;
value: any;
constraint: string;
}
class QueryError extends DebrosFrameworkError {
query: string;
parameters?: any[];
}
class RelationshipError extends DebrosFrameworkError {
modelName: string;
relationshipName: string;
relatedModel: string;
}
```
## Response Types
### Query Results
```typescript
interface QueryResult<T> {
data: T[];
total: number;
page?: number;
perPage?: number;
totalPages?: number;
hasMore?: boolean;
}
interface PaginationInfo {
page: number;
perPage: number;
total: number;
totalPages: number;
hasNext: boolean;
hasPrev: boolean;
}
```
### Operation Results
```typescript
interface CreateResult<T> {
model: T;
created: boolean;
errors?: ValidationError[];
}
interface UpdateResult<T> {
model: T;
updated: boolean;
changes: string[];
errors?: ValidationError[];
}
interface DeleteResult {
deleted: boolean;
id: string;
}
```
## Event Types
### Model Events
```typescript
interface ModelEvent {
type: 'create' | 'update' | 'delete';
modelName: string;
modelId: string;
data?: any;
changes?: string[];
timestamp: number;
userId?: string;
}
interface RelationshipEvent {
type: 'attach' | 'detach';
modelName: string;
modelId: string;
relationshipName: string;
relatedModelName: string;
relatedModelId: string;
timestamp: number;
}
```
### Framework Events
```typescript
interface FrameworkEvent {
type: 'initialized' | 'stopped' | 'error';
message?: string;
error?: Error;
timestamp: number;
}
interface DatabaseEvent {
type: 'created' | 'opened' | 'closed';
databaseName: string;
scope: 'user' | 'global';
userId?: string;
timestamp: number;
}
```
## Migration Types
### Migration Configuration
```typescript
interface Migration {
id: string;
version: string;
name: string;
description: string;
targetModels: string[];
up: MigrationOperation[];
down: MigrationOperation[];
dependencies?: string[];
validators?: MigrationValidator[];
createdAt: number;
}
interface MigrationOperation {
type:
| 'add_field'
| 'remove_field'
| 'modify_field'
| 'rename_field'
| 'add_index'
| 'remove_index'
| 'transform_data'
| 'custom';
modelName: string;
fieldName?: string;
newFieldName?: string;
fieldConfig?: FieldConfig;
transformer?: (data: any) => any;
customOperation?: (context: MigrationContext) => Promise<void>;
}
```
## Constants
### Default Values
```typescript
const DEFAULT_CONFIG = {
CACHE_SIZE: 1000,
CACHE_TTL: 300000, // 5 minutes
QUERY_TIMEOUT: 30000, // 30 seconds
MAX_CONCURRENT_QUERIES: 10,
DEFAULT_PAGE_SIZE: 20,
MAX_PAGE_SIZE: 1000,
SHARD_COUNT: 4,
PIN_FACTOR: 2,
};
```
### Status Codes
```typescript
enum StatusCodes {
SUCCESS = 200,
CREATED = 201,
NO_CONTENT = 204,
BAD_REQUEST = 400,
NOT_FOUND = 404,
VALIDATION_ERROR = 422,
INTERNAL_ERROR = 500,
}
```
## Utility Functions
### Helper Functions
```typescript
// Model utilities
function getModelConfig(modelClass: typeof BaseModel): ModelConfig;
function getFieldConfig(modelClass: typeof BaseModel, fieldName: string): FieldConfig;
function getRelationshipConfig(modelClass: typeof BaseModel): RelationshipConfig[];
// Query utilities
function buildWhereClause(field: string, operator: QueryOperator, value: any): WhereClause;
function optimizeQuery(query: QueryBuilder): QueryBuilder;
function cacheKey(query: QueryBuilder): string;
// Validation utilities
function validateFieldValue(value: any, config: FieldConfig): ValidationResult;
function sanitizeInput(value: any): any;
function normalizeEmail(email: string): string;
```
## Version Information
Current API version: **1.0.0**
### Version Compatibility
| Framework Version | API Version | Breaking Changes |
| ----------------- | ----------- | --------------------------- |
| 1.0.x | 1.0.0 | None |
| 1.1.x | 1.0.0 | None (backwards compatible) |
### Deprecation Policy
- Deprecated APIs are marked with `@deprecated` tags
- Deprecated features are supported for at least 2 minor versions
- Breaking changes only occur in major version updates
- Migration guides are provided for breaking changes
## Getting Help
### Documentation
- **[Getting Started Guide](../getting-started)** - Basic setup and usage
- **[Core Concepts](../core-concepts/architecture)** - Framework architecture
- **[Examples](../examples/basic-usage)** - Practical examples
### Support
- **GitHub Issues** - Bug reports and feature requests
- **Discord Community** - Real-time help and discussion
- **Stack Overflow** - Tagged questions with `debros-framework`
### Contributing
- **[Contributing Guide](https://github.com/debros/network/blob/main/CONTRIBUTING.md)** - How to contribute
- **[API Design Guidelines](https://github.com/debros/network/blob/main/API_DESIGN.md)** - API design principles
- **[Development Setup](https://github.com/debros/network/blob/main/DEVELOPMENT.md)** - Local development setup
This API reference provides comprehensive documentation for all public interfaces in DebrosFramework. For detailed information about specific classes and methods, explore the individual API documentation pages.

View File

@ -0,0 +1,340 @@
---
sidebar_position: 1
---
# Architecture Overview
DebrosFramework is designed with a modular architecture that provides powerful abstractions over OrbitDB and IPFS while maintaining scalability and performance. This guide explains how the framework components work together.
## High-Level Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ Your Application │
├─────────────────────────────────────────────────────────────┤
│ DebrosFramework │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Models │ │ Queries │ │ Migrations │ │
│ │ & Decorators│ │ System │ │ System │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Database │ │ Shard │ │ Relationship│ │
│ │ Manager │ │ Manager │ │ Manager │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Pinning │ │ PubSub │ │ Cache │ │
│ │ Manager │ │ Manager │ │ System │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ OrbitDB Layer │
├─────────────────────────────────────────────────────────────┤
│ IPFS Layer │
└─────────────────────────────────────────────────────────────┘
```
## Core Components
### 1. Models & Decorators Layer
The foundation of DebrosFramework is the model layer, which provides:
- **BaseModel**: Abstract base class with CRUD operations
- **Decorators**: Type-safe decorators for defining models, fields, and relationships
- **Model Registry**: Central registry for model management
- **Validation System**: Built-in validation with custom validators
```typescript
// Example: Model definition with decorators
@Model({
scope: 'user',
type: 'docstore',
sharding: { strategy: 'hash', count: 4, key: 'userId' },
})
class Post extends BaseModel {
@Field({ type: 'string', required: true })
title: string;
@BelongsTo(() => User, 'userId')
user: User;
}
```
### 2. Database Management Layer
Handles the complexity of distributed database operations:
#### Database Manager
- **User-Scoped Databases**: Each user gets their own database instance
- **Global Databases**: Shared databases for global data
- **Automatic Creation**: Databases are created on-demand
- **Lifecycle Management**: Handles database initialization and cleanup
#### Shard Manager
- **Distribution Strategy**: Distributes data across multiple databases
- **Hash-based Sharding**: Uses consistent hashing for data distribution
- **Range-based Sharding**: Distributes data based on value ranges
- **User-based Sharding**: Dedicated shards per user or user group
```typescript
// Example: Database scoping
@Model({ scope: 'user' }) // Each user gets their own database
class UserPost extends BaseModel {}
@Model({ scope: 'global' }) // Shared across all users
class GlobalNews extends BaseModel {}
```
### 3. Query System
Provides powerful querying capabilities with optimization:
#### Query Builder
- **Chainable API**: Fluent interface for building queries
- **Type Safety**: Full TypeScript support with auto-completion
- **Complex Conditions**: Support for complex where clauses
- **Relationship Loading**: Eager and lazy loading of relationships
#### Query Executor
- **Smart Routing**: Routes queries to appropriate databases/shards
- **Optimization**: Automatically optimizes query execution
- **Parallel Execution**: Executes queries across shards in parallel
- **Result Aggregation**: Combines results from multiple sources
#### Query Cache
- **Intelligent Caching**: Caches frequently accessed data
- **Cache Invalidation**: Automatic cache invalidation on updates
- **Memory Management**: Efficient memory usage with LRU eviction
```typescript
// Example: Complex query with optimization
const posts = await Post.query()
.where('userId', currentUser.id)
.where('isPublic', true)
.where('createdAt', '>', Date.now() - 30 * 24 * 60 * 60 * 1000)
.with(['user', 'comments.user'])
.orderBy('popularity', 'desc')
.limit(20)
.cache(300) // Cache for 5 minutes
.find();
```
### 4. Relationship Management
Handles complex relationships between distributed models:
#### Relationship Manager
- **Lazy Loading**: Load relationships on-demand
- **Eager Loading**: Pre-load relationships to reduce queries
- **Cross-Database**: Handle relationships across different databases
- **Performance Optimization**: Batch loading and caching
#### Relationship Cache
- **Intelligent Caching**: Cache relationship data based on access patterns
- **Consistency**: Maintain consistency across cached relationships
- **Memory Efficiency**: Optimize memory usage for large datasets
```typescript
// Example: Complex relationships
@Model({ scope: 'global' })
class User extends BaseModel {
@HasMany(() => Post, 'userId')
posts: Post[];
@ManyToMany(() => User, 'followers', 'following')
followers: User[];
}
// Load user with all relationships
const user = await User.findById(userId, {
with: ['posts.comments', 'followers.posts'],
});
```
### 5. Automatic Features
Provides built-in optimization and convenience features:
#### Pinning Manager
- **Automatic Pinning**: Intelligently pin important data
- **Popularity-based**: Pin data based on access frequency
- **Tiered Pinning**: Different pinning strategies for different data types
- **Resource Management**: Optimize pinning resources across the network
#### PubSub Manager
- **Event Publishing**: Automatically publish model events
- **Real-time Updates**: Enable real-time application features
- **Event Filtering**: Intelligent event routing and filtering
- **Performance**: Efficient event handling with batching
```typescript
// Example: Automatic features in action
@Model({
pinning: { strategy: 'popularity', factor: 2 },
pubsub: { publishEvents: ['create', 'update'] },
})
class ImportantData extends BaseModel {
// Data is automatically pinned based on popularity
// Events are published on create/update
}
```
### 6. Migration System
Handles schema evolution and data transformation:
#### Migration Manager
- **Version Management**: Track schema versions across databases
- **Safe Migrations**: Rollback capabilities for failed migrations
- **Data Transformation**: Transform existing data during migrations
- **Conflict Resolution**: Handle migration conflicts in distributed systems
#### Migration Builder
- **Fluent API**: Easy-to-use migration definition
- **Validation**: Pre and post migration validation
- **Batch Processing**: Handle large datasets efficiently
- **Progress Tracking**: Monitor migration progress
```typescript
// Example: Schema migration
const migration = createMigration('add_user_profiles', '1.1.0')
.addField('User', 'profilePicture', {
type: 'string',
required: false,
})
.transformData('User', (user) => ({
...user,
displayName: user.username || 'Anonymous',
}))
.addValidator('check_profile_data', async (context) => {
// Validate migration
return { valid: true, errors: [], warnings: [] };
})
.build();
await migrationManager.runMigration(migration.id);
```
## Data Flow
### 1. Model Operation Flow
```
User Code → Model Method → Database Manager → Shard Manager → OrbitDB → IPFS
↑ ↓
└─── Query Cache ← Query Optimizer ← Query Executor ←──────────────────
```
### 2. Query Execution Flow
1. **Query Building**: User builds query using Query Builder
2. **Optimization**: Query Optimizer analyzes and optimizes the query
3. **Routing**: Query Executor determines which databases/shards to query
4. **Execution**: Parallel execution across relevant databases
5. **Aggregation**: Results are combined and returned
6. **Caching**: Results are cached for future queries
### 3. Relationship Loading Flow
1. **Detection**: Framework detects relationship access
2. **Strategy**: Determines lazy vs eager loading strategy
3. **Batching**: Batches multiple relationship loads
4. **Caching**: Caches loaded relationships
5. **Resolution**: Returns resolved relationship data
## Scalability Features
### Horizontal Scaling
- **Automatic Sharding**: Data is automatically distributed across shards
- **Dynamic Scaling**: Add new shards without downtime
- **Load Balancing**: Distribute queries across available resources
- **Peer Distribution**: Leverage IPFS network for data distribution
### Performance Optimization
- **Query Optimization**: Automatic query optimization and caching
- **Lazy Loading**: Load data only when needed
- **Batch Operations**: Combine multiple operations for efficiency
- **Memory Management**: Efficient memory usage with automatic cleanup
### Data Consistency
- **Eventual Consistency**: Handle distributed system consistency challenges
- **Conflict Resolution**: Automatic conflict resolution strategies
- **Version Management**: Track data versions across the network
- **Validation**: Ensure data integrity with comprehensive validation
## Security Considerations
### Access Control
- **User-Scoped Data**: Automatic isolation of user data
- **Permission System**: Built-in permission checking
- **Validation**: Input validation and sanitization
- **Audit Logging**: Track all data operations
### Data Protection
- **Encryption**: Support for data encryption at rest and in transit
- **Privacy**: User-scoped databases ensure data privacy
- **Network Security**: Leverage IPFS and OrbitDB security features
- **Key Management**: Secure key storage and rotation
## Framework Lifecycle
### Initialization
1. **Service Setup**: Initialize IPFS and OrbitDB services
2. **Framework Init**: Initialize DebrosFramework with services
3. **Model Registration**: Register application models
4. **Database Creation**: Create necessary databases on-demand
### Operation
1. **Request Processing**: Handle user requests through models
2. **Query Execution**: Execute optimized queries across shards
3. **Data Management**: Manage data lifecycle and cleanup
4. **Event Publishing**: Publish relevant events through PubSub
### Shutdown
1. **Graceful Shutdown**: Complete ongoing operations
2. **Data Persistence**: Ensure all data is persisted
3. **Resource Cleanup**: Clean up resources and connections
4. **Service Shutdown**: Stop underlying services
## Best Practices
### Model Design
- Use appropriate scoping (user vs global) based on data access patterns
- Design efficient sharding strategies for your data distribution
- Implement proper validation to ensure data integrity
- Use relationships judiciously to avoid performance issues
### Query Optimization
- Use indexes for frequently queried fields
- Implement proper caching strategies
- Use eager loading for predictable relationship access
- Monitor query performance and optimize accordingly
### Data Management
- Implement proper migration strategies for schema evolution
- Use appropriate pinning strategies for data availability
- Monitor and manage resource usage
- Implement proper error handling and recovery
This architecture enables DebrosFramework to provide a powerful, scalable, and easy-to-use abstraction over the complexities of distributed systems while maintaining the benefits of decentralization.

View File

@ -0,0 +1,696 @@
---
sidebar_position: 3
---
# Decorators Reference
DebrosFramework uses TypeScript decorators to provide a clean, declarative way to define models, fields, relationships, and hooks. This guide covers all available decorators and their usage patterns.
## Model Decorators
### @Model
The `@Model` decorator is used to mark a class as a DebrosFramework model and configure its behavior.
```typescript
import { BaseModel, Model } from 'debros-framework';
@Model({
scope: 'global',
type: 'docstore',
sharding: {
strategy: 'hash',
count: 4,
key: 'id',
},
pinning: {
strategy: 'popularity',
factor: 2,
},
pubsub: {
publishEvents: ['create', 'update'],
},
})
export class User extends BaseModel {
// Model definition
}
```
#### Configuration Options
| Option | Type | Description | Default |
| ------------ | -------------------- | ------------------------ | ------------ |
| `scope` | `'user' \| 'global'` | Database scope | `'global'` |
| `type` | `StoreType` | OrbitDB store type | `'docstore'` |
| `sharding` | `ShardingConfig` | Sharding configuration | `undefined` |
| `pinning` | `PinningConfig` | Pinning configuration | `undefined` |
| `pubsub` | `PubSubConfig` | PubSub configuration | `undefined` |
| `validation` | `ValidationConfig` | Validation configuration | `undefined` |
#### Store Types
```typescript
type StoreType = 'docstore' | 'eventlog' | 'keyvalue' | 'counter' | 'feed';
// Examples of different store types
@Model({ type: 'docstore' }) // Document storage (most common)
class Document extends BaseModel {}
@Model({ type: 'eventlog' }) // Event log storage
class Event extends BaseModel {}
@Model({ type: 'keyvalue' }) // Key-value storage
class Setting extends BaseModel {}
@Model({ type: 'counter' }) // Counter storage
class Counter extends BaseModel {}
@Model({ type: 'feed' }) // Feed storage
class FeedItem extends BaseModel {}
```
#### Sharding Configuration
```typescript
interface ShardingConfig {
strategy: 'hash' | 'range' | 'user';
count: number;
key: string;
ranges?: Array<{ min: any; max: any; shard: number }>;
}
// Hash-based sharding
@Model({
sharding: {
strategy: 'hash',
count: 8,
key: 'userId', // Shard based on userId hash
},
})
class UserPost extends BaseModel {}
// Range-based sharding
@Model({
sharding: {
strategy: 'range',
count: 4,
key: 'createdAt',
ranges: [
{ min: 0, max: Date.now() - 365 * 24 * 60 * 60 * 1000, shard: 0 }, // Old data
{
min: Date.now() - 365 * 24 * 60 * 60 * 1000,
max: Date.now() - 30 * 24 * 60 * 60 * 1000,
shard: 1,
}, // Medium data
{ min: Date.now() - 30 * 24 * 60 * 60 * 1000, max: Date.now(), shard: 2 }, // Recent data
{ min: Date.now(), max: Infinity, shard: 3 }, // Future data
],
},
})
class TimeBasedData extends BaseModel {}
// User-based sharding
@Model({
sharding: {
strategy: 'user',
count: 1, // One shard per user
key: 'userId',
},
})
class PrivateUserData extends BaseModel {}
```
## Field Decorators
### @Field
The `@Field` decorator defines model fields with validation, transformation, and serialization options.
```typescript
import { Field } from 'debros-framework';
export class User extends BaseModel {
@Field({
type: 'string',
required: true,
unique: true,
minLength: 3,
maxLength: 20,
pattern: /^[a-zA-Z0-9_]+$/,
validate: (value: string) => value.toLowerCase() !== 'admin',
transform: (value: string) => value.toLowerCase(),
index: true,
})
username: string;
}
```
#### Field Types
```typescript
// String fields
@Field({ type: 'string', required: true })
name: string;
@Field({
type: 'string',
required: false,
default: 'default-value',
minLength: 3,
maxLength: 100,
pattern: /^[a-zA-Z\s]+$/
})
description?: string;
// Number fields
@Field({
type: 'number',
required: true,
min: 0,
max: 100
})
score: number;
@Field({
type: 'number',
required: false,
default: () => Date.now()
})
timestamp?: number;
// Boolean fields
@Field({
type: 'boolean',
required: false,
default: false
})
isActive: boolean;
// Array fields
@Field({
type: 'array',
required: false,
default: [],
maxLength: 10
})
tags: string[];
// Object fields
@Field({
type: 'object',
required: false,
default: {}
})
metadata: Record<string, any>;
// Date fields
@Field({
type: 'date',
required: false,
default: () => new Date()
})
createdAt: Date;
```
#### Field Configuration Options
| Option | Type | Description | Applies To |
| ----------- | ----------------- | ----------------------------- | --------------- |
| `type` | `FieldType` | Field data type | All |
| `required` | `boolean` | Whether field is required | All |
| `unique` | `boolean` | Whether field must be unique | All |
| `default` | `any \| Function` | Default value or function | All |
| `min` | `number` | Minimum value | Numbers |
| `max` | `number` | Maximum value | Numbers |
| `minLength` | `number` | Minimum length | Strings, Arrays |
| `maxLength` | `number` | Maximum length | Strings, Arrays |
| `pattern` | `RegExp` | Validation pattern | Strings |
| `validate` | `Function` | Custom validation function | All |
| `transform` | `Function` | Value transformation function | All |
| `serialize` | `boolean` | Include in serialization | All |
| `index` | `boolean` | Create index for queries | All |
| `virtual` | `boolean` | Virtual field (computed) | All |
#### Advanced Field Examples
```typescript
export class User extends BaseModel {
// Email with validation
@Field({
type: 'string',
required: true,
unique: true,
validate: (email: string) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
throw new Error('Invalid email format');
}
return true;
},
transform: (email: string) => email.toLowerCase(),
index: true,
})
email: string;
// Password with hashing
@Field({
type: 'string',
required: true,
serialize: false, // Don't include in JSON
validate: (password: string) => {
if (password.length < 8) {
throw new Error('Password must be at least 8 characters');
}
return true;
},
})
passwordHash: string;
// Tags with normalization
@Field({
type: 'array',
required: false,
default: [],
maxLength: 10,
transform: (tags: string[]) => {
// Normalize and deduplicate tags
return [...new Set(tags.map((tag) => tag.toLowerCase().trim()))].filter(
(tag) => tag.length > 0,
);
},
})
tags: string[];
// Virtual computed field
@Field({
type: 'string',
virtual: true,
})
get emailDomain(): string {
return this.email.split('@')[1];
}
// Score with validation
@Field({
type: 'number',
required: false,
default: 0,
min: 0,
max: 1000,
validate: (score: number) => {
if (score % 1 !== 0) {
throw new Error('Score must be a whole number');
}
return true;
},
})
score: number;
}
```
## Relationship Decorators
### @BelongsTo
Defines a many-to-one relationship where this model belongs to another model.
```typescript
import { BelongsTo } from 'debros-framework';
@Model({ scope: 'user' })
export class Post extends BaseModel {
@Field({ type: 'string', required: true })
title: string;
@Field({ type: 'string', required: true })
userId: string;
// Post belongs to a User
@BelongsTo(() => User, 'userId')
user: User;
// Post belongs to a Category (with options)
@BelongsTo(() => Category, 'categoryId', {
cache: true,
eager: false,
})
category: Category;
}
```
### @HasMany
Defines a one-to-many relationship where this model has many of another model.
```typescript
import { HasMany } from 'debros-framework';
@Model({ scope: 'global' })
export class User extends BaseModel {
@Field({ type: 'string', required: true })
username: string;
// User has many Posts
@HasMany(() => Post, 'userId')
posts: Post[];
// User has many Comments (with options)
@HasMany(() => Comment, 'userId', {
cache: true,
eager: false,
orderBy: 'createdAt',
limit: 100,
})
comments: Comment[];
}
```
### @HasOne
Defines a one-to-one relationship where this model has one of another model.
```typescript
import { HasOne } from 'debros-framework';
@Model({ scope: 'global' })
export class User extends BaseModel {
@Field({ type: 'string', required: true })
username: string;
// User has one Profile
@HasOne(() => UserProfile, 'userId')
profile: UserProfile;
// User has one Setting (with options)
@HasOne(() => UserSetting, 'userId', {
cache: true,
eager: true,
})
settings: UserSetting;
}
```
### @ManyToMany
Defines a many-to-many relationship through a join table or field.
```typescript
import { ManyToMany } from 'debros-framework';
@Model({ scope: 'global' })
export class User extends BaseModel {
@Field({ type: 'string', required: true })
username: string;
// Many-to-many through join table
@ManyToMany(() => Role, 'user_roles', 'userId', 'roleId')
roles: Role[];
// Many-to-many through array field
@ManyToMany(() => Tag, 'userTags', {
through: 'tagIds', // Array field in this model
cache: true,
})
tags: Tag[];
@Field({ type: 'array', required: false, default: [] })
tagIds: string[]; // Array of tag IDs
}
```
#### Relationship Configuration Options
| Option | Type | Description | Default |
| --------- | --------- | ------------------------------- | ----------- |
| `cache` | `boolean` | Cache relationship data | `false` |
| `eager` | `boolean` | Load relationship eagerly | `false` |
| `orderBy` | `string` | Order related records by field | `undefined` |
| `limit` | `number` | Limit number of related records | `undefined` |
| `where` | `object` | Additional where conditions | `undefined` |
#### Advanced Relationship Examples
```typescript
@Model({ scope: 'global' })
export class User extends BaseModel {
@Field({ type: 'string', required: true })
username: string;
// Posts with caching and ordering
@HasMany(() => Post, 'userId', {
cache: true,
orderBy: 'createdAt',
limit: 50,
where: { isPublished: true },
})
publishedPosts: Post[];
// Recent posts
@HasMany(() => Post, 'userId', {
orderBy: 'createdAt',
limit: 10,
where: {
createdAt: { $gt: Date.now() - 30 * 24 * 60 * 60 * 1000 },
},
})
recentPosts: Post[];
// Followers (many-to-many)
@ManyToMany(() => User, 'user_follows', 'followingId', 'followerId')
followers: User[];
// Following (many-to-many)
@ManyToMany(() => User, 'user_follows', 'followerId', 'followingId')
following: User[];
}
```
## Hook Decorators
Hook decorators allow you to execute code at specific points in the model lifecycle.
### Lifecycle Hooks
```typescript
import {
BeforeCreate,
AfterCreate,
BeforeUpdate,
AfterUpdate,
BeforeDelete,
AfterDelete,
BeforeSave,
AfterSave,
} from 'debros-framework';
export class User extends BaseModel {
@Field({ type: 'string', required: true })
username: string;
@Field({ type: 'string', required: true })
email: string;
@Field({ type: 'number', required: false })
createdAt: number;
@Field({ type: 'number', required: false })
updatedAt: number;
// Before creating a new record
@BeforeCreate()
async beforeCreate() {
this.createdAt = Date.now();
this.updatedAt = Date.now();
// Validate uniqueness
const existing = await User.findOne({ email: this.email });
if (existing) {
throw new Error('Email already exists');
}
}
// After creating a new record
@AfterCreate()
async afterCreate() {
// Send welcome email
await this.sendWelcomeEmail();
// Create default settings
await this.createDefaultSettings();
}
// Before updating a record
@BeforeUpdate()
async beforeUpdate() {
this.updatedAt = Date.now();
// Log the change
console.log(`Updating user ${this.username}`);
}
// After updating a record
@AfterUpdate()
async afterUpdate() {
// Invalidate cache
await this.invalidateCache();
}
// Before deleting a record
@BeforeDelete()
async beforeDelete() {
// Clean up related data
await this.cleanupRelatedData();
}
// After deleting a record
@AfterDelete()
async afterDelete() {
// Log deletion
console.log(`User ${this.username} deleted`);
}
// Before any save operation (create or update)
@BeforeSave()
async beforeSave() {
// Validate data
await this.validateData();
}
// After any save operation (create or update)
@AfterSave()
async afterSave() {
// Update search index
await this.updateSearchIndex();
}
private async sendWelcomeEmail(): Promise<void> {
// Implementation
}
private async createDefaultSettings(): Promise<void> {
// Implementation
}
private async invalidateCache(): Promise<void> {
// Implementation
}
private async cleanupRelatedData(): Promise<void> {
// Implementation
}
private async validateData(): Promise<void> {
// Implementation
}
private async updateSearchIndex(): Promise<void> {
// Implementation
}
}
```
### Hook Parameters
Some hooks can receive parameters with information about the operation:
```typescript
export class AuditedModel extends BaseModel {
@BeforeUpdate()
async beforeUpdate(changes: Record<string, any>) {
// Log what fields are changing
console.log('Fields changing:', Object.keys(changes));
// Audit specific changes
if (changes.status) {
await this.auditStatusChange(changes.status);
}
}
@AfterCreate()
async afterCreate(model: this) {
// The model instance is passed to after hooks
console.log(`Created ${model.constructor.name} with ID ${model.id}`);
}
private async auditStatusChange(newStatus: string): Promise<void> {
// Implementation
}
}
```
## Decorator Best Practices
### TypeScript Configuration
Ensure your `tsconfig.json` has the required decorator settings:
```json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"target": "ES2020",
"module": "commonjs"
}
}
```
### Performance Considerations
1. **Use caching wisely**: Only cache relationships that are accessed frequently
2. **Limit eager loading**: Eager loading can impact performance with large datasets
3. **Optimize hooks**: Keep hook operations lightweight and fast
4. **Index frequently queried fields**: Add indexes to improve query performance
### Code Organization
1. **Group related decorators**: Keep related decorators together
2. **Document complex logic**: Add comments for complex validation or transformation logic
3. **Use consistent naming**: Follow consistent naming conventions for fields and relationships
4. **Separate concerns**: Keep business logic in separate methods, not in decorators
### Error Handling
```typescript
export class User extends BaseModel {
@Field({
type: 'string',
required: true,
validate: (value: string) => {
if (!value || value.trim().length === 0) {
throw new Error('Username cannot be empty');
}
if (value.length < 3) {
throw new Error('Username must be at least 3 characters long');
}
if (!/^[a-zA-Z0-9_]+$/.test(value)) {
throw new Error('Username can only contain letters, numbers, and underscores');
}
return true;
},
})
username: string;
@BeforeCreate()
async beforeCreate() {
try {
// Expensive validation
await this.validateUniqueEmail();
} catch (error) {
throw new Error(`Validation failed: ${error.message}`);
}
}
private async validateUniqueEmail(): Promise<void> {
const existing = await User.findOne({ email: this.email });
if (existing) {
throw new Error('Email address is already in use');
}
}
}
```
This comprehensive decorator system provides a powerful, type-safe way to define your data models and their behavior in DebrosFramework applications.

View File

@ -0,0 +1,566 @@
---
sidebar_position: 2
---
# Models and Fields
Models are the foundation of DebrosFramework applications. They define your data structure, validation rules, and behavior using TypeScript classes and decorators. This guide covers everything you need to know about creating and working with models.
## Basic Model Structure
### Creating a Model
Every model in DebrosFramework extends the `BaseModel` class and uses the `@Model` decorator:
```typescript
import { BaseModel, Model, Field } from 'debros-framework';
@Model({
scope: 'global',
type: 'docstore',
})
export class User extends BaseModel {
@Field({ type: 'string', required: true, unique: true })
username: string;
@Field({ type: 'string', required: true })
email: string;
}
```
### Model Configuration Options
The `@Model` decorator accepts several configuration options:
```typescript
@Model({
// Database scope: 'user' or 'global'
scope: 'user',
// OrbitDB store type
type: 'docstore', // 'docstore' | 'eventlog' | 'keyvalue' | 'counter' | 'feed'
// Sharding configuration
sharding: {
strategy: 'hash', // 'hash' | 'range' | 'user'
count: 4, // Number of shards
key: 'userId', // Field to use for sharding
},
// Pinning configuration
pinning: {
strategy: 'popularity', // 'fixed' | 'popularity' | 'tiered'
factor: 2, // Pinning factor
},
// PubSub configuration
pubsub: {
publishEvents: ['create', 'update', 'delete'],
},
// Validation configuration
validation: {
strict: true, // Strict validation mode
allowExtraFields: false,
},
})
export class Post extends BaseModel {
// Model fields go here
}
```
## Field Types and Validation
### Basic Field Types
DebrosFramework supports several field types with built-in validation:
```typescript
export class ExampleModel extends BaseModel {
@Field({ type: 'string', required: true })
name: string;
@Field({ type: 'number', required: true, min: 0, max: 100 })
score: number;
@Field({ type: 'boolean', required: false, default: false })
isActive: boolean;
@Field({ type: 'array', required: false, default: [] })
tags: string[];
@Field({ type: 'object', required: false })
metadata: Record<string, any>;
@Field({ type: 'date', required: false, default: () => new Date() })
createdAt: Date;
}
```
### Field Configuration Options
Each field can be configured with various options:
```typescript
@Field({
// Basic type information
type: 'string',
required: true,
unique: false,
// Default values
default: 'default-value',
default: () => Date.now(), // Function for dynamic defaults
// Validation constraints
min: 0, // Minimum value (numbers)
max: 100, // Maximum value (numbers)
minLength: 3, // Minimum length (strings/arrays)
maxLength: 50, // Maximum length (strings/arrays)
pattern: /^[a-zA-Z0-9]+$/, // Regex pattern (strings)
// Custom validation
validate: (value: any) => {
return value.length >= 3 && value.length <= 20;
},
// Field transformation
transform: (value: any) => value.toLowerCase(),
// Serialization options
serialize: true, // Include in serialization
// Indexing (for query optimization)
index: true
})
fieldName: string;
```
### Custom Validation
You can implement complex validation logic using custom validators:
```typescript
export class User extends BaseModel {
@Field({
type: 'string',
required: true,
validate: (value: string) => {
// Username validation
if (value.length < 3 || value.length > 20) {
throw new Error('Username must be between 3 and 20 characters');
}
if (!/^[a-zA-Z0-9_]+$/.test(value)) {
throw new Error('Username can only contain letters, numbers, and underscores');
}
return true;
},
})
username: string;
@Field({
type: 'string',
required: true,
validate: (value: string) => {
// Email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(value)) {
throw new Error('Invalid email format');
}
return true;
},
})
email: string;
@Field({
type: 'number',
required: false,
validate: (value: number) => {
// Age validation
if (value < 13 || value > 120) {
throw new Error('Age must be between 13 and 120');
}
return true;
},
})
age?: number;
}
```
### Field Transformation
Transform field values before storage or after retrieval:
```typescript
export class User extends BaseModel {
@Field({
type: 'string',
required: true,
transform: (value: string) => value.toLowerCase().trim(),
})
username: string;
@Field({
type: 'string',
required: true,
transform: (value: string) => value.toLowerCase(),
})
email: string;
@Field({
type: 'array',
required: false,
default: [],
transform: (tags: string[]) => {
// Normalize and deduplicate tags
return [...new Set(tags.map((tag) => tag.toLowerCase().trim()))];
},
})
tags: string[];
}
```
## Model Scoping
### User-Scoped Models
User-scoped models create separate databases for each user, providing data isolation:
```typescript
@Model({
scope: 'user', // Each user gets their own database
type: 'docstore',
sharding: {
strategy: 'user',
count: 2,
key: 'userId',
},
})
export class UserPost extends BaseModel {
@Field({ type: 'string', required: true })
title: string;
@Field({ type: 'string', required: true })
content: string;
@Field({ type: 'string', required: true })
userId: string; // Required for user-scoped models
}
```
### Global Models
Global models are shared across all users:
```typescript
@Model({
scope: 'global', // Shared across all users
type: 'docstore',
sharding: {
strategy: 'hash',
count: 8,
key: 'id',
},
})
export class GlobalNews extends BaseModel {
@Field({ type: 'string', required: true })
title: string;
@Field({ type: 'string', required: true })
content: string;
@Field({ type: 'string', required: true })
category: string;
@Field({ type: 'boolean', required: false, default: true })
isPublished: boolean;
}
```
## Model Hooks
Use hooks to execute code at specific points in the model lifecycle:
```typescript
import {
BeforeCreate,
AfterCreate,
BeforeUpdate,
AfterUpdate,
BeforeDelete,
} from 'debros-framework';
export class User extends BaseModel {
@Field({ type: 'string', required: true })
username: string;
@Field({ type: 'string', required: true })
passwordHash: string;
@Field({ type: 'number', required: false })
createdAt: number;
@Field({ type: 'number', required: false })
updatedAt: number;
@BeforeCreate()
async beforeCreate() {
this.createdAt = Date.now();
this.updatedAt = Date.now();
// Hash password before saving
if (this.passwordHash && !this.passwordHash.startsWith('$2b$')) {
this.passwordHash = await this.hashPassword(this.passwordHash);
}
}
@BeforeUpdate()
async beforeUpdate() {
this.updatedAt = Date.now();
// Hash password if it was changed
if (this.isFieldModified('passwordHash') && !this.passwordHash.startsWith('$2b$')) {
this.passwordHash = await this.hashPassword(this.passwordHash);
}
}
@AfterCreate()
async afterCreate() {
// Send welcome email
await this.sendWelcomeEmail();
}
@BeforeDelete()
async beforeDelete() {
// Clean up user's data
await this.cleanupUserData();
}
private async hashPassword(password: string): Promise<string> {
// Implementation of password hashing
const bcrypt = require('bcrypt');
return await bcrypt.hash(password, 10);
}
private async sendWelcomeEmail(): Promise<void> {
// Implementation of welcome email
console.log(`Welcome email sent to ${this.username}`);
}
private async cleanupUserData(): Promise<void> {
// Implementation of data cleanup
console.log(`Cleaning up data for user ${this.username}`);
}
}
```
## Advanced Model Features
### Computed Properties
Create computed properties that are automatically calculated:
```typescript
export class User extends BaseModel {
@Field({ type: 'string', required: true })
firstName: string;
@Field({ type: 'string', required: true })
lastName: string;
@Field({ type: 'string', required: true })
email: string;
// Computed property
get fullName(): string {
return `${this.firstName} ${this.lastName}`;
}
get emailDomain(): string {
return this.email.split('@')[1];
}
// Virtual field (not stored but serialized)
@Field({ type: 'string', virtual: true })
get displayName(): string {
return this.fullName || this.email.split('@')[0];
}
}
```
### Model Methods
Add custom methods to your models:
```typescript
export class Post extends BaseModel {
@Field({ type: 'string', required: true })
title: string;
@Field({ type: 'string', required: true })
content: string;
@Field({ type: 'array', required: false, default: [] })
tags: string[];
@Field({ type: 'number', required: false, default: 0 })
viewCount: number;
// Instance methods
async incrementViews(): Promise<void> {
this.viewCount += 1;
await this.save();
}
addTag(tag: string): void {
if (!this.tags.includes(tag)) {
this.tags.push(tag);
}
}
removeTag(tag: string): void {
this.tags = this.tags.filter((t) => t !== tag);
}
getWordCount(): number {
return this.content.split(/\s+/).length;
}
// Static methods
static async findByTag(tag: string): Promise<Post[]> {
return await this.query().where('tags', 'includes', tag).find();
}
static async findPopular(limit: number = 10): Promise<Post[]> {
return await this.query().orderBy('viewCount', 'desc').limit(limit).find();
}
}
```
### Model Serialization
Control how models are serialized:
```typescript
export class User extends BaseModel {
@Field({ type: 'string', required: true })
username: string;
@Field({ type: 'string', required: true })
email: string;
@Field({
type: 'string',
required: true,
serialize: false, // Don't include in serialization
})
passwordHash: string;
@Field({ type: 'string', required: false })
profilePicture?: string;
// Custom serialization
toJSON(): any {
const json = super.toJSON();
// Add computed fields
json.initials = this.getInitials();
// Remove sensitive data
delete json.passwordHash;
return json;
}
// Safe serialization for public APIs
toPublic(): any {
return {
id: this.id,
username: this.username,
profilePicture: this.profilePicture,
initials: this.getInitials(),
};
}
private getInitials(): string {
return this.username.substring(0, 2).toUpperCase();
}
}
```
## Model Inheritance
Create base models for common functionality:
```typescript
// Base model with common fields
abstract class TimestampedModel extends BaseModel {
@Field({ type: 'number', required: false, default: () => Date.now() })
createdAt: number;
@Field({ type: 'number', required: false, default: () => Date.now() })
updatedAt: number;
@BeforeUpdate()
updateTimestamp() {
this.updatedAt = Date.now();
}
}
// User model extending base
@Model({ scope: 'global', type: 'docstore' })
export class User extends TimestampedModel {
@Field({ type: 'string', required: true, unique: true })
username: string;
@Field({ type: 'string', required: true, unique: true })
email: string;
}
// Post model extending base
@Model({ scope: 'user', type: 'docstore' })
export class Post extends TimestampedModel {
@Field({ type: 'string', required: true })
title: string;
@Field({ type: 'string', required: true })
content: string;
@Field({ type: 'string', required: true })
userId: string;
}
```
## Best Practices
### Model Design
1. **Use appropriate scoping**: Choose 'user' or 'global' scope based on your data access patterns
2. **Design for sharding**: Consider how your data will be distributed when choosing sharding keys
3. **Validate early**: Use field validation to catch errors early in the development process
4. **Use TypeScript**: Take advantage of TypeScript's type safety throughout your models
### Performance Optimization
1. **Index frequently queried fields**: Add indexes to fields you query often
2. **Use computed properties sparingly**: Heavy computations can impact performance
3. **Optimize serialization**: Only serialize the data you need
4. **Consider caching**: Use caching for expensive operations
### Security Considerations
1. **Validate all input**: Never trust user input without validation
2. **Sanitize data**: Clean data before storage
3. **Control serialization**: Be careful about what data you expose in APIs
4. **Use appropriate scoping**: User-scoped models provide better data isolation
### Code Organization
1. **Keep models focused**: Each model should have a single responsibility
2. **Use inheritance wisely**: Create base models for common functionality
3. **Document your models**: Use clear names and add comments for complex logic
4. **Test thoroughly**: Write comprehensive tests for your model logic
This comprehensive model system provides the foundation for building scalable, maintainable decentralized applications with DebrosFramework.

View File

@ -0,0 +1,935 @@
---
sidebar_position: 1
---
# Basic Usage Examples
This guide provides practical examples of using DebrosFramework for common development tasks. These examples will help you understand how to implement typical application features using the framework.
## Setting Up Your First Application
### 1. Project Setup
```bash
mkdir my-debros-app
cd my-debros-app
npm init -y
npm install debros-framework @orbitdb/core @helia/helia
npm install --save-dev typescript @types/node ts-node
```
### 2. Basic Configuration
Create `src/config.ts`:
```typescript
export const config = {
ipfs: {
// IPFS configuration
addresses: {
swarm: ['/ip4/0.0.0.0/tcp/4001'],
api: '/ip4/127.0.0.1/tcp/5001',
gateway: '/ip4/127.0.0.1/tcp/8080',
},
},
orbitdb: {
// OrbitDB configuration
directory: './orbitdb',
},
framework: {
// Framework configuration
cacheSize: 1000,
enableQueryOptimization: true,
enableAutomaticPinning: true,
},
};
```
## Simple Blog Application
Let's build a simple blog application to demonstrate basic DebrosFramework usage.
### 1. Define Models
Create `src/models/User.ts`:
```typescript
import { BaseModel, Model, Field, HasMany, BeforeCreate, AfterCreate } from 'debros-framework';
import { Post } from './Post';
@Model({
scope: 'global',
type: 'docstore',
sharding: {
strategy: 'hash',
count: 4,
key: 'id',
},
})
export class User extends BaseModel {
@Field({
type: 'string',
required: true,
unique: true,
minLength: 3,
maxLength: 20,
validate: (username: string) => /^[a-zA-Z0-9_]+$/.test(username),
})
username: string;
@Field({
type: 'string',
required: true,
unique: true,
validate: (email: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email),
transform: (email: string) => email.toLowerCase(),
})
email: string;
@Field({ type: 'string', required: false, maxLength: 500 })
bio?: string;
@Field({ type: 'string', required: false })
avatarUrl?: string;
@Field({ type: 'boolean', required: false, default: true })
isActive: boolean;
@Field({ type: 'number', required: false, default: () => Date.now() })
registeredAt: number;
@Field({ type: 'number', required: false, default: () => Date.now() })
lastLoginAt: number;
// Relationships
@HasMany(() => Post, 'authorId')
posts: Post[];
@BeforeCreate()
setupNewUser() {
this.registeredAt = Date.now();
this.lastLoginAt = Date.now();
this.isActive = true;
}
@AfterCreate()
async afterUserCreated() {
console.log(`New user created: ${this.username}`);
// Here you could send a welcome email, create default settings, etc.
}
// Helper methods
updateLastLogin() {
this.lastLoginAt = Date.now();
return this.save();
}
async getPostCount(): Promise<number> {
return await Post.query().where('authorId', this.id).count();
}
async getRecentPosts(limit: number = 5): Promise<Post[]> {
return await Post.query()
.where('authorId', this.id)
.orderBy('createdAt', 'desc')
.limit(limit)
.find();
}
}
```
Create `src/models/Post.ts`:
```typescript
import {
BaseModel,
Model,
Field,
BelongsTo,
HasMany,
BeforeCreate,
BeforeUpdate,
} from 'debros-framework';
import { User } from './User';
import { Comment } from './Comment';
@Model({
scope: 'user',
type: 'docstore',
sharding: {
strategy: 'user',
count: 2,
key: 'authorId',
},
})
export class Post extends BaseModel {
@Field({ type: 'string', required: true, minLength: 1, maxLength: 200 })
title: string;
@Field({ type: 'string', required: true, minLength: 1, maxLength: 10000 })
content: string;
@Field({ type: 'string', required: true })
authorId: string;
@Field({ type: 'array', required: false, default: [] })
tags: string[];
@Field({ type: 'boolean', required: false, default: false })
isPublished: boolean;
@Field({ type: 'number', required: false, default: 0 })
viewCount: number;
@Field({ type: 'number', required: false, default: 0 })
likeCount: number;
@Field({ type: 'number', required: false, default: () => Date.now() })
createdAt: number;
@Field({ type: 'number', required: false, default: () => Date.now() })
updatedAt: number;
@Field({ type: 'number', required: false })
publishedAt?: number;
// Relationships
@BelongsTo(() => User, 'authorId')
author: User;
@HasMany(() => Comment, 'postId')
comments: Comment[];
@BeforeCreate()
setupNewPost() {
this.createdAt = Date.now();
this.updatedAt = Date.now();
this.viewCount = 0;
this.likeCount = 0;
}
@BeforeUpdate()
updateTimestamp() {
this.updatedAt = Date.now();
}
// Helper methods
async publish(): Promise<void> {
this.isPublished = true;
this.publishedAt = Date.now();
await this.save();
}
async unpublish(): Promise<void> {
this.isPublished = false;
this.publishedAt = undefined;
await this.save();
}
async incrementViews(): Promise<void> {
this.viewCount += 1;
await this.save();
}
async like(): Promise<void> {
this.likeCount += 1;
await this.save();
}
async unlike(): Promise<void> {
if (this.likeCount > 0) {
this.likeCount -= 1;
await this.save();
}
}
addTag(tag: string): void {
const normalizedTag = tag.toLowerCase().trim();
if (normalizedTag && !this.tags.includes(normalizedTag)) {
this.tags.push(normalizedTag);
}
}
removeTag(tag: string): void {
this.tags = this.tags.filter((t) => t !== tag.toLowerCase().trim());
}
getExcerpt(length: number = 150): string {
if (this.content.length <= length) {
return this.content;
}
return this.content.substring(0, length).trim() + '...';
}
getReadingTime(): number {
const wordsPerMinute = 200;
const wordCount = this.content.split(/\s+/).length;
return Math.ceil(wordCount / wordsPerMinute);
}
}
```
Create `src/models/Comment.ts`:
```typescript
import { BaseModel, Model, Field, BelongsTo, BeforeCreate } from 'debros-framework';
import { User } from './User';
import { Post } from './Post';
@Model({
scope: 'user',
type: 'docstore',
sharding: {
strategy: 'user',
count: 2,
key: 'authorId',
},
})
export class Comment extends BaseModel {
@Field({ type: 'string', required: true, minLength: 1, maxLength: 1000 })
content: string;
@Field({ type: 'string', required: true })
postId: string;
@Field({ type: 'string', required: true })
authorId: string;
@Field({ type: 'boolean', required: false, default: true })
isApproved: boolean;
@Field({ type: 'number', required: false, default: 0 })
likeCount: number;
@Field({ type: 'number', required: false, default: () => Date.now() })
createdAt: number;
// Relationships
@BelongsTo(() => Post, 'postId')
post: Post;
@BelongsTo(() => User, 'authorId')
author: User;
@BeforeCreate()
setupNewComment() {
this.createdAt = Date.now();
this.isApproved = true; // Auto-approve for now
}
// Helper methods
async approve(): Promise<void> {
this.isApproved = true;
await this.save();
}
async reject(): Promise<void> {
this.isApproved = false;
await this.save();
}
async like(): Promise<void> {
this.likeCount += 1;
await this.save();
}
async unlike(): Promise<void> {
if (this.likeCount > 0) {
this.likeCount -= 1;
await this.save();
}
}
}
```
Create `src/models/index.ts`:
```typescript
export { User } from './User';
export { Post } from './Post';
export { Comment } from './Comment';
```
### 2. Application Service
Create `src/BlogService.ts`:
```typescript
import { DebrosFramework } from 'debros-framework';
import { User, Post, Comment } from './models';
export class BlogService {
private framework: DebrosFramework;
constructor(framework: DebrosFramework) {
this.framework = framework;
}
// User operations
async createUser(userData: {
username: string;
email: string;
bio?: string;
avatarUrl?: string;
}): Promise<User> {
return await User.create(userData);
}
async getUserByUsername(username: string): Promise<User | null> {
return await User.query().where('username', username).findOne();
}
async getUserById(id: string): Promise<User | null> {
return await User.findById(id);
}
async updateUserProfile(
userId: string,
updates: {
bio?: string;
avatarUrl?: string;
},
): Promise<User> {
const user = await User.findById(userId);
if (!user) {
throw new Error('User not found');
}
if (updates.bio !== undefined) user.bio = updates.bio;
if (updates.avatarUrl !== undefined) user.avatarUrl = updates.avatarUrl;
await user.save();
return user;
}
// Post operations
async createPost(
authorId: string,
postData: {
title: string;
content: string;
tags?: string[];
},
): Promise<Post> {
const post = await Post.create({
title: postData.title,
content: postData.content,
authorId,
tags: postData.tags || [],
});
return post;
}
async getPostById(id: string): Promise<Post | null> {
const post = await Post.findById(id);
if (post) {
await post.incrementViews(); // Track view
}
return post;
}
async getPostWithDetails(id: string): Promise<Post | null> {
return await Post.query().where('id', id).with(['author', 'comments.author']).findOne();
}
async getPublishedPosts(
options: {
page?: number;
limit?: number;
tag?: string;
authorId?: string;
} = {},
): Promise<{ posts: Post[]; total: number }> {
const { page = 1, limit = 10, tag, authorId } = options;
let query = Post.query().where('isPublished', true).with(['author']);
if (tag) {
query = query.where('tags', 'includes', tag);
}
if (authorId) {
query = query.where('authorId', authorId);
}
const result = await query.orderBy('publishedAt', 'desc').paginate(page, limit);
return {
posts: result.data,
total: result.total,
};
}
async getUserPosts(userId: string, includeUnpublished: boolean = false): Promise<Post[]> {
let query = Post.query().where('authorId', userId);
if (!includeUnpublished) {
query = query.where('isPublished', true);
}
return await query.orderBy('createdAt', 'desc').find();
}
async updatePost(
postId: string,
updates: {
title?: string;
content?: string;
tags?: string[];
},
): Promise<Post> {
const post = await Post.findById(postId);
if (!post) {
throw new Error('Post not found');
}
if (updates.title !== undefined) post.title = updates.title;
if (updates.content !== undefined) post.content = updates.content;
if (updates.tags !== undefined) post.tags = updates.tags;
await post.save();
return post;
}
async deletePost(postId: string): Promise<void> {
const post = await Post.findById(postId);
if (!post) {
throw new Error('Post not found');
}
// Delete associated comments first
const comments = await Comment.query().where('postId', postId).find();
for (const comment of comments) {
await comment.delete();
}
await post.delete();
}
// Comment operations
async createComment(authorId: string, postId: string, content: string): Promise<Comment> {
const comment = await Comment.create({
content,
postId,
authorId,
});
return comment;
}
async getPostComments(postId: string): Promise<Comment[]> {
return await Comment.query()
.where('postId', postId)
.where('isApproved', true)
.with(['author'])
.orderBy('createdAt', 'asc')
.find();
}
async deleteComment(commentId: string): Promise<void> {
const comment = await Comment.findById(commentId);
if (!comment) {
throw new Error('Comment not found');
}
await comment.delete();
}
// Search and discovery
async searchPosts(
query: string,
options: {
limit?: number;
tags?: string[];
} = {},
): Promise<Post[]> {
const { limit = 20, tags } = options;
let searchQuery = Post.query()
.where('isPublished', true)
.where((q) => {
q.where('title', 'like', `%${query}%`).orWhere('content', 'like', `%${query}%`);
});
if (tags && tags.length > 0) {
searchQuery = searchQuery.where('tags', 'includes any', tags);
}
return await searchQuery
.with(['author'])
.orderBy('likeCount', 'desc')
.orderBy('createdAt', 'desc')
.limit(limit)
.find();
}
async getPopularPosts(timeframe: 'day' | 'week' | 'month' | 'all' = 'week'): Promise<Post[]> {
let sinceTime: number;
switch (timeframe) {
case 'day':
sinceTime = Date.now() - 24 * 60 * 60 * 1000;
break;
case 'week':
sinceTime = Date.now() - 7 * 24 * 60 * 60 * 1000;
break;
case 'month':
sinceTime = Date.now() - 30 * 24 * 60 * 60 * 1000;
break;
default:
sinceTime = 0;
}
let query = Post.query().where('isPublished', true);
if (sinceTime > 0) {
query = query.where('publishedAt', '>', sinceTime);
}
return await query
.with(['author'])
.orderBy('likeCount', 'desc')
.orderBy('viewCount', 'desc')
.limit(10)
.find();
}
async getTags(): Promise<Array<{ tag: string; count: number }>> {
// Get all published posts
const posts = await Post.query().where('isPublished', true).select(['tags']).find();
// Count tags
const tagCounts = new Map<string, number>();
posts.forEach((post) => {
post.tags.forEach((tag) => {
tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1);
});
});
// Convert to array and sort by count
return Array.from(tagCounts.entries())
.map(([tag, count]) => ({ tag, count }))
.sort((a, b) => b.count - a.count);
}
// Analytics
async getUserStats(userId: string): Promise<{
postCount: number;
totalViews: number;
totalLikes: number;
commentCount: number;
}> {
const [posts, comments] = await Promise.all([
Post.query().where('authorId', userId).find(),
Comment.query().where('authorId', userId).count(),
]);
const totalViews = posts.reduce((sum, post) => sum + post.viewCount, 0);
const totalLikes = posts.reduce((sum, post) => sum + post.likeCount, 0);
return {
postCount: posts.length,
totalViews,
totalLikes,
commentCount: comments,
};
}
async getSystemStats(): Promise<{
userCount: number;
postCount: number;
commentCount: number;
publishedPostCount: number;
}> {
const [userCount, postCount, commentCount, publishedPostCount] = await Promise.all([
User.query().count(),
Post.query().count(),
Comment.query().count(),
Post.query().where('isPublished', true).count(),
]);
return {
userCount,
postCount,
commentCount,
publishedPostCount,
};
}
}
```
### 3. Main Application
Create `src/index.ts`:
```typescript
import { DebrosFramework } from 'debros-framework';
import { BlogService } from './BlogService';
import { User, Post, Comment } from './models';
import { setupServices } from './setup';
async function main() {
try {
console.log('🚀 Starting DebrosFramework Blog Application...');
// Initialize services
const { orbitDBService, ipfsService } = await setupServices();
// Initialize framework
const framework = new DebrosFramework();
await framework.initialize(orbitDBService, ipfsService);
console.log('✅ Framework initialized successfully');
// Create blog service
const blogService = new BlogService(framework);
// Demo: Create sample data
await createSampleData(blogService);
// Demo: Query data
await demonstrateQueries(blogService);
// Keep running for demo
console.log('📝 Blog application is running...');
console.log('Press Ctrl+C to stop');
// Graceful shutdown
process.on('SIGINT', async () => {
console.log('\n🛑 Shutting down...');
await framework.stop();
process.exit(0);
});
} catch (error) {
console.error('❌ Application failed to start:', error);
process.exit(1);
}
}
async function createSampleData(blogService: BlogService) {
console.log('\n📝 Creating sample data...');
// Create users
const alice = await blogService.createUser({
username: 'alice',
email: 'alice@example.com',
bio: 'Tech enthusiast and blogger',
avatarUrl: 'https://example.com/avatars/alice.jpg',
});
const bob = await blogService.createUser({
username: 'bob',
email: 'bob@example.com',
bio: 'Developer and writer',
});
console.log(`✅ Created users: ${alice.username}, ${bob.username}`);
// Create posts
const post1 = await blogService.createPost(alice.id, {
title: 'Getting Started with DebrosFramework',
content: 'DebrosFramework makes it easy to build decentralized applications...',
tags: ['debros', 'tutorial', 'decentralized'],
});
const post2 = await blogService.createPost(bob.id, {
title: 'Building Scalable dApps',
content: 'Scalability is crucial for decentralized applications...',
tags: ['scaling', 'dapps', 'blockchain'],
});
// Publish posts
await post1.publish();
await post2.publish();
console.log(`✅ Created and published posts: "${post1.title}", "${post2.title}"`);
// Create comments
const comment1 = await blogService.createComment(
bob.id,
post1.id,
'Great introduction to DebrosFramework!',
);
const comment2 = await blogService.createComment(
alice.id,
post2.id,
'Very insightful article about scaling.',
);
console.log(`✅ Created ${[comment1, comment2].length} comments`);
// Add some interactions
await post1.like();
await post1.like();
await post2.like();
await comment1.like();
console.log('✅ Added sample interactions (likes)');
}
async function demonstrateQueries(blogService: BlogService) {
console.log('\n🔍 Demonstrating queries...');
// Get all published posts
const { posts, total } = await blogService.getPublishedPosts({ limit: 10 });
console.log(`📚 Found ${total} published posts:`);
posts.forEach((post) => {
console.log(
` - "${post.title}" by ${post.author.username} (${post.likeCount} likes, ${post.viewCount} views)`,
);
});
// Search posts
const searchResults = await blogService.searchPosts('DebrosFramework');
console.log(`\n🔍 Search results for "DebrosFramework": ${searchResults.length} posts`);
searchResults.forEach((post) => {
console.log(` - "${post.title}" by ${post.author.username}`);
});
// Get popular posts
const popularPosts = await blogService.getPopularPosts('week');
console.log(`\n⭐ Popular posts this week: ${popularPosts.length}`);
popularPosts.forEach((post) => {
console.log(` - "${post.title}" (${post.likeCount} likes)`);
});
// Get tags
const tags = await blogService.getTags();
console.log(`\n🏷 Popular tags:`);
tags.slice(0, 5).forEach(({ tag, count }) => {
console.log(` - ${tag}: ${count} posts`);
});
// Get user stats
const users = await User.query().find();
for (const user of users) {
const stats = await blogService.getUserStats(user.id);
console.log(`\n📊 Stats for ${user.username}:`);
console.log(` - Posts: ${stats.postCount}`);
console.log(` - Total views: ${stats.totalViews}`);
console.log(` - Total likes: ${stats.totalLikes}`);
console.log(` - Comments: ${stats.commentCount}`);
}
// System stats
const systemStats = await blogService.getSystemStats();
console.log(`\n🌐 System stats:`);
console.log(` - Users: ${systemStats.userCount}`);
console.log(` - Posts: ${systemStats.postCount} (${systemStats.publishedPostCount} published)`);
console.log(` - Comments: ${systemStats.commentCount}`);
}
// Run the application
main().catch(console.error);
```
### 4. Service Setup
Create `src/setup.ts`:
```typescript
import { createHelia } from '@helia/helia';
import { createOrbitDB } from '@orbitdb/core';
export async function setupServices() {
console.log('🔧 Setting up IPFS and OrbitDB services...');
// Create IPFS instance
const ipfs = await createHelia({
// Configure as needed for your environment
});
// Create OrbitDB instance
const orbitdb = await createOrbitDB({ ipfs });
// Wrap services for DebrosFramework
const ipfsService = {
async init() {
/* Already initialized */
},
getHelia: () => ipfs,
getLibp2pInstance: () => ipfs.libp2p,
async stop() {
await ipfs.stop();
},
};
const orbitDBService = {
async init() {
/* Already initialized */
},
async openDB(name: string, type: string) {
return await orbitdb.open(name, { type });
},
getOrbitDB: () => orbitdb,
async stop() {
await orbitdb.stop();
},
};
console.log('✅ Services setup complete');
return { ipfsService, orbitDBService };
}
```
### 5. Running the Application
Add to your `package.json`:
```json
{
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "ts-node src/index.ts"
}
}
```
Run the application:
```bash
npm run dev
```
## Key Concepts Demonstrated
### 1. Model Definition
- Using decorators to define models and fields
- Implementing validation and transformation
- Setting up relationships between models
- Using hooks for lifecycle management
### 2. Database Scoping
- Global models for shared data (User)
- User-scoped models for private data (Post, Comment)
- Automatic sharding based on model configuration
### 3. Query Operations
- Basic CRUD operations
- Complex queries with filtering and sorting
- Relationship loading (eager and lazy)
- Pagination and search functionality
### 4. Business Logic
- Service layer for application logic
- Model methods for domain-specific operations
- Data validation and transformation
- Analytics and reporting
### 5. Error Handling
- Graceful error handling in service methods
- Validation error handling
- Application lifecycle management
This example provides a solid foundation for building more complex applications with DebrosFramework. You can extend it by adding features like user authentication, real-time notifications, file uploads, and more advanced search capabilities.

View File

@ -0,0 +1,449 @@
---
sidebar_position: 2
---
# Getting Started
This guide will help you set up DebrosFramework and create your first decentralized application in just a few minutes.
## Prerequisites
Before you begin, make sure you have:
- **Node.js** (version 18.0 or above)
- **npm** or **pnpm** package manager
- Basic knowledge of **TypeScript** and **decorators**
- Familiarity with **async/await** patterns
## Installation
### 1. Create a New Project
```bash
mkdir my-debros-app
cd my-debros-app
npm init -y
```
### 2. Install DebrosFramework
```bash
npm install debros-framework
npm install --save-dev typescript @types/node
```
### 3. Set Up TypeScript Configuration
Create a `tsconfig.json` file:
```json
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020"],
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
```
### 4. Install OrbitDB and IPFS Dependencies
DebrosFramework requires OrbitDB and IPFS services:
```bash
npm install @orbitdb/core @helia/helia @helia/unixfs @libp2p/peer-id
```
## Your First Application
Let's create a simple social media application to demonstrate DebrosFramework's capabilities.
### 1. Create the Project Structure
```
src/
├── models/
│ ├── User.ts
│ ├── Post.ts
│ └── index.ts
├── services/
│ ├── orbitdb.ts
│ └── ipfs.ts
└── index.ts
```
### 2. Set Up IPFS Service
Create `src/services/ipfs.ts`:
```typescript
import { createHelia } from '@helia/helia';
import { createLibp2p } from 'libp2p';
// Add other necessary imports based on your setup
export class IPFSService {
private helia: any;
private libp2p: any;
async init(): Promise<void> {
// Initialize your IPFS/Helia instance
// This is a simplified example - customize based on your needs
this.libp2p = await createLibp2p({
// Your libp2p configuration
});
this.helia = await createHelia({
libp2p: this.libp2p,
});
}
getHelia() {
return this.helia;
}
getLibp2pInstance() {
return this.libp2p;
}
async stop(): Promise<void> {
if (this.helia) {
await this.helia.stop();
}
}
}
```
### 3. Set Up OrbitDB Service
Create `src/services/orbitdb.ts`:
```typescript
import { createOrbitDB } from '@orbitdb/core';
export class OrbitDBService {
private orbitdb: any;
private ipfs: any;
constructor(ipfsService: any) {
this.ipfs = ipfsService;
}
async init(): Promise<void> {
this.orbitdb = await createOrbitDB({
ipfs: this.ipfs.getHelia(),
// Add other OrbitDB configuration options
});
}
async openDB(name: string, type: string): Promise<any> {
return await this.orbitdb.open(name, { type });
}
getOrbitDB() {
return this.orbitdb;
}
async stop(): Promise<void> {
if (this.orbitdb) {
await this.orbitdb.stop();
}
}
}
```
### 4. Define Your First Model
Create `src/models/User.ts`:
```typescript
import { BaseModel, Model, Field, HasMany } from 'debros-framework';
import { Post } from './Post';
@Model({
scope: 'global', // Global model - shared across all users
type: 'docstore',
sharding: {
strategy: 'hash',
count: 4,
key: 'id',
},
})
export class User extends BaseModel {
@Field({
type: 'string',
required: true,
unique: true,
validate: (value: string) => value.length >= 3 && value.length <= 20,
})
username: string;
@Field({
type: 'string',
required: true,
unique: true,
validate: (value: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
})
email: string;
@Field({ type: 'string', required: false })
bio?: string;
@Field({ type: 'string', required: false })
profilePicture?: string;
@Field({ type: 'boolean', required: false, default: true })
isActive: boolean;
@Field({ type: 'number', required: false, default: () => Date.now() })
registeredAt: number;
// Relationship: One user has many posts
@HasMany(() => Post, 'userId')
posts: Post[];
}
```
### 5. Create a Post Model
Create `src/models/Post.ts`:
```typescript
import { BaseModel, Model, Field, BelongsTo } from 'debros-framework';
import { User } from './User';
@Model({
scope: 'user', // User-scoped model - each user has their own database
type: 'docstore',
sharding: {
strategy: 'user',
count: 2,
key: 'userId',
},
})
export class Post extends BaseModel {
@Field({ type: 'string', required: true })
title: string;
@Field({
type: 'string',
required: true,
validate: (value: string) => value.length <= 5000,
})
content: string;
@Field({ type: 'string', required: true })
userId: string;
@Field({ type: 'array', required: false, default: [] })
tags: string[];
@Field({ type: 'boolean', required: false, default: true })
isPublic: boolean;
@Field({ type: 'number', required: false, default: 0 })
likeCount: number;
@Field({ type: 'number', required: false, default: () => Date.now() })
createdAt: number;
@Field({ type: 'number', required: false, default: () => Date.now() })
updatedAt: number;
// Relationship: Post belongs to a user
@BelongsTo(() => User, 'userId')
user: User;
}
```
### 6. Export Your Models
Create `src/models/index.ts`:
```typescript
export { User } from './User';
export { Post } from './Post';
```
### 7. Create the Main Application
Create `src/index.ts`:
```typescript
import { DebrosFramework } from 'debros-framework';
import { IPFSService } from './services/ipfs';
import { OrbitDBService } from './services/orbitdb';
import { User, Post } from './models';
async function main() {
// Initialize services
const ipfsService = new IPFSService();
await ipfsService.init();
const orbitDBService = new OrbitDBService(ipfsService);
await orbitDBService.init();
// Initialize DebrosFramework
const framework = new DebrosFramework();
await framework.initialize(orbitDBService, ipfsService);
console.log('🚀 DebrosFramework initialized successfully!');
// Create a user
const user = await User.create({
username: 'alice',
email: 'alice@example.com',
bio: 'Hello, I am Alice!',
isActive: true,
});
console.log('✅ Created user:', user.id);
// Create a post for the user
const post = await Post.create({
title: 'My First Post',
content: 'This is my first post using DebrosFramework!',
userId: user.id,
tags: ['introduction', 'debros'],
isPublic: true,
});
console.log('✅ Created post:', post.id);
// Query users with their posts
const usersWithPosts = await User.query().where('isActive', true).with(['posts']).find();
console.log('📊 Users with posts:');
usersWithPosts.forEach((user) => {
console.log(`- ${user.username}: ${user.posts.length} posts`);
});
// Find posts by tags
const taggedPosts = await Post.query()
.where('tags', 'includes', 'debros')
.with(['user'])
.orderBy('createdAt', 'desc')
.find();
console.log('🏷️ Posts tagged with "debros":');
taggedPosts.forEach((post) => {
console.log(`- "${post.title}" by ${post.user.username}`);
});
// Clean up
await framework.stop();
console.log('👋 Framework stopped successfully');
}
main().catch(console.error);
```
### 8. Add Package.json Scripts
Update your `package.json`:
```json
{
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "ts-node src/index.ts"
}
}
```
### 9. Install Additional Development Dependencies
```bash
npm install --save-dev ts-node
```
## Running Your Application
### 1. Build the Project
```bash
npm run build
```
### 2. Run the Application
```bash
npm start
```
Or for development with hot reloading:
```bash
npm run dev
```
You should see output similar to:
```
🚀 DebrosFramework initialized successfully!
✅ Created user: user_abc123
✅ Created post: post_def456
📊 Users with posts:
- alice: 1 posts
🏷️ Posts tagged with "debros":
- "My First Post" by alice
👋 Framework stopped successfully
```
## What's Next?
Congratulations! You've successfully created your first DebrosFramework application. Here's what you can explore next:
### Learn Core Concepts
- [Architecture Overview](./core-concepts/architecture) - Understand how DebrosFramework works
- [Models and Decorators](./core-concepts/models) - Deep dive into model definition
- [Database Management](./core-concepts/database-management) - Learn about user-scoped vs global databases
### Explore Advanced Features
- [Query System](./query-system/query-builder) - Build complex queries
- [Relationships](./query-system/relationships) - Work with model relationships
- [Automatic Pinning](./advanced/automatic-pinning) - Optimize data availability
- [Migrations](./advanced/migrations) - Evolve your schema over time
### Check Out Examples
- [Social Platform Example](./examples/social-platform) - Complete social media application
- [Complex Queries](./examples/complex-queries) - Advanced query patterns
- [Migration Examples](./examples/migrations) - Schema evolution patterns
## Common Issues
### TypeScript Decorator Errors
Make sure you have `"experimentalDecorators": true` and `"emitDecoratorMetadata": true` in your `tsconfig.json`.
### IPFS/OrbitDB Connection Issues
Ensure your IPFS and OrbitDB services are properly configured. Check the console for connection errors and verify your network configuration.
### Model Registration Issues
Models are automatically registered when imported. Make sure you're importing your models before using DebrosFramework.
## Need Help?
- 📖 Check our [comprehensive documentation](./core-concepts/architecture)
- 💻 Browse [example code](https://github.com/debros/network/tree/main/examples)
- 💬 Join our [Discord community](#)
- 📧 Contact [support](#)
Happy coding with DebrosFramework! 🎉

121
docs/docs/intro.md Normal file
View File

@ -0,0 +1,121 @@
---
sidebar_position: 1
---
# Welcome to DebrosFramework
**DebrosFramework** is a powerful Node.js framework that provides an ORM-like abstraction over OrbitDB and IPFS, making it easy to build scalable decentralized applications.
## What is DebrosFramework?
DebrosFramework simplifies the development of decentralized applications by providing:
- **Model-based Abstraction**: Define your data models using decorators and TypeScript classes
- **Automatic Database Management**: Handle user-scoped and global databases automatically
- **Smart Sharding**: Distribute data across multiple databases for scalability
- **Advanced Query System**: Rich query capabilities with relationship loading and caching
- **Automatic Features**: Built-in pinning strategies and PubSub event publishing
- **Migration System**: Schema evolution and data transformation capabilities
- **Type Safety**: Full TypeScript support with strong typing throughout
## Key Features
### 🏗️ Model-Driven Development
Define your data models using familiar decorator patterns:
```typescript
@Model({
scope: 'user',
type: 'docstore',
sharding: { strategy: 'hash', count: 4, key: 'userId' },
})
class User extends BaseModel {
@Field({ type: 'string', required: true, unique: true })
username: string;
@Field({ type: 'string', required: true, unique: true })
email: string;
@HasMany(() => Post, 'userId')
posts: Post[];
}
```
### 🔍 Powerful Query System
Build complex queries with relationship loading:
```typescript
const users = await User.query()
.where('isActive', true)
.where('registeredAt', '>', Date.now() - 30 * 24 * 60 * 60 * 1000)
.with(['posts', 'followers'])
.orderBy('username')
.limit(20)
.find();
```
### 🚀 Automatic Scaling
Handle millions of users with automatic sharding and pinning:
```typescript
// Framework automatically:
// - Creates user-scoped databases
// - Distributes data across shards
// - Manages pinning strategies
// - Optimizes query routing
```
### 🔄 Schema Evolution
Migrate your data structures safely:
```typescript
const migration = createMigration('add_user_profiles', '1.1.0')
.addField('User', 'profilePicture', { type: 'string', required: false })
.addField('User', 'bio', { type: 'string', required: false })
.transformData('User', (user) => ({
...user,
displayName: user.username || 'Anonymous',
}))
.build();
```
## Architecture Overview
DebrosFramework is built around several core components:
1. **Models & Decorators**: Define your data structure and behavior
2. **Database Manager**: Handles database creation and management
3. **Shard Manager**: Distributes data across multiple databases
4. **Query System**: Processes queries with optimization and caching
5. **Relationship Manager**: Handles complex relationships between models
6. **Migration System**: Manages schema evolution over time
7. **Automatic Features**: Pinning, PubSub, and performance optimization
## Who Should Use DebrosFramework?
DebrosFramework is perfect for developers who want to:
- Build decentralized applications without dealing with low-level OrbitDB complexities
- Create scalable applications that can handle millions of users
- Use familiar ORM patterns in a decentralized environment
- Implement complex data relationships in distributed systems
- Focus on business logic rather than infrastructure concerns
## Getting Started
Ready to build your first decentralized application? Check out our [Getting Started Guide](./getting-started) to set up your development environment and create your first models.
## Community and Support
- 📖 [Documentation](./getting-started) - Comprehensive guides and examples
- 💻 [GitHub Repository](https://github.com/debros/network) - Source code and issue tracking
- 💬 [Discord Community](#) - Chat with other developers
- 📧 [Support Email](#) - Get help from the core team
---
_DebrosFramework is designed to make decentralized application development as simple as traditional web development, while providing the benefits of distributed systems._

View File

@ -0,0 +1,528 @@
---
sidebar_position: 1
---
# Query Builder
The DebrosFramework Query Builder provides a powerful, type-safe, and intuitive API for querying your decentralized data. It supports complex filtering, ordering, pagination, and relationship loading across distributed databases.
## Basic Query Syntax
### Simple Queries
```typescript
import { User, Post } from './models';
// Find all users
const allUsers = await User.query().find();
// Find a single user
const user = await User.query().findOne();
// Find user by ID
const userById = await User.findById('user_123');
// Count users
const userCount = await User.query().count();
// Check if any users exist
const hasUsers = await User.query().exists();
```
### Where Clauses
The query builder supports various where clause operators:
```typescript
// Basic equality
const activeUsers = await User.query().where('isActive', true).find();
// Comparison operators
const recentPosts = await Post.query()
.where('createdAt', '>', Date.now() - 24 * 60 * 60 * 1000)
.find();
const popularPosts = await Post.query().where('likeCount', '>=', 100).find();
// Multiple conditions (AND)
const recentPopularPosts = await Post.query()
.where('createdAt', '>', Date.now() - 24 * 60 * 60 * 1000)
.where('likeCount', '>=', 50)
.where('isPublished', true)
.find();
// IN operator
const specificUsers = await User.query().where('id', 'in', ['user_1', 'user_2', 'user_3']).find();
// NOT IN operator
const excludedUsers = await User.query().where('username', 'not in', ['admin', 'test']).find();
// LIKE operator (for strings)
const usersStartingWithA = await User.query().where('username', 'like', 'a%').find();
// Regular expressions
const emailUsers = await User.query()
.where('email', 'regex', /@gmail\.com$/)
.find();
// Null checks
const usersWithoutBio = await User.query().where('bio', 'is null').find();
const usersWithBio = await User.query().where('bio', 'is not null').find();
// Array operations
const techPosts = await Post.query().where('tags', 'includes', 'technology').find();
const multipleTags = await Post.query()
.where('tags', 'includes any', ['tech', 'programming', 'code'])
.find();
const allTags = await Post.query().where('tags', 'includes all', ['react', 'typescript']).find();
```
#### Supported Where Operators
| Operator | Description | Example |
| -------------- | ------------------------- | -------------------------------------------- |
| `=` or `eq` | Equal to | `.where('status', 'active')` |
| `!=` or `ne` | Not equal to | `.where('status', '!=', 'deleted')` |
| `>` or `gt` | Greater than | `.where('score', '>', 100)` |
| `>=` or `gte` | Greater than or equal | `.where('score', '>=', 100)` |
| `<` or `lt` | Less than | `.where('age', '<', 18)` |
| `<=` or `lte` | Less than or equal | `.where('age', '<=', 65)` |
| `in` | In array | `.where('id', 'in', ['1', '2'])` |
| `not in` | Not in array | `.where('status', 'not in', ['deleted'])` |
| `like` | Pattern matching | `.where('name', 'like', 'John%')` |
| `regex` | Regular expression | `.where('email', 'regex', /@gmail/)` |
| `is null` | Is null | `.where('deletedAt', 'is null')` |
| `is not null` | Is not null | `.where('email', 'is not null')` |
| `includes` | Array includes value | `.where('tags', 'includes', 'tech')` |
| `includes any` | Array includes any value | `.where('tags', 'includes any', ['a', 'b'])` |
| `includes all` | Array includes all values | `.where('tags', 'includes all', ['a', 'b'])` |
### OR Conditions
Use `orWhere` to create OR conditions:
```typescript
// Users who are either active OR have logged in recently
const relevantUsers = await User.query()
.where('isActive', true)
.orWhere('lastLoginAt', '>', Date.now() - 7 * 24 * 60 * 60 * 1000)
.find();
// Complex OR conditions with grouping
const complexQuery = await Post.query()
.where('isPublished', true)
.where((query) => {
query.where('category', 'tech').orWhere('category', 'programming');
})
.orWhere((query) => {
query.where('likeCount', '>', 100).where('commentCount', '>', 10);
})
.find();
```
### Query Grouping
Group conditions using nested query builders:
```typescript
// (status = 'active' OR status = 'pending') AND (role = 'admin' OR role = 'moderator')
const privilegedUsers = await User.query()
.where((query) => {
query.where('status', 'active').orWhere('status', 'pending');
})
.where((query) => {
query.where('role', 'admin').orWhere('role', 'moderator');
})
.find();
// Complex nested conditions
const complexPosts = await Post.query()
.where('isPublished', true)
.where((query) => {
query
.where((subQuery) => {
subQuery.where('category', 'tech').where('difficulty', 'beginner');
})
.orWhere((subQuery) => {
subQuery.where('category', 'tutorial').where('likeCount', '>', 50);
});
})
.find();
```
## Ordering and Pagination
### Ordering Results
```typescript
// Order by single field
const usersByName = await User.query().orderBy('username').find();
// Order by multiple fields
const postsByPopularity = await Post.query()
.orderBy('likeCount', 'desc')
.orderBy('createdAt', 'desc')
.find();
// Order by computed fields
const usersByActivity = await User.query()
.orderBy('lastLoginAt', 'desc')
.orderBy('username', 'asc')
.find();
// Random ordering
const randomPosts = await Post.query().orderBy('random').limit(10).find();
```
### Pagination
```typescript
// Limit results
const latestPosts = await Post.query().orderBy('createdAt', 'desc').limit(10).find();
// Offset pagination
const secondPage = await Post.query().orderBy('createdAt', 'desc').limit(10).offset(10).find();
// Cursor-based pagination (more efficient for large datasets)
const cursorPosts = await Post.query()
.orderBy('createdAt', 'desc')
.after('cursor_value_here')
.limit(10)
.find();
// Get pagination info
const paginatedResult = await Post.query().orderBy('createdAt', 'desc').paginate(1, 20); // page 1, 20 items per page
console.log(paginatedResult.data); // Results
console.log(paginatedResult.total); // Total count
console.log(paginatedResult.page); // Current page
console.log(paginatedResult.perPage); // Items per page
console.log(paginatedResult.totalPages); // Total pages
```
## Relationship Loading
### Eager Loading
Load relationships along with the main query:
```typescript
// Load user with their posts
const usersWithPosts = await User.query().with(['posts']).find();
// Load nested relationships
const usersWithPostsAndComments = await User.query().with(['posts.comments']).find();
// Load multiple relationships
const fullUserData = await User.query().with(['posts', 'comments', 'followers']).find();
// Load relationships with conditions
const usersWithRecentPosts = await User.query()
.with(['posts'], (query) => {
query
.where('createdAt', '>', Date.now() - 30 * 24 * 60 * 60 * 1000)
.orderBy('createdAt', 'desc')
.limit(5);
})
.find();
// Complex relationship loading
const complexUserData = await User.query()
.with([
'posts.comments.user', // Deep nested relationships
'followers.posts', // Multiple levels
'settings', // Simple relationship
])
.find();
```
### Lazy Loading
Relationships are loaded automatically when accessed:
```typescript
const user = await User.findById('user_123');
// Posts are loaded when first accessed
console.log(user.posts.length); // Triggers lazy load
// Subsequent access uses cached data
user.posts.forEach((post) => console.log(post.title));
```
### Relationship Constraints
Apply constraints to relationship loading:
```typescript
// Load users with their published posts only
const usersWithPublishedPosts = await User.query()
.with(['posts'], (query) => {
query.where('isPublished', true).orderBy('publishedAt', 'desc');
})
.find();
// Load posts with recent comments only
const postsWithRecentComments = await Post.query()
.with(['comments'], (query) => {
query
.where('createdAt', '>', Date.now() - 24 * 60 * 60 * 1000)
.with(['user']) // Load comment users too
.orderBy('createdAt', 'desc');
})
.find();
// Load users with follower count > 100
const popularUsers = await User.query()
.with(['followers'], (query) => {
query.limit(100); // Limit followers loaded
})
.having('followers.length', '>', 100)
.find();
```
## Advanced Query Features
### Aggregation
```typescript
// Count records
const userCount = await User.query().where('isActive', true).count();
// Count distinct values
const uniqueCategories = await Post.query().countDistinct('category');
// Sum values
const totalLikes = await Post.query().sum('likeCount');
// Average values
const averageScore = await User.query().average('score');
// Min/Max values
const oldestPost = await Post.query().min('createdAt');
const newestPost = await Post.query().max('createdAt');
// Group by aggregations
const postsByCategory = await Post.query()
.groupBy('category')
.select(['category', 'COUNT(*) as count', 'AVG(likeCount) as avgLikes'])
.find();
```
### Having Clauses
Use `having` for filtering aggregated results:
```typescript
// Categories with more than 10 posts
const popularCategories = await Post.query()
.groupBy('category')
.having('COUNT(*)', '>', 10)
.select(['category', 'COUNT(*) as postCount'])
.find();
// Users with high average post likes
const influentialUsers = await User.query()
.join('posts', 'users.id', 'posts.userId')
.groupBy('users.id')
.having('AVG(posts.likeCount)', '>', 50)
.select(['users.*', 'AVG(posts.likeCount) as avgLikes'])
.find();
```
### Subqueries
```typescript
// Users who have posts
const usersWithPosts = await User.query()
.whereExists(User.query().select('1').from('posts').whereColumn('posts.userId', 'users.id'))
.find();
// Users who don't have posts
const usersWithoutPosts = await User.query()
.whereNotExists(User.query().select('1').from('posts').whereColumn('posts.userId', 'users.id'))
.find();
// Posts with above-average like count
const popularPosts = await Post.query()
.where('likeCount', '>', (query) => {
query.select('AVG(likeCount)').from('posts');
})
.find();
```
### Raw Queries
For complex queries that can't be expressed with the query builder:
```typescript
// Raw where clause
const complexUsers = await User.query()
.whereRaw('score > ? AND (status = ? OR created_at > ?)', [
100,
'active',
Date.now() - 30 * 24 * 60 * 60 * 1000,
])
.find();
// Raw select
const userStats = await User.query()
.selectRaw('username, COUNT(posts.id) as post_count, AVG(posts.like_count) as avg_likes')
.join('posts', 'users.id', 'posts.user_id')
.groupBy('users.id', 'username')
.find();
// Completely raw query
const customResults = await User.raw(
`
SELECT u.username, COUNT(p.id) as posts
FROM users u
LEFT JOIN posts p ON u.id = p.user_id
WHERE u.created_at > ?
GROUP BY u.id, u.username
HAVING COUNT(p.id) > ?
`,
[Date.now() - 30 * 24 * 60 * 60 * 1000, 5],
);
```
## Query Caching
### Basic Caching
```typescript
// Cache query results for 5 minutes
const cachedUsers = await User.query()
.where('isActive', true)
.cache(300) // 300 seconds
.find();
// Cache with custom key
const cachedPosts = await Post.query()
.where('category', 'tech')
.cache(600, 'tech-posts') // Custom cache key
.find();
// Disable caching for sensitive data
const sensitiveData = await User.query().where('role', 'admin').noCache().find();
```
### Cache Tags
Use cache tags for intelligent cache invalidation:
```typescript
// Tag cache entries
const taggedQuery = await Post.query()
.where('category', 'tech')
.cacheTag(['posts', 'tech-posts'])
.cache(600)
.find();
// Invalidate tagged cache entries
await Post.invalidateCacheTag('tech-posts');
```
## Query Optimization
### Indexes
Ensure your frequently queried fields are indexed:
```typescript
@Model({
indexes: [
{ fields: ['username'], unique: true },
{ fields: ['email'], unique: true },
{ fields: ['createdAt', 'isActive'] },
{ fields: ['category', 'publishedAt'] },
],
})
export class User extends BaseModel {
// Model definition
}
```
### Query Hints
Provide hints to the query optimizer:
```typescript
// Hint about expected result size
const users = await User.query()
.where('isActive', true)
.hint('small_result_set') // Expects < 100 results
.find();
// Hint about query pattern
const recentPosts = await Post.query()
.where('createdAt', '>', Date.now() - 24 * 60 * 60 * 1000)
.hint('time_range_query')
.orderBy('createdAt', 'desc')
.find();
```
### Batch Operations
Optimize multiple queries with batching:
```typescript
// Batch multiple queries
const [users, posts, comments] = await Promise.all([
User.query().where('isActive', true).find(),
Post.query().where('isPublished', true).find(),
Comment.query().where('isApproved', true).find(),
]);
// Batch with shared cache
const batchResults = await Query.batch()
.add('users', User.query().where('isActive', true))
.add('posts', Post.query().where('isPublished', true))
.add('comments', Comment.query().where('isApproved', true))
.cache(300)
.execute();
```
## Error Handling
```typescript
try {
const users = await User.query()
.where('invalidField', 'value') // This might cause an error
.find();
} catch (error) {
if (error.code === 'INVALID_FIELD') {
console.error('Invalid field in query:', error.field);
} else if (error.code === 'NETWORK_ERROR') {
console.error('Network error during query:', error.message);
} else {
console.error('Unexpected error:', error);
}
}
// Query with error recovery
const safeUsers = await User.query()
.where('isActive', true)
.fallback(() => {
// Fallback to cache if query fails
return User.fromCache('active-users') || [];
})
.find();
```
## Performance Best Practices
1. **Use indexes** for frequently queried fields
2. **Limit results** with `.limit()` to avoid loading large datasets
3. **Use caching** for expensive or repeated queries
4. **Eager load relationships** when you know you'll need them
5. **Use pagination** for large result sets
6. **Monitor query performance** and optimize slow queries
7. **Batch related queries** when possible
8. **Use appropriate data types** and avoid unnecessary type conversions
The DebrosFramework Query Builder provides a powerful and flexible way to query your decentralized data while maintaining type safety and performance across distributed systems.

View File

@ -0,0 +1,8 @@
{
"label": "Tutorial - Basics",
"position": 2,
"link": {
"type": "generated-index",
"description": "5 minutes to learn the most important Docusaurus concepts."
}
}

View File

@ -0,0 +1,23 @@
---
sidebar_position: 6
---
# Congratulations!
You have just learned the **basics of Docusaurus** and made some changes to the **initial template**.
Docusaurus has **much more to offer**!
Have **5 more minutes**? Take a look at **[versioning](../tutorial-extras/manage-docs-versions.md)** and **[i18n](../tutorial-extras/translate-your-site.md)**.
Anything **unclear** or **buggy** in this tutorial? [Please report it!](https://github.com/facebook/docusaurus/discussions/4610)
## What's next?
- Read the [official documentation](https://docusaurus.io/)
- Modify your site configuration with [`docusaurus.config.js`](https://docusaurus.io/docs/api/docusaurus-config)
- Add navbar and footer items with [`themeConfig`](https://docusaurus.io/docs/api/themes/configuration)
- Add a custom [Design and Layout](https://docusaurus.io/docs/styling-layout)
- Add a [search bar](https://docusaurus.io/docs/search)
- Find inspirations in the [Docusaurus showcase](https://docusaurus.io/showcase)
- Get involved in the [Docusaurus Community](https://docusaurus.io/community/support)

View File

@ -0,0 +1,34 @@
---
sidebar_position: 3
---
# Create a Blog Post
Docusaurus creates a **page for each blog post**, but also a **blog index page**, a **tag system**, an **RSS** feed...
## Create your first Post
Create a file at `blog/2021-02-28-greetings.md`:
```md title="blog/2021-02-28-greetings.md"
---
slug: greetings
title: Greetings!
authors:
- name: Joel Marcey
title: Co-creator of Docusaurus 1
url: https://github.com/JoelMarcey
image_url: https://github.com/JoelMarcey.png
- name: Sébastien Lorber
title: Docusaurus maintainer
url: https://sebastienlorber.com
image_url: https://github.com/slorber.png
tags: [greetings]
---
Congratulations, you have made your first post!
Feel free to play around and edit this post as much as you like.
```
A new blog post is now available at [http://localhost:3000/blog/greetings](http://localhost:3000/blog/greetings).

View File

@ -0,0 +1,57 @@
---
sidebar_position: 2
---
# Create a Document
Documents are **groups of pages** connected through:
- a **sidebar**
- **previous/next navigation**
- **versioning**
## Create your first Doc
Create a Markdown file at `docs/hello.md`:
```md title="docs/hello.md"
# Hello
This is my **first Docusaurus document**!
```
A new document is now available at [http://localhost:3000/docs/hello](http://localhost:3000/docs/hello).
## Configure the Sidebar
Docusaurus automatically **creates a sidebar** from the `docs` folder.
Add metadata to customize the sidebar label and position:
```md title="docs/hello.md" {1-4}
---
sidebar_label: 'Hi!'
sidebar_position: 3
---
# Hello
This is my **first Docusaurus document**!
```
It is also possible to create your sidebar explicitly in `sidebars.js`:
```js title="sidebars.js"
export default {
tutorialSidebar: [
'intro',
// highlight-next-line
'hello',
{
type: 'category',
label: 'Tutorial',
items: ['tutorial-basics/create-a-document'],
},
],
};
```

View File

@ -0,0 +1,43 @@
---
sidebar_position: 1
---
# Create a Page
Add **Markdown or React** files to `src/pages` to create a **standalone page**:
- `src/pages/index.js``localhost:3000/`
- `src/pages/foo.md``localhost:3000/foo`
- `src/pages/foo/bar.js``localhost:3000/foo/bar`
## Create your first React Page
Create a file at `src/pages/my-react-page.js`:
```jsx title="src/pages/my-react-page.js"
import React from 'react';
import Layout from '@theme/Layout';
export default function MyReactPage() {
return (
<Layout>
<h1>My React page</h1>
<p>This is a React page</p>
</Layout>
);
}
```
A new page is now available at [http://localhost:3000/my-react-page](http://localhost:3000/my-react-page).
## Create your first Markdown Page
Create a file at `src/pages/my-markdown-page.md`:
```mdx title="src/pages/my-markdown-page.md"
# My Markdown page
This is a Markdown page
```
A new page is now available at [http://localhost:3000/my-markdown-page](http://localhost:3000/my-markdown-page).

View File

@ -0,0 +1,31 @@
---
sidebar_position: 5
---
# Deploy your site
Docusaurus is a **static-site-generator** (also called **[Jamstack](https://jamstack.org/)**).
It builds your site as simple **static HTML, JavaScript and CSS files**.
## Build your site
Build your site **for production**:
```bash
npm run build
```
The static files are generated in the `build` folder.
## Deploy your site
Test your production build locally:
```bash
npm run serve
```
The `build` folder is now served at [http://localhost:3000/](http://localhost:3000/).
You can now deploy the `build` folder **almost anywhere** easily, **for free** or very small cost (read the **[Deployment Guide](https://docusaurus.io/docs/deployment)**).

View File

@ -0,0 +1,152 @@
---
sidebar_position: 4
---
# Markdown Features
Docusaurus supports **[Markdown](https://daringfireball.net/projects/markdown/syntax)** and a few **additional features**.
## Front Matter
Markdown documents have metadata at the top called [Front Matter](https://jekyllrb.com/docs/front-matter/):
```text title="my-doc.md"
// highlight-start
---
id: my-doc-id
title: My document title
description: My document description
slug: /my-custom-url
---
// highlight-end
## Markdown heading
Markdown text with [links](./hello.md)
```
## Links
Regular Markdown links are supported, using url paths or relative file paths.
```md
Let's see how to [Create a page](/create-a-page).
```
```md
Let's see how to [Create a page](./create-a-page.md).
```
**Result:** Let's see how to [Create a page](./create-a-page.md).
## Images
Regular Markdown images are supported.
You can use absolute paths to reference images in the static directory (`static/img/docusaurus.png`):
```md
![Docusaurus logo](/img/docusaurus.png)
```
![Docusaurus logo](/img/docusaurus.png)
You can reference images relative to the current file as well. This is particularly useful to colocate images close to the Markdown files using them:
```md
![Docusaurus logo](./img/docusaurus.png)
```
## Code Blocks
Markdown code blocks are supported with Syntax highlighting.
````md
```jsx title="src/components/HelloDocusaurus.js"
function HelloDocusaurus() {
return <h1>Hello, Docusaurus!</h1>;
}
```
````
```jsx title="src/components/HelloDocusaurus.js"
function HelloDocusaurus() {
return <h1>Hello, Docusaurus!</h1>;
}
```
## Admonitions
Docusaurus has a special syntax to create admonitions and callouts:
```md
:::tip My tip
Use this awesome feature option
:::
:::danger Take care
This action is dangerous
:::
```
:::tip My tip
Use this awesome feature option
:::
:::danger Take care
This action is dangerous
:::
## MDX and React Components
[MDX](https://mdxjs.com/) can make your documentation more **interactive** and allows using any **React components inside Markdown**:
```jsx
export const Highlight = ({children, color}) => (
<span
style={{
backgroundColor: color,
borderRadius: '20px',
color: '#fff',
padding: '10px',
cursor: 'pointer',
}}
onClick={() => {
alert(`You clicked the color ${color} with label ${children}`)
}}>
{children}
</span>
);
This is <Highlight color="#25c2a0">Docusaurus green</Highlight> !
This is <Highlight color="#1877F2">Facebook blue</Highlight> !
```
export const Highlight = ({children, color}) => (
<span
style={{
backgroundColor: color,
borderRadius: '20px',
color: '#fff',
padding: '10px',
cursor: 'pointer',
}}
onClick={() => {
alert(`You clicked the color ${color} with label ${children}`);
}}>
{children}
</span>
);
This is <Highlight color="#25c2a0">Docusaurus green</Highlight> !
This is <Highlight color="#1877F2">Facebook blue</Highlight> !

View File

@ -0,0 +1,7 @@
{
"label": "Tutorial - Extras",
"position": 3,
"link": {
"type": "generated-index"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -0,0 +1,55 @@
---
sidebar_position: 1
---
# Manage Docs Versions
Docusaurus can manage multiple versions of your docs.
## Create a docs version
Release a version 1.0 of your project:
```bash
npm run docusaurus docs:version 1.0
```
The `docs` folder is copied into `versioned_docs/version-1.0` and `versions.json` is created.
Your docs now have 2 versions:
- `1.0` at `http://localhost:3000/docs/` for the version 1.0 docs
- `current` at `http://localhost:3000/docs/next/` for the **upcoming, unreleased docs**
## Add a Version Dropdown
To navigate seamlessly across versions, add a version dropdown.
Modify the `docusaurus.config.js` file:
```js title="docusaurus.config.js"
export default {
themeConfig: {
navbar: {
items: [
// highlight-start
{
type: 'docsVersionDropdown',
},
// highlight-end
],
},
},
};
```
The docs version dropdown appears in your navbar:
![Docs Version Dropdown](./img/docsVersionDropdown.png)
## Update an existing version
It is possible to edit versioned docs in their respective folder:
- `versioned_docs/version-1.0/hello.md` updates `http://localhost:3000/docs/hello`
- `docs/hello.md` updates `http://localhost:3000/docs/next/hello`

View File

@ -0,0 +1,88 @@
---
sidebar_position: 2
---
# Translate your site
Let's translate `docs/intro.md` to French.
## Configure i18n
Modify `docusaurus.config.js` to add support for the `fr` locale:
```js title="docusaurus.config.js"
export default {
i18n: {
defaultLocale: 'en',
locales: ['en', 'fr'],
},
};
```
## Translate a doc
Copy the `docs/intro.md` file to the `i18n/fr` folder:
```bash
mkdir -p i18n/fr/docusaurus-plugin-content-docs/current/
cp docs/intro.md i18n/fr/docusaurus-plugin-content-docs/current/intro.md
```
Translate `i18n/fr/docusaurus-plugin-content-docs/current/intro.md` in French.
## Start your localized site
Start your site on the French locale:
```bash
npm run start -- --locale fr
```
Your localized site is accessible at [http://localhost:3000/fr/](http://localhost:3000/fr/) and the `Getting Started` page is translated.
:::caution
In development, you can only use one locale at a time.
:::
## Add a Locale Dropdown
To navigate seamlessly across languages, add a locale dropdown.
Modify the `docusaurus.config.js` file:
```js title="docusaurus.config.js"
export default {
themeConfig: {
navbar: {
items: [
// highlight-start
{
type: 'localeDropdown',
},
// highlight-end
],
},
},
};
```
The locale dropdown now appears in your navbar:
![Locale Dropdown](./img/localeDropdown.png)
## Build your localized site
Build your site for a specific locale:
```bash
npm run build -- --locale fr
```
Or build your site to include all the locales at once:
```bash
npm run build
```

154
docs/docusaurus.config.ts Normal file
View File

@ -0,0 +1,154 @@
import {themes as prismThemes} from 'prism-react-renderer';
import type {Config} from '@docusaurus/types';
import type * as Preset from '@docusaurus/preset-classic';
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
const config: Config = {
title: 'DebrosFramework',
tagline: 'Build scalable decentralized applications with ease',
favicon: 'img/favicon.ico',
// Future flags, see https://docusaurus.io/docs/api/docusaurus-config#future
future: {
v4: true, // Improve compatibility with the upcoming Docusaurus v4
},
// Set the production url of your site here
url: 'https://debros-framework.example.com',
// Set the /<baseUrl>/ pathname under which your site is served
// For GitHub pages deployment, it is often '/<projectName>/'
baseUrl: '/',
// GitHub pages deployment config.
// If you aren't using GitHub pages, you don't need these.
organizationName: 'debros', // Usually your GitHub org/user name.
projectName: 'debros-framework', // Usually your repo name.
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
// Even if you don't use internationalization, you can use this field to set
// useful metadata like html lang. For example, if your site is Chinese, you
// may want to replace "en" with "zh-Hans".
i18n: {
defaultLocale: 'en',
locales: ['en'],
},
presets: [
[
'classic',
{
docs: {
sidebarPath: './sidebars.ts',
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
editUrl:
'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
},
blog: {
showReadingTime: true,
feedOptions: {
type: ['rss', 'atom'],
xslt: true,
},
// Please change this to your repo.
// Remove this to remove the "edit this page" links.
editUrl:
'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/',
// Useful options to enforce blogging best practices
onInlineTags: 'warn',
onInlineAuthors: 'warn',
onUntruncatedBlogPosts: 'warn',
},
theme: {
customCss: './src/css/custom.css',
},
} satisfies Preset.Options,
],
],
themeConfig: {
// Replace with your project's social card
image: 'img/docusaurus-social-card.jpg',
navbar: {
title: 'DebrosFramework',
logo: {
alt: 'DebrosFramework Logo',
src: 'img/logo.svg',
},
items: [
{
type: 'docSidebar',
sidebarId: 'tutorialSidebar',
position: 'left',
label: 'Documentation',
},
{
type: 'docSidebar',
sidebarId: 'apiSidebar',
position: 'left',
label: 'API Reference',
},
{to: '/blog', label: 'Blog', position: 'left'},
{
href: 'https://github.com/debros/network',
label: 'GitHub',
position: 'right',
},
],
},
footer: {
style: 'dark',
links: [
{
title: 'Docs',
items: [
{
label: 'Tutorial',
to: '/docs/intro',
},
],
},
{
title: 'Community',
items: [
{
label: 'Stack Overflow',
href: 'https://stackoverflow.com/questions/tagged/docusaurus',
},
{
label: 'Discord',
href: 'https://discordapp.com/invite/docusaurus',
},
{
label: 'X',
href: 'https://x.com/docusaurus',
},
],
},
{
title: 'More',
items: [
{
label: 'Blog',
to: '/blog',
},
{
label: 'GitHub',
href: 'https://github.com/facebook/docusaurus',
},
],
},
],
copyright: `Copyright © ${new Date().getFullYear()} DebrosFramework. Built with Docusaurus.`,
},
prism: {
theme: prismThemes.github,
darkTheme: prismThemes.dracula,
},
} satisfies Preset.ThemeConfig,
};
export default config;

47
docs/package.json Normal file
View File

@ -0,0 +1,47 @@
{
"name": "docs",
"version": "0.0.0",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids",
"typecheck": "tsc"
},
"dependencies": {
"@docusaurus/core": "3.8.1",
"@docusaurus/preset-classic": "3.8.1",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"prism-react-renderer": "^2.3.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "3.8.1",
"@docusaurus/tsconfig": "3.8.1",
"@docusaurus/types": "3.8.1",
"typescript": "~5.6.2"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
],
"development": [
"last 3 chrome version",
"last 3 firefox version",
"last 5 safari version"
]
},
"engines": {
"node": ">=18.0"
}
}

11012
docs/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

58
docs/sidebars.ts Normal file
View File

@ -0,0 +1,58 @@
import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)
/**
* Creating a sidebar enables you to:
- create an ordered group of docs
- render a sidebar for each doc of that group
- provide next/previous navigation
The sidebars can be generated from the filesystem, or explicitly defined here.
Create as many sidebars as you want.
*/
const sidebars: SidebarsConfig = {
// Main documentation sidebar
tutorialSidebar: [
'intro',
'getting-started',
{
type: 'category',
label: 'Core Concepts',
items: [
'core-concepts/architecture',
'core-concepts/models',
'core-concepts/decorators',
],
},
{
type: 'category',
label: 'Query System',
items: [
'query-system/query-builder',
],
},
{
type: 'category',
label: 'Examples',
items: [
'examples/basic-usage',
],
},
],
// API Reference sidebar
apiSidebar: [
'api/overview',
{
type: 'category',
label: 'Framework Classes',
items: [
'api/debros-framework',
],
},
],
};
export default sidebars;

View File

@ -0,0 +1,71 @@
import type {ReactNode} from 'react';
import clsx from 'clsx';
import Heading from '@theme/Heading';
import styles from './styles.module.css';
type FeatureItem = {
title: string;
Svg: React.ComponentType<React.ComponentProps<'svg'>>;
description: ReactNode;
};
const FeatureList: FeatureItem[] = [
{
title: 'Easy to Use',
Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default,
description: (
<>
Docusaurus was designed from the ground up to be easily installed and
used to get your website up and running quickly.
</>
),
},
{
title: 'Focus on What Matters',
Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default,
description: (
<>
Docusaurus lets you focus on your docs, and we&apos;ll do the chores. Go
ahead and move your docs into the <code>docs</code> directory.
</>
),
},
{
title: 'Powered by React',
Svg: require('@site/static/img/undraw_docusaurus_react.svg').default,
description: (
<>
Extend or customize your website layout by reusing React. Docusaurus can
be extended while reusing the same header and footer.
</>
),
},
];
function Feature({title, Svg, description}: FeatureItem) {
return (
<div className={clsx('col col--4')}>
<div className="text--center">
<Svg className={styles.featureSvg} role="img" />
</div>
<div className="text--center padding-horiz--md">
<Heading as="h3">{title}</Heading>
<p>{description}</p>
</div>
</div>
);
}
export default function HomepageFeatures(): ReactNode {
return (
<section className={styles.features}>
<div className="container">
<div className="row">
{FeatureList.map((props, idx) => (
<Feature key={idx} {...props} />
))}
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,11 @@
.features {
display: flex;
align-items: center;
padding: 2rem 0;
width: 100%;
}
.featureSvg {
height: 200px;
width: 200px;
}

30
docs/src/css/custom.css Normal file
View File

@ -0,0 +1,30 @@
/**
* Any CSS included here will be global. The classic template
* bundles Infima by default. Infima is a CSS framework designed to
* work well for content-centric websites.
*/
/* You can override the default Infima variables here. */
:root {
--ifm-color-primary: #2e8555;
--ifm-color-primary-dark: #29784c;
--ifm-color-primary-darker: #277148;
--ifm-color-primary-darkest: #205d3b;
--ifm-color-primary-light: #33925d;
--ifm-color-primary-lighter: #359962;
--ifm-color-primary-lightest: #3cad6e;
--ifm-code-font-size: 95%;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
}
/* For readability concerns, you should choose a lighter palette in dark mode. */
[data-theme='dark'] {
--ifm-color-primary: #25c2a0;
--ifm-color-primary-dark: #21af90;
--ifm-color-primary-darker: #1fa588;
--ifm-color-primary-darkest: #1a8870;
--ifm-color-primary-light: #29d5b0;
--ifm-color-primary-lighter: #32d8b4;
--ifm-color-primary-lightest: #4fddbf;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
}

View File

@ -0,0 +1,23 @@
/**
* CSS files with the .module.css suffix will be treated as CSS modules
* and scoped locally.
*/
.heroBanner {
padding: 4rem 0;
text-align: center;
position: relative;
overflow: hidden;
}
@media screen and (max-width: 996px) {
.heroBanner {
padding: 2rem;
}
}
.buttons {
display: flex;
align-items: center;
justify-content: center;
}

44
docs/src/pages/index.tsx Normal file
View File

@ -0,0 +1,44 @@
import type {ReactNode} from 'react';
import clsx from 'clsx';
import Link from '@docusaurus/Link';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import Layout from '@theme/Layout';
import HomepageFeatures from '@site/src/components/HomepageFeatures';
import Heading from '@theme/Heading';
import styles from './index.module.css';
function HomepageHeader() {
const {siteConfig} = useDocusaurusContext();
return (
<header className={clsx('hero hero--primary', styles.heroBanner)}>
<div className="container">
<Heading as="h1" className="hero__title">
{siteConfig.title}
</Heading>
<p className="hero__subtitle">{siteConfig.tagline}</p>
<div className={styles.buttons}>
<Link
className="button button--secondary button--lg"
to="/docs/intro">
Docusaurus Tutorial - 5min
</Link>
</div>
</div>
</header>
);
}
export default function Home(): ReactNode {
const {siteConfig} = useDocusaurusContext();
return (
<Layout
title={`Hello from ${siteConfig.title}`}
description="Description will go into a meta tag in <head />">
<HomepageHeader />
<main>
<HomepageFeatures />
</main>
</Layout>
);
}

View File

@ -0,0 +1,7 @@
---
title: Markdown page example
---
# Markdown page example
You don't need React to write simple standalone pages.

0
docs/static/.nojekyll vendored Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
docs/static/img/docusaurus.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
docs/static/img/favicon.ico vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

1
docs/static/img/logo.svg vendored Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@ -0,0 +1,171 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1088" height="687.962" viewBox="0 0 1088 687.962">
<title>Easy to Use</title>
<g id="Group_12" data-name="Group 12" transform="translate(-57 -56)">
<g id="Group_11" data-name="Group 11" transform="translate(57 56)">
<path id="Path_83" data-name="Path 83" d="M1017.81,560.461c-5.27,45.15-16.22,81.4-31.25,110.31-20,38.52-54.21,54.04-84.77,70.28a193.275,193.275,0,0,1-27.46,11.94c-55.61,19.3-117.85,14.18-166.74,3.99a657.282,657.282,0,0,0-104.09-13.16q-14.97-.675-29.97-.67c-15.42.02-293.07,5.29-360.67-131.57-16.69-33.76-28.13-75-32.24-125.27-11.63-142.12,52.29-235.46,134.74-296.47,155.97-115.41,369.76-110.57,523.43,7.88C941.15,276.621,1036.99,396.031,1017.81,560.461Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_84" data-name="Path 84" d="M986.56,670.771c-20,38.52-47.21,64.04-77.77,80.28a193.272,193.272,0,0,1-27.46,11.94c-55.61,19.3-117.85,14.18-166.74,3.99a657.3,657.3,0,0,0-104.09-13.16q-14.97-.675-29.97-.67-23.13.03-46.25,1.72c-100.17,7.36-253.82-6.43-321.42-143.29L382,283.981,444.95,445.6l20.09,51.59,55.37-75.98L549,381.981l130.2,149.27,36.8-81.27L970.78,657.9l14.21,11.59Z" transform="translate(-56 -106.019)" fill="#f2f2f2"/>
<path id="Path_85" data-name="Path 85" d="M302,282.962l26-57,36,83-31-60Z" opacity="0.1"/>
<path id="Path_86" data-name="Path 86" d="M610.5,753.821q-14.97-.675-29.97-.67L465.04,497.191Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<path id="Path_87" data-name="Path 87" d="M464.411,315.191,493,292.962l130,150-132-128Z" opacity="0.1"/>
<path id="Path_88" data-name="Path 88" d="M908.79,751.051a193.265,193.265,0,0,1-27.46,11.94L679.2,531.251Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<circle id="Ellipse_11" data-name="Ellipse 11" cx="3" cy="3" r="3" transform="translate(479 98.962)" fill="#f2f2f2"/>
<circle id="Ellipse_12" data-name="Ellipse 12" cx="3" cy="3" r="3" transform="translate(396 201.962)" fill="#f2f2f2"/>
<circle id="Ellipse_13" data-name="Ellipse 13" cx="2" cy="2" r="2" transform="translate(600 220.962)" fill="#f2f2f2"/>
<circle id="Ellipse_14" data-name="Ellipse 14" cx="2" cy="2" r="2" transform="translate(180 265.962)" fill="#f2f2f2"/>
<circle id="Ellipse_15" data-name="Ellipse 15" cx="2" cy="2" r="2" transform="translate(612 96.962)" fill="#f2f2f2"/>
<circle id="Ellipse_16" data-name="Ellipse 16" cx="2" cy="2" r="2" transform="translate(736 192.962)" fill="#f2f2f2"/>
<circle id="Ellipse_17" data-name="Ellipse 17" cx="2" cy="2" r="2" transform="translate(858 344.962)" fill="#f2f2f2"/>
<path id="Path_89" data-name="Path 89" d="M306,121.222h-2.76v-2.76h-1.48v2.76H299V122.7h2.76v2.759h1.48V122.7H306Z" fill="#f2f2f2"/>
<path id="Path_90" data-name="Path 90" d="M848,424.222h-2.76v-2.76h-1.48v2.76H841V425.7h2.76v2.759h1.48V425.7H848Z" fill="#f2f2f2"/>
<path id="Path_91" data-name="Path 91" d="M1144,719.981c0,16.569-243.557,74-544,74s-544-57.431-544-74,243.557,14,544,14S1144,703.413,1144,719.981Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_92" data-name="Path 92" d="M1144,719.981c0,16.569-243.557,74-544,74s-544-57.431-544-74,243.557,14,544,14S1144,703.413,1144,719.981Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<ellipse id="Ellipse_18" data-name="Ellipse 18" cx="544" cy="30" rx="544" ry="30" transform="translate(0 583.962)" fill="#3f3d56"/>
<path id="Path_93" data-name="Path 93" d="M624,677.981c0,33.137-14.775,24-33,24s-33,9.137-33-24,33-96,33-96S624,644.844,624,677.981Z" transform="translate(-56 -106.019)" fill="#ff6584"/>
<path id="Path_94" data-name="Path 94" d="M606,690.66c0,15.062-6.716,10.909-15,10.909s-15,4.153-15-10.909,15-43.636,15-43.636S606,675.6,606,690.66Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<rect id="Rectangle_97" data-name="Rectangle 97" width="92" height="18" rx="9" transform="translate(489 604.962)" fill="#2f2e41"/>
<rect id="Rectangle_98" data-name="Rectangle 98" width="92" height="18" rx="9" transform="translate(489 586.962)" fill="#2f2e41"/>
<path id="Path_95" data-name="Path 95" d="M193,596.547c0,55.343,34.719,100.126,77.626,100.126" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_96" data-name="Path 96" d="M270.626,696.673c0-55.965,38.745-101.251,86.626-101.251" transform="translate(-56 -106.019)" fill="#6c63ff"/>
<path id="Path_97" data-name="Path 97" d="M221.125,601.564c0,52.57,22.14,95.109,49.5,95.109" transform="translate(-56 -106.019)" fill="#6c63ff"/>
<path id="Path_98" data-name="Path 98" d="M270.626,696.673c0-71.511,44.783-129.377,100.126-129.377" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_99" data-name="Path 99" d="M254.3,697.379s11.009-.339,14.326-2.7,16.934-5.183,17.757-1.395,16.544,18.844,4.115,18.945-28.879-1.936-32.19-3.953S254.3,697.379,254.3,697.379Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_100" data-name="Path 100" d="M290.716,710.909c-12.429.1-28.879-1.936-32.19-3.953-2.522-1.536-3.527-7.048-3.863-9.591l-.368.014s.7,8.879,4.009,10.9,19.761,4.053,32.19,3.953c3.588-.029,4.827-1.305,4.759-3.2C294.755,710.174,293.386,710.887,290.716,710.909Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_101" data-name="Path 101" d="M777.429,633.081c0,38.029,23.857,68.8,53.341,68.8" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_102" data-name="Path 102" d="M830.769,701.882c0-38.456,26.623-69.575,59.525-69.575" transform="translate(-56 -106.019)" fill="#6c63ff"/>
<path id="Path_103" data-name="Path 103" d="M796.755,636.528c0,36.124,15.213,65.354,34.014,65.354" transform="translate(-56 -106.019)" fill="#6c63ff"/>
<path id="Path_104" data-name="Path 104" d="M830.769,701.882c0-49.139,30.773-88.9,68.8-88.9" transform="translate(-56 -106.019)" fill="#3f3d56"/>
<path id="Path_105" data-name="Path 105" d="M819.548,702.367s7.565-.233,9.844-1.856,11.636-3.562,12.2-.958,11.368,12.949,2.828,13.018-19.844-1.33-22.119-2.716S819.548,702.367,819.548,702.367Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_106" data-name="Path 106" d="M844.574,711.664c-8.54.069-19.844-1.33-22.119-2.716-1.733-1.056-2.423-4.843-2.654-6.59l-.253.01s.479,6.1,2.755,7.487,13.579,2.785,22.119,2.716c2.465-.02,3.317-.9,3.27-2.2C847.349,711.159,846.409,711.649,844.574,711.664Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_107" data-name="Path 107" d="M949.813,724.718s11.36-1.729,14.5-4.591,16.89-7.488,18.217-3.667,19.494,17.447,6.633,19.107-30.153,1.609-33.835-.065S949.813,724.718,949.813,724.718Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_108" data-name="Path 108" d="M989.228,734.173c-12.86,1.659-30.153,1.609-33.835-.065-2.8-1.275-4.535-6.858-5.2-9.45l-.379.061s1.833,9.109,5.516,10.783,20.975,1.725,33.835.065c3.712-.479,4.836-1.956,4.529-3.906C993.319,732.907,991.991,733.817,989.228,734.173Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_109" data-name="Path 109" d="M670.26,723.9s9.587-1.459,12.237-3.875,14.255-6.32,15.374-3.095,16.452,14.725,5.6,16.125-25.448,1.358-28.555-.055S670.26,723.9,670.26,723.9Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_110" data-name="Path 110" d="M703.524,731.875c-10.853,1.4-25.448,1.358-28.555-.055-2.367-1.076-3.827-5.788-4.39-7.976l-.32.051s1.547,7.687,4.655,9.1,17.7,1.456,28.555.055c3.133-.4,4.081-1.651,3.822-3.3C706.977,730.807,705.856,731.575,703.524,731.875Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_111" data-name="Path 111" d="M178.389,719.109s7.463-1.136,9.527-3.016,11.1-4.92,11.969-2.409,12.808,11.463,4.358,12.553-19.811,1.057-22.23-.043S178.389,719.109,178.389,719.109Z" transform="translate(-56 -106.019)" fill="#a8a8a8"/>
<path id="Path_112" data-name="Path 112" d="M204.285,725.321c-8.449,1.09-19.811,1.057-22.23-.043-1.842-.838-2.979-4.506-3.417-6.209l-.249.04s1.2,5.984,3.624,7.085,13.781,1.133,22.23.043c2.439-.315,3.177-1.285,2.976-2.566C206.973,724.489,206.1,725.087,204.285,725.321Z" transform="translate(-56 -106.019)" opacity="0.2"/>
<path id="Path_113" data-name="Path 113" d="M439.7,707.337c0,30.22-42.124,20.873-93.7,20.873s-93.074,9.347-93.074-20.873,42.118-36.793,93.694-36.793S439.7,677.117,439.7,707.337Z" transform="translate(-56 -106.019)" opacity="0.1"/>
<path id="Path_114" data-name="Path 114" d="M439.7,699.9c0,30.22-42.124,20.873-93.7,20.873s-93.074,9.347-93.074-20.873S295.04,663.1,346.616,663.1,439.7,669.676,439.7,699.9Z" transform="translate(-56 -106.019)" fill="#3f3d56"/>
</g>
<g id="docusaurus_keytar" transform="translate(312.271 493.733)">
<path id="Path_40" data-name="Path 40" d="M99,52h91.791V89.153H99Z" transform="translate(5.904 -14.001)" fill="#fff" fill-rule="evenodd"/>
<path id="Path_41" data-name="Path 41" d="M24.855,163.927A21.828,21.828,0,0,1,5.947,153a21.829,21.829,0,0,0,18.908,32.782H46.71V163.927Z" transform="translate(-3 -4.634)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_42" data-name="Path 42" d="M121.861,61.1l76.514-4.782V45.39A21.854,21.854,0,0,0,176.52,23.535H78.173L75.441,18.8a3.154,3.154,0,0,0-5.464,0l-2.732,4.732L64.513,18.8a3.154,3.154,0,0,0-5.464,0l-2.732,4.732L53.586,18.8a3.154,3.154,0,0,0-5.464,0L45.39,23.535c-.024,0-.046,0-.071,0l-4.526-4.525a3.153,3.153,0,0,0-5.276,1.414l-1.5,5.577-5.674-1.521a3.154,3.154,0,0,0-3.863,3.864L26,34.023l-5.575,1.494a3.155,3.155,0,0,0-1.416,5.278l4.526,4.526c0,.023,0,.046,0,.07L18.8,48.122a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,59.05a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,69.977a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,80.9a3.154,3.154,0,0,0,0,5.464L23.535,89.1,18.8,91.832a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,102.76a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,113.687a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,124.615a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,135.542a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,146.469a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,157.4a3.154,3.154,0,0,0,0,5.464l4.732,2.732L18.8,168.324a3.154,3.154,0,0,0,0,5.464l4.732,2.732A21.854,21.854,0,0,0,45.39,198.375H176.52a21.854,21.854,0,0,0,21.855-21.855V89.1l-76.514-4.782a11.632,11.632,0,0,1,0-23.219" transform="translate(-1.681 -17.226)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_43" data-name="Path 43" d="M143,186.71h32.782V143H143Z" transform="translate(9.984 -5.561)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_44" data-name="Path 44" d="M196.71,159.855a5.438,5.438,0,0,0-.7.07c-.042-.164-.081-.329-.127-.493a5.457,5.457,0,1,0-5.4-9.372q-.181-.185-.366-.367a5.454,5.454,0,1,0-9.384-5.4c-.162-.046-.325-.084-.486-.126a5.467,5.467,0,1,0-10.788,0c-.162.042-.325.08-.486.126a5.457,5.457,0,1,0-9.384,5.4,21.843,21.843,0,1,0,36.421,21.02,5.452,5.452,0,1,0,.7-10.858" transform="translate(10.912 -6.025)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_45" data-name="Path 45" d="M153,124.855h32.782V103H153Z" transform="translate(10.912 -9.271)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_46" data-name="Path 46" d="M194.855,116.765a2.732,2.732,0,1,0,0-5.464,2.811,2.811,0,0,0-.349.035c-.022-.082-.04-.164-.063-.246a2.733,2.733,0,0,0-1.052-5.253,2.7,2.7,0,0,0-1.648.566q-.09-.093-.184-.184a2.7,2.7,0,0,0,.553-1.633,2.732,2.732,0,0,0-5.245-1.07,10.928,10.928,0,1,0,0,21.031,2.732,2.732,0,0,0,5.245-1.07,2.7,2.7,0,0,0-.553-1.633q.093-.09.184-.184a2.7,2.7,0,0,0,1.648.566,2.732,2.732,0,0,0,1.052-5.253c.023-.081.042-.164.063-.246a2.814,2.814,0,0,0,.349.035" transform="translate(12.767 -9.377)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_47" data-name="Path 47" d="M65.087,56.891a2.732,2.732,0,0,1-2.732-2.732,8.2,8.2,0,0,0-16.391,0,2.732,2.732,0,0,1-5.464,0,13.659,13.659,0,0,1,27.319,0,2.732,2.732,0,0,1-2.732,2.732" transform="translate(0.478 -15.068)" fill-rule="evenodd"/>
<path id="Path_48" data-name="Path 48" d="M103,191.347h65.565a21.854,21.854,0,0,0,21.855-21.855V93H124.855A21.854,21.854,0,0,0,103,114.855Z" transform="translate(6.275 -10.199)" fill="#ffff50" fill-rule="evenodd"/>
<path id="Path_49" data-name="Path 49" d="M173.216,129.787H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0,21.855H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186m0,21.855H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0-54.434H118.535a1.093,1.093,0,1,1,0-2.185h54.681a1.093,1.093,0,0,1,0,2.185m0,21.652H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186m0,21.855H118.535a1.093,1.093,0,1,1,0-2.186h54.681a1.093,1.093,0,0,1,0,2.186M189.585,61.611c-.013,0-.024-.007-.037-.005-3.377.115-4.974,3.492-6.384,6.472-1.471,3.114-2.608,5.139-4.473,5.078-2.064-.074-3.244-2.406-4.494-4.874-1.436-2.835-3.075-6.049-6.516-5.929-3.329.114-4.932,3.053-6.346,5.646-1.5,2.762-2.529,4.442-4.5,4.364-2.106-.076-3.225-1.972-4.52-4.167-1.444-2.443-3.112-5.191-6.487-5.1-3.272.113-4.879,2.606-6.3,4.808-1.5,2.328-2.552,3.746-4.551,3.662-2.156-.076-3.27-1.65-4.558-3.472-1.447-2.047-3.077-4.363-6.442-4.251-3.2.109-4.807,2.153-6.224,3.954-1.346,1.709-2.4,3.062-4.621,2.977a1.093,1.093,0,0,0-.079,2.186c3.3.11,4.967-1.967,6.417-3.81,1.286-1.635,2.4-3.045,4.582-3.12,2.1-.09,3.091,1.218,4.584,3.327,1.417,2,3.026,4.277,6.263,4.394,3.391.114,5.022-2.42,6.467-4.663,1.292-2,2.406-3.734,4.535-3.807,1.959-.073,3.026,1.475,4.529,4.022,1.417,2.4,3.023,5.121,6.324,5.241,3.415.118,5.064-2.863,6.5-5.5,1.245-2.282,2.419-4.437,4.5-4.509,1.959-.046,2.981,1.743,4.492,4.732,1.412,2.79,3.013,5.95,6.365,6.071l.185,0c3.348,0,4.937-3.36,6.343-6.331,1.245-2.634,2.423-5.114,4.444-5.216Z" transform="translate(7.109 -13.11)" fill-rule="evenodd"/>
<path id="Path_50" data-name="Path 50" d="M83,186.71h43.71V143H83Z" transform="translate(4.42 -5.561)" fill="#3ecc5f" fill-rule="evenodd"/>
<g id="Group_8" data-name="Group 8" transform="matrix(0.966, -0.259, 0.259, 0.966, 109.327, 91.085)">
<rect id="Rectangle_3" data-name="Rectangle 3" width="92.361" height="36.462" rx="2" transform="translate(0 0)" fill="#d8d8d8"/>
<g id="Group_2" data-name="Group 2" transform="translate(1.531 23.03)">
<rect id="Rectangle_4" data-name="Rectangle 4" width="5.336" height="5.336" rx="1" transform="translate(16.797 0)" fill="#4a4a4a"/>
<rect id="Rectangle_5" data-name="Rectangle 5" width="5.336" height="5.336" rx="1" transform="translate(23.12 0)" fill="#4a4a4a"/>
<rect id="Rectangle_6" data-name="Rectangle 6" width="5.336" height="5.336" rx="1" transform="translate(29.444 0)" fill="#4a4a4a"/>
<rect id="Rectangle_7" data-name="Rectangle 7" width="5.336" height="5.336" rx="1" transform="translate(35.768 0)" fill="#4a4a4a"/>
<rect id="Rectangle_8" data-name="Rectangle 8" width="5.336" height="5.336" rx="1" transform="translate(42.091 0)" fill="#4a4a4a"/>
<rect id="Rectangle_9" data-name="Rectangle 9" width="5.336" height="5.336" rx="1" transform="translate(48.415 0)" fill="#4a4a4a"/>
<rect id="Rectangle_10" data-name="Rectangle 10" width="5.336" height="5.336" rx="1" transform="translate(54.739 0)" fill="#4a4a4a"/>
<rect id="Rectangle_11" data-name="Rectangle 11" width="5.336" height="5.336" rx="1" transform="translate(61.063 0)" fill="#4a4a4a"/>
<rect id="Rectangle_12" data-name="Rectangle 12" width="5.336" height="5.336" rx="1" transform="translate(67.386 0)" fill="#4a4a4a"/>
<path id="Path_51" data-name="Path 51" d="M1.093,0H14.518a1.093,1.093,0,0,1,1.093,1.093V4.243a1.093,1.093,0,0,1-1.093,1.093H1.093A1.093,1.093,0,0,1,0,4.243V1.093A1.093,1.093,0,0,1,1.093,0ZM75,0H88.426a1.093,1.093,0,0,1,1.093,1.093V4.243a1.093,1.093,0,0,1-1.093,1.093H75a1.093,1.093,0,0,1-1.093-1.093V1.093A1.093,1.093,0,0,1,75,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
</g>
<g id="Group_3" data-name="Group 3" transform="translate(1.531 10.261)">
<path id="Path_52" data-name="Path 52" d="M1.093,0H6.218A1.093,1.093,0,0,1,7.31,1.093V4.242A1.093,1.093,0,0,1,6.218,5.335H1.093A1.093,1.093,0,0,1,0,4.242V1.093A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_13" data-name="Rectangle 13" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
<rect id="Rectangle_14" data-name="Rectangle 14" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
<rect id="Rectangle_15" data-name="Rectangle 15" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
<rect id="Rectangle_16" data-name="Rectangle 16" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
<rect id="Rectangle_17" data-name="Rectangle 17" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
<rect id="Rectangle_18" data-name="Rectangle 18" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
<rect id="Rectangle_19" data-name="Rectangle 19" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
<rect id="Rectangle_20" data-name="Rectangle 20" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
<rect id="Rectangle_21" data-name="Rectangle 21" width="5.336" height="5.336" rx="1" transform="translate(58.888 0)" fill="#4a4a4a"/>
<rect id="Rectangle_22" data-name="Rectangle 22" width="5.336" height="5.336" rx="1" transform="translate(65.212 0)" fill="#4a4a4a"/>
<rect id="Rectangle_23" data-name="Rectangle 23" width="5.336" height="5.336" rx="1" transform="translate(71.536 0)" fill="#4a4a4a"/>
<rect id="Rectangle_24" data-name="Rectangle 24" width="5.336" height="5.336" rx="1" transform="translate(77.859 0)" fill="#4a4a4a"/>
<rect id="Rectangle_25" data-name="Rectangle 25" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
</g>
<g id="Group_4" data-name="Group 4" transform="translate(91.05 9.546) rotate(180)">
<path id="Path_53" data-name="Path 53" d="M1.093,0H6.219A1.093,1.093,0,0,1,7.312,1.093v3.15A1.093,1.093,0,0,1,6.219,5.336H1.093A1.093,1.093,0,0,1,0,4.243V1.093A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_26" data-name="Rectangle 26" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
<rect id="Rectangle_27" data-name="Rectangle 27" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
<rect id="Rectangle_28" data-name="Rectangle 28" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
<rect id="Rectangle_29" data-name="Rectangle 29" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
<rect id="Rectangle_30" data-name="Rectangle 30" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
<rect id="Rectangle_31" data-name="Rectangle 31" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
<rect id="Rectangle_32" data-name="Rectangle 32" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
<rect id="Rectangle_33" data-name="Rectangle 33" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
<rect id="Rectangle_34" data-name="Rectangle 34" width="5.336" height="5.336" rx="1" transform="translate(58.889 0)" fill="#4a4a4a"/>
<rect id="Rectangle_35" data-name="Rectangle 35" width="5.336" height="5.336" rx="1" transform="translate(65.213 0)" fill="#4a4a4a"/>
<rect id="Rectangle_36" data-name="Rectangle 36" width="5.336" height="5.336" rx="1" transform="translate(71.537 0)" fill="#4a4a4a"/>
<rect id="Rectangle_37" data-name="Rectangle 37" width="5.336" height="5.336" rx="1" transform="translate(77.86 0)" fill="#4a4a4a"/>
<rect id="Rectangle_38" data-name="Rectangle 38" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
<rect id="Rectangle_39" data-name="Rectangle 39" width="5.336" height="5.336" rx="1" transform="translate(8.299 0)" fill="#4a4a4a"/>
<rect id="Rectangle_40" data-name="Rectangle 40" width="5.336" height="5.336" rx="1" transform="translate(14.623 0)" fill="#4a4a4a"/>
<rect id="Rectangle_41" data-name="Rectangle 41" width="5.336" height="5.336" rx="1" transform="translate(20.947 0)" fill="#4a4a4a"/>
<rect id="Rectangle_42" data-name="Rectangle 42" width="5.336" height="5.336" rx="1" transform="translate(27.271 0)" fill="#4a4a4a"/>
<rect id="Rectangle_43" data-name="Rectangle 43" width="5.336" height="5.336" rx="1" transform="translate(33.594 0)" fill="#4a4a4a"/>
<rect id="Rectangle_44" data-name="Rectangle 44" width="5.336" height="5.336" rx="1" transform="translate(39.918 0)" fill="#4a4a4a"/>
<rect id="Rectangle_45" data-name="Rectangle 45" width="5.336" height="5.336" rx="1" transform="translate(46.242 0)" fill="#4a4a4a"/>
<rect id="Rectangle_46" data-name="Rectangle 46" width="5.336" height="5.336" rx="1" transform="translate(52.565 0)" fill="#4a4a4a"/>
<rect id="Rectangle_47" data-name="Rectangle 47" width="5.336" height="5.336" rx="1" transform="translate(58.889 0)" fill="#4a4a4a"/>
<rect id="Rectangle_48" data-name="Rectangle 48" width="5.336" height="5.336" rx="1" transform="translate(65.213 0)" fill="#4a4a4a"/>
<rect id="Rectangle_49" data-name="Rectangle 49" width="5.336" height="5.336" rx="1" transform="translate(71.537 0)" fill="#4a4a4a"/>
<rect id="Rectangle_50" data-name="Rectangle 50" width="5.336" height="5.336" rx="1" transform="translate(77.86 0)" fill="#4a4a4a"/>
<rect id="Rectangle_51" data-name="Rectangle 51" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
</g>
<g id="Group_6" data-name="Group 6" transform="translate(1.531 16.584)">
<path id="Path_54" data-name="Path 54" d="M1.093,0h7.3A1.093,1.093,0,0,1,9.485,1.093v3.15A1.093,1.093,0,0,1,8.392,5.336h-7.3A1.093,1.093,0,0,1,0,4.243V1.094A1.093,1.093,0,0,1,1.093,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<g id="Group_5" data-name="Group 5" transform="translate(10.671 0)">
<rect id="Rectangle_52" data-name="Rectangle 52" width="5.336" height="5.336" rx="1" fill="#4a4a4a"/>
<rect id="Rectangle_53" data-name="Rectangle 53" width="5.336" height="5.336" rx="1" transform="translate(6.324 0)" fill="#4a4a4a"/>
<rect id="Rectangle_54" data-name="Rectangle 54" width="5.336" height="5.336" rx="1" transform="translate(12.647 0)" fill="#4a4a4a"/>
<rect id="Rectangle_55" data-name="Rectangle 55" width="5.336" height="5.336" rx="1" transform="translate(18.971 0)" fill="#4a4a4a"/>
<rect id="Rectangle_56" data-name="Rectangle 56" width="5.336" height="5.336" rx="1" transform="translate(25.295 0)" fill="#4a4a4a"/>
<rect id="Rectangle_57" data-name="Rectangle 57" width="5.336" height="5.336" rx="1" transform="translate(31.619 0)" fill="#4a4a4a"/>
<rect id="Rectangle_58" data-name="Rectangle 58" width="5.336" height="5.336" rx="1" transform="translate(37.942 0)" fill="#4a4a4a"/>
<rect id="Rectangle_59" data-name="Rectangle 59" width="5.336" height="5.336" rx="1" transform="translate(44.265 0)" fill="#4a4a4a"/>
<rect id="Rectangle_60" data-name="Rectangle 60" width="5.336" height="5.336" rx="1" transform="translate(50.589 0)" fill="#4a4a4a"/>
<rect id="Rectangle_61" data-name="Rectangle 61" width="5.336" height="5.336" rx="1" transform="translate(56.912 0)" fill="#4a4a4a"/>
<rect id="Rectangle_62" data-name="Rectangle 62" width="5.336" height="5.336" rx="1" transform="translate(63.236 0)" fill="#4a4a4a"/>
</g>
<path id="Path_55" data-name="Path 55" d="M1.094,0H8A1.093,1.093,0,0,1,9.091,1.093v3.15A1.093,1.093,0,0,1,8,5.336H1.093A1.093,1.093,0,0,1,0,4.243V1.094A1.093,1.093,0,0,1,1.093,0Z" transform="translate(80.428 0)" fill="#4a4a4a" fill-rule="evenodd"/>
</g>
<g id="Group_7" data-name="Group 7" transform="translate(1.531 29.627)">
<rect id="Rectangle_63" data-name="Rectangle 63" width="5.336" height="5.336" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
<rect id="Rectangle_64" data-name="Rectangle 64" width="5.336" height="5.336" rx="1" transform="translate(6.324 0)" fill="#4a4a4a"/>
<rect id="Rectangle_65" data-name="Rectangle 65" width="5.336" height="5.336" rx="1" transform="translate(12.647 0)" fill="#4a4a4a"/>
<rect id="Rectangle_66" data-name="Rectangle 66" width="5.336" height="5.336" rx="1" transform="translate(18.971 0)" fill="#4a4a4a"/>
<path id="Path_56" data-name="Path 56" d="M1.093,0H31.515a1.093,1.093,0,0,1,1.093,1.093V4.244a1.093,1.093,0,0,1-1.093,1.093H1.093A1.093,1.093,0,0,1,0,4.244V1.093A1.093,1.093,0,0,1,1.093,0ZM34.687,0h3.942a1.093,1.093,0,0,1,1.093,1.093V4.244a1.093,1.093,0,0,1-1.093,1.093H34.687a1.093,1.093,0,0,1-1.093-1.093V1.093A1.093,1.093,0,0,1,34.687,0Z" transform="translate(25.294 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_67" data-name="Rectangle 67" width="5.336" height="5.336" rx="1" transform="translate(66.003 0)" fill="#4a4a4a"/>
<rect id="Rectangle_68" data-name="Rectangle 68" width="5.336" height="5.336" rx="1" transform="translate(72.327 0)" fill="#4a4a4a"/>
<rect id="Rectangle_69" data-name="Rectangle 69" width="5.336" height="5.336" rx="1" transform="translate(84.183 0)" fill="#4a4a4a"/>
<path id="Path_57" data-name="Path 57" d="M5.336,0V1.18A1.093,1.093,0,0,1,4.243,2.273H1.093A1.093,1.093,0,0,1,0,1.18V0Z" transform="translate(83.59 2.273) rotate(180)" fill="#4a4a4a"/>
<path id="Path_58" data-name="Path 58" d="M5.336,0V1.18A1.093,1.093,0,0,1,4.243,2.273H1.093A1.093,1.093,0,0,1,0,1.18V0Z" transform="translate(78.255 3.063)" fill="#4a4a4a"/>
</g>
<rect id="Rectangle_70" data-name="Rectangle 70" width="88.927" height="2.371" rx="1.085" transform="translate(1.925 1.17)" fill="#4a4a4a"/>
<rect id="Rectangle_71" data-name="Rectangle 71" width="4.986" height="1.581" rx="0.723" transform="translate(4.1 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_72" data-name="Rectangle 72" width="4.986" height="1.581" rx="0.723" transform="translate(10.923 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_73" data-name="Rectangle 73" width="4.986" height="1.581" rx="0.723" transform="translate(16.173 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_74" data-name="Rectangle 74" width="4.986" height="1.581" rx="0.723" transform="translate(21.421 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_75" data-name="Rectangle 75" width="4.986" height="1.581" rx="0.723" transform="translate(26.671 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_76" data-name="Rectangle 76" width="4.986" height="1.581" rx="0.723" transform="translate(33.232 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_77" data-name="Rectangle 77" width="4.986" height="1.581" rx="0.723" transform="translate(38.48 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_78" data-name="Rectangle 78" width="4.986" height="1.581" rx="0.723" transform="translate(43.73 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_79" data-name="Rectangle 79" width="4.986" height="1.581" rx="0.723" transform="translate(48.978 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_80" data-name="Rectangle 80" width="4.986" height="1.581" rx="0.723" transform="translate(55.54 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_81" data-name="Rectangle 81" width="4.986" height="1.581" rx="0.723" transform="translate(60.788 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_82" data-name="Rectangle 82" width="4.986" height="1.581" rx="0.723" transform="translate(66.038 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_83" data-name="Rectangle 83" width="4.986" height="1.581" rx="0.723" transform="translate(72.599 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_84" data-name="Rectangle 84" width="4.986" height="1.581" rx="0.723" transform="translate(77.847 1.566)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_85" data-name="Rectangle 85" width="4.986" height="1.581" rx="0.723" transform="translate(83.097 1.566)" fill="#d8d8d8" opacity="0.136"/>
</g>
<path id="Path_59" data-name="Path 59" d="M146.71,159.855a5.439,5.439,0,0,0-.7.07c-.042-.164-.081-.329-.127-.493a5.457,5.457,0,1,0-5.4-9.372q-.181-.185-.366-.367a5.454,5.454,0,1,0-9.384-5.4c-.162-.046-.325-.084-.486-.126a5.467,5.467,0,1,0-10.788,0c-.162.042-.325.08-.486.126a5.457,5.457,0,1,0-9.384,5.4,21.843,21.843,0,1,0,36.421,21.02,5.452,5.452,0,1,0,.7-10.858" transform="translate(6.275 -6.025)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_60" data-name="Path 60" d="M83,124.855h43.71V103H83Z" transform="translate(4.42 -9.271)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_61" data-name="Path 61" d="M134.855,116.765a2.732,2.732,0,1,0,0-5.464,2.811,2.811,0,0,0-.349.035c-.022-.082-.04-.164-.063-.246a2.733,2.733,0,0,0-1.052-5.253,2.7,2.7,0,0,0-1.648.566q-.09-.093-.184-.184a2.7,2.7,0,0,0,.553-1.633,2.732,2.732,0,0,0-5.245-1.07,10.928,10.928,0,1,0,0,21.031,2.732,2.732,0,0,0,5.245-1.07,2.7,2.7,0,0,0-.553-1.633q.093-.09.184-.184a2.7,2.7,0,0,0,1.648.566,2.732,2.732,0,0,0,1.052-5.253c.023-.081.042-.164.063-.246a2.811,2.811,0,0,0,.349.035" transform="translate(7.202 -9.377)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_62" data-name="Path 62" d="M143.232,42.33a2.967,2.967,0,0,1-.535-.055,2.754,2.754,0,0,1-.514-.153,2.838,2.838,0,0,1-.471-.251,4.139,4.139,0,0,1-.415-.339,3.2,3.2,0,0,1-.338-.415A2.7,2.7,0,0,1,140.5,39.6a2.968,2.968,0,0,1,.055-.535,3.152,3.152,0,0,1,.152-.514,2.874,2.874,0,0,1,.252-.47,2.633,2.633,0,0,1,.753-.754,2.837,2.837,0,0,1,.471-.251,2.753,2.753,0,0,1,.514-.153,2.527,2.527,0,0,1,1.071,0,2.654,2.654,0,0,1,.983.4,4.139,4.139,0,0,1,.415.339,4.019,4.019,0,0,1,.339.415,2.786,2.786,0,0,1,.251.47,2.864,2.864,0,0,1,.208,1.049,2.77,2.77,0,0,1-.8,1.934,4.139,4.139,0,0,1-.415.339,2.722,2.722,0,0,1-1.519.459m21.855-1.366a2.789,2.789,0,0,1-1.935-.8,4.162,4.162,0,0,1-.338-.415,2.7,2.7,0,0,1-.459-1.519,2.789,2.789,0,0,1,.8-1.934,4.139,4.139,0,0,1,.415-.339,2.838,2.838,0,0,1,.471-.251,2.752,2.752,0,0,1,.514-.153,2.527,2.527,0,0,1,1.071,0,2.654,2.654,0,0,1,.983.4,4.139,4.139,0,0,1,.415.339,2.79,2.79,0,0,1,.8,1.934,3.069,3.069,0,0,1-.055.535,2.779,2.779,0,0,1-.153.514,3.885,3.885,0,0,1-.251.47,4.02,4.02,0,0,1-.339.415,4.138,4.138,0,0,1-.415.339,2.722,2.722,0,0,1-1.519.459" transform="translate(9.753 -15.532)" fill-rule="evenodd"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -0,0 +1,170 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1041.277" height="554.141" viewBox="0 0 1041.277 554.141">
<title>Powered by React</title>
<g id="Group_24" data-name="Group 24" transform="translate(-440 -263)">
<g id="Group_23" data-name="Group 23" transform="translate(439.989 262.965)">
<path id="Path_299" data-name="Path 299" d="M1040.82,611.12q-1.74,3.75-3.47,7.4-2.7,5.67-5.33,11.12c-.78,1.61-1.56,3.19-2.32,4.77-8.6,17.57-16.63,33.11-23.45,45.89A73.21,73.21,0,0,1,942.44,719l-151.65,1.65h-1.6l-13,.14-11.12.12-34.1.37h-1.38l-17.36.19h-.53l-107,1.16-95.51,1-11.11.12-69,.75H429l-44.75.48h-.48l-141.5,1.53-42.33.46a87.991,87.991,0,0,1-10.79-.54h0c-1.22-.14-2.44-.3-3.65-.49a87.38,87.38,0,0,1-51.29-27.54C116,678.37,102.75,655,93.85,629.64q-1.93-5.49-3.6-11.12C59.44,514.37,97,380,164.6,290.08q4.25-5.64,8.64-11l.07-.08c20.79-25.52,44.1-46.84,68.93-62,44-26.91,92.75-34.49,140.7-11.9,40.57,19.12,78.45,28.11,115.17,30.55,3.71.24,7.42.42,11.11.53,84.23,2.65,163.17-27.7,255.87-47.29,3.69-.78,7.39-1.55,11.12-2.28,66.13-13.16,139.49-20.1,226.73-5.51a189.089,189.089,0,0,1,26.76,6.4q5.77,1.86,11.12,4c41.64,16.94,64.35,48.24,74,87.46q1.37,5.46,2.37,11.11C1134.3,384.41,1084.19,518.23,1040.82,611.12Z" transform="translate(-79.34 -172.91)" fill="#f2f2f2"/>
<path id="Path_300" data-name="Path 300" d="M576.36,618.52a95.21,95.21,0,0,1-1.87,11.12h93.7V618.52Zm-78.25,62.81,11.11-.09V653.77c-3.81-.17-7.52-.34-11.11-.52ZM265.19,618.52v11.12h198.5V618.52ZM1114.87,279h-74V191.51q-5.35-2.17-11.12-4V279H776.21V186.58c-3.73.73-7.43,1.5-11.12,2.28V279H509.22V236.15c-3.69-.11-7.4-.29-11.11-.53V279H242.24V217c-24.83,15.16-48.14,36.48-68.93,62h-.07v.08q-4.4,5.4-8.64,11h8.64V618.52h-83q1.66,5.63,3.6,11.12h79.39v93.62a87,87,0,0,0,12.2,2.79c1.21.19,2.43.35,3.65.49h0a87.991,87.991,0,0,0,10.79.54l42.33-.46v-97H498.11v94.21l11.11-.12V629.64H765.09V721l11.12-.12V629.64H1029.7v4.77c.76-1.58,1.54-3.16,2.32-4.77q2.63-5.45,5.33-11.12,1.73-3.64,3.47-7.4v-321h76.42Q1116.23,284.43,1114.87,279ZM242.24,618.52V290.08H498.11V618.52Zm267,0V290.08H765.09V618.52Zm520.48,0H776.21V290.08H1029.7Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_301" data-name="Path 301" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l46.65-28,93.6-.78,2-.01.66-.01,2-.03,44.94-.37,2.01-.01.64-.01,2-.01L315,509.3l.38-.01,35.55-.3h.29l277.4-2.34,6.79-.05h.68l5.18-.05,37.65-.31,2-.03,1.85-.02h.96l11.71-.09,2.32-.03,3.11-.02,9.75-.09,15.47-.13,2-.02,3.48-.02h.65l74.71-.64Z" fill="#65617d"/>
<path id="Path_302" data-name="Path 302" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l46.65-28,93.6-.78,2-.01.66-.01,2-.03,44.94-.37,2.01-.01.64-.01,2-.01L315,509.3l.38-.01,35.55-.3h.29l277.4-2.34,6.79-.05h.68l5.18-.05,37.65-.31,2-.03,1.85-.02h.96l11.71-.09,2.32-.03,3.11-.02,9.75-.09,15.47-.13,2-.02,3.48-.02h.65l74.71-.64Z" opacity="0.2"/>
<path id="Path_303" data-name="Path 303" d="M375.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
<path id="Path_304" data-name="Path 304" d="M375.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_305" data-name="Path 305" d="M377.44,656.57v24.49a6.13,6.13,0,0,1-3.5,5.54,6,6,0,0,1-2.5.6l-34.9.74a6,6,0,0,1-2.7-.57,6.12,6.12,0,0,1-3.57-5.57V656.57Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
<rect id="Rectangle_137" data-name="Rectangle 137" width="47.17" height="31.5" transform="translate(680.92 483.65)" fill="#3f3d56"/>
<rect id="Rectangle_138" data-name="Rectangle 138" width="47.17" height="31.5" transform="translate(680.92 483.65)" opacity="0.1"/>
<rect id="Rectangle_139" data-name="Rectangle 139" width="47.17" height="31.5" transform="translate(678.92 483.65)" fill="#3f3d56"/>
<path id="Path_306" data-name="Path 306" d="M298.09,483.65v4.97l-47.17,1.26v-6.23Z" opacity="0.1"/>
<path id="Path_307" data-name="Path 307" d="M460.69,485.27v168.2a4,4,0,0,1-3.85,3.95l-191.65,5.1h-.05a4,4,0,0,1-3.95-3.95V485.27a4,4,0,0,1,3.95-3.95h191.6a4,4,0,0,1,3.95,3.95Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
<path id="Path_308" data-name="Path 308" d="M265.19,481.32v181.2h-.05a4,4,0,0,1-3.95-3.95V485.27a4,4,0,0,1,3.95-3.95Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_309" data-name="Path 309" d="M194.59,319.15h177.5V467.4l-177.5,4Z" fill="#39374d"/>
<path id="Path_310" data-name="Path 310" d="M726.09,483.65v6.41l-47.17-1.26v-5.15Z" opacity="0.1"/>
<path id="Path_311" data-name="Path 311" d="M867.69,485.27v173.3a4,4,0,0,1-4,3.95h0L672,657.42a4,4,0,0,1-3.85-3.95V485.27a4,4,0,0,1,3.95-3.95H863.7a4,4,0,0,1,3.99,3.95Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
<path id="Path_312" data-name="Path 312" d="M867.69,485.27v173.3a4,4,0,0,1-4,3.95h0V481.32h0a4,4,0,0,1,4,3.95Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_313" data-name="Path 313" d="M775.59,319.15H598.09V467.4l177.5,4Z" fill="#39374d"/>
<path id="Path_314" data-name="Path 314" d="M663.19,485.27v168.2a4,4,0,0,1-3.85,3.95l-191.65,5.1h0a4,4,0,0,1-4-3.95V485.27a4,4,0,0,1,3.95-3.95h191.6A4,4,0,0,1,663.19,485.27Z" transform="translate(-79.34 -172.91)" fill="#65617d"/>
<path id="Path_315" data-name="Path 315" d="M397.09,319.15h177.5V467.4l-177.5,4Z" fill="#4267b2"/>
<path id="Path_316" data-name="Path 316" d="M863.09,533.65v13l-151.92,1.4-1.62.03-57.74.53-1.38.02-17.55.15h-.52l-106.98.99L349.77,551.4h-.15l-44.65.42-.48.01-198.4,1.82v-15l202.51-1.33h.48l40.99-.28h.19l283.08-1.87h.29l.17-.01h.47l4.79-.03h1.46l74.49-.5,4.4-.02.98-.01Z" opacity="0.1"/>
<circle id="Ellipse_111" data-name="Ellipse 111" cx="51.33" cy="51.33" r="51.33" transform="translate(435.93 246.82)" fill="#fbbebe"/>
<path id="Path_317" data-name="Path 317" d="M617.94,550.07s-99.5,12-90,0c3.44-4.34,4.39-17.2,4.2-31.85-.06-4.45-.22-9.06-.45-13.65-1.1-22-3.75-43.5-3.75-43.5s87-41,77-8.5c-4,13.13-2.69,31.57.35,48.88.89,5.05,1.92,10,3,14.7a344.66,344.66,0,0,0,9.65,33.92Z" transform="translate(-79.34 -172.91)" fill="#fbbebe"/>
<path id="Path_318" data-name="Path 318" d="M585.47,546c11.51-2.13,23.7-6,34.53-1.54,2.85,1.17,5.47,2.88,8.39,3.86s6.12,1.22,9.16,1.91c10.68,2.42,19.34,10.55,24.9,20s8.44,20.14,11.26,30.72l6.9,25.83c6,22.45,12,45.09,13.39,68.3a2437.506,2437.506,0,0,1-250.84,1.43c5.44-10.34,11-21.31,10.54-33s-7.19-23.22-4.76-34.74c1.55-7.34,6.57-13.39,9.64-20.22,8.75-19.52,1.94-45.79,17.32-60.65,6.92-6.68,17-9.21,26.63-8.89,12.28.41,24.85,4.24,37,6.11C555.09,547.48,569.79,548.88,585.47,546Z" transform="translate(-79.34 -172.91)" fill="#ff6584"/>
<path id="Path_319" data-name="Path 319" d="M716.37,657.17l-.1,1.43v.1l-.17,2.3-1.33,18.51-1.61,22.3-.46,6.28-1,13.44v.17l-107,1-175.59,1.9v.84h-.14v-1.12l.45-14.36.86-28.06.74-23.79.07-2.37a10.53,10.53,0,0,1,11.42-10.17c4.72.4,10.85.89,18.18,1.41l3,.22c42.33,2.94,120.56,6.74,199.5,2,1.66-.09,3.33-.19,5-.31,12.24-.77,24.47-1.76,36.58-3a10.53,10.53,0,0,1,11.6,11.23Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_320" data-name="Path 320" d="M429.08,725.44v-.84l175.62-1.91,107-1h.3v-.17l1-13.44.43-6,1.64-22.61,1.29-17.9v-.44a10.617,10.617,0,0,0-.11-2.47.3.3,0,0,0,0-.1,10.391,10.391,0,0,0-2-4.64,10.54,10.54,0,0,0-9.42-4c-12.11,1.24-24.34,2.23-36.58,3-1.67.12-3.34.22-5,.31-78.94,4.69-157.17.89-199.5-2l-3-.22c-7.33-.52-13.46-1-18.18-1.41a10.54,10.54,0,0,0-11.24,8.53,11,11,0,0,0-.18,1.64l-.68,22.16L429.54,710l-.44,14.36v1.12Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
<path id="Path_321" data-name="Path 321" d="M716.67,664.18l-1.23,15.33-1.83,22.85-.46,5.72-1,12.81-.06.64v.17h0l-.15,1.48.11-1.48h-.29l-107,1-175.65,1.9v-.28l.49-14.36,1-28.06.64-18.65A6.36,6.36,0,0,1,434.3,658a6.25,6.25,0,0,1,3.78-.9c2.1.17,4.68.37,7.69.59,4.89.36,10.92.78,17.94,1.22,13,.82,29.31,1.7,48,2.42,52,2,122.2,2.67,188.88-3.17,3-.26,6.1-.55,9.13-.84a6.26,6.26,0,0,1,3.48.66,5.159,5.159,0,0,1,.86.54,6.14,6.14,0,0,1,2,2.46,3.564,3.564,0,0,1,.25.61A6.279,6.279,0,0,1,716.67,664.18Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_322" data-name="Path 322" d="M377.44,677.87v3.19a6.13,6.13,0,0,1-3.5,5.54l-40.1.77a6.12,6.12,0,0,1-3.57-5.57v-3Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_323" data-name="Path 323" d="M298.59,515.57l-52.25,1V507.9l52.25-1Z" fill="#3f3d56"/>
<path id="Path_324" data-name="Path 324" d="M298.59,515.57l-52.25,1V507.9l52.25-1Z" opacity="0.1"/>
<path id="Path_325" data-name="Path 325" d="M300.59,515.57l-52.25,1V507.9l52.25-1Z" fill="#3f3d56"/>
<path id="Path_326" data-name="Path 326" d="M758.56,679.87v3.19a6.13,6.13,0,0,0,3.5,5.54l40.1.77a6.12,6.12,0,0,0,3.57-5.57v-3Z" transform="translate(-79.34 -172.91)" opacity="0.1"/>
<path id="Path_327" data-name="Path 327" d="M678.72,517.57l52.25,1V509.9l-52.25-1Z" opacity="0.1"/>
<path id="Path_328" data-name="Path 328" d="M676.72,517.57l52.25,1V509.9l-52.25-1Z" fill="#3f3d56"/>
<path id="Path_329" data-name="Path 329" d="M534.13,486.79c.08,7-3.16,13.6-5.91,20.07a163.491,163.491,0,0,0-12.66,74.71c.73,11,2.58,22,.73,32.9s-8.43,21.77-19,24.9c17.53,10.45,41.26,9.35,57.76-2.66,8.79-6.4,15.34-15.33,21.75-24.11a97.86,97.86,0,0,1-13.31,44.75A103.43,103.43,0,0,0,637,616.53c4.31-5.81,8.06-12.19,9.72-19.23,3.09-13-1.22-26.51-4.51-39.5a266.055,266.055,0,0,1-6.17-33c-.43-3.56-.78-7.22.1-10.7,1-4.07,3.67-7.51,5.64-11.22,5.6-10.54,5.73-23.3,2.86-34.88s-8.49-22.26-14.06-32.81c-4.46-8.46-9.3-17.31-17.46-22.28-5.1-3.1-11-4.39-16.88-5.64l-25.37-5.43c-5.55-1.19-11.26-2.38-16.87-1.51-9.47,1.48-16.14,8.32-22,15.34-4.59,5.46-15.81,15.71-16.6,22.86-.72,6.59,5.1,17.63,6.09,24.58,1.3,9,2.22,6,7.3,11.52C532,478.05,534.07,482,534.13,486.79Z" transform="translate(-79.34 -172.91)" fill="#3f3d56"/>
</g>
<g id="docusaurus_keytar" transform="translate(670.271 615.768)">
<path id="Path_40" data-name="Path 40" d="M99,52h43.635V69.662H99Z" transform="translate(-49.132 -33.936)" fill="#fff" fill-rule="evenodd"/>
<path id="Path_41" data-name="Path 41" d="M13.389,158.195A10.377,10.377,0,0,1,4.4,153a10.377,10.377,0,0,0,8.988,15.584H23.779V158.195Z" transform="translate(-3 -82.47)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_42" data-name="Path 42" d="M66.967,38.083l36.373-2.273V30.615A10.389,10.389,0,0,0,92.95,20.226H46.2l-1.3-2.249a1.5,1.5,0,0,0-2.6,0L41,20.226l-1.3-2.249a1.5,1.5,0,0,0-2.6,0l-1.3,2.249-1.3-2.249a1.5,1.5,0,0,0-2.6,0l-1.3,2.249-.034,0-2.152-2.151a1.5,1.5,0,0,0-2.508.672L25.21,21.4l-2.7-.723a1.5,1.5,0,0,0-1.836,1.837l.722,2.7-2.65.71a1.5,1.5,0,0,0-.673,2.509l2.152,2.152c0,.011,0,.022,0,.033l-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6L20.226,41l-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3-2.249,1.3a1.5,1.5,0,0,0,0,2.6l2.249,1.3A10.389,10.389,0,0,0,30.615,103.34H92.95A10.389,10.389,0,0,0,103.34,92.95V51.393L66.967,49.12a5.53,5.53,0,0,1,0-11.038" transform="translate(-9.836 -17.226)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_43" data-name="Path 43" d="M143,163.779h15.584V143H143Z" transform="translate(-70.275 -77.665)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_44" data-name="Path 44" d="M173.779,148.389a2.582,2.582,0,0,0-.332.033c-.02-.078-.038-.156-.06-.234a2.594,2.594,0,1,0-2.567-4.455q-.086-.088-.174-.175a2.593,2.593,0,1,0-4.461-2.569c-.077-.022-.154-.04-.231-.06a2.6,2.6,0,1,0-5.128,0c-.077.02-.154.038-.231.06a2.594,2.594,0,1,0-4.461,2.569,10.384,10.384,0,1,0,17.314,9.992,2.592,2.592,0,1,0,.332-5.161" transform="translate(-75.08 -75.262)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_45" data-name="Path 45" d="M153,113.389h15.584V103H153Z" transform="translate(-75.08 -58.444)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_46" data-name="Path 46" d="M183.389,108.944a1.3,1.3,0,1,0,0-2.6,1.336,1.336,0,0,0-.166.017c-.01-.039-.019-.078-.03-.117a1.3,1.3,0,0,0-.5-2.5,1.285,1.285,0,0,0-.783.269q-.043-.044-.087-.087a1.285,1.285,0,0,0,.263-.776,1.3,1.3,0,0,0-2.493-.509,5.195,5.195,0,1,0,0,10,1.3,1.3,0,0,0,2.493-.509,1.285,1.285,0,0,0-.263-.776q.044-.043.087-.087a1.285,1.285,0,0,0,.783.269,1.3,1.3,0,0,0,.5-2.5c.011-.038.02-.078.03-.117a1.337,1.337,0,0,0,.166.017" transform="translate(-84.691 -57.894)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_47" data-name="Path 47" d="M52.188,48.292a1.3,1.3,0,0,1-1.3-1.3,3.9,3.9,0,0,0-7.792,0,1.3,1.3,0,1,1-2.6,0,6.493,6.493,0,0,1,12.987,0,1.3,1.3,0,0,1-1.3,1.3" transform="translate(-21.02 -28.41)" fill-rule="evenodd"/>
<path id="Path_48" data-name="Path 48" d="M103,139.752h31.168a10.389,10.389,0,0,0,10.389-10.389V93H113.389A10.389,10.389,0,0,0,103,103.389Z" transform="translate(-51.054 -53.638)" fill="#ffff50" fill-rule="evenodd"/>
<path id="Path_49" data-name="Path 49" d="M141.1,94.017H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0-25.877H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.293H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m0,10.389H115.106a.519.519,0,1,1,0-1.039H141.1a.519.519,0,0,1,0,1.039m7.782-47.993c-.006,0-.011,0-.018,0-1.605.055-2.365,1.66-3.035,3.077-.7,1.48-1.24,2.443-2.126,2.414-.981-.035-1.542-1.144-2.137-2.317-.683-1.347-1.462-2.876-3.1-2.819-1.582.054-2.344,1.451-3.017,2.684-.715,1.313-1.2,2.112-2.141,2.075-1-.036-1.533-.938-2.149-1.981-.686-1.162-1.479-2.467-3.084-2.423-1.555.053-2.319,1.239-2.994,2.286-.713,1.106-1.213,1.781-2.164,1.741-1.025-.036-1.554-.784-2.167-1.65-.688-.973-1.463-2.074-3.062-2.021a3.815,3.815,0,0,0-2.959,1.879c-.64.812-1.14,1.456-2.2,1.415a.52.52,0,0,0-.037,1.039,3.588,3.588,0,0,0,3.05-1.811c.611-.777,1.139-1.448,2.178-1.483,1-.043,1.47.579,2.179,1.582.674.953,1.438,2.033,2.977,2.089,1.612.054,2.387-1.151,3.074-2.217.614-.953,1.144-1.775,2.156-1.81.931-.035,1.438.7,2.153,1.912.674,1.141,1.437,2.434,3.006,2.491,1.623.056,2.407-1.361,3.09-2.616.592-1.085,1.15-2.109,2.14-2.143.931-.022,1.417.829,2.135,2.249.671,1.326,1.432,2.828,3.026,2.886l.088,0c1.592,0,2.347-1.6,3.015-3.01.592-1.252,1.152-2.431,2.113-2.479Z" transform="translate(-55.378 -38.552)" fill-rule="evenodd"/>
<path id="Path_50" data-name="Path 50" d="M83,163.779h20.779V143H83Z" transform="translate(-41.443 -77.665)" fill="#3ecc5f" fill-rule="evenodd"/>
<g id="Group_8" data-name="Group 8" transform="matrix(0.966, -0.259, 0.259, 0.966, 51.971, 43.3)">
<rect id="Rectangle_3" data-name="Rectangle 3" width="43.906" height="17.333" rx="2" transform="translate(0 0)" fill="#d8d8d8"/>
<g id="Group_2" data-name="Group 2" transform="translate(0.728 10.948)">
<rect id="Rectangle_4" data-name="Rectangle 4" width="2.537" height="2.537" rx="1" transform="translate(7.985 0)" fill="#4a4a4a"/>
<rect id="Rectangle_5" data-name="Rectangle 5" width="2.537" height="2.537" rx="1" transform="translate(10.991 0)" fill="#4a4a4a"/>
<rect id="Rectangle_6" data-name="Rectangle 6" width="2.537" height="2.537" rx="1" transform="translate(13.997 0)" fill="#4a4a4a"/>
<rect id="Rectangle_7" data-name="Rectangle 7" width="2.537" height="2.537" rx="1" transform="translate(17.003 0)" fill="#4a4a4a"/>
<rect id="Rectangle_8" data-name="Rectangle 8" width="2.537" height="2.537" rx="1" transform="translate(20.009 0)" fill="#4a4a4a"/>
<rect id="Rectangle_9" data-name="Rectangle 9" width="2.537" height="2.537" rx="1" transform="translate(23.015 0)" fill="#4a4a4a"/>
<rect id="Rectangle_10" data-name="Rectangle 10" width="2.537" height="2.537" rx="1" transform="translate(26.021 0)" fill="#4a4a4a"/>
<rect id="Rectangle_11" data-name="Rectangle 11" width="2.537" height="2.537" rx="1" transform="translate(29.028 0)" fill="#4a4a4a"/>
<rect id="Rectangle_12" data-name="Rectangle 12" width="2.537" height="2.537" rx="1" transform="translate(32.034 0)" fill="#4a4a4a"/>
<path id="Path_51" data-name="Path 51" d="M.519,0H6.9A.519.519,0,0,1,7.421.52v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0ZM35.653,0h6.383a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H35.652a.519.519,0,0,1-.519-.519V.519A.519.519,0,0,1,35.652,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
</g>
<g id="Group_3" data-name="Group 3" transform="translate(0.728 4.878)">
<path id="Path_52" data-name="Path 52" d="M.519,0H2.956a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_13" data-name="Rectangle 13" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
<rect id="Rectangle_14" data-name="Rectangle 14" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
<rect id="Rectangle_15" data-name="Rectangle 15" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
<rect id="Rectangle_16" data-name="Rectangle 16" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
<rect id="Rectangle_17" data-name="Rectangle 17" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
<rect id="Rectangle_18" data-name="Rectangle 18" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
<rect id="Rectangle_19" data-name="Rectangle 19" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
<rect id="Rectangle_20" data-name="Rectangle 20" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
<rect id="Rectangle_21" data-name="Rectangle 21" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
<rect id="Rectangle_22" data-name="Rectangle 22" width="2.537" height="2.537" rx="1" transform="translate(31 0)" fill="#4a4a4a"/>
<rect id="Rectangle_23" data-name="Rectangle 23" width="2.537" height="2.537" rx="1" transform="translate(34.006 0)" fill="#4a4a4a"/>
<rect id="Rectangle_24" data-name="Rectangle 24" width="2.537" height="2.537" rx="1" transform="translate(37.012 0)" fill="#4a4a4a"/>
<rect id="Rectangle_25" data-name="Rectangle 25" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
</g>
<g id="Group_4" data-name="Group 4" transform="translate(43.283 4.538) rotate(180)">
<path id="Path_53" data-name="Path 53" d="M.519,0H2.956a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.519A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_26" data-name="Rectangle 26" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
<rect id="Rectangle_27" data-name="Rectangle 27" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
<rect id="Rectangle_28" data-name="Rectangle 28" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
<rect id="Rectangle_29" data-name="Rectangle 29" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
<rect id="Rectangle_30" data-name="Rectangle 30" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
<rect id="Rectangle_31" data-name="Rectangle 31" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
<rect id="Rectangle_32" data-name="Rectangle 32" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
<rect id="Rectangle_33" data-name="Rectangle 33" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
<rect id="Rectangle_34" data-name="Rectangle 34" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
<rect id="Rectangle_35" data-name="Rectangle 35" width="2.537" height="2.537" rx="1" transform="translate(31.001 0)" fill="#4a4a4a"/>
<rect id="Rectangle_36" data-name="Rectangle 36" width="2.537" height="2.537" rx="1" transform="translate(34.007 0)" fill="#4a4a4a"/>
<rect id="Rectangle_37" data-name="Rectangle 37" width="2.537" height="2.537" rx="1" transform="translate(37.013 0)" fill="#4a4a4a"/>
<rect id="Rectangle_38" data-name="Rectangle 38" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
<rect id="Rectangle_39" data-name="Rectangle 39" width="2.537" height="2.537" rx="1" transform="translate(3.945 0)" fill="#4a4a4a"/>
<rect id="Rectangle_40" data-name="Rectangle 40" width="2.537" height="2.537" rx="1" transform="translate(6.951 0)" fill="#4a4a4a"/>
<rect id="Rectangle_41" data-name="Rectangle 41" width="2.537" height="2.537" rx="1" transform="translate(9.958 0)" fill="#4a4a4a"/>
<rect id="Rectangle_42" data-name="Rectangle 42" width="2.537" height="2.537" rx="1" transform="translate(12.964 0)" fill="#4a4a4a"/>
<rect id="Rectangle_43" data-name="Rectangle 43" width="2.537" height="2.537" rx="1" transform="translate(15.97 0)" fill="#4a4a4a"/>
<rect id="Rectangle_44" data-name="Rectangle 44" width="2.537" height="2.537" rx="1" transform="translate(18.976 0)" fill="#4a4a4a"/>
<rect id="Rectangle_45" data-name="Rectangle 45" width="2.537" height="2.537" rx="1" transform="translate(21.982 0)" fill="#4a4a4a"/>
<rect id="Rectangle_46" data-name="Rectangle 46" width="2.537" height="2.537" rx="1" transform="translate(24.988 0)" fill="#4a4a4a"/>
<rect id="Rectangle_47" data-name="Rectangle 47" width="2.537" height="2.537" rx="1" transform="translate(27.994 0)" fill="#4a4a4a"/>
<rect id="Rectangle_48" data-name="Rectangle 48" width="2.537" height="2.537" rx="1" transform="translate(31.001 0)" fill="#4a4a4a"/>
<rect id="Rectangle_49" data-name="Rectangle 49" width="2.537" height="2.537" rx="1" transform="translate(34.007 0)" fill="#4a4a4a"/>
<rect id="Rectangle_50" data-name="Rectangle 50" width="2.537" height="2.537" rx="1" transform="translate(37.013 0)" fill="#4a4a4a"/>
<rect id="Rectangle_51" data-name="Rectangle 51" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
</g>
<g id="Group_6" data-name="Group 6" transform="translate(0.728 7.883)">
<path id="Path_54" data-name="Path 54" d="M.519,0h3.47a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.52A.519.519,0,0,1,.519,0Z" transform="translate(0 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<g id="Group_5" data-name="Group 5" transform="translate(5.073 0)">
<rect id="Rectangle_52" data-name="Rectangle 52" width="2.537" height="2.537" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
<rect id="Rectangle_53" data-name="Rectangle 53" width="2.537" height="2.537" rx="1" transform="translate(3.006 0)" fill="#4a4a4a"/>
<rect id="Rectangle_54" data-name="Rectangle 54" width="2.537" height="2.537" rx="1" transform="translate(6.012 0)" fill="#4a4a4a"/>
<rect id="Rectangle_55" data-name="Rectangle 55" width="2.537" height="2.537" rx="1" transform="translate(9.018 0)" fill="#4a4a4a"/>
<rect id="Rectangle_56" data-name="Rectangle 56" width="2.537" height="2.537" rx="1" transform="translate(12.025 0)" fill="#4a4a4a"/>
<rect id="Rectangle_57" data-name="Rectangle 57" width="2.537" height="2.537" rx="1" transform="translate(15.031 0)" fill="#4a4a4a"/>
<rect id="Rectangle_58" data-name="Rectangle 58" width="2.537" height="2.537" rx="1" transform="translate(18.037 0)" fill="#4a4a4a"/>
<rect id="Rectangle_59" data-name="Rectangle 59" width="2.537" height="2.537" rx="1" transform="translate(21.042 0)" fill="#4a4a4a"/>
<rect id="Rectangle_60" data-name="Rectangle 60" width="2.537" height="2.537" rx="1" transform="translate(24.049 0)" fill="#4a4a4a"/>
<rect id="Rectangle_61" data-name="Rectangle 61" width="2.537" height="2.537" rx="1" transform="translate(27.055 0)" fill="#4a4a4a"/>
<rect id="Rectangle_62" data-name="Rectangle 62" width="2.537" height="2.537" rx="1" transform="translate(30.061 0)" fill="#4a4a4a"/>
</g>
<path id="Path_55" data-name="Path 55" d="M.52,0H3.8a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.017V.52A.519.519,0,0,1,.519,0Z" transform="translate(38.234 0)" fill="#4a4a4a" fill-rule="evenodd"/>
</g>
<g id="Group_7" data-name="Group 7" transform="translate(0.728 14.084)">
<rect id="Rectangle_63" data-name="Rectangle 63" width="2.537" height="2.537" rx="1" transform="translate(0 0)" fill="#4a4a4a"/>
<rect id="Rectangle_64" data-name="Rectangle 64" width="2.537" height="2.537" rx="1" transform="translate(3.006 0)" fill="#4a4a4a"/>
<rect id="Rectangle_65" data-name="Rectangle 65" width="2.537" height="2.537" rx="1" transform="translate(6.012 0)" fill="#4a4a4a"/>
<rect id="Rectangle_66" data-name="Rectangle 66" width="2.537" height="2.537" rx="1" transform="translate(9.018 0)" fill="#4a4a4a"/>
<path id="Path_56" data-name="Path 56" d="M.519,0H14.981A.519.519,0,0,1,15.5.519v1.5a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,2.018V.519A.519.519,0,0,1,.519,0Zm15.97,0h1.874a.519.519,0,0,1,.519.519v1.5a.519.519,0,0,1-.519.519H16.489a.519.519,0,0,1-.519-.519V.519A.519.519,0,0,1,16.489,0Z" transform="translate(12.024 0)" fill="#4a4a4a" fill-rule="evenodd"/>
<rect id="Rectangle_67" data-name="Rectangle 67" width="2.537" height="2.537" rx="1" transform="translate(31.376 0)" fill="#4a4a4a"/>
<rect id="Rectangle_68" data-name="Rectangle 68" width="2.537" height="2.537" rx="1" transform="translate(34.382 0)" fill="#4a4a4a"/>
<rect id="Rectangle_69" data-name="Rectangle 69" width="2.537" height="2.537" rx="1" transform="translate(40.018 0)" fill="#4a4a4a"/>
<path id="Path_57" data-name="Path 57" d="M2.537,0V.561a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,.561V0Z" transform="translate(39.736 1.08) rotate(180)" fill="#4a4a4a"/>
<path id="Path_58" data-name="Path 58" d="M2.537,0V.561a.519.519,0,0,1-.519.519H.519A.519.519,0,0,1,0,.561V0Z" transform="translate(37.2 1.456)" fill="#4a4a4a"/>
</g>
<rect id="Rectangle_70" data-name="Rectangle 70" width="42.273" height="1.127" rx="0.564" transform="translate(0.915 0.556)" fill="#4a4a4a"/>
<rect id="Rectangle_71" data-name="Rectangle 71" width="2.37" height="0.752" rx="0.376" transform="translate(1.949 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_72" data-name="Rectangle 72" width="2.37" height="0.752" rx="0.376" transform="translate(5.193 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_73" data-name="Rectangle 73" width="2.37" height="0.752" rx="0.376" transform="translate(7.688 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_74" data-name="Rectangle 74" width="2.37" height="0.752" rx="0.376" transform="translate(10.183 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_75" data-name="Rectangle 75" width="2.37" height="0.752" rx="0.376" transform="translate(12.679 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_76" data-name="Rectangle 76" width="2.37" height="0.752" rx="0.376" transform="translate(15.797 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_77" data-name="Rectangle 77" width="2.37" height="0.752" rx="0.376" transform="translate(18.292 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_78" data-name="Rectangle 78" width="2.37" height="0.752" rx="0.376" transform="translate(20.788 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_79" data-name="Rectangle 79" width="2.37" height="0.752" rx="0.376" transform="translate(23.283 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_80" data-name="Rectangle 80" width="2.37" height="0.752" rx="0.376" transform="translate(26.402 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_81" data-name="Rectangle 81" width="2.37" height="0.752" rx="0.376" transform="translate(28.897 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_82" data-name="Rectangle 82" width="2.37" height="0.752" rx="0.376" transform="translate(31.393 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_83" data-name="Rectangle 83" width="2.37" height="0.752" rx="0.376" transform="translate(34.512 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_84" data-name="Rectangle 84" width="2.37" height="0.752" rx="0.376" transform="translate(37.007 0.744)" fill="#d8d8d8" opacity="0.136"/>
<rect id="Rectangle_85" data-name="Rectangle 85" width="2.37" height="0.752" rx="0.376" transform="translate(39.502 0.744)" fill="#d8d8d8" opacity="0.136"/>
</g>
<path id="Path_59" data-name="Path 59" d="M123.779,148.389a2.583,2.583,0,0,0-.332.033c-.02-.078-.038-.156-.06-.234a2.594,2.594,0,1,0-2.567-4.455q-.086-.088-.174-.175a2.593,2.593,0,1,0-4.461-2.569c-.077-.022-.154-.04-.231-.06a2.6,2.6,0,1,0-5.128,0c-.077.02-.154.038-.231.06a2.594,2.594,0,1,0-4.461,2.569,10.384,10.384,0,1,0,17.314,9.992,2.592,2.592,0,1,0,.332-5.161" transform="translate(-51.054 -75.262)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_60" data-name="Path 60" d="M83,113.389h20.779V103H83Z" transform="translate(-41.443 -58.444)" fill="#3ecc5f" fill-rule="evenodd"/>
<path id="Path_61" data-name="Path 61" d="M123.389,108.944a1.3,1.3,0,1,0,0-2.6,1.338,1.338,0,0,0-.166.017c-.01-.039-.019-.078-.03-.117a1.3,1.3,0,0,0-.5-2.5,1.285,1.285,0,0,0-.783.269q-.043-.044-.087-.087a1.285,1.285,0,0,0,.263-.776,1.3,1.3,0,0,0-2.493-.509,5.195,5.195,0,1,0,0,10,1.3,1.3,0,0,0,2.493-.509,1.285,1.285,0,0,0-.263-.776q.044-.043.087-.087a1.285,1.285,0,0,0,.783.269,1.3,1.3,0,0,0,.5-2.5c.011-.038.02-.078.03-.117a1.335,1.335,0,0,0,.166.017" transform="translate(-55.859 -57.894)" fill="#44d860" fill-rule="evenodd"/>
<path id="Path_62" data-name="Path 62" d="M141.8,38.745a1.41,1.41,0,0,1-.255-.026,1.309,1.309,0,0,1-.244-.073,1.349,1.349,0,0,1-.224-.119,1.967,1.967,0,0,1-.2-.161,1.52,1.52,0,0,1-.161-.2,1.282,1.282,0,0,1-.218-.722,1.41,1.41,0,0,1,.026-.255,1.5,1.5,0,0,1,.072-.244,1.364,1.364,0,0,1,.12-.223,1.252,1.252,0,0,1,.358-.358,1.349,1.349,0,0,1,.224-.119,1.309,1.309,0,0,1,.244-.073,1.2,1.2,0,0,1,.509,0,1.262,1.262,0,0,1,.468.192,1.968,1.968,0,0,1,.2.161,1.908,1.908,0,0,1,.161.2,1.322,1.322,0,0,1,.12.223,1.361,1.361,0,0,1,.1.5,1.317,1.317,0,0,1-.379.919,1.968,1.968,0,0,1-.2.161,1.346,1.346,0,0,1-.223.119,1.332,1.332,0,0,1-.5.1m10.389-.649a1.326,1.326,0,0,1-.92-.379,1.979,1.979,0,0,1-.161-.2,1.282,1.282,0,0,1-.218-.722,1.326,1.326,0,0,1,.379-.919,1.967,1.967,0,0,1,.2-.161,1.351,1.351,0,0,1,.224-.119,1.308,1.308,0,0,1,.244-.073,1.2,1.2,0,0,1,.509,0,1.262,1.262,0,0,1,.468.192,1.967,1.967,0,0,1,.2.161,1.326,1.326,0,0,1,.379.919,1.461,1.461,0,0,1-.026.255,1.323,1.323,0,0,1-.073.244,1.847,1.847,0,0,1-.119.223,1.911,1.911,0,0,1-.161.2,1.967,1.967,0,0,1-.2.161,1.294,1.294,0,0,1-.722.218" transform="translate(-69.074 -26.006)" fill-rule="evenodd"/>
</g>
<g id="React-icon" transform="translate(906.3 541.56)">
<path id="Path_330" data-name="Path 330" d="M263.668,117.179c0-5.827-7.3-11.35-18.487-14.775,2.582-11.4,1.434-20.477-3.622-23.382a7.861,7.861,0,0,0-4.016-1v4a4.152,4.152,0,0,1,2.044.466c2.439,1.4,3.5,6.724,2.672,13.574-.2,1.685-.52,3.461-.914,5.272a86.9,86.9,0,0,0-11.386-1.954,87.469,87.469,0,0,0-7.459-8.965c5.845-5.433,11.332-8.41,15.062-8.41V78h0c-4.931,0-11.386,3.514-17.913,9.611-6.527-6.061-12.982-9.539-17.913-9.539v4c3.712,0,9.216,2.959,15.062,8.356a84.687,84.687,0,0,0-7.405,8.947,83.732,83.732,0,0,0-11.4,1.972c-.412-1.793-.717-3.532-.932-5.2-.843-6.85.2-12.175,2.618-13.592a3.991,3.991,0,0,1,2.062-.466v-4h0a8,8,0,0,0-4.052,1c-5.039,2.9-6.168,11.96-3.568,23.328-11.153,3.443-18.415,8.947-18.415,14.757,0,5.828,7.3,11.35,18.487,14.775-2.582,11.4-1.434,20.477,3.622,23.382a7.882,7.882,0,0,0,4.034,1c4.931,0,11.386-3.514,17.913-9.611,6.527,6.061,12.982,9.539,17.913,9.539a8,8,0,0,0,4.052-1c5.039-2.9,6.168-11.96,3.568-23.328C256.406,128.511,263.668,122.988,263.668,117.179Zm-23.346-11.96c-.663,2.313-1.488,4.7-2.421,7.083-.735-1.434-1.506-2.869-2.349-4.3-.825-1.434-1.7-2.833-2.582-4.2C235.517,104.179,237.974,104.645,240.323,105.219Zm-8.212,19.1c-1.4,2.421-2.833,4.716-4.321,6.85-2.672.233-5.379.359-8.1.359-2.708,0-5.415-.126-8.069-.341q-2.232-3.2-4.339-6.814-2.044-3.523-3.73-7.136c1.112-2.4,2.367-4.805,3.712-7.154,1.4-2.421,2.833-4.716,4.321-6.85,2.672-.233,5.379-.359,8.1-.359,2.708,0,5.415.126,8.069.341q2.232,3.2,4.339,6.814,2.044,3.523,3.73,7.136C234.692,119.564,233.455,121.966,232.11,124.315Zm5.792-2.331c.968,2.4,1.793,4.805,2.474,7.136-2.349.574-4.823,1.058-7.387,1.434.879-1.381,1.757-2.8,2.582-4.25C236.4,124.871,237.167,123.419,237.9,121.984ZM219.72,141.116a73.921,73.921,0,0,1-4.985-5.738c1.614.072,3.263.126,4.931.126,1.685,0,3.353-.036,4.985-.126A69.993,69.993,0,0,1,219.72,141.116ZM206.38,130.555c-2.546-.377-5-.843-7.352-1.417.663-2.313,1.488-4.7,2.421-7.083.735,1.434,1.506,2.869,2.349,4.3S205.5,129.192,206.38,130.555ZM219.63,93.241a73.924,73.924,0,0,1,4.985,5.738c-1.614-.072-3.263-.126-4.931-.126-1.686,0-3.353.036-4.985.126A69.993,69.993,0,0,1,219.63,93.241ZM206.362,103.8c-.879,1.381-1.757,2.8-2.582,4.25-.825,1.434-1.6,2.869-2.331,4.3-.968-2.4-1.793-4.805-2.474-7.136C201.323,104.663,203.8,104.179,206.362,103.8Zm-16.227,22.449c-6.348-2.708-10.454-6.258-10.454-9.073s4.106-6.383,10.454-9.073c1.542-.663,3.228-1.255,4.967-1.811a86.122,86.122,0,0,0,4.034,10.92,84.9,84.9,0,0,0-3.981,10.866C193.38,127.525,191.694,126.915,190.134,126.252Zm9.647,25.623c-2.439-1.4-3.5-6.724-2.672-13.574.2-1.686.52-3.461.914-5.272a86.9,86.9,0,0,0,11.386,1.954,87.465,87.465,0,0,0,7.459,8.965c-5.845,5.433-11.332,8.41-15.062,8.41A4.279,4.279,0,0,1,199.781,151.875Zm42.532-13.663c.843,6.85-.2,12.175-2.618,13.592a3.99,3.99,0,0,1-2.062.466c-3.712,0-9.216-2.959-15.062-8.356a84.689,84.689,0,0,0,7.405-8.947,83.731,83.731,0,0,0,11.4-1.972A50.194,50.194,0,0,1,242.313,138.212Zm6.9-11.96c-1.542.663-3.228,1.255-4.967,1.811a86.12,86.12,0,0,0-4.034-10.92,84.9,84.9,0,0,0,3.981-10.866c1.775.556,3.461,1.165,5.039,1.829,6.348,2.708,10.454,6.258,10.454,9.073C259.67,119.994,255.564,123.562,249.216,126.252Z" fill="#61dafb"/>
<path id="Path_331" data-name="Path 331" d="M320.8,78.4Z" transform="translate(-119.082 -0.328)" fill="#61dafb"/>
<circle id="Ellipse_112" data-name="Ellipse 112" cx="8.194" cy="8.194" r="8.194" transform="translate(211.472 108.984)" fill="#61dafb"/>
<path id="Path_332" data-name="Path 332" d="M520.5,78.1Z" transform="translate(-282.975 -0.082)" fill="#61dafb"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -0,0 +1,40 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1129" height="663" viewBox="0 0 1129 663">
<title>Focus on What Matters</title>
<circle cx="321" cy="321" r="321" fill="#f2f2f2" />
<ellipse cx="559" cy="635.49998" rx="514" ry="27.50002" fill="#3f3d56" />
<ellipse cx="558" cy="627" rx="460" ry="22" opacity="0.2" />
<rect x="131" y="152.5" width="840" height="50" fill="#3f3d56" />
<path d="M166.5,727.3299A21.67009,21.67009,0,0,0,188.1701,749H984.8299A21.67009,21.67009,0,0,0,1006.5,727.3299V296h-840Z" transform="translate(-35.5 -118.5)" fill="#3f3d56" />
<path d="M984.8299,236H188.1701A21.67009,21.67009,0,0,0,166.5,257.6701V296h840V257.6701A21.67009,21.67009,0,0,0,984.8299,236Z" transform="translate(-35.5 -118.5)" fill="#3f3d56" />
<path d="M984.8299,236H188.1701A21.67009,21.67009,0,0,0,166.5,257.6701V296h840V257.6701A21.67009,21.67009,0,0,0,984.8299,236Z" transform="translate(-35.5 -118.5)" opacity="0.2" />
<circle cx="181" cy="147.5" r="13" fill="#3f3d56" />
<circle cx="217" cy="147.5" r="13" fill="#3f3d56" />
<circle cx="253" cy="147.5" r="13" fill="#3f3d56" />
<rect x="168" y="213.5" width="337" height="386" rx="5.33505" fill="#606060" />
<rect x="603" y="272.5" width="284" height="22" rx="5.47638" fill="#2e8555" />
<rect x="537" y="352.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
<rect x="537" y="396.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
<rect x="537" y="440.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
<rect x="537" y="484.5" width="416" height="15" rx="5.47638" fill="#2e8555" />
<rect x="865" y="552.5" width="88" height="26" rx="7.02756" fill="#3ecc5f" />
<path d="M1088.60287,624.61594a30.11371,30.11371,0,0,0,3.98291-15.266c0-13.79652-8.54358-24.98081-19.08256-24.98081s-19.08256,11.18429-19.08256,24.98081a30.11411,30.11411,0,0,0,3.98291,15.266,31.248,31.248,0,0,0,0,30.53213,31.248,31.248,0,0,0,0,30.53208,31.248,31.248,0,0,0,0,30.53208,30.11408,30.11408,0,0,0-3.98291,15.266c0,13.79652,8.54353,24.98081,19.08256,24.98081s19.08256-11.18429,19.08256-24.98081a30.11368,30.11368,0,0,0-3.98291-15.266,31.248,31.248,0,0,0,0-30.53208,31.248,31.248,0,0,0,0-30.53208,31.248,31.248,0,0,0,0-30.53213Z" transform="translate(-35.5 -118.5)" fill="#3f3d56" />
<ellipse cx="1038.00321" cy="460.31783" rx="19.08256" ry="24.9808" fill="#3f3d56" />
<ellipse cx="1038.00321" cy="429.78574" rx="19.08256" ry="24.9808" fill="#3f3d56" />
<path d="M1144.93871,339.34489a91.61081,91.61081,0,0,0,7.10658-10.46092l-50.141-8.23491,54.22885.4033a91.566,91.566,0,0,0,1.74556-72.42605l-72.75449,37.74139,67.09658-49.32086a91.41255,91.41255,0,1,0-150.971,102.29805,91.45842,91.45842,0,0,0-10.42451,16.66946l65.0866,33.81447-69.40046-23.292a91.46011,91.46011,0,0,0,14.73837,85.83669,91.40575,91.40575,0,1,0,143.68892,0,91.41808,91.41808,0,0,0,0-113.02862Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M981.6885,395.8592a91.01343,91.01343,0,0,0,19.56129,56.51431,91.40575,91.40575,0,1,0,143.68892,0C1157.18982,436.82067,981.6885,385.60008,981.6885,395.8592Z" transform="translate(-35.5 -118.5)" opacity="0.1" />
<path d="M365.62,461.43628H477.094v45.12043H365.62Z" transform="translate(-35.5 -118.5)" fill="#fff" fill-rule="evenodd" />
<path d="M264.76252,608.74122a26.50931,26.50931,0,0,1-22.96231-13.27072,26.50976,26.50976,0,0,0,22.96231,39.81215H291.304V608.74122Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M384.17242,468.57061l92.92155-5.80726V449.49263a26.54091,26.54091,0,0,0-26.54143-26.54143H331.1161l-3.31768-5.74622a3.83043,3.83043,0,0,0-6.63536,0l-3.31768,5.74622-3.31767-5.74622a3.83043,3.83043,0,0,0-6.63536,0l-3.31768,5.74622L301.257,417.205a3.83043,3.83043,0,0,0-6.63536,0L291.304,422.9512c-.02919,0-.05573.004-.08625.004l-5.49674-5.49541a3.8293,3.8293,0,0,0-6.4071,1.71723l-1.81676,6.77338L270.607,424.1031a3.82993,3.82993,0,0,0-4.6912,4.69253l1.84463,6.89148-6.77072,1.81411a3.8315,3.8315,0,0,0-1.71988,6.40975l5.49673,5.49673c0,.02787-.004.05574-.004.08493l-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74621,3.31768L259.0163,466.081a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31767a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31767a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83042,3.83042,0,0,0,0,6.63535l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768L259.0163,558.976a3.83042,3.83042,0,0,0,0,6.63535l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768-5.74622,3.31768a3.83042,3.83042,0,0,0,0,6.63535l5.74622,3.31768-5.74622,3.31768a3.83043,3.83043,0,0,0,0,6.63536l5.74622,3.31768A26.54091,26.54091,0,0,0,291.304,635.28265H450.55254A26.5409,26.5409,0,0,0,477.094,608.74122V502.5755l-92.92155-5.80727a14.12639,14.12639,0,0,1,0-28.19762" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M424.01111,635.28265h39.81214V582.19979H424.01111Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M490.36468,602.10586a6.60242,6.60242,0,0,0-.848.08493c-.05042-.19906-.09821-.39945-.15393-.59852A6.62668,6.62668,0,1,0,482.80568,590.21q-.2203-.22491-.44457-.44589a6.62391,6.62391,0,1,0-11.39689-6.56369c-.1964-.05575-.39414-.10218-.59056-.15262a6.63957,6.63957,0,1,0-13.10086,0c-.1964.05042-.39414.09687-.59056.15262a6.62767,6.62767,0,1,0-11.39688,6.56369,26.52754,26.52754,0,1,0,44.23127,25.52756,6.6211,6.6211,0,1,0,.848-13.18579" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
<path d="M437.28182,555.65836H477.094V529.11693H437.28182Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M490.36468,545.70532a3.31768,3.31768,0,0,0,0-6.63536,3.41133,3.41133,0,0,0-.42333.04247c-.02655-.09953-.04911-.19907-.077-.29859a3.319,3.319,0,0,0-1.278-6.37923,3.28174,3.28174,0,0,0-2.00122.68742q-.10947-.11346-.22294-.22295a3.282,3.282,0,0,0,.67149-1.98265,3.31768,3.31768,0,0,0-6.37-1.2992,13.27078,13.27078,0,1,0,0,25.54082,3.31768,3.31768,0,0,0,6.37-1.2992,3.282,3.282,0,0,0-.67149-1.98265q.11347-.10947.22294-.22294a3.28174,3.28174,0,0,0,2.00122.68742,3.31768,3.31768,0,0,0,1.278-6.37923c.02786-.0982.05042-.19907.077-.29859a3.41325,3.41325,0,0,0,.42333.04246" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
<path d="M317.84538,466.081a3.31768,3.31768,0,0,1-3.31767-3.31768,9.953,9.953,0,1,0-19.90608,0,3.31768,3.31768,0,1,1-6.63535,0,16.58839,16.58839,0,1,1,33.17678,0,3.31768,3.31768,0,0,1-3.31768,3.31768" transform="translate(-35.5 -118.5)" fill-rule="evenodd" />
<path d="M370.92825,635.28265h79.62429A26.5409,26.5409,0,0,0,477.094,608.74122v-92.895H397.46968a26.54091,26.54091,0,0,0-26.54143,26.54143Z" transform="translate(-35.5 -118.5)" fill="#ffff50" fill-rule="evenodd" />
<path d="M457.21444,556.98543H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.54143H390.80778a1.32707,1.32707,0,1,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.54143H390.80778a1.32707,1.32707,0,1,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0-66.10674H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.29459H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414m0,26.54143H390.80778a1.32707,1.32707,0,0,1,0-2.65414h66.40666a1.32707,1.32707,0,0,1,0,2.65414M477.094,474.19076c-.01592,0-.0292-.008-.04512-.00663-4.10064.13934-6.04083,4.24132-7.75274,7.86024-1.78623,3.78215-3.16771,6.24122-5.43171,6.16691-2.50685-.09024-3.94007-2.92222-5.45825-5.91874-1.74377-3.44243-3.73438-7.34667-7.91333-7.20069-4.04227.138-5.98907,3.70784-7.70631,6.857-1.82738,3.35484-3.07084,5.39455-5.46887,5.30033-2.55727-.09289-3.91619-2.39536-5.48877-5.06013-1.75306-2.96733-3.77951-6.30359-7.8775-6.18946-3.97326.13669-5.92537,3.16507-7.64791,5.83912-1.82207,2.82666-3.09872,4.5492-5.52725,4.447-2.61832-.09289-3.9706-2.00388-5.53522-4.21611-1.757-2.4856-3.737-5.299-7.82308-5.16231-3.88567.13271-5.83779,2.61434-7.559,4.80135-1.635,2.07555-2.9116,3.71846-5.61218,3.615a1.32793,1.32793,0,1,0-.09555,2.65414c4.00377.134,6.03154-2.38873,7.79257-4.6275,1.562-1.9853,2.91027-3.69855,5.56441-3.78879,2.55594-.10882,3.75429,1.47968,5.56707,4.04093,1.7212,2.43385,3.67465,5.19416,7.60545,5.33616,4.11789.138,6.09921-2.93946,7.8536-5.66261,1.56861-2.43385,2.92221-4.53461,5.50734-4.62352,2.37944-.08892,3.67466,1.79154,5.50072,4.885,1.72121,2.91557,3.67069,6.21865,7.67977,6.36463,4.14709.14332,6.14965-3.47693,7.89475-6.68181,1.51155-2.77092,2.93814-5.38791,5.46621-5.4755,2.37944-.05573,3.62025,2.11668,5.45558,5.74622,1.71459,3.388,3.65875,7.22591,7.73019,7.37321l.22429.004c4.06614,0,5.99571-4.08074,7.70364-7.68905,1.51154-3.19825,2.94211-6.21069,5.3972-6.33411Z" transform="translate(-35.5 -118.5)" fill-rule="evenodd" />
<path d="M344.38682,635.28265h53.08286V582.19979H344.38682Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M424.01111,602.10586a6.60242,6.60242,0,0,0-.848.08493c-.05042-.19906-.09821-.39945-.15394-.59852A6.62667,6.62667,0,1,0,416.45211,590.21q-.2203-.22491-.44458-.44589a6.62391,6.62391,0,1,0-11.39689-6.56369c-.1964-.05575-.39413-.10218-.59054-.15262a6.63957,6.63957,0,1,0-13.10084,0c-.19641.05042-.39414.09687-.59055.15262a6.62767,6.62767,0,1,0-11.39689,6.56369,26.52755,26.52755,0,1,0,44.2313,25.52756,6.6211,6.6211,0,1,0,.848-13.18579" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
<path d="M344.38682,555.65836h53.08286V529.11693H344.38682Z" transform="translate(-35.5 -118.5)" fill="#3ecc5f" fill-rule="evenodd" />
<path d="M410.74039,545.70532a3.31768,3.31768,0,1,0,0-6.63536,3.41133,3.41133,0,0,0-.42333.04247c-.02655-.09953-.04911-.19907-.077-.29859a3.319,3.319,0,0,0-1.278-6.37923,3.28174,3.28174,0,0,0-2.00122.68742q-.10947-.11346-.22294-.22295a3.282,3.282,0,0,0,.67149-1.98265,3.31768,3.31768,0,0,0-6.37-1.2992,13.27078,13.27078,0,1,0,0,25.54082,3.31768,3.31768,0,0,0,6.37-1.2992,3.282,3.282,0,0,0-.67149-1.98265q.11347-.10947.22294-.22294a3.28174,3.28174,0,0,0,2.00122.68742,3.31768,3.31768,0,0,0,1.278-6.37923c.02786-.0982.05042-.19907.077-.29859a3.41325,3.41325,0,0,0,.42333.04246" transform="translate(-35.5 -118.5)" fill="#44d860" fill-rule="evenodd" />
<path d="M424.01111,447.8338a3.60349,3.60349,0,0,1-.65028-.06636,3.34415,3.34415,0,0,1-.62372-.18579,3.44679,3.44679,0,0,1-.572-.30522,5.02708,5.02708,0,0,1-.50429-.4114,3.88726,3.88726,0,0,1-.41007-.50428,3.27532,3.27532,0,0,1-.55737-1.84463,3.60248,3.60248,0,0,1,.06636-.65027,3.82638,3.82638,0,0,1,.18447-.62373,3.48858,3.48858,0,0,1,.30656-.57064,3.197,3.197,0,0,1,.91436-.91568,3.44685,3.44685,0,0,1,.572-.30523,3.344,3.344,0,0,1,.62372-.18578,3.06907,3.06907,0,0,1,1.30053,0,3.22332,3.22332,0,0,1,1.19436.491,5.02835,5.02835,0,0,1,.50429.41139,4.8801,4.8801,0,0,1,.41139.50429,3.38246,3.38246,0,0,1,.30522.57064,3.47806,3.47806,0,0,1,.25215,1.274A3.36394,3.36394,0,0,1,426.36,446.865a5.02708,5.02708,0,0,1-.50429.4114,3.3057,3.3057,0,0,1-1.84463.55737m26.54143-1.65884a3.38754,3.38754,0,0,1-2.35024-.96877,5.04185,5.04185,0,0,1-.41007-.50428,3.27532,3.27532,0,0,1-.55737-1.84463,3.38659,3.38659,0,0,1,.96744-2.34892,5.02559,5.02559,0,0,1,.50429-.41139,3.44685,3.44685,0,0,1,.572-.30523,3.3432,3.3432,0,0,1,.62373-.18579,3.06952,3.06952,0,0,1,1.30052,0,3.22356,3.22356,0,0,1,1.19436.491,5.02559,5.02559,0,0,1,.50429.41139,3.38792,3.38792,0,0,1,.96876,2.34892,3.72635,3.72635,0,0,1-.06636.65026,3.37387,3.37387,0,0,1-.18579.62373,4.71469,4.71469,0,0,1-.30522.57064,4.8801,4.8801,0,0,1-.41139.50429,5.02559,5.02559,0,0,1-.50429.41139,3.30547,3.30547,0,0,1-1.84463.55737" transform="translate(-35.5 -118.5)" fill-rule="evenodd" />
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

8
docs/tsconfig.json Normal file
View File

@ -0,0 +1,8 @@
{
// This file is not used in compilation. It is here just for a nice editor experience.
"extends": "@docusaurus/tsconfig",
"compilerOptions": {
"baseUrl": "."
},
"exclude": [".docusaurus", "build"]
}