@spoosh/core 0.7.0 → 0.8.0

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;
@@ -1175,13 +1178,6 @@ function createOperationController(options) {
1175
1178
  try {
1176
1179
  const response = await fetchFn(context.requestOptions);
1177
1180
  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
1181
  return response;
1186
1182
  } catch (err) {
1187
1183
  const errorResponse = {
@@ -1199,11 +1195,19 @@ function createOperationController(options) {
1199
1195
  });
1200
1196
  return fetchPromise;
1201
1197
  };
1202
- return pluginExecutor.executeMiddleware(
1198
+ const finalResponse = await pluginExecutor.executeMiddleware(
1203
1199
  operationType,
1204
1200
  context,
1205
1201
  coreFetch
1206
1202
  );
1203
+ if (finalResponse.data !== void 0 && !finalResponse.error) {
1204
+ updateState({
1205
+ data: finalResponse.data,
1206
+ error: void 0,
1207
+ timestamp: Date.now()
1208
+ });
1209
+ }
1210
+ return finalResponse;
1207
1211
  },
1208
1212
  getState() {
1209
1213
  const cached = stateManager.getCache(queryKey);
@@ -1462,34 +1466,6 @@ function createInfiniteReadController(options) {
1462
1466
  aborted: true
1463
1467
  };
1464
1468
  }
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
1469
  return response;
1494
1470
  } catch (err) {
1495
1471
  if (signal.aborted) {
@@ -1517,7 +1493,37 @@ function createInfiniteReadController(options) {
1517
1493
  stateManager.setPendingPromise(pageKey, fetchPromise);
1518
1494
  return fetchPromise;
1519
1495
  };
1520
- await pluginExecutor.executeMiddleware("infiniteRead", context, coreFetch);
1496
+ const finalResponse = await pluginExecutor.executeMiddleware(
1497
+ "infiniteRead",
1498
+ context,
1499
+ coreFetch
1500
+ );
1501
+ if (finalResponse.data !== void 0 && !finalResponse.error) {
1502
+ pageRequests.set(pageKey, mergedRequest);
1503
+ if (direction === "next") {
1504
+ if (!pageKeys.includes(pageKey)) {
1505
+ pageKeys = [...pageKeys, pageKey];
1506
+ }
1507
+ } else {
1508
+ if (!pageKeys.includes(pageKey)) {
1509
+ pageKeys = [pageKey, ...pageKeys];
1510
+ }
1511
+ }
1512
+ saveToTracker();
1513
+ subscribeToPages();
1514
+ stateManager.setCache(pageKey, {
1515
+ state: {
1516
+ data: finalResponse.data,
1517
+ error: void 0,
1518
+ timestamp: Date.now()
1519
+ },
1520
+ tags,
1521
+ stale: false
1522
+ });
1523
+ latestError = void 0;
1524
+ } else if (finalResponse.error) {
1525
+ latestError = finalResponse.error;
1526
+ }
1521
1527
  };
1522
1528
  const controller = {
1523
1529
  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;
@@ -1119,13 +1122,6 @@ function createOperationController(options) {
1119
1122
  try {
1120
1123
  const response = await fetchFn(context.requestOptions);
1121
1124
  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
1125
  return response;
1130
1126
  } catch (err) {
1131
1127
  const errorResponse = {
@@ -1143,11 +1139,19 @@ function createOperationController(options) {
1143
1139
  });
1144
1140
  return fetchPromise;
1145
1141
  };
1146
- return pluginExecutor.executeMiddleware(
1142
+ const finalResponse = await pluginExecutor.executeMiddleware(
1147
1143
  operationType,
1148
1144
  context,
1149
1145
  coreFetch
1150
1146
  );
1147
+ if (finalResponse.data !== void 0 && !finalResponse.error) {
1148
+ updateState({
1149
+ data: finalResponse.data,
1150
+ error: void 0,
1151
+ timestamp: Date.now()
1152
+ });
1153
+ }
1154
+ return finalResponse;
1151
1155
  },
1152
1156
  getState() {
1153
1157
  const cached = stateManager.getCache(queryKey);
@@ -1406,34 +1410,6 @@ function createInfiniteReadController(options) {
1406
1410
  aborted: true
1407
1411
  };
1408
1412
  }
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
1413
  return response;
1438
1414
  } catch (err) {
1439
1415
  if (signal.aborted) {
@@ -1461,7 +1437,37 @@ function createInfiniteReadController(options) {
1461
1437
  stateManager.setPendingPromise(pageKey, fetchPromise);
1462
1438
  return fetchPromise;
1463
1439
  };
1464
- await pluginExecutor.executeMiddleware("infiniteRead", context, coreFetch);
1440
+ const finalResponse = await pluginExecutor.executeMiddleware(
1441
+ "infiniteRead",
1442
+ context,
1443
+ coreFetch
1444
+ );
1445
+ if (finalResponse.data !== void 0 && !finalResponse.error) {
1446
+ pageRequests.set(pageKey, mergedRequest);
1447
+ if (direction === "next") {
1448
+ if (!pageKeys.includes(pageKey)) {
1449
+ pageKeys = [...pageKeys, pageKey];
1450
+ }
1451
+ } else {
1452
+ if (!pageKeys.includes(pageKey)) {
1453
+ pageKeys = [pageKey, ...pageKeys];
1454
+ }
1455
+ }
1456
+ saveToTracker();
1457
+ subscribeToPages();
1458
+ stateManager.setCache(pageKey, {
1459
+ state: {
1460
+ data: finalResponse.data,
1461
+ error: void 0,
1462
+ timestamp: Date.now()
1463
+ },
1464
+ tags,
1465
+ stale: false
1466
+ });
1467
+ latestError = void 0;
1468
+ } else if (finalResponse.error) {
1469
+ latestError = finalResponse.error;
1470
+ }
1465
1471
  };
1466
1472
  const controller = {
1467
1473
  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.0",
4
4
  "license": "MIT",
5
5
  "description": "Type-safe API client with plugin middleware system",
6
6
  "keywords": [