@nexushub/client 1.1.4 → 1.1.7

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.cjs CHANGED
@@ -903,14 +903,13 @@ var ContentEngine = class {
903
903
  this.localCache = new LocalCacheProxy();
904
904
  this.memoryCache = new MemoryCache({
905
905
  maxSize: 500,
906
- ttl: 5 * 60 * 1e3
907
- // 5 minutes
906
+ ttl: 60 * 1e3
908
907
  });
909
908
  if (!this.isServer) {
910
909
  this.browserCache = new BrowserCache(config.projectId);
911
910
  }
912
911
  this.rateLimiter = new RateLimiter({
913
- maxRequests: config.debug ? 100 : 50,
912
+ maxRequests: config.debug ? 200 : 100,
914
913
  timeWindow: 6e4
915
914
  });
916
915
  this.backoff = new ExponentialBackoff({
@@ -927,15 +926,11 @@ var ContentEngine = class {
927
926
  window.addEventListener("beforeunload", this.cleanup.bind(this));
928
927
  }
929
928
  }
930
- /** Update runtime configuration without exposing internal mutation. */
931
929
  updateConfig(config) {
932
930
  this.config = config;
933
931
  this.defaultRevalidate = config.revalidateTime ?? false;
934
932
  this.cacheStrategy = config.cacheStrategy || "memory";
935
933
  }
936
- /**
937
- * Fetch a Single Page with full strategy pipeline
938
- */
939
934
  async getPage(slug, options = {}) {
940
935
  const { result, duration } = measurePerformance(
941
936
  `getPage("${slug}")`,
@@ -957,66 +952,66 @@ var ContentEngine = class {
957
952
  includeMetadata = false
958
953
  } = options;
959
954
  const cacheKey = `page:${slug}`;
960
- const cached = await this.checkCaches(
961
- cacheKey,
962
- forceRefresh,
963
- includeMetadata
964
- );
965
- if (cached) {
966
- if (this.config.debug) console.log(`[NexusHub] \u26A1 Cache hit: ${slug}`);
967
- return includeMetadata ? cached : cached.data;
955
+ if (!this.isServer && !forceRefresh && this.cacheStrategy === "memory") {
956
+ const cached = this.memoryCache.get(cacheKey);
957
+ if (cached && this.isCacheValid(cached.metadata)) {
958
+ return includeMetadata ? cached : cached.data;
959
+ }
960
+ }
961
+ if (!forceRefresh && process.env.NODE_ENV === "development") {
962
+ try {
963
+ const local = await this.localCache.getPage(slug);
964
+ if (local) {
965
+ return includeMetadata ? {
966
+ data: local,
967
+ metadata: {
968
+ timestamp: Date.now(),
969
+ expiresAt: Infinity,
970
+ tags: []
971
+ }
972
+ } : local;
973
+ }
974
+ } catch {
975
+ }
968
976
  }
969
977
  if (this.circuitBreaker.isOpen()) {
970
- throw new Error(`[NexusHub] Circuit open. API is unavailable.`);
978
+ throw new Error("[NexusHub] Circuit open. API is unavailable.");
971
979
  }
972
980
  try {
973
981
  await this.rateLimiter.checkLimit();
974
- const data = await this.backoff.execute(
975
- async () => {
976
- return this.fetchPage(
977
- slug,
978
- cacheKey,
979
- tags,
980
- revalidate,
981
- forceRefresh
982
- );
983
- },
984
- (attempt, delay, error) => {
985
- if (this.config.debug) {
986
- console.log(
987
- `[NexusHub] \u{1F504} Retry ${attempt} for '${slug}' after ${delay}ms: ${error.message}`
988
- );
989
- }
990
- }
991
- );
982
+ const data = await this.backoff.execute(async () => {
983
+ return this.fetchPage(
984
+ slug,
985
+ cacheKey,
986
+ tags,
987
+ revalidate,
988
+ forceRefresh
989
+ );
990
+ });
992
991
  this.circuitBreaker.recordSuccess();
993
992
  return includeMetadata ? data : data.data;
994
993
  } catch (error) {
995
994
  this.circuitBreaker.recordFailure();
996
- const staleCache = this.memoryCache.get(cacheKey);
997
- if (staleCache && !forceRefresh) {
998
- console.warn(`[NexusHub] \u26A0\uFE0F Serving stale content for '${slug}'`);
999
- return includeMetadata ? staleCache : staleCache.data;
995
+ if (process.env.NODE_ENV === "development") {
996
+ try {
997
+ const local = await this.localCache.getPage(slug);
998
+ if (local) {
999
+ return includeMetadata ? {
1000
+ data: local,
1001
+ metadata: {
1002
+ timestamp: Date.now(),
1003
+ expiresAt: Infinity,
1004
+ tags: []
1005
+ }
1006
+ } : local;
1007
+ }
1008
+ } catch {
1009
+ }
1000
1010
  }
1001
1011
  throw this.normalizeError(error, `Failed to fetch page '${slug}'`);
1002
1012
  }
1003
1013
  }
1004
- /**
1005
- * Fetch a Collection (Optimized)
1006
- */
1007
1014
  async getCollection(collectionId, query = {}, options = {}) {
1008
- const { result, duration } = measurePerformance(
1009
- `getCollection("${collectionId}")`,
1010
- () => this._getCollection(collectionId, query, options)
1011
- );
1012
- if (this.config.debug && duration > 100) {
1013
- console.warn(
1014
- `[NexusHub] \u26A0\uFE0F getCollection("${collectionId}") took ${duration.toFixed(2)}ms`
1015
- );
1016
- }
1017
- return result;
1018
- }
1019
- async _getCollection(collectionId, query = {}, options = {}) {
1020
1015
  const normalizedQuery = normalizeQuery(query);
1021
1016
  const {
1022
1017
  revalidate = this.defaultRevalidate,
@@ -1026,20 +1021,13 @@ var ContentEngine = class {
1026
1021
  } = options;
1027
1022
  const queryString = buildQueryString(normalizedQuery);
1028
1023
  const cacheKey = `collection:${collectionId}:${queryString}`;
1029
- const cached = await this.checkCaches(
1030
- cacheKey,
1031
- forceRefresh,
1032
- includeMetadata
1033
- );
1034
- if (cached) {
1035
- if (this.config.debug) {
1036
- console.log(
1037
- `[NexusHub] \u26A1 Served collection '${collectionId}' from memory cache.`
1038
- );
1024
+ if (!this.isServer && !forceRefresh && this.cacheStrategy === "memory") {
1025
+ const cached = this.memoryCache.get(cacheKey);
1026
+ if (cached && this.isCacheValid(cached.metadata)) {
1027
+ return includeMetadata ? cached : cached.data;
1039
1028
  }
1040
- return includeMetadata ? cached : cached.data;
1041
1029
  }
1042
- if (!forceRefresh) {
1030
+ if (!forceRefresh && process.env.NODE_ENV === "development") {
1043
1031
  try {
1044
1032
  const localCollection = await this.localCache.getCollection(collectionId);
1045
1033
  if (localCollection) {
@@ -1047,21 +1035,14 @@ var ContentEngine = class {
1047
1035
  localCollection,
1048
1036
  normalizedQuery
1049
1037
  );
1050
- if (this.config.debug) {
1051
- console.log(
1052
- `[NexusHub] \u{1F4C1} Compiled collection '${collectionId}' from local offline binaries.`
1053
- );
1054
- }
1055
- this.memoryCache.set(cacheKey, result, {
1056
- tags: [...tags, CacheTags.collection(collectionId)],
1057
- revalidate: revalidate === false ? void 0 : revalidate
1058
- });
1059
1038
  return result;
1060
1039
  }
1061
1040
  } catch {
1062
1041
  }
1063
1042
  }
1064
- if (this.circuitBreaker.isOpen()) throw new Error("Circuit open");
1043
+ if (this.circuitBreaker.isOpen()) {
1044
+ throw new Error("[NexusHub] Circuit open. API is unavailable.");
1045
+ }
1065
1046
  try {
1066
1047
  await this.rateLimiter.checkLimit();
1067
1048
  const result = await this.backoff.execute(async () => {
@@ -1070,19 +1051,25 @@ var ContentEngine = class {
1070
1051
  normalizedQuery,
1071
1052
  cacheKey,
1072
1053
  tags,
1073
- revalidate
1054
+ revalidate,
1055
+ forceRefresh
1074
1056
  );
1075
1057
  });
1076
1058
  this.circuitBreaker.recordSuccess();
1077
1059
  return includeMetadata ? result : result.data;
1078
1060
  } catch (error) {
1079
1061
  this.circuitBreaker.recordFailure();
1080
- const staleCache = this.memoryCache.get(cacheKey);
1081
- if (staleCache && !forceRefresh) {
1082
- console.warn(
1083
- `[NexusHub] \u26A0\uFE0F Using stale cache for collection '${collectionId}'`
1084
- );
1085
- return includeMetadata ? staleCache : staleCache.data;
1062
+ if (process.env.NODE_ENV === "development") {
1063
+ try {
1064
+ const localCollection = await this.localCache.getCollection(collectionId);
1065
+ if (localCollection) {
1066
+ return this.applyLocalQuery(
1067
+ localCollection,
1068
+ normalizedQuery
1069
+ );
1070
+ }
1071
+ } catch {
1072
+ }
1086
1073
  }
1087
1074
  throw this.normalizeError(
1088
1075
  error,
@@ -1090,9 +1077,6 @@ var ContentEngine = class {
1090
1077
  );
1091
1078
  }
1092
1079
  }
1093
- /**
1094
- * Fetch Global Settings
1095
- */
1096
1080
  async getGlobals(options = {}) {
1097
1081
  const {
1098
1082
  include = [],
@@ -1100,28 +1084,16 @@ var ContentEngine = class {
1100
1084
  forceRefresh = false
1101
1085
  } = options;
1102
1086
  const cacheKey = `globals:${include.join(",")}`;
1103
- if (!forceRefresh && this.cacheStrategy === "memory") {
1087
+ if (!this.isServer && !forceRefresh && this.cacheStrategy === "memory") {
1104
1088
  const cached = this.memoryCache.get(cacheKey);
1105
1089
  if (cached && this.isCacheValid(cached.metadata)) {
1106
- if (this.config.debug)
1107
- console.log("[NexusHub] \u26A1 Served globals from memory cache.");
1108
1090
  return cached.data;
1109
1091
  }
1110
1092
  }
1111
- if (!forceRefresh && !this.isServer && this.cacheStrategy === "localStorage" && this.browserCache) {
1112
- const cached = this.browserCache.get(cacheKey);
1113
- if (cached) {
1114
- return cached;
1115
- }
1116
- }
1117
- if (!forceRefresh) {
1093
+ if (!forceRefresh && process.env.NODE_ENV === "development") {
1118
1094
  try {
1119
1095
  const localGlobals = await this.localCache.getGlobals();
1120
1096
  if (localGlobals) {
1121
- this.memoryCache.set(cacheKey, localGlobals, {
1122
- tags: [CacheTags.global],
1123
- revalidate: revalidate === false ? void 0 : revalidate
1124
- });
1125
1097
  return localGlobals;
1126
1098
  }
1127
1099
  } catch {
@@ -1140,80 +1112,79 @@ var ContentEngine = class {
1140
1112
  method: "GET",
1141
1113
  headers: this.getHeaders(),
1142
1114
  tags: fetchTags,
1143
- revalidate
1115
+ revalidate,
1116
+ forceRefresh
1144
1117
  });
1145
1118
  if (!res.ok) {
1146
- if (res.status === 408) {
1147
- throw new Error("Request timeout");
1148
- }
1149
1119
  throw new Error(`API Error ${res.status}: ${res.statusText}`);
1150
1120
  }
1151
1121
  const json = await res.json();
1152
1122
  const data = json.data || json;
1153
- this.writeCache(cacheKey, data, {
1154
- revalidate,
1155
- tags: [CacheTags.project(this.config.projectId), CacheTags.global]
1156
- });
1123
+ if (!this.isServer) {
1124
+ this.writeCache(cacheKey, data, {
1125
+ revalidate,
1126
+ tags: fetchTags
1127
+ });
1128
+ }
1157
1129
  return data;
1158
1130
  } catch (error) {
1159
- const staleCache = this.memoryCache.get(cacheKey);
1160
- if (staleCache && !forceRefresh) {
1161
- console.warn("[NexusHub] \u26A0\uFE0F Using stale cache for globals");
1162
- return staleCache.data;
1131
+ if (process.env.NODE_ENV === "development") {
1132
+ try {
1133
+ const localGlobals = await this.localCache.getGlobals();
1134
+ if (localGlobals) {
1135
+ return localGlobals;
1136
+ }
1137
+ } catch {
1138
+ }
1163
1139
  }
1164
1140
  throw this.normalizeError(error, "Failed to fetch globals");
1165
1141
  }
1166
1142
  }
1167
- /**
1168
- * Get a single item from a collection (Uses Request Batching)
1169
- */
1170
1143
  async getItem(collectionId, itemId, options = {}) {
1144
+ const { forceRefresh = false } = options;
1171
1145
  const cacheKey = `item:${collectionId}:${itemId}`;
1172
- if (this.cacheStrategy === "memory") {
1146
+ if (!this.isServer && !forceRefresh && this.cacheStrategy === "memory") {
1173
1147
  const cached = this.memoryCache.get(cacheKey);
1174
1148
  if (cached && this.isCacheValid(cached.metadata)) {
1175
- if (this.config.debug) {
1176
- console.log(`[NexusHub] \u26A1 Served item '${itemId}' from cache.`);
1177
- }
1178
1149
  return cached.data;
1179
1150
  }
1180
1151
  }
1181
- return this.requestBatcher.schedule(cacheKey, async () => {
1182
- const params = new URLSearchParams();
1183
- if (options.include?.length) {
1184
- params.append("include", options.include.join(","));
1185
- }
1186
- const url = `${this.config.apiUrl}/content/${this.config.projectId}/collection/${collectionId}/${itemId}?${params}`;
1187
- const fetchTags = [
1188
- CacheTags.project(this.config.projectId),
1189
- CacheTags.collection(collectionId),
1190
- `item_${itemId}`,
1191
- ...options.tags || []
1192
- ];
1193
- const res = await this.fetchWithTimeout(url, {
1194
- method: "GET",
1195
- headers: this.getHeaders(),
1196
- tags: fetchTags,
1197
- revalidate: options.revalidate ?? this.defaultRevalidate
1198
- });
1199
- if (!res.ok) {
1200
- if (res.status === 408) {
1201
- throw new Error("Request timeout");
1152
+ return this.requestBatcher.schedule(
1153
+ forceRefresh ? `${cacheKey}:refresh:${Date.now()}` : cacheKey,
1154
+ async () => {
1155
+ const params = new URLSearchParams();
1156
+ if (options.include?.length) {
1157
+ params.append("include", options.include.join(","));
1202
1158
  }
1203
- throw new Error(`API Error ${res.status}: ${res.statusText}`);
1159
+ const url = `${this.config.apiUrl}/content/${this.config.projectId}/collection/${collectionId}/${itemId}?${params}`;
1160
+ const fetchTags = [
1161
+ CacheTags.project(this.config.projectId),
1162
+ CacheTags.collection(collectionId),
1163
+ `item_${itemId}`,
1164
+ ...options.tags || []
1165
+ ];
1166
+ const res = await this.fetchWithTimeout(url, {
1167
+ method: "GET",
1168
+ headers: this.getHeaders(),
1169
+ tags: fetchTags,
1170
+ revalidate: options.revalidate ?? this.defaultRevalidate,
1171
+ forceRefresh
1172
+ });
1173
+ if (!res.ok) {
1174
+ throw new Error(`API Error ${res.status}: ${res.statusText}`);
1175
+ }
1176
+ const json = await res.json();
1177
+ const data = json.data || json;
1178
+ if (!this.isServer) {
1179
+ this.writeCache(cacheKey, data, {
1180
+ revalidate: options.revalidate ?? this.defaultRevalidate,
1181
+ tags: fetchTags
1182
+ });
1183
+ }
1184
+ return data;
1204
1185
  }
1205
- const json = await res.json();
1206
- const data = json.data || json;
1207
- this.writeCache(cacheKey, data, {
1208
- revalidate: options.revalidate ?? this.defaultRevalidate,
1209
- tags: fetchTags
1210
- });
1211
- return data;
1212
- });
1186
+ );
1213
1187
  }
1214
- /**
1215
- * Search across collections
1216
- */
1217
1188
  async search(query, options = {}) {
1218
1189
  const params = new URLSearchParams({
1219
1190
  q: query,
@@ -1226,65 +1197,61 @@ var ContentEngine = class {
1226
1197
  params.append("fields", options.fields.join(","));
1227
1198
  }
1228
1199
  const url = `${this.config.apiUrl}/content/${this.config.projectId}/search?${params}`;
1200
+ const fetchTags = [
1201
+ CacheTags.project(this.config.projectId),
1202
+ ...(options.collections || []).map(
1203
+ (collection) => CacheTags.collection(collection)
1204
+ )
1205
+ ];
1229
1206
  const res = await this.fetchWithTimeout(url, {
1230
1207
  method: "GET",
1231
- headers: this.getHeaders()
1208
+ headers: this.getHeaders(),
1209
+ tags: fetchTags,
1210
+ revalidate: options.revalidate ?? this.defaultRevalidate,
1211
+ forceRefresh: options.forceRefresh ?? false
1232
1212
  });
1233
1213
  if (!res.ok) {
1234
- if (res.status === 408) {
1235
- throw new Error("Request timeout");
1236
- }
1237
1214
  throw new Error(`API Error ${res.status}: ${res.statusText}`);
1238
1215
  }
1239
1216
  return await res.json();
1240
1217
  }
1241
- /**
1242
- * Prefetch content
1243
- */
1244
1218
  async prefetch(urls) {
1245
1219
  if (typeof window !== "undefined" && "requestIdleCallback" in window) {
1246
1220
  requestIdleCallback(async () => {
1247
1221
  await Promise.allSettled(
1248
- urls.map((url) => fetch(url, { priority: "low" }))
1222
+ urls.map(
1223
+ (url) => fetch(url, {
1224
+ priority: "low"
1225
+ })
1226
+ )
1249
1227
  );
1250
1228
  });
1251
1229
  }
1252
1230
  }
1253
- /**
1254
- * Robust Real-time Subscriptions (SSE) with Auto-Reconnect
1255
- */
1256
1231
  subscribeToUpdates(callback) {
1257
1232
  if (this.isServer || typeof EventSource === "undefined") {
1258
- if (this.config.debug) {
1259
- console.warn(
1260
- "[NexusHub] subscribeToUpdates() called in a non-browser environment."
1261
- );
1262
- }
1263
1233
  return () => {
1264
1234
  };
1265
1235
  }
1266
1236
  if (!this.config.apiKey) {
1267
- console.warn("[NexusHub] subscribeToUpdates(): no apiKey configured.");
1237
+ return () => {
1238
+ };
1268
1239
  }
1269
1240
  let eventSource = null;
1270
1241
  let retryCount = 0;
1271
1242
  let isClosed = false;
1272
- const MAX_RETRY_DELAY_MS = 3e4;
1273
1243
  const connect = () => {
1274
1244
  if (isClosed) return;
1275
1245
  const url = `${this.config.apiUrl}/content/${this.config.projectId}/updates?key=${this.config.apiKey ?? ""}`;
1276
1246
  eventSource = new EventSource(url);
1277
1247
  eventSource.onopen = () => {
1278
1248
  retryCount = 0;
1279
- if (this.config.debug)
1280
- console.log("[NexusHub] \u{1F7E2} Real-time stream connected");
1281
1249
  };
1282
1250
  eventSource.onmessage = (event) => {
1283
1251
  let data;
1284
1252
  try {
1285
1253
  data = JSON.parse(event.data);
1286
- } catch (e) {
1287
- console.error("[NexusHub] SSE Parse Error", e);
1254
+ } catch {
1288
1255
  return;
1289
1256
  }
1290
1257
  if (!data || typeof data.type !== "string") {
@@ -1296,6 +1263,9 @@ var ContentEngine = class {
1296
1263
  if (data.type === "collection.updated" && data.collectionId) {
1297
1264
  this.invalidateCache([CacheTags.collection(data.collectionId)]);
1298
1265
  }
1266
+ if (data.type === "globals.updated") {
1267
+ this.invalidateCache([CacheTags.global]);
1268
+ }
1299
1269
  if (data.type === "schema.updated") {
1300
1270
  this.clearCache();
1301
1271
  }
@@ -1304,16 +1274,8 @@ var ContentEngine = class {
1304
1274
  eventSource.onerror = () => {
1305
1275
  eventSource?.close();
1306
1276
  if (isClosed) return;
1307
- const timeout = Math.min(
1308
- 1e3 * Math.pow(2, retryCount),
1309
- MAX_RETRY_DELAY_MS
1310
- );
1277
+ const timeout = Math.min(1e3 * Math.pow(2, retryCount), 3e4);
1311
1278
  retryCount++;
1312
- if (this.config.debug) {
1313
- console.warn(
1314
- `[NexusHub] \u{1F534} Stream disconnected. Reconnecting in ${timeout}ms...`
1315
- );
1316
- }
1317
1279
  setTimeout(connect, timeout);
1318
1280
  };
1319
1281
  };
@@ -1323,70 +1285,19 @@ var ContentEngine = class {
1323
1285
  eventSource?.close();
1324
1286
  };
1325
1287
  }
1326
- // --- CACHE MANAGEMENT ---
1327
- async checkCaches(key, forceRefresh, includeMetadata) {
1328
- if (forceRefresh) return null;
1329
- if (this.cacheStrategy === "memory") {
1330
- const cached = this.memoryCache.get(key);
1331
- if (cached && this.isCacheValid(cached.metadata)) {
1332
- return cached;
1333
- }
1334
- }
1335
- if (!this.isServer && this.cacheStrategy === "localStorage" && this.browserCache) {
1336
- const cached = this.browserCache.get(key);
1337
- if (cached) {
1338
- return {
1339
- data: cached,
1340
- metadata: {
1341
- timestamp: 0,
1342
- expiresAt: Infinity,
1343
- tags: [],
1344
- etag: void 0
1345
- }
1346
- };
1347
- }
1348
- }
1349
- if (key.startsWith("page:")) {
1350
- const slug = key.split(":")[1];
1351
- try {
1352
- const local = await this.localCache.getPage(slug);
1353
- if (local && (process.env.NODE_ENV === "development" || !this.config.apiKey)) {
1354
- return {
1355
- data: local,
1356
- metadata: {
1357
- timestamp: Date.now(),
1358
- expiresAt: Infinity,
1359
- tags: [
1360
- CacheTags.content(slug),
1361
- CacheTags.project(this.config.projectId)
1362
- ],
1363
- etag: void 0
1364
- }
1365
- };
1366
- }
1367
- } catch {
1368
- }
1369
- }
1370
- return null;
1371
- }
1372
1288
  writeCache(key, data, options) {
1373
- const numericRevalidate = options.revalidate === false ? 31536e5 : options.revalidate;
1289
+ const ttlSeconds = options.revalidate === false ? 60 : Math.max(1, options.revalidate);
1374
1290
  if (this.cacheStrategy === "memory") {
1375
1291
  this.memoryCache.set(key, data, {
1376
1292
  tags: options.tags,
1377
- revalidate: numericRevalidate
1293
+ ttl: ttlSeconds * 1e3
1378
1294
  });
1379
1295
  }
1380
- if (!this.isServer && this.cacheStrategy === "localStorage" && this.browserCache) {
1381
- this.browserCache.set(key, data, numericRevalidate * 1e3);
1382
- }
1383
1296
  }
1384
1297
  invalidateCache(tags) {
1385
1298
  this.memoryCache.invalidateByTags(tags);
1386
- if (this.config.debug) {
1387
- console.log(
1388
- `[NexusHub] \u267B\uFE0F Invalidated cache for tags: ${tags.join(", ")}`
1389
- );
1299
+ if (this.browserCache) {
1300
+ this.browserCache.clear();
1390
1301
  }
1391
1302
  }
1392
1303
  clearCache() {
@@ -1394,24 +1305,17 @@ var ContentEngine = class {
1394
1305
  if (this.browserCache) {
1395
1306
  this.browserCache.clear();
1396
1307
  }
1397
- if (this.config.debug) {
1398
- console.log("[NexusHub] \u{1F9F9} Cleared all caches");
1399
- }
1400
1308
  }
1401
1309
  getCacheStats() {
1402
- const stats = {
1310
+ return {
1403
1311
  memory: this.memoryCache.getStats(),
1404
- local: { loaded: this.localCache.isLoaded() }
1312
+ local: {
1313
+ loaded: this.localCache.isLoaded()
1314
+ }
1405
1315
  };
1406
- if (this.browserCache) {
1407
- stats.browser = { size: 0 };
1408
- }
1409
- return stats;
1410
1316
  }
1411
- // --- REQUEST METHODS ---
1412
1317
  async fetchPage(slug, cacheKey, tags, revalidate, forceRefresh) {
1413
1318
  const url = `${this.config.apiUrl}/content/${this.config.projectId}/page/${slug}`;
1414
- const expiresAt = revalidate === false ? Infinity : Date.now() + revalidate * 1e3;
1415
1319
  const fetchTags = [
1416
1320
  CacheTags.project(this.config.projectId),
1417
1321
  CacheTags.content(slug),
@@ -1421,13 +1325,12 @@ var ContentEngine = class {
1421
1325
  method: "GET",
1422
1326
  headers: this.getHeaders(),
1423
1327
  tags: fetchTags,
1424
- revalidate
1328
+ revalidate,
1329
+ forceRefresh
1425
1330
  });
1426
1331
  if (!res.ok) {
1427
1332
  if (res.status === 404) {
1428
- throw new Error(
1429
- `Page '${slug}' not found. Check your Dashboard or Seed data.`
1430
- );
1333
+ throw new Error(`Page '${slug}' not found.`);
1431
1334
  }
1432
1335
  if (res.status === 408) {
1433
1336
  throw new Error("Request timeout");
@@ -1441,17 +1344,19 @@ var ContentEngine = class {
1441
1344
  metadata: {
1442
1345
  timestamp: Date.now(),
1443
1346
  etag: etag || void 0,
1444
- expiresAt,
1347
+ expiresAt: revalidate === false ? Infinity : Date.now() + revalidate * 1e3,
1445
1348
  tags: fetchTags
1446
1349
  }
1447
1350
  };
1448
- this.writeCache(cacheKey, cacheEntry.data, {
1449
- revalidate,
1450
- tags: fetchTags
1451
- });
1351
+ if (!this.isServer) {
1352
+ this.writeCache(cacheKey, cacheEntry.data, {
1353
+ revalidate,
1354
+ tags: fetchTags
1355
+ });
1356
+ }
1452
1357
  return cacheEntry;
1453
1358
  }
1454
- async fetchCollection(collectionId, query, cacheKey, tags, revalidate) {
1359
+ async fetchCollection(collectionId, query, cacheKey, tags, revalidate, forceRefresh) {
1455
1360
  const params = buildQueryString(query);
1456
1361
  const url = `${this.config.apiUrl}/content/${this.config.projectId}/collection/${collectionId}?${params}`;
1457
1362
  const fetchTags = [
@@ -1463,7 +1368,8 @@ var ContentEngine = class {
1463
1368
  method: "GET",
1464
1369
  headers: this.getHeaders(),
1465
1370
  tags: fetchTags,
1466
- revalidate
1371
+ revalidate,
1372
+ forceRefresh
1467
1373
  });
1468
1374
  if (!res.ok) {
1469
1375
  if (res.status === 408) {
@@ -1473,23 +1379,23 @@ var ContentEngine = class {
1473
1379
  }
1474
1380
  const json = await res.json();
1475
1381
  const etag = res.headers.get("etag");
1476
- const expiresAt = revalidate === false ? Infinity : Date.now() + revalidate * 1e3;
1477
1382
  const cacheEntry = {
1478
1383
  data: json,
1479
1384
  metadata: {
1480
1385
  timestamp: Date.now(),
1481
1386
  etag: etag || void 0,
1482
- expiresAt,
1387
+ expiresAt: revalidate === false ? Infinity : Date.now() + revalidate * 1e3,
1483
1388
  tags: fetchTags
1484
1389
  }
1485
1390
  };
1486
- this.writeCache(cacheKey, cacheEntry.data, {
1487
- revalidate,
1488
- tags: fetchTags
1489
- });
1391
+ if (!this.isServer) {
1392
+ this.writeCache(cacheKey, cacheEntry.data, {
1393
+ revalidate,
1394
+ tags: fetchTags
1395
+ });
1396
+ }
1490
1397
  return cacheEntry;
1491
1398
  }
1492
- // --- HELPER METHODS ---
1493
1399
  applyLocalQuery(items, query) {
1494
1400
  let filtered = [...items];
1495
1401
  if (query.search) {
@@ -1502,7 +1408,9 @@ var ContentEngine = class {
1502
1408
  filtered = filtered.filter((item) => {
1503
1409
  return Object.entries(query.filter).every(([key, value]) => {
1504
1410
  const itemValue = item[key];
1505
- if (itemValue === void 0) return false;
1411
+ if (itemValue === void 0) {
1412
+ return false;
1413
+ }
1506
1414
  if (Array.isArray(value)) {
1507
1415
  return value.includes(itemValue);
1508
1416
  }
@@ -1515,8 +1423,12 @@ var ContentEngine = class {
1515
1423
  const aVal = a[query.sort];
1516
1424
  const bVal = b[query.sort];
1517
1425
  const order = query.order === "asc" ? 1 : -1;
1518
- if (aVal < bVal) return -1 * order;
1519
- if (aVal > bVal) return 1 * order;
1426
+ if (aVal < bVal) {
1427
+ return -1 * order;
1428
+ }
1429
+ if (aVal > bVal) {
1430
+ return 1 * order;
1431
+ }
1520
1432
  return 0;
1521
1433
  });
1522
1434
  }
@@ -1540,15 +1452,18 @@ var ContentEngine = class {
1540
1452
  timeout = this.config.timeout || 1e4,
1541
1453
  tags = [],
1542
1454
  revalidate,
1455
+ forceRefresh = false,
1543
1456
  ...fetchOptions
1544
1457
  } = options;
1545
1458
  const controller = new AbortController();
1546
1459
  const id = setTimeout(() => controller.abort(), timeout);
1547
1460
  const nextConfig = this.isServer ? {
1548
- cache: revalidate === 0 ? "no-store" : "force-cache",
1549
- next: {
1550
- tags,
1551
- revalidate: revalidate === false ? false : revalidate
1461
+ cache: forceRefresh || revalidate === 0 ? "no-store" : "force-cache",
1462
+ ...forceRefresh ? {} : {
1463
+ next: {
1464
+ tags,
1465
+ revalidate: revalidate === false ? false : revalidate
1466
+ }
1552
1467
  }
1553
1468
  } : {};
1554
1469
  try {
@@ -1567,9 +1482,9 @@ var ContentEngine = class {
1567
1482
  getHeaders() {
1568
1483
  const headers = {
1569
1484
  "Content-Type": "application/json",
1570
- "X-Nexus-Client": `client-sdk/${this.config.sdkVersion ?? "1.1.0"}`,
1571
- "X-Nexus-Request-ID": typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `nx_${Date.now()}_${Math.random().toString(36).slice(2)}`,
1572
- "X-Nexus-Project": this.config.projectId
1485
+ "X-GN-Apex-Client": `gnapex-sdk/${this.config.sdkVersion ?? "1.1.6"}`,
1486
+ "X-GN-Apex-Request-ID": typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `nx_${Date.now()}_${Math.random().toString(36).slice(2)}`,
1487
+ "X-GN-Apex-Project": this.config.projectId
1573
1488
  };
1574
1489
  if (this.config.apiKey) {
1575
1490
  headers["Authorization"] = `Bearer ${this.config.apiKey}`;
@@ -1579,10 +1494,6 @@ var ContentEngine = class {
1579
1494
  isCacheValid(metadata) {
1580
1495
  return Date.now() < metadata.expiresAt;
1581
1496
  }
1582
- /**
1583
- * 🛡️ FIX: Normalizes timeout errors (AbortError, 408, or Timeout messages)
1584
- * into clean, standardized "Request timeout" errors.
1585
- */
1586
1497
  normalizeError(error, context) {
1587
1498
  if (error instanceof Error) {
1588
1499
  const msg = error.message.toLowerCase();
@@ -1597,14 +1508,7 @@ var ContentEngine = class {
1597
1508
  }
1598
1509
  return new Error(`${context}: ${String(error)}`);
1599
1510
  }
1600
- cancelRequests() {
1601
- if (this.abortController) {
1602
- this.abortController.abort();
1603
- this.abortController = new AbortController();
1604
- }
1605
- }
1606
1511
  cleanup() {
1607
- this.cancelRequests();
1608
1512
  if (!this.isServer) {
1609
1513
  window.removeEventListener("beforeunload", this.cleanup.bind(this));
1610
1514
  }