@classytic/arc-next 0.4.0 → 0.5.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
@@ -1,11 +1,14 @@
1
1
  "use client";
2
2
 
3
- import { getAuthContext, getAuthMode } from "./client.js";
3
+ import { getAuthMode, getClientAuthContext } from "./client.js";
4
4
  import { isKeysetPagination, isOffsetPagination } from "./api.js";
5
- import { DEFAULT_QUERY_CONFIG, createCacheUtils, createQueryKeys, extractItem, getItemId, updateListCache, useDetailQuery, useInfiniteListQuery, useListQuery } from "./query.js";
5
+ import { DEFAULT_QUERY_CONFIG, createCacheUtils, createQueryKeys, extractItem, getItemId, updateListCache } from "./cache.js";
6
+ import { useDetailQuery, useInfiniteListQuery, useListQuery } from "./query.js";
6
7
  import { useMutationWithTransition, useOptimisticMutation } from "./mutation.js";
8
+ import { subscribeToEvents } from "./sse.js";
9
+ import { connectWs } from "./ws.js";
7
10
  import { useQueryClient } from "@tanstack/react-query";
8
- import { useCallback, useRef } from "react";
11
+ import { useCallback, useEffect, useRef, useState } from "react";
9
12
 
10
13
  //#region src/hooks.ts
11
14
  let useRouterHook = null;
@@ -21,12 +24,17 @@ let useRouterHook = null;
21
24
  function configureNavigation(hook) {
22
25
  useRouterHook = hook;
23
26
  }
24
- function createEnabledRule(token, options, authMode = getAuthMode()) {
27
+ function createEnabledRule(token, options, authMode = getAuthMode(), hasStaticAuth = false) {
25
28
  if (authMode === "cookie" || options.public) return options.enabled ?? true;
29
+ if (hasStaticAuth) return options.enabled ?? true;
26
30
  return options.enabled !== void 0 ? options.enabled && !!token : !!token;
27
31
  }
28
32
  function createCrudHooks({ api, entityKey, singular, plural, idField, defaults = {}, callbacks = {}, client }) {
29
33
  const pluralName = plural ?? `${singular}s`;
34
+ /** Resolve auth context — per-client auth takes priority over global */
35
+ const resolveAuth = () => getClientAuthContext(client);
36
+ /** Whether auth is provided via static config (headers, internalApiKey, per-client auth) — no token needed for enablement */
37
+ const hasStaticAuth = !!(client?.config?.defaultHeaders || client?.config?.internalApiKey || client?.auth);
30
38
  /** Extract ID from an item using configured idField, falling back to _id → id */
31
39
  function resolveItemId(item) {
32
40
  if (!item || typeof item !== "object") return null;
@@ -60,12 +68,12 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
60
68
  let token;
61
69
  let params;
62
70
  let options;
63
- if (tokenOrParams === null || typeof tokenOrParams === "string") {
71
+ if (typeof tokenOrParams === "string" || tokenOrParams === null && maybeOptions !== void 0) {
64
72
  token = tokenOrParams;
65
73
  params = paramsOrOptions ?? {};
66
74
  options = maybeOptions ?? {};
67
75
  } else {
68
- const auth = getAuthContext();
76
+ const auth = resolveAuth();
69
77
  token = auth.token;
70
78
  params = tokenOrParams ?? {};
71
79
  options = paramsOrOptions ?? {};
@@ -91,7 +99,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
91
99
  ...requestOpts
92
100
  }
93
101
  }),
94
- enabled: createEnabledRule(token, queryOpts, resolveAuthMode()),
102
+ enabled: createEnabledRule(token, queryOpts, resolveAuthMode(), hasStaticAuth),
95
103
  options: {
96
104
  staleTime: queryOpts.staleTime ?? config.staleTime,
97
105
  gcTime: queryOpts.gcTime ?? config.gcTime,
@@ -101,7 +109,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
101
109
  refetchIntervalInBackground: queryOpts.refetchIntervalInBackground
102
110
  },
103
111
  prefillDetailCache: queryOpts.prefillDetailCache ?? true,
104
- detailKeyBuilder: (id) => KEYS.detail(id),
112
+ detailKeyBuilder: (id) => KEYS.scopedDetail(id, organizationId ?? null),
105
113
  itemIdResolver: resolveItemId,
106
114
  select: queryOpts.select
107
115
  });
@@ -109,11 +117,11 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
109
117
  function useDetail(id, tokenOrOptions, maybeOptions) {
110
118
  let token;
111
119
  let options;
112
- if (tokenOrOptions === null || typeof tokenOrOptions === "string") {
120
+ if (typeof tokenOrOptions === "string" || tokenOrOptions === null && maybeOptions !== void 0) {
113
121
  token = tokenOrOptions;
114
122
  options = maybeOptions ?? {};
115
123
  } else {
116
- const auth = getAuthContext();
124
+ const auth = resolveAuth();
117
125
  token = auth.token;
118
126
  options = tokenOrOptions ?? {};
119
127
  if (auth.organizationId && !options.organizationId) options = {
@@ -122,8 +130,9 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
122
130
  };
123
131
  }
124
132
  const { organizationId, params: queryParams, request: requestOpts, ...restOptions } = options;
133
+ const detailKey = KEYS.scopedDetail(id || "", organizationId ?? null);
125
134
  return useDetailQuery({
126
- queryKey: queryParams ? [...KEYS.detail(id || ""), queryParams] : KEYS.detail(id || ""),
135
+ queryKey: queryParams ? [...detailKey, queryParams] : detailKey,
127
136
  queryFn: ({ signal }) => api.getById({
128
137
  id,
129
138
  token,
@@ -134,7 +143,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
134
143
  ...requestOpts
135
144
  }
136
145
  }),
137
- enabled: !!id && createEnabledRule(token, restOptions, resolveAuthMode()),
146
+ enabled: !!id && createEnabledRule(token, restOptions, resolveAuthMode(), hasStaticAuth),
138
147
  options: {
139
148
  staleTime: restOptions.staleTime ?? config.staleTime,
140
149
  gcTime: restOptions.gcTime ?? config.gcTime,
@@ -197,13 +206,16 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
197
206
  ...item,
198
207
  ...data
199
208
  } : item));
200
- queryClient.setQueryData(KEYS.detail(id), (current) => current ? {
209
+ const detailUpdater = (current) => current ? {
201
210
  ...current,
202
211
  data: {
203
212
  ...current.data || {},
204
213
  ...data
205
214
  }
206
- } : current);
215
+ } : current;
216
+ queryClient.getQueriesData({ queryKey: KEYS.detail(id) }).forEach(([qKey, qData]) => {
217
+ if (qData) queryClient.setQueryData(qKey, detailUpdater);
218
+ });
207
219
  return updated;
208
220
  },
209
221
  onSuccess: (raw, { id, data: updateData }) => {
@@ -276,8 +288,8 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
276
288
  },
277
289
  toastHandler: instanceToast
278
290
  });
279
- const resolveAuth = useCallback((params) => {
280
- const auth = getAuthContext();
291
+ const resolveActionAuth = useCallback((params) => {
292
+ const auth = resolveAuth();
281
293
  return {
282
294
  ...params,
283
295
  token: params.token ?? auth.token,
@@ -288,7 +300,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
288
300
  create: useCallback(async (params, options) => {
289
301
  silentRef.current = options?.silent ?? false;
290
302
  try {
291
- const entity = extractItem(await createMutation.mutateAsync(resolveAuth(params)));
303
+ const entity = extractItem(await createMutation.mutateAsync(resolveActionAuth(params)));
292
304
  options?.onSuccess?.(entity);
293
305
  options?.onSettled?.(entity, null);
294
306
  return entity;
@@ -299,11 +311,11 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
299
311
  } finally {
300
312
  silentRef.current = false;
301
313
  }
302
- }, [createMutation, resolveAuth]),
314
+ }, [createMutation, resolveActionAuth]),
303
315
  update: useCallback(async (params, options) => {
304
316
  silentRef.current = options?.silent ?? false;
305
317
  try {
306
- const entity = extractItem(await updateMutation.mutateAsync(resolveAuth(params)));
318
+ const entity = extractItem(await updateMutation.mutateAsync(resolveActionAuth(params)));
307
319
  options?.onSuccess?.(entity);
308
320
  options?.onSettled?.(entity, null);
309
321
  return entity;
@@ -314,11 +326,11 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
314
326
  } finally {
315
327
  silentRef.current = false;
316
328
  }
317
- }, [updateMutation, resolveAuth]),
329
+ }, [updateMutation, resolveActionAuth]),
318
330
  remove: useCallback(async (params, options) => {
319
331
  silentRef.current = options?.silent ?? false;
320
332
  try {
321
- const result = await deleteMutation.mutateAsync(resolveAuth(params));
333
+ const result = await deleteMutation.mutateAsync(resolveActionAuth(params));
322
334
  options?.onSuccess?.(result);
323
335
  options?.onSettled?.(result, null);
324
336
  return result;
@@ -329,11 +341,11 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
329
341
  } finally {
330
342
  silentRef.current = false;
331
343
  }
332
- }, [deleteMutation, resolveAuth]),
344
+ }, [deleteMutation, resolveActionAuth]),
333
345
  restore: useCallback(async (params, options) => {
334
346
  silentRef.current = options?.silent ?? false;
335
347
  try {
336
- const entity = extractItem(await restoreMutation.mutateAsync(resolveAuth(params)));
348
+ const entity = extractItem(await restoreMutation.mutateAsync(resolveActionAuth(params)));
337
349
  options?.onSuccess?.(entity);
338
350
  options?.onSettled?.(entity, null);
339
351
  return entity;
@@ -344,7 +356,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
344
356
  } finally {
345
357
  silentRef.current = false;
346
358
  }
347
- }, [restoreMutation, resolveAuth]),
359
+ }, [restoreMutation, resolveActionAuth]),
348
360
  isCreating: createMutation.isPending,
349
361
  isUpdating: updateMutation.isPending,
350
362
  isDeleting: deleteMutation.isPending,
@@ -356,12 +368,12 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
356
368
  let token;
357
369
  let params;
358
370
  let options;
359
- if (tokenOrParams === null || typeof tokenOrParams === "string") {
371
+ if (typeof tokenOrParams === "string" || tokenOrParams === null && maybeOptions !== void 0) {
360
372
  token = tokenOrParams;
361
373
  params = paramsOrOptions ?? {};
362
374
  options = maybeOptions ?? {};
363
375
  } else {
364
- const auth = getAuthContext();
376
+ const auth = resolveAuth();
365
377
  token = auth.token;
366
378
  params = tokenOrParams ?? {};
367
379
  options = paramsOrOptions ?? {};
@@ -393,7 +405,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
393
405
  }
394
406
  });
395
407
  },
396
- enabled: createEnabledRule(token, queryOpts, resolveAuthMode()),
408
+ enabled: createEnabledRule(token, queryOpts, resolveAuthMode(), hasStaticAuth),
397
409
  initialPageParam: restParams.after ? restParams.after : 1,
398
410
  getNextPageParam: (lastPage) => {
399
411
  const page = lastPage;
@@ -423,7 +435,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
423
435
  return useMutationWithTransition({
424
436
  mutationFn: ({ data, id, path }) => {
425
437
  if (!api.upload) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define an upload method`));
426
- const auth = getAuthContext();
438
+ const auth = resolveAuth();
427
439
  return api.upload({
428
440
  token: auth.token,
429
441
  organizationId: auth.organizationId,
@@ -443,46 +455,6 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
443
455
  toastHandler: instanceToast
444
456
  });
445
457
  }
446
- function useSearch(query, params, options) {
447
- const auth = getAuthContext();
448
- const token = params?.token ?? auth.token;
449
- const organizationId = params?.organizationId ?? auth.organizationId;
450
- const { organizationId: _, token: _t, ...restParams } = params ?? {};
451
- const searchParams = {
452
- q: query,
453
- ...restParams
454
- };
455
- const { request: requestOpts, ...queryOpts } = options ?? {};
456
- const searchKeyParams = organizationId ? {
457
- organizationId,
458
- ...searchParams
459
- } : searchParams;
460
- return useListQuery({
461
- queryKey: [
462
- ...KEYS.lists(),
463
- "search",
464
- searchKeyParams
465
- ],
466
- queryFn: ({ signal }) => {
467
- if (!api.search) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define a search method`));
468
- return api.search({
469
- token,
470
- organizationId,
471
- params: searchParams,
472
- options: {
473
- signal,
474
- ...requestOpts
475
- }
476
- });
477
- },
478
- enabled: !!api.search && query.length > 0 && createEnabledRule(token, queryOpts, resolveAuthMode()),
479
- options: {
480
- staleTime: queryOpts.staleTime ?? config.staleTime,
481
- gcTime: queryOpts.gcTime ?? config.gcTime
482
- },
483
- select: queryOpts.select
484
- });
485
- }
486
458
  function useCustomMutation(mutationConfig) {
487
459
  return useMutationWithTransition({
488
460
  mutationFn: mutationConfig.mutationFn,
@@ -495,7 +467,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
495
467
  });
496
468
  }
497
469
  function useDeleted(params, options) {
498
- const auth = getAuthContext();
470
+ const auth = resolveAuth();
499
471
  const token = auth.token;
500
472
  const mergedParams = params ?? {};
501
473
  const organizationId = mergedParams.organizationId ?? auth.organizationId;
@@ -518,7 +490,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
518
490
  }
519
491
  });
520
492
  },
521
- enabled: !!api.getDeleted && createEnabledRule(token, queryOpts, resolveAuthMode()),
493
+ enabled: !!api.getDeleted && createEnabledRule(token, queryOpts, resolveAuthMode(), hasStaticAuth),
522
494
  options: {
523
495
  staleTime: queryOpts.staleTime ?? config.staleTime,
524
496
  gcTime: queryOpts.gcTime ?? config.gcTime
@@ -527,7 +499,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
527
499
  });
528
500
  }
529
501
  function useDetailBySlug(slug, options) {
530
- const auth = getAuthContext();
502
+ const auth = resolveAuth();
531
503
  const token = auth.token;
532
504
  const resolvedOptions = options ?? {};
533
505
  const organizationId = resolvedOptions.organizationId ?? auth.organizationId;
@@ -547,7 +519,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
547
519
  }
548
520
  });
549
521
  },
550
- enabled: !!api.getBySlug && !!slug && createEnabledRule(token, restOptions, resolveAuthMode()),
522
+ enabled: !!api.getBySlug && !!slug && createEnabledRule(token, restOptions, resolveAuthMode(), hasStaticAuth),
551
523
  options: {
552
524
  staleTime: restOptions.staleTime ?? config.staleTime,
553
525
  gcTime: restOptions.gcTime ?? config.gcTime,
@@ -558,7 +530,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
558
530
  });
559
531
  }
560
532
  function useTree(params, options) {
561
- const auth = getAuthContext();
533
+ const auth = resolveAuth();
562
534
  const token = auth.token;
563
535
  const mergedParams = params ?? {};
564
536
  const organizationId = mergedParams.organizationId ?? auth.organizationId;
@@ -581,7 +553,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
581
553
  }
582
554
  });
583
555
  },
584
- enabled: !!api.getTree && createEnabledRule(token, queryOpts, resolveAuthMode()),
556
+ enabled: !!api.getTree && createEnabledRule(token, queryOpts, resolveAuthMode(), hasStaticAuth),
585
557
  options: {
586
558
  staleTime: queryOpts.staleTime ?? config.staleTime,
587
559
  gcTime: queryOpts.gcTime ?? config.gcTime
@@ -590,7 +562,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
590
562
  });
591
563
  }
592
564
  function useChildren(parentId, params, options) {
593
- const auth = getAuthContext();
565
+ const auth = resolveAuth();
594
566
  const token = auth.token;
595
567
  const mergedParams = params ?? {};
596
568
  const organizationId = mergedParams.organizationId ?? auth.organizationId;
@@ -614,45 +586,13 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
614
586
  }
615
587
  });
616
588
  },
617
- enabled: !!api.getChildren && !!parentId && createEnabledRule(token, queryOpts, resolveAuthMode()),
618
- options: {
619
- staleTime: queryOpts.staleTime ?? config.staleTime,
620
- gcTime: queryOpts.gcTime ?? config.gcTime
621
- },
622
- prefillDetailCache: queryOpts.prefillDetailCache ?? true,
623
- detailKeyBuilder: (id) => KEYS.detail(id),
624
- itemIdResolver: resolveItemId,
625
- select: queryOpts.select
626
- });
627
- }
628
- function useFindBy(field, value, options) {
629
- const auth = getAuthContext();
630
- const token = auth.token;
631
- const organizationId = auth.organizationId;
632
- const { operator, request: requestOpts, ...queryOpts } = options ?? {};
633
- return useListQuery({
634
- queryKey: KEYS.custom("findBy", field, value, operator),
635
- queryFn: ({ signal }) => {
636
- if (!api.findBy) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define a findBy method`));
637
- return api.findBy({
638
- token,
639
- organizationId,
640
- field,
641
- value,
642
- operator,
643
- options: {
644
- signal,
645
- ...requestOpts
646
- }
647
- });
648
- },
649
- enabled: !!api.findBy && value !== void 0 && value !== null && createEnabledRule(token, queryOpts, resolveAuthMode()),
589
+ enabled: !!api.getChildren && !!parentId && createEnabledRule(token, queryOpts, resolveAuthMode(), hasStaticAuth),
650
590
  options: {
651
591
  staleTime: queryOpts.staleTime ?? config.staleTime,
652
592
  gcTime: queryOpts.gcTime ?? config.gcTime
653
593
  },
654
594
  prefillDetailCache: queryOpts.prefillDetailCache ?? true,
655
- detailKeyBuilder: (id) => KEYS.detail(id),
595
+ detailKeyBuilder: (id) => KEYS.scopedDetail(id, organizationId ?? null),
656
596
  itemIdResolver: resolveItemId,
657
597
  select: queryOpts.select
658
598
  });
@@ -661,7 +601,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
661
601
  const bulkCreateMutation = useMutationWithTransition({
662
602
  mutationFn: (vars) => {
663
603
  if (!api.bulkCreate) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define a bulkCreate method`));
664
- const auth = getAuthContext();
604
+ const auth = resolveAuth();
665
605
  return api.bulkCreate({
666
606
  token: vars.token ?? auth.token,
667
607
  organizationId: vars.organizationId ?? auth.organizationId,
@@ -678,7 +618,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
678
618
  const bulkUpdateMutation = useMutationWithTransition({
679
619
  mutationFn: (vars) => {
680
620
  if (!api.bulkUpdate) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define a bulkUpdate method`));
681
- const auth = getAuthContext();
621
+ const auth = resolveAuth();
682
622
  return api.bulkUpdate({
683
623
  token: vars.token ?? auth.token,
684
624
  organizationId: vars.organizationId ?? auth.organizationId,
@@ -696,7 +636,7 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
696
636
  const bulkDeleteMutation = useMutationWithTransition({
697
637
  mutationFn: (vars) => {
698
638
  if (!api.bulkDelete) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define a bulkDelete method`));
699
- const auth = getAuthContext();
639
+ const auth = resolveAuth();
700
640
  return api.bulkDelete({
701
641
  token: vars.token ?? auth.token,
702
642
  organizationId: vars.organizationId ?? auth.organizationId,
@@ -731,6 +671,191 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
731
671
  isBulkDeleting: bulkDeleteMutation.isPending
732
672
  };
733
673
  }
674
+ function useAction(options) {
675
+ const queryClient = useQueryClient();
676
+ return useMutationWithTransition({
677
+ mutationFn: (vars) => {
678
+ if (!api.dispatchAction) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define a dispatchAction method`));
679
+ const action = vars.action ?? options?.action;
680
+ if (!action) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] useAction: action name required (pass via mutate({ action }) or factory options)`));
681
+ const auth = resolveAuth();
682
+ return api.dispatchAction({
683
+ token: auth.token,
684
+ organizationId: auth.organizationId,
685
+ id: vars.id,
686
+ action,
687
+ data: vars.data
688
+ });
689
+ },
690
+ invalidateQueries: options?.invalidateQueries ?? [KEYS.lists(), KEYS.details()],
691
+ onSuccess: (data, vars) => {
692
+ const action = vars.action ?? options?.action ?? "";
693
+ if (vars.id) queryClient.invalidateQueries({ queryKey: KEYS.detail(vars.id) });
694
+ options?.onSuccess?.(data, {
695
+ id: vars.id,
696
+ action,
697
+ data: vars.data
698
+ });
699
+ },
700
+ onError: (error, vars) => {
701
+ const action = vars.action ?? options?.action ?? "";
702
+ options?.onError?.(error, {
703
+ id: vars.id,
704
+ action,
705
+ data: vars.data
706
+ });
707
+ },
708
+ onSettled: (data, error, vars) => {
709
+ const action = vars.action ?? options?.action ?? "";
710
+ options?.onSettled?.(data, error, {
711
+ id: vars.id,
712
+ action,
713
+ data: vars.data
714
+ });
715
+ },
716
+ messages: options?.messages,
717
+ toastHandler: instanceToast
718
+ });
719
+ }
720
+ function useSearchEngine(options) {
721
+ return useMutationWithTransition({
722
+ mutationFn: (vars) => {
723
+ if (!api.searchEngine) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define a searchEngine method`));
724
+ const auth = resolveAuth();
725
+ return api.searchEngine({
726
+ token: auth.token,
727
+ organizationId: auth.organizationId,
728
+ query: vars.query,
729
+ body: vars.body,
730
+ path: options?.path
731
+ });
732
+ },
733
+ invalidateQueries: options?.invalidateQueries ?? [],
734
+ messages: options?.messages,
735
+ toastHandler: instanceToast
736
+ });
737
+ }
738
+ function useSearchSimilar(options) {
739
+ return useMutationWithTransition({
740
+ mutationFn: (vars) => {
741
+ if (!api.searchSimilar) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define a searchSimilar method`));
742
+ const auth = resolveAuth();
743
+ return api.searchSimilar({
744
+ token: auth.token,
745
+ organizationId: auth.organizationId,
746
+ query: vars.query,
747
+ vector: vars.vector,
748
+ body: vars.body,
749
+ path: options?.path
750
+ });
751
+ },
752
+ invalidateQueries: options?.invalidateQueries ?? [],
753
+ messages: options?.messages,
754
+ toastHandler: instanceToast
755
+ });
756
+ }
757
+ function useEmbed(options) {
758
+ return useMutationWithTransition({
759
+ mutationFn: (vars) => {
760
+ if (!api.embed) return Promise.reject(/* @__PURE__ */ new Error(`[arc-next] "${entityKey}" api does not define an embed method`));
761
+ const auth = resolveAuth();
762
+ return api.embed({
763
+ token: auth.token,
764
+ organizationId: auth.organizationId,
765
+ input: vars.input,
766
+ body: vars.body,
767
+ path: options?.path
768
+ });
769
+ },
770
+ invalidateQueries: options?.invalidateQueries ?? [],
771
+ messages: options?.messages,
772
+ toastHandler: instanceToast
773
+ });
774
+ }
775
+ /**
776
+ * Subscribe to live `<resource>.<operation>` broadcasts from arc and
777
+ * auto-invalidate this entity's TanStack Query cache.
778
+ *
779
+ * - `source: 'ws'` (default) → arc's `websocketPlugin` at `/ws`. Sends a
780
+ * `{ type: 'subscribe', resource }` handshake on connect.
781
+ * - `source: 'sse'` → arc's `ssePlugin` at `/events/stream`. Auto-derives
782
+ * patterns from the resource name.
783
+ *
784
+ * Both transports invalidate `KEYS.lists()` on `<resource>.created` and
785
+ * `<resource>.deleted`, and `KEYS.detail(id)` (prefix-matches scoped /
786
+ * parameterized variants) on `<resource>.updated` / `.deleted`.
787
+ */
788
+ function useResourceSync(options) {
789
+ const queryClient = useQueryClient();
790
+ const [isConnected, setIsConnected] = useState(false);
791
+ const source = options?.source ?? "ws";
792
+ const resource = options?.resource ?? entityKey;
793
+ const enabled = options?.enabled ?? true;
794
+ const path = options?.path;
795
+ const onEventRef = useRef(options?.onEvent);
796
+ onEventRef.current = options?.onEvent;
797
+ const onConnRef = useRef(options?.onConnectionChange);
798
+ onConnRef.current = options?.onConnectionChange;
799
+ useEffect(() => {
800
+ if (!enabled) return;
801
+ const handleBroadcast = (incomingType, payload) => {
802
+ const dot = incomingType.lastIndexOf(".");
803
+ const operation = dot >= 0 ? incomingType.slice(dot + 1) : incomingType;
804
+ if (operation !== "created" && operation !== "updated" && operation !== "deleted") return;
805
+ let doc = payload;
806
+ if (payload && typeof payload === "object" && !Array.isArray(payload) && "data" in payload) {
807
+ const inner = payload.data;
808
+ if (inner !== void 0) doc = inner;
809
+ }
810
+ const id = typeof doc === "object" && doc !== null ? (() => {
811
+ const o = doc;
812
+ const raw = idField ? o[idField] : o._id ?? o.id;
813
+ return raw != null ? String(raw) : void 0;
814
+ })() : void 0;
815
+ queryClient.invalidateQueries({ queryKey: KEYS.lists() });
816
+ if (id && (operation === "updated" || operation === "deleted")) queryClient.invalidateQueries({ queryKey: KEYS.detail(id) });
817
+ onEventRef.current?.({
818
+ operation,
819
+ id,
820
+ data: doc
821
+ });
822
+ };
823
+ if (source === "sse") {
824
+ const handle = subscribeToEvents({
825
+ resource,
826
+ path,
827
+ onConnectionChange: (c) => {
828
+ setIsConnected(c);
829
+ onConnRef.current?.(c);
830
+ },
831
+ onEvent: (event) => {
832
+ handleBroadcast(event.type, event.data);
833
+ }
834
+ });
835
+ return () => handle.close();
836
+ }
837
+ const handle = connectWs({
838
+ path,
839
+ subscribe: [resource],
840
+ patterns: [`${resource}.`],
841
+ onConnectionChange: (c) => {
842
+ setIsConnected(c);
843
+ onConnRef.current?.(c);
844
+ },
845
+ onMessage: (message) => {
846
+ handleBroadcast(message.type, message.data);
847
+ }
848
+ });
849
+ return () => handle.close();
850
+ }, [
851
+ enabled,
852
+ source,
853
+ resource,
854
+ path,
855
+ queryClient
856
+ ]);
857
+ return { isConnected };
858
+ }
734
859
  const resolvedRouterHook = instanceNavigation ?? useRouterHook ?? (() => ({
735
860
  push: () => {},
736
861
  replace: () => {}
@@ -740,7 +865,11 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
740
865
  const router = resolvedRouterHook();
741
866
  return useCallback((href, item, options = {}) => {
742
867
  const id = resolveItemId(item);
743
- if (id) queryClient.setQueryData(KEYS.detail(id), { data: item });
868
+ if (id) {
869
+ const orgId = resolveAuth().organizationId;
870
+ queryClient.setQueryData(KEYS.scopedDetail(id, orgId), { data: item });
871
+ if (orgId) queryClient.setQueryData(KEYS.detail(id), { data: item });
872
+ }
744
873
  if (!router) return;
745
874
  const { scroll = true, replace = false } = options;
746
875
  if (replace) router.replace(href, { scroll });
@@ -759,10 +888,13 @@ function createCrudHooks({ api, entityKey, singular, plural, idField, defaults =
759
888
  useDetailBySlug,
760
889
  useTree,
761
890
  useChildren,
762
- useFindBy,
763
891
  useUpload,
764
- useSearch,
765
892
  useCustomMutation,
893
+ useAction,
894
+ useSearchEngine,
895
+ useSearchSimilar,
896
+ useEmbed,
897
+ useResourceSync,
766
898
  useNavigation
767
899
  };
768
900
  }
@@ -1,5 +1,4 @@
1
1
  import { ToastHandler } from "./client.js";
2
- import { QUERY_CONFIGS } from "./query.js";
3
2
  import * as _$_tanstack_react_query0 from "@tanstack/react-query";
4
3
  import { QueryClient, QueryKey, UseMutateAsyncFunction, UseMutateFunction } from "@tanstack/react-query";
5
4
 
@@ -34,6 +33,23 @@ interface TransitionMutationReturn<TData, TVariables> {
34
33
  * configureToast({ success: toast.success, error: toast.error });
35
34
  */
36
35
  declare function configureToast(handler: ToastHandler): void;
36
+ /**
37
+ * Get the configured toast handler. Returns the console-based default when
38
+ * no handler has been configured yet.
39
+ *
40
+ * Use this when domain code outside the react-query lifecycle needs to fire
41
+ * ad-hoc success/error toasts using the same handler the SDK uses internally
42
+ * — avoids the need for consumer SDKs to keep a parallel cache of the handler.
43
+ *
44
+ * @example
45
+ * // In a domain helper outside any mutation lifecycle:
46
+ * import { getToastHandler } from '@classytic/arc-next/mutation';
47
+ *
48
+ * function notifySaved(label: string) {
49
+ * getToastHandler().success(`${label} saved`);
50
+ * }
51
+ */
52
+ declare function getToastHandler(): ToastHandler;
37
53
  interface TransitionMutationConfig<TData, TVariables> {
38
54
  mutationFn: (variables: TVariables) => Promise<TData>;
39
55
  invalidateQueries?: QueryKey[];
@@ -107,7 +123,5 @@ declare function useOptimisticMutation<TData, TVariables>(config: CreateOptimist
107
123
  data: [readonly unknown[], unknown][];
108
124
  }[];
109
125
  }>;
110
- /** @deprecated Use `useOptimisticMutation` */
111
- declare const createOptimisticMutation: typeof useOptimisticMutation;
112
126
  //#endregion
113
- export { CreateOptimisticMutationConfig, MutationCallbacks, MutationMessages, OptimisticMutationConfig, QUERY_CONFIGS, TransitionMutationConfig, TransitionMutationReturn, configureToast, createOptimisticMutation, useMutationWithOptimistic, useMutationWithTransition, useOptimisticMutation };
127
+ export { CreateOptimisticMutationConfig, MutationCallbacks, MutationMessages, OptimisticMutationConfig, TransitionMutationConfig, TransitionMutationReturn, configureToast, getToastHandler, useMutationWithOptimistic, useMutationWithTransition, useOptimisticMutation };