@press2ai/engine 0.5.1 → 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.1",
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,29 +78,29 @@ 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',
@@ -129,8 +127,6 @@ export const trenerTemplate: PresenceTemplate<TrenerContent, TrenerTheme> = {
129
127
 
130
128
  dataSources: [
131
129
  {
132
- // CEIDG fetcher — w produkcji bije do dane.biznes.gov.pl, mapuje do
133
- // profiles[]. Tu STUB — kontrakt: dostaje NIP, zwraca Partial<TrenerContent>.
134
130
  id: 'ceidg',
135
131
  refresh: 'daily',
136
132
  async fetch({ taxId }) {
@@ -168,7 +164,7 @@ export const trenerTemplate: PresenceTemplate<TrenerContent, TrenerTheme> = {
168
164
 
169
165
  meta: {
170
166
  locale: 'pl-PL',
171
- description: 'Katalog trenerów personalnych',
167
+ description: 'Katalog specjalistów',
172
168
  suggestedTheme: 'specialist-glossy',
173
169
  tags: ['katalog', 'wizytówka', 'rejestr-publiczny'],
174
170
  },
@@ -176,7 +172,7 @@ export const trenerTemplate: PresenceTemplate<TrenerContent, TrenerTheme> = {
176
172
 
177
173
  /* ─────────────── Render handlers (private) ─────────────── */
178
174
 
179
- function renderIndex(req: RenderRequest<TrenerContent>, ctx: RenderContext<TrenerTheme>): RenderResult {
175
+ function renderIndex(req: RenderRequest<CatalogContent>, ctx: RenderContext<CatalogTheme>): RenderResult {
180
176
  const { content } = req;
181
177
  const t = ctx.theme;
182
178
  const q = (req.query.q ?? '').trim();
@@ -222,7 +218,7 @@ function renderIndex(req: RenderRequest<TrenerContent>, ctx: RenderContext<Trene
222
218
 
223
219
  const cards = slice.map((p) => t.profileCard(p, `/${p.slug}`)).join('\n');
224
220
  const grid = t.catalogGrid({
225
- title: q ? `Wyniki dla „${t.esc(q)}" (${total})` : 'Trenerzy',
221
+ title: q ? `Wyniki dla „${t.esc(q)}" (${total})` : content.brand.siteName,
226
222
  cards,
227
223
  });
228
224
 
@@ -247,7 +243,7 @@ function renderIndex(req: RenderRequest<TrenerContent>, ctx: RenderContext<Trene
247
243
  };
248
244
  }
249
245
 
250
- function renderProfile(req: RenderRequest<TrenerContent>, ctx: RenderContext<TrenerTheme>): RenderResult {
246
+ function renderProfile(req: RenderRequest<CatalogContent>, ctx: RenderContext<CatalogTheme>): RenderResult {
251
247
  const profile = req.content.profiles.find((p) => p.slug === req.params.slug);
252
248
  if (!profile) {
253
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>;