@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 +9 -0
- package/convex/publishing.ts +4 -36
- package/convex/revalidate.ts +42 -0
- package/package.json +2 -2
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
|
})
|
package/convex/publishing.ts
CHANGED
|
@@ -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
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
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.
|
|
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.
|
|
115
|
+
"@elytracms/core": "^0.0.14"
|
|
116
116
|
},
|
|
117
117
|
"peerDependencies": {
|
|
118
118
|
"@tanstack/react-router": "^1.170.0",
|