@murumets-ee/content 0.37.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.
- package/dist/admin.mjs +1 -1
- package/dist/chrome-provider.d.mts +107 -0
- package/dist/chrome-provider.d.mts.map +1 -0
- package/dist/chrome-provider.mjs +2 -0
- package/dist/chrome-provider.mjs.map +1 -0
- package/dist/client.mjs +1 -1
- package/dist/client.mjs.map +1 -1
- package/dist/entities/index.d.mts +32 -10
- package/dist/entities/index.d.mts.map +1 -1
- package/dist/entities/index.mjs.map +1 -1
- package/dist/index-Bsmju0UF.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/{locale-utils-Dyw5qWDf.mjs → locale-utils-DFEk4CE9.mjs} +2 -2
- package/dist/{locale-utils-Dyw5qWDf.mjs.map → locale-utils-DFEk4CE9.mjs.map} +1 -1
- package/dist/plugin-BSxbiGc1.mjs +2 -0
- package/dist/plugin-BSxbiGc1.mjs.map +1 -0
- package/dist/plugin.mjs +1 -1
- package/dist/query-client.mjs +1 -1
- package/dist/resolve-context-BVk6uObB.mjs +2 -0
- package/dist/resolve-context-BVk6uObB.mjs.map +1 -0
- package/dist/resolve.d.mts +19 -10
- package/dist/resolve.d.mts.map +1 -1
- package/dist/resolve.mjs +1 -1
- package/dist/resolve.mjs.map +1 -1
- package/dist/{routes-D3dzAfQw.mjs → routes-Bc9DcaPk.mjs} +2 -2
- package/dist/{routes-D3dzAfQw.mjs.map → routes-Bc9DcaPk.mjs.map} +1 -1
- package/dist/schema-BJxAwPrI.mjs.map +1 -1
- package/package.json +13 -9
- package/dist/plugin-BTynhZMx.mjs +0 -2
- package/dist/plugin-BTynhZMx.mjs.map +0 -1
- package/dist/resolve-context-p3m6gn3_.mjs +0 -2
- package/dist/resolve-context-p3m6gn3_.mjs.map +0 -1
package/dist/admin.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e,t}from"./routes-
|
|
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.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
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-BTynhZMx.mjs";import{t as s}from"./locale-utils-Dyw5qWDf.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??ee(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 ee(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 m(g(e),g(t))}function te(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 b(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 x(e){let t=new Map,n=0;for(let r of e){let e=te(r);e&&(t.set(e.id,{block:e,index:n}),n++)}return t}function S(e,t){let n=x(e),r=x(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=b(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 C(e,t){return{entity:e.entity,admin:e.admin,db:e.admin.getDb(),versionableOptions:t,logger:e.logger}}function w(e){let{drafts:t}=o(e.entity.name);return t?t.makeClient(e.db):null}function T(e){let{versions:t}=o(e.entity.name);return t?t.makeClient(e.db):null}function E(e){let{localeStatus:t}=o(e.entity.name);return t?t.makeClient(e.db):null}async function D(e,t){let n=(await k(e,[t])).get(t);return n?Object.values(n):[]}async function O(e,n,r,o){if(s(r),r===i().defaultLocale){let t={status:o};o===`published`&&(t.publishedAt=new Date),await e.admin.update(n,t);return}let c=E(e);if(!c)throw new t(e.entity.name,`localeStatus`);await a(c,n,r,o)}async function k(e,t){let n=new Map;if(t.length===0)return n;let r=E(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 A(e){return e===`_`||e===i().defaultLocale}function j(e){let{defaultLocale:t}=i();return A(e)?{defaultLocale:t}:{locale:e,defaultLocale:t}}async function M(e,t,n,r){let a=E(e)!==null;return e.admin.updateForLocale(t,n,r,{defaultLocale:i().defaultLocale,...a&&{localeStatusWriter:(t,n,r)=>O(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 F(e,t){return JSON.stringify(e)===JSON.stringify(t)}function I(e,t){let n={};for(let r of Object.keys(t))r===`id`||r.startsWith(`_`)||F(e[r],t[r])||(n[r]=t[r]);return n}async function L(e,t,n){n?.locale!==void 0&&s(n.locale);let r=T(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 R(e,t,n,r=`_`){s(r);let i=T(e);return i?await i.findOne({entityId:t,version:n,locale:r})??null:null}async function z(e,t,n,r,i=`_`){s(i);let[a,o]=await Promise.all([R(e,t,n,i),R(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=S(Array.isArray(e.from)?e.from:[],Array.isArray(e.to)?e.to:[]));return{diffs:l,fromVersion:a,toVersion:o}}async function B(e,n,i,a=`_`){if(s(a),!T(e))throw new t(e.entity.name,`versions`);let o=await R(e,n,i,a);if(!o)throw new r(n,i,a);let c=o.data;return M(e,n,P(e.entity,c),a)}async function V(e,t,n=`_`){if(s(n),!e.versionableOptions)return;let r=T(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 H(e,t,n,r,i,a=`_`,o){s(a);let c=T(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 U(e,t,n,r,i,a){if(i!==void 0&&s(i),!e.versionableOptions)return;let o=i??`_`,c=await H(e,t,n,r?I(r,n):null,!1,o,a);c!==void 0&&o===`_`&&await W(e,t,c),await V(e,t,o)}async function W(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 G(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 K(e,n,r,i=`_`,a){s(i);let o=w(e);if(!o)throw new t(e.entity.name,`drafts`);let c=P(e.entity,r),l=new Date,u=a?.id??``,d=a?.name??null;return G(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 q(e,t,n=`_`){s(n);let r=w(e);if(!r)return null;let i=await r.findOne({entityId:t,locale:n});return i?G(i):null}async function J(e,t,n=`_`){s(n);let r=w(e);return r?(await r.deleteMany({entityId:t,locale:n})).length>0:!1}async function Y(e,r,i=`_`,a){if(s(i),!w(e))throw new t(e.entity.name,`drafts`);let o=await q(e,r,i);if(!o)throw new n(r,i);let c=null;e.versionableOptions&&(c=await e.admin.findById(r,j(i)));let l=o.data,u=l.status===void 0?{...l,status:`published`}:l,d=await M(e,r,P(e.entity,u),i);return await J(e,r,i),e.versionableOptions&&await U(e,r,d,c,i,a),d}async function X(e,t,n=`_`){s(n);let r=w(e);return r?r.exists({entityId:t,locale:n}):!1}async function Z(e,t=`_`){s(t);let n=w(e);return n?n.count({locale:t}):0}async function ne(e,t,n=`_`){s(n);let r=new Set;if(t.length===0)return r;let i=w(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 re(e,t,n=`_`){s(n);let r=await q(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=S(Array.isArray(e.from)?e.from:[],Array.isArray(e.to)?e.to:[]));return{diffs:p,current:c,draft:r}}function ie(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 ae(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,ie(a,c.get(e),r.locales,r.defaultLocale));return n}async function oe(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=C(t,n)}async create(e){let t=await this.ctx.admin.create(e);return this.ctx.versionableOptions&&await H(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=I(n,r),i=await H(this.ctx,e,r,t,!1);if(i!==void 0)return await W(this.ctx,e,i),await V(this.ctx,e),{...r,_version:i};await V(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 L(this.ctx,e,t)}getVersion(e,t,n){return R(this.ctx,e,t,n)}compareVersions(e,t,n,r){return z(this.ctx,e,t,n,r)}restoreVersion(e,t,n){return B(this.ctx,e,t,n)}pruneVersions(e,t){return V(this.ctx,e,t)}createVersionSnapshot(e,t,n,r,i){return U(this.ctx,e,t,n,r,i)}getTranslationStatus(e){return ae(this.ctx,e)}getTranslationStatusBatch(e){return Q(this.ctx,e)}getTranslationReport(e){return oe(this.ctx,e)}getLocaleStatus(e){return D(this.ctx,e)}setLocaleStatus(e,t,n){return O(this.ctx,e,t,n)}getLocaleStatusBatch(e){return k(this.ctx,e)}saveDraft(e,t,n,r){return K(this.ctx,e,t,n,r)}getDraft(e,t){return q(this.ctx,e,t)}deleteDraft(e,t){return J(this.ctx,e,t)}publishDraft(e,t,n){return Y(this.ctx,e,t,n)}hasDraft(e,t){return X(this.ctx,e,t)}countDrafts(e){return Z(this.ctx,e)}hasDraftBatch(e,t){return ne(this.ctx,e,t)}compareDraftWithCurrent(e,t){return re(this.ctx,e,t)}};function se(e){return new $({admin:f(e),entity:e,logger:c().logger.child({content:e.name})})}export{$ as ContentClient,se 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
|