@signalium/query 1.0.18 → 1.1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,46 @@
1
1
  # @signalium/query
2
2
 
3
+ ## 1.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - af443c5: Add request body support to query() function
8
+
9
+ Queries can now send JSON request bodies for POST requests, enabling read-like operations that require complex data structures (e.g., fetching prices for an array of tokens).
10
+
11
+ **New features:**
12
+
13
+ - Added `body` field to query definitions for specifying request body schema
14
+ - Body parameters are automatically serialized as JSON with `Content-Type: application/json` header
15
+ - Body params work alongside path params and search params
16
+ - All query features (caching, staleTime, deduplication) work with body queries
17
+
18
+ **API changes:**
19
+
20
+ - Query methods are now restricted to `GET` and `POST` only (PUT, PATCH, DELETE should use `mutation()`)
21
+
22
+ **Example:**
23
+
24
+ ```typescript
25
+ const getPrices = query(() => ({
26
+ path: '/prices',
27
+ method: 'POST',
28
+ body: {
29
+ tokens: t.array(t.string),
30
+ },
31
+ searchParams: {
32
+ currency: t.string,
33
+ },
34
+ response: {
35
+ prices: t.array(t.object({ token: t.string, price: t.number })),
36
+ },
37
+ cache: { staleTime: 30_000 },
38
+ }));
39
+
40
+ // Usage: POST /prices?currency=USD with body: {"tokens":["ETH","BTC"]}
41
+ const result = getPrices({ tokens: ['ETH', 'BTC'], currency: 'USD' });
42
+ ```
43
+
3
44
  ## 1.0.18
4
45
 
5
46
  ### Patch Changes
@@ -2562,7 +2562,7 @@ var QueryType = /* @__PURE__ */ ((QueryType2) => {
2562
2562
  return QueryType2;
2563
2563
  })(QueryType || {});
2564
2564
  function isSignal(value) {
2565
- return typeof value === "object" && value !== null;
2565
+ return typeof value === "object" && value !== null && !Array.isArray(value) && "value" in value && "_id" in value;
2566
2566
  }
2567
2567
  function extractParamsForKey(params) {
2568
2568
  if (params === void 0) {
@@ -2706,7 +2706,7 @@ function createPathInterpolator(pathTemplate) {
2706
2706
  lastIndex = paramRegex.lastIndex;
2707
2707
  }
2708
2708
  segments.push(pathTemplate.slice(lastIndex));
2709
- return (params) => {
2709
+ const interpolate = (params) => {
2710
2710
  let result = segments[0];
2711
2711
  for (let i = 0; i < paramKeys.length; i++) {
2712
2712
  result += encodeURIComponent(String(params[paramKeys[i]])) + segments[i + 1];
@@ -2725,6 +2725,7 @@ function createPathInterpolator(pathTemplate) {
2725
2725
  }
2726
2726
  return result;
2727
2727
  };
2728
+ return { interpolate, pathParamNames: paramKeysSet };
2728
2729
  }
2729
2730
  const QUERY_DEFINITION_MAP = /* @__PURE__ */ new Map();
2730
2731
  function buildQueryFn(queryDefinitionBuilder) {
@@ -2734,6 +2735,8 @@ function buildQueryFn(queryDefinitionBuilder) {
2734
2735
  const {
2735
2736
  path,
2736
2737
  method = "GET",
2738
+ searchParams,
2739
+ body,
2737
2740
  response,
2738
2741
  requestOptions,
2739
2742
  cache,
@@ -2760,14 +2763,53 @@ function buildQueryFn(queryDefinitionBuilder) {
2760
2763
  shape = response;
2761
2764
  shapeKey = utils.hashValue(shape);
2762
2765
  }
2763
- const interpolatePath = createPathInterpolator(path);
2766
+ const { interpolate: interpolatePath, pathParamNames } = createPathInterpolator(path);
2767
+ const bodyParamNames = /* @__PURE__ */ new Set();
2768
+ const hasBody = body !== void 0 && typeof body === "object" && !(body instanceof ValidatorDef) && !(body instanceof Set);
2769
+ if (hasBody) {
2770
+ for (const key of Object.keys(body)) {
2771
+ bodyParamNames.add(key);
2772
+ }
2773
+ }
2774
+ const searchParamNames = new Set(
2775
+ searchParams && typeof searchParams === "object" && !(searchParams instanceof ValidatorDef) && !(searchParams instanceof Set) ? Object.keys(searchParams) : []
2776
+ );
2777
+ const checkConflicts = (sourceNames, targetNames, sourceLabel, targetLabel) => {
2778
+ const conflicts = [...sourceNames].filter((name) => targetNames.has(name));
2779
+ if (conflicts.length > 0) {
2780
+ throw new Error(
2781
+ `Query definition error: ${sourceLabel} [${conflicts.join(", ")}] conflict with ${targetLabel}. Please rename to avoid this conflict.`
2782
+ );
2783
+ }
2784
+ };
2785
+ checkConflicts(searchParamNames, pathParamNames, "Search param(s)", `path parameter(s) in "${path}"`);
2786
+ checkConflicts(bodyParamNames, pathParamNames, "Body field(s)", `path parameter(s) in "${path}"`);
2787
+ checkConflicts(bodyParamNames, searchParamNames, "Body field(s)", "search param(s)");
2764
2788
  const fetchFn = async (context, params) => {
2765
- const interpolatedPath = interpolatePath(params);
2789
+ let bodyData;
2790
+ let urlParams = params;
2791
+ if (hasBody) {
2792
+ bodyData = {};
2793
+ urlParams = params !== void 0 ? {} : void 0;
2794
+ if (params !== void 0) {
2795
+ for (const key in params) {
2796
+ if (bodyParamNames.has(key)) {
2797
+ bodyData[key] = params[key];
2798
+ } else {
2799
+ urlParams[key] = params[key];
2800
+ }
2801
+ }
2802
+ }
2803
+ }
2804
+ const interpolatedPath = interpolatePath(urlParams ?? {});
2766
2805
  const baseUrl = resolveBaseUrl(requestOptions?.baseUrl) ?? resolveBaseUrl(context.baseUrl);
2767
2806
  const fullUrl = baseUrl ? `${baseUrl}${interpolatedPath}` : interpolatedPath;
2768
- const { baseUrl: _baseUrl, ...fetchOptions } = requestOptions ?? {};
2807
+ const { baseUrl: _baseUrl, headers: userHeaders, ...fetchOptions } = requestOptions ?? {};
2808
+ const headers = bodyData ? { "Content-Type": "application/json", ...userHeaders } : userHeaders;
2769
2809
  const response2 = await context.fetch(fullUrl, {
2770
2810
  method,
2811
+ headers,
2812
+ body: bodyData ? JSON.stringify(bodyData) : void 0,
2771
2813
  ...fetchOptions
2772
2814
  });
2773
2815
  return response2.json();
@@ -2928,13 +2970,7 @@ function buildMutationFn(mutationDefinitionBuilder) {
2928
2970
  const id = `mutation:${method}:${path}`;
2929
2971
  const { shape: requestShape, shapeKey: requestShapeKey } = processTypeDef(request);
2930
2972
  const { shape: responseShape, shapeKey: responseShapeKey } = processTypeDef(response);
2931
- const interpolatePath = createPathInterpolator(path);
2932
- const pathParamNames = /* @__PURE__ */ new Set();
2933
- const paramRegex = /\[([^\]]+)\]/g;
2934
- let match;
2935
- while ((match = paramRegex.exec(path)) !== null) {
2936
- pathParamNames.add(match[1]);
2937
- }
2973
+ const { interpolate: interpolatePath, pathParamNames } = createPathInterpolator(path);
2938
2974
  const mutateFn = async (context, requestData) => {
2939
2975
  const pathParams = {};
2940
2976
  for (const paramName of pathParamNames) {