@critical-path/core 0.1.2 → 0.2.1
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.
- package/CHANGELOG.md +19 -0
- package/README.md +43 -26
- package/dist/engine/index.d.ts +19 -1
- package/dist/engine/index.d.ts.map +1 -1
- package/dist/engine/index.js +129 -6
- package/dist/engine/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +96 -1
- package/dist/index.test.js.map +1 -1
- package/dist/store/firebase.d.ts +113 -0
- package/dist/store/firebase.d.ts.map +1 -0
- package/dist/store/firebase.js +319 -0
- package/dist/store/firebase.js.map +1 -0
- package/dist/store/firebase.test.d.ts +2 -0
- package/dist/store/firebase.test.d.ts.map +1 -0
- package/dist/store/firebase.test.js +45 -0
- package/dist/store/firebase.test.js.map +1 -0
- package/dist/store/index.d.ts +36 -8
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +78 -12
- package/dist/store/index.js.map +1 -1
- package/dist/store/sqlite.d.ts +61 -0
- package/dist/store/sqlite.d.ts.map +1 -0
- package/dist/store/sqlite.js +492 -0
- package/dist/store/sqlite.js.map +1 -0
- package/dist/store/sqlite.test.d.ts +2 -0
- package/dist/store/sqlite.test.d.ts.map +1 -0
- package/dist/store/sqlite.test.js +63 -0
- package/dist/store/sqlite.test.js.map +1 -0
- package/dist/types/index.d.ts +68 -8
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/status.d.ts +14 -0
- package/dist/utils/status.d.ts.map +1 -0
- package/dist/utils/status.js +58 -0
- package/dist/utils/status.js.map +1 -0
- package/package.json +9 -1
- package/src/engine/index.ts +161 -8
- package/src/index.test.ts +113 -1
- package/src/index.ts +1 -0
- package/src/store/firebase.test.ts +59 -0
- package/src/store/firebase.ts +381 -0
- package/src/store/index.ts +113 -19
- package/src/store/sqlite.test.ts +78 -0
- package/src/store/sqlite.ts +729 -0
- package/src/types/index.ts +74 -9
- package/src/utils/status.ts +85 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { FirebaseStore, InMemoryFirestoreMock } from './firebase.js';
|
|
3
|
+
|
|
4
|
+
describe('FirebaseStore', () => {
|
|
5
|
+
let store: FirebaseStore;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
store = new FirebaseStore({ db: new InMemoryFirestoreMock() });
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should throw an error if db is missing in config', () => {
|
|
12
|
+
expect(() => new FirebaseStore({} as any)).toThrow(
|
|
13
|
+
'FirebaseStore requires a valid Firestore db instance'
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should create and retrieve projects in FirebaseStore', async () => {
|
|
18
|
+
const project = await store.createProject({
|
|
19
|
+
key: 'FIRE',
|
|
20
|
+
name: 'Firebase Test Project',
|
|
21
|
+
description: 'Testing Firebase Firestore adapter'
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
expect(project.id).toBeDefined();
|
|
25
|
+
expect(project.name).toBe('Firebase Test Project');
|
|
26
|
+
|
|
27
|
+
const fetched = await store.getProject(project.id);
|
|
28
|
+
expect(fetched).not.toBeNull();
|
|
29
|
+
expect(fetched?.key).toBe('FIRE');
|
|
30
|
+
|
|
31
|
+
const all = await store.getProjects();
|
|
32
|
+
expect(all).toHaveLength(1);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should create, update, and delete tasks in FirebaseStore', async () => {
|
|
36
|
+
const project = await store.createProject({ key: 'P1', name: 'Proj 1' });
|
|
37
|
+
const task = await store.createTask({
|
|
38
|
+
projectId: project.id,
|
|
39
|
+
title: 'Firebase Task 1',
|
|
40
|
+
status: 'todo',
|
|
41
|
+
priority: 'urgent'
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
expect(task.id).toBeDefined();
|
|
45
|
+
expect(task.priority).toBe('urgent');
|
|
46
|
+
|
|
47
|
+
const updated = await store.updateTask(task.id, { status: 'done' });
|
|
48
|
+
expect(updated?.status).toBe('done');
|
|
49
|
+
|
|
50
|
+
const tasks = await store.getTasks(project.id);
|
|
51
|
+
expect(tasks).toHaveLength(1);
|
|
52
|
+
|
|
53
|
+
const deleted = await store.deleteTask(task.id);
|
|
54
|
+
expect(deleted).toBe(true);
|
|
55
|
+
|
|
56
|
+
const remaining = await store.getTasks(project.id);
|
|
57
|
+
expect(remaining).toHaveLength(0);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import type { StorageAdapter } from './index.js';
|
|
2
|
+
import type {
|
|
3
|
+
Project,
|
|
4
|
+
Task,
|
|
5
|
+
Iteration,
|
|
6
|
+
Team,
|
|
7
|
+
TaskContainer,
|
|
8
|
+
Comment,
|
|
9
|
+
TimeEntry,
|
|
10
|
+
Activity,
|
|
11
|
+
Webhook,
|
|
12
|
+
TaskDependency
|
|
13
|
+
} from '../types/index.js';
|
|
14
|
+
|
|
15
|
+
export interface FirestoreDBInterface {
|
|
16
|
+
collection(name: string): {
|
|
17
|
+
doc(id?: string): {
|
|
18
|
+
id: string;
|
|
19
|
+
get(): Promise<{ exists: boolean; id: string; data(): any }>;
|
|
20
|
+
set(data: any, options?: { merge?: boolean }): Promise<void>;
|
|
21
|
+
delete(): Promise<void>;
|
|
22
|
+
};
|
|
23
|
+
get(): Promise<{ docs: Array<{ id: string; data(): any }> }>;
|
|
24
|
+
where(field: string, op: string, value: any): {
|
|
25
|
+
get(): Promise<{ docs: Array<{ id: string; data(): any }> }>;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class InMemoryFirestoreMock implements FirestoreDBInterface {
|
|
31
|
+
private collections = new Map<string, Map<string, any>>();
|
|
32
|
+
|
|
33
|
+
private getCollectionMap(name: string) {
|
|
34
|
+
if (!this.collections.has(name)) {
|
|
35
|
+
this.collections.set(name, new Map());
|
|
36
|
+
}
|
|
37
|
+
return this.collections.get(name)!;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
collection(name: string) {
|
|
41
|
+
const colMap = this.getCollectionMap(name);
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
doc(id?: string) {
|
|
45
|
+
const docId = id || `doc_${Math.random().toString(36).substring(2, 9)}`;
|
|
46
|
+
return {
|
|
47
|
+
id: docId,
|
|
48
|
+
async get() {
|
|
49
|
+
const data = colMap.get(docId);
|
|
50
|
+
return {
|
|
51
|
+
exists: !!data,
|
|
52
|
+
id: docId,
|
|
53
|
+
data: () => data
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
async set(data: any, options?: { merge?: boolean }) {
|
|
57
|
+
if (options?.merge && colMap.has(docId)) {
|
|
58
|
+
colMap.set(docId, { ...colMap.get(docId), ...data });
|
|
59
|
+
} else {
|
|
60
|
+
colMap.set(docId, data);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
async delete() {
|
|
64
|
+
colMap.delete(docId);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
async get() {
|
|
69
|
+
const docs = Array.from(colMap.entries()).map(([id, data]) => ({
|
|
70
|
+
id,
|
|
71
|
+
data: () => data
|
|
72
|
+
}));
|
|
73
|
+
return { docs };
|
|
74
|
+
},
|
|
75
|
+
where(field: string, op: string, value: any) {
|
|
76
|
+
return {
|
|
77
|
+
async get() {
|
|
78
|
+
const docs = Array.from(colMap.entries())
|
|
79
|
+
.filter(([_, data]) => {
|
|
80
|
+
if (op === '==') return data[field] === value;
|
|
81
|
+
return true;
|
|
82
|
+
})
|
|
83
|
+
.map(([id, data]) => ({
|
|
84
|
+
id,
|
|
85
|
+
data: () => data
|
|
86
|
+
}));
|
|
87
|
+
return { docs };
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface FirebaseStoreConfig {
|
|
96
|
+
/**
|
|
97
|
+
* Firestore instance or mockable DB interface. Required.
|
|
98
|
+
*/
|
|
99
|
+
db: FirestoreDBInterface;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export class FirebaseStore implements StorageAdapter {
|
|
103
|
+
private db: FirestoreDBInterface;
|
|
104
|
+
|
|
105
|
+
constructor(config: FirebaseStoreConfig) {
|
|
106
|
+
if (!config?.db) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
'FirebaseStore requires a valid Firestore db instance (e.g. FirebaseStoreConfig.db). Automatic fallback to InMemoryFirestoreMock has been removed.'
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
this.db = config.db;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// --- Projects ---
|
|
115
|
+
async getProjects(): Promise<Project[]> {
|
|
116
|
+
const snap = await this.db.collection('projects').get();
|
|
117
|
+
return snap.docs.map((doc) => ({ ...doc.data(), id: doc.id }));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async getProject(id: string): Promise<Project | null> {
|
|
121
|
+
const snap = await this.db.collection('projects').doc(id).get();
|
|
122
|
+
if (!snap.exists) return null;
|
|
123
|
+
return { ...snap.data(), id: snap.id };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async createProject(project: Omit<Project, 'id' | 'createdAt' | 'updatedAt'>): Promise<Project> {
|
|
127
|
+
const docRef = this.db.collection('projects').doc();
|
|
128
|
+
const now = new Date().toISOString();
|
|
129
|
+
const newProj: Project = { ...project, id: docRef.id, createdAt: now, updatedAt: now };
|
|
130
|
+
await docRef.set(newProj);
|
|
131
|
+
return newProj;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async updateProject(id: string, updates: Partial<Project>): Promise<Project | null> {
|
|
135
|
+
const existing = await this.getProject(id);
|
|
136
|
+
if (!existing) return null;
|
|
137
|
+
|
|
138
|
+
const updated: Project = {
|
|
139
|
+
...existing,
|
|
140
|
+
...updates,
|
|
141
|
+
updatedAt: new Date().toISOString()
|
|
142
|
+
};
|
|
143
|
+
await this.db.collection('projects').doc(id).set(updated, { merge: true });
|
|
144
|
+
return updated;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async deleteProject(id: string): Promise<boolean> {
|
|
148
|
+
const existing = await this.getProject(id);
|
|
149
|
+
if (!existing) return false;
|
|
150
|
+
await this.db.collection('projects').doc(id).delete();
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// --- Teams ---
|
|
155
|
+
async getTeams(): Promise<Team[]> {
|
|
156
|
+
const snap = await this.db.collection('teams').get();
|
|
157
|
+
return snap.docs.map((doc) => ({ ...doc.data(), id: doc.id }));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async getTeam(id: string): Promise<Team | null> {
|
|
161
|
+
const snap = await this.db.collection('teams').doc(id).get();
|
|
162
|
+
if (!snap.exists) return null;
|
|
163
|
+
return { ...snap.data(), id: snap.id };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async createTeam(team: Omit<Team, 'id' | 'createdAt' | 'updatedAt'>): Promise<Team> {
|
|
167
|
+
const docRef = this.db.collection('teams').doc();
|
|
168
|
+
const now = new Date().toISOString();
|
|
169
|
+
const newTeam: Team = { ...team, id: docRef.id, createdAt: now, updatedAt: now };
|
|
170
|
+
await docRef.set(newTeam);
|
|
171
|
+
return newTeam;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async updateTeam(id: string, updates: Partial<Team>): Promise<Team | null> {
|
|
175
|
+
const existing = await this.getTeam(id);
|
|
176
|
+
if (!existing) return null;
|
|
177
|
+
|
|
178
|
+
const updated: Team = { ...existing, ...updates, updatedAt: new Date().toISOString() };
|
|
179
|
+
await this.db.collection('teams').doc(id).set(updated, { merge: true });
|
|
180
|
+
return updated;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async deleteTeam(id: string): Promise<boolean> {
|
|
184
|
+
const existing = await this.getTeam(id);
|
|
185
|
+
if (!existing) return false;
|
|
186
|
+
await this.db.collection('teams').doc(id).delete();
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// --- Containers ---
|
|
191
|
+
async getContainers(projectId: string): Promise<TaskContainer[]> {
|
|
192
|
+
const snap = await this.db.collection('containers').where('projectId', '==', projectId).get();
|
|
193
|
+
return snap.docs.map((doc) => ({ ...doc.data(), id: doc.id }));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async getContainer(id: string): Promise<TaskContainer | null> {
|
|
197
|
+
const snap = await this.db.collection('containers').doc(id).get();
|
|
198
|
+
if (!snap.exists) return null;
|
|
199
|
+
return { ...snap.data(), id: snap.id };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async createContainer(container: Omit<TaskContainer, 'id' | 'createdAt' | 'updatedAt'>): Promise<TaskContainer> {
|
|
203
|
+
const docRef = this.db.collection('containers').doc();
|
|
204
|
+
const now = new Date().toISOString();
|
|
205
|
+
const newContainer: TaskContainer = { ...container, id: docRef.id, createdAt: now, updatedAt: now };
|
|
206
|
+
await docRef.set(newContainer);
|
|
207
|
+
return newContainer;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async updateContainer(id: string, updates: Partial<TaskContainer>): Promise<TaskContainer | null> {
|
|
211
|
+
const existing = await this.getContainer(id);
|
|
212
|
+
if (!existing) return null;
|
|
213
|
+
|
|
214
|
+
const updated: TaskContainer = { ...existing, ...updates, updatedAt: new Date().toISOString() };
|
|
215
|
+
await this.db.collection('containers').doc(id).set(updated, { merge: true });
|
|
216
|
+
return updated;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async deleteContainer(id: string): Promise<boolean> {
|
|
220
|
+
const existing = await this.getContainer(id);
|
|
221
|
+
if (!existing) return false;
|
|
222
|
+
await this.db.collection('containers').doc(id).delete();
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// --- Tasks ---
|
|
227
|
+
async getTasks(projectId?: string): Promise<Task[]> {
|
|
228
|
+
let snap;
|
|
229
|
+
if (projectId) {
|
|
230
|
+
snap = await this.db.collection('tasks').where('projectId', '==', projectId).get();
|
|
231
|
+
} else {
|
|
232
|
+
snap = await this.db.collection('tasks').get();
|
|
233
|
+
}
|
|
234
|
+
return snap.docs.map((doc) => ({ ...doc.data(), id: doc.id }));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async getTask(id: string): Promise<Task | null> {
|
|
238
|
+
const snap = await this.db.collection('tasks').doc(id).get();
|
|
239
|
+
if (!snap.exists) return null;
|
|
240
|
+
return { ...snap.data(), id: snap.id };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async createTask(task: Omit<Task, 'id' | 'createdAt' | 'updatedAt'>): Promise<Task> {
|
|
244
|
+
const docRef = this.db.collection('tasks').doc();
|
|
245
|
+
const now = new Date().toISOString();
|
|
246
|
+
const newTask: Task = { ...task, id: docRef.id, createdAt: now, updatedAt: now };
|
|
247
|
+
await docRef.set(newTask);
|
|
248
|
+
return newTask;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async updateTask(id: string, updates: Partial<Task>): Promise<Task | null> {
|
|
252
|
+
const existing = await this.getTask(id);
|
|
253
|
+
if (!existing) return null;
|
|
254
|
+
|
|
255
|
+
const updated: Task = {
|
|
256
|
+
...existing,
|
|
257
|
+
...updates,
|
|
258
|
+
updatedAt: new Date().toISOString()
|
|
259
|
+
};
|
|
260
|
+
await this.db.collection('tasks').doc(id).set(updated, { merge: true });
|
|
261
|
+
return updated;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async deleteTask(id: string): Promise<boolean> {
|
|
265
|
+
const existing = await this.getTask(id);
|
|
266
|
+
if (!existing) return false;
|
|
267
|
+
await this.db.collection('tasks').doc(id).delete();
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// --- Iterations ---
|
|
272
|
+
async getIterations(projectId: string): Promise<Iteration[]> {
|
|
273
|
+
const snap = await this.db.collection('iterations').where('projectId', '==', projectId).get();
|
|
274
|
+
return snap.docs.map((doc) => ({ ...doc.data(), id: doc.id }));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async getIteration(id: string): Promise<Iteration | null> {
|
|
278
|
+
const snap = await this.db.collection('iterations').doc(id).get();
|
|
279
|
+
if (!snap.exists) return null;
|
|
280
|
+
return { ...snap.data(), id: snap.id };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async createIteration(iteration: Omit<Iteration, 'id' | 'createdAt'>): Promise<Iteration> {
|
|
284
|
+
const docRef = this.db.collection('iterations').doc();
|
|
285
|
+
const now = new Date().toISOString();
|
|
286
|
+
const newIteration: Iteration = { ...iteration, id: docRef.id, createdAt: now };
|
|
287
|
+
await docRef.set(newIteration);
|
|
288
|
+
return newIteration;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async updateIteration(id: string, updates: Partial<Iteration>): Promise<Iteration | null> {
|
|
292
|
+
const snap = await this.db.collection('iterations').doc(id).get();
|
|
293
|
+
if (!snap.exists) return null;
|
|
294
|
+
|
|
295
|
+
const updated: Iteration = { ...snap.data(), id, ...updates };
|
|
296
|
+
await this.db.collection('iterations').doc(id).set(updated, { merge: true });
|
|
297
|
+
return updated;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async deleteIteration(id: string): Promise<boolean> {
|
|
301
|
+
const existing = await this.getIteration(id);
|
|
302
|
+
if (!existing) return false;
|
|
303
|
+
await this.db.collection('iterations').doc(id).delete();
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// --- Comments & Activity ---
|
|
308
|
+
async getComments(taskId: string): Promise<Comment[]> {
|
|
309
|
+
const snap = await this.db.collection('comments').where('taskId', '==', taskId).get();
|
|
310
|
+
return snap.docs.map((doc) => ({ ...doc.data(), id: doc.id }));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async addComment(comment: Omit<Comment, 'id' | 'createdAt' | 'updatedAt'>): Promise<Comment> {
|
|
314
|
+
const docRef = this.db.collection('comments').doc();
|
|
315
|
+
const now = new Date().toISOString();
|
|
316
|
+
const newComment: Comment = { ...comment, id: docRef.id, createdAt: now, updatedAt: now };
|
|
317
|
+
await docRef.set(newComment);
|
|
318
|
+
return newComment;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async getActivities(filter?: { projectId?: string; taskId?: string }): Promise<Activity[]> {
|
|
322
|
+
let snap;
|
|
323
|
+
if (filter?.taskId) {
|
|
324
|
+
snap = await this.db.collection('activities').where('taskId', '==', filter.taskId).get();
|
|
325
|
+
} else if (filter?.projectId) {
|
|
326
|
+
snap = await this.db.collection('activities').where('projectId', '==', filter.projectId).get();
|
|
327
|
+
} else {
|
|
328
|
+
snap = await this.db.collection('activities').get();
|
|
329
|
+
}
|
|
330
|
+
return snap.docs.map((doc) => ({ ...doc.data(), id: doc.id }));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async logActivity(activity: Omit<Activity, 'id' | 'createdAt'>): Promise<Activity> {
|
|
334
|
+
const docRef = this.db.collection('activities').doc();
|
|
335
|
+
const now = new Date().toISOString();
|
|
336
|
+
const newAct: Activity = { ...activity, id: docRef.id, createdAt: now };
|
|
337
|
+
await docRef.set(newAct);
|
|
338
|
+
return newAct;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// --- Time Tracking ---
|
|
342
|
+
async getTimeEntries(taskId: string): Promise<TimeEntry[]> {
|
|
343
|
+
const snap = await this.db.collection('time_entries').where('taskId', '==', taskId).get();
|
|
344
|
+
return snap.docs.map((doc) => ({ ...doc.data(), id: doc.id }));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
async logTime(entry: Omit<TimeEntry, 'id' | 'loggedAt'>): Promise<TimeEntry> {
|
|
348
|
+
const docRef = this.db.collection('time_entries').doc();
|
|
349
|
+
const now = new Date().toISOString();
|
|
350
|
+
const newEntry: TimeEntry = { ...entry, id: docRef.id, loggedAt: now };
|
|
351
|
+
await docRef.set(newEntry);
|
|
352
|
+
return newEntry;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// --- Dependencies ---
|
|
356
|
+
async getDependencies(taskId: string): Promise<TaskDependency[]> {
|
|
357
|
+
const snap = await this.db.collection('dependencies').where('taskId', '==', taskId).get();
|
|
358
|
+
return snap.docs.map((doc) => ({ ...doc.data(), id: doc.id }));
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async addDependency(dep: Omit<TaskDependency, 'id'>): Promise<TaskDependency> {
|
|
362
|
+
const docRef = this.db.collection('dependencies').doc();
|
|
363
|
+
const newDep: TaskDependency = { ...dep, id: docRef.id };
|
|
364
|
+
await docRef.set(newDep);
|
|
365
|
+
return newDep;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// --- Webhooks ---
|
|
369
|
+
async getWebhooks(): Promise<Webhook[]> {
|
|
370
|
+
const snap = await this.db.collection('webhooks').get();
|
|
371
|
+
return snap.docs.map((doc) => ({ ...doc.data(), id: doc.id }));
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async addWebhook(webhook: Omit<Webhook, 'id' | 'createdAt'>): Promise<Webhook> {
|
|
375
|
+
const docRef = this.db.collection('webhooks').doc();
|
|
376
|
+
const now = new Date().toISOString();
|
|
377
|
+
const newWh: Webhook = { ...webhook, id: docRef.id, createdAt: now };
|
|
378
|
+
await docRef.set(newWh);
|
|
379
|
+
return newWh;
|
|
380
|
+
}
|
|
381
|
+
}
|
package/src/store/index.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
export * from './sqlite.js';
|
|
2
|
+
export * from './firebase.js';
|
|
3
|
+
|
|
1
4
|
import type {
|
|
2
5
|
Project,
|
|
3
6
|
Task,
|
|
4
|
-
|
|
5
|
-
|
|
7
|
+
Iteration,
|
|
8
|
+
Team,
|
|
9
|
+
TaskContainer,
|
|
6
10
|
Comment,
|
|
7
11
|
TimeEntry,
|
|
8
12
|
Activity,
|
|
@@ -25,10 +29,26 @@ export interface StorageAdapter {
|
|
|
25
29
|
updateTask(id: string, updates: Partial<Task>): Promise<Task | null>;
|
|
26
30
|
deleteTask(id: string): Promise<boolean>;
|
|
27
31
|
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
// Teams
|
|
33
|
+
getTeams(): Promise<Team[]>;
|
|
34
|
+
getTeam(id: string): Promise<Team | null>;
|
|
35
|
+
createTeam(team: Omit<Team, 'id' | 'createdAt' | 'updatedAt'>): Promise<Team>;
|
|
36
|
+
updateTeam(id: string, updates: Partial<Team>): Promise<Team | null>;
|
|
37
|
+
deleteTeam(id: string): Promise<boolean>;
|
|
38
|
+
|
|
39
|
+
// Task Containers
|
|
40
|
+
getContainers(projectId: string): Promise<TaskContainer[]>;
|
|
41
|
+
getContainer(id: string): Promise<TaskContainer | null>;
|
|
42
|
+
createContainer(container: Omit<TaskContainer, 'id' | 'createdAt' | 'updatedAt'>): Promise<TaskContainer>;
|
|
43
|
+
updateContainer(id: string, updates: Partial<TaskContainer>): Promise<TaskContainer | null>;
|
|
44
|
+
deleteContainer(id: string): Promise<boolean>;
|
|
45
|
+
|
|
46
|
+
// Iterations
|
|
47
|
+
getIterations(projectId: string): Promise<Iteration[]>;
|
|
48
|
+
getIteration(id: string): Promise<Iteration | null>;
|
|
49
|
+
createIteration(iteration: Omit<Iteration, 'id' | 'createdAt'>): Promise<Iteration>;
|
|
50
|
+
updateIteration(id: string, updates: Partial<Iteration>): Promise<Iteration | null>;
|
|
51
|
+
deleteIteration(id: string): Promise<boolean>;
|
|
32
52
|
|
|
33
53
|
// Comments & Activity
|
|
34
54
|
getComments(taskId: string): Promise<Comment[]>;
|
|
@@ -52,7 +72,9 @@ export interface StorageAdapter {
|
|
|
52
72
|
export class InMemoryStore implements StorageAdapter {
|
|
53
73
|
private projects = new Map<string, Project>();
|
|
54
74
|
private tasks = new Map<string, Task>();
|
|
55
|
-
private
|
|
75
|
+
private teams = new Map<string, Team>();
|
|
76
|
+
private containers = new Map<string, TaskContainer>();
|
|
77
|
+
private iterations = new Map<string, Iteration>();
|
|
56
78
|
private comments = new Map<string, Comment>();
|
|
57
79
|
private activities: Activity[] = [];
|
|
58
80
|
private timeEntries = new Map<string, TimeEntry>();
|
|
@@ -137,29 +159,97 @@ export class InMemoryStore implements StorageAdapter {
|
|
|
137
159
|
return this.tasks.delete(id);
|
|
138
160
|
}
|
|
139
161
|
|
|
140
|
-
|
|
141
|
-
|
|
162
|
+
// Teams
|
|
163
|
+
async getTeams(): Promise<Team[]> {
|
|
164
|
+
return Array.from(this.teams.values());
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async getTeam(id: string): Promise<Team | null> {
|
|
168
|
+
return this.teams.get(id) || null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async createTeam(team: Omit<Team, 'id' | 'createdAt' | 'updatedAt'>): Promise<Team> {
|
|
172
|
+
const id = `team_${Math.random().toString(36).substring(2, 9)}`;
|
|
173
|
+
const now = new Date().toISOString();
|
|
174
|
+
const newTeam: Team = { ...team, id, createdAt: now, updatedAt: now };
|
|
175
|
+
this.teams.set(id, newTeam);
|
|
176
|
+
return newTeam;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async updateTeam(id: string, updates: Partial<Team>): Promise<Team | null> {
|
|
180
|
+
const existing = this.teams.get(id);
|
|
181
|
+
if (!existing) return null;
|
|
182
|
+
const updated: Team = { ...existing, ...updates, updatedAt: new Date().toISOString() };
|
|
183
|
+
this.teams.set(id, updated);
|
|
184
|
+
return updated;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async deleteTeam(id: string): Promise<boolean> {
|
|
188
|
+
return this.teams.delete(id);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Containers
|
|
192
|
+
async getContainers(projectId: string): Promise<TaskContainer[]> {
|
|
193
|
+
return Array.from(this.containers.values()).filter((c) => c.projectId === projectId);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async getContainer(id: string): Promise<TaskContainer | null> {
|
|
197
|
+
return this.containers.get(id) || null;
|
|
142
198
|
}
|
|
143
199
|
|
|
144
|
-
async
|
|
145
|
-
const id = `
|
|
146
|
-
const
|
|
147
|
-
|
|
200
|
+
async createContainer(container: Omit<TaskContainer, 'id' | 'createdAt' | 'updatedAt'>): Promise<TaskContainer> {
|
|
201
|
+
const id = `cnt_${Math.random().toString(36).substring(2, 9)}`;
|
|
202
|
+
const now = new Date().toISOString();
|
|
203
|
+
const newContainer: TaskContainer = { ...container, id, createdAt: now, updatedAt: now };
|
|
204
|
+
this.containers.set(id, newContainer);
|
|
205
|
+
return newContainer;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async updateContainer(id: string, updates: Partial<TaskContainer>): Promise<TaskContainer | null> {
|
|
209
|
+
const existing = this.containers.get(id);
|
|
210
|
+
if (!existing) return null;
|
|
211
|
+
const updated: TaskContainer = { ...existing, ...updates, updatedAt: new Date().toISOString() };
|
|
212
|
+
this.containers.set(id, updated);
|
|
213
|
+
return updated;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async deleteContainer(id: string): Promise<boolean> {
|
|
217
|
+
return this.containers.delete(id);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Iterations
|
|
221
|
+
async getIterations(projectId: string): Promise<Iteration[]> {
|
|
222
|
+
return Array.from(this.iterations.values()).filter((s) => s.projectId === projectId);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async getIteration(id: string): Promise<Iteration | null> {
|
|
226
|
+
return this.iterations.get(id) || null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async createIteration(iteration: Omit<Iteration, 'id' | 'createdAt'>): Promise<Iteration> {
|
|
230
|
+
const id = `iter_${Math.random().toString(36).substring(2, 9)}`;
|
|
231
|
+
const newIteration: Iteration = {
|
|
232
|
+
...iteration,
|
|
148
233
|
id,
|
|
149
234
|
createdAt: new Date().toISOString()
|
|
150
235
|
};
|
|
151
|
-
this.
|
|
152
|
-
return
|
|
236
|
+
this.iterations.set(id, newIteration);
|
|
237
|
+
return newIteration;
|
|
153
238
|
}
|
|
154
239
|
|
|
155
|
-
async
|
|
156
|
-
const existing = this.
|
|
240
|
+
async updateIteration(id: string, updates: Partial<Iteration>): Promise<Iteration | null> {
|
|
241
|
+
const existing = this.iterations.get(id);
|
|
157
242
|
if (!existing) return null;
|
|
158
|
-
const updated:
|
|
159
|
-
this.
|
|
243
|
+
const updated: Iteration = { ...existing, ...updates };
|
|
244
|
+
this.iterations.set(id, updated);
|
|
160
245
|
return updated;
|
|
161
246
|
}
|
|
162
247
|
|
|
248
|
+
async deleteIteration(id: string): Promise<boolean> {
|
|
249
|
+
return this.iterations.delete(id);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Comments
|
|
163
253
|
async getComments(taskId: string): Promise<Comment[]> {
|
|
164
254
|
return Array.from(this.comments.values()).filter((c) => c.taskId === taskId);
|
|
165
255
|
}
|
|
@@ -172,6 +262,7 @@ export class InMemoryStore implements StorageAdapter {
|
|
|
172
262
|
return newComment;
|
|
173
263
|
}
|
|
174
264
|
|
|
265
|
+
// Activities
|
|
175
266
|
async getActivities(filter?: { projectId?: string; taskId?: string }): Promise<Activity[]> {
|
|
176
267
|
return this.activities.filter((a) => {
|
|
177
268
|
if (filter?.projectId && a.projectId !== filter.projectId) return false;
|
|
@@ -187,6 +278,7 @@ export class InMemoryStore implements StorageAdapter {
|
|
|
187
278
|
return newAct;
|
|
188
279
|
}
|
|
189
280
|
|
|
281
|
+
// Time Entries
|
|
190
282
|
async getTimeEntries(taskId: string): Promise<TimeEntry[]> {
|
|
191
283
|
return Array.from(this.timeEntries.values()).filter((e) => e.taskId === taskId);
|
|
192
284
|
}
|
|
@@ -198,6 +290,7 @@ export class InMemoryStore implements StorageAdapter {
|
|
|
198
290
|
return newEntry;
|
|
199
291
|
}
|
|
200
292
|
|
|
293
|
+
// Dependencies
|
|
201
294
|
async getDependencies(taskId: string): Promise<TaskDependency[]> {
|
|
202
295
|
return Array.from(this.dependencies.values()).filter(
|
|
203
296
|
(d) => d.taskId === taskId || d.dependsOnTaskId === taskId
|
|
@@ -211,6 +304,7 @@ export class InMemoryStore implements StorageAdapter {
|
|
|
211
304
|
return newDep;
|
|
212
305
|
}
|
|
213
306
|
|
|
307
|
+
// Webhooks
|
|
214
308
|
async getWebhooks(): Promise<Webhook[]> {
|
|
215
309
|
return Array.from(this.webhooks.values());
|
|
216
310
|
}
|