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.
- package/README.md +548 -0
- package/dist/ast.d.ts +367 -0
- package/dist/ast.js +4 -0
- package/dist/ast.js.map +1 -0
- package/dist/async-basic.test.d.ts +1 -0
- package/dist/async-basic.test.js +88 -0
- package/dist/async-basic.test.js.map +1 -0
- package/dist/async-jest.test.d.ts +1 -0
- package/dist/async-jest.test.js +99 -0
- package/dist/async-jest.test.js.map +1 -0
- package/dist/channel-jest.test.d.ts +1 -0
- package/dist/channel-jest.test.js +148 -0
- package/dist/channel-jest.test.js.map +1 -0
- package/dist/checker-jest.test.d.ts +1 -0
- package/dist/checker-jest.test.js +160 -0
- package/dist/checker-jest.test.js.map +1 -0
- package/dist/checker.d.ts +149 -0
- package/dist/checker.js +1565 -0
- package/dist/checker.js.map +1 -0
- package/dist/checker.test.d.ts +1 -0
- package/dist/checker.test.js +217 -0
- package/dist/checker.test.js.map +1 -0
- package/dist/compiler-jest.test.d.ts +1 -0
- package/dist/compiler-jest.test.js +233 -0
- package/dist/compiler-jest.test.js.map +1 -0
- package/dist/compiler.d.ts +127 -0
- package/dist/compiler.js +1588 -0
- package/dist/compiler.js.map +1 -0
- package/dist/compiler.test.d.ts +1 -0
- package/dist/compiler.test.js +313 -0
- package/dist/compiler.test.js.map +1 -0
- package/dist/db-100m-full.d.ts +5 -0
- package/dist/db-100m-full.js +78 -0
- package/dist/db-100m-full.js.map +1 -0
- package/dist/db-100m-no-index.d.ts +12 -0
- package/dist/db-100m-no-index.js +119 -0
- package/dist/db-100m-no-index.js.map +1 -0
- package/dist/db-100m-real.d.ts +5 -0
- package/dist/db-100m-real.js +131 -0
- package/dist/db-100m-real.js.map +1 -0
- package/dist/db-100m-streaming.d.ts +15 -0
- package/dist/db-100m-streaming.js +164 -0
- package/dist/db-100m-streaming.js.map +1 -0
- package/dist/db-100m-test.d.ts +5 -0
- package/dist/db-100m-test.js +111 -0
- package/dist/db-100m-test.js.map +1 -0
- package/dist/db-jest.test.d.ts +1 -0
- package/dist/db-jest.test.js +182 -0
- package/dist/db-jest.test.js.map +1 -0
- package/dist/db-runtime.d.ts +24 -0
- package/dist/db-runtime.js +204 -0
- package/dist/db-runtime.js.map +1 -0
- package/dist/db.d.ts +249 -0
- package/dist/db.js +593 -0
- package/dist/db.js.map +1 -0
- package/dist/file-io-jest.test.d.ts +1 -0
- package/dist/file-io-jest.test.js +225 -0
- package/dist/file-io-jest.test.js.map +1 -0
- package/dist/for-of-jest.test.d.ts +1 -0
- package/dist/for-of-jest.test.js +230 -0
- package/dist/for-of-jest.test.js.map +1 -0
- package/dist/for-of.test.d.ts +1 -0
- package/dist/for-of.test.js +305 -0
- package/dist/for-of.test.js.map +1 -0
- package/dist/function-literal-jest.test.d.ts +1 -0
- package/dist/function-literal-jest.test.js +180 -0
- package/dist/function-literal-jest.test.js.map +1 -0
- package/dist/function-literal.test.d.ts +1 -0
- package/dist/function-literal.test.js +245 -0
- package/dist/function-literal.test.js.map +1 -0
- package/dist/generics-jest.test.d.ts +1 -0
- package/dist/generics-jest.test.js +93 -0
- package/dist/generics-jest.test.js.map +1 -0
- package/dist/ir-gen.d.ts +15 -0
- package/dist/ir-gen.js +400 -0
- package/dist/ir-gen.js.map +1 -0
- package/dist/ir.d.ts +114 -0
- package/dist/ir.js +5 -0
- package/dist/ir.js.map +1 -0
- package/dist/lexer.d.ts +110 -0
- package/dist/lexer.js +467 -0
- package/dist/lexer.js.map +1 -0
- package/dist/lexer.test.d.ts +1 -0
- package/dist/lexer.test.js +426 -0
- package/dist/lexer.test.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +241 -0
- package/dist/main.js.map +1 -0
- package/dist/module-jest.test.d.ts +1 -0
- package/dist/module-jest.test.js +123 -0
- package/dist/module-jest.test.js.map +1 -0
- package/dist/parser.d.ts +56 -0
- package/dist/parser.js +1060 -0
- package/dist/parser.js.map +1 -0
- package/dist/parser.test.d.ts +1 -0
- package/dist/parser.test.js +461 -0
- package/dist/parser.test.js.map +1 -0
- package/dist/pattern-matching-jest.test.d.ts +1 -0
- package/dist/pattern-matching-jest.test.js +158 -0
- package/dist/pattern-matching-jest.test.js.map +1 -0
- package/dist/pkg/init.d.ts +1 -0
- package/dist/pkg/init.js +118 -0
- package/dist/pkg/init.js.map +1 -0
- package/dist/pkg/install.d.ts +1 -0
- package/dist/pkg/install.js +77 -0
- package/dist/pkg/install.js.map +1 -0
- package/dist/pkg/registry.d.ts +23 -0
- package/dist/pkg/registry.js +106 -0
- package/dist/pkg/registry.js.map +1 -0
- package/dist/pkg/run.d.ts +1 -0
- package/dist/pkg/run.js +76 -0
- package/dist/pkg/run.js.map +1 -0
- package/dist/pkg/toml.d.ts +5 -0
- package/dist/pkg/toml.js +117 -0
- package/dist/pkg/toml.js.map +1 -0
- package/dist/repl.d.ts +15 -0
- package/dist/repl.js +197 -0
- package/dist/repl.js.map +1 -0
- package/dist/runtime/bytecode.d.ts +92 -0
- package/dist/runtime/bytecode.js +253 -0
- package/dist/runtime/bytecode.js.map +1 -0
- package/dist/runtime/value.d.ts +102 -0
- package/dist/runtime/value.js +302 -0
- package/dist/runtime/value.js.map +1 -0
- package/dist/runtime/vm.d.ts +65 -0
- package/dist/runtime/vm.js +293 -0
- package/dist/runtime/vm.js.map +1 -0
- package/dist/struct-instance-jest.test.d.ts +1 -0
- package/dist/struct-instance-jest.test.js +209 -0
- package/dist/struct-instance-jest.test.js.map +1 -0
- package/dist/struct-instance.test.d.ts +1 -0
- package/dist/struct-instance.test.js +291 -0
- package/dist/struct-instance.test.js.map +1 -0
- package/dist/struct-jest.test.d.ts +1 -0
- package/dist/struct-jest.test.js +176 -0
- package/dist/struct-jest.test.js.map +1 -0
- package/dist/struct.test.d.ts +1 -0
- package/dist/struct.test.js +231 -0
- package/dist/struct.test.js.map +1 -0
- package/dist/trait-jest.test.d.ts +1 -0
- package/dist/trait-jest.test.js +120 -0
- package/dist/trait-jest.test.js.map +1 -0
- package/dist/vm-jest.test.d.ts +1 -0
- package/dist/vm-jest.test.js +569 -0
- package/dist/vm-jest.test.js.map +1 -0
- package/dist/vm.d.ts +81 -0
- package/dist/vm.js +1956 -0
- package/dist/vm.js.map +1 -0
- package/dist/vm.test.d.ts +1 -0
- package/dist/vm.test.js +337 -0
- package/dist/vm.test.js.map +1 -0
- package/dist/web-repl/sandbox.d.ts +11 -0
- package/dist/web-repl/sandbox.js +76 -0
- package/dist/web-repl/sandbox.js.map +1 -0
- package/dist/web-repl/server.d.ts +1 -0
- package/dist/web-repl/server.js +111 -0
- package/dist/web-repl/server.js.map +1 -0
- package/dist/while-loop-jest.test.d.ts +1 -0
- package/dist/while-loop-jest.test.js +201 -0
- package/dist/while-loop-jest.test.js.map +1 -0
- package/dist/while-loop.test.d.ts +1 -0
- package/dist/while-loop.test.js +262 -0
- package/dist/while-loop.test.js.map +1 -0
- package/docs/EXPERIENCE.md +787 -0
- package/docs/README.md +175 -0
- package/docs/V1_V2_V3_ANALYSIS.md +107 -0
- package/docs/_config.yml +36 -0
- package/docs/api-reference.md +459 -0
- package/docs/architecture.md +470 -0
- package/docs/benchmarks.md +295 -0
- package/docs/comparison.md +454 -0
- package/docs/index.md +335 -0
- package/docs/language-completeness.md +228 -0
- package/docs/learning-guide.md +651 -0
- package/package.json +65 -0
- package/src/api/deploy_key.fl +294 -0
- package/src/api/issue.fl +302 -0
- package/src/api/org.fl +356 -0
- package/src/api/repo.fl +394 -0
- package/src/api/team.fl +299 -0
- package/src/api/user.fl +385 -0
- package/src/api/webhook.fl +273 -0
- package/src/ast.ts +158 -0
- package/src/async-basic.test.ts +94 -0
- package/src/async-jest.test.ts +107 -0
- package/src/channel-jest.test.ts +158 -0
- package/src/checker-jest.test.ts +189 -0
- package/src/checker.test.ts +279 -0
- package/src/checker.ts +1861 -0
- package/src/commands/analyze.fl +227 -0
- package/src/commands/auth.fl +315 -0
- package/src/commands/batch.fl +349 -0
- package/src/commands/config.fl +199 -0
- package/src/commands/deploy_key.fl +352 -0
- package/src/commands/issue.fl +275 -0
- package/src/commands/main.fl +492 -0
- package/src/commands/org.fl +425 -0
- package/src/commands/repo.fl +581 -0
- package/src/commands/team.fl +244 -0
- package/src/commands/user.fl +423 -0
- package/src/commands/webhook.fl +400 -0
- package/src/compiler-jest.test.ts +275 -0
- package/src/compiler.test.ts +375 -0
- package/src/compiler.ts +1770 -0
- package/src/config.fl +175 -0
- package/src/core/batch.fl +355 -0
- package/src/core/cache.fl +284 -0
- package/src/core/ensure.fl +324 -0
- package/src/db-100m-full.ts +96 -0
- package/src/db-100m-no-index.ts +133 -0
- package/src/db-100m-real.ts +152 -0
- package/src/db-100m-streaming.ts +154 -0
- package/src/db-100m-test.ts +136 -0
- package/src/db-jest.test.ts +161 -0
- package/src/db-runtime.ts +242 -0
- package/src/db.ts +676 -0
- package/src/errors.fl +134 -0
- package/src/for-of-jest.test.ts +246 -0
- package/src/for-of.test.ts +308 -0
- package/src/function-literal-jest.test.ts +193 -0
- package/src/function-literal.test.ts +248 -0
- package/src/generics-jest.test.ts +104 -0
- package/src/http/client.fl +327 -0
- package/src/ir-gen.ts +459 -0
- package/src/ir.ts +80 -0
- package/src/lexer.test.ts +499 -0
- package/src/lexer.ts +522 -0
- package/src/main.ts +223 -0
- package/src/models.fl +162 -0
- package/src/module-jest.test.ts +145 -0
- package/src/parser.test.ts +542 -0
- package/src/parser.ts +1211 -0
- package/src/pattern-matching-jest.test.ts +170 -0
- package/src/pkg/init.ts +91 -0
- package/src/pkg/install.ts +56 -0
- package/src/pkg/registry.ts +103 -0
- package/src/pkg/run.ts +49 -0
- package/src/pkg/toml.ts +129 -0
- package/src/repl.ts +190 -0
- package/src/runtime/bytecode.ts +291 -0
- package/src/runtime/value.ts +322 -0
- package/src/runtime/vm.ts +354 -0
- package/src/self-host/bootstrap.fl +68 -0
- package/src/self-host/interpreter.fl +361 -0
- package/src/self-host/lexer-simple.fl +22 -0
- package/src/self-host/lexer.fl +305 -0
- package/src/self-host/parser.fl +580 -0
- package/src/struct-instance-jest.test.ts +221 -0
- package/src/struct-instance.test.ts +293 -0
- package/src/struct-jest.test.ts +187 -0
- package/src/struct.test.ts +234 -0
- package/src/trait-jest.test.ts +136 -0
- package/src/vm-jest.test.ts +754 -0
- package/src/vm.ts +1976 -0
- package/src/web-repl/public/index.html +50 -0
- package/src/web-repl/public/main.js +105 -0
- package/src/web-repl/public/style.css +225 -0
- package/src/web-repl/sandbox.ts +88 -0
- package/src/web-repl/server.ts +97 -0
- package/src/while-loop-jest.test.ts +218 -0
- package/src/while-loop.test.ts +267 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
// FreeLang v4.2 — 캐시 시스템
|
|
2
|
+
// API 응답 캐싱으로 성능 향상 (80% API 호출 감소)
|
|
3
|
+
|
|
4
|
+
/// 캐시 항목
|
|
5
|
+
struct CacheEntry {
|
|
6
|
+
key: str
|
|
7
|
+
value: str
|
|
8
|
+
created_at: i32
|
|
9
|
+
ttl_seconds: i32
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/// 캐시 관리자
|
|
13
|
+
struct CacheManager {
|
|
14
|
+
entries: [CacheEntry]
|
|
15
|
+
ttl_default: i32 // 기본 TTL (초)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/// 캐시 생성
|
|
19
|
+
fn new_cache(ttl_seconds: i32) -> CacheManager {
|
|
20
|
+
CacheManager {
|
|
21
|
+
entries: [],
|
|
22
|
+
ttl_default: ttl_seconds
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// 캐시에 값 저장
|
|
27
|
+
fn cache_set(cache: CacheManager, key: str, value: str) {
|
|
28
|
+
// 기존 키 확인
|
|
29
|
+
var i: i32 = 0
|
|
30
|
+
while i < length(cache.entries) {
|
|
31
|
+
if cache.entries[i].key == key {
|
|
32
|
+
// 기존 항목 업데이트
|
|
33
|
+
cache.entries[i].value = value
|
|
34
|
+
cache.entries[i].created_at = now_seconds()
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
i = i + 1
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 새 항목 추가
|
|
41
|
+
var entry = CacheEntry {
|
|
42
|
+
key: key,
|
|
43
|
+
value: value,
|
|
44
|
+
created_at: now_seconds(),
|
|
45
|
+
ttl_seconds: cache.ttl_default
|
|
46
|
+
}
|
|
47
|
+
cache.entries.push(entry)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/// 캐시에서 값 조회
|
|
51
|
+
fn cache_get(cache: CacheManager, key: str) -> str {
|
|
52
|
+
var i: i32 = 0
|
|
53
|
+
while i < length(cache.entries) {
|
|
54
|
+
var entry = cache.entries[i]
|
|
55
|
+
|
|
56
|
+
if entry.key == key {
|
|
57
|
+
// TTL 확인
|
|
58
|
+
var age = now_seconds() - entry.created_at
|
|
59
|
+
if age < entry.ttl_seconds {
|
|
60
|
+
// 캐시 유효
|
|
61
|
+
return entry.value
|
|
62
|
+
} else {
|
|
63
|
+
// 캐시 만료 - 제거
|
|
64
|
+
cache.entries[i] = cache.entries[length(cache.entries) - 1]
|
|
65
|
+
pop(cache.entries)
|
|
66
|
+
return ""
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
i = i + 1
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
"" // 캐시 미스
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// 캐시 유효성 확인
|
|
76
|
+
fn cache_has(cache: CacheManager, key: str) -> bool {
|
|
77
|
+
var value = cache_get(cache, key)
|
|
78
|
+
value != ""
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// 캐시 무효화 (특정 키)
|
|
82
|
+
fn cache_invalidate(cache: CacheManager, key: str) -> bool {
|
|
83
|
+
var i: i32 = 0
|
|
84
|
+
while i < length(cache.entries) {
|
|
85
|
+
if cache.entries[i].key == key {
|
|
86
|
+
cache.entries[i] = cache.entries[length(cache.entries) - 1]
|
|
87
|
+
pop(cache.entries)
|
|
88
|
+
return true
|
|
89
|
+
}
|
|
90
|
+
i = i + 1
|
|
91
|
+
}
|
|
92
|
+
false
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// 캐시 전체 삭제
|
|
96
|
+
fn cache_clear(cache: CacheManager) {
|
|
97
|
+
cache.entries = []
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/// 캐시 통계
|
|
101
|
+
struct CacheStats {
|
|
102
|
+
total_entries: i32
|
|
103
|
+
total_size: i32
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/// 캐시 통계 조회
|
|
107
|
+
fn cache_stats(cache: CacheManager) -> CacheStats {
|
|
108
|
+
var total_size: i32 = 0
|
|
109
|
+
var i: i32 = 0
|
|
110
|
+
|
|
111
|
+
while i < length(cache.entries) {
|
|
112
|
+
total_size = total_size + length(cache.entries[i].value)
|
|
113
|
+
i = i + 1
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
CacheStats {
|
|
117
|
+
total_entries: length(cache.entries),
|
|
118
|
+
total_size: total_size
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ==================== 고수준 캐시 API ====================
|
|
123
|
+
|
|
124
|
+
/// 캐시를 사용하여 데이터 조회 (또는 가져오기)
|
|
125
|
+
async fn cache_get_or_fetch(
|
|
126
|
+
cache: CacheManager,
|
|
127
|
+
key: str,
|
|
128
|
+
fetch_fn: async fn() -> str
|
|
129
|
+
) -> str {
|
|
130
|
+
// 캐시 확인
|
|
131
|
+
var cached = cache_get(cache, key)
|
|
132
|
+
if cached != "" {
|
|
133
|
+
println("[cache] hit: " + key)
|
|
134
|
+
return cached
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
println("[cache] miss: " + key)
|
|
138
|
+
|
|
139
|
+
// 캐시 미스 - fetch 실행
|
|
140
|
+
var value = await fetch_fn()
|
|
141
|
+
|
|
142
|
+
// 캐시에 저장
|
|
143
|
+
if value != "" {
|
|
144
|
+
cache_set(cache, key, value)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
value
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/// 저장소 목록 캐시 조회
|
|
151
|
+
async fn cache_list_repos(
|
|
152
|
+
cache: CacheManager,
|
|
153
|
+
client: HttpClient
|
|
154
|
+
) -> str {
|
|
155
|
+
await cache_get_or_fetch(
|
|
156
|
+
cache,
|
|
157
|
+
"repos:list",
|
|
158
|
+
async fn() -> str {
|
|
159
|
+
var resp = await http_get(client, "/api/v1/user/repos")
|
|
160
|
+
resp.body
|
|
161
|
+
}
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/// 저장소 상세정보 캐시 조회
|
|
166
|
+
async fn cache_get_repo(
|
|
167
|
+
cache: CacheManager,
|
|
168
|
+
client: HttpClient,
|
|
169
|
+
owner: str,
|
|
170
|
+
repo: str
|
|
171
|
+
) -> str {
|
|
172
|
+
var key = "repo:" + owner + "/" + repo
|
|
173
|
+
|
|
174
|
+
await cache_get_or_fetch(
|
|
175
|
+
cache,
|
|
176
|
+
key,
|
|
177
|
+
async fn() -> str {
|
|
178
|
+
var resp = await http_get(client, "/api/v1/repos/" + owner + "/" + repo)
|
|
179
|
+
resp.body
|
|
180
|
+
}
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/// 사용자 정보 캐시 조회
|
|
185
|
+
async fn cache_get_user(
|
|
186
|
+
cache: CacheManager,
|
|
187
|
+
client: HttpClient,
|
|
188
|
+
username: str
|
|
189
|
+
) -> str {
|
|
190
|
+
var key = "user:" + username
|
|
191
|
+
|
|
192
|
+
await cache_get_or_fetch(
|
|
193
|
+
cache,
|
|
194
|
+
key,
|
|
195
|
+
async fn() -> str {
|
|
196
|
+
var resp = await http_get(client, "/api/v1/users/" + username)
|
|
197
|
+
resp.body
|
|
198
|
+
}
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ==================== 캐시 정책 ====================
|
|
203
|
+
|
|
204
|
+
/// TTL 기반 캐시 정책
|
|
205
|
+
struct CachePolicy {
|
|
206
|
+
key: str
|
|
207
|
+
ttl_seconds: i32
|
|
208
|
+
max_size: i32
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/// 기본 캐시 정책들
|
|
212
|
+
fn policy_repos_list() -> CachePolicy {
|
|
213
|
+
CachePolicy {
|
|
214
|
+
key: "repos:list",
|
|
215
|
+
ttl_seconds: 300, // 5분
|
|
216
|
+
max_size: 1000000 // 1MB
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
fn policy_repo_detail() -> CachePolicy {
|
|
221
|
+
CachePolicy {
|
|
222
|
+
key: "repo:*",
|
|
223
|
+
ttl_seconds: 600, // 10분
|
|
224
|
+
max_size: 1000000 // 1MB
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
fn policy_user_info() -> CachePolicy {
|
|
229
|
+
CachePolicy {
|
|
230
|
+
key: "user:*",
|
|
231
|
+
ttl_seconds: 1800, // 30분
|
|
232
|
+
max_size: 100000 // 100KB
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
fn policy_org_list() -> CachePolicy {
|
|
237
|
+
CachePolicy {
|
|
238
|
+
key: "orgs:list",
|
|
239
|
+
ttl_seconds: 600, // 10분
|
|
240
|
+
max_size: 100000 // 100KB
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ==================== 캐시 프리페칭 ====================
|
|
245
|
+
|
|
246
|
+
/// 자주 사용되는 데이터 미리 캐시
|
|
247
|
+
async fn cache_prefetch(
|
|
248
|
+
cache: CacheManager,
|
|
249
|
+
client: HttpClient
|
|
250
|
+
) {
|
|
251
|
+
println("Prefetching cache...")
|
|
252
|
+
|
|
253
|
+
// 저장소 목록
|
|
254
|
+
var repos_json = await cache_list_repos(cache, client)
|
|
255
|
+
println("✓ Cached repositories list")
|
|
256
|
+
|
|
257
|
+
// 사용자 정보
|
|
258
|
+
var user_json = await cache_get_user(cache, client, "admin")
|
|
259
|
+
println("✓ Cached admin user")
|
|
260
|
+
|
|
261
|
+
println("Cache prefetch complete")
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ==================== 유틸리티 ====================
|
|
265
|
+
|
|
266
|
+
/// 캐시 상태 출력
|
|
267
|
+
fn print_cache_status(cache: CacheManager) {
|
|
268
|
+
var stats = cache_stats(cache)
|
|
269
|
+
println("=== Cache Status ===")
|
|
270
|
+
println("Entries: " + str(stats.total_entries))
|
|
271
|
+
println("Size: " + str(stats.total_size / 1024) + " KB")
|
|
272
|
+
println("====================")
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/// 초 단위 현재 시간
|
|
276
|
+
fn now_seconds() -> i32 {
|
|
277
|
+
// 프로덕션: system clock
|
|
278
|
+
0 // placeholder
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// 필요한 함수들:
|
|
282
|
+
// - http_get, http_post_json, http_delete
|
|
283
|
+
// - sleep, now_seconds
|
|
284
|
+
// - pop (배열에서 마지막 요소 제거)
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
// FreeLang v4.2 — ensure 알고리즘
|
|
2
|
+
// 멱등성을 보증하는 상태 동기화 엔진
|
|
3
|
+
|
|
4
|
+
/// ensure 작업의 결과
|
|
5
|
+
enum EnsureAction {
|
|
6
|
+
Created
|
|
7
|
+
Updated(changes: [str])
|
|
8
|
+
Unchanged
|
|
9
|
+
Error(msg: str)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/// ensure 컨텍스트 정의
|
|
13
|
+
struct EnsureContext {
|
|
14
|
+
name: str // 리소스 이름 (예: "myrepo")
|
|
15
|
+
|
|
16
|
+
// 콜백 함수들
|
|
17
|
+
get_fn: async fn() -> Result<str> // 현재 상태 조회
|
|
18
|
+
create_fn: async fn() -> Result<str> // 생성
|
|
19
|
+
update_fn: async fn([str]) -> Result<str> // 업데이트
|
|
20
|
+
delete_fn: async fn() -> Result<str> // 삭제
|
|
21
|
+
|
|
22
|
+
// 원하는 상태 (JSON 문자열)
|
|
23
|
+
desired: str
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// ensure 알고리즘 실행
|
|
27
|
+
async fn ensure(ctx: EnsureContext) -> EnsureAction {
|
|
28
|
+
// Step 1: 현재 상태 조회
|
|
29
|
+
var current_result = await ctx.get_fn()
|
|
30
|
+
|
|
31
|
+
match current_result {
|
|
32
|
+
Result::Ok(current) => {
|
|
33
|
+
// Step 2: 없으면 생성
|
|
34
|
+
if current == "" || current == "null" {
|
|
35
|
+
var create_result = await ctx.create_fn()
|
|
36
|
+
match create_result {
|
|
37
|
+
Result::Ok(_) => {
|
|
38
|
+
return EnsureAction::Created
|
|
39
|
+
}
|
|
40
|
+
Result::Err(err) => {
|
|
41
|
+
var msg = error_message(err)
|
|
42
|
+
println("✗ Failed to create " + ctx.name + ": " + msg)
|
|
43
|
+
return EnsureAction::Error(msg)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Step 3: 있으면 diff 계산
|
|
49
|
+
var diff = compute_diff(current, ctx.desired)
|
|
50
|
+
|
|
51
|
+
// Step 4: diff 없으면 unchanged
|
|
52
|
+
if length(diff) == 0 {
|
|
53
|
+
println("✓ " + ctx.name + " unchanged")
|
|
54
|
+
return EnsureAction::Unchanged
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Step 5: diff 있으면 업데이트
|
|
58
|
+
var update_result = await ctx.update_fn(diff)
|
|
59
|
+
match update_result {
|
|
60
|
+
Result::Ok(_) => {
|
|
61
|
+
println("✓ " + ctx.name + " updated (" + str(length(diff)) + " changes)")
|
|
62
|
+
return EnsureAction::Updated(diff)
|
|
63
|
+
}
|
|
64
|
+
Result::Err(err) => {
|
|
65
|
+
var msg = error_message(err)
|
|
66
|
+
println("✗ Failed to update " + ctx.name + ": " + msg)
|
|
67
|
+
return EnsureAction::Error(msg)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
Result::Err(err) => {
|
|
72
|
+
// 조회 실패
|
|
73
|
+
var msg = error_message(err)
|
|
74
|
+
println("✗ Failed to get " + ctx.name + ": " + msg)
|
|
75
|
+
return EnsureAction::Error(msg)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// 변경사항 계산 (간단한 JSON 비교)
|
|
81
|
+
fn compute_diff(current: str, desired: str) -> [str] {
|
|
82
|
+
var diff: [str] = []
|
|
83
|
+
|
|
84
|
+
// 간단한 문자열 비교 (프로덕션에선 JSON 구조 분석 필요)
|
|
85
|
+
if current == desired {
|
|
86
|
+
return diff
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 현재와 원하는 상태가 다르면 모든 필드가 변경됨
|
|
90
|
+
diff.push("content")
|
|
91
|
+
|
|
92
|
+
diff
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// 에러 메시지 변환
|
|
96
|
+
fn error_message(err: Error) -> str {
|
|
97
|
+
match err {
|
|
98
|
+
Error::NotFound(msg) => "Not found: " + msg,
|
|
99
|
+
Error::BadRequest(msg) => "Bad request: " + msg,
|
|
100
|
+
Error::Unauthorized(msg) => "Unauthorized: " + msg,
|
|
101
|
+
Error::Forbidden(msg) => "Forbidden: " + msg,
|
|
102
|
+
Error::NetworkError(msg) => "Network error: " + msg,
|
|
103
|
+
Error::HttpError(status, msg) => "HTTP " + str(status) + ": " + msg,
|
|
104
|
+
Error::Unknown(msg) => "Unknown error: " + msg,
|
|
105
|
+
_ => "Error occurred",
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ==================== 저장소 ensure ====================
|
|
110
|
+
|
|
111
|
+
/// 저장소 ensure 예시
|
|
112
|
+
async fn ensure_repo(
|
|
113
|
+
client: HttpClient,
|
|
114
|
+
owner: str,
|
|
115
|
+
repo_name: str,
|
|
116
|
+
private: bool,
|
|
117
|
+
description: str
|
|
118
|
+
) -> EnsureAction {
|
|
119
|
+
var path = "/api/v1/repos/" + owner + "/" + repo_name
|
|
120
|
+
|
|
121
|
+
// desired 상태 정의
|
|
122
|
+
var desired_json = "{
|
|
123
|
+
\"owner\": \"" + owner + "\",
|
|
124
|
+
\"name\": \"" + repo_name + "\",
|
|
125
|
+
\"private\": " + str(private) + ",
|
|
126
|
+
\"description\": \"" + description + "\"
|
|
127
|
+
}"
|
|
128
|
+
|
|
129
|
+
var ctx = EnsureContext {
|
|
130
|
+
name: owner + "/" + repo_name,
|
|
131
|
+
|
|
132
|
+
get_fn: async fn() -> Result<str> {
|
|
133
|
+
var resp = await http_get(client, path)
|
|
134
|
+
if is_error(resp.status) {
|
|
135
|
+
if resp.status == 404 {
|
|
136
|
+
Result::Ok("") // 없음
|
|
137
|
+
} else {
|
|
138
|
+
Result::Err(response_to_error(resp))
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
Result::Ok(resp.body)
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
create_fn: async fn() -> Result<str> {
|
|
146
|
+
var body = "{\"name\": \"" + repo_name + "\", \"private\": " + str(private) + ", \"description\": \"" + description + "\"}"
|
|
147
|
+
var resp = await http_post_json(client, "/api/v1/user/repos", body)
|
|
148
|
+
if is_error(resp.status) {
|
|
149
|
+
Result::Err(response_to_error(resp))
|
|
150
|
+
} else {
|
|
151
|
+
Result::Ok(resp.body)
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
update_fn: async fn(changes: [str]) -> Result<str> {
|
|
156
|
+
// PATCH로 업데이트
|
|
157
|
+
var update_body = "{\"description\": \"" + description + "\", \"private\": " + str(private) + "}"
|
|
158
|
+
var resp = await fetch(
|
|
159
|
+
build_url(client.base_url, path),
|
|
160
|
+
"PATCH",
|
|
161
|
+
headers_to_object(default_headers(client.token)),
|
|
162
|
+
update_body
|
|
163
|
+
)
|
|
164
|
+
Result::Ok(resp)
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
delete_fn: async fn() -> Result<str> {
|
|
168
|
+
var resp = await http_delete(client, path)
|
|
169
|
+
if is_error(resp.status) {
|
|
170
|
+
Result::Err(response_to_error(resp))
|
|
171
|
+
} else {
|
|
172
|
+
Result::Ok("")
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
desired: desired_json
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
await ensure(ctx)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ==================== 사용자 ensure ====================
|
|
183
|
+
|
|
184
|
+
/// 사용자 ensure 예시
|
|
185
|
+
async fn ensure_user(
|
|
186
|
+
client: HttpClient,
|
|
187
|
+
username: str,
|
|
188
|
+
email: str,
|
|
189
|
+
is_admin: bool
|
|
190
|
+
) -> EnsureAction {
|
|
191
|
+
var path = "/api/v1/users/" + username
|
|
192
|
+
|
|
193
|
+
var desired_json = "{
|
|
194
|
+
\"login\": \"" + username + "\",
|
|
195
|
+
\"email\": \"" + email + "\",
|
|
196
|
+
\"is_admin\": " + str(is_admin) + "
|
|
197
|
+
}"
|
|
198
|
+
|
|
199
|
+
var ctx = EnsureContext {
|
|
200
|
+
name: username,
|
|
201
|
+
|
|
202
|
+
get_fn: async fn() -> Result<str> {
|
|
203
|
+
var resp = await http_get(client, path)
|
|
204
|
+
if is_error(resp.status) {
|
|
205
|
+
if resp.status == 404 {
|
|
206
|
+
Result::Ok("")
|
|
207
|
+
} else {
|
|
208
|
+
Result::Err(response_to_error(resp))
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
Result::Ok(resp.body)
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
create_fn: async fn() -> Result<str> {
|
|
216
|
+
var body = "{\"username\": \"" + username + "\", \"email\": \"" + email + "\", \"is_admin\": " + str(is_admin) + "}"
|
|
217
|
+
var resp = await http_post_json(client, "/api/v1/admin/users", body)
|
|
218
|
+
if is_error(resp.status) {
|
|
219
|
+
Result::Err(response_to_error(resp))
|
|
220
|
+
} else {
|
|
221
|
+
Result::Ok(resp.body)
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
update_fn: async fn(changes: [str]) -> Result<str> {
|
|
226
|
+
var update_body = "{\"email\": \"" + email + "\", \"is_admin\": " + str(is_admin) + "}"
|
|
227
|
+
var resp = await fetch(
|
|
228
|
+
build_url(client.base_url, path),
|
|
229
|
+
"PATCH",
|
|
230
|
+
headers_to_object(default_headers(client.token)),
|
|
231
|
+
update_body
|
|
232
|
+
)
|
|
233
|
+
Result::Ok(resp)
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
delete_fn: async fn() -> Result<str> {
|
|
237
|
+
var resp = await http_delete(client, path)
|
|
238
|
+
if is_error(resp.status) {
|
|
239
|
+
Result::Err(response_to_error(resp))
|
|
240
|
+
} else {
|
|
241
|
+
Result::Ok("")
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
desired: desired_json
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
await ensure(ctx)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ==================== 조직 ensure ====================
|
|
252
|
+
|
|
253
|
+
/// 조직 ensure 예시
|
|
254
|
+
async fn ensure_org(
|
|
255
|
+
client: HttpClient,
|
|
256
|
+
org_name: str,
|
|
257
|
+
full_name: str,
|
|
258
|
+
email: str
|
|
259
|
+
) -> EnsureAction {
|
|
260
|
+
var path = "/api/v1/orgs/" + org_name
|
|
261
|
+
|
|
262
|
+
var desired_json = "{
|
|
263
|
+
\"username\": \"" + org_name + "\",
|
|
264
|
+
\"full_name\": \"" + full_name + "\",
|
|
265
|
+
\"email\": \"" + email + "\"
|
|
266
|
+
}"
|
|
267
|
+
|
|
268
|
+
var ctx = EnsureContext {
|
|
269
|
+
name: org_name,
|
|
270
|
+
|
|
271
|
+
get_fn: async fn() -> Result<str> {
|
|
272
|
+
var resp = await http_get(client, path)
|
|
273
|
+
if is_error(resp.status) {
|
|
274
|
+
if resp.status == 404 {
|
|
275
|
+
Result::Ok("")
|
|
276
|
+
} else {
|
|
277
|
+
Result::Err(response_to_error(resp))
|
|
278
|
+
}
|
|
279
|
+
} else {
|
|
280
|
+
Result::Ok(resp.body)
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
create_fn: async fn() -> Result<str> {
|
|
285
|
+
var body = "{\"username\": \"" + org_name + "\", \"full_name\": \"" + full_name + "\", \"email\": \"" + email + "\"}"
|
|
286
|
+
var resp = await http_post_json(client, "/api/v1/orgs", body)
|
|
287
|
+
if is_error(resp.status) {
|
|
288
|
+
Result::Err(response_to_error(resp))
|
|
289
|
+
} else {
|
|
290
|
+
Result::Ok(resp.body)
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
update_fn: async fn(changes: [str]) -> Result<str> {
|
|
295
|
+
var update_body = "{\"full_name\": \"" + full_name + "\", \"email\": \"" + email + "\"}"
|
|
296
|
+
var resp = await fetch(
|
|
297
|
+
build_url(client.base_url, path),
|
|
298
|
+
"PATCH",
|
|
299
|
+
headers_to_object(default_headers(client.token)),
|
|
300
|
+
update_body
|
|
301
|
+
)
|
|
302
|
+
Result::Ok(resp)
|
|
303
|
+
},
|
|
304
|
+
|
|
305
|
+
delete_fn: async fn() -> Result<str> {
|
|
306
|
+
var resp = await http_delete(client, path)
|
|
307
|
+
if is_error(resp.status) {
|
|
308
|
+
Result::Err(response_to_error(resp))
|
|
309
|
+
} else {
|
|
310
|
+
Result::Ok("")
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
|
|
314
|
+
desired: desired_json
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
await ensure(ctx)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// 필요한 함수들 (다른 모듈에서 임포트)
|
|
321
|
+
// - http_get, http_post_json, http_delete
|
|
322
|
+
// - build_url, headers_to_object, default_headers
|
|
323
|
+
// - is_error, response_to_error
|
|
324
|
+
// - fetch (FreeLang 내장)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FreeLang v4 × 100M Clone Full Database Test
|
|
3
|
+
* 최대한 많은 데이터 저장
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { DBRuntime } from './db-runtime';
|
|
7
|
+
|
|
8
|
+
async function main() {
|
|
9
|
+
console.log('\n🚀 FreeLang v4 × 100M Clone Full Database Test');
|
|
10
|
+
console.log('═══════════════════════════════════════════════════\n');
|
|
11
|
+
|
|
12
|
+
const db = new DBRuntime();
|
|
13
|
+
|
|
14
|
+
db.createTable('clone_100m', {
|
|
15
|
+
id: 'i32',
|
|
16
|
+
app: 'string',
|
|
17
|
+
clone_id: 'i32',
|
|
18
|
+
status: 'string',
|
|
19
|
+
throughput: 'i32'
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// 메모리 모니터링
|
|
23
|
+
const getMemUsage = () => {
|
|
24
|
+
const usage = process.memoryUsage();
|
|
25
|
+
return Math.round(usage.heapUsed / (1024 * 1024));
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
console.log(`Initial Memory: ${getMemUsage()}MB\n`);
|
|
29
|
+
|
|
30
|
+
// Phase: 최대한 큰 배치로 저장 (메모리 한계까지)
|
|
31
|
+
const BATCH_INSERT = 1000000; // 100만씩
|
|
32
|
+
let totalInserted = 0;
|
|
33
|
+
let phase = 1;
|
|
34
|
+
|
|
35
|
+
const apps = ['proof_ai', 'cwm', 'freelang', 'kim_ai_os'];
|
|
36
|
+
const startTotal = Date.now();
|
|
37
|
+
|
|
38
|
+
while (getMemUsage() < 25000) { // 25GB 한계
|
|
39
|
+
console.log(`📊 Phase ${phase}: Batch ${(totalInserted / 1000000).toFixed(1)}M`);
|
|
40
|
+
|
|
41
|
+
const records = [];
|
|
42
|
+
for (let i = 0; i < BATCH_INSERT; i++) {
|
|
43
|
+
records.push({
|
|
44
|
+
id: totalInserted + i,
|
|
45
|
+
app: apps[(totalInserted + i) % 4],
|
|
46
|
+
clone_id: totalInserted + i,
|
|
47
|
+
status: Math.random() > 0.01 ? 'success' : 'failed',
|
|
48
|
+
throughput: 2000000 + Math.floor(Math.random() * 200000)
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const startPhase = Date.now();
|
|
53
|
+
db.bulkInsert('clone_100m', records);
|
|
54
|
+
const phaseTime = Date.now() - startPhase;
|
|
55
|
+
|
|
56
|
+
totalInserted += BATCH_INSERT;
|
|
57
|
+
const mem = getMemUsage();
|
|
58
|
+
|
|
59
|
+
console.log(` ✅ ${BATCH_INSERT.toLocaleString()} records in ${phaseTime}ms`);
|
|
60
|
+
console.log(` 📈 Total: ${totalInserted.toLocaleString()} | Memory: ${mem}MB`);
|
|
61
|
+
console.log(` 💾 Rate: ${Math.round((BATCH_INSERT / (phaseTime / 1000))).toLocaleString()} inserts/sec\n`);
|
|
62
|
+
|
|
63
|
+
phase++;
|
|
64
|
+
|
|
65
|
+
if (totalInserted >= 100000000) {
|
|
66
|
+
console.log('✅ Reached 100M records!');
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const totalTime = Date.now() - startTotal;
|
|
72
|
+
const finalStats = db.getStats('clone_100m');
|
|
73
|
+
|
|
74
|
+
console.log('═══════════════════════════════════════════════════');
|
|
75
|
+
console.log('📊 FINAL DATABASE STATISTICS');
|
|
76
|
+
console.log('═══════════════════════════════════════════════════\n');
|
|
77
|
+
|
|
78
|
+
console.log(`Total Records: ${finalStats.rows.toLocaleString()}`);
|
|
79
|
+
console.log(`Total Memory: ${finalStats.memory_mb}MB`);
|
|
80
|
+
console.log(`Per Record: ${(finalStats.memory_mb * 1024 / finalStats.rows).toFixed(3)}KB`);
|
|
81
|
+
console.log(`Total Time: ${totalTime}ms (${(totalTime / 1000).toFixed(1)}s)`);
|
|
82
|
+
console.log(`Final Memory Usage: ${getMemUsage()}MB\n`);
|
|
83
|
+
|
|
84
|
+
console.log(`Records/Sec: ${Math.round((finalStats.rows / (totalTime / 1000))).toLocaleString()}`);
|
|
85
|
+
|
|
86
|
+
if (finalStats.rows >= 100000000) {
|
|
87
|
+
console.log(`\n✅ ACHIEVED: 100M+ Clone Database!`);
|
|
88
|
+
} else {
|
|
89
|
+
console.log(`\nReached: ${(finalStats.rows / 1000000).toFixed(1)}M clones`);
|
|
90
|
+
console.log(`Memory Limit: ${getMemUsage()}MB / 30000MB`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
console.log('\n✅ FreeLang v4 Database Test Complete!\n');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
main().catch(console.error);
|