@owlmeans/planning 0.1.18-rc.8 → 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.
Files changed (295) hide show
  1. package/README.md +122 -29
  2. package/agent-meta/manifest.json +131 -4
  3. package/agent-meta/skills/planning/SKILL.md +78 -171
  4. package/agent-meta/skills/planning/parts/collaboration.md +67 -0
  5. package/agent-meta/skills/planning/parts/errors.md +51 -0
  6. package/agent-meta/skills/planning/parts/fold.md +69 -0
  7. package/agent-meta/skills/planning/parts/mounting.md +64 -0
  8. package/agent-meta/skills/planning/parts/types.md +141 -0
  9. package/build/aliases/types.d.ts +72 -0
  10. package/build/aliases/types.d.ts.map +1 -0
  11. package/build/aliases/types.js +2 -0
  12. package/build/aliases/types.js.map +1 -0
  13. package/build/aliases.d.ts +4 -0
  14. package/build/aliases.d.ts.map +1 -0
  15. package/build/aliases.js +28 -0
  16. package/build/aliases.js.map +1 -0
  17. package/build/consts.d.ts +43 -34
  18. package/build/consts.d.ts.map +1 -1
  19. package/build/consts.js +49 -25
  20. package/build/consts.js.map +1 -1
  21. package/build/entrypoints/types.d.ts +105 -0
  22. package/build/entrypoints/types.d.ts.map +1 -0
  23. package/build/entrypoints/types.js +2 -0
  24. package/build/entrypoints/types.js.map +1 -0
  25. package/build/entrypoints.d.ts +5 -1
  26. package/build/entrypoints.d.ts.map +1 -1
  27. package/build/entrypoints.js +24 -6
  28. package/build/entrypoints.js.map +1 -1
  29. package/build/errors.d.ts +41 -0
  30. package/build/errors.d.ts.map +1 -1
  31. package/build/errors.js +61 -0
  32. package/build/errors.js.map +1 -1
  33. package/build/helpers/app/consts.local.d.ts +5 -0
  34. package/build/helpers/app/consts.local.d.ts.map +1 -0
  35. package/build/helpers/app/consts.local.js +5 -0
  36. package/build/helpers/app/consts.local.js.map +1 -0
  37. package/build/helpers/app/types.d.ts +53 -0
  38. package/build/helpers/app/types.d.ts.map +1 -0
  39. package/build/helpers/app/types.js +2 -0
  40. package/build/helpers/app/types.js.map +1 -0
  41. package/build/helpers/app.d.ts +4 -0
  42. package/build/helpers/app.d.ts.map +1 -0
  43. package/build/helpers/app.js +139 -0
  44. package/build/helpers/app.js.map +1 -0
  45. package/build/helpers/apply/consts.local.d.ts +7 -0
  46. package/build/helpers/apply/consts.local.d.ts.map +1 -0
  47. package/build/helpers/apply/consts.local.js +9 -0
  48. package/build/helpers/apply/consts.local.js.map +1 -0
  49. package/build/helpers/apply/types.d.ts +32 -0
  50. package/build/helpers/apply/types.d.ts.map +1 -0
  51. package/build/helpers/apply/types.js +2 -0
  52. package/build/helpers/apply/types.js.map +1 -0
  53. package/build/helpers/apply.d.ts +5 -23
  54. package/build/helpers/apply.d.ts.map +1 -1
  55. package/build/helpers/apply.js +127 -144
  56. package/build/helpers/apply.js.map +1 -1
  57. package/build/helpers/card/types.d.ts +20 -0
  58. package/build/helpers/card/types.d.ts.map +1 -0
  59. package/build/helpers/card/types.js +2 -0
  60. package/build/helpers/card/types.js.map +1 -0
  61. package/build/helpers/card.d.ts +11 -7
  62. package/build/helpers/card.d.ts.map +1 -1
  63. package/build/helpers/card.js +41 -28
  64. package/build/helpers/card.js.map +1 -1
  65. package/build/helpers/changes/consts.local.d.ts +13 -0
  66. package/build/helpers/changes/consts.local.d.ts.map +1 -0
  67. package/build/helpers/changes/consts.local.js +13 -0
  68. package/build/helpers/changes/consts.local.js.map +1 -0
  69. package/build/helpers/changes/types.d.ts +58 -0
  70. package/build/helpers/changes/types.d.ts.map +1 -0
  71. package/build/helpers/changes/types.js +2 -0
  72. package/build/helpers/changes/types.js.map +1 -0
  73. package/build/helpers/changes.d.ts +11 -42
  74. package/build/helpers/changes.d.ts.map +1 -1
  75. package/build/helpers/changes.js +237 -251
  76. package/build/helpers/changes.js.map +1 -1
  77. package/build/helpers/code/types.d.ts +27 -0
  78. package/build/helpers/code/types.d.ts.map +1 -0
  79. package/build/helpers/code/types.js +2 -0
  80. package/build/helpers/code/types.js.map +1 -0
  81. package/build/helpers/code.d.ts +6 -17
  82. package/build/helpers/code.d.ts.map +1 -1
  83. package/build/helpers/code.js +39 -40
  84. package/build/helpers/code.js.map +1 -1
  85. package/build/helpers/index.d.ts +16 -0
  86. package/build/helpers/index.d.ts.map +1 -1
  87. package/build/helpers/index.js +4 -0
  88. package/build/helpers/index.js.map +1 -1
  89. package/build/helpers/query/types.d.ts +23 -0
  90. package/build/helpers/query/types.d.ts.map +1 -0
  91. package/build/helpers/query/types.js +2 -0
  92. package/build/helpers/query/types.js.map +1 -0
  93. package/build/helpers/query.d.ts +25 -17
  94. package/build/helpers/query.d.ts.map +1 -1
  95. package/build/helpers/query.js +108 -280
  96. package/build/helpers/query.js.map +1 -1
  97. package/build/helpers/reply/consts.d.ts +2 -0
  98. package/build/helpers/reply/consts.d.ts.map +1 -0
  99. package/build/helpers/reply/consts.js +2 -0
  100. package/build/helpers/reply/consts.js.map +1 -0
  101. package/build/helpers/reply/types.d.ts +10 -0
  102. package/build/helpers/reply/types.d.ts.map +1 -0
  103. package/build/helpers/reply/types.js +2 -0
  104. package/build/helpers/reply/types.js.map +1 -0
  105. package/build/helpers/reply.d.ts +5 -0
  106. package/build/helpers/reply.d.ts.map +1 -0
  107. package/build/helpers/reply.js +29 -0
  108. package/build/helpers/reply.js.map +1 -0
  109. package/build/helpers/scoped/types.d.ts +73 -0
  110. package/build/helpers/scoped/types.d.ts.map +1 -0
  111. package/build/helpers/scoped/types.js +2 -0
  112. package/build/helpers/scoped/types.js.map +1 -0
  113. package/build/helpers/scoped.d.ts +25 -0
  114. package/build/helpers/scoped.d.ts.map +1 -0
  115. package/build/helpers/scoped.js +223 -0
  116. package/build/helpers/scoped.js.map +1 -0
  117. package/build/helpers/specification/types.d.ts +24 -0
  118. package/build/helpers/specification/types.d.ts.map +1 -0
  119. package/build/helpers/specification/types.js +2 -0
  120. package/build/helpers/specification/types.js.map +1 -0
  121. package/build/helpers/specification.d.ts +9 -13
  122. package/build/helpers/specification.d.ts.map +1 -1
  123. package/build/helpers/specification.js +47 -44
  124. package/build/helpers/specification.js.map +1 -1
  125. package/build/helpers/status/types.d.ts +48 -0
  126. package/build/helpers/status/types.d.ts.map +1 -0
  127. package/build/helpers/status/types.js +2 -0
  128. package/build/helpers/status/types.js.map +1 -0
  129. package/build/helpers/status.d.ts +16 -29
  130. package/build/helpers/status.d.ts.map +1 -1
  131. package/build/helpers/status.js +77 -73
  132. package/build/helpers/status.js.map +1 -1
  133. package/build/helpers/types.local.d.ts +17 -0
  134. package/build/helpers/types.local.d.ts.map +1 -0
  135. package/build/helpers/types.local.js +2 -0
  136. package/build/helpers/types.local.js.map +1 -0
  137. package/build/helpers/validate/types.d.ts +31 -0
  138. package/build/helpers/validate/types.d.ts.map +1 -0
  139. package/build/helpers/validate/types.js +2 -0
  140. package/build/helpers/validate/types.js.map +1 -0
  141. package/build/helpers/validate.d.ts +8 -19
  142. package/build/helpers/validate.d.ts.map +1 -1
  143. package/build/helpers/validate.js +79 -79
  144. package/build/helpers/validate.js.map +1 -1
  145. package/build/helpers/wire/types.d.ts +28 -0
  146. package/build/helpers/wire/types.d.ts.map +1 -0
  147. package/build/helpers/wire/types.js +2 -0
  148. package/build/helpers/wire/types.js.map +1 -0
  149. package/build/helpers/wire.d.ts +4 -0
  150. package/build/helpers/wire.d.ts.map +1 -0
  151. package/build/helpers/wire.js +216 -0
  152. package/build/helpers/wire.js.map +1 -0
  153. package/build/i18n/be.json +5 -0
  154. package/build/i18n/de.json +5 -0
  155. package/build/i18n/en.json +5 -0
  156. package/build/i18n/es.json +5 -0
  157. package/build/i18n/fr.json +5 -0
  158. package/build/i18n/pl.json +5 -0
  159. package/build/i18n/ru.json +5 -0
  160. package/build/i18n/uk.json +5 -0
  161. package/build/i18n.js +3 -3
  162. package/build/i18n.js.map +1 -1
  163. package/build/index.d.ts +7 -0
  164. package/build/index.d.ts.map +1 -1
  165. package/build/index.js +3 -0
  166. package/build/index.js.map +1 -1
  167. package/build/models/index.d.ts +2 -3
  168. package/build/models/index.d.ts.map +1 -1
  169. package/build/models/index.js +1 -15
  170. package/build/models/index.js.map +1 -1
  171. package/build/models/model.d.ts +5 -0
  172. package/build/models/model.d.ts.map +1 -0
  173. package/build/models/model.js +16 -0
  174. package/build/models/model.js.map +1 -0
  175. package/build/models/project.d.ts +2 -1
  176. package/build/models/project.d.ts.map +1 -1
  177. package/build/models/project.js.map +1 -1
  178. package/build/models/specification.d.ts +2 -1
  179. package/build/models/specification.d.ts.map +1 -1
  180. package/build/models/specification.js.map +1 -1
  181. package/build/models/types.d.ts +69 -0
  182. package/build/models/types.d.ts.map +1 -0
  183. package/build/models/types.js +2 -0
  184. package/build/models/types.js.map +1 -0
  185. package/build/models/workcard.d.ts +2 -1
  186. package/build/models/workcard.d.ts.map +1 -1
  187. package/build/models/workcard.js +12 -12
  188. package/build/models/workcard.js.map +1 -1
  189. package/build/registry.d.ts +3 -2
  190. package/build/registry.d.ts.map +1 -1
  191. package/build/registry.js +51 -3
  192. package/build/registry.js.map +1 -1
  193. package/build/resources/mentions.d.ts +4 -0
  194. package/build/resources/mentions.d.ts.map +1 -0
  195. package/build/resources/mentions.js +15 -0
  196. package/build/resources/mentions.js.map +1 -0
  197. package/build/resources/protocol/types.d.ts +30 -0
  198. package/build/resources/protocol/types.d.ts.map +1 -0
  199. package/build/resources/protocol/types.js +2 -0
  200. package/build/resources/protocol/types.js.map +1 -0
  201. package/build/resources/protocol.d.ts +6 -0
  202. package/build/resources/protocol.d.ts.map +1 -0
  203. package/build/resources/protocol.js +31 -0
  204. package/build/resources/protocol.js.map +1 -0
  205. package/build/resources/types.d.ts +146 -0
  206. package/build/resources/types.d.ts.map +1 -0
  207. package/build/resources/types.js +2 -0
  208. package/build/resources/types.js.map +1 -0
  209. package/build/resources/types.local.d.ts +6 -0
  210. package/build/resources/types.local.d.ts.map +1 -0
  211. package/build/resources/types.local.js +2 -0
  212. package/build/resources/types.local.js.map +1 -0
  213. package/build/schemas.d.ts +21 -2
  214. package/build/schemas.d.ts.map +1 -1
  215. package/build/schemas.js +164 -2
  216. package/build/schemas.js.map +1 -1
  217. package/build/types.d.ts +330 -151
  218. package/build/types.d.ts.map +1 -1
  219. package/build/utils.d.ts +6 -0
  220. package/build/utils.d.ts.map +1 -0
  221. package/build/utils.js +6 -0
  222. package/build/utils.js.map +1 -0
  223. package/package.json +13 -13
  224. package/src/aliases/types.ts +41 -0
  225. package/src/aliases.ts +34 -0
  226. package/src/consts.ts +50 -25
  227. package/src/entrypoints/types.ts +77 -0
  228. package/src/entrypoints.ts +53 -24
  229. package/src/errors.ts +71 -0
  230. package/src/helpers/app/consts.local.ts +5 -0
  231. package/src/helpers/app/types.ts +54 -0
  232. package/src/helpers/app.ts +158 -0
  233. package/src/helpers/apply/consts.local.ts +10 -0
  234. package/src/helpers/apply/types.ts +32 -0
  235. package/src/helpers/apply.ts +141 -153
  236. package/src/helpers/card/types.ts +20 -0
  237. package/src/helpers/card.ts +59 -33
  238. package/src/helpers/changes/consts.local.ts +17 -0
  239. package/src/helpers/changes/types.ts +66 -0
  240. package/src/helpers/changes.ts +270 -280
  241. package/src/helpers/code/types.ts +31 -0
  242. package/src/helpers/code.ts +52 -48
  243. package/src/helpers/index.ts +17 -0
  244. package/src/helpers/query/types.ts +26 -0
  245. package/src/helpers/query.ts +144 -314
  246. package/src/helpers/reply/consts.ts +1 -0
  247. package/src/helpers/reply/types.ts +13 -0
  248. package/src/helpers/reply.ts +27 -0
  249. package/src/helpers/scoped/types.ts +78 -0
  250. package/src/helpers/scoped.ts +281 -0
  251. package/src/helpers/specification/types.ts +26 -0
  252. package/src/helpers/specification.ts +69 -51
  253. package/src/helpers/status/types.ts +46 -0
  254. package/src/helpers/status.ts +117 -93
  255. package/src/helpers/types.local.ts +18 -0
  256. package/src/helpers/validate/types.ts +31 -0
  257. package/src/helpers/validate.ts +89 -77
  258. package/src/helpers/wire/types.ts +31 -0
  259. package/src/helpers/wire.ts +252 -0
  260. package/src/i18n/be.json +5 -0
  261. package/src/i18n/de.json +5 -0
  262. package/src/i18n/en.json +5 -0
  263. package/src/i18n/es.json +5 -0
  264. package/src/i18n/fr.json +5 -0
  265. package/src/i18n/pl.json +5 -0
  266. package/src/i18n/ru.json +5 -0
  267. package/src/i18n/uk.json +5 -0
  268. package/src/i18n.ts +4 -3
  269. package/src/index.ts +8 -0
  270. package/src/models/index.ts +2 -17
  271. package/src/models/model.ts +18 -0
  272. package/src/models/project.ts +2 -1
  273. package/src/models/specification.ts +2 -1
  274. package/src/models/types.ts +70 -0
  275. package/src/models/workcard.ts +14 -16
  276. package/src/registry.ts +55 -4
  277. package/src/resources/mentions.ts +11 -0
  278. package/src/resources/protocol/types.ts +21 -0
  279. package/src/resources/protocol.ts +36 -0
  280. package/src/resources/types.local.ts +5 -0
  281. package/src/resources/types.ts +148 -0
  282. package/src/schemas.ts +179 -4
  283. package/src/types.ts +334 -130
  284. package/src/utils.ts +9 -0
  285. package/tests/app.spec.ts +188 -0
  286. package/tests/apply.spec.ts +95 -31
  287. package/tests/code.spec.ts +9 -9
  288. package/tests/csp.spec.ts +122 -0
  289. package/tests/entrypoints.spec.ts +18 -2
  290. package/tests/error-status.spec.ts +6 -1
  291. package/tests/models.spec.ts +24 -16
  292. package/tests/query.spec.ts +13 -15
  293. package/tests/schemas.spec.ts +8 -8
  294. package/tests/scoped.spec.ts +161 -0
  295. package/tests/status.spec.ts +31 -22
@@ -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
- AnyTypeSchema, PlanningSchemaRegistry, StatusDefinition, StatusFlowSchema, StatusTransitionRule,
5
- } from '../types.js'
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
- type FlowLookup = Pick<PlanningSchemaRegistry, 'flow'>
63
+ const initialFlowsOf = (
64
+ type: AnyTypeSchema, registry: FlowLookup, status?: string
65
+ ): Record<string, string> => {
66
+ const primary = primaryFlowOf(type)
8
67
 
9
- /** The primary flow id of a type — `flows[0]`. @throws {UnknownStatusFlow} when it declares none */
10
- export const primaryFlowOf = (type: AnyTypeSchema): string => {
11
- const primary = type.flows[0]
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
- return primary
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 flowIdsOf = (type: AnyTypeSchema): string[] => [...type.flows]
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
- flow.statuses.find(definition => definition.key === status)
109
+ statusHelper.statusDefinitionOf(flow, status)
23
110
 
24
- /** The intrinsic state a status maps onto, or `undefined` for a status the flow does not declare. */
111
+ /** @deprecated compat:factory-refactor — use `statusHelper.intrinsicOf(…)` */
25
112
  export const intrinsicOf = (flow: StatusFlowSchema, status: string): IntrinsicStatus | undefined =>
26
- statusDefinitionOf(flow, status)?.intrinsic
113
+ statusHelper.intrinsicOf(flow, status)
27
114
 
28
- /** The status marked `initial`, else the first one. @throws {PlanningError} for a flow with none */
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
- return status.key
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
- export const isTerminal = (flow: StatusFlowSchema, status: string): boolean =>
39
- statusDefinitionOf(flow, status)?.terminal === true
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
- ruleOf(flow, transition, from) != null
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
- return names.map(name => ruleOf(flow, name, status))
72
- .filter((rule): rule is StatusTransitionRule => rule != null)
73
- .filter(rule => opts?.explicit !== true || rule.explicit === true)
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,31 @@
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
+ assertSchema: (schema: object, value: unknown) => void
7
+ /** One line per ajv error: `/path message`. */
8
+ ajvErrorText: (errors?: ErrorObject[] | null) => string
9
+ /** Keys holding `.` or `$` at any depth — a document store cannot hold them. */
10
+ invalidFieldKeys: (fields: unknown, path?: string) => string[]
11
+ /**
12
+ * Check `fields` against the type's schema, and its keys against the store rule.
13
+ *
14
+ * @throws {FieldsInvalid}
15
+ */
16
+ validateFields: (registry: Pick<PlanningSchemaRegistry, 'validator'>, type: string, fields: Record<string, unknown>) => void
17
+ /**
18
+ * Check a whole record: its shape by kind, then its `fields` by type.
19
+ *
20
+ * @throws {PlanningError} `malformed:card:…` for a shape fault
21
+ * @throws {FieldsInvalid}
22
+ */
23
+ validateCard: (card: Workcard, registry: Pick<PlanningSchemaRegistry, 'validator'>) => void
24
+ /**
25
+ * Check a document body against its slot: a JSON slot must parse, and match the slot's schema when
26
+ * it declares one.
27
+ *
28
+ * @throws {FieldsInvalid}
29
+ */
30
+ validateSpecificationBody: (slot: SpecificationSlot, body?: string | null) => void
31
+ }
@@ -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,104 @@ export const makeAjv = (): Ajv => {
14
16
  return ajv
15
17
  }
16
18
 
17
- let shared: Ajv | undefined
18
- const ajvOf = (): Ajv => shared ??= makeAjv()
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
- let validate = compiled.get(schema)
23
- if (validate == null) {
24
- validate = ajvOf().compile(schema)
25
- compiled.set(schema, validate)
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
- return validate
29
- }
33
+ const ajvErrorText = (errors?: ErrorObject[] | null): string =>
34
+ (errors ?? []).map(error => `${error.instancePath === '' ? '/' : error.instancePath} ${error.message ?? 'invalid'}`)
35
+ .join('; ')
30
36
 
31
- /** One line per ajv error: `/path message`. */
32
- export const ajvErrorText = (errors?: ErrorObject[] | null): string =>
33
- (errors ?? []).map(error => `${error.instancePath === '' ? '/' : error.instancePath} ${error.message ?? 'invalid'}`)
34
- .join('; ')
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
- /** Keys holding `.` or `$` at any depth — a document store cannot hold them. */
37
- export 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}.`))
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
- return Object.entries(fields).flatMap(([key, value]) => [
46
- ...(key.includes('.') || key.includes('$') ? [`${path}${key}`] : []),
47
- ...invalidFieldKeys(value, `${path}${key}.`),
48
- ])
49
- }
50
-
51
- /**
52
- * Check `fields` against the type's schema, and its keys against the store rule.
53
- *
54
- * @throws {FieldsInvalid}
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
- * Check a whole record: its shape by kind, then its `fields` by type.
71
- *
72
- * @throws {PlanningError} `malformed:card:…` for a shape fault
73
- * @throws {FieldsInvalid}
74
- */
75
- export const validateCard = (card: Workcard, registry: Pick<PlanningSchemaRegistry, 'validator'>): void => {
76
- const validate = compile(card.kind === WorkcardKind.Specification
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
- * Check a document body against its slot: a JSON slot must parse, and match the slot's schema when
87
- * it declares one.
88
- *
89
- * @throws {FieldsInvalid}
90
- */
91
- export const validateSpecificationBody = (slot: SpecificationSlot, body?: string | null): void => {
92
- if (body == null || slot.format !== SpecificationFormat.Json) {
93
- return
94
- }
95
- let parsed: unknown
96
- try {
97
- parsed = JSON.parse(body)
98
- } catch {
99
- throw new FieldsInvalid(`body:${slot.category}:not-json`)
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
+ const assertSchema = (schema: object, value: unknown): void => {
93
+ const validate = compile(schema)
94
+ if (!validate(value)) throw new FieldsInvalid(ajvErrorText(validate.errors))
95
+ }
96
+
97
+ return { assertSchema, ajvErrorText, invalidFieldKeys, validateFields, validateCard, validateSpecificationBody }
107
98
  }
99
+
100
+ export const validateHelper = createValidateHelper()
101
+
102
+ /** @deprecated compat:factory-refactor — use `validateHelper.ajvErrorText(…)` */
103
+ export const ajvErrorText = (errors?: ErrorObject[] | null): string => validateHelper.ajvErrorText(errors)
104
+
105
+ /** @deprecated compat:factory-refactor — use `validateHelper.invalidFieldKeys(…)` */
106
+ export const invalidFieldKeys = (fields: unknown, path: string = ''): string[] => validateHelper.invalidFieldKeys(fields, path)
107
+
108
+ /** @deprecated compat:factory-refactor — use `validateHelper.validateFields(…)` */
109
+ export const validateFields = (
110
+ registry: Pick<PlanningSchemaRegistry, 'validator'>, type: string, fields: Record<string, unknown>
111
+ ): void => validateHelper.validateFields(registry, type, fields)
112
+
113
+ /** @deprecated compat:factory-refactor — use `validateHelper.validateCard(…)` */
114
+ export const validateCard = (card: Workcard, registry: Pick<PlanningSchemaRegistry, 'validator'>): void =>
115
+ validateHelper.validateCard(card, registry)
116
+
117
+ /** @deprecated compat:factory-refactor — use `validateHelper.validateSpecificationBody(…)` */
118
+ export const validateSpecificationBody = (slot: SpecificationSlot, body?: string | null): void =>
119
+ 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
+ }