@beechcms/core 0.4.0-preview.4 → 0.4.0-preview.6
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/package.json +4 -1
- package/src/define-seed.ts +0 -5
- package/src/engine.ts +0 -465
- package/src/index.ts +0 -19
- package/src/policies.test.ts +0 -127
- package/src/policies.ts +0 -32
- package/src/richtext-render.ts +0 -87
- package/src/richtext.ts +0 -16
- package/src/seeds.ts +0 -28
- package/src/slug-utils.ts +0 -33
- package/src/types.ts +0 -144
- package/src/validation.ts +0 -667
- package/tsconfig.json +0 -14
- package/tsconfig.tsbuildinfo +0 -1
package/src/richtext-render.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { generateHTML } from '@tiptap/html'
|
|
2
|
-
import type { JSONContent } from '@tiptap/core'
|
|
3
|
-
import Highlight from '@tiptap/extension-highlight'
|
|
4
|
-
import Image from '@tiptap/extension-image'
|
|
5
|
-
import Link from '@tiptap/extension-link'
|
|
6
|
-
import Subscript from '@tiptap/extension-subscript'
|
|
7
|
-
import Superscript from '@tiptap/extension-superscript'
|
|
8
|
-
import { Table, TableCell, TableHeader, TableRow } from '@tiptap/extension-table'
|
|
9
|
-
import TextAlign from '@tiptap/extension-text-align'
|
|
10
|
-
import { Mathematics } from '@tiptap/extension-mathematics'
|
|
11
|
-
import StarterKit from '@tiptap/starter-kit'
|
|
12
|
-
|
|
13
|
-
import { isRichtextEnvelopeV1 } from './richtext.js'
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Allinea l'output HTML allo schema TipTap usato dall'editor dashboard.
|
|
17
|
-
* Mantieni sincronizzato con `apps/dashboard/src/features/richtext-editor/extensions/build-editor-extensions.ts`.
|
|
18
|
-
*/
|
|
19
|
-
function createRichTextHtmlExtensions() {
|
|
20
|
-
return [
|
|
21
|
-
StarterKit.configure({
|
|
22
|
-
link: false,
|
|
23
|
-
codeBlock: {
|
|
24
|
-
HTMLAttributes: {
|
|
25
|
-
class: 'richtext-code-block',
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
}),
|
|
29
|
-
Link.configure({
|
|
30
|
-
openOnClick: false,
|
|
31
|
-
autolink: true,
|
|
32
|
-
defaultProtocol: 'https',
|
|
33
|
-
}),
|
|
34
|
-
Mathematics.configure({
|
|
35
|
-
katexOptions: {
|
|
36
|
-
throwOnError: false,
|
|
37
|
-
},
|
|
38
|
-
}),
|
|
39
|
-
Highlight,
|
|
40
|
-
Superscript,
|
|
41
|
-
Subscript,
|
|
42
|
-
Image.configure({
|
|
43
|
-
allowBase64: false,
|
|
44
|
-
}),
|
|
45
|
-
TextAlign.configure({
|
|
46
|
-
types: ['heading', 'paragraph'],
|
|
47
|
-
}),
|
|
48
|
-
Table.configure({
|
|
49
|
-
resizable: false,
|
|
50
|
-
}),
|
|
51
|
-
TableRow,
|
|
52
|
-
TableHeader,
|
|
53
|
-
TableCell,
|
|
54
|
-
]
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Accetta JSON TipTap (`{ type: 'doc', ... }`), envelope v1, o stringa HTML legacy.
|
|
59
|
-
*/
|
|
60
|
-
export function normalizeRichtextForRender(value: unknown): JSONContent | string | null {
|
|
61
|
-
if (value == null || value === '') return null
|
|
62
|
-
if (typeof value === 'string') return value
|
|
63
|
-
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
64
|
-
const o = value as Record<string, unknown>
|
|
65
|
-
if (isRichtextEnvelopeV1(value)) {
|
|
66
|
-
return o.doc as JSONContent
|
|
67
|
-
}
|
|
68
|
-
if (o.type === 'doc') {
|
|
69
|
-
return value as JSONContent
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return null
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Render deterministico JSON → HTML (per display, anteprime, API pubblica).
|
|
77
|
-
* Per stringhe HTML legacy restituisce la stringa sanificata come pass-through (nessun parse TipTap).
|
|
78
|
-
*/
|
|
79
|
-
export function renderRichText(value: unknown): string {
|
|
80
|
-
const normalized = normalizeRichtextForRender(value)
|
|
81
|
-
if (normalized == null) return ''
|
|
82
|
-
if (typeof normalized === 'string') {
|
|
83
|
-
return normalized
|
|
84
|
-
}
|
|
85
|
-
const extensions = createRichTextHtmlExtensions()
|
|
86
|
-
return generateHTML(normalized, extensions)
|
|
87
|
-
}
|
package/src/richtext.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Convenzione storage richtext TipTap nel Content Engine.
|
|
3
|
-
* @see docs/Sprints/tiptap-elevation.md
|
|
4
|
-
*/
|
|
5
|
-
export const RICHTEXT_SCHEMA_VERSION = 1 as const
|
|
6
|
-
|
|
7
|
-
export type RichtextEnvelopeV1 = {
|
|
8
|
-
schemaVersion: typeof RICHTEXT_SCHEMA_VERSION
|
|
9
|
-
doc: Record<string, unknown>
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function isRichtextEnvelopeV1(value: unknown): value is RichtextEnvelopeV1 {
|
|
13
|
-
if (typeof value !== 'object' || value === null || Array.isArray(value)) return false
|
|
14
|
-
const o = value as Record<string, unknown>
|
|
15
|
-
return o.schemaVersion === RICHTEXT_SCHEMA_VERSION && typeof o.doc === 'object' && o.doc !== null
|
|
16
|
-
}
|
package/src/seeds.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Seed Registry: configurazione degli schemi di contenuto.
|
|
3
|
-
* In v0.4.0 ogni Seed genera una tabella `content_{slug}` con colonne reali.
|
|
4
|
-
* `branch.alias` è il nome della colonna SQL.
|
|
5
|
-
*/
|
|
6
|
-
import type { Seed } from './types.js'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Registro globale dei Seed.
|
|
10
|
-
* In produzione, questo viene popolato dalle definizioni dell'utente.
|
|
11
|
-
*/
|
|
12
|
-
export const SEED_REGISTRY: Record<string, Seed> = {}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Registra un set di Seed nel registro globale.
|
|
16
|
-
*/
|
|
17
|
-
export function registerSeeds(seeds: Seed[]) {
|
|
18
|
-
seeds.forEach(seed => {
|
|
19
|
-
SEED_REGISTRY[seed.slug] = seed
|
|
20
|
-
})
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Ritorna il Seed per lo slug dato, o null se non esiste.
|
|
25
|
-
*/
|
|
26
|
-
export function getSeed(slug: string): Seed | null {
|
|
27
|
-
return SEED_REGISTRY[slug] ?? null
|
|
28
|
-
}
|
package/src/slug-utils.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Utility per la gestione degli slug.
|
|
3
|
-
* Condivisa tra Dashboard e API pubbliche.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Converte una stringa in uno slug URL-safe.
|
|
8
|
-
* Limita la lunghezza a 15 caratteri come richiesto.
|
|
9
|
-
*/
|
|
10
|
-
export function slugify(value: string): string {
|
|
11
|
-
return value
|
|
12
|
-
.normalize('NFKD') // Normalizza caratteri accentati
|
|
13
|
-
.replace(/[^\w\s-]/g, '') // Rimuove tutto ciò che non è alfanumerico (accetta underscore temporaneamente)
|
|
14
|
-
.trim()
|
|
15
|
-
.toLowerCase()
|
|
16
|
-
.replace(/[\s_-]+/g, '-') // Sostituisce spazi, underscore e trattini multipli con singolo trattino
|
|
17
|
-
.replace(/^-+|-+$/g, '') // Rimuove trattini all'inizio o alla fine
|
|
18
|
-
.slice(0, 15) // Limita a 15 caratteri
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Genera uno slug da un input (es. titolo o nome) o un fallback UUID.
|
|
23
|
-
*/
|
|
24
|
-
export function generateEntrySlug(input: { slug?: string; title?: unknown; name?: unknown }): string {
|
|
25
|
-
const candidate =
|
|
26
|
-
(typeof input.slug === 'string' && input.slug) ||
|
|
27
|
-
(typeof input.title === 'string' && input.title) ||
|
|
28
|
-
(typeof input.name === 'string' && input.name) ||
|
|
29
|
-
Math.random().toString(36).slice(2, 10) // Fallback compatto se crypto non disponibile (o slice 8)
|
|
30
|
-
|
|
31
|
-
const normalized = slugify(candidate)
|
|
32
|
-
return normalized || Math.random().toString(36).slice(2, 10)
|
|
33
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
export type BranchType = 'text' | 'number' | 'boolean' | 'json' | 'date' | 'richtext' | 'file'
|
|
2
|
-
|
|
3
|
-
/** Branch: definizione di una proprietà. alias = nome colonna SQL. */
|
|
4
|
-
export interface Branch {
|
|
5
|
-
/** Alias human-readable, usato nel payload API e come nome colonna SQL nella tabella dedicata */
|
|
6
|
-
alias: string
|
|
7
|
-
/** Etichetta per la UI */
|
|
8
|
-
label: string
|
|
9
|
-
/** Tipo del valore */
|
|
10
|
-
type: BranchType
|
|
11
|
-
/**
|
|
12
|
-
* Variante semantica opzionale del campo per UI/validazione.
|
|
13
|
-
* `asset-list` su `file` multiplo abilita la gestione galleria.
|
|
14
|
-
*/
|
|
15
|
-
format?: 'plain' | 'markdown' | 'html' | 'date' | 'datetime' | 'asset-list'
|
|
16
|
-
/**
|
|
17
|
-
* Cardinalità opzionale per campi media:
|
|
18
|
-
* - false/undefined: singolo asset (string URL)
|
|
19
|
-
* - true: lista asset (string[] URL)
|
|
20
|
-
*/
|
|
21
|
-
multiple?: boolean
|
|
22
|
-
/**
|
|
23
|
-
* Vocabolario predefinito per campi tag/select/multiselect.
|
|
24
|
-
* Lista statica definita nel Seed (non salvata nel DB).
|
|
25
|
-
*/
|
|
26
|
-
options?: string[]
|
|
27
|
-
/** Campo obbligatorio in creazione — genera NOT NULL in generateCreateTable */
|
|
28
|
-
requiredOnCreate?: boolean
|
|
29
|
-
/** Campo obbligatorio in update */
|
|
30
|
-
requiredOnUpdate?: boolean
|
|
31
|
-
/**
|
|
32
|
-
* Policy di accesso e trattamento del campo.
|
|
33
|
-
* Tutti i valori sono opzionali — `resolvePolicies(branch)` fornisce i default.
|
|
34
|
-
*/
|
|
35
|
-
policies?: {
|
|
36
|
-
/** Come il valore viene memorizzato. Default: 'plain' */
|
|
37
|
-
privacy?: 'plain' | 'hash' | 'encrypt'
|
|
38
|
-
/** Come il valore viene restituito nelle risposte API. Default: 'full' */
|
|
39
|
-
visibility?: 'full' | 'masked' | 'hidden'
|
|
40
|
-
/** Il campo è incluso nelle query di ricerca full-text. Default: true */
|
|
41
|
-
search?: boolean
|
|
42
|
-
/** Il campo è disponibile come colonna di filtro nella dashboard. Default: true */
|
|
43
|
-
filter?: boolean
|
|
44
|
-
/** Il campo è disponibile come colonna di ordinamento nella dashboard. Default: true */
|
|
45
|
-
sort?: boolean
|
|
46
|
-
/** Il campo è incluso nelle risposte della Public API. Default: true */
|
|
47
|
-
public?: boolean
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** Dashboard-specific config embedded in a Seed. All fields optional — defaults applied by the dashboard. */
|
|
52
|
-
export interface DashboardSeedConfig {
|
|
53
|
-
/** Lucide icon name (string, resolved to component client-side). Default: 'Folder' */
|
|
54
|
-
icon?: string
|
|
55
|
-
/** Sidebar group label. Ungrouped seeds share a single 'Contents' section. */
|
|
56
|
-
group?: string
|
|
57
|
-
/** Sort order within the group. Lower = higher. Default: 99 */
|
|
58
|
-
order?: number
|
|
59
|
-
/** Hide from sidebar navigation. Default: false */
|
|
60
|
-
hidden?: boolean
|
|
61
|
-
/** Tooltip description shown in the sidebar. */
|
|
62
|
-
description?: string
|
|
63
|
-
/** UI feature toggles. All default to true unless specified. */
|
|
64
|
-
features?: {
|
|
65
|
-
search?: boolean
|
|
66
|
-
filter?: boolean
|
|
67
|
-
export?: boolean
|
|
68
|
-
bulkDelete?: boolean
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** Seed: definizione dello schema di un tipo di contenuto */
|
|
73
|
-
export interface Seed {
|
|
74
|
-
/** Slug identificativo — anche nome tabella: `content_{slug}` */
|
|
75
|
-
slug: string
|
|
76
|
-
/** Etichetta singolare per la UI */
|
|
77
|
-
label: string
|
|
78
|
-
/** Etichetta plurale per la UI. Se assente si usa `label`. */
|
|
79
|
-
labelPlural?: string
|
|
80
|
-
/** Abilita lettura dalla Public API (`GET /api/v1/public/:seed`). Default: false */
|
|
81
|
-
allowPublicRead?: boolean
|
|
82
|
-
/** Abilita creazione dalla Public API (`POST /api/v1/public/:seed/add`). Default: false */
|
|
83
|
-
allowPublicPost?: boolean
|
|
84
|
-
/** Abilita modifica dalla Public API (`PUT /api/v1/public/:seed/edit/:id`). Default: false */
|
|
85
|
-
allowPublicEdit?: boolean
|
|
86
|
-
/**
|
|
87
|
-
* Abilita la feature "bozza in attesa" per questo seed.
|
|
88
|
-
* Quando true, genera tabella `content_{slug}_drafts` e abilita gli endpoint `/draft`.
|
|
89
|
-
* Default: false
|
|
90
|
-
*/
|
|
91
|
-
allowDrafts?: boolean
|
|
92
|
-
/**
|
|
93
|
-
* Alias del branch usato come nome leggibile dell'entry (es. "title", "name", "author").
|
|
94
|
-
* Obbligatorio — le UI lo usano per display senza euristica.
|
|
95
|
-
*/
|
|
96
|
-
displayNameAlias: string
|
|
97
|
-
/** Lista dei campi (Branch) */
|
|
98
|
-
branches: Branch[]
|
|
99
|
-
/** Optional dashboard-specific UI config. Ignored by the Botanical Engine. */
|
|
100
|
-
dashboard?: DashboardSeedConfig
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// ---- Query types (usati da buildSelectQuery nel Botanical Engine) ----
|
|
104
|
-
|
|
105
|
-
export type FilterOperator =
|
|
106
|
-
| 'eq'
|
|
107
|
-
| 'gt'
|
|
108
|
-
| 'gte'
|
|
109
|
-
| 'lt'
|
|
110
|
-
| 'lte'
|
|
111
|
-
| 'contains'
|
|
112
|
-
| 'is_empty'
|
|
113
|
-
| 'is_not_empty'
|
|
114
|
-
|
|
115
|
-
export type FilterType = 'text' | 'number' | 'date' | 'boolean' | 'tags' | 'select' | 'system'
|
|
116
|
-
|
|
117
|
-
export interface FilterCondition {
|
|
118
|
-
op: FilterOperator
|
|
119
|
-
value: string | number | boolean | null
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export interface FilterGroup {
|
|
123
|
-
/** Nome colonna: system column (id/slug/status/created_at/updated_at) o branch alias */
|
|
124
|
-
column: string
|
|
125
|
-
type: FilterType
|
|
126
|
-
conditions: FilterCondition[]
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export interface SelectOptions {
|
|
130
|
-
filters?: FilterGroup[]
|
|
131
|
-
orderBy?: { column: string; dir: 'ASC' | 'DESC' }
|
|
132
|
-
pagination?: { limit: number; offset: number }
|
|
133
|
-
/** Filtra per status. null = nessun filtro status. */
|
|
134
|
-
status?: string | null
|
|
135
|
-
/** Full-text search — usa FTS5 se il seed ha branch richtext indicizzabili */
|
|
136
|
-
search?: string
|
|
137
|
-
/** Proiezione colonne. Vuoto = SELECT * */
|
|
138
|
-
fields?: string[]
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export interface ParameterizedQuery {
|
|
142
|
-
sql: string
|
|
143
|
-
bindings: (string | number | boolean | null)[]
|
|
144
|
-
}
|