@elytracms/studio 0.0.13 → 0.0.14

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/convex/cms.ts CHANGED
@@ -3,6 +3,7 @@ import { v } from 'convex/values'
3
3
  import { mutation, query } from './functions'
4
4
  import { requireRole } from './guard'
5
5
  import { maintainDocumentReferences, removeDocumentReferences } from './referenceMaintenance'
6
+ import { scheduleRevalidate } from './revalidate'
6
7
 
7
8
  /**
8
9
  * CMS document persistence (EC-043). The backend stores **documents only**
@@ -174,6 +175,11 @@ export const publishDocumentVersion = mutation({
174
175
  // entries are extracted from the live draft content (matching the EC-173 guard
175
176
  // and `reindexDocumentReferences`), not the pinned snapshot.
176
177
  await maintainDocumentReferences(ctx, args.collection, args.docId, doc.document as CmsDocument, true)
178
+ // EC-146 instant publish: a CONTENT publish must invalidate the host cache just
179
+ // like a graph publish — without this, instant-publish silently never fires for
180
+ // a content-only site (the server-side webhook path previously covered only
181
+ // `publishing:publish`). Fire-and-forget; unconfigured deployments no-op.
182
+ await scheduleRevalidate(ctx, 'documents.publish')
177
183
  return ctx.db.get(doc._id)
178
184
  },
179
185
  })
@@ -191,6 +197,9 @@ export const unpublishDocument = mutation({
191
197
  await ctx.db.patch(doc._id, { publishedVersion: undefined, updatedAt: args.updatedAt })
192
198
  // EC-277: the live pin is cleared — drop this doc's published index rows.
193
199
  await maintainDocumentReferences(ctx, args.collection, args.docId, doc.document as CmsDocument, false)
200
+ // EC-146 instant publish: unpublishing content must drop the host's cached
201
+ // pages too (the page that baked this doc now serves stale published content).
202
+ await scheduleRevalidate(ctx, 'documents.unpublish')
194
203
  return ctx.db.get(doc._id)
195
204
  },
196
205
  })
@@ -1,50 +1,18 @@
1
- import { makeFunctionReference } from 'convex/server'
2
1
  import { v } from 'convex/values'
3
2
  import { mutation, query } from './functions'
4
- import type { MutationCtx } from './functions'
5
3
  import { requireRole } from './guard'
6
4
  import { maintainGraphPublishedReferences } from './referenceMaintenance'
7
-
8
- // EC-223: the studio's convex functions are package-shipped and deployment-agnostic
9
- // — they address each other by STRING ref (resolved against the consumer's deployment),
10
- // never the per-deployment `_generated/api`. Mirrors the lib's `queryRef`/`actionRef`.
11
- const webhooksDeliver = makeFunctionReference<'action'>('webhooks:deliver')
5
+ import { scheduleRevalidate } from './revalidate'
12
6
 
13
7
  /**
14
8
  * Publishing state (EC-045). Single-environment (EC-179, vision AD-8): a
15
9
  * deployment IS one environment, so there is exactly ONE publishingState row —
16
10
  * see `env-is-deployment-boundary`.
17
- */
18
-
19
- /**
20
- * EC-180 / EC-146 — fire the host revalidation webhook on a publish-state change.
21
- *
22
- * A mutation cannot `fetch`, so we hand off to the `webhooks.deliver` internal
23
- * action (it signs + POSTs server-side; the secret never leaves Convex). A graph
24
- * publish/unpublish can affect any page, so the honest v1 invalidation is the
25
- * whole-project sweep tag `p:<scope>:project` — a mirror of
26
- * `projectSweepWebhookTag` (`src/lib/webhooks/tags.ts`) / `@elytracms/next`'s
27
- * `projectSweepTag`; the tests on those two sides pin the literal.
28
11
  *
29
- * `scope` must equal the host's `cache.scope` (its `ELYTRA_PROJECT_ID`) for the
30
- * tag to match what the host stamped on its cached fetches; it comes from the
31
- * `CANVAS_PROJECT_SCOPE` Convex env var. Unconfigured deployments (local dev,
32
- * no env) are a silent no-op — publishing must never depend on a reachable host.
12
+ * EC-180 / EC-146: a graph publish/unpublish fires the host revalidation webhook
13
+ * via `scheduleRevalidate` (`./revalidate`) — the SAME helper CMS document
14
+ * publish uses, so content-only sites invalidate too (see `cms.ts`).
33
15
  */
34
- async function scheduleRevalidate(
35
- ctx: MutationCtx,
36
- event: 'publishing.publish' | 'publishing.unpublish',
37
- ): Promise<void> {
38
- const env =
39
- (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env ?? {}
40
- const scope = env['CANVAS_PROJECT_SCOPE']
41
- if (!scope) return
42
- await ctx.scheduler.runAfter(0, webhooksDeliver, {
43
- projectId: scope,
44
- event,
45
- tags: [`p:${scope}:project`],
46
- })
47
- }
48
16
 
49
17
  export const publish = mutation({
50
18
  args: {
@@ -0,0 +1,42 @@
1
+ import { makeFunctionReference } from 'convex/server'
2
+ import type { MutationCtx } from './functions'
3
+
4
+ // EC-223: the studio's convex functions address each other by STRING ref
5
+ // (resolved against the consumer's deployment), never the per-deployment
6
+ // `_generated/api`. Mirrors the lib's `queryRef`/`actionRef`.
7
+ const webhooksDeliver = makeFunctionReference<'action'>('webhooks:deliver')
8
+
9
+ /**
10
+ * EC-146 / EC-180 — schedule the host revalidation webhook after a publish-state
11
+ * change. A mutation cannot `fetch`, so it hands off to the `webhooks.deliver`
12
+ * internal action (it signs + POSTs server-side; the secret never leaves Convex).
13
+ *
14
+ * The v1 invalidation is the whole-project sweep tag `p:<scope>:project` — every
15
+ * cached host page carries it (`@elytracms/next` `nextCacheTags` stamps it on
16
+ * every entry), so it is GUARANTEED to drop the affected pages with no tag-format
17
+ * coupling that could silently miss. `scope` must equal the host's `cache.scope`
18
+ * (its `ELYTRA_PROJECT_ID`); it comes from the `CANVAS_PROJECT_SCOPE` Convex env
19
+ * var. An unconfigured deployment (local dev, no env) is a silent no-op —
20
+ * publishing must never depend on a reachable host.
21
+ *
22
+ * Shared by BOTH graph publish (`publishing.ts`) and CMS **document** publish
23
+ * (`cms.ts`). A content-only site never touches the graph, so without the
24
+ * document-publish call instant-publish silently never fires for it (the
25
+ * footgun this consolidates away).
26
+ *
27
+ * Doc/collection-scoped tags (matching the client dispatcher's
28
+ * `webhookTagsForEvent`) would be more surgical, but need the project locales —
29
+ * not available in the Convex env here. The sweep is the honest, reliable v1;
30
+ * threading scoped tags is a precision follow-up.
31
+ */
32
+ export async function scheduleRevalidate(ctx: MutationCtx, event: string): Promise<void> {
33
+ const env =
34
+ (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env ?? {}
35
+ const scope = env['CANVAS_PROJECT_SCOPE']
36
+ if (!scope) return
37
+ await ctx.scheduler.runAfter(0, webhooksDeliver, {
38
+ projectId: scope,
39
+ event,
40
+ tags: [`p:${scope}:project`],
41
+ })
42
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@elytracms/studio",
3
3
  "author": "finaldream <hi@finaldream.de>",
4
- "version": "0.0.13",
4
+ "version": "0.0.14",
5
5
  "private": false,
6
6
  "description": "The Elytra studio engine (UI + lib + routes + data-only Convex schema) as an importable package. Extraction staged in EXTRACTION.md (EC-223); Stages 1-2 = UI primitives + router-free lib leaves.",
7
7
  "license": "MIT",
@@ -112,7 +112,7 @@
112
112
  "lucide-react": "^1.17.0",
113
113
  "tailwind-merge": "^3.6.0",
114
114
  "zod": "^4.0.0",
115
- "@elytracms/core": "^0.0.13"
115
+ "@elytracms/core": "^0.0.14"
116
116
  },
117
117
  "peerDependencies": {
118
118
  "@tanstack/react-router": "^1.170.0",