@owlmeans/server-planning 0.1.18-rc.9 → 0.1.18
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/README.md +92 -19
- package/agent-meta/manifest.json +105 -4
- package/agent-meta/skills/server-planning/SKILL.md +89 -138
- package/agent-meta/skills/server-planning/parts/auxiliary.md +54 -0
- package/agent-meta/skills/server-planning/parts/executor.md +82 -0
- package/agent-meta/skills/server-planning/parts/schemas.md +26 -0
- package/agent-meta/skills/server-planning/parts/serving.md +88 -0
- package/agent-meta/skills/server-planning/parts/stores.md +91 -0
- package/build/actions/apply-schema-request.d.ts +8 -0
- package/build/actions/apply-schema-request.d.ts.map +1 -0
- package/build/actions/apply-schema-request.js +33 -0
- package/build/actions/apply-schema-request.js.map +1 -0
- package/build/actions/commit.d.ts +3 -3
- package/build/actions/commit.d.ts.map +1 -1
- package/build/actions/commit.js +5 -4
- package/build/actions/commit.js.map +1 -1
- package/build/actions/define-schemas.d.ts +13 -0
- package/build/actions/define-schemas.d.ts.map +1 -0
- package/build/actions/define-schemas.js +28 -0
- package/build/actions/define-schemas.js.map +1 -0
- package/build/actions/execute.d.ts +11 -10
- package/build/actions/execute.d.ts.map +1 -1
- package/build/actions/execute.js +17 -27
- package/build/actions/execute.js.map +1 -1
- package/build/actions/execution/types.d.ts +34 -0
- package/build/actions/execution/types.d.ts.map +1 -0
- package/build/actions/execution/types.js +2 -0
- package/build/actions/execution/types.js.map +1 -0
- package/build/actions/execution.d.ts +4 -0
- package/build/actions/execution.d.ts.map +1 -0
- package/build/actions/execution.js +88 -0
- package/build/actions/execution.js.map +1 -0
- package/build/actions/get-card.d.ts +6 -0
- package/build/actions/get-card.d.ts.map +1 -0
- package/build/actions/get-card.js +9 -0
- package/build/actions/get-card.js.map +1 -0
- package/build/actions/get-specification.d.ts +6 -0
- package/build/actions/get-specification.d.ts.map +1 -0
- package/build/actions/get-specification.js +9 -0
- package/build/actions/get-specification.js.map +1 -0
- package/build/actions/index.d.ts +12 -3
- package/build/actions/index.d.ts.map +1 -1
- package/build/actions/index.js +11 -3
- package/build/actions/index.js.map +1 -1
- package/build/actions/links.d.ts +3 -3
- package/build/actions/links.d.ts.map +1 -1
- package/build/actions/links.js +6 -5
- package/build/actions/links.js.map +1 -1
- package/build/actions/list-card-specifications.d.ts +6 -0
- package/build/actions/list-card-specifications.d.ts.map +1 -0
- package/build/actions/list-card-specifications.js +11 -0
- package/build/actions/list-card-specifications.js.map +1 -0
- package/build/actions/list-card-transitions.d.ts +6 -0
- package/build/actions/list-card-transitions.d.ts.map +1 -0
- package/build/actions/list-card-transitions.js +11 -0
- package/build/actions/list-card-transitions.js.map +1 -0
- package/build/actions/list-cards.d.ts +6 -0
- package/build/actions/list-cards.d.ts.map +1 -0
- package/build/actions/list-cards.js +10 -0
- package/build/actions/list-cards.js.map +1 -0
- package/build/actions/list-schemas.d.ts +13 -0
- package/build/actions/list-schemas.d.ts.map +1 -0
- package/build/actions/list-schemas.js +20 -0
- package/build/actions/list-schemas.js.map +1 -0
- package/build/actions/list-specification-revisions.d.ts +6 -0
- package/build/actions/list-specification-revisions.d.ts.map +1 -0
- package/build/actions/list-specification-revisions.js +10 -0
- package/build/actions/list-specification-revisions.js.map +1 -0
- package/build/actions/resources.d.ts +5 -0
- package/build/actions/resources.d.ts.map +1 -0
- package/build/actions/resources.js +111 -0
- package/build/actions/resources.js.map +1 -0
- package/build/actions/summarize-cards.d.ts +6 -0
- package/build/actions/summarize-cards.d.ts.map +1 -0
- package/build/actions/summarize-cards.js +11 -0
- package/build/actions/summarize-cards.js.map +1 -0
- package/build/actions/transitions.d.ts +3 -3
- package/build/actions/transitions.d.ts.map +1 -1
- package/build/actions/transitions.js +4 -3
- package/build/actions/transitions.js.map +1 -1
- package/build/actions/types.d.ts +6 -0
- package/build/actions/types.d.ts.map +1 -0
- package/build/actions/types.js +2 -0
- package/build/actions/types.js.map +1 -0
- package/build/actions/watch.d.ts +2 -2
- package/build/actions/watch.d.ts.map +1 -1
- package/build/actions/watch.js +8 -6
- package/build/actions/watch.js.map +1 -1
- package/build/conformance/assert/types.d.ts +19 -0
- package/build/conformance/assert/types.d.ts.map +1 -0
- package/build/conformance/assert/types.js +2 -0
- package/build/conformance/assert/types.js.map +1 -0
- package/build/conformance/assert.d.ts +15 -0
- package/build/conformance/assert.d.ts.map +1 -0
- package/build/conformance/assert.js +52 -0
- package/build/conformance/assert.js.map +1 -0
- package/build/conformance/cases.d.ts +7 -0
- package/build/conformance/cases.d.ts.map +1 -0
- package/build/conformance/cases.js +544 -0
- package/build/conformance/cases.js.map +1 -0
- package/build/conformance/clock.d.ts +6 -0
- package/build/conformance/clock.d.ts.map +1 -0
- package/build/conformance/clock.js +12 -0
- package/build/conformance/clock.js.map +1 -0
- package/build/conformance/consts.d.ts +30 -0
- package/build/conformance/consts.d.ts.map +1 -0
- package/build/conformance/consts.js +159 -0
- package/build/conformance/consts.js.map +1 -0
- package/build/conformance/errors.d.ts +5 -0
- package/build/conformance/errors.d.ts.map +1 -0
- package/build/conformance/errors.js +8 -0
- package/build/conformance/errors.js.map +1 -0
- package/build/conformance/fixtures/types.d.ts +13 -0
- package/build/conformance/fixtures/types.d.ts.map +1 -0
- package/build/conformance/fixtures/types.js +2 -0
- package/build/conformance/fixtures/types.js.map +1 -0
- package/build/conformance/fixtures.d.ts +14 -0
- package/build/conformance/fixtures.d.ts.map +1 -0
- package/build/conformance/fixtures.js +26 -0
- package/build/conformance/fixtures.js.map +1 -0
- package/build/conformance/index.d.ts +15 -0
- package/build/conformance/index.d.ts.map +1 -0
- package/build/conformance/index.js +12 -0
- package/build/conformance/index.js.map +1 -0
- package/build/conformance/resources.d.ts +4 -0
- package/build/conformance/resources.d.ts.map +1 -0
- package/build/conformance/resources.js +228 -0
- package/build/conformance/resources.js.map +1 -0
- package/build/conformance/types.d.ts +18 -0
- package/build/conformance/types.d.ts.map +1 -0
- package/build/conformance/types.js +2 -0
- package/build/conformance/types.js.map +1 -0
- package/build/consts.d.ts +5 -0
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +5 -0
- package/build/consts.js.map +1 -1
- package/build/definitions.d.ts +18 -0
- package/build/definitions.d.ts.map +1 -0
- package/build/definitions.js +225 -0
- package/build/definitions.js.map +1 -0
- package/build/executor/changes/types.d.ts +36 -0
- package/build/executor/changes/types.d.ts.map +1 -0
- package/build/executor/changes/types.js +2 -0
- package/build/executor/changes/types.js.map +1 -0
- package/build/executor/changes.d.ts +3 -30
- package/build/executor/changes.d.ts.map +1 -1
- package/build/executor/changes.js +42 -51
- package/build/executor/changes.js.map +1 -1
- package/build/executor/code/types.d.ts +17 -0
- package/build/executor/code/types.d.ts.map +1 -0
- package/build/executor/code/types.js +2 -0
- package/build/executor/code/types.js.map +1 -0
- package/build/executor/code.d.ts +3 -14
- package/build/executor/code.d.ts.map +1 -1
- package/build/executor/code.js +50 -54
- package/build/executor/code.js.map +1 -1
- package/build/executor/consts.local.d.ts +5 -0
- package/build/executor/consts.local.d.ts.map +1 -0
- package/build/executor/consts.local.js +6 -0
- package/build/executor/consts.local.js.map +1 -0
- package/build/executor/creator/types.d.ts +33 -0
- package/build/executor/creator/types.d.ts.map +1 -0
- package/build/executor/creator/types.js +2 -0
- package/build/executor/creator/types.js.map +1 -0
- package/build/executor/creator.d.ts +11 -0
- package/build/executor/creator.d.ts.map +1 -0
- package/build/executor/creator.js +32 -0
- package/build/executor/creator.js.map +1 -0
- package/build/executor/resolve/types.d.ts +30 -0
- package/build/executor/resolve/types.d.ts.map +1 -0
- package/build/executor/resolve/types.js +2 -0
- package/build/executor/resolve/types.js.map +1 -0
- package/build/executor/resolve.d.ts +5 -34
- package/build/executor/resolve.d.ts.map +1 -1
- package/build/executor/resolve.js +169 -110
- package/build/executor/resolve.js.map +1 -1
- package/build/executor/types.d.ts +23 -0
- package/build/executor/types.d.ts.map +1 -0
- package/build/executor/types.js +2 -0
- package/build/executor/types.js.map +1 -0
- package/build/executor/validate.d.ts +6 -5
- package/build/executor/validate.d.ts.map +1 -1
- package/build/executor/validate.js +38 -27
- package/build/executor/validate.js.map +1 -1
- package/build/executor.d.ts +11 -7
- package/build/executor.d.ts.map +1 -1
- package/build/executor.js +27 -21
- package/build/executor.js.map +1 -1
- package/build/facade.d.ts +20 -2
- package/build/facade.d.ts.map +1 -1
- package/build/facade.js +144 -22
- package/build/facade.js.map +1 -1
- package/build/helper.d.ts +5 -9
- package/build/helper.d.ts.map +1 -1
- package/build/helper.js +12 -11
- package/build/helper.js.map +1 -1
- package/build/hierarchy/types.d.ts +20 -0
- package/build/hierarchy/types.d.ts.map +1 -0
- package/build/hierarchy/types.js +2 -0
- package/build/hierarchy/types.js.map +1 -0
- package/build/hierarchy.d.ts +5 -0
- package/build/hierarchy.d.ts.map +1 -0
- package/build/hierarchy.js +133 -0
- package/build/hierarchy.js.map +1 -0
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/invariants/types.d.ts +6 -0
- package/build/invariants/types.d.ts.map +1 -0
- package/build/invariants/types.js +2 -0
- package/build/invariants/types.js.map +1 -0
- package/build/invariants.d.ts +5 -0
- package/build/invariants.d.ts.map +1 -0
- package/build/invariants.js +126 -0
- package/build/invariants.js.map +1 -0
- package/build/projection.d.ts +4 -10
- package/build/projection.d.ts.map +1 -1
- package/build/projection.js +7 -5
- package/build/projection.js.map +1 -1
- package/build/queue/consts.d.ts +5 -0
- package/build/queue/consts.d.ts.map +1 -0
- package/build/queue/consts.js +5 -0
- package/build/queue/consts.js.map +1 -0
- package/build/queue/declare.d.ts +5 -0
- package/build/queue/declare.d.ts.map +1 -0
- package/build/queue/declare.js +5 -0
- package/build/queue/declare.js.map +1 -0
- package/build/queue/index.d.ts +3 -18
- package/build/queue/index.d.ts.map +1 -1
- package/build/queue/index.js +2 -7
- package/build/queue/index.js.map +1 -1
- package/build/queue/types.d.ts +13 -0
- package/build/queue/types.d.ts.map +1 -0
- package/build/queue/types.js +2 -0
- package/build/queue/types.js.map +1 -0
- package/build/registry.d.ts +5 -22
- package/build/registry.d.ts.map +1 -1
- package/build/registry.js +16 -10
- package/build/registry.js.map +1 -1
- package/build/resources.d.ts +5 -0
- package/build/resources.d.ts.map +1 -0
- package/build/resources.js +287 -0
- package/build/resources.js.map +1 -0
- package/build/service.d.ts +3 -16
- package/build/service.d.ts.map +1 -1
- package/build/service.js +12 -0
- package/build/service.js.map +1 -1
- package/build/store/commits.d.ts.map +1 -1
- package/build/store/commits.js +3 -1
- package/build/store/commits.js.map +1 -1
- package/build/store/composite.d.ts +1 -0
- package/build/store/composite.d.ts.map +1 -1
- package/build/store/composite.js +7 -2
- package/build/store/composite.js.map +1 -1
- package/build/store/consts.local.d.ts +2 -0
- package/build/store/consts.local.d.ts.map +1 -0
- package/build/store/consts.local.js +2 -0
- package/build/store/consts.local.js.map +1 -0
- package/build/store/fold/types.d.ts +43 -0
- package/build/store/fold/types.d.ts.map +1 -0
- package/build/store/fold/types.js +2 -0
- package/build/store/fold/types.js.map +1 -0
- package/build/store/fold.d.ts +8 -30
- package/build/store/fold.d.ts.map +1 -1
- package/build/store/fold.js +190 -198
- package/build/store/fold.js.map +1 -1
- package/build/store/index.d.ts +1 -0
- package/build/store/index.d.ts.map +1 -1
- package/build/store/index.js.map +1 -1
- package/build/store/memory.d.ts +12 -0
- package/build/store/memory.d.ts.map +1 -1
- package/build/store/memory.js +0 -0
- package/build/store/memory.js.map +1 -1
- package/build/store/types.d.ts +15 -1
- package/build/store/types.d.ts.map +1 -1
- package/build/store/types.local.d.ts +5 -0
- package/build/store/types.local.d.ts.map +1 -0
- package/build/store/types.local.js +2 -0
- package/build/store/types.local.js.map +1 -0
- package/build/types.d.ts +126 -2
- package/build/types.d.ts.map +1 -1
- package/build/types.local.d.ts +7 -0
- package/build/types.local.d.ts.map +1 -0
- package/build/types.local.js +2 -0
- package/build/types.local.js.map +1 -0
- package/build/utils/access/types.d.ts +30 -0
- package/build/utils/access/types.d.ts.map +1 -0
- package/build/utils/access/types.js +2 -0
- package/build/utils/access/types.js.map +1 -0
- package/build/utils/access.d.ts +4 -0
- package/build/utils/access.d.ts.map +1 -0
- package/build/utils/access.js +28 -0
- package/build/utils/access.js.map +1 -0
- package/build/utils/guard/types.d.ts +23 -0
- package/build/utils/guard/types.d.ts.map +1 -0
- package/build/utils/guard/types.js +2 -0
- package/build/utils/guard/types.js.map +1 -0
- package/build/utils/guard.d.ts +7 -10
- package/build/utils/guard.d.ts.map +1 -1
- package/build/utils/guard.js +36 -32
- package/build/utils/guard.js.map +1 -1
- package/build/utils/handler/types.d.ts +26 -0
- package/build/utils/handler/types.d.ts.map +1 -0
- package/build/utils/handler/types.js +2 -0
- package/build/utils/handler/types.js.map +1 -0
- package/build/utils/handler.d.ts +6 -0
- package/build/utils/handler.d.ts.map +1 -0
- package/build/utils/handler.js +21 -0
- package/build/utils/handler.js.map +1 -0
- package/build/utils/index.d.ts +8 -0
- package/build/utils/index.d.ts.map +1 -1
- package/build/utils/index.js +3 -0
- package/build/utils/index.js.map +1 -1
- package/build/utils/request/types.d.ts +23 -0
- package/build/utils/request/types.d.ts.map +1 -0
- package/build/utils/request/types.js +2 -0
- package/build/utils/request/types.js.map +1 -0
- package/build/utils/request.d.ts +4 -0
- package/build/utils/request.d.ts.map +1 -0
- package/build/utils/request.js +27 -0
- package/build/utils/request.js.map +1 -0
- package/build/utils/scope.d.ts +17 -13
- package/build/utils/scope.d.ts.map +1 -1
- package/build/utils/scope.js +21 -35
- package/build/utils/scope.js.map +1 -1
- package/build/utils/types.d.ts +8 -0
- package/build/utils/types.d.ts.map +1 -0
- package/build/utils/types.js +2 -0
- package/build/utils/types.js.map +1 -0
- package/package.json +30 -22
- package/src/actions/apply-schema-request.ts +36 -0
- package/src/actions/commit.ts +7 -5
- package/src/actions/define-schemas.ts +34 -0
- package/src/actions/execute.ts +32 -34
- package/src/actions/execution/types.ts +34 -0
- package/src/actions/execution.ts +100 -0
- package/src/actions/get-card.ts +14 -0
- package/src/actions/get-specification.ts +14 -0
- package/src/actions/index.ts +13 -3
- package/src/actions/links.ts +8 -6
- package/src/actions/list-card-specifications.ts +15 -0
- package/src/actions/list-card-transitions.ts +15 -0
- package/src/actions/list-cards.ts +14 -0
- package/src/actions/list-schemas.ts +29 -0
- package/src/actions/list-specification-revisions.ts +15 -0
- package/src/actions/resources.ts +79 -0
- package/src/actions/summarize-cards.ts +15 -0
- package/src/actions/transitions.ts +6 -4
- package/src/actions/types.ts +5 -0
- package/src/actions/watch.ts +11 -7
- package/src/conformance/assert/types.ts +16 -0
- package/src/conformance/assert.ts +67 -0
- package/src/conformance/cases.ts +631 -0
- package/src/conformance/clock.ts +11 -0
- package/src/conformance/consts.ts +165 -0
- package/src/conformance/errors.ts +7 -0
- package/src/conformance/fixtures/types.ts +11 -0
- package/src/conformance/fixtures.ts +46 -0
- package/src/conformance/index.ts +14 -0
- package/src/conformance/resources.ts +227 -0
- package/src/conformance/types.ts +20 -0
- package/src/consts.ts +6 -0
- package/src/definitions.ts +264 -0
- package/src/executor/changes/types.ts +38 -0
- package/src/executor/changes.ts +49 -68
- package/src/executor/code/types.ts +23 -0
- package/src/executor/code.ts +62 -69
- package/src/executor/consts.local.ts +8 -0
- package/src/executor/creator/types.ts +33 -0
- package/src/executor/creator.ts +43 -0
- package/src/executor/resolve/types.ts +30 -0
- package/src/executor/resolve.ts +181 -139
- package/src/executor/types.ts +23 -0
- package/src/executor/validate.ts +40 -38
- package/src/executor.ts +33 -27
- package/src/facade.ts +132 -29
- package/src/helper.ts +12 -11
- package/src/hierarchy/types.ts +20 -0
- package/src/hierarchy.ts +113 -0
- package/src/index.ts +1 -0
- package/src/invariants/types.ts +6 -0
- package/src/invariants.ts +106 -0
- package/src/projection.ts +12 -18
- package/src/queue/consts.ts +5 -0
- package/src/queue/declare.ts +7 -0
- package/src/queue/index.ts +3 -25
- package/src/queue/types.ts +14 -0
- package/src/registry.ts +18 -36
- package/src/resources.ts +256 -0
- package/src/service.ts +16 -20
- package/src/store/commits.ts +5 -4
- package/src/store/composite.ts +7 -2
- package/src/store/consts.local.ts +1 -0
- package/src/store/fold/types.ts +44 -0
- package/src/store/fold.ts +214 -212
- package/src/store/index.ts +1 -0
- package/src/store/memory.ts +0 -0
- package/src/store/types.local.ts +3 -0
- package/src/store/types.ts +16 -1
- package/src/types.local.ts +7 -0
- package/src/types.ts +139 -6
- package/src/utils/access/types.ts +30 -0
- package/src/utils/access.ts +33 -0
- package/src/utils/guard/types.ts +21 -0
- package/src/utils/guard.ts +44 -31
- package/src/utils/handler/types.ts +28 -0
- package/src/utils/handler.ts +35 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/request/types.ts +23 -0
- package/src/utils/request.ts +40 -0
- package/src/utils/scope.ts +39 -42
- package/src/utils/types.ts +8 -0
- package/tests/access.spec.ts +160 -0
- package/tests/app.spec.ts +112 -0
- package/tests/conformance.spec.ts +47 -0
- package/tests/create-example.spec.ts +202 -0
- package/tests/creator.spec.ts +257 -0
- package/tests/entrypoints.spec.ts +5 -0
- package/tests/memory-store.spec.ts +52 -0
- package/tests/narrowing.spec.ts +94 -0
- package/tests/plugins.spec.ts +4 -3
- package/tests/writes.spec.ts +140 -0
- package/build/actions/cards.d.ts +0 -16
- package/build/actions/cards.d.ts.map +0 -1
- package/build/actions/cards.js +0 -32
- package/build/actions/cards.js.map +0 -1
- package/build/actions/schemas.d.ts +0 -6
- package/build/actions/schemas.d.ts.map +0 -1
- package/build/actions/schemas.js +0 -9
- package/build/actions/schemas.js.map +0 -1
- package/build/actions/specs.d.ts +0 -10
- package/build/actions/specs.d.ts.map +0 -1
- package/build/actions/specs.js +0 -14
- package/build/actions/specs.js.map +0 -1
- package/src/actions/cards.ts +0 -52
- package/src/actions/schemas.ts +0 -15
- package/src/actions/specs.ts +0 -23
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { cardHelper, PlanningSchemaKind, PlanningUnsupported, SchemaConflict, SchemaInUse, scopedSchemaHelper, UnknownStatusFlow, UnknownWorkcardType, WorkcardNotFound, type AssigneeTypeSchema, type PlanningDefinitions, type PlanningFacade, type PlanningSchemaRegistry, type SchemaDeclarations, type SchemaStore, type SchemaWriteOptions, type ScopedSchemaRecord, type ScopedSchemaRegistry, type StatusFlowSchema, type TransitionExecution, type Unsubscribe, type WorkcardTypeSchema } from '@owlmeans/planning'
|
|
2
|
+
import { DEFAULT_SCHEMA_VIEWS } from './consts.js'
|
|
3
|
+
import { changesUtils } from './executor/changes.js'
|
|
4
|
+
import type { PlanningRuntime, SchemaViews, SchemaViewsOptions } from './types.js'
|
|
5
|
+
import type { Planned } from './types.local.js'
|
|
6
|
+
|
|
7
|
+
const isoNow = (): string => new Date().toISOString()
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The resolved layers of one planning service, cached per (organization, project) and keyed by the
|
|
11
|
+
* organization's schema revision and the code registry's version — a write anywhere, in any
|
|
12
|
+
* process, moves the revision the next lookup reads, so a cached layer is never served stale. A
|
|
13
|
+
* store that can `watch` evicts an organization's layers as soon as it hears of a write.
|
|
14
|
+
*/
|
|
15
|
+
export const makeSchemaViews = (opts: SchemaViewsOptions): SchemaViews => {
|
|
16
|
+
const limit = opts.limit ?? DEFAULT_SCHEMA_VIEWS
|
|
17
|
+
const cache = new Map<string, { revision: number, version: number, view: ScopedSchemaRegistry }>()
|
|
18
|
+
let watching: { store: SchemaStore, release: Unsubscribe } | undefined
|
|
19
|
+
|
|
20
|
+
const keyOf = (entityId: string, project?: string): string => `${entityId}\u0000${project ?? ''}`
|
|
21
|
+
|
|
22
|
+
const evict = (entityId: string): void => {
|
|
23
|
+
const prefix = `${entityId}\u0000`
|
|
24
|
+
for (const key of [...cache.keys()]) {
|
|
25
|
+
if (key.startsWith(prefix)) {
|
|
26
|
+
cache.delete(key)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const scoped: SchemaViews['scoped'] = async (entityId, project) => {
|
|
32
|
+
const store = opts.port()
|
|
33
|
+
if (store == null) {
|
|
34
|
+
throw new PlanningUnsupported('schemas')
|
|
35
|
+
}
|
|
36
|
+
if (store.watch != null && watching?.store !== store) {
|
|
37
|
+
watching?.release()
|
|
38
|
+
watching = { store, release: store.watch(evict) }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const revision = await store.revision(entityId)
|
|
42
|
+
const version = opts.version()
|
|
43
|
+
const key = keyOf(entityId, project)
|
|
44
|
+
const hit = cache.get(key)
|
|
45
|
+
if (hit != null && hit.revision === revision && hit.version === version) {
|
|
46
|
+
cache.delete(key)
|
|
47
|
+
cache.set(key, hit)
|
|
48
|
+
return hit.view
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const records = [
|
|
52
|
+
...await store.list({ entityId, project: null }),
|
|
53
|
+
...(project != null ? await store.list({ entityId, project }) : []),
|
|
54
|
+
]
|
|
55
|
+
const view = scopedSchemaHelper.scopedRegistryOf(scopedSchemaHelper.resolveScopedBundle(opts.code().bundle(), records, { entityId, project, revision }))
|
|
56
|
+
cache.delete(key)
|
|
57
|
+
cache.set(key, { revision, version, view })
|
|
58
|
+
while (cache.size > limit) {
|
|
59
|
+
const oldest = cache.keys().next().value
|
|
60
|
+
if (oldest == null) {
|
|
61
|
+
break
|
|
62
|
+
}
|
|
63
|
+
cache.delete(oldest)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return view
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
of: async (entityId, project) => opts.port() == null ? opts.code() : await scoped(entityId, project),
|
|
71
|
+
scoped,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const planOf = (declarations: SchemaDeclarations): Planned[] => [
|
|
76
|
+
...(declarations.assigneeTypes ?? []).map(schema => ({ kind: PlanningSchemaKind.AssigneeType, key: schema.type, definition: schema })),
|
|
77
|
+
...(declarations.flows ?? []).map(flow => ({ kind: PlanningSchemaKind.Flow, key: flow.id, definition: flow })),
|
|
78
|
+
...(declarations.types ?? []).map(type => ({ kind: PlanningSchemaKind.Type, key: type.type, definition: type })),
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
const layerKey = (kind: PlanningSchemaKind, key: string): string => `${kind}:${key}`
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The data-defined schema surface of one scoped facade. Every write checks, before anything is
|
|
85
|
+
* stored: the project (a project card this scope can see), the seal of the code registry, the
|
|
86
|
+
* declaration's closed-form checks (a type's flows must resolve in its layer, counting the flows
|
|
87
|
+
* written beside it; a type's and a flow's `app` bag against the application's declarations) — and
|
|
88
|
+
* then writes through the port's compare-and-set.
|
|
89
|
+
*/
|
|
90
|
+
export const makeDefinitions = (runtime: PlanningRuntime, facade: PlanningFacade): PlanningDefinitions => {
|
|
91
|
+
const scope = facade.scope
|
|
92
|
+
const entityId = scope.entityId
|
|
93
|
+
const now = runtime.options.now ?? isoNow
|
|
94
|
+
|
|
95
|
+
const port = (): SchemaStore => {
|
|
96
|
+
const store = runtime.schemaStore()
|
|
97
|
+
if (store == null) {
|
|
98
|
+
throw new PlanningUnsupported('schemas')
|
|
99
|
+
}
|
|
100
|
+
return store
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const visible = async (project: string): Promise<boolean> => scope.projects == null || await facade.cards.load(project) != null
|
|
104
|
+
|
|
105
|
+
/** @throws {WorkcardNotFound} for anything but a project card this scope can see */
|
|
106
|
+
const assertProject = async (project?: string): Promise<void> => {
|
|
107
|
+
if (project == null) {
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
const card = await facade.cards.load(project)
|
|
111
|
+
if (card == null || !cardHelper.isProject(card)) {
|
|
112
|
+
throw new WorkcardNotFound(project)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const layerOf = async (project?: string): Promise<Map<string, ScopedSchemaRecord>> =>
|
|
117
|
+
new Map((await port().list({ entityId, project: project ?? null }))
|
|
118
|
+
.map(record => [layerKey(record.kind, record.key), record]))
|
|
119
|
+
|
|
120
|
+
/** Seal and closed-form checks for everything in the plan — nothing is written when one fails. */
|
|
121
|
+
const validate = async (plan: Planned[], project?: string): Promise<void> => {
|
|
122
|
+
const code = runtime.service().schemas
|
|
123
|
+
const app = code.app()
|
|
124
|
+
const layer = await runtime.scopedSchemas(entityId, project)
|
|
125
|
+
const pending = new Map(plan.filter(entry => entry.kind === PlanningSchemaKind.Flow)
|
|
126
|
+
.map(entry => [entry.key, entry.definition as StatusFlowSchema]))
|
|
127
|
+
const flows: Pick<PlanningSchemaRegistry, 'flow'> = { flow: id => pending.get(id) ?? layer.flow(id) }
|
|
128
|
+
|
|
129
|
+
for (const entry of plan) {
|
|
130
|
+
scopedSchemaHelper.assertOverridable(code, entry.kind, entry.key)
|
|
131
|
+
if (entry.kind === PlanningSchemaKind.AssigneeType) {
|
|
132
|
+
if (project != null) throw new PlanningUnsupported('assignee-schema:project-layer')
|
|
133
|
+
scopedSchemaHelper.assertAssigneeTypeSchema(entry.definition as AssigneeTypeSchema)
|
|
134
|
+
} else if (entry.kind === PlanningSchemaKind.Flow) {
|
|
135
|
+
scopedSchemaHelper.assertFlowSchema(entry.definition as StatusFlowSchema, app)
|
|
136
|
+
} else {
|
|
137
|
+
scopedSchemaHelper.assertTypeSchema(entry.definition as WorkcardTypeSchema, flows, app)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const recordOf = (
|
|
143
|
+
entry: Planned, version: number, project: string | undefined, current?: ScopedSchemaRecord, retired?: boolean
|
|
144
|
+
): ScopedSchemaRecord => {
|
|
145
|
+
const at = now()
|
|
146
|
+
const by = changesUtils.actorOf(scope, {} as TransitionExecution)
|
|
147
|
+
return Object.fromEntries(Object.entries({
|
|
148
|
+
entityId,
|
|
149
|
+
project,
|
|
150
|
+
kind: entry.kind,
|
|
151
|
+
key: entry.key,
|
|
152
|
+
version,
|
|
153
|
+
definition: { ...structuredClone(entry.definition), version },
|
|
154
|
+
retired: retired === true ? true : undefined,
|
|
155
|
+
createdAt: current?.createdAt ?? at,
|
|
156
|
+
updatedAt: current != null ? at : undefined,
|
|
157
|
+
by: Object.keys(by).length > 0 ? by : undefined,
|
|
158
|
+
}).filter(([, value]) => value !== undefined)) as unknown as ScopedSchemaRecord
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const unit = async <R>(run: () => Promise<R>): Promise<R> => {
|
|
162
|
+
const store = runtime.service().store()
|
|
163
|
+
return store.unit != null ? await store.unit(entityId, run) : await run()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const write = async (
|
|
167
|
+
plan: Planned[], opts: SchemaWriteOptions | undefined, versionOf: (entry: Planned, current?: ScopedSchemaRecord) => number | null
|
|
168
|
+
): Promise<ScopedSchemaRecord[]> => await unit(async () => {
|
|
169
|
+
await assertProject(opts?.project)
|
|
170
|
+
const layer = await layerOf(opts?.project)
|
|
171
|
+
const todo = plan
|
|
172
|
+
.map(entry => ({ entry, current: layer.get(layerKey(entry.kind, entry.key)) }))
|
|
173
|
+
.map(item => ({ ...item, version: versionOf(item.entry, item.current) }))
|
|
174
|
+
.filter((item): item is typeof item & { version: number } => item.version != null)
|
|
175
|
+
await validate(todo.map(item => item.entry), opts?.project)
|
|
176
|
+
|
|
177
|
+
const written: ScopedSchemaRecord[] = []
|
|
178
|
+
for (const item of todo) {
|
|
179
|
+
written.push(await port().put(recordOf(item.entry, item.version, opts?.project, item.current)))
|
|
180
|
+
}
|
|
181
|
+
return written
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
const put = async (entry: Planned, opts?: SchemaWriteOptions): Promise<ScopedSchemaRecord> =>
|
|
185
|
+
(await write([entry], opts, () => entry.definition.version))[0]
|
|
186
|
+
|
|
187
|
+
const definitions: PlanningDefinitions = {
|
|
188
|
+
bundle: async project => {
|
|
189
|
+
await assertProject(project)
|
|
190
|
+
return (await runtime.scopedSchemas(entityId, project)).bundle()
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
registry: async project => {
|
|
194
|
+
await assertProject(project)
|
|
195
|
+
return await runtime.scopedSchemas(entityId, project)
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
records: async opts => {
|
|
199
|
+
if (typeof opts?.project === 'string' && !await visible(opts.project)) {
|
|
200
|
+
return []
|
|
201
|
+
}
|
|
202
|
+
const records = await port().list(Object.fromEntries(Object.entries({
|
|
203
|
+
entityId, project: opts?.project, kind: opts?.kind, retired: opts?.retired,
|
|
204
|
+
}).filter(([, value]) => value !== undefined)) as { entityId: string })
|
|
205
|
+
const allowed = await Promise.all(records.map(async record => record.project == null || await visible(record.project)))
|
|
206
|
+
return records.filter((_record, index) => allowed[index])
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
putAssigneeType: async schema => await put({ kind: PlanningSchemaKind.AssigneeType, key: schema.type, definition: schema }),
|
|
210
|
+
|
|
211
|
+
putType: async (type, opts) => await put({ kind: PlanningSchemaKind.Type, key: type.type, definition: type }, opts),
|
|
212
|
+
|
|
213
|
+
putFlow: async (flow, opts) => await put({ kind: PlanningSchemaKind.Flow, key: flow.id, definition: flow }, opts),
|
|
214
|
+
|
|
215
|
+
define: async (declarations, opts) =>
|
|
216
|
+
await write(planOf(declarations), opts, (_, current) => (current?.version ?? 0) + 1),
|
|
217
|
+
|
|
218
|
+
seed: async (declarations, opts) => {
|
|
219
|
+
const written: ScopedSchemaRecord[] = []
|
|
220
|
+
try {
|
|
221
|
+
written.push(...await write(planOf(declarations), opts, (_, current) => current == null ? 1 : null))
|
|
222
|
+
} catch (error) {
|
|
223
|
+
if (!(error instanceof SchemaConflict)) {
|
|
224
|
+
throw error
|
|
225
|
+
}
|
|
226
|
+
// A concurrent seed wrote a key first; what is left is still only what the layer lacks.
|
|
227
|
+
written.push(...await write(planOf(declarations), opts, (_, current) => current == null ? 1 : null))
|
|
228
|
+
}
|
|
229
|
+
return written
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
retire: async (kind, key, opts) => await unit(async () => {
|
|
233
|
+
await assertProject(opts?.project)
|
|
234
|
+
const current = (await layerOf(opts?.project)).get(layerKey(kind, key))
|
|
235
|
+
if (current == null) {
|
|
236
|
+
throw kind === PlanningSchemaKind.Type ? new UnknownWorkcardType(key) : new UnknownStatusFlow(key)
|
|
237
|
+
}
|
|
238
|
+
if (current.retired === true) {
|
|
239
|
+
return current
|
|
240
|
+
}
|
|
241
|
+
const retiring = recordOf({ kind, key, definition: current.definition }, current.version + 1, opts?.project, current, true)
|
|
242
|
+
|
|
243
|
+
if (kind === PlanningSchemaKind.Flow) {
|
|
244
|
+
const code = runtime.service().schemas.bundle()
|
|
245
|
+
const all = (await port().list({ entityId }))
|
|
246
|
+
.map(record => record.kind === kind && record.key === key && record.project === current.project ? retiring : record)
|
|
247
|
+
// The organization's layer reaches every project; a project's reaches itself alone.
|
|
248
|
+
const projects = opts?.project != null
|
|
249
|
+
? [opts.project]
|
|
250
|
+
: [undefined, ...new Set(all.map(record => record.project).filter((id): id is string => id != null))]
|
|
251
|
+
for (const project of projects) {
|
|
252
|
+
const user = scopedSchemaHelper.flowInUse(scopedSchemaHelper.resolveScopedBundle(code, all, { entityId, project }), key)
|
|
253
|
+
if (user != null) {
|
|
254
|
+
throw new SchemaInUse(`flow:${key}:type:${user}${project != null ? `:project:${project}` : ''}`)
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return await port().put(retiring)
|
|
260
|
+
}),
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return definitions
|
|
264
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ChangeSet, PlanningSchemaRegistry, PlanningScope, Transition, TransitionActor, TransitionExecution,
|
|
3
|
+
} from '@owlmeans/planning'
|
|
4
|
+
import type { Resolved } from '../types.js'
|
|
5
|
+
|
|
6
|
+
/** What step 11 builds the appended transition from. */
|
|
7
|
+
export interface AppendParams {
|
|
8
|
+
exec: TransitionExecution
|
|
9
|
+
resolved: Resolved
|
|
10
|
+
scope: PlanningScope
|
|
11
|
+
set: ChangeSet
|
|
12
|
+
cardId: string
|
|
13
|
+
seq: number
|
|
14
|
+
project?: string
|
|
15
|
+
at: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** The record an execution appends: its changes, its actor and the transition itself. */
|
|
19
|
+
export interface ChangesUtils {
|
|
20
|
+
/**
|
|
21
|
+
* Step 9: the recorded `changes`/`unset`, and whether an update wrote nothing.
|
|
22
|
+
*
|
|
23
|
+
* @throws {IllegalTransition | PlanningError}
|
|
24
|
+
*/
|
|
25
|
+
changeSetOf: (
|
|
26
|
+
exec: TransitionExecution, resolved: Resolved, schemas: PlanningSchemaRegistry, at: string
|
|
27
|
+
) => { set: ChangeSet, empty: boolean }
|
|
28
|
+
/**
|
|
29
|
+
* Who wrote it — the SCOPE's identity, never the execution's.
|
|
30
|
+
*
|
|
31
|
+
* `profileId`, `userId`, `service` and `channel` come only from the scope (the server builds it
|
|
32
|
+
* from the authenticated request); the descriptive `agent`/`runId` an in-process caller passes on
|
|
33
|
+
* the execution are kept unless the scope's own actor names them.
|
|
34
|
+
*/
|
|
35
|
+
actorOf: (scope: PlanningScope, exec: TransitionExecution) => TransitionActor
|
|
36
|
+
/** Step 11's record: the event as it is appended, `pending`. */
|
|
37
|
+
transitionOf: (params: AppendParams) => Transition
|
|
38
|
+
}
|
package/src/executor/changes.ts
CHANGED
|
@@ -1,78 +1,59 @@
|
|
|
1
|
-
import { CommitState,
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
TransitionExecution, WorkcardDraft,
|
|
5
|
-
} from '@owlmeans/planning'
|
|
6
|
-
import type { Resolved } from './resolve.js'
|
|
1
|
+
import { changesHelper, CommitState, TransitionAction, type ChangeSet, type PlanningSchemaRegistry, type PlanningScope, type RelationshipDraft, type Transition, type TransitionActor, type TransitionExecution, type WorkcardDraft } from '@owlmeans/planning'
|
|
2
|
+
import type { ChangesUtils, AppendParams } from './changes/types.js'
|
|
3
|
+
import type { Resolved } from './types.js'
|
|
7
4
|
|
|
8
5
|
const clean = <T extends object>(record: T): T =>
|
|
9
6
|
Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined)) as T
|
|
10
7
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
exec: TransitionExecution, resolved: Resolved, schemas: PlanningSchemaRegistry, at: string
|
|
18
|
-
): { set: ChangeSet, empty: boolean } => {
|
|
19
|
-
const set = computeChanges(resolved.card, exec, resolved.type, schemas, at, { slot: resolved.slot })
|
|
20
|
-
const empty = exec.action === TransitionAction.Update && isEmptyChange(set)
|
|
8
|
+
export const createChangesUtils = (): ChangesUtils => {
|
|
9
|
+
const changeSetOf = (
|
|
10
|
+
exec: TransitionExecution, resolved: Resolved, schemas: PlanningSchemaRegistry, at: string
|
|
11
|
+
): { set: ChangeSet, empty: boolean } => {
|
|
12
|
+
const set = changesHelper.computeChanges(resolved.card, exec, resolved.type, schemas, at, { slot: resolved.slot })
|
|
13
|
+
const empty = exec.action === TransitionAction.Update && changesHelper.isEmptyChange(set)
|
|
21
14
|
|
|
22
|
-
|
|
23
|
-
}
|
|
15
|
+
return { set, empty }
|
|
16
|
+
}
|
|
24
17
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
export const actorOf = (scope: PlanningScope, exec: TransitionExecution): TransitionActor => clean({
|
|
33
|
-
agent: exec.actor?.agent,
|
|
34
|
-
runId: exec.actor?.runId,
|
|
35
|
-
...clean({ profileId: scope.profileId, userId: scope.userId, service: scope.service }),
|
|
36
|
-
...clean(scope.actor ?? {}),
|
|
37
|
-
channel: scope.channel ?? scope.actor?.channel,
|
|
38
|
-
})
|
|
18
|
+
const actorOf = (scope: PlanningScope, exec: TransitionExecution): TransitionActor => clean({
|
|
19
|
+
agent: exec.actor?.agent,
|
|
20
|
+
runId: exec.actor?.runId,
|
|
21
|
+
...clean({ profileId: scope.profileId, userId: scope.userId, service: scope.service }),
|
|
22
|
+
...clean(scope.actor ?? {}),
|
|
23
|
+
channel: scope.channel ?? scope.actor?.channel,
|
|
24
|
+
})
|
|
39
25
|
|
|
40
|
-
const withFrom = (link: RelationshipDraft, card: string): RelationshipDraft => ({ ...link, from: link.from ?? card })
|
|
26
|
+
const withFrom = (link: RelationshipDraft, card: string): RelationshipDraft => ({ ...link, from: link.from ?? card })
|
|
41
27
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
resolved: Resolved
|
|
46
|
-
scope: PlanningScope
|
|
47
|
-
set: ChangeSet
|
|
48
|
-
cardId: string
|
|
49
|
-
seq: number
|
|
50
|
-
project?: string
|
|
51
|
-
at: string
|
|
52
|
-
}): Transition => {
|
|
53
|
-
const { exec, resolved, scope, set, cardId, seq, project, at } = params
|
|
54
|
-
const kind = resolved.create ? (exec.card as WorkcardDraft).kind : resolved.card!.kind
|
|
28
|
+
const transitionOf = (params: AppendParams): Transition => {
|
|
29
|
+
const { exec, resolved, scope, set, cardId, seq, project, at } = params
|
|
30
|
+
const kind = resolved.create ? (exec.card as WorkcardDraft).kind : resolved.card!.kind
|
|
55
31
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
32
|
+
return clean({
|
|
33
|
+
entityId: scope.entityId,
|
|
34
|
+
card: cardId,
|
|
35
|
+
kind,
|
|
36
|
+
type: resolved.type.type,
|
|
37
|
+
project,
|
|
38
|
+
seq,
|
|
39
|
+
action: exec.action,
|
|
40
|
+
flow: set.flow,
|
|
41
|
+
transition: exec.action === TransitionAction.Transit ? exec.transition : undefined,
|
|
42
|
+
from: set.from,
|
|
43
|
+
to: set.to,
|
|
44
|
+
changes: set.changes,
|
|
45
|
+
unset: set.unset.length > 0 ? set.unset : undefined,
|
|
46
|
+
link: exec.link != null && exec.action !== TransitionAction.Create ? withFrom(exec.link, cardId) : undefined,
|
|
47
|
+
links: resolved.create && (exec.links?.length ?? 0) > 0 ? exec.links!.map(link => withFrom(link, cardId)) : undefined,
|
|
48
|
+
actor: actorOf(scope, exec),
|
|
49
|
+
cause: exec.cause,
|
|
50
|
+
key: exec.key,
|
|
51
|
+
at,
|
|
52
|
+
commit: { state: CommitState.Pending },
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { changeSetOf, actorOf, transitionOf }
|
|
78
57
|
}
|
|
58
|
+
|
|
59
|
+
export const changesUtils = createChangesUtils()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { PlanningExecContext, PlanningPlugin, PlanningScope, TransitionExecution } from '@owlmeans/planning'
|
|
2
|
+
import type { PluginRegistry } from '../../types.js'
|
|
3
|
+
import type { Resolved } from '../types.js'
|
|
4
|
+
|
|
5
|
+
/** Step 8 of the write path: the code a card gets, unique within its policy's scope. */
|
|
6
|
+
export interface CodeUtils {
|
|
7
|
+
/**
|
|
8
|
+
* Step 8: the code a new card gets, or the check a changed code passes.
|
|
9
|
+
*
|
|
10
|
+
* A caller-supplied code is checked for uniqueness; otherwise the plugins' `mintCode` chain answers
|
|
11
|
+
* (first answer wins, and is checked too); otherwise the type's policy mints one — a slug policy
|
|
12
|
+
* derives it from the title. A type with no policy and no supplied code gets none.
|
|
13
|
+
*
|
|
14
|
+
* @throws {CodeTaken}
|
|
15
|
+
*/
|
|
16
|
+
assignCode: (
|
|
17
|
+
exec: TransitionExecution,
|
|
18
|
+
resolved: Resolved,
|
|
19
|
+
scope: PlanningScope,
|
|
20
|
+
registry: PluginRegistry,
|
|
21
|
+
contextOf: (plugin: PlanningPlugin) => PlanningExecContext
|
|
22
|
+
) => Promise<TransitionExecution>
|
|
23
|
+
}
|
package/src/executor/code.ts
CHANGED
|
@@ -1,83 +1,76 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CODE_MINT_ATTEMPTS, CodeScope, CodeStyle, CodeTaken, mintCode, TransitionAction,
|
|
3
|
-
} from '@owlmeans/planning'
|
|
4
|
-
import type {
|
|
5
|
-
CodePolicy, PlanningExecContext, PlanningPlugin, PlanningScope, TransitionExecution, WorkcardDraft,
|
|
6
|
-
} from '@owlmeans/planning'
|
|
1
|
+
import { CODE_MINT_ATTEMPTS, codeHelper, CodeScope, CodeStyle, CodeTaken, TransitionAction, type CodePolicy, type PlanningExecContext, type PlanningPlugin, type PlanningScope, type TransitionExecution, type Workcard, type WorkcardDraft } from '@owlmeans/planning'
|
|
7
2
|
import type { Criteria } from '@owlmeans/resource'
|
|
8
|
-
import type {
|
|
9
|
-
import type {
|
|
10
|
-
import type { Resolved } from './
|
|
3
|
+
import type { PluginRegistry } from '../types.js'
|
|
4
|
+
import type { CodeUtils } from './code/types.js'
|
|
5
|
+
import type { Resolved } from './types.js'
|
|
11
6
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
7
|
+
export const createCodeUtils = (): CodeUtils => {
|
|
8
|
+
/** The criteria a code must be unique within — the policy's scope, `parent` when there is none. */
|
|
9
|
+
const scopeCriteria = (
|
|
10
|
+
scope: PlanningScope, type: string, parent: string | undefined, policy?: CodePolicy
|
|
11
|
+
): Criteria<Workcard> => ({
|
|
12
|
+
entityId: scope.entityId,
|
|
13
|
+
type,
|
|
14
|
+
...((policy?.uniqueWithin ?? CodeScope.Parent) === CodeScope.Parent ? { parent: parent ?? null } : {}),
|
|
15
|
+
}) as Criteria<Workcard>
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
const
|
|
17
|
+
/** Is a code taken within the scope the type's policy names (its parent when it names none)? */
|
|
18
|
+
const takenProbe = (resolved: Resolved, scope: PlanningScope, parent: string | undefined): (code: string) => Promise<boolean> => {
|
|
19
|
+
const base = scopeCriteria(scope, resolved.type.type, parent, resolved.type.code)
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
21
|
+
return async (code: string): Promise<boolean> =>
|
|
22
|
+
await resolved.store.cards.count({ ...base, code } as Criteria<Workcard>) > 0
|
|
23
|
+
}
|
|
27
24
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
*/
|
|
37
|
-
export const assignCode = async (
|
|
38
|
-
exec: TransitionExecution,
|
|
39
|
-
resolved: Resolved,
|
|
40
|
-
scope: PlanningScope,
|
|
41
|
-
registry: PluginRegistry,
|
|
42
|
-
contextOf: (plugin: PlanningPlugin) => PlanningExecContext
|
|
43
|
-
): Promise<TransitionExecution> => {
|
|
44
|
-
const policy = resolved.type.code
|
|
25
|
+
const assignCode = async (
|
|
26
|
+
exec: TransitionExecution,
|
|
27
|
+
resolved: Resolved,
|
|
28
|
+
scope: PlanningScope,
|
|
29
|
+
registry: PluginRegistry,
|
|
30
|
+
contextOf: (plugin: PlanningPlugin) => PlanningExecContext
|
|
31
|
+
): Promise<TransitionExecution> => {
|
|
32
|
+
const policy = resolved.type.code
|
|
45
33
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
34
|
+
if (exec.action !== TransitionAction.Create) {
|
|
35
|
+
const code = exec.changes?.code
|
|
36
|
+
if (code != null && code !== resolved.card?.code
|
|
37
|
+
&& await takenProbe(resolved, scope, exec.changes?.parent ?? resolved.card?.parent)(code)) {
|
|
38
|
+
throw new CodeTaken(code)
|
|
39
|
+
}
|
|
40
|
+
return exec
|
|
51
41
|
}
|
|
52
|
-
return exec
|
|
53
|
-
}
|
|
54
42
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
43
|
+
const draft = exec.card as WorkcardDraft
|
|
44
|
+
const taken = takenProbe(resolved, scope, draft.parent)
|
|
45
|
+
if (draft.code != null && draft.code !== '') {
|
|
46
|
+
if (await taken(draft.code)) {
|
|
47
|
+
throw new CodeTaken(draft.code)
|
|
48
|
+
}
|
|
49
|
+
return exec
|
|
50
|
+
}
|
|
51
|
+
if (policy == null) {
|
|
52
|
+
return exec
|
|
60
53
|
}
|
|
61
|
-
return exec
|
|
62
|
-
}
|
|
63
|
-
if (policy == null) {
|
|
64
|
-
return exec
|
|
65
|
-
}
|
|
66
54
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
55
|
+
let code = await registry.mintCode(draft, taken, contextOf)
|
|
56
|
+
if (code != null) {
|
|
57
|
+
if (await taken(code)) {
|
|
58
|
+
throw new CodeTaken(code)
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
const count = policy.style === CodeStyle.Sequential
|
|
62
|
+
? await resolved.store.cards.count(scopeCriteria(scope, draft.type, draft.parent, policy))
|
|
63
|
+
: undefined
|
|
64
|
+
code = await codeHelper.mintCode(policy, taken, CODE_MINT_ATTEMPTS, {
|
|
65
|
+
...(policy.style === CodeStyle.Slug ? { seed: draft.title } : {}),
|
|
66
|
+
...(count != null ? { count } : {}),
|
|
67
|
+
})
|
|
71
68
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
? await resolved.store.cards.count(scopeCriteria(scope, draft.type, draft.parent, policy))
|
|
75
|
-
: undefined
|
|
76
|
-
code = await mintCode(policy, taken, CODE_MINT_ATTEMPTS, {
|
|
77
|
-
...(policy.style === CodeStyle.Slug ? { seed: draft.title } : {}),
|
|
78
|
-
...(count != null ? { count } : {}),
|
|
79
|
-
})
|
|
69
|
+
|
|
70
|
+
return { ...exec, card: { ...draft, code } }
|
|
80
71
|
}
|
|
81
72
|
|
|
82
|
-
return {
|
|
73
|
+
return { assignCode }
|
|
83
74
|
}
|
|
75
|
+
|
|
76
|
+
export const codeUtils = createCodeUtils()
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { TransitionAction } from '@owlmeans/planning'
|
|
2
|
+
|
|
3
|
+
/** The field a create's draft writes once and nothing moves afterwards. */
|
|
4
|
+
export const CREATOR = 'createdBy'
|
|
5
|
+
|
|
6
|
+
export const TRIMMED = ['title', 'description', 'code'] as const
|
|
7
|
+
|
|
8
|
+
export const ACTIONS = new Set<string>(Object.values(TransitionAction))
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { PlanningScope, TransitionExecution } from '@owlmeans/planning'
|
|
2
|
+
|
|
3
|
+
/** Who owns a card: `createdBy`, stamped from the scope on a create and fixed after it. */
|
|
4
|
+
export interface CreatorHelper {
|
|
5
|
+
/**
|
|
6
|
+
* The subject a create made under `scope` is stamped with as its `createdBy`: the scope's
|
|
7
|
+
* `profileId`, else its `userId`, else the same two of its `actor` — the authenticated subject an
|
|
8
|
+
* HTTP scope carries, in the same format. `undefined` for a scope that names nobody (a service or a
|
|
9
|
+
* system scope).
|
|
10
|
+
*/
|
|
11
|
+
creatorOf: (scope: PlanningScope) => string | undefined
|
|
12
|
+
/**
|
|
13
|
+
* A create's draft with `createdBy` defaulted to {@link CreatorHelper.creatorOf} the scope. A
|
|
14
|
+
* `createdBy` the caller supplied wins; a scope that names nobody leaves the draft as it is;
|
|
15
|
+
* anything but a create is returned untouched. Never mutates `exec`.
|
|
16
|
+
*
|
|
17
|
+
* The executor applies it to every execution, so an in-process facade create and a wire create
|
|
18
|
+
* stamp the same value.
|
|
19
|
+
*/
|
|
20
|
+
withCreator: (exec: TransitionExecution, scope: PlanningScope) => TransitionExecution
|
|
21
|
+
/**
|
|
22
|
+
* Refuse an execution that names `createdBy` in `changes` or `unset`, whatever the action — a
|
|
23
|
+
* create included, whose owner is its draft's. After the create it never moves, so an ownership
|
|
24
|
+
* check (`card.createdBy === profileId`) cannot be bypassed by anyone allowed to update the card.
|
|
25
|
+
*
|
|
26
|
+
* The executor runs it with the immutables of step 6, after the `before` chain, so a wire body, an
|
|
27
|
+
* in-process caller and a plugin are refused alike. It is the executor's own check, whatever
|
|
28
|
+
* `@owlmeans/planning` build `changesHelper.assertMutable` comes from.
|
|
29
|
+
*
|
|
30
|
+
* @throws {PlanningError} `planning:immutable:createdBy`
|
|
31
|
+
*/
|
|
32
|
+
assertCreatorFixed: (exec: TransitionExecution) => void
|
|
33
|
+
}
|