@owlmeans/planning 0.1.18-rc.2 → 0.1.18-rc.21
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/src/helpers/query.ts
CHANGED
|
@@ -1,345 +1,175 @@
|
|
|
1
1
|
import type { Criteria, ListOptions, ResourceRecord, Sort } from '@owlmeans/resource'
|
|
2
2
|
import { IntrinsicStatus, WorkcardKind } from '../consts.js'
|
|
3
|
-
import {
|
|
4
|
-
import type {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
TransitionQuery, TransitionQueryWire, TransitionWhere, Workcard, WorkcardQuery, WorkcardQueryWire,
|
|
8
|
-
} from '../types.js'
|
|
9
|
-
|
|
10
|
-
type Scope = Pick<PlanningScope, 'entityId'>
|
|
3
|
+
import type { IntrinsicCounts, RelationshipQuery, RelationshipQueryWire, RelationshipWhere, Specification, SpecificationQuery, SpecificationQueryWire, SummaryQuery, SummaryQueryWire, SummaryView, TransitionQuery, TransitionQueryWire, TransitionWhere, Workcard, WorkcardQuery, WorkcardQueryWire } from '../types.js'
|
|
4
|
+
import type { QueryHelper } from './query/types.js'
|
|
5
|
+
import type { Scope } from './types.local.js'
|
|
6
|
+
import { wireHelper } from './wire.js'
|
|
11
7
|
|
|
12
8
|
const clean = <T extends object>(record: T): T =>
|
|
13
9
|
Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined)) as T
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
parent: query?.parent,
|
|
34
|
-
parents: query?.within != null ? { $contains: query.within } : undefined,
|
|
35
|
-
labels: query?.labels != null ? { $overlaps: query.labels } : undefined,
|
|
36
|
-
id: query?.ids != null ? { $in: query.ids } : undefined,
|
|
37
|
-
category: query?.category,
|
|
38
|
-
updatedAt: query?.updatedSince != null ? { $gte: query.updatedSince } : undefined,
|
|
39
|
-
}
|
|
40
|
-
if (query?.flow != null) {
|
|
41
|
-
where[`flows.${query.flow.id}`] = query.flow.status
|
|
42
|
-
}
|
|
43
|
-
for (const [key, value] of Object.entries(query?.fields ?? {})) {
|
|
44
|
-
if (value !== undefined) {
|
|
45
|
-
where[`fields.${key}`] = value
|
|
11
|
+
export const createQueryHelper = (): QueryHelper => {
|
|
12
|
+
/** `%`, `_` and `\` are wildcards/escapes to `$ilike`; a search text means them literally. */
|
|
13
|
+
const escapeLike = (text: string): string => text.replace(/[\\%_]/g, char => `\\${char}`)
|
|
14
|
+
|
|
15
|
+
const criteriaOf = (query?: WorkcardQuery | null, scope?: Scope): Criteria<Workcard> => {
|
|
16
|
+
const where: Record<string, unknown> = {
|
|
17
|
+
entityId: scope?.entityId,
|
|
18
|
+
kind: query?.kind,
|
|
19
|
+
type: query?.type,
|
|
20
|
+
status: query?.status,
|
|
21
|
+
intrinsic: query?.intrinsic,
|
|
22
|
+
code: query?.code,
|
|
23
|
+
parent: query?.parent,
|
|
24
|
+
parents: query?.within != null ? { $contains: query.within } : undefined,
|
|
25
|
+
labels: query?.labels != null ? { $overlaps: query.labels } : undefined,
|
|
26
|
+
id: query?.ids != null ? { $in: query.ids } : undefined,
|
|
27
|
+
category: query?.category,
|
|
28
|
+
updatedAt: query?.updatedSince != null ? { $gte: query.updatedSince } : undefined,
|
|
46
29
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
30
|
+
if (query?.flow != null) {
|
|
31
|
+
where[`flows.${query.flow.id}`] = query.flow.status
|
|
32
|
+
}
|
|
33
|
+
for (const [key, value] of Object.entries(query?.fields ?? {})) {
|
|
34
|
+
if (value !== undefined) {
|
|
35
|
+
where[`fields.${key}`] = value
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (query?.q != null && query.q !== '') {
|
|
39
|
+
const like = `%${escapeLike(query.q)}%`
|
|
40
|
+
where.$or = [
|
|
41
|
+
{ title: { $ilike: like } },
|
|
42
|
+
{ description: { $ilike: like } },
|
|
43
|
+
{ code: { $startsWith: query.q } },
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return clean(where) as Criteria<Workcard>
|
|
55
48
|
}
|
|
56
49
|
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
const listOptionsOf = <T extends ResourceRecord>(query?: ListOptions<T> | null): ListOptions<T> => clean({
|
|
51
|
+
page: query?.page,
|
|
52
|
+
size: query?.size,
|
|
53
|
+
sort: query?.sort,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const specCriteriaOf = (
|
|
57
|
+
parent: string, query?: SpecificationQuery | null, scope?: Scope
|
|
58
|
+
): Criteria<Specification> => clean({
|
|
59
|
+
entityId: scope?.entityId,
|
|
60
|
+
kind: WorkcardKind.Specification,
|
|
61
|
+
parent,
|
|
62
|
+
category: query?.category,
|
|
63
|
+
}) as Criteria<Specification>
|
|
59
64
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
})
|
|
65
|
+
const linkWhereOf = (query: RelationshipQuery | null | undefined, scope: Scope): RelationshipWhere => clean({
|
|
66
|
+
entityId: scope.entityId,
|
|
67
|
+
from: query?.from,
|
|
68
|
+
to: query?.to,
|
|
69
|
+
type: query?.type,
|
|
70
|
+
})
|
|
66
71
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}) as Criteria<Specification>
|
|
76
|
-
|
|
77
|
-
export const linkWhereOf = (query: RelationshipQuery | null | undefined, scope: Scope): RelationshipWhere => clean({
|
|
78
|
-
entityId: scope.entityId,
|
|
79
|
-
from: query?.from,
|
|
80
|
-
to: query?.to,
|
|
81
|
-
type: query?.type,
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
export const transitionWhereOf = (query: TransitionQuery | null | undefined, scope: Scope): TransitionWhere => clean({
|
|
85
|
-
entityId: scope.entityId,
|
|
86
|
-
card: query?.card,
|
|
87
|
-
project: query?.project,
|
|
88
|
-
sinceSeq: query?.sinceSeq,
|
|
89
|
-
action: query?.action,
|
|
90
|
-
state: query?.state,
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
const emptyCounts = (): IntrinsicCounts => ({
|
|
94
|
-
total: 0, [IntrinsicStatus.Planned]: 0, [IntrinsicStatus.InProgress]: 0, [IntrinsicStatus.Closed]: 0,
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
/** Counts of DIRECT children (`card.parent`) per parent, by intrinsic state. No key for a parent with none. */
|
|
98
|
-
export const summaryOf = (cards: Workcard[], parents: string[]): SummaryView => cards.reduce<SummaryView>(
|
|
99
|
-
(view, card) => {
|
|
100
|
-
if (card.parent == null || !parents.includes(card.parent)) {
|
|
101
|
-
return view
|
|
102
|
-
}
|
|
103
|
-
const counts = view[card.parent] ?? emptyCounts()
|
|
104
|
-
counts.total += 1
|
|
105
|
-
counts[card.intrinsic] = (counts[card.intrinsic] ?? 0) + 1
|
|
106
|
-
view[card.parent] = counts
|
|
107
|
-
return view
|
|
108
|
-
}, {}
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
// ─── Wire encoding ───────────────────────────────────────────────────────────────────────────────
|
|
112
|
-
|
|
113
|
-
/** A list as one query value: comma-joined, or JSON when an element itself holds a comma. */
|
|
114
|
-
export const encodeList = (value?: string | readonly string[] | null): string | undefined => {
|
|
115
|
-
if (value == null) {
|
|
116
|
-
return undefined
|
|
117
|
-
}
|
|
118
|
-
if (typeof value === 'string') {
|
|
119
|
-
return encodeList([value])
|
|
120
|
-
}
|
|
121
|
-
if (value.length === 0) {
|
|
122
|
-
return undefined
|
|
123
|
-
}
|
|
72
|
+
const transitionWhereOf = (query: TransitionQuery | null | undefined, scope: Scope): TransitionWhere => clean({
|
|
73
|
+
entityId: scope.entityId,
|
|
74
|
+
card: query?.card,
|
|
75
|
+
project: query?.project,
|
|
76
|
+
sinceSeq: query?.sinceSeq,
|
|
77
|
+
action: query?.action,
|
|
78
|
+
state: query?.state,
|
|
79
|
+
})
|
|
124
80
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
81
|
+
const emptyCounts = (): IntrinsicCounts => ({
|
|
82
|
+
total: 0, [IntrinsicStatus.Planned]: 0, [IntrinsicStatus.InProgress]: 0, [IntrinsicStatus.Closed]: 0,
|
|
83
|
+
})
|
|
129
84
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
if (Array.isArray(value)) {
|
|
136
|
-
const entries = value.filter(entry => entry != null && entry !== '').map(entry => `${entry}`)
|
|
137
|
-
return entries.length > 0 ? entries : undefined
|
|
138
|
-
}
|
|
139
|
-
if (typeof value !== 'string') {
|
|
140
|
-
return [`${value}`]
|
|
141
|
-
}
|
|
142
|
-
if (value.startsWith('[')) {
|
|
143
|
-
const parsed: unknown = (() => {
|
|
144
|
-
try {
|
|
145
|
-
return JSON.parse(value)
|
|
146
|
-
} catch {
|
|
147
|
-
throw new PlanningError(`malformed:query:${key}`)
|
|
85
|
+
const summaryOf = (cards: Workcard[], parents: string[]): SummaryView => cards.reduce<SummaryView>(
|
|
86
|
+
(view, card) => {
|
|
87
|
+
if (card.parent == null || !parents.includes(card.parent)) {
|
|
88
|
+
return view
|
|
148
89
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
90
|
+
const counts = view[card.parent] ?? emptyCounts()
|
|
91
|
+
counts.total += 1
|
|
92
|
+
counts[card.intrinsic] = (counts[card.intrinsic] ?? 0) + 1
|
|
93
|
+
view[card.parent] = counts
|
|
94
|
+
return view
|
|
95
|
+
}, {}
|
|
96
|
+
)
|
|
153
97
|
|
|
154
|
-
return
|
|
98
|
+
return { criteriaOf, listOptionsOf, specCriteriaOf, linkWhereOf, transitionWhereOf, summaryOf }
|
|
155
99
|
}
|
|
156
100
|
|
|
157
|
-
|
|
158
|
-
const decodeOneOrMany = <T extends string>(value: unknown, key: string): T | T[] | undefined => {
|
|
159
|
-
const entries = decodeList(value, key) as T[] | undefined
|
|
160
|
-
return entries == null ? undefined : entries.length === 1 ? entries[0] : entries
|
|
161
|
-
}
|
|
101
|
+
export const queryHelper = createQueryHelper()
|
|
162
102
|
|
|
163
|
-
|
|
103
|
+
/** @deprecated compat:factory-refactor — use `queryHelper.criteriaOf(…)` */
|
|
104
|
+
export const criteriaOf = (query?: WorkcardQuery | null, scope?: Scope): Criteria<Workcard> =>
|
|
105
|
+
queryHelper.criteriaOf(query, scope)
|
|
164
106
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
if (typeof value !== 'string') {
|
|
170
|
-
return value as T
|
|
171
|
-
}
|
|
172
|
-
try {
|
|
173
|
-
return JSON.parse(value) as T
|
|
174
|
-
} catch {
|
|
175
|
-
throw new PlanningError(`malformed:query:${key}`)
|
|
176
|
-
}
|
|
177
|
-
}
|
|
107
|
+
/** @deprecated compat:factory-refactor — use `queryHelper.listOptionsOf(…)` */
|
|
108
|
+
export const listOptionsOf = <T extends ResourceRecord>(query?: ListOptions<T> | null): ListOptions<T> =>
|
|
109
|
+
queryHelper.listOptionsOf<T>(query)
|
|
178
110
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
const number = typeof value === 'number' ? value : Number(value)
|
|
184
|
-
if (Number.isNaN(number)) {
|
|
185
|
-
throw new PlanningError(`malformed:query:${key}`)
|
|
186
|
-
}
|
|
111
|
+
/** @deprecated compat:factory-refactor — use `queryHelper.specCriteriaOf(…)` */
|
|
112
|
+
export const specCriteriaOf = (
|
|
113
|
+
parent: string, query?: SpecificationQuery | null, scope?: Scope
|
|
114
|
+
): Criteria<Specification> => queryHelper.specCriteriaOf(parent, query, scope)
|
|
187
115
|
|
|
188
|
-
|
|
189
|
-
|
|
116
|
+
/** @deprecated compat:factory-refactor — use `queryHelper.linkWhereOf(…)` */
|
|
117
|
+
export const linkWhereOf = (query: RelationshipQuery | null | undefined, scope: Scope): RelationshipWhere =>
|
|
118
|
+
queryHelper.linkWhereOf(query, scope)
|
|
190
119
|
|
|
191
|
-
|
|
192
|
-
|
|
120
|
+
/** @deprecated compat:factory-refactor — use `queryHelper.transitionWhereOf(…)` */
|
|
121
|
+
export const transitionWhereOf = (query: TransitionQuery | null | undefined, scope: Scope): TransitionWhere =>
|
|
122
|
+
queryHelper.transitionWhereOf(query, scope)
|
|
193
123
|
|
|
194
|
-
|
|
195
|
-
|
|
124
|
+
/** @deprecated compat:factory-refactor — use `queryHelper.summaryOf(…)` */
|
|
125
|
+
export const summaryOf = (cards: Workcard[], parents: string[]): SummaryView => queryHelper.summaryOf(cards, parents)
|
|
196
126
|
|
|
197
|
-
/**
|
|
198
|
-
export const
|
|
199
|
-
sort == null || sort.length === 0 ? undefined : sort.map(entry => typeof entry === 'string'
|
|
200
|
-
? entry
|
|
201
|
-
: `${entry.order === 'desc' ? '-' : ''}${entry.field}`).join(',')
|
|
127
|
+
/** @deprecated compat:factory-refactor — use `wireHelper.encodeList(…)` */
|
|
128
|
+
export const encodeList = (value?: string | readonly string[] | null): string | undefined => wireHelper.encodeList(value)
|
|
202
129
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
return undefined
|
|
206
|
-
}
|
|
207
|
-
const entries: unknown[] = Array.isArray(value) ? value : typeof value === 'string' ? value.split(',') : [value]
|
|
208
|
-
const sort = entries.flatMap((entry): Sort<T>[] => {
|
|
209
|
-
if (typeof entry !== 'string') {
|
|
210
|
-
return entry != null && typeof entry === 'object' && 'field' in entry ? [entry as Sort<T>] : []
|
|
211
|
-
}
|
|
212
|
-
const field = entry.trim()
|
|
213
|
-
if (field === '' || field === '-') {
|
|
214
|
-
return []
|
|
215
|
-
}
|
|
216
|
-
return field.startsWith('-')
|
|
217
|
-
? [{ field: field.slice(1) as Sort<T> & string, order: 'desc' } as Sort<T>]
|
|
218
|
-
: [field as Sort<T>]
|
|
219
|
-
})
|
|
130
|
+
/** @deprecated compat:factory-refactor — use `wireHelper.decodeList(…)` */
|
|
131
|
+
export const decodeList = (value: unknown, key: string = 'list'): string[] | undefined => wireHelper.decodeList(value, key)
|
|
220
132
|
|
|
221
|
-
|
|
222
|
-
|
|
133
|
+
/** @deprecated compat:factory-refactor — use `wireHelper.encodeSort(…)` */
|
|
134
|
+
export const encodeSort = <T>(sort?: Sort<T>[] | null): string | undefined => wireHelper.encodeSort<T>(sort)
|
|
223
135
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
size: query?.size,
|
|
227
|
-
sort: encodeSort(query?.sort),
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
const decodeListOptions = <T>(wire: Record<string, unknown>): ListOptions<T> => clean({
|
|
231
|
-
page: decodeNumber(wire.page, 'page'),
|
|
232
|
-
size: decodeNumber(wire.size, 'size'),
|
|
233
|
-
sort: decodeSort<T>(wire.sort),
|
|
234
|
-
})
|
|
235
|
-
|
|
236
|
-
const asRecord = (wire: unknown): Record<string, unknown> =>
|
|
237
|
-
wire != null && typeof wire === 'object' ? wire as Record<string, unknown> : {}
|
|
238
|
-
|
|
239
|
-
export const encodeWorkcardQuery = (query?: WorkcardQuery | null): WorkcardQueryWire => clean({
|
|
240
|
-
...encodeListOptions(query),
|
|
241
|
-
kind: encodeList(query?.kind),
|
|
242
|
-
type: encodeList(query?.type),
|
|
243
|
-
parent: query?.parent,
|
|
244
|
-
within: query?.within,
|
|
245
|
-
status: encodeList(query?.status),
|
|
246
|
-
intrinsic: encodeList(query?.intrinsic),
|
|
247
|
-
flow: encodeJson(query?.flow),
|
|
248
|
-
labels: encodeList(query?.labels),
|
|
249
|
-
code: encodeList(query?.code),
|
|
250
|
-
ids: encodeList(query?.ids),
|
|
251
|
-
fields: query?.fields != null && Object.keys(query.fields).length > 0 ? encodeJson(query.fields) : undefined,
|
|
252
|
-
q: query?.q,
|
|
253
|
-
category: encodeList(query?.category),
|
|
254
|
-
updatedSince: query?.updatedSince,
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
/** @throws {PlanningError} `malformed:query:<key>` for a value that does not decode */
|
|
258
|
-
export const decodeWorkcardQuery = (wire?: WorkcardQueryWire | WorkcardQuery | null): WorkcardQuery => {
|
|
259
|
-
const value = asRecord(wire)
|
|
260
|
-
return clean({
|
|
261
|
-
...decodeListOptions<Workcard>(value),
|
|
262
|
-
kind: decodeOneOrMany<WorkcardKind>(value.kind, 'kind'),
|
|
263
|
-
type: decodeOneOrMany(value.type, 'type'),
|
|
264
|
-
parent: decodeText(value.parent),
|
|
265
|
-
within: decodeText(value.within),
|
|
266
|
-
status: decodeOneOrMany(value.status, 'status'),
|
|
267
|
-
intrinsic: decodeOneOrMany<IntrinsicStatus>(value.intrinsic, 'intrinsic'),
|
|
268
|
-
flow: decodeJson<WorkcardQuery['flow']>(value.flow, 'flow'),
|
|
269
|
-
labels: decodeList(value.labels, 'labels'),
|
|
270
|
-
code: decodeOneOrMany(value.code, 'code'),
|
|
271
|
-
ids: decodeList(value.ids, 'ids'),
|
|
272
|
-
fields: decodeJson<Record<string, unknown>>(value.fields, 'fields'),
|
|
273
|
-
q: decodeText(value.q),
|
|
274
|
-
category: decodeOneOrMany(value.category, 'category'),
|
|
275
|
-
updatedSince: decodeText(value.updatedSince),
|
|
276
|
-
})
|
|
277
|
-
}
|
|
136
|
+
/** @deprecated compat:factory-refactor — use `wireHelper.decodeSort(…)` */
|
|
137
|
+
export const decodeSort = <T>(value: unknown): Sort<T>[] | undefined => wireHelper.decodeSort<T>(value)
|
|
278
138
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
kind: query.kind,
|
|
282
|
-
type: encodeList(query.type),
|
|
283
|
-
})
|
|
284
|
-
|
|
285
|
-
export const decodeSummaryQuery = (wire?: SummaryQueryWire | SummaryQuery | null): SummaryQuery => {
|
|
286
|
-
const value = asRecord(wire)
|
|
287
|
-
return clean({
|
|
288
|
-
parents: decodeList(value.parents, 'parents') ?? [],
|
|
289
|
-
kind: decodeText(value.kind) as WorkcardKind | undefined,
|
|
290
|
-
type: decodeOneOrMany(value.type, 'type'),
|
|
291
|
-
})
|
|
292
|
-
}
|
|
139
|
+
/** @deprecated compat:factory-refactor — use `wireHelper.encodeWorkcardQuery(…)` */
|
|
140
|
+
export const encodeWorkcardQuery = (query?: WorkcardQuery | null): WorkcardQueryWire => wireHelper.encodeWorkcardQuery(query)
|
|
293
141
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
project: query?.project,
|
|
298
|
-
sinceSeq: query?.sinceSeq,
|
|
299
|
-
action: encodeList(query?.action),
|
|
300
|
-
state: query?.state,
|
|
301
|
-
})
|
|
302
|
-
|
|
303
|
-
export const decodeTransitionQuery = (wire?: TransitionQueryWire | TransitionQuery | null): TransitionQuery => {
|
|
304
|
-
const value = asRecord(wire)
|
|
305
|
-
return clean({
|
|
306
|
-
...decodeListOptions(value),
|
|
307
|
-
card: decodeText(value.card),
|
|
308
|
-
project: decodeText(value.project),
|
|
309
|
-
sinceSeq: decodeNumber(value.sinceSeq, 'sinceSeq'),
|
|
310
|
-
action: decodeOneOrMany(value.action, 'action'),
|
|
311
|
-
state: decodeText(value.state),
|
|
312
|
-
}) as TransitionQuery
|
|
313
|
-
}
|
|
142
|
+
/** @deprecated compat:factory-refactor — use `wireHelper.decodeWorkcardQuery(…)` */
|
|
143
|
+
export const decodeWorkcardQuery = (wire?: WorkcardQueryWire | WorkcardQuery | null): WorkcardQuery =>
|
|
144
|
+
wireHelper.decodeWorkcardQuery(wire)
|
|
314
145
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
category: encodeList(query?.category),
|
|
318
|
-
all: query?.all,
|
|
319
|
-
})
|
|
320
|
-
|
|
321
|
-
export const decodeSpecificationQuery = (wire?: SpecificationQueryWire | SpecificationQuery | null): SpecificationQuery => {
|
|
322
|
-
const value = asRecord(wire)
|
|
323
|
-
return clean({
|
|
324
|
-
...decodeListOptions<Specification>(value),
|
|
325
|
-
category: decodeOneOrMany(value.category, 'category'),
|
|
326
|
-
all: decodeBoolean(value.all),
|
|
327
|
-
})
|
|
328
|
-
}
|
|
146
|
+
/** @deprecated compat:factory-refactor — use `wireHelper.encodeSummaryQuery(…)` */
|
|
147
|
+
export const encodeSummaryQuery = (query: SummaryQuery): SummaryQueryWire => wireHelper.encodeSummaryQuery(query)
|
|
329
148
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
149
|
+
/** @deprecated compat:factory-refactor — use `wireHelper.decodeSummaryQuery(…)` */
|
|
150
|
+
export const decodeSummaryQuery = (wire?: SummaryQueryWire | SummaryQuery | null): SummaryQuery =>
|
|
151
|
+
wireHelper.decodeSummaryQuery(wire)
|
|
152
|
+
|
|
153
|
+
/** @deprecated compat:factory-refactor — use `wireHelper.encodeTransitionQuery(…)` */
|
|
154
|
+
export const encodeTransitionQuery = (query?: TransitionQuery | null): TransitionQueryWire =>
|
|
155
|
+
wireHelper.encodeTransitionQuery(query)
|
|
156
|
+
|
|
157
|
+
/** @deprecated compat:factory-refactor — use `wireHelper.decodeTransitionQuery(…)` */
|
|
158
|
+
export const decodeTransitionQuery = (wire?: TransitionQueryWire | TransitionQuery | null): TransitionQuery =>
|
|
159
|
+
wireHelper.decodeTransitionQuery(wire)
|
|
160
|
+
|
|
161
|
+
/** @deprecated compat:factory-refactor — use `wireHelper.encodeSpecificationQuery(…)` */
|
|
162
|
+
export const encodeSpecificationQuery = (query?: SpecificationQuery | null): SpecificationQueryWire =>
|
|
163
|
+
wireHelper.encodeSpecificationQuery(query)
|
|
164
|
+
|
|
165
|
+
/** @deprecated compat:factory-refactor — use `wireHelper.decodeSpecificationQuery(…)` */
|
|
166
|
+
export const decodeSpecificationQuery = (wire?: SpecificationQueryWire | SpecificationQuery | null): SpecificationQuery =>
|
|
167
|
+
wireHelper.decodeSpecificationQuery(wire)
|
|
168
|
+
|
|
169
|
+
/** @deprecated compat:factory-refactor — use `wireHelper.encodeRelationshipQuery(…)` */
|
|
170
|
+
export const encodeRelationshipQuery = (query?: RelationshipQuery | null): RelationshipQueryWire =>
|
|
171
|
+
wireHelper.encodeRelationshipQuery(query)
|
|
172
|
+
|
|
173
|
+
/** @deprecated compat:factory-refactor — use `wireHelper.decodeRelationshipQuery(…)` */
|
|
174
|
+
export const decodeRelationshipQuery = (wire?: RelationshipQueryWire | RelationshipQuery | null): RelationshipQuery =>
|
|
175
|
+
wireHelper.decodeRelationshipQuery(wire)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { PlanningSchemaKind } from '../../consts.js'
|
|
2
|
+
import type {
|
|
3
|
+
PlanningSchemaBundle, PlanningSchemaRegistry, SchemaScope, ScopedSchemaBundle, ScopedSchemaRecord,
|
|
4
|
+
ScopedSchemaRegistry, StatusFlowSchema, WorkcardTypeSchema,
|
|
5
|
+
} from '../../types.js'
|
|
6
|
+
|
|
7
|
+
/** Data-defined (scoped) types and flows: their keys, the checks a write passes, and the layer they resolve to. */
|
|
8
|
+
export interface ScopedSchemaHelper {
|
|
9
|
+
/** One layer's identity of a record: organization, project (or none), kind, key. */
|
|
10
|
+
schemaRecordKey: (record: Pick<ScopedSchemaRecord, 'entityId' | 'project' | 'kind' | 'key'>) => string
|
|
11
|
+
/** The key a declaration is addressed by: a type's `type`, a flow's `id`. */
|
|
12
|
+
schemaKeyOf: (kind: PlanningSchemaKind, definition: WorkcardTypeSchema | StatusFlowSchema) => string
|
|
13
|
+
/**
|
|
14
|
+
* Refuse a data-defined declaration of a key the code registry seals: a code type or flow that does
|
|
15
|
+
* not say `overridable: true`, and any project or specification type.
|
|
16
|
+
*
|
|
17
|
+
* @throws {SchemaSealed}
|
|
18
|
+
*/
|
|
19
|
+
assertOverridable: (
|
|
20
|
+
code: Pick<PlanningSchemaRegistry, 'has' | 'type' | 'flows'>, kind: PlanningSchemaKind, key: string
|
|
21
|
+
) => void
|
|
22
|
+
/**
|
|
23
|
+
* The closed-form checks of a data-defined flow: the declaration's own schema, at least one status,
|
|
24
|
+
* unique status keys, every rule's `to` declared, and a `from` that is `'*'` or a list of declared
|
|
25
|
+
* statuses.
|
|
26
|
+
*
|
|
27
|
+
* @throws {SchemaInvalid}
|
|
28
|
+
*/
|
|
29
|
+
assertFlowSchema: (flow: StatusFlowSchema) => void
|
|
30
|
+
/**
|
|
31
|
+
* The closed-form checks of a data-defined type: the declaration's own schema, the `card` kind,
|
|
32
|
+
* a `fields` schema that compiles, and unique flow ids every one of which resolves.
|
|
33
|
+
*
|
|
34
|
+
* @throws {SchemaInvalid}
|
|
35
|
+
*/
|
|
36
|
+
assertTypeSchema: (type: WorkcardTypeSchema, flows: Pick<PlanningSchemaRegistry, 'flow'>) => void
|
|
37
|
+
/**
|
|
38
|
+
* One layer resolved: the code bundle, overlaid by the organization-wide records, then by the
|
|
39
|
+
* project's (when `scope.project` names one). Pure — every store and both sides of the wire resolve
|
|
40
|
+
* the same records to the same bundle.
|
|
41
|
+
*
|
|
42
|
+
* - A code key is sealed unless it says `overridable: true`; project and specification types are
|
|
43
|
+
* always sealed, so only card types and flows are data-defined. A record under a sealed key is
|
|
44
|
+
* ignored here (the write path refuses it with `SchemaSealed`).
|
|
45
|
+
* - A project record overrides an organization-wide one of the same key.
|
|
46
|
+
* - A retired record gives way to the nearest live declaration below it; a key retired in every
|
|
47
|
+
* layer resolves retired — present in `types`/`flows`, listed in `retired`.
|
|
48
|
+
* - A data-defined declaration carries its record's `version`.
|
|
49
|
+
*/
|
|
50
|
+
resolveScopedBundle: (
|
|
51
|
+
code: PlanningSchemaBundle, records: readonly ScopedSchemaRecord[], scope: SchemaScope & { revision?: number }
|
|
52
|
+
) => ScopedSchemaBundle
|
|
53
|
+
/**
|
|
54
|
+
* A read-only registry over a resolved layer. It answers exactly what `makeSchemaRegistry` answers
|
|
55
|
+
* for the same types and flows, plus where each key came from and whether it is retired; its
|
|
56
|
+
* `registerType`, `registerFlow` and `load` refuse with `PlanningUnsupported`.
|
|
57
|
+
*/
|
|
58
|
+
scopedRegistryOf: (bundle: ScopedSchemaBundle) => ScopedSchemaRegistry
|
|
59
|
+
/**
|
|
60
|
+
* The live type that keeps a flow from being retired in a resolved layer: the flow resolves retired
|
|
61
|
+
* (no live declaration below the retired record) while a type that is not retired still runs it.
|
|
62
|
+
*/
|
|
63
|
+
flowInUse: (bundle: ScopedSchemaBundle, flow: string) => string | undefined
|
|
64
|
+
}
|