refactor: migrate to monorepo structure with Docker, PocketBase, and e2e tests

- Reorganize into apps/, packages/, docs/, e2e/, pocketbase/ directories
- Add Dockerfiles for web, worker, and PocketBase services
- Add docker-compose.yml for local orchestration
- Add turbo.json for monorepo task management
- Add Playwright e2e test infrastructure
- Add PocketBase backend with migrations
- Remove Vite/Next.js/ESLint/PostCSS config files
- Update package.json with workspace dependencies
- Add .env.example and .dockerignore
This commit is contained in:
2026-07-16 06:19:58 -04:00
parent ec14645a4b
commit 8f55626e03
286 changed files with 31992 additions and 9245 deletions
View File
@@ -0,0 +1,39 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'domains',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'name',
type: 'text',
required: true,
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'color',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'icon',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'sort_order',
type: 'number',
options: {}
})
]),
indexes: [
'CREATE UNIQUE INDEX idx_domains_name ON domains (name)'
]
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('domains');
return app.delete(collection);
}
};
@@ -0,0 +1,29 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'tags',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'name',
type: 'text',
required: true,
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'color',
type: 'text',
options: { maxSize: 255 }
})
]),
indexes: [
'CREATE UNIQUE INDEX idx_tags_name ON tags (name)'
]
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('tags');
return app.delete(collection);
}
};
@@ -0,0 +1,72 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'projects',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'name',
type: 'text',
required: true,
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'description',
type: 'text',
options: {}
}),
app.models.NewSchemaField({
name: 'color',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'icon',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'domain',
type: 'relation',
options: { collectionId: 'domains', maxSelect: 1, cascadeDelete: false }
}),
app.models.NewSchemaField({
name: 'status',
type: 'select',
options: { values: ['active', 'paused', 'archived'], maxSelect: 1 }
}),
app.models.NewSchemaField({
name: 'tags',
type: 'relation',
options: { collectionId: 'tags', maxSelect: 10, cascadeDelete: false }
}),
app.models.NewSchemaField({
name: 'goal',
type: 'text',
options: {}
}),
app.models.NewSchemaField({
name: 'deadline',
type: 'date',
options: {}
}),
app.models.NewSchemaField({
name: 'progress',
type: 'number',
options: { min: 0, max: 100 }
}),
app.models.NewSchemaField({
name: 'members',
type: 'json',
options: {}
})
]),
indexes: []
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('projects');
return app.delete(collection);
}
};
@@ -0,0 +1,79 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'project_settings',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'project_id',
type: 'relation',
required: true,
options: { collectionId: 'projects', maxSelect: 1, cascadeDelete: true }
}),
app.models.NewSchemaField({
name: 'default_priority',
type: 'select',
options: { values: ['low', 'medium', 'high', 'urgent'], maxSelect: 1 }
}),
app.models.NewSchemaField({
name: 'default_domain',
type: 'relation',
options: { collectionId: 'domains', maxSelect: 1, cascadeDelete: false }
}),
app.models.NewSchemaField({
name: 'default_tags',
type: 'relation',
options: { collectionId: 'tags', maxSelect: 10, cascadeDelete: false }
}),
app.models.NewSchemaField({
name: 'auto_archive_completed_after_days',
type: 'number',
options: {}
}),
app.models.NewSchemaField({
name: 'default_view',
type: 'select',
options: { values: ['kanban', 'list'], maxSelect: 1 }
}),
app.models.NewSchemaField({
name: 'habit_reminder_time_default',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'pomodoro_focus_minutes',
type: 'number',
options: { min: 1, max: 120 }
}),
app.models.NewSchemaField({
name: 'pomodoro_break_minutes',
type: 'number',
options: { min: 1, max: 60 }
}),
app.models.NewSchemaField({
name: 'accent_color_override',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'milestone_dependency_enforcement',
type: 'bool',
options: {}
}),
app.models.NewSchemaField({
name: 'custom_fields',
type: 'json',
options: {}
})
]),
indexes: [
'CREATE UNIQUE INDEX idx_project_settings_project_id ON project_settings (project_id)'
]
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('project_settings');
return app.delete(collection);
}
};
@@ -0,0 +1,50 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'milestones',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'title',
type: 'text',
required: true,
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'description',
type: 'text',
options: {}
}),
app.models.NewSchemaField({
name: 'project_id',
type: 'relation',
required: true,
options: { collectionId: 'projects', maxSelect: 1, cascadeDelete: true }
}),
app.models.NewSchemaField({
name: 'status',
type: 'select',
options: { values: ['planned', 'in_progress', 'complete'], maxSelect: 1 }
}),
app.models.NewSchemaField({
name: 'due_date',
type: 'date',
options: {}
}),
app.models.NewSchemaField({
name: 'sort_order',
type: 'number',
options: {}
})
]),
indexes: [
'CREATE INDEX idx_milestones_project_id ON milestones (project_id)'
]
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('milestones');
return app.delete(collection);
}
};
@@ -0,0 +1,31 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'milestone_dependencies',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'milestone_id',
type: 'relation',
required: true,
options: { collectionId: 'milestones', maxSelect: 1, cascadeDelete: true }
}),
app.models.NewSchemaField({
name: 'depends_on_milestone_id',
type: 'relation',
required: true,
options: { collectionId: 'milestones', maxSelect: 1, cascadeDelete: true }
})
]),
indexes: [
'CREATE INDEX idx_milestone_dependencies_milestone_id ON milestone_dependencies (milestone_id)',
'CREATE INDEX idx_milestone_dependencies_depends_on ON milestone_dependencies (depends_on_milestone_id)'
]
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('milestone_dependencies');
return app.delete(collection);
}
};
@@ -0,0 +1,32 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'milestone_templates',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'name',
type: 'text',
required: true,
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'description',
type: 'text',
options: {}
}),
app.models.NewSchemaField({
name: 'milestones',
type: 'json',
options: {}
})
]),
indexes: []
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('milestone_templates');
return app.delete(collection);
}
};
@@ -0,0 +1,44 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'milestone_history',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'milestone_id',
type: 'relation',
required: true,
options: { collectionId: 'milestones', maxSelect: 1, cascadeDelete: true }
}),
app.models.NewSchemaField({
name: 'old_status',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'new_status',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'changed_by',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'changed_at',
type: 'date',
options: {}
})
]),
indexes: [
'CREATE INDEX idx_milestone_history_milestone_id ON milestone_history (milestone_id)'
]
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('milestone_history');
return app.delete(collection);
}
};
@@ -0,0 +1,112 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'tasks',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'title',
type: 'text',
required: true,
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'description',
type: 'text',
options: {}
}),
app.models.NewSchemaField({
name: 'status',
type: 'select',
options: { values: ['todo', 'in_progress', 'done', 'cancelled'], maxSelect: 1 }
}),
app.models.NewSchemaField({
name: 'priority',
type: 'select',
options: { values: ['low', 'medium', 'high', 'urgent'], maxSelect: 1 }
}),
app.models.NewSchemaField({
name: 'due_date',
type: 'date',
options: {}
}),
app.models.NewSchemaField({
name: 'project_id',
type: 'relation',
options: { collectionId: 'projects', maxSelect: 1, cascadeDelete: false }
}),
app.models.NewSchemaField({
name: 'milestone_id',
type: 'relation',
options: { collectionId: 'milestones', maxSelect: 1, cascadeDelete: false }
}),
app.models.NewSchemaField({
name: 'tags',
type: 'relation',
options: { collectionId: 'tags', maxSelect: 10, cascadeDelete: false }
}),
app.models.NewSchemaField({
name: 'domain',
type: 'relation',
options: { collectionId: 'domains', maxSelect: 1, cascadeDelete: false }
}),
app.models.NewSchemaField({
name: 'assignee',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'estimate',
type: 'number',
options: { min: 0 }
}),
app.models.NewSchemaField({
name: 'time_spent',
type: 'number',
options: { min: 0 }
}),
app.models.NewSchemaField({
name: 'recurring_config',
type: 'json',
options: {}
}),
app.models.NewSchemaField({
name: 'attachments',
type: 'json',
options: {}
}),
app.models.NewSchemaField({
name: 'dependencies',
type: 'json',
options: {}
}),
app.models.NewSchemaField({
name: 'subtasks',
type: 'json',
options: {}
}),
app.models.NewSchemaField({
name: 'custom_fields',
type: 'json',
options: {}
}),
app.models.NewSchemaField({
name: 'completed_at',
type: 'date',
options: {}
})
]),
indexes: [
'CREATE INDEX idx_tasks_project_id ON tasks (project_id)',
'CREATE INDEX idx_tasks_milestone_id ON tasks (milestone_id)',
'CREATE INDEX idx_tasks_status ON tasks (status)',
'CREATE INDEX idx_tasks_priority ON tasks (priority)'
]
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('tasks');
return app.delete(collection);
}
};
@@ -0,0 +1,40 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'task_subtasks',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'task_id',
type: 'relation',
required: true,
options: { collectionId: 'tasks', maxSelect: 1, cascadeDelete: true }
}),
app.models.NewSchemaField({
name: 'title',
type: 'text',
required: true,
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'done',
type: 'bool',
options: {}
}),
app.models.NewSchemaField({
name: 'sort_order',
type: 'number',
options: {}
})
]),
indexes: [
'CREATE INDEX idx_task_subtasks_task_id ON task_subtasks (task_id)'
]
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('task_subtasks');
return app.delete(collection);
}
};
@@ -0,0 +1,31 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'task_dependencies',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'task_id',
type: 'relation',
required: true,
options: { collectionId: 'tasks', maxSelect: 1, cascadeDelete: true }
}),
app.models.NewSchemaField({
name: 'depends_on_task_id',
type: 'relation',
required: true,
options: { collectionId: 'tasks', maxSelect: 1, cascadeDelete: true }
})
]),
indexes: [
'CREATE INDEX idx_task_dependencies_task_id ON task_dependencies (task_id)',
'CREATE INDEX idx_task_dependencies_depends_on ON task_dependencies (depends_on_task_id)'
]
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('task_dependencies');
return app.delete(collection);
}
};
@@ -0,0 +1,45 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'task_attachments',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'task_id',
type: 'relation',
required: true,
options: { collectionId: 'tasks', maxSelect: 1, cascadeDelete: true }
}),
app.models.NewSchemaField({
name: 'file',
type: 'file',
required: true,
options: { maxSelect: 1, maxSize: 5242880, mimeTypes: [] }
}),
app.models.NewSchemaField({
name: 'filename',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'mime_type',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'size',
type: 'number',
options: { min: 0 }
})
]),
indexes: [
'CREATE INDEX idx_task_attachments_task_id ON task_attachments (task_id)'
]
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('task_attachments');
return app.delete(collection);
}
};
@@ -0,0 +1,50 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'task_time_entries',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'task_id',
type: 'relation',
required: true,
options: { collectionId: 'tasks', maxSelect: 1, cascadeDelete: true }
}),
app.models.NewSchemaField({
name: 'duration',
type: 'number',
required: true,
options: { min: 0 }
}),
app.models.NewSchemaField({
name: 'started_at',
type: 'date',
options: {}
}),
app.models.NewSchemaField({
name: 'ended_at',
type: 'date',
options: {}
}),
app.models.NewSchemaField({
name: 'source',
type: 'select',
options: { values: ['manual', 'timer', 'pomodoro'], maxSelect: 1 }
}),
app.models.NewSchemaField({
name: 'notes',
type: 'text',
options: {}
})
]),
indexes: [
'CREATE INDEX idx_task_time_entries_task_id ON task_time_entries (task_id)'
]
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('task_time_entries');
return app.delete(collection);
}
};
@@ -0,0 +1,119 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'habits',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'title',
type: 'text',
required: true,
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'description',
type: 'text',
options: {}
}),
app.models.NewSchemaField({
name: 'frequency',
type: 'select',
options: { values: ['daily', 'weekly', 'custom'], maxSelect: 1 }
}),
app.models.NewSchemaField({
name: 'cron_expression',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'target_count',
type: 'number',
options: { min: 1 }
}),
app.models.NewSchemaField({
name: 'streak_current',
type: 'number',
options: { min: 0 }
}),
app.models.NewSchemaField({
name: 'streak_best',
type: 'number',
options: { min: 0 }
}),
app.models.NewSchemaField({
name: 'last_completed_at',
type: 'date',
options: {}
}),
app.models.NewSchemaField({
name: 'project_id',
type: 'relation',
options: { collectionId: 'projects', maxSelect: 1, cascadeDelete: false }
}),
app.models.NewSchemaField({
name: 'tags',
type: 'relation',
options: { collectionId: 'tags', maxSelect: 10, cascadeDelete: false }
}),
app.models.NewSchemaField({
name: 'domain',
type: 'relation',
options: { collectionId: 'domains', maxSelect: 1, cascadeDelete: false }
}),
app.models.NewSchemaField({
name: 'difficulty',
type: 'select',
options: { values: ['easy', 'medium', 'hard'], maxSelect: 1 }
}),
app.models.NewSchemaField({
name: 'mood_tracking',
type: 'bool',
options: {}
}),
app.models.NewSchemaField({
name: 'reminder_times',
type: 'json',
options: {}
}),
app.models.NewSchemaField({
name: 'skip_days',
type: 'json',
options: {}
}),
app.models.NewSchemaField({
name: 'unit',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'goal_per_period',
type: 'number',
options: { min: 0 }
}),
app.models.NewSchemaField({
name: 'score',
type: 'number',
options: { min: 0 }
}),
app.models.NewSchemaField({
name: 'score_config',
type: 'json',
options: {}
}),
app.models.NewSchemaField({
name: 'completion_mode',
type: 'select',
options: { values: ['quick', 'detailed'], maxSelect: 1 }
})
]),
indexes: [
'CREATE INDEX idx_habits_project_id ON habits (project_id)'
]
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('habits');
return app.delete(collection);
}
};
@@ -0,0 +1,46 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'habit_logs',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'habit_id',
type: 'relation',
required: true,
options: { collectionId: 'habits', maxSelect: 1, cascadeDelete: true }
}),
app.models.NewSchemaField({
name: 'date',
type: 'date',
required: true,
options: {}
}),
app.models.NewSchemaField({
name: 'mood',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'quantity',
type: 'number',
options: { min: 0 }
}),
app.models.NewSchemaField({
name: 'notes',
type: 'text',
options: {}
})
]),
indexes: [
'CREATE INDEX idx_habit_logs_habit_id ON habit_logs (habit_id)',
'CREATE INDEX idx_habit_logs_date ON habit_logs (date)'
]
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('habit_logs');
return app.delete(collection);
}
};
@@ -0,0 +1,36 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'habit_skip_days',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'habit_id',
type: 'relation',
required: true,
options: { collectionId: 'habits', maxSelect: 1, cascadeDelete: true }
}),
app.models.NewSchemaField({
name: 'date',
type: 'date',
required: true,
options: {}
}),
app.models.NewSchemaField({
name: 'reason',
type: 'text',
options: {}
})
]),
indexes: [
'CREATE INDEX idx_habit_skip_days_habit_id ON habit_skip_days (habit_id)',
'CREATE INDEX idx_habit_skip_days_date ON habit_skip_days (date)'
]
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('habit_skip_days');
return app.delete(collection);
}
};
@@ -0,0 +1,72 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'notes',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'title',
type: 'text',
required: true,
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'content',
type: 'text',
options: {}
}),
app.models.NewSchemaField({
name: 'domain',
type: 'relation',
options: { collectionId: 'domains', maxSelect: 1, cascadeDelete: false }
}),
app.models.NewSchemaField({
name: 'tags',
type: 'relation',
options: { collectionId: 'tags', maxSelect: 10, cascadeDelete: false }
}),
app.models.NewSchemaField({
name: 'linked_entities',
type: 'json',
options: {}
}),
app.models.NewSchemaField({
name: 'created_by',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'ai_generated',
type: 'bool',
options: {}
}),
app.models.NewSchemaField({
name: 'bookmarked',
type: 'bool',
options: {}
}),
app.models.NewSchemaField({
name: 'pinned',
type: 'bool',
options: {}
}),
app.models.NewSchemaField({
name: 'frontmatter',
type: 'json',
options: {}
}),
app.models.NewSchemaField({
name: 'wikilinks',
type: 'json',
options: {}
})
]),
indexes: []
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('notes');
return app.delete(collection);
}
};
@@ -0,0 +1,36 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'note_links',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'source_note_id',
type: 'relation',
required: true,
options: { collectionId: 'notes', maxSelect: 1, cascadeDelete: true }
}),
app.models.NewSchemaField({
name: 'target_note_id',
type: 'relation',
required: true,
options: { collectionId: 'notes', maxSelect: 1, cascadeDelete: true }
}),
app.models.NewSchemaField({
name: 'link_text',
type: 'text',
options: { maxSize: 255 }
})
]),
indexes: [
'CREATE INDEX idx_note_links_source ON note_links (source_note_id)',
'CREATE INDEX idx_note_links_target ON note_links (target_note_id)'
]
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('note_links');
return app.delete(collection);
}
};
@@ -0,0 +1,36 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'note_task_links',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'note_id',
type: 'relation',
required: true,
options: { collectionId: 'notes', maxSelect: 1, cascadeDelete: true }
}),
app.models.NewSchemaField({
name: 'task_id',
type: 'relation',
required: true,
options: { collectionId: 'tasks', maxSelect: 1, cascadeDelete: true }
}),
app.models.NewSchemaField({
name: 'checkbox_position',
type: 'number',
options: {}
})
]),
indexes: [
'CREATE INDEX idx_note_task_links_note_id ON note_task_links (note_id)',
'CREATE INDEX idx_note_task_links_task_id ON note_task_links (task_id)'
]
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('note_task_links');
return app.delete(collection);
}
};
@@ -0,0 +1,42 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'report_templates',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'name',
type: 'text',
required: true,
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'description',
type: 'text',
options: {}
}),
app.models.NewSchemaField({
name: 'report_type',
type: 'select',
options: { values: ['weekly', 'monthly', 'project', 'habit', 'custom'], maxSelect: 1 }
}),
app.models.NewSchemaField({
name: 'content_template',
type: 'text',
options: {}
}),
app.models.NewSchemaField({
name: 'is_builtin',
type: 'bool',
options: {}
})
]),
indexes: []
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('report_templates');
return app.delete(collection);
}
};
@@ -0,0 +1,72 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'reports',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'title',
type: 'text',
required: true,
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'content',
type: 'text',
options: {}
}),
app.models.NewSchemaField({
name: 'report_type',
type: 'select',
options: { values: ['weekly', 'monthly', 'project', 'habit', 'custom'], maxSelect: 1 }
}),
app.models.NewSchemaField({
name: 'date_range_start',
type: 'date',
options: {}
}),
app.models.NewSchemaField({
name: 'date_range_end',
type: 'date',
options: {}
}),
app.models.NewSchemaField({
name: 'generated_by',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'ai_generated',
type: 'bool',
options: {}
}),
app.models.NewSchemaField({
name: 'linked_entities',
type: 'json',
options: {}
}),
app.models.NewSchemaField({
name: 'domain',
type: 'relation',
options: { collectionId: 'domains', maxSelect: 1, cascadeDelete: false }
}),
app.models.NewSchemaField({
name: 'tags',
type: 'relation',
options: { collectionId: 'tags', maxSelect: 10, cascadeDelete: false }
}),
app.models.NewSchemaField({
name: 'template_id',
type: 'relation',
options: { collectionId: 'report_templates', maxSelect: 1, cascadeDelete: false }
})
]),
indexes: []
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('reports');
return app.delete(collection);
}
};
@@ -0,0 +1,39 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'canvases',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'name',
type: 'text',
required: true,
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'mode',
type: 'select',
options: { values: ['freeform', 'graph'], maxSelect: 1 }
}),
app.models.NewSchemaField({
name: 'project_id',
type: 'relation',
options: { collectionId: 'projects', maxSelect: 1, cascadeDelete: false }
}),
app.models.NewSchemaField({
name: 'state',
type: 'json',
options: {}
})
]),
indexes: [
'CREATE INDEX idx_canvases_project_id ON canvases (project_id)'
]
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('canvases');
return app.delete(collection);
}
};
@@ -0,0 +1,64 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'canvas_cards',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'canvas_id',
type: 'relation',
required: true,
options: { collectionId: 'canvases', maxSelect: 1, cascadeDelete: true }
}),
app.models.NewSchemaField({
name: 'card_type',
type: 'select',
options: { values: ['note', 'task', 'image', 'entity'], maxSelect: 1 }
}),
app.models.NewSchemaField({
name: 'entity_id',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'entity_type',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'position_x',
type: 'number',
options: {}
}),
app.models.NewSchemaField({
name: 'position_y',
type: 'number',
options: {}
}),
app.models.NewSchemaField({
name: 'width',
type: 'number',
options: { min: 1 }
}),
app.models.NewSchemaField({
name: 'height',
type: 'number',
options: { min: 1 }
}),
app.models.NewSchemaField({
name: 'content',
type: 'json',
options: {}
})
]),
indexes: [
'CREATE INDEX idx_canvas_cards_canvas_id ON canvas_cards (canvas_id)'
]
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('canvas_cards');
return app.delete(collection);
}
};
@@ -0,0 +1,65 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'agents',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'name',
type: 'text',
required: true,
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'api_key',
type: 'text',
required: true,
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'avatar',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'description',
type: 'text',
options: {}
}),
app.models.NewSchemaField({
name: 'permission_tier',
type: 'select',
options: { values: ['full_access', 'read_only', 'content_creator', 'task_manager', 'custom'], maxSelect: 1 }
}),
app.models.NewSchemaField({
name: 'custom_permissions',
type: 'json',
options: {}
}),
app.models.NewSchemaField({
name: 'webhook_url',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'last_activity_at',
type: 'date',
options: {}
}),
app.models.NewSchemaField({
name: 'status',
type: 'select',
options: { values: ['active', 'disabled'], maxSelect: 1 }
})
]),
indexes: [
'CREATE UNIQUE INDEX idx_agents_api_key ON agents (api_key)'
]
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('agents');
return app.delete(collection);
}
};
@@ -0,0 +1,59 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'agent_activity',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'agent_id',
type: 'relation',
required: true,
options: { collectionId: 'agents', maxSelect: 1, cascadeDelete: true }
}),
app.models.NewSchemaField({
name: 'action',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'entity_type',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'entity_id',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'before_state',
type: 'json',
options: {}
}),
app.models.NewSchemaField({
name: 'after_state',
type: 'json',
options: {}
}),
app.models.NewSchemaField({
name: 'prompt',
type: 'text',
options: {}
}),
app.models.NewSchemaField({
name: 'response',
type: 'text',
options: {}
})
]),
indexes: [
'CREATE INDEX idx_agent_activity_agent_id ON agent_activity (agent_id)'
]
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('agent_activity');
return app.delete(collection);
}
};
@@ -0,0 +1,43 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'webhooks',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'name',
type: 'text',
required: true,
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'url',
type: 'text',
required: true,
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'events',
type: 'json',
options: {}
}),
app.models.NewSchemaField({
name: 'secret',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'active',
type: 'bool',
options: {}
})
]),
indexes: []
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('webhooks');
return app.delete(collection);
}
};
@@ -0,0 +1,54 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'webhook_deliveries',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'webhook_id',
type: 'relation',
required: true,
options: { collectionId: 'webhooks', maxSelect: 1, cascadeDelete: true }
}),
app.models.NewSchemaField({
name: 'event_type',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'payload',
type: 'json',
options: {}
}),
app.models.NewSchemaField({
name: 'response_status',
type: 'number',
options: {}
}),
app.models.NewSchemaField({
name: 'response_body',
type: 'text',
options: {}
}),
app.models.NewSchemaField({
name: 'success',
type: 'bool',
options: {}
}),
app.models.NewSchemaField({
name: 'attempts',
type: 'number',
options: { min: 0 }
})
]),
indexes: [
'CREATE INDEX idx_webhook_deliveries_webhook_id ON webhook_deliveries (webhook_id)'
]
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('webhook_deliveries');
return app.delete(collection);
}
};
@@ -0,0 +1,50 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'agent_tasks',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'agent_id',
type: 'relation',
required: true,
options: { collectionId: 'agents', maxSelect: 1, cascadeDelete: true }
}),
app.models.NewSchemaField({
name: 'entity_type',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'entity_id',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'instruction',
type: 'text',
options: {}
}),
app.models.NewSchemaField({
name: 'status',
type: 'select',
options: { values: ['pending', 'in_progress', 'completed', 'failed', 'delivery_failed'], maxSelect: 1 }
}),
app.models.NewSchemaField({
name: 'result',
type: 'json',
options: {}
})
]),
indexes: [
'CREATE INDEX idx_agent_tasks_agent_id ON agent_tasks (agent_id)',
'CREATE INDEX idx_agent_tasks_status ON agent_tasks (status)'
]
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('agent_tasks');
return app.delete(collection);
}
};
@@ -0,0 +1,49 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'notifications',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'title',
type: 'text',
required: true,
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'message',
type: 'text',
options: {}
}),
app.models.NewSchemaField({
name: 'type',
type: 'select',
options: { values: ['info', 'success', 'warning', 'error'], maxSelect: 1 }
}),
app.models.NewSchemaField({
name: 'entity_type',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'entity_id',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'read',
type: 'bool',
options: {}
})
]),
indexes: [
'CREATE INDEX idx_notifications_read ON notifications (read)'
]
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('notifications');
return app.delete(collection);
}
};
@@ -0,0 +1,44 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'error_logs',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'code',
type: 'text',
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'message',
type: 'text',
options: {}
}),
app.models.NewSchemaField({
name: 'details',
type: 'json',
options: {}
}),
app.models.NewSchemaField({
name: 'stack_trace',
type: 'text',
options: {}
}),
app.models.NewSchemaField({
name: 'source',
type: 'text',
options: { maxSize: 255 }
})
]),
indexes: [
'CREATE INDEX idx_error_logs_code ON error_logs (code)',
'CREATE INDEX idx_error_logs_source ON error_logs (source)'
]
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('error_logs');
return app.delete(collection);
}
};
@@ -0,0 +1,66 @@
module.exports = {
up: async (app) => {
const collection = new app.models.Collection({
name: 'queue_jobs',
type: 'base',
schema: app.models.NewSchema([
app.models.NewSchemaField({
name: 'job_type',
type: 'text',
required: true,
options: { maxSize: 255 }
}),
app.models.NewSchemaField({
name: 'payload',
type: 'json',
options: {}
}),
app.models.NewSchemaField({
name: 'status',
type: 'select',
options: { values: ['pending', 'in_progress', 'completed', 'failed'], maxSelect: 1 }
}),
app.models.NewSchemaField({
name: 'attempts',
type: 'number',
options: { min: 0 }
}),
app.models.NewSchemaField({
name: 'max_attempts',
type: 'number',
options: { min: 1 }
}),
app.models.NewSchemaField({
name: 'error',
type: 'text',
options: {}
}),
app.models.NewSchemaField({
name: 'scheduled_at',
type: 'date',
options: {}
}),
app.models.NewSchemaField({
name: 'started_at',
type: 'date',
options: {}
}),
app.models.NewSchemaField({
name: 'completed_at',
type: 'date',
options: {}
})
]),
indexes: [
'CREATE INDEX idx_queue_jobs_status ON queue_jobs (status)',
'CREATE INDEX idx_queue_jobs_scheduled_at ON queue_jobs (scheduled_at)',
'CREATE INDEX idx_queue_jobs_job_type ON queue_jobs (job_type)'
]
});
return app.save(collection);
},
down: async (app) => {
const collection = await app.findCollectionByNameOrId('queue_jobs');
return app.delete(collection);
}
};
+498
View File
@@ -0,0 +1,498 @@
// pocketbase/schema.ts
// TypeScript type definitions for PocketBase collections
// Used by API layer for type safety
// ---------------------------------------------------------------------------
// Helper types
// ---------------------------------------------------------------------------
export interface RecurringConfig {
/** RRULE string */
rule: string;
next_due?: string;
}
export interface Attachment {
id: string;
filename: string;
mime_type: string;
size: number;
url: string;
}
export interface Subtask {
id: string;
title: string;
done: boolean;
sort_order: number;
}
/** Base PocketBase record fields present on every collection row. */
export interface BaseRecord {
id: string;
created: string;
updated: string;
collectionId: string;
collectionName: string;
}
// ---------------------------------------------------------------------------
// System collections
// ---------------------------------------------------------------------------
export interface Domain extends BaseRecord {
name: string;
color: string;
icon: string;
sort_order: number;
}
export interface Tag extends BaseRecord {
name: string;
color?: string;
}
// ---------------------------------------------------------------------------
// Project collections
// ---------------------------------------------------------------------------
export interface Project extends BaseRecord {
name: string;
description?: string;
color?: string;
icon?: string;
/** Relation → domains */
domain: string;
status: 'active' | 'paused' | 'archived';
/** Relation → tags (multiple) */
tags: string[];
goal?: string;
deadline?: string;
progress: number;
members?: unknown[];
}
export interface ProjectSettings extends BaseRecord {
/** Relation → projects */
project_id: string;
default_priority?: 'low' | 'medium' | 'high' | 'urgent';
/** Relation → domains */
default_domain?: string;
/** Relation → tags (multiple) */
default_tags?: string[];
auto_archive_completed_after_days?: number;
default_view?: 'kanban' | 'list';
habit_reminder_time_default?: string;
pomodoro_focus_minutes: number;
pomodoro_break_minutes: number;
accent_color_override?: string;
milestone_dependency_enforcement: boolean;
custom_fields?: Record<string, unknown>;
}
export interface Milestone extends BaseRecord {
title: string;
description?: string;
/** Relation → projects */
project_id: string;
status: 'planned' | 'in_progress' | 'complete';
due_date?: string;
sort_order: number;
}
export interface MilestoneDependency extends BaseRecord {
/** Relation → milestones */
milestone_id: string;
/** Relation → milestones */
depends_on_milestone_id: string;
}
export interface MilestoneTemplate extends BaseRecord {
name: string;
description?: string;
milestones: unknown[];
}
export interface MilestoneHistory extends BaseRecord {
/** Relation → milestones */
milestone_id: string;
old_status: string;
new_status: string;
changed_by: string;
changed_at: string;
}
// ---------------------------------------------------------------------------
// Task collections
// ---------------------------------------------------------------------------
export interface Task extends BaseRecord {
title: string;
description?: string;
status: 'todo' | 'in_progress' | 'done' | 'cancelled';
priority: 'low' | 'medium' | 'high' | 'urgent';
due_date?: string;
/** Relation → projects */
project_id?: string;
/** Relation → milestones */
milestone_id?: string;
/** Relation → tags (multiple) */
tags: string[];
/** Relation → domains */
domain: string;
assignee?: string;
estimate?: number;
time_spent: number;
recurring_config?: RecurringConfig;
attachments: Attachment[];
dependencies: string[];
subtasks: Subtask[];
custom_fields?: Record<string, unknown>;
completed_at?: string;
}
export interface TaskSubtask extends BaseRecord {
/** Relation → tasks */
task_id: string;
title: string;
done: boolean;
sort_order: number;
}
export interface TaskDependency extends BaseRecord {
/** Relation → tasks */
task_id: string;
/** Relation → tasks */
depends_on_task_id: string;
}
export interface TaskAttachment extends BaseRecord {
/** Relation → tasks */
task_id: string;
/** File reference */
file: string;
filename?: string;
mime_type?: string;
size?: number;
}
export interface TaskTimeEntry extends BaseRecord {
/** Relation → tasks */
task_id: string;
duration: number;
started_at?: string;
ended_at?: string;
source: 'manual' | 'timer' | 'pomodoro';
notes?: string;
}
// ---------------------------------------------------------------------------
// Habit collections
// ---------------------------------------------------------------------------
export interface Habit extends BaseRecord {
title: string;
description?: string;
frequency: 'daily' | 'weekly' | 'custom';
cron_expression?: string;
target_count: number;
streak_current: number;
streak_best: number;
last_completed_at?: string;
/** Relation → projects */
project_id?: string;
/** Relation → tags (multiple) */
tags: string[];
/** Relation → domains */
domain: string;
difficulty: 'easy' | 'medium' | 'hard';
mood_tracking: boolean;
reminder_times?: string[];
skip_days?: string[];
unit?: string;
goal_per_period?: number;
score: number;
score_config?: Record<string, unknown>;
completion_mode: 'quick' | 'detailed';
}
export interface HabitLog extends BaseRecord {
/** Relation → habits */
habit_id: string;
date: string;
mood?: string;
quantity?: number;
notes?: string;
}
export interface HabitSkipDay extends BaseRecord {
/** Relation → habits */
habit_id: string;
date: string;
reason?: string;
}
// ---------------------------------------------------------------------------
// Note collections
// ---------------------------------------------------------------------------
export interface Note extends BaseRecord {
title: string;
content?: string;
/** Relation → domains */
domain: string;
/** Relation → tags (multiple) */
tags: string[];
linked_entities?: unknown[];
created_by?: string;
ai_generated: boolean;
bookmarked: boolean;
pinned: boolean;
frontmatter?: Record<string, unknown>;
wikilinks?: string[];
}
export interface NoteLink extends BaseRecord {
/** Relation → notes */
source_note_id: string;
/** Relation → notes */
target_note_id: string;
link_text?: string;
}
export interface NoteTaskLink extends BaseRecord {
/** Relation → notes */
note_id: string;
/** Relation → tasks */
task_id: string;
checkbox_position: number;
}
// ---------------------------------------------------------------------------
// Report collections
// ---------------------------------------------------------------------------
export interface ReportTemplate extends BaseRecord {
name: string;
description?: string;
report_type: 'weekly' | 'monthly' | 'project' | 'habit' | 'custom';
content_template?: string;
is_builtin: boolean;
}
export interface Report extends BaseRecord {
title: string;
content?: string;
report_type: 'weekly' | 'monthly' | 'project' | 'habit' | 'custom';
date_range_start?: string;
date_range_end?: string;
generated_by?: string;
ai_generated: boolean;
linked_entities?: unknown[];
/** Relation → domains */
domain: string;
/** Relation → tags (multiple) */
tags: string[];
/** Relation → report_templates */
template_id?: string;
}
// ---------------------------------------------------------------------------
// Canvas collections
// ---------------------------------------------------------------------------
export interface Canvas extends BaseRecord {
name: string;
mode: 'freeform' | 'graph';
/** Relation → projects */
project_id?: string;
state?: Record<string, unknown>;
}
export interface CanvasCard extends BaseRecord {
/** Relation → canvases */
canvas_id: string;
card_type: 'note' | 'task' | 'image' | 'entity';
entity_id?: string;
entity_type?: string;
position_x: number;
position_y: number;
width: number;
height: number;
content?: Record<string, unknown>;
}
// ---------------------------------------------------------------------------
// Agent collections
// ---------------------------------------------------------------------------
export interface Agent extends BaseRecord {
name: string;
api_key: string;
avatar?: string;
description?: string;
permission_tier:
| 'full_access'
| 'read_only'
| 'content_creator'
| 'task_manager'
| 'custom';
custom_permissions?: Record<string, unknown>;
webhook_url?: string;
last_activity_at?: string;
status: 'active' | 'disabled';
}
export interface AgentActivity extends BaseRecord {
/** Relation → agents */
agent_id: string;
action: string;
entity_type: string;
entity_id: string;
before_state?: Record<string, unknown>;
after_state?: Record<string, unknown>;
prompt?: string;
response?: string;
}
// ---------------------------------------------------------------------------
// Webhook collections
// ---------------------------------------------------------------------------
export interface Webhook extends BaseRecord {
name: string;
url: string;
events: string[];
secret?: string;
active: boolean;
}
export interface WebhookDelivery extends BaseRecord {
/** Relation → webhooks */
webhook_id: string;
event_type: string;
payload?: Record<string, unknown>;
response_status?: number;
response_body?: string;
success: boolean;
attempts: number;
}
// ---------------------------------------------------------------------------
// Agent task collection
// ---------------------------------------------------------------------------
export interface AgentTask extends BaseRecord {
/** Relation → agents */
agent_id: string;
entity_type: string;
entity_id: string;
instruction: string;
status: 'pending' | 'in_progress' | 'completed' | 'failed' | 'delivery_failed';
result?: Record<string, unknown>;
}
// ---------------------------------------------------------------------------
// System collections
// ---------------------------------------------------------------------------
export interface Notification extends BaseRecord {
title: string;
message?: string;
type: 'info' | 'success' | 'warning' | 'error';
entity_type?: string;
entity_id?: string;
read: boolean;
}
export interface ErrorLog extends BaseRecord {
code?: string;
message?: string;
details?: Record<string, unknown>;
stack_trace?: string;
source?: string;
}
export interface QueueJob extends BaseRecord {
job_type: string;
payload?: Record<string, unknown>;
status: 'pending' | 'in_progress' | 'completed' | 'failed';
attempts: number;
max_attempts: number;
error?: string;
scheduled_at?: string;
started_at?: string;
completed_at?: string;
}
// ---------------------------------------------------------------------------
// Collection names & type map
// ---------------------------------------------------------------------------
export type CollectionName =
| 'domains'
| 'tags'
| 'projects'
| 'project_settings'
| 'milestones'
| 'milestone_dependencies'
| 'milestone_templates'
| 'milestone_history'
| 'tasks'
| 'task_subtasks'
| 'task_dependencies'
| 'task_attachments'
| 'task_time_entries'
| 'habits'
| 'habit_logs'
| 'habit_skip_days'
| 'notes'
| 'note_links'
| 'note_task_links'
| 'report_templates'
| 'reports'
| 'canvases'
| 'canvas_cards'
| 'agents'
| 'agent_activity'
| 'webhooks'
| 'webhook_deliveries'
| 'agent_tasks'
| 'notifications'
| 'error_logs'
| 'queue_jobs';
/** Maps each PocketBase collection name to its corresponding TypeScript type. */
export interface CollectionTypes {
domains: Domain;
tags: Tag;
projects: Project;
project_settings: ProjectSettings;
milestones: Milestone;
milestone_dependencies: MilestoneDependency;
milestone_templates: MilestoneTemplate;
milestone_history: MilestoneHistory;
tasks: Task;
task_subtasks: TaskSubtask;
task_dependencies: TaskDependency;
task_attachments: TaskAttachment;
task_time_entries: TaskTimeEntry;
habits: Habit;
habit_logs: HabitLog;
habit_skip_days: HabitSkipDay;
notes: Note;
note_links: NoteLink;
note_task_links: NoteTaskLink;
report_templates: ReportTemplate;
reports: Report;
canvases: Canvas;
canvas_cards: CanvasCard;
agents: Agent;
agent_activity: AgentActivity;
webhooks: Webhook;
webhook_deliveries: WebhookDelivery;
agent_tasks: AgentTask;
notifications: Notification;
error_logs: ErrorLog;
queue_jobs: QueueJob;
}