@nexushub/client 0.7.8 → 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/cli.cjs +423 -107
- package/dist/index.cjs +76 -20
- package/dist/index.d.cts +20 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +76 -20
- package/dist/react.cjs +76 -20
- package/dist/react.d.cts +20 -0
- package/dist/react.d.ts +20 -0
- package/dist/react.js +76 -20
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -936,7 +936,7 @@ var ContentEngine = class {
|
|
|
936
936
|
});
|
|
937
937
|
this.requestBatcher = new RequestBatcher(20, 50);
|
|
938
938
|
this.circuitBreaker = new CircuitBreaker();
|
|
939
|
-
this.defaultRevalidate = config.revalidateTime
|
|
939
|
+
this.defaultRevalidate = config.revalidateTime ?? false;
|
|
940
940
|
this.cacheStrategy = config.cacheStrategy || "memory";
|
|
941
941
|
if (!this.isServer) {
|
|
942
942
|
window.addEventListener("beforeunload", this.cleanup.bind(this));
|
|
@@ -1200,7 +1200,10 @@ var ContentEngine = class {
|
|
|
1200
1200
|
method: "GET",
|
|
1201
1201
|
headers: this.getHeaders(),
|
|
1202
1202
|
tags: fetchTags,
|
|
1203
|
-
|
|
1203
|
+
// ?? not || — see the constructor comment on defaultRevalidate for
|
|
1204
|
+
// why: an explicit `revalidate: 0` on this call must not be
|
|
1205
|
+
// discarded in favor of the engine's default.
|
|
1206
|
+
revalidate: options.revalidate ?? this.defaultRevalidate
|
|
1204
1207
|
});
|
|
1205
1208
|
if (!res.ok) {
|
|
1206
1209
|
throw new Error(`API Error ${res.status}: ${res.statusText}`);
|
|
@@ -1208,7 +1211,7 @@ var ContentEngine = class {
|
|
|
1208
1211
|
const json = await res.json();
|
|
1209
1212
|
const data = json.data || json;
|
|
1210
1213
|
this.writeCache(cacheKey, data, {
|
|
1211
|
-
revalidate: options.revalidate
|
|
1214
|
+
revalidate: options.revalidate ?? this.defaultRevalidate,
|
|
1212
1215
|
tags: fetchTags
|
|
1213
1216
|
});
|
|
1214
1217
|
return data;
|
|
@@ -1252,19 +1255,49 @@ var ContentEngine = class {
|
|
|
1252
1255
|
}
|
|
1253
1256
|
/**
|
|
1254
1257
|
* Robust Real-time Subscriptions (SSE) with Auto-Reconnect
|
|
1258
|
+
*
|
|
1259
|
+
* ⚠️ SCOPE — READ BEFORE RELYING ON THIS FOR "instant" UPDATES:
|
|
1260
|
+
* This connects from the BROWSER TAB it's called in, and on message it
|
|
1261
|
+
* only clears THIS SDK INSTANCE's in-memory `memoryCache` (via
|
|
1262
|
+
* invalidateCache below) in THAT browser tab's JS heap. In a typical
|
|
1263
|
+
* Next.js deployment — and Cloudflare specifically, which is stateless
|
|
1264
|
+
* per-request at the edge — that is a different process/isolate than the
|
|
1265
|
+
* one that will render the NEXT server request for this content. So:
|
|
1266
|
+
* - ✅ Useful for: a client component that reads from `nexus.content`
|
|
1267
|
+
* directly in the browser and re-renders in place without a page
|
|
1268
|
+
* navigation (e.g. a live-updating dashboard widget).
|
|
1269
|
+
* - ❌ NOT sufficient for: "user A edits content in the CMS, user B's
|
|
1270
|
+
* already-loaded page updates" via a server-rendered page. That
|
|
1271
|
+
* requires the backend's /api/revalidate webhook (see
|
|
1272
|
+
* content.service.ts `pingNextRevalidateWebhook`) to have actually
|
|
1273
|
+
* cleared the *server's* Data Cache, so the NEXT navigation or
|
|
1274
|
+
* server request picks up fresh data. This SSE channel does not
|
|
1275
|
+
* replace that — it's a complementary, browser-local optimization.
|
|
1276
|
+
* If your symptom was "stale content after editing," fix the webhook
|
|
1277
|
+
* wiring first; treat this method as an enhancement layered on top.
|
|
1255
1278
|
*/
|
|
1256
1279
|
subscribeToUpdates(callback) {
|
|
1257
1280
|
if (this.isServer || typeof EventSource === "undefined") {
|
|
1258
|
-
|
|
1281
|
+
if (this.config.debug) {
|
|
1282
|
+
console.warn(
|
|
1283
|
+
"[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."
|
|
1284
|
+
);
|
|
1285
|
+
}
|
|
1259
1286
|
return () => {
|
|
1260
1287
|
};
|
|
1261
1288
|
}
|
|
1289
|
+
if (!this.config.apiKey) {
|
|
1290
|
+
console.warn(
|
|
1291
|
+
"[NexusHub] subscribeToUpdates(): no apiKey configured, the SSE connection will likely be rejected by the backend. Set NEXT_PUBLIC_NEXUS_KEY."
|
|
1292
|
+
);
|
|
1293
|
+
}
|
|
1262
1294
|
let eventSource = null;
|
|
1263
1295
|
let retryCount = 0;
|
|
1264
1296
|
let isClosed = false;
|
|
1297
|
+
const MAX_RETRY_DELAY_MS = 3e4;
|
|
1265
1298
|
const connect = () => {
|
|
1266
1299
|
if (isClosed) return;
|
|
1267
|
-
const url = `${this.config.apiUrl}/content/${this.config.projectId}/updates?key=${this.config.apiKey}`;
|
|
1300
|
+
const url = `${this.config.apiUrl}/content/${this.config.projectId}/updates?key=${this.config.apiKey ?? ""}`;
|
|
1268
1301
|
eventSource = new EventSource(url);
|
|
1269
1302
|
eventSource.onopen = () => {
|
|
1270
1303
|
retryCount = 0;
|
|
@@ -1272,27 +1305,40 @@ var ContentEngine = class {
|
|
|
1272
1305
|
console.log("[NexusHub] \u{1F7E2} Real-time stream connected");
|
|
1273
1306
|
};
|
|
1274
1307
|
eventSource.onmessage = (event) => {
|
|
1308
|
+
let data;
|
|
1275
1309
|
try {
|
|
1276
|
-
|
|
1277
|
-
if (data.type === "content.updated" && data.slug) {
|
|
1278
|
-
this.invalidateCache([CacheTags.content(data.slug)]);
|
|
1279
|
-
}
|
|
1280
|
-
if (data.type === "collection.updated") {
|
|
1281
|
-
this.invalidateCache([CacheTags.collection(data.collectionId)]);
|
|
1282
|
-
}
|
|
1283
|
-
callback(data);
|
|
1310
|
+
data = JSON.parse(event.data);
|
|
1284
1311
|
} catch (e) {
|
|
1285
1312
|
console.error("[NexusHub] SSE Parse Error", e);
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
if (!data || typeof data.type !== "string") {
|
|
1316
|
+
return;
|
|
1286
1317
|
}
|
|
1318
|
+
if (data.type === "content.updated" && data.slug) {
|
|
1319
|
+
this.invalidateCache([CacheTags.content(data.slug)]);
|
|
1320
|
+
}
|
|
1321
|
+
if (data.type === "collection.updated" && data.collectionId) {
|
|
1322
|
+
this.invalidateCache([CacheTags.collection(data.collectionId)]);
|
|
1323
|
+
}
|
|
1324
|
+
if (data.type === "schema.updated") {
|
|
1325
|
+
this.clearCache();
|
|
1326
|
+
}
|
|
1327
|
+
callback(data);
|
|
1287
1328
|
};
|
|
1288
1329
|
eventSource.onerror = () => {
|
|
1289
1330
|
eventSource?.close();
|
|
1290
1331
|
if (isClosed) return;
|
|
1291
|
-
const timeout = Math.min(
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
`[NexusHub] \u{1F534} Stream disconnected. Reconnecting in ${timeout}ms...`
|
|
1332
|
+
const timeout = Math.min(
|
|
1333
|
+
1e3 * Math.pow(2, retryCount),
|
|
1334
|
+
MAX_RETRY_DELAY_MS
|
|
1295
1335
|
);
|
|
1336
|
+
retryCount++;
|
|
1337
|
+
if (this.config.debug) {
|
|
1338
|
+
console.warn(
|
|
1339
|
+
`[NexusHub] \u{1F534} Stream disconnected. Reconnecting in ${timeout}ms...`
|
|
1340
|
+
);
|
|
1341
|
+
}
|
|
1296
1342
|
setTimeout(connect, timeout);
|
|
1297
1343
|
};
|
|
1298
1344
|
};
|
|
@@ -1517,8 +1563,11 @@ var ContentEngine = class {
|
|
|
1517
1563
|
const controller = new AbortController();
|
|
1518
1564
|
const id = setTimeout(() => controller.abort(), timeout);
|
|
1519
1565
|
const nextConfig = this.isServer ? {
|
|
1520
|
-
cache: "force-cache",
|
|
1521
|
-
next: {
|
|
1566
|
+
cache: revalidate === 0 ? "no-store" : "force-cache",
|
|
1567
|
+
next: {
|
|
1568
|
+
tags,
|
|
1569
|
+
revalidate: revalidate === false ? false : revalidate
|
|
1570
|
+
}
|
|
1522
1571
|
} : {};
|
|
1523
1572
|
try {
|
|
1524
1573
|
const response = await fetch(url, {
|
|
@@ -2541,13 +2590,20 @@ function getSelector(el, depth = 0) {
|
|
|
2541
2590
|
}
|
|
2542
2591
|
|
|
2543
2592
|
// src/client.ts
|
|
2593
|
+
var DEFAULT_REVALIDATE_SECONDS = 60;
|
|
2544
2594
|
var NexusClient = class {
|
|
2545
2595
|
constructor(config) {
|
|
2546
2596
|
const fullConfig = getFullConfig(config);
|
|
2547
2597
|
this.config = {
|
|
2548
2598
|
debug: config?.debug ?? false,
|
|
2549
2599
|
cacheStrategy: config?.cacheStrategy ?? "memory",
|
|
2550
|
-
|
|
2600
|
+
// Only fall through to the safe default when revalidateTime is
|
|
2601
|
+
// genuinely *unset* (undefined). An explicit `false` from the caller
|
|
2602
|
+
// is a deliberate "never revalidate" choice and must be respected,
|
|
2603
|
+
// not silently upgraded — `??` (not `||`) is required here so that
|
|
2604
|
+
// `0` (revalidate on every request) also passes through untouched
|
|
2605
|
+
// instead of being treated as falsy.
|
|
2606
|
+
revalidateTime: config?.revalidateTime ?? DEFAULT_REVALIDATE_SECONDS,
|
|
2551
2607
|
timeout: config?.timeout ?? 1e4,
|
|
2552
2608
|
retries: config?.retries ?? 3,
|
|
2553
2609
|
...fullConfig
|
package/dist/index.d.cts
CHANGED
|
@@ -147,6 +147,26 @@ declare class ContentEngine {
|
|
|
147
147
|
prefetch(urls: string[]): Promise<void>;
|
|
148
148
|
/**
|
|
149
149
|
* Robust Real-time Subscriptions (SSE) with Auto-Reconnect
|
|
150
|
+
*
|
|
151
|
+
* ⚠️ SCOPE — READ BEFORE RELYING ON THIS FOR "instant" UPDATES:
|
|
152
|
+
* This connects from the BROWSER TAB it's called in, and on message it
|
|
153
|
+
* only clears THIS SDK INSTANCE's in-memory `memoryCache` (via
|
|
154
|
+
* invalidateCache below) in THAT browser tab's JS heap. In a typical
|
|
155
|
+
* Next.js deployment — and Cloudflare specifically, which is stateless
|
|
156
|
+
* per-request at the edge — that is a different process/isolate than the
|
|
157
|
+
* one that will render the NEXT server request for this content. So:
|
|
158
|
+
* - ✅ Useful for: a client component that reads from `nexus.content`
|
|
159
|
+
* directly in the browser and re-renders in place without a page
|
|
160
|
+
* navigation (e.g. a live-updating dashboard widget).
|
|
161
|
+
* - ❌ NOT sufficient for: "user A edits content in the CMS, user B's
|
|
162
|
+
* already-loaded page updates" via a server-rendered page. That
|
|
163
|
+
* requires the backend's /api/revalidate webhook (see
|
|
164
|
+
* content.service.ts `pingNextRevalidateWebhook`) to have actually
|
|
165
|
+
* cleared the *server's* Data Cache, so the NEXT navigation or
|
|
166
|
+
* server request picks up fresh data. This SSE channel does not
|
|
167
|
+
* replace that — it's a complementary, browser-local optimization.
|
|
168
|
+
* If your symptom was "stale content after editing," fix the webhook
|
|
169
|
+
* wiring first; treat this method as an enhancement layered on top.
|
|
150
170
|
*/
|
|
151
171
|
subscribeToUpdates(callback: (data: any) => void): () => void;
|
|
152
172
|
private checkCaches;
|
package/dist/index.d.ts
CHANGED
|
@@ -147,6 +147,26 @@ declare class ContentEngine {
|
|
|
147
147
|
prefetch(urls: string[]): Promise<void>;
|
|
148
148
|
/**
|
|
149
149
|
* Robust Real-time Subscriptions (SSE) with Auto-Reconnect
|
|
150
|
+
*
|
|
151
|
+
* ⚠️ SCOPE — READ BEFORE RELYING ON THIS FOR "instant" UPDATES:
|
|
152
|
+
* This connects from the BROWSER TAB it's called in, and on message it
|
|
153
|
+
* only clears THIS SDK INSTANCE's in-memory `memoryCache` (via
|
|
154
|
+
* invalidateCache below) in THAT browser tab's JS heap. In a typical
|
|
155
|
+
* Next.js deployment — and Cloudflare specifically, which is stateless
|
|
156
|
+
* per-request at the edge — that is a different process/isolate than the
|
|
157
|
+
* one that will render the NEXT server request for this content. So:
|
|
158
|
+
* - ✅ Useful for: a client component that reads from `nexus.content`
|
|
159
|
+
* directly in the browser and re-renders in place without a page
|
|
160
|
+
* navigation (e.g. a live-updating dashboard widget).
|
|
161
|
+
* - ❌ NOT sufficient for: "user A edits content in the CMS, user B's
|
|
162
|
+
* already-loaded page updates" via a server-rendered page. That
|
|
163
|
+
* requires the backend's /api/revalidate webhook (see
|
|
164
|
+
* content.service.ts `pingNextRevalidateWebhook`) to have actually
|
|
165
|
+
* cleared the *server's* Data Cache, so the NEXT navigation or
|
|
166
|
+
* server request picks up fresh data. This SSE channel does not
|
|
167
|
+
* replace that — it's a complementary, browser-local optimization.
|
|
168
|
+
* If your symptom was "stale content after editing," fix the webhook
|
|
169
|
+
* wiring first; treat this method as an enhancement layered on top.
|
|
150
170
|
*/
|
|
151
171
|
subscribeToUpdates(callback: (data: any) => void): () => void;
|
|
152
172
|
private checkCaches;
|
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
|
|
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));
|
|
@@ -1154,7 +1154,10 @@ var ContentEngine = class {
|
|
|
1154
1154
|
method: "GET",
|
|
1155
1155
|
headers: this.getHeaders(),
|
|
1156
1156
|
tags: fetchTags,
|
|
1157
|
-
|
|
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
|
|
1158
1161
|
});
|
|
1159
1162
|
if (!res.ok) {
|
|
1160
1163
|
throw new Error(`API Error ${res.status}: ${res.statusText}`);
|
|
@@ -1162,7 +1165,7 @@ var ContentEngine = class {
|
|
|
1162
1165
|
const json = await res.json();
|
|
1163
1166
|
const data = json.data || json;
|
|
1164
1167
|
this.writeCache(cacheKey, data, {
|
|
1165
|
-
revalidate: options.revalidate
|
|
1168
|
+
revalidate: options.revalidate ?? this.defaultRevalidate,
|
|
1166
1169
|
tags: fetchTags
|
|
1167
1170
|
});
|
|
1168
1171
|
return data;
|
|
@@ -1206,19 +1209,49 @@ var ContentEngine = class {
|
|
|
1206
1209
|
}
|
|
1207
1210
|
/**
|
|
1208
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.
|
|
1209
1232
|
*/
|
|
1210
1233
|
subscribeToUpdates(callback) {
|
|
1211
1234
|
if (this.isServer || typeof EventSource === "undefined") {
|
|
1212
|
-
|
|
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
|
+
}
|
|
1213
1240
|
return () => {
|
|
1214
1241
|
};
|
|
1215
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
|
+
}
|
|
1216
1248
|
let eventSource = null;
|
|
1217
1249
|
let retryCount = 0;
|
|
1218
1250
|
let isClosed = false;
|
|
1251
|
+
const MAX_RETRY_DELAY_MS = 3e4;
|
|
1219
1252
|
const connect = () => {
|
|
1220
1253
|
if (isClosed) return;
|
|
1221
|
-
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 ?? ""}`;
|
|
1222
1255
|
eventSource = new EventSource(url);
|
|
1223
1256
|
eventSource.onopen = () => {
|
|
1224
1257
|
retryCount = 0;
|
|
@@ -1226,27 +1259,40 @@ var ContentEngine = class {
|
|
|
1226
1259
|
console.log("[NexusHub] \u{1F7E2} Real-time stream connected");
|
|
1227
1260
|
};
|
|
1228
1261
|
eventSource.onmessage = (event) => {
|
|
1262
|
+
let data;
|
|
1229
1263
|
try {
|
|
1230
|
-
|
|
1231
|
-
if (data.type === "content.updated" && data.slug) {
|
|
1232
|
-
this.invalidateCache([CacheTags.content(data.slug)]);
|
|
1233
|
-
}
|
|
1234
|
-
if (data.type === "collection.updated") {
|
|
1235
|
-
this.invalidateCache([CacheTags.collection(data.collectionId)]);
|
|
1236
|
-
}
|
|
1237
|
-
callback(data);
|
|
1264
|
+
data = JSON.parse(event.data);
|
|
1238
1265
|
} catch (e) {
|
|
1239
1266
|
console.error("[NexusHub] SSE Parse Error", e);
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
if (!data || typeof data.type !== "string") {
|
|
1270
|
+
return;
|
|
1240
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)]);
|
|
1277
|
+
}
|
|
1278
|
+
if (data.type === "schema.updated") {
|
|
1279
|
+
this.clearCache();
|
|
1280
|
+
}
|
|
1281
|
+
callback(data);
|
|
1241
1282
|
};
|
|
1242
1283
|
eventSource.onerror = () => {
|
|
1243
1284
|
eventSource?.close();
|
|
1244
1285
|
if (isClosed) return;
|
|
1245
|
-
const timeout = Math.min(
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
`[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
|
|
1249
1289
|
);
|
|
1290
|
+
retryCount++;
|
|
1291
|
+
if (this.config.debug) {
|
|
1292
|
+
console.warn(
|
|
1293
|
+
`[NexusHub] \u{1F534} Stream disconnected. Reconnecting in ${timeout}ms...`
|
|
1294
|
+
);
|
|
1295
|
+
}
|
|
1250
1296
|
setTimeout(connect, timeout);
|
|
1251
1297
|
};
|
|
1252
1298
|
};
|
|
@@ -1471,8 +1517,11 @@ var ContentEngine = class {
|
|
|
1471
1517
|
const controller = new AbortController();
|
|
1472
1518
|
const id = setTimeout(() => controller.abort(), timeout);
|
|
1473
1519
|
const nextConfig = this.isServer ? {
|
|
1474
|
-
cache: "force-cache",
|
|
1475
|
-
next: {
|
|
1520
|
+
cache: revalidate === 0 ? "no-store" : "force-cache",
|
|
1521
|
+
next: {
|
|
1522
|
+
tags,
|
|
1523
|
+
revalidate: revalidate === false ? false : revalidate
|
|
1524
|
+
}
|
|
1476
1525
|
} : {};
|
|
1477
1526
|
try {
|
|
1478
1527
|
const response = await fetch(url, {
|
|
@@ -2495,13 +2544,20 @@ function getSelector(el, depth = 0) {
|
|
|
2495
2544
|
}
|
|
2496
2545
|
|
|
2497
2546
|
// src/client.ts
|
|
2547
|
+
var DEFAULT_REVALIDATE_SECONDS = 60;
|
|
2498
2548
|
var NexusClient = class {
|
|
2499
2549
|
constructor(config) {
|
|
2500
2550
|
const fullConfig = getFullConfig(config);
|
|
2501
2551
|
this.config = {
|
|
2502
2552
|
debug: config?.debug ?? false,
|
|
2503
2553
|
cacheStrategy: config?.cacheStrategy ?? "memory",
|
|
2504
|
-
|
|
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,
|
|
2505
2561
|
timeout: config?.timeout ?? 1e4,
|
|
2506
2562
|
retries: config?.retries ?? 3,
|
|
2507
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
|
|
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));
|
|
@@ -799,7 +799,10 @@ var ContentEngine = class {
|
|
|
799
799
|
method: "GET",
|
|
800
800
|
headers: this.getHeaders(),
|
|
801
801
|
tags: fetchTags,
|
|
802
|
-
|
|
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))
|
|
803
806
|
});
|
|
804
807
|
if (!res.ok) {
|
|
805
808
|
throw new Error(`API Error ${res.status}: ${res.statusText}`);
|
|
@@ -807,7 +810,7 @@ var ContentEngine = class {
|
|
|
807
810
|
const json = await res.json();
|
|
808
811
|
const data = json.data || json;
|
|
809
812
|
this.writeCache(cacheKey, data, {
|
|
810
|
-
revalidate: options.revalidate
|
|
813
|
+
revalidate: _nullishCoalesce(options.revalidate, () => ( this.defaultRevalidate)),
|
|
811
814
|
tags: fetchTags
|
|
812
815
|
});
|
|
813
816
|
return data;
|
|
@@ -851,19 +854,49 @@ var ContentEngine = class {
|
|
|
851
854
|
}
|
|
852
855
|
/**
|
|
853
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.
|
|
854
877
|
*/
|
|
855
878
|
subscribeToUpdates(callback) {
|
|
856
879
|
if (this.isServer || typeof EventSource === "undefined") {
|
|
857
|
-
|
|
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
|
+
}
|
|
858
885
|
return () => {
|
|
859
886
|
};
|
|
860
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
|
+
}
|
|
861
893
|
let eventSource = null;
|
|
862
894
|
let retryCount = 0;
|
|
863
895
|
let isClosed = false;
|
|
896
|
+
const MAX_RETRY_DELAY_MS = 3e4;
|
|
864
897
|
const connect = () => {
|
|
865
898
|
if (isClosed) return;
|
|
866
|
-
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, () => ( ""))}`;
|
|
867
900
|
eventSource = new EventSource(url);
|
|
868
901
|
eventSource.onopen = () => {
|
|
869
902
|
retryCount = 0;
|
|
@@ -871,27 +904,40 @@ var ContentEngine = class {
|
|
|
871
904
|
console.log("[NexusHub] \u{1F7E2} Real-time stream connected");
|
|
872
905
|
};
|
|
873
906
|
eventSource.onmessage = (event) => {
|
|
907
|
+
let data;
|
|
874
908
|
try {
|
|
875
|
-
|
|
876
|
-
if (data.type === "content.updated" && data.slug) {
|
|
877
|
-
this.invalidateCache([CacheTags.content(data.slug)]);
|
|
878
|
-
}
|
|
879
|
-
if (data.type === "collection.updated") {
|
|
880
|
-
this.invalidateCache([CacheTags.collection(data.collectionId)]);
|
|
881
|
-
}
|
|
882
|
-
callback(data);
|
|
909
|
+
data = JSON.parse(event.data);
|
|
883
910
|
} catch (e) {
|
|
884
911
|
console.error("[NexusHub] SSE Parse Error", e);
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
if (!data || typeof data.type !== "string") {
|
|
915
|
+
return;
|
|
885
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();
|
|
925
|
+
}
|
|
926
|
+
callback(data);
|
|
886
927
|
};
|
|
887
928
|
eventSource.onerror = () => {
|
|
888
929
|
_optionalChain([eventSource, 'optionalAccess', _24 => _24.close, 'call', _25 => _25()]);
|
|
889
930
|
if (isClosed) return;
|
|
890
|
-
const timeout = Math.min(
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
`[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
|
|
894
934
|
);
|
|
935
|
+
retryCount++;
|
|
936
|
+
if (this.config.debug) {
|
|
937
|
+
console.warn(
|
|
938
|
+
`[NexusHub] \u{1F534} Stream disconnected. Reconnecting in ${timeout}ms...`
|
|
939
|
+
);
|
|
940
|
+
}
|
|
895
941
|
setTimeout(connect, timeout);
|
|
896
942
|
};
|
|
897
943
|
};
|
|
@@ -1116,8 +1162,11 @@ var ContentEngine = class {
|
|
|
1116
1162
|
const controller = new AbortController();
|
|
1117
1163
|
const id = setTimeout(() => controller.abort(), timeout);
|
|
1118
1164
|
const nextConfig = this.isServer ? {
|
|
1119
|
-
cache: "force-cache",
|
|
1120
|
-
next: {
|
|
1165
|
+
cache: revalidate === 0 ? "no-store" : "force-cache",
|
|
1166
|
+
next: {
|
|
1167
|
+
tags,
|
|
1168
|
+
revalidate: revalidate === false ? false : revalidate
|
|
1169
|
+
}
|
|
1121
1170
|
} : {};
|
|
1122
1171
|
try {
|
|
1123
1172
|
const response = await fetch(url, {
|
|
@@ -2140,13 +2189,20 @@ function getSelector(el, depth = 0) {
|
|
|
2140
2189
|
}
|
|
2141
2190
|
|
|
2142
2191
|
// src/client.ts
|
|
2192
|
+
var DEFAULT_REVALIDATE_SECONDS = 60;
|
|
2143
2193
|
var NexusClient = class {
|
|
2144
2194
|
constructor(config) {
|
|
2145
2195
|
const fullConfig = _chunkWZ47UVA2cjs.getFullConfig.call(void 0, config);
|
|
2146
2196
|
this.config = {
|
|
2147
2197
|
debug: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _41 => _41.debug]), () => ( false)),
|
|
2148
2198
|
cacheStrategy: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _42 => _42.cacheStrategy]), () => ( "memory")),
|
|
2149
|
-
|
|
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)),
|
|
2150
2206
|
timeout: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _44 => _44.timeout]), () => ( 1e4)),
|
|
2151
2207
|
retries: _nullishCoalesce(_optionalChain([config, 'optionalAccess', _45 => _45.retries]), () => ( 3)),
|
|
2152
2208
|
...fullConfig
|
package/dist/react.d.cts
CHANGED
|
@@ -113,6 +113,26 @@ declare class ContentEngine {
|
|
|
113
113
|
prefetch(urls: string[]): Promise<void>;
|
|
114
114
|
/**
|
|
115
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.
|
|
116
136
|
*/
|
|
117
137
|
subscribeToUpdates(callback: (data: any) => void): () => void;
|
|
118
138
|
private checkCaches;
|
package/dist/react.d.ts
CHANGED
|
@@ -113,6 +113,26 @@ declare class ContentEngine {
|
|
|
113
113
|
prefetch(urls: string[]): Promise<void>;
|
|
114
114
|
/**
|
|
115
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.
|
|
116
136
|
*/
|
|
117
137
|
subscribeToUpdates(callback: (data: any) => void): () => void;
|
|
118
138
|
private checkCaches;
|