create-rindle 0.1.6 → 0.2.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
@@ -22,7 +22,8 @@ pnpm dev
22
22
  A minimal but real three-tier Rindle app (a tiny forum-of-rooms with **live message counts**):
23
23
 
24
24
  - **Browser** — a TanStack Start SPA whose data layer is Rindle's in-process wasm IVM engine: local
25
- instant reads, optimistic writes, live `subscribe`, and a dev-only Rindle devtools pane.
25
+ instant reads, optimistic writes, fragment-rooted `useRoot` reads, and a dev-only Rindle devtools
26
+ pane.
26
27
  - **API server** — the authority: named-query resolution, authoritative SQL mutators, and policy (a
27
28
  `"spam"` rejection demo shows the optimistic snap-back + toast).
28
29
  - **Daemon** (`rindled`) — owns the SQLite data + live incremental views, streamed to subscribers.
@@ -34,6 +35,19 @@ the dev loop), so the TypeScript can't drift from the DDL.
34
35
  The prebuilt `rindle` + `rindled` binaries come from `@rindle/cli` (per-platform, installed as a dev
35
36
  dependency) — **no Rust toolchain required**.
36
37
 
38
+ ## Fragment flow in the template
39
+
40
+ The generated routes use the current co-located fragment pattern:
41
+
42
+ - `src/components/*.queries.ts` defines each component's `defineFragment` beside the named
43
+ `defineQuery` that roots it.
44
+ - The home route preloads `roomsQuery()` for SSR, then calls
45
+ `useRoot(roomsQuery, RoomCardFragment)` to receive opaque room refs.
46
+ - Row components call `useFragment(RoomCardFragment, room)` to open narrow local reads without a
47
+ new server subscription.
48
+ - The room detail route calls `useRoot(roomDetailQuery, id)` when the route itself owns the root
49
+ fields and passes child message refs down with `fragmentKey(ref)` list keys.
50
+
37
51
  ## Usage
38
52
 
39
53
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-rindle",
3
- "version": "0.1.6",
3
+ "version": "0.2.0",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -8,6 +8,8 @@ import { formatDateTime } from "../lib/format.ts";
8
8
 
9
9
  export function MessageCard({ message }: { message: MessageCardRef }) {
10
10
  const data = useFragment(MessageCardFragment, message);
11
+ if (!data) return null;
12
+
11
13
  return (
12
14
  <article className="app-message">
13
15
  <div className="app-message-head">
@@ -9,6 +9,8 @@ import type { RoomCardRef } from "./RoomCard.queries.ts";
9
9
 
10
10
  export function RoomCard({ room }: { room: RoomCardRef }) {
11
11
  const data = useFragment(RoomCardFragment, room);
12
+ if (!data) return null;
13
+
12
14
  return (
13
15
  <li className="app-room">
14
16
  <Link to="/r/$id" params={{ id: data.id }} className="app-room-link">
@@ -13,7 +13,9 @@ import { MessageCardFragment } from "./MessageCard.queries.ts";
13
13
  export const RoomDetailFragment = defineFragment(room, (r) =>
14
14
  r
15
15
  .select("id", "name", "createdAt")
16
- .sub("messages", rels.roomMessages, (m) => m.include(MessageCardFragment).orderBy("createdAt", "asc").orderBy("id", "asc")),
16
+ .sub("messages", rels.roomMessages, MessageCardFragment, (m) =>
17
+ m.orderBy("createdAt", "asc").orderBy("id", "asc")
18
+ ),
17
19
  );
18
20
  export type RoomDetailRef = FragmentRef<typeof RoomDetailFragment>;
19
21
 
@@ -2,12 +2,11 @@
2
2
  // room. Its loader seeds the rooms query for first paint (SSR); after hydration the wasm engine owns
3
3
  // the live read — post a message in any room and its count here updates with no polling.
4
4
 
5
- import { useMemo } from "react";
6
5
  import { createFileRoute } from "@tanstack/react-router";
7
- import { useQuery, useQueryStatus } from "@rindle/react";
6
+ import { fragmentKey, useRoot } from "@rindle/react";
8
7
  import type { DehydratedState } from "@rindle/client";
9
8
 
10
- import { roomsQuery } from "../components/RoomCard.queries.ts";
9
+ import { RoomCardFragment, roomsQuery } from "../components/RoomCard.queries.ts";
11
10
  import { RoomCard } from "../components/RoomCard.tsx";
12
11
  import { NewRoomForm } from "../components/NewRoomForm.tsx";
13
12
 
@@ -23,9 +22,7 @@ export const Route = createFileRoute("/")({
23
22
  });
24
23
 
25
24
  function Home() {
26
- const query = useMemo(() => roomsQuery(), []);
27
- const rooms = useQuery(query);
28
- const status = useQueryStatus(query);
25
+ const [rooms, { status }] = useRoot(roomsQuery, RoomCardFragment);
29
26
  const loading = status !== "complete" && rooms.length === 0;
30
27
 
31
28
  return (
@@ -42,7 +39,7 @@ function Home() {
42
39
  ) : (
43
40
  <ul className="app-rooms">
44
41
  {rooms.map((room) => (
45
- <RoomCard key={room.id} room={room} />
42
+ <RoomCard key={fragmentKey(room)} room={room} />
46
43
  ))}
47
44
  </ul>
48
45
  )}
@@ -2,9 +2,8 @@
2
2
  // single detail query is seeded by the loader for first paint; after hydration the wasm engine owns
3
3
  // the live read and every new message streams in.
4
4
 
5
- import { useMemo } from "react";
6
5
  import { Link, createFileRoute } from "@tanstack/react-router";
7
- import { useQuery, useQueryStatus } from "@rindle/react";
6
+ import { fragmentKey, useRoot } from "@rindle/react";
8
7
  import type { DehydratedState } from "@rindle/client";
9
8
 
10
9
  import { roomDetailQuery } from "../components/RoomView.queries.ts";
@@ -22,9 +21,7 @@ export const Route = createFileRoute("/r/$id")({
22
21
 
23
22
  function RoomView() {
24
23
  const { id } = Route.useParams();
25
- const query = useMemo(() => roomDetailQuery(id), [id]);
26
- const room = useQuery(query);
27
- const status = useQueryStatus(query);
24
+ const [room, { status }] = useRoot(roomDetailQuery, id);
28
25
 
29
26
  if (!room) {
30
27
  return (
@@ -50,7 +47,7 @@ function RoomView() {
50
47
  {messages.length === 0 ? (
51
48
  <p className="app-empty">No messages yet — say something below.</p>
52
49
  ) : (
53
- messages.map((message) => <MessageCard key={message.id} message={message} />)
50
+ messages.map((message) => <MessageCard key={fragmentKey(message)} message={message} />)
54
51
  )}
55
52
  </div>
56
53