@spoosh/core 0.7.0 → 0.8.1

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.d.mts CHANGED
@@ -287,9 +287,10 @@ type PluginHandler<TData = unknown, TError = unknown> = (context: PluginContext<
287
287
  type PluginUpdateHandler<TData = unknown, TError = unknown> = (context: PluginContext<TData, TError>, previousContext: PluginContext<TData, TError>) => void | Promise<void>;
288
288
  /**
289
289
  * Handler called after every response, regardless of early returns from middleware.
290
- * Use this for post-response logic like scheduling polls or emitting events.
290
+ * Can return a new response to transform it, or void for side effects only.
291
+ * Returned responses are chained through plugins in order.
291
292
  */
292
- type PluginResponseHandler<TData = unknown, TError = unknown> = (context: PluginContext<TData, TError>, response: SpooshResponse<TData, TError>) => void | Promise<void>;
293
+ type PluginResponseHandler<TData = unknown, TError = unknown> = (context: PluginContext<TData, TError>, response: SpooshResponse<TData, TError>) => SpooshResponse<TData, TError> | void | Promise<SpooshResponse<TData, TError> | void>;
293
294
  type PluginLifecycle<TData = unknown, TError = unknown> = {
294
295
  /** Called on component mount */
295
296
  onMount?: PluginHandler<TData, TError>;
@@ -333,7 +334,7 @@ type PluginTypeConfig = {
333
334
  *
334
335
  * Plugins can implement:
335
336
  * - `middleware`: Wraps the fetch flow for full control (intercept, retry, transform)
336
- * - `onResponse`: Called after every response, regardless of early returns
337
+ * - `afterResponse`: Called after every response, regardless of early returns
337
338
  * - `lifecycle`: Component lifecycle hooks (onMount, onUpdate, onUnmount)
338
339
  * - `exports`: Functions/variables accessible to other plugins
339
340
  *
@@ -353,7 +354,7 @@ type PluginTypeConfig = {
353
354
  * const result = await next();
354
355
  * return result;
355
356
  * },
356
- * onResponse(context, response) {
357
+ * afterResponse(context, response) {
357
358
  * // Always runs after response
358
359
  * },
359
360
  * lifecycle: {
@@ -370,8 +371,11 @@ interface SpooshPlugin<T extends PluginTypeConfig = PluginTypeConfig> {
370
371
  operations: OperationType[];
371
372
  /** Middleware for controlling the fetch flow. Called in plugin order, composing a chain. */
372
373
  middleware?: PluginMiddleware;
373
- /** Called after every response, regardless of early returns from middleware. */
374
- onResponse?: PluginResponseHandler;
374
+ /**
375
+ * Called after middleware chain completes, regardless of early returns.
376
+ * Return a new response to transform it, or void for side effects only.
377
+ */
378
+ afterResponse?: PluginResponseHandler;
375
379
  /** Component lifecycle hooks (setup, cleanup, option changes) */
376
380
  lifecycle?: PluginLifecycle;
377
381
  /** Expose functions/variables for other plugins to access via `context.plugins.get(name)` */
@@ -568,7 +572,7 @@ type PluginExecutor = {
568
572
  executeLifecycle: <TData, TError>(phase: "onMount" | "onUnmount", operationType: OperationType, context: PluginContext<TData, TError>) => Promise<void>;
569
573
  /** Execute onUpdate lifecycle with previous context */
570
574
  executeUpdateLifecycle: <TData, TError>(operationType: OperationType, context: PluginContext<TData, TError>, previousContext: PluginContext<TData, TError>) => Promise<void>;
571
- /** Execute middleware chain with a core fetch function, then run onResponse handlers */
575
+ /** Execute middleware chain with a core fetch function, then run afterResponse handlers */
572
576
  executeMiddleware: <TData, TError>(operationType: OperationType, context: PluginContext<TData, TError>, coreFetch: () => Promise<SpooshResponse<TData, TError>>) => Promise<SpooshResponse<TData, TError>>;
573
577
  getPlugins: () => readonly SpooshPlugin[];
574
578
  /** Creates a full PluginContext with plugins accessor injected */
package/dist/index.d.ts CHANGED
@@ -287,9 +287,10 @@ type PluginHandler<TData = unknown, TError = unknown> = (context: PluginContext<
287
287
  type PluginUpdateHandler<TData = unknown, TError = unknown> = (context: PluginContext<TData, TError>, previousContext: PluginContext<TData, TError>) => void | Promise<void>;
288
288
  /**
289
289
  * Handler called after every response, regardless of early returns from middleware.
290
- * Use this for post-response logic like scheduling polls or emitting events.
290
+ * Can return a new response to transform it, or void for side effects only.
291
+ * Returned responses are chained through plugins in order.
291
292
  */
292
- type PluginResponseHandler<TData = unknown, TError = unknown> = (context: PluginContext<TData, TError>, response: SpooshResponse<TData, TError>) => void | Promise<void>;
293
+ type PluginResponseHandler<TData = unknown, TError = unknown> = (context: PluginContext<TData, TError>, response: SpooshResponse<TData, TError>) => SpooshResponse<TData, TError> | void | Promise<SpooshResponse<TData, TError> | void>;
293
294
  type PluginLifecycle<TData = unknown, TError = unknown> = {
294
295
  /** Called on component mount */
295
296
  onMount?: PluginHandler<TData, TError>;
@@ -333,7 +334,7 @@ type PluginTypeConfig = {
333
334
  *
334
335
  * Plugins can implement:
335
336
  * - `middleware`: Wraps the fetch flow for full control (intercept, retry, transform)
336
- * - `onResponse`: Called after every response, regardless of early returns
337
+ * - `afterResponse`: Called after every response, regardless of early returns
337
338
  * - `lifecycle`: Component lifecycle hooks (onMount, onUpdate, onUnmount)
338
339
  * - `exports`: Functions/variables accessible to other plugins
339
340
  *
@@ -353,7 +354,7 @@ type PluginTypeConfig = {
353
354
  * const result = await next();
354
355
  * return result;
355
356
  * },
356
- * onResponse(context, response) {
357
+ * afterResponse(context, response) {
357
358
  * // Always runs after response
358
359
  * },
359
360
  * lifecycle: {
@@ -370,8 +371,11 @@ interface SpooshPlugin<T extends PluginTypeConfig = PluginTypeConfig> {
370
371
  operations: OperationType[];
371
372
  /** Middleware for controlling the fetch flow. Called in plugin order, composing a chain. */
372
373
  middleware?: PluginMiddleware;
373
- /** Called after every response, regardless of early returns from middleware. */
374
- onResponse?: PluginResponseHandler;
374
+ /**
375
+ * Called after middleware chain completes, regardless of early returns.
376
+ * Return a new response to transform it, or void for side effects only.
377
+ */
378
+ afterResponse?: PluginResponseHandler;
375
379
  /** Component lifecycle hooks (setup, cleanup, option changes) */
376
380
  lifecycle?: PluginLifecycle;
377
381
  /** Expose functions/variables for other plugins to access via `context.plugins.get(name)` */
@@ -568,7 +572,7 @@ type PluginExecutor = {
568
572
  executeLifecycle: <TData, TError>(phase: "onMount" | "onUnmount", operationType: OperationType, context: PluginContext<TData, TError>) => Promise<void>;
569
573
  /** Execute onUpdate lifecycle with previous context */
570
574
  executeUpdateLifecycle: <TData, TError>(operationType: OperationType, context: PluginContext<TData, TError>, previousContext: PluginContext<TData, TError>) => Promise<void>;
571
- /** Execute middleware chain with a core fetch function, then run onResponse handlers */
575
+ /** Execute middleware chain with a core fetch function, then run afterResponse handlers */
572
576
  executeMiddleware: <TData, TError>(operationType: OperationType, context: PluginContext<TData, TError>, coreFetch: () => Promise<SpooshResponse<TData, TError>>) => Promise<SpooshResponse<TData, TError>>;
573
577
  getPlugins: () => readonly SpooshPlugin[];
574
578
  /** Creates a full PluginContext with plugins accessor injected */
package/dist/index.js CHANGED
@@ -836,11 +836,14 @@ function createPluginExecutor(initialPlugins = []) {
836
836
  response = await chain();
837
837
  }
838
838
  for (const plugin of applicablePlugins) {
839
- if (plugin.onResponse) {
840
- await plugin.onResponse(
839
+ if (plugin.afterResponse) {
840
+ const newResponse = await plugin.afterResponse(
841
841
  context,
842
842
  response
843
843
  );
844
+ if (newResponse) {
845
+ response = newResponse;
846
+ }
844
847
  }
845
848
  }
846
849
  return response;
@@ -1144,12 +1147,14 @@ function createOperationController(options) {
1144
1147
  const cached = stateManager.getCache(queryKey);
1145
1148
  if (cached) {
1146
1149
  stateManager.setCache(queryKey, {
1147
- state: { ...cached.state, ...updater }
1150
+ state: { ...cached.state, ...updater },
1151
+ stale: false
1148
1152
  });
1149
1153
  } else {
1150
1154
  stateManager.setCache(queryKey, {
1151
1155
  state: { ...createInitialState(), ...updater },
1152
- tags
1156
+ tags,
1157
+ stale: false
1153
1158
  });
1154
1159
  }
1155
1160
  };
@@ -1175,13 +1180,6 @@ function createOperationController(options) {
1175
1180
  try {
1176
1181
  const response = await fetchFn(context.requestOptions);
1177
1182
  context.response = response;
1178
- if (response.data !== void 0 && !response.error) {
1179
- updateState({
1180
- data: response.data,
1181
- error: void 0,
1182
- timestamp: Date.now()
1183
- });
1184
- }
1185
1183
  return response;
1186
1184
  } catch (err) {
1187
1185
  const errorResponse = {
@@ -1199,11 +1197,19 @@ function createOperationController(options) {
1199
1197
  });
1200
1198
  return fetchPromise;
1201
1199
  };
1202
- return pluginExecutor.executeMiddleware(
1200
+ const finalResponse = await pluginExecutor.executeMiddleware(
1203
1201
  operationType,
1204
1202
  context,
1205
1203
  coreFetch
1206
1204
  );
1205
+ if (finalResponse.data !== void 0 && !finalResponse.error) {
1206
+ updateState({
1207
+ data: finalResponse.data,
1208
+ error: void 0,
1209
+ timestamp: Date.now()
1210
+ });
1211
+ }
1212
+ return finalResponse;
1207
1213
  },
1208
1214
  getState() {
1209
1215
  const cached = stateManager.getCache(queryKey);
@@ -1462,34 +1468,6 @@ function createInfiniteReadController(options) {
1462
1468
  aborted: true
1463
1469
  };
1464
1470
  }
1465
- if (response.data !== void 0 && !response.error) {
1466
- pageRequests.set(pageKey, mergedRequest);
1467
- if (direction === "next") {
1468
- if (!pageKeys.includes(pageKey)) {
1469
- pageKeys = [...pageKeys, pageKey];
1470
- }
1471
- } else {
1472
- if (!pageKeys.includes(pageKey)) {
1473
- pageKeys = [pageKey, ...pageKeys];
1474
- }
1475
- }
1476
- saveToTracker();
1477
- subscribeToPages();
1478
- stateManager.setCache(pageKey, {
1479
- state: {
1480
- data: response.data,
1481
- error: void 0,
1482
- timestamp: Date.now()
1483
- },
1484
- tags,
1485
- stale: false
1486
- });
1487
- }
1488
- if (response.data !== void 0 && !response.error) {
1489
- latestError = void 0;
1490
- } else if (response.error) {
1491
- latestError = response.error;
1492
- }
1493
1471
  return response;
1494
1472
  } catch (err) {
1495
1473
  if (signal.aborted) {
@@ -1517,7 +1495,37 @@ function createInfiniteReadController(options) {
1517
1495
  stateManager.setPendingPromise(pageKey, fetchPromise);
1518
1496
  return fetchPromise;
1519
1497
  };
1520
- await pluginExecutor.executeMiddleware("infiniteRead", context, coreFetch);
1498
+ const finalResponse = await pluginExecutor.executeMiddleware(
1499
+ "infiniteRead",
1500
+ context,
1501
+ coreFetch
1502
+ );
1503
+ if (finalResponse.data !== void 0 && !finalResponse.error) {
1504
+ pageRequests.set(pageKey, mergedRequest);
1505
+ if (direction === "next") {
1506
+ if (!pageKeys.includes(pageKey)) {
1507
+ pageKeys = [...pageKeys, pageKey];
1508
+ }
1509
+ } else {
1510
+ if (!pageKeys.includes(pageKey)) {
1511
+ pageKeys = [pageKey, ...pageKeys];
1512
+ }
1513
+ }
1514
+ saveToTracker();
1515
+ subscribeToPages();
1516
+ stateManager.setCache(pageKey, {
1517
+ state: {
1518
+ data: finalResponse.data,
1519
+ error: void 0,
1520
+ timestamp: Date.now()
1521
+ },
1522
+ tags,
1523
+ stale: false
1524
+ });
1525
+ latestError = void 0;
1526
+ } else if (finalResponse.error) {
1527
+ latestError = finalResponse.error;
1528
+ }
1521
1529
  };
1522
1530
  const controller = {
1523
1531
  getState() {
package/dist/index.mjs CHANGED
@@ -780,11 +780,14 @@ function createPluginExecutor(initialPlugins = []) {
780
780
  response = await chain();
781
781
  }
782
782
  for (const plugin of applicablePlugins) {
783
- if (plugin.onResponse) {
784
- await plugin.onResponse(
783
+ if (plugin.afterResponse) {
784
+ const newResponse = await plugin.afterResponse(
785
785
  context,
786
786
  response
787
787
  );
788
+ if (newResponse) {
789
+ response = newResponse;
790
+ }
788
791
  }
789
792
  }
790
793
  return response;
@@ -1088,12 +1091,14 @@ function createOperationController(options) {
1088
1091
  const cached = stateManager.getCache(queryKey);
1089
1092
  if (cached) {
1090
1093
  stateManager.setCache(queryKey, {
1091
- state: { ...cached.state, ...updater }
1094
+ state: { ...cached.state, ...updater },
1095
+ stale: false
1092
1096
  });
1093
1097
  } else {
1094
1098
  stateManager.setCache(queryKey, {
1095
1099
  state: { ...createInitialState(), ...updater },
1096
- tags
1100
+ tags,
1101
+ stale: false
1097
1102
  });
1098
1103
  }
1099
1104
  };
@@ -1119,13 +1124,6 @@ function createOperationController(options) {
1119
1124
  try {
1120
1125
  const response = await fetchFn(context.requestOptions);
1121
1126
  context.response = response;
1122
- if (response.data !== void 0 && !response.error) {
1123
- updateState({
1124
- data: response.data,
1125
- error: void 0,
1126
- timestamp: Date.now()
1127
- });
1128
- }
1129
1127
  return response;
1130
1128
  } catch (err) {
1131
1129
  const errorResponse = {
@@ -1143,11 +1141,19 @@ function createOperationController(options) {
1143
1141
  });
1144
1142
  return fetchPromise;
1145
1143
  };
1146
- return pluginExecutor.executeMiddleware(
1144
+ const finalResponse = await pluginExecutor.executeMiddleware(
1147
1145
  operationType,
1148
1146
  context,
1149
1147
  coreFetch
1150
1148
  );
1149
+ if (finalResponse.data !== void 0 && !finalResponse.error) {
1150
+ updateState({
1151
+ data: finalResponse.data,
1152
+ error: void 0,
1153
+ timestamp: Date.now()
1154
+ });
1155
+ }
1156
+ return finalResponse;
1151
1157
  },
1152
1158
  getState() {
1153
1159
  const cached = stateManager.getCache(queryKey);
@@ -1406,34 +1412,6 @@ function createInfiniteReadController(options) {
1406
1412
  aborted: true
1407
1413
  };
1408
1414
  }
1409
- if (response.data !== void 0 && !response.error) {
1410
- pageRequests.set(pageKey, mergedRequest);
1411
- if (direction === "next") {
1412
- if (!pageKeys.includes(pageKey)) {
1413
- pageKeys = [...pageKeys, pageKey];
1414
- }
1415
- } else {
1416
- if (!pageKeys.includes(pageKey)) {
1417
- pageKeys = [pageKey, ...pageKeys];
1418
- }
1419
- }
1420
- saveToTracker();
1421
- subscribeToPages();
1422
- stateManager.setCache(pageKey, {
1423
- state: {
1424
- data: response.data,
1425
- error: void 0,
1426
- timestamp: Date.now()
1427
- },
1428
- tags,
1429
- stale: false
1430
- });
1431
- }
1432
- if (response.data !== void 0 && !response.error) {
1433
- latestError = void 0;
1434
- } else if (response.error) {
1435
- latestError = response.error;
1436
- }
1437
1415
  return response;
1438
1416
  } catch (err) {
1439
1417
  if (signal.aborted) {
@@ -1461,7 +1439,37 @@ function createInfiniteReadController(options) {
1461
1439
  stateManager.setPendingPromise(pageKey, fetchPromise);
1462
1440
  return fetchPromise;
1463
1441
  };
1464
- await pluginExecutor.executeMiddleware("infiniteRead", context, coreFetch);
1442
+ const finalResponse = await pluginExecutor.executeMiddleware(
1443
+ "infiniteRead",
1444
+ context,
1445
+ coreFetch
1446
+ );
1447
+ if (finalResponse.data !== void 0 && !finalResponse.error) {
1448
+ pageRequests.set(pageKey, mergedRequest);
1449
+ if (direction === "next") {
1450
+ if (!pageKeys.includes(pageKey)) {
1451
+ pageKeys = [...pageKeys, pageKey];
1452
+ }
1453
+ } else {
1454
+ if (!pageKeys.includes(pageKey)) {
1455
+ pageKeys = [pageKey, ...pageKeys];
1456
+ }
1457
+ }
1458
+ saveToTracker();
1459
+ subscribeToPages();
1460
+ stateManager.setCache(pageKey, {
1461
+ state: {
1462
+ data: finalResponse.data,
1463
+ error: void 0,
1464
+ timestamp: Date.now()
1465
+ },
1466
+ tags,
1467
+ stale: false
1468
+ });
1469
+ latestError = void 0;
1470
+ } else if (finalResponse.error) {
1471
+ latestError = finalResponse.error;
1472
+ }
1465
1473
  };
1466
1474
  const controller = {
1467
1475
  getState() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spoosh/core",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "license": "MIT",
5
5
  "description": "Type-safe API client with plugin middleware system",
6
6
  "keywords": [