@pagebridge/core 0.0.1

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.
@@ -0,0 +1,333 @@
1
+ import { searchAnalytics, queryAnalytics, syncLog, pageIndexStatus, } from "@pagebridge/db";
2
+ import { and, eq, gte, lte } from "drizzle-orm";
3
+ export class SyncEngine {
4
+ gsc;
5
+ db;
6
+ sanity;
7
+ constructor(options) {
8
+ this.gsc = options.gsc;
9
+ this.db = options.db;
10
+ this.sanity = options.sanity;
11
+ }
12
+ async sync(options) {
13
+ const { siteUrl, startDate = daysAgo(90), endDate = daysAgo(3), dimensions = ["page", "date"], } = options;
14
+ const syncLogId = `${siteUrl}:${Date.now()}`;
15
+ await this.db.insert(syncLog).values({
16
+ id: syncLogId,
17
+ siteId: siteUrl,
18
+ startedAt: new Date(),
19
+ status: "running",
20
+ });
21
+ try {
22
+ const rows = await this.gsc.fetchSearchAnalytics({
23
+ siteUrl,
24
+ startDate,
25
+ endDate,
26
+ dimensions,
27
+ });
28
+ const pages = new Set();
29
+ for (const row of rows) {
30
+ pages.add(row.page);
31
+ if (row.date) {
32
+ const id = `${siteUrl}:${row.page}:${row.date}`;
33
+ await this.db
34
+ .insert(searchAnalytics)
35
+ .values({
36
+ id,
37
+ siteId: siteUrl,
38
+ page: row.page,
39
+ date: row.date,
40
+ clicks: row.clicks,
41
+ impressions: row.impressions,
42
+ ctr: row.ctr,
43
+ position: row.position,
44
+ })
45
+ .onConflictDoUpdate({
46
+ target: searchAnalytics.id,
47
+ set: {
48
+ clicks: row.clicks,
49
+ impressions: row.impressions,
50
+ ctr: row.ctr,
51
+ position: row.position,
52
+ fetchedAt: new Date(),
53
+ },
54
+ });
55
+ }
56
+ if (row.query && row.date) {
57
+ const id = `${siteUrl}:${row.page}:${row.query}:${row.date}`;
58
+ await this.db
59
+ .insert(queryAnalytics)
60
+ .values({
61
+ id,
62
+ siteId: siteUrl,
63
+ page: row.page,
64
+ query: row.query,
65
+ date: row.date,
66
+ clicks: row.clicks,
67
+ impressions: row.impressions,
68
+ ctr: row.ctr,
69
+ position: row.position,
70
+ })
71
+ .onConflictDoUpdate({
72
+ target: queryAnalytics.id,
73
+ set: {
74
+ clicks: row.clicks,
75
+ impressions: row.impressions,
76
+ ctr: row.ctr,
77
+ position: row.position,
78
+ },
79
+ });
80
+ }
81
+ }
82
+ await this.db
83
+ .update(syncLog)
84
+ .set({
85
+ status: "completed",
86
+ completedAt: new Date(),
87
+ rowsProcessed: rows.length,
88
+ })
89
+ .where(eq(syncLog.id, syncLogId));
90
+ return {
91
+ pages: Array.from(pages),
92
+ rowsProcessed: rows.length,
93
+ syncLogId,
94
+ };
95
+ }
96
+ catch (error) {
97
+ await this.db
98
+ .update(syncLog)
99
+ .set({
100
+ status: "failed",
101
+ completedAt: new Date(),
102
+ error: error instanceof Error ? error.message : String(error),
103
+ })
104
+ .where(eq(syncLog.id, syncLogId));
105
+ throw error;
106
+ }
107
+ }
108
+ async writeSnapshots(siteId, matches, siteUrl) {
109
+ // Get the siteUrl from Sanity if not provided
110
+ let resolvedSiteUrl = siteUrl;
111
+ if (!resolvedSiteUrl) {
112
+ const siteDoc = await this.sanity.fetch(`*[_type == "gscSite" && _id == $siteId][0]{ siteUrl }`, { siteId });
113
+ resolvedSiteUrl = siteDoc?.siteUrl;
114
+ }
115
+ if (!resolvedSiteUrl) {
116
+ throw new Error(`Could not find siteUrl for site ID: ${siteId}`);
117
+ }
118
+ const periods = ["last7", "last28", "last90"];
119
+ const periodDays = { last7: 7, last28: 28, last90: 90 };
120
+ for (const period of periods) {
121
+ const startDate = daysAgo(periodDays[period]);
122
+ const endDate = daysAgo(3);
123
+ for (const match of matches) {
124
+ if (!match.sanityId)
125
+ continue;
126
+ const metrics = await this.getAggregatedMetrics(resolvedSiteUrl, match.gscUrl, startDate, endDate);
127
+ if (!metrics)
128
+ continue;
129
+ const topQueries = await this.getTopQueries(resolvedSiteUrl, match.gscUrl, startDate, endDate);
130
+ // Get index status from database
131
+ const indexStatusData = await this.getIndexStatus(resolvedSiteUrl, match.gscUrl);
132
+ const existingSnapshot = await this.sanity.fetch(`*[_type == "gscSnapshot" && site._ref == $siteId && page == $page && period == $period][0]._id`, { siteId, page: match.gscUrl, period });
133
+ const snapshotData = {
134
+ _type: "gscSnapshot",
135
+ site: { _type: "reference", _ref: siteId },
136
+ page: match.gscUrl,
137
+ linkedDocument: { _type: "reference", _ref: match.sanityId },
138
+ period,
139
+ clicks: metrics.clicks,
140
+ impressions: metrics.impressions,
141
+ ctr: metrics.ctr,
142
+ position: metrics.position,
143
+ topQueries,
144
+ fetchedAt: new Date().toISOString(),
145
+ indexStatus: indexStatusData
146
+ ? {
147
+ verdict: mapVerdictToSanity(indexStatusData.verdict),
148
+ coverageState: indexStatusData.coverageState,
149
+ lastCrawlTime: indexStatusData.lastCrawlTime?.toISOString() ?? null,
150
+ robotsTxtState: indexStatusData.robotsTxtState,
151
+ pageFetchState: indexStatusData.pageFetchState,
152
+ }
153
+ : undefined,
154
+ };
155
+ if (existingSnapshot) {
156
+ await this.sanity.patch(existingSnapshot).set(snapshotData).commit();
157
+ }
158
+ else {
159
+ await this.sanity.create(snapshotData);
160
+ }
161
+ }
162
+ }
163
+ }
164
+ async syncIndexStatus(siteUrl, pages) {
165
+ const result = {
166
+ checked: 0,
167
+ indexed: 0,
168
+ notIndexed: 0,
169
+ skipped: 0,
170
+ };
171
+ const CACHE_DURATION_MS = 24 * 60 * 60 * 1000; // 24 hours
172
+ for (const page of pages) {
173
+ const id = `${siteUrl}:${page}`;
174
+ // Check if we already have a recent status
175
+ const existing = await this.db
176
+ .select({ fetchedAt: pageIndexStatus.fetchedAt })
177
+ .from(pageIndexStatus)
178
+ .where(eq(pageIndexStatus.id, id))
179
+ .limit(1);
180
+ if (existing.length > 0 &&
181
+ existing[0]?.fetchedAt &&
182
+ Date.now() - existing[0].fetchedAt.getTime() < CACHE_DURATION_MS) {
183
+ result.skipped++;
184
+ continue;
185
+ }
186
+ try {
187
+ const status = await this.gsc.inspectUrl(siteUrl, page);
188
+ await this.db
189
+ .insert(pageIndexStatus)
190
+ .values({
191
+ id,
192
+ siteId: siteUrl,
193
+ page,
194
+ verdict: status.verdict,
195
+ coverageState: status.coverageState,
196
+ indexingState: status.indexingState,
197
+ pageFetchState: status.pageFetchState,
198
+ lastCrawlTime: status.lastCrawlTime,
199
+ robotsTxtState: status.robotsTxtState,
200
+ fetchedAt: new Date(),
201
+ })
202
+ .onConflictDoUpdate({
203
+ target: pageIndexStatus.id,
204
+ set: {
205
+ verdict: status.verdict,
206
+ coverageState: status.coverageState,
207
+ indexingState: status.indexingState,
208
+ pageFetchState: status.pageFetchState,
209
+ lastCrawlTime: status.lastCrawlTime,
210
+ robotsTxtState: status.robotsTxtState,
211
+ fetchedAt: new Date(),
212
+ },
213
+ });
214
+ result.checked++;
215
+ if (status.verdict === "PASS") {
216
+ result.indexed++;
217
+ }
218
+ else {
219
+ result.notIndexed++;
220
+ }
221
+ // Small delay to respect rate limits (600/min = 100ms between requests)
222
+ await delay(100);
223
+ }
224
+ catch (error) {
225
+ console.error(`Failed to check index status for ${page}:`, error);
226
+ result.skipped++;
227
+ }
228
+ }
229
+ return result;
230
+ }
231
+ async getIndexStatus(siteUrl, page) {
232
+ const id = `${siteUrl}:${page}`;
233
+ const rows = await this.db
234
+ .select()
235
+ .from(pageIndexStatus)
236
+ .where(eq(pageIndexStatus.id, id))
237
+ .limit(1);
238
+ if (rows.length === 0)
239
+ return null;
240
+ const row = rows[0];
241
+ return {
242
+ verdict: row.verdict,
243
+ coverageState: row.coverageState,
244
+ indexingState: row.indexingState,
245
+ pageFetchState: row.pageFetchState,
246
+ lastCrawlTime: row.lastCrawlTime,
247
+ robotsTxtState: row.robotsTxtState,
248
+ };
249
+ }
250
+ async getAggregatedMetrics(siteId, page, startDate, endDate) {
251
+ const results = await this.db
252
+ .select({
253
+ totalClicks: searchAnalytics.clicks,
254
+ totalImpressions: searchAnalytics.impressions,
255
+ avgCtr: searchAnalytics.ctr,
256
+ avgPosition: searchAnalytics.position,
257
+ })
258
+ .from(searchAnalytics)
259
+ .where(and(eq(searchAnalytics.siteId, siteId), eq(searchAnalytics.page, page), gte(searchAnalytics.date, formatDate(startDate)), lte(searchAnalytics.date, formatDate(endDate))));
260
+ if (results.length === 0)
261
+ return undefined;
262
+ const totalClicks = results.reduce((sum, r) => sum + (r.totalClicks ?? 0), 0);
263
+ const totalImpressions = results.reduce((sum, r) => sum + (r.totalImpressions ?? 0), 0);
264
+ const avgCtr = totalImpressions > 0 ? totalClicks / totalImpressions : 0;
265
+ const avgPosition = results.reduce((sum, r) => sum + (r.avgPosition ?? 0), 0) /
266
+ results.length;
267
+ return {
268
+ clicks: totalClicks,
269
+ impressions: totalImpressions,
270
+ ctr: avgCtr,
271
+ position: avgPosition,
272
+ };
273
+ }
274
+ async getTopQueries(siteId, page, startDate, endDate) {
275
+ const results = await this.db
276
+ .select({
277
+ query: queryAnalytics.query,
278
+ clicks: queryAnalytics.clicks,
279
+ impressions: queryAnalytics.impressions,
280
+ position: queryAnalytics.position,
281
+ })
282
+ .from(queryAnalytics)
283
+ .where(and(eq(queryAnalytics.siteId, siteId), eq(queryAnalytics.page, page), gte(queryAnalytics.date, formatDate(startDate)), lte(queryAnalytics.date, formatDate(endDate))))
284
+ .limit(10);
285
+ const queryMap = new Map();
286
+ for (const row of results) {
287
+ const existing = queryMap.get(row.query);
288
+ if (existing) {
289
+ existing.clicks += row.clicks ?? 0;
290
+ existing.impressions += row.impressions ?? 0;
291
+ existing.positions.push(row.position ?? 0);
292
+ }
293
+ else {
294
+ queryMap.set(row.query, {
295
+ clicks: row.clicks ?? 0,
296
+ impressions: row.impressions ?? 0,
297
+ positions: [row.position ?? 0],
298
+ });
299
+ }
300
+ }
301
+ return Array.from(queryMap.entries())
302
+ .map(([query, data]) => ({
303
+ query,
304
+ clicks: data.clicks,
305
+ impressions: data.impressions,
306
+ position: data.positions.reduce((a, b) => a + b, 0) / data.positions.length,
307
+ }))
308
+ .sort((a, b) => b.clicks - a.clicks)
309
+ .slice(0, 10);
310
+ }
311
+ }
312
+ function daysAgo(days) {
313
+ const date = new Date();
314
+ date.setDate(date.getDate() - days);
315
+ return date;
316
+ }
317
+ function formatDate(date) {
318
+ return date.toISOString().split("T")[0];
319
+ }
320
+ function delay(ms) {
321
+ return new Promise((resolve) => setTimeout(resolve, ms));
322
+ }
323
+ function mapVerdictToSanity(verdict) {
324
+ switch (verdict) {
325
+ case "PASS":
326
+ return "indexed";
327
+ case "NEUTRAL":
328
+ return "excluded";
329
+ case "FAIL":
330
+ default:
331
+ return "not_indexed";
332
+ }
333
+ }
@@ -0,0 +1,26 @@
1
+ import type { SanityClient } from "@sanity/client";
2
+ import type { DrizzleClient } from "@pagebridge/db";
3
+ import type { DecaySignal } from "./decay-detector.js";
4
+ import type { MatchResult } from "./url-matcher.js";
5
+ export interface QueryContext {
6
+ query: string;
7
+ impressions: number;
8
+ clicks: number;
9
+ position: number;
10
+ }
11
+ export interface TaskGeneratorOptions {
12
+ sanity: SanityClient;
13
+ db?: DrizzleClient;
14
+ }
15
+ export declare class TaskGenerator {
16
+ private sanity;
17
+ private db?;
18
+ constructor(options: TaskGeneratorOptions | SanityClient);
19
+ createTasks(siteId: string, signals: DecaySignal[], matches: MatchResult[], siteUrl?: string): Promise<number>;
20
+ private getTopQueries;
21
+ updateTaskStatus(taskId: string, status: "open" | "snoozed" | "in_progress" | "done" | "dismissed", options?: {
22
+ snoozeDays?: number;
23
+ notes?: string;
24
+ }): Promise<void>;
25
+ }
26
+ //# sourceMappingURL=task-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-generator.d.ts","sourceRoot":"","sources":["../src/task-generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAGpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,YAAY,CAAC;IACrB,EAAE,CAAC,EAAE,aAAa,CAAC;CACpB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,EAAE,CAAC,CAAgB;gBAEf,OAAO,EAAE,oBAAoB,GAAG,YAAY;IAUlD,WAAW,CACf,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,WAAW,EAAE,EACtB,OAAO,EAAE,WAAW,EAAE,EACtB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC;YAuDJ,aAAa;IAuCrB,gBAAgB,CACpB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,GAAG,MAAM,GAAG,WAAW,EACjE,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAChD,OAAO,CAAC,IAAI,CAAC;CAmBjB"}
@@ -0,0 +1,101 @@
1
+ import { queryAnalytics } from "@pagebridge/db";
2
+ import { and, eq, gte, lte, sql, desc } from "drizzle-orm";
3
+ export class TaskGenerator {
4
+ sanity;
5
+ db;
6
+ constructor(options) {
7
+ // Support both old (SanityClient) and new (options object) signatures
8
+ if ("fetch" in options) {
9
+ this.sanity = options;
10
+ }
11
+ else {
12
+ this.sanity = options.sanity;
13
+ this.db = options.db;
14
+ }
15
+ }
16
+ async createTasks(siteId, signals, matches, siteUrl) {
17
+ let created = 0;
18
+ // Get siteUrl from Sanity if not provided (needed for query lookup)
19
+ let resolvedSiteUrl = siteUrl;
20
+ if (!resolvedSiteUrl && this.db) {
21
+ const siteDoc = await this.sanity.fetch(`*[_type == "gscSite" && _id == $siteId][0]{ siteUrl }`, { siteId });
22
+ resolvedSiteUrl = siteDoc?.siteUrl;
23
+ }
24
+ for (const signal of signals) {
25
+ const match = matches.find((m) => m.gscUrl === signal.page);
26
+ if (!match?.sanityId)
27
+ continue;
28
+ const existingTask = await this.sanity.fetch(`*[_type == "gscRefreshTask" && linkedDocument._ref == $docId && status in ["open", "in_progress"]][0]._id`, { docId: match.sanityId });
29
+ if (existingTask)
30
+ continue;
31
+ // Fetch top queries for this page if database is available
32
+ let queryContext;
33
+ if (this.db && resolvedSiteUrl) {
34
+ queryContext = await this.getTopQueries(resolvedSiteUrl, signal.page);
35
+ }
36
+ await this.sanity.create({
37
+ _type: "gscRefreshTask",
38
+ site: { _type: "reference", _ref: siteId },
39
+ linkedDocument: { _type: "reference", _ref: match.sanityId },
40
+ reason: signal.reason,
41
+ severity: signal.severity,
42
+ status: "open",
43
+ metrics: {
44
+ positionBefore: signal.metrics.positionBefore,
45
+ positionNow: signal.metrics.positionNow,
46
+ positionDelta: signal.metrics.positionDelta,
47
+ ctrBefore: signal.metrics.ctrBefore,
48
+ ctrNow: signal.metrics.ctrNow,
49
+ impressions: signal.metrics.impressions,
50
+ },
51
+ ...(queryContext && queryContext.length > 0 && { queryContext }),
52
+ createdAt: new Date().toISOString(),
53
+ });
54
+ created++;
55
+ }
56
+ return created;
57
+ }
58
+ async getTopQueries(siteId, page, limit = 5) {
59
+ if (!this.db)
60
+ return [];
61
+ const endDate = new Date();
62
+ const startDate = new Date();
63
+ startDate.setDate(startDate.getDate() - 28);
64
+ const results = await this.db
65
+ .select({
66
+ query: queryAnalytics.query,
67
+ totalClicks: sql `sum(${queryAnalytics.clicks})`,
68
+ totalImpressions: sql `sum(${queryAnalytics.impressions})`,
69
+ avgPosition: sql `avg(${queryAnalytics.position})`,
70
+ })
71
+ .from(queryAnalytics)
72
+ .where(and(eq(queryAnalytics.siteId, siteId), eq(queryAnalytics.page, page), gte(queryAnalytics.date, formatDate(startDate)), lte(queryAnalytics.date, formatDate(endDate))))
73
+ .groupBy(queryAnalytics.query)
74
+ .orderBy(desc(sql `sum(${queryAnalytics.impressions})`))
75
+ .limit(limit);
76
+ return results.map((r) => ({
77
+ query: r.query,
78
+ clicks: Number(r.totalClicks) || 0,
79
+ impressions: Number(r.totalImpressions) || 0,
80
+ position: Number(r.avgPosition) || 0,
81
+ }));
82
+ }
83
+ async updateTaskStatus(taskId, status, options) {
84
+ const patch = { status };
85
+ if (status === "snoozed" && options?.snoozeDays) {
86
+ const until = new Date();
87
+ until.setDate(until.getDate() + options.snoozeDays);
88
+ patch.snoozedUntil = until.toISOString();
89
+ }
90
+ if (status === "done" || status === "dismissed") {
91
+ patch.resolvedAt = new Date().toISOString();
92
+ }
93
+ if (options?.notes) {
94
+ patch.notes = options.notes;
95
+ }
96
+ await this.sanity.patch(taskId).set(patch).commit();
97
+ }
98
+ }
99
+ function formatDate(date) {
100
+ return date.toISOString().split("T")[0];
101
+ }
@@ -0,0 +1,49 @@
1
+ import type { SanityClient } from "@sanity/client";
2
+ export type UnmatchReason = "matched" | "no_slug_extracted" | "no_matching_document" | "outside_path_prefix";
3
+ export interface MatchDiagnostics {
4
+ normalizedUrl: string;
5
+ pathAfterPrefix: string | null;
6
+ configuredPrefix: string | null;
7
+ availableSlugsCount: number;
8
+ similarSlugs: string[];
9
+ }
10
+ export interface MatchResult {
11
+ gscUrl: string;
12
+ sanityId: string | undefined;
13
+ confidence: "exact" | "normalized" | "fuzzy" | "none";
14
+ matchedSlug?: string;
15
+ unmatchReason: UnmatchReason;
16
+ extractedSlug?: string;
17
+ diagnostics?: MatchDiagnostics;
18
+ }
19
+ export interface URLMatcherConfig {
20
+ contentTypes: string[];
21
+ slugField: string;
22
+ pathPrefix?: string;
23
+ baseUrl: string;
24
+ }
25
+ export declare class URLMatcher {
26
+ private sanityClient;
27
+ private config;
28
+ constructor(sanityClient: SanityClient, config: URLMatcherConfig);
29
+ matchUrls(gscUrls: string[]): Promise<MatchResult[]>;
30
+ /**
31
+ * Get all available slugs from Sanity for diagnostic purposes
32
+ */
33
+ getAvailableSlugs(): Promise<string[]>;
34
+ private matchSingleUrl;
35
+ private normalizeUrl;
36
+ private extractSlug;
37
+ private extractSlugWithDiagnostics;
38
+ private escapeRegex;
39
+ private normalizeSlug;
40
+ /**
41
+ * Find similar slugs using Levenshtein distance
42
+ */
43
+ private findSimilarSlugs;
44
+ /**
45
+ * Calculate Levenshtein distance between two strings
46
+ */
47
+ private levenshteinDistance;
48
+ }
49
+ //# sourceMappingURL=url-matcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"url-matcher.d.ts","sourceRoot":"","sources":["../src/url-matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,MAAM,MAAM,aAAa,GACrB,SAAS,GACT,mBAAmB,GACnB,sBAAsB,GACtB,qBAAqB,CAAC;AAE1B,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,UAAU,EAAE,OAAO,GAAG,YAAY,GAAG,OAAO,GAAG,MAAM,CAAC;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,aAAa,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,gBAAgB,CAAC;CAChC;AAED,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAQD,qBAAa,UAAU;IAEnB,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,MAAM;gBADN,YAAY,EAAE,YAAY,EAC1B,MAAM,EAAE,gBAAgB;IAG5B,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAyB1D;;OAEG;IACG,iBAAiB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAc5C,OAAO,CAAC,cAAc;IAoGtB,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,0BAA0B;IA0ClC,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,aAAa;IAIrB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAiBxB;;OAEG;IACH,OAAO,CAAC,mBAAmB;CA8B5B"}