@tekki-forge/core 0.1.0 → 0.3.0

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/app.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import type { AnyField } from "./fields.js";
2
- import type { DashboardWidget } from "./dashboard.js";
2
+ import type { DashboardConfig } from "./dashboard.js";
3
+ import type { EntityIcon } from "./icons.js";
3
4
  import type { PluginDefinition } from "./plugin.js";
5
+ import type { SiteConfig } from "./site.js";
4
6
  /**
5
7
  * A permission rule lists who may perform an operation. Tokens:
6
8
  * - a role name, e.g. "admin", "manager", "member"
@@ -113,8 +115,8 @@ export interface EntityDefinition {
113
115
  storageName?: string;
114
116
  /** Field used as the human-readable label of a record. */
115
117
  display?: string;
116
- /** lucide icon name used in navigation. */
117
- icon?: string;
118
+ /** Icon in navigation: one of ENTITY_ICONS, e.g. "receipt", "users", "truck", "ticket", "building". */
119
+ icon?: EntityIcon;
118
120
  /** Show in the sidebar. Default true. */
119
121
  nav?: boolean;
120
122
  description?: string;
@@ -142,14 +144,15 @@ export interface MultiTenantConfig {
142
144
  enabled: boolean;
143
145
  model?: "organization";
144
146
  }
145
- export type UIStyle = "simple" | "shadow" | "neumorphism" | "glassmorphism" | "skeuomorphism" | "material" | "brutalist" | "editorial" | "aurora" | "terminal" | "clay";
147
+ export type UIStyle = "simple" | "shadow" | "neumorphism" | "glassmorphism" | "skeuomorphism" | "material" | "brutalist" | "editorial" | "aurora" | "terminal" | "clay" | "corporate" | "soft" | "luxury" | "retro" | "neon" | "swiss" | "nature" | "cyber" | "minimal" | "playful";
146
148
  /** Curated looks (style + colours + font + corners + mode + layout). Explicit theme keys override them. */
147
- export type ThemePreset = "midnight" | "sandstone" | "ocean" | "forest" | "sunset" | "graphite" | "candy" | "paper" | "phosphor";
149
+ export type ThemePreset = "midnight" | "sandstone" | "ocean" | "forest" | "sunset" | "graphite" | "candy" | "paper" | "phosphor" | "boardroom" | "sakura" | "noir" | "arcade" | "ember" | "atelier" | "matcha" | "circuit" | "lagoon" | "orchid" | "harbor" | "blueprint";
148
150
  /** Typeface pairing (headings + body), loaded from Google Fonts at runtime; "system" loads nothing. */
149
- export type ThemeFont = "inter" | "geist" | "plex" | "grotesk" | "serif" | "rounded" | "mono" | "system";
151
+ export type ThemeFont = "inter" | "geist" | "plex" | "grotesk" | "serif" | "rounded" | "mono" | "system" | "elegant" | "pixel" | "tech" | "playful";
150
152
  export interface ThemeConfig {
151
153
  /**
152
- * Start from a curated look: midnight, sandstone, ocean, forest, sunset, graphite, candy, paper, phosphor.
154
+ * Start from a curated look: midnight, sandstone, ocean, forest, sunset, graphite, candy, paper, phosphor,
155
+ * boardroom, sakura, noir, arcade, ember, atelier, matcha, circuit, lagoon, orchid, harbor, blueprint.
153
156
  * Any other key set here overrides the preset's value.
154
157
  */
155
158
  preset?: ThemePreset;
@@ -166,6 +169,16 @@ export interface ThemeConfig {
166
169
  * - aurora soft animated gradient backdrop from your colours, glassy cards
167
170
  * - terminal dark-first, monospace, phosphor accents
168
171
  * - clay claymorphism: puffy rounded surfaces with inner highlights
172
+ * - corporate sober and dense, enterprise blue-grey, crisp tables
173
+ * - soft pastel, rounded and airy, gentle shadows
174
+ * - luxury deep tones, gold accents, serif display, thin rules (dark by default)
175
+ * - retro 80s/90s desktop: bevelled windows, pixel headings, readable body
176
+ * - neon dark, glowing accents and crisp borders (glow off for reduced motion / forced colours)
177
+ * - swiss strict grid, big type, red accent, no shadows
178
+ * - nature earthy tones, organic shapes, paper texture
179
+ * - cyber angled corners, HUD accents, dark-first
180
+ * - minimal almost no chrome: whitespace and hairlines
181
+ * - playful bold colours, hand-drawn edges, bouncy hover
169
182
  */
170
183
  style?: UIStyle;
171
184
  colors?: {
@@ -181,7 +194,8 @@ export interface ThemeConfig {
181
194
  radius?: "none" | "small" | "medium" | "large" | "full";
182
195
  /**
183
196
  * Typeface pairing: inter, geist, plex (IBM Plex), grotesk (Space Grotesk headings), serif (Fraunces headings),
184
- * rounded (Nunito), mono (JetBrains Mono), system (no web font). Default: the style's own, else system.
197
+ * rounded (Nunito), mono (JetBrains Mono), elegant (Cormorant Garamond headings), pixel (Silkscreen headings),
198
+ * tech (Chakra Petch headings), playful (Fredoka + Nunito), system (no web font). Default: the style's own, else system.
185
199
  */
186
200
  font?: ThemeFont;
187
201
  /** Navigation: a sidebar (default), a top bar, or a compact icon rail (tooltips on hover/focus, expandable with its toggle). */
@@ -192,6 +206,20 @@ export interface ThemeConfig {
192
206
  logo?: string;
193
207
  /** Short line shown on the sign-in pages' brand panel. */
194
208
  tagline?: string;
209
+ /** Page background made from the theme colours (CSS only, static under reduced motion). Default: plain (the style's own). */
210
+ background?: "plain" | "gradient" | "mesh" | "dots" | "grid" | "noise";
211
+ /** Card treatment. Default: the style's own. */
212
+ cards?: "flat" | "bordered" | "elevated" | "glass";
213
+ /** Button shape and fill. Default: the style's own. */
214
+ buttons?: "solid" | "soft" | "outline" | "pill";
215
+ /** Page and card transitions: none, subtle (default) or lively. Always none under prefers-reduced-motion. */
216
+ motion?: "none" | "subtle" | "lively";
217
+ /** Sidebar surface: light (the regular surface), dark, or brand (the primary colour). Default: the style's own. */
218
+ sidebar?: "light" | "dark" | "brand";
219
+ /** Navigation icons: outline (default) or duotone (tinted fill). */
220
+ iconStyle?: "outline" | "duotone";
221
+ /** Colours of charts, exposed as --forge-chart-1 … --forge-chart-8: theme (default), vivid, pastel or mono. */
222
+ chartPalette?: "theme" | "vivid" | "pastel" | "mono";
195
223
  }
196
224
  export interface NotificationRule {
197
225
  /**
@@ -235,16 +263,12 @@ export interface AppDefinition {
235
263
  };
236
264
  /** Client SDKs to generate. Default: TypeScript only (packages/sdk). */
237
265
  sdk?: SdkConfig;
238
- admin?: {
239
- enabled?: boolean;
240
- };
266
+ admin?: AdminConfig;
241
267
  theme?: ThemeConfig;
242
268
  /** Plugins such as storage(), jobs(), webhooks(), payments(). */
243
269
  plugins?: readonly PluginDefinition[];
244
- /** Widgets on the home page (metric, chart, list). Default: a count per entity. */
245
- dashboard?: {
246
- widgets: readonly DashboardWidget[];
247
- };
270
+ /** Widgets on the home page (metric, chart, list), optionally in tabs. Default: a count per entity. */
271
+ dashboard?: DashboardConfig;
248
272
  /** In-app (and optional email) notifications triggered by record events. */
249
273
  notifications?: readonly NotificationRule[];
250
274
  /**
@@ -260,6 +284,26 @@ export interface AppDefinition {
260
284
  * `true` builds one from the app's name, description, tagline and entities; an object overrides any part.
261
285
  */
262
286
  landing?: boolean | LandingConfig;
287
+ /**
288
+ * Feature flags with their default value, e.g. `{ newEditor: false }`. Global admins switch them at
289
+ * runtime in Administration → System settings; read them with `useFeatureFlag("newEditor")` in the UI
290
+ * and `ctx.use<FeatureFlags>("featureFlags")?.enabled("newEditor")` in extensions.
291
+ */
292
+ featureFlags?: Record<string, boolean>;
293
+ /**
294
+ * A public website: pages made of sections (hero, features, pricing, FAQ, blog…), a header menu and a footer.
295
+ * Seeded into the database and editable in the admin (Content); signed-out visitors of `/` see the home page.
296
+ */
297
+ site?: SiteConfig;
298
+ }
299
+ export interface AdminConfig {
300
+ /** The administration area (/admin) and its API. Default: true. */
301
+ enabled?: boolean;
302
+ /**
303
+ * Built-in support inbox: a "Help & support" form in the app (tickets with a message thread) answered
304
+ * by global admins in Administration → Support. Default: true.
305
+ */
306
+ support?: boolean;
263
307
  }
264
308
  export interface LandingFeature {
265
309
  title: string;
package/dist/app.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AA2DA,MAAM,UAAU,MAAM,CAAC,aAAgD,EAAE;IACvE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,UAAU,EAAE,CAAC;AAC9C,CAAC;AAqED,MAAM,UAAU,MAAM,CAAC,UAA6C;IAClE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,UAAU,EAAE,CAAC;AAC9C,CAAC;AAiMD,MAAM,UAAU,SAAS,CAAC,UAAyB;IACjD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,UAAU,EAAE,CAAC;AAC3C,CAAC"}
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AA6DA,MAAM,UAAU,MAAM,CAAC,aAAgD,EAAE;IACvE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,UAAU,EAAE,CAAC;AAC9C,CAAC;AAqED,MAAM,UAAU,MAAM,CAAC,UAA6C;IAClE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,UAAU,EAAE,CAAC;AAC9C,CAAC;AA+QD,MAAM,UAAU,SAAS,CAAC,UAAyB;IACjD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,UAAU,EAAE,CAAC;AAC3C,CAAC"}
@@ -4,41 +4,124 @@
4
4
  */
5
5
  export type WidgetFilter = Record<string, string | number | boolean | readonly (string | number)[]>;
6
6
  export type Aggregate = "count" | "sum" | "avg" | "min" | "max";
7
+ /** Date buckets of a chart axis. */
8
+ export type DateBucket = "day" | "week" | "month" | "year";
9
+ /** Calendar periods (weeks start on Monday). */
10
+ export type Period = "week" | "month" | "quarter" | "year";
11
+ /**
12
+ * How numbers are written. Default: like the field (money keeps its currency, percentage fields show "%").
13
+ * "compact" = 12.5K, "currency" = money (the field's currency, else `currency`), "percent" = a fraction as % (0.25 → 25%).
14
+ */
15
+ export type NumberFormat = "number" | "compact" | "currency" | "percent";
16
+ export type ChartKind = "bar" | "line" | "area" | "donut" | "pie" | "stackedBar" | "funnel" | "heatmap" | "table";
7
17
  interface WidgetBase {
8
18
  title: string;
9
19
  entity: string;
10
20
  where?: WidgetFilter;
11
21
  /** Columns in a 3-column grid. Default 1 (charts: 2, lists: 2). */
12
22
  span?: 1 | 2 | 3;
23
+ /**
24
+ * Roles that see this widget, e.g. ["admin"] for revenue only the owner should see. Default: everyone who
25
+ * can read the entity. (The numbers always respect the viewer's permissions on top of this.)
26
+ */
27
+ visibleTo?: readonly string[];
13
28
  }
14
- export interface MetricWidget extends WidgetBase {
29
+ /** One side of a ratio metric. */
30
+ export interface MetricSource {
31
+ entity: string;
32
+ where?: WidgetFilter;
33
+ aggregate?: Aggregate;
34
+ field?: string;
35
+ /** Date or datetime field that places records in a period (`period`, `compare`). Default "createdAt". */
36
+ dateField?: string;
37
+ }
38
+ export interface MetricWidget extends Omit<WidgetBase, "entity"> {
15
39
  readonly type: "metric";
40
+ /** Required, except for ratio metrics (each side names its own entity). */
41
+ entity?: string;
16
42
  aggregate?: Aggregate;
17
43
  /** Numeric field for sum/avg/min/max. */
18
44
  field?: string;
19
45
  /**
20
46
  * Show the change versus the previous period ("+12% vs previous month") with a sparkline of the last 12 periods.
21
47
  * Periods are rolling windows ending now (month = the last month, compared with the month before).
22
- * Default "month" when the entity has a date to compare on, false when `where` already uses relative dates ($today, $startOfMonth…).
48
+ * Default "month" when the entity has a date to compare on, false when `where` already uses relative dates ($today, $startOfMonth…) or `period` is set.
23
49
  */
24
- compare?: "week" | "month" | "quarter" | "year" | false;
50
+ compare?: Period | false;
25
51
  /** Date or datetime field that places records in a period. Default "createdAt". */
26
52
  dateField?: string;
27
- /** Which direction is good news (colours the change). Default "up-is-good". */
53
+ /** Which direction is good news (colours the change and the goal). Default "up-is-good". */
28
54
  trend?: "up-is-good" | "down-is-good";
55
+ /**
56
+ * Count only the current calendar period (this week/month/quarter/year, in the dashboard's timezone), the whole
57
+ * period: records dated later in it (upcoming bookings, due dates) count too. Without `period` the value is all-time.
58
+ */
59
+ period?: Period;
60
+ /** A target: shows progress towards it (e.g. 50000 with period "month" = "50K this month"). */
61
+ goal?: number;
62
+ /**
63
+ * A ratio of two aggregates, e.g. a conversion rate: { numerator: { entity: "Deal", where: { stage: "won" } }, denominator: { entity: "Lead" } }.
64
+ * Both sides use the viewer's permissions; the widget is hidden from viewers who can't read either side. Shown as a percentage by default.
65
+ */
66
+ ratio?: {
67
+ numerator: MetricSource;
68
+ denominator: MetricSource;
69
+ };
70
+ format?: NumberFormat;
71
+ /** ISO currency code for `format: "currency"` when the field is not money. */
72
+ currency?: string;
29
73
  }
30
- export interface ChartWidget extends WidgetBase {
74
+ /** One line/bar series of a multi-series chart; each series can come from another entity. */
75
+ export interface ChartSeries {
76
+ entity: string;
77
+ /** Legend label. Default: the entity's plural label. */
78
+ label?: string;
79
+ where?: WidgetFilter;
80
+ aggregate?: Aggregate;
81
+ field?: string;
82
+ /** Date axis (`groupBy: { by }`): the date field that places this series' records. Default: the chart's groupBy field, else "createdAt". */
83
+ dateField?: string;
84
+ /** Category axis (`groupBy: "stage"`): the field this series groups by. Default: the chart's groupBy. */
85
+ groupBy?: string;
86
+ /** Value axis. Default: left, or right when the unit differs from the first series (count vs money). */
87
+ axis?: "left" | "right";
88
+ }
89
+ export interface ChartWidget extends Omit<WidgetBase, "entity"> {
31
90
  readonly type: "chart";
32
- chart: "bar" | "line" | "donut";
33
- /** Enum, boolean or many-to-one relation field — or a date field bucketed by day/week/month/year. */
34
- groupBy: string | {
35
- field: string;
36
- by: "day" | "week" | "month" | "year";
91
+ chart: ChartKind;
92
+ /** Required unless `series` is set. */
93
+ entity?: string;
94
+ /**
95
+ * Enum, boolean or many-to-one relation field — or a date field bucketed by day/week/month/year.
96
+ * Heatmaps: `{ field, by: "hour" }` (day of week × hour of day) or `{ field, by: "day" }` (one cell per day, a column per week).
97
+ * Multi-series date axis: `{ by: "month" }` (each series names its `dateField`).
98
+ */
99
+ groupBy?: string | {
100
+ field?: string;
101
+ by: DateBucket | "hour";
37
102
  };
103
+ /** A second enum, boolean or many-to-one field: one series per value (stackedBar, or grouped bar/line/area/table). */
104
+ stackBy?: string;
105
+ /** Several series (possibly from several entities) on a shared date axis or a shared category axis. */
106
+ series?: readonly ChartSeries[];
38
107
  aggregate?: Aggregate;
39
108
  field?: string;
40
109
  /** Max categories shown (the rest are grouped as "Other"). Default 8. */
41
110
  limit?: number;
111
+ /** Funnel: the enum values to show, in order. Default: every value, in declaration order. */
112
+ stages?: readonly string[];
113
+ /** Funnel: count records at a stage or any later one ("reached this stage"). Default false. */
114
+ cumulative?: boolean;
115
+ /** Plot height: sm 160px, md 220px (default), lg 320px. */
116
+ height?: "sm" | "md" | "lg";
117
+ /** Series/segment colours in order: hex ("#2563eb") or theme tokens ("primary", "accent", "success", "info", "danger", "chart-1"…"chart-8"). Default: the theme's chart palette. */
118
+ colors?: readonly string[];
119
+ /** Show the legend. Default: when there are several series (and for donut/pie). */
120
+ legend?: boolean;
121
+ /** Stack the series of a bar/area chart. Default: true for stackedBar, false otherwise. */
122
+ stacked?: boolean;
123
+ format?: NumberFormat;
124
+ currency?: string;
42
125
  }
43
126
  export interface ListWidget extends WidgetBase {
44
127
  readonly type: "list";
@@ -48,6 +131,19 @@ export interface ListWidget extends WidgetBase {
48
131
  columns?: readonly string[];
49
132
  }
50
133
  export type DashboardWidget = MetricWidget | ChartWidget | ListWidget;
134
+ /** A dashboard tab; `visibleTo` gives roles their own dashboards. */
135
+ export interface DashboardTab {
136
+ title: string;
137
+ widgets: readonly DashboardWidget[];
138
+ visibleTo?: readonly string[];
139
+ }
140
+ export interface DashboardConfig {
141
+ /** The widgets (one page). Use `tabs` instead for several pages. */
142
+ widgets?: readonly DashboardWidget[];
143
+ tabs?: readonly DashboardTab[];
144
+ /** IANA timezone for date buckets, heatmaps and goal periods (e.g. "Europe/Paris"). Default "UTC". */
145
+ timezone?: string;
146
+ }
51
147
  export declare const metric: (w: Omit<MetricWidget, "type">) => MetricWidget;
52
148
  export declare const chart: (w: Omit<ChartWidget, "type">) => ChartWidget;
53
149
  export declare const list: (w: Omit<ListWidget, "type">) => ListWidget;
@@ -1 +1 @@
1
- {"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../src/dashboard.ts"],"names":[],"mappings":"AAqDA,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,CAA6B,EAAgB,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;AAClG,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,CAA4B,EAAe,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;AAC9F,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,CAA2B,EAAc,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC"}
1
+ {"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../src/dashboard.ts"],"names":[],"mappings":"AAsJA,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,CAA6B,EAAgB,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;AAClG,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,CAA4B,EAAe,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;AAC9F,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,CAA2B,EAAc,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ /** Icons an entity can use in navigation (`icon: "receipt"`); the admin renders them with lucide. */
2
+ export declare const ENTITY_ICONS: readonly ["folder", "users", "user", "briefcase", "building", "file-text", "box", "tag", "layers", "star", "calendar", "credit-card", "mail", "map-pin", "package", "shopping-cart", "ticket", "check-square", "receipt", "wallet", "banknote", "dollar-sign", "chart", "trending-up", "target", "phone", "message-square", "headphones", "life-buoy", "clipboard", "list-todo", "kanban", "flag", "bug", "wrench", "hammer", "truck", "warehouse", "store", "factory", "home", "bed", "car", "plane", "map", "globe", "graduation-cap", "book", "library", "stethoscope", "heart-pulse", "pill", "utensils", "coffee", "shirt", "gift", "camera", "image", "video", "music", "newspaper", "megaphone", "award", "trophy", "shield", "key", "lock", "server", "database", "cpu", "code", "git-branch", "rocket", "zap", "leaf", "sprout", "paw", "dumbbell", "clock", "timer", "bell", "inbox", "archive", "file", "paperclip", "contact", "id-card", "handshake", "scale", "landmark", "calculator", "percent", "boxes", "barcode", "scan", "settings", "sliders", "puzzle", "lightbulb"];
3
+ export type EntityIcon = (typeof ENTITY_ICONS)[number];
package/dist/icons.js ADDED
@@ -0,0 +1,3 @@
1
+ /** Icons an entity can use in navigation (`icon: "receipt"`); the admin renders them with lucide. */
2
+ export const ENTITY_ICONS = ["folder", "users", "user", "briefcase", "building", "file-text", "box", "tag", "layers", "star", "calendar", "credit-card", "mail", "map-pin", "package", "shopping-cart", "ticket", "check-square", "receipt", "wallet", "banknote", "dollar-sign", "chart", "trending-up", "target", "phone", "message-square", "headphones", "life-buoy", "clipboard", "list-todo", "kanban", "flag", "bug", "wrench", "hammer", "truck", "warehouse", "store", "factory", "home", "bed", "car", "plane", "map", "globe", "graduation-cap", "book", "library", "stethoscope", "heart-pulse", "pill", "utensils", "coffee", "shirt", "gift", "camera", "image", "video", "music", "newspaper", "megaphone", "award", "trophy", "shield", "key", "lock", "server", "database", "cpu", "code", "git-branch", "rocket", "zap", "leaf", "sprout", "paw", "dumbbell", "clock", "timer", "bell", "inbox", "archive", "file", "paperclip", "contact", "id-card", "handshake", "scale", "landmark", "calculator", "percent", "boxes", "barcode", "scan", "settings", "sliders", "puzzle", "lightbulb"];
3
+ //# sourceMappingURL=icons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icons.js","sourceRoot":"","sources":["../src/icons.ts"],"names":[],"mappings":"AAAA,qGAAqG;AACrG,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,eAAe,EAAE,QAAQ,EAAE,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,CAAU,CAAC"}
package/dist/index.d.ts CHANGED
@@ -2,3 +2,5 @@ export * from "./fields.js";
2
2
  export * from "./app.js";
3
3
  export * from "./plugin.js";
4
4
  export * from "./dashboard.js";
5
+ export * from "./icons.js";
6
+ export * from "./site.js";
package/dist/index.js CHANGED
@@ -2,4 +2,6 @@ export * from "./fields.js";
2
2
  export * from "./app.js";
3
3
  export * from "./plugin.js";
4
4
  export * from "./dashboard.js";
5
+ export * from "./icons.js";
6
+ export * from "./site.js";
5
7
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC"}
package/dist/site.d.ts ADDED
@@ -0,0 +1,350 @@
1
+ /**
2
+ * The public website (`site` in forge.config.ts): pages made of sections from a built-in library, a header menu,
3
+ * a footer and a blog. Pages are seeded into the database and stay editable in the admin (Content).
4
+ */
5
+ /** A same-site path ("/pricing", "/sign-up", "#plans"), an http(s) URL, mailto: or tel:. */
6
+ export type Href = string;
7
+ /** An image: an http(s) URL, a file in apps/web/public ("/hero.png") or an upload ("/api/files/<id>", storage plugin). */
8
+ export type ImageUrl = string;
9
+ export interface SiteLink {
10
+ label: string;
11
+ href: Href;
12
+ }
13
+ /** Heading shown above most sections. */
14
+ export interface SectionHeading {
15
+ /** Small line above the title. */
16
+ eyebrow?: string;
17
+ title?: string;
18
+ subtitle?: string;
19
+ }
20
+ export type PostKind = "post" | "guide" | "webinar" | "case-study";
21
+ export type DataFilter = Record<string, string | number | boolean | readonly (string | number)[]>;
22
+ export interface HeroProps {
23
+ eyebrow?: string;
24
+ title: string;
25
+ subtitle?: string;
26
+ primary?: SiteLink;
27
+ secondary?: SiteLink;
28
+ /** Shown by the "split" variant (and behind the "minimal" one when set). */
29
+ image?: ImageUrl;
30
+ imageAlt?: string;
31
+ }
32
+ export interface FeatureItem {
33
+ title: string;
34
+ description?: string;
35
+ /** zap, shield, chart, sparkles, lock, globe, clock, bell, search, heart, rocket, users, calendar… */
36
+ icon?: string;
37
+ /** Shown by the "alternating" and "bento" variants. */
38
+ image?: ImageUrl;
39
+ imageAlt?: string;
40
+ href?: Href;
41
+ }
42
+ export interface FeaturesProps extends SectionHeading {
43
+ items: readonly FeatureItem[];
44
+ }
45
+ export interface LogosProps extends SectionHeading {
46
+ items: readonly {
47
+ name: string;
48
+ image?: ImageUrl;
49
+ href?: Href;
50
+ }[];
51
+ }
52
+ export interface TestimonialsProps extends SectionHeading {
53
+ items: readonly {
54
+ quote: string;
55
+ author: string;
56
+ role?: string;
57
+ avatar?: ImageUrl;
58
+ }[];
59
+ }
60
+ /**
61
+ * A number: static (`value: "99.9%"`) or live — `entity` (+ `aggregate`, `field`, `where`), computed like a
62
+ * dashboard metric. Live numbers need an entity whose read rule includes "public" (and no organization scope).
63
+ */
64
+ export interface StatItem {
65
+ label: string;
66
+ value?: string;
67
+ entity?: string;
68
+ aggregate?: "count" | "sum" | "avg" | "min" | "max";
69
+ field?: string;
70
+ where?: DataFilter;
71
+ prefix?: string;
72
+ suffix?: string;
73
+ }
74
+ export interface StatsProps extends SectionHeading {
75
+ items: readonly StatItem[];
76
+ }
77
+ export interface StepsProps extends SectionHeading {
78
+ items: readonly {
79
+ title: string;
80
+ description?: string;
81
+ }[];
82
+ }
83
+ export interface PricingPlan {
84
+ name: string;
85
+ price: string;
86
+ /** e.g. "/ month" */
87
+ period?: string;
88
+ description?: string;
89
+ features?: readonly string[];
90
+ cta?: SiteLink;
91
+ /** Emphasized card ("Most popular"). */
92
+ highlighted?: boolean;
93
+ }
94
+ export interface PricingProps extends SectionHeading {
95
+ /** "payments": the plans of the payments plugin (name, price, description, features). Default: "static" when `plans` is set. */
96
+ source?: "static" | "payments";
97
+ plans?: readonly PricingPlan[];
98
+ }
99
+ export interface FaqProps extends SectionHeading {
100
+ /** Answers are markdown. */
101
+ items: readonly {
102
+ question: string;
103
+ answer: string;
104
+ }[];
105
+ }
106
+ export interface CtaProps {
107
+ title: string;
108
+ subtitle?: string;
109
+ primary?: SiteLink;
110
+ secondary?: SiteLink;
111
+ }
112
+ export interface ComparisonProps extends SectionHeading {
113
+ /** Column headings, e.g. ["Acme", "Spreadsheets", "Others"]. */
114
+ columns: readonly string[];
115
+ /** One value per column: true/false shows a check or a dash, text is shown as is. */
116
+ rows: readonly {
117
+ feature: string;
118
+ values: readonly (string | boolean)[];
119
+ }[];
120
+ }
121
+ export interface IntegrationsProps extends SectionHeading {
122
+ items: readonly {
123
+ name: string;
124
+ description?: string;
125
+ image?: ImageUrl;
126
+ href?: Href;
127
+ }[];
128
+ }
129
+ export interface TeamProps extends SectionHeading {
130
+ items: readonly {
131
+ name: string;
132
+ role?: string;
133
+ bio?: string;
134
+ avatar?: ImageUrl;
135
+ href?: Href;
136
+ }[];
137
+ }
138
+ export interface GalleryProps extends SectionHeading {
139
+ items: readonly {
140
+ image: ImageUrl;
141
+ alt: string;
142
+ caption?: string;
143
+ }[];
144
+ }
145
+ /** A form whose messages are stored (built-in ContactMessage model) and notified to admins. */
146
+ export interface ContactProps extends SectionHeading {
147
+ /** Shown beside the form by the "split" variant. */
148
+ email?: string;
149
+ phone?: string;
150
+ address?: string;
151
+ /** Ask for a company name too. */
152
+ company?: boolean;
153
+ buttonLabel?: string;
154
+ successMessage?: string;
155
+ }
156
+ /** Email capture stored in the built-in NewsletterSubscriber model. */
157
+ export interface NewsletterProps extends SectionHeading {
158
+ buttonLabel?: string;
159
+ successMessage?: string;
160
+ }
161
+ export interface RichTextProps {
162
+ /** Markdown (sanitized: no raw HTML). */
163
+ body: string;
164
+ }
165
+ export interface ImageTextProps {
166
+ eyebrow?: string;
167
+ title?: string;
168
+ /** Markdown. */
169
+ body?: string;
170
+ image: ImageUrl;
171
+ imageAlt?: string;
172
+ cta?: SiteLink;
173
+ }
174
+ export interface VideoProps extends SectionHeading {
175
+ /** A YouTube or Vimeo link, or a video file (.mp4/.webm). */
176
+ url: string;
177
+ caption?: string;
178
+ }
179
+ export interface EmbedProps extends SectionHeading {
180
+ /** https URL whose host is allowed (YouTube, Vimeo, Google Maps/Docs/Forms, Calendly, Typeform, Loom, Figma… or site.embedHosts). */
181
+ url: string;
182
+ /** Accessible name of the frame. */
183
+ frameTitle: string;
184
+ /** Height in pixels (200–1200). Default 480. */
185
+ height?: number;
186
+ }
187
+ export interface BlogListProps extends SectionHeading {
188
+ /** Which posts: "post" (blog), "guide", "webinar" or "case-study". Default "post". */
189
+ kind?: PostKind;
190
+ /** Default 6. */
191
+ limit?: number;
192
+ tag?: string;
193
+ }
194
+ export interface SpacerProps {
195
+ }
196
+ export interface DividerProps {
197
+ }
198
+ /** Records of an entity whose read rule includes "public" (e.g. a product catalog or a job board). */
199
+ export interface EntityListProps extends SectionHeading {
200
+ entity: string;
201
+ /** Fields shown (default: the display field and the first list columns). */
202
+ fields?: readonly string[];
203
+ /** List-query filter, e.g. { status: "open" }. */
204
+ filter?: DataFilter;
205
+ /** e.g. "-createdAt" */
206
+ sort?: string;
207
+ /** Default 12, at most 48. */
208
+ limit?: number;
209
+ }
210
+ export interface SectionPropsByType {
211
+ hero: HeroProps;
212
+ features: FeaturesProps;
213
+ logos: LogosProps;
214
+ testimonials: TestimonialsProps;
215
+ stats: StatsProps;
216
+ steps: StepsProps;
217
+ pricing: PricingProps;
218
+ faq: FaqProps;
219
+ cta: CtaProps;
220
+ comparison: ComparisonProps;
221
+ integrations: IntegrationsProps;
222
+ team: TeamProps;
223
+ gallery: GalleryProps;
224
+ contact: ContactProps;
225
+ newsletter: NewsletterProps;
226
+ richText: RichTextProps;
227
+ imageText: ImageTextProps;
228
+ video: VideoProps;
229
+ embed: EmbedProps;
230
+ blogList: BlogListProps;
231
+ spacer: SpacerProps;
232
+ divider: DividerProps;
233
+ entityList: EntityListProps;
234
+ entityGrid: EntityListProps;
235
+ }
236
+ export interface SectionVariantsByType {
237
+ hero: "centered" | "split" | "brand" | "minimal";
238
+ features: "grid" | "alternating" | "bento";
239
+ logos: "strip" | "grid";
240
+ testimonials: "cards" | "single";
241
+ stats: "row" | "cards";
242
+ steps: "numbered" | "timeline";
243
+ pricing: "cards" | "compact";
244
+ faq: "accordion" | "columns";
245
+ cta: "banner" | "split" | "minimal";
246
+ comparison: "table" | "cards";
247
+ integrations: "grid" | "list";
248
+ team: "cards" | "compact";
249
+ gallery: "grid" | "masonry";
250
+ contact: "form" | "split";
251
+ newsletter: "inline" | "card";
252
+ richText: "default" | "narrow";
253
+ imageText: "left" | "right";
254
+ video: "default" | "wide";
255
+ embed: "default" | "wide";
256
+ blogList: "grid" | "list" | "featured";
257
+ spacer: "small" | "medium" | "large";
258
+ divider: "line" | "dots";
259
+ entityList: "table" | "list";
260
+ entityGrid: "cards" | "compact";
261
+ }
262
+ export type SectionType = keyof SectionPropsByType;
263
+ export interface SectionOptions<T extends SectionType> {
264
+ variant?: SectionVariantsByType[T];
265
+ /** Stable key (letters, digits, dashes) so admin edits survive reordering in the config. Default: the type (+ "-2", "-3"…). */
266
+ id?: string;
267
+ /** Kept but not shown. */
268
+ hidden?: boolean;
269
+ }
270
+ export interface SectionDefinition {
271
+ readonly __forge: "section";
272
+ type: string;
273
+ variant?: string;
274
+ id?: string;
275
+ hidden?: boolean;
276
+ props: Record<string, unknown>;
277
+ }
278
+ /**
279
+ * A section of a page: `section("hero", { variant: "split", title: "…", image: "/hero.png" })`.
280
+ * Props are typed per section type; see SectionPropsByType.
281
+ */
282
+ export declare function section<T extends SectionType>(type: T, props?: SectionPropsByType[T] & SectionOptions<T>): SectionDefinition;
283
+ /** Ready-made pages with sensible sections and placeholder copy from the app's name, description and entities. */
284
+ export type PageTemplate = "home" | "features" | "how-it-works" | "integrations" | "product-tour" | "use-cases" | "industries" | "pricing" | "customers" | "blog" | "guides" | "webinars" | "docs" | "about" | "team" | "careers" | "contact" | "security" | "privacy" | "compliance" | "login" | "signup" | "blank";
285
+ export interface PageSeo {
286
+ /** Default: the page title. */
287
+ title?: string;
288
+ description?: string;
289
+ /** Open Graph image. */
290
+ image?: ImageUrl;
291
+ }
292
+ export interface PageDefinition {
293
+ readonly __forge: "page";
294
+ /** Fills the title, path, SEO and sections you leave out. */
295
+ template?: PageTemplate;
296
+ title?: string;
297
+ /** "/", "/pricing", "/product/features" (lowercase, at most 3 segments). */
298
+ path?: string;
299
+ seo?: PageSeo;
300
+ sections?: readonly SectionDefinition[];
301
+ /** "draft" pages are only visible to site editors. Default "published". */
302
+ status?: "published" | "draft";
303
+ /**
304
+ * Place in the automatic header menu: `false` hides it, `{ group: "Resources" }` puts it in a dropdown,
305
+ * `{ label }` renames the entry, `{ footerOnly: true }` lists it only in the footer (legal pages).
306
+ * Default: grouped by the first path segment ("/product/x" → "Product").
307
+ */
308
+ nav?: false | {
309
+ group?: string;
310
+ label?: string;
311
+ footerOnly?: boolean;
312
+ };
313
+ /** Make the page a link to another address (the login and signup templates point at /sign-in and /sign-up). */
314
+ redirect?: Href;
315
+ }
316
+ export declare function page(definition?: Omit<PageDefinition, "__forge">): PageDefinition;
317
+ /** A header menu entry: a link (`href` or `page`, a key of site.pages) or a dropdown group (`items`). */
318
+ export interface SiteNavItem {
319
+ label?: string;
320
+ href?: Href;
321
+ page?: string;
322
+ description?: string;
323
+ items?: readonly SiteNavItem[];
324
+ }
325
+ export interface SiteFooter {
326
+ columns?: readonly {
327
+ title: string;
328
+ links: readonly SiteNavItem[];
329
+ }[];
330
+ /** Line under the columns, e.g. "Made in Dakar". */
331
+ note?: string;
332
+ /** Social profile links shown as icons. */
333
+ social?: readonly {
334
+ network: "x" | "github" | "linkedin" | "youtube" | "instagram" | "facebook" | "mastodon" | "discord";
335
+ href: string;
336
+ }[];
337
+ }
338
+ export interface SiteConfig {
339
+ pages: Record<string, PageDefinition>;
340
+ /** Header menu. Default: built from the pages (groups by first path segment or `nav.group`). */
341
+ nav?: readonly SiteNavItem[];
342
+ /** Footer columns. Default: the menu's groups and pages. */
343
+ footer?: SiteFooter;
344
+ /** Base path of each post kind (detail pages are `<path>/<slug>`, feed `<path>/rss.xml`). Default: the page listing that kind. */
345
+ blog?: Partial<Record<PostKind, string>>;
346
+ /** Extra hosts the `embed` section may show (besides YouTube, Vimeo, Google, Calendly, Typeform, Loom, Figma…). */
347
+ embedHosts?: readonly string[];
348
+ /** Roles (global user roles) that may edit the site in the admin. Default ["admin"]. */
349
+ editors?: readonly string[];
350
+ }
package/dist/site.js ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The public website (`site` in forge.config.ts): pages made of sections from a built-in library, a header menu,
3
+ * a footer and a blog. Pages are seeded into the database and stay editable in the admin (Content).
4
+ */
5
+ /**
6
+ * A section of a page: `section("hero", { variant: "split", title: "…", image: "/hero.png" })`.
7
+ * Props are typed per section type; see SectionPropsByType.
8
+ */
9
+ export function section(type, props) {
10
+ const { variant, id, hidden, ...rest } = (props ?? {});
11
+ return { __forge: "section", type, ...(variant !== undefined ? { variant } : {}), ...(id !== undefined ? { id } : {}), ...(hidden !== undefined ? { hidden } : {}), props: rest };
12
+ }
13
+ export function page(definition = {}) {
14
+ return { __forge: "page", ...definition };
15
+ }
16
+ //# sourceMappingURL=site.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"site.js","sourceRoot":"","sources":["../src/site.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAkRH;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAwB,IAAO,EAAE,KAAiD;IACvG,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,CAAC,KAAK,IAAI,EAAE,CAAkF,CAAC;IACxI,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACpL,CAAC;AAyDD,MAAM,UAAU,IAAI,CAAC,aAA8C,EAAE;IACnE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,UAAU,EAAE,CAAC;AAC5C,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekki-forge/core",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Forge schema DSL: defineApp, entity and field builders",
5
5
  "keywords": [
6
6
  "code-generator",