blockprint 0.1.0 → 0.1.1

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.
Files changed (2) hide show
  1. package/README.md +48 -7
  2. package/package.json +5 -1
package/README.md CHANGED
@@ -1,16 +1,52 @@
1
1
  # blockprint
2
2
 
3
- Typed, config-driven block rendering for React. See the [repository README](../../README.md) for the full introduction.
3
+ **Typed, config-driven block rendering for React.** Render arbitrary UI trees from CMS-controlled JSON — with compile-time safety between your components and the config, and runtime validation at the boundary.
4
+
5
+ **Live demo: [blockprint.terzidest.com](https://blockprint.terzidest.com)** — three completely different pages (commerce, SaaS, editorial) from one renderer and one registry, plus a deliberately broken config showing graceful fallback. · [GitHub](https://github.com/terzidest/blockprint)
6
+
7
+ ## The problem
8
+
9
+ Content teams compose pages from a fixed set of block types; your app receives JSON like `[{ "type": "hero", "props": { … } }, …]`. Consuming that in React usually collapses into a `switch` on `block.type` — every block imported into every page, props typed `any`, unknown types crashing the page.
10
+
11
+ blockprint inverts it: you define the blocks and their prop types, register them once, and render any config — including configs containing block types or malformed props you've never seen.
12
+
13
+ ## Usage
14
+
15
+ ```tsx
16
+ import { createRegistry, Renderer } from "blockprint";
17
+ import { z } from "zod";
18
+
19
+ const HeroBanner = ({ title, subtitle }: { title: string; subtitle?: string }) => (
20
+ <header><h1>{title}</h1>{subtitle && <p>{subtitle}</p>}</header>
21
+ );
22
+
23
+ const registry = createRegistry({
24
+ hero: HeroBanner, // compile-time trust
25
+ gameGrid: { // runtime guarantee
26
+ component: GameGrid,
27
+ schema: z.object({ games: z.array(gameSchema) }),
28
+ },
29
+ });
30
+
31
+ // config typically arrives from a CMS at runtime
32
+ <Renderer config={pageConfig} registry={registry} />;
33
+ ```
34
+
35
+ - A registered component whose props don't match its schema is a **compile error on that key**, with a message naming the block — not a runtime crash.
36
+ - Unknown block types and schema-rejected props render a configurable **fallback**; the rest of the page renders normally.
37
+ - Configs authored in TypeScript get block-name autocomplete and per-block prop checking via `ConfigFor<typeof registry>`.
38
+ - **Zero runtime dependencies.** Schemas are typed against [Standard Schema](https://standardschema.dev), so zod (≥ 3.24), valibot, and arktype all work — blockprint depends on none of them.
39
+ - Framework-agnostic: React only. No Next.js/Remix imports; the library ships the mechanism, not the content.
4
40
 
5
41
  ## API
6
42
 
7
43
  ### `createRegistry(blocks)`
8
44
 
9
- Builds a typed registry from a map of block-type strings to definitions. A definition is either a bare component or `{ component, schema? }`, where `schema` is any [Standard Schema](https://standardschema.dev) validator (zod ≥ 3.24, valibot, arktype, …).
45
+ Builds a typed registry from a map of block-type strings to definitions. A definition is either a bare component or `{ component, schema? }`, where `schema` is any Standard Schema validator.
10
46
 
11
47
  When a schema is present it is the source of truth: the component receives the schema's parsed output (so schema defaults become prop defaults), and a component that cannot accept that output is a compile error on the offending key.
12
48
 
13
- ### `<Renderer config registry fallback? />`
49
+ ### `<Renderer config registry fallback? wrapBlock? />`
14
50
 
15
51
  Renders a config against a registry. `config` is `unknown` on purpose — the outer shape is validated internally at a single boundary. Every entry resolves to exactly one of:
16
52
 
@@ -18,7 +54,7 @@ Renders a config against a registry. `config` is `unknown` on purpose — the ou
18
54
  2. unknown type → `fallback` with `{ reason: "unknown-type" }`;
19
55
  3. schema-rejected props → `fallback` with `{ reason: "invalid-props", issues }`.
20
56
 
21
- The default fallback renders a warning box in development and `null` in production.
57
+ The default fallback renders a warning box in development and `null` in production — a page that wants visible failure in production must pass its own `fallback`.
22
58
 
23
59
  The optional `wrapBlock={(node, entry, index) => …}` prop wraps every rendered entry — blocks and fallbacks alike — with access to the config entry and its index. This is the hook point for visual-editing overlays, analytics boundaries, or per-block chrome.
24
60
 
@@ -30,11 +66,16 @@ The boundary validator, exported for consumers who want to fail at fetch time ra
30
66
 
31
67
  - `Registry<T>` — the registry's captured shape.
32
68
  - `BlockConfig` — one untrusted config entry: `{ type: string; props?: unknown; id?: string | number }`.
33
- - `ConfigFor<R>` — the closed, fully typed config union for a registry; for configs authored in TypeScript.
69
+ - `ConfigFor<R>` — the closed, fully typed config union for a registry, for configs authored in TypeScript. Typed as each schema's *input*, so fields with defaults are optional to write.
34
70
  - `FallbackProps` — the fallback contract.
35
71
  - `StandardSchemaV1` — re-exported for custom schema implementations.
36
72
 
37
- ## Notes
73
+ ## Gotchas
38
74
 
39
- - Async schemas are not supported (React render is synchronous); an async validation routes the entry to the fallback with an explanatory issue.
75
+ - **`exactOptionalPropertyTypes` + zod's `.optional()`**: with that compiler flag enabled, a schema field like `z.string().optional()` produces `prop?: string | undefined`, which is not assignable to a component prop declared `prop?: string` — so the registry's compile-time check (correctly) rejects the registration. Either declare such component props as `prop?: string | undefined`, or leave the flag off in the consuming app.
76
+ - **Async schemas are not supported** (React render is synchronous); an async validation routes the entry to the fallback with an explanatory issue.
40
77
  - Entries use `id` for React keys when present, falling back to `type-index`.
78
+
79
+ ## License
80
+
81
+ MIT © [Triantafillos Terzidis](https://github.com/terzidest)
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "blockprint",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Typed, config-driven block renderer for React. Render arbitrary UI trees from untrusted JSON with compile-time registry safety and runtime schema validation.",
5
5
  "license": "MIT",
6
+ "homepage": "https://blockprint.terzidest.com",
7
+ "bugs": {
8
+ "url": "https://github.com/terzidest/blockprint/issues"
9
+ },
6
10
  "repository": {
7
11
  "type": "git",
8
12
  "url": "git+https://github.com/terzidest/blockprint.git",