creo 0.1.0 → 0.2.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nik Mostovoy (xnim.me)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,5 +1,350 @@
1
- # creo
1
+ # Creo: Lightweight UI framework
2
2
 
3
- UI library utilising vdom to render for selected targets:
4
- - HTML
5
- - Canvas (using skia)
3
+ > _"There are many UI framework but this one is mine!"_
4
+
5
+ - Lightweight, "Streaming" UI framework.
6
+ - No JSX / templates / compiler
7
+ - Simplicity over tons of features
8
+ - Easy to start, easy to work with
9
+ - Extendability by defining renderers
10
+
11
+ ## Philosophy
12
+
13
+ ### Streaming the UI
14
+
15
+ > _"There is a point of no return"_
16
+
17
+ Most frameworks are based on "returning" the data. Creo started back in (early 2025)[https://x.com/xnimorz/status/1876212381568905348] with the idea, that "return" is not the point. We can intersect the components during rendering cycle to handle it.
18
+
19
+ It comes with some limitations, yes, like using VirtualDOM, instead of compiling it all and putting just renderer instructions but I believe VDOM is not the weakest point of modern software engineering.
20
+
21
+ The weakest point is complexity. Every time I looked at "framework A/B/C" I always find myself struggling with remembering all "you should do X/Y/Z in order to make it work".
22
+
23
+ And look.. I get it, it's important to follow the rules (e.g. I remember early svelte they experience problems with props, where incorrect usage de-optimised perfromance a lot). But after getting to the point where "a little bit too much" of the rules, I experience mental overload.
24
+
25
+ This is why I want to have a framework which I enjoy using on daily basis. And one major thing I don't like is "too many DSLs" to remember.
26
+
27
+ So... Why not just to use JAVASCRIPT?
28
+
29
+ ```ts
30
+ render() {
31
+ h1(_, "Title"); // first child
32
+ p(_, "Paragraph"); // second child
33
+ ul(_, () => { // third child, with its own children streamed inside
34
+ li(_, "One");
35
+ li(_, "Two");
36
+ });
37
+ }
38
+ ```
39
+
40
+ ### Native control flow
41
+
42
+ No `v-if`, no `{condition && <X/>}`, no `.map()` wrappers.
43
+ Creo renders imperatively: use `if`, `for`, `while`, `switch`, or any JavaScript you want.
44
+
45
+ **The language is the template language on its own**:
46
+
47
+ ```ts
48
+ render() {
49
+ if (loading.get()) {
50
+ Spinner();
51
+ } else if (error.get()) {
52
+ ErrorBanner({ message: error.get() });
53
+ } else {
54
+ for (const item of items.get()) {
55
+ ListItem({ key: item.id, data: item });
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ It reduces mental load. We don't require people to learn:
62
+
63
+ - Any specific templating syntax
64
+ - Framework-specific iteration helpers.
65
+ - Ternary gymnastics.
66
+
67
+ ### Minimal model
68
+
69
+ The entire reactivity model is three concepts:
70
+
71
+ - **`use(value)`**: local state. Call `.get()`, `.set()`, `.update()`;
72
+ - **`store.new(value)`**: global store. Same interface. Any view can subscribe with `use(store)`, same to state mechanics. Library takes a shot to manage subscriptions;
73
+ - **`props()`**: read-only, passed by parent;
74
+
75
+ One of major ideas under the framework "if user can do it with same efficiency, let it outside framework zone". I want to keep the framework efficient, but with super limited API blast radius.
76
+
77
+ ### Pluggable renderers
78
+
79
+ Creo allows any renderer overrides.
80
+
81
+ - **`HtmlRender`** — DOM output for browsers
82
+ - **`JsonRender`** — JSON AST for testing and serialization
83
+ - **`HtmlStringRenderer`** — HTML strings for SSR
84
+
85
+ ```ts
86
+ // Browser
87
+ createApp(() => App(), new HtmlRender(document.getElementById("app")!)).mount();
88
+
89
+ // Server
90
+ const renderer = new StringRender();
91
+ const engine = new Engine(renderer);
92
+ engine.createRoot(() => App(), {});
93
+ engine.render();
94
+ return renderer.renderToString();
95
+ ```
96
+
97
+ ## Quick Start
98
+
99
+ ```ts
100
+ import { createApp, view, div, text, button, _ } from "creo";
101
+ import { HtmlRender } from "creo";
102
+
103
+ const Counter = view<{ initial: number }>(({ props, use }) => {
104
+ const count = use(props().initial);
105
+ const increment = () => count.update((n) => n + 1);
106
+
107
+ return {
108
+ render() {
109
+ button({ onClick: increment }, () => {
110
+ text(count.get());
111
+ });
112
+ },
113
+ };
114
+ });
115
+
116
+ createApp(
117
+ () => Counter({ initial: 0 }),
118
+ new HtmlRender(document.getElementById("app")!),
119
+ ).mount();
120
+ ```
121
+
122
+ ## Core Concepts
123
+
124
+ ### Views
125
+
126
+ Views are components. Define them with `view<Props>()`, which takes a setup function called once per lifecycle. The setup returns a `ViewBody` with a `render()` function called on every update.
127
+
128
+ ```ts
129
+ const Greeting = view<{ name: string }>(({ props }) => ({
130
+ render() {
131
+ div({ class: "greeting" }, `Hello, ${props().name}!`);
132
+ },
133
+ }));
134
+
135
+ // Usage in another view's render:
136
+ Greeting({ name: "world" });
137
+ ```
138
+
139
+ ### State
140
+
141
+ Local reactive state via `use()`. Called once in the setup function, read via `.get()` in render.
142
+
143
+ ```ts
144
+ const Toggle = view(({ use }) => {
145
+ const on = use(false);
146
+ const toggle = () => on.update((v) => !v);
147
+
148
+ return {
149
+ render() {
150
+ button({ onClick: toggle }, () => {
151
+ text(on.get() ? "ON" : "OFF");
152
+ });
153
+ },
154
+ };
155
+ });
156
+ ```
157
+
158
+ ### Store
159
+
160
+ Global shared state. Create outside views, subscribe inside with `use(store)`.
161
+
162
+ ```ts
163
+ import { store } from "creo";
164
+
165
+ const ThemeStore = store.new<"light" | "dark">("light");
166
+
167
+ // Any view can subscribe:
168
+ const ThemeDisplay = view(({ use }) => {
169
+ const theme = use(ThemeStore);
170
+ return {
171
+ render() {
172
+ text(`Theme: ${theme.get()}`);
173
+ },
174
+ };
175
+ });
176
+
177
+ // Update from anywhere:
178
+ ThemeStore.set("dark");
179
+ ```
180
+
181
+ ### Slots (Children)
182
+
183
+ Pass children via a slot callback — the second argument to any view or primitive.
184
+
185
+ ```ts
186
+ const Card = view<{ title: string }>(({ props, slot }) => ({
187
+ render() {
188
+ div({ class: "card" }, () => {
189
+ h1(_, () => text(props().title));
190
+ div({ class: "card-body" }, slot);
191
+ });
192
+ },
193
+ }));
194
+
195
+ // Usage:
196
+ Card({ title: "Hello" }, () => {
197
+ p(_, "Card content here.");
198
+ });
199
+ ```
200
+
201
+ ### Lifecycle Hooks
202
+
203
+ ```ts
204
+ const MyView = view<{ value: number }>(({ props }) => ({
205
+ onMount() {
206
+ /* after first render */
207
+ },
208
+ onUpdateBefore() {
209
+ /* before re-render */
210
+ },
211
+ onUpdateAfter() {
212
+ /* after re-render */
213
+ },
214
+ shouldUpdate(nextProps) {
215
+ return nextProps.value !== props().value;
216
+ },
217
+ render() {
218
+ text(props().value);
219
+ },
220
+ }));
221
+ ```
222
+
223
+ ### Keyed Lists
224
+
225
+ Use `key` in props for efficient list reconciliation:
226
+
227
+ ```ts
228
+ for (const item of items.get()) {
229
+ TodoItem({ key: item.id, text: item.text, done: item.done });
230
+ }
231
+ ```
232
+
233
+ ### Conditional Rendering
234
+
235
+ Standard JavaScript control flow — no special syntax:
236
+
237
+ ```ts
238
+ render() {
239
+ if (editing.get()) {
240
+ Editor({ key: id, value });
241
+ } else {
242
+ Display({ key: id, value });
243
+ }
244
+ }
245
+ ```
246
+
247
+ ## Conventions
248
+
249
+ ### `_` for Empty Props
250
+
251
+ Use `_` (exported from `creo`) instead of `{}` when no props are needed:
252
+
253
+ ```ts
254
+ h1(_, "Title"); // not h1({}, "Title")
255
+ div(_, () => { ... }); // not div({}, () => { ... })
256
+ ```
257
+
258
+ ### Inline Strings
259
+
260
+ Pass strings directly as slots instead of wrapping in `() => text(...)`:
261
+
262
+ ```ts
263
+ button({ onClick: handler }, "Click me"); // not () => text("Click me")
264
+ li(_, "Item text"); // not () => text("Item text")
265
+ ```
266
+
267
+ Use `text()` only for dynamic values or when mixing text with other elements in a function slot.
268
+
269
+ ## Create App
270
+
271
+ Scaffold a new Creo project with one command:
272
+
273
+ ```bash
274
+ bunx creo-create-app my-app
275
+ cd my-app
276
+ bun install
277
+ bun run dev
278
+ ```
279
+
280
+ The CLI prompts whether to include a **Hono server**. When enabled, the generated project includes:
281
+
282
+ - A Hono backend (`src/server.ts`) serving static files from `dist/` by default
283
+ - Vite dev server with `/api` proxy to Hono
284
+ - Scripts for both dev (`dev:server` + `dev`) and production (`build` + `start`)
285
+
286
+ Without a server, you get a pure client-side Vite + Creo setup.
287
+
288
+ See [`packages/creo-create-app/`](./packages/creo-create-app/) for full details.
289
+
290
+ ## Create Tauri App
291
+
292
+ Build cross-platform desktop and mobile apps with Creo + Tauri v2:
293
+
294
+ ```bash
295
+ bunx creo-create-tauri-app my-app
296
+ cd my-app
297
+ bun install
298
+ bun run tauri:dev
299
+ ```
300
+
301
+ The CLI lets you select target platforms: **macOS**, **Windows**, **Linux**, **iOS**, **Android**, and **Web**. Desktop targets work immediately; mobile targets require a one-time `tauri ios init` / `tauri android init` after scaffolding.
302
+
303
+ The generated project includes a full Tauri v2 setup (`src-tauri/`) with Rust backend, Vite frontend, and a sample Tauri command.
304
+
305
+ See [`packages/creo-create-tauri-app/`](./packages/creo-create-tauri-app/) for full details.
306
+
307
+ ## Router
308
+
309
+ Hash-based router available as `creo-router` (separate package):
310
+
311
+ ```ts
312
+ import { createRouter } from "creo-router";
313
+
314
+ const { routeStore, navigate, RouterView, Link } = createRouter({
315
+ routes: [
316
+ { path: "/", view: () => HomePage() },
317
+ { path: "/users/:id", view: () => UserPage() },
318
+ ],
319
+ fallback: () => NotFoundPage(),
320
+ });
321
+
322
+ // Navigation:
323
+ Link({ href: "/users/42" }, "User 42");
324
+ navigate("/users/42"); // programmatic
325
+
326
+ // Read params:
327
+ const route = use(routeStore);
328
+ route.get().params.id; // "42"
329
+ ```
330
+
331
+ ## Development
332
+
333
+ ```bash
334
+ bun install
335
+ bun test packages/creo/src/ # Run tests
336
+ bun run build # Build all packages
337
+ bun run typecheck # Type-check
338
+
339
+ # Run examples:
340
+ cd examples/todo && bun install && bun run dev
341
+ cd examples/router && bun install && bun run dev
342
+
343
+ # Version management:
344
+ bun run version:patch # Bump patch version across all packages
345
+ bun run version:minor # Bump minor version
346
+ bun run version:major # Bump major version
347
+
348
+ # Publishing:
349
+ bun run publish:all # Dry-run publish (pass --no-dry-run to publish for real)
350
+ ```
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Longest Increasing Subsequence — O(n log n).
3
+ *
4
+ * Given an array of numbers, returns the Set of *indices* into that array
5
+ * whose elements form the longest strictly increasing subsequence.
6
+ * Entries equal to -1 are skipped (treated as "not present").
7
+ *
8
+ * Used by the keyed reconciler to identify the maximal set of children
9
+ * that are already in correct relative order and don't need DOM moves.
10
+ */
11
+ export declare function lis(arr: number[]): Set<number>;
@@ -2,5 +2,7 @@ export type None = null | undefined;
2
2
  export type Just<T> = T;
3
3
  export type Maybe<T> = Just<T> | None;
4
4
  export declare function just<T>(maybe: Maybe<T>, errorMessage?: string): asserts maybe is Just<T>;
5
+ export declare function isJust<T>(maybe: Maybe<T>): maybe is Just<T>;
6
+ export declare function isNone<T>(maybe: Maybe<T>): maybe is None;
5
7
  export declare function withDefault<T, K>(v: Maybe<T>, alternative: K): K | NonNullable<T>;
6
8
  export declare const _: undefined;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { createApp } from "./public/app";
2
2
  export { view } from "./public/view";
3
- export type { ViewBody, ViewFn, Slot, Children, PublicView } from "./public/view";
3
+ export type { ViewBody, ViewFn, Slot, SlotContent, PublicView } from "./public/view";
4
4
  export type { Reactive, Use } from "./public/state";
5
5
  export { State } from "./public/state";
6
6
  export { Store, store, isStore } from "./public/store";
@@ -12,7 +12,7 @@ export type { IRender } from "./render/render_interface";
12
12
  export { HtmlRender } from "./render/html_render";
13
13
  export { JsonRender } from "./render/json_render";
14
14
  export type { JsonNode } from "./render/json_render";
15
- export { StringRender } from "./render/string_render";
15
+ export { HtmlStringRender, StringRender } from "./render/string_render";
16
16
  export { Engine, type Scheduler } from "./internal/engine";
17
17
  export { type Maybe, type None, type Just, just, withDefault, _ } from "./functional/maybe";
18
18
  export type { Key } from "./functional/key";