@murumets-ee/content 0.36.0 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/admin.d.mts +32 -0
  2. package/dist/admin.d.mts.map +1 -0
  3. package/dist/admin.mjs +1 -0
  4. package/dist/chrome-provider.d.mts +107 -0
  5. package/dist/chrome-provider.d.mts.map +1 -0
  6. package/dist/chrome-provider.mjs +2 -0
  7. package/dist/chrome-provider.mjs.map +1 -0
  8. package/dist/client.d.mts +2 -2
  9. package/dist/client.mjs +1 -1
  10. package/dist/client.mjs.map +1 -1
  11. package/dist/content-config-store-8omQF5p5.mjs +2 -0
  12. package/dist/content-config-store-8omQF5p5.mjs.map +1 -0
  13. package/dist/entities/index.d.mts +37 -11
  14. package/dist/entities/index.d.mts.map +1 -1
  15. package/dist/entities/index.mjs +1 -1
  16. package/dist/entities/index.mjs.map +1 -1
  17. package/dist/{index-D8nN7BFd.d.mts → index-Bsmju0UF.d.mts} +30 -3
  18. package/dist/index-Bsmju0UF.d.mts.map +1 -0
  19. package/dist/index.d.mts +4 -4
  20. package/dist/index.mjs +1 -1
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/locale-utils-DFEk4CE9.mjs +2 -0
  23. package/dist/{locale-utils-CuIBKeaz.mjs.map → locale-utils-DFEk4CE9.mjs.map} +1 -1
  24. package/dist/lock-service.d.mts +1 -1
  25. package/dist/plugin-BSxbiGc1.mjs +2 -0
  26. package/dist/plugin-BSxbiGc1.mjs.map +1 -0
  27. package/dist/plugin.d.mts +17 -6
  28. package/dist/plugin.d.mts.map +1 -1
  29. package/dist/plugin.mjs +1 -1
  30. package/dist/query-client.mjs +1 -1
  31. package/dist/resolve-context-BVk6uObB.mjs +2 -0
  32. package/dist/resolve-context-BVk6uObB.mjs.map +1 -0
  33. package/dist/resolve.d.mts +596 -0
  34. package/dist/resolve.d.mts.map +1 -0
  35. package/dist/resolve.mjs +9 -0
  36. package/dist/resolve.mjs.map +1 -0
  37. package/dist/routes-Bc9DcaPk.mjs +2 -0
  38. package/dist/routes-Bc9DcaPk.mjs.map +1 -0
  39. package/dist/{routing-strategy-TPSzrapD.d.mts → routing-strategy-D3Qowm3D.d.mts} +40 -6
  40. package/dist/routing-strategy-D3Qowm3D.d.mts.map +1 -0
  41. package/dist/routing-strategy-dkt2yO25.mjs +2 -0
  42. package/dist/routing-strategy-dkt2yO25.mjs.map +1 -0
  43. package/dist/schema-BJxAwPrI.mjs +2 -0
  44. package/dist/schema-BJxAwPrI.mjs.map +1 -0
  45. package/dist/{types-CUw2Pf0Y.d.mts → types-B3K2p9t8.d.mts} +22 -1
  46. package/dist/types-B3K2p9t8.d.mts.map +1 -0
  47. package/package.json +21 -7
  48. package/dist/index-D8nN7BFd.d.mts.map +0 -1
  49. package/dist/locale-utils-CuIBKeaz.mjs +0 -2
  50. package/dist/plugin-IW9SM9iO.mjs +0 -2
  51. package/dist/plugin-IW9SM9iO.mjs.map +0 -1
  52. package/dist/routing-strategy-DGx6Rq6F.mjs +0 -2
  53. package/dist/routing-strategy-DGx6Rq6F.mjs.map +0 -1
  54. package/dist/routing-strategy-TPSzrapD.d.mts.map +0 -1
  55. package/dist/types-CUw2Pf0Y.d.mts.map +0 -1
@@ -0,0 +1,32 @@
1
+ import { AdminRoute } from "@murumets-ee/core";
2
+
3
+ //#region src/paths/routes.d.ts
4
+ /** The answer shape the caller (admin-ui's editor canvas) consumes. */
5
+ type ResolvePathResponse = /** The path is served by this document. */{
6
+ readonly kind: 'document';
7
+ readonly entityType: string;
8
+ readonly id: string;
9
+ } /** The path redirects OFF-SITE (a `url`-kind manual redirect). */ | {
10
+ readonly kind: 'redirect';
11
+ readonly to: string;
12
+ } /** Nothing the caller may see owns this path. */ | {
13
+ readonly kind: 'none';
14
+ };
15
+ /**
16
+ * Build the path-resolution admin routes. One entry: `GET /paths/resolve`.
17
+ */
18
+ declare function pathRoutes(): AdminRoute[];
19
+ //#endregion
20
+ //#region src/redirects/routes.d.ts
21
+ /**
22
+ * Build the manual-redirect admin API routes.
23
+ *
24
+ * Returns `AdminRoute[]` from `combineAdminRoutes` — one entry for prefix
25
+ * `'redirects'` with GET/POST/PATCH/DELETE handlers. Every entry gates on
26
+ * `redirect:manage` (defaultRoles `['admin']`); the permission is
27
+ * auto-registered into the catalog by `defineAdminRoute`.
28
+ */
29
+ declare function redirectRoutes(): AdminRoute[];
30
+ //#endregion
31
+ export { type ResolvePathResponse, pathRoutes, redirectRoutes };
32
+ //# sourceMappingURL=admin.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin.d.mts","names":[],"sources":["../src/paths/routes.ts","../src/redirects/routes.ts"],"mappings":";;;;KAsEY,mBAAA;WAEG,IAAA;EAAA,SAA2B,UAAA;EAAA,SAA6B,EAAA;AAAA;WAExD,IAAA;EAAA,SAA2B,EAAA;AAAA;WAE3B,IAAA;AAAA;;;;iBAiHC,UAAA,CAAA,GAAc,UAAU;;;;;;;;;;;iBC6VxB,cAAA,CAAA,GAAkB,UAAU"}
package/dist/admin.mjs ADDED
@@ -0,0 +1 @@
1
+ import{n as e,t}from"./routes-Bc9DcaPk.mjs";export{e as pathRoutes,t as redirectRoutes};
@@ -0,0 +1,107 @@
1
+ import { ToolkitApp } from "@murumets-ee/core";
2
+
3
+ //#region src/chrome-provider.d.ts
4
+ /**
5
+ * The slice of `content-api`'s `ResolutionBudget` this provider uses.
6
+ *
7
+ * Structurally mirrored, not imported: `content-api` is the leaf every other
8
+ * type here is mirrored from, for the cycle reason its own docblocks spell out.
9
+ * Narrowed to `claim` on purpose — a provider has no business reading the
10
+ * remaining count or the drop summary; it asks for work and is told how much it
11
+ * may do.
12
+ */
13
+ interface ChromeResolutionBudget {
14
+ claim: (kind: 'block', n: number) => number;
15
+ }
16
+ /** One region as it reaches `content-api`. Structural mirror of `ContentApiChromeRegion`. */
17
+ interface ChromeProviderRegion {
18
+ blocks: unknown[];
19
+ overridden: boolean;
20
+ }
21
+ /** The four named regions. Structural mirror of `ContentApiChromeRegions`. */
22
+ interface ChromeProviderRegions {
23
+ header: ChromeProviderRegion | null;
24
+ beforeContent: ChromeProviderRegion | null;
25
+ afterContent: ChromeProviderRegion | null;
26
+ footer: ChromeProviderRegion | null;
27
+ }
28
+ /** A minimal read seam, so this is testable without booting an app. */
29
+ interface ChromeProviderDeps {
30
+ /**
31
+ * Claim one unit of the response's resolution budget. Returns `false` when
32
+ * the budget refused.
33
+ *
34
+ * Passed as a DEPENDENCY rather than read off `opts` so the honour-the-grant
35
+ * decision lives in the pure half and can be tested without an app. That is
36
+ * not incidental: the first version of this claimed and then read anyway,
37
+ * which fabricated a "budget exhausted" warning on every `/_chrome` request
38
+ * at `maxResolutionsPerDocument: 0` — and it went unnoticed precisely because
39
+ * the claim sat in the untestable half.
40
+ */
41
+ claim?: (() => boolean) | undefined;
42
+ /**
43
+ * Read the site's chrome row for a locale, PUBLISHED-only and locale-MERGED.
44
+ *
45
+ * Must go through the same locale-merging client path every other
46
+ * translatable field uses — never a bespoke reader that special-cases the
47
+ * four region columns. A bespoke reader would have to decide for itself what
48
+ * a missing translation means, and the honest answer (fall back to the base
49
+ * row) is already implemented once; two implementations of a fallback is how
50
+ * the base-versus-translation distinction comes to be answered differently in
51
+ * two places.
52
+ *
53
+ * Returns `null` when no chrome row exists — an unseeded CMS, which is a
54
+ * different answer from an empty one.
55
+ */
56
+ readSiteLayout: (locale: string) => Promise<Record<string, unknown> | null>;
57
+ /**
58
+ * Read one document's `_overrides` map, PUBLISHED-only and honouring the
59
+ * caller's permissions. `null` for a document that does not exist or is not
60
+ * visible — both collapse to "no overrides", per D026.
61
+ */
62
+ readOverrides: (entityType: string, id: string, locale: string) => Promise<Record<string, unknown> | null>;
63
+ logger?: {
64
+ warn: (obj: object, msg: string) => void;
65
+ } | undefined;
66
+ }
67
+ /**
68
+ * Compose the four chrome regions for a locale, optionally overridden by one
69
+ * document.
70
+ *
71
+ * The two forms COLLAPSE STRUCTURALLY: `applyOverrides` returns
72
+ * `composed.slice()` when the override map is empty or nothing in it applies,
73
+ * so a document that overrides nothing produces output byte-identical to the
74
+ * no-document form. That is a property of shipped code, not a cache rule anyone
75
+ * maintains — and it matters today more than it will later, because no document
76
+ * overrides anything yet on any site.
77
+ */
78
+ declare function composeChromeRegions(opts: {
79
+ locale: string;
80
+ document?: {
81
+ entityType: string;
82
+ id: string;
83
+ };
84
+ }, deps: ChromeProviderDeps): Promise<ChromeProviderRegions | null>;
85
+ /**
86
+ * Wire {@link composeChromeRegions} to the running app — the value an app puts
87
+ * into `createContentApiHandler({ chrome })`.
88
+ *
89
+ * Both reads go through `QueryClient`, so the publish filter, the `view` gate
90
+ * and locale merging all apply. Never `AdminClient`: an admin read here would
91
+ * surface an unpublished header, or one document's overrides to a caller who
92
+ * cannot see that document.
93
+ */
94
+ declare function createChromeProvider(getApp: () => Promise<ToolkitApp> | ToolkitApp): {
95
+ entity: string;
96
+ getChrome: (opts: {
97
+ locale: string;
98
+ document?: {
99
+ entityType: string;
100
+ id: string;
101
+ };
102
+ budget: ChromeResolutionBudget;
103
+ }) => Promise<ChromeProviderRegions | null>;
104
+ };
105
+ //#endregion
106
+ export { ChromeProviderDeps, ChromeProviderRegion, ChromeProviderRegions, ChromeResolutionBudget, composeChromeRegions, createChromeProvider };
107
+ //# sourceMappingURL=chrome-provider.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chrome-provider.d.mts","names":[],"sources":["../src/chrome-provider.ts"],"mappings":";;;;;;;AAkE8B;AAmB9B;;;;UAlCiB,sBAAA;EACf,KAAA,GAAQ,IAAA,WAAe,CAAA;AAAA;;UAIR,oBAAA;EACf,MAAA;EACA,UAAU;AAAA;;UAIK,qBAAA;EACf,MAAA,EAAQ,oBAAA;EACR,aAAA,EAAe,oBAAA;EACf,YAAA,EAAc,oBAAA;EACd,MAAA,EAAQ,oBAAA;AAAA;;UAmBO,kBAAA;EAsCJ;;;;AAA+B;AAoB5C;;;;;;EA9CE,KAAA;EAiDQ;;;;;;;;;;;AAAsB;AA0FhC;;EA5HE,cAAA,GAAiB,MAAA,aAAmB,OAAA,CAAQ,MAAA;EA4Ha;;;;;EAtHzD,aAAA,GACE,UAAA,UACA,EAAA,UACA,MAAA,aACG,OAAA,CAAQ,MAAA;EACb,MAAA;IAAW,IAAA,GAAO,GAAA,UAAa,GAAA;EAAA;AAAA;;;;;;;;;;;;iBAoBX,oBAAA,CACpB,IAAA;EAAQ,MAAA;EAAgB,QAAA;IAAa,UAAA;IAAoB,EAAA;EAAA;AAAA,GACzD,IAAA,EAAM,kBAAA,GACL,OAAA,CAAQ,qBAAA;;;;;;;;;;iBA0FK,oBAAA,CAAqB,MAAA,QAAc,OAAA,CAAQ,UAAA,IAAc,UAAA;EACvE,MAAA;EACA,SAAA,GAAY,IAAA;IACV,MAAA;IACA,QAAA;MAAa,UAAA;MAAoB,EAAA;IAAA;IACjC,MAAA,EAAQ,sBAAA;EAAA,MACJ,OAAA,CAAQ,qBAAA;AAAA"}
@@ -0,0 +1,2 @@
1
+ import{composeChromeRegion as e,stripScopeTags as t}from"@murumets-ee/blocks/render";const n=[[`header`,`headerBlocks`],[`beforeContent`,`beforeContentBlocks`],[`afterContent`,`afterContentBlocks`],[`footer`,`footerBlocks`]];function r(e,t){let n=e[t];return Array.isArray(n)?n:[]}async function i(i,a){if(a.claim&&!a.claim())return null;let o=await a.readSiteLayout(i.locale);if(!o)return null;let s=typeof o.id==`string`?o.id:`site`,c=i.document&&(!a.claim||a.claim())?await a.readOverrides(i.document.entityType,i.document.id,i.locale):null,l={header:null,beforeContent:null,afterContent:null,footer:null};for(let[u,d]of n){let n={id:s,blocks:r(o,d)};try{let r=e(n,c);if(!r)continue;l[u]={blocks:t(r.blocks),overridden:r.overridden}}catch(e){a.logger?.warn({region:u,siteLayoutId:s,locale:i.locale,err:e},`content: chrome region failed to compose (malformed stored blocks, or strip depth exceeded) — serving no chrome for this region`)}}return l}function a(e){return{entity:`site_layout`,getChrome:async t=>{let n=await e(),{createQueryClient:r}=await import(`@murumets-ee/core/clients`),a=async(e,t,i,a)=>{let o=n.entities.get(e);return o?(await r(o).findMany({limit:1,locale:t,...i!==void 0&&{where:i},...a!==void 0&&{exposeInternalFields:a}}))[0]??null:null};return i(t,{claim:()=>t.budget.claim(`block`,1)>0,readSiteLayout:e=>a(`site_layout`,e),readOverrides:async(e,t,n)=>{let{schemaRegistry:r}=await import(`@murumets-ee/db`),{eq:i,getTableColumns:o}=await import(`drizzle-orm`),s=r.get(e);if(!s)return null;let c=o(s)?.id;if(!c)return null;let l=await a(e,n,i(c,t),[`_overrides`]);if(!l)return null;let u=l._overrides;return u&&typeof u==`object`&&!Array.isArray(u)?u:null},logger:n.logger})}}}export{i as composeChromeRegions,a as createChromeProvider};
2
+ //# sourceMappingURL=chrome-provider.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chrome-provider.mjs","names":[],"sources":["../src/chrome-provider.ts"],"sourcesContent":["/**\n * The site-chrome provider for `@murumets-ee/content-api`'s `GET /_chrome`\n * (plan/api phase 07, D011/D026/D027).\n *\n * This is the half of chrome composition `content-api` structurally cannot do:\n * it reads `site_layout`, reads the requesting document's `_overrides`, and\n * runs `@murumets-ee/blocks`' composer. `content-api` is a leaf — `blocks`\n * depends back on it — so the API takes this as an injected provider, exactly\n * as it takes routing resolution and menus.\n *\n * **The pipeline is tag → apply → strip, and no step is optional.**\n * `tagScopedRegion` writes `scope`/`scopeDocId`/`scopeBlockId` onto every\n * top-level block, and `compose-chrome.ts` documents that tagging is\n * `applyOverrides`' PRECONDITION: hand it an untagged document and it returns\n * the blocks unchanged, with no error, no warning and zero overrides applied.\n * An API that skipped the tagging would serve un-overridden chrome while the\n * editor canvas showed overridden chrome — the canvas lying about what ships,\n * which is the failure `plan/site-chrome`'s \"one pure composition helper both\n * sides call\" exists to close. So `composeChromeRegion` is used as-is, and its\n * tags are removed afterwards through the existing `stripScopeTags` rather than\n * a second implementation of \"remove these keys, recursively\".\n *\n * **`stripScopeTags`' depth throw degrades here; it does not 500.** It raises\n * `BlockNestingTooDeepError` above 64. On `op-commit` that is correct — the\n * input is caller-supplied and the answer is 413. On a READ path the input is a\n * STORED tree, so a throw means something persisted deeper than 64 by a path\n * that bypassed op-commit (raw SQL, a seed, a migration), and it must not take\n * every page on the site down. The region degrades to `null` with an operator\n * log naming the document — phase 05's failure semantics, applied unchanged.\n */\n\nimport {\n composeChromeRegion,\n stripScopeTags,\n type ChromeDocumentSnapshot,\n type OverridesMap,\n} from '@murumets-ee/blocks/render'\nimport type { ToolkitApp } from '@murumets-ee/core'\nimport type { QueryClient } from '@murumets-ee/entity/query'\nimport type { SQL } from 'drizzle-orm'\nimport type { PgColumn } from 'drizzle-orm/pg-core'\n\n/**\n * The slice of `content-api`'s `ResolutionBudget` this provider uses.\n *\n * Structurally mirrored, not imported: `content-api` is the leaf every other\n * type here is mirrored from, for the cycle reason its own docblocks spell out.\n * Narrowed to `claim` on purpose — a provider has no business reading the\n * remaining count or the drop summary; it asks for work and is told how much it\n * may do.\n */\nexport interface ChromeResolutionBudget {\n claim: (kind: 'block', n: number) => number\n}\n\n/** One region as it reaches `content-api`. Structural mirror of `ContentApiChromeRegion`. */\nexport interface ChromeProviderRegion {\n blocks: unknown[]\n overridden: boolean\n}\n\n/** The four named regions. Structural mirror of `ContentApiChromeRegions`. */\nexport interface ChromeProviderRegions {\n header: ChromeProviderRegion | null\n beforeContent: ChromeProviderRegion | null\n afterContent: ChromeProviderRegion | null\n footer: ChromeProviderRegion | null\n}\n\n/**\n * The four regions, paired with the `site_layout` column each reads.\n *\n * Declared as data rather than as four call sites so the wire names and the\n * entity's own field names cannot drift apart — a reader of either can predict\n * the other, which is the property phase 07 relies on to stop the region model\n * forking.\n */\nconst REGIONS = [\n ['header', 'headerBlocks'],\n ['beforeContent', 'beforeContentBlocks'],\n ['afterContent', 'afterContentBlocks'],\n ['footer', 'footerBlocks'],\n] as const\n\n/** A minimal read seam, so this is testable without booting an app. */\nexport interface ChromeProviderDeps {\n /**\n * Claim one unit of the response's resolution budget. Returns `false` when\n * the budget refused.\n *\n * Passed as a DEPENDENCY rather than read off `opts` so the honour-the-grant\n * decision lives in the pure half and can be tested without an app. That is\n * not incidental: the first version of this claimed and then read anyway,\n * which fabricated a \"budget exhausted\" warning on every `/_chrome` request\n * at `maxResolutionsPerDocument: 0` — and it went unnoticed precisely because\n * the claim sat in the untestable half.\n */\n claim?: (() => boolean) | undefined\n /**\n * Read the site's chrome row for a locale, PUBLISHED-only and locale-MERGED.\n *\n * Must go through the same locale-merging client path every other\n * translatable field uses — never a bespoke reader that special-cases the\n * four region columns. A bespoke reader would have to decide for itself what\n * a missing translation means, and the honest answer (fall back to the base\n * row) is already implemented once; two implementations of a fallback is how\n * the base-versus-translation distinction comes to be answered differently in\n * two places.\n *\n * Returns `null` when no chrome row exists — an unseeded CMS, which is a\n * different answer from an empty one.\n */\n readSiteLayout: (locale: string) => Promise<Record<string, unknown> | null>\n /**\n * Read one document's `_overrides` map, PUBLISHED-only and honouring the\n * caller's permissions. `null` for a document that does not exist or is not\n * visible — both collapse to \"no overrides\", per D026.\n */\n readOverrides: (\n entityType: string,\n id: string,\n locale: string,\n ) => Promise<Record<string, unknown> | null>\n logger?: { warn: (obj: object, msg: string) => void } | undefined\n}\n\n/** Read a region's stored blocks off the chrome row. */\nfunction regionBlocks(row: Record<string, unknown>, column: string): unknown[] {\n const value = row[column]\n return Array.isArray(value) ? value : []\n}\n\n/**\n * Compose the four chrome regions for a locale, optionally overridden by one\n * document.\n *\n * The two forms COLLAPSE STRUCTURALLY: `applyOverrides` returns\n * `composed.slice()` when the override map is empty or nothing in it applies,\n * so a document that overrides nothing produces output byte-identical to the\n * no-document form. That is a property of shipped code, not a cache rule anyone\n * maintains — and it matters today more than it will later, because no document\n * overrides anything yet on any site.\n */\nexport async function composeChromeRegions(\n opts: { locale: string; document?: { entityType: string; id: string } },\n deps: ChromeProviderDeps,\n): Promise<ChromeProviderRegions | null> {\n // Claimed BEFORE the read, and HONOURED. \"Resolve nothing on this\n // deployment\" is a coherent operator posture, and `null` is how this provider\n // already says \"no chrome document\" — the consumer renders its own fallback,\n // and the drop the handler then reports is true.\n if (deps.claim && !deps.claim()) return null\n\n const row = await deps.readSiteLayout(opts.locale)\n // No chrome document at all. `null`, never four empty regions: \"render your\n // own fallback\" and \"the page hid everything\" must stay distinguishable.\n if (!row) return null\n\n const documentId = typeof row.id === 'string' ? row.id : 'site'\n // The SECOND read, claimed and honoured like the first — but ONLY when a\n // document was actually named. Claiming unconditionally burns a unit for a\n // read that never runs, and on a tight budget records a drop the operator log\n // then reports as truncation when nothing was truncated. `&&` short-circuits,\n // so the claim is reached only on the path that performs the read.\n //\n // A refusal degrades to \"no overrides\", which is byte-identical to the\n // site-default form — the same outcome D026 already specifies for a document\n // this caller cannot reach. The budget shortens the work without inventing a\n // new response shape.\n const overrides =\n opts.document && (!deps.claim || deps.claim())\n ? await deps.readOverrides(opts.document.entityType, opts.document.id, opts.locale)\n : null\n\n const out: ChromeProviderRegions = {\n header: null,\n beforeContent: null,\n afterContent: null,\n footer: null,\n }\n\n for (const [region, column] of REGIONS) {\n const snapshot: ChromeDocumentSnapshot = {\n id: documentId,\n blocks: regionBlocks(row, column) as ChromeDocumentSnapshot['blocks'],\n }\n // `as OverridesMap | null`, never `as never`: `as never` is assignable to\n // everything, so it disables the checker rather than narrowing. The\n // narrowing IS justified here — the value comes from a JSONB column, and\n // `composeChromeRegion`'s own `asOverridesMap` guard rejects anything that\n // is not a plain object — but the cast should say which type it is\n // asserting.\n try {\n // INSIDE the try, not before it. `composeChromeRegion` → `tagScopedRegion`\n // dereferences `block.id`, so a stored array containing a null or a\n // scalar throws here — and since `handleChrome` deliberately 500s on a\n // provider throw, that would take every page on the site down for one\n // malformed region. Same degradation rule as the strip-depth throw below.\n const composed = composeChromeRegion(snapshot, overrides as OverridesMap | null)\n if (!composed) continue\n out[region] = {\n // D027 — the composition's own vocabulary is removed at the\n // serialization boundary, through the ONE existing definition.\n // `scopeBlockId` in particular is the key `_overrides` is written\n // under, so serialising it would hand a consumer the override map's key\n // space immediately after D011 banned the map.\n blocks: stripScopeTags(composed.blocks),\n overridden: composed.overridden,\n }\n } catch (err) {\n // TWO failure modes reach here, and the message must not name only one:\n // `tagScopedRegion` dereferences `block.id`, so a malformed stored array\n // throws during composition; and `stripScopeTags` throws above\n // MAX_STRIP_DEPTH. An operator reading this line for the first cause\n // would otherwise be sent looking at nesting depth. Either way one region\n // degrades and the site does not go down.\n deps.logger?.warn(\n { region, siteLayoutId: documentId, locale: opts.locale, err },\n 'content: chrome region failed to compose (malformed stored blocks, or strip depth ' +\n 'exceeded) — serving no chrome for this region',\n )\n }\n }\n\n return out\n}\n\n/**\n * Wire {@link composeChromeRegions} to the running app — the value an app puts\n * into `createContentApiHandler({ chrome })`.\n *\n * Both reads go through `QueryClient`, so the publish filter, the `view` gate\n * and locale merging all apply. Never `AdminClient`: an admin read here would\n * surface an unpublished header, or one document's overrides to a caller who\n * cannot see that document.\n */\nexport function createChromeProvider(getApp: () => Promise<ToolkitApp> | ToolkitApp): {\n entity: string\n getChrome: (opts: {\n locale: string\n document?: { entityType: string; id: string }\n budget: ChromeResolutionBudget\n }) => Promise<ChromeProviderRegions | null>\n} {\n return {\n entity: 'site_layout',\n getChrome: async (opts) => {\n // TWO reads at most, each CLAIMED AND HONOURED against the response's\n // ONE budget.\n //\n // Honouring it is the whole point — an earlier version claimed and then\n // read regardless, which was worse than not claiming at all: at\n // `maxResolutionsPerDocument: 0` (a posture the budget explicitly\n // supports) the claim came up short, so the handler logged \"budget\n // exhausted — response rendered with unresolved targets\" on every\n // `/_chrome` request while serving fully-composed chrome. That log is\n // documented as the ONLY place a dropped resolution survives, so\n // fabricating a drop degrades precisely the signal it exists to carry.\n //\n // The blocks INSIDE these regions are a different matter, and this is\n // where the provider is honest rather than complete: their links,\n // `query()` fields and media refs are NOT resolved, because the wire\n // format for a resolved block tree is not settled (see the finding filed\n // against `plan/api` PR 5 — the seam ships, the format is a decision).\n // When it is, the resolution goes here and draws on this same `budget`.\n //\n // The accounting lives ENTIRELY in `composeChromeRegions`, via the\n // injected `claim` below. An earlier version also claimed here, so two\n // reads cost three units — which reproduced the fabricated-drop failure\n // this comment describes, one step over: at\n // `maxResolutionsPerDocument: 2` with a document, the pre-claim took unit\n // one, the layout read took unit two, and the override claim was refused,\n // logging \"budget exhausted\" for a request that needed exactly the two\n // units it was given. One owner, one claim per read.\n const app = await getApp()\n const { createQueryClient } = await import('@murumets-ee/core/clients')\n\n const readOne = async (\n entityName: string,\n locale: string,\n where?: unknown,\n exposeInternalFields?: readonly string[],\n ): Promise<Record<string, unknown> | null> => {\n const entity = app.entities.get(entityName)\n if (!entity) return null\n const client = createQueryClient(entity) as QueryClient\n const rows = await client.findMany({\n limit: 1,\n locale,\n ...(where !== undefined && { where: where as SQL }),\n ...(exposeInternalFields !== undefined && { exposeInternalFields }),\n })\n return (rows[0] as Record<string, unknown> | undefined) ?? null\n }\n\n return composeChromeRegions(opts, {\n claim: () => opts.budget.claim('block', 1) > 0,\n // NO `orderBy` — `QueryClient.findMany` already falls back to\n // `singletonReadOrder()` for a `kind: 'singleton'` entity, and passing\n // one here would WIN over that fallback. On a site that wrongly has two\n // chrome rows, this endpoint would then disagree with every other read\n // path about which row IS the singleton.\n readSiteLayout: (locale) => readOne('site_layout', locale),\n readOverrides: async (entityType, id, locale) => {\n const { schemaRegistry } = await import('@murumets-ee/db')\n const { eq, getTableColumns } = await import('drizzle-orm')\n const table = schemaRegistry.get(entityType)\n if (!table) return null\n // Column refs come from drizzle's own typed accessor, never from a\n // bare `table.id` — the registry hands back a dynamically-generated\n // table whose property access is untyped, so reaching through it puts\n // an `any` into the WHERE clause. Same rule as the sibling media\n // resolver; two files in one package stating opposite rules is how a\n // rule stops being followed.\n // `?.` because `getTableColumns` reads a drizzle symbol off the\n // value: a registry entry that is not a real pgTable yields\n // `undefined`, and reaching `.id` through it would THROW. The old\n // untyped access degraded to \"no overrides\" instead, and since\n // `handleChrome` deliberately 500s on a provider throw, losing that\n // tolerance would escalate a malformed registry entry from\n // \"site-default chrome\" to \"every /_chrome request 500s\".\n const columns = getTableColumns(table) as Record<string, PgColumn> | undefined\n const idColumn = columns?.id\n if (!idColumn) return null\n // `_overrides` is `internal: true`, so a plain read strips it — which\n // is exactly right for the WIRE and exactly wrong here, because this\n // is the one place that has to READ the map in order to apply it.\n //\n // `exposeInternalFields: ['_overrides']` is the narrow, per-name\n // form: `_workflowStatus`, `_version` and the rest stay hidden, and\n // the all-or-nothing `includeInternal` switch is never set. Note this\n // is the ENTITY-level option, which survives D024 — what D024 removed\n // was `ContentApiConfig.exposeInternalFields`, the per-DEPLOYMENT\n // config key that put an internal field on the WIRE. Here the value\n // never leaves this function: it is consumed by `applyOverrides` and\n // the composed output carries no trace of it, which is precisely the\n // guarantee D011 makes and D024 made unconditional.\n const row = await readOne(entityType, locale, eq(idColumn, id), ['_overrides'])\n if (!row) return null\n const map = row._overrides\n return map && typeof map === 'object' && !Array.isArray(map)\n ? (map as Record<string, unknown>)\n : null\n },\n logger: app.logger,\n })\n },\n }\n}\n"],"mappings":"qFA6EA,MAAM,EAAU,CACd,CAAC,SAAU,cAAc,EACzB,CAAC,gBAAiB,qBAAqB,EACvC,CAAC,eAAgB,oBAAoB,EACrC,CAAC,SAAU,cAAc,CAC3B,EA6CA,SAAS,EAAa,EAA8B,EAA2B,CAC7E,IAAM,EAAQ,EAAI,GAClB,OAAO,MAAM,QAAQ,CAAK,EAAI,EAAQ,CAAC,CACzC,CAaA,eAAsB,EACpB,EACA,EACuC,CAKvC,GAAI,EAAK,OAAS,CAAC,EAAK,MAAM,EAAG,OAAO,KAExC,IAAM,EAAM,MAAM,EAAK,eAAe,EAAK,MAAM,EAGjD,GAAI,CAAC,EAAK,OAAO,KAEjB,IAAM,EAAa,OAAO,EAAI,IAAO,SAAW,EAAI,GAAK,OAWnD,EACJ,EAAK,WAAa,CAAC,EAAK,OAAS,EAAK,MAAM,GACxC,MAAM,EAAK,cAAc,EAAK,SAAS,WAAY,EAAK,SAAS,GAAI,EAAK,MAAM,EAChF,KAEA,EAA6B,CACjC,OAAQ,KACR,cAAe,KACf,aAAc,KACd,OAAQ,IACV,EAEA,IAAK,GAAM,CAAC,EAAQ,KAAW,EAAS,CACtC,IAAM,EAAmC,CACvC,GAAI,EACJ,OAAQ,EAAa,EAAK,CAAM,CAClC,EAOA,GAAI,CAMF,IAAM,EAAW,EAAoB,EAAU,CAAgC,EAC/E,GAAI,CAAC,EAAU,SACf,EAAI,GAAU,CAMZ,OAAQ,EAAe,EAAS,MAAM,EACtC,WAAY,EAAS,UACvB,CACF,OAAS,EAAK,CAOZ,EAAK,QAAQ,KACX,CAAE,SAAQ,aAAc,EAAY,OAAQ,EAAK,OAAQ,KAAI,EAC7D,iIAEF,CACF,CACF,CAEA,OAAO,CACT,CAWA,SAAgB,EAAqB,EAOnC,CACA,MAAO,CACL,OAAQ,cACR,UAAW,KAAO,IAAS,CA4BzB,IAAM,EAAM,MAAM,EAAO,EACnB,CAAE,qBAAsB,MAAM,OAAO,6BAErC,EAAU,MACd,EACA,EACA,EACA,IAC4C,CAC5C,IAAM,EAAS,EAAI,SAAS,IAAI,CAAU,EAS1C,OARK,GAQG,MAPO,EAAkB,CACT,CAAC,CAAC,SAAS,CACjC,MAAO,EACP,SACA,GAAI,IAAU,IAAA,IAAa,CAAS,OAAa,EACjD,GAAI,IAAyB,IAAA,IAAa,CAAE,sBAAqB,CACnE,CAAC,EAAA,CACY,IAA8C,KARvC,IAStB,EAEA,OAAO,EAAqB,EAAM,CAChC,UAAa,EAAK,OAAO,MAAM,QAAS,CAAC,EAAI,EAM7C,eAAiB,GAAW,EAAQ,cAAe,CAAM,EACzD,cAAe,MAAO,EAAY,EAAI,IAAW,CAC/C,GAAM,CAAE,kBAAmB,MAAM,OAAO,mBAClC,CAAE,KAAI,mBAAoB,MAAM,OAAO,eACvC,EAAQ,EAAe,IAAI,CAAU,EAC3C,GAAI,CAAC,EAAO,OAAO,KAenB,IAAM,EADU,EAAgB,CACT,CAAC,EAAE,GAC1B,GAAI,CAAC,EAAU,OAAO,KActB,IAAM,EAAM,MAAM,EAAQ,EAAY,EAAQ,EAAG,EAAU,CAAE,EAAG,CAAC,YAAY,CAAC,EAC9E,GAAI,CAAC,EAAK,OAAO,KACjB,IAAM,EAAM,EAAI,WAChB,OAAO,GAAO,OAAO,GAAQ,UAAY,CAAC,MAAM,QAAQ,CAAG,EACtD,EACD,IACN,EACA,OAAQ,EAAI,MACd,CAAC,CACH,CACF,CACF"}
package/dist/client.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { d as TranslationReport, f as TranslationStatus, n as DraftEntry, p as VersionEntry, s as LocalePublishStatus } from "./types-CUw2Pf0Y.mjs";
2
- import { u as FieldDiff } from "./index-D8nN7BFd.mjs";
1
+ import { d as TranslationReport, f as TranslationStatus, n as DraftEntry, p as VersionEntry, s as LocalePublishStatus } from "./types-B3K2p9t8.mjs";
2
+ import { f as FieldDiff } from "./index-Bsmju0UF.mjs";
3
3
  import { Entity, FieldConfig } from "@murumets-ee/entity";
4
4
  import { Logger } from "@murumets-ee/core";
5
5
  import { AdminClient, FindManyOptions, InferCreateInput, InferEntityDTO, InferUpdateInput } from "@murumets-ee/entity/admin";
package/dist/client.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{t as e}from"./versionable-Br_G5VGR.mjs";import{c as t,l as n,r}from"./routing-strategy-DGx6Rq6F.mjs";import{n as i,r as a}from"./plugin-IW9SM9iO.mjs";import{t as o}from"./locale-utils-CuIBKeaz.mjs";import{getApp as s}from"@murumets-ee/core";import"server-only";import{createAdminClient as c}from"@murumets-ee/core/clients";import{schemaRegistry as l}from"@murumets-ee/db";import{eq as u,inArray as d}from"drizzle-orm";function f(e){return e.match(/\S+|\s+/g)??[]}function p(e,t){if(e===t)return e?[{type:`equal`,value:e}]:[];if(!e)return[{type:`add`,value:t}];if(!t)return[{type:`remove`,value:e}];let n=f(e),r=f(t),i=n.length,a=r.length,o=Array.from({length:i+1},()=>Array(a+1).fill(0));for(let e=1;e<=i;e++){let t=o[e],i=o[e-1];if(!(!t||!i))for(let o=1;o<=a;o++)n[e-1]===r[o-1]?t[o]=(i[o-1]??0)+1:t[o]=Math.max(i[o]??0,t[o-1]??0)}let s=[],c=i,l=a,u=[];for(;c>0||l>0;){let e=c>0?n[c-1]:void 0,t=l>0?r[l-1]:void 0;if(c>0&&l>0&&e!==void 0&&e===t)u.push({type:`equal`,value:e}),c--,l--;else if(l>0&&t!==void 0&&(c===0||(o[c]?.[l-1]??0)>=(o[c-1]?.[l]??0)))u.push({type:`add`,value:t}),l--;else if(e!==void 0)u.push({type:`remove`,value:e}),c--;else break}u.reverse();for(let e of u){let t=s[s.length-1];t&&t.type===e.type?t.value+=e.value:s.push({...e})}return s}function m(e){return e.replace(/<[^>]*>/g,` `).replace(/\s+/g,` `).trim()}function h(e){return e==null?``:e instanceof Date?e.toISOString():typeof e==`object`?JSON.stringify(e):String(e)}function g(e,t,n,r){if(JSON.stringify(t)===JSON.stringify(n))return null;switch(r?.type??v(t,n)){case`text`:case`slug`:return{field:e,type:`text`,from:t,to:n,segments:p(h(t),h(n))};case`richtext`:return{field:e,type:`richtext`,from:t,to:n,segments:p(m(h(t)),m(h(n)))};case`number`:return{field:e,type:`number`,from:t,to:n};case`boolean`:return{field:e,type:`boolean`,from:t,to:n};case`date`:return{field:e,type:`date`,from:t,to:n};case`select`:return{field:e,type:`select`,from:t,to:n};case`media`:return{field:e,type:`media`,from:t,to:n};case`json`:return{field:e,type:`json`,from:t,to:n};case`blockTree`:return{field:e,type:`blockTree`,from:t,to:n};default:return{field:e,type:`unknown`,from:t,to:n}}}function _(e,t,n){let r=new Set([...Object.keys(e),...Object.keys(t)]),i=[];for(let a of r){if(a===`id`||a===`createdAt`||a===`updatedAt`)continue;let r=g(a,e[a],t[a],n?.[a]);r&&i.push(r)}return i}function v(e,t){let n=e??t;if(typeof n==`number`)return`number`;if(typeof n==`boolean`)return`boolean`;if(n instanceof Date)return`date`;if(typeof n==`string`)return/^\d{4}-\d{2}-\d{2}/.test(n)?`date`:`text`;if(Array.isArray(n)){let e=n[0];return n.length>0&&typeof e==`object`&&e&&`type`in e&&`id`in e?`blockTree`:`json`}return typeof n==`object`?`json`:`unknown`}function y(e,t){if(typeof e==`string`||typeof t==`string`)return p(h(e),h(t))}function b(e){if(typeof e!=`object`||!e)return null;let t=e;if(typeof t.id!=`string`||typeof t.type!=`string`)return null;let n={...t.props??{}};return t.traits!==void 0&&(n.traits=t.traits),t.children!==void 0&&(n.children=t.children),{id:t.id,type:t.type,props:n}}function x(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]),r=[];for(let i of n){let n=e[i],a=t[i];JSON.stringify(n)!==JSON.stringify(a)&&r.push({field:i,from:n,to:a,segments:y(n,a)})}return r}function S(e){let t=new Map,n=0;for(let r of e){let e=b(r);e&&(t.set(e.id,{block:e,index:n}),n++)}return t}function C(e,t){let n=S(e),r=S(t),i=[],a=new Set;for(let[e,{block:t,index:o}]of r){a.add(e);let r=n.get(e);if(!r){i.push({blockId:e,blockType:t.type,status:`added`,toIndex:o});continue}let s=r.block,c=r.index,l=x(s.props,t.props),u=c!==o;l.length===0&&!u?i.push({blockId:e,blockType:t.type,status:`unchanged`,fromIndex:c,toIndex:o}):l.length>0?i.push({blockId:e,blockType:t.type,status:`changed`,fromIndex:c,toIndex:o,propDiffs:l}):i.push({blockId:e,blockType:t.type,status:`moved`,fromIndex:c,toIndex:o})}for(let[e,{block:t,index:r}]of n)a.has(e)||i.push({blockId:e,blockType:t.type,status:`removed`,fromIndex:r});return i}function w(e,t){return{entity:e.entity,admin:e.admin,db:e.admin.getDb(),versionableOptions:t,logger:e.logger}}function T(e){let{drafts:t}=a(e.entity.name);return t?t.makeClient(e.db):null}function E(e){let{versions:t}=a(e.entity.name);return t?t.makeClient(e.db):null}function D(e){let{localeStatus:t}=a(e.entity.name);return t?t.makeClient(e.db):null}async function O(e,t){let n=(await A(e,[t])).get(t);return n?Object.values(n):[]}async function k(e,t,a,s){if(o(a),a===r().defaultLocale){let n={status:s};s===`published`&&(n.publishedAt=new Date),await e.admin.update(t,n);return}let c=D(e);if(!c)throw new n(e.entity.name,`localeStatus`);await i(c,t,a,s)}async function A(e,t){let n=new Map;if(t.length===0)return n;let r=D(e);if(!r)return n;let i=await r.findMany({where:{entityId:{in:t}}});for(let e of i){let t=e.entityId,r=e.locale,i=n.get(t);i||(i={},n.set(t,i)),i[r]={locale:r,status:e.status,publishedAt:e.publishedAt??null}}return n}function ee(e){return e===`_`||e===r().defaultLocale}function j(e){let{defaultLocale:t}=r();return ee(e)?{defaultLocale:t}:{locale:e,defaultLocale:t}}async function M(e,t,n,i){let a=D(e)!==null;return e.admin.updateForLocale(t,n,i,{defaultLocale:r().defaultLocale,...a&&{localeStatusWriter:(t,n,r)=>k(e,t,n,r)}})}const N=new Set([`id`,`createdAt`,`createdBy`,`updatedAt`,`updatedBy`]);function P(e,t){let n=e.allFields,r={};for(let e of Object.keys(t)){if(N.has(e)||e.startsWith(`_`)||!Object.hasOwn(n,e))continue;let i=n[e];i&&i.internal===!0||(r[e]=t[e])}return r}function te(e,t){return JSON.stringify(e)===JSON.stringify(t)}function F(e,t){let n={};for(let r of Object.keys(t))r===`id`||r.startsWith(`_`)||te(e[r],t[r])||(n[r]=t[r]);return n}async function I(e,t,n){n?.locale!==void 0&&o(n.locale);let r=E(e);return r?await r.findMany({where:{entityId:t,locale:n?.locale??`_`},orderBy:[{column:`version`,dir:`desc`}],...n?.limit===void 0?{}:{limit:n.limit},...n?.offset===void 0?{}:{offset:n.offset}}):[]}async function L(e,t,n,r=`_`){o(r);let i=E(e);return i?await i.findOne({entityId:t,version:n,locale:r})??null:null}async function R(e,t,n,r,i=`_`){o(i);let[a,s]=await Promise.all([L(e,t,n,i),L(e,t,r,i)]);if(!a||!s)return null;let c=e.entity.allFields,l=_(a.data,s.data,c);for(let e of l)e.type===`blockTree`&&(e.blockDiffs=C(Array.isArray(e.from)?e.from:[],Array.isArray(e.to)?e.to:[]));return{diffs:l,fromVersion:a,toVersion:s}}async function z(e,t,r,i=`_`){if(o(i),!E(e))throw new n(e.entity.name,`versions`);let a=await L(e,t,r,i);if(!a)throw Error(`Version ${r} not found for entity ${t}`);let s=a.data;return M(e,t,P(e.entity,s),i)}async function B(e,t,n=`_`){if(o(n),!e.versionableOptions)return;let r=E(e);if(!r)return;let i=e.versionableOptions.maxPerDoc,a=await r.findMany({where:{entityId:t,locale:n},orderBy:[{column:`version`,dir:`desc`}],limit:i});if(a.length<i)return;let s=a.map(e=>e.id);await r.deleteMany({entityId:t,locale:n,id:{notIn:s}})}async function V(e,t,n,r,i,a=`_`,s){o(a);let c=E(e);if(!c)return;let[l]=await c.aggregate({select:{maxVersion:{fn:`max`,column:`version`}},where:{entityId:t,locale:a}}),u=Number(l?.maxVersion??0)+1,d=n.status??null;if(i){let e=await c.findOne({entityId:t,isAutosave:!0,version:u,locale:a});if(e)return await c.update({id:e.id},{data:n,delta:r,status:d,createdAt:new Date}),u}return await c.insert({entityId:t,version:u,locale:a,data:n,delta:r,status:d,createdBy:s?.id??null,createdByName:s?.name??null,isAutosave:i}),u}async function H(e,t,n,r,i,a){if(i!==void 0&&o(i),!e.versionableOptions)return;let s=i??`_`,c=await V(e,t,n,r?F(r,n):null,!1,s,a);c!==void 0&&s===`_`&&await U(e,t,c),await B(e,t,s)}async function U(e,t,n){let r=l.get(e.entity.name);r&&await e.db.update(r).set({_version:n}).where(u(r.id,t))}function W(e){return{id:e.id,entityId:e.entityId,locale:e.locale,data:e.data,createdBy:e.createdBy,createdByName:e.createdByName??null,createdAt:e.createdAt,updatedAt:e.updatedAt}}async function G(e,t,r,i=`_`,a){o(i);let s=T(e);if(!s)throw new n(e.entity.name,`drafts`);let c=new Date,l=a?.id??``,u=a?.name??null;return W(await s.upsert({entityId:t,locale:i,data:r,createdBy:l,createdByName:u,createdAt:c,updatedAt:c},{target:[`entityId`,`locale`],set:{data:r,updatedAt:c}}))}async function K(e,t,n=`_`){o(n);let r=T(e);if(!r)return null;let i=await r.findOne({entityId:t,locale:n});return i?W(i):null}async function q(e,t,n=`_`){o(n);let r=T(e);return r?(await r.deleteMany({entityId:t,locale:n})).length>0:!1}async function J(e,r,i=`_`,a){if(o(i),!T(e))throw new n(e.entity.name,`drafts`);let s=await K(e,r,i);if(!s)throw new t(r,i);let c=null;e.versionableOptions&&(c=await e.admin.findById(r,j(i)));let l=s.data,u=l.status===void 0?{...l,status:`published`}:l,d=await M(e,r,P(e.entity,u),i);return await q(e,r,i),e.versionableOptions&&await H(e,r,d,c,i,a),d}async function Y(e,t,n=`_`){o(n);let r=T(e);return r?r.exists({entityId:t,locale:n}):!1}async function X(e,t=`_`){o(t);let n=T(e);return n?n.count({locale:t}):0}async function Z(e,t,n=`_`){o(n);let r=new Set;if(t.length===0)return r;let i=T(e);if(!i)return r;let a=await i.findMany({where:{entityId:{in:t},locale:n}});for(let e of a)r.add(e.entityId);return r}async function ne(e,t,n=`_`){o(n);let i=await K(e,t,n);if(!i)return null;let a=r().defaultLocale,s=n===`_`?{defaultLocale:a}:{locale:n,defaultLocale:a},c=await e.admin.findById(t,s);if(!c)return null;let l=e.entity.allFields,u=c,d=i.data,f={};for(let e of Object.keys(d))f[e]=u[e];let p=_(f,d,l);for(let e of p)e.type===`blockTree`&&(e.blockDiffs=C(Array.isArray(e.from)?e.from:[],Array.isArray(e.to)?e.to:[]));return{diffs:p,current:c,draft:i}}function re(e,t,n,r){let i={};for(let a of n){if(a.code===r)continue;let n=t?.get(a.code)??{},o=[];for(let t of e)(n[t]===void 0||n[t]===null)&&o.push(t);i[a.code]={complete:o.length===0,total:e.length,translated:e.length-o.length,missing:o}}return i}async function ie(e,t){return(await Q(e,[t])).get(t)??{}}async function Q(e,t){let n=new Map;if(t.length===0)return n;let i=r(),a=Object.entries(e.entity.allFields).filter(([e,t])=>t.translatable).map(([e])=>e);if(a.length===0)return n;let o=l.get(`${e.entity.name}_translations`);if(!o)return n;let s=await e.db.select().from(o).where(d(o.entityId,t)),c=new Map;for(let e of s){let t=e,n=t.entityId,r=t.locale,i={};for(let e of a)i[e]=t[e];let o=c.get(n);o||(o=new Map,c.set(n,o)),o.set(r,i)}for(let e of t)n.set(e,re(a,c.get(e),i.locales,i.defaultLocale));return n}async function ae(e,t){let n=r(),i=t?.locale??n.locales.find(e=>e.code!==n.defaultLocale)?.code;if(!i)return{total:0,complete:0,partial:0,missing:0};let a=await e.admin.findMany();if(a.length===0)return{total:0,complete:0,partial:0,missing:0};let o=await Q(e,a.map(e=>e.id)),s=0,c=0,l=0;for(let e of a){let t=o.get(e.id)?.[i];!t||t.translated===0?l++:t.complete?s++:c++}return{total:a.length,complete:s,partial:c,missing:l}}var $=class{ctx;constructor(t){let n=e(t.entity.behaviors);this.ctx=w(t,n)}async create(e){let t=await this.ctx.admin.create(e);return this.ctx.versionableOptions&&await V(this.ctx,t.id,t,null,!1),t}findById(e,t){return this.ctx.admin.findById(e,t)}findMany(e){return this.ctx.admin.findMany(e)}async update(e,t){let n=null;this.ctx.versionableOptions&&(n=await this.ctx.admin.findById(e));let r=await this.ctx.admin.update(e,t);if(this.ctx.versionableOptions&&n){let t=F(n,r),i=await V(this.ctx,e,r,t,!1);if(i!==void 0)return await U(this.ctx,e,i),await B(this.ctx,e),{...r,_version:i};await B(this.ctx,e)}return r}delete(e){return this.ctx.admin.delete(e)}saveTranslation(e,t,n){return this.ctx.admin.saveTranslation(e,t,n)}deleteTranslation(e,t){return this.ctx.admin.deleteTranslation(e,t)}getVersions(e,t){return I(this.ctx,e,t)}getVersion(e,t,n){return L(this.ctx,e,t,n)}compareVersions(e,t,n,r){return R(this.ctx,e,t,n,r)}restoreVersion(e,t,n){return z(this.ctx,e,t,n)}pruneVersions(e,t){return B(this.ctx,e,t)}createVersionSnapshot(e,t,n,r,i){return H(this.ctx,e,t,n,r,i)}getTranslationStatus(e){return ie(this.ctx,e)}getTranslationStatusBatch(e){return Q(this.ctx,e)}getTranslationReport(e){return ae(this.ctx,e)}getLocaleStatus(e){return O(this.ctx,e)}setLocaleStatus(e,t,n){return k(this.ctx,e,t,n)}getLocaleStatusBatch(e){return A(this.ctx,e)}saveDraft(e,t,n,r){return G(this.ctx,e,t,n,r)}getDraft(e,t){return K(this.ctx,e,t)}deleteDraft(e,t){return q(this.ctx,e,t)}publishDraft(e,t,n){return J(this.ctx,e,t,n)}hasDraft(e,t){return Y(this.ctx,e,t)}countDrafts(e){return X(this.ctx,e)}hasDraftBatch(e,t){return Z(this.ctx,e,t)}compareDraftWithCurrent(e,t){return ne(this.ctx,e,t)}};function oe(e){return new $({admin:c(e),entity:e,logger:s().logger.child({content:e.name})})}export{$ as ContentClient,oe as createContentClient};
1
+ import{t as e}from"./versionable-Br_G5VGR.mjs";import{c as t,s as n,u as r}from"./routing-strategy-dkt2yO25.mjs";import{t as i}from"./content-config-store-8omQF5p5.mjs";import{n as a,r as o}from"./plugin-BSxbiGc1.mjs";import{t as s}from"./locale-utils-DFEk4CE9.mjs";import{getApp as c}from"@murumets-ee/core";import{schemaRegistry as l}from"@murumets-ee/db";import{eq as u,inArray as d}from"drizzle-orm";import"server-only";import{createAdminClient as f}from"@murumets-ee/core/clients";function p(e){return e.match(/\S+|\s+/g)??[]}function m(e,t){if(e===t)return e?[{type:`equal`,value:e}]:[];if(!e)return[{type:`add`,value:t}];if(!t)return[{type:`remove`,value:e}];let n=p(e),r=p(t),i=n.length,a=r.length,o=Array.from({length:i+1},()=>Array(a+1).fill(0));for(let e=1;e<=i;e++){let t=o[e],i=o[e-1];if(!(!t||!i))for(let o=1;o<=a;o++)n[e-1]===r[o-1]?t[o]=(i[o-1]??0)+1:t[o]=Math.max(i[o]??0,t[o-1]??0)}let s=[],c=i,l=a,u=[];for(;c>0||l>0;){let e=c>0?n[c-1]:void 0,t=l>0?r[l-1]:void 0;if(c>0&&l>0&&e!==void 0&&e===t)u.push({type:`equal`,value:e}),c--,l--;else if(l>0&&t!==void 0&&(c===0||(o[c]?.[l-1]??0)>=(o[c-1]?.[l]??0)))u.push({type:`add`,value:t}),l--;else if(e!==void 0)u.push({type:`remove`,value:e}),c--;else break}u.reverse();for(let e of u){let t=s[s.length-1];t&&t.type===e.type?t.value+=e.value:s.push({...e})}return s}function h(e){return e.replace(/<[^>]*>/g,` `).replace(/\s+/g,` `).trim()}function g(e){return e==null?``:e instanceof Date?e.toISOString():typeof e==`object`?JSON.stringify(e):String(e)}function _(e,t,n,r){if(JSON.stringify(t)===JSON.stringify(n))return null;switch(r?.type??y(t,n)){case`text`:case`slug`:return{field:e,type:`text`,from:t,to:n,segments:m(g(t),g(n))};case`richtext`:return{field:e,type:`richtext`,from:t,to:n,segments:m(h(g(t)),h(g(n)))};case`number`:return{field:e,type:`number`,from:t,to:n};case`boolean`:return{field:e,type:`boolean`,from:t,to:n};case`date`:return{field:e,type:`date`,from:t,to:n};case`select`:return{field:e,type:`select`,from:t,to:n};case`media`:return{field:e,type:`media`,from:t,to:n};case`json`:return{field:e,type:`json`,from:t,to:n};case`blockTree`:return{field:e,type:`blockTree`,from:t,to:n};default:return{field:e,type:`unknown`,from:t,to:n}}}function v(e,t,n){let r=new Set([...Object.keys(e),...Object.keys(t)]),i=[];for(let a of r){if(a===`id`||a===`createdAt`||a===`updatedAt`)continue;let r=_(a,e[a],t[a],n?.[a]);r&&i.push(r)}return i}function y(e,t){let n=e??t;if(typeof n==`number`)return`number`;if(typeof n==`boolean`)return`boolean`;if(n instanceof Date)return`date`;if(typeof n==`string`)return/^\d{4}-\d{2}-\d{2}/.test(n)?`date`:`text`;if(Array.isArray(n)){let e=n[0];return n.length>0&&typeof e==`object`&&e&&`type`in e&&`id`in e?`blockTree`:`json`}return typeof n==`object`?`json`:`unknown`}function b(e,t){if(typeof e==`string`||typeof t==`string`)return m(g(e),g(t))}function x(e){if(typeof e!=`object`||!e)return null;let t=e;if(typeof t.id!=`string`||typeof t.type!=`string`)return null;let n={...t.props??{}};return t.traits!==void 0&&(n.traits=t.traits),t.children!==void 0&&(n.children=t.children),{id:t.id,type:t.type,props:n}}function S(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]),r=[];for(let i of n){let n=e[i],a=t[i];JSON.stringify(n)!==JSON.stringify(a)&&r.push({field:i,from:n,to:a,segments:b(n,a)})}return r}function C(e){let t=new Map,n=0;for(let r of e){let e=x(r);e&&(t.set(e.id,{block:e,index:n}),n++)}return t}function w(e,t){let n=C(e),r=C(t),i=[],a=new Set;for(let[e,{block:t,index:o}]of r){a.add(e);let r=n.get(e);if(!r){i.push({blockId:e,blockType:t.type,status:`added`,toIndex:o});continue}let s=r.block,c=r.index,l=S(s.props,t.props),u=c!==o;l.length===0&&!u?i.push({blockId:e,blockType:t.type,status:`unchanged`,fromIndex:c,toIndex:o}):l.length>0?i.push({blockId:e,blockType:t.type,status:`changed`,fromIndex:c,toIndex:o,propDiffs:l}):i.push({blockId:e,blockType:t.type,status:`moved`,fromIndex:c,toIndex:o})}for(let[e,{block:t,index:r}]of n)a.has(e)||i.push({blockId:e,blockType:t.type,status:`removed`,fromIndex:r});return i}function T(e,t){return{entity:e.entity,admin:e.admin,db:e.admin.getDb(),versionableOptions:t,logger:e.logger}}function E(e){let{drafts:t}=o(e.entity.name);return t?t.makeClient(e.db):null}function D(e){let{versions:t}=o(e.entity.name);return t?t.makeClient(e.db):null}function O(e){let{localeStatus:t}=o(e.entity.name);return t?t.makeClient(e.db):null}function ee(e){return e===`_`||e===i().defaultLocale}function k(e){let{defaultLocale:t}=i();return ee(e)?{defaultLocale:t}:{locale:e,defaultLocale:t}}async function A(e,t,n,r){let o=O(e);return e.admin.updateForLocale(t,n,r,{defaultLocale:i().defaultLocale,...o!==null&&{localeStatusWriter:(e,t,n)=>(s(t),a(o,e,t,n))}})}const j=new Set([`id`,`createdAt`,`createdBy`,`updatedAt`,`updatedBy`]);function M(e,t){let n=e.allFields,r={};for(let e of Object.keys(t)){if(j.has(e)||e.startsWith(`_`)||!Object.hasOwn(n,e))continue;let i=n[e];i&&i.internal===!0||(r[e]=t[e])}return r}function N(e,t){return JSON.stringify(e)===JSON.stringify(t)}function P(e,t){let n={};for(let r of Object.keys(t))r===`id`||r.startsWith(`_`)||N(e[r],t[r])||(n[r]=t[r]);return n}async function te(e,t,n){n?.locale!==void 0&&s(n.locale);let r=D(e);return r?await r.findMany({where:{entityId:t,locale:n?.locale??`_`},orderBy:[{column:`version`,dir:`desc`}],...n?.limit===void 0?{}:{limit:n.limit},...n?.offset===void 0?{}:{offset:n.offset}}):[]}async function F(e,t,n,r=`_`){s(r);let i=D(e);return i?await i.findOne({entityId:t,version:n,locale:r})??null:null}async function ne(e,t,n,r,i=`_`){s(i);let[a,o]=await Promise.all([F(e,t,n,i),F(e,t,r,i)]);if(!a||!o)return null;let c=e.entity.allFields,l=v(a.data,o.data,c);for(let e of l)e.type===`blockTree`&&(e.blockDiffs=w(Array.isArray(e.from)?e.from:[],Array.isArray(e.to)?e.to:[]));return{diffs:l,fromVersion:a,toVersion:o}}async function I(e,n,i,a=`_`){if(s(a),!D(e))throw new t(e.entity.name,`versions`);let o=await F(e,n,i,a);if(!o)throw new r(n,i,a);let c=o.data;return A(e,n,M(e.entity,c),a)}async function L(e,t,n=`_`){if(s(n),!e.versionableOptions)return;let r=D(e);if(!r)return;let i=e.versionableOptions.maxPerDoc,a=await r.findMany({where:{entityId:t,locale:n},orderBy:[{column:`version`,dir:`desc`}],limit:i});if(a.length<i)return;let o=a.map(e=>e.id);await r.deleteMany({entityId:t,locale:n,id:{notIn:o}})}async function R(e,t,n,r,i,a=`_`,o){s(a);let c=D(e);if(!c)return;let[l]=await c.aggregate({select:{maxVersion:{fn:`max`,column:`version`}},where:{entityId:t,locale:a}}),u=Number(l?.maxVersion??0)+1,d=n.status??null;if(i){let e=await c.findOne({entityId:t,isAutosave:!0,version:u,locale:a});if(e)return await c.update({id:e.id},{data:n,delta:r,status:d,createdAt:new Date}),u}return await c.insert({entityId:t,version:u,locale:a,data:n,delta:r,status:d,createdBy:o?.id??null,createdByName:o?.name??null,isAutosave:i}),u}async function z(e,t,n,r,i,a){if(i!==void 0&&s(i),!e.versionableOptions)return;let o=i??`_`,c=await R(e,t,n,r?P(r,n):null,!1,o,a);c!==void 0&&o===`_`&&await B(e,t,c),await L(e,t,o)}async function B(e,t,n){let r=l.get(e.entity.name);r&&await e.db.update(r).set({_version:n}).where(u(r.id,t))}function V(e){return{id:e.id,entityId:e.entityId,locale:e.locale,data:e.data,createdBy:e.createdBy,createdByName:e.createdByName??null,createdAt:e.createdAt,updatedAt:e.updatedAt}}async function H(e,n,r,i=`_`,a){s(i);let o=E(e);if(!o)throw new t(e.entity.name,`drafts`);let c=M(e.entity,r),l=new Date,u=a?.id??``,d=a?.name??null;return V(await o.upsert({entityId:n,locale:i,data:c,createdBy:u,createdByName:d,createdAt:l,updatedAt:l},{target:[`entityId`,`locale`],set:{data:c,updatedAt:l}}))}async function U(e,t,n=`_`){s(n);let r=E(e);if(!r)return null;let i=await r.findOne({entityId:t,locale:n});return i?V(i):null}async function W(e,t,n=`_`){s(n);let r=E(e);return r?(await r.deleteMany({entityId:t,locale:n})).length>0:!1}async function G(e,r,i=`_`,a){if(s(i),!E(e))throw new t(e.entity.name,`drafts`);let o=await U(e,r,i);if(!o)throw new n(r,i);let c=null;e.versionableOptions&&(c=await e.admin.findById(r,k(i)));let l=o.data,u=l.status===void 0?{...l,status:`published`}:l,d=await A(e,r,M(e.entity,u),i),f=K(e.entity,l);return f!==void 0&&await e.admin.updateInternal(r,{_overrides:f}),await W(e,r,i),e.versionableOptions&&await z(e,r,d,c,i,a),d}function K(e,t){if(!(e.behaviors??[]).some(e=>e.name===`withOverrides`))return;let n=e.allFields._overrides;if(n===void 0||n.internal!==!0)return;let r=t._overrides;if(!(typeof r!=`object`||!r||Array.isArray(r)))return r}async function q(e,t,n=`_`){s(n);let r=E(e);return r?r.exists({entityId:t,locale:n}):!1}async function J(e,t=`_`){s(t);let n=E(e);return n?n.count({locale:t}):0}async function Y(e,t,n=`_`){s(n);let r=new Set;if(t.length===0)return r;let i=E(e);if(!i)return r;let a=await i.findMany({where:{entityId:{in:t},locale:n}});for(let e of a)r.add(e.entityId);return r}async function X(e,t,n=`_`){s(n);let r=await U(e,t,n);if(!r)return null;let a=i().defaultLocale,o=n===`_`?{defaultLocale:a}:{locale:n,defaultLocale:a},c=await e.admin.findById(t,o);if(!c)return null;let l=e.entity.allFields,u=c,d=r.data,f={};for(let e of Object.keys(d))f[e]=u[e];let p=v(f,d,l);for(let e of p)e.type===`blockTree`&&(e.blockDiffs=w(Array.isArray(e.from)?e.from:[],Array.isArray(e.to)?e.to:[]));return{diffs:p,current:c,draft:r}}async function re(e,t){let n=(await Z(e,[t])).get(t);return n?Object.values(n):[]}async function ie(e,n,r,o){s(r);let c=i();if(r===c.defaultLocale){let t={status:o};o===`published`&&(t.publishedAt=new Date),await e.admin.update(n,t);return}let l=O(e);if(!l)throw new t(e.entity.name,`localeStatus`);let u={status:o};await e.admin.updateForLocale(n,u,r,{defaultLocale:c.defaultLocale,localeStatusWriter:(e,t,n)=>a(l,e,t,n)})}async function Z(e,t){let n=new Map;if(t.length===0)return n;let r=O(e);if(!r)return n;let i=await r.findMany({where:{entityId:{in:t}}});for(let e of i){let t=e.entityId,r=e.locale,i=n.get(t);i||(i={},n.set(t,i)),i[r]={locale:r,status:e.status,publishedAt:e.publishedAt??null}}return n}function ae(e,t,n,r){let i={};for(let a of n){if(a.code===r)continue;let n=t?.get(a.code)??{},o=[];for(let t of e)(n[t]===void 0||n[t]===null)&&o.push(t);i[a.code]={complete:o.length===0,total:e.length,translated:e.length-o.length,missing:o}}return i}async function oe(e,t){return(await Q(e,[t])).get(t)??{}}async function Q(e,t){let n=new Map;if(t.length===0)return n;let r=i(),a=Object.entries(e.entity.allFields).filter(([e,t])=>t.translatable).map(([e])=>e);if(a.length===0)return n;let o=l.get(`${e.entity.name}_translations`);if(!o)return n;let s=await e.db.select().from(o).where(d(o.entityId,t)),c=new Map;for(let e of s){let t=e,n=t.entityId,r=t.locale,i={};for(let e of a)i[e]=t[e];let o=c.get(n);o||(o=new Map,c.set(n,o)),o.set(r,i)}for(let e of t)n.set(e,ae(a,c.get(e),r.locales,r.defaultLocale));return n}async function se(e,t){let n=i(),r=t?.locale??n.locales.find(e=>e.code!==n.defaultLocale)?.code;if(!r)return{total:0,complete:0,partial:0,missing:0};let a=await e.admin.findMany();if(a.length===0)return{total:0,complete:0,partial:0,missing:0};let o=await Q(e,a.map(e=>e.id)),s=0,c=0,l=0;for(let e of a){let t=o.get(e.id)?.[r];!t||t.translated===0?l++:t.complete?s++:c++}return{total:a.length,complete:s,partial:c,missing:l}}var $=class{ctx;constructor(t){let n=e(t.entity.behaviors);this.ctx=T(t,n)}async create(e){let t=await this.ctx.admin.create(e);return this.ctx.versionableOptions&&await R(this.ctx,t.id,t,null,!1),t}findById(e,t){return this.ctx.admin.findById(e,t)}findMany(e){return this.ctx.admin.findMany(e)}async update(e,t){let n=null;this.ctx.versionableOptions&&(n=await this.ctx.admin.findById(e));let r=await this.ctx.admin.update(e,t);if(this.ctx.versionableOptions&&n){let t=P(n,r),i=await R(this.ctx,e,r,t,!1);if(i!==void 0)return await B(this.ctx,e,i),await L(this.ctx,e),{...r,_version:i};await L(this.ctx,e)}return r}delete(e){return this.ctx.admin.delete(e)}saveTranslation(e,t,n){return this.ctx.admin.saveTranslation(e,t,n)}deleteTranslation(e,t){return this.ctx.admin.deleteTranslation(e,t)}getVersions(e,t){return te(this.ctx,e,t)}getVersion(e,t,n){return F(this.ctx,e,t,n)}compareVersions(e,t,n,r){return ne(this.ctx,e,t,n,r)}restoreVersion(e,t,n){return I(this.ctx,e,t,n)}pruneVersions(e,t){return L(this.ctx,e,t)}createVersionSnapshot(e,t,n,r,i){return z(this.ctx,e,t,n,r,i)}getTranslationStatus(e){return oe(this.ctx,e)}getTranslationStatusBatch(e){return Q(this.ctx,e)}getTranslationReport(e){return se(this.ctx,e)}getLocaleStatus(e){return re(this.ctx,e)}setLocaleStatus(e,t,n){return ie(this.ctx,e,t,n)}getLocaleStatusBatch(e){return Z(this.ctx,e)}saveDraft(e,t,n,r){return H(this.ctx,e,t,n,r)}getDraft(e,t){return U(this.ctx,e,t)}deleteDraft(e,t){return W(this.ctx,e,t)}publishDraft(e,t,n){return G(this.ctx,e,t,n)}hasDraft(e,t){return q(this.ctx,e,t)}countDrafts(e){return J(this.ctx,e)}hasDraftBatch(e,t){return Y(this.ctx,e,t)}compareDraftWithCurrent(e,t){return X(this.ctx,e,t)}};function ce(e){return new $({admin:f(e),entity:e,logger:c().logger.child({content:e.name})})}export{$ as ContentClient,ce as createContentClient};
2
2
  //# sourceMappingURL=client.mjs.map