@spoosh/core 0.13.1 → 0.13.3

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
@@ -62,6 +62,7 @@ declare function urlencoded<T>(value: T): SpooshBody<T>;
62
62
  declare function resolveRequestBody(rawBody: unknown): {
63
63
  body: BodyInit;
64
64
  headers?: Record<string, string>;
65
+ removeHeaders?: string[];
65
66
  } | undefined;
66
67
 
67
68
  interface TransportResponse {
@@ -1723,6 +1724,7 @@ declare function setHeaders(requestOptions: {
1723
1724
  * Returns undefined if no Content-Type is set.
1724
1725
  */
1725
1726
  declare function getContentType(headers?: HeadersInit): string | undefined;
1727
+ declare function removeHeaderKeys(headers: HeadersInit | undefined, keysToRemove: string[]): HeadersInit | undefined;
1726
1728
 
1727
1729
  declare function objectToFormData(obj: Record<string, unknown>): FormData;
1728
1730
 
@@ -1748,6 +1750,21 @@ type TagOptions = {
1748
1750
  };
1749
1751
  declare function resolveTags(options: TagOptions | undefined, resolvedPath: string[]): string[];
1750
1752
  declare function resolvePath(path: string[], params: Record<string, string | number> | undefined): string[];
1753
+ /**
1754
+ * Resolves dynamic path parameters in a string path.
1755
+ * Unlike `resolvePath`, this works with string paths and doesn't throw on missing params.
1756
+ *
1757
+ * @param path - The path string with dynamic segments (e.g., "products/:id/comments")
1758
+ * @param params - The params object containing values to substitute
1759
+ * @returns The resolved path string (e.g., "products/1/comments")
1760
+ *
1761
+ * @example
1762
+ * ```ts
1763
+ * resolvePathString("products/:id/comments", { id: 1 })
1764
+ * // => "products/1/comments"
1765
+ * ```
1766
+ */
1767
+ declare function resolvePathString(path: string, params: Record<string, string | number> | undefined): string;
1751
1768
 
1752
1769
  declare const isNetworkError: (err: unknown) => boolean;
1753
1770
  declare const isAbortError: (err: unknown) => boolean;
@@ -2002,4 +2019,4 @@ type CreateInfiniteReadOptions<TData, TItem, TError, TRequest> = {
2002
2019
  };
2003
2020
  declare function createInfiniteReadController<TData, TItem, TError, TRequest extends InfiniteRequestOptions = InfiniteRequestOptions>(options: CreateInfiniteReadOptions<TData, TItem, TError, TRequest>): InfiniteReadController<TData, TItem, TError>;
2004
2021
 
2005
- export { type AnyRequestOptions, type ApiSchema, type BuiltInEvents, type CacheEntry, type CacheEntryWithKey, type CapturedCall, type ComputeRequestOptions, type CoreRequestOptionsBase, type CreateInfiniteReadOptions, type CreateOperationOptions, type DataAwareCallback, type DataAwareTransform, type DevtoolEvents, type EventEmitter, type EventListener, type EventOptions, type EventTracer, type ExtractBody$1 as ExtractBody, type ExtractData, type ExtractError, type ExtractMethodOptions, type ExtractParamNames, type ExtractQuery$1 as ExtractQuery, type FetchDirection, type FetchExecutor, type FindMatchingKey, HTTP_METHODS, type HasParams, type HasReadMethod, type HasWriteMethod, type HeadersInitOrGetter, type HttpMethod, type HttpMethodKey, type InfiniteReadController, type InfiniteReadState, type InfiniteRequestOptions, type InstanceApiContext, type InstanceApiResolvers, type InstancePluginExecutor, type LifecyclePhase, type MergePluginInstanceApi, type MergePluginOptions, type MergePluginResults, type MethodOptionsMap, type OperationController, type OperationState, type OperationType, type PageContext, type PluginAccessor, type PluginArray, type PluginContext, type PluginContextBase, type PluginContextExtensions, type PluginContextInput, type PluginExecutor, type PluginExportsRegistry, type PluginFactory, type PluginHandler, type PluginLifecycle, type PluginMiddleware, type PluginRegistry, type PluginRequestOptions, type PluginResolvers, type PluginResponseHandler, type PluginResultResolvers, type PluginTypeConfig, type PluginUpdateHandler, type ReadClient, type ReadPaths, type ReadSchemaHelper, type RefetchEvent, type RequestCompleteEvent, type RequestOptions$1 as RequestOptions, type RequestTracer, type ResolveInstanceApi, type ResolveResultTypes, type ResolveSchemaTypes, type ResolveTypes, type ResolverContext, type SchemaPaths, type SelectedEndpoint, type SelectorFunction, type SelectorResult, type SetupContext, type Simplify, Spoosh, type SpooshBody, type SpooshClient, type SpooshConfig, type SpooshInstance, type SpooshOptions, type SpooshOptionsInput, type SpooshPlugin, type SpooshResponse, type SpooshSchema, type StandaloneEvent, type StateManager, type StripPrefix, type TagMode, type TagOptions, type Trace, type TraceColor, type TraceEvent, type TraceInfo, type TraceListener, type TraceOptions, type TraceStage, type Transport, type TransportOption, type TransportOptionsMap, type TransportResponse, type WriteClient, type WriteMethod, type WritePaths, type WriteSchemaHelper, type WriteSelectorClient, __DEV__, buildUrl, clone, containsFile, createClient, createEventEmitter, createInfiniteReadController, createInitialState, createOperationController, createPluginExecutor, createPluginRegistry, createProxyHandler, createSelectorProxy, createStateManager, createTracer, executeFetch, extractMethodFromSelector, extractPathFromSelector, fetchTransport, form, generateTags, getContentType, isAbortError, isJsonBody, isNetworkError, isSpooshBody, json, mergeHeaders, objectToFormData, objectToUrlEncoded, resolveHeadersToRecord, resolvePath, resolveRequestBody, resolveTags, setHeaders, sortObjectKeys, urlencoded, xhrTransport };
2022
+ export { type AnyRequestOptions, type ApiSchema, type BuiltInEvents, type CacheEntry, type CacheEntryWithKey, type CapturedCall, type ComputeRequestOptions, type CoreRequestOptionsBase, type CreateInfiniteReadOptions, type CreateOperationOptions, type DataAwareCallback, type DataAwareTransform, type DevtoolEvents, type EventEmitter, type EventListener, type EventOptions, type EventTracer, type ExtractBody$1 as ExtractBody, type ExtractData, type ExtractError, type ExtractMethodOptions, type ExtractParamNames, type ExtractQuery$1 as ExtractQuery, type FetchDirection, type FetchExecutor, type FindMatchingKey, HTTP_METHODS, type HasParams, type HasReadMethod, type HasWriteMethod, type HeadersInitOrGetter, type HttpMethod, type HttpMethodKey, type InfiniteReadController, type InfiniteReadState, type InfiniteRequestOptions, type InstanceApiContext, type InstanceApiResolvers, type InstancePluginExecutor, type LifecyclePhase, type MergePluginInstanceApi, type MergePluginOptions, type MergePluginResults, type MethodOptionsMap, type OperationController, type OperationState, type OperationType, type PageContext, type PluginAccessor, type PluginArray, type PluginContext, type PluginContextBase, type PluginContextExtensions, type PluginContextInput, type PluginExecutor, type PluginExportsRegistry, type PluginFactory, type PluginHandler, type PluginLifecycle, type PluginMiddleware, type PluginRegistry, type PluginRequestOptions, type PluginResolvers, type PluginResponseHandler, type PluginResultResolvers, type PluginTypeConfig, type PluginUpdateHandler, type ReadClient, type ReadPaths, type ReadSchemaHelper, type RefetchEvent, type RequestCompleteEvent, type RequestOptions$1 as RequestOptions, type RequestTracer, type ResolveInstanceApi, type ResolveResultTypes, type ResolveSchemaTypes, type ResolveTypes, type ResolverContext, type SchemaPaths, type SelectedEndpoint, type SelectorFunction, type SelectorResult, type SetupContext, type Simplify, Spoosh, type SpooshBody, type SpooshClient, type SpooshConfig, type SpooshInstance, type SpooshOptions, type SpooshOptionsInput, type SpooshPlugin, type SpooshResponse, type SpooshSchema, type StandaloneEvent, type StateManager, type StripPrefix, type TagMode, type TagOptions, type Trace, type TraceColor, type TraceEvent, type TraceInfo, type TraceListener, type TraceOptions, type TraceStage, type Transport, type TransportOption, type TransportOptionsMap, type TransportResponse, type WriteClient, type WriteMethod, type WritePaths, type WriteSchemaHelper, type WriteSelectorClient, __DEV__, buildUrl, clone, containsFile, createClient, createEventEmitter, createInfiniteReadController, createInitialState, createOperationController, createPluginExecutor, createPluginRegistry, createProxyHandler, createSelectorProxy, createStateManager, createTracer, executeFetch, extractMethodFromSelector, extractPathFromSelector, fetchTransport, form, generateTags, getContentType, isAbortError, isJsonBody, isNetworkError, isSpooshBody, json, mergeHeaders, objectToFormData, objectToUrlEncoded, removeHeaderKeys, resolveHeadersToRecord, resolvePath, resolvePathString, resolveRequestBody, resolveTags, setHeaders, sortObjectKeys, urlencoded, xhrTransport };
package/dist/index.d.ts CHANGED
@@ -62,6 +62,7 @@ declare function urlencoded<T>(value: T): SpooshBody<T>;
62
62
  declare function resolveRequestBody(rawBody: unknown): {
63
63
  body: BodyInit;
64
64
  headers?: Record<string, string>;
65
+ removeHeaders?: string[];
65
66
  } | undefined;
66
67
 
67
68
  interface TransportResponse {
@@ -1723,6 +1724,7 @@ declare function setHeaders(requestOptions: {
1723
1724
  * Returns undefined if no Content-Type is set.
1724
1725
  */
1725
1726
  declare function getContentType(headers?: HeadersInit): string | undefined;
1727
+ declare function removeHeaderKeys(headers: HeadersInit | undefined, keysToRemove: string[]): HeadersInit | undefined;
1726
1728
 
1727
1729
  declare function objectToFormData(obj: Record<string, unknown>): FormData;
1728
1730
 
@@ -1748,6 +1750,21 @@ type TagOptions = {
1748
1750
  };
1749
1751
  declare function resolveTags(options: TagOptions | undefined, resolvedPath: string[]): string[];
1750
1752
  declare function resolvePath(path: string[], params: Record<string, string | number> | undefined): string[];
1753
+ /**
1754
+ * Resolves dynamic path parameters in a string path.
1755
+ * Unlike `resolvePath`, this works with string paths and doesn't throw on missing params.
1756
+ *
1757
+ * @param path - The path string with dynamic segments (e.g., "products/:id/comments")
1758
+ * @param params - The params object containing values to substitute
1759
+ * @returns The resolved path string (e.g., "products/1/comments")
1760
+ *
1761
+ * @example
1762
+ * ```ts
1763
+ * resolvePathString("products/:id/comments", { id: 1 })
1764
+ * // => "products/1/comments"
1765
+ * ```
1766
+ */
1767
+ declare function resolvePathString(path: string, params: Record<string, string | number> | undefined): string;
1751
1768
 
1752
1769
  declare const isNetworkError: (err: unknown) => boolean;
1753
1770
  declare const isAbortError: (err: unknown) => boolean;
@@ -2002,4 +2019,4 @@ type CreateInfiniteReadOptions<TData, TItem, TError, TRequest> = {
2002
2019
  };
2003
2020
  declare function createInfiniteReadController<TData, TItem, TError, TRequest extends InfiniteRequestOptions = InfiniteRequestOptions>(options: CreateInfiniteReadOptions<TData, TItem, TError, TRequest>): InfiniteReadController<TData, TItem, TError>;
2004
2021
 
2005
- export { type AnyRequestOptions, type ApiSchema, type BuiltInEvents, type CacheEntry, type CacheEntryWithKey, type CapturedCall, type ComputeRequestOptions, type CoreRequestOptionsBase, type CreateInfiniteReadOptions, type CreateOperationOptions, type DataAwareCallback, type DataAwareTransform, type DevtoolEvents, type EventEmitter, type EventListener, type EventOptions, type EventTracer, type ExtractBody$1 as ExtractBody, type ExtractData, type ExtractError, type ExtractMethodOptions, type ExtractParamNames, type ExtractQuery$1 as ExtractQuery, type FetchDirection, type FetchExecutor, type FindMatchingKey, HTTP_METHODS, type HasParams, type HasReadMethod, type HasWriteMethod, type HeadersInitOrGetter, type HttpMethod, type HttpMethodKey, type InfiniteReadController, type InfiniteReadState, type InfiniteRequestOptions, type InstanceApiContext, type InstanceApiResolvers, type InstancePluginExecutor, type LifecyclePhase, type MergePluginInstanceApi, type MergePluginOptions, type MergePluginResults, type MethodOptionsMap, type OperationController, type OperationState, type OperationType, type PageContext, type PluginAccessor, type PluginArray, type PluginContext, type PluginContextBase, type PluginContextExtensions, type PluginContextInput, type PluginExecutor, type PluginExportsRegistry, type PluginFactory, type PluginHandler, type PluginLifecycle, type PluginMiddleware, type PluginRegistry, type PluginRequestOptions, type PluginResolvers, type PluginResponseHandler, type PluginResultResolvers, type PluginTypeConfig, type PluginUpdateHandler, type ReadClient, type ReadPaths, type ReadSchemaHelper, type RefetchEvent, type RequestCompleteEvent, type RequestOptions$1 as RequestOptions, type RequestTracer, type ResolveInstanceApi, type ResolveResultTypes, type ResolveSchemaTypes, type ResolveTypes, type ResolverContext, type SchemaPaths, type SelectedEndpoint, type SelectorFunction, type SelectorResult, type SetupContext, type Simplify, Spoosh, type SpooshBody, type SpooshClient, type SpooshConfig, type SpooshInstance, type SpooshOptions, type SpooshOptionsInput, type SpooshPlugin, type SpooshResponse, type SpooshSchema, type StandaloneEvent, type StateManager, type StripPrefix, type TagMode, type TagOptions, type Trace, type TraceColor, type TraceEvent, type TraceInfo, type TraceListener, type TraceOptions, type TraceStage, type Transport, type TransportOption, type TransportOptionsMap, type TransportResponse, type WriteClient, type WriteMethod, type WritePaths, type WriteSchemaHelper, type WriteSelectorClient, __DEV__, buildUrl, clone, containsFile, createClient, createEventEmitter, createInfiniteReadController, createInitialState, createOperationController, createPluginExecutor, createPluginRegistry, createProxyHandler, createSelectorProxy, createStateManager, createTracer, executeFetch, extractMethodFromSelector, extractPathFromSelector, fetchTransport, form, generateTags, getContentType, isAbortError, isJsonBody, isNetworkError, isSpooshBody, json, mergeHeaders, objectToFormData, objectToUrlEncoded, resolveHeadersToRecord, resolvePath, resolveRequestBody, resolveTags, setHeaders, sortObjectKeys, urlencoded, xhrTransport };
2022
+ export { type AnyRequestOptions, type ApiSchema, type BuiltInEvents, type CacheEntry, type CacheEntryWithKey, type CapturedCall, type ComputeRequestOptions, type CoreRequestOptionsBase, type CreateInfiniteReadOptions, type CreateOperationOptions, type DataAwareCallback, type DataAwareTransform, type DevtoolEvents, type EventEmitter, type EventListener, type EventOptions, type EventTracer, type ExtractBody$1 as ExtractBody, type ExtractData, type ExtractError, type ExtractMethodOptions, type ExtractParamNames, type ExtractQuery$1 as ExtractQuery, type FetchDirection, type FetchExecutor, type FindMatchingKey, HTTP_METHODS, type HasParams, type HasReadMethod, type HasWriteMethod, type HeadersInitOrGetter, type HttpMethod, type HttpMethodKey, type InfiniteReadController, type InfiniteReadState, type InfiniteRequestOptions, type InstanceApiContext, type InstanceApiResolvers, type InstancePluginExecutor, type LifecyclePhase, type MergePluginInstanceApi, type MergePluginOptions, type MergePluginResults, type MethodOptionsMap, type OperationController, type OperationState, type OperationType, type PageContext, type PluginAccessor, type PluginArray, type PluginContext, type PluginContextBase, type PluginContextExtensions, type PluginContextInput, type PluginExecutor, type PluginExportsRegistry, type PluginFactory, type PluginHandler, type PluginLifecycle, type PluginMiddleware, type PluginRegistry, type PluginRequestOptions, type PluginResolvers, type PluginResponseHandler, type PluginResultResolvers, type PluginTypeConfig, type PluginUpdateHandler, type ReadClient, type ReadPaths, type ReadSchemaHelper, type RefetchEvent, type RequestCompleteEvent, type RequestOptions$1 as RequestOptions, type RequestTracer, type ResolveInstanceApi, type ResolveResultTypes, type ResolveSchemaTypes, type ResolveTypes, type ResolverContext, type SchemaPaths, type SelectedEndpoint, type SelectorFunction, type SelectorResult, type SetupContext, type Simplify, Spoosh, type SpooshBody, type SpooshClient, type SpooshConfig, type SpooshInstance, type SpooshOptions, type SpooshOptionsInput, type SpooshPlugin, type SpooshResponse, type SpooshSchema, type StandaloneEvent, type StateManager, type StripPrefix, type TagMode, type TagOptions, type Trace, type TraceColor, type TraceEvent, type TraceInfo, type TraceListener, type TraceOptions, type TraceStage, type Transport, type TransportOption, type TransportOptionsMap, type TransportResponse, type WriteClient, type WriteMethod, type WritePaths, type WriteSchemaHelper, type WriteSelectorClient, __DEV__, buildUrl, clone, containsFile, createClient, createEventEmitter, createInfiniteReadController, createInitialState, createOperationController, createPluginExecutor, createPluginRegistry, createProxyHandler, createSelectorProxy, createStateManager, createTracer, executeFetch, extractMethodFromSelector, extractPathFromSelector, fetchTransport, form, generateTags, getContentType, isAbortError, isJsonBody, isNetworkError, isSpooshBody, json, mergeHeaders, objectToFormData, objectToUrlEncoded, removeHeaderKeys, resolveHeadersToRecord, resolvePath, resolvePathString, resolveRequestBody, resolveTags, setHeaders, sortObjectKeys, urlencoded, xhrTransport };
package/dist/index.js CHANGED
@@ -52,8 +52,10 @@ __export(src_exports, {
52
52
  mergeHeaders: () => mergeHeaders,
53
53
  objectToFormData: () => objectToFormData,
54
54
  objectToUrlEncoded: () => objectToUrlEncoded,
55
+ removeHeaderKeys: () => removeHeaderKeys,
55
56
  resolveHeadersToRecord: () => resolveHeadersToRecord,
56
57
  resolvePath: () => resolvePath,
58
+ resolvePathString: () => resolvePathString,
57
59
  resolveRequestBody: () => resolveRequestBody,
58
60
  resolveTags: () => resolveTags,
59
61
  setHeaders: () => setHeaders,
@@ -170,6 +172,15 @@ function getContentType(headers) {
170
172
  const headersObj = new Headers(headers);
171
173
  return headersObj.get("content-type") ?? void 0;
172
174
  }
175
+ function removeHeaderKeys(headers, keysToRemove) {
176
+ if (!headers) return void 0;
177
+ const headersObj = new Headers(headers);
178
+ for (const key of keysToRemove) {
179
+ headersObj.delete(key);
180
+ }
181
+ const entries = [...headersObj.entries()];
182
+ return entries.length > 0 ? Object.fromEntries(entries) : void 0;
183
+ }
173
184
 
174
185
  // src/utils/objectToFormData.ts
175
186
  function objectToFormData(obj) {
@@ -277,7 +288,8 @@ function resolveRequestBody(rawBody) {
277
288
  switch (body.kind) {
278
289
  case "form":
279
290
  return {
280
- body: objectToFormData(body.value)
291
+ body: objectToFormData(body.value),
292
+ removeHeaders: ["Content-Type"]
281
293
  };
282
294
  case "json":
283
295
  return {
@@ -302,6 +314,9 @@ function resolveRequestBody(rawBody) {
302
314
  headers: { "Content-Type": "application/json" }
303
315
  };
304
316
  }
317
+ if (rawBody instanceof FormData) {
318
+ return { body: rawBody, removeHeaders: ["Content-Type"] };
319
+ }
305
320
  return { body: rawBody };
306
321
  }
307
322
 
@@ -355,6 +370,17 @@ function resolvePath(path, params) {
355
370
  return segment;
356
371
  });
357
372
  }
373
+ function resolvePathString(path, params) {
374
+ if (!params) return path;
375
+ return path.split("/").map((segment) => {
376
+ if (segment.startsWith(":")) {
377
+ const paramName = segment.slice(1);
378
+ const value = params[paramName];
379
+ return value !== void 0 ? String(value) : segment;
380
+ }
381
+ return segment;
382
+ }).join("/");
383
+ }
358
384
 
359
385
  // src/utils/errors.ts
360
386
  var isNetworkError = (err) => err instanceof TypeError;
@@ -415,7 +441,15 @@ var fetchTransport = async (url, init) => {
415
441
  const res = await fetch(url, init);
416
442
  const contentType = res.headers.get("content-type");
417
443
  const isJson = contentType?.includes("application/json");
418
- const data = isJson ? await res.json() : res;
444
+ const isText = contentType?.includes("text/") || contentType?.includes("application/xml");
445
+ let data;
446
+ if (isJson) {
447
+ data = await res.json();
448
+ } else if (isText) {
449
+ data = await res.text();
450
+ } else {
451
+ data = void 0;
452
+ }
419
453
  return { ok: res.ok, status: res.status, headers: res.headers, data };
420
454
  };
421
455
 
@@ -570,12 +604,13 @@ async function executeCoreFetch(config) {
570
604
  const resolved = resolveRequestBody(requestOptions.body);
571
605
  if (resolved) {
572
606
  fetchInit.body = resolved.body;
607
+ if (resolved.removeHeaders) {
608
+ headers = removeHeaderKeys(headers, resolved.removeHeaders);
609
+ }
573
610
  if (resolved.headers) {
574
611
  headers = await mergeHeaders(headers, resolved.headers);
575
- if (headers) {
576
- fetchInit.headers = headers;
577
- }
578
612
  }
613
+ fetchInit.headers = headers;
579
614
  }
580
615
  }
581
616
  const resolvedTransport = resolveTransport(
@@ -1325,30 +1360,26 @@ function createOperationController(options) {
1325
1360
  const coreFetch = async () => {
1326
1361
  abortController = new AbortController();
1327
1362
  context.request.signal = abortController.signal;
1328
- const fetchPromise = (async () => {
1329
- try {
1330
- const response = await fetchFn(context.request);
1331
- return response;
1332
- } catch (err) {
1333
- const errorResponse = {
1334
- status: 0,
1335
- error: err,
1336
- data: void 0
1337
- };
1338
- return errorResponse;
1339
- }
1340
- })();
1341
- stateManager.setPendingPromise(queryKey, fetchPromise);
1342
- fetchPromise.finally(() => {
1343
- stateManager.setPendingPromise(queryKey, void 0);
1344
- });
1345
- return fetchPromise;
1363
+ try {
1364
+ const response = await fetchFn(context.request);
1365
+ return response;
1366
+ } catch (err) {
1367
+ const errorResponse = {
1368
+ status: 0,
1369
+ error: err,
1370
+ data: void 0
1371
+ };
1372
+ return errorResponse;
1373
+ }
1346
1374
  };
1347
- const finalResponse = await pluginExecutor.executeMiddleware(
1375
+ const middlewarePromise = pluginExecutor.executeMiddleware(
1348
1376
  operationType,
1349
1377
  context,
1350
1378
  coreFetch
1351
1379
  );
1380
+ stateManager.setPendingPromise(queryKey, middlewarePromise);
1381
+ const finalResponse = await middlewarePromise;
1382
+ stateManager.setPendingPromise(queryKey, void 0);
1352
1383
  if (finalResponse.data !== void 0 && !finalResponse.error) {
1353
1384
  updateState({
1354
1385
  data: finalResponse.data,
@@ -1483,6 +1514,7 @@ function createInfiniteReadController(options) {
1483
1514
  let cachedState = createInitialInfiniteState();
1484
1515
  const trackerKey = createTrackerKey(path, method, baseOptionsForKey);
1485
1516
  let pageSubscriptions = [];
1517
+ let trackerSubscription = null;
1486
1518
  let refetchUnsubscribe = null;
1487
1519
  const loadFromTracker = () => {
1488
1520
  const cached = stateManager.getCache(trackerKey);
@@ -1597,52 +1629,50 @@ function createInfiniteReadController(options) {
1597
1629
  }
1598
1630
  pendingFetches.add(pageKey);
1599
1631
  fetchingDirection = direction;
1632
+ latestError = void 0;
1600
1633
  notify();
1601
1634
  abortController = new AbortController();
1602
1635
  const signal = abortController.signal;
1603
1636
  const context = createContext(pageKey, mergedRequest);
1604
1637
  const coreFetch = async () => {
1605
- const fetchPromise = (async () => {
1606
- try {
1607
- const response = await fetchFn(mergedRequest, signal);
1608
- if (signal.aborted) {
1609
- return {
1610
- status: 0,
1611
- data: void 0,
1612
- aborted: true
1613
- };
1614
- }
1615
- return response;
1616
- } catch (err) {
1617
- if (signal.aborted) {
1618
- return {
1619
- status: 0,
1620
- data: void 0,
1621
- aborted: true
1622
- };
1623
- }
1624
- const errorResponse = {
1638
+ try {
1639
+ const response = await fetchFn(mergedRequest, signal);
1640
+ if (signal.aborted) {
1641
+ return {
1625
1642
  status: 0,
1626
- error: err,
1627
- data: void 0
1643
+ data: void 0,
1644
+ aborted: true
1628
1645
  };
1629
- latestError = err;
1630
- return errorResponse;
1631
- } finally {
1632
- pendingFetches.delete(pageKey);
1633
- fetchingDirection = null;
1634
- stateManager.setPendingPromise(pageKey, void 0);
1635
- notify();
1636
1646
  }
1637
- })();
1638
- stateManager.setPendingPromise(pageKey, fetchPromise);
1639
- return fetchPromise;
1647
+ return response;
1648
+ } catch (err) {
1649
+ if (signal.aborted) {
1650
+ return {
1651
+ status: 0,
1652
+ data: void 0,
1653
+ aborted: true
1654
+ };
1655
+ }
1656
+ const errorResponse = {
1657
+ status: 0,
1658
+ error: err,
1659
+ data: void 0
1660
+ };
1661
+ latestError = err;
1662
+ return errorResponse;
1663
+ }
1640
1664
  };
1641
- const finalResponse = await pluginExecutor.executeMiddleware(
1665
+ const middlewarePromise = pluginExecutor.executeMiddleware(
1642
1666
  "infiniteRead",
1643
1667
  context,
1644
1668
  coreFetch
1645
1669
  );
1670
+ stateManager.setPendingPromise(pageKey, middlewarePromise);
1671
+ const finalResponse = await middlewarePromise;
1672
+ pendingFetches.delete(pageKey);
1673
+ fetchingDirection = null;
1674
+ stateManager.setPendingPromise(pageKey, void 0);
1675
+ notify();
1646
1676
  if (finalResponse.data !== void 0 && !finalResponse.error) {
1647
1677
  pageRequests.set(pageKey, mergedRequest);
1648
1678
  if (direction === "next") {
@@ -1755,6 +1785,7 @@ function createInfiniteReadController(options) {
1755
1785
  loadFromTracker();
1756
1786
  cachedState = computeState();
1757
1787
  subscribeToPages();
1788
+ trackerSubscription = stateManager.subscribeCache(trackerKey, notify);
1758
1789
  const context = createContext(trackerKey, initialRequest);
1759
1790
  pluginExecutor.executeLifecycle("onMount", "infiniteRead", context);
1760
1791
  refetchUnsubscribe = eventEmitter.on("refetch", (event) => {
@@ -1776,6 +1807,8 @@ function createInfiniteReadController(options) {
1776
1807
  pluginExecutor.executeLifecycle("onUnmount", "infiniteRead", context);
1777
1808
  pageSubscriptions.forEach((unsub) => unsub());
1778
1809
  pageSubscriptions = [];
1810
+ trackerSubscription?.();
1811
+ trackerSubscription = null;
1779
1812
  refetchUnsubscribe?.();
1780
1813
  refetchUnsubscribe = null;
1781
1814
  },
package/dist/index.mjs CHANGED
@@ -105,6 +105,15 @@ function getContentType(headers) {
105
105
  const headersObj = new Headers(headers);
106
106
  return headersObj.get("content-type") ?? void 0;
107
107
  }
108
+ function removeHeaderKeys(headers, keysToRemove) {
109
+ if (!headers) return void 0;
110
+ const headersObj = new Headers(headers);
111
+ for (const key of keysToRemove) {
112
+ headersObj.delete(key);
113
+ }
114
+ const entries = [...headersObj.entries()];
115
+ return entries.length > 0 ? Object.fromEntries(entries) : void 0;
116
+ }
108
117
 
109
118
  // src/utils/objectToFormData.ts
110
119
  function objectToFormData(obj) {
@@ -212,7 +221,8 @@ function resolveRequestBody(rawBody) {
212
221
  switch (body.kind) {
213
222
  case "form":
214
223
  return {
215
- body: objectToFormData(body.value)
224
+ body: objectToFormData(body.value),
225
+ removeHeaders: ["Content-Type"]
216
226
  };
217
227
  case "json":
218
228
  return {
@@ -237,6 +247,9 @@ function resolveRequestBody(rawBody) {
237
247
  headers: { "Content-Type": "application/json" }
238
248
  };
239
249
  }
250
+ if (rawBody instanceof FormData) {
251
+ return { body: rawBody, removeHeaders: ["Content-Type"] };
252
+ }
240
253
  return { body: rawBody };
241
254
  }
242
255
 
@@ -290,6 +303,17 @@ function resolvePath(path, params) {
290
303
  return segment;
291
304
  });
292
305
  }
306
+ function resolvePathString(path, params) {
307
+ if (!params) return path;
308
+ return path.split("/").map((segment) => {
309
+ if (segment.startsWith(":")) {
310
+ const paramName = segment.slice(1);
311
+ const value = params[paramName];
312
+ return value !== void 0 ? String(value) : segment;
313
+ }
314
+ return segment;
315
+ }).join("/");
316
+ }
293
317
 
294
318
  // src/utils/errors.ts
295
319
  var isNetworkError = (err) => err instanceof TypeError;
@@ -350,7 +374,15 @@ var fetchTransport = async (url, init) => {
350
374
  const res = await fetch(url, init);
351
375
  const contentType = res.headers.get("content-type");
352
376
  const isJson = contentType?.includes("application/json");
353
- const data = isJson ? await res.json() : res;
377
+ const isText = contentType?.includes("text/") || contentType?.includes("application/xml");
378
+ let data;
379
+ if (isJson) {
380
+ data = await res.json();
381
+ } else if (isText) {
382
+ data = await res.text();
383
+ } else {
384
+ data = void 0;
385
+ }
354
386
  return { ok: res.ok, status: res.status, headers: res.headers, data };
355
387
  };
356
388
 
@@ -505,12 +537,13 @@ async function executeCoreFetch(config) {
505
537
  const resolved = resolveRequestBody(requestOptions.body);
506
538
  if (resolved) {
507
539
  fetchInit.body = resolved.body;
540
+ if (resolved.removeHeaders) {
541
+ headers = removeHeaderKeys(headers, resolved.removeHeaders);
542
+ }
508
543
  if (resolved.headers) {
509
544
  headers = await mergeHeaders(headers, resolved.headers);
510
- if (headers) {
511
- fetchInit.headers = headers;
512
- }
513
545
  }
546
+ fetchInit.headers = headers;
514
547
  }
515
548
  }
516
549
  const resolvedTransport = resolveTransport(
@@ -1260,30 +1293,26 @@ function createOperationController(options) {
1260
1293
  const coreFetch = async () => {
1261
1294
  abortController = new AbortController();
1262
1295
  context.request.signal = abortController.signal;
1263
- const fetchPromise = (async () => {
1264
- try {
1265
- const response = await fetchFn(context.request);
1266
- return response;
1267
- } catch (err) {
1268
- const errorResponse = {
1269
- status: 0,
1270
- error: err,
1271
- data: void 0
1272
- };
1273
- return errorResponse;
1274
- }
1275
- })();
1276
- stateManager.setPendingPromise(queryKey, fetchPromise);
1277
- fetchPromise.finally(() => {
1278
- stateManager.setPendingPromise(queryKey, void 0);
1279
- });
1280
- return fetchPromise;
1296
+ try {
1297
+ const response = await fetchFn(context.request);
1298
+ return response;
1299
+ } catch (err) {
1300
+ const errorResponse = {
1301
+ status: 0,
1302
+ error: err,
1303
+ data: void 0
1304
+ };
1305
+ return errorResponse;
1306
+ }
1281
1307
  };
1282
- const finalResponse = await pluginExecutor.executeMiddleware(
1308
+ const middlewarePromise = pluginExecutor.executeMiddleware(
1283
1309
  operationType,
1284
1310
  context,
1285
1311
  coreFetch
1286
1312
  );
1313
+ stateManager.setPendingPromise(queryKey, middlewarePromise);
1314
+ const finalResponse = await middlewarePromise;
1315
+ stateManager.setPendingPromise(queryKey, void 0);
1287
1316
  if (finalResponse.data !== void 0 && !finalResponse.error) {
1288
1317
  updateState({
1289
1318
  data: finalResponse.data,
@@ -1418,6 +1447,7 @@ function createInfiniteReadController(options) {
1418
1447
  let cachedState = createInitialInfiniteState();
1419
1448
  const trackerKey = createTrackerKey(path, method, baseOptionsForKey);
1420
1449
  let pageSubscriptions = [];
1450
+ let trackerSubscription = null;
1421
1451
  let refetchUnsubscribe = null;
1422
1452
  const loadFromTracker = () => {
1423
1453
  const cached = stateManager.getCache(trackerKey);
@@ -1532,52 +1562,50 @@ function createInfiniteReadController(options) {
1532
1562
  }
1533
1563
  pendingFetches.add(pageKey);
1534
1564
  fetchingDirection = direction;
1565
+ latestError = void 0;
1535
1566
  notify();
1536
1567
  abortController = new AbortController();
1537
1568
  const signal = abortController.signal;
1538
1569
  const context = createContext(pageKey, mergedRequest);
1539
1570
  const coreFetch = async () => {
1540
- const fetchPromise = (async () => {
1541
- try {
1542
- const response = await fetchFn(mergedRequest, signal);
1543
- if (signal.aborted) {
1544
- return {
1545
- status: 0,
1546
- data: void 0,
1547
- aborted: true
1548
- };
1549
- }
1550
- return response;
1551
- } catch (err) {
1552
- if (signal.aborted) {
1553
- return {
1554
- status: 0,
1555
- data: void 0,
1556
- aborted: true
1557
- };
1558
- }
1559
- const errorResponse = {
1571
+ try {
1572
+ const response = await fetchFn(mergedRequest, signal);
1573
+ if (signal.aborted) {
1574
+ return {
1560
1575
  status: 0,
1561
- error: err,
1562
- data: void 0
1576
+ data: void 0,
1577
+ aborted: true
1563
1578
  };
1564
- latestError = err;
1565
- return errorResponse;
1566
- } finally {
1567
- pendingFetches.delete(pageKey);
1568
- fetchingDirection = null;
1569
- stateManager.setPendingPromise(pageKey, void 0);
1570
- notify();
1571
1579
  }
1572
- })();
1573
- stateManager.setPendingPromise(pageKey, fetchPromise);
1574
- return fetchPromise;
1580
+ return response;
1581
+ } catch (err) {
1582
+ if (signal.aborted) {
1583
+ return {
1584
+ status: 0,
1585
+ data: void 0,
1586
+ aborted: true
1587
+ };
1588
+ }
1589
+ const errorResponse = {
1590
+ status: 0,
1591
+ error: err,
1592
+ data: void 0
1593
+ };
1594
+ latestError = err;
1595
+ return errorResponse;
1596
+ }
1575
1597
  };
1576
- const finalResponse = await pluginExecutor.executeMiddleware(
1598
+ const middlewarePromise = pluginExecutor.executeMiddleware(
1577
1599
  "infiniteRead",
1578
1600
  context,
1579
1601
  coreFetch
1580
1602
  );
1603
+ stateManager.setPendingPromise(pageKey, middlewarePromise);
1604
+ const finalResponse = await middlewarePromise;
1605
+ pendingFetches.delete(pageKey);
1606
+ fetchingDirection = null;
1607
+ stateManager.setPendingPromise(pageKey, void 0);
1608
+ notify();
1581
1609
  if (finalResponse.data !== void 0 && !finalResponse.error) {
1582
1610
  pageRequests.set(pageKey, mergedRequest);
1583
1611
  if (direction === "next") {
@@ -1690,6 +1718,7 @@ function createInfiniteReadController(options) {
1690
1718
  loadFromTracker();
1691
1719
  cachedState = computeState();
1692
1720
  subscribeToPages();
1721
+ trackerSubscription = stateManager.subscribeCache(trackerKey, notify);
1693
1722
  const context = createContext(trackerKey, initialRequest);
1694
1723
  pluginExecutor.executeLifecycle("onMount", "infiniteRead", context);
1695
1724
  refetchUnsubscribe = eventEmitter.on("refetch", (event) => {
@@ -1711,6 +1740,8 @@ function createInfiniteReadController(options) {
1711
1740
  pluginExecutor.executeLifecycle("onUnmount", "infiniteRead", context);
1712
1741
  pageSubscriptions.forEach((unsub) => unsub());
1713
1742
  pageSubscriptions = [];
1743
+ trackerSubscription?.();
1744
+ trackerSubscription = null;
1714
1745
  refetchUnsubscribe?.();
1715
1746
  refetchUnsubscribe = null;
1716
1747
  },
@@ -1764,8 +1795,10 @@ export {
1764
1795
  mergeHeaders,
1765
1796
  objectToFormData,
1766
1797
  objectToUrlEncoded,
1798
+ removeHeaderKeys,
1767
1799
  resolveHeadersToRecord,
1768
1800
  resolvePath,
1801
+ resolvePathString,
1769
1802
  resolveRequestBody,
1770
1803
  resolveTags,
1771
1804
  setHeaders,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spoosh/core",
3
- "version": "0.13.1",
3
+ "version": "0.13.3",
4
4
  "license": "MIT",
5
5
  "description": "Type-safe API toolkit with plugin middleware system",
6
6
  "keywords": [