@cyberismo/backend 0.0.22 → 0.0.24

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 (164) hide show
  1. package/dist/app.js +3 -1
  2. package/dist/app.js.map +1 -1
  3. package/dist/domain/cards/index.js +169 -12
  4. package/dist/domain/cards/index.js.map +1 -1
  5. package/dist/domain/cards/lib.js +4 -4
  6. package/dist/domain/cards/lib.js.map +1 -1
  7. package/dist/domain/cards/presence.d.ts +50 -0
  8. package/dist/domain/cards/presence.js +93 -0
  9. package/dist/domain/cards/presence.js.map +1 -0
  10. package/dist/domain/cards/schema.d.ts +55 -0
  11. package/dist/domain/cards/schema.js +44 -0
  12. package/dist/domain/cards/schema.js.map +1 -0
  13. package/dist/domain/cards/service.d.ts +7 -2
  14. package/dist/domain/cards/service.js +37 -4
  15. package/dist/domain/cards/service.js.map +1 -1
  16. package/dist/domain/connectors/index.d.ts +15 -0
  17. package/dist/domain/connectors/index.js +37 -0
  18. package/dist/domain/connectors/index.js.map +1 -0
  19. package/dist/domain/connectors/service.d.ts +23 -0
  20. package/dist/domain/connectors/service.js +46 -0
  21. package/dist/domain/connectors/service.js.map +1 -0
  22. package/dist/domain/project/index.js +12 -1
  23. package/dist/domain/project/index.js.map +1 -1
  24. package/dist/domain/project/schema.d.ts +3 -0
  25. package/dist/domain/project/schema.js +8 -0
  26. package/dist/domain/project/schema.js.map +1 -1
  27. package/dist/domain/project/service.d.ts +3 -1
  28. package/dist/domain/project/service.js +7 -1
  29. package/dist/domain/project/service.js.map +1 -1
  30. package/dist/domain/resources/index.js +63 -1
  31. package/dist/domain/resources/index.js.map +1 -1
  32. package/dist/domain/resources/schema.d.ts +9 -0
  33. package/dist/domain/resources/schema.js +8 -1
  34. package/dist/domain/resources/schema.js.map +1 -1
  35. package/dist/domain/resources/service.d.ts +12 -0
  36. package/dist/domain/resources/service.js +47 -4
  37. package/dist/domain/resources/service.js.map +1 -1
  38. package/dist/domain/tree/service.js +0 -1
  39. package/dist/domain/tree/service.js.map +1 -1
  40. package/dist/export.js +1 -2
  41. package/dist/export.js.map +1 -1
  42. package/dist/index.js +7 -1
  43. package/dist/index.js.map +1 -1
  44. package/dist/public/THIRD-PARTY.txt +4283 -1734
  45. package/dist/public/assets/arc-DFTvTCxD.js +1 -0
  46. package/dist/public/assets/architecture-YZFGNWBL-DsWVZJri.js +1 -0
  47. package/dist/public/assets/architectureDiagram-Q4EWVU46-AN0fWZIG.js +36 -0
  48. package/dist/public/assets/array-xS8TccZC.js +1 -0
  49. package/dist/public/assets/blockDiagram-DXYQGD6D-RrIidZT3.js +132 -0
  50. package/dist/public/assets/c4Diagram-AHTNJAMY-CW7AUKhY.js +10 -0
  51. package/dist/public/assets/channel-BxffgrNT.js +1 -0
  52. package/dist/public/assets/chunk-2KRD3SAO-D5XH6bj9.js +1 -0
  53. package/dist/public/assets/chunk-336JU56O-CVDEj5x8.js +2 -0
  54. package/dist/public/assets/chunk-426QAEUC-CIWkCWTf.js +1 -0
  55. package/dist/public/assets/chunk-4BX2VUAB-O8dxzEpn.js +1 -0
  56. package/dist/public/assets/chunk-4TB4RGXK-Bt4fWDlh.js +206 -0
  57. package/dist/public/assets/chunk-55IACEB6-R-yr7oHq.js +1 -0
  58. package/dist/public/assets/chunk-5FUZZQ4R-D7L4hZzZ.js +62 -0
  59. package/dist/public/assets/chunk-5PVQY5BW-D46cRkay.js +2 -0
  60. package/dist/public/assets/chunk-67CJDMHE-1fLguPDq.js +1 -0
  61. package/dist/public/assets/chunk-7N4EOEYR-BPbEiVZr.js +1 -0
  62. package/dist/public/assets/chunk-AA7GKIK3-DZOqN73n.js +1 -0
  63. package/dist/public/assets/chunk-BSJP7CBP-BQPMRa-Q.js +1 -0
  64. package/dist/public/assets/chunk-CFjPhJqf.js +1 -0
  65. package/dist/public/assets/chunk-CIAEETIT-CoKBG93U.js +1 -0
  66. package/dist/public/assets/chunk-EDXVE4YY-BxmMvdBY.js +1 -0
  67. package/dist/public/assets/chunk-ENJZ2VHE-BrjxYY_T.js +10 -0
  68. package/dist/public/assets/chunk-FMBD7UC4-DChB7ne2.js +15 -0
  69. package/dist/public/assets/chunk-FOC6F5B3-DyK4SoM2.js +1 -0
  70. package/dist/public/assets/chunk-ICPOFSXX-6-ABzkfw.js +122 -0
  71. package/dist/public/assets/chunk-K5T4RW27-432kjUXO.js +94 -0
  72. package/dist/public/assets/chunk-KGLVRYIC-ChpiuJys.js +1 -0
  73. package/dist/public/assets/chunk-LIHQZDEY-C65uflEj.js +1 -0
  74. package/dist/public/assets/chunk-ORNJ4GCN-DPvOqXKg.js +1 -0
  75. package/dist/public/assets/chunk-OYMX7WX6-BecMUsrL.js +231 -0
  76. package/dist/public/assets/chunk-QZHKN3VN-d1i_Lm1t.js +1 -0
  77. package/dist/public/assets/chunk-U2HBQHQK-C6yvwOAo.js +70 -0
  78. package/dist/public/assets/chunk-X2U36JSP-D1C3tOED.js +1 -0
  79. package/dist/public/assets/chunk-XPW4576I-BH37LuuF.js +32 -0
  80. package/dist/public/assets/chunk-YZCP3GAM-D83gHdmx.js +1 -0
  81. package/dist/public/assets/chunk-ZZ45TVLE-DrCjtkdu.js +1 -0
  82. package/dist/public/assets/classDiagram-6PBFFD2Q-CRYacdjd.js +1 -0
  83. package/dist/public/assets/classDiagram-v2-HSJHXN6E-qOJXC_A9.js +1 -0
  84. package/dist/public/assets/clone-Bno0nirE.js +1 -0
  85. package/dist/public/assets/colors-DZGTowqM.js +1 -0
  86. package/dist/public/assets/cose-bilkent-S5V4N54A-BdSMsFO7.js +1 -0
  87. package/dist/public/assets/cytoscape.esm-DpmcErfs.js +321 -0
  88. package/dist/public/assets/dagre-D0KOcyEK.js +1 -0
  89. package/dist/public/assets/dagre-KV5264BT-C_l1Bck_.js +4 -0
  90. package/dist/public/assets/defaultLocale-B6dPnyNg.js +1 -0
  91. package/dist/public/assets/diagram-5BDNPKRD-D6FxiIv1.js +10 -0
  92. package/dist/public/assets/diagram-G4DWMVQ6-COGBv7tQ.js +24 -0
  93. package/dist/public/assets/diagram-MMDJMWI5-U6E9w334.js +43 -0
  94. package/dist/public/assets/diagram-TYMM5635-Tvfdnlo4.js +24 -0
  95. package/dist/public/assets/dist-C6vvxvVV.js +1 -0
  96. package/dist/public/assets/erDiagram-SMLLAGMA-VcPXkmy9.js +85 -0
  97. package/dist/public/assets/flatten-BKeNCJRx.js +1 -0
  98. package/dist/public/assets/flowDiagram-DWJPFMVM-PRQdlypB.js +162 -0
  99. package/dist/public/assets/ganttDiagram-T4ZO3ILL-CSVaTciZ.js +292 -0
  100. package/dist/public/assets/gitGraph-7Q5UKJZL-C6GVmHC4.js +1 -0
  101. package/dist/public/assets/gitGraphDiagram-UUTBAWPF-DMa-Gcy1.js +106 -0
  102. package/dist/public/assets/graphlib-BYojtb6k.js +1 -0
  103. package/dist/public/assets/identity-nF-8qK2-.js +1 -0
  104. package/dist/public/assets/index-oVbUFngg.js +737 -0
  105. package/dist/public/assets/index-ypsafPwV.css +1 -0
  106. package/dist/public/assets/info-OMHHGYJF-CjsmQeYR.js +1 -0
  107. package/dist/public/assets/infoDiagram-42DDH7IO-COL_Na4w.js +2 -0
  108. package/dist/public/assets/init-B9nbfZCT.js +1 -0
  109. package/dist/public/assets/isEmpty-Di-NpihJ.js +1 -0
  110. package/dist/public/assets/ishikawaDiagram-UXIWVN3A-B3HoSFaq.js +70 -0
  111. package/dist/public/assets/journeyDiagram-VCZTEJTY-kH30WNF3.js +139 -0
  112. package/dist/public/assets/kanban-definition-6JOO6SKY-RdjEsejt.js +89 -0
  113. package/dist/public/assets/katex-Bfn1OZEl.js +257 -0
  114. package/dist/public/assets/line-DrKJYWGi.js +1 -0
  115. package/dist/public/assets/linear-C8MDrvxz.js +1 -0
  116. package/dist/public/assets/mermaid-parser.core-BeZK8g2G.js +4 -0
  117. package/dist/public/assets/mindmap-definition-QFDTVHPH-CNWWI4MF.js +96 -0
  118. package/dist/public/assets/ordinal-BXaEVJbT.js +1 -0
  119. package/dist/public/assets/packet-4T2RLAQJ-Dq0Z9G3r.js +1 -0
  120. package/dist/public/assets/path-C7Bv0Qdk.js +1 -0
  121. package/dist/public/assets/pie-ZZUOXDRM-Coeb8Atk.js +1 -0
  122. package/dist/public/assets/pieDiagram-DEJITSTG-CrDW-30P.js +30 -0
  123. package/dist/public/assets/quadrantDiagram-34T5L4WZ-BoDA5WQ-.js +7 -0
  124. package/dist/public/assets/radar-PYXPWWZC-BF2NN2TG.js +1 -0
  125. package/dist/public/assets/range-DuD7Go1R.js +1 -0
  126. package/dist/public/assets/reduce-5tLTMRkg.js +1 -0
  127. package/dist/public/assets/requirementDiagram-MS252O5E-BnG7epLH.js +84 -0
  128. package/dist/public/assets/rough.esm-DE7XMpOC.js +1 -0
  129. package/dist/public/assets/sankeyDiagram-XADWPNL6-oyTFoxhB.js +10 -0
  130. package/dist/public/assets/sequenceDiagram-FGHM5R23-hwG2jT_2.js +157 -0
  131. package/dist/public/assets/src-ps-3oTnY.js +1 -0
  132. package/dist/public/assets/stateDiagram-FHFEXIEX-BiYqw63j.js +1 -0
  133. package/dist/public/assets/stateDiagram-v2-QKLJ7IA2-CggSrcDh.js +1 -0
  134. package/dist/public/assets/time-Bgnk7ODM.js +1 -0
  135. package/dist/public/assets/timeline-definition-GMOUNBTQ-9MnBod43.js +120 -0
  136. package/dist/public/assets/treeView-SZITEDCU-IRdgIoRV.js +1 -0
  137. package/dist/public/assets/treemap-DK8fitek.js +1 -0
  138. package/dist/public/assets/treemap-W4RFUUIX-C-JBwyWW.js +1 -0
  139. package/dist/public/assets/vennDiagram-DHZGUBPP-sEA6JNL-.js +34 -0
  140. package/dist/public/assets/wardley-RL74JXVD-DdU04Q7E.js +1 -0
  141. package/dist/public/assets/wardleyDiagram-NUSXRM2D-Dws321pY.js +20 -0
  142. package/dist/public/assets/xychartDiagram-5P7HB3ND-BcqyvmmW.js +7 -0
  143. package/dist/public/images/broken_link.svg +7 -0
  144. package/dist/public/index.html +31 -2
  145. package/package.json +7 -7
  146. package/src/app.ts +3 -1
  147. package/src/domain/cards/index.ts +271 -48
  148. package/src/domain/cards/lib.ts +6 -5
  149. package/src/domain/cards/presence.ts +124 -0
  150. package/src/domain/cards/schema.ts +50 -0
  151. package/src/domain/cards/service.ts +91 -5
  152. package/src/domain/connectors/index.ts +39 -0
  153. package/src/domain/connectors/service.ts +67 -0
  154. package/src/domain/project/index.ts +23 -1
  155. package/src/domain/project/schema.ts +9 -0
  156. package/src/domain/project/service.ts +18 -2
  157. package/src/domain/resources/index.ts +74 -0
  158. package/src/domain/resources/schema.ts +14 -0
  159. package/src/domain/resources/service.ts +50 -4
  160. package/src/domain/tree/service.ts +0 -1
  161. package/src/export.ts +1 -2
  162. package/src/index.ts +8 -4
  163. package/dist/public/assets/index-B_lh6qtv.css +0 -1
  164. package/dist/public/assets/index-CEol8Bfi.js +0 -164949
@@ -12,9 +12,13 @@
12
12
  */
13
13
 
14
14
  import Processor from '@asciidoctor/core';
15
- import { type MetadataContent } from '@cyberismo/data-handler/interfaces/project-interfaces';
15
+ import type {
16
+ ExportPdfOptions,
17
+ MetadataContent,
18
+ } from '@cyberismo/data-handler/interfaces/project-interfaces';
16
19
  import type { attachmentPayload } from '@cyberismo/data-handler/interfaces/request-status-interfaces';
17
20
  import { type CommandManager, evaluateMacros } from '@cyberismo/data-handler';
21
+ import { preprocessMermaidBlocksForHtml } from '@cyberismo/data-handler/utils/mermaid-renderer';
18
22
  import { allCards } from './lib.js';
19
23
  import type { TreeOptions } from '../../types.js';
20
24
 
@@ -120,11 +124,19 @@ export async function uploadAttachments(
120
124
  };
121
125
  }
122
126
 
127
+ function validateAttachmentFileName(filename: string): void {
128
+ const decoded = decodeURIComponent(filename);
129
+ if (/(^|[/\\])\.\.([/\\]|$)/.test(decoded)) {
130
+ throw new Error('Invalid attachment filename');
131
+ }
132
+ }
133
+
123
134
  export async function removeAttachment(
124
135
  commands: CommandManager,
125
136
  key: string,
126
137
  filename: string,
127
138
  ) {
139
+ validateAttachmentFileName(filename);
128
140
  await commands.removeCmd.remove('attachment', key, filename);
129
141
  return { message: 'Attachment removed successfully' };
130
142
  }
@@ -134,6 +146,7 @@ export async function openAttachment(
134
146
  key: string,
135
147
  filename: string,
136
148
  ) {
149
+ validateAttachmentFileName(filename);
137
150
  await commands.showCmd.openAttachment(key, filename);
138
151
  return { message: 'Attachment opened successfully' };
139
152
  }
@@ -156,6 +169,9 @@ export async function parseContent(
156
169
  asciidocContent = `Macro error: ${error instanceof Error ? error.message : 'Unknown error'}\n\n${content}`;
157
170
  }
158
171
 
172
+ // Convert [mermaid] AsciiDoc blocks to passthrough HTML before asciidoctor processes them
173
+ asciidocContent = preprocessMermaidBlocksForHtml(asciidocContent);
174
+
159
175
  const processor = Processor();
160
176
  const parsedContent = processor
161
177
  .convert(asciidocContent, {
@@ -174,30 +190,92 @@ export async function parseContent(
174
190
  export async function createLink(
175
191
  commands: CommandManager,
176
192
  key: string,
177
- toCard: string,
193
+ target: string,
178
194
  linkType: string,
195
+ direction: 'outbound' | 'inbound' = 'outbound',
179
196
  description?: string,
180
197
  ) {
181
- await commands.createCmd.createLink(key, toCard, linkType, description);
198
+ // For outbound: key is source, target is destination
199
+ // For inbound: target is source, key is destination
200
+ const source = direction === 'outbound' ? key : target;
201
+ const destination = direction === 'outbound' ? target : key;
202
+
203
+ await commands.createCmd.createLink(
204
+ source,
205
+ destination,
206
+ linkType,
207
+ description,
208
+ direction,
209
+ );
210
+
182
211
  return { message: 'Link created successfully' };
183
212
  }
184
213
 
185
214
  export async function removeLink(
186
215
  commands: CommandManager,
187
216
  key: string,
188
- toCard: string,
217
+ target: string,
189
218
  linkType: string,
219
+ direction: 'outbound' | 'inbound' = 'outbound',
190
220
  description?: string,
191
221
  ) {
192
- await commands.removeCmd.remove('link', key, toCard, linkType, description);
222
+ // For outbound: key is source, target is destination
223
+ // For inbound: target is source, key is destination
224
+ const source = direction === 'outbound' ? key : target;
225
+ const destination = direction === 'outbound' ? target : key;
226
+ await commands.removeCmd.remove(
227
+ 'link',
228
+ source,
229
+ destination,
230
+ linkType,
231
+ description,
232
+ );
193
233
  return { message: 'Link removed successfully' };
194
234
  }
195
235
 
236
+ export async function updateLink(
237
+ commands: CommandManager,
238
+ key: string,
239
+ toCard: string,
240
+ linkType: string,
241
+ direction: 'outbound' | 'inbound',
242
+ previousToCard: string,
243
+ previousLinkType: string,
244
+ previousDirection: 'outbound' | 'inbound',
245
+ linkDescription?: string,
246
+ previousLinkDescription?: string,
247
+ ) {
248
+ // For simplicity create the new link first so that duplicate-link validation runs before
249
+ // the old link is removed. This also handles direction changes.
250
+ return commands.atomic(async () => {
251
+ await createLink(
252
+ commands,
253
+ key,
254
+ toCard,
255
+ linkType,
256
+ direction,
257
+ linkDescription,
258
+ );
259
+
260
+ await removeLink(
261
+ commands,
262
+ key,
263
+ previousToCard,
264
+ previousLinkType,
265
+ previousDirection,
266
+ previousLinkDescription,
267
+ );
268
+
269
+ return { message: 'Link updated successfully' };
270
+ }, `Update link on ${key} to ${toCard}`);
271
+ }
272
+
196
273
  export async function getAttachment(
197
274
  commands: CommandManager,
198
275
  key: string,
199
276
  filename: string,
200
277
  ): Promise<attachmentPayload> {
278
+ validateAttachmentFileName(filename);
201
279
  return commands.showCmd.showAttachment(key, filename);
202
280
  }
203
281
 
@@ -237,3 +315,11 @@ export async function findRelevantAttachments(
237
315
  attachment: attachment.fileName,
238
316
  }));
239
317
  }
318
+
319
+ export async function exportCard(
320
+ commands: CommandManager,
321
+ options: ExportPdfOptions,
322
+ ): Promise<Buffer> {
323
+ const result = await commands.exportCmd.exportPdfBuffer(options);
324
+ return result;
325
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ Cyberismo
3
+ Copyright © Cyberismo Ltd and contributors 2026
4
+ This program is free software: you can redistribute it and/or modify it under
5
+ the terms of the GNU Affero General Public License version 3 as published by
6
+ the Free Software Foundation. This program is distributed in the hope that it
7
+ will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
8
+ of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
9
+ See the GNU Affero General Public License for more details.
10
+ You should have received a copy of the GNU Affero General Public
11
+ License along with this program. If not, see <https://www.gnu.org/licenses/>.
12
+ */
13
+ import { Hono } from 'hono';
14
+ import { isSSGContext } from 'hono/ssg';
15
+ import * as connectorsService from './service.js';
16
+ import { UserRole } from '../../types.js';
17
+ import { requireRole } from '../../middleware/auth.js';
18
+
19
+ const router = new Hono();
20
+
21
+ /**
22
+ * @openapi
23
+ * /api/connectors:
24
+ * get:
25
+ * summary: Returns all available connectors
26
+ * responses:
27
+ * 200:
28
+ * description: List of connectors with their display names
29
+ */
30
+ router.get('/', requireRole(UserRole.Reader), async (c) => {
31
+ if (isSSGContext(c)) {
32
+ return c.json([]);
33
+ }
34
+ const commands = c.get('commands');
35
+ const connectors = await connectorsService.getConnectors(commands);
36
+ return c.json(connectors);
37
+ });
38
+
39
+ export default router;
@@ -0,0 +1,67 @@
1
+ /**
2
+ Cyberismo
3
+ Copyright © Cyberismo Ltd and contributors 2024
4
+ This program is free software: you can redistribute it and/or modify it under
5
+ the terms of the GNU Affero General Public License version 3 as published by
6
+ the Free Software Foundation.
7
+ This program is distributed in the hope that it will be useful, but WITHOUT
8
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
9
+ FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
10
+ details. You should have received a copy of the GNU Affero General Public
11
+ License along with this program. If not, see <https://www.gnu.org/licenses/>.
12
+ */
13
+
14
+ import type { CommandManager } from '@cyberismo/data-handler';
15
+
16
+ export interface ExternalItem {
17
+ key: string;
18
+ title: string;
19
+ }
20
+
21
+ export interface Connector {
22
+ name: string;
23
+ displayName: string;
24
+ items: ExternalItem[];
25
+ }
26
+
27
+ export async function getConnectors(
28
+ commands: CommandManager,
29
+ ): Promise<Connector[]> {
30
+ const results = await commands.calculateCmd.runQuery(
31
+ 'connectors',
32
+ 'localApp',
33
+ );
34
+
35
+ // Separate connectors from items based on type field
36
+ const connectorsMap = new Map<string, Connector>();
37
+ const items: { connector: string; key: string; title: string }[] = [];
38
+
39
+ for (const r of results) {
40
+ if (r.type === 'connector') {
41
+ connectorsMap.set(r.key, {
42
+ name: r.key,
43
+ displayName: r.displayName ?? r.key,
44
+ items: [],
45
+ });
46
+ } else {
47
+ items.push({
48
+ connector: r.connector ?? '',
49
+ key: r.itemKey ?? '',
50
+ title: r.title ?? '',
51
+ });
52
+ }
53
+ }
54
+
55
+ // Associate items with their connectors
56
+ for (const item of items) {
57
+ const connector = connectorsMap.get(item.connector);
58
+ if (connector) {
59
+ connector.items.push({
60
+ key: item.key,
61
+ title: item.title,
62
+ });
63
+ }
64
+ }
65
+
66
+ return Array.from(connectorsMap.values());
67
+ }
@@ -13,7 +13,11 @@
13
13
 
14
14
  import { Hono } from 'hono';
15
15
  import { zValidator } from '../../middleware/zvalidator.js';
16
- import { moduleParamSchema, updateProjectSchema } from './schema.js';
16
+ import {
17
+ importModuleSchema,
18
+ moduleParamSchema,
19
+ updateProjectSchema,
20
+ } from './schema.js';
17
21
  import * as projectService from './service.js';
18
22
  import { UserRole } from '../../types.js';
19
23
  import { requireRole } from '../../middleware/auth.js';
@@ -58,6 +62,24 @@ router.post(
58
62
  },
59
63
  );
60
64
 
65
+ router.get('/modules/importable', requireRole(UserRole.Reader), async (c) => {
66
+ const commands = c.get('commands');
67
+ const modules = await projectService.getImportableModules(commands);
68
+ return c.json(modules);
69
+ });
70
+
71
+ router.post(
72
+ '/modules',
73
+ requireRole(UserRole.Admin),
74
+ zValidator('json', importModuleSchema),
75
+ async (c) => {
76
+ const commands = c.get('commands');
77
+ const { source } = c.req.valid('json');
78
+ await projectService.importModule(commands, source);
79
+ return c.json({ message: 'Module imported successfully' });
80
+ },
81
+ );
82
+
61
83
  router.delete(
62
84
  '/modules/:module',
63
85
  requireRole(UserRole.Admin),
@@ -21,3 +21,12 @@ export const updateProjectSchema = z.object({
21
21
  name: z.string().optional(),
22
22
  cardKeyPrefix: z.string().optional(),
23
23
  });
24
+
25
+ export const importModuleSchema = z.object({
26
+ source: z
27
+ .string()
28
+ .min(1)
29
+ .refine((s) => s.startsWith('https') || s.startsWith('git@'), {
30
+ message: 'Source must be a git URL (https:// or git@)',
31
+ }),
32
+ });
@@ -11,7 +11,10 @@
11
11
  License along with this program. If not, see <https://www.gnu.org/licenses/>.
12
12
  */
13
13
 
14
- import { type CommandManager } from '@cyberismo/data-handler';
14
+ import {
15
+ type CommandManager,
16
+ type ModuleSettingFromHub,
17
+ } from '@cyberismo/data-handler';
15
18
 
16
19
  export interface ProjectModule {
17
20
  name: string;
@@ -54,7 +57,7 @@ export async function getProject(
54
57
  const project = await commands.showCmd.showProject();
55
58
  const modules = await commands.showCmd.showModules();
56
59
  const moduleDetails = await Promise.all(
57
- modules.map((mod) => toModuleInfo(commands, mod)),
60
+ modules.map((mod) => toModuleInfo(commands, mod.name)),
58
61
  );
59
62
 
60
63
  return {
@@ -94,3 +97,16 @@ export async function updateAllModules(commands: CommandManager) {
94
97
  export async function deleteModule(commands: CommandManager, module: string) {
95
98
  await commands.removeCmd.remove('module', module);
96
99
  }
100
+
101
+ export async function getImportableModules(
102
+ commands: CommandManager,
103
+ ): Promise<ModuleSettingFromHub[]> {
104
+ return commands.showCmd.showImportableModules(false, true);
105
+ }
106
+
107
+ export async function importModule(
108
+ commands: CommandManager,
109
+ source: string,
110
+ ): Promise<void> {
111
+ await commands.importCmd.importModule(source);
112
+ }
@@ -20,6 +20,8 @@ import { zValidator } from '../../middleware/zvalidator.js';
20
20
  import {
21
21
  validateResourceParamsSchema,
22
22
  updateOperationBodySchema,
23
+ workflowGraphParamsSchema,
24
+ workflowGraphQuerySchema,
23
25
  } from './schema.js';
24
26
  import { requireRole } from '../../middleware/auth.js';
25
27
 
@@ -132,6 +134,78 @@ router.delete(
132
134
  },
133
135
  );
134
136
 
137
+ /**
138
+ * @swagger
139
+ * /api/resources/{prefix}/workflows/{identifier}/graph:
140
+ * get:
141
+ * summary: Render the state-machine graph for a workflow
142
+ * description: Returns a base64-encoded sanitized SVG of the workflow's
143
+ * state-machine diagram, rendered with the built-in
144
+ * workflow graph model and view. When the optional `card`
145
+ * query parameter is provided, the diagram highlights
146
+ * that card's current workflowState.
147
+ * parameters:
148
+ * - in: path
149
+ * name: prefix
150
+ * required: true
151
+ * schema:
152
+ * type: string
153
+ * - in: path
154
+ * name: identifier
155
+ * required: true
156
+ * schema:
157
+ * type: string
158
+ * - in: query
159
+ * name: card
160
+ * required: false
161
+ * schema:
162
+ * type: string
163
+ * description: Card key whose current workflowState should be
164
+ * highlighted in the rendered diagram.
165
+ * responses:
166
+ * 200:
167
+ * description: Rendered diagram
168
+ * content:
169
+ * application/json:
170
+ * schema:
171
+ * type: object
172
+ * properties:
173
+ * svg:
174
+ * type: string
175
+ * description: Base64-encoded SVG document.
176
+ * 400:
177
+ * description: Invalid path parameters
178
+ * 404:
179
+ * description: Workflow or card not found
180
+ * 500:
181
+ * description: Server error
182
+ */
183
+ router.get(
184
+ '/:prefix/workflows/:identifier/graph',
185
+ requireRole(UserRole.Reader),
186
+ zValidator('param', workflowGraphParamsSchema),
187
+ zValidator('query', workflowGraphQuerySchema),
188
+ async (c) => {
189
+ const commands = c.get('commands');
190
+ const { prefix, identifier } = c.req.valid('param');
191
+ const { card } = c.req.valid('query');
192
+ const workflowName = `${prefix}/workflows/${identifier}`;
193
+ try {
194
+ const svg = await resourceService.getWorkflowGraph(
195
+ commands,
196
+ workflowName,
197
+ card,
198
+ );
199
+ return c.json({ svg });
200
+ } catch (error) {
201
+ if (error instanceof Error && error.message.includes('not found')) {
202
+ return c.json({ error: error.message }, 404);
203
+ }
204
+ throw error;
205
+ }
206
+ },
207
+ );
208
+
135
209
  router.post(
136
210
  '/:prefix/:type/:identifier/operation',
137
211
  requireRole(UserRole.Admin),
@@ -1,9 +1,23 @@
1
1
  import { z } from 'zod';
2
2
  import {
3
+ identifierSchema,
3
4
  resourceParamsSchema,
4
5
  resourceTypes,
5
6
  } from '../../common/validationSchemas.js';
6
7
 
8
+ export const workflowGraphParamsSchema = z.object({
9
+ prefix: z.string(),
10
+ identifier: identifierSchema,
11
+ });
12
+
13
+ export type WorkflowGraphParams = z.infer<typeof workflowGraphParamsSchema>;
14
+
15
+ export const workflowGraphQuerySchema = z.object({
16
+ card: z.string().optional(),
17
+ });
18
+
19
+ export type WorkflowGraphQuery = z.infer<typeof workflowGraphQuerySchema>;
20
+
7
21
  export const resourceFileParamsSchema = resourceParamsSchema.extend({
8
22
  file: z.string(),
9
23
  });
@@ -42,14 +42,14 @@ const resourceTypes: ResourceFolderType[] = [
42
42
 
43
43
  async function getModules(commands: CommandManager) {
44
44
  try {
45
- const moduleNames = await commands.showCmd.showModules();
45
+ const modules = await commands.showCmd.showModules();
46
46
  return Promise.all(
47
- moduleNames.map(async (moduleName) => {
47
+ modules.map(async (mod) => {
48
48
  try {
49
- const module = await commands.showCmd.showModule(moduleName);
49
+ const module = await commands.showCmd.showModule(mod.name);
50
50
  return { name: module.name, cardKeyPrefix: module.cardKeyPrefix };
51
51
  } catch {
52
- return { name: moduleName, cardKeyPrefix: moduleName };
52
+ return { name: mod.name, cardKeyPrefix: mod.name };
53
53
  }
54
54
  }),
55
55
  );
@@ -467,6 +467,52 @@ export async function validateResource(
467
467
  };
468
468
  }
469
469
 
470
+ /**
471
+ * Renders the built-in state-machine graph for a single workflow.
472
+ * When `cardKey` is provided, the card's current workflowState is
473
+ * highlighted in the diagram. The workflow lookup, card lookup and
474
+ * graph rendering all run inside the same consistency window so the
475
+ * highlighted state matches the diagram even if the card is being
476
+ * transitioned concurrently.
477
+ * @returns base64-encoded sanitized SVG.
478
+ * @throws Error with 'not found' in the message when the workflow or
479
+ * card cannot be resolved.
480
+ */
481
+ export async function getWorkflowGraph(
482
+ commands: CommandManager,
483
+ workflowName: string,
484
+ cardKey?: string,
485
+ ): Promise<string> {
486
+ return commands.consistent(async () => {
487
+ try {
488
+ await commands.showCmd.showResource(workflowName, 'workflows');
489
+ } catch (error) {
490
+ if (error instanceof Error && error.message.includes('does not exist')) {
491
+ throw new Error(`Workflow '${workflowName}' not found`, {
492
+ cause: error,
493
+ });
494
+ }
495
+ throw error;
496
+ }
497
+ let currentState: string | undefined;
498
+ if (cardKey) {
499
+ let card;
500
+ try {
501
+ card = await commands.showCmd.showCardDetails(cardKey);
502
+ } catch (error) {
503
+ throw new Error(`Card '${cardKey}' not found`, { cause: error });
504
+ }
505
+ if (!card?.metadata) {
506
+ throw new Error(`Card '${cardKey}' not found`);
507
+ }
508
+ currentState = card.metadata.workflowState;
509
+ }
510
+ return commands.calculateCmd.runWorkflowGraph(workflowName, {
511
+ currentState,
512
+ });
513
+ });
514
+ }
515
+
470
516
  /**
471
517
  * Perform an updateOperation on a resource key.
472
518
  * This delegates to data-handler Update.applyResourceOperation.
@@ -27,7 +27,6 @@ export async function getCardTree(
27
27
  cardKey?: string,
28
28
  recursive?: boolean,
29
29
  ): ReturnType<typeof commands.calculateCmd.runQuery> {
30
- await commands.calculateCmd.generate();
31
30
  return commands.calculateCmd.runQuery(
32
31
  'tree',
33
32
  isSsg ? 'exportedSite' : 'localApp',
package/src/export.ts CHANGED
@@ -52,7 +52,7 @@ export async function getCardQueryResult(
52
52
  ): Promise<QueryResult<'card'>[]> {
53
53
  if (!_cardQueryPromise) {
54
54
  // fetch all cards
55
- _cardQueryPromise = commands.project.calculationEngine.runQuery(
55
+ _cardQueryPromise = commands.calculateCmd.runQuery(
56
56
  'card',
57
57
  'exportedSite',
58
58
  {},
@@ -109,7 +109,6 @@ export async function exportSite(
109
109
  );
110
110
 
111
111
  reset();
112
- await commands.project.calculationEngine.generate();
113
112
 
114
113
  // estimate total based on the number of cards to export
115
114
  const cards = await findAllCards(commands, opts);
package/src/index.ts CHANGED
@@ -36,11 +36,14 @@ const DEFAULT_MAX_PORT = DEFAULT_PORT + 100;
36
36
  export async function previewSite(dir: string, findPort: boolean = true) {
37
37
  const app = new Hono();
38
38
  app.use(serveStatic({ root: dir }));
39
- app.get('*', (c) =>
40
- c.html(
39
+ app.get('*', (c) => {
40
+ if (c.req.path.startsWith('/api/')) {
41
+ return c.json({ error: 'Resource not found' }, 404);
42
+ }
43
+ return c.html(
41
44
  readFile(path.join(dir, 'index.html')).then((file) => file.toString()),
42
- ),
43
- );
45
+ );
46
+ });
44
47
 
45
48
  let port = parseInt(process.env.PORT || DEFAULT_PORT.toString(), 10);
46
49
 
@@ -78,6 +81,7 @@ function startApp<E extends Env, S extends Schema, P extends string>(
78
81
  {
79
82
  fetch: app.fetch,
80
83
  port: port,
84
+ hostname: process.env.HOST || '127.0.0.1',
81
85
  },
82
86
  (info) => {
83
87
  console.log(`Running Cyberismo app on http://localhost:${info.port}`);
@@ -1 +0,0 @@
1
- @media(max-width:800px){.breadcrumbs{display:none}}[role=treeitem][aria-selected=false]:hover{.treenode{background-color:var(--joy-palette-background-level1, #dedede)}}[role=treeitem][aria-selected=true]{outline:none}.resizeHandle{width:2px}.resizeHandle:hover,.resizeHandle:active{background-color:var(--joy-palette-primary-500, #0b6bcb)}.cyberismo-svg-wrapper svg{max-height:100vh}.doc .MuiButton-root{margin-top:12px}.doc table.tableblock{border-collapse:collapse}.doc{color:var(--joy-palette-text-primary, #333);font-size:inherit;-ms-hyphens:auto;hyphens:auto;line-height:1.6;margin:0;padding:0}@media screen and (min-width:1024px){.doc{-webkit-box-flex:1;-ms-flex:auto;flex:auto;font-size:.94444rem;margin:0;min-width:0}}.doc h1,.doc h2,.doc h3,.doc h4,.doc h5,.doc h6{color:var(--joy-palette-text-primary, #191919);font-weight:400;-ms-hyphens:none;hyphens:none;line-height:1.3;margin:1rem 0 0}.doc>h1.page:first-child{font-size:2rem;margin:1.5rem 0}@media screen and (min-width:769px){.doc>h1.page:first-child{margin-top:2.5rem}}.doc>h1.page:first-child+aside.toc.embedded{margin-top:-.5rem}.doc>h2#name+.sectionbody{margin-top:1rem}#preamble+.sect1,.doc .sect1+.sect1{margin-top:2rem}.doc h1.sect0{background:var(--joy-palette-background-surface, #f0f0f0);font-size:1.8em;margin:1.5rem -1rem 0;padding:.5rem 1rem}.doc h2:not(.discrete){border-bottom:1px solid var(--joy-palette-divider, #e1e1e1);margin-left:-1rem;margin-right:-1rem;padding:.4rem 1rem .1rem}.doc h3:not(.discrete),.doc h4:not(.discrete){font-weight:600}.doc h1 .anchor,.doc h2 .anchor,.doc h3 .anchor,.doc h4 .anchor,.doc h5 .anchor,.doc h6 .anchor{position:absolute;text-decoration:none;width:1.75ex;margin-left:-1.5ex;visibility:hidden;font-size:.8em;font-weight:400;padding-top:.05em}.doc h1 .anchor:before,.doc h2 .anchor:before,.doc h3 .anchor:before,.doc h4 .anchor:before,.doc h5 .anchor:before,.doc h6 .anchor:before{content:"§"}.doc h1:hover .anchor,.doc h2:hover .anchor,.doc h3:hover .anchor,.doc h4:hover .anchor,.doc h5:hover .anchor,.doc h6:hover .anchor{visibility:visible}.doc dl,.doc p{margin:0}.doc a{color:var(--joy-palette-primary-600, #1565c0)}.doc a:hover{color:var(--joy-palette-primary-700, #104d92)}.doc a.bare{-ms-hyphens:none;hyphens:none}.doc a.unresolved{color:var(--joy-palette-danger-500, #d32f2f)}.doc i.fa{-ms-hyphens:none;hyphens:none;font-style:normal}.doc .colist>table code,.doc p code,.doc thead code{color:var(--joy-palette-text-primary, #222);background:var(--joy-palette-background-level1, #fafafa);border-radius:.25em;font-size:.95em;padding:.125em .25em}.doc code,.doc pre{-ms-hyphens:none;hyphens:none}.doc pre{font-size:.88889rem;line-height:1.5;margin:0}.doc blockquote{margin:0}.doc .paragraph.lead>p{font-size:1rem}.doc .right{float:right}.doc .left{float:left}.doc .float-gap.right{margin:0 1rem 1rem 0}.doc .float-gap.left{margin:0 0 1rem 1rem}.doc .float-group:after{content:"";display:table;clear:both}.doc .text-left{text-align:left}.doc .text-center{text-align:center}.doc .text-right{text-align:right}.doc .text-justify{text-align:justify}.doc .stretch{width:100%}.doc .big{font-size:larger}.doc .small{font-size:smaller}.doc .underline{text-decoration:underline}.doc .line-through{text-decoration:line-through}.doc .dlist,.doc .exampleblock,.doc .hdlist,.doc .imageblock,.doc .listingblock,.doc .literalblock,.doc .olist,.doc .paragraph,.doc .partintro,.doc .quoteblock,.doc .sidebarblock,.doc .tabs,.doc .ulist,.doc .verseblock,.doc .videoblock,.doc details,.doc hr{margin:1rem 0 0}.doc .tablecontainer,.doc .tablecontainer+*,.doc :not(.tablecontainer)>table.tableblock,.doc :not(.tablecontainer)>table.tableblock+*,.doc>table.tableblock,.doc>table.tableblock+*{margin-top:1.5rem}.doc table.tableblock{font-size:.83333rem}.doc p.tableblock+p.tableblock{margin-top:.5rem}.doc table.tableblock pre{font-size:inherit}.doc td.tableblock>.content{word-wrap:anywhere}.doc td.tableblock>.content>:first-child{margin-top:0}.doc table.tableblock td{padding:.5rem}.doc table.tableblock th{padding:.5rem;background:var(--joy-palette-background-level1, #fbfcfe)}.doc table.tableblock,.doc table.tableblock>*>tr>*{border:0 solid var(--joy-palette-divider, #e1e1e1)}.doc table.grid-all>*>tr>*{border-width:1px}.doc table.grid-cols>*>tr>*{border-width:0 1px}.doc table.grid-rows>*>tr>*{border-width:1px 0}.doc table.grid-all>thead th,.doc table.grid-rows>thead th{border-bottom-width:2.5px}.doc table.frame-all{border-width:1px}.doc table.frame-ends{border-width:1px 0}.doc table.frame-sides{border-width:0 1px}.doc table.frame-none>colgroup+*>:first-child>*,.doc table.frame-sides>colgroup+*>:first-child>*{border-top-width:0}.doc table.frame-sides>:last-child>:last-child>*{border-bottom-width:0}.doc table.frame-ends>*>tr>:first-child,.doc table.frame-none>*>tr>:first-child{border-left-width:0}.doc table.frame-ends>*>tr>:last-child,.doc table.frame-none>*>tr>:last-child{border-right-width:0}.doc table.stripes-all>tbody>tr,.doc table.stripes-even>tbody>tr:nth-of-type(2n),.doc table.stripes-hover>tbody>tr:hover,.doc table.stripes-odd>tbody>tr:nth-of-type(odd){background:var(--joy-palette-background-level1, #fafafa)}.doc table.tableblock>tfoot{background:var(--joy-palette-background-surface, #f0f0f0)}.doc .halign-left{text-align:left}.doc .halign-right{text-align:right}.doc .halign-center{text-align:center}.doc .valign-top{vertical-align:top}.doc .valign-bottom{vertical-align:bottom}.doc .valign-middle{vertical-align:middle}.doc .admonitionblock{margin:1.4rem 0 0}.doc .admonitionblock p,.doc .admonitionblock td.content{font-size:.88889rem}.doc .admonitionblock td.content>.title+*,.doc .admonitionblock td.content>:not(.title):first-child{margin-top:0}.doc .admonitionblock td.content pre{font-size:.83333rem}.doc .admonitionblock>table{table-layout:fixed;position:relative;width:100%}.doc .admonitionblock td.content{padding:1rem 1rem .75rem;background:var(--joy-palette-background-level1, #fafafa);width:100%;word-wrap:anywhere}.doc .admonitionblock td.icon{font-size:.83333rem;left:0;line-height:1;padding:0;position:absolute;top:0;-webkit-transform:translate(-.5rem,-50%);transform:translate(-.5rem,-50%)}.doc .admonitionblock td.icon i{-webkit-box-align:center;-ms-flex-align:center;align-items:center;border-radius:.45rem;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-filter:initial;filter:none;height:1.25rem;padding:0 .5rem;vertical-align:initial;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}.doc .admonitionblock td.icon i:after{content:attr(title);font-weight:600;font-style:normal;text-transform:uppercase}.doc .admonitionblock td.icon i.icon-caution{background-color:#a0439c;color:#fff}.doc .admonitionblock td.icon i.icon-important{background-color:#d32f2f;color:#fff}.doc .admonitionblock td.icon i.icon-note{background-color:#217ee7;color:#fff}.doc .admonitionblock td.icon i.icon-tip{background-color:#41af46;color:#fff}.doc .admonitionblock td.icon i.icon-warning{background-color:#e18114;color:#fff}.doc .imageblock,.doc .videoblock{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.doc .imageblock .content{-ms-flex-item-align:stretch;align-self:stretch;text-align:center}.doc .imageblock.text-left,.doc .videoblock.text-left{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.doc .imageblock.text-left .content{text-align:left}.doc .imageblock.text-right,.doc .videoblock.text-right{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.doc .imageblock.text-right .content{text-align:right}.doc .image>img,.doc .image>object,.doc .image>svg,.doc .imageblock img,.doc .imageblock object,.doc .imageblock svg{display:inline-block;max-height:75vh;max-width:100%;vertical-align:middle}.doc .image:not(.left):not(.right)>img{margin-top:-.2em}.doc .videoblock iframe,.doc .videoblock video{max-width:100%;vertical-align:middle}#preamble .abstract blockquote{background:var(--joy-palette-background-surface, #f0f0f0);border-left:5px solid var(--joy-palette-divider, #e1e1e1);color:var(--joy-palette-text-secondary, #4a4a4a);font-size:.88889rem;padding:.75em 1em}.doc .quoteblock,.doc .verseblock{background:var(--joy-palette-background-level1, #fafafa);border-left:5px solid var(--joy-palette-neutral-500, #5d5d5d);color:var(--joy-palette-text-secondary, #5d5d5d)}.doc .quoteblock{padding:.25rem 2rem 1.25rem}.doc .quoteblock .attribution{color:var(--joy-palette-text-tertiary, #8e8e8e);font-size:.83333rem;margin-top:.75rem}.doc .quoteblock blockquote{margin-top:1rem}.doc .quoteblock .paragraph{font-style:italic}.doc .quoteblock cite{padding-left:1em}.doc .verseblock{font-size:1.15em;padding:1rem 2rem}.doc .verseblock pre{font-family:inherit;font-size:inherit}.doc ol,.doc ul{margin:0;padding:0 0 0 2rem}.doc ol.none,.doc ol.unnumbered,.doc ol.unstyled,.doc ul.checklist,.doc ul.no-bullet,.doc ul.none,.doc ul.unstyled{list-style-type:none}.doc ol.unnumbered,.doc ul.no-bullet{padding-left:1.25rem}.doc ol.unstyled,.doc ul.unstyled{padding-left:0}.doc ul.circle{list-style-type:circle}.doc ul.disc{list-style-type:disc}.doc ul.square{list-style-type:square}.doc ul.circle ul:not([class]),.doc ul.disc ul:not([class]),.doc ul.square ul:not([class]){list-style:inherit}.doc ol.arabic{list-style-type:decimal}.doc ol.decimal{list-style-type:decimal-leading-zero}.doc ol.loweralpha{list-style-type:lower-alpha}.doc ol.upperalpha{list-style-type:upper-alpha}.doc ol.lowerroman{list-style-type:lower-roman}.doc ol.upperroman{list-style-type:upper-roman}.doc ol.lowergreek{list-style-type:lower-greek}.doc ul.checklist{padding-left:1.75rem}.doc .dlist .dlist,.doc .dlist .olist,.doc .dlist .ulist,.doc .olist .dlist,.doc .olist .olist,.doc .olist .ulist,.doc .olist li+li,.doc .ulist .dlist,.doc .ulist .olist,.doc .ulist .ulist,.doc .ulist li+li{margin-top:.5rem}.doc .admonitionblock .listingblock,.doc .olist .listingblock,.doc .ulist .listingblock{padding:0}.doc .admonitionblock .title,.doc .exampleblock .title,.doc .imageblock .title,.doc .listingblock .title,.doc .literalblock .title,.doc .openblock .title,.doc .videoblock .title,.doc table.tableblock caption{color:var(--joy-palette-text-secondary, #5d5d5d);font-size:.88889rem;font-style:italic;font-weight:600;-ms-hyphens:none;hyphens:none;letter-spacing:.01em;padding-bottom:.075rem}.doc table.tableblock caption{text-align:left}.doc .olist .title,.doc .ulist .title{font-style:italic;font-weight:600;margin-bottom:.25rem}.doc .imageblock .title,.doc .videoblock .title{margin-top:.5rem;padding-bottom:0}.doc details{margin-left:1rem}.doc details>summary{display:block;position:relative;line-height:1.6;margin-bottom:.5rem}.doc details>summary::-webkit-details-marker{display:none}.doc details>summary:before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1rem;-webkit-transform:translateX(15%);transform:translate(15%)}.doc details[open]>summary:before{border-color:currentColor transparent transparent;border-width:.5rem .3rem 0;-webkit-transform:translateY(15%);transform:translateY(15%)}.doc details>summary:after{content:"";width:1rem;height:1em;position:absolute;top:.3em;left:-1rem}.doc details.result{margin-top:.25rem}.doc details.result>summary{color:var(--joy-palette-text-secondary, #5d5d5d);font-style:italic;margin-bottom:0}.doc details.result>.content{margin-left:-1rem}.doc .exampleblock>.content,.doc details.result>.content{background:var(--joy-palette-background-body, #fff);border:.25rem solid var(--joy-palette-neutral-500, #5d5d5d);border-radius:.5rem;padding:.75rem}.doc .exampleblock>.content:after,.doc details.result>.content:after{content:"";display:table;clear:both}.doc .exampleblock>.content>:first-child,.doc details>.content>:first-child{margin-top:0}.doc .sidebarblock{background:var(--joy-palette-neutral-softBg, #e1e1e1);border-radius:.75rem;padding:.75rem 1.5rem}.doc .sidebarblock>.content>.title{font-size:1.25rem;font-weight:600;line-height:1.3;margin-bottom:.5rem;text-align:center}.doc .sidebarblock>.content>.title+*,.doc .sidebarblock>.content>:not(.title):first-child{margin-top:0}.doc .listingblock.wrap pre,.doc table.tableblock pre{white-space:pre-wrap}.doc .listingblock pre:not(.highlight),.doc .literalblock pre,.doc pre.highlight>code{background:var(--joy-palette-background-level1, #fafafa);-webkit-box-shadow:inset 0 0 1.75px var(--joy-palette-divider, #e1e1e1);box-shadow:inset 0 0 1.75px var(--joy-palette-divider, #e1e1e1);display:block;overflow-x:auto;padding:.875em}.doc .listingblock>.content{position:relative}.doc .source-toolbox{display:-webkit-box;display:-ms-flexbox;display:flex;visibility:hidden;position:absolute;top:.25rem;right:.5rem;color:gray;font-family:Roboto,sans-serif;font-size:.72222rem;line-height:1;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;white-space:nowrap;z-index:1}.doc .listingblock:hover .source-toolbox{visibility:visible}.doc .source-toolbox .source-lang{text-transform:uppercase;letter-spacing:.075em}.doc .source-toolbox>:not(:last-child):after{content:"|";letter-spacing:0;padding:0 1ch}.doc .source-toolbox .copy-button{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center;background:none;border:none;color:inherit;outline:none;padding:0;font-size:inherit;line-height:inherit;width:1em;height:1em}.doc .source-toolbox .copy-icon{-webkit-box-flex:0;-ms-flex:none;flex:none;width:inherit;height:inherit}.doc .source-toolbox img.copy-icon{-webkit-filter:invert(50.2%);filter:invert(50.2%)}.doc .source-toolbox svg.copy-icon{fill:currentColor}.doc .source-toolbox .copy-toast{-webkit-box-flex:0;-ms-flex:none;flex:none;position:relative;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;margin-top:1em;background-color:var(--joy-palette-neutral-800, #333);border-radius:.25em;padding:.5em;color:var(--joy-palette-common-white, #fff);cursor:auto;opacity:0;-webkit-transition:opacity .5s ease .5s;transition:opacity .5s ease .5s}.doc .source-toolbox .copy-toast:after{content:"";position:absolute;top:0;width:1em;height:1em;border:.55em solid transparent;border-left-color:var(--joy-palette-neutral-800, #333);-webkit-transform:rotate(-90deg) translateX(50%) translateY(50%);transform:rotate(-90deg) translate(50%) translateY(50%);-webkit-transform-origin:left;transform-origin:left}.doc .source-toolbox .copy-button.clicked .copy-toast{opacity:1;-webkit-transition:none;transition:none}.doc .language-console .hljs-meta{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.doc .dlist dt{font-style:italic}.doc .dlist dd{margin:0 0 0 1.5rem}.doc .dlist dd+dt,.doc .dlist dd>p:first-child{margin-top:.5rem}.doc td.hdlist1,.doc td.hdlist2{padding:.5rem 0 0;vertical-align:top}.doc tr:first-child>.hdlist1,.doc tr:first-child>.hdlist2{padding-top:0}.doc td.hdlist1{font-weight:600;padding-right:.25rem}.doc td.hdlist2{padding-left:.25rem}.doc .colist{font-size:.88889rem;margin:.25rem 0 -.25rem}.doc .colist>table>tbody>tr>:first-child,.doc .colist>table>tr>:first-child{padding:.25em .5rem 0;vertical-align:top}.doc .colist>table>tbody>tr>:last-child,.doc .colist>table>tr>:last-child{padding:.25rem 0}.doc .conum[data-value]{border:1px solid;border-radius:100%;display:inline-block;font-family:Roboto,sans-serif;font-size:.75rem;font-style:normal;line-height:1.2;text-align:center;width:1.25em;height:1.25em;letter-spacing:-.25ex;text-indent:-.25ex}.doc .conum[data-value]:after{content:attr(data-value)}.doc .conum[data-value]+b{display:none}.doc hr{border:solid var(--joy-palette-divider, #e1e1e1);border-width:2px 0 0;height:0}.doc b.button{white-space:nowrap}.doc b.button:before{content:"[";padding-right:.25em}.doc b.button:after{content:"]";padding-left:.25em}.doc kbd{display:inline-block;font-size:.66667rem;background:var(--joy-palette-background-level1, #fafafa);border:1px solid var(--joy-palette-neutral-400, #c1c1c1);border-radius:.25em;-webkit-box-shadow:0 1px 0 var(--joy-palette-neutral-400, #c1c1c1),0 0 0 .1em var(--joy-palette-background-body, #fff) inset;box-shadow:0 1px 0 var(--joy-palette-neutral-400, #c1c1c1),inset 0 0 0 .1em var(--joy-palette-background-body, #fff);padding:.25em .5em;vertical-align:text-bottom;white-space:nowrap}.doc .keyseq,.doc kbd{line-height:1}.doc .keyseq{font-size:.88889rem}.doc .keyseq kbd{margin:0 .125em}.doc .keyseq kbd:first-child{margin-left:0}.doc .keyseq kbd:last-child{margin-right:0}.doc .menuseq,.doc .path{-ms-hyphens:none;hyphens:none}.doc .menuseq i.caret:before{content:"›";font-size:1.1em;font-weight:600;line-height:.90909}.doc :not(pre).nowrap{white-space:nowrap}.doc .nobreak{-ms-hyphens:none;hyphens:none;word-wrap:normal}.doc :not(pre).pre-wrap{white-space:pre-wrap}#footnotes{font-size:.85em;line-height:1.5;margin:2rem -.5rem 0}.doc td.tableblock>.content #footnotes{margin:2rem 0 0}#footnotes hr{border-top-width:1px;margin-top:0;width:20%}#footnotes .footnote{margin:.5em 0 0 1em}#footnotes .footnote+.footnote{margin-top:.25em}#footnotes .footnote>a:first-of-type{display:inline-block;margin-left:-2em;text-align:right;width:1.5em}.toc-menu{color:var(--joy-palette-text-secondary, #5d5d5d)}.toc.sidebar .toc-menu{margin-right:.75rem;position:sticky;top:6rem}.toc .toc-menu h3{color:var(--joy-palette-text-primary, #333);font-size:.88889rem;font-weight:600;line-height:1.3;margin:0 -.5px;padding-bottom:.25rem}.toc.sidebar .toc-menu h3{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;height:2.5rem;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.toc .toc-menu ul{font-size:.83333rem;line-height:1.2;list-style:none;margin:0;padding:0}.toc.sidebar .toc-menu ul{max-height:calc(100vh - 8.5rem);overflow-y:auto;-ms-scroll-chaining:none;overscroll-behavior:none}@supports (scrollbar-width: none){.toc.sidebar .toc-menu ul{scrollbar-width:none}}.toc .toc-menu ul::-webkit-scrollbar{width:0;height:0}@media screen and (min-width:1024px){.toc .toc-menu h3{font-size:.83333rem}.toc .toc-menu ul{font-size:.75rem}}.toc .toc-menu li{margin:0}.toc .toc-menu li[data-level="2"] a{padding-left:1.25rem}.toc .toc-menu li[data-level="3"] a{padding-left:2rem}.toc .toc-menu a{color:inherit;border-left:2px solid var(--joy-palette-divider, #e1e1e1);display:inline-block;padding:.25rem 0 .25rem .5rem;text-decoration:none}.sidebar.toc .toc-menu a{display:block;outline:none}.toc .toc-menu a:hover{color:var(--joy-palette-primary-600, #1565c0)}.toc .toc-menu a.is-active{border-left-color:var(--joy-palette-primary-500, #2a7ee4);color:var(--joy-palette-primary-500, #2a7ee4)}.sidebar.toc .toc-menu a:focus{background:var(--joy-palette-background-level1, #fafafa)}.toc .toc-menu .is-hidden-toc{display:none!important}.contentSidebar{min-width:160px}.doc .videoblock .content{position:relative;width:100%;max-width:100%}.doc .videoblock .content:before{content:"";display:block;padding-top:56.25%}.doc .videoblock .content>iframe,.doc .videoblock .content>video{position:absolute;inset:0;width:100%!important;height:100%!important;border:0;background:#000;display:block}