@owlmeans/planning 0.1.18-rc.2 → 0.1.18-rc.21
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 +69 -26
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/planning/SKILL.md +157 -45
- package/build/aliases/types.d.ts +49 -0
- package/build/aliases/types.d.ts.map +1 -0
- package/build/aliases/types.js +2 -0
- package/build/aliases/types.js.map +1 -0
- package/build/aliases.d.ts +4 -0
- package/build/aliases.d.ts.map +1 -0
- package/build/aliases.js +27 -0
- package/build/aliases.js.map +1 -0
- package/build/consts.d.ts +23 -34
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +26 -25
- package/build/consts.js.map +1 -1
- package/build/entrypoints/types.d.ts +96 -0
- package/build/entrypoints/types.d.ts.map +1 -0
- package/build/entrypoints/types.js +2 -0
- package/build/entrypoints/types.js.map +1 -0
- package/build/entrypoints.d.ts +5 -1
- package/build/entrypoints.d.ts.map +1 -1
- package/build/entrypoints.js +17 -6
- package/build/entrypoints.js.map +1 -1
- package/build/errors.d.ts +41 -0
- package/build/errors.d.ts.map +1 -1
- package/build/errors.js +61 -0
- package/build/errors.js.map +1 -1
- package/build/helpers/apply/consts.local.d.ts +7 -0
- package/build/helpers/apply/consts.local.d.ts.map +1 -0
- package/build/helpers/apply/consts.local.js +9 -0
- package/build/helpers/apply/consts.local.js.map +1 -0
- package/build/helpers/apply/types.d.ts +32 -0
- package/build/helpers/apply/types.d.ts.map +1 -0
- package/build/helpers/apply/types.js +2 -0
- package/build/helpers/apply/types.js.map +1 -0
- package/build/helpers/apply.d.ts +5 -23
- package/build/helpers/apply.d.ts.map +1 -1
- package/build/helpers/apply.js +121 -143
- package/build/helpers/apply.js.map +1 -1
- package/build/helpers/card/types.d.ts +20 -0
- package/build/helpers/card/types.d.ts.map +1 -0
- package/build/helpers/card/types.js +2 -0
- package/build/helpers/card/types.js.map +1 -0
- package/build/helpers/card.d.ts +11 -7
- package/build/helpers/card.d.ts.map +1 -1
- package/build/helpers/card.js +41 -28
- package/build/helpers/card.js.map +1 -1
- package/build/helpers/changes/consts.local.d.ts +13 -0
- package/build/helpers/changes/consts.local.d.ts.map +1 -0
- package/build/helpers/changes/consts.local.js +13 -0
- package/build/helpers/changes/consts.local.js.map +1 -0
- package/build/helpers/changes/types.d.ts +58 -0
- package/build/helpers/changes/types.d.ts.map +1 -0
- package/build/helpers/changes/types.js +2 -0
- package/build/helpers/changes/types.js.map +1 -0
- package/build/helpers/changes.d.ts +11 -42
- package/build/helpers/changes.d.ts.map +1 -1
- package/build/helpers/changes.js +234 -251
- package/build/helpers/changes.js.map +1 -1
- package/build/helpers/code/types.d.ts +27 -0
- package/build/helpers/code/types.d.ts.map +1 -0
- package/build/helpers/code/types.js +2 -0
- package/build/helpers/code/types.js.map +1 -0
- package/build/helpers/code.d.ts +6 -17
- package/build/helpers/code.d.ts.map +1 -1
- package/build/helpers/code.js +39 -40
- package/build/helpers/code.js.map +1 -1
- package/build/helpers/index.d.ts +12 -0
- package/build/helpers/index.d.ts.map +1 -1
- package/build/helpers/index.js +2 -0
- package/build/helpers/index.js.map +1 -1
- package/build/helpers/query/types.d.ts +23 -0
- package/build/helpers/query/types.d.ts.map +1 -0
- package/build/helpers/query/types.js +2 -0
- package/build/helpers/query/types.js.map +1 -0
- package/build/helpers/query.d.ts +25 -17
- package/build/helpers/query.d.ts.map +1 -1
- package/build/helpers/query.js +108 -280
- package/build/helpers/query.js.map +1 -1
- package/build/helpers/scoped/types.d.ts +59 -0
- package/build/helpers/scoped/types.d.ts.map +1 -0
- package/build/helpers/scoped/types.js +2 -0
- package/build/helpers/scoped/types.js.map +1 -0
- package/build/helpers/scoped.d.ts +24 -0
- package/build/helpers/scoped.d.ts.map +1 -0
- package/build/helpers/scoped.js +197 -0
- package/build/helpers/scoped.js.map +1 -0
- package/build/helpers/specification/types.d.ts +24 -0
- package/build/helpers/specification/types.d.ts.map +1 -0
- package/build/helpers/specification/types.js +2 -0
- package/build/helpers/specification/types.js.map +1 -0
- package/build/helpers/specification.d.ts +9 -13
- package/build/helpers/specification.d.ts.map +1 -1
- package/build/helpers/specification.js +47 -44
- package/build/helpers/specification.js.map +1 -1
- package/build/helpers/status/types.d.ts +48 -0
- package/build/helpers/status/types.d.ts.map +1 -0
- package/build/helpers/status/types.js +2 -0
- package/build/helpers/status/types.js.map +1 -0
- package/build/helpers/status.d.ts +16 -29
- package/build/helpers/status.d.ts.map +1 -1
- package/build/helpers/status.js +77 -73
- package/build/helpers/status.js.map +1 -1
- package/build/helpers/types.local.d.ts +17 -0
- package/build/helpers/types.local.d.ts.map +1 -0
- package/build/helpers/types.local.js +2 -0
- package/build/helpers/types.local.js.map +1 -0
- package/build/helpers/validate/types.d.ts +30 -0
- package/build/helpers/validate/types.d.ts.map +1 -0
- package/build/helpers/validate/types.js +2 -0
- package/build/helpers/validate/types.js.map +1 -0
- package/build/helpers/validate.d.ts +8 -19
- package/build/helpers/validate.d.ts.map +1 -1
- package/build/helpers/validate.js +74 -79
- package/build/helpers/validate.js.map +1 -1
- package/build/helpers/wire/types.d.ts +28 -0
- package/build/helpers/wire/types.d.ts.map +1 -0
- package/build/helpers/wire/types.js +2 -0
- package/build/helpers/wire/types.js.map +1 -0
- package/build/helpers/wire.d.ts +4 -0
- package/build/helpers/wire.d.ts.map +1 -0
- package/build/helpers/wire.js +215 -0
- package/build/helpers/wire.js.map +1 -0
- package/build/i18n/be.json +5 -0
- package/build/i18n/de.json +5 -0
- package/build/i18n/en.json +5 -0
- package/build/i18n/es.json +5 -0
- package/build/i18n/fr.json +56 -0
- package/build/i18n/pl.json +5 -0
- package/build/i18n/ru.json +5 -0
- package/build/i18n/uk.json +5 -0
- package/build/i18n.js +5 -4
- package/build/i18n.js.map +1 -1
- package/build/index.d.ts +4 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -0
- package/build/index.js.map +1 -1
- package/build/models/index.d.ts +2 -3
- package/build/models/index.d.ts.map +1 -1
- package/build/models/index.js +1 -15
- package/build/models/index.js.map +1 -1
- package/build/models/model.d.ts +5 -0
- package/build/models/model.d.ts.map +1 -0
- package/build/models/model.js +16 -0
- package/build/models/model.js.map +1 -0
- package/build/models/project.d.ts +2 -1
- package/build/models/project.d.ts.map +1 -1
- package/build/models/project.js.map +1 -1
- package/build/models/specification.d.ts +2 -1
- package/build/models/specification.d.ts.map +1 -1
- package/build/models/specification.js.map +1 -1
- package/build/models/types.d.ts +69 -0
- package/build/models/types.d.ts.map +1 -0
- package/build/models/types.js +2 -0
- package/build/models/types.js.map +1 -0
- package/build/models/workcard.d.ts +2 -1
- package/build/models/workcard.d.ts.map +1 -1
- package/build/models/workcard.js +12 -12
- package/build/models/workcard.js.map +1 -1
- package/build/schemas.d.ts +10 -2
- package/build/schemas.d.ts.map +1 -1
- package/build/schemas.js +87 -1
- package/build/schemas.js.map +1 -1
- package/build/types.d.ts +199 -148
- package/build/types.d.ts.map +1 -1
- package/build/utils.d.ts +6 -0
- package/build/utils.d.ts.map +1 -0
- package/build/utils.js +6 -0
- package/build/utils.js.map +1 -0
- package/package.json +11 -11
- package/src/aliases/types.ts +38 -0
- package/src/aliases.ts +31 -0
- package/src/consts.ts +26 -25
- package/src/entrypoints/types.ts +68 -0
- package/src/entrypoints.ts +34 -14
- package/src/errors.ts +71 -0
- package/src/helpers/apply/consts.local.ts +10 -0
- package/src/helpers/apply/types.ts +32 -0
- package/src/helpers/apply.ts +135 -152
- package/src/helpers/card/types.ts +20 -0
- package/src/helpers/card.ts +59 -33
- package/src/helpers/changes/consts.local.ts +17 -0
- package/src/helpers/changes/types.ts +66 -0
- package/src/helpers/changes.ts +267 -280
- package/src/helpers/code/types.ts +31 -0
- package/src/helpers/code.ts +52 -48
- package/src/helpers/index.ts +13 -0
- package/src/helpers/query/types.ts +26 -0
- package/src/helpers/query.ts +144 -314
- package/src/helpers/scoped/types.ts +64 -0
- package/src/helpers/scoped.ts +257 -0
- package/src/helpers/specification/types.ts +26 -0
- package/src/helpers/specification.ts +69 -51
- package/src/helpers/status/types.ts +46 -0
- package/src/helpers/status.ts +117 -93
- package/src/helpers/types.local.ts +18 -0
- package/src/helpers/validate/types.ts +30 -0
- package/src/helpers/validate.ts +84 -77
- package/src/helpers/wire/types.ts +31 -0
- package/src/helpers/wire.ts +251 -0
- package/src/i18n/be.json +5 -0
- package/src/i18n/de.json +5 -0
- package/src/i18n/en.json +5 -0
- package/src/i18n/es.json +5 -0
- package/src/i18n/fr.json +56 -0
- package/src/i18n/pl.json +5 -0
- package/src/i18n/ru.json +5 -0
- package/src/i18n/uk.json +5 -0
- package/src/i18n.ts +6 -4
- package/src/index.ts +4 -0
- package/src/models/index.ts +2 -17
- package/src/models/model.ts +18 -0
- package/src/models/project.ts +2 -1
- package/src/models/specification.ts +2 -1
- package/src/models/types.ts +70 -0
- package/src/models/workcard.ts +14 -16
- package/src/schemas.ts +98 -2
- package/src/types.ts +207 -126
- package/src/utils.ts +9 -0
- package/tests/apply.spec.ts +95 -31
- package/tests/code.spec.ts +9 -9
- package/tests/entrypoints.spec.ts +18 -2
- package/tests/error-status.spec.ts +6 -1
- package/tests/models.spec.ts +16 -15
- package/tests/query.spec.ts +13 -15
- package/tests/schemas.spec.ts +8 -8
- package/tests/scoped.spec.ts +154 -0
- package/tests/status.spec.ts +31 -22
package/src/helpers/status.ts
CHANGED
|
@@ -1,117 +1,141 @@
|
|
|
1
1
|
import { ANY_STATUS, INTRINSIC_ORDER, IntrinsicPolicy, IntrinsicStatus } from '../consts.js'
|
|
2
2
|
import { PlanningError, UnknownStatusFlow } from '../errors.js'
|
|
3
|
-
import type {
|
|
4
|
-
|
|
5
|
-
} from '
|
|
3
|
+
import type { AnyTypeSchema, StatusDefinition, StatusFlowSchema, StatusTransitionRule } from '../types.js'
|
|
4
|
+
import type { StatusHelper } from './status/types.js'
|
|
5
|
+
import type { FlowLookup } from './types.local.js'
|
|
6
|
+
|
|
7
|
+
export const createStatusHelper = (): StatusHelper => {
|
|
8
|
+
const primaryFlowOf = (type: AnyTypeSchema): string => {
|
|
9
|
+
const primary = type.flows[0]
|
|
10
|
+
if (primary == null) {
|
|
11
|
+
throw new UnknownStatusFlow(`${type.type}:primary`)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return primary
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const flowIdsOf = (type: AnyTypeSchema): string[] => [...type.flows]
|
|
18
|
+
|
|
19
|
+
const statusDefinitionOf = (flow: StatusFlowSchema, status: string): StatusDefinition | undefined =>
|
|
20
|
+
flow.statuses.find(definition => definition.key === status)
|
|
21
|
+
|
|
22
|
+
const intrinsicOf = (flow: StatusFlowSchema, status: string): IntrinsicStatus | undefined =>
|
|
23
|
+
statusDefinitionOf(flow, status)?.intrinsic
|
|
24
|
+
|
|
25
|
+
const initialStatusOf = (flow: StatusFlowSchema): string => {
|
|
26
|
+
const status = flow.statuses.find(definition => definition.initial === true) ?? flow.statuses[0]
|
|
27
|
+
if (status == null) {
|
|
28
|
+
throw new PlanningError(`flow-empty:${flow.id}`)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return status.key
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const isTerminal = (flow: StatusFlowSchema, status: string): boolean =>
|
|
35
|
+
statusDefinitionOf(flow, status)?.terminal === true
|
|
36
|
+
|
|
37
|
+
const fromMatches = (rule: StatusTransitionRule, status: string): boolean =>
|
|
38
|
+
rule.from === ANY_STATUS || (Array.isArray(rule.from) && rule.from.includes(status))
|
|
39
|
+
|
|
40
|
+
const ruleOf = (
|
|
41
|
+
flow: StatusFlowSchema, transition: string, from: string
|
|
42
|
+
): StatusTransitionRule | undefined => {
|
|
43
|
+
const named = flow.transitions.filter(rule => rule.name === transition)
|
|
44
|
+
|
|
45
|
+
return named.find(rule => rule.from !== ANY_STATUS && fromMatches(rule, from))
|
|
46
|
+
?? named.find(rule => rule.from === ANY_STATUS)
|
|
47
|
+
?? (statusDefinitionOf(flow, from) == null ? named[0] : undefined)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const canTransit = (flow: StatusFlowSchema, transition: string, from: string): boolean =>
|
|
51
|
+
ruleOf(flow, transition, from) != null
|
|
52
|
+
|
|
53
|
+
const transitionsFrom = (
|
|
54
|
+
flow: StatusFlowSchema, status: string, opts?: { explicit?: boolean }
|
|
55
|
+
): StatusTransitionRule[] => {
|
|
56
|
+
const names = [...new Set(flow.transitions.map(rule => rule.name))]
|
|
57
|
+
|
|
58
|
+
return names.map(name => ruleOf(flow, name, status))
|
|
59
|
+
.filter((rule): rule is StatusTransitionRule => rule != null)
|
|
60
|
+
.filter(rule => opts?.explicit !== true || rule.explicit === true)
|
|
61
|
+
}
|
|
6
62
|
|
|
7
|
-
|
|
63
|
+
const initialFlowsOf = (
|
|
64
|
+
type: AnyTypeSchema, registry: FlowLookup, status?: string
|
|
65
|
+
): Record<string, string> => {
|
|
66
|
+
const primary = primaryFlowOf(type)
|
|
8
67
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (primary == null) {
|
|
13
|
-
throw new UnknownStatusFlow(`${type.type}:primary`)
|
|
68
|
+
return Object.fromEntries(type.flows.map(id => [
|
|
69
|
+
id, id === primary && status != null ? status : initialStatusOf(registry.flow(id)),
|
|
70
|
+
]))
|
|
14
71
|
}
|
|
15
72
|
|
|
16
|
-
|
|
73
|
+
const resolveIntrinsic = (
|
|
74
|
+
type: AnyTypeSchema, flows: Record<string, string>, registry: FlowLookup
|
|
75
|
+
): IntrinsicStatus => {
|
|
76
|
+
const read = (id: string): IntrinsicStatus => {
|
|
77
|
+
const status = flows[id]
|
|
78
|
+
return status == null
|
|
79
|
+
? IntrinsicStatus.Planned
|
|
80
|
+
: intrinsicOf(registry.flow(id), status) ?? IntrinsicStatus.Planned
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (type.intrinsic !== IntrinsicPolicy.All) {
|
|
84
|
+
return read(primaryFlowOf(type))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return type.flows.map(read).reduce<IntrinsicStatus>(
|
|
88
|
+
(least, intrinsic) => INTRINSIC_ORDER[intrinsic] < INTRINSIC_ORDER[least] ? intrinsic : least,
|
|
89
|
+
IntrinsicStatus.Closed
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
primaryFlowOf, flowIdsOf, statusDefinitionOf, intrinsicOf, initialStatusOf, isTerminal, ruleOf, canTransit,
|
|
95
|
+
transitionsFrom, initialFlowsOf, resolveIntrinsic,
|
|
96
|
+
}
|
|
17
97
|
}
|
|
18
98
|
|
|
19
|
-
export const
|
|
99
|
+
export const statusHelper = createStatusHelper()
|
|
100
|
+
|
|
101
|
+
/** @deprecated compat:factory-refactor — use `statusHelper.primaryFlowOf(…)` */
|
|
102
|
+
export const primaryFlowOf = (type: AnyTypeSchema): string => statusHelper.primaryFlowOf(type)
|
|
20
103
|
|
|
104
|
+
/** @deprecated compat:factory-refactor — use `statusHelper.flowIdsOf(…)` */
|
|
105
|
+
export const flowIdsOf = (type: AnyTypeSchema): string[] => statusHelper.flowIdsOf(type)
|
|
106
|
+
|
|
107
|
+
/** @deprecated compat:factory-refactor — use `statusHelper.statusDefinitionOf(…)` */
|
|
21
108
|
export const statusDefinitionOf = (flow: StatusFlowSchema, status: string): StatusDefinition | undefined =>
|
|
22
|
-
|
|
109
|
+
statusHelper.statusDefinitionOf(flow, status)
|
|
23
110
|
|
|
24
|
-
/**
|
|
111
|
+
/** @deprecated compat:factory-refactor — use `statusHelper.intrinsicOf(…)` */
|
|
25
112
|
export const intrinsicOf = (flow: StatusFlowSchema, status: string): IntrinsicStatus | undefined =>
|
|
26
|
-
|
|
113
|
+
statusHelper.intrinsicOf(flow, status)
|
|
27
114
|
|
|
28
|
-
/**
|
|
29
|
-
export const initialStatusOf = (flow: StatusFlowSchema): string =>
|
|
30
|
-
const status = flow.statuses.find(definition => definition.initial === true) ?? flow.statuses[0]
|
|
31
|
-
if (status == null) {
|
|
32
|
-
throw new PlanningError(`flow-empty:${flow.id}`)
|
|
33
|
-
}
|
|
115
|
+
/** @deprecated compat:factory-refactor — use `statusHelper.initialStatusOf(…)` */
|
|
116
|
+
export const initialStatusOf = (flow: StatusFlowSchema): string => statusHelper.initialStatusOf(flow)
|
|
34
117
|
|
|
35
|
-
|
|
36
|
-
|
|
118
|
+
/** @deprecated compat:factory-refactor — use `statusHelper.isTerminal(…)` */
|
|
119
|
+
export const isTerminal = (flow: StatusFlowSchema, status: string): boolean => statusHelper.isTerminal(flow, status)
|
|
37
120
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const fromMatches = (rule: StatusTransitionRule, status: string): boolean =>
|
|
42
|
-
rule.from === ANY_STATUS || (Array.isArray(rule.from) && rule.from.includes(status))
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* The rule a named transition follows from a status.
|
|
46
|
-
*
|
|
47
|
-
* A name may be declared several times with different `from` sets; the rule naming the status
|
|
48
|
-
* explicitly wins, and a `'*'` rule of that name answers only when none does.
|
|
49
|
-
*/
|
|
50
|
-
export const ruleOf = (
|
|
51
|
-
flow: StatusFlowSchema, transition: string, from: string
|
|
52
|
-
): StatusTransitionRule | undefined => {
|
|
53
|
-
const named = flow.transitions.filter(rule => rule.name === transition)
|
|
54
|
-
|
|
55
|
-
return named.find(rule => rule.from !== ANY_STATUS && fromMatches(rule, from))
|
|
56
|
-
?? named.find(rule => rule.from === ANY_STATUS)
|
|
57
|
-
}
|
|
121
|
+
/** @deprecated compat:factory-refactor — use `statusHelper.ruleOf(…)` */
|
|
122
|
+
export const ruleOf = (flow: StatusFlowSchema, transition: string, from: string): StatusTransitionRule | undefined =>
|
|
123
|
+
statusHelper.ruleOf(flow, transition, from)
|
|
58
124
|
|
|
125
|
+
/** @deprecated compat:factory-refactor — use `statusHelper.canTransit(…)` */
|
|
59
126
|
export const canTransit = (flow: StatusFlowSchema, transition: string, from: string): boolean =>
|
|
60
|
-
|
|
127
|
+
statusHelper.canTransit(flow, transition, from)
|
|
61
128
|
|
|
62
|
-
/**
|
|
63
|
-
* Every transition available from a status, one rule per name (the one `ruleOf` would pick), in
|
|
64
|
-
* declaration order. `explicit: true` keeps only the rules offered to a person.
|
|
65
|
-
*/
|
|
129
|
+
/** @deprecated compat:factory-refactor — use `statusHelper.transitionsFrom(…)` */
|
|
66
130
|
export const transitionsFrom = (
|
|
67
131
|
flow: StatusFlowSchema, status: string, opts?: { explicit?: boolean }
|
|
68
|
-
): StatusTransitionRule[] =>
|
|
69
|
-
const names = [...new Set(flow.transitions.map(rule => rule.name))]
|
|
132
|
+
): StatusTransitionRule[] => statusHelper.transitionsFrom(flow, status, opts)
|
|
70
133
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* A new card's status per flow: every flow's initial status, with the primary one overridden by
|
|
78
|
-
* `status` when the draft names one.
|
|
79
|
-
*
|
|
80
|
-
* @throws {UnknownStatusFlow}
|
|
81
|
-
*/
|
|
82
|
-
export const initialFlowsOf = (
|
|
83
|
-
type: AnyTypeSchema, registry: FlowLookup, status?: string
|
|
84
|
-
): Record<string, string> => {
|
|
85
|
-
const primary = primaryFlowOf(type)
|
|
86
|
-
|
|
87
|
-
return Object.fromEntries(type.flows.map(id => [
|
|
88
|
-
id, id === primary && status != null ? status : initialStatusOf(registry.flow(id)),
|
|
89
|
-
]))
|
|
90
|
-
}
|
|
134
|
+
/** @deprecated compat:factory-refactor — use `statusHelper.initialFlowsOf(…)` */
|
|
135
|
+
export const initialFlowsOf = (type: AnyTypeSchema, registry: FlowLookup, status?: string): Record<string, string> =>
|
|
136
|
+
statusHelper.initialFlowsOf(type, registry, status)
|
|
91
137
|
|
|
92
|
-
/**
|
|
93
|
-
* The card's intrinsic state over its flows. {@link IntrinsicPolicy.Primary} reads the primary
|
|
94
|
-
* flow; {@link IntrinsicPolicy.All} takes the least advanced flow. A status the flow does not
|
|
95
|
-
* declare reads as planned.
|
|
96
|
-
*
|
|
97
|
-
* @throws {UnknownStatusFlow}
|
|
98
|
-
*/
|
|
138
|
+
/** @deprecated compat:factory-refactor — use `statusHelper.resolveIntrinsic(…)` */
|
|
99
139
|
export const resolveIntrinsic = (
|
|
100
140
|
type: AnyTypeSchema, flows: Record<string, string>, registry: FlowLookup
|
|
101
|
-
): IntrinsicStatus =>
|
|
102
|
-
const read = (id: string): IntrinsicStatus => {
|
|
103
|
-
const status = flows[id]
|
|
104
|
-
return status == null
|
|
105
|
-
? IntrinsicStatus.Planned
|
|
106
|
-
: intrinsicOf(registry.flow(id), status) ?? IntrinsicStatus.Planned
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (type.intrinsic !== IntrinsicPolicy.All) {
|
|
110
|
-
return read(primaryFlowOf(type))
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return type.flows.map(read).reduce<IntrinsicStatus>(
|
|
114
|
-
(least, intrinsic) => INTRINSIC_ORDER[intrinsic] < INTRINSIC_ORDER[least] ? intrinsic : least,
|
|
115
|
-
IntrinsicStatus.Closed
|
|
116
|
-
)
|
|
117
|
-
}
|
|
141
|
+
): IntrinsicStatus => statusHelper.resolveIntrinsic(type, flows, registry)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { PlanningSchemaRegistry, PlanningScope } from '../types.js'
|
|
2
|
+
import type { SchemaOrigin } from '../consts.js'
|
|
3
|
+
|
|
4
|
+
export interface FlowLookup extends Pick<PlanningSchemaRegistry, 'flow'> {}
|
|
5
|
+
|
|
6
|
+
export interface Scope extends Pick<PlanningScope, 'entityId'> {}
|
|
7
|
+
|
|
8
|
+
export interface Candidate<T> {
|
|
9
|
+
origin: SchemaOrigin
|
|
10
|
+
definition: T
|
|
11
|
+
retired: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface Resolved<T> {
|
|
15
|
+
definitions: T[]
|
|
16
|
+
origins: Record<string, SchemaOrigin>
|
|
17
|
+
retired: string[]
|
|
18
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ErrorObject } from 'ajv'
|
|
2
|
+
import type { PlanningSchemaRegistry, SpecificationSlot, Workcard } from '../../types.js'
|
|
3
|
+
|
|
4
|
+
/** A record's shape, its type's `fields` and a document's body, checked against their schemas. */
|
|
5
|
+
export interface ValidateHelper {
|
|
6
|
+
/** One line per ajv error: `/path message`. */
|
|
7
|
+
ajvErrorText: (errors?: ErrorObject[] | null) => string
|
|
8
|
+
/** Keys holding `.` or `$` at any depth — a document store cannot hold them. */
|
|
9
|
+
invalidFieldKeys: (fields: unknown, path?: string) => string[]
|
|
10
|
+
/**
|
|
11
|
+
* Check `fields` against the type's schema, and its keys against the store rule.
|
|
12
|
+
*
|
|
13
|
+
* @throws {FieldsInvalid}
|
|
14
|
+
*/
|
|
15
|
+
validateFields: (registry: Pick<PlanningSchemaRegistry, 'validator'>, type: string, fields: Record<string, unknown>) => void
|
|
16
|
+
/**
|
|
17
|
+
* Check a whole record: its shape by kind, then its `fields` by type.
|
|
18
|
+
*
|
|
19
|
+
* @throws {PlanningError} `malformed:card:…` for a shape fault
|
|
20
|
+
* @throws {FieldsInvalid}
|
|
21
|
+
*/
|
|
22
|
+
validateCard: (card: Workcard, registry: Pick<PlanningSchemaRegistry, 'validator'>) => void
|
|
23
|
+
/**
|
|
24
|
+
* Check a document body against its slot: a JSON slot must parse, and match the slot's schema when
|
|
25
|
+
* it declares one.
|
|
26
|
+
*
|
|
27
|
+
* @throws {FieldsInvalid}
|
|
28
|
+
*/
|
|
29
|
+
validateSpecificationBody: (slot: SpecificationSlot, body?: string | null) => void
|
|
30
|
+
}
|
package/src/helpers/validate.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { Ajv } from 'ajv'
|
|
2
2
|
import type { ErrorObject, ValidateFunction } from 'ajv'
|
|
3
3
|
import formatsPlugin from 'ajv-formats'
|
|
4
|
+
import { memoHelper } from '@owlmeans/context'
|
|
4
5
|
import { SpecificationFormat, WorkcardKind } from '../consts.js'
|
|
5
6
|
import { FieldsInvalid, PlanningError } from '../errors.js'
|
|
6
7
|
import { AnyWorkcardSchema, SpecificationSchema, WorkcardSchema } from '../schemas.js'
|
|
7
8
|
import type { PlanningSchemaRegistry, SpecificationSlot, Workcard } from '../types.js'
|
|
9
|
+
import type { ValidateHelper } from './validate/types.js'
|
|
8
10
|
|
|
9
11
|
/** The ajv every planning validation compiles with: lenient about unknown keywords, all errors, formats. */
|
|
10
12
|
export const makeAjv = (): Ajv => {
|
|
@@ -14,94 +16,99 @@ export const makeAjv = (): Ajv => {
|
|
|
14
16
|
return ajv
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
const ajvOf = (
|
|
19
|
+
export const createValidateHelper = (): ValidateHelper => {
|
|
20
|
+
const ajvOf = memoHelper.once(makeAjv)
|
|
19
21
|
|
|
20
|
-
const compiled = new Map<object, ValidateFunction>()
|
|
21
|
-
const compile = (schema: object): ValidateFunction => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
const compiled = new Map<object, ValidateFunction>()
|
|
23
|
+
const compile = (schema: object): ValidateFunction => {
|
|
24
|
+
let validate = compiled.get(schema)
|
|
25
|
+
if (validate == null) {
|
|
26
|
+
validate = ajvOf().compile(schema)
|
|
27
|
+
compiled.set(schema, validate)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return validate
|
|
26
31
|
}
|
|
27
32
|
|
|
28
|
-
|
|
29
|
-
}
|
|
33
|
+
const ajvErrorText = (errors?: ErrorObject[] | null): string =>
|
|
34
|
+
(errors ?? []).map(error => `${error.instancePath === '' ? '/' : error.instancePath} ${error.message ?? 'invalid'}`)
|
|
35
|
+
.join('; ')
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
const invalidFieldKeys = (fields: unknown, path: string = ''): string[] => {
|
|
38
|
+
if (fields == null || typeof fields !== 'object') {
|
|
39
|
+
return []
|
|
40
|
+
}
|
|
41
|
+
if (Array.isArray(fields)) {
|
|
42
|
+
return fields.flatMap((entry, index) => invalidFieldKeys(entry, `${path}${index}.`))
|
|
43
|
+
}
|
|
35
44
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
if (Array.isArray(fields)) {
|
|
42
|
-
return fields.flatMap((entry, index) => invalidFieldKeys(entry, `${path}${index}.`))
|
|
45
|
+
return Object.entries(fields).flatMap(([key, value]) => [
|
|
46
|
+
...(key.includes('.') || key.includes('$') ? [`${path}${key}`] : []),
|
|
47
|
+
...invalidFieldKeys(value, `${path}${key}.`),
|
|
48
|
+
])
|
|
43
49
|
}
|
|
44
50
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
export const validateFields = (
|
|
57
|
-
registry: Pick<PlanningSchemaRegistry, 'validator'>, type: string, fields: Record<string, unknown>
|
|
58
|
-
): void => {
|
|
59
|
-
const keys = invalidFieldKeys(fields)
|
|
60
|
-
if (keys.length > 0) {
|
|
61
|
-
throw new FieldsInvalid(`keys:${keys.join(',')}`)
|
|
62
|
-
}
|
|
63
|
-
const validate = registry.validator(type)
|
|
64
|
-
if (!validate(fields)) {
|
|
65
|
-
throw new FieldsInvalid(`${type}:${ajvErrorText(validate.errors)}`)
|
|
51
|
+
const validateFields = (
|
|
52
|
+
registry: Pick<PlanningSchemaRegistry, 'validator'>, type: string, fields: Record<string, unknown>
|
|
53
|
+
): void => {
|
|
54
|
+
const keys = invalidFieldKeys(fields)
|
|
55
|
+
if (keys.length > 0) {
|
|
56
|
+
throw new FieldsInvalid(`keys:${keys.join(',')}`)
|
|
57
|
+
}
|
|
58
|
+
const validate = registry.validator(type)
|
|
59
|
+
if (!validate(fields)) {
|
|
60
|
+
throw new FieldsInvalid(`${type}:${ajvErrorText(validate.errors)}`)
|
|
61
|
+
}
|
|
66
62
|
}
|
|
67
|
-
}
|
|
68
63
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
? SpecificationSchema
|
|
78
|
-
: card.kind === WorkcardKind.Card || card.kind === WorkcardKind.Project ? WorkcardSchema : AnyWorkcardSchema)
|
|
79
|
-
if (!validate(card)) {
|
|
80
|
-
throw new PlanningError(`malformed:card:${ajvErrorText(validate.errors)}`)
|
|
64
|
+
const validateCard = (card: Workcard, registry: Pick<PlanningSchemaRegistry, 'validator'>): void => {
|
|
65
|
+
const validate = compile(card.kind === WorkcardKind.Specification
|
|
66
|
+
? SpecificationSchema
|
|
67
|
+
: card.kind === WorkcardKind.Card || card.kind === WorkcardKind.Project ? WorkcardSchema : AnyWorkcardSchema)
|
|
68
|
+
if (!validate(card)) {
|
|
69
|
+
throw new PlanningError(`malformed:card:${ajvErrorText(validate.errors)}`)
|
|
70
|
+
}
|
|
71
|
+
validateFields(registry, card.type, card.fields)
|
|
81
72
|
}
|
|
82
|
-
validateFields(registry, card.type, card.fields)
|
|
83
|
-
}
|
|
84
73
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
if (slot.schema != null && typeof slot.schema === 'object') {
|
|
102
|
-
const validate = compile(slot.schema)
|
|
103
|
-
if (!validate(parsed)) {
|
|
104
|
-
throw new FieldsInvalid(`body:${slot.category}:${ajvErrorText(validate.errors)}`)
|
|
74
|
+
const validateSpecificationBody = (slot: SpecificationSlot, body?: string | null): void => {
|
|
75
|
+
if (body == null || slot.format !== SpecificationFormat.Json) {
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
let parsed: unknown
|
|
79
|
+
try {
|
|
80
|
+
parsed = JSON.parse(body)
|
|
81
|
+
} catch {
|
|
82
|
+
throw new FieldsInvalid(`body:${slot.category}:not-json`)
|
|
83
|
+
}
|
|
84
|
+
if (slot.schema != null && typeof slot.schema === 'object') {
|
|
85
|
+
const validate = compile(slot.schema)
|
|
86
|
+
if (!validate(parsed)) {
|
|
87
|
+
throw new FieldsInvalid(`body:${slot.category}:${ajvErrorText(validate.errors)}`)
|
|
88
|
+
}
|
|
105
89
|
}
|
|
106
90
|
}
|
|
91
|
+
|
|
92
|
+
return { ajvErrorText, invalidFieldKeys, validateFields, validateCard, validateSpecificationBody }
|
|
107
93
|
}
|
|
94
|
+
|
|
95
|
+
export const validateHelper = createValidateHelper()
|
|
96
|
+
|
|
97
|
+
/** @deprecated compat:factory-refactor — use `validateHelper.ajvErrorText(…)` */
|
|
98
|
+
export const ajvErrorText = (errors?: ErrorObject[] | null): string => validateHelper.ajvErrorText(errors)
|
|
99
|
+
|
|
100
|
+
/** @deprecated compat:factory-refactor — use `validateHelper.invalidFieldKeys(…)` */
|
|
101
|
+
export const invalidFieldKeys = (fields: unknown, path: string = ''): string[] => validateHelper.invalidFieldKeys(fields, path)
|
|
102
|
+
|
|
103
|
+
/** @deprecated compat:factory-refactor — use `validateHelper.validateFields(…)` */
|
|
104
|
+
export const validateFields = (
|
|
105
|
+
registry: Pick<PlanningSchemaRegistry, 'validator'>, type: string, fields: Record<string, unknown>
|
|
106
|
+
): void => validateHelper.validateFields(registry, type, fields)
|
|
107
|
+
|
|
108
|
+
/** @deprecated compat:factory-refactor — use `validateHelper.validateCard(…)` */
|
|
109
|
+
export const validateCard = (card: Workcard, registry: Pick<PlanningSchemaRegistry, 'validator'>): void =>
|
|
110
|
+
validateHelper.validateCard(card, registry)
|
|
111
|
+
|
|
112
|
+
/** @deprecated compat:factory-refactor — use `validateHelper.validateSpecificationBody(…)` */
|
|
113
|
+
export const validateSpecificationBody = (slot: SpecificationSlot, body?: string | null): void =>
|
|
114
|
+
validateHelper.validateSpecificationBody(slot, body)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Sort } from '@owlmeans/resource'
|
|
2
|
+
import type {
|
|
3
|
+
RelationshipQuery, RelationshipQueryWire, SpecificationQuery, SpecificationQueryWire, SummaryQuery, SummaryQueryWire,
|
|
4
|
+
TransitionQuery, TransitionQueryWire, WorkcardQuery, WorkcardQueryWire,
|
|
5
|
+
} from '../../types.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The query objects to and from the shape they travel in a URL (`*QueryWire`): a list is
|
|
9
|
+
* comma-joined (JSON when an element holds a comma), an object is JSON, a sort is `field,-field`.
|
|
10
|
+
* Every decoder also accepts the rich form.
|
|
11
|
+
*/
|
|
12
|
+
export interface WireHelper {
|
|
13
|
+
/** A list as one query value: comma-joined, or JSON when an element itself holds a comma. */
|
|
14
|
+
encodeList: (value?: string | readonly string[] | null) => string | undefined
|
|
15
|
+
/** Every entry of a list query value, whatever shape the transport left it in. */
|
|
16
|
+
decodeList: (value: unknown, key?: string) => string[] | undefined
|
|
17
|
+
/** `[{ field: 'order' }, { field: 'updatedAt', order: 'desc' }]` ⇄ `order,-updatedAt`. */
|
|
18
|
+
encodeSort: <T>(sort?: Sort<T>[] | null) => string | undefined
|
|
19
|
+
decodeSort: <T>(value: unknown) => Sort<T>[] | undefined
|
|
20
|
+
encodeWorkcardQuery: (query?: WorkcardQuery | null) => WorkcardQueryWire
|
|
21
|
+
/** @throws {PlanningError} `malformed:query:<key>` for a value that does not decode */
|
|
22
|
+
decodeWorkcardQuery: (wire?: WorkcardQueryWire | WorkcardQuery | null) => WorkcardQuery
|
|
23
|
+
encodeSummaryQuery: (query: SummaryQuery) => SummaryQueryWire
|
|
24
|
+
decodeSummaryQuery: (wire?: SummaryQueryWire | SummaryQuery | null) => SummaryQuery
|
|
25
|
+
encodeTransitionQuery: (query?: TransitionQuery | null) => TransitionQueryWire
|
|
26
|
+
decodeTransitionQuery: (wire?: TransitionQueryWire | TransitionQuery | null) => TransitionQuery
|
|
27
|
+
encodeSpecificationQuery: (query?: SpecificationQuery | null) => SpecificationQueryWire
|
|
28
|
+
decodeSpecificationQuery: (wire?: SpecificationQueryWire | SpecificationQuery | null) => SpecificationQuery
|
|
29
|
+
encodeRelationshipQuery: (query?: RelationshipQuery | null) => RelationshipQueryWire
|
|
30
|
+
decodeRelationshipQuery: (wire?: RelationshipQueryWire | RelationshipQuery | null) => RelationshipQuery
|
|
31
|
+
}
|