@mandujs/core 0.41.2 → 0.43.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 (88) hide show
  1. package/package.json +21 -4
  2. package/src/auth/__tests__/login.test.ts +420 -419
  3. package/src/auth/__tests__/reset.test.ts +296 -296
  4. package/src/brain/adapters/anthropic-oauth.ts +421 -420
  5. package/src/brain/adapters/index.ts +2 -1
  6. package/src/brain/adapters/ollama.ts +1 -1
  7. package/src/brain/adapters/openai-oauth.ts +534 -533
  8. package/src/brain/brain.ts +2 -1
  9. package/src/brain/redactor.ts +196 -196
  10. package/src/bundler/__tests__/cli-bench-utils.test.ts +149 -149
  11. package/src/bundler/__tests__/cold-start.test.ts +504 -504
  12. package/src/bundler/__tests__/fast-refresh.test.ts +607 -606
  13. package/src/bundler/__tests__/hdr.test.ts +1 -1
  14. package/src/bundler/analyzer.ts +958 -958
  15. package/src/bundler/build.ts +104 -14
  16. package/src/bundler/dev.ts +125 -0
  17. package/src/bundler/hmr-types.ts +1 -0
  18. package/src/bundler/plugins/__tests__/react-compiler-lint.test.ts +110 -0
  19. package/src/bundler/plugins/index.ts +14 -0
  20. package/src/bundler/plugins/react-compiler-lint.ts +253 -0
  21. package/src/bundler/plugins/react-compiler.ts +162 -0
  22. package/src/bundler/types.ts +12 -0
  23. package/src/change/integrity.ts +2 -1
  24. package/src/client/index.ts +10 -0
  25. package/src/client/island.ts +38 -11
  26. package/src/client/router.ts +6 -1
  27. package/src/config/mandu.ts +57 -0
  28. package/src/config/validate.ts +42 -0
  29. package/src/content/collection.ts +844 -809
  30. package/src/content/content-layer.ts +316 -314
  31. package/src/content/content.test.ts +433 -433
  32. package/src/content/digest.ts +133 -133
  33. package/src/content/generate-types.ts +168 -168
  34. package/src/content/index.ts +6 -1
  35. package/src/content/llms-txt.ts +277 -277
  36. package/src/contract/define.ts +474 -474
  37. package/src/contract/route-helpers.ts +2 -1
  38. package/src/contract/zod-utils.ts +158 -155
  39. package/src/db/index.ts +513 -513
  40. package/src/desktop/__tests__/smoke.test.ts +100 -100
  41. package/src/desktop/webview-fallback.ts +583 -583
  42. package/src/desktop/window.ts +3 -1
  43. package/src/dev-error-overlay/overlay-client.ts +300 -300
  44. package/src/devtools/ai/mcp-connector.ts +499 -498
  45. package/src/devtools/client/components/kitchen-root.tsx +7 -2
  46. package/src/email/resend.ts +163 -163
  47. package/src/guard/__tests__/tsgolint-bridge.test.ts +347 -0
  48. package/src/guard/ast-analyzer.ts +806 -806
  49. package/src/guard/graph.ts +898 -898
  50. package/src/guard/index.ts +16 -0
  51. package/src/guard/statistics.ts +578 -578
  52. package/src/guard/tsgolint-bridge.ts +512 -0
  53. package/src/i18n/locale-resolver.ts +214 -214
  54. package/src/id/__tests__/id.test.ts +120 -120
  55. package/src/intent/index.ts +321 -321
  56. package/src/island/index.ts +39 -23
  57. package/src/kitchen/api/contract-api.ts +15 -8
  58. package/src/kitchen/kitchen-ui.ts +2137 -2137
  59. package/src/lockfile/index.ts +3 -2
  60. package/src/middleware/oauth/__tests__/oauth.test.ts +575 -574
  61. package/src/middleware/rate-limit/__tests__/rate-limit.test.ts +642 -642
  62. package/src/middleware/secure/index.ts +417 -417
  63. package/src/observability/event-bus.ts +2 -2
  64. package/src/observability/metrics.ts +334 -334
  65. package/src/observability/tracing.ts +694 -694
  66. package/src/openapi/generator.ts +1 -1
  67. package/src/perf/user-marks.ts +553 -553
  68. package/src/plugins/registry.ts +387 -387
  69. package/src/resource/ddl/diff.ts +392 -392
  70. package/src/resource/ddl/snapshot.ts +448 -447
  71. package/src/resource/generator-schema.ts +477 -476
  72. package/src/resource/parser.ts +4 -2
  73. package/src/resource/schema.ts +1 -1
  74. package/src/router/fs-patterns.ts +422 -422
  75. package/src/runtime/fast-refresh-types.ts +126 -128
  76. package/src/runtime/image-handler.ts +206 -195
  77. package/src/runtime/router.test.ts +476 -476
  78. package/src/runtime/security.ts +155 -155
  79. package/src/runtime/server.ts +36 -19
  80. package/src/runtime/session-key.ts +328 -328
  81. package/src/scheduler/__tests__/scheduler.test.ts +514 -514
  82. package/src/seo/resolve/index.ts +353 -353
  83. package/src/spec/load.ts +1 -1
  84. package/src/testing/reporter.ts +676 -676
  85. package/src/testing/server.ts +196 -196
  86. package/src/testing/snapshot.ts +444 -444
  87. package/src/utils/__tests__/lru-cache.test.ts +186 -186
  88. package/src/utils/bun.ts +8 -8
@@ -1,314 +1,316 @@
1
- /**
2
- * ContentLayer - 콘텐츠 레이어 메인 클래스
3
- *
4
- * 빌드 타임에 모든 컬렉션을 로드하고 관리
5
- * getCollection(), getEntry() API 제공
6
- */
7
-
8
- import type {
9
- ContentConfig,
10
- CollectionConfig,
11
- DataEntry,
12
- ManduContentConfig,
13
- ContentWatcher,
14
- RenderedContent,
15
- } from "./types";
16
- import { LoaderError } from "./types";
17
- import { ContentDataStore, createDataStore } from "./data-store";
18
- import { ContentMetaStore, createMetaStore } from "./meta-store";
19
- import { createLoaderContext, createSimpleMarkdownRenderer } from "./loader-context";
20
- import { CONTENT } from "../constants";
21
- import type { ZodSchema } from "zod";
22
- import * as path from "path";
23
-
24
- /**
25
- * ContentLayer 옵션
26
- */
27
- export interface ContentLayerOptions {
28
- /** 콘텐츠 설정 */
29
- contentConfig: ContentConfig;
30
- /** Mandu 설정 */
31
- manduConfig: ManduContentConfig;
32
- /** Markdown 렌더러 (선택) */
33
- markdownRenderer?: (content: string) => Promise<RenderedContent>;
34
- /** 파일 감시자 (dev 모드) */
35
- watcher?: ContentWatcher;
36
- }
37
-
38
- /**
39
- * ContentLayer 클래스
40
- *
41
- * 빌드 타임 콘텐츠 로딩 및 관리
42
- */
43
- export class ContentLayer {
44
- private contentConfig: ContentConfig;
45
- private manduConfig: ManduContentConfig;
46
- private dataStore: ContentDataStore;
47
- private metaStore: ContentMetaStore;
48
- private markdownRenderer: (content: string) => Promise<RenderedContent>;
49
- private watcher?: ContentWatcher;
50
- private loaded: boolean = false;
51
- private loading: Promise<void> | null = null;
52
-
53
- constructor(options: ContentLayerOptions) {
54
- const { contentConfig, manduConfig, markdownRenderer, watcher } = options;
55
-
56
- this.contentConfig = contentConfig;
57
- this.manduConfig = manduConfig;
58
- this.watcher = watcher;
59
- this.markdownRenderer = markdownRenderer ?? createSimpleMarkdownRenderer();
60
-
61
- // 스토어 초기화
62
- const storeFile = path.join(manduConfig.root, CONTENT.STORE_FILE);
63
- const metaFile = path.join(manduConfig.root, CONTENT.META_FILE);
64
-
65
- this.dataStore = createDataStore({
66
- filePath: storeFile,
67
- autoSave: true,
68
- saveDebounce: CONTENT.DEBOUNCE_SAVE,
69
- });
70
-
71
- this.metaStore = createMetaStore({
72
- filePath: metaFile,
73
- autoSave: true,
74
- });
75
- }
76
-
77
- /**
78
- * 모든 컬렉션 로드
79
- */
80
- async load(): Promise<void> {
81
- // 이미 로딩 중이면 기다림
82
- if (this.loading) {
83
- return this.loading;
84
- }
85
-
86
- // 이미 로드됨
87
- if (this.loaded) {
88
- return;
89
- }
90
-
91
- this.loading = this.doLoad();
92
- await this.loading;
93
- this.loading = null;
94
- this.loaded = true;
95
- }
96
-
97
- private async doLoad(): Promise<void> {
98
- // 캐시에서 로드
99
- await Promise.all([this.dataStore.load(), this.metaStore.load()]);
100
-
101
- const collections = Object.entries(this.contentConfig.collections);
102
-
103
- // 병렬로 모든 컬렉션 로드
104
- await Promise.all(
105
- collections.map(([name, config]) => this.loadCollection(name, config))
106
- );
107
-
108
- // 저장
109
- await Promise.all([this.dataStore.save(), this.metaStore.save()]);
110
- }
111
-
112
- /**
113
- * 단일 컬렉션 로드
114
- */
115
- private async loadCollection(name: string, config: CollectionConfig): Promise<void> {
116
- const { loader, schema } = config;
117
-
118
- // 로더 스키마와 컬렉션 스키마 병합 (컬렉션 우선)
119
- let finalSchema: ZodSchema | undefined = schema;
120
- if (!finalSchema && loader.schema) {
121
- finalSchema =
122
- typeof loader.schema === "function" ? await loader.schema() : loader.schema;
123
- }
124
-
125
- // LoaderContext 생성
126
- const context = createLoaderContext({
127
- collection: name,
128
- store: this.dataStore.getStore(name),
129
- meta: this.metaStore.getStore(name),
130
- config: this.manduConfig,
131
- schema: finalSchema,
132
- markdownRenderer: this.markdownRenderer,
133
- watcher: this.watcher,
134
- });
135
-
136
- try {
137
- // 타임아웃 처리
138
- const timeoutPromise = new Promise<never>((_, reject) => {
139
- setTimeout(
140
- () => reject(new LoaderError(`Loader "${loader.name}" timed out`, name)),
141
- CONTENT.LOADER_TIMEOUT
142
- );
143
- });
144
-
145
- await Promise.race([loader.load(context), timeoutPromise]);
146
-
147
- context.logger.info(`Loaded ${context.store.size()} entries`);
148
- } catch (error) {
149
- const message = error instanceof Error ? error.message : String(error);
150
- context.logger.error(`Failed to load: ${message}`);
151
-
152
- if (error instanceof LoaderError) {
153
- throw error;
154
- }
155
-
156
- throw new LoaderError(`Failed to load collection "${name}": ${message}`, name);
157
- }
158
- }
159
-
160
- /**
161
- * 단일 컬렉션 다시 로드
162
- */
163
- async reloadCollection(name: string): Promise<void> {
164
- const config = this.contentConfig.collections[name];
165
- if (!config) {
166
- throw new Error(`Collection "${name}" not found`);
167
- }
168
-
169
- await this.loadCollection(name, config);
170
- await this.dataStore.save();
171
- await this.metaStore.save();
172
- }
173
-
174
- /**
175
- * 컬렉션의 모든 엔트리 조회
176
- */
177
- getCollection<T = Record<string, unknown>>(
178
- collection: string
179
- ): Array<DataEntry<T>> {
180
- this.ensureLoaded();
181
-
182
- const store = this.dataStore.getStore(collection);
183
- return store.values<T>();
184
- }
185
-
186
- /**
187
- * 컬렉션에서 단일 엔트리 조회
188
- */
189
- getEntry<T = Record<string, unknown>>(
190
- collection: string,
191
- id: string
192
- ): DataEntry<T> | undefined {
193
- this.ensureLoaded();
194
-
195
- const store = this.dataStore.getStore(collection);
196
- return store.get<T>(id);
197
- }
198
-
199
- /**
200
- * 컬렉션 존재 여부 확인
201
- */
202
- hasCollection(collection: string): boolean {
203
- return collection in this.contentConfig.collections;
204
- }
205
-
206
- /**
207
- * 모든 컬렉션 이름 조회
208
- */
209
- getCollectionNames(): string[] {
210
- return Object.keys(this.contentConfig.collections);
211
- }
212
-
213
- /**
214
- * 컬렉션 통계
215
- */
216
- getStats(): {
217
- collections: number;
218
- totalEntries: number;
219
- byCollection: Record<string, number>;
220
- } {
221
- this.ensureLoaded();
222
-
223
- const byCollection: Record<string, number> = {};
224
- let totalEntries = 0;
225
-
226
- for (const name of this.getCollectionNames()) {
227
- const store = this.dataStore.getStore(name);
228
- const count = store.size();
229
- byCollection[name] = count;
230
- totalEntries += count;
231
- }
232
-
233
- return {
234
- collections: Object.keys(byCollection).length,
235
- totalEntries,
236
- byCollection,
237
- };
238
- }
239
-
240
- /**
241
- * 로드 확인
242
- */
243
- private ensureLoaded(): void {
244
- if (!this.loaded) {
245
- throw new Error(
246
- "ContentLayer not loaded. Call await contentLayer.load() first."
247
- );
248
- }
249
- }
250
-
251
- /**
252
- * 리소스 정리
253
- */
254
- dispose(): void {
255
- this.dataStore.dispose();
256
- this.watcher?.close();
257
- }
258
- }
259
-
260
- /**
261
- * ContentLayer 팩토리
262
- */
263
- export function createContentLayer(options: ContentLayerOptions): ContentLayer {
264
- return new ContentLayer(options);
265
- }
266
-
267
- // ============================================================================
268
- // 전역 ContentLayer 인스턴스 (싱글톤)
269
- // ============================================================================
270
-
271
- let globalContentLayer: ContentLayer | null = null;
272
-
273
- /**
274
- * 전역 ContentLayer 설정
275
- */
276
- export function setGlobalContentLayer(layer: ContentLayer): void {
277
- globalContentLayer = layer;
278
- }
279
-
280
- /**
281
- * 전역 ContentLayer 조회
282
- */
283
- export function getGlobalContentLayer(): ContentLayer | null {
284
- return globalContentLayer;
285
- }
286
-
287
- /**
288
- * 컬렉션 조회 (전역 ContentLayer 사용)
289
- */
290
- export async function getCollection<T = Record<string, unknown>>(
291
- collection: string
292
- ): Promise<Array<DataEntry<T>>> {
293
- if (!globalContentLayer) {
294
- throw new Error("ContentLayer not initialized. Call setGlobalContentLayer() first.");
295
- }
296
-
297
- await globalContentLayer.load();
298
- return globalContentLayer.getCollection<T>(collection);
299
- }
300
-
301
- /**
302
- * 단일 엔트리 조회 (전역 ContentLayer 사용)
303
- */
304
- export async function getEntry<T = Record<string, unknown>>(
305
- collection: string,
306
- id: string
307
- ): Promise<DataEntry<T> | undefined> {
308
- if (!globalContentLayer) {
309
- throw new Error("ContentLayer not initialized. Call setGlobalContentLayer() first.");
310
- }
311
-
312
- await globalContentLayer.load();
313
- return globalContentLayer.getEntry<T>(collection, id);
314
- }
1
+ /**
2
+ * ContentLayer - 콘텐츠 레이어 메인 클래스
3
+ *
4
+ * 빌드 타임에 모든 컬렉션을 로드하고 관리
5
+ * getCollection(), getEntry() API 제공
6
+ */
7
+
8
+ import type {
9
+ ContentConfig,
10
+ CollectionConfig,
11
+ DataEntry,
12
+ ManduContentConfig,
13
+ ContentWatcher,
14
+ RenderedContent,
15
+ } from "./types";
16
+ import { LoaderError } from "./types";
17
+ import type { ContentDataStore} from "./data-store";
18
+ import { createDataStore } from "./data-store";
19
+ import type { ContentMetaStore} from "./meta-store";
20
+ import { createMetaStore } from "./meta-store";
21
+ import { createLoaderContext, createSimpleMarkdownRenderer } from "./loader-context";
22
+ import { CONTENT } from "../constants";
23
+ import type { ZodSchema } from "zod";
24
+ import * as path from "path";
25
+
26
+ /**
27
+ * ContentLayer 옵션
28
+ */
29
+ export interface ContentLayerOptions {
30
+ /** 콘텐츠 설정 */
31
+ contentConfig: ContentConfig;
32
+ /** Mandu 설정 */
33
+ manduConfig: ManduContentConfig;
34
+ /** Markdown 렌더러 (선택) */
35
+ markdownRenderer?: (content: string) => Promise<RenderedContent>;
36
+ /** 파일 감시자 (dev 모드) */
37
+ watcher?: ContentWatcher;
38
+ }
39
+
40
+ /**
41
+ * ContentLayer 클래스
42
+ *
43
+ * 빌드 타임 콘텐츠 로딩 및 관리
44
+ */
45
+ export class ContentLayer {
46
+ private contentConfig: ContentConfig;
47
+ private manduConfig: ManduContentConfig;
48
+ private dataStore: ContentDataStore;
49
+ private metaStore: ContentMetaStore;
50
+ private markdownRenderer: (content: string) => Promise<RenderedContent>;
51
+ private watcher?: ContentWatcher;
52
+ private loaded: boolean = false;
53
+ private loading: Promise<void> | null = null;
54
+
55
+ constructor(options: ContentLayerOptions) {
56
+ const { contentConfig, manduConfig, markdownRenderer, watcher } = options;
57
+
58
+ this.contentConfig = contentConfig;
59
+ this.manduConfig = manduConfig;
60
+ this.watcher = watcher;
61
+ this.markdownRenderer = markdownRenderer ?? createSimpleMarkdownRenderer();
62
+
63
+ // 스토어 초기화
64
+ const storeFile = path.join(manduConfig.root, CONTENT.STORE_FILE);
65
+ const metaFile = path.join(manduConfig.root, CONTENT.META_FILE);
66
+
67
+ this.dataStore = createDataStore({
68
+ filePath: storeFile,
69
+ autoSave: true,
70
+ saveDebounce: CONTENT.DEBOUNCE_SAVE,
71
+ });
72
+
73
+ this.metaStore = createMetaStore({
74
+ filePath: metaFile,
75
+ autoSave: true,
76
+ });
77
+ }
78
+
79
+ /**
80
+ * 모든 컬렉션 로드
81
+ */
82
+ async load(): Promise<void> {
83
+ // 이미 로딩 중이면 기다림
84
+ if (this.loading) {
85
+ return this.loading;
86
+ }
87
+
88
+ // 이미 로드됨
89
+ if (this.loaded) {
90
+ return;
91
+ }
92
+
93
+ this.loading = this.doLoad();
94
+ await this.loading;
95
+ this.loading = null;
96
+ this.loaded = true;
97
+ }
98
+
99
+ private async doLoad(): Promise<void> {
100
+ // 캐시에서 로드
101
+ await Promise.all([this.dataStore.load(), this.metaStore.load()]);
102
+
103
+ const collections = Object.entries(this.contentConfig.collections);
104
+
105
+ // 병렬로 모든 컬렉션 로드
106
+ await Promise.all(
107
+ collections.map(([name, config]) => this.loadCollection(name, config))
108
+ );
109
+
110
+ // 저장
111
+ await Promise.all([this.dataStore.save(), this.metaStore.save()]);
112
+ }
113
+
114
+ /**
115
+ * 단일 컬렉션 로드
116
+ */
117
+ private async loadCollection(name: string, config: CollectionConfig): Promise<void> {
118
+ const { loader, schema } = config;
119
+
120
+ // 로더 스키마와 컬렉션 스키마 병합 (컬렉션 우선)
121
+ let finalSchema: ZodSchema | undefined = schema;
122
+ if (!finalSchema && loader.schema) {
123
+ finalSchema =
124
+ typeof loader.schema === "function" ? await loader.schema() : loader.schema;
125
+ }
126
+
127
+ // LoaderContext 생성
128
+ const context = createLoaderContext({
129
+ collection: name,
130
+ store: this.dataStore.getStore(name),
131
+ meta: this.metaStore.getStore(name),
132
+ config: this.manduConfig,
133
+ schema: finalSchema,
134
+ markdownRenderer: this.markdownRenderer,
135
+ watcher: this.watcher,
136
+ });
137
+
138
+ try {
139
+ // 타임아웃 처리
140
+ const timeoutPromise = new Promise<never>((_, reject) => {
141
+ setTimeout(
142
+ () => reject(new LoaderError(`Loader "${loader.name}" timed out`, name)),
143
+ CONTENT.LOADER_TIMEOUT
144
+ );
145
+ });
146
+
147
+ await Promise.race([loader.load(context), timeoutPromise]);
148
+
149
+ context.logger.info(`Loaded ${context.store.size()} entries`);
150
+ } catch (error) {
151
+ const message = error instanceof Error ? error.message : String(error);
152
+ context.logger.error(`Failed to load: ${message}`);
153
+
154
+ if (error instanceof LoaderError) {
155
+ throw error;
156
+ }
157
+
158
+ throw new LoaderError(`Failed to load collection "${name}": ${message}`, name);
159
+ }
160
+ }
161
+
162
+ /**
163
+ * 단일 컬렉션 다시 로드
164
+ */
165
+ async reloadCollection(name: string): Promise<void> {
166
+ const config = this.contentConfig.collections[name];
167
+ if (!config) {
168
+ throw new Error(`Collection "${name}" not found`);
169
+ }
170
+
171
+ await this.loadCollection(name, config);
172
+ await this.dataStore.save();
173
+ await this.metaStore.save();
174
+ }
175
+
176
+ /**
177
+ * 컬렉션의 모든 엔트리 조회
178
+ */
179
+ getCollection<T = Record<string, unknown>>(
180
+ collection: string
181
+ ): Array<DataEntry<T>> {
182
+ this.ensureLoaded();
183
+
184
+ const store = this.dataStore.getStore(collection);
185
+ return store.values<T>();
186
+ }
187
+
188
+ /**
189
+ * 컬렉션에서 단일 엔트리 조회
190
+ */
191
+ getEntry<T = Record<string, unknown>>(
192
+ collection: string,
193
+ id: string
194
+ ): DataEntry<T> | undefined {
195
+ this.ensureLoaded();
196
+
197
+ const store = this.dataStore.getStore(collection);
198
+ return store.get<T>(id);
199
+ }
200
+
201
+ /**
202
+ * 컬렉션 존재 여부 확인
203
+ */
204
+ hasCollection(collection: string): boolean {
205
+ return collection in this.contentConfig.collections;
206
+ }
207
+
208
+ /**
209
+ * 모든 컬렉션 이름 조회
210
+ */
211
+ getCollectionNames(): string[] {
212
+ return Object.keys(this.contentConfig.collections);
213
+ }
214
+
215
+ /**
216
+ * 컬렉션 통계
217
+ */
218
+ getStats(): {
219
+ collections: number;
220
+ totalEntries: number;
221
+ byCollection: Record<string, number>;
222
+ } {
223
+ this.ensureLoaded();
224
+
225
+ const byCollection: Record<string, number> = {};
226
+ let totalEntries = 0;
227
+
228
+ for (const name of this.getCollectionNames()) {
229
+ const store = this.dataStore.getStore(name);
230
+ const count = store.size();
231
+ byCollection[name] = count;
232
+ totalEntries += count;
233
+ }
234
+
235
+ return {
236
+ collections: Object.keys(byCollection).length,
237
+ totalEntries,
238
+ byCollection,
239
+ };
240
+ }
241
+
242
+ /**
243
+ * 로드 확인
244
+ */
245
+ private ensureLoaded(): void {
246
+ if (!this.loaded) {
247
+ throw new Error(
248
+ "ContentLayer not loaded. Call await contentLayer.load() first."
249
+ );
250
+ }
251
+ }
252
+
253
+ /**
254
+ * 리소스 정리
255
+ */
256
+ dispose(): void {
257
+ this.dataStore.dispose();
258
+ this.watcher?.close();
259
+ }
260
+ }
261
+
262
+ /**
263
+ * ContentLayer 팩토리
264
+ */
265
+ export function createContentLayer(options: ContentLayerOptions): ContentLayer {
266
+ return new ContentLayer(options);
267
+ }
268
+
269
+ // ============================================================================
270
+ // 전역 ContentLayer 인스턴스 (싱글톤)
271
+ // ============================================================================
272
+
273
+ let globalContentLayer: ContentLayer | null = null;
274
+
275
+ /**
276
+ * 전역 ContentLayer 설정
277
+ */
278
+ export function setGlobalContentLayer(layer: ContentLayer): void {
279
+ globalContentLayer = layer;
280
+ }
281
+
282
+ /**
283
+ * 전역 ContentLayer 조회
284
+ */
285
+ export function getGlobalContentLayer(): ContentLayer | null {
286
+ return globalContentLayer;
287
+ }
288
+
289
+ /**
290
+ * 컬렉션 조회 (전역 ContentLayer 사용)
291
+ */
292
+ export async function getCollection<T = Record<string, unknown>>(
293
+ collection: string
294
+ ): Promise<Array<DataEntry<T>>> {
295
+ if (!globalContentLayer) {
296
+ throw new Error("ContentLayer not initialized. Call setGlobalContentLayer() first.");
297
+ }
298
+
299
+ await globalContentLayer.load();
300
+ return globalContentLayer.getCollection<T>(collection);
301
+ }
302
+
303
+ /**
304
+ * 단일 엔트리 조회 (전역 ContentLayer 사용)
305
+ */
306
+ export async function getEntry<T = Record<string, unknown>>(
307
+ collection: string,
308
+ id: string
309
+ ): Promise<DataEntry<T> | undefined> {
310
+ if (!globalContentLayer) {
311
+ throw new Error("ContentLayer not initialized. Call setGlobalContentLayer() first.");
312
+ }
313
+
314
+ await globalContentLayer.load();
315
+ return globalContentLayer.getEntry<T>(collection, id);
316
+ }