freelang-v4 4.3.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 (261) hide show
  1. package/README.md +548 -0
  2. package/dist/ast.d.ts +367 -0
  3. package/dist/ast.js +4 -0
  4. package/dist/ast.js.map +1 -0
  5. package/dist/async-basic.test.d.ts +1 -0
  6. package/dist/async-basic.test.js +88 -0
  7. package/dist/async-basic.test.js.map +1 -0
  8. package/dist/async-jest.test.d.ts +1 -0
  9. package/dist/async-jest.test.js +99 -0
  10. package/dist/async-jest.test.js.map +1 -0
  11. package/dist/channel-jest.test.d.ts +1 -0
  12. package/dist/channel-jest.test.js +148 -0
  13. package/dist/channel-jest.test.js.map +1 -0
  14. package/dist/checker-jest.test.d.ts +1 -0
  15. package/dist/checker-jest.test.js +160 -0
  16. package/dist/checker-jest.test.js.map +1 -0
  17. package/dist/checker.d.ts +149 -0
  18. package/dist/checker.js +1565 -0
  19. package/dist/checker.js.map +1 -0
  20. package/dist/checker.test.d.ts +1 -0
  21. package/dist/checker.test.js +217 -0
  22. package/dist/checker.test.js.map +1 -0
  23. package/dist/compiler-jest.test.d.ts +1 -0
  24. package/dist/compiler-jest.test.js +233 -0
  25. package/dist/compiler-jest.test.js.map +1 -0
  26. package/dist/compiler.d.ts +127 -0
  27. package/dist/compiler.js +1588 -0
  28. package/dist/compiler.js.map +1 -0
  29. package/dist/compiler.test.d.ts +1 -0
  30. package/dist/compiler.test.js +313 -0
  31. package/dist/compiler.test.js.map +1 -0
  32. package/dist/db-100m-full.d.ts +5 -0
  33. package/dist/db-100m-full.js +78 -0
  34. package/dist/db-100m-full.js.map +1 -0
  35. package/dist/db-100m-no-index.d.ts +12 -0
  36. package/dist/db-100m-no-index.js +119 -0
  37. package/dist/db-100m-no-index.js.map +1 -0
  38. package/dist/db-100m-real.d.ts +5 -0
  39. package/dist/db-100m-real.js +131 -0
  40. package/dist/db-100m-real.js.map +1 -0
  41. package/dist/db-100m-streaming.d.ts +15 -0
  42. package/dist/db-100m-streaming.js +164 -0
  43. package/dist/db-100m-streaming.js.map +1 -0
  44. package/dist/db-100m-test.d.ts +5 -0
  45. package/dist/db-100m-test.js +111 -0
  46. package/dist/db-100m-test.js.map +1 -0
  47. package/dist/db-jest.test.d.ts +1 -0
  48. package/dist/db-jest.test.js +182 -0
  49. package/dist/db-jest.test.js.map +1 -0
  50. package/dist/db-runtime.d.ts +24 -0
  51. package/dist/db-runtime.js +204 -0
  52. package/dist/db-runtime.js.map +1 -0
  53. package/dist/db.d.ts +249 -0
  54. package/dist/db.js +593 -0
  55. package/dist/db.js.map +1 -0
  56. package/dist/file-io-jest.test.d.ts +1 -0
  57. package/dist/file-io-jest.test.js +225 -0
  58. package/dist/file-io-jest.test.js.map +1 -0
  59. package/dist/for-of-jest.test.d.ts +1 -0
  60. package/dist/for-of-jest.test.js +230 -0
  61. package/dist/for-of-jest.test.js.map +1 -0
  62. package/dist/for-of.test.d.ts +1 -0
  63. package/dist/for-of.test.js +305 -0
  64. package/dist/for-of.test.js.map +1 -0
  65. package/dist/function-literal-jest.test.d.ts +1 -0
  66. package/dist/function-literal-jest.test.js +180 -0
  67. package/dist/function-literal-jest.test.js.map +1 -0
  68. package/dist/function-literal.test.d.ts +1 -0
  69. package/dist/function-literal.test.js +245 -0
  70. package/dist/function-literal.test.js.map +1 -0
  71. package/dist/generics-jest.test.d.ts +1 -0
  72. package/dist/generics-jest.test.js +93 -0
  73. package/dist/generics-jest.test.js.map +1 -0
  74. package/dist/ir-gen.d.ts +15 -0
  75. package/dist/ir-gen.js +400 -0
  76. package/dist/ir-gen.js.map +1 -0
  77. package/dist/ir.d.ts +114 -0
  78. package/dist/ir.js +5 -0
  79. package/dist/ir.js.map +1 -0
  80. package/dist/lexer.d.ts +110 -0
  81. package/dist/lexer.js +467 -0
  82. package/dist/lexer.js.map +1 -0
  83. package/dist/lexer.test.d.ts +1 -0
  84. package/dist/lexer.test.js +426 -0
  85. package/dist/lexer.test.js.map +1 -0
  86. package/dist/main.d.ts +2 -0
  87. package/dist/main.js +241 -0
  88. package/dist/main.js.map +1 -0
  89. package/dist/module-jest.test.d.ts +1 -0
  90. package/dist/module-jest.test.js +123 -0
  91. package/dist/module-jest.test.js.map +1 -0
  92. package/dist/parser.d.ts +56 -0
  93. package/dist/parser.js +1060 -0
  94. package/dist/parser.js.map +1 -0
  95. package/dist/parser.test.d.ts +1 -0
  96. package/dist/parser.test.js +461 -0
  97. package/dist/parser.test.js.map +1 -0
  98. package/dist/pattern-matching-jest.test.d.ts +1 -0
  99. package/dist/pattern-matching-jest.test.js +158 -0
  100. package/dist/pattern-matching-jest.test.js.map +1 -0
  101. package/dist/pkg/init.d.ts +1 -0
  102. package/dist/pkg/init.js +118 -0
  103. package/dist/pkg/init.js.map +1 -0
  104. package/dist/pkg/install.d.ts +1 -0
  105. package/dist/pkg/install.js +77 -0
  106. package/dist/pkg/install.js.map +1 -0
  107. package/dist/pkg/registry.d.ts +23 -0
  108. package/dist/pkg/registry.js +106 -0
  109. package/dist/pkg/registry.js.map +1 -0
  110. package/dist/pkg/run.d.ts +1 -0
  111. package/dist/pkg/run.js +76 -0
  112. package/dist/pkg/run.js.map +1 -0
  113. package/dist/pkg/toml.d.ts +5 -0
  114. package/dist/pkg/toml.js +117 -0
  115. package/dist/pkg/toml.js.map +1 -0
  116. package/dist/repl.d.ts +15 -0
  117. package/dist/repl.js +197 -0
  118. package/dist/repl.js.map +1 -0
  119. package/dist/runtime/bytecode.d.ts +92 -0
  120. package/dist/runtime/bytecode.js +253 -0
  121. package/dist/runtime/bytecode.js.map +1 -0
  122. package/dist/runtime/value.d.ts +102 -0
  123. package/dist/runtime/value.js +302 -0
  124. package/dist/runtime/value.js.map +1 -0
  125. package/dist/runtime/vm.d.ts +65 -0
  126. package/dist/runtime/vm.js +293 -0
  127. package/dist/runtime/vm.js.map +1 -0
  128. package/dist/struct-instance-jest.test.d.ts +1 -0
  129. package/dist/struct-instance-jest.test.js +209 -0
  130. package/dist/struct-instance-jest.test.js.map +1 -0
  131. package/dist/struct-instance.test.d.ts +1 -0
  132. package/dist/struct-instance.test.js +291 -0
  133. package/dist/struct-instance.test.js.map +1 -0
  134. package/dist/struct-jest.test.d.ts +1 -0
  135. package/dist/struct-jest.test.js +176 -0
  136. package/dist/struct-jest.test.js.map +1 -0
  137. package/dist/struct.test.d.ts +1 -0
  138. package/dist/struct.test.js +231 -0
  139. package/dist/struct.test.js.map +1 -0
  140. package/dist/trait-jest.test.d.ts +1 -0
  141. package/dist/trait-jest.test.js +120 -0
  142. package/dist/trait-jest.test.js.map +1 -0
  143. package/dist/vm-jest.test.d.ts +1 -0
  144. package/dist/vm-jest.test.js +569 -0
  145. package/dist/vm-jest.test.js.map +1 -0
  146. package/dist/vm.d.ts +81 -0
  147. package/dist/vm.js +1956 -0
  148. package/dist/vm.js.map +1 -0
  149. package/dist/vm.test.d.ts +1 -0
  150. package/dist/vm.test.js +337 -0
  151. package/dist/vm.test.js.map +1 -0
  152. package/dist/web-repl/sandbox.d.ts +11 -0
  153. package/dist/web-repl/sandbox.js +76 -0
  154. package/dist/web-repl/sandbox.js.map +1 -0
  155. package/dist/web-repl/server.d.ts +1 -0
  156. package/dist/web-repl/server.js +111 -0
  157. package/dist/web-repl/server.js.map +1 -0
  158. package/dist/while-loop-jest.test.d.ts +1 -0
  159. package/dist/while-loop-jest.test.js +201 -0
  160. package/dist/while-loop-jest.test.js.map +1 -0
  161. package/dist/while-loop.test.d.ts +1 -0
  162. package/dist/while-loop.test.js +262 -0
  163. package/dist/while-loop.test.js.map +1 -0
  164. package/docs/EXPERIENCE.md +787 -0
  165. package/docs/README.md +175 -0
  166. package/docs/V1_V2_V3_ANALYSIS.md +107 -0
  167. package/docs/_config.yml +36 -0
  168. package/docs/api-reference.md +459 -0
  169. package/docs/architecture.md +470 -0
  170. package/docs/benchmarks.md +295 -0
  171. package/docs/comparison.md +454 -0
  172. package/docs/index.md +335 -0
  173. package/docs/language-completeness.md +228 -0
  174. package/docs/learning-guide.md +651 -0
  175. package/package.json +65 -0
  176. package/src/api/deploy_key.fl +294 -0
  177. package/src/api/issue.fl +302 -0
  178. package/src/api/org.fl +356 -0
  179. package/src/api/repo.fl +394 -0
  180. package/src/api/team.fl +299 -0
  181. package/src/api/user.fl +385 -0
  182. package/src/api/webhook.fl +273 -0
  183. package/src/ast.ts +158 -0
  184. package/src/async-basic.test.ts +94 -0
  185. package/src/async-jest.test.ts +107 -0
  186. package/src/channel-jest.test.ts +158 -0
  187. package/src/checker-jest.test.ts +189 -0
  188. package/src/checker.test.ts +279 -0
  189. package/src/checker.ts +1861 -0
  190. package/src/commands/analyze.fl +227 -0
  191. package/src/commands/auth.fl +315 -0
  192. package/src/commands/batch.fl +349 -0
  193. package/src/commands/config.fl +199 -0
  194. package/src/commands/deploy_key.fl +352 -0
  195. package/src/commands/issue.fl +275 -0
  196. package/src/commands/main.fl +492 -0
  197. package/src/commands/org.fl +425 -0
  198. package/src/commands/repo.fl +581 -0
  199. package/src/commands/team.fl +244 -0
  200. package/src/commands/user.fl +423 -0
  201. package/src/commands/webhook.fl +400 -0
  202. package/src/compiler-jest.test.ts +275 -0
  203. package/src/compiler.test.ts +375 -0
  204. package/src/compiler.ts +1770 -0
  205. package/src/config.fl +175 -0
  206. package/src/core/batch.fl +355 -0
  207. package/src/core/cache.fl +284 -0
  208. package/src/core/ensure.fl +324 -0
  209. package/src/db-100m-full.ts +96 -0
  210. package/src/db-100m-no-index.ts +133 -0
  211. package/src/db-100m-real.ts +152 -0
  212. package/src/db-100m-streaming.ts +154 -0
  213. package/src/db-100m-test.ts +136 -0
  214. package/src/db-jest.test.ts +161 -0
  215. package/src/db-runtime.ts +242 -0
  216. package/src/db.ts +676 -0
  217. package/src/errors.fl +134 -0
  218. package/src/for-of-jest.test.ts +246 -0
  219. package/src/for-of.test.ts +308 -0
  220. package/src/function-literal-jest.test.ts +193 -0
  221. package/src/function-literal.test.ts +248 -0
  222. package/src/generics-jest.test.ts +104 -0
  223. package/src/http/client.fl +327 -0
  224. package/src/ir-gen.ts +459 -0
  225. package/src/ir.ts +80 -0
  226. package/src/lexer.test.ts +499 -0
  227. package/src/lexer.ts +522 -0
  228. package/src/main.ts +223 -0
  229. package/src/models.fl +162 -0
  230. package/src/module-jest.test.ts +145 -0
  231. package/src/parser.test.ts +542 -0
  232. package/src/parser.ts +1211 -0
  233. package/src/pattern-matching-jest.test.ts +170 -0
  234. package/src/pkg/init.ts +91 -0
  235. package/src/pkg/install.ts +56 -0
  236. package/src/pkg/registry.ts +103 -0
  237. package/src/pkg/run.ts +49 -0
  238. package/src/pkg/toml.ts +129 -0
  239. package/src/repl.ts +190 -0
  240. package/src/runtime/bytecode.ts +291 -0
  241. package/src/runtime/value.ts +322 -0
  242. package/src/runtime/vm.ts +354 -0
  243. package/src/self-host/bootstrap.fl +68 -0
  244. package/src/self-host/interpreter.fl +361 -0
  245. package/src/self-host/lexer-simple.fl +22 -0
  246. package/src/self-host/lexer.fl +305 -0
  247. package/src/self-host/parser.fl +580 -0
  248. package/src/struct-instance-jest.test.ts +221 -0
  249. package/src/struct-instance.test.ts +293 -0
  250. package/src/struct-jest.test.ts +187 -0
  251. package/src/struct.test.ts +234 -0
  252. package/src/trait-jest.test.ts +136 -0
  253. package/src/vm-jest.test.ts +754 -0
  254. package/src/vm.ts +1976 -0
  255. package/src/web-repl/public/index.html +50 -0
  256. package/src/web-repl/public/main.js +105 -0
  257. package/src/web-repl/public/style.css +225 -0
  258. package/src/web-repl/sandbox.ts +88 -0
  259. package/src/web-repl/server.ts +97 -0
  260. package/src/while-loop-jest.test.ts +218 -0
  261. package/src/while-loop.test.ts +267 -0
@@ -0,0 +1,394 @@
1
+ // FreeLang v4.2 — Gogs CLI RepoService
2
+ // 저장소 관리 API 서비스
3
+ // 400라인: list, get, create, update, delete, ensure, fork, list_branches
4
+
5
+ /// 저장소 서비스
6
+ struct RepoService {
7
+ client: HttpClient
8
+ cache: CacheManager
9
+ }
10
+
11
+ /// RepoService 생성
12
+ fn new_repo_service(client: HttpClient, cache: CacheManager) -> RepoService {
13
+ RepoService {
14
+ client: client,
15
+ cache: cache
16
+ }
17
+ }
18
+
19
+ // ==================== 저장소 CRUD ====================
20
+
21
+ /// 저장소 목록 조회 (캐시 활용)
22
+ async fn list_repos(service: RepoService) -> Result<[Repo]> {
23
+ var cache_key = "repos:list"
24
+
25
+ // 캐시 확인
26
+ var cached = cache_get(service.cache, cache_key)
27
+ if cached != "" {
28
+ println("[cache] hit: repos list")
29
+ var repos_arr = json_parse(cached) as [Repo]
30
+ return Result::Ok(repos_arr)
31
+ }
32
+
33
+ // API 호출
34
+ var resp = await http_get(service.client, "/api/v1/user/repos")
35
+
36
+ if is_error(resp.status) {
37
+ return Result::Err(response_to_error(resp))
38
+ }
39
+
40
+ // 캐시 저장
41
+ cache_set(service.cache, cache_key, resp.body)
42
+
43
+ // 파싱
44
+ var repos_arr = json_parse(resp.body) as [Repo]
45
+ Result::Ok(repos_arr)
46
+ }
47
+
48
+ /// 저장소 상세 조회 (캐시 활용)
49
+ async fn get_repo(service: RepoService, owner: str, name: str) -> Result<Repo> {
50
+ var cache_key = "repo:" + owner + "/" + name
51
+
52
+ // 캐시 확인
53
+ var cached = cache_get(service.cache, cache_key)
54
+ if cached != "" {
55
+ println("[cache] hit: " + cache_key)
56
+ var repo = json_parse(cached) as Repo
57
+ return Result::Ok(repo)
58
+ }
59
+
60
+ // API 호출
61
+ var path = "/api/v1/repos/" + owner + "/" + name
62
+ var resp = await http_get(service.client, path)
63
+
64
+ if is_error(resp.status) {
65
+ return Result::Err(response_to_error(resp))
66
+ }
67
+
68
+ // 캐시 저장
69
+ cache_set(service.cache, cache_key, resp.body)
70
+
71
+ // 파싱
72
+ var repo = json_parse(resp.body) as Repo
73
+ Result::Ok(repo)
74
+ }
75
+
76
+ /// 저장소 생성
77
+ async fn create_repo(service: RepoService, req: CreateRepoRequest) -> Result<Repo> {
78
+ var json_data = json_stringify(req)
79
+ var resp = await http_post_json(service.client, "/api/v1/user/repos", json_data)
80
+
81
+ if is_error(resp.status) {
82
+ return Result::Err(response_to_error(resp))
83
+ }
84
+
85
+ // 캐시 무효화
86
+ cache_invalidate(service.cache, "repos:list")
87
+
88
+ // 파싱
89
+ var repo = json_parse(resp.body) as Repo
90
+ Result::Ok(repo)
91
+ }
92
+
93
+ /// 저장소 업데이트
94
+ async fn update_repo(service: RepoService, owner: str, name: str, req: CreateRepoRequest) -> Result<Repo> {
95
+ var path = "/api/v1/repos/" + owner + "/" + name
96
+ var json_data = json_stringify(req)
97
+ var resp = await http_patch(service.client, path, json_data)
98
+
99
+ if is_error(resp.status) {
100
+ return Result::Err(response_to_error(resp))
101
+ }
102
+
103
+ // 캐시 무효화
104
+ var cache_key = "repo:" + owner + "/" + name
105
+ cache_invalidate(service.cache, cache_key)
106
+ cache_invalidate(service.cache, "repos:list")
107
+
108
+ // 파싱
109
+ var repo = json_parse(resp.body) as Repo
110
+ Result::Ok(repo)
111
+ }
112
+
113
+ /// 저장소 삭제
114
+ async fn delete_repo(service: RepoService, owner: str, name: str) -> Result<str> {
115
+ var path = "/api/v1/repos/" + owner + "/" + name
116
+ var resp = await http_delete(service.client, path)
117
+
118
+ if is_error(resp.status) {
119
+ return Result::Err(response_to_error(resp))
120
+ }
121
+
122
+ // 캐시 무효화
123
+ var cache_key = "repo:" + owner + "/" + name
124
+ cache_invalidate(service.cache, cache_key)
125
+ cache_invalidate(service.cache, "repos:list")
126
+
127
+ Result::Ok("Repository deleted")
128
+ }
129
+
130
+ // ==================== Ensure 패턴 ====================
131
+
132
+ /// 저장소 ensure (멱등성 보증)
133
+ async fn ensure_repo(service: RepoService, owner: str, req: CreateRepoRequest) -> Result<EnsureAction> {
134
+ var path = "/api/v1/repos/" + owner + "/" + req.name
135
+
136
+ // desired 상태 정의
137
+ var desired_json = json_stringify(req)
138
+
139
+ var ctx = EnsureContext {
140
+ name: owner + "/" + req.name,
141
+
142
+ get_fn: async fn() -> Result<str> {
143
+ var resp = await http_get(service.client, path)
144
+ if is_error(resp.status) {
145
+ if resp.status == 404 {
146
+ Result::Ok("") // 없음
147
+ } else {
148
+ Result::Err(response_to_error(resp))
149
+ }
150
+ } else {
151
+ Result::Ok(resp.body)
152
+ }
153
+ },
154
+
155
+ create_fn: async fn() -> Result<str> {
156
+ var json_data = json_stringify(req)
157
+ var resp = await http_post_json(service.client, "/api/v1/user/repos", json_data)
158
+ if is_error(resp.status) {
159
+ Result::Err(response_to_error(resp))
160
+ } else {
161
+ // 캐시 무효화
162
+ cache_invalidate(service.cache, "repos:list")
163
+ Result::Ok(resp.body)
164
+ }
165
+ },
166
+
167
+ update_fn: async fn(changes: [str]) -> Result<str> {
168
+ var json_data = json_stringify(req)
169
+ var resp = await http_patch(service.client, path, json_data)
170
+ if is_error(resp.status) {
171
+ Result::Err(response_to_error(resp))
172
+ } else {
173
+ // 캐시 무효화
174
+ var cache_key = "repo:" + owner + "/" + req.name
175
+ cache_invalidate(service.cache, cache_key)
176
+ cache_invalidate(service.cache, "repos:list")
177
+ Result::Ok(resp.body)
178
+ }
179
+ },
180
+
181
+ delete_fn: async fn() -> Result<str> {
182
+ var resp = await http_delete(service.client, path)
183
+ if is_error(resp.status) {
184
+ Result::Err(response_to_error(resp))
185
+ } else {
186
+ // 캐시 무효화
187
+ var cache_key = "repo:" + owner + "/" + req.name
188
+ cache_invalidate(service.cache, cache_key)
189
+ cache_invalidate(service.cache, "repos:list")
190
+ Result::Ok("")
191
+ }
192
+ },
193
+
194
+ desired: desired_json
195
+ }
196
+
197
+ var action = await ensure(ctx)
198
+ Result::Ok(action)
199
+ }
200
+
201
+ // ==================== 저장소 포크 ====================
202
+
203
+ /// 저장소 포크
204
+ async fn fork_repo(service: RepoService, owner: str, name: str, new_owner: str) -> Result<Repo> {
205
+ var path = "/api/v1/repos/" + owner + "/" + name + "/forks"
206
+
207
+ // 포크 요청 본문
208
+ var body_str = "{\"owner\": \"" + new_owner + "\"}"
209
+ var resp = await http_post_json(service.client, path, body_str)
210
+
211
+ if is_error(resp.status) {
212
+ return Result::Err(response_to_error(resp))
213
+ }
214
+
215
+ // 캐시 무효화 (사용자 저장소 목록)
216
+ cache_invalidate(service.cache, "repos:list")
217
+
218
+ // 파싱
219
+ var repo = json_parse(resp.body) as Repo
220
+ Result::Ok(repo)
221
+ }
222
+
223
+ // ==================== 브랜치 관리 ====================
224
+
225
+ /// 저장소 브랜치 목록
226
+ async fn list_branches(service: RepoService, owner: str, name: str) -> Result<[Branch]> {
227
+ var cache_key = "branches:" + owner + "/" + name
228
+
229
+ // 캐시 확인
230
+ var cached = cache_get(service.cache, cache_key)
231
+ if cached != "" {
232
+ println("[cache] hit: " + cache_key)
233
+ var branches_arr = json_parse(cached) as [Branch]
234
+ return Result::Ok(branches_arr)
235
+ }
236
+
237
+ // API 호출
238
+ var path = "/api/v1/repos/" + owner + "/" + name + "/branches"
239
+ var resp = await http_get(service.client, path)
240
+
241
+ if is_error(resp.status) {
242
+ return Result::Err(response_to_error(resp))
243
+ }
244
+
245
+ // 캐시 저장
246
+ cache_set(service.cache, cache_key, resp.body)
247
+
248
+ // 파싱
249
+ var branches_arr = json_parse(resp.body) as [Branch]
250
+ Result::Ok(branches_arr)
251
+ }
252
+
253
+ // ==================== 저장소 통계 ====================
254
+
255
+ /// 저장소 통계 조회
256
+ async fn get_repo_stats(service: RepoService, owner: str, name: str) -> Result<RepoStats> {
257
+ var path = "/api/v1/repos/" + owner + "/" + name + "/stats"
258
+ var resp = await http_get(service.client, path)
259
+
260
+ if is_error(resp.status) {
261
+ return Result::Err(response_to_error(resp))
262
+ }
263
+
264
+ // 파싱
265
+ var stats = json_parse(resp.body) as RepoStats
266
+ Result::Ok(stats)
267
+ }
268
+
269
+ // ==================== 저장소 검색 ====================
270
+
271
+ /// 저장소 검색
272
+ async fn search_repos(service: RepoService, query: str) -> Result<[Repo]> {
273
+ var path = "/api/v1/repos/search?q=" + query
274
+ var resp = await http_get(service.client, path)
275
+
276
+ if is_error(resp.status) {
277
+ return Result::Err(response_to_error(resp))
278
+ }
279
+
280
+ // 파싱
281
+ var repos_arr = json_parse(resp.body) as [Repo]
282
+ Result::Ok(repos_arr)
283
+ }
284
+
285
+ // ==================== 저장소 권한 ====================
286
+
287
+ /// 저장소 협업자 추가
288
+ async fn add_collaborator(
289
+ service: RepoService,
290
+ owner: str,
291
+ name: str,
292
+ username: str,
293
+ permission: str
294
+ ) -> Result<str> {
295
+ var path = "/api/v1/repos/" + owner + "/" + name + "/collaborators/" + username
296
+ var body_str = "{\"permission\": \"" + permission + "\"}"
297
+ var resp = await http_put(service.client, path, body_str)
298
+
299
+ if is_error(resp.status) {
300
+ return Result::Err(response_to_error(resp))
301
+ }
302
+
303
+ Result::Ok("Collaborator added")
304
+ }
305
+
306
+ /// 저장소 협업자 제거
307
+ async fn remove_collaborator(service: RepoService, owner: str, name: str, username: str) -> Result<str> {
308
+ var path = "/api/v1/repos/" + owner + "/" + name + "/collaborators/" + username
309
+ var resp = await http_delete(service.client, path)
310
+
311
+ if is_error(resp.status) {
312
+ return Result::Err(response_to_error(resp))
313
+ }
314
+
315
+ Result::Ok("Collaborator removed")
316
+ }
317
+
318
+ // ==================== 저장소 이슈 ====================
319
+
320
+ /// 저장소 이슈 수 조회
321
+ async fn get_issue_count(service: RepoService, owner: str, name: str) -> Result<i32> {
322
+ var path = "/api/v1/repos/" + owner + "/" + name + "/issues"
323
+ var resp = await http_get(service.client, path)
324
+
325
+ if is_error(resp.status) {
326
+ return Result::Err(response_to_error(resp))
327
+ }
328
+
329
+ // 이슈 배열 파싱
330
+ var issues_arr = json_parse(resp.body) as [Issue]
331
+ Result::Ok(length(issues_arr))
332
+ }
333
+
334
+ // ==================== 별명 (Alias) ====================
335
+
336
+ /// 저장소 목록 조회 (별명)
337
+ async fn list_all_repos(service: RepoService) -> Result<[Repo]> {
338
+ await list_repos(service)
339
+ }
340
+
341
+ /// 저장소 상세 조회 (별명)
342
+ async fn get_single_repo(service: RepoService, owner: str, name: str) -> Result<Repo> {
343
+ await get_repo(service, owner, name)
344
+ }
345
+
346
+ /// 저장소 생성 (별명)
347
+ async fn new_repo(service: RepoService, name: str, description: str, private: bool) -> Result<Repo> {
348
+ var req = CreateRepoRequest {
349
+ name: name,
350
+ description: description,
351
+ private: private
352
+ }
353
+ await create_repo(service, req)
354
+ }
355
+
356
+ /// 저장소 삭제 (별명)
357
+ async fn rm_repo(service: RepoService, owner: str, name: str) -> Result<str> {
358
+ await delete_repo(service, owner, name)
359
+ }
360
+
361
+ // Branch 모델 정의
362
+ struct Branch {
363
+ name: str
364
+ commit: Commit
365
+ protected: bool
366
+ }
367
+
368
+ struct Commit {
369
+ id: str
370
+ message: str
371
+ author: Author
372
+ }
373
+
374
+ struct Author {
375
+ name: str
376
+ email: str
377
+ }
378
+
379
+ // RepoStats 모델 정의
380
+ struct RepoStats {
381
+ total_commits: i32
382
+ total_branches: i32
383
+ total_tags: i32
384
+ total_issues: i32
385
+ total_pull_requests: i32
386
+ }
387
+
388
+ // 필요한 함수들 (다른 모듈에서 임포트):
389
+ // - http_get, http_post_json, http_delete, http_patch, http_put
390
+ // - is_error, response_to_error
391
+ // - cache_get, cache_set, cache_invalidate
392
+ // - json_stringify, json_parse
393
+ // - ensure, EnsureContext, EnsureAction
394
+ // - println, length
@@ -0,0 +1,299 @@
1
+ // FreeLang v4.2 — Gogs CLI TeamService
2
+ // 팀 관리 API 서비스
3
+ // 200라인: create, list, delete, add_member, remove_member, list_members
4
+
5
+ /// 팀 서비스
6
+ struct TeamService {
7
+ client: HttpClient
8
+ cache: CacheManager
9
+ }
10
+
11
+ /// TeamService 생성
12
+ fn new_team_service(client: HttpClient, cache: CacheManager) -> TeamService {
13
+ TeamService {
14
+ client: client,
15
+ cache: cache
16
+ }
17
+ }
18
+
19
+ // ==================== 팀 CRUD ====================
20
+
21
+ /// 팀 목록 조회 (조직 단위)
22
+ async fn list_teams(service: TeamService, org_name: str) -> Result<[Team]> {
23
+ var cache_key = "teams:" + org_name
24
+
25
+ // 캐시 확인
26
+ var cached = cache_get(service.cache, cache_key)
27
+ if cached != "" {
28
+ println("[cache] hit: " + cache_key)
29
+ var teams_arr = json_parse(cached) as [Team]
30
+ return Result::Ok(teams_arr)
31
+ }
32
+
33
+ // API 호출
34
+ var path = "/api/v1/orgs/" + org_name + "/teams"
35
+ var resp = await http_get(service.client, path)
36
+
37
+ if is_error(resp.status) {
38
+ return Result::Err(response_to_error(resp))
39
+ }
40
+
41
+ // 캐시 저장
42
+ cache_set(service.cache, cache_key, resp.body)
43
+
44
+ // 파싱
45
+ var teams_arr = json_parse(resp.body) as [Team]
46
+ Result::Ok(teams_arr)
47
+ }
48
+
49
+ /// 팀 상세 조회
50
+ async fn get_team(service: TeamService, team_id: i32) -> Result<Team> {
51
+ var cache_key = "team:" + str(team_id)
52
+
53
+ // 캐시 확인
54
+ var cached = cache_get(service.cache, cache_key)
55
+ if cached != "" {
56
+ println("[cache] hit: " + cache_key)
57
+ var team = json_parse(cached) as Team
58
+ return Result::Ok(team)
59
+ }
60
+
61
+ // API 호출
62
+ var path = "/api/v1/teams/" + str(team_id)
63
+ var resp = await http_get(service.client, path)
64
+
65
+ if is_error(resp.status) {
66
+ return Result::Err(response_to_error(resp))
67
+ }
68
+
69
+ // 캐시 저장
70
+ cache_set(service.cache, cache_key, resp.body)
71
+
72
+ // 파싱
73
+ var team = json_parse(resp.body) as Team
74
+ Result::Ok(team)
75
+ }
76
+
77
+ /// 팀 생성
78
+ async fn create_team(service: TeamService, org_name: str, req: CreateTeamRequest) -> Result<Team> {
79
+ var path = "/api/v1/orgs/" + org_name + "/teams"
80
+ var json_data = json_stringify(req)
81
+ var resp = await http_post_json(service.client, path, json_data)
82
+
83
+ if is_error(resp.status) {
84
+ return Result::Err(response_to_error(resp))
85
+ }
86
+
87
+ // 캐시 무효화
88
+ var cache_key = "teams:" + org_name
89
+ cache_invalidate(service.cache, cache_key)
90
+
91
+ // 파싱
92
+ var team = json_parse(resp.body) as Team
93
+ Result::Ok(team)
94
+ }
95
+
96
+ /// 팀 업데이트
97
+ async fn update_team(service: TeamService, team_id: i32, req: UpdateTeamRequest) -> Result<Team> {
98
+ var path = "/api/v1/teams/" + str(team_id)
99
+ var json_data = json_stringify(req)
100
+ var resp = await http_patch(service.client, path, json_data)
101
+
102
+ if is_error(resp.status) {
103
+ return Result::Err(response_to_error(resp))
104
+ }
105
+
106
+ // 캐시 무효화
107
+ var cache_key = "team:" + str(team_id)
108
+ cache_invalidate(service.cache, cache_key)
109
+
110
+ // 파싱
111
+ var team = json_parse(resp.body) as Team
112
+ Result::Ok(team)
113
+ }
114
+
115
+ /// 팀 삭제
116
+ async fn delete_team(service: TeamService, team_id: i32) -> Result<str> {
117
+ var path = "/api/v1/teams/" + str(team_id)
118
+ var resp = await http_delete(service.client, path)
119
+
120
+ if is_error(resp.status) {
121
+ return Result::Err(response_to_error(resp))
122
+ }
123
+
124
+ // 캐시 무효화
125
+ var cache_key = "team:" + str(team_id)
126
+ cache_invalidate(service.cache, cache_key)
127
+
128
+ Result::Ok("Team deleted")
129
+ }
130
+
131
+ // ==================== 팀 멤버 ====================
132
+
133
+ /// 팀 멤버 목록
134
+ async fn list_team_members(service: TeamService, team_id: i32) -> Result<[User]> {
135
+ var cache_key = "team_members:" + str(team_id)
136
+
137
+ // 캐시 확인
138
+ var cached = cache_get(service.cache, cache_key)
139
+ if cached != "" {
140
+ println("[cache] hit: " + cache_key)
141
+ var members_arr = json_parse(cached) as [User]
142
+ return Result::Ok(members_arr)
143
+ }
144
+
145
+ // API 호출
146
+ var path = "/api/v1/teams/" + str(team_id) + "/members"
147
+ var resp = await http_get(service.client, path)
148
+
149
+ if is_error(resp.status) {
150
+ return Result::Err(response_to_error(resp))
151
+ }
152
+
153
+ // 캐시 저장
154
+ cache_set(service.cache, cache_key, resp.body)
155
+
156
+ // 파싱
157
+ var members_arr = json_parse(resp.body) as [User]
158
+ Result::Ok(members_arr)
159
+ }
160
+
161
+ /// 팀 멤버 추가
162
+ async fn add_team_member(service: TeamService, team_id: i32, username: str) -> Result<str> {
163
+ var path = "/api/v1/teams/" + str(team_id) + "/members/" + username
164
+ var resp = await http_put(service.client, path, "{}")
165
+
166
+ if is_error(resp.status) {
167
+ return Result::Err(response_to_error(resp))
168
+ }
169
+
170
+ // 캐시 무효화
171
+ var cache_key = "team_members:" + str(team_id)
172
+ cache_invalidate(service.cache, cache_key)
173
+
174
+ Result::Ok("Member added to team")
175
+ }
176
+
177
+ /// 팀 멤버 제거
178
+ async fn remove_team_member(service: TeamService, team_id: i32, username: str) -> Result<str> {
179
+ var path = "/api/v1/teams/" + str(team_id) + "/members/" + username
180
+ var resp = await http_delete(service.client, path)
181
+
182
+ if is_error(resp.status) {
183
+ return Result::Err(response_to_error(resp))
184
+ }
185
+
186
+ // 캐시 무효화
187
+ var cache_key = "team_members:" + str(team_id)
188
+ cache_invalidate(service.cache, cache_key)
189
+
190
+ Result::Ok("Member removed from team")
191
+ }
192
+
193
+ /// 사용자가 팀의 멤버인지 확인
194
+ async fn is_team_member(service: TeamService, team_id: i32, username: str) -> Result<bool> {
195
+ var path = "/api/v1/teams/" + str(team_id) + "/members/" + username
196
+ var resp = await http_get(service.client, path)
197
+
198
+ if is_error(resp.status) {
199
+ if resp.status == 404 {
200
+ return Result::Ok(false)
201
+ }
202
+ return Result::Err(response_to_error(resp))
203
+ }
204
+
205
+ Result::Ok(true)
206
+ }
207
+
208
+ // ==================== 팀 저장소 ====================
209
+
210
+ /// 팀 저장소 목록
211
+ async fn list_team_repos(service: TeamService, team_id: i32) -> Result<[Repo]> {
212
+ var cache_key = "team_repos:" + str(team_id)
213
+
214
+ // 캐시 확인
215
+ var cached = cache_get(service.cache, cache_key)
216
+ if cached != "" {
217
+ println("[cache] hit: " + cache_key)
218
+ var repos_arr = json_parse(cached) as [Repo]
219
+ return Result::Ok(repos_arr)
220
+ }
221
+
222
+ // API 호출
223
+ var path = "/api/v1/teams/" + str(team_id) + "/repos"
224
+ var resp = await http_get(service.client, path)
225
+
226
+ if is_error(resp.status) {
227
+ return Result::Err(response_to_error(resp))
228
+ }
229
+
230
+ // 캐시 저장
231
+ cache_set(service.cache, cache_key, resp.body)
232
+
233
+ // 파싱
234
+ var repos_arr = json_parse(resp.body) as [Repo]
235
+ Result::Ok(repos_arr)
236
+ }
237
+
238
+ /// 팀에 저장소 추가
239
+ async fn add_team_repo(service: TeamService, team_id: i32, owner: str, repo: str) -> Result<str> {
240
+ var path = "/api/v1/teams/" + str(team_id) + "/repos/" + owner + "/" + repo
241
+ var resp = await http_put(service.client, path, "{}")
242
+
243
+ if is_error(resp.status) {
244
+ return Result::Err(response_to_error(resp))
245
+ }
246
+
247
+ // 캐시 무효화
248
+ var cache_key = "team_repos:" + str(team_id)
249
+ cache_invalidate(service.cache, cache_key)
250
+
251
+ Result::Ok("Repository added to team")
252
+ }
253
+
254
+ /// 팀에서 저장소 제거
255
+ async fn remove_team_repo(service: TeamService, team_id: i32, owner: str, repo: str) -> Result<str> {
256
+ var path = "/api/v1/teams/" + str(team_id) + "/repos/" + owner + "/" + repo
257
+ var resp = await http_delete(service.client, path)
258
+
259
+ if is_error(resp.status) {
260
+ return Result::Err(response_to_error(resp))
261
+ }
262
+
263
+ // 캐시 무효화
264
+ var cache_key = "team_repos:" + str(team_id)
265
+ cache_invalidate(service.cache, cache_key)
266
+
267
+ Result::Ok("Repository removed from team")
268
+ }
269
+
270
+ // ==================== 별명 (Alias) ====================
271
+
272
+ /// 팀 생성 (별명)
273
+ async fn new_team(service: TeamService, org_name: str, name: str, description: str) -> Result<Team> {
274
+ var req = CreateTeamRequest {
275
+ name: name,
276
+ description: description,
277
+ permission: "push"
278
+ }
279
+ await create_team(service, org_name, req)
280
+ }
281
+
282
+ /// 팀 삭제 (별명)
283
+ async fn rm_team(service: TeamService, team_id: i32) -> Result<str> {
284
+ await delete_team(service, team_id)
285
+ }
286
+
287
+ // UpdateTeamRequest 모델 정의
288
+ struct UpdateTeamRequest {
289
+ name: str
290
+ description: str
291
+ permission: str
292
+ }
293
+
294
+ // 필요한 함수들 (다른 모듈에서 임포트):
295
+ // - http_get, http_post_json, http_delete, http_patch, http_put
296
+ // - is_error, response_to_error
297
+ // - cache_get, cache_set, cache_invalidate
298
+ // - json_stringify, json_parse
299
+ // - println, length, str