@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.
Files changed (314) hide show
  1. package/README.md +24 -17
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/server-planning/SKILL.md +28 -28
  4. package/build/actions/apply-schema-request.d.ts +8 -0
  5. package/build/actions/apply-schema-request.d.ts.map +1 -0
  6. package/build/actions/apply-schema-request.js +31 -0
  7. package/build/actions/apply-schema-request.js.map +1 -0
  8. package/build/actions/commit.d.ts +3 -3
  9. package/build/actions/commit.d.ts.map +1 -1
  10. package/build/actions/commit.js +5 -4
  11. package/build/actions/commit.js.map +1 -1
  12. package/build/actions/define-schemas.d.ts +13 -0
  13. package/build/actions/define-schemas.d.ts.map +1 -0
  14. package/build/actions/define-schemas.js +28 -0
  15. package/build/actions/define-schemas.js.map +1 -0
  16. package/build/actions/execute.d.ts +8 -28
  17. package/build/actions/execute.d.ts.map +1 -1
  18. package/build/actions/execute.js +17 -101
  19. package/build/actions/execute.js.map +1 -1
  20. package/build/actions/execution/types.d.ts +34 -0
  21. package/build/actions/execution/types.d.ts.map +1 -0
  22. package/build/actions/execution/types.js +2 -0
  23. package/build/actions/execution/types.js.map +1 -0
  24. package/build/actions/execution.d.ts +4 -0
  25. package/build/actions/execution.d.ts.map +1 -0
  26. package/build/actions/execution.js +79 -0
  27. package/build/actions/execution.js.map +1 -0
  28. package/build/actions/get-card.d.ts +6 -0
  29. package/build/actions/get-card.d.ts.map +1 -0
  30. package/build/actions/get-card.js +9 -0
  31. package/build/actions/get-card.js.map +1 -0
  32. package/build/actions/get-specification.d.ts +6 -0
  33. package/build/actions/get-specification.d.ts.map +1 -0
  34. package/build/actions/get-specification.js +9 -0
  35. package/build/actions/get-specification.js.map +1 -0
  36. package/build/actions/index.d.ts +12 -3
  37. package/build/actions/index.d.ts.map +1 -1
  38. package/build/actions/index.js +11 -3
  39. package/build/actions/index.js.map +1 -1
  40. package/build/actions/links.d.ts +3 -3
  41. package/build/actions/links.d.ts.map +1 -1
  42. package/build/actions/links.js +6 -5
  43. package/build/actions/links.js.map +1 -1
  44. package/build/actions/list-card-specifications.d.ts +6 -0
  45. package/build/actions/list-card-specifications.d.ts.map +1 -0
  46. package/build/actions/list-card-specifications.js +11 -0
  47. package/build/actions/list-card-specifications.js.map +1 -0
  48. package/build/actions/list-card-transitions.d.ts +6 -0
  49. package/build/actions/list-card-transitions.d.ts.map +1 -0
  50. package/build/actions/list-card-transitions.js +11 -0
  51. package/build/actions/list-card-transitions.js.map +1 -0
  52. package/build/actions/list-cards.d.ts +6 -0
  53. package/build/actions/list-cards.d.ts.map +1 -0
  54. package/build/actions/list-cards.js +10 -0
  55. package/build/actions/list-cards.js.map +1 -0
  56. package/build/actions/list-schemas.d.ts +13 -0
  57. package/build/actions/list-schemas.d.ts.map +1 -0
  58. package/build/actions/list-schemas.js +20 -0
  59. package/build/actions/list-schemas.js.map +1 -0
  60. package/build/actions/{specs.d.ts → list-specification-revisions.d.ts} +1 -3
  61. package/build/actions/list-specification-revisions.d.ts.map +1 -0
  62. package/build/actions/list-specification-revisions.js +10 -0
  63. package/build/actions/list-specification-revisions.js.map +1 -0
  64. package/build/actions/summarize-cards.d.ts +6 -0
  65. package/build/actions/summarize-cards.d.ts.map +1 -0
  66. package/build/actions/summarize-cards.js +11 -0
  67. package/build/actions/summarize-cards.js.map +1 -0
  68. package/build/actions/transitions.d.ts +3 -3
  69. package/build/actions/transitions.d.ts.map +1 -1
  70. package/build/actions/transitions.js +4 -3
  71. package/build/actions/transitions.js.map +1 -1
  72. package/build/actions/types.d.ts +5 -3
  73. package/build/actions/types.d.ts.map +1 -1
  74. package/build/actions/watch.d.ts +2 -2
  75. package/build/actions/watch.d.ts.map +1 -1
  76. package/build/actions/watch.js +2 -2
  77. package/build/actions/watch.js.map +1 -1
  78. package/build/conformance/assert/types.d.ts +19 -0
  79. package/build/conformance/assert/types.d.ts.map +1 -0
  80. package/build/conformance/assert/types.js +2 -0
  81. package/build/conformance/assert/types.js.map +1 -0
  82. package/build/conformance/assert.d.ts +8 -13
  83. package/build/conformance/assert.d.ts.map +1 -1
  84. package/build/conformance/assert.js +41 -43
  85. package/build/conformance/assert.js.map +1 -1
  86. package/build/conformance/cases.d.ts.map +1 -1
  87. package/build/conformance/cases.js +86 -67
  88. package/build/conformance/cases.js.map +1 -1
  89. package/build/conformance/clock.d.ts +6 -0
  90. package/build/conformance/clock.d.ts.map +1 -0
  91. package/build/conformance/clock.js +12 -0
  92. package/build/conformance/clock.js.map +1 -0
  93. package/build/conformance/consts.d.ts +3 -1
  94. package/build/conformance/consts.d.ts.map +1 -1
  95. package/build/conformance/consts.js +84 -1
  96. package/build/conformance/consts.js.map +1 -1
  97. package/build/conformance/errors.d.ts +5 -0
  98. package/build/conformance/errors.d.ts.map +1 -0
  99. package/build/conformance/errors.js +8 -0
  100. package/build/conformance/errors.js.map +1 -0
  101. package/build/conformance/fixtures/types.d.ts +13 -0
  102. package/build/conformance/fixtures/types.d.ts.map +1 -0
  103. package/build/conformance/fixtures/types.js +2 -0
  104. package/build/conformance/fixtures/types.js.map +1 -0
  105. package/build/conformance/fixtures.d.ts +8 -8
  106. package/build/conformance/fixtures.d.ts.map +1 -1
  107. package/build/conformance/fixtures.js +24 -107
  108. package/build/conformance/fixtures.js.map +1 -1
  109. package/build/conformance/index.d.ts +3 -0
  110. package/build/conformance/index.d.ts.map +1 -1
  111. package/build/conformance/index.js +1 -0
  112. package/build/conformance/index.js.map +1 -1
  113. package/build/definitions.d.ts.map +1 -1
  114. package/build/definitions.js +9 -9
  115. package/build/definitions.js.map +1 -1
  116. package/build/executor/changes/types.d.ts +36 -0
  117. package/build/executor/changes/types.d.ts.map +1 -0
  118. package/build/executor/changes/types.js +2 -0
  119. package/build/executor/changes/types.js.map +1 -0
  120. package/build/executor/changes.d.ts +3 -30
  121. package/build/executor/changes.d.ts.map +1 -1
  122. package/build/executor/changes.js +42 -51
  123. package/build/executor/changes.js.map +1 -1
  124. package/build/executor/code/types.d.ts +17 -0
  125. package/build/executor/code/types.d.ts.map +1 -0
  126. package/build/executor/code/types.js +2 -0
  127. package/build/executor/code/types.js.map +1 -0
  128. package/build/executor/code.d.ts +3 -14
  129. package/build/executor/code.d.ts.map +1 -1
  130. package/build/executor/code.js +50 -54
  131. package/build/executor/code.js.map +1 -1
  132. package/build/executor/creator/types.d.ts +33 -0
  133. package/build/executor/creator/types.d.ts.map +1 -0
  134. package/build/executor/creator/types.js +2 -0
  135. package/build/executor/creator/types.js.map +1 -0
  136. package/build/executor/creator.d.ts +6 -25
  137. package/build/executor/creator.d.ts.map +1 -1
  138. package/build/executor/creator.js +26 -41
  139. package/build/executor/creator.js.map +1 -1
  140. package/build/executor/resolve/types.d.ts +30 -0
  141. package/build/executor/resolve/types.d.ts.map +1 -0
  142. package/build/executor/resolve/types.js +2 -0
  143. package/build/executor/resolve/types.js.map +1 -0
  144. package/build/executor/resolve.d.ts +4 -41
  145. package/build/executor/resolve.d.ts.map +1 -1
  146. package/build/executor/resolve.js +142 -157
  147. package/build/executor/resolve.js.map +1 -1
  148. package/build/executor/types.d.ts +0 -2
  149. package/build/executor/types.d.ts.map +1 -1
  150. package/build/executor/validate.d.ts +1 -1
  151. package/build/executor/validate.d.ts.map +1 -1
  152. package/build/executor/validate.js +16 -15
  153. package/build/executor/validate.js.map +1 -1
  154. package/build/executor.d.ts +4 -3
  155. package/build/executor.d.ts.map +1 -1
  156. package/build/executor.js +18 -17
  157. package/build/executor.js.map +1 -1
  158. package/build/facade.d.ts.map +1 -1
  159. package/build/facade.js +16 -16
  160. package/build/facade.js.map +1 -1
  161. package/build/helper.d.ts +1 -6
  162. package/build/helper.d.ts.map +1 -1
  163. package/build/helper.js +3 -8
  164. package/build/helper.js.map +1 -1
  165. package/build/projection.d.ts +2 -0
  166. package/build/projection.d.ts.map +1 -1
  167. package/build/projection.js +6 -4
  168. package/build/projection.js.map +1 -1
  169. package/build/queue/declare.d.ts +5 -0
  170. package/build/queue/declare.d.ts.map +1 -0
  171. package/build/queue/declare.js +5 -0
  172. package/build/queue/declare.js.map +1 -0
  173. package/build/queue/index.d.ts +1 -4
  174. package/build/queue/index.d.ts.map +1 -1
  175. package/build/queue/index.js +1 -4
  176. package/build/queue/index.js.map +1 -1
  177. package/build/store/composite.d.ts.map +1 -1
  178. package/build/store/composite.js +2 -2
  179. package/build/store/composite.js.map +1 -1
  180. package/build/store/fold/types.d.ts +43 -0
  181. package/build/store/fold/types.d.ts.map +1 -0
  182. package/build/store/fold/types.js +2 -0
  183. package/build/store/fold/types.js.map +1 -0
  184. package/build/store/fold.d.ts +7 -30
  185. package/build/store/fold.d.ts.map +1 -1
  186. package/build/store/fold.js +185 -203
  187. package/build/store/fold.js.map +1 -1
  188. package/build/store/index.d.ts +1 -0
  189. package/build/store/index.d.ts.map +1 -1
  190. package/build/store/index.js.map +1 -1
  191. package/build/store/memory.d.ts.map +1 -1
  192. package/build/store/memory.js +0 -0
  193. package/build/store/memory.js.map +1 -1
  194. package/build/store/types.local.d.ts +3 -1
  195. package/build/store/types.local.d.ts.map +1 -1
  196. package/build/types.d.ts +2 -1
  197. package/build/types.d.ts.map +1 -1
  198. package/build/utils/access/types.d.ts +30 -0
  199. package/build/utils/access/types.d.ts.map +1 -0
  200. package/build/utils/access/types.js +2 -0
  201. package/build/utils/access/types.js.map +1 -0
  202. package/build/utils/access.d.ts +4 -0
  203. package/build/utils/access.d.ts.map +1 -0
  204. package/build/utils/access.js +28 -0
  205. package/build/utils/access.js.map +1 -0
  206. package/build/utils/guard/types.d.ts +21 -0
  207. package/build/utils/guard/types.d.ts.map +1 -0
  208. package/build/utils/guard/types.js +2 -0
  209. package/build/utils/guard/types.js.map +1 -0
  210. package/build/utils/guard.d.ts +7 -10
  211. package/build/utils/guard.d.ts.map +1 -1
  212. package/build/utils/guard.js +33 -31
  213. package/build/utils/guard.js.map +1 -1
  214. package/build/utils/handler/types.d.ts +26 -0
  215. package/build/utils/handler/types.d.ts.map +1 -0
  216. package/build/utils/handler/types.js +2 -0
  217. package/build/utils/handler/types.js.map +1 -0
  218. package/build/utils/handler.d.ts +6 -0
  219. package/build/utils/handler.d.ts.map +1 -0
  220. package/build/utils/handler.js +21 -0
  221. package/build/utils/handler.js.map +1 -0
  222. package/build/utils/index.d.ts +8 -1
  223. package/build/utils/index.d.ts.map +1 -1
  224. package/build/utils/index.js +3 -0
  225. package/build/utils/index.js.map +1 -1
  226. package/build/utils/request/types.d.ts +23 -0
  227. package/build/utils/request/types.d.ts.map +1 -0
  228. package/build/utils/request/types.js +2 -0
  229. package/build/utils/request/types.js.map +1 -0
  230. package/build/utils/request.d.ts +4 -0
  231. package/build/utils/request.d.ts.map +1 -0
  232. package/build/utils/request.js +27 -0
  233. package/build/utils/request.js.map +1 -0
  234. package/build/utils/scope.d.ts +10 -43
  235. package/build/utils/scope.d.ts.map +1 -1
  236. package/build/utils/scope.js +21 -99
  237. package/build/utils/scope.js.map +1 -1
  238. package/package.json +18 -18
  239. package/src/actions/apply-schema-request.ts +35 -0
  240. package/src/actions/commit.ts +7 -5
  241. package/src/actions/define-schemas.ts +34 -0
  242. package/src/actions/execute.ts +30 -115
  243. package/src/actions/execution/types.ts +34 -0
  244. package/src/actions/execution.ts +96 -0
  245. package/src/actions/get-card.ts +14 -0
  246. package/src/actions/get-specification.ts +14 -0
  247. package/src/actions/index.ts +13 -3
  248. package/src/actions/links.ts +8 -6
  249. package/src/actions/list-card-specifications.ts +15 -0
  250. package/src/actions/list-card-transitions.ts +15 -0
  251. package/src/actions/list-cards.ts +14 -0
  252. package/src/actions/list-schemas.ts +29 -0
  253. package/src/actions/{specs.ts → list-specification-revisions.ts} +4 -11
  254. package/src/actions/summarize-cards.ts +15 -0
  255. package/src/actions/transitions.ts +6 -4
  256. package/src/actions/types.ts +4 -3
  257. package/src/actions/watch.ts +4 -3
  258. package/src/conformance/assert/types.ts +16 -0
  259. package/src/conformance/assert.ts +49 -41
  260. package/src/conformance/cases.ts +88 -67
  261. package/src/conformance/clock.ts +11 -0
  262. package/src/conformance/consts.ts +87 -1
  263. package/src/conformance/errors.ts +7 -0
  264. package/src/conformance/fixtures/types.ts +11 -0
  265. package/src/conformance/fixtures.ts +32 -107
  266. package/src/conformance/index.ts +3 -0
  267. package/src/definitions.ts +9 -9
  268. package/src/executor/changes/types.ts +38 -0
  269. package/src/executor/changes.ts +48 -63
  270. package/src/executor/code/types.ts +23 -0
  271. package/src/executor/code.ts +60 -61
  272. package/src/executor/creator/types.ts +33 -0
  273. package/src/executor/creator.ts +33 -41
  274. package/src/executor/resolve/types.ts +30 -0
  275. package/src/executor/resolve.ts +153 -167
  276. package/src/executor/types.ts +0 -3
  277. package/src/executor/validate.ts +17 -16
  278. package/src/executor.ts +20 -17
  279. package/src/facade.ts +16 -16
  280. package/src/helper.ts +3 -8
  281. package/src/projection.ts +8 -4
  282. package/src/queue/declare.ts +7 -0
  283. package/src/queue/index.ts +1 -8
  284. package/src/store/composite.ts +2 -2
  285. package/src/store/fold/types.ts +44 -0
  286. package/src/store/fold.ts +209 -212
  287. package/src/store/index.ts +1 -0
  288. package/src/store/memory.ts +0 -0
  289. package/src/store/types.local.ts +1 -1
  290. package/src/types.ts +1 -1
  291. package/src/utils/access/types.ts +30 -0
  292. package/src/utils/access.ts +33 -0
  293. package/src/utils/guard/types.ts +19 -0
  294. package/src/utils/guard.ts +41 -30
  295. package/src/utils/handler/types.ts +28 -0
  296. package/src/utils/handler.ts +35 -0
  297. package/src/utils/index.ts +9 -1
  298. package/src/utils/request/types.ts +23 -0
  299. package/src/utils/request.ts +40 -0
  300. package/src/utils/scope.ts +25 -108
  301. package/tests/creator.spec.ts +18 -18
  302. package/build/actions/cards.d.ts +0 -14
  303. package/build/actions/cards.d.ts.map +0 -1
  304. package/build/actions/cards.js +0 -32
  305. package/build/actions/cards.js.map +0 -1
  306. package/build/actions/schemas.d.ts +0 -28
  307. package/build/actions/schemas.d.ts.map +0 -1
  308. package/build/actions/schemas.js +0 -69
  309. package/build/actions/schemas.js.map +0 -1
  310. package/build/actions/specs.d.ts.map +0 -1
  311. package/build/actions/specs.js +0 -14
  312. package/build/actions/specs.js.map +0 -1
  313. package/src/actions/cards.ts +0 -48
  314. package/src/actions/schemas.ts +0 -87
@@ -1,6 +1,6 @@
1
- import { assertFlowSchema, assertOverridable, assertTypeSchema, flowInUse, isProject, PlanningSchemaKind, PlanningUnsupported, resolveScopedBundle, SchemaConflict, SchemaInUse, scopedRegistryOf, UnknownStatusFlow, UnknownWorkcardType, WorkcardNotFound, type PlanningDefinitions, type PlanningFacade, type PlanningSchemaRegistry, type SchemaDeclarations, type SchemaStore, type SchemaWriteOptions, type ScopedSchemaRecord, type ScopedSchemaRegistry, type StatusFlowSchema, type TransitionExecution, type Unsubscribe, type WorkcardTypeSchema } from '@owlmeans/planning'
1
+ import { cardHelper, PlanningSchemaKind, PlanningUnsupported, SchemaConflict, SchemaInUse, scopedSchemaHelper, UnknownStatusFlow, UnknownWorkcardType, WorkcardNotFound, type PlanningDefinitions, type PlanningFacade, type PlanningSchemaRegistry, type SchemaDeclarations, type SchemaStore, type SchemaWriteOptions, type ScopedSchemaRecord, type ScopedSchemaRegistry, type StatusFlowSchema, type TransitionExecution, type Unsubscribe, type WorkcardTypeSchema } from '@owlmeans/planning'
2
2
  import { DEFAULT_SCHEMA_VIEWS } from './consts.js'
3
- import { actorOf } from './executor/changes.js'
3
+ import { changesUtils } from './executor/changes.js'
4
4
  import type { PlanningRuntime, SchemaViews, SchemaViewsOptions } from './types.js'
5
5
  import type { Planned } from './types.local.js'
6
6
 
@@ -52,7 +52,7 @@ export const makeSchemaViews = (opts: SchemaViewsOptions): SchemaViews => {
52
52
  ...await store.list({ entityId, project: null }),
53
53
  ...(project != null ? await store.list({ entityId, project }) : []),
54
54
  ]
55
- const view = scopedRegistryOf(resolveScopedBundle(opts.code().bundle(), records, { entityId, project, revision }))
55
+ const view = scopedSchemaHelper.scopedRegistryOf(scopedSchemaHelper.resolveScopedBundle(opts.code().bundle(), records, { entityId, project, revision }))
56
56
  cache.delete(key)
57
57
  cache.set(key, { revision, version, view })
58
58
  while (cache.size > limit) {
@@ -106,7 +106,7 @@ export const makeDefinitions = (runtime: PlanningRuntime, facade: PlanningFacade
106
106
  return
107
107
  }
108
108
  const card = await facade.cards.load(project)
109
- if (card == null || !isProject(card)) {
109
+ if (card == null || !cardHelper.isProject(card)) {
110
110
  throw new WorkcardNotFound(project)
111
111
  }
112
112
  }
@@ -124,11 +124,11 @@ export const makeDefinitions = (runtime: PlanningRuntime, facade: PlanningFacade
124
124
  const flows: Pick<PlanningSchemaRegistry, 'flow'> = { flow: id => pending.get(id) ?? layer.flow(id) }
125
125
 
126
126
  for (const entry of plan) {
127
- assertOverridable(code, entry.kind, entry.key)
127
+ scopedSchemaHelper.assertOverridable(code, entry.kind, entry.key)
128
128
  if (entry.kind === PlanningSchemaKind.Flow) {
129
- assertFlowSchema(entry.definition as StatusFlowSchema)
129
+ scopedSchemaHelper.assertFlowSchema(entry.definition as StatusFlowSchema)
130
130
  } else {
131
- assertTypeSchema(entry.definition as WorkcardTypeSchema, flows)
131
+ scopedSchemaHelper.assertTypeSchema(entry.definition as WorkcardTypeSchema, flows)
132
132
  }
133
133
  }
134
134
  }
@@ -137,7 +137,7 @@ export const makeDefinitions = (runtime: PlanningRuntime, facade: PlanningFacade
137
137
  entry: Planned, version: number, project: string | undefined, current?: ScopedSchemaRecord, retired?: boolean
138
138
  ): ScopedSchemaRecord => {
139
139
  const at = now()
140
- const by = actorOf(scope, {} as TransitionExecution)
140
+ const by = changesUtils.actorOf(scope, {} as TransitionExecution)
141
141
  return Object.fromEntries(Object.entries({
142
142
  entityId,
143
143
  project,
@@ -235,7 +235,7 @@ export const makeDefinitions = (runtime: PlanningRuntime, facade: PlanningFacade
235
235
  ? [opts.project]
236
236
  : [undefined, ...new Set(all.map(record => record.project).filter((id): id is string => id != null))]
237
237
  for (const project of projects) {
238
- const user = flowInUse(resolveScopedBundle(code, all, { entityId, project }), key)
238
+ const user = scopedSchemaHelper.flowInUse(scopedSchemaHelper.resolveScopedBundle(code, all, { entityId, project }), key)
239
239
  if (user != null) {
240
240
  throw new SchemaInUse(`flow:${key}:type:${user}${project != null ? `:project:${project}` : ''}`)
241
241
  }
@@ -0,0 +1,38 @@
1
+ import type {
2
+ ChangeSet, PlanningSchemaRegistry, PlanningScope, Transition, TransitionActor, TransitionExecution,
3
+ } from '@owlmeans/planning'
4
+ import type { Resolved } from '../types.js'
5
+
6
+ /** What step 11 builds the appended transition from. */
7
+ export interface AppendParams {
8
+ exec: TransitionExecution
9
+ resolved: Resolved
10
+ scope: PlanningScope
11
+ set: ChangeSet
12
+ cardId: string
13
+ seq: number
14
+ project?: string
15
+ at: string
16
+ }
17
+
18
+ /** The record an execution appends: its changes, its actor and the transition itself. */
19
+ export interface ChangesUtils {
20
+ /**
21
+ * Step 9: the recorded `changes`/`unset`, and whether an update wrote nothing.
22
+ *
23
+ * @throws {IllegalTransition | PlanningError}
24
+ */
25
+ changeSetOf: (
26
+ exec: TransitionExecution, resolved: Resolved, schemas: PlanningSchemaRegistry, at: string
27
+ ) => { set: ChangeSet, empty: boolean }
28
+ /**
29
+ * Who wrote it — the SCOPE's identity, never the execution's.
30
+ *
31
+ * `profileId`, `userId`, `service` and `channel` come only from the scope (the server builds it
32
+ * from the authenticated request); the descriptive `agent`/`runId` an in-process caller passes on
33
+ * the execution are kept unless the scope's own actor names them.
34
+ */
35
+ actorOf: (scope: PlanningScope, exec: TransitionExecution) => TransitionActor
36
+ /** Step 11's record: the event as it is appended, `pending`. */
37
+ transitionOf: (params: AppendParams) => Transition
38
+ }
@@ -1,74 +1,59 @@
1
- import { CommitState, computeChanges, isEmptyChange, TransitionAction, type ChangeSet, type PlanningSchemaRegistry, type PlanningScope, type RelationshipDraft, type Transition, type TransitionActor, type TransitionExecution, type WorkcardDraft } from '@owlmeans/planning'
1
+ import { changesHelper, CommitState, TransitionAction, type ChangeSet, type PlanningSchemaRegistry, type PlanningScope, type RelationshipDraft, type Transition, type TransitionActor, type TransitionExecution, type WorkcardDraft } from '@owlmeans/planning'
2
+ import type { ChangesUtils, AppendParams } from './changes/types.js'
2
3
  import type { Resolved } from './types.js'
3
4
 
4
5
  const clean = <T extends object>(record: T): T =>
5
6
  Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined)) as T
6
7
 
7
- /**
8
- * Step 9: the recorded `changes`/`unset`, and whether an update wrote nothing.
9
- *
10
- * @throws {IllegalTransition | PlanningError}
11
- */
12
- export const changeSetOf = (
13
- exec: TransitionExecution, resolved: Resolved, schemas: PlanningSchemaRegistry, at: string
14
- ): { set: ChangeSet, empty: boolean } => {
15
- const set = computeChanges(resolved.card, exec, resolved.type, schemas, at, { slot: resolved.slot })
16
- const empty = exec.action === TransitionAction.Update && isEmptyChange(set)
8
+ export const createChangesUtils = (): ChangesUtils => {
9
+ const changeSetOf = (
10
+ exec: TransitionExecution, resolved: Resolved, schemas: PlanningSchemaRegistry, at: string
11
+ ): { set: ChangeSet, empty: boolean } => {
12
+ const set = changesHelper.computeChanges(resolved.card, exec, resolved.type, schemas, at, { slot: resolved.slot })
13
+ const empty = exec.action === TransitionAction.Update && changesHelper.isEmptyChange(set)
17
14
 
18
- return { set, empty }
19
- }
15
+ return { set, empty }
16
+ }
20
17
 
21
- /**
22
- * Who wrote it — the SCOPE's identity, never the execution's.
23
- *
24
- * `profileId`, `userId`, `service` and `channel` come only from the scope (the server builds it
25
- * from the authenticated request); the descriptive `agent`/`runId` an in-process caller passes on
26
- * the execution are kept unless the scope's own actor names them.
27
- */
28
- export const actorOf = (scope: PlanningScope, exec: TransitionExecution): TransitionActor => clean({
29
- agent: exec.actor?.agent,
30
- runId: exec.actor?.runId,
31
- ...clean({ profileId: scope.profileId, userId: scope.userId, service: scope.service }),
32
- ...clean(scope.actor ?? {}),
33
- channel: scope.channel ?? scope.actor?.channel,
34
- })
18
+ const actorOf = (scope: PlanningScope, exec: TransitionExecution): TransitionActor => clean({
19
+ agent: exec.actor?.agent,
20
+ runId: exec.actor?.runId,
21
+ ...clean({ profileId: scope.profileId, userId: scope.userId, service: scope.service }),
22
+ ...clean(scope.actor ?? {}),
23
+ channel: scope.channel ?? scope.actor?.channel,
24
+ })
35
25
 
36
- const withFrom = (link: RelationshipDraft, card: string): RelationshipDraft => ({ ...link, from: link.from ?? card })
26
+ const withFrom = (link: RelationshipDraft, card: string): RelationshipDraft => ({ ...link, from: link.from ?? card })
37
27
 
38
- /** Step 11's record: the event as it is appended, `pending`. */
39
- export const transitionOf = (params: {
40
- exec: TransitionExecution
41
- resolved: Resolved
42
- scope: PlanningScope
43
- set: ChangeSet
44
- cardId: string
45
- seq: number
46
- project?: string
47
- at: string
48
- }): Transition => {
49
- const { exec, resolved, scope, set, cardId, seq, project, at } = params
50
- const kind = resolved.create ? (exec.card as WorkcardDraft).kind : resolved.card!.kind
28
+ const transitionOf = (params: AppendParams): Transition => {
29
+ const { exec, resolved, scope, set, cardId, seq, project, at } = params
30
+ const kind = resolved.create ? (exec.card as WorkcardDraft).kind : resolved.card!.kind
51
31
 
52
- return clean({
53
- entityId: scope.entityId,
54
- card: cardId,
55
- kind,
56
- type: resolved.type.type,
57
- project,
58
- seq,
59
- action: exec.action,
60
- flow: set.flow,
61
- transition: exec.action === TransitionAction.Transit ? exec.transition : undefined,
62
- from: set.from,
63
- to: set.to,
64
- changes: set.changes,
65
- unset: set.unset.length > 0 ? set.unset : undefined,
66
- link: exec.link != null && exec.action !== TransitionAction.Create ? withFrom(exec.link, cardId) : undefined,
67
- links: resolved.create && (exec.links?.length ?? 0) > 0 ? exec.links!.map(link => withFrom(link, cardId)) : undefined,
68
- actor: actorOf(scope, exec),
69
- cause: exec.cause,
70
- key: exec.key,
71
- at,
72
- commit: { state: CommitState.Pending },
73
- })
32
+ return clean({
33
+ entityId: scope.entityId,
34
+ card: cardId,
35
+ kind,
36
+ type: resolved.type.type,
37
+ project,
38
+ seq,
39
+ action: exec.action,
40
+ flow: set.flow,
41
+ transition: exec.action === TransitionAction.Transit ? exec.transition : undefined,
42
+ from: set.from,
43
+ to: set.to,
44
+ changes: set.changes,
45
+ unset: set.unset.length > 0 ? set.unset : undefined,
46
+ link: exec.link != null && exec.action !== TransitionAction.Create ? withFrom(exec.link, cardId) : undefined,
47
+ links: resolved.create && (exec.links?.length ?? 0) > 0 ? exec.links!.map(link => withFrom(link, cardId)) : undefined,
48
+ actor: actorOf(scope, exec),
49
+ cause: exec.cause,
50
+ key: exec.key,
51
+ at,
52
+ commit: { state: CommitState.Pending },
53
+ })
54
+ }
55
+
56
+ return { changeSetOf, actorOf, transitionOf }
74
57
  }
58
+
59
+ export const changesUtils = createChangesUtils()
@@ -0,0 +1,23 @@
1
+ import type { PlanningExecContext, PlanningPlugin, PlanningScope, TransitionExecution } from '@owlmeans/planning'
2
+ import type { PluginRegistry } from '../../types.js'
3
+ import type { Resolved } from '../types.js'
4
+
5
+ /** Step 8 of the write path: the code a card gets, unique within its policy's scope. */
6
+ export interface CodeUtils {
7
+ /**
8
+ * Step 8: the code a new card gets, or the check a changed code passes.
9
+ *
10
+ * A caller-supplied code is checked for uniqueness; otherwise the plugins' `mintCode` chain answers
11
+ * (first answer wins, and is checked too); otherwise the type's policy mints one — a slug policy
12
+ * derives it from the title. A type with no policy and no supplied code gets none.
13
+ *
14
+ * @throws {CodeTaken}
15
+ */
16
+ assignCode: (
17
+ exec: TransitionExecution,
18
+ resolved: Resolved,
19
+ scope: PlanningScope,
20
+ registry: PluginRegistry,
21
+ contextOf: (plugin: PlanningPlugin) => PlanningExecContext
22
+ ) => Promise<TransitionExecution>
23
+ }
@@ -1,77 +1,76 @@
1
- import { CODE_MINT_ATTEMPTS, CodeScope, CodeStyle, CodeTaken, mintCode, TransitionAction, type CodePolicy, type PlanningExecContext, type PlanningPlugin, type PlanningScope, type TransitionExecution, type WorkcardDraft, type Workcard } from '@owlmeans/planning'
1
+ import { CODE_MINT_ATTEMPTS, codeHelper, CodeScope, CodeStyle, CodeTaken, TransitionAction, type CodePolicy, type PlanningExecContext, type PlanningPlugin, type PlanningScope, type TransitionExecution, type Workcard, type WorkcardDraft } from '@owlmeans/planning'
2
2
  import type { Criteria } from '@owlmeans/resource'
3
3
  import type { PluginRegistry } from '../types.js'
4
+ import type { CodeUtils } from './code/types.js'
4
5
  import type { Resolved } from './types.js'
5
6
 
6
- /** The criteria a code must be unique within — the policy's scope, `parent` when there is none. */
7
- const scopeCriteria = (
8
- scope: PlanningScope, type: string, parent: string | undefined, policy?: CodePolicy
9
- ): Criteria<Workcard> => ({
10
- entityId: scope.entityId,
11
- type,
12
- ...((policy?.uniqueWithin ?? CodeScope.Parent) === CodeScope.Parent ? { parent: parent ?? null } : {}),
13
- }) as Criteria<Workcard>
7
+ export const createCodeUtils = (): CodeUtils => {
8
+ /** The criteria a code must be unique within — the policy's scope, `parent` when there is none. */
9
+ const scopeCriteria = (
10
+ scope: PlanningScope, type: string, parent: string | undefined, policy?: CodePolicy
11
+ ): Criteria<Workcard> => ({
12
+ entityId: scope.entityId,
13
+ type,
14
+ ...((policy?.uniqueWithin ?? CodeScope.Parent) === CodeScope.Parent ? { parent: parent ?? null } : {}),
15
+ }) as Criteria<Workcard>
14
16
 
15
- export const takenProbe = (resolved: Resolved, scope: PlanningScope, parent: string | undefined) => {
16
- const base = scopeCriteria(scope, resolved.type.type, parent, resolved.type.code)
17
+ /** Is a code taken within the scope the type's policy names (its parent when it names none)? */
18
+ const takenProbe = (resolved: Resolved, scope: PlanningScope, parent: string | undefined): (code: string) => Promise<boolean> => {
19
+ const base = scopeCriteria(scope, resolved.type.type, parent, resolved.type.code)
17
20
 
18
- return async (code: string): Promise<boolean> =>
19
- await resolved.store.cards.count({ ...base, code } as Criteria<Workcard>) > 0
20
- }
21
+ return async (code: string): Promise<boolean> =>
22
+ await resolved.store.cards.count({ ...base, code } as Criteria<Workcard>) > 0
23
+ }
21
24
 
22
- /**
23
- * Step 8: the code a new card gets, or the check a changed code passes.
24
- *
25
- * A caller-supplied code is checked for uniqueness; otherwise the plugins' `mintCode` chain answers
26
- * (first answer wins, and is checked too); otherwise the type's policy mints one — a slug policy
27
- * derives it from the title. A type with no policy and no supplied code gets none.
28
- *
29
- * @throws {CodeTaken}
30
- */
31
- export const assignCode = async (
32
- exec: TransitionExecution,
33
- resolved: Resolved,
34
- scope: PlanningScope,
35
- registry: PluginRegistry,
36
- contextOf: (plugin: PlanningPlugin) => PlanningExecContext
37
- ): Promise<TransitionExecution> => {
38
- const policy = resolved.type.code
25
+ const assignCode = async (
26
+ exec: TransitionExecution,
27
+ resolved: Resolved,
28
+ scope: PlanningScope,
29
+ registry: PluginRegistry,
30
+ contextOf: (plugin: PlanningPlugin) => PlanningExecContext
31
+ ): Promise<TransitionExecution> => {
32
+ const policy = resolved.type.code
39
33
 
40
- if (exec.action !== TransitionAction.Create) {
41
- const code = exec.changes?.code
42
- if (code != null && code !== resolved.card?.code
43
- && await takenProbe(resolved, scope, exec.changes?.parent ?? resolved.card?.parent)(code)) {
44
- throw new CodeTaken(code)
34
+ if (exec.action !== TransitionAction.Create) {
35
+ const code = exec.changes?.code
36
+ if (code != null && code !== resolved.card?.code
37
+ && await takenProbe(resolved, scope, exec.changes?.parent ?? resolved.card?.parent)(code)) {
38
+ throw new CodeTaken(code)
39
+ }
40
+ return exec
45
41
  }
46
- return exec
47
- }
48
42
 
49
- const draft = exec.card as WorkcardDraft
50
- const taken = takenProbe(resolved, scope, draft.parent)
51
- if (draft.code != null && draft.code !== '') {
52
- if (await taken(draft.code)) {
53
- throw new CodeTaken(draft.code)
43
+ const draft = exec.card as WorkcardDraft
44
+ const taken = takenProbe(resolved, scope, draft.parent)
45
+ if (draft.code != null && draft.code !== '') {
46
+ if (await taken(draft.code)) {
47
+ throw new CodeTaken(draft.code)
48
+ }
49
+ return exec
50
+ }
51
+ if (policy == null) {
52
+ return exec
54
53
  }
55
- return exec
56
- }
57
- if (policy == null) {
58
- return exec
59
- }
60
54
 
61
- let code = await registry.mintCode(draft, taken, contextOf)
62
- if (code != null) {
63
- if (await taken(code)) {
64
- throw new CodeTaken(code)
55
+ let code = await registry.mintCode(draft, taken, contextOf)
56
+ if (code != null) {
57
+ if (await taken(code)) {
58
+ throw new CodeTaken(code)
59
+ }
60
+ } else {
61
+ const count = policy.style === CodeStyle.Sequential
62
+ ? await resolved.store.cards.count(scopeCriteria(scope, draft.type, draft.parent, policy))
63
+ : undefined
64
+ code = await codeHelper.mintCode(policy, taken, CODE_MINT_ATTEMPTS, {
65
+ ...(policy.style === CodeStyle.Slug ? { seed: draft.title } : {}),
66
+ ...(count != null ? { count } : {}),
67
+ })
65
68
  }
66
- } else {
67
- const count = policy.style === CodeStyle.Sequential
68
- ? await resolved.store.cards.count(scopeCriteria(scope, draft.type, draft.parent, policy))
69
- : undefined
70
- code = await mintCode(policy, taken, CODE_MINT_ATTEMPTS, {
71
- ...(policy.style === CodeStyle.Slug ? { seed: draft.title } : {}),
72
- ...(count != null ? { count } : {}),
73
- })
69
+
70
+ return { ...exec, card: { ...draft, code } }
74
71
  }
75
72
 
76
- return { ...exec, card: { ...draft, code } }
73
+ return { assignCode }
77
74
  }
75
+
76
+ export const codeUtils = createCodeUtils()
@@ -0,0 +1,33 @@
1
+ import type { PlanningScope, TransitionExecution } from '@owlmeans/planning'
2
+
3
+ /** Who owns a card: `createdBy`, stamped from the scope on a create and fixed after it. */
4
+ export interface CreatorHelper {
5
+ /**
6
+ * The subject a create made under `scope` is stamped with as its `createdBy`: the scope's
7
+ * `profileId`, else its `userId`, else the same two of its `actor` — the authenticated subject an
8
+ * HTTP scope carries, in the same format. `undefined` for a scope that names nobody (a service or a
9
+ * system scope).
10
+ */
11
+ creatorOf: (scope: PlanningScope) => string | undefined
12
+ /**
13
+ * A create's draft with `createdBy` defaulted to {@link CreatorHelper.creatorOf} the scope. A
14
+ * `createdBy` the caller supplied wins; a scope that names nobody leaves the draft as it is;
15
+ * anything but a create is returned untouched. Never mutates `exec`.
16
+ *
17
+ * The executor applies it to every execution, so an in-process facade create and a wire create
18
+ * stamp the same value.
19
+ */
20
+ withCreator: (exec: TransitionExecution, scope: PlanningScope) => TransitionExecution
21
+ /**
22
+ * Refuse an execution that names `createdBy` in `changes` or `unset`, whatever the action — a
23
+ * create included, whose owner is its draft's. After the create it never moves, so an ownership
24
+ * check (`card.createdBy === profileId`) cannot be bypassed by anyone allowed to update the card.
25
+ *
26
+ * The executor runs it with the immutables of step 6, after the `before` chain, so a wire body, an
27
+ * in-process caller and a plugin are refused alike. It is the executor's own check, whatever
28
+ * `@owlmeans/planning` build `changesHelper.assertMutable` comes from.
29
+ *
30
+ * @throws {PlanningError} `planning:immutable:createdBy`
31
+ */
32
+ assertCreatorFixed: (exec: TransitionExecution) => void
33
+ }
@@ -1,51 +1,43 @@
1
1
  import { PlanningError, TransitionAction, type PlanningScope, type TransitionExecution } from '@owlmeans/planning'
2
2
  import { CREATOR } from './consts.local.js'
3
+ import type { CreatorHelper } from './creator/types.js'
3
4
 
4
5
  const named = (value?: string | null): value is string => value != null && value !== ''
5
6
 
6
- /**
7
- * The subject a create made under `scope` is stamped with as its `createdBy`: the scope's
8
- * `profileId`, else its `userId`, else the same two of its `actor` — the authenticated subject an
9
- * HTTP scope carries, in the same format. `undefined` for a scope that names nobody (a service or a
10
- * system scope).
11
- */
12
- export const creatorOf = (scope: PlanningScope): string | undefined =>
13
- [scope.profileId, scope.userId, scope.actor?.profileId, scope.actor?.userId].find(named)
14
-
15
- /**
16
- * A create's draft with `createdBy` defaulted to {@link creatorOf} the scope. A `createdBy` the
17
- * caller supplied wins; a scope that names nobody leaves the draft as it is; anything but a create
18
- * is returned untouched. Never mutates `exec`.
19
- *
20
- * The executor applies it to every execution, so an in-process facade create and a wire create
21
- * stamp the same value.
22
- */
23
- export const withCreator = (exec: TransitionExecution, scope: PlanningScope): TransitionExecution => {
24
- if (exec.action !== TransitionAction.Create || exec.card == null || typeof exec.card !== 'object') {
25
- return exec
7
+ export const createCreatorHelper = (): CreatorHelper => {
8
+ const creatorOf = (scope: PlanningScope): string | undefined =>
9
+ [scope.profileId, scope.userId, scope.actor?.profileId, scope.actor?.userId].find(named)
10
+
11
+ const withCreator = (exec: TransitionExecution, scope: PlanningScope): TransitionExecution => {
12
+ if (exec.action !== TransitionAction.Create || exec.card == null || typeof exec.card !== 'object') {
13
+ return exec
14
+ }
15
+ if (named(exec.card.createdBy)) {
16
+ return exec
17
+ }
18
+ const creator = creatorOf(scope)
19
+
20
+ return creator == null ? exec : { ...exec, card: { ...exec.card, createdBy: creator } }
26
21
  }
27
- if (named(exec.card.createdBy)) {
28
- return exec
22
+
23
+ const assertCreatorFixed = (exec: TransitionExecution): void => {
24
+ if ((exec.changes as Record<string, unknown> | undefined)?.[CREATOR] !== undefined
25
+ || (exec.unset ?? []).some(path => path.split('.')[0] === CREATOR)) {
26
+ throw new PlanningError(`immutable:${CREATOR}`)
27
+ }
29
28
  }
30
- const creator = creatorOf(scope)
31
29
 
32
- return creator == null ? exec : { ...exec, card: { ...exec.card, createdBy: creator } }
30
+ return { creatorOf, withCreator, assertCreatorFixed }
33
31
  }
34
32
 
35
- /**
36
- * Refuse an execution that names `createdBy` in `changes` or `unset`, whatever the action — a
37
- * create included, whose owner is its draft's. After the create it never moves, so an ownership
38
- * check (`card.createdBy === profileId`) cannot be bypassed by anyone allowed to update the card.
39
- *
40
- * The executor runs it with the immutables of step 6, after the `before` chain, so a wire body, an
41
- * in-process caller and a plugin are refused alike. It is the executor's own check, whatever
42
- * `@owlmeans/planning` build `assertMutable` comes from.
43
- *
44
- * @throws {PlanningError} `planning:immutable:createdBy`
45
- */
46
- export const assertCreatorFixed = (exec: TransitionExecution): void => {
47
- if ((exec.changes as Record<string, unknown> | undefined)?.[CREATOR] !== undefined
48
- || (exec.unset ?? []).some(path => path.split('.')[0] === CREATOR)) {
49
- throw new PlanningError(`immutable:${CREATOR}`)
50
- }
51
- }
33
+ export const creatorHelper = createCreatorHelper()
34
+
35
+ /** @deprecated compat:factory-refactor — use `creatorHelper.creatorOf(…)` */
36
+ export const creatorOf = (scope: PlanningScope): string | undefined => creatorHelper.creatorOf(scope)
37
+
38
+ /** @deprecated compat:factory-refactor — use `creatorHelper.withCreator(…)` */
39
+ export const withCreator = (exec: TransitionExecution, scope: PlanningScope): TransitionExecution =>
40
+ creatorHelper.withCreator(exec, scope)
41
+
42
+ /** @deprecated compat:factory-refactor — use `creatorHelper.assertCreatorFixed(…)` */
43
+ export const assertCreatorFixed = (exec: TransitionExecution): void => creatorHelper.assertCreatorFixed(exec)
@@ -0,0 +1,30 @@
1
+ import type { PlanningFacade, PlanningSchemaRegistry, TransitionExecution, Workcard, WorkcardKind } from '@owlmeans/planning'
2
+ import type { Resolved } from '../types.js'
3
+
4
+ /** Steps 1 and 4 of the write path, over one service runtime. */
5
+ export interface ResolveUtils {
6
+ /**
7
+ * Step 1: a deep copy with trimmed text, `parents ∋ parent` on a draft, and no duplicate `unset`
8
+ * paths. The caller's object is never touched.
9
+ */
10
+ normalizeExecution: (input: TransitionExecution) => TransitionExecution
11
+ /**
12
+ * The parent type's rule for a child of `kind`/`type`: a project lists the `cardTypes` (and
13
+ * `projectTypes`) it admits, and one declaring `scopedCardTypes` also admits a card type its layer
14
+ * (`view`, the parent project's resolved schemas) defines as data. Project types are code's alone.
15
+ *
16
+ * @throws {CardTypeNotAllowed}
17
+ */
18
+ assertChildAllowed: (parent: Workcard, kind: WorkcardKind, type: string, view?: PlanningSchemaRegistry) => void
19
+ /** The layer a parent project admits data-defined children through — `undefined` without a schema port. */
20
+ childViewOf: (entityId: string, parent: Workcard) => Promise<PlanningSchemaRegistry | undefined>
21
+ /**
22
+ * Step 4: the card, its type and flow, its parent and — for a specification — its slot.
23
+ *
24
+ * @throws {WorkcardNotFound | PlanningScopeMismatch | UnknownWorkcardType | UnknownStatusFlow}
25
+ * @throws {ParentNotFound | CardTypeNotAllowed | PlanningError}
26
+ */
27
+ resolveExecution: (facade: PlanningFacade, exec: TransitionExecution) => Promise<Resolved>
28
+ /** The project a transition is filed under: a project's own id, else its parent's project. */
29
+ projectFor: (resolved: Resolved, exec: TransitionExecution, cardId: string) => string | undefined
30
+ }