@critical-path/core 0.21.0 → 0.23.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 +53 -0
- package/README.md +14 -0
- package/dist/domain/ddd.test.js +12 -0
- package/dist/domain/ddd.test.js.map +1 -1
- package/dist/domain/entities.d.ts.map +1 -1
- package/dist/domain/entities.js +3 -2
- package/dist/domain/entities.js.map +1 -1
- package/dist/domain/errors.d.ts +13 -0
- package/dist/domain/errors.d.ts.map +1 -0
- package/dist/domain/errors.js +19 -0
- package/dist/domain/errors.js.map +1 -0
- package/dist/domain/events.d.ts +9 -1
- package/dist/domain/events.d.ts.map +1 -1
- package/dist/domain/events.js.map +1 -1
- package/dist/domain/index.d.ts +1 -0
- package/dist/domain/index.d.ts.map +1 -1
- package/dist/domain/index.js +1 -0
- package/dist/domain/index.js.map +1 -1
- package/dist/engine/actor.test.d.ts +2 -0
- package/dist/engine/actor.test.d.ts.map +1 -0
- package/dist/engine/actor.test.js +53 -0
- package/dist/engine/actor.test.js.map +1 -0
- package/dist/engine/index.d.ts +23 -1
- package/dist/engine/index.d.ts.map +1 -1
- package/dist/engine/index.js +125 -29
- package/dist/engine/index.js.map +1 -1
- package/dist/index.test.js +20 -0
- package/dist/index.test.js.map +1 -1
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.js +5 -1
- package/dist/store/sqlite.js.map +1 -1
- package/dist/store/sqlite.test.js +20 -0
- package/dist/store/sqlite.test.js.map +1 -1
- package/dist/types/index.d.ts +7 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/fractional-index.d.ts.map +1 -1
- package/dist/utils/fractional-index.js +22 -26
- package/dist/utils/fractional-index.js.map +1 -1
- package/dist/utils/fractional-index.test.js +31 -0
- package/dist/utils/fractional-index.test.js.map +1 -1
- package/package.json +1 -5
- package/src/domain/ddd.test.ts +22 -0
- package/src/domain/entities.ts +3 -2
- package/src/domain/errors.ts +19 -0
- package/src/domain/events.ts +6 -0
- package/src/domain/index.ts +1 -0
- package/src/engine/actor.test.ts +62 -0
- package/src/engine/index.ts +134 -30
- package/src/index.test.ts +24 -0
- package/src/store/sqlite.test.ts +22 -0
- package/src/store/sqlite.ts +7 -1
- package/src/types/index.ts +8 -0
- package/src/utils/fractional-index.test.ts +37 -0
- package/src/utils/fractional-index.ts +20 -26
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/engine/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,OAAO,EACP,IAAI,EACJ,cAAc,EACd,mBAAmB,EACnB,IAAI,EACJ,aAAa,EACb,SAAS,EACT,OAAO,EACP,UAAU,EACV,kBAAkB,EAClB,eAAe,EACf,mBAAmB,EACnB,qBAAqB,EACrB,SAAS,EAET,QAAQ,EACR,eAAe,EAEf,WAAW,EACX,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACrB,cAAc,EAId,WAAW,EACX,mBAAmB,EACnB,oBAAoB,EACpB,2BAA2B,EAC5B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAiB,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAWrD,OAAO,EACL,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/engine/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,KAAK,EACL,kBAAkB,EAClB,OAAO,EACP,IAAI,EACJ,cAAc,EACd,mBAAmB,EACnB,IAAI,EACJ,aAAa,EACb,SAAS,EACT,OAAO,EACP,UAAU,EACV,kBAAkB,EAClB,eAAe,EACf,mBAAmB,EACnB,qBAAqB,EACrB,SAAS,EAET,QAAQ,EACR,eAAe,EAEf,WAAW,EACX,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACrB,cAAc,EAId,WAAW,EACX,mBAAmB,EACnB,oBAAoB,EACpB,2BAA2B,EAC5B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAiB,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAWrD,OAAO,EACL,cAAc,EA8Bf,MAAM,qBAAqB,CAAC;AAK7B,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEjE,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAG9B,qBAAa,kBAAkB;IAC7B,SAAgB,MAAM,EAAE,kBAAkB,CAAC;IAC3C,SAAgB,KAAK,EAAE,cAAc,CAAC;IACtC,SAAgB,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjD,SAAgB,OAAO,EAAE,cAAc,CAAC;IACxC,SAAgB,MAAM,EAAE,cAAc,CAAC;IACvC,SAAgB,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAqB;IACzD,2FAA2F;IAC3F,SAAgB,KAAK,CAAC,EAAE,KAAK,CAAC;gBAElB,MAAM,GAAE,kBAAuB;IAqB3C;;;;;;OAMG;IACH,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,kBAAkB;IAM3C,OAAO,CAAC,SAAS;YAIH,eAAe;IAuCvB,YAAY,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;IAInC,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAIjD,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC;IAuB7F,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IA2BhF,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA2B5C,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAgBnE,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAO5D,6BAA6B,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAOhE,iCAAiC,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAQpE,WAAW,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAIjC,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAI/C,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAwBzF,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IA4BnF;;;OAGG;IACG,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAqC3C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAI7C,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAIzC,UAAU,CAAC,SAAS,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAkGrD,UAAU,CACd,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG;QACvB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;YAAC,SAAS,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;KACnE,EACD,OAAO,CAAC,EAAE;QACR,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,KAAK,CAAC,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;YAAC,SAAS,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;KACnE,GACA,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;YA2VT,8BAA8B;IA6EtC,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAsCxC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC;IAwB7E;;;;OAIG;YACW,2BAA2B;IA0BnC,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IA6BpE,qBAAqB,CACzB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,IAAI,CAAC;QAAC,oBAAoB,CAAC,EAAE,MAAM,CAAA;KAAE,GAChE,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAmB/B,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,GAAG,UAAU,GAAG,QAAQ,CAAC,GAAG;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,SAAS,CAAC;IAwC1H,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAI/C,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAI/C,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAsBtF,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAyB7E,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAsB3C,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IA8C3G,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAiC9G,cAAc,CAAC,MAAM,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAQlI,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAIrD,gBAAgB,CAAC,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC;IAsBrG,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA8B9C,oBAAoB,CACxB,KAAK,EAAE,eAAe,GAAG;QACvB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;QAC3C,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,aAAa,GAAG,QAAQ,GAAG,SAAS,CAAC;QACtE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACpC,GACA,OAAO,CAAC,UAAU,CAAC;IA4BhB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAiC/C,+BAA+B,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAQ7F,QAAQ,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;IAI3B,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAIzC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAkB7E,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAIpE,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKxC,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAI1D,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAIvD,eAAe,CAAC,SAAS,EAAE,IAAI,CAAC,aAAa,EAAE,IAAI,GAAG,WAAW,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC;IAkBzG,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAI3F,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK7C,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAI1D,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAIvD,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CAAC,WAAW,CAAC;IAkCtE,iBAAiB,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAsDzF,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA4B/C,qBAAqB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAgDhF,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IAItD,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAInD,eAAe,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC;IAkBnF,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAkCnF,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK7C,qBAAqB,CACzB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,UAAe,GACvB,OAAO,CAAC,oBAAoB,CAAC;IAmB1B,iBAAiB,CACrB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,cAAc,CAAC;IAwDpB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAwB7D,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAmBpF,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC;IAkBpG,uBAAuB,CAC3B,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,GAAE,2BAAgC,GACxC,OAAO,CAAC,oBAAoB,CAAC;YAuBlB,eAAe;CAc9B"}
|
package/dist/engine/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { DomainEventBus } from '../domain/events.js';
|
|
|
7
7
|
import { validateAttachmentUrl } from '../domain/entities.js';
|
|
8
8
|
import { validateCustomFieldValues } from '../domain/custom-fields.js';
|
|
9
9
|
import { detectDependencyCycle, CircularDependencyError } from '../domain/graph.js';
|
|
10
|
+
import { ValidationError, NotFoundError } from '../domain/errors.js';
|
|
10
11
|
import { calculateCPM } from '../domain/cpm.js';
|
|
11
12
|
import { buildTimelineLadder, aggregateConcreteEvidenceForTask } from '../domain/ladder.js';
|
|
12
13
|
import { calculateTaskMetrics, reconstructTaskProgressHistory } from '../domain/metrics.js';
|
|
@@ -18,6 +19,8 @@ export class CriticalPathEngine {
|
|
|
18
19
|
plugins;
|
|
19
20
|
events;
|
|
20
21
|
ready = Promise.resolve();
|
|
22
|
+
/** The identity mutations are attributed to. Set only on views returned by `withActor`. */
|
|
23
|
+
actor;
|
|
21
24
|
constructor(config = {}) {
|
|
22
25
|
this.config = config;
|
|
23
26
|
this.store = typeof config.store === 'object' && config.store !== null
|
|
@@ -35,6 +38,21 @@ export class CriticalPathEngine {
|
|
|
35
38
|
this.ready = this.seedInitialData(config.initialData);
|
|
36
39
|
}
|
|
37
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Returns a view of this engine that attributes every mutation to `actor`: activity log
|
|
43
|
+
* entries, comment authors, reactions, time entries, attachment uploaders and task reporters.
|
|
44
|
+
* Identity fields supplied in payloads are ignored on the view, so callers cannot claim to be
|
|
45
|
+
* someone else. The view shares the store, plugins and event bus, and does not modify this engine,
|
|
46
|
+
* so it is safe to create one per request.
|
|
47
|
+
*/
|
|
48
|
+
withActor(actor) {
|
|
49
|
+
const scoped = Object.create(this);
|
|
50
|
+
Object.defineProperty(scoped, 'actor', { value: Object.freeze({ ...actor }), enumerable: true });
|
|
51
|
+
return scoped;
|
|
52
|
+
}
|
|
53
|
+
actorIdOr(fallback) {
|
|
54
|
+
return this.actor?.userId ?? fallback;
|
|
55
|
+
}
|
|
38
56
|
async seedInitialData(data) {
|
|
39
57
|
if (data.workflows) {
|
|
40
58
|
for (const wf of data.workflows) {
|
|
@@ -92,7 +110,7 @@ export class CriticalPathEngine {
|
|
|
92
110
|
};
|
|
93
111
|
await this.events.publish(event);
|
|
94
112
|
await this.store.logActivity({
|
|
95
|
-
actorId: 'system',
|
|
113
|
+
actorId: this.actorIdOr('system'),
|
|
96
114
|
action: 'workflow.created',
|
|
97
115
|
details: { name: created.name }
|
|
98
116
|
});
|
|
@@ -116,7 +134,7 @@ export class CriticalPathEngine {
|
|
|
116
134
|
};
|
|
117
135
|
await this.events.publish(event);
|
|
118
136
|
await this.store.logActivity({
|
|
119
|
-
actorId: 'system',
|
|
137
|
+
actorId: this.actorIdOr('system'),
|
|
120
138
|
action: 'workflow.updated',
|
|
121
139
|
details: { name: updated.name }
|
|
122
140
|
});
|
|
@@ -141,7 +159,7 @@ export class CriticalPathEngine {
|
|
|
141
159
|
};
|
|
142
160
|
await this.events.publish(event);
|
|
143
161
|
await this.store.logActivity({
|
|
144
|
-
actorId: 'system',
|
|
162
|
+
actorId: this.actorIdOr('system'),
|
|
145
163
|
action: 'workflow.deleted',
|
|
146
164
|
details: { name: existing.name }
|
|
147
165
|
});
|
|
@@ -208,7 +226,7 @@ export class CriticalPathEngine {
|
|
|
208
226
|
await this.events.publish(event);
|
|
209
227
|
await this.store.logActivity({
|
|
210
228
|
projectId: created.id,
|
|
211
|
-
actorId: project.ownerId || 'system',
|
|
229
|
+
actorId: this.actorIdOr(project.ownerId || 'system'),
|
|
212
230
|
action: 'project.created',
|
|
213
231
|
details: { name: created.name, key: created.key }
|
|
214
232
|
});
|
|
@@ -233,7 +251,7 @@ export class CriticalPathEngine {
|
|
|
233
251
|
await this.events.publish(event);
|
|
234
252
|
await this.store.logActivity({
|
|
235
253
|
projectId: updated.id,
|
|
236
|
-
actorId: 'system',
|
|
254
|
+
actorId: this.actorIdOr('system'),
|
|
237
255
|
action: 'project.updated',
|
|
238
256
|
details: { name: updated.name }
|
|
239
257
|
});
|
|
@@ -241,6 +259,43 @@ export class CriticalPathEngine {
|
|
|
241
259
|
}
|
|
242
260
|
return updated;
|
|
243
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* Deletes a project and its tasks. Each task goes through `deleteTask`, so plugin
|
|
264
|
+
* hooks run and `task.deleted` events fire before `project.deleted` is published.
|
|
265
|
+
*/
|
|
266
|
+
async deleteProject(id) {
|
|
267
|
+
const existing = await this.store.getProject(id);
|
|
268
|
+
if (!existing)
|
|
269
|
+
return false;
|
|
270
|
+
const tasks = await this.store.getTasks(id);
|
|
271
|
+
const deletedTaskIds = [];
|
|
272
|
+
for (const task of tasks) {
|
|
273
|
+
if (await this.deleteTask(task.id)) {
|
|
274
|
+
deletedTaskIds.push(task.id);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const deleted = await this.store.deleteProject(id);
|
|
278
|
+
if (deleted) {
|
|
279
|
+
const now = new Date().toISOString();
|
|
280
|
+
const event = {
|
|
281
|
+
id: `evt_${Math.random().toString(36).substring(2, 9)}`,
|
|
282
|
+
name: 'project.deleted',
|
|
283
|
+
aggregateId: id,
|
|
284
|
+
aggregateType: 'Project',
|
|
285
|
+
occurredAt: now,
|
|
286
|
+
payload: { projectId: id, name: existing.name, deletedTaskIds }
|
|
287
|
+
};
|
|
288
|
+
await this.events.publish(event);
|
|
289
|
+
await this.store.logActivity({
|
|
290
|
+
projectId: id,
|
|
291
|
+
actorId: this.actorIdOr('system'),
|
|
292
|
+
action: 'project.deleted',
|
|
293
|
+
details: { name: existing.name, deletedTaskCount: deletedTaskIds.length }
|
|
294
|
+
});
|
|
295
|
+
this.dispatchWebhook('project.deleted', { projectId: id, name: existing.name, deletedTaskIds });
|
|
296
|
+
}
|
|
297
|
+
return deleted;
|
|
298
|
+
}
|
|
244
299
|
// --- Tasks ---
|
|
245
300
|
async getTasks(projectId) {
|
|
246
301
|
return this.store.getTasks(projectId);
|
|
@@ -249,6 +304,9 @@ export class CriticalPathEngine {
|
|
|
249
304
|
return this.store.getTask(id);
|
|
250
305
|
}
|
|
251
306
|
async createTask(taskInput) {
|
|
307
|
+
if (this.actor && !taskInput.reporterId) {
|
|
308
|
+
taskInput = { ...taskInput, reporterId: this.actor.userId };
|
|
309
|
+
}
|
|
252
310
|
const processedInput = await this.plugins.runBeforeTaskCreate(taskInput);
|
|
253
311
|
const projectId = processedInput.projectId || taskInput.projectId;
|
|
254
312
|
const project = await this.store.getProject(projectId);
|
|
@@ -323,7 +381,7 @@ export class CriticalPathEngine {
|
|
|
323
381
|
await this.store.logActivity({
|
|
324
382
|
projectId: created.projectId,
|
|
325
383
|
taskId: created.id,
|
|
326
|
-
actorId: created.reporterId || 'system',
|
|
384
|
+
actorId: this.actorIdOr(created.reporterId || 'system'),
|
|
327
385
|
action: 'task.created',
|
|
328
386
|
details: { title: created.title, status: created.status }
|
|
329
387
|
});
|
|
@@ -334,22 +392,26 @@ export class CriticalPathEngine {
|
|
|
334
392
|
const existing = await this.store.getTask(id);
|
|
335
393
|
if (!existing)
|
|
336
394
|
return null;
|
|
395
|
+
// On a withActor view, the scoped actor replaces any identity claimed in the payload.
|
|
396
|
+
// Explicit options from trusted server-side code still take precedence.
|
|
397
|
+
options = options ?? (this.actor ? { actor: this.actor } : undefined);
|
|
398
|
+
const claimed = this.actor ? undefined : updates;
|
|
337
399
|
const actorId = options?.actorId ||
|
|
338
|
-
|
|
400
|
+
claimed?.actorId ||
|
|
339
401
|
options?.actor?.userId ||
|
|
340
|
-
|
|
402
|
+
claimed?.actor?.userId ||
|
|
341
403
|
'system';
|
|
342
404
|
const actorName = options?.actorName ||
|
|
343
|
-
|
|
405
|
+
claimed?.actorName ||
|
|
344
406
|
options?.actor?.username ||
|
|
345
|
-
|
|
407
|
+
claimed?.actor?.username ||
|
|
346
408
|
(actorId === 'system' ? 'System' : undefined);
|
|
347
409
|
const actorType = options?.actorType ||
|
|
348
|
-
|
|
410
|
+
claimed?.actorType ||
|
|
349
411
|
options?.actor?.actorType ||
|
|
350
|
-
|
|
412
|
+
claimed?.actor?.actorType ||
|
|
351
413
|
(actorId === 'system' ? 'system' : 'user');
|
|
352
|
-
const actorObj = options?.actor ||
|
|
414
|
+
const actorObj = options?.actor || claimed?.actor || {
|
|
353
415
|
userId: actorId,
|
|
354
416
|
username: actorName,
|
|
355
417
|
actorType
|
|
@@ -688,7 +750,7 @@ export class CriticalPathEngine {
|
|
|
688
750
|
await this.store.logActivity({
|
|
689
751
|
projectId: downstream.projectId,
|
|
690
752
|
taskId: downstream.id,
|
|
691
|
-
actorId: 'system',
|
|
753
|
+
actorId: this.actorIdOr('system'),
|
|
692
754
|
action: 'task.unblocked',
|
|
693
755
|
details: { unblockedByTaskId: completedTask.id }
|
|
694
756
|
});
|
|
@@ -728,7 +790,7 @@ export class CriticalPathEngine {
|
|
|
728
790
|
await this.store.logActivity({
|
|
729
791
|
projectId: existing.projectId,
|
|
730
792
|
taskId: id,
|
|
731
|
-
actorId: 'system',
|
|
793
|
+
actorId: this.actorIdOr('system'),
|
|
732
794
|
action: 'task.deleted',
|
|
733
795
|
details: { title: existing.title }
|
|
734
796
|
});
|
|
@@ -738,13 +800,9 @@ export class CriticalPathEngine {
|
|
|
738
800
|
}
|
|
739
801
|
// --- Task Dependencies & Graph ---
|
|
740
802
|
async addDependency(dep) {
|
|
741
|
-
const existingDependencies = await this.store.getDependencies(dep.taskId);
|
|
742
|
-
const allProjectDeps = [
|
|
743
|
-
...existingDependencies,
|
|
744
|
-
...(await this.store.getDependencies(dep.dependsOnTaskId))
|
|
745
|
-
];
|
|
746
803
|
// Enforce Directed Acyclic Graph (DAG) Invariant
|
|
747
|
-
const
|
|
804
|
+
const reachableDeps = await this.collectUpstreamDependencies(dep.dependsOnTaskId);
|
|
805
|
+
const cycleCheck = detectDependencyCycle(reachableDeps, dep);
|
|
748
806
|
if (cycleCheck.hasCycle) {
|
|
749
807
|
throw new CircularDependencyError(dep.taskId, dep.dependsOnTaskId, cycleCheck.cyclePath);
|
|
750
808
|
}
|
|
@@ -761,6 +819,34 @@ export class CriticalPathEngine {
|
|
|
761
819
|
await this.events.publish(event);
|
|
762
820
|
return created;
|
|
763
821
|
}
|
|
822
|
+
/**
|
|
823
|
+
* Collects every dependency edge reachable upstream from `startTaskId`. A new edge
|
|
824
|
+
* `taskId -> dependsOnTaskId` creates a cycle exactly when `taskId` is reachable from
|
|
825
|
+
* `dependsOnTaskId`, so this is the full set of edges the cycle check needs.
|
|
826
|
+
*/
|
|
827
|
+
async collectUpstreamDependencies(startTaskId) {
|
|
828
|
+
const edges = new Map();
|
|
829
|
+
const visited = new Set();
|
|
830
|
+
let frontier = [startTaskId];
|
|
831
|
+
while (frontier.length > 0) {
|
|
832
|
+
const next = [];
|
|
833
|
+
const results = await Promise.all(frontier.map((id) => this.store.getDependencies(id)));
|
|
834
|
+
frontier.forEach((id) => visited.add(id));
|
|
835
|
+
for (const deps of results) {
|
|
836
|
+
for (const d of deps) {
|
|
837
|
+
// getDependencies returns edges in both directions; follow only outgoing ones.
|
|
838
|
+
if (!visited.has(d.taskId))
|
|
839
|
+
continue;
|
|
840
|
+
edges.set(d.id, d);
|
|
841
|
+
if (!visited.has(d.dependsOnTaskId) && !next.includes(d.dependsOnTaskId)) {
|
|
842
|
+
next.push(d.dependsOnTaskId);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
frontier = next;
|
|
847
|
+
}
|
|
848
|
+
return Array.from(edges.values());
|
|
849
|
+
}
|
|
764
850
|
async getTaskDependencyGraph(taskId) {
|
|
765
851
|
const dependencies = await this.store.getDependencies(taskId);
|
|
766
852
|
const upstreamTaskIds = new Set();
|
|
@@ -800,8 +886,8 @@ export class CriticalPathEngine {
|
|
|
800
886
|
}
|
|
801
887
|
// --- Time Tracking ---
|
|
802
888
|
async logTime(entry) {
|
|
803
|
-
if (entry.hours <= 0) {
|
|
804
|
-
throw new
|
|
889
|
+
if (!Number.isFinite(entry.hours) || entry.hours <= 0) {
|
|
890
|
+
throw new ValidationError('Logged hours must be a positive number.');
|
|
805
891
|
}
|
|
806
892
|
const task = await this.store.getTask(entry.taskId);
|
|
807
893
|
if (task) {
|
|
@@ -816,7 +902,7 @@ export class CriticalPathEngine {
|
|
|
816
902
|
}
|
|
817
903
|
const fullEntry = {
|
|
818
904
|
...entry,
|
|
819
|
-
userId: entry.userId || task?.assigneeId || 'system',
|
|
905
|
+
userId: this.actor?.userId || entry.userId || task?.assigneeId || 'system',
|
|
820
906
|
loggedAt: entry.loggedAt
|
|
821
907
|
};
|
|
822
908
|
const created = await this.store.logTime(fullEntry);
|
|
@@ -840,6 +926,9 @@ export class CriticalPathEngine {
|
|
|
840
926
|
return this.store.getComment(id);
|
|
841
927
|
}
|
|
842
928
|
async addComment(comment) {
|
|
929
|
+
if (this.actor) {
|
|
930
|
+
comment = { ...comment, authorId: this.actor.userId, authorType: this.actor.actorType ?? 'user' };
|
|
931
|
+
}
|
|
843
932
|
const mentions = comment.mentions ?? extractMentions(comment.content);
|
|
844
933
|
const created = await this.store.addComment({ ...comment, mentions });
|
|
845
934
|
const now = new Date().toISOString();
|
|
@@ -898,6 +987,8 @@ export class CriticalPathEngine {
|
|
|
898
987
|
return deleted;
|
|
899
988
|
}
|
|
900
989
|
async addCommentReaction(commentId, reaction) {
|
|
990
|
+
if (this.actor)
|
|
991
|
+
reaction = { ...reaction, userId: this.actor.userId };
|
|
901
992
|
const existing = await this.store.getComment(commentId);
|
|
902
993
|
if (!existing)
|
|
903
994
|
return null;
|
|
@@ -941,6 +1032,8 @@ export class CriticalPathEngine {
|
|
|
941
1032
|
return updated;
|
|
942
1033
|
}
|
|
943
1034
|
async removeCommentReaction(commentId, reaction) {
|
|
1035
|
+
if (this.actor)
|
|
1036
|
+
reaction = { ...reaction, userId: this.actor.userId };
|
|
944
1037
|
const existing = await this.store.getComment(commentId);
|
|
945
1038
|
if (!existing)
|
|
946
1039
|
return null;
|
|
@@ -979,6 +1072,9 @@ export class CriticalPathEngine {
|
|
|
979
1072
|
return this.store.getAttachment(id);
|
|
980
1073
|
}
|
|
981
1074
|
async createAttachment(attachment) {
|
|
1075
|
+
if (this.actor) {
|
|
1076
|
+
attachment = { ...attachment, uploaderId: this.actor.userId, uploaderType: this.actor.actorType ?? 'user' };
|
|
1077
|
+
}
|
|
982
1078
|
validateAttachmentUrl(attachment.url);
|
|
983
1079
|
const created = await this.store.createAttachment(attachment);
|
|
984
1080
|
const now = new Date().toISOString();
|
|
@@ -1050,7 +1146,7 @@ export class CriticalPathEngine {
|
|
|
1050
1146
|
async readAttachmentText(id) {
|
|
1051
1147
|
const attachment = await this.getAttachment(id);
|
|
1052
1148
|
if (!attachment) {
|
|
1053
|
-
throw new
|
|
1149
|
+
throw new NotFoundError(`Attachment with id "${id}" not found.`);
|
|
1054
1150
|
}
|
|
1055
1151
|
if (attachment.storageKey && this.fileStorage && typeof this.fileStorage.download === 'function') {
|
|
1056
1152
|
const bytes = await this.fileStorage.download(attachment.storageKey);
|
|
@@ -1167,7 +1263,7 @@ export class CriticalPathEngine {
|
|
|
1167
1263
|
await this.events.publish(event);
|
|
1168
1264
|
await this.store.logActivity({
|
|
1169
1265
|
projectId: created.projectId,
|
|
1170
|
-
actorId: 'system',
|
|
1266
|
+
actorId: this.actorIdOr('system'),
|
|
1171
1267
|
action: 'deliverable.created',
|
|
1172
1268
|
details: { title: created.title, format: created.format }
|
|
1173
1269
|
});
|
|
@@ -1215,7 +1311,7 @@ export class CriticalPathEngine {
|
|
|
1215
1311
|
await this.events.publish(updateEvent);
|
|
1216
1312
|
await this.store.logActivity({
|
|
1217
1313
|
projectId: updated.projectId,
|
|
1218
|
-
actorId: 'system',
|
|
1314
|
+
actorId: this.actorIdOr('system'),
|
|
1219
1315
|
action: 'deliverable.updated',
|
|
1220
1316
|
details: { title: updated.title, status: updated.status }
|
|
1221
1317
|
});
|
|
@@ -1241,7 +1337,7 @@ export class CriticalPathEngine {
|
|
|
1241
1337
|
await this.events.publish(event);
|
|
1242
1338
|
await this.store.logActivity({
|
|
1243
1339
|
projectId: existing.projectId,
|
|
1244
|
-
actorId: 'system',
|
|
1340
|
+
actorId: this.actorIdOr('system'),
|
|
1245
1341
|
action: 'deliverable.deleted',
|
|
1246
1342
|
details: { title: existing.title }
|
|
1247
1343
|
});
|
|
@@ -1376,7 +1472,7 @@ export class CriticalPathEngine {
|
|
|
1376
1472
|
async getTimelineLadder(projectId, options = {}) {
|
|
1377
1473
|
const project = await this.store.getProject(projectId);
|
|
1378
1474
|
if (!project) {
|
|
1379
|
-
throw new
|
|
1475
|
+
throw new NotFoundError(`Project with ID "${projectId}" not found.`);
|
|
1380
1476
|
}
|
|
1381
1477
|
let tasks = await this.store.getTasks(projectId);
|
|
1382
1478
|
if (options.containerId) {
|