@orthacms/content-server 0.0.0-reserve.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 (396) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +7 -0
  3. package/dist/define.d.ts +17 -0
  4. package/dist/define.d.ts.map +1 -0
  5. package/dist/define.js +28 -0
  6. package/dist/index.d.ts +48 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +109 -0
  9. package/dist/lib/collection/define.d.ts +35 -0
  10. package/dist/lib/collection/define.d.ts.map +1 -0
  11. package/dist/lib/collection/define.js +217 -0
  12. package/dist/lib/collection/table-builder.d.ts +28 -0
  13. package/dist/lib/collection/table-builder.d.ts.map +1 -0
  14. package/dist/lib/collection/table-builder.js +306 -0
  15. package/dist/lib/content-types/controllers/get-content-schema.controller.d.ts +24 -0
  16. package/dist/lib/content-types/controllers/get-content-schema.controller.d.ts.map +1 -0
  17. package/dist/lib/content-types/controllers/get-content-schema.controller.js +58 -0
  18. package/dist/lib/content-types/controllers/get-filter-fields.controller.d.ts +35 -0
  19. package/dist/lib/content-types/controllers/get-filter-fields.controller.d.ts.map +1 -0
  20. package/dist/lib/content-types/controllers/get-filter-fields.controller.js +74 -0
  21. package/dist/lib/content-types/controllers/list-content-schema.controller.d.ts +15 -0
  22. package/dist/lib/content-types/controllers/list-content-schema.controller.d.ts.map +1 -0
  23. package/dist/lib/content-types/controllers/list-content-schema.controller.js +38 -0
  24. package/dist/lib/content-types/queries/workspace-grants.query.d.ts +25 -0
  25. package/dist/lib/content-types/queries/workspace-grants.query.d.ts.map +1 -0
  26. package/dist/lib/content-types/queries/workspace-grants.query.js +45 -0
  27. package/dist/lib/content.module.d.ts +21 -0
  28. package/dist/lib/content.module.d.ts.map +1 -0
  29. package/dist/lib/content.module.js +264 -0
  30. package/dist/lib/content.tokens.d.ts +5 -0
  31. package/dist/lib/content.tokens.d.ts.map +1 -0
  32. package/dist/lib/content.tokens.js +9 -0
  33. package/dist/lib/copilot/content-tool.provider.d.ts +64 -0
  34. package/dist/lib/copilot/content-tool.provider.d.ts.map +1 -0
  35. package/dist/lib/copilot/content-tool.provider.js +309 -0
  36. package/dist/lib/copilot/diff-snapshots.d.ts +42 -0
  37. package/dist/lib/copilot/diff-snapshots.d.ts.map +1 -0
  38. package/dist/lib/copilot/diff-snapshots.js +77 -0
  39. package/dist/lib/copilot/entry-proposal.applier.d.ts +89 -0
  40. package/dist/lib/copilot/entry-proposal.applier.d.ts.map +1 -0
  41. package/dist/lib/copilot/entry-proposal.applier.js +245 -0
  42. package/dist/lib/copilot/entry-proposal.provider.d.ts +157 -0
  43. package/dist/lib/copilot/entry-proposal.provider.d.ts.map +1 -0
  44. package/dist/lib/copilot/entry-proposal.provider.js +619 -0
  45. package/dist/lib/copilot/filter-schema.d.ts +61 -0
  46. package/dist/lib/copilot/filter-schema.d.ts.map +1 -0
  47. package/dist/lib/copilot/filter-schema.js +139 -0
  48. package/dist/lib/copilot/project-entry.d.ts +23 -0
  49. package/dist/lib/copilot/project-entry.d.ts.map +1 -0
  50. package/dist/lib/copilot/project-entry.js +36 -0
  51. package/dist/lib/copilot/proposal-kinds.d.ts +30 -0
  52. package/dist/lib/copilot/proposal-kinds.d.ts.map +1 -0
  53. package/dist/lib/copilot/proposal-kinds.js +32 -0
  54. package/dist/lib/copilot/revision-tool.provider.d.ts +56 -0
  55. package/dist/lib/copilot/revision-tool.provider.d.ts.map +1 -0
  56. package/dist/lib/copilot/revision-tool.provider.js +215 -0
  57. package/dist/lib/docs/content-schemas.d.ts +34 -0
  58. package/dist/lib/docs/content-schemas.d.ts.map +1 -0
  59. package/dist/lib/docs/content-schemas.js +617 -0
  60. package/dist/lib/docs/describe-content-api.d.ts +22 -0
  61. package/dist/lib/docs/describe-content-api.d.ts.map +1 -0
  62. package/dist/lib/docs/describe-content-api.js +173 -0
  63. package/dist/lib/docs/field-schema.d.ts +28 -0
  64. package/dist/lib/docs/field-schema.d.ts.map +1 -0
  65. package/dist/lib/docs/field-schema.js +214 -0
  66. package/dist/lib/entries/application/use-cases/bulk-publish-entries.use-case.d.ts +23 -0
  67. package/dist/lib/entries/application/use-cases/bulk-publish-entries.use-case.d.ts.map +1 -0
  68. package/dist/lib/entries/application/use-cases/bulk-publish-entries.use-case.js +77 -0
  69. package/dist/lib/entries/application/use-cases/bulk-unpublish-entries.use-case.d.ts +23 -0
  70. package/dist/lib/entries/application/use-cases/bulk-unpublish-entries.use-case.d.ts.map +1 -0
  71. package/dist/lib/entries/application/use-cases/bulk-unpublish-entries.use-case.js +66 -0
  72. package/dist/lib/entries/application/use-cases/publish-entry.use-case.d.ts +43 -0
  73. package/dist/lib/entries/application/use-cases/publish-entry.use-case.d.ts.map +1 -0
  74. package/dist/lib/entries/application/use-cases/publish-entry.use-case.js +106 -0
  75. package/dist/lib/entries/application/use-cases/unpublish-entry.use-case.d.ts +30 -0
  76. package/dist/lib/entries/application/use-cases/unpublish-entry.use-case.d.ts.map +1 -0
  77. package/dist/lib/entries/application/use-cases/unpublish-entry.use-case.js +70 -0
  78. package/dist/lib/entries/controllers/bulk-entries.controller.d.ts +26 -0
  79. package/dist/lib/entries/controllers/bulk-entries.controller.d.ts.map +1 -0
  80. package/dist/lib/entries/controllers/create-entry.controller.d.ts +20 -0
  81. package/dist/lib/entries/controllers/create-entry.controller.d.ts.map +1 -0
  82. package/dist/lib/entries/controllers/delete-entry.controller.d.ts +24 -0
  83. package/dist/lib/entries/controllers/delete-entry.controller.d.ts.map +1 -0
  84. package/dist/lib/entries/controllers/get-entry.controller.d.ts +34 -0
  85. package/dist/lib/entries/controllers/get-entry.controller.d.ts.map +1 -0
  86. package/dist/lib/entries/controllers/list-entries.controller.d.ts +20 -0
  87. package/dist/lib/entries/controllers/list-entries.controller.d.ts.map +1 -0
  88. package/dist/lib/entries/controllers/publish-entry.controller.d.ts +19 -0
  89. package/dist/lib/entries/controllers/publish-entry.controller.d.ts.map +1 -0
  90. package/dist/lib/entries/controllers/resolve-type.d.ts +9 -0
  91. package/dist/lib/entries/controllers/resolve-type.d.ts.map +1 -0
  92. package/dist/lib/entries/controllers/update-entry.controller.d.ts +18 -0
  93. package/dist/lib/entries/controllers/update-entry.controller.d.ts.map +1 -0
  94. package/dist/lib/entries/domain/entry-publish-blocked.error.d.ts +13 -0
  95. package/dist/lib/entries/domain/entry-publish-blocked.error.d.ts.map +1 -0
  96. package/dist/lib/entries/domain/entry-publish-blocked.error.js +19 -0
  97. package/dist/lib/entries/domain/entry.d.ts +71 -0
  98. package/dist/lib/entries/domain/entry.d.ts.map +1 -0
  99. package/dist/lib/entries/domain/entry.js +90 -0
  100. package/dist/lib/entries/domain/events/entry-events.d.ts +67 -0
  101. package/dist/lib/entries/domain/events/entry-events.d.ts.map +1 -0
  102. package/dist/lib/entries/domain/events/entry-events.js +100 -0
  103. package/dist/lib/entries/dto/bulk-ids.dto.d.ts +11 -0
  104. package/dist/lib/entries/dto/bulk-ids.dto.d.ts.map +1 -0
  105. package/dist/lib/entries/dto/list-entries-query.dto.d.ts +46 -0
  106. package/dist/lib/entries/dto/list-entries-query.dto.d.ts.map +1 -0
  107. package/dist/lib/entries/dto/relation-delta-map.validator.d.ts +19 -0
  108. package/dist/lib/entries/dto/relation-delta-map.validator.d.ts.map +1 -0
  109. package/dist/lib/entries/dto/save-entry.dto.d.ts +46 -0
  110. package/dist/lib/entries/dto/save-entry.dto.d.ts.map +1 -0
  111. package/dist/lib/entries/entries.constants.d.ts +19 -0
  112. package/dist/lib/entries/entries.constants.d.ts.map +1 -0
  113. package/dist/lib/entries/entries.constants.js +21 -0
  114. package/dist/lib/entries/http/controllers/bulk-entries.controller.d.ts +33 -0
  115. package/dist/lib/entries/http/controllers/bulk-entries.controller.d.ts.map +1 -0
  116. package/dist/lib/entries/http/controllers/bulk-entries.controller.js +143 -0
  117. package/dist/lib/entries/http/controllers/create-entry.controller.d.ts +21 -0
  118. package/dist/lib/entries/http/controllers/create-entry.controller.d.ts.map +1 -0
  119. package/dist/lib/entries/http/controllers/create-entry.controller.js +52 -0
  120. package/dist/lib/entries/http/controllers/delete-entry.controller.d.ts +25 -0
  121. package/dist/lib/entries/http/controllers/delete-entry.controller.d.ts.map +1 -0
  122. package/dist/lib/entries/http/controllers/delete-entry.controller.js +84 -0
  123. package/dist/lib/entries/http/controllers/get-entry.controller.d.ts +44 -0
  124. package/dist/lib/entries/http/controllers/get-entry.controller.d.ts.map +1 -0
  125. package/dist/lib/entries/http/controllers/get-entry.controller.js +124 -0
  126. package/dist/lib/entries/http/controllers/list-entries.controller.d.ts +20 -0
  127. package/dist/lib/entries/http/controllers/list-entries.controller.d.ts.map +1 -0
  128. package/dist/lib/entries/http/controllers/list-entries.controller.js +52 -0
  129. package/dist/lib/entries/http/controllers/publish-entry.controller.d.ts +22 -0
  130. package/dist/lib/entries/http/controllers/publish-entry.controller.d.ts.map +1 -0
  131. package/dist/lib/entries/http/controllers/publish-entry.controller.js +68 -0
  132. package/dist/lib/entries/http/controllers/resolve-type.d.ts +9 -0
  133. package/dist/lib/entries/http/controllers/resolve-type.d.ts.map +1 -0
  134. package/dist/lib/entries/http/controllers/resolve-type.js +16 -0
  135. package/dist/lib/entries/http/controllers/to-actor.d.ts +13 -0
  136. package/dist/lib/entries/http/controllers/to-actor.d.ts.map +1 -0
  137. package/dist/lib/entries/http/controllers/to-actor.js +15 -0
  138. package/dist/lib/entries/http/controllers/update-entry.controller.d.ts +19 -0
  139. package/dist/lib/entries/http/controllers/update-entry.controller.d.ts.map +1 -0
  140. package/dist/lib/entries/http/controllers/update-entry.controller.js +51 -0
  141. package/dist/lib/entries/http/dto/bulk-ids.dto.d.ts +11 -0
  142. package/dist/lib/entries/http/dto/bulk-ids.dto.d.ts.map +1 -0
  143. package/dist/lib/entries/http/dto/bulk-ids.dto.js +33 -0
  144. package/dist/lib/entries/http/dto/list-entries-query.dto.d.ts +66 -0
  145. package/dist/lib/entries/http/dto/list-entries-query.dto.d.ts.map +1 -0
  146. package/dist/lib/entries/http/dto/list-entries-query.dto.js +192 -0
  147. package/dist/lib/entries/http/dto/relation-delta-map.validator.d.ts +30 -0
  148. package/dist/lib/entries/http/dto/relation-delta-map.validator.d.ts.map +1 -0
  149. package/dist/lib/entries/http/dto/relation-delta-map.validator.js +102 -0
  150. package/dist/lib/entries/http/dto/save-entry.dto.d.ts +48 -0
  151. package/dist/lib/entries/http/dto/save-entry.dto.d.ts.map +1 -0
  152. package/dist/lib/entries/http/dto/save-entry.dto.js +120 -0
  153. package/dist/lib/entries/http/guards/content-grant.guard.d.ts +37 -0
  154. package/dist/lib/entries/http/guards/content-grant.guard.d.ts.map +1 -0
  155. package/dist/lib/entries/http/guards/content-grant.guard.js +67 -0
  156. package/dist/lib/entries/infrastructure/persistence/bulk-publish-verdicts.d.ts +22 -0
  157. package/dist/lib/entries/infrastructure/persistence/bulk-publish-verdicts.d.ts.map +1 -0
  158. package/dist/lib/entries/infrastructure/persistence/bulk-publish-verdicts.js +81 -0
  159. package/dist/lib/entries/infrastructure/persistence/entry-counter.service.d.ts +42 -0
  160. package/dist/lib/entries/infrastructure/persistence/entry-counter.service.d.ts.map +1 -0
  161. package/dist/lib/entries/infrastructure/persistence/entry-counter.service.js +73 -0
  162. package/dist/lib/entries/infrastructure/persistence/entry-row.d.ts +56 -0
  163. package/dist/lib/entries/infrastructure/persistence/entry-row.d.ts.map +1 -0
  164. package/dist/lib/entries/infrastructure/persistence/entry-row.js +185 -0
  165. package/dist/lib/entries/infrastructure/persistence/entry-writer.service.d.ts +392 -0
  166. package/dist/lib/entries/infrastructure/persistence/entry-writer.service.d.ts.map +1 -0
  167. package/dist/lib/entries/infrastructure/persistence/entry-writer.service.js +1223 -0
  168. package/dist/lib/entries/infrastructure/persistence/field-selection.d.ts +24 -0
  169. package/dist/lib/entries/infrastructure/persistence/field-selection.d.ts.map +1 -0
  170. package/dist/lib/entries/infrastructure/persistence/media-accept.d.ts +23 -0
  171. package/dist/lib/entries/infrastructure/persistence/media-accept.d.ts.map +1 -0
  172. package/dist/lib/entries/infrastructure/persistence/media-accept.js +55 -0
  173. package/dist/lib/entries/infrastructure/persistence/relation-link.service.d.ts +327 -0
  174. package/dist/lib/entries/infrastructure/persistence/relation-link.service.d.ts.map +1 -0
  175. package/dist/lib/entries/infrastructure/persistence/relation-link.service.js +1008 -0
  176. package/dist/lib/entries/infrastructure/queries/bulk-publish-preview.query.d.ts +19 -0
  177. package/dist/lib/entries/infrastructure/queries/bulk-publish-preview.query.d.ts.map +1 -0
  178. package/dist/lib/entries/infrastructure/queries/bulk-publish-preview.query.js +39 -0
  179. package/dist/lib/entries/infrastructure/queries/entries.service.d.ts +64 -0
  180. package/dist/lib/entries/infrastructure/queries/entries.service.d.ts.map +1 -0
  181. package/dist/lib/entries/infrastructure/queries/entries.service.js +202 -0
  182. package/dist/lib/entries/infrastructure/queries/entry-filter-schema.d.ts +40 -0
  183. package/dist/lib/entries/infrastructure/queries/entry-filter-schema.d.ts.map +1 -0
  184. package/dist/lib/entries/infrastructure/queries/entry-filter-surface.d.ts +38 -0
  185. package/dist/lib/entries/infrastructure/queries/entry-filter-surface.d.ts.map +1 -0
  186. package/dist/lib/entries/infrastructure/queries/entry-filter-surface.js +310 -0
  187. package/dist/lib/entries/infrastructure/queries/entry-scalar-fields.d.ts +25 -0
  188. package/dist/lib/entries/infrastructure/queries/entry-scalar-fields.d.ts.map +1 -0
  189. package/dist/lib/entries/infrastructure/queries/entry-scalar-fields.js +51 -0
  190. package/dist/lib/entries/infrastructure/queries/entry-search.d.ts +19 -0
  191. package/dist/lib/entries/infrastructure/queries/entry-search.d.ts.map +1 -0
  192. package/dist/lib/entries/infrastructure/queries/entry-search.js +52 -0
  193. package/dist/lib/entries/infrastructure/queries/media-refs.query.d.ts +21 -0
  194. package/dist/lib/entries/infrastructure/queries/media-refs.query.d.ts.map +1 -0
  195. package/dist/lib/entries/infrastructure/queries/media-refs.query.js +117 -0
  196. package/dist/lib/entries/services/entries.service.d.ts +59 -0
  197. package/dist/lib/entries/services/entries.service.d.ts.map +1 -0
  198. package/dist/lib/entries/services/entry-counter.service.d.ts +31 -0
  199. package/dist/lib/entries/services/entry-counter.service.d.ts.map +1 -0
  200. package/dist/lib/entries/services/entry-filter-schema.d.ts +32 -0
  201. package/dist/lib/entries/services/entry-filter-schema.d.ts.map +1 -0
  202. package/dist/lib/entries/services/entry-row.d.ts +56 -0
  203. package/dist/lib/entries/services/entry-row.d.ts.map +1 -0
  204. package/dist/lib/entries/services/entry-writer.service.d.ts +193 -0
  205. package/dist/lib/entries/services/entry-writer.service.d.ts.map +1 -0
  206. package/dist/lib/entries/services/relation-link.service.d.ts +115 -0
  207. package/dist/lib/entries/services/relation-link.service.d.ts.map +1 -0
  208. package/dist/lib/entries/types/bulk-publish.d.ts +75 -0
  209. package/dist/lib/entries/types/bulk-publish.d.ts.map +1 -0
  210. package/dist/lib/entries/types/bulk-publish.js +22 -0
  211. package/dist/lib/entries/types/entry-list-view.d.ts +205 -0
  212. package/dist/lib/entries/types/entry-list-view.d.ts.map +1 -0
  213. package/dist/lib/entries/types/entry-list-view.js +5 -0
  214. package/dist/lib/entries/types/filter-surface.d.ts +35 -0
  215. package/dist/lib/entries/types/filter-surface.d.ts.map +1 -0
  216. package/dist/lib/entries/types/filter-surface.js +2 -0
  217. package/dist/lib/extension/entry-extension-boot-check.d.ts +16 -0
  218. package/dist/lib/extension/entry-extension-boot-check.d.ts.map +1 -0
  219. package/dist/lib/extension/entry-extension-boot-check.js +42 -0
  220. package/dist/lib/extension/entry-extension.d.ts +225 -0
  221. package/dist/lib/extension/entry-extension.d.ts.map +1 -0
  222. package/dist/lib/extension/entry-extension.js +27 -0
  223. package/dist/lib/extension/media-asset-resolver.d.ts +85 -0
  224. package/dist/lib/extension/media-asset-resolver.d.ts.map +1 -0
  225. package/dist/lib/extension/media-asset-resolver.js +26 -0
  226. package/dist/lib/extension/per-locale-relation.d.ts +18 -0
  227. package/dist/lib/extension/per-locale-relation.d.ts.map +1 -0
  228. package/dist/lib/extension/relation-locale-sync.d.ts +62 -0
  229. package/dist/lib/extension/relation-locale-sync.d.ts.map +1 -0
  230. package/dist/lib/extension/relation-locale-sync.js +88 -0
  231. package/dist/lib/fields/index.d.ts +94 -0
  232. package/dist/lib/fields/index.d.ts.map +1 -0
  233. package/dist/lib/fields/index.js +203 -0
  234. package/dist/lib/insights/http/controllers/content-pipeline.controller.d.ts +16 -0
  235. package/dist/lib/insights/http/controllers/content-pipeline.controller.d.ts.map +1 -0
  236. package/dist/lib/insights/http/controllers/content-pipeline.controller.js +39 -0
  237. package/dist/lib/insights/http/controllers/content-punchcard.controller.d.ts +18 -0
  238. package/dist/lib/insights/http/controllers/content-punchcard.controller.d.ts.map +1 -0
  239. package/dist/lib/insights/http/controllers/content-punchcard.controller.js +42 -0
  240. package/dist/lib/insights/http/controllers/content-stale.controller.d.ts +17 -0
  241. package/dist/lib/insights/http/controllers/content-stale.controller.d.ts.map +1 -0
  242. package/dist/lib/insights/http/controllers/content-stale.controller.js +40 -0
  243. package/dist/lib/insights/http/controllers/content-totals.controller.d.ts +18 -0
  244. package/dist/lib/insights/http/controllers/content-totals.controller.d.ts.map +1 -0
  245. package/dist/lib/insights/http/controllers/content-totals.controller.js +42 -0
  246. package/dist/lib/insights/http/controllers/content-unshipped.controller.d.ts +17 -0
  247. package/dist/lib/insights/http/controllers/content-unshipped.controller.d.ts.map +1 -0
  248. package/dist/lib/insights/http/controllers/content-unshipped.controller.js +40 -0
  249. package/dist/lib/insights/http/controllers/content-velocity.controller.d.ts +17 -0
  250. package/dist/lib/insights/http/controllers/content-velocity.controller.d.ts.map +1 -0
  251. package/dist/lib/insights/http/controllers/content-velocity.controller.js +41 -0
  252. package/dist/lib/insights/http/dto/insights-range-query.dto.d.ts +15 -0
  253. package/dist/lib/insights/http/dto/insights-range-query.dto.d.ts.map +1 -0
  254. package/dist/lib/insights/http/dto/insights-range-query.dto.js +38 -0
  255. package/dist/lib/insights/infrastructure/queries/content-insights.query.d.ts +77 -0
  256. package/dist/lib/insights/infrastructure/queries/content-insights.query.d.ts.map +1 -0
  257. package/dist/lib/insights/infrastructure/queries/content-insights.query.js +380 -0
  258. package/dist/lib/insights/types/content-insights-view.d.ts +134 -0
  259. package/dist/lib/insights/types/content-insights-view.d.ts.map +1 -0
  260. package/dist/lib/insights/types/content-insights-view.js +10 -0
  261. package/dist/lib/mcp/content-tools.provider.d.ts +83 -0
  262. package/dist/lib/mcp/content-tools.provider.d.ts.map +1 -0
  263. package/dist/lib/mcp/content-tools.provider.js +441 -0
  264. package/dist/lib/mcp/tool-input.d.ts +43 -0
  265. package/dist/lib/mcp/tool-input.d.ts.map +1 -0
  266. package/dist/lib/mcp/tool-input.js +117 -0
  267. package/dist/lib/mcp/tool-schemas.d.ts +34 -0
  268. package/dist/lib/mcp/tool-schemas.d.ts.map +1 -0
  269. package/dist/lib/mcp/tool-schemas.js +282 -0
  270. package/dist/lib/public/controllers/get-public-entry.controller.d.ts +26 -0
  271. package/dist/lib/public/controllers/get-public-entry.controller.d.ts.map +1 -0
  272. package/dist/lib/public/controllers/list-public-entries.controller.d.ts +24 -0
  273. package/dist/lib/public/controllers/list-public-entries.controller.d.ts.map +1 -0
  274. package/dist/lib/public/controllers/public-schema.controller.d.ts +18 -0
  275. package/dist/lib/public/controllers/public-schema.controller.d.ts.map +1 -0
  276. package/dist/lib/public-api/http/api-token-request.d.ts +41 -0
  277. package/dist/lib/public-api/http/api-token-request.d.ts.map +1 -0
  278. package/dist/lib/public-api/http/api-token-request.js +2 -0
  279. package/dist/lib/public-api/http/controllers/public-content-types.controller.d.ts +31 -0
  280. package/dist/lib/public-api/http/controllers/public-content-types.controller.d.ts.map +1 -0
  281. package/dist/lib/public-api/http/controllers/public-content-types.controller.js +93 -0
  282. package/dist/lib/public-api/http/controllers/public-entries.controller.d.ts +101 -0
  283. package/dist/lib/public-api/http/controllers/public-entries.controller.d.ts.map +1 -0
  284. package/dist/lib/public-api/http/controllers/public-entries.controller.js +307 -0
  285. package/dist/lib/public-api/http/controllers/public-entry-writes.controller.d.ts +76 -0
  286. package/dist/lib/public-api/http/controllers/public-entry-writes.controller.d.ts.map +1 -0
  287. package/dist/lib/public-api/http/controllers/public-entry-writes.controller.js +353 -0
  288. package/dist/lib/public-api/http/controllers/resolve-granted-type.d.ts +42 -0
  289. package/dist/lib/public-api/http/controllers/resolve-granted-type.d.ts.map +1 -0
  290. package/dist/lib/public-api/http/controllers/resolve-granted-type.js +25 -0
  291. package/dist/lib/public-api/http/decorators/current-api-token.decorator.d.ts +8 -0
  292. package/dist/lib/public-api/http/decorators/current-api-token.decorator.d.ts.map +1 -0
  293. package/dist/lib/public-api/http/decorators/current-api-token.decorator.js +17 -0
  294. package/dist/lib/public-api/http/dto/public-bulk.dto.d.ts +58 -0
  295. package/dist/lib/public-api/http/dto/public-bulk.dto.d.ts.map +1 -0
  296. package/dist/lib/public-api/http/dto/public-bulk.dto.js +118 -0
  297. package/dist/lib/public-api/http/dto/public-list-entries-query.dto.d.ts +106 -0
  298. package/dist/lib/public-api/http/dto/public-list-entries-query.dto.d.ts.map +1 -0
  299. package/dist/lib/public-api/http/dto/public-list-entries-query.dto.js +297 -0
  300. package/dist/lib/public-api/http/dto/public-save-entry.dto.d.ts +39 -0
  301. package/dist/lib/public-api/http/dto/public-save-entry.dto.d.ts.map +1 -0
  302. package/dist/lib/public-api/http/dto/public-save-entry.dto.js +112 -0
  303. package/dist/lib/public-api/http/guards/api-token-workspace.guard.d.ts +25 -0
  304. package/dist/lib/public-api/http/guards/api-token-workspace.guard.d.ts.map +1 -0
  305. package/dist/lib/public-api/http/guards/api-token-workspace.guard.js +58 -0
  306. package/dist/lib/public-api/http/guards/api-token.guard.d.ts +32 -0
  307. package/dist/lib/public-api/http/guards/api-token.guard.d.ts.map +1 -0
  308. package/dist/lib/public-api/http/guards/api-token.guard.js +92 -0
  309. package/dist/lib/public-api/http/guards/draft-visibility.guard.d.ts +25 -0
  310. package/dist/lib/public-api/http/guards/draft-visibility.guard.d.ts.map +1 -0
  311. package/dist/lib/public-api/http/guards/draft-visibility.guard.js +56 -0
  312. package/dist/lib/public-api/infrastructure/bulk-save-op.d.ts +55 -0
  313. package/dist/lib/public-api/infrastructure/bulk-save-op.d.ts.map +1 -0
  314. package/dist/lib/public-api/infrastructure/bulk-save-op.js +61 -0
  315. package/dist/lib/public-api/infrastructure/field-selection.d.ts +21 -0
  316. package/dist/lib/public-api/infrastructure/field-selection.d.ts.map +1 -0
  317. package/dist/lib/public-api/infrastructure/field-selection.js +72 -0
  318. package/dist/lib/public-api/infrastructure/public-entries.query.d.ts +248 -0
  319. package/dist/lib/public-api/infrastructure/public-entries.query.d.ts.map +1 -0
  320. package/dist/lib/public-api/infrastructure/public-entries.query.js +557 -0
  321. package/dist/lib/public-api/infrastructure/public-entry-row.d.ts +31 -0
  322. package/dist/lib/public-api/infrastructure/public-entry-row.d.ts.map +1 -0
  323. package/dist/lib/public-api/infrastructure/public-entry-row.js +76 -0
  324. package/dist/lib/public-api/infrastructure/public-entry-writes.service.d.ts +188 -0
  325. package/dist/lib/public-api/infrastructure/public-entry-writes.service.d.ts.map +1 -0
  326. package/dist/lib/public-api/infrastructure/public-entry-writes.service.js +321 -0
  327. package/dist/lib/public-api/infrastructure/public-expansion.query.d.ts +98 -0
  328. package/dist/lib/public-api/infrastructure/public-expansion.query.d.ts.map +1 -0
  329. package/dist/lib/public-api/infrastructure/public-expansion.query.js +384 -0
  330. package/dist/lib/public-api/types/public-bulk.d.ts +77 -0
  331. package/dist/lib/public-api/types/public-bulk.d.ts.map +1 -0
  332. package/dist/lib/public-api/types/public-bulk.js +23 -0
  333. package/dist/lib/public-api/types/public-entry.d.ts +97 -0
  334. package/dist/lib/public-api/types/public-entry.d.ts.map +1 -0
  335. package/dist/lib/public-api/types/public-entry.js +8 -0
  336. package/dist/lib/public-api/types/public-expansion.d.ts +92 -0
  337. package/dist/lib/public-api/types/public-expansion.d.ts.map +1 -0
  338. package/dist/lib/public-api/types/public-expansion.js +2 -0
  339. package/dist/lib/registry/content-type-registry.d.ts +103 -0
  340. package/dist/lib/registry/content-type-registry.d.ts.map +1 -0
  341. package/dist/lib/registry/content-type-registry.js +155 -0
  342. package/dist/lib/revisions/application/ports/revision-store.d.ts +71 -0
  343. package/dist/lib/revisions/application/ports/revision-store.d.ts.map +1 -0
  344. package/dist/lib/revisions/application/ports/revision-store.js +9 -0
  345. package/dist/lib/revisions/application/use-cases/publish-revision.use-case.d.ts +40 -0
  346. package/dist/lib/revisions/application/use-cases/publish-revision.use-case.d.ts.map +1 -0
  347. package/dist/lib/revisions/application/use-cases/publish-revision.use-case.js +66 -0
  348. package/dist/lib/revisions/application/use-cases/restore-revision.use-case.d.ts +36 -0
  349. package/dist/lib/revisions/application/use-cases/restore-revision.use-case.d.ts.map +1 -0
  350. package/dist/lib/revisions/application/use-cases/restore-revision.use-case.js +57 -0
  351. package/dist/lib/revisions/domain/revision-status.d.ts +18 -0
  352. package/dist/lib/revisions/domain/revision-status.d.ts.map +1 -0
  353. package/dist/lib/revisions/domain/revision-status.js +18 -0
  354. package/dist/lib/revisions/domain/revision.d.ts +80 -0
  355. package/dist/lib/revisions/domain/revision.d.ts.map +1 -0
  356. package/dist/lib/revisions/domain/revision.js +84 -0
  357. package/dist/lib/revisions/http/controllers/publish-revision.controller.d.ts +19 -0
  358. package/dist/lib/revisions/http/controllers/publish-revision.controller.d.ts.map +1 -0
  359. package/dist/lib/revisions/http/controllers/publish-revision.controller.js +51 -0
  360. package/dist/lib/revisions/http/controllers/restore-revision.controller.d.ts +17 -0
  361. package/dist/lib/revisions/http/controllers/restore-revision.controller.d.ts.map +1 -0
  362. package/dist/lib/revisions/http/controllers/restore-revision.controller.js +49 -0
  363. package/dist/lib/revisions/http/controllers/revisions.controller.d.ts +20 -0
  364. package/dist/lib/revisions/http/controllers/revisions.controller.d.ts.map +1 -0
  365. package/dist/lib/revisions/http/controllers/revisions.controller.js +76 -0
  366. package/dist/lib/revisions/infrastructure/persistence/drizzle-revision.store.d.ts +33 -0
  367. package/dist/lib/revisions/infrastructure/persistence/drizzle-revision.store.d.ts.map +1 -0
  368. package/dist/lib/revisions/infrastructure/persistence/drizzle-revision.store.js +174 -0
  369. package/dist/lib/revisions/infrastructure/persistence/revision-snapshot.d.ts +17 -0
  370. package/dist/lib/revisions/infrastructure/persistence/revision-snapshot.d.ts.map +1 -0
  371. package/dist/lib/revisions/infrastructure/persistence/revision-snapshot.js +20 -0
  372. package/dist/lib/revisions/infrastructure/persistence/revision-table.d.ts +228 -0
  373. package/dist/lib/revisions/infrastructure/persistence/revision-table.d.ts.map +1 -0
  374. package/dist/lib/revisions/infrastructure/persistence/revision-table.js +57 -0
  375. package/dist/lib/revisions/infrastructure/queries/revision-refs.query.d.ts +22 -0
  376. package/dist/lib/revisions/infrastructure/queries/revision-refs.query.d.ts.map +1 -0
  377. package/dist/lib/revisions/infrastructure/queries/revision-refs.query.js +66 -0
  378. package/dist/lib/revisions/revisions.constants.d.ts +10 -0
  379. package/dist/lib/revisions/revisions.constants.d.ts.map +1 -0
  380. package/dist/lib/revisions/revisions.constants.js +12 -0
  381. package/dist/lib/revisions/types/revision-view.d.ts +74 -0
  382. package/dist/lib/revisions/types/revision-view.d.ts.map +1 -0
  383. package/dist/lib/revisions/types/revision-view.js +2 -0
  384. package/dist/lib/types/content-type.d.ts +131 -0
  385. package/dist/lib/types/content-type.d.ts.map +1 -0
  386. package/dist/lib/types/content-type.js +28 -0
  387. package/dist/lib/types/fields.d.ts +303 -0
  388. package/dist/lib/types/fields.d.ts.map +1 -0
  389. package/dist/lib/types/fields.js +52 -0
  390. package/dist/lib/utils/content-plugin.d.ts +35 -0
  391. package/dist/lib/utils/content-plugin.d.ts.map +1 -0
  392. package/dist/lib/utils/content-plugin.js +35 -0
  393. package/dist/lib/validation/services/entry-validation.service.d.ts +24 -0
  394. package/dist/lib/validation/services/entry-validation.service.d.ts.map +1 -0
  395. package/dist/lib/validation/services/entry-validation.service.js +35 -0
  396. package/package.json +54 -0
@@ -0,0 +1,557 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PublicEntriesQuery = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const common_1 = require("@nestjs/common");
6
+ const drizzle_orm_1 = require("drizzle-orm");
7
+ const database_1 = require("@orthacms/database");
8
+ const utils_server_1 = require("@orthacms/utils-server");
9
+ const entry_extension_1 = require("../../extension/entry-extension");
10
+ const content_type_1 = require("../../types/content-type");
11
+ const fields_1 = require("../../types/fields");
12
+ const relation_link_service_1 = require("../../entries/infrastructure/persistence/relation-link.service");
13
+ const entry_scalar_fields_1 = require("../../entries/infrastructure/queries/entry-scalar-fields");
14
+ const entry_filter_surface_1 = require("../../entries/infrastructure/queries/entry-filter-surface");
15
+ const entry_search_1 = require("../../entries/infrastructure/queries/entry-search");
16
+ const field_selection_1 = require("./field-selection");
17
+ const public_expansion_query_1 = require("./public-expansion.query");
18
+ const entries_constants_1 = require("../../entries/entries.constants");
19
+ const public_list_entries_query_dto_1 = require("../http/dto/public-list-entries-query.dto");
20
+ const public_entry_row_1 = require("./public-entry-row");
21
+ /**
22
+ * The read side of the public content API. Generic over the content type like
23
+ * the admin's `EntriesService` — the physical table and the sortable columns
24
+ * are derived from `type` per request, so one service serves every code-defined
25
+ * collection and single.
26
+ *
27
+ * It **reuses** the admin's query machinery where the semantics are identical —
28
+ * the same ILIKE search predicate and the same query-builder filter engine, so
29
+ * one query language covers both surfaces — but it is deliberately **not** built
30
+ * on `EntriesService` itself. Several of that service's knobs (`?deleted=only`
31
+ * most obviously) select rows this API must never serve, and reaching through it
32
+ * to subtract them would make the visibility rule an argument about what was
33
+ * *not* passed. Instead the public read states its own WHERE, so "what can a
34
+ * token see?" is answerable by reading one method:
35
+ *
36
+ * - the **workspace** the request resolved to, always;
37
+ * - **published only**, on publishable types — a draft is unfinished work and
38
+ * never leaves the admin;
39
+ * - **not soft-deleted**, on paranoid types;
40
+ * - the bound `CONTENT_ENTRY_EXTENSION`'s scope, so a localized type reads the
41
+ * requested locale (and the same rules the admin gets) for free.
42
+ *
43
+ * `?search=` and `?filter=` narrow that set further; neither can widen it, since
44
+ * both are AND-ed onto the predicate above.
45
+ */
46
+ let PublicEntriesQuery = class PublicEntriesQuery {
47
+ db;
48
+ expansion;
49
+ relationLinks;
50
+ extension;
51
+ constructor(db, expansion, relationLinks, extension) {
52
+ this.db = db;
53
+ this.expansion = expansion;
54
+ this.relationLinks = relationLinks;
55
+ this.extension = extension;
56
+ }
57
+ /**
58
+ * One page of a type's publicly readable entries. Count and page share the
59
+ * WHERE and run concurrently, so the request pays the slower of the two.
60
+ */
61
+ async list(type, query, workspaceId, grantedTypes) {
62
+ const page = query.page ?? 1;
63
+ const pageSize = query.pageSize ?? entries_constants_1.DEFAULT_PAGE_SIZE;
64
+ const selected = (0, field_selection_1.parseFieldSelection)(type, query.fields);
65
+ const relationFields = query.relations === public_list_entries_query_dto_1.PREVIEW
66
+ ? this.expansion.parseRelationFields(type, query.relationFields, grantedTypes)
67
+ : [];
68
+ const mediaFields = query.media === public_list_entries_query_dto_1.PREVIEW
69
+ ? this.expansion.parseMediaFields(type, query.mediaFields)
70
+ : [];
71
+ const translations = this.wantsTranslations(type, query);
72
+ const where = (0, drizzle_orm_1.and)(this.readableWhere(type, workspaceId, query.locale, query.status), (0, entry_search_1.buildSearchPredicate)(type, query.search), await this.filterPredicate(type, query.filter, workspaceId, grantedTypes));
73
+ // An expansion reads columns that never appear in `values` — an owning
74
+ // single relation's FK, a media field's ids — so a `?fields=` selection
75
+ // must still carry them or the preview would find nothing to resolve.
76
+ const projection = this.projection(type, selected, [
77
+ ...relationFields,
78
+ ...mediaFields
79
+ ]);
80
+ const rowsQuery = projection
81
+ ? this.db.select(projection).from(type.table)
82
+ : this.db.select().from(type.table);
83
+ const [[{ total }], rows] = await Promise.all([
84
+ this.db.select({ total: (0, drizzle_orm_1.count)() }).from(type.table).where(where),
85
+ rowsQuery
86
+ .where(where)
87
+ .orderBy(...this.orderBy(type, query.sort))
88
+ .limit(pageSize)
89
+ .offset((page - 1) * pageSize)
90
+ ]);
91
+ const items = rows.map((row) => (0, public_entry_row_1.toPublicEntry)(type, row, selected));
92
+ await this.attachExpansions(type, items, rows, workspaceId, { relations: relationFields, media: mediaFields, translations }, {
93
+ relation: query.relationLimit,
94
+ media: query.mediaLimit
95
+ }, selected);
96
+ return { items, total, page, pageSize };
97
+ }
98
+ /**
99
+ * Attach the opt-in relation / media / translation previews to an
100
+ * already-projected page. Every resolver is batched across the whole page,
101
+ * so this adds a bounded number of queries per requested field (and one for
102
+ * translations) — never one per row.
103
+ */
104
+ async attachExpansions(type, items, rows, workspaceId, want, limits, selected) {
105
+ if (!want.relations.length &&
106
+ !want.media.length &&
107
+ !want.translations) {
108
+ return;
109
+ }
110
+ const [relations, media, translations] = await Promise.all([
111
+ this.expansion.relationsForRows(type, want.relations, rows, workspaceId, limits.relation),
112
+ this.expansion.mediaForRows(type, want.media, rows, workspaceId, limits.media),
113
+ want.translations
114
+ ? this.translationsForEntries(type, items, workspaceId, selected)
115
+ : undefined
116
+ ]);
117
+ for (const item of items) {
118
+ // Keys are present-but-empty when the caller asked for a field the
119
+ // entry has no links in, so a consumer can read `relations.author`
120
+ // without testing for the map itself.
121
+ if (want.relations.length) {
122
+ item.relations = relations.get(item.id) ?? {};
123
+ for (const field of want.relations) {
124
+ item.relations[field] ??= { items: [], total: 0 };
125
+ }
126
+ }
127
+ if (want.media.length) {
128
+ item.media = media.get(item.id) ?? {};
129
+ for (const field of want.media) {
130
+ item.media[field] ??= { items: [], total: 0 };
131
+ }
132
+ }
133
+ // Likewise present-but-empty: an entry that is the only published
134
+ // row in its group reports `[]`, not a missing key, so "no other
135
+ // translations" and "you didn't ask" stay distinguishable.
136
+ if (want.translations) {
137
+ item.translations = translations?.get(item.id) ?? [];
138
+ }
139
+ }
140
+ }
141
+ /**
142
+ * Whether this request wants sibling translations — and a **400** when it
143
+ * asks for them on a type that isn't localized.
144
+ *
145
+ * Refusing rather than returning an empty list is the same call
146
+ * `relationFields` makes for a non-relation field: the type has no
147
+ * translation group at all, so `[]` would read as "this entry has no other
148
+ * locales" when the truth is "this content is not localized".
149
+ */
150
+ wantsTranslations(type, query) {
151
+ if (query.translations !== public_list_entries_query_dto_1.PREVIEW) {
152
+ return false;
153
+ }
154
+ this.assertLocalized(type, 'translations');
155
+ return true;
156
+ }
157
+ /** Guard a locale-only feature against a type that has no locale rows. */
158
+ assertLocalized(type, param) {
159
+ if (!type.i18n) {
160
+ throw new common_1.BadRequestException(`${param}: "${type.name}" is not a localized content type.`);
161
+ }
162
+ }
163
+ /**
164
+ * The sibling translations of every entry on a page, keyed by entry id.
165
+ *
166
+ * **One** query for the whole page: the group ids are collected and read
167
+ * with a single `locale_group_id IN (…)`, which the
168
+ * `(locale_group_id, locale)` index serves. No cap is applied — a group
169
+ * holds at most one row per configured locale, so its size is bounded by
170
+ * the host's own config rather than by user data.
171
+ *
172
+ * Deliberately built on {@link liveWhere} and not `readableWhere`: the
173
+ * extension's `listScope` pins the read to a **single** locale, which is
174
+ * exactly what a translation lookup must not do. Everything else about
175
+ * visibility is unchanged — workspace, published-only, not soft-deleted —
176
+ * so a locale that exists only as a draft stays invisible here too.
177
+ */
178
+ async translationsForEntries(type, items, workspaceId, selected) {
179
+ const out = new Map();
180
+ const groupIds = [
181
+ ...new Set(items
182
+ .map((item) => item.localeGroupId)
183
+ .filter((id) => !!id))
184
+ ];
185
+ if (!groupIds.length) {
186
+ return out;
187
+ }
188
+ const table = type.table;
189
+ const projection = this.projection(type, selected);
190
+ const rowsQuery = projection
191
+ ? this.db.select(projection).from(type.table)
192
+ : this.db.select().from(type.table);
193
+ const rows = (await rowsQuery
194
+ .where((0, drizzle_orm_1.and)((0, drizzle_orm_1.inArray)(table['localeGroupId'], groupIds), this.liveWhere(type, workspaceId)))
195
+ .orderBy((0, drizzle_orm_1.asc)(table['locale'])));
196
+ const byGroup = new Map();
197
+ for (const row of rows) {
198
+ const group = row['localeGroupId'];
199
+ const bucket = byGroup.get(group);
200
+ if (bucket)
201
+ bucket.push(row);
202
+ else
203
+ byGroup.set(group, [row]);
204
+ }
205
+ for (const item of items) {
206
+ const siblings = byGroup.get(item.localeGroupId ?? '') ?? [];
207
+ out.set(item.id, siblings
208
+ // The entry itself is the row the caller already holds;
209
+ // repeating it inside its own `translations` would double
210
+ // every payload for nothing.
211
+ .filter((row) => row['id'] !== item.id)
212
+ .map((row) => (0, public_entry_row_1.toPublicEntry)(type, row, selected)));
213
+ }
214
+ return out;
215
+ }
216
+ /**
217
+ * The sibling translations of one entry — the `/translations` sibling route,
218
+ * for a consumer that has the entry and wants its other locales without
219
+ * re-reading it. 404 for an entry that isn't publicly readable, exactly like
220
+ * the entry route; 400 on a type that isn't localized.
221
+ */
222
+ async translationsOf(type, locator, workspaceId, query) {
223
+ this.assertLocalized(type, 'translations');
224
+ const selected = (0, field_selection_1.parseFieldSelection)(type, query.fields);
225
+ const row = await this.readableRow(type, locator, workspaceId, query.locale, query.status);
226
+ const entry = (0, public_entry_row_1.toPublicEntry)(type, row, selected);
227
+ const byEntry = await this.translationsForEntries(type, [entry], workspaceId, selected);
228
+ return byEntry.get(entry.id) ?? [];
229
+ }
230
+ /**
231
+ * The SELECT list for a `?fields=` selection, or `undefined` to select the
232
+ * whole row. Narrowing this is the point of a sparse fieldset: without it
233
+ * the query still reads (and Postgres still detoasts) a richtext column the
234
+ * caller asked to leave out, and only the serializer saves any bytes.
235
+ *
236
+ * The envelope is always included — `toPublicEntry` reads those columns
237
+ * unconditionally, and they are what make an entry addressable. The WHERE
238
+ * and ORDER BY may reference columns outside this list; SQL allows that, so
239
+ * filtering and sorting stay unrestricted by the selection.
240
+ */
241
+ projection(type, selected, alsoNeeded = []) {
242
+ if (!selected) {
243
+ return undefined;
244
+ }
245
+ const table = type.table;
246
+ const columns = {
247
+ id: table['id'],
248
+ createdAt: table['createdAt'],
249
+ updatedAt: table['updatedAt']
250
+ };
251
+ if (type.publishable) {
252
+ columns['status'] = table['status'];
253
+ columns['publishedAt'] = table['publishedAt'];
254
+ }
255
+ if (type.i18n) {
256
+ columns['locale'] = table['locale'];
257
+ columns['localeGroupId'] = table['localeGroupId'];
258
+ }
259
+ // `Object.hasOwn` on both loops. `selected` is already own-property
260
+ // validated against the type's field map (`parseFieldSelection`), and
261
+ // `alsoNeeded` carries internal constants — but the failure mode here
262
+ // is putting a drizzle table's INHERITED member (a class function) into
263
+ // a `.select()` list, which is a 500, and a reader should not have to
264
+ // work out which of the two loops is the safe one.
265
+ for (const name of selected) {
266
+ if (Object.hasOwn(table, name))
267
+ columns[name] = table[name];
268
+ }
269
+ for (const name of alsoNeeded) {
270
+ // Join-backed relations own no column on this table; skip them
271
+ // rather than putting `undefined` in the SELECT list.
272
+ if (Object.hasOwn(table, name) && table[name]) {
273
+ columns[name] = table[name];
274
+ }
275
+ }
276
+ return columns;
277
+ }
278
+ /**
279
+ * One publicly readable entry, addressed by **either** spelling of
280
+ * {@link EntryLocator}.
281
+ *
282
+ * By id: a draft, a soft-deleted row, an entry in another workspace, and an
283
+ * id that never existed are all the same 404 — an unpublished entry must not
284
+ * be distinguishable from a missing one.
285
+ *
286
+ * By translation group, the read a localized front-end actually wants: a
287
+ * consumer that knows an article by its group id ("this story") renders it
288
+ * in whatever locale the visitor is in by varying `?locale=` alone, instead
289
+ * of keeping a per-locale id map — the group id is the stable identity of
290
+ * the story across languages, while each locale's `id` is not. A group with
291
+ * no published row in the requested locale is the same 404 as an unknown
292
+ * group: the caller asked for content that isn't live in that language, and
293
+ * which of the two it is isn't theirs to learn.
294
+ */
295
+ async getOne(type, locator, workspaceId, query, grantedTypes) {
296
+ const selected = (0, field_selection_1.parseFieldSelection)(type, query.fields);
297
+ const relationFields = query.relations === public_list_entries_query_dto_1.PREVIEW
298
+ ? this.expansion.parseRelationFields(type, query.relationFields, grantedTypes)
299
+ : [];
300
+ const mediaFields = query.media === public_list_entries_query_dto_1.PREVIEW
301
+ ? this.expansion.parseMediaFields(type, query.mediaFields)
302
+ : [];
303
+ const projection = this.projection(type, selected, [
304
+ ...relationFields,
305
+ ...mediaFields
306
+ ]);
307
+ const rowQuery = projection
308
+ ? this.db.select(projection).from(type.table)
309
+ : this.db.select().from(type.table);
310
+ const [row] = await rowQuery
311
+ .where(this.entryWhere(type, locator, workspaceId, query.locale, query.status))
312
+ .limit(1);
313
+ if (!row) {
314
+ throw new common_1.NotFoundException(this.notFoundFor(type, locator));
315
+ }
316
+ const entry = (0, public_entry_row_1.toPublicEntry)(type, row, selected);
317
+ // One row is still a "page" as far as the batched resolvers care, so
318
+ // the single read reuses exactly the list's expansion path.
319
+ await this.attachExpansions(type, [entry], [row], workspaceId, {
320
+ relations: relationFields,
321
+ media: mediaFields,
322
+ translations: this.wantsTranslations(type, query)
323
+ }, { relation: query.relationLimit, media: query.mediaLimit }, selected);
324
+ return entry;
325
+ }
326
+ /**
327
+ * The row a locator names, for a **write** — its id addresses the write, and
328
+ * its stored values are what a partial update merges onto.
329
+ *
330
+ * Resolved with `status: 'any'`, because writes address rows in whatever
331
+ * state they are in — the overwhelmingly common case is editing a draft,
332
+ * which the published-only default would hide. Everything else about the
333
+ * lookup is the read rule verbatim (workspace, not soft-deleted, the
334
+ * requested locale), so a token can never reach across a workspace with a
335
+ * write that it could not reach with a read.
336
+ *
337
+ * Lives here rather than in the write service so there is exactly one
338
+ * implementation of "which row does this locator mean?" — a second one that
339
+ * forgot the workspace clause is the failure this avoids.
340
+ */
341
+ async resolveWritableRow(type, locator, workspaceId, locale) {
342
+ // An id locator is still a real lookup, not a pass-through: an id in
343
+ // another workspace or a soft-deleted one has to 404 here rather than
344
+ // reach the writer, which scopes by workspace but would report the
345
+ // mismatch as its own kind of failure.
346
+ return this.readableRow(type, locator, workspaceId, locale, 'any');
347
+ }
348
+ /**
349
+ * The full predicate for a **single-entry** read: the locator, the
350
+ * locale-independent visibility rule, and — for a group locator only — the
351
+ * extension's locale scope.
352
+ *
353
+ * That asymmetry is the point. A **group** id names a whole translation
354
+ * group, so the locale is what picks the row out of it; `LIMIT 1` never has
355
+ * to break a tie. An **entry id** already names exactly one row, so AND-ing
356
+ * the locale scope onto it can do nothing except turn a valid id into a 404
357
+ * whenever that row is not in the default locale. That used to be the
358
+ * behaviour, documented as a sharp edge; the write API made it untenable,
359
+ * since updating a German article by its own id would have needed
360
+ * `?locale=de` appended to a request that already named the row uniquely.
361
+ *
362
+ * The narrowing is safe because `liveWhere` still carries the whole of the
363
+ * *visibility* rule — workspace, publish state, soft delete. The extension's
364
+ * `listScope` is, per its own name and its one implementation, about
365
+ * choosing rows out of a *set*; a request that names one row has already
366
+ * chosen.
367
+ *
368
+ * What the asymmetry must **not** cost is validation. Dropping the scope
369
+ * from an id read also dropped the only thing that looks at `?locale=`, so
370
+ * `?locale=zz` went from a 400 to being silently ignored — a typo that
371
+ * quietly returns another language's content, on a contract that says
372
+ * every route taking a locale rejects an unknown one. Hence the discarded
373
+ * call below: `listScope` validates as well as narrows (see the port's
374
+ * docs), and only the narrowing is unwanted here.
375
+ */
376
+ entryWhere(type, locator, workspaceId, locale, visibility) {
377
+ const table = type.table;
378
+ if ('id' in locator) {
379
+ // Called for its validation, not its predicate — an unknown slug
380
+ // throws here. Do not "clean up" the unused return value.
381
+ this.extension?.listScope(type, workspaceId, { locale });
382
+ return (0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(table['id'], locator.id), this.liveWhere(type, workspaceId, visibility));
383
+ }
384
+ // A group locator is only meaningful on a localized type, and is a 400
385
+ // elsewhere — the caller addressed the entry by something the type does
386
+ // not have, which is a different mistake from naming a group that does
387
+ // not exist (a 404).
388
+ this.assertLocalized(type, 'localeGroupId');
389
+ return (0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(table['localeGroupId'], locator.localeGroupId), this.liveWhere(type, workspaceId, visibility), this.extension?.listScope(type, workspaceId, { locale }));
390
+ }
391
+ /** The 404 message for a locator that matched nothing readable. */
392
+ notFoundFor(type, locator) {
393
+ return 'id' in locator
394
+ ? `No published "${type.name}" entry with id "${locator.id}".`
395
+ : `No published "${type.name}" entry in translation group "${locator.localeGroupId}" for the requested locale.`;
396
+ }
397
+ /**
398
+ * One page of a single relation field's links — the only relation read that
399
+ * `?relations=preview` cannot express, and therefore the only one with a
400
+ * route of its own.
401
+ */
402
+ async relationField(type, locator, field, page, pageSize, workspaceId, grantedTypes, locale, visibility) {
403
+ const spec = type.fields[field];
404
+ if (!spec ||
405
+ spec.type !== fields_1.CONTENT_FIELD_TYPE.Relation ||
406
+ !spec.relation) {
407
+ throw new common_1.BadRequestException(`"${field}" is not a relation field on "${type.name}".`);
408
+ }
409
+ if (!grantedTypes.has(spec.relation.to().name)) {
410
+ throw new common_1.BadRequestException(`"${field}" cannot be expanded — this workspace has no access to "${spec.relation.to().name}".`);
411
+ }
412
+ const row = await this.readableRow(type, locator, workspaceId, locale, visibility);
413
+ const view = await this.relationLinks.readField(type, row, field, spec, page, pageSize, workspaceId, { publishedOnly: true });
414
+ const hydrated = await this.expansion.hydrateRelationViews(type, { [field]: view }, workspaceId);
415
+ return hydrated[field];
416
+ }
417
+ /** Every media field of one entry, resolved to asset metadata + URLs. */
418
+ async mediaOf(type, locator, workspaceId, locale, limit = public_list_entries_query_dto_1.DEFAULT_EXPANSION_LIMIT, visibility) {
419
+ const row = await this.readableRow(type, locator, workspaceId, locale, visibility);
420
+ const fields = Object.entries(type.fields)
421
+ .filter(([, spec]) => spec.type === fields_1.CONTENT_FIELD_TYPE.Media)
422
+ .map(([name]) => name);
423
+ const media = await this.expansion.mediaForRows(type, fields, [row], workspaceId, limit);
424
+ // Keyed by the row's OWN id, not by anything in the locator — a group
425
+ // locator resolved to a row whose id the caller may never have seen.
426
+ const out = media.get(row['id']) ?? {};
427
+ for (const field of fields)
428
+ out[field] ??= { items: [], total: 0 };
429
+ return out;
430
+ }
431
+ /**
432
+ * The full row of one publicly readable entry, or a 404 — the shared
433
+ * pre-step of the sibling routes, so `/relations/:field`, `/media`, and
434
+ * `/translations` are exactly as invisible for a draft or foreign entry as
435
+ * the entry read itself, in either addressing form.
436
+ */
437
+ async readableRow(type, locator, workspaceId, locale, visibility) {
438
+ const [row] = await this.db
439
+ .select()
440
+ .from(type.table)
441
+ .where(this.entryWhere(type, locator, workspaceId, locale, visibility))
442
+ .limit(1);
443
+ if (!row) {
444
+ throw new common_1.NotFoundException(this.notFoundFor(type, locator));
445
+ }
446
+ return row;
447
+ }
448
+ /**
449
+ * Translate one `?filter=` payload — the same query-builder tree the admin
450
+ * records list takes — against a surface derived from the type.
451
+ *
452
+ * Two departures from the admin's version, both deliberate:
453
+ *
454
+ * - **Grant-pruned.** `grantedTypes` is passed, so a relation whose target
455
+ * the workspace was never granted is omitted from the surface and a rule
456
+ * naming it 400s. Without it, `author.name eq "Ada"` would let a token
457
+ * infer relation data by watching which entries come back — data this API
458
+ * deliberately does not return. The admin's list leaves this unset on
459
+ * purpose (there it is a SQL whitelist, not a visibility boundary); here
460
+ * it is exactly a visibility boundary.
461
+ * - **No `status`.** The read already forces `status = published`, so a
462
+ * `status` rule could only be a no-op or match nothing. Dropping it from
463
+ * the schema turns a confusingly empty page into a clear 400.
464
+ *
465
+ * Built lazily — the surface walks the type's whole relation graph to the
466
+ * hop budget, so an unfiltered list (the common case) never pays for it.
467
+ * A malformed filter throws `FilterException`, which is a 400.
468
+ */
469
+ async filterPredicate(type, filter, workspaceId, grantedTypes) {
470
+ if (!filter) {
471
+ return undefined;
472
+ }
473
+ const { schema } = (0, entry_filter_surface_1.buildEntryFilterSurface)(type, {
474
+ workspaceId,
475
+ grantedTypes
476
+ });
477
+ if (schema.fields) {
478
+ delete schema.fields['status'];
479
+ }
480
+ const tree = (0, utils_server_1.parseFilterTree)(filter, schema);
481
+ return (0, utils_server_1.applyFilterTree)(tree, schema, type.table, this.db);
482
+ }
483
+ /**
484
+ * The predicate every public read shares — the whole visibility rule in one
485
+ * place. `and(undefined, …)` collapses the clauses a type doesn't have, so
486
+ * a plain non-publishable, non-paranoid type reduces to the workspace scope.
487
+ */
488
+ readableWhere(type, workspaceId, locale, visibility = 'published') {
489
+ return (0, drizzle_orm_1.and)(this.liveWhere(type, workspaceId, visibility),
490
+ // No-op for types the extension doesn't apply to; for an i18n type
491
+ // it scopes to the requested locale (rejecting an unknown one).
492
+ this.extension?.listScope(type, workspaceId, { locale }));
493
+ }
494
+ /**
495
+ * The locale-**independent** half of {@link readableWhere}: workspace,
496
+ * published-only, not soft-deleted. Split out for the one read that must
497
+ * span locales — a translation lookup, whose whole job is to find the rows
498
+ * `listScope` would have filtered away — so that read narrows the entry
499
+ * visibility rule in exactly one respect and inherits the rest verbatim.
500
+ */
501
+ liveWhere(type, workspaceId, visibility = 'published') {
502
+ const table = type.table;
503
+ return (0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(table['workspaceId'], workspaceId), this.statusWhere(type, visibility), type.paranoid ? (0, drizzle_orm_1.isNull)(table['deletedAt']) : undefined);
504
+ }
505
+ /**
506
+ * The publish-state clause. `published` — the default and everything a
507
+ * read-only token can ask for — is the API's headline rule; the other two
508
+ * exist so a write-scoped token can read back what it just created.
509
+ *
510
+ * A non-publishable type has no `status` column and every row is simply
511
+ * live, so this is a no-op there rather than an error: the caller asked for
512
+ * a distinction the type does not make.
513
+ */
514
+ statusWhere(type, visibility) {
515
+ if (!type.publishable || visibility === 'any') {
516
+ return undefined;
517
+ }
518
+ const table = type.table;
519
+ return (0, drizzle_orm_1.eq)(table['status'], visibility === 'draft' ? content_type_1.ENTRY_STATUS.Draft : content_type_1.ENTRY_STATUS.Published);
520
+ }
521
+ /**
522
+ * Translate `?sort=` (`col` ascending, `-col` descending) into an ORDER BY,
523
+ * always with an `id` tiebreaker so paging is stable across requests.
524
+ * Sortable = the envelope timestamps, `publishedAt` (publishable types),
525
+ * `locale` (i18n types), and the type's **scalar** fields — the same
526
+ * whitelist rule the admin list uses, so a sort key never reaches
527
+ * `asc(undefined)`. Unknown or excluded keys fall back to newest-updated.
528
+ */
529
+ orderBy(type, sort) {
530
+ const table = type.table;
531
+ const sortable = new Set([
532
+ 'createdAt',
533
+ 'updatedAt',
534
+ ...(type.publishable ? ['publishedAt'] : []),
535
+ ...(type.i18n ? ['locale'] : []),
536
+ ...Object.entries(type.fields)
537
+ .filter(([, spec]) => (0, entry_scalar_fields_1.isScalarField)(spec))
538
+ .map(([name]) => name)
539
+ ]);
540
+ const isDesc = !!sort && sort.startsWith('-');
541
+ const columnId = sort ? (isDesc ? sort.slice(1) : sort) : '';
542
+ const column = sortable.has(columnId) ? table[columnId] : undefined;
543
+ if (column) {
544
+ return [isDesc ? (0, drizzle_orm_1.desc)(column) : (0, drizzle_orm_1.asc)(column), (0, drizzle_orm_1.asc)(table['id'])];
545
+ }
546
+ return [(0, drizzle_orm_1.desc)(table['updatedAt']), (0, drizzle_orm_1.asc)(table['id'])];
547
+ }
548
+ };
549
+ exports.PublicEntriesQuery = PublicEntriesQuery;
550
+ exports.PublicEntriesQuery = PublicEntriesQuery = tslib_1.__decorate([
551
+ (0, common_1.Injectable)(),
552
+ tslib_1.__param(0, (0, database_1.InjectDatabase)()),
553
+ tslib_1.__param(3, (0, common_1.Optional)()),
554
+ tslib_1.__param(3, (0, common_1.Inject)(entry_extension_1.CONTENT_ENTRY_EXTENSION)),
555
+ tslib_1.__metadata("design:paramtypes", [Object, public_expansion_query_1.PublicExpansionQuery,
556
+ relation_link_service_1.RelationLinkService, Object])
557
+ ], PublicEntriesQuery);
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The row → {@link PublicEntry} projection. Pure shape translation: no DB
3
+ * access, no NestJS. Separate from the admin's `toRecord` because the public
4
+ * wire contract is its own thing (see `types/public-entry.ts`).
5
+ */
6
+ import type { AnyContentType } from '../../types/content-type';
7
+ import type { PublicEntry } from '../types/public-entry';
8
+ /** A generated content table row seen as a bag of values by property name. */
9
+ type Row = Record<string, unknown>;
10
+ /**
11
+ * Whether a field carries the entry's **own** value — a scalar, a `json` or
12
+ * `multiselect` bag, anything stored in the row and meaningful on its own.
13
+ * See {@link REFERENCE_FIELD_TYPES} for what this excludes and why.
14
+ */
15
+ export declare function isPureValueField(spec: AnyContentType['fields'][string]): boolean;
16
+ /**
17
+ * Projects a raw DB row onto the public wire shape: the envelope (`id`,
18
+ * timestamps, plus `publishedAt` / locale columns where the type has them) and
19
+ * a `values` bag of the entry's **own** field values — every field except the
20
+ * reference kinds (see {@link REFERENCE_FIELD_TYPES}). An unset field reads
21
+ * back as `null`, never as a missing key, so the key set is stable across
22
+ * entries of a type.
23
+ *
24
+ * `selected` narrows `values` to a `?fields=` sparse fieldset. It must be
25
+ * passed whenever the row was read with a narrowed projection: without it the
26
+ * loop would emit `title: null` for a column the query never selected, which
27
+ * reads as "this entry has no title" rather than "you didn't ask for it".
28
+ */
29
+ export declare function toPublicEntry(type: AnyContentType, row: Row, selected?: ReadonlySet<string>): PublicEntry;
30
+ export {};
31
+ //# sourceMappingURL=public-entry-row.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-entry-row.d.ts","sourceRoot":"","sources":["../../../../src/lib/public-api/infrastructure/public-entry-row.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEzD,8EAA8E;AAC9E,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAsBnC;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC5B,IAAI,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GACvC,OAAO,CAET;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CACzB,IAAI,EAAE,cAAc,EACpB,GAAG,EAAE,GAAG,EACR,QAAQ,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAC/B,WAAW,CAwBb"}
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ /**
3
+ * The row → {@link PublicEntry} projection. Pure shape translation: no DB
4
+ * access, no NestJS. Separate from the admin's `toRecord` because the public
5
+ * wire contract is its own thing (see `types/public-entry.ts`).
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.isPureValueField = isPureValueField;
9
+ exports.toPublicEntry = toPublicEntry;
10
+ const fields_1 = require("../../types/fields");
11
+ /**
12
+ * Field types the public read omits entirely — every kind whose value is a
13
+ * **reference to something else** rather than the entry's own data.
14
+ *
15
+ * `relation` covers all four cardinalities: an owning many-relation and an
16
+ * inverse have no column to read anyway, and an owning single one does (a
17
+ * `<field>_id` FK) but is dropped too, so the rule is one line rather than a
18
+ * per-cardinality carve-out. `media` is an asset id (or a list of them) in the
19
+ * media plugin's own store.
20
+ *
21
+ * The public API resolves neither today, so emitting a bare uuid would hand a
22
+ * consumer an identifier with nothing to do with it. **This is deliberately
23
+ * provisional** — when relation and media reads land, this set shrinks and the
24
+ * omitted fields start appearing in `values`, which is an additive change.
25
+ */
26
+ const REFERENCE_FIELD_TYPES = new Set([
27
+ fields_1.CONTENT_FIELD_TYPE.Relation,
28
+ fields_1.CONTENT_FIELD_TYPE.Media
29
+ ]);
30
+ /**
31
+ * Whether a field carries the entry's **own** value — a scalar, a `json` or
32
+ * `multiselect` bag, anything stored in the row and meaningful on its own.
33
+ * See {@link REFERENCE_FIELD_TYPES} for what this excludes and why.
34
+ */
35
+ function isPureValueField(spec) {
36
+ return !REFERENCE_FIELD_TYPES.has(spec.type);
37
+ }
38
+ /**
39
+ * Projects a raw DB row onto the public wire shape: the envelope (`id`,
40
+ * timestamps, plus `publishedAt` / locale columns where the type has them) and
41
+ * a `values` bag of the entry's **own** field values — every field except the
42
+ * reference kinds (see {@link REFERENCE_FIELD_TYPES}). An unset field reads
43
+ * back as `null`, never as a missing key, so the key set is stable across
44
+ * entries of a type.
45
+ *
46
+ * `selected` narrows `values` to a `?fields=` sparse fieldset. It must be
47
+ * passed whenever the row was read with a narrowed projection: without it the
48
+ * loop would emit `title: null` for a column the query never selected, which
49
+ * reads as "this entry has no title" rather than "you didn't ask for it".
50
+ */
51
+ function toPublicEntry(type, row, selected) {
52
+ const values = {};
53
+ for (const [name, spec] of Object.entries(type.fields)) {
54
+ if (!isPureValueField(spec))
55
+ continue;
56
+ if (selected && !selected.has(name))
57
+ continue;
58
+ values[name] = row[name] ?? null;
59
+ }
60
+ const entry = {
61
+ id: row['id'],
62
+ createdAt: row['createdAt'].toISOString(),
63
+ updatedAt: row['updatedAt'].toISOString(),
64
+ values
65
+ };
66
+ if (type.publishable) {
67
+ entry.status = row['status'];
68
+ const publishedAt = row['publishedAt'];
69
+ entry.publishedAt = publishedAt ? publishedAt.toISOString() : null;
70
+ }
71
+ if (type.i18n) {
72
+ entry.locale = row['locale'];
73
+ entry.localeGroupId = row['localeGroupId'];
74
+ }
75
+ return entry;
76
+ }