Skip to content

docs: improve #660

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 15 commits into
base: master
Choose a base branch
from
183 changes: 170 additions & 13 deletions website/src/pages/documentation/app.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,35 +154,192 @@ In Deepkit everything is now done via this `app.ts`. You can rename the file as

Deepkit App automatically converts function parameters into CLI arguments and flags. The order of the parameters dictates the order of the CLI arguments

Parameters can be any TypeScript type and are automatically validated and deserialized.
Parameters can be any TypeScript type and are automatically validated and deserialized.

See the chapter [Arguments & Flags](./app/arguments.md) for more information.
```typescript
// Simple arguments
app.command('greet', (name: string, age?: number) => {
console.log(`Hello ${name}${age ? `, you are ${age}` : ''}`);
});

// Flags with validation
import { Flag, Email, Positive } from '@deepkit/app';

app.command('create-user', (
email: string & Email & Flag,
age: number & Positive & Flag,
admin: boolean & Flag = false
) => {
console.log(`Creating user: ${email}, age: ${age}, admin: ${admin}`);
});
```

## Dependency Injection
See the chapter [Arguments & Flags](./app/arguments.md) for comprehensive examples and advanced usage.

Deepkit App sets up a service container and for each imported module its own Dependency Injection container that inherits from its parents.
It brings out of the box following providers you can automatically inject into your services, controllers, and event listeners:
## Services & Dependency Injection

Deepkit App provides a powerful dependency injection system that automatically resolves and injects dependencies:

```typescript
class UserService {
users: string[] = [];

addUser(name: string) {
this.users.push(name);
return `User ${name} added`;
}
}

class EmailService {
sendEmail(to: string, subject: string) {
console.log(`Sending email to ${to}: ${subject}`);
}
}

const app = new App({
providers: [UserService, EmailService]
});

// Functional command with DI
app.command('add-user', (name: string, userService: UserService, emailService: EmailService) => {
const result = userService.addUser(name);
emailService.sendEmail(`${name}@example.com`, 'Welcome!');
console.log(result);
});
```

Built-in providers available out of the box:

- `Logger` for logging
- `EventDispatcher` for event handling
- `CliControllerRegistry` for registered CLI commands
- `MiddlewareRegistry` for registered middleware
- `InjectorContext` for the current injector context

As soon as you import Deepkit Framework you get additional providers. See [Deepkit Framework](./framework.md) for more details.
See the chapters [Services](./app/services.md) and [Dependency Injection](./app/dependency-injection.md) for more details.

## Configuration

Type-safe configuration with automatic environment variable loading:

```typescript
import { MinLength } from '@deepkit/type';

class AppConfig {
appName: string & MinLength<3> = 'MyApp';
port: number = 3000;
debug: boolean = false;
databaseUrl!: string; // Required
}

const app = new App({
config: AppConfig
})
.loadConfigFromEnv() // Loads APP_* environment variables
.configure({ debug: true }); // Override programmatically

app.command('show-config', (config: AppConfig) => {
console.log('App configuration:', config);
});
```

See the chapter [Configuration](./app/configuration.md) for advanced configuration patterns.

## Exit code
## Modules

The exit code is 0 by default, which means that the command was executed successfully. To change the exit code, a number other than 0 should be returned in the exucute method.
Organize your application into reusable modules:

```typescript
@cli.controller('test')
export class TestCommand {
async execute() {
console.error('Error :(');
return 12;
import { createModuleClass } from '@deepkit/app';

export class DatabaseModule extends createModuleClass({
providers: [DatabaseService],
exports: [DatabaseService]
}) {}

export class UserModule extends createModuleClass({
imports: [new DatabaseModule()],
providers: [UserService],
controllers: [UserController]
}) {}

const app = new App({
imports: [new UserModule()]
});
```

See the chapter [Modules](./app/modules.md) for module patterns and best practices.

## Event System

Handle application events and create custom event-driven architectures:

```typescript
import { EventToken, onAppExecute } from '@deepkit/app';

const UserCreated = new EventToken('user.created');

app.listen(onAppExecute, (event) => {
console.log('Command starting:', event.command);
});

app.listen(UserCreated, (event, logger: Logger) => {
logger.log('User created:', event.data);
});

app.command('create-user', async (name: string, eventDispatcher: EventDispatcher) => {
// Create user logic...
await eventDispatcher.dispatch(UserCreated, { name });
});
```

See the chapter [Events](./app/events.md) for comprehensive event handling.

## Testing

Deepkit App provides excellent testing support:

```typescript
import { createTestingApp } from '@deepkit/framework';

test('user creation command', async () => {
const testing = createTestingApp({
providers: [UserService],
controllers: [CreateUserCommand]
});

const exitCode = await testing.app.execute(['create-user', 'John']);
expect(exitCode).toBe(0);

const userService = testing.app.get(UserService);
expect(userService.users).toContain('John');
});
```

See the chapter [Testing](./app/testing.md) for comprehensive testing strategies.

## Exit Codes

Control command exit codes for proper CLI behavior:

```typescript
@cli.controller('deploy')
export class DeployCommand {
async execute(environment: string): Promise<number> {
try {
await this.deployToEnvironment(environment);
console.log('Deployment successful');
return 0; // Success
} catch (error) {
console.error('Deployment failed:', error.message);
return 1; // Error
}
}
}
```

## Advanced Topics

- **[Application Lifecycle](./app/lifecycle.md)**: Understanding startup, shutdown, and lifecycle events
- **[Best Practices](./app/best-practices.md)**: Patterns and recommendations for robust applications
- **[Troubleshooting](./app/troubleshooting.md)**: Common issues and debugging techniques
138 changes: 138 additions & 0 deletions website/src/pages/documentation/app/arguments.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,24 @@ and are automatically validated and deserialized. The order of the parameters di
As soon as a complex object (interface, class, object literal) is defined, it is treated as a service dependency
and the Dependency Injection Container tries to resolve it. See the chapter [Dependency Injection](dependency-injection.md) for more information.

## Multiple Arguments

You can define multiple arguments by adding more parameters to your function or method:

```typescript
new App().command('greet', (firstName: string, lastName: string, age?: number) => {
console.log(`Hello ${firstName} ${lastName}${age ? `, you are ${age} years old` : ''}`);
});
```

```sh
$ ts-node app.ts greet John Doe
Hello John Doe

$ ts-node app.ts greet John Doe 25
Hello John Doe, you are 25 years old
```

## Flags

Flags are another way to pass values to your command. Mostly these are optional, but they don't have to be. Parameters decorated with the `Flag` type can be passed via `--name value` or `--name=value`.
Expand Down Expand Up @@ -118,6 +136,59 @@ $ ts-node app.ts test --remove
delete? true
```

### Object Flags

You can use object literals as flags to group related options together:

```typescript
import { Flag } from '@deepkit/app';

interface UserOptions {
name: string;
age?: number;
email?: string;
}

new App().command('create-user', (options: UserOptions & Flag) => {
console.log('Creating user:', options);
});
```

```sh
$ ts-node app.ts create-user --name "John Doe" --age 30 --email "[email protected]"
Creating user: { name: 'John Doe', age: 30, email: '[email protected]' }
```

### Flag Prefixes

When using multiple object flags, you can use prefixes to avoid naming conflicts:

```typescript
interface DatabaseOptions {
host: string;
port?: number;
}

interface CacheOptions {
host: string;
ttl?: number;
}

new App().command('setup', (
db: DatabaseOptions & Flag,
cache: CacheOptions & Flag<{ prefix: 'cache' }>
) => {
console.log('Database:', db);
console.log('Cache:', cache);
});
```

```sh
$ ts-node app.ts setup --host "db.example.com" --cache.host "cache.example.com" --cache.ttl 3600
Database: { host: 'db.example.com' }
Cache: { host: 'cache.example.com', ttl: 3600 }
```

### Multiple Flags

To pass multiple values to the same flag, a flag can be marked as an array.
Expand Down Expand Up @@ -284,6 +355,41 @@ Validation error in id: Number needs to be positive [positive]

This additional validation, which is very easy to do, makes the command much more robust against wrong entries. See the chapter [Validation](../runtime-types/validation.md) for more information.

### Union Types

Union types allow you to restrict values to a specific set of options:

```typescript
type LogLevel = 'debug' | 'info' | 'warn' | 'error';

new App().command('log', (message: string, level: LogLevel & Flag = 'info') => {
console.log(`[${level.toUpperCase()}] ${message}`);
});
```

```sh
$ ts-node app.ts log "Hello World" --level debug
[DEBUG] Hello World

$ ts-node app.ts log "Hello World" --level invalid
Invalid value for option --level: invalid. No valid union member found. Valid: 'debug' | 'info' | 'warn' | 'error'
```

### Date and Complex Types

Deepkit automatically handles complex types like dates:

```typescript
new App().command('schedule', (task: string, date: Date & Flag) => {
console.log(`Task "${task}" scheduled for ${date.toISOString()}`);
});
```

```sh
$ ts-node app.ts schedule "Meeting" --date "2024-01-15T10:00:00Z"
Task "Meeting" scheduled for 2024-01-15T10:00:00.000Z
```

## Description

To describe a flag or argument, use `@description` comment decorator.
Expand Down Expand Up @@ -316,3 +422,35 @@ ARGUMENTS
OPTIONS
--remove Delete the user?
```

## Command Namespacing

You can organize commands into namespaces using the colon syntax:

```typescript
new App()
.command('user:create', (name: string) => {
console.log(`Creating user: ${name}`);
})
.command('user:delete', (id: number) => {
console.log(`Deleting user: ${id}`);
})
.command('user:list', () => {
console.log('Listing all users');
});
```

```sh
$ ts-node app.ts user
USAGE
$ ts-node app.ts user:[COMMAND]

COMMANDS
user
user:create
user:delete
user:list

$ ts-node app.ts user:create "John Doe"
Creating user: John Doe
```
Loading