166 lines
5.6 KiB
TypeScript
166 lines
5.6 KiB
TypeScript
/**
|
|
* Migration: rTime TasksDoc → rTasks BoardDoc + WeavingDoc
|
|
*
|
|
* Reads all {space}:rtime:tasks docs and:
|
|
* 1. Creates TaskItem entries in the rTasks board (reusing Task.id)
|
|
* 2. Creates WeavingOverlay entries in the WeavingDoc
|
|
* 3. Moves connections + execStates from TasksDoc → WeavingDoc
|
|
*
|
|
* Usage: npx tsx scripts/migrate-rtime-tasks.ts [space]
|
|
* Default space: demo
|
|
*
|
|
* This script must be run on the server where Automerge data lives.
|
|
* It operates on the SyncServer data files directly.
|
|
*/
|
|
|
|
import * as Automerge from '@automerge/automerge';
|
|
import type { SyncServer } from '../server/local-first/sync-server';
|
|
import {
|
|
tasksDocId, weavingDocId, weavingSchema,
|
|
} from '../modules/rtime/schemas';
|
|
import type { TasksDoc, WeavingDoc, Task, Connection, ExecState } from '../modules/rtime/schemas';
|
|
import { boardDocId, createTaskItem, boardSchema } from '../modules/rtasks/schemas';
|
|
import type { BoardDoc, TaskItem } from '../modules/rtasks/schemas';
|
|
|
|
const space = process.argv[2] || 'demo';
|
|
|
|
console.log(`[migrate] Starting rTime → rTasks migration for space: ${space}`);
|
|
|
|
/**
|
|
* Standalone migration logic. Call with a SyncServer instance.
|
|
* This is exported so it can also be called from server startup if needed.
|
|
*/
|
|
export function migrateRTimeTasks(syncServer: SyncServer, spaceSlug: string): {
|
|
tasksCreated: number;
|
|
overlaysCreated: number;
|
|
connectionsMoved: number;
|
|
execStatesMoved: number;
|
|
} {
|
|
const result = { tasksCreated: 0, overlaysCreated: 0, connectionsMoved: 0, execStatesMoved: 0 };
|
|
|
|
// 1. Read legacy TasksDoc
|
|
const oldDocId = tasksDocId(spaceSlug);
|
|
const oldDoc = syncServer.getDoc<TasksDoc>(oldDocId);
|
|
if (!oldDoc) {
|
|
console.log(`[migrate] No TasksDoc found at ${oldDocId}, nothing to migrate.`);
|
|
return result;
|
|
}
|
|
|
|
const oldTasks = Object.values(oldDoc.tasks || {});
|
|
const oldConnections = Object.values(oldDoc.connections || {});
|
|
const oldExecStates = Object.values(oldDoc.execStates || {});
|
|
|
|
if (oldTasks.length === 0 && oldConnections.length === 0) {
|
|
console.log(`[migrate] TasksDoc is empty, nothing to migrate.`);
|
|
return result;
|
|
}
|
|
|
|
console.log(`[migrate] Found ${oldTasks.length} tasks, ${oldConnections.length} connections, ${oldExecStates.length} exec states`);
|
|
|
|
// 2. Ensure rTasks board exists
|
|
const bDocId = boardDocId(spaceSlug, spaceSlug);
|
|
let boardDoc = syncServer.getDoc<BoardDoc>(bDocId);
|
|
if (!boardDoc) {
|
|
boardDoc = Automerge.change(Automerge.init<BoardDoc>(), 'init board for migration', (d) => {
|
|
const init = boardSchema.init();
|
|
Object.assign(d, init);
|
|
d.meta.spaceSlug = spaceSlug;
|
|
d.board.id = spaceSlug;
|
|
d.board.slug = spaceSlug;
|
|
d.board.name = `${spaceSlug} Board`;
|
|
});
|
|
syncServer.setDoc(bDocId, boardDoc);
|
|
console.log(`[migrate] Created rTasks board: ${bDocId}`);
|
|
}
|
|
|
|
// 3. Create TaskItem for each rTime Task (reuse ID)
|
|
for (const task of oldTasks) {
|
|
if (boardDoc.tasks[task.id]) {
|
|
console.log(`[migrate] Task ${task.id} already exists in board, skipping.`);
|
|
continue;
|
|
}
|
|
const taskItem = createTaskItem(task.id, spaceSlug, task.name, {
|
|
description: task.description || '',
|
|
});
|
|
syncServer.changeDoc<BoardDoc>(bDocId, `migrate task: ${task.name}`, (d) => {
|
|
d.tasks[task.id] = taskItem as any;
|
|
});
|
|
result.tasksCreated++;
|
|
}
|
|
|
|
// 4. Ensure WeavingDoc exists
|
|
const wDocId = weavingDocId(spaceSlug);
|
|
let wDoc = syncServer.getDoc<WeavingDoc>(wDocId);
|
|
if (!wDoc) {
|
|
wDoc = Automerge.change(Automerge.init<WeavingDoc>(), 'init weaving for migration', (d) => {
|
|
const init = weavingSchema.init();
|
|
Object.assign(d, init);
|
|
d.meta.spaceSlug = spaceSlug;
|
|
d.boardSlug = spaceSlug;
|
|
});
|
|
syncServer.setDoc(wDocId, wDoc);
|
|
}
|
|
|
|
// 5. Create WeavingOverlay for each task (with needs, notes, links)
|
|
let canvasX = 300;
|
|
for (const task of oldTasks) {
|
|
if (wDoc.weavingOverlays[task.id]) {
|
|
console.log(`[migrate] Overlay for ${task.id} already exists, skipping.`);
|
|
continue;
|
|
}
|
|
syncServer.changeDoc<WeavingDoc>(wDocId, `migrate overlay: ${task.name}`, (d) => {
|
|
d.weavingOverlays[task.id] = {
|
|
rtasksId: task.id,
|
|
needs: task.needs || {},
|
|
canvasX,
|
|
canvasY: 150,
|
|
notes: task.notes || '',
|
|
links: task.links || [],
|
|
intentFrameId: task.intentFrameId,
|
|
} as any;
|
|
});
|
|
canvasX += 280;
|
|
result.overlaysCreated++;
|
|
}
|
|
|
|
// 6. Move connections from TasksDoc → WeavingDoc
|
|
for (const conn of oldConnections) {
|
|
syncServer.changeDoc<WeavingDoc>(wDocId, `migrate connection: ${conn.id}`, (d) => {
|
|
if (!d.connections[conn.id]) {
|
|
d.connections[conn.id] = { ...conn } as any;
|
|
result.connectionsMoved++;
|
|
}
|
|
});
|
|
}
|
|
|
|
// 7. Move execStates from TasksDoc → WeavingDoc
|
|
for (const es of oldExecStates) {
|
|
syncServer.changeDoc<WeavingDoc>(wDocId, `migrate exec state: ${es.taskId}`, (d) => {
|
|
if (!d.execStates[es.taskId]) {
|
|
d.execStates[es.taskId] = { ...es } as any;
|
|
result.execStatesMoved++;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Refresh doc after changes
|
|
const finalW = syncServer.getDoc<WeavingDoc>(wDocId)!;
|
|
result.connectionsMoved = Object.keys(finalW.connections).length;
|
|
result.execStatesMoved = Object.keys(finalW.execStates).length;
|
|
|
|
console.log(`[migrate] Migration complete:
|
|
Tasks created in rTasks: ${result.tasksCreated}
|
|
Weaving overlays created: ${result.overlaysCreated}
|
|
Connections moved: ${result.connectionsMoved}
|
|
Exec states moved: ${result.execStatesMoved}`);
|
|
|
|
return result;
|
|
}
|
|
|
|
// CLI entry point — only runs when executed directly
|
|
if (process.argv[1]?.includes('migrate-rtime-tasks')) {
|
|
console.log('[migrate] This script must be imported and called with a SyncServer instance.');
|
|
console.log('[migrate] Example: import { migrateRTimeTasks } from "./scripts/migrate-rtime-tasks";');
|
|
console.log('[migrate] migrateRTimeTasks(syncServer, "demo");');
|
|
}
|