@press2ai/engine 0.5.0 → 0.6.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/README.md CHANGED
@@ -13,7 +13,7 @@ npm install @press2ai/engine
13
13
  ## Subpath exports
14
14
 
15
15
  - `@press2ai/engine` — core runtime + types
16
- - `@press2ai/engine/template-trener` — fitness/trainer vertical template
16
+ - `@press2ai/engine/template-catalog` — universal catalog template (trener, terapeuta, lekarz, ...)
17
17
  - `@press2ai/engine/template-blog` — blog template
18
18
  - `@press2ai/engine/types` — TypeScript type exports
19
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@press2ai/engine",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Multi-tenant runtime + template contracts dla otwarty-* verticali. SSR na Cloudflare Workers, isomorphic renderer (SSG/browser w roadmapie).",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -13,7 +13,7 @@
13
13
  },
14
14
  "exports": {
15
15
  ".": "./src/index.ts",
16
- "./template-trener": "./src/template-trener.ts",
16
+ "./template-catalog": "./src/template-catalog.ts",
17
17
  "./template-blog": "./src/template-blog.ts",
18
18
  "./ceidg-vertical": "./src/ceidg-vertical.ts",
19
19
  "./types": "./src/types.ts"
@@ -15,7 +15,7 @@
15
15
  * - Schema CEIDG (`leads`, `lead_pkd`, `lead_categories`, `cities`) jest
16
16
  * hard-coded w queries — to jest *cel* tego helpera. „Generic vertical
17
17
  * framework" nie istnieje, dopóki nie ma trzeciej rodziny niż otwarty-*.
18
- * - Theme bundle (`TrenerTheme`) jest **input**, nie tworzony tutaj. Engine
18
+ * - Theme bundle (`CatalogTheme`) jest **input**, nie tworzony tutaj. Engine
19
19
  * nie zna theme-specialist-glossy. Wertykal podaje gotowy bundle (~10 LOC
20
20
  * adaptera w landingu), helper tylko go wywołuje.
21
21
  * - `loadContent` filtruje po `EXISTS lead_categories WHERE category=?`
@@ -34,11 +34,11 @@
34
34
  import { Hono } from 'hono';
35
35
  import { createSSRWorker } from './runtime/ssr';
36
36
  import {
37
- trenerTemplate,
38
- type TrenerContent,
39
- type TrenerProfile,
40
- type TrenerTheme,
41
- } from './template-trener';
37
+ catalogTemplate,
38
+ type CatalogContent,
39
+ type CatalogProfile,
40
+ type CatalogTheme,
41
+ } from './template-catalog';
42
42
 
43
43
  /* ─────────────── Public types ─────────────── */
44
44
 
@@ -99,8 +99,8 @@ export interface VerticalConfig {
99
99
  afterHero?: string;
100
100
  afterGrid?: string;
101
101
  };
102
- /** Theme bundle. Wertykal buduje adapter (Profile ↔ TrenerProfile) i podaje gotowy. */
103
- theme: TrenerTheme;
102
+ /** Theme bundle. Wertykal buduje adapter (Profile ↔ CatalogProfile) i podaje gotowy. */
103
+ theme: CatalogTheme;
104
104
  }
105
105
 
106
106
  /* ─────────────── Implementation ─────────────── */
@@ -141,7 +141,7 @@ export function createVerticalApp(config: VerticalConfig): Hono<{ Bindings: Ceid
141
141
  GROUP BY c.name ORDER BY count DESC LIMIT 8`;
142
142
 
143
143
  // ─────────── Content loader ───────────
144
- function leadToProfile(l: CeidgLead): TrenerProfile {
144
+ function leadToProfile(l: CeidgLead): CatalogProfile {
145
145
  return {
146
146
  slug: l.slug,
147
147
  firstName: l.first_name,
@@ -155,7 +155,7 @@ export function createVerticalApp(config: VerticalConfig): Hono<{ Bindings: Ceid
155
155
  };
156
156
  }
157
157
 
158
- async function loadContent(_host: string, env: unknown): Promise<TrenerContent> {
158
+ async function loadContent(_host: string, env: unknown): Promise<CatalogContent> {
159
159
  const e = env as CeidgBindings;
160
160
  const [leadsRes, citiesRes] = await Promise.all([
161
161
  e.DB.prepare(leadsQuery + ' ORDER BY c.name, l.last_name').bind(category).all<CeidgLead>(),
@@ -180,7 +180,7 @@ export function createVerticalApp(config: VerticalConfig): Hono<{ Bindings: Ceid
180
180
  };
181
181
  }
182
182
 
183
- const engine = createSSRWorker({ template: trenerTemplate, theme, loadContent });
183
+ const engine = createSSRWorker({ template: catalogTemplate, theme, loadContent });
184
184
 
185
185
  // ─────────── KV cache helper ───────────
186
186
  async function cached<T>(kv: KVNamespace, key: string, fn: () => Promise<T>): Promise<T> {
@@ -1,10 +1,9 @@
1
1
  /**
2
- * template-trener — referencyjna implementacja PresenceTemplate.
2
+ * template-catalog — referencyjna implementacja PresenceTemplate.
3
3
  *
4
- * Zawiera pełen flow który dziś jest w OTWARTY-TRENER/landing/src/index.tsx,
5
- * tylko jako czyste funkcje renderujące zamiast handlerów Hono. Engine
6
- * wywoła render() per request (SSR) albo per route (SSG); template nie
7
- * wie który tryb jest aktywny i nie powinien tego sprawdzać.
4
+ * Uniwersalny katalog wizytówek (trener, terapeuta, lekarz, ...).
5
+ * Engine wywoła render() per request (SSR) albo per route (SSG);
6
+ * template nie wie który tryb jest aktywny i nie powinien tego sprawdzać.
8
7
  */
9
8
 
10
9
  import { z } from 'zod';
@@ -19,17 +18,16 @@ import type {
19
18
  /* ─────────────── Theme contract dla tego templatu ───────────────
20
19
  * Każdy template deklaruje JAKIEGO theme'u potrzebuje. Engine pilnuje
21
20
  * przez generics że theme przekazany do createWorker() ma te same metody.
22
- * Trener używa zestawu z @press2ai/theme-specialist-glossy.
23
21
  */
24
- export interface TrenerTheme {
22
+ export interface CatalogTheme {
25
23
  layout(props: { title: string; description?: string; jsonLd?: object; headExtra?: string }, body: string): string;
26
24
  catalogHero(p: { badge?: string; title: string; subtitle?: string; searchAction?: string; searchPlaceholder?: string; searchValue?: string }): string;
27
25
  catalogGrid(p: { title: string; filters?: string; cards: string }): string;
28
26
  statBar(items: { value: string; label: string; icon?: string }[], summary?: string): string;
29
27
  categoryNav(items: { href: string; label: string }[], ariaLabel?: string): string;
30
28
  pagination(p: { current: number; total: number; extraParams?: string }): string;
31
- profileCard(p: TrenerProfile, href: string): string;
32
- profileArticle(p: TrenerProfile): string;
29
+ profileCard(p: CatalogProfile, href: string): string;
30
+ profileArticle(p: CatalogProfile): string;
33
31
  esc(s: string): string;
34
32
  }
35
33
 
@@ -39,7 +37,7 @@ const profileSchema = z.object({
39
37
  slug: z.string(),
40
38
  firstName: z.string(),
41
39
  lastName: z.string(),
42
- jobTitle: z.string().default('Trener personalny'),
40
+ jobTitle: z.string().default('Specjalista'),
43
41
  city: z.string().optional(),
44
42
  bio: z.string().optional(),
45
43
  photo: z.string().url().optional(),
@@ -55,9 +53,9 @@ const profileSchema = z.object({
55
53
  social: z.record(z.string(), z.string()).default({}),
56
54
  });
57
55
 
58
- export type TrenerProfile = z.infer<typeof profileSchema>;
56
+ export type CatalogProfile = z.infer<typeof profileSchema>;
59
57
 
60
- const trenerContentSchema = z.object({
58
+ const catalogContentSchema = z.object({
61
59
  brand: z.object({
62
60
  siteName: z.string(),
63
61
  description: z.string(),
@@ -80,35 +78,36 @@ const trenerContentSchema = z.object({
80
78
  }).default({}),
81
79
  });
82
80
 
83
- export type TrenerContent = z.infer<typeof trenerContentSchema>;
81
+ export type CatalogContent = z.infer<typeof catalogContentSchema>;
84
82
 
85
83
  const PAGE_SIZE = 12;
86
84
 
87
85
  /* ─────────────── Template ─────────────── */
88
86
 
89
- export const trenerTemplate: PresenceTemplate<TrenerContent, TrenerTheme> = {
87
+ export const catalogTemplate: PresenceTemplate<CatalogContent, CatalogTheme> = {
90
88
  kind: 'presence',
91
- id: 'trener',
92
- name: 'Otwarty Trener',
89
+ id: 'catalog',
90
+ name: 'Otwarty Katalog',
93
91
  version: '1.0.0',
94
92
 
95
- schema: trenerContentSchema,
93
+ schema: catalogContentSchema,
96
94
 
97
95
  defaultContent: {
98
96
  brand: {
99
- siteName: 'Otwarty Trener',
100
- description: 'Otwarta baza trenerów personalnych w Polsce. Dane z CEIDG.',
97
+ siteName: 'Otwarty Katalog',
98
+ description: 'Otwarta baza specjalistów w Polsce. Dane z CEIDG.',
101
99
  },
102
100
  copy: {
103
101
  heroBadge: 'Dane z publicznego rejestru CEIDG',
104
- heroTitle: 'Znajdź trenera w Twojej okolicy',
105
- heroSubtitle: 'Otwarty katalog trenerów. Bezpłatnie i bez rejestracji.',
102
+ heroTitle: 'Znajdź specjalistę w Twojej okolicy',
103
+ heroSubtitle: 'Otwarty katalog specjalistów. Bezpłatnie i bez rejestracji.',
106
104
  searchPlaceholder: 'Szukaj po nazwisku, mieście lub firmie...',
107
105
  itemsLabel: 'wpisów',
108
106
  itemSingular: 'wpisu',
109
107
  },
110
108
  profiles: [],
111
109
  cities: [],
110
+ sections: { afterHero: '', afterGrid: '' },
112
111
  },
113
112
 
114
113
  editor: {
@@ -128,8 +127,6 @@ export const trenerTemplate: PresenceTemplate<TrenerContent, TrenerTheme> = {
128
127
 
129
128
  dataSources: [
130
129
  {
131
- // CEIDG fetcher — w produkcji bije do dane.biznes.gov.pl, mapuje do
132
- // profiles[]. Tu STUB — kontrakt: dostaje NIP, zwraca Partial<TrenerContent>.
133
130
  id: 'ceidg',
134
131
  refresh: 'daily',
135
132
  async fetch({ taxId }) {
@@ -167,7 +164,7 @@ export const trenerTemplate: PresenceTemplate<TrenerContent, TrenerTheme> = {
167
164
 
168
165
  meta: {
169
166
  locale: 'pl-PL',
170
- description: 'Katalog trenerów personalnych',
167
+ description: 'Katalog specjalistów',
171
168
  suggestedTheme: 'specialist-glossy',
172
169
  tags: ['katalog', 'wizytówka', 'rejestr-publiczny'],
173
170
  },
@@ -175,7 +172,7 @@ export const trenerTemplate: PresenceTemplate<TrenerContent, TrenerTheme> = {
175
172
 
176
173
  /* ─────────────── Render handlers (private) ─────────────── */
177
174
 
178
- function renderIndex(req: RenderRequest<TrenerContent>, ctx: RenderContext<TrenerTheme>): RenderResult {
175
+ function renderIndex(req: RenderRequest<CatalogContent>, ctx: RenderContext<CatalogTheme>): RenderResult {
179
176
  const { content } = req;
180
177
  const t = ctx.theme;
181
178
  const q = (req.query.q ?? '').trim();
@@ -221,7 +218,7 @@ function renderIndex(req: RenderRequest<TrenerContent>, ctx: RenderContext<Trene
221
218
 
222
219
  const cards = slice.map((p) => t.profileCard(p, `/${p.slug}`)).join('\n');
223
220
  const grid = t.catalogGrid({
224
- title: q ? `Wyniki dla „${t.esc(q)}" (${total})` : 'Trenerzy',
221
+ title: q ? `Wyniki dla „${t.esc(q)}" (${total})` : content.brand.siteName,
225
222
  cards,
226
223
  });
227
224
 
@@ -246,7 +243,7 @@ function renderIndex(req: RenderRequest<TrenerContent>, ctx: RenderContext<Trene
246
243
  };
247
244
  }
248
245
 
249
- function renderProfile(req: RenderRequest<TrenerContent>, ctx: RenderContext<TrenerTheme>): RenderResult {
246
+ function renderProfile(req: RenderRequest<CatalogContent>, ctx: RenderContext<CatalogTheme>): RenderResult {
250
247
  const profile = req.content.profiles.find((p) => p.slug === req.params.slug);
251
248
  if (!profile) {
252
249
  return {
@@ -1,43 +0,0 @@
1
- /**
2
- * Engine signatures — pozostałe TRZY tryby wykonania (SSG + Browser).
3
- * SSR zostało wyciągnięte do runtime/ssr.ts (ma już prawdziwą implementację).
4
- * Te dwa pozostają jako `declare` do czasu implementacji w runtime/.
5
- */
6
-
7
- import type { OtwartyTemplate, BuildFile, RenderResult } from './types';
8
-
9
- /* ─────────────── Faza 1: SSG build do plików statycznych ─────────────── */
10
-
11
- export interface SSGBuildOpts<C, T> {
12
- template: OtwartyTemplate<C, T>;
13
- theme: T;
14
- /** Treść jest stała na czas builda — z disku, z migracji z Faza 0, z CMS. */
15
- content: C;
16
- baseUrl: string;
17
- /** Output adapter — pisanie plików, upload do CF Pages, push do Codeberg Pages. */
18
- output: SSGOutput;
19
- }
20
-
21
- export interface SSGOutput {
22
- write(file: BuildFile): Promise<void>;
23
- finalize?(): Promise<void>;
24
- }
25
-
26
- export declare function createSSGBuild<C, T>(opts: SSGBuildOpts<C, T>): Promise<{ files: BuildFile[] }>;
27
-
28
- /* ─────────────── Browser: live preview w lokalnym edytorze ─────────────── */
29
-
30
- export interface BrowserRendererOpts<C, T> {
31
- template: OtwartyTemplate<C, T>;
32
- theme: T;
33
- baseUrl: string;
34
- }
35
-
36
- export interface BrowserRenderer<C> {
37
- /** Renderuje konkretną ścieżkę dla aktualnej treści — używane przez iframe preview. */
38
- preview(content: C, path: string): RenderResult;
39
- /** Lista wszystkich ścieżek wyliczonych z treści — używane przez side panel "Strony". */
40
- listRoutes(content: C): string[];
41
- }
42
-
43
- export declare function createBrowserRenderer<C, T>(opts: BrowserRendererOpts<C, T>): BrowserRenderer<C>;