| 1 | const NEW_TASK_DRAFT_PREFIX = "new-task:"; |
| 2 | |
| 3 | /** Every new-task draft key: `new-task:<draftId>`. */ |
| 4 | export function newTaskDraftKey(draftId: string): string { |
| 5 | return `${NEW_TASK_DRAFT_PREFIX}${draftId}`; |
| 6 | } |
| 7 | |
| 8 | export function isNewTaskDraftKey(draftKey: string): boolean { |
| 9 | return draftKey.startsWith(NEW_TASK_DRAFT_PREFIX); |
| 10 | } |
| 11 | |
| 12 | /** |
| 13 | * The draft a rejected queued task's content is restored into. The outbox |
| 14 | * drain writes it and the thread screen's "Edit task" action opens it, so both |
| 15 | * sides derive the key here rather than rebuilding the string. |
| 16 | */ |
| 17 | export function restoredNewTaskDraftKey(messageId: string): string { |
| 18 | return newTaskDraftKey(`restored-${messageId}`); |
| 19 | } |
| 20 | |
| 21 | /** |
| 22 | * Builds before drafts were id-keyed used `new-task:<environmentId>:<projectId>`, |
| 23 | * one slot per project. Ids are UUIDs and never contain a colon, so a colon |
| 24 | * after the prefix marks the legacy shape. Returns the split scope, or null |
| 25 | * when the key is not legacy. |
| 26 | */ |
| 27 | export function parseLegacyNewTaskDraftKey( |
| 28 | draftKey: string, |
| 29 | ): { readonly environmentId: string; readonly projectId: string } | null { |
| 30 | if (!isNewTaskDraftKey(draftKey)) { |
| 31 | return null; |
| 32 | } |
| 33 | const scope = draftKey.slice(NEW_TASK_DRAFT_PREFIX.length); |
| 34 | const separator = scope.lastIndexOf(":"); |
| 35 | if (separator <= 0 || separator === scope.length - 1) { |
| 36 | return null; |
| 37 | } |
| 38 | return { environmentId: scope.slice(0, separator), projectId: scope.slice(separator + 1) }; |
| 39 | } |