@opennextjs/aws 3.1.4

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 (175) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +94 -0
  3. package/dist/adapters/binary.d.ts +1 -0
  4. package/dist/adapters/binary.js +63 -0
  5. package/dist/adapters/cache.d.ts +96 -0
  6. package/dist/adapters/cache.js +278 -0
  7. package/dist/adapters/config/index.d.ts +27 -0
  8. package/dist/adapters/config/index.js +21 -0
  9. package/dist/adapters/config/util.d.ts +28 -0
  10. package/dist/adapters/config/util.js +84 -0
  11. package/dist/adapters/dynamo-provider.d.ts +8 -0
  12. package/dist/adapters/dynamo-provider.js +42 -0
  13. package/dist/adapters/edge-adapter.d.ts +8 -0
  14. package/dist/adapters/edge-adapter.js +54 -0
  15. package/dist/adapters/image-optimization-adapter.d.ts +4 -0
  16. package/dist/adapters/image-optimization-adapter.js +193 -0
  17. package/dist/adapters/logger.d.ts +13 -0
  18. package/dist/adapters/logger.js +51 -0
  19. package/dist/adapters/middleware.d.ts +5 -0
  20. package/dist/adapters/middleware.js +84 -0
  21. package/dist/adapters/plugins/14.1/util.d.ts +1 -0
  22. package/dist/adapters/plugins/14.1/util.js +29 -0
  23. package/dist/adapters/plugins/image-optimization/image-optimization.d.ts +11 -0
  24. package/dist/adapters/plugins/image-optimization/image-optimization.js +14 -0
  25. package/dist/adapters/plugins/image-optimization/image-optimization.replacement.d.ts +11 -0
  26. package/dist/adapters/plugins/image-optimization/image-optimization.replacement.js +22 -0
  27. package/dist/adapters/plugins/without-routing/requestHandler.d.ts +2 -0
  28. package/dist/adapters/plugins/without-routing/requestHandler.js +10 -0
  29. package/dist/adapters/revalidate.d.ts +9 -0
  30. package/dist/adapters/revalidate.js +71 -0
  31. package/dist/adapters/server-adapter.d.ts +4 -0
  32. package/dist/adapters/server-adapter.js +27 -0
  33. package/dist/adapters/util.d.ts +10 -0
  34. package/dist/adapters/util.js +28 -0
  35. package/dist/adapters/warmer-function.d.ts +12 -0
  36. package/dist/adapters/warmer-function.js +80 -0
  37. package/dist/build/bundleNextServer.d.ts +1 -0
  38. package/dist/build/bundleNextServer.js +87 -0
  39. package/dist/build/compileConfig.d.ts +3 -0
  40. package/dist/build/compileConfig.js +64 -0
  41. package/dist/build/copyTracedFiles.d.ts +1 -0
  42. package/dist/build/copyTracedFiles.js +207 -0
  43. package/dist/build/createServerBundle.d.ts +3 -0
  44. package/dist/build/createServerBundle.js +228 -0
  45. package/dist/build/edge/createEdgeBundle.d.ts +20 -0
  46. package/dist/build/edge/createEdgeBundle.js +149 -0
  47. package/dist/build/generateOutput.d.ts +2 -0
  48. package/dist/build/generateOutput.js +239 -0
  49. package/dist/build/helper.d.ts +33 -0
  50. package/dist/build/helper.js +193 -0
  51. package/dist/build/patch/patchedAsyncStorage.d.ts +5 -0
  52. package/dist/build/patch/patchedAsyncStorage.js +16 -0
  53. package/dist/build/validateConfig.d.ts +2 -0
  54. package/dist/build/validateConfig.js +76 -0
  55. package/dist/build.d.ts +7 -0
  56. package/dist/build.js +533 -0
  57. package/dist/cache/incremental/s3-lite.d.ts +3 -0
  58. package/dist/cache/incremental/s3-lite.js +72 -0
  59. package/dist/cache/incremental/s3.d.ts +3 -0
  60. package/dist/cache/incremental/s3.js +44 -0
  61. package/dist/cache/incremental/types.d.ts +34 -0
  62. package/dist/cache/incremental/types.js +1 -0
  63. package/dist/cache/next-types.d.ts +62 -0
  64. package/dist/cache/next-types.js +1 -0
  65. package/dist/cache/tag/constants.d.ts +2 -0
  66. package/dist/cache/tag/constants.js +17 -0
  67. package/dist/cache/tag/dynamodb-lite.d.ts +3 -0
  68. package/dist/cache/tag/dynamodb-lite.js +174 -0
  69. package/dist/cache/tag/dynamodb.d.ts +3 -0
  70. package/dist/cache/tag/dynamodb.js +129 -0
  71. package/dist/cache/tag/types.d.ts +11 -0
  72. package/dist/cache/tag/types.js +1 -0
  73. package/dist/converters/aws-apigw-v1.d.ts +3 -0
  74. package/dist/converters/aws-apigw-v1.js +89 -0
  75. package/dist/converters/aws-apigw-v2.d.ts +3 -0
  76. package/dist/converters/aws-apigw-v2.js +99 -0
  77. package/dist/converters/aws-cloudfront.d.ts +3 -0
  78. package/dist/converters/aws-cloudfront.js +170 -0
  79. package/dist/converters/dummy.d.ts +7 -0
  80. package/dist/converters/dummy.js +16 -0
  81. package/dist/converters/edge.d.ts +6 -0
  82. package/dist/converters/edge.js +88 -0
  83. package/dist/converters/node.d.ts +3 -0
  84. package/dist/converters/node.js +45 -0
  85. package/dist/converters/sqs-revalidate.d.ts +4 -0
  86. package/dist/converters/sqs-revalidate.js +22 -0
  87. package/dist/converters/utils.d.ts +1 -0
  88. package/dist/converters/utils.js +9 -0
  89. package/dist/core/createGenericHandler.d.ts +11 -0
  90. package/dist/core/createGenericHandler.js +16 -0
  91. package/dist/core/createMainHandler.d.ts +18 -0
  92. package/dist/core/createMainHandler.js +24 -0
  93. package/dist/core/edgeFunctionHandler.d.ts +54 -0
  94. package/dist/core/edgeFunctionHandler.js +21 -0
  95. package/dist/core/patchAsyncStorage.d.ts +1 -0
  96. package/dist/core/patchAsyncStorage.js +16 -0
  97. package/dist/core/requestHandler.d.ts +3 -0
  98. package/dist/core/requestHandler.js +162 -0
  99. package/dist/core/require-hooks.d.ts +3 -0
  100. package/dist/core/require-hooks.js +102 -0
  101. package/dist/core/resolve.d.ts +31 -0
  102. package/dist/core/resolve.js +81 -0
  103. package/dist/core/routing/cacheInterceptor.d.ts +2 -0
  104. package/dist/core/routing/cacheInterceptor.js +162 -0
  105. package/dist/core/routing/i18n/accept-header.d.ts +1 -0
  106. package/dist/core/routing/i18n/accept-header.js +101 -0
  107. package/dist/core/routing/i18n/index.d.ts +2 -0
  108. package/dist/core/routing/i18n/index.js +36 -0
  109. package/dist/core/routing/matcher.d.ts +25 -0
  110. package/dist/core/routing/matcher.js +294 -0
  111. package/dist/core/routing/middleware.d.ts +7 -0
  112. package/dist/core/routing/middleware.js +167 -0
  113. package/dist/core/routing/util.d.ts +105 -0
  114. package/dist/core/routing/util.js +415 -0
  115. package/dist/core/routingHandler.d.ts +8 -0
  116. package/dist/core/routingHandler.js +127 -0
  117. package/dist/core/util.d.ts +5 -0
  118. package/dist/core/util.js +84 -0
  119. package/dist/helpers/withCloudflare.d.ts +65 -0
  120. package/dist/helpers/withCloudflare.js +58 -0
  121. package/dist/helpers/withSST.d.ts +54 -0
  122. package/dist/helpers/withSST.js +28 -0
  123. package/dist/http/index.d.ts +2 -0
  124. package/dist/http/index.js +4 -0
  125. package/dist/http/openNextResponse.d.ts +78 -0
  126. package/dist/http/openNextResponse.js +281 -0
  127. package/dist/http/request.d.ts +12 -0
  128. package/dist/http/request.js +35 -0
  129. package/dist/http/util.d.ts +4 -0
  130. package/dist/http/util.js +34 -0
  131. package/dist/index.d.ts +2 -0
  132. package/dist/index.js +37 -0
  133. package/dist/logger.d.ts +12 -0
  134. package/dist/logger.js +13 -0
  135. package/dist/minimize-js.d.ts +1 -0
  136. package/dist/minimize-js.js +101 -0
  137. package/dist/overrides/imageLoader/host.d.ts +3 -0
  138. package/dist/overrides/imageLoader/host.js +29 -0
  139. package/dist/overrides/imageLoader/s3.d.ts +3 -0
  140. package/dist/overrides/imageLoader/s3.js +33 -0
  141. package/dist/plugins/edge.d.ts +15 -0
  142. package/dist/plugins/edge.js +157 -0
  143. package/dist/plugins/replacement.d.ts +42 -0
  144. package/dist/plugins/replacement.js +74 -0
  145. package/dist/plugins/resolve.d.ts +18 -0
  146. package/dist/plugins/resolve.js +45 -0
  147. package/dist/queue/sqs-lite.d.ts +3 -0
  148. package/dist/queue/sqs-lite.js +52 -0
  149. package/dist/queue/sqs.d.ts +3 -0
  150. package/dist/queue/sqs.js +20 -0
  151. package/dist/queue/types.d.ts +12 -0
  152. package/dist/queue/types.js +1 -0
  153. package/dist/types/aws-lambda.d.ts +22 -0
  154. package/dist/types/aws-lambda.js +1 -0
  155. package/dist/types/next-types.d.ts +155 -0
  156. package/dist/types/next-types.js +2 -0
  157. package/dist/types/open-next.d.ts +303 -0
  158. package/dist/types/open-next.js +1 -0
  159. package/dist/utils/error.d.ts +23 -0
  160. package/dist/utils/error.js +32 -0
  161. package/dist/utils/fetch.d.ts +2 -0
  162. package/dist/utils/fetch.js +23 -0
  163. package/dist/utils/promise.d.ts +18 -0
  164. package/dist/utils/promise.js +48 -0
  165. package/dist/utils/stream.d.ts +5 -0
  166. package/dist/utils/stream.js +30 -0
  167. package/dist/wrappers/aws-lambda-streaming.d.ts +7 -0
  168. package/dist/wrappers/aws-lambda-streaming.js +88 -0
  169. package/dist/wrappers/aws-lambda.d.ts +7 -0
  170. package/dist/wrappers/aws-lambda.js +39 -0
  171. package/dist/wrappers/cloudflare.d.ts +11 -0
  172. package/dist/wrappers/cloudflare.js +20 -0
  173. package/dist/wrappers/node.d.ts +7 -0
  174. package/dist/wrappers/node.js +57 -0
  175. package/package.json +66 -0
@@ -0,0 +1,34 @@
1
+ import { Meta } from "../next-types";
2
+ export type S3CachedFile = {
3
+ type: "redirect";
4
+ props?: Object;
5
+ meta?: Meta;
6
+ } | {
7
+ type: "page";
8
+ html: string;
9
+ json: Object;
10
+ meta?: Meta;
11
+ } | {
12
+ type: "app";
13
+ html: string;
14
+ rsc: string;
15
+ meta?: Meta;
16
+ } | {
17
+ type: "route";
18
+ body: string;
19
+ meta?: Meta;
20
+ };
21
+ export type S3FetchCache = Object;
22
+ export type WithLastModified<T> = {
23
+ lastModified?: number;
24
+ value?: T;
25
+ };
26
+ export type CacheValue<IsFetch extends boolean> = (IsFetch extends true ? S3FetchCache : S3CachedFile) & {
27
+ revalidate?: number | false;
28
+ };
29
+ export type IncrementalCache = {
30
+ get<IsFetch extends boolean = false>(key: string, isFetch?: IsFetch): Promise<WithLastModified<CacheValue<IsFetch>>>;
31
+ set<IsFetch extends boolean = false>(key: string, value: CacheValue<IsFetch>, isFetch?: IsFetch): Promise<void>;
32
+ delete(key: string): Promise<void>;
33
+ name: string;
34
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,62 @@
1
+ /// <reference types="node" />
2
+ interface CachedFetchValue {
3
+ kind: "FETCH";
4
+ data: {
5
+ headers: {
6
+ [k: string]: string;
7
+ };
8
+ body: string;
9
+ url: string;
10
+ status?: number;
11
+ tags?: string[];
12
+ };
13
+ revalidate: number;
14
+ }
15
+ interface CachedRedirectValue {
16
+ kind: "REDIRECT";
17
+ props: Object;
18
+ }
19
+ interface CachedRouteValue {
20
+ kind: "ROUTE";
21
+ body: Buffer;
22
+ status: number;
23
+ headers: Record<string, undefined | string | string[]>;
24
+ }
25
+ interface CachedImageValue {
26
+ kind: "IMAGE";
27
+ etag: string;
28
+ buffer: Buffer;
29
+ extension: string;
30
+ isMiss?: boolean;
31
+ isStale?: boolean;
32
+ }
33
+ interface IncrementalCachedPageValue {
34
+ kind: "PAGE";
35
+ html: string;
36
+ pageData: Object;
37
+ status?: number;
38
+ headers?: Record<string, undefined | string>;
39
+ }
40
+ type IncrementalCacheValue = CachedRedirectValue | IncrementalCachedPageValue | CachedImageValue | CachedFetchValue | CachedRouteValue;
41
+ export interface CacheHandlerContext {
42
+ fs?: never;
43
+ dev?: boolean;
44
+ flushToDisk?: boolean;
45
+ serverDistDir?: string;
46
+ maxMemoryCacheSize?: number;
47
+ _appDir: boolean;
48
+ _requestHeaders: never;
49
+ fetchCacheKeyPrefix?: string;
50
+ }
51
+ export interface CacheHandlerValue {
52
+ lastModified?: number;
53
+ age?: number;
54
+ cacheState?: string;
55
+ value: IncrementalCacheValue | null;
56
+ }
57
+ export type Extension = "cache" | "fetch";
58
+ export interface Meta {
59
+ status?: number;
60
+ headers?: Record<string, undefined | string | string[]>;
61
+ }
62
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export declare const MAX_DYNAMO_BATCH_WRITE_ITEM_COUNT = 25;
2
+ export declare const getDynamoBatchWriteCommandConcurrency: () => number;
@@ -0,0 +1,17 @@
1
+ export const MAX_DYNAMO_BATCH_WRITE_ITEM_COUNT = 25;
2
+ /**
3
+ * Sending to dynamo X commands at a time, using about X * 25 write units per batch to not overwhelm DDB
4
+ * and give production plenty of room to work with. With DDB Response times, you can expect about 10 batches per second.
5
+ */
6
+ const DEFAULT_DYNAMO_BATCH_WRITE_COMMAND_CONCURRENCY = 4;
7
+ export const getDynamoBatchWriteCommandConcurrency = () => {
8
+ const dynamoBatchWriteCommandConcurrencyFromEnv = process.env.DYNAMO_BATCH_WRITE_COMMAND_CONCURRENCY;
9
+ const parsedDynamoBatchWriteCommandConcurrencyFromEnv = dynamoBatchWriteCommandConcurrencyFromEnv
10
+ ? parseInt(dynamoBatchWriteCommandConcurrencyFromEnv)
11
+ : undefined;
12
+ if (parsedDynamoBatchWriteCommandConcurrencyFromEnv &&
13
+ !isNaN(parsedDynamoBatchWriteCommandConcurrencyFromEnv)) {
14
+ return parsedDynamoBatchWriteCommandConcurrencyFromEnv;
15
+ }
16
+ return DEFAULT_DYNAMO_BATCH_WRITE_COMMAND_CONCURRENCY;
17
+ };
@@ -0,0 +1,3 @@
1
+ import { TagCache } from "./types";
2
+ declare const tagCache: TagCache;
3
+ export default tagCache;
@@ -0,0 +1,174 @@
1
+ /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
+ import { AwsClient } from "aws4fetch";
3
+ import path from "path";
4
+ import { RecoverableError } from "../../utils/error";
5
+ import { customFetchClient } from "../../utils/fetch";
6
+ import { debug, error } from "../../adapters/logger";
7
+ import { chunk, parseNumberFromEnv } from "../../adapters/util";
8
+ import { getDynamoBatchWriteCommandConcurrency, MAX_DYNAMO_BATCH_WRITE_ITEM_COUNT, } from "./constants";
9
+ let awsClient = null;
10
+ const getAwsClient = () => {
11
+ const { CACHE_BUCKET_REGION } = process.env;
12
+ if (awsClient) {
13
+ return awsClient;
14
+ }
15
+ else {
16
+ awsClient = new AwsClient({
17
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
18
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
19
+ sessionToken: process.env.AWS_SESSION_TOKEN,
20
+ region: CACHE_BUCKET_REGION,
21
+ retries: parseNumberFromEnv(process.env.AWS_SDK_S3_MAX_ATTEMPTS),
22
+ });
23
+ return awsClient;
24
+ }
25
+ };
26
+ const awsFetch = (body, type = "query") => {
27
+ const { CACHE_BUCKET_REGION } = process.env;
28
+ const client = getAwsClient();
29
+ return customFetchClient(client)(`https://dynamodb.${CACHE_BUCKET_REGION}.amazonaws.com`, {
30
+ method: "POST",
31
+ headers: {
32
+ "Content-Type": "application/x-amz-json-1.0",
33
+ "X-Amz-Target": `DynamoDB_20120810.${type === "query" ? "Query" : "BatchWriteItem"}`,
34
+ },
35
+ body,
36
+ });
37
+ };
38
+ function buildDynamoKey(key) {
39
+ const { NEXT_BUILD_ID } = process.env;
40
+ // FIXME: We should probably use something else than path.join here
41
+ // this could transform some fetch cache key into a valid path
42
+ return path.posix.join(NEXT_BUILD_ID ?? "", key);
43
+ }
44
+ function buildDynamoObject(path, tags, revalidatedAt) {
45
+ return {
46
+ path: { S: buildDynamoKey(path) },
47
+ tag: { S: buildDynamoKey(tags) },
48
+ revalidatedAt: { N: `${revalidatedAt ?? Date.now()}` },
49
+ };
50
+ }
51
+ const tagCache = {
52
+ async getByPath(path) {
53
+ try {
54
+ if (globalThis.disableDynamoDBCache)
55
+ return [];
56
+ const { CACHE_DYNAMO_TABLE, NEXT_BUILD_ID } = process.env;
57
+ const result = await awsFetch(JSON.stringify({
58
+ TableName: CACHE_DYNAMO_TABLE,
59
+ IndexName: "revalidate",
60
+ KeyConditionExpression: "#key = :key",
61
+ ExpressionAttributeNames: {
62
+ "#key": "path",
63
+ },
64
+ ExpressionAttributeValues: {
65
+ ":key": { S: buildDynamoKey(path) },
66
+ },
67
+ }));
68
+ if (result.status !== 200) {
69
+ throw new RecoverableError(`Failed to get tags by path: ${result.status}`);
70
+ }
71
+ const { Items } = (await result.json());
72
+ const tags = Items?.map((item) => item.tag.S ?? "") ?? [];
73
+ debug("tags for path", path, tags);
74
+ // We need to remove the buildId from the path
75
+ return tags.map((tag) => tag.replace(`${NEXT_BUILD_ID}/`, ""));
76
+ }
77
+ catch (e) {
78
+ error("Failed to get tags by path", e);
79
+ return [];
80
+ }
81
+ },
82
+ async getByTag(tag) {
83
+ try {
84
+ if (globalThis.disableDynamoDBCache)
85
+ return [];
86
+ const { CACHE_DYNAMO_TABLE, NEXT_BUILD_ID } = process.env;
87
+ const result = await awsFetch(JSON.stringify({
88
+ TableName: CACHE_DYNAMO_TABLE,
89
+ KeyConditionExpression: "#tag = :tag",
90
+ ExpressionAttributeNames: {
91
+ "#tag": "tag",
92
+ },
93
+ ExpressionAttributeValues: {
94
+ ":tag": { S: buildDynamoKey(tag) },
95
+ },
96
+ }));
97
+ if (result.status !== 200) {
98
+ throw new RecoverableError(`Failed to get by tag: ${result.status}`);
99
+ }
100
+ const { Items } = (await result.json());
101
+ return (
102
+ // We need to remove the buildId from the path
103
+ Items?.map(({ path: { S: key } }) => key?.replace(`${NEXT_BUILD_ID}/`, "") ?? "") ?? []);
104
+ }
105
+ catch (e) {
106
+ error("Failed to get by tag", e);
107
+ return [];
108
+ }
109
+ },
110
+ async getLastModified(key, lastModified) {
111
+ try {
112
+ if (globalThis.disableDynamoDBCache)
113
+ return lastModified ?? Date.now();
114
+ const { CACHE_DYNAMO_TABLE } = process.env;
115
+ const result = await awsFetch(JSON.stringify({
116
+ TableName: CACHE_DYNAMO_TABLE,
117
+ IndexName: "revalidate",
118
+ KeyConditionExpression: "#key = :key AND #revalidatedAt > :lastModified",
119
+ ExpressionAttributeNames: {
120
+ "#key": "path",
121
+ "#revalidatedAt": "revalidatedAt",
122
+ },
123
+ ExpressionAttributeValues: {
124
+ ":key": { S: buildDynamoKey(key) },
125
+ ":lastModified": { N: String(lastModified ?? 0) },
126
+ },
127
+ }));
128
+ if (result.status !== 200) {
129
+ throw new RecoverableError(`Failed to get last modified: ${result.status}`);
130
+ }
131
+ const revalidatedTags = (await result.json()).Items ?? [];
132
+ debug("revalidatedTags", revalidatedTags);
133
+ // If we have revalidated tags we return -1 to force revalidation
134
+ return revalidatedTags.length > 0 ? -1 : lastModified ?? Date.now();
135
+ }
136
+ catch (e) {
137
+ error("Failed to get revalidated tags", e);
138
+ return lastModified ?? Date.now();
139
+ }
140
+ },
141
+ async writeTags(tags) {
142
+ try {
143
+ const { CACHE_DYNAMO_TABLE } = process.env;
144
+ if (globalThis.disableDynamoDBCache)
145
+ return;
146
+ const dataChunks = chunk(tags, MAX_DYNAMO_BATCH_WRITE_ITEM_COUNT).map((Items) => ({
147
+ RequestItems: {
148
+ [CACHE_DYNAMO_TABLE ?? ""]: Items.map((Item) => ({
149
+ PutRequest: {
150
+ Item: {
151
+ ...buildDynamoObject(Item.path, Item.tag, Item.revalidatedAt),
152
+ },
153
+ },
154
+ })),
155
+ },
156
+ }));
157
+ const toInsert = chunk(dataChunks, getDynamoBatchWriteCommandConcurrency());
158
+ for (const paramsChunk of toInsert) {
159
+ await Promise.all(paramsChunk.map(async (params) => {
160
+ const response = await awsFetch(JSON.stringify(params), "batchWrite");
161
+ if (response.status !== 200) {
162
+ throw new RecoverableError(`Failed to batch write dynamo item: ${response.status}`);
163
+ }
164
+ return response;
165
+ }));
166
+ }
167
+ }
168
+ catch (e) {
169
+ error("Failed to batch write dynamo item", e);
170
+ }
171
+ },
172
+ name: "dynamoDb",
173
+ };
174
+ export default tagCache;
@@ -0,0 +1,3 @@
1
+ import { TagCache } from "./types";
2
+ declare const tagCache: TagCache;
3
+ export default tagCache;
@@ -0,0 +1,129 @@
1
+ import { BatchWriteItemCommand, DynamoDBClient, QueryCommand, } from "@aws-sdk/client-dynamodb";
2
+ import path from "path";
3
+ import { awsLogger, debug, error } from "../../adapters/logger";
4
+ import { chunk, parseNumberFromEnv } from "../../adapters/util";
5
+ import { getDynamoBatchWriteCommandConcurrency, MAX_DYNAMO_BATCH_WRITE_ITEM_COUNT, } from "./constants";
6
+ const { CACHE_BUCKET_REGION, CACHE_DYNAMO_TABLE, NEXT_BUILD_ID } = process.env;
7
+ function parseDynamoClientConfigFromEnv() {
8
+ return {
9
+ region: CACHE_BUCKET_REGION,
10
+ logger: awsLogger,
11
+ maxAttempts: parseNumberFromEnv(process.env.AWS_SDK_DYNAMODB_MAX_ATTEMPTS),
12
+ };
13
+ }
14
+ const dynamoClient = new DynamoDBClient(parseDynamoClientConfigFromEnv());
15
+ function buildDynamoKey(key) {
16
+ // FIXME: We should probably use something else than path.join here
17
+ // this could transform some fetch cache key into a valid path
18
+ return path.posix.join(NEXT_BUILD_ID ?? "", key);
19
+ }
20
+ function buildDynamoObject(path, tags, revalidatedAt) {
21
+ return {
22
+ path: { S: buildDynamoKey(path) },
23
+ tag: { S: buildDynamoKey(tags) },
24
+ revalidatedAt: { N: `${revalidatedAt ?? Date.now()}` },
25
+ };
26
+ }
27
+ const tagCache = {
28
+ async getByPath(path) {
29
+ try {
30
+ if (globalThis.disableDynamoDBCache)
31
+ return [];
32
+ const result = await dynamoClient.send(new QueryCommand({
33
+ TableName: CACHE_DYNAMO_TABLE,
34
+ IndexName: "revalidate",
35
+ KeyConditionExpression: "#key = :key",
36
+ ExpressionAttributeNames: {
37
+ "#key": "path",
38
+ },
39
+ ExpressionAttributeValues: {
40
+ ":key": { S: buildDynamoKey(path) },
41
+ },
42
+ }));
43
+ const tags = result.Items?.map((item) => item.tag.S ?? "") ?? [];
44
+ debug("tags for path", path, tags);
45
+ // We need to remove the buildId from the path
46
+ return tags.map((tag) => tag.replace(`${NEXT_BUILD_ID}/`, ""));
47
+ }
48
+ catch (e) {
49
+ error("Failed to get tags by path", e);
50
+ return [];
51
+ }
52
+ },
53
+ async getByTag(tag) {
54
+ try {
55
+ if (globalThis.disableDynamoDBCache)
56
+ return [];
57
+ const { Items } = await dynamoClient.send(new QueryCommand({
58
+ TableName: CACHE_DYNAMO_TABLE,
59
+ KeyConditionExpression: "#tag = :tag",
60
+ ExpressionAttributeNames: {
61
+ "#tag": "tag",
62
+ },
63
+ ExpressionAttributeValues: {
64
+ ":tag": { S: buildDynamoKey(tag) },
65
+ },
66
+ }));
67
+ return (
68
+ // We need to remove the buildId from the path
69
+ Items?.map(({ path: { S: key } }) => key?.replace(`${NEXT_BUILD_ID}/`, "") ?? "") ?? []);
70
+ }
71
+ catch (e) {
72
+ error("Failed to get by tag", e);
73
+ return [];
74
+ }
75
+ },
76
+ async getLastModified(key, lastModified) {
77
+ try {
78
+ if (globalThis.disableDynamoDBCache)
79
+ return lastModified ?? Date.now();
80
+ const result = await dynamoClient.send(new QueryCommand({
81
+ TableName: CACHE_DYNAMO_TABLE,
82
+ IndexName: "revalidate",
83
+ KeyConditionExpression: "#key = :key AND #revalidatedAt > :lastModified",
84
+ ExpressionAttributeNames: {
85
+ "#key": "path",
86
+ "#revalidatedAt": "revalidatedAt",
87
+ },
88
+ ExpressionAttributeValues: {
89
+ ":key": { S: buildDynamoKey(key) },
90
+ ":lastModified": { N: String(lastModified ?? 0) },
91
+ },
92
+ }));
93
+ const revalidatedTags = result.Items ?? [];
94
+ debug("revalidatedTags", revalidatedTags);
95
+ // If we have revalidated tags we return -1 to force revalidation
96
+ return revalidatedTags.length > 0 ? -1 : lastModified ?? Date.now();
97
+ }
98
+ catch (e) {
99
+ error("Failed to get revalidated tags", e);
100
+ return lastModified ?? Date.now();
101
+ }
102
+ },
103
+ async writeTags(tags) {
104
+ try {
105
+ if (globalThis.disableDynamoDBCache)
106
+ return;
107
+ const dataChunks = chunk(tags, MAX_DYNAMO_BATCH_WRITE_ITEM_COUNT).map((Items) => ({
108
+ RequestItems: {
109
+ [CACHE_DYNAMO_TABLE ?? ""]: Items.map((Item) => ({
110
+ PutRequest: {
111
+ Item: {
112
+ ...buildDynamoObject(Item.path, Item.tag, Item.revalidatedAt),
113
+ },
114
+ },
115
+ })),
116
+ },
117
+ }));
118
+ const toInsert = chunk(dataChunks, getDynamoBatchWriteCommandConcurrency());
119
+ for (const paramsChunk of toInsert) {
120
+ await Promise.all(paramsChunk.map(async (params) => dynamoClient.send(new BatchWriteItemCommand(params))));
121
+ }
122
+ }
123
+ catch (e) {
124
+ error("Failed to batch write dynamo item", e);
125
+ }
126
+ },
127
+ name: "dynamoDb",
128
+ };
129
+ export default tagCache;
@@ -0,0 +1,11 @@
1
+ export type TagCache = {
2
+ getByTag(tag: string): Promise<string[]>;
3
+ getByPath(path: string): Promise<string[]>;
4
+ getLastModified(path: string, lastModified?: number): Promise<number>;
5
+ writeTags(tags: {
6
+ tag: string;
7
+ path: string;
8
+ revalidatedAt?: number;
9
+ }[]): Promise<void>;
10
+ name: string;
11
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,3 @@
1
+ import type { Converter, InternalEvent, InternalResult } from "../types/open-next";
2
+ declare const _default: Converter<InternalEvent, InternalResult>;
3
+ export default _default;
@@ -0,0 +1,89 @@
1
+ import { fromReadableStream } from "../utils/stream";
2
+ import { debug } from "../adapters/logger";
3
+ import { removeUndefinedFromQuery } from "./utils";
4
+ function normalizeAPIGatewayProxyEventHeaders(event) {
5
+ event.multiValueHeaders;
6
+ const headers = {};
7
+ for (const [key, values] of Object.entries(event.multiValueHeaders || {})) {
8
+ if (values) {
9
+ headers[key.toLowerCase()] = values.join(",");
10
+ }
11
+ }
12
+ for (const [key, value] of Object.entries(event.headers || {})) {
13
+ if (value) {
14
+ headers[key.toLowerCase()] = value;
15
+ }
16
+ }
17
+ return headers;
18
+ }
19
+ function normalizeAPIGatewayProxyEventQueryParams(event) {
20
+ // Note that the same query string values are returned in both
21
+ // "multiValueQueryStringParameters" and "queryStringParameters".
22
+ // We only need to use one of them.
23
+ // For example:
24
+ // "?name=foo" appears in the event object as
25
+ // {
26
+ // ...
27
+ // queryStringParameters: { name: 'foo' },
28
+ // multiValueQueryStringParameters: { name: [ 'foo' ] },
29
+ // ...
30
+ // }
31
+ const params = new URLSearchParams();
32
+ for (const [key, value] of Object.entries(event.multiValueQueryStringParameters || {})) {
33
+ if (value !== undefined) {
34
+ for (const v of value) {
35
+ params.append(key, v);
36
+ }
37
+ }
38
+ }
39
+ const value = params.toString();
40
+ return value ? `?${value}` : "";
41
+ }
42
+ async function convertFromAPIGatewayProxyEvent(event) {
43
+ const { path, body, httpMethod, requestContext, isBase64Encoded } = event;
44
+ return {
45
+ type: "core",
46
+ method: httpMethod,
47
+ rawPath: path,
48
+ url: path + normalizeAPIGatewayProxyEventQueryParams(event),
49
+ body: Buffer.from(body ?? "", isBase64Encoded ? "base64" : "utf8"),
50
+ headers: normalizeAPIGatewayProxyEventHeaders(event),
51
+ remoteAddress: requestContext.identity.sourceIp,
52
+ query: removeUndefinedFromQuery(event.multiValueQueryStringParameters ?? {}),
53
+ cookies: event.multiValueHeaders?.cookie?.reduce((acc, cur) => {
54
+ const [key, value] = cur.split("=");
55
+ return { ...acc, [key]: value };
56
+ }, {}) ?? {},
57
+ };
58
+ }
59
+ async function convertToApiGatewayProxyResult(result) {
60
+ const headers = {};
61
+ const multiValueHeaders = {};
62
+ Object.entries(result.headers).forEach(([key, value]) => {
63
+ if (Array.isArray(value)) {
64
+ multiValueHeaders[key] = value;
65
+ }
66
+ else {
67
+ if (value === null) {
68
+ headers[key] = "";
69
+ return;
70
+ }
71
+ headers[key] = value;
72
+ }
73
+ });
74
+ const body = await fromReadableStream(result.body, result.isBase64Encoded);
75
+ const response = {
76
+ statusCode: result.statusCode,
77
+ headers,
78
+ body,
79
+ isBase64Encoded: result.isBase64Encoded,
80
+ multiValueHeaders,
81
+ };
82
+ debug(response);
83
+ return response;
84
+ }
85
+ export default {
86
+ convertFrom: convertFromAPIGatewayProxyEvent,
87
+ convertTo: convertToApiGatewayProxyResult,
88
+ name: "aws-apigw-v1",
89
+ };
@@ -0,0 +1,3 @@
1
+ import type { Converter, InternalEvent, InternalResult } from "../types/open-next";
2
+ declare const _default: Converter<InternalEvent, InternalResult>;
3
+ export default _default;
@@ -0,0 +1,99 @@
1
+ import { parseCookies } from "../http/util";
2
+ import { fromReadableStream } from "../utils/stream";
3
+ import { debug } from "../adapters/logger";
4
+ import { convertToQuery } from "../core/routing/util";
5
+ import { removeUndefinedFromQuery } from "./utils";
6
+ // Not sure which one is reallly needed as this is not documented anywhere but server actions redirect are not working without this, it causes a 500 error from cloudfront itself with a 'x-amzErrortype: InternalFailure' header
7
+ const CloudFrontBlacklistedHeaders = [
8
+ "connection",
9
+ "expect",
10
+ "keep-alive",
11
+ "proxy-authenticate",
12
+ "proxy-authorization",
13
+ "proxy-connection",
14
+ "trailer",
15
+ "upgrade",
16
+ "x-accel-buffering",
17
+ "x-accel-charset",
18
+ "x-accel-limit-rate",
19
+ "x-accel-redirect",
20
+ /x-amz-cf-(.*)/,
21
+ /x-amzn-(.*)/,
22
+ /x-edge-(.*)/,
23
+ "x-cache",
24
+ "x-forwarded-proto",
25
+ "x-real-ip",
26
+ "set-cookie",
27
+ "age",
28
+ "via",
29
+ ];
30
+ function normalizeAPIGatewayProxyEventV2Body(event) {
31
+ const { body, isBase64Encoded } = event;
32
+ if (Buffer.isBuffer(body)) {
33
+ return body;
34
+ }
35
+ else if (typeof body === "string") {
36
+ return Buffer.from(body, isBase64Encoded ? "base64" : "utf8");
37
+ }
38
+ else if (typeof body === "object") {
39
+ return Buffer.from(JSON.stringify(body));
40
+ }
41
+ else {
42
+ return Buffer.from("", "utf8");
43
+ }
44
+ }
45
+ function normalizeAPIGatewayProxyEventV2Headers(event) {
46
+ const { headers: rawHeaders, cookies } = event;
47
+ const headers = {};
48
+ if (Array.isArray(cookies)) {
49
+ headers["cookie"] = cookies.join("; ");
50
+ }
51
+ for (const [key, value] of Object.entries(rawHeaders || {})) {
52
+ headers[key.toLowerCase()] = value;
53
+ }
54
+ return headers;
55
+ }
56
+ async function convertFromAPIGatewayProxyEventV2(event) {
57
+ const { rawPath, rawQueryString, requestContext } = event;
58
+ return {
59
+ type: "core",
60
+ method: requestContext.http.method,
61
+ rawPath,
62
+ url: rawPath + (rawQueryString ? `?${rawQueryString}` : ""),
63
+ body: normalizeAPIGatewayProxyEventV2Body(event),
64
+ headers: normalizeAPIGatewayProxyEventV2Headers(event),
65
+ remoteAddress: requestContext.http.sourceIp,
66
+ query: removeUndefinedFromQuery(convertToQuery(rawQueryString)),
67
+ cookies: event.cookies?.reduce((acc, cur) => {
68
+ const [key, value] = cur.split("=");
69
+ return { ...acc, [key]: value };
70
+ }, {}) ?? {},
71
+ };
72
+ }
73
+ async function convertToApiGatewayProxyResultV2(result) {
74
+ const headers = {};
75
+ Object.entries(result.headers)
76
+ .filter(([key]) => !CloudFrontBlacklistedHeaders.some((header) => typeof header === "string" ? header === key : header.test(key)))
77
+ .forEach(([key, value]) => {
78
+ if (value === null || value === undefined) {
79
+ headers[key] = "";
80
+ return;
81
+ }
82
+ headers[key] = Array.isArray(value) ? value.join(", ") : `${value}`;
83
+ });
84
+ const body = await fromReadableStream(result.body, result.isBase64Encoded);
85
+ const response = {
86
+ statusCode: result.statusCode,
87
+ headers,
88
+ cookies: parseCookies(result.headers["set-cookie"]),
89
+ body,
90
+ isBase64Encoded: result.isBase64Encoded,
91
+ };
92
+ debug(response);
93
+ return response;
94
+ }
95
+ export default {
96
+ convertFrom: convertFromAPIGatewayProxyEventV2,
97
+ convertTo: convertToApiGatewayProxyResultV2,
98
+ name: "aws-apigw-v2",
99
+ };
@@ -0,0 +1,3 @@
1
+ import type { Converter, InternalEvent, InternalResult } from "../types/open-next";
2
+ declare const _default: Converter<InternalEvent, InternalResult>;
3
+ export default _default;