@strifeapp/strife 1.9.0 → 1.9.2

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.
@@ -1,3 +1,3 @@
1
1
  // AUTO-GENERATED by scripts/gen-content-index.mjs from data/ravendb/localized-content-index.js.
2
2
  // Do not edit by hand — re-run `npm run build`.
3
- export const source = "/**\n * -----------------------------------------------------------------------------\n * STRIFE CONTENT INDEX — L10N + StrifeUri + Template-Aware Relations\n * -----------------------------------------------------------------------------\n * What this does\n * Projects Strife CMS documents into a query‑friendly, localized, relation‑expanded\n * shape with one entry per locale. It keeps a deterministic output and a\n * centralized storage policy (Corax‑safe).\n * Cycle safety and performance caching.\n *\n * Canonical root fields (unchanged semantics)\n * - id, locale, displayName, url, origin, collection\n * - publishedAt, createdAt, changedAt, dependencies, labels, deleted\n *\n * Labels\n * - `labels` carries ONE entry per label, live and unfolded. A validly grouped\n * label resolves to \"<group.name>/<label.name>\" using the REAL display names\n * (no slugs, no folding, e.g. \"Färger/Blå\") — it does NOT also contribute a\n * separate bare entry. Ungrouped labels (and anything that disqualifies a\n * label from being validly grouped: deleted, ETL-sourced, dangling/deleted\n * group, empty name on either side) fall back to the bare name. Computed\n * fresh every index run, never frozen — Strife's query pattern is always\n * fetch-then-query (read a live value, use it immediately), so there is no\n * hardcoded-name staleness risk to guard against. The array is deduped as a\n * whole (label Name uniqueness isn't server-enforced, so two labels can\n * resolve to the identical string). See buildLabelTerms.\n * - Root-level custom fields of editor type 'label' resolve to the SAME\n * single-entry array, under their own property name, via the identical\n * buildLabelTerms call. Not supported when nested inside chapters/content-template\n * (populateByShape has no editor-type awareness — see its own comment).\n *\n * Root template (whitelist + localizable)\n * - Root fields are populated from the document's template. Scalars marked\n * localizable are translated using the current entry's locale and stored.\n * - Non‑localizable scalars are index‑only at root.\n * - Arrays/objects (relations, chapters, content objects) are stored.\n * - Reserved root names can never be redefined by editors.\n *\n * Relations (template‑driven for referenced docs)\n * - Relations are detected by value shape (array of GUIDs or { id: GUID }).\n * - For each referenced GUID, the target document is loaded and projected with\n * its template (resolved by collection), using the ROOT entry's locale for\n * any localizable scalars.\n * - Order is preserved. Only GUIDs are followed. Cycles are prevented via a\n * visit cache.\n *\n * Nested content by shape (no template loads)\n * - Chapters: arrays of small objects; expanded recursively by shape.\n * - Content‑template (object): expanded recursively by shape.\n * - While expanding by shape, if a field is a relation array by shape, those\n * relations are projected via templates (same as above). Otherwise, values\n * are copied through. No per‑field localization occurs in shape expansion.\n *\n * StrifeUri\n * - Form: strife://<docId>.<collection>.<db>/<path>\n * - Resolution: loads the target doc and walks the path; the first path\n * segment is localized with the ROOT entry's locale. The resolved value can be:\n * • a scalar → copied as is\n * • an object/array → expanded by shape\n * • a relation array → projected via templates\n *\n * Storage policy (centralized at root copy step)\n * - Arrays/objects → storeAs\n * - Scalars localizable at root → storeAs (so projections return translated strings)\n * - Scalars not localizable → indexAs (leaner index)\n * - Root url is always stored\n * - Keep per‑field storage/analyzer consistent across documents (Corax‑safe)\n *\n * Caching & cycle safety\n * - docCache: documents, tplCache: templates, labelCache: labels\n * - visit cache prevents cycles and provides memoization per entry\n * - URL builder protects against origin cycles via a local visited set\n *\n * Localization model\n * - Emits one entry per configured locale.\n * - Root scalars marked localizable are translated and stored.\n * - Referenced docs projected via templates also translate localizable scalars\n * using the ROOT locale. displayName is treated as non‑localized.\n * - Shape expansion itself does not localize values, except StrifeUri's first\n * path segment at dereference time.\n *\n * Collection mapping\n * - This index must map BOTH 'Contents' AND 'Drafts' collections.\n * - Draft entries are emitted with draft: true so consumers can filter.\n * - When building URLs for draft entries, the origin chain falls back to\n * parent draft documents when the published parent lacks the locale slug.\n *\n * Determinism & engine constraints\n * - ES5‑only (no for..of, no arrow functions).\n * - Deterministic shapes under the same inputs.\n * - Relation depth is capped at MAX_RELATION_DEPTH hops; cycles are still safe.\n * -----------------------------------------------------------------------------\n */\n\n\nvar MAX_RELATION_DEPTH = 1;\n\nvar RESERVED_ROOT_FIELDS = {\n url: true,\n collection: true,\n publishedAt: true,\n createdAt: true,\n changedAt: true,\n labels: true,\n locale: true,\n origin: true,\n id: true,\n docId: true,\n displayName: true,\n dependencies: true,\n deleted: true,\n draft: true,\n status: true\n};\n\nfunction storeAs(name, value) {\n return { $value: value, $name: name, $options: { storage: true } };\n}\n\nfunction indexAs(name, value) {\n return { $value: value, $name: name, $options: { storage: false } };\n}\n\n// ES5 endsWith (Jint engine — no String.prototype.endsWith reliance).\nfunction strEndsWith(s, suffix) {\n return typeof s === 'string' && s.length >= suffix.length &&\n s.substring(s.length - suffix.length) === suffix;\n}\n\nfunction loadDocCached(id, docCache) {\n if (!id) return null;\n var key = id;\n if (docCache.has(key)) return docCache.get(key);\n var d = load(id, '@all_docs');\n if (d) docCache.set(key, d);\n return d;\n}\n\nfunction loadTemplateByCollectionCached(templateId, tplCache) {\n if (!templateId) return null;\n if (tplCache.has(templateId)) return tplCache.get(templateId);\n var t = load(templateId, 'templates');\n if (t) tplCache.set(templateId, t);\n return t;\n}\n\nfunction loadTemplateByIdCached(templateId, tplCache) {\n if (!templateId) return null;\n var key = templateId;\n if (tplCache.has(key)) return tplCache.get(key);\n var t = load(templateId, 'templates');\n if (t) tplCache.set(key, t);\n return t;\n}\n\nfunction loadLocalizationSettings() {\n return load('configurations/localization', 'configurations');\n}\n\nfunction loadLabelCached(id, labelCache) {\n if (!id) return null;\n if (labelCache.has(id)) return labelCache.get(id);\n var d = load(id, 'Labels');\n if (d) labelCache.set(id, d);\n return d;\n}\n\nfunction loadGroupCached(id, groupCache) {\n if (!id) return null;\n if (groupCache.has(id)) return groupCache.get(id);\n var d = load(id, 'LabelGroups');\n if (d) groupCache.set(id, d);\n return d;\n}\n\nfunction resolveSlug(slug, locale) {\n if (typeof slug === 'string') return slug; // legacy string format\n if (slug && locale && slug[locale]) return slug[locale]; // locale-keyed object\n return null;\n}\n\n// Checks if obj is a locale-keyed object where ALL keys are configured locales.\n// Uses configured locales (not BCP 47 syntax) to avoid false positives on\n// objects like { \"id\": \"abc\", \"no\": false } where keys happen to be 2-letter strings.\nfunction isLocaleObject(obj, configuredLocales) {\n if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return false;\n var keys = Object.keys(obj);\n if (keys.length === 0) return false;\n for (var i = 0; i < keys.length; i++) {\n if (configuredLocales.indexOf(keys[i]) === -1) return false;\n }\n return true;\n}\n\nfunction buildUrlCached(doc, locale, docCache, isDraft) {\n var visited = {};\n var slugs = [];\n var c = doc;\n do {\n var nodeSlug = resolveSlug(c.slug, locale);\n // Draft URL building: if a published ancestor lacks the locale slug, try its draft\n if (!nodeSlug && isDraft) {\n var nodeId = c['@metadata'] ? c['@metadata']['@id'] : null;\n // Only hop to an ancestor's /draft when this node isn't itself a sibling\n // (id-suffix, collection-agnostic — survives the Drafts collection retirement).\n if (nodeId && !strEndsWith(nodeId, '/draft') && !strEndsWith(nodeId, '/scheduled')) {\n var draftNode = loadDocCached(nodeId + '/draft', docCache);\n if (draftNode) nodeSlug = resolveSlug(draftNode.slug, locale);\n }\n }\n if (nodeSlug) slugs.unshift(nodeSlug);\n if (!c.origin || visited[c.origin.id]) break;\n visited[c.origin.id] = true;\n c = loadDocCached(c.origin.id, docCache);\n } while (c);\n\n // ENG-62: do NOT return slugs.shift() + slugs.join('/').\n // For an empty array that expression evaluates to the literal string \"undefined\"\n // (because [].shift() is undefined and undefined + '' coerces to \"undefined\").\n // For a two-element array it drops the separator (['a','b'] -> \"ab\" instead of \"a/b\").\n // Both behaviors are confirmed in api-test/Integration/Eng62LinkResolutionDiagnosticTests.\n //\n // We also can't just slugs.join('/'): the root home's slug is \"/\" by convention,\n // so ['/', 'page'] would produce \"//page\". Instead, normalize each slug (strip its\n // own leading/trailing slashes so \"/\" becomes \"\" and drops out) and prepend a\n // single leading \"/\" so URLs always start with \"/\".\n if (slugs.length === 0) return null;\n var parts = [];\n for (var si = 0; si < slugs.length; si++) {\n var s = slugs[si];\n if (typeof s !== 'string') continue;\n // Strip leading and trailing slashes from this segment. Reduces \"/\" to \"\"\n // (skipped) and leaves \"foo\" untouched.\n var stripped = s;\n while (stripped.length > 0 && stripped.charAt(0) === '/') stripped = stripped.substring(1);\n while (stripped.length > 0 && stripped.charAt(stripped.length - 1) === '/') stripped = stripped.substring(0, stripped.length - 1);\n if (stripped.length > 0) parts.push(stripped);\n }\n return '/' + parts.join('/');\n}\n\nfunction isLinkValue(val) {\n return val && typeof val === 'object' && !Array.isArray(val) &&\n typeof val.href === 'string' && typeof val.target === 'string' &&\n isGuid(val.id);\n}\n\nfunction resolveLinkHref(val, locale, docCache) {\n var doc = loadDocCached(val.id, docCache);\n if (!doc) return val.href;\n // ENG-62: do NOT use `'slug' in doc` here. RavenDB's Jint wraps loaded docs in a\n // BlittableObjectInstance whose [[HasProperty]] returns true for ANY property name,\n // making `'slug' in doc` always-true dead code. Use the @metadata collection check\n // (proven reliable) and a value-presence check instead.\n // See api-test/Integration/Eng62LinkResolutionDiagnosticTests for proof.\n var collection = doc['@metadata'] && doc['@metadata']['@collection'];\n if (collection === 'Files' || collection === 'Folders') return val.href;\n if (doc.slug == null) return val.href;\n var url = buildUrlCached(doc, locale, docCache);\n if (!url || url === 'undefined') return val.href;\n return url;\n}\n\nfunction resolveLink(val, locale, docCache) {\n return {\n id: val.id,\n text: val.text,\n href: resolveLinkHref(val, locale, docCache),\n target: val.target\n };\n}\n\nfunction resolveHtmlLinks(html, locale, docCache) {\n if (html.indexOf('data-id=\"') === -1) return html;\n\n var guidPat = '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}';\n\n // data-id before href\n var p1 = new RegExp(\n '<a\\\\s([^>]*?)data-id=\"(' + guidPat + ')\"([^>]*?)href=\"([^\"]*)\"([^>]*?)>',\n 'g'\n );\n // ENG-62: same Jint quirk as resolveLinkHref — `'slug' in doc` is dead code on\n // a wrapped loaded doc. Use collection check + value check instead.\n var result = html.replace(p1, function(match, before, id, mid, href, after) {\n var doc = loadDocCached(id, docCache);\n if (!doc) return match;\n var coll = doc['@metadata'] && doc['@metadata']['@collection'];\n if (coll === 'Files' || coll === 'Folders') return match;\n if (doc.slug == null) return match;\n var url = buildUrlCached(doc, locale, docCache);\n if (!url || url === 'undefined') return match;\n return '<a ' + before + 'data-id=\"' + id + '\"' + mid + 'href=\"' + url + '\"' + after + '>';\n });\n\n // href before data-id\n var p2 = new RegExp(\n '<a\\\\s([^>]*?)href=\"([^\"]*)\"([^>]*?)data-id=\"(' + guidPat + ')\"([^>]*?)>',\n 'g'\n );\n result = result.replace(p2, function(match, before, href, mid, id, after) {\n var doc = loadDocCached(id, docCache);\n if (!doc) return match;\n var coll = doc['@metadata'] && doc['@metadata']['@collection'];\n if (coll === 'Files' || coll === 'Folders') return match;\n if (doc.slug == null) return match;\n var url = buildUrlCached(doc, locale, docCache);\n if (!url || url === 'undefined') return match;\n return '<a ' + before + 'href=\"' + url + '\"' + mid + 'data-id=\"' + id + '\"' + after + '>';\n });\n\n return result;\n}\n\nfunction getTranslator(locale, defaultLocale, fallbackToPrimary) {\n return function (obj) {\n if (!obj) return null;\n if (typeof obj === 'object' && obj.hasOwnProperty) {\n if (obj.hasOwnProperty(locale)) return obj[locale];\n // if (fallbackToPrimary && locale && locale.length > 2) {\n // var base = locale.slice(0, 2);\n // if (obj.hasOwnProperty(base)) return obj[base];\n // }\n //return obj[defaultLocale];\n }\n // Return non-objects as-is only for the default locale (handles false→true\n // mismatch where editor.localizable is true but stored value is still a\n // plain scalar). Non-default locales get null — the value was never translated.\n if ((typeof obj === 'string' || typeof obj === 'number') && locale === defaultLocale) return obj;\n return null;\n };\n}\n\nfunction isLocalized(obj, configuredLocales) {\n if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return false;\n if (!obj.hasOwnProperty) return false;\n for (var i = 0; i < configuredLocales.length; i++) {\n if (obj.hasOwnProperty(configuredLocales[i])) return true;\n }\n return false;\n}\n\nfunction isGuid(s) {\n return typeof s === 'string' &&\n /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(s);\n}\nfunction isRefLike(x) {\n // GUID string id, or object with GUID `id`\n if (typeof x === 'string') return isGuid(x);\n if (!x || typeof x !== 'object') return false;\n return typeof x.id === 'string' && isGuid(x.id) && Object.keys(x).length <= 2;\n}\nfunction isRelationArray(v) {\n if (!v || !Array.isArray(v)) return false;\n if (v.length === 0) return false; // empty arrays are ambiguous\n for (var i = 0; i < v.length; i++) {\n if (!isRefLike(v[i])) return false;\n }\n return true;\n}\n\nvar strifeUriPattern = /^strife:\\/\\/(?<id>[-\\w_]*)\\.(?<coll>[\\w_-]*)\\.(?<db>[\\w_-]*)(?<path>[-\\w_./]*)?$/;\nfunction isStrifeUri(value) {\n var match = strifeUriPattern.exec(value || '');\n if (!match) return null;\n try {\n var path = match.groups.path || '';\n var segs = path.length > 1 ? path.slice(1).split('/') : [];\n return {\n original: value,\n db: match.groups.db,\n collection: match.groups.coll,\n docId: match.groups.id, // may be non-GUID by design for StrifeUri\n segments: segs\n };\n } catch (e) {\n return null;\n }\n}\n\nfunction tryGetReferencedValue(strifeUri, populationContext, docCache) {\n var document = loadDocCached(strifeUri.docId, docCache);\n if (!document) return null;\n\n var val = document;\n if (strifeUri.segments.length > 0) {\n // first segment may be localized; use isLocalized to distinguish locale-keyed objects\n var first = strifeUri.segments[0];\n var rawFirst = val[first];\n if (populationContext.isLocalized(rawFirst)) {\n val = populationContext.translate(rawFirst);\n } else {\n val = rawFirst;\n }\n for (var i = 1; i < strifeUri.segments.length; i++) {\n if (val == null) break;\n var segment = strifeUri.segments[i];\n val = val[segment];\n }\n }\n return val;\n}\n\nfunction resolveStrifeUriIfAny(value, populationContext, docCache) {\n var maybeUri = isStrifeUri(value);\n return maybeUri ? tryGetReferencedValue(maybeUri, populationContext, docCache) : value;\n}\n\nfunction loadRelatedLabels(labelIdsArray, labelCache) {\n if (!labelIdsArray || !labelIdsArray.length) return null;\n var out = [];\n for (var i = 0; i < labelIdsArray.length; i++) {\n var lid = labelIdsArray[i];\n var d = loadLabelCached(lid, labelCache);\n if (d) out.push(d);\n }\n return out;\n}\n\n// STR-2099 (revised, single-entry): builds the consolidated `labels` array — ONE\n// entry per label, from the SAME loaded label docs `labels` has always used (no\n// duplicate load() calls). A label is VALIDLY GROUPED when: non-deleted, not\n// ETL-sourced (D9 — the admin UI never offers grouping for ETL labels, so a\n// groupId there is defensively ignored), has a groupId whose target group exists\n// and is non-deleted, and both group.name and label.name are non-empty. A validly\n// grouped label resolves to \"<group.name>/<label.name>\" — the REAL display names,\n// live, never slugified or frozen (Strife's query pattern is always\n// fetch-then-query, so a live value can never be \"stale\" the way a hardcoded one\n// could). Anything that disqualifies a label from being validly grouped falls back\n// to its bare name — same as a genuinely ungrouped label (R11: grouping is\n// opt-in, nothing is ever omitted from the index over a grouping problem).\n// Deduped as a whole: Name uniqueness is a client-side pre-check only (Label\n// /validity), never enforced by Post/Put, so two labels can resolve to the\n// identical string and would otherwise double an entry.\nfunction buildLabelTerms(labelsArray, groupCache) {\n var seen = new Map();\n var terms = [];\n\n function pushUnique(term) {\n if (seen.has(term)) return;\n seen.set(term, true);\n terms.push(term);\n }\n\n if (!labelsArray) return terms;\n for (var i = 0; i < labelsArray.length; i++) {\n var label = labelsArray[i];\n if (!label) continue;\n\n var resolved = label.name;\n var isEtl = label['@metadata'] && label['@metadata'].external === true;\n if (!label.deleted && !isEtl && label.groupId) {\n var group = loadGroupCached(label.groupId, groupCache);\n if (group && !group.deleted && group.name && label.name) {\n resolved = group.name + '/' + label.name;\n }\n }\n pushUnique(resolved);\n }\n return terms;\n}\n\nfunction getVisitEntry(visit, id) {\n var e = visit.get(id);\n if (!e) { e = { state: 'done', node: null }; visit.set(id, e); }\n return e;\n}\n\nfunction isVisiting(visit, id) {\n var e = visit.get(id);\n return e ? e.state === 'visiting' : false;\n}\n\nfunction markVisiting(visit, id) {\n var e = getVisitEntry(visit, id);\n e.state = 'visiting';\n}\n\nfunction markDone(visit, id) {\n var e = getVisitEntry(visit, id);\n e.state = 'done';\n}\n\nfunction getCachedNode(visit, id) {\n var e = visit.get(id);\n return e && e.node ? e.node : null;\n}\n\nfunction setCachedNode(visit, id, node) {\n var e = getVisitEntry(visit, id);\n e.node = node;\n}\n\nfunction isContentTemplateNode(v) {\n return v && typeof v === 'object' && !Array.isArray(v) &&\n v['@strife'] && v['@strife'].template;\n}\n\nfunction isChaptersArray(v) {\n if (!v || !Array.isArray(v)) return false;\n if (v.length === 0) return false;\n for (var i = 0; i < v.length; i++) {\n var it = v[i];\n if (!it || !it['@strife'] || !it['@strife'].template) return false;\n }\n return true;\n}\n\n// Shape-only — deliberately has NO template/editor-type awareness (see the file\n// header). A `label`-type custom field nested in here has the identical raw shape\n// to a relation array (both are just arrays of GUIDs) and there is no way to tell\n// them apart without a template lookup, so it falls through to the relation branch\n// below and is NOT resolved as a label. Known, accepted limitation — root-level\n// custom fields only; see populateValuesByEditor for the field-level resolution\n// that DOES apply.\nfunction populateByShape(document, out, populationContext, visit,\n docCache, tplCache, labelCache, groupCache, hopsLeft) {\n if (!document || typeof document !== 'object') return;\n\n for (var prop in document) {\n if (!document || !document.hasOwnProperty || !document.hasOwnProperty(prop)) continue;\n\n // Keep metadata decorations as-is\n if (prop.charAt(0) === '@') { out[prop] = document[prop]; continue; }\n\n var val = document[prop];\n // StrifeUri resolution\n val = resolveStrifeUriIfAny(val, populationContext, docCache);\n\n // Chapters\n if (isChaptersArray(val)) {\n var arr = [];\n for (var ci = 0; ci < val.length; ci++) {\n var chapter = val[ci];\n if (chapter && chapter['@strife'] && chapter['@strife'].template) {\n var child = {};\n populateByShape(chapter, child, populationContext, visit,\n docCache, tplCache, labelCache, groupCache, hopsLeft);\n arr.push(child);\n }\n }\n out[prop] = arr;\n continue;\n }\n\n // Content-template node\n if (isContentTemplateNode(val)) {\n var nested = {};\n populateByShape(val, nested, populationContext, visit,\n docCache, tplCache, labelCache, groupCache, hopsLeft);\n out[prop] = nested;\n continue;\n }\n\n // Relations (GUID-strict)\n if (isRelationArray(val)) {\n var related = projectRelationsIfAny(val, populationContext, visit, docCache, tplCache, labelCache, groupCache, hopsLeft);\n if (related) { out[prop] = related; continue; }\n }\n\n // Link resolution\n if (isLinkValue(val)) {\n out[prop] = resolveLink(val, populationContext.locale, docCache);\n continue;\n }\n if (Array.isArray(val) && val.length > 0 && isLinkValue(val[0])) {\n var linkArr = [];\n for (var lki = 0; lki < val.length; lki++) {\n linkArr.push(isLinkValue(val[lki]) ? resolveLink(val[lki], populationContext.locale, docCache) : val[lki]);\n }\n out[prop] = linkArr;\n continue;\n }\n\n // HTML link resolution (rich text fields)\n if (typeof val === 'string' && val.indexOf('data-id=\"') !== -1) {\n out[prop] = resolveHtmlLinks(val, populationContext.locale, docCache);\n continue;\n }\n\n // Default: copy-through (no localization inside nested)\n out[prop] = val;\n }\n\n if (document['@strife'] && !out['@strife']) out['@strife'] = document['@strife'];\n}\n\nfunction projectRelationsIfAny(value, populationContext, visit, docCache, tplCache, labelCache, groupCache, hopsLeft) {\n if (!isRelationArray(value)) return null;\n if (hopsLeft <= 0) return [];\n var refs = [];\n for (var i = 0; i < value.length; i++) {\n var v = value[i];\n var idStr = (typeof v === 'string') ? v : (v && v.id);\n if (isGuid(idStr)) refs.push(idStr);\n }\n return loadRelatedDocumentsRecurse(refs, populationContext, visit, docCache, tplCache, labelCache, groupCache, hopsLeft);\n}\n\nfunction populateValuesByEditor(document, template, result, populationContext, visit,\n docCache, tplCache, labelCache, groupCache, isRootLevel, hopsLeft) {\n if (!template || !template.editors) return;\n var tEditors = template.editors || [];\n var i, editor;\n\n function processEditor(editor, isRoot) {\n if (!editor || !editor.editor || !editor.editor.propertyName) return;\n\n var propertyName = editor.editor.propertyName;\n\n // Never let editors redefine reserved fields (consistent everywhere)\n if (RESERVED_ROOT_FIELDS[propertyName]) return;\n\n // Read value with optional localization\n var rawVal = document[propertyName];\n\n // true→false mismatch: editor is no longer localizable but value is still a locale-object\n if (!editor.localizable && isLocaleObject(rawVal, populationContext.locales)) {\n var defVal = rawVal[populationContext.defaultLocale];\n rawVal = (defVal != null) ? defVal : rawVal[Object.keys(rawVal)[0]];\n if (rawVal == null) rawVal = null;\n }\n\n var docValue = (editor.localizable) ? populationContext.translate(rawVal) : rawVal;\n\n // StrifeUri resolution\n docValue = resolveStrifeUriIfAny(docValue, populationContext, docCache);\n\n // Label fields — resolve to the SAME single-entry array as root `labels`, keyed\n // by this field's own property name. MUST run before the generic relation check\n // below: a label field's raw value (array of label doc IDs) has the identical\n // shape to a relation array (isRelationArray can't tell them apart), and would\n // otherwise be silently mis-projected as a relation to a non-existent template.\n if (editor.editor.type === 'label') {\n var labelIds = Array.isArray(docValue) ? docValue : null;\n var resolvedLabels = loadRelatedLabels(labelIds, labelCache) || [];\n result[propertyName] = buildLabelTerms(resolvedLabels, groupCache);\n return;\n }\n\n // Relations (GUID-strict) — detect by value shape\n var relatedDocs = projectRelationsIfAny(docValue, populationContext, visit, docCache, tplCache, labelCache, groupCache, hopsLeft);\n if (relatedDocs) { result[propertyName] = relatedDocs; return; }\n\n // Chapters (nested without template loads)\n if (editor.editor.type === 'chapters') {\n if (docValue == null) { // missing translation\n result[propertyName] = null; // keep null instead of dropping field\n return;\n }\n if (!Array.isArray(docValue)) return;\n var docResults = [];\n for (var ci = 0; ci < docValue.length; ci++) {\n var chapter = docValue[ci];\n if (chapter && chapter['@strife'] && chapter['@strife'].template) {\n var child = {};\n populateByShape(chapter, child, populationContext, visit,\n docCache, tplCache, labelCache, groupCache, hopsLeft);\n docResults.push(child);\n }\n }\n result[propertyName] = docResults;\n return;\n }\n\n // Content-template (nested without template loads)\n if (editor.editor.type === 'content-template') {\n if (docValue == null) { // missing translation\n result[propertyName] = null;\n return;\n }\n if (docValue && typeof docValue === 'object') {\n var nested = {};\n populateByShape(docValue, nested, populationContext, visit,\n docCache, tplCache, labelCache, groupCache, hopsLeft);\n result[propertyName] = nested;\n }\n return;\n }\n\n // Link resolution\n if (isLinkValue(docValue)) {\n result[propertyName] = resolveLink(docValue, populationContext.locale, docCache);\n return;\n }\n if (Array.isArray(docValue) && docValue.length > 0 && isLinkValue(docValue[0])) {\n var linkResults = [];\n for (var lki = 0; lki < docValue.length; lki++) {\n linkResults.push(isLinkValue(docValue[lki]) ? resolveLink(docValue[lki], populationContext.locale, docCache) : docValue[lki]);\n }\n result[propertyName] = linkResults;\n return;\n }\n\n // HTML link resolution (rich text fields)\n if (typeof docValue === 'string' && docValue.indexOf('data-id=\"') !== -1) {\n result[propertyName] = resolveHtmlLinks(docValue, populationContext.locale, docCache);\n return;\n }\n\n // Other fields: plain values (localized at root when editor.localizable)\n if (docValue !== undefined) {\n result[propertyName] = docValue;\n }\n }\n\n for (i = 0; i < tEditors.length; i++) {\n processEditor(tEditors[i], isRootLevel);\n }\n\n if (document['@strife']) result['@strife'] = document['@strife'];\n}\n\nfunction projectDocumentWithTemplate(doc, populationContext, visit,\n docCache, tplCache, labelCache, groupCache, locale, hopsLeft) {\n var collection = doc && doc['@metadata'] ? doc['@metadata']['@collection'] : null;\n var templateId = 'templates/' + (collection === 'Drafts' ? doc['@metadata']['@base-collection'] : collection);\n var tpl = loadTemplateByCollectionCached(templateId, tplCache);\n var labelsArr = Array.isArray(doc.labels) ? doc.labels : null;\n\n var result = {\n id: doc['@metadata']['@id'],\n url: buildUrlCached(doc, locale, docCache),\n collection: collection,\n displayName: doc.displayName,\n slug: resolveSlug(doc.slug, locale),\n publishedDate: doc.publishedDate,\n // Pre-existing, separate concern from the root document's own `labels` field\n // below (mapDocument) — this is a relation target's embedded label snapshot and\n // has never carried group-path entries. Left untouched; not in scope here.\n labels: loadRelatedLabels(labelsArr, labelCache) || []\n };\n\n // Fill fields per template (use root locale via populationContext.translate)\n if (tpl && tpl.editors) {\n populateValuesByEditor(doc, tpl, result, populationContext, visit,\n docCache, tplCache, labelCache, groupCache, false, hopsLeft);\n }\n\n return result;\n}\n\nfunction loadRelatedDocumentsRecurse(refGuids, populationContext, visit,\n docCache, tplCache, labelCache, groupCache, hopsLeft) {\n if (!refGuids) return null;\n\n var results = [];\n for (var i = 0; i < refGuids.length; i++) {\n var id = refGuids[i];\n if (!isGuid(id)) continue;\n\n if (isVisiting(visit, id)) continue;\n\n var cached = getCachedNode(visit, id);\n if (cached) { results.push(cached); continue; }\n\n markVisiting(visit, id);\n var doc = loadDocCached(id, docCache);\n\n if (doc) {\n var projected = projectDocumentWithTemplate(doc, populationContext, visit,\n docCache, tplCache, labelCache, groupCache, populationContext.locale, hopsLeft - 1);\n setCachedNode(visit, id, projected);\n results.push(projected);\n }\n\n markDone(visit, id);\n }\n\n return results;\n}\n\nfunction mapDocument(document) {\n var collection = document['@metadata']['@collection'];\n var docId = document['@metadata']['@id'];\n var statusVal = document.status;\n\n // Role detection is dual during the migration window: id-suffix OR the legacy\n // Drafts collection OR the persisted status. Robust whether siblings still live\n // in the Drafts collection (pre-migration) or the canonical collection (post).\n var _isDraft = strEndsWith(docId, '/draft') || collection === 'Drafts' || statusVal === 'draft';\n var _isScheduled = strEndsWith(docId, '/scheduled') || statusVal === 'scheduled';\n var _isSibling = _isDraft || _isScheduled;\n\n var baseCollection = _isSibling ? (document['@metadata']['@base-collection'] || collection) : collection;\n var templateId = 'templates/' + baseCollection;\n var template = load(templateId, 'templates');\n if (!template || !template.editors) return null;\n // Per-status emission: never emit Archived or Deleted (neither the public SDK\n // resolver nor edit-mode preview may see them). Drop by status OR legacy flag.\n if (statusVal === 'deleted' || statusVal === 'archived' || document.deleted || document.archived) return null;\n\n var l10n = loadLocalizationSettings();\n var locales = (l10n && Array.isArray(l10n.locales)) ? l10n.locales : [l10n && l10n.defaultLocale ? l10n.defaultLocale : 'en'];\n\n // Shared per-document caches (shared across locales)\n var _docCache = new Map();\n var _tplCache = new Map();\n var _labelCache = new Map();\n var _groupCache = new Map();\n\n var entries = [];\n\n // Precompute root labels (loaded in helper)\n var rootLabelIds = Array.isArray(document.labels) ? document.labels : null;\n // Computed ONCE per document (not per locale — label/group names aren't localized).\n var _relatedLabels = loadRelatedLabels(rootLabelIds, _labelCache) || [];\n var _labelTerms = buildLabelTerms(_relatedLabels, _groupCache);\n\n // Precompute dependencies (ES5)\n var depsArr = [];\n if (document.dependencies && Array.isArray(document.dependencies)) {\n for (var di = 0; di < document.dependencies.length; di++) {\n var dep = document.dependencies[di];\n if (dep && typeof dep.id === 'string') depsArr.push(dep.id);\n }\n }\n\n // Build a set of localizable root scalar fields from the template\n var localizableRoot = {};\n // Root-level `label`-type fields need forcing into storeAs below: their value is\n // an array of plain strings (buildLabelTerms' output), and the generic classifier\n // right below only auto-stores arrays of PROJECTED OBJECTS (relations/chapters) —\n // a plain string array would otherwise fall to indexAs (query-only), silently\n // breaking selectFields('categories', ...) for exactly the query pattern this\n // field type exists for.\n var forceStoreRoot = {};\n if (template && template.editors) {\n for (var li0 = 0; li0 < template.editors.length; li0++) {\n var ed0 = template.editors[li0];\n if (!ed0 || !ed0.editor || !ed0.editor.propertyName) continue;\n var t0 = ed0.editor.type;\n if (t0 === 'label') { forceStoreRoot[ed0.editor.propertyName] = true; continue; }\n if (t0 !== 'related' && t0 !== 'references' && t0 !== 'content-template' && t0 !== 'chapters') {\n if (ed0.localizable === true) localizableRoot[ed0.editor.propertyName] = true;\n }\n }\n }\n\n for (var li = 0; li < locales.length; li++) {\n\n var currentLocale = locales[li];\n if (document.locales && Array.isArray(document.locales)) {\n var found = false;\n for (var k = 0; k < document.locales.length; k++) {\n if (document.locales[k] === currentLocale) { found = true; break; }\n }\n if (!found) continue;\n }\n\n var translate = getTranslator(currentLocale, l10n.defaultLocale, l10n.fallbackToPrimary);\n var checkLocalized = function (obj) { return isLocalized(obj, locales); };\n\n // Root result (stored fields)\n var result = {\n id: storeAs('id',Id(document)),\n docId: storeAs('docId', document['@metadata']['@id']),\n locale: storeAs('locale', currentLocale),\n displayName: storeAs('displayName',document.displayName),\n url: template.disableURL ? null : storeAs('url', buildUrlCached(document, currentLocale, _docCache, _isSibling)),\n origin: storeAs('origin', document.origin ? document.origin.id : null ),\n collection: storeAs('collection', baseCollection),\n // Legacy boolean kept during the deprecation window so pre-status SDK builds\n // keep matching. Scheduled siblings carry draft:true so the pre-U5 resolver\n // (which filters draft==false) never returns them and cannot throw on a\n // multi-entry URL.\n draft: storeAs('draft', _isSibling),\n // Legacy fallback (status absent) treats a FUTURE publishedDate as unpublished, matching\n // the backfill (StatusMigrationBootstrap) — otherwise a scheduled-via-future-date doc\n // would emit status:'published' and leak publicly during the pre-backfill window.\n status: storeAs('status', statusVal || (_isScheduled ? 'scheduled' : (_isDraft ? 'draft' : ((document.publishedDate && new Date(document.publishedDate) <= new Date()) ? 'published' : 'unpublished')))),\n publishedAt: storeAs('publishedAt', document.publishedDate),\n createdAt: storeAs('createdAt', document.createdAt),\n changedAt: storeAs('changedAt', document.changedAt),\n dependencies: storeAs('dependencies', depsArr),\n labels: storeAs('labels', _labelTerms),\n deleted: indexAs('deleted', document.deleted)\n };\n\n // Visit cache per-locale (keeps shapes deterministic per entry)\n var visit = new Map();\n\n // Build dynamic fields into a temp container using ROOT TEMPLATE (respect localizable)\n var rootContainer = {};\n populateValuesByEditor(document, template, rootContainer,\n { translate: translate, isLocalized: checkLocalized, locale: currentLocale, defaultLocale: l10n.defaultLocale, locales: locales },\n visit, _docCache, _tplCache, _labelCache, _groupCache, true, MAX_RELATION_DEPTH);\n\n // Copy fields from temp container into result\n // - arrays/objects -> storeAs\n // - scalars -> storeAs if localizable, else indexAs\n var keys = [];\n for (var key in rootContainer) {\n if (rootContainer && rootContainer.hasOwnProperty && rootContainer.hasOwnProperty(key)) {\n keys.push(key);\n }\n }\n\n for (var ei = 0; ei < keys.length; ei++) {\n var prop = keys[ei];\n\n // never redefine canonical root fields\n if (RESERVED_ROOT_FIELDS[prop]) continue;\n\n var val = rootContainer[prop];\n var isArray = Array.isArray(val);\n var isProjectedArray = isArray && (val.length === 0 || typeof val[0] === 'object');\n var isProjectedObject = !isArray && typeof val === 'object';\n\n if (isProjectedArray || isProjectedObject || forceStoreRoot[prop] === true) {\n result[prop] = storeAs(prop, val);\n } else {\n if (localizableRoot[prop] === true) {\n result[prop] = storeAs(prop, val); // translated scalar → store so projections return translated value\n } else {\n result[prop] = indexAs(prop, val); // non-localizable scalar → index-only\n }\n }\n }\n\n entries.push(result);\n }\n\n return entries;\n}\n";
3
+ export const source = "/**\n * -----------------------------------------------------------------------------\n * STRIFE CONTENT INDEX — L10N + StrifeUri + Template-Aware Relations\n * -----------------------------------------------------------------------------\n * What this does\n * Projects Strife CMS documents into a query‑friendly, localized, relation‑expanded\n * shape with one entry per locale. It keeps a deterministic output and a\n * centralized storage policy (Corax‑safe).\n * Cycle safety and performance caching.\n *\n * Canonical root fields (unchanged semantics)\n * - id, locale, displayName, url, origin, collection\n * - publishedAt, createdAt, changedAt, dependencies, labels, deleted\n *\n * Labels\n * - `labels` carries ONE entry per label, live and unfolded. A validly grouped\n * label resolves to \"<group.name>/<label.name>\" using the REAL display names\n * (no slugs, no folding, e.g. \"Färger/Blå\") — it does NOT also contribute a\n * separate bare entry. Ungrouped labels (and anything that disqualifies a\n * label from being validly grouped: deleted, ETL-sourced, dangling/deleted\n * group, empty name on either side) fall back to the bare name. Computed\n * fresh every index run, never frozen — Strife's query pattern is always\n * fetch-then-query (read a live value, use it immediately), so there is no\n * hardcoded-name staleness risk to guard against. The array is deduped as a\n * whole (label Name uniqueness isn't server-enforced, so two labels can\n * resolve to the identical string). See buildLabelTerms.\n * - Root-level custom fields of editor type 'label' resolve to the SAME\n * single-entry array, under their own property name, via the identical\n * buildLabelTerms call. A label field nested inside chapters/content-template\n * resolves the same way too, even though populateByShape has no editor-type\n * awareness — it detects a label array by what its IDs actually resolve to,\n * not by a declared field type: a missing/dangling ID is skipped (same as\n * the root-level path), but an ID that loads as a document from some OTHER\n * collection is decisive proof this is a genuine relation, not a labels\n * field — the whole array then falls back to normal relation projection.\n * See populateByShape's own comment and resolveIfLabelsCollection.\n *\n * Root template (whitelist + localizable)\n * - Root fields are populated from the document's template. Scalars marked\n * localizable are translated using the current entry's locale and stored.\n * - Non‑localizable scalars are index‑only at root.\n * - Arrays/objects (relations, chapters, content objects) are stored.\n * - Reserved root names can never be redefined by editors.\n *\n * Relations (template‑driven for referenced docs)\n * - Relations are detected by value shape (array of GUIDs or { id: GUID }).\n * - For each referenced GUID, the target document is loaded and projected with\n * its template (resolved by collection), using the ROOT entry's locale for\n * any localizable scalars.\n * - Order is preserved. Only GUIDs are followed. Cycles are prevented via a\n * visit cache.\n *\n * Nested content by shape (no template loads)\n * - Chapters: arrays of small objects; expanded recursively by shape.\n * - Content‑template (object): expanded recursively by shape.\n * - While expanding by shape, a relation-shaped field resolves one of two ways:\n * to label terms if its IDs are consistent with a labels field — none of\n * them loads as a document from some other collection (see the Labels\n * section above) — otherwise projected via templates as a relation.\n * Anything else is copied through. No per‑field localization occurs in\n * shape expansion.\n *\n * StrifeUri\n * - Form: strife://<docId>.<collection>.<db>/<path>\n * - Resolution: loads the target doc and walks the path; the first path\n * segment is localized with the ROOT entry's locale. The resolved value can be:\n * • a scalar → copied as is\n * • an object/array → expanded by shape\n * • a relation array → projected via templates\n *\n * Storage policy (centralized at root copy step)\n * - Arrays/objects → storeAs\n * - Scalars localizable at root → storeAs (so projections return translated strings)\n * - Scalars not localizable → indexAs (leaner index)\n * - Root url is always stored\n * - Keep per‑field storage/analyzer consistent across documents (Corax‑safe)\n *\n * Caching & cycle safety\n * - docCache: documents, tplCache: templates, labelCache: labels\n * - visit cache prevents cycles and provides memoization per entry\n * - URL builder protects against origin cycles via a local visited set\n *\n * Localization model\n * - Emits one entry per configured locale.\n * - Root scalars marked localizable are translated and stored.\n * - Referenced docs projected via templates also translate localizable scalars\n * using the ROOT locale. displayName is treated as non‑localized.\n * - Shape expansion itself does not localize values, except StrifeUri's first\n * path segment at dereference time.\n *\n * Collection mapping\n * - This index must map BOTH 'Contents' AND 'Drafts' collections.\n * - Draft entries are emitted with draft: true so consumers can filter.\n * - When building URLs for draft entries, the origin chain falls back to\n * parent draft documents when the published parent lacks the locale slug.\n *\n * Determinism & engine constraints\n * - ES5‑only (no for..of, no arrow functions).\n * - Deterministic shapes under the same inputs.\n * - Relation depth is capped at MAX_RELATION_DEPTH hops; cycles are still safe.\n * -----------------------------------------------------------------------------\n */\n\n\nvar MAX_RELATION_DEPTH = 1;\n\nvar RESERVED_ROOT_FIELDS = {\n url: true,\n collection: true,\n publishedAt: true,\n createdAt: true,\n changedAt: true,\n labels: true,\n locale: true,\n origin: true,\n id: true,\n docId: true,\n displayName: true,\n dependencies: true,\n deleted: true,\n draft: true,\n status: true\n};\n\nfunction storeAs(name, value) {\n return { $value: value, $name: name, $options: { storage: true } };\n}\n\nfunction indexAs(name, value) {\n return { $value: value, $name: name, $options: { storage: false } };\n}\n\n// ES5 endsWith (Jint engine — no String.prototype.endsWith reliance).\nfunction strEndsWith(s, suffix) {\n return typeof s === 'string' && s.length >= suffix.length &&\n s.substring(s.length - suffix.length) === suffix;\n}\n\nfunction loadDocCached(id, docCache) {\n if (!id) return null;\n var key = id;\n if (docCache.has(key)) return docCache.get(key);\n var d = load(id, '@all_docs');\n if (d) docCache.set(key, d);\n return d;\n}\n\nfunction loadTemplateByCollectionCached(templateId, tplCache) {\n if (!templateId) return null;\n if (tplCache.has(templateId)) return tplCache.get(templateId);\n var t = load(templateId, 'templates');\n if (t) tplCache.set(templateId, t);\n return t;\n}\n\nfunction loadTemplateByIdCached(templateId, tplCache) {\n if (!templateId) return null;\n var key = templateId;\n if (tplCache.has(key)) return tplCache.get(key);\n var t = load(templateId, 'templates');\n if (t) tplCache.set(key, t);\n return t;\n}\n\nfunction loadLocalizationSettings() {\n return load('configurations/localization', 'configurations');\n}\n\nfunction loadLabelCached(id, labelCache) {\n if (!id) return null;\n if (labelCache.has(id)) return labelCache.get(id);\n var d = load(id, 'Labels');\n if (d) labelCache.set(id, d);\n return d;\n}\n\nfunction loadGroupCached(id, groupCache) {\n if (!id) return null;\n if (groupCache.has(id)) return groupCache.get(id);\n var d = load(id, 'LabelGroups');\n if (d) groupCache.set(id, d);\n return d;\n}\n\nfunction resolveSlug(slug, locale) {\n if (typeof slug === 'string') return slug; // legacy string format\n if (slug && locale && slug[locale]) return slug[locale]; // locale-keyed object\n return null;\n}\n\n// Checks if obj is a locale-keyed object where ALL keys are configured locales.\n// Uses configured locales (not BCP 47 syntax) to avoid false positives on\n// objects like { \"id\": \"abc\", \"no\": false } where keys happen to be 2-letter strings.\nfunction isLocaleObject(obj, configuredLocales) {\n if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return false;\n var keys = Object.keys(obj);\n if (keys.length === 0) return false;\n for (var i = 0; i < keys.length; i++) {\n if (configuredLocales.indexOf(keys[i]) === -1) return false;\n }\n return true;\n}\n\nfunction buildUrlCached(doc, locale, docCache, isDraft) {\n var visited = {};\n var slugs = [];\n var c = doc;\n do {\n var nodeSlug = resolveSlug(c.slug, locale);\n // Draft URL building: if a published ancestor lacks the locale slug, try its draft\n if (!nodeSlug && isDraft) {\n var nodeId = c['@metadata'] ? c['@metadata']['@id'] : null;\n // Only hop to an ancestor's /draft when this node isn't itself a sibling\n // (id-suffix, collection-agnostic — survives the Drafts collection retirement).\n if (nodeId && !strEndsWith(nodeId, '/draft') && !strEndsWith(nodeId, '/scheduled')) {\n var draftNode = loadDocCached(nodeId + '/draft', docCache);\n if (draftNode) nodeSlug = resolveSlug(draftNode.slug, locale);\n }\n }\n if (nodeSlug) slugs.unshift(nodeSlug);\n if (!c.origin || visited[c.origin.id]) break;\n visited[c.origin.id] = true;\n c = loadDocCached(c.origin.id, docCache);\n } while (c);\n\n // ENG-62: do NOT return slugs.shift() + slugs.join('/').\n // For an empty array that expression evaluates to the literal string \"undefined\"\n // (because [].shift() is undefined and undefined + '' coerces to \"undefined\").\n // For a two-element array it drops the separator (['a','b'] -> \"ab\" instead of \"a/b\").\n // Both behaviors are confirmed in api-test/Integration/Eng62LinkResolutionDiagnosticTests.\n //\n // We also can't just slugs.join('/'): the root home's slug is \"/\" by convention,\n // so ['/', 'page'] would produce \"//page\". Instead, normalize each slug (strip its\n // own leading/trailing slashes so \"/\" becomes \"\" and drops out) and prepend a\n // single leading \"/\" so URLs always start with \"/\".\n if (slugs.length === 0) return null;\n var parts = [];\n for (var si = 0; si < slugs.length; si++) {\n var s = slugs[si];\n if (typeof s !== 'string') continue;\n // Strip leading and trailing slashes from this segment. Reduces \"/\" to \"\"\n // (skipped) and leaves \"foo\" untouched.\n var stripped = s;\n while (stripped.length > 0 && stripped.charAt(0) === '/') stripped = stripped.substring(1);\n while (stripped.length > 0 && stripped.charAt(stripped.length - 1) === '/') stripped = stripped.substring(0, stripped.length - 1);\n if (stripped.length > 0) parts.push(stripped);\n }\n return '/' + parts.join('/');\n}\n\nfunction isLinkValue(val) {\n return val && typeof val === 'object' && !Array.isArray(val) &&\n typeof val.href === 'string' && typeof val.target === 'string' &&\n isGuid(val.id);\n}\n\nfunction resolveLinkHref(val, locale, docCache) {\n var doc = loadDocCached(val.id, docCache);\n if (!doc) return val.href;\n // ENG-62: do NOT use `'slug' in doc` here. RavenDB's Jint wraps loaded docs in a\n // BlittableObjectInstance whose [[HasProperty]] returns true for ANY property name,\n // making `'slug' in doc` always-true dead code. Use the @metadata collection check\n // (proven reliable) and a value-presence check instead.\n // See api-test/Integration/Eng62LinkResolutionDiagnosticTests for proof.\n var collection = doc['@metadata'] && doc['@metadata']['@collection'];\n if (collection === 'Files' || collection === 'Folders') return val.href;\n if (doc.slug == null) return val.href;\n var url = buildUrlCached(doc, locale, docCache);\n if (!url || url === 'undefined') return val.href;\n return url;\n}\n\nfunction resolveLink(val, locale, docCache) {\n return {\n id: val.id,\n text: val.text,\n href: resolveLinkHref(val, locale, docCache),\n target: val.target\n };\n}\n\nfunction resolveHtmlLinks(html, locale, docCache) {\n if (html.indexOf('data-id=\"') === -1) return html;\n\n var guidPat = '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}';\n\n // data-id before href\n var p1 = new RegExp(\n '<a\\\\s([^>]*?)data-id=\"(' + guidPat + ')\"([^>]*?)href=\"([^\"]*)\"([^>]*?)>',\n 'g'\n );\n // ENG-62: same Jint quirk as resolveLinkHref — `'slug' in doc` is dead code on\n // a wrapped loaded doc. Use collection check + value check instead.\n var result = html.replace(p1, function(match, before, id, mid, href, after) {\n var doc = loadDocCached(id, docCache);\n if (!doc) return match;\n var coll = doc['@metadata'] && doc['@metadata']['@collection'];\n if (coll === 'Files' || coll === 'Folders') return match;\n if (doc.slug == null) return match;\n var url = buildUrlCached(doc, locale, docCache);\n if (!url || url === 'undefined') return match;\n return '<a ' + before + 'data-id=\"' + id + '\"' + mid + 'href=\"' + url + '\"' + after + '>';\n });\n\n // href before data-id\n var p2 = new RegExp(\n '<a\\\\s([^>]*?)href=\"([^\"]*)\"([^>]*?)data-id=\"(' + guidPat + ')\"([^>]*?)>',\n 'g'\n );\n result = result.replace(p2, function(match, before, href, mid, id, after) {\n var doc = loadDocCached(id, docCache);\n if (!doc) return match;\n var coll = doc['@metadata'] && doc['@metadata']['@collection'];\n if (coll === 'Files' || coll === 'Folders') return match;\n if (doc.slug == null) return match;\n var url = buildUrlCached(doc, locale, docCache);\n if (!url || url === 'undefined') return match;\n return '<a ' + before + 'href=\"' + url + '\"' + mid + 'data-id=\"' + id + '\"' + after + '>';\n });\n\n return result;\n}\n\nfunction getTranslator(locale, defaultLocale, fallbackToPrimary) {\n return function (obj) {\n if (!obj) return null;\n if (typeof obj === 'object' && obj.hasOwnProperty) {\n if (obj.hasOwnProperty(locale)) return obj[locale];\n // if (fallbackToPrimary && locale && locale.length > 2) {\n // var base = locale.slice(0, 2);\n // if (obj.hasOwnProperty(base)) return obj[base];\n // }\n //return obj[defaultLocale];\n }\n // Return non-objects as-is only for the default locale (handles false→true\n // mismatch where editor.localizable is true but stored value is still a\n // plain scalar). Non-default locales get null — the value was never translated.\n if ((typeof obj === 'string' || typeof obj === 'number') && locale === defaultLocale) return obj;\n return null;\n };\n}\n\nfunction isLocalized(obj, configuredLocales) {\n if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return false;\n if (!obj.hasOwnProperty) return false;\n for (var i = 0; i < configuredLocales.length; i++) {\n if (obj.hasOwnProperty(configuredLocales[i])) return true;\n }\n return false;\n}\n\nfunction isGuid(s) {\n return typeof s === 'string' &&\n /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(s);\n}\nfunction isRefLike(x) {\n // GUID string id, or object with GUID `id`\n if (typeof x === 'string') return isGuid(x);\n if (!x || typeof x !== 'object') return false;\n return typeof x.id === 'string' && isGuid(x.id) && Object.keys(x).length <= 2;\n}\nfunction isRelationArray(v) {\n if (!v || !Array.isArray(v)) return false;\n if (v.length === 0) return false; // empty arrays are ambiguous\n for (var i = 0; i < v.length; i++) {\n if (!isRefLike(v[i])) return false;\n }\n return true;\n}\n\nvar strifeUriPattern = /^strife:\\/\\/(?<id>[-\\w_]*)\\.(?<coll>[\\w_-]*)\\.(?<db>[\\w_-]*)(?<path>[-\\w_./]*)?$/;\nfunction isStrifeUri(value) {\n var match = strifeUriPattern.exec(value || '');\n if (!match) return null;\n try {\n var path = match.groups.path || '';\n var segs = path.length > 1 ? path.slice(1).split('/') : [];\n return {\n original: value,\n db: match.groups.db,\n collection: match.groups.coll,\n docId: match.groups.id, // may be non-GUID by design for StrifeUri\n segments: segs\n };\n } catch (e) {\n return null;\n }\n}\n\nfunction tryGetReferencedValue(strifeUri, populationContext, docCache) {\n var document = loadDocCached(strifeUri.docId, docCache);\n if (!document) return null;\n\n var val = document;\n if (strifeUri.segments.length > 0) {\n // first segment may be localized; use isLocalized to distinguish locale-keyed objects\n var first = strifeUri.segments[0];\n var rawFirst = val[first];\n if (populationContext.isLocalized(rawFirst)) {\n val = populationContext.translate(rawFirst);\n } else {\n val = rawFirst;\n }\n for (var i = 1; i < strifeUri.segments.length; i++) {\n if (val == null) break;\n var segment = strifeUri.segments[i];\n val = val[segment];\n }\n }\n return val;\n}\n\nfunction resolveStrifeUriIfAny(value, populationContext, docCache) {\n var maybeUri = isStrifeUri(value);\n return maybeUri ? tryGetReferencedValue(maybeUri, populationContext, docCache) : value;\n}\n\nfunction loadRelatedLabels(labelIdsArray, labelCache) {\n if (!labelIdsArray || !labelIdsArray.length) return null;\n var out = [];\n for (var i = 0; i < labelIdsArray.length; i++) {\n var lid = labelIdsArray[i];\n var d = loadLabelCached(lid, labelCache);\n if (d) out.push(d);\n }\n return out;\n}\n\n// STR-2099 (revised, single-entry): builds the consolidated `labels` array — ONE\n// entry per label, from the SAME loaded label docs `labels` has always used (no\n// duplicate load() calls). A label is VALIDLY GROUPED when: non-deleted, not\n// ETL-sourced (D9 — the admin UI never offers grouping for ETL labels, so a\n// groupId there is defensively ignored), has a groupId whose target group exists\n// and is non-deleted, and both group.name and label.name are non-empty. A validly\n// grouped label resolves to \"<group.name>/<label.name>\" — the REAL display names,\n// live, never slugified or frozen (Strife's query pattern is always\n// fetch-then-query, so a live value can never be \"stale\" the way a hardcoded one\n// could). Anything that disqualifies a label from being validly grouped falls back\n// to its bare name — same as a genuinely ungrouped label (R11: grouping is\n// opt-in, nothing is ever omitted from the index over a grouping problem).\n// Deduped as a whole: Name uniqueness is a client-side pre-check only (Label\n// /validity), never enforced by Post/Put, so two labels can resolve to the\n// identical string and would otherwise double an entry.\nfunction buildLabelTerms(labelsArray, groupCache) {\n var seen = new Map();\n var terms = [];\n\n function pushUnique(term) {\n if (seen.has(term)) return;\n seen.set(term, true);\n terms.push(term);\n }\n\n if (!labelsArray) return terms;\n for (var i = 0; i < labelsArray.length; i++) {\n var label = labelsArray[i];\n if (!label) continue;\n\n var resolved = label.name;\n var isEtl = label['@metadata'] && label['@metadata'].external === true;\n if (!label.deleted && !isEtl && label.groupId) {\n var group = loadGroupCached(label.groupId, groupCache);\n if (group && !group.deleted && group.name && label.name) {\n resolved = group.name + '/' + label.name;\n }\n }\n pushUnique(resolved);\n }\n return terms;\n}\n\n// Resolves a relation-shaped ID array to label terms if its IDs are consistent\n// with a labels field — used by populateByShape (nested chapters/content-\n// template) to detect a label field without knowing its declared editor type.\n// A missing/dangling ID is skipped (mirrors loadRelatedLabels's root-level\n// behavior below) — it proves nothing either way, so it doesn't disqualify the\n// rest of the array. An ID that DOES load, but as a document from some OTHER\n// collection, is decisive: that's real evidence this is a genuine relation, not\n// a labels field, so the whole array defers to normal relation projection —\n// never a mixed array of resolved names and relation objects.\n//\n// Deliberately loads via loadDocCached (the `@all_docs` pseudo-collection —\n// same helper `resolveLinkHref`/`resolveHtmlLinks`/`projectDocumentWithTemplate`\n// already use for this exact \"detect what a document IS\" purpose), NOT\n// loadLabelCached. `@all_docs` is genuinely collection-agnostic, but a specific\n// collection hint like `'Labels'` is NOT just a reference-tracking hint the way\n// it is on the document currently being mapped — for a document loaded from\n// elsewhere, `load(id, 'Labels')` returns nothing at all when the target isn't\n// actually in that collection (verified empirically; see\n// LabelFieldNestedInChaptersIndexTests). That makes it useless here: every\n// non-Labels ID would look identical to a missing one, so a genuine relation\n// could never be told apart from a dangling reference. loadRelatedLabels below\n// can safely use the filtering `'Labels'` hint precisely because its input is\n// schema-guaranteed to only ever contain label IDs — this function's input\n// carries no such guarantee, which is the whole reason it exists.\nfunction resolveIfLabelsCollection(refIds, docCache, groupCache) {\n if (!refIds || !refIds.length) return null;\n var loaded = [];\n for (var i = 0; i < refIds.length; i++) {\n var doc = loadDocCached(refIds[i], docCache);\n if (!doc) continue;\n if (!doc['@metadata'] || doc['@metadata']['@collection'] !== 'Labels') return null;\n loaded.push(doc);\n }\n return loaded.length ? buildLabelTerms(loaded, groupCache) : null;\n}\n\nfunction getVisitEntry(visit, id) {\n var e = visit.get(id);\n if (!e) { e = { state: 'done', node: null }; visit.set(id, e); }\n return e;\n}\n\nfunction isVisiting(visit, id) {\n var e = visit.get(id);\n return e ? e.state === 'visiting' : false;\n}\n\nfunction markVisiting(visit, id) {\n var e = getVisitEntry(visit, id);\n e.state = 'visiting';\n}\n\nfunction markDone(visit, id) {\n var e = getVisitEntry(visit, id);\n e.state = 'done';\n}\n\nfunction getCachedNode(visit, id) {\n var e = visit.get(id);\n return e && e.node ? e.node : null;\n}\n\nfunction setCachedNode(visit, id, node) {\n var e = getVisitEntry(visit, id);\n e.node = node;\n}\n\nfunction isContentTemplateNode(v) {\n return v && typeof v === 'object' && !Array.isArray(v) &&\n v['@strife'] && v['@strife'].template;\n}\n\nfunction isChaptersArray(v) {\n if (!v || !Array.isArray(v)) return false;\n if (v.length === 0) return false;\n for (var i = 0; i < v.length; i++) {\n var it = v[i];\n if (!it || !it['@strife'] || !it['@strife'].template) return false;\n }\n return true;\n}\n\n// Shape-only — deliberately has NO template/editor-type awareness (see the file\n// header). A `label`-type custom field nested in here has the identical raw shape\n// to a relation array (both are just arrays of GUIDs), so there is no way to tell\n// them apart from the field's declared type alone. Resolved a different way\n// instead: the relation branch below checks what the IDs actually resolve to\n// (resolveIfLabelsCollection) before assuming they're a relation to real content —\n// if none of them turn out to be a document from some OTHER collection, it's\n// treated as a labels field regardless of what the schema calls it. See that\n// function's own comment.\nfunction populateByShape(document, out, populationContext, visit,\n docCache, tplCache, labelCache, groupCache, hopsLeft) {\n if (!document || typeof document !== 'object') return;\n\n for (var prop in document) {\n if (!document || !document.hasOwnProperty || !document.hasOwnProperty(prop)) continue;\n\n // Keep metadata decorations as-is\n if (prop.charAt(0) === '@') { out[prop] = document[prop]; continue; }\n\n var val = document[prop];\n // StrifeUri resolution\n val = resolveStrifeUriIfAny(val, populationContext, docCache);\n\n // Chapters\n if (isChaptersArray(val)) {\n var arr = [];\n for (var ci = 0; ci < val.length; ci++) {\n var chapter = val[ci];\n if (chapter && chapter['@strife'] && chapter['@strife'].template) {\n var child = {};\n populateByShape(chapter, child, populationContext, visit,\n docCache, tplCache, labelCache, groupCache, hopsLeft);\n arr.push(child);\n }\n }\n out[prop] = arr;\n continue;\n }\n\n // Content-template node\n if (isContentTemplateNode(val)) {\n var nested = {};\n populateByShape(val, nested, populationContext, visit,\n docCache, tplCache, labelCache, groupCache, hopsLeft);\n out[prop] = nested;\n continue;\n }\n\n // Relations (GUID-strict) — see resolveIfLabelsCollection's own comment.\n // Deliberately runs even at hopsLeft <= 0, ahead of projectRelationsIfAny's\n // own hop-cap check below: label resolution is a terminal lookup (it never\n // recurses into further relation-chasing), not another hop of the capped\n // traversal, so it's exempt from the cap by design, not by oversight.\n if (isRelationArray(val)) {\n var refIds = extractRelationRefIds(val);\n var labelTerms = resolveIfLabelsCollection(refIds, docCache, groupCache);\n if (labelTerms) { out[prop] = labelTerms; continue; }\n var related = projectRelationsIfAny(val, populationContext, visit, docCache, tplCache, labelCache, groupCache, hopsLeft);\n if (related) { out[prop] = related; continue; }\n }\n\n // Link resolution\n if (isLinkValue(val)) {\n out[prop] = resolveLink(val, populationContext.locale, docCache);\n continue;\n }\n if (Array.isArray(val) && val.length > 0 && isLinkValue(val[0])) {\n var linkArr = [];\n for (var lki = 0; lki < val.length; lki++) {\n linkArr.push(isLinkValue(val[lki]) ? resolveLink(val[lki], populationContext.locale, docCache) : val[lki]);\n }\n out[prop] = linkArr;\n continue;\n }\n\n // HTML link resolution (rich text fields)\n if (typeof val === 'string' && val.indexOf('data-id=\"') !== -1) {\n out[prop] = resolveHtmlLinks(val, populationContext.locale, docCache);\n continue;\n }\n\n // Default: copy-through (no localization inside nested)\n out[prop] = val;\n }\n\n if (document['@strife'] && !out['@strife']) out['@strife'] = document['@strife'];\n}\n\n// Extracts GUID strings from a relation array's mixed shape (bare GUID string,\n// or { id: GUID } — see isRefLike). Shared by projectRelationsIfAny and\n// populateByShape's labels-collection check so both agree on what counts as a\n// reference.\nfunction extractRelationRefIds(value) {\n var refs = [];\n for (var i = 0; i < value.length; i++) {\n var v = value[i];\n var idStr = (typeof v === 'string') ? v : (v && v.id);\n if (isGuid(idStr)) refs.push(idStr);\n }\n return refs;\n}\n\nfunction projectRelationsIfAny(value, populationContext, visit, docCache, tplCache, labelCache, groupCache, hopsLeft) {\n if (!isRelationArray(value)) return null;\n if (hopsLeft <= 0) return [];\n var refs = extractRelationRefIds(value);\n return loadRelatedDocumentsRecurse(refs, populationContext, visit, docCache, tplCache, labelCache, groupCache, hopsLeft);\n}\n\nfunction populateValuesByEditor(document, template, result, populationContext, visit,\n docCache, tplCache, labelCache, groupCache, isRootLevel, hopsLeft) {\n if (!template || !template.editors) return;\n var tEditors = template.editors || [];\n var i, editor;\n\n function processEditor(editor, isRoot) {\n if (!editor || !editor.editor || !editor.editor.propertyName) return;\n\n var propertyName = editor.editor.propertyName;\n\n // Never let editors redefine reserved fields (consistent everywhere)\n if (RESERVED_ROOT_FIELDS[propertyName]) return;\n\n // Read value with optional localization\n var rawVal = document[propertyName];\n\n // true→false mismatch: editor is no longer localizable but value is still a locale-object\n if (!editor.localizable && isLocaleObject(rawVal, populationContext.locales)) {\n var defVal = rawVal[populationContext.defaultLocale];\n rawVal = (defVal != null) ? defVal : rawVal[Object.keys(rawVal)[0]];\n if (rawVal == null) rawVal = null;\n }\n\n var docValue = (editor.localizable) ? populationContext.translate(rawVal) : rawVal;\n\n // StrifeUri resolution\n docValue = resolveStrifeUriIfAny(docValue, populationContext, docCache);\n\n // Label fields — resolve to the SAME single-entry array as root `labels`, keyed\n // by this field's own property name. MUST run before the generic relation check\n // below: a label field's raw value (array of label doc IDs) has the identical\n // shape to a relation array (isRelationArray can't tell them apart), and would\n // otherwise be silently mis-projected as a relation to a non-existent template.\n if (editor.editor.type === 'label') {\n var labelIds = Array.isArray(docValue) ? docValue : null;\n var resolvedLabels = loadRelatedLabels(labelIds, labelCache) || [];\n result[propertyName] = buildLabelTerms(resolvedLabels, groupCache);\n return;\n }\n\n // Relations (GUID-strict) — detect by value shape\n var relatedDocs = projectRelationsIfAny(docValue, populationContext, visit, docCache, tplCache, labelCache, groupCache, hopsLeft);\n if (relatedDocs) { result[propertyName] = relatedDocs; return; }\n\n // Chapters (nested without template loads)\n if (editor.editor.type === 'chapters') {\n if (docValue == null) { // missing translation\n result[propertyName] = null; // keep null instead of dropping field\n return;\n }\n if (!Array.isArray(docValue)) return;\n var docResults = [];\n for (var ci = 0; ci < docValue.length; ci++) {\n var chapter = docValue[ci];\n if (chapter && chapter['@strife'] && chapter['@strife'].template) {\n var child = {};\n populateByShape(chapter, child, populationContext, visit,\n docCache, tplCache, labelCache, groupCache, hopsLeft);\n docResults.push(child);\n }\n }\n result[propertyName] = docResults;\n return;\n }\n\n // Content-template (nested without template loads)\n if (editor.editor.type === 'content-template') {\n if (docValue == null) { // missing translation\n result[propertyName] = null;\n return;\n }\n if (docValue && typeof docValue === 'object') {\n var nested = {};\n populateByShape(docValue, nested, populationContext, visit,\n docCache, tplCache, labelCache, groupCache, hopsLeft);\n result[propertyName] = nested;\n }\n return;\n }\n\n // Link resolution\n if (isLinkValue(docValue)) {\n result[propertyName] = resolveLink(docValue, populationContext.locale, docCache);\n return;\n }\n if (Array.isArray(docValue) && docValue.length > 0 && isLinkValue(docValue[0])) {\n var linkResults = [];\n for (var lki = 0; lki < docValue.length; lki++) {\n linkResults.push(isLinkValue(docValue[lki]) ? resolveLink(docValue[lki], populationContext.locale, docCache) : docValue[lki]);\n }\n result[propertyName] = linkResults;\n return;\n }\n\n // HTML link resolution (rich text fields)\n if (typeof docValue === 'string' && docValue.indexOf('data-id=\"') !== -1) {\n result[propertyName] = resolveHtmlLinks(docValue, populationContext.locale, docCache);\n return;\n }\n\n // Other fields: plain values (localized at root when editor.localizable)\n if (docValue !== undefined) {\n result[propertyName] = docValue;\n }\n }\n\n for (i = 0; i < tEditors.length; i++) {\n processEditor(tEditors[i], isRootLevel);\n }\n\n if (document['@strife']) result['@strife'] = document['@strife'];\n}\n\nfunction projectDocumentWithTemplate(doc, populationContext, visit,\n docCache, tplCache, labelCache, groupCache, locale, hopsLeft) {\n var collection = doc && doc['@metadata'] ? doc['@metadata']['@collection'] : null;\n var templateId = 'templates/' + (collection === 'Drafts' ? doc['@metadata']['@base-collection'] : collection);\n var tpl = loadTemplateByCollectionCached(templateId, tplCache);\n var labelsArr = Array.isArray(doc.labels) ? doc.labels : null;\n\n var result = {\n id: doc['@metadata']['@id'],\n url: buildUrlCached(doc, locale, docCache),\n collection: collection,\n displayName: doc.displayName,\n slug: resolveSlug(doc.slug, locale),\n publishedDate: doc.publishedDate,\n // Pre-existing, separate concern from the root document's own `labels` field\n // below (mapDocument) — this is a relation target's embedded label snapshot and\n // has never carried group-path entries. Left untouched; not in scope here.\n labels: loadRelatedLabels(labelsArr, labelCache) || []\n };\n\n // Fill fields per template (use root locale via populationContext.translate)\n if (tpl && tpl.editors) {\n populateValuesByEditor(doc, tpl, result, populationContext, visit,\n docCache, tplCache, labelCache, groupCache, false, hopsLeft);\n }\n\n return result;\n}\n\nfunction loadRelatedDocumentsRecurse(refGuids, populationContext, visit,\n docCache, tplCache, labelCache, groupCache, hopsLeft) {\n if (!refGuids) return null;\n\n var results = [];\n for (var i = 0; i < refGuids.length; i++) {\n var id = refGuids[i];\n if (!isGuid(id)) continue;\n\n if (isVisiting(visit, id)) continue;\n\n var cached = getCachedNode(visit, id);\n if (cached) { results.push(cached); continue; }\n\n markVisiting(visit, id);\n var doc = loadDocCached(id, docCache);\n\n if (doc) {\n var projected = projectDocumentWithTemplate(doc, populationContext, visit,\n docCache, tplCache, labelCache, groupCache, populationContext.locale, hopsLeft - 1);\n setCachedNode(visit, id, projected);\n results.push(projected);\n }\n\n markDone(visit, id);\n }\n\n return results;\n}\n\nfunction mapDocument(document) {\n var collection = document['@metadata']['@collection'];\n var docId = document['@metadata']['@id'];\n var statusVal = document.status;\n\n // Role detection is dual during the migration window: id-suffix OR the legacy\n // Drafts collection OR the persisted status. Robust whether siblings still live\n // in the Drafts collection (pre-migration) or the canonical collection (post).\n var _isDraft = strEndsWith(docId, '/draft') || collection === 'Drafts' || statusVal === 'draft';\n var _isScheduled = strEndsWith(docId, '/scheduled') || statusVal === 'scheduled';\n var _isSibling = _isDraft || _isScheduled;\n\n var baseCollection = _isSibling ? (document['@metadata']['@base-collection'] || collection) : collection;\n var templateId = 'templates/' + baseCollection;\n var template = load(templateId, 'templates');\n if (!template || !template.editors) return null;\n // Per-status emission: never emit Archived or Deleted (neither the public SDK\n // resolver nor edit-mode preview may see them). Drop by status OR legacy flag.\n if (statusVal === 'deleted' || statusVal === 'archived' || document.deleted || document.archived) return null;\n\n var l10n = loadLocalizationSettings();\n var locales = (l10n && Array.isArray(l10n.locales)) ? l10n.locales : [l10n && l10n.defaultLocale ? l10n.defaultLocale : 'en'];\n\n // Shared per-document caches (shared across locales)\n var _docCache = new Map();\n var _tplCache = new Map();\n var _labelCache = new Map();\n var _groupCache = new Map();\n\n var entries = [];\n\n // Precompute root labels (loaded in helper)\n var rootLabelIds = Array.isArray(document.labels) ? document.labels : null;\n // Computed ONCE per document (not per locale — label/group names aren't localized).\n var _relatedLabels = loadRelatedLabels(rootLabelIds, _labelCache) || [];\n var _labelTerms = buildLabelTerms(_relatedLabels, _groupCache);\n\n // Precompute dependencies (ES5)\n var depsArr = [];\n if (document.dependencies && Array.isArray(document.dependencies)) {\n for (var di = 0; di < document.dependencies.length; di++) {\n var dep = document.dependencies[di];\n if (dep && typeof dep.id === 'string') depsArr.push(dep.id);\n }\n }\n\n // Build a set of localizable root scalar fields from the template\n var localizableRoot = {};\n // Root-level `label`-type fields need forcing into storeAs below: their value is\n // an array of plain strings (buildLabelTerms' output), and the generic classifier\n // right below only auto-stores arrays of PROJECTED OBJECTS (relations/chapters) —\n // a plain string array would otherwise fall to indexAs (query-only), silently\n // breaking selectFields('categories', ...) for exactly the query pattern this\n // field type exists for.\n var forceStoreRoot = {};\n if (template && template.editors) {\n for (var li0 = 0; li0 < template.editors.length; li0++) {\n var ed0 = template.editors[li0];\n if (!ed0 || !ed0.editor || !ed0.editor.propertyName) continue;\n var t0 = ed0.editor.type;\n if (t0 === 'label') { forceStoreRoot[ed0.editor.propertyName] = true; continue; }\n if (t0 !== 'related' && t0 !== 'references' && t0 !== 'content-template' && t0 !== 'chapters') {\n if (ed0.localizable === true) localizableRoot[ed0.editor.propertyName] = true;\n }\n }\n }\n\n for (var li = 0; li < locales.length; li++) {\n\n var currentLocale = locales[li];\n if (document.locales && Array.isArray(document.locales)) {\n var found = false;\n for (var k = 0; k < document.locales.length; k++) {\n if (document.locales[k] === currentLocale) { found = true; break; }\n }\n if (!found) continue;\n }\n\n var translate = getTranslator(currentLocale, l10n.defaultLocale, l10n.fallbackToPrimary);\n var checkLocalized = function (obj) { return isLocalized(obj, locales); };\n\n // Root result (stored fields)\n var result = {\n id: storeAs('id',Id(document)),\n docId: storeAs('docId', document['@metadata']['@id']),\n locale: storeAs('locale', currentLocale),\n displayName: storeAs('displayName',document.displayName),\n url: template.disableURL ? null : storeAs('url', buildUrlCached(document, currentLocale, _docCache, _isSibling)),\n origin: storeAs('origin', document.origin ? document.origin.id : null ),\n collection: storeAs('collection', baseCollection),\n // Legacy boolean kept during the deprecation window so pre-status SDK builds\n // keep matching. Scheduled siblings carry draft:true so the pre-U5 resolver\n // (which filters draft==false) never returns them and cannot throw on a\n // multi-entry URL.\n draft: storeAs('draft', _isSibling),\n // Legacy fallback (status absent) treats a FUTURE publishedDate as unpublished, matching\n // the backfill (StatusMigrationBootstrap) — otherwise a scheduled-via-future-date doc\n // would emit status:'published' and leak publicly during the pre-backfill window.\n status: storeAs('status', statusVal || (_isScheduled ? 'scheduled' : (_isDraft ? 'draft' : ((document.publishedDate && new Date(document.publishedDate) <= new Date()) ? 'published' : 'unpublished')))),\n publishedAt: storeAs('publishedAt', document.publishedDate),\n createdAt: storeAs('createdAt', document.createdAt),\n changedAt: storeAs('changedAt', document.changedAt),\n dependencies: storeAs('dependencies', depsArr),\n labels: storeAs('labels', _labelTerms),\n deleted: indexAs('deleted', document.deleted)\n };\n\n // Visit cache per-locale (keeps shapes deterministic per entry)\n var visit = new Map();\n\n // Build dynamic fields into a temp container using ROOT TEMPLATE (respect localizable)\n var rootContainer = {};\n populateValuesByEditor(document, template, rootContainer,\n { translate: translate, isLocalized: checkLocalized, locale: currentLocale, defaultLocale: l10n.defaultLocale, locales: locales },\n visit, _docCache, _tplCache, _labelCache, _groupCache, true, MAX_RELATION_DEPTH);\n\n // Copy fields from temp container into result\n // - arrays/objects -> storeAs\n // - scalars -> storeAs if localizable, else indexAs\n var keys = [];\n for (var key in rootContainer) {\n if (rootContainer && rootContainer.hasOwnProperty && rootContainer.hasOwnProperty(key)) {\n keys.push(key);\n }\n }\n\n for (var ei = 0; ei < keys.length; ei++) {\n var prop = keys[ei];\n\n // never redefine canonical root fields\n if (RESERVED_ROOT_FIELDS[prop]) continue;\n\n var val = rootContainer[prop];\n var isArray = Array.isArray(val);\n var isProjectedArray = isArray && (val.length === 0 || typeof val[0] === 'object');\n var isProjectedObject = !isArray && typeof val === 'object';\n\n if (isProjectedArray || isProjectedObject || forceStoreRoot[prop] === true) {\n result[prop] = storeAs(prop, val);\n } else {\n if (localizableRoot[prop] === true) {\n result[prop] = storeAs(prop, val); // translated scalar → store so projections return translated value\n } else {\n result[prop] = indexAs(prop, val); // non-localizable scalar → index-only\n }\n }\n }\n\n entries.push(result);\n }\n\n return entries;\n}\n";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@strifeapp/strife",
3
- "version": "1.9.0",
3
+ "version": "1.9.2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -51,8 +51,8 @@
51
51
  "clean": "rimraf dist"
52
52
  },
53
53
  "dependencies": {
54
- "@strifeapp/schema": "^0.9.0",
55
- "@strifeapp/types": "^0.1.2"
54
+ "@strifeapp/schema": "^0.10.0",
55
+ "@strifeapp/types": "^0.2.0"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@rollup/plugin-terser": "^1.0.0",