@owlmeans/planning 0.1.18-rc.21 → 0.1.18-rc.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/agent-meta/manifest.json +2 -2
- package/agent-meta/skills/planning/SKILL.md +51 -3
- package/build/aliases/types.d.ts +23 -0
- package/build/aliases/types.d.ts.map +1 -1
- package/build/aliases.d.ts.map +1 -1
- package/build/aliases.js +2 -1
- package/build/aliases.js.map +1 -1
- package/build/consts.d.ts +21 -1
- package/build/consts.d.ts.map +1 -1
- package/build/consts.js +23 -0
- package/build/consts.js.map +1 -1
- package/build/entrypoints/types.d.ts +7 -0
- package/build/entrypoints/types.d.ts.map +1 -1
- package/build/entrypoints.d.ts.map +1 -1
- package/build/entrypoints.js +7 -0
- package/build/entrypoints.js.map +1 -1
- package/build/helpers/apply.d.ts.map +1 -1
- package/build/helpers/apply.js +8 -3
- package/build/helpers/apply.js.map +1 -1
- package/build/helpers/changes/consts.local.d.ts.map +1 -1
- package/build/helpers/changes/consts.local.js +1 -1
- package/build/helpers/changes/consts.local.js.map +1 -1
- package/build/helpers/changes.d.ts.map +1 -1
- package/build/helpers/changes.js +3 -0
- package/build/helpers/changes.js.map +1 -1
- package/build/helpers/query.js +1 -1
- package/build/helpers/query.js.map +1 -1
- package/build/helpers/scoped/types.d.ts +4 -2
- package/build/helpers/scoped/types.d.ts.map +1 -1
- package/build/helpers/scoped.d.ts +3 -2
- package/build/helpers/scoped.d.ts.map +1 -1
- package/build/helpers/scoped.js +29 -8
- package/build/helpers/scoped.js.map +1 -1
- package/build/helpers/validate/types.d.ts +1 -0
- package/build/helpers/validate/types.d.ts.map +1 -1
- package/build/helpers/validate.d.ts.map +1 -1
- package/build/helpers/validate.js +6 -1
- package/build/helpers/validate.js.map +1 -1
- package/build/helpers/wire.d.ts.map +1 -1
- package/build/helpers/wire.js +2 -1
- package/build/helpers/wire.js.map +1 -1
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/registry.d.ts.map +1 -1
- package/build/registry.js +26 -0
- 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 +29 -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 +6 -0
- package/build/schemas.d.ts.map +1 -1
- package/build/schemas.js +59 -3
- package/build/schemas.js.map +1 -1
- package/build/types.d.ts +55 -3
- package/build/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/aliases/types.ts +3 -0
- package/src/aliases.ts +5 -2
- package/src/consts.ts +24 -0
- package/src/entrypoints/types.ts +7 -0
- package/src/entrypoints.ts +8 -0
- package/src/helpers/apply.ts +8 -3
- package/src/helpers/changes/consts.local.ts +1 -1
- package/src/helpers/changes.ts +3 -0
- package/src/helpers/query.ts +1 -1
- package/src/helpers/scoped/types.ts +4 -2
- package/src/helpers/scoped.ts +32 -13
- package/src/helpers/validate/types.ts +1 -0
- package/src/helpers/validate.ts +6 -1
- package/src/helpers/wire.ts +2 -1
- package/src/index.ts +4 -0
- package/src/registry.ts +26 -0
- package/src/resources/mentions.ts +11 -0
- package/src/resources/protocol/types.ts +20 -0
- package/src/resources/protocol.ts +35 -0
- package/src/resources/types.local.ts +5 -0
- package/src/resources/types.ts +148 -0
- package/src/schemas.ts +61 -3
- package/src/types.ts +50 -4
package/src/helpers/scoped.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { AssigneeTypeSchema } from '../resources/types.js'
|
|
2
|
+
import { AssigneeTypeSchemaSchema } from '../schemas.js'
|
|
1
3
|
import { memoHelper } from '@owlmeans/context'
|
|
2
4
|
import { PlanningSchemaKind, SchemaOrigin, WorkcardKind } from '../consts.js'
|
|
3
5
|
import { PlanningUnsupported, SchemaInvalid, SchemaSealed } from '../errors.js'
|
|
@@ -16,20 +18,25 @@ const messageOf = (error: unknown): string => error instanceof Error ? error.mes
|
|
|
16
18
|
export const createScopedSchemaHelper = (): ScopedSchemaHelper => {
|
|
17
19
|
const checkers = memoHelper.once(() => {
|
|
18
20
|
const ajv = makeAjv()
|
|
19
|
-
return { flow: ajv.compile(StatusFlowSchemaSchema), type: ajv.compile(WorkcardTypeSchemaSchema) }
|
|
21
|
+
return { flow: ajv.compile(StatusFlowSchemaSchema), type: ajv.compile(WorkcardTypeSchemaSchema), assignee: ajv.compile(AssigneeTypeSchemaSchema) }
|
|
20
22
|
})
|
|
21
23
|
|
|
22
24
|
const schemaRecordKey = (record: Pick<ScopedSchemaRecord, 'entityId' | 'project' | 'kind' | 'key'>): string =>
|
|
23
25
|
`${record.entityId}\u0000${record.project ?? ''}\u0000${record.kind}\u0000${record.key}`
|
|
24
26
|
|
|
25
|
-
const schemaKeyOf = (kind: PlanningSchemaKind, definition: WorkcardTypeSchema | StatusFlowSchema): string =>
|
|
26
|
-
kind
|
|
27
|
+
const schemaKeyOf = (kind: PlanningSchemaKind, definition: WorkcardTypeSchema | StatusFlowSchema | AssigneeTypeSchema): string =>
|
|
28
|
+
kind !== PlanningSchemaKind.Flow ? (definition as WorkcardTypeSchema).type : (definition as StatusFlowSchema).id
|
|
27
29
|
|
|
28
30
|
const openType = (type: AnyTypeSchema): boolean => type.kind === WorkcardKind.Card && type.overridable === true
|
|
29
31
|
|
|
30
32
|
const assertOverridable = (
|
|
31
|
-
code: Pick<PlanningSchemaRegistry, 'has' | 'type' | 'flows'>, kind: PlanningSchemaKind, key: string
|
|
33
|
+
code: Pick<PlanningSchemaRegistry, 'has' | 'type' | 'flows' | 'assigneeTypes'>, kind: PlanningSchemaKind, key: string
|
|
32
34
|
): void => {
|
|
35
|
+
if (kind === PlanningSchemaKind.AssigneeType) {
|
|
36
|
+
const schema = code.assigneeTypes().find(schema => schema.type === key)
|
|
37
|
+
if (schema != null && schema.overridable !== true) throw new SchemaSealed(`assignee:${key}`)
|
|
38
|
+
return
|
|
39
|
+
}
|
|
33
40
|
if (kind === PlanningSchemaKind.Type) {
|
|
34
41
|
if (code.has(key) && !openType(code.type(key))) {
|
|
35
42
|
throw new SchemaSealed(`type:${key}`)
|
|
@@ -42,6 +49,12 @@ export const createScopedSchemaHelper = (): ScopedSchemaHelper => {
|
|
|
42
49
|
}
|
|
43
50
|
}
|
|
44
51
|
|
|
52
|
+
const assertAssigneeTypeSchema = (schema: AssigneeTypeSchema): void => {
|
|
53
|
+
const validate = checkers().assignee
|
|
54
|
+
if (!validate(schema)) throw new SchemaInvalid(`assignee:${(schema as Partial<AssigneeTypeSchema>)?.type}:${validateHelper.ajvErrorText(validate.errors)}`)
|
|
55
|
+
try { makeAjv().compile(schema.fields) } catch (error) { throw new SchemaInvalid(`assignee:${schema.type}:fields:${messageOf(error)}`) }
|
|
56
|
+
}
|
|
57
|
+
|
|
45
58
|
const assertFlowSchema = (flow: StatusFlowSchema): void => {
|
|
46
59
|
const validate = checkers().flow
|
|
47
60
|
if (!validate(flow)) {
|
|
@@ -100,7 +113,7 @@ export const createScopedSchemaHelper = (): ScopedSchemaHelper => {
|
|
|
100
113
|
}
|
|
101
114
|
}
|
|
102
115
|
|
|
103
|
-
const resolveKind = <T extends WorkcardTypeSchema | StatusFlowSchema | AnyTypeSchema>(
|
|
116
|
+
const resolveKind = <T extends WorkcardTypeSchema | StatusFlowSchema | AssigneeTypeSchema | AnyTypeSchema>(
|
|
104
117
|
kind: PlanningSchemaKind,
|
|
105
118
|
code: readonly T[],
|
|
106
119
|
keyOf: (definition: T) => string,
|
|
@@ -171,14 +184,19 @@ export const createScopedSchemaHelper = (): ScopedSchemaHelper => {
|
|
|
171
184
|
flow => flow != null && typeof flow.id === 'string', layers,
|
|
172
185
|
)
|
|
173
186
|
|
|
187
|
+
const assignees = resolveKind<AssigneeTypeSchema>(
|
|
188
|
+
PlanningSchemaKind.AssigneeType, code.assigneeTypes ?? [], schema => schema.type, schema => schema.overridable === true,
|
|
189
|
+
schema => schema != null && typeof schema.type === 'string', { entity: layers.entity, project: [] },
|
|
190
|
+
)
|
|
174
191
|
return structuredClone({
|
|
192
|
+
assigneeTypes: assignees.definitions,
|
|
175
193
|
version: code.version,
|
|
176
194
|
types: types.definitions,
|
|
177
195
|
flows: flows.definitions,
|
|
178
196
|
scope: scope.project == null ? { entityId: scope.entityId } : { entityId: scope.entityId, project: scope.project },
|
|
179
197
|
revision: scope.revision ?? 0,
|
|
180
|
-
origins: { types: types.origins, flows: flows.origins },
|
|
181
|
-
retired: { types: types.retired, flows: flows.retired },
|
|
198
|
+
origins: { types: types.origins, flows: flows.origins, assigneeTypes: assignees.origins },
|
|
199
|
+
retired: { types: types.retired, flows: flows.retired, ...(assignees.retired.length > 0 ? { assigneeTypes: assignees.retired } : {}) },
|
|
182
200
|
})
|
|
183
201
|
}
|
|
184
202
|
|
|
@@ -189,19 +207,20 @@ export const createScopedSchemaHelper = (): ScopedSchemaHelper => {
|
|
|
189
207
|
throw new PlanningUnsupported('schemas:read-only')
|
|
190
208
|
}
|
|
191
209
|
const originsOf = (kind: PlanningSchemaKind) =>
|
|
192
|
-
kind === PlanningSchemaKind.Type ? kept.origins?.types : kept.origins?.flows
|
|
210
|
+
kind === PlanningSchemaKind.AssigneeType ? kept.origins?.assigneeTypes : kind === PlanningSchemaKind.Type ? kept.origins?.types : kept.origins?.flows
|
|
193
211
|
const retiredOf = (kind: PlanningSchemaKind) =>
|
|
194
|
-
kind === PlanningSchemaKind.Type ? kept.retired?.types : kept.retired?.flows
|
|
212
|
+
kind === PlanningSchemaKind.AssigneeType ? kept.retired?.assigneeTypes : kind === PlanningSchemaKind.Type ? kept.retired?.types : kept.retired?.flows
|
|
195
213
|
|
|
196
214
|
return {
|
|
197
215
|
...base,
|
|
216
|
+
registerAssigneeType: refuse,
|
|
198
217
|
registerType: refuse,
|
|
199
218
|
registerFlow: refuse,
|
|
200
219
|
load: refuse,
|
|
201
220
|
scope: kept.scope ?? { entityId: '' },
|
|
202
221
|
revision: kept.revision ?? 0,
|
|
203
222
|
originOf: (kind, key) => originsOf(kind)?.[key]
|
|
204
|
-
?? ((kind === PlanningSchemaKind.Type ? base.has(key) : base.flows().some(flow => flow.id === key))
|
|
223
|
+
?? ((kind === PlanningSchemaKind.AssigneeType ? base.assigneeTypes().some(schema => schema.type === key) : kind === PlanningSchemaKind.Type ? base.has(key) : base.flows().some(flow => flow.id === key))
|
|
205
224
|
? SchemaOrigin.Code
|
|
206
225
|
: undefined),
|
|
207
226
|
isRetired: (kind, key) => retiredOf(kind)?.includes(key) === true,
|
|
@@ -218,7 +237,7 @@ export const createScopedSchemaHelper = (): ScopedSchemaHelper => {
|
|
|
218
237
|
}
|
|
219
238
|
|
|
220
239
|
return {
|
|
221
|
-
schemaRecordKey, schemaKeyOf, assertOverridable, assertFlowSchema, assertTypeSchema, resolveScopedBundle,
|
|
240
|
+
schemaRecordKey, schemaKeyOf, assertOverridable, assertAssigneeTypeSchema, assertFlowSchema, assertTypeSchema, resolveScopedBundle,
|
|
222
241
|
scopedRegistryOf, flowInUse,
|
|
223
242
|
}
|
|
224
243
|
}
|
|
@@ -230,12 +249,12 @@ export const schemaRecordKey = (record: Pick<ScopedSchemaRecord, 'entityId' | 'p
|
|
|
230
249
|
scopedSchemaHelper.schemaRecordKey(record)
|
|
231
250
|
|
|
232
251
|
/** @deprecated compat:factory-refactor — use `scopedSchemaHelper.schemaKeyOf(…)` */
|
|
233
|
-
export const schemaKeyOf = (kind: PlanningSchemaKind, definition: WorkcardTypeSchema | StatusFlowSchema): string =>
|
|
252
|
+
export const schemaKeyOf = (kind: PlanningSchemaKind, definition: WorkcardTypeSchema | StatusFlowSchema | AssigneeTypeSchema): string =>
|
|
234
253
|
scopedSchemaHelper.schemaKeyOf(kind, definition)
|
|
235
254
|
|
|
236
255
|
/** @deprecated compat:factory-refactor — use `scopedSchemaHelper.assertOverridable(…)` */
|
|
237
256
|
export const assertOverridable = (
|
|
238
|
-
code: Pick<PlanningSchemaRegistry, 'has' | 'type' | 'flows'>, kind: PlanningSchemaKind, key: string
|
|
257
|
+
code: Pick<PlanningSchemaRegistry, 'has' | 'type' | 'flows' | 'assigneeTypes'>, kind: PlanningSchemaKind, key: string
|
|
239
258
|
): void => scopedSchemaHelper.assertOverridable(code, kind, key)
|
|
240
259
|
|
|
241
260
|
/** @deprecated compat:factory-refactor — use `scopedSchemaHelper.assertFlowSchema(…)` */
|
|
@@ -3,6 +3,7 @@ import type { PlanningSchemaRegistry, SpecificationSlot, Workcard } from '../../
|
|
|
3
3
|
|
|
4
4
|
/** A record's shape, its type's `fields` and a document's body, checked against their schemas. */
|
|
5
5
|
export interface ValidateHelper {
|
|
6
|
+
assertSchema: (schema: object, value: unknown) => void
|
|
6
7
|
/** One line per ajv error: `/path message`. */
|
|
7
8
|
ajvErrorText: (errors?: ErrorObject[] | null) => string
|
|
8
9
|
/** Keys holding `.` or `$` at any depth — a document store cannot hold them. */
|
package/src/helpers/validate.ts
CHANGED
|
@@ -89,7 +89,12 @@ export const createValidateHelper = (): ValidateHelper => {
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
const assertSchema = (schema: object, value: unknown): void => {
|
|
93
|
+
const validate = compile(schema)
|
|
94
|
+
if (!validate(value)) throw new FieldsInvalid(ajvErrorText(validate.errors))
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return { assertSchema, ajvErrorText, invalidFieldKeys, validateFields, validateCard, validateSpecificationBody }
|
|
93
98
|
}
|
|
94
99
|
|
|
95
100
|
export const validateHelper = createValidateHelper()
|
package/src/helpers/wire.ts
CHANGED
|
@@ -225,7 +225,7 @@ export const createWireHelper = (): WireHelper => {
|
|
|
225
225
|
...encodeListOptions(query),
|
|
226
226
|
from: encodeList(query?.from),
|
|
227
227
|
to: encodeList(query?.to),
|
|
228
|
-
type: encodeList(query?.type),
|
|
228
|
+
type: encodeList(query?.type), fromKind: query?.fromKind, toKind: query?.toKind,
|
|
229
229
|
})
|
|
230
230
|
|
|
231
231
|
const decodeRelationshipQuery = (wire?: RelationshipQueryWire | RelationshipQuery | null): RelationshipQuery => {
|
|
@@ -235,6 +235,7 @@ export const createWireHelper = (): WireHelper => {
|
|
|
235
235
|
from: decodeOneOrMany(value.from, 'from'),
|
|
236
236
|
to: decodeOneOrMany(value.to, 'to'),
|
|
237
237
|
type: decodeOneOrMany(value.type, 'type'),
|
|
238
|
+
fromKind: value.fromKind, toKind: value.toKind,
|
|
238
239
|
}) as RelationshipQuery
|
|
239
240
|
}
|
|
240
241
|
|
package/src/index.ts
CHANGED
|
@@ -11,3 +11,7 @@ export * from './registry.js'
|
|
|
11
11
|
export * from './helpers/index.js'
|
|
12
12
|
export * from './models/index.js'
|
|
13
13
|
export * from './i18n.js'
|
|
14
|
+
|
|
15
|
+
export type * from './resources/types.js'
|
|
16
|
+
export * from './resources/mentions.js'
|
|
17
|
+
export type * from './resources/protocol/types.js'
|
package/src/registry.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
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'
|
|
@@ -13,11 +14,31 @@ import type { AnyTypeSchema, PlanningSchemaBundle, PlanningSchemaRegistry, Statu
|
|
|
13
14
|
*/
|
|
14
15
|
export const makeSchemaRegistry = (bundle?: Partial<PlanningSchemaBundle>): PlanningSchemaRegistry => {
|
|
15
16
|
const types = new Map<string, AnyTypeSchema>()
|
|
17
|
+
const assigneeTypes = new Map<string, AssigneeTypeSchema>()
|
|
18
|
+
const assigneeValidators = new Map<string, ValidateFunction>()
|
|
16
19
|
const flows = new Map<string, StatusFlowSchema>()
|
|
17
20
|
const validators = new Map<string, ValidateFunction>()
|
|
18
21
|
let ajv = makeAjv()
|
|
19
22
|
|
|
20
23
|
const registry: PlanningSchemaRegistry = {
|
|
24
|
+
registerAssigneeType: schema => {
|
|
25
|
+
assigneeTypes.set(schema.type, schema)
|
|
26
|
+
assigneeValidators.delete(schema.type)
|
|
27
|
+
},
|
|
28
|
+
assigneeTypes: () => [...assigneeTypes.values()],
|
|
29
|
+
assigneeType: type => {
|
|
30
|
+
const schema = assigneeTypes.get(type)
|
|
31
|
+
if (schema == null) throw new UnknownWorkcardType(`assignee:${type}`)
|
|
32
|
+
return schema
|
|
33
|
+
},
|
|
34
|
+
assigneeValidator: type => {
|
|
35
|
+
let validator = assigneeValidators.get(type)
|
|
36
|
+
if (validator == null) {
|
|
37
|
+
validator = ajv.compile(registry.assigneeType(type).fields)
|
|
38
|
+
assigneeValidators.set(type, validator)
|
|
39
|
+
}
|
|
40
|
+
return validator
|
|
41
|
+
},
|
|
21
42
|
registerType: schema => {
|
|
22
43
|
types.set(schema.type, schema)
|
|
23
44
|
validators.delete(schema.type)
|
|
@@ -70,19 +91,24 @@ export const makeSchemaRegistry = (bundle?: Partial<PlanningSchemaBundle>): Plan
|
|
|
70
91
|
version: PLANNING_SCHEMA_VERSION,
|
|
71
92
|
types: [...types.values()],
|
|
72
93
|
flows: [...flows.values()],
|
|
94
|
+
assigneeTypes: [...assigneeTypes.values()],
|
|
73
95
|
}),
|
|
74
96
|
|
|
75
97
|
load: next => {
|
|
98
|
+
assigneeTypes.clear()
|
|
99
|
+
assigneeValidators.clear()
|
|
76
100
|
types.clear()
|
|
77
101
|
flows.clear()
|
|
78
102
|
validators.clear()
|
|
79
103
|
// A fresh ajv: a replaced type must not collide with a schema `$id` the old one compiled.
|
|
80
104
|
ajv = makeAjv()
|
|
105
|
+
next.assigneeTypes?.forEach(registry.registerAssigneeType)
|
|
81
106
|
next.flows.forEach(registry.registerFlow)
|
|
82
107
|
next.types.forEach(registry.registerType)
|
|
83
108
|
},
|
|
84
109
|
}
|
|
85
110
|
|
|
111
|
+
bundle?.assigneeTypes?.forEach(registry.registerAssigneeType)
|
|
86
112
|
bundle?.flows?.forEach(registry.registerFlow)
|
|
87
113
|
bundle?.types?.forEach(registry.registerType)
|
|
88
114
|
|
|
@@ -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,20 @@
|
|
|
1
|
+
import type { EntrypointProtocol } from '@owlmeans/entrypoint'
|
|
2
|
+
import type { ListResult } from '@owlmeans/resource'
|
|
3
|
+
import type { AssigneeDraft, TeamDraft, CommentDraft, PlanningRecord } from '../types.js'
|
|
4
|
+
|
|
5
|
+
export interface PlanningResourceQueryWire { query?: string }
|
|
6
|
+
export interface PlanningResourceCommand {
|
|
7
|
+
action: 'create' | 'update' | 'retire' | 'remove' | 'members' | 'projects' | 'assignees' | 'addMember' | 'removeMember' | 'attach' | 'detach' | 'rebuild'
|
|
8
|
+
id?: string
|
|
9
|
+
draft?: Partial<AssigneeDraft & TeamDraft & CommentDraft>
|
|
10
|
+
version?: number
|
|
11
|
+
assignee?: string
|
|
12
|
+
project?: string
|
|
13
|
+
}
|
|
14
|
+
export type PlanningResourceReply = PlanningRecord | PlanningRecord[] | string[] | null
|
|
15
|
+
|
|
16
|
+
export interface PlanningResourceProtocols<R extends PlanningRecord = PlanningRecord> {
|
|
17
|
+
get: EntrypointProtocol<{ params: { id: string } }, R>
|
|
18
|
+
list: EntrypointProtocol<{ query: PlanningResourceQueryWire }, ListResult<R>>
|
|
19
|
+
write: EntrypointProtocol<{ body: PlanningResourceCommand }, PlanningResourceReply>
|
|
20
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { planningAliasHelper } from '../aliases.js'
|
|
2
|
+
import { contract, protocol, typed } from '@owlmeans/entrypoint'
|
|
3
|
+
import type { EntrypointProtocol } from '@owlmeans/entrypoint'
|
|
4
|
+
import { backend, route, RouteMethod } from '@owlmeans/route'
|
|
5
|
+
import { AssigneeKind } from '../consts.js'
|
|
6
|
+
import { WorkcardParamsSchema } from '../schemas.js'
|
|
7
|
+
import type { ListResult } from '@owlmeans/resource'
|
|
8
|
+
import type { PlanningRecord } from './types.js'
|
|
9
|
+
import type { PlanningResourceCommand, PlanningResourceProtocols, PlanningResourceQueryWire, PlanningResourceReply } from './protocol/types.js'
|
|
10
|
+
|
|
11
|
+
/** Opt-in auxiliary branches share the planning base's authentication guards. */
|
|
12
|
+
export const makePlanningResourceProtocols = <R extends PlanningRecord>(base: EntrypointProtocol<any, any>, alias: string, name: 'assignees' | 'teams' | 'comments' | 'mentions'): PlanningResourceProtocols<R> => {
|
|
13
|
+
const aliases = planningAliasHelper.planningResourceAliases(alias)[name]
|
|
14
|
+
const commandSchema = {
|
|
15
|
+
type: 'object', properties: {
|
|
16
|
+
action: { type: 'string', enum: ['create', 'update', 'retire', 'remove', 'members', 'projects', 'assignees', 'addMember', 'removeMember', 'attach', 'detach', 'rebuild'] },
|
|
17
|
+
id: { type: 'string', minLength: 1, maxLength: 128, nullable: true },
|
|
18
|
+
version: { type: 'number', minimum: 1, nullable: true },
|
|
19
|
+
assignee: { type: 'string', minLength: 1, maxLength: 128, nullable: true },
|
|
20
|
+
project: { type: 'string', minLength: 1, maxLength: 128, nullable: true },
|
|
21
|
+
draft: { type: 'object', properties: {
|
|
22
|
+
nickname: { type: 'string', nullable: true }, kind: { type: 'string', enum: [...Object.values(AssigneeKind), null], nullable: true },
|
|
23
|
+
type: { type: 'string', nullable: true }, name: { type: 'string', nullable: true }, externalId: { type: 'string', nullable: true },
|
|
24
|
+
card: { type: 'string', nullable: true }, body: { type: 'string', nullable: true },
|
|
25
|
+
fields: { type: 'object', additionalProperties: true, required: [], nullable: true },
|
|
26
|
+
authentication: { type: 'object', properties: { provider: { type: 'string' }, externalId: { type: 'string' } }, required: ['provider', 'externalId'], additionalProperties: false, nullable: true },
|
|
27
|
+
}, required: [], additionalProperties: false, nullable: true },
|
|
28
|
+
}, required: ['action'], additionalProperties: false,
|
|
29
|
+
}
|
|
30
|
+
return Object.freeze({
|
|
31
|
+
get: protocol(route(aliases.get, `/${name}/:id`, backend({ parent: base }, RouteMethod.GET)), contract.request({ params: typed<{ id: string }>(WorkcardParamsSchema) }, typed<R>())),
|
|
32
|
+
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<ListResult<R>>())),
|
|
33
|
+
write: protocol(route(aliases.write, `/${name}`, backend({ parent: base }, RouteMethod.POST)), contract.request({ body: typed<PlanningResourceCommand>(commandSchema as any) }, typed<PlanningResourceReply>())),
|
|
34
|
+
})
|
|
35
|
+
}
|
|
@@ -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'
|
|
@@ -68,6 +70,10 @@ const workcardProperties = {
|
|
|
68
70
|
title: { type: 'string', minLength: 1, maxLength: TITLE_MAX },
|
|
69
71
|
description: { type: 'string', maxLength: DESCRIPTION_MAX, nullable: true },
|
|
70
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 },
|
|
71
77
|
parents: { type: 'array', maxItems: MAX_PARENTS, items: Id },
|
|
72
78
|
status: Label,
|
|
73
79
|
intrinsic: IntrinsicStatusSchema,
|
|
@@ -101,7 +107,7 @@ const specificationProperties = {
|
|
|
101
107
|
/** The optional form of a schema: `nullable`, and `null` admitted by an enum too. */
|
|
102
108
|
const nullable = (schema: object): object => {
|
|
103
109
|
const enumerated = (schema as { enum?: unknown[] }).enum
|
|
104
|
-
return { ...schema, nullable: true, ...(enumerated != null ? { enum: [...enumerated, null] } : {}) }
|
|
110
|
+
return { ...schema, nullable: true, ...(enumerated != null ? { enum: [...new Set([...enumerated, null])] } : {}) }
|
|
105
111
|
}
|
|
106
112
|
|
|
107
113
|
const nullableOf = (properties: Record<string, object>): Record<string, object> =>
|
|
@@ -135,6 +141,8 @@ export const RelationshipDraftSchema = cast<RelationshipDraft>({
|
|
|
135
141
|
type: Label,
|
|
136
142
|
from: OptionalId,
|
|
137
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 },
|
|
138
146
|
fields: OptionalOpenObject,
|
|
139
147
|
},
|
|
140
148
|
required: ['type', 'to'],
|
|
@@ -149,6 +157,8 @@ export const RelationshipSchema = cast<Relationship>({
|
|
|
149
157
|
type: Label,
|
|
150
158
|
from: Id,
|
|
151
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 },
|
|
152
162
|
project: OptionalId,
|
|
153
163
|
fields: OptionalOpenObject,
|
|
154
164
|
createdAt: IsoDate,
|
|
@@ -166,6 +176,10 @@ export const WorkcardChangesSchema = cast<WorkcardChanges>({
|
|
|
166
176
|
title: workcardProperties.title,
|
|
167
177
|
description: workcardProperties.description,
|
|
168
178
|
parent: workcardProperties.parent,
|
|
179
|
+
parentType: workcardProperties.parentType,
|
|
180
|
+
reporter: workcardProperties.reporter,
|
|
181
|
+
assignee: workcardProperties.assignee,
|
|
182
|
+
mode: workcardProperties.mode,
|
|
169
183
|
parents: workcardProperties.parents,
|
|
170
184
|
status: workcardProperties.status,
|
|
171
185
|
intrinsic: workcardProperties.intrinsic,
|
|
@@ -306,6 +320,10 @@ export const RelationshipTypeSchema = cast<RelationshipType>({
|
|
|
306
320
|
name: Label,
|
|
307
321
|
from: { type: 'array', items: TypeKey, nullable: true },
|
|
308
322
|
to: { type: 'array', items: TypeKey, nullable: true },
|
|
323
|
+
fromKind: { type: 'string', enum: [...Object.values(PlanningResourceKind), null], nullable: true },
|
|
324
|
+
toKind: { type: 'string', enum: [...Object.values(PlanningResourceKind), null], nullable: true },
|
|
325
|
+
field: { ...TypeKey, nullable: true },
|
|
326
|
+
multiple: { type: 'boolean', nullable: true },
|
|
309
327
|
inverse: { ...Label, nullable: true },
|
|
310
328
|
single: { type: 'boolean', nullable: true },
|
|
311
329
|
label: { type: 'string', maxLength: TITLE_MAX, nullable: true },
|
|
@@ -321,6 +339,8 @@ const typeProperties = {
|
|
|
321
339
|
flows: { type: 'array', minItems: 1, items: TypeKey },
|
|
322
340
|
intrinsic: nullable(IntrinsicPolicySchema),
|
|
323
341
|
specifications: { type: 'array', items: SpecificationSlotSchema },
|
|
342
|
+
parents: { type: 'object', properties: { types: { type: 'array', items: TypeKey, nullable: true }, required: { type: 'boolean', nullable: true } }, required: [], additionalProperties: false, nullable: true },
|
|
343
|
+
children: { type: 'object', properties: { types: { type: 'array', items: TypeKey } }, required: ['types'], additionalProperties: false, nullable: true },
|
|
324
344
|
relationships: { type: 'array', items: RelationshipTypeSchema, nullable: true },
|
|
325
345
|
labels: { type: 'array', items: Label, nullable: true },
|
|
326
346
|
code: { ...CodePolicySchema, nullable: true },
|
|
@@ -367,12 +387,43 @@ export const AnyTypeSchemaSchema = cast<AnyTypeSchema>({
|
|
|
367
387
|
additionalProperties: false,
|
|
368
388
|
})
|
|
369
389
|
|
|
390
|
+
export const AssigneeTypeSchemaSchema = cast<AssigneeTypeSchema>({
|
|
391
|
+
type: 'object', properties: {
|
|
392
|
+
type: TypeKey, version: { type: 'number', minimum: 1 }, kind: { type: 'string', enum: Object.values(AssigneeKind) },
|
|
393
|
+
fields: OpenObject, authentication: { type: 'string', enum: ['required', 'optional', null], nullable: true },
|
|
394
|
+
label: { type: 'string', maxLength: TITLE_MAX, nullable: true }, overridable: { type: 'boolean', nullable: true },
|
|
395
|
+
}, required: ['type', 'version', 'kind', 'fields'], additionalProperties: false,
|
|
396
|
+
})
|
|
397
|
+
const planningRecordProperties = { id: OptionalId, entityId: Id, version: { type: 'number', minimum: 1 }, createdAt: IsoDate, updatedAt: OptionalIsoDate }
|
|
398
|
+
const planningRecordRequired = ['entityId', 'version', 'createdAt']
|
|
399
|
+
export const AssigneeSchema = cast<Assignee>({
|
|
400
|
+
type: 'object', properties: { ...planningRecordProperties,
|
|
401
|
+
nickname: { type: 'string', minLength: 1, maxLength: TYPE_MAX }, nicknameKey: TypeKey,
|
|
402
|
+
kind: { type: 'string', enum: Object.values(AssigneeKind) }, type: TypeKey, fields: OpenObject,
|
|
403
|
+
authentication: { type: 'object', properties: { provider: TypeKey, externalId: { type: 'string', minLength: 1, maxLength: REF_MAX } }, required: ['provider', 'externalId'], additionalProperties: false, nullable: true },
|
|
404
|
+
retired: { type: 'boolean', nullable: true },
|
|
405
|
+
}, required: [...planningRecordRequired, 'nickname', 'nicknameKey', 'kind', 'type', 'fields'], additionalProperties: false,
|
|
406
|
+
})
|
|
407
|
+
export const TeamSchema = cast<Team>({
|
|
408
|
+
type: 'object', properties: { ...planningRecordProperties, name: { type: 'string', minLength: 1, maxLength: TITLE_MAX }, externalId: { type: 'string', minLength: 1, maxLength: REF_MAX, nullable: true }, fields: OpenObject },
|
|
409
|
+
required: [...planningRecordRequired, 'name', 'fields'], additionalProperties: false,
|
|
410
|
+
})
|
|
411
|
+
export const CommentSchema = cast<Comment>({
|
|
412
|
+
type: 'object', properties: { ...planningRecordProperties, card: Id, author: Id, body: { type: 'string', minLength: 1, maxLength: BODY_MAX } },
|
|
413
|
+
required: [...planningRecordRequired, 'card', 'author', 'body'], additionalProperties: false,
|
|
414
|
+
})
|
|
415
|
+
export const CommentMentionSchema = cast<CommentMention>({
|
|
416
|
+
type: 'object', properties: { ...planningRecordProperties, comment: Id, card: Id, assignee: Id, revision: { type: 'number', minimum: 1 } },
|
|
417
|
+
required: [...planningRecordRequired, 'comment', 'card', 'assignee', 'revision'], additionalProperties: false,
|
|
418
|
+
})
|
|
419
|
+
|
|
370
420
|
export const PlanningSchemaBundleSchema = cast<PlanningSchemaBundle>({
|
|
371
421
|
type: 'object',
|
|
372
422
|
properties: {
|
|
373
423
|
version: { type: 'number', minimum: 1 },
|
|
374
424
|
types: { type: 'array', items: AnyTypeSchemaSchema },
|
|
375
425
|
flows: { type: 'array', items: StatusFlowSchemaSchema },
|
|
426
|
+
assigneeTypes: { type: 'array', items: AssigneeTypeSchemaSchema, nullable: true },
|
|
376
427
|
},
|
|
377
428
|
required: ['version', 'types', 'flows'],
|
|
378
429
|
additionalProperties: false,
|
|
@@ -421,6 +472,7 @@ export const ScopedSchemaBundleSchema = cast<ScopedSchemaBundle>({
|
|
|
421
472
|
version: { type: 'number', minimum: 1 },
|
|
422
473
|
types: { type: 'array', items: AnyTypeSchemaSchema },
|
|
423
474
|
flows: { type: 'array', items: StatusFlowSchemaSchema },
|
|
475
|
+
assigneeTypes: { type: 'array', items: AssigneeTypeSchemaSchema, nullable: true },
|
|
424
476
|
scope: {
|
|
425
477
|
type: 'object',
|
|
426
478
|
properties: { entityId: Id, project: OptionalId },
|
|
@@ -431,14 +483,14 @@ export const ScopedSchemaBundleSchema = cast<ScopedSchemaBundle>({
|
|
|
431
483
|
revision: { type: 'number', minimum: 0, nullable: true },
|
|
432
484
|
origins: {
|
|
433
485
|
type: 'object',
|
|
434
|
-
properties: { types: originMap(), flows: originMap() },
|
|
486
|
+
properties: { types: originMap(), flows: originMap(), assigneeTypes: { ...originMap(), nullable: true } },
|
|
435
487
|
required: ['types', 'flows'],
|
|
436
488
|
additionalProperties: false,
|
|
437
489
|
nullable: true,
|
|
438
490
|
},
|
|
439
491
|
retired: {
|
|
440
492
|
type: 'object',
|
|
441
|
-
properties: { types: keyList(), flows: keyList() },
|
|
493
|
+
properties: { types: keyList(), flows: keyList(), assigneeTypes: { ...keyList(), nullable: true } },
|
|
442
494
|
required: ['types', 'flows'],
|
|
443
495
|
additionalProperties: false,
|
|
444
496
|
nullable: true,
|
|
@@ -462,6 +514,7 @@ export const SchemaDefineRequestSchema = cast<SchemaDefineRequest>({
|
|
|
462
514
|
mode: nullable(SchemaWriteModeSchema),
|
|
463
515
|
types: { type: 'array', maxItems: MAX_QUERY_LIST, items: WorkcardTypeSchemaSchema, nullable: true },
|
|
464
516
|
flows: { type: 'array', maxItems: MAX_QUERY_LIST, items: StatusFlowSchemaSchema, nullable: true },
|
|
517
|
+
assigneeTypes: { type: 'array', maxItems: MAX_QUERY_LIST, items: AssigneeTypeSchemaSchema, nullable: true },
|
|
465
518
|
retire: { type: 'array', maxItems: MAX_QUERY_LIST, items: SchemaKeySchema, nullable: true },
|
|
466
519
|
},
|
|
467
520
|
required: [],
|
|
@@ -476,6 +529,9 @@ export const WorkcardDraftSchema = cast<WorkcardDraft>({
|
|
|
476
529
|
kind: WorkcardKindSchema,
|
|
477
530
|
type: TypeKey,
|
|
478
531
|
parent: OptionalId,
|
|
532
|
+
reporter: OptionalId,
|
|
533
|
+
assignee: OptionalId,
|
|
534
|
+
mode: workcardProperties.mode,
|
|
479
535
|
parents: { ...workcardProperties.parents, nullable: true },
|
|
480
536
|
title: workcardProperties.title,
|
|
481
537
|
description: workcardProperties.description,
|
|
@@ -673,6 +729,8 @@ export const SpecificationQuerySchema = cast<SpecificationQueryWire>({
|
|
|
673
729
|
export const RelationshipQuerySchema = cast<RelationshipQueryWire>({
|
|
674
730
|
type: 'object',
|
|
675
731
|
properties: {
|
|
732
|
+
fromKind: { type: 'string', enum: [...Object.values(PlanningResourceKind), null], nullable: true },
|
|
733
|
+
toKind: { type: 'string', enum: [...Object.values(PlanningResourceKind), null], nullable: true },
|
|
676
734
|
...listWireProperties(),
|
|
677
735
|
from: listValue(),
|
|
678
736
|
to: listValue(),
|