@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.
- package/LICENSE +21 -0
- package/README.md +7 -0
- package/dist/define.d.ts +17 -0
- package/dist/define.d.ts.map +1 -0
- package/dist/define.js +28 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +109 -0
- package/dist/lib/collection/define.d.ts +35 -0
- package/dist/lib/collection/define.d.ts.map +1 -0
- package/dist/lib/collection/define.js +217 -0
- package/dist/lib/collection/table-builder.d.ts +28 -0
- package/dist/lib/collection/table-builder.d.ts.map +1 -0
- package/dist/lib/collection/table-builder.js +306 -0
- package/dist/lib/content-types/controllers/get-content-schema.controller.d.ts +24 -0
- package/dist/lib/content-types/controllers/get-content-schema.controller.d.ts.map +1 -0
- package/dist/lib/content-types/controllers/get-content-schema.controller.js +58 -0
- package/dist/lib/content-types/controllers/get-filter-fields.controller.d.ts +35 -0
- package/dist/lib/content-types/controllers/get-filter-fields.controller.d.ts.map +1 -0
- package/dist/lib/content-types/controllers/get-filter-fields.controller.js +74 -0
- package/dist/lib/content-types/controllers/list-content-schema.controller.d.ts +15 -0
- package/dist/lib/content-types/controllers/list-content-schema.controller.d.ts.map +1 -0
- package/dist/lib/content-types/controllers/list-content-schema.controller.js +38 -0
- package/dist/lib/content-types/queries/workspace-grants.query.d.ts +25 -0
- package/dist/lib/content-types/queries/workspace-grants.query.d.ts.map +1 -0
- package/dist/lib/content-types/queries/workspace-grants.query.js +45 -0
- package/dist/lib/content.module.d.ts +21 -0
- package/dist/lib/content.module.d.ts.map +1 -0
- package/dist/lib/content.module.js +264 -0
- package/dist/lib/content.tokens.d.ts +5 -0
- package/dist/lib/content.tokens.d.ts.map +1 -0
- package/dist/lib/content.tokens.js +9 -0
- package/dist/lib/copilot/content-tool.provider.d.ts +64 -0
- package/dist/lib/copilot/content-tool.provider.d.ts.map +1 -0
- package/dist/lib/copilot/content-tool.provider.js +309 -0
- package/dist/lib/copilot/diff-snapshots.d.ts +42 -0
- package/dist/lib/copilot/diff-snapshots.d.ts.map +1 -0
- package/dist/lib/copilot/diff-snapshots.js +77 -0
- package/dist/lib/copilot/entry-proposal.applier.d.ts +89 -0
- package/dist/lib/copilot/entry-proposal.applier.d.ts.map +1 -0
- package/dist/lib/copilot/entry-proposal.applier.js +245 -0
- package/dist/lib/copilot/entry-proposal.provider.d.ts +157 -0
- package/dist/lib/copilot/entry-proposal.provider.d.ts.map +1 -0
- package/dist/lib/copilot/entry-proposal.provider.js +619 -0
- package/dist/lib/copilot/filter-schema.d.ts +61 -0
- package/dist/lib/copilot/filter-schema.d.ts.map +1 -0
- package/dist/lib/copilot/filter-schema.js +139 -0
- package/dist/lib/copilot/project-entry.d.ts +23 -0
- package/dist/lib/copilot/project-entry.d.ts.map +1 -0
- package/dist/lib/copilot/project-entry.js +36 -0
- package/dist/lib/copilot/proposal-kinds.d.ts +30 -0
- package/dist/lib/copilot/proposal-kinds.d.ts.map +1 -0
- package/dist/lib/copilot/proposal-kinds.js +32 -0
- package/dist/lib/copilot/revision-tool.provider.d.ts +56 -0
- package/dist/lib/copilot/revision-tool.provider.d.ts.map +1 -0
- package/dist/lib/copilot/revision-tool.provider.js +215 -0
- package/dist/lib/docs/content-schemas.d.ts +34 -0
- package/dist/lib/docs/content-schemas.d.ts.map +1 -0
- package/dist/lib/docs/content-schemas.js +617 -0
- package/dist/lib/docs/describe-content-api.d.ts +22 -0
- package/dist/lib/docs/describe-content-api.d.ts.map +1 -0
- package/dist/lib/docs/describe-content-api.js +173 -0
- package/dist/lib/docs/field-schema.d.ts +28 -0
- package/dist/lib/docs/field-schema.d.ts.map +1 -0
- package/dist/lib/docs/field-schema.js +214 -0
- package/dist/lib/entries/application/use-cases/bulk-publish-entries.use-case.d.ts +23 -0
- package/dist/lib/entries/application/use-cases/bulk-publish-entries.use-case.d.ts.map +1 -0
- package/dist/lib/entries/application/use-cases/bulk-publish-entries.use-case.js +77 -0
- package/dist/lib/entries/application/use-cases/bulk-unpublish-entries.use-case.d.ts +23 -0
- package/dist/lib/entries/application/use-cases/bulk-unpublish-entries.use-case.d.ts.map +1 -0
- package/dist/lib/entries/application/use-cases/bulk-unpublish-entries.use-case.js +66 -0
- package/dist/lib/entries/application/use-cases/publish-entry.use-case.d.ts +43 -0
- package/dist/lib/entries/application/use-cases/publish-entry.use-case.d.ts.map +1 -0
- package/dist/lib/entries/application/use-cases/publish-entry.use-case.js +106 -0
- package/dist/lib/entries/application/use-cases/unpublish-entry.use-case.d.ts +30 -0
- package/dist/lib/entries/application/use-cases/unpublish-entry.use-case.d.ts.map +1 -0
- package/dist/lib/entries/application/use-cases/unpublish-entry.use-case.js +70 -0
- package/dist/lib/entries/controllers/bulk-entries.controller.d.ts +26 -0
- package/dist/lib/entries/controllers/bulk-entries.controller.d.ts.map +1 -0
- package/dist/lib/entries/controllers/create-entry.controller.d.ts +20 -0
- package/dist/lib/entries/controllers/create-entry.controller.d.ts.map +1 -0
- package/dist/lib/entries/controllers/delete-entry.controller.d.ts +24 -0
- package/dist/lib/entries/controllers/delete-entry.controller.d.ts.map +1 -0
- package/dist/lib/entries/controllers/get-entry.controller.d.ts +34 -0
- package/dist/lib/entries/controllers/get-entry.controller.d.ts.map +1 -0
- package/dist/lib/entries/controllers/list-entries.controller.d.ts +20 -0
- package/dist/lib/entries/controllers/list-entries.controller.d.ts.map +1 -0
- package/dist/lib/entries/controllers/publish-entry.controller.d.ts +19 -0
- package/dist/lib/entries/controllers/publish-entry.controller.d.ts.map +1 -0
- package/dist/lib/entries/controllers/resolve-type.d.ts +9 -0
- package/dist/lib/entries/controllers/resolve-type.d.ts.map +1 -0
- package/dist/lib/entries/controllers/update-entry.controller.d.ts +18 -0
- package/dist/lib/entries/controllers/update-entry.controller.d.ts.map +1 -0
- package/dist/lib/entries/domain/entry-publish-blocked.error.d.ts +13 -0
- package/dist/lib/entries/domain/entry-publish-blocked.error.d.ts.map +1 -0
- package/dist/lib/entries/domain/entry-publish-blocked.error.js +19 -0
- package/dist/lib/entries/domain/entry.d.ts +71 -0
- package/dist/lib/entries/domain/entry.d.ts.map +1 -0
- package/dist/lib/entries/domain/entry.js +90 -0
- package/dist/lib/entries/domain/events/entry-events.d.ts +67 -0
- package/dist/lib/entries/domain/events/entry-events.d.ts.map +1 -0
- package/dist/lib/entries/domain/events/entry-events.js +100 -0
- package/dist/lib/entries/dto/bulk-ids.dto.d.ts +11 -0
- package/dist/lib/entries/dto/bulk-ids.dto.d.ts.map +1 -0
- package/dist/lib/entries/dto/list-entries-query.dto.d.ts +46 -0
- package/dist/lib/entries/dto/list-entries-query.dto.d.ts.map +1 -0
- package/dist/lib/entries/dto/relation-delta-map.validator.d.ts +19 -0
- package/dist/lib/entries/dto/relation-delta-map.validator.d.ts.map +1 -0
- package/dist/lib/entries/dto/save-entry.dto.d.ts +46 -0
- package/dist/lib/entries/dto/save-entry.dto.d.ts.map +1 -0
- package/dist/lib/entries/entries.constants.d.ts +19 -0
- package/dist/lib/entries/entries.constants.d.ts.map +1 -0
- package/dist/lib/entries/entries.constants.js +21 -0
- package/dist/lib/entries/http/controllers/bulk-entries.controller.d.ts +33 -0
- package/dist/lib/entries/http/controllers/bulk-entries.controller.d.ts.map +1 -0
- package/dist/lib/entries/http/controllers/bulk-entries.controller.js +143 -0
- package/dist/lib/entries/http/controllers/create-entry.controller.d.ts +21 -0
- package/dist/lib/entries/http/controllers/create-entry.controller.d.ts.map +1 -0
- package/dist/lib/entries/http/controllers/create-entry.controller.js +52 -0
- package/dist/lib/entries/http/controllers/delete-entry.controller.d.ts +25 -0
- package/dist/lib/entries/http/controllers/delete-entry.controller.d.ts.map +1 -0
- package/dist/lib/entries/http/controllers/delete-entry.controller.js +84 -0
- package/dist/lib/entries/http/controllers/get-entry.controller.d.ts +44 -0
- package/dist/lib/entries/http/controllers/get-entry.controller.d.ts.map +1 -0
- package/dist/lib/entries/http/controllers/get-entry.controller.js +124 -0
- package/dist/lib/entries/http/controllers/list-entries.controller.d.ts +20 -0
- package/dist/lib/entries/http/controllers/list-entries.controller.d.ts.map +1 -0
- package/dist/lib/entries/http/controllers/list-entries.controller.js +52 -0
- package/dist/lib/entries/http/controllers/publish-entry.controller.d.ts +22 -0
- package/dist/lib/entries/http/controllers/publish-entry.controller.d.ts.map +1 -0
- package/dist/lib/entries/http/controllers/publish-entry.controller.js +68 -0
- package/dist/lib/entries/http/controllers/resolve-type.d.ts +9 -0
- package/dist/lib/entries/http/controllers/resolve-type.d.ts.map +1 -0
- package/dist/lib/entries/http/controllers/resolve-type.js +16 -0
- package/dist/lib/entries/http/controllers/to-actor.d.ts +13 -0
- package/dist/lib/entries/http/controllers/to-actor.d.ts.map +1 -0
- package/dist/lib/entries/http/controllers/to-actor.js +15 -0
- package/dist/lib/entries/http/controllers/update-entry.controller.d.ts +19 -0
- package/dist/lib/entries/http/controllers/update-entry.controller.d.ts.map +1 -0
- package/dist/lib/entries/http/controllers/update-entry.controller.js +51 -0
- package/dist/lib/entries/http/dto/bulk-ids.dto.d.ts +11 -0
- package/dist/lib/entries/http/dto/bulk-ids.dto.d.ts.map +1 -0
- package/dist/lib/entries/http/dto/bulk-ids.dto.js +33 -0
- package/dist/lib/entries/http/dto/list-entries-query.dto.d.ts +66 -0
- package/dist/lib/entries/http/dto/list-entries-query.dto.d.ts.map +1 -0
- package/dist/lib/entries/http/dto/list-entries-query.dto.js +192 -0
- package/dist/lib/entries/http/dto/relation-delta-map.validator.d.ts +30 -0
- package/dist/lib/entries/http/dto/relation-delta-map.validator.d.ts.map +1 -0
- package/dist/lib/entries/http/dto/relation-delta-map.validator.js +102 -0
- package/dist/lib/entries/http/dto/save-entry.dto.d.ts +48 -0
- package/dist/lib/entries/http/dto/save-entry.dto.d.ts.map +1 -0
- package/dist/lib/entries/http/dto/save-entry.dto.js +120 -0
- package/dist/lib/entries/http/guards/content-grant.guard.d.ts +37 -0
- package/dist/lib/entries/http/guards/content-grant.guard.d.ts.map +1 -0
- package/dist/lib/entries/http/guards/content-grant.guard.js +67 -0
- package/dist/lib/entries/infrastructure/persistence/bulk-publish-verdicts.d.ts +22 -0
- package/dist/lib/entries/infrastructure/persistence/bulk-publish-verdicts.d.ts.map +1 -0
- package/dist/lib/entries/infrastructure/persistence/bulk-publish-verdicts.js +81 -0
- package/dist/lib/entries/infrastructure/persistence/entry-counter.service.d.ts +42 -0
- package/dist/lib/entries/infrastructure/persistence/entry-counter.service.d.ts.map +1 -0
- package/dist/lib/entries/infrastructure/persistence/entry-counter.service.js +73 -0
- package/dist/lib/entries/infrastructure/persistence/entry-row.d.ts +56 -0
- package/dist/lib/entries/infrastructure/persistence/entry-row.d.ts.map +1 -0
- package/dist/lib/entries/infrastructure/persistence/entry-row.js +185 -0
- package/dist/lib/entries/infrastructure/persistence/entry-writer.service.d.ts +392 -0
- package/dist/lib/entries/infrastructure/persistence/entry-writer.service.d.ts.map +1 -0
- package/dist/lib/entries/infrastructure/persistence/entry-writer.service.js +1223 -0
- package/dist/lib/entries/infrastructure/persistence/field-selection.d.ts +24 -0
- package/dist/lib/entries/infrastructure/persistence/field-selection.d.ts.map +1 -0
- package/dist/lib/entries/infrastructure/persistence/media-accept.d.ts +23 -0
- package/dist/lib/entries/infrastructure/persistence/media-accept.d.ts.map +1 -0
- package/dist/lib/entries/infrastructure/persistence/media-accept.js +55 -0
- package/dist/lib/entries/infrastructure/persistence/relation-link.service.d.ts +327 -0
- package/dist/lib/entries/infrastructure/persistence/relation-link.service.d.ts.map +1 -0
- package/dist/lib/entries/infrastructure/persistence/relation-link.service.js +1008 -0
- package/dist/lib/entries/infrastructure/queries/bulk-publish-preview.query.d.ts +19 -0
- package/dist/lib/entries/infrastructure/queries/bulk-publish-preview.query.d.ts.map +1 -0
- package/dist/lib/entries/infrastructure/queries/bulk-publish-preview.query.js +39 -0
- package/dist/lib/entries/infrastructure/queries/entries.service.d.ts +64 -0
- package/dist/lib/entries/infrastructure/queries/entries.service.d.ts.map +1 -0
- package/dist/lib/entries/infrastructure/queries/entries.service.js +202 -0
- package/dist/lib/entries/infrastructure/queries/entry-filter-schema.d.ts +40 -0
- package/dist/lib/entries/infrastructure/queries/entry-filter-schema.d.ts.map +1 -0
- package/dist/lib/entries/infrastructure/queries/entry-filter-surface.d.ts +38 -0
- package/dist/lib/entries/infrastructure/queries/entry-filter-surface.d.ts.map +1 -0
- package/dist/lib/entries/infrastructure/queries/entry-filter-surface.js +310 -0
- package/dist/lib/entries/infrastructure/queries/entry-scalar-fields.d.ts +25 -0
- package/dist/lib/entries/infrastructure/queries/entry-scalar-fields.d.ts.map +1 -0
- package/dist/lib/entries/infrastructure/queries/entry-scalar-fields.js +51 -0
- package/dist/lib/entries/infrastructure/queries/entry-search.d.ts +19 -0
- package/dist/lib/entries/infrastructure/queries/entry-search.d.ts.map +1 -0
- package/dist/lib/entries/infrastructure/queries/entry-search.js +52 -0
- package/dist/lib/entries/infrastructure/queries/media-refs.query.d.ts +21 -0
- package/dist/lib/entries/infrastructure/queries/media-refs.query.d.ts.map +1 -0
- package/dist/lib/entries/infrastructure/queries/media-refs.query.js +117 -0
- package/dist/lib/entries/services/entries.service.d.ts +59 -0
- package/dist/lib/entries/services/entries.service.d.ts.map +1 -0
- package/dist/lib/entries/services/entry-counter.service.d.ts +31 -0
- package/dist/lib/entries/services/entry-counter.service.d.ts.map +1 -0
- package/dist/lib/entries/services/entry-filter-schema.d.ts +32 -0
- package/dist/lib/entries/services/entry-filter-schema.d.ts.map +1 -0
- package/dist/lib/entries/services/entry-row.d.ts +56 -0
- package/dist/lib/entries/services/entry-row.d.ts.map +1 -0
- package/dist/lib/entries/services/entry-writer.service.d.ts +193 -0
- package/dist/lib/entries/services/entry-writer.service.d.ts.map +1 -0
- package/dist/lib/entries/services/relation-link.service.d.ts +115 -0
- package/dist/lib/entries/services/relation-link.service.d.ts.map +1 -0
- package/dist/lib/entries/types/bulk-publish.d.ts +75 -0
- package/dist/lib/entries/types/bulk-publish.d.ts.map +1 -0
- package/dist/lib/entries/types/bulk-publish.js +22 -0
- package/dist/lib/entries/types/entry-list-view.d.ts +205 -0
- package/dist/lib/entries/types/entry-list-view.d.ts.map +1 -0
- package/dist/lib/entries/types/entry-list-view.js +5 -0
- package/dist/lib/entries/types/filter-surface.d.ts +35 -0
- package/dist/lib/entries/types/filter-surface.d.ts.map +1 -0
- package/dist/lib/entries/types/filter-surface.js +2 -0
- package/dist/lib/extension/entry-extension-boot-check.d.ts +16 -0
- package/dist/lib/extension/entry-extension-boot-check.d.ts.map +1 -0
- package/dist/lib/extension/entry-extension-boot-check.js +42 -0
- package/dist/lib/extension/entry-extension.d.ts +225 -0
- package/dist/lib/extension/entry-extension.d.ts.map +1 -0
- package/dist/lib/extension/entry-extension.js +27 -0
- package/dist/lib/extension/media-asset-resolver.d.ts +85 -0
- package/dist/lib/extension/media-asset-resolver.d.ts.map +1 -0
- package/dist/lib/extension/media-asset-resolver.js +26 -0
- package/dist/lib/extension/per-locale-relation.d.ts +18 -0
- package/dist/lib/extension/per-locale-relation.d.ts.map +1 -0
- package/dist/lib/extension/relation-locale-sync.d.ts +62 -0
- package/dist/lib/extension/relation-locale-sync.d.ts.map +1 -0
- package/dist/lib/extension/relation-locale-sync.js +88 -0
- package/dist/lib/fields/index.d.ts +94 -0
- package/dist/lib/fields/index.d.ts.map +1 -0
- package/dist/lib/fields/index.js +203 -0
- package/dist/lib/insights/http/controllers/content-pipeline.controller.d.ts +16 -0
- package/dist/lib/insights/http/controllers/content-pipeline.controller.d.ts.map +1 -0
- package/dist/lib/insights/http/controllers/content-pipeline.controller.js +39 -0
- package/dist/lib/insights/http/controllers/content-punchcard.controller.d.ts +18 -0
- package/dist/lib/insights/http/controllers/content-punchcard.controller.d.ts.map +1 -0
- package/dist/lib/insights/http/controllers/content-punchcard.controller.js +42 -0
- package/dist/lib/insights/http/controllers/content-stale.controller.d.ts +17 -0
- package/dist/lib/insights/http/controllers/content-stale.controller.d.ts.map +1 -0
- package/dist/lib/insights/http/controllers/content-stale.controller.js +40 -0
- package/dist/lib/insights/http/controllers/content-totals.controller.d.ts +18 -0
- package/dist/lib/insights/http/controllers/content-totals.controller.d.ts.map +1 -0
- package/dist/lib/insights/http/controllers/content-totals.controller.js +42 -0
- package/dist/lib/insights/http/controllers/content-unshipped.controller.d.ts +17 -0
- package/dist/lib/insights/http/controllers/content-unshipped.controller.d.ts.map +1 -0
- package/dist/lib/insights/http/controllers/content-unshipped.controller.js +40 -0
- package/dist/lib/insights/http/controllers/content-velocity.controller.d.ts +17 -0
- package/dist/lib/insights/http/controllers/content-velocity.controller.d.ts.map +1 -0
- package/dist/lib/insights/http/controllers/content-velocity.controller.js +41 -0
- package/dist/lib/insights/http/dto/insights-range-query.dto.d.ts +15 -0
- package/dist/lib/insights/http/dto/insights-range-query.dto.d.ts.map +1 -0
- package/dist/lib/insights/http/dto/insights-range-query.dto.js +38 -0
- package/dist/lib/insights/infrastructure/queries/content-insights.query.d.ts +77 -0
- package/dist/lib/insights/infrastructure/queries/content-insights.query.d.ts.map +1 -0
- package/dist/lib/insights/infrastructure/queries/content-insights.query.js +380 -0
- package/dist/lib/insights/types/content-insights-view.d.ts +134 -0
- package/dist/lib/insights/types/content-insights-view.d.ts.map +1 -0
- package/dist/lib/insights/types/content-insights-view.js +10 -0
- package/dist/lib/mcp/content-tools.provider.d.ts +83 -0
- package/dist/lib/mcp/content-tools.provider.d.ts.map +1 -0
- package/dist/lib/mcp/content-tools.provider.js +441 -0
- package/dist/lib/mcp/tool-input.d.ts +43 -0
- package/dist/lib/mcp/tool-input.d.ts.map +1 -0
- package/dist/lib/mcp/tool-input.js +117 -0
- package/dist/lib/mcp/tool-schemas.d.ts +34 -0
- package/dist/lib/mcp/tool-schemas.d.ts.map +1 -0
- package/dist/lib/mcp/tool-schemas.js +282 -0
- package/dist/lib/public/controllers/get-public-entry.controller.d.ts +26 -0
- package/dist/lib/public/controllers/get-public-entry.controller.d.ts.map +1 -0
- package/dist/lib/public/controllers/list-public-entries.controller.d.ts +24 -0
- package/dist/lib/public/controllers/list-public-entries.controller.d.ts.map +1 -0
- package/dist/lib/public/controllers/public-schema.controller.d.ts +18 -0
- package/dist/lib/public/controllers/public-schema.controller.d.ts.map +1 -0
- package/dist/lib/public-api/http/api-token-request.d.ts +41 -0
- package/dist/lib/public-api/http/api-token-request.d.ts.map +1 -0
- package/dist/lib/public-api/http/api-token-request.js +2 -0
- package/dist/lib/public-api/http/controllers/public-content-types.controller.d.ts +31 -0
- package/dist/lib/public-api/http/controllers/public-content-types.controller.d.ts.map +1 -0
- package/dist/lib/public-api/http/controllers/public-content-types.controller.js +93 -0
- package/dist/lib/public-api/http/controllers/public-entries.controller.d.ts +101 -0
- package/dist/lib/public-api/http/controllers/public-entries.controller.d.ts.map +1 -0
- package/dist/lib/public-api/http/controllers/public-entries.controller.js +307 -0
- package/dist/lib/public-api/http/controllers/public-entry-writes.controller.d.ts +76 -0
- package/dist/lib/public-api/http/controllers/public-entry-writes.controller.d.ts.map +1 -0
- package/dist/lib/public-api/http/controllers/public-entry-writes.controller.js +353 -0
- package/dist/lib/public-api/http/controllers/resolve-granted-type.d.ts +42 -0
- package/dist/lib/public-api/http/controllers/resolve-granted-type.d.ts.map +1 -0
- package/dist/lib/public-api/http/controllers/resolve-granted-type.js +25 -0
- package/dist/lib/public-api/http/decorators/current-api-token.decorator.d.ts +8 -0
- package/dist/lib/public-api/http/decorators/current-api-token.decorator.d.ts.map +1 -0
- package/dist/lib/public-api/http/decorators/current-api-token.decorator.js +17 -0
- package/dist/lib/public-api/http/dto/public-bulk.dto.d.ts +58 -0
- package/dist/lib/public-api/http/dto/public-bulk.dto.d.ts.map +1 -0
- package/dist/lib/public-api/http/dto/public-bulk.dto.js +118 -0
- package/dist/lib/public-api/http/dto/public-list-entries-query.dto.d.ts +106 -0
- package/dist/lib/public-api/http/dto/public-list-entries-query.dto.d.ts.map +1 -0
- package/dist/lib/public-api/http/dto/public-list-entries-query.dto.js +297 -0
- package/dist/lib/public-api/http/dto/public-save-entry.dto.d.ts +39 -0
- package/dist/lib/public-api/http/dto/public-save-entry.dto.d.ts.map +1 -0
- package/dist/lib/public-api/http/dto/public-save-entry.dto.js +112 -0
- package/dist/lib/public-api/http/guards/api-token-workspace.guard.d.ts +25 -0
- package/dist/lib/public-api/http/guards/api-token-workspace.guard.d.ts.map +1 -0
- package/dist/lib/public-api/http/guards/api-token-workspace.guard.js +58 -0
- package/dist/lib/public-api/http/guards/api-token.guard.d.ts +32 -0
- package/dist/lib/public-api/http/guards/api-token.guard.d.ts.map +1 -0
- package/dist/lib/public-api/http/guards/api-token.guard.js +92 -0
- package/dist/lib/public-api/http/guards/draft-visibility.guard.d.ts +25 -0
- package/dist/lib/public-api/http/guards/draft-visibility.guard.d.ts.map +1 -0
- package/dist/lib/public-api/http/guards/draft-visibility.guard.js +56 -0
- package/dist/lib/public-api/infrastructure/bulk-save-op.d.ts +55 -0
- package/dist/lib/public-api/infrastructure/bulk-save-op.d.ts.map +1 -0
- package/dist/lib/public-api/infrastructure/bulk-save-op.js +61 -0
- package/dist/lib/public-api/infrastructure/field-selection.d.ts +21 -0
- package/dist/lib/public-api/infrastructure/field-selection.d.ts.map +1 -0
- package/dist/lib/public-api/infrastructure/field-selection.js +72 -0
- package/dist/lib/public-api/infrastructure/public-entries.query.d.ts +248 -0
- package/dist/lib/public-api/infrastructure/public-entries.query.d.ts.map +1 -0
- package/dist/lib/public-api/infrastructure/public-entries.query.js +557 -0
- package/dist/lib/public-api/infrastructure/public-entry-row.d.ts +31 -0
- package/dist/lib/public-api/infrastructure/public-entry-row.d.ts.map +1 -0
- package/dist/lib/public-api/infrastructure/public-entry-row.js +76 -0
- package/dist/lib/public-api/infrastructure/public-entry-writes.service.d.ts +188 -0
- package/dist/lib/public-api/infrastructure/public-entry-writes.service.d.ts.map +1 -0
- package/dist/lib/public-api/infrastructure/public-entry-writes.service.js +321 -0
- package/dist/lib/public-api/infrastructure/public-expansion.query.d.ts +98 -0
- package/dist/lib/public-api/infrastructure/public-expansion.query.d.ts.map +1 -0
- package/dist/lib/public-api/infrastructure/public-expansion.query.js +384 -0
- package/dist/lib/public-api/types/public-bulk.d.ts +77 -0
- package/dist/lib/public-api/types/public-bulk.d.ts.map +1 -0
- package/dist/lib/public-api/types/public-bulk.js +23 -0
- package/dist/lib/public-api/types/public-entry.d.ts +97 -0
- package/dist/lib/public-api/types/public-entry.d.ts.map +1 -0
- package/dist/lib/public-api/types/public-entry.js +8 -0
- package/dist/lib/public-api/types/public-expansion.d.ts +92 -0
- package/dist/lib/public-api/types/public-expansion.d.ts.map +1 -0
- package/dist/lib/public-api/types/public-expansion.js +2 -0
- package/dist/lib/registry/content-type-registry.d.ts +103 -0
- package/dist/lib/registry/content-type-registry.d.ts.map +1 -0
- package/dist/lib/registry/content-type-registry.js +155 -0
- package/dist/lib/revisions/application/ports/revision-store.d.ts +71 -0
- package/dist/lib/revisions/application/ports/revision-store.d.ts.map +1 -0
- package/dist/lib/revisions/application/ports/revision-store.js +9 -0
- package/dist/lib/revisions/application/use-cases/publish-revision.use-case.d.ts +40 -0
- package/dist/lib/revisions/application/use-cases/publish-revision.use-case.d.ts.map +1 -0
- package/dist/lib/revisions/application/use-cases/publish-revision.use-case.js +66 -0
- package/dist/lib/revisions/application/use-cases/restore-revision.use-case.d.ts +36 -0
- package/dist/lib/revisions/application/use-cases/restore-revision.use-case.d.ts.map +1 -0
- package/dist/lib/revisions/application/use-cases/restore-revision.use-case.js +57 -0
- package/dist/lib/revisions/domain/revision-status.d.ts +18 -0
- package/dist/lib/revisions/domain/revision-status.d.ts.map +1 -0
- package/dist/lib/revisions/domain/revision-status.js +18 -0
- package/dist/lib/revisions/domain/revision.d.ts +80 -0
- package/dist/lib/revisions/domain/revision.d.ts.map +1 -0
- package/dist/lib/revisions/domain/revision.js +84 -0
- package/dist/lib/revisions/http/controllers/publish-revision.controller.d.ts +19 -0
- package/dist/lib/revisions/http/controllers/publish-revision.controller.d.ts.map +1 -0
- package/dist/lib/revisions/http/controllers/publish-revision.controller.js +51 -0
- package/dist/lib/revisions/http/controllers/restore-revision.controller.d.ts +17 -0
- package/dist/lib/revisions/http/controllers/restore-revision.controller.d.ts.map +1 -0
- package/dist/lib/revisions/http/controllers/restore-revision.controller.js +49 -0
- package/dist/lib/revisions/http/controllers/revisions.controller.d.ts +20 -0
- package/dist/lib/revisions/http/controllers/revisions.controller.d.ts.map +1 -0
- package/dist/lib/revisions/http/controllers/revisions.controller.js +76 -0
- package/dist/lib/revisions/infrastructure/persistence/drizzle-revision.store.d.ts +33 -0
- package/dist/lib/revisions/infrastructure/persistence/drizzle-revision.store.d.ts.map +1 -0
- package/dist/lib/revisions/infrastructure/persistence/drizzle-revision.store.js +174 -0
- package/dist/lib/revisions/infrastructure/persistence/revision-snapshot.d.ts +17 -0
- package/dist/lib/revisions/infrastructure/persistence/revision-snapshot.d.ts.map +1 -0
- package/dist/lib/revisions/infrastructure/persistence/revision-snapshot.js +20 -0
- package/dist/lib/revisions/infrastructure/persistence/revision-table.d.ts +228 -0
- package/dist/lib/revisions/infrastructure/persistence/revision-table.d.ts.map +1 -0
- package/dist/lib/revisions/infrastructure/persistence/revision-table.js +57 -0
- package/dist/lib/revisions/infrastructure/queries/revision-refs.query.d.ts +22 -0
- package/dist/lib/revisions/infrastructure/queries/revision-refs.query.d.ts.map +1 -0
- package/dist/lib/revisions/infrastructure/queries/revision-refs.query.js +66 -0
- package/dist/lib/revisions/revisions.constants.d.ts +10 -0
- package/dist/lib/revisions/revisions.constants.d.ts.map +1 -0
- package/dist/lib/revisions/revisions.constants.js +12 -0
- package/dist/lib/revisions/types/revision-view.d.ts +74 -0
- package/dist/lib/revisions/types/revision-view.d.ts.map +1 -0
- package/dist/lib/revisions/types/revision-view.js +2 -0
- package/dist/lib/types/content-type.d.ts +131 -0
- package/dist/lib/types/content-type.d.ts.map +1 -0
- package/dist/lib/types/content-type.js +28 -0
- package/dist/lib/types/fields.d.ts +303 -0
- package/dist/lib/types/fields.d.ts.map +1 -0
- package/dist/lib/types/fields.js +52 -0
- package/dist/lib/utils/content-plugin.d.ts +35 -0
- package/dist/lib/utils/content-plugin.d.ts.map +1 -0
- package/dist/lib/utils/content-plugin.js +35 -0
- package/dist/lib/validation/services/entry-validation.service.d.ts +24 -0
- package/dist/lib/validation/services/entry-validation.service.d.ts.map +1 -0
- package/dist/lib/validation/services/entry-validation.service.js +35 -0
- 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
|
+
}
|