revine 1.3.0 → 1.4.0

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/client.d.ts CHANGED
@@ -8,5 +8,7 @@ export type { NavLinkProps } from "./components/NavLink.js";
8
8
  export { useRouter } from "./hooks/useRouter.js";
9
9
  export { defineConfig } from "./runtime/defineConfig.js";
10
10
  export { env, envAll } from "./runtime/env.js";
11
+ export { middlewareResponse } from "./runtime/middleware.js";
12
+ export type { MiddlewareConfig, MiddlewareFn, MiddlewareRequest, MiddlewareResponse } from "./runtime/middleware.js";
11
13
  export type { LayoutProps } from "./runtime/types.js";
12
14
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,cAAc,EACd,WAAW,EACX,WAAW,EACX,SAAS,EACT,eAAe,EAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAC9C,YAAY,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,YAAY,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,cAAc,EACd,WAAW,EACX,WAAW,EACX,SAAS,EACT,eAAe,EAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAC9C,YAAY,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,YAAY,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACrH,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC"}
package/dist/client.js CHANGED
@@ -5,3 +5,4 @@ export { NavLink } from "./components/NavLink.js";
5
5
  export { useRouter } from "./hooks/useRouter.js";
6
6
  export { defineConfig } from "./runtime/defineConfig.js";
7
7
  export { env, envAll } from "./runtime/env.js";
8
+ export { middlewareResponse } from "./runtime/middleware.js";
@@ -0,0 +1,10 @@
1
+ import React from "react";
2
+ import type { MiddlewareFn } from "../runtime/middleware.js";
3
+ type Props = {
4
+ middleware: MiddlewareFn;
5
+ children: React.ReactNode;
6
+ loadingFallback?: React.ReactNode;
7
+ };
8
+ export declare function MiddlewareGuard({ middleware, children, loadingFallback }: Props): import("react/jsx-runtime").JSX.Element;
9
+ export {};
10
+ //# sourceMappingURL=MiddlewareGuard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MiddlewareGuard.d.ts","sourceRoot":"","sources":["../../src/components/MiddlewareGuard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAEnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAE7D,KAAK,KAAK,GAAG;IACT,UAAU,EAAE,YAAY,CAAC;IACzB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,eAAe,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACrC,CAAC;AAEF,wBAAgB,eAAe,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,eAAe,EAAE,EAAE,KAAK,2CA4B/E"}
@@ -0,0 +1,28 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { useLocation, useNavigate } from "react-router-dom";
4
+ export function MiddlewareGuard({ middleware, children, loadingFallback }) {
5
+ const location = useLocation();
6
+ const navigate = useNavigate();
7
+ const [status, setStatus] = useState("pending");
8
+ useEffect(() => {
9
+ setStatus("pending");
10
+ const request = {
11
+ pathname: location.pathname,
12
+ searchParams: new URLSearchParams(location.search),
13
+ };
14
+ Promise.resolve(middleware(request)).then((response) => {
15
+ if (response.type === "redirect") {
16
+ setStatus("redirecting");
17
+ navigate(response.destination, { replace: true });
18
+ }
19
+ else {
20
+ setStatus("allowed");
21
+ }
22
+ });
23
+ }, [location.pathname]);
24
+ if (status === "pending" || status === "redirecting") {
25
+ return _jsx(_Fragment, { children: loadingFallback ?? null });
26
+ }
27
+ return _jsx(_Fragment, { children: children });
28
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"revinePlugin.d.ts","sourceRoot":"","sources":["../../../src/runtime/bundler/revinePlugin.ts"],"names":[],"mappings":"AA2VA,wBAAgB,YAAY,IAAI,GAAG,CA6IlC"}
1
+ {"version":3,"file":"revinePlugin.d.ts","sourceRoot":"","sources":["../../../src/runtime/bundler/revinePlugin.ts"],"names":[],"mappings":"AA2VA,wBAAgB,YAAY,IAAI,GAAG,CA4LlC"}
@@ -365,12 +365,51 @@ export function revinePlugin() {
365
365
  load(id) {
366
366
  if (id === VIRTUAL_ROUTING_ID) {
367
367
  return `
368
- import { createBrowserRouter, useRouteError } from "react-router-dom";
369
- import { lazy, Suspense, createElement } from "react";
368
+ import { createBrowserRouter, useRouteError, Outlet } from "react-router-dom";
369
+ import { lazy, Suspense, createElement, useState, useEffect, useRef } from "react";
370
370
  import React from "react";
371
371
 
372
+ // ── Middleware support ──────────────────────────────────────────────
373
+ const middlewareModules = import.meta.glob("/src/middleware.{ts,tsx}", { eager: true });
374
+ const middlewareMod = Object.values(middlewareModules)[0];
375
+ const userMiddleware = middlewareMod?.default ?? null;
376
+
372
377
  ${errorBoundaryComponent}
373
378
 
379
+ // ── MiddlewareGuard ─────────────────────────────────────────────────
380
+ function MiddlewareGuard({ children }) {
381
+ const [status, setStatus] = React.useState("pending");
382
+ const lastPathnameRef = React.useRef(null);
383
+
384
+ React.useEffect(() => {
385
+ if (!userMiddleware) { setStatus("allowed"); return; }
386
+
387
+ const run = async (pathname, search) => {
388
+ if (pathname === lastPathnameRef.current) return;
389
+ lastPathnameRef.current = pathname;
390
+
391
+ setStatus("pending");
392
+ const req = { pathname, searchParams: new URLSearchParams(search) };
393
+ const res = await Promise.resolve(userMiddleware(req));
394
+ if (res.type === "redirect") {
395
+ router.navigate(res.destination, { replace: true });
396
+ setStatus("redirecting");
397
+ } else {
398
+ setStatus("allowed");
399
+ }
400
+ };
401
+
402
+ run(window.location.pathname, window.location.search);
403
+
404
+ return router.subscribe((state) => {
405
+ run(state.location.pathname, state.location.search);
406
+ });
407
+ }, []);
408
+
409
+ if (status === "pending" || status === "redirecting") return null;
410
+ return React.createElement(React.Fragment, null, children);
411
+ }
412
+
374
413
  const notFoundModules = import.meta.glob("/src/NotFound.tsx", { eager: true });
375
414
  const NotFoundComponent = Object.values(notFoundModules)[0]?.default;
376
415
 
@@ -430,7 +469,7 @@ const pageEntries = Object.entries(pages).filter(([filePath]) => {
430
469
  return !segments.some((s) => s.startsWith("_"));
431
470
  });
432
471
 
433
- const routes = pageEntries.map(([filePath, component]) => {
472
+ const innerRoutes = pageEntries.map(([filePath, component]) => {
434
473
  const routePath = toRoutePath(filePath);
435
474
  const Component = lazy(component);
436
475
  const layouts = getLayoutsForPath(filePath);
@@ -453,7 +492,7 @@ const routes = pageEntries.map(([filePath, component]) => {
453
492
  };
454
493
  });
455
494
 
456
- routes.push({
495
+ innerRoutes.push({
457
496
  path: "*",
458
497
  element: NotFoundComponent
459
498
  ? createElement(NotFoundComponent)
@@ -461,6 +500,14 @@ routes.push({
461
500
  errorElement: createElement(RevineErrorDialog),
462
501
  });
463
502
 
503
+ const routes = [
504
+ {
505
+ element: createElement(MiddlewareGuard, null, createElement(Outlet)),
506
+ children: innerRoutes,
507
+ errorElement: createElement(RevineErrorDialog),
508
+ },
509
+ ];
510
+
464
511
  export const router = createBrowserRouter(routes, {
465
512
  future: {
466
513
  v7_startTransition: true,
@@ -0,0 +1,24 @@
1
+ export type MiddlewareRequest = {
2
+ pathname: string;
3
+ searchParams: URLSearchParams;
4
+ };
5
+ export type MiddlewareResponse = {
6
+ type: "next";
7
+ } | {
8
+ type: "redirect";
9
+ destination: string;
10
+ };
11
+ export declare const middlewareResponse: {
12
+ next: () => MiddlewareResponse;
13
+ redirect: (destination: string) => MiddlewareResponse;
14
+ };
15
+ export type MiddlewareFn = (request: MiddlewareRequest) => MiddlewareResponse | Promise<MiddlewareResponse>;
16
+ export type MiddlewareConfig = {
17
+ publicPaths?: string[];
18
+ authPaths?: string[];
19
+ redirects?: {
20
+ whenAuthenticated?: string;
21
+ whenUnauthenticated?: string;
22
+ };
23
+ };
24
+ //# sourceMappingURL=middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/runtime/middleware.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,eAAe,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9C,eAAO,MAAM,kBAAkB;gBACnB,kBAAkB;4BACJ,MAAM,KAAG,kBAAkB;CAIpD,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,CACzB,OAAO,EAAE,iBAAiB,KACvB,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;AAEtD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,CAAC,EAAE;QACV,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,mBAAmB,CAAC,EAAE,MAAM,CAAC;KAC9B,CAAC;CACH,CAAC"}
@@ -0,0 +1,7 @@
1
+ export const middlewareResponse = {
2
+ next: () => ({ type: "next" }),
3
+ redirect: (destination) => ({
4
+ type: "redirect",
5
+ destination,
6
+ }),
7
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "revine",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "A react framework, but better.",
5
5
  "license": "MIT",
6
6
  "author": "Rachit Bharadwaj",
package/src/client.ts CHANGED
@@ -15,4 +15,6 @@ export type { NavLinkProps } from "./components/NavLink.js";
15
15
  export { useRouter } from "./hooks/useRouter.js";
16
16
  export { defineConfig } from "./runtime/defineConfig.js";
17
17
  export { env, envAll } from "./runtime/env.js";
18
+ export { middlewareResponse } from "./runtime/middleware.js";
19
+ export type { MiddlewareConfig, MiddlewareFn, MiddlewareRequest, MiddlewareResponse } from "./runtime/middleware.js";
18
20
  export type { LayoutProps } from "./runtime/types.js";
@@ -0,0 +1,39 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { useLocation, useNavigate } from "react-router-dom";
3
+ import type { MiddlewareFn } from "../runtime/middleware.js";
4
+
5
+ type Props = {
6
+ middleware: MiddlewareFn;
7
+ children: React.ReactNode;
8
+ loadingFallback?: React.ReactNode;
9
+ };
10
+
11
+ export function MiddlewareGuard({ middleware, children, loadingFallback }: Props) {
12
+ const location = useLocation();
13
+ const navigate = useNavigate();
14
+ const [status, setStatus] = useState<"pending" | "allowed" | "redirecting">("pending");
15
+
16
+ useEffect(() => {
17
+ setStatus("pending");
18
+
19
+ const request = {
20
+ pathname: location.pathname,
21
+ searchParams: new URLSearchParams(location.search),
22
+ };
23
+
24
+ Promise.resolve(middleware(request)).then((response) => {
25
+ if (response.type === "redirect") {
26
+ setStatus("redirecting");
27
+ navigate(response.destination, { replace: true });
28
+ } else {
29
+ setStatus("allowed");
30
+ }
31
+ });
32
+ }, [location.pathname]);
33
+
34
+ if (status === "pending" || status === "redirecting") {
35
+ return <>{loadingFallback ?? null}</>;
36
+ }
37
+
38
+ return <>{children}</>;
39
+ }
@@ -380,12 +380,51 @@ export function revinePlugin(): any {
380
380
  load(id: string) {
381
381
  if (id === VIRTUAL_ROUTING_ID) {
382
382
  return `
383
- import { createBrowserRouter, useRouteError } from "react-router-dom";
384
- import { lazy, Suspense, createElement } from "react";
383
+ import { createBrowserRouter, useRouteError, Outlet } from "react-router-dom";
384
+ import { lazy, Suspense, createElement, useState, useEffect, useRef } from "react";
385
385
  import React from "react";
386
386
 
387
+ // ── Middleware support ──────────────────────────────────────────────
388
+ const middlewareModules = import.meta.glob("/src/middleware.{ts,tsx}", { eager: true });
389
+ const middlewareMod = Object.values(middlewareModules)[0];
390
+ const userMiddleware = middlewareMod?.default ?? null;
391
+
387
392
  ${errorBoundaryComponent}
388
393
 
394
+ // ── MiddlewareGuard ─────────────────────────────────────────────────
395
+ function MiddlewareGuard({ children }) {
396
+ const [status, setStatus] = React.useState("pending");
397
+ const lastPathnameRef = React.useRef(null);
398
+
399
+ React.useEffect(() => {
400
+ if (!userMiddleware) { setStatus("allowed"); return; }
401
+
402
+ const run = async (pathname, search) => {
403
+ if (pathname === lastPathnameRef.current) return;
404
+ lastPathnameRef.current = pathname;
405
+
406
+ setStatus("pending");
407
+ const req = { pathname, searchParams: new URLSearchParams(search) };
408
+ const res = await Promise.resolve(userMiddleware(req));
409
+ if (res.type === "redirect") {
410
+ router.navigate(res.destination, { replace: true });
411
+ setStatus("redirecting");
412
+ } else {
413
+ setStatus("allowed");
414
+ }
415
+ };
416
+
417
+ run(window.location.pathname, window.location.search);
418
+
419
+ return router.subscribe((state) => {
420
+ run(state.location.pathname, state.location.search);
421
+ });
422
+ }, []);
423
+
424
+ if (status === "pending" || status === "redirecting") return null;
425
+ return React.createElement(React.Fragment, null, children);
426
+ }
427
+
389
428
  const notFoundModules = import.meta.glob("/src/NotFound.tsx", { eager: true });
390
429
  const NotFoundComponent = Object.values(notFoundModules)[0]?.default;
391
430
 
@@ -445,7 +484,7 @@ const pageEntries = Object.entries(pages).filter(([filePath]) => {
445
484
  return !segments.some((s) => s.startsWith("_"));
446
485
  });
447
486
 
448
- const routes = pageEntries.map(([filePath, component]) => {
487
+ const innerRoutes = pageEntries.map(([filePath, component]) => {
449
488
  const routePath = toRoutePath(filePath);
450
489
  const Component = lazy(component);
451
490
  const layouts = getLayoutsForPath(filePath);
@@ -468,7 +507,7 @@ const routes = pageEntries.map(([filePath, component]) => {
468
507
  };
469
508
  });
470
509
 
471
- routes.push({
510
+ innerRoutes.push({
472
511
  path: "*",
473
512
  element: NotFoundComponent
474
513
  ? createElement(NotFoundComponent)
@@ -476,6 +515,14 @@ routes.push({
476
515
  errorElement: createElement(RevineErrorDialog),
477
516
  });
478
517
 
518
+ const routes = [
519
+ {
520
+ element: createElement(MiddlewareGuard, null, createElement(Outlet)),
521
+ children: innerRoutes,
522
+ errorElement: createElement(RevineErrorDialog),
523
+ },
524
+ ];
525
+
479
526
  export const router = createBrowserRouter(routes, {
480
527
  future: {
481
528
  v7_startTransition: true,
@@ -0,0 +1,29 @@
1
+ export type MiddlewareRequest = {
2
+ pathname: string;
3
+ searchParams: URLSearchParams;
4
+ };
5
+
6
+ export type MiddlewareResponse =
7
+ | { type: "next" }
8
+ | { type: "redirect"; destination: string };
9
+
10
+ export const middlewareResponse = {
11
+ next: (): MiddlewareResponse => ({ type: "next" }),
12
+ redirect: (destination: string): MiddlewareResponse => ({
13
+ type: "redirect",
14
+ destination,
15
+ }),
16
+ };
17
+
18
+ export type MiddlewareFn = (
19
+ request: MiddlewareRequest,
20
+ ) => MiddlewareResponse | Promise<MiddlewareResponse>;
21
+
22
+ export type MiddlewareConfig = {
23
+ publicPaths?: string[]; // paths accessible WITHOUT login
24
+ authPaths?: string[]; // paths only for GUESTS (login, register)
25
+ redirects?: {
26
+ whenAuthenticated?: string; // redirect auth pages to (default: "/")
27
+ whenUnauthenticated?: string; // redirect private pages to (default: "/login")
28
+ };
29
+ };