@critical-path/core 0.28.0 → 0.30.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 +43 -0
- package/README.md +14 -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 +25 -2
- package/dist/engine/index.d.ts.map +1 -1
- package/dist/engine/index.js +117 -6
- package/dist/engine/index.js.map +1 -1
- package/dist/schemas/index.d.ts +81 -28
- package/dist/schemas/index.d.ts.map +1 -1
- package/dist/schemas/index.js +26 -0
- package/dist/schemas/index.js.map +1 -1
- package/dist/store/firebase.d.ts +12 -0
- package/dist/store/firebase.d.ts.map +1 -1
- package/dist/store/firebase.js +40 -8
- package/dist/store/firebase.js.map +1 -1
- package/dist/store/index.d.ts +15 -0
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +19 -0
- package/dist/store/index.js.map +1 -1
- package/dist/store/query.d.ts +50 -0
- package/dist/store/query.d.ts.map +1 -0
- package/dist/store/query.js +79 -0
- package/dist/store/query.js.map +1 -0
- package/dist/store/query.test.d.ts +2 -0
- package/dist/store/query.test.d.ts.map +1 -0
- package/dist/store/query.test.js +89 -0
- package/dist/store/query.test.js.map +1 -0
- package/dist/store/sqlite.d.ts +6 -0
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.js +131 -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 +126 -6
- package/src/schemas/index.ts +31 -0
- package/src/store/firebase.ts +47 -8
- package/src/store/index.ts +40 -0
- package/src/store/query.test.ts +110 -0
- package/src/store/query.ts +113 -0
- package/src/store/sqlite.ts +149 -31
- package/src/webhooks/webhooks.test.ts +2 -1
- package/tsconfig.tsbuildinfo +1 -1
package/src/store/sqlite.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { DatabaseSync } from 'node:sqlite';
|
|
2
2
|
import type { StorageAdapter } from './index.js';
|
|
3
|
+
import { decodeCursor, encodeCursor, pageSize, type ActivityQuery, type Page, type TaskQuery } from './query.js';
|
|
3
4
|
import type {
|
|
4
5
|
Project,
|
|
5
6
|
Task,
|
|
@@ -18,6 +19,14 @@ import type {
|
|
|
18
19
|
} from '../types/index.js';
|
|
19
20
|
import { generateProjectKey } from '../utils/key.js';
|
|
20
21
|
|
|
22
|
+
/**
|
|
23
|
+
* SQLite returns NULL columns as `null`, while domain objects leave unset optional fields out.
|
|
24
|
+
* Dropping nulls keeps SQLiteStore results identical to the other adapters.
|
|
25
|
+
*/
|
|
26
|
+
function dropNulls<T extends object>(row: T): T {
|
|
27
|
+
return Object.fromEntries(Object.entries(row).filter(([, value]) => value !== null)) as T;
|
|
28
|
+
}
|
|
29
|
+
|
|
21
30
|
export interface SQLiteStoreConfig {
|
|
22
31
|
/**
|
|
23
32
|
* Database file path (e.g., 'critical-path.db' or ':memory:').
|
|
@@ -280,6 +289,17 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
280
289
|
// Column may already exist
|
|
281
290
|
}
|
|
282
291
|
|
|
292
|
+
// Indexes for filtered queries and keyset pagination
|
|
293
|
+
this.db.exec(`
|
|
294
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_project_created ON tasks (projectId, createdAt, id);
|
|
295
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_parent ON tasks (parentId);
|
|
296
|
+
CREATE INDEX IF NOT EXISTS idx_activities_project_created ON activities (projectId, createdAt, id);
|
|
297
|
+
CREATE INDEX IF NOT EXISTS idx_activities_task_created ON activities (taskId, createdAt, id);
|
|
298
|
+
CREATE INDEX IF NOT EXISTS idx_comments_task ON comments (taskId);
|
|
299
|
+
CREATE INDEX IF NOT EXISTS idx_dependencies_task ON dependencies (taskId);
|
|
300
|
+
CREATE INDEX IF NOT EXISTS idx_dependencies_upstream ON dependencies (dependsOnTaskId);
|
|
301
|
+
`);
|
|
302
|
+
|
|
283
303
|
for (const column of ['name TEXT', 'tenantId TEXT']) {
|
|
284
304
|
try {
|
|
285
305
|
this.db.exec(`ALTER TABLE webhooks ADD COLUMN ${column}`);
|
|
@@ -516,13 +536,13 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
516
536
|
async getContainers(projectId: string): Promise<TaskContainer[]> {
|
|
517
537
|
const stmt = this.db.prepare('SELECT * FROM containers WHERE projectId = ?');
|
|
518
538
|
const rows = stmt.all(projectId) as any[];
|
|
519
|
-
return rows;
|
|
539
|
+
return rows.map(dropNulls);
|
|
520
540
|
}
|
|
521
541
|
|
|
522
542
|
async getContainer(id: string): Promise<TaskContainer | null> {
|
|
523
543
|
const stmt = this.db.prepare('SELECT * FROM containers WHERE id = ?');
|
|
524
544
|
const row = stmt.get(id) as any;
|
|
525
|
-
return row
|
|
545
|
+
return row ? dropNulls(row) : null;
|
|
526
546
|
}
|
|
527
547
|
|
|
528
548
|
async createContainer(container: Omit<TaskContainer, 'id' | 'createdAt' | 'updatedAt'>): Promise<TaskContainer> {
|
|
@@ -680,6 +700,55 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
680
700
|
return rows.map((r) => this.mapTask(r));
|
|
681
701
|
}
|
|
682
702
|
|
|
703
|
+
async queryTasks(query: TaskQuery): Promise<Page<Task>> {
|
|
704
|
+
const where: string[] = [];
|
|
705
|
+
const params: Array<string | number> = [];
|
|
706
|
+
const inList = (column: string, values: string[]) => {
|
|
707
|
+
where.push(`${column} IN (${values.map(() => '?').join(', ')})`);
|
|
708
|
+
params.push(...values);
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
if (query.projectId) {
|
|
712
|
+
where.push('projectId = ?');
|
|
713
|
+
params.push(query.projectId);
|
|
714
|
+
}
|
|
715
|
+
if (query.projectIds) {
|
|
716
|
+
if (query.projectIds.length === 0) return { items: [] };
|
|
717
|
+
inList('projectId', query.projectIds);
|
|
718
|
+
}
|
|
719
|
+
if (query.status?.length) inList('status', query.status);
|
|
720
|
+
if (query.priority?.length) inList('priority', query.priority);
|
|
721
|
+
for (const column of ['iterationId', 'deliverableId', 'containerId'] as const) {
|
|
722
|
+
if (query[column]) {
|
|
723
|
+
where.push(`${column} = ?`);
|
|
724
|
+
params.push(query[column]!);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
if (query.parentId === null) where.push('parentId IS NULL');
|
|
728
|
+
else if (query.parentId) {
|
|
729
|
+
where.push('parentId = ?');
|
|
730
|
+
params.push(query.parentId);
|
|
731
|
+
}
|
|
732
|
+
if (query.assigneeId) {
|
|
733
|
+
where.push(
|
|
734
|
+
"(assigneeId = ? OR EXISTS (SELECT 1 FROM json_each(COALESCE(assignees, '[]')) WHERE json_extract(value, '$.id') = ?))"
|
|
735
|
+
);
|
|
736
|
+
params.push(query.assigneeId, query.assigneeId);
|
|
737
|
+
}
|
|
738
|
+
const after = decodeCursor(query.cursor);
|
|
739
|
+
if (after) {
|
|
740
|
+
where.push('(createdAt > ? OR (createdAt = ? AND id > ?))');
|
|
741
|
+
params.push(after[0], after[0], after[1]);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const size = pageSize(query.limit);
|
|
745
|
+
const sql = `SELECT * FROM tasks ${where.length ? `WHERE ${where.join(' AND ')}` : ''} ORDER BY createdAt ASC, id ASC LIMIT ?`;
|
|
746
|
+
const rows = this.db.prepare(sql).all(...params, size + 1) as any[];
|
|
747
|
+
const items = rows.slice(0, size).map((r) => this.mapTask(r));
|
|
748
|
+
const last = items[items.length - 1];
|
|
749
|
+
return { items, nextCursor: rows.length > size && last ? encodeCursor(last.createdAt, last.id) : undefined };
|
|
750
|
+
}
|
|
751
|
+
|
|
683
752
|
async getTask(id: string): Promise<Task | null> {
|
|
684
753
|
const stmt = this.db.prepare('SELECT * FROM tasks WHERE id = ?');
|
|
685
754
|
const row = stmt.get(id) as any;
|
|
@@ -821,13 +890,13 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
821
890
|
async getIterations(projectId: string): Promise<Iteration[]> {
|
|
822
891
|
const stmt = this.db.prepare('SELECT * FROM iterations WHERE projectId = ?');
|
|
823
892
|
const rows = stmt.all(projectId) as any[];
|
|
824
|
-
return rows;
|
|
893
|
+
return rows.map(dropNulls);
|
|
825
894
|
}
|
|
826
895
|
|
|
827
896
|
async getIteration(id: string): Promise<Iteration | null> {
|
|
828
897
|
const stmt = this.db.prepare('SELECT * FROM iterations WHERE id = ?');
|
|
829
898
|
const row = stmt.get(id) as any;
|
|
830
|
-
return row
|
|
899
|
+
return row ? dropNulls(row) : null;
|
|
831
900
|
}
|
|
832
901
|
|
|
833
902
|
async createIteration(iteration: Omit<Iteration, 'id' | 'createdAt'>): Promise<Iteration> {
|
|
@@ -883,7 +952,7 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
883
952
|
|
|
884
953
|
// --- Comments ---
|
|
885
954
|
private mapComment(row: any): Comment {
|
|
886
|
-
return {
|
|
955
|
+
return dropNulls({
|
|
887
956
|
id: row.id,
|
|
888
957
|
taskId: row.taskId,
|
|
889
958
|
authorId: row.authorId,
|
|
@@ -895,7 +964,7 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
895
964
|
metadata: row.metadata ? JSON.parse(row.metadata) : undefined,
|
|
896
965
|
createdAt: row.createdAt,
|
|
897
966
|
updatedAt: row.updatedAt
|
|
898
|
-
};
|
|
967
|
+
});
|
|
899
968
|
}
|
|
900
969
|
|
|
901
970
|
async getComments(taskId: string): Promise<Comment[]> {
|
|
@@ -1028,22 +1097,24 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
1028
1097
|
|
|
1029
1098
|
const stmt = this.db.prepare(sql);
|
|
1030
1099
|
const rows = stmt.all(...params) as any[];
|
|
1031
|
-
return rows.map((r) =>
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1100
|
+
return rows.map((r) =>
|
|
1101
|
+
dropNulls({
|
|
1102
|
+
...r,
|
|
1103
|
+
sizeBytes: Number(r.sizeBytes),
|
|
1104
|
+
metadata: r.metadata ? JSON.parse(r.metadata) : undefined
|
|
1105
|
+
})
|
|
1106
|
+
);
|
|
1036
1107
|
}
|
|
1037
1108
|
|
|
1038
1109
|
async getAttachment(id: string): Promise<Attachment | null> {
|
|
1039
1110
|
const stmt = this.db.prepare('SELECT * FROM attachments WHERE id = ?');
|
|
1040
1111
|
const row = stmt.get(id) as any;
|
|
1041
1112
|
if (!row) return null;
|
|
1042
|
-
return {
|
|
1113
|
+
return dropNulls({
|
|
1043
1114
|
...row,
|
|
1044
1115
|
sizeBytes: Number(row.sizeBytes),
|
|
1045
1116
|
metadata: row.metadata ? JSON.parse(row.metadata) : undefined
|
|
1046
|
-
};
|
|
1117
|
+
});
|
|
1047
1118
|
}
|
|
1048
1119
|
|
|
1049
1120
|
async createAttachment(attachment: Omit<Attachment, 'id' | 'createdAt' | 'updatedAt'>): Promise<Attachment> {
|
|
@@ -1106,10 +1177,44 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
1106
1177
|
|
|
1107
1178
|
const stmt = this.db.prepare(sql);
|
|
1108
1179
|
const rows = stmt.all(...params) as any[];
|
|
1109
|
-
return rows.map((r) =>
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1180
|
+
return rows.map((r) =>
|
|
1181
|
+
dropNulls({
|
|
1182
|
+
...r,
|
|
1183
|
+
details: r.details ? JSON.parse(r.details) : undefined
|
|
1184
|
+
})
|
|
1185
|
+
);
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
async queryActivities(query: ActivityQuery): Promise<Page<Activity>> {
|
|
1189
|
+
const where: string[] = [];
|
|
1190
|
+
const params: Array<string | number> = [];
|
|
1191
|
+
if (query.projectId) {
|
|
1192
|
+
where.push('projectId = ?');
|
|
1193
|
+
params.push(query.projectId);
|
|
1194
|
+
}
|
|
1195
|
+
if (query.projectIds) {
|
|
1196
|
+
if (query.projectIds.length === 0) return { items: [] };
|
|
1197
|
+
where.push(`projectId IN (${query.projectIds.map(() => '?').join(', ')})`);
|
|
1198
|
+
params.push(...query.projectIds);
|
|
1199
|
+
}
|
|
1200
|
+
if (query.taskId) {
|
|
1201
|
+
where.push('taskId = ?');
|
|
1202
|
+
params.push(query.taskId);
|
|
1203
|
+
}
|
|
1204
|
+
const after = decodeCursor(query.cursor);
|
|
1205
|
+
if (after) {
|
|
1206
|
+
where.push('(createdAt < ? OR (createdAt = ? AND id < ?))');
|
|
1207
|
+
params.push(after[0], after[0], after[1]);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
const size = pageSize(query.limit);
|
|
1211
|
+
const sql = `SELECT * FROM activities ${where.length ? `WHERE ${where.join(' AND ')}` : ''} ORDER BY createdAt DESC, id DESC LIMIT ?`;
|
|
1212
|
+
const rows = this.db.prepare(sql).all(...params, size + 1) as any[];
|
|
1213
|
+
const items: Activity[] = rows
|
|
1214
|
+
.slice(0, size)
|
|
1215
|
+
.map((r) => dropNulls({ ...r, details: r.details ? JSON.parse(r.details) : undefined }));
|
|
1216
|
+
const last = items[items.length - 1];
|
|
1217
|
+
return { items, nextCursor: rows.length > size && last ? encodeCursor(last.createdAt, last.id) : undefined };
|
|
1113
1218
|
}
|
|
1114
1219
|
|
|
1115
1220
|
async logActivity(activity: Omit<Activity, 'id' | 'createdAt'>): Promise<Activity> {
|
|
@@ -1137,7 +1242,7 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
1137
1242
|
async getTimeEntries(taskId: string): Promise<TimeEntry[]> {
|
|
1138
1243
|
const stmt = this.db.prepare('SELECT * FROM time_entries WHERE taskId = ?');
|
|
1139
1244
|
const rows = stmt.all(taskId) as any[];
|
|
1140
|
-
return rows.map((r) => ({ ...r, isBillable: r.isBillable !== null ? Boolean(r.isBillable) : undefined }));
|
|
1245
|
+
return rows.map((r) => dropNulls({ ...r, isBillable: r.isBillable !== null ? Boolean(r.isBillable) : undefined }));
|
|
1141
1246
|
}
|
|
1142
1247
|
|
|
1143
1248
|
async logTime(entry: Omit<TimeEntry, 'id' | 'loggedAt'> & { loggedAt?: string }): Promise<TimeEntry> {
|
|
@@ -1164,7 +1269,7 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
1164
1269
|
// --- Dependencies ---
|
|
1165
1270
|
async getDependencies(taskId: string): Promise<TaskDependency[]> {
|
|
1166
1271
|
const stmt = this.db.prepare('SELECT * FROM dependencies WHERE taskId = ? OR dependsOnTaskId = ?');
|
|
1167
|
-
return stmt.all(taskId, taskId) as any[];
|
|
1272
|
+
return (stmt.all(taskId, taskId) as any[]).map(dropNulls);
|
|
1168
1273
|
}
|
|
1169
1274
|
|
|
1170
1275
|
async addDependency(dep: Omit<TaskDependency, 'id'>): Promise<TaskDependency> {
|
|
@@ -1179,6 +1284,19 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
1179
1284
|
return newDep;
|
|
1180
1285
|
}
|
|
1181
1286
|
|
|
1287
|
+
async getDependency(id: string): Promise<TaskDependency | null> {
|
|
1288
|
+
const row = this.db.prepare('SELECT * FROM dependencies WHERE id = ?').get(id) as any;
|
|
1289
|
+
return row ? { id: row.id, taskId: row.taskId, dependsOnTaskId: row.dependsOnTaskId, type: row.type } : null;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
async removeDependency(id: string): Promise<boolean> {
|
|
1293
|
+
return Number(this.db.prepare('DELETE FROM dependencies WHERE id = ?').run(id).changes) > 0;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
async deleteTimeEntry(id: string): Promise<boolean> {
|
|
1297
|
+
return Number(this.db.prepare('DELETE FROM time_entries WHERE id = ?').run(id).changes) > 0;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1182
1300
|
// --- Webhooks ---
|
|
1183
1301
|
async getWebhooks(): Promise<Webhook[]> {
|
|
1184
1302
|
const stmt = this.db.prepare('SELECT * FROM webhooks');
|
|
@@ -1215,7 +1333,7 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
1215
1333
|
}
|
|
1216
1334
|
|
|
1217
1335
|
private mapWebhook(row: any): Webhook {
|
|
1218
|
-
return {
|
|
1336
|
+
return dropNulls({
|
|
1219
1337
|
id: row.id,
|
|
1220
1338
|
name: row.name ?? '',
|
|
1221
1339
|
url: row.url,
|
|
@@ -1224,7 +1342,7 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
1224
1342
|
active: Boolean(row.active),
|
|
1225
1343
|
tenantId: row.tenantId ?? undefined,
|
|
1226
1344
|
createdAt: row.createdAt
|
|
1227
|
-
};
|
|
1345
|
+
});
|
|
1228
1346
|
}
|
|
1229
1347
|
|
|
1230
1348
|
async addWebhook(webhook: Omit<Webhook, 'id' | 'createdAt'>): Promise<Webhook> {
|
|
@@ -1251,7 +1369,7 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
1251
1369
|
|
|
1252
1370
|
// Helper mappers
|
|
1253
1371
|
private mapProject(row: any): Project {
|
|
1254
|
-
return {
|
|
1372
|
+
return dropNulls({
|
|
1255
1373
|
...row,
|
|
1256
1374
|
tenantId: row.tenantId ?? undefined,
|
|
1257
1375
|
workflowId: row.workflowId || undefined,
|
|
@@ -1261,30 +1379,30 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
1261
1379
|
statusDefinitions: row.statusDefinitions ? JSON.parse(row.statusDefinitions) : [],
|
|
1262
1380
|
priorityDefinitions: row.priorityDefinitions ? JSON.parse(row.priorityDefinitions) : [],
|
|
1263
1381
|
customFieldDefinitions: row.customFieldDefinitions ? JSON.parse(row.customFieldDefinitions) : []
|
|
1264
|
-
};
|
|
1382
|
+
});
|
|
1265
1383
|
}
|
|
1266
1384
|
|
|
1267
1385
|
private mapWorkflow(row: any): Workflow {
|
|
1268
|
-
return {
|
|
1386
|
+
return dropNulls({
|
|
1269
1387
|
...row,
|
|
1270
1388
|
tenantId: row.tenantId ?? undefined,
|
|
1271
1389
|
statuses: row.statuses ? JSON.parse(row.statuses) : [],
|
|
1272
1390
|
transitions: row.transitions ? JSON.parse(row.transitions) : [],
|
|
1273
1391
|
taskTypes: row.taskTypes ? JSON.parse(row.taskTypes) : undefined,
|
|
1274
1392
|
isDefault: row.isDefault !== null && row.isDefault !== undefined ? Boolean(row.isDefault) : undefined
|
|
1275
|
-
};
|
|
1393
|
+
});
|
|
1276
1394
|
}
|
|
1277
1395
|
|
|
1278
1396
|
private mapTeam(row: any): Team {
|
|
1279
|
-
return {
|
|
1397
|
+
return dropNulls({
|
|
1280
1398
|
...row,
|
|
1281
1399
|
tenantId: row.tenantId ?? undefined,
|
|
1282
1400
|
memberIds: row.memberIds ? JSON.parse(row.memberIds) : []
|
|
1283
|
-
};
|
|
1401
|
+
});
|
|
1284
1402
|
}
|
|
1285
1403
|
|
|
1286
1404
|
private mapTask(row: any): Task {
|
|
1287
|
-
return {
|
|
1405
|
+
return dropNulls({
|
|
1288
1406
|
...row,
|
|
1289
1407
|
actualDurationSeconds: row.actualDurationSeconds ?? undefined,
|
|
1290
1408
|
inProgressSince: row.inProgressSince || undefined,
|
|
@@ -1299,15 +1417,15 @@ export class SQLiteStore implements StorageAdapter {
|
|
|
1299
1417
|
tags: row.tags ? JSON.parse(row.tags) : [],
|
|
1300
1418
|
assignees: row.assignees ? JSON.parse(row.assignees) : undefined,
|
|
1301
1419
|
customFields: row.customFields ? JSON.parse(row.customFields) : {}
|
|
1302
|
-
};
|
|
1420
|
+
});
|
|
1303
1421
|
}
|
|
1304
1422
|
|
|
1305
1423
|
private mapDeliverable(row: any): Deliverable {
|
|
1306
|
-
return {
|
|
1424
|
+
return dropNulls({
|
|
1307
1425
|
...row,
|
|
1308
1426
|
specs: row.specs ? JSON.parse(row.specs) : undefined,
|
|
1309
1427
|
outputUrls: row.outputUrls ? JSON.parse(row.outputUrls) : [],
|
|
1310
1428
|
customFields: row.customFields ? JSON.parse(row.customFields) : {}
|
|
1311
|
-
};
|
|
1429
|
+
});
|
|
1312
1430
|
}
|
|
1313
1431
|
}
|
|
@@ -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/query.test.ts","./src/store/query.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"}
|