Added protecting hooks

This commit is contained in:
2026-09-25 12:42:38 +02:00
parent 9a18434f4a
commit b82ef82f50
33 changed files with 733 additions and 6 deletions
+35
View File
@@ -0,0 +1,35 @@
import type { CollectionAfterChangeHook, CollectionAfterDeleteHook } from 'payload';
import type { I18nConfig } from '../i18n/index.js';
type RevalidateFn = (path: string) => void;
type BuildRevalidateHookArgs = {
config: I18nConfig;
/** Home slug (string or per-locale map) — home revalidates the root. */
homeSlug?: Record<string, string> | string;
/**
* next/cache revalidatePath, INJECTED by the project. The plugin never imports
* next/cache itself — that would crash when Payload runs as plain Node
* (generate:importmap). The project passes it: `revalidatePath` from 'next/cache'.
*/
revalidatePath: RevalidateFn;
};
/**
* Builds afterChange + afterDelete hooks that revalidate a page's ISR cache when
* an editor saves or deletes it — so changes appear immediately instead of
* waiting for the revalidate window. Without this, ISR means editors wait; with
* it, ISR is usable for a CMS.
*
* Handles every locale, the root (home), AND a changed slug (revalidates both the
* old and new path so neither goes stale). revalidatePath is injected — the
* plugin never imports next/cache (safe under generate:importmap / plain Node).
*
* // in your Media/Pages collection config, from a project file that CAN import next/cache:
* import { revalidatePath } from 'next/cache'
* import { buildRevalidateHook } from '@intecion/ipal-kit'
* const { afterChange, afterDelete } = buildRevalidateHook({ revalidatePath, config: i18nConfig })
* // hooks: { afterChange: [afterChange], afterDelete: [afterDelete] }
*/
export declare function buildRevalidateHook({ config, homeSlug, revalidatePath, }: BuildRevalidateHookArgs): {
afterChange: CollectionAfterChangeHook;
afterDelete: CollectionAfterDeleteHook;
};
export {};
+71
View File
@@ -0,0 +1,71 @@
import { buildLocalizedPath, getLocaleCodes } from '../i18n/index.js';
/** Resolve a doc's path in one locale (root for home). Null if no slug there. */ function pathForLocale(doc, locale, config, homeSlug) {
const slugField = doc.slug;
const slug = typeof slugField === 'string' ? slugField : slugField && typeof slugField === 'object' ? slugField[locale] : undefined;
if (!slug) {
return null;
}
return buildLocalizedPath({
config,
homeSlug,
locale,
slugs: {
[locale]: slug
}
}) ?? null;
}
/**
* Builds afterChange + afterDelete hooks that revalidate a page's ISR cache when
* an editor saves or deletes it — so changes appear immediately instead of
* waiting for the revalidate window. Without this, ISR means editors wait; with
* it, ISR is usable for a CMS.
*
* Handles every locale, the root (home), AND a changed slug (revalidates both the
* old and new path so neither goes stale). revalidatePath is injected — the
* plugin never imports next/cache (safe under generate:importmap / plain Node).
*
* // in your Media/Pages collection config, from a project file that CAN import next/cache:
* import { revalidatePath } from 'next/cache'
* import { buildRevalidateHook } from '@intecion/ipal-kit'
* const { afterChange, afterDelete } = buildRevalidateHook({ revalidatePath, config: i18nConfig })
* // hooks: { afterChange: [afterChange], afterDelete: [afterDelete] }
*/ export function buildRevalidateHook({ config, homeSlug, revalidatePath }) {
const locales = getLocaleCodes(config);
const afterChange = ({ doc, previousDoc })=>{
const seen = new Set();
for (const locale of locales){
// New path.
const newPath = pathForLocale(doc, locale, config, homeSlug);
if (newPath && !seen.has(newPath)) {
revalidatePath(newPath);
seen.add(newPath);
}
// Old path, if the slug changed — so the old URL doesn't serve stale content.
if (previousDoc) {
const oldPath = pathForLocale(previousDoc, locale, config, homeSlug);
if (oldPath && oldPath !== newPath && !seen.has(oldPath)) {
revalidatePath(oldPath);
seen.add(oldPath);
}
}
}
return doc;
};
const afterDelete = ({ doc })=>{
const seen = new Set();
for (const locale of locales){
const path = pathForLocale(doc, locale, config, homeSlug);
if (path && !seen.has(path)) {
revalidatePath(path);
seen.add(path);
}
}
return doc;
};
return {
afterChange,
afterDelete
};
}
//# sourceMappingURL=buildRevalidateHook.js.map
File diff suppressed because one or more lines are too long
+7
View File
@@ -0,0 +1,7 @@
export { normalizeFilenameHook } from '../media/index.js';
export { buildAutoFillMetaHook, validateFaviconField } from '../seo/index.js';
export { buildRevalidateHook } from './buildRevalidateHook.js';
export { buildPreventDeleteSystemPage } from './preventDeleteSystemPage.js';
export { setPublishedAtHook } from './setPublishedAt.js';
export { trackSlugHistoryHook } from './trackSlugHistory.js';
export { buildValidateUniqueRole } from './validateUniqueRole.js';
+11
View File
@@ -0,0 +1,11 @@
// Re-eksport hooków domenowych (mieszkają w swoich modułach, tu dla przeglądu —
// żeby był jeden katalog "wszystkie hooki pluginu"). Źródło prawdy to ich moduły.
export { normalizeFilenameHook } from '../media/index.js';
export { buildAutoFillMetaHook, validateFaviconField } from '../seo/index.js';
export { buildRevalidateHook } from './buildRevalidateHook.js';
export { buildPreventDeleteSystemPage } from './preventDeleteSystemPage.js';
export { setPublishedAtHook } from './setPublishedAt.js';
export { trackSlugHistoryHook } from './trackSlugHistory.js';
export { buildValidateUniqueRole } from './validateUniqueRole.js';
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"sources":["../../../src/modules/hooks/index.ts"],"sourcesContent":["// Re-eksport hooków domenowych (mieszkają w swoich modułach, tu dla przeglądu —\n// żeby był jeden katalog \"wszystkie hooki pluginu\"). Źródło prawdy to ich moduły.\nexport { normalizeFilenameHook } from '../media/index.js'\nexport { buildAutoFillMetaHook, validateFaviconField } from '../seo/index.js'\nexport { buildRevalidateHook } from './buildRevalidateHook.js'\nexport { buildPreventDeleteSystemPage } from './preventDeleteSystemPage.js'\nexport { setPublishedAtHook } from './setPublishedAt.js'\n\nexport { trackSlugHistoryHook } from './trackSlugHistory.js'\nexport { buildValidateUniqueRole } from './validateUniqueRole.js'\n"],"names":["normalizeFilenameHook","buildAutoFillMetaHook","validateFaviconField","buildRevalidateHook","buildPreventDeleteSystemPage","setPublishedAtHook","trackSlugHistoryHook","buildValidateUniqueRole"],"mappings":"AAAA,gFAAgF;AAChF,kFAAkF;AAClF,SAASA,qBAAqB,QAAQ,oBAAmB;AACzD,SAASC,qBAAqB,EAAEC,oBAAoB,QAAQ,kBAAiB;AAC7E,SAASC,mBAAmB,QAAQ,2BAA0B;AAC9D,SAASC,4BAA4B,QAAQ,+BAA8B;AAC3E,SAASC,kBAAkB,QAAQ,sBAAqB;AAExD,SAASC,oBAAoB,QAAQ,wBAAuB;AAC5D,SAASC,uBAAuB,QAAQ,0BAAyB"}
+15
View File
@@ -0,0 +1,15 @@
import type { CollectionBeforeDeleteHook } from 'payload';
/**
* Blocks deletion of a page assigned a System Page role (homepage,
* privacyPolicy, cookiePolicy, termsOfService). An editor deleting the privacy
* policy or homepage by accident would break routing and compliance links; this
* stops it with a clear error. They must unassign the role first (deliberate).
*
* Reads the role assignments from SiteSettings (which page holds which role).
*
* hooks: { beforeDelete: [buildPreventDeleteSystemPage({ settingsSlug: 'site-settings' })] }
*/
export declare function buildPreventDeleteSystemPage(args?: {
roleFields?: string[];
settingsSlug?: string;
}): CollectionBeforeDeleteHook;
+37
View File
@@ -0,0 +1,37 @@
import { APIError } from 'payload';
/**
* Blocks deletion of a page assigned a System Page role (homepage,
* privacyPolicy, cookiePolicy, termsOfService). An editor deleting the privacy
* policy or homepage by accident would break routing and compliance links; this
* stops it with a clear error. They must unassign the role first (deliberate).
*
* Reads the role assignments from SiteSettings (which page holds which role).
*
* hooks: { beforeDelete: [buildPreventDeleteSystemPage({ settingsSlug: 'site-settings' })] }
*/ export function buildPreventDeleteSystemPage(args = {}) {
const settingsSlug = args.settingsSlug ?? 'site-settings';
const roleFields = args.roleFields ?? [
'homepage',
'privacyPolicy',
'cookiePolicy',
'termsOfService'
];
return async ({ id, req })=>{
const settings = await req.payload.findGlobal({
slug: settingsSlug,
depth: 0
}).catch(()=>null);
if (!settings) {
return;
}
for (const field of roleFields){
const assigned = settings[field];
const assignedId = assigned && typeof assigned === 'object' ? assigned.id : assigned;
if (assignedId != null && String(assignedId) === String(id)) {
throw new APIError(`Nie można usunąć strony przypisanej do roli systemowej "${field}". ` + `Najpierw odłącz rolę w Site Settings.`, 400);
}
}
};
}
//# sourceMappingURL=preventDeleteSystemPage.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"sources":["../../../src/modules/hooks/preventDeleteSystemPage.ts"],"sourcesContent":["import type { CollectionBeforeDeleteHook } from 'payload'\n\nimport { APIError } from 'payload'\n\n/**\n * Blocks deletion of a page assigned a System Page role (homepage,\n * privacyPolicy, cookiePolicy, termsOfService). An editor deleting the privacy\n * policy or homepage by accident would break routing and compliance links; this\n * stops it with a clear error. They must unassign the role first (deliberate).\n *\n * Reads the role assignments from SiteSettings (which page holds which role).\n *\n * hooks: { beforeDelete: [buildPreventDeleteSystemPage({ settingsSlug: 'site-settings' })] }\n */\nexport function buildPreventDeleteSystemPage(\n args: { roleFields?: string[]; settingsSlug?: string } = {},\n): CollectionBeforeDeleteHook {\n const settingsSlug = args.settingsSlug ?? 'site-settings'\n const roleFields = args.roleFields ?? [\n 'homepage',\n 'privacyPolicy',\n 'cookiePolicy',\n 'termsOfService',\n ]\n\n return async ({ id, req }) => {\n const settings = (await req.payload\n .findGlobal({ slug: settingsSlug as never, depth: 0 })\n .catch(() => null)) as null | Record<string, unknown>\n if (!settings) {return}\n\n for (const field of roleFields) {\n const assigned = settings[field]\n const assignedId =\n assigned && typeof assigned === 'object' ? (assigned as { id?: unknown }).id : assigned\n if (assignedId != null && String(assignedId) === String(id)) {\n throw new APIError(\n `Nie można usunąć strony przypisanej do roli systemowej \"${field}\". ` +\n `Najpierw odłącz rolę w Site Settings.`,\n 400,\n )\n }\n }\n }\n}\n"],"names":["APIError","buildPreventDeleteSystemPage","args","settingsSlug","roleFields","id","req","settings","payload","findGlobal","slug","depth","catch","field","assigned","assignedId","String"],"mappings":"AAEA,SAASA,QAAQ,QAAQ,UAAS;AAElC;;;;;;;;;CASC,GACD,OAAO,SAASC,6BACdC,OAAyD,CAAC,CAAC;IAE3D,MAAMC,eAAeD,KAAKC,YAAY,IAAI;IAC1C,MAAMC,aAAaF,KAAKE,UAAU,IAAI;QACpC;QACA;QACA;QACA;KACD;IAED,OAAO,OAAO,EAAEC,EAAE,EAAEC,GAAG,EAAE;QACvB,MAAMC,WAAY,MAAMD,IAAIE,OAAO,CAChCC,UAAU,CAAC;YAAEC,MAAMP;YAAuBQ,OAAO;QAAE,GACnDC,KAAK,CAAC,IAAM;QACf,IAAI,CAACL,UAAU;YAAC;QAAM;QAEtB,KAAK,MAAMM,SAAST,WAAY;YAC9B,MAAMU,WAAWP,QAAQ,CAACM,MAAM;YAChC,MAAME,aACJD,YAAY,OAAOA,aAAa,WAAW,AAACA,SAA8BT,EAAE,GAAGS;YACjF,IAAIC,cAAc,QAAQC,OAAOD,gBAAgBC,OAAOX,KAAK;gBAC3D,MAAM,IAAIL,SACR,CAAC,wDAAwD,EAAEa,MAAM,GAAG,CAAC,GACnE,CAAC,qCAAqC,CAAC,EACzC;YAEJ;QACF;IACF;AACF"}
+13
View File
@@ -0,0 +1,13 @@
import type { CollectionBeforeChangeHook } from 'payload';
/**
* Sets `publishedAt` to now the first time a document transitions to published,
* if it isn't already set. Saves editors from filling the date manually and
* keeps blog/article dates accurate for Article JSON-LD and sitemaps.
*
* Attach to collections with drafts enabled (blog, articles):
* hooks: { beforeChange: [setPublishedAtHook] }
*
* Only sets on the published transition; never overwrites an existing date
* (an editor can still backdate manually).
*/
export declare const setPublishedAtHook: CollectionBeforeChangeHook;
+19
View File
@@ -0,0 +1,19 @@
/**
* Sets `publishedAt` to now the first time a document transitions to published,
* if it isn't already set. Saves editors from filling the date manually and
* keeps blog/article dates accurate for Article JSON-LD and sitemaps.
*
* Attach to collections with drafts enabled (blog, articles):
* hooks: { beforeChange: [setPublishedAtHook] }
*
* Only sets on the published transition; never overwrites an existing date
* (an editor can still backdate manually).
*/ export const setPublishedAtHook = ({ data, originalDoc })=>{
const becomingPublished = data._status === 'published' && originalDoc?._status !== 'published';
if (becomingPublished && !data.publishedAt) {
data.publishedAt = new Date().toISOString();
}
return data;
};
//# sourceMappingURL=setPublishedAt.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"sources":["../../../src/modules/hooks/setPublishedAt.ts"],"sourcesContent":["import type { CollectionBeforeChangeHook } from 'payload'\n\n/**\n * Sets `publishedAt` to now the first time a document transitions to published,\n * if it isn't already set. Saves editors from filling the date manually and\n * keeps blog/article dates accurate for Article JSON-LD and sitemaps.\n *\n * Attach to collections with drafts enabled (blog, articles):\n * hooks: { beforeChange: [setPublishedAtHook] }\n *\n * Only sets on the published transition; never overwrites an existing date\n * (an editor can still backdate manually).\n */\nexport const setPublishedAtHook: CollectionBeforeChangeHook = ({ data, originalDoc }) => {\n const becomingPublished = data._status === 'published' && originalDoc?._status !== 'published'\n if (becomingPublished && !data.publishedAt) {\n data.publishedAt = new Date().toISOString()\n }\n return data\n}\n"],"names":["setPublishedAtHook","data","originalDoc","becomingPublished","_status","publishedAt","Date","toISOString"],"mappings":"AAEA;;;;;;;;;;CAUC,GACD,OAAO,MAAMA,qBAAiD,CAAC,EAAEC,IAAI,EAAEC,WAAW,EAAE;IAClF,MAAMC,oBAAoBF,KAAKG,OAAO,KAAK,eAAeF,aAAaE,YAAY;IACnF,IAAID,qBAAqB,CAACF,KAAKI,WAAW,EAAE;QAC1CJ,KAAKI,WAAW,GAAG,IAAIC,OAAOC,WAAW;IAC3C;IACA,OAAON;AACT,EAAC"}
+17
View File
@@ -0,0 +1,17 @@
import type { CollectionBeforeChangeHook } from 'payload';
/**
* When a document's slug changes, appends the OLD slug to a `slugHistory` array
* field. The project reads slugHistory to serve a 301 redirect from old URLs to
* the current one — so changing a slug doesn't 404 the old address (a real SEO
* loss / audit finding).
*
* Requires a `slugHistory` field on the collection:
* { name: 'slugHistory', type: 'array', fields: [{ name: 'slug', type: 'text' }],
* admin: { readOnly: true } }
*
* hooks: { beforeChange: [trackSlugHistoryHook] }
*
* The project then, in resolveRoute or a redirect check, looks up slugHistory and
* 301s to the current slug. See docs/hooks.md.
*/
export declare const trackSlugHistoryHook: CollectionBeforeChangeHook;
+33
View File
@@ -0,0 +1,33 @@
/**
* When a document's slug changes, appends the OLD slug to a `slugHistory` array
* field. The project reads slugHistory to serve a 301 redirect from old URLs to
* the current one — so changing a slug doesn't 404 the old address (a real SEO
* loss / audit finding).
*
* Requires a `slugHistory` field on the collection:
* { name: 'slugHistory', type: 'array', fields: [{ name: 'slug', type: 'text' }],
* admin: { readOnly: true } }
*
* hooks: { beforeChange: [trackSlugHistoryHook] }
*
* The project then, in resolveRoute or a redirect check, looks up slugHistory and
* 301s to the current slug. See docs/hooks.md.
*/ export const trackSlugHistoryHook = ({ data, originalDoc })=>{
const oldSlug = originalDoc?.slug;
const newSlug = data.slug;
if (typeof oldSlug === 'string' && typeof newSlug === 'string' && oldSlug !== newSlug && oldSlug.length > 0) {
const history = Array.isArray(data.slugHistory) ? data.slugHistory : Array.isArray(originalDoc?.slugHistory) ? originalDoc.slugHistory : [];
// Avoid duplicates; don't record the new slug itself.
if (!history.some((h)=>h?.slug === oldSlug)) {
data.slugHistory = [
...history,
{
slug: oldSlug
}
];
}
}
return data;
};
//# sourceMappingURL=trackSlugHistory.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"sources":["../../../src/modules/hooks/trackSlugHistory.ts"],"sourcesContent":["import type { CollectionBeforeChangeHook } from 'payload'\n\n/**\n * When a document's slug changes, appends the OLD slug to a `slugHistory` array\n * field. The project reads slugHistory to serve a 301 redirect from old URLs to\n * the current one — so changing a slug doesn't 404 the old address (a real SEO\n * loss / audit finding).\n *\n * Requires a `slugHistory` field on the collection:\n * { name: 'slugHistory', type: 'array', fields: [{ name: 'slug', type: 'text' }],\n * admin: { readOnly: true } }\n *\n * hooks: { beforeChange: [trackSlugHistoryHook] }\n *\n * The project then, in resolveRoute or a redirect check, looks up slugHistory and\n * 301s to the current slug. See docs/hooks.md.\n */\nexport const trackSlugHistoryHook: CollectionBeforeChangeHook = ({ data, originalDoc }) => {\n const oldSlug = originalDoc?.slug\n const newSlug = data.slug\n if (\n typeof oldSlug === 'string' &&\n typeof newSlug === 'string' &&\n oldSlug !== newSlug &&\n oldSlug.length > 0\n ) {\n const history: Array<{ slug: string }> = Array.isArray(data.slugHistory)\n ? data.slugHistory\n : Array.isArray(originalDoc?.slugHistory)\n ? originalDoc.slugHistory\n : []\n // Avoid duplicates; don't record the new slug itself.\n if (!history.some((h) => h?.slug === oldSlug)) {\n data.slugHistory = [...history, { slug: oldSlug }]\n }\n }\n return data\n}\n"],"names":["trackSlugHistoryHook","data","originalDoc","oldSlug","slug","newSlug","length","history","Array","isArray","slugHistory","some","h"],"mappings":"AAEA;;;;;;;;;;;;;;CAcC,GACD,OAAO,MAAMA,uBAAmD,CAAC,EAAEC,IAAI,EAAEC,WAAW,EAAE;IACpF,MAAMC,UAAUD,aAAaE;IAC7B,MAAMC,UAAUJ,KAAKG,IAAI;IACzB,IACE,OAAOD,YAAY,YACnB,OAAOE,YAAY,YACnBF,YAAYE,WACZF,QAAQG,MAAM,GAAG,GACjB;QACA,MAAMC,UAAmCC,MAAMC,OAAO,CAACR,KAAKS,WAAW,IACnET,KAAKS,WAAW,GAChBF,MAAMC,OAAO,CAACP,aAAaQ,eACzBR,YAAYQ,WAAW,GACvB,EAAE;QACR,sDAAsD;QACtD,IAAI,CAACH,QAAQI,IAAI,CAAC,CAACC,IAAMA,GAAGR,SAASD,UAAU;YAC7CF,KAAKS,WAAW,GAAG;mBAAIH;gBAAS;oBAAEH,MAAMD;gBAAQ;aAAE;QACpD;IACF;IACA,OAAOF;AACT,EAAC"}
+16
View File
@@ -0,0 +1,16 @@
import type { FieldHook } from 'payload';
/**
* Field hook for a System Page role relationship in SiteSettings: ensures a page
* isn't assigned to two roles at once (e.g. the same page as both homepage and
* privacyPolicy), which would make routing ambiguous.
*
* Attach to each role field's beforeValidate. `siblingFields` are the OTHER role
* field names to check against.
*
* hooks: { beforeValidate: [buildValidateUniqueRole({
* siblingFields: ['privacyPolicy', 'cookiePolicy', 'termsOfService'],
* })] }
*/
export declare function buildValidateUniqueRole(args: {
siblingFields: string[];
}): FieldHook;
+31
View File
@@ -0,0 +1,31 @@
import { APIError } from 'payload';
/**
* Field hook for a System Page role relationship in SiteSettings: ensures a page
* isn't assigned to two roles at once (e.g. the same page as both homepage and
* privacyPolicy), which would make routing ambiguous.
*
* Attach to each role field's beforeValidate. `siblingFields` are the OTHER role
* field names to check against.
*
* hooks: { beforeValidate: [buildValidateUniqueRole({
* siblingFields: ['privacyPolicy', 'cookiePolicy', 'termsOfService'],
* })] }
*/ export function buildValidateUniqueRole(args) {
return ({ field, siblingData, value })=>{
if (value == null) {
return value;
}
const thisId = typeof value === 'object' ? value.id : value;
for (const sibling of args.siblingFields){
const other = siblingData?.[sibling];
const otherId = other && typeof other === 'object' ? other.id : other;
if (otherId != null && String(otherId) === String(thisId)) {
const name = typeof field === 'object' && 'name' in field ? field.name : 'ta rola';
throw new APIError(`Ta sama strona jest przypisana do "${name}" i "${sibling}". ` + `Każda rola systemowa musi wskazywać inną stronę.`, 400);
}
}
return value;
};
}
//# sourceMappingURL=validateUniqueRole.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"sources":["../../../src/modules/hooks/validateUniqueRole.ts"],"sourcesContent":["import type { FieldHook } from 'payload'\n\nimport { APIError } from 'payload'\n\n/**\n * Field hook for a System Page role relationship in SiteSettings: ensures a page\n * isn't assigned to two roles at once (e.g. the same page as both homepage and\n * privacyPolicy), which would make routing ambiguous.\n *\n * Attach to each role field's beforeValidate. `siblingFields` are the OTHER role\n * field names to check against.\n *\n * hooks: { beforeValidate: [buildValidateUniqueRole({\n * siblingFields: ['privacyPolicy', 'cookiePolicy', 'termsOfService'],\n * })] }\n */\nexport function buildValidateUniqueRole(args: { siblingFields: string[] }): FieldHook {\n return ({ field, siblingData, value }) => {\n if (value == null) {return value}\n const thisId = typeof value === 'object' ? (value as { id?: unknown }).id : value\n for (const sibling of args.siblingFields) {\n const other = (siblingData as Record<string, unknown>)?.[sibling]\n const otherId = other && typeof other === 'object' ? (other as { id?: unknown }).id : other\n if (otherId != null && String(otherId) === String(thisId)) {\n const name = typeof field === 'object' && 'name' in field ? field.name : 'ta rola'\n throw new APIError(\n `Ta sama strona jest przypisana do \"${name}\" i \"${sibling}\". ` +\n `Każda rola systemowa musi wskazywać inną stronę.`,\n 400,\n )\n }\n }\n return value\n }\n}\n"],"names":["APIError","buildValidateUniqueRole","args","field","siblingData","value","thisId","id","sibling","siblingFields","other","otherId","String","name"],"mappings":"AAEA,SAASA,QAAQ,QAAQ,UAAS;AAElC;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,wBAAwBC,IAAiC;IACvE,OAAO,CAAC,EAAEC,KAAK,EAAEC,WAAW,EAAEC,KAAK,EAAE;QACnC,IAAIA,SAAS,MAAM;YAAC,OAAOA;QAAK;QAChC,MAAMC,SAAS,OAAOD,UAAU,WAAW,AAACA,MAA2BE,EAAE,GAAGF;QAC5E,KAAK,MAAMG,WAAWN,KAAKO,aAAa,CAAE;YACxC,MAAMC,QAASN,aAAyC,CAACI,QAAQ;YACjE,MAAMG,UAAUD,SAAS,OAAOA,UAAU,WAAW,AAACA,MAA2BH,EAAE,GAAGG;YACtF,IAAIC,WAAW,QAAQC,OAAOD,aAAaC,OAAON,SAAS;gBACzD,MAAMO,OAAO,OAAOV,UAAU,YAAY,UAAUA,QAAQA,MAAMU,IAAI,GAAG;gBACzE,MAAM,IAAIb,SACR,CAAC,mCAAmC,EAAEa,KAAK,KAAK,EAAEL,QAAQ,GAAG,CAAC,GAC5D,CAAC,gDAAgD,CAAC,EACpD;YAEJ;QACF;QACA,OAAOH;IACT;AACF"}