@nexushub/client 0.7.7 → 0.7.9

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.
package/dist/index.js CHANGED
@@ -890,7 +890,7 @@ var ContentEngine = class {
890
890
  });
891
891
  this.requestBatcher = new RequestBatcher(20, 50);
892
892
  this.circuitBreaker = new CircuitBreaker();
893
- this.defaultRevalidate = config.revalidateTime || false;
893
+ this.defaultRevalidate = config.revalidateTime ?? false;
894
894
  this.cacheStrategy = config.cacheStrategy || "memory";
895
895
  if (!this.isServer) {
896
896
  window.addEventListener("beforeunload", this.cleanup.bind(this));
@@ -1094,9 +1094,16 @@ var ContentEngine = class {
1094
1094
  await this.rateLimiter.checkLimit();
1095
1095
  const url = `${this.config.apiUrl}/content/${this.config.projectId}/globals`;
1096
1096
  const params = include.length > 0 ? `?include=${include.join(",")}` : "";
1097
+ const fetchTags = [
1098
+ CacheTags.project(this.config.projectId),
1099
+ CacheTags.global,
1100
+ ...options.tags || []
1101
+ ];
1097
1102
  const res = await this.fetchWithTimeout(`${url}${params}`, {
1098
1103
  method: "GET",
1099
- headers: this.getHeaders()
1104
+ headers: this.getHeaders(),
1105
+ tags: fetchTags,
1106
+ revalidate
1100
1107
  });
1101
1108
  if (!res.ok) {
1102
1109
  throw new Error(`API Error ${res.status}: ${res.statusText}`);
@@ -1137,9 +1144,20 @@ var ContentEngine = class {
1137
1144
  params.append("include", options.include.join(","));
1138
1145
  }
1139
1146
  const url = `${this.config.apiUrl}/content/${this.config.projectId}/collection/${collectionId}/${itemId}?${params}`;
1147
+ const fetchTags = [
1148
+ CacheTags.project(this.config.projectId),
1149
+ CacheTags.collection(collectionId),
1150
+ `item_${itemId}`,
1151
+ ...options.tags || []
1152
+ ];
1140
1153
  const res = await this.fetchWithTimeout(url, {
1141
1154
  method: "GET",
1142
- headers: this.getHeaders()
1155
+ headers: this.getHeaders(),
1156
+ tags: fetchTags,
1157
+ // ?? not || — see the constructor comment on defaultRevalidate for
1158
+ // why: an explicit `revalidate: 0` on this call must not be
1159
+ // discarded in favor of the engine's default.
1160
+ revalidate: options.revalidate ?? this.defaultRevalidate
1143
1161
  });
1144
1162
  if (!res.ok) {
1145
1163
  throw new Error(`API Error ${res.status}: ${res.statusText}`);
@@ -1147,13 +1165,8 @@ var ContentEngine = class {
1147
1165
  const json = await res.json();
1148
1166
  const data = json.data || json;
1149
1167
  this.writeCache(cacheKey, data, {
1150
- revalidate: options.revalidate || this.defaultRevalidate,
1151
- tags: [
1152
- CacheTags.project(this.config.projectId),
1153
- CacheTags.collection(collectionId),
1154
- `item_${itemId}`,
1155
- ...options.tags || []
1156
- ]
1168
+ revalidate: options.revalidate ?? this.defaultRevalidate,
1169
+ tags: fetchTags
1157
1170
  });
1158
1171
  return data;
1159
1172
  });
@@ -1196,19 +1209,49 @@ var ContentEngine = class {
1196
1209
  }
1197
1210
  /**
1198
1211
  * Robust Real-time Subscriptions (SSE) with Auto-Reconnect
1212
+ *
1213
+ * ⚠️ SCOPE — READ BEFORE RELYING ON THIS FOR "instant" UPDATES:
1214
+ * This connects from the BROWSER TAB it's called in, and on message it
1215
+ * only clears THIS SDK INSTANCE's in-memory `memoryCache` (via
1216
+ * invalidateCache below) in THAT browser tab's JS heap. In a typical
1217
+ * Next.js deployment — and Cloudflare specifically, which is stateless
1218
+ * per-request at the edge — that is a different process/isolate than the
1219
+ * one that will render the NEXT server request for this content. So:
1220
+ * - ✅ Useful for: a client component that reads from `nexus.content`
1221
+ * directly in the browser and re-renders in place without a page
1222
+ * navigation (e.g. a live-updating dashboard widget).
1223
+ * - ❌ NOT sufficient for: "user A edits content in the CMS, user B's
1224
+ * already-loaded page updates" via a server-rendered page. That
1225
+ * requires the backend's /api/revalidate webhook (see
1226
+ * content.service.ts `pingNextRevalidateWebhook`) to have actually
1227
+ * cleared the *server's* Data Cache, so the NEXT navigation or
1228
+ * server request picks up fresh data. This SSE channel does not
1229
+ * replace that — it's a complementary, browser-local optimization.
1230
+ * If your symptom was "stale content after editing," fix the webhook
1231
+ * wiring first; treat this method as an enhancement layered on top.
1199
1232
  */
1200
1233
  subscribeToUpdates(callback) {
1201
1234
  if (this.isServer || typeof EventSource === "undefined") {
1202
- console.warn("[NexusHub] EventSource not supported in this environment");
1235
+ if (this.config.debug) {
1236
+ console.warn(
1237
+ "[NexusHub] subscribeToUpdates() called in a non-browser environment (server render or SSR pass) \u2014 this is expected and safely skipped; EventSource only makes sense client-side."
1238
+ );
1239
+ }
1203
1240
  return () => {
1204
1241
  };
1205
1242
  }
1243
+ if (!this.config.apiKey) {
1244
+ console.warn(
1245
+ "[NexusHub] subscribeToUpdates(): no apiKey configured, the SSE connection will likely be rejected by the backend. Set NEXT_PUBLIC_NEXUS_KEY."
1246
+ );
1247
+ }
1206
1248
  let eventSource = null;
1207
1249
  let retryCount = 0;
1208
1250
  let isClosed = false;
1251
+ const MAX_RETRY_DELAY_MS = 3e4;
1209
1252
  const connect = () => {
1210
1253
  if (isClosed) return;
1211
- const url = `${this.config.apiUrl}/content/${this.config.projectId}/updates?key=${this.config.apiKey}`;
1254
+ const url = `${this.config.apiUrl}/content/${this.config.projectId}/updates?key=${this.config.apiKey ?? ""}`;
1212
1255
  eventSource = new EventSource(url);
1213
1256
  eventSource.onopen = () => {
1214
1257
  retryCount = 0;
@@ -1216,27 +1259,40 @@ var ContentEngine = class {
1216
1259
  console.log("[NexusHub] \u{1F7E2} Real-time stream connected");
1217
1260
  };
1218
1261
  eventSource.onmessage = (event) => {
1262
+ let data;
1219
1263
  try {
1220
- const data = JSON.parse(event.data);
1221
- if (data.type === "content.updated" && data.slug) {
1222
- this.invalidateCache([CacheTags.content(data.slug)]);
1223
- }
1224
- if (data.type === "collection.updated") {
1225
- this.invalidateCache([CacheTags.collection(data.collectionId)]);
1226
- }
1227
- callback(data);
1264
+ data = JSON.parse(event.data);
1228
1265
  } catch (e) {
1229
1266
  console.error("[NexusHub] SSE Parse Error", e);
1267
+ return;
1268
+ }
1269
+ if (!data || typeof data.type !== "string") {
1270
+ return;
1271
+ }
1272
+ if (data.type === "content.updated" && data.slug) {
1273
+ this.invalidateCache([CacheTags.content(data.slug)]);
1274
+ }
1275
+ if (data.type === "collection.updated" && data.collectionId) {
1276
+ this.invalidateCache([CacheTags.collection(data.collectionId)]);
1230
1277
  }
1278
+ if (data.type === "schema.updated") {
1279
+ this.clearCache();
1280
+ }
1281
+ callback(data);
1231
1282
  };
1232
1283
  eventSource.onerror = () => {
1233
1284
  eventSource?.close();
1234
1285
  if (isClosed) return;
1235
- const timeout = Math.min(1e3 * Math.pow(2, retryCount), 1e4);
1236
- retryCount++;
1237
- console.warn(
1238
- `[NexusHub] \u{1F534} Stream disconnected. Reconnecting in ${timeout}ms...`
1286
+ const timeout = Math.min(
1287
+ 1e3 * Math.pow(2, retryCount),
1288
+ MAX_RETRY_DELAY_MS
1239
1289
  );
1290
+ retryCount++;
1291
+ if (this.config.debug) {
1292
+ console.warn(
1293
+ `[NexusHub] \u{1F534} Stream disconnected. Reconnecting in ${timeout}ms...`
1294
+ );
1295
+ }
1240
1296
  setTimeout(connect, timeout);
1241
1297
  };
1242
1298
  };
@@ -1247,9 +1303,6 @@ var ContentEngine = class {
1247
1303
  };
1248
1304
  }
1249
1305
  // --- CACHE MANAGEMENT ---
1250
- /**
1251
- * Check all caches in order of speed
1252
- */
1253
1306
  async checkCaches(key, forceRefresh, includeMetadata) {
1254
1307
  if (forceRefresh) return null;
1255
1308
  if (this.cacheStrategy === "memory") {
@@ -1306,9 +1359,6 @@ var ContentEngine = class {
1306
1359
  this.browserCache.set(key, data, numericRevalidate * 1e3);
1307
1360
  }
1308
1361
  }
1309
- /**
1310
- * Invalidate cache by tags
1311
- */
1312
1362
  invalidateCache(tags) {
1313
1363
  this.memoryCache.invalidateByTags(tags);
1314
1364
  if (this.config.debug) {
@@ -1317,9 +1367,6 @@ var ContentEngine = class {
1317
1367
  );
1318
1368
  }
1319
1369
  }
1320
- /**
1321
- * Clear all caches
1322
- */
1323
1370
  clearCache() {
1324
1371
  this.memoryCache.clear();
1325
1372
  if (this.browserCache) {
@@ -1329,9 +1376,6 @@ var ContentEngine = class {
1329
1376
  console.log("[NexusHub] \u{1F9F9} Cleared all caches");
1330
1377
  }
1331
1378
  }
1332
- /**
1333
- * Get cache statistics
1334
- */
1335
1379
  getCacheStats() {
1336
1380
  const stats = {
1337
1381
  memory: this.memoryCache.getStats(),
@@ -1345,14 +1389,16 @@ var ContentEngine = class {
1345
1389
  // --- REQUEST METHODS ---
1346
1390
  async fetchPage(slug, cacheKey, tags, revalidate, forceRefresh) {
1347
1391
  const url = `${this.config.apiUrl}/content/${this.config.projectId}/page/${slug}`;
1348
- if (this.config.debug) {
1349
- console.log(`[NexusHub] \u{1F310} Fetching: ${url}`);
1350
- }
1351
1392
  const expiresAt = revalidate === false ? Infinity : Date.now() + revalidate * 1e3;
1393
+ const fetchTags = [
1394
+ CacheTags.project(this.config.projectId),
1395
+ CacheTags.content(slug),
1396
+ ...tags
1397
+ ];
1352
1398
  const res = await this.fetchWithTimeout(url, {
1353
1399
  method: "GET",
1354
1400
  headers: this.getHeaders(),
1355
- tags,
1401
+ tags: fetchTags,
1356
1402
  revalidate
1357
1403
  });
1358
1404
  if (!res.ok) {
@@ -1371,29 +1417,27 @@ var ContentEngine = class {
1371
1417
  timestamp: Date.now(),
1372
1418
  etag: etag || void 0,
1373
1419
  expiresAt,
1374
- tags: [...tags, CacheTags.content(slug)]
1420
+ tags: fetchTags
1375
1421
  }
1376
1422
  };
1377
1423
  this.writeCache(cacheKey, cacheEntry.data, {
1378
1424
  revalidate,
1379
- tags: [
1380
- CacheTags.project(this.config.projectId),
1381
- CacheTags.content(slug),
1382
- ...tags
1383
- ]
1425
+ tags: fetchTags
1384
1426
  });
1385
1427
  return cacheEntry;
1386
1428
  }
1387
1429
  async fetchCollection(collectionId, query, cacheKey, tags, revalidate) {
1388
1430
  const params = buildQueryString(query);
1389
1431
  const url = `${this.config.apiUrl}/content/${this.config.projectId}/collection/${collectionId}?${params}`;
1390
- if (this.config.debug) {
1391
- console.log(`[NexusHub] \u{1F310} Fetching Collection: ${url}`);
1392
- }
1432
+ const fetchTags = [
1433
+ CacheTags.project(this.config.projectId),
1434
+ CacheTags.collection(collectionId),
1435
+ ...tags
1436
+ ];
1393
1437
  const res = await this.fetchWithTimeout(url, {
1394
1438
  method: "GET",
1395
1439
  headers: this.getHeaders(),
1396
- tags,
1440
+ tags: fetchTags,
1397
1441
  revalidate
1398
1442
  });
1399
1443
  if (!res.ok) {
@@ -1408,16 +1452,12 @@ var ContentEngine = class {
1408
1452
  timestamp: Date.now(),
1409
1453
  etag: etag || void 0,
1410
1454
  expiresAt,
1411
- tags: [...tags, CacheTags.collection(collectionId)]
1455
+ tags: fetchTags
1412
1456
  }
1413
1457
  };
1414
1458
  this.writeCache(cacheKey, cacheEntry.data, {
1415
1459
  revalidate,
1416
- tags: [
1417
- CacheTags.project(this.config.projectId),
1418
- CacheTags.collection(collectionId),
1419
- ...tags
1420
- ]
1460
+ tags: fetchTags
1421
1461
  });
1422
1462
  return cacheEntry;
1423
1463
  }
@@ -1477,8 +1517,11 @@ var ContentEngine = class {
1477
1517
  const controller = new AbortController();
1478
1518
  const id = setTimeout(() => controller.abort(), timeout);
1479
1519
  const nextConfig = this.isServer ? {
1480
- cache: "force-cache",
1481
- next: { tags, revalidate: revalidate === false ? false : revalidate }
1520
+ cache: revalidate === 0 ? "no-store" : "force-cache",
1521
+ next: {
1522
+ tags,
1523
+ revalidate: revalidate === false ? false : revalidate
1524
+ }
1482
1525
  } : {};
1483
1526
  try {
1484
1527
  const response = await fetch(url, {
@@ -2501,13 +2544,20 @@ function getSelector(el, depth = 0) {
2501
2544
  }
2502
2545
 
2503
2546
  // src/client.ts
2547
+ var DEFAULT_REVALIDATE_SECONDS = 60;
2504
2548
  var NexusClient = class {
2505
2549
  constructor(config) {
2506
2550
  const fullConfig = getFullConfig(config);
2507
2551
  this.config = {
2508
2552
  debug: config?.debug ?? false,
2509
2553
  cacheStrategy: config?.cacheStrategy ?? "memory",
2510
- revalidateTime: config?.revalidateTime ?? false,
2554
+ // Only fall through to the safe default when revalidateTime is
2555
+ // genuinely *unset* (undefined). An explicit `false` from the caller
2556
+ // is a deliberate "never revalidate" choice and must be respected,
2557
+ // not silently upgraded — `??` (not `||`) is required here so that
2558
+ // `0` (revalidate on every request) also passes through untouched
2559
+ // instead of being treated as falsy.
2560
+ revalidateTime: config?.revalidateTime ?? DEFAULT_REVALIDATE_SECONDS,
2511
2561
  timeout: config?.timeout ?? 1e4,
2512
2562
  retries: config?.retries ?? 3,
2513
2563
  ...fullConfig
package/dist/react.cjs CHANGED
@@ -535,7 +535,7 @@ var ContentEngine = class {
535
535
  });
536
536
  this.requestBatcher = new RequestBatcher(20, 50);
537
537
  this.circuitBreaker = new CircuitBreaker();
538
- this.defaultRevalidate = config.revalidateTime || false;
538
+ this.defaultRevalidate = _nullishCoalesce(config.revalidateTime, () => ( false));
539
539
  this.cacheStrategy = config.cacheStrategy || "memory";
540
540
  if (!this.isServer) {
541
541
  window.addEventListener("beforeunload", this.cleanup.bind(this));
@@ -739,9 +739,16 @@ var ContentEngine = class {
739
739
  await this.rateLimiter.checkLimit();
740
740
  const url = `${this.config.apiUrl}/content/${this.config.projectId}/globals`;
741
741
  const params = include.length > 0 ? `?include=${include.join(",")}` : "";
742
+ const fetchTags = [
743
+ CacheTags.project(this.config.projectId),
744
+ CacheTags.global,
745
+ ...options.tags || []
746
+ ];
742
747
  const res = await this.fetchWithTimeout(`${url}${params}`, {
743
748
  method: "GET",
744
- headers: this.getHeaders()
749
+ headers: this.getHeaders(),
750
+ tags: fetchTags,
751
+ revalidate
745
752
  });
746
753
  if (!res.ok) {
747
754
  throw new Error(`API Error ${res.status}: ${res.statusText}`);
@@ -782,9 +789,20 @@ var ContentEngine = class {
782
789
  params.append("include", options.include.join(","));
783
790
  }
784
791
  const url = `${this.config.apiUrl}/content/${this.config.projectId}/collection/${collectionId}/${itemId}?${params}`;
792
+ const fetchTags = [
793
+ CacheTags.project(this.config.projectId),
794
+ CacheTags.collection(collectionId),
795
+ `item_${itemId}`,
796
+ ...options.tags || []
797
+ ];
785
798
  const res = await this.fetchWithTimeout(url, {
786
799
  method: "GET",
787
- headers: this.getHeaders()
800
+ headers: this.getHeaders(),
801
+ tags: fetchTags,
802
+ // ?? not || — see the constructor comment on defaultRevalidate for
803
+ // why: an explicit `revalidate: 0` on this call must not be
804
+ // discarded in favor of the engine's default.
805
+ revalidate: _nullishCoalesce(options.revalidate, () => ( this.defaultRevalidate))
788
806
  });
789
807
  if (!res.ok) {
790
808
  throw new Error(`API Error ${res.status}: ${res.statusText}`);
@@ -792,13 +810,8 @@ var ContentEngine = class {
792
810
  const json = await res.json();
793
811
  const data = json.data || json;
794
812
  this.writeCache(cacheKey, data, {
795
- revalidate: options.revalidate || this.defaultRevalidate,
796
- tags: [
797
- CacheTags.project(this.config.projectId),
798
- CacheTags.collection(collectionId),
799
- `item_${itemId}`,
800
- ...options.tags || []
801
- ]
813
+ revalidate: _nullishCoalesce(options.revalidate, () => ( this.defaultRevalidate)),
814
+ tags: fetchTags
802
815
  });
803
816
  return data;
804
817
  });
@@ -841,19 +854,49 @@ var ContentEngine = class {
841
854
  }
842
855
  /**
843
856
  * Robust Real-time Subscriptions (SSE) with Auto-Reconnect
857
+ *
858
+ * ⚠️ SCOPE — READ BEFORE RELYING ON THIS FOR "instant" UPDATES:
859
+ * This connects from the BROWSER TAB it's called in, and on message it
860
+ * only clears THIS SDK INSTANCE's in-memory `memoryCache` (via
861
+ * invalidateCache below) in THAT browser tab's JS heap. In a typical
862
+ * Next.js deployment — and Cloudflare specifically, which is stateless
863
+ * per-request at the edge — that is a different process/isolate than the
864
+ * one that will render the NEXT server request for this content. So:
865
+ * - ✅ Useful for: a client component that reads from `nexus.content`
866
+ * directly in the browser and re-renders in place without a page
867
+ * navigation (e.g. a live-updating dashboard widget).
868
+ * - ❌ NOT sufficient for: "user A edits content in the CMS, user B's
869
+ * already-loaded page updates" via a server-rendered page. That
870
+ * requires the backend's /api/revalidate webhook (see
871
+ * content.service.ts `pingNextRevalidateWebhook`) to have actually
872
+ * cleared the *server's* Data Cache, so the NEXT navigation or
873
+ * server request picks up fresh data. This SSE channel does not
874
+ * replace that — it's a complementary, browser-local optimization.
875
+ * If your symptom was "stale content after editing," fix the webhook
876
+ * wiring first; treat this method as an enhancement layered on top.
844
877
  */
845
878
  subscribeToUpdates(callback) {
846
879
  if (this.isServer || typeof EventSource === "undefined") {
847
- console.warn("[NexusHub] EventSource not supported in this environment");
880
+ if (this.config.debug) {
881
+ console.warn(
882
+ "[NexusHub] subscribeToUpdates() called in a non-browser environment (server render or SSR pass) \u2014 this is expected and safely skipped; EventSource only makes sense client-side."
883
+ );
884
+ }
848
885
  return () => {
849
886
  };
850
887
  }
888
+ if (!this.config.apiKey) {
889
+ console.warn(
890
+ "[NexusHub] subscribeToUpdates(): no apiKey configured, the SSE connection will likely be rejected by the backend. Set NEXT_PUBLIC_NEXUS_KEY."
891
+ );
892
+ }
851
893
  let eventSource = null;
852
894
  let retryCount = 0;
853
895
  let isClosed = false;
896
+ const MAX_RETRY_DELAY_MS = 3e4;
854
897
  const connect = () => {
855
898
  if (isClosed) return;
856
- const url = `${this.config.apiUrl}/content/${this.config.projectId}/updates?key=${this.config.apiKey}`;
899
+ const url = `${this.config.apiUrl}/content/${this.config.projectId}/updates?key=${_nullishCoalesce(this.config.apiKey, () => ( ""))}`;
857
900
  eventSource = new EventSource(url);
858
901
  eventSource.onopen = () => {
859
902
  retryCount = 0;
@@ -861,27 +904,40 @@ var ContentEngine = class {
861
904
  console.log("[NexusHub] \u{1F7E2} Real-time stream connected");
862
905
  };
863
906
  eventSource.onmessage = (event) => {
907
+ let data;
864
908
  try {
865
- const data = JSON.parse(event.data);
866
- if (data.type === "content.updated" && data.slug) {
867
- this.invalidateCache([CacheTags.content(data.slug)]);
868
- }
869
- if (data.type === "collection.updated") {
870
- this.invalidateCache([CacheTags.collection(data.collectionId)]);
871
- }
872
- callback(data);
909
+ data = JSON.parse(event.data);
873
910
  } catch (e) {
874
911
  console.error("[NexusHub] SSE Parse Error", e);
912
+ return;
913
+ }
914
+ if (!data || typeof data.type !== "string") {
915
+ return;
916
+ }
917
+ if (data.type === "content.updated" && data.slug) {
918
+ this.invalidateCache([CacheTags.content(data.slug)]);
919
+ }
920
+ if (data.type === "collection.updated" && data.collectionId) {
921
+ this.invalidateCache([CacheTags.collection(data.collectionId)]);
922
+ }
923
+ if (data.type === "schema.updated") {
924
+ this.clearCache();
875
925
  }
926
+ callback(data);
876
927
  };
877
928
  eventSource.onerror = () => {
878
929
  _optionalChain([eventSource, 'optionalAccess', _24 => _24.close, 'call', _25 => _25()]);
879
930
  if (isClosed) return;
880
- const timeout = Math.min(1e3 * Math.pow(2, retryCount), 1e4);
881
- retryCount++;
882
- console.warn(
883
- `[NexusHub] \u{1F534} Stream disconnected. Reconnecting in ${timeout}ms...`
931
+ const timeout = Math.min(
932
+ 1e3 * Math.pow(2, retryCount),
933
+ MAX_RETRY_DELAY_MS
884
934
  );
935
+ retryCount++;
936
+ if (this.config.debug) {
937
+ console.warn(
938
+ `[NexusHub] \u{1F534} Stream disconnected. Reconnecting in ${timeout}ms...`
939
+ );
940
+ }
885
941
  setTimeout(connect, timeout);
886
942
  };
887
943
  };
@@ -892,9 +948,6 @@ var ContentEngine = class {
892
948
  };
893
949
  }
894
950
  // --- CACHE MANAGEMENT ---
895
- /**
896
- * Check all caches in order of speed
897
- */
898
951
  async checkCaches(key, forceRefresh, includeMetadata) {
899
952
  if (forceRefresh) return null;
900
953
  if (this.cacheStrategy === "memory") {
@@ -951,9 +1004,6 @@ var ContentEngine = class {
951
1004
  this.browserCache.set(key, data, numericRevalidate * 1e3);
952
1005
  }
953
1006
  }
954
- /**
955
- * Invalidate cache by tags
956
- */
957
1007
  invalidateCache(tags) {
958
1008
  this.memoryCache.invalidateByTags(tags);
959
1009
  if (this.config.debug) {
@@ -962,9 +1012,6 @@ var ContentEngine = class {
962
1012
  );
963
1013
  }
964
1014
  }
965
- /**
966
- * Clear all caches
967
- */
968
1015
  clearCache() {
969
1016
  this.memoryCache.clear();
970
1017
  if (this.browserCache) {
@@ -974,9 +1021,6 @@ var ContentEngine = class {
974
1021
  console.log("[NexusHub] \u{1F9F9} Cleared all caches");
975
1022
  }
976
1023
  }
977
- /**
978
- * Get cache statistics
979
- */
980
1024
  getCacheStats() {
981
1025
  const stats = {
982
1026
  memory: this.memoryCache.getStats(),
@@ -990,14 +1034,16 @@ var ContentEngine = class {
990
1034
  // --- REQUEST METHODS ---
991
1035
  async fetchPage(slug, cacheKey, tags, revalidate, forceRefresh) {
992
1036
  const url = `${this.config.apiUrl}/content/${this.config.projectId}/page/${slug}`;
993
- if (this.config.debug) {
994
- console.log(`[NexusHub] \u{1F310} Fetching: ${url}`);
995
- }
996
1037
  const expiresAt = revalidate === false ? Infinity : Date.now() + revalidate * 1e3;
1038
+ const fetchTags = [
1039
+ CacheTags.project(this.config.projectId),
1040
+ CacheTags.content(slug),
1041
+ ...tags
1042
+ ];
997
1043
  const res = await this.fetchWithTimeout(url, {
998
1044
  method: "GET",
999
1045
  headers: this.getHeaders(),
1000
- tags,
1046
+ tags: fetchTags,
1001
1047
  revalidate
1002
1048
  });
1003
1049
  if (!res.ok) {
@@ -1016,29 +1062,27 @@ var ContentEngine = class {
1016
1062
  timestamp: Date.now(),
1017
1063
  etag: etag || void 0,
1018
1064
  expiresAt,
1019
- tags: [...tags, CacheTags.content(slug)]
1065
+ tags: fetchTags
1020
1066
  }
1021
1067
  };
1022
1068
  this.writeCache(cacheKey, cacheEntry.data, {
1023
1069
  revalidate,
1024
- tags: [
1025
- CacheTags.project(this.config.projectId),
1026
- CacheTags.content(slug),
1027
- ...tags
1028
- ]
1070
+ tags: fetchTags
1029
1071
  });
1030
1072
  return cacheEntry;
1031
1073
  }
1032
1074
  async fetchCollection(collectionId, query, cacheKey, tags, revalidate) {
1033
1075
  const params = buildQueryString(query);
1034
1076
  const url = `${this.config.apiUrl}/content/${this.config.projectId}/collection/${collectionId}?${params}`;
1035
- if (this.config.debug) {
1036
- console.log(`[NexusHub] \u{1F310} Fetching Collection: ${url}`);
1037
- }
1077
+ const fetchTags = [
1078
+ CacheTags.project(this.config.projectId),
1079
+ CacheTags.collection(collectionId),
1080
+ ...tags
1081
+ ];
1038
1082
  const res = await this.fetchWithTimeout(url, {
1039
1083
  method: "GET",
1040
1084
  headers: this.getHeaders(),
1041
- tags,
1085
+ tags: fetchTags,
1042
1086
  revalidate
1043
1087
  });
1044
1088
  if (!res.ok) {
@@ -1053,16 +1097,12 @@ var ContentEngine = class {
1053
1097
  timestamp: Date.now(),
1054
1098
  etag: etag || void 0,
1055
1099
  expiresAt,
1056
- tags: [...tags, CacheTags.collection(collectionId)]
1100
+ tags: fetchTags
1057
1101
  }
1058
1102
  };
1059
1103
  this.writeCache(cacheKey, cacheEntry.data, {
1060
1104
  revalidate,
1061
- tags: [
1062
- CacheTags.project(this.config.projectId),
1063
- CacheTags.collection(collectionId),
1064
- ...tags
1065
- ]
1105
+ tags: fetchTags
1066
1106
  });
1067
1107
  return cacheEntry;
1068
1108
  }
@@ -1122,8 +1162,11 @@ var ContentEngine = class {
1122
1162
  const controller = new AbortController();
1123
1163
  const id = setTimeout(() => controller.abort(), timeout);
1124
1164
  const nextConfig = this.isServer ? {
1125
- cache: "force-cache",
1126
- next: { tags, revalidate: revalidate === false ? false : revalidate }
1165
+ cache: revalidate === 0 ? "no-store" : "force-cache",
1166
+ next: {
1167
+ tags,
1168
+ revalidate: revalidate === false ? false : revalidate
1169
+ }
1127
1170
  } : {};
1128
1171
  try {
1129
1172
  const response = await fetch(url, {
@@ -2146,13 +2189,20 @@ function getSelector(el, depth = 0) {
2146
2189
  }
2147
2190
 
2148
2191
  // src/client.ts
2192
+ var DEFAULT_REVALIDATE_SECONDS = 60;
2149
2193
  var NexusClient = class {
2150
2194
  constructor(config) {
2151
2195
  const fullConfig = _chunkWZ47UVA2cjs.getFullConfig.call(void 0, config);
2152
2196
  this.config = {
2153
2197
  debug: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _41 => _41.debug]), () => ( false)),
2154
2198
  cacheStrategy: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _42 => _42.cacheStrategy]), () => ( "memory")),
2155
- revalidateTime: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _43 => _43.revalidateTime]), () => ( false)),
2199
+ // Only fall through to the safe default when revalidateTime is
2200
+ // genuinely *unset* (undefined). An explicit `false` from the caller
2201
+ // is a deliberate "never revalidate" choice and must be respected,
2202
+ // not silently upgraded — `??` (not `||`) is required here so that
2203
+ // `0` (revalidate on every request) also passes through untouched
2204
+ // instead of being treated as falsy.
2205
+ revalidateTime: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _43 => _43.revalidateTime]), () => ( DEFAULT_REVALIDATE_SECONDS)),
2156
2206
  timeout: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _44 => _44.timeout]), () => ( 1e4)),
2157
2207
  retries: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _45 => _45.retries]), () => ( 3)),
2158
2208
  ...fullConfig
package/dist/react.d.cts CHANGED
@@ -85,6 +85,7 @@ declare class ContentEngine {
85
85
  include?: string[];
86
86
  revalidate?: number | false;
87
87
  forceRefresh?: boolean;
88
+ tags?: string[];
88
89
  }): Promise<T>;
89
90
  /**
90
91
  * Get a single item from a collection (Uses Request Batching)
@@ -112,24 +113,32 @@ declare class ContentEngine {
112
113
  prefetch(urls: string[]): Promise<void>;
113
114
  /**
114
115
  * Robust Real-time Subscriptions (SSE) with Auto-Reconnect
116
+ *
117
+ * ⚠️ SCOPE — READ BEFORE RELYING ON THIS FOR "instant" UPDATES:
118
+ * This connects from the BROWSER TAB it's called in, and on message it
119
+ * only clears THIS SDK INSTANCE's in-memory `memoryCache` (via
120
+ * invalidateCache below) in THAT browser tab's JS heap. In a typical
121
+ * Next.js deployment — and Cloudflare specifically, which is stateless
122
+ * per-request at the edge — that is a different process/isolate than the
123
+ * one that will render the NEXT server request for this content. So:
124
+ * - ✅ Useful for: a client component that reads from `nexus.content`
125
+ * directly in the browser and re-renders in place without a page
126
+ * navigation (e.g. a live-updating dashboard widget).
127
+ * - ❌ NOT sufficient for: "user A edits content in the CMS, user B's
128
+ * already-loaded page updates" via a server-rendered page. That
129
+ * requires the backend's /api/revalidate webhook (see
130
+ * content.service.ts `pingNextRevalidateWebhook`) to have actually
131
+ * cleared the *server's* Data Cache, so the NEXT navigation or
132
+ * server request picks up fresh data. This SSE channel does not
133
+ * replace that — it's a complementary, browser-local optimization.
134
+ * If your symptom was "stale content after editing," fix the webhook
135
+ * wiring first; treat this method as an enhancement layered on top.
115
136
  */
116
137
  subscribeToUpdates(callback: (data: any) => void): () => void;
117
- /**
118
- * Check all caches in order of speed
119
- */
120
138
  private checkCaches;
121
139
  private writeCache;
122
- /**
123
- * Invalidate cache by tags
124
- */
125
140
  invalidateCache(tags: string[]): void;
126
- /**
127
- * Clear all caches
128
- */
129
141
  clearCache(): void;
130
- /**
131
- * Get cache statistics
132
- */
133
142
  getCacheStats(): {
134
143
  memory: {
135
144
  size: number;