@eventcatalog/core 5.0.0-beta.1 → 5.0.0-beta.10
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/LICENSE +227 -128
- package/dist/analytics/analytics.cjs +18 -3
- package/dist/analytics/analytics.d.cts +1 -1
- package/dist/analytics/analytics.d.ts +1 -1
- package/dist/analytics/analytics.js +3 -2
- package/dist/analytics/log-build.cjs +71 -35
- package/dist/analytics/log-build.js +5 -4
- package/dist/analytics/telemetry.cjs +42 -0
- package/dist/analytics/telemetry.d.cts +20 -0
- package/dist/analytics/telemetry.d.ts +20 -0
- package/dist/analytics/telemetry.js +8 -0
- package/dist/{chunk-K2ACDO7Y.js → chunk-2LBDRHE4.js} +1 -1
- package/dist/{chunk-TQLRG35T.js → chunk-AWOO63ME.js} +71 -45
- package/dist/chunk-CPW6G62V.js +17 -0
- package/dist/{chunk-UDS3SYLZ.js → chunk-CRKJBDN7.js} +56 -33
- package/dist/{chunk-Q6SD5SNQ.js → chunk-EM36OL6A.js} +1 -1
- package/dist/{chunk-S2QGT7ZF.js → chunk-IONYKKOY.js} +1 -1
- package/dist/{chunk-KYJZJLBD.js → chunk-LJMXOEEC.js} +9 -3
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/eventcatalog.cjs +204 -124
- package/dist/eventcatalog.config.d.cts +59 -1
- package/dist/eventcatalog.config.d.ts +59 -1
- package/dist/eventcatalog.js +30 -11
- package/dist/federation/federate.js +2 -2
- package/dist/generate.cjs +1 -1
- package/dist/generate.js +3 -3
- package/dist/runtime-dependencies/@hocuspocus__provider.mjs +3 -0
- package/dist/runtime-dependencies/@hocuspocus__server.mjs +3 -0
- package/dist/runtime-dependencies/@mcp-b__webmcp-local-relay.mjs +3 -0
- package/dist/runtime-dependencies/@mcp-b__webmcp-polyfill.mjs +3 -0
- package/dist/runtime-dependencies/manifest.json +8 -1
- package/dist/runtime-dependencies/react-hotkeys-hook.mjs +3 -0
- package/dist/runtime-dependencies/y-protocols.mjs +3 -0
- package/dist/runtime-dependencies/y-protocols__awareness.mjs +3 -0
- package/dist/runtime-dependencies/yjs.mjs +3 -0
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/dist/utils/license-status.cjs +71 -45
- package/dist/utils/license-status.d.cts +14 -5
- package/dist/utils/license-status.d.ts +14 -5
- package/dist/utils/license-status.js +1 -1
- package/eventcatalog/astro.config.mjs +5 -0
- package/eventcatalog/integrations/storage.mjs +20 -0
- package/eventcatalog/integrations/studio-server.mjs +32 -0
- package/eventcatalog/src/components/Grids/utils.tsx +0 -23
- package/eventcatalog/src/components/Header.astro +3 -3
- package/eventcatalog/src/components/MDX/CodeGroup/CodeGroup.tsx +6 -1
- package/eventcatalog/src/components/SchemaExplorer/SchemaDetailsPanel.tsx +12 -4
- package/eventcatalog/src/components/SideNav/NestedSideBar/index.tsx +16 -7
- package/eventcatalog/src/components/SideNav/NestedSideBar/utils.ts +10 -0
- package/eventcatalog/src/components/Tables/Discover/DiscoverTable.tsx +4 -62
- package/eventcatalog/src/components/Tables/Discover/columns.tsx +17 -150
- package/eventcatalog/src/components/Tables/Resources/ResourcesTable.tsx +425 -0
- package/eventcatalog/src/components/Tables/RowActionsMenu.tsx +157 -0
- package/eventcatalog/src/components/Tables/TablePagination.tsx +82 -0
- package/eventcatalog/src/features/ai/chat-api.ts +3 -3
- package/eventcatalog/src/features/auth/middleware/middleware-auth.ts +10 -0
- package/eventcatalog/src/features/integrations/eventcatalog-features.ts +34 -1
- package/eventcatalog/src/features/mcp/apps/canvas/shared.ts +47 -0
- package/eventcatalog/src/features/mcp/apps/canvas/styles.css +22 -0
- package/eventcatalog/src/features/mcp/apps/canvas/view.tsx +276 -0
- package/eventcatalog/src/features/mcp/apps/generated/canvas.html +210 -0
- package/eventcatalog/src/features/mcp/apps/generated/viewer.html +75 -74
- package/eventcatalog/src/features/mcp/apps/shared/app-view.tsx +2 -1
- package/eventcatalog/src/features/mcp/mcp-apps.ts +6 -3
- package/eventcatalog/src/features/mcp/mcp-server.ts +36 -5
- package/eventcatalog/src/features/server/start.ts +25 -0
- package/eventcatalog/src/features/storage/database.ts +88 -0
- package/eventcatalog/src/features/storage/migrations.ts +85 -0
- package/eventcatalog/src/features/storage/sql/database.sql +5 -0
- package/eventcatalog/src/features/storage/sql/migrations/0001-studio-canvases.sql +12 -0
- package/eventcatalog/src/features/storage/sql/migrations.sql +15 -0
- package/eventcatalog/src/features/storage/sql.ts +28 -0
- package/eventcatalog/src/features/studio/agent-choreography.ts +381 -0
- package/eventcatalog/src/features/studio/api/canvases-api.ts +170 -0
- package/eventcatalog/src/features/studio/api/canvases.ts +25 -0
- package/eventcatalog/src/features/studio/api/client.ts +35 -0
- package/eventcatalog/src/features/studio/canvas-actions.ts +414 -0
- package/eventcatalog/src/features/studio/canvas-doc.ts +414 -0
- package/eventcatalog/src/features/studio/canvas-preview.ts +53 -0
- package/eventcatalog/src/features/studio/catalog-resources.ts +155 -0
- package/eventcatalog/src/features/studio/catalog.ts +171 -0
- package/eventcatalog/src/features/studio/clipboard.ts +97 -0
- package/eventcatalog/src/features/studio/components/CanvasControls.tsx +154 -0
- package/eventcatalog/src/features/studio/components/CanvasHeader.tsx +370 -0
- package/eventcatalog/src/features/studio/components/CanvasStatusMenu.tsx +117 -0
- package/eventcatalog/src/features/studio/components/Comments.tsx +770 -0
- package/eventcatalog/src/features/studio/components/ConnectAgentDialog.tsx +281 -0
- package/eventcatalog/src/features/studio/components/ConnectionsChooser.tsx +144 -0
- package/eventcatalog/src/features/studio/components/ContainerChooser.tsx +145 -0
- package/eventcatalog/src/features/studio/components/DeleteCanvasDialog.tsx +81 -0
- package/eventcatalog/src/features/studio/components/DragGhost.tsx +115 -0
- package/eventcatalog/src/features/studio/components/JoinForm.tsx +34 -0
- package/eventcatalog/src/features/studio/components/LeftPanel.tsx +314 -0
- package/eventcatalog/src/features/studio/components/Picture.tsx +21 -0
- package/eventcatalog/src/features/studio/components/Presence.tsx +286 -0
- package/eventcatalog/src/features/studio/components/PropertiesPanel.tsx +212 -0
- package/eventcatalog/src/features/studio/components/ShareDialog.tsx +260 -0
- package/eventcatalog/src/features/studio/components/StudioDesigner.tsx +1216 -0
- package/eventcatalog/src/features/studio/components/StudioNotice.astro +24 -0
- package/eventcatalog/src/features/studio/components/canvas-nodes.tsx +255 -0
- package/eventcatalog/src/features/studio/components/icons.ts +53 -0
- package/eventcatalog/src/features/studio/components/status.ts +53 -0
- package/eventcatalog/src/features/studio/edges.ts +56 -0
- package/eventcatalog/src/features/studio/flow-state.ts +92 -0
- package/eventcatalog/src/features/studio/grouping.ts +226 -0
- package/eventcatalog/src/features/studio/hooks/presence-sender.ts +46 -0
- package/eventcatalog/src/features/studio/hooks/presence-store.ts +119 -0
- package/eventcatalog/src/features/studio/hooks/use-canvas-webmcp.ts +347 -0
- package/eventcatalog/src/features/studio/hooks/use-comments.ts +54 -0
- package/eventcatalog/src/features/studio/hooks/use-follow-camera.ts +104 -0
- package/eventcatalog/src/features/studio/hooks/use-studio-flow.ts +589 -0
- package/eventcatalog/src/features/studio/identity.ts +37 -0
- package/eventcatalog/src/features/studio/layout.ts +42 -0
- package/eventcatalog/src/features/studio/levels.ts +282 -0
- package/eventcatalog/src/features/studio/node-types.ts +205 -0
- package/eventcatalog/src/features/studio/pages/[id].astro +68 -0
- package/eventcatalog/src/features/studio/pages/index.astro +784 -0
- package/eventcatalog/src/features/studio/pages/webmcp-relay.ts +27 -0
- package/eventcatalog/src/features/studio/placement.ts +197 -0
- package/eventcatalog/src/features/studio/server/canvas-mcp.ts +510 -0
- package/eventcatalog/src/features/studio/server/runtime.ts +491 -0
- package/eventcatalog/src/features/studio/server/sign-in.ts +18 -0
- package/eventcatalog/src/features/studio/server/sql/storage.sql +18 -0
- package/eventcatalog/src/features/studio/server/storage.ts +90 -0
- package/eventcatalog/src/features/studio/server/tool-icons.ts +30 -0
- package/eventcatalog/src/features/studio/studio.css +273 -0
- package/eventcatalog/src/features/studio/tool-sync.ts +75 -0
- package/eventcatalog/src/layouts/VerticalSideBarLayout.astro +10 -5
- package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.spec.ts +52 -63
- package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/_index.data.ts +34 -46
- package/eventcatalog/src/pages/architecture/[type]/[id]/[version]/index.astro +11 -29
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_ResourcesPage.astro +79 -0
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_index.data.spec.ts +32 -14
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_index.data.ts +63 -58
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/index.astro +2 -62
- package/eventcatalog/src/pages/docs/services/[id]/[version]/resources/index.astro +13 -0
- package/eventcatalog/src/stores/sidebar-store/builders/domain.ts +24 -42
- package/eventcatalog/src/stores/sidebar-store/builders/flow.ts +2 -0
- package/eventcatalog/src/stores/sidebar-store/builders/message.ts +2 -0
- package/eventcatalog/src/stores/sidebar-store/builders/service.ts +23 -23
- package/eventcatalog/src/stores/sidebar-store/builders/shared.ts +13 -0
- package/eventcatalog/src/stores/sidebar-store/builders/system.ts +18 -27
- package/eventcatalog/src/stores/sidebar-store/state.ts +5 -0
- package/eventcatalog/src/styles/tailwind.css +2 -2
- package/eventcatalog/src/utils/collections/channels.ts +56 -1
- package/eventcatalog/src/utils/collections/flows.ts +50 -1
- package/eventcatalog/src/utils/collections/message-connections.ts +60 -0
- package/eventcatalog/src/utils/collections/message-triggers.ts +2 -1
- package/eventcatalog/src/utils/collections/resource-owners.ts +193 -0
- package/eventcatalog/src/utils/collections/resources.ts +310 -0
- package/eventcatalog/src/utils/collections/util.ts +31 -0
- package/eventcatalog/src/utils/feature.ts +8 -0
- package/eventcatalog/src/utils/git.ts +11 -5
- package/eventcatalog/src/utils/node-graphs/export-mermaid.ts +0 -2
- package/eventcatalog/src/utils/trial.ts +1 -1
- package/package.json +17 -12
- package/dist/runtime-dependencies/shelljs.mjs +0 -3
- package/eventcatalog/public/studio-bg.png +0 -0
- package/eventcatalog/src/components/Grids/DomainGrid.tsx +0 -838
- package/eventcatalog/src/components/Grids/MessageGrid.tsx +0 -344
- package/eventcatalog/src/components/Grids/SystemGrid.tsx +0 -185
- package/eventcatalog/src/components/Grids/components.tsx +0 -170
- package/eventcatalog/src/components/Grids/message-link.ts +0 -38
- package/eventcatalog/src/components/Studio/StudioPageModal.tsx +0 -150
- package/eventcatalog/src/components/Tables/SystemResources/SystemResourcesTable.tsx +0 -294
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_resources.spec.ts +0 -65
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/resources/_resources.ts +0 -46
- package/eventcatalog/src/pages/studio.astro +0 -316
- package/dist/{chunk-AYNZXUB5.js → chunk-KHACCTW2.js} +3 -3
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The visualiser hides node handles (it is read only), here they are the connection points,
|
|
3
|
+
* so show them on hover, on selection and while connecting.
|
|
4
|
+
*/
|
|
5
|
+
.studio-canvas .react-flow__handle {
|
|
6
|
+
opacity: 0 !important;
|
|
7
|
+
width: 11px;
|
|
8
|
+
height: 11px;
|
|
9
|
+
background: rgb(var(--ec-card-bg));
|
|
10
|
+
border: 2px solid rgb(var(--ec-accent));
|
|
11
|
+
transition: opacity 0.15s ease;
|
|
12
|
+
z-index: 30;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.studio-canvas .react-flow__node:hover .react-flow__handle,
|
|
16
|
+
.studio-canvas .react-flow__node.selected .react-flow__handle,
|
|
17
|
+
.studio-canvas .react-flow__pane.connecting ~ * .react-flow__handle,
|
|
18
|
+
.studio-canvas .react-flow__handle.connectingto {
|
|
19
|
+
opacity: 1 !important;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.studio-canvas .react-flow__handle:hover,
|
|
23
|
+
.studio-canvas .react-flow__handle.connectingto {
|
|
24
|
+
background: rgb(var(--ec-accent));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.studio-canvas .react-flow__handle.connectingto.valid {
|
|
28
|
+
background: rgb(var(--ec-badge-color-green-text));
|
|
29
|
+
border-color: rgb(var(--ec-badge-color-green-text));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.studio-canvas .react-flow__edge.selected .react-flow__edge-path,
|
|
33
|
+
.studio-canvas .react-flow__edge:hover .react-flow__edge-path {
|
|
34
|
+
stroke: rgb(var(--ec-accent)) !important;
|
|
35
|
+
stroke-width: 2px !important;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/*
|
|
39
|
+
* A connection being dragged (a new one, or an end moved to another node): once it's on a node it can connect
|
|
40
|
+
* to, it turns the hover colour, so you know letting go will connect it
|
|
41
|
+
*/
|
|
42
|
+
.studio-canvas .react-flow__connection-path {
|
|
43
|
+
stroke-width: 2px;
|
|
44
|
+
transition: stroke 100ms ease;
|
|
45
|
+
}
|
|
46
|
+
/* Like a hovered edge in the visualiser: glowing, with the dashes flowing (ec-edge-dash, from its styles) */
|
|
47
|
+
.studio-canvas .react-flow__connection.valid .react-flow__connection-path {
|
|
48
|
+
stroke: #2563eb;
|
|
49
|
+
stroke-width: 4px;
|
|
50
|
+
filter: drop-shadow(0 0 8px #2563eb);
|
|
51
|
+
stroke-dasharray: 5 5;
|
|
52
|
+
animation: ec-edge-dash 0.5s linear infinite;
|
|
53
|
+
}
|
|
54
|
+
[data-theme='dark'] .studio-canvas .react-flow__connection.valid .react-flow__connection-path {
|
|
55
|
+
stroke: #00d9ff;
|
|
56
|
+
filter: drop-shadow(0 0 8px #00d9ff);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* Comment mode (C): click anywhere to drop a comment */
|
|
60
|
+
.studio-canvas.comment-mode .react-flow__pane,
|
|
61
|
+
.studio-canvas.comment-mode .react-flow__node {
|
|
62
|
+
cursor: crosshair !important;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* After a layout or a level change, nodes glide to their new place */
|
|
66
|
+
.studio-canvas.is-layout-transition .react-flow__node {
|
|
67
|
+
transition: transform 450ms ease;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* Edges are drawn where the nodes end up, so they wait for the glide to finish */
|
|
71
|
+
.studio-canvas .react-flow__edges {
|
|
72
|
+
transition: opacity 200ms ease;
|
|
73
|
+
}
|
|
74
|
+
.studio-canvas.is-layout-transition .react-flow__edges {
|
|
75
|
+
opacity: 0;
|
|
76
|
+
transition: none;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Containers grow smoothly when they're fitted to what's in them, or someone else resizes them */
|
|
80
|
+
.studio-canvas.is-container-resize :is(.react-flow__node-domain-group, .react-flow__node-system-group) {
|
|
81
|
+
transition:
|
|
82
|
+
width 300ms ease,
|
|
83
|
+
height 300ms ease,
|
|
84
|
+
transform 300ms ease;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* Nodes pop in when they're added (by anyone, people or agents) */
|
|
88
|
+
@keyframes studio-node-in {
|
|
89
|
+
from {
|
|
90
|
+
opacity: 0;
|
|
91
|
+
scale: 0.85;
|
|
92
|
+
}
|
|
93
|
+
to {
|
|
94
|
+
opacity: 1;
|
|
95
|
+
scale: 1;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
.studio-canvas .react-flow__node {
|
|
99
|
+
animation: studio-node-in 220ms ease-out;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* No React Flow attribution on the canvas */
|
|
103
|
+
.studio-canvas .react-flow__attribution {
|
|
104
|
+
display: none;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/*
|
|
108
|
+
* Containers let the pointer through their body to the canvas (so dragging inside one draws a selection box),
|
|
109
|
+
* and are moved, selected and resized by their header and resize handles. React Flow sets pointer-events inline.
|
|
110
|
+
*/
|
|
111
|
+
.studio-canvas .react-flow__node-domain-group,
|
|
112
|
+
.studio-canvas .react-flow__node-system-group {
|
|
113
|
+
pointer-events: none !important;
|
|
114
|
+
}
|
|
115
|
+
.studio-canvas .react-flow__node-domain-group :is(.ec-group-grip, .react-flow__resize-control),
|
|
116
|
+
.studio-canvas .react-flow__node-system-group :is(.ec-group-grip, .react-flow__resize-control) {
|
|
117
|
+
pointer-events: auto;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/*
|
|
121
|
+
* Selected nodes get an accent outline and glow around them, and hovered ones a faint outline (like Figma and
|
|
122
|
+
* Miro). Drawn by the node's wrapper, so it fits every kind of node. Containers draw their own.
|
|
123
|
+
*/
|
|
124
|
+
.studio-canvas .react-flow__node:not(.react-flow__node-domain-group, .react-flow__node-system-group)::after {
|
|
125
|
+
content: '';
|
|
126
|
+
position: absolute;
|
|
127
|
+
inset: -6px;
|
|
128
|
+
border-radius: 12px;
|
|
129
|
+
border: 2px solid transparent;
|
|
130
|
+
pointer-events: none;
|
|
131
|
+
transition:
|
|
132
|
+
border-color 120ms ease,
|
|
133
|
+
box-shadow 120ms ease;
|
|
134
|
+
}
|
|
135
|
+
.studio-canvas .react-flow__node:not(.react-flow__node-domain-group, .react-flow__node-system-group):hover::after {
|
|
136
|
+
border-color: rgb(var(--ec-accent) / 0.35);
|
|
137
|
+
}
|
|
138
|
+
.studio-canvas .react-flow__node.selected:not(.react-flow__node-domain-group, .react-flow__node-system-group)::after {
|
|
139
|
+
border-color: rgb(var(--ec-accent));
|
|
140
|
+
box-shadow: 0 0 0 4px rgb(var(--ec-accent) / 0.15);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* A selected or hovered connection shows the ends you can drag to another node */
|
|
144
|
+
.studio-canvas .react-flow__edge .react-flow__edgeupdater {
|
|
145
|
+
cursor: grab;
|
|
146
|
+
}
|
|
147
|
+
.studio-canvas .react-flow__edge:is(.selected, :hover) .react-flow__edgeupdater {
|
|
148
|
+
fill: rgb(var(--ec-accent) / 0.2);
|
|
149
|
+
stroke: rgb(var(--ec-accent));
|
|
150
|
+
stroke-width: 1.5;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* A selected container */
|
|
154
|
+
.studio-canvas .ec-group-selected {
|
|
155
|
+
position: absolute;
|
|
156
|
+
inset: 0;
|
|
157
|
+
border-radius: 14px;
|
|
158
|
+
box-shadow: 0 0 0 2px rgb(var(--ec-accent) / 0.7);
|
|
159
|
+
pointer-events: none;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* A container something's dragged over: it goes in it when it's dropped (in the theme's button colours, which stay
|
|
163
|
+
strong in light and dark themes) */
|
|
164
|
+
.studio-canvas .ec-drop-target {
|
|
165
|
+
position: absolute;
|
|
166
|
+
inset: -5px;
|
|
167
|
+
border-radius: 17px;
|
|
168
|
+
border: 3px solid rgb(var(--ec-button-bg));
|
|
169
|
+
background: rgb(var(--ec-button-bg) / 0.1);
|
|
170
|
+
box-shadow: 0 0 0 6px rgb(var(--ec-button-bg) / 0.2);
|
|
171
|
+
pointer-events: none;
|
|
172
|
+
animation: ec-drop-target-in 150ms ease-out;
|
|
173
|
+
}
|
|
174
|
+
.studio-canvas .ec-drop-target-label {
|
|
175
|
+
position: absolute;
|
|
176
|
+
top: -16px;
|
|
177
|
+
left: 50%;
|
|
178
|
+
translate: -50% 0;
|
|
179
|
+
white-space: nowrap;
|
|
180
|
+
border-radius: 9999px;
|
|
181
|
+
padding: 4px 12px;
|
|
182
|
+
font-size: 12px;
|
|
183
|
+
font-weight: 600;
|
|
184
|
+
color: rgb(var(--ec-button-text));
|
|
185
|
+
background: rgb(var(--ec-button-bg));
|
|
186
|
+
box-shadow: 0 2px 8px rgb(0 0 0 / 0.25);
|
|
187
|
+
}
|
|
188
|
+
@keyframes ec-drop-target-in {
|
|
189
|
+
from {
|
|
190
|
+
opacity: 0;
|
|
191
|
+
scale: 0.98;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/*
|
|
196
|
+
* What's dragged from the left panel (DragGhost): just the node, on nothing. The visualiser's class gives it the page
|
|
197
|
+
* background, and its handles are hidden on the canvas only.
|
|
198
|
+
*/
|
|
199
|
+
.studio-drag-preview,
|
|
200
|
+
.studio-drag-preview .react-flow,
|
|
201
|
+
.studio-drag-preview .react-flow__renderer,
|
|
202
|
+
.studio-drag-preview .react-flow__pane {
|
|
203
|
+
background: transparent !important;
|
|
204
|
+
}
|
|
205
|
+
.studio-drag-preview .react-flow__handle {
|
|
206
|
+
display: none !important;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/* Text written on the canvas: no card around it, in the page's text colour */
|
|
210
|
+
.studio-canvas .studio-text,
|
|
211
|
+
.studio-drag-preview .studio-text {
|
|
212
|
+
min-width: 80px;
|
|
213
|
+
padding: 2px 4px;
|
|
214
|
+
}
|
|
215
|
+
.studio-text .studio-text-body {
|
|
216
|
+
margin: 0;
|
|
217
|
+
font-size: 16px;
|
|
218
|
+
line-height: 1.4;
|
|
219
|
+
color: rgb(var(--ec-page-text));
|
|
220
|
+
white-space: pre-wrap;
|
|
221
|
+
overflow-wrap: anywhere;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.studio-canvas .studio-width-handle {
|
|
225
|
+
border-color: rgb(var(--ec-accent)) !important;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* A sticky note: about square like a post-it, taller if more is written on it (dark text: the paper is light in
|
|
229
|
+
every theme) */
|
|
230
|
+
.studio-note {
|
|
231
|
+
position: relative;
|
|
232
|
+
min-height: 176px;
|
|
233
|
+
height: 100%;
|
|
234
|
+
padding: 12px 14px;
|
|
235
|
+
border: 1px solid;
|
|
236
|
+
border-radius: 6px;
|
|
237
|
+
box-shadow:
|
|
238
|
+
0 1px 2px rgb(0 0 0 / 0.06),
|
|
239
|
+
0 6px 16px -6px rgb(0 0 0 / 0.18);
|
|
240
|
+
color: #1f2937;
|
|
241
|
+
}
|
|
242
|
+
.studio-note .studio-note-body {
|
|
243
|
+
margin: 0;
|
|
244
|
+
font-size: 13px;
|
|
245
|
+
line-height: 1.45;
|
|
246
|
+
color: inherit;
|
|
247
|
+
white-space: pre-wrap;
|
|
248
|
+
overflow-wrap: anywhere;
|
|
249
|
+
}
|
|
250
|
+
.studio-note .studio-note-body::placeholder {
|
|
251
|
+
color: inherit;
|
|
252
|
+
opacity: 0.5;
|
|
253
|
+
}
|
|
254
|
+
/* Its colours, above it while it's selected */
|
|
255
|
+
.studio-note .studio-note-colors {
|
|
256
|
+
position: absolute;
|
|
257
|
+
bottom: calc(100% + 8px);
|
|
258
|
+
left: 0;
|
|
259
|
+
display: flex;
|
|
260
|
+
gap: 4px;
|
|
261
|
+
padding: 4px;
|
|
262
|
+
border-radius: 9999px;
|
|
263
|
+
border: 1px solid rgb(var(--ec-page-border));
|
|
264
|
+
background: rgb(var(--ec-card-bg));
|
|
265
|
+
box-shadow: 0 2px 8px rgb(0 0 0 / 0.15);
|
|
266
|
+
}
|
|
267
|
+
.studio-note .studio-note-color {
|
|
268
|
+
width: 16px;
|
|
269
|
+
height: 16px;
|
|
270
|
+
border-radius: 9999px;
|
|
271
|
+
border: 2px solid;
|
|
272
|
+
cursor: pointer;
|
|
273
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as Y from 'yjs';
|
|
2
|
+
import { applyAwarenessUpdate, encodeAwarenessUpdate, type Awareness } from 'y-protocols/awareness';
|
|
3
|
+
import type { Status } from './hooks/use-studio-flow';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Syncing a canvas through MCP tool calls, for when a view can't open the collaboration WebSocket
|
|
7
|
+
* (e.g. an MCP App inside a chat whose sandbox blocks it). Each call sends the server what it doesn't have
|
|
8
|
+
* yet (by its state vector) and our presence, and gets back what we don't have and everyone's presence.
|
|
9
|
+
* Slower than the WebSocket (it polls), but works wherever the chat can call the MCP server's tools.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export type SyncRequest = {
|
|
13
|
+
canvasId: string;
|
|
14
|
+
clientId: number;
|
|
15
|
+
/** What we have, so the server sends only what we're missing */
|
|
16
|
+
stateVector: string;
|
|
17
|
+
/** What we have that the server doesn't (as of its last state vector) */
|
|
18
|
+
update: string;
|
|
19
|
+
/** Our presence (name, pointer, selection...) */
|
|
20
|
+
awareness: string;
|
|
21
|
+
};
|
|
22
|
+
export type SyncResponse = { update: string; stateVector: string; awareness?: string };
|
|
23
|
+
export type ToolSync = (request: SyncRequest) => Promise<SyncResponse>;
|
|
24
|
+
|
|
25
|
+
// Binary Yjs updates travel as base64 in tool arguments and results (atob/btoa exist in browsers and Node)
|
|
26
|
+
export const toBase64 = (bytes: Uint8Array) => {
|
|
27
|
+
let text = '';
|
|
28
|
+
for (let i = 0; i < bytes.length; i += 0x8000) text += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
|
|
29
|
+
return btoa(text);
|
|
30
|
+
};
|
|
31
|
+
export const fromBase64 = (text: string) => Uint8Array.from(atob(text), (char) => char.charCodeAt(0));
|
|
32
|
+
|
|
33
|
+
/** Applied with this origin, so undo (which tracks local changes) leaves other people's changes alone */
|
|
34
|
+
const REMOTE = Symbol('tool-sync');
|
|
35
|
+
|
|
36
|
+
export function startToolSync(
|
|
37
|
+
doc: Y.Doc,
|
|
38
|
+
awareness: Awareness,
|
|
39
|
+
{
|
|
40
|
+
canvasId,
|
|
41
|
+
sync,
|
|
42
|
+
onStatus,
|
|
43
|
+
intervalMs = 1000,
|
|
44
|
+
}: { canvasId: string; sync: ToolSync; onStatus: (status: Status) => void; intervalMs?: number }
|
|
45
|
+
) {
|
|
46
|
+
let serverVector: Uint8Array | undefined;
|
|
47
|
+
let stopped = false;
|
|
48
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
49
|
+
|
|
50
|
+
const tick = async () => {
|
|
51
|
+
try {
|
|
52
|
+
const response = await sync({
|
|
53
|
+
canvasId,
|
|
54
|
+
clientId: doc.clientID,
|
|
55
|
+
stateVector: toBase64(Y.encodeStateVector(doc)),
|
|
56
|
+
update: toBase64(Y.encodeStateAsUpdate(doc, serverVector)),
|
|
57
|
+
awareness: toBase64(encodeAwarenessUpdate(awareness, [doc.clientID])),
|
|
58
|
+
});
|
|
59
|
+
if (stopped) return;
|
|
60
|
+
Y.applyUpdate(doc, fromBase64(response.update), REMOTE);
|
|
61
|
+
serverVector = fromBase64(response.stateVector);
|
|
62
|
+
if (response.awareness) applyAwarenessUpdate(awareness, fromBase64(response.awareness), REMOTE);
|
|
63
|
+
onStatus('connected');
|
|
64
|
+
} catch {
|
|
65
|
+
if (!stopped) onStatus('disconnected');
|
|
66
|
+
}
|
|
67
|
+
if (!stopped) timer = setTimeout(tick, intervalMs);
|
|
68
|
+
};
|
|
69
|
+
void tick();
|
|
70
|
+
|
|
71
|
+
return () => {
|
|
72
|
+
stopped = true;
|
|
73
|
+
clearTimeout(timer);
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -52,7 +52,7 @@ import { buildUrl } from '@utils/url-builder';
|
|
|
52
52
|
import { getQueries } from '@utils/collections/queries';
|
|
53
53
|
import { getFirstNavigationPage } from '@features/custom-documentation/utils/custom-docs';
|
|
54
54
|
|
|
55
|
-
import { isSSR } from '@utils/feature';
|
|
55
|
+
import { isCanvasEnabled, isSSR } from '@utils/feature';
|
|
56
56
|
|
|
57
57
|
const customDocs = await getCollection('customPages');
|
|
58
58
|
const firstCustomDocsPage = customDocs.length > 0 ? await getFirstNavigationPage() : null;
|
|
@@ -241,7 +241,7 @@ const builtinNavigationItems: Record<string, Omit<VerticalNavigationItem, 'curre
|
|
|
241
241
|
'/studio': {
|
|
242
242
|
id: '/studio',
|
|
243
243
|
matchId: '/studio',
|
|
244
|
-
label: '
|
|
244
|
+
label: 'Studio',
|
|
245
245
|
icon: 'SquareDashedMousePointer',
|
|
246
246
|
href: buildUrl('/studio'),
|
|
247
247
|
},
|
|
@@ -292,7 +292,12 @@ const builtinNavigationItems: Record<string, Omit<VerticalNavigationItem, 'curre
|
|
|
292
292
|
const defaultNavigationGroups: NavigationGroupConfig[] = [
|
|
293
293
|
{
|
|
294
294
|
id: 'main',
|
|
295
|
-
items: [
|
|
295
|
+
items: [
|
|
296
|
+
{ id: '/' },
|
|
297
|
+
{ id: '/docs/custom', visible: customDocs.length > 0 },
|
|
298
|
+
// Collaborative canvases, where they're on
|
|
299
|
+
{ id: '/studio', visible: isCanvasEnabled() },
|
|
300
|
+
],
|
|
296
301
|
},
|
|
297
302
|
{
|
|
298
303
|
id: 'browse',
|
|
@@ -323,7 +328,6 @@ const isNavigationItemCurrent = (item: Pick<VerticalNavigationItem, 'href' | 'ma
|
|
|
323
328
|
return (
|
|
324
329
|
pathname === itemPath ||
|
|
325
330
|
(pathname.includes('/docs') && !pathname.includes('/docs/custom')) ||
|
|
326
|
-
pathname.includes('/architecture/') ||
|
|
327
331
|
pathname.includes('/visualiser') ||
|
|
328
332
|
(pathname.includes('/schemas') && !pathname.includes('/schemas/explorer') && !pathname.includes('/schemas/fields'))
|
|
329
333
|
);
|
|
@@ -372,6 +376,8 @@ const resolveNavigationItem = (item: NavigationItemConfig): VerticalNavigationIt
|
|
|
372
376
|
if (item.visible === false) return null;
|
|
373
377
|
|
|
374
378
|
const builtinId = navigationBuiltinAliases[item.id] || item.id;
|
|
379
|
+
// Studio is only there where canvases are on
|
|
380
|
+
if (builtinId === '/studio' && !item.href && !isCanvasEnabled()) return null;
|
|
375
381
|
const builtin = builtinNavigationItems[builtinId];
|
|
376
382
|
const href = item.href ? normalizeNavigationHref(item.href) : builtin?.href;
|
|
377
383
|
|
|
@@ -889,7 +895,6 @@ const verticalNavAutoCollapsePaths = [buildUrl('/discover', true), buildUrl('/do
|
|
|
889
895
|
return (
|
|
890
896
|
pathname === targetPath ||
|
|
891
897
|
(pathname.includes('/docs') && !pathname.includes('/docs/custom')) ||
|
|
892
|
-
pathname.includes('/architecture/') ||
|
|
893
898
|
pathname.includes('/visualiser') ||
|
|
894
899
|
(pathname.includes('/schemas') && !pathname.includes('/schemas/explorer') && !pathname.includes('/schemas/fields'))
|
|
895
900
|
);
|
|
@@ -1,88 +1,77 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import { Page } from './_index.data';
|
|
3
3
|
|
|
4
|
-
const
|
|
4
|
+
const mocks = vi.hoisted(() => ({
|
|
5
|
+
isSSR: vi.fn(() => false),
|
|
5
6
|
getDomains: vi.fn(),
|
|
6
7
|
getServices: vi.fn(),
|
|
7
8
|
getSystems: vi.fn(),
|
|
9
|
+
getOwnersWithResources: vi.fn(),
|
|
8
10
|
}));
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
const loaders: Record<string, () => Promise<unknown>> = {
|
|
13
|
+
domains: () => mocks.getDomains(),
|
|
14
|
+
services: () => mocks.getServices(),
|
|
15
|
+
systems: () => mocks.getSystems(),
|
|
16
|
+
};
|
|
13
17
|
|
|
14
|
-
vi.mock('@utils/
|
|
15
|
-
vi.mock('@utils/
|
|
16
|
-
vi.mock('@utils/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
systems: (...args: unknown[]) => collectionMocks.getSystems(...args),
|
|
21
|
-
},
|
|
18
|
+
vi.mock('@utils/feature', () => ({ isSSR: mocks.isSSR }));
|
|
19
|
+
vi.mock('@utils/url-builder', () => ({ buildUrl: (path: string) => path }));
|
|
20
|
+
vi.mock('@utils/collections/resource-owners', () => ({
|
|
21
|
+
createResourcesCatalog: () => ({}),
|
|
22
|
+
loadResourceOwners: (type: string) => loaders[type](),
|
|
23
|
+
getOwnersWithResources: mocks.getOwnersWithResources,
|
|
22
24
|
}));
|
|
23
25
|
|
|
24
|
-
const
|
|
25
|
-
collection: 'domains',
|
|
26
|
-
data: { id: 'ordering', name: 'Ordering', version: '1.0.0' },
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const service = {
|
|
30
|
-
collection: 'services',
|
|
31
|
-
data: { id: 'order-service', name: 'Order Service', version: '1.0.0' },
|
|
32
|
-
};
|
|
26
|
+
const entry = (collection: string, id: string, version = '1.0.0') => ({ collection, data: { id, name: id, version } });
|
|
33
27
|
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
28
|
+
const ordering = entry('domains', 'ordering');
|
|
29
|
+
const orderService = entry('services', 'order-service');
|
|
30
|
+
const orderServiceV0 = entry('services', 'order-service', '0.1.0');
|
|
31
|
+
const cartSystem = entry('systems', 'cart-system');
|
|
38
32
|
|
|
39
|
-
describe('architecture page
|
|
33
|
+
describe('architecture page redirects', () => {
|
|
40
34
|
beforeEach(() => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
mocks.isSSR.mockReturnValue(false);
|
|
36
|
+
mocks.getDomains.mockReset().mockResolvedValue([ordering]);
|
|
37
|
+
mocks.getServices.mockReset().mockResolvedValue([orderService, orderServiceV0]);
|
|
38
|
+
mocks.getSystems.mockReset().mockResolvedValue([cartSystem]);
|
|
39
|
+
// Only the latest order service and the cart system have a Resources page.
|
|
40
|
+
mocks.getOwnersWithResources.mockReset().mockImplementation(async (type: string) => {
|
|
41
|
+
if (type === 'services') return [orderService];
|
|
42
|
+
if (type === 'systems') return [cartSystem];
|
|
43
|
+
return [];
|
|
44
|
+
});
|
|
44
45
|
});
|
|
45
46
|
|
|
46
|
-
it('
|
|
47
|
+
it('redirects every architecture page to its Resources page, or the docs page when it has no Resources page', async () => {
|
|
47
48
|
const paths = await Page.getStaticPaths();
|
|
48
49
|
|
|
49
|
-
expect(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
params: { type: 'systems', id: 'order-management-system', version: '1.0.0' },
|
|
58
|
-
}),
|
|
59
|
-
expect.objectContaining({
|
|
60
|
-
params: { type: 'services', id: 'order-service', version: '1.0.0' },
|
|
61
|
-
}),
|
|
62
|
-
])
|
|
63
|
-
);
|
|
50
|
+
expect(
|
|
51
|
+
paths.map(({ params, props }) => [`/architecture/${params.type}/${params.id}/${params.version}`, props.redirectPath])
|
|
52
|
+
).toEqual([
|
|
53
|
+
['/architecture/services/order-service/1.0.0', '/docs/services/order-service/1.0.0/resources'],
|
|
54
|
+
['/architecture/services/order-service/0.1.0', '/docs/services/order-service/0.1.0'],
|
|
55
|
+
['/architecture/domains/ordering/1.0.0', '/docs/domains/ordering/1.0.0'],
|
|
56
|
+
['/architecture/systems/cart-system/1.0.0', '/docs/systems/cart-system/1.0.0/resources'],
|
|
57
|
+
]);
|
|
64
58
|
});
|
|
65
59
|
|
|
66
|
-
it('
|
|
67
|
-
|
|
68
|
-
props: {},
|
|
69
|
-
params: { type: 'domains', id: 'ordering', version: '1.0.0' },
|
|
70
|
-
} as any);
|
|
60
|
+
it('works out the redirect when the page is requested on a server', async () => {
|
|
61
|
+
mocks.isSSR.mockReturnValue(true);
|
|
71
62
|
|
|
72
|
-
expect(
|
|
63
|
+
await expect(
|
|
64
|
+
Page.getData({ params: { type: 'systems', id: 'cart-system', version: '1.0.0' }, props: {} } as any)
|
|
65
|
+
).resolves.toMatchObject({ redirectPath: '/docs/systems/cart-system/1.0.0/resources' });
|
|
73
66
|
});
|
|
74
67
|
|
|
75
|
-
it('
|
|
76
|
-
|
|
77
|
-
props: {},
|
|
78
|
-
params: { type: 'systems', id: 'order-management-system', version: '1.0.0' },
|
|
79
|
-
} as any);
|
|
80
|
-
|
|
81
|
-
expect(collectionMocks.getSystems).toHaveBeenCalledWith({ enrichServices: true });
|
|
82
|
-
});
|
|
68
|
+
it('returns not found for resources without an architecture page', async () => {
|
|
69
|
+
mocks.isSSR.mockReturnValue(true);
|
|
83
70
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
71
|
+
await expect(
|
|
72
|
+
Page.getData({ params: { type: 'events', id: 'order-created', version: '1.0.0' }, props: {} } as any)
|
|
73
|
+
).rejects.toMatchObject({
|
|
74
|
+
status: 404,
|
|
75
|
+
});
|
|
87
76
|
});
|
|
88
77
|
});
|
|
@@ -1,53 +1,48 @@
|
|
|
1
1
|
import { isSSR } from '@utils/feature';
|
|
2
2
|
import { HybridPage } from '@utils/page-loaders/hybrid-page';
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import {
|
|
4
|
+
createResourcesCatalog,
|
|
5
|
+
getOwnersWithResources,
|
|
6
|
+
loadResourceOwners,
|
|
7
|
+
type ResourcesCatalog,
|
|
8
|
+
} from '@utils/collections/resource-owners';
|
|
9
|
+
import type { ResourceOwner } from '@utils/collections/resources';
|
|
7
10
|
|
|
8
|
-
|
|
11
|
+
// The architecture overview pages were replaced by the Resources pages. These routes stay so
|
|
12
|
+
// old links keep working: each one redirects to the resource's Resources page, or to its docs
|
|
13
|
+
// page when it has nothing to list.
|
|
14
|
+
const architecturePageTypes = ['services', 'domains', 'systems'] as const;
|
|
15
|
+
type ArchitecturePageType = (typeof architecturePageTypes)[number];
|
|
9
16
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* systems must hydrate those messages (collection + name). `pageDataLoader`
|
|
13
|
-
* uses the cheaper unenriched path used by docs/sidebar.
|
|
14
|
-
*/
|
|
15
|
-
export const loadArchitectureItems = (type: PageTypes) => {
|
|
16
|
-
if (type === 'domains') {
|
|
17
|
-
return getDomains({ enrichServices: true });
|
|
18
|
-
}
|
|
17
|
+
const isArchitecturePageType = (type: string): type is ArchitecturePageType =>
|
|
18
|
+
architecturePageTypes.includes(type as ArchitecturePageType);
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
const key = (item: ResourceOwner) => `${item.data.id}:${item.data.version}`;
|
|
21
|
+
|
|
22
|
+
const getRedirectPaths = async (type: ArchitecturePageType, catalog: ResourcesCatalog = createResourcesCatalog()) => {
|
|
23
|
+
const [items, ownersWithResources] = await Promise.all([loadResourceOwners(type), getOwnersWithResources(type, catalog)]);
|
|
24
|
+
const hasResourcesPage = new Set(ownersWithResources.map(key));
|
|
23
25
|
|
|
24
|
-
return
|
|
26
|
+
return items.map((item) => {
|
|
27
|
+
const docsPath = `/docs/${type}/${item.data.id}/${item.data.version}`;
|
|
28
|
+
return { item, redirectPath: hasResourcesPage.has(key(item)) ? `${docsPath}/resources` : docsPath };
|
|
29
|
+
});
|
|
25
30
|
};
|
|
26
31
|
|
|
27
|
-
/**
|
|
28
|
-
* Documentation page class for all collection types with versioning
|
|
29
|
-
*/
|
|
30
32
|
export class Page extends HybridPage {
|
|
31
33
|
static async getStaticPaths() {
|
|
32
34
|
if (isSSR()) {
|
|
33
35
|
return [];
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
const
|
|
38
|
+
const catalog = createResourcesCatalog();
|
|
39
|
+
const redirects: Awaited<ReturnType<typeof getRedirectPaths>>[] = [];
|
|
40
|
+
for (const type of architecturePageTypes) redirects.push(await getRedirectPaths(type, catalog));
|
|
37
41
|
|
|
38
|
-
return
|
|
39
|
-
|
|
40
|
-
params: {
|
|
41
|
-
|
|
42
|
-
id: item.data.id,
|
|
43
|
-
version: item.data.version,
|
|
44
|
-
},
|
|
45
|
-
props: {
|
|
46
|
-
type: architecturePageTypes[index],
|
|
47
|
-
...item,
|
|
48
|
-
// Not everything needs the body of the page itself.
|
|
49
|
-
body: undefined,
|
|
50
|
-
},
|
|
42
|
+
return architecturePageTypes.flatMap((type, index) =>
|
|
43
|
+
redirects[index].map(({ item, redirectPath }) => ({
|
|
44
|
+
params: { type, id: item.data.id, version: item.data.version },
|
|
45
|
+
props: { type, data: { id: item.data.id, name: item.data.name, version: item.data.version }, redirectPath },
|
|
51
46
|
}))
|
|
52
47
|
);
|
|
53
48
|
}
|
|
@@ -55,23 +50,16 @@ export class Page extends HybridPage {
|
|
|
55
50
|
protected static async fetchData(params: any) {
|
|
56
51
|
const { type, id, version } = params;
|
|
57
52
|
|
|
58
|
-
if (!type || !id || !version || !
|
|
53
|
+
if (!type || !id || !version || !isArchitecturePageType(type)) {
|
|
59
54
|
return null;
|
|
60
55
|
}
|
|
61
56
|
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
// Find the specific item by id and version
|
|
65
|
-
const item = items.find((i) => i.data.id === id && i.data.version === version);
|
|
66
|
-
|
|
67
|
-
if (!item) {
|
|
57
|
+
const match = (await getRedirectPaths(type)).find(({ item }) => item.data.id === id && item.data.version === version);
|
|
58
|
+
if (!match) {
|
|
68
59
|
return null;
|
|
69
60
|
}
|
|
70
61
|
|
|
71
|
-
return {
|
|
72
|
-
type,
|
|
73
|
-
...item,
|
|
74
|
-
};
|
|
62
|
+
return { type, data: { id, name: match.item.data.name, version }, redirectPath: match.redirectPath };
|
|
75
63
|
}
|
|
76
64
|
|
|
77
65
|
protected static createNotFoundResponse(): Response {
|