AI Development Guide - Dart SDK

This guide provides a comprehensive, fast reference for AI systems to quickly develop applications using the BosBase Dart SDK. All examples are production-ready and follow best practices.

Table of Contents

  1. Authentication
  2. Initialize Collections
  3. Define Collection Fields
  4. Add Data to Collections
  5. Modify Collection Data
  6. Delete Data from Collections
  7. Query Collection Contents
  8. Add and Delete Fields from Collections
  9. Query Collection Field Information
  10. Upload Files
  11. Query Logs
  12. Send Emails

Authentication

Initialize Client

import 'package:bosbase/bosbase.dart';

final pb = Bosbase('http://localhost:8090');

Password Authentication

// Authenticate with email/username and password
final authData = await pb.collection('users').authWithPassword(
  'user@example.com',
  'password123',
);

// Auth data is automatically stored
print(pb.authStore.isValid);  // true
print(pb.authStore.token);    // JWT token
print(pb.authStore.record);   // User record

OAuth2 Authentication

// Get OAuth2 providers
final methods = await pb.collection('users').listAuthMethods();
print(methods.oauth2.providers); // Available providers

// Authenticate with OAuth2
final authData = await pb.collection('users').authWithOAuth2(
  'google',
  (url) async {
    // Open OAuth2 URL
    await launchUrl(url);
  },
);

OTP Authentication

// Request OTP
final otpResponse = await pb.collection('users').requestVerification('user@example.com');

// Authenticate with OTP
final authData = await pb.collection('users').authWithOTP(
  otpResponse.otpId,
  '123456', // OTP code
);

Check Authentication Status

if (pb.authStore.isValid) {
  print('Authenticated as: ${pb.authStore.record?.email}');
} else {
  print('Not authenticated');
}

Logout

pb.authStore.clear();

Initialize Collections

Create Base Collection

final collection = await pb.collections.create(body: {
  'name': 'posts',
  'type': 'base',
  'fields': [
    {
      'name': 'title',
      'type': 'text',
      'required': true,
    },
  ],
});

print('Collection ID: ${collection.id}');

Create Auth Collection

final authCollection = await pb.collections.create(body: {
  'name': 'users',
  'type': 'auth',
  'fields': [
    {
      'name': 'name',
      'type': 'text',
      'required': false,
    },
  ],
  'passwordAuth': {
    'enabled': true,
    'identityFields': ['email', 'username'],
  },
});

Create View Collection

final viewCollection = await pb.collections.create(body: {
  'name': 'published_posts',
  'type': 'view',
  'viewQuery': 'SELECT * FROM posts WHERE published = true',
});

Get Collection by ID or Name

final collection = await pb.collections.getOne('posts');
// or by ID
final collection = await pb.collections.getOne('_pbc_2287844090');

Define Collection Fields

Add Field to Collection

final updatedCollection = await pb.collections.addField('posts', {
  'name': 'content',
  'type': 'editor',
  'required': false,
});

Common Field Types

// Text field
{
  'name': 'title',
  'type': 'text',
  'required': true,
  'min': 10,
  'max': 255,
}

// Number field
{
  'name': 'views',
  'type': 'number',
  'required': false,
  'min': 0,
}

// Boolean field
{
  'name': 'published',
  'type': 'bool',
  'required': false,
}

// Date field
{
  'name': 'published_at',
  'type': 'date',
  'required': false,
}

// File field
{
  'name': 'avatar',
  'type': 'file',
  'required': false,
  'maxSelect': 1,
  'maxSize': 2097152, // 2MB
  'mimeTypes': ['image/jpeg', 'image/png'],
}

// Relation field
{
  'name': 'author',
  'type': 'relation',
  'required': true,
  'collectionId': '_pbc_users_auth_',
  'maxSelect': 1,
}

// Select field
{
  'name': 'status',
  'type': 'select',
  'required': true,
  'options': {
    'values': ['draft', 'published', 'archived'],
  },
}

Update Field

final updatedCollection = await pb.collections.updateField('posts', 'title', {
  'max': 500,
  'required': true,
});

Remove Field

final updatedCollection = await pb.collections.removeField('posts', 'old_field');

Add Data to Collections

Create Single Record

final record = await pb.collection('posts').create(body: {
  'title': 'My First Post',
  'content': 'This is the content',
  'published': true,
});

print('Created record ID: ${record.id}');

Create Record with File Upload

import 'package:http/http.dart' as http;

final file = await http.MultipartFile.fromPath('image', '/path/to/image.jpg');
final request = http.MultipartRequest(
  'POST',
  Uri.parse('${pb.baseURL}/api/collections/posts/records'),
);
request.fields['title'] = 'Post with Image';
request.files.add(file);
request.headers['Authorization'] = 'Bearer ${pb.authStore.token}';

final response = await request.send();
final responseBody = await response.stream.bytesToString();
final record = RecordModel.fromJson(jsonDecode(responseBody));

Create Record with Relations

final record = await pb.collection('posts').create(body: {
  'title': 'My Post',
  'author': 'user_record_id', // Related record ID
  'categories': ['cat1_id', 'cat2_id'], // Multiple relations
});

Batch Create Records

final records = await pb.batch([
  {
    'method': 'POST',
    'url': '/api/collections/posts/records',
    'body': {'title': 'Post 1'},
  },
  {
    'method': 'POST',
    'url': '/api/collections/posts/records',
    'body': {'title': 'Post 2'},
  },
]);

Modify Collection Data

Update Single Record

final updated = await pb.collection('posts').update('record_id', body: {
  'title': 'Updated Title',
  'content': 'Updated content',
});

Update Record with File

import 'package:http/http.dart' as http;

final file = await http.MultipartFile.fromPath('image', '/path/to/new_image.jpg');
final request = http.MultipartRequest(
  'PATCH',
  Uri.parse('${pb.baseURL}/api/collections/posts/records/record_id'),
);
request.fields['title'] = 'Updated Title';
request.files.add(file);
request.headers['Authorization'] = 'Bearer ${pb.authStore.token}';

final response = await request.send();

Partial Update

// Only update specific fields
final updated = await pb.collection('posts').update('record_id', body: {
  'views': 100, // Only update views
});

Delete Data from Collections

Delete Single Record

await pb.collection('posts').delete('record_id');

Delete Multiple Records

// Using batch
await pb.batch([
  {
    'method': 'DELETE',
    'url': '/api/collections/posts/records/record_id_1',
  },
  {
    'method': 'DELETE',
    'url': '/api/collections/posts/records/record_id_2',
  },
]);

Delete All Records (Truncate)

await pb.collections.truncate('posts');

Query Collection Contents

List Records with Pagination

final result = await pb.collection('posts').getList(page: 1, perPage: 50);

print(result.page);        // 1
print(result.perPage);     // 50
print(result.totalItems);  // Total count
print(result.items);        // List of records

Filter Records

final result = await pb.collection('posts').getList(
  page: 1,
  perPage: 50,
  filter: 'published = true && views > 100',
  sort: '-created',
);

Filter Operators

// Equality
filter: 'status = "published"'

// Comparison
filter: 'views > 100'
filter: 'created >= "2023-01-01"'

// Text search
filter: 'title ~ "dart"'

// Multiple conditions
filter: 'status = "published" && views > 100'
filter: 'status = "draft" || status = "pending"'

// Relation filter
filter: 'author.id = "user_id"'

Sort Records

// Single field
sort: '-created'  // DESC
sort: 'title'     // ASC

// Multiple fields
sort: '-created,title'  // DESC by created, then ASC by title

Expand Relations

final result = await pb.collection('posts').getList(
  page: 1,
  perPage: 50,
  expand: 'author,categories',
);

// Access expanded data
for (final post in result.items) {
  print(post.expand?['author']?.name);
  print(post.expand?['categories']);
}

Get Single Record

final record = await pb.collection('posts').getOne(
  'record_id',
  expand: 'author',
);

Get First Matching Record

final record = await pb.collection('posts').getFirstListItem(
  'slug = "my-post-slug"',
  expand: 'author',
);

Get All Records

final allRecords = await pb.collection('posts').getFullList(
  filter: 'published = true',
  sort: '-created',
);

Add and Delete Fields from Collections

Add Field

final collection = await pb.collections.addField('posts', {
  'name': 'tags',
  'type': 'select',
  'options': {
    'values': ['tech', 'science', 'art'],
  },
});

Update Field

final collection = await pb.collections.updateField('posts', 'tags', {
  'options': {
    'values': ['tech', 'science', 'art', 'music'],
  },
});

Remove Field

final collection = await pb.collections.removeField('posts', 'old_field');

Get Field Information

final field = await pb.collections.getField('posts', 'title');
print('${field?.type}, ${field?.required}, ${field?.options}');

Query Collection Field Information

Get All Fields for a Collection

final collection = await pb.collections.getOne('posts');
for (final field in collection.fields) {
  print('${field.name}, ${field.type}, ${field.required}');
}

Get Collection Schema (Simplified)

final schema = await pb.collections.getSchema('posts');
print(schema.fields); // List of field info

Get All Collection Schemas

final schemas = await pb.collections.getAllSchemas();
for (final collection in schemas) {
  print('${collection.name}, ${collection.fields}');
}

Query Field Information for Single Collection

// Method 1: Get full collection
final collection = await pb.collections.getOne('posts');
final titleField = collection.fields.firstWhere(
  (f) => f.name == 'title',
  orElse: () => throw Exception('Field not found'),
);

// Method 2: Get specific field
final field = await pb.collections.getField('posts', 'title');

// Method 3: Get schema
final schema = await pb.collections.getSchema('posts');
final titleFieldInfo = schema.fields.firstWhere(
  (f) => f.name == 'title',
  orElse: () => throw Exception('Field not found'),
);

Upload Files

Upload File with Record Creation

import 'package:http/http.dart' as http;
import 'dart:io';

final file = File('/path/to/image.jpg');
final fileBytes = await file.readAsBytes();
final multipartFile = http.MultipartFile.fromBytes(
  'image',
  fileBytes,
  filename: 'image.jpg',
);

final request = http.MultipartRequest(
  'POST',
  Uri.parse('${pb.baseURL}/api/collections/posts/records'),
);
request.fields['title'] = 'Post Title';
request.files.add(multipartFile);
request.headers['Authorization'] = 'Bearer ${pb.authStore.token}';

final response = await request.send();
final responseBody = await response.stream.bytesToString();
final record = RecordModel.fromJson(jsonDecode(responseBody));

Upload File with Record Update

final file = File('/path/to/new_image.jpg');
final fileBytes = await file.readAsBytes();
final multipartFile = http.MultipartFile.fromBytes(
  'image',
  fileBytes,
  filename: 'new_image.jpg',
);

final request = http.MultipartRequest(
  'PATCH',
  Uri.parse('${pb.baseURL}/api/collections/posts/records/record_id'),
);
request.files.add(multipartFile);
request.headers['Authorization'] = 'Bearer ${pb.authStore.token}';

final response = await request.send();

Get File URL

final record = await pb.collection('posts').getOne('record_id');
final fileUrl = pb.files.getURL(record, record.data['image'] as String);

Get File URL with Options

final fileUrl = pb.files.getURL(
  record,
  record.data['image'] as String,
  thumb: '100x100',  // Thumbnail
  download: true,    // Force download
);

Get Private File Token

// For accessing private files
final token = await pb.files.getToken();
// Use token in file URL
final fileUrl = pb.files.getURL(
  record,
  record.data['image'] as String,
  token: token,
);

Query Logs

List Logs

final logs = await pb.logs.getList(page: 1, perPage: 50);
print(logs.items); // List of log entries

Filter Logs

final logs = await pb.logs.getList(
  page: 1,
  perPage: 50,
  filter: 'level >= 400', // Error level and above
  sort: '-created',
);

Get Single Log

final log = await pb.logs.getOne('log_id');
print('${log.message}, ${log.data}');

Get Log Statistics

final stats = await pb.logs.getStats(
  query: {'filter': 'level >= 400'},
);

for (final stat in stats) {
  print('${stat.date}, ${stat.total}');
}

Log Levels

  • 0 - Debug
  • 1 - Info
  • 2 - Warning
  • 3 - Error
  • 4 - Fatal

Send Emails

Note: Email sending is typically handled server-side via hooks or backend code. The SDK doesn’t provide direct email sending methods, but you can trigger email-related operations.

Trigger Email Verification

// Request verification email
await pb.collection('users').requestVerification('user@example.com');

Trigger Password Reset Email

// Request password reset email
await pb.collection('users').requestPasswordReset('user@example.com');

Email Change Request

// Request email change
await pb.collection('users').requestEmailChange('newemail@example.com');

Server-Side Email Sending

Email sending is configured in the backend settings and triggered automatically by:

  • User registration (verification email)
  • Password reset requests
  • Email change requests
  • Custom hooks

To send custom emails, you would typically:

  1. Create a backend hook that uses app.NewMailClient()
  2. Or use the admin API to configure email templates
  3. Or trigger email-related record operations that automatically send emails

Complete Example: Full Application Flow

import 'package:bosbase/bosbase.dart';

final pb = Bosbase('http://localhost:8090');

Future<void> setupApplication() async {
  // 1. Authenticate
  await pb.collection('users').authWithPassword('admin@example.com', 'password');
  
  // 2. Create collection
  final collection = await pb.collections.create(body: {
    'name': 'posts',
    'type': 'base',
    'fields': [
      {'name': 'title', 'type': 'text', 'required': true},
      {'name': 'content', 'type': 'editor'},
      {'name': 'published', 'type': 'bool'},
    ],
  });
  
  // 3. Add more fields
  await pb.collections.addField('posts', {
    'name': 'views',
    'type': 'number',
    'min': 0,
  });
  
  // 4. Create records
  final post = await pb.collection('posts').create(body: {
    'title': 'Hello World',
    'content': 'My first post',
    'published': true,
    'views': 0,
  });
  
  // 5. Query records
  final posts = await pb.collection('posts').getList(
    page: 1,
    perPage: 10,
    filter: 'published = true',
    sort: '-created',
  );
  
  // 6. Update record
  await pb.collection('posts').update(post.id, body: {
    'views': 100,
  });
  
  // 7. Query logs
  final logs = await pb.logs.getList(
    page: 1,
    perPage: 20,
    filter: 'level >= 400',
  );
  
  print('Application setup complete!');
}

void main() {
  setupApplication().catchError(print);
}

Quick Reference

Common Patterns

// Check if authenticated
if (pb.authStore.isValid) { /* ... */ }

// Get current user
final user = pb.authStore.record;

// Refresh auth token
await pb.collection('users').authRefresh();

// Error handling
try {
  await pb.collection('posts').create(body: {'title': 'Test'});
} on ClientException catch (e) {
  if (e.statusCode == 400) {
    print('Validation error: ${e.response}');
  } else if (e.statusCode == 401) {
    print('Not authenticated');
  }
}

Field Types Reference

  • text - Text input
  • number - Numeric value
  • bool - Boolean
  • email - Email address
  • url - URL
  • date - Date
  • select - Single select
  • json - JSON data
  • file - File upload
  • relation - Relation to another collection
  • editor - Rich text editor

Best Practices

  1. Always handle errors: Wrap API calls in try-catch
  2. Check authentication: Verify pb.authStore.isValid before operations
  3. Use pagination: Don’t fetch all records at once for large collections
  4. Validate data: Ensure required fields are provided
  5. Use filters: Filter data on the server, not client-side
  6. Expand relations wisely: Only expand what you need
  7. Handle file uploads: Use MultipartRequest for file fields
  8. Refresh tokens: Use authRefresh() to maintain sessions
  9. Use async/await: Prefer async/await over Future.then() for better readability
  10. Type safety: Use proper type annotations for better IDE support

LangChaingo Examples

Completion

final result = await pb.langchaingo.completions(
  LangChaingoCompletionRequest(
    model: const LangChaingoModelConfig(provider: "openai", model: "gpt-4o-mini"),
    messages: const [
      LangChaingoCompletionMessage(role: "system", content: "Answer in one sentence."),
      LangChaingoCompletionMessage(role: "user", content: "Give a fun Mars fact.")
    ],
  ),
);

print(result.content);

Retrieval-Augmented Answer

final rag = await pb.langchaingo.rag(
  LangChaingoRAGRequest(
    collection: "knowledge-base",
    question: "Why is the sky blue?",
    topK: 3,
    returnSources: true,
  ),
);

print(rag.answer);
for (final source in rag.sources ?? const []) {
  print("${source.score} ${source.metadata}");
}

This guide provides all essential operations for building applications with the BosBase Dart SDK. For more detailed information, refer to the specific API documentation files.