@batterai/host 0.1.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 (148) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/LICENSE +21 -0
  3. package/README.md +23 -0
  4. package/dist/cache/dependency-graph.d.ts +18 -0
  5. package/dist/cache/dependency-graph.d.ts.map +1 -0
  6. package/dist/cache/dependency-graph.js +30 -0
  7. package/dist/cache/dependency-graph.js.map +1 -0
  8. package/dist/cache/render-cache.d.ts +39 -0
  9. package/dist/cache/render-cache.d.ts.map +1 -0
  10. package/dist/cache/render-cache.js +60 -0
  11. package/dist/cache/render-cache.js.map +1 -0
  12. package/dist/client/mount.d.ts +9 -0
  13. package/dist/client/mount.d.ts.map +1 -0
  14. package/dist/client/mount.js +69 -0
  15. package/dist/client/mount.js.map +1 -0
  16. package/dist/client.d.ts +2 -0
  17. package/dist/client.d.ts.map +1 -0
  18. package/dist/client.js +2 -0
  19. package/dist/client.js.map +1 -0
  20. package/dist/collab/in-memory-page-draft-store.d.ts +44 -0
  21. package/dist/collab/in-memory-page-draft-store.d.ts.map +1 -0
  22. package/dist/collab/in-memory-page-draft-store.js +217 -0
  23. package/dist/collab/in-memory-page-draft-store.js.map +1 -0
  24. package/dist/collab/page-doc-page-draft-store.d.ts +41 -0
  25. package/dist/collab/page-doc-page-draft-store.d.ts.map +1 -0
  26. package/dist/collab/page-doc-page-draft-store.js +328 -0
  27. package/dist/collab/page-doc-page-draft-store.js.map +1 -0
  28. package/dist/collab/page-draft-store.d.ts +152 -0
  29. package/dist/collab/page-draft-store.d.ts.map +1 -0
  30. package/dist/collab/page-draft-store.js +61 -0
  31. package/dist/collab/page-draft-store.js.map +1 -0
  32. package/dist/content-source/conformance/cases-delegation.d.ts +3 -0
  33. package/dist/content-source/conformance/cases-delegation.d.ts.map +1 -0
  34. package/dist/content-source/conformance/cases-delegation.js +55 -0
  35. package/dist/content-source/conformance/cases-delegation.js.map +1 -0
  36. package/dist/content-source/conformance/cases-fidelity.d.ts +3 -0
  37. package/dist/content-source/conformance/cases-fidelity.d.ts.map +1 -0
  38. package/dist/content-source/conformance/cases-fidelity.js +67 -0
  39. package/dist/content-source/conformance/cases-fidelity.js.map +1 -0
  40. package/dist/content-source/conformance/cases-isolation.d.ts +3 -0
  41. package/dist/content-source/conformance/cases-isolation.d.ts.map +1 -0
  42. package/dist/content-source/conformance/cases-isolation.js +57 -0
  43. package/dist/content-source/conformance/cases-isolation.js.map +1 -0
  44. package/dist/content-source/conformance/cases-permissions.d.ts +3 -0
  45. package/dist/content-source/conformance/cases-permissions.d.ts.map +1 -0
  46. package/dist/content-source/conformance/cases-permissions.js +50 -0
  47. package/dist/content-source/conformance/cases-permissions.js.map +1 -0
  48. package/dist/content-source/conformance/cases-reads.d.ts +3 -0
  49. package/dist/content-source/conformance/cases-reads.d.ts.map +1 -0
  50. package/dist/content-source/conformance/cases-reads.js +216 -0
  51. package/dist/content-source/conformance/cases-reads.js.map +1 -0
  52. package/dist/content-source/conformance/cases-writes.d.ts +11 -0
  53. package/dist/content-source/conformance/cases-writes.d.ts.map +1 -0
  54. package/dist/content-source/conformance/cases-writes.js +73 -0
  55. package/dist/content-source/conformance/cases-writes.js.map +1 -0
  56. package/dist/content-source/conformance/session-required.d.ts +10 -0
  57. package/dist/content-source/conformance/session-required.d.ts.map +1 -0
  58. package/dist/content-source/conformance/session-required.js +11 -0
  59. package/dist/content-source/conformance/session-required.js.map +1 -0
  60. package/dist/content-source/conformance/sessionless-viewer.d.ts +12 -0
  61. package/dist/content-source/conformance/sessionless-viewer.d.ts.map +1 -0
  62. package/dist/content-source/conformance/sessionless-viewer.js +5 -0
  63. package/dist/content-source/conformance/sessionless-viewer.js.map +1 -0
  64. package/dist/content-source/conformance/target.d.ts +111 -0
  65. package/dist/content-source/conformance/target.d.ts.map +1 -0
  66. package/dist/content-source/conformance/target.js +2 -0
  67. package/dist/content-source/conformance/target.js.map +1 -0
  68. package/dist/content-source/conformance-kit.d.ts +4 -0
  69. package/dist/content-source/conformance-kit.d.ts.map +1 -0
  70. package/dist/content-source/conformance-kit.js +41 -0
  71. package/dist/content-source/conformance-kit.js.map +1 -0
  72. package/dist/content-source/content-source.d.ts +153 -0
  73. package/dist/content-source/content-source.d.ts.map +1 -0
  74. package/dist/content-source/content-source.js +12 -0
  75. package/dist/content-source/content-source.js.map +1 -0
  76. package/dist/content-source/permissions.d.ts +46 -0
  77. package/dist/content-source/permissions.d.ts.map +1 -0
  78. package/dist/content-source/permissions.js +33 -0
  79. package/dist/content-source/permissions.js.map +1 -0
  80. package/dist/content-source/viewer.d.ts +48 -0
  81. package/dist/content-source/viewer.d.ts.map +1 -0
  82. package/dist/content-source/viewer.js +24 -0
  83. package/dist/content-source/viewer.js.map +1 -0
  84. package/dist/index.d.ts +30 -0
  85. package/dist/index.d.ts.map +1 -0
  86. package/dist/index.js +30 -0
  87. package/dist/index.js.map +1 -0
  88. package/dist/lifecycle/cms-lifecycle-store.d.ts +72 -0
  89. package/dist/lifecycle/cms-lifecycle-store.d.ts.map +1 -0
  90. package/dist/lifecycle/cms-lifecycle-store.js +150 -0
  91. package/dist/lifecycle/cms-lifecycle-store.js.map +1 -0
  92. package/dist/lifecycle/lifecycle-store.d.ts +35 -0
  93. package/dist/lifecycle/lifecycle-store.d.ts.map +1 -0
  94. package/dist/lifecycle/lifecycle-store.js +69 -0
  95. package/dist/lifecycle/lifecycle-store.js.map +1 -0
  96. package/dist/lifecycle/version-token.d.ts +30 -0
  97. package/dist/lifecycle/version-token.d.ts.map +1 -0
  98. package/dist/lifecycle/version-token.js +22 -0
  99. package/dist/lifecycle/version-token.js.map +1 -0
  100. package/dist/messages.d.ts +26 -0
  101. package/dist/messages.d.ts.map +1 -0
  102. package/dist/messages.js +31 -0
  103. package/dist/messages.js.map +1 -0
  104. package/dist/render/image-usage-preview.d.ts +19 -0
  105. package/dist/render/image-usage-preview.d.ts.map +1 -0
  106. package/dist/render/image-usage-preview.js +25 -0
  107. package/dist/render/image-usage-preview.js.map +1 -0
  108. package/dist/render/image-usage.d.ts +32 -0
  109. package/dist/render/image-usage.d.ts.map +1 -0
  110. package/dist/render/image-usage.js +116 -0
  111. package/dist/render/image-usage.js.map +1 -0
  112. package/dist/render/page-renderer.d.ts +44 -0
  113. package/dist/render/page-renderer.d.ts.map +1 -0
  114. package/dist/render/page-renderer.js +115 -0
  115. package/dist/render/page-renderer.js.map +1 -0
  116. package/dist/resolver/data-loader.d.ts +49 -0
  117. package/dist/resolver/data-loader.d.ts.map +1 -0
  118. package/dist/resolver/data-loader.js +121 -0
  119. package/dist/resolver/data-loader.js.map +1 -0
  120. package/dist/resolver/resolution-service.d.ts +49 -0
  121. package/dist/resolver/resolution-service.d.ts.map +1 -0
  122. package/dist/resolver/resolution-service.js +193 -0
  123. package/dist/resolver/resolution-service.js.map +1 -0
  124. package/dist/routing/route-resolver.d.ts +73 -0
  125. package/dist/routing/route-resolver.d.ts.map +1 -0
  126. package/dist/routing/route-resolver.js +114 -0
  127. package/dist/routing/route-resolver.js.map +1 -0
  128. package/dist/scope-engine/build-stack.d.ts +49 -0
  129. package/dist/scope-engine/build-stack.d.ts.map +1 -0
  130. package/dist/scope-engine/build-stack.js +77 -0
  131. package/dist/scope-engine/build-stack.js.map +1 -0
  132. package/dist/scope-engine/repeater-query.d.ts +9 -0
  133. package/dist/scope-engine/repeater-query.d.ts.map +1 -0
  134. package/dist/scope-engine/repeater-query.js +39 -0
  135. package/dist/scope-engine/repeater-query.js.map +1 -0
  136. package/dist/seo/metadata.d.ts +30 -0
  137. package/dist/seo/metadata.d.ts.map +1 -0
  138. package/dist/seo/metadata.js +118 -0
  139. package/dist/seo/metadata.js.map +1 -0
  140. package/dist/seo/sitemap.d.ts +11 -0
  141. package/dist/seo/sitemap.d.ts.map +1 -0
  142. package/dist/seo/sitemap.js +20 -0
  143. package/dist/seo/sitemap.js.map +1 -0
  144. package/dist/write/write-path.d.ts +63 -0
  145. package/dist/write/write-path.d.ts.map +1 -0
  146. package/dist/write/write-path.js +139 -0
  147. package/dist/write/write-path.js.map +1 -0
  148. package/package.json +44 -0
@@ -0,0 +1,31 @@
1
+ import { resolveMessage } from "@batterai/contract";
2
+ export const HOST_MESSAGES_EN = {
3
+ "binding.broken": "broken binding: {reason}",
4
+ "block.unknown": "Unknown block type: {type}",
5
+ "loadMore.retry": "Loading failed - try again",
6
+ "metadata.brokenTitle": "Broken title binding",
7
+ "repeater.empty": "No content in this list yet.",
8
+ "route.previewAuthRequired": "preview requires a signed-in editor",
9
+ "write.fieldUnreadable": "field is not readable for this user",
10
+ "write.pageLoginRequired": "only signed-in editors can edit pages",
11
+ "write.unsupportedInstanceValue": "UnsupportedInstanceWriteValue: a {kind} value cannot be written to a CMS field yet - only literal values are supported",
12
+ };
13
+ export const HOST_MESSAGES_NL = {
14
+ "binding.broken": "binding kapot: {reason}",
15
+ "block.unknown": "Onbekend bloktype: {type}",
16
+ "loadMore.retry": "Laden mislukt - opnieuw proberen",
17
+ "metadata.brokenTitle": "kapotte titel-binding",
18
+ "repeater.empty": "Nog geen inhoud in deze lijst.",
19
+ "route.previewAuthRequired": "preview vereist een ingelogde redacteur",
20
+ "write.fieldUnreadable": "veld niet leesbaar voor deze gebruiker",
21
+ "write.pageLoginRequired": "alleen ingelogde redacteuren kunnen pagina's bewerken",
22
+ "write.unsupportedInstanceValue": "UnsupportedInstanceWriteValue: een {kind}-waarde kan nog niet naar een CMS-veld worden geschreven - alleen letterlijke waarden",
23
+ };
24
+ const HOST_CATALOGS = {
25
+ en: HOST_MESSAGES_EN,
26
+ nl: HOST_MESSAGES_NL,
27
+ };
28
+ export function hostMessage(locale, key, params) {
29
+ return resolveMessage(HOST_CATALOGS, locale ? String(locale) : "en", key, HOST_MESSAGES_EN[key], params);
30
+ }
31
+ //# sourceMappingURL=messages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.js","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAwD,MAAM,oBAAoB,CAAC;AAE1G,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,gBAAgB,EAAE,0BAA0B;IAC5C,eAAe,EAAE,4BAA4B;IAC7C,gBAAgB,EAAE,4BAA4B;IAC9C,sBAAsB,EAAE,sBAAsB;IAC9C,gBAAgB,EAAE,8BAA8B;IAChD,2BAA2B,EAAE,qCAAqC;IAClE,uBAAuB,EAAE,qCAAqC;IAC9D,yBAAyB,EAAE,uCAAuC;IAClE,gCAAgC,EAC9B,wHAAwH;CACzF,CAAC;AAEpC,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,gBAAgB,EAAE,yBAAyB;IAC3C,eAAe,EAAE,2BAA2B;IAC5C,gBAAgB,EAAE,kCAAkC;IACpD,sBAAsB,EAAE,uBAAuB;IAC/C,gBAAgB,EAAE,gCAAgC;IAClD,2BAA2B,EAAE,yCAAyC;IACtE,uBAAuB,EAAE,wCAAwC;IACjE,yBAAyB,EAAE,uDAAuD;IAClF,gCAAgC,EAC9B,gIAAgI;CAClE,CAAC;AAInE,MAAM,aAAa,GAAG;IACpB,EAAE,EAAE,gBAAgB;IACpB,EAAE,EAAE,gBAAgB;CAC6B,CAAC;AAEpD,MAAM,UAAU,WAAW,CAAC,MAAmC,EAAE,GAAmB,EAAE,MAAsB;IAC1G,OAAO,cAAc,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,gBAAgB,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;AAC3G,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { Locale } from "@batterai/bind-core";
2
+ import type { BlockConfig, ImageUsage } from "@batterai/contract";
3
+ import type { ImageView } from "@batterai/block-sdk";
4
+ import { type ImageUsageResolutionDeps } from "./image-usage";
5
+ export interface ImageUsagePreviewRequest extends ImageUsageResolutionDeps {
6
+ readonly usages: readonly ImageUsage[];
7
+ readonly view?: ImageView;
8
+ readonly locale: Locale;
9
+ readonly sizes?: string;
10
+ }
11
+ export interface ImageUsagePreview {
12
+ readonly config: BlockConfig;
13
+ readonly html: string;
14
+ }
15
+ /** Resolve authored image usages and render them through the exact public
16
+ * `battery.image` DSD path. Editor previews and MCP previews should use this
17
+ * instead of hand-rendering images from storage-shaped data. */
18
+ export declare function renderImageUsagePreview(request: ImageUsagePreviewRequest): Promise<ImageUsagePreview>;
19
+ //# sourceMappingURL=image-usage-preview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-usage-preview.d.ts","sourceRoot":"","sources":["../../src/render/image-usage-preview.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAElD,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAsB,KAAK,wBAAwB,EAAE,MAAM,eAAe,CAAC;AAElF,MAAM,WAAW,wBAAyB,SAAQ,wBAAwB;IACxE,QAAQ,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAC;IACvC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;gEAEgE;AAChE,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,iBAAiB,CAAC,CAkB5B"}
@@ -0,0 +1,25 @@
1
+ import { imagePackage, renderBlockToDSD } from "@batterai/block-sdk";
2
+ import { resolveImageUsages } from "./image-usage.js";
3
+ /** Resolve authored image usages and render them through the exact public
4
+ * `battery.image` DSD path. Editor previews and MCP previews should use this
5
+ * instead of hand-rendering images from storage-shaped data. */
6
+ export async function renderImageUsagePreview(request) {
7
+ const images = await resolveImageUsages(request.usages, request);
8
+ const view = request.view ?? "single";
9
+ const config = {
10
+ props: {
11
+ assetId: "",
12
+ alt: "",
13
+ caption: "",
14
+ sizes: request.sizes ?? "100vw",
15
+ view,
16
+ },
17
+ data: { view, images },
18
+ context: { locale: request.locale, editMode: false },
19
+ };
20
+ return {
21
+ config,
22
+ html: renderBlockToDSD(imagePackage, config),
23
+ };
24
+ }
25
+ //# sourceMappingURL=image-usage-preview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-usage-preview.js","sourceRoot":"","sources":["../../src/render/image-usage-preview.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAGrE,OAAO,EAAE,kBAAkB,EAAiC,MAAM,eAAe,CAAC;AAclF;;gEAEgE;AAChE,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,OAAiC;IAEjC,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,QAAQ,CAAC;IACtC,MAAM,MAAM,GAAgB;QAC1B,KAAK,EAAE;YACL,OAAO,EAAE,EAAE;YACX,GAAG,EAAE,EAAE;YACP,OAAO,EAAE,EAAE;YACX,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,OAAO;YAC/B,IAAI;SACL;QACD,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAW;QAC/B,OAAO,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE;KACrD,CAAC;IACF,OAAO;QACL,MAAM;QACN,IAAI,EAAE,gBAAgB,CAAC,YAAY,EAAE,MAAM,CAAC;KAC7C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Host-side image usage resolution: authored/editor `ImageUsage` data becomes
3
+ * the pure `battery.image` data payload. Blocks never talk to media storage or
4
+ * routing directly.
5
+ */
6
+ import { type PageId, type TenantId } from "@batterai/bind-core";
7
+ import type { ImageLink, ImageUsage } from "@batterai/contract";
8
+ import type { ImageMediaData } from "@batterai/block-sdk";
9
+ import { type MediaStore } from "@batterai/media";
10
+ import type { RouteResolver } from "../routing/route-resolver";
11
+ export declare class MissingImageAsset extends Error {
12
+ readonly assetId: string;
13
+ constructor(assetId: string);
14
+ }
15
+ export declare class MissingImagePageRoute extends Error {
16
+ readonly pageId: PageId;
17
+ constructor(pageId: PageId);
18
+ }
19
+ export interface ImageUsageResolutionDeps {
20
+ readonly tenant: TenantId;
21
+ readonly media: MediaStore;
22
+ /** Delegated end-user bearer for authoring/preview reads, or the empty string
23
+ * for explicit anonymous public-delivery reads. CMS stores omit Authorization
24
+ * only for read methods when this is empty; uploads still require a token. */
25
+ readonly mediaToken: string;
26
+ readonly routeResolver?: Pick<RouteResolver, "hrefForPage">;
27
+ readonly externalAllowedSchemes?: readonly string[];
28
+ }
29
+ export declare function resolveImageUsages(usages: readonly ImageUsage[], deps: ImageUsageResolutionDeps): Promise<readonly ImageMediaData[]>;
30
+ export declare function resolveImageUsage(usage: ImageUsage, deps: ImageUsageResolutionDeps): Promise<ImageMediaData>;
31
+ export declare function resolveImageLink(link: ImageLink | undefined, deps: ImageUsageResolutionDeps): string | undefined;
32
+ //# sourceMappingURL=image-usage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-usage.d.ts","sourceRoot":"","sources":["../../src/render/image-usage.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAkB,KAAK,MAAM,EAAE,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,KAAK,EAAe,SAAS,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAE7E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,qBAAa,iBAAkB,SAAQ,KAAK;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM;gBAAf,OAAO,EAAE,MAAM;CAIrC;AAED,qBAAa,qBAAsB,SAAQ,KAAK;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM;CAIpC;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B;;kFAE8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;IAC5D,QAAQ,CAAC,sBAAsB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACrD;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,SAAS,UAAU,EAAE,EAC7B,IAAI,EAAE,wBAAwB,GAC7B,OAAO,CAAC,SAAS,cAAc,EAAE,CAAC,CAMpC;AAED,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,UAAU,EACjB,IAAI,EAAE,wBAAwB,GAC7B,OAAO,CAAC,cAAc,CAAC,CA0BzB;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,SAAS,GAAG,SAAS,EAC3B,IAAI,EAAE,wBAAwB,GAC7B,MAAM,GAAG,SAAS,CAiBpB"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Host-side image usage resolution: authored/editor `ImageUsage` data becomes
3
+ * the pure `battery.image` data payload. Blocks never talk to media storage or
4
+ * routing directly.
5
+ */
6
+ import { defaultEscaper } from "@batterai/bind-core";
7
+ import { isCssLength } from "@batterai/contract";
8
+ import { buildSrcset } from "@batterai/media";
9
+ export class MissingImageAsset extends Error {
10
+ assetId;
11
+ constructor(assetId) {
12
+ super(`image usage: media asset not found (${assetId})`);
13
+ this.assetId = assetId;
14
+ this.name = "MissingImageAsset";
15
+ }
16
+ }
17
+ export class MissingImagePageRoute extends Error {
18
+ pageId;
19
+ constructor(pageId) {
20
+ super(`image usage: page route not found (${pageId})`);
21
+ this.pageId = pageId;
22
+ this.name = "MissingImagePageRoute";
23
+ }
24
+ }
25
+ export async function resolveImageUsages(usages, deps) {
26
+ const resolved = [];
27
+ for (const usage of usages) {
28
+ resolved.push(await resolveImageUsage(usage, deps));
29
+ }
30
+ return resolved;
31
+ }
32
+ export async function resolveImageUsage(usage, deps) {
33
+ const asset = await deps.media.get(deps.tenant, deps.mediaToken, usage.asset);
34
+ if (!asset)
35
+ throw new MissingImageAsset(usage.asset);
36
+ const derivatives = await deps.media.derivativesOf(deps.tenant, deps.mediaToken, usage.asset);
37
+ const original = derivatives.find((ref) => ref.format === "original");
38
+ const fallback = derivatives
39
+ .filter((ref) => ref.format === "webp")
40
+ .sort((a, b) => b.width - a.width)[0];
41
+ const srcRef = original ?? fallback;
42
+ if (!srcRef)
43
+ throw new MissingImageAsset(usage.asset);
44
+ const srcset = buildSrcset(deps.media, deps.tenant, asset, derivatives, "webp");
45
+ return {
46
+ src: deps.media.urlFor(deps.tenant, srcRef),
47
+ srcset: srcset || undefined,
48
+ width: asset.width,
49
+ height: asset.height,
50
+ alt: nonEmpty(usage.altOverride) ?? asset.alt,
51
+ title: nonEmpty(usage.titleOverride),
52
+ caption: nonEmpty(usage.caption),
53
+ href: resolveImageLink(usage.link, deps),
54
+ focal: validFocal(usage.focalOverride) ?? validFocal(asset.focal),
55
+ aspectRatio: aspectRatio(usage.aspect),
56
+ sizing: sizing(usage),
57
+ opacity: validOpacity(usage.opacity),
58
+ };
59
+ }
60
+ export function resolveImageLink(link, deps) {
61
+ if (!link)
62
+ return undefined;
63
+ if (link.kind === "external") {
64
+ const href = link.href.trim();
65
+ // Run the platform escaper for scheme validation, but return the raw href:
66
+ // Lit/DSD will perform attribute escaping when the block emits it.
67
+ defaultEscaper.escape(href, {
68
+ kind: "url",
69
+ allowedSchemes: [...(deps.externalAllowedSchemes ?? ["https", "http"])],
70
+ });
71
+ return href;
72
+ }
73
+ const href = deps.routeResolver?.hrefForPage(link.pageId);
74
+ if (!href)
75
+ throw new MissingImagePageRoute(link.pageId);
76
+ defaultEscaper.escape(href, { kind: "url", allowedSchemes: ["https", "http"] });
77
+ return href;
78
+ }
79
+ function nonEmpty(value) {
80
+ const trimmed = value?.trim();
81
+ return trimmed ? trimmed : undefined;
82
+ }
83
+ function aspectRatio(aspect) {
84
+ if (!aspect)
85
+ return undefined;
86
+ if ("preset" in aspect)
87
+ return aspect.preset.replace(":", " / ");
88
+ const { w, h } = aspect.custom;
89
+ return Number.isFinite(w) && Number.isFinite(h) && w > 0 && h > 0 ? `${w} / ${h}` : undefined;
90
+ }
91
+ function sizing(usage) {
92
+ const width = usage.sizing?.width;
93
+ const height = usage.sizing?.height;
94
+ const objectFit = usage.sizing?.objectFit;
95
+ const validWidth = typeof width === "string" && isCssLength(width) ? width : undefined;
96
+ const validHeight = typeof height === "string" && isCssLength(height) ? height : undefined;
97
+ const validObjectFit = objectFit === "cover" || objectFit === "contain" || objectFit === "fill" ? objectFit : undefined;
98
+ return validWidth || validHeight || validObjectFit
99
+ ? { width: validWidth, height: validHeight, objectFit: validObjectFit }
100
+ : undefined;
101
+ }
102
+ function validFocal(value) {
103
+ return value &&
104
+ Number.isFinite(value.x) &&
105
+ Number.isFinite(value.y) &&
106
+ value.x >= 0 &&
107
+ value.x <= 1 &&
108
+ value.y >= 0 &&
109
+ value.y <= 1
110
+ ? value
111
+ : undefined;
112
+ }
113
+ function validOpacity(value) {
114
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1 ? value : undefined;
115
+ }
116
+ //# sourceMappingURL=image-usage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-usage.js","sourceRoot":"","sources":["../../src/render/image-usage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * THE page/fragment renderer (Phase 7): ONE render path shared by the public
3
+ * page, the infinite-scroll fragment endpoint, and the editor canvas — the
4
+ * append path goes through the same function as the initial render (invariant
5
+ * A's append clause, decision 004 §3.2).
6
+ *
7
+ * - Broken bindings render a VISIBLE diagnostic in BOTH runtimes (plan §6
8
+ * break-it-on-purpose clause) — never a silent empty.
9
+ * - Empty repeaters render an explicit empty-state slot (invariant J).
10
+ */
11
+ import { type BlockNode, type DataView, type Instance, type Locale, type PageNode, type RepeaterNode, type ResolveCtx, type ScopeStack } from "@batterai/bind-core";
12
+ import type { BlockPackage } from "@batterai/contract";
13
+ import type { RepeaterRender } from "../resolver/resolution-service";
14
+ export declare class BrokenBinding {
15
+ readonly reason: string;
16
+ constructor(reason: string);
17
+ }
18
+ export interface PageRenderDeps {
19
+ readonly blocks: ReadonlyMap<string, BlockPackage>;
20
+ readonly locale: Locale;
21
+ readonly resolveCtx: ResolveCtx;
22
+ /** true => blocks receive editMode context (canvas); public stays false. */
23
+ readonly editMode?: boolean;
24
+ /**
25
+ * Detail click-through (plan §3 "click on the detail page"): the HOST owns
26
+ * URL knowledge (route registry), the renderer only wraps rows in a link.
27
+ * Return undefined to render a row without one. Because this lives on the
28
+ * shared deps, initial rows, fragment-appended rows, and the editor canvas
29
+ * all agree (invariant A's append clause covers the anchor too).
30
+ */
31
+ readonly detailHrefFor?: (node: RepeaterNode, instance: Instance) => string | undefined;
32
+ /**
33
+ * Host-supplied block DATA (BlockConfig.data): resolved media info for
34
+ * `battery.image`, the routed instance id for `battery.form`, etc. Blocks
35
+ * stay pure functions (never do I/O); the host computes this ONCE and the
36
+ * same hook feeds page, fragment, and editor canvas (invariant A). Receives
37
+ * the RESOLVED props so a bound prop (e.g. an instance field holding a
38
+ * media asset id) drives the lookup exactly like a literal one.
39
+ */
40
+ readonly dataFor?: (node: BlockNode, resolvedProps: Readonly<Record<string, unknown>>) => Record<string, unknown> | undefined;
41
+ }
42
+ export declare function renderRepeaterRows(render: RepeaterRender, view: DataView, deps: PageRenderDeps, repeaters: ReadonlyMap<string, RepeaterRender>): string[];
43
+ export declare function renderNode(node: PageNode, stack: ScopeStack, view: DataView, deps: PageRenderDeps, repeaters: ReadonlyMap<string, RepeaterRender>): string;
44
+ //# sourceMappingURL=page-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-renderer.d.ts","sourceRoot":"","sources":["../../src/render/page-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAKL,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,QAAQ,EACb,KAAK,MAAM,EACX,KAAK,QAAQ,EAEb,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,UAAU,EAChB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAe,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEpE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAGrE,qBAAa,aAAa;IACZ,QAAQ,CAAC,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM;CACpC;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACnD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;OAMG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,KAAK,MAAM,GAAG,SAAS,CAAC;IACxF;;;;;;;OAOG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CACjB,IAAI,EAAE,SAAS,EACf,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,KAC7C,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CAC1C;AAoCD,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,cAAc,EACpB,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,GAC7C,MAAM,EAAE,CAWV;AAED,wBAAgB,UAAU,CACxB,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,UAAU,EACjB,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,cAAc,EACpB,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,GAC7C,MAAM,CA8DR"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * THE page/fragment renderer (Phase 7): ONE render path shared by the public
3
+ * page, the infinite-scroll fragment endpoint, and the editor canvas — the
4
+ * append path goes through the same function as the initial render (invariant
5
+ * A's append clause, decision 004 §3.2).
6
+ *
7
+ * - Broken bindings render a VISIBLE diagnostic in BOTH runtimes (plan §6
8
+ * break-it-on-purpose clause) — never a silent empty.
9
+ * - Empty repeaters render an explicit empty-state slot (invariant J).
10
+ */
11
+ import { MalformedExpr, MissingData, defaultEscaper, resolve, } from "@batterai/bind-core";
12
+ import { renderBlockToDSD } from "@batterai/block-sdk";
13
+ import { hostMessage } from "../messages.js";
14
+ export class BrokenBinding {
15
+ reason;
16
+ constructor(reason) {
17
+ this.reason = reason;
18
+ }
19
+ }
20
+ function resolveProp(prop, stack, view, ctx) {
21
+ try {
22
+ if (prop.kind === "literal")
23
+ return prop.value;
24
+ if (prop.kind === "binding") {
25
+ const rv = resolve(prop.bind.$bind, stack, view, ctx);
26
+ return rv.kind === "reference" ? rv.value.id : rv.value;
27
+ }
28
+ return prop.rich.doc
29
+ .map((n) => {
30
+ if (n.t === "text")
31
+ return n.text;
32
+ const rv = resolve(n.expr, stack, view, ctx);
33
+ return rv.kind === "string" ? rv.value : rv.kind === "number" ? String(rv.value) : "";
34
+ })
35
+ .join("");
36
+ }
37
+ catch (e) {
38
+ if (e instanceof MissingData || e instanceof MalformedExpr) {
39
+ // The explicit, monitorable diagnostic — NEVER a silent empty render.
40
+ return new BrokenBinding(e.message);
41
+ }
42
+ throw e;
43
+ }
44
+ }
45
+ function brokenMarker(field, broken, locale) {
46
+ const esc = (s) => defaultEscaper.escape(s, { kind: "html-text" });
47
+ const message = hostMessage(locale, "binding.broken", { reason: broken.reason });
48
+ return `<span class="be-broken-binding" role="alert" data-broken-field="${esc(field)}">${esc(message)}</span>`;
49
+ }
50
+ export function renderRepeaterRows(render, view, deps, repeaters) {
51
+ return render.items.map((inst, i) => {
52
+ const row = renderNode(render.node.itemTemplate, render.itemStacks[i], view, deps, repeaters);
53
+ const href = deps.detailHrefFor?.(render.node, inst);
54
+ if (href === undefined)
55
+ return row;
56
+ // Block-level anchor around the row (valid: <a> takes flow content). The
57
+ // href passes the url-context escaper — relative paths only carry
58
+ // attribute-escaping; an absolute value would hit the scheme allowlist.
59
+ const safe = defaultEscaper.escape(href, { kind: "url", allowedSchemes: ["https", "http"] });
60
+ return `<a class="be-detail-link" href="${safe}">${row}</a>`;
61
+ });
62
+ }
63
+ export function renderNode(node, stack, view, deps, repeaters) {
64
+ if (node.kind === "repeater") {
65
+ const render = repeaters.get(node.id);
66
+ const id = defaultEscaper.escape(node.id, { kind: "attribute", attrName: "data-repeater" });
67
+ if (!render || render.items.length === 0) {
68
+ // Invariant J: an explicit, visible empty state — never a blank gap.
69
+ return `<div data-repeater="${id}" data-repeater-empty role="note">${defaultEscaper.escape(hostMessage(deps.locale, "repeater.empty"), { kind: "html-text" })}</div>`;
70
+ }
71
+ const rows = renderRepeaterRows(render, view, deps, repeaters);
72
+ const sentinel = render.hasMore && render.cursor
73
+ ? `<div data-load-more data-repeater-id="${id}" data-cursor="${defaultEscaper.escape(render.cursor, { kind: "attribute", attrName: "data-cursor" })}"></div>`
74
+ : "";
75
+ return `<div data-repeater="${id}">${rows.join("\n")}${sentinel}</div>`;
76
+ }
77
+ const block = node;
78
+ const pkg = deps.blocks.get(block.blockType);
79
+ if (!pkg) {
80
+ const type = defaultEscaper.escape(block.blockType, { kind: "html-text" });
81
+ return `<div class="be-unregistered-block" role="alert">${defaultEscaper.escape(hostMessage(deps.locale, "block.unknown", { type: block.blockType }), { kind: "html-text" })}</div>`;
82
+ }
83
+ const props = {};
84
+ const brokenMarkers = [];
85
+ for (const [name, prop] of Object.entries(block.props)) {
86
+ const value = resolveProp(prop, stack, view, deps.resolveCtx);
87
+ if (value instanceof BrokenBinding) {
88
+ brokenMarkers.push(brokenMarker(name, value, deps.locale));
89
+ props[name] = ""; // the block renders empty; the DIAGNOSTIC is beside it
90
+ }
91
+ else {
92
+ props[name] = value;
93
+ }
94
+ }
95
+ const config = {
96
+ props,
97
+ data: deps.dataFor?.(block, props) ?? {},
98
+ context: { locale: deps.locale, editMode: deps.editMode ?? false },
99
+ };
100
+ let html = renderBlockToDSD(pkg, config);
101
+ if (brokenMarkers.length > 0)
102
+ html += brokenMarkers.join("");
103
+ for (const child of block.children ?? []) {
104
+ html += renderNode(child, stack, view, deps, repeaters);
105
+ }
106
+ if (deps.editMode) {
107
+ // Editor canvas only: stamp node identity so the client maps DOM events
108
+ // back to PageNodes without hardcoding ids (blueprint §13). display:
109
+ // contents keeps the wrapper layout-neutral; public bytes are untouched.
110
+ const id = defaultEscaper.escape(block.id, { kind: "attribute", attrName: "data-be-node" });
111
+ html = `<div class="be-node" style="display: contents" data-be-node="${id}">${html}</div>`;
112
+ }
113
+ return html;
114
+ }
115
+ //# sourceMappingURL=page-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-renderer.js","sourceRoot":"","sources":["../../src/render/page-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,aAAa,EACb,WAAW,EACX,cAAc,EACd,OAAO,GAUR,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,OAAO,aAAa;IACH;IAArB,YAAqB,MAAc;QAAd,WAAM,GAAN,MAAM,CAAQ;IAAG,CAAC;CACxC;AA8BD,SAAS,WAAW,CAClB,IAAe,EACf,KAAiB,EACjB,IAAc,EACd,GAAe;IAEf,IAAI,CAAC;QACH,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC;QAC/C,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC5B,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;YACtD,OAAO,EAAE,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC;QAC1D,CAAC;QACD,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG;aACjB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YACT,IAAI,CAAC,CAAC,CAAC,KAAK,MAAM;gBAAE,OAAO,CAAC,CAAC,IAAI,CAAC;YAClC,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;YAC7C,OAAO,EAAE,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACxF,CAAC,CAAC;aACD,IAAI,CAAC,EAAE,CAAC,CAAC;IACd,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,YAAY,WAAW,IAAI,CAAC,YAAY,aAAa,EAAE,CAAC;YAC3D,sEAAsE;YACtE,OAAO,IAAI,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QACtC,CAAC;QACD,MAAM,CAAC,CAAC;IACV,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,KAAa,EAAE,MAAqB,EAAE,MAAc;IACxE,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;IAC3E,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,EAAE,gBAAgB,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACjF,OAAO,mEAAmE,GAAG,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC;AACjH,CAAC;AAED,MAAM,UAAU,kBAAkB,CAChC,MAAsB,EACtB,IAAc,EACd,IAAoB,EACpB,SAA8C;IAE9C,OAAO,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE;QAClC,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAE,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;QAC/F,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACrD,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,GAAG,CAAC;QACnC,yEAAyE;QACzE,kEAAkE;QAClE,wEAAwE;QACxE,MAAM,IAAI,GAAG,cAAc,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QAC7F,OAAO,mCAAmC,IAAI,KAAK,GAAG,MAAM,CAAC;IAC/D,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,UAAU,CACxB,IAAc,EACd,KAAiB,EACjB,IAAc,EACd,IAAoB,EACpB,SAA8C;IAE9C,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACtC,MAAM,EAAE,GAAG,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC,CAAC;QAC5F,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzC,qEAAqE;YACrE,OAAO,uBAAuB,EAAE,qCAAqC,cAAc,CAAC,MAAM,CACxF,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,gBAAgB,CAAC,EAC1C,EAAE,IAAI,EAAE,WAAW,EAAE,CACtB,QAAQ,CAAC;QACZ,CAAC;QACD,MAAM,IAAI,GAAG,kBAAkB,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;QAC/D,MAAM,QAAQ,GACZ,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,MAAM;YAC7B,CAAC,CAAC,yCAAyC,EAAE,kBAAkB,cAAc,CAAC,MAAM,CAChF,MAAM,CAAC,MAAM,EACb,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,CAC/C,UAAU;YACb,CAAC,CAAC,EAAE,CAAC;QACT,OAAO,uBAAuB,EAAE,KAAK,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,QAAQ,QAAQ,CAAC;IAC1E,CAAC;IAED,MAAM,KAAK,GAAG,IAAiB,CAAC;IAChC,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAC7C,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,GAAG,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;QAC3E,OAAO,mDAAmD,cAAc,CAAC,MAAM,CAC7E,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,eAAe,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,EACpE,EAAE,IAAI,EAAE,WAAW,EAAE,CACtB,QAAQ,CAAC;IACZ,CAAC;IAED,MAAM,KAAK,GAA4B,EAAE,CAAC;IAC1C,MAAM,aAAa,GAAa,EAAE,CAAC;IACnC,KAAK,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QACvD,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QAC9D,IAAI,KAAK,YAAY,aAAa,EAAE,CAAC;YACnC,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;YAC3D,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,uDAAuD;QAC3E,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;QACtB,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAgB;QAC1B,KAAK;QACL,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE;QACxC,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,KAAK,EAAE;KACnE,CAAC;IACF,IAAI,IAAI,GAAG,gBAAgB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACzC,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC;QAAE,IAAI,IAAI,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC7D,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;QACzC,IAAI,IAAI,UAAU,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC;IAC1D,CAAC;IACD,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClB,wEAAwE;QACxE,qEAAqE;QACrE,yEAAyE;QACzE,MAAM,EAAE,GAAG,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC,CAAC;QAC5F,IAAI,GAAG,gEAAgE,EAAE,KAAK,IAAI,QAAQ,CAAC;IAC7F,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * HostDataLoader (decision 004 §3.1): the concrete DataLoader — dedup +
3
+ * batch-by-group + reject-on-unsatisfiable. The literal mechanism behind
4
+ * invariant C: N rows referencing the same content type collapse into ONE
5
+ * getInstancesByIds call, counted by suite C.
6
+ */
7
+ import type { ContentType, ContentTypeId, DataLoader, DataNeed, DataView, Instance, PlanContext } from "@batterai/bind-core";
8
+ import type { ContentSource } from "../content-source/content-source";
9
+ import type { Viewer } from "../content-source/viewer";
10
+ export declare class UnsatisfiableNeed extends Error {
11
+ readonly need: DataNeed;
12
+ readonly reason: string;
13
+ constructor(need: DataNeed, reason: string);
14
+ }
15
+ export interface LoadedData {
16
+ readonly instances: ReadonlyMap<string, Instance>;
17
+ readonly pages: ReadonlyMap<string, readonly Instance[]>;
18
+ }
19
+ export declare class HostDataLoader implements DataLoader {
20
+ private readonly source;
21
+ private readonly viewer;
22
+ private readonly contentTypes;
23
+ /** Suite C's call accounting. */
24
+ readonly callLog: string[];
25
+ private readonly instances;
26
+ private readonly pageByScope;
27
+ private readonly cursorByScope;
28
+ /** Memo: a need satisfied earlier in THIS render is never re-fetched —
29
+ * multi-wave planning may legitimately re-encounter the same need (a
30
+ * repeater's own node re-planned per item), and re-encounter must be free
31
+ * (suite C2's exact-call-count bound depends on it). */
32
+ private readonly seenInstancesNeeds;
33
+ constructor(source: ContentSource, viewer: Viewer, contentTypes: ReadonlyMap<ContentTypeId, ContentType>);
34
+ /** Pages are keyed by the FULL need identity (type+query+cursor JSON) — two
35
+ * same-type repeaters with different queries must never clobber each other
36
+ * (a collision C2's distinct-query fixture exposed during the build). */
37
+ static needKey(need: Extract<DataNeed, {
38
+ kind: "instances";
39
+ }>): string;
40
+ pageMeta(needKey: string): {
41
+ cursor?: string;
42
+ hasMore: boolean;
43
+ } | undefined;
44
+ load(needs: readonly DataNeed[], ctx: PlanContext): Promise<DataView>;
45
+ /** Builds the DataView from the ctx the caller asked for (decision 004
46
+ * §3.1). Data scoping is delegated to the CMS (decision 012); no owner brand. */
47
+ view(ctx: PlanContext): DataView;
48
+ }
49
+ //# sourceMappingURL=data-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-loader.d.ts","sourceRoot":"","sources":["../../src/resolver/data-loader.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EACV,WAAW,EACX,aAAa,EACb,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,QAAQ,EAER,WAAW,EAEZ,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAEvD,qBAAa,iBAAkB,SAAQ,KAAK;IAC9B,QAAQ,CAAC,IAAI,EAAE,QAAQ;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM;gBAAvC,IAAI,EAAE,QAAQ,EAAW,MAAM,EAAE,MAAM;CAI7D;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAClD,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,CAAC,CAAC;CAC1D;AAED,qBAAa,cAAe,YAAW,UAAU;IAa7C,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAd/B,iCAAiC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAM;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+B;IACzD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA0C;IACtE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA4D;IAC1F;;;4DAGwD;IACxD,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAqB;gBAGrC,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,WAAW,CAAC,aAAa,EAAE,WAAW,CAAC;IAGxE;;6EAEyE;IACzE,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,QAAQ,EAAE;QAAE,IAAI,EAAE,WAAW,CAAA;KAAE,CAAC,GAAG,MAAM;IAItE,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,GAAG,SAAS;IAItE,IAAI,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC;IA+D3E;qFACiF;IACjF,IAAI,CAAC,GAAG,EAAE,WAAW,GAAG,QAAQ;CAajC"}
@@ -0,0 +1,121 @@
1
+ export class UnsatisfiableNeed extends Error {
2
+ need;
3
+ reason;
4
+ constructor(need, reason) {
5
+ super(`UnsatisfiableNeed: ${JSON.stringify(need)} — ${reason}`);
6
+ this.need = need;
7
+ this.reason = reason;
8
+ this.name = "UnsatisfiableNeed";
9
+ }
10
+ }
11
+ export class HostDataLoader {
12
+ source;
13
+ viewer;
14
+ contentTypes;
15
+ /** Suite C's call accounting. */
16
+ callLog = [];
17
+ instances = new Map();
18
+ pageByScope = new Map();
19
+ cursorByScope = new Map();
20
+ /** Memo: a need satisfied earlier in THIS render is never re-fetched —
21
+ * multi-wave planning may legitimately re-encounter the same need (a
22
+ * repeater's own node re-planned per item), and re-encounter must be free
23
+ * (suite C2's exact-call-count bound depends on it). */
24
+ seenInstancesNeeds = new Set();
25
+ constructor(source, viewer, contentTypes) {
26
+ this.source = source;
27
+ this.viewer = viewer;
28
+ this.contentTypes = contentTypes;
29
+ }
30
+ /** Pages are keyed by the FULL need identity (type+query+cursor JSON) — two
31
+ * same-type repeaters with different queries must never clobber each other
32
+ * (a collision C2's distinct-query fixture exposed during the build). */
33
+ static needKey(need) {
34
+ return JSON.stringify(need);
35
+ }
36
+ pageMeta(needKey) {
37
+ return this.cursorByScope.get(needKey);
38
+ }
39
+ async load(needs, ctx) {
40
+ // ---- group ---------------------------------------------------------
41
+ const instanceIdsByType = new Map();
42
+ const instancesNeeds = [];
43
+ const referenceNeeds = [];
44
+ for (const need of needs) {
45
+ if (need.kind === "instance") {
46
+ if (this.instances.has(`${need.contentTypeId}:${need.instanceId}`))
47
+ continue; // memo
48
+ if (!instanceIdsByType.has(need.contentTypeId))
49
+ instanceIdsByType.set(need.contentTypeId, new Set());
50
+ instanceIdsByType.get(need.contentTypeId).add(need.instanceId);
51
+ }
52
+ else if (need.kind === "instances") {
53
+ const memoKey = JSON.stringify(need);
54
+ if (this.seenInstancesNeeds.has(memoKey))
55
+ continue; // memo (incl. intra-batch dedup)
56
+ this.seenInstancesNeeds.add(memoKey);
57
+ instancesNeeds.push(need);
58
+ }
59
+ else {
60
+ referenceNeeds.push(need);
61
+ }
62
+ }
63
+ // Reference needs: resolve source instance -> target id, group by TARGET type.
64
+ for (const ref of referenceNeeds) {
65
+ const source = [...this.instances.values()].find((i) => i.id === ref.fromInstanceId);
66
+ if (!source)
67
+ throw new UnsatisfiableNeed(ref, "source instance not loaded in a prior wave");
68
+ const ct = this.contentTypes.get(source.contentTypeId);
69
+ const field = ct?.fields.find((f) => f.name === ref.field);
70
+ if (!field?.refType)
71
+ throw new UnsatisfiableNeed(ref, `field ${ref.field} is not a reference`);
72
+ const targetId = source.values[ref.field];
73
+ if (typeof targetId !== "string")
74
+ throw new UnsatisfiableNeed(ref, "reference value is not an id");
75
+ if (this.instances.has(`${field.refType}:${targetId}`))
76
+ continue; // memo
77
+ if (!instanceIdsByType.has(field.refType))
78
+ instanceIdsByType.set(field.refType, new Set());
79
+ instanceIdsByType.get(field.refType).add(targetId);
80
+ }
81
+ // ---- batched I/O: ONE call per group --------------------------------
82
+ for (const [ct, ids] of instanceIdsByType) {
83
+ this.callLog.push(`getInstancesByIds:${ct}:${ids.size}`);
84
+ const got = await this.source.getInstancesByIds(ct, [...ids], this.viewer);
85
+ const gotIds = new Set(got.map((g) => g.id));
86
+ for (const id of ids) {
87
+ if (!gotIds.has(id)) {
88
+ throw new UnsatisfiableNeed({ kind: "instance", contentTypeId: ct, instanceId: id }, "adapter returned no instance for a declared need");
89
+ }
90
+ }
91
+ for (const inst of got)
92
+ this.instances.set(`${ct}:${inst.id}`, inst);
93
+ }
94
+ for (const need of instancesNeeds) {
95
+ const key = HostDataLoader.needKey(need);
96
+ this.callLog.push(`listInstances:${need.contentTypeId}:${need.cursor ?? "first"}`);
97
+ const page = await this.source.listInstances(need.contentTypeId, need.query, need.cursor, this.viewer);
98
+ this.pageByScope.set(key, page.items);
99
+ this.cursorByScope.set(key, { cursor: page.cursor, hasMore: page.hasMore });
100
+ for (const inst of page.items)
101
+ this.instances.set(`${need.contentTypeId}:${inst.id}`, inst);
102
+ }
103
+ return this.view(ctx);
104
+ }
105
+ /** Builds the DataView from the ctx the caller asked for (decision 004
106
+ * §3.1). Data scoping is delegated to the CMS (decision 012); no owner brand. */
107
+ view(ctx) {
108
+ const instances = this.instances;
109
+ const pages = this.pageByScope;
110
+ const contentTypes = this.contentTypes;
111
+ return {
112
+ locale: ctx.locale,
113
+ fallbackLocale: ctx.fallbackLocale,
114
+ timeZone: ctx.timeZone,
115
+ getInstance: (ct, id) => instances.get(`${ct}:${id}`),
116
+ getInstancePage: (_ct, scopeId) => pages.get(scopeId),
117
+ getContentType: (ct) => contentTypes.get(ct),
118
+ };
119
+ }
120
+ }
121
+ //# sourceMappingURL=data-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-loader.js","sourceRoot":"","sources":["../../src/resolver/data-loader.ts"],"names":[],"mappings":"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"}