@dotbep/core 0.2.7 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/index.d.ts +7 -5
  2. package/dist/index.js +604 -596
  3. package/package.json +4 -1
  4. package/examples/01-participants.ts +0 -127
  5. package/examples/02-files.ts +0 -100
  6. package/examples/03-workflows.ts +0 -149
  7. package/examples/04-bim-uses.ts +0 -70
  8. package/examples/05-standards.ts +0 -60
  9. package/examples/06-schedule.ts +0 -124
  10. package/examples/07-loin.ts +0 -133
  11. package/examples/08-deliverables.ts +0 -126
  12. package/examples/09-notes.ts +0 -73
  13. package/examples/10-llm.ts +0 -109
  14. package/examples/11-resolved.ts +0 -133
  15. package/examples/12-history.ts +0 -166
  16. package/examples/13-engine.ts +0 -152
  17. package/examples/bep.d.ts +0 -38
  18. package/examples/example.bep +0 -0
  19. package/examples/run-all.ts +0 -38
  20. package/src/base/entity.ts +0 -148
  21. package/src/base/history.ts +0 -497
  22. package/src/base/index.ts +0 -5
  23. package/src/base/singleton.ts +0 -26
  24. package/src/entities/actions.ts +0 -25
  25. package/src/entities/adapters.ts +0 -16
  26. package/src/entities/annexes.ts +0 -17
  27. package/src/entities/assetTypes.ts +0 -30
  28. package/src/entities/automations.ts +0 -24
  29. package/src/entities/bimUses.ts +0 -50
  30. package/src/entities/deliverables.ts +0 -66
  31. package/src/entities/disciplines.ts +0 -21
  32. package/src/entities/effects.ts +0 -28
  33. package/src/entities/env.ts +0 -17
  34. package/src/entities/events.ts +0 -24
  35. package/src/entities/extensions.ts +0 -16
  36. package/src/entities/flags.ts +0 -17
  37. package/src/entities/guides.ts +0 -26
  38. package/src/entities/index.ts +0 -32
  39. package/src/entities/lbsNodes.ts +0 -193
  40. package/src/entities/lods.ts +0 -22
  41. package/src/entities/loin.ts +0 -127
  42. package/src/entities/lois.ts +0 -22
  43. package/src/entities/members.ts +0 -137
  44. package/src/entities/milestones.ts +0 -32
  45. package/src/entities/notes.ts +0 -27
  46. package/src/entities/objectives.ts +0 -17
  47. package/src/entities/phases.ts +0 -17
  48. package/src/entities/remoteData.ts +0 -17
  49. package/src/entities/resolvers.ts +0 -20
  50. package/src/entities/roles.ts +0 -29
  51. package/src/entities/softwares.ts +0 -26
  52. package/src/entities/standards.ts +0 -68
  53. package/src/entities/teams.ts +0 -42
  54. package/src/entities/workflows.ts +0 -256
  55. package/src/index.ts +0 -464
  56. package/src/runtime/Engine.ts +0 -352
  57. package/src/runtime/MemoryStorage.ts +0 -31
  58. package/src/runtime/Runtime.ts +0 -106
  59. package/src/runtime/index.ts +0 -4
  60. package/src/runtime/transitions.ts +0 -456
  61. package/src/runtime/types.ts +0 -279
  62. package/src/types/history.ts +0 -37
  63. package/src/types/index.ts +0 -24
  64. package/src/types/resolved.ts +0 -137
  65. package/src/types/schema.ts +0 -757
  66. package/src/utils/diff.ts +0 -109
  67. package/src/utils/index.ts +0 -9
  68. package/src/utils/integrity.ts +0 -108
  69. package/src/utils/lbs.ts +0 -116
  70. package/src/utils/mermaid.ts +0 -110
  71. package/src/utils/naming.ts +0 -62
  72. package/src/utils/nomenclature.ts +0 -107
  73. package/src/utils/normalize.ts +0 -35
  74. package/src/utils/raci.ts +0 -25
  75. package/src/utils/textFile.ts +0 -24
  76. package/tsconfig.json +0 -12
  77. package/vite.config.ts +0 -24
@@ -1,50 +0,0 @@
1
- import type { BEP, BIMUse, Milestone, Objective } from '../types/schema.js'
2
- import { Entity } from '../base/entity.js'
3
- import { BIMUseSchema } from '../types/schema.js'
4
- import type { BIMUseResolved, MilestoneResolved, WorkflowResolved } from '../types/resolved.js'
5
- import type { Workflows } from './workflows.js'
6
-
7
- export class BIMUses extends Entity<BIMUse, true> {
8
- constructor(getBep: () => BEP, private readonly getWorkflows: () => Workflows) {
9
- super(
10
- () => getBep().bimUses,
11
- getBep,
12
- {
13
- key: 'bimUses',
14
- schema: BIMUseSchema,
15
- autoId: true,
16
- validate: (item, bep) => {
17
- const errors: string[] = []
18
- for (const id of item.objectiveIds ?? []) {
19
- if (!bep.objectives.some(o => o.id === id))
20
- errors.push(`objectives["${id}"] not found`)
21
- }
22
- for (const id of item.milestoneIds ?? []) {
23
- if (!bep.milestones.some(m => m.id === id))
24
- errors.push(`milestones["${id}"] not found`)
25
- }
26
- for (const id of item.workflowIds ?? []) {
27
- if (!bep.workflows.some(w => w.id === id))
28
- errors.push(`workflows["${id}"] not found`)
29
- }
30
- return errors
31
- },
32
- },
33
- )
34
- }
35
-
36
- listResolved(): BIMUseResolved[] {
37
- const bep = this.getBep()
38
- const workflowMap = new Map(this.getWorkflows().listResolved().map(w => [w.id, w]))
39
- return bep.bimUses.map(bu => ({
40
- ...bu,
41
- objectives: (bu.objectiveIds ?? []).map(id => bep.objectives.find(o => o.id === id)).filter(Boolean) as Objective[],
42
- milestones: (bu.milestoneIds ?? []).map(id => {
43
- const m = bep.milestones.find(m => m.id === id)
44
- if (!m) return null
45
- return { ...m, phase: bep.phases.find(p => p.id === m.phaseId) ?? null } satisfies MilestoneResolved
46
- }).filter(Boolean) as MilestoneResolved[],
47
- workflows: (bu.workflowIds ?? []).map(id => workflowMap.get(id)).filter(Boolean) as WorkflowResolved[],
48
- }))
49
- }
50
- }
@@ -1,66 +0,0 @@
1
- import type { BEP, Deliverable, Discipline, Extension } from '../types/schema.js'
2
- import { Entity } from '../base/entity.js'
3
- import { DeliverableSchema } from '../types/schema.js'
4
- import type { DeliverableResolved, AssetTypeResolved, LBSNodeResolved, TeamResolved } from '../types/resolved.js'
5
- import { buildCodeMap } from '../utils/nomenclature.js'
6
- import type { AssetTypes } from './assetTypes.js'
7
- import type { LBSNodes } from './lbsNodes.js'
8
- import type { Milestones } from './milestones.js'
9
- import type { Teams } from './teams.js'
10
-
11
- export class Deliverables extends Entity<Deliverable, true> {
12
- constructor(
13
- getBep: () => BEP,
14
- private readonly getTeams: () => Teams,
15
- private readonly getAssetTypes: () => AssetTypes,
16
- private readonly getLBSNodes: () => LBSNodes,
17
- private readonly getMilestones: () => Milestones,
18
- ) {
19
- super(
20
- () => getBep().deliverables,
21
- getBep,
22
- {
23
- key: 'deliverables',
24
- schema: DeliverableSchema,
25
- autoId: true,
26
- },
27
- )
28
- }
29
-
30
- listResolved(): DeliverableResolved[] {
31
- const bep = this.getBep()
32
- const codeMap = buildCodeMap(bep.deliverables, bep.project.code, bep.lbs)
33
- const teamMap = new Map(this.getTeams().listResolved().map(t => [t.id, t]))
34
- const assetTypeMap = new Map(this.getAssetTypes().listResolved().map(dt => [dt.id, dt]))
35
- const lbsNodeMap = new Map(this.getLBSNodes().listResolved().map(n => [n.id, n]))
36
- const milestoneMap = new Map(this.getMilestones().listResolved().map(m => [m.id, m]))
37
-
38
- // First pass: resolve everything except predecessor
39
- const partials = new Map<string, DeliverableResolved>()
40
- for (const d of bep.deliverables) {
41
- const milestone = milestoneMap.get(d.milestoneId) ?? null
42
- partials.set(d.id, {
43
- ...d,
44
- nomenclatureCode: codeMap.get(d.id) ?? '',
45
- effectiveDate: d.dueDate ?? milestone?.date ?? '',
46
- lbsNode: d.lbsNodeId ? (lbsNodeMap.get(d.lbsNodeId) ?? null) : null as LBSNodeResolved | null,
47
- discipline: bep.disciplines.find(di => di.id === d.disciplineId) ?? null as Discipline | null,
48
- assetType: assetTypeMap.get(d.assetTypeId) ?? null as AssetTypeResolved | null,
49
- extensions: (d.extensionIds ?? []).map(id => bep.extensions.find(e => e.id === id)).filter(Boolean) as Extension[],
50
- responsible: teamMap.get(d.responsibleId) ?? null as TeamResolved | null,
51
- milestone: milestone,
52
- predecessor: null,
53
- })
54
- }
55
-
56
- // Second pass: resolve predecessors using the already-computed partials
57
- for (const d of bep.deliverables) {
58
- if (d.predecessorId) {
59
- const resolved = partials.get(d.id)!
60
- resolved.predecessor = partials.get(d.predecessorId) ?? null
61
- }
62
- }
63
-
64
- return [...partials.values()]
65
- }
66
- }
@@ -1,21 +0,0 @@
1
- import type { BEP, Discipline } from '../types/schema.js'
2
- import { Entity } from '../base/entity.js'
3
- import { DisciplineSchema } from '../types/schema.js'
4
- import { validateTokenValue } from '../utils/naming.js'
5
-
6
- export class Disciplines extends Entity<Discipline> {
7
- constructor(getBep: () => BEP) {
8
- super(
9
- () => getBep().disciplines,
10
- getBep,
11
- {
12
- key: 'disciplines',
13
- schema: DisciplineSchema,
14
- validate: (d, bep) => {
15
- const err = validateTokenValue('discipline', d.id, bep.deliverableNamingConvention)
16
- return err ? [err] : []
17
- },
18
- },
19
- )
20
- }
21
- }
@@ -1,28 +0,0 @@
1
- import type { BEP, FlowEffect } from '../types/schema.js'
2
- import { Entity } from '../base/entity.js'
3
- import { FlowEffectSchema } from '../types/schema.js'
4
-
5
- export class Effects extends Entity<FlowEffect> {
6
- constructor(getBep: () => BEP) {
7
- super(
8
- () => getBep().effects,
9
- getBep,
10
- {
11
- key: 'effects',
12
- schema: FlowEffectSchema,
13
- beforeRemove: (id, bep) => {
14
- for (const wf of bep.workflows) {
15
- for (const [nodeKey, node] of Object.entries(wf.diagram.nodes)) {
16
- if ((node.type === 'process' || node.type === 'automation') && node.timeouts?.some(t => t.effectId === id))
17
- throw new Error(`Referenced by: workflows["${wf.id}"].diagram.nodes["${nodeKey}"].timeouts[].effectId`)
18
- }
19
- for (const [edgeKey, edge] of Object.entries(wf.diagram.edges)) {
20
- if (edge.effectIds?.includes(id))
21
- throw new Error(`Referenced by: workflows["${wf.id}"].diagram.edges["${edgeKey}"].effectIds`)
22
- }
23
- }
24
- },
25
- },
26
- )
27
- }
28
- }
@@ -1,17 +0,0 @@
1
- import type { BEP, EnvVar } from '../types/schema.js'
2
- import { Entity } from '../base/entity.js'
3
- import { EnvVarSchema } from '../types/schema.js'
4
-
5
- export class Env extends Entity<EnvVar> {
6
- constructor(getBep: () => BEP) {
7
- super(
8
- () => getBep().env,
9
- getBep,
10
- {
11
- key: 'env',
12
- idField: 'key',
13
- schema: EnvVarSchema,
14
- },
15
- )
16
- }
17
- }
@@ -1,24 +0,0 @@
1
- import type { BEP, FlowEvent } from '../types/schema.js'
2
- import { Entity } from '../base/entity.js'
3
- import { FlowEventSchema } from '../types/schema.js'
4
-
5
- export class Events extends Entity<FlowEvent> {
6
- constructor(getBep: () => BEP) {
7
- super(
8
- () => getBep().events,
9
- getBep,
10
- {
11
- key: 'events',
12
- schema: FlowEventSchema,
13
- beforeRemove: (id, bep) => {
14
- for (const wf of bep.workflows) {
15
- for (const [edgeKey, edge] of Object.entries(wf.diagram.edges)) {
16
- if ('triggerEventId' in edge && edge.triggerEventId === id)
17
- throw new Error(`Referenced by: workflows["${wf.id}"].diagram.edges["${edgeKey}"].trigger`)
18
- }
19
- }
20
- },
21
- },
22
- )
23
- }
24
- }
@@ -1,16 +0,0 @@
1
- import type { BEP, Extension } from '../types/schema.js'
2
- import { Entity } from '../base/entity.js'
3
- import { ExtensionSchema } from '../types/schema.js'
4
-
5
- export class Extensions extends Entity<Extension> {
6
- constructor(getBep: () => BEP) {
7
- super(
8
- () => getBep().extensions,
9
- getBep,
10
- {
11
- key: 'extensions',
12
- schema: ExtensionSchema,
13
- },
14
- )
15
- }
16
- }
@@ -1,17 +0,0 @@
1
- import type { BEP, Flag } from '../types/schema.js'
2
- import { Entity } from '../base/entity.js'
3
- import { FlagSchema } from '../types/schema.js'
4
-
5
- export class Flags extends Entity<Flag, true> {
6
- constructor(getBep: () => BEP) {
7
- super(
8
- () => getBep().flags,
9
- getBep,
10
- {
11
- key: 'flags',
12
- schema: FlagSchema,
13
- autoId: true,
14
- },
15
- )
16
- }
17
- }
@@ -1,26 +0,0 @@
1
- import type { Annex, BEP, Guide } from '../types/schema.js'
2
- import { Entity } from '../base/entity.js'
3
- import { GuideSchema } from '../types/schema.js'
4
- import type { GuideResolved } from '../types/resolved.js'
5
-
6
- export class Guides extends Entity<Guide, true> {
7
- constructor(getBep: () => BEP) {
8
- super(
9
- () => getBep().guides,
10
- getBep,
11
- {
12
- key: 'guides',
13
- schema: GuideSchema,
14
- autoId: true,
15
- },
16
- )
17
- }
18
-
19
- listResolved(): GuideResolved[] {
20
- const bep = this.getBep()
21
- return bep.guides.map(g => ({
22
- ...g,
23
- annexes: (g.annexIds ?? []).map(id => bep.annexes.find(a => a.id === id)).filter(Boolean) as Annex[],
24
- }))
25
- }
26
- }
@@ -1,32 +0,0 @@
1
- export { Actions } from './actions.js'
2
- export { Adapters } from './adapters.js'
3
- export { Automations } from './automations.js'
4
- export { Env } from './env.js'
5
- export { Effects } from './effects.js'
6
- export { Events } from './events.js'
7
- export { Annexes } from './annexes.js'
8
- export { BIMUses } from './bimUses.js'
9
- export { Deliverables } from './deliverables.js'
10
- export { Disciplines } from './disciplines.js'
11
- export { AssetTypes } from './assetTypes.js'
12
- export { Extensions } from './extensions.js'
13
- export { Flags } from './flags.js'
14
- export { Roles } from './roles.js'
15
- export { Guides } from './guides.js'
16
- export { LBSNodes } from './lbsNodes.js'
17
- export type { LBSNodeAddInput, LBSNodeUpdateInput } from './lbsNodes.js'
18
- export { LODs } from './lods.js'
19
- export { LOIs } from './lois.js'
20
- export { LOINEntity } from './loin.js'
21
- export { Members } from './members.js'
22
- export { Milestones } from './milestones.js'
23
- export { Notes } from './notes.js'
24
- export { Objectives } from './objectives.js'
25
- export { Phases } from './phases.js'
26
- export { RemoteDataEntity } from './remoteData.js'
27
- export { Resolvers } from './resolvers.js'
28
- export { Softwares } from './softwares.js'
29
- export { Standards } from './standards.js'
30
- export type { StandardAddInput } from './standards.js'
31
- export { Teams } from './teams.js'
32
- export { Workflows } from './workflows.js'
@@ -1,193 +0,0 @@
1
- import type { BEP, LBSNode } from '../types/schema.js'
2
- import { Entity } from '../base/entity.js'
3
- import { LBSNodeSchema } from '../types/schema.js'
4
- import { buildParentMap, getRootIds, resolveLBSCodes, validateLBS } from '../utils/lbs.js'
5
- import type { LBSNodeResolved } from '../types/resolved.js'
6
- import { validateTokenValue } from '../utils/naming.js'
7
-
8
- export type LBSNodeAddInput = Omit<LBSNode, 'lbsNodeIds'> & { parentId?: string }
9
- export type LBSNodeUpdateInput = Partial<Omit<LBSNode, 'lbsNodeIds'>> & { id: string; parentId?: string | null }
10
-
11
- export class LBSNodes extends Entity<LBSNode> {
12
- constructor(getBep: () => BEP) {
13
- super(
14
- () => getBep().lbs,
15
- getBep,
16
- {
17
- key: 'lbs',
18
- schema: LBSNodeSchema,
19
- validate: (item, bep) => {
20
- const errors: string[] = []
21
-
22
- const token = item.type === 'zone' ? 'lbsZone' : 'lbsLocation'
23
- const tokenErr = validateTokenValue(token, item.id, bep.deliverableNamingConvention)
24
- if (tokenErr) errors.push(tokenErr)
25
-
26
- // Per-node check: location nodes cannot have zone children.
27
- // Root-must-be-zone and cycle detection are whole-tree invariants
28
- // — use validateTree() after building the full structure.
29
- if (item.type === 'location' && item.lbsNodeIds?.length) {
30
- const nodeMap = new Map(bep.lbs.map(n => [n.id, n]))
31
- for (const childId of item.lbsNodeIds ?? []) {
32
- if (nodeMap.get(childId)?.type === 'zone')
33
- errors.push(`Node "${item.id}" (location) cannot have a zone child ("${childId}").`)
34
- }
35
- }
36
-
37
- return errors
38
- },
39
- },
40
- )
41
- }
42
-
43
- listResolved(): LBSNodeResolved[] {
44
- const bep = this.getBep()
45
- const parentMap = buildParentMap(bep.lbs)
46
- const rootIds = getRootIds(bep.lbs)
47
- const nodeMap = new Map(bep.lbs.map(n => [n.id, n]))
48
- return bep.lbs.map(node => {
49
- const parentId = parentMap.get(node.id)
50
- const parentNode = parentId ? nodeMap.get(parentId) : undefined
51
- return {
52
- ...node,
53
- isRoot: rootIds.has(node.id),
54
- parent: parentNode ? { id: parentNode.id, name: parentNode.name, type: parentNode.type } : null,
55
- children: (node.lbsNodeIds ?? []).map(id => nodeMap.get(id)).filter(Boolean) as LBSNode[],
56
- }
57
- })
58
- }
59
-
60
- addNodes(items: LBSNodeAddInput[]): {
61
- succeeded: Array<LBSNode & { parentId: string | null }>
62
- failed: Array<{ input: unknown; error: string }>
63
- } {
64
- const bep = this.getBep()
65
- const succeeded: Array<LBSNode & { parentId: string | null }> = []
66
- const failed: Array<{ input: unknown; error: string }> = []
67
-
68
- for (const item of items) {
69
- if (!item.parentId && item.type !== 'zone') {
70
- failed.push({ input: item, error: 'Root nodes (no parentId) must be type "zone".' })
71
- continue
72
- }
73
- if (item.parentId && !bep.lbs.find(n => n.id === item.parentId)) {
74
- failed.push({ input: item, error: `No LBS node found with ID "${item.parentId}".` })
75
- continue
76
- }
77
-
78
- const { parentId, ...node } = item
79
- const addResult = this.add([node])
80
- if (addResult.failed.length > 0) {
81
- failed.push({ input: item, error: addResult.failed[0].error })
82
- continue
83
- }
84
-
85
- let parent: LBSNode | undefined
86
- if (parentId) {
87
- parent = bep.lbs.find(n => n.id === parentId)
88
- if (parent) {
89
- parent.lbsNodeIds ??= []
90
- parent.lbsNodeIds.push(item.id)
91
- }
92
- }
93
-
94
- const errors = this.validateTree()
95
- if (errors.length > 0) {
96
- bep.lbs.splice(bep.lbs.findIndex(n => n.id === item.id), 1)
97
- if (parent?.lbsNodeIds) parent.lbsNodeIds = parent.lbsNodeIds.filter(c => c !== item.id)
98
- failed.push({ input: item, error: errors.join('; ') })
99
- continue
100
- }
101
-
102
- succeeded.push({ ...bep.lbs.find(n => n.id === item.id)!, parentId: parentId ?? null })
103
- }
104
-
105
- return { succeeded, failed }
106
- }
107
-
108
- updateNodes(items: LBSNodeUpdateInput[]): {
109
- succeeded: Array<{
110
- id: string
111
- before: { name: string; type: string; description: string | undefined; parentId: string | null }
112
- after: { name: string; type: string; description: string | undefined; parentId: string | null }
113
- }>
114
- failed: Array<{ id: string; error: string }>
115
- } {
116
- const bep = this.getBep()
117
- const succeeded: Array<{
118
- id: string
119
- before: { name: string; type: string; description: string | undefined; parentId: string | null }
120
- after: { name: string; type: string; description: string | undefined; parentId: string | null }
121
- }> = []
122
- const failed: Array<{ id: string; error: string }> = []
123
-
124
- for (const item of items) {
125
- if (item.parentId && !bep.lbs.find(n => n.id === item.parentId)) {
126
- failed.push({ id: item.id, error: `No LBS node found with ID "${item.parentId}".` })
127
- continue
128
- }
129
- if (item.parentId === item.id) {
130
- failed.push({ id: item.id, error: 'A node cannot be its own parent.' })
131
- continue
132
- }
133
-
134
- const beforeNode = bep.lbs.find(n => n.id === item.id)
135
- const beforeParentId = buildParentMap(bep.lbs).get(item.id) ?? null
136
-
137
- const { parentId, ...fields } = item
138
- const updateResult = this.update([fields])
139
- if (updateResult.failed.length > 0) {
140
- failed.push({ id: item.id, error: updateResult.failed[0].error })
141
- continue
142
- }
143
-
144
- const before = { name: beforeNode!.name, type: beforeNode!.type, description: beforeNode!.description, parentId: beforeParentId }
145
- const node = bep.lbs.find(n => n.id === item.id)!
146
-
147
- if (parentId !== undefined) {
148
- for (const n of bep.lbs) {
149
- if (n.lbsNodeIds?.includes(item.id)) n.lbsNodeIds = n.lbsNodeIds.filter(c => c !== item.id)
150
- }
151
- if (parentId) {
152
- const newParent = bep.lbs.find(n => n.id === parentId)!
153
- newParent.lbsNodeIds ??= []
154
- newParent.lbsNodeIds.push(item.id)
155
- }
156
- }
157
-
158
- const errors = this.validateTree()
159
- if (errors.length > 0) {
160
- node.name = before.name
161
- node.type = before.type as 'zone' | 'location'
162
- if (before.description !== undefined) node.description = before.description
163
- else delete node.description
164
- if (parentId !== undefined) {
165
- for (const n of bep.lbs) {
166
- if (n.lbsNodeIds?.includes(item.id)) n.lbsNodeIds = n.lbsNodeIds.filter(c => c !== item.id)
167
- }
168
- if (before.parentId) {
169
- const prevParent = bep.lbs.find(n => n.id === before.parentId)
170
- if (prevParent) { prevParent.lbsNodeIds ??= []; prevParent.lbsNodeIds.push(item.id) }
171
- }
172
- }
173
- failed.push({ id: item.id, error: errors.join('; ') })
174
- continue
175
- }
176
-
177
- const afterParentId = buildParentMap(bep.lbs).get(node.id) ?? null
178
- succeeded.push({ id: item.id, before, after: { name: node.name, type: node.type, description: node.description, parentId: afterParentId } })
179
- }
180
-
181
- return { succeeded, failed }
182
- }
183
-
184
- /** Resolves zone and location codes for nomenclature given a node id. */
185
- resolveCodes(nodeId: string | undefined): { zoneCode: string; locationCode: string } {
186
- return resolveLBSCodes(nodeId, this.list())
187
- }
188
-
189
- /** Validates the full LBS tree and returns a list of structural errors. */
190
- validateTree(): string[] {
191
- return validateLBS(this.list())
192
- }
193
- }
@@ -1,22 +0,0 @@
1
- import type { BEP, LOD } from '../types/schema.js'
2
- import { Entity } from '../base/entity.js'
3
- import { LODSchema } from '../types/schema.js'
4
-
5
- export class LODs extends Entity<LOD> {
6
- constructor(getBep: () => BEP) {
7
- super(
8
- () => getBep().lods,
9
- getBep,
10
- {
11
- key: 'lods',
12
- schema: LODSchema,
13
- beforeRemove: (id, bep) => {
14
- for (const loin of bep.loin) {
15
- const ref = loin.milestones?.find(m => m.lodId === id)
16
- if (ref) throw new Error(`Referenced by: loin["${loin.id}"].milestones[lodId=${id}]`)
17
- }
18
- },
19
- },
20
- )
21
- }
22
- }
@@ -1,127 +0,0 @@
1
- import type { BEP, LOIN, LOINMilestone } from '../types/schema.js'
2
- import { Entity } from '../base/entity.js'
3
- import { LOINSchema } from '../types/schema.js'
4
- import type { LOINResolved } from '../types/resolved.js'
5
-
6
- export class LOINEntity extends Entity<LOIN, true> {
7
- constructor(getBep: () => BEP) {
8
- super(
9
- () => getBep().loin,
10
- getBep,
11
- {
12
- key: 'loin',
13
- schema: LOINSchema,
14
- autoId: true,
15
- validate: (item, bep) => {
16
- const errors: string[] = []
17
- for (const loinMilestone of item.milestones ?? []) {
18
- if (!bep.milestones.some(m => m.id === loinMilestone.milestoneId))
19
- errors.push(`milestones["${loinMilestone.milestoneId}"] not found`)
20
- if (!bep.lods.some(l => l.id === loinMilestone.lodId))
21
- errors.push(`lods[${loinMilestone.lodId}] not found`)
22
- if (!bep.lois.some(l => l.id === loinMilestone.loiId))
23
- errors.push(`lois[${loinMilestone.loiId}] not found`)
24
- }
25
- return errors
26
- },
27
- },
28
- )
29
- }
30
-
31
- addMilestones(items: { loinId: LOIN['id']; milestones: LOINMilestone[] }[]): {
32
- succeeded: { loinId: LOIN['id']; element: LOIN['element']; addedMilestones: LOINMilestone['milestoneId'][] }[]
33
- failed: { loinId: LOIN['id']; error: string }[]
34
- } {
35
- const succeeded: { loinId: LOIN['id']; element: LOIN['element']; addedMilestones: LOINMilestone['milestoneId'][] }[] = []
36
- const failed: { loinId: LOIN['id']; error: string }[] = []
37
-
38
- for (const item of items) {
39
- const loin = this.getBep().loin.find(l => l.id === item.loinId)
40
- if (!loin) { failed.push({ loinId: item.loinId, error: `Not found: ${item.loinId}` }); continue }
41
-
42
- const dup = item.milestones.find(m => (loin.milestones ?? []).some(lm => lm.milestoneId === m.milestoneId))
43
- if (dup) { failed.push({ loinId: item.loinId, error: `Milestone "${dup.milestoneId}" already exists. Use updateMilestones to modify it.` }); continue }
44
-
45
- const newMilestones = [...(loin.milestones ?? []), ...item.milestones]
46
- const result = this.update([{ id: item.loinId, milestones: newMilestones }])
47
- if (result.failed.length > 0) { failed.push({ loinId: item.loinId, error: result.failed[0].error }); continue }
48
-
49
- succeeded.push({ loinId: item.loinId, element: loin.element, addedMilestones: item.milestones.map(m => m.milestoneId) })
50
- }
51
-
52
- return { succeeded, failed }
53
- }
54
-
55
- updateMilestones(items: { loinId: LOIN['id']; milestones: (Partial<LOINMilestone> & { milestoneId: LOINMilestone['milestoneId'] })[] }[]): {
56
- succeeded: { loinId: LOIN['id']; element: LOIN['element']; milestones: { milestoneId: LOINMilestone['milestoneId']; before: Omit<LOINMilestone, 'milestoneId'>; after: Omit<LOINMilestone, 'milestoneId'> }[] }[]
57
- failed: { loinId: LOIN['id']; error: string }[]
58
- } {
59
- const succeeded: { loinId: LOIN['id']; element: LOIN['element']; milestones: { milestoneId: LOINMilestone['milestoneId']; before: Omit<LOINMilestone, 'milestoneId'>; after: Omit<LOINMilestone, 'milestoneId'> }[] }[] = []
60
- const failed: { loinId: LOIN['id']; error: string }[] = []
61
-
62
- for (const item of items) {
63
- const loin = this.getBep().loin.find(l => l.id === item.loinId)
64
- if (!loin) { failed.push({ loinId: item.loinId, error: `Not found: ${item.loinId}` }); continue }
65
-
66
- const missing = item.milestones.find(m => !(loin.milestones ?? []).some(lm => lm.milestoneId === m.milestoneId))
67
- if (missing) { failed.push({ loinId: item.loinId, error: `Milestone "${missing.milestoneId}" not found. Use addMilestones to create it.` }); continue }
68
-
69
- const befores = new Map(loin.milestones!.map(m => [m.milestoneId, { lodId: m.lodId, loiId: m.loiId, idsPath: m.idsPath }]))
70
- const newMilestones: LOINMilestone[] = (loin.milestones ?? []).map(lm => {
71
- const patch = item.milestones.find(m => m.milestoneId === lm.milestoneId)
72
- return patch ? { ...lm, ...patch } : lm
73
- })
74
-
75
- const result = this.update([{ id: item.loinId, milestones: newMilestones }])
76
- if (result.failed.length > 0) { failed.push({ loinId: item.loinId, error: result.failed[0].error }); continue }
77
-
78
- const updatedLoin = this.getBep().loin.find(l => l.id === item.loinId)!
79
- succeeded.push({
80
- loinId: item.loinId,
81
- element: loin.element,
82
- milestones: item.milestones.map(m => {
83
- const after = updatedLoin.milestones!.find(lm => lm.milestoneId === m.milestoneId)!
84
- const before = befores.get(m.milestoneId)!
85
- return { milestoneId: m.milestoneId, before, after: { lodId: after.lodId, loiId: after.loiId, idsPath: after.idsPath } }
86
- }),
87
- })
88
- }
89
-
90
- return { succeeded, failed }
91
- }
92
-
93
- removeMilestones(items: { loinId: LOIN['id']; milestoneIds: LOINMilestone['milestoneId'][] }[]): {
94
- succeeded: { loinId: LOIN['id']; element: LOIN['element']; removedMilestones: LOINMilestone['milestoneId'][] }[]
95
- failed: { loinId: LOIN['id']; error: string }[]
96
- } {
97
- const succeeded: { loinId: LOIN['id']; element: LOIN['element']; removedMilestones: LOINMilestone['milestoneId'][] }[] = []
98
- const failed: { loinId: LOIN['id']; error: string }[] = []
99
-
100
- for (const item of items) {
101
- const loin = this.getBep().loin.find(l => l.id === item.loinId)
102
- if (!loin) { failed.push({ loinId: item.loinId, error: `Not found: ${item.loinId}` }); continue }
103
-
104
- const missing = item.milestoneIds.filter(id => !(loin.milestones ?? []).some(m => m.milestoneId === id))
105
- if (missing.length > 0) { failed.push({ loinId: item.loinId, error: `Milestones not found in this LOIN: ${missing.join(', ')}` }); continue }
106
-
107
- loin.milestones = (loin.milestones ?? []).filter(m => !item.milestoneIds.includes(m.milestoneId))
108
- succeeded.push({ loinId: item.loinId, element: loin.element, removedMilestones: item.milestoneIds })
109
- }
110
-
111
- return { succeeded, failed }
112
- }
113
-
114
- listResolved(): LOINResolved[] {
115
- const bep = this.getBep()
116
- return bep.loin.map(l => ({
117
- ...l,
118
- discipline: bep.disciplines.find(d => d.id === l.disciplineId) ?? null,
119
- milestones: (l.milestones ?? []).map(lm => ({
120
- ...lm,
121
- milestone: bep.milestones.find(m => m.id === lm.milestoneId) ?? null,
122
- lod: bep.lods.find(ld => ld.id === lm.lodId) ?? null,
123
- loi: bep.lois.find(li => li.id === lm.loiId) ?? null,
124
- })),
125
- }))
126
- }
127
- }
@@ -1,22 +0,0 @@
1
- import type { BEP, LOI } from '../types/schema.js'
2
- import { Entity } from '../base/entity.js'
3
- import { LOISchema } from '../types/schema.js'
4
-
5
- export class LOIs extends Entity<LOI> {
6
- constructor(getBep: () => BEP) {
7
- super(
8
- () => getBep().lois,
9
- getBep,
10
- {
11
- key: 'lois',
12
- schema: LOISchema,
13
- beforeRemove: (id, bep) => {
14
- for (const loin of bep.loin) {
15
- const ref = loin.milestones?.find(m => m.loiId === id)
16
- if (ref) throw new Error(`Referenced by: loin["${loin.id}"].milestones[loiId=${id}]`)
17
- }
18
- },
19
- },
20
- )
21
- }
22
- }