@aigne/doc-smith 0.8.3 → 0.8.5
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/.aigne/doc-smith/config.yaml +3 -3
- package/.aigne/doc-smith/preferences.yml +58 -12
- package/.aigne/doc-smith/upload-cache.yaml +600 -207
- package/CHANGELOG.md +22 -0
- package/README.md +77 -5
- package/agents/input-generator.mjs +12 -6
- package/agents/publish-docs.mjs +53 -4
- package/docs/_sidebar.md +1 -1
- package/docs/advanced-how-it-works.md +55 -60
- package/docs/advanced-how-it-works.zh.md +60 -65
- package/docs/advanced-quality-assurance.md +73 -38
- package/docs/advanced-quality-assurance.zh.md +73 -38
- package/docs/advanced.md +2 -14
- package/docs/advanced.zh.md +5 -17
- package/docs/changelog.md +41 -4
- package/docs/changelog.zh.md +77 -40
- package/docs/cli-reference.md +79 -13
- package/docs/cli-reference.zh.md +92 -26
- package/docs/configuration-interactive-setup.md +102 -49
- package/docs/configuration-interactive-setup.zh.md +102 -49
- package/docs/configuration-language-support.md +69 -39
- package/docs/configuration-language-support.zh.md +68 -38
- package/docs/configuration-llm-setup.md +25 -62
- package/docs/configuration-llm-setup.zh.md +25 -62
- package/docs/configuration-preferences.md +79 -67
- package/docs/configuration-preferences.zh.md +78 -67
- package/docs/configuration.md +122 -109
- package/docs/configuration.zh.md +130 -117
- package/docs/features-generate-documentation.md +44 -24
- package/docs/features-generate-documentation.zh.md +52 -32
- package/docs/features-publish-your-docs.md +41 -40
- package/docs/features-publish-your-docs.zh.md +50 -49
- package/docs/features-translate-documentation.md +73 -17
- package/docs/features-translate-documentation.zh.md +76 -20
- package/docs/features-update-and-refine.md +72 -21
- package/docs/features-update-and-refine.zh.md +80 -29
- package/docs/features.md +24 -28
- package/docs/features.zh.md +25 -29
- package/docs/getting-started.md +87 -38
- package/docs/getting-started.zh.md +88 -39
- package/docs/overview.md +17 -35
- package/docs/overview.zh.md +18 -36
- package/package.json +9 -8
- package/prompts/content-detail-generator.md +1 -0
- package/prompts/document/custom-code-block.md +101 -0
- package/prompts/document/d2-chart/rules.md +941 -1031
- package/prompts/document/detail-generator.md +7 -53
- package/tests/input-generator.test.mjs +2 -2
- package/tests/kroki-utils.test.mjs +88 -17
- package/utils/auth-utils.mjs +9 -2
- package/utils/blocklet.mjs +25 -6
- package/utils/constants.mjs +17 -1
- package/utils/deploy.mjs +404 -0
- package/utils/kroki-utils.mjs +22 -14
- package/utils/markdown-checker.mjs +1 -1
- package/utils/utils.mjs +3 -2
- package/prompts/document/d2-chart/diy-examples.md +0 -44
- package/prompts/document/d2-chart/shape-rules.md +0 -182
|
@@ -1,1088 +1,998 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
```
|
|
12
|
-
- good
|
|
13
|
-
```d2
|
|
14
|
-
"TokenService": {
|
|
15
|
-
label: "TokenService"
|
|
16
|
-
shape: class
|
|
17
|
-
}
|
|
18
|
-
```
|
|
19
|
-
- 连线上的文字描述,尽量简洁明了,一般来说只需要使用单个词或两个词即可
|
|
20
|
-
- bad:
|
|
21
|
-
```d2
|
|
22
|
-
"User Login" -> "Session Creation": "User submits login form with credentials"
|
|
23
|
-
```
|
|
24
|
-
- good:
|
|
25
|
-
```d2
|
|
26
|
-
"User Login" -> "Session Creation": "login"
|
|
27
|
-
```
|
|
28
|
-
- d2 代码块必须完整且可渲染,避免使用未闭合的语法与奇异字符,避免语法错误
|
|
29
|
-
- 确保每一个节点都有 label 属性,用来表达节点的名称
|
|
30
|
-
- 如果节点的 label 过长,则应该使用 `\n` 来进行换行
|
|
31
|
-
- bad
|
|
32
|
-
```d2
|
|
33
|
-
"AuthService": {
|
|
34
|
-
label: "AuthService (Handles user authentication, profile management, privacy settings, and related actions)"
|
|
35
|
-
shape: class
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
|
-
- good
|
|
39
|
-
```d2
|
|
40
|
-
"AuthService": {
|
|
41
|
-
label: "AuthService\n(Handles user authentication,\nprofile management, privacy settings,\nand related actions)"
|
|
42
|
-
shape: class
|
|
43
|
-
}
|
|
44
|
-
```
|
|
45
|
-
- **非常重要** 如果节点的名称包含了特殊字符(如 `@`、` `、`/`, 空格等),请将名称中的特殊字符转换为 `-`,然后使用 label 来表达原始的名称,确保节点的名称一定不要使用 `"` 包裹
|
|
46
|
-
- bad:
|
|
47
|
-
```d2
|
|
48
|
-
"@blocklet/js-sdk": {
|
|
49
|
-
shape: package
|
|
50
|
-
|
|
51
|
-
TokenService: {
|
|
52
|
-
shape: class
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
```
|
|
56
|
-
- good:
|
|
57
|
-
```d2
|
|
58
|
-
"blocklet-js-sdk": {
|
|
59
|
-
shape: package
|
|
60
|
-
label: "@blocklet/js-sdk
|
|
61
|
-
|
|
62
|
-
TokenService: {
|
|
63
|
-
shape: class
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
```
|
|
67
|
-
- 必须确保每个节点和子节点是有名称的
|
|
68
|
-
- bad:
|
|
69
|
-
```d2
|
|
70
|
-
"SDK Core Instance": {
|
|
71
|
-
shape: package
|
|
72
|
-
"TokenService": "Manages session and refresh tokens"
|
|
73
|
-
"Services": {
|
|
74
|
-
grid-columns: 2
|
|
75
|
-
"AuthService": ""
|
|
76
|
-
"BlockletService": ""
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
```
|
|
80
|
-
- good:
|
|
81
|
-
```d2
|
|
82
|
-
"SDK Core Instance": {
|
|
83
|
-
shape: package
|
|
84
|
-
"TokenService": "Manages session and refresh tokens"
|
|
85
|
-
"Services": {
|
|
86
|
-
grid-columns: 2
|
|
87
|
-
"AuthService": "AuthService"
|
|
88
|
-
"BlockletService": "BlockletService"
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
- 不要为节点添加 `tooltip`,保持简单即可
|
|
93
|
-
- bad
|
|
94
|
-
```d2
|
|
95
|
-
"AuthService": {
|
|
96
|
-
label: "AuthService"
|
|
97
|
-
tooltip: "Manages user profiles, privacy, and authentication actions"
|
|
98
|
-
shape: class
|
|
99
|
-
}
|
|
100
|
-
```
|
|
101
|
-
- good
|
|
102
|
-
```d2
|
|
103
|
-
"AuthService": {
|
|
104
|
-
label: "AuthService"
|
|
105
|
-
shape: class
|
|
106
|
-
}
|
|
107
|
-
```
|
|
108
|
-
- 不要随意给节点/连线填充颜色,除非节点/连线有明确的 yes/no 的状态,此时可以添加 `error`, `warning`, `success` 之类的颜色
|
|
109
|
-
- bad
|
|
110
|
-
```d2
|
|
111
|
-
"TokenService" {
|
|
112
|
-
shape: class
|
|
113
|
-
style.fill: "#fffbe6"
|
|
114
|
-
}
|
|
115
|
-
```
|
|
116
|
-
- good
|
|
117
|
-
```d2
|
|
118
|
-
"TokenService" {
|
|
119
|
-
shape: class
|
|
120
|
-
}
|
|
121
|
-
```
|
|
122
|
-
- 对于单个节点和连线,不要使用 `animate: true`,避免有些地方有,但有些地方没有的情况(看起来会很奇怪)
|
|
123
|
-
- 连线的箭头方向必须正确,确保箭头指向关系的下游端
|
|
124
|
-
- 连线的样式,尽量保持一致,不要有些实线,有些虚线的情况,除非有明确的区分意义
|
|
125
|
-
- 页面的整体布局使用 `direction: down`,这样能确保图表适合在网页中进行阅读;子图中可以根据情况来使用其他的方向布局,需要确保图表的整体效果看起来不会太宽
|
|
126
|
-
- bad:
|
|
127
|
-
```d2
|
|
128
|
-
direction: right
|
|
129
|
-
|
|
130
|
-
"online": {
|
|
131
|
-
shape: circle
|
|
132
|
-
style.fill: "#52c41a"
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
"offline": {
|
|
136
|
-
shape: circle
|
|
137
|
-
style.fill: "#faad14"
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
"expired": {
|
|
141
|
-
shape: circle
|
|
142
|
-
style.fill: "#ff4d4f"
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
"New Login" -> "online": "User authenticates"
|
|
146
|
-
"online" -> "offline": "User closes app/browser"
|
|
147
|
-
"online" -> "expired": "Token expires"
|
|
148
|
-
"offline" -> "online": "User returns"
|
|
149
|
-
"offline" -> "expired": "Extended inactivity"
|
|
150
|
-
```
|
|
151
|
-
- good:
|
|
152
|
-
```d2
|
|
153
|
-
direction: down
|
|
154
|
-
|
|
155
|
-
"online": {
|
|
156
|
-
shape: circle
|
|
157
|
-
style.fill: "#52c41a"
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
"offline": {
|
|
161
|
-
shape: circle
|
|
162
|
-
style.fill: "#faad14"
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
"expired": {
|
|
166
|
-
shape: circle
|
|
167
|
-
style.fill: "#ff4d4f"
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
"New Login" -> "online": "User authenticates"
|
|
171
|
-
"online" -> "offline": "User closes app/browser"
|
|
172
|
-
"online" -> "expired": "Token expires"
|
|
173
|
-
"offline" -> "online": "User returns"
|
|
174
|
-
"offline" -> "expired": "Extended inactivity"
|
|
175
|
-
```
|
|
176
|
-
- 如果一个节点中的字节点太多了(超过3个),请使用 `grid-columns` 限制一下单行的列数,`grid-columns` 的值优先使用2,最大不要超过 3,例如
|
|
177
|
-
- good:
|
|
178
|
-
```d2
|
|
179
|
-
"Instance": {
|
|
180
|
-
grid-columns: 3
|
|
181
|
-
"A": "A"
|
|
182
|
-
"B": "B"
|
|
183
|
-
"C": "C"
|
|
184
|
-
"D": "D"
|
|
185
|
-
"E": "E"
|
|
186
|
-
}
|
|
187
|
-
```
|
|
188
|
-
```d2
|
|
189
|
-
"Instance": {
|
|
190
|
-
grid-columns: 2
|
|
191
|
-
"A": "A"
|
|
192
|
-
"B": "B"
|
|
193
|
-
"C": "C"
|
|
194
|
-
"D": "D"
|
|
195
|
-
}
|
|
196
|
-
```
|
|
197
|
-
- 每一个容器节点中,最好设置 `grid-columns`
|
|
198
|
-
- bad:
|
|
199
|
-
```d2
|
|
200
|
-
direction: down
|
|
201
|
-
|
|
202
|
-
"SDK": "@blocklet/js-sdk" {
|
|
203
|
-
shape: package
|
|
204
|
-
|
|
205
|
-
"Core Instance": {
|
|
206
|
-
shape: rectangle
|
|
207
|
-
"BlockletSDK": "Main SDK Class"
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
"Services": {
|
|
211
|
-
grid-columns: 3
|
|
212
|
-
"AuthService": "User Authentication" {
|
|
213
|
-
shape: class
|
|
214
|
-
}
|
|
215
|
-
"TokenService": "Token Management" {
|
|
216
|
-
shape: class
|
|
217
|
-
}
|
|
218
|
-
"UserSessionService": "Session Management" {
|
|
219
|
-
shape: class
|
|
220
|
-
}
|
|
221
|
-
"BlockletService": "Blocklet Metadata" {
|
|
222
|
-
shape: class
|
|
223
|
-
}
|
|
224
|
-
"FederatedService": "Federated Login" {
|
|
225
|
-
shape: class
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
"HTTP Clients": {
|
|
230
|
-
grid-columns: 2
|
|
231
|
-
"createAxios": "Axios-based Client" {
|
|
232
|
-
shape: rectangle
|
|
233
|
-
}
|
|
234
|
-
"createFetch": "Fetch-based Client" {
|
|
235
|
-
shape: rectangle
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
1
|
+
# D2 Diagram Generation Expert Guide
|
|
2
|
+
|
|
3
|
+
## Preamble: LLM Role and Core Objective
|
|
4
|
+
|
|
5
|
+
You are an expert Software Architect and a master of the D2 (Declarative Diagramming) language. Your primary function is to translate abstract descriptions of software systems, components, and processes into precise, readable, and visually effective D2 diagram code.
|
|
6
|
+
|
|
7
|
+
Your core directive is to produce D2 code that is not only syntactically correct but also semantically meaningful and adheres to the highest standards of technical diagramming. The generated output must follow all instructions, constraints, and best practices detailed in this document. You will operate in a zero-tolerance mode for syntactical errors, especially concerning predefined keyword values. The fundamental principle is the separation of concerns: the logical structure of the diagram must be defined independently of its visual styling. The following chapters are structured to enforce this principle.
|
|
8
|
+
|
|
9
|
+
## Chapter 1: Core Instructions for D2 Diagram Generation
|
|
239
10
|
|
|
240
|
-
|
|
241
|
-
shape: rectangle
|
|
242
|
-
}
|
|
11
|
+
This chapter establishes the foundational rules for generating the structure and logic of a D2 diagram. It prioritizes semantic correctness and adherence to diagramming principles over aesthetic concerns, which are addressed in Chapter 2.
|
|
243
12
|
|
|
244
|
-
|
|
245
|
-
shape: cylinder
|
|
246
|
-
}
|
|
13
|
+
### 1.1 Foundational Principles of Technical Diagramming
|
|
247
14
|
|
|
248
|
-
|
|
249
|
-
"SDK" -> "Blocklet Services": "Authenticated Requests"
|
|
250
|
-
"Blocklet Services" -> "SDK": "Responses & Tokens"
|
|
251
|
-
```
|
|
252
|
-
- good:
|
|
253
|
-
```d2
|
|
254
|
-
direction: down
|
|
255
|
-
|
|
256
|
-
"SDK": "@blocklet/js-sdk" {
|
|
257
|
-
shape: package
|
|
258
|
-
grid-columns: 1
|
|
259
|
-
|
|
260
|
-
"Core Instance": {
|
|
261
|
-
shape: rectangle
|
|
262
|
-
"BlockletSDK": "Main SDK Class"
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
"Services": {
|
|
266
|
-
grid-columns: 3
|
|
267
|
-
"AuthService": "User Authentication" {
|
|
268
|
-
shape: class
|
|
269
|
-
}
|
|
270
|
-
"TokenService": "Token Management" {
|
|
271
|
-
shape: class
|
|
272
|
-
}
|
|
273
|
-
"UserSessionService": "Session Management" {
|
|
274
|
-
shape: class
|
|
275
|
-
}
|
|
276
|
-
"BlockletService": "Blocklet Metadata" {
|
|
277
|
-
shape: class
|
|
278
|
-
}
|
|
279
|
-
"FederatedService": "Federated Login" {
|
|
280
|
-
shape: class
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
"HTTP Clients": {
|
|
285
|
-
grid-columns: 2
|
|
286
|
-
"createAxios": "Axios-based Client" {
|
|
287
|
-
shape: rectangle
|
|
288
|
-
}
|
|
289
|
-
"createFetch": "Fetch-based Client" {
|
|
290
|
-
shape: rectangle
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
"Your App": "Application Code" {
|
|
296
|
-
shape: rectangle
|
|
297
|
-
}
|
|
15
|
+
All generated diagrams must adhere to established best practices to ensure they are effective communication tools, not merely decorative images. The primary audience for these diagrams is software engineers who need to understand a system's architecture, data flow, or component interactions.
|
|
298
16
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
17
|
+
#### Clarity and Conciseness
|
|
18
|
+
Use clear, simple language for all labels. Text within shapes should be minimal, ideally one to two words. Avoid lengthy descriptions in labels. For extensive explanations, use an accompanying Markdown text block. The goal is to reduce cognitive load and make the diagram's structure immediately apparent. Long labels should be manually broken with newline characters (`\n`) to ensure they render correctly and do not disrupt the layout.
|
|
302
19
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
20
|
+
- **Bad Practice (Overly descriptive label):**
|
|
21
|
+
```d2
|
|
22
|
+
TokenService: {
|
|
23
|
+
label: "TokenService (Handles token storage & refresh)"
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
- **Good Practice (Concise label):**
|
|
27
|
+
```d2
|
|
28
|
+
TokenService: {
|
|
29
|
+
label: "TokenService"
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- **Bad Practice (Verbose connection label):**
|
|
34
|
+
```d2
|
|
35
|
+
User-Login -> Session-Creation: "User submits login form with credentials"
|
|
36
|
+
```
|
|
37
|
+
- **Good Practice (Concise connection label):**
|
|
38
|
+
```d2
|
|
39
|
+
User-Login -> Session-Creation: "login"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- **Bad Practice (Manual line breaks):**
|
|
43
|
+
```d2
|
|
44
|
+
"AuthService": {
|
|
45
|
+
label: "AuthService (Handles user authentication, profile management, privacy settings, and related actions)"
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
- **Good Practice (Manual line breaks):**
|
|
49
|
+
```d2
|
|
50
|
+
"AuthService": {
|
|
51
|
+
label: "AuthService\n(Handles user authentication,\nprofile management, privacy settings,\nand related actions)"
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
#### Logical Flow and Organization
|
|
56
|
+
The diagram's layout must represent a logical flow, whether of data, control, or time. The visual arrangement of elements should guide the reader's eye naturally through the process or structure being depicted. For optimal readability on web pages, the overall layout direction should be set to `down`.
|
|
57
|
+
|
|
58
|
+
```d2
|
|
59
|
+
direction: down
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Lines should be straight and avoid crossing where possible to maintain readability. All nodes within a single diagram must be interconnected to form a cohesive graph. If unrelated groups of nodes exist, they should be split into separate diagrams.
|
|
63
|
+
|
|
64
|
+
#### Appropriate Diagram Type
|
|
65
|
+
Based on the input description, select the most suitable diagram type. For interactions over time, a sequence diagram is appropriate. For static system structure, a component or class diagram should be used. For process flows, a flowchart-style diagram is best. D2 is specifically designed for documenting software and does not support general-purpose charts like mind maps or Gantt charts; these are considered bloat and must not be generated.
|
|
66
|
+
|
|
67
|
+
#### Consistent Abstraction Level
|
|
68
|
+
Maintain a consistent level of detail throughout a single diagram. Do not mix high-level architectural concepts (e.g., "API Gateway") with low-level implementation details (e.g., a specific function name) unless the input explicitly requires this hybrid view.
|
|
69
|
+
|
|
70
|
+
### 1.2 D2 Core Syntax: The Grammar of Diagrams
|
|
71
|
+
|
|
72
|
+
This section provides the precise and unambiguous definition of D2's fundamental syntax for creating the structural elements of a diagram.
|
|
73
|
+
|
|
74
|
+
#### Shapes and Labels
|
|
75
|
+
|
|
76
|
+
- A shape is defined by its key. By default, the key also serves as the shape's label. Shape keys are case-insensitive. For example, `api_gateway` creates a rectangle shape with the label "api_gateway".
|
|
77
|
+
- To assign a different, case-sensitive label, use a colon: `api_gateway: "API Gateway"`.
|
|
78
|
+
- **Critical Rule**: Node IDs (keys) containing special characters (e.g., `@`, ` `, `/`) must be normalized by replacing these characters with a hyphen (`-`). The original name must then be assigned to the `label` attribute.
|
|
79
|
+
- **Bad Practice:**
|
|
80
|
+
```d2
|
|
81
|
+
"@blocklet/js-sdk": {
|
|
82
|
+
shape: rectangle
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
- **Good Practice:**
|
|
86
|
+
```d2
|
|
87
|
+
blocklet-js-sdk: {
|
|
88
|
+
label: "@blocklet/js-sdk"
|
|
89
|
+
shape: rectangle
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
- Labels containing reserved characters (e.g., `(`, `)`, `$`, `-`, `:`) or spaces must be enclosed in single or double quotes to prevent parsing errors. Example: `"user-service:v1"`.
|
|
93
|
+
- Multiple shapes can be declared on a single line using a semicolon as a delimiter. Example: `service_a; service_b; service_c`.
|
|
315
94
|
|
|
316
|
-
|
|
317
|
-
label: "@blocklet/js-sdk (createAxios / createFetch)"
|
|
318
|
-
shape: package
|
|
319
|
-
}
|
|
95
|
+
#### Connections and Arrowheads
|
|
320
96
|
|
|
321
|
-
|
|
322
|
-
shape: cylinder
|
|
323
|
-
}
|
|
97
|
+
Connections define relationships between shapes. The following syntaxes are valid:
|
|
324
98
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
99
|
+
- `->`: Uni-directional connection
|
|
100
|
+
- `<->`: Bi-directional connection
|
|
101
|
+
- `--`: A connection with no direction specified
|
|
102
|
+
- `<-`: Uni-directional connection (reversed)
|
|
103
|
+
|
|
104
|
+
Labels can be added to connections by appending a colon and the label text. Example: `user -> api: "requests data via HTTPS"`.
|
|
105
|
+
|
|
106
|
+
Custom arrowheads are specified by defining a special shape on the connection named `source-arrowhead` or `target-arrowhead`. This is essential for creating compliant UML or entity-relationship diagrams. Example: `a -> b: { target-arrowhead: { shape: diamond } }`.
|
|
107
|
+
|
|
108
|
+
#### Containers
|
|
109
|
+
|
|
110
|
+
Containers are used to group related shapes, representing subsystems or logical boundaries. They are defined using nested blocks `{}` or dot notation.
|
|
111
|
+
|
|
112
|
+
- **Block Notation**: `aws: { ec2: "EC2 Instance"; s3: "S3 Bucket" }`
|
|
113
|
+
- **Dot Notation**: `aws.ec2: "EC2 Instance"`. This is useful for defining shapes and connections in a single line.
|
|
114
|
+
|
|
115
|
+
Containers can be nested to any depth to represent hierarchical systems. Connections between shapes residing in the same container should be defined within that container's block.
|
|
116
|
+
|
|
117
|
+
Container labels can be assigned using shorthand (`gcloud: "Google Cloud" {}`) or the reserved label keyword (`gcloud: { label: "Google Cloud" }`).
|
|
118
|
+
|
|
119
|
+
When a container has more than three child nodes, use `grid-columns` to limit the number of columns in a single row, preferably to 2 or at most 3, to improve readability. If a container has nested containers, it is recommended to use `grid-gap` (with a value greater than 100) to increase the spacing between them.
|
|
120
|
+
|
|
121
|
+
- **Bad Practice (Grid Layout):**
|
|
122
|
+
```d2
|
|
123
|
+
Instance: {
|
|
124
|
+
A: "A"
|
|
125
|
+
B: "B"
|
|
126
|
+
C: "C"
|
|
127
|
+
D: "D"
|
|
128
|
+
E: "E"
|
|
129
|
+
F: "F"
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
- **Good Practice (Grid Layout):**
|
|
133
|
+
```d2
|
|
134
|
+
Instance: {
|
|
135
|
+
grid-columns: 2
|
|
136
|
+
A: "A"
|
|
137
|
+
B: "B"
|
|
138
|
+
C: "C"
|
|
139
|
+
D: "D"
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
- **Good Practice (Grid Gap for Nested Containers):**
|
|
144
|
+
```d2
|
|
145
|
+
SDK-blocklet-js-sdk: {
|
|
146
|
+
shape: rectangle
|
|
147
|
+
grid-columns: 1
|
|
148
|
+
grid-gap: 100
|
|
149
|
+
// ... nested containers
|
|
150
|
+
}
|
|
151
|
+
```
|
|
329
152
|
|
|
330
|
-
|
|
153
|
+
#### Text and Code Blocks
|
|
331
154
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
stroke-dash: 2
|
|
335
|
-
}
|
|
336
|
-
}
|
|
155
|
+
- For multi-line descriptions or annotations that are part of the diagram, use Markdown blocks. This is initiated with `|md`. Example: `explanation: |md # System Overview \n - Component A does X. \n - Component B does Y. |`.
|
|
156
|
+
- To display formatted code snippets, specify the programming language after the pipe. D2 supports most common languages. Example: `code_sample: |go func main() { fmt.Println("Hello") } |`.
|
|
337
157
|
|
|
338
|
-
|
|
339
|
-
style.stroke: "#52c41a"
|
|
158
|
+
### 1.3 Specialized Diagram Constructs
|
|
340
159
|
|
|
341
|
-
|
|
342
|
-
"SDK Request Helper" -> "Your App": "4a. Returns Data"
|
|
343
|
-
}
|
|
160
|
+
D2 provides special syntax for creating complex, structured diagram types commonly used in software documentation.
|
|
344
161
|
|
|
162
|
+
#### Sequence Diagrams
|
|
345
163
|
|
|
346
|
-
|
|
347
|
-
|
|
164
|
+
- A sequence diagram is created by setting `shape: sequence_diagram` on a container.
|
|
165
|
+
- **Critical Rule**: The order of statements within the `sequence_diagram` block is paramount. Unlike other D2 diagrams where layout is algorithmic, here the vertical order of messages is determined by their declaration order in the source code.
|
|
166
|
+
- Actors are defined like regular shapes (e.g., `alice: "Alice"`). Messages are represented as connections between actors.
|
|
167
|
+
- Lifeline activations, also known as spans, are defined by connecting to a nested object on an actor. This syntax indicates the start and end of an operation on an actor's lifeline. Example: `alice.t1 -> bob: "invoke operation"`.
|
|
168
|
+
- Groups (fragments) like loops or optional blocks are defined using nested containers that are not connected to anything. Example: `loop: { alice -> bob: "ping"; bob -> alice: "pong" }`.
|
|
348
169
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
170
|
+
#### UML Class Diagrams
|
|
171
|
+
|
|
172
|
+
- A class diagram is created by setting `shape: class` on a shape.
|
|
173
|
+
- Fields and methods are defined as key-value pairs within the shape's block.
|
|
174
|
+
- Visibility is specified with a prefix: `+` for public (this is the default), `-` for private, and `#` for protected.
|
|
175
|
+
- Methods are identified by keys containing parentheses `()`. The value of the key specifies the return type. Example: `D2Parser: { shape: class; +reader: io.RuneReader; "-lookahead:rune"; "+peek(): (rune, eof bool)" }`.
|
|
176
|
+
|
|
177
|
+
#### SQL Table Diagrams
|
|
178
|
+
|
|
179
|
+
- An SQL table is created by setting `shape: sql_table`.
|
|
180
|
+
- Columns are defined as keys, with their data type as the value.
|
|
181
|
+
- Constraints (e.g., `primary_key`, `foreign_key`, `unique`) are defined in a nested block for the relevant column. Example: `users: { shape: sql_table; id: int { constraint: primary_key }; email: string { constraint: unique } }`.
|
|
182
|
+
- Foreign key relationships are established by creating a standard connection from the foreign key column in one table to the primary key column in another. Example: `orders.user_id -> users.id`.
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
### 1.4 Strict Adherence to Predefined Keyword Values
|
|
186
|
+
|
|
187
|
+
Many D2 keywords accept only a specific, predefined set of string values. These function like enumerations in a programming language. LLMs often make mistakes by generating plausible but invalid values for these keywords. To prevent this, the following rules are non-negotiable.
|
|
188
|
+
|
|
189
|
+
**Core Directive**: For any D2 keyword listed in the tables below, you MUST use one of the provided values EXACTLY as written. The values are case-sensitive. You are FORBIDDEN from inventing, assuming, or modifying these values in any way. This is a critical instruction to prevent compilation errors.
|
|
190
|
+
|
|
191
|
+
#### D2 Shape Catalog
|
|
192
|
+
|
|
193
|
+
The `shape` attribute must be assigned one of the following values:
|
|
194
|
+
|
|
195
|
+
| Shape Value | Description |
|
|
196
|
+
|-------------|-------------|
|
|
197
|
+
| `rectangle` | The default shape. A standard rectangle. |
|
|
198
|
+
| `square` | A shape that maintains a 1:1 aspect ratio. |
|
|
199
|
+
| `cylinder` | A cylinder, typically representing a database or data store. |
|
|
200
|
+
| `queue` | A shape representing a message queue. |
|
|
201
|
+
| `diamond` | A diamond shape, often used for decisions in flowcharts. |
|
|
202
|
+
| `oval` | An oval or ellipse, often used for start/end terminators. |
|
|
203
|
+
| `circle` | A perfect circle, maintains a 1:1 aspect ratio. |
|
|
204
|
+
| `c4-person` | A more detailed person icon based on the C4 model. |
|
|
205
|
+
| `sequence_diagram` | A special container shape for rendering sequence diagrams. |
|
|
206
|
+
|
|
207
|
+
- If person shape is needed, use `c4-person` replace `person`.
|
|
208
|
+
- **Forbidden Attributes**: Do not use any other shape, like: `package`, `step`, `callout`, `stored_data`, `person`, `document`, `multiple_document`, `class`, `sql_table`, `image`, `hexagon`, `parallelogram`. These shapes are either deprecated, not suitable for software diagrams, or have been replaced by more appropriate alternatives.
|
|
209
|
+
|
|
210
|
+
#### Predefined Keyword Values (Master Reference)
|
|
211
|
+
|
|
212
|
+
This table centralizes all other keywords with a restricted set of valid values.
|
|
213
|
+
|
|
214
|
+
| Keyword | Valid Values |
|
|
215
|
+
|---------|--------------|
|
|
216
|
+
| `direction` | `up`, `down`, `left`, `right` |
|
|
217
|
+
| `style.fill-pattern` | `dots`, `lines`, `grain`, `none` |
|
|
218
|
+
| `style.text-transform` | `uppercase`, `lowercase`, `title`, `none` |
|
|
219
|
+
| `style.font` | `mono` |
|
|
220
|
+
| UML Visibility | `+` (public), `-` (private), `#` (protected) |
|
|
221
|
+
| Arrowhead shape | `triangle`, `arrow`, `diamond`, `circle`, `box`, `cf-one`, `cf-one-required`, `cf-many`, `cf-many-required`, `cross`, `unfilled triangle` |
|
|
222
|
+
|
|
223
|
+
### 1.5 Known Limitations and Error Handling
|
|
224
|
+
|
|
225
|
+
To generate robust D2 code, be aware of the language's limitations and common sources of errors.
|
|
226
|
+
|
|
227
|
+
- **Quoting and Escaping**: Always enclose keys or labels that are reserved D2 keywords in quotes. Example: `shape_A: { "label": "My Label" }`. If a string must contain a `#` character (which normally signifies a comment), it must be quoted.
|
|
228
|
+
- **Non-ASCII Characters**: While D2 supports Unicode, care must be taken to use ASCII versions of special characters like the colon (`:`) for defining labels, as visually similar Unicode characters will not be parsed correctly.
|
|
229
|
+
- **Styling**: Use colors and styles (like `style.fill`) sparingly. Should apply them to represent specific states (e.g., success, warning, error), should apply theme to diffrent purpose shape (Use colors with less contrast, not too prominent), not for arbitrary decoration.
|
|
230
|
+
- **Forbidden Attributes**: Do not use the `tooltip` attribute. Interactive features should be handled by accompanying text. Similarly, do not use `animate: true` for individual shapes or connections.
|
|
231
|
+
- **Bad Practice:**
|
|
232
|
+
```d2
|
|
233
|
+
"AuthService": {
|
|
234
|
+
label: "AuthService"
|
|
235
|
+
tooltip: "Manages user profiles, privacy, and authentication actions"
|
|
236
|
+
animate: true
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
- **Good Practice:**
|
|
240
|
+
```d2
|
|
241
|
+
AuthService: {
|
|
242
|
+
label: "AuthService"
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### 1.6 Shape Usage Best Practices
|
|
247
|
+
|
|
248
|
+
Selecting the correct shape is crucial for conveying the intended meaning of a component at a glance. Use the following guidelines to choose the most appropriate shape for different elements in your software diagrams.
|
|
364
249
|
|
|
365
|
-
|
|
366
|
-
shape: rectangle
|
|
367
|
-
}
|
|
250
|
+
#### General Purpose & Processes
|
|
368
251
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
252
|
+
- **rectangle**: The default shape, suitable for any general-purpose component, service, or action step
|
|
253
|
+
- **oval**: Traditionally used to indicate the start or end points of a process or flowchart
|
|
254
|
+
- **diamond**: Reserved for decision points in flowcharts, where a process diverges based on a condition
|
|
255
|
+
|
|
256
|
+
#### Data and Storage
|
|
373
257
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
258
|
+
- **cylinder**: The standard shape for representing a database or data store
|
|
259
|
+
- **queue**: Specifically used to represent a message queue
|
|
260
|
+
|
|
261
|
+
#### Actors and Documents
|
|
262
|
+
|
|
263
|
+
- **c4-person**: Represents a human user or an external actor interacting with the system.
|
|
264
|
+
- **image**: Used for embedding logos or icons, especially when representing third-party services or well-known platforms. When using this shape, ensure the `icon` attribute points to a valid image URL.
|
|
265
|
+
The following logos are predefined for common services:
|
|
266
|
+
- "NFT Studio": `https://www.arcblock.io/image-bin/uploads/5542354d3d33e5fcd211562ee734da01.png`
|
|
267
|
+
- "AIGNE": `https://www.arcblock.io/image-bin/uploads/89a24f04c34eca94f26c9dd30aec44fc.png`
|
|
268
|
+
- "Aistro": `https://www.arcblock.io/image-bin/uploads/b83c22b9f92e4837c2e21bb6fb831f1a.png`
|
|
269
|
+
|
|
270
|
+
- "Blocklet Launcher": `https://www.arcblock.io/image-bin/uploads/6cb1e2aa568509e1a874bf7ae1666c26.svg`
|
|
271
|
+
- "Blocklet Store": `https://store.blocklet.dev/assets/z8ia29UsENBg6tLZUKi2HABj38Cw1LmHZocbQ/logo.png`
|
|
272
|
+
- "Web3 Kit": `https://www.arcblock.io/image-bin/uploads/f409e3bdc7a2b42ba8fba9bae286aeda.svg`
|
|
273
|
+
- "AI Kit": `https://www.arcblock.io/image-bin/uploads/9745710dce319d9bf117516ad5d1f811.svg`
|
|
274
|
+
|
|
275
|
+
- "Blocklet": `https://www.arcblock.io/image-bin/uploads/eb1cf5d60cd85c42362920c49e3768cb.svg`
|
|
276
|
+
- "Blocklet Server": `https://www.arcblock.io/image-bin/uploads/eb1cf5d60cd85c42362920c49e3768cb.svg`
|
|
277
|
+
- "DID Spaces": `https://www.arcblock.io/image-bin/uploads/fb3d25d6fcd3f35c5431782a35bef879.svg`
|
|
278
|
+
|
|
279
|
+
- "DID": `https://www.arcblock.io/image-bin/uploads/71eea946246150766324008427d2f63d.svg`
|
|
280
|
+
- "DID Wallet": `https://www.arcblock.io/image-bin/uploads/37198ddc4a0b9e91e5c1c821ab895a34.svg`
|
|
281
|
+
- "DID Connect": `https://www.arcblock.io/image-bin/uploads/71eea946246150766324008427d2f63d.svg`
|
|
282
|
+
- "DID Names": `https://www.arcblock.io/image-bin/uploads/db36f9832a99d4dccb21a30ff269bb22.svg`
|
|
283
|
+
|
|
284
|
+
- **Bad Practice:**
|
|
285
|
+
```d2
|
|
286
|
+
DID-Wallet: {
|
|
287
|
+
shape: image
|
|
288
|
+
icon: https://www.arcblock.io/image-bin/uploads/37198ddc4a0b9e91e5c1c821ab895a34.svg
|
|
289
|
+
}
|
|
290
|
+
Blocklet-Store: {
|
|
291
|
+
shape: image
|
|
292
|
+
icon: https://store.blocklet.dev/assets/z8ia29UsENBg6tLZUKi2HABj38Cw1LmHZocbQ/logo.png
|
|
293
|
+
}
|
|
294
|
+
DID-Wallet -> Blocklet-Store: "Login"
|
|
295
|
+
```
|
|
296
|
+
- **Good Practice:**
|
|
297
|
+
```d2
|
|
298
|
+
DID-Wallet: {
|
|
299
|
+
label: "DID Wallet"
|
|
300
|
+
icon: https://www.arcblock.io/image-bin/uploads/37198ddc4a0b9e91e5c1c821ab895a34.svg
|
|
301
|
+
}
|
|
302
|
+
Blocklet-Store: {
|
|
303
|
+
label: "Blocklet Store"
|
|
304
|
+
icon: https://store.blocklet.dev/assets/z8ia29UsENBg6tLZUKi2HABj38Cw1LmHZocbQ/logo.png
|
|
305
|
+
}
|
|
306
|
+
DID-Wallet -> Blocklet-Store: "Login"
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
#### Specialized Diagram Types
|
|
310
|
+
|
|
311
|
+
- **sequence_diagram**: These are not general-purpose shapes. They are special containers that render specific, structured diagram types and must be used exclusively for that purpose.
|
|
312
|
+
|
|
313
|
+
## Chapter 2: Best Practices for real case
|
|
314
|
+
|
|
315
|
+
#### Connections between shapes
|
|
316
|
+
|
|
317
|
+
Ensure all connections are defined at the root level of the diagram, not nested within containers. This maintains clarity and prevents layout issues.
|
|
318
|
+
Ensure that the shape names used in connections are accurate and match the actual node identifiers. When connecting shapes nested within containers, always use the full, qualified name (including all parent containers) to reference the target shape correctly.
|
|
319
|
+
|
|
320
|
+
- **Bad Practice:**
|
|
321
|
+
```d2
|
|
322
|
+
direction: down
|
|
323
|
+
|
|
324
|
+
User: {
|
|
325
|
+
shape: c4-person
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
App: {
|
|
329
|
+
label: "Your Blocklet Application"
|
|
330
|
+
shape: rectangle
|
|
331
|
+
|
|
332
|
+
Uploader-Component: {
|
|
333
|
+
label: "Uploader Component"
|
|
334
|
+
shape: rectangle
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
Backend-Server: {
|
|
338
|
+
label: "Backend Server"
|
|
339
|
+
shape: rectangle
|
|
340
|
+
|
|
341
|
+
Uploader-Server: {
|
|
342
|
+
label: "@blocklet/uploader-server"
|
|
343
|
+
}
|
|
377
344
|
|
|
378
|
-
|
|
379
|
-
label: "
|
|
380
|
-
shape: rectangle
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
"Your App" -> "SDK Request Helper": "1. Make API Call (e.g., /api/profile)"
|
|
384
|
-
|
|
385
|
-
"SDK Request Helper" -> "Blocklet Service": "2. Adds Auth Header & Sends Request" {
|
|
386
|
-
style {
|
|
387
|
-
stroke-dash: 2
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
```
|
|
391
|
-
```d2
|
|
392
|
-
"Success Path": {
|
|
393
|
-
style.stroke: "#52c41a"
|
|
394
|
-
|
|
395
|
-
"Blocklet Service" -> "SDK Request Helper": "3a. 200 OK (Token Valid)"
|
|
396
|
-
"SDK Request Helper" -> "Your App": "4a. Returns Data"
|
|
397
|
-
}
|
|
398
|
-
```
|
|
399
|
-
```d2
|
|
400
|
-
"Token Renewal Path": {
|
|
401
|
-
style.stroke: "#faad14"
|
|
402
|
-
|
|
403
|
-
"Blocklet Service" -> "SDK Request Helper": "3b. 401 Unauthorized (Token Expired)"
|
|
404
|
-
"SDK Request Helper" -> "Token Refresh Endpoint": "4b. Request New Token"
|
|
405
|
-
"Token Refresh Endpoint" -> "SDK Request Helper": "5b. New Tokens"
|
|
406
|
-
"SDK Request Helper" -> "Blocklet Service": "6b. Retry Original Request"
|
|
407
|
-
"Blocklet Service" -> "SDK Request Helper": "7b. 200 OK" {
|
|
408
|
-
style.stroke: "#52c41a"
|
|
409
|
-
}
|
|
410
|
-
"SDK Request Helper" -> "Your App": "8b. Returns Data Transparently" {
|
|
411
|
-
style.stroke: "#52c41a"
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
```
|
|
415
|
-
- 当有关联关系的节点,处于一个节点内部时,则它们的关联关系也应该写在节点内部
|
|
416
|
-
- bad
|
|
417
|
-
```d2
|
|
418
|
-
direction: down
|
|
419
|
-
|
|
420
|
-
"@blocklet/js-sdk": {
|
|
421
|
-
shape: package
|
|
422
|
-
|
|
423
|
-
"Main SDK Instance": {
|
|
424
|
-
shape: rectangle
|
|
425
|
-
"BlockletSDK Class": "Main entry point"
|
|
426
|
-
"getBlockletSDK()": "Singleton factory"
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
"HTTP Clients": {
|
|
430
|
-
shape: rectangle
|
|
431
|
-
grid-columns: 2
|
|
432
|
-
"createAxios()": "Axios-based client"
|
|
433
|
-
"createFetch()": "Fetch-based client"
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
"Core Services": {
|
|
437
|
-
shape: rectangle
|
|
438
|
-
grid-columns: 3
|
|
439
|
-
"AuthService": "User authentication"
|
|
440
|
-
"TokenService": "Token management"
|
|
441
|
-
"BlockletService": "Blocklet metadata"
|
|
442
|
-
"UserSessionService": "Session management"
|
|
443
|
-
"FederatedService": "Federated login"
|
|
444
|
-
"ComponentService": "Component utilities"
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
"Main SDK Instance" -> "HTTP Clients": "Uses for requests"
|
|
449
|
-
"Main SDK Instance" -> "Core Services": "Provides access to"
|
|
450
|
-
"HTTP Clients" -> "Core Services": "Configured with"
|
|
451
|
-
```
|
|
452
|
-
- good
|
|
453
|
-
```d2
|
|
454
|
-
direction: down
|
|
455
|
-
|
|
456
|
-
"@blocklet/js-sdk": {
|
|
457
|
-
shape: package
|
|
458
|
-
|
|
459
|
-
"Main SDK Instance": {
|
|
460
|
-
shape: rectangle
|
|
461
|
-
"BlockletSDK Class": "Main entry point"
|
|
462
|
-
"getBlockletSDK()": "Singleton factory"
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
"HTTP Clients": {
|
|
466
|
-
shape: rectangle
|
|
467
|
-
grid-columns: 2
|
|
468
|
-
"createAxios()": "Axios-based client"
|
|
469
|
-
"createFetch()": "Fetch-based client"
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
"Core Services": {
|
|
473
|
-
shape: rectangle
|
|
474
|
-
grid-columns: 3
|
|
475
|
-
"AuthService": "User authentication"
|
|
476
|
-
"TokenService": "Token management"
|
|
477
|
-
"BlockletService": "Blocklet metadata"
|
|
478
|
-
"UserSessionService": "Session management"
|
|
479
|
-
"FederatedService": "Federated login"
|
|
480
|
-
"ComponentService": "Component utilities"
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
"Main SDK Instance" -> "HTTP Clients": "Uses for requests"
|
|
484
|
-
"Main SDK Instance" -> "Core Services": "Provides access to"
|
|
485
|
-
"HTTP Clients" -> "Core Services": "Configured with"
|
|
486
|
-
}
|
|
487
|
-
```
|
|
488
|
-
- bad:
|
|
489
|
-
```d2
|
|
490
|
-
direction: down
|
|
491
|
-
|
|
492
|
-
"Your App": {
|
|
493
|
-
shape: rectangle
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
"SDK Request Helper": {
|
|
497
|
-
label: "@blocklet/js-sdk (createAxios / createFetch)"
|
|
498
|
-
shape: package
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
"Blocklet Service": {
|
|
502
|
-
shape: cylinder
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
"Your App" -> "SDK Request Helper": "1. Make API Call (e.g., /api/profile)"
|
|
507
|
-
|
|
508
|
-
"SDK Request Helper" -> "Blocklet Service": "2. Adds Auth Header & Sends Request" {
|
|
509
|
-
style {
|
|
510
|
-
stroke-dash: 2
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
"Token Renewal Path": {
|
|
515
|
-
style.stroke: "#faad14"
|
|
516
|
-
|
|
517
|
-
"Blocklet Service" -> "SDK Request Helper": "3. 401 Unauthorized (Token Expired)"
|
|
518
|
-
"SDK Request Helper" -> "Token Refresh Endpoint": "4. Request New Token"
|
|
519
|
-
"Token Refresh Endpoint" -> "SDK Request Helper": "5. New Tokens Received"
|
|
520
|
-
"SDK Request Helper" -> "Blocklet Service": "6. Retry Original Request with New Token"
|
|
521
|
-
"Blocklet Service" -> "SDK Request Helper": "7. 200 OK" {
|
|
522
|
-
style.stroke: "#52c41a"
|
|
523
|
-
}
|
|
524
|
-
"SDK Request Helper" -> "Your App": "8. Returns Data Transparently" {
|
|
525
|
-
style.stroke: "#52c41a"
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
```
|
|
529
|
-
- good:
|
|
530
|
-
```d2
|
|
531
|
-
direction: down
|
|
532
|
-
|
|
533
|
-
"Your App": {
|
|
534
|
-
shape: rectangle
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
"SDK Request Helper": {
|
|
538
|
-
label: "@blocklet/js-sdk (createAxios / createFetch)"
|
|
539
|
-
shape: package
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
"Blocklet Service": {
|
|
543
|
-
shape: cylinder
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
"Your App" -> "SDK Request Helper": "1. Make API Call (e.g., /api/profile)"
|
|
548
|
-
|
|
549
|
-
"SDK Request Helper" -> "Blocklet Service": "2. Adds Auth Header & Sends Request" {
|
|
550
|
-
style {
|
|
551
|
-
stroke-dash: 2
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
"Blocklet Service" -> "SDK Request Helper": "3. 401 Unauthorized (Token Expired)"
|
|
556
|
-
"SDK Request Helper" -> "Token Refresh Endpoint": "4. Request New Token"
|
|
557
|
-
"Token Refresh Endpoint" -> "SDK Request Helper": "5. New Tokens Received"
|
|
558
|
-
"SDK Request Helper" -> "Blocklet Service": "6. Retry Original Request with New Token"
|
|
559
|
-
"Blocklet Service" -> "SDK Request Helper": "7. 200 OK" {
|
|
560
|
-
style.stroke: "#52c41a"
|
|
561
|
-
}
|
|
562
|
-
"SDK Request Helper" -> "Your App": "8. Returns Data Transparently" {
|
|
563
|
-
style.stroke: "#52c41a"
|
|
564
|
-
}
|
|
565
|
-
```
|
|
566
|
-
- 如果整个图表只有一个容器节点,就不要增加这个容器节点,直接将内部的节点放在最外层
|
|
567
|
-
- bad:
|
|
568
|
-
```d2
|
|
569
|
-
direction: down
|
|
570
|
-
|
|
571
|
-
"User Sessions Flow": {
|
|
572
|
-
shape: package
|
|
573
|
-
grid-columns: 1
|
|
574
|
-
|
|
575
|
-
"User Login": {
|
|
576
|
-
shape: person
|
|
577
|
-
style.fill: "#e6f7ff"
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
"Session Creation": {
|
|
581
|
-
shape: rectangle
|
|
582
|
-
style.fill: "#f6ffed"
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
"Session Storage": {
|
|
586
|
-
shape: cylinder
|
|
587
|
-
style.fill: "#fff7e6"
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
"Multi-Device Access": {
|
|
591
|
-
shape: package
|
|
592
|
-
grid-columns: 3
|
|
593
|
-
"Web Browser": {
|
|
594
|
-
shape: rectangle
|
|
595
|
-
}
|
|
596
|
-
"Mobile App": {
|
|
597
|
-
shape: rectangle
|
|
598
|
-
}
|
|
599
|
-
"Desktop App": {
|
|
600
|
-
shape: rectangle
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
"User Login" -> "Session Creation": "Authenticate"
|
|
604
|
-
"Session Creation" -> "Session Storage": "Store session"
|
|
605
|
-
"Session Storage" -> "Multi-Device Access": "Access from devices"
|
|
606
|
-
}
|
|
607
|
-
```
|
|
608
|
-
- good:
|
|
609
|
-
```d2
|
|
610
|
-
direction: down
|
|
611
|
-
|
|
612
|
-
"User Login": {
|
|
613
|
-
shape: person
|
|
614
|
-
style.fill: "#e6f7ff"
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
"Session Creation": {
|
|
618
|
-
shape: rectangle
|
|
619
|
-
style.fill: "#f6ffed"
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
"Session Storage": {
|
|
345
|
+
DB: {
|
|
346
|
+
label: "Database"
|
|
623
347
|
shape: cylinder
|
|
624
|
-
style.fill: "#fff7e6"
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
"Multi-Device Access": {
|
|
628
|
-
shape: package
|
|
629
|
-
grid-columns: 3
|
|
630
|
-
"Web Browser": {
|
|
631
|
-
shape: rectangle
|
|
632
|
-
}
|
|
633
|
-
"Mobile App": {
|
|
634
|
-
shape: rectangle
|
|
635
|
-
}
|
|
636
|
-
"Desktop App": {
|
|
637
|
-
shape: rectangle
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
"User Login" -> "Session Creation": "Authenticate"
|
|
641
|
-
"Session Creation" -> "Session Storage": "Store session"
|
|
642
|
-
"Session Storage" -> "Multi-Device Access": "Access from devices"
|
|
643
|
-
```
|
|
644
|
-
- 某些情况下,单纯的设置 `direction: down` 还无法控制图表的整体方向,可以再结合 `grid-columns: 1` 来进行设置
|
|
645
|
-
- bad:
|
|
646
|
-
```d2
|
|
647
|
-
direction: down
|
|
648
|
-
|
|
649
|
-
"Your Application": {
|
|
650
|
-
shape: rectangle
|
|
651
348
|
}
|
|
652
349
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
350
|
+
Uploader-Server -> DB: "4. Save metadata"
|
|
351
|
+
DB -> Uploader-Server: "5. Return DB record"
|
|
352
|
+
}
|
|
656
353
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
354
|
+
User -> Uploader-Component: "1. Drop file"
|
|
355
|
+
Uploader-Component -> Backend-Server.Uploader-Server: "2. Upload file chunks (Tus)"
|
|
356
|
+
Backend-Server.Uploader-Server -> Backend-Server.Uploader-Server: "3. Trigger backend onUploadFinish"
|
|
357
|
+
Backend-Server.Uploader-Server -> Uploader-Component: "6. Send JSON response"
|
|
358
|
+
Uploader-Component -> Uploader-Component: "7. Trigger frontend onUploadFinish"
|
|
359
|
+
Uploader-Component -> User: "8. Update UI with file URL"
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
- **Good Practice:**
|
|
363
|
+
```d2
|
|
364
|
+
direction: down
|
|
661
365
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
"/api/user/profile": {}
|
|
666
|
-
"/api/user/privacy/config": {}
|
|
667
|
-
"/api/user/notification/config": {}
|
|
668
|
-
"/api/user/logout": {}
|
|
669
|
-
"/api/user/follow/{did}": {}
|
|
670
|
-
"/api/user": {}
|
|
671
|
-
}
|
|
366
|
+
User: {
|
|
367
|
+
shape: c4-person
|
|
368
|
+
}
|
|
672
369
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
- good:
|
|
677
|
-
```d2
|
|
678
|
-
direction: down
|
|
679
|
-
grid-columns: 1
|
|
370
|
+
App: {
|
|
371
|
+
label: "Your Blocklet Application"
|
|
372
|
+
shape: rectangle
|
|
680
373
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
374
|
+
Uploader-Component: {
|
|
375
|
+
label: "Uploader Component"
|
|
376
|
+
shape: rectangle
|
|
377
|
+
}
|
|
684
378
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
379
|
+
Backend-Server: {
|
|
380
|
+
label: "Backend Server"
|
|
381
|
+
shape: rectangle
|
|
688
382
|
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
}
|
|
383
|
+
Uploader-Server: {
|
|
384
|
+
label: "@blocklet/uploader-server"
|
|
692
385
|
}
|
|
693
386
|
|
|
694
|
-
|
|
387
|
+
DB: {
|
|
388
|
+
label: "Database"
|
|
695
389
|
shape: cylinder
|
|
696
|
-
grid-columns: 2
|
|
697
|
-
"/api/user/profile": {}
|
|
698
|
-
"/api/user/privacy/config": {}
|
|
699
|
-
"/api/user/notification/config": {}
|
|
700
|
-
"/api/user/logout": {}
|
|
701
|
-
"/api/user/follow/{did}": {}
|
|
702
|
-
"/api/user": {}
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
"Your Application" -> "@blocklet/js-sdk".AuthService: "e.g., sdk.auth.getProfile()"
|
|
706
|
-
"@blocklet/js-sdk".AuthService -> "Blocklet API Endpoints": "Makes authenticated API calls"
|
|
707
|
-
```
|
|
708
|
-
- **非常重要** 当容器节点中子节点个数与 `grid-columns` 值相同时,则应该去掉容器节点中的 `grid-columns` 字段
|
|
709
|
-
- bad:
|
|
710
|
-
```d2
|
|
711
|
-
"@blocklet/js-sdk": {
|
|
712
|
-
shape: package
|
|
713
|
-
grid-columns: 1
|
|
714
|
-
|
|
715
|
-
"AuthService": {
|
|
716
|
-
shape: class
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
```
|
|
720
|
-
- good:
|
|
721
|
-
```d2
|
|
722
|
-
"@blocklet/js-sdk": {
|
|
723
|
-
shape: package
|
|
724
|
-
|
|
725
|
-
"AuthService": {
|
|
726
|
-
shape: class
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
```
|
|
730
|
-
- bad:
|
|
731
|
-
```d2
|
|
732
|
-
"Browser Storage": {
|
|
733
|
-
shape: package
|
|
734
|
-
grid-columns: 2
|
|
735
|
-
|
|
736
|
-
"Cookies": {
|
|
737
|
-
shape: document
|
|
738
|
-
"Session Token": {}
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
"LocalStorage": {
|
|
742
|
-
shape: stored_data
|
|
743
|
-
"Refresh Token": {}
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
```
|
|
747
|
-
- good:
|
|
748
|
-
```d2
|
|
749
|
-
"Browser Storage": {
|
|
750
|
-
shape: package
|
|
751
|
-
|
|
752
|
-
"Cookies": {
|
|
753
|
-
shape: document
|
|
754
|
-
"Session Token": {}
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
"LocalStorage": {
|
|
758
|
-
shape: stored_data
|
|
759
|
-
"Refresh Token": {}
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
```
|
|
763
|
-
- 当一个容器节点外部有节点与当前容器节点内部节点相互关联时,应该将这些节点放在同一层级
|
|
764
|
-
- bad:
|
|
765
|
-
```d2
|
|
766
|
-
direction: down
|
|
767
|
-
|
|
768
|
-
"Federated Login Group": {
|
|
769
|
-
shape: package
|
|
770
|
-
|
|
771
|
-
"Master App": {
|
|
772
|
-
shape: rectangle
|
|
773
|
-
style.stroke: "#0052cc"
|
|
774
|
-
style.stroke-width: 2
|
|
775
|
-
"Provides central authentication"
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
"Member App 1 (Current App)": {
|
|
779
|
-
shape: rectangle
|
|
780
|
-
"User interacts here"
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
"Member App 2": {
|
|
784
|
-
shape: rectangle
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
"Master App" -> "Member App 1 (Current App)": "Shares user session"
|
|
788
|
-
"Master App" -> "Member App 2": "Shares user session"
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
User: {
|
|
792
|
-
shape: person
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
User -> "Member App 1 (Current App)": "Logs in via Master App"
|
|
796
|
-
```
|
|
797
|
-
- good:
|
|
798
|
-
```d2
|
|
799
|
-
direction: down
|
|
800
|
-
|
|
801
|
-
"Federated Login Group": {
|
|
802
|
-
shape: package
|
|
803
|
-
|
|
804
|
-
"Master App": {
|
|
805
|
-
shape: rectangle
|
|
806
|
-
style.stroke: "#0052cc"
|
|
807
|
-
style.stroke-width: 2
|
|
808
|
-
"Provides central authentication"
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
"Member App 1 (Current App)": {
|
|
812
|
-
shape: rectangle
|
|
813
|
-
"User interacts here"
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
"Member App 2": {
|
|
817
|
-
shape: rectangle
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
"Master App" -> "Member App 1 (Current App)": "Shares user session"
|
|
821
|
-
"Master App" -> "Member App 2": "Shares user session"
|
|
822
|
-
|
|
823
|
-
User: {
|
|
824
|
-
shape: person
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
User -> "Member App 1 (Current App)": "Logs in via Master App"
|
|
828
390
|
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
829
393
|
|
|
830
|
-
|
|
831
|
-
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
394
|
+
User -> App.Uploader-Component: "1. Drop file"
|
|
395
|
+
App.Uploader-Component -> App.Backend-Server.Uploader-Server: "2. Upload file"
|
|
396
|
+
App.Backend-Server.Uploader-Server -> App.Backend-Server.Uploader-Server: "3. Backend onUploadFinish"
|
|
397
|
+
App.Backend-Server.Uploader-Server -> App.Backend-Server.DB: "4. Save metadata"
|
|
398
|
+
App.Backend-Server.DB -> App.Backend-Server.Uploader-Server: "5. Return DB record"
|
|
399
|
+
App.Backend-Server.Uploader-Server -> App.Uploader-Component: "6. Send JSON response"
|
|
400
|
+
App.Uploader-Component -> App.Uploader-Component: "7. Frontend onUploadFinish"
|
|
401
|
+
App.Uploader-Component -> User: "8. Update UI"
|
|
402
|
+
```
|
|
835
403
|
|
|
836
|
-
|
|
837
|
-
shape: person
|
|
838
|
-
}
|
|
404
|
+
> Move all connections to root, and the `User` shape is outside the `App` container, so the connection must reference the full path `App.Uploader-Component`.
|
|
839
405
|
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
406
|
+
#### Shape name should not contain special characters or quotes
|
|
407
|
+
- **Bad Practice:**
|
|
408
|
+
```d2
|
|
409
|
+
direction: down
|
|
843
410
|
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
"UA: Chrome on macOS"
|
|
848
|
-
"Status: online"
|
|
849
|
-
}
|
|
411
|
+
"@blocklet/app": {
|
|
412
|
+
label: "Your Blocklet Application"
|
|
413
|
+
shape: rectangle
|
|
850
414
|
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
"UA: MyApp/1.2 iOS"
|
|
855
|
-
"Status: online"
|
|
856
|
-
}
|
|
415
|
+
"uploader-component": {
|
|
416
|
+
label: "<Uploader /> Component"
|
|
417
|
+
shape: rectangle
|
|
857
418
|
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
"IP: 172.16.0.20"
|
|
861
|
-
"UA: Firefox on Windows"
|
|
862
|
-
"Status: expired"
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
"User Account" -> "Sessions": "Has multiple"
|
|
867
|
-
```
|
|
868
|
-
- good:
|
|
869
|
-
```d2
|
|
870
|
-
direction: down
|
|
871
|
-
|
|
872
|
-
"User Account": {
|
|
873
|
-
shape: person
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
"Sessions": {
|
|
877
|
-
shape: package
|
|
878
|
-
grid-columns: 1
|
|
879
|
-
|
|
880
|
-
"Web Browser Session": {
|
|
881
|
-
shape: rectangle
|
|
882
|
-
"IP: 192.168.1.10"
|
|
883
|
-
"UA: Chrome on macOS"
|
|
884
|
-
"Status: online"
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
"iOS App Session": {
|
|
888
|
-
shape: rectangle
|
|
889
|
-
"IP: 10.0.0.5"
|
|
890
|
-
"UA: MyApp/1.2 iOS"
|
|
891
|
-
"Status: online"
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
"Old Laptop Session": {
|
|
895
|
-
shape: rectangle
|
|
896
|
-
"IP: 172.16.0.20"
|
|
897
|
-
"UA: Firefox on Windows"
|
|
898
|
-
"Status: expired"
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
"User Account" -> "Sessions": "Has multiple"
|
|
903
|
-
```
|
|
904
|
-
- 当一个节点容器中包含了其他的节点容器,建议使用 `grid-gap` 来增加各个节点容器的距离,尽量大于 `100`
|
|
905
|
-
- bad:
|
|
906
|
-
```d2
|
|
907
|
-
direction: down
|
|
908
|
-
|
|
909
|
-
"Your Application": {
|
|
419
|
+
"uppy-ecosystem": {
|
|
420
|
+
label: "Uppy Ecosystem"
|
|
910
421
|
shape: rectangle
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
"SDK: @blocklet/js-sdk": {
|
|
914
|
-
shape: package
|
|
915
|
-
grid-columns: 1
|
|
916
422
|
|
|
917
|
-
"
|
|
918
|
-
|
|
919
|
-
grid-columns: 2
|
|
920
|
-
"createAxios()": "Axios-based client"
|
|
921
|
-
"createFetch()": "Fetch-based client"
|
|
423
|
+
"uppy-core": {
|
|
424
|
+
label: "Uppy Core Instance"
|
|
922
425
|
}
|
|
923
426
|
|
|
924
|
-
"
|
|
925
|
-
|
|
926
|
-
grid-columns: 3
|
|
927
|
-
"AuthService": "User & Auth"
|
|
928
|
-
"TokenService": "Token Management"
|
|
929
|
-
"UserSessionService": "Session Data"
|
|
930
|
-
"BlockletService": "Blocklet Metadata"
|
|
931
|
-
"FederatedService": "Federated Login"
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
"HTTP Clients" -> "Core Services".TokenService: "Uses for auth tokens"
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
"Blocklet Services": {
|
|
938
|
-
shape: cylinder
|
|
939
|
-
"Remote APIs"
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
"Your Application" -> "SDK: @blocklet/js-sdk": "Imports & Initializes"
|
|
943
|
-
"SDK: @blocklet/js-sdk" -> "Blocklet Services": "Makes authenticated requests"
|
|
944
|
-
```
|
|
945
|
-
- bad:
|
|
946
|
-
```d2
|
|
947
|
-
direction: down
|
|
948
|
-
|
|
949
|
-
"Your Application": {
|
|
950
|
-
shape: rectangle
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
"SDK: @blocklet/js-sdk": {
|
|
954
|
-
shape: package
|
|
955
|
-
grid-columns: 1
|
|
956
|
-
grid-gap: 100
|
|
957
|
-
|
|
958
|
-
"HTTP Clients": {
|
|
427
|
+
"standard-plugins": {
|
|
428
|
+
label: "Standard Uppy Plugins"
|
|
959
429
|
shape: rectangle
|
|
960
|
-
|
|
961
|
-
"
|
|
962
|
-
"
|
|
430
|
+
"Dashboard": {}
|
|
431
|
+
"Tus": {}
|
|
432
|
+
"Webcam": {}
|
|
433
|
+
"Url": {}
|
|
963
434
|
}
|
|
964
435
|
|
|
965
|
-
"
|
|
436
|
+
"custom-plugins": {
|
|
437
|
+
label: "Custom Blocklet Plugins"
|
|
966
438
|
shape: rectangle
|
|
967
|
-
|
|
968
|
-
"
|
|
969
|
-
"
|
|
970
|
-
"UserSessionService": "Session Data"
|
|
971
|
-
"BlockletService": "Blocklet Metadata"
|
|
972
|
-
"FederatedService": "Federated Login"
|
|
439
|
+
"AIImage": {}
|
|
440
|
+
"Resources": {}
|
|
441
|
+
"Uploaded": {}
|
|
973
442
|
}
|
|
974
443
|
|
|
975
|
-
"
|
|
444
|
+
"uppy-core" -> "standard-plugins"
|
|
445
|
+
"uppy-core" -> "custom-plugins"
|
|
976
446
|
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
977
449
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
450
|
+
"media-kit": {
|
|
451
|
+
label: "Media Kit Blocklet"
|
|
452
|
+
shape: cylinder
|
|
453
|
+
}
|
|
982
454
|
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
```d2
|
|
989
|
-
"User Account": {
|
|
990
|
-
shape: person
|
|
991
|
-
"did:z... (John Doe)"
|
|
992
|
-
}
|
|
993
|
-
```
|
|
994
|
-
- good:
|
|
995
|
-
```d2
|
|
996
|
-
"User Account": {
|
|
997
|
-
shape: person
|
|
998
|
-
}
|
|
999
|
-
```
|
|
1000
|
-
- **非常重要** 在绘制连线的时候,一定要注意连接的节点的 ID 到底是什么,它可能有多个层级,但一定要弄清楚关系才能添加连线
|
|
1001
|
-
- bad:
|
|
1002
|
-
```d2
|
|
1003
|
-
direction: down
|
|
1004
|
-
|
|
1005
|
-
"User-Browser": {
|
|
1006
|
-
label: "User's Browser"
|
|
1007
|
-
shape: rectangle
|
|
455
|
+
"@blocklet/app.uploader-component" <-> media-kit: "Provides Config & Plugins"
|
|
456
|
+
```
|
|
457
|
+
- **Good Practice:**
|
|
458
|
+
```d2
|
|
459
|
+
direction: down
|
|
1008
460
|
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
461
|
+
blocklet-app: {
|
|
462
|
+
label: "Your Blocklet Application"
|
|
463
|
+
shape: rectangle
|
|
1012
464
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
465
|
+
uploader-component: {
|
|
466
|
+
label: "<Uploader /> Component"
|
|
467
|
+
shape: rectangle
|
|
1019
468
|
|
|
1020
|
-
|
|
1021
|
-
label: "
|
|
469
|
+
uppy-ecosystem: {
|
|
470
|
+
label: "Uppy Ecosystem"
|
|
1022
471
|
shape: rectangle
|
|
1023
472
|
|
|
1024
|
-
|
|
1025
|
-
label: "
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
473
|
+
uppy-core: {
|
|
474
|
+
label: "Uppy Core Instance"
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
standard-plugins: {
|
|
478
|
+
label: "Standard Uppy Plugins"
|
|
479
|
+
shape: rectangle
|
|
480
|
+
Dashboard: {}
|
|
481
|
+
Tus: {}
|
|
482
|
+
Webcam: {}
|
|
483
|
+
Url: {}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
custom-plugins: {
|
|
487
|
+
label: "Custom Blocklet Plugins"
|
|
488
|
+
shape: rectangle
|
|
489
|
+
AIImage: {}
|
|
490
|
+
Resources: {}
|
|
491
|
+
Uploaded: {}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
media-kit: {
|
|
498
|
+
label: "Media Kit Blocklet"
|
|
499
|
+
shape: cylinder
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
blocklet-app.uploader-component.uppy-ecosystem.uppy-core -> blocklet-app.uploader-component.uppy-ecosystem.standard-plugins
|
|
503
|
+
blocklet-app.uploader-component.uppy-ecosystem.uppy-core -> blocklet-app.uploader-component.uppy-ecosystem.custom-plugins
|
|
504
|
+
blocklet-app.uploader-component <-> media-kit: "Provides Config & Plugins"
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
#### Shape should not contain both label and remark
|
|
508
|
+
- **Bad Practice:**
|
|
509
|
+
```d2
|
|
510
|
+
uploader-trigger: {
|
|
511
|
+
label: "UploaderTrigger"
|
|
512
|
+
shape: rectangle
|
|
513
|
+
style.fill: "#e6f7ff"
|
|
514
|
+
"A wrapper to create a clickable element (e.g., a button) that opens the Uploader UI."
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
- **Good Practice:**
|
|
518
|
+
```d2
|
|
519
|
+
uploader-trigger: {
|
|
520
|
+
label: "UploaderTrigger"
|
|
521
|
+
shape: rectangle
|
|
522
|
+
style.fill: "#e6f7ff"
|
|
523
|
+
}
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
#### Shorten long connections text
|
|
527
|
+
- **Bad Practice:**
|
|
528
|
+
```d2
|
|
529
|
+
direction: down
|
|
530
|
+
|
|
531
|
+
User: {
|
|
532
|
+
shape: c4-person
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
Frontend: {
|
|
536
|
+
label: "Frontend (Browser)"
|
|
537
|
+
shape: rectangle
|
|
538
|
+
|
|
539
|
+
Uploader-Component: {
|
|
540
|
+
label: "Uploader Component"
|
|
541
|
+
shape: rectangle
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
Backend: {
|
|
546
|
+
label: "Backend Server"
|
|
547
|
+
shape: rectangle
|
|
548
|
+
|
|
549
|
+
Companion-Middleware: {
|
|
550
|
+
label: "Companion Middleware\n(@blocklet/uploader-server)"
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
Local-Storage-Middleware: {
|
|
554
|
+
label: "Local Storage Middleware"
|
|
555
|
+
shape: rectangle
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
Remote-Source: {
|
|
560
|
+
label: "Remote Source\n(e.g., Unsplash, URL)"
|
|
561
|
+
shape: cylinder
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
User -> Frontend.Uploader-Component: "1. Selects remote file"
|
|
565
|
+
Frontend.Uploader-Component -> Backend.Companion-Middleware: "2. Request file from remote source via Companion URL"
|
|
566
|
+
Backend.Companion-Middleware -> Remote-Source: "3. Fetch file"
|
|
567
|
+
Remote-Source -> Backend.Companion-Middleware: "4. Stream file data"
|
|
568
|
+
Backend.Companion-Middleware -> Frontend.Uploader-Component: "5. Stream file back to browser"
|
|
569
|
+
Frontend.Uploader-Component -> Backend.Local-Storage-Middleware: "6. Upload file via Tus protocol"
|
|
570
|
+
```
|
|
571
|
+
- **Good Practice:**
|
|
572
|
+
```d2
|
|
573
|
+
direction: down
|
|
574
|
+
|
|
575
|
+
User: {
|
|
576
|
+
shape: c4-person
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
Frontend: {
|
|
580
|
+
label: "Frontend (Browser)"
|
|
581
|
+
shape: rectangle
|
|
582
|
+
|
|
583
|
+
Uploader-Component: {
|
|
584
|
+
label: "Uploader Component"
|
|
585
|
+
shape: rectangle
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
Backend: {
|
|
590
|
+
label: "Backend Server"
|
|
591
|
+
shape: rectangle
|
|
592
|
+
|
|
593
|
+
Companion-Middleware: {
|
|
594
|
+
label: "Companion Middleware\n(@blocklet/uploader-server)"
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
Local-Storage-Middleware: {
|
|
598
|
+
label: "Local Storage Middleware"
|
|
599
|
+
shape: rectangle
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
Remote-Source: {
|
|
604
|
+
label: "Remote Source\n(e.g., Unsplash, URL)"
|
|
605
|
+
shape: cylinder
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
User -> Frontend.Uploader-Component: "1. Selects file"
|
|
609
|
+
Frontend.Uploader-Component -> Backend.Companion-Middleware: "2. Request file"
|
|
610
|
+
Backend.Companion-Middleware -> Remote-Source: "3. Fetch file"
|
|
611
|
+
Remote-Source -> Backend.Companion-Middleware: "4. Stream file data"
|
|
612
|
+
Backend.Companion-Middleware -> Frontend.Uploader-Component: "5. Back to browser"
|
|
613
|
+
Frontend.Uploader-Component -> Backend.Local-Storage-Middleware: "6. Upload file"
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
#### Remove unnecessary grid-columns
|
|
617
|
+
- **Bad Practice:**
|
|
618
|
+
```d2
|
|
619
|
+
direction: down
|
|
620
|
+
|
|
621
|
+
User: {
|
|
622
|
+
shape: c4-person
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
Checkout-Flow: {
|
|
626
|
+
label: "Checkout Flow"
|
|
627
|
+
shape: rectangle
|
|
628
|
+
|
|
629
|
+
Entry-Points: {
|
|
630
|
+
label: "User-Facing Components"
|
|
631
|
+
shape: rectangle
|
|
632
|
+
grid-columns: 2
|
|
633
|
+
|
|
634
|
+
CheckoutTable: {
|
|
635
|
+
label: "CheckoutTable"
|
|
636
|
+
"Renders pricing plans"
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
CheckoutDonate: {
|
|
640
|
+
label: "CheckoutDonate"
|
|
641
|
+
"Manages donation flow"
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
Core-Processor: {
|
|
646
|
+
label: "Core Payment Processor"
|
|
647
|
+
shape: rectangle
|
|
648
|
+
|
|
649
|
+
CheckoutForm: {
|
|
650
|
+
label: "CheckoutForm"
|
|
651
|
+
"Processes the final payment"
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
Entry-Points.CheckoutTable -> Core-Processor.CheckoutForm: "On plan selection"
|
|
655
|
+
Entry-Points.CheckoutDonate -> Core-Processor.CheckoutForm: "On donate action"
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
User -> Checkout-Flow.Entry-Points.CheckoutTable: "Selects a subscription"
|
|
659
|
+
User -> Checkout-Flow.Entry-Points.CheckoutDonate: "Makes a donation"
|
|
660
|
+
```
|
|
661
|
+
- **Good Practice:**
|
|
662
|
+
```d2
|
|
663
|
+
direction: down
|
|
664
|
+
|
|
665
|
+
User: {
|
|
666
|
+
shape: c4-person
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
Checkout-Flow: {
|
|
670
|
+
label: "Checkout Flow"
|
|
671
|
+
shape: rectangle
|
|
672
|
+
|
|
673
|
+
Entry-Points: {
|
|
674
|
+
label: "User-Facing Components"
|
|
675
|
+
shape: rectangle
|
|
676
|
+
|
|
677
|
+
CheckoutTable: {
|
|
678
|
+
label: "CheckoutTable"
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
CheckoutDonate: {
|
|
682
|
+
label: "CheckoutDonate"
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
Core-Processor: {
|
|
687
|
+
label: "Core Payment Processor"
|
|
688
|
+
shape: rectangle
|
|
689
|
+
|
|
690
|
+
CheckoutForm: {
|
|
691
|
+
label: "CheckoutForm"
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
Checkout-Flow.Entry-Points.CheckoutTable -> Checkout-Flow.Core-Processor.CheckoutForm: "On plan selection"
|
|
698
|
+
Checkout-Flow.Entry-Points.CheckoutDonate -> Checkout-Flow.Core-Processor.CheckoutForm: "On donate action"
|
|
699
|
+
User -> Checkout-Flow.Entry-Points.CheckoutTable: "Selects a subscription"
|
|
700
|
+
User -> Checkout-Flow.Entry-Points.CheckoutDonate: "Makes a donation"
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
> Checkout-Flow.Entry-Points has only two child nodes, and `grid-columns` is set to `2`, so `grid-columns` is unnecessary.
|
|
704
|
+
|
|
705
|
+
#### Ensure style properties are valid
|
|
706
|
+
- **Bad Practice:**
|
|
707
|
+
```d2
|
|
708
|
+
App: {
|
|
709
|
+
shape: rectangle
|
|
710
|
+
style: {
|
|
711
|
+
stroke: "#888"
|
|
712
|
+
"stroke-width": 2
|
|
713
|
+
dashed: true
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
```
|
|
717
|
+
- **Good Practice:**
|
|
718
|
+
```d2
|
|
719
|
+
App: {
|
|
720
|
+
shape: rectangle
|
|
721
|
+
style: {
|
|
722
|
+
stroke: "#888"
|
|
723
|
+
stroke-width: 2
|
|
724
|
+
stroke-dash: 2
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
#### Sequence Diagram
|
|
730
|
+
- **Bad Practice:**
|
|
731
|
+
```d2
|
|
732
|
+
direction: down
|
|
733
|
+
|
|
734
|
+
User: {
|
|
735
|
+
shape: c4-person
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
App: {
|
|
739
|
+
label: "Your Application"
|
|
740
|
+
shape: rectangle
|
|
741
|
+
|
|
742
|
+
ResumeSubscription: {
|
|
743
|
+
label: "ResumeSubscription Component"
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
Payment-API: {
|
|
748
|
+
label: "Payment Backend API"
|
|
749
|
+
shape: rectangle
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
DID-Wallet: {
|
|
753
|
+
label: "DID Wallet"
|
|
754
|
+
icon: "https://www.arcblock.io/image-bin/uploads/37198ddc4a0b9e91e5c1c821ab895a34.svg"
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
sequence: {
|
|
758
|
+
shape: sequence_diagram
|
|
759
|
+
|
|
760
|
+
User -> App.ResumeSubscription: "1. Triggers resume action"
|
|
761
|
+
|
|
762
|
+
App.ResumeSubscription -> Payment-API: "2. Fetch recovery info\n(GET /recover-info)"
|
|
763
|
+
Payment-API -> App.ResumeSubscription: "3. Return status (e.g., needStake: true)"
|
|
764
|
+
|
|
765
|
+
App.ResumeSubscription.t1 -> User: "4. Display confirmation dialog"
|
|
766
|
+
User -> App.ResumeSubscription.t1: "5. Clicks 'Confirm'"
|
|
767
|
+
|
|
768
|
+
alt: "If Re-Staking is Required" {
|
|
769
|
+
App.ResumeSubscription.t1 -> DID-Wallet: "6a. Open 're-stake' session"
|
|
770
|
+
User -> DID-Wallet: "7a. Approve in wallet"
|
|
771
|
+
DID-Wallet -> App.ResumeSubscription.t1: "8a. Send success callback"
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
alt: "If No Staking is Required" {
|
|
775
|
+
App.ResumeSubscription.t1 -> Payment-API: "6b. Call recover endpoint\n(PUT /recover)"
|
|
776
|
+
Payment-API -> App.ResumeSubscription.t1: "7b. Return success"
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
App.ResumeSubscription.t1 -> Payment-API: "9. Fetch updated subscription details"
|
|
780
|
+
Payment-API -> App.ResumeSubscription.t1: "10. Return latest subscription"
|
|
781
|
+
App.ResumeSubscription.t1 -> App.ResumeSubscription: "11. Call onResumed() & close dialog"
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
```
|
|
785
|
+
- **Good Practice:**
|
|
786
|
+
```d2
|
|
787
|
+
shape: sequence_diagram
|
|
788
|
+
|
|
789
|
+
User -> App.ResumeSubscription: "1. Triggers resume action"
|
|
790
|
+
|
|
791
|
+
App.ResumeSubscription -> Payment-API: "2. Fetch recovery info\n(GET /recover-info)"
|
|
792
|
+
Payment-API -> App.ResumeSubscription: "3. Return status (e.g., needStake: true)"
|
|
793
|
+
|
|
794
|
+
App.ResumeSubscription.t1 -> User: "4. Display confirmation dialog"
|
|
795
|
+
User -> App.ResumeSubscription.t1: "5. Clicks 'Confirm'"
|
|
796
|
+
|
|
797
|
+
App.ResumeSubscription.t1 -> DID-Wallet: "6a. Open 're-stake' session"
|
|
798
|
+
User -> DID-Wallet: "7a. Approve in wallet"
|
|
799
|
+
DID-Wallet -> App.ResumeSubscription.t1: "8a. Send success callback"
|
|
800
|
+
|
|
801
|
+
App.ResumeSubscription.t1 -> Payment-API: "6b. Call recover endpoint\n(PUT /recover)"
|
|
802
|
+
Payment-API -> App.ResumeSubscription.t1: "7b. Return success"
|
|
803
|
+
|
|
804
|
+
App.ResumeSubscription.t1 -> Payment-API: "9. Fetch updated subscription details"
|
|
805
|
+
Payment-API -> App.ResumeSubscription.t1: "10. Return latest subscription"
|
|
806
|
+
App.ResumeSubscription.t1 -> App.ResumeSubscription: "11. Call onResumed() & close dialog"
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
> If using sequence diagram, remove all other shapes, and only keep the sequence diagram part.
|
|
810
|
+
|
|
811
|
+
#### Don't use icon in Sequence Diagram
|
|
812
|
+
|
|
813
|
+
- **Bad Practice:**
|
|
814
|
+
```d2
|
|
815
|
+
shape: sequence_diagram
|
|
816
|
+
|
|
817
|
+
Developer: {
|
|
818
|
+
shape: c4-person
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
CLI: {
|
|
822
|
+
label: "CLI"
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
Blocklet-Store: {
|
|
826
|
+
label: "Blocklet Store"
|
|
827
|
+
icon: "https://store.blocklet.dev/assets/z8ia29UsENBg6tLZUKi2HABj38Cw1LmHZocbQ/logo.png"
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
Local-Config: {
|
|
831
|
+
label: "Local Config"
|
|
832
|
+
shape: cylinder
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
Developer -> CLI: "blocklet connect https://..."
|
|
836
|
+
CLI -> Blocklet-Store: "1. Opens auth URL in browser"
|
|
837
|
+
Developer -> Blocklet-Store: "2. Authenticates & authorizes CLI"
|
|
838
|
+
Blocklet-Store -> CLI: "3. Sends token & developer info"
|
|
839
|
+
CLI -> Local-Config: "4. Saves credentials"
|
|
840
|
+
CLI -> Developer: "5. Displays success message"
|
|
841
|
+
```
|
|
842
|
+
- **Good Practice:**
|
|
843
|
+
```d2
|
|
844
|
+
shape: sequence_diagram
|
|
845
|
+
|
|
846
|
+
Developer: {
|
|
847
|
+
shape: c4-person
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
CLI: {
|
|
851
|
+
label: "CLI"
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
Blocklet-Store: {
|
|
855
|
+
label: "Blocklet Store"
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
Local-Config: {
|
|
859
|
+
label: "Local Config"
|
|
860
|
+
shape: cylinder
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
Developer -> CLI: "blocklet connect"
|
|
864
|
+
CLI -> Blocklet-Store: "1. Opens auth URL"
|
|
865
|
+
Developer -> Blocklet-Store: "2. Authenticates CLI"
|
|
866
|
+
Blocklet-Store -> CLI: "3. Sends token"
|
|
867
|
+
CLI -> Local-Config: "4. Saves credentials"
|
|
868
|
+
CLI -> Developer: "5. Success"
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
#### Avoid unexpected characters
|
|
872
|
+
- **Bad Practice:**
|
|
873
|
+
```d2
|
|
874
|
+
User -> CLI: "$ blocklet init"
|
|
875
|
+
```
|
|
876
|
+
- **Good Practice:**
|
|
877
|
+
```d2
|
|
878
|
+
User -> CLI: "blocklet init"
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
#### If have alt, don't forget to add `shape: sequence_diagram`
|
|
882
|
+
|
|
883
|
+
- **Bad Practice:**
|
|
884
|
+
```d2
|
|
885
|
+
direction: down
|
|
886
|
+
Client: {
|
|
887
|
+
shape: c4-person
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
Application: {
|
|
891
|
+
label: "Your Blocklet (Express.js)"
|
|
892
|
+
shape: rectangle
|
|
893
|
+
|
|
894
|
+
Session-Middleware: {
|
|
895
|
+
label: "session()"
|
|
896
|
+
}
|
|
897
|
+
Auth-Middleware: {
|
|
898
|
+
label: "auth()"
|
|
899
|
+
}
|
|
900
|
+
Protected-Route: {
|
|
901
|
+
label: "Route Handler"
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
Blocklet-Service: {
|
|
906
|
+
label: "Blocklet Service"
|
|
907
|
+
shape: cylinder
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
Client -> Application.Session-Middleware: "1. Request to /protected"
|
|
911
|
+
Application.Session-Middleware -> Application.Auth-Middleware: "2. next() with req.user"
|
|
912
|
+
Application.Auth-Middleware -> Blocklet-Service: "3. Get permissions for role\n(if needed)"
|
|
913
|
+
Blocklet-Service -> Application.Auth-Middleware: "4. Return permissions"
|
|
914
|
+
Application.Auth-Middleware -> Application.Auth-Middleware: "5. Evaluate all rules"
|
|
915
|
+
|
|
916
|
+
alt "If Authorized" {
|
|
917
|
+
Application.Auth-Middleware -> Application.Protected-Route: "6a. next()"
|
|
918
|
+
Application.Protected-Route -> Client: "7a. 200 OK Response"
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
alt "If Forbidden" {
|
|
922
|
+
Application.Auth-Middleware -> Client: "6b. 403 Forbidden Response"
|
|
923
|
+
}
|
|
924
|
+
```
|
|
925
|
+
- **Good Practice:**
|
|
926
|
+
```d2
|
|
927
|
+
direction: down
|
|
928
|
+
shape: sequence_diagram
|
|
929
|
+
Client: {
|
|
930
|
+
shape: c4-person
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
Application: {
|
|
934
|
+
label: "Your Blocklet (Express.js)"
|
|
935
|
+
shape: rectangle
|
|
936
|
+
|
|
937
|
+
Session-Middleware: {
|
|
938
|
+
label: "session()"
|
|
939
|
+
}
|
|
940
|
+
Auth-Middleware: {
|
|
941
|
+
label: "auth()"
|
|
942
|
+
}
|
|
943
|
+
Protected-Route: {
|
|
944
|
+
label: "Route Handler"
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
Blocklet-Service: {
|
|
949
|
+
label: "Blocklet Service"
|
|
950
|
+
shape: cylinder
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
Client -> Application.Session-Middleware: "1. Request to /protected"
|
|
954
|
+
Application.Session-Middleware -> Application.Auth-Middleware: "2. next() with req.user"
|
|
955
|
+
Application.Auth-Middleware -> Blocklet-Service: "3. Get permissions for role\n(if needed)"
|
|
956
|
+
Blocklet-Service -> Application.Auth-Middleware: "4. Return permissions"
|
|
957
|
+
Application.Auth-Middleware -> Application.Auth-Middleware: "5. Evaluate all rules"
|
|
958
|
+
|
|
959
|
+
alt "If Authorized" {
|
|
960
|
+
Application.Auth-Middleware -> Application.Protected-Route: "6a. next()"
|
|
961
|
+
Application.Protected-Route -> Client: "7a. 200 OK Response"
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
alt "If Forbidden" {
|
|
965
|
+
Application.Auth-Middleware -> Client: "6b. 403 Forbidden Response"
|
|
966
|
+
}
|
|
967
|
+
```
|
|
968
|
+
|
|
969
|
+
## Chapter 3: Official Best Practices
|
|
970
|
+
|
|
971
|
+
##### Game State Sequence
|
|
972
|
+
```d2
|
|
973
|
+
shape: sequence_diagram
|
|
974
|
+
|
|
975
|
+
User
|
|
976
|
+
Session
|
|
977
|
+
Lua
|
|
978
|
+
|
|
979
|
+
User.Init
|
|
980
|
+
|
|
981
|
+
User.t1 -> Session.t1: "SetupFight()"
|
|
982
|
+
Session.t1 -> Session.t1: "Create clean fight state"
|
|
983
|
+
Session.t1 -> Lua: "Trigger OnPlayerTurn"
|
|
984
|
+
User.t1 <- Session.t1
|
|
985
|
+
|
|
986
|
+
User.Repeat
|
|
987
|
+
|
|
988
|
+
User.mid -> Session.mid: "PlayerCastHand() etc."
|
|
989
|
+
Session.mid -> Lua: "Trigger OnDamage etc."
|
|
990
|
+
User.mid <- Session.mid
|
|
991
|
+
|
|
992
|
+
User.t2 -> Session.t2: "FinishPlayerTurn()"
|
|
993
|
+
Session.t2 -> Lua: "Trigger OnTurn"
|
|
994
|
+
Session.t2 -> Session.t2: "Update and remove status effects"
|
|
995
|
+
Session.t2 -> Lua: "Trigger OnPlayerTurn"
|
|
996
|
+
User.t2 <- Session.t2
|
|
997
|
+
```
|
|
1081
998
|
|
|
1082
|
-
User-Browser.React-App.Uploader-Component -> Blocklet-Server.Express-App.Uploader-Middleware: "HTTP POST Request\n(File Upload)"
|
|
1083
|
-
Blocklet-Server.Express-App.Uploader-Middleware -> "File-System": "Saves File"
|
|
1084
|
-
```
|
|
1085
|
-
- 对于节点 shape 的选择,可以参考
|
|
1086
|
-
{% include "shape-rules.md" %}
|
|
1087
|
-
- 示例参考:
|
|
1088
|
-
{% include "diy-examples.md" %}
|