@pylonsync/react 0.3.247 → 0.3.249
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 +3 -3
- package/src/Form.tsx +41 -0
- package/src/index.ts +8 -0
- package/src/ssr.ts +103 -0
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.3.
|
|
6
|
+
"version": "0.3.249",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "src/index.ts",
|
|
9
9
|
"types": "src/index.ts",
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
"check": "tsc -p tsconfig.json --noEmit"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@pylonsync/sdk": "0.3.
|
|
16
|
-
"@pylonsync/sync": "0.3.
|
|
15
|
+
"@pylonsync/sdk": "0.3.249",
|
|
16
|
+
"@pylonsync/sync": "0.3.249"
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
19
19
|
"react": ">=19.0.0"
|
package/src/Form.tsx
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
// Pylon's progressive-enhancement form. Renders a plain `<form method="post">`
|
|
4
|
+
// that works WITHOUT client JS: the browser POSTs to `action` (a route.ts
|
|
5
|
+
// path), the handler processes + redirects (303 See Other), and the browser
|
|
6
|
+
// follows with a GET (POST-redirect-GET). WITH JS, the runtime's global submit
|
|
7
|
+
// handler intercepts (via `data-pylon-form`), `fetch()`es the same endpoint,
|
|
8
|
+
// and follows the redirect through client-side navigation — no full reload.
|
|
9
|
+
//
|
|
10
|
+
// `<form method>` is POST-only without JS (browsers only do GET/POST natively),
|
|
11
|
+
// so POST is the default + the no-JS-safe choice. Set `navigate={false}` to
|
|
12
|
+
// force a native full-page submit even when JS is available.
|
|
13
|
+
export interface FormProps
|
|
14
|
+
extends Omit<React.FormHTMLAttributes<HTMLFormElement>, "method" | "action"> {
|
|
15
|
+
/** The `route.ts` handler path (e.g. "/notes"). */
|
|
16
|
+
action: string;
|
|
17
|
+
/** HTTP method. Default "post" (the only method usable without JS). */
|
|
18
|
+
method?: "post" | "put" | "patch" | "delete";
|
|
19
|
+
/** Opt out of client interception — force a native full-page submit. */
|
|
20
|
+
navigate?: boolean;
|
|
21
|
+
children?: React.ReactNode;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function Form({
|
|
25
|
+
action,
|
|
26
|
+
method = "post",
|
|
27
|
+
navigate = true,
|
|
28
|
+
children,
|
|
29
|
+
...rest
|
|
30
|
+
}: FormProps) {
|
|
31
|
+
return (
|
|
32
|
+
<form
|
|
33
|
+
method={method}
|
|
34
|
+
action={action}
|
|
35
|
+
{...(navigate ? { "data-pylon-form": "" } : {})}
|
|
36
|
+
{...rest}
|
|
37
|
+
>
|
|
38
|
+
{children}
|
|
39
|
+
</form>
|
|
40
|
+
);
|
|
41
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,8 @@ export { Link } from "./Link";
|
|
|
7
7
|
export type { LinkProps } from "./Link";
|
|
8
8
|
export { Image } from "./Image";
|
|
9
9
|
export type { ImageProps } from "./Image";
|
|
10
|
+
export { Form } from "./Form";
|
|
11
|
+
export type { FormProps } from "./Form";
|
|
10
12
|
|
|
11
13
|
// SSR page-author types — the contract every `app/**/page.tsx` is handed
|
|
12
14
|
// in props, plus `metadata` / `generateMetadata`. Type-only.
|
|
@@ -18,6 +20,12 @@ export type {
|
|
|
18
20
|
SsrCookieOptions,
|
|
19
21
|
Metadata,
|
|
20
22
|
GenerateMetadata,
|
|
23
|
+
ErrorBoundaryProps,
|
|
24
|
+
NotFoundProps,
|
|
25
|
+
FormFields,
|
|
26
|
+
FormDb,
|
|
27
|
+
FormRequest,
|
|
28
|
+
RouteHandler,
|
|
21
29
|
} from "./ssr";
|
|
22
30
|
|
|
23
31
|
// Client navigation hooks for SSR pages (Next-style).
|
package/src/ssr.ts
CHANGED
|
@@ -205,3 +205,106 @@ export type GenerateMetadata<
|
|
|
205
205
|
> = (
|
|
206
206
|
props: PageProps<TParams, TSearchParams>,
|
|
207
207
|
) => Metadata | Promise<Metadata>;
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Props an `app/.../error.tsx` boundary receives. Error boundaries are now
|
|
211
|
+
* HYDRATED (interactive — useState/onClick/effects work), so `reset` is a
|
|
212
|
+
* real callback that re-attempts rendering the segment (a transient error
|
|
213
|
+
* clears to the page; a deterministic one re-shows the boundary).
|
|
214
|
+
*
|
|
215
|
+
* `error` carries ONLY the thrown error's `message` plus a short,
|
|
216
|
+
* non-reversible `digest` (a correlation id matching the server log). The
|
|
217
|
+
* stack NEVER reaches the client — read it from the dev overlay
|
|
218
|
+
* (`PYLON_DEV_MODE`) or the server logs.
|
|
219
|
+
*
|
|
220
|
+
* ```tsx
|
|
221
|
+
* export default function Error({ error, reset }: ErrorBoundaryProps) {
|
|
222
|
+
* return (
|
|
223
|
+
* <div>
|
|
224
|
+
* <p>Something went wrong: {error.message}</p>
|
|
225
|
+
* <button onClick={reset}>Try again</button>
|
|
226
|
+
* </div>
|
|
227
|
+
* );
|
|
228
|
+
* }
|
|
229
|
+
* ```
|
|
230
|
+
*/
|
|
231
|
+
export interface ErrorBoundaryProps {
|
|
232
|
+
error: { message: string; digest?: string };
|
|
233
|
+
reset: () => void;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Props an `app/.../not-found.tsx` boundary receives. Not-found boundaries
|
|
238
|
+
* are hydrated (interactive) too, but — matching Next — receive NO `reset`.
|
|
239
|
+
* Same shape as a page.
|
|
240
|
+
*/
|
|
241
|
+
export type NotFoundProps = PageProps;
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Parsed form fields handed to a `route.ts` handler. Mirrors URLSearchParams
|
|
245
|
+
* `get`/`getAll`/`has` semantics over the submitted body.
|
|
246
|
+
*/
|
|
247
|
+
export interface FormFields {
|
|
248
|
+
/** First value for `name`, or null. */
|
|
249
|
+
get(name: string): string | null;
|
|
250
|
+
/** All values for `name` (empty array if none). */
|
|
251
|
+
getAll(name: string): string[];
|
|
252
|
+
has(name: string): boolean;
|
|
253
|
+
/** Raw map: name → value (single) or values (repeated field). */
|
|
254
|
+
readonly fields: Record<string, string | string[]>;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Read + write DB handle for a `route.ts` form handler (mutation-shaped). The
|
|
259
|
+
* read surface is `ServerData`; writes go through the same policy-checked,
|
|
260
|
+
* broadcast-firing path a mutation's `ctx.db` uses.
|
|
261
|
+
*/
|
|
262
|
+
export interface FormDb extends ServerData {
|
|
263
|
+
insert<T = Record<string, unknown>>(
|
|
264
|
+
entity: string,
|
|
265
|
+
data: Record<string, unknown>,
|
|
266
|
+
): Promise<T>;
|
|
267
|
+
update<T = Record<string, unknown>>(
|
|
268
|
+
entity: string,
|
|
269
|
+
id: string,
|
|
270
|
+
data: Record<string, unknown>,
|
|
271
|
+
): Promise<T>;
|
|
272
|
+
delete(entity: string, id: string): Promise<void>;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* The request a `route.ts` POST/PUT/PATCH/DELETE handler receives. Shape the
|
|
277
|
+
* reply through `response` — usually `response.redirect("/x?ok=1")` (303
|
|
278
|
+
* POST-redirect-GET, the default) after a write, so a no-JS browser follows
|
|
279
|
+
* with a GET. Enforce trust with `auth` inside the handler (forms run with the
|
|
280
|
+
* standard function trust model).
|
|
281
|
+
*
|
|
282
|
+
* ```ts
|
|
283
|
+
* import type { RouteHandler } from "@pylonsync/react";
|
|
284
|
+
* export const POST: RouteHandler = async ({ form, db, response, auth }) => {
|
|
285
|
+
* const body = form.get("body");
|
|
286
|
+
* if (!body) return response.redirect("/notes?error=empty");
|
|
287
|
+
* await db.insert("Note", { body });
|
|
288
|
+
* response.redirect("/notes?created=1");
|
|
289
|
+
* };
|
|
290
|
+
* ```
|
|
291
|
+
*/
|
|
292
|
+
export interface FormRequest<
|
|
293
|
+
TParams extends Record<string, string> = Record<string, string>,
|
|
294
|
+
TSearchParams extends Record<string, string> = Record<string, string>,
|
|
295
|
+
> {
|
|
296
|
+
form: FormFields;
|
|
297
|
+
params: TParams;
|
|
298
|
+
searchParams: TSearchParams;
|
|
299
|
+
auth: PageAuth;
|
|
300
|
+
cookies: Record<string, string>;
|
|
301
|
+
headers: Record<string, string>;
|
|
302
|
+
db: FormDb;
|
|
303
|
+
response: SsrResponse;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** Signature of a `route.ts` method handler export (POST/PUT/PATCH/DELETE). */
|
|
307
|
+
export type RouteHandler<
|
|
308
|
+
TParams extends Record<string, string> = Record<string, string>,
|
|
309
|
+
TSearchParams extends Record<string, string> = Record<string, string>,
|
|
310
|
+
> = (req: FormRequest<TParams, TSearchParams>) => void | Promise<void>;
|