@netlify/plugin-nextjs 5.14.0 → 5.14.2

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.
@@ -1385,7 +1385,7 @@ import {
1385
1385
  isAnyTagStaleOrExpired,
1386
1386
  markTagsAsStaleAndPurgeEdgeCache
1387
1387
  } from "./tags-handler.cjs";
1388
- import { getTracer } from "./tracer.cjs";
1388
+ import { getTracer, withActiveSpan } from "./tracer.cjs";
1389
1389
  var LRU_CACHE_GLOBAL_KEY = Symbol.for("nf-use-cache-handler-lru-cache");
1390
1390
  var PENDING_SETS_GLOBAL_KEY = Symbol.for("nf-use-cache-handler-pending-sets");
1391
1391
  var cacheHandlersSymbol = Symbol.for("@next/cache-handlers");
@@ -1415,11 +1415,12 @@ var tmpResolvePendingBeforeCreatingAPromise = () => {
1415
1415
  };
1416
1416
  var NetlifyDefaultUseCacheHandler = {
1417
1417
  get(cacheKey) {
1418
- return getTracer().withActiveSpan(
1418
+ return withActiveSpan(
1419
+ getTracer(),
1419
1420
  "DefaultUseCacheHandler.get",
1420
1421
  async (span) => {
1421
1422
  getLogger().withFields({ cacheKey }).debug(`[NetlifyDefaultUseCacheHandler] get`);
1422
- span.setAttributes({
1423
+ span?.setAttributes({
1423
1424
  cacheKey
1424
1425
  });
1425
1426
  const pendingPromise = getPendingSets().get(cacheKey);
@@ -1429,7 +1430,7 @@ var NetlifyDefaultUseCacheHandler = {
1429
1430
  const privateEntry = getLRUCache().get(cacheKey);
1430
1431
  if (!privateEntry) {
1431
1432
  getLogger().withFields({ cacheKey, status: "MISS" }).debug(`[NetlifyDefaultUseCacheHandler] get result`);
1432
- span.setAttributes({
1433
+ span?.setAttributes({
1433
1434
  cacheStatus: "miss"
1434
1435
  });
1435
1436
  return void 0;
@@ -1438,41 +1439,47 @@ var NetlifyDefaultUseCacheHandler = {
1438
1439
  const ttl = (entry.timestamp + entry.revalidate * 1e3 - Date.now()) / 1e3;
1439
1440
  if (ttl < 0) {
1440
1441
  getLogger().withFields({ cacheKey, ttl, status: "STALE" }).debug(`[NetlifyDefaultUseCacheHandler] get result`);
1441
- span.setAttributes({
1442
+ span?.setAttributes({
1442
1443
  cacheStatus: "expired, discarded",
1443
1444
  ttl
1444
1445
  });
1445
1446
  return void 0;
1446
1447
  }
1447
- const { stale } = await isAnyTagStaleOrExpired(entry.tags, entry.timestamp);
1448
- if (stale) {
1449
- getLogger().withFields({ cacheKey, ttl, status: "STALE BY TAG" }).debug(`[NetlifyDefaultUseCacheHandler] get result`);
1450
- span.setAttributes({
1451
- cacheStatus: "stale tag, discarded",
1448
+ const { stale, expired } = await isAnyTagStaleOrExpired(entry.tags, entry.timestamp);
1449
+ if (expired) {
1450
+ getLogger().withFields({ cacheKey, ttl, status: "EXPIRED BY TAG" }).debug(`[NetlifyDefaultUseCacheHandler] get result`);
1451
+ span?.setAttributes({
1452
+ cacheStatus: "expired tag, discarded",
1452
1453
  ttl
1453
1454
  });
1454
1455
  return void 0;
1455
1456
  }
1456
- const [returnStream, newSaved] = entry.value.tee();
1457
+ let { revalidate, value } = entry;
1458
+ if (stale) {
1459
+ revalidate = -1;
1460
+ }
1461
+ const [returnStream, newSaved] = value.tee();
1457
1462
  entry.value = newSaved;
1458
- getLogger().withFields({ cacheKey, ttl, status: "HIT" }).debug(`[NetlifyDefaultUseCacheHandler] get result`);
1459
- span.setAttributes({
1460
- cacheStatus: "hit",
1463
+ getLogger().withFields({ cacheKey, ttl, status: stale ? "STALE" : "HIT" }).debug(`[NetlifyDefaultUseCacheHandler] get result`);
1464
+ span?.setAttributes({
1465
+ cacheStatus: stale ? "stale" : "hit",
1461
1466
  ttl
1462
1467
  });
1463
1468
  return {
1464
1469
  ...entry,
1470
+ revalidate,
1465
1471
  value: returnStream
1466
1472
  };
1467
1473
  }
1468
1474
  );
1469
1475
  },
1470
1476
  set(cacheKey, pendingEntry) {
1471
- return getTracer().withActiveSpan(
1477
+ return withActiveSpan(
1478
+ getTracer(),
1472
1479
  "DefaultUseCacheHandler.set",
1473
1480
  async (span) => {
1474
1481
  getLogger().withFields({ cacheKey }).debug(`[NetlifyDefaultUseCacheHandler]: set`);
1475
- span.setAttributes({
1482
+ span?.setAttributes({
1476
1483
  cacheKey
1477
1484
  });
1478
1485
  let resolvePending = tmpResolvePendingBeforeCreatingAPromise;
@@ -1482,7 +1489,7 @@ var NetlifyDefaultUseCacheHandler = {
1482
1489
  const pendingSets = getPendingSets();
1483
1490
  pendingSets.set(cacheKey, pendingPromise);
1484
1491
  const entry = await pendingEntry;
1485
- span.setAttributes({
1492
+ span?.setAttributes({
1486
1493
  cacheKey
1487
1494
  });
1488
1495
  let size = 0;
@@ -1493,7 +1500,7 @@ var NetlifyDefaultUseCacheHandler = {
1493
1500
  for (let chunk; !(chunk = await reader.read()).done; ) {
1494
1501
  size += Buffer.from(chunk.value).byteLength;
1495
1502
  }
1496
- span.setAttributes({
1503
+ span?.setAttributes({
1497
1504
  tags: entry.tags,
1498
1505
  timestamp: entry.timestamp,
1499
1506
  revalidate: entry.revalidate,
@@ -1514,33 +1521,52 @@ var NetlifyDefaultUseCacheHandler = {
1514
1521
  },
1515
1522
  async refreshTags() {
1516
1523
  },
1517
- getExpiration: function(...tags) {
1518
- return getTracer().withActiveSpan(
1524
+ getExpiration: function(...notNormalizedTags) {
1525
+ return withActiveSpan(
1526
+ getTracer(),
1519
1527
  "DefaultUseCacheHandler.getExpiration",
1520
1528
  async (span) => {
1521
- span.setAttributes({
1529
+ const tags = notNormalizedTags.flat();
1530
+ span?.setAttributes({
1522
1531
  tags
1523
1532
  });
1524
1533
  const expiration = await getMostRecentTagExpirationTimestamp(tags);
1525
1534
  getLogger().withFields({ tags, expiration }).debug(`[NetlifyDefaultUseCacheHandler] getExpiration`);
1526
- span.setAttributes({
1535
+ span?.setAttributes({
1527
1536
  expiration
1528
1537
  });
1529
1538
  return expiration;
1530
1539
  }
1531
1540
  );
1532
1541
  },
1542
+ // this is for CacheHandlerV2
1533
1543
  expireTags(...tags) {
1534
- return getTracer().withActiveSpan(
1544
+ return withActiveSpan(
1545
+ getTracer(),
1535
1546
  "DefaultUseCacheHandler.expireTags",
1536
1547
  async (span) => {
1537
1548
  getLogger().withFields({ tags }).debug(`[NetlifyDefaultUseCacheHandler] expireTags`);
1538
- span.setAttributes({
1549
+ span?.setAttributes({
1539
1550
  tags
1540
1551
  });
1541
1552
  await markTagsAsStaleAndPurgeEdgeCache(tags);
1542
1553
  }
1543
1554
  );
1555
+ },
1556
+ // this is for CacheHandlerV3 / Next 16
1557
+ updateTags(tags, durations) {
1558
+ return withActiveSpan(
1559
+ getTracer(),
1560
+ "DefaultUseCacheHandler.updateTags",
1561
+ async (span) => {
1562
+ getLogger().withFields({ tags, durations }).debug(`[NetlifyDefaultUseCacheHandler] updateTags`);
1563
+ span?.setAttributes({
1564
+ tags,
1565
+ durations: JSON.stringify(durations)
1566
+ });
1567
+ await markTagsAsStaleAndPurgeEdgeCache(tags, durations);
1568
+ }
1569
+ );
1544
1570
  }
1545
1571
  };
1546
1572
  function configureUseCacheHandlers() {
package/dist/run/next.cjs CHANGED
@@ -545,8 +545,7 @@ ResponseCache.prototype.get = function get(...getArgs) {
545
545
  async function getMockedRequestHandler(nextConfig, ...args) {
546
546
  const initContext = { initializingServer: true };
547
547
  const initAsyncLocalStorage = new import_node_async_hooks.AsyncLocalStorage();
548
- const tracer = (0, import_tracer.getTracer)();
549
- return tracer.withActiveSpan("mocked request handler", async () => {
548
+ return (0, import_tracer.withActiveSpan)((0, import_tracer.getTracer)(), "mocked request handler", async () => {
550
549
  const ofs = { ...import_promises.default };
551
550
  async function readFileFallbackBlobStore(...fsargs) {
552
551
  const [path, options] = fsargs;
@@ -25,40 +25,12 @@ __export(regional_blob_store_exports, {
25
25
  });
26
26
  module.exports = __toCommonJS(regional_blob_store_exports);
27
27
 
28
- // node_modules/@netlify/blobs/dist/chunk-XR3MUBBK.js
29
- var NF_ERROR = "x-nf-error";
30
- var NF_REQUEST_ID = "x-nf-request-id";
31
- var BlobsInternalError = class extends Error {
32
- constructor(res) {
33
- let details = res.headers.get(NF_ERROR) || `${res.status} status code`;
34
- if (res.headers.has(NF_REQUEST_ID)) {
35
- details += `, ID: ${res.headers.get(NF_REQUEST_ID)}`;
36
- }
37
- super(`Netlify Blobs has generated an internal error (${details})`);
38
- this.name = "BlobsInternalError";
39
- }
40
- };
41
- var collectIterator = async (iterator) => {
42
- const result = [];
43
- for await (const item of iterator) {
44
- result.push(item);
45
- }
46
- return result;
47
- };
48
- var base64Decode = (input) => {
49
- const { Buffer: Buffer2 } = globalThis;
50
- if (Buffer2) {
51
- return Buffer2.from(input, "base64").toString();
52
- }
53
- return atob(input);
54
- };
55
- var base64Encode = (input) => {
56
- const { Buffer: Buffer2 } = globalThis;
57
- if (Buffer2) {
58
- return Buffer2.from(input).toString("base64");
59
- }
60
- return btoa(input);
61
- };
28
+ // node_modules/@netlify/blobs/node_modules/@netlify/runtime-utils/dist/main.js
29
+ var getString = (input) => typeof input === "string" ? input : JSON.stringify(input);
30
+ var base64Decode = globalThis.Buffer ? (input) => Buffer.from(input, "base64").toString() : (input) => atob(input);
31
+ var base64Encode = globalThis.Buffer ? (input) => Buffer.from(getString(input)).toString("base64") : (input) => btoa(getString(input));
32
+
33
+ // node_modules/@netlify/blobs/dist/chunk-HN33TXZT.js
62
34
  var getEnvironment = () => {
63
35
  const { Deno, Netlify, process: process2 } = globalThis;
64
36
  return Netlify?.env ?? Deno?.env ?? {
@@ -111,7 +83,7 @@ var encodeMetadata = (metadata) => {
111
83
  return payload;
112
84
  };
113
85
  var decodeMetadata = (header) => {
114
- if (!header || !header.startsWith(BASE64_PREFIX)) {
86
+ if (!header?.startsWith(BASE64_PREFIX)) {
115
87
  return {};
116
88
  }
117
89
  const encodedData = header.slice(BASE64_PREFIX.length);
@@ -132,6 +104,25 @@ var getMetadataFromResponse = (response) => {
132
104
  );
133
105
  }
134
106
  };
107
+ var NF_ERROR = "x-nf-error";
108
+ var NF_REQUEST_ID = "x-nf-request-id";
109
+ var BlobsInternalError = class extends Error {
110
+ constructor(res) {
111
+ let details = res.headers.get(NF_ERROR) || `${res.status} status code`;
112
+ if (res.headers.has(NF_REQUEST_ID)) {
113
+ details += `, ID: ${res.headers.get(NF_REQUEST_ID)}`;
114
+ }
115
+ super(`Netlify Blobs has generated an internal error (${details})`);
116
+ this.name = "BlobsInternalError";
117
+ }
118
+ };
119
+ var collectIterator = async (iterator) => {
120
+ const result = [];
121
+ for await (const item of iterator) {
122
+ result.push(item);
123
+ }
124
+ return result;
125
+ };
135
126
  var BlobsConsistencyError = class extends Error {
136
127
  constructor() {
137
128
  super(
@@ -283,6 +274,7 @@ var Client = class {
283
274
  }
284
275
  async makeRequest({
285
276
  body,
277
+ conditions = {},
286
278
  consistency,
287
279
  headers: extraHeaders,
288
280
  key,
@@ -306,6 +298,11 @@ var Client = class {
306
298
  if (method === "put") {
307
299
  headers["cache-control"] = "max-age=0, stale-while-revalidate=60";
308
300
  }
301
+ if ("onlyIfMatch" in conditions && conditions.onlyIfMatch) {
302
+ headers["if-match"] = conditions.onlyIfMatch;
303
+ } else if ("onlyIfNew" in conditions && conditions.onlyIfNew) {
304
+ headers["if-none-match"] = "*";
305
+ }
309
306
  const options = {
310
307
  body,
311
308
  headers,
@@ -344,6 +341,8 @@ var getClientOptions = (options, contextOverride) => {
344
341
  var DEPLOY_STORE_PREFIX = "deploy:";
345
342
  var LEGACY_STORE_INTERNAL_PREFIX = "netlify-internal/legacy-namespace/";
346
343
  var SITE_STORE_PREFIX = "site:";
344
+ var STATUS_OK = 200;
345
+ var STATUS_PRE_CONDITION_FAILED = 412;
347
346
  var Store = class _Store {
348
347
  constructor(options) {
349
348
  this.client = options.client;
@@ -468,36 +467,56 @@ var Store = class _Store {
468
467
  )
469
468
  );
470
469
  }
471
- async set(key, data, { metadata } = {}) {
470
+ async set(key, data, options = {}) {
472
471
  _Store.validateKey(key);
472
+ const conditions = _Store.getConditions(options);
473
473
  const res = await this.client.makeRequest({
474
+ conditions,
474
475
  body: data,
475
476
  key,
476
- metadata,
477
+ metadata: options.metadata,
477
478
  method: "put",
478
479
  storeName: this.name
479
480
  });
480
- if (res.status !== 200) {
481
- throw new BlobsInternalError(res);
481
+ const etag = res.headers.get("etag") ?? "";
482
+ if (conditions) {
483
+ return res.status === STATUS_PRE_CONDITION_FAILED ? { modified: false } : { etag, modified: true };
484
+ }
485
+ if (res.status === STATUS_OK) {
486
+ return {
487
+ etag,
488
+ modified: true
489
+ };
482
490
  }
491
+ throw new BlobsInternalError(res);
483
492
  }
484
- async setJSON(key, data, { metadata } = {}) {
493
+ async setJSON(key, data, options = {}) {
485
494
  _Store.validateKey(key);
495
+ const conditions = _Store.getConditions(options);
486
496
  const payload = JSON.stringify(data);
487
497
  const headers = {
488
498
  "content-type": "application/json"
489
499
  };
490
500
  const res = await this.client.makeRequest({
501
+ ...conditions,
491
502
  body: payload,
492
503
  headers,
493
504
  key,
494
- metadata,
505
+ metadata: options.metadata,
495
506
  method: "put",
496
507
  storeName: this.name
497
508
  });
498
- if (res.status !== 200) {
499
- throw new BlobsInternalError(res);
509
+ const etag = res.headers.get("etag") ?? "";
510
+ if (conditions) {
511
+ return res.status === STATUS_PRE_CONDITION_FAILED ? { modified: false } : { etag, modified: true };
500
512
  }
513
+ if (res.status === STATUS_OK) {
514
+ return {
515
+ etag,
516
+ modified: true
517
+ };
518
+ }
519
+ throw new BlobsInternalError(res);
501
520
  }
502
521
  static formatListResultBlob(result) {
503
522
  if (!result.key) {
@@ -508,6 +527,31 @@ var Store = class _Store {
508
527
  key: result.key
509
528
  };
510
529
  }
530
+ static getConditions(options) {
531
+ if ("onlyIfMatch" in options && "onlyIfNew" in options) {
532
+ throw new Error(
533
+ `The 'onlyIfMatch' and 'onlyIfNew' options are mutually exclusive. Using 'onlyIfMatch' will make the write succeed only if there is an entry for the key with the given content, while 'onlyIfNew' will make the write succeed only if there is no entry for the key.`
534
+ );
535
+ }
536
+ if ("onlyIfMatch" in options && options.onlyIfMatch) {
537
+ if (typeof options.onlyIfMatch !== "string") {
538
+ throw new Error(`The 'onlyIfMatch' property expects a string representing an ETag.`);
539
+ }
540
+ return {
541
+ onlyIfMatch: options.onlyIfMatch
542
+ };
543
+ }
544
+ if ("onlyIfNew" in options && options.onlyIfNew) {
545
+ if (typeof options.onlyIfNew !== "boolean") {
546
+ throw new Error(
547
+ `The 'onlyIfNew' property expects a boolean indicating whether the write should fail if an entry for the key already exists.`
548
+ );
549
+ }
550
+ return {
551
+ onlyIfNew: true
552
+ };
553
+ }
554
+ }
511
555
  static validateKey(key) {
512
556
  if (key === "") {
513
557
  throw new Error("Blob key must not be empty.");
@@ -55,11 +55,11 @@ var getMemoizedKeyValueStoreBackedByRegionalBlobStore = (...args) => {
55
55
  return memoizedValue;
56
56
  }
57
57
  const blobKey = await encodeBlobKey(key);
58
- const getPromise = tracer.withActiveSpan(otelSpanTitle, async (span) => {
59
- span.setAttributes({ key, blobKey });
58
+ const getPromise = (0, import_tracer.withActiveSpan)(tracer, otelSpanTitle, async (span) => {
59
+ span?.setAttributes({ key, blobKey });
60
60
  const blob = await store.get(blobKey, { type: "json" });
61
61
  inMemoryCache.set(key, blob);
62
- span.addEvent(blob ? "Hit" : "Miss");
62
+ span?.addEvent(blob ? "Hit" : "Miss");
63
63
  return blob;
64
64
  });
65
65
  inMemoryCache.set(key, getPromise);
@@ -69,8 +69,8 @@ var getMemoizedKeyValueStoreBackedByRegionalBlobStore = (...args) => {
69
69
  const inMemoryCache = (0, import_request_scoped_in_memory_cache.getRequestScopedInMemoryCache)();
70
70
  inMemoryCache.set(key, value);
71
71
  const blobKey = await encodeBlobKey(key);
72
- return tracer.withActiveSpan(otelSpanTitle, async (span) => {
73
- span.setAttributes({ key, blobKey });
72
+ return (0, import_tracer.withActiveSpan)(tracer, otelSpanTitle, async (span) => {
73
+ span?.setAttributes({ key, blobKey });
74
74
  return await store.setJSON(blobKey, value);
75
75
  });
76
76
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/plugin-nextjs",
3
- "version": "5.14.0",
3
+ "version": "5.14.2",
4
4
  "description": "Run Next.js seamlessly on Netlify",
5
5
  "main": "./dist/index.js",
6
6
  "type": "module",