@bobbykim/manguito-cms-api 0.2.0 → 0.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/dist/chunk-FBS2RQI3.mjs +232 -0
- package/dist/chunk-FBS2RQI3.mjs.map +1 -0
- package/dist/chunk-QXMW74MR.mjs +589 -0
- package/dist/chunk-QXMW74MR.mjs.map +1 -0
- package/dist/graphql/index.d.mts +15 -0
- package/dist/graphql/index.d.ts +15 -0
- package/dist/graphql/index.js +729 -0
- package/dist/graphql/index.js.map +1 -0
- package/dist/graphql/index.mjs +8 -0
- package/dist/graphql/index.mjs.map +1 -0
- package/dist/handler-GMCFYZY5.mjs +8 -0
- package/dist/handler-GMCFYZY5.mjs.map +1 -0
- package/dist/index.d.mts +7 -14
- package/dist/index.d.ts +7 -14
- package/dist/index.js +959 -253
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +184 -330
- package/dist/index.mjs.map +1 -1
- package/dist/resolve-Dtsk27aI.d.mts +15 -0
- package/dist/resolve-Dtsk27aI.d.ts +15 -0
- package/package.json +12 -3
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// src/relations.ts
|
|
2
|
+
import crypto from "crypto";
|
|
3
|
+
import { sql } from "drizzle-orm";
|
|
4
|
+
function quoteIdent(name) {
|
|
5
|
+
if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`);
|
|
6
|
+
return `"${name}"`;
|
|
7
|
+
}
|
|
8
|
+
function paragraphMediaFieldNames(pType) {
|
|
9
|
+
return new Set(
|
|
10
|
+
pType.fields.filter((f) => f.field_type === "image" || f.field_type === "video" || f.field_type === "file").map((f) => f.name)
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
async function fetchParagraphMediaIds(db, pType, itemId, fieldName) {
|
|
14
|
+
const mediaColumns = pType.fields.filter((f) => f.field_type === "image" || f.field_type === "video" || f.field_type === "file").map((f) => f.db_column.column_name);
|
|
15
|
+
if (mediaColumns.length === 0) return [];
|
|
16
|
+
const tbl = sql.raw(quoteIdent(pType.db.table_name));
|
|
17
|
+
const cols = sql.join(mediaColumns.map((c) => sql.raw(quoteIdent(c))), sql`, `);
|
|
18
|
+
const result = await db.execute(
|
|
19
|
+
sql`SELECT ${cols} FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`
|
|
20
|
+
);
|
|
21
|
+
const ids = [];
|
|
22
|
+
for (const row of result.rows) {
|
|
23
|
+
for (const col of mediaColumns) {
|
|
24
|
+
const v = row[col];
|
|
25
|
+
if (typeof v === "string" && v !== "") ids.push(v);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return ids;
|
|
29
|
+
}
|
|
30
|
+
async function persistParagraphField(db, itemId, parentType, fieldName, pType, items) {
|
|
31
|
+
const removed = await fetchParagraphMediaIds(db, pType, itemId, fieldName);
|
|
32
|
+
const tbl = sql.raw(quoteIdent(pType.db.table_name));
|
|
33
|
+
await db.execute(sql`DELETE FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`);
|
|
34
|
+
const mediaFieldNames = paragraphMediaFieldNames(pType);
|
|
35
|
+
const added = [];
|
|
36
|
+
for (let i = 0; i < items.length; i++) {
|
|
37
|
+
const pItem = items[i];
|
|
38
|
+
const data = {
|
|
39
|
+
id: crypto.randomUUID(),
|
|
40
|
+
parent_id: itemId,
|
|
41
|
+
parent_type: parentType,
|
|
42
|
+
parent_field: fieldName,
|
|
43
|
+
order: i,
|
|
44
|
+
created_at: /* @__PURE__ */ new Date(),
|
|
45
|
+
updated_at: /* @__PURE__ */ new Date()
|
|
46
|
+
};
|
|
47
|
+
for (const pf of pType.fields) {
|
|
48
|
+
if (!pf.db_column || pf.db_column.junction) continue;
|
|
49
|
+
data[pf.db_column.column_name] = pItem[pf.name] ?? null;
|
|
50
|
+
}
|
|
51
|
+
const cols = sql.join(Object.keys(data).map((k) => sql.raw(quoteIdent(k))), sql`, `);
|
|
52
|
+
const vals = sql.join(Object.values(data).map((v) => sql`${v}`), sql`, `);
|
|
53
|
+
await db.execute(sql`INSERT INTO ${tbl} (${cols}) VALUES (${vals})`);
|
|
54
|
+
for (const name of mediaFieldNames) {
|
|
55
|
+
const val = pItem[name];
|
|
56
|
+
if (typeof val === "string" && val !== "") added.push(val);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { added, removed };
|
|
60
|
+
}
|
|
61
|
+
async function deleteParagraphField(db, itemId, fieldName, pType) {
|
|
62
|
+
const removed = await fetchParagraphMediaIds(db, pType, itemId, fieldName);
|
|
63
|
+
const tbl = sql.raw(quoteIdent(pType.db.table_name));
|
|
64
|
+
await db.execute(sql`DELETE FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`);
|
|
65
|
+
return { added: [], removed };
|
|
66
|
+
}
|
|
67
|
+
async function persistJunctionField(db, itemId, junction, relatedIds) {
|
|
68
|
+
const tbl = sql.raw(quoteIdent(junction.table_name));
|
|
69
|
+
const left = sql.raw(quoteIdent(junction.left_column));
|
|
70
|
+
const right = sql.raw(quoteIdent(junction.right_column));
|
|
71
|
+
await db.execute(sql`DELETE FROM ${tbl} WHERE ${left} = ${itemId}`);
|
|
72
|
+
for (const rid of relatedIds) {
|
|
73
|
+
await db.execute(sql`INSERT INTO ${tbl} (${left}, ${right}) VALUES (${itemId}, ${rid})`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function buildRelationsMap(fields, registry) {
|
|
77
|
+
const relations = {};
|
|
78
|
+
for (const field of fields) {
|
|
79
|
+
if (field.field_type === "image" || field.field_type === "video" || field.field_type === "file") {
|
|
80
|
+
if (!field.db_column) continue;
|
|
81
|
+
relations[field.name] = { type: "media", fk_column: field.db_column.column_name };
|
|
82
|
+
} else if (field.field_type === "paragraph") {
|
|
83
|
+
const ref = field.ui_component.ref;
|
|
84
|
+
const pType = ref ? registry.paragraph_types[ref] : void 0;
|
|
85
|
+
if (!pType) continue;
|
|
86
|
+
relations[field.name] = { type: "paragraph", table: pType.db.table_name };
|
|
87
|
+
} else if (field.field_type === "reference") {
|
|
88
|
+
if (!field.db_column) continue;
|
|
89
|
+
if (field.db_column.junction) {
|
|
90
|
+
const j = field.db_column.junction;
|
|
91
|
+
relations[field.name] = {
|
|
92
|
+
type: "junction",
|
|
93
|
+
table: j.right_table,
|
|
94
|
+
junction_table: j.table_name,
|
|
95
|
+
left_column: j.left_column,
|
|
96
|
+
right_column: j.right_column,
|
|
97
|
+
order_column: j.order_column
|
|
98
|
+
};
|
|
99
|
+
} else if (field.db_column.foreign_key) {
|
|
100
|
+
relations[field.name] = {
|
|
101
|
+
type: "reference",
|
|
102
|
+
table: field.db_column.foreign_key.table,
|
|
103
|
+
fk_column: field.db_column.column_name
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return relations;
|
|
109
|
+
}
|
|
110
|
+
function groupBy(items, key) {
|
|
111
|
+
const result = {};
|
|
112
|
+
for (const item of items) {
|
|
113
|
+
const k = String(item[key]);
|
|
114
|
+
if (!result[k]) result[k] = [];
|
|
115
|
+
result[k].push(item);
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
async function resolveRelationField(db, rows, fieldName, rel, cache, publishedOnly = false) {
|
|
120
|
+
if (rows.length === 0) return;
|
|
121
|
+
if (rel.type === "paragraph") {
|
|
122
|
+
const parentIds = rows.map((r) => r["id"]);
|
|
123
|
+
const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `);
|
|
124
|
+
const result = await db.execute(
|
|
125
|
+
sql`SELECT * FROM ${sql.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) ORDER BY "order" ASC`
|
|
126
|
+
);
|
|
127
|
+
const byParent = groupBy(result.rows, "parent_id");
|
|
128
|
+
for (const row of rows) {
|
|
129
|
+
row[fieldName] = byParent[row["id"]] ?? [];
|
|
130
|
+
}
|
|
131
|
+
} else if (rel.type === "reference") {
|
|
132
|
+
const fkValues = rows.map((r) => r[rel.fk_column]).filter(Boolean);
|
|
133
|
+
if (fkValues.length === 0) {
|
|
134
|
+
for (const row of rows) row[fieldName] = null;
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const unique = [...new Set(fkValues)];
|
|
138
|
+
const uncached = unique.filter((id) => !cache.has(`${rel.table}:${id}`));
|
|
139
|
+
if (uncached.length > 0) {
|
|
140
|
+
const inList = sql.join(uncached.map((id) => sql`${id}`), sql`, `);
|
|
141
|
+
const publishedCond = publishedOnly ? sql` AND published = true` : sql``;
|
|
142
|
+
const result = await db.execute(
|
|
143
|
+
sql`SELECT * FROM ${sql.raw(quoteIdent(rel.table))} WHERE id IN (${inList})${publishedCond}`
|
|
144
|
+
);
|
|
145
|
+
for (const item of result.rows) {
|
|
146
|
+
cache.set(`${rel.table}:${item["id"]}`, item);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
for (const row of rows) {
|
|
150
|
+
const fkVal = row[rel.fk_column];
|
|
151
|
+
row[fieldName] = fkVal ? cache.get(`${rel.table}:${fkVal}`) ?? null : null;
|
|
152
|
+
}
|
|
153
|
+
} else if (rel.type === "junction") {
|
|
154
|
+
const parentIds = rows.map((r) => r["id"]);
|
|
155
|
+
const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `);
|
|
156
|
+
const orderBy = rel.order_column ? sql` ORDER BY "order" ASC` : sql``;
|
|
157
|
+
const junctionResult = await db.execute(
|
|
158
|
+
sql`SELECT * FROM ${sql.raw(quoteIdent(rel.junction_table))} WHERE ${sql.raw(quoteIdent(rel.left_column))} IN (${inList})${orderBy}`
|
|
159
|
+
);
|
|
160
|
+
const jRows = junctionResult.rows;
|
|
161
|
+
const rightIds = [...new Set(jRows.map((r) => r[rel.right_column]))];
|
|
162
|
+
const uncached = rightIds.filter((id) => !cache.has(`${rel.table}:${id}`));
|
|
163
|
+
if (uncached.length > 0) {
|
|
164
|
+
const rightInList = sql.join(uncached.map((id) => sql`${id}`), sql`, `);
|
|
165
|
+
const publishedCond = publishedOnly ? sql` AND published = true` : sql``;
|
|
166
|
+
const entitiesResult = await db.execute(
|
|
167
|
+
sql`SELECT * FROM ${sql.raw(quoteIdent(rel.table))} WHERE id IN (${rightInList})${publishedCond}`
|
|
168
|
+
);
|
|
169
|
+
for (const item of entitiesResult.rows) {
|
|
170
|
+
cache.set(`${rel.table}:${item["id"]}`, item);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const byLeft = groupBy(jRows, rel.left_column);
|
|
174
|
+
for (const row of rows) {
|
|
175
|
+
const jEntries = byLeft[row["id"]] ?? [];
|
|
176
|
+
row[fieldName] = jEntries.map((jr) => cache.get(`${rel.table}:${jr[rel.right_column]}`)).filter(Boolean);
|
|
177
|
+
}
|
|
178
|
+
} else if (rel.type === "media") {
|
|
179
|
+
const fkValues = rows.map((r) => r[rel.fk_column]).filter(Boolean);
|
|
180
|
+
if (fkValues.length === 0) {
|
|
181
|
+
for (const row of rows) row[fieldName] = null;
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const unique = [...new Set(fkValues)];
|
|
185
|
+
const uncached = unique.filter((id) => !cache.has(`media:${id}`));
|
|
186
|
+
if (uncached.length > 0) {
|
|
187
|
+
const inList = sql.join(uncached.map((id) => sql`${id}`), sql`, `);
|
|
188
|
+
const result = await db.execute(sql`SELECT * FROM "media" WHERE id IN (${inList})`);
|
|
189
|
+
for (const item of result.rows) {
|
|
190
|
+
cache.set(`media:${item["id"]}`, item);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
for (const row of rows) {
|
|
194
|
+
const fkVal = row[rel.fk_column];
|
|
195
|
+
row[fieldName] = fkVal ? cache.get(`media:${fkVal}`) ?? null : null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
async function resolveRelationBareIds(db, rows, fieldName, rel) {
|
|
200
|
+
if (rows.length === 0) return;
|
|
201
|
+
if (rel.type === "paragraph") {
|
|
202
|
+
const parentIds = rows.map((r) => r["id"]);
|
|
203
|
+
const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `);
|
|
204
|
+
const result = await db.execute(
|
|
205
|
+
sql`SELECT id, parent_id FROM ${sql.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) ORDER BY "order" ASC`
|
|
206
|
+
);
|
|
207
|
+
const byParent = groupBy(result.rows, "parent_id");
|
|
208
|
+
for (const row of rows) {
|
|
209
|
+
row[fieldName] = (byParent[row["id"]] ?? []).map((r) => r["id"]);
|
|
210
|
+
}
|
|
211
|
+
} else if (rel.type === "junction") {
|
|
212
|
+
const parentIds = rows.map((r) => r["id"]);
|
|
213
|
+
const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `);
|
|
214
|
+
const result = await db.execute(
|
|
215
|
+
sql`SELECT ${sql.raw(quoteIdent(rel.left_column))} AS left_id, ${sql.raw(quoteIdent(rel.right_column))} AS right_id FROM ${sql.raw(quoteIdent(rel.junction_table))} WHERE ${sql.raw(quoteIdent(rel.left_column))} IN (${inList})`
|
|
216
|
+
);
|
|
217
|
+
const byLeft = groupBy(result.rows, "left_id");
|
|
218
|
+
for (const row of rows) {
|
|
219
|
+
row[fieldName] = (byLeft[row["id"]] ?? []).map((r) => r["right_id"]);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export {
|
|
225
|
+
persistParagraphField,
|
|
226
|
+
deleteParagraphField,
|
|
227
|
+
persistJunctionField,
|
|
228
|
+
buildRelationsMap,
|
|
229
|
+
resolveRelationField,
|
|
230
|
+
resolveRelationBareIds
|
|
231
|
+
};
|
|
232
|
+
//# sourceMappingURL=chunk-FBS2RQI3.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/relations.ts"],"sourcesContent":["import crypto from 'node:crypto'\nimport { sql } from 'drizzle-orm'\nimport type { ParsedField, ParsedParagraphType, SchemaRegistry } from '@bobbykim/manguito-cms-core'\nimport type { DrizzlePostgresInstance } from '@bobbykim/manguito-cms-db'\nimport type { MediaDelta } from './media-references.js'\n\n// ─── Relation persistence ─────────────────────────────────────────────────────\n//\n// Owns paragraph/junction/reference/media relation table SQL — both the write\n// side (persist/delete below) and the read side (descriptors + resolvers further\n// down). Each persist/delete returns the MediaDelta of media ids it gained and\n// lost (see media-references.ts), which the caller merges and reconciles once.\n// The repository's resolveRows() drives the read resolvers.\n\nfunction quoteIdent(name: string): string {\n if (!/^[a-z][a-z0-9_-]*$/.test(name)) throw new Error(`Unsafe identifier: ${name}`)\n return `\"${name}\"`\n}\n\n// Paragraph rows live on a child table with their own image/video/file columns\n// (e.g. a \"photo card\" block embeds an image) — these need the same reference-count\n// bookkeeping as top-level content fields, one level down.\nfunction paragraphMediaFieldNames(pType: ParsedParagraphType): Set<string> {\n return new Set(\n pType.fields\n .filter((f) => f.field_type === 'image' || f.field_type === 'video' || f.field_type === 'file')\n .map((f) => f.name)\n )\n}\n\nasync function fetchParagraphMediaIds(\n db: DrizzlePostgresInstance,\n pType: ParsedParagraphType,\n itemId: string,\n fieldName: string\n): Promise<string[]> {\n const mediaColumns = pType.fields\n .filter((f) => f.field_type === 'image' || f.field_type === 'video' || f.field_type === 'file')\n .map((f) => f.db_column!.column_name)\n if (mediaColumns.length === 0) return []\n\n const tbl = sql.raw(quoteIdent(pType.db.table_name))\n const cols = sql.join(mediaColumns.map((c) => sql.raw(quoteIdent(c))), sql`, `)\n const result = await db.execute(\n sql`SELECT ${cols} FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`\n )\n\n const ids: string[] = []\n for (const row of result.rows as Record<string, unknown>[]) {\n for (const col of mediaColumns) {\n const v = row[col]\n if (typeof v === 'string' && v !== '') ids.push(v)\n }\n }\n return ids\n}\n\n// Replaces every paragraph row for one field of one parent (delete + reinsert),\n// returning the media ids removed (old rows) and added (new rows).\nexport async function persistParagraphField(\n db: DrizzlePostgresInstance,\n itemId: string,\n parentType: string,\n fieldName: string,\n pType: ParsedParagraphType,\n items: unknown[]\n): Promise<MediaDelta> {\n const removed = await fetchParagraphMediaIds(db, pType, itemId, fieldName)\n\n const tbl = sql.raw(quoteIdent(pType.db.table_name))\n await db.execute(sql`DELETE FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`)\n\n const mediaFieldNames = paragraphMediaFieldNames(pType)\n const added: string[] = []\n\n for (let i = 0; i < items.length; i++) {\n const pItem = items[i] as Record<string, unknown>\n const data: Record<string, unknown> = {\n id: crypto.randomUUID(),\n parent_id: itemId,\n parent_type: parentType,\n parent_field: fieldName,\n order: i,\n created_at: new Date(),\n updated_at: new Date(),\n }\n for (const pf of pType.fields) {\n if (!pf.db_column || pf.db_column.junction) continue\n data[pf.db_column.column_name] = pItem[pf.name] ?? null\n }\n const cols = sql.join(Object.keys(data).map((k) => sql.raw(quoteIdent(k))), sql`, `)\n const vals = sql.join(Object.values(data).map((v) => sql`${v}`), sql`, `)\n await db.execute(sql`INSERT INTO ${tbl} (${cols}) VALUES (${vals})`)\n\n for (const name of mediaFieldNames) {\n const val = pItem[name]\n if (typeof val === 'string' && val !== '') added.push(val)\n }\n }\n\n return { added, removed }\n}\n\n// Deletes all paragraph rows for one field of one parent (used when the parent is\n// being deleted — paragraph rows have no FK/cascade back to their parent, so they'd\n// leak otherwise). Returns the media those rows referenced, all removed.\nexport async function deleteParagraphField(\n db: DrizzlePostgresInstance,\n itemId: string,\n fieldName: string,\n pType: ParsedParagraphType\n): Promise<MediaDelta> {\n const removed = await fetchParagraphMediaIds(db, pType, itemId, fieldName)\n const tbl = sql.raw(quoteIdent(pType.db.table_name))\n await db.execute(sql`DELETE FROM ${tbl} WHERE parent_id = ${itemId} AND parent_field = ${fieldName}`)\n return { added: [], removed }\n}\n\n// Replaces every junction row for one many-to-many field of one parent.\nexport async function persistJunctionField(\n db: DrizzlePostgresInstance,\n itemId: string,\n junction: { table_name: string; left_column: string; right_column: string },\n relatedIds: string[]\n): Promise<void> {\n const tbl = sql.raw(quoteIdent(junction.table_name))\n const left = sql.raw(quoteIdent(junction.left_column))\n const right = sql.raw(quoteIdent(junction.right_column))\n await db.execute(sql`DELETE FROM ${tbl} WHERE ${left} = ${itemId}`)\n for (const rid of relatedIds) {\n await db.execute(sql`INSERT INTO ${tbl} (${left}, ${right}) VALUES (${itemId}, ${rid})`)\n }\n}\n\n// ─── Relation descriptors and resolution (read side) ──────────────────────────\n//\n// Relation read resolution. These were lifted out of the repository so that all\n// paragraph/junction/reference/media table SQL lives in one module alongside the\n// write persistence above. The repository's resolveRows() calls into here.\n\nexport type ParagraphRelationDef = {\n type: 'paragraph'\n table: string\n}\n\nexport type ReferenceRelationDef = {\n type: 'reference'\n table: string\n fk_column: string\n}\n\nexport type JunctionRelationDef = {\n type: 'junction'\n table: string\n junction_table: string\n left_column: string\n right_column: string\n // Only ordered junctions have an \"order\" column (db codegen emits it only when\n // order_column is true) — the resolver must not ORDER BY it otherwise.\n order_column: boolean\n}\n\nexport type MediaRelationDef = {\n type: 'media'\n fk_column: string\n}\n\nexport type RelationDef =\n | ParagraphRelationDef\n | ReferenceRelationDef\n | JunctionRelationDef\n | MediaRelationDef\n\n// Derives a content type's relations map from its parsed fields, so paragraph/\n// reference/media fields are actually resolved instead of silently dropped.\n// Junction and foreign_key info is already on each field's db_column; paragraph\n// fields store their target table on the paragraph type, hence the registry.\nexport function buildRelationsMap(\n fields: ParsedField[],\n registry: SchemaRegistry\n): Record<string, RelationDef> {\n const relations: Record<string, RelationDef> = {}\n\n for (const field of fields) {\n if (field.field_type === 'image' || field.field_type === 'video' || field.field_type === 'file') {\n if (!field.db_column) continue\n relations[field.name] = { type: 'media', fk_column: field.db_column.column_name }\n } else if (field.field_type === 'paragraph') {\n const ref = (field.ui_component as { ref?: string }).ref\n const pType = ref ? registry.paragraph_types[ref] : undefined\n if (!pType) continue\n relations[field.name] = { type: 'paragraph', table: pType.db.table_name }\n } else if (field.field_type === 'reference') {\n if (!field.db_column) continue\n if (field.db_column.junction) {\n const j = field.db_column.junction\n relations[field.name] = {\n type: 'junction',\n table: j.right_table,\n junction_table: j.table_name,\n left_column: j.left_column,\n right_column: j.right_column,\n order_column: j.order_column,\n }\n } else if (field.db_column.foreign_key) {\n relations[field.name] = {\n type: 'reference',\n table: field.db_column.foreign_key.table,\n fk_column: field.db_column.column_name,\n }\n }\n }\n }\n\n return relations\n}\n\nfunction groupBy<T extends Record<string, unknown>>(\n items: T[],\n key: string\n): Record<string, T[]> {\n const result: Record<string, T[]> = {}\n for (const item of items) {\n const k = String(item[key])\n if (!result[k]) result[k] = []\n result[k]!.push(item)\n }\n return result\n}\n\n// Fully resolves a relation field on a batch of rows: paragraph → ordered rows,\n// reference → the target row, junction → target rows, media → the media row.\n// The cache (keyed table:id) dedupes target lookups within a request.\n//\n// `publishedOnly` (default false, preserving prior behavior for admin callers)\n// adds `AND published = true` to the reference/junction target-row SELECTs.\n// Reference and junction targets are always content or taxonomy tables, which\n// always carry a `published` boolean system field — see CONTENT_SYSTEM_FIELDS /\n// TAXONOMY_SYSTEM_FIELDS in packages/core/src/parser/parseSchema.ts. Paragraph\n// and media targets are deliberately excluded: paragraph rows are owned by\n// their parent (no independent publish state) and media rows have no\n// `published` column at all.\nexport async function resolveRelationField(\n db: DrizzlePostgresInstance,\n rows: Record<string, unknown>[],\n fieldName: string,\n rel: RelationDef,\n cache: Map<string, unknown>,\n publishedOnly = false\n): Promise<void> {\n if (rows.length === 0) return\n\n if (rel.type === 'paragraph') {\n const parentIds = rows.map((r) => r['id'] as string)\n const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `)\n const result = await db.execute(\n sql`SELECT * FROM ${sql.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) ORDER BY \"order\" ASC`\n )\n const byParent = groupBy(result.rows as Record<string, unknown>[], 'parent_id')\n for (const row of rows) {\n row[fieldName] = byParent[row['id'] as string] ?? []\n }\n } else if (rel.type === 'reference') {\n const fkValues = rows.map((r) => r[rel.fk_column] as string).filter(Boolean)\n if (fkValues.length === 0) {\n for (const row of rows) row[fieldName] = null\n return\n }\n const unique = [...new Set(fkValues)]\n const uncached = unique.filter((id) => !cache.has(`${rel.table}:${id}`))\n if (uncached.length > 0) {\n const inList = sql.join(uncached.map((id) => sql`${id}`), sql`, `)\n const publishedCond = publishedOnly ? sql` AND published = true` : sql``\n const result = await db.execute(\n sql`SELECT * FROM ${sql.raw(quoteIdent(rel.table))} WHERE id IN (${inList})${publishedCond}`\n )\n for (const item of result.rows as Record<string, unknown>[]) {\n cache.set(`${rel.table}:${item['id']}`, item)\n }\n }\n for (const row of rows) {\n const fkVal = row[rel.fk_column] as string\n row[fieldName] = fkVal ? (cache.get(`${rel.table}:${fkVal}`) ?? null) : null\n }\n } else if (rel.type === 'junction') {\n const parentIds = rows.map((r) => r['id'] as string)\n const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `)\n const orderBy = rel.order_column ? sql` ORDER BY \"order\" ASC` : sql``\n const junctionResult = await db.execute(\n sql`SELECT * FROM ${sql.raw(quoteIdent(rel.junction_table))} WHERE ${sql.raw(quoteIdent(rel.left_column))} IN (${inList})${orderBy}`\n )\n const jRows = junctionResult.rows as Record<string, unknown>[]\n const rightIds = [...new Set(jRows.map((r) => r[rel.right_column] as string))]\n const uncached = rightIds.filter((id) => !cache.has(`${rel.table}:${id}`))\n if (uncached.length > 0) {\n const rightInList = sql.join(uncached.map((id) => sql`${id}`), sql`, `)\n const publishedCond = publishedOnly ? sql` AND published = true` : sql``\n const entitiesResult = await db.execute(\n sql`SELECT * FROM ${sql.raw(quoteIdent(rel.table))} WHERE id IN (${rightInList})${publishedCond}`\n )\n for (const item of entitiesResult.rows as Record<string, unknown>[]) {\n cache.set(`${rel.table}:${item['id']}`, item)\n }\n }\n const byLeft = groupBy(jRows, rel.left_column)\n for (const row of rows) {\n const jEntries = byLeft[row['id'] as string] ?? []\n row[fieldName] = jEntries\n .map((jr) => cache.get(`${rel.table}:${jr[rel.right_column] as string}`))\n .filter(Boolean)\n }\n } else if (rel.type === 'media') {\n const fkValues = rows.map((r) => r[rel.fk_column] as string).filter(Boolean)\n if (fkValues.length === 0) {\n for (const row of rows) row[fieldName] = null\n return\n }\n const unique = [...new Set(fkValues)]\n const uncached = unique.filter((id) => !cache.has(`media:${id}`))\n if (uncached.length > 0) {\n const inList = sql.join(uncached.map((id) => sql`${id}`), sql`, `)\n const result = await db.execute(sql`SELECT * FROM \"media\" WHERE id IN (${inList})`)\n for (const item of result.rows as Record<string, unknown>[]) {\n cache.set(`media:${item['id']}`, item)\n }\n }\n for (const row of rows) {\n const fkVal = row[rel.fk_column] as string\n row[fieldName] = fkVal ? (cache.get(`media:${fkVal}`) ?? null) : null\n }\n }\n}\n\n// Populates paragraph/junction fields with bare ID arrays when they are not\n// explicitly included — they have no column on the owning table, so without this\n// they'd be silently absent. Reference fields already carry their bare ID as a\n// real column from `SELECT *`, so they need no extra query.\nexport async function resolveRelationBareIds(\n db: DrizzlePostgresInstance,\n rows: Record<string, unknown>[],\n fieldName: string,\n rel: RelationDef\n): Promise<void> {\n if (rows.length === 0) return\n\n if (rel.type === 'paragraph') {\n const parentIds = rows.map((r) => r['id'] as string)\n const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `)\n const result = await db.execute(\n sql`SELECT id, parent_id FROM ${sql.raw(quoteIdent(rel.table))} WHERE parent_id IN (${inList}) ORDER BY \"order\" ASC`\n )\n const byParent = groupBy(result.rows as Record<string, unknown>[], 'parent_id')\n for (const row of rows) {\n row[fieldName] = (byParent[row['id'] as string] ?? []).map((r) => r['id'])\n }\n } else if (rel.type === 'junction') {\n const parentIds = rows.map((r) => r['id'] as string)\n const inList = sql.join(parentIds.map((id) => sql`${id}`), sql`, `)\n const result = await db.execute(\n sql`SELECT ${sql.raw(quoteIdent(rel.left_column))} AS left_id, ${sql.raw(quoteIdent(rel.right_column))} AS right_id FROM ${sql.raw(quoteIdent(rel.junction_table))} WHERE ${sql.raw(quoteIdent(rel.left_column))} IN (${inList})`\n )\n const byLeft = groupBy(result.rows as Record<string, unknown>[], 'left_id')\n for (const row of rows) {\n row[fieldName] = (byLeft[row['id'] as string] ?? []).map((r) => r['right_id'])\n }\n }\n}\n"],"mappings":";AAAA,OAAO,YAAY;AACnB,SAAS,WAAW;AAapB,SAAS,WAAW,MAAsB;AACxC,MAAI,CAAC,qBAAqB,KAAK,IAAI,EAAG,OAAM,IAAI,MAAM,sBAAsB,IAAI,EAAE;AAClF,SAAO,IAAI,IAAI;AACjB;AAKA,SAAS,yBAAyB,OAAyC;AACzE,SAAO,IAAI;AAAA,IACT,MAAM,OACH,OAAO,CAAC,MAAM,EAAE,eAAe,WAAW,EAAE,eAAe,WAAW,EAAE,eAAe,MAAM,EAC7F,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,EACtB;AACF;AAEA,eAAe,uBACb,IACA,OACA,QACA,WACmB;AACnB,QAAM,eAAe,MAAM,OACxB,OAAO,CAAC,MAAM,EAAE,eAAe,WAAW,EAAE,eAAe,WAAW,EAAE,eAAe,MAAM,EAC7F,IAAI,CAAC,MAAM,EAAE,UAAW,WAAW;AACtC,MAAI,aAAa,WAAW,EAAG,QAAO,CAAC;AAEvC,QAAM,MAAM,IAAI,IAAI,WAAW,MAAM,GAAG,UAAU,CAAC;AACnD,QAAM,OAAO,IAAI,KAAK,aAAa,IAAI,CAAC,MAAM,IAAI,IAAI,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO;AAC9E,QAAM,SAAS,MAAM,GAAG;AAAA,IACtB,aAAa,IAAI,SAAS,GAAG,sBAAsB,MAAM,uBAAuB,SAAS;AAAA,EAC3F;AAEA,QAAM,MAAgB,CAAC;AACvB,aAAW,OAAO,OAAO,MAAmC;AAC1D,eAAW,OAAO,cAAc;AAC9B,YAAM,IAAI,IAAI,GAAG;AACjB,UAAI,OAAO,MAAM,YAAY,MAAM,GAAI,KAAI,KAAK,CAAC;AAAA,IACnD;AAAA,EACF;AACA,SAAO;AACT;AAIA,eAAsB,sBACpB,IACA,QACA,YACA,WACA,OACA,OACqB;AACrB,QAAM,UAAU,MAAM,uBAAuB,IAAI,OAAO,QAAQ,SAAS;AAEzE,QAAM,MAAM,IAAI,IAAI,WAAW,MAAM,GAAG,UAAU,CAAC;AACnD,QAAM,GAAG,QAAQ,kBAAkB,GAAG,sBAAsB,MAAM,uBAAuB,SAAS,EAAE;AAEpG,QAAM,kBAAkB,yBAAyB,KAAK;AACtD,QAAM,QAAkB,CAAC;AAEzB,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,QAAQ,MAAM,CAAC;AACrB,UAAM,OAAgC;AAAA,MACpC,IAAI,OAAO,WAAW;AAAA,MACtB,WAAW;AAAA,MACX,aAAa;AAAA,MACb,cAAc;AAAA,MACd,OAAO;AAAA,MACP,YAAY,oBAAI,KAAK;AAAA,MACrB,YAAY,oBAAI,KAAK;AAAA,IACvB;AACA,eAAW,MAAM,MAAM,QAAQ;AAC7B,UAAI,CAAC,GAAG,aAAa,GAAG,UAAU,SAAU;AAC5C,WAAK,GAAG,UAAU,WAAW,IAAI,MAAM,GAAG,IAAI,KAAK;AAAA,IACrD;AACA,UAAM,OAAO,IAAI,KAAK,OAAO,KAAK,IAAI,EAAE,IAAI,CAAC,MAAM,IAAI,IAAI,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO;AACnF,UAAM,OAAO,IAAI,KAAK,OAAO,OAAO,IAAI,EAAE,IAAI,CAAC,MAAM,MAAM,CAAC,EAAE,GAAG,OAAO;AACxE,UAAM,GAAG,QAAQ,kBAAkB,GAAG,KAAK,IAAI,aAAa,IAAI,GAAG;AAEnE,eAAW,QAAQ,iBAAiB;AAClC,YAAM,MAAM,MAAM,IAAI;AACtB,UAAI,OAAO,QAAQ,YAAY,QAAQ,GAAI,OAAM,KAAK,GAAG;AAAA,IAC3D;AAAA,EACF;AAEA,SAAO,EAAE,OAAO,QAAQ;AAC1B;AAKA,eAAsB,qBACpB,IACA,QACA,WACA,OACqB;AACrB,QAAM,UAAU,MAAM,uBAAuB,IAAI,OAAO,QAAQ,SAAS;AACzE,QAAM,MAAM,IAAI,IAAI,WAAW,MAAM,GAAG,UAAU,CAAC;AACnD,QAAM,GAAG,QAAQ,kBAAkB,GAAG,sBAAsB,MAAM,uBAAuB,SAAS,EAAE;AACpG,SAAO,EAAE,OAAO,CAAC,GAAG,QAAQ;AAC9B;AAGA,eAAsB,qBACpB,IACA,QACA,UACA,YACe;AACf,QAAM,MAAM,IAAI,IAAI,WAAW,SAAS,UAAU,CAAC;AACnD,QAAM,OAAO,IAAI,IAAI,WAAW,SAAS,WAAW,CAAC;AACrD,QAAM,QAAQ,IAAI,IAAI,WAAW,SAAS,YAAY,CAAC;AACvD,QAAM,GAAG,QAAQ,kBAAkB,GAAG,UAAU,IAAI,MAAM,MAAM,EAAE;AAClE,aAAW,OAAO,YAAY;AAC5B,UAAM,GAAG,QAAQ,kBAAkB,GAAG,KAAK,IAAI,KAAK,KAAK,aAAa,MAAM,KAAK,GAAG,GAAG;AAAA,EACzF;AACF;AA6CO,SAAS,kBACd,QACA,UAC6B;AAC7B,QAAM,YAAyC,CAAC;AAEhD,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,eAAe,WAAW,MAAM,eAAe,WAAW,MAAM,eAAe,QAAQ;AAC/F,UAAI,CAAC,MAAM,UAAW;AACtB,gBAAU,MAAM,IAAI,IAAI,EAAE,MAAM,SAAS,WAAW,MAAM,UAAU,YAAY;AAAA,IAClF,WAAW,MAAM,eAAe,aAAa;AAC3C,YAAM,MAAO,MAAM,aAAkC;AACrD,YAAM,QAAQ,MAAM,SAAS,gBAAgB,GAAG,IAAI;AACpD,UAAI,CAAC,MAAO;AACZ,gBAAU,MAAM,IAAI,IAAI,EAAE,MAAM,aAAa,OAAO,MAAM,GAAG,WAAW;AAAA,IAC1E,WAAW,MAAM,eAAe,aAAa;AAC3C,UAAI,CAAC,MAAM,UAAW;AACtB,UAAI,MAAM,UAAU,UAAU;AAC5B,cAAM,IAAI,MAAM,UAAU;AAC1B,kBAAU,MAAM,IAAI,IAAI;AAAA,UACtB,MAAM;AAAA,UACN,OAAO,EAAE;AAAA,UACT,gBAAgB,EAAE;AAAA,UAClB,aAAa,EAAE;AAAA,UACf,cAAc,EAAE;AAAA,UAChB,cAAc,EAAE;AAAA,QAClB;AAAA,MACF,WAAW,MAAM,UAAU,aAAa;AACtC,kBAAU,MAAM,IAAI,IAAI;AAAA,UACtB,MAAM;AAAA,UACN,OAAO,MAAM,UAAU,YAAY;AAAA,UACnC,WAAW,MAAM,UAAU;AAAA,QAC7B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,QACP,OACA,KACqB;AACrB,QAAM,SAA8B,CAAC;AACrC,aAAW,QAAQ,OAAO;AACxB,UAAM,IAAI,OAAO,KAAK,GAAG,CAAC;AAC1B,QAAI,CAAC,OAAO,CAAC,EAAG,QAAO,CAAC,IAAI,CAAC;AAC7B,WAAO,CAAC,EAAG,KAAK,IAAI;AAAA,EACtB;AACA,SAAO;AACT;AAcA,eAAsB,qBACpB,IACA,MACA,WACA,KACA,OACA,gBAAgB,OACD;AACf,MAAI,KAAK,WAAW,EAAG;AAEvB,MAAI,IAAI,SAAS,aAAa;AAC5B,UAAM,YAAY,KAAK,IAAI,CAAC,MAAM,EAAE,IAAI,CAAW;AACnD,UAAM,SAAS,IAAI,KAAK,UAAU,IAAI,CAAC,OAAO,MAAM,EAAE,EAAE,GAAG,OAAO;AAClE,UAAM,SAAS,MAAM,GAAG;AAAA,MACtB,oBAAoB,IAAI,IAAI,WAAW,IAAI,KAAK,CAAC,CAAC,wBAAwB,MAAM;AAAA,IAClF;AACA,UAAM,WAAW,QAAQ,OAAO,MAAmC,WAAW;AAC9E,eAAW,OAAO,MAAM;AACtB,UAAI,SAAS,IAAI,SAAS,IAAI,IAAI,CAAW,KAAK,CAAC;AAAA,IACrD;AAAA,EACF,WAAW,IAAI,SAAS,aAAa;AACnC,UAAM,WAAW,KAAK,IAAI,CAAC,MAAM,EAAE,IAAI,SAAS,CAAW,EAAE,OAAO,OAAO;AAC3E,QAAI,SAAS,WAAW,GAAG;AACzB,iBAAW,OAAO,KAAM,KAAI,SAAS,IAAI;AACzC;AAAA,IACF;AACA,UAAM,SAAS,CAAC,GAAG,IAAI,IAAI,QAAQ,CAAC;AACpC,UAAM,WAAW,OAAO,OAAO,CAAC,OAAO,CAAC,MAAM,IAAI,GAAG,IAAI,KAAK,IAAI,EAAE,EAAE,CAAC;AACvE,QAAI,SAAS,SAAS,GAAG;AACvB,YAAM,SAAS,IAAI,KAAK,SAAS,IAAI,CAAC,OAAO,MAAM,EAAE,EAAE,GAAG,OAAO;AACjE,YAAM,gBAAgB,gBAAgB,6BAA6B;AACnE,YAAM,SAAS,MAAM,GAAG;AAAA,QACtB,oBAAoB,IAAI,IAAI,WAAW,IAAI,KAAK,CAAC,CAAC,iBAAiB,MAAM,IAAI,aAAa;AAAA,MAC5F;AACA,iBAAW,QAAQ,OAAO,MAAmC;AAC3D,cAAM,IAAI,GAAG,IAAI,KAAK,IAAI,KAAK,IAAI,CAAC,IAAI,IAAI;AAAA,MAC9C;AAAA,IACF;AACA,eAAW,OAAO,MAAM;AACtB,YAAM,QAAQ,IAAI,IAAI,SAAS;AAC/B,UAAI,SAAS,IAAI,QAAS,MAAM,IAAI,GAAG,IAAI,KAAK,IAAI,KAAK,EAAE,KAAK,OAAQ;AAAA,IAC1E;AAAA,EACF,WAAW,IAAI,SAAS,YAAY;AAClC,UAAM,YAAY,KAAK,IAAI,CAAC,MAAM,EAAE,IAAI,CAAW;AACnD,UAAM,SAAS,IAAI,KAAK,UAAU,IAAI,CAAC,OAAO,MAAM,EAAE,EAAE,GAAG,OAAO;AAClE,UAAM,UAAU,IAAI,eAAe,6BAA6B;AAChE,UAAM,iBAAiB,MAAM,GAAG;AAAA,MAC9B,oBAAoB,IAAI,IAAI,WAAW,IAAI,cAAc,CAAC,CAAC,UAAU,IAAI,IAAI,WAAW,IAAI,WAAW,CAAC,CAAC,QAAQ,MAAM,IAAI,OAAO;AAAA,IACpI;AACA,UAAM,QAAQ,eAAe;AAC7B,UAAM,WAAW,CAAC,GAAG,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI,YAAY,CAAW,CAAC,CAAC;AAC7E,UAAM,WAAW,SAAS,OAAO,CAAC,OAAO,CAAC,MAAM,IAAI,GAAG,IAAI,KAAK,IAAI,EAAE,EAAE,CAAC;AACzE,QAAI,SAAS,SAAS,GAAG;AACvB,YAAM,cAAc,IAAI,KAAK,SAAS,IAAI,CAAC,OAAO,MAAM,EAAE,EAAE,GAAG,OAAO;AACtE,YAAM,gBAAgB,gBAAgB,6BAA6B;AACnE,YAAM,iBAAiB,MAAM,GAAG;AAAA,QAC9B,oBAAoB,IAAI,IAAI,WAAW,IAAI,KAAK,CAAC,CAAC,iBAAiB,WAAW,IAAI,aAAa;AAAA,MACjG;AACA,iBAAW,QAAQ,eAAe,MAAmC;AACnE,cAAM,IAAI,GAAG,IAAI,KAAK,IAAI,KAAK,IAAI,CAAC,IAAI,IAAI;AAAA,MAC9C;AAAA,IACF;AACA,UAAM,SAAS,QAAQ,OAAO,IAAI,WAAW;AAC7C,eAAW,OAAO,MAAM;AACtB,YAAM,WAAW,OAAO,IAAI,IAAI,CAAW,KAAK,CAAC;AACjD,UAAI,SAAS,IAAI,SACd,IAAI,CAAC,OAAO,MAAM,IAAI,GAAG,IAAI,KAAK,IAAI,GAAG,IAAI,YAAY,CAAW,EAAE,CAAC,EACvE,OAAO,OAAO;AAAA,IACnB;AAAA,EACF,WAAW,IAAI,SAAS,SAAS;AAC/B,UAAM,WAAW,KAAK,IAAI,CAAC,MAAM,EAAE,IAAI,SAAS,CAAW,EAAE,OAAO,OAAO;AAC3E,QAAI,SAAS,WAAW,GAAG;AACzB,iBAAW,OAAO,KAAM,KAAI,SAAS,IAAI;AACzC;AAAA,IACF;AACA,UAAM,SAAS,CAAC,GAAG,IAAI,IAAI,QAAQ,CAAC;AACpC,UAAM,WAAW,OAAO,OAAO,CAAC,OAAO,CAAC,MAAM,IAAI,SAAS,EAAE,EAAE,CAAC;AAChE,QAAI,SAAS,SAAS,GAAG;AACvB,YAAM,SAAS,IAAI,KAAK,SAAS,IAAI,CAAC,OAAO,MAAM,EAAE,EAAE,GAAG,OAAO;AACjE,YAAM,SAAS,MAAM,GAAG,QAAQ,yCAAyC,MAAM,GAAG;AAClF,iBAAW,QAAQ,OAAO,MAAmC;AAC3D,cAAM,IAAI,SAAS,KAAK,IAAI,CAAC,IAAI,IAAI;AAAA,MACvC;AAAA,IACF;AACA,eAAW,OAAO,MAAM;AACtB,YAAM,QAAQ,IAAI,IAAI,SAAS;AAC/B,UAAI,SAAS,IAAI,QAAS,MAAM,IAAI,SAAS,KAAK,EAAE,KAAK,OAAQ;AAAA,IACnE;AAAA,EACF;AACF;AAMA,eAAsB,uBACpB,IACA,MACA,WACA,KACe;AACf,MAAI,KAAK,WAAW,EAAG;AAEvB,MAAI,IAAI,SAAS,aAAa;AAC5B,UAAM,YAAY,KAAK,IAAI,CAAC,MAAM,EAAE,IAAI,CAAW;AACnD,UAAM,SAAS,IAAI,KAAK,UAAU,IAAI,CAAC,OAAO,MAAM,EAAE,EAAE,GAAG,OAAO;AAClE,UAAM,SAAS,MAAM,GAAG;AAAA,MACtB,gCAAgC,IAAI,IAAI,WAAW,IAAI,KAAK,CAAC,CAAC,wBAAwB,MAAM;AAAA,IAC9F;AACA,UAAM,WAAW,QAAQ,OAAO,MAAmC,WAAW;AAC9E,eAAW,OAAO,MAAM;AACtB,UAAI,SAAS,KAAK,SAAS,IAAI,IAAI,CAAW,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AAAA,IAC3E;AAAA,EACF,WAAW,IAAI,SAAS,YAAY;AAClC,UAAM,YAAY,KAAK,IAAI,CAAC,MAAM,EAAE,IAAI,CAAW;AACnD,UAAM,SAAS,IAAI,KAAK,UAAU,IAAI,CAAC,OAAO,MAAM,EAAE,EAAE,GAAG,OAAO;AAClE,UAAM,SAAS,MAAM,GAAG;AAAA,MACtB,aAAa,IAAI,IAAI,WAAW,IAAI,WAAW,CAAC,CAAC,gBAAgB,IAAI,IAAI,WAAW,IAAI,YAAY,CAAC,CAAC,qBAAqB,IAAI,IAAI,WAAW,IAAI,cAAc,CAAC,CAAC,UAAU,IAAI,IAAI,WAAW,IAAI,WAAW,CAAC,CAAC,QAAQ,MAAM;AAAA,IAChO;AACA,UAAM,SAAS,QAAQ,OAAO,MAAmC,SAAS;AAC1E,eAAW,OAAO,MAAM;AACtB,UAAI,SAAS,KAAK,OAAO,IAAI,IAAI,CAAW,KAAK,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC;AAAA,IAC/E;AAAA,EACF;AACF;","names":[]}
|