@otl-core/cms-api 1.1.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/dist/index.js ADDED
@@ -0,0 +1,466 @@
1
+ import 'server-only';
2
+
3
+ // src/index.ts
4
+
5
+ // src/services/collection.service.ts
6
+ var CollectionService = class {
7
+ constructor(httpClient) {
8
+ this.httpClient = httpClient;
9
+ }
10
+ async fetchCollections(siteId) {
11
+ return this.httpClient.get(`/api/v1/public/sites/${siteId}/collections`);
12
+ }
13
+ async fetchEntries(collectionId, params) {
14
+ return this.httpClient.get(
15
+ `/api/v1/public/collections/${collectionId}/entries`,
16
+ params
17
+ );
18
+ }
19
+ async fetchEntry(collectionId, entrySlug) {
20
+ return this.httpClient.get(
21
+ `/api/v1/public/collections/${collectionId}/entries/${entrySlug}`
22
+ );
23
+ }
24
+ async fetchCategories(collectionId) {
25
+ return this.httpClient.get(
26
+ `/api/v1/public/collections/${collectionId}/categories`
27
+ );
28
+ }
29
+ };
30
+
31
+ // src/api.config.ts
32
+ function getBackendApiBaseUrl() {
33
+ if (process.env.API_URL) {
34
+ return process.env.API_URL;
35
+ }
36
+ if (process.env.NODE_ENV === "development") {
37
+ return "http://localhost:8080";
38
+ }
39
+ if (process.env.STAGE === "staging") {
40
+ return "https://api-staging.otl-cms.com";
41
+ }
42
+ return "https://api.otl-cms.com";
43
+ }
44
+ function getBackendSiteId() {
45
+ const siteId = process.env.SITE_ID;
46
+ if (!siteId) {
47
+ console.error("SITE_ID environment variable is not set!");
48
+ console.error("Add the following to your .env.local:");
49
+ console.error(" SITE_ID=your-site-id");
50
+ console.error(" API_URL=http://localhost:8080");
51
+ throw new Error("SITE_ID environment variable is required.");
52
+ }
53
+ return siteId;
54
+ }
55
+ function getSiteAccessToken() {
56
+ const token = process.env.SITE_ACCESS_TOKEN;
57
+ if (!token) {
58
+ console.error("SITE_ACCESS_TOKEN environment variable is not set!");
59
+ console.error("Add the following to your .env.local:");
60
+ console.error(" SITE_ACCESS_TOKEN=kpt_...");
61
+ console.error("");
62
+ console.error("To obtain an access token:");
63
+ console.error("1. Log into the CMS management interface");
64
+ console.error("2. Navigate to your site settings");
65
+ console.error("3. Go to Access Tokens section");
66
+ console.error("4. Create a new token and copy it here");
67
+ throw new Error(
68
+ "SITE_ACCESS_TOKEN environment variable is required. Please obtain an access token from the CMS site settings."
69
+ );
70
+ }
71
+ return token;
72
+ }
73
+ function getBackendEnvironment() {
74
+ if (process.env.NODE_ENV === "production") {
75
+ return process.env.STAGE === "staging" ? "staging" : "production";
76
+ }
77
+ return "development";
78
+ }
79
+ var defaultAPIConfig = {
80
+ http: {
81
+ baseUrl: getBackendApiBaseUrl(),
82
+ timeout: 3e4,
83
+ defaultHeaders: {
84
+ "Content-Type": "application/json",
85
+ "User-Agent": "OTL-CMS-Engine/1.0"
86
+ }
87
+ },
88
+ siteId: getBackendSiteId(),
89
+ devToken: getSiteAccessToken(),
90
+ environment: getBackendEnvironment()
91
+ };
92
+ function createAPIConfig(overrides) {
93
+ return {
94
+ ...defaultAPIConfig,
95
+ ...overrides,
96
+ http: {
97
+ ...defaultAPIConfig.http,
98
+ ...overrides?.http
99
+ }
100
+ };
101
+ }
102
+ function validateAPIConfig(config) {
103
+ if (!config.http.baseUrl) {
104
+ throw new Error("API base URL is required");
105
+ }
106
+ try {
107
+ new URL(config.http.baseUrl);
108
+ } catch {
109
+ throw new Error("Invalid API base URL format");
110
+ }
111
+ if (!config.siteId) {
112
+ throw new Error("Site ID is required");
113
+ }
114
+ if (!config.devToken) {
115
+ throw new Error(
116
+ "Site access token is required. Please set SITE_ACCESS_TOKEN environment variable."
117
+ );
118
+ }
119
+ if (config.http.timeout && config.http.timeout < 1e3) {
120
+ console.warn("API timeout is very low, this might cause issues");
121
+ }
122
+ }
123
+
124
+ // src/services/form.service.ts
125
+ var FormService = class {
126
+ constructor(httpClient) {
127
+ this.httpClient = httpClient;
128
+ }
129
+ async submitForm(formId, data) {
130
+ return this.httpClient.post(
131
+ `/api/v1/public/forms/${formId}/submit`,
132
+ data
133
+ );
134
+ }
135
+ };
136
+
137
+ // src/http-client.ts
138
+ var HttpClient = class {
139
+ constructor(config) {
140
+ this.baseUrl = config.baseUrl.replace(/\/$/, "");
141
+ this.timeout = config.timeout || 3e4;
142
+ this.defaultHeaders = {
143
+ "Content-Type": "application/json",
144
+ ...config.defaultHeaders
145
+ };
146
+ }
147
+ /**
148
+ * Make a generic HTTP request
149
+ */
150
+ async request(endpoint, options = {}) {
151
+ const url = `${this.baseUrl}${endpoint}`;
152
+ const controller = new AbortController();
153
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
154
+ const { next: nextOptions, ...fetchOptions } = options;
155
+ const mergedHeaders = {
156
+ ...this.defaultHeaders,
157
+ ...fetchOptions.headers
158
+ };
159
+ const headers = Object.fromEntries(
160
+ Object.entries(mergedHeaders).filter(
161
+ (entry) => entry[1] !== void 0
162
+ )
163
+ );
164
+ try {
165
+ const response = await fetch(url, {
166
+ ...fetchOptions,
167
+ headers,
168
+ signal: controller.signal,
169
+ ...process.env.NODE_ENV === "development" ? { cache: "no-store" } : nextOptions ? { next: nextOptions } : {}
170
+ });
171
+ clearTimeout(timeoutId);
172
+ if (response.status >= 500 && process.env.NODE_ENV === "production" && nextOptions) {
173
+ const staleData = await this.tryStaleCache(
174
+ url,
175
+ fetchOptions,
176
+ headers
177
+ );
178
+ if (staleData !== null) return staleData;
179
+ }
180
+ const data = await response.json();
181
+ if (!response.ok) {
182
+ if (data && typeof data === "object") {
183
+ if ("success" in data) {
184
+ return data;
185
+ }
186
+ if ("type" in data) {
187
+ return {
188
+ success: response.status >= 200 && response.status < 300,
189
+ data
190
+ };
191
+ }
192
+ }
193
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
194
+ }
195
+ return data;
196
+ } catch (error) {
197
+ clearTimeout(timeoutId);
198
+ if (error instanceof Error) {
199
+ if (process.env.NODE_ENV === "production" && nextOptions) {
200
+ const staleData = await this.tryStaleCache(
201
+ url,
202
+ fetchOptions,
203
+ headers
204
+ );
205
+ if (staleData !== null) return staleData;
206
+ }
207
+ if (error.name === "AbortError") {
208
+ throw new Error("Request timeout");
209
+ }
210
+ throw error;
211
+ }
212
+ throw new Error("Unknown error occurred");
213
+ }
214
+ }
215
+ /**
216
+ * Attempt to serve a stale cached response when the backend is unreachable
217
+ * or returning server errors. Returns null if no cache entry exists.
218
+ */
219
+ async tryStaleCache(url, fetchOptions, headers) {
220
+ try {
221
+ console.warn(
222
+ `[HttpClient] Backend unavailable, attempting stale cache: ${url}`
223
+ );
224
+ const staleResponse = await fetch(url, {
225
+ ...fetchOptions,
226
+ headers,
227
+ cache: "force-cache"
228
+ });
229
+ if (staleResponse.ok) {
230
+ const staleData = await staleResponse.json();
231
+ console.log(`[HttpClient] Serving stale cache for: ${url}`);
232
+ return staleData;
233
+ }
234
+ } catch (cacheError) {
235
+ console.error(
236
+ `[HttpClient] No cache available for: ${url}`,
237
+ cacheError instanceof Error ? cacheError.message : cacheError
238
+ );
239
+ }
240
+ return null;
241
+ }
242
+ async get(endpoint, params, options) {
243
+ let url = endpoint;
244
+ if (params) {
245
+ const searchParams = new URLSearchParams();
246
+ Object.entries(params).forEach(([key, value]) => {
247
+ if (value !== void 0 && value !== null) {
248
+ searchParams.append(key, String(value));
249
+ }
250
+ });
251
+ if (searchParams.toString()) {
252
+ url += `?${searchParams.toString()}`;
253
+ }
254
+ }
255
+ return this.request(url, { method: "GET", ...options });
256
+ }
257
+ async post(endpoint, data) {
258
+ return this.request(endpoint, {
259
+ method: "POST",
260
+ body: data ? JSON.stringify(data) : void 0
261
+ });
262
+ }
263
+ async put(endpoint, data) {
264
+ return this.request(endpoint, {
265
+ method: "PUT",
266
+ body: data ? JSON.stringify(data) : void 0
267
+ });
268
+ }
269
+ async delete(endpoint) {
270
+ return this.request(endpoint, { method: "DELETE" });
271
+ }
272
+ async upload(endpoint, formData) {
273
+ return this.request(endpoint, {
274
+ method: "POST",
275
+ body: formData,
276
+ headers: { "Content-Type": void 0 }
277
+ });
278
+ }
279
+ setAuthToken(token) {
280
+ this.defaultHeaders["Authorization"] = `Bearer ${token}`;
281
+ }
282
+ removeAuthToken() {
283
+ delete this.defaultHeaders["Authorization"];
284
+ }
285
+ setBaseUrl(baseUrl) {
286
+ this.baseUrl = baseUrl.replace(/\/$/, "");
287
+ }
288
+ };
289
+
290
+ // src/services/media.service.ts
291
+ var MediaService = class {
292
+ constructor(httpClient) {
293
+ this.httpClient = httpClient;
294
+ }
295
+ async fetchMediaFile(siteId, mediaId) {
296
+ return this.httpClient.get(
297
+ `/api/v1/public/sites/${siteId}/media/${mediaId}`
298
+ );
299
+ }
300
+ async fetchMedia(siteId) {
301
+ return this.httpClient.get(`/api/v1/public/sites/${siteId}/media`);
302
+ }
303
+ };
304
+
305
+ // src/services/website.service.ts
306
+ var WebsiteService = class {
307
+ constructor(httpClient) {
308
+ this.httpClient = httpClient;
309
+ }
310
+ async fetchConfigs(siteId, locale, headerPresetId, footerPresetId) {
311
+ const params = new URLSearchParams();
312
+ if (locale) params.set("locale", locale);
313
+ if (headerPresetId) params.set("header_preset", headerPresetId);
314
+ if (footerPresetId) params.set("footer_preset", footerPresetId);
315
+ const queryString = params.toString();
316
+ const url = queryString ? `/api/v1/public/sites/${siteId}/configs?${queryString}` : `/api/v1/public/sites/${siteId}/configs`;
317
+ const defaultRevalidate = parseInt(
318
+ process.env.CACHE_REVALIDATE_SECONDS || "60",
319
+ 10
320
+ );
321
+ const tags = [`site:${siteId}`, `config:${siteId}`, "config"];
322
+ if (headerPresetId) tags.push(`header-preset:${headerPresetId}`);
323
+ if (footerPresetId) tags.push(`footer-preset:${footerPresetId}`);
324
+ return this.httpClient.get(url, void 0, {
325
+ next: {
326
+ revalidate: defaultRevalidate === 0 ? false : defaultRevalidate,
327
+ tags
328
+ }
329
+ });
330
+ }
331
+ async resolvePath(siteId, path, locale, options) {
332
+ const params = new URLSearchParams({
333
+ path,
334
+ locale,
335
+ ...options?.fetchContent && { fetch_content: "true" },
336
+ ...options?.mode && { mode: options.mode },
337
+ ...options?.page && { page: options.page.toString() },
338
+ ...options?.perPage && { perPage: options.perPage.toString() },
339
+ ...options?.searchQuery && { q: options.searchQuery }
340
+ });
341
+ const defaultRevalidate = parseInt(
342
+ process.env.CACHE_REVALIDATE_SECONDS || "60",
343
+ 10
344
+ );
345
+ return this.httpClient.get(
346
+ `/api/v1/public/sites/${siteId}/resolve-path?${params}`,
347
+ void 0,
348
+ {
349
+ next: {
350
+ revalidate: defaultRevalidate === 0 ? false : defaultRevalidate,
351
+ tags: [
352
+ `site:${siteId}`,
353
+ `path:${siteId}:${locale}:${path}`,
354
+ "content"
355
+ ]
356
+ }
357
+ }
358
+ );
359
+ }
360
+ async resolvePaths(siteId, paths, locale, fetchContent = false) {
361
+ return this.httpClient.post(
362
+ `/api/v1/public/sites/${siteId}/resolve-paths`,
363
+ {
364
+ paths,
365
+ locale,
366
+ fetch_content: fetchContent
367
+ }
368
+ );
369
+ }
370
+ async fetchAllPaths(siteId, locale, options) {
371
+ const params = new URLSearchParams({
372
+ locale,
373
+ ...options?.limit && { limit: options.limit.toString() },
374
+ ...options?.offset && { offset: options.offset.toString() }
375
+ });
376
+ return this.httpClient.get(
377
+ `/api/v1/public/sites/${siteId}/all-paths?${params}`
378
+ );
379
+ }
380
+ async fetchPageSections(siteId, path) {
381
+ const encodedPath = encodeURIComponent(path || "/");
382
+ return this.httpClient.get(
383
+ `/api/v1/sites/${siteId}/pages/${encodedPath}/sections`
384
+ );
385
+ }
386
+ async fetchPageSEO(siteId, path) {
387
+ const encodedPath = encodeURIComponent(path || "/");
388
+ return this.httpClient.get(
389
+ `/api/v1/sites/${siteId}/pages/${encodedPath}/seo`
390
+ );
391
+ }
392
+ async fetchRedirects(siteId) {
393
+ return this.httpClient.get(`/api/v1/sites/${siteId}/redirects`);
394
+ }
395
+ async fetchSitemap(siteId) {
396
+ return this.httpClient.get(`/api/v1/sites/${siteId}/sitemap`);
397
+ }
398
+ async fetchRobots(siteId) {
399
+ return this.httpClient.get(`/api/v1/sites/${siteId}/robots`);
400
+ }
401
+ };
402
+
403
+ // src/api-client.ts
404
+ var APIClient = class {
405
+ constructor(config) {
406
+ this.config = {
407
+ ...defaultAPIConfig,
408
+ ...config,
409
+ http: {
410
+ ...defaultAPIConfig.http,
411
+ ...config?.http
412
+ }
413
+ };
414
+ validateAPIConfig(this.config);
415
+ this.httpClient = new HttpClient(this.config.http);
416
+ if (this.config.devToken) {
417
+ this.httpClient.setAuthToken(this.config.devToken);
418
+ }
419
+ this.website = new WebsiteService(this.httpClient);
420
+ this.collection = new CollectionService(this.httpClient);
421
+ this.media = new MediaService(this.httpClient);
422
+ this.forms = new FormService(this.httpClient);
423
+ }
424
+ getSiteId() {
425
+ return this.config.siteId;
426
+ }
427
+ setSiteId(siteId) {
428
+ this.config.siteId = siteId;
429
+ }
430
+ setAuthToken(token) {
431
+ this.httpClient.setAuthToken(token);
432
+ }
433
+ removeAuthToken() {
434
+ this.httpClient.removeAuthToken();
435
+ }
436
+ setBaseUrl(baseUrl) {
437
+ this.httpClient.setBaseUrl(baseUrl);
438
+ this.config.http.baseUrl = baseUrl;
439
+ }
440
+ getBaseUrl() {
441
+ return this.httpClient.baseUrl;
442
+ }
443
+ getConfig() {
444
+ return { ...this.config };
445
+ }
446
+ };
447
+ var apiClient;
448
+ function initializeAPIClient(config) {
449
+ apiClient = new APIClient(config);
450
+ return apiClient;
451
+ }
452
+ function getAPIClient() {
453
+ if (!apiClient) {
454
+ try {
455
+ apiClient = new APIClient();
456
+ } catch (error) {
457
+ console.error("Failed to initialize API client:", error);
458
+ throw error;
459
+ }
460
+ }
461
+ return apiClient;
462
+ }
463
+
464
+ export { APIClient, CollectionService, FormService, HttpClient, MediaService, WebsiteService, createAPIConfig, defaultAPIConfig, getAPIClient, initializeAPIClient, validateAPIConfig };
465
+ //# sourceMappingURL=index.js.map
466
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/services/collection.service.ts","../src/api.config.ts","../src/services/form.service.ts","../src/http-client.ts","../src/services/media.service.ts","../src/services/website.service.ts","../src/api-client.ts"],"names":[],"mappings":";;;;;AAaO,IAAM,oBAAN,MAAwB;AAAA,EAC7B,YAAoB,UAAA,EAAwB;AAAxB,IAAA,IAAA,CAAA,UAAA,GAAA,UAAA;AAAA,EAAyB;AAAA,EAE7C,MAAM,iBAAiB,MAAA,EAAoD;AACzE,IAAA,OAAO,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,CAAA,qBAAA,EAAwB,MAAM,CAAA,YAAA,CAAc,CAAA;AAAA,EACzE;AAAA,EAEA,MAAM,YAAA,CACJ,YAAA,EACA,MAAA,EAC+B;AAC/B,IAAA,OAAO,KAAK,UAAA,CAAW,GAAA;AAAA,MACrB,8BAA8B,YAAY,CAAA,QAAA,CAAA;AAAA,MAC1C;AAAA,KACF;AAAA,EACF;AAAA,EAEA,MAAM,UAAA,CACJ,YAAA,EACA,SAAA,EAC6B;AAC7B,IAAA,OAAO,KAAK,UAAA,CAAW,GAAA;AAAA,MACrB,CAAA,2BAAA,EAA8B,YAAY,CAAA,SAAA,EAAY,SAAS,CAAA;AAAA,KACjE;AAAA,EACF;AAAA,EAEA,MAAM,gBACJ,YAAA,EACkC;AAClC,IAAA,OAAO,KAAK,UAAA,CAAW,GAAA;AAAA,MACrB,8BAA8B,YAAY,CAAA,WAAA;AAAA,KAC5C;AAAA,EACF;AACF;;;AC5BA,SAAS,oBAAA,GAA+B;AACtC,EAAA,IAAI,OAAA,CAAQ,IAAI,OAAA,EAAS;AACvB,IAAA,OAAO,QAAQ,GAAA,CAAI,OAAA;AAAA,EACrB;AAEA,EAAA,IAAI,OAAA,CAAQ,GAAA,CAAI,QAAA,KAAa,aAAA,EAAe;AAC1C,IAAA,OAAO,uBAAA;AAAA,EACT;AAEA,EAAA,IAAI,OAAA,CAAQ,GAAA,CAAI,KAAA,KAAU,SAAA,EAAW;AACnC,IAAA,OAAO,iCAAA;AAAA,EACT;AAEA,EAAA,OAAO,yBAAA;AACT;AAKA,SAAS,gBAAA,GAA2B;AAClC,EAAA,MAAM,MAAA,GAAS,QAAQ,GAAA,CAAI,OAAA;AAC3B,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,OAAA,CAAQ,MAAM,0CAA0C,CAAA;AACxD,IAAA,OAAA,CAAQ,MAAM,uCAAuC,CAAA;AACrD,IAAA,OAAA,CAAQ,MAAM,wBAAwB,CAAA;AACtC,IAAA,OAAA,CAAQ,MAAM,iCAAiC,CAAA;AAC/C,IAAA,MAAM,IAAI,MAAM,2CAA2C,CAAA;AAAA,EAC7D;AACA,EAAA,OAAO,MAAA;AACT;AAKA,SAAS,kBAAA,GAA6B;AACpC,EAAA,MAAM,KAAA,GAAQ,QAAQ,GAAA,CAAI,iBAAA;AAC1B,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,OAAA,CAAQ,MAAM,oDAAoD,CAAA;AAClE,IAAA,OAAA,CAAQ,MAAM,uCAAuC,CAAA;AACrD,IAAA,OAAA,CAAQ,MAAM,6BAA6B,CAAA;AAC3C,IAAA,OAAA,CAAQ,MAAM,EAAE,CAAA;AAChB,IAAA,OAAA,CAAQ,MAAM,4BAA4B,CAAA;AAC1C,IAAA,OAAA,CAAQ,MAAM,0CAA0C,CAAA;AACxD,IAAA,OAAA,CAAQ,MAAM,mCAAmC,CAAA;AACjD,IAAA,OAAA,CAAQ,MAAM,gCAAgC,CAAA;AAC9C,IAAA,OAAA,CAAQ,MAAM,wCAAwC,CAAA;AACtD,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KAEF;AAAA,EACF;AACA,EAAA,OAAO,KAAA;AACT;AAKA,SAAS,qBAAA,GAAkE;AACzE,EAAA,IAAI,OAAA,CAAQ,GAAA,CAAI,QAAA,KAAa,YAAA,EAAc;AACzC,IAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,KAAA,KAAU,SAAA,GAAY,SAAA,GAAY,YAAA;AAAA,EACvD;AACA,EAAA,OAAO,aAAA;AACT;AAKO,IAAM,gBAAA,GAA8B;AAAA,EACzC,IAAA,EAAM;AAAA,IACJ,SAAS,oBAAA,EAAqB;AAAA,IAC9B,OAAA,EAAS,GAAA;AAAA,IACT,cAAA,EAAgB;AAAA,MACd,cAAA,EAAgB,kBAAA;AAAA,MAChB,YAAA,EAAc;AAAA;AAChB,GACF;AAAA,EACA,QAAQ,gBAAA,EAAiB;AAAA,EACzB,UAAU,kBAAA,EAAmB;AAAA,EAC7B,aAAa,qBAAA;AACf;AAKO,SAAS,gBAAgB,SAAA,EAA2C;AACzE,EAAA,OAAO;AAAA,IACL,GAAG,gBAAA;AAAA,IACH,GAAG,SAAA;AAAA,IACH,IAAA,EAAM;AAAA,MACJ,GAAG,gBAAA,CAAiB,IAAA;AAAA,MACpB,GAAG,SAAA,EAAW;AAAA;AAChB,GACF;AACF;AAKO,SAAS,kBAAkB,MAAA,EAAyB;AACzD,EAAA,IAAI,CAAC,MAAA,CAAO,IAAA,CAAK,OAAA,EAAS;AACxB,IAAA,MAAM,IAAI,MAAM,0BAA0B,CAAA;AAAA,EAC5C;AAEA,EAAA,IAAI;AACF,IAAA,IAAI,GAAA,CAAI,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA;AAAA,EAC7B,CAAA,CAAA,MAAQ;AACN,IAAA,MAAM,IAAI,MAAM,6BAA6B,CAAA;AAAA,EAC/C;AAEA,EAAA,IAAI,CAAC,OAAO,MAAA,EAAQ;AAClB,IAAA,MAAM,IAAI,MAAM,qBAAqB,CAAA;AAAA,EACvC;AAEA,EAAA,IAAI,CAAC,OAAO,QAAA,EAAU;AACpB,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KAEF;AAAA,EACF;AAEA,EAAA,IAAI,OAAO,IAAA,CAAK,OAAA,IAAW,MAAA,CAAO,IAAA,CAAK,UAAU,GAAA,EAAM;AACrD,IAAA,OAAA,CAAQ,KAAK,kDAAkD,CAAA;AAAA,EACjE;AACF;;;ACpHO,IAAM,cAAN,MAAkB;AAAA,EACvB,YAAoB,UAAA,EAAwB;AAAxB,IAAA,IAAA,CAAA,UAAA,GAAA,UAAA;AAAA,EAAyB;AAAA,EAE7C,MAAM,UAAA,CACJ,MAAA,EACA,IAAA,EACiC;AACjC,IAAA,OAAO,KAAK,UAAA,CAAW,IAAA;AAAA,MACrB,wBAAwB,MAAM,CAAA,OAAA,CAAA;AAAA,MAC9B;AAAA,KACF;AAAA,EACF;AACF;;;ACvBO,IAAM,aAAN,MAAiB;AAAA,EAKtB,YAAY,MAAA,EAA0B;AACpC,IAAA,IAAA,CAAK,OAAA,GAAU,MAAA,CAAO,OAAA,CAAQ,OAAA,CAAQ,OAAO,EAAE,CAAA;AAC/C,IAAA,IAAA,CAAK,OAAA,GAAU,OAAO,OAAA,IAAW,GAAA;AACjC,IAAA,IAAA,CAAK,cAAA,GAAiB;AAAA,MACpB,cAAA,EAAgB,kBAAA;AAAA,MAChB,GAAG,MAAA,CAAO;AAAA,KACZ;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,OAAA,CACZ,QAAA,EACA,OAAA,GAAwB,EAAC,EACb;AACZ,IAAA,MAAM,GAAA,GAAM,CAAA,EAAG,IAAA,CAAK,OAAO,GAAG,QAAQ,CAAA,CAAA;AAEtC,IAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,IAAA,MAAM,YAAY,UAAA,CAAW,MAAM,WAAW,KAAA,EAAM,EAAG,KAAK,OAAO,CAAA;AAEnE,IAAA,MAAM,EAAE,IAAA,EAAM,WAAA,EAAa,GAAG,cAAa,GAAI,OAAA;AAE/C,IAAA,MAAM,aAAA,GAAoD;AAAA,MACxD,GAAG,IAAA,CAAK,cAAA;AAAA,MACR,GAAG,YAAA,CAAa;AAAA,KAClB;AACA,IAAA,MAAM,UAAU,MAAA,CAAO,WAAA;AAAA,MACrB,MAAA,CAAO,OAAA,CAAQ,aAAa,CAAA,CAAE,MAAA;AAAA,QAC5B,CAAC,KAAA,KAAqC,KAAA,CAAM,CAAC,CAAA,KAAM;AAAA;AACrD,KACF;AAEA,IAAA,IAAI;AACF,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,QAChC,GAAG,YAAA;AAAA,QACH,OAAA;AAAA,QACA,QAAQ,UAAA,CAAW,MAAA;AAAA,QACnB,GAAI,OAAA,CAAQ,GAAA,CAAI,QAAA,KAAa,gBACzB,EAAE,KAAA,EAAO,UAAA,EAAoB,GAC7B,WAAA,GACE,EAAE,IAAA,EAAM,WAAA,KACR;AAAC,OACR,CAAA;AAED,MAAA,YAAA,CAAa,SAAS,CAAA;AAGtB,MAAA,IACE,SAAS,MAAA,IAAU,GAAA,IACnB,QAAQ,GAAA,CAAI,QAAA,KAAa,gBACzB,WAAA,EACA;AACA,QAAA,MAAM,SAAA,GAAY,MAAM,IAAA,CAAK,aAAA;AAAA,UAC3B,GAAA;AAAA,UACA,YAAA;AAAA,UACA;AAAA,SACF;AACA,QAAA,IAAI,SAAA,KAAc,MAAM,OAAO,SAAA;AAAA,MACjC;AAEA,MAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AAEjC,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,QAAA,IAAI,IAAA,IAAQ,OAAO,IAAA,KAAS,QAAA,EAAU;AACpC,UAAA,IAAI,aAAa,IAAA,EAAM;AACrB,YAAA,OAAO,IAAA;AAAA,UACT;AACA,UAAA,IAAI,UAAU,IAAA,EAAM;AAClB,YAAA,OAAO;AAAA,cACL,OAAA,EAAS,QAAA,CAAS,MAAA,IAAU,GAAA,IAAO,SAAS,MAAA,GAAS,GAAA;AAAA,cACrD;AAAA,aACF;AAAA,UACF;AAAA,QACF;AACA,QAAA,MAAM,IAAI,MAAM,CAAA,KAAA,EAAQ,QAAA,CAAS,MAAM,CAAA,EAAA,EAAK,QAAA,CAAS,UAAU,CAAA,CAAE,CAAA;AAAA,MACnE;AAEA,MAAA,OAAO,IAAA;AAAA,IACT,SAAS,KAAA,EAAO;AACd,MAAA,YAAA,CAAa,SAAS,CAAA;AAEtB,MAAA,IAAI,iBAAiB,KAAA,EAAO;AAE1B,QAAA,IAAI,OAAA,CAAQ,GAAA,CAAI,QAAA,KAAa,YAAA,IAAgB,WAAA,EAAa;AACxD,UAAA,MAAM,SAAA,GAAY,MAAM,IAAA,CAAK,aAAA;AAAA,YAC3B,GAAA;AAAA,YACA,YAAA;AAAA,YACA;AAAA,WACF;AACA,UAAA,IAAI,SAAA,KAAc,MAAM,OAAO,SAAA;AAAA,QACjC;AAEA,QAAA,IAAI,KAAA,CAAM,SAAS,YAAA,EAAc;AAC/B,UAAA,MAAM,IAAI,MAAM,iBAAiB,CAAA;AAAA,QACnC;AACA,QAAA,MAAM,KAAA;AAAA,MACR;AAEA,MAAA,MAAM,IAAI,MAAM,wBAAwB,CAAA;AAAA,IAC1C;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,aAAA,CACZ,GAAA,EACA,YAAA,EACA,OAAA,EACmB;AACnB,IAAA,IAAI;AACF,MAAA,OAAA,CAAQ,IAAA;AAAA,QACN,6DAA6D,GAAG,CAAA;AAAA,OAClE;AACA,MAAA,MAAM,aAAA,GAAgB,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,QACrC,GAAG,YAAA;AAAA,QACH,OAAA;AAAA,QACA,KAAA,EAAO;AAAA,OACR,CAAA;AAED,MAAA,IAAI,cAAc,EAAA,EAAI;AACpB,QAAA,MAAM,SAAA,GAAY,MAAM,aAAA,CAAc,IAAA,EAAK;AAC3C,QAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,sCAAA,EAAyC,GAAG,CAAA,CAAE,CAAA;AAC1D,QAAA,OAAO,SAAA;AAAA,MACT;AAAA,IACF,SAAS,UAAA,EAAY;AACnB,MAAA,OAAA,CAAQ,KAAA;AAAA,QACN,wCAAwC,GAAG,CAAA,CAAA;AAAA,QAC3C,UAAA,YAAsB,KAAA,GAAQ,UAAA,CAAW,OAAA,GAAU;AAAA,OACrD;AAAA,IACF;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,MAAM,GAAA,CACJ,QAAA,EACA,MAAA,EACA,OAAA,EACY;AACZ,IAAA,IAAI,GAAA,GAAM,QAAA;AAEV,IAAA,IAAI,MAAA,EAAQ;AACV,MAAA,MAAM,YAAA,GAAe,IAAI,eAAA,EAAgB;AACzC,MAAA,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,CAAE,OAAA,CAAQ,CAAC,CAAC,GAAA,EAAK,KAAK,CAAA,KAAM;AAC/C,QAAA,IAAI,KAAA,KAAU,MAAA,IAAa,KAAA,KAAU,IAAA,EAAM;AACzC,UAAA,YAAA,CAAa,MAAA,CAAO,GAAA,EAAK,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,QACxC;AAAA,MACF,CAAC,CAAA;AAED,MAAA,IAAI,YAAA,CAAa,UAAS,EAAG;AAC3B,QAAA,GAAA,IAAO,CAAA,CAAA,EAAI,YAAA,CAAa,QAAA,EAAU,CAAA,CAAA;AAAA,MACpC;AAAA,IACF;AAEA,IAAA,OAAO,IAAA,CAAK,QAAW,GAAA,EAAK,EAAE,QAAQ,KAAA,EAAO,GAAG,SAAS,CAAA;AAAA,EAC3D;AAAA,EAEA,MAAM,IAAA,CAAQ,QAAA,EAAkB,IAAA,EAA4B;AAC1D,IAAA,OAAO,IAAA,CAAK,QAAW,QAAA,EAAU;AAAA,MAC/B,MAAA,EAAQ,MAAA;AAAA,MACR,IAAA,EAAM,IAAA,GAAO,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA,GAAI;AAAA,KACrC,CAAA;AAAA,EACH;AAAA,EAEA,MAAM,GAAA,CAAO,QAAA,EAAkB,IAAA,EAA4B;AACzD,IAAA,OAAO,IAAA,CAAK,QAAW,QAAA,EAAU;AAAA,MAC/B,MAAA,EAAQ,KAAA;AAAA,MACR,IAAA,EAAM,IAAA,GAAO,IAAA,CAAK,SAAA,CAAU,IAAI,CAAA,GAAI;AAAA,KACrC,CAAA;AAAA,EACH;AAAA,EAEA,MAAM,OAAU,QAAA,EAA8B;AAC5C,IAAA,OAAO,KAAK,OAAA,CAAW,QAAA,EAAU,EAAE,MAAA,EAAQ,UAAU,CAAA;AAAA,EACvD;AAAA,EAEA,MAAM,MAAA,CAAU,QAAA,EAAkB,QAAA,EAAgC;AAChE,IAAA,OAAO,IAAA,CAAK,QAAW,QAAA,EAAU;AAAA,MAC/B,MAAA,EAAQ,MAAA;AAAA,MACR,IAAA,EAAM,QAAA;AAAA,MACN,OAAA,EAAS,EAAE,cAAA,EAAgB,MAAA;AAAU,KACtC,CAAA;AAAA,EACH;AAAA,EAEA,aAAa,KAAA,EAAe;AAC1B,IAAA,IAAA,CAAK,cAAA,CAAe,eAAe,CAAA,GAAI,CAAA,OAAA,EAAU,KAAK,CAAA,CAAA;AAAA,EACxD;AAAA,EAEA,eAAA,GAAkB;AAChB,IAAA,OAAO,IAAA,CAAK,eAAe,eAAe,CAAA;AAAA,EAC5C;AAAA,EAEA,WAAW,OAAA,EAAiB;AAC1B,IAAA,IAAA,CAAK,OAAA,GAAU,OAAA,CAAQ,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AAAA,EAC1C;AACF;;;AC/MO,IAAM,eAAN,MAAmB;AAAA,EACxB,YAAoB,UAAA,EAAwB;AAAxB,IAAA,IAAA,CAAA,UAAA,GAAA,UAAA;AAAA,EAAyB;AAAA,EAE7C,MAAM,cAAA,CACJ,MAAA,EACA,OAAA,EAC6B;AAC7B,IAAA,OAAO,KAAK,UAAA,CAAW,GAAA;AAAA,MACrB,CAAA,qBAAA,EAAwB,MAAM,CAAA,OAAA,EAAU,OAAO,CAAA;AAAA,KACjD;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,MAAA,EAA+C;AAC9D,IAAA,OAAO,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,CAAA,qBAAA,EAAwB,MAAM,CAAA,MAAA,CAAQ,CAAA;AAAA,EACnE;AACF;;;AC8CO,IAAM,iBAAN,MAAqB;AAAA,EAC1B,YAAoB,UAAA,EAAwB;AAAxB,IAAA,IAAA,CAAA,UAAA,GAAA,UAAA;AAAA,EAAyB;AAAA,EAE7C,MAAM,YAAA,CACJ,MAAA,EACA,MAAA,EACA,gBACA,cAAA,EACkC;AAClC,IAAA,MAAM,MAAA,GAAS,IAAI,eAAA,EAAgB;AACnC,IAAA,IAAI,MAAA,EAAQ,MAAA,CAAO,GAAA,CAAI,QAAA,EAAU,MAAM,CAAA;AACvC,IAAA,IAAI,cAAA,EAAgB,MAAA,CAAO,GAAA,CAAI,eAAA,EAAiB,cAAc,CAAA;AAC9D,IAAA,IAAI,cAAA,EAAgB,MAAA,CAAO,GAAA,CAAI,eAAA,EAAiB,cAAc,CAAA;AAE9D,IAAA,MAAM,WAAA,GAAc,OAAO,QAAA,EAAS;AACpC,IAAA,MAAM,GAAA,GAAM,cACR,CAAA,qBAAA,EAAwB,MAAM,YAAY,WAAW,CAAA,CAAA,GACrD,wBAAwB,MAAM,CAAA,QAAA,CAAA;AAElC,IAAA,MAAM,iBAAA,GAAoB,QAAA;AAAA,MACxB,OAAA,CAAQ,IAAI,wBAAA,IAA4B,IAAA;AAAA,MACxC;AAAA,KACF;AAEA,IAAA,MAAM,IAAA,GAAO,CAAC,CAAA,KAAA,EAAQ,MAAM,IAAI,CAAA,OAAA,EAAU,MAAM,IAAI,QAAQ,CAAA;AAC5D,IAAA,IAAI,cAAA,EAAgB,IAAA,CAAK,IAAA,CAAK,CAAA,cAAA,EAAiB,cAAc,CAAA,CAAE,CAAA;AAC/D,IAAA,IAAI,cAAA,EAAgB,IAAA,CAAK,IAAA,CAAK,CAAA,cAAA,EAAiB,cAAc,CAAA,CAAE,CAAA;AAE/D,IAAA,OAAO,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,GAAA,EAAK,MAAA,EAAW;AAAA,MACzC,IAAA,EAAM;AAAA,QACJ,UAAA,EAAY,iBAAA,KAAsB,CAAA,GAAI,KAAA,GAAQ,iBAAA;AAAA,QAC9C;AAAA;AACF,KACD,CAAA;AAAA,EACH;AAAA,EAEA,MAAM,WAAA,CACJ,MAAA,EACA,IAAA,EACA,QACA,OAAA,EAO8C;AAC9C,IAAA,MAAM,MAAA,GAAS,IAAI,eAAA,CAAgB;AAAA,MACjC,IAAA;AAAA,MACA,MAAA;AAAA,MACA,GAAI,OAAA,EAAS,YAAA,IAAgB,EAAE,eAAe,MAAA,EAAO;AAAA,MACrD,GAAI,OAAA,EAAS,IAAA,IAAQ,EAAE,IAAA,EAAM,QAAQ,IAAA,EAAK;AAAA,MAC1C,GAAI,SAAS,IAAA,IAAQ,EAAE,MAAM,OAAA,CAAQ,IAAA,CAAK,UAAS,EAAE;AAAA,MACrD,GAAI,SAAS,OAAA,IAAW,EAAE,SAAS,OAAA,CAAQ,OAAA,CAAQ,UAAS,EAAE;AAAA,MAC9D,GAAI,OAAA,EAAS,WAAA,IAAe,EAAE,CAAA,EAAG,QAAQ,WAAA;AAAY,KACtD,CAAA;AAED,IAAA,MAAM,iBAAA,GAAoB,QAAA;AAAA,MACxB,OAAA,CAAQ,IAAI,wBAAA,IAA4B,IAAA;AAAA,MACxC;AAAA,KACF;AAEA,IAAA,OAAO,KAAK,UAAA,CAAW,GAAA;AAAA,MACrB,CAAA,qBAAA,EAAwB,MAAM,CAAA,cAAA,EAAiB,MAAM,CAAA,CAAA;AAAA,MACrD,MAAA;AAAA,MACA;AAAA,QACE,IAAA,EAAM;AAAA,UACJ,UAAA,EAAY,iBAAA,KAAsB,CAAA,GAAI,KAAA,GAAQ,iBAAA;AAAA,UAC9C,IAAA,EAAM;AAAA,YACJ,QAAQ,MAAM,CAAA,CAAA;AAAA,YACd,CAAA,KAAA,EAAQ,MAAM,CAAA,CAAA,EAAI,MAAM,IAAI,IAAI,CAAA,CAAA;AAAA,YAChC;AAAA;AACF;AACF;AACF,KACF;AAAA,EACF;AAAA,EAEA,MAAM,YAAA,CACJ,MAAA,EACA,KAAA,EACA,MAAA,EACA,eAAwB,KAAA,EAC2B;AACnD,IAAA,OAAO,KAAK,UAAA,CAAW,IAAA;AAAA,MACrB,wBAAwB,MAAM,CAAA,cAAA,CAAA;AAAA,MAC9B;AAAA,QACE,KAAA;AAAA,QACA,MAAA;AAAA,QACA,aAAA,EAAe;AAAA;AACjB,KACF;AAAA,EACF;AAAA,EAEA,MAAM,aAAA,CACJ,MAAA,EACA,MAAA,EACA,OAAA,EAIwC;AACxC,IAAA,MAAM,MAAA,GAAS,IAAI,eAAA,CAAgB;AAAA,MACjC,MAAA;AAAA,MACA,GAAI,SAAS,KAAA,IAAS,EAAE,OAAO,OAAA,CAAQ,KAAA,CAAM,UAAS,EAAE;AAAA,MACxD,GAAI,SAAS,MAAA,IAAU,EAAE,QAAQ,OAAA,CAAQ,MAAA,CAAO,UAAS;AAAE,KAC5D,CAAA;AAED,IAAA,OAAO,KAAK,UAAA,CAAW,GAAA;AAAA,MACrB,CAAA,qBAAA,EAAwB,MAAM,CAAA,WAAA,EAAc,MAAM,CAAA;AAAA,KACpD;AAAA,EACF;AAAA,EAEA,MAAM,iBAAA,CACJ,MAAA,EACA,IAAA,EACiC;AACjC,IAAA,MAAM,WAAA,GAAc,kBAAA,CAAmB,IAAA,IAAQ,GAAG,CAAA;AAClD,IAAA,OAAO,KAAK,UAAA,CAAW,GAAA;AAAA,MACrB,CAAA,cAAA,EAAiB,MAAM,CAAA,OAAA,EAAU,WAAW,CAAA,SAAA;AAAA,KAC9C;AAAA,EACF;AAAA,EAEA,MAAM,YAAA,CACJ,MAAA,EACA,IAAA,EAC+B;AAC/B,IAAA,MAAM,WAAA,GAAc,kBAAA,CAAmB,IAAA,IAAQ,GAAG,CAAA;AAClD,IAAA,OAAO,KAAK,UAAA,CAAW,GAAA;AAAA,MACrB,CAAA,cAAA,EAAiB,MAAM,CAAA,OAAA,EAAU,WAAW,CAAA,IAAA;AAAA,KAC9C;AAAA,EACF;AAAA,EAEA,MAAM,eAAe,MAAA,EAAiD;AACpE,IAAA,OAAO,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,CAAA,cAAA,EAAiB,MAAM,CAAA,UAAA,CAAY,CAAA;AAAA,EAChE;AAAA,EAEA,MAAM,aAAa,MAAA,EAA8C;AAC/D,IAAA,OAAO,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,CAAA,cAAA,EAAiB,MAAM,CAAA,QAAA,CAAU,CAAA;AAAA,EAC9D;AAAA,EAEA,MAAM,YAAY,MAAA,EAA8C;AAC9D,IAAA,OAAO,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,CAAA,cAAA,EAAiB,MAAM,CAAA,OAAA,CAAS,CAAA;AAAA,EAC7D;AACF;;;ACvMO,IAAM,YAAN,MAAgB;AAAA,EASrB,YAAY,MAAA,EAA6B;AACvC,IAAA,IAAA,CAAK,MAAA,GAAS;AAAA,MACZ,GAAG,gBAAA;AAAA,MACH,GAAG,MAAA;AAAA,MACH,IAAA,EAAM;AAAA,QACJ,GAAG,gBAAA,CAAiB,IAAA;AAAA,QACpB,GAAG,MAAA,EAAQ;AAAA;AACb,KACF;AAEA,IAAA,iBAAA,CAAkB,KAAK,MAAM,CAAA;AAE7B,IAAA,IAAA,CAAK,UAAA,GAAa,IAAI,UAAA,CAAW,IAAA,CAAK,OAAO,IAAI,CAAA;AAEjD,IAAA,IAAI,IAAA,CAAK,OAAO,QAAA,EAAU;AACxB,MAAA,IAAA,CAAK,UAAA,CAAW,YAAA,CAAa,IAAA,CAAK,MAAA,CAAO,QAAQ,CAAA;AAAA,IACnD;AAEA,IAAA,IAAA,CAAK,OAAA,GAAU,IAAI,cAAA,CAAe,IAAA,CAAK,UAAU,CAAA;AACjD,IAAA,IAAA,CAAK,UAAA,GAAa,IAAI,iBAAA,CAAkB,IAAA,CAAK,UAAU,CAAA;AACvD,IAAA,IAAA,CAAK,KAAA,GAAQ,IAAI,YAAA,CAAa,IAAA,CAAK,UAAU,CAAA;AAC7C,IAAA,IAAA,CAAK,KAAA,GAAQ,IAAI,WAAA,CAAY,IAAA,CAAK,UAAU,CAAA;AAAA,EAC9C;AAAA,EAEA,SAAA,GAAoB;AAClB,IAAA,OAAO,KAAK,MAAA,CAAO,MAAA;AAAA,EACrB;AAAA,EAEA,UAAU,MAAA,EAAsB;AAC9B,IAAA,IAAA,CAAK,OAAO,MAAA,GAAS,MAAA;AAAA,EACvB;AAAA,EAEA,aAAa,KAAA,EAAqB;AAChC,IAAA,IAAA,CAAK,UAAA,CAAW,aAAa,KAAK,CAAA;AAAA,EACpC;AAAA,EAEA,eAAA,GAAwB;AACtB,IAAA,IAAA,CAAK,WAAW,eAAA,EAAgB;AAAA,EAClC;AAAA,EAEA,WAAW,OAAA,EAAuB;AAChC,IAAA,IAAA,CAAK,UAAA,CAAW,WAAW,OAAO,CAAA;AAClC,IAAA,IAAA,CAAK,MAAA,CAAO,KAAK,OAAA,GAAU,OAAA;AAAA,EAC7B;AAAA,EAEA,UAAA,GAAqB;AACnB,IAAA,OAAO,KAAK,UAAA,CAAW,OAAA;AAAA,EACzB;AAAA,EAEA,SAAA,GAAuB;AACrB,IAAA,OAAO,EAAE,GAAG,IAAA,CAAK,MAAA,EAAO;AAAA,EAC1B;AACF;AAGA,IAAI,SAAA;AAKG,SAAS,oBAAoB,MAAA,EAAwC;AAC1E,EAAA,SAAA,GAAY,IAAI,UAAU,MAAM,CAAA;AAChC,EAAA,OAAO,SAAA;AACT;AAKO,SAAS,YAAA,GAA0B;AACxC,EAAA,IAAI,CAAC,SAAA,EAAW;AACd,IAAA,IAAI;AACF,MAAA,SAAA,GAAY,IAAI,SAAA,EAAU;AAAA,IAC5B,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,KAAA,CAAM,oCAAoC,KAAK,CAAA;AACvD,MAAA,MAAM,KAAA;AAAA,IACR;AAAA,EACF;AACA,EAAA,OAAO,SAAA;AACT","file":"index.js","sourcesContent":["/**\n * Collection Service\n * Handles collection, entry, and category retrieval\n */\n\nimport type {\n APIResponse,\n Collection,\n Category,\n Entry,\n} from \"@otl-core/cms-types\";\nimport { HttpClient } from \"../http-client\";\n\nexport class CollectionService {\n constructor(private httpClient: HttpClient) {}\n\n async fetchCollections(siteId: string): Promise<APIResponse<Collection[]>> {\n return this.httpClient.get(`/api/v1/public/sites/${siteId}/collections`);\n }\n\n async fetchEntries(\n collectionId: string,\n params?: { page?: number; limit?: number; category?: string },\n ): Promise<APIResponse<Entry[]>> {\n return this.httpClient.get(\n `/api/v1/public/collections/${collectionId}/entries`,\n params,\n );\n }\n\n async fetchEntry(\n collectionId: string,\n entrySlug: string,\n ): Promise<APIResponse<Entry>> {\n return this.httpClient.get(\n `/api/v1/public/collections/${collectionId}/entries/${entrySlug}`,\n );\n }\n\n async fetchCategories(\n collectionId: string,\n ): Promise<APIResponse<Category[]>> {\n return this.httpClient.get(\n `/api/v1/public/collections/${collectionId}/categories`,\n );\n }\n}\n","/**\n * API Configuration\n * Server-side only configuration for CMS backend communication.\n * Uses only process.env.API_URL (never NEXT_PUBLIC_ prefixed variables).\n */\n\nimport { HttpClientConfig } from \"./http-client\";\n\nexport interface APIConfig {\n http: HttpClientConfig;\n siteId: string;\n devToken?: string;\n environment: \"development\" | \"production\" | \"staging\";\n}\n\n/**\n * Get backend API base URL (server-side only, never exposed to client)\n */\nfunction getBackendApiBaseUrl(): string {\n if (process.env.API_URL) {\n return process.env.API_URL;\n }\n\n if (process.env.NODE_ENV === \"development\") {\n return \"http://localhost:8080\";\n }\n\n if (process.env.STAGE === \"staging\") {\n return \"https://api-staging.otl-cms.com\";\n }\n\n return \"https://api.otl-cms.com\";\n}\n\n/**\n * Get site ID from environment (server-side only)\n */\nfunction getBackendSiteId(): string {\n const siteId = process.env.SITE_ID;\n if (!siteId) {\n console.error(\"SITE_ID environment variable is not set!\");\n console.error(\"Add the following to your .env.local:\");\n console.error(\" SITE_ID=your-site-id\");\n console.error(\" API_URL=http://localhost:8080\");\n throw new Error(\"SITE_ID environment variable is required.\");\n }\n return siteId;\n}\n\n/**\n * Get site access token from environment (server-side only)\n */\nfunction getSiteAccessToken(): string {\n const token = process.env.SITE_ACCESS_TOKEN;\n if (!token) {\n console.error(\"SITE_ACCESS_TOKEN environment variable is not set!\");\n console.error(\"Add the following to your .env.local:\");\n console.error(\" SITE_ACCESS_TOKEN=kpt_...\");\n console.error(\"\");\n console.error(\"To obtain an access token:\");\n console.error(\"1. Log into the CMS management interface\");\n console.error(\"2. Navigate to your site settings\");\n console.error(\"3. Go to Access Tokens section\");\n console.error(\"4. Create a new token and copy it here\");\n throw new Error(\n \"SITE_ACCESS_TOKEN environment variable is required. \" +\n \"Please obtain an access token from the CMS site settings.\",\n );\n }\n return token;\n}\n\n/**\n * Get current environment (server-side only)\n */\nfunction getBackendEnvironment(): \"development\" | \"production\" | \"staging\" {\n if (process.env.NODE_ENV === \"production\") {\n return process.env.STAGE === \"staging\" ? \"staging\" : \"production\";\n }\n return \"development\";\n}\n\n/**\n * Default API configuration\n */\nexport const defaultAPIConfig: APIConfig = {\n http: {\n baseUrl: getBackendApiBaseUrl(),\n timeout: 30000,\n defaultHeaders: {\n \"Content-Type\": \"application/json\",\n \"User-Agent\": \"OTL-CMS-Engine/1.0\",\n },\n },\n siteId: getBackendSiteId(),\n devToken: getSiteAccessToken(),\n environment: getBackendEnvironment(),\n};\n\n/**\n * Create API configuration with overrides\n */\nexport function createAPIConfig(overrides?: Partial<APIConfig>): APIConfig {\n return {\n ...defaultAPIConfig,\n ...overrides,\n http: {\n ...defaultAPIConfig.http,\n ...overrides?.http,\n },\n };\n}\n\n/**\n * Validate API configuration\n */\nexport function validateAPIConfig(config: APIConfig): void {\n if (!config.http.baseUrl) {\n throw new Error(\"API base URL is required\");\n }\n\n try {\n new URL(config.http.baseUrl);\n } catch {\n throw new Error(\"Invalid API base URL format\");\n }\n\n if (!config.siteId) {\n throw new Error(\"Site ID is required\");\n }\n\n if (!config.devToken) {\n throw new Error(\n \"Site access token is required. \" +\n \"Please set SITE_ACCESS_TOKEN environment variable.\",\n );\n }\n\n if (config.http.timeout && config.http.timeout < 1000) {\n console.warn(\"API timeout is very low, this might cause issues\");\n }\n}\n","/**\n * Form Service\n * Handles form submissions\n */\n\nimport type { HttpClient } from \"../http-client\";\n\nexport interface FormSubmissionRequest {\n type: string;\n locale: string;\n data: Record<string, unknown>;\n form_variant_id?: string;\n environment_type?: string;\n environment_id?: string;\n environment_path?: string;\n environment_variant_id?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface FormSubmissionResponse {\n success: boolean;\n submission_id: string;\n message?: string;\n}\n\nexport class FormService {\n constructor(private httpClient: HttpClient) {}\n\n async submitForm(\n formId: string,\n data: FormSubmissionRequest,\n ): Promise<FormSubmissionResponse> {\n return this.httpClient.post<FormSubmissionResponse>(\n `/api/v1/public/forms/${formId}/submit`,\n data,\n );\n }\n}\n","export interface HttpClientConfig {\n baseUrl: string;\n timeout?: number;\n defaultHeaders?: Record<string, string>;\n}\n\ninterface FetchOptions extends Omit<RequestInit, \"headers\"> {\n headers?: Record<string, string | undefined>;\n next?: {\n revalidate?: number | false;\n tags?: string[];\n };\n}\n\nexport class HttpClient {\n public baseUrl: string;\n private timeout: number;\n private defaultHeaders: Record<string, string>;\n\n constructor(config: HttpClientConfig) {\n this.baseUrl = config.baseUrl.replace(/\\/$/, \"\");\n this.timeout = config.timeout || 30000;\n this.defaultHeaders = {\n \"Content-Type\": \"application/json\",\n ...config.defaultHeaders,\n };\n }\n\n /**\n * Make a generic HTTP request\n */\n private async request<T>(\n endpoint: string,\n options: FetchOptions = {},\n ): Promise<T> {\n const url = `${this.baseUrl}${endpoint}`;\n\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), this.timeout);\n\n const { next: nextOptions, ...fetchOptions } = options;\n\n const mergedHeaders: Record<string, string | undefined> = {\n ...this.defaultHeaders,\n ...fetchOptions.headers,\n };\n const headers = Object.fromEntries(\n Object.entries(mergedHeaders).filter(\n (entry): entry is [string, string] => entry[1] !== undefined,\n ),\n );\n\n try {\n const response = await fetch(url, {\n ...fetchOptions,\n headers,\n signal: controller.signal,\n ...(process.env.NODE_ENV === \"development\"\n ? { cache: \"no-store\" as const }\n : nextOptions\n ? { next: nextOptions }\n : {}),\n });\n\n clearTimeout(timeoutId);\n\n // Server error (5xx) -- try stale cache before returning the error\n if (\n response.status >= 500 &&\n process.env.NODE_ENV === \"production\" &&\n nextOptions\n ) {\n const staleData = await this.tryStaleCache<T>(\n url,\n fetchOptions,\n headers,\n );\n if (staleData !== null) return staleData;\n }\n\n const data = await response.json();\n\n if (!response.ok) {\n if (data && typeof data === \"object\") {\n if (\"success\" in data) {\n return data;\n }\n if (\"type\" in data) {\n return {\n success: response.status >= 200 && response.status < 300,\n data,\n } as T;\n }\n }\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n return data;\n } catch (error) {\n clearTimeout(timeoutId);\n\n if (error instanceof Error) {\n // Network failure, timeout, or unstructured error -- try stale cache\n if (process.env.NODE_ENV === \"production\" && nextOptions) {\n const staleData = await this.tryStaleCache<T>(\n url,\n fetchOptions,\n headers,\n );\n if (staleData !== null) return staleData;\n }\n\n if (error.name === \"AbortError\") {\n throw new Error(\"Request timeout\");\n }\n throw error;\n }\n\n throw new Error(\"Unknown error occurred\");\n }\n }\n\n /**\n * Attempt to serve a stale cached response when the backend is unreachable\n * or returning server errors. Returns null if no cache entry exists.\n */\n private async tryStaleCache<T>(\n url: string,\n fetchOptions: Omit<RequestInit, \"headers\">,\n headers: Record<string, string>,\n ): Promise<T | null> {\n try {\n console.warn(\n `[HttpClient] Backend unavailable, attempting stale cache: ${url}`,\n );\n const staleResponse = await fetch(url, {\n ...fetchOptions,\n headers,\n cache: \"force-cache\" as const,\n });\n\n if (staleResponse.ok) {\n const staleData = await staleResponse.json();\n console.log(`[HttpClient] Serving stale cache for: ${url}`);\n return staleData;\n }\n } catch (cacheError) {\n console.error(\n `[HttpClient] No cache available for: ${url}`,\n cacheError instanceof Error ? cacheError.message : cacheError,\n );\n }\n return null;\n }\n\n async get<T>(\n endpoint: string,\n params?: Record<string, unknown>,\n options?: FetchOptions,\n ): Promise<T> {\n let url = endpoint;\n\n if (params) {\n const searchParams = new URLSearchParams();\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n searchParams.append(key, String(value));\n }\n });\n\n if (searchParams.toString()) {\n url += `?${searchParams.toString()}`;\n }\n }\n\n return this.request<T>(url, { method: \"GET\", ...options });\n }\n\n async post<T>(endpoint: string, data?: unknown): Promise<T> {\n return this.request<T>(endpoint, {\n method: \"POST\",\n body: data ? JSON.stringify(data) : undefined,\n });\n }\n\n async put<T>(endpoint: string, data?: unknown): Promise<T> {\n return this.request<T>(endpoint, {\n method: \"PUT\",\n body: data ? JSON.stringify(data) : undefined,\n });\n }\n\n async delete<T>(endpoint: string): Promise<T> {\n return this.request<T>(endpoint, { method: \"DELETE\" });\n }\n\n async upload<T>(endpoint: string, formData: FormData): Promise<T> {\n return this.request<T>(endpoint, {\n method: \"POST\",\n body: formData,\n headers: { \"Content-Type\": undefined },\n });\n }\n\n setAuthToken(token: string) {\n this.defaultHeaders[\"Authorization\"] = `Bearer ${token}`;\n }\n\n removeAuthToken() {\n delete this.defaultHeaders[\"Authorization\"];\n }\n\n setBaseUrl(baseUrl: string) {\n this.baseUrl = baseUrl.replace(/\\/$/, \"\");\n }\n}\n","/**\n * Media Service\n * Handles media file retrieval\n */\n\nimport type { APIResponse, Media } from \"@otl-core/cms-types\";\nimport { HttpClient } from \"../http-client\";\n\nexport class MediaService {\n constructor(private httpClient: HttpClient) {}\n\n async fetchMediaFile(\n siteId: string,\n mediaId: string,\n ): Promise<APIResponse<Media>> {\n return this.httpClient.get(\n `/api/v1/public/sites/${siteId}/media/${mediaId}`,\n );\n }\n\n async fetchMedia(siteId: string): Promise<APIResponse<Media[]>> {\n return this.httpClient.get(`/api/v1/public/sites/${siteId}/media`);\n }\n}\n","/**\n * Website Service\n * Handles configuration, path resolution, sitemap, and redirect operations\n */\n\nimport type { APIResponse, SiteConfig } from \"@otl-core/cms-types\";\nimport { HttpClient } from \"../http-client\";\n\nexport interface PathResolutionResponse {\n path: string;\n type:\n | \"page\"\n | \"collection\"\n | \"entry\"\n | \"category\"\n | \"category_index\"\n | \"author\"\n | \"author_index\"\n | \"tag\"\n | \"tag_index\"\n | \"redirect\"\n | \"not_found\"\n | \"multiple\";\n content_id?: string;\n entry_id?: string;\n category_id?: string;\n collection_id?: string;\n locale: string;\n content?: Record<string, unknown>;\n redirect?: {\n to_path?: string;\n to_url?: string;\n status_code: number;\n query_string_behavior: string;\n };\n status_code?: number;\n cache_ttl: number;\n all_matches?: {\n redirect?: unknown;\n page?: unknown;\n category?: unknown;\n entry?: unknown;\n primary_match: string;\n };\n}\n\nexport interface BatchPathResolutionResponse {\n results: PathResolutionResponse[];\n}\n\nexport interface PathInfo {\n path: string;\n content_type: string;\n content_id?: string;\n locale: string;\n}\n\nexport interface AllPathsResponse {\n paths: PathInfo[];\n pagination: {\n page: number;\n limit: number;\n total: number;\n total_pages: number;\n has_next: boolean;\n has_prev: boolean;\n };\n}\n\nexport class WebsiteService {\n constructor(private httpClient: HttpClient) {}\n\n async fetchConfigs(\n siteId: string,\n locale?: string,\n headerPresetId?: string,\n footerPresetId?: string,\n ): Promise<APIResponse<SiteConfig>> {\n const params = new URLSearchParams();\n if (locale) params.set(\"locale\", locale);\n if (headerPresetId) params.set(\"header_preset\", headerPresetId);\n if (footerPresetId) params.set(\"footer_preset\", footerPresetId);\n\n const queryString = params.toString();\n const url = queryString\n ? `/api/v1/public/sites/${siteId}/configs?${queryString}`\n : `/api/v1/public/sites/${siteId}/configs`;\n\n const defaultRevalidate = parseInt(\n process.env.CACHE_REVALIDATE_SECONDS || \"60\",\n 10,\n );\n\n const tags = [`site:${siteId}`, `config:${siteId}`, \"config\"];\n if (headerPresetId) tags.push(`header-preset:${headerPresetId}`);\n if (footerPresetId) tags.push(`footer-preset:${footerPresetId}`);\n\n return this.httpClient.get(url, undefined, {\n next: {\n revalidate: defaultRevalidate === 0 ? false : defaultRevalidate,\n tags,\n },\n });\n }\n\n async resolvePath(\n siteId: string,\n path: string,\n locale: string,\n options?: {\n fetchContent?: boolean;\n mode?: \"default\" | \"all\";\n page?: number;\n perPage?: number;\n searchQuery?: string;\n },\n ): Promise<APIResponse<PathResolutionResponse>> {\n const params = new URLSearchParams({\n path,\n locale,\n ...(options?.fetchContent && { fetch_content: \"true\" }),\n ...(options?.mode && { mode: options.mode }),\n ...(options?.page && { page: options.page.toString() }),\n ...(options?.perPage && { perPage: options.perPage.toString() }),\n ...(options?.searchQuery && { q: options.searchQuery }),\n });\n\n const defaultRevalidate = parseInt(\n process.env.CACHE_REVALIDATE_SECONDS || \"60\",\n 10,\n );\n\n return this.httpClient.get(\n `/api/v1/public/sites/${siteId}/resolve-path?${params}`,\n undefined,\n {\n next: {\n revalidate: defaultRevalidate === 0 ? false : defaultRevalidate,\n tags: [\n `site:${siteId}`,\n `path:${siteId}:${locale}:${path}`,\n \"content\",\n ],\n },\n },\n );\n }\n\n async resolvePaths(\n siteId: string,\n paths: string[],\n locale: string,\n fetchContent: boolean = false,\n ): Promise<APIResponse<BatchPathResolutionResponse>> {\n return this.httpClient.post(\n `/api/v1/public/sites/${siteId}/resolve-paths`,\n {\n paths,\n locale,\n fetch_content: fetchContent,\n },\n );\n }\n\n async fetchAllPaths(\n siteId: string,\n locale: string,\n options?: {\n limit?: number;\n offset?: number;\n },\n ): Promise<APIResponse<AllPathsResponse>> {\n const params = new URLSearchParams({\n locale,\n ...(options?.limit && { limit: options.limit.toString() }),\n ...(options?.offset && { offset: options.offset.toString() }),\n });\n\n return this.httpClient.get(\n `/api/v1/public/sites/${siteId}/all-paths?${params}`,\n );\n }\n\n async fetchPageSections(\n siteId: string,\n path: string,\n ): Promise<APIResponse<unknown[]>> {\n const encodedPath = encodeURIComponent(path || \"/\");\n return this.httpClient.get(\n `/api/v1/sites/${siteId}/pages/${encodedPath}/sections`,\n );\n }\n\n async fetchPageSEO(\n siteId: string,\n path: string,\n ): Promise<APIResponse<unknown>> {\n const encodedPath = encodeURIComponent(path || \"/\");\n return this.httpClient.get(\n `/api/v1/sites/${siteId}/pages/${encodedPath}/seo`,\n );\n }\n\n async fetchRedirects(siteId: string): Promise<APIResponse<unknown[]>> {\n return this.httpClient.get(`/api/v1/sites/${siteId}/redirects`);\n }\n\n async fetchSitemap(siteId: string): Promise<APIResponse<string>> {\n return this.httpClient.get(`/api/v1/sites/${siteId}/sitemap`);\n }\n\n async fetchRobots(siteId: string): Promise<APIResponse<string>> {\n return this.httpClient.get(`/api/v1/sites/${siteId}/robots`);\n }\n}\n","/**\n * API Client\n * Main interface for CMS backend communication - SERVER-SIDE ONLY\n *\n * This module is protected by the `server-only` package.\n * Any attempt to import it from a client component will fail at build time.\n */\n\nimport { CollectionService } from \"./services/collection.service\";\nimport { APIConfig, defaultAPIConfig, validateAPIConfig } from \"./api.config\";\nimport { FormService } from \"./services/form.service\";\nimport { HttpClient } from \"./http-client\";\nimport { MediaService } from \"./services/media.service\";\nimport { WebsiteService } from \"./services/website.service\";\n\nexport class APIClient {\n private httpClient: HttpClient;\n private config: APIConfig;\n\n public readonly website: WebsiteService;\n public readonly collection: CollectionService;\n public readonly media: MediaService;\n public readonly forms: FormService;\n\n constructor(config?: Partial<APIConfig>) {\n this.config = {\n ...defaultAPIConfig,\n ...config,\n http: {\n ...defaultAPIConfig.http,\n ...config?.http,\n },\n };\n\n validateAPIConfig(this.config);\n\n this.httpClient = new HttpClient(this.config.http);\n\n if (this.config.devToken) {\n this.httpClient.setAuthToken(this.config.devToken);\n }\n\n this.website = new WebsiteService(this.httpClient);\n this.collection = new CollectionService(this.httpClient);\n this.media = new MediaService(this.httpClient);\n this.forms = new FormService(this.httpClient);\n }\n\n getSiteId(): string {\n return this.config.siteId;\n }\n\n setSiteId(siteId: string): void {\n this.config.siteId = siteId;\n }\n\n setAuthToken(token: string): void {\n this.httpClient.setAuthToken(token);\n }\n\n removeAuthToken(): void {\n this.httpClient.removeAuthToken();\n }\n\n setBaseUrl(baseUrl: string): void {\n this.httpClient.setBaseUrl(baseUrl);\n this.config.http.baseUrl = baseUrl;\n }\n\n getBaseUrl(): string {\n return this.httpClient.baseUrl;\n }\n\n getConfig(): APIConfig {\n return { ...this.config };\n }\n}\n\n// Singleton instance\nlet apiClient: APIClient;\n\n/**\n * Initialize the API client\n */\nexport function initializeAPIClient(config?: Partial<APIConfig>): APIClient {\n apiClient = new APIClient(config);\n return apiClient;\n}\n\n/**\n * Get the initialized API client instance\n */\nexport function getAPIClient(): APIClient {\n if (!apiClient) {\n try {\n apiClient = new APIClient();\n } catch (error) {\n console.error(\"Failed to initialize API client:\", error);\n throw error;\n }\n }\n return apiClient;\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@otl-core/cms-api",
3
+ "version": "1.1.0",
4
+ "type": "module",
5
+ "description": "Server-side API client for OTL CMS backend communication",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "clean": "rm -rf dist",
22
+ "rebuild": "npm run clean && npm run build",
23
+ "test": "vitest run"
24
+ },
25
+ "keywords": [
26
+ "api",
27
+ "client",
28
+ "typescript",
29
+ "cms",
30
+ "server"
31
+ ],
32
+ "author": "OTL Core",
33
+ "license": "PolyForm-Shield-1.0.0",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/otl-core/cms-api.git"
37
+ },
38
+ "dependencies": {
39
+ "@otl-core/cms-types": "^1.1.0",
40
+ "server-only": "^0.0.1"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^22.0.0",
44
+ "tsup": "^8.0.0",
45
+ "typescript": "^5.0.0",
46
+ "vitest": "^4.0.0"
47
+ }
48
+ }