@signalium/query 1.0.18 → 1.1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,52 @@
1
1
  # @signalium/query
2
2
 
3
+ ## 1.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - bb0a5a9: Fix entity proxies not being created for preloaded entities from cache, and `__entityRef` not being resolved in proxy get handler. This fixes validation errors when accessing nested entities loaded from persistent cache.
8
+
9
+ ## 1.1.0
10
+
11
+ ### Minor Changes
12
+
13
+ - af443c5: Add request body support to query() function
14
+
15
+ 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).
16
+
17
+ **New features:**
18
+
19
+ - Added `body` field to query definitions for specifying request body schema
20
+ - Body parameters are automatically serialized as JSON with `Content-Type: application/json` header
21
+ - Body params work alongside path params and search params
22
+ - All query features (caching, staleTime, deduplication) work with body queries
23
+
24
+ **API changes:**
25
+
26
+ - Query methods are now restricted to `GET` and `POST` only (PUT, PATCH, DELETE should use `mutation()`)
27
+
28
+ **Example:**
29
+
30
+ ```typescript
31
+ const getPrices = query(() => ({
32
+ path: '/prices',
33
+ method: 'POST',
34
+ body: {
35
+ tokens: t.array(t.string),
36
+ },
37
+ searchParams: {
38
+ currency: t.string,
39
+ },
40
+ response: {
41
+ prices: t.array(t.object({ token: t.string, price: t.number })),
42
+ },
43
+ cache: { staleTime: 30_000 },
44
+ }));
45
+
46
+ // Usage: POST /prices?currency=USD with body: {"tokens":["ETH","BTC"]}
47
+ const result = getPrices({ tokens: ['ETH', 'BTC'], currency: 'USD' });
48
+ ```
49
+
3
50
  ## 1.0.18
4
51
 
5
52
  ### Patch Changes
@@ -912,6 +912,43 @@ function createEntityProxy(id, entityRecord, def, entityRelay, scopeOwner, warn
912
912
  if (!Object.hasOwnProperty.call(shape, prop)) {
913
913
  return value;
914
914
  }
915
+ if (value && typeof value === "object") {
916
+ const ownerWithHydrate = scopeOwner;
917
+ if (typeof value.__entityRef === "number") {
918
+ const nestedRecord = ownerWithHydrate.hydrateEntity?.(
919
+ value.__entityRef,
920
+ propDef
921
+ );
922
+ if (nestedRecord && nestedRecord.proxy) {
923
+ cache.set(prop, nestedRecord.proxy);
924
+ return nestedRecord.proxy;
925
+ }
926
+ }
927
+ if (Array.isArray(value) && value.length > 0) {
928
+ const firstItem = value[0];
929
+ if (firstItem && typeof firstItem === "object" && typeof firstItem.__entityRef === "number") {
930
+ const arrayTypeDef = propDef;
931
+ const isArrayType = typeof arrayTypeDef.mask === "number" && (arrayTypeDef.mask & Mask.ARRAY) !== 0;
932
+ const itemDef = isArrayType ? arrayTypeDef.shape : void 0;
933
+ if (itemDef) {
934
+ const hydratedArray = value.map((item) => {
935
+ if (item && typeof item === "object" && typeof item.__entityRef === "number") {
936
+ const nestedRecord = ownerWithHydrate.hydrateEntity?.(
937
+ item.__entityRef,
938
+ itemDef
939
+ );
940
+ if (nestedRecord && nestedRecord.proxy) {
941
+ return nestedRecord.proxy;
942
+ }
943
+ }
944
+ return item;
945
+ });
946
+ cache.set(prop, hydratedArray);
947
+ return hydratedArray;
948
+ }
949
+ }
950
+ }
951
+ }
915
952
  const parsed = parseValue(value, propDef, `[[${desc}]].${prop}`, false, warn);
916
953
  cache.set(prop, parsed);
917
954
  return parsed;
@@ -1052,6 +1089,9 @@ class EntityStore {
1052
1089
  record.data = mergeValues(record.data, obj);
1053
1090
  record.notifier.notify();
1054
1091
  record.cache.clear();
1092
+ if (record.proxy === void 0) {
1093
+ record.proxy = this.createEntityProxy(record, shape);
1094
+ }
1055
1095
  }
1056
1096
  record.entityRefs = entityRefs;
1057
1097
  return record;
@@ -2562,7 +2602,7 @@ var QueryType = /* @__PURE__ */ ((QueryType2) => {
2562
2602
  return QueryType2;
2563
2603
  })(QueryType || {});
2564
2604
  function isSignal(value) {
2565
- return typeof value === "object" && value !== null;
2605
+ return typeof value === "object" && value !== null && !Array.isArray(value) && "value" in value && "_id" in value;
2566
2606
  }
2567
2607
  function extractParamsForKey(params) {
2568
2608
  if (params === void 0) {
@@ -2706,7 +2746,7 @@ function createPathInterpolator(pathTemplate) {
2706
2746
  lastIndex = paramRegex.lastIndex;
2707
2747
  }
2708
2748
  segments.push(pathTemplate.slice(lastIndex));
2709
- return (params) => {
2749
+ const interpolate = (params) => {
2710
2750
  let result = segments[0];
2711
2751
  for (let i = 0; i < paramKeys.length; i++) {
2712
2752
  result += encodeURIComponent(String(params[paramKeys[i]])) + segments[i + 1];
@@ -2725,6 +2765,7 @@ function createPathInterpolator(pathTemplate) {
2725
2765
  }
2726
2766
  return result;
2727
2767
  };
2768
+ return { interpolate, pathParamNames: paramKeysSet };
2728
2769
  }
2729
2770
  const QUERY_DEFINITION_MAP = /* @__PURE__ */ new Map();
2730
2771
  function buildQueryFn(queryDefinitionBuilder) {
@@ -2734,6 +2775,8 @@ function buildQueryFn(queryDefinitionBuilder) {
2734
2775
  const {
2735
2776
  path,
2736
2777
  method = "GET",
2778
+ searchParams,
2779
+ body,
2737
2780
  response,
2738
2781
  requestOptions,
2739
2782
  cache,
@@ -2760,14 +2803,53 @@ function buildQueryFn(queryDefinitionBuilder) {
2760
2803
  shape = response;
2761
2804
  shapeKey = utils.hashValue(shape);
2762
2805
  }
2763
- const interpolatePath = createPathInterpolator(path);
2806
+ const { interpolate: interpolatePath, pathParamNames } = createPathInterpolator(path);
2807
+ const bodyParamNames = /* @__PURE__ */ new Set();
2808
+ const hasBody = body !== void 0 && typeof body === "object" && !(body instanceof ValidatorDef) && !(body instanceof Set);
2809
+ if (hasBody) {
2810
+ for (const key of Object.keys(body)) {
2811
+ bodyParamNames.add(key);
2812
+ }
2813
+ }
2814
+ const searchParamNames = new Set(
2815
+ searchParams && typeof searchParams === "object" && !(searchParams instanceof ValidatorDef) && !(searchParams instanceof Set) ? Object.keys(searchParams) : []
2816
+ );
2817
+ const checkConflicts = (sourceNames, targetNames, sourceLabel, targetLabel) => {
2818
+ const conflicts = [...sourceNames].filter((name) => targetNames.has(name));
2819
+ if (conflicts.length > 0) {
2820
+ throw new Error(
2821
+ `Query definition error: ${sourceLabel} [${conflicts.join(", ")}] conflict with ${targetLabel}. Please rename to avoid this conflict.`
2822
+ );
2823
+ }
2824
+ };
2825
+ checkConflicts(searchParamNames, pathParamNames, "Search param(s)", `path parameter(s) in "${path}"`);
2826
+ checkConflicts(bodyParamNames, pathParamNames, "Body field(s)", `path parameter(s) in "${path}"`);
2827
+ checkConflicts(bodyParamNames, searchParamNames, "Body field(s)", "search param(s)");
2764
2828
  const fetchFn = async (context, params) => {
2765
- const interpolatedPath = interpolatePath(params);
2829
+ let bodyData;
2830
+ let urlParams = params;
2831
+ if (hasBody) {
2832
+ bodyData = {};
2833
+ urlParams = params !== void 0 ? {} : void 0;
2834
+ if (params !== void 0) {
2835
+ for (const key in params) {
2836
+ if (bodyParamNames.has(key)) {
2837
+ bodyData[key] = params[key];
2838
+ } else {
2839
+ urlParams[key] = params[key];
2840
+ }
2841
+ }
2842
+ }
2843
+ }
2844
+ const interpolatedPath = interpolatePath(urlParams ?? {});
2766
2845
  const baseUrl = resolveBaseUrl(requestOptions?.baseUrl) ?? resolveBaseUrl(context.baseUrl);
2767
2846
  const fullUrl = baseUrl ? `${baseUrl}${interpolatedPath}` : interpolatedPath;
2768
- const { baseUrl: _baseUrl, ...fetchOptions } = requestOptions ?? {};
2847
+ const { baseUrl: _baseUrl, headers: userHeaders, ...fetchOptions } = requestOptions ?? {};
2848
+ const headers = bodyData ? { "Content-Type": "application/json", ...userHeaders } : userHeaders;
2769
2849
  const response2 = await context.fetch(fullUrl, {
2770
2850
  method,
2851
+ headers,
2852
+ body: bodyData ? JSON.stringify(bodyData) : void 0,
2771
2853
  ...fetchOptions
2772
2854
  });
2773
2855
  return response2.json();
@@ -2928,13 +3010,7 @@ function buildMutationFn(mutationDefinitionBuilder) {
2928
3010
  const id = `mutation:${method}:${path}`;
2929
3011
  const { shape: requestShape, shapeKey: requestShapeKey } = processTypeDef(request);
2930
3012
  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
- }
3013
+ const { interpolate: interpolatePath, pathParamNames } = createPathInterpolator(path);
2938
3014
  const mutateFn = async (context, requestData) => {
2939
3015
  const pathParams = {};
2940
3016
  for (const paramName of pathParamNames) {