@laitszkin/apollo-toolkit 3.10.0 → 3.11.0

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 (47) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/analyse-app-logs/scripts/__pycache__/filter_logs_by_time.cpython-312.pyc +0 -0
  3. package/analyse-app-logs/scripts/__pycache__/log_cli_utils.cpython-312.pyc +0 -0
  4. package/analyse-app-logs/scripts/__pycache__/search_logs.cpython-312.pyc +0 -0
  5. package/docs-to-voice/scripts/__pycache__/docs_to_voice.cpython-312.pyc +0 -0
  6. package/generate-spec/SKILL.md +17 -15
  7. package/generate-spec/agents/openai.yaml +1 -1
  8. package/generate-spec/references/TEMPLATE_SPEC.md +103 -84
  9. package/generate-spec/scripts/__pycache__/create-specscpython-312.pyc +0 -0
  10. package/init-project-html/SKILL.md +82 -126
  11. package/init-project-html/agents/openai.yaml +17 -8
  12. package/init-project-html/lib/atlas/assets/architecture.css +140 -0
  13. package/init-project-html/lib/atlas/assets/viewer.client.js +93 -0
  14. package/init-project-html/lib/atlas/cli.js +995 -0
  15. package/init-project-html/lib/atlas/layout.js +229 -0
  16. package/init-project-html/lib/atlas/render.js +485 -0
  17. package/init-project-html/lib/atlas/schema.js +310 -0
  18. package/init-project-html/lib/atlas/state.js +402 -0
  19. package/init-project-html/references/TEMPLATE_SPEC.md +123 -84
  20. package/init-project-html/sample-demo/resources/project-architecture/assets/architecture.css +139 -1058
  21. package/init-project-html/sample-demo/resources/project-architecture/assets/viewer.client.js +93 -0
  22. package/init-project-html/sample-demo/resources/project-architecture/atlas/atlas.index.yaml +34 -0
  23. package/init-project-html/sample-demo/resources/project-architecture/atlas/features/get-invite-codes.yaml +159 -0
  24. package/init-project-html/sample-demo/resources/project-architecture/atlas/features/invite-code-registration.yaml +160 -0
  25. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/index.html +67 -52
  26. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/invite-code-generator.html +48 -163
  27. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/invite-issuance-service.html +70 -196
  28. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/postgresql.html +64 -163
  29. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/public-api.html +68 -150
  30. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/web-get-invite-ui.html +65 -138
  31. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/index.html +61 -51
  32. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/postgresql.html +66 -159
  33. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/public-api.html +63 -143
  34. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/registration-service.html +77 -188
  35. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/web-register-ui.html +65 -138
  36. package/init-project-html/sample-demo/resources/project-architecture/index.html +232 -335
  37. package/init-project-html/scripts/architecture.js +65 -247
  38. package/katex/scripts/__pycache__/render_katex.cpython-312.pyc +0 -0
  39. package/open-github-issue/scripts/__pycache__/open_github_issue.cpython-312.pyc +0 -0
  40. package/package.json +6 -2
  41. package/read-github-issue/scripts/__pycache__/find_issues.cpython-312.pyc +0 -0
  42. package/read-github-issue/scripts/__pycache__/read_issue.cpython-312.pyc +0 -0
  43. package/resolve-review-comments/scripts/__pycache__/review_threads.cpython-312.pyc +0 -0
  44. package/spec-to-project-html/SKILL.md +61 -63
  45. package/spec-to-project-html/agents/openai.yaml +14 -8
  46. package/spec-to-project-html/references/TEMPLATE_SPEC.md +96 -83
  47. package/text-to-short-video/scripts/__pycache__/enforce_video_aspect_ratio.cpython-312.pyc +0 -0
@@ -1,165 +1,50 @@
1
1
  <!DOCTYPE html>
2
- <html lang="zh-Hant">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <title>子模組 · invite-code-generator — 獲取邀請碼</title>
7
- <link rel="preconnect" href="https://fonts.googleapis.com" />
8
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
- <link
10
- href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;600&family=Plus+Jakarta+Sans:wght@400;500;600&display=swap"
11
- rel="stylesheet"
12
- />
13
- <link rel="stylesheet" href="../../assets/architecture.css" />
14
- </head>
15
- <body>
16
- <main class="atlas-page atlas-page--feature">
17
- <nav class="breadcrumb" aria-label="階層導航">
18
- <a href="../../index.html">宏觀架構</a> · <a href="./index.html">獲取邀請碼</a> · <span>invite-code-generator</span>
19
- </nav>
20
-
21
- <header class="atlas-header">
22
- <p class="atlas-kicker">Submodule · invite-code-generator</p>
23
- <h1 class="atlas-title">invite-code-generator</h1>
24
- <p class="atlas-meta">自身職責:把熵編碼成符合產品規格的可讀字串。<strong>無 I/O 副作用</strong>(除讀取隨機源)。本頁只列自身函式 I/O 與內部資料流。</p>
25
- </header>
26
-
27
- <section class="sub-io">
28
- <h2>函式 I/O</h2>
29
- <table>
30
- <thead><tr><th>函式</th><th>簽名</th><th>副作用</th><th>用途</th></tr></thead>
31
- <tbody>
32
- <tr>
33
- <td><code>NewGenerator</code></td>
34
- <td class="sub-io__signature">
35
- <strong>in:</strong> <code>Config{bits, alphabet, groupSize}</code><br>
36
- <strong>out:</strong> <code>Generator</code>
37
- </td>
38
- <td><span class="sub-io__side sub-io__side--pure">pure</span></td>
39
- <td>建構,驗證 <code>bits &gt;= 64</code>、字元表非空且不含易混字。</td>
40
- </tr>
41
- <tr>
42
- <td><code>Generator.Next</code></td>
43
- <td class="sub-io__signature">
44
- <strong>in:</strong> —<br>
45
- <strong>out:</strong> <code>string</code> | <code>ErrEntropy</code>
46
- </td>
47
- <td><span class="sub-io__side sub-io__side--io">io</span> 讀 <code>crypto/rand</code></td>
48
- <td>單次抽碼。</td>
49
- </tr>
50
- <tr>
51
- <td><code>encode</code></td>
52
- <td class="sub-io__signature">
53
- <strong>in:</strong> <code>bytes</code>, <code>alphabet</code><br>
54
- <strong>out:</strong> <code>string</code>
55
- </td>
56
- <td><span class="sub-io__side sub-io__side--pure">pure</span></td>
57
- <td>把位元組以 Crockford Base32(或同類)映射成字元。</td>
58
- </tr>
59
- <tr>
60
- <td><code>group</code></td>
61
- <td class="sub-io__signature">
62
- <strong>in:</strong> <code>string</code>, <code>groupSize</code><br>
63
- <strong>out:</strong> <code>string</code>
64
- </td>
65
- <td><span class="sub-io__side sub-io__side--pure">pure</span></td>
66
- <td>每 N 位插入 <code>-</code>,便於人讀。</td>
67
- </tr>
68
- </tbody>
69
- </table>
70
- </section>
71
-
72
- <section class="sub-vars">
73
- <h2>變數與業務用途</h2>
74
- <p class="sub-vars__intro">本子模組屬於純函式工具;下表分兩段:上半為建構時固定的設定,下半為單次 <code>Next</code> 期間的臨時值。</p>
75
- <table>
76
- <thead><tr><th>變數</th><th>型別</th><th>作用域</th><th>業務用途</th></tr></thead>
77
- <tbody>
78
- <tr>
79
- <td class="sub-vars__name">Config</td>
80
- <td class="sub-vars__type">{bits, alphabet, groupSize}</td>
81
- <td><span class="sub-vars__scope sub-vars__scope--instance">instance</span></td>
82
- <td>整個 Generator 生命週期固定;決定碼強度(bits)、可讀性(alphabet)、人讀分組(groupSize)。</td>
83
- </tr>
84
- <tr>
85
- <td class="sub-vars__name">bits</td>
86
- <td class="sub-vars__type">int</td>
87
- <td><span class="sub-vars__scope sub-vars__scope--instance">instance</span></td>
88
- <td>熵預算;決定隨機字串對 UNIQUE 碰撞的容忍度;過低會推高呼叫端的重試次數與失敗率。</td>
89
- </tr>
90
- <tr>
91
- <td class="sub-vars__name">alphabet</td>
92
- <td class="sub-vars__type">string</td>
93
- <td><span class="sub-vars__scope sub-vars__scope--instance">instance</span></td>
94
- <td>字元表;剃除 0/O、1/I/l 等易混字,業務上降低使用者抄錄錯誤率,間接提高分享成功率。</td>
95
- </tr>
96
- <tr>
97
- <td class="sub-vars__name">groupSize</td>
98
- <td class="sub-vars__type">int</td>
99
- <td><span class="sub-vars__scope sub-vars__scope--instance">instance</span></td>
100
- <td>每幾個字元插入連字號;只影響顯示/輸入體驗,不影響熵或唯一性。</td>
101
- </tr>
102
- <tr>
103
- <td class="sub-vars__name">bytes / buf</td>
104
- <td class="sub-vars__type">[]byte</td>
105
- <td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
106
- <td>從 <code>crypto/rand</code> 讀出的原始熵;只在單次 <code>Next</code> 期間活著,不持久化、不外傳,避免被推測下一次值。</td>
107
- </tr>
108
- <tr>
109
- <td class="sub-vars__name">raw</td>
110
- <td class="sub-vars__type">string</td>
111
- <td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
112
- <td>經 base32 編碼後尚未分組的中介字串;只是內部步驟,不對外。</td>
113
- </tr>
114
- <tr>
115
- <td class="sub-vars__name">out / candidate</td>
116
- <td class="sub-vars__type">string</td>
117
- <td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
118
- <td>最終回傳的候選碼;本子模組不快取、不記錄;唯一性由呼叫端結合 DB UNIQUE 保證。</td>
119
- </tr>
120
- </tbody>
121
- </table>
122
- </section>
123
-
124
- <section class="sub-dataflow">
125
- <h2>內部資料流(單次 Next)</h2>
126
- <ol>
127
- <li>從 <code>crypto/rand</code> 讀 <code>bytes[ceil(bits/8)]</code>。</li>
128
- <li><code>bytes</code> → <code>encode(bytes, alphabet)</code> → <code>raw: string</code>。</li>
129
- <li><code>raw</code> → <code>group(raw, groupSize)</code> → 最終字串。</li>
130
- <li>內部不持有狀態(除設定);多次呼叫互相獨立,無快取無記錄。</li>
131
- </ol>
132
- <svg class="sub-dataflow__svg" viewBox="0 0 720 110" role="img" aria-label="生成器資料流">
133
- <defs>
134
- <marker id="d-arr3" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto">
135
- <path d="M0,0 L9,4.5 L0,9 Z" fill="currentColor"></path>
136
- </marker>
137
- </defs>
138
- <g class="d-node"><rect x="20" y="30" width="150" height="50" rx="8"></rect><text x="95" y="50" text-anchor="middle">crypto/rand</text><text x="95" y="68" text-anchor="middle" class="d-label">→ bytes</text></g>
139
- <g class="d-node"><rect x="220" y="30" width="150" height="50" rx="8"></rect><text x="295" y="50" text-anchor="middle">encode</text><text x="295" y="68" text-anchor="middle" class="d-label">bytes → raw</text></g>
140
- <g class="d-node"><rect x="420" y="30" width="150" height="50" rx="8"></rect><text x="495" y="50" text-anchor="middle">group</text><text x="495" y="68" text-anchor="middle" class="d-label">raw → 字串</text></g>
141
- <g class="d-node"><rect x="600" y="30" width="100" height="50" rx="8"></rect><text x="650" y="58" text-anchor="middle">candidate</text></g>
142
- <line class="d-edge" x1="170" y1="55" x2="216" y2="55" marker-end="url(#d-arr3)"></line>
143
- <line class="d-edge" x1="370" y1="55" x2="416" y2="55" marker-end="url(#d-arr3)"></line>
144
- <line class="d-edge" x1="570" y1="55" x2="596" y2="55" marker-end="url(#d-arr3)"></line>
145
- </svg>
146
- </section>
147
-
148
- <section class="sub-errors">
149
- <h2>本子模組會拋出的錯誤</h2>
150
- <table>
151
- <thead><tr><th>錯誤</th><th>條件</th></tr></thead>
152
- <tbody>
153
- <tr><td><code>ErrEntropy</code></td><td><code>crypto/rand.Read</code> 短讀/失敗(罕見)。</td></tr>
154
- <tr><td><code>ErrConfig</code></td><td><code>NewGenerator</code> 收到非法 <code>Config</code>。</td></tr>
155
- </tbody>
156
- </table>
157
- <p class="atlas-meta">注意:「唯一性」<strong>不在本子模組</strong>;DB UNIQUE 違例由呼叫端處理(重新呼叫 <code>Next</code>)。</p>
158
- </section>
159
-
160
- <footer class="atlas-meta" style="margin-top: 2rem">
161
- <p><a href="../../index.html">← 宏觀架構</a> · <a href="./index.html">← 功能總覽</a></p>
162
- </footer>
163
- </main>
164
- </body>
2
+ <html lang="en" data-atlas-page="submodule">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Get invite codes · invite-code-generator</title>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <link rel="stylesheet" href="../../assets/architecture.css">
8
+ </head>
9
+ <body>
10
+ <header class="submodule-header">
11
+ <nav class="submodule-breadcrumb"><a href="../../index.html">← Atlas</a> · <a href="index.html">← Get invite codes</a></nav>
12
+ <h1>invite-code-generator <small class="submodule-kind submodule-kind--pure-fn">Pure fn</small></h1>
13
+ <p class="submodule-role">Pure helper that turns random bytes into a printable invite code.</p>
14
+ </header>
15
+ <main class="submodule-main">
16
+ <section class="sub-io" aria-label="Function I/O">
17
+ <h2>Function I/O</h2>
18
+ <table class="sub-table">
19
+ <thead><tr><th scope="col">Name</th><th scope="col">In</th><th scope="col">Out</th><th scope="col">Side</th><th scope="col">Purpose</th></tr></thead>
20
+ <tbody>
21
+ <tr><td>encode</td><td>bytes[8]</td><td>code</td><td>pure</td><td>Base32 encoding without padding for human-readable codes.</td></tr>
22
+ </tbody>
23
+ </table>
24
+ </section>
25
+ <section class="sub-vars" aria-label="Variables">
26
+ <h2>Variables</h2>
27
+ <p class="sub-section__empty">No variables recorded.</p>
28
+ </section>
29
+ <section class="sub-dataflow" aria-label="Internal data flow">
30
+ <h2>Internal data flow</h2>
31
+ <svg class="sub-dataflow__svg" viewBox="0 0 400 180" role="img" aria-label="Internal dataflow">
32
+ <defs><marker id="sub-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 Z" /></marker></defs>
33
+ <g class="sub-dataflow__step">
34
+ <rect x="20" y="20" width="360" height="56" rx="8" ry="8" />
35
+ <text x="200" y="44" text-anchor="middle">Read 8 random bytes.</text>
36
+ </g>
37
+ <line class="sub-dataflow__arrow" x1="200" y1="76" x2="200" y2="104" marker-end="url(#sub-arrow)" />
38
+ <g class="sub-dataflow__step">
39
+ <rect x="20" y="104" width="360" height="56" rx="8" ry="8" />
40
+ <text x="200" y="128" text-anchor="middle">Base32 encode without padding.</text>
41
+ </g>
42
+ </svg>
43
+ </section>
44
+ <section class="sub-errors" aria-label="Errors">
45
+ <h2>Errors</h2>
46
+ <p class="sub-section__empty">No errors recorded.</p>
47
+ </section>
48
+ </main>
49
+ </body>
165
50
  </html>
@@ -1,198 +1,72 @@
1
1
  <!DOCTYPE html>
2
- <html lang="zh-Hant">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <title>子模組 · invite-issuance-service — 獲取邀請碼</title>
7
- <link rel="preconnect" href="https://fonts.googleapis.com" />
8
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
- <link
10
- href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;600&family=Plus+Jakarta+Sans:wght@400;500;600&display=swap"
11
- rel="stylesheet"
12
- />
13
- <link rel="stylesheet" href="../../assets/architecture.css" />
14
- </head>
15
- <body>
16
- <main class="atlas-page atlas-page--feature">
17
- <nav class="breadcrumb" aria-label="階層導航">
18
- <a href="../../index.html">宏觀架構</a> · <a href="./index.html">獲取邀請碼</a> · <span>invite-issuance-service</span>
19
- </nav>
20
-
21
- <header class="atlas-header">
22
- <p class="atlas-kicker">Submodule · invite-issuance-service</p>
23
- <h1 class="atlas-title">invite-issuance-service</h1>
24
- <p class="atlas-meta">自身職責:在同一交易內判斷使用者額度、組裝待寫入列、處理 UNIQUE 碰撞重試。本頁只列自身函式 I/O 與內部資料流。</p>
25
- </header>
26
-
27
- <section class="sub-io">
28
- <h2>函式 I/O</h2>
29
- <table>
30
- <thead><tr><th>函式</th><th>簽名</th><th>副作用</th><th>用途</th></tr></thead>
31
- <tbody>
32
- <tr>
33
- <td><code>IssueInviteCode</code></td>
34
- <td class="sub-io__signature">
35
- <strong>in:</strong> <code>ctx</code>, <code>userID</code><br>
36
- <strong>out:</strong> <code>InviteCodeRecord</code> | <code>error</code>
37
- </td>
38
- <td><span class="sub-io__side sub-io__side--io">tx</span> 開/關交易<br><span class="sub-io__side sub-io__side--write">db</span> 條件成立時寫入新行</td>
39
- <td>對外唯一入口。</td>
40
- </tr>
41
- <tr>
42
- <td><code>checkQuota</code></td>
43
- <td class="sub-io__signature">
44
- <strong>in:</strong> <code>used</code>, <code>limit</code>, <code>userStatus</code><br>
45
- <strong>out:</strong> <code>true | ErrQuotaExceeded | ErrForbidden</code>
46
- </td>
47
- <td><span class="sub-io__side sub-io__side--pure">pure</span></td>
48
- <td>窗口計數 + 帳號狀態合法性判斷。</td>
49
- </tr>
50
- <tr>
51
- <td><code>buildRecord</code></td>
52
- <td class="sub-io__signature">
53
- <strong>in:</strong> <code>userID</code>, <code>code: string</code>, <code>now</code><br>
54
- <strong>out:</strong> <code>InviteCodeRecord</code>
55
- </td>
56
- <td><span class="sub-io__side sub-io__side--pure">pure</span></td>
57
- <td>組裝待寫入承載:<code>owner_user_id</code>、<code>allowed_registration=true</code>、<code>created_at=now</code>。</td>
58
- </tr>
59
- <tr>
60
- <td><code>shouldRetryOnCollision</code></td>
61
- <td class="sub-io__signature">
62
- <strong>in:</strong> <code>err</code>, <code>attempt: int</code>, <code>max: int</code><br>
63
- <strong>out:</strong> <code>bool</code>
64
- </td>
65
- <td><span class="sub-io__side sub-io__side--pure">pure</span></td>
66
- <td><code>err</code> UNIQUE 違例且 <code>attempt &lt; max</code> 才回 <code>true</code>。</td>
67
- </tr>
68
- </tbody>
69
- </table>
70
- </section>
71
-
72
- <section class="sub-vars">
73
- <h2>變數與業務用途</h2>
74
- <p class="sub-vars__intro">本表盤點額度判定、寫入承載組裝、與 UNIQUE 碰撞重試所依賴的識別子。</p>
75
- <table>
76
- <thead><tr><th>變數</th><th>型別</th><th>作用域</th><th>業務用途</th></tr></thead>
77
- <tbody>
78
- <tr>
79
- <td class="sub-vars__name">ctx</td>
80
- <td class="sub-vars__type">context.Context</td>
81
- <td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
82
- <td>限制整次發放的時間預算;避免重試迴圈無限延長導致連線資源被吃光。</td>
83
- </tr>
84
- <tr>
85
- <td class="sub-vars__name">userID</td>
86
- <td class="sub-vars__type">UUID</td>
87
- <td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
88
- <td>本次發放歸屬;同時也是額度視窗的主鍵;由 <code>public-api</code> 從可信來源(Principal)注入。</td>
89
- </tr>
90
- <tr>
91
- <td class="sub-vars__name">tx</td>
92
- <td class="sub-vars__type">*sql.Tx</td>
93
- <td><span class="sub-vars__scope sub-vars__scope--tx">tx</span></td>
94
- <td>把「額度檢查 + 帳號狀態檢查 + 寫入」綁成原子單元;避免在檢查與寫入之間出現空隙造成雙倍超發。</td>
95
- </tr>
96
- <tr>
97
- <td class="sub-vars__name">used</td>
98
- <td class="sub-vars__type">int64</td>
99
- <td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
100
- <td>當前滑動視窗已發放數;額度判定的「左值」;錯算即可能放行超額或誤拒。</td>
101
- </tr>
102
- <tr>
103
- <td class="sub-vars__name">limit</td>
104
- <td class="sub-vars__type">int</td>
105
- <td><span class="sub-vars__scope sub-vars__scope--instance">instance</span></td>
106
- <td>滑動視窗額度上限;產品設定,跨呼叫不變;改值代表產品策略改變,需配套更新文案。</td>
107
- </tr>
108
- <tr>
109
- <td class="sub-vars__name">userStatus</td>
110
- <td class="sub-vars__type">active | banned | suspended</td>
111
- <td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
112
- <td>帳號是否處於可發放狀態;停權或暫停狀態下立即拒絕,不浪費 generator/DB 資源。</td>
113
- </tr>
114
- <tr>
115
- <td class="sub-vars__name">now</td>
116
- <td class="sub-vars__type">time.Time</td>
117
- <td><span class="sub-vars__scope sub-vars__scope--call">call</span></td>
118
- <td>滑動視窗起點計算與 <code>created_at</code> 的基準;測試可注入。</td>
119
- </tr>
120
- <tr>
121
- <td class="sub-vars__name">attempt</td>
122
- <td class="sub-vars__type">int</td>
123
- <td><span class="sub-vars__scope sub-vars__scope--loop">loop</span></td>
124
- <td>UNIQUE 碰撞重試計數;搭配 <code>max</code> 限制最壞情況避免無限循環。</td>
125
- </tr>
126
- <tr>
127
- <td class="sub-vars__name">max</td>
128
- <td class="sub-vars__type">int</td>
129
- <td><span class="sub-vars__scope sub-vars__scope--instance">instance</span></td>
130
- <td>重試上限;超出即拋 <code>ErrCollisionExhausted</code>;上限與 generator 的熵預算共同決定罕見失敗率。</td>
131
- </tr>
132
- <tr>
133
- <td class="sub-vars__name">candidate</td>
134
- <td class="sub-vars__type">string</td>
135
- <td><span class="sub-vars__scope sub-vars__scope--loop">loop</span></td>
136
- <td>一輪重試取得的候選碼;尚未確定唯一;UNIQUE 違例即丟棄並重抽,不會被使用者看到。</td>
137
- </tr>
138
- <tr>
139
- <td class="sub-vars__name">InviteCodeRecord</td>
140
- <td class="sub-vars__type">{code, owner_user_id, allowed_registration, created_at}</td>
141
- <td><span class="sub-vars__scope sub-vars__scope--loop">loop</span></td>
142
- <td>寫入 DB 的承載;<code>allowed_registration=true</code> 是「邀請碼註冊」功能消費的關鍵旗標——關閉此值代表暫停發放但保留歷史紀錄。</td>
143
- </tr>
144
- </tbody>
145
- </table>
146
- </section>
147
-
148
- <section class="sub-dataflow">
149
- <h2>內部資料流</h2>
150
- <ol>
151
- <li>取得 <code>userID</code> + 開 <code>tx</code> → 讀<code>{used, userStatus}</code>(透過 DB 子模組;其 SQL 介面見宏觀圖邊 <code>e-B-...</code>)。</li>
152
- <li><code>checkQuota</code> 不通過 → 拋 <code>ErrQuotaExceeded</code>/<code>ErrForbidden</code>;本函式直接結束(呼叫端負責回滾)。</li>
153
- <li><code>candidate</code> ← 生成器產出的字串。</li>
154
- <li><code>buildRecord(userID, candidate, now)</code> → <code>InviteCodeRecord</code>。</li>
155
- <li>嘗試寫入;若 <code>err</code> 與 <code>shouldRetryOnCollision(err, attempt, max)</code> 為 <code>true</code>,<code>attempt+=1</code>,回到步驟 3。</li>
156
- <li>寫入成功 → 提交 → 回傳 <code>InviteCodeRecord</code>。</li>
157
- </ol>
158
- <svg class="sub-dataflow__svg" viewBox="0 0 720 220" role="img" aria-label="內部資料流">
159
- <defs>
160
- <marker id="d-arr2" markerWidth="9" markerHeight="9" refX="8" refY="4.5" orient="auto">
161
- <path d="M0,0 L9,4.5 L0,9 Z" fill="currentColor"></path>
162
- </marker>
163
- </defs>
164
- <g class="d-node"><rect x="20" y="30" width="150" height="50" rx="8"></rect><text x="95" y="58" text-anchor="middle">userID</text></g>
165
- <g class="d-node"><rect x="200" y="30" width="150" height="50" rx="8"></rect><text x="275" y="50" text-anchor="middle">checkQuota</text><text x="275" y="68" text-anchor="middle" class="d-label">→ true | err</text></g>
166
- <g class="d-node"><rect x="380" y="30" width="170" height="50" rx="8"></rect><text x="465" y="50" text-anchor="middle">candidate</text><text x="465" y="68" text-anchor="middle" class="d-label">← Generator.Next()</text></g>
167
- <g class="d-node"><rect x="580" y="30" width="120" height="50" rx="8"></rect><text x="640" y="58" text-anchor="middle">buildRecord</text></g>
168
-
169
- <g class="d-node"><rect x="380" y="140" width="170" height="50" rx="8"></rect><text x="465" y="160" text-anchor="middle">shouldRetryOnCollision</text><text x="465" y="178" text-anchor="middle" class="d-label">err+attempt → bool</text></g>
170
- <g class="d-node"><rect x="580" y="140" width="120" height="50" rx="8"></rect><text x="640" y="168" text-anchor="middle">InviteCodeRecord</text></g>
171
-
172
- <line class="d-edge" x1="170" y1="55" x2="196" y2="55" marker-end="url(#d-arr2)"></line>
173
- <line class="d-edge" x1="350" y1="55" x2="376" y2="55" marker-end="url(#d-arr2)"></line>
174
- <line class="d-edge" x1="550" y1="55" x2="576" y2="55" marker-end="url(#d-arr2)"></line>
175
- <line class="d-edge" x1="640" y1="80" x2="640" y2="136" marker-end="url(#d-arr2)"></line>
176
- <line class="d-edge d-edge--side" x1="465" y1="190" x2="465" y2="80" marker-end="url(#d-arr2)"></line>
177
- </svg>
178
- </section>
179
-
180
- <section class="sub-errors">
181
- <h2>本子模組會拋出的錯誤</h2>
182
- <table>
183
- <thead><tr><th>錯誤</th><th>條件</th></tr></thead>
184
- <tbody>
185
- <tr><td><code>ErrQuotaExceeded</code></td><td><code>checkQuota</code> 失敗(含 <code>retryAfter</code>)</td></tr>
186
- <tr><td><code>ErrForbidden</code></td><td>帳號停用</td></tr>
187
- <tr><td><code>ErrCollisionExhausted</code></td><td>重試次數用盡;極罕見,視熵設計。</td></tr>
188
- <tr><td><code>ErrTransient</code></td><td>連線中斷/<code>ctx</code> 取消</td></tr>
189
- </tbody>
190
- </table>
191
- </section>
192
-
193
- <footer class="atlas-meta" style="margin-top: 2rem">
194
- <p><a href="../../index.html">← 宏觀架構</a> · <a href="./index.html">← 功能總覽</a></p>
195
- </footer>
196
- </main>
197
- </body>
2
+ <html lang="en" data-atlas-page="submodule">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Get invite codes · invite-issuance-service</title>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <link rel="stylesheet" href="../../assets/architecture.css">
8
+ </head>
9
+ <body>
10
+ <header class="submodule-header">
11
+ <nav class="submodule-breadcrumb"><a href="../../index.html">← Atlas</a> · <a href="index.html">← Get invite codes</a></nav>
12
+ <h1>invite-issuance-service <small class="submodule-kind submodule-kind--service">Service</small></h1>
13
+ <p class="submodule-role">Domain service that mints and persists a single invite row per request.</p>
14
+ </header>
15
+ <main class="submodule-main">
16
+ <section class="sub-io" aria-label="Function I/O">
17
+ <h2>Function I/O</h2>
18
+ <table class="sub-table">
19
+ <thead><tr><th scope="col">Name</th><th scope="col">In</th><th scope="col">Out</th><th scope="col">Side</th><th scope="col">Purpose</th></tr></thead>
20
+ <tbody>
21
+ <tr><td>Issue</td><td>ctx, userId</td><td>InviteCode | error</td><td>tx</td><td>Generates a unique code and writes the matching invite_codes row.</td></tr>
22
+ <tr><td>generateCode</td><td>rand</td><td>code</td><td>pure</td><td>Produces a 10-char alphanumeric token.</td></tr>
23
+ </tbody>
24
+ </table>
25
+ </section>
26
+ <section class="sub-vars" aria-label="Variables">
27
+ <h2>Variables</h2>
28
+ <table class="sub-table">
29
+ <thead><tr><th scope="col">Name</th><th scope="col">Type</th><th scope="col">Scope</th><th scope="col">Purpose</th></tr></thead>
30
+ <tbody>
31
+ <tr><td>code</td><td>string</td><td>tx</td><td>Newly minted invite token recorded against the member.</td></tr>
32
+ <tr><td>row</td><td>InviteCodeRow</td><td>tx</td><td>Persisted invite_codes row carrying owner, code, expiry, and consumption state.</td></tr>
33
+ </tbody>
34
+ </table>
35
+ </section>
36
+ <section class="sub-dataflow" aria-label="Internal data flow">
37
+ <h2>Internal data flow</h2>
38
+ <svg class="sub-dataflow__svg" viewBox="0 0 400 348" role="img" aria-label="Internal dataflow">
39
+ <defs><marker id="sub-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 Z" /></marker></defs>
40
+ <g class="sub-dataflow__step">
41
+ <rect x="20" y="20" width="360" height="56" rx="8" ry="8" />
42
+ <text x="200" y="44" text-anchor="middle">Open transaction.</text>
43
+ </g>
44
+ <line class="sub-dataflow__arrow" x1="200" y1="76" x2="200" y2="104" marker-end="url(#sub-arrow)" />
45
+ <g class="sub-dataflow__step">
46
+ <rect x="20" y="104" width="360" height="56" rx="8" ry="8" />
47
+ <text x="200" y="128" text-anchor="middle">Generate candidate code via generateCode.</text>
48
+ </g>
49
+ <line class="sub-dataflow__arrow" x1="200" y1="160" x2="200" y2="188" marker-end="url(#sub-arrow)" />
50
+ <g class="sub-dataflow__step">
51
+ <rect x="20" y="188" width="360" height="56" rx="8" ry="8" />
52
+ <text x="200" y="212" text-anchor="middle">INSERT invite_codes row (retry on unique-violation).</text>
53
+ </g>
54
+ <line class="sub-dataflow__arrow" x1="200" y1="244" x2="200" y2="272" marker-end="url(#sub-arrow)" />
55
+ <g class="sub-dataflow__step">
56
+ <rect x="20" y="272" width="360" height="56" rx="8" ry="8" />
57
+ <text x="200" y="296" text-anchor="middle">Commit and return code.</text>
58
+ </g>
59
+ </svg>
60
+ </section>
61
+ <section class="sub-errors" aria-label="Errors">
62
+ <h2>Errors</h2>
63
+ <table class="sub-table">
64
+ <thead><tr><th scope="col">Name</th><th scope="col">When</th><th scope="col">Means</th></tr></thead>
65
+ <tbody>
66
+ <tr><td>ErrCollision</td><td>Unique constraint on code repeatedly violated.</td><td>Surface 503 after retry budget exhausted.</td></tr>
67
+ </tbody>
68
+ </table>
69
+ </section>
70
+ </main>
71
+ </body>
198
72
  </html>