@critical-path/core 0.6.0 → 0.8.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 (75) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +6 -0
  3. package/dist/domain/entities.d.ts +4 -0
  4. package/dist/domain/entities.d.ts.map +1 -1
  5. package/dist/domain/entities.js +14 -0
  6. package/dist/domain/entities.js.map +1 -1
  7. package/dist/domain/events.d.ts +32 -4
  8. package/dist/domain/events.d.ts.map +1 -1
  9. package/dist/domain/events.js.map +1 -1
  10. package/dist/engine/index.d.ts +23 -1
  11. package/dist/engine/index.d.ts.map +1 -1
  12. package/dist/engine/index.js +132 -2
  13. package/dist/engine/index.js.map +1 -1
  14. package/dist/index.d.ts +1 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +1 -0
  17. package/dist/index.js.map +1 -1
  18. package/dist/index.test.js +26 -0
  19. package/dist/index.test.js.map +1 -1
  20. package/dist/storage/file-storage.d.ts +24 -0
  21. package/dist/storage/file-storage.d.ts.map +1 -0
  22. package/dist/storage/file-storage.js +73 -0
  23. package/dist/storage/file-storage.js.map +1 -0
  24. package/dist/storage/firebase-storage.d.ts +48 -0
  25. package/dist/storage/firebase-storage.d.ts.map +1 -0
  26. package/dist/storage/firebase-storage.js +142 -0
  27. package/dist/storage/firebase-storage.js.map +1 -0
  28. package/dist/storage/index.d.ts +4 -0
  29. package/dist/storage/index.d.ts.map +1 -0
  30. package/dist/storage/index.js +4 -0
  31. package/dist/storage/index.js.map +1 -0
  32. package/dist/storage/s3.d.ts +36 -0
  33. package/dist/storage/s3.d.ts.map +1 -0
  34. package/dist/storage/s3.js +135 -0
  35. package/dist/storage/s3.js.map +1 -0
  36. package/dist/storage/storage.test.d.ts +2 -0
  37. package/dist/storage/storage.test.d.ts.map +1 -0
  38. package/dist/storage/storage.test.js +117 -0
  39. package/dist/storage/storage.test.js.map +1 -0
  40. package/dist/store/firebase.d.ts +13 -1
  41. package/dist/store/firebase.d.ts.map +1 -1
  42. package/dist/store/firebase.js +95 -18
  43. package/dist/store/firebase.js.map +1 -1
  44. package/dist/store/firebase.test.js +12 -0
  45. package/dist/store/firebase.test.js.map +1 -1
  46. package/dist/store/index.d.ts +27 -2
  47. package/dist/store/index.d.ts.map +1 -1
  48. package/dist/store/index.js +49 -0
  49. package/dist/store/index.js.map +1 -1
  50. package/dist/store/sqlite.d.ts +12 -1
  51. package/dist/store/sqlite.d.ts.map +1 -1
  52. package/dist/store/sqlite.js +128 -6
  53. package/dist/store/sqlite.js.map +1 -1
  54. package/dist/store/sqlite.test.js +47 -1
  55. package/dist/store/sqlite.test.js.map +1 -1
  56. package/dist/types/index.d.ts +52 -1
  57. package/dist/types/index.d.ts.map +1 -1
  58. package/package.json +1 -1
  59. package/src/domain/entities.ts +19 -0
  60. package/src/domain/events.ts +28 -3
  61. package/src/engine/index.ts +168 -3
  62. package/src/index.test.ts +32 -0
  63. package/src/index.ts +2 -0
  64. package/src/storage/file-storage.ts +96 -0
  65. package/src/storage/firebase-storage.ts +177 -0
  66. package/src/storage/index.ts +3 -0
  67. package/src/storage/s3.ts +165 -0
  68. package/src/storage/storage.test.ts +146 -0
  69. package/src/store/firebase.test.ts +15 -0
  70. package/src/store/firebase.ts +97 -18
  71. package/src/store/index.ts +64 -0
  72. package/src/store/sqlite.test.ts +59 -1
  73. package/src/store/sqlite.ts +172 -6
  74. package/src/types/index.ts +63 -0
  75. package/tsconfig.tsbuildinfo +1 -1
@@ -157,14 +157,64 @@ export interface Iteration {
157
157
  status: 'planning' | 'active' | 'completed';
158
158
  createdAt: string;
159
159
  }
160
+ export type AuthorType = 'user' | 'agent' | 'system';
160
161
  export interface Comment {
161
162
  id: string;
162
163
  taskId: string;
163
164
  authorId: string;
165
+ authorType?: AuthorType;
166
+ parentId?: string;
164
167
  content: string;
168
+ metadata?: Record<string, unknown>;
169
+ createdAt: string;
170
+ updatedAt: string;
171
+ }
172
+ export interface Attachment {
173
+ id: string;
174
+ taskId?: string;
175
+ projectId?: string;
176
+ commentId?: string;
177
+ uploaderId: string;
178
+ uploaderType?: AuthorType;
179
+ filename: string;
180
+ mimeType: string;
181
+ sizeBytes: number;
182
+ url: string;
183
+ storageKey?: string;
184
+ metadata?: Record<string, unknown>;
165
185
  createdAt: string;
166
186
  updatedAt: string;
167
187
  }
188
+ export type CreateAttachmentInput = Omit<Attachment, 'id' | 'createdAt' | 'updatedAt'>;
189
+ export interface UploadFileInput {
190
+ filename: string;
191
+ data: Uint8Array | ArrayBuffer | Buffer | Blob | string;
192
+ mimeType?: string;
193
+ pathPrefix?: string;
194
+ }
195
+ export interface UploadFileResult {
196
+ storageKey: string;
197
+ url: string;
198
+ sizeBytes: number;
199
+ mimeType: string;
200
+ }
201
+ export interface PresignedUrlOptions {
202
+ storageKey: string;
203
+ expiresInSeconds?: number;
204
+ contentType?: string;
205
+ }
206
+ export interface PresignedUploadResult {
207
+ uploadUrl: string;
208
+ storageKey: string;
209
+ method?: 'PUT' | 'POST';
210
+ headers?: Record<string, string>;
211
+ }
212
+ export interface FileStorageAdapter {
213
+ upload(input: UploadFileInput): Promise<UploadFileResult>;
214
+ delete(storageKey: string): Promise<boolean>;
215
+ getDownloadUrl?(storageKey: string): Promise<string>;
216
+ getPresignedUploadUrl?(options: PresignedUrlOptions): Promise<PresignedUploadResult>;
217
+ }
168
218
  export interface TimeEntry {
169
219
  id: string;
170
220
  taskId: string;
@@ -192,7 +242,7 @@ export interface Webhook {
192
242
  active: boolean;
193
243
  createdAt: string;
194
244
  }
195
- export type WebhookEvent = 'project.created' | 'project.updated' | 'task.created' | 'task.updated' | 'task.deleted' | 'task.status_changed' | 'comment.created' | 'iteration.started' | 'iteration.completed' | 'team.created' | 'container.created' | 'workflow.created' | 'workflow.updated' | 'workflow.deleted';
245
+ export type WebhookEvent = 'project.created' | 'project.updated' | 'task.created' | 'task.updated' | 'task.deleted' | 'task.status_changed' | 'comment.created' | 'comment.updated' | 'comment.deleted' | 'attachment.created' | 'attachment.deleted' | 'iteration.started' | 'iteration.completed' | 'team.created' | 'container.created' | 'workflow.created' | 'workflow.updated' | 'workflow.deleted';
196
246
  export interface PluginHooks {
197
247
  beforeTaskCreate?: (task: Partial<Task>) => Promise<Partial<Task>> | Partial<Task>;
198
248
  afterTaskCreate?: (task: Task) => Promise<void> | void;
@@ -212,6 +262,7 @@ export interface CriticalPathPlugin {
212
262
  }
213
263
  export interface CriticalPathConfig {
214
264
  store?: 'memory' | 'sqlite' | unknown;
265
+ fileStorage?: FileStorageAdapter;
215
266
  plugins?: CriticalPathPlugin[];
216
267
  webhooks?: Omit<Webhook, 'id' | 'createdAt'>[];
217
268
  initialData?: {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;AAC5E,MAAM,MAAM,QAAQ,GAAG,eAAe,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAEvD,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,MAAM,GAAG,aAAa,GAAG,WAAW,GAAG,MAAM,GAAG,UAAU,CAAC;AACvG,MAAM,MAAM,UAAU,GAAG,iBAAiB,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAE3D,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,UAAU,CAAC;AAClD,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEnD,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,eAAe,CAAC;IACjC,cAAc,EAAE,cAAc,CAAC;IAC/B,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,MAAM,IAAI,GAAG,OAAO,GAAG,iBAAiB,GAAG,aAAa,GAAG,QAAQ,CAAC;AAE1E,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,IAAI,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,eAAe,GAAG,cAAc,GAAG,MAAM,CAAC;IACzF,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,GAAG,GAAG,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC7B,WAAW,EAAE,kBAAkB,EAAE,CAAC;IAClC,SAAS,CAAC,EAAE,kBAAkB,EAAE,CAAC;IACjC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,SAAS,CAAC,EAAE,kBAAkB,EAAE,CAAC;IACjC,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,CAAC;IACvC,mBAAmB,CAAC,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5E,sBAAsB,CAAC,EAAE,qBAAqB,EAAE,CAAC;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,IAAI,EAAE,UAAU,GAAG,YAAY,GAAG,YAAY,CAAC;CAChD;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,IAAI,EAAE,CAAC;IACtB,eAAe,EAAE,IAAI,EAAE,CAAC;IACxB,YAAY,EAAE,cAAc,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,QAAQ,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,uBAAuB,CAAC,EAAE,MAAM,CAAC;IAEjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,GAAG,QAAQ,GAAG,UAAU,CAAC,GAAG;IACnG,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,CAAC,CAAC;AAEjF,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,WAAW,GAAG,MAAM,CAAC;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,UAAU,GAAG,QAAQ,GAAG,WAAW,CAAC;IAC5C,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAErD,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,UAAU,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,qBAAqB,GAAG,IAAI,CAAC,UAAU,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,CAAC,CAAC;AAEvF,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,UAAU,GAAG,WAAW,GAAG,MAAM,GAAG,IAAI,GAAG,MAAM,CAAC;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC1D,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7C,cAAc,CAAC,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,qBAAqB,CAAC,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;CACtF;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,YAAY,GACpB,iBAAiB,GACjB,iBAAiB,GACjB,cAAc,GACd,cAAc,GACd,cAAc,GACd,qBAAqB,GACrB,iBAAiB,GACjB,iBAAiB,GACjB,iBAAiB,GACjB,oBAAoB,GACpB,oBAAoB,GACpB,mBAAmB,GACnB,qBAAqB,GACrB,cAAc,GACd,mBAAmB,GACnB,kBAAkB,GAClB,kBAAkB,GAClB,kBAAkB,CAAC;AAEvB,MAAM,WAAW,WAAW;IAC1B,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnF,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvD,gBAAgB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClG,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC5E,gBAAgB,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACxD,eAAe,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACxD;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,gBAAgB,CAAC,EAAE,qBAAqB,EAAE,CAAC;IAC3C,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAClD;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;IACtC,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,OAAO,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC/B,QAAQ,CAAC,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,GAAG,WAAW,CAAC,EAAE,CAAC;IAC/C,WAAW,CAAC,EAAE;QACZ,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;QACrB,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;QACf,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;QACf,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;QACzB,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC;QACf,UAAU,CAAC,EAAE,aAAa,EAAE,CAAC;QAC7B,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;KACxB,CAAC;CACH"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@critical-path/core",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Core headless domain engine, plugin architecture, and data stores for Critical Path",
5
5
  "author": "Jack James",
6
6
  "license": "MIT",
@@ -388,3 +388,22 @@ export class ProjectEntity extends BaseEntity {
388
388
  };
389
389
  }
390
390
  }
391
+
392
+ export class AttachmentValidationError extends Error {
393
+ constructor(message: string) {
394
+ super(message);
395
+ this.name = 'AttachmentValidationError';
396
+ }
397
+ }
398
+
399
+ export function validateAttachmentUrl(url?: string): void {
400
+ if (!url || typeof url !== 'string' || url.trim() === '') {
401
+ throw new AttachmentValidationError('Attachment URL must be a non-empty string.');
402
+ }
403
+ if (url.startsWith('data:') && url.length > 2048) {
404
+ throw new AttachmentValidationError(
405
+ 'Attachment URL cannot be a large data URI. Please upload the file using a FileStorageAdapter or Cloud Storage and provide the resulting URL.'
406
+ );
407
+ }
408
+ }
409
+
@@ -7,14 +7,15 @@ import type {
7
7
  TaskContainer,
8
8
  TaskDependency,
9
9
  TimeEntry,
10
- Comment
10
+ Comment,
11
+ Attachment
11
12
  } from '../types/index.js';
12
13
 
13
14
  export interface DomainEvent<TPayload = Record<string, unknown>> {
14
15
  readonly id: string;
15
16
  readonly name: string;
16
17
  readonly aggregateId: string;
17
- readonly aggregateType: 'Task' | 'Project' | 'Workflow' | 'Iteration' | 'Team' | 'Container' | 'Dependency';
18
+ readonly aggregateType: 'Task' | 'Project' | 'Workflow' | 'Iteration' | 'Team' | 'Container' | 'Dependency' | 'Comment' | 'Attachment';
18
19
  readonly occurredAt: string;
19
20
  readonly payload: TPayload;
20
21
  }
@@ -51,7 +52,27 @@ export interface TimeLoggedEvent extends DomainEvent<{ timeEntry: TimeEntry; tas
51
52
 
52
53
  export interface CommentAddedEvent extends DomainEvent<{ comment: Comment; taskId: string }> {
53
54
  readonly name: 'comment.created';
54
- readonly aggregateType: 'Task';
55
+ readonly aggregateType: 'Comment';
56
+ }
57
+
58
+ export interface CommentUpdatedEvent extends DomainEvent<{ comment: Comment; previous: Comment }> {
59
+ readonly name: 'comment.updated';
60
+ readonly aggregateType: 'Comment';
61
+ }
62
+
63
+ export interface CommentDeletedEvent extends DomainEvent<{ commentId: string; taskId: string }> {
64
+ readonly name: 'comment.deleted';
65
+ readonly aggregateType: 'Comment';
66
+ }
67
+
68
+ export interface AttachmentCreatedEvent extends DomainEvent<{ attachment: Attachment }> {
69
+ readonly name: 'attachment.created';
70
+ readonly aggregateType: 'Attachment';
71
+ }
72
+
73
+ export interface AttachmentDeletedEvent extends DomainEvent<{ attachmentId: string; storageKey?: string; url: string }> {
74
+ readonly name: 'attachment.deleted';
75
+ readonly aggregateType: 'Attachment';
55
76
  }
56
77
 
57
78
  export interface TaskDependencyAddedEvent extends DomainEvent<{ dependency: TaskDependency }> {
@@ -111,6 +132,10 @@ export type CriticalPathDomainEvent =
111
132
  | TaskDeletedEvent
112
133
  | TimeLoggedEvent
113
134
  | CommentAddedEvent
135
+ | CommentUpdatedEvent
136
+ | CommentDeletedEvent
137
+ | AttachmentCreatedEvent
138
+ | AttachmentDeletedEvent
114
139
  | TaskDependencyAddedEvent
115
140
  | ProjectCreatedEvent
116
141
  | ProjectUpdatedEvent
@@ -8,6 +8,11 @@ import type {
8
8
  TaskContainer,
9
9
  Iteration,
10
10
  Comment,
11
+ Attachment,
12
+ FileStorageAdapter,
13
+ UploadFileInput,
14
+ PresignedUrlOptions,
15
+ PresignedUploadResult,
11
16
  TimeEntry,
12
17
  WebhookEvent,
13
18
  Workflow,
@@ -40,13 +45,19 @@ import {
40
45
  ContainerCreatedEvent,
41
46
  TaskDependencyAddedEvent,
42
47
  TimeLoggedEvent,
43
- CommentAddedEvent
48
+ CommentAddedEvent,
49
+ CommentUpdatedEvent,
50
+ CommentDeletedEvent,
51
+ AttachmentCreatedEvent,
52
+ AttachmentDeletedEvent
44
53
  } from '../domain/events.js';
54
+ import { validateAttachmentUrl, AttachmentValidationError } from '../domain/entities.js';
45
55
  import { validateCustomFieldValues } from '../domain/custom-fields.js';
46
56
  import { detectDependencyCycle, CircularDependencyError } from '../domain/graph.js';
47
57
 
48
58
  export class CriticalPathEngine {
49
59
  public readonly store: StorageAdapter;
60
+ public readonly fileStorage?: FileStorageAdapter;
50
61
  public readonly plugins: PluginRegistry;
51
62
  public readonly events: DomainEventBus;
52
63
 
@@ -55,6 +66,7 @@ export class CriticalPathEngine {
55
66
  ? (config.store as StorageAdapter)
56
67
  : new InMemoryStore();
57
68
 
69
+ this.fileStorage = config.fileStorage;
58
70
  this.plugins = new PluginRegistry();
59
71
  this.events = new DomainEventBus();
60
72
 
@@ -601,6 +613,14 @@ export class CriticalPathEngine {
601
613
  }
602
614
 
603
615
  // --- Comments ---
616
+ async getComments(taskId: string): Promise<Comment[]> {
617
+ return this.store.getComments(taskId);
618
+ }
619
+
620
+ async getComment(id: string): Promise<Comment | null> {
621
+ return this.store.getComment(id);
622
+ }
623
+
604
624
  async addComment(comment: Omit<Comment, 'id' | 'createdAt' | 'updatedAt'>): Promise<Comment> {
605
625
  const created = await this.store.addComment(comment);
606
626
  const now = new Date().toISOString();
@@ -608,8 +628,8 @@ export class CriticalPathEngine {
608
628
  const event: CommentAddedEvent = {
609
629
  id: `evt_${Math.random().toString(36).substring(2, 9)}`,
610
630
  name: 'comment.created',
611
- aggregateId: comment.taskId,
612
- aggregateType: 'Task',
631
+ aggregateId: created.id,
632
+ aggregateType: 'Comment',
613
633
  occurredAt: now,
614
634
  payload: { taskId: comment.taskId, comment: created }
615
635
  };
@@ -619,6 +639,151 @@ export class CriticalPathEngine {
619
639
  return created;
620
640
  }
621
641
 
642
+ async updateComment(id: string, updates: Partial<Comment>): Promise<Comment | null> {
643
+ const existing = await this.store.getComment(id);
644
+ if (!existing) return null;
645
+
646
+ const updated = await this.store.updateComment(id, updates);
647
+ if (!updated) return null;
648
+
649
+ const now = new Date().toISOString();
650
+ const event: CommentUpdatedEvent = {
651
+ id: `evt_${Math.random().toString(36).substring(2, 9)}`,
652
+ name: 'comment.updated',
653
+ aggregateId: updated.id,
654
+ aggregateType: 'Comment',
655
+ occurredAt: now,
656
+ payload: { comment: updated, previous: existing }
657
+ };
658
+ await this.events.publish(event);
659
+
660
+ this.dispatchWebhook('comment.updated', { comment: updated });
661
+ return updated;
662
+ }
663
+
664
+ async deleteComment(id: string): Promise<boolean> {
665
+ const existing = await this.store.getComment(id);
666
+ if (!existing) return false;
667
+
668
+ const deleted = await this.store.deleteComment(id);
669
+ if (deleted) {
670
+ const now = new Date().toISOString();
671
+ const event: CommentDeletedEvent = {
672
+ id: `evt_${Math.random().toString(36).substring(2, 9)}`,
673
+ name: 'comment.deleted',
674
+ aggregateId: id,
675
+ aggregateType: 'Comment',
676
+ occurredAt: now,
677
+ payload: { taskId: existing.taskId, commentId: id }
678
+ };
679
+ await this.events.publish(event);
680
+
681
+ this.dispatchWebhook('comment.deleted', { commentId: id, taskId: existing.taskId });
682
+ }
683
+ return deleted;
684
+ }
685
+
686
+ // --- Attachments ---
687
+ async getAttachments(filter?: { taskId?: string; projectId?: string; commentId?: string }): Promise<Attachment[]> {
688
+ return this.store.getAttachments(filter);
689
+ }
690
+
691
+ async getAttachment(id: string): Promise<Attachment | null> {
692
+ return this.store.getAttachment(id);
693
+ }
694
+
695
+ async createAttachment(attachment: Omit<Attachment, 'id' | 'createdAt' | 'updatedAt'>): Promise<Attachment> {
696
+ validateAttachmentUrl(attachment.url);
697
+ const created = await this.store.createAttachment(attachment);
698
+ const now = new Date().toISOString();
699
+
700
+ const event: AttachmentCreatedEvent = {
701
+ id: `evt_${Math.random().toString(36).substring(2, 9)}`,
702
+ name: 'attachment.created',
703
+ aggregateId: created.id,
704
+ aggregateType: 'Attachment',
705
+ occurredAt: now,
706
+ payload: { attachment: created }
707
+ };
708
+ await this.events.publish(event);
709
+
710
+ this.dispatchWebhook('attachment.created', { attachment: created });
711
+ return created;
712
+ }
713
+
714
+ async deleteAttachment(id: string): Promise<boolean> {
715
+ const existing = await this.store.getAttachment(id);
716
+ if (!existing) return false;
717
+
718
+ if (existing.storageKey && this.fileStorage) {
719
+ try {
720
+ await this.fileStorage.delete(existing.storageKey);
721
+ } catch {
722
+ // Silently continue if underlying file is already gone
723
+ }
724
+ }
725
+
726
+ const deleted = await this.store.deleteAttachment(id);
727
+ if (deleted) {
728
+ const now = new Date().toISOString();
729
+ const event: AttachmentDeletedEvent = {
730
+ id: `evt_${Math.random().toString(36).substring(2, 9)}`,
731
+ name: 'attachment.deleted',
732
+ aggregateId: id,
733
+ aggregateType: 'Attachment',
734
+ occurredAt: now,
735
+ payload: { attachmentId: id, storageKey: existing.storageKey, url: existing.url }
736
+ };
737
+ await this.events.publish(event);
738
+
739
+ this.dispatchWebhook('attachment.deleted', { attachmentId: id, taskId: existing.taskId, projectId: existing.projectId });
740
+ }
741
+ return deleted;
742
+ }
743
+
744
+ async uploadAttachmentFile(
745
+ input: UploadFileInput & {
746
+ taskId?: string;
747
+ projectId?: string;
748
+ commentId?: string;
749
+ uploaderId: string;
750
+ uploaderType?: 'user' | 'agent' | 'system';
751
+ metadata?: Record<string, unknown>;
752
+ }
753
+ ): Promise<Attachment> {
754
+ if (!this.fileStorage) {
755
+ throw new Error('No FileStorageAdapter configured in CriticalPathEngine. Pass "fileStorage" in config to enable direct file uploads.');
756
+ }
757
+
758
+ const uploadResult = await this.fileStorage.upload({
759
+ filename: input.filename,
760
+ data: input.data,
761
+ mimeType: input.mimeType,
762
+ pathPrefix: input.pathPrefix || (input.projectId ? `projects/${input.projectId}` : input.taskId ? `tasks/${input.taskId}` : undefined)
763
+ });
764
+
765
+ return this.createAttachment({
766
+ taskId: input.taskId,
767
+ projectId: input.projectId,
768
+ commentId: input.commentId,
769
+ uploaderId: input.uploaderId,
770
+ uploaderType: input.uploaderType || 'user',
771
+ filename: input.filename,
772
+ mimeType: uploadResult.mimeType,
773
+ sizeBytes: uploadResult.sizeBytes,
774
+ url: uploadResult.url,
775
+ storageKey: uploadResult.storageKey,
776
+ metadata: input.metadata
777
+ });
778
+ }
779
+
780
+ async getPresignedAttachmentUploadUrl(options: PresignedUrlOptions): Promise<PresignedUploadResult> {
781
+ if (!this.fileStorage || !this.fileStorage.getPresignedUploadUrl) {
782
+ throw new Error('Presigned uploads are not supported by the configured FileStorageAdapter.');
783
+ }
784
+ return this.fileStorage.getPresignedUploadUrl(options);
785
+ }
786
+
622
787
  // --- Teams ---
623
788
  async getTeams(): Promise<Team[]> {
624
789
  return this.store.getTeams();
package/src/index.test.ts CHANGED
@@ -226,4 +226,36 @@ describe('CriticalPathEngine Core Tests', () => {
226
226
  const doneTask = await engine.updateTask(task.id, { status: 'done' });
227
227
  expect(doneTask?.status).toBe('done');
228
228
  });
229
+
230
+ it('validates attachment URLs to prevent massive data URIs', async () => {
231
+ const engine = new CriticalPathEngine();
232
+ const proj = await engine.createProject({ key: 'ATT', name: 'Attachment Project' });
233
+ const task = await engine.createTask({ projectId: proj.id, title: 'Task with attachment', status: 'todo' });
234
+
235
+ // Valid HTTPS URL
236
+ const validAtt = await engine.createAttachment({
237
+ taskId: task.id,
238
+ uploaderId: 'user_1',
239
+ filename: 'spec.pdf',
240
+ mimeType: 'application/pdf',
241
+ sizeBytes: 1024,
242
+ url: 'https://storage.googleapis.com/bucket/spec.pdf'
243
+ });
244
+ expect(validAtt.id).toBeDefined();
245
+ expect(validAtt.url).toBe('https://storage.googleapis.com/bucket/spec.pdf');
246
+
247
+ // Invalid large data URI (>2048 chars)
248
+ const largeDataUri = `data:application/pdf;base64,${'A'.repeat(3000)}`;
249
+ await expect(
250
+ engine.createAttachment({
251
+ taskId: task.id,
252
+ uploaderId: 'user_1',
253
+ filename: 'huge.pdf',
254
+ mimeType: 'application/pdf',
255
+ sizeBytes: 3000,
256
+ url: largeDataUri
257
+ })
258
+ ).rejects.toThrow('Attachment URL cannot be a large data URI');
259
+ });
229
260
  });
261
+
package/src/index.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export * from './types/index.js';
2
2
  export * from './utils/index.js';
3
3
  export * from './store/index.js';
4
+ export * from './storage/index.js';
4
5
  export * from './plugins/index.js';
5
6
  export * from './engine/index.js';
6
7
  export * from './domain/index.js';
8
+
@@ -0,0 +1,96 @@
1
+ import type {
2
+ FileStorageAdapter,
3
+ UploadFileInput,
4
+ UploadFileResult,
5
+ PresignedUrlOptions,
6
+ PresignedUploadResult
7
+ } from '../types/index.js';
8
+
9
+ export interface StoredFile {
10
+ storageKey: string;
11
+ data: Uint8Array;
12
+ mimeType: string;
13
+ sizeBytes: number;
14
+ url: string;
15
+ createdAt: string;
16
+ }
17
+
18
+ export class InMemoryFileStore implements FileStorageAdapter {
19
+ private files = new Map<string, StoredFile>();
20
+ private publicBaseUrl: string;
21
+
22
+ constructor(options: { publicBaseUrl?: string; publicUrlBase?: string } = {}) {
23
+ this.publicBaseUrl = options.publicUrlBase || options.publicBaseUrl || 'memory://attachments';
24
+ }
25
+
26
+ async upload(input: UploadFileInput): Promise<UploadFileResult> {
27
+ const prefix = input.pathPrefix ? `${input.pathPrefix.replace(/\/+$/, '')}/` : '';
28
+ const randomId = Math.random().toString(36).substring(2, 9);
29
+ const sanitizedFilename = input.filename.replace(/[^a-zA-Z0-9._-]/g, '_');
30
+ const storageKey = `${prefix}${Date.now()}_${randomId}_${sanitizedFilename}`;
31
+
32
+ let buffer: Uint8Array;
33
+ if (typeof input.data === 'string') {
34
+ buffer = new TextEncoder().encode(input.data);
35
+ } else if (input.data instanceof Uint8Array) {
36
+ buffer = input.data;
37
+ } else if (input.data instanceof ArrayBuffer) {
38
+ buffer = new Uint8Array(input.data);
39
+ } else if (typeof Buffer !== 'undefined' && Buffer.isBuffer(input.data)) {
40
+ buffer = new Uint8Array(input.data);
41
+ } else {
42
+ buffer = new Uint8Array();
43
+ }
44
+
45
+ const mimeType = input.mimeType || 'application/octet-stream';
46
+ const sizeBytes = buffer.byteLength;
47
+ const url = `${this.publicBaseUrl}/${storageKey}`;
48
+
49
+ this.files.set(storageKey, {
50
+ storageKey,
51
+ data: buffer,
52
+ mimeType,
53
+ sizeBytes,
54
+ url,
55
+ createdAt: new Date().toISOString()
56
+ });
57
+
58
+ return {
59
+ storageKey,
60
+ url,
61
+ sizeBytes,
62
+ mimeType
63
+ };
64
+ }
65
+
66
+ async delete(storageKey: string): Promise<boolean> {
67
+ return this.files.delete(storageKey);
68
+ }
69
+
70
+ async getDownloadUrl(storageKey: string): Promise<string> {
71
+ const file = this.files.get(storageKey);
72
+ if (!file) {
73
+ throw new Error(`File with storageKey "${storageKey}" not found`);
74
+ }
75
+ return file.url;
76
+ }
77
+
78
+ async getPresignedUploadUrl(options: PresignedUrlOptions): Promise<PresignedUploadResult> {
79
+ const storageKey = options.storageKey;
80
+ const uploadUrl = `${this.publicBaseUrl}/upload/${encodeURIComponent(storageKey)}`;
81
+ return {
82
+ uploadUrl,
83
+ storageKey,
84
+ method: 'PUT',
85
+ headers: options.contentType ? { 'Content-Type': options.contentType } : {}
86
+ };
87
+ }
88
+
89
+ getFile(storageKey: string): StoredFile | undefined {
90
+ return this.files.get(storageKey);
91
+ }
92
+
93
+ clear(): void {
94
+ this.files.clear();
95
+ }
96
+ }