timsoft_os 0.1.2
timsoft_os: ^0.1.2 copied to clipboard
TIMSoft Platform OS — Backend-less Application Engine. Local-first database with Sync, Auth, Realtime, RBAC, and Admin tools inside a single SDK. No external server required.
example/main.dart
// example/main.dart
// ─────────────────────────────────────────────────────────────────────────────
// TIMSoft Platform OS — Complete Todo Application Example
// ─────────────────────────────────────────────────────────────────────────────
// Demonstrates: boot, auth, RBAC, create/read/update/delete, live query,
// pagination, validation, change log, and sync.
//
// Run: dart run example/main.dart
// ─────────────────────────────────────────────────────────────────────────────
import 'package:timsoft_os/timsoft_os.dart';
// ── Entity ────────────────────────────────────────────────────────────────────
enum TodoPriority { low, medium, high }
@TimEntity(collection: 'todos', syncable: true)
class Todo {
final String? id;
final String title;
final String? description;
final bool done;
final TodoPriority priority;
final List<String> tags;
const Todo({
this.id,
required this.title,
this.description,
this.done = false,
this.priority = TodoPriority.medium,
this.tags = const [],
});
Map<String, Object?> toMap() => {
'title': title,
'description': description,
'done': done,
'priority': priority.name,
'tags': tags,
};
factory Todo.fromMap(Map<String, Object?> m) => Todo(
id: m['__id'] as String?,
title: m['title'] as String,
description: m['description'] as String?,
done: (m['done'] as bool?) ?? false,
priority: TodoPriority.values.firstWhere(
(p) => p.name == m['priority'],
orElse: () => TodoPriority.medium,
),
tags: ((m['tags'] as List?) ?? []).cast<String>(),
);
@override
String toString() =>
'Todo(${done ? "✅" : "⬜"} [$priority] $title'
'${tags.isNotEmpty ? " #${tags.join(" #")}" : ""})';
}
// ── Main ──────────────────────────────────────────────────────────────────────
Future<void> main() async {
print('═══════════════════════════════════════════');
print(' TIMSoft Platform OS — Todo App Example ');
print('═══════════════════════════════════════════\n');
// ── 1. Boot ─────────────────────────────────────────────────────────────────
print('▶ Booting platform...');
// Setup JWT provider with a user
final jwtProvider = JwtAuthProvider(
secret: 'my-super-secret-key-change-in-prod',
tokenTtl: const Duration(hours: 2),
);
jwtProvider.registerUser(
email: 'admin@timsoft.dz',
password: 'admin123',
roles: {'admin', 'user'},
displayName: 'TIMSoft Admin',
);
// RBAC: admin can do everything, user can read+write todos
final policy = RbacPolicy([
Role('admin', [Permission.fullAccess('*')]),
Role('user', [
Permission('todos', canRead: true, canWrite: true, canDelete: true),
]),
]);
final boot = await TimPlatform.boot(
authProvider: jwtProvider,
rbac: policy,
conflictResolver: const FieldMergeResolver(),
config: PlatformConfig.development,
);
boot
..onSuccess((_) => print(' ✅ Platform booted\n'))
..onFailure((e) {
print(' ❌ Boot failed: $e');
return;
});
final tim = TimPlatform.instance;
// ── 2. Authenticate ──────────────────────────────────────────────────────────
print('▶ Signing in...');
final authResult = await tim.auth.signIn({
'email': 'admin@timsoft.dz',
'password': 'admin123',
});
authResult
..onSuccess((s) => print(' ✅ Signed in as ${s.email} (roles: ${s.roles})\n'))
..onFailure((e) => print(' ❌ Auth failed: $e'));
// ── 3. Create repository ─────────────────────────────────────────────────────
final todos = tim.collection<Todo>(
'todos',
toMap: (t) => t.toMap(),
fromMap: Todo.fromMap,
validator: ValidatorChain<Todo>(validators: [
(t) => t.title.trim().isEmpty ? 'Title cannot be empty' : null,
(t) => t.title.length > 200 ? 'Title too long (max 200 chars)' : null,
]),
);
// ── 4. Live Query ────────────────────────────────────────────────────────────
print('▶ Starting live query...');
final liveQ = tim.liveQuery<Todo>(
'todos',
fromMap: Todo.fromMap,
query: const Query()
.orderBy('__createdAt', descending: true)
.take(5),
);
final liveSub = liveQ.watch().listen((result) {
if (!result.isLoading) {
print(' 📡 Live: ${result.items.length} todo(s) in view');
}
});
// ── 5. CREATE ────────────────────────────────────────────────────────────────
print('\n▶ Creating todos...');
await Future.delayed(const Duration(milliseconds: 50));
final t1 = await todos.create(Todo(
title: 'Build TIMSoft Platform OS',
priority: TodoPriority.high,
tags: ['dart', 'platform'],
));
t1.onSuccess((t) => print(' ✅ Created: $t'));
final t2 = await todos.create(Todo(
title: 'Write comprehensive tests',
priority: TodoPriority.high,
tags: ['testing'],
));
final t3 = await todos.create(Todo(
title: 'Publish to pub.dev',
description: 'Run dart pub publish',
priority: TodoPriority.medium,
tags: ['release', 'dart'],
));
final t4 = await todos.create(Todo(
title: 'Build Flutter UI package',
priority: TodoPriority.low,
tags: ['flutter', 'ui'],
));
// Test validation
print('\n▶ Testing validator...');
final invalid = await todos.create(Todo(title: '', priority: TodoPriority.low));
invalid.onFailure((e) => print(' ✅ Validation blocked: ${e.message}'));
// ── 6. READ ──────────────────────────────────────────────────────────────────
print('\n▶ Querying todos...');
// All todos
final all = await todos.findMany();
all.onSuccess((list) => print(' 📋 All: ${list.length} todos'));
// Filter by priority
final highPrio = await todos.findMany(
const Query().whereEq('priority', 'high').orderBy('__createdAt'),
);
highPrio.onSuccess((list) {
print(' 🔴 High priority: ${list.length}');
for (final t in list) print(' $t');
});
// Search by title
final search = await todos.findMany(
const Query().whereContains('title', 'dart'),
);
search.onSuccess((list) => print(' 🔍 Contains "dart": ${list.length}'));
// Count
final cnt = await todos.count();
cnt.onSuccess((n) => print(' 📊 Total count: $n'));
// ── 7. UPDATE ────────────────────────────────────────────────────────────────
print('\n▶ Updating first todo...');
final firstId = t1.value.id!;
final updated = await todos.update(firstId, Todo(
title: 'Build TIMSoft Platform OS',
done: true,
priority: TodoPriority.high,
tags: ['dart', 'platform', 'completed'],
));
updated.onSuccess((t) => print(' ✅ Updated: $t'));
// PATCH — only change price
print('\n▶ Patching second todo...');
final patched = await todos.patch(t2.value.id!, {'done': true});
patched.onSuccess((t) => print(' ✅ Patched: $t'));
// ── 8. FILTER: done vs undone ─────────────────────────────────────────────────
print('\n▶ Done vs undone...');
final done = await todos.findMany(const Query().whereEq('done', true));
final undone = await todos.findMany(const Query().whereEq('done', false));
done.onSuccess((l) => print(' ✅ Done: ${l.length}'));
undone.onSuccess((l) => print(' ⬜ Undone: ${l.length}'));
// ── 9. DELETE ────────────────────────────────────────────────────────────────
print('\n▶ Deleting last todo (soft delete)...');
final del = await todos.delete(t4.value.id!);
del.onSuccess((_) => print(' 🗑️ Soft deleted'));
final afterDel = await todos.count();
afterDel.onSuccess((n) => print(' 📊 Remaining: $n'));
// ── 10. CHANGE LOG ────────────────────────────────────────────────────────────
print('\n▶ Change log...');
print(' 📝 Total entries: ${tim.pendingChanges} pending');
print(' 📝 All entries: ${tim.changeLog.length}');
for (final entry in tim.changeLog.take(3)) {
print(' ${entry.op.name} → ${entry.recordId.substring(0, 12)}... '
'[${entry.syncState.name}]');
}
// ── 11. SYNC ─────────────────────────────────────────────────────────────────
print('\n▶ Running sync cycle (NoOp adapter)...');
final syncResult = await tim.syncNow();
syncResult.onSuccess((s) => print(
' 🔄 Sync: pushed=${s.pushed}, pulled=${s.pulled}, '
'conflicts=${s.conflicts}, deadLetters=${s.deadLetters}'));
// ── 12. ADMIN SPEC ────────────────────────────────────────────────────────────
print('\n▶ Generating admin spec...');
const todoSchema = CollectionSchema(
collection: 'todos',
label: 'Tasks',
fields: [
FieldSchema(
name: 'title',
type: FieldType.string,
constraints: FieldConstraints(required: true, minLength: 1),
listColumn: true,
searchable: true,
label: 'Title',
),
FieldSchema(
name: 'done',
type: FieldType.boolean,
listColumn: true,
label: 'Done',
),
FieldSchema(
name: 'priority',
type: FieldType.enumValue,
enumValues: ['low', 'medium', 'high'],
listColumn: true,
label: 'Priority',
),
FieldSchema(
name: 'tags',
type: FieldType.list,
label: 'Tags',
),
],
);
final generator = const DashboardGenerator(title: 'TIMSoft Todo Admin');
final spec = generator.generate([todoSchema]);
print(' 🎛️ Collections: ${spec.collections.length}');
print(' 🎛️ Title: ${spec.title}');
// ── 13. DIAGNOSTICS ───────────────────────────────────────────────────────────
print('\n▶ Platform diagnostics:');
final diag = tim.diagnostics();
diag.forEach((k, v) => print(' $k: $v'));
// ── Cleanup ───────────────────────────────────────────────────────────────────
await liveSub.cancel();
await tim.shutdown();
print('\n═══════════════════════════════════════════');
print(' ✅ Example complete!');
print('═══════════════════════════════════════════');
}