@owlmeans/planning 0.1.18-rc.9 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -29
- package/agent-meta/manifest.json +131 -4
- package/agent-meta/skills/planning/SKILL.md +78 -171
- package/agent-meta/skills/planning/parts/collaboration.md +67 -0
- package/agent-meta/skills/planning/parts/errors.md +51 -0
- package/agent-meta/skills/planning/parts/fold.md +69 -0
- package/agent-meta/skills/planning/parts/mounting.md +64 -0
- package/agent-meta/skills/planning/parts/types.md +141 -0
- package/build/aliases/types.d.ts +72 -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 +28 -0
- package/build/aliases.js.map +1 -0
- package/build/consts.d.ts +43 -34
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +49 -25
- package/build/consts.js.map +1 -1
- package/build/entrypoints/types.d.ts +105 -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 +24 -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/app/consts.local.d.ts +5 -0
- package/build/helpers/app/consts.local.d.ts.map +1 -0
- package/build/helpers/app/consts.local.js +5 -0
- package/build/helpers/app/consts.local.js.map +1 -0
- package/build/helpers/app/types.d.ts +53 -0
- package/build/helpers/app/types.d.ts.map +1 -0
- package/build/helpers/app/types.js +2 -0
- package/build/helpers/app/types.js.map +1 -0
- package/build/helpers/app.d.ts +4 -0
- package/build/helpers/app.d.ts.map +1 -0
- package/build/helpers/app.js +139 -0
- package/build/helpers/app.js.map +1 -0
- 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 +127 -144
- 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 +237 -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 +16 -0
- package/build/helpers/index.d.ts.map +1 -1
- package/build/helpers/index.js +4 -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/reply/consts.d.ts +2 -0
- package/build/helpers/reply/consts.d.ts.map +1 -0
- package/build/helpers/reply/consts.js +2 -0
- package/build/helpers/reply/consts.js.map +1 -0
- package/build/helpers/reply/types.d.ts +10 -0
- package/build/helpers/reply/types.d.ts.map +1 -0
- package/build/helpers/reply/types.js +2 -0
- package/build/helpers/reply/types.js.map +1 -0
- package/build/helpers/reply.d.ts +5 -0
- package/build/helpers/reply.d.ts.map +1 -0
- package/build/helpers/reply.js +29 -0
- package/build/helpers/reply.js.map +1 -0
- package/build/helpers/scoped/types.d.ts +73 -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 +25 -0
- package/build/helpers/scoped.d.ts.map +1 -0
- package/build/helpers/scoped.js +223 -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 +31 -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 +79 -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 +216 -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 +5 -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 +3 -3
- package/build/i18n.js.map +1 -1
- package/build/index.d.ts +7 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +3 -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/registry.d.ts +3 -2
- package/build/registry.d.ts.map +1 -1
- package/build/registry.js +51 -3
- package/build/registry.js.map +1 -1
- package/build/resources/mentions.d.ts +4 -0
- package/build/resources/mentions.d.ts.map +1 -0
- package/build/resources/mentions.js +15 -0
- package/build/resources/mentions.js.map +1 -0
- package/build/resources/protocol/types.d.ts +30 -0
- package/build/resources/protocol/types.d.ts.map +1 -0
- package/build/resources/protocol/types.js +2 -0
- package/build/resources/protocol/types.js.map +1 -0
- package/build/resources/protocol.d.ts +6 -0
- package/build/resources/protocol.d.ts.map +1 -0
- package/build/resources/protocol.js +31 -0
- package/build/resources/protocol.js.map +1 -0
- package/build/resources/types.d.ts +146 -0
- package/build/resources/types.d.ts.map +1 -0
- package/build/resources/types.js +2 -0
- package/build/resources/types.js.map +1 -0
- package/build/resources/types.local.d.ts +6 -0
- package/build/resources/types.local.d.ts.map +1 -0
- package/build/resources/types.local.js +2 -0
- package/build/resources/types.local.js.map +1 -0
- package/build/schemas.d.ts +21 -2
- package/build/schemas.d.ts.map +1 -1
- package/build/schemas.js +164 -2
- package/build/schemas.js.map +1 -1
- package/build/types.d.ts +330 -151
- 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 +13 -13
- package/src/aliases/types.ts +41 -0
- package/src/aliases.ts +34 -0
- package/src/consts.ts +50 -25
- package/src/entrypoints/types.ts +77 -0
- package/src/entrypoints.ts +53 -24
- package/src/errors.ts +71 -0
- package/src/helpers/app/consts.local.ts +5 -0
- package/src/helpers/app/types.ts +54 -0
- package/src/helpers/app.ts +158 -0
- package/src/helpers/apply/consts.local.ts +10 -0
- package/src/helpers/apply/types.ts +32 -0
- package/src/helpers/apply.ts +141 -153
- 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 +270 -280
- package/src/helpers/code/types.ts +31 -0
- package/src/helpers/code.ts +52 -48
- package/src/helpers/index.ts +17 -0
- package/src/helpers/query/types.ts +26 -0
- package/src/helpers/query.ts +144 -314
- package/src/helpers/reply/consts.ts +1 -0
- package/src/helpers/reply/types.ts +13 -0
- package/src/helpers/reply.ts +27 -0
- package/src/helpers/scoped/types.ts +78 -0
- package/src/helpers/scoped.ts +281 -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 +31 -0
- package/src/helpers/validate.ts +89 -77
- package/src/helpers/wire/types.ts +31 -0
- package/src/helpers/wire.ts +252 -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 +5 -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 +4 -3
- package/src/index.ts +8 -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/registry.ts +55 -4
- package/src/resources/mentions.ts +11 -0
- package/src/resources/protocol/types.ts +21 -0
- package/src/resources/protocol.ts +36 -0
- package/src/resources/types.local.ts +5 -0
- package/src/resources/types.ts +148 -0
- package/src/schemas.ts +179 -4
- package/src/types.ts +334 -130
- package/src/utils.ts +9 -0
- package/tests/app.spec.ts +188 -0
- package/tests/apply.spec.ts +95 -31
- package/tests/code.spec.ts +9 -9
- package/tests/csp.spec.ts +122 -0
- package/tests/entrypoints.spec.ts +18 -2
- package/tests/error-status.spec.ts +6 -1
- package/tests/models.spec.ts +24 -16
- package/tests/query.spec.ts +13 -15
- package/tests/schemas.spec.ts +8 -8
- package/tests/scoped.spec.ts +161 -0
- package/tests/status.spec.ts +31 -22
package/src/registry.ts
CHANGED
|
@@ -1,23 +1,47 @@
|
|
|
1
|
+
import type { AssigneeTypeSchema } from './resources/types.js'
|
|
1
2
|
import type { ValidateFunction } from 'ajv'
|
|
2
3
|
import { PLANNING_SCHEMA_VERSION } from './consts.js'
|
|
3
4
|
import { UnknownStatusFlow, UnknownWorkcardType } from './errors.js'
|
|
5
|
+
import { appSchemaHelper } from './helpers/app.js'
|
|
4
6
|
import { makeAjv } from './helpers/validate.js'
|
|
5
|
-
import type { AnyTypeSchema, PlanningSchemaBundle, PlanningSchemaRegistry, StatusFlowSchema } from './types.js'
|
|
7
|
+
import type { AnyTypeSchema, PlanningAppDeclarations, PlanningSchemaBundle, PlanningSchemaRegistry, StatusFlowSchema } from './types.js'
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
|
-
* Types, flows and the compiled validator of each type's `fields
|
|
10
|
+
* Types, flows, the application's declarations and the compiled validator of each type's `fields`
|
|
11
|
+
* (its own schema, with the application's base card fields for a `card` type).
|
|
9
12
|
*
|
|
10
13
|
* It lives in the contract package so a client answers `can()` / `available()` from a loaded bundle
|
|
11
14
|
* with no round trip. A later registration of the same type or flow id replaces the earlier one;
|
|
12
|
-
* `load(bundle)` replaces everything.
|
|
15
|
+
* `registerApp` replaces the declarations; `load(bundle)` replaces everything.
|
|
13
16
|
*/
|
|
14
17
|
export const makeSchemaRegistry = (bundle?: Partial<PlanningSchemaBundle>): PlanningSchemaRegistry => {
|
|
15
18
|
const types = new Map<string, AnyTypeSchema>()
|
|
19
|
+
const assigneeTypes = new Map<string, AssigneeTypeSchema>()
|
|
20
|
+
const assigneeValidators = new Map<string, ValidateFunction>()
|
|
16
21
|
const flows = new Map<string, StatusFlowSchema>()
|
|
17
22
|
const validators = new Map<string, ValidateFunction>()
|
|
23
|
+
let app: PlanningAppDeclarations | undefined
|
|
18
24
|
let ajv = makeAjv()
|
|
19
25
|
|
|
20
26
|
const registry: PlanningSchemaRegistry = {
|
|
27
|
+
registerAssigneeType: schema => {
|
|
28
|
+
assigneeTypes.set(schema.type, schema)
|
|
29
|
+
assigneeValidators.delete(schema.type)
|
|
30
|
+
},
|
|
31
|
+
assigneeTypes: () => [...assigneeTypes.values()],
|
|
32
|
+
assigneeType: type => {
|
|
33
|
+
const schema = assigneeTypes.get(type)
|
|
34
|
+
if (schema == null) throw new UnknownWorkcardType(`assignee:${type}`)
|
|
35
|
+
return schema
|
|
36
|
+
},
|
|
37
|
+
assigneeValidator: type => {
|
|
38
|
+
let validator = assigneeValidators.get(type)
|
|
39
|
+
if (validator == null) {
|
|
40
|
+
validator = ajv.compile(registry.assigneeType(type).fields)
|
|
41
|
+
assigneeValidators.set(type, validator)
|
|
42
|
+
}
|
|
43
|
+
return validator
|
|
44
|
+
},
|
|
21
45
|
registerType: schema => {
|
|
22
46
|
types.set(schema.type, schema)
|
|
23
47
|
validators.delete(schema.type)
|
|
@@ -27,6 +51,22 @@ export const makeSchemaRegistry = (bundle?: Partial<PlanningSchemaBundle>): Plan
|
|
|
27
51
|
flows.set(flow.id, flow)
|
|
28
52
|
},
|
|
29
53
|
|
|
54
|
+
registerApp: next => {
|
|
55
|
+
if (next != null) {
|
|
56
|
+
appSchemaHelper.assertDeclarations(next)
|
|
57
|
+
}
|
|
58
|
+
if (next == null && app == null) {
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
app = next == null ? undefined : structuredClone(next)
|
|
62
|
+
validators.clear()
|
|
63
|
+
assigneeValidators.clear()
|
|
64
|
+
// A fresh ajv: a replaced declaration must not collide with a schema `$id` the old one compiled.
|
|
65
|
+
ajv = makeAjv()
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
app: () => app,
|
|
69
|
+
|
|
30
70
|
types: () => [...types.values()],
|
|
31
71
|
|
|
32
72
|
type: type => {
|
|
@@ -57,10 +97,12 @@ export const makeSchemaRegistry = (bundle?: Partial<PlanningSchemaBundle>): Plan
|
|
|
57
97
|
return registry.flow(primary)
|
|
58
98
|
},
|
|
59
99
|
|
|
100
|
+
fieldsSchema: type => appSchemaHelper.fieldsSchemaOf(registry.type(type), app),
|
|
101
|
+
|
|
60
102
|
validator: type => {
|
|
61
103
|
let validate = validators.get(type)
|
|
62
104
|
if (validate == null) {
|
|
63
|
-
validate = ajv.compile(registry.
|
|
105
|
+
validate = ajv.compile(registry.fieldsSchema(type))
|
|
64
106
|
validators.set(type, validate)
|
|
65
107
|
}
|
|
66
108
|
return validate
|
|
@@ -70,19 +112,28 @@ export const makeSchemaRegistry = (bundle?: Partial<PlanningSchemaBundle>): Plan
|
|
|
70
112
|
version: PLANNING_SCHEMA_VERSION,
|
|
71
113
|
types: [...types.values()],
|
|
72
114
|
flows: [...flows.values()],
|
|
115
|
+
assigneeTypes: [...assigneeTypes.values()],
|
|
116
|
+
...(app != null ? { app } : {}),
|
|
73
117
|
}),
|
|
74
118
|
|
|
75
119
|
load: next => {
|
|
120
|
+
assigneeTypes.clear()
|
|
121
|
+
assigneeValidators.clear()
|
|
76
122
|
types.clear()
|
|
77
123
|
flows.clear()
|
|
78
124
|
validators.clear()
|
|
125
|
+
app = undefined
|
|
79
126
|
// A fresh ajv: a replaced type must not collide with a schema `$id` the old one compiled.
|
|
80
127
|
ajv = makeAjv()
|
|
128
|
+
registry.registerApp(next.app ?? undefined)
|
|
129
|
+
next.assigneeTypes?.forEach(registry.registerAssigneeType)
|
|
81
130
|
next.flows.forEach(registry.registerFlow)
|
|
82
131
|
next.types.forEach(registry.registerType)
|
|
83
132
|
},
|
|
84
133
|
}
|
|
85
134
|
|
|
135
|
+
registry.registerApp(bundle?.app ?? undefined)
|
|
136
|
+
bundle?.assigneeTypes?.forEach(registry.registerAssigneeType)
|
|
86
137
|
bundle?.flows?.forEach(registry.registerFlow)
|
|
87
138
|
bundle?.types?.forEach(registry.registerType)
|
|
88
139
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { MentionHelper } from './types.local.js'
|
|
2
|
+
|
|
3
|
+
/** Stable-id Markdown mentions. Escaped display text cannot change the referenced identity. */
|
|
4
|
+
export const mentionHelper: MentionHelper = {
|
|
5
|
+
encode: (id, nickname) => `[@${nickname.replace(/[\\\[\]]/g, '\\$&')}](assignee:${encodeURIComponent(id)})`,
|
|
6
|
+
parse: body => [...new Set([...body.matchAll(/\[@(?:\\.|[^\]\\])*\]\(assignee:([^\s)]+)\)/g)]
|
|
7
|
+
.flatMap(match => {
|
|
8
|
+
try { return [decodeURIComponent(match[1])] } catch { return [] }
|
|
9
|
+
}))],
|
|
10
|
+
nicknameKey: nickname => nickname.normalize('NFKC').trim().toLocaleLowerCase('en-US'),
|
|
11
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PlanningReply } from '../../helpers/reply/types.js'
|
|
2
|
+
import type { EntrypointProtocol } from '@owlmeans/entrypoint'
|
|
3
|
+
import type { ListResult } from '@owlmeans/resource'
|
|
4
|
+
import type { AssigneeDraft, TeamDraft, CommentDraft, PlanningRecord } from '../types.js'
|
|
5
|
+
|
|
6
|
+
export interface PlanningResourceQueryWire { query?: string }
|
|
7
|
+
export interface PlanningResourceCommand {
|
|
8
|
+
action: 'create' | 'update' | 'retire' | 'remove' | 'members' | 'projects' | 'assignees' | 'addMember' | 'removeMember' | 'attach' | 'detach' | 'rebuild'
|
|
9
|
+
id?: string
|
|
10
|
+
draft?: Partial<AssigneeDraft & TeamDraft & CommentDraft>
|
|
11
|
+
version?: number
|
|
12
|
+
assignee?: string
|
|
13
|
+
project?: string
|
|
14
|
+
}
|
|
15
|
+
export type PlanningResourceReply = PlanningRecord | PlanningRecord[] | string[] | null
|
|
16
|
+
|
|
17
|
+
export interface PlanningResourceProtocols<R extends PlanningRecord = PlanningRecord> {
|
|
18
|
+
get: EntrypointProtocol<{ params: { id: string } }, PlanningReply<R>>
|
|
19
|
+
list: EntrypointProtocol<{ query: PlanningResourceQueryWire }, PlanningReply<ListResult<R>>>
|
|
20
|
+
write: EntrypointProtocol<{ body: PlanningResourceCommand }, PlanningReply<PlanningResourceReply>>
|
|
21
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { PlanningReply } from '../helpers/reply/types.js'
|
|
2
|
+
import { planningAliasHelper } from '../aliases.js'
|
|
3
|
+
import { contract, protocol, typed } from '@owlmeans/entrypoint'
|
|
4
|
+
import type { EntrypointProtocol } from '@owlmeans/entrypoint'
|
|
5
|
+
import { backend, route, RouteMethod } from '@owlmeans/route'
|
|
6
|
+
import { AssigneeKind } from '../consts.js'
|
|
7
|
+
import { WorkcardParamsSchema } from '../schemas.js'
|
|
8
|
+
import type { ListResult } from '@owlmeans/resource'
|
|
9
|
+
import type { PlanningRecord } from './types.js'
|
|
10
|
+
import type { PlanningResourceCommand, PlanningResourceProtocols, PlanningResourceQueryWire, PlanningResourceReply } from './protocol/types.js'
|
|
11
|
+
|
|
12
|
+
/** Opt-in auxiliary branches share the planning base's authentication guards. */
|
|
13
|
+
export const makePlanningResourceProtocols = <R extends PlanningRecord>(base: EntrypointProtocol<any, any>, alias: string, name: 'assignees' | 'teams' | 'comments' | 'mentions'): PlanningResourceProtocols<R> => {
|
|
14
|
+
const aliases = planningAliasHelper.planningResourceAliases(alias)[name]
|
|
15
|
+
const commandSchema = {
|
|
16
|
+
type: 'object', properties: {
|
|
17
|
+
action: { type: 'string', enum: ['create', 'update', 'retire', 'remove', 'members', 'projects', 'assignees', 'addMember', 'removeMember', 'attach', 'detach', 'rebuild'] },
|
|
18
|
+
id: { type: 'string', minLength: 1, maxLength: 128, nullable: true },
|
|
19
|
+
version: { type: 'number', minimum: 1, nullable: true },
|
|
20
|
+
assignee: { type: 'string', minLength: 1, maxLength: 128, nullable: true },
|
|
21
|
+
project: { type: 'string', minLength: 1, maxLength: 128, nullable: true },
|
|
22
|
+
draft: { type: 'object', properties: {
|
|
23
|
+
nickname: { type: 'string', nullable: true }, kind: { type: 'string', enum: [...Object.values(AssigneeKind), null], nullable: true },
|
|
24
|
+
type: { type: 'string', nullable: true }, name: { type: 'string', nullable: true }, externalId: { type: 'string', nullable: true },
|
|
25
|
+
card: { type: 'string', nullable: true }, body: { type: 'string', nullable: true },
|
|
26
|
+
fields: { type: 'object', additionalProperties: true, required: [], nullable: true },
|
|
27
|
+
authentication: { type: 'object', properties: { provider: { type: 'string' }, externalId: { type: 'string' } }, required: ['provider', 'externalId'], additionalProperties: false, nullable: true },
|
|
28
|
+
}, required: [], additionalProperties: false, nullable: true },
|
|
29
|
+
}, required: ['action'], additionalProperties: false,
|
|
30
|
+
}
|
|
31
|
+
return Object.freeze({
|
|
32
|
+
get: protocol(route(aliases.get, `/${name}/:id`, backend({ parent: base }, RouteMethod.GET)), contract.request({ params: typed<{ id: string }>(WorkcardParamsSchema) }, typed<PlanningReply<R>>())),
|
|
33
|
+
list: protocol(route(aliases.list, `/${name}`, backend({ parent: base }, RouteMethod.GET)), contract.request({ query: typed<PlanningResourceQueryWire>({ type: 'object', properties: { query: { type: 'string', maxLength: 16384, nullable: true } }, required: [], additionalProperties: false }) }, typed<PlanningReply<ListResult<R>>>())),
|
|
34
|
+
write: protocol(route(aliases.write, `/${name}`, backend({ parent: base }, RouteMethod.POST)), contract.request({ body: typed<PlanningResourceCommand>(commandSchema as any) }, typed<PlanningReply<PlanningResourceReply>>())),
|
|
35
|
+
})
|
|
36
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { AnySchema, ValidateFunction } from 'ajv'
|
|
2
|
+
import type { ListOptions, ListResult, ResourceRecord, Criteria } from '@owlmeans/resource'
|
|
3
|
+
import type { AssigneeKind } from '../consts.js'
|
|
4
|
+
|
|
5
|
+
/** Identity in an external authentication domain; neither email nor nickname is its key. */
|
|
6
|
+
export interface AssigneeAuthentication {
|
|
7
|
+
provider: string
|
|
8
|
+
externalId: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface AssigneeTypeSchema {
|
|
12
|
+
type: string
|
|
13
|
+
version: number
|
|
14
|
+
kind: AssigneeKind
|
|
15
|
+
fields: AnySchema
|
|
16
|
+
authentication?: 'required' | 'optional'
|
|
17
|
+
label?: string
|
|
18
|
+
overridable?: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface PlanningRecord extends ResourceRecord {
|
|
22
|
+
id?: string
|
|
23
|
+
entityId: string
|
|
24
|
+
version: number
|
|
25
|
+
createdAt: string
|
|
26
|
+
updatedAt?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface Assignee extends PlanningRecord {
|
|
30
|
+
nickname: string
|
|
31
|
+
/** Organization-scoped, Unicode-normalized, case-insensitive identity key. */
|
|
32
|
+
nicknameKey: string
|
|
33
|
+
kind: AssigneeKind
|
|
34
|
+
type: string
|
|
35
|
+
fields: Record<string, unknown>
|
|
36
|
+
authentication?: AssigneeAuthentication
|
|
37
|
+
retired?: boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface Team extends PlanningRecord {
|
|
41
|
+
name: string
|
|
42
|
+
externalId?: string
|
|
43
|
+
fields: Record<string, unknown>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface Comment extends PlanningRecord {
|
|
47
|
+
card: string
|
|
48
|
+
/** Immutable author, resolved by the host from a trusted planning scope. */
|
|
49
|
+
author: string
|
|
50
|
+
body: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** A derived mention of a stable assignee id; revision is the source comment's version. */
|
|
54
|
+
export interface CommentMention extends PlanningRecord {
|
|
55
|
+
comment: string
|
|
56
|
+
card: string
|
|
57
|
+
assignee: string
|
|
58
|
+
revision: number
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface AssigneeDraft {
|
|
62
|
+
nickname: string
|
|
63
|
+
kind: AssigneeKind
|
|
64
|
+
type: string
|
|
65
|
+
fields?: Record<string, unknown>
|
|
66
|
+
authentication?: AssigneeAuthentication
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface TeamDraft {
|
|
70
|
+
name: string
|
|
71
|
+
externalId?: string
|
|
72
|
+
fields?: Record<string, unknown>
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface CommentDraft {
|
|
76
|
+
card: string
|
|
77
|
+
body: string
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Compare-and-set version is mandatory for updates/removal, optional for retiring an assignee. */
|
|
81
|
+
export interface PlanningWriteOptions { version: number }
|
|
82
|
+
|
|
83
|
+
export interface AssigneeQuery extends ListOptions<Assignee> {
|
|
84
|
+
ids?: string[]
|
|
85
|
+
nickname?: string
|
|
86
|
+
kind?: AssigneeKind
|
|
87
|
+
type?: string
|
|
88
|
+
authentication?: AssigneeAuthentication
|
|
89
|
+
retired?: boolean
|
|
90
|
+
}
|
|
91
|
+
export interface TeamQuery extends ListOptions<Team> { ids?: string[], externalId?: string }
|
|
92
|
+
export interface CommentQuery extends ListOptions<Comment> { card?: string, author?: string }
|
|
93
|
+
export interface MentionQuery extends ListOptions<CommentMention> { comment?: string, card?: string, assignee?: string }
|
|
94
|
+
|
|
95
|
+
/** Durable versioned resource port. put expects stored version + 1, with 1 for creation. */
|
|
96
|
+
export interface PlanningRecordStore<R extends PlanningRecord> {
|
|
97
|
+
get: (id: string, entityId: string) => Promise<R | null>
|
|
98
|
+
list: (where: Criteria<R>, opts?: ListOptions<R>) => Promise<ListResult<R>>
|
|
99
|
+
put: (record: R) => Promise<R>
|
|
100
|
+
drop: (id: string, entityId: string, version: number) => Promise<void>
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface PlanningResourceFacade {
|
|
104
|
+
assignees: {
|
|
105
|
+
get: (id: string) => Promise<Assignee>
|
|
106
|
+
load: (id: string) => Promise<Assignee | null>
|
|
107
|
+
list: (query?: AssigneeQuery) => Promise<ListResult<Assignee>>
|
|
108
|
+
create: (draft: AssigneeDraft) => Promise<Assignee>
|
|
109
|
+
update: (id: string, changes: Partial<AssigneeDraft>, opts: PlanningWriteOptions) => Promise<Assignee>
|
|
110
|
+
retire: (id: string, opts: PlanningWriteOptions) => Promise<Assignee>
|
|
111
|
+
}
|
|
112
|
+
teams: {
|
|
113
|
+
get: (id: string) => Promise<Team>
|
|
114
|
+
load: (id: string) => Promise<Team | null>
|
|
115
|
+
list: (query?: TeamQuery) => Promise<ListResult<Team>>
|
|
116
|
+
create: (draft: TeamDraft) => Promise<Team>
|
|
117
|
+
update: (id: string, changes: Partial<TeamDraft>, opts: PlanningWriteOptions) => Promise<Team>
|
|
118
|
+
remove: (id: string, opts: PlanningWriteOptions) => Promise<void>
|
|
119
|
+
members: (team: string) => Promise<Assignee[]>
|
|
120
|
+
addMember: (team: string, assignee: string) => Promise<void>
|
|
121
|
+
removeMember: (team: string, assignee: string) => Promise<void>
|
|
122
|
+
projects: (team: string) => Promise<string[]>
|
|
123
|
+
attach: (team: string, project: string) => Promise<void>
|
|
124
|
+
detach: (team: string, project: string) => Promise<void>
|
|
125
|
+
assignees: (project: string) => Promise<Assignee[]>
|
|
126
|
+
}
|
|
127
|
+
comments: {
|
|
128
|
+
get: (id: string) => Promise<Comment>
|
|
129
|
+
load: (id: string) => Promise<Comment | null>
|
|
130
|
+
list: (query?: CommentQuery) => Promise<ListResult<Comment>>
|
|
131
|
+
create: (draft: CommentDraft) => Promise<Comment>
|
|
132
|
+
update: (id: string, changes: Pick<CommentDraft, 'body'>, opts: PlanningWriteOptions) => Promise<Comment>
|
|
133
|
+
remove: (id: string, opts: PlanningWriteOptions) => Promise<void>
|
|
134
|
+
}
|
|
135
|
+
mentions: {
|
|
136
|
+
get: (id: string) => Promise<CommentMention>
|
|
137
|
+
load: (id: string) => Promise<CommentMention | null>
|
|
138
|
+
list: (query?: MentionQuery) => Promise<ListResult<CommentMention>>
|
|
139
|
+
rebuild: (comment: string) => Promise<CommentMention[]>
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface AssigneeSchemaRegistry {
|
|
144
|
+
registerAssigneeType: (schema: AssigneeTypeSchema) => void
|
|
145
|
+
assigneeType: (type: string) => AssigneeTypeSchema
|
|
146
|
+
assigneeTypes: () => AssigneeTypeSchema[]
|
|
147
|
+
assigneeValidator: (type: string) => ValidateFunction
|
|
148
|
+
}
|
package/src/schemas.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { AssigneeTypeSchema, Assignee, Team, Comment, CommentMention } from './resources/types.js'
|
|
2
|
+
import { AssigneeKind, PlanningResourceKind, ProjectMode } from './consts.js'
|
|
1
3
|
import type { JSONSchemaType } from 'ajv'
|
|
2
4
|
import { IdValueSchema } from '@owlmeans/auth'
|
|
3
5
|
import { createListSchema } from '@owlmeans/resource'
|
|
@@ -5,12 +7,14 @@ import type { ListResult } from '@owlmeans/resource'
|
|
|
5
7
|
import {
|
|
6
8
|
BODY_MAX, CAUSE_MAX, CODE_MAX, CodeScope, CodeStyle, CommitState, DESCRIPTION_MAX, IntrinsicPolicy,
|
|
7
9
|
IntrinsicStatus, KEY_MAX, LABEL_MAX, MAX_COMMIT_POLL, MAX_LABELS, MAX_PARENTS, MAX_QUERY_LIST,
|
|
8
|
-
QUERY_TEXT_MAX, REF_MAX,
|
|
10
|
+
PlanningSchemaKind, QUERY_TEXT_MAX, REF_MAX, SchemaOrigin, SchemaWriteMode, SpecificationFormat,
|
|
11
|
+
TITLE_MAX, TransitionAction, TYPE_MAX, WorkcardKind,
|
|
9
12
|
} from './consts.js'
|
|
10
13
|
import type {
|
|
11
14
|
AnyTypeSchema, CodePolicy, CommitEvent, CommitFeedQuery, CommitQuery, CommitStatus, ExecuteRequest,
|
|
12
|
-
PlanningSchemaBundle, ProjectTypeSchema, Relationship, RelationshipDraft, RelationshipQueryWire,
|
|
13
|
-
RelationshipType, RevisionsQuery,
|
|
15
|
+
PlanningAppDeclarations, PlanningSchemaBundle, ProjectTypeSchema, Relationship, RelationshipDraft, RelationshipQueryWire,
|
|
16
|
+
RelationshipType, RevisionsQuery, SchemaDefineRequest, SchemaKey, SchemaListQuery, ScopedSchemaBundle,
|
|
17
|
+
ScopedSchemaRecord, Specification, SpecificationQueryWire, SpecificationRevision,
|
|
14
18
|
SpecificationRevisionList, SpecificationSlot, StatusDefinition, StatusFlowSchema,
|
|
15
19
|
StatusTransitionRule, SummaryQueryWire, SummaryView, Transition, TransitionActor, TransitionCommit,
|
|
16
20
|
TransitionExecution, TransitionParams, TransitionQueryWire, TransitionReceiptView, Workcard,
|
|
@@ -66,6 +70,10 @@ const workcardProperties = {
|
|
|
66
70
|
title: { type: 'string', minLength: 1, maxLength: TITLE_MAX },
|
|
67
71
|
description: { type: 'string', maxLength: DESCRIPTION_MAX, nullable: true },
|
|
68
72
|
parent: OptionalId,
|
|
73
|
+
parentType: { ...TypeKey, nullable: true },
|
|
74
|
+
reporter: OptionalId,
|
|
75
|
+
assignee: OptionalId,
|
|
76
|
+
mode: { type: 'string', enum: [...Object.values(ProjectMode), null], nullable: true },
|
|
69
77
|
parents: { type: 'array', maxItems: MAX_PARENTS, items: Id },
|
|
70
78
|
status: Label,
|
|
71
79
|
intrinsic: IntrinsicStatusSchema,
|
|
@@ -99,7 +107,7 @@ const specificationProperties = {
|
|
|
99
107
|
/** The optional form of a schema: `nullable`, and `null` admitted by an enum too. */
|
|
100
108
|
const nullable = (schema: object): object => {
|
|
101
109
|
const enumerated = (schema as { enum?: unknown[] }).enum
|
|
102
|
-
return { ...schema, nullable: true, ...(enumerated != null ? { enum: [...enumerated, null] } : {}) }
|
|
110
|
+
return { ...schema, nullable: true, ...(enumerated != null ? { enum: [...new Set([...enumerated, null])] } : {}) }
|
|
103
111
|
}
|
|
104
112
|
|
|
105
113
|
const nullableOf = (properties: Record<string, object>): Record<string, object> =>
|
|
@@ -133,6 +141,8 @@ export const RelationshipDraftSchema = cast<RelationshipDraft>({
|
|
|
133
141
|
type: Label,
|
|
134
142
|
from: OptionalId,
|
|
135
143
|
to: Id,
|
|
144
|
+
fromKind: { type: 'string', enum: [...Object.values(PlanningResourceKind), null], nullable: true },
|
|
145
|
+
toKind: { type: 'string', enum: [...Object.values(PlanningResourceKind), null], nullable: true },
|
|
136
146
|
fields: OptionalOpenObject,
|
|
137
147
|
},
|
|
138
148
|
required: ['type', 'to'],
|
|
@@ -147,6 +157,8 @@ export const RelationshipSchema = cast<Relationship>({
|
|
|
147
157
|
type: Label,
|
|
148
158
|
from: Id,
|
|
149
159
|
to: Id,
|
|
160
|
+
fromKind: { type: 'string', enum: [...Object.values(PlanningResourceKind), null], nullable: true },
|
|
161
|
+
toKind: { type: 'string', enum: [...Object.values(PlanningResourceKind), null], nullable: true },
|
|
150
162
|
project: OptionalId,
|
|
151
163
|
fields: OptionalOpenObject,
|
|
152
164
|
createdAt: IsoDate,
|
|
@@ -164,6 +176,10 @@ export const WorkcardChangesSchema = cast<WorkcardChanges>({
|
|
|
164
176
|
title: workcardProperties.title,
|
|
165
177
|
description: workcardProperties.description,
|
|
166
178
|
parent: workcardProperties.parent,
|
|
179
|
+
parentType: workcardProperties.parentType,
|
|
180
|
+
reporter: workcardProperties.reporter,
|
|
181
|
+
assignee: workcardProperties.assignee,
|
|
182
|
+
mode: workcardProperties.mode,
|
|
167
183
|
parents: workcardProperties.parents,
|
|
168
184
|
status: workcardProperties.status,
|
|
169
185
|
intrinsic: workcardProperties.intrinsic,
|
|
@@ -260,6 +276,11 @@ export const StatusFlowSchemaSchema = cast<StatusFlowSchema>({
|
|
|
260
276
|
statuses: { type: 'array', minItems: 1, items: StatusDefinitionSchema },
|
|
261
277
|
transitions: { type: 'array', items: StatusTransitionRuleSchema },
|
|
262
278
|
label: { type: 'string', maxLength: TITLE_MAX, nullable: true },
|
|
279
|
+
tone: { type: 'string', maxLength: LABEL_MAX, nullable: true },
|
|
280
|
+
icon: { type: 'string', maxLength: LABEL_MAX, nullable: true },
|
|
281
|
+
overridable: { type: 'boolean', nullable: true },
|
|
282
|
+
/** The application's own properties — their own schema is applied by `assertFlowSchema`. */
|
|
283
|
+
app: OptionalOpenObject,
|
|
263
284
|
},
|
|
264
285
|
required: ['id', 'version', 'statuses', 'transitions'],
|
|
265
286
|
additionalProperties: false,
|
|
@@ -303,6 +324,10 @@ export const RelationshipTypeSchema = cast<RelationshipType>({
|
|
|
303
324
|
name: Label,
|
|
304
325
|
from: { type: 'array', items: TypeKey, nullable: true },
|
|
305
326
|
to: { type: 'array', items: TypeKey, nullable: true },
|
|
327
|
+
fromKind: { type: 'string', enum: [...Object.values(PlanningResourceKind), null], nullable: true },
|
|
328
|
+
toKind: { type: 'string', enum: [...Object.values(PlanningResourceKind), null], nullable: true },
|
|
329
|
+
field: { ...TypeKey, nullable: true },
|
|
330
|
+
multiple: { type: 'boolean', nullable: true },
|
|
306
331
|
inverse: { ...Label, nullable: true },
|
|
307
332
|
single: { type: 'boolean', nullable: true },
|
|
308
333
|
label: { type: 'string', maxLength: TITLE_MAX, nullable: true },
|
|
@@ -318,10 +343,17 @@ const typeProperties = {
|
|
|
318
343
|
flows: { type: 'array', minItems: 1, items: TypeKey },
|
|
319
344
|
intrinsic: nullable(IntrinsicPolicySchema),
|
|
320
345
|
specifications: { type: 'array', items: SpecificationSlotSchema },
|
|
346
|
+
parents: { type: 'object', properties: { types: { type: 'array', items: TypeKey, nullable: true }, required: { type: 'boolean', nullable: true } }, required: [], additionalProperties: false, nullable: true },
|
|
347
|
+
children: { type: 'object', properties: { types: { type: 'array', items: TypeKey } }, required: ['types'], additionalProperties: false, nullable: true },
|
|
321
348
|
relationships: { type: 'array', items: RelationshipTypeSchema, nullable: true },
|
|
322
349
|
labels: { type: 'array', items: Label, nullable: true },
|
|
323
350
|
code: { ...CodePolicySchema, nullable: true },
|
|
324
351
|
label: { type: 'string', maxLength: TITLE_MAX, nullable: true },
|
|
352
|
+
tone: { type: 'string', maxLength: LABEL_MAX, nullable: true },
|
|
353
|
+
icon: { type: 'string', maxLength: LABEL_MAX, nullable: true },
|
|
354
|
+
overridable: { type: 'boolean', nullable: true },
|
|
355
|
+
/** The application's own properties — their own schema is applied by `assertTypeSchema`. */
|
|
356
|
+
app: OptionalOpenObject,
|
|
325
357
|
}
|
|
326
358
|
|
|
327
359
|
const typeRequired = ['type', 'kind', 'version', 'fields', 'flows', 'specifications']
|
|
@@ -343,6 +375,7 @@ export const ProjectTypeSchemaSchema = cast<ProjectTypeSchema>({
|
|
|
343
375
|
kind: { type: 'string', enum: [WorkcardKind.Project] },
|
|
344
376
|
cardTypes: { type: 'array', items: TypeKey },
|
|
345
377
|
projectTypes: { type: 'array', items: TypeKey, nullable: true },
|
|
378
|
+
scopedCardTypes: { type: 'boolean', nullable: true },
|
|
346
379
|
},
|
|
347
380
|
required: [...typeRequired, 'cardTypes'],
|
|
348
381
|
additionalProperties: false,
|
|
@@ -356,22 +389,159 @@ export const AnyTypeSchemaSchema = cast<AnyTypeSchema>({
|
|
|
356
389
|
kind: WorkcardKindSchema,
|
|
357
390
|
cardTypes: { type: 'array', items: TypeKey, nullable: true },
|
|
358
391
|
projectTypes: { type: 'array', items: TypeKey, nullable: true },
|
|
392
|
+
scopedCardTypes: { type: 'boolean', nullable: true },
|
|
359
393
|
},
|
|
360
394
|
required: typeRequired,
|
|
361
395
|
additionalProperties: false,
|
|
362
396
|
})
|
|
363
397
|
|
|
398
|
+
export const AssigneeTypeSchemaSchema = cast<AssigneeTypeSchema>({
|
|
399
|
+
type: 'object', properties: {
|
|
400
|
+
type: TypeKey, version: { type: 'number', minimum: 1 }, kind: { type: 'string', enum: Object.values(AssigneeKind) },
|
|
401
|
+
fields: OpenObject, authentication: { type: 'string', enum: ['required', 'optional', null], nullable: true },
|
|
402
|
+
label: { type: 'string', maxLength: TITLE_MAX, nullable: true }, overridable: { type: 'boolean', nullable: true },
|
|
403
|
+
}, required: ['type', 'version', 'kind', 'fields'], additionalProperties: false,
|
|
404
|
+
})
|
|
405
|
+
const planningRecordProperties = { id: OptionalId, entityId: Id, version: { type: 'number', minimum: 1 }, createdAt: IsoDate, updatedAt: OptionalIsoDate }
|
|
406
|
+
const planningRecordRequired = ['entityId', 'version', 'createdAt']
|
|
407
|
+
export const AssigneeSchema = cast<Assignee>({
|
|
408
|
+
type: 'object', properties: { ...planningRecordProperties,
|
|
409
|
+
nickname: { type: 'string', minLength: 1, maxLength: TYPE_MAX }, nicknameKey: TypeKey,
|
|
410
|
+
kind: { type: 'string', enum: Object.values(AssigneeKind) }, type: TypeKey, fields: OpenObject,
|
|
411
|
+
authentication: { type: 'object', properties: { provider: TypeKey, externalId: { type: 'string', minLength: 1, maxLength: REF_MAX } }, required: ['provider', 'externalId'], additionalProperties: false, nullable: true },
|
|
412
|
+
retired: { type: 'boolean', nullable: true },
|
|
413
|
+
}, required: [...planningRecordRequired, 'nickname', 'nicknameKey', 'kind', 'type', 'fields'], additionalProperties: false,
|
|
414
|
+
})
|
|
415
|
+
export const TeamSchema = cast<Team>({
|
|
416
|
+
type: 'object', properties: { ...planningRecordProperties, name: { type: 'string', minLength: 1, maxLength: TITLE_MAX }, externalId: { type: 'string', minLength: 1, maxLength: REF_MAX, nullable: true }, fields: OpenObject },
|
|
417
|
+
required: [...planningRecordRequired, 'name', 'fields'], additionalProperties: false,
|
|
418
|
+
})
|
|
419
|
+
export const CommentSchema = cast<Comment>({
|
|
420
|
+
type: 'object', properties: { ...planningRecordProperties, card: Id, author: Id, body: { type: 'string', minLength: 1, maxLength: BODY_MAX } },
|
|
421
|
+
required: [...planningRecordRequired, 'card', 'author', 'body'], additionalProperties: false,
|
|
422
|
+
})
|
|
423
|
+
export const CommentMentionSchema = cast<CommentMention>({
|
|
424
|
+
type: 'object', properties: { ...planningRecordProperties, comment: Id, card: Id, assignee: Id, revision: { type: 'number', minimum: 1 } },
|
|
425
|
+
required: [...planningRecordRequired, 'comment', 'card', 'assignee', 'revision'], additionalProperties: false,
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* The application's declarations as a bundle carries them. Each kind is an object schema whose
|
|
430
|
+
* closed form (`type`, `properties`, `required` only) `appSchemaHelper.assertDeclarations` checks.
|
|
431
|
+
*/
|
|
432
|
+
export const PlanningAppDeclarationsSchema = cast<PlanningAppDeclarations>({
|
|
433
|
+
type: 'object',
|
|
434
|
+
properties: { type: OptionalOpenObject, flow: OptionalOpenObject, card: OptionalOpenObject },
|
|
435
|
+
required: [],
|
|
436
|
+
additionalProperties: false,
|
|
437
|
+
})
|
|
438
|
+
|
|
364
439
|
export const PlanningSchemaBundleSchema = cast<PlanningSchemaBundle>({
|
|
365
440
|
type: 'object',
|
|
366
441
|
properties: {
|
|
367
442
|
version: { type: 'number', minimum: 1 },
|
|
368
443
|
types: { type: 'array', items: AnyTypeSchemaSchema },
|
|
369
444
|
flows: { type: 'array', items: StatusFlowSchemaSchema },
|
|
445
|
+
assigneeTypes: { type: 'array', items: AssigneeTypeSchemaSchema, nullable: true },
|
|
446
|
+
app: { ...PlanningAppDeclarationsSchema, nullable: true },
|
|
447
|
+
},
|
|
448
|
+
required: ['version', 'types', 'flows'],
|
|
449
|
+
additionalProperties: false,
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
// ─── Data-defined (scoped) schemas ───────────────────────────────────────────────────────────────
|
|
453
|
+
|
|
454
|
+
export const PlanningSchemaKindSchema = cast<PlanningSchemaKind>({ type: 'string', enum: Object.values(PlanningSchemaKind) })
|
|
455
|
+
export const SchemaOriginSchema = cast<SchemaOrigin>({ type: 'string', enum: Object.values(SchemaOrigin) })
|
|
456
|
+
export const SchemaWriteModeSchema = cast<SchemaWriteMode>({ type: 'string', enum: Object.values(SchemaWriteMode) })
|
|
457
|
+
|
|
458
|
+
export const ScopedSchemaRecordSchema = cast<ScopedSchemaRecord>({
|
|
459
|
+
type: 'object',
|
|
460
|
+
properties: {
|
|
461
|
+
id: OptionalId,
|
|
462
|
+
entityId: Id,
|
|
463
|
+
project: OptionalId,
|
|
464
|
+
kind: PlanningSchemaKindSchema,
|
|
465
|
+
key: TypeKey,
|
|
466
|
+
version: { type: 'number', minimum: 1 },
|
|
467
|
+
/** A card type or a flow — its own schema is applied by `assertTypeSchema` / `assertFlowSchema`. */
|
|
468
|
+
definition: OpenObject,
|
|
469
|
+
retired: { type: 'boolean', nullable: true },
|
|
470
|
+
rev: { type: 'number', minimum: 0, nullable: true },
|
|
471
|
+
createdAt: IsoDate,
|
|
472
|
+
updatedAt: OptionalIsoDate,
|
|
473
|
+
by: { ...TransitionActorSchema, nullable: true },
|
|
474
|
+
},
|
|
475
|
+
required: ['entityId', 'kind', 'key', 'version', 'definition', 'createdAt'],
|
|
476
|
+
additionalProperties: false,
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
export const SchemaKeySchema = cast<SchemaKey>({
|
|
480
|
+
type: 'object',
|
|
481
|
+
properties: { kind: PlanningSchemaKindSchema, key: TypeKey },
|
|
482
|
+
required: ['kind', 'key'],
|
|
483
|
+
additionalProperties: false,
|
|
484
|
+
})
|
|
485
|
+
|
|
486
|
+
const originMap = () => ({ type: 'object', additionalProperties: { type: 'string', enum: Object.values(SchemaOrigin) }, required: [] })
|
|
487
|
+
const keyList = () => ({ type: 'array', items: TypeKey })
|
|
488
|
+
|
|
489
|
+
export const ScopedSchemaBundleSchema = cast<ScopedSchemaBundle>({
|
|
490
|
+
type: 'object',
|
|
491
|
+
properties: {
|
|
492
|
+
version: { type: 'number', minimum: 1 },
|
|
493
|
+
types: { type: 'array', items: AnyTypeSchemaSchema },
|
|
494
|
+
flows: { type: 'array', items: StatusFlowSchemaSchema },
|
|
495
|
+
assigneeTypes: { type: 'array', items: AssigneeTypeSchemaSchema, nullable: true },
|
|
496
|
+
app: { ...PlanningAppDeclarationsSchema, nullable: true },
|
|
497
|
+
scope: {
|
|
498
|
+
type: 'object',
|
|
499
|
+
properties: { entityId: Id, project: OptionalId },
|
|
500
|
+
required: ['entityId'],
|
|
501
|
+
additionalProperties: false,
|
|
502
|
+
nullable: true,
|
|
503
|
+
},
|
|
504
|
+
revision: { type: 'number', minimum: 0, nullable: true },
|
|
505
|
+
origins: {
|
|
506
|
+
type: 'object',
|
|
507
|
+
properties: { types: originMap(), flows: originMap(), assigneeTypes: { ...originMap(), nullable: true } },
|
|
508
|
+
required: ['types', 'flows'],
|
|
509
|
+
additionalProperties: false,
|
|
510
|
+
nullable: true,
|
|
511
|
+
},
|
|
512
|
+
retired: {
|
|
513
|
+
type: 'object',
|
|
514
|
+
properties: { types: keyList(), flows: keyList(), assigneeTypes: { ...keyList(), nullable: true } },
|
|
515
|
+
required: ['types', 'flows'],
|
|
516
|
+
additionalProperties: false,
|
|
517
|
+
nullable: true,
|
|
518
|
+
},
|
|
370
519
|
},
|
|
371
520
|
required: ['version', 'types', 'flows'],
|
|
372
521
|
additionalProperties: false,
|
|
373
522
|
})
|
|
374
523
|
|
|
524
|
+
export const SchemaListQuerySchema = cast<SchemaListQuery>({
|
|
525
|
+
type: 'object',
|
|
526
|
+
properties: { project: OptionalId },
|
|
527
|
+
required: [],
|
|
528
|
+
additionalProperties: false,
|
|
529
|
+
})
|
|
530
|
+
|
|
531
|
+
export const SchemaDefineRequestSchema = cast<SchemaDefineRequest>({
|
|
532
|
+
type: 'object',
|
|
533
|
+
properties: {
|
|
534
|
+
project: OptionalId,
|
|
535
|
+
mode: nullable(SchemaWriteModeSchema),
|
|
536
|
+
types: { type: 'array', maxItems: MAX_QUERY_LIST, items: WorkcardTypeSchemaSchema, nullable: true },
|
|
537
|
+
flows: { type: 'array', maxItems: MAX_QUERY_LIST, items: StatusFlowSchemaSchema, nullable: true },
|
|
538
|
+
assigneeTypes: { type: 'array', maxItems: MAX_QUERY_LIST, items: AssigneeTypeSchemaSchema, nullable: true },
|
|
539
|
+
retire: { type: 'array', maxItems: MAX_QUERY_LIST, items: SchemaKeySchema, nullable: true },
|
|
540
|
+
},
|
|
541
|
+
required: [],
|
|
542
|
+
additionalProperties: false,
|
|
543
|
+
})
|
|
544
|
+
|
|
375
545
|
// ─── Execution ───────────────────────────────────────────────────────────────────────────────────
|
|
376
546
|
|
|
377
547
|
export const WorkcardDraftSchema = cast<WorkcardDraft>({
|
|
@@ -380,6 +550,9 @@ export const WorkcardDraftSchema = cast<WorkcardDraft>({
|
|
|
380
550
|
kind: WorkcardKindSchema,
|
|
381
551
|
type: TypeKey,
|
|
382
552
|
parent: OptionalId,
|
|
553
|
+
reporter: OptionalId,
|
|
554
|
+
assignee: OptionalId,
|
|
555
|
+
mode: workcardProperties.mode,
|
|
383
556
|
parents: { ...workcardProperties.parents, nullable: true },
|
|
384
557
|
title: workcardProperties.title,
|
|
385
558
|
description: workcardProperties.description,
|
|
@@ -577,6 +750,8 @@ export const SpecificationQuerySchema = cast<SpecificationQueryWire>({
|
|
|
577
750
|
export const RelationshipQuerySchema = cast<RelationshipQueryWire>({
|
|
578
751
|
type: 'object',
|
|
579
752
|
properties: {
|
|
753
|
+
fromKind: { type: 'string', enum: [...Object.values(PlanningResourceKind), null], nullable: true },
|
|
754
|
+
toKind: { type: 'string', enum: [...Object.values(PlanningResourceKind), null], nullable: true },
|
|
580
755
|
...listWireProperties(),
|
|
581
756
|
from: listValue(),
|
|
582
757
|
to: listValue(),
|