@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,13 +1,16 @@
1
- import { uuid } from '@owlmeans/basic-ids'
2
- import { applyTransition, CardTypeNotAllowed, CommitState, IntrinsicStatus, PlanningError, PlanningSchemaKind, RelationshipRefused, SchemaConflict, SchemaInUse, SchemaInvalid, SchemaOrigin, SchemaSealed, SpecificationFormat, TransitionAction, UnknownWorkcardType, WorkcardConflict, WorkcardKind, WorkcardNotFound, type CommitEvent, type PlanningFacade, type PlanningStore, type StatusFlowSchema, type Transition, type TransitionExecution, type Unsubscribe, type Workcard, type WorkcardQuery, type WorkcardTypeSchema } from '@owlmeans/planning'
3
- import { creatorOf } from '../executor/creator.js'
4
- import { check, rejects, same, sameSet } from './assert.js'
5
- import { createBook, createBranch, transit } from './fixtures.js'
1
+
2
+ import { applyHelper, CardTypeNotAllowed, CommitState, IntrinsicStatus, PlanningError, PlanningSchemaKind, RelationshipRefused, SchemaConflict, SchemaInUse, SchemaInvalid, SchemaOrigin, SchemaSealed, SpecificationFormat, TransitionAction, UnknownWorkcardType, WorkcardConflict, WorkcardKind, WorkcardNotFound, type CommitEvent, type PlanningFacade, type PlanningStore, type StatusFlowSchema, type Transition, type TransitionExecution, type Unsubscribe, type Workcard, type WorkcardQuery, type WorkcardTypeSchema } from '@owlmeans/planning'
3
+ import { creatorHelper } from '../executor/creator.js'
4
+ import { assertHelper } from './assert.js'
5
+ import { conformanceFixturesOf } from './fixtures.js'
6
6
  import { BOOK_TYPE, LIBRARY, PERIODICAL_TYPE } from './consts.js'
7
7
  import type { ConformanceCase } from './types.js'
8
+ import { idHelper } from '@owlmeans/basic-ids'
9
+
10
+ const { check, rejects, same, sameSet } = assertHelper
8
11
 
9
12
  /** A fresh organization per case, so cases never see each other's cards on a shared database. */
10
- const organization = (): string => `conformance-${uuid()}`
13
+ const organization = (): string => `conformance-${idHelper.uuid()}`
11
14
 
12
15
  const ids = async (planning: PlanningFacade, query: WorkcardQuery): Promise<string[]> =>
13
16
  (await planning.cards.list({ ...query, size: 0 })).items.map(card => card.id!)
@@ -21,13 +24,14 @@ const cases: ConformanceCase[] = [
21
24
  name: 'folds transitions in seq order and settles each one once',
22
25
  run: async ({ facade }) => {
23
26
  const planning = facade(organization())
27
+ const fixtures = conformanceFixturesOf(planning)
24
28
  const events: CommitEvent[] = []
25
29
  const unsubscribe: Unsubscribe = await planning.commits.subscribe(event => { events.push(event) })
26
30
  try {
27
- const branch = await createBranch(planning)
28
- const book = await createBook(planning, branch.id!, 'Winter poems')
31
+ const branch = await fixtures.createBranch()
32
+ const book = await fixtures.createBook(branch.id!, 'Winter poems')
29
33
  await execute(planning, { card: book.id!, action: TransitionAction.Update, changes: { title: 'Winter verses' } })
30
- await transit(planning, book.id!, 'lend')
34
+ await fixtures.transit(book.id!, 'lend')
31
35
 
32
36
  const card = await planning.cards.get(book.id!)
33
37
  same([card.seq, card.head, card.title, card.status, card.intrinsic], [3, 3, 'Winter verses', 'lent', IntrinsicStatus.InProgress], 'the folded card')
@@ -43,13 +47,14 @@ const cases: ConformanceCase[] = [
43
47
  name: 'allocates seq against the head and refuses a stale expectSeq',
44
48
  run: async ({ facade, store }) => {
45
49
  const planning = facade(organization())
46
- const book = await createBook(planning, (await createBranch(planning)).id!)
50
+ const fixtures = conformanceFixturesOf(planning)
51
+ const book = await fixtures.createBook((await fixtures.createBranch()).id!)
47
52
 
48
53
  await execute(planning, { card: book.id!, action: TransitionAction.Update, changes: { title: 'First' }, expectSeq: 1 })
49
54
  await rejects(planning.execute({ card: book.id!, action: TransitionAction.Update, changes: { title: 'Stale' }, expectSeq: 1 }),
50
55
  WorkcardConflict, 'a stale expectSeq')
51
56
  same(await store.transitions!.head(book.id!), 2, 'the head after two transitions')
52
- same(await store.transitions!.nextSeq(`conformance-${uuid()}`, null), 1, 'a card with no log allocates 1')
57
+ same(await store.transitions!.nextSeq(`conformance-${idHelper.uuid()}`, null), 1, 'a card with no log allocates 1')
53
58
  same((await planning.cards.get(book.id!)).title, 'First', 'the refused write left the card alone')
54
59
  },
55
60
  },
@@ -58,7 +63,8 @@ const cases: ConformanceCase[] = [
58
63
  name: 'an idempotency key answers its first receipt',
59
64
  run: async ({ facade }) => {
60
65
  const planning = facade(organization())
61
- const book = await createBook(planning, (await createBranch(planning)).id!)
66
+ const fixtures = conformanceFixturesOf(planning)
67
+ const book = await fixtures.createBook((await fixtures.createBranch()).id!)
62
68
 
63
69
  const first = await planning.execute({ card: book.id!, action: TransitionAction.Update, changes: { title: 'Kept' }, key: 'import:1' }, { wait: true })
64
70
  const again = await planning.execute({ card: book.id!, action: TransitionAction.Update, changes: { title: 'Lost' }, key: 'import:1' }, { wait: true })
@@ -73,14 +79,15 @@ const cases: ConformanceCase[] = [
73
79
  name: 'keeps a create\'s createdBy — the scope\'s subject, or the one the caller named — through later writes',
74
80
  run: async ({ facade }) => {
75
81
  const planning = facade(organization())
76
- const subject = creatorOf(planning.scope)
82
+ const fixtures = conformanceFixturesOf(planning)
83
+ const subject = creatorHelper.creatorOf(planning.scope)
77
84
  check(subject != null, 'the conformance facade acts as a signed-in person')
78
85
 
79
- const branch = await createBranch(planning)
80
- const stamped = await createBook(planning, branch.id!, 'Stamped')
81
- const named = await createBook(planning, branch.id!, 'Named', { createdBy: 'conformance-donor' })
86
+ const branch = await fixtures.createBranch()
87
+ const stamped = await fixtures.createBook(branch.id!, 'Stamped')
88
+ const named = await fixtures.createBook(branch.id!, 'Named', { createdBy: 'conformance-donor' })
82
89
  await execute(planning, { card: stamped.id!, action: TransitionAction.Update, changes: { title: 'Stamped, revised' } })
83
- await transit(planning, stamped.id!, 'lend')
90
+ await fixtures.transit(stamped.id!, 'lend')
84
91
 
85
92
  same([branch.createdBy, (await planning.cards.get(stamped.id!)).createdBy, (await planning.cards.get(named.id!)).createdBy],
86
93
  [subject, subject, 'conformance-donor'], 'createdBy read back after later writes')
@@ -94,8 +101,9 @@ const cases: ConformanceCase[] = [
94
101
  run: async ({ facade, store }) => {
95
102
  const org = organization()
96
103
  const planning = facade(org)
97
- const branch = await createBranch(planning)
98
- const book = await createBook(planning, branch.id!, 'Owned')
104
+ const fixtures = conformanceFixturesOf(planning)
105
+ const branch = await fixtures.createBranch()
106
+ const book = await fixtures.createBook(branch.id!, 'Owned')
99
107
  const moves: TransitionExecution[] = [
100
108
  { card: book.id!, action: TransitionAction.Update, changes: { title: 'Taken', createdBy: 'conformance-intruder' } },
101
109
  { card: book.id!, action: TransitionAction.Update, unset: ['createdBy'] },
@@ -107,7 +115,7 @@ const cases: ConformanceCase[] = [
107
115
  }
108
116
  const kept = await planning.cards.get(book.id!)
109
117
  same([kept.createdBy, kept.title, kept.seq, (await planning.transitions.list({ card: book.id! })).total],
110
- [creatorOf(planning.scope), 'Owned', 1, 1], 'the refused writes left the card and its log alone')
118
+ [creatorHelper.creatorOf(planning.scope), 'Owned', 1, 1], 'the refused writes left the card and its log alone')
111
119
 
112
120
  // Rows appended before the refusal existed: the store folds them exactly as the pure fold does.
113
121
  const legacy: Transition[] = []
@@ -124,7 +132,7 @@ const cases: ConformanceCase[] = [
124
132
  for (const row of legacy) {
125
133
  same((await planning.commits.status(row.id!)).state, CommitState.Committed, `legacy row ${row.seq} settles committed`)
126
134
  }
127
- const expected = legacy.reduce<Workcard | null>((card, row) => applyTransition(card, row), kept)!
135
+ const expected = legacy.reduce<Workcard | null>((card, row) => applyHelper.applyTransition(card, row), kept)!
128
136
  const folded = await planning.cards.get(book.id!)
129
137
  same([folded.title, folded.seq, folded.createdBy], [expected.title, expected.seq, expected.createdBy], 'the legacy rows folded')
130
138
  await execute(planning, { card: book.id!, action: TransitionAction.Update, changes: { title: 'After' } })
@@ -136,18 +144,19 @@ const cases: ConformanceCase[] = [
136
144
  name: 'transit moves the intrinsic state and closedAt, and a second flow leaves the primary alone',
137
145
  run: async ({ facade }) => {
138
146
  const planning = facade(organization())
139
- const book = await createBook(planning, (await createBranch(planning)).id!)
147
+ const fixtures = conformanceFixturesOf(planning)
148
+ const book = await fixtures.createBook((await fixtures.createBranch()).id!)
140
149
 
141
- await transit(planning, book.id!, 'retire')
150
+ await fixtures.transit(book.id!, 'retire')
142
151
  const retired = await planning.cards.get(book.id!)
143
152
  same([retired.status, retired.intrinsic], ['retired', IntrinsicStatus.Closed], 'retired')
144
153
  check(typeof retired.closedAt === 'string', 'closedAt is set on entering closed')
145
154
 
146
- await transit(planning, book.id!, 'restore')
155
+ await fixtures.transit(book.id!, 'restore')
147
156
  const restored = await planning.cards.get(book.id!)
148
157
  same([restored.status, restored.intrinsic, restored.closedAt], ['shelved', IntrinsicStatus.Planned, undefined], 'closedAt cleared on leaving closed')
149
158
 
150
- await transit(planning, book.id!, 'review', { flow: LIBRARY.review })
159
+ await fixtures.transit(book.id!, 'review', { flow: LIBRARY.review })
151
160
  const reviewed = await planning.cards.get(book.id!)
152
161
  same([reviewed.status, reviewed.flows[LIBRARY.review], reviewed.intrinsic], ['shelved', 'reviewed', IntrinsicStatus.Planned], 'the second flow moved alone')
153
162
  },
@@ -158,12 +167,13 @@ const cases: ConformanceCase[] = [
158
167
  run: async ({ facade }) => {
159
168
  const org = organization()
160
169
  const planning = facade(org)
161
- const east = await createBranch(planning, 'East branch')
162
- const west = await createBranch(planning, 'West branch')
163
- const poems = await createBook(planning, east.id!, 'Winter poems', { labels: ['rare'], fields: { genre: 'poetry', signed: true } })
164
- const atlas = await createBook(planning, east.id!, 'River atlas', { labels: ['new'], parents: [west.id!], fields: { genre: 'history' } })
165
- const harbour = await createBook(planning, east.id!, 'Quiet harbour', { fields: { genre: 'fiction', signed: false } })
166
- await transit(planning, atlas.id!, 'lend')
170
+ const fixtures = conformanceFixturesOf(planning)
171
+ const east = await fixtures.createBranch('East branch')
172
+ const west = await fixtures.createBranch('West branch')
173
+ const poems = await fixtures.createBook(east.id!, 'Winter poems', { labels: ['rare'], fields: { genre: 'poetry', signed: true } })
174
+ const atlas = await fixtures.createBook(east.id!, 'River atlas', { labels: ['new'], parents: [west.id!], fields: { genre: 'history' } })
175
+ const harbour = await fixtures.createBook(east.id!, 'Quiet harbour', { fields: { genre: 'fiction', signed: false } })
176
+ await fixtures.transit(atlas.id!, 'lend')
167
177
  const since = (await planning.cards.get(atlas.id!)).updatedAt!
168
178
 
169
179
  sameSet(await ids(planning, { within: west.id }), [atlas.id], 'within')
@@ -189,8 +199,9 @@ const cases: ConformanceCase[] = [
189
199
  name: 'lists specifications only when the query asks for them',
190
200
  run: async ({ facade }) => {
191
201
  const planning = facade(organization())
192
- const branch = await createBranch(planning)
193
- const book = await createBook(planning, branch.id!)
202
+ const fixtures = conformanceFixturesOf(planning)
203
+ const branch = await fixtures.createBranch()
204
+ const book = await fixtures.createBook(branch.id!)
194
205
  const model = await planning.model(book)
195
206
  const summary = (await model.write('summary', '{"lines":["a"]}', { wait: true })).card!
196
207
  const charter = (await (await planning.model(branch)).write('charter', '# Charter', { wait: true })).card!
@@ -208,9 +219,10 @@ const cases: ConformanceCase[] = [
208
219
  name: 'pages and sorts a list, counting the whole match',
209
220
  run: async ({ facade }) => {
210
221
  const planning = facade(organization())
211
- const branch = await createBranch(planning)
222
+ const fixtures = conformanceFixturesOf(planning)
223
+ const branch = await fixtures.createBranch()
212
224
  for (const order of [5, 3, 1, 4, 2]) {
213
- await createBook(planning, branch.id!, `Volume ${order}`, { order })
225
+ await fixtures.createBook(branch.id!, `Volume ${order}`, { order })
214
226
  }
215
227
 
216
228
  const page = await planning.cards.list({ parent: branch.id, sort: ['order'], size: 2, page: 1 })
@@ -225,15 +237,16 @@ const cases: ConformanceCase[] = [
225
237
  name: 'summarizes direct children by intrinsic state',
226
238
  run: async ({ facade }) => {
227
239
  const planning = facade(organization())
228
- const branch = await createBranch(planning)
229
- const empty = await createBranch(planning, 'Empty branch')
230
- const lent = await createBook(planning, branch.id!, 'Lent')
231
- const retired = await createBook(planning, branch.id!, 'Retired')
232
- await createBook(planning, branch.id!, 'Shelved')
233
- await transit(planning, lent.id!, 'lend')
234
- await transit(planning, retired.id!, 'retire')
235
- const annex = await createBranch(planning, 'Annex', { parent: branch.id })
236
- await createBook(planning, annex.id!, 'Deeper')
240
+ const fixtures = conformanceFixturesOf(planning)
241
+ const branch = await fixtures.createBranch()
242
+ const empty = await fixtures.createBranch('Empty branch')
243
+ const lent = await fixtures.createBook(branch.id!, 'Lent')
244
+ const retired = await fixtures.createBook(branch.id!, 'Retired')
245
+ await fixtures.createBook(branch.id!, 'Shelved')
246
+ await fixtures.transit(lent.id!, 'lend')
247
+ await fixtures.transit(retired.id!, 'retire')
248
+ const annex = await fixtures.createBranch('Annex', { parent: branch.id })
249
+ await fixtures.createBook(annex.id!, 'Deeper')
237
250
  await (await planning.model(branch)).write('charter', '# Charter', { wait: true })
238
251
 
239
252
  const summary = await planning.cards.summary([branch.id!, empty.id!])
@@ -248,8 +261,9 @@ const cases: ConformanceCase[] = [
248
261
  name: 'answers a specification current, listed, every document and its revisions',
249
262
  run: async ({ facade }) => {
250
263
  const planning = facade(organization())
251
- const branch = await createBranch(planning)
252
- const book = await createBook(planning, branch.id!)
264
+ const fixtures = conformanceFixturesOf(planning)
265
+ const branch = await fixtures.createBranch()
266
+ const book = await fixtures.createBook(branch.id!)
253
267
  const model = await planning.model(book)
254
268
  await model.write('summary', '{"lines":["first"]}', { wait: true })
255
269
  await (await planning.model(book.id!)).write('summary', '{"lines":["second"]}', { wait: true })
@@ -275,10 +289,11 @@ const cases: ConformanceCase[] = [
275
289
  name: 'keeps links: both ends, the single constraint, unlink and a delete dropping edges',
276
290
  run: async ({ facade }) => {
277
291
  const planning = facade(organization())
278
- const branch = await createBranch(planning)
279
- const first = await createBook(planning, branch.id!, 'First')
280
- const second = await createBook(planning, branch.id!, 'Second')
281
- const third = await createBook(planning, branch.id!, 'Third')
292
+ const fixtures = conformanceFixturesOf(planning)
293
+ const branch = await fixtures.createBranch()
294
+ const first = await fixtures.createBook(branch.id!, 'First')
295
+ const second = await fixtures.createBook(branch.id!, 'Second')
296
+ const third = await fixtures.createBook(branch.id!, 'Third')
282
297
 
283
298
  await execute(planning, { card: second.id!, action: TransitionAction.Link, link: { type: 'sequel-of', to: first.id! } })
284
299
  same((await planning.relationships.list({ from: second.id })).total, 1, 'from')
@@ -301,9 +316,10 @@ const cases: ConformanceCase[] = [
301
316
  name: 'isolates organizations on every read and write',
302
317
  run: async ({ facade }) => {
303
318
  const mine = facade(organization())
319
+ const fixtures = conformanceFixturesOf(mine)
304
320
  const theirs = facade(organization())
305
- const branch = await createBranch(mine)
306
- const book = await createBook(mine, branch.id!)
321
+ const branch = await fixtures.createBranch()
322
+ const book = await fixtures.createBook(branch.id!)
307
323
  const receipt = await mine.execute({ card: book.id!, action: TransitionAction.Update, changes: { title: 'Mine' } }, { wait: true })
308
324
 
309
325
  same(await theirs.cards.load(book.id!), null, 'load')
@@ -322,15 +338,16 @@ const cases: ConformanceCase[] = [
322
338
  name: 'purges everything under a deleted project, keeping at most its own delete as a tombstone',
323
339
  run: async ({ facade }) => {
324
340
  const planning = facade(organization())
325
- const doomed = await createBranch(planning, 'Doomed branch')
326
- const kept = await createBranch(planning, 'Kept branch')
327
- const first = await createBook(planning, doomed.id!, 'First')
328
- const second = await createBook(planning, doomed.id!, 'Second')
341
+ const fixtures = conformanceFixturesOf(planning)
342
+ const doomed = await fixtures.createBranch('Doomed branch')
343
+ const kept = await fixtures.createBranch('Kept branch')
344
+ const first = await fixtures.createBook(doomed.id!, 'First')
345
+ const second = await fixtures.createBook(doomed.id!, 'Second')
329
346
  await execute(planning, { card: second.id!, action: TransitionAction.Link, link: { type: 'sequel-of', to: first.id! } })
330
347
  const summary = (await (await planning.model(first)).write('summary', '{}', { wait: true })).card!
331
- const annex = await createBranch(planning, 'Annex', { parent: doomed.id })
332
- const deeper = await createBook(planning, annex.id!, 'Deeper')
333
- const survivor = await createBook(planning, kept.id!, 'Survivor')
348
+ const annex = await fixtures.createBranch('Annex', { parent: doomed.id })
349
+ const deeper = await fixtures.createBook(annex.id!, 'Deeper')
350
+ const survivor = await fixtures.createBook(kept.id!, 'Survivor')
334
351
 
335
352
  const deleted = await planning.execute({ card: doomed.id!, action: TransitionAction.Delete }, { wait: true })
336
353
 
@@ -352,7 +369,8 @@ const cases: ConformanceCase[] = [
352
369
  run: async ({ facade, store }) => {
353
370
  const org = organization()
354
371
  const planning = facade(org)
355
- const book = await createBook(planning, (await createBranch(planning)).id!)
372
+ const fixtures = conformanceFixturesOf(planning)
373
+ const book = await fixtures.createBook((await fixtures.createBranch()).id!)
356
374
  // A row two past the card with nothing between — its allocation lost long ago.
357
375
  const rogue = await store.transitions!.append({
358
376
  entityId: org, card: book.id!, kind: WorkcardKind.Card, type: LIBRARY.book, seq: 3,
@@ -375,6 +393,7 @@ const cases: ConformanceCase[] = [
375
393
  needs: ['schemas'],
376
394
  run: async ({ facade }) => {
377
395
  const planning = facade(organization())
396
+ const fixtures = conformanceFixturesOf(planning)
378
397
  const pamphlets: StatusFlowSchema = {
379
398
  id: 'library:pamphlet-life', version: 1,
380
399
  statuses: [{ key: 'stacked', intrinsic: IntrinsicStatus.Planned, initial: true }, { key: 'taken', intrinsic: IntrinsicStatus.Closed }],
@@ -387,11 +406,11 @@ const cases: ConformanceCase[] = [
387
406
  same(written.map(record => [record.kind, record.key, record.version]),
388
407
  [[PlanningSchemaKind.Flow, pamphlets.id, 1], [PlanningSchemaKind.Type, pamphlet.type, 1]], 'flows first, then types')
389
408
 
390
- const branch = await createBranch(planning)
409
+ const branch = await fixtures.createBranch()
391
410
  const card = await execute(planning, { card: { kind: WorkcardKind.Card, type: pamphlet.type, parent: branch.id!, title: 'Opening hours' }, action: TransitionAction.Create })
392
411
  same([card?.status, card?.intrinsic], ['stacked', IntrinsicStatus.Planned], 'the data-defined flow started it')
393
412
  same((await planning.model(card!)).available().map(rule => rule.name), ['take'], 'the model reads the layer')
394
- await transit(planning, card!.id!, 'take')
413
+ await fixtures.transit(card!.id!, 'take')
395
414
 
396
415
  const room = await execute(planning, { card: { kind: WorkcardKind.Project, type: LIBRARY.room, title: 'Reading room' }, action: TransitionAction.Create })
397
416
  await rejects(planning.execute({ card: { kind: WorkcardKind.Card, type: pamphlet.type, parent: room!.id!, title: 'Refused' }, action: TransitionAction.Create }),
@@ -408,8 +427,9 @@ const cases: ConformanceCase[] = [
408
427
  needs: ['schemas'],
409
428
  run: async ({ facade }) => {
410
429
  const planning = facade(organization())
411
- const branch = await createBranch(planning)
412
- const other = await createBranch(planning, 'Other branch')
430
+ const fixtures = conformanceFixturesOf(planning)
431
+ const branch = await fixtures.createBranch()
432
+ const other = await fixtures.createBranch('Other branch')
413
433
  await planning.definitions!.define({ types: [{ ...PERIODICAL_TYPE, label: 'Magazine' }] })
414
434
  await planning.definitions!.define({ types: [{ ...PERIODICAL_TYPE, label: 'Zine' }] }, { project: branch.id })
415
435
 
@@ -450,6 +470,7 @@ const cases: ConformanceCase[] = [
450
470
  needs: ['schemas'],
451
471
  run: async ({ facade }) => {
452
472
  const planning = facade(organization())
473
+ const fixtures = conformanceFixturesOf(planning)
453
474
  const definitions = planning.definitions!
454
475
  const repair: StatusFlowSchema = {
455
476
  id: 'library:repair', version: 1,
@@ -464,15 +485,15 @@ const cases: ConformanceCase[] = [
464
485
  type: 'library:folio', kind: WorkcardKind.Card, version: 1, fields: { type: 'object' }, flows: [repair.id], specifications: [],
465
486
  }
466
487
  await definitions.define({ flows: [repair], types: [folio] })
467
- const branch = await createBranch(planning)
488
+ const branch = await fixtures.createBranch()
468
489
  const card = await execute(planning, { card: { kind: WorkcardKind.Card, type: folio.type, parent: branch.id!, title: 'Old map' }, action: TransitionAction.Create })
469
- await transit(planning, card!.id!, 'mend')
490
+ await fixtures.transit(card!.id!, 'mend')
470
491
 
471
492
  await rejects(definitions.retire(PlanningSchemaKind.Flow, repair.id), SchemaInUse, 'a flow a live type runs')
472
493
 
473
494
  // The flow changes under the card: `mending` is no longer declared, and the card still moves.
474
495
  await definitions.define({ flows: [{ ...repair, statuses: [repair.statuses[0], repair.statuses[2]], transitions: [{ name: 'finish', from: ['queued'], to: 'done' }] }] })
475
- await transit(planning, card!.id!, 'finish')
496
+ await fixtures.transit(card!.id!, 'finish')
476
497
  same((await planning.cards.get(card!.id!)).status, 'done', 'an undeclared status follows the rule of the name')
477
498
 
478
499
  await definitions.retire(PlanningSchemaKind.Type, folio.type)
@@ -0,0 +1,11 @@
1
+ /**
2
+ * A clock that never repeats an instant — what a conformance service is booted with, so an
3
+ * `updatedSince` or an ordering by `at` is deterministic on a fast store.
4
+ */
5
+ export const conformanceClock = (): (() => string) => {
6
+ let last = 0
7
+ return () => {
8
+ last = Math.max(Date.now(), last + 1)
9
+ return new Date(last).toISOString()
10
+ }
11
+ }
@@ -1,4 +1,4 @@
1
- import { type WorkcardTypeSchema, CodeScope, CodeStyle, SpecificationFormat, WorkcardKind } from '@owlmeans/planning'
1
+ import { type AnyTypeSchema, type PlanningPlugin, type StatusFlowSchema, type WorkcardTypeSchema, CodeScope, CodeStyle, IntrinsicStatus, SpecificationFormat, WorkcardKind } from '@owlmeans/planning'
2
2
 
3
3
  /**
4
4
  * The conformance vocabulary: a lending library. Branches (projects) hold books and periodicals
@@ -42,3 +42,89 @@ export const PERIODICAL_TYPE: WorkcardTypeSchema = {
42
42
  flows: [LIBRARY.circulation],
43
43
  specifications: [],
44
44
  }
45
+
46
+ const flows: StatusFlowSchema[] = [
47
+ {
48
+ id: LIBRARY.circulation,
49
+ version: 1,
50
+ statuses: [
51
+ { key: 'shelved', intrinsic: IntrinsicStatus.Planned, initial: true },
52
+ { key: 'lent', intrinsic: IntrinsicStatus.InProgress },
53
+ { key: 'retired', intrinsic: IntrinsicStatus.Closed, terminal: true },
54
+ ],
55
+ transitions: [
56
+ { name: 'lend', from: ['shelved'], to: 'lent', explicit: true },
57
+ { name: 'return', from: ['lent'], to: 'shelved', explicit: true },
58
+ { name: 'retire', from: '*', to: 'retired' },
59
+ { name: 'restore', from: ['retired'], to: 'shelved' },
60
+ ],
61
+ },
62
+ {
63
+ id: LIBRARY.review,
64
+ version: 1,
65
+ statuses: [
66
+ { key: 'unreviewed', intrinsic: IntrinsicStatus.Planned, initial: true },
67
+ { key: 'reviewed', intrinsic: IntrinsicStatus.Closed },
68
+ ],
69
+ transitions: [{ name: 'review', from: ['unreviewed'], to: 'reviewed' }],
70
+ },
71
+ {
72
+ id: LIBRARY.branchLife,
73
+ version: 1,
74
+ statuses: [
75
+ { key: 'open', intrinsic: IntrinsicStatus.Planned, initial: true },
76
+ { key: 'active', intrinsic: IntrinsicStatus.InProgress },
77
+ ],
78
+ transitions: [{ name: 'activate', from: ['open'], to: 'active' }],
79
+ },
80
+ {
81
+ id: LIBRARY.pageLife,
82
+ version: 1,
83
+ statuses: [{ key: 'draft', intrinsic: IntrinsicStatus.Planned, initial: true }],
84
+ transitions: [],
85
+ },
86
+ ]
87
+
88
+ const types: AnyTypeSchema[] = [
89
+ {
90
+ type: LIBRARY.branch,
91
+ kind: WorkcardKind.Project,
92
+ version: 1,
93
+ fields: { type: 'object', additionalProperties: true },
94
+ flows: [LIBRARY.branchLife],
95
+ cardTypes: [LIBRARY.book, LIBRARY.periodical],
96
+ projectTypes: [LIBRARY.branch],
97
+ scopedCardTypes: true,
98
+ specifications: [
99
+ { category: 'charter', format: SpecificationFormat.Markdown },
100
+ { category: 'notes', format: SpecificationFormat.Markdown, multiple: true },
101
+ ],
102
+ code: { style: CodeStyle.Slug, uniqueWithin: CodeScope.Entity, mutable: true },
103
+ },
104
+ {
105
+ type: LIBRARY.room,
106
+ kind: WorkcardKind.Project,
107
+ version: 1,
108
+ fields: { type: 'object', additionalProperties: true },
109
+ flows: [LIBRARY.branchLife],
110
+ cardTypes: [LIBRARY.book],
111
+ specifications: [],
112
+ },
113
+ BOOK_TYPE,
114
+ PERIODICAL_TYPE,
115
+ {
116
+ type: LIBRARY.page,
117
+ kind: WorkcardKind.Specification,
118
+ version: 1,
119
+ fields: { type: 'object', additionalProperties: true },
120
+ flows: [LIBRARY.pageLife],
121
+ specifications: [],
122
+ },
123
+ ]
124
+
125
+ /** The types and flows every conformance case runs against — register it on the service under test. */
126
+ export const planningConformancePlugin: PlanningPlugin = Object.freeze({
127
+ name: 'planning-conformance',
128
+ order: 0,
129
+ schemas: { types, flows },
130
+ })
@@ -0,0 +1,7 @@
1
+ /** A conformance expectation that did not hold. Plain `Error`, so any test runner reports it. */
2
+ export class ConformanceFailure extends Error {
3
+ constructor(message: string) {
4
+ super(`planning conformance: ${message}`)
5
+ this.name = 'ConformanceFailure'
6
+ }
7
+ }
@@ -0,0 +1,11 @@
1
+ import type { TransitionReceipt, Workcard } from '@owlmeans/planning'
2
+
3
+ /** The records a conformance case creates and moves, through one organization's facade. */
4
+ export interface ConformanceFixtures {
5
+ /** A branch (project) at the root, written and committed. */
6
+ createBranch: (title?: string, extra?: Record<string, unknown>) => Promise<Workcard>
7
+ /** A book (card) under a branch, written and committed. */
8
+ createBook: (branch: string, title?: string, extra?: Record<string, unknown>) => Promise<Workcard>
9
+ /** A transit of a card, waited until it commits. */
10
+ transit: (card: string, transition: string, extra?: { flow?: string }) => Promise<TransitionReceipt>
11
+ }
@@ -1,121 +1,46 @@
1
- // owlmeans-structure: allow-file the conformance fixtures are one declarative data set together with the plugin that serves it
2
- import { CodeScope, CodeStyle, IntrinsicStatus, SpecificationFormat, TransitionAction, WorkcardKind, type AnyTypeSchema, type PlanningFacade, type PlanningPlugin, type StatusFlowSchema, type TransitionReceipt, type Workcard } from '@owlmeans/planning'
3
- import { BOOK_TYPE, LIBRARY, PERIODICAL_TYPE } from './consts.js'
1
+ import { memoHelper } from '@owlmeans/context'
2
+ import { TransitionAction, WorkcardKind, type PlanningFacade, type TransitionReceipt, type Workcard } from '@owlmeans/planning'
3
+ import { LIBRARY } from './consts.js'
4
+ import type { ConformanceFixtures } from './fixtures/types.js'
4
5
 
5
- const flows: StatusFlowSchema[] = [
6
- {
7
- id: LIBRARY.circulation,
8
- version: 1,
9
- statuses: [
10
- { key: 'shelved', intrinsic: IntrinsicStatus.Planned, initial: true },
11
- { key: 'lent', intrinsic: IntrinsicStatus.InProgress },
12
- { key: 'retired', intrinsic: IntrinsicStatus.Closed, terminal: true },
13
- ],
14
- transitions: [
15
- { name: 'lend', from: ['shelved'], to: 'lent', explicit: true },
16
- { name: 'return', from: ['lent'], to: 'shelved', explicit: true },
17
- { name: 'retire', from: '*', to: 'retired' },
18
- { name: 'restore', from: ['retired'], to: 'shelved' },
19
- ],
20
- },
21
- {
22
- id: LIBRARY.review,
23
- version: 1,
24
- statuses: [
25
- { key: 'unreviewed', intrinsic: IntrinsicStatus.Planned, initial: true },
26
- { key: 'reviewed', intrinsic: IntrinsicStatus.Closed },
27
- ],
28
- transitions: [{ name: 'review', from: ['unreviewed'], to: 'reviewed' }],
29
- },
30
- {
31
- id: LIBRARY.branchLife,
32
- version: 1,
33
- statuses: [
34
- { key: 'open', intrinsic: IntrinsicStatus.Planned, initial: true },
35
- { key: 'active', intrinsic: IntrinsicStatus.InProgress },
36
- ],
37
- transitions: [{ name: 'activate', from: ['open'], to: 'active' }],
38
- },
39
- {
40
- id: LIBRARY.pageLife,
41
- version: 1,
42
- statuses: [{ key: 'draft', intrinsic: IntrinsicStatus.Planned, initial: true }],
43
- transitions: [],
44
- },
45
- ]
6
+ export const makeConformanceFixtures = (planning: PlanningFacade): ConformanceFixtures => {
7
+ const createBranch = async (
8
+ title: string = 'Riverside branch', extra: Record<string, unknown> = {}
9
+ ): Promise<Workcard> => (await planning.execute({
10
+ card: { kind: WorkcardKind.Project, type: LIBRARY.branch, title, ...extra },
11
+ action: TransitionAction.Create,
12
+ }, { wait: true })).card!
46
13
 
47
- const types: AnyTypeSchema[] = [
48
- {
49
- type: LIBRARY.branch,
50
- kind: WorkcardKind.Project,
51
- version: 1,
52
- fields: { type: 'object', additionalProperties: true },
53
- flows: [LIBRARY.branchLife],
54
- cardTypes: [LIBRARY.book, LIBRARY.periodical],
55
- projectTypes: [LIBRARY.branch],
56
- scopedCardTypes: true,
57
- specifications: [
58
- { category: 'charter', format: SpecificationFormat.Markdown },
59
- { category: 'notes', format: SpecificationFormat.Markdown, multiple: true },
60
- ],
61
- code: { style: CodeStyle.Slug, uniqueWithin: CodeScope.Entity, mutable: true },
62
- },
63
- {
64
- type: LIBRARY.room,
65
- kind: WorkcardKind.Project,
66
- version: 1,
67
- fields: { type: 'object', additionalProperties: true },
68
- flows: [LIBRARY.branchLife],
69
- cardTypes: [LIBRARY.book],
70
- specifications: [],
71
- },
72
- BOOK_TYPE,
73
- PERIODICAL_TYPE,
74
- {
75
- type: LIBRARY.page,
76
- kind: WorkcardKind.Specification,
77
- version: 1,
78
- fields: { type: 'object', additionalProperties: true },
79
- flows: [LIBRARY.pageLife],
80
- specifications: [],
81
- },
82
- ]
14
+ const createBook = async (
15
+ branch: string, title: string = 'A book', extra: Record<string, unknown> = {}
16
+ ): Promise<Workcard> => (await planning.execute({
17
+ card: { kind: WorkcardKind.Card, type: LIBRARY.book, parent: branch, title, ...extra },
18
+ action: TransitionAction.Create,
19
+ }, { wait: true })).card!
83
20
 
84
- /** The types and flows every conformance case runs against — register it on the service under test. */
85
- export const planningConformancePlugin: PlanningPlugin = Object.freeze({
86
- name: 'planning-conformance',
87
- order: 0,
88
- schemas: { types, flows },
89
- })
21
+ const transit = async (
22
+ card: string, transition: string, extra: { flow?: string } = {}
23
+ ): Promise<TransitionReceipt> => await planning.execute({
24
+ card, action: TransitionAction.Transit, transition, ...extra,
25
+ }, { wait: true })
90
26
 
91
- /**
92
- * A clock that never repeats an instant — what a conformance service is booted with, so an
93
- * `updatedSince` or an ordering by `at` is deterministic on a fast store.
94
- */
95
- export const conformanceClock = (): (() => string) => {
96
- let last = 0
97
- return () => {
98
- last = Math.max(Date.now(), last + 1)
99
- return new Date(last).toISOString()
100
- }
27
+ return { createBranch, createBook, transit }
101
28
  }
102
29
 
30
+ /** The fixtures of one facade — one per facade. */
31
+ export const conformanceFixturesOf = memoHelper.oncePer(makeConformanceFixtures)
32
+
33
+ /** @deprecated compat:factory-refactor — use `conformanceFixturesOf(planning).createBranch(…)` */
103
34
  export const createBranch = async (
104
35
  planning: PlanningFacade, title: string = 'Riverside branch', extra: Record<string, unknown> = {}
105
- ): Promise<Workcard> => (await planning.execute({
106
- card: { kind: WorkcardKind.Project, type: LIBRARY.branch, title, ...extra },
107
- action: TransitionAction.Create,
108
- }, { wait: true })).card!
36
+ ): Promise<Workcard> => await conformanceFixturesOf(planning).createBranch(title, extra)
109
37
 
38
+ /** @deprecated compat:factory-refactor — use `conformanceFixturesOf(planning).createBook(…)` */
110
39
  export const createBook = async (
111
40
  planning: PlanningFacade, branch: string, title: string = 'A book', extra: Record<string, unknown> = {}
112
- ): Promise<Workcard> => (await planning.execute({
113
- card: { kind: WorkcardKind.Card, type: LIBRARY.book, parent: branch, title, ...extra },
114
- action: TransitionAction.Create,
115
- }, { wait: true })).card!
41
+ ): Promise<Workcard> => await conformanceFixturesOf(planning).createBook(branch, title, extra)
116
42
 
43
+ /** @deprecated compat:factory-refactor — use `conformanceFixturesOf(planning).transit(…)` */
117
44
  export const transit = async (
118
45
  planning: PlanningFacade, card: string, transition: string, extra: { flow?: string } = {}
119
- ): Promise<TransitionReceipt> => await planning.execute({
120
- card, action: TransitionAction.Transit, transition, ...extra,
121
- }, { wait: true })
46
+ ): Promise<TransitionReceipt> => await conformanceFixturesOf(planning).transit(card, transition, extra)
@@ -7,5 +7,8 @@
7
7
  export * from './assert.js'
8
8
  export * from './cases.js'
9
9
  export type * from './types.js'
10
+ export type * from './assert/types.js'
11
+ export type * from './fixtures/types.js'
12
+ export * from './clock.js'
10
13
  export * from './fixtures.js'
11
14
  export * from './consts.js'