@okam/directus-next 1.1.1 → 1.2.1

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 (47) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/{draft → directus-next/src/draft}/route.js +3 -3
  3. package/{draft → directus-next/src/draft}/route.mjs +3 -3
  4. package/{index.js → directus-next/src/index.js} +11 -1
  5. package/directus-next/src/index.mjs +24 -0
  6. package/{lib → directus-next/src/lib}/directusRouteRouter.js +10 -4
  7. package/{lib → directus-next/src/lib}/directusRouteRouter.mjs +14 -8
  8. package/directus-next/src/redirect/env.js +20 -0
  9. package/directus-next/src/redirect/env.mjs +20 -0
  10. package/directus-next/src/redirect/route.js +31 -0
  11. package/directus-next/src/redirect/route.mjs +31 -0
  12. package/directus-next/src/redirect/utils/getRedirectsRoute.js +27 -0
  13. package/directus-next/src/redirect/utils/getRedirectsRoute.mjs +27 -0
  14. package/directus-next/src/redirect/utils/handleRedirect.js +48 -0
  15. package/directus-next/src/redirect/utils/handleRedirect.mjs +48 -0
  16. package/directus-node/src/lib/redirection/fetchRedirectsData.js +92 -0
  17. package/directus-node/src/lib/redirection/fetchRedirectsData.mjs +92 -0
  18. package/directus-node/src/lib/redirection/utils/validateRedirects.js +22 -0
  19. package/directus-node/src/lib/redirection/utils/validateRedirects.mjs +22 -0
  20. package/draft/index.d.ts +1 -1
  21. package/draft/route.d.ts +1 -1
  22. package/index.d.ts +2 -0
  23. package/lib/directusRouteRouter.d.ts +9 -14
  24. package/package.json +3 -1
  25. package/redirect/env.d.ts +2 -0
  26. package/redirect/index.d.ts +5 -0
  27. package/redirect/interface.d.ts +25 -0
  28. package/redirect/route.d.ts +8 -0
  29. package/redirect/utils/getRedirectsRoute.d.ts +8 -0
  30. package/redirect/utils/handleRedirect.d.ts +7 -0
  31. package/types/directusRouteConfig.d.ts +17 -0
  32. package/types/index.d.ts +2 -0
  33. package/types/next.d.ts +12 -0
  34. package/types/pageSettings.d.ts +13 -0
  35. package/index.mjs +0 -14
  36. /package/{draft → directus-next/src/draft}/env.js +0 -0
  37. /package/{draft → directus-next/src/draft}/env.mjs +0 -0
  38. /package/{logger.js → directus-next/src/logger.js} +0 -0
  39. /package/{logger.mjs → directus-next/src/logger.mjs} +0 -0
  40. /package/{pageSettings → directus-next/src/pageSettings}/context.js +0 -0
  41. /package/{pageSettings → directus-next/src/pageSettings}/context.mjs +0 -0
  42. /package/{pageSettings → directus-next/src/pageSettings}/usePageSettings.js +0 -0
  43. /package/{pageSettings → directus-next/src/pageSettings}/usePageSettings.mjs +0 -0
  44. /package/{response.js → directus-next/src/response.js} +0 -0
  45. /package/{response.mjs → directus-next/src/response.mjs} +0 -0
  46. /package/{server.js → directus-next/src/server.js} +0 -0
  47. /package/{server.mjs → directus-next/src/server.mjs} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,27 @@
1
+ ## 1.2.1 (2025-07-22)
2
+
3
+ ### 🩹 Fixes
4
+
5
+ - **directus-next:** import from direrctus-node/edge instead ([99d3bce](https://github.com/OKAMca/stack/commit/99d3bce))
6
+
7
+ ### 🧱 Updated Dependencies
8
+
9
+ - Updated directus-node to 0.6.0
10
+
11
+ ### ❤️ Thank You
12
+
13
+ - poclerson
14
+
15
+ ## 1.2.0 (2025-07-22)
16
+
17
+ ### 🚀 Features
18
+
19
+ - **directus-next:** /api/redirect route handler to fetch + cache directus redirects ([c21b953](https://github.com/OKAMca/stack/commit/c21b953))
20
+
21
+ ### ❤️ Thank You
22
+
23
+ - Pierre-Olivier Clerson @poclerson
24
+
1
25
  ## 1.1.1 (2025-07-09)
2
26
 
3
27
  ### 🩹 Fixes
@@ -5,7 +5,7 @@ const navigation = require("next/navigation");
5
5
  const radashi = require("radashi");
6
6
  const response = require("../response.js");
7
7
  const env = require("./env.js");
8
- function parseParams(url) {
8
+ function parseDraftParams(url) {
9
9
  const { searchParams } = new URL(url);
10
10
  const secret = searchParams.get("secret") || "";
11
11
  const languagesParam = searchParams.get("languages");
@@ -91,7 +91,7 @@ function handleDraftRoute({
91
91
  }) {
92
92
  const getSecretFunction = getDraftSecret || env.getDraftSecretDefault;
93
93
  const getJsonErrorResponseFunction = getJsonError || response.getJsonErrorResponse;
94
- const { secret, languages, paths, routes, type, version } = parseParams(url);
94
+ const { secret, languages, paths, routes, type, version } = parseDraftParams(url);
95
95
  if (secret !== getSecretFunction()) {
96
96
  return getJsonErrorResponseFunction({ error: "Invalid argument" }, 401);
97
97
  }
@@ -138,4 +138,4 @@ function handleDraftRoute({
138
138
  }
139
139
  exports.default = handleDraftRoute;
140
140
  exports.getPathFromRoute = getPathFromRoute;
141
- exports.parseParams = parseParams;
141
+ exports.parseDraftParams = parseDraftParams;
@@ -3,7 +3,7 @@ import { redirect } from "next/navigation";
3
3
  import { template } from "radashi";
4
4
  import { getJsonErrorResponse } from "../response.mjs";
5
5
  import { getDraftSecretDefault } from "./env.mjs";
6
- function parseParams(url) {
6
+ function parseDraftParams(url) {
7
7
  const { searchParams } = new URL(url);
8
8
  const secret = searchParams.get("secret") || "";
9
9
  const languagesParam = searchParams.get("languages");
@@ -89,7 +89,7 @@ function handleDraftRoute({
89
89
  }) {
90
90
  const getSecretFunction = getDraftSecret || getDraftSecretDefault;
91
91
  const getJsonErrorResponseFunction = getJsonError || getJsonErrorResponse;
92
- const { secret, languages, paths, routes, type, version } = parseParams(url);
92
+ const { secret, languages, paths, routes, type, version } = parseDraftParams(url);
93
93
  if (secret !== getSecretFunction()) {
94
94
  return getJsonErrorResponseFunction({ error: "Invalid argument" }, 401);
95
95
  }
@@ -137,5 +137,5 @@ function handleDraftRoute({
137
137
  export {
138
138
  handleDraftRoute as default,
139
139
  getPathFromRoute,
140
- parseParams
140
+ parseDraftParams
141
141
  };
@@ -2,13 +2,23 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const route = require("./draft/route.js");
4
4
  const env = require("./draft/env.js");
5
+ const env$1 = require("./redirect/env.js");
6
+ const route$1 = require("./redirect/route.js");
7
+ const getRedirectsRoute = require("./redirect/utils/getRedirectsRoute.js");
8
+ const handleRedirect = require("./redirect/utils/handleRedirect.js");
5
9
  const logger = require("./logger.js");
6
10
  const directusRouteRouter = require("./lib/directusRouteRouter.js");
7
11
  const response = require("./response.js");
8
12
  exports.getPathFromRoute = route.getPathFromRoute;
9
13
  exports.handleDraftRoute = route.default;
10
- exports.parseParams = route.parseParams;
14
+ exports.parseDraftParams = route.parseDraftParams;
11
15
  exports.getDraftSecretDefault = env.getDraftSecretDefault;
16
+ exports.getApiRouteUrlDefault = env$1.getApiRouteUrlDefault;
17
+ exports.getRedirectSecretDefault = env$1.getRedirectSecretDefault;
18
+ exports.handleRedirectsRoute = route$1.default;
19
+ exports.parseRedirectParams = route$1.parseRedirectParams;
20
+ exports.getRedirectsRoute = getRedirectsRoute.getRedirectsRoute;
21
+ exports.handleRedirect = handleRedirect.handleRedirect;
12
22
  exports.DirectusNextLogger = logger.logger;
13
23
  exports.directusRouteRouter = directusRouteRouter.directusRouteRouter;
14
24
  exports.getJsonErrorResponse = response.getJsonErrorResponse;
@@ -0,0 +1,24 @@
1
+ import { getPathFromRoute, default as default2, parseDraftParams } from "./draft/route.mjs";
2
+ import { getDraftSecretDefault } from "./draft/env.mjs";
3
+ import { getApiRouteUrlDefault, getRedirectSecretDefault } from "./redirect/env.mjs";
4
+ import { default as default3, parseRedirectParams } from "./redirect/route.mjs";
5
+ import { getRedirectsRoute } from "./redirect/utils/getRedirectsRoute.mjs";
6
+ import { handleRedirect } from "./redirect/utils/handleRedirect.mjs";
7
+ import { logger } from "./logger.mjs";
8
+ import { directusRouteRouter } from "./lib/directusRouteRouter.mjs";
9
+ import { getJsonErrorResponse } from "./response.mjs";
10
+ export {
11
+ logger as DirectusNextLogger,
12
+ directusRouteRouter,
13
+ getApiRouteUrlDefault,
14
+ getDraftSecretDefault,
15
+ getJsonErrorResponse,
16
+ getPathFromRoute,
17
+ getRedirectSecretDefault,
18
+ getRedirectsRoute,
19
+ default2 as handleDraftRoute,
20
+ handleRedirect,
21
+ default3 as handleRedirectsRoute,
22
+ parseDraftParams,
23
+ parseRedirectParams
24
+ };
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const server = require("next/server");
3
4
  const logger = require("../logger.js");
5
+ const handleRedirect = require("../redirect/utils/handleRedirect.js");
4
6
  async function fetchPageSettingsTranslation(path) {
5
7
  const graphqlEndpoint = process.env.NEXT_SERVER_GRAPHQL_URL || process.env.NEXT_PUBLIC_GRAPHQL_URL;
6
8
  const graphqlApiKey = process.env.NEXT_PUBLIC_API_TOKEN;
@@ -56,11 +58,15 @@ function removeLocaleFromPathname(pathname, config) {
56
58
  const currentLocale = Object.values(config.localeMap ?? {}).find((locale) => pathname.startsWith(`/${locale}/`));
57
59
  return { locale: currentLocale, pathname: currentLocale ? pathname.replace(`/${currentLocale}/`, "/") : pathname };
58
60
  }
59
- async function directusRouteRouter(request, config, NextResponse) {
60
- var _a, _b;
61
+ async function directusRouteRouter(request, config, NextResponse = server.NextResponse) {
62
+ var _a, _b, _c;
61
63
  const { pathname: localizedPathname } = request.nextUrl;
62
64
  const { locale, pathname } = removeLocaleFromPathname(localizedPathname, config);
63
65
  logger.log("Processing request for pathname:", { locale, pathname });
66
+ const redirect = await handleRedirect.handleRedirect(request, (_a = config.modules) == null ? void 0 : _a.redirects);
67
+ if (redirect) {
68
+ return redirect;
69
+ }
64
70
  const translations = await fetchPageSettingsTranslation(pathname);
65
71
  if (!translations || translations.length === 0) {
66
72
  logger.log("No translation found for path:", pathname);
@@ -79,8 +85,8 @@ async function directusRouteRouter(request, config, NextResponse) {
79
85
  logger.log(`PageSettings with id ${id} was found but is not associated with any collection.`, { id }, "warn");
80
86
  return NextResponse.next();
81
87
  }
82
- const mappedLocale = ((_a = config.localeMap) == null ? void 0 : _a[directusLocale]) || directusLocale;
83
- const idField = ((_b = config.collectionSettings[collection]) == null ? void 0 : _b.idField) || config.collectionSettings.default.idField;
88
+ const mappedLocale = ((_b = config.localeMap) == null ? void 0 : _b[directusLocale]) || directusLocale;
89
+ const idField = ((_c = config.collectionSettings[collection]) == null ? void 0 : _c.idField) || config.collectionSettings.default.idField;
84
90
  logger.log("Directus locale:", directusLocale);
85
91
  logger.log("Mapped locale:", mappedLocale);
86
92
  logger.log("Collection:", collection);
@@ -1,4 +1,6 @@
1
+ import { NextResponse } from "next/server";
1
2
  import { log } from "../logger.mjs";
3
+ import { handleRedirect } from "../redirect/utils/handleRedirect.mjs";
2
4
  async function fetchPageSettingsTranslation(path) {
3
5
  const graphqlEndpoint = process.env.NEXT_SERVER_GRAPHQL_URL || process.env.NEXT_PUBLIC_GRAPHQL_URL;
4
6
  const graphqlApiKey = process.env.NEXT_PUBLIC_API_TOKEN;
@@ -54,31 +56,35 @@ function removeLocaleFromPathname(pathname, config) {
54
56
  const currentLocale = Object.values(config.localeMap ?? {}).find((locale) => pathname.startsWith(`/${locale}/`));
55
57
  return { locale: currentLocale, pathname: currentLocale ? pathname.replace(`/${currentLocale}/`, "/") : pathname };
56
58
  }
57
- async function directusRouteRouter(request, config, NextResponse) {
58
- var _a, _b;
59
+ async function directusRouteRouter(request, config, NextResponse$1 = NextResponse) {
60
+ var _a, _b, _c;
59
61
  const { pathname: localizedPathname } = request.nextUrl;
60
62
  const { locale, pathname } = removeLocaleFromPathname(localizedPathname, config);
61
63
  log("Processing request for pathname:", { locale, pathname });
64
+ const redirect = await handleRedirect(request, (_a = config.modules) == null ? void 0 : _a.redirects);
65
+ if (redirect) {
66
+ return redirect;
67
+ }
62
68
  const translations = await fetchPageSettingsTranslation(pathname);
63
69
  if (!translations || translations.length === 0) {
64
70
  log("No translation found for path:", pathname);
65
- return NextResponse.next();
71
+ return NextResponse$1.next();
66
72
  }
67
73
  const translation = translations[0];
68
74
  log("Using translation:", translation);
69
75
  if (!translation.languages_code || !translation.page_settings_id) {
70
76
  log(`Invalid translation data for path: ${pathname}`, { pathname }, "warn");
71
- return NextResponse.next();
77
+ return NextResponse$1.next();
72
78
  }
73
79
  const directusLocale = translation.languages_code.code;
74
80
  const collection = translation.page_settings_id.belongs_to_collection;
75
81
  const id = translation.page_settings_id.belongs_to_key;
76
82
  if (!collection) {
77
83
  log(`PageSettings with id ${id} was found but is not associated with any collection.`, { id }, "warn");
78
- return NextResponse.next();
84
+ return NextResponse$1.next();
79
85
  }
80
- const mappedLocale = ((_a = config.localeMap) == null ? void 0 : _a[directusLocale]) || directusLocale;
81
- const idField = ((_b = config.collectionSettings[collection]) == null ? void 0 : _b.idField) || config.collectionSettings.default.idField;
86
+ const mappedLocale = ((_b = config.localeMap) == null ? void 0 : _b[directusLocale]) || directusLocale;
87
+ const idField = ((_c = config.collectionSettings[collection]) == null ? void 0 : _c.idField) || config.collectionSettings.default.idField;
82
88
  log("Directus locale:", directusLocale);
83
89
  log("Mapped locale:", mappedLocale);
84
90
  log("Collection:", collection);
@@ -89,7 +95,7 @@ async function directusRouteRouter(request, config, NextResponse) {
89
95
  const url = request.nextUrl.clone();
90
96
  url.pathname = newPath;
91
97
  log("Rewriting to URL:", url.toString());
92
- return NextResponse.rewrite(url);
98
+ return NextResponse$1.rewrite(url);
93
99
  }
94
100
  export {
95
101
  directusRouteRouter
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const logger = require("../logger.js");
4
+ const defaultInternalUrl = "http://localhost:3000";
5
+ function getRedirectSecretDefault() {
6
+ return process.env.NEXT_API_REDIRECT_SECRET || "";
7
+ }
8
+ function getVercelUrl() {
9
+ const url = process.env.VERCEL_URL;
10
+ if (!url) return null;
11
+ return `https://${url}`;
12
+ }
13
+ function getApiRouteUrlDefault() {
14
+ const url = process.env.NEXT_MIDDLEWARE_REDIRECT_URL ?? getVercelUrl() ?? defaultInternalUrl;
15
+ if (URL.canParse(url)) return url;
16
+ logger.log(`Invalid URL ${url}. Falling back to default`, { url }, "warn");
17
+ return defaultInternalUrl;
18
+ }
19
+ exports.getApiRouteUrlDefault = getApiRouteUrlDefault;
20
+ exports.getRedirectSecretDefault = getRedirectSecretDefault;
@@ -0,0 +1,20 @@
1
+ import { log } from "../logger.mjs";
2
+ const defaultInternalUrl = "http://localhost:3000";
3
+ function getRedirectSecretDefault() {
4
+ return process.env.NEXT_API_REDIRECT_SECRET || "";
5
+ }
6
+ function getVercelUrl() {
7
+ const url = process.env.VERCEL_URL;
8
+ if (!url) return null;
9
+ return `https://${url}`;
10
+ }
11
+ function getApiRouteUrlDefault() {
12
+ const url = process.env.NEXT_MIDDLEWARE_REDIRECT_URL ?? getVercelUrl() ?? defaultInternalUrl;
13
+ if (URL.canParse(url)) return url;
14
+ log(`Invalid URL ${url}. Falling back to default`, { url }, "warn");
15
+ return defaultInternalUrl;
16
+ }
17
+ export {
18
+ getApiRouteUrlDefault,
19
+ getRedirectSecretDefault
20
+ };
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
+ const fetchRedirectsData = require("../../../directus-node/src/lib/redirection/fetchRedirectsData.js");
4
+ require("@okam/core-lib");
5
+ const response = require("../response.js");
6
+ const env = require("./env.js");
7
+ function parseRedirectParams(url) {
8
+ const { searchParams } = new URL(url);
9
+ const secret = searchParams.get("secret") || "";
10
+ return { secret };
11
+ }
12
+ async function handleRedirectsRoute({
13
+ url,
14
+ getRedirectSecret = env.getRedirectSecretDefault,
15
+ getJsonError = response.getJsonErrorResponse,
16
+ getDirectusApiToken,
17
+ getDirectusGraphqlUrl,
18
+ limit,
19
+ init
20
+ }) {
21
+ const { secret } = parseRedirectParams(url);
22
+ if (secret !== getRedirectSecret()) {
23
+ return getJsonError({ error: "Invalid argument" }, 401);
24
+ }
25
+ const graphqlEndpoint = getDirectusGraphqlUrl == null ? void 0 : getDirectusGraphqlUrl();
26
+ const graphqlApiKey = getDirectusApiToken == null ? void 0 : getDirectusApiToken();
27
+ const { redirects, rewrites } = await fetchRedirectsData.fetchRedirectsData({ graphqlEndpoint, graphqlApiKey, limit }, init);
28
+ return new Response(JSON.stringify({ redirects, rewrites }), { status: 200 });
29
+ }
30
+ exports.default = handleRedirectsRoute;
31
+ exports.parseRedirectParams = parseRedirectParams;
@@ -0,0 +1,31 @@
1
+ import { fetchRedirectsData } from "../../../directus-node/src/lib/redirection/fetchRedirectsData.mjs";
2
+ import "@okam/core-lib";
3
+ import { getJsonErrorResponse } from "../response.mjs";
4
+ import { getRedirectSecretDefault } from "./env.mjs";
5
+ function parseRedirectParams(url) {
6
+ const { searchParams } = new URL(url);
7
+ const secret = searchParams.get("secret") || "";
8
+ return { secret };
9
+ }
10
+ async function handleRedirectsRoute({
11
+ url,
12
+ getRedirectSecret = getRedirectSecretDefault,
13
+ getJsonError = getJsonErrorResponse,
14
+ getDirectusApiToken,
15
+ getDirectusGraphqlUrl,
16
+ limit,
17
+ init
18
+ }) {
19
+ const { secret } = parseRedirectParams(url);
20
+ if (secret !== getRedirectSecret()) {
21
+ return getJsonError({ error: "Invalid argument" }, 401);
22
+ }
23
+ const graphqlEndpoint = getDirectusGraphqlUrl == null ? void 0 : getDirectusGraphqlUrl();
24
+ const graphqlApiKey = getDirectusApiToken == null ? void 0 : getDirectusApiToken();
25
+ const { redirects, rewrites } = await fetchRedirectsData({ graphqlEndpoint, graphqlApiKey, limit }, init);
26
+ return new Response(JSON.stringify({ redirects, rewrites }), { status: 200 });
27
+ }
28
+ export {
29
+ handleRedirectsRoute as default,
30
+ parseRedirectParams
31
+ };
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const logger = require("../../logger.js");
4
+ const env = require("../env.js");
5
+ const defaultApiRoute = "/api/redirect";
6
+ async function getRedirectsRoute({
7
+ apiRoute = defaultApiRoute,
8
+ getApiRouteUrl = env.getApiRouteUrlDefault,
9
+ getRedirectSecret = env.getRedirectSecretDefault
10
+ } = {}) {
11
+ const secret = getRedirectSecret();
12
+ try {
13
+ const url = new URL(apiRoute, getApiRouteUrl());
14
+ url.searchParams.set("secret", encodeURIComponent(secret));
15
+ const response = await fetch(url);
16
+ if (!response.ok) {
17
+ logger.log(`${apiRoute} not ok. Returned`, { status: response.status }, "error");
18
+ return { redirects: [], rewrites: [] };
19
+ }
20
+ const data = await response.json();
21
+ return data;
22
+ } catch (error) {
23
+ logger.log(`Error fetching redirects from ${apiRoute}.`, { error }, "error");
24
+ return { redirects: [], rewrites: [] };
25
+ }
26
+ }
27
+ exports.getRedirectsRoute = getRedirectsRoute;
@@ -0,0 +1,27 @@
1
+ import { log } from "../../logger.mjs";
2
+ import { getApiRouteUrlDefault, getRedirectSecretDefault } from "../env.mjs";
3
+ const defaultApiRoute = "/api/redirect";
4
+ async function getRedirectsRoute({
5
+ apiRoute = defaultApiRoute,
6
+ getApiRouteUrl = getApiRouteUrlDefault,
7
+ getRedirectSecret = getRedirectSecretDefault
8
+ } = {}) {
9
+ const secret = getRedirectSecret();
10
+ try {
11
+ const url = new URL(apiRoute, getApiRouteUrl());
12
+ url.searchParams.set("secret", encodeURIComponent(secret));
13
+ const response = await fetch(url);
14
+ if (!response.ok) {
15
+ log(`${apiRoute} not ok. Returned`, { status: response.status }, "error");
16
+ return { redirects: [], rewrites: [] };
17
+ }
18
+ const data = await response.json();
19
+ return data;
20
+ } catch (error) {
21
+ log(`Error fetching redirects from ${apiRoute}.`, { error }, "error");
22
+ return { redirects: [], rewrites: [] };
23
+ }
24
+ }
25
+ export {
26
+ getRedirectsRoute
27
+ };
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const coreLib = require("@okam/core-lib");
4
+ const server = require("next/server");
5
+ const radashi = require("radashi");
6
+ const logger = require("../../logger.js");
7
+ const getRedirectsRoute = require("./getRedirectsRoute.js");
8
+ function splitDestination(destination) {
9
+ const [pathname, search] = destination.split("?");
10
+ if (!search) {
11
+ return [pathname];
12
+ }
13
+ return [pathname, `?${search}`];
14
+ }
15
+ function validateExternalRedirect(redirect) {
16
+ if (!URL.canParse(redirect.destination)) {
17
+ return null;
18
+ }
19
+ return redirect.destination;
20
+ }
21
+ async function handleRedirect(request, options = {}) {
22
+ const url = request.nextUrl.clone();
23
+ const { pathname } = request.nextUrl;
24
+ const normalizedPathname = coreLib.normalizePath(pathname);
25
+ const { redirects, rewrites } = await getRedirectsRoute.getRedirectsRoute(options);
26
+ const redirect = redirects.find(({ source }) => source === normalizedPathname);
27
+ const rewrite = rewrites.find(({ source }) => source === normalizedPathname);
28
+ const type = redirect ? "redirect" : "rewrite";
29
+ const reroute = redirect ?? rewrite;
30
+ if (!reroute) {
31
+ return null;
32
+ }
33
+ const { permanent } = reroute;
34
+ const status = permanent ? 308 : 307;
35
+ const externalRedirect = validateExternalRedirect(reroute);
36
+ if (externalRedirect) {
37
+ logger.log(`External ${type} found`, { [type]: externalRedirect, permanent });
38
+ return server.NextResponse.redirect(externalRedirect, { status });
39
+ }
40
+ logger.log(`${radashi.capitalize(type)} found`, { [type]: reroute, permanent });
41
+ const { destination } = reroute;
42
+ const [destinationPathname, search] = splitDestination(destination);
43
+ if (search) url.search = search;
44
+ url.pathname = destinationPathname;
45
+ logger.log(`${radashi.capitalize(type)}ing to ${url.toString()} with status ${status}`);
46
+ return server.NextResponse[type](url, { status });
47
+ }
48
+ exports.handleRedirect = handleRedirect;
@@ -0,0 +1,48 @@
1
+ import { normalizePath } from "@okam/core-lib";
2
+ import { NextResponse } from "next/server";
3
+ import { capitalize } from "radashi";
4
+ import { log } from "../../logger.mjs";
5
+ import { getRedirectsRoute } from "./getRedirectsRoute.mjs";
6
+ function splitDestination(destination) {
7
+ const [pathname, search] = destination.split("?");
8
+ if (!search) {
9
+ return [pathname];
10
+ }
11
+ return [pathname, `?${search}`];
12
+ }
13
+ function validateExternalRedirect(redirect) {
14
+ if (!URL.canParse(redirect.destination)) {
15
+ return null;
16
+ }
17
+ return redirect.destination;
18
+ }
19
+ async function handleRedirect(request, options = {}) {
20
+ const url = request.nextUrl.clone();
21
+ const { pathname } = request.nextUrl;
22
+ const normalizedPathname = normalizePath(pathname);
23
+ const { redirects, rewrites } = await getRedirectsRoute(options);
24
+ const redirect = redirects.find(({ source }) => source === normalizedPathname);
25
+ const rewrite = rewrites.find(({ source }) => source === normalizedPathname);
26
+ const type = redirect ? "redirect" : "rewrite";
27
+ const reroute = redirect ?? rewrite;
28
+ if (!reroute) {
29
+ return null;
30
+ }
31
+ const { permanent } = reroute;
32
+ const status = permanent ? 308 : 307;
33
+ const externalRedirect = validateExternalRedirect(reroute);
34
+ if (externalRedirect) {
35
+ log(`External ${type} found`, { [type]: externalRedirect, permanent });
36
+ return NextResponse.redirect(externalRedirect, { status });
37
+ }
38
+ log(`${capitalize(type)} found`, { [type]: reroute, permanent });
39
+ const { destination } = reroute;
40
+ const [destinationPathname, search] = splitDestination(destination);
41
+ if (search) url.search = search;
42
+ url.pathname = destinationPathname;
43
+ log(`${capitalize(type)}ing to ${url.toString()} with status ${status}`);
44
+ return NextResponse[type](url, { status });
45
+ }
46
+ export {
47
+ handleRedirect
48
+ };
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const logger = require("@okam/logger");
4
+ const validateRedirects = require("./utils/validateRedirects.js");
5
+ const redirectDefaultLimit = 2e3;
6
+ function getDefaultConfig() {
7
+ return {
8
+ graphqlEndpoint: process.env["NEXT_REDIRECT_GRAPHQL_URL"] || process.env["NEXT_PUBLIC_GRAPHQL_URL"] || "",
9
+ graphqlApiKey: process.env["NEXT_API_TOKEN_ADMIN"] || "",
10
+ redirectsFilename: "./redirect/redirects.json",
11
+ rewritesFilename: "./redirect/rewrites.json",
12
+ limit: redirectDefaultLimit
13
+ };
14
+ }
15
+ async function fetchRedirectsData(config, init) {
16
+ const {
17
+ graphqlApiKey: defaultGraphqlApiKey,
18
+ graphqlEndpoint: defaultGraphqlEndpoint,
19
+ limit: defaultLimit
20
+ } = getDefaultConfig();
21
+ const {
22
+ graphqlEndpoint = defaultGraphqlEndpoint,
23
+ graphqlApiKey = defaultGraphqlApiKey,
24
+ limit = defaultLimit
25
+ } = config;
26
+ if (!graphqlEndpoint) {
27
+ throw new Error(
28
+ "Missing fetchRedirects configuration `graphqlEndpoint`. Check environment variables NEXT_REDIRECT_GRAPHQL_URL or NEXT_PUBLIC_GRAPHQL_URL"
29
+ );
30
+ }
31
+ if (!graphqlApiKey) {
32
+ throw new Error(
33
+ "Missing fetchRedirects configuration `graphqlApiKey`. Check environment variable NEXT_API_TOKEN_ADMIN"
34
+ );
35
+ }
36
+ const query = `query fetchRedirects($limit: Int = 2000) {
37
+ redirects(filter: {status:{_eq:"published"},isrewrite:{_eq:false}}, sort: "sort", limit: $limit) {
38
+ source
39
+ destination
40
+ permanent
41
+ locale
42
+ }
43
+ rewrites: redirects(filter: {status:{_eq:"published"},isrewrite:{_eq:true}}, sort: "sort", limit: $limit) {
44
+ source
45
+ destination
46
+ permanent
47
+ locale
48
+ }
49
+ }`;
50
+ const graphqlBody = {
51
+ query,
52
+ // "operationName": "",
53
+ variables: {
54
+ limit: Number(limit) || redirectDefaultLimit
55
+ }
56
+ };
57
+ try {
58
+ const response = await fetch(graphqlEndpoint, {
59
+ ...init,
60
+ method: "POST",
61
+ headers: {
62
+ // eslint-disable-next-line @typescript-eslint/naming-convention
63
+ "Content-Type": "application/json",
64
+ Authorization: `Bearer ${graphqlApiKey}`
65
+ },
66
+ body: JSON.stringify(graphqlBody)
67
+ });
68
+ const { data } = await response.json();
69
+ const { redirects, rewrites } = data ?? {};
70
+ if (!(redirects == null ? void 0 : redirects.length) && !(rewrites == null ? void 0 : rewrites.length)) {
71
+ logger.logger.log("No redirects/rewrites found", "warn");
72
+ return {
73
+ redirects: [],
74
+ rewrites: []
75
+ };
76
+ }
77
+ logger.logger.log(`Fetch redirects count: ${(redirects == null ? void 0 : redirects.length) || 0}, rewrites count: ${(rewrites == null ? void 0 : rewrites.length) || 0}`);
78
+ return {
79
+ redirects: validateRedirects.normalizeRedirects(redirects),
80
+ rewrites: validateRedirects.normalizeRedirects(rewrites)
81
+ };
82
+ } catch (e) {
83
+ logger.logger.log(`Error fetching redirects: ${e.message}`, "error");
84
+ }
85
+ return {
86
+ redirects: [],
87
+ rewrites: []
88
+ };
89
+ }
90
+ exports.fetchRedirectsData = fetchRedirectsData;
91
+ exports.getDefaultConfig = getDefaultConfig;
92
+ exports.redirectDefaultLimit = redirectDefaultLimit;
@@ -0,0 +1,92 @@
1
+ import { logger } from "@okam/logger";
2
+ import { normalizeRedirects } from "./utils/validateRedirects.mjs";
3
+ const redirectDefaultLimit = 2e3;
4
+ function getDefaultConfig() {
5
+ return {
6
+ graphqlEndpoint: process.env["NEXT_REDIRECT_GRAPHQL_URL"] || process.env["NEXT_PUBLIC_GRAPHQL_URL"] || "",
7
+ graphqlApiKey: process.env["NEXT_API_TOKEN_ADMIN"] || "",
8
+ redirectsFilename: "./redirect/redirects.json",
9
+ rewritesFilename: "./redirect/rewrites.json",
10
+ limit: redirectDefaultLimit
11
+ };
12
+ }
13
+ async function fetchRedirectsData(config, init) {
14
+ const {
15
+ graphqlApiKey: defaultGraphqlApiKey,
16
+ graphqlEndpoint: defaultGraphqlEndpoint,
17
+ limit: defaultLimit
18
+ } = getDefaultConfig();
19
+ const {
20
+ graphqlEndpoint = defaultGraphqlEndpoint,
21
+ graphqlApiKey = defaultGraphqlApiKey,
22
+ limit = defaultLimit
23
+ } = config;
24
+ if (!graphqlEndpoint) {
25
+ throw new Error(
26
+ "Missing fetchRedirects configuration `graphqlEndpoint`. Check environment variables NEXT_REDIRECT_GRAPHQL_URL or NEXT_PUBLIC_GRAPHQL_URL"
27
+ );
28
+ }
29
+ if (!graphqlApiKey) {
30
+ throw new Error(
31
+ "Missing fetchRedirects configuration `graphqlApiKey`. Check environment variable NEXT_API_TOKEN_ADMIN"
32
+ );
33
+ }
34
+ const query = `query fetchRedirects($limit: Int = 2000) {
35
+ redirects(filter: {status:{_eq:"published"},isrewrite:{_eq:false}}, sort: "sort", limit: $limit) {
36
+ source
37
+ destination
38
+ permanent
39
+ locale
40
+ }
41
+ rewrites: redirects(filter: {status:{_eq:"published"},isrewrite:{_eq:true}}, sort: "sort", limit: $limit) {
42
+ source
43
+ destination
44
+ permanent
45
+ locale
46
+ }
47
+ }`;
48
+ const graphqlBody = {
49
+ query,
50
+ // "operationName": "",
51
+ variables: {
52
+ limit: Number(limit) || redirectDefaultLimit
53
+ }
54
+ };
55
+ try {
56
+ const response = await fetch(graphqlEndpoint, {
57
+ ...init,
58
+ method: "POST",
59
+ headers: {
60
+ // eslint-disable-next-line @typescript-eslint/naming-convention
61
+ "Content-Type": "application/json",
62
+ Authorization: `Bearer ${graphqlApiKey}`
63
+ },
64
+ body: JSON.stringify(graphqlBody)
65
+ });
66
+ const { data } = await response.json();
67
+ const { redirects, rewrites } = data ?? {};
68
+ if (!(redirects == null ? void 0 : redirects.length) && !(rewrites == null ? void 0 : rewrites.length)) {
69
+ logger.log("No redirects/rewrites found", "warn");
70
+ return {
71
+ redirects: [],
72
+ rewrites: []
73
+ };
74
+ }
75
+ logger.log(`Fetch redirects count: ${(redirects == null ? void 0 : redirects.length) || 0}, rewrites count: ${(rewrites == null ? void 0 : rewrites.length) || 0}`);
76
+ return {
77
+ redirects: normalizeRedirects(redirects),
78
+ rewrites: normalizeRedirects(rewrites)
79
+ };
80
+ } catch (e) {
81
+ logger.log(`Error fetching redirects: ${e.message}`, "error");
82
+ }
83
+ return {
84
+ redirects: [],
85
+ rewrites: []
86
+ };
87
+ }
88
+ export {
89
+ fetchRedirectsData,
90
+ getDefaultConfig,
91
+ redirectDefaultLimit
92
+ };
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const coreLib = require("@okam/core-lib");
4
+ function isRedirect(redirect) {
5
+ return !!redirect && typeof redirect === "object" && "source" in redirect && "destination" in redirect;
6
+ }
7
+ function normalizeRedirects(redirects) {
8
+ if (!redirects || !Array.isArray(redirects)) return [];
9
+ return redirects.flatMap((redirect) => {
10
+ const { source, destination, ...rest } = redirect ?? {};
11
+ if (!redirect || !source || !destination || !isRedirect(redirect)) return [];
12
+ return [
13
+ {
14
+ ...rest,
15
+ source: coreLib.normalizePath(source),
16
+ destination: coreLib.normalizePath(destination)
17
+ }
18
+ ];
19
+ });
20
+ }
21
+ exports.isRedirect = isRedirect;
22
+ exports.normalizeRedirects = normalizeRedirects;
@@ -0,0 +1,22 @@
1
+ import { normalizePath } from "@okam/core-lib";
2
+ function isRedirect(redirect) {
3
+ return !!redirect && typeof redirect === "object" && "source" in redirect && "destination" in redirect;
4
+ }
5
+ function normalizeRedirects(redirects) {
6
+ if (!redirects || !Array.isArray(redirects)) return [];
7
+ return redirects.flatMap((redirect) => {
8
+ const { source, destination, ...rest } = redirect ?? {};
9
+ if (!redirect || !source || !destination || !isRedirect(redirect)) return [];
10
+ return [
11
+ {
12
+ ...rest,
13
+ source: normalizePath(source),
14
+ destination: normalizePath(destination)
15
+ }
16
+ ];
17
+ });
18
+ }
19
+ export {
20
+ isRedirect,
21
+ normalizeRedirects
22
+ };
package/draft/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { default as handleDraftRoute, getPathFromRoute, parseParams } from './route';
1
+ export { default as handleDraftRoute, getPathFromRoute, parseDraftParams } from './route';
2
2
  export { getDraftSecretDefault } from './env';
3
3
  export type { HandleDraftOptions } from './route';
package/draft/route.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare function parseParams(url: string): {
1
+ export declare function parseDraftParams(url: string): {
2
2
  secret: string;
3
3
  languages: any;
4
4
  paths: never[];
package/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export * from './draft';
2
+ export * from './redirect';
2
3
  export { logger as DirectusNextLogger } from './logger';
3
4
  export { directusRouteRouter } from './lib/directusRouteRouter';
4
5
  export { getJsonErrorResponse } from './response';
6
+ export * from './types';
5
7
  export type { TPageSettings, TPageSettingsTranslation, TPageSettingsItemQuery, TPageSettingsItemDocument, TUsePageSettingsProps, } from './pageSettings/interface';
6
8
  export type { TFiles } from './files/interface';
@@ -1,15 +1,10 @@
1
+ import type { NextRequest, NextResponse as NextResponseType } from 'next/server';
1
2
  import type { DirectusRouteConfig } from '../types/directusRouteConfig';
2
- interface MinimalNextUrl {
3
- pathname: string;
4
- clone: () => MinimalNextUrl;
5
- }
6
- interface MinimalNextRequest {
7
- nextUrl: MinimalNextUrl;
8
- }
9
- interface MinimalNextResponse {
10
- next: () => MinimalNextResponse;
11
- notFound: () => MinimalNextResponse;
12
- rewrite: (url: MinimalNextUrl | URL) => MinimalNextResponse;
13
- }
14
- export declare function directusRouteRouter(request: MinimalNextRequest, config: DirectusRouteConfig, NextResponse: MinimalNextResponse): Promise<MinimalNextResponse>;
15
- export {};
3
+ /**
4
+ * Handles routing for Directus.
5
+ *
6
+ * @param {NextRequest} request - The incoming request object.
7
+ * @param {DirectusRouteConfig} config - Configuration for routing.
8
+ * @returns {Promise<NextResponse>} The response object.
9
+ */
10
+ export declare function directusRouteRouter(request: NextRequest, config: DirectusRouteConfig): Promise<NextResponseType>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@okam/directus-next",
3
3
  "main": "./index.js",
4
- "version": "1.1.1",
4
+ "version": "1.2.1",
5
5
  "types": "./index.d.ts",
6
6
  "exports": {
7
7
  ".": {
@@ -32,6 +32,8 @@
32
32
  "access": "public"
33
33
  },
34
34
  "dependencies": {
35
+ "@okam/core-lib": "1.16.0",
36
+ "@okam/directus-node": "0.6.0",
35
37
  "@okam/logger": "1.1.0",
36
38
  "next": "^14.1.1",
37
39
  "radashi": "^12.3.0",
@@ -0,0 +1,2 @@
1
+ export declare function getRedirectSecretDefault(): string;
2
+ export declare function getApiRouteUrlDefault(): string;
@@ -0,0 +1,5 @@
1
+ export * from './env';
2
+ export { default as handleRedirectsRoute, parseRedirectParams } from './route';
3
+ export type { HandleRedirectOptions } from './interface';
4
+ export { getRedirectsRoute } from './utils/getRedirectsRoute';
5
+ export { handleRedirect } from './utils/handleRedirect';
@@ -0,0 +1,25 @@
1
+ export interface HandleRedirectOptions {
2
+ url: string;
3
+ /**
4
+ * @default process.env.NEXT_API_REDIRECT_SECRET
5
+ */
6
+ getRedirectSecret?: () => string;
7
+ /**
8
+ * @default process.env.NEXT_REDIRECT_GRAPHQL_URL || process.env.NEXT_PUBLIC_GRAPHQL_URL
9
+ */
10
+ getDirectusGraphqlUrl?: () => string;
11
+ /**
12
+ * @default process.env.NEXT_API_TOKEN_ADMIN
13
+ */
14
+ getDirectusApiToken?: () => string;
15
+ getJsonError?: (data: any, status: number) => Response;
16
+ /**
17
+ * @default 2000
18
+ */
19
+ limit?: number;
20
+ /**
21
+ * Gets spread into the second argument of fetch.
22
+ * Allows passing options like `cache` or `next.revalidate`
23
+ */
24
+ init?: Omit<RequestInit, 'body' | 'method' | 'headers'>;
25
+ }
@@ -0,0 +1,8 @@
1
+ import type { HandleRedirectOptions } from './interface';
2
+ export declare function parseRedirectParams(url: string): {
3
+ secret: string;
4
+ };
5
+ /**
6
+ * Wrapper for `fetchRedirectsData` with secret validation
7
+ */
8
+ export default function handleRedirectsRoute({ url, getRedirectSecret, getJsonError, getDirectusApiToken, getDirectusGraphqlUrl, limit, init, }: HandleRedirectOptions): Promise<Response | undefined>;
@@ -0,0 +1,8 @@
1
+ import type { TFetchRedirectsResponse } from '@okam/directus-node/edge';
2
+ import type { DirectusRouteRedirectsModule } from '../../types/directusRouteConfig';
3
+ /**
4
+ * Gets a response from `options.apiRoute`
5
+ * @param {DirectusRouteRedirectsModule} options
6
+ * @returns {Promise<TFetchRedirectsResponse>}
7
+ */
8
+ export declare function getRedirectsRoute({ apiRoute, getApiRouteUrl, getRedirectSecret, }?: DirectusRouteRedirectsModule): Promise<TFetchRedirectsResponse>;
@@ -0,0 +1,7 @@
1
+ import type { NextRequest } from 'next/server';
2
+ import { NextResponse } from 'next/server';
3
+ import type { DirectusRouteRedirectsModule } from '../../types/directusRouteConfig';
4
+ /**
5
+ * Handles next redirection using directus redirects
6
+ */
7
+ export declare function handleRedirect(request: NextRequest, options?: DirectusRouteRedirectsModule): Promise<NextResponse<unknown> | null>;
@@ -1,3 +1,17 @@
1
+ export interface DirectusRouteRedirectsModule {
2
+ /**
3
+ * @default process.env.NEXT_API_REDIRECT_SECRET
4
+ */
5
+ getRedirectSecret?: () => string;
6
+ /**
7
+ * @default process.env.NEXT_MIDDLEWARE_REDIRECT_URL ?? `https://${process.env.VERCEL_URL}` ?? 'http://localhost:3000'
8
+ */
9
+ getApiRouteUrl?: () => string;
10
+ /**
11
+ * @default /api/redirects
12
+ */
13
+ apiRoute?: string;
14
+ }
1
15
  export interface DirectusRouteConfig {
2
16
  localeMap?: Record<string, string>;
3
17
  collectionSettings: {
@@ -9,4 +23,7 @@ export interface DirectusRouteConfig {
9
23
  idField: string;
10
24
  };
11
25
  };
26
+ modules?: {
27
+ redirects?: DirectusRouteRedirectsModule;
28
+ };
12
29
  }
@@ -0,0 +1,2 @@
1
+ export type { DirectusRouteConfig, DirectusRouteRedirectsModule } from './directusRouteConfig';
2
+ export type { PageSettingsTranslation } from './pageSettings';
@@ -0,0 +1,12 @@
1
+ export interface MinimalNextUrl extends URL {
2
+ clone: () => MinimalNextUrl;
3
+ }
4
+ export interface MinimalNextRequest {
5
+ nextUrl: MinimalNextUrl;
6
+ }
7
+ export interface MinimalNextResponse {
8
+ next: () => MinimalNextResponse;
9
+ notFound: () => MinimalNextResponse;
10
+ rewrite: (url: MinimalNextUrl | URL | string, options?: ResponseInit) => MinimalNextResponse;
11
+ redirect: (url: MinimalNextUrl | URL | string, options?: ResponseInit) => MinimalNextResponse;
12
+ }
@@ -0,0 +1,13 @@
1
+ export interface PageSettingsTranslation {
2
+ languages_code: {
3
+ code: string;
4
+ };
5
+ id: string;
6
+ page_settings_id: {
7
+ belongs_to_collection: string;
8
+ belongs_to_key: string;
9
+ };
10
+ title: string;
11
+ slug: string;
12
+ path: string;
13
+ }
package/index.mjs DELETED
@@ -1,14 +0,0 @@
1
- import { getPathFromRoute, default as default2, parseParams } from "./draft/route.mjs";
2
- import { getDraftSecretDefault } from "./draft/env.mjs";
3
- import { logger } from "./logger.mjs";
4
- import { directusRouteRouter } from "./lib/directusRouteRouter.mjs";
5
- import { getJsonErrorResponse } from "./response.mjs";
6
- export {
7
- logger as DirectusNextLogger,
8
- directusRouteRouter,
9
- getDraftSecretDefault,
10
- getJsonErrorResponse,
11
- getPathFromRoute,
12
- default2 as handleDraftRoute,
13
- parseParams
14
- };
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes