@owlmeans/server-planning 0.1.18-rc.21 → 0.1.18-rc.22
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 +24 -17
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/server-planning/SKILL.md +28 -28
- 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 +31 -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 +8 -28
- package/build/actions/execute.d.ts.map +1 -1
- package/build/actions/execute.js +17 -101
- 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 +79 -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/{specs.d.ts → list-specification-revisions.d.ts} +1 -3
- 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/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 +5 -3
- package/build/actions/types.d.ts.map +1 -1
- package/build/actions/watch.d.ts +2 -2
- package/build/actions/watch.d.ts.map +1 -1
- package/build/actions/watch.js +2 -2
- 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 +8 -13
- package/build/conformance/assert.d.ts.map +1 -1
- package/build/conformance/assert.js +41 -43
- package/build/conformance/assert.js.map +1 -1
- package/build/conformance/cases.d.ts.map +1 -1
- package/build/conformance/cases.js +86 -67
- package/build/conformance/cases.js.map +1 -1
- 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 +3 -1
- package/build/conformance/consts.d.ts.map +1 -1
- package/build/conformance/consts.js +84 -1
- package/build/conformance/consts.js.map +1 -1
- 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 +8 -8
- package/build/conformance/fixtures.d.ts.map +1 -1
- package/build/conformance/fixtures.js +24 -107
- package/build/conformance/fixtures.js.map +1 -1
- package/build/conformance/index.d.ts +3 -0
- package/build/conformance/index.d.ts.map +1 -1
- package/build/conformance/index.js +1 -0
- package/build/conformance/index.js.map +1 -1
- package/build/definitions.d.ts.map +1 -1
- package/build/definitions.js +9 -9
- package/build/definitions.js.map +1 -1
- 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/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 +6 -25
- package/build/executor/creator.d.ts.map +1 -1
- package/build/executor/creator.js +26 -41
- package/build/executor/creator.js.map +1 -1
- 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 +4 -41
- package/build/executor/resolve.d.ts.map +1 -1
- package/build/executor/resolve.js +142 -157
- package/build/executor/resolve.js.map +1 -1
- package/build/executor/types.d.ts +0 -2
- package/build/executor/types.d.ts.map +1 -1
- package/build/executor/validate.d.ts +1 -1
- package/build/executor/validate.d.ts.map +1 -1
- package/build/executor/validate.js +16 -15
- package/build/executor/validate.js.map +1 -1
- package/build/executor.d.ts +4 -3
- package/build/executor.d.ts.map +1 -1
- package/build/executor.js +18 -17
- package/build/executor.js.map +1 -1
- package/build/facade.d.ts.map +1 -1
- package/build/facade.js +16 -16
- package/build/facade.js.map +1 -1
- package/build/helper.d.ts +1 -6
- package/build/helper.d.ts.map +1 -1
- package/build/helper.js +3 -8
- package/build/helper.js.map +1 -1
- package/build/projection.d.ts +2 -0
- package/build/projection.d.ts.map +1 -1
- package/build/projection.js +6 -4
- package/build/projection.js.map +1 -1
- 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 +1 -4
- package/build/queue/index.d.ts.map +1 -1
- package/build/queue/index.js +1 -4
- package/build/queue/index.js.map +1 -1
- package/build/store/composite.d.ts.map +1 -1
- package/build/store/composite.js +2 -2
- package/build/store/composite.js.map +1 -1
- 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 +7 -30
- package/build/store/fold.d.ts.map +1 -1
- package/build/store/fold.js +185 -203
- 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.map +1 -1
- package/build/store/memory.js +0 -0
- package/build/store/memory.js.map +1 -1
- package/build/store/types.local.d.ts +3 -1
- package/build/store/types.local.d.ts.map +1 -1
- package/build/types.d.ts +2 -1
- package/build/types.d.ts.map +1 -1
- 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 +21 -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 +33 -31
- 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 -1
- 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 +10 -43
- package/build/utils/scope.d.ts.map +1 -1
- package/build/utils/scope.js +21 -99
- package/build/utils/scope.js.map +1 -1
- package/package.json +18 -18
- package/src/actions/apply-schema-request.ts +35 -0
- package/src/actions/commit.ts +7 -5
- package/src/actions/define-schemas.ts +34 -0
- package/src/actions/execute.ts +30 -115
- package/src/actions/execution/types.ts +34 -0
- package/src/actions/execution.ts +96 -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/{specs.ts → list-specification-revisions.ts} +4 -11
- package/src/actions/summarize-cards.ts +15 -0
- package/src/actions/transitions.ts +6 -4
- package/src/actions/types.ts +4 -3
- package/src/actions/watch.ts +4 -3
- package/src/conformance/assert/types.ts +16 -0
- package/src/conformance/assert.ts +49 -41
- package/src/conformance/cases.ts +88 -67
- package/src/conformance/clock.ts +11 -0
- package/src/conformance/consts.ts +87 -1
- package/src/conformance/errors.ts +7 -0
- package/src/conformance/fixtures/types.ts +11 -0
- package/src/conformance/fixtures.ts +32 -107
- package/src/conformance/index.ts +3 -0
- package/src/definitions.ts +9 -9
- package/src/executor/changes/types.ts +38 -0
- package/src/executor/changes.ts +48 -63
- package/src/executor/code/types.ts +23 -0
- package/src/executor/code.ts +60 -61
- package/src/executor/creator/types.ts +33 -0
- package/src/executor/creator.ts +33 -41
- package/src/executor/resolve/types.ts +30 -0
- package/src/executor/resolve.ts +153 -167
- package/src/executor/types.ts +0 -3
- package/src/executor/validate.ts +17 -16
- package/src/executor.ts +20 -17
- package/src/facade.ts +16 -16
- package/src/helper.ts +3 -8
- package/src/projection.ts +8 -4
- package/src/queue/declare.ts +7 -0
- package/src/queue/index.ts +1 -8
- package/src/store/composite.ts +2 -2
- package/src/store/fold/types.ts +44 -0
- package/src/store/fold.ts +209 -212
- package/src/store/index.ts +1 -0
- package/src/store/memory.ts +0 -0
- package/src/store/types.local.ts +1 -1
- package/src/types.ts +1 -1
- package/src/utils/access/types.ts +30 -0
- package/src/utils/access.ts +33 -0
- package/src/utils/guard/types.ts +19 -0
- package/src/utils/guard.ts +41 -30
- package/src/utils/handler/types.ts +28 -0
- package/src/utils/handler.ts +35 -0
- package/src/utils/index.ts +9 -1
- package/src/utils/request/types.ts +23 -0
- package/src/utils/request.ts +40 -0
- package/src/utils/scope.ts +25 -108
- package/tests/creator.spec.ts +18 -18
- package/build/actions/cards.d.ts +0 -14
- 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 -28
- package/build/actions/schemas.d.ts.map +0 -1
- package/build/actions/schemas.js +0 -69
- package/build/actions/schemas.js.map +0 -1
- 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 -48
- package/src/actions/schemas.ts +0 -87
package/src/store/fold.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { logger } from '@owlmeans/log'
|
|
2
|
-
import {
|
|
2
|
+
import { applyHelper, cardHelper, CommitState, PlanningUnsupported, TransitionAction, type CommitEvent, type PlanningStore, type Relationship, type SpecificationRevision, type Transition, type Workcard } from '@owlmeans/planning'
|
|
3
|
+
import type { FoldHelper } from './fold/types.js'
|
|
3
4
|
import type { FoldOptions, FoldResult } from './types.js'
|
|
4
5
|
import { CONTENT_KEYS } from './consts.local.js'
|
|
5
6
|
|
|
@@ -13,248 +14,244 @@ const clean = <T extends object>(record: T): T =>
|
|
|
13
14
|
const errorText = (error: unknown): string =>
|
|
14
15
|
error instanceof Error ? error.message : `${error}`
|
|
15
16
|
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
export const createFoldHelper = (): FoldHelper => {
|
|
18
|
+
const safely = async (label: string, run: () => Promise<void> | void | undefined): Promise<void> => {
|
|
19
|
+
try {
|
|
20
|
+
await run()
|
|
21
|
+
} catch (error) {
|
|
22
|
+
log.error('Planning store step failed', { step: label, error })
|
|
23
|
+
}
|
|
21
24
|
}
|
|
22
|
-
}
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
})
|
|
26
|
+
const commitEventOf = (
|
|
27
|
+
transition: Transition, state: CommitState, at: string, extra?: { error?: string, record?: Workcard | null }
|
|
28
|
+
): CommitEvent => clean({
|
|
29
|
+
transition: transition.id!,
|
|
30
|
+
card: transition.card,
|
|
31
|
+
entityId: transition.entityId,
|
|
32
|
+
project: transition.project,
|
|
33
|
+
kind: transition.kind,
|
|
34
|
+
type: transition.type,
|
|
35
|
+
seq: transition.seq,
|
|
36
|
+
action: transition.action,
|
|
37
|
+
state,
|
|
38
|
+
at,
|
|
39
|
+
error: extra?.error,
|
|
40
|
+
record: extra?.record,
|
|
41
|
+
})
|
|
41
42
|
|
|
42
|
-
const bearsRelationships = (transition: Transition): boolean =>
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
const bearsRelationships = (transition: Transition): boolean =>
|
|
44
|
+
transition.action === TransitionAction.Link
|
|
45
|
+
|| transition.action === TransitionAction.Unlink
|
|
46
|
+
|| transition.action === TransitionAction.Delete
|
|
47
|
+
|| (transition.action === TransitionAction.Create && (transition.links?.length ?? 0) > 0)
|
|
47
48
|
|
|
48
|
-
const foldRelationships = async (store: PlanningStore, transition: Transition): Promise<void> => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
49
|
+
const foldRelationships = async (store: PlanningStore, transition: Transition): Promise<void> => {
|
|
50
|
+
const links = store.links
|
|
51
|
+
if (links == null || !bearsRelationships(transition)) {
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
const where = { entityId: transition.entityId }
|
|
55
|
+
const [outgoing, incoming] = await Promise.all([
|
|
56
|
+
links.list({ ...where, from: transition.card }, { size: 0 }),
|
|
57
|
+
links.list({ ...where, to: transition.card }, { size: 0 }),
|
|
58
|
+
])
|
|
59
|
+
const before = [...new Map([...outgoing.items, ...incoming.items].map(link => [link.id, link])).values()]
|
|
60
|
+
const after = applyHelper.applyRelationship(before, transition)
|
|
61
|
+
const kept = new Set(after.filter(link => link.id != null).map(link => link.id))
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
for (const link of before) {
|
|
64
|
+
if (link.id != null && !kept.has(link.id)) {
|
|
65
|
+
await links.drop({ entityId: transition.entityId, id: link.id })
|
|
66
|
+
}
|
|
65
67
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
for (const link of after) {
|
|
69
|
+
if (link.id == null) {
|
|
70
|
+
await links.put(link as Relationship)
|
|
71
|
+
}
|
|
70
72
|
}
|
|
71
73
|
}
|
|
72
|
-
}
|
|
73
74
|
|
|
74
|
-
const writeFold = async (
|
|
75
|
-
|
|
76
|
-
): Promise<void> => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
const writeFold = async (
|
|
76
|
+
store: PlanningStore, before: Workcard | null, after: Workcard | null, transition: Transition
|
|
77
|
+
): Promise<void> => {
|
|
78
|
+
if (transition.action === TransitionAction.Delete) {
|
|
79
|
+
if (before != null && cardHelper.isProject(before)) {
|
|
80
|
+
// A project takes everything under it, its own log included — the hub remembers the commit.
|
|
81
|
+
await store.cards.purge(transition.card, transition.entityId)
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
await foldRelationships(store, transition)
|
|
85
|
+
await store.cards.drop(transition.card, transition.entityId)
|
|
81
86
|
return
|
|
82
87
|
}
|
|
88
|
+
if (after != null) {
|
|
89
|
+
await store.cards.put(after)
|
|
90
|
+
}
|
|
83
91
|
await foldRelationships(store, transition)
|
|
84
|
-
await store.cards.drop(transition.card, transition.entityId)
|
|
85
|
-
return
|
|
86
92
|
}
|
|
87
|
-
if (after != null) {
|
|
88
|
-
await store.cards.put(after)
|
|
89
|
-
}
|
|
90
|
-
await foldRelationships(store, transition)
|
|
91
|
-
}
|
|
92
93
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
* it `committed`, `publish` the event, then run `onCommitted` — so the `after` chain runs in THIS
|
|
100
|
-
* process, exactly once per committed transition. A transition that cannot be folded is marked
|
|
101
|
-
* `failed` with the reason and a failed event is published, and the fold goes PAST it: the card's
|
|
102
|
-
* `seq` advances to the failed transition with every other value as it was, so the next
|
|
103
|
-
* transition applies normally. Nothing is removed from the log. A create that fails leaves no card
|
|
104
|
-
* to advance, so what follows it fails too; a store that refuses the advancing write itself stops
|
|
105
|
-
* the fold there and reports `followUp`, leaving the rest pending for a retry.
|
|
106
|
-
*
|
|
107
|
-
* The caller owns single flight: two folds of one card running at once would both publish. A
|
|
108
|
-
* transactional store passes `unit`, which wraps each transition's writes (a savepoint), so a write
|
|
109
|
-
* the database refuses is undone whole before the fold marks the transition failed.
|
|
110
|
-
*
|
|
111
|
-
* @throws {PlanningUnsupported} for a store without a transition log
|
|
112
|
-
*/
|
|
113
|
-
export const foldPending = async (
|
|
114
|
-
store: PlanningStore, cardId: string, entityId: string, opts: FoldOptions = {}
|
|
115
|
-
): Promise<FoldResult> => {
|
|
116
|
-
const transitions = store.transitions
|
|
117
|
-
if (transitions == null) {
|
|
118
|
-
throw new PlanningUnsupported(`fold:${store.alias ?? 'store'}`)
|
|
119
|
-
}
|
|
120
|
-
const now = opts.now ?? isoNow
|
|
121
|
-
|
|
122
|
-
let card = await store.cards.get(cardId, entityId)
|
|
123
|
-
const listed = await transitions.list(
|
|
124
|
-
{ entityId, card: cardId, sinceSeq: card?.seq ?? 0, state: CommitState.Pending },
|
|
125
|
-
{ sort: ['seq'], size: opts.limit ?? 0 }
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
let folded = 0
|
|
129
|
-
let failed = 0
|
|
130
|
-
for (const transition of listed.items) {
|
|
131
|
-
if (transition.commit.state !== CommitState.Pending || transition.id == null) {
|
|
132
|
-
continue
|
|
94
|
+
const foldPending = async (
|
|
95
|
+
store: PlanningStore, cardId: string, entityId: string, opts: FoldOptions = {}
|
|
96
|
+
): Promise<FoldResult> => {
|
|
97
|
+
const transitions = store.transitions
|
|
98
|
+
if (transitions == null) {
|
|
99
|
+
throw new PlanningUnsupported(`fold:${store.alias ?? 'store'}`)
|
|
133
100
|
}
|
|
134
|
-
|
|
135
|
-
const at = now()
|
|
101
|
+
const now = opts.now ?? isoNow
|
|
136
102
|
|
|
137
|
-
let
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
103
|
+
let card = await store.cards.get(cardId, entityId)
|
|
104
|
+
const listed = await transitions.list(
|
|
105
|
+
{ entityId, card: cardId, sinceSeq: card?.seq ?? 0, state: CommitState.Pending },
|
|
106
|
+
{ sort: ['seq'], size: opts.limit ?? 0 }
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
let folded = 0
|
|
110
|
+
let failed = 0
|
|
111
|
+
for (const transition of listed.items) {
|
|
112
|
+
if (transition.commit.state !== CommitState.Pending || transition.id == null) {
|
|
142
113
|
continue
|
|
143
114
|
}
|
|
144
|
-
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
115
|
+
await opts.touch?.()
|
|
116
|
+
const at = now()
|
|
117
|
+
|
|
118
|
+
let next: Workcard | null
|
|
119
|
+
try {
|
|
120
|
+
next = applyHelper.applyTransition(card, transition)
|
|
121
|
+
if (card != null && next === card) {
|
|
122
|
+
// Already applied by another folder, which owns its commit and its event.
|
|
123
|
+
continue
|
|
124
|
+
}
|
|
125
|
+
const before = card
|
|
126
|
+
const after = next
|
|
127
|
+
await (opts.unit != null
|
|
128
|
+
? opts.unit(async () => { await writeFold(store, before, after, transition) })
|
|
129
|
+
: writeFold(store, before, after, transition))
|
|
130
|
+
} catch (error) {
|
|
131
|
+
failed++
|
|
132
|
+
const message = errorText(error)
|
|
133
|
+
if (card != null && transition.seq > card.seq) {
|
|
134
|
+
// Fold PAST it: the record keeps its previous values and only its cursor moves, so the
|
|
135
|
+
// next transition still applies. The card is re-written whole, which also undoes a write
|
|
136
|
+
// that got half way before it threw.
|
|
137
|
+
const skipped: Workcard = { ...card, seq: transition.seq, head: Math.max(card.head ?? card.seq, transition.seq) }
|
|
138
|
+
try {
|
|
139
|
+
await store.cards.put(skipped)
|
|
140
|
+
card = skipped
|
|
141
|
+
} catch (advance) {
|
|
142
|
+
// The store itself refuses writes: leave the rest pending for a retry rather than
|
|
143
|
+
// failing every later transition as out of order.
|
|
144
|
+
log.error('Planning cannot advance a card past a failed transition', { cardId, seq: transition.seq, error: advance })
|
|
145
|
+
await safely('commit failure', () => transitions.commit(transition.id!, { state: CommitState.Failed, at, error: message }))
|
|
146
|
+
await safely('publish', () => opts.publish?.(commitEventOf(transition, CommitState.Failed, at, { error: message })))
|
|
147
|
+
return { card, folded, failed, followUp: true }
|
|
148
|
+
}
|
|
167
149
|
}
|
|
150
|
+
await safely('commit failure', () => transitions.commit(transition.id!, { state: CommitState.Failed, at, error: message }))
|
|
151
|
+
const event = commitEventOf(transition, CommitState.Failed, at, { error: message })
|
|
152
|
+
await safely('publish', () => opts.publish?.(event))
|
|
153
|
+
continue
|
|
168
154
|
}
|
|
169
|
-
|
|
170
|
-
|
|
155
|
+
|
|
156
|
+
card = next
|
|
157
|
+
folded++
|
|
158
|
+
await safely('commit', () => transitions.commit(transition.id!, { state: CommitState.Committed, at }))
|
|
159
|
+
const event = commitEventOf(transition, CommitState.Committed, at, { record: next })
|
|
171
160
|
await safely('publish', () => opts.publish?.(event))
|
|
172
|
-
|
|
161
|
+
await safely('onCommitted', () => opts.onCommitted?.(event))
|
|
173
162
|
}
|
|
174
163
|
|
|
175
|
-
card
|
|
176
|
-
folded++
|
|
177
|
-
await safely('commit', () => transitions.commit(transition.id!, { state: CommitState.Committed, at }))
|
|
178
|
-
const event = commitEventOf(transition, CommitState.Committed, at, { record: next })
|
|
179
|
-
await safely('publish', () => opts.publish?.(event))
|
|
180
|
-
await safely('onCommitted', () => opts.onCommitted?.(event))
|
|
164
|
+
return { card, folded, failed, followUp: listed.total > listed.items.length }
|
|
181
165
|
}
|
|
182
166
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
for (const transition of pending.items) {
|
|
201
|
-
if (transition.id == null) {
|
|
202
|
-
continue
|
|
167
|
+
const failPending = async (
|
|
168
|
+
store: PlanningStore, cardId: string, entityId: string, reason: string,
|
|
169
|
+
opts: Pick<FoldOptions, 'publish' | 'now'> = {}
|
|
170
|
+
): Promise<number> => {
|
|
171
|
+
const transitions = store.transitions
|
|
172
|
+
if (transitions == null) {
|
|
173
|
+
return 0
|
|
174
|
+
}
|
|
175
|
+
const at = (opts.now ?? isoNow)()
|
|
176
|
+
const pending = await transitions.list({ entityId, card: cardId, state: CommitState.Pending }, { sort: ['seq'], size: 0 })
|
|
177
|
+
for (const transition of pending.items) {
|
|
178
|
+
if (transition.id == null) {
|
|
179
|
+
continue
|
|
180
|
+
}
|
|
181
|
+
await transitions.commit(transition.id, { state: CommitState.Failed, at, error: reason })
|
|
182
|
+
const event = commitEventOf(transition, CommitState.Failed, at, { error: reason })
|
|
183
|
+
await safely('publish', () => opts.publish?.(event))
|
|
203
184
|
}
|
|
204
|
-
await transitions.commit(transition.id, { state: CommitState.Failed, at, error: reason })
|
|
205
|
-
const event = commitEventOf(transition, CommitState.Failed, at, { error: reason })
|
|
206
|
-
await safely('publish', () => opts.publish?.(event))
|
|
207
|
-
}
|
|
208
185
|
|
|
209
|
-
|
|
210
|
-
}
|
|
186
|
+
return pending.items.length
|
|
187
|
+
}
|
|
211
188
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
* body AT that revision even when the transition changed only `ref`.
|
|
216
|
-
*/
|
|
217
|
-
export const revisionsFromLog = (log: Transition[], limit?: number): SpecificationRevision[] => {
|
|
218
|
-
const ordered = log.filter(transition => transition.commit.state !== CommitState.Pending)
|
|
219
|
-
.sort((left, right) => left.seq - right.seq)
|
|
189
|
+
const revisionsFromLog = (log: Transition[], limit?: number): SpecificationRevision[] => {
|
|
190
|
+
const ordered = log.filter(transition => transition.commit.state !== CommitState.Pending)
|
|
191
|
+
.sort((left, right) => left.seq - right.seq)
|
|
220
192
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
193
|
+
const entries: SpecificationRevision[] = []
|
|
194
|
+
let state: Workcard | null = null
|
|
195
|
+
for (const transition of ordered) {
|
|
196
|
+
if (transition.commit.state === CommitState.Failed) {
|
|
197
|
+
// The fold went past it; so does the replay.
|
|
198
|
+
if (state != null && transition.seq > state.seq) {
|
|
199
|
+
state = { ...(state as Workcard), seq: transition.seq }
|
|
200
|
+
}
|
|
201
|
+
continue
|
|
228
202
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
203
|
+
try {
|
|
204
|
+
state = applyHelper.applyTransition(state, transition)
|
|
205
|
+
} catch {
|
|
206
|
+
break
|
|
207
|
+
}
|
|
208
|
+
if (state == null) {
|
|
209
|
+
break
|
|
210
|
+
}
|
|
211
|
+
const changed = transition.action === TransitionAction.Create
|
|
212
|
+
|| CONTENT_KEYS.some(key => (transition.changes as Record<string, unknown>)[key] !== undefined
|
|
213
|
+
|| transition.unset?.includes(key) === true)
|
|
214
|
+
if (!changed) {
|
|
215
|
+
continue
|
|
216
|
+
}
|
|
217
|
+
const spec = state as Workcard & { body?: string, ref?: string, bodyChars?: number, version?: number, revision?: number }
|
|
218
|
+
entries.push(clean({
|
|
219
|
+
revision: spec.revision ?? entries.length + 1,
|
|
220
|
+
body: spec.body,
|
|
221
|
+
ref: spec.ref,
|
|
222
|
+
bodyChars: spec.bodyChars,
|
|
223
|
+
version: spec.version,
|
|
224
|
+
at: transition.at,
|
|
225
|
+
by: transition.actor,
|
|
226
|
+
transition: transition.id!,
|
|
227
|
+
}))
|
|
244
228
|
}
|
|
245
|
-
|
|
246
|
-
entries.
|
|
247
|
-
|
|
248
|
-
body: spec.body,
|
|
249
|
-
ref: spec.ref,
|
|
250
|
-
bodyChars: spec.bodyChars,
|
|
251
|
-
version: spec.version,
|
|
252
|
-
at: transition.at,
|
|
253
|
-
by: transition.actor,
|
|
254
|
-
transition: transition.id!,
|
|
255
|
-
}))
|
|
229
|
+
|
|
230
|
+
const newest = entries.reverse()
|
|
231
|
+
return limit != null && limit > 0 ? newest.slice(0, limit) : newest
|
|
256
232
|
}
|
|
257
233
|
|
|
258
|
-
|
|
259
|
-
return limit != null && limit > 0 ? newest.slice(0, limit) : newest
|
|
234
|
+
return { commitEventOf, foldPending, failPending, revisionsFromLog }
|
|
260
235
|
}
|
|
236
|
+
|
|
237
|
+
export const foldHelper = createFoldHelper()
|
|
238
|
+
|
|
239
|
+
/** @deprecated compat:factory-refactor — use `foldHelper.commitEventOf(…)` */
|
|
240
|
+
export const commitEventOf = (
|
|
241
|
+
transition: Transition, state: CommitState, at: string, extra?: { error?: string, record?: Workcard | null }
|
|
242
|
+
): CommitEvent => foldHelper.commitEventOf(transition, state, at, extra)
|
|
243
|
+
|
|
244
|
+
/** @deprecated compat:factory-refactor — use `foldHelper.foldPending(…)` */
|
|
245
|
+
export const foldPending = async (
|
|
246
|
+
store: PlanningStore, cardId: string, entityId: string, opts: FoldOptions = {}
|
|
247
|
+
): Promise<FoldResult> => await foldHelper.foldPending(store, cardId, entityId, opts)
|
|
248
|
+
|
|
249
|
+
/** @deprecated compat:factory-refactor — use `foldHelper.failPending(…)` */
|
|
250
|
+
export const failPending = async (
|
|
251
|
+
store: PlanningStore, cardId: string, entityId: string, reason: string,
|
|
252
|
+
opts: Pick<FoldOptions, 'publish' | 'now'> = {}
|
|
253
|
+
): Promise<number> => await foldHelper.failPending(store, cardId, entityId, reason, opts)
|
|
254
|
+
|
|
255
|
+
/** @deprecated compat:factory-refactor — use `foldHelper.revisionsFromLog(…)` */
|
|
256
|
+
export const revisionsFromLog = (log: Transition[], limit?: number): SpecificationRevision[] =>
|
|
257
|
+
foldHelper.revisionsFromLog(log, limit)
|
package/src/store/index.ts
CHANGED
package/src/store/memory.ts
CHANGED
|
Binary file
|
package/src/store/types.local.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -146,7 +146,7 @@ export interface PluginRegistry {
|
|
|
146
146
|
version: () => number
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
export
|
|
149
|
+
export interface PlanningServiceApi extends Pick<PlanningService, 'use' | 'plugins' | 'schemas' | 'store' | 'for' | 'committed'> {}
|
|
150
150
|
|
|
151
151
|
/** What the facade and the executor reach the service through. */
|
|
152
152
|
export interface PlanningRuntime {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Workcard } from '@owlmeans/planning'
|
|
2
|
+
import type { PlanningAccess, PlanningAccessGrants } from '../../types.js'
|
|
3
|
+
|
|
4
|
+
/** The write rules of one resolved {@link PlanningAccess} — no access at all gates nothing. */
|
|
5
|
+
export interface PlanningAccessModel {
|
|
6
|
+
readonly record: PlanningAccess | undefined
|
|
7
|
+
/**
|
|
8
|
+
* Refuse a write the access does not grant. No access, or access without `grants`, gates nothing;
|
|
9
|
+
* a `grants` object refuses every flag it leaves out. `true` grants everything; a list grants the
|
|
10
|
+
* project ids it names — `target` is the project the write is about (`undefined` for the
|
|
11
|
+
* organization's root, which only `true` reaches).
|
|
12
|
+
*
|
|
13
|
+
* @throws {PlanningForbidden}
|
|
14
|
+
*/
|
|
15
|
+
assertGranted: (grant: keyof PlanningAccessGrants, target?: string) => void
|
|
16
|
+
/**
|
|
17
|
+
* A card the access's `writes` admits — the rule a narrowed read admits, over `writes`: a project
|
|
18
|
+
* it names, or a card whose `parents` name one. A specification is admitted through its parent card
|
|
19
|
+
* by the caller. No `writes` admits everything.
|
|
20
|
+
*/
|
|
21
|
+
writableIn: (card: Pick<Workcard, 'id' | 'parents'>) => boolean
|
|
22
|
+
/**
|
|
23
|
+
* Refuse a write into a project the access's `writes` leaves out. No `writes` refuses nothing;
|
|
24
|
+
* `target` is the project the write is in (`undefined` for the organization's root, which a set
|
|
25
|
+
* `writes` never admits).
|
|
26
|
+
*
|
|
27
|
+
* @throws {PlanningForbidden}
|
|
28
|
+
*/
|
|
29
|
+
assertWrites: (target?: string) => void
|
|
30
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { PlanningForbidden, type Workcard } from '@owlmeans/planning'
|
|
2
|
+
import type { PlanningAccess, PlanningAccessGrants } from '../types.js'
|
|
3
|
+
import type { PlanningAccessModel } from './access/types.js'
|
|
4
|
+
|
|
5
|
+
export const makePlanningAccessModel = (access: PlanningAccess | undefined): PlanningAccessModel => {
|
|
6
|
+
const assertGranted = (grant: keyof PlanningAccessGrants, target?: string): void => {
|
|
7
|
+
if (access?.grants == null) {
|
|
8
|
+
return
|
|
9
|
+
}
|
|
10
|
+
const granted = access.grants[grant]
|
|
11
|
+
if (granted === true || (Array.isArray(granted) && target != null && granted.includes(target))) {
|
|
12
|
+
return
|
|
13
|
+
}
|
|
14
|
+
throw new PlanningForbidden(`${grant}:${target ?? 'root'}`)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const writableIn = (card: Pick<Workcard, 'id' | 'parents'>): boolean => {
|
|
18
|
+
const writes = access?.writes
|
|
19
|
+
if (writes == null) {
|
|
20
|
+
return true
|
|
21
|
+
}
|
|
22
|
+
return (card.id != null && writes.includes(card.id)) || card.parents.some(parent => writes.includes(parent))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const assertWrites = (target?: string): void => {
|
|
26
|
+
if (access?.writes == null || (target != null && access.writes.includes(target))) {
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
throw new PlanningForbidden(`writes:${target ?? 'root'}`)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return { record: access, assertGranted, writableIn, assertWrites }
|
|
33
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { WorkcardNotFound, Workcard } from '@owlmeans/planning'
|
|
2
|
+
|
|
3
|
+
/** What every handler answers with when a record is not the caller's: absence, never a refusal. */
|
|
4
|
+
export interface GuardHelper {
|
|
5
|
+
notFoundOf: (id: string) => WorkcardNotFound
|
|
6
|
+
/**
|
|
7
|
+
* A record of another entity is absent, not forbidden.
|
|
8
|
+
*
|
|
9
|
+
* @throws {WorkcardNotFound}
|
|
10
|
+
*/
|
|
11
|
+
assertScope: <T extends Pick<Workcard, 'entityId'>>(record: T | null | undefined, scope: { entityId: string }, id: string) => T
|
|
12
|
+
/**
|
|
13
|
+
* Run a handler body so that a scope mismatch leaves as `WorkcardNotFound` — telling "not yours"
|
|
14
|
+
* apart from "does not exist" is what turns an id space into an enumeration oracle.
|
|
15
|
+
*/
|
|
16
|
+
concealed: <T>(run: () => Promise<T>) => Promise<T>
|
|
17
|
+
/** Seconds as the wire sends them, clamped to `[0, max]`. */
|
|
18
|
+
clampSeconds: (value: unknown, max: number) => number
|
|
19
|
+
}
|