@uniformdev/canvas-next-rsc 19.30.1-alpha.5 → 19.34.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.
@@ -6,7 +6,7 @@ import { getManifestFromApi } from '../client/manifestClient';
6
6
  import { isDraftModeEnabled, isOnVercelPreviewEnvironment } from '../utils/draft';
7
7
  import { evaluateComposition } from './evaluateComposition';
8
8
  import { resolvePath } from './resolvePath';
9
- import { resolveRoute } from './resolveRoute';
9
+ import { resolveRedirectHref, retrieveRoute } from './retrieveRoute';
10
10
  import { UniformComponent } from './UniformComponent';
11
11
  import { UniformProvider } from './UniformContext';
12
12
  import { UniformScript } from './UniformScript';
@@ -31,14 +31,15 @@ const UniformCompositionInner = async ({ params, searchParams, children }) => {
31
31
  params,
32
32
  });
33
33
  // resolve the route from the path
34
- const resolveResult = await resolveRoute({
34
+ const resolveResult = await retrieveRoute({
35
35
  path,
36
36
  state: draftMode || previewEnvironment ? CANVAS_DRAFT_STATE : CANVAS_PUBLISHED_STATE,
37
37
  searchParams,
38
38
  });
39
39
  // if the route is a redirect, redirect
40
40
  if (resolveResult.type === 'redirect') {
41
- redirect(resolveResult.redirect.href);
41
+ const href = resolveRedirectHref(resolveResult, path);
42
+ redirect(href);
42
43
  }
43
44
  // if the route is not found, return 404
44
45
  if (resolveResult.type === 'notFound') {
@@ -46,7 +47,7 @@ const UniformCompositionInner = async ({ params, searchParams, children }) => {
46
47
  }
47
48
  // evaluate the composition
48
49
  const { cookieValue, composition: evaluatedComposition, seenComponents, } = await evaluateComposition({
49
- root: resolveResult.composition,
50
+ root: resolveResult.compositionApiResponse.composition,
50
51
  params,
51
52
  searchParams: searchParams !== null && searchParams !== void 0 ? searchParams : {},
52
53
  });
@@ -0,0 +1,10 @@
1
+ import { ResolvedRouteGetResponse, RouteGetResponseRedirect } from '@uniformdev/canvas';
2
+ export type ResolveRouteOptions = {
3
+ path: string;
4
+ state?: number;
5
+ searchParams: {
6
+ [key: string]: string | undefined;
7
+ } | undefined;
8
+ };
9
+ export declare const retrieveRoute: (data: ResolveRouteOptions) => Promise<ResolvedRouteGetResponse>;
10
+ export declare const resolveRedirectHref: (resolveResult: RouteGetResponseRedirect, path: string) => string;
@@ -0,0 +1,70 @@
1
+ import { CANVAS_DRAFT_STATE } from '@uniformdev/canvas';
2
+ import { RedirectClient } from '@uniformdev/redirect';
3
+ import { get } from '@vercel/edge-config';
4
+ import { getRouteClient } from '../client/routeClient';
5
+ import { getRouteRevalidateInterval } from '../config/helpers';
6
+ import config from '../config/uniform.server.config';
7
+ import { isOnVercelPreviewEnvironment } from '../utils/draft';
8
+ import { buildPathTag } from '../utils/tag';
9
+ import { getBaseUrl } from '../utils/url';
10
+ export const retrieveRoute = async (data) => {
11
+ void resolveRouteByEdgeConfig(data);
12
+ void resolveRouteByRouteApi(data);
13
+ const edgeConfigValue = await resolveRouteByEdgeConfig(data);
14
+ let result;
15
+ if (edgeConfigValue) {
16
+ result = edgeConfigValue;
17
+ }
18
+ else {
19
+ const routeApiValue = await resolveRouteByRouteApi(data);
20
+ result = routeApiValue;
21
+ }
22
+ return result;
23
+ };
24
+ export const resolveRedirectHref = (resolveResult, path) => {
25
+ let href;
26
+ if (resolveResult.redirect.targetProjectMapNodeId) {
27
+ const requestUrl = `${getBaseUrl()}${path}`;
28
+ const expandedUrl = RedirectClient.getTargetVariableExpandedUrl(requestUrl, resolveResult.redirect);
29
+ const url = new URL(expandedUrl);
30
+ href = url.pathname;
31
+ }
32
+ else {
33
+ href = resolveResult.redirect.targetUrl;
34
+ }
35
+ return href;
36
+ };
37
+ const resolveRouteByEdgeConfig = async (data) => {
38
+ var _a;
39
+ if (!((_a = config.experimental) === null || _a === void 0 ? void 0 : _a.edgeRedirects) || !process.env.EDGE_CONFIG) {
40
+ return undefined;
41
+ }
42
+ const sourcePathKey = buildPathTag(data.path);
43
+ const key = sourcePathKey.replace(/\W+/g, '');
44
+ let edgeConfig;
45
+ try {
46
+ edgeConfig = await get(key);
47
+ }
48
+ catch (e) {
49
+ // eslint-disable-next-line no-console
50
+ console.warn('Failed to retrieve edge config', e);
51
+ }
52
+ if (!edgeConfig) {
53
+ return undefined;
54
+ }
55
+ return edgeConfig;
56
+ };
57
+ const resolveRouteByRouteApi = async (data) => {
58
+ const routeClient = getRouteClient({
59
+ revalidate: getRouteRevalidateInterval({
60
+ searchParams: data.searchParams,
61
+ }),
62
+ });
63
+ const routeResult = await routeClient.getRoute({
64
+ path: data.path,
65
+ state: data.state,
66
+ withComponentIDs: data.state === CANVAS_DRAFT_STATE,
67
+ withContentSourceMap: isOnVercelPreviewEnvironment(),
68
+ });
69
+ return routeResult;
70
+ };
@@ -23,4 +23,21 @@ export type UniformServerConfig = {
23
23
  * @default 10
24
24
  */
25
25
  canvasRevalidateInterval?: number;
26
+ /**
27
+ * 😅
28
+ */
29
+ experimental?: {
30
+ /**
31
+ * Enables edge cache of redirects.
32
+ *
33
+ * @default false
34
+ */
35
+ edgeRedirects?: boolean;
36
+ /**
37
+ * Enables edge cache of compositions.
38
+ *
39
+ * @default false
40
+ */
41
+ edgeCompositions?: boolean;
42
+ };
26
43
  };
@@ -1,7 +1,7 @@
1
1
  import { CANVAS_DRAFT_STATE, IN_CONTEXT_EDITOR_QUERY_STRING_PARAM } from '@uniformdev/canvas';
2
2
  import { draftMode } from 'next/headers';
3
3
  import { redirect } from 'next/navigation';
4
- import { resolveRoute } from '../components/resolveRoute';
4
+ import { resolveRedirectHref, retrieveRoute } from '../components/retrieveRoute';
5
5
  export const createPreviewGETRouteHandler = () => {
6
6
  return async (request) => {
7
7
  if (!process.env.UNIFORM_PREVIEW_SECRET) {
@@ -16,7 +16,7 @@ export const createPreviewGETRouteHandler = () => {
16
16
  if (!path) {
17
17
  return new Response('Path not provided', { status: 401 });
18
18
  }
19
- const resolveResult = await resolveRoute({
19
+ const resolveResult = await retrieveRoute({
20
20
  path,
21
21
  state: CANVAS_DRAFT_STATE,
22
22
  searchParams: {
@@ -26,13 +26,13 @@ export const createPreviewGETRouteHandler = () => {
26
26
  });
27
27
  draftMode().enable();
28
28
  if (resolveResult.type === 'redirect') {
29
- const { redirect: theRedirect } = resolveResult;
30
- if (theRedirect.nodeId) {
29
+ const href = resolveRedirectHref(resolveResult, path);
30
+ if (resolveResult.redirect.targetProjectMapNodeId) {
31
31
  // do we need this query string param still with draft mode cookie?
32
- redirect(`${theRedirect.href}?${IN_CONTEXT_EDITOR_QUERY_STRING_PARAM}=true`);
32
+ redirect(`${href}?${IN_CONTEXT_EDITOR_QUERY_STRING_PARAM}=true`);
33
33
  }
34
34
  else {
35
- redirect(theRedirect.href);
35
+ redirect(href);
36
36
  }
37
37
  }
38
38
  if (resolveResult.type === 'notFound') {
@@ -5,9 +5,13 @@ export declare const isSvixMessage: (request: Request) => Promise<{
5
5
  export declare const processCompositionChange: (compositionId: string) => Promise<{
6
6
  tags: string[];
7
7
  }>;
8
+ export declare const getPathName: (url: string) => string;
8
9
  export declare const processRedirectChange: (sourceUrl: string) => Promise<{
9
10
  tags: string[];
10
11
  }>;
12
+ export declare const processEdgeConfigChange: ({ source_url }: {
13
+ source_url: string;
14
+ }) => Promise<void>;
11
15
  export type MessageHandlerResponse = {
12
16
  tags: string[];
13
17
  };
@@ -1,5 +1,8 @@
1
+ import { parseConnectionString } from '@vercel/edge-config';
1
2
  import { Webhook } from 'svix';
2
3
  import { getProjectMapClient } from '../client/projectMapClient';
4
+ import { getRouteClient } from '../client/routeClient';
5
+ import config from '../config/uniform.server.config';
3
6
  import { buildPathTag } from '../utils/tag';
4
7
  export const isSvixMessage = async (request) => {
5
8
  const requiredHeaders = ['svix-id', 'svix-timestamp', 'svix-signature'];
@@ -38,23 +41,85 @@ export const processCompositionChange = async (compositionId) => {
38
41
  compositionId,
39
42
  });
40
43
  const tags = [];
41
- nodes === null || nodes === void 0 ? void 0 : nodes.forEach((node) => {
42
- tags.push(buildPathTag(node.path));
43
- });
44
+ if (nodes) {
45
+ for (let i = 0; i < nodes.length; i++) {
46
+ const node = nodes[i];
47
+ await processEdgeConfigChange({
48
+ source_url: node.path,
49
+ });
50
+ tags.push(buildPathTag(node.path));
51
+ }
52
+ }
44
53
  return {
45
54
  tags,
46
55
  };
47
56
  };
48
- export const processRedirectChange = async (sourceUrl) => {
49
- const tags = [];
57
+ export const getPathName = (url) => {
50
58
  try {
51
- const url = new URL(sourceUrl);
52
- tags.push(buildPathTag(url.pathname));
59
+ const { pathname } = new URL(url);
60
+ return pathname;
53
61
  }
54
- catch (e) {
55
- tags.push(buildPathTag(sourceUrl));
62
+ catch (_a) {
63
+ return url;
56
64
  }
65
+ };
66
+ export const processRedirectChange = async (sourceUrl) => {
67
+ const tags = [];
68
+ const pathName = getPathName(sourceUrl);
69
+ tags.push(buildPathTag(pathName));
57
70
  return {
58
71
  tags,
59
72
  };
60
73
  };
74
+ export const processEdgeConfigChange = async ({ source_url }) => {
75
+ var _a, _b;
76
+ if (!process.env.UNIFORM_VERCEL_EDGE_CONFIG_TOKEN) {
77
+ // eslint-disable-next-line no-console
78
+ console.warn('UNIFORM_VERCEL_EDGE_CONFIG_TOKEN is not set, skipping edge redirect upsert');
79
+ return;
80
+ }
81
+ const routeClient = getRouteClient({
82
+ revalidate: -1,
83
+ });
84
+ const route = await routeClient.getRoute({
85
+ path: source_url,
86
+ });
87
+ let valueToStore = undefined;
88
+ const shouldSaveRedirect = route.type === 'redirect' && ((_a = config.experimental) === null || _a === void 0 ? void 0 : _a.edgeRedirects);
89
+ const shouldSaveComposition = route.type === 'composition' && ((_b = config.experimental) === null || _b === void 0 ? void 0 : _b.edgeCompositions);
90
+ if (shouldSaveRedirect || shouldSaveComposition) {
91
+ valueToStore = route;
92
+ }
93
+ const sourcePath = getPathName(source_url);
94
+ const soucrcePathKey = buildPathTag(sourcePath);
95
+ const { id } = parseConnectionString(process.env.EDGE_CONFIG) || {};
96
+ if (!id) {
97
+ // eslint-disable-next-line no-console
98
+ console.warn('EDGE_CONFIG is not set or not a connection string, skipping edge redirect upsert');
99
+ return;
100
+ }
101
+ const url = new URL(`https://api.vercel.com/v1/edge-config/${id}/items`);
102
+ if (process.env.UNIFORM_VERCEL_EDGE_CONFIG_TEAM_ID) {
103
+ url.searchParams.set('teamId', process.env.UNIFORM_VERCEL_EDGE_CONFIG_TEAM_ID);
104
+ }
105
+ const response = await fetch(url.toString(), {
106
+ method: 'PATCH',
107
+ headers: {
108
+ 'Content-Type': 'application/json',
109
+ Authorization: `Bearer ${process.env.UNIFORM_VERCEL_EDGE_CONFIG_TOKEN}`,
110
+ },
111
+ body: JSON.stringify({
112
+ items: [
113
+ {
114
+ operation: valueToStore ? 'upsert' : 'delete',
115
+ key: soucrcePathKey.replace(/\W+/g, ''),
116
+ value: valueToStore !== null && valueToStore !== void 0 ? valueToStore : null,
117
+ },
118
+ ],
119
+ }),
120
+ });
121
+ if (!response.ok) {
122
+ // eslint-disable-next-line no-console
123
+ console.warn(`Failed to upsert edge redirect for ${source_url}`, await response.text());
124
+ }
125
+ };
@@ -1,12 +1,20 @@
1
1
  import { ProjectMapNodeDeleteDefinition } from '@uniformdev/webhooks';
2
+ import config from '../../config/uniform.server.config';
2
3
  import { buildPathTag } from '../../utils/tag';
4
+ import { processEdgeConfigChange } from '../helpers';
3
5
  export const handleProjectMapNodeDelete = async (body) => {
6
+ var _a;
4
7
  const parsed = ProjectMapNodeDeleteDefinition.schema.safeParse(body);
5
8
  if (!parsed.success) {
6
9
  return undefined;
7
10
  }
8
11
  const tags = [];
9
12
  tags.push(buildPathTag(parsed.data.path));
13
+ if ((_a = config.experimental) === null || _a === void 0 ? void 0 : _a.edgeCompositions) {
14
+ await processEdgeConfigChange({
15
+ source_url: parsed.data.path,
16
+ });
17
+ }
10
18
  return {
11
19
  tags,
12
20
  };
@@ -1,12 +1,20 @@
1
1
  import { ProjectMapNodeInsertDefinition } from '@uniformdev/webhooks';
2
+ import config from '../../config/uniform.server.config';
2
3
  import { buildPathTag } from '../../utils/tag';
4
+ import { processEdgeConfigChange } from '../helpers';
3
5
  export const handleProjectMapNodeInsert = async (body) => {
6
+ var _a;
4
7
  const parsed = ProjectMapNodeInsertDefinition.schema.safeParse(body);
5
8
  if (!parsed.success) {
6
9
  return undefined;
7
10
  }
8
11
  const tags = [];
9
12
  tags.push(buildPathTag(parsed.data.path));
13
+ if ((_a = config.experimental) === null || _a === void 0 ? void 0 : _a.edgeCompositions) {
14
+ await processEdgeConfigChange({
15
+ source_url: parsed.data.path,
16
+ });
17
+ }
10
18
  return {
11
19
  tags,
12
20
  };
@@ -1,6 +1,9 @@
1
1
  import { ProjectMapNodeUpdateDefinition } from '@uniformdev/webhooks';
2
+ import config from '../../config/uniform.server.config';
2
3
  import { buildPathTag } from '../../utils/tag';
4
+ import { processEdgeConfigChange } from '../helpers';
3
5
  export const handleProjectMapNodeUpdate = async (body) => {
6
+ var _a;
4
7
  const parsed = ProjectMapNodeUpdateDefinition.schema.safeParse(body);
5
8
  if (!parsed.success) {
6
9
  return undefined;
@@ -8,6 +11,11 @@ export const handleProjectMapNodeUpdate = async (body) => {
8
11
  const tags = [];
9
12
  tags.push(buildPathTag(parsed.data.path));
10
13
  tags.push(buildPathTag(parsed.data.previous_path));
14
+ if ((_a = config.experimental) === null || _a === void 0 ? void 0 : _a.edgeCompositions) {
15
+ await processEdgeConfigChange({
16
+ source_url: parsed.data.path,
17
+ });
18
+ }
11
19
  return {
12
20
  tags,
13
21
  };
@@ -1,9 +1,14 @@
1
1
  import { RedirectDeleteDefinition } from '@uniformdev/webhooks';
2
- import { processRedirectChange } from '../helpers';
2
+ import config from '../../config/uniform.server.config';
3
+ import { processEdgeConfigChange, processRedirectChange } from '../helpers';
3
4
  export const handleRedirectDelete = async (body) => {
5
+ var _a;
4
6
  const parsed = RedirectDeleteDefinition.schema.safeParse(body);
5
7
  if (!parsed.success) {
6
8
  return undefined;
7
9
  }
10
+ if ((_a = config.experimental) === null || _a === void 0 ? void 0 : _a.edgeRedirects) {
11
+ await processEdgeConfigChange(parsed.data);
12
+ }
8
13
  return processRedirectChange(parsed.data.source_url);
9
14
  };
@@ -1,9 +1,14 @@
1
1
  import { RedirectInsertDefinition } from '@uniformdev/webhooks';
2
- import { processRedirectChange } from '../helpers';
2
+ import config from '../../config/uniform.server.config';
3
+ import { processEdgeConfigChange, processRedirectChange } from '../helpers';
3
4
  export const handleRedirectInsert = async (body) => {
5
+ var _a;
4
6
  const parsed = RedirectInsertDefinition.schema.safeParse(body);
5
7
  if (!parsed.success) {
6
8
  return undefined;
7
9
  }
10
+ if ((_a = config.experimental) === null || _a === void 0 ? void 0 : _a.edgeRedirects) {
11
+ await processEdgeConfigChange(parsed.data);
12
+ }
8
13
  return processRedirectChange(parsed.data.source_url);
9
14
  };
@@ -1,9 +1,14 @@
1
1
  import { RedirectUpdateDefinition } from '@uniformdev/webhooks';
2
- import { processRedirectChange } from '../helpers';
2
+ import config from '../../config/uniform.server.config';
3
+ import { processEdgeConfigChange, processRedirectChange } from '../helpers';
3
4
  export const handleRedirectUpdate = async (body) => {
5
+ var _a;
4
6
  const parsed = RedirectUpdateDefinition.schema.safeParse(body);
5
7
  if (!parsed.success) {
6
8
  return undefined;
7
9
  }
10
+ if ((_a = config.experimental) === null || _a === void 0 ? void 0 : _a.edgeRedirects) {
11
+ await processEdgeConfigChange(parsed.data);
12
+ }
8
13
  return processRedirectChange(parsed.data.source_url);
9
14
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniformdev/canvas-next-rsc",
3
- "version": "19.30.1-alpha.5+2a53a5820",
3
+ "version": "19.34.0",
4
4
  "license": "SEE LICENSE IN LICENSE.txt",
5
5
  "scripts": {
6
6
  "dev": "pnpm build --watch",
@@ -56,12 +56,12 @@
56
56
  "typescript": "^5.0.4"
57
57
  },
58
58
  "dependencies": {
59
- "@uniformdev/canvas": "^19.30.1-alpha.5+2a53a5820",
60
- "@uniformdev/canvas-react": "^19.30.1-alpha.5+2a53a5820",
61
- "@uniformdev/context": "^19.30.1-alpha.5+2a53a5820",
62
- "@uniformdev/project-map": "^19.30.1-alpha.5+2a53a5820",
63
- "@uniformdev/redirect": "^19.30.1-alpha.5+2a53a5820",
64
- "@uniformdev/webhooks": "^19.30.1-alpha.5+2a53a5820",
59
+ "@uniformdev/canvas": "^19.34.0",
60
+ "@uniformdev/canvas-react": "^19.34.0",
61
+ "@uniformdev/context": "^19.34.0",
62
+ "@uniformdev/project-map": "^19.34.0",
63
+ "@uniformdev/redirect": "^19.34.0",
64
+ "@uniformdev/webhooks": "^19.34.0",
65
65
  "@vercel/edge-config": "^0.1.5",
66
66
  "dequal": "^2.0.3",
67
67
  "js-cookie": "^3.0.5",
@@ -75,5 +75,5 @@
75
75
  "publishConfig": {
76
76
  "access": "public"
77
77
  },
78
- "gitHead": "2a53a5820c0343ca6bc8d883ed6737717afba488"
78
+ "gitHead": "303a002356452a5fc312cd384a67977545a4e888"
79
79
  }
@@ -1,26 +0,0 @@
1
- import { RootComponentInstance } from '@uniformdev/canvas';
2
- export type CompositionRouteResult = {
3
- type: 'composition';
4
- composition: RootComponentInstance;
5
- };
6
- export type RedirectRouteResult = {
7
- type: 'redirect';
8
- redirect: {
9
- href: string;
10
- statusCode: number;
11
- nodeId: string | undefined;
12
- };
13
- };
14
- export type NotFoundRouteResult = {
15
- type: 'notFound';
16
- };
17
- export type ResolveRouteResult = CompositionRouteResult | RedirectRouteResult | NotFoundRouteResult;
18
- export type ResolveRouteOptions = {
19
- path: string;
20
- state?: number;
21
- searchParams: {
22
- [key: string]: string | undefined;
23
- } | undefined;
24
- };
25
- export declare const resolveRoute: (data: ResolveRouteOptions) => Promise<ResolveRouteResult>;
26
- export declare const resolveRouteByRouteApi: (data: Pick<ResolveRouteOptions, 'path' | 'state' | 'searchParams'>) => Promise<ResolveRouteResult | undefined>;
@@ -1,59 +0,0 @@
1
- import { CANVAS_DRAFT_STATE } from '@uniformdev/canvas';
2
- import { RedirectClient } from '@uniformdev/redirect';
3
- import { getRouteClient } from '../client/routeClient';
4
- import { getRouteRevalidateInterval } from '../config/helpers';
5
- import { isOnVercelPreviewEnvironment } from '../utils/draft';
6
- import { getBaseUrl } from '../utils/url';
7
- export const resolveRoute = async (data) => {
8
- const routeResult = await resolveRouteByRouteApi(data);
9
- if (routeResult) {
10
- return routeResult;
11
- }
12
- return {
13
- type: 'notFound',
14
- };
15
- };
16
- export const resolveRouteByRouteApi = async (data) => {
17
- const routeClient = getRouteClient({
18
- revalidate: getRouteRevalidateInterval({
19
- searchParams: data.searchParams,
20
- }),
21
- });
22
- const routeResult = await routeClient.getRoute({
23
- path: data.path,
24
- state: data.state,
25
- withComponentIDs: data.state === CANVAS_DRAFT_STATE,
26
- withContentSourceMap: isOnVercelPreviewEnvironment(),
27
- });
28
- if (routeResult.type === 'notFound') {
29
- return {
30
- type: 'notFound',
31
- };
32
- }
33
- else if (routeResult.type === 'composition') {
34
- return {
35
- type: 'composition',
36
- composition: routeResult.compositionApiResponse.composition,
37
- };
38
- }
39
- else if (routeResult.type === 'redirect') {
40
- let href;
41
- if (routeResult.redirect.targetProjectMapNodeId) {
42
- const requestUrl = `${getBaseUrl()}${data.path}`;
43
- const expandedUrl = RedirectClient.getTargetVariableExpandedUrl(requestUrl, routeResult.redirect);
44
- const url = new URL(expandedUrl);
45
- href = url.pathname;
46
- }
47
- else {
48
- href = routeResult.redirect.targetUrl;
49
- }
50
- return {
51
- type: 'redirect',
52
- redirect: {
53
- href,
54
- statusCode: routeResult.redirect.targetStatusCode,
55
- nodeId: routeResult.redirect.targetProjectMapNodeId,
56
- },
57
- };
58
- }
59
- };