@signalium/query 1.0.17 → 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,52 @@
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
+
44
+ ## 1.0.18
45
+
46
+ ### Patch Changes
47
+
48
+ - 395730a: Fix entity cache keys to include shapeKey, preventing stale entity validation errors after schema changes
49
+
3
50
  ## 1.0.17
4
51
 
5
52
  ### Patch Changes
@@ -1139,7 +1139,8 @@ class EntityStore {
1139
1139
  });
1140
1140
  }
1141
1141
  const warn = this.queryClient.getContext().log?.warn;
1142
- return createEntityProxy(record.key, record, shape, entityRelay, this.queryClient, warn);
1142
+ const desc = `${shape.typenameValue}:${id}`;
1143
+ return createEntityProxy(record.key, record, shape, entityRelay, this.queryClient, warn, desc);
1143
1144
  }
1144
1145
  }
1145
1146
  class NetworkManager {
@@ -1331,7 +1332,7 @@ function parseObjectEntities(obj, objectShape, queryClient, entityRefs) {
1331
1332
  throw new Error(`Entity id is required: ${typename}`);
1332
1333
  }
1333
1334
  const desc = `${typename}:${id}`;
1334
- const key = utils.hashValue(desc);
1335
+ const key = utils.hashValue([desc, entityDef.shapeKey]);
1335
1336
  if (entityRefs !== void 0) {
1336
1337
  entityRefs.add(key);
1337
1338
  }
@@ -2350,7 +2351,7 @@ class MutationResultImpl {
2350
2351
  const entityId = obj[idField];
2351
2352
  if (entityId !== void 0) {
2352
2353
  const typename = entityDef.typenameValue;
2353
- const entityKey = utils.hashValue(`${typename}:${entityId}`);
2354
+ const entityKey = utils.hashValue([`${typename}:${entityId}`, entityDef.shapeKey]);
2354
2355
  this.queryClient.registerOptimisticUpdate(entityKey, obj);
2355
2356
  this._pendingOptimisticKeys.add(entityKey);
2356
2357
  }
@@ -2561,7 +2562,7 @@ var QueryType = /* @__PURE__ */ ((QueryType2) => {
2561
2562
  return QueryType2;
2562
2563
  })(QueryType || {});
2563
2564
  function isSignal(value) {
2564
- return typeof value === "object" && value !== null;
2565
+ return typeof value === "object" && value !== null && !Array.isArray(value) && "value" in value && "_id" in value;
2565
2566
  }
2566
2567
  function extractParamsForKey(params) {
2567
2568
  if (params === void 0) {
@@ -2705,7 +2706,7 @@ function createPathInterpolator(pathTemplate) {
2705
2706
  lastIndex = paramRegex.lastIndex;
2706
2707
  }
2707
2708
  segments.push(pathTemplate.slice(lastIndex));
2708
- return (params) => {
2709
+ const interpolate = (params) => {
2709
2710
  let result = segments[0];
2710
2711
  for (let i = 0; i < paramKeys.length; i++) {
2711
2712
  result += encodeURIComponent(String(params[paramKeys[i]])) + segments[i + 1];
@@ -2724,6 +2725,7 @@ function createPathInterpolator(pathTemplate) {
2724
2725
  }
2725
2726
  return result;
2726
2727
  };
2728
+ return { interpolate, pathParamNames: paramKeysSet };
2727
2729
  }
2728
2730
  const QUERY_DEFINITION_MAP = /* @__PURE__ */ new Map();
2729
2731
  function buildQueryFn(queryDefinitionBuilder) {
@@ -2733,6 +2735,8 @@ function buildQueryFn(queryDefinitionBuilder) {
2733
2735
  const {
2734
2736
  path,
2735
2737
  method = "GET",
2738
+ searchParams,
2739
+ body,
2736
2740
  response,
2737
2741
  requestOptions,
2738
2742
  cache,
@@ -2759,14 +2763,53 @@ function buildQueryFn(queryDefinitionBuilder) {
2759
2763
  shape = response;
2760
2764
  shapeKey = utils.hashValue(shape);
2761
2765
  }
2762
- 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)");
2763
2788
  const fetchFn = async (context, params) => {
2764
- 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 ?? {});
2765
2805
  const baseUrl = resolveBaseUrl(requestOptions?.baseUrl) ?? resolveBaseUrl(context.baseUrl);
2766
2806
  const fullUrl = baseUrl ? `${baseUrl}${interpolatedPath}` : interpolatedPath;
2767
- const { baseUrl: _baseUrl, ...fetchOptions } = requestOptions ?? {};
2807
+ const { baseUrl: _baseUrl, headers: userHeaders, ...fetchOptions } = requestOptions ?? {};
2808
+ const headers = bodyData ? { "Content-Type": "application/json", ...userHeaders } : userHeaders;
2768
2809
  const response2 = await context.fetch(fullUrl, {
2769
2810
  method,
2811
+ headers,
2812
+ body: bodyData ? JSON.stringify(bodyData) : void 0,
2770
2813
  ...fetchOptions
2771
2814
  });
2772
2815
  return response2.json();
@@ -2927,13 +2970,7 @@ function buildMutationFn(mutationDefinitionBuilder) {
2927
2970
  const id = `mutation:${method}:${path}`;
2928
2971
  const { shape: requestShape, shapeKey: requestShapeKey } = processTypeDef(request);
2929
2972
  const { shape: responseShape, shapeKey: responseShapeKey } = processTypeDef(response);
2930
- const interpolatePath = createPathInterpolator(path);
2931
- const pathParamNames = /* @__PURE__ */ new Set();
2932
- const paramRegex = /\[([^\]]+)\]/g;
2933
- let match;
2934
- while ((match = paramRegex.exec(path)) !== null) {
2935
- pathParamNames.add(match[1]);
2936
- }
2973
+ const { interpolate: interpolatePath, pathParamNames } = createPathInterpolator(path);
2937
2974
  const mutateFn = async (context, requestData) => {
2938
2975
  const pathParams = {};
2939
2976
  for (const paramName of pathParamNames) {