Files
stack/packages/tasks/src/create-recovery.mjs

44 lines
2.3 KiB
JavaScript

import { board, walk, normal } from './sync.mjs';
import { checkTask, ref, taskFields, digest } from './digest.mjs';
import { outcome } from './vikunja.mjs';
import { createMarker, matchesCreate, createDigest } from '../../bus/src/broker.mjs';
// Host/adapter only. All tracker I/O here uses the read-only sync authority. A complete negative
// scan is evidence of uncertainty, never permission to issue a replacement create.
export async function reconcileCreate({ ctx, receipt }) {
const p = receipt.body;
const wait = () => {
ctx.broker.recordCreateWait({ receipt, code: 'task-create-uncertain' });
return { state: 'uncertain', code: 'task-create-uncertain' };
};
const committed = ctx.broker.taskCreateResult(receipt);
if (committed) return { state: 'committed', task_ref: committed };
try {
const view = ctx.tree?.get(p.project);
if (!view || createDigest(view) !== createDigest(p.view)) return wait();
// An id-keyed complete walk includes done tasks and cannot accept a truncated page set.
const tasks = await walk(ctx, '', {}, p.project);
const marker = createMarker(p);
// Count all occurrences of this operation, including edited suffixes and foreign creators.
// A duplicate with changed fields must not make an otherwise valid candidate unique.
const operation = `Mosaic operation: ${p.op}`;
const hits = tasks.filter(t => (t.description ?? '').split('\n').some(line => line === operation || line.startsWith(operation + ';')));
if (hits.length !== 1 || !hits[0].description.endsWith(marker)) return wait();
const r = await ctx.sync('GET', `/tasks/${hits[0].id}`);
if (outcome(r) !== 'ok') return wait();
const task = r.json;
checkTask(task);
if (task.id !== hits[0].id || task.project_id !== p.project) return wait();
const bucket = task.done ? view.done : (await board(ctx, p.project, view)).get(task.id)?.bucket;
const fields = taskFields(task, bucket);
if (!matchesCreate(p, fields, task.created_by?.id)) return wait();
const task_ref = ref(p.project, task.id);
ctx.broker.recordRecoveredCreate({ receipt, task_ref, snapshot: {
task_ref, updated: normal(task.updated), etag: r.etag ?? null, digest: digest(fields), fields, creator: task.created_by.id,
} });
return { state: 'committed', task_ref };
} catch {
return wait();
}
}