create-rindle 0.10.3 → 0.10.5
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 +8 -81
- package/index.mjs +20 -5
- package/lib/scaffold.mjs +35 -8
- package/package.json +4 -1
- package/templates/minimal/AGENTS.md +39 -87
- package/templates/minimal/README.md +20 -3
package/README.md
CHANGED
|
@@ -1,90 +1,17 @@
|
|
|
1
1
|
# create-rindle
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Create a [Rindle](https://rindle.sh) application with TanStack Start, live queries,
|
|
4
|
+
optimistic writes, SQL migrations, and local guidance for coding agents.
|
|
5
5
|
|
|
6
6
|
```bash
|
|
7
7
|
npm create rindle@latest my-app
|
|
8
|
-
# or
|
|
9
|
-
npx create-rindle my-app
|
|
10
|
-
# or: pnpm create rindle my-app · yarn create rindle my-app · bun create rindle my-app
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
Then:
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
8
|
cd my-app
|
|
17
|
-
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
Full docs — what the template contains and how to grow it:
|
|
21
|
-
**[rindle.sh/docs/create-rindle](https://rindle.sh/docs/create-rindle)** · markdown
|
|
22
|
-
mirror: [`create-rindle.md`](https://rindle.sh/docs/create-rindle.md) · for agents:
|
|
23
|
-
[llms.txt](https://rindle.sh/llms.txt)
|
|
24
|
-
|
|
25
|
-
## What you get
|
|
26
|
-
|
|
27
|
-
A minimal but real three-tier Rindle app (a tiny forum-of-rooms with **live message counts**):
|
|
28
|
-
|
|
29
|
-
- **Browser** — a TanStack Start SPA whose data layer is Rindle's in-process wasm IVM engine: local
|
|
30
|
-
instant reads, optimistic writes, fragment-rooted `useRoot` reads, and a dev-only Rindle devtools
|
|
31
|
-
pane.
|
|
32
|
-
- **API server** — the authority: named-query resolution, authoritative SQL mutators, and policy (a
|
|
33
|
-
`"spam"` rejection demo shows the optimistic snap-back + toast).
|
|
34
|
-
- **Daemon** (`rindled`) — owns the SQLite data + live incremental views, streamed to subscribers.
|
|
35
|
-
|
|
36
|
-
The schema is **SQL-first**: `migrations/*.sql` is the source of truth, and the `@rindle/client`
|
|
37
|
-
schema is generated from it into `shared/schema.gen.ts` (`rindle up --migrate --gen shared/schema.gen.ts --watch` in
|
|
38
|
-
the dev loop), so the TypeScript can't drift from the DDL.
|
|
39
|
-
|
|
40
|
-
The prebuilt `rindle` + `rindled` binaries come from `@rindle/cli` (per-platform, installed as a dev
|
|
41
|
-
dependency) — **no Rust toolchain required**.
|
|
42
|
-
|
|
43
|
-
## Fragment flow in the template
|
|
44
|
-
|
|
45
|
-
The generated routes use the current co-located fragment pattern:
|
|
46
|
-
|
|
47
|
-
- `src/components/*.queries.ts` defines each component's `defineFragment` beside the named
|
|
48
|
-
`defineQuery` that roots it.
|
|
49
|
-
- The home route declares `roomsQuery()` through `@rindle/tanstack`'s `rindle.loader`, which seeds
|
|
50
|
-
SSR and waits for the same live query on client navigation, then calls
|
|
51
|
-
`useRoot(roomsQuery, RoomCardFragment)` to receive opaque room refs.
|
|
52
|
-
- Row components call `useFragment(RoomCardFragment, room)` to open narrow local reads without a
|
|
53
|
-
new server subscription.
|
|
54
|
-
- The room detail route calls `useRoot(roomDetailQuery, id)` when the route itself owns the root
|
|
55
|
-
fields and passes child message refs down with `fragmentKey(ref)` list keys.
|
|
56
|
-
|
|
57
|
-
## Usage
|
|
58
|
-
|
|
59
|
-
```
|
|
60
|
-
create-rindle [directory] [options]
|
|
61
|
-
|
|
62
|
-
Options:
|
|
63
|
-
--no-install Don't run the package-manager install after scaffolding
|
|
64
|
-
--pm <name> Package manager: npm | pnpm | yarn | bun (default: auto-detected)
|
|
65
|
-
--link Internal: wire @rindle/* to this monorepo's workspace (for apps/* in the repo)
|
|
66
|
-
-h, --help Show help
|
|
9
|
+
npm run dev
|
|
67
10
|
```
|
|
68
11
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
aliases the in-repo examples use, so a scaffold dropped into `apps/` typechecks against source. CI
|
|
72
|
-
uses it to typecheck the generated app on every change.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
## Devtools in generated apps
|
|
76
|
-
|
|
77
|
-
The default template mounts `@rindle/react-devtools` in development and attaches
|
|
78
|
-
`@rindle/devtools` to the generated Rindle client. Click the floating **🌊 Rindle** launcher to
|
|
79
|
-
inspect the optimistic mutation timeline, live queries, and raw delta stream. Both packages are
|
|
80
|
-
imported behind `import.meta.env.DEV`, so they tree-shake out of production builds.
|
|
81
|
-
|
|
82
|
-
## Templates
|
|
83
|
-
|
|
84
|
-
| name | description |
|
|
85
|
-
|---|---|
|
|
86
|
-
| `minimal` (default) | rooms + messages with live counts, optimistic writes, SSR, the auth seam |
|
|
87
|
-
|
|
88
|
-
## Requirements
|
|
12
|
+
Published scaffold releases use matching Rindle packages and documentation.
|
|
13
|
+
Use `--no-agents` to skip local documentation and skill installation.
|
|
89
14
|
|
|
90
|
-
|
|
15
|
+
- [Scaffold guide](https://rindle.sh/docs/create-rindle)
|
|
16
|
+
- [Agent installation and version checks](https://rindle.sh/docs/for-agents)
|
|
17
|
+
- [Documentation index for agents](https://rindle.sh/llms.txt)
|
package/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// `npm create rindle@latest [dir]` / `npx create-rindle [dir]` — scaffold a SQL-first Rindle app on
|
|
3
|
-
// TanStack Start.
|
|
4
|
-
//
|
|
3
|
+
// TanStack Start. The bundled agent context and scaffold use Node built-ins. No native
|
|
4
|
+
// binaries are needed until the generated application's development server starts.
|
|
5
5
|
//
|
|
6
6
|
// create-rindle my-app # scaffold ./my-app (the only template today: "minimal")
|
|
7
7
|
// create-rindle my-app --no-install # skip the package-manager install
|
|
@@ -12,6 +12,7 @@ import { spawnSync } from "node:child_process";
|
|
|
12
12
|
import { createInterface } from "node:readline/promises";
|
|
13
13
|
import { fileURLToPath } from "node:url";
|
|
14
14
|
import { dirname, join, resolve } from "node:path";
|
|
15
|
+
import { setupAgentContext } from "@rindle/agent-context";
|
|
15
16
|
|
|
16
17
|
import { isValidPackageName, packageNameFromDir, scaffold } from "./lib/scaffold.mjs";
|
|
17
18
|
|
|
@@ -19,11 +20,12 @@ const here = dirname(fileURLToPath(import.meta.url));
|
|
|
19
20
|
const TEMPLATE = "minimal";
|
|
20
21
|
|
|
21
22
|
function parseArgs(argv) {
|
|
22
|
-
const opts = { dir: undefined, install: true, pm: undefined, link: false, help: false };
|
|
23
|
+
const opts = { dir: undefined, install: true, agents: true, pm: undefined, link: false, help: false };
|
|
23
24
|
for (let i = 0; i < argv.length; i++) {
|
|
24
25
|
const a = argv[i];
|
|
25
26
|
if (a === "--help" || a === "-h") opts.help = true;
|
|
26
27
|
else if (a === "--no-install") opts.install = false;
|
|
28
|
+
else if (a === "--no-agents") opts.agents = false;
|
|
27
29
|
else if (a === "--link") opts.link = true;
|
|
28
30
|
else if (a === "--pm") opts.pm = argv[++i];
|
|
29
31
|
else if (a.startsWith("--pm=")) opts.pm = a.slice("--pm=".length);
|
|
@@ -31,6 +33,10 @@ function parseArgs(argv) {
|
|
|
31
33
|
else if (opts.dir === undefined) opts.dir = a;
|
|
32
34
|
else fail(`unexpected argument: ${a}`);
|
|
33
35
|
}
|
|
36
|
+
if (opts.pm !== undefined && !["npm", "pnpm", "yarn", "bun"].includes(opts.pm)) {
|
|
37
|
+
fail("--pm must be npm, pnpm, yarn, or bun");
|
|
38
|
+
}
|
|
39
|
+
if (argv.includes("--pm") && opts.pm === undefined) fail("--pm requires a package manager");
|
|
34
40
|
return opts;
|
|
35
41
|
}
|
|
36
42
|
|
|
@@ -47,6 +53,7 @@ Usage:
|
|
|
47
53
|
|
|
48
54
|
Options:
|
|
49
55
|
--no-install Don't run the package-manager install after scaffolding
|
|
56
|
+
--no-agents Skip local documentation, skill, and agent instruction setup
|
|
50
57
|
--pm <name> Package manager to use (npm | pnpm | yarn | bun); default: auto-detect
|
|
51
58
|
--link Internal: wire @rindle/* to this monorepo's workspace (for apps/* in the repo)
|
|
52
59
|
-h, --help Show this help
|
|
@@ -94,7 +101,7 @@ async function main() {
|
|
|
94
101
|
process.stdout.write(`\n Scaffolding a Rindle app in ${targetDir}\n`);
|
|
95
102
|
let created;
|
|
96
103
|
try {
|
|
97
|
-
created = await scaffold({ templateDir, targetDir, projectName, link: opts.link });
|
|
104
|
+
created = await scaffold({ templateDir, targetDir, projectName, link: opts.link, agents: opts.agents });
|
|
98
105
|
} catch (err) {
|
|
99
106
|
fail(err instanceof Error ? err.message : String(err));
|
|
100
107
|
}
|
|
@@ -105,8 +112,12 @@ async function main() {
|
|
|
105
112
|
process.stdout.write(`\n Installing dependencies with ${pm}…\n\n`);
|
|
106
113
|
const install = spawnSync(pm, ["install"], { cwd: targetDir, stdio: "inherit", shell: process.platform === "win32" });
|
|
107
114
|
if (install.status !== 0) {
|
|
108
|
-
process.
|
|
115
|
+
process.stderr.write(`\n The project was created, but dependency installation failed${install.error ? `: ${install.error.message}` : "."}\n`);
|
|
116
|
+
process.stderr.write(` Run \`${pm} install\` in ${targetDir}${opts.agents ? `, then \`${pm === "npm" ? "npx" : `${pm} exec`} rindle agents setup\` to verify its documentation` : ""}.\n`);
|
|
117
|
+
process.exitCode = 1;
|
|
118
|
+
return;
|
|
109
119
|
}
|
|
120
|
+
if (opts.agents) await setupAgentContext({ projectDir: targetDir });
|
|
110
121
|
}
|
|
111
122
|
|
|
112
123
|
const run = pm === "npm" ? "npm run" : pm;
|
|
@@ -114,11 +125,15 @@ async function main() {
|
|
|
114
125
|
`\n Done. Next steps:\n\n` +
|
|
115
126
|
` cd ${dir}\n` +
|
|
116
127
|
(opts.install ? "" : ` ${pm} install\n`) +
|
|
128
|
+
(!opts.install && opts.agents ? ` ${pm === "npm" ? "npx" : `${pm} exec`} rindle agents setup\n` : "") +
|
|
117
129
|
` ${run} dev\n\n` +
|
|
118
130
|
` The migrations in migrations/*.sql are the source of truth — \`${run} dev\` applies them,\n` +
|
|
119
131
|
` generates shared/schema.gen.ts, and boots the topology pair + Vite (app + /api/rindle routes).\n` +
|
|
120
132
|
` Open two browser windows, create a room, and watch writes sync live.\n\n`,
|
|
121
133
|
);
|
|
134
|
+
if (opts.agents) {
|
|
135
|
+
process.stdout.write(" Agent guidance is in AGENTS.md and CLAUDE.md. Local docs: .rindle/agent-context/index.md\n");
|
|
136
|
+
}
|
|
122
137
|
}
|
|
123
138
|
|
|
124
139
|
main().catch((err) => fail(err instanceof Error ? err.stack ?? err.message : String(err)));
|
package/lib/scaffold.mjs
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
// The scaffold engine: copy a template tree into a target directory, substituting the project name
|
|
2
2
|
// and renaming the `_`-prefixed dotfiles npm refuses to publish (`_gitignore` → `.gitignore`). Pure
|
|
3
|
-
// Node built-ins
|
|
3
|
+
// Node built-ins plus the bundled agent-context package; no native tools are needed.
|
|
4
4
|
//
|
|
5
5
|
// `--link` mode (internal/dev) rewrites the generated app to consume the @rindle/* packages from the
|
|
6
6
|
// surrounding pnpm WORKSPACE instead of npm: deps become `workspace:*`, and vite/tsconfig switch to the
|
|
7
7
|
// `@rindle/source` aliases the in-repo examples use. That lets us scaffold straight into `apps/` and
|
|
8
8
|
// typecheck the output against the real source — the "CI typechecks the generated app" discipline.
|
|
9
9
|
|
|
10
|
-
import { cp, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
11
|
-
import { existsSync } from "node:fs";
|
|
10
|
+
import { cp, lstat, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
12
11
|
import { basename, join, relative } from "node:path";
|
|
12
|
+
import { setupAgentContext } from "@rindle/agent-context";
|
|
13
|
+
|
|
14
|
+
const SCAFFOLD_VERSION = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
13
15
|
|
|
14
16
|
/** The token the template files carry wherever the project's package name belongs. */
|
|
15
17
|
export const PROJECT_NAME_TOKEN = "__PROJECT_NAME__";
|
|
@@ -143,17 +145,42 @@ export async function applyWorkspaceLinks(targetDir) {
|
|
|
143
145
|
await writeFile(join(targetDir, "tsconfig.json"), WORKSPACE_TSCONFIG);
|
|
144
146
|
}
|
|
145
147
|
|
|
146
|
-
/**
|
|
147
|
-
export async function
|
|
148
|
-
if (
|
|
149
|
-
|
|
148
|
+
/** Pin the generated app to the same release as its template and documentation. */
|
|
149
|
+
export async function pinRindleVersion(targetDir, version) {
|
|
150
|
+
if (version === "0.0.0") return; // An unreleased source checkout has no registry release to pin.
|
|
151
|
+
const pkgPath = join(targetDir, "package.json");
|
|
152
|
+
const pkg = JSON.parse(await readFile(pkgPath, "utf8"));
|
|
153
|
+
for (const field of ["dependencies", "devDependencies"]) {
|
|
154
|
+
for (const name of Object.keys(pkg[field] ?? {})) {
|
|
155
|
+
if (name.startsWith("@rindle/")) pkg[field][name] = version;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
await writeFile(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Copy a new application, then install its bundled agent documentation. */
|
|
162
|
+
export async function scaffold({ templateDir, targetDir, projectName, link = false, agents = true, rindleVersion = SCAFFOLD_VERSION }) {
|
|
163
|
+
const targetStat = await lstat(targetDir).catch(error => {
|
|
164
|
+
if (error.code === "ENOENT") return null;
|
|
165
|
+
throw error;
|
|
166
|
+
});
|
|
167
|
+
if (targetStat) {
|
|
168
|
+
if (targetStat.isSymbolicLink() || !targetStat.isDirectory()) {
|
|
169
|
+
throw new Error(`target must be a real directory, not a file or symlink: ${targetDir}`);
|
|
170
|
+
}
|
|
171
|
+
const entries = await readdir(targetDir);
|
|
150
172
|
if (entries.length > 0) {
|
|
151
173
|
throw new Error(`target directory is not empty: ${targetDir}`);
|
|
152
174
|
}
|
|
153
175
|
}
|
|
154
176
|
const created = await copyTemplate(templateDir, targetDir, projectName);
|
|
155
177
|
if (link) await applyWorkspaceLinks(targetDir);
|
|
156
|
-
|
|
178
|
+
else await pinRindleVersion(targetDir, rindleVersion);
|
|
179
|
+
if (agents) {
|
|
180
|
+
const context = await setupAgentContext({ projectDir: targetDir, allowUninstalled: true, deferVersionCheck: true });
|
|
181
|
+
created.push(...context.files);
|
|
182
|
+
}
|
|
183
|
+
return [...new Set(created)].sort();
|
|
157
184
|
}
|
|
158
185
|
|
|
159
186
|
// Re-exported for the CLI's "is this a sane package name?" check.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-rindle",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.5",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
"engines": {
|
|
22
22
|
"node": ">=22"
|
|
23
23
|
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@rindle/agent-context": "0.10.5"
|
|
26
|
+
},
|
|
24
27
|
"keywords": [
|
|
25
28
|
"rindle",
|
|
26
29
|
"tanstack",
|
|
@@ -1,94 +1,46 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Working on __PROJECT_NAME__
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
running the wasm engine in-process, an API authority, and the data tier — the one
|
|
7
|
-
topology (design 214): a `rindle-replicator` write-master + a `rindled`
|
|
8
|
-
read-follower (`followers = 1` = the colocated pair). The correctness contract
|
|
9
|
-
everywhere is **view-after-write == fresh-query**.
|
|
3
|
+
This project uses TanStack Start and Rindle. The browser has optimistic writes.
|
|
4
|
+
The application's API authorizes named queries and mutations. `rindle.ncl`
|
|
5
|
+
selects a replicated data tier with one write master and one read follower.
|
|
10
6
|
|
|
11
|
-
|
|
12
|
-
<https://rindle.sh/llms.txt>, the whole app track in one file at
|
|
13
|
-
<https://rindle.sh/llms-app.txt>.
|
|
7
|
+
## Project commands
|
|
14
8
|
|
|
15
|
-
|
|
9
|
+
- `pnpm dev` starts the data tier and application, applies migrations, and generates schema types.
|
|
10
|
+
- `pnpm typecheck` generates the route tree and checks TypeScript.
|
|
11
|
+
- `pnpm migrate` applies the files in `migrations/` to the configured local data tier.
|
|
12
|
+
- `pnpm rindle:deploy` deploys the data tier to Rindle Cloud after `rindle login`.
|
|
13
|
+
- `pnpm rindle:migrate:cloud` applies migrations to the linked Cloud database.
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
once, supervises the `rindle-replicator` write-master + `rindled` follower +
|
|
19
|
-
fleet edge (prebuilt binaries from `@rindle/cli`, no Rust toolchain), waits for
|
|
20
|
-
them, applies `migrations/*.sql`, regenerates `shared/schema.gen.ts`, then runs
|
|
21
|
-
Vite on :3000 with `RINDLE_URL` + `RINDLE_DATABASE_TOKEN`. It watches migration
|
|
22
|
-
and follower-schema changes and tears down the whole process tree together.
|
|
23
|
-
- `pnpm typecheck` — regenerates the route tree, then `tsc --noEmit`.
|
|
24
|
-
- `pnpm migrate` — one-shot `rindle migrate apply` against the unified ingress derived
|
|
25
|
-
from `rindle.ncl` (the follower's `/migrate` is write-fenced). The dev loop already
|
|
26
|
-
applies on boot + on every `migrations/` change.
|
|
27
|
-
- `pnpm rindle:deploy` / `pnpm rindle:migrate:cloud` — deploy the data tier to
|
|
28
|
-
Rindle Cloud (reads `rindle.ncl`, the same file `rindle up` runs locally; run
|
|
29
|
-
`rindle login` once first) and push `migrations/*.sql` to the deployed master.
|
|
15
|
+
With npm, use `npm run` followed by the script name.
|
|
30
16
|
|
|
31
|
-
##
|
|
17
|
+
## Application files
|
|
32
18
|
|
|
33
|
-
|
|
34
|
-
review:
|
|
35
|
-
|
|
36
|
-
1. **SQL is the source of truth.** Change the schema by **adding** a
|
|
37
|
-
`migrations/*.sql` file (SQL DDL: `CREATE TABLE` / `ADD COLUMN` /
|
|
38
|
-
`CREATE INDEX`, plus the destructive `DROP TABLE` / `DROP COLUMN` /
|
|
39
|
-
`DROP INDEX` — no `RENAME`; every table a single primary key). **Never
|
|
40
|
-
hand-edit `shared/schema.gen.ts`** — it is generated from the live daemon
|
|
41
|
-
and overwritten on the next migration.
|
|
42
|
-
2. **Mutators are one isomorphic body, deterministic and replayable**
|
|
43
|
-
(`shared/app-def.ts`): a generator that `yield`s logical ops
|
|
44
|
-
(`yield tx.insert(...)`), paired with its zod arg schema via
|
|
45
|
-
`shared(args, gen)`. No `Date.now()`, no `Math.random()`, no I/O — the client
|
|
46
|
-
re-invokes the body on every rebase. Generate ids and timestamps at the
|
|
47
|
-
callsite and pass them in as args; the acting user is `ctx.user` (injected
|
|
48
|
-
per tier), never an arg.
|
|
49
|
-
3. **The server drives the same body** — `sharedApiMutators` in
|
|
50
|
-
`server/app-api.ts` auto-wraps the whole registry (parse the untrusted wire
|
|
51
|
-
args through each mutator's `.args`, inject the authenticated principal,
|
|
52
|
-
render every op to SQL). Add an explicit entry ONLY for server-only
|
|
53
|
-
authority the client must not predict (a policy guard, a raw `tx.exec`
|
|
54
|
-
relational gate). Only `(name, args)` ever crosses the wire; `throw` to
|
|
55
|
-
hard-reject (the optimistic write snaps back on its own; write no rollback
|
|
56
|
-
code).
|
|
57
|
-
4. **Remote subscriptions must be named.** Define queries with `defineQuery` in
|
|
58
|
-
`src/components/*.queries.ts` and register them in `server/app-api.ts`. An
|
|
59
|
-
ad-hoc `store.query.…` builder resolves **locally only** — it never opens a
|
|
60
|
-
server subscription.
|
|
61
|
-
5. **Database tokens are server-only.** `rindle dev` injects the one application connection as
|
|
62
|
-
`RINDLE_URL` + `RINDLE_DATABASE_TOKEN`; the bearer must never reach the browser. The browser
|
|
63
|
-
learns only the public WebSocket endpoint + placement ticket from its query-lease response.
|
|
64
|
-
Never copy topology ports into package scripts or add a browser config endpoint.
|
|
65
|
-
6. **Subscribe to windows, not whole tables** — order + `limit`, and ratchet
|
|
66
|
-
`limit` up for "load more". The engine keeps the window (and any `countAs`)
|
|
67
|
-
exact as rows enter and leave.
|
|
68
|
-
7. **Keep `*.queries.ts` modules React-free** — no `.tsx` imports. The browser,
|
|
69
|
-
the API authority, and the SSR loader all import these same modules.
|
|
70
|
-
8. **Declare route reads through `rindle.loader(...)`** (`src/rindle-tanstack.ts`).
|
|
71
|
-
It owns server preloading, client navigation readiness/cancellation, and
|
|
72
|
-
blocking stale reloads. Keep `rindle.Provider` at the root; do not recreate
|
|
73
|
-
the old `useMatches()` merge or a separate `<RindleSSR>` wrapper.
|
|
74
|
-
|
|
75
|
-
## File map
|
|
76
|
-
|
|
77
|
-
| Path | What it is |
|
|
19
|
+
| Path | Purpose |
|
|
78
20
|
| --- | --- |
|
|
79
|
-
| `migrations/*.sql` |
|
|
80
|
-
| `shared/schema.gen.ts` |
|
|
81
|
-
| `shared/app-def.ts` |
|
|
82
|
-
| `src/components/*.queries.ts` |
|
|
83
|
-
| `src/rindle-client.ts` |
|
|
84
|
-
| `src/rindle-tanstack.ts` | the shared
|
|
85
|
-
| `
|
|
86
|
-
| `src/
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
21
|
+
| `migrations/*.sql` | Ordered database migrations |
|
|
22
|
+
| `shared/schema.gen.ts` | Generated schema types. Do not edit this file. |
|
|
23
|
+
| `shared/app-def.ts` | Relationships, query builder, and shared mutators |
|
|
24
|
+
| `src/components/*.queries.ts` | Named queries and component fragments |
|
|
25
|
+
| `src/rindle-client.ts` | Browser client initialization |
|
|
26
|
+
| `src/rindle-tanstack.ts` | Route loaders and the shared Rindle provider |
|
|
27
|
+
| `src/routes/__root.tsx` | Application root with `rindle.Provider` |
|
|
28
|
+
| `src/ssr.ts` | Server preloading through the application's API authority |
|
|
29
|
+
| `server/app-api.ts` | Query registration and authoritative mutation handlers |
|
|
30
|
+
| `server/auth-dev.ts` | Demonstration identity provider |
|
|
31
|
+
| `src/routes/api.rindle.*.tsx` | Browser-facing API routes |
|
|
32
|
+
| `rindle.ncl` | Local and Cloud data-tier configuration |
|
|
33
|
+
|
|
34
|
+
Keep shared query modules independent of React components and server secrets.
|
|
35
|
+
Use the existing `rindle.loader` and `rindle.Provider` integration for route reads.
|
|
36
|
+
|
|
37
|
+
## Demonstration identity
|
|
38
|
+
|
|
39
|
+
`server/auth-dev.ts` accepts an unverified `x-rindle-user` header.
|
|
40
|
+
Reads are public. The header lets the demonstration create writes without an account.
|
|
41
|
+
Before production, replace this provider with verified application authentication and review access policies.
|
|
42
|
+
|
|
43
|
+
## Product documentation
|
|
44
|
+
|
|
45
|
+
The agent setup command adds the local documentation index and skill below.
|
|
46
|
+
If setup was skipped, start with <https://rindle.sh/docs/for-agents.md>.
|
|
@@ -20,10 +20,10 @@ Three tiers, same as the Rindle flagship examples:
|
|
|
20
20
|
reaches it through TanStack Start **server routes** (`src/routes/api.rindle.{query,read,mutate}.tsx`,
|
|
21
21
|
via `server/rindle-http.ts`) that run in the same server as the app, and SSR calls the very same
|
|
22
22
|
factory **in-process** (no network hop).
|
|
23
|
-
- **Data tier** — the
|
|
23
|
+
- **Data tier** — the topology selected by this template: a `rindle-replicator` **write-master** plus a
|
|
24
24
|
`rindled` **read-follower** that owns the live IVM and streams normalized deltas to subscribers.
|
|
25
25
|
Writes land on the master; the follower serves reads. `followers = 1` is the *colocated pair* —
|
|
26
|
-
both processes on one box
|
|
26
|
+
both processes on one box.
|
|
27
27
|
|
|
28
28
|
## SQL is the source of truth
|
|
29
29
|
|
|
@@ -65,7 +65,7 @@ see the rejection path (the optimistic write snaps back + a toast).
|
|
|
65
65
|
|
|
66
66
|
## Deploy
|
|
67
67
|
|
|
68
|
-
`rindle.ncl` describes
|
|
68
|
+
`rindle.ncl` describes this application’s **data topology** — the same file `rindle up` runs locally. To run the
|
|
69
69
|
**data tier** on Rindle Cloud:
|
|
70
70
|
|
|
71
71
|
```bash
|
|
@@ -85,6 +85,23 @@ public WebSocket endpoint from that same URL, so there is no browser-side topolo
|
|
|
85
85
|
the deployment has one follower or many. Set the server-only `RINDLE_WS_URL` override only when a
|
|
86
86
|
host exposes WebSocket ingress at a different origin.
|
|
87
87
|
|
|
88
|
+
## Coding agents
|
|
89
|
+
|
|
90
|
+
The default scaffold installs a local documentation snapshot and the Rindle skill.
|
|
91
|
+
`AGENTS.md` contains this application's commands and file map. Its managed block
|
|
92
|
+
and `CLAUDE.md` point to `.rindle/agent-context/index.md`.
|
|
93
|
+
|
|
94
|
+
After installing or upgrading Rindle packages, run:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npx rindle agents setup
|
|
98
|
+
npx rindle agents setup --check
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The second command checks the installed files and package versions without writing files.
|
|
102
|
+
If you used `--no-agents`, setup installs the guidance when you need it.
|
|
103
|
+
See [agent setup](https://rindle.sh/docs/for-agents) for the compatibility rules.
|
|
104
|
+
|
|
88
105
|
## Devtools
|
|
89
106
|
|
|
90
107
|
In development a floating **🌊 Rindle** devtools pane is mounted (`src/devtools.tsx`): a live view of the
|