@pramen/cms 0.0.51 → 0.0.53
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/README.md +33 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +14 -8
- package/package.json +2 -2
- package/src/index.ts +16 -7
package/README.md
CHANGED
|
@@ -489,6 +489,39 @@ become multilingual. `listCmsCapabilities` is what the editor reads; `listLocale
|
|
|
489
489
|
DATA query (which locales are actually authored), and the two differ while a locale is
|
|
490
490
|
declared but not yet used.
|
|
491
491
|
|
|
492
|
+
### Which store: D1 or the Durable Object
|
|
493
|
+
|
|
494
|
+
For a CMS behind a site, **D1 is usually the right store** — and it is the only one that
|
|
495
|
+
lets the CMS live inside an Astro site's own Worker, because D1 is a binding while a
|
|
496
|
+
Durable Object has to be exported from the worker entry.
|
|
497
|
+
|
|
498
|
+
What you give up, and it is worth knowing before you pick:
|
|
499
|
+
|
|
500
|
+
- **No atomic multi-statement mutations.** D1 has no interactive transactions, so
|
|
501
|
+
`transaction(fn)` runs `fn` as-is: a mutation that throws midway keeps what it already
|
|
502
|
+
wrote. Single-statement writes are still atomic. The Worker logs a specific error naming
|
|
503
|
+
the handler and how many statements had committed, so a partial write is diagnosable
|
|
504
|
+
rather than looking like an ordinary 500. Where the CMS does several writes in one
|
|
505
|
+
mutation — a revision plus the row it belongs to, a delete plus its revision purge — the
|
|
506
|
+
order is chosen so a failure leaves a benign state, but it is not a rollback.
|
|
507
|
+
- **No live queries.** Those need the Durable Object's socket host. The editor does not use
|
|
508
|
+
them, so this only matters if your own frontend subscribes.
|
|
509
|
+
- **Scheduled publish needs a Cron trigger.** There is no DO alarm to self-drain the outbox,
|
|
510
|
+
so a delayed task runs only when a Cron trigger calls `createPramen().scheduled`:
|
|
511
|
+
|
|
512
|
+
```jsonc
|
|
513
|
+
"triggers": { "crons": ["* * * * *"] }
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
Forget it and a scheduled publish simply never fires. The Worker now notices — when a
|
|
517
|
+
request-tail drain leaves a task queued for the future and no Cron drain has ever run, it
|
|
518
|
+
logs once naming the fix, and stops as soon as a Cron drain is seen.
|
|
519
|
+
- **One shared database, no tenant column.** A non-`main` tenant on D1 is refused unless you
|
|
520
|
+
set `PRAMEN_D1_ALLOW_MULTITENANT=true`, because the rows would commingle.
|
|
521
|
+
|
|
522
|
+
Pick the Durable Object store when a mutation must be atomic, when you want live queries, or
|
|
523
|
+
when tenants must be isolated by construction.
|
|
524
|
+
|
|
492
525
|
## Limitations
|
|
493
526
|
|
|
494
527
|
- **Block `fields` are opaque JSON**, so pramen's row/cell-level ACL and relational queries
|
package/dist/index.d.ts
CHANGED
|
@@ -908,6 +908,20 @@ export declare function createCmsHandlers(opts?: CmsHandlerOpts): {
|
|
|
908
908
|
ok: true;
|
|
909
909
|
}>;
|
|
910
910
|
listContentTypes: import("@pramen/server").Handler<unknown, Record<string, unknown>[]>;
|
|
911
|
+
/** Content-type slugs + names, PUBLIC. The editor-facing `listContentTypes` above is
|
|
912
|
+
* viewer-gated, which a BUILD cannot satisfy: `@pramen/cms-astro`'s `collections: "auto"`
|
|
913
|
+
* runs in `astro:config:setup`, where there is no editor session and shipping a token to
|
|
914
|
+
* CI just to list type names would be the wrong trade.
|
|
915
|
+
*
|
|
916
|
+
* Nothing new is exposed. `cmsPolicies().public` already grants anonymous read of
|
|
917
|
+
* `cms_content_types` ("slugs/names are structural, not sensitive"), and
|
|
918
|
+
* `listPublishedPages` already returns the content-type slug of every published page.
|
|
919
|
+
* The projection is deliberately narrow — slug and name only, never the regions or
|
|
920
|
+
* field schema, which describe the editing surface rather than the published site. */
|
|
921
|
+
listPublicContentTypes: import("@pramen/server").Handler<unknown, {
|
|
922
|
+
slug: string;
|
|
923
|
+
name: string;
|
|
924
|
+
}[]>;
|
|
911
925
|
getContentType: import("@pramen/server").Handler<{
|
|
912
926
|
id: string;
|
|
913
927
|
}, Record<string, unknown>>;
|
|
@@ -1262,6 +1276,20 @@ export declare const cmsHandlers: {
|
|
|
1262
1276
|
ok: true;
|
|
1263
1277
|
}>;
|
|
1264
1278
|
listContentTypes: import("@pramen/server").Handler<unknown, Record<string, unknown>[]>;
|
|
1279
|
+
/** Content-type slugs + names, PUBLIC. The editor-facing `listContentTypes` above is
|
|
1280
|
+
* viewer-gated, which a BUILD cannot satisfy: `@pramen/cms-astro`'s `collections: "auto"`
|
|
1281
|
+
* runs in `astro:config:setup`, where there is no editor session and shipping a token to
|
|
1282
|
+
* CI just to list type names would be the wrong trade.
|
|
1283
|
+
*
|
|
1284
|
+
* Nothing new is exposed. `cmsPolicies().public` already grants anonymous read of
|
|
1285
|
+
* `cms_content_types` ("slugs/names are structural, not sensitive"), and
|
|
1286
|
+
* `listPublishedPages` already returns the content-type slug of every published page.
|
|
1287
|
+
* The projection is deliberately narrow — slug and name only, never the regions or
|
|
1288
|
+
* field schema, which describe the editing surface rather than the published site. */
|
|
1289
|
+
listPublicContentTypes: import("@pramen/server").Handler<unknown, {
|
|
1290
|
+
slug: string;
|
|
1291
|
+
name: string;
|
|
1292
|
+
}[]>;
|
|
1265
1293
|
getContentType: import("@pramen/server").Handler<{
|
|
1266
1294
|
id: string;
|
|
1267
1295
|
}, Record<string, unknown>>;
|
package/dist/index.js
CHANGED
|
@@ -1273,6 +1273,20 @@ export function createCmsHandlers(opts = {}) {
|
|
|
1273
1273
|
return { ok: true };
|
|
1274
1274
|
}, { ...reviewer, ...mediaIdInput }),
|
|
1275
1275
|
listContentTypes: query((ctx) => cdb(ctx).find({ from: "cms_content_types", orderBy: { column: "name" } }), viewer),
|
|
1276
|
+
/** Content-type slugs + names, PUBLIC. The editor-facing `listContentTypes` above is
|
|
1277
|
+
* viewer-gated, which a BUILD cannot satisfy: `@pramen/cms-astro`'s `collections: "auto"`
|
|
1278
|
+
* runs in `astro:config:setup`, where there is no editor session and shipping a token to
|
|
1279
|
+
* CI just to list type names would be the wrong trade.
|
|
1280
|
+
*
|
|
1281
|
+
* Nothing new is exposed. `cmsPolicies().public` already grants anonymous read of
|
|
1282
|
+
* `cms_content_types` ("slugs/names are structural, not sensitive"), and
|
|
1283
|
+
* `listPublishedPages` already returns the content-type slug of every published page.
|
|
1284
|
+
* The projection is deliberately narrow — slug and name only, never the regions or
|
|
1285
|
+
* field schema, which describe the editing surface rather than the published site. */
|
|
1286
|
+
listPublicContentTypes: query(async (ctx) => {
|
|
1287
|
+
const rows = await cdb(ctx).find({ from: "cms_content_types", orderBy: { column: "name" } });
|
|
1288
|
+
return rows.map((r) => ({ slug: String(r.slug), name: String(r.name ?? r.slug) }));
|
|
1289
|
+
}),
|
|
1276
1290
|
getContentType: query(async (ctx, input) => {
|
|
1277
1291
|
const rows = await cdb(ctx).find({ from: "cms_content_types", where: { id: input.id }, limit: 1 });
|
|
1278
1292
|
return rows[0] ?? null;
|
|
@@ -1883,8 +1897,6 @@ export function createCmsHandlers(opts = {}) {
|
|
|
1883
1897
|
// has no notion of `x-pramen-store`. Minting on the D1 store therefore produces a
|
|
1884
1898
|
// link that 404s forever while the editor reports success — refuse instead of
|
|
1885
1899
|
// handing out a token that cannot work.
|
|
1886
|
-
if (ctx.store === "d1")
|
|
1887
|
-
throw new PramenError("page preview is not available on the D1 store (redemption requires the Durable Object)", 503, "unavailable");
|
|
1888
1900
|
const db = cdb(ctx);
|
|
1889
1901
|
// Read the page through the ACL first: minting a link is granting access to it, so a
|
|
1890
1902
|
// caller who cannot read the page must not be able to mint a link that can.
|
|
@@ -2959,12 +2971,6 @@ export function createCollectionHandlers(collections, opts = {}) {
|
|
|
2959
2971
|
const secret = previewSecret(ctx.env);
|
|
2960
2972
|
if (!secret)
|
|
2961
2973
|
throw previewUnconfigured(); // fail closed — never mint a forgeable link
|
|
2962
|
-
// Redemption always reaches a Durable Object (callPrivileged -> PRAMEN.get) and has no
|
|
2963
|
-
// notion of `x-pramen-store`, so a link minted on D1 would 404 forever while the
|
|
2964
|
-
// editor reported success.
|
|
2965
|
-
if (ctx.store === "d1") {
|
|
2966
|
-
throw new PramenError("collection preview is not available on the D1 store (redemption requires the Durable Object)", 503, "unavailable");
|
|
2967
|
-
}
|
|
2968
2974
|
const row = await loadRow(cdb(ctx), c, input.id);
|
|
2969
2975
|
const ttl = Math.max(60, Math.min(input.expiresIn ?? previewTtl, 30 * 24 * 3600));
|
|
2970
2976
|
const exp = Math.floor(Date.now() / 1000) + ttl;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pramen/cms",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.53",
|
|
4
4
|
"description": "Optional block/page builder for pramen — Drupal-Paragraphs-style typed blocks in named regions, reusable blocks, scheduled publishing, built entirely from pramen primitives.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"access": "public"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@pramen/server": "0.0.
|
|
44
|
+
"@pramen/server": "0.0.53"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"react": ">=18"
|
package/src/index.ts
CHANGED
|
@@ -1724,6 +1724,21 @@ export function createCmsHandlers(opts: CmsHandlerOpts = {}) {
|
|
|
1724
1724
|
|
|
1725
1725
|
listContentTypes: query((ctx) => cdb(ctx).find({ from: "cms_content_types", orderBy: { column: "name" } }), viewer),
|
|
1726
1726
|
|
|
1727
|
+
/** Content-type slugs + names, PUBLIC. The editor-facing `listContentTypes` above is
|
|
1728
|
+
* viewer-gated, which a BUILD cannot satisfy: `@pramen/cms-astro`'s `collections: "auto"`
|
|
1729
|
+
* runs in `astro:config:setup`, where there is no editor session and shipping a token to
|
|
1730
|
+
* CI just to list type names would be the wrong trade.
|
|
1731
|
+
*
|
|
1732
|
+
* Nothing new is exposed. `cmsPolicies().public` already grants anonymous read of
|
|
1733
|
+
* `cms_content_types` ("slugs/names are structural, not sensitive"), and
|
|
1734
|
+
* `listPublishedPages` already returns the content-type slug of every published page.
|
|
1735
|
+
* The projection is deliberately narrow — slug and name only, never the regions or
|
|
1736
|
+
* field schema, which describe the editing surface rather than the published site. */
|
|
1737
|
+
listPublicContentTypes: query(async (ctx) => {
|
|
1738
|
+
const rows = await cdb(ctx).find({ from: "cms_content_types", orderBy: { column: "name" } });
|
|
1739
|
+
return rows.map((r) => ({ slug: String(r.slug), name: String(r.name ?? r.slug) }));
|
|
1740
|
+
}),
|
|
1741
|
+
|
|
1727
1742
|
getContentType: query(async (ctx, input: { id: string }) => {
|
|
1728
1743
|
const rows = await cdb(ctx).find({ from: "cms_content_types", where: { id: input.id }, limit: 1 });
|
|
1729
1744
|
return rows[0] ?? null;
|
|
@@ -2314,7 +2329,7 @@ export function createCmsHandlers(opts: CmsHandlerOpts = {}) {
|
|
|
2314
2329
|
// has no notion of `x-pramen-store`. Minting on the D1 store therefore produces a
|
|
2315
2330
|
// link that 404s forever while the editor reports success — refuse instead of
|
|
2316
2331
|
// handing out a token that cannot work.
|
|
2317
|
-
|
|
2332
|
+
|
|
2318
2333
|
const db = cdb(ctx);
|
|
2319
2334
|
// Read the page through the ACL first: minting a link is granting access to it, so a
|
|
2320
2335
|
// caller who cannot read the page must not be able to mint a link that can.
|
|
@@ -3561,12 +3576,6 @@ export function createCollectionHandlers(collections: readonly CollectionDef[],
|
|
|
3561
3576
|
needs(c, "preview");
|
|
3562
3577
|
const secret = previewSecret(ctx.env);
|
|
3563
3578
|
if (!secret) throw previewUnconfigured(); // fail closed — never mint a forgeable link
|
|
3564
|
-
// Redemption always reaches a Durable Object (callPrivileged -> PRAMEN.get) and has no
|
|
3565
|
-
// notion of `x-pramen-store`, so a link minted on D1 would 404 forever while the
|
|
3566
|
-
// editor reported success.
|
|
3567
|
-
if (ctx.store === "d1") {
|
|
3568
|
-
throw new PramenError("collection preview is not available on the D1 store (redemption requires the Durable Object)", 503, "unavailable");
|
|
3569
|
-
}
|
|
3570
3579
|
const row = await loadRow(cdb(ctx), c, input.id);
|
|
3571
3580
|
const ttl = Math.max(60, Math.min(input.expiresIn ?? previewTtl, 30 * 24 * 3600));
|
|
3572
3581
|
const exp = Math.floor(Date.now() / 1000) + ttl;
|