@opennextjs/aws 3.5.2 → 3.5.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 (44) hide show
  1. package/dist/adapters/config/util.d.ts +1 -0
  2. package/dist/adapters/config/util.js +5 -2
  3. package/dist/adapters/logger.js +1 -1
  4. package/dist/build/compileCache.js +1 -1
  5. package/dist/build/compileConfig.d.ts +6 -2
  6. package/dist/build/compileConfig.js +8 -7
  7. package/dist/build/constant.d.ts +1 -0
  8. package/dist/build/constant.js +1 -0
  9. package/dist/build/copyTracedFiles.d.ts +21 -1
  10. package/dist/build/copyTracedFiles.js +28 -5
  11. package/dist/build/createImageOptimizationBundle.js +1 -1
  12. package/dist/build/createMiddleware.js +1 -0
  13. package/dist/build/createServerBundle.d.ts +9 -1
  14. package/dist/build/createServerBundle.js +39 -15
  15. package/dist/build/edge/createEdgeBundle.d.ts +9 -2
  16. package/dist/build/edge/createEdgeBundle.js +28 -17
  17. package/dist/build/helper.d.ts +3 -2
  18. package/dist/build/helper.js +45 -20
  19. package/dist/build/patch/astCodePatcher.d.ts +58 -0
  20. package/dist/build/patch/astCodePatcher.js +76 -0
  21. package/dist/build/patch/codePatcher.d.ts +46 -0
  22. package/dist/build/patch/codePatcher.js +119 -0
  23. package/dist/build/patch/patchFetchCacheISR.d.ts +5 -0
  24. package/dist/build/patch/patchFetchCacheISR.js +102 -0
  25. package/dist/build/patch/patchFetchCacheWaitUntil.d.ts +3 -0
  26. package/dist/build/patch/patchFetchCacheWaitUntil.js +36 -0
  27. package/dist/build.js +1 -1
  28. package/dist/core/requestHandler.js +3 -1
  29. package/dist/core/routing/cacheInterceptor.js +6 -1
  30. package/dist/core/routing/middleware.js +12 -2
  31. package/dist/core/routing/queue.d.ts +1 -0
  32. package/dist/core/routing/queue.js +12 -8
  33. package/dist/core/routing/util.js +3 -3
  34. package/dist/logger.d.ts +8 -0
  35. package/dist/logger.js +2 -0
  36. package/dist/overrides/wrappers/express-dev.js +2 -0
  37. package/dist/overrides/wrappers/node.js +0 -1
  38. package/dist/plugins/content-updater.d.ts +48 -0
  39. package/dist/plugins/content-updater.js +65 -0
  40. package/dist/plugins/edge.d.ts +3 -3
  41. package/dist/plugins/edge.js +18 -8
  42. package/dist/types/next-types.d.ts +3 -0
  43. package/dist/types/overrides.d.ts +2 -0
  44. package/package.json +4 -2
@@ -0,0 +1,58 @@
1
+ import { type Edit, Lang, type NapiConfig, type SgNode } from "@ast-grep/napi";
2
+ import type { PatchCodeFn } from "./codePatcher";
3
+ export type * from "@ast-grep/napi";
4
+ /**
5
+ * fix has the same meaning as in yaml rules
6
+ * see https://ast-grep.github.io/guide/rewrite-code.html#using-fix-in-yaml-rule
7
+ */
8
+ export type RuleConfig = NapiConfig & {
9
+ fix?: string;
10
+ };
11
+ /**
12
+ * Returns the `Edit`s and `Match`es for an ast-grep rule in yaml format
13
+ *
14
+ * The rule must have a `fix` to rewrite the matched node.
15
+ *
16
+ * Tip: use https://ast-grep.github.io/playground.html to create rules.
17
+ *
18
+ * @param rule The rule. Either a yaml string or an instance of `RuleConfig`
19
+ * @param root The root node
20
+ * @param once only apply once
21
+ * @returns A list of edits and a list of matches.
22
+ */
23
+ export declare function applyRule(rule: string | RuleConfig, root: SgNode, { once }?: {
24
+ once?: boolean | undefined;
25
+ }): {
26
+ edits: Edit[];
27
+ matches: SgNode[];
28
+ };
29
+ /**
30
+ * Parse a file and obtain its root.
31
+ *
32
+ * @param path The file path
33
+ * @param lang The language to parse. Defaults to TypeScript.
34
+ * @returns The root for the file.
35
+ */
36
+ export declare function parseFile(path: string, lang?: Lang): SgNode;
37
+ /**
38
+ * Patches the code from by applying the rule.
39
+ *
40
+ * This function is mainly for on off edits and tests,
41
+ * use `getRuleEdits` to apply multiple rules.
42
+ *
43
+ * @param code The source code
44
+ * @param rule The astgrep rule (yaml or NapiConfig)
45
+ * @param lang The language used by the source code
46
+ * @param lang Whether to apply the rule only once
47
+ * @returns The patched code
48
+ */
49
+ export declare function patchCode(code: string, rule: string | RuleConfig, { lang, once }?: {
50
+ lang?: Lang | undefined;
51
+ once?: boolean | undefined;
52
+ }): string;
53
+ /**
54
+ * @param rule
55
+ * @param lang
56
+ * @returns a callback applying the the rule.
57
+ */
58
+ export declare function createPatchCode(rule: string | RuleConfig, lang?: Lang): PatchCodeFn;
@@ -0,0 +1,76 @@
1
+ // Mostly copied from the cloudflare adapter
2
+ import { readFileSync } from "node:fs";
3
+ import { Lang, parse, } from "@ast-grep/napi";
4
+ import yaml from "yaml";
5
+ /**
6
+ * Returns the `Edit`s and `Match`es for an ast-grep rule in yaml format
7
+ *
8
+ * The rule must have a `fix` to rewrite the matched node.
9
+ *
10
+ * Tip: use https://ast-grep.github.io/playground.html to create rules.
11
+ *
12
+ * @param rule The rule. Either a yaml string or an instance of `RuleConfig`
13
+ * @param root The root node
14
+ * @param once only apply once
15
+ * @returns A list of edits and a list of matches.
16
+ */
17
+ export function applyRule(rule, root, { once = false } = {}) {
18
+ const ruleConfig = typeof rule === "string" ? yaml.parse(rule) : rule;
19
+ if (ruleConfig.transform) {
20
+ throw new Error("transform is not supported");
21
+ }
22
+ if (!ruleConfig.fix) {
23
+ throw new Error("no fix to apply");
24
+ }
25
+ const fix = ruleConfig.fix;
26
+ const matches = once
27
+ ? [root.find(ruleConfig)].filter((m) => m !== null)
28
+ : root.findAll(ruleConfig);
29
+ const edits = [];
30
+ matches.forEach((match) => {
31
+ edits.push(match.replace(
32
+ // Replace known placeholders by their value
33
+ fix
34
+ .replace(/\$\$\$([A-Z0-9_]+)/g, (_m, name) => match
35
+ .getMultipleMatches(name)
36
+ .map((n) => n.text())
37
+ .join(""))
38
+ .replace(/\$([A-Z0-9_]+)/g, (m, name) => match.getMatch(name)?.text() ?? m)));
39
+ });
40
+ return { edits, matches };
41
+ }
42
+ /**
43
+ * Parse a file and obtain its root.
44
+ *
45
+ * @param path The file path
46
+ * @param lang The language to parse. Defaults to TypeScript.
47
+ * @returns The root for the file.
48
+ */
49
+ export function parseFile(path, lang = Lang.TypeScript) {
50
+ return parse(lang, readFileSync(path, { encoding: "utf-8" })).root();
51
+ }
52
+ /**
53
+ * Patches the code from by applying the rule.
54
+ *
55
+ * This function is mainly for on off edits and tests,
56
+ * use `getRuleEdits` to apply multiple rules.
57
+ *
58
+ * @param code The source code
59
+ * @param rule The astgrep rule (yaml or NapiConfig)
60
+ * @param lang The language used by the source code
61
+ * @param lang Whether to apply the rule only once
62
+ * @returns The patched code
63
+ */
64
+ export function patchCode(code, rule, { lang = Lang.TypeScript, once = false } = {}) {
65
+ const node = parse(lang, code).root();
66
+ const { edits } = applyRule(rule, node, { once });
67
+ return node.commitEdits(edits);
68
+ }
69
+ /**
70
+ * @param rule
71
+ * @param lang
72
+ * @returns a callback applying the the rule.
73
+ */
74
+ export function createPatchCode(rule, lang = Lang.TypeScript) {
75
+ return async ({ code }) => patchCode(code, rule, { lang });
76
+ }
@@ -0,0 +1,46 @@
1
+ import type { getManifests } from "../copyTracedFiles.js";
2
+ import * as buildHelper from "../helper.js";
3
+ type Versions = `>=${number}.${number}.${number} <=${number}.${number}.${number}` | `>=${number}.${number}.${number}` | `<=${number}.${number}.${number}`;
4
+ export interface VersionedField<T> {
5
+ /**
6
+ * The versions of Next.js that this field should be used for
7
+ * Should be in the format `">=16.0.0 <=17.0.0"` or `">=16.0.0"` or `"<=17.0.0"`
8
+ * **Be careful with spaces**
9
+ */
10
+ versions?: Versions;
11
+ field: T;
12
+ }
13
+ export type PatchCodeFn = (args: {
14
+ /**
15
+ * The code of the file that needs to be patched
16
+ */
17
+ code: string;
18
+ /**
19
+ * The final path of the file that needs to be patched
20
+ */
21
+ filePath: string;
22
+ /**
23
+ * All files that are traced and will be included in the bundle
24
+ */
25
+ tracedFiles: string[];
26
+ /**
27
+ * Next.js manifests that are used by Next at runtime
28
+ */
29
+ manifests: ReturnType<typeof getManifests>;
30
+ }) => Promise<string>;
31
+ interface IndividualPatch {
32
+ pathFilter: RegExp;
33
+ contentFilter?: RegExp;
34
+ patchCode: PatchCodeFn;
35
+ }
36
+ export interface CodePatcher {
37
+ name: string;
38
+ patches: IndividualPatch | VersionedField<IndividualPatch>[];
39
+ }
40
+ export declare function parseVersions(versions?: Versions): {
41
+ before?: string;
42
+ after?: string;
43
+ };
44
+ export declare function extractVersionedField<T>(fields: VersionedField<T>[], version: string): T[];
45
+ export declare function applyCodePatches(buildOptions: buildHelper.BuildOptions, tracedFiles: string[], manifests: ReturnType<typeof getManifests>, codePatcher: CodePatcher[]): Promise<void>;
46
+ export {};
@@ -0,0 +1,119 @@
1
+ import * as fs from "node:fs/promises";
2
+ import logger from "../../logger.js";
3
+ import * as buildHelper from "../helper.js";
4
+ export function parseVersions(versions) {
5
+ if (!versions) {
6
+ return {};
7
+ }
8
+ // We need to use regex to extract the versions
9
+ const versionRegex = /([<>]=)(\d+\.\d+\.\d+)/g;
10
+ const matches = Array.from(versions.matchAll(versionRegex));
11
+ if (matches.length === 0) {
12
+ throw new Error("Invalid version range, no matches found");
13
+ }
14
+ if (matches.length > 2) {
15
+ throw new Error("Invalid version range, too many matches found");
16
+ }
17
+ let after;
18
+ let before;
19
+ for (const match of matches) {
20
+ const [_, operator, version] = match;
21
+ if (operator === "<=") {
22
+ before = version;
23
+ }
24
+ else {
25
+ after = version;
26
+ }
27
+ }
28
+ // Before returning we reconstruct the version string and compare it to the original
29
+ // If they don't match we throw an error
30
+ // We have to do this because template literal types here seems to allow for extra spaces
31
+ // that could easily break the version comparison and allow some patch to be applied on incorrect versions
32
+ // This might even go unnoticed
33
+ const reconstructedVersion = `${after ? `>=${after}` : ""}${before ? `${after ? " " : ""}<=${before}` : ""}`;
34
+ if (reconstructedVersion !== versions) {
35
+ throw new Error("Invalid version range, the reconstructed version does not match the original");
36
+ }
37
+ return {
38
+ before,
39
+ after,
40
+ };
41
+ }
42
+ export function extractVersionedField(fields, version) {
43
+ const result = [];
44
+ for (const field of fields) {
45
+ // No versions specified, the patch always apply
46
+ if (!field.versions) {
47
+ result.push(field.field);
48
+ continue;
49
+ }
50
+ const { before, after } = parseVersions(field.versions);
51
+ // range
52
+ if (before && after) {
53
+ if (buildHelper.compareSemver(version, "<=", before) &&
54
+ buildHelper.compareSemver(version, ">=", after)) {
55
+ result.push(field.field);
56
+ }
57
+ continue;
58
+ }
59
+ // before only
60
+ if (before) {
61
+ if (buildHelper.compareSemver(version, "<=", before)) {
62
+ result.push(field.field);
63
+ }
64
+ continue;
65
+ }
66
+ // after only
67
+ if (after) {
68
+ if (buildHelper.compareSemver(version, ">=", after)) {
69
+ result.push(field.field);
70
+ }
71
+ }
72
+ }
73
+ return result;
74
+ }
75
+ export async function applyCodePatches(buildOptions, tracedFiles, manifests, codePatcher) {
76
+ const nextVersion = buildOptions.nextVersion;
77
+ logger.time("Applying code patches");
78
+ // We first filter against the version
79
+ // We also flatten the array of patches so that we get both the name and all the necessary patches
80
+ const patchesToApply = codePatcher.flatMap(({ name, patches }) => Array.isArray(patches)
81
+ ? extractVersionedField(patches, nextVersion).map((patch) => ({
82
+ name,
83
+ patch,
84
+ }))
85
+ : [{ name, patch: patches }]);
86
+ await Promise.all(tracedFiles.map(async (filePath) => {
87
+ // We check the filename against the filter to see if we should apply the patch
88
+ const patchMatchingPath = patchesToApply.filter(({ patch }) => {
89
+ return filePath.match(patch.pathFilter);
90
+ });
91
+ if (patchMatchingPath.length === 0) {
92
+ return;
93
+ }
94
+ const content = await fs.readFile(filePath, "utf-8");
95
+ // We filter a last time against the content this time
96
+ const patchToApply = patchMatchingPath.filter(({ patch }) => {
97
+ if (!patch.contentFilter) {
98
+ return true;
99
+ }
100
+ return content.match(patch.contentFilter);
101
+ });
102
+ if (patchToApply.length === 0) {
103
+ return;
104
+ }
105
+ // We apply the patches
106
+ let patchedContent = content;
107
+ for (const { patch, name } of patchToApply) {
108
+ logger.debug(`Applying code patch: ${name} to ${filePath}`);
109
+ patchedContent = await patch.patchCode({
110
+ code: patchedContent,
111
+ filePath,
112
+ tracedFiles,
113
+ manifests,
114
+ });
115
+ }
116
+ await fs.writeFile(filePath, patchedContent);
117
+ }));
118
+ logger.timeEnd("Applying code patches");
119
+ }
@@ -0,0 +1,5 @@
1
+ import type { CodePatcher } from "./codePatcher";
2
+ export declare const fetchRule = "\nrule:\n kind: member_expression\n pattern: $WORK_STORE.isOnDemandRevalidate\n inside:\n kind: ternary_expression\n all:\n - has: {kind: 'null'}\n - has: \n kind: await_expression\n has:\n kind: call_expression\n all:\n - has:\n kind: member_expression\n has:\n kind: property_identifier\n field: property\n regex: get\n - has:\n kind: arguments\n has:\n kind: object\n has:\n kind: pair\n all:\n - has:\n kind: property_identifier\n field: key\n regex: softTags\n inside:\n kind: variable_declarator\n\nfix:\n ($WORK_STORE.isOnDemandRevalidate && !globalThis.__openNextAls?.getStore()?.isISRRevalidation)\n";
3
+ export declare const unstable_cacheRule = "\nrule:\n kind: member_expression\n pattern: $STORE_OR_CACHE.isOnDemandRevalidate\n inside:\n kind: if_statement\n stopBy: end\n has:\n kind: statement_block\n has:\n kind: variable_declarator\n has: \n kind: await_expression\n has:\n kind: call_expression\n all:\n - has:\n kind: member_expression\n has:\n kind: property_identifier\n field: property\n regex: get\n - has:\n kind: arguments\n has:\n kind: object\n has:\n kind: pair\n all:\n - has:\n kind: property_identifier\n field: key\n regex: softTags\n stopBy: end\nfix:\n ($STORE_OR_CACHE.isOnDemandRevalidate && !globalThis.__openNextAls?.getStore()?.isISRRevalidation)\n";
4
+ export declare const patchFetchCacheForISR: CodePatcher;
5
+ export declare const patchUnstableCacheForISR: CodePatcher;
@@ -0,0 +1,102 @@
1
+ import { Lang } from "@ast-grep/napi";
2
+ import { getCrossPlatformPathRegex } from "../../utils/regex.js";
3
+ import { createPatchCode } from "./astCodePatcher.js";
4
+ export const fetchRule = `
5
+ rule:
6
+ kind: member_expression
7
+ pattern: $WORK_STORE.isOnDemandRevalidate
8
+ inside:
9
+ kind: ternary_expression
10
+ all:
11
+ - has: {kind: 'null'}
12
+ - has:
13
+ kind: await_expression
14
+ has:
15
+ kind: call_expression
16
+ all:
17
+ - has:
18
+ kind: member_expression
19
+ has:
20
+ kind: property_identifier
21
+ field: property
22
+ regex: get
23
+ - has:
24
+ kind: arguments
25
+ has:
26
+ kind: object
27
+ has:
28
+ kind: pair
29
+ all:
30
+ - has:
31
+ kind: property_identifier
32
+ field: key
33
+ regex: softTags
34
+ inside:
35
+ kind: variable_declarator
36
+
37
+ fix:
38
+ ($WORK_STORE.isOnDemandRevalidate && !globalThis.__openNextAls?.getStore()?.isISRRevalidation)
39
+ `;
40
+ export const unstable_cacheRule = `
41
+ rule:
42
+ kind: member_expression
43
+ pattern: $STORE_OR_CACHE.isOnDemandRevalidate
44
+ inside:
45
+ kind: if_statement
46
+ stopBy: end
47
+ has:
48
+ kind: statement_block
49
+ has:
50
+ kind: variable_declarator
51
+ has:
52
+ kind: await_expression
53
+ has:
54
+ kind: call_expression
55
+ all:
56
+ - has:
57
+ kind: member_expression
58
+ has:
59
+ kind: property_identifier
60
+ field: property
61
+ regex: get
62
+ - has:
63
+ kind: arguments
64
+ has:
65
+ kind: object
66
+ has:
67
+ kind: pair
68
+ all:
69
+ - has:
70
+ kind: property_identifier
71
+ field: key
72
+ regex: softTags
73
+ stopBy: end
74
+ fix:
75
+ ($STORE_OR_CACHE.isOnDemandRevalidate && !globalThis.__openNextAls?.getStore()?.isISRRevalidation)
76
+ `;
77
+ export const patchFetchCacheForISR = {
78
+ name: "patch-fetch-cache-for-isr",
79
+ patches: [
80
+ {
81
+ versions: ">=14.0.0",
82
+ field: {
83
+ pathFilter: getCrossPlatformPathRegex(String.raw `(server/chunks/.*\.js|.*\.runtime\..*\.js|patch-fetch\.js)$`, { escape: false }),
84
+ contentFilter: /\.isOnDemandRevalidate/,
85
+ patchCode: createPatchCode(fetchRule, Lang.JavaScript),
86
+ },
87
+ },
88
+ ],
89
+ };
90
+ export const patchUnstableCacheForISR = {
91
+ name: "patch-unstable-cache-for-isr",
92
+ patches: [
93
+ {
94
+ versions: ">=14.2.0",
95
+ field: {
96
+ pathFilter: getCrossPlatformPathRegex(String.raw `(server/chunks/.*\.js|.*\.runtime\..*\.js|spec-extension/unstable-cache\.js)$`, { escape: false }),
97
+ contentFilter: /\.isOnDemandRevalidate/,
98
+ patchCode: createPatchCode(unstable_cacheRule, Lang.JavaScript),
99
+ },
100
+ },
101
+ ],
102
+ };
@@ -0,0 +1,3 @@
1
+ import type { CodePatcher } from "./codePatcher";
2
+ export declare const rule = "\nrule:\n kind: call_expression\n pattern: $PROMISE\n all:\n - has: { pattern: $_.arrayBuffer().then, stopBy: end }\n - has: { pattern: \"Buffer.from\", stopBy: end }\n - any:\n - inside:\n kind: sequence_expression\n inside:\n kind: return_statement\n - inside:\n kind: expression_statement\n precedes:\n kind: return_statement\n - has: { pattern: $_.FETCH, stopBy: end }\n\nfix: |\n globalThis.__openNextAls?.getStore()?.pendingPromiseRunner.add($PROMISE)\n";
3
+ export declare const patchFetchCacheSetMissingWaitUntil: CodePatcher;
@@ -0,0 +1,36 @@
1
+ import { getCrossPlatformPathRegex } from "../../utils/regex.js";
2
+ import { createPatchCode } from "./astCodePatcher.js";
3
+ export const rule = `
4
+ rule:
5
+ kind: call_expression
6
+ pattern: $PROMISE
7
+ all:
8
+ - has: { pattern: $_.arrayBuffer().then, stopBy: end }
9
+ - has: { pattern: "Buffer.from", stopBy: end }
10
+ - any:
11
+ - inside:
12
+ kind: sequence_expression
13
+ inside:
14
+ kind: return_statement
15
+ - inside:
16
+ kind: expression_statement
17
+ precedes:
18
+ kind: return_statement
19
+ - has: { pattern: $_.FETCH, stopBy: end }
20
+
21
+ fix: |
22
+ globalThis.__openNextAls?.getStore()?.pendingPromiseRunner.add($PROMISE)
23
+ `;
24
+ export const patchFetchCacheSetMissingWaitUntil = {
25
+ name: "patch-fetch-cache-set-missing-wait-until",
26
+ patches: [
27
+ {
28
+ versions: ">=15.0.0",
29
+ field: {
30
+ pathFilter: getCrossPlatformPathRegex(String.raw `(server/chunks/.*\.js|.*\.runtime\..*\.js|patch-fetch\.js)$`, { escape: false }),
31
+ contentFilter: /arrayBuffer\(\)\s*\.then/,
32
+ patchCode: createPatchCode(rule),
33
+ },
34
+ },
35
+ ],
36
+ };
package/dist/build.js CHANGED
@@ -17,7 +17,7 @@ export async function build(openNextConfigPath, nodeExternals) {
17
17
  showWarningOnWindows();
18
18
  const baseDir = process.cwd();
19
19
  const openNextDistDir = url.fileURLToPath(new URL(".", import.meta.url));
20
- const { config, buildDir } = await compileOpenNextConfig(baseDir, openNextConfigPath, nodeExternals);
20
+ const { config, buildDir } = await compileOpenNextConfig(baseDir, openNextConfigPath, { nodeExternals });
21
21
  // Initialize options
22
22
  const options = buildHelper.normalizeOptions(config, openNextDistDir, buildDir);
23
23
  logger.setLevel(options.debug ? "debug" : "info");
@@ -9,7 +9,9 @@ import routingHandler, { INTERNAL_HEADER_INITIAL_URL, INTERNAL_HEADER_RESOLVED_R
9
9
  import { requestHandler, setNextjsPrebundledReact } from "./util";
10
10
  // This is used to identify requests in the cache
11
11
  globalThis.__openNextAls = new AsyncLocalStorage();
12
+ //#override patchAsyncStorage
12
13
  patchAsyncStorage();
14
+ //#endOverride
13
15
  export async function openNextHandler(internalEvent, options) {
14
16
  const initialHeaders = internalEvent.headers;
15
17
  // We run everything in the async local storage context so that it is available in the middleware as well as in NextServer
@@ -94,7 +96,7 @@ export async function openNextHandler(internalEvent, options) {
94
96
  resolvedRoutes: [],
95
97
  origin: false,
96
98
  initialURL: internalEvent.url,
97
- }, headers, options.streamCreator);
99
+ }, routingResult.headers, options.streamCreator);
98
100
  response.statusCode = routingResult.statusCode;
99
101
  response.flushHeaders();
100
102
  const [bodyToConsume, bodyToReturn] = routingResult.body.tee();
@@ -48,7 +48,12 @@ async function computeCacheControl(path, body, host, revalidate, lastModified) {
48
48
  url = `${NextConfig.basePath}${url}`;
49
49
  }
50
50
  await globalThis.queue.send({
51
- MessageBody: { host, url },
51
+ MessageBody: {
52
+ host,
53
+ url,
54
+ eTag: etag,
55
+ lastModified: lastModified ?? Date.now(),
56
+ },
52
57
  MessageDeduplicationId: hash(`${path}-${lastModified}-${etag}`),
53
58
  MessageGroupId: generateMessageGroupId(path),
54
59
  });
@@ -1,4 +1,4 @@
1
- import { FunctionsConfigManifest, MiddlewareManifest, NextConfig, } from "../../adapters/config/index.js";
1
+ import { FunctionsConfigManifest, MiddlewareManifest, NextConfig, PrerenderManifest, } from "../../adapters/config/index.js";
2
2
  import { emptyReadableStream } from "../../utils/stream.js";
3
3
  import { localizePath } from "./i18n/index.js";
4
4
  //NOTE: we should try to avoid importing stuff from next as much as possible
@@ -24,7 +24,11 @@ function defaultMiddlewareLoader() {
24
24
  export async function handleMiddleware(internalEvent, middlewareLoader = defaultMiddlewareLoader) {
25
25
  const headers = internalEvent.headers;
26
26
  // We bypass the middleware if the request is internal
27
- if (headers["x-isr"])
27
+ // We should only do that if the request has the correct `x-prerender-revalidate` header
28
+ // The `x-prerender-revalidate` header is set at build time and should be safe to trust
29
+ if (headers["x-isr"] &&
30
+ headers["x-prerender-revalidate"] ===
31
+ PrerenderManifest.preview.previewModeId)
28
32
  return internalEvent;
29
33
  // We only need the normalizedPath to check if the middleware should run
30
34
  const normalizedPath = localizePath(internalEvent);
@@ -73,6 +77,12 @@ export async function handleMiddleware(internalEvent, middlewareLoader = default
73
77
  const reqHeaders = {};
74
78
  const resHeaders = {};
75
79
  responseHeaders.delete("x-middleware-override-headers");
80
+ /* Next will set the header `x-middleware-set-cookie` when you `set-cookie` in the middleware.
81
+ * We can delete it here since it will be set in `set-cookie` aswell. Next removes this header in the response themselves.
82
+ * `x-middleware-next` is set when you invoke `NextResponse.next()`. We can delete it here aswell.
83
+ */
84
+ responseHeaders.delete("x-middleware-set-cookie");
85
+ responseHeaders.delete("x-middleware-next");
76
86
  const xMiddlewareKey = "x-middleware-request-";
77
87
  responseHeaders.forEach((value, key) => {
78
88
  if (key.startsWith(xMiddlewareKey)) {
@@ -1 +1,2 @@
1
+ export declare function generateShardId(rawPath: string, maxConcurrency: number, prefix: string): string;
1
2
  export declare function generateMessageGroupId(rawPath: string): string;
@@ -1,3 +1,14 @@
1
+ export function generateShardId(rawPath, maxConcurrency, prefix) {
2
+ let a = cyrb128(rawPath);
3
+ // We use mulberry32 to generate a random int between 0 and MAX_REVALIDATE_CONCURRENCY
4
+ let t = (a += 0x6d2b79f5);
5
+ t = Math.imul(t ^ (t >>> 15), t | 1);
6
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
7
+ const randomFloat = ((t ^ (t >>> 14)) >>> 0) / 4294967296;
8
+ // This will generate a random int between 0 and maxConcurrency
9
+ const randomInt = Math.floor(randomFloat * maxConcurrency);
10
+ return `${prefix}-${randomInt}`;
11
+ }
1
12
  // Since we're using a FIFO queue, every messageGroupId is treated sequentially
2
13
  // This could cause a backlog of messages in the queue if there is too much page to
3
14
  // revalidate at once. To avoid this, we generate a random messageGroupId for each
@@ -6,17 +17,10 @@
6
17
  // will always have the same messageGroupId.
7
18
  // https://stackoverflow.com/questions/521295/seeding-the-random-number-generator-in-javascript#answer-47593316
8
19
  export function generateMessageGroupId(rawPath) {
9
- let a = cyrb128(rawPath);
10
- // We use mulberry32 to generate a random int between 0 and MAX_REVALIDATE_CONCURRENCY
11
- let t = (a += 0x6d2b79f5);
12
- t = Math.imul(t ^ (t >>> 15), t | 1);
13
- t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
14
- const randomFloat = ((t ^ (t >>> 14)) >>> 0) / 4294967296;
15
20
  // This will generate a random int between 0 and MAX_REVALIDATE_CONCURRENCY
16
21
  // This means that we could have 1000 revalidate request at the same time
17
22
  const maxConcurrency = Number.parseInt(process.env.MAX_REVALIDATE_CONCURRENCY ?? "10");
18
- const randomInt = Math.floor(randomFloat * maxConcurrency);
19
- return `revalidate-${randomInt}`;
23
+ return generateShardId(rawPath, maxConcurrency, "revalidate");
20
24
  }
21
25
  // Used to generate a hash int from a string
22
26
  function cyrb128(str) {
@@ -268,10 +268,10 @@ export async function revalidateIfRequired(host, rawPath, headers, req) {
268
268
  const lastModified = globalThis.__openNextAls.getStore()?.lastModified ?? 0;
269
269
  // For some weird cases, lastModified is not set, haven't been able to figure out yet why
270
270
  // For those cases we add the etag to the deduplication id, it might help
271
- const etag = headers.etag ?? headers.ETag ?? "";
271
+ const eTag = `${headers.etag ?? headers.ETag ?? ""}`;
272
272
  await globalThis.queue.send({
273
- MessageBody: { host, url: revalidateUrl },
274
- MessageDeduplicationId: hash(`${rawPath}-${lastModified}-${etag}`),
273
+ MessageBody: { host, url: revalidateUrl, eTag, lastModified },
274
+ MessageDeduplicationId: hash(`${rawPath}-${lastModified}-${eTag}`),
275
275
  MessageGroupId: generateMessageGroupId(rawPath),
276
276
  });
277
277
  }
package/dist/logger.d.ts CHANGED
@@ -8,5 +8,13 @@ declare const _default: {
8
8
  };
9
9
  warn: (...args: any[]) => void;
10
10
  error: (...args: any[]) => void;
11
+ time: {
12
+ (label?: string): void;
13
+ (label?: string): void;
14
+ };
15
+ timeEnd: {
16
+ (label?: string): void;
17
+ (label?: string): void;
18
+ };
11
19
  };
12
20
  export default _default;
package/dist/logger.js CHANGED
@@ -10,4 +10,6 @@ export default {
10
10
  info: console.log,
11
11
  warn: (...args) => console.warn(chalk.yellow("WARN"), ...args),
12
12
  error: (...args) => console.error(chalk.red("ERROR"), ...args),
13
+ time: console.time,
14
+ timeEnd: console.timeEnd,
13
15
  };
@@ -19,7 +19,9 @@ const wrapper = async (handler, converter) => {
19
19
  const internalEvent = await converter.convertFrom(req);
20
20
  const streamCreator = {
21
21
  writeHeaders: (prelude) => {
22
+ res.setHeader("Set-Cookie", prelude.cookies);
22
23
  res.writeHead(prelude.statusCode, prelude.headers);
24
+ res.flushHeaders();
23
25
  return res;
24
26
  },
25
27
  onFinish: () => { },
@@ -8,7 +8,6 @@ const wrapper = async (handler, converter) => {
8
8
  res.setHeader("Set-Cookie", prelude.cookies);
9
9
  res.writeHead(prelude.statusCode, prelude.headers);
10
10
  res.flushHeaders();
11
- res.uncork();
12
11
  return res;
13
12
  },
14
13
  };