@owlmeans/server-planning 0.1.18-rc.9 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (438) hide show
  1. package/README.md +92 -19
  2. package/agent-meta/manifest.json +105 -4
  3. package/agent-meta/skills/server-planning/SKILL.md +89 -138
  4. package/agent-meta/skills/server-planning/parts/auxiliary.md +54 -0
  5. package/agent-meta/skills/server-planning/parts/executor.md +82 -0
  6. package/agent-meta/skills/server-planning/parts/schemas.md +26 -0
  7. package/agent-meta/skills/server-planning/parts/serving.md +88 -0
  8. package/agent-meta/skills/server-planning/parts/stores.md +91 -0
  9. package/build/actions/apply-schema-request.d.ts +8 -0
  10. package/build/actions/apply-schema-request.d.ts.map +1 -0
  11. package/build/actions/apply-schema-request.js +33 -0
  12. package/build/actions/apply-schema-request.js.map +1 -0
  13. package/build/actions/commit.d.ts +3 -3
  14. package/build/actions/commit.d.ts.map +1 -1
  15. package/build/actions/commit.js +5 -4
  16. package/build/actions/commit.js.map +1 -1
  17. package/build/actions/define-schemas.d.ts +13 -0
  18. package/build/actions/define-schemas.d.ts.map +1 -0
  19. package/build/actions/define-schemas.js +28 -0
  20. package/build/actions/define-schemas.js.map +1 -0
  21. package/build/actions/execute.d.ts +11 -10
  22. package/build/actions/execute.d.ts.map +1 -1
  23. package/build/actions/execute.js +17 -27
  24. package/build/actions/execute.js.map +1 -1
  25. package/build/actions/execution/types.d.ts +34 -0
  26. package/build/actions/execution/types.d.ts.map +1 -0
  27. package/build/actions/execution/types.js +2 -0
  28. package/build/actions/execution/types.js.map +1 -0
  29. package/build/actions/execution.d.ts +4 -0
  30. package/build/actions/execution.d.ts.map +1 -0
  31. package/build/actions/execution.js +88 -0
  32. package/build/actions/execution.js.map +1 -0
  33. package/build/actions/get-card.d.ts +6 -0
  34. package/build/actions/get-card.d.ts.map +1 -0
  35. package/build/actions/get-card.js +9 -0
  36. package/build/actions/get-card.js.map +1 -0
  37. package/build/actions/get-specification.d.ts +6 -0
  38. package/build/actions/get-specification.d.ts.map +1 -0
  39. package/build/actions/get-specification.js +9 -0
  40. package/build/actions/get-specification.js.map +1 -0
  41. package/build/actions/index.d.ts +12 -3
  42. package/build/actions/index.d.ts.map +1 -1
  43. package/build/actions/index.js +11 -3
  44. package/build/actions/index.js.map +1 -1
  45. package/build/actions/links.d.ts +3 -3
  46. package/build/actions/links.d.ts.map +1 -1
  47. package/build/actions/links.js +6 -5
  48. package/build/actions/links.js.map +1 -1
  49. package/build/actions/list-card-specifications.d.ts +6 -0
  50. package/build/actions/list-card-specifications.d.ts.map +1 -0
  51. package/build/actions/list-card-specifications.js +11 -0
  52. package/build/actions/list-card-specifications.js.map +1 -0
  53. package/build/actions/list-card-transitions.d.ts +6 -0
  54. package/build/actions/list-card-transitions.d.ts.map +1 -0
  55. package/build/actions/list-card-transitions.js +11 -0
  56. package/build/actions/list-card-transitions.js.map +1 -0
  57. package/build/actions/list-cards.d.ts +6 -0
  58. package/build/actions/list-cards.d.ts.map +1 -0
  59. package/build/actions/list-cards.js +10 -0
  60. package/build/actions/list-cards.js.map +1 -0
  61. package/build/actions/list-schemas.d.ts +13 -0
  62. package/build/actions/list-schemas.d.ts.map +1 -0
  63. package/build/actions/list-schemas.js +20 -0
  64. package/build/actions/list-schemas.js.map +1 -0
  65. package/build/actions/list-specification-revisions.d.ts +6 -0
  66. package/build/actions/list-specification-revisions.d.ts.map +1 -0
  67. package/build/actions/list-specification-revisions.js +10 -0
  68. package/build/actions/list-specification-revisions.js.map +1 -0
  69. package/build/actions/resources.d.ts +5 -0
  70. package/build/actions/resources.d.ts.map +1 -0
  71. package/build/actions/resources.js +111 -0
  72. package/build/actions/resources.js.map +1 -0
  73. package/build/actions/summarize-cards.d.ts +6 -0
  74. package/build/actions/summarize-cards.d.ts.map +1 -0
  75. package/build/actions/summarize-cards.js +11 -0
  76. package/build/actions/summarize-cards.js.map +1 -0
  77. package/build/actions/transitions.d.ts +3 -3
  78. package/build/actions/transitions.d.ts.map +1 -1
  79. package/build/actions/transitions.js +4 -3
  80. package/build/actions/transitions.js.map +1 -1
  81. package/build/actions/types.d.ts +6 -0
  82. package/build/actions/types.d.ts.map +1 -0
  83. package/build/actions/types.js +2 -0
  84. package/build/actions/types.js.map +1 -0
  85. package/build/actions/watch.d.ts +2 -2
  86. package/build/actions/watch.d.ts.map +1 -1
  87. package/build/actions/watch.js +8 -6
  88. package/build/actions/watch.js.map +1 -1
  89. package/build/conformance/assert/types.d.ts +19 -0
  90. package/build/conformance/assert/types.d.ts.map +1 -0
  91. package/build/conformance/assert/types.js +2 -0
  92. package/build/conformance/assert/types.js.map +1 -0
  93. package/build/conformance/assert.d.ts +15 -0
  94. package/build/conformance/assert.d.ts.map +1 -0
  95. package/build/conformance/assert.js +52 -0
  96. package/build/conformance/assert.js.map +1 -0
  97. package/build/conformance/cases.d.ts +7 -0
  98. package/build/conformance/cases.d.ts.map +1 -0
  99. package/build/conformance/cases.js +544 -0
  100. package/build/conformance/cases.js.map +1 -0
  101. package/build/conformance/clock.d.ts +6 -0
  102. package/build/conformance/clock.d.ts.map +1 -0
  103. package/build/conformance/clock.js +12 -0
  104. package/build/conformance/clock.js.map +1 -0
  105. package/build/conformance/consts.d.ts +30 -0
  106. package/build/conformance/consts.d.ts.map +1 -0
  107. package/build/conformance/consts.js +159 -0
  108. package/build/conformance/consts.js.map +1 -0
  109. package/build/conformance/errors.d.ts +5 -0
  110. package/build/conformance/errors.d.ts.map +1 -0
  111. package/build/conformance/errors.js +8 -0
  112. package/build/conformance/errors.js.map +1 -0
  113. package/build/conformance/fixtures/types.d.ts +13 -0
  114. package/build/conformance/fixtures/types.d.ts.map +1 -0
  115. package/build/conformance/fixtures/types.js +2 -0
  116. package/build/conformance/fixtures/types.js.map +1 -0
  117. package/build/conformance/fixtures.d.ts +14 -0
  118. package/build/conformance/fixtures.d.ts.map +1 -0
  119. package/build/conformance/fixtures.js +26 -0
  120. package/build/conformance/fixtures.js.map +1 -0
  121. package/build/conformance/index.d.ts +15 -0
  122. package/build/conformance/index.d.ts.map +1 -0
  123. package/build/conformance/index.js +12 -0
  124. package/build/conformance/index.js.map +1 -0
  125. package/build/conformance/resources.d.ts +4 -0
  126. package/build/conformance/resources.d.ts.map +1 -0
  127. package/build/conformance/resources.js +228 -0
  128. package/build/conformance/resources.js.map +1 -0
  129. package/build/conformance/types.d.ts +18 -0
  130. package/build/conformance/types.d.ts.map +1 -0
  131. package/build/conformance/types.js +2 -0
  132. package/build/conformance/types.js.map +1 -0
  133. package/build/consts.d.ts +5 -0
  134. package/build/consts.d.ts.map +1 -1
  135. package/build/consts.js +5 -0
  136. package/build/consts.js.map +1 -1
  137. package/build/definitions.d.ts +18 -0
  138. package/build/definitions.d.ts.map +1 -0
  139. package/build/definitions.js +225 -0
  140. package/build/definitions.js.map +1 -0
  141. package/build/executor/changes/types.d.ts +36 -0
  142. package/build/executor/changes/types.d.ts.map +1 -0
  143. package/build/executor/changes/types.js +2 -0
  144. package/build/executor/changes/types.js.map +1 -0
  145. package/build/executor/changes.d.ts +3 -30
  146. package/build/executor/changes.d.ts.map +1 -1
  147. package/build/executor/changes.js +42 -51
  148. package/build/executor/changes.js.map +1 -1
  149. package/build/executor/code/types.d.ts +17 -0
  150. package/build/executor/code/types.d.ts.map +1 -0
  151. package/build/executor/code/types.js +2 -0
  152. package/build/executor/code/types.js.map +1 -0
  153. package/build/executor/code.d.ts +3 -14
  154. package/build/executor/code.d.ts.map +1 -1
  155. package/build/executor/code.js +50 -54
  156. package/build/executor/code.js.map +1 -1
  157. package/build/executor/consts.local.d.ts +5 -0
  158. package/build/executor/consts.local.d.ts.map +1 -0
  159. package/build/executor/consts.local.js +6 -0
  160. package/build/executor/consts.local.js.map +1 -0
  161. package/build/executor/creator/types.d.ts +33 -0
  162. package/build/executor/creator/types.d.ts.map +1 -0
  163. package/build/executor/creator/types.js +2 -0
  164. package/build/executor/creator/types.js.map +1 -0
  165. package/build/executor/creator.d.ts +11 -0
  166. package/build/executor/creator.d.ts.map +1 -0
  167. package/build/executor/creator.js +32 -0
  168. package/build/executor/creator.js.map +1 -0
  169. package/build/executor/resolve/types.d.ts +30 -0
  170. package/build/executor/resolve/types.d.ts.map +1 -0
  171. package/build/executor/resolve/types.js +2 -0
  172. package/build/executor/resolve/types.js.map +1 -0
  173. package/build/executor/resolve.d.ts +5 -34
  174. package/build/executor/resolve.d.ts.map +1 -1
  175. package/build/executor/resolve.js +169 -110
  176. package/build/executor/resolve.js.map +1 -1
  177. package/build/executor/types.d.ts +23 -0
  178. package/build/executor/types.d.ts.map +1 -0
  179. package/build/executor/types.js +2 -0
  180. package/build/executor/types.js.map +1 -0
  181. package/build/executor/validate.d.ts +6 -5
  182. package/build/executor/validate.d.ts.map +1 -1
  183. package/build/executor/validate.js +38 -27
  184. package/build/executor/validate.js.map +1 -1
  185. package/build/executor.d.ts +11 -7
  186. package/build/executor.d.ts.map +1 -1
  187. package/build/executor.js +27 -21
  188. package/build/executor.js.map +1 -1
  189. package/build/facade.d.ts +20 -2
  190. package/build/facade.d.ts.map +1 -1
  191. package/build/facade.js +144 -22
  192. package/build/facade.js.map +1 -1
  193. package/build/helper.d.ts +5 -9
  194. package/build/helper.d.ts.map +1 -1
  195. package/build/helper.js +12 -11
  196. package/build/helper.js.map +1 -1
  197. package/build/hierarchy/types.d.ts +20 -0
  198. package/build/hierarchy/types.d.ts.map +1 -0
  199. package/build/hierarchy/types.js +2 -0
  200. package/build/hierarchy/types.js.map +1 -0
  201. package/build/hierarchy.d.ts +5 -0
  202. package/build/hierarchy.d.ts.map +1 -0
  203. package/build/hierarchy.js +133 -0
  204. package/build/hierarchy.js.map +1 -0
  205. package/build/index.d.ts +1 -0
  206. package/build/index.d.ts.map +1 -1
  207. package/build/index.js +1 -0
  208. package/build/index.js.map +1 -1
  209. package/build/invariants/types.d.ts +6 -0
  210. package/build/invariants/types.d.ts.map +1 -0
  211. package/build/invariants/types.js +2 -0
  212. package/build/invariants/types.js.map +1 -0
  213. package/build/invariants.d.ts +5 -0
  214. package/build/invariants.d.ts.map +1 -0
  215. package/build/invariants.js +126 -0
  216. package/build/invariants.js.map +1 -0
  217. package/build/projection.d.ts +4 -10
  218. package/build/projection.d.ts.map +1 -1
  219. package/build/projection.js +7 -5
  220. package/build/projection.js.map +1 -1
  221. package/build/queue/consts.d.ts +5 -0
  222. package/build/queue/consts.d.ts.map +1 -0
  223. package/build/queue/consts.js +5 -0
  224. package/build/queue/consts.js.map +1 -0
  225. package/build/queue/declare.d.ts +5 -0
  226. package/build/queue/declare.d.ts.map +1 -0
  227. package/build/queue/declare.js +5 -0
  228. package/build/queue/declare.js.map +1 -0
  229. package/build/queue/index.d.ts +3 -18
  230. package/build/queue/index.d.ts.map +1 -1
  231. package/build/queue/index.js +2 -7
  232. package/build/queue/index.js.map +1 -1
  233. package/build/queue/types.d.ts +13 -0
  234. package/build/queue/types.d.ts.map +1 -0
  235. package/build/queue/types.js +2 -0
  236. package/build/queue/types.js.map +1 -0
  237. package/build/registry.d.ts +5 -22
  238. package/build/registry.d.ts.map +1 -1
  239. package/build/registry.js +16 -10
  240. package/build/registry.js.map +1 -1
  241. package/build/resources.d.ts +5 -0
  242. package/build/resources.d.ts.map +1 -0
  243. package/build/resources.js +287 -0
  244. package/build/resources.js.map +1 -0
  245. package/build/service.d.ts +3 -16
  246. package/build/service.d.ts.map +1 -1
  247. package/build/service.js +12 -0
  248. package/build/service.js.map +1 -1
  249. package/build/store/commits.d.ts.map +1 -1
  250. package/build/store/commits.js +3 -1
  251. package/build/store/commits.js.map +1 -1
  252. package/build/store/composite.d.ts +1 -0
  253. package/build/store/composite.d.ts.map +1 -1
  254. package/build/store/composite.js +7 -2
  255. package/build/store/composite.js.map +1 -1
  256. package/build/store/consts.local.d.ts +2 -0
  257. package/build/store/consts.local.d.ts.map +1 -0
  258. package/build/store/consts.local.js +2 -0
  259. package/build/store/consts.local.js.map +1 -0
  260. package/build/store/fold/types.d.ts +43 -0
  261. package/build/store/fold/types.d.ts.map +1 -0
  262. package/build/store/fold/types.js +2 -0
  263. package/build/store/fold/types.js.map +1 -0
  264. package/build/store/fold.d.ts +8 -30
  265. package/build/store/fold.d.ts.map +1 -1
  266. package/build/store/fold.js +190 -198
  267. package/build/store/fold.js.map +1 -1
  268. package/build/store/index.d.ts +1 -0
  269. package/build/store/index.d.ts.map +1 -1
  270. package/build/store/index.js.map +1 -1
  271. package/build/store/memory.d.ts +12 -0
  272. package/build/store/memory.d.ts.map +1 -1
  273. package/build/store/memory.js +0 -0
  274. package/build/store/memory.js.map +1 -1
  275. package/build/store/types.d.ts +15 -1
  276. package/build/store/types.d.ts.map +1 -1
  277. package/build/store/types.local.d.ts +5 -0
  278. package/build/store/types.local.d.ts.map +1 -0
  279. package/build/store/types.local.js +2 -0
  280. package/build/store/types.local.js.map +1 -0
  281. package/build/types.d.ts +126 -2
  282. package/build/types.d.ts.map +1 -1
  283. package/build/types.local.d.ts +7 -0
  284. package/build/types.local.d.ts.map +1 -0
  285. package/build/types.local.js +2 -0
  286. package/build/types.local.js.map +1 -0
  287. package/build/utils/access/types.d.ts +30 -0
  288. package/build/utils/access/types.d.ts.map +1 -0
  289. package/build/utils/access/types.js +2 -0
  290. package/build/utils/access/types.js.map +1 -0
  291. package/build/utils/access.d.ts +4 -0
  292. package/build/utils/access.d.ts.map +1 -0
  293. package/build/utils/access.js +28 -0
  294. package/build/utils/access.js.map +1 -0
  295. package/build/utils/guard/types.d.ts +23 -0
  296. package/build/utils/guard/types.d.ts.map +1 -0
  297. package/build/utils/guard/types.js +2 -0
  298. package/build/utils/guard/types.js.map +1 -0
  299. package/build/utils/guard.d.ts +7 -10
  300. package/build/utils/guard.d.ts.map +1 -1
  301. package/build/utils/guard.js +36 -32
  302. package/build/utils/guard.js.map +1 -1
  303. package/build/utils/handler/types.d.ts +26 -0
  304. package/build/utils/handler/types.d.ts.map +1 -0
  305. package/build/utils/handler/types.js +2 -0
  306. package/build/utils/handler/types.js.map +1 -0
  307. package/build/utils/handler.d.ts +6 -0
  308. package/build/utils/handler.d.ts.map +1 -0
  309. package/build/utils/handler.js +21 -0
  310. package/build/utils/handler.js.map +1 -0
  311. package/build/utils/index.d.ts +8 -0
  312. package/build/utils/index.d.ts.map +1 -1
  313. package/build/utils/index.js +3 -0
  314. package/build/utils/index.js.map +1 -1
  315. package/build/utils/request/types.d.ts +23 -0
  316. package/build/utils/request/types.d.ts.map +1 -0
  317. package/build/utils/request/types.js +2 -0
  318. package/build/utils/request/types.js.map +1 -0
  319. package/build/utils/request.d.ts +4 -0
  320. package/build/utils/request.d.ts.map +1 -0
  321. package/build/utils/request.js +27 -0
  322. package/build/utils/request.js.map +1 -0
  323. package/build/utils/scope.d.ts +17 -13
  324. package/build/utils/scope.d.ts.map +1 -1
  325. package/build/utils/scope.js +21 -35
  326. package/build/utils/scope.js.map +1 -1
  327. package/build/utils/types.d.ts +8 -0
  328. package/build/utils/types.d.ts.map +1 -0
  329. package/build/utils/types.js +2 -0
  330. package/build/utils/types.js.map +1 -0
  331. package/package.json +30 -22
  332. package/src/actions/apply-schema-request.ts +36 -0
  333. package/src/actions/commit.ts +7 -5
  334. package/src/actions/define-schemas.ts +34 -0
  335. package/src/actions/execute.ts +32 -34
  336. package/src/actions/execution/types.ts +34 -0
  337. package/src/actions/execution.ts +100 -0
  338. package/src/actions/get-card.ts +14 -0
  339. package/src/actions/get-specification.ts +14 -0
  340. package/src/actions/index.ts +13 -3
  341. package/src/actions/links.ts +8 -6
  342. package/src/actions/list-card-specifications.ts +15 -0
  343. package/src/actions/list-card-transitions.ts +15 -0
  344. package/src/actions/list-cards.ts +14 -0
  345. package/src/actions/list-schemas.ts +29 -0
  346. package/src/actions/list-specification-revisions.ts +15 -0
  347. package/src/actions/resources.ts +79 -0
  348. package/src/actions/summarize-cards.ts +15 -0
  349. package/src/actions/transitions.ts +6 -4
  350. package/src/actions/types.ts +5 -0
  351. package/src/actions/watch.ts +11 -7
  352. package/src/conformance/assert/types.ts +16 -0
  353. package/src/conformance/assert.ts +67 -0
  354. package/src/conformance/cases.ts +631 -0
  355. package/src/conformance/clock.ts +11 -0
  356. package/src/conformance/consts.ts +165 -0
  357. package/src/conformance/errors.ts +7 -0
  358. package/src/conformance/fixtures/types.ts +11 -0
  359. package/src/conformance/fixtures.ts +46 -0
  360. package/src/conformance/index.ts +14 -0
  361. package/src/conformance/resources.ts +227 -0
  362. package/src/conformance/types.ts +20 -0
  363. package/src/consts.ts +6 -0
  364. package/src/definitions.ts +264 -0
  365. package/src/executor/changes/types.ts +38 -0
  366. package/src/executor/changes.ts +49 -68
  367. package/src/executor/code/types.ts +23 -0
  368. package/src/executor/code.ts +62 -69
  369. package/src/executor/consts.local.ts +8 -0
  370. package/src/executor/creator/types.ts +33 -0
  371. package/src/executor/creator.ts +43 -0
  372. package/src/executor/resolve/types.ts +30 -0
  373. package/src/executor/resolve.ts +181 -139
  374. package/src/executor/types.ts +23 -0
  375. package/src/executor/validate.ts +40 -38
  376. package/src/executor.ts +33 -27
  377. package/src/facade.ts +132 -29
  378. package/src/helper.ts +12 -11
  379. package/src/hierarchy/types.ts +20 -0
  380. package/src/hierarchy.ts +113 -0
  381. package/src/index.ts +1 -0
  382. package/src/invariants/types.ts +6 -0
  383. package/src/invariants.ts +106 -0
  384. package/src/projection.ts +12 -18
  385. package/src/queue/consts.ts +5 -0
  386. package/src/queue/declare.ts +7 -0
  387. package/src/queue/index.ts +3 -25
  388. package/src/queue/types.ts +14 -0
  389. package/src/registry.ts +18 -36
  390. package/src/resources.ts +256 -0
  391. package/src/service.ts +16 -20
  392. package/src/store/commits.ts +5 -4
  393. package/src/store/composite.ts +7 -2
  394. package/src/store/consts.local.ts +1 -0
  395. package/src/store/fold/types.ts +44 -0
  396. package/src/store/fold.ts +214 -212
  397. package/src/store/index.ts +1 -0
  398. package/src/store/memory.ts +0 -0
  399. package/src/store/types.local.ts +3 -0
  400. package/src/store/types.ts +16 -1
  401. package/src/types.local.ts +7 -0
  402. package/src/types.ts +139 -6
  403. package/src/utils/access/types.ts +30 -0
  404. package/src/utils/access.ts +33 -0
  405. package/src/utils/guard/types.ts +21 -0
  406. package/src/utils/guard.ts +44 -31
  407. package/src/utils/handler/types.ts +28 -0
  408. package/src/utils/handler.ts +35 -0
  409. package/src/utils/index.ts +9 -0
  410. package/src/utils/request/types.ts +23 -0
  411. package/src/utils/request.ts +40 -0
  412. package/src/utils/scope.ts +39 -42
  413. package/src/utils/types.ts +8 -0
  414. package/tests/access.spec.ts +160 -0
  415. package/tests/app.spec.ts +112 -0
  416. package/tests/conformance.spec.ts +47 -0
  417. package/tests/create-example.spec.ts +202 -0
  418. package/tests/creator.spec.ts +257 -0
  419. package/tests/entrypoints.spec.ts +5 -0
  420. package/tests/memory-store.spec.ts +52 -0
  421. package/tests/narrowing.spec.ts +94 -0
  422. package/tests/plugins.spec.ts +4 -3
  423. package/tests/writes.spec.ts +140 -0
  424. package/build/actions/cards.d.ts +0 -16
  425. package/build/actions/cards.d.ts.map +0 -1
  426. package/build/actions/cards.js +0 -32
  427. package/build/actions/cards.js.map +0 -1
  428. package/build/actions/schemas.d.ts +0 -6
  429. package/build/actions/schemas.d.ts.map +0 -1
  430. package/build/actions/schemas.js +0 -9
  431. package/build/actions/schemas.js.map +0 -1
  432. package/build/actions/specs.d.ts +0 -10
  433. package/build/actions/specs.d.ts.map +0 -1
  434. package/build/actions/specs.js +0 -14
  435. package/build/actions/specs.js.map +0 -1
  436. package/src/actions/cards.ts +0 -52
  437. package/src/actions/schemas.ts +0 -15
  438. package/src/actions/specs.ts +0 -23
@@ -0,0 +1,264 @@
1
+ import { cardHelper, PlanningSchemaKind, PlanningUnsupported, SchemaConflict, SchemaInUse, scopedSchemaHelper, UnknownStatusFlow, UnknownWorkcardType, WorkcardNotFound, type AssigneeTypeSchema, type PlanningDefinitions, type PlanningFacade, type PlanningSchemaRegistry, type SchemaDeclarations, type SchemaStore, type SchemaWriteOptions, type ScopedSchemaRecord, type ScopedSchemaRegistry, type StatusFlowSchema, type TransitionExecution, type Unsubscribe, type WorkcardTypeSchema } from '@owlmeans/planning'
2
+ import { DEFAULT_SCHEMA_VIEWS } from './consts.js'
3
+ import { changesUtils } from './executor/changes.js'
4
+ import type { PlanningRuntime, SchemaViews, SchemaViewsOptions } from './types.js'
5
+ import type { Planned } from './types.local.js'
6
+
7
+ const isoNow = (): string => new Date().toISOString()
8
+
9
+ /**
10
+ * The resolved layers of one planning service, cached per (organization, project) and keyed by the
11
+ * organization's schema revision and the code registry's version — a write anywhere, in any
12
+ * process, moves the revision the next lookup reads, so a cached layer is never served stale. A
13
+ * store that can `watch` evicts an organization's layers as soon as it hears of a write.
14
+ */
15
+ export const makeSchemaViews = (opts: SchemaViewsOptions): SchemaViews => {
16
+ const limit = opts.limit ?? DEFAULT_SCHEMA_VIEWS
17
+ const cache = new Map<string, { revision: number, version: number, view: ScopedSchemaRegistry }>()
18
+ let watching: { store: SchemaStore, release: Unsubscribe } | undefined
19
+
20
+ const keyOf = (entityId: string, project?: string): string => `${entityId}\u0000${project ?? ''}`
21
+
22
+ const evict = (entityId: string): void => {
23
+ const prefix = `${entityId}\u0000`
24
+ for (const key of [...cache.keys()]) {
25
+ if (key.startsWith(prefix)) {
26
+ cache.delete(key)
27
+ }
28
+ }
29
+ }
30
+
31
+ const scoped: SchemaViews['scoped'] = async (entityId, project) => {
32
+ const store = opts.port()
33
+ if (store == null) {
34
+ throw new PlanningUnsupported('schemas')
35
+ }
36
+ if (store.watch != null && watching?.store !== store) {
37
+ watching?.release()
38
+ watching = { store, release: store.watch(evict) }
39
+ }
40
+
41
+ const revision = await store.revision(entityId)
42
+ const version = opts.version()
43
+ const key = keyOf(entityId, project)
44
+ const hit = cache.get(key)
45
+ if (hit != null && hit.revision === revision && hit.version === version) {
46
+ cache.delete(key)
47
+ cache.set(key, hit)
48
+ return hit.view
49
+ }
50
+
51
+ const records = [
52
+ ...await store.list({ entityId, project: null }),
53
+ ...(project != null ? await store.list({ entityId, project }) : []),
54
+ ]
55
+ const view = scopedSchemaHelper.scopedRegistryOf(scopedSchemaHelper.resolveScopedBundle(opts.code().bundle(), records, { entityId, project, revision }))
56
+ cache.delete(key)
57
+ cache.set(key, { revision, version, view })
58
+ while (cache.size > limit) {
59
+ const oldest = cache.keys().next().value
60
+ if (oldest == null) {
61
+ break
62
+ }
63
+ cache.delete(oldest)
64
+ }
65
+
66
+ return view
67
+ }
68
+
69
+ return {
70
+ of: async (entityId, project) => opts.port() == null ? opts.code() : await scoped(entityId, project),
71
+ scoped,
72
+ }
73
+ }
74
+
75
+ const planOf = (declarations: SchemaDeclarations): Planned[] => [
76
+ ...(declarations.assigneeTypes ?? []).map(schema => ({ kind: PlanningSchemaKind.AssigneeType, key: schema.type, definition: schema })),
77
+ ...(declarations.flows ?? []).map(flow => ({ kind: PlanningSchemaKind.Flow, key: flow.id, definition: flow })),
78
+ ...(declarations.types ?? []).map(type => ({ kind: PlanningSchemaKind.Type, key: type.type, definition: type })),
79
+ ]
80
+
81
+ const layerKey = (kind: PlanningSchemaKind, key: string): string => `${kind}:${key}`
82
+
83
+ /**
84
+ * The data-defined schema surface of one scoped facade. Every write checks, before anything is
85
+ * stored: the project (a project card this scope can see), the seal of the code registry, the
86
+ * declaration's closed-form checks (a type's flows must resolve in its layer, counting the flows
87
+ * written beside it; a type's and a flow's `app` bag against the application's declarations) — and
88
+ * then writes through the port's compare-and-set.
89
+ */
90
+ export const makeDefinitions = (runtime: PlanningRuntime, facade: PlanningFacade): PlanningDefinitions => {
91
+ const scope = facade.scope
92
+ const entityId = scope.entityId
93
+ const now = runtime.options.now ?? isoNow
94
+
95
+ const port = (): SchemaStore => {
96
+ const store = runtime.schemaStore()
97
+ if (store == null) {
98
+ throw new PlanningUnsupported('schemas')
99
+ }
100
+ return store
101
+ }
102
+
103
+ const visible = async (project: string): Promise<boolean> => scope.projects == null || await facade.cards.load(project) != null
104
+
105
+ /** @throws {WorkcardNotFound} for anything but a project card this scope can see */
106
+ const assertProject = async (project?: string): Promise<void> => {
107
+ if (project == null) {
108
+ return
109
+ }
110
+ const card = await facade.cards.load(project)
111
+ if (card == null || !cardHelper.isProject(card)) {
112
+ throw new WorkcardNotFound(project)
113
+ }
114
+ }
115
+
116
+ const layerOf = async (project?: string): Promise<Map<string, ScopedSchemaRecord>> =>
117
+ new Map((await port().list({ entityId, project: project ?? null }))
118
+ .map(record => [layerKey(record.kind, record.key), record]))
119
+
120
+ /** Seal and closed-form checks for everything in the plan — nothing is written when one fails. */
121
+ const validate = async (plan: Planned[], project?: string): Promise<void> => {
122
+ const code = runtime.service().schemas
123
+ const app = code.app()
124
+ const layer = await runtime.scopedSchemas(entityId, project)
125
+ const pending = new Map(plan.filter(entry => entry.kind === PlanningSchemaKind.Flow)
126
+ .map(entry => [entry.key, entry.definition as StatusFlowSchema]))
127
+ const flows: Pick<PlanningSchemaRegistry, 'flow'> = { flow: id => pending.get(id) ?? layer.flow(id) }
128
+
129
+ for (const entry of plan) {
130
+ scopedSchemaHelper.assertOverridable(code, entry.kind, entry.key)
131
+ if (entry.kind === PlanningSchemaKind.AssigneeType) {
132
+ if (project != null) throw new PlanningUnsupported('assignee-schema:project-layer')
133
+ scopedSchemaHelper.assertAssigneeTypeSchema(entry.definition as AssigneeTypeSchema)
134
+ } else if (entry.kind === PlanningSchemaKind.Flow) {
135
+ scopedSchemaHelper.assertFlowSchema(entry.definition as StatusFlowSchema, app)
136
+ } else {
137
+ scopedSchemaHelper.assertTypeSchema(entry.definition as WorkcardTypeSchema, flows, app)
138
+ }
139
+ }
140
+ }
141
+
142
+ const recordOf = (
143
+ entry: Planned, version: number, project: string | undefined, current?: ScopedSchemaRecord, retired?: boolean
144
+ ): ScopedSchemaRecord => {
145
+ const at = now()
146
+ const by = changesUtils.actorOf(scope, {} as TransitionExecution)
147
+ return Object.fromEntries(Object.entries({
148
+ entityId,
149
+ project,
150
+ kind: entry.kind,
151
+ key: entry.key,
152
+ version,
153
+ definition: { ...structuredClone(entry.definition), version },
154
+ retired: retired === true ? true : undefined,
155
+ createdAt: current?.createdAt ?? at,
156
+ updatedAt: current != null ? at : undefined,
157
+ by: Object.keys(by).length > 0 ? by : undefined,
158
+ }).filter(([, value]) => value !== undefined)) as unknown as ScopedSchemaRecord
159
+ }
160
+
161
+ const unit = async <R>(run: () => Promise<R>): Promise<R> => {
162
+ const store = runtime.service().store()
163
+ return store.unit != null ? await store.unit(entityId, run) : await run()
164
+ }
165
+
166
+ const write = async (
167
+ plan: Planned[], opts: SchemaWriteOptions | undefined, versionOf: (entry: Planned, current?: ScopedSchemaRecord) => number | null
168
+ ): Promise<ScopedSchemaRecord[]> => await unit(async () => {
169
+ await assertProject(opts?.project)
170
+ const layer = await layerOf(opts?.project)
171
+ const todo = plan
172
+ .map(entry => ({ entry, current: layer.get(layerKey(entry.kind, entry.key)) }))
173
+ .map(item => ({ ...item, version: versionOf(item.entry, item.current) }))
174
+ .filter((item): item is typeof item & { version: number } => item.version != null)
175
+ await validate(todo.map(item => item.entry), opts?.project)
176
+
177
+ const written: ScopedSchemaRecord[] = []
178
+ for (const item of todo) {
179
+ written.push(await port().put(recordOf(item.entry, item.version, opts?.project, item.current)))
180
+ }
181
+ return written
182
+ })
183
+
184
+ const put = async (entry: Planned, opts?: SchemaWriteOptions): Promise<ScopedSchemaRecord> =>
185
+ (await write([entry], opts, () => entry.definition.version))[0]
186
+
187
+ const definitions: PlanningDefinitions = {
188
+ bundle: async project => {
189
+ await assertProject(project)
190
+ return (await runtime.scopedSchemas(entityId, project)).bundle()
191
+ },
192
+
193
+ registry: async project => {
194
+ await assertProject(project)
195
+ return await runtime.scopedSchemas(entityId, project)
196
+ },
197
+
198
+ records: async opts => {
199
+ if (typeof opts?.project === 'string' && !await visible(opts.project)) {
200
+ return []
201
+ }
202
+ const records = await port().list(Object.fromEntries(Object.entries({
203
+ entityId, project: opts?.project, kind: opts?.kind, retired: opts?.retired,
204
+ }).filter(([, value]) => value !== undefined)) as { entityId: string })
205
+ const allowed = await Promise.all(records.map(async record => record.project == null || await visible(record.project)))
206
+ return records.filter((_record, index) => allowed[index])
207
+ },
208
+
209
+ putAssigneeType: async schema => await put({ kind: PlanningSchemaKind.AssigneeType, key: schema.type, definition: schema }),
210
+
211
+ putType: async (type, opts) => await put({ kind: PlanningSchemaKind.Type, key: type.type, definition: type }, opts),
212
+
213
+ putFlow: async (flow, opts) => await put({ kind: PlanningSchemaKind.Flow, key: flow.id, definition: flow }, opts),
214
+
215
+ define: async (declarations, opts) =>
216
+ await write(planOf(declarations), opts, (_, current) => (current?.version ?? 0) + 1),
217
+
218
+ seed: async (declarations, opts) => {
219
+ const written: ScopedSchemaRecord[] = []
220
+ try {
221
+ written.push(...await write(planOf(declarations), opts, (_, current) => current == null ? 1 : null))
222
+ } catch (error) {
223
+ if (!(error instanceof SchemaConflict)) {
224
+ throw error
225
+ }
226
+ // A concurrent seed wrote a key first; what is left is still only what the layer lacks.
227
+ written.push(...await write(planOf(declarations), opts, (_, current) => current == null ? 1 : null))
228
+ }
229
+ return written
230
+ },
231
+
232
+ retire: async (kind, key, opts) => await unit(async () => {
233
+ await assertProject(opts?.project)
234
+ const current = (await layerOf(opts?.project)).get(layerKey(kind, key))
235
+ if (current == null) {
236
+ throw kind === PlanningSchemaKind.Type ? new UnknownWorkcardType(key) : new UnknownStatusFlow(key)
237
+ }
238
+ if (current.retired === true) {
239
+ return current
240
+ }
241
+ const retiring = recordOf({ kind, key, definition: current.definition }, current.version + 1, opts?.project, current, true)
242
+
243
+ if (kind === PlanningSchemaKind.Flow) {
244
+ const code = runtime.service().schemas.bundle()
245
+ const all = (await port().list({ entityId }))
246
+ .map(record => record.kind === kind && record.key === key && record.project === current.project ? retiring : record)
247
+ // The organization's layer reaches every project; a project's reaches itself alone.
248
+ const projects = opts?.project != null
249
+ ? [opts.project]
250
+ : [undefined, ...new Set(all.map(record => record.project).filter((id): id is string => id != null))]
251
+ for (const project of projects) {
252
+ const user = scopedSchemaHelper.flowInUse(scopedSchemaHelper.resolveScopedBundle(code, all, { entityId, project }), key)
253
+ if (user != null) {
254
+ throw new SchemaInUse(`flow:${key}:type:${user}${project != null ? `:project:${project}` : ''}`)
255
+ }
256
+ }
257
+ }
258
+
259
+ return await port().put(retiring)
260
+ }),
261
+ }
262
+
263
+ return definitions
264
+ }
@@ -0,0 +1,38 @@
1
+ import type {
2
+ ChangeSet, PlanningSchemaRegistry, PlanningScope, Transition, TransitionActor, TransitionExecution,
3
+ } from '@owlmeans/planning'
4
+ import type { Resolved } from '../types.js'
5
+
6
+ /** What step 11 builds the appended transition from. */
7
+ export interface AppendParams {
8
+ exec: TransitionExecution
9
+ resolved: Resolved
10
+ scope: PlanningScope
11
+ set: ChangeSet
12
+ cardId: string
13
+ seq: number
14
+ project?: string
15
+ at: string
16
+ }
17
+
18
+ /** The record an execution appends: its changes, its actor and the transition itself. */
19
+ export interface ChangesUtils {
20
+ /**
21
+ * Step 9: the recorded `changes`/`unset`, and whether an update wrote nothing.
22
+ *
23
+ * @throws {IllegalTransition | PlanningError}
24
+ */
25
+ changeSetOf: (
26
+ exec: TransitionExecution, resolved: Resolved, schemas: PlanningSchemaRegistry, at: string
27
+ ) => { set: ChangeSet, empty: boolean }
28
+ /**
29
+ * Who wrote it — the SCOPE's identity, never the execution's.
30
+ *
31
+ * `profileId`, `userId`, `service` and `channel` come only from the scope (the server builds it
32
+ * from the authenticated request); the descriptive `agent`/`runId` an in-process caller passes on
33
+ * the execution are kept unless the scope's own actor names them.
34
+ */
35
+ actorOf: (scope: PlanningScope, exec: TransitionExecution) => TransitionActor
36
+ /** Step 11's record: the event as it is appended, `pending`. */
37
+ transitionOf: (params: AppendParams) => Transition
38
+ }
@@ -1,78 +1,59 @@
1
- import { CommitState, computeChanges, isEmptyChange, TransitionAction } from '@owlmeans/planning'
2
- import type {
3
- ChangeSet, PlanningSchemaRegistry, PlanningScope, RelationshipDraft, Transition, TransitionActor,
4
- TransitionExecution, WorkcardDraft,
5
- } from '@owlmeans/planning'
6
- import type { Resolved } from './resolve.js'
1
+ import { changesHelper, CommitState, TransitionAction, type ChangeSet, type PlanningSchemaRegistry, type PlanningScope, type RelationshipDraft, type Transition, type TransitionActor, type TransitionExecution, type WorkcardDraft } from '@owlmeans/planning'
2
+ import type { ChangesUtils, AppendParams } from './changes/types.js'
3
+ import type { Resolved } from './types.js'
7
4
 
8
5
  const clean = <T extends object>(record: T): T =>
9
6
  Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined)) as T
10
7
 
11
- /**
12
- * Step 9: the recorded `changes`/`unset`, and whether an update wrote nothing.
13
- *
14
- * @throws {IllegalTransition | PlanningError}
15
- */
16
- export const changeSetOf = (
17
- exec: TransitionExecution, resolved: Resolved, schemas: PlanningSchemaRegistry, at: string
18
- ): { set: ChangeSet, empty: boolean } => {
19
- const set = computeChanges(resolved.card, exec, resolved.type, schemas, at, { slot: resolved.slot })
20
- const empty = exec.action === TransitionAction.Update && isEmptyChange(set)
8
+ export const createChangesUtils = (): ChangesUtils => {
9
+ const changeSetOf = (
10
+ exec: TransitionExecution, resolved: Resolved, schemas: PlanningSchemaRegistry, at: string
11
+ ): { set: ChangeSet, empty: boolean } => {
12
+ const set = changesHelper.computeChanges(resolved.card, exec, resolved.type, schemas, at, { slot: resolved.slot })
13
+ const empty = exec.action === TransitionAction.Update && changesHelper.isEmptyChange(set)
21
14
 
22
- return { set, empty }
23
- }
15
+ return { set, empty }
16
+ }
24
17
 
25
- /**
26
- * Who wrote it — the SCOPE's identity, never the execution's.
27
- *
28
- * `profileId`, `userId`, `service` and `channel` come only from the scope (the server builds it
29
- * from the authenticated request); the descriptive `agent`/`runId` an in-process caller passes on
30
- * the execution are kept unless the scope's own actor names them.
31
- */
32
- export const actorOf = (scope: PlanningScope, exec: TransitionExecution): TransitionActor => clean({
33
- agent: exec.actor?.agent,
34
- runId: exec.actor?.runId,
35
- ...clean({ profileId: scope.profileId, userId: scope.userId, service: scope.service }),
36
- ...clean(scope.actor ?? {}),
37
- channel: scope.channel ?? scope.actor?.channel,
38
- })
18
+ const actorOf = (scope: PlanningScope, exec: TransitionExecution): TransitionActor => clean({
19
+ agent: exec.actor?.agent,
20
+ runId: exec.actor?.runId,
21
+ ...clean({ profileId: scope.profileId, userId: scope.userId, service: scope.service }),
22
+ ...clean(scope.actor ?? {}),
23
+ channel: scope.channel ?? scope.actor?.channel,
24
+ })
39
25
 
40
- const withFrom = (link: RelationshipDraft, card: string): RelationshipDraft => ({ ...link, from: link.from ?? card })
26
+ const withFrom = (link: RelationshipDraft, card: string): RelationshipDraft => ({ ...link, from: link.from ?? card })
41
27
 
42
- /** Step 11's record: the event as it is appended, `pending`. */
43
- export const transitionOf = (params: {
44
- exec: TransitionExecution
45
- resolved: Resolved
46
- scope: PlanningScope
47
- set: ChangeSet
48
- cardId: string
49
- seq: number
50
- project?: string
51
- at: string
52
- }): Transition => {
53
- const { exec, resolved, scope, set, cardId, seq, project, at } = params
54
- const kind = resolved.create ? (exec.card as WorkcardDraft).kind : resolved.card!.kind
28
+ const transitionOf = (params: AppendParams): Transition => {
29
+ const { exec, resolved, scope, set, cardId, seq, project, at } = params
30
+ const kind = resolved.create ? (exec.card as WorkcardDraft).kind : resolved.card!.kind
55
31
 
56
- return clean({
57
- entityId: scope.entityId,
58
- card: cardId,
59
- kind,
60
- type: resolved.type.type,
61
- project,
62
- seq,
63
- action: exec.action,
64
- flow: set.flow,
65
- transition: exec.action === TransitionAction.Transit ? exec.transition : undefined,
66
- from: set.from,
67
- to: set.to,
68
- changes: set.changes,
69
- unset: set.unset.length > 0 ? set.unset : undefined,
70
- link: exec.link != null && exec.action !== TransitionAction.Create ? withFrom(exec.link, cardId) : undefined,
71
- links: resolved.create && (exec.links?.length ?? 0) > 0 ? exec.links!.map(link => withFrom(link, cardId)) : undefined,
72
- actor: actorOf(scope, exec),
73
- cause: exec.cause,
74
- key: exec.key,
75
- at,
76
- commit: { state: CommitState.Pending },
77
- })
32
+ return clean({
33
+ entityId: scope.entityId,
34
+ card: cardId,
35
+ kind,
36
+ type: resolved.type.type,
37
+ project,
38
+ seq,
39
+ action: exec.action,
40
+ flow: set.flow,
41
+ transition: exec.action === TransitionAction.Transit ? exec.transition : undefined,
42
+ from: set.from,
43
+ to: set.to,
44
+ changes: set.changes,
45
+ unset: set.unset.length > 0 ? set.unset : undefined,
46
+ link: exec.link != null && exec.action !== TransitionAction.Create ? withFrom(exec.link, cardId) : undefined,
47
+ links: resolved.create && (exec.links?.length ?? 0) > 0 ? exec.links!.map(link => withFrom(link, cardId)) : undefined,
48
+ actor: actorOf(scope, exec),
49
+ cause: exec.cause,
50
+ key: exec.key,
51
+ at,
52
+ commit: { state: CommitState.Pending },
53
+ })
54
+ }
55
+
56
+ return { changeSetOf, actorOf, transitionOf }
78
57
  }
58
+
59
+ export const changesUtils = createChangesUtils()
@@ -0,0 +1,23 @@
1
+ import type { PlanningExecContext, PlanningPlugin, PlanningScope, TransitionExecution } from '@owlmeans/planning'
2
+ import type { PluginRegistry } from '../../types.js'
3
+ import type { Resolved } from '../types.js'
4
+
5
+ /** Step 8 of the write path: the code a card gets, unique within its policy's scope. */
6
+ export interface CodeUtils {
7
+ /**
8
+ * Step 8: the code a new card gets, or the check a changed code passes.
9
+ *
10
+ * A caller-supplied code is checked for uniqueness; otherwise the plugins' `mintCode` chain answers
11
+ * (first answer wins, and is checked too); otherwise the type's policy mints one — a slug policy
12
+ * derives it from the title. A type with no policy and no supplied code gets none.
13
+ *
14
+ * @throws {CodeTaken}
15
+ */
16
+ assignCode: (
17
+ exec: TransitionExecution,
18
+ resolved: Resolved,
19
+ scope: PlanningScope,
20
+ registry: PluginRegistry,
21
+ contextOf: (plugin: PlanningPlugin) => PlanningExecContext
22
+ ) => Promise<TransitionExecution>
23
+ }
@@ -1,83 +1,76 @@
1
- import {
2
- CODE_MINT_ATTEMPTS, CodeScope, CodeStyle, CodeTaken, mintCode, TransitionAction,
3
- } from '@owlmeans/planning'
4
- import type {
5
- CodePolicy, PlanningExecContext, PlanningPlugin, PlanningScope, TransitionExecution, WorkcardDraft,
6
- } from '@owlmeans/planning'
1
+ import { CODE_MINT_ATTEMPTS, codeHelper, CodeScope, CodeStyle, CodeTaken, TransitionAction, type CodePolicy, type PlanningExecContext, type PlanningPlugin, type PlanningScope, type TransitionExecution, type Workcard, type WorkcardDraft } from '@owlmeans/planning'
7
2
  import type { Criteria } from '@owlmeans/resource'
8
- import type { Workcard } from '@owlmeans/planning'
9
- import type { PluginRegistry } from '../registry.js'
10
- import type { Resolved } from './resolve.js'
3
+ import type { PluginRegistry } from '../types.js'
4
+ import type { CodeUtils } from './code/types.js'
5
+ import type { Resolved } from './types.js'
11
6
 
12
- /** The criteria a code must be unique within — the policy's scope, `parent` when there is none. */
13
- const scopeCriteria = (
14
- scope: PlanningScope, type: string, parent: string | undefined, policy?: CodePolicy
15
- ): Criteria<Workcard> => ({
16
- entityId: scope.entityId,
17
- type,
18
- ...((policy?.uniqueWithin ?? CodeScope.Parent) === CodeScope.Parent ? { parent: parent ?? null } : {}),
19
- }) 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>
20
16
 
21
- export const takenProbe = (resolved: Resolved, scope: PlanningScope, parent: string | undefined) => {
22
- 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)
23
20
 
24
- return async (code: string): Promise<boolean> =>
25
- await resolved.store.cards.count({ ...base, code } as Criteria<Workcard>) > 0
26
- }
21
+ return async (code: string): Promise<boolean> =>
22
+ await resolved.store.cards.count({ ...base, code } as Criteria<Workcard>) > 0
23
+ }
27
24
 
28
- /**
29
- * Step 8: the code a new card gets, or the check a changed code passes.
30
- *
31
- * A caller-supplied code is checked for uniqueness; otherwise the plugins' `mintCode` chain answers
32
- * (first answer wins, and is checked too); otherwise the type's policy mints one — a slug policy
33
- * derives it from the title. A type with no policy and no supplied code gets none.
34
- *
35
- * @throws {CodeTaken}
36
- */
37
- export const assignCode = async (
38
- exec: TransitionExecution,
39
- resolved: Resolved,
40
- scope: PlanningScope,
41
- registry: PluginRegistry,
42
- contextOf: (plugin: PlanningPlugin) => PlanningExecContext
43
- ): Promise<TransitionExecution> => {
44
- const policy = resolved.type.code
25
+ const assignCode = async (
26
+ exec: TransitionExecution,
27
+ resolved: Resolved,
28
+ scope: PlanningScope,
29
+ registry: PluginRegistry,
30
+ contextOf: (plugin: PlanningPlugin) => PlanningExecContext
31
+ ): Promise<TransitionExecution> => {
32
+ const policy = resolved.type.code
45
33
 
46
- if (exec.action !== TransitionAction.Create) {
47
- const code = exec.changes?.code
48
- if (code != null && code !== resolved.card?.code
49
- && await takenProbe(resolved, scope, exec.changes?.parent ?? resolved.card?.parent)(code)) {
50
- 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
51
41
  }
52
- return exec
53
- }
54
42
 
55
- const draft = exec.card as WorkcardDraft
56
- const taken = takenProbe(resolved, scope, draft.parent)
57
- if (draft.code != null && draft.code !== '') {
58
- if (await taken(draft.code)) {
59
- 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
60
53
  }
61
- return exec
62
- }
63
- if (policy == null) {
64
- return exec
65
- }
66
54
 
67
- let code = await registry.mintCode(draft, taken, contextOf)
68
- if (code != null) {
69
- if (await taken(code)) {
70
- 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
+ })
71
68
  }
72
- } else {
73
- const count = policy.style === CodeStyle.Sequential
74
- ? await resolved.store.cards.count(scopeCriteria(scope, draft.type, draft.parent, policy))
75
- : undefined
76
- code = await mintCode(policy, taken, CODE_MINT_ATTEMPTS, {
77
- ...(policy.style === CodeStyle.Slug ? { seed: draft.title } : {}),
78
- ...(count != null ? { count } : {}),
79
- })
69
+
70
+ return { ...exec, card: { ...draft, code } }
80
71
  }
81
72
 
82
- return { ...exec, card: { ...draft, code } }
73
+ return { assignCode }
83
74
  }
75
+
76
+ export const codeUtils = createCodeUtils()
@@ -0,0 +1,8 @@
1
+ import { TransitionAction } from '@owlmeans/planning'
2
+
3
+ /** The field a create's draft writes once and nothing moves afterwards. */
4
+ export const CREATOR = 'createdBy'
5
+
6
+ export const TRIMMED = ['title', 'description', 'code'] as const
7
+
8
+ export const ACTIONS = new Set<string>(Object.values(TransitionAction))
@@ -0,0 +1,33 @@
1
+ import type { PlanningScope, TransitionExecution } from '@owlmeans/planning'
2
+
3
+ /** Who owns a card: `createdBy`, stamped from the scope on a create and fixed after it. */
4
+ export interface CreatorHelper {
5
+ /**
6
+ * The subject a create made under `scope` is stamped with as its `createdBy`: the scope's
7
+ * `profileId`, else its `userId`, else the same two of its `actor` — the authenticated subject an
8
+ * HTTP scope carries, in the same format. `undefined` for a scope that names nobody (a service or a
9
+ * system scope).
10
+ */
11
+ creatorOf: (scope: PlanningScope) => string | undefined
12
+ /**
13
+ * A create's draft with `createdBy` defaulted to {@link CreatorHelper.creatorOf} the scope. A
14
+ * `createdBy` the caller supplied wins; a scope that names nobody leaves the draft as it is;
15
+ * anything but a create is returned untouched. Never mutates `exec`.
16
+ *
17
+ * The executor applies it to every execution, so an in-process facade create and a wire create
18
+ * stamp the same value.
19
+ */
20
+ withCreator: (exec: TransitionExecution, scope: PlanningScope) => TransitionExecution
21
+ /**
22
+ * Refuse an execution that names `createdBy` in `changes` or `unset`, whatever the action — a
23
+ * create included, whose owner is its draft's. After the create it never moves, so an ownership
24
+ * check (`card.createdBy === profileId`) cannot be bypassed by anyone allowed to update the card.
25
+ *
26
+ * The executor runs it with the immutables of step 6, after the `before` chain, so a wire body, an
27
+ * in-process caller and a plugin are refused alike. It is the executor's own check, whatever
28
+ * `@owlmeans/planning` build `changesHelper.assertMutable` comes from.
29
+ *
30
+ * @throws {PlanningError} `planning:immutable:createdBy`
31
+ */
32
+ assertCreatorFixed: (exec: TransitionExecution) => void
33
+ }