@owlmeans/planning 0.1.18-rc.2 → 0.1.18-rc.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -26
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/planning/SKILL.md +157 -45
- package/build/aliases/types.d.ts +49 -0
- package/build/aliases/types.d.ts.map +1 -0
- package/build/aliases/types.js +2 -0
- package/build/aliases/types.js.map +1 -0
- package/build/aliases.d.ts +4 -0
- package/build/aliases.d.ts.map +1 -0
- package/build/aliases.js +27 -0
- package/build/aliases.js.map +1 -0
- package/build/consts.d.ts +23 -34
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +26 -25
- package/build/consts.js.map +1 -1
- package/build/entrypoints/types.d.ts +96 -0
- package/build/entrypoints/types.d.ts.map +1 -0
- package/build/entrypoints/types.js +2 -0
- package/build/entrypoints/types.js.map +1 -0
- package/build/entrypoints.d.ts +5 -1
- package/build/entrypoints.d.ts.map +1 -1
- package/build/entrypoints.js +17 -6
- package/build/entrypoints.js.map +1 -1
- package/build/errors.d.ts +41 -0
- package/build/errors.d.ts.map +1 -1
- package/build/errors.js +61 -0
- package/build/errors.js.map +1 -1
- package/build/helpers/apply/consts.local.d.ts +7 -0
- package/build/helpers/apply/consts.local.d.ts.map +1 -0
- package/build/helpers/apply/consts.local.js +9 -0
- package/build/helpers/apply/consts.local.js.map +1 -0
- package/build/helpers/apply/types.d.ts +32 -0
- package/build/helpers/apply/types.d.ts.map +1 -0
- package/build/helpers/apply/types.js +2 -0
- package/build/helpers/apply/types.js.map +1 -0
- package/build/helpers/apply.d.ts +5 -23
- package/build/helpers/apply.d.ts.map +1 -1
- package/build/helpers/apply.js +121 -143
- package/build/helpers/apply.js.map +1 -1
- package/build/helpers/card/types.d.ts +20 -0
- package/build/helpers/card/types.d.ts.map +1 -0
- package/build/helpers/card/types.js +2 -0
- package/build/helpers/card/types.js.map +1 -0
- package/build/helpers/card.d.ts +11 -7
- package/build/helpers/card.d.ts.map +1 -1
- package/build/helpers/card.js +41 -28
- package/build/helpers/card.js.map +1 -1
- package/build/helpers/changes/consts.local.d.ts +13 -0
- package/build/helpers/changes/consts.local.d.ts.map +1 -0
- package/build/helpers/changes/consts.local.js +13 -0
- package/build/helpers/changes/consts.local.js.map +1 -0
- package/build/helpers/changes/types.d.ts +58 -0
- package/build/helpers/changes/types.d.ts.map +1 -0
- package/build/helpers/changes/types.js +2 -0
- package/build/helpers/changes/types.js.map +1 -0
- package/build/helpers/changes.d.ts +11 -42
- package/build/helpers/changes.d.ts.map +1 -1
- package/build/helpers/changes.js +234 -251
- package/build/helpers/changes.js.map +1 -1
- package/build/helpers/code/types.d.ts +27 -0
- package/build/helpers/code/types.d.ts.map +1 -0
- package/build/helpers/code/types.js +2 -0
- package/build/helpers/code/types.js.map +1 -0
- package/build/helpers/code.d.ts +6 -17
- package/build/helpers/code.d.ts.map +1 -1
- package/build/helpers/code.js +39 -40
- package/build/helpers/code.js.map +1 -1
- package/build/helpers/index.d.ts +12 -0
- package/build/helpers/index.d.ts.map +1 -1
- package/build/helpers/index.js +2 -0
- package/build/helpers/index.js.map +1 -1
- package/build/helpers/query/types.d.ts +23 -0
- package/build/helpers/query/types.d.ts.map +1 -0
- package/build/helpers/query/types.js +2 -0
- package/build/helpers/query/types.js.map +1 -0
- package/build/helpers/query.d.ts +25 -17
- package/build/helpers/query.d.ts.map +1 -1
- package/build/helpers/query.js +108 -280
- package/build/helpers/query.js.map +1 -1
- package/build/helpers/scoped/types.d.ts +59 -0
- package/build/helpers/scoped/types.d.ts.map +1 -0
- package/build/helpers/scoped/types.js +2 -0
- package/build/helpers/scoped/types.js.map +1 -0
- package/build/helpers/scoped.d.ts +24 -0
- package/build/helpers/scoped.d.ts.map +1 -0
- package/build/helpers/scoped.js +197 -0
- package/build/helpers/scoped.js.map +1 -0
- package/build/helpers/specification/types.d.ts +24 -0
- package/build/helpers/specification/types.d.ts.map +1 -0
- package/build/helpers/specification/types.js +2 -0
- package/build/helpers/specification/types.js.map +1 -0
- package/build/helpers/specification.d.ts +9 -13
- package/build/helpers/specification.d.ts.map +1 -1
- package/build/helpers/specification.js +47 -44
- package/build/helpers/specification.js.map +1 -1
- package/build/helpers/status/types.d.ts +48 -0
- package/build/helpers/status/types.d.ts.map +1 -0
- package/build/helpers/status/types.js +2 -0
- package/build/helpers/status/types.js.map +1 -0
- package/build/helpers/status.d.ts +16 -29
- package/build/helpers/status.d.ts.map +1 -1
- package/build/helpers/status.js +77 -73
- package/build/helpers/status.js.map +1 -1
- package/build/helpers/types.local.d.ts +17 -0
- package/build/helpers/types.local.d.ts.map +1 -0
- package/build/helpers/types.local.js +2 -0
- package/build/helpers/types.local.js.map +1 -0
- package/build/helpers/validate/types.d.ts +30 -0
- package/build/helpers/validate/types.d.ts.map +1 -0
- package/build/helpers/validate/types.js +2 -0
- package/build/helpers/validate/types.js.map +1 -0
- package/build/helpers/validate.d.ts +8 -19
- package/build/helpers/validate.d.ts.map +1 -1
- package/build/helpers/validate.js +74 -79
- package/build/helpers/validate.js.map +1 -1
- package/build/helpers/wire/types.d.ts +28 -0
- package/build/helpers/wire/types.d.ts.map +1 -0
- package/build/helpers/wire/types.js +2 -0
- package/build/helpers/wire/types.js.map +1 -0
- package/build/helpers/wire.d.ts +4 -0
- package/build/helpers/wire.d.ts.map +1 -0
- package/build/helpers/wire.js +215 -0
- package/build/helpers/wire.js.map +1 -0
- package/build/i18n/be.json +5 -0
- package/build/i18n/de.json +5 -0
- package/build/i18n/en.json +5 -0
- package/build/i18n/es.json +5 -0
- package/build/i18n/fr.json +56 -0
- package/build/i18n/pl.json +5 -0
- package/build/i18n/ru.json +5 -0
- package/build/i18n/uk.json +5 -0
- package/build/i18n.js +5 -4
- package/build/i18n.js.map +1 -1
- package/build/index.d.ts +4 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -0
- package/build/index.js.map +1 -1
- package/build/models/index.d.ts +2 -3
- package/build/models/index.d.ts.map +1 -1
- package/build/models/index.js +1 -15
- package/build/models/index.js.map +1 -1
- package/build/models/model.d.ts +5 -0
- package/build/models/model.d.ts.map +1 -0
- package/build/models/model.js +16 -0
- package/build/models/model.js.map +1 -0
- package/build/models/project.d.ts +2 -1
- package/build/models/project.d.ts.map +1 -1
- package/build/models/project.js.map +1 -1
- package/build/models/specification.d.ts +2 -1
- package/build/models/specification.d.ts.map +1 -1
- package/build/models/specification.js.map +1 -1
- package/build/models/types.d.ts +69 -0
- package/build/models/types.d.ts.map +1 -0
- package/build/models/types.js +2 -0
- package/build/models/types.js.map +1 -0
- package/build/models/workcard.d.ts +2 -1
- package/build/models/workcard.d.ts.map +1 -1
- package/build/models/workcard.js +12 -12
- package/build/models/workcard.js.map +1 -1
- package/build/schemas.d.ts +10 -2
- package/build/schemas.d.ts.map +1 -1
- package/build/schemas.js +87 -1
- package/build/schemas.js.map +1 -1
- package/build/types.d.ts +199 -148
- package/build/types.d.ts.map +1 -1
- package/build/utils.d.ts +6 -0
- package/build/utils.d.ts.map +1 -0
- package/build/utils.js +6 -0
- package/build/utils.js.map +1 -0
- package/package.json +11 -11
- package/src/aliases/types.ts +38 -0
- package/src/aliases.ts +31 -0
- package/src/consts.ts +26 -25
- package/src/entrypoints/types.ts +68 -0
- package/src/entrypoints.ts +34 -14
- package/src/errors.ts +71 -0
- package/src/helpers/apply/consts.local.ts +10 -0
- package/src/helpers/apply/types.ts +32 -0
- package/src/helpers/apply.ts +135 -152
- package/src/helpers/card/types.ts +20 -0
- package/src/helpers/card.ts +59 -33
- package/src/helpers/changes/consts.local.ts +17 -0
- package/src/helpers/changes/types.ts +66 -0
- package/src/helpers/changes.ts +267 -280
- package/src/helpers/code/types.ts +31 -0
- package/src/helpers/code.ts +52 -48
- package/src/helpers/index.ts +13 -0
- package/src/helpers/query/types.ts +26 -0
- package/src/helpers/query.ts +144 -314
- package/src/helpers/scoped/types.ts +64 -0
- package/src/helpers/scoped.ts +257 -0
- package/src/helpers/specification/types.ts +26 -0
- package/src/helpers/specification.ts +69 -51
- package/src/helpers/status/types.ts +46 -0
- package/src/helpers/status.ts +117 -93
- package/src/helpers/types.local.ts +18 -0
- package/src/helpers/validate/types.ts +30 -0
- package/src/helpers/validate.ts +84 -77
- package/src/helpers/wire/types.ts +31 -0
- package/src/helpers/wire.ts +251 -0
- package/src/i18n/be.json +5 -0
- package/src/i18n/de.json +5 -0
- package/src/i18n/en.json +5 -0
- package/src/i18n/es.json +5 -0
- package/src/i18n/fr.json +56 -0
- package/src/i18n/pl.json +5 -0
- package/src/i18n/ru.json +5 -0
- package/src/i18n/uk.json +5 -0
- package/src/i18n.ts +6 -4
- package/src/index.ts +4 -0
- package/src/models/index.ts +2 -17
- package/src/models/model.ts +18 -0
- package/src/models/project.ts +2 -1
- package/src/models/specification.ts +2 -1
- package/src/models/types.ts +70 -0
- package/src/models/workcard.ts +14 -16
- package/src/schemas.ts +98 -2
- package/src/types.ts +207 -126
- package/src/utils.ts +9 -0
- package/tests/apply.spec.ts +95 -31
- package/tests/code.spec.ts +9 -9
- package/tests/entrypoints.spec.ts +18 -2
- package/tests/error-status.spec.ts +6 -1
- package/tests/models.spec.ts +16 -15
- package/tests/query.spec.ts +13 -15
- package/tests/schemas.spec.ts +8 -8
- package/tests/scoped.spec.ts +154 -0
- package/tests/status.spec.ts +31 -22
package/README.md
CHANGED
|
@@ -9,20 +9,21 @@ the executor with `@owlmeans/server-planning` and reads remotely with `@owlmeans
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
|
-
bun add @owlmeans/planning@^0.1.18-rc.
|
|
12
|
+
bun add @owlmeans/planning@^0.1.18-rc.20 ajv ajv-formats
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
## Concepts
|
|
16
16
|
|
|
17
17
|
- **Transition** — the only way a record changes: an append-only event; a card is the fold of its
|
|
18
|
-
transitions in `seq` order (`applyTransition`).
|
|
18
|
+
transitions in `seq` order (`applyHelper.applyTransition`).
|
|
19
19
|
- **Workcard** — base fields at the top level (`title`, `status`, `parent`, `parents`, `labels`,
|
|
20
20
|
`order`); a type's own fields under `fields`, validated by the type's schema.
|
|
21
21
|
- **Status flow** — shareable data declaring statuses (each mapped to `planned`, `in-progress` or
|
|
22
22
|
`closed`) and named transitions; a type may run several flows.
|
|
23
23
|
- **Specification** — a document workcard held in a slot the parent type declares; a revisioned
|
|
24
24
|
slot revises one record in place.
|
|
25
|
-
- **Wire query** — the scalar form a query travels in over HTTP (`encode*Query` /
|
|
25
|
+
- **Wire query** — the scalar form a query travels in over HTTP (`wireHelper.encode*Query` /
|
|
26
|
+
`wireHelper.decode*Query`).
|
|
26
27
|
|
|
27
28
|
## Usage
|
|
28
29
|
|
|
@@ -69,13 +70,27 @@ export const planningProtocols = makePlanningProtocols({
|
|
|
69
70
|
Encode a query on the client, decode it in a handler:
|
|
70
71
|
|
|
71
72
|
```ts
|
|
72
|
-
import {
|
|
73
|
+
import { queryHelper, wireHelper } from '@owlmeans/planning'
|
|
73
74
|
|
|
74
|
-
const query = encodeWorkcardQuery({ parent: projectId, status: ['todo', 'doing'], sort: ['order'] })
|
|
75
|
+
const query = wireHelper.encodeWorkcardQuery({ parent: projectId, status: ['todo', 'doing'], sort: ['order'] })
|
|
75
76
|
const page = await context.entrypoint(planningProtocols.card.list).call({ query })
|
|
76
77
|
|
|
77
78
|
// server side
|
|
78
|
-
const where = criteriaOf(decodeWorkcardQuery(req.query), { entityId })
|
|
79
|
+
const where = queryHelper.criteriaOf(wireHelper.decodeWorkcardQuery(req.query), { entityId })
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Create a card and read it back through a facade — in process the one of `@owlmeans/server-planning`
|
|
83
|
+
(`ctx.service<PlanningHostService>(PLANNING_SERVICE).for({ entityId, profileId })`), remotely the one
|
|
84
|
+
of `@owlmeans/client-planning`:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { TransitionAction, WorkcardKind } from '@owlmeans/planning'
|
|
88
|
+
|
|
89
|
+
const drill = await planning.execute({
|
|
90
|
+
action: TransitionAction.Create,
|
|
91
|
+
card: { kind: WorkcardKind.Card, type: 'shed:tool', parent: shedId, title: 'Cordless drill' },
|
|
92
|
+
}, { wait: true }) // the options are the SECOND argument
|
|
93
|
+
const again = await planning.cards.get(drill.card!.id!)
|
|
79
94
|
```
|
|
80
95
|
|
|
81
96
|
Work through a model — the same code on either side of the wire:
|
|
@@ -93,10 +108,10 @@ await task.update({ title: 'Renamed' }) // expectSeq defaults to the record's he
|
|
|
93
108
|
Fold transitions yourself (a mirror, a test, a store):
|
|
94
109
|
|
|
95
110
|
```ts
|
|
96
|
-
import {
|
|
111
|
+
import { applyHelper } from '@owlmeans/planning'
|
|
97
112
|
|
|
98
|
-
const card = transitions.reduce((current, transition) => applyTransition(current, transition), undefined)
|
|
99
|
-
const links = transitions.reduce(applyRelationship, [])
|
|
113
|
+
const card = transitions.reduce((current, transition) => applyHelper.applyTransition(current, transition), undefined)
|
|
114
|
+
const links = transitions.reduce(applyHelper.applyRelationship, [])
|
|
100
115
|
```
|
|
101
116
|
|
|
102
117
|
## API
|
|
@@ -107,48 +122,76 @@ const links = transitions.reduce(applyRelationship, [])
|
|
|
107
122
|
- Write path and feed: `TransitionExecution`, `WorkcardDraft`, `ExecuteRequest`,
|
|
108
123
|
`TransitionReceipt`, `TransitionReceiptView`, `CommitEvent`, `CommitStatus`, `CommitSource`.
|
|
109
124
|
- Ports and seam: `TransitionStore`, `ProjectionStore`, `SpecificationStore`, `RelationshipStore`,
|
|
110
|
-
`PlanningStore`, `PlanningPlugin`, `PlanningScope`, `PlanningFacade`,
|
|
125
|
+
`SchemaStore`, `PlanningStore`, `PlanningPlugin`, `PlanningScope`, `PlanningFacade`,
|
|
126
|
+
`PlanningService`, `PlanningDefinitions`.
|
|
127
|
+
- Data-defined types and flows: `ScopedSchemaRecord`, `ScopedSchemaWhere`, `ScopedSchemaBundle`,
|
|
128
|
+
`ScopedSchemaRegistry`, `SchemaDeclarations`, `SchemaWriteOptions`, `SchemaDefineRequest`,
|
|
129
|
+
`SchemaDefineReply`, and `scopedSchemaHelper` (`resolveScopedBundle`, `scopedRegistryOf`,
|
|
130
|
+
`assertTypeSchema`, `assertFlowSchema`, `assertOverridable`, `flowInUse`, `schemaRecordKey`,
|
|
131
|
+
`schemaKeyOf`).
|
|
111
132
|
- Enums and constants: `WorkcardKind`, `IntrinsicStatus`, `IntrinsicPolicy`, `TransitionAction`,
|
|
112
|
-
`CommitState`, `SpecificationFormat`, `CodeStyle`, `CodeScope`, `
|
|
113
|
-
`
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
-
|
|
119
|
-
|
|
120
|
-
`
|
|
121
|
-
|
|
122
|
-
-
|
|
123
|
-
|
|
124
|
-
|
|
133
|
+
`CommitState`, `SpecificationFormat`, `CodeStyle`, `CodeScope`, `PlanningSchemaKind`,
|
|
134
|
+
`SchemaOrigin`, `SchemaWriteMode`, `PLANNING_SERVICE`, `PLANNING_PATH`, `PLANNING_COMMIT_EVENT`,
|
|
135
|
+
limits (`TITLE_MAX`, …); `planningAliasHelper` (`planningAliases`, `planningDefinitionAliases`).
|
|
136
|
+
- Helpers — each a ready object (`xxxHelper`) built by its `createXxxHelper()`, its interface
|
|
137
|
+
exported beside it:
|
|
138
|
+
- `applyHelper` — the fold: `applyTransition`, `applyRelationship`.
|
|
139
|
+
- `changesHelper` — `computeChanges`, `isEmptyChange`, `assertMutable`, `mergeFields`,
|
|
140
|
+
`applyUnset`, `sameValue`.
|
|
141
|
+
- `cardHelper` — `isProject`, `isCard`, `isSpecification`, `normalizeParents`, `cardDefaults`,
|
|
142
|
+
`isPending`, `parentOf`, `projectOf`.
|
|
143
|
+
- `statusHelper` — `intrinsicOf`, `initialStatusOf`, `ruleOf`, `canTransit`, `transitionsFrom`,
|
|
144
|
+
`initialFlowsOf`, `resolveIntrinsic`, `primaryFlowOf`, `flowIdsOf`, `statusDefinitionOf`,
|
|
145
|
+
`isTerminal`.
|
|
146
|
+
- `queryHelper` — `criteriaOf`, `listOptionsOf`, `specCriteriaOf`, `linkWhereOf`,
|
|
147
|
+
`transitionWhereOf`, `summaryOf`.
|
|
148
|
+
- `wireHelper` — `encode/decodeWorkcardQuery`, `encode/decodeSummaryQuery`,
|
|
149
|
+
`encode/decodeTransitionQuery`, `encode/decodeSpecificationQuery`,
|
|
150
|
+
`encode/decodeRelationshipQuery`, `encode/decodeList`, `encode/decodeSort`.
|
|
151
|
+
- `codeHelper` — `mintCode`, `codeScopeOf`, `slugOf`.
|
|
152
|
+
- `specificationHelper` — `slotOf`, `currentSpecification`, `nextRevision`, `isRevisioned`,
|
|
153
|
+
`bodyCharsOf`, `specificationTypeOf`.
|
|
154
|
+
- `validateHelper` — `validateFields`, `validateCard`, `validateSpecificationBody`,
|
|
155
|
+
`invalidFieldKeys`, `ajvErrorText`; and `makeAjv`.
|
|
156
|
+
- The former plain functions (`applyTransition`, `criteriaOf`, `encodeWorkcardQuery`, …) remain as
|
|
157
|
+
deprecated delegates to these helpers.
|
|
125
158
|
- Registry, protocols, models: `makeSchemaRegistry`, `makePlanningProtocols`, `makeWorkcardModel`,
|
|
126
159
|
`makeProjectModel`, `makeSpecificationModel`, `modelOf`.
|
|
127
160
|
- Errors: `PlanningError`, `WorkcardNotFound`, `UnknownWorkcardType`, `UnknownStatusFlow`,
|
|
128
161
|
`ParentNotFound`, `CardTypeNotAllowed`, `PlanningRefused`, `IllegalTransition`, `FieldsInvalid`,
|
|
129
162
|
`LabelNotAllowed`, `SpecificationSlotUnknown`, `SpecificationRevisionConflict`,
|
|
130
163
|
`RelationshipRefused`, `CodeTaken`, `WorkcardConflict`, `CommitTimeout`, `CommitFailed`,
|
|
131
|
-
`PlanningScopeMismatch`, `
|
|
164
|
+
`PlanningScopeMismatch`, `SchemaConflict`, `SchemaInUse`, `SchemaSealed`, `SchemaInvalid`,
|
|
165
|
+
`PlanningForbidden`, `PlanningUnsupported`.
|
|
132
166
|
- Schemas: `WorkcardSchema`, `SpecificationSchema`, `TransitionSchema`, `ExecuteRequestSchema`,
|
|
133
167
|
`WorkcardQuerySchema`, … (every record, declaration, request and view).
|
|
134
168
|
|
|
135
169
|
## Common pitfalls
|
|
136
170
|
|
|
171
|
+
- A create's draft goes under `card` (never `draft`), and `{ wait, timeout }` is the second argument
|
|
172
|
+
of `execute` — inside the execution an in-process facade ignores it.
|
|
173
|
+
- A type or flow declaration has no `name` or `key`: the display name is `label`, the key is `type`
|
|
174
|
+
(a type) or `id` (a flow); a status has `key`, a transition rule `name`. The declaration schemas
|
|
175
|
+
are closed — `definitions.define` refuses an extra key with `SchemaInvalid`.
|
|
137
176
|
- Pass a query through `encode*Query` before an HTTP call — arrays and objects do not survive a
|
|
138
177
|
query string as they are.
|
|
139
178
|
- `status`, `intrinsic` and `flows` move only through `transit`; an `update` naming them is refused.
|
|
179
|
+
- `createdBy` is written once, by a create's draft: `changes` or `unset` naming it is refused on
|
|
180
|
+
every action (`planning:immutable:createdBy`), so an ownership check can trust it.
|
|
140
181
|
- `fields` and `flows` in `changes` merge; every other key replaces. Clear with `null` or `unset`.
|
|
141
182
|
- A model's `expectSeq` is its record's head: a stale model refuses with `WorkcardConflict`.
|
|
142
183
|
- `WorkcardNotFound` is also the answer for another entity's card — never read it as "deleted".
|
|
143
184
|
- `CommitTimeout` leaves the transition pending; it will still commit.
|
|
144
185
|
- Keep schemas `$jsonSchema`-safe: no `integer`, every optional property nullable.
|
|
186
|
+
- Only card types and flows are data-defined, and a code key needs `overridable: true` to be
|
|
187
|
+
overridden; a data-defined declaration's `version` is its compare-and-set token.
|
|
145
188
|
|
|
146
189
|
## Related packages
|
|
147
190
|
|
|
148
191
|
- `@owlmeans/server-planning` — executor, plugin registry, memory store, handlers
|
|
149
192
|
- `@owlmeans/client-planning` — remote facade, state mirror, commit waiting
|
|
150
193
|
- `@owlmeans/resource` — the criteria language
|
|
151
|
-
- `@owlmeans/
|
|
194
|
+
- `@owlmeans/planning-postgres` — the durable Postgres store
|
|
152
195
|
|
|
153
196
|
<!-- owlmeans:agent-guidance:start -->
|
|
154
197
|
## Agent guidance
|
|
@@ -158,7 +201,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
|
|
|
158
201
|
your project's skill store (`.agents/skills/`):
|
|
159
202
|
|
|
160
203
|
```sh
|
|
161
|
-
npx @owlmeans/agent-skills@^0.1.18-rc.
|
|
204
|
+
npx @owlmeans/agent-skills@^0.1.18-rc.51
|
|
162
205
|
```
|
|
163
206
|
|
|
164
207
|
The embedded files are version-matched to this package release. Do not edit them
|
package/agent-meta/manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
3
|
"package": "@owlmeans/planning",
|
|
4
|
-
"version": "0.1.18-rc.
|
|
5
|
-
"generatedAt": "2026-
|
|
4
|
+
"version": "0.1.18-rc.20",
|
|
5
|
+
"generatedAt": "2026-10-06T11:45:57.665Z",
|
|
6
6
|
"canonicalRepo": "https://github.com/owlmeans/common",
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: planning
|
|
3
|
-
description: How to use @owlmeans/planning — the runtime-free workcard model (cards, projects, specifications), typed relationships, shareable status flows with three intrinsic states, the Transition event and its pure fold, the schema registry, the query language and its wire encoding, the protocol tree, and the models that work identically on a server and in a browser. Auto-invoked when importing a Workcard type, a status flow,
|
|
3
|
+
description: How to use @owlmeans/planning — the runtime-free workcard model (cards, projects, specifications), typed relationships, shareable status flows with three intrinsic states, the Transition event and its pure fold, the schema registry, the query language and its wire encoding, the protocol tree, and the models that work identically on a server and in a browser. Auto-invoked when importing a Workcard type, a status flow, applyHelper, changesHelper, queryHelper, wireHelper, makePlanningProtocols or a planning model.
|
|
4
4
|
user-invocable: false
|
|
5
5
|
---
|
|
6
6
|
<!-- AUTO-GENERATED — do not edit. Regenerate via sync-agent-meta. -->
|
|
@@ -8,7 +8,7 @@ user-invocable: false
|
|
|
8
8
|
# @owlmeans/planning
|
|
9
9
|
|
|
10
10
|
**Layer:** Cross-cutting domain
|
|
11
|
-
**Install:** `"@owlmeans/planning": "^0.1.18-rc.
|
|
11
|
+
**Install:** `"@owlmeans/planning": "^0.1.18-rc.20"` in `dependencies` (`ajv` and `ajv-formats` are peers)
|
|
12
12
|
|
|
13
13
|
The contracts of project planning: record shapes, schemas, refusals, the protocol tree, the pure
|
|
14
14
|
fold and the models. No database, no fastify, no React. The executor, the plugin registry, the
|
|
@@ -19,9 +19,27 @@ mirror are `@owlmeans/client-planning`; a durable store implements the ports dec
|
|
|
19
19
|
|
|
20
20
|
Every change is a **transition** — an append-only event — and a card IS the fold of its
|
|
21
21
|
transitions in `seq` order. There is one write path (`facade.execute(exec)`), one fold
|
|
22
|
-
(`applyTransition`) and one query translation (`criteriaOf`), shared by every store and both sides
|
|
22
|
+
(`applyHelper.applyTransition`) and one query translation (`queryHelper.criteriaOf`), shared by every store and both sides
|
|
23
23
|
of the wire, so one log always means one card.
|
|
24
24
|
|
|
25
|
+
## The write call
|
|
26
|
+
|
|
27
|
+
`facade.execute(execution, options?)` — the same signature on the server facade
|
|
28
|
+
(`@owlmeans/server-planning`, reached as `ctx.service<PlanningHostService>(PLANNING_SERVICE).for({
|
|
29
|
+
entityId, profileId })`) and the remote one (`@owlmeans/client-planning`).
|
|
30
|
+
|
|
31
|
+
- The **execution** has exactly these keys — `TransitionExecutionSchema` is closed, but only the
|
|
32
|
+
wire validates against it; in process an unknown key is silently ignored: `card` — the `WorkcardDraft` of a
|
|
33
|
+
create (`kind`, `type`, `title` required; `parent`/`parents`, `description`, `code`, `labels`,
|
|
34
|
+
`order`, `fields`, `status`, and a specification's `category`/`format`/`body`), the card id
|
|
35
|
+
otherwise — and `action` (`TransitionAction`), then per action `transition`, `flow`, `changes`,
|
|
36
|
+
`unset`, `link` / `links`, and `cause`, `key`, `expectSeq`.
|
|
37
|
+
- The **options** are `{ wait?, timeout? }`, always the second argument. Only the wire body
|
|
38
|
+
(`ExecuteRequest`) carries them beside the execution.
|
|
39
|
+
|
|
40
|
+
The worked example (facade, create, read back, transit, a data-defined type and flow) and the wrong
|
|
41
|
+
forms generated code keeps writing are `server-planning` → Creating and reading a card.
|
|
42
|
+
|
|
25
43
|
## Key exports
|
|
26
44
|
|
|
27
45
|
| Export | What it is |
|
|
@@ -29,20 +47,22 @@ of the wire, so one log always means one card.
|
|
|
29
47
|
| `Workcard`, `Card`, `Project`, `Specification`, `Relationship`, `Transition` | The records |
|
|
30
48
|
| `StatusFlowSchema`, `WorkcardTypeSchema`, `ProjectTypeSchema`, `SpecificationSlot`, `CodePolicy`, `RelationshipType`, `PlanningSchemaBundle` | Type declarations — data, not code |
|
|
31
49
|
| `TransitionExecution`, `WorkcardDraft`, `TransitionReceipt`, `CommitEvent`, `CommitSource` | The write path and its commit feed |
|
|
32
|
-
| `TransitionStore`, `ProjectionStore`, `SpecificationStore`, `RelationshipStore`, `PlanningStore` | Ports a store implements (only `cards` is required) |
|
|
33
|
-
| `PlanningPlugin`, `PlanningScope`, `PlanningFacade`, `PlanningService` | The plugin seam and the facade, typed here so a plugin and a client can name them |
|
|
34
|
-
| `
|
|
35
|
-
| `
|
|
50
|
+
| `TransitionStore`, `ProjectionStore`, `SpecificationStore`, `RelationshipStore`, `SchemaStore`, `PlanningStore` | Ports a store implements (only `cards` is required) |
|
|
51
|
+
| `PlanningPlugin`, `PlanningScope`, `PlanningFacade`, `PlanningService`, `PlanningDefinitions` | The plugin seam and the facade, typed here so a plugin and a client can name them |
|
|
52
|
+
| `ScopedSchemaRecord`, `ScopedSchemaWhere`, `ScopedSchemaBundle`, `ScopedSchemaRegistry`, `SchemaDeclarations`, `SchemaWriteOptions`, `SchemaScope`, `SchemaKey`, `SchemaListQuery`, `SchemaDefineRequest`, `SchemaDefineReply` | Data-defined types and flows |
|
|
53
|
+
| `WorkcardKind`, `IntrinsicStatus`, `IntrinsicPolicy`, `TransitionAction`, `CommitState`, `SpecificationFormat`, `CodeStyle`, `CodeScope`, `PlanningSchemaKind`, `SchemaOrigin`, `SchemaWriteMode` | Enums |
|
|
54
|
+
| `PLANNING_SERVICE` (`'planning'`), `PLANNING_PATH`, `PLANNING_COMMIT_EVENT` (`'planning-commit'`), `planningAliasHelper.planningAliases(base)`, `planningAliasHelper.planningDefinitionAliases(base)` | Names both sides share |
|
|
36
55
|
| `TITLE_MAX` 2048, `DESCRIPTION_MAX` 16384, `CODE_MAX` 64, `BODY_MAX` 1048576, `MAX_LABELS` 32, `MAX_PARENTS` 32, `DEFAULT_COMMIT_TIMEOUT` 30 s, `DEFAULT_COMMIT_POLL` 20 s, `MAX_COMMIT_POLL` 55 s, `CODE_MINT_ATTEMPTS` 8 | Limits |
|
|
37
|
-
| `applyTransition`, `applyRelationship` | The fold |
|
|
38
|
-
| `computeChanges`, `isEmptyChange`, `assertMutable`, `mergeFields`, `applyUnset`, `sameValue` | What a transition records |
|
|
39
|
-
| `intrinsicOf`, `initialStatusOf`, `ruleOf`, `canTransit`, `transitionsFrom`, `initialFlowsOf`, `resolveIntrinsic`, `primaryFlowOf`, `isTerminal` | Status helpers |
|
|
40
|
-
| `criteriaOf`, `listOptionsOf`, `specCriteriaOf`, `linkWhereOf`, `transitionWhereOf`, `summaryOf` | Querying |
|
|
41
|
-
| `encode*Query` / `decode*Query` (Workcard, Summary, Transition, Specification, Relationship), `encodeSort`, `decodeSort`, `encodeList`, `decodeList` | The wire form of a query |
|
|
42
|
-
| `mintCode`, `codeScopeOf`, `slugOf` | Codes |
|
|
43
|
-
| `slotOf`, `currentSpecification`, `nextRevision`, `specificationTypeOf`, `bodyCharsOf` | Specifications |
|
|
44
|
-
| `makeAjv`, `validateFields`, `validateCard`, `validateSpecificationBody`, `invalidFieldKeys`, `ajvErrorText` | Validation |
|
|
56
|
+
| `applyHelper` — `applyTransition`, `applyRelationship` | The fold |
|
|
57
|
+
| `changesHelper` — `computeChanges`, `isEmptyChange`, `assertMutable`, `mergeFields`, `applyUnset`, `sameValue` | What a transition records |
|
|
58
|
+
| `statusHelper` — `intrinsicOf`, `initialStatusOf`, `ruleOf`, `canTransit`, `transitionsFrom`, `initialFlowsOf`, `resolveIntrinsic`, `primaryFlowOf`, `isTerminal` | Status helpers |
|
|
59
|
+
| `queryHelper` — `criteriaOf`, `listOptionsOf`, `specCriteriaOf`, `linkWhereOf`, `transitionWhereOf`, `summaryOf` | Querying |
|
|
60
|
+
| `wireHelper` — `encode*Query` / `decode*Query` (Workcard, Summary, Transition, Specification, Relationship), `encodeSort`, `decodeSort`, `encodeList`, `decodeList` | The wire form of a query |
|
|
61
|
+
| `codeHelper` — `mintCode`, `codeScopeOf`, `slugOf` | Codes |
|
|
62
|
+
| `specificationHelper` — `slotOf`, `currentSpecification`, `nextRevision`, `specificationTypeOf`, `bodyCharsOf` | Specifications |
|
|
63
|
+
| `makeAjv`, `validateHelper` — `validateFields`, `validateCard`, `validateSpecificationBody`, `invalidFieldKeys`, `ajvErrorText` | Validation |
|
|
45
64
|
| `makeSchemaRegistry(bundle?)` | Types, flows, cached field validators; `bundle()` / `load()` |
|
|
65
|
+
| `scopedSchemaHelper` — `resolveScopedBundle`, `scopedRegistryOf`, `assertTypeSchema`, `assertFlowSchema`, `assertOverridable`, `flowInUse`, `schemaRecordKey`, `schemaKeyOf` | Data-defined layers: resolution, the read-only view, the closed-form checks |
|
|
46
66
|
| `makePlanningProtocols(opts)` | The protocol tree |
|
|
47
67
|
| `makeWorkcardModel`, `makeProjectModel`, `makeSpecificationModel`, `modelOf`, `executeFor` | Models |
|
|
48
68
|
| `*Schema` | AJV schemas of every record, declaration, request and view |
|
|
@@ -52,8 +72,10 @@ of the wire, so one log always means one card.
|
|
|
52
72
|
- **Base fields at the top level, provider fields under `fields`.** `title`, `description`, `code`,
|
|
53
73
|
`status`, `labels`, `order`, `parent` are generic and indexable; everything a type adds lives in
|
|
54
74
|
`fields` and is validated by the type's `fields` JSON schema. Generic code reads only the top.
|
|
55
|
-
A `fields` key never holds `.` or `$` at any depth (`invalidFieldKeys`).
|
|
56
|
-
- **`entityId` is the organization's stable id
|
|
75
|
+
A `fields` key never holds `.` or `$` at any depth (`validateHelper.invalidFieldKeys`).
|
|
76
|
+
- **`entityId` is the organization's stable record id** — never the wire `entitySlug`, never a body
|
|
77
|
+
value. Every read is scoped by it; where a request's comes from is `server-planning` → Scope and
|
|
78
|
+
security.
|
|
57
79
|
- **Membership is `parent` + `parents`, never a relationship.** `parents` always contains `parent`
|
|
58
80
|
(first), so many-to-many membership needs no junction row. `within` queries `parents`.
|
|
59
81
|
- **Specification is a workcard** (`kind: specification`) with `category`, `format`, `body`, `ref`,
|
|
@@ -61,8 +83,13 @@ of the wire, so one log always means one card.
|
|
|
61
83
|
`keepRevisions`, a JSON body `schema` — is declared on the PARENT's type. One record per
|
|
62
84
|
`(parent, category)` unless the slot is `multiple`; a revisioned slot increments `revision` on the
|
|
63
85
|
same record, and earlier bodies come back from the transition log.
|
|
86
|
+
- **`createdBy` is written once, from a create's draft, and never moves.** Named in `changes` or
|
|
87
|
+
`unset` it is refused on every action (`planning:immutable:createdBy`), so an ownership check
|
|
88
|
+
(`card.createdBy === profileId`) can trust it. A card that changes hands keeps that in a field of
|
|
89
|
+
its own type (`fields.assignee`); who made each later write is the transition's `actor`.
|
|
64
90
|
- **`seq` is the last folded transition, `head` the highest allocated.** `head > seq` means a write
|
|
65
|
-
is in flight (`isPending`); optimistic concurrency (`expectSeq`) compares against the
|
|
91
|
+
is in flight (`cardHelper.isPending`); optimistic concurrency (`expectSeq`) compares against the
|
|
92
|
+
head.
|
|
66
93
|
- `order` is a double: a card may sit between two others (`3.5`).
|
|
67
94
|
- Timestamps are ISO-8601 UTC strings (`toISOString()`), so lexicographic order is chronological.
|
|
68
95
|
|
|
@@ -73,10 +100,12 @@ of the wire, so one log always means one card.
|
|
|
73
100
|
- A flow is shareable across types, and a type may run several (`flows[0]` is primary).
|
|
74
101
|
`flows` on the record is authoritative for every flow; `status`/`intrinsic` mirror the primary.
|
|
75
102
|
- A rule name may repeat with different `from` sets (`start` from `planned`, `start` from
|
|
76
|
-
`failed`). `ruleOf` picks the rule naming the current status; a `'*'` rule of that name answers
|
|
103
|
+
`failed`). `statusHelper.ruleOf` picks the rule naming the current status; a `'*'` rule of that name answers
|
|
77
104
|
only when none does, wherever it is declared. `'*'` includes the target status itself, so
|
|
78
|
-
`reset` from `planned` is legal.
|
|
79
|
-
|
|
105
|
+
`reset` from `planned` is legal. A status the flow does NOT declare (a card whose flow changed
|
|
106
|
+
under it) matches every rule of the name, the first declared answering — such a card can always
|
|
107
|
+
move back onto the flow it now runs.
|
|
108
|
+
- `statusHelper.transitionsFrom(flow, status)` answers one rule per name in declaration order;
|
|
80
109
|
`{ explicit: true }` keeps the ones offered to a person.
|
|
81
110
|
- `IntrinsicPolicy.Primary` (default) reads the primary flow; `IntrinsicPolicy.All` takes the least
|
|
82
111
|
advanced flow — closed only when every flow is closed. A status a flow does not declare reads as
|
|
@@ -85,21 +114,72 @@ of the wire, so one log always means one card.
|
|
|
85
114
|
## Declaring a type
|
|
86
115
|
|
|
87
116
|
A `WorkcardTypeSchema` / `ProjectTypeSchema` is plain data registered through a plugin's `schemas`
|
|
88
|
-
or `makeSchemaRegistry({ types, flows })
|
|
117
|
+
or `makeSchemaRegistry({ types, flows })`. **Every declaration schema is closed** — these are ALL
|
|
118
|
+
the keys:
|
|
119
|
+
|
|
120
|
+
| Declaration | Required | Optional |
|
|
121
|
+
|---|---|---|
|
|
122
|
+
| card / specification type | `type` (its key), `kind`, `version`, `fields`, `flows` (at least one), `specifications` | `intrinsic`, `relationships`, `labels`, `code`, `label`, `overridable` |
|
|
123
|
+
| project type | the same, plus `cardTypes` | `projectTypes`, `scopedCardTypes` |
|
|
124
|
+
| flow | `id` (its key), `version`, `statuses` (at least one), `transitions` | `label`, `overridable` |
|
|
125
|
+
| status | `key`, `intrinsic` | `initial`, `terminal`, `label`, `tone` |
|
|
126
|
+
| transition rule | `name`, `from` (status keys or `'*'`), `to` | `label`, `explicit` |
|
|
127
|
+
|
|
128
|
+
The display name is `label` everywhere; a type or a flow has no `name`, `key` or `description`
|
|
129
|
+
(`key` belongs to a status, `name` to a rule). As data, `definitions` refuses an extra key with
|
|
130
|
+
`SchemaInvalid`; in code the registry does not validate a declaration, so only TypeScript's
|
|
131
|
+
excess-property check catches it — keep the literal typed.
|
|
89
132
|
|
|
90
133
|
- `fields` — the AJV schema of `fields`; the registry compiles and caches it (`validator(type)`).
|
|
91
134
|
- `specifications` — the slots this type's cards carry as children. A slot may name its
|
|
92
|
-
specification `type`; otherwise `specificationTypeOf` takes the only registered specification
|
|
135
|
+
specification `type`; otherwise `specificationHelper.specificationTypeOf` takes the only registered specification
|
|
93
136
|
type, then the one sharing the parent type's prefix (`viable:project` → `viable:spec`).
|
|
94
137
|
- `code` — `CodePolicy { prefix?, style: random|sequential|slug, length?, uppercase?, uniqueWithin:
|
|
95
138
|
parent|entity, mutable? }`. A code is fixed once minted unless `mutable`.
|
|
96
139
|
- `relationships` — the edge types a card may start (`single` = at most one per card).
|
|
97
140
|
- `labels` — the allowed labels (any when omitted).
|
|
98
|
-
- A project type lists `cardTypes` (and `projectTypes` for nesting) its children may have
|
|
141
|
+
- A project type lists `cardTypes` (and `projectTypes` for nesting) its children may have;
|
|
142
|
+
`scopedCardTypes: true` also admits card types defined as data (below).
|
|
143
|
+
- `overridable: true` on a code type or flow opens it to a data-defined override.
|
|
144
|
+
|
|
145
|
+
## Data-defined types and flows
|
|
146
|
+
|
|
147
|
+
Card types and status flows may also be DATA — `ScopedSchemaRecord`s a store keeps behind the
|
|
148
|
+
optional `PlanningStore.schemas` port — layered **code → entity → project**:
|
|
149
|
+
|
|
150
|
+
- `code` is the plugin registry (the default), `entity` an organization-wide record
|
|
151
|
+
(`project` absent), `project` a record of one project card. A project record overrides an
|
|
152
|
+
organization one of the same key.
|
|
153
|
+
- **Only card types and flows are data.** A code key is sealed unless its declaration says
|
|
154
|
+
`overridable: true`; project and specification types are always code's (`scopedSchemaHelper.assertOverridable` →
|
|
155
|
+
`SchemaSealed`).
|
|
156
|
+
- A record's `version` is its compare-and-set token and is written into the declaration's own
|
|
157
|
+
`version`: a write lands only at its layer's next version, 1 for a new key (`SchemaConflict`).
|
|
158
|
+
- The closed-form checks are shared by every store: `scopedSchemaHelper.assertFlowSchema` (the declaration's schema,
|
|
159
|
+
unique status keys, every `to` declared, `from` `'*'` or declared statuses) and
|
|
160
|
+
`scopedSchemaHelper.assertTypeSchema` (the `card` kind, a compiling `fields` schema, unique flows that resolve) —
|
|
161
|
+
`SchemaInvalid`.
|
|
162
|
+
- **Retired** means offered for nothing new, still resolved for what uses it: a retired record
|
|
163
|
+
gives way to a live declaration below it, and a key retired in every layer resolves retired —
|
|
164
|
+
present in the bundle's `types`/`flows`, listed in `retired`. A flow cannot be retired while a
|
|
165
|
+
live type of an affected layer still runs it (`scopedSchemaHelper.flowInUse` → `SchemaInUse`).
|
|
166
|
+
- `scopedSchemaHelper.resolveScopedBundle(code, records, scope)` is the ONE resolution (pure);
|
|
167
|
+
`scopedRegistryOf` is its read-only registry (`originOf`, `isRetired`; `register*`/`load` refuse). A
|
|
168
|
+
`ScopedSchemaBundle` extends the plain bundle with `scope`, `revision`, `origins`, `retired`, so a
|
|
169
|
+
reader of the plain bundle is unaffected.
|
|
170
|
+
- `SchemaStore`: `list(where)`, `put(record)` (the CAS), `purge({ entityId, project })`,
|
|
171
|
+
`revision(entityId)` (monotonic per organization), optional `watch(listener)`.
|
|
172
|
+
- `PlanningFacade.definitions` (present only where the store has the port): `bundle(project?)`,
|
|
173
|
+
`registry(project?)`, `records`, `putType`/`putFlow` (CAS on the declaration's version),
|
|
174
|
+
`define` (each at its layer's next version, flows first), `seed` (only the keys the layer lacks),
|
|
175
|
+
`retire(kind, key, { project? })`. The declaration still carries its own `version` (any number
|
|
176
|
+
≥ 1 for `define`/`seed`, which assign the layer's next one; the exact next one for `put*`), and
|
|
177
|
+
a project layer is `{ project: <project card id> }` in the second argument (a `define` call:
|
|
178
|
+
`server-planning` → Creating and reading a card).
|
|
99
179
|
|
|
100
180
|
## The fold
|
|
101
181
|
|
|
102
|
-
`applyTransition(card | undefined, transition)` is pure and total:
|
|
182
|
+
`applyHelper.applyTransition(card | undefined, transition)` is pure and total:
|
|
103
183
|
|
|
104
184
|
- an already-applied seq (`seq <= card.seq`) returns the same card — re-folding is safe;
|
|
105
185
|
- anything but `card.seq + 1` (or seq 1 for a create with no card) throws
|
|
@@ -109,12 +189,15 @@ or `makeSchemaRegistry({ types, flows })`:
|
|
|
109
189
|
replaces** — and clear `unset` (`key`, `fields.key`, `flows.id`); identity and required keys are
|
|
110
190
|
never written or cleared;
|
|
111
191
|
- `link`/`unlink` move only `seq`/`head`/`updatedAt`; `delete` answers `null`;
|
|
112
|
-
- every result carries `seq = transition.seq`, `head = max(head, seq)`, `updatedAt = transition.at
|
|
192
|
+
- every result carries `seq = transition.seq`, `head = max(head, seq)`, `updatedAt = transition.at`;
|
|
193
|
+
- the log is replayed AS WRITTEN: a stored row admission would refuse (an update naming
|
|
194
|
+
`createdBy`) still folds. Guards live at admission (`changesHelper.assertMutable`, the executor), never in the
|
|
195
|
+
fold, so a stored log never stops projecting.
|
|
113
196
|
|
|
114
|
-
`applyRelationship(links, transition)` folds the edges: a create's `links`, `link`, `unlink`, and a
|
|
197
|
+
`applyHelper.applyRelationship(links, transition)` folds the edges: a create's `links`, `link`, `unlink`, and a
|
|
115
198
|
`delete` dropping every edge touching the card. An existing edge is kept, so re-folding adds nothing.
|
|
116
199
|
|
|
117
|
-
`computeChanges(card, exec, type, registry, at, { slot? })` is what the executor records:
|
|
200
|
+
`changesHelper.computeChanges(card, exec, type, registry, at, { slot? })` is what the executor records:
|
|
118
201
|
|
|
119
202
|
- create — the whole initial record: normalized `parents`, every flow at its initial status (the
|
|
120
203
|
draft's `status` for the primary), mirrored `status`/`intrinsic`, `closedAt` when it starts
|
|
@@ -122,37 +205,40 @@ or `makeSchemaRegistry({ types, flows })`:
|
|
|
122
205
|
The code must already be on the draft (the executor mints first);
|
|
123
206
|
- update — only differing values; `null` or `exec.unset` clears a field that has one; a moved
|
|
124
207
|
`parent` replaces the old one in `parents`; changed content of a revisioned document sets
|
|
125
|
-
`revision + 1`. An update that changes nothing is `isEmptyChange` and must append nothing;
|
|
208
|
+
`revision + 1`. An update that changes nothing is `changesHelper.isEmptyChange` and must append nothing;
|
|
126
209
|
- transit — `flows[flow] = rule.to`, `status` on the primary flow, `intrinsic` when it moves,
|
|
127
210
|
`closedAt` set on entering closed and cleared on leaving; throws `IllegalTransition`;
|
|
128
211
|
- link/unlink/delete — nothing.
|
|
129
212
|
|
|
130
|
-
`assertMutable(exec, type)` refuses (`planning:immutable:<field>`) identity keys
|
|
131
|
-
`
|
|
132
|
-
`
|
|
213
|
+
`changesHelper.assertMutable(exec, type)` refuses (`planning:immutable:<field>`) identity keys (`createdAt`
|
|
214
|
+
among them), `createdBy` in `changes` or `unset` on any action (a create included),
|
|
215
|
+
`revision` / `bodyChars`, `status`/`intrinsic`/`flows`/`closedAt` outside a create (they move only
|
|
216
|
+
through `transit`), `category`, a fixed `code`, and clearing a required field.
|
|
133
217
|
|
|
134
218
|
## Querying
|
|
135
219
|
|
|
136
|
-
`criteriaOf(query, scope)` is the ONE translation from `WorkcardQuery` to `Criteria<Workcard>`:
|
|
220
|
+
`queryHelper.criteriaOf(query, scope)` is the ONE translation from `WorkcardQuery` to `Criteria<Workcard>`:
|
|
137
221
|
bare values and arrays pass through (`kind`, `type`, `status`, `intrinsic`, `code`, `category`),
|
|
138
222
|
`parent` is direct children, `within` → `parents: { $contains }`, `labels` → `$overlaps`, `ids` →
|
|
139
223
|
`id: { $in }`, `flow` → `flows.<id>`, `fields` → `fields.<key>` equality, `q` → `$or` of title /
|
|
140
224
|
description `$ilike` (wildcards escaped) and code `$startsWith`, `updatedSince` → `updatedAt: {
|
|
141
225
|
$gte }`. `entityId` always comes from the scope; `undefined` is omitted. Paging and sort are
|
|
142
|
-
`listOptionsOf` (`size: 0` = no limit). `summaryOf(cards, parents)` counts DIRECT children by
|
|
143
|
-
intrinsic state and gives no key to a parent with none.
|
|
226
|
+
`queryHelper.listOptionsOf` (`size: 0` = no limit). `queryHelper.summaryOf(cards, parents)` counts DIRECT children by
|
|
227
|
+
intrinsic state and gives no key to a parent with none. A scope naming `projects` is narrowed by
|
|
228
|
+
the SERVER facade on top of this translation (`server-planning` → The access resolver);
|
|
229
|
+
`TransitionWhere.project` and `RelationshipWhere.project` take one id or a list.
|
|
144
230
|
|
|
145
231
|
**A query crosses HTTP in its wire form.** The OwlMeans transport cannot carry arrays or nested
|
|
146
232
|
objects in a query string (the client writes `key[]=`, the server reads that key literally), so
|
|
147
233
|
the list, summary, transitions, specifications and links protocols take `*QueryWire` shapes: lists
|
|
148
234
|
comma-joined (a JSON array when an entry holds a comma), objects as JSON, sort as `field,-field`.
|
|
149
|
-
A client calls `encodeWorkcardQuery(query)` before `call({ query })`; a handler calls
|
|
150
|
-
`decodeWorkcardQuery(req.query)` — tolerant of arrays and of the rich form — and then
|
|
151
|
-
`criteriaOf`. A value that does not decode throws `PlanningError('malformed:query:<key>')`.
|
|
235
|
+
A client calls `wireHelper.encodeWorkcardQuery(query)` before `call({ query })`; a handler calls
|
|
236
|
+
`wireHelper.decodeWorkcardQuery(req.query)` — tolerant of arrays and of the rich form — and then
|
|
237
|
+
`queryHelper.criteriaOf`. A value that does not decode throws `PlanningError('malformed:query:<key>')`.
|
|
152
238
|
|
|
153
239
|
## The protocol tree and mounting
|
|
154
240
|
|
|
155
|
-
`makePlanningProtocols({ base: { alias, path?, parent?, service? }, guards, gate?, socketBase? })`:
|
|
241
|
+
`makePlanningProtocols({ base: { alias, path?, parent?, service? }, guards, gate?, socketBase?, definitions? })`:
|
|
156
242
|
|
|
157
243
|
| Leaf | Route | Request → reply |
|
|
158
244
|
|---|---|---|
|
|
@@ -168,9 +254,13 @@ A client calls `encodeWorkcardQuery(query)` before `call({ query })`; a handler
|
|
|
168
254
|
| `execute` | POST `/execute` | body `ExecuteRequest` → `TransitionReceiptView` |
|
|
169
255
|
| `commit.get` | GET `/commits/:transition` | query `CommitQuery { wait? }` → `CommitStatus` |
|
|
170
256
|
| `commit.events` | SOCKET `/commits` | query `CommitFeedQuery` → `CommitEvent` frames |
|
|
257
|
+
| `schema.define` (with `definitions: true`) | POST `/schemas` | body `SchemaDefineRequest { project?, mode?, types?, flows?, retire? }` → `SchemaDefineReply { records, bundle }` |
|
|
171
258
|
|
|
172
|
-
- Aliases derive from the base alias (`planningAliases(base)` → `<base>:card:list`, …); two mounts
|
|
173
|
-
are two base aliases.
|
|
259
|
+
- Aliases derive from the base alias (`planningAliasHelper.planningAliases(base)` → `<base>:card:list`, …); two mounts
|
|
260
|
+
are two base aliases. `definitions: true` adds `schema.define` (its alias from
|
|
261
|
+
`planningAliasHelper.planningDefinitionAliases(base)`, kept apart so a tree without it names no undeclared alias)
|
|
262
|
+
and gives `schema.list` a `SchemaListQuery { project? }` answered with that layer's scoped bundle.
|
|
263
|
+
Without it the tree declares exactly the other leaves.
|
|
174
264
|
- The guards and the gate sit on the base alone; every HTTP leaf inherits them. `path` defaults to
|
|
175
265
|
`/planning`.
|
|
176
266
|
- Static `/cards/summary` is declared before parametric `/cards/:id`.
|
|
@@ -179,6 +269,24 @@ A client calls `encodeWorkcardQuery(query)` before `call({ query })`; a handler
|
|
|
179
269
|
- `actor` in an execute body is ignored on the wire: the server fills it from the request.
|
|
180
270
|
- Responses carry no runtime schema (typed only), so a serializer never strips `fields`.
|
|
181
271
|
|
|
272
|
+
## Mounting in a target
|
|
273
|
+
|
|
274
|
+
A generated target serves this tree as it is — no hand-written card, transition, schema or document
|
|
275
|
+
routes; domain rules are `before` plugins. This section is the hub; each package skill has its half.
|
|
276
|
+
|
|
277
|
+
- **common** declares the one tree the api and the web share: `makePlanningProtocols({ base: {
|
|
278
|
+
alias: 'api:planning' }, guards: DEFAULT_GUARD, definitions: true })`, in its own module beside
|
|
279
|
+
the `PLANNING` plugin (`sources/common/src/planning.ts`).
|
|
280
|
+
- **api** — `server-planning` → Mounting in a target (stock handlers, no commit socket, the
|
|
281
|
+
resolver answering `writes`). **web** — `client-planning` → Mounting in a target. **store** —
|
|
282
|
+
`planning-postgres`.
|
|
283
|
+
- **Kit literals**: card types and flows are LITERAL objects in `PLANNING.schemas.flows` / `.types`,
|
|
284
|
+
above the `// owlmeans: add planning flows|types above this line` sentinels — a spread or an
|
|
285
|
+
imported constant hides them from the library's registration checks. Card types and flows carry
|
|
286
|
+
`overridable: true` with open `fields` (`additionalProperties: true`); the container project type
|
|
287
|
+
(always sealed) carries `scopedCardTypes: true`, so the app's people override and extend them as
|
|
288
|
+
data through `schema.define`.
|
|
289
|
+
|
|
182
290
|
## Models
|
|
183
291
|
|
|
184
292
|
`makeWorkcardModel(record, facade)` (and `makeProjectModel`, `makeSpecificationModel`, `modelOf`
|
|
@@ -213,13 +321,17 @@ the marker survives a hop where the class is unknown, so match on it there:
|
|
|
213
321
|
| `SpecificationSlotUnknown` / `SpecificationRevisionConflict` | `specification-slot-unknown:` / `specification-revision-conflict:` | 422 / 409 |
|
|
214
322
|
| `RelationshipRefused` / `CodeTaken` | `relationship-refused:` / `code-taken:` | 422 / 409 |
|
|
215
323
|
| `WorkcardConflict` | `workcard-conflict:` — stale `expectSeq` | 409 |
|
|
324
|
+
| `SchemaConflict` / `SchemaInUse` | `schema-conflict:` (lost version CAS) / `schema-in-use:` (retiring a flow a live type runs) | 409 |
|
|
325
|
+
| `SchemaSealed` / `SchemaInvalid` | `schema-sealed:` / `schema-invalid:` | 422 |
|
|
326
|
+
| `PlanningForbidden` | `forbidden:<grant>:<target>` — the request's access lacks a grant; `forbidden:writes:<id>` — the card or project is outside its `writes` | 403 |
|
|
216
327
|
| `CommitTimeout` / `CommitFailed` | `commit-timeout:` (still pending, nothing undone) / `commit-failed:` | 500 |
|
|
217
328
|
| `PlanningScopeMismatch` / `PlanningUnsupported` | `scope-mismatch:` / `unsupported:` | 404 / 500 |
|
|
218
329
|
|
|
219
330
|
Type names are `PlanningError<Suffix>` (`PlanningErrorWorkcardNotFound`).
|
|
220
331
|
|
|
221
332
|
**The HTTP column is each class's `static httpStatus`** (the `error` skill's principle; 500 = none
|
|
222
|
-
declared): an addressed card that is absent or another entity's is 404, a
|
|
333
|
+
declared): an addressed card that is absent or another entity's is 404, a write the request's
|
|
334
|
+
access does not grant is 403, a card whose state or head (or a schema record whose version)
|
|
223
335
|
refuses the write is 409, a body naming what the registry, the parent or a schema refuses is 422.
|
|
224
336
|
Faults declare nothing, and so does `PlanningError` itself — its three causes share one class, and
|
|
225
337
|
a base's static would reach every subclass. A new refusal declares on its own leaf class, and
|
|
@@ -229,7 +341,7 @@ throw a `ResilientError` that declares nothing.
|
|
|
229
341
|
|
|
230
342
|
## i18n
|
|
231
343
|
|
|
232
|
-
Importing the package registers, in
|
|
344
|
+
Importing the package registers, in eight languages (en, pl, ru, be, uk, es, de, fr), `lib:planning.*` labels (`kind.*`,
|
|
233
345
|
`intrinsic.*`, `action.*`, `commit.*`, `format.*`) and one text per refusal type under the shared
|
|
234
346
|
`errors` resource (`errors.<TypeName>`), where a panel's error lookup finds it.
|
|
235
347
|
|
|
@@ -251,5 +363,5 @@ is part of the package's own tests.
|
|
|
251
363
|
|
|
252
364
|
- [[server-planning]] — the executor, the plugin registry, the memory store, the handlers
|
|
253
365
|
- [[client-planning]] — the remote facade, the state mirror, waiting for a commit
|
|
254
|
-
- [[resource]] — the criteria language `criteriaOf` targets
|
|
255
|
-
- [[
|
|
366
|
+
- [[resource]] — the criteria language `queryHelper.criteriaOf` targets
|
|
367
|
+
- [[planning-postgres]] — a durable store implementing every port, the schema port included
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** The aliases one planning tree answers under — every one derived from the mount's base alias. */
|
|
2
|
+
export interface PlanningAliases {
|
|
3
|
+
readonly base: string;
|
|
4
|
+
readonly schema: {
|
|
5
|
+
readonly list: string;
|
|
6
|
+
};
|
|
7
|
+
readonly card: {
|
|
8
|
+
readonly list: string;
|
|
9
|
+
readonly summary: string;
|
|
10
|
+
readonly get: string;
|
|
11
|
+
readonly transitions: string;
|
|
12
|
+
readonly specifications: string;
|
|
13
|
+
};
|
|
14
|
+
readonly spec: {
|
|
15
|
+
readonly get: string;
|
|
16
|
+
readonly revisions: string;
|
|
17
|
+
};
|
|
18
|
+
readonly link: {
|
|
19
|
+
readonly list: string;
|
|
20
|
+
};
|
|
21
|
+
readonly transition: {
|
|
22
|
+
readonly get: string;
|
|
23
|
+
};
|
|
24
|
+
readonly execute: string;
|
|
25
|
+
readonly commit: {
|
|
26
|
+
readonly get: string;
|
|
27
|
+
readonly events: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** The aliases of the leaves a tree gains with `definitions: true`. */
|
|
31
|
+
export interface PlanningDefinitionAliases {
|
|
32
|
+
readonly define: string;
|
|
33
|
+
}
|
|
34
|
+
export interface PlanningAliasHelper {
|
|
35
|
+
/**
|
|
36
|
+
* The aliases one planning tree answers under, derived from the mount's base alias.
|
|
37
|
+
*
|
|
38
|
+
* Two mounts of the tree in one deployment are two base aliases, never two copies of this table —
|
|
39
|
+
* which is why nothing here is a global constant.
|
|
40
|
+
*/
|
|
41
|
+
planningAliases: (base: string) => PlanningAliases;
|
|
42
|
+
/**
|
|
43
|
+
* The aliases of the leaves a tree gains with `definitions: true` — kept apart from
|
|
44
|
+
* {@link PlanningAliasHelper.planningAliases} so a tree declared without them names no alias it
|
|
45
|
+
* does not declare.
|
|
46
|
+
*/
|
|
47
|
+
planningDefinitionAliases: (base: string) => PlanningDefinitionAliases;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/aliases/types.ts"],"names":[],"mappings":"AAAA,mGAAmG;AACnG,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1C,QAAQ,CAAC,IAAI,EAAE;QACb,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;QACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;QACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;QACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;QAC5B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;KAChC,CAAA;IACD,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;IACnE,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACxC,QAAQ,CAAC,UAAU,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;IAC7C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CACnE;AAED,uEAAuE;AACvE,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;;OAKG;IACH,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,eAAe,CAAA;IAClD;;;;OAIG;IACH,yBAAyB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,yBAAyB,CAAA;CACvE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/aliases/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aliases.d.ts","sourceRoot":"","sources":["../src/aliases.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,mBAAmB,EAA6B,MAAM,oBAAoB,CAAA;AAEzG,eAAO,MAAM,yBAAyB,QAAO,mBA0B5C,CAAA;AAED,eAAO,MAAM,mBAAmB,qBAA8B,CAAA"}
|