@critical-path/core 0.2.0 → 0.3.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 (57) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/engine/index.d.ts +19 -1
  3. package/dist/engine/index.d.ts.map +1 -1
  4. package/dist/engine/index.js +129 -6
  5. package/dist/engine/index.js.map +1 -1
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.test.js +96 -1
  11. package/dist/index.test.js.map +1 -1
  12. package/dist/store/firebase.d.ts +19 -7
  13. package/dist/store/firebase.d.ts.map +1 -1
  14. package/dist/store/firebase.js +98 -14
  15. package/dist/store/firebase.js.map +1 -1
  16. package/dist/store/firebase.test.js +5 -2
  17. package/dist/store/firebase.test.js.map +1 -1
  18. package/dist/store/index.d.ts +34 -8
  19. package/dist/store/index.d.ts.map +1 -1
  20. package/dist/store/index.js +79 -12
  21. package/dist/store/index.js.map +1 -1
  22. package/dist/store/sqlite.d.ts +17 -4
  23. package/dist/store/sqlite.d.ts.map +1 -1
  24. package/dist/store/sqlite.js +179 -39
  25. package/dist/store/sqlite.js.map +1 -1
  26. package/dist/types/index.d.ts +70 -9
  27. package/dist/types/index.d.ts.map +1 -1
  28. package/dist/utils/index.d.ts +3 -0
  29. package/dist/utils/index.d.ts.map +1 -0
  30. package/dist/utils/index.js +3 -0
  31. package/dist/utils/index.js.map +1 -0
  32. package/dist/utils/key.d.ts +14 -0
  33. package/dist/utils/key.d.ts.map +1 -0
  34. package/dist/utils/key.js +49 -0
  35. package/dist/utils/key.js.map +1 -0
  36. package/dist/utils/key.test.d.ts +2 -0
  37. package/dist/utils/key.test.d.ts.map +1 -0
  38. package/dist/utils/key.test.js +48 -0
  39. package/dist/utils/key.test.js.map +1 -0
  40. package/dist/utils/status.d.ts +14 -0
  41. package/dist/utils/status.d.ts.map +1 -0
  42. package/dist/utils/status.js +58 -0
  43. package/dist/utils/status.js.map +1 -0
  44. package/package.json +5 -1
  45. package/src/engine/index.ts +161 -8
  46. package/src/index.test.ts +113 -1
  47. package/src/index.ts +2 -0
  48. package/src/store/firebase.test.ts +8 -2
  49. package/src/store/firebase.ts +112 -18
  50. package/src/store/index.ts +113 -18
  51. package/src/store/sqlite.ts +277 -47
  52. package/src/types/index.ts +76 -10
  53. package/src/utils/index.ts +2 -0
  54. package/src/utils/key.test.ts +55 -0
  55. package/src/utils/key.ts +51 -0
  56. package/src/utils/status.ts +85 -0
  57. package/tsconfig.tsbuildinfo +1 -1
@@ -2,13 +2,16 @@ import type { StorageAdapter } from './index.js';
2
2
  import type {
3
3
  Project,
4
4
  Task,
5
- Sprint,
5
+ Iteration,
6
+ Team,
7
+ TaskContainer,
6
8
  Comment,
7
9
  TimeEntry,
8
10
  Activity,
9
11
  Webhook,
10
12
  TaskDependency
11
13
  } from '../types/index.js';
14
+ import { generateProjectKey } from '../utils/key.js';
12
15
 
13
16
  export interface FirestoreDBInterface {
14
17
  collection(name: string): {
@@ -92,16 +95,21 @@ export class InMemoryFirestoreMock implements FirestoreDBInterface {
92
95
 
93
96
  export interface FirebaseStoreConfig {
94
97
  /**
95
- * Firestore instance or mockable DB interface.
98
+ * Firestore instance or mockable DB interface. Required.
96
99
  */
97
- db?: FirestoreDBInterface;
100
+ db: FirestoreDBInterface;
98
101
  }
99
102
 
100
103
  export class FirebaseStore implements StorageAdapter {
101
104
  private db: FirestoreDBInterface;
102
105
 
103
- constructor(config: FirebaseStoreConfig = {}) {
104
- this.db = config.db || new InMemoryFirestoreMock();
106
+ constructor(config: FirebaseStoreConfig) {
107
+ if (!config?.db) {
108
+ throw new Error(
109
+ 'FirebaseStore requires a valid Firestore db instance (e.g. FirebaseStoreConfig.db). Automatic fallback to InMemoryFirestoreMock has been removed.'
110
+ );
111
+ }
112
+ this.db = config.db;
105
113
  }
106
114
 
107
115
  // --- Projects ---
@@ -119,7 +127,8 @@ export class FirebaseStore implements StorageAdapter {
119
127
  async createProject(project: Omit<Project, 'id' | 'createdAt' | 'updatedAt'>): Promise<Project> {
120
128
  const docRef = this.db.collection('projects').doc();
121
129
  const now = new Date().toISOString();
122
- const newProj: Project = { ...project, id: docRef.id, createdAt: now, updatedAt: now };
130
+ const key = project.key || generateProjectKey(project.name);
131
+ const newProj: Project = { ...project, key, id: docRef.id, createdAt: now, updatedAt: now };
123
132
  await docRef.set(newProj);
124
133
  return newProj;
125
134
  }
@@ -144,6 +153,78 @@ export class FirebaseStore implements StorageAdapter {
144
153
  return true;
145
154
  }
146
155
 
156
+ // --- Teams ---
157
+ async getTeams(): Promise<Team[]> {
158
+ const snap = await this.db.collection('teams').get();
159
+ return snap.docs.map((doc) => ({ ...doc.data(), id: doc.id }));
160
+ }
161
+
162
+ async getTeam(id: string): Promise<Team | null> {
163
+ const snap = await this.db.collection('teams').doc(id).get();
164
+ if (!snap.exists) return null;
165
+ return { ...snap.data(), id: snap.id };
166
+ }
167
+
168
+ async createTeam(team: Omit<Team, 'id' | 'createdAt' | 'updatedAt'>): Promise<Team> {
169
+ const docRef = this.db.collection('teams').doc();
170
+ const now = new Date().toISOString();
171
+ const newTeam: Team = { ...team, id: docRef.id, createdAt: now, updatedAt: now };
172
+ await docRef.set(newTeam);
173
+ return newTeam;
174
+ }
175
+
176
+ async updateTeam(id: string, updates: Partial<Team>): Promise<Team | null> {
177
+ const existing = await this.getTeam(id);
178
+ if (!existing) return null;
179
+
180
+ const updated: Team = { ...existing, ...updates, updatedAt: new Date().toISOString() };
181
+ await this.db.collection('teams').doc(id).set(updated, { merge: true });
182
+ return updated;
183
+ }
184
+
185
+ async deleteTeam(id: string): Promise<boolean> {
186
+ const existing = await this.getTeam(id);
187
+ if (!existing) return false;
188
+ await this.db.collection('teams').doc(id).delete();
189
+ return true;
190
+ }
191
+
192
+ // --- Containers ---
193
+ async getContainers(projectId: string): Promise<TaskContainer[]> {
194
+ const snap = await this.db.collection('containers').where('projectId', '==', projectId).get();
195
+ return snap.docs.map((doc) => ({ ...doc.data(), id: doc.id }));
196
+ }
197
+
198
+ async getContainer(id: string): Promise<TaskContainer | null> {
199
+ const snap = await this.db.collection('containers').doc(id).get();
200
+ if (!snap.exists) return null;
201
+ return { ...snap.data(), id: snap.id };
202
+ }
203
+
204
+ async createContainer(container: Omit<TaskContainer, 'id' | 'createdAt' | 'updatedAt'>): Promise<TaskContainer> {
205
+ const docRef = this.db.collection('containers').doc();
206
+ const now = new Date().toISOString();
207
+ const newContainer: TaskContainer = { ...container, id: docRef.id, createdAt: now, updatedAt: now };
208
+ await docRef.set(newContainer);
209
+ return newContainer;
210
+ }
211
+
212
+ async updateContainer(id: string, updates: Partial<TaskContainer>): Promise<TaskContainer | null> {
213
+ const existing = await this.getContainer(id);
214
+ if (!existing) return null;
215
+
216
+ const updated: TaskContainer = { ...existing, ...updates, updatedAt: new Date().toISOString() };
217
+ await this.db.collection('containers').doc(id).set(updated, { merge: true });
218
+ return updated;
219
+ }
220
+
221
+ async deleteContainer(id: string): Promise<boolean> {
222
+ const existing = await this.getContainer(id);
223
+ if (!existing) return false;
224
+ await this.db.collection('containers').doc(id).delete();
225
+ return true;
226
+ }
227
+
147
228
  // --- Tasks ---
148
229
  async getTasks(projectId?: string): Promise<Task[]> {
149
230
  let snap;
@@ -189,29 +270,42 @@ export class FirebaseStore implements StorageAdapter {
189
270
  return true;
190
271
  }
191
272
 
192
- // --- Sprints ---
193
- async getSprints(projectId: string): Promise<Sprint[]> {
194
- const snap = await this.db.collection('sprints').where('projectId', '==', projectId).get();
273
+ // --- Iterations ---
274
+ async getIterations(projectId: string): Promise<Iteration[]> {
275
+ const snap = await this.db.collection('iterations').where('projectId', '==', projectId).get();
195
276
  return snap.docs.map((doc) => ({ ...doc.data(), id: doc.id }));
196
277
  }
197
278
 
198
- async createSprint(sprint: Omit<Sprint, 'id' | 'createdAt'>): Promise<Sprint> {
199
- const docRef = this.db.collection('sprints').doc();
279
+ async getIteration(id: string): Promise<Iteration | null> {
280
+ const snap = await this.db.collection('iterations').doc(id).get();
281
+ if (!snap.exists) return null;
282
+ return { ...snap.data(), id: snap.id };
283
+ }
284
+
285
+ async createIteration(iteration: Omit<Iteration, 'id' | 'createdAt'>): Promise<Iteration> {
286
+ const docRef = this.db.collection('iterations').doc();
200
287
  const now = new Date().toISOString();
201
- const newSprint: Sprint = { ...sprint, id: docRef.id, createdAt: now };
202
- await docRef.set(newSprint);
203
- return newSprint;
288
+ const newIteration: Iteration = { ...iteration, id: docRef.id, createdAt: now };
289
+ await docRef.set(newIteration);
290
+ return newIteration;
204
291
  }
205
292
 
206
- async updateSprint(id: string, updates: Partial<Sprint>): Promise<Sprint | null> {
207
- const snap = await this.db.collection('sprints').doc(id).get();
293
+ async updateIteration(id: string, updates: Partial<Iteration>): Promise<Iteration | null> {
294
+ const snap = await this.db.collection('iterations').doc(id).get();
208
295
  if (!snap.exists) return null;
209
296
 
210
- const updated: Sprint = { ...snap.data(), id, ...updates };
211
- await this.db.collection('sprints').doc(id).set(updated, { merge: true });
297
+ const updated: Iteration = { ...snap.data(), id, ...updates };
298
+ await this.db.collection('iterations').doc(id).set(updated, { merge: true });
212
299
  return updated;
213
300
  }
214
301
 
302
+ async deleteIteration(id: string): Promise<boolean> {
303
+ const existing = await this.getIteration(id);
304
+ if (!existing) return false;
305
+ await this.db.collection('iterations').doc(id).delete();
306
+ return true;
307
+ }
308
+
215
309
  // --- Comments & Activity ---
216
310
  async getComments(taskId: string): Promise<Comment[]> {
217
311
  const snap = await this.db.collection('comments').where('taskId', '==', taskId).get();
@@ -4,13 +4,16 @@ export * from './firebase.js';
4
4
  import type {
5
5
  Project,
6
6
  Task,
7
- Sprint,
7
+ Iteration,
8
+ Team,
9
+ TaskContainer,
8
10
  Comment,
9
11
  TimeEntry,
10
12
  Activity,
11
13
  Webhook,
12
14
  TaskDependency
13
15
  } from '../types/index.js';
16
+ import { generateProjectKey } from '../utils/key.js';
14
17
 
15
18
  export interface StorageAdapter {
16
19
  // Projects
@@ -27,10 +30,26 @@ export interface StorageAdapter {
27
30
  updateTask(id: string, updates: Partial<Task>): Promise<Task | null>;
28
31
  deleteTask(id: string): Promise<boolean>;
29
32
 
30
- // Sprints
31
- getSprints(projectId: string): Promise<Sprint[]>;
32
- createSprint(sprint: Omit<Sprint, 'id' | 'createdAt'>): Promise<Sprint>;
33
- updateSprint(id: string, updates: Partial<Sprint>): Promise<Sprint | null>;
33
+ // Teams
34
+ getTeams(): Promise<Team[]>;
35
+ getTeam(id: string): Promise<Team | null>;
36
+ createTeam(team: Omit<Team, 'id' | 'createdAt' | 'updatedAt'>): Promise<Team>;
37
+ updateTeam(id: string, updates: Partial<Team>): Promise<Team | null>;
38
+ deleteTeam(id: string): Promise<boolean>;
39
+
40
+ // Task Containers
41
+ getContainers(projectId: string): Promise<TaskContainer[]>;
42
+ getContainer(id: string): Promise<TaskContainer | null>;
43
+ createContainer(container: Omit<TaskContainer, 'id' | 'createdAt' | 'updatedAt'>): Promise<TaskContainer>;
44
+ updateContainer(id: string, updates: Partial<TaskContainer>): Promise<TaskContainer | null>;
45
+ deleteContainer(id: string): Promise<boolean>;
46
+
47
+ // Iterations
48
+ getIterations(projectId: string): Promise<Iteration[]>;
49
+ getIteration(id: string): Promise<Iteration | null>;
50
+ createIteration(iteration: Omit<Iteration, 'id' | 'createdAt'>): Promise<Iteration>;
51
+ updateIteration(id: string, updates: Partial<Iteration>): Promise<Iteration | null>;
52
+ deleteIteration(id: string): Promise<boolean>;
34
53
 
35
54
  // Comments & Activity
36
55
  getComments(taskId: string): Promise<Comment[]>;
@@ -54,7 +73,9 @@ export interface StorageAdapter {
54
73
  export class InMemoryStore implements StorageAdapter {
55
74
  private projects = new Map<string, Project>();
56
75
  private tasks = new Map<string, Task>();
57
- private sprints = new Map<string, Sprint>();
76
+ private teams = new Map<string, Team>();
77
+ private containers = new Map<string, TaskContainer>();
78
+ private iterations = new Map<string, Iteration>();
58
79
  private comments = new Map<string, Comment>();
59
80
  private activities: Activity[] = [];
60
81
  private timeEntries = new Map<string, TimeEntry>();
@@ -72,8 +93,10 @@ export class InMemoryStore implements StorageAdapter {
72
93
  async createProject(project: Omit<Project, 'id' | 'createdAt' | 'updatedAt'>): Promise<Project> {
73
94
  const id = `proj_${Math.random().toString(36).substring(2, 9)}`;
74
95
  const now = new Date().toISOString();
96
+ const key = project.key || generateProjectKey(project.name);
75
97
  const newProject: Project = {
76
98
  ...project,
99
+ key,
77
100
  id,
78
101
  createdAt: now,
79
102
  updatedAt: now
@@ -139,29 +162,97 @@ export class InMemoryStore implements StorageAdapter {
139
162
  return this.tasks.delete(id);
140
163
  }
141
164
 
142
- async getSprints(projectId: string): Promise<Sprint[]> {
143
- return Array.from(this.sprints.values()).filter((s) => s.projectId === projectId);
165
+ // Teams
166
+ async getTeams(): Promise<Team[]> {
167
+ return Array.from(this.teams.values());
144
168
  }
145
169
 
146
- async createSprint(sprint: Omit<Sprint, 'id' | 'createdAt'>): Promise<Sprint> {
147
- const id = `sprint_${Math.random().toString(36).substring(2, 9)}`;
148
- const newSprint: Sprint = {
149
- ...sprint,
170
+ async getTeam(id: string): Promise<Team | null> {
171
+ return this.teams.get(id) || null;
172
+ }
173
+
174
+ async createTeam(team: Omit<Team, 'id' | 'createdAt' | 'updatedAt'>): Promise<Team> {
175
+ const id = `team_${Math.random().toString(36).substring(2, 9)}`;
176
+ const now = new Date().toISOString();
177
+ const newTeam: Team = { ...team, id, createdAt: now, updatedAt: now };
178
+ this.teams.set(id, newTeam);
179
+ return newTeam;
180
+ }
181
+
182
+ async updateTeam(id: string, updates: Partial<Team>): Promise<Team | null> {
183
+ const existing = this.teams.get(id);
184
+ if (!existing) return null;
185
+ const updated: Team = { ...existing, ...updates, updatedAt: new Date().toISOString() };
186
+ this.teams.set(id, updated);
187
+ return updated;
188
+ }
189
+
190
+ async deleteTeam(id: string): Promise<boolean> {
191
+ return this.teams.delete(id);
192
+ }
193
+
194
+ // Containers
195
+ async getContainers(projectId: string): Promise<TaskContainer[]> {
196
+ return Array.from(this.containers.values()).filter((c) => c.projectId === projectId);
197
+ }
198
+
199
+ async getContainer(id: string): Promise<TaskContainer | null> {
200
+ return this.containers.get(id) || null;
201
+ }
202
+
203
+ async createContainer(container: Omit<TaskContainer, 'id' | 'createdAt' | 'updatedAt'>): Promise<TaskContainer> {
204
+ const id = `cnt_${Math.random().toString(36).substring(2, 9)}`;
205
+ const now = new Date().toISOString();
206
+ const newContainer: TaskContainer = { ...container, id, createdAt: now, updatedAt: now };
207
+ this.containers.set(id, newContainer);
208
+ return newContainer;
209
+ }
210
+
211
+ async updateContainer(id: string, updates: Partial<TaskContainer>): Promise<TaskContainer | null> {
212
+ const existing = this.containers.get(id);
213
+ if (!existing) return null;
214
+ const updated: TaskContainer = { ...existing, ...updates, updatedAt: new Date().toISOString() };
215
+ this.containers.set(id, updated);
216
+ return updated;
217
+ }
218
+
219
+ async deleteContainer(id: string): Promise<boolean> {
220
+ return this.containers.delete(id);
221
+ }
222
+
223
+ // Iterations
224
+ async getIterations(projectId: string): Promise<Iteration[]> {
225
+ return Array.from(this.iterations.values()).filter((s) => s.projectId === projectId);
226
+ }
227
+
228
+ async getIteration(id: string): Promise<Iteration | null> {
229
+ return this.iterations.get(id) || null;
230
+ }
231
+
232
+ async createIteration(iteration: Omit<Iteration, 'id' | 'createdAt'>): Promise<Iteration> {
233
+ const id = `iter_${Math.random().toString(36).substring(2, 9)}`;
234
+ const newIteration: Iteration = {
235
+ ...iteration,
150
236
  id,
151
237
  createdAt: new Date().toISOString()
152
238
  };
153
- this.sprints.set(id, newSprint);
154
- return newSprint;
239
+ this.iterations.set(id, newIteration);
240
+ return newIteration;
155
241
  }
156
242
 
157
- async updateSprint(id: string, updates: Partial<Sprint>): Promise<Sprint | null> {
158
- const existing = this.sprints.get(id);
243
+ async updateIteration(id: string, updates: Partial<Iteration>): Promise<Iteration | null> {
244
+ const existing = this.iterations.get(id);
159
245
  if (!existing) return null;
160
- const updated: Sprint = { ...existing, ...updates };
161
- this.sprints.set(id, updated);
246
+ const updated: Iteration = { ...existing, ...updates };
247
+ this.iterations.set(id, updated);
162
248
  return updated;
163
249
  }
164
250
 
251
+ async deleteIteration(id: string): Promise<boolean> {
252
+ return this.iterations.delete(id);
253
+ }
254
+
255
+ // Comments
165
256
  async getComments(taskId: string): Promise<Comment[]> {
166
257
  return Array.from(this.comments.values()).filter((c) => c.taskId === taskId);
167
258
  }
@@ -174,6 +265,7 @@ export class InMemoryStore implements StorageAdapter {
174
265
  return newComment;
175
266
  }
176
267
 
268
+ // Activities
177
269
  async getActivities(filter?: { projectId?: string; taskId?: string }): Promise<Activity[]> {
178
270
  return this.activities.filter((a) => {
179
271
  if (filter?.projectId && a.projectId !== filter.projectId) return false;
@@ -189,6 +281,7 @@ export class InMemoryStore implements StorageAdapter {
189
281
  return newAct;
190
282
  }
191
283
 
284
+ // Time Entries
192
285
  async getTimeEntries(taskId: string): Promise<TimeEntry[]> {
193
286
  return Array.from(this.timeEntries.values()).filter((e) => e.taskId === taskId);
194
287
  }
@@ -200,6 +293,7 @@ export class InMemoryStore implements StorageAdapter {
200
293
  return newEntry;
201
294
  }
202
295
 
296
+ // Dependencies
203
297
  async getDependencies(taskId: string): Promise<TaskDependency[]> {
204
298
  return Array.from(this.dependencies.values()).filter(
205
299
  (d) => d.taskId === taskId || d.dependsOnTaskId === taskId
@@ -213,6 +307,7 @@ export class InMemoryStore implements StorageAdapter {
213
307
  return newDep;
214
308
  }
215
309
 
310
+ // Webhooks
216
311
  async getWebhooks(): Promise<Webhook[]> {
217
312
  return Array.from(this.webhooks.values());
218
313
  }