@growth-labs/cms 0.6.6 → 0.6.7

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 (72) hide show
  1. package/dist/engine/foundry-dispatch.d.ts.map +1 -1
  2. package/dist/engine/foundry-dispatch.js +36 -7
  3. package/dist/engine/foundry-dispatch.js.map +1 -1
  4. package/dist/engine/fronts-publish-intent.d.ts +15 -4
  5. package/dist/engine/fronts-publish-intent.d.ts.map +1 -1
  6. package/dist/engine/fronts-publish-intent.js +52 -19
  7. package/dist/engine/fronts-publish-intent.js.map +1 -1
  8. package/dist/engine/fronts-publish.d.ts +19 -0
  9. package/dist/engine/fronts-publish.d.ts.map +1 -1
  10. package/dist/engine/fronts-publish.js +158 -5
  11. package/dist/engine/fronts-publish.js.map +1 -1
  12. package/dist/engine/index.d.ts +1 -0
  13. package/dist/engine/index.d.ts.map +1 -1
  14. package/dist/engine/index.js +1 -0
  15. package/dist/engine/index.js.map +1 -1
  16. package/dist/engine/podcast-source.d.ts +28 -0
  17. package/dist/engine/podcast-source.d.ts.map +1 -0
  18. package/dist/engine/podcast-source.js +50 -0
  19. package/dist/engine/podcast-source.js.map +1 -0
  20. package/dist/engine/publication-current-guard.d.ts +10 -0
  21. package/dist/engine/publication-current-guard.d.ts.map +1 -0
  22. package/dist/engine/publication-current-guard.js +76 -0
  23. package/dist/engine/publication-current-guard.js.map +1 -0
  24. package/dist/engine/publication.d.ts +4 -0
  25. package/dist/engine/publication.d.ts.map +1 -1
  26. package/dist/engine/publication.js +13 -0
  27. package/dist/engine/publication.js.map +1 -1
  28. package/dist/engine/publisher.d.ts.map +1 -1
  29. package/dist/engine/publisher.js +20 -13
  30. package/dist/engine/publisher.js.map +1 -1
  31. package/dist/engine/soft-delete.js +2 -2
  32. package/dist/engine/soft-delete.js.map +1 -1
  33. package/dist/providers/types.d.ts +9 -3
  34. package/dist/providers/types.d.ts.map +1 -1
  35. package/dist/providers/types.js.map +1 -1
  36. package/dist/routes/content.d.ts +2 -0
  37. package/dist/routes/content.d.ts.map +1 -1
  38. package/dist/routes/content.js +1 -1
  39. package/dist/routes/content.js.map +1 -1
  40. package/dist/routes/fronts-publish.d.ts +2 -2
  41. package/dist/routes/fronts-publish.d.ts.map +1 -1
  42. package/dist/routes/fronts-publish.js +52 -23
  43. package/dist/routes/fronts-publish.js.map +1 -1
  44. package/dist/routes/index.js +1 -1
  45. package/dist/routes/index.js.map +1 -1
  46. package/dist/schema/layout.d.ts +2 -2
  47. package/dist/ui/api/_content-config.d.ts.map +1 -1
  48. package/dist/ui/api/_content-config.js +1 -0
  49. package/dist/ui/api/_content-config.js.map +1 -1
  50. package/dist/ui/api/fronts/publish-callback.d.ts.map +1 -1
  51. package/dist/ui/api/fronts/publish-callback.js +2 -6
  52. package/dist/ui/api/fronts/publish-callback.js.map +1 -1
  53. package/dist/ui/api/fronts/schedule.d.ts.map +1 -1
  54. package/dist/ui/api/fronts/schedule.js +2 -6
  55. package/dist/ui/api/fronts/schedule.js.map +1 -1
  56. package/package.json +1 -1
  57. package/src/engine/foundry-dispatch.ts +67 -12
  58. package/src/engine/fronts-publish-intent.ts +78 -26
  59. package/src/engine/fronts-publish.ts +223 -5
  60. package/src/engine/index.ts +4 -0
  61. package/src/engine/podcast-source.ts +82 -0
  62. package/src/engine/publication-current-guard.ts +87 -0
  63. package/src/engine/publication.ts +23 -0
  64. package/src/engine/publisher.ts +21 -13
  65. package/src/engine/soft-delete.ts +2 -2
  66. package/src/providers/types.ts +6 -1
  67. package/src/routes/content.ts +3 -1
  68. package/src/routes/fronts-publish.ts +63 -25
  69. package/src/routes/index.ts +1 -1
  70. package/src/ui/api/_content-config.ts +2 -0
  71. package/src/ui/api/fronts/publish-callback.ts +2 -8
  72. package/src/ui/api/fronts/schedule.ts +2 -8
@@ -29,9 +29,13 @@ import {
29
29
  isFrontsPublishEnabled,
30
30
  listFrontsPublishSchedule,
31
31
  } from '../engine/fronts-publish.js'
32
- import { hydrateFrontsPublishIntents } from '../engine/fronts-publish-intent.js'
32
+ import {
33
+ FrontsScheduleOverflowError,
34
+ hydrateFrontsPublishIntents,
35
+ } from '../engine/fronts-publish-intent.js'
33
36
  import { publicUrlFor } from '../ui/screens/public-url.js'
34
- import { type CmsRouteConfig, resolveConfig } from './config.js'
37
+ import { resolveConfig } from './config.js'
38
+ import type { ContentRouteConfig } from './content.js'
35
39
  import { json, type RouteContext } from './context.js'
36
40
 
37
41
  /**
@@ -112,6 +116,7 @@ const CallbackSchema = z
112
116
  intent_id: z.string().min(1).nullish(),
113
117
  content_id: z.string().min(1),
114
118
  version: z.number().int().nullish(),
119
+ publish_at: z.number().int().positive().nullish(),
115
120
  correlation_id: z.string().min(1).nullish(),
116
121
  status: z.enum(['queued', 'publishing', 'live', 'late', 'failed']),
117
122
  occurred_at: z.union([z.string(), z.number()]).nullish(),
@@ -151,7 +156,7 @@ function parseWindow(
151
156
  return { fromSeconds, toSeconds }
152
157
  }
153
158
 
154
- export function createFrontsPublishRoutes(config: CmsRouteConfig): FrontsPublishRouteHandlers {
159
+ export function createFrontsPublishRoutes(config: ContentRouteConfig): FrontsPublishRouteHandlers {
155
160
  const resolved = resolveConfig(config)
156
161
  return {
157
162
  async schedule(ctx) {
@@ -184,28 +189,37 @@ export function createFrontsPublishRoutes(config: CmsRouteConfig): FrontsPublish
184
189
  // predicate, and `incomplete` names every due item that cannot form a
185
190
  // complete intent, so a missing canonical field is an explicit, visible
186
191
  // contract error rather than a silently short `intents` array.
187
- const items = await listFrontsPublishSchedule(ctx.db, {
188
- toSeconds: window.toSeconds,
189
- nowSeconds: now,
190
- })
191
- const hydrated = await hydrateFrontsPublishIntents(ctx.db, {
192
- toSeconds: window.toSeconds,
193
- nowSeconds: now,
194
- mediaPublicDomain: resolved.mediaPublicDomain,
195
- // Site-RELATIVE paths: the CMS does not know the site origin, and the
196
- // consumer joins its own trusted configured origin. A host-supplied
197
- // origin is deliberately deferred (packages-docs/cms.md).
198
- resolveContentUrl: (row) => publicUrlFor(row),
199
- })
200
- return json({
201
- ok: true,
202
- now,
203
- window: { from: window.fromSeconds, to: window.toSeconds },
204
- count: items.length,
205
- items,
206
- intents: hydrated.intents,
207
- incomplete: hydrated.incomplete,
208
- })
192
+ try {
193
+ const items = await listFrontsPublishSchedule(ctx.db, {
194
+ toSeconds: window.toSeconds,
195
+ nowSeconds: now,
196
+ })
197
+ const hydrated = await hydrateFrontsPublishIntents(ctx.db, {
198
+ toSeconds: window.toSeconds,
199
+ nowSeconds: now,
200
+ mediaPublicDomain: resolved.mediaPublicDomain,
201
+ podcastSourceProductionEnabled: config.podcastSourceProductionEnabled,
202
+ // Site-RELATIVE paths: the CMS does not know the site origin, and the
203
+ // consumer joins its own trusted configured origin. A host-supplied
204
+ // origin is deliberately deferred (packages-docs/cms.md).
205
+ resolveContentUrl: (row) => publicUrlFor(row),
206
+ })
207
+ return json({
208
+ ok: true,
209
+ now,
210
+ window: { from: window.fromSeconds, to: window.toSeconds },
211
+ count: items.length,
212
+ items,
213
+ intents: hydrated.intents,
214
+ incomplete: hydrated.incomplete,
215
+ })
216
+ } catch (error) {
217
+ if (error instanceof FrontsScheduleOverflowError) {
218
+ console.error(`[cms] fronts/schedule unavailable: ${error.message}`)
219
+ return json({ ok: false, error: 'schedule_overflow', message: error.message }, 503)
220
+ }
221
+ throw error
222
+ }
209
223
  },
210
224
 
211
225
  async publishCallback(ctx) {
@@ -253,16 +267,40 @@ export function createFrontsPublishRoutes(config: CmsRouteConfig): FrontsPublish
253
267
  intentId: parsed.data.intent_id ?? null,
254
268
  contentId: parsed.data.content_id,
255
269
  version: parsed.data.version ?? null,
270
+ publishAt: parsed.data.publish_at ?? null,
256
271
  correlationId: parsed.data.correlation_id ?? null,
257
272
  status: parsed.data.status,
258
273
  occurredAt: parsed.data.occurred_at ?? null,
259
274
  receipt: parsed.data.receipt ?? null,
260
275
  error: parsed.data.error ?? null,
276
+ checkReadiness: config.publishReadiness
277
+ ? (item) =>
278
+ config.publishReadiness!({
279
+ ctx,
280
+ contentId: item.id,
281
+ type: item.type,
282
+ status: item.status,
283
+ })
284
+ : undefined,
261
285
  })
262
286
 
263
287
  if (!result.contentFound) {
264
288
  return json({ error: 'content_not_found', contentId: parsed.data.content_id }, 404)
265
289
  }
290
+ if (result.rejection) {
291
+ console.warn(
292
+ `[cms] fronts/publish-callback rejected for content ${parsed.data.content_id}: ${result.rejection.code}`,
293
+ )
294
+ return json(
295
+ {
296
+ ok: false,
297
+ error: result.rejection.code,
298
+ message: result.rejection.message,
299
+ ...(result.rejection.blockers ? { blockers: result.rejection.blockers } : {}),
300
+ },
301
+ result.rejection.status,
302
+ )
303
+ }
266
304
  if (result.ledgerError) {
267
305
  // A confirmed `live` that could not be committed through the ledger is a
268
306
  // hard, loud failure — never a silent zero.
@@ -312,7 +312,7 @@ export function createCmsRoutes(
312
312
  authz: config.authz,
313
313
  activeWorkspaceId: config.activeWorkspaceId,
314
314
  }),
315
- frontsPublish: createFrontsPublishRoutes(base),
315
+ frontsPublish: createFrontsPublishRoutes(config),
316
316
  foundryPublish: createFoundryPublishRoutes(config),
317
317
  }
318
318
  }
@@ -24,6 +24,7 @@ type CmsHooksLocals = ContentRouteConfig['hooks'] & {
24
24
  dispatchFaq?: DispatchFaqHook
25
25
  dispatchWebhook?: DispatchWebhookHook
26
26
  publishReadiness?: PublishReadinessHook
27
+ podcastSourceProductionEnabled?: boolean
27
28
  }
28
29
 
29
30
  /**
@@ -73,5 +74,6 @@ export function toContentConfig(c: APIContext): ContentRouteConfig {
73
74
  dispatchFaq: hooks.dispatchFaq,
74
75
  dispatchWebhook: hooks.dispatchWebhook,
75
76
  publishReadiness: hooks.publishReadiness,
77
+ podcastSourceProductionEnabled: hooks.podcastSourceProductionEnabled,
76
78
  }
77
79
  }
@@ -5,14 +5,8 @@
5
5
  // content_publication_attempts 2PC ledger. HMAC-SHA256 (X-Foundry-Signature vs
6
6
  // env.FOUNDRY_HMAC_SECRET) only — NOT behind requirePublisher.
7
7
  import type { APIContext } from 'astro'
8
- import type { RouteContext } from '../../../routes/context.js'
9
8
  import { createFrontsPublishRoutes } from '../../../routes/fronts-publish.js'
10
- import { buildSiteAuthz } from '../_authz.js'
11
-
12
- function toCtx(c: APIContext): RouteContext {
13
- const env = (c.locals as { cmsEnv?: Record<string, unknown> }).cmsEnv ?? {}
14
- return { request: c.request, params: {}, db: env.SITE_DB as RouteContext['db'], env }
15
- }
9
+ import { toContentConfig, toContentCtx } from '../_content-config.js'
16
10
 
17
11
  export const POST = (c: APIContext) =>
18
- createFrontsPublishRoutes({ authz: buildSiteAuthz(c) }).publishCallback(toCtx(c))
12
+ createFrontsPublishRoutes(toContentConfig(c)).publishCallback(toContentCtx(c, {}))
@@ -5,14 +5,8 @@
5
5
  // env.FOUNDRY_HMAC_SECRET) only — NOT behind requirePublisher. Sibling of the
6
6
  // content foundry-callback / insights-ingest M2M routes.
7
7
  import type { APIContext } from 'astro'
8
- import type { RouteContext } from '../../../routes/context.js'
9
8
  import { createFrontsPublishRoutes } from '../../../routes/fronts-publish.js'
10
- import { buildSiteAuthz } from '../_authz.js'
11
-
12
- function toCtx(c: APIContext): RouteContext {
13
- const env = (c.locals as { cmsEnv?: Record<string, unknown> }).cmsEnv ?? {}
14
- return { request: c.request, params: {}, db: env.SITE_DB as RouteContext['db'], env }
15
- }
9
+ import { toContentConfig, toContentCtx } from '../_content-config.js'
16
10
 
17
11
  export const GET = (c: APIContext) =>
18
- createFrontsPublishRoutes({ authz: buildSiteAuthz(c) }).schedule(toCtx(c))
12
+ createFrontsPublishRoutes(toContentConfig(c)).schedule(toContentCtx(c, {}))