Add SVG illustration and TypeScript configuration file
- Added a new SVG file `undraw_docusaurus_tree.svg` to the static images directory for enhanced visual representation. - Created a `tsconfig.json` file to improve TypeScript support and editor experience, extending from the Docusaurus base configuration.
20
docs/.gitignore
vendored
Normal 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
@ -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.
|
12
docs/blog/2019-05-28-first-blog-post.md
Normal 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
|
44
docs/blog/2019-05-29-long-blog-post.md
Normal 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
|
24
docs/blog/2021-08-01-mdx-blog-post.mdx
Normal 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>
|
BIN
docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg
Normal file
After Width: | Height: | Size: 94 KiB |
29
docs/blog/2021-08-26-welcome/index.md
Normal 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:
|
||||
|
||||

|
||||
|
||||
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
@ -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
@ -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
|
629
docs/docs/api/debros-framework.md
Normal 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
@ -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.
|
340
docs/docs/core-concepts/architecture.md
Normal 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.
|
696
docs/docs/core-concepts/decorators.md
Normal 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.
|
566
docs/docs/core-concepts/models.md
Normal 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.
|
935
docs/docs/examples/basic-usage.md
Normal 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.
|
449
docs/docs/getting-started.md
Normal 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
@ -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._
|
528
docs/docs/query-system/query-builder.md
Normal 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.
|
8
docs/docs/tutorial-basics/_category_.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"label": "Tutorial - Basics",
|
||||
"position": 2,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "5 minutes to learn the most important Docusaurus concepts."
|
||||
}
|
||||
}
|
23
docs/docs/tutorial-basics/congratulations.md
Normal 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)
|
34
docs/docs/tutorial-basics/create-a-blog-post.md
Normal 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).
|
57
docs/docs/tutorial-basics/create-a-document.md
Normal 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'],
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
43
docs/docs/tutorial-basics/create-a-page.md
Normal 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).
|
31
docs/docs/tutorial-basics/deploy-your-site.md
Normal 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)**).
|
152
docs/docs/tutorial-basics/markdown-features.mdx
Normal 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
|
||||

|
||||
```
|
||||
|
||||

|
||||
|
||||
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
|
||||

|
||||
```
|
||||
|
||||
## 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> !
|
7
docs/docs/tutorial-extras/_category_.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"label": "Tutorial - Extras",
|
||||
"position": 3,
|
||||
"link": {
|
||||
"type": "generated-index"
|
||||
}
|
||||
}
|
BIN
docs/docs/tutorial-extras/img/docsVersionDropdown.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
docs/docs/tutorial-extras/img/localeDropdown.png
Normal file
After Width: | Height: | Size: 27 KiB |
55
docs/docs/tutorial-extras/manage-docs-versions.md
Normal 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:
|
||||
|
||||

|
||||
|
||||
## 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`
|
88
docs/docs/tutorial-extras/translate-your-site.md
Normal 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:
|
||||
|
||||

|
||||
|
||||
## 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
@ -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
@ -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
58
docs/sidebars.ts
Normal 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;
|
71
docs/src/components/HomepageFeatures/index.tsx
Normal 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'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>
|
||||
);
|
||||
}
|
11
docs/src/components/HomepageFeatures/styles.module.css
Normal 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
@ -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);
|
||||
}
|
23
docs/src/pages/index.module.css
Normal 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
@ -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>
|
||||
);
|
||||
}
|
7
docs/src/pages/markdown-page.md
Normal 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
BIN
docs/static/img/docusaurus-social-card.jpg
vendored
Normal file
After Width: | Height: | Size: 54 KiB |
BIN
docs/static/img/docusaurus.png
vendored
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
docs/static/img/favicon.ico
vendored
Normal file
After Width: | Height: | Size: 3.5 KiB |
1
docs/static/img/logo.svg
vendored
Normal file
After Width: | Height: | Size: 6.3 KiB |
171
docs/static/img/undraw_docusaurus_mountain.svg
vendored
Normal 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 |
170
docs/static/img/undraw_docusaurus_react.svg
vendored
Normal 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 |
40
docs/static/img/undraw_docusaurus_tree.svg
vendored
Normal 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
@ -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"]
|
||||
}
|