@classytic/arc-next 0.5.0 → 0.6.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/hooks.js CHANGED
@@ -7,7 +7,7 @@ import { useDetailQuery, useInfiniteListQuery, useListQuery } from "./query.js";
7
7
  import { useMutationWithTransition, useOptimisticMutation } from "./mutation.js";
8
8
  import { subscribeToEvents } from "./sse.js";
9
9
  import { connectWs } from "./ws.js";
10
- import { useQueryClient } from "@tanstack/react-query";
10
+ import { useQuery, useQueryClient } from "@tanstack/react-query";
11
11
  import { useCallback, useEffect, useRef, useState } from "react";
12
12
 
13
13
  //#region src/hooks.ts
@@ -166,7 +166,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
166
166
  data
167
167
  }),
168
168
  queryClient,
169
- queryKeys: [KEYS.lists()],
169
+ queryKeys: [KEYS.lists(), KEYS.aggregations()],
170
170
  shouldToast,
171
171
  optimisticUpdate: (oldData, { data }) => {
172
172
  const optimisticItem = {
@@ -199,7 +199,11 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
199
199
  data
200
200
  }),
201
201
  queryClient,
202
- queryKeys: [KEYS.lists(), KEYS.details()],
202
+ queryKeys: [
203
+ KEYS.lists(),
204
+ KEYS.details(),
205
+ KEYS.aggregations()
206
+ ],
203
207
  shouldToast,
204
208
  optimisticUpdate: (oldData, { id, data }) => {
205
209
  const updated = updateListCache(oldData, (arr) => (arr || []).map((item) => resolveItemId(item) === id ? {
@@ -250,7 +254,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
250
254
  id
251
255
  }),
252
256
  queryClient,
253
- queryKeys: [KEYS.lists()],
257
+ queryKeys: [KEYS.lists(), KEYS.aggregations()],
254
258
  shouldToast,
255
259
  optimisticUpdate: (oldData, { id }) => {
256
260
  return updateListCache(oldData, (arr) => (arr || []).filter((item) => resolveItemId(item) !== id));
@@ -280,7 +284,11 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
280
284
  id
281
285
  });
282
286
  },
283
- invalidateQueries: [KEYS.lists(), KEYS.custom("deleted")],
287
+ invalidateQueries: [
288
+ KEYS.lists(),
289
+ KEYS.custom("deleted"),
290
+ KEYS.aggregations()
291
+ ],
284
292
  shouldToast,
285
293
  messages: {
286
294
  success: `${singular} restored successfully`,
@@ -608,7 +616,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
608
616
  data: vars.data
609
617
  });
610
618
  },
611
- invalidateQueries: [KEYS.lists()],
619
+ invalidateQueries: [KEYS.lists(), KEYS.aggregations()],
612
620
  messages: {
613
621
  success: `${pluralName} created successfully`,
614
622
  error: `Failed to create ${pluralName.toLowerCase()}`
@@ -626,7 +634,11 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
626
634
  data: vars.data
627
635
  });
628
636
  },
629
- invalidateQueries: [KEYS.lists(), KEYS.details()],
637
+ invalidateQueries: [
638
+ KEYS.lists(),
639
+ KEYS.details(),
640
+ KEYS.aggregations()
641
+ ],
630
642
  messages: {
631
643
  success: `${pluralName} updated successfully`,
632
644
  error: `Failed to update ${pluralName.toLowerCase()}`
@@ -643,7 +655,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
643
655
  filter: vars.filter
644
656
  });
645
657
  },
646
- invalidateQueries: [KEYS.lists()],
658
+ invalidateQueries: [KEYS.lists(), KEYS.aggregations()],
647
659
  messages: {
648
660
  success: `${pluralName} deleted successfully`,
649
661
  error: `Failed to delete ${pluralName.toLowerCase()}`
@@ -772,6 +784,38 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
772
784
  toastHandler: instanceToast
773
785
  });
774
786
  }
787
+ function useAggregation(args) {
788
+ const { name, filter, public: isPublic, enabled = true, staleTime = config.staleTime, gcTime = config.gcTime, refetchOnWindowFocus = false, refetchInterval, refetchIntervalInBackground, select, request, placeholderData } = args;
789
+ const auth = resolveAuth();
790
+ const filterKey = auth.organizationId ? {
791
+ _org: auth.organizationId,
792
+ ...filter ?? {}
793
+ } : filter ?? {};
794
+ return useQuery({
795
+ queryKey: KEYS.aggregation(name, filterKey),
796
+ queryFn: () => {
797
+ if (!api.aggregate) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define an aggregate method (requires arc 2.13+)`));
798
+ return api.aggregate({
799
+ name,
800
+ filter,
801
+ token: auth.token,
802
+ organizationId: auth.organizationId,
803
+ ...request ? { options: request } : {}
804
+ });
805
+ },
806
+ enabled: !!name && !!api.aggregate && createEnabledRule(auth.token, {
807
+ public: isPublic,
808
+ enabled
809
+ }, resolveAuthMode(), hasStaticAuth),
810
+ staleTime,
811
+ gcTime,
812
+ refetchOnWindowFocus,
813
+ ...select ? { select } : {},
814
+ ...refetchInterval !== void 0 ? { refetchInterval } : {},
815
+ ...refetchIntervalInBackground !== void 0 ? { refetchIntervalInBackground } : {},
816
+ ...placeholderData !== void 0 ? { placeholderData } : {}
817
+ });
818
+ }
775
819
  /**
776
820
  * Subscribe to live `<resource>.<operation>` broadcasts from arc and
777
821
  * auto-invalidate this entity's TanStack Query cache.
@@ -813,6 +857,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
813
857
  return raw != null ? String(raw) : void 0;
814
858
  })() : void 0;
815
859
  queryClient.invalidateQueries({ queryKey: KEYS.lists() });
860
+ queryClient.invalidateQueries({ queryKey: KEYS.aggregations() });
816
861
  if (id && (operation === "updated" || operation === "deleted")) queryClient.invalidateQueries({ queryKey: KEYS.detail(id) });
817
862
  onEventRef.current?.({
818
863
  operation,
@@ -894,6 +939,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
894
939
  useSearchEngine,
895
940
  useSearchSimilar,
896
941
  useEmbed,
942
+ useAggregation,
897
943
  useResourceSync,
898
944
  useNavigation
899
945
  };
@@ -61,6 +61,20 @@ interface CrudPrefetcher {
61
61
  * @example
62
62
  * await productsPrefetcher.prefetchInfiniteList(queryClient, { limit: 20 });
63
63
  */
64
+ /**
65
+ * Prefetch a declared aggregation (arc 2.13+). Uses the same query key as
66
+ * `useAggregation` so RSC-pre-rendered dashboard rows hydrate without a
67
+ * client refetch.
68
+ *
69
+ * @example
70
+ * await ordersPrefetcher.prefetchAggregation(
71
+ * queryClient,
72
+ * 'salesByDay',
73
+ * { from: '2025-01-01', to: '2025-12-31' },
74
+ * { token: jwt, organizationId: orgId, staleTime: 60_000 },
75
+ * );
76
+ */
77
+ prefetchAggregation: (queryClient: QueryClient, name: string, filter?: Record<string, unknown>, options?: PrefetchOptions) => Promise<void>;
64
78
  prefetchInfiniteList: (queryClient: QueryClient, params?: Record<string, unknown>, options?: PrefetchOptions) => Promise<void>;
65
79
  }
66
80
  /**
@@ -122,6 +136,15 @@ declare function createCrudPrefetcher(api: {
122
136
  headerOptions?: Record<string, string>;
123
137
  };
124
138
  }) => Promise<unknown>;
139
+ aggregate?: (opts: {
140
+ name: string;
141
+ filter?: Record<string, unknown>;
142
+ token?: string | null;
143
+ organizationId?: string | null;
144
+ options?: {
145
+ headerOptions?: Record<string, string>;
146
+ };
147
+ }) => Promise<unknown>;
125
148
  getTree?: (opts: {
126
149
  params?: Record<string, unknown>;
127
150
  token?: string | null;
package/dist/prefetch.js CHANGED
@@ -100,6 +100,26 @@ function createCrudPrefetcher(api, entityKey) {
100
100
  staleTime: options.staleTime
101
101
  });
102
102
  },
103
+ async prefetchAggregation(queryClient, name, filter, options = {}) {
104
+ if (!api.aggregate) throw new Error(`[arc-next] prefetchAggregation requires an api with aggregate (arc 2.13+)`);
105
+ if (!name) throw new Error("[arc-next] prefetchAggregation: aggregation name is required");
106
+ const orgId = options.organizationId ?? null;
107
+ const filterKey = orgId ? {
108
+ _org: orgId,
109
+ ...filter ?? {}
110
+ } : filter ?? {};
111
+ await queryClient.prefetchQuery({
112
+ queryKey: KEYS.aggregation(name, filterKey),
113
+ queryFn: () => api.aggregate({
114
+ name,
115
+ filter,
116
+ token: options.token ?? null,
117
+ organizationId: orgId,
118
+ ...options.headers ? { options: { headerOptions: options.headers } } : {}
119
+ }),
120
+ staleTime: options.staleTime
121
+ });
122
+ },
103
123
  async prefetchTree(queryClient, params = {}, options = {}) {
104
124
  if (!api.getTree) throw new Error(`[arc-next] prefetchTree requires an api with getTree (tree preset)`);
105
125
  const { organizationId: paramOrgId, ...restParams } = params;
@@ -1,11 +1,12 @@
1
- import { BaseApi, BulkCreateResponse, BulkDeleteResponse, BulkUpdateResponse, ScopedArgs } from "../api.js";
1
+ import { BaseApi, ScopedArgs } from "../api.js";
2
+ import { BulkCreateResult, DeleteManyResult, UpdateManyResult } from "@classytic/repo-core/repository";
2
3
 
3
4
  //#region src/presets/bulk.d.ts
4
5
  interface BulkMethods<TDoc, TCreate = Partial<TDoc>, TUpdate = Partial<TDoc>> {
5
6
  /** Insert many docs in one round-trip. Backend mounts `POST /:resource/bulk`. */
6
7
  bulkCreate(args: ScopedArgs & {
7
8
  data: TCreate[];
8
- }): Promise<BulkCreateResponse<TDoc>>;
9
+ }): Promise<BulkCreateResult<TDoc>>;
9
10
  /**
10
11
  * Update all docs matching `filter` with `data`.
11
12
  * Backend mounts `PATCH /:resource/bulk`.
@@ -13,14 +14,14 @@ interface BulkMethods<TDoc, TCreate = Partial<TDoc>, TUpdate = Partial<TDoc>> {
13
14
  bulkUpdate(args: ScopedArgs & {
14
15
  filter: Record<string, unknown>;
15
16
  data: TUpdate;
16
- }): Promise<BulkUpdateResponse>;
17
+ }): Promise<UpdateManyResult>;
17
18
  /**
18
19
  * Delete all docs matching `filter`.
19
20
  * Backend mounts `DELETE /:resource/bulk`.
20
21
  */
21
22
  bulkDelete(args: ScopedArgs & {
22
23
  filter: Record<string, unknown>;
23
- }): Promise<BulkDeleteResponse>;
24
+ }): Promise<DeleteManyResult>;
24
25
  }
25
26
  /**
26
27
  * Adds bulk preset methods to a BaseApi.
@@ -1,4 +1,5 @@
1
- import { ApiResponse, BaseApi, PaginatedResponse, ScopedArgs } from "../api.js";
1
+ import { BaseApi, ScopedArgs } from "../api.js";
2
+ import { PaginatedResult } from "@classytic/repo-core/pagination";
2
3
 
3
4
  //#region src/presets/search.d.ts
4
5
  interface SearchPresetMethods<TDoc> {
@@ -10,7 +11,7 @@ interface SearchPresetMethods<TDoc> {
10
11
  /** Free-text query forwarded as `body.query`. */query?: string; /** Engine-specific options merged into the request body. */
11
12
  body?: TBody; /** Override path (default `/search`). */
12
13
  path?: string;
13
- }): Promise<ApiResponse<TResult[]> | PaginatedResponse<TResult>>;
14
+ }): Promise<TResult[] | PaginatedResult<TResult>>;
14
15
  /**
15
16
  * Vector / semantic similarity (Atlas, Pinecone, Qdrant...).
16
17
  * Backend mounts `POST /:resource/search-similar`.
@@ -20,7 +21,7 @@ interface SearchPresetMethods<TDoc> {
20
21
  vector?: number[]; /** Vector-engine options (`topK`, `filter`, `index`, ...). */
21
22
  body?: TBody; /** Override path (default `/search-similar`). */
22
23
  path?: string;
23
- }): Promise<ApiResponse<TResult[]>>;
24
+ }): Promise<TResult[]>;
24
25
  /**
25
26
  * Convert text/media to a vector embedding via the engine the resource is wired to.
26
27
  * Backend mounts `POST /:resource/embed`.
@@ -29,7 +30,7 @@ interface SearchPresetMethods<TDoc> {
29
30
  /** Text or array of texts to embed. */input: string | string[]; /** Embed-engine options (`model`, `dimensions`, ...). */
30
31
  body?: Record<string, unknown>; /** Override path (default `/embed`). */
31
32
  path?: string;
32
- }): Promise<ApiResponse<number[] | number[][]>>;
33
+ }): Promise<number[] | number[][]>;
33
34
  }
34
35
  /**
35
36
  * Adds search preset methods to a BaseApi.
@@ -1,4 +1,4 @@
1
- import { ApiResponse, BaseApi, ScopedArgs } from "../api.js";
1
+ import { BaseApi, ScopedArgs } from "../api.js";
2
2
 
3
3
  //#region src/presets/slug.d.ts
4
4
  interface SlugLookupMethods<TDoc> {
@@ -9,7 +9,7 @@ interface SlugLookupMethods<TDoc> {
9
9
  select?: string;
10
10
  populate?: string | string[];
11
11
  };
12
- }): Promise<ApiResponse<TDoc>>;
12
+ }): Promise<TDoc>;
13
13
  }
14
14
  /**
15
15
  * Adds slug-lookup preset methods to a BaseApi.
@@ -1,15 +1,16 @@
1
- import { ApiResponse, BaseApi, PaginatedResponse, QueryParams, ScopedArgs } from "../api.js";
1
+ import { BaseApi, QueryParams, ScopedArgs } from "../api.js";
2
+ import { PaginatedResult } from "@classytic/repo-core/pagination";
2
3
 
3
4
  //#region src/presets/soft-delete.d.ts
4
5
  interface SoftDeleteMethods<TDoc> {
5
6
  /** List soft-deleted docs. Backend mounts `GET /:resource/deleted`. */
6
7
  getDeleted(args?: ScopedArgs & {
7
8
  params?: QueryParams;
8
- }): Promise<PaginatedResponse<TDoc>>;
9
+ }): Promise<PaginatedResult<TDoc>>;
9
10
  /** Undo a soft-delete. Backend mounts `POST /:resource/:id/restore`. */
10
11
  restore(args: ScopedArgs & {
11
12
  id: string;
12
- }): Promise<ApiResponse<TDoc>>;
13
+ }): Promise<TDoc>;
13
14
  }
14
15
  /**
15
16
  * Adds soft-delete preset methods to a BaseApi.
@@ -1,16 +1,17 @@
1
- import { ApiResponse, BaseApi, PaginatedResponse, QueryParams, ScopedArgs } from "../api.js";
1
+ import { BaseApi, QueryParams, ScopedArgs } from "../api.js";
2
+ import { PaginatedResult } from "@classytic/repo-core/pagination";
2
3
 
3
4
  //#region src/presets/tree.d.ts
4
5
  interface TreeMethods<TDoc> {
5
6
  /** Fetch the full hierarchy. Backend mounts `GET /:resource/tree`. */
6
7
  getTree(args?: ScopedArgs & {
7
8
  params?: QueryParams;
8
- }): Promise<ApiResponse<TDoc[]>>;
9
+ }): Promise<TDoc[]>;
9
10
  /** Fetch direct children of a node. Backend mounts `GET /:resource/:id/children`. */
10
11
  getChildren(args: ScopedArgs & {
11
12
  parentId: string;
12
13
  params?: QueryParams;
13
- }): Promise<PaginatedResponse<TDoc>>;
14
+ }): Promise<PaginatedResult<TDoc>>;
14
15
  }
15
16
  /**
16
17
  * Adds tree preset methods to a BaseApi.
package/dist/query.d.ts CHANGED
@@ -169,13 +169,13 @@ declare function useInfiniteListQuery<T>({
169
169
  /** Recognized data-freshness presets. Maps to QUERY_CONFIGS. */
170
170
  type QueryFreshness = keyof typeof QUERY_CONFIGS;
171
171
  /**
172
- * Extracts the inner type of an Arc envelope `{ success, data: T }`.
173
- * Falls through to `T` itself when the response isn't an envelope.
172
+ * Identity passthrough arc emits raw data on success (no envelope; HTTP
173
+ * status discriminates errors via thrown `ArcApiError`). Kept as a named
174
+ * type for the `useApiQuery` default param so the public signature stays
175
+ * `useApiQuery<TResponse, TData = ExtractData<TResponse>>` for callers
176
+ * that want a custom select projection.
174
177
  */
175
- type ExtractData<T> = T extends {
176
- success: unknown;
177
- data: infer D;
178
- } ? D : T;
178
+ type ExtractData<T> = T;
179
179
  /** Per-call request pass-through and TanStack Query overrides. */
180
180
  interface UseApiQueryOptions {
181
181
  staleTime?: number;
@@ -196,9 +196,9 @@ interface UseApiQueryConfig<TResponse, TData> {
196
196
  /** Freshness preset name (`realtime` | `frequent` | `stable` | `static`). */
197
197
  freshness?: QueryFreshness;
198
198
  /**
199
- * Custom projection from the raw response. When omitted, the hook auto-unwraps
200
- * Arc envelopes (`{ success, data }`) and returns `data`. Non-envelope responses
201
- * pass through unchanged.
199
+ * Custom projection from the raw response. When omitted, the response is
200
+ * returned unchanged arc 2.13+ has no wire envelope, so the response
201
+ * already IS the data.
202
202
  */
203
203
  select?: (response: TResponse) => TData;
204
204
  /** TanStack Query overrides (staleTime, refetchInterval, retry, etc.). */
@@ -216,29 +216,29 @@ interface UseApiQueryResult<TData> {
216
216
  }
217
217
  /**
218
218
  * Generic query hook for non-CRUD reads (reports, aggregates, lookups, RPC-style
219
- * endpoints). Wraps TanStack Query with three ergonomics on top:
219
+ * endpoints). Wraps TanStack Query with two ergonomics on top:
220
220
  *
221
- * 1. **Envelope auto-unwrap.** `{ success, data: T }` responses are projected to
222
- * `T` automatically. Override with a custom `select` for shape changes or
223
- * multi-field projections.
224
- * 2. **Freshness presets.** Pass `freshness: 'realtime' | 'frequent' | 'stable' |
221
+ * 1. **Freshness presets.** Pass `freshness: 'realtime' | 'frequent' | 'stable' |
225
222
  * 'static'` to map onto `QUERY_CONFIGS`. Per-option overrides still win.
226
- * 3. **Standardized result shape.** `{ data, isLoading, isFetching, isError,
223
+ * 2. **Standardized result shape.** `{ data, isLoading, isFetching, isError,
227
224
  * isSuccess, isStale, error, refetch }` — same contract as the CRUD hooks.
228
225
  *
226
+ * Arc emits raw data on success (no envelope), so the response IS the data.
227
+ * Use `select` for shape transformations / multi-field projections.
228
+ *
229
229
  * @example
230
- * // Auto-unwrap envelope
231
- * const { data } = useApiQuery<ApiResponse<DashboardStats>>({
230
+ * // Direct typing — response IS the data
231
+ * const { data } = useApiQuery<DashboardStats>({
232
232
  * queryKey: ['dashboard', 'stats'],
233
233
  * queryFn: ({ signal }) => api.request('GET', '/dashboard/stats', { options: { signal } }),
234
234
  * freshness: 'realtime',
235
235
  * });
236
236
  *
237
- * // Custom projection
237
+ * // Custom projection (e.g. extract a sub-field)
238
238
  * const { data } = useApiQuery({
239
239
  * queryKey: ['ledger', accountId],
240
- * queryFn: ({ signal }) => api.request('GET', `/ledger/${accountId}`, { options: { signal } }),
241
- * select: (res) => res.data?.entries ?? [],
240
+ * queryFn: ({ signal }) => api.request<{ entries: Entry[] }>('GET', `/ledger/${accountId}`, { options: { signal } }),
241
+ * select: (res) => res.entries,
242
242
  * });
243
243
  */
244
244
  declare function useApiQuery<TResponse = unknown, TData = ExtractData<TResponse>>({
package/dist/query.js CHANGED
@@ -98,40 +98,30 @@ function useInfiniteListQuery({ queryKey, queryFn, enabled = true, options = {},
98
98
  };
99
99
  }
100
100
  /**
101
- * Strict Arc envelope detector — requires BOTH `success` and `data` keys.
102
- *
103
- * Stricter than checking only `data` so we don't mis-unwrap responses that happen
104
- * to use a `data` field for unrelated semantics (e.g. a chart that returns
105
- * `{ data: number[], labels: [] }`).
106
- */
107
- function isArcEnvelope(value) {
108
- return typeof value === "object" && value !== null && "success" in value && "data" in value;
109
- }
110
- /**
111
101
  * Generic query hook for non-CRUD reads (reports, aggregates, lookups, RPC-style
112
- * endpoints). Wraps TanStack Query with three ergonomics on top:
102
+ * endpoints). Wraps TanStack Query with two ergonomics on top:
113
103
  *
114
- * 1. **Envelope auto-unwrap.** `{ success, data: T }` responses are projected to
115
- * `T` automatically. Override with a custom `select` for shape changes or
116
- * multi-field projections.
117
- * 2. **Freshness presets.** Pass `freshness: 'realtime' | 'frequent' | 'stable' |
104
+ * 1. **Freshness presets.** Pass `freshness: 'realtime' | 'frequent' | 'stable' |
118
105
  * 'static'` to map onto `QUERY_CONFIGS`. Per-option overrides still win.
119
- * 3. **Standardized result shape.** `{ data, isLoading, isFetching, isError,
106
+ * 2. **Standardized result shape.** `{ data, isLoading, isFetching, isError,
120
107
  * isSuccess, isStale, error, refetch }` — same contract as the CRUD hooks.
121
108
  *
109
+ * Arc emits raw data on success (no envelope), so the response IS the data.
110
+ * Use `select` for shape transformations / multi-field projections.
111
+ *
122
112
  * @example
123
- * // Auto-unwrap envelope
124
- * const { data } = useApiQuery<ApiResponse<DashboardStats>>({
113
+ * // Direct typing — response IS the data
114
+ * const { data } = useApiQuery<DashboardStats>({
125
115
  * queryKey: ['dashboard', 'stats'],
126
116
  * queryFn: ({ signal }) => api.request('GET', '/dashboard/stats', { options: { signal } }),
127
117
  * freshness: 'realtime',
128
118
  * });
129
119
  *
130
- * // Custom projection
120
+ * // Custom projection (e.g. extract a sub-field)
131
121
  * const { data } = useApiQuery({
132
122
  * queryKey: ['ledger', accountId],
133
- * queryFn: ({ signal }) => api.request('GET', `/ledger/${accountId}`, { options: { signal } }),
134
- * select: (res) => res.data?.entries ?? [],
123
+ * queryFn: ({ signal }) => api.request<{ entries: Entry[] }>('GET', `/ledger/${accountId}`, { options: { signal } }),
124
+ * select: (res) => res.entries,
135
125
  * });
136
126
  */
137
127
  function useApiQuery({ queryKey, queryFn, enabled = true, freshness, select, options = {} }) {
@@ -140,7 +130,7 @@ function useApiQuery({ queryKey, queryFn, enabled = true, freshness, select, opt
140
130
  const projection = useCallback((response) => {
141
131
  const fn = selectRef.current;
142
132
  if (fn) return fn(response);
143
- return isArcEnvelope(response) ? response.data : response;
133
+ return response;
144
134
  }, []);
145
135
  const preset = freshness ? QUERY_CONFIGS[freshness] : void 0;
146
136
  const query = useQuery({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/arc-next",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "React + TanStack Query SDK for Arc resources",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -122,11 +122,13 @@
122
122
  "prepublishOnly": "npm run typecheck && npm test && npm run build"
123
123
  },
124
124
  "peerDependencies": {
125
+ "@classytic/repo-core": ">=0.4.0",
125
126
  "@tanstack/react-query": ">=5.0.0",
126
127
  "react": ">=19.0.0"
127
128
  },
128
129
  "devDependencies": {
129
130
  "@classytic/dev-tools": "^0.2.0",
131
+ "@classytic/repo-core": "^0.4.0",
130
132
  "@tanstack/react-query": "^5.97.0",
131
133
  "@testing-library/jest-dom": "^6.9.1",
132
134
  "@testing-library/react": "^16.3.2",