@pylonsync/create-pylon 0.3.268 → 0.3.270
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/bin/create-pylon.js +11 -9
- package/package.json +1 -1
- package/templates/b2b/app/layout.tsx +1 -1
- package/templates/b2b/app/page.tsx +2 -2
- package/templates/b2b/tsconfig.json +1 -1
- package/templates/barebones/app/page.tsx +1 -1
- package/templates/barebones/tsconfig.json +1 -1
- package/templates/chat/app/page.tsx +1 -1
- package/templates/chat/tsconfig.json +1 -1
- package/templates/consumer/app/page.tsx +1 -1
- package/templates/consumer/tsconfig.json +1 -1
- package/templates/default/.env.example +19 -0
- package/templates/default/README.md +85 -0
- package/templates/default/app/auth-form.tsx +218 -0
- package/templates/default/app/auth-shell.tsx +76 -0
- package/templates/default/app/company/[slug]/page.tsx +28 -0
- package/templates/default/app/compare/[slug]/page.tsx +27 -0
- package/templates/default/app/dashboard/billing/page.tsx +49 -0
- package/templates/default/app/dashboard/dashboard-client.tsx +832 -0
- package/templates/default/app/dashboard/members/page.tsx +37 -0
- package/templates/default/app/dashboard/page.tsx +64 -0
- package/templates/default/app/dashboard/projects/page.tsx +37 -0
- package/templates/default/app/dashboard/settings/page.tsx +45 -0
- package/templates/{ssr → default}/app/globals.css +14 -0
- package/templates/default/app/layout.tsx +466 -0
- package/templates/default/app/login/page.tsx +27 -0
- package/templates/default/app/onboarding/onboarding-client.tsx +261 -0
- package/templates/default/app/onboarding/page.tsx +29 -0
- package/templates/default/app/page.tsx +653 -0
- package/templates/default/app/products/[slug]/page.tsx +134 -0
- package/templates/default/app/resources/[slug]/page.tsx +28 -0
- package/templates/default/app/signup/page.tsx +24 -0
- package/templates/default/app/sitemap.ts +40 -0
- package/templates/default/app/solutions/[slug]/page.tsx +28 -0
- package/templates/default/app.ts +194 -0
- package/templates/default/components/dashboard-shell.tsx +150 -0
- package/templates/default/components/marketing.tsx +370 -0
- package/templates/default/functions/_pylonStripeFindActiveSubForReference.ts +3 -0
- package/templates/default/functions/_pylonStripeFindByCustomerId.ts +3 -0
- package/templates/default/functions/_pylonStripeGetCustomerHolder.ts +3 -0
- package/templates/default/functions/_pylonStripeListSubsForReference.ts +3 -0
- package/templates/default/functions/_pylonStripeOrgMembership.ts +3 -0
- package/templates/default/functions/_pylonStripeSetCustomerId.ts +3 -0
- package/templates/default/functions/_pylonStripeUpsertSubscription.ts +3 -0
- package/templates/default/functions/cancelSubscription.ts +3 -0
- package/templates/default/functions/createBillingPortalSession.ts +3 -0
- package/templates/default/functions/createCheckoutSession.ts +3 -0
- package/templates/default/functions/restoreSubscription.ts +3 -0
- package/templates/default/functions/stripeWebhook.ts +3 -0
- package/templates/default/lib/billing.ts +46 -0
- package/templates/default/lib/products.ts +122 -0
- package/templates/default/lib/site.ts +261 -0
- package/templates/{ssr → default}/package.json +2 -0
- package/templates/{ssr → default}/tsconfig.json +2 -2
- package/templates/todo/app/page.tsx +1 -1
- package/templates/todo/tsconfig.json +1 -1
- package/templates/ssr/README.md +0 -56
- package/templates/ssr/app/auth-form.tsx +0 -142
- package/templates/ssr/app/dashboard/dashboard-client.tsx +0 -116
- package/templates/ssr/app/dashboard/page.tsx +0 -70
- package/templates/ssr/app/layout.tsx +0 -71
- package/templates/ssr/app/login/page.tsx +0 -47
- package/templates/ssr/app/page.tsx +0 -114
- package/templates/ssr/app/signup/page.tsx +0 -44
- package/templates/ssr/app/sitemap.ts +0 -27
- package/templates/ssr/app.ts +0 -94
- package/templates/ssr/functions/_keep.ts +0 -13
- /package/templates/{ssr → default}/AGENTS.md +0 -0
- /package/templates/{ssr → default}/app/error.tsx +0 -0
- /package/templates/{ssr → default}/app/not-found.tsx +0 -0
- /package/templates/{ssr → default}/app/robots.ts +0 -0
- /package/templates/{ssr → default}/components/ui/button.tsx +0 -0
- /package/templates/{ssr → default}/components/ui/card.tsx +0 -0
- /package/templates/{ssr → default}/components.json +0 -0
- /package/templates/{ssr → default}/gitignore +0 -0
- /package/templates/{ssr → default}/lib/utils.ts +0 -0
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import React, { Suspense, use } from "react";
|
|
2
|
-
import {
|
|
3
|
-
type Metadata,
|
|
4
|
-
type PageProps,
|
|
5
|
-
type ServerData,
|
|
6
|
-
} from "@pylonsync/react";
|
|
7
|
-
import { Dashboard, type Note } from "./dashboard-client";
|
|
8
|
-
|
|
9
|
-
export const metadata: Metadata = {
|
|
10
|
-
title: "Dashboard — __APP_NAME__",
|
|
11
|
-
robots: "noindex",
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
interface User {
|
|
15
|
-
id: string;
|
|
16
|
-
email: string;
|
|
17
|
-
displayName?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Reads the signed-in user + their notes DURING the server render. The reads
|
|
21
|
-
// run through the same policy gate as a client query, so they're owner-scoped
|
|
22
|
-
// (User: only your row; Note: only your notes) — see the policies in app.ts.
|
|
23
|
-
// React 19 `use()` suspends until they resolve on the server, so the HTML
|
|
24
|
-
// arrives with your notes already in it (no empty flash); then the <Dashboard>
|
|
25
|
-
// island hydrates and takes over live.
|
|
26
|
-
function DashboardBody({
|
|
27
|
-
serverData,
|
|
28
|
-
userId,
|
|
29
|
-
}: {
|
|
30
|
-
serverData: ServerData;
|
|
31
|
-
userId: string;
|
|
32
|
-
}) {
|
|
33
|
-
const user = use(serverData.get<User>("User", userId));
|
|
34
|
-
const notes = use(serverData.list<Note>("Note"));
|
|
35
|
-
return (
|
|
36
|
-
<>
|
|
37
|
-
<p className="text-sm text-muted-foreground">
|
|
38
|
-
Signed in as{" "}
|
|
39
|
-
<span className="font-medium text-foreground">
|
|
40
|
-
{user?.displayName || user?.email || "you"}
|
|
41
|
-
</span>
|
|
42
|
-
.
|
|
43
|
-
</p>
|
|
44
|
-
<Dashboard initial={notes} />
|
|
45
|
-
</>
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// `app/dashboard/page.tsx` → `/dashboard`.
|
|
50
|
-
export default function DashboardPage({
|
|
51
|
-
auth,
|
|
52
|
-
response,
|
|
53
|
-
serverData,
|
|
54
|
-
}: PageProps) {
|
|
55
|
-
// Server-side auth gate: anonymous requests get a 307 to /login before any
|
|
56
|
-
// HTML. The redirect MUST fire here in the synchronous shell render — not
|
|
57
|
-
// inside the <Suspense> below — or React swallows it. No flash of the
|
|
58
|
-
// dashboard, works with JS disabled.
|
|
59
|
-
if (!auth.user_id) response.redirect("/login");
|
|
60
|
-
return (
|
|
61
|
-
<div className="space-y-6">
|
|
62
|
-
<h1 className="text-2xl font-semibold tracking-tight">Your notes</h1>
|
|
63
|
-
<Suspense
|
|
64
|
-
fallback={<p className="text-sm text-muted-foreground">Loading…</p>}
|
|
65
|
-
>
|
|
66
|
-
<DashboardBody serverData={serverData} userId={auth.user_id!} />
|
|
67
|
-
</Suspense>
|
|
68
|
-
</div>
|
|
69
|
-
);
|
|
70
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Link, type PageAuth } from "@pylonsync/react";
|
|
3
|
-
|
|
4
|
-
// A layout receives the page props plus `children`. `auth.user_id` is null
|
|
5
|
-
// for anonymous visitors and the signed-in user's id otherwise — resolved
|
|
6
|
-
// server-side from the session cookie, before any HTML is sent, so the nav
|
|
7
|
-
// renders the right links on the first byte (no flash, no client fetch). The
|
|
8
|
-
// `PageAuth` type is exported from @pylonsync/react so you never hand-roll it.
|
|
9
|
-
interface LayoutProps {
|
|
10
|
-
children: React.ReactNode;
|
|
11
|
-
url: string;
|
|
12
|
-
auth: PageAuth;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// The root layout wraps every page.
|
|
16
|
-
export default function RootLayout({ children, auth }: LayoutProps) {
|
|
17
|
-
const signedIn = Boolean(auth?.user_id);
|
|
18
|
-
// Add `className="dark"` to this <html> to flip every shadcn token to its
|
|
19
|
-
// dark value. The classes below use semantic tokens (bg-background,
|
|
20
|
-
// text-foreground, …) so the whole UI re-themes from app/globals.css.
|
|
21
|
-
return (
|
|
22
|
-
<html lang="en">
|
|
23
|
-
<head>
|
|
24
|
-
<meta charSet="utf-8" />
|
|
25
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
26
|
-
<title>__APP_NAME__</title>
|
|
27
|
-
{/* Tailwind is compiled by Pylon from app/globals.css and the
|
|
28
|
-
stylesheet link is injected here automatically — nothing to
|
|
29
|
-
wire up. */}
|
|
30
|
-
</head>
|
|
31
|
-
<body className="min-h-screen bg-background text-foreground antialiased">
|
|
32
|
-
<header className="sticky top-0 z-10 border-b bg-background/80 backdrop-blur">
|
|
33
|
-
<div className="mx-auto flex max-w-3xl items-center justify-between px-4 py-3">
|
|
34
|
-
<Link
|
|
35
|
-
href="/"
|
|
36
|
-
className="text-sm font-semibold tracking-tight hover:text-muted-foreground"
|
|
37
|
-
>
|
|
38
|
-
__APP_NAME__
|
|
39
|
-
</Link>
|
|
40
|
-
<nav className="flex items-center gap-4 text-sm text-muted-foreground">
|
|
41
|
-
<Link href="/" className="hover:text-foreground">
|
|
42
|
-
Home
|
|
43
|
-
</Link>
|
|
44
|
-
{signedIn ? (
|
|
45
|
-
<Link href="/dashboard" className="hover:text-foreground">
|
|
46
|
-
Dashboard
|
|
47
|
-
</Link>
|
|
48
|
-
) : (
|
|
49
|
-
<>
|
|
50
|
-
<Link href="/login" className="hover:text-foreground">
|
|
51
|
-
Sign in
|
|
52
|
-
</Link>
|
|
53
|
-
<Link
|
|
54
|
-
href="/signup"
|
|
55
|
-
className="rounded-md bg-primary px-3 py-1.5 font-medium text-primary-foreground hover:opacity-90"
|
|
56
|
-
>
|
|
57
|
-
Sign up
|
|
58
|
-
</Link>
|
|
59
|
-
</>
|
|
60
|
-
)}
|
|
61
|
-
</nav>
|
|
62
|
-
</div>
|
|
63
|
-
</header>
|
|
64
|
-
<main className="mx-auto max-w-3xl px-4 py-10">{children}</main>
|
|
65
|
-
<footer className="border-t py-6 text-center text-xs text-muted-foreground">
|
|
66
|
-
Rendered by Pylon · one server, one port
|
|
67
|
-
</footer>
|
|
68
|
-
</body>
|
|
69
|
-
</html>
|
|
70
|
-
);
|
|
71
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Link, type Metadata, type PageProps } from "@pylonsync/react";
|
|
3
|
-
import {
|
|
4
|
-
Card,
|
|
5
|
-
CardContent,
|
|
6
|
-
CardDescription,
|
|
7
|
-
CardHeader,
|
|
8
|
-
CardTitle,
|
|
9
|
-
} from "@/components/ui/card";
|
|
10
|
-
import { AuthForm } from "../auth-form";
|
|
11
|
-
|
|
12
|
-
export const metadata: Metadata = {
|
|
13
|
-
title: "Sign in — __APP_NAME__",
|
|
14
|
-
// Auth pages shouldn't be indexed.
|
|
15
|
-
robots: "noindex",
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
// `app/login/page.tsx` → `/login`. A server-rendered shell around the
|
|
19
|
-
// client-side <AuthForm> island.
|
|
20
|
-
export default function LoginPage({ auth, response }: PageProps) {
|
|
21
|
-
// Already signed in? Skip the form. `response.redirect` runs in the
|
|
22
|
-
// synchronous shell render, so it's a real 307 before any HTML is sent
|
|
23
|
-
// (no flash, works with JS disabled).
|
|
24
|
-
if (auth.user_id) response.redirect("/dashboard");
|
|
25
|
-
return (
|
|
26
|
-
<div className="mx-auto max-w-sm">
|
|
27
|
-
<Card>
|
|
28
|
-
<CardHeader>
|
|
29
|
-
<CardTitle>Sign in</CardTitle>
|
|
30
|
-
<CardDescription>Welcome back.</CardDescription>
|
|
31
|
-
</CardHeader>
|
|
32
|
-
<CardContent className="space-y-4">
|
|
33
|
-
<AuthForm mode="login" />
|
|
34
|
-
<p className="text-center text-sm text-muted-foreground">
|
|
35
|
-
No account?{" "}
|
|
36
|
-
<Link
|
|
37
|
-
href="/signup"
|
|
38
|
-
className="font-medium text-foreground hover:underline"
|
|
39
|
-
>
|
|
40
|
-
Create one
|
|
41
|
-
</Link>
|
|
42
|
-
</p>
|
|
43
|
-
</CardContent>
|
|
44
|
-
</Card>
|
|
45
|
-
</div>
|
|
46
|
-
);
|
|
47
|
-
}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Link, type Metadata, type PageProps } from "@pylonsync/react";
|
|
3
|
-
import { Button } from "@/components/ui/button";
|
|
4
|
-
import {
|
|
5
|
-
Card,
|
|
6
|
-
CardContent,
|
|
7
|
-
CardDescription,
|
|
8
|
-
CardHeader,
|
|
9
|
-
CardTitle,
|
|
10
|
-
} from "@/components/ui/card";
|
|
11
|
-
|
|
12
|
-
// SEO metadata. Export `metadata` (static) or `generateMetadata(props)`
|
|
13
|
-
// (dynamic) from any page or layout — Pylon renders the <title>/<meta>
|
|
14
|
-
// into <head> server-side. The `Metadata` type is exported from
|
|
15
|
-
// @pylonsync/react.
|
|
16
|
-
export const metadata: Metadata = {
|
|
17
|
-
title: "__APP_NAME__ — full-stack Pylon app",
|
|
18
|
-
description:
|
|
19
|
-
"A server-rendered homepage, email/password auth, and a live client dashboard over one synced backend — one binary, one port.",
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
// `app/page.tsx` → `/`. This page is server-rendered: view source and the copy
|
|
23
|
-
// is in the HTML, not fetched later — good for SEO and first paint. It reads
|
|
24
|
-
// `auth` (resolved from the session cookie during the render) to show the
|
|
25
|
-
// right call to action. Every page receives `PageProps` from the SSR runtime:
|
|
26
|
-
// `{ url, params, searchParams, auth, response, serverData }` — typed, no
|
|
27
|
-
// hand-rolled interface.
|
|
28
|
-
export default function IndexPage({ auth }: PageProps) {
|
|
29
|
-
const signedIn = Boolean(auth.user_id);
|
|
30
|
-
return (
|
|
31
|
-
<div className="space-y-12">
|
|
32
|
-
<section className="space-y-5">
|
|
33
|
-
<span className="inline-flex items-center rounded-full border px-3 py-1 text-xs font-medium text-muted-foreground">
|
|
34
|
-
Server-rendered · authenticated · synced · one port
|
|
35
|
-
</span>
|
|
36
|
-
<h1 className="text-4xl font-semibold tracking-tight">
|
|
37
|
-
Full-stack apps, one binary.
|
|
38
|
-
</h1>
|
|
39
|
-
<p className="max-w-xl text-lg text-muted-foreground">
|
|
40
|
-
This homepage is server-rendered React. Sign in and your dashboard
|
|
41
|
-
becomes a live, local-first view over the same Pylon backend — writes
|
|
42
|
-
appear instantly and sync across tabs. No Next.js, no separate API
|
|
43
|
-
server, no realtime sidecar.
|
|
44
|
-
</p>
|
|
45
|
-
<div className="flex flex-wrap items-center gap-3">
|
|
46
|
-
{signedIn ? (
|
|
47
|
-
<Button asChild>
|
|
48
|
-
<Link href="/dashboard">Go to your dashboard →</Link>
|
|
49
|
-
</Button>
|
|
50
|
-
) : (
|
|
51
|
-
<>
|
|
52
|
-
<Button asChild>
|
|
53
|
-
<Link href="/signup">Get started</Link>
|
|
54
|
-
</Button>
|
|
55
|
-
<Button asChild variant="outline">
|
|
56
|
-
<Link href="/login">Sign in</Link>
|
|
57
|
-
</Button>
|
|
58
|
-
</>
|
|
59
|
-
)}
|
|
60
|
-
</div>
|
|
61
|
-
</section>
|
|
62
|
-
|
|
63
|
-
<section className="grid gap-4 sm:grid-cols-3">
|
|
64
|
-
<Feature title="Server-rendered">
|
|
65
|
-
File-based routes under <Code>app/</Code>. Pages render to HTML on the
|
|
66
|
-
server with <Code>metadata</Code> in <Code>{"<head>"}</Code>, then
|
|
67
|
-
hydrate. Drop <Code>app/about/page.tsx</Code> to add{" "}
|
|
68
|
-
<Code>/about</Code>.
|
|
69
|
-
</Feature>
|
|
70
|
-
<Feature title="Auth included">
|
|
71
|
-
Email/password is built in. <Code>/login</Code> and{" "}
|
|
72
|
-
<Code>/signup</Code> hit <Code>/api/auth/password/*</Code>; the server
|
|
73
|
-
sets an HttpOnly session cookie. <Code>/dashboard</Code> gates on it
|
|
74
|
-
server-side.
|
|
75
|
-
</Feature>
|
|
76
|
-
<Feature title="Synced database">
|
|
77
|
-
Every <Code>entity()</Code> in <Code>app.ts</Code> gets a REST +
|
|
78
|
-
realtime API and a typed client. <Code>db.useQuery</Code> is live;{" "}
|
|
79
|
-
<Code>db.insert</Code> is optimistic.
|
|
80
|
-
</Feature>
|
|
81
|
-
</section>
|
|
82
|
-
|
|
83
|
-
<p className="text-xs text-muted-foreground">
|
|
84
|
-
Edit <Code>app/page.tsx</Code> and save — the page reloads instantly.
|
|
85
|
-
The data model and access policies live in <Code>app.ts</Code>.
|
|
86
|
-
</p>
|
|
87
|
-
</div>
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function Feature({
|
|
92
|
-
title,
|
|
93
|
-
children,
|
|
94
|
-
}: {
|
|
95
|
-
title: string;
|
|
96
|
-
children: React.ReactNode;
|
|
97
|
-
}) {
|
|
98
|
-
return (
|
|
99
|
-
<Card>
|
|
100
|
-
<CardHeader>
|
|
101
|
-
<CardTitle className="text-base">{title}</CardTitle>
|
|
102
|
-
</CardHeader>
|
|
103
|
-
<CardContent>
|
|
104
|
-
<CardDescription className="text-sm leading-relaxed">
|
|
105
|
-
{children}
|
|
106
|
-
</CardDescription>
|
|
107
|
-
</CardContent>
|
|
108
|
-
</Card>
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function Code({ children }: { children: React.ReactNode }) {
|
|
113
|
-
return <code className="rounded bg-muted px-1 text-xs">{children}</code>;
|
|
114
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { Link, type Metadata, type PageProps } from "@pylonsync/react";
|
|
3
|
-
import {
|
|
4
|
-
Card,
|
|
5
|
-
CardContent,
|
|
6
|
-
CardDescription,
|
|
7
|
-
CardHeader,
|
|
8
|
-
CardTitle,
|
|
9
|
-
} from "@/components/ui/card";
|
|
10
|
-
import { AuthForm } from "../auth-form";
|
|
11
|
-
|
|
12
|
-
export const metadata: Metadata = {
|
|
13
|
-
title: "Create your account — __APP_NAME__",
|
|
14
|
-
robots: "noindex",
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
// `app/signup/page.tsx` → `/signup`. Same shell as /login, register mode.
|
|
18
|
-
export default function SignupPage({ auth, response }: PageProps) {
|
|
19
|
-
if (auth.user_id) response.redirect("/dashboard");
|
|
20
|
-
return (
|
|
21
|
-
<div className="mx-auto max-w-sm">
|
|
22
|
-
<Card>
|
|
23
|
-
<CardHeader>
|
|
24
|
-
<CardTitle>Create your account</CardTitle>
|
|
25
|
-
<CardDescription>
|
|
26
|
-
Email + password. No credit card, no email verification step in dev.
|
|
27
|
-
</CardDescription>
|
|
28
|
-
</CardHeader>
|
|
29
|
-
<CardContent className="space-y-4">
|
|
30
|
-
<AuthForm mode="signup" />
|
|
31
|
-
<p className="text-center text-sm text-muted-foreground">
|
|
32
|
-
Already have an account?{" "}
|
|
33
|
-
<Link
|
|
34
|
-
href="/login"
|
|
35
|
-
className="font-medium text-foreground hover:underline"
|
|
36
|
-
>
|
|
37
|
-
Sign in
|
|
38
|
-
</Link>
|
|
39
|
-
</p>
|
|
40
|
-
</CardContent>
|
|
41
|
-
</Card>
|
|
42
|
-
</div>
|
|
43
|
-
);
|
|
44
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { Sitemap } from "@pylonsync/react";
|
|
2
|
-
|
|
3
|
-
// app/sitemap.ts → served at /sitemap.xml. The default export can be async, so
|
|
4
|
-
// it can enumerate dynamic pages from your database. Point SITE_URL at your
|
|
5
|
-
// domain in production.
|
|
6
|
-
const SITE = process.env.SITE_URL ?? "http://localhost:4321";
|
|
7
|
-
|
|
8
|
-
export default async function sitemap(): Promise<Sitemap> {
|
|
9
|
-
// Only public pages belong here — /dashboard is private (and noindex), so
|
|
10
|
-
// it's intentionally left out.
|
|
11
|
-
const staticRoutes: Sitemap = [
|
|
12
|
-
{ url: `${SITE}/`, changeFrequency: "weekly", priority: 1 },
|
|
13
|
-
{ url: `${SITE}/login`, changeFrequency: "yearly", priority: 0.3 },
|
|
14
|
-
{ url: `${SITE}/signup`, changeFrequency: "yearly", priority: 0.5 },
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
// The export is async, so you can enumerate dynamic pages from a DB read:
|
|
18
|
-
//
|
|
19
|
-
// const posts = await fetchPublishedPosts();
|
|
20
|
-
// const postRoutes: Sitemap = posts.map((p) => ({
|
|
21
|
-
// url: `${SITE}/blog/${p.slug}`,
|
|
22
|
-
// lastModified: p.updatedAt,
|
|
23
|
-
// }));
|
|
24
|
-
// return [...staticRoutes, ...postRoutes];
|
|
25
|
-
|
|
26
|
-
return staticRoutes;
|
|
27
|
-
}
|
package/templates/ssr/app.ts
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
entity,
|
|
3
|
-
field,
|
|
4
|
-
policy,
|
|
5
|
-
auth,
|
|
6
|
-
buildManifest,
|
|
7
|
-
discoverAppRoutes,
|
|
8
|
-
} from "@pylonsync/sdk";
|
|
9
|
-
|
|
10
|
-
// Accounts. Email/password auth is built in: POST /api/auth/password/register
|
|
11
|
-
// hashes the password and writes this row; /api/auth/password/login mints a
|
|
12
|
-
// session and sets an HttpOnly cookie. The framework treats the entity named
|
|
13
|
-
// "User" as the account table — `passwordHash` is server-only and never
|
|
14
|
-
// serialized to a client.
|
|
15
|
-
const User = entity(
|
|
16
|
-
"User",
|
|
17
|
-
{
|
|
18
|
-
email: field.string(),
|
|
19
|
-
displayName: field.string().optional(),
|
|
20
|
-
passwordHash: field.string().serverOnly().optional(),
|
|
21
|
-
// The framework's /api/auth/password/register stamps a generated avatar
|
|
22
|
-
// color here, so the User entity must declare it.
|
|
23
|
-
avatarColor: field.string().optional(),
|
|
24
|
-
createdAt: field.datetime().defaultNow(),
|
|
25
|
-
},
|
|
26
|
-
{ indexes: [{ name: "by_email", fields: ["email"], unique: true }] },
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
// A note that belongs to one user. `ownerId: field.owner()` is the key move:
|
|
30
|
-
// the framework stamps the signed-in user's id server-side on insert and
|
|
31
|
-
// rejects any forged value — so the dashboard can do a plain, optimistic
|
|
32
|
-
// `db.insert("Note", { body })` (the row shows instantly, no round-trip) while
|
|
33
|
-
// the owner stays unspoofable. No createNote function to write.
|
|
34
|
-
const Note = entity(
|
|
35
|
-
"Note",
|
|
36
|
-
{
|
|
37
|
-
ownerId: field.string().owner(),
|
|
38
|
-
body: field.string(),
|
|
39
|
-
done: field.boolean().default(false),
|
|
40
|
-
createdAt: field.datetime().defaultNow(),
|
|
41
|
-
},
|
|
42
|
-
{ indexes: [{ name: "by_owner", fields: ["ownerId"], unique: false }] },
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
// Notes are private — every read and write is gated to the owner. An entity
|
|
46
|
-
// with NO policy is denied to clients by default, so this is exactly what
|
|
47
|
-
// makes the dashboard's live query + optimistic writes work, and only for
|
|
48
|
-
// your own rows. `auth.userId` is the session user; `data.ownerId` is the row.
|
|
49
|
-
const notePolicy = policy({
|
|
50
|
-
name: "note_access",
|
|
51
|
-
entity: "Note",
|
|
52
|
-
allowRead: "auth.userId == data.ownerId",
|
|
53
|
-
allowInsert: "auth.userId != null",
|
|
54
|
-
allowUpdate: "auth.userId == data.ownerId",
|
|
55
|
-
allowDelete: "auth.userId == data.ownerId",
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
// User rows are read-only to clients, and only your own (so the dashboard can
|
|
59
|
-
// read your display name). The auth subsystem owns writes — registration and
|
|
60
|
-
// login go through /api/auth/password/*, never the entity API.
|
|
61
|
-
const userPolicy = policy({
|
|
62
|
-
name: "user_access",
|
|
63
|
-
entity: "User",
|
|
64
|
-
allowRead: "auth.userId == data.id",
|
|
65
|
-
allowInsert: "false",
|
|
66
|
-
allowUpdate: "false",
|
|
67
|
-
allowDelete: "false",
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
// The manifest is your whole app in one object: data, policies, and the
|
|
71
|
-
// file-based routes under `app/`. `pylon dev` reads this, serves the SSR
|
|
72
|
-
// frontend and the API from one port, and regenerates a typed client on
|
|
73
|
-
// every change.
|
|
74
|
-
const manifest = buildManifest({
|
|
75
|
-
name: "__APP_NAME__",
|
|
76
|
-
version: "0.1.0",
|
|
77
|
-
entities: [User, Note],
|
|
78
|
-
queries: [],
|
|
79
|
-
actions: [],
|
|
80
|
-
policies: [userPolicy, notePolicy],
|
|
81
|
-
// Email/password is on by default against the User entity above. `auth()`
|
|
82
|
-
// is the knob for session lifetime, exposed fields, orgs, and trusted
|
|
83
|
-
// origins — `auth({ session: { expiresIn: 60 * 60 * 24 * 7 } })` for a
|
|
84
|
-
// 7-day session, etc.
|
|
85
|
-
auth: auth(),
|
|
86
|
-
// File-based routing: `discoverAppRoutes()` walks `app/**/page.tsx` and
|
|
87
|
-
// emits one route per page. Drop `app/about/page.tsx` to add `/about`.
|
|
88
|
-
routes: await discoverAppRoutes(),
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
// Emit canonical manifest JSON to stdout for `pylon codegen`.
|
|
92
|
-
console.log(JSON.stringify(manifest, null, 2));
|
|
93
|
-
|
|
94
|
-
export default manifest;
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// Server functions go here. Each file in this directory that exports a
|
|
2
|
-
// query() or action() becomes a typed RPC endpoint, callable from your
|
|
3
|
-
// pages and client with full type inference. Delete this placeholder when
|
|
4
|
-
// you add your first one.
|
|
5
|
-
//
|
|
6
|
-
// Example (functions/notes.ts):
|
|
7
|
-
//
|
|
8
|
-
// import { query } from "@pylonsync/functions";
|
|
9
|
-
//
|
|
10
|
-
// export const listNotes = query(async (ctx) => {
|
|
11
|
-
// return ctx.db.list("Note");
|
|
12
|
-
// });
|
|
13
|
-
export {};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|