@pylonsync/react 0.3.292 → 0.3.294
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/dist/Form.d.ts +11 -0
- package/dist/Image.d.ts +39 -0
- package/dist/Link.d.ts +27 -0
- package/dist/db.d.ts +163 -0
- package/dist/hooks.d.ts +388 -0
- package/dist/index.d.ts +189 -0
- package/dist/ssr.d.ts +415 -0
- package/dist/typed.d.ts +75 -0
- package/dist/useRoom.d.ts +93 -0
- package/dist/useRouter.d.ts +74 -0
- package/dist/useSession.d.ts +41 -0
- package/dist/useShard.d.ts +58 -0
- package/dist/useSyncStatus.d.ts +30 -0
- package/package.json +14 -8
- package/tsconfig.json +0 -10
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
export { defineRoute } from "@pylonsync/sdk";
|
|
2
|
+
export type { RouteMode, AppManifest } from "@pylonsync/sdk";
|
|
3
|
+
export { Link } from "./Link";
|
|
4
|
+
export type { LinkProps } from "./Link";
|
|
5
|
+
export { Image } from "./Image";
|
|
6
|
+
export type { ImageProps } from "./Image";
|
|
7
|
+
export { Form } from "./Form";
|
|
8
|
+
export type { FormProps } from "./Form";
|
|
9
|
+
export type { PageProps, PageAuth, ServerData, SsrResponse, SsrCookieOptions, Metadata, GenerateMetadata, Sitemap, SitemapEntry, Robots, RobotsRule, RouteSegmentConfig, ErrorBoundaryProps, NotFoundProps, FormFields, FormDb, FormRequest, RouteHandler, RawRouteHandler, RawResponse, } from "./ssr";
|
|
10
|
+
export { useRouter, useSearchParams, usePathname, useParams, redirect, notFound, NotFoundError, } from "./useRouter";
|
|
11
|
+
export type { PylonRouter } from "./useRouter";
|
|
12
|
+
import { type Storage as PylonStorage } from "@pylonsync/sync";
|
|
13
|
+
export { useQuery, useQueryOne, useReactiveQuery, useMutation, useInfiniteQuery, usePaginatedQuery, useEntityMutation, useAction, useQueryRaw, useQueryOneRaw, useLiveList, useLiveRow, useInsert, useUpdate, useDelete, useFn, useAggregate, useSearch, } from "./hooks";
|
|
14
|
+
export type { QueryOptions, QueryFilter, IncludeSpec, UseQueryReturn, UseQueryOneReturn, UseReactiveQueryReturn, UseMutationReturn, UseInfiniteQueryReturn, UsePaginatedQueryReturn, PaginatedQueryStatus, UseFnReturn, AggregateSpec, UseAggregateReturn, SearchSpec, UseSearchReturn, } from "./hooks";
|
|
15
|
+
export { useRoom } from "./useRoom";
|
|
16
|
+
export type { RoomPeer, RoomSnapshot, UseRoomOptions, UseRoomReturn, } from "./useRoom";
|
|
17
|
+
export { useShard, connectShard } from "./useShard";
|
|
18
|
+
export type { UseShardOptions, UseShardReturn, ShardClient, } from "./useShard";
|
|
19
|
+
export { useSession } from "./useSession";
|
|
20
|
+
export type { UseSessionReturn, ResolvedSession } from "./useSession";
|
|
21
|
+
export { useSyncStatus } from "./useSyncStatus";
|
|
22
|
+
export type { SyncConnectionStatus } from "./useSyncStatus";
|
|
23
|
+
export { db, init, getSync } from "./db";
|
|
24
|
+
export { createTypedDb } from "./typed";
|
|
25
|
+
export type { TypedDb, AgentDBSchema } from "./typed";
|
|
26
|
+
export { SyncEngine, createSyncEngine, getServerData, LocalStore, MutationQueue, } from "@pylonsync/sync";
|
|
27
|
+
export type { ChangeEvent, SyncCursor, PullResponse, HydrationData, Row, } from "@pylonsync/sync";
|
|
28
|
+
export interface AgentDBClientConfig {
|
|
29
|
+
baseUrl?: string;
|
|
30
|
+
/**
|
|
31
|
+
* App identifier used to namespace all client-side storage keys —
|
|
32
|
+
* localStorage (token, cached user, feature-flag toggles) and
|
|
33
|
+
* IndexedDB (sync replica). Two apps served from the same browser
|
|
34
|
+
* origin (different ports in dev, or the same domain in prod) must
|
|
35
|
+
* pick different names or they'll see each other's sessions and
|
|
36
|
+
* local replicas. Defaults to "default" for a single-app setup.
|
|
37
|
+
*/
|
|
38
|
+
appName?: string;
|
|
39
|
+
}
|
|
40
|
+
/** Current effective base URL. Used by hooks (useRoom, useShard) and the
|
|
41
|
+
* @pylonsync/client auth helpers (createOrg, passwordRegister, createInvite,
|
|
42
|
+
* …) that share the client config but don't have access to the module-private
|
|
43
|
+
* state.
|
|
44
|
+
*
|
|
45
|
+
* When NOT explicitly configured, default to the page origin in a browser
|
|
46
|
+
* instead of the `http://localhost:4321` dev constant. A unified SSR/embedded
|
|
47
|
+
* app serves its API same-origin, so the static default was a footgun: every
|
|
48
|
+
* auth/org call fired at `localhost:4321` — broken on any non-4321 dev port
|
|
49
|
+
* AND in production (it would hit the engineer's dev port, not the app's
|
|
50
|
+
* domain). `init()`/`createSyncEngine` already resolve `window.location.origin`
|
|
51
|
+
* for the sync engine; this brings the auth helpers to the same origin so the
|
|
52
|
+
* two never disagree. An explicit `configureClient({ baseUrl })` still wins
|
|
53
|
+
* (separate-origin API setups), and SSR/node (no `window`) keeps the dev
|
|
54
|
+
* default (server-side calls use same-process paths anyway). */
|
|
55
|
+
export declare function getBaseUrl(): string;
|
|
56
|
+
/** Current app name. Used by sync engine + storage helpers to namespace keys. */
|
|
57
|
+
export declare function getAppName(): string;
|
|
58
|
+
/**
|
|
59
|
+
* Resolve the localStorage key for a conceptual slot (e.g. "token",
|
|
60
|
+
* "user") into its actual storage key. When `appName` is "default" we
|
|
61
|
+
* fall back to the legacy unprefixed key so older single-app setups
|
|
62
|
+
* keep working without migration.
|
|
63
|
+
*/
|
|
64
|
+
export declare function storageKey(slot: string): string;
|
|
65
|
+
export declare function configureClient(config: AgentDBClientConfig): void;
|
|
66
|
+
export declare function fetchList(entity: string): Promise<Record<string, unknown>[]>;
|
|
67
|
+
export declare function fetchById(entity: string, id: string): Promise<Record<string, unknown> | null>;
|
|
68
|
+
export declare function insert(entity: string, data: Record<string, unknown>): Promise<{
|
|
69
|
+
id: string;
|
|
70
|
+
}>;
|
|
71
|
+
export declare function update(entity: string, id: string, data: Record<string, unknown>): Promise<{
|
|
72
|
+
updated: boolean;
|
|
73
|
+
}>;
|
|
74
|
+
export declare function remove(entity: string, id: string): Promise<{
|
|
75
|
+
deleted: boolean;
|
|
76
|
+
}>;
|
|
77
|
+
export declare function createSession(userId: string): Promise<{
|
|
78
|
+
token: string;
|
|
79
|
+
user_id: string;
|
|
80
|
+
}>;
|
|
81
|
+
export declare function getAuthContext(token?: string): Promise<{
|
|
82
|
+
user_id: string | null;
|
|
83
|
+
}>;
|
|
84
|
+
/**
|
|
85
|
+
* Exchange a current session token for a new one with a fresh 30-day expiry.
|
|
86
|
+
* The old token is revoked server-side. Call this before expiry to keep
|
|
87
|
+
* long-lived sessions alive without forcing a re-login.
|
|
88
|
+
*
|
|
89
|
+
* Returns `null` if the old token is already expired or invalid — the
|
|
90
|
+
* caller should treat that as "log back in."
|
|
91
|
+
*/
|
|
92
|
+
export declare function refreshSession(token: string): Promise<{
|
|
93
|
+
token: string;
|
|
94
|
+
user_id: string;
|
|
95
|
+
expires_at: number;
|
|
96
|
+
} | null>;
|
|
97
|
+
/**
|
|
98
|
+
* Keep a session alive by automatically refreshing ~1 hour before expiry.
|
|
99
|
+
*
|
|
100
|
+
* ```ts
|
|
101
|
+
* const session = await createSession("alice");
|
|
102
|
+
* const stop = startSessionAutoRefresh(session, {
|
|
103
|
+
* onRefresh: (next) => localStorage.setItem("token", next.token),
|
|
104
|
+
* onExpired: () => redirect("/login"),
|
|
105
|
+
* });
|
|
106
|
+
* // later:
|
|
107
|
+
* stop();
|
|
108
|
+
* ```
|
|
109
|
+
*
|
|
110
|
+
* Returns a cleanup function that cancels the scheduled refresh. Call it
|
|
111
|
+
* on logout or unmount — otherwise the timer leaks.
|
|
112
|
+
*
|
|
113
|
+
* Default refresh margin is 1 hour. Pass `{ marginSecs }` to tune.
|
|
114
|
+
*/
|
|
115
|
+
export declare function startSessionAutoRefresh(session: {
|
|
116
|
+
token: string;
|
|
117
|
+
expires_at: number;
|
|
118
|
+
}, opts: {
|
|
119
|
+
onRefresh: (next: {
|
|
120
|
+
token: string;
|
|
121
|
+
user_id: string;
|
|
122
|
+
expires_at: number;
|
|
123
|
+
}) => void;
|
|
124
|
+
onExpired?: () => void;
|
|
125
|
+
marginSecs?: number;
|
|
126
|
+
}): () => void;
|
|
127
|
+
/**
|
|
128
|
+
* Swap the storage adapter used by the React free helpers (`callFn`,
|
|
129
|
+
* `useSession`, `getAuthToken`, etc). React Native's `init()` calls this
|
|
130
|
+
* with an AsyncStorage-backed adapter so token reads/writes go through
|
|
131
|
+
* the same backend as the sync engine.
|
|
132
|
+
*/
|
|
133
|
+
export declare function setReactStorage(storage: PylonStorage): void;
|
|
134
|
+
/** Current storage adapter used by the React layer. Exposed for adapters. */
|
|
135
|
+
export declare function getReactStorage(): PylonStorage;
|
|
136
|
+
export declare function callFn<T = unknown>(name: string, args?: Record<string, unknown>, options?: {
|
|
137
|
+
token?: string;
|
|
138
|
+
}): Promise<T>;
|
|
139
|
+
/**
|
|
140
|
+
* Stream a server-side function's output as Server-Sent Events.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```ts
|
|
144
|
+
* for await (const chunk of streamFn("chat", { message: "hello" })) {
|
|
145
|
+
* console.log(chunk);
|
|
146
|
+
* }
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
export declare function streamFn(name: string, args?: Record<string, unknown>, options?: {
|
|
150
|
+
token?: string;
|
|
151
|
+
}): AsyncGenerator<string, unknown, unknown>;
|
|
152
|
+
/**
|
|
153
|
+
* List all server-side functions available.
|
|
154
|
+
*/
|
|
155
|
+
export declare function listFns(): Promise<{
|
|
156
|
+
name: string;
|
|
157
|
+
fn_type: "query" | "mutation" | "action";
|
|
158
|
+
}[]>;
|
|
159
|
+
export interface UploadedFile {
|
|
160
|
+
id: string;
|
|
161
|
+
url: string;
|
|
162
|
+
size: number;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Upload a file (File/Blob or raw bytes) to /api/files/upload.
|
|
166
|
+
*
|
|
167
|
+
* For File / Blob inputs this sends a single raw binary request with the
|
|
168
|
+
* filename and content-type as headers (the server short-circuits on this
|
|
169
|
+
* shape so uploads avoid being coerced through string-based handling).
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```ts
|
|
173
|
+
* const uploaded = await uploadFile(fileFromInput);
|
|
174
|
+
* console.log(uploaded.url, uploaded.id, uploaded.size);
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
export declare function uploadFile(input: File | Blob | ArrayBuffer | Uint8Array, options?: {
|
|
178
|
+
filename?: string;
|
|
179
|
+
contentType?: string;
|
|
180
|
+
token?: string;
|
|
181
|
+
}): Promise<UploadedFile>;
|
|
182
|
+
/**
|
|
183
|
+
* Upload via multipart/form-data. Useful when the app needs to pass extra
|
|
184
|
+
* fields alongside the file (captions, categories, etc.), though only the
|
|
185
|
+
* first file part is stored today.
|
|
186
|
+
*/
|
|
187
|
+
export declare function uploadFileMultipart(file: File | Blob, fields?: Record<string, string>, options?: {
|
|
188
|
+
token?: string;
|
|
189
|
+
}): Promise<UploadedFile>;
|
package/dist/ssr.d.ts
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The resolved Pylon auth context for the request that rendered the page.
|
|
3
|
+
* Safe to read in the component body — it is serialized into the hydration
|
|
4
|
+
* payload, so the server render and the client hydration see the same
|
|
5
|
+
* values (no hydration mismatch).
|
|
6
|
+
*/
|
|
7
|
+
export interface PageAuth {
|
|
8
|
+
/** The signed-in user's id, or null for an anonymous request. */
|
|
9
|
+
user_id: string | null;
|
|
10
|
+
/** True when the session is an admin session (PYLON_ADMIN_EMAILS). */
|
|
11
|
+
is_admin: boolean;
|
|
12
|
+
/** The active tenant/org id, or null. */
|
|
13
|
+
tenant_id: string | null;
|
|
14
|
+
/** Role slugs granted to the session. */
|
|
15
|
+
roles: string[];
|
|
16
|
+
}
|
|
17
|
+
/** Options for `response.setCookie`. Defaults: HttpOnly + SameSite=Lax. */
|
|
18
|
+
export interface SsrCookieOptions {
|
|
19
|
+
path?: string;
|
|
20
|
+
domain?: string;
|
|
21
|
+
maxAge?: number;
|
|
22
|
+
expires?: Date | string;
|
|
23
|
+
/** Defaults to true (secure default). Pass false for a client-readable cookie. */
|
|
24
|
+
httpOnly?: boolean;
|
|
25
|
+
secure?: boolean;
|
|
26
|
+
sameSite?: "Strict" | "Lax" | "None";
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The per-render `response` controller. Pylon already has a backend for
|
|
30
|
+
* data + mutations, so SSR's job is just the HTTP response envelope:
|
|
31
|
+
* status, redirects, 404, and the occasional Set-Cookie.
|
|
32
|
+
*
|
|
33
|
+
* IMPORTANT — call these during the SYNCHRONOUS shell render (the component
|
|
34
|
+
* body, before any `await` / Suspense boundary). The HTTP head is committed
|
|
35
|
+
* when the shell is ready; status/headers/cookies set from a suspended
|
|
36
|
+
* subtree that streams in later are lost, and a `redirect()` / `notFound()`
|
|
37
|
+
* thrown below a Suspense boundary is swallowed by React's error handling
|
|
38
|
+
* rather than turned into a 3xx / 404.
|
|
39
|
+
*/
|
|
40
|
+
export interface SsrResponse {
|
|
41
|
+
/** Set the HTTP status (100–599). Default 200. */
|
|
42
|
+
setStatus(code: number): void;
|
|
43
|
+
/** Set a response header (name must be a token; value CR/LF/NUL-free). */
|
|
44
|
+
setHeader(name: string, value: string): void;
|
|
45
|
+
/** Append a Set-Cookie. Defaults: HttpOnly + SameSite=Lax. */
|
|
46
|
+
setCookie(name: string, value: string, opts?: SsrCookieOptions): void;
|
|
47
|
+
/** Throw to send a 3xx (default 307) + Location, no body. Shell-render only. */
|
|
48
|
+
redirect(url: string, status?: number): never;
|
|
49
|
+
/**
|
|
50
|
+
* Throw to send a 404. Renders the nearest `not-found.tsx` (walking up
|
|
51
|
+
* from the page's directory, wrapped in the route's layout chain), or a
|
|
52
|
+
* minimal framework body if none is defined. Shell-render only.
|
|
53
|
+
*/
|
|
54
|
+
notFound(): never;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Read-only database handle a page reaches during render via React 19
|
|
58
|
+
* `use()` + `<Suspense>`. Reads run through the same store + policy gate as
|
|
59
|
+
* a query function's `ctx.db`; writes are rejected. Resolved values are
|
|
60
|
+
* cached and replayed into the hydration payload, so the client does not
|
|
61
|
+
* re-fetch on hydration.
|
|
62
|
+
*
|
|
63
|
+
* ```tsx
|
|
64
|
+
* export default function Page({ serverData }: PageProps) {
|
|
65
|
+
* const posts = use(serverData.list<Post>("Post"));
|
|
66
|
+
* return <ul>{posts.map((p) => <li key={p.id}>{p.title}</li>)}</ul>;
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export interface ServerData {
|
|
71
|
+
/** Get a single row by id. Resolves to null if not found. */
|
|
72
|
+
get<T = Record<string, unknown>>(entity: string, id: string): Promise<T | null>;
|
|
73
|
+
/** List all (policy-visible) rows for an entity. */
|
|
74
|
+
list<T = Record<string, unknown>>(entity: string): Promise<T[]>;
|
|
75
|
+
/** Look up a row by a field value (e.g. email). Null if not found. */
|
|
76
|
+
lookup<T = Record<string, unknown>>(entity: string, field: string, value: string): Promise<T | null>;
|
|
77
|
+
/** Query with filters ($gt, $lt, $in, $like, $order, $limit, …). */
|
|
78
|
+
query<T = Record<string, unknown>>(entity: string, filter: Record<string, unknown>): Promise<T[]>;
|
|
79
|
+
/** Execute a graph query with nested relation includes. */
|
|
80
|
+
queryGraph<T = Record<string, unknown>>(query: Record<string, unknown>): Promise<T>;
|
|
81
|
+
/** Cursor-paginated list. Pass `cursor` from a previous page's result. */
|
|
82
|
+
paginate<T = Record<string, unknown>>(entity: string, opts: {
|
|
83
|
+
numItems: number;
|
|
84
|
+
cursor?: string | null;
|
|
85
|
+
}): Promise<{
|
|
86
|
+
rows?: T[];
|
|
87
|
+
page?: T[];
|
|
88
|
+
nextCursor?: string | null;
|
|
89
|
+
}>;
|
|
90
|
+
/** Faceted full-text search against an entity with a `search:` config. */
|
|
91
|
+
search<T = Record<string, unknown>>(entity: string, query: Record<string, unknown>): Promise<{
|
|
92
|
+
hits: T[];
|
|
93
|
+
facetCounts?: Record<string, Record<string, number>>;
|
|
94
|
+
total: number;
|
|
95
|
+
tookMs?: number;
|
|
96
|
+
}>;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Props every `page.tsx` / `layout.tsx` receives. Generic over the dynamic
|
|
100
|
+
* route params and the parsed query string, so a route like
|
|
101
|
+
* `app/blog/[slug]/page.tsx` can type them:
|
|
102
|
+
*
|
|
103
|
+
* ```tsx
|
|
104
|
+
* export default function Post({ params }: PageProps<{ slug: string }>) {
|
|
105
|
+
* return <article>{params.slug}</article>;
|
|
106
|
+
* }
|
|
107
|
+
* ```
|
|
108
|
+
*
|
|
109
|
+
* Note: the incoming request's headers + cookies are intentionally NOT on
|
|
110
|
+
* this type. They are available only during the server render and are
|
|
111
|
+
* stripped from the hydration payload (a session cookie must never reach
|
|
112
|
+
* client JS), so reading them in the component body would hydrate-mismatch.
|
|
113
|
+
* Read request-derived data through `serverData` or a server function.
|
|
114
|
+
*/
|
|
115
|
+
export interface PageProps<TParams extends Record<string, string> = Record<string, string>, TSearchParams extends Record<string, string> = Record<string, string>> {
|
|
116
|
+
/** The incoming URL path (e.g. `/blog/hello-world`). */
|
|
117
|
+
url: string;
|
|
118
|
+
/** Dynamic-segment matches keyed by name (e.g. `{ slug: "hello-world" }`). */
|
|
119
|
+
params: TParams;
|
|
120
|
+
/** Parsed query string (e.g. `?start=10` → `{ start: "10" }`). */
|
|
121
|
+
searchParams: TSearchParams;
|
|
122
|
+
/** The resolved auth context for the request. */
|
|
123
|
+
auth: PageAuth;
|
|
124
|
+
/** The HTTP response controller (status / headers / cookies / redirect). */
|
|
125
|
+
response: SsrResponse;
|
|
126
|
+
/** Read-only database handle for in-render data (use with `use()`). */
|
|
127
|
+
serverData: ServerData;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Page SEO metadata. Export `const metadata` (static) or
|
|
131
|
+
* `async function generateMetadata(props)` (dynamic, e.g. param-derived
|
|
132
|
+
* titles) from a `page.tsx` / `layout.tsx`. React 19 hoists the resulting
|
|
133
|
+
* `<title>` / `<meta>` / `<link>` into `<head>`.
|
|
134
|
+
*/
|
|
135
|
+
/** A single OpenGraph image (for `openGraph.images` — multiple images). */
|
|
136
|
+
export interface OgImage {
|
|
137
|
+
url: string;
|
|
138
|
+
secureUrl?: string;
|
|
139
|
+
type?: string;
|
|
140
|
+
width?: number;
|
|
141
|
+
height?: number;
|
|
142
|
+
alt?: string;
|
|
143
|
+
}
|
|
144
|
+
export interface Metadata {
|
|
145
|
+
title?: string;
|
|
146
|
+
description?: string;
|
|
147
|
+
keywords?: string | string[];
|
|
148
|
+
canonical?: string;
|
|
149
|
+
robots?: string;
|
|
150
|
+
/** `<meta name="author">` — one tag per author. */
|
|
151
|
+
authors?: string | string[];
|
|
152
|
+
/** `<meta name="theme-color">` — browser UI tint for the page. */
|
|
153
|
+
themeColor?: string;
|
|
154
|
+
openGraph?: {
|
|
155
|
+
title?: string;
|
|
156
|
+
description?: string;
|
|
157
|
+
image?: string;
|
|
158
|
+
imageSecureUrl?: string;
|
|
159
|
+
imageType?: string;
|
|
160
|
+
imageWidth?: number;
|
|
161
|
+
imageHeight?: number;
|
|
162
|
+
imageAlt?: string;
|
|
163
|
+
/** Additional images beyond the primary `image` (each emits its own
|
|
164
|
+
* `og:image` + dimensions). Provide absolute URLs. */
|
|
165
|
+
images?: OgImage[];
|
|
166
|
+
url?: string;
|
|
167
|
+
type?: string;
|
|
168
|
+
/** `og:locale` (e.g. "en_US"). */
|
|
169
|
+
locale?: string;
|
|
170
|
+
/** `og:site_name` — the brand the page belongs to (e.g. "Pylon").
|
|
171
|
+
* Discord and other unfurlers show this above the title. */
|
|
172
|
+
siteName?: string;
|
|
173
|
+
/** `article:*` tags for `og:type=article` pages. */
|
|
174
|
+
article?: {
|
|
175
|
+
author?: string | string[];
|
|
176
|
+
publishedTime?: string;
|
|
177
|
+
modifiedTime?: string;
|
|
178
|
+
section?: string;
|
|
179
|
+
tags?: string | string[];
|
|
180
|
+
};
|
|
181
|
+
};
|
|
182
|
+
twitter?: {
|
|
183
|
+
card?: string;
|
|
184
|
+
title?: string;
|
|
185
|
+
description?: string;
|
|
186
|
+
image?: string;
|
|
187
|
+
/** `twitter:site` / `twitter:creator` — @handles. */
|
|
188
|
+
site?: string;
|
|
189
|
+
creator?: string;
|
|
190
|
+
/** `twitter:image:alt` — alt text for the card image. */
|
|
191
|
+
imageAlt?: string;
|
|
192
|
+
};
|
|
193
|
+
icons?: {
|
|
194
|
+
icon?: {
|
|
195
|
+
url: string;
|
|
196
|
+
type?: string;
|
|
197
|
+
sizes?: string;
|
|
198
|
+
};
|
|
199
|
+
apple?: {
|
|
200
|
+
url: string;
|
|
201
|
+
type?: string;
|
|
202
|
+
sizes?: string;
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
/** Alternate URLs. `languages` emits `<link rel="alternate" hreflang>`
|
|
206
|
+
* per locale; `canonical` is an alias for the top-level `canonical`. */
|
|
207
|
+
alternates?: {
|
|
208
|
+
canonical?: string;
|
|
209
|
+
languages?: Record<string, string>;
|
|
210
|
+
};
|
|
211
|
+
/** Structured data (schema.org), emitted as `<script type="application/ld+json">`.
|
|
212
|
+
* Pass one object or an array (each item gets its own script). The payload is
|
|
213
|
+
* serialized with `<`/`>`/`&` escaped so it can't break out of the script. */
|
|
214
|
+
jsonLd?: Record<string, unknown> | Record<string, unknown>[];
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Signature of a dynamic `generateMetadata` export. Receives the same props
|
|
218
|
+
* as the page; return (or resolve to) the page's `Metadata`. Awaited before
|
|
219
|
+
* the first byte, so keep it to cheap derivations.
|
|
220
|
+
*/
|
|
221
|
+
export type GenerateMetadata<TParams extends Record<string, string> = Record<string, string>, TSearchParams extends Record<string, string> = Record<string, string>> = (props: PageProps<TParams, TSearchParams>) => Metadata | Promise<Metadata>;
|
|
222
|
+
/**
|
|
223
|
+
* Return type for an `app/sitemap.ts` default export (Next-shaped). The runtime
|
|
224
|
+
* serializes it to `/sitemap.xml`. The export may be sync or async, so it can
|
|
225
|
+
* enumerate dynamic pages from the DB:
|
|
226
|
+
*
|
|
227
|
+
* export default async function sitemap(): Promise<Sitemap> {
|
|
228
|
+
* const posts = await getPosts();
|
|
229
|
+
* return [{ url: "https://x.com/", priority: 1 }, ...posts.map(...)];
|
|
230
|
+
* }
|
|
231
|
+
*/
|
|
232
|
+
export interface SitemapEntry {
|
|
233
|
+
url: string;
|
|
234
|
+
lastModified?: string | Date;
|
|
235
|
+
changeFrequency?: "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never";
|
|
236
|
+
priority?: number;
|
|
237
|
+
/** hreflang alternates, e.g. `{ languages: { "en-US": "https://…/en" } }`. */
|
|
238
|
+
alternates?: {
|
|
239
|
+
languages?: Record<string, string>;
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
export type Sitemap = SitemapEntry[];
|
|
243
|
+
export interface RobotsRule {
|
|
244
|
+
userAgent?: string | string[];
|
|
245
|
+
allow?: string | string[];
|
|
246
|
+
disallow?: string | string[];
|
|
247
|
+
crawlDelay?: number;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Return type for an `app/robots.ts` default export (Next-shaped). The runtime
|
|
251
|
+
* serializes it to `/robots.txt`.
|
|
252
|
+
*
|
|
253
|
+
* export default function robots(): Robots {
|
|
254
|
+
* return {
|
|
255
|
+
* rules: { userAgent: "*", allow: "/", disallow: "/admin" },
|
|
256
|
+
* sitemap: "https://x.com/sitemap.xml",
|
|
257
|
+
* };
|
|
258
|
+
* }
|
|
259
|
+
*/
|
|
260
|
+
export interface Robots {
|
|
261
|
+
rules: RobotsRule | RobotsRule[];
|
|
262
|
+
sitemap?: string | string[];
|
|
263
|
+
host?: string;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Per-route configuration, declared as top-level `export const` in a
|
|
267
|
+
* `page.tsx` (Next-shaped). All optional. The runtime reads these statically
|
|
268
|
+
* before the render.
|
|
269
|
+
*
|
|
270
|
+
* ```ts
|
|
271
|
+
* export const revalidate = 60; // CDN + origin-disk cache for 60s
|
|
272
|
+
* export const dynamic = "force-dynamic"; // never cache
|
|
273
|
+
* export const streaming = true; // progressive Suspense streaming
|
|
274
|
+
* ```
|
|
275
|
+
*
|
|
276
|
+
* - `revalidate` — seconds an auth-independent render stays cacheable
|
|
277
|
+
* (`public, s-maxage=N`); also kept in the origin disk cache. See the
|
|
278
|
+
* "Anonymous output caching" rules — it only takes effect if the render
|
|
279
|
+
* never reads `auth`, sets no cookie, and isn't running strict policies.
|
|
280
|
+
* - `dynamic` — `"force-static"` caches until the next deploy; `"force-dynamic"`
|
|
281
|
+
* opts out of all caching.
|
|
282
|
+
* - `streaming` — opt into PROGRESSIVE streaming: the shell + each inner
|
|
283
|
+
* `<Suspense>` fallback flush immediately, then each boundary's real content
|
|
284
|
+
* streams in as its data resolves (instead of buffering the whole document).
|
|
285
|
+
* A page with a `loading.tsx` already streams at the route level; this
|
|
286
|
+
* extends it to a page's OWN inner boundaries. Mutually exclusive with
|
|
287
|
+
* caching: a streaming render commits its HTTP head before suspended
|
|
288
|
+
* subtrees finish, so it can never be marked cacheable (and a deep
|
|
289
|
+
* `<Suspense>` throw resolves via its `error.tsx` at HTTP 200, not a 5xx).
|
|
290
|
+
* Set `response.*` (status/cookies) only in the synchronous shell — late
|
|
291
|
+
* calls from a suspended subtree are dropped (the runtime warns).
|
|
292
|
+
*/
|
|
293
|
+
export interface RouteSegmentConfig {
|
|
294
|
+
revalidate?: number;
|
|
295
|
+
dynamic?: "force-static" | "force-dynamic";
|
|
296
|
+
streaming?: boolean;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Props an `app/.../error.tsx` boundary receives. Error boundaries are now
|
|
300
|
+
* HYDRATED (interactive — useState/onClick/effects work), so `reset` is a
|
|
301
|
+
* real callback that re-attempts rendering the segment (a transient error
|
|
302
|
+
* clears to the page; a deterministic one re-shows the boundary).
|
|
303
|
+
*
|
|
304
|
+
* `error` carries ONLY the thrown error's `message` plus a short,
|
|
305
|
+
* non-reversible `digest` (a correlation id matching the server log). The
|
|
306
|
+
* stack NEVER reaches the client — read it from the dev overlay
|
|
307
|
+
* (`PYLON_DEV_MODE`) or the server logs.
|
|
308
|
+
*
|
|
309
|
+
* ```tsx
|
|
310
|
+
* export default function Error({ error, reset }: ErrorBoundaryProps) {
|
|
311
|
+
* return (
|
|
312
|
+
* <div>
|
|
313
|
+
* <p>Something went wrong: {error.message}</p>
|
|
314
|
+
* <button onClick={reset}>Try again</button>
|
|
315
|
+
* </div>
|
|
316
|
+
* );
|
|
317
|
+
* }
|
|
318
|
+
* ```
|
|
319
|
+
*/
|
|
320
|
+
export interface ErrorBoundaryProps {
|
|
321
|
+
error: {
|
|
322
|
+
message: string;
|
|
323
|
+
digest?: string;
|
|
324
|
+
};
|
|
325
|
+
reset: () => void;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Props an `app/.../not-found.tsx` boundary receives. Not-found boundaries
|
|
329
|
+
* are hydrated (interactive) too, but — matching Next — receive NO `reset`.
|
|
330
|
+
* Same shape as a page.
|
|
331
|
+
*/
|
|
332
|
+
export type NotFoundProps = PageProps;
|
|
333
|
+
/**
|
|
334
|
+
* Parsed form fields handed to a `route.ts` handler. Mirrors URLSearchParams
|
|
335
|
+
* `get`/`getAll`/`has` semantics over the submitted body.
|
|
336
|
+
*/
|
|
337
|
+
export interface FormFields {
|
|
338
|
+
/** First value for `name`, or null. */
|
|
339
|
+
get(name: string): string | null;
|
|
340
|
+
/** All values for `name` (empty array if none). */
|
|
341
|
+
getAll(name: string): string[];
|
|
342
|
+
has(name: string): boolean;
|
|
343
|
+
/** Raw map: name → value (single) or values (repeated field). */
|
|
344
|
+
readonly fields: Record<string, string | string[]>;
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Read + write DB handle for a `route.ts` form handler (mutation-shaped). The
|
|
348
|
+
* read surface is `ServerData`; writes go through the same policy-checked,
|
|
349
|
+
* broadcast-firing path a mutation's `ctx.db` uses.
|
|
350
|
+
*/
|
|
351
|
+
export interface FormDb extends ServerData {
|
|
352
|
+
insert<T = Record<string, unknown>>(entity: string, data: Record<string, unknown>): Promise<T>;
|
|
353
|
+
update<T = Record<string, unknown>>(entity: string, id: string, data: Record<string, unknown>): Promise<T>;
|
|
354
|
+
delete(entity: string, id: string): Promise<void>;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* The request a `route.ts` POST/PUT/PATCH/DELETE handler receives. Shape the
|
|
358
|
+
* reply through `response` — usually `response.redirect("/x?ok=1")` (303
|
|
359
|
+
* POST-redirect-GET, the default) after a write, so a no-JS browser follows
|
|
360
|
+
* with a GET. Enforce trust with `auth` inside the handler (forms run with the
|
|
361
|
+
* standard function trust model).
|
|
362
|
+
*
|
|
363
|
+
* ```ts
|
|
364
|
+
* import type { RouteHandler } from "@pylonsync/react";
|
|
365
|
+
* export const POST: RouteHandler = async ({ form, db, response, auth }) => {
|
|
366
|
+
* const body = form.get("body");
|
|
367
|
+
* if (!body) return response.redirect("/notes?error=empty");
|
|
368
|
+
* await db.insert("Note", { body });
|
|
369
|
+
* response.redirect("/notes?created=1");
|
|
370
|
+
* };
|
|
371
|
+
* ```
|
|
372
|
+
*/
|
|
373
|
+
export interface FormRequest<TParams extends Record<string, string> = Record<string, string>, TSearchParams extends Record<string, string> = Record<string, string>> {
|
|
374
|
+
form: FormFields;
|
|
375
|
+
params: TParams;
|
|
376
|
+
searchParams: TSearchParams;
|
|
377
|
+
auth: PageAuth;
|
|
378
|
+
cookies: Record<string, string>;
|
|
379
|
+
headers: Record<string, string>;
|
|
380
|
+
db: FormDb;
|
|
381
|
+
response: SsrResponse;
|
|
382
|
+
}
|
|
383
|
+
/** Signature of a `route.ts` method handler export (POST/PUT/PATCH/DELETE). */
|
|
384
|
+
export type RouteHandler<TParams extends Record<string, string> = Record<string, string>, TSearchParams extends Record<string, string> = Record<string, string>> = (req: FormRequest<TParams, TSearchParams>) => void | Promise<void>;
|
|
385
|
+
/**
|
|
386
|
+
* What a `route.ts` `GET` (raw) handler returns. The body is streamed verbatim
|
|
387
|
+
* with `contentType` (default `text/plain; charset=utf-8`), `status` (default
|
|
388
|
+
* 200), and any extra `headers` — no React render, no hydration tail. This is
|
|
389
|
+
* the GET analogue of `sitemap.ts`/`robots.ts`, at an arbitrary route path:
|
|
390
|
+
* RSS/Atom feeds, dynamic XML, plain text, JSON, `.well-known` documents.
|
|
391
|
+
*/
|
|
392
|
+
export interface RawResponse {
|
|
393
|
+
body?: string;
|
|
394
|
+
/** Defaults to `text/plain; charset=utf-8`. */
|
|
395
|
+
contentType?: string;
|
|
396
|
+
/** Defaults to 200. */
|
|
397
|
+
status?: number;
|
|
398
|
+
/** Extra response headers (e.g. `cache-control`). Lower-cased by the host. */
|
|
399
|
+
headers?: Record<string, string>;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Signature of a `route.ts` `GET` export — a RAW handler. Return a
|
|
403
|
+
* {@link RawResponse} to stream a body, or use `response.redirect()` /
|
|
404
|
+
* `response.notFound()` and return nothing.
|
|
405
|
+
*
|
|
406
|
+
* ```ts
|
|
407
|
+
* import type { RawRouteHandler } from "@pylonsync/react";
|
|
408
|
+
* export const GET: RawRouteHandler = async () => ({
|
|
409
|
+
* body: "<rss>…</rss>",
|
|
410
|
+
* contentType: "application/xml; charset=utf-8",
|
|
411
|
+
* headers: { "cache-control": "public, max-age=300" },
|
|
412
|
+
* });
|
|
413
|
+
* ```
|
|
414
|
+
*/
|
|
415
|
+
export type RawRouteHandler<TParams extends Record<string, string> = Record<string, string>, TSearchParams extends Record<string, string> = Record<string, string>> = (req: FormRequest<TParams, TSearchParams>) => RawResponse | void | Promise<RawResponse | void>;
|
package/dist/typed.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed database client — narrow pylon's untyped `db` object using your
|
|
3
|
+
* generated `AppSchema` (from `pylon codegen client`).
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* // src/pylon.client.ts (generated by `pylon codegen client`)
|
|
8
|
+
* export interface AppSchema {
|
|
9
|
+
* entities: { Todo: Todo; User: User };
|
|
10
|
+
* functions: {
|
|
11
|
+
* placeBid: { input: { lotId: string; amount: number }; output: { accepted: boolean } };
|
|
12
|
+
* };
|
|
13
|
+
* queries: {};
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* // src/db.ts
|
|
17
|
+
* import { createTypedDb } from "@pylonsync/react";
|
|
18
|
+
* import type { AppSchema } from "./pylon.client";
|
|
19
|
+
* export const db = createTypedDb<AppSchema>();
|
|
20
|
+
*
|
|
21
|
+
* // Components get full type safety:
|
|
22
|
+
* const { data } = db.useQuery("Todo"); // data: Todo[]
|
|
23
|
+
* const { data: user } = db.useQueryOne("User", "u_1"); // user: User | null
|
|
24
|
+
* const bid = db.useMutation("placeBid"); // typed args + result
|
|
25
|
+
* await bid.mutate({ lotId: "x", amount: 150 }); // TS checks input type
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
import { db as untypedDb } from "./db";
|
|
29
|
+
import type { QueryOptions, UseQueryReturn, UseQueryOneReturn, UseMutationReturn, UseInfiniteQueryReturn } from "./hooks";
|
|
30
|
+
/**
|
|
31
|
+
* Shape of the generated `AppSchema` interface.
|
|
32
|
+
*
|
|
33
|
+
* The CLI's `client_codegen` emits a type matching this shape. Consumers
|
|
34
|
+
* never construct `AppSchema` manually.
|
|
35
|
+
*/
|
|
36
|
+
export interface AgentDBSchema {
|
|
37
|
+
entities: Record<string, unknown>;
|
|
38
|
+
functions: Record<string, {
|
|
39
|
+
input: unknown;
|
|
40
|
+
output: unknown;
|
|
41
|
+
}>;
|
|
42
|
+
queries: Record<string, {
|
|
43
|
+
input: unknown;
|
|
44
|
+
output: unknown;
|
|
45
|
+
}>;
|
|
46
|
+
}
|
|
47
|
+
export interface TypedDb<S extends AgentDBSchema> {
|
|
48
|
+
/** Live query with type inferred from the schema. */
|
|
49
|
+
useQuery<K extends keyof S["entities"]>(entity: K, options?: QueryOptions): UseQueryReturn<S["entities"][K]>;
|
|
50
|
+
/** Live single-row query. */
|
|
51
|
+
useQueryOne<K extends keyof S["entities"]>(entity: K, id: string): UseQueryOneReturn<S["entities"][K]>;
|
|
52
|
+
/** Server-side function call with typed input/output. */
|
|
53
|
+
useMutation<K extends keyof S["functions"]>(fnName: K): UseMutationReturn<S["functions"][K]["input"], S["functions"][K]["output"]>;
|
|
54
|
+
/** Paginated live query. */
|
|
55
|
+
useInfiniteQuery<K extends keyof S["entities"]>(entity: K, options?: {
|
|
56
|
+
pageSize?: number;
|
|
57
|
+
}): UseInfiniteQueryReturn<S["entities"][K]>;
|
|
58
|
+
/** Call a server-side function directly (outside React). */
|
|
59
|
+
fn<K extends keyof S["functions"]>(name: K, args: S["functions"][K]["input"]): Promise<S["functions"][K]["output"]>;
|
|
60
|
+
/** Insert a row (optimistic). */
|
|
61
|
+
insert<K extends keyof S["entities"]>(entity: K, data: Partial<S["entities"][K]>): unknown;
|
|
62
|
+
/** Update a row (optimistic). */
|
|
63
|
+
update<K extends keyof S["entities"]>(entity: K, id: string, data: Partial<S["entities"][K]>): unknown;
|
|
64
|
+
/** Delete a row (optimistic). */
|
|
65
|
+
delete<K extends keyof S["entities"]>(entity: K, id: string): unknown;
|
|
66
|
+
/** Underlying untyped db (escape hatch). */
|
|
67
|
+
readonly untyped: typeof untypedDb;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Create a typed client from a generated `AppSchema`.
|
|
71
|
+
*
|
|
72
|
+
* At runtime this is literally the same object as `db`; types are narrowed
|
|
73
|
+
* at compile time via generics.
|
|
74
|
+
*/
|
|
75
|
+
export declare function createTypedDb<S extends AgentDBSchema>(): TypedDb<S>;
|