@pylonsync/create-pylon 0.3.327 → 0.3.328

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pylonsync/create-pylon",
3
- "version": "0.3.327",
3
+ "version": "0.3.328",
4
4
  "description": "Scaffold a new Pylon app — realtime backend + web/mobile/expo frontends in one command. Run via `npm create @pylonsync/pylon@latest`.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -22,7 +22,7 @@ Operating rules for a coding agent in this Pylon app. You — the agent — are
22
22
 
23
23
  ## Key gotchas
24
24
 
25
- - **Policies deny by default; server functions BYPASS them.** Direct client CRUD (`/api/entities/*`) and sync are policy-checked. Functions run with full DB access — enforce trust with `ctx.auth` checks inside the handler, not policies.
25
+ - **Policies deny by default; server functions BYPASS them.** Direct client CRUD (`/api/entities/*`) and sync are policy-checked. Functions run with full DB access — enforce trust inside the handler with `ctx.auth` checks, or `ctx.requireMember(orgId, { role: ["owner", "admin"] })` for org membership / role gating (throws `FORBIDDEN` for non-members; available on query, mutation, and action). Don't hand-roll a members lookup — the primitive fails closed.
26
26
  - **Type page props from the SDK, don't hand-roll them.** `import type { PageProps, Metadata } from "@pylonsync/react"`. Every page/layout gets `{ url, params, searchParams, auth, response, serverData }`; `PageProps<{ slug: string }>` types a `[slug]` route's params. Request headers/cookies are intentionally NOT on `PageProps` — they're server-only and stripped from hydration, so reading them in the render would mismatch.
27
27
  - **Anonymous output caching is opt-in + earned.** `export const revalidate = 60` (seconds) on a page makes it CDN-cacheable (`public, s-maxage=60`) — but ONLY if the render is auth-INDEPENDENT: it must NOT read `props.auth` (reading it at all opts out, even for anonymous), set no cookie, and the app must not run strict per-caller policies (`PYLON_STRICT_FN_POLICIES`). `export const dynamic = "force-static"` caches until the next deploy; `"force-dynamic"` never caches. Fail-closed: without the opt-in (or if any condition fails) the page is `no-cache`. A page that reads `auth` or sets a cookie is never shared. The SAME earned render is also kept in an **origin disk cache** (`.pylon/.cache/ssr`): a cookie-less GET with no query string is served straight off disk for the TTL — skipping the render entirely — then re-rendered live when stale. The disk cache is namespaced per deploy (wiped on each new build) and OFF in `pylon dev` (so an edit is never masked by a stale entry); invalidation is by the `revalidate` TTL or the next deploy.
28
28
  - **No-JS forms use `route.ts` + `<Form>`.** Drop `app/.../route.ts` exporting `export const POST: RouteHandler = async ({ form, db, response, auth }) => { await db.insert("X", {...}); response.redirect("/x?ok=1"); }` (303 POST-redirect-GET by default). Render `<Form action="/x">` (from @pylonsync/react) with plain `<input name=...>` — works with JS off (native POST→handler→redirect) and is enhanced to no-reload when JS is on. The handler's `db` is read+write (mutation trust model — gate on `auth`); CSRF is automatic (Origin gate + SameSite=Lax). Multipart/file uploads aren't supported yet — use urlencoded forms + `/api/files`.
@@ -1049,12 +1049,14 @@ function BillingView({
1049
1049
  const msg = e instanceof Error ? e.message : String(e);
1050
1050
  // A missing STRIPE_PRICE_PRO surfaces as "plan pro has no monthly
1051
1051
  // priceId" rather than STRIPE_NOT_CONFIGURED, so treat the price/config
1052
- // errors the same — both mean "finish the Stripe setup".
1052
+ // errors the same — both mean the Stripe setup isn't finished (set
1053
+ // STRIPE_SECRET_KEY + STRIPE_PRICE_PRO). That guidance is for you, the
1054
+ // developer; the customer just sees the generic "contact support" line.
1053
1055
  setError(
1054
1056
  /not.?configured|STRIPE_NOT_CONFIGURED|no monthly price|priceId/i.test(
1055
1057
  msg,
1056
1058
  )
1057
- ? "Stripe isn't configured yet set STRIPE_SECRET_KEY + STRIPE_PRICE_PRO to enable billing."
1059
+ ? "Billing isn't available yet. Contact support and we'll get you set up."
1058
1060
  : msg,
1059
1061
  );
1060
1062
  setBusy(null);
@@ -1174,10 +1176,12 @@ function BillingView({
1174
1176
  {error && <p className="mt-3 text-xs text-red-600">{error}</p>}
1175
1177
  </Card>
1176
1178
 
1179
+ {/* Dev setup (not shown to customers): set STRIPE_SECRET_KEY,
1180
+ STRIPE_WEBHOOK_SECRET, and STRIPE_PRICE_PRO to go live; point the
1181
+ Stripe webhook at /api/fn/stripeWebhook. Billing is wired through
1182
+ @pylonsync/stripe — see lib/billing.ts. */}
1177
1183
  <p className="text-xs text-zinc-400">
1178
- Powered by Stripe Checkout + Customer Portal via the @pylonsync/stripe
1179
- plugin. Set STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, and STRIPE_PRICE_PRO
1180
- to go live; point the webhook at <code>/api/fn/stripeWebhook</code>.
1184
+ Payments and invoices are processed securely by Stripe.
1181
1185
  </p>
1182
1186
  </div>
1183
1187
  );
@@ -439,17 +439,8 @@ function SiteFooter() {
439
439
  </div>
440
440
  </div>
441
441
 
442
- <div className="mt-14 flex flex-col items-start justify-between gap-3 border-t border-zinc-200/70 pt-6 text-[12px] text-zinc-400 sm:flex-row sm:items-center">
442
+ <div className="mt-14 border-t border-zinc-200/70 pt-6 text-[12px] text-zinc-400">
443
443
  <span>© {new Date().getFullYear()} {siteConfig.brand.copyrightName}</span>
444
- <span>
445
- Built with{" "}
446
- <a
447
- href="https://pylonsync.com"
448
- className="font-medium text-zinc-600 hover:text-zinc-900"
449
- >
450
- Pylon
451
- </a>
452
- </span>
453
444
  </div>
454
445
  </div>
455
446
  </footer>
@@ -124,12 +124,12 @@ export const siteConfig: SiteConfig = {
124
124
  socials: [
125
125
  {
126
126
  label: "X",
127
- href: "https://x.com/pylonsync",
127
+ href: "https://x.com/acme",
128
128
  path: "M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z",
129
129
  },
130
130
  {
131
131
  label: "GitHub",
132
- href: "https://github.com/pylonsync/pylon",
132
+ href: "https://github.com/acme",
133
133
  path: "M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12",
134
134
  },
135
135
  ],
@@ -10,8 +10,8 @@ import {
10
10
  Platform,
11
11
  Alert,
12
12
  KeyboardAvoidingView,
13
- SafeAreaView,
14
13
  } from "react-native";
14
+ import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
15
15
  import { StatusBar } from "expo-status-bar";
16
16
  import { init, db, callFn } from "@pylonsync/react-native";
17
17
 
@@ -47,6 +47,14 @@ function ensureInit() {
47
47
  }
48
48
 
49
49
  export default function App() {
50
+ return (
51
+ <SafeAreaProvider>
52
+ <AppContent />
53
+ </SafeAreaProvider>
54
+ );
55
+ }
56
+
57
+ function AppContent() {
50
58
  const [ready, setReady] = useState(false);
51
59
  useEffect(() => {
52
60
  ensureInit().then(() => setReady(true));
@@ -21,7 +21,8 @@
21
21
  "expo-status-bar": "~56.0.0",
22
22
  "react": "19.2.0",
23
23
  "react-dom": "19.2.0",
24
- "react-native": "0.85.3"
24
+ "react-native": "0.85.3",
25
+ "react-native-safe-area-context": "~5.6.0"
25
26
  },
26
27
  "devDependencies": {
27
28
  "@babel/core": "^7.25.0",
@@ -9,8 +9,8 @@ import {
9
9
  StyleSheet,
10
10
  Platform,
11
11
  Alert,
12
- SafeAreaView,
13
12
  } from "react-native";
13
+ import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
14
14
  import { StatusBar } from "expo-status-bar";
15
15
  import { init, db, callFn } from "@pylonsync/react-native";
16
16
  import AsyncStorage from "@react-native-async-storage/async-storage";
@@ -56,6 +56,14 @@ function ensureInit() {
56
56
  }
57
57
 
58
58
  export default function App() {
59
+ return (
60
+ <SafeAreaProvider>
61
+ <AppContent />
62
+ </SafeAreaProvider>
63
+ );
64
+ }
65
+
66
+ function AppContent() {
59
67
  const [ready, setReady] = useState(false);
60
68
  const [profileId, setProfileId] = useState<string | null>(null);
61
69
  useEffect(() => {
@@ -21,7 +21,8 @@
21
21
  "expo-status-bar": "~56.0.0",
22
22
  "react": "19.2.0",
23
23
  "react-dom": "19.2.0",
24
- "react-native": "0.85.3"
24
+ "react-native": "0.85.3",
25
+ "react-native-safe-area-context": "~5.6.0"
25
26
  },
26
27
  "devDependencies": {
27
28
  "@babel/core": "^7.25.0",