create-lego-one 2.0.12 → 2.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/index.cjs +150 -15
  2. package/dist/index.cjs.map +1 -1
  3. package/package.json +1 -1
  4. package/template/.cursor/rules/rules.mdc +639 -0
  5. package/template/.dockerignore +58 -0
  6. package/template/.env.example +18 -0
  7. package/template/.eslintignore +5 -0
  8. package/template/.eslintrc.js +28 -0
  9. package/template/.prettierignore +6 -0
  10. package/template/.prettierrc +11 -0
  11. package/template/CLAUDE.md +634 -0
  12. package/template/Dockerfile +67 -0
  13. package/template/PROMPT.md +457 -0
  14. package/template/README.md +325 -0
  15. package/template/docker-compose.yml +48 -0
  16. package/template/docker-entrypoint.sh +23 -0
  17. package/template/docs/checkpoints/.template.md +64 -0
  18. package/template/docs/checkpoints/framework/01-infrastructure-setup.md +132 -0
  19. package/template/docs/checkpoints/framework/02-pocketbase-setup.md +155 -0
  20. package/template/docs/checkpoints/framework/03-host-kernel.md +170 -0
  21. package/template/docs/checkpoints/framework/04-auth-system.md +163 -0
  22. package/template/docs/checkpoints/framework/phase-05-multitenancy-rbac.md +223 -0
  23. package/template/docs/checkpoints/framework/phase-06-ui-components.md +260 -0
  24. package/template/docs/checkpoints/framework/phase-07-communication-system.md +276 -0
  25. package/template/docs/checkpoints/framework/phase-08-plugin-system.md +91 -0
  26. package/template/docs/checkpoints/framework/phase-09-dashboard-plugin.md +111 -0
  27. package/template/docs/checkpoints/framework/phase-10-todo-plugin.md +169 -0
  28. package/template/docs/checkpoints/framework/phase-11-testing.md +264 -0
  29. package/template/docs/checkpoints/framework/phase-12-deployment.md +294 -0
  30. package/template/docs/checkpoints/framework/phase-13-documentation.md +312 -0
  31. package/template/docs/framework/plans/00-index.md +164 -0
  32. package/template/docs/framework/plans/01-infrastructure-setup.md +855 -0
  33. package/template/docs/framework/plans/02-pocketbase-setup.md +1374 -0
  34. package/template/docs/framework/plans/03-host-kernel.md +1518 -0
  35. package/template/docs/framework/plans/04-auth-system.md +1466 -0
  36. package/template/docs/framework/plans/05-multitenancy-rbac.md +1527 -0
  37. package/template/docs/framework/plans/06-ui-components.md +1478 -0
  38. package/template/docs/framework/plans/07-communication-system.md +1106 -0
  39. package/template/docs/framework/plans/08-plugin-system.md +1179 -0
  40. package/template/docs/framework/plans/09-dashboard-plugin.md +1137 -0
  41. package/template/docs/framework/plans/10-todo-plugin.md +1343 -0
  42. package/template/docs/framework/plans/11-testing.md +935 -0
  43. package/template/docs/framework/plans/12-deployment.md +896 -0
  44. package/template/docs/framework/prompts/0-boilerplate-modernjs.md +151 -0
  45. package/template/docs/framework/research/00-modernjs-audit.md +488 -0
  46. package/template/docs/framework/research/01-system-blueprint.md +721 -0
  47. package/template/docs/framework/research/02-data-migration-protocol.md +699 -0
  48. package/template/docs/framework/research/03-host-setup.md +714 -0
  49. package/template/docs/framework/research/04-plugin-architecture.md +645 -0
  50. package/template/docs/framework/research/05-slot-injection-pattern.md +671 -0
  51. package/template/docs/framework/research/06-cli-strategy.md +615 -0
  52. package/template/docs/framework/research/07-deployment.md +629 -0
  53. package/template/docs/framework/research/README.md +282 -0
  54. package/template/docs/framework/setup/00-index.md +210 -0
  55. package/template/docs/framework/setup/01-framework-structure.md +308 -0
  56. package/template/docs/framework/setup/02-development-workflow.md +405 -0
  57. package/template/docs/framework/setup/03-environment-setup.md +215 -0
  58. package/template/docs/framework/setup/04-kernel-architecture.md +499 -0
  59. package/template/docs/framework/setup/05-plugin-system.md +620 -0
  60. package/template/docs/framework/setup/06-communication-patterns.md +451 -0
  61. package/template/docs/framework/setup/07-plugin-development.md +582 -0
  62. package/template/docs/framework/setup/08-component-library.md +658 -0
  63. package/template/docs/framework/setup/09-data-integration.md +609 -0
  64. package/template/docs/framework/setup/10-auth-rbac.md +497 -0
  65. package/template/docs/framework/setup/11-hooks-api.md +393 -0
  66. package/template/docs/framework/setup/12-components-api.md +665 -0
  67. package/template/docs/framework/setup/13-deployment-guide.md +566 -0
  68. package/template/docs/framework/setup/README.md +548 -0
  69. package/template/host/package.json +1 -1
  70. package/template/nginx.conf +72 -0
  71. package/template/package.json +1 -1
  72. package/template/packages/plugins/@lego/plugin-dashboard/package.json +1 -1
  73. package/template/packages/plugins/@lego/plugin-todo/package.json +1 -1
  74. package/template/pocketbase/CHANGELOG.md +911 -0
  75. package/template/pocketbase/LICENSE.md +17 -0
  76. package/template/scripts/create-plugin.js +221 -0
  77. package/template/scripts/deploy.sh +56 -0
  78. package/template/tsconfig.base.json +26 -0
@@ -0,0 +1,699 @@
1
+ # Data Migration Protocol: PocketBase Auto-Schema
2
+
3
+ **Project:** Lego-One (Modern.js SaaS OS)
4
+ **Document:** 02 - Data Migration Protocol
5
+ **Status:** Research Phase
6
+
7
+ ## Executive Summary
8
+
9
+ This document defines the **Migration Protocol** for automatically synchronizing PocketBase collections when plugins are enabled. Since PocketBase lacks traditional SQL migrations, we use a **startup schema check** pattern that leverages the PocketBase Admin API to programmatically create collections.
10
+
11
+ ---
12
+
13
+ ## 1. Problem Statement
14
+
15
+ ### 1.1 The Challenge
16
+
17
+ PocketBase manages collections through:
18
+ 1. **Dashboard UI** - Manual creation/modification
19
+ 2. **Admin API** - Programmatic operations (superuser only)
20
+ 3. **JavaScript Hooks** - Server-side extensions
21
+
22
+ **Problem:** Each plugin requires specific database collections, but:
23
+ - Users shouldn't manually create collections via dashboard
24
+ - Plugins must be "drop-in" - work immediately when enabled
25
+ - Schema versioning is needed for plugin updates
26
+
27
+ ### 1.2 Requirements
28
+
29
+ | Requirement | Description |
30
+ |-------------|-------------|
31
+ | **Auto-Discovery** | Host detects enabled plugins from `saas.config.ts` |
32
+ | **Schema Check** | Verify required collections exist before app starts |
33
+ | **Auto-Creation** | Create missing collections programmatically |
34
+ | **Idempotency** | Safe to run multiple times without errors |
35
+ | **Versioning** | Support schema migrations for plugin updates |
36
+ | **Multi-Tenancy** | Apply PocketBase API Rules for Row Level Security |
37
+
38
+ ---
39
+
40
+ ## 2. Migration Architecture
41
+
42
+ ### 2.1 Startup Protocol Flow
43
+
44
+ ```
45
+ ┌─────────────────────────────────────────────────────────────────────────┐
46
+ │ Host Application Boot │
47
+ └─────────────────────────────────────────────────────────────────────────┘
48
+
49
+
50
+ ┌─────────────────────────────────────────────────────────────────────────┐
51
+ │ 1. Read saas.config.ts │
52
+ │ - Parse enabled plugins list │
53
+ │ - Load plugin.config.ts from each plugin │
54
+ └──────┬──────────────────────────────────────────────────────────────────┘
55
+
56
+
57
+ ┌─────────────────────────────────────────────────────────────────────────┐
58
+ │ 2. Initialize PocketBase Admin Client │
59
+ │ - Load admin credentials from env vars │
60
+ │ - Authenticate as superuser │
61
+ └──────┬──────────────────────────────────────────────────────────────────┘
62
+
63
+
64
+ ┌─────────────────────────────────────────────────────────────────────────┐
65
+ │ 3. For each enabled plugin: │
66
+ │ a. Load migration files from plugin/migrations/ │
67
+ │ b. Check if collection exists ($app.findCollectionByNameOrId) │
68
+ │ c. If missing, create collection ($app.save(new Collection(...))) │
69
+ │ d. If exists, check schema version, migrate if needed │
70
+ └──────┬──────────────────────────────────────────────────────────────────┘
71
+
72
+
73
+ ┌─────────────────────────────────────────────────────────────────────────┐
74
+ │ 4. Store migration version in _lego_migrations table │
75
+ │ - Track which migrations have been applied │
76
+ │ - Prevent re-running same migrations │
77
+ └──────┬──────────────────────────────────────────────────────────────────┘
78
+
79
+
80
+ ┌─────────────────────────────────────────────────────────────────────────┐
81
+ │ 5. Proceed to app initialization │
82
+ │ - All required collections guaranteed to exist │
83
+ │ - Plugins can safely query their collections │
84
+ └─────────────────────────────────────────────────────────────────────────┘
85
+ ```
86
+
87
+ ### 2.2 Migration File Structure
88
+
89
+ ```
90
+ packages/plugins/@lego/plugin-inventory/
91
+ ├── migrations/
92
+ │ ├── 001_initial.ts # Initial schema
93
+ │ ├── 002_add_categories.ts # Feature addition
94
+ │ └── 003_add_suppliers.ts # Another feature
95
+ ├── plugin.config.ts
96
+ └── src/
97
+ ```
98
+
99
+ ---
100
+
101
+ ## 3. Implementation
102
+
103
+ ### 3.1 Migration System Library
104
+
105
+ **File:** `host/src/kernel/migration-system.ts`
106
+
107
+ ```typescript
108
+ import PocketBase from 'pocketbase';
109
+ import { Collection } from 'pocketbase';
110
+
111
+ // ============================================================================
112
+ // Types
113
+ // ============================================================================
114
+
115
+ export interface Migration {
116
+ version: number;
117
+ name: string;
118
+ collection: string;
119
+ up: () => Collection;
120
+ down?: () => void;
121
+ }
122
+
123
+ export interface PluginMigrations {
124
+ pluginName: string;
125
+ migrations: Migration[];
126
+ }
127
+
128
+ export interface MigrationRecord {
129
+ id: string;
130
+ pluginName: string;
131
+ collection: string;
132
+ version: number;
133
+ executedAt: string;
134
+ }
135
+
136
+ // ============================================================================
137
+ // Migration Runner
138
+ // ============================================================================
139
+
140
+ export class MigrationSystem {
141
+ private pb: PocketBase;
142
+
143
+ constructor(adminUrl: string, adminEmail: string, adminPassword: string) {
144
+ this.pb = new PocketBase(adminUrl);
145
+ }
146
+
147
+ async initialize(): Promise<void> {
148
+ // Authenticate as admin
149
+ await this.pb.admins.authWithPassword(
150
+ process.env.VITE_POCKETBASE_ADMIN_EMAIL!,
151
+ process.env.VITE_POCKETBASE_ADMIN_PASSWORD!
152
+ );
153
+
154
+ // Ensure migrations tracking table exists
155
+ await this.ensureMigrationsTable();
156
+ }
157
+
158
+ private async ensureMigrationsTable(): Promise<void> {
159
+ // Check if _lego_migrations collection exists
160
+ const existing = await this.pb.collections.getList(1, 1, {
161
+ filter: `name = '_lego_migrations'`,
162
+ });
163
+
164
+ if (existing.items.length === 0) {
165
+ // Create the migrations tracking collection
166
+ const migrationCollection = new Collection({
167
+ name: '_lego_migrations',
168
+ type: 'base',
169
+ listRule: null, // No list access via API
170
+ viewRule: null, // No view access via API
171
+ createRule: null, // No create access via API
172
+ updateRule: null, // No update access via API
173
+ deleteRule: null, // No delete access via API
174
+ fields: [
175
+ {
176
+ name: 'pluginName',
177
+ type: 'text',
178
+ required: true,
179
+ },
180
+ {
181
+ name: 'collection',
182
+ type: 'text',
183
+ required: true,
184
+ },
185
+ {
186
+ name: 'version',
187
+ type: 'number',
188
+ required: true,
189
+ },
190
+ {
191
+ name: 'executedAt',
192
+ type: 'autodate',
193
+ required: true,
194
+ },
195
+ ],
196
+ indexes: [
197
+ 'CREATE UNIQUE INDEX idx_migrations_plugin_collection_version ON _lego_migrations (pluginName, collection, version)',
198
+ ],
199
+ });
200
+
201
+ await this.executeAdminRequest('/api/collections', 'POST', migrationCollection);
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Check if a migration has already been executed
207
+ */
208
+ private async isExecuted(pluginName: string, collection: string, version: number): Promise<boolean> {
209
+ try {
210
+ const records = await this.pb.records.getList('_lego_migrations', 1, 1, {
211
+ filter: `pluginName = "${pluginName}" && collection = "${collection}" && version = ${version}`,
212
+ });
213
+ return records.totalItems > 0;
214
+ } catch {
215
+ return false;
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Record a migration as executed
221
+ */
222
+ private async recordMigration(pluginName: string, collection: string, version: number): Promise<void> {
223
+ await this.pb.records.create('_lego_migrations', {
224
+ pluginName,
225
+ collection,
226
+ version,
227
+ });
228
+ }
229
+
230
+ /**
231
+ * Execute a PocketBase Admin API request
232
+ * Note: Collection operations require superuser privileges
233
+ */
234
+ private async executeAdminRequest(endpoint: string, method: string, body?: any): Promise<any> {
235
+ const url = `${this.pb.baseUrl}${endpoint}`;
236
+
237
+ const response = await fetch(url, {
238
+ method,
239
+ headers: {
240
+ 'Content-Type': 'application/json',
241
+ 'Authorization': this.pb.authStore.token,
242
+ },
243
+ body: body ? JSON.stringify(body) : undefined,
244
+ });
245
+
246
+ if (!response.ok) {
247
+ const error = await response.text();
248
+ throw new Error(`Admin API request failed: ${error}`);
249
+ }
250
+
251
+ return response.json();
252
+ }
253
+
254
+ /**
255
+ * Check if a collection exists
256
+ */
257
+ async collectionExists(name: string): Promise<boolean> {
258
+ try {
259
+ // Try to fetch via Admin API
260
+ await this.executeAdminRequest(`/api/collections/${name}`, 'GET');
261
+ return true;
262
+ } catch {
263
+ return false;
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Create a new collection from a migration
269
+ */
270
+ async createCollection(collection: Collection): Promise<void> {
271
+ await this.executeAdminRequest('/api/collections', 'POST', collection);
272
+ }
273
+
274
+ /**
275
+ * Update an existing collection
276
+ */
277
+ async updateCollection(id: string, collection: Partial<Collection>): Promise<void> {
278
+ await this.executeAdminRequest(`/api/collections/${id}`, 'PATCH', collection);
279
+ }
280
+
281
+ /**
282
+ * Run migrations for a plugin
283
+ */
284
+ async runMigrations(pluginMigrations: PluginMigrations): Promise<void> {
285
+ const { pluginName, migrations } = pluginMigrations;
286
+
287
+ // Sort migrations by version
288
+ const sortedMigrations = migrations.sort((a, b) => a.version - b.version);
289
+
290
+ for (const migration of sortedMigrations) {
291
+ const executed = await this.isExecuted(pluginName, migration.collection, migration.version);
292
+
293
+ if (!executed) {
294
+ console.log(`[Migration] Running ${pluginName}/${migration.name} (v${migration.version})`);
295
+
296
+ // Execute the migration
297
+ const collection = migration.up();
298
+
299
+ // Check if collection exists
300
+ const exists = await this.collectionExists(migration.collection);
301
+
302
+ if (exists) {
303
+ // Update existing collection
304
+ const existing = await this.executeAdminRequest(`/api/collections/${migration.collection}`, 'GET');
305
+ await this.updateCollection(existing.id, collection);
306
+ } else {
307
+ // Create new collection
308
+ await this.createCollection(collection);
309
+ }
310
+
311
+ // Record as executed
312
+ await this.recordMigration(pluginName, migration.collection, migration.version);
313
+
314
+ console.log(`[Migration] Completed ${pluginName}/${migration.name}`);
315
+ } else {
316
+ console.log(`[Migration] Skipped ${pluginName}/${migration.name} (already executed)`);
317
+ }
318
+ }
319
+ }
320
+
321
+ /**
322
+ * Run all pending migrations for multiple plugins
323
+ */
324
+ async runAllMigrations(allMigrations: PluginMigrations[]): Promise<void> {
325
+ for (const pluginMigrations of allMigrations) {
326
+ await this.runMigrations(pluginMigrations);
327
+ }
328
+ }
329
+ }
330
+ ```
331
+
332
+ ### 3.2 Plugin Migration Definition
333
+
334
+ **File:** `packages/plugins/@lego/plugin-inventory/migrations/001_initial.ts`
335
+
336
+ ```typescript
337
+ import { Collection } from 'pocketbase';
338
+ import { BoolField, NumberField, TextField, RelationField } from 'pocketbase';
339
+
340
+ export const version = 1;
341
+ export const name = 'initial';
342
+
343
+ export function up(): Collection {
344
+ return new Collection({
345
+ type: 'base',
346
+ name: 'inventory_items',
347
+ // Multi-tenancy: Users can only see their own items
348
+ listRule: '@request.auth.id != "" && owner = @request.auth.id',
349
+ viewRule: '@request.auth.id != "" && owner = @request.auth.id',
350
+ createRule: '@request.auth.id != ""',
351
+ updateRule: '@request.auth.id != "" && owner = @request.auth.id',
352
+ deleteRule: '@request.auth.id != "" && owner = @request.auth.id',
353
+ fields: [
354
+ {
355
+ name: 'name',
356
+ type: 'text',
357
+ required: true,
358
+ max: 200,
359
+ },
360
+ {
361
+ name: 'description',
362
+ type: 'text',
363
+ required: false,
364
+ max: 1000,
365
+ },
366
+ {
367
+ name: 'quantity',
368
+ type: 'number',
369
+ required: true,
370
+ min: 0,
371
+ },
372
+ {
373
+ name: 'price',
374
+ type: 'number',
375
+ required: true,
376
+ min: 0,
377
+ },
378
+ {
379
+ name: 'owner',
380
+ type: 'relation',
381
+ required: true,
382
+ maxSelect: 1,
383
+ collectionId: '_pb_users_auth_',
384
+ cascadeDelete: true,
385
+ },
386
+ ],
387
+ indexes: [
388
+ 'CREATE INDEX idx_inventory_owner ON inventory_items (owner)',
389
+ ],
390
+ });
391
+ }
392
+ ```
393
+
394
+ **File:** `packages/plugins/@lego/plugin-inventory/migrations/002_add_categories.ts`
395
+
396
+ ```typescript
397
+ import { Collection } from 'pocketbase';
398
+ import { SelectField } from 'pocketbase';
399
+
400
+ export const version = 2;
401
+ export const name = 'add_categories';
402
+
403
+ export function up(): Collection {
404
+ return new Collection({
405
+ type: 'base',
406
+ name: 'inventory_items',
407
+ // Preserve existing rules
408
+ listRule: '@request.auth.id != "" && owner = @request.auth.id',
409
+ viewRule: '@request.auth.id != "" && owner = @request.auth.id',
410
+ createRule: '@request.auth.id != ""',
411
+ updateRule: '@request.auth.id != "" && owner = @request.auth.id',
412
+ deleteRule: '@request.auth.id != "" && owner = @request.auth.id',
413
+ fields: [
414
+ // ... existing fields from 001_initial ...
415
+ {
416
+ name: 'name',
417
+ type: 'text',
418
+ required: true,
419
+ max: 200,
420
+ },
421
+ {
422
+ name: 'quantity',
423
+ type: 'number',
424
+ required: true,
425
+ min: 0,
426
+ },
427
+ {
428
+ name: 'price',
429
+ type: 'number',
430
+ required: true,
431
+ min: 0,
432
+ },
433
+ {
434
+ name: 'owner',
435
+ type: 'relation',
436
+ required: true,
437
+ maxSelect: 1,
438
+ collectionId: '_pb_users_auth_',
439
+ cascadeDelete: true,
440
+ },
441
+ // NEW FIELD
442
+ {
443
+ name: 'category',
444
+ type: 'select',
445
+ required: false,
446
+ values: ['electronics', 'clothing', 'food', 'other'],
447
+ },
448
+ ],
449
+ indexes: [
450
+ 'CREATE INDEX idx_inventory_owner ON inventory_items (owner)',
451
+ 'CREATE INDEX idx_inventory_category ON inventory_items (category)',
452
+ ],
453
+ });
454
+ }
455
+ ```
456
+
457
+ ### 3.3 Plugin Config: Expose Migrations
458
+
459
+ **File:** `packages/plugins/@lego/plugin-inventory/plugin.config.ts`
460
+
461
+ ```typescript
462
+ import { definePluginConfig } from '@lego/kernel/plugin-config';
463
+
464
+ export default definePluginConfig({
465
+ name: '@lego/plugin-inventory',
466
+ version: '1.0.0',
467
+ displayName: 'Inventory Management',
468
+ description: 'Track inventory items and stock levels',
469
+
470
+ // Expose migrations to host
471
+ migrations: {
472
+ collection: 'inventory_items',
473
+ files: [
474
+ './migrations/001_initial',
475
+ './migrations/002_add_categories',
476
+ ],
477
+ },
478
+
479
+ // ... other config ...
480
+ });
481
+ ```
482
+
483
+ ### 3.4 Host: Load and Run Migrations
484
+
485
+ **File:** `host/src/kernel/migration-loader.ts`
486
+
487
+ ```typescript
488
+ import { MigrationSystem, PluginMigrations } from './migration-system';
489
+ import { loadSaaSConfig } from './plugin-loader';
490
+
491
+ export async function loadAndRunMigrations(): Promise<void> {
492
+ // Initialize migration system
493
+ const migrationSystem = new MigrationSystem(
494
+ import.meta.env.VITE_POCKETBASE_URL,
495
+ import.meta.env.VITE_POCKETBASE_ADMIN_EMAIL,
496
+ import.meta.env.VITE_POCKETBASE_ADMIN_PASSWORD
497
+ );
498
+
499
+ await migrationSystem.initialize();
500
+
501
+ // Load enabled plugins
502
+ const enabledPlugins = await loadSaaSConfig();
503
+
504
+ // Load migrations from each plugin
505
+ const allMigrations: PluginMigrations[] = [];
506
+
507
+ for (const plugin of enabledPlugins) {
508
+ try {
509
+ // Dynamically import plugin config
510
+ const pluginConfig = await import(/* @vite-ignore */ `${plugin.entry}/plugin.config.ts`);
511
+
512
+ if (pluginConfig.default?.migrations) {
513
+ const migrations = [];
514
+
515
+ for (const migrationFile of pluginConfig.default.migrations.files) {
516
+ const migrationModule = await import(/* @vite-ignore */ migrationFile);
517
+ migrations.push({
518
+ version: migrationModule.version,
519
+ name: migrationModule.name,
520
+ collection: pluginConfig.default.migrations.collection,
521
+ up: migrationModule.up,
522
+ });
523
+ }
524
+
525
+ allMigrations.push({
526
+ pluginName: plugin.name,
527
+ migrations,
528
+ });
529
+ }
530
+ } catch (error) {
531
+ console.warn(`[Migration] Could not load migrations for ${plugin.name}:`, error);
532
+ }
533
+ }
534
+
535
+ // Run all pending migrations
536
+ await migrationSystem.runAllMigrations(allMigrations);
537
+ }
538
+ ```
539
+
540
+ ### 3.5 Host: Run Migrations on Bootstrap
541
+
542
+ **File:** `host/src/bootstrap.tsx`
543
+
544
+ ```typescript
545
+ import { StrictMode } from 'react';
546
+ import ReactDOM from 'react-dom';
547
+ import App from './App';
548
+ import { loadAndRunMigrations } from './kernel/migration-loader';
549
+ import { registerSharedState } from './kernel/shared-state-bridge';
550
+ import './styles/globals.css';
551
+
552
+ async function bootstrap() {
553
+ // 1. Run database migrations first
554
+ console.log('[Boot] Running database migrations...');
555
+ try {
556
+ await loadAndRunMigrations();
557
+ console.log('[Boot] Migrations complete');
558
+ } catch (error) {
559
+ console.error('[Boot] Migration failed:', error);
560
+ // You might want to block app startup or show error UI
561
+ return;
562
+ }
563
+
564
+ // 2. Register shared state bridge
565
+ registerSharedState();
566
+
567
+ // 3. Mount React app
568
+ ReactDOM.createRoot(document.getElementById('root')!).render(
569
+ <StrictMode>
570
+ <App />
571
+ </StrictMode>
572
+ );
573
+ }
574
+
575
+ bootstrap();
576
+ ```
577
+
578
+ ---
579
+
580
+ ## 4. PocketBase API Rules for Multi-Tenancy
581
+
582
+ ### 4.1 Row Level Security Pattern
583
+
584
+ All plugin collections must follow this pattern:
585
+
586
+ ```typescript
587
+ const collection = new Collection({
588
+ name: 'plugin_data',
589
+ // Users can only list their own records
590
+ listRule: '@request.auth.id != "" && owner = @request.auth.id',
591
+ // Users can only view their own records
592
+ viewRule: '@request.auth.id != "" && owner = @request.auth.id',
593
+ // Authenticated users can create
594
+ createRule: '@request.auth.id != ""',
595
+ // Users can only update their own records
596
+ updateRule: '@request.auth.id != "" && owner = @request.auth.id',
597
+ // Users can only delete their own records
598
+ deleteRule: '@request.auth.id != "" && owner = @request.auth.id',
599
+ fields: [
600
+ // ...
601
+ {
602
+ name: 'owner',
603
+ type: 'relation',
604
+ required: true,
605
+ maxSelect: 1,
606
+ collectionId: '_pb_users_auth_',
607
+ },
608
+ ],
609
+ });
610
+ ```
611
+
612
+ ### 4.2 Admin-Only Collections
613
+
614
+ For settings that apply across all users (app-level config):
615
+
616
+ ```typescript
617
+ const adminCollection = new Collection({
618
+ name: 'plugin_settings',
619
+ // Only admins can access
620
+ listRule: 'id = @request.auth.id && @collection.permissions = "admin"',
621
+ viewRule: 'id = @request.auth.id && @collection.permissions = "admin"',
622
+ createRule: 'id = @request.auth.id && @collection.permissions = "admin"',
623
+ updateRule: 'id = @request.auth.id && @collection.permissions = "admin"',
624
+ deleteRule: 'id = @request.auth.id && @collection.permissions = "admin"',
625
+ fields: [ /* ... */ ],
626
+ });
627
+ ```
628
+
629
+ ---
630
+
631
+ ## 5. Best Practices
632
+
633
+ ### 5.1 Migration Guidelines
634
+
635
+ | Practice | Description |
636
+ |----------|-------------|
637
+ | **Version Numbers** | Use sequential integers (001, 002, 003...) |
638
+ | **Idempotency** | Migrations should be safe to re-run |
639
+ | **Backwards Compatible** | New migrations shouldn't break existing data |
640
+ | **Test Locally** | Test migrations against a fresh PocketBase instance |
641
+ | **Document Changes** | Add comments explaining schema changes |
642
+
643
+ ### 5.2 Error Handling
644
+
645
+ ```typescript
646
+ // Always wrap migration execution in try-catch
647
+ try {
648
+ await loadAndRunMigrations();
649
+ } catch (error) {
650
+ if (error.message.includes('authentication')) {
651
+ showErrorDialog('Database authentication failed. Check admin credentials.');
652
+ } else if (error.message.includes('collection')) {
653
+ showErrorDialog('Failed to create collection. Check migration syntax.');
654
+ } else {
655
+ showErrorDialog('Migration failed. Check console for details.');
656
+ }
657
+ throw error; // Prevent app from starting with invalid schema
658
+ }
659
+ ```
660
+
661
+ ### 5.3 Development vs Production
662
+
663
+ **Development:**
664
+ ```bash
665
+ # Start PocketBase with fresh data
666
+ pocketbase serve --dev
667
+ ```
668
+
669
+ **Production:**
670
+ - Back up PocketBase data directory before migrations
671
+ - Test migrations in staging first
672
+ - Keep `_lego_migrations` table for audit trail
673
+
674
+ ---
675
+
676
+ ## 6. Alternative Approach: PB_MIGRATE Hook
677
+
678
+ For production deployments, consider using PocketBase's **migrate** hook:
679
+
680
+ **File:** `pocketbase/migrations/migrate.go` (Go - not covered in our TypeScript-only stack)
681
+
682
+ Since we're using a **TypeScript-only** approach with the Admin API, the startup protocol is preferred over Go hooks.
683
+
684
+ ---
685
+
686
+ ## 7. Next Steps
687
+
688
+ 1. **`03-host-setup.md`**: Initialize the Modern.js host app
689
+ 2. **`04-plugin-architecture.md`**: Plugin development patterns
690
+ 3. **`05-slot-injection-pattern.md`**: UI extension system
691
+
692
+ ---
693
+
694
+ ## References
695
+
696
+ - [PocketBase JS Collections Documentation](https://pocketbase.io/docs/js-collections/)
697
+ - [PocketBase API Rules Guide](https://pocketbase.io/docs/collections/)
698
+ - [PocketBase Admin API](https://pocketbase.io/docs/api-collections/)
699
+ - [GitHub Discussion: Programmatically Create Collections](https://github.com/pocketbase/pocketbase/discussions/890)