@pylonsync/create-pylon 0.3.267 → 0.3.268

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 (83) hide show
  1. package/bin/create-pylon.js +18 -10
  2. package/package.json +1 -1
  3. package/templates/b2b/AGENTS.md +61 -0
  4. package/templates/b2b/README.md +62 -0
  5. package/templates/b2b/app/auth-form.tsx +142 -0
  6. package/templates/b2b/app/dashboard/dashboard-client.tsx +192 -0
  7. package/templates/b2b/app/dashboard/page.tsx +63 -0
  8. package/templates/b2b/app/error.tsx +43 -0
  9. package/templates/b2b/app/globals.css +139 -0
  10. package/templates/b2b/app/layout.tsx +71 -0
  11. package/templates/b2b/app/login/page.tsx +47 -0
  12. package/templates/b2b/app/not-found.tsx +29 -0
  13. package/templates/b2b/app/page.tsx +114 -0
  14. package/templates/b2b/app/robots.ts +12 -0
  15. package/templates/b2b/app/signup/page.tsx +44 -0
  16. package/templates/b2b/app/sitemap.ts +27 -0
  17. package/templates/b2b/app.ts +179 -0
  18. package/templates/b2b/components/ui/button.tsx +56 -0
  19. package/templates/b2b/components/ui/card.tsx +90 -0
  20. package/templates/b2b/components.json +20 -0
  21. package/templates/b2b/functions/_keep.ts +13 -0
  22. package/templates/b2b/gitignore +10 -0
  23. package/templates/b2b/lib/utils.ts +10 -0
  24. package/templates/b2b/package.json +33 -0
  25. package/templates/b2b/tsconfig.json +18 -0
  26. package/templates/barebones/AGENTS.md +61 -0
  27. package/templates/barebones/README.md +45 -0
  28. package/templates/barebones/app/error.tsx +43 -0
  29. package/templates/barebones/app/globals.css +139 -0
  30. package/templates/barebones/app/items-client.tsx +96 -0
  31. package/templates/barebones/app/layout.tsx +27 -0
  32. package/templates/barebones/app/not-found.tsx +29 -0
  33. package/templates/barebones/app/page.tsx +28 -0
  34. package/templates/barebones/app/robots.ts +12 -0
  35. package/templates/barebones/app/sitemap.ts +27 -0
  36. package/templates/barebones/app.ts +55 -0
  37. package/templates/barebones/components/ui/button.tsx +56 -0
  38. package/templates/barebones/components/ui/card.tsx +90 -0
  39. package/templates/barebones/components.json +20 -0
  40. package/templates/barebones/functions/_keep.ts +13 -0
  41. package/templates/barebones/gitignore +10 -0
  42. package/templates/barebones/lib/utils.ts +10 -0
  43. package/templates/barebones/package.json +33 -0
  44. package/templates/barebones/tsconfig.json +18 -0
  45. package/templates/chat/AGENTS.md +61 -0
  46. package/templates/chat/README.md +51 -0
  47. package/templates/chat/app/chat-client.tsx +113 -0
  48. package/templates/chat/app/error.tsx +43 -0
  49. package/templates/chat/app/globals.css +139 -0
  50. package/templates/chat/app/layout.tsx +25 -0
  51. package/templates/chat/app/not-found.tsx +29 -0
  52. package/templates/chat/app/page.tsx +26 -0
  53. package/templates/chat/app/robots.ts +12 -0
  54. package/templates/chat/app/sitemap.ts +27 -0
  55. package/templates/chat/app.ts +59 -0
  56. package/templates/chat/components/ui/button.tsx +56 -0
  57. package/templates/chat/components/ui/card.tsx +90 -0
  58. package/templates/chat/components.json +20 -0
  59. package/templates/chat/functions/_keep.ts +13 -0
  60. package/templates/chat/gitignore +10 -0
  61. package/templates/chat/lib/utils.ts +10 -0
  62. package/templates/chat/package.json +33 -0
  63. package/templates/chat/tsconfig.json +18 -0
  64. package/templates/consumer/AGENTS.md +61 -0
  65. package/templates/consumer/README.md +52 -0
  66. package/templates/consumer/app/error.tsx +43 -0
  67. package/templates/consumer/app/feed-client.tsx +154 -0
  68. package/templates/consumer/app/globals.css +139 -0
  69. package/templates/consumer/app/layout.tsx +27 -0
  70. package/templates/consumer/app/not-found.tsx +29 -0
  71. package/templates/consumer/app/page.tsx +27 -0
  72. package/templates/consumer/app/robots.ts +12 -0
  73. package/templates/consumer/app/sitemap.ts +27 -0
  74. package/templates/consumer/app.ts +89 -0
  75. package/templates/consumer/components/ui/button.tsx +56 -0
  76. package/templates/consumer/components/ui/card.tsx +90 -0
  77. package/templates/consumer/components.json +20 -0
  78. package/templates/consumer/functions/_keep.ts +13 -0
  79. package/templates/consumer/gitignore +10 -0
  80. package/templates/consumer/lib/utils.ts +10 -0
  81. package/templates/consumer/package.json +33 -0
  82. package/templates/consumer/tsconfig.json +18 -0
  83. package/templates/ssr/app.ts +3 -0
@@ -0,0 +1,89 @@
1
+ import {
2
+ entity,
3
+ field,
4
+ policy,
5
+ auth,
6
+ buildManifest,
7
+ discoverAppRoutes,
8
+ } from "@pylonsync/sdk";
9
+
10
+ // A post in the public feed. `authorId: field.owner()` stamps the signed-in
11
+ // (guest) user's id server-side, so an optimistic `db.insert("Post", { text })`
12
+ // can't forge authorship. The feed itself is public-read (everyone sees every
13
+ // post) — that's intentional for a social feed, NOT the insecure wide-open
14
+ // default; writes are still owner-only.
15
+ const Post = entity(
16
+ "Post",
17
+ {
18
+ authorId: field.string().owner(),
19
+ text: field.string(),
20
+ createdAt: field.datetime().defaultNow(),
21
+ },
22
+ { indexes: [{ name: "by_created", fields: ["createdAt"], unique: false }] },
23
+ );
24
+
25
+ // A like is a join row (one per user per post). To toggle a like the client
26
+ // inserts or deletes this row; the like count for a post is just how many
27
+ // Like rows point at it. `userId: field.owner()` keeps a like attributable to
28
+ // exactly one user.
29
+ const Like = entity(
30
+ "Like",
31
+ {
32
+ userId: field.string().owner(),
33
+ postId: field.string(),
34
+ createdAt: field.datetime().defaultNow(),
35
+ },
36
+ {
37
+ indexes: [
38
+ { name: "by_post", fields: ["postId"], unique: false },
39
+ // One like per user per post — the unique index makes a double-like a
40
+ // no-op at the storage layer.
41
+ { name: "by_user_post", fields: ["userId", "postId"], unique: true },
42
+ ],
43
+ },
44
+ );
45
+
46
+ // Posts + likes are public-read so the feed and its counts render for
47
+ // everyone; writes are gated to the owner. An entity with no policy is denied
48
+ // to clients by default, so these allow-lists are what make the feed work.
49
+ // `allowInsert` is `auth.userId != null`, not `== data.authorId`: the owner
50
+ // field is stamped by field.owner() *after* the policy check, so it's null at
51
+ // insert-time. The stamp still guarantees the new row is owned by the caller,
52
+ // and read/update/delete enforce ownership on the persisted row.
53
+ const postPolicy = policy({
54
+ name: "post_feed",
55
+ entity: "Post",
56
+ allowRead: "true",
57
+ allowInsert: "auth.userId != null",
58
+ allowUpdate: "auth.userId == data.authorId",
59
+ allowDelete: "auth.userId == data.authorId",
60
+ });
61
+
62
+ const likePolicy = policy({
63
+ name: "like_access",
64
+ entity: "Like",
65
+ allowRead: "true",
66
+ allowInsert: "auth.userId != null",
67
+ allowUpdate: "false",
68
+ allowDelete: "auth.userId == data.userId",
69
+ });
70
+
71
+ // `pylon dev` serves the SSR feed and the API from one port. Guest sessions
72
+ // (via `<EnsureGuest>` on the page) let every visitor post + like with no
73
+ // login. Natural next steps: a `Profile` entity (displayName/avatar keyed by
74
+ // userId) to show names instead of ids, and a `Follow` join entity
75
+ // (followerId/followedId) to scope the feed to people you follow.
76
+ const manifest = buildManifest({
77
+ name: "__APP_NAME__",
78
+ version: "0.1.0",
79
+ entities: [Post, Like],
80
+ queries: [],
81
+ actions: [],
82
+ policies: [postPolicy, likePolicy],
83
+ auth: auth(),
84
+ routes: await discoverAppRoutes(),
85
+ });
86
+
87
+ console.log(JSON.stringify(manifest, null, 2));
88
+
89
+ export default manifest;
@@ -0,0 +1,56 @@
1
+ import * as React from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+
5
+ import { cn } from "@/lib/utils";
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
13
+ destructive:
14
+ "bg-destructive text-white hover:bg-destructive/90",
15
+ outline:
16
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17
+ secondary:
18
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19
+ ghost: "hover:bg-accent hover:text-accent-foreground",
20
+ link: "text-primary underline-offset-4 hover:underline",
21
+ },
22
+ size: {
23
+ default: "h-9 px-4 py-2",
24
+ sm: "h-8 rounded-md px-3 text-xs",
25
+ lg: "h-10 rounded-md px-8",
26
+ icon: "h-9 w-9",
27
+ },
28
+ },
29
+ defaultVariants: {
30
+ variant: "default",
31
+ size: "default",
32
+ },
33
+ },
34
+ );
35
+
36
+ export interface ButtonProps
37
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
38
+ VariantProps<typeof buttonVariants> {
39
+ asChild?: boolean;
40
+ }
41
+
42
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
43
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
44
+ const Comp = asChild ? Slot : "button";
45
+ return (
46
+ <Comp
47
+ className={cn(buttonVariants({ variant, size, className }))}
48
+ ref={ref}
49
+ {...props}
50
+ />
51
+ );
52
+ },
53
+ );
54
+ Button.displayName = "Button";
55
+
56
+ export { Button, buttonVariants };
@@ -0,0 +1,90 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ function Card({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
6
+ return (
7
+ <div
8
+ data-slot="card"
9
+ className={cn(
10
+ "rounded-xl border bg-card text-card-foreground shadow-sm",
11
+ className,
12
+ )}
13
+ {...props}
14
+ />
15
+ );
16
+ }
17
+
18
+ function CardHeader({
19
+ className,
20
+ ...props
21
+ }: React.HTMLAttributes<HTMLDivElement>) {
22
+ return (
23
+ <div
24
+ data-slot="card-header"
25
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
26
+ {...props}
27
+ />
28
+ );
29
+ }
30
+
31
+ function CardTitle({
32
+ className,
33
+ ...props
34
+ }: React.HTMLAttributes<HTMLDivElement>) {
35
+ return (
36
+ <div
37
+ data-slot="card-title"
38
+ className={cn("font-semibold leading-none tracking-tight", className)}
39
+ {...props}
40
+ />
41
+ );
42
+ }
43
+
44
+ function CardDescription({
45
+ className,
46
+ ...props
47
+ }: React.HTMLAttributes<HTMLDivElement>) {
48
+ return (
49
+ <div
50
+ data-slot="card-description"
51
+ className={cn("text-sm text-muted-foreground", className)}
52
+ {...props}
53
+ />
54
+ );
55
+ }
56
+
57
+ function CardContent({
58
+ className,
59
+ ...props
60
+ }: React.HTMLAttributes<HTMLDivElement>) {
61
+ return (
62
+ <div
63
+ data-slot="card-content"
64
+ className={cn("p-6 pt-0", className)}
65
+ {...props}
66
+ />
67
+ );
68
+ }
69
+
70
+ function CardFooter({
71
+ className,
72
+ ...props
73
+ }: React.HTMLAttributes<HTMLDivElement>) {
74
+ return (
75
+ <div
76
+ data-slot="card-footer"
77
+ className={cn("flex items-center p-6 pt-0", className)}
78
+ {...props}
79
+ />
80
+ );
81
+ }
82
+
83
+ export {
84
+ Card,
85
+ CardHeader,
86
+ CardFooter,
87
+ CardTitle,
88
+ CardDescription,
89
+ CardContent,
90
+ };
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": false,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "app/globals.css",
9
+ "baseColor": "zinc",
10
+ "cssVariables": true
11
+ },
12
+ "aliases": {
13
+ "components": "@/components",
14
+ "utils": "@/lib/utils",
15
+ "ui": "@/components/ui",
16
+ "lib": "@/lib",
17
+ "hooks": "@/hooks"
18
+ },
19
+ "iconLibrary": "lucide"
20
+ }
@@ -0,0 +1,13 @@
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 {};
@@ -0,0 +1,10 @@
1
+ node_modules/
2
+ .pylon/
3
+ pylon.manifest.json
4
+ pylon.client.ts
5
+ web/dist/
6
+ *.db
7
+ *.db-*
8
+ .env
9
+ .env.local
10
+ .DS_Store
@@ -0,0 +1,10 @@
1
+ import { type ClassValue, clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ // `cn` — the shadcn class merger. clsx resolves conditional/array class
5
+ // inputs; tailwind-merge then dedupes conflicting Tailwind utilities so
6
+ // the last one wins (e.g. `cn("px-2", "px-4")` → "px-4"). Every shadcn
7
+ // component routes its className through this.
8
+ export function cn(...inputs: ClassValue[]) {
9
+ return twMerge(clsx(inputs));
10
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "__APP_NAME_KEBAB__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "pylon dev",
8
+ "deploy": "pylon deploy",
9
+ "check": "tsc --noEmit"
10
+ },
11
+ "dependencies": {
12
+ "@pylonsync/react": "^__PYLON_VERSION__",
13
+ "@pylonsync/sdk": "^__PYLON_VERSION__",
14
+ "@pylonsync/functions": "^__PYLON_VERSION__",
15
+ "@pylonsync/client": "^__PYLON_VERSION__",
16
+ "react": "^19.0.0",
17
+ "react-dom": "^19.0.0",
18
+ "tailwindcss": "^4.3.0",
19
+ "@tailwindcss/cli": "^4.3.0",
20
+ "tw-animate-css": "^1.2.0",
21
+ "class-variance-authority": "^0.7.1",
22
+ "clsx": "^2.1.1",
23
+ "tailwind-merge": "^2.5.0",
24
+ "lucide-react": "^0.460.0",
25
+ "@radix-ui/react-slot": "^1.1.0"
26
+ },
27
+ "devDependencies": {
28
+ "@pylonsync/cli": "^__PYLON_VERSION__",
29
+ "@types/react": "^19.0.0",
30
+ "@types/react-dom": "^19.0.0",
31
+ "typescript": "^5.6.0"
32
+ }
33
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "jsx": "react",
7
+ "esModuleInterop": true,
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "lib": ["ES2022", "DOM"],
11
+ "types": ["react", "react-dom"],
12
+ "baseUrl": ".",
13
+ "paths": {
14
+ "@/*": ["./*"]
15
+ }
16
+ },
17
+ "include": ["app.ts", "app/**/*", "components/**/*", "lib/**/*"]
18
+ }
@@ -18,6 +18,9 @@ const User = entity(
18
18
  email: field.string(),
19
19
  displayName: field.string().optional(),
20
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(),
21
24
  createdAt: field.datetime().defaultNow(),
22
25
  },
23
26
  { indexes: [{ name: "by_email", fields: ["email"], unique: true }] },