@pramen/cms 0.0.46 → 0.0.47
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/dist/index.d.ts +29 -2
- package/dist/index.js +3 -0
- package/package.json +2 -2
- package/src/index.ts +31 -1
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,34 @@ import type { HandlerContext, Policy, FileRef, BootstrapFn } from "@pramen/serve
|
|
|
4
4
|
export interface FieldDefinition {
|
|
5
5
|
name: string;
|
|
6
6
|
label?: string;
|
|
7
|
-
type: "text" | "textarea" | "richtext" | "url" | "number" | "boolean" | "date" | "datetime"
|
|
7
|
+
type: "text" | "textarea" | "richtext" | "url" | "number" | "boolean" | "date" | "datetime"
|
|
8
|
+
/**
|
|
9
|
+
* A publication timestamp with three states: unset (hidden), a past time
|
|
10
|
+
* (published), or a future time (scheduled). The editor renders it as
|
|
11
|
+
* publish-now / schedule / unpublish rather than a bare date picker, because that is
|
|
12
|
+
* the decision an editor is actually making.
|
|
13
|
+
*
|
|
14
|
+
* Validated as a `datetime` on the wire, but NOT interchangeable with one: the
|
|
15
|
+
* `publish` control always writes a UTC instant with a `Z` suffix
|
|
16
|
+
* (`2026-08-20T12:00:00.000Z`), where the `datetime` control writes the picker's
|
|
17
|
+
* naive local string (`2026-08-20T14:00`). Both land in the same TEXT column and
|
|
18
|
+
* both pass validation, yet they sort and range-compare lexicographically against
|
|
19
|
+
* each other as if hours apart — so converting an existing `datetime` field to
|
|
20
|
+
* `publish` needs a backfill of the stored values, not just a type change.
|
|
21
|
+
*
|
|
22
|
+
* A collection has no page-style publish workflow, so this is how a row goes live.
|
|
23
|
+
* Scope the anonymous read policy to it with `$now()` from `@pramen/server`:
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* policy("cms_lectures", { read: { where: { publishedAt: { lte: $now() } } } })
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* That, and not `{ publishedAt: { isNull: false } }`, is the real access boundary.
|
|
30
|
+
* `isNull: false` matches a FUTURE timestamp too, so a row the editor scheduled for
|
|
31
|
+
* next week would be anonymously readable the moment it was saved — the scheduling
|
|
32
|
+
* affordance would be a UI label over no enforcement at all.
|
|
33
|
+
*/
|
|
34
|
+
| "publish" | "media" | "select" | "repeater" | "group";
|
|
8
35
|
required?: boolean;
|
|
9
36
|
default?: unknown;
|
|
10
37
|
/** repeater/group only — the nested fields. */
|
|
@@ -38,7 +65,7 @@ export type RichText = string | {
|
|
|
38
65
|
};
|
|
39
66
|
/** Map one FieldDefinition (as a const literal) to the TS type of its RENDERED value.
|
|
40
67
|
* Media resolves to `ResolvedMedia` (the assemble-time shape a component receives). */
|
|
41
|
-
export type FieldTsType<D extends FieldDefinition> = D["type"] extends "text" | "textarea" | "url" | "select" | "date" | "datetime" ? string : D["type"] extends "richtext" ? RichText : D["type"] extends "number" ? number : D["type"] extends "boolean" ? boolean : D["type"] extends "media" ? ResolvedMedia | null : D["type"] extends "group" ? InferBlockFields<NonNullable<D["fields"]>> : D["type"] extends "repeater" ? InferBlockFields<NonNullable<D["fields"]>>[] : unknown;
|
|
68
|
+
export type FieldTsType<D extends FieldDefinition> = D["type"] extends "text" | "textarea" | "url" | "select" | "date" | "datetime" | "publish" ? string : D["type"] extends "richtext" ? RichText : D["type"] extends "number" ? number : D["type"] extends "boolean" ? boolean : D["type"] extends "media" ? ResolvedMedia | null : D["type"] extends "group" ? InferBlockFields<NonNullable<D["fields"]>> : D["type"] extends "repeater" ? InferBlockFields<NonNullable<D["fields"]>>[] : unknown;
|
|
42
69
|
/** Infer the `fields` object type from a const `FieldDefinition[]`. Required fields are
|
|
43
70
|
* present; optional ones are `| undefined`. */
|
|
44
71
|
export type InferBlockFields<T extends readonly FieldDefinition[]> = {
|
package/dist/index.js
CHANGED
|
@@ -118,6 +118,7 @@ function tsTypeOf(f) {
|
|
|
118
118
|
case "select":
|
|
119
119
|
case "date":
|
|
120
120
|
case "datetime":
|
|
121
|
+
case "publish":
|
|
121
122
|
return "string";
|
|
122
123
|
case "richtext":
|
|
123
124
|
return "RichText";
|
|
@@ -321,6 +322,8 @@ export function validateFields(schema, values, path = "", opts = {}) {
|
|
|
321
322
|
throw new BadRequest(`field '${at}' must be a date (YYYY-MM-DD)`);
|
|
322
323
|
break;
|
|
323
324
|
case "datetime":
|
|
325
|
+
// `publish` is a datetime on the wire; only the editor control differs.
|
|
326
|
+
case "publish":
|
|
324
327
|
if (typeof v !== "string" || !isDateTimeString(v))
|
|
325
328
|
throw new BadRequest(`field '${at}' must be a date-time (ISO 8601)`);
|
|
326
329
|
break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pramen/cms",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.47",
|
|
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.47",
|
|
45
45
|
"xss": "^1.0.15"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
package/src/index.ts
CHANGED
|
@@ -61,6 +61,33 @@ export interface FieldDefinition {
|
|
|
61
61
|
| "boolean"
|
|
62
62
|
| "date"
|
|
63
63
|
| "datetime"
|
|
64
|
+
/**
|
|
65
|
+
* A publication timestamp with three states: unset (hidden), a past time
|
|
66
|
+
* (published), or a future time (scheduled). The editor renders it as
|
|
67
|
+
* publish-now / schedule / unpublish rather than a bare date picker, because that is
|
|
68
|
+
* the decision an editor is actually making.
|
|
69
|
+
*
|
|
70
|
+
* Validated as a `datetime` on the wire, but NOT interchangeable with one: the
|
|
71
|
+
* `publish` control always writes a UTC instant with a `Z` suffix
|
|
72
|
+
* (`2026-08-20T12:00:00.000Z`), where the `datetime` control writes the picker's
|
|
73
|
+
* naive local string (`2026-08-20T14:00`). Both land in the same TEXT column and
|
|
74
|
+
* both pass validation, yet they sort and range-compare lexicographically against
|
|
75
|
+
* each other as if hours apart — so converting an existing `datetime` field to
|
|
76
|
+
* `publish` needs a backfill of the stored values, not just a type change.
|
|
77
|
+
*
|
|
78
|
+
* A collection has no page-style publish workflow, so this is how a row goes live.
|
|
79
|
+
* Scope the anonymous read policy to it with `$now()` from `@pramen/server`:
|
|
80
|
+
*
|
|
81
|
+
* ```ts
|
|
82
|
+
* policy("cms_lectures", { read: { where: { publishedAt: { lte: $now() } } } })
|
|
83
|
+
* ```
|
|
84
|
+
*
|
|
85
|
+
* That, and not `{ publishedAt: { isNull: false } }`, is the real access boundary.
|
|
86
|
+
* `isNull: false` matches a FUTURE timestamp too, so a row the editor scheduled for
|
|
87
|
+
* next week would be anonymously readable the moment it was saved — the scheduling
|
|
88
|
+
* affordance would be a UI label over no enforcement at all.
|
|
89
|
+
*/
|
|
90
|
+
| "publish"
|
|
64
91
|
| "media"
|
|
65
92
|
| "select"
|
|
66
93
|
| "repeater"
|
|
@@ -107,7 +134,7 @@ export type RichText = string | { type: string; content?: unknown[] };
|
|
|
107
134
|
|
|
108
135
|
/** Map one FieldDefinition (as a const literal) to the TS type of its RENDERED value.
|
|
109
136
|
* Media resolves to `ResolvedMedia` (the assemble-time shape a component receives). */
|
|
110
|
-
export type FieldTsType<D extends FieldDefinition> = D["type"] extends "text" | "textarea" | "url" | "select" | "date" | "datetime"
|
|
137
|
+
export type FieldTsType<D extends FieldDefinition> = D["type"] extends "text" | "textarea" | "url" | "select" | "date" | "datetime" | "publish"
|
|
111
138
|
? string
|
|
112
139
|
: D["type"] extends "richtext"
|
|
113
140
|
? RichText
|
|
@@ -282,6 +309,7 @@ function tsTypeOf(f: FieldDefinition): string {
|
|
|
282
309
|
case "select":
|
|
283
310
|
case "date":
|
|
284
311
|
case "datetime":
|
|
312
|
+
case "publish":
|
|
285
313
|
return "string";
|
|
286
314
|
case "richtext":
|
|
287
315
|
return "RichText";
|
|
@@ -514,6 +542,8 @@ export function validateFields(schema: FieldDefinition[] | undefined | null, val
|
|
|
514
542
|
if (typeof v !== "string" || !isDateString(v)) throw new BadRequest(`field '${at}' must be a date (YYYY-MM-DD)`);
|
|
515
543
|
break;
|
|
516
544
|
case "datetime":
|
|
545
|
+
// `publish` is a datetime on the wire; only the editor control differs.
|
|
546
|
+
case "publish":
|
|
517
547
|
if (typeof v !== "string" || !isDateTimeString(v)) throw new BadRequest(`field '${at}' must be a date-time (ISO 8601)`);
|
|
518
548
|
break;
|
|
519
549
|
case "media":
|