@critical-path/core 0.11.0 → 0.12.0

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 (46) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +35 -0
  3. package/dist/deliverables.test.d.ts +2 -0
  4. package/dist/deliverables.test.d.ts.map +1 -0
  5. package/dist/deliverables.test.js +232 -0
  6. package/dist/deliverables.test.js.map +1 -0
  7. package/dist/domain/entities.d.ts +25 -1
  8. package/dist/domain/entities.d.ts.map +1 -1
  9. package/dist/domain/entities.js +153 -0
  10. package/dist/domain/entities.js.map +1 -1
  11. package/dist/domain/events.d.ts +31 -3
  12. package/dist/domain/events.d.ts.map +1 -1
  13. package/dist/domain/events.js.map +1 -1
  14. package/dist/engine/index.d.ts +8 -1
  15. package/dist/engine/index.d.ts.map +1 -1
  16. package/dist/engine/index.js +164 -1
  17. package/dist/engine/index.js.map +1 -1
  18. package/dist/store/firebase.d.ts +6 -1
  19. package/dist/store/firebase.d.ts.map +1 -1
  20. package/dist/store/firebase.js +41 -0
  21. package/dist/store/firebase.js.map +1 -1
  22. package/dist/store/index.d.ts +15 -2
  23. package/dist/store/index.d.ts.map +1 -1
  24. package/dist/store/index.js +38 -0
  25. package/dist/store/index.js.map +1 -1
  26. package/dist/store/sqlite.d.ts +7 -1
  27. package/dist/store/sqlite.d.ts.map +1 -1
  28. package/dist/store/sqlite.js +94 -5
  29. package/dist/store/sqlite.js.map +1 -1
  30. package/dist/types/index.d.ts +33 -1
  31. package/dist/types/index.d.ts.map +1 -1
  32. package/dist/utils/workflow.d.ts +1 -0
  33. package/dist/utils/workflow.d.ts.map +1 -1
  34. package/dist/utils/workflow.js +37 -0
  35. package/dist/utils/workflow.js.map +1 -1
  36. package/package.json +1 -1
  37. package/src/deliverables.test.ts +284 -0
  38. package/src/domain/entities.ts +185 -2
  39. package/src/domain/events.ts +28 -3
  40. package/src/engine/index.ts +188 -2
  41. package/src/store/firebase.ts +47 -1
  42. package/src/store/index.ts +54 -1
  43. package/src/store/sqlite.ts +135 -4
  44. package/src/types/index.ts +51 -1
  45. package/src/utils/workflow.ts +38 -0
  46. package/tsconfig.tsbuildinfo +1 -1
@@ -12,7 +12,9 @@ import type {
12
12
  Activity,
13
13
  Webhook,
14
14
  TaskDependency,
15
- Workflow
15
+ Workflow,
16
+ Deliverable,
17
+ CreateDeliverableInput
16
18
  } from '../types/index.js';
17
19
  import { generateProjectKey } from '../utils/key.js';
18
20
 
@@ -116,6 +118,7 @@ export class SQLiteStore implements StorageAdapter {
116
118
  iterationId TEXT,
117
119
  teamId TEXT,
118
120
  containerId TEXT,
121
+ deliverableId TEXT,
119
122
  plannedStartDate TEXT,
120
123
  actualStartDate TEXT,
121
124
  actualEndDate TEXT,
@@ -135,6 +138,24 @@ export class SQLiteStore implements StorageAdapter {
135
138
  updatedAt TEXT NOT NULL
136
139
  );
137
140
 
141
+ CREATE TABLE IF NOT EXISTS deliverables (
142
+ id TEXT PRIMARY KEY,
143
+ projectId TEXT NOT NULL,
144
+ title TEXT NOT NULL,
145
+ description TEXT,
146
+ status TEXT NOT NULL,
147
+ format TEXT,
148
+ specs TEXT,
149
+ leadId TEXT,
150
+ reviewerId TEXT,
151
+ dueDate TEXT,
152
+ deliveredAt TEXT,
153
+ outputUrls TEXT,
154
+ customFields TEXT,
155
+ createdAt TEXT NOT NULL,
156
+ updatedAt TEXT NOT NULL
157
+ );
158
+
138
159
  CREATE TABLE IF NOT EXISTS iterations (
139
160
  id TEXT PRIMARY KEY,
140
161
  projectId TEXT NOT NULL,
@@ -226,6 +247,12 @@ export class SQLiteStore implements StorageAdapter {
226
247
  } catch {
227
248
  // Column may already exist
228
249
  }
250
+
251
+ try {
252
+ this.db.exec('ALTER TABLE tasks ADD COLUMN deliverableId TEXT');
253
+ } catch {
254
+ // Column may already exist
255
+ }
229
256
  }
230
257
 
231
258
  // --- Projects ---
@@ -503,6 +530,98 @@ export class SQLiteStore implements StorageAdapter {
503
530
  return (result.changes ?? 0) > 0;
504
531
  }
505
532
 
533
+ // --- Deliverables ---
534
+ async getDeliverables(projectId: string): Promise<Deliverable[]> {
535
+ const stmt = this.db.prepare('SELECT * FROM deliverables WHERE projectId = ?');
536
+ const rows = stmt.all(projectId) as any[];
537
+ return rows.map((r) => this.mapDeliverable(r));
538
+ }
539
+
540
+ async getDeliverable(id: string): Promise<Deliverable | null> {
541
+ const stmt = this.db.prepare('SELECT * FROM deliverables WHERE id = ?');
542
+ const row = stmt.get(id) as any;
543
+ return row ? this.mapDeliverable(row) : null;
544
+ }
545
+
546
+ async createDeliverable(deliverable: CreateDeliverableInput): Promise<Deliverable> {
547
+ const id = `deliv_${Math.random().toString(36).substring(2, 9)}`;
548
+ const now = new Date().toISOString();
549
+ const newDeliverable: Deliverable = {
550
+ ...deliverable,
551
+ id,
552
+ status: deliverable.status || 'planned',
553
+ outputUrls: deliverable.outputUrls ? [...deliverable.outputUrls] : [],
554
+ customFields: deliverable.customFields ? { ...deliverable.customFields } : {},
555
+ createdAt: now,
556
+ updatedAt: now
557
+ };
558
+
559
+ const stmt = this.db.prepare(`
560
+ INSERT INTO deliverables (
561
+ id, projectId, title, description, status, format, specs, leadId, reviewerId,
562
+ dueDate, deliveredAt, outputUrls, customFields, createdAt, updatedAt
563
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
564
+ `);
565
+ stmt.run(
566
+ newDeliverable.id,
567
+ newDeliverable.projectId,
568
+ newDeliverable.title,
569
+ newDeliverable.description || null,
570
+ newDeliverable.status,
571
+ newDeliverable.format || null,
572
+ newDeliverable.specs ? JSON.stringify(newDeliverable.specs) : null,
573
+ newDeliverable.leadId || null,
574
+ newDeliverable.reviewerId || null,
575
+ newDeliverable.dueDate || null,
576
+ newDeliverable.deliveredAt || null,
577
+ JSON.stringify(newDeliverable.outputUrls || []),
578
+ JSON.stringify(newDeliverable.customFields || {}),
579
+ newDeliverable.createdAt,
580
+ newDeliverable.updatedAt
581
+ );
582
+ return newDeliverable;
583
+ }
584
+
585
+ async updateDeliverable(id: string, updates: Partial<Deliverable>): Promise<Deliverable | null> {
586
+ const existing = await this.getDeliverable(id);
587
+ if (!existing) return null;
588
+
589
+ const updated: Deliverable = {
590
+ ...existing,
591
+ ...updates,
592
+ updatedAt: new Date().toISOString()
593
+ };
594
+
595
+ const stmt = this.db.prepare(`
596
+ UPDATE deliverables SET
597
+ title = ?, description = ?, status = ?, format = ?, specs = ?, leadId = ?, reviewerId = ?,
598
+ dueDate = ?, deliveredAt = ?, outputUrls = ?, customFields = ?, updatedAt = ?
599
+ WHERE id = ?
600
+ `);
601
+ stmt.run(
602
+ updated.title,
603
+ updated.description || null,
604
+ updated.status,
605
+ updated.format || null,
606
+ updated.specs ? JSON.stringify(updated.specs) : null,
607
+ updated.leadId || null,
608
+ updated.reviewerId || null,
609
+ updated.dueDate || null,
610
+ updated.deliveredAt || null,
611
+ JSON.stringify(updated.outputUrls || []),
612
+ JSON.stringify(updated.customFields || {}),
613
+ updated.updatedAt,
614
+ id
615
+ );
616
+ return updated;
617
+ }
618
+
619
+ async deleteDeliverable(id: string): Promise<boolean> {
620
+ const stmt = this.db.prepare('DELETE FROM deliverables WHERE id = ?');
621
+ const result = stmt.run(id);
622
+ return (result.changes ?? 0) > 0;
623
+ }
624
+
506
625
  // --- Tasks ---
507
626
  async getTasks(projectId?: string): Promise<Task[]> {
508
627
  let rows: any[];
@@ -530,11 +649,11 @@ export class SQLiteStore implements StorageAdapter {
530
649
  const stmt = this.db.prepare(`
531
650
  INSERT INTO tasks (
532
651
  id, projectId, title, description, status, priority, taskType, assigneeId, reporterId,
533
- reviewerId, iterationId, teamId, containerId, plannedStartDate, actualStartDate, actualEndDate,
652
+ reviewerId, iterationId, teamId, containerId, deliverableId, plannedStartDate, actualStartDate, actualEndDate,
534
653
  dueDate, estimatedHours, loggedHours, actualHours, billableHours,
535
654
  estimatedDurationMinutes, actualDurationMinutes, billableDurationMinutes, progress,
536
655
  tags, customFields, parentId, createdAt, updatedAt
537
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
656
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
538
657
  `);
539
658
  stmt.run(
540
659
  newTask.id,
@@ -550,6 +669,7 @@ export class SQLiteStore implements StorageAdapter {
550
669
  newTask.iterationId || null,
551
670
  newTask.teamId || null,
552
671
  newTask.containerId || null,
672
+ newTask.deliverableId || null,
553
673
  newTask.plannedStartDate || null,
554
674
  newTask.actualStartDate || null,
555
675
  newTask.actualEndDate || null,
@@ -584,7 +704,7 @@ export class SQLiteStore implements StorageAdapter {
584
704
  const stmt = this.db.prepare(`
585
705
  UPDATE tasks SET
586
706
  projectId = ?, title = ?, description = ?, status = ?, priority = ?, taskType = ?,
587
- assigneeId = ?, reporterId = ?, reviewerId = ?, iterationId = ?, teamId = ?, containerId = ?,
707
+ assigneeId = ?, reporterId = ?, reviewerId = ?, iterationId = ?, teamId = ?, containerId = ?, deliverableId = ?,
588
708
  plannedStartDate = ?, actualStartDate = ?, actualEndDate = ?, dueDate = ?,
589
709
  estimatedHours = ?, loggedHours = ?, actualHours = ?, billableHours = ?,
590
710
  estimatedDurationMinutes = ?, actualDurationMinutes = ?, billableDurationMinutes = ?, progress = ?,
@@ -604,6 +724,7 @@ export class SQLiteStore implements StorageAdapter {
604
724
  updated.iterationId || null,
605
725
  updated.teamId || null,
606
726
  updated.containerId || null,
727
+ updated.deliverableId || null,
607
728
  updated.plannedStartDate || null,
608
729
  updated.actualStartDate || null,
609
730
  updated.actualEndDate || null,
@@ -1059,8 +1180,18 @@ export class SQLiteStore implements StorageAdapter {
1059
1180
  return {
1060
1181
  ...row,
1061
1182
  taskType: row.taskType || undefined,
1183
+ deliverableId: row.deliverableId || undefined,
1062
1184
  tags: row.tags ? JSON.parse(row.tags) : [],
1063
1185
  customFields: row.customFields ? JSON.parse(row.customFields) : {}
1064
1186
  };
1065
1187
  }
1188
+
1189
+ private mapDeliverable(row: any): Deliverable {
1190
+ return {
1191
+ ...row,
1192
+ specs: row.specs ? JSON.parse(row.specs) : undefined,
1193
+ outputUrls: row.outputUrls ? JSON.parse(row.outputUrls) : [],
1194
+ customFields: row.customFields ? JSON.parse(row.customFields) : {}
1195
+ };
1196
+ }
1066
1197
  }
@@ -142,6 +142,7 @@ export interface Task {
142
142
  iterationId?: string;
143
143
  teamId?: string;
144
144
  containerId?: string;
145
+ deliverableId?: string; // Direct link to parent deliverable
145
146
  plannedStartDate?: string;
146
147
  actualStartDate?: string;
147
148
  actualEndDate?: string;
@@ -169,6 +170,50 @@ export type CreateTaskInput = Omit<Task, 'id' | 'createdAt' | 'updatedAt' | 'sta
169
170
  priority?: Priority;
170
171
  };
171
172
 
173
+ export type DeliverableStatus =
174
+ | 'draft'
175
+ | 'planned'
176
+ | 'in_production'
177
+ | 'internal_review'
178
+ | 'client_review'
179
+ | 'revision_requested'
180
+ | 'approved'
181
+ | 'delivered'
182
+ | 'canceled'
183
+ | (string & {});
184
+
185
+ export interface Deliverable {
186
+ id: string;
187
+ projectId: string;
188
+ title: string;
189
+ description?: string;
190
+ status: DeliverableStatus;
191
+ format?: string; // e.g., '16:9 4K ProRes', 'Figma', 'PDF'
192
+ specs?: Record<string, unknown>; // Technical specifications (resolution, frame rate, etc.)
193
+ leadId?: string; // Creative director or lead creator
194
+ reviewerId?: string; // Approver or client reviewer
195
+ dueDate?: string;
196
+ deliveredAt?: string;
197
+ outputUrls?: string[]; // Links to final rendered assets, exports, or storage keys
198
+ customFields?: Record<string, unknown>;
199
+ createdAt: string;
200
+ updatedAt: string;
201
+ }
202
+
203
+ export interface DeliverableSummary {
204
+ deliverable: Deliverable;
205
+ totalTasks: number;
206
+ completedTasks: number;
207
+ activeTasks: number;
208
+ progressPercentage: number;
209
+ estimatedHours: number;
210
+ loggedHours: number;
211
+ }
212
+
213
+ export type CreateDeliverableInput = Omit<Deliverable, 'id' | 'createdAt' | 'updatedAt' | 'status'> & {
214
+ status?: DeliverableStatus;
215
+ };
216
+
172
217
  export type CreateProjectInput = Omit<Project, 'id' | 'createdAt' | 'updatedAt'>;
173
218
 
174
219
  export interface Iteration {
@@ -309,7 +354,11 @@ export type WebhookEvent =
309
354
  | 'container.created'
310
355
  | 'workflow.created'
311
356
  | 'workflow.updated'
312
- | 'workflow.deleted';
357
+ | 'workflow.deleted'
358
+ | 'deliverable.created'
359
+ | 'deliverable.updated'
360
+ | 'deliverable.deleted'
361
+ | 'deliverable.status_changed';
313
362
 
314
363
  export interface PluginHooks {
315
364
  beforeTaskCreate?: (task: Partial<Task>) => Promise<Partial<Task>> | Partial<Task>;
@@ -343,5 +392,6 @@ export interface CriticalPathConfig {
343
392
  teams?: Team[];
344
393
  containers?: TaskContainer[];
345
394
  workflows?: Workflow[];
395
+ deliverables?: Deliverable[];
346
396
  };
347
397
  }
@@ -117,6 +117,44 @@ export const DEFAULT_VFX_WORKFLOW: Workflow = {
117
117
  updatedAt: '2026-01-01T00:00:00.000Z'
118
118
  };
119
119
 
120
+ export const DEFAULT_CREATIVE_WORKFLOW: Workflow = {
121
+ id: 'wf_creative_default',
122
+ name: 'Creative Production Workflow',
123
+ description: 'Production pipeline for creative deliverables from briefing and concepts to internal review, client feedback, and final delivery.',
124
+ statuses: [
125
+ { key: 'briefing', label: 'Briefing & Scoping', completionState: 'not_done', executionState: 'inactive' },
126
+ { key: 'concept', label: 'Concept & Storyboarding', completionState: 'not_done', executionState: 'active' },
127
+ { key: 'in_production', label: 'In Production', completionState: 'not_done', executionState: 'active' },
128
+ { key: 'internal_review', label: 'Internal Review', completionState: 'not_done', executionState: 'active' },
129
+ { key: 'client_review', label: 'Client Review', completionState: 'not_done', executionState: 'active' },
130
+ { key: 'revision_requested', label: 'Revision Requested', completionState: 'not_done', executionState: 'active' },
131
+ { key: 'approved', label: 'Approved', completionState: 'done', executionState: 'inactive' },
132
+ { key: 'delivered', label: 'Delivered', completionState: 'done', executionState: 'inactive' },
133
+ DEFAULT_STATUS_DEFINITIONS.canceled
134
+ ],
135
+ transitions: [
136
+ { name: 'Start Concepts', fromStatusKey: 'briefing', toStatusKey: 'concept' },
137
+ { name: 'Approve Concept for Production', fromStatusKey: 'concept', toStatusKey: 'in_production' },
138
+ { name: 'Submit for Internal Review', fromStatusKey: 'in_production', toStatusKey: 'internal_review' },
139
+ { name: 'Submit for Client Review', fromStatusKey: 'internal_review', toStatusKey: 'client_review' },
140
+ { name: 'Request Revisions (Internal)', fromStatusKey: 'internal_review', toStatusKey: 'revision_requested' },
141
+ { name: 'Request Revisions (Client)', fromStatusKey: 'client_review', toStatusKey: 'revision_requested' },
142
+ { name: 'Address Revisions', fromStatusKey: 'revision_requested', toStatusKey: 'in_production' },
143
+ { name: 'Approve Work', fromStatusKey: 'client_review', toStatusKey: 'approved' },
144
+ { name: 'Deliver Final Assets', fromStatusKey: 'approved', toStatusKey: 'delivered' },
145
+ { name: 'Cancel', fromStatusKey: '*', toStatusKey: 'canceled' }
146
+ ],
147
+ taskTypes: [
148
+ { key: 'deliverable', label: 'Deliverable', description: 'Finished asset, cutdown, or packaged export', icon: 'package' },
149
+ { key: 'creative_task', label: 'Creative Task', description: 'Production task (e.g. Design, Copy, Editing, Color)', icon: 'pen-tool' },
150
+ { key: 'review', label: 'Review Gate', description: 'Internal or external client sign-off', icon: 'check-circle' }
151
+ ],
152
+ defaultStatusKey: 'briefing',
153
+ isDefault: false,
154
+ createdAt: '2026-01-01T00:00:00.000Z',
155
+ updatedAt: '2026-01-01T00:00:00.000Z'
156
+ };
157
+
120
158
  export function validateTransition(
121
159
  workflow: Workflow | undefined,
122
160
  fromStatus: string,
@@ -1 +1 @@
1
- {"root":["./src/index.test.ts","./src/index.ts","./src/use-case-validation.test.ts","./src/domain/custom-fields.ts","./src/domain/ddd.test.ts","./src/domain/entities.ts","./src/domain/events.ts","./src/domain/graph.ts","./src/domain/index.ts","./src/engine/index.ts","./src/plugins/index.ts","./src/storage/file-storage.ts","./src/storage/firebase-storage.ts","./src/storage/index.ts","./src/storage/s3.ts","./src/storage/storage.test.ts","./src/store/firebase.test.ts","./src/store/firebase.ts","./src/store/index.ts","./src/store/sqlite.test.ts","./src/store/sqlite.ts","./src/types/index.ts","./src/utils/index.ts","./src/utils/key.test.ts","./src/utils/key.ts","./src/utils/mentions.test.ts","./src/utils/mentions.ts","./src/utils/status.ts","./src/utils/workflow.ts"],"version":"5.9.3"}
1
+ {"root":["./src/deliverables.test.ts","./src/index.test.ts","./src/index.ts","./src/use-case-validation.test.ts","./src/domain/custom-fields.ts","./src/domain/ddd.test.ts","./src/domain/entities.ts","./src/domain/events.ts","./src/domain/graph.ts","./src/domain/index.ts","./src/engine/index.ts","./src/plugins/index.ts","./src/storage/file-storage.ts","./src/storage/firebase-storage.ts","./src/storage/index.ts","./src/storage/s3.ts","./src/storage/storage.test.ts","./src/store/firebase.test.ts","./src/store/firebase.ts","./src/store/index.ts","./src/store/sqlite.test.ts","./src/store/sqlite.ts","./src/types/index.ts","./src/utils/index.ts","./src/utils/key.test.ts","./src/utils/key.ts","./src/utils/mentions.test.ts","./src/utils/mentions.ts","./src/utils/status.ts","./src/utils/workflow.ts"],"version":"5.9.3"}