@critical-path/core 0.28.0 → 0.29.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.
- package/CHANGELOG.md +21 -0
- package/README.md +4 -0
- package/dist/domain/events.d.ts +8 -2
- package/dist/domain/events.d.ts.map +1 -1
- package/dist/domain/events.js +1 -0
- package/dist/domain/events.js.map +1 -1
- package/dist/engine/cascade.test.d.ts +2 -0
- package/dist/engine/cascade.test.d.ts.map +1 -0
- package/dist/engine/cascade.test.js +99 -0
- package/dist/engine/cascade.test.js.map +1 -0
- package/dist/engine/index.d.ts +16 -1
- package/dist/engine/index.d.ts.map +1 -1
- package/dist/engine/index.js +89 -6
- package/dist/engine/index.js.map +1 -1
- package/dist/schemas/index.d.ts +6 -6
- package/dist/store/firebase.d.ts +8 -0
- package/dist/store/firebase.d.ts.map +1 -1
- package/dist/store/firebase.js +30 -8
- package/dist/store/firebase.js.map +1 -1
- package/dist/store/index.d.ts +7 -0
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +9 -0
- package/dist/store/index.js.map +1 -1
- package/dist/store/sqlite.d.ts +3 -0
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.js +41 -24
- package/dist/store/sqlite.js.map +1 -1
- package/dist/webhooks/webhooks.test.js +2 -1
- package/dist/webhooks/webhooks.test.js.map +1 -1
- package/package.json +1 -1
- package/src/domain/events.ts +7 -0
- package/src/engine/cascade.test.ts +121 -0
- package/src/engine/index.ts +96 -5
- package/src/store/firebase.ts +33 -8
- package/src/store/index.ts +16 -0
- package/src/store/sqlite.ts +56 -31
- package/src/webhooks/webhooks.test.ts +2 -1
- package/tsconfig.tsbuildinfo +1 -1
package/src/store/firebase.ts
CHANGED
|
@@ -104,6 +104,10 @@ export interface FirebaseStoreConfig {
|
|
|
104
104
|
db: FirestoreDBInterface;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Removes `undefined` values, which Firestore rejects. Update methods write the complete merged
|
|
109
|
+
* record without `{ merge: true }`, so a field cleared with `undefined` is actually removed.
|
|
110
|
+
*/
|
|
107
111
|
export function sanitizeFirestoreData<T>(obj: T): T {
|
|
108
112
|
if (obj === null || typeof obj !== 'object') {
|
|
109
113
|
return obj;
|
|
@@ -162,7 +166,7 @@ export class FirebaseStore implements StorageAdapter {
|
|
|
162
166
|
...updates,
|
|
163
167
|
updatedAt: new Date().toISOString()
|
|
164
168
|
};
|
|
165
|
-
await this.db.collection('projects').doc(id).set(sanitizeFirestoreData(updated)
|
|
169
|
+
await this.db.collection('projects').doc(id).set(sanitizeFirestoreData(updated));
|
|
166
170
|
return updated;
|
|
167
171
|
}
|
|
168
172
|
|
|
@@ -202,7 +206,7 @@ export class FirebaseStore implements StorageAdapter {
|
|
|
202
206
|
...updates,
|
|
203
207
|
updatedAt: new Date().toISOString()
|
|
204
208
|
};
|
|
205
|
-
await this.db.collection('workflows').doc(id).set(sanitizeFirestoreData(updated)
|
|
209
|
+
await this.db.collection('workflows').doc(id).set(sanitizeFirestoreData(updated));
|
|
206
210
|
return updated;
|
|
207
211
|
}
|
|
208
212
|
|
|
@@ -238,7 +242,7 @@ export class FirebaseStore implements StorageAdapter {
|
|
|
238
242
|
if (!existing) return null;
|
|
239
243
|
|
|
240
244
|
const updated: Team = { ...existing, ...updates, updatedAt: new Date().toISOString() };
|
|
241
|
-
await this.db.collection('teams').doc(id).set(sanitizeFirestoreData(updated)
|
|
245
|
+
await this.db.collection('teams').doc(id).set(sanitizeFirestoreData(updated));
|
|
242
246
|
return updated;
|
|
243
247
|
}
|
|
244
248
|
|
|
@@ -274,7 +278,7 @@ export class FirebaseStore implements StorageAdapter {
|
|
|
274
278
|
if (!existing) return null;
|
|
275
279
|
|
|
276
280
|
const updated: TaskContainer = { ...existing, ...updates, updatedAt: new Date().toISOString() };
|
|
277
|
-
await this.db.collection('containers').doc(id).set(sanitizeFirestoreData(updated)
|
|
281
|
+
await this.db.collection('containers').doc(id).set(sanitizeFirestoreData(updated));
|
|
278
282
|
return updated;
|
|
279
283
|
}
|
|
280
284
|
|
|
@@ -318,7 +322,7 @@ export class FirebaseStore implements StorageAdapter {
|
|
|
318
322
|
if (!existing) return null;
|
|
319
323
|
|
|
320
324
|
const updated: Deliverable = { ...existing, ...updates, updatedAt: new Date().toISOString() };
|
|
321
|
-
await this.db.collection('deliverables').doc(id).set(sanitizeFirestoreData(updated)
|
|
325
|
+
await this.db.collection('deliverables').doc(id).set(sanitizeFirestoreData(updated));
|
|
322
326
|
return updated;
|
|
323
327
|
}
|
|
324
328
|
|
|
@@ -363,7 +367,7 @@ export class FirebaseStore implements StorageAdapter {
|
|
|
363
367
|
...updates,
|
|
364
368
|
updatedAt: new Date().toISOString()
|
|
365
369
|
};
|
|
366
|
-
await this.db.collection('tasks').doc(id).set(sanitizeFirestoreData(updated)
|
|
370
|
+
await this.db.collection('tasks').doc(id).set(sanitizeFirestoreData(updated));
|
|
367
371
|
return updated;
|
|
368
372
|
}
|
|
369
373
|
|
|
@@ -399,7 +403,7 @@ export class FirebaseStore implements StorageAdapter {
|
|
|
399
403
|
if (!snap.exists) return null;
|
|
400
404
|
|
|
401
405
|
const updated: Iteration = { ...snap.data(), id, ...updates };
|
|
402
|
-
await this.db.collection('iterations').doc(id).set(sanitizeFirestoreData(updated)
|
|
406
|
+
await this.db.collection('iterations').doc(id).set(sanitizeFirestoreData(updated));
|
|
403
407
|
return updated;
|
|
404
408
|
}
|
|
405
409
|
|
|
@@ -439,7 +443,7 @@ export class FirebaseStore implements StorageAdapter {
|
|
|
439
443
|
...updates,
|
|
440
444
|
updatedAt: new Date().toISOString()
|
|
441
445
|
};
|
|
442
|
-
await this.db.collection('comments').doc(id).set(sanitizeFirestoreData(updated)
|
|
446
|
+
await this.db.collection('comments').doc(id).set(sanitizeFirestoreData(updated));
|
|
443
447
|
return updated;
|
|
444
448
|
}
|
|
445
449
|
|
|
@@ -567,6 +571,27 @@ export class FirebaseStore implements StorageAdapter {
|
|
|
567
571
|
return newDep;
|
|
568
572
|
}
|
|
569
573
|
|
|
574
|
+
async getDependency(id: string): Promise<TaskDependency | null> {
|
|
575
|
+
const doc = await this.db.collection('dependencies').doc(id).get();
|
|
576
|
+
return doc.exists ? ({ ...doc.data(), id: doc.id } as TaskDependency) : null;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
async removeDependency(id: string): Promise<boolean> {
|
|
580
|
+
return this.deleteDoc('dependencies', id);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
async deleteTimeEntry(id: string): Promise<boolean> {
|
|
584
|
+
return this.deleteDoc('time_entries', id);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
private async deleteDoc(collection: string, id: string): Promise<boolean> {
|
|
588
|
+
const ref = this.db.collection(collection).doc(id);
|
|
589
|
+
const doc = await ref.get();
|
|
590
|
+
if (!doc.exists) return false;
|
|
591
|
+
await ref.delete();
|
|
592
|
+
return true;
|
|
593
|
+
}
|
|
594
|
+
|
|
570
595
|
// --- Webhooks ---
|
|
571
596
|
async getWebhooks(): Promise<Webhook[]> {
|
|
572
597
|
const snap = await this.db.collection('webhooks').get();
|
package/src/store/index.ts
CHANGED
|
@@ -92,11 +92,15 @@ export interface ActivityRepository {
|
|
|
92
92
|
export interface TimeEntryRepository {
|
|
93
93
|
getTimeEntries(taskId: string): Promise<TimeEntry[]>;
|
|
94
94
|
logTime(entry: Omit<TimeEntry, 'id' | 'loggedAt'> & { loggedAt?: string }): Promise<TimeEntry>;
|
|
95
|
+
deleteTimeEntry(id: string): Promise<boolean>;
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
export interface DependencyRepository {
|
|
99
|
+
/** Dependencies where `taskId` is either the dependent or the upstream task. */
|
|
98
100
|
getDependencies(taskId: string): Promise<TaskDependency[]>;
|
|
101
|
+
getDependency(id: string): Promise<TaskDependency | null>;
|
|
99
102
|
addDependency(dep: Omit<TaskDependency, 'id'>): Promise<TaskDependency>;
|
|
103
|
+
removeDependency(id: string): Promise<boolean>;
|
|
100
104
|
}
|
|
101
105
|
|
|
102
106
|
export interface WebhookRepository {
|
|
@@ -522,6 +526,10 @@ export class InMemoryStore implements StorageAdapter {
|
|
|
522
526
|
return newEntry;
|
|
523
527
|
}
|
|
524
528
|
|
|
529
|
+
async deleteTimeEntry(id: string): Promise<boolean> {
|
|
530
|
+
return this.timeEntries.delete(id);
|
|
531
|
+
}
|
|
532
|
+
|
|
525
533
|
// Dependencies
|
|
526
534
|
async getDependencies(taskId: string): Promise<TaskDependency[]> {
|
|
527
535
|
return Array.from(this.dependencies.values()).filter(
|
|
@@ -529,6 +537,10 @@ export class InMemoryStore implements StorageAdapter {
|
|
|
529
537
|
);
|
|
530
538
|
}
|
|
531
539
|
|
|
540
|
+
async getDependency(id: string): Promise<TaskDependency | null> {
|
|
541
|
+
return this.dependencies.get(id) ?? null;
|
|
542
|
+
}
|
|
543
|
+
|
|
532
544
|
async addDependency(dep: Omit<TaskDependency, 'id'>): Promise<TaskDependency> {
|
|
533
545
|
const id = `dep_${Math.random().toString(36).substring(2, 9)}`;
|
|
534
546
|
const newDep: TaskDependency = { ...dep, id };
|
|
@@ -536,6 +548,10 @@ export class InMemoryStore implements StorageAdapter {
|
|
|
536
548
|
return newDep;
|
|
537
549
|
}
|
|
538
550
|
|
|
551
|
+
async removeDependency(id: string): Promise<boolean> {
|
|
552
|
+
return this.dependencies.delete(id);
|
|
553
|
+
}
|
|
554
|
+
|
|
539
555
|
// Webhooks
|
|
540
556
|
async getWebhooks(): Promise<Webhook[]> {
|
|
541
557
|
return Array.from(this.webhooks.values());
|
package/src/store/sqlite.ts
CHANGED
|
@@ -18,6 +18,14 @@ import type {
|
|
|
18
18
|
} from '../types/index.js';
|
|
19
19
|
import { generateProjectKey } from '../utils/key.js';
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* SQLite returns NULL columns as `null`, while domain objects leave unset optional fields out.
|
|
23
|
+
* Dropping nulls keeps SQLiteStore results identical to the other adapters.
|
|
24
|
+
*/
|
|
25
|
+
function dropNulls<T extends object>(row: T): T {
|
|
26
|
+
return Object.fromEntries(Object.entries(row).filter(([, value]) => value !== null)) as T;
|
|
27
|
+
}
|
|
28
|
+
|
|
21
29
|
export interface SQLiteStoreConfig {
|
|
22
30
|
/**
|
|
23
31
|
* Database file path (e.g., 'critical-path.db' or ':memory:').
|
|
@@ -516,13 +524,13 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
516
524
|
async getContainers(projectId: string): Promise<TaskContainer[]> {
|
|
517
525
|
const stmt = this.db.prepare('SELECT * FROM containers WHERE projectId = ?');
|
|
518
526
|
const rows = stmt.all(projectId) as any[];
|
|
519
|
-
return rows;
|
|
527
|
+
return rows.map(dropNulls);
|
|
520
528
|
}
|
|
521
529
|
|
|
522
530
|
async getContainer(id: string): Promise<TaskContainer | null> {
|
|
523
531
|
const stmt = this.db.prepare('SELECT * FROM containers WHERE id = ?');
|
|
524
532
|
const row = stmt.get(id) as any;
|
|
525
|
-
return row
|
|
533
|
+
return row ? dropNulls(row) : null;
|
|
526
534
|
}
|
|
527
535
|
|
|
528
536
|
async createContainer(container: Omit<TaskContainer, 'id' | 'createdAt' | 'updatedAt'>): Promise<TaskContainer> {
|
|
@@ -821,13 +829,13 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
821
829
|
async getIterations(projectId: string): Promise<Iteration[]> {
|
|
822
830
|
const stmt = this.db.prepare('SELECT * FROM iterations WHERE projectId = ?');
|
|
823
831
|
const rows = stmt.all(projectId) as any[];
|
|
824
|
-
return rows;
|
|
832
|
+
return rows.map(dropNulls);
|
|
825
833
|
}
|
|
826
834
|
|
|
827
835
|
async getIteration(id: string): Promise<Iteration | null> {
|
|
828
836
|
const stmt = this.db.prepare('SELECT * FROM iterations WHERE id = ?');
|
|
829
837
|
const row = stmt.get(id) as any;
|
|
830
|
-
return row
|
|
838
|
+
return row ? dropNulls(row) : null;
|
|
831
839
|
}
|
|
832
840
|
|
|
833
841
|
async createIteration(iteration: Omit<Iteration, 'id' | 'createdAt'>): Promise<Iteration> {
|
|
@@ -883,7 +891,7 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
883
891
|
|
|
884
892
|
// --- Comments ---
|
|
885
893
|
private mapComment(row: any): Comment {
|
|
886
|
-
return {
|
|
894
|
+
return dropNulls({
|
|
887
895
|
id: row.id,
|
|
888
896
|
taskId: row.taskId,
|
|
889
897
|
authorId: row.authorId,
|
|
@@ -895,7 +903,7 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
895
903
|
metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
|
|
896
904
|
createdAt: row.createdAt,
|
|
897
905
|
updatedAt: row.updatedAt
|
|
898
|
-
};
|
|
906
|
+
});
|
|
899
907
|
}
|
|
900
908
|
|
|
901
909
|
async getComments(taskId: string): Promise<Comment[]> {
|
|
@@ -1028,22 +1036,24 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
1028
1036
|
|
|
1029
1037
|
const stmt = this.db.prepare(sql);
|
|
1030
1038
|
const rows = stmt.all(...params) as any[];
|
|
1031
|
-
return rows.map((r) =>
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1039
|
+
return rows.map((r) =>
|
|
1040
|
+
dropNulls({
|
|
1041
|
+
...r,
|
|
1042
|
+
sizeBytes: Number(r.sizeBytes),
|
|
1043
|
+
metadata: r.metadata ? JSON.parse(r.metadata) : undefined
|
|
1044
|
+
})
|
|
1045
|
+
);
|
|
1036
1046
|
}
|
|
1037
1047
|
|
|
1038
1048
|
async getAttachment(id: string): Promise<Attachment | null> {
|
|
1039
1049
|
const stmt = this.db.prepare('SELECT * FROM attachments WHERE id = ?');
|
|
1040
1050
|
const row = stmt.get(id) as any;
|
|
1041
1051
|
if (!row) return null;
|
|
1042
|
-
return {
|
|
1052
|
+
return dropNulls({
|
|
1043
1053
|
...row,
|
|
1044
1054
|
sizeBytes: Number(row.sizeBytes),
|
|
1045
1055
|
metadata: row.metadata ? JSON.parse(row.metadata) : undefined
|
|
1046
|
-
};
|
|
1056
|
+
});
|
|
1047
1057
|
}
|
|
1048
1058
|
|
|
1049
1059
|
async createAttachment(attachment: Omit<Attachment, 'id' | 'createdAt' | 'updatedAt'>): Promise<Attachment> {
|
|
@@ -1106,10 +1116,12 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
1106
1116
|
|
|
1107
1117
|
const stmt = this.db.prepare(sql);
|
|
1108
1118
|
const rows = stmt.all(...params) as any[];
|
|
1109
|
-
return rows.map((r) =>
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1119
|
+
return rows.map((r) =>
|
|
1120
|
+
dropNulls({
|
|
1121
|
+
...r,
|
|
1122
|
+
details: r.details ? JSON.parse(r.details) : undefined
|
|
1123
|
+
})
|
|
1124
|
+
);
|
|
1113
1125
|
}
|
|
1114
1126
|
|
|
1115
1127
|
async logActivity(activity: Omit<Activity, 'id' | 'createdAt'>): Promise<Activity> {
|
|
@@ -1137,7 +1149,7 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
1137
1149
|
async getTimeEntries(taskId: string): Promise<TimeEntry[]> {
|
|
1138
1150
|
const stmt = this.db.prepare('SELECT * FROM time_entries WHERE taskId = ?');
|
|
1139
1151
|
const rows = stmt.all(taskId) as any[];
|
|
1140
|
-
return rows.map((r) => ({ ...r, isBillable: r.isBillable !== null ? Boolean(r.isBillable) : undefined }));
|
|
1152
|
+
return rows.map((r) => dropNulls({ ...r, isBillable: r.isBillable !== null ? Boolean(r.isBillable) : undefined }));
|
|
1141
1153
|
}
|
|
1142
1154
|
|
|
1143
1155
|
async logTime(entry: Omit<TimeEntry, 'id' | 'loggedAt'> & { loggedAt?: string }): Promise<TimeEntry> {
|
|
@@ -1164,7 +1176,7 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
1164
1176
|
// --- Dependencies ---
|
|
1165
1177
|
async getDependencies(taskId: string): Promise<TaskDependency[]> {
|
|
1166
1178
|
const stmt = this.db.prepare('SELECT * FROM dependencies WHERE taskId = ? OR dependsOnTaskId = ?');
|
|
1167
|
-
return stmt.all(taskId, taskId) as any[];
|
|
1179
|
+
return (stmt.all(taskId, taskId) as any[]).map(dropNulls);
|
|
1168
1180
|
}
|
|
1169
1181
|
|
|
1170
1182
|
async addDependency(dep: Omit<TaskDependency, 'id'>): Promise<TaskDependency> {
|
|
@@ -1179,6 +1191,19 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
1179
1191
|
return newDep;
|
|
1180
1192
|
}
|
|
1181
1193
|
|
|
1194
|
+
async getDependency(id: string): Promise<TaskDependency | null> {
|
|
1195
|
+
const row = this.db.prepare('SELECT * FROM dependencies WHERE id = ?').get(id) as any;
|
|
1196
|
+
return row ? { id: row.id, taskId: row.taskId, dependsOnTaskId: row.dependsOnTaskId, type: row.type } : null;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
async removeDependency(id: string): Promise<boolean> {
|
|
1200
|
+
return Number(this.db.prepare('DELETE FROM dependencies WHERE id = ?').run(id).changes) > 0;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
async deleteTimeEntry(id: string): Promise<boolean> {
|
|
1204
|
+
return Number(this.db.prepare('DELETE FROM time_entries WHERE id = ?').run(id).changes) > 0;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1182
1207
|
// --- Webhooks ---
|
|
1183
1208
|
async getWebhooks(): Promise<Webhook[]> {
|
|
1184
1209
|
const stmt = this.db.prepare('SELECT * FROM webhooks');
|
|
@@ -1215,7 +1240,7 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
1215
1240
|
}
|
|
1216
1241
|
|
|
1217
1242
|
private mapWebhook(row: any): Webhook {
|
|
1218
|
-
return {
|
|
1243
|
+
return dropNulls({
|
|
1219
1244
|
id: row.id,
|
|
1220
1245
|
name: row.name ?? '',
|
|
1221
1246
|
url: row.url,
|
|
@@ -1224,7 +1249,7 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
1224
1249
|
active: Boolean(row.active),
|
|
1225
1250
|
tenantId: row.tenantId ?? undefined,
|
|
1226
1251
|
createdAt: row.createdAt
|
|
1227
|
-
};
|
|
1252
|
+
});
|
|
1228
1253
|
}
|
|
1229
1254
|
|
|
1230
1255
|
async addWebhook(webhook: Omit<Webhook, 'id' | 'createdAt'>): Promise<Webhook> {
|
|
@@ -1251,7 +1276,7 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
1251
1276
|
|
|
1252
1277
|
// Helper mappers
|
|
1253
1278
|
private mapProject(row: any): Project {
|
|
1254
|
-
return {
|
|
1279
|
+
return dropNulls({
|
|
1255
1280
|
...row,
|
|
1256
1281
|
tenantId: row.tenantId ?? undefined,
|
|
1257
1282
|
workflowId: row.workflowId || undefined,
|
|
@@ -1261,30 +1286,30 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
1261
1286
|
statusDefinitions: row.statusDefinitions ? JSON.parse(row.statusDefinitions) : [],
|
|
1262
1287
|
priorityDefinitions: row.priorityDefinitions ? JSON.parse(row.priorityDefinitions) : [],
|
|
1263
1288
|
customFieldDefinitions: row.customFieldDefinitions ? JSON.parse(row.customFieldDefinitions) : []
|
|
1264
|
-
};
|
|
1289
|
+
});
|
|
1265
1290
|
}
|
|
1266
1291
|
|
|
1267
1292
|
private mapWorkflow(row: any): Workflow {
|
|
1268
|
-
return {
|
|
1293
|
+
return dropNulls({
|
|
1269
1294
|
...row,
|
|
1270
1295
|
tenantId: row.tenantId ?? undefined,
|
|
1271
1296
|
statuses: row.statuses ? JSON.parse(row.statuses) : [],
|
|
1272
1297
|
transitions: row.transitions ? JSON.parse(row.transitions) : [],
|
|
1273
1298
|
taskTypes: row.taskTypes ? JSON.parse(row.taskTypes) : undefined,
|
|
1274
1299
|
isDefault: row.isDefault !== null && row.isDefault !== undefined ? Boolean(row.isDefault) : undefined
|
|
1275
|
-
};
|
|
1300
|
+
});
|
|
1276
1301
|
}
|
|
1277
1302
|
|
|
1278
1303
|
private mapTeam(row: any): Team {
|
|
1279
|
-
return {
|
|
1304
|
+
return dropNulls({
|
|
1280
1305
|
...row,
|
|
1281
1306
|
tenantId: row.tenantId ?? undefined,
|
|
1282
1307
|
memberIds: row.memberIds ? JSON.parse(row.memberIds) : []
|
|
1283
|
-
};
|
|
1308
|
+
});
|
|
1284
1309
|
}
|
|
1285
1310
|
|
|
1286
1311
|
private mapTask(row: any): Task {
|
|
1287
|
-
return {
|
|
1312
|
+
return dropNulls({
|
|
1288
1313
|
...row,
|
|
1289
1314
|
actualDurationSeconds: row.actualDurationSeconds ?? undefined,
|
|
1290
1315
|
inProgressSince: row.inProgressSince || undefined,
|
|
@@ -1299,15 +1324,15 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
1299
1324
|
tags: row.tags ? JSON.parse(row.tags) : [],
|
|
1300
1325
|
assignees: row.assignees ? JSON.parse(row.assignees) : undefined,
|
|
1301
1326
|
customFields: row.customFields ? JSON.parse(row.customFields) : {}
|
|
1302
|
-
};
|
|
1327
|
+
});
|
|
1303
1328
|
}
|
|
1304
1329
|
|
|
1305
1330
|
private mapDeliverable(row: any): Deliverable {
|
|
1306
|
-
return {
|
|
1331
|
+
return dropNulls({
|
|
1307
1332
|
...row,
|
|
1308
1333
|
specs: row.specs ? JSON.parse(row.specs) : undefined,
|
|
1309
1334
|
outputUrls: row.outputUrls ? JSON.parse(row.outputUrls) : [],
|
|
1310
1335
|
customFields: row.customFields ? JSON.parse(row.customFields) : {}
|
|
1311
|
-
};
|
|
1336
|
+
});
|
|
1312
1337
|
}
|
|
1313
1338
|
}
|
|
@@ -78,7 +78,8 @@ describe('webhook delivery', () => {
|
|
|
78
78
|
await engine.createTask({ projectId: project.id, title: 'T' });
|
|
79
79
|
await engine.webhooks.idle();
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
// Deliveries are asynchronous, so arrival order is not guaranteed
|
|
82
|
+
expect(deliveries.map((d) => d.body.event).sort()).toEqual(['project.created', 'task.created']);
|
|
82
83
|
expect(deliveries.every((d) => d.url.endsWith('/all'))).toBe(true);
|
|
83
84
|
});
|
|
84
85
|
|
package/tsconfig.tsbuildinfo
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/deliverables.test.ts","./src/index.test.ts","./src/index.ts","./src/status.test.ts","./src/task-state.test.ts","./src/use-case-validation.test.ts","./src/domain/authorization.ts","./src/domain/calendar.test.ts","./src/domain/calendar.ts","./src/domain/cpm.test.ts","./src/domain/cpm.ts","./src/domain/custom-fields.ts","./src/domain/ddd.test.ts","./src/domain/entities.ts","./src/domain/errors.ts","./src/domain/events.ts","./src/domain/graph.ts","./src/domain/index.ts","./src/domain/ladder.test.ts","./src/domain/ladder.ts","./src/domain/metrics.test.ts","./src/domain/metrics.ts","./src/domain/task-duration.test.ts","./src/domain/workload.test.ts","./src/domain/workload.ts","./src/engine/actor.test.ts","./src/engine/authorization.test.ts","./src/engine/index.ts","./src/plugins/index.ts","./src/plugins/plugins.test.ts","./src/schemas/index.ts","./src/schemas/schemas.test.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/fractional-index.test.ts","./src/utils/fractional-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","./src/webhooks/dispatcher.ts","./src/webhooks/index.ts","./src/webhooks/signature.ts","./src/webhooks/webhooks.test.ts"],"version":"5.9.3"}
|
|
1
|
+
{"root":["./src/deliverables.test.ts","./src/index.test.ts","./src/index.ts","./src/status.test.ts","./src/task-state.test.ts","./src/use-case-validation.test.ts","./src/domain/authorization.ts","./src/domain/calendar.test.ts","./src/domain/calendar.ts","./src/domain/cpm.test.ts","./src/domain/cpm.ts","./src/domain/custom-fields.ts","./src/domain/ddd.test.ts","./src/domain/entities.ts","./src/domain/errors.ts","./src/domain/events.ts","./src/domain/graph.ts","./src/domain/index.ts","./src/domain/ladder.test.ts","./src/domain/ladder.ts","./src/domain/metrics.test.ts","./src/domain/metrics.ts","./src/domain/task-duration.test.ts","./src/domain/workload.test.ts","./src/domain/workload.ts","./src/engine/actor.test.ts","./src/engine/authorization.test.ts","./src/engine/cascade.test.ts","./src/engine/index.ts","./src/plugins/index.ts","./src/plugins/plugins.test.ts","./src/schemas/index.ts","./src/schemas/schemas.test.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/fractional-index.test.ts","./src/utils/fractional-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","./src/webhooks/dispatcher.ts","./src/webhooks/index.ts","./src/webhooks/signature.ts","./src/webhooks/webhooks.test.ts"],"version":"5.9.3"}
|