import type { CreateItemOp, FieldValue, Operation, SetBaseTemplatesOp, SetFieldOp, SetStandardValuesOp } from "../ir/operations.js"; import { type MediaFallback } from "../api/ref-encoding.js"; import type { AuthoringApiClient, ItemSelector, RemoteFieldValue, RemoteItem, UpdateItemInput } from "../api/client.js"; export declare const lookupField: (remote: RemoteItem, fieldId: string, fieldName: string | undefined, language: string | undefined, version: number | undefined) => RemoteFieldValue | undefined; /** Resolve every recipe-ref / source-prefix in a field value list. */ export declare const resolveAll: (fields: FieldValue[], capturedItemIds: ReadonlyMap, mediaFallbacks?: ReadonlyMap) => FieldValue[]; /** * Build an `UpdateItemInput`, lifting a uniform field `language` / `version` * to the input level. The Authoring API writes every `FieldValueInput` at * the input's language/version — per-field language/version is not on the * wire — so a `SetField` targeting a non-default language or a story-seed * numbered version must surface it here. When the fields disagree (or carry * none) the level is left unset and the write lands on the item's default * language / latest version. */ export declare const toUpdateItemInput: (itemId: string, fields: FieldValue[]) => UpdateItemInput; /** * Resolve a CreateItem op's parent ref to a Sitecore itemId. * * Sitecore's `createItem` requires `parent: ID!` (a GUID). Path-only * parents (`ref-path`) are looked up via `getItem({ path })` and cached * in `capturedItemIds` keyed by the path string itself. Recipe-internal * `ref-recipe` parents resolve via the same map keyed by their refKey, * populated as parent ops apply. * * Returns `unresolvedRefKey` only when a `ref-recipe` parent's CreateItem * has not yet captured an itemId (plan-mode against an empty tenant * before applies happen). */ export declare const resolveCreateItemParent: (op: CreateItemOp, capturedItemIds: ReadonlyMap) => { resolved: string; } | { unresolvedRefKey: string; }; /** * Strict variant of parent resolution for the plan-time sibling-name * fallback. Unlike `resolveCreateItemParent` (which has plan-mode-friendly * path-string fallbacks), this returns an itemId ONLY when one is * actually in the captured map — so the caller can safely pass the * result to `getChildren({ itemId })` without risk of feeding it a path. */ export declare const resolveParentItemIdForFallback: (op: CreateItemOp, capturedItemIds: ReadonlyMap) => string | null; /** * Key a CreateItem op by the parent it creates under — the same resolution * `resolveParentItemIdForFallback` uses, but as the IR-level identity rather * than the live itemId (available before anything is captured). */ export declare const createItemParentKey: (op: CreateItemOp) => string; /** * Every item NAME this push's CreateItem ops claim, grouped by parent. * * The sibling-rename fallback needs this to tell "a CMS user renamed my item" * apart from "that item belongs to a DIFFERENT op in this same push". * See {@link findCreateItemSibling}. */ export declare const buildSiblingCreateNames: (operations: readonly Operation[]) => Map>; /** * RefKeys of items this push writes FIELDS to via separate SetField ops. * * Content-item IRs seed their fields as standalone `SetField` ops rather * than on the CreateItem — so the create itself carries only the `Scai * Handle` marker and looks fieldless. Convergence eligibility keyed * purely on the create's own fields therefore missed the entire * content-item class (the blank-environment batch-9 aborts: the create * skipped as "already exists", and the SetField ops then hit a * wrong-template twin with "Cannot find a field with the name "). * A create whose refKey appears here WILL write fields downstream, so it * needs apply-time convergence exactly as much as one with inline fields. */ export declare const buildFieldTargetRefKeys: (operations: readonly Operation[]) => Set; /** * True when the op creates an item conforming to a known FOLDER-class * built-in template (see {@link FOLDER_CLASS_TEMPLATE_IDS}). Only the * string-constant form can match — a recipe-created template's refKey or * a `ref-path` templateOf is never folder-class. */ export declare const isFolderClassCreate: (op: CreateItemOp) => boolean; /** * The expected LIVE template a CreateItem op's adopted name-twin must be * retemplated to, or `null` when adopt-and-retemplate doesn't apply. * * Eligibility mirrors the failure class it heals — recipe-SEEDED items * (content items, page items) whose deterministic path can collide with * a twin stranded by an earlier partial/rolled-back install: * * - `CreateOnly` policy only. CreateAndUpdate structure ops * (templates, sections, renderings, enumerations, dictionaries) * live under physically site-scoped subtrees and keep their * existing drift-update behavior. * - Not folder-class: organisational folders keep the v0.33.0 * lossless adopt-as-is behavior — retemplating them is * unnecessary (no authored data) and could clobber SXA grouping * templates. * - Seeds AUTHORED fields — at least one field beyond the injected * `Scai Handle` marker. This is the positive signal for the * failure class: adoption only breaks when the recipe writes * field values the twin's live template can't resolve. Ops with * no authored fields (recipe-created GROUPING folders — e.g. * `enumerations-grouping-folder`, whose custom folder templates * the built-in folder-class set can't enumerate, and whose * cross-seed twins carry a different site-family template GUID by * construction) adopt as-is untouched, exactly as v0.33.0/0.34.0 * did. Without this, v0.34.1 retemplated the `Enumerations/Card` * grouping folder on repeat installs and aborted batch-1. * NOTE deliberately ABSENT from this list: plan-time resolution of the * expected live template. Batch-separated pushes (the orchestrator's * content batches) reference datasource templates whose recipes live in * EARLIER batches, so `resolveLiveTemplateIdForRebind` returns null for * them — and requiring it here silently disabled convergence for every * content batch (0.34.3: batch-9 nav-item/footer-link field ops kept * aborting with "Cannot find a field with the name "). Eligibility * is plan-local; the authoritative template compare happens at APPLY * time in `adoptExistingChild`, against the create mutation's resolved * `templateId` (which the mutation must carry regardless). */ export declare const convergenceEligible: (op: CreateItemOp, fieldTargetRefKeys?: ReadonlySet) => boolean; /** * The versionless base of a `Scai Handle` marker (`nav-item-about@1` → * `nav-item-about`). Re-versioned recipes (`@1` → `@2`) still own their * item — only a DIFFERENT recipe family is foreign. */ export declare const markerHandleBase: (handle: string) => string; /** * Whether a `Scai Handle` marker is a synthetic cross-recipe AGGREGATE * handle (`__enumeration-templates__`, `__shared-data-folders__`, …) — * the `__…__` convention `compile/aggregates.ts` mints for shared items * a whole recipe SET co-owns. Concrete recipe handles never take this * form (`action-placement@1`, `hero@1`). Used by the ownership-collision * guard to recognise the pre-aggregate → aggregate ownership migration. */ export declare const isSyntheticAggregateHandle: (handle: string) => boolean; /** * The live templateId a rebind candidate must carry, or `null` when it * can't be known — in which case the template check is skipped (the * pre-check-era behavior). * * Only `capturedItemIds` hits are trusted: the workspace push seeds every * cross-recipe template refKey to its LIVE itemId before planning * (`seedCrossRecipeRefs`), and same-recipe template creates capture on * apply. An unresolved string `templateOf` is ambiguous — it may be a * Sitecore built-in constant (live) or a deterministic refKey whose * template item simply wasn't looked up (not live) — so it can't be * compared against a candidate's live templateId without producing false * mismatches that would break the rename fallback outright. */ export declare const resolveLiveTemplateIdForRebind: (op: CreateItemOp, capturedItemIds: ReadonlyMap) => string | null; /** * The `Scai Handle` recipe-identity marker `injectHandleMarker` stamped on a * CreateItem op, or `undefined` for an op that carries none (e.g. an IR that * never went through `injectHandleMarker`). */ export declare const opHandleMarker: (op: CreateItemOp) => string | undefined; /** * The `Scai Handle` marker value that the item OWNS, or `undefined` when * unmarked. * * The marker is a SHARED field, so a component template that carries the * marker on its `__Standard Values` makes every datasource item built on * that template INHERIT the component's handle. An inherited marker is NOT * ownership — reading it as such makes a page's scoped-datasource op collide * with the component whose template it conforms to ("owned by 'hero@1', not * 'sync-home@1'"). So an inherited value (`containsStandardValue`) counts as * unmarked. Fields predating the flag (mocks, older reads) omit it and are * treated as own values, preserving prior behavior. */ export declare const remoteHandleMarker: (item: RemoteItem) => string | undefined; /** * Resolve a CreateItem op's templateOf to a Sitecore item ID. * * - String form: usually a constant Sitecore built-in GUID. If it * matches a refKey captured during this push (e.g. SV item under * a recipe-created template), resolve to the captured itemId. * - `{kind: "ref-path"}` form: late-resolved against a content-tree * path. The push pipeline seeds `crossRecipeRefs[templatePathRefKey(path)] = path`; * the executor's `getItemsByPaths` batch lookup populates * `capturedItemIds` before planning starts. A miss here means the * template item doesn't exist on the tenant — planner skips with a * clear reason rather than letting the upstream createItem throw. */ export declare const resolveTemplateOf: (op: CreateItemOp, capturedItemIds: ReadonlyMap) => { resolved: string; } | { unresolvedRefKey: string; reason?: string; }; export declare const setFieldDesired: (op: SetFieldOp) => FieldValue[]; export declare const setBaseTemplatesDesired: (op: SetBaseTemplatesOp, effectiveBaseTemplates: readonly string[]) => FieldValue[]; /** * Resolve a SetBaseTemplates op's effective base list: the static * `baseTemplates` plus, per `pathBases` entry, either the live item found * at the tenant path or that entry's compile-time fallbacks. Deduped so * a fallback GUID that also appears statically isn't written twice. */ export declare const resolveEffectiveBaseTemplates: (op: SetBaseTemplatesOp, readByPath: (path: string) => Promise) => Promise; export declare const setStandardValuesDesired: (op: SetStandardValuesOp) => FieldValue[]; /** * Compute the lookup selector for a given op. CreateItem looks up by * path; update-style ops look up by the captured itemId for their target * refKey. Returns `null` when the captured map doesn't have the refKey * yet — that signals the planner to skip. */ export declare const lookupSelector: (op: Operation, capturedItemIds: ReadonlyMap) => ItemSelector | null; /** * Parse a Sitecore multi-list field value (pipe-separated GUIDs, each * either bare or curly-wrapped) into a normalised lowercase, no-curly * GUID set. Tolerates extra whitespace / empty entries from operator * edits. */ export declare const parseMultiList: (value: string | null | undefined) => string[]; export declare const formatMultiList: (guids: readonly string[]) => string; /** * Plan an `AddItemVersion` op. Reads the target item's current versions in * `op.language` and emits an `addItemVersion` mutation only when the * declared `version` doesn't exist yet — so a re-push of a story-seed * recipe is an all-`skip` no-op once the version stack is materialised. * * `addCount` is `op.version - currentMax`: the executor adds that many * versions (Sitecore assigns the numbers sequentially). When `language` has * no versions yet `currentMax` is 0, and adding version 1 also creates the * language version. */ /** * Resolve the current max version of `(item, language)` for * `planAddItemVersion`. With a `versionStackCache`, the first read of an * item fetches its stacks for EVERY language the IR adds to it * (`languagesHint`) in one `getItemPerLanguageBatch` call — a 9-locale * item's version reconciliation costs 1 round trip, not 9 — and the * executor's write-through keeps the stacks current across ops. Without * a cache, fall back to the historical per-op `getItemVersions` read. */ export declare const readCurrentMaxVersion: (client: AuthoringApiClient, itemId: string, language: string, versionStackCache: Map> | undefined, languagesHint: readonly string[] | undefined) => Promise; /** Per-fetch timeout for external-URL media byte sourcing. */ export declare const MEDIA_FETCH_TIMEOUT_MS = 15000; /** Response-size cap for external-URL media byte sourcing. */ export declare const MEDIA_FETCH_MAX_BYTES: number; /** * SSRF hygiene for external-URL media fetches: recipe files can come * from third-party registries, so refuse loopback / RFC1918 / * link-local / unique-local hosts. Literal-hostname checks only — no * DNS resolution (a resolver-based guard is still TOCTOU-racy; runners * that need stronger isolation put an egress proxy in front). */ export declare const isPrivateMediaHost: (url: URL) => boolean;