@svadmin/core 0.49.1 → 0.52.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/README.md +32 -0
- package/package.json +21 -4
- package/src/access-control-contract.ts +250 -0
- package/src/adapters/casbin.ts +20 -14
- package/src/adapters/casl.ts +22 -19
- package/src/admin-tool.ts +7 -7
- package/src/audit-contract.ts +105 -0
- package/src/audit.ts +88 -128
- package/src/auth-hooks.svelte.ts +593 -261
- package/src/auth-mutation-contract.ts +143 -0
- package/src/auth-query-contract.ts +97 -0
- package/src/captured-mutation.svelte.ts +45 -0
- package/src/chatProvider.svelte.ts +1 -1
- package/src/command-contract.ts +195 -0
- package/src/context.svelte.ts +217 -205
- package/src/contract-context.svelte.ts +37 -0
- package/src/contract-form.svelte.ts +566 -0
- package/src/create-contract.ts +56 -0
- package/src/create-hooks.svelte.ts +347 -0
- package/src/create-many-contract.ts +90 -0
- package/src/create-many-hooks.svelte.ts +396 -0
- package/src/data-transfer.svelte.ts +1 -215
- package/src/defined-options.ts +50 -0
- package/src/delete-contract.ts +50 -0
- package/src/delete-hooks.svelte.ts +403 -0
- package/src/delete-many-contract.ts +60 -0
- package/src/delete-many-hooks.svelte.ts +402 -0
- package/src/enterprise.ts +4 -4
- package/src/export-format.ts +16 -14
- package/src/export-hooks.svelte.ts +191 -0
- package/src/feature-gate-contract.ts +53 -0
- package/src/hook-utils.svelte.ts +53 -105
- package/src/hooks.svelte.ts +19 -774
- package/src/http.ts +88 -87
- package/src/i18n.svelte.ts +56 -2
- package/src/import-contract.ts +106 -0
- package/src/import-hooks.svelte.ts +310 -0
- package/src/index.ts +77 -28
- package/src/inferencer-graphql.ts +7 -6
- package/src/infinite-query.svelte.ts +228 -0
- package/src/invalidation-contract.ts +26 -0
- package/src/live-sse.ts +91 -127
- package/src/live-subscription.svelte.ts +130 -0
- package/src/live-transport.ts +127 -0
- package/src/live-websocket.ts +85 -118
- package/src/live.svelte.ts +66 -68
- package/src/notification.svelte.ts +17 -15
- package/src/permission-catalog.ts +121 -0
- package/src/permission-hints-contract.ts +48 -0
- package/src/permissions.svelte.ts +112 -59
- package/src/plain-data.ts +61 -0
- package/src/provider-bundle.ts +85 -79
- package/src/provider-response.ts +35 -0
- package/src/query-hooks.svelte.ts +241 -286
- package/src/query-invalidation.ts +172 -0
- package/src/query-keys.ts +221 -110
- package/src/query-session.svelte.ts +216 -0
- package/src/query-snapshot.ts +144 -0
- package/src/reactive-projection.ts +53 -0
- package/src/record-decoder.ts +63 -0
- package/src/resource-binding.svelte.ts +19 -0
- package/src/resource-contract.ts +595 -0
- package/src/resource-mutation-types.ts +30 -0
- package/src/resource-schemas.ts +371 -0
- package/src/resource-transport-config.ts +156 -0
- package/src/route-parsing.ts +11 -11
- package/src/router-provider.ts +64 -59
- package/src/router.ts +8 -3
- package/src/routing-hooks.svelte.ts +90 -62
- package/src/schema-validation.ts +92 -0
- package/src/schema.ts +13 -0
- package/src/select-options.ts +95 -0
- package/src/select-query.svelte.ts +191 -0
- package/src/session-query.svelte.ts +88 -0
- package/src/strict-command-hooks.svelte.ts +333 -0
- package/src/strict-hooks.svelte.ts +198 -0
- package/src/table-hooks.svelte.ts +180 -174
- package/src/table-state.svelte.ts +117 -117
- package/src/task-contract.ts +87 -0
- package/src/task-hooks.svelte.ts +193 -149
- package/src/task-provider.ts +115 -0
- package/src/task-subscription.ts +132 -0
- package/src/theme.svelte.ts +155 -154
- package/src/toast.svelte.ts +38 -37
- package/src/types.ts +55 -103
- package/src/unsafe.ts +7 -0
- package/src/update-contract.ts +65 -0
- package/src/update-hooks.svelte.ts +347 -0
- package/src/update-many-contract.ts +79 -0
- package/src/update-many-hooks.svelte.ts +373 -0
- package/src/url-sync.ts +11 -10
- package/src/useCan.ts +96 -31
- package/src/useParsed.svelte.ts +18 -14
- package/src/useStepsForm.svelte.ts +123 -63
- package/src/utility-hooks.svelte.ts +101 -158
- package/src/form-hooks.svelte.ts +0 -889
- package/src/mutation-hooks.svelte.ts +0 -561
|
@@ -1,402 +1,357 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { definedOptions } from './defined-options';
|
|
2
|
+
import { createSessionQuery } from './session-query.svelte';
|
|
2
3
|
import { getAdminOptions } from './options.svelte';
|
|
3
4
|
import { captureAdminContext } from './context.svelte';
|
|
5
|
+
import { captureContractContext,type ContractOptions } from './contract-context.svelte';
|
|
4
6
|
import { useParsed } from './useParsed.svelte';
|
|
5
7
|
import {
|
|
6
|
-
checkError,
|
|
7
8
|
createOvertimeTracker,
|
|
8
9
|
createLiveSubscription,
|
|
9
|
-
fireSuccessNotification,
|
|
10
|
-
fireErrorNotification,
|
|
11
10
|
} from './hook-utils.svelte';
|
|
12
|
-
import type { NotificationConfig,
|
|
11
|
+
import type { NotificationConfig,OvertimeOptions,LiveSubscriptionParams } from './hook-utils.svelte';
|
|
13
12
|
import type {
|
|
14
|
-
GetListResult,
|
|
15
|
-
GetOneResult,
|
|
13
|
+
GetListResult,Pagination,Sort,Filter,BaseRecord,
|
|
14
|
+
GetOneResult,GetManyResult,
|
|
16
15
|
KnownResources,
|
|
17
16
|
} from './types';
|
|
18
|
-
import type { LiveMode,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
*/
|
|
24
|
-
function extendQuery<Q extends object, E extends Record<string, unknown>>(
|
|
25
|
-
query: Q,
|
|
26
|
-
extensions: E,
|
|
27
|
-
): Q & E {
|
|
28
|
-
return new Proxy(query, {
|
|
29
|
-
get(target, prop) {
|
|
30
|
-
if (typeof prop === 'string' && prop in extensions) {
|
|
31
|
-
return extensions[prop as keyof E];
|
|
32
|
-
}
|
|
33
|
-
return target[prop as keyof Q];
|
|
34
|
-
},
|
|
35
|
-
has(target, prop) {
|
|
36
|
-
if (typeof prop === 'string' && prop in extensions) return true;
|
|
37
|
-
return prop in target;
|
|
38
|
-
},
|
|
39
|
-
}) as Q & E;
|
|
40
|
-
}
|
|
17
|
+
import type { LiveMode,LiveEvent } from './live.svelte';
|
|
18
|
+
import { decodeBaseRecord, decodeListResult, decodeOneResult, decodeManyResult, rejectProviderResponse, type RecordDecoder } from './record-decoder';
|
|
19
|
+
import { HttpError } from './types';
|
|
20
|
+
import { extendReactiveMembers } from './reactive-projection';
|
|
21
|
+
import { captureQueryProvider, snapshotListParams, snapshotOneParams, snapshotManyParams } from './query-snapshot';
|
|
41
22
|
|
|
42
23
|
// ─── useList ───────────────────────────────────────────────────
|
|
43
24
|
|
|
44
|
-
export type MaybeGetter<T
|
|
45
|
-
|
|
46
|
-
function cloneQueryKeyPart<T>(value: T): T {
|
|
47
|
-
if (Array.isArray(value)) {
|
|
48
|
-
return value.map(item => cloneQueryKeyPart(item)) as T;
|
|
49
|
-
}
|
|
50
|
-
if (value && typeof value === 'object') {
|
|
51
|
-
const proto = Object.getPrototypeOf(value);
|
|
52
|
-
if (proto !== Object.prototype && proto !== null) return value;
|
|
53
|
-
return Object.fromEntries(
|
|
54
|
-
Object.entries(value as Record<string, unknown>).map(([key, entry]) => [key, cloneQueryKeyPart(entry)])
|
|
55
|
-
) as T;
|
|
56
|
-
}
|
|
57
|
-
return value;
|
|
58
|
-
}
|
|
25
|
+
export type MaybeGetter<T>=T|(() => T);
|
|
59
26
|
|
|
60
|
-
export interface UseListOptions<_TData extends BaseRecord
|
|
27
|
+
export interface UseListOptions<_TData extends BaseRecord=BaseRecord,_TError=HttpError> extends ContractOptions {
|
|
61
28
|
resource?: KnownResources;
|
|
62
29
|
pagination?: Pagination;
|
|
63
30
|
sorters?: Sort[];
|
|
64
31
|
filters?: Filter[];
|
|
65
|
-
meta?: Record<string,
|
|
32
|
+
meta?: Record<string,unknown>;
|
|
66
33
|
dataProviderName?: string;
|
|
67
34
|
queryOptions?: { staleTime?: number; enabled?: boolean; gcTime?: number; refetchOnWindowFocus?: boolean };
|
|
68
35
|
successNotification?: NotificationConfig;
|
|
69
36
|
errorNotification?: NotificationConfig;
|
|
70
37
|
liveMode?: LiveMode;
|
|
71
38
|
onLiveEvent?: (event: LiveEvent) => void;
|
|
72
|
-
liveParams?: Record<string,
|
|
39
|
+
liveParams?: Record<string,unknown>;
|
|
73
40
|
overtimeOptions?: OvertimeOptions;
|
|
74
41
|
}
|
|
75
42
|
|
|
76
|
-
export function useList
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
43
|
+
export function useList(optionsOrGetter: MaybeGetter<UseListOptions>={}) {
|
|
44
|
+
return createListQuery(optionsOrGetter, () => decodeBaseRecord);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createListQuery<TData extends BaseRecord>(
|
|
48
|
+
optionsOrGetter: MaybeGetter<UseListOptions>, getDecoder: () => RecordDecoder<TData>,
|
|
49
|
+
) {
|
|
50
|
+
const adminContext=captureContractContext(optionsOrGetter);
|
|
51
|
+
const parsed=useParsed();
|
|
52
|
+
const adminOptions=getAdminOptions();
|
|
53
|
+
|
|
54
|
+
const getOptions=() => typeof optionsOrGetter==='function'? optionsOrGetter():optionsOrGetter;
|
|
55
|
+
const getResource=() => getOptions().resource??parsed.resource??'';
|
|
56
|
+
|
|
57
|
+
const query=createSessionQuery<GetListResult<TData>>(adminContext,() => {
|
|
58
|
+
const decode=getDecoder();
|
|
59
|
+
const opts=getOptions();
|
|
60
|
+
const checkId=opts.contract!==undefined;
|
|
61
|
+
const resource=opts.resource??parsed.resource??'';
|
|
62
|
+
const captured=captureQueryProvider(adminContext,{
|
|
63
|
+
resource,...definedOptions({ dataProviderName: opts.dataProviderName,contract: opts.contract,meta: opts.meta }),
|
|
64
|
+
});
|
|
65
|
+
const queryOptions=opts.queryOptions;
|
|
66
|
+
|
|
67
|
+
// Inject a parent filter for nested resources.
|
|
68
|
+
// Consume only parentParams parsed from the path, such as teamId in /teams/123/users,
|
|
69
|
+
// instead of query params such as ?tenantId=1 being silently converted into filters.
|
|
97
70
|
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
const parentFilters:
|
|
101
|
-
const injectParent
|
|
102
|
-
if
|
|
103
|
-
for
|
|
104
|
-
if
|
|
105
|
-
parentFilters.push({ field: paramKey,
|
|
71
|
+
// Security boundary: inject only when useList falls back to parsed.resource,
|
|
72
|
+
// or when the explicit resource matches the current route resource.
|
|
73
|
+
const parentFilters: Filter[]=[];
|
|
74
|
+
const injectParent=!opts.resource||opts.resource===parsed.resource;
|
|
75
|
+
if(injectParent&&parsed.parentParams) {
|
|
76
|
+
for(const [paramKey,paramVal] of Object.entries(parsed.parentParams)) {
|
|
77
|
+
if(paramKey.endsWith('Id')&¶mVal) {
|
|
78
|
+
parentFilters.push({ field: paramKey,operator: 'eq',value: paramVal });
|
|
106
79
|
}
|
|
107
80
|
}
|
|
108
81
|
}
|
|
109
|
-
const
|
|
82
|
+
const params=snapshotListParams({
|
|
83
|
+
resource,
|
|
84
|
+
...definedOptions({
|
|
85
|
+
pagination: opts.pagination,sorters: opts.sorters,
|
|
86
|
+
filters: [...parentFilters,...(opts.filters??[])],meta: captured.meta,
|
|
87
|
+
}),
|
|
88
|
+
});
|
|
89
|
+
const decodeResult=(value: unknown): GetListResult<TData> => {
|
|
90
|
+
const result=decodeListResult(value,decode);
|
|
91
|
+
if(checkId && new Set(result.data.map(row => row['id'])).size!==result.data.length) return rejectProviderResponse();
|
|
92
|
+
return result;
|
|
93
|
+
};
|
|
110
94
|
|
|
111
95
|
return {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
96
|
+
resource, successNotification: opts.successNotification, errorNotification: opts.errorNotification,
|
|
97
|
+
decode: decodeResult,
|
|
98
|
+
queryKey: adminContext.queryKeys(resource,opts.dataProviderName).data.list(resource,{
|
|
99
|
+
...definedOptions({
|
|
100
|
+
pagination: params.pagination,sorters: params.sorters,filters: params.filters,meta: params.meta,
|
|
101
|
+
}),
|
|
102
|
+
source: captured.source,
|
|
117
103
|
}),
|
|
118
|
-
queryFn: async () =>
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
104
|
+
queryFn: async (signal) => {
|
|
105
|
+
const current = captureQueryProvider(adminContext, {
|
|
106
|
+
resource, ...definedOptions({ dataProviderName: opts.dataProviderName, contract: opts.contract, meta: opts.meta, fresh: true }),
|
|
107
|
+
});
|
|
108
|
+
return decodeResult(await current.provider.getList(snapshotListParams({ ...params, ...definedOptions({ signal }) })));
|
|
109
|
+
},
|
|
110
|
+
enabled: queryOptions?.enabled??true,
|
|
111
|
+
...definedOptions({
|
|
112
|
+
staleTime: queryOptions?.staleTime??adminOptions.reactQuery?.staleTime,
|
|
113
|
+
gcTime: queryOptions?.gcTime??adminOptions.reactQuery?.gcTime,
|
|
114
|
+
refetchOnWindowFocus: queryOptions?.refetchOnWindowFocus??adminOptions.reactQuery?.refetchOnWindowFocus,
|
|
124
115
|
}),
|
|
125
|
-
enabled: queryOptions?.enabled ?? true,
|
|
126
|
-
staleTime: queryOptions?.staleTime ?? adminOptions.reactQuery?.staleTime,
|
|
127
|
-
gcTime: queryOptions?.gcTime ?? adminOptions.reactQuery?.gcTime,
|
|
128
|
-
refetchOnWindowFocus: queryOptions?.refetchOnWindowFocus ?? adminOptions.reactQuery?.refetchOnWindowFocus,
|
|
129
116
|
};
|
|
130
117
|
});
|
|
131
118
|
|
|
132
|
-
const overtime
|
|
119
|
+
const overtime=createOvertimeTracker(() => query.isLoading,typeof optionsOrGetter==='function'? optionsOrGetter().overtimeOptions:optionsOrGetter.overtimeOptions??adminOptions.overtime);
|
|
133
120
|
|
|
134
121
|
createLiveSubscription((): LiveSubscriptionParams => {
|
|
135
|
-
const opts
|
|
122
|
+
const opts=getOptions();
|
|
136
123
|
return {
|
|
137
124
|
resource: getResource(),
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
opts.
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
125
|
+
...definedOptions({
|
|
126
|
+
liveProvider: adminContext.liveProvider,
|
|
127
|
+
liveMode: opts.liveMode??adminOptions.liveMode,
|
|
128
|
+
contract: opts.contract,
|
|
129
|
+
onLiveEvent: opts.onLiveEvent,
|
|
130
|
+
onGlobalLiveEvent: adminOptions.onLiveEvent,
|
|
131
|
+
}),
|
|
132
|
+
...definedOptions({ liveParams: opts.liveParams }),
|
|
133
|
+
enabled: opts.queryOptions?.enabled??true,
|
|
134
|
+
...definedOptions({ dataProviderName: opts.dataProviderName }),
|
|
147
135
|
};
|
|
148
136
|
});
|
|
149
137
|
|
|
150
|
-
|
|
151
|
-
let lastSuccessAt = 0;
|
|
152
|
-
let lastErrorAt = 0;
|
|
153
|
-
$effect(() => {
|
|
154
|
-
const opts = getOptions();
|
|
155
|
-
if (query.isSuccess && query.dataUpdatedAt > lastSuccessAt) {
|
|
156
|
-
lastSuccessAt = query.dataUpdatedAt;
|
|
157
|
-
if (opts.successNotification) {
|
|
158
|
-
fireSuccessNotification({
|
|
159
|
-
config: opts.successNotification,
|
|
160
|
-
defaultMessage: '',
|
|
161
|
-
data: query.data,
|
|
162
|
-
resource: getResource(),
|
|
163
|
-
provider: adminContext.notificationProvider,
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
} else if (query.isError && query.errorUpdatedAt > lastErrorAt) {
|
|
167
|
-
lastErrorAt = query.errorUpdatedAt;
|
|
168
|
-
checkError(query.error, adminContext);
|
|
169
|
-
fireErrorNotification({
|
|
170
|
-
config: opts.errorNotification,
|
|
171
|
-
defaultMessage: 'Fetch failed',
|
|
172
|
-
error: query.error,
|
|
173
|
-
resource: getResource(),
|
|
174
|
-
provider: adminContext.notificationProvider,
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
return extendQuery(query, { overtime });
|
|
138
|
+
return extendReactiveMembers(query,{ overtime });
|
|
180
139
|
}
|
|
181
140
|
|
|
182
141
|
// ─── useOne ────────────────────────────────────────────────────
|
|
183
142
|
|
|
184
|
-
export interface UseOneOptions<_TData extends BaseRecord
|
|
143
|
+
export interface UseOneOptions<_TData extends BaseRecord=BaseRecord,_TError=HttpError> extends ContractOptions {
|
|
185
144
|
resource?: KnownResources;
|
|
186
|
-
id?: string
|
|
187
|
-
meta?: Record<string,
|
|
145
|
+
id?: string|number;
|
|
146
|
+
meta?: Record<string,unknown>;
|
|
188
147
|
dataProviderName?: string;
|
|
189
148
|
queryOptions?: { staleTime?: number; enabled?: boolean; gcTime?: number; refetchOnWindowFocus?: boolean };
|
|
190
149
|
successNotification?: NotificationConfig;
|
|
191
150
|
errorNotification?: NotificationConfig;
|
|
192
151
|
liveMode?: LiveMode;
|
|
193
152
|
onLiveEvent?: (event: LiveEvent) => void;
|
|
194
|
-
liveParams?: Record<string,
|
|
153
|
+
liveParams?: Record<string,unknown>;
|
|
195
154
|
overtimeOptions?: OvertimeOptions;
|
|
196
155
|
}
|
|
197
156
|
|
|
198
|
-
export function useOne
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
157
|
+
export function useOne(optionsOrGetter: MaybeGetter<UseOneOptions>={}) {
|
|
158
|
+
return createOneQuery(optionsOrGetter, () => decodeBaseRecord);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function createOneQuery<TData extends BaseRecord>(
|
|
162
|
+
optionsOrGetter: MaybeGetter<UseOneOptions>, getDecoder: () => RecordDecoder<TData>,
|
|
163
|
+
) {
|
|
164
|
+
const adminContext=captureContractContext(optionsOrGetter);
|
|
165
|
+
const parsed=useParsed();
|
|
166
|
+
const adminOptions=getAdminOptions();
|
|
167
|
+
|
|
168
|
+
const getOptions=() => typeof optionsOrGetter==='function'? optionsOrGetter():optionsOrGetter;
|
|
169
|
+
const getResource=() => getOptions().resource??parsed.resource??'';
|
|
170
|
+
const getId=() => getOptions().id??parsed.id;
|
|
171
|
+
|
|
172
|
+
const query=createSessionQuery<GetOneResult<TData>>(adminContext,() => {
|
|
173
|
+
const decode=getDecoder();
|
|
174
|
+
const opts=getOptions();
|
|
175
|
+
const resource=opts.resource??parsed.resource??'';
|
|
176
|
+
const id=opts.id??parsed.id;
|
|
177
|
+
const checkId=opts.contract!==undefined;
|
|
178
|
+
const captured=captureQueryProvider(adminContext,{
|
|
179
|
+
resource,...definedOptions({ dataProviderName: opts.dataProviderName,contract: opts.contract,meta: opts.meta }),
|
|
180
|
+
});
|
|
181
|
+
const params=id==null? undefined:snapshotOneParams({
|
|
182
|
+
resource,id,...definedOptions({ meta: captured.meta }),
|
|
183
|
+
});
|
|
184
|
+
const queryOptions=opts.queryOptions;
|
|
213
185
|
|
|
214
186
|
return {
|
|
215
|
-
|
|
216
|
-
|
|
187
|
+
resource, successNotification: opts.successNotification, errorNotification: opts.errorNotification,
|
|
188
|
+
decode: value => {
|
|
189
|
+
const decoded=decodeOneResult(value,decode);
|
|
190
|
+
if(checkId && params && decoded.data['id']!==params.id) return rejectProviderResponse();
|
|
191
|
+
return decoded;
|
|
192
|
+
},
|
|
193
|
+
queryKey: adminContext.queryKeys(resource,opts.dataProviderName).data.one(resource,id??'',{
|
|
194
|
+
...definedOptions({ meta: captured.meta }),
|
|
195
|
+
source: captured.source,
|
|
217
196
|
}),
|
|
218
|
-
queryFn: async () => {
|
|
219
|
-
if
|
|
220
|
-
const result
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
});
|
|
225
|
-
return result;
|
|
197
|
+
queryFn: async (signal) => {
|
|
198
|
+
if(!params) throw new HttpError('Reading a record requires an id',422,undefined,{ code: 'INVALID_RESOURCE_INPUT' });
|
|
199
|
+
const result=await captured.provider.getOne(snapshotOneParams({ ...params, ...definedOptions({ signal }) }));
|
|
200
|
+
const decoded=decodeOneResult(result, decode);
|
|
201
|
+
if(checkId && decoded.data['id']!==params.id) return rejectProviderResponse();
|
|
202
|
+
return decoded;
|
|
226
203
|
},
|
|
227
|
-
enabled: (queryOptions?.enabled
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
204
|
+
enabled: (queryOptions?.enabled??true)&&id!=null,
|
|
205
|
+
...definedOptions({
|
|
206
|
+
staleTime: queryOptions?.staleTime??adminOptions.reactQuery?.staleTime,
|
|
207
|
+
gcTime: queryOptions?.gcTime??adminOptions.reactQuery?.gcTime,
|
|
208
|
+
refetchOnWindowFocus: queryOptions?.refetchOnWindowFocus??adminOptions.reactQuery?.refetchOnWindowFocus,
|
|
209
|
+
}),
|
|
231
210
|
};
|
|
232
211
|
});
|
|
233
212
|
|
|
234
|
-
const overtime
|
|
213
|
+
const overtime=createOvertimeTracker(() => query.isLoading,typeof optionsOrGetter==='function'? optionsOrGetter().overtimeOptions:optionsOrGetter.overtimeOptions??adminOptions.overtime);
|
|
235
214
|
|
|
236
215
|
createLiveSubscription((): LiveSubscriptionParams => {
|
|
237
|
-
const opts
|
|
238
|
-
const resource
|
|
216
|
+
const opts=getOptions();
|
|
217
|
+
const resource=getResource();
|
|
239
218
|
return {
|
|
240
219
|
resource,
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
opts.
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
220
|
+
...definedOptions({
|
|
221
|
+
liveProvider: adminContext.liveProvider,
|
|
222
|
+
liveMode: opts.liveMode??adminOptions.liveMode,
|
|
223
|
+
contract: opts.contract,
|
|
224
|
+
onLiveEvent: opts.onLiveEvent,
|
|
225
|
+
onGlobalLiveEvent: adminOptions.onLiveEvent,
|
|
226
|
+
}),
|
|
227
|
+
...definedOptions({ liveParams: opts.liveParams }),
|
|
228
|
+
enabled: (opts.queryOptions?.enabled??true)&&getId()!=null,
|
|
229
|
+
...definedOptions({ dataProviderName: opts.dataProviderName }),
|
|
250
230
|
};
|
|
251
231
|
});
|
|
252
232
|
|
|
253
|
-
|
|
254
|
-
let lastErrorAt = 0;
|
|
255
|
-
$effect(() => {
|
|
256
|
-
const opts = getOptions();
|
|
257
|
-
if (query.isSuccess && query.dataUpdatedAt > lastSuccessAt) {
|
|
258
|
-
lastSuccessAt = query.dataUpdatedAt;
|
|
259
|
-
if (opts.successNotification) {
|
|
260
|
-
fireSuccessNotification({
|
|
261
|
-
config: opts.successNotification,
|
|
262
|
-
defaultMessage: '',
|
|
263
|
-
data: query.data,
|
|
264
|
-
resource: getResource(),
|
|
265
|
-
provider: adminContext.notificationProvider,
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
} else if (query.isError && query.errorUpdatedAt > lastErrorAt) {
|
|
269
|
-
lastErrorAt = query.errorUpdatedAt;
|
|
270
|
-
checkError(query.error, adminContext);
|
|
271
|
-
fireErrorNotification({
|
|
272
|
-
config: opts.errorNotification,
|
|
273
|
-
defaultMessage: 'Fetch failed',
|
|
274
|
-
error: query.error,
|
|
275
|
-
resource: getResource(),
|
|
276
|
-
provider: adminContext.notificationProvider,
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
return extendQuery(query, { overtime });
|
|
233
|
+
return extendReactiveMembers(query,{ overtime });
|
|
282
234
|
}
|
|
283
235
|
|
|
284
236
|
// ─── useShow ──────────────────────────────────────────────────
|
|
285
|
-
export function
|
|
286
|
-
optionsOrGetter: MaybeGetter<UseOneOptions
|
|
237
|
+
export function createShowQuery<TData extends BaseRecord>(
|
|
238
|
+
optionsOrGetter: MaybeGetter<UseOneOptions>, getDecoder: () => RecordDecoder<TData>,
|
|
287
239
|
) {
|
|
288
|
-
const parsed
|
|
289
|
-
const getOptions
|
|
290
|
-
|
|
291
|
-
let _showId
|
|
292
|
-
|
|
240
|
+
const parsed=useParsed();
|
|
241
|
+
const getOptions=() => typeof optionsOrGetter==='function'? optionsOrGetter():optionsOrGetter;
|
|
242
|
+
|
|
243
|
+
let _showId=$state<string|number|undefined>(undefined);
|
|
244
|
+
|
|
293
245
|
// showId prioritizes explicit setShowId, then options.id, then URL id.
|
|
294
|
-
const showId
|
|
246
|
+
const showId=$derived(_showId??getOptions().id??parsed.id);
|
|
295
247
|
|
|
296
|
-
function setShowId(id: string
|
|
248
|
+
function setShowId(id: string|number) { _showId=id; }
|
|
297
249
|
|
|
298
250
|
// We wrap it in a getter so useOne evaluates it dynamically.
|
|
299
|
-
const result
|
|
251
|
+
const result=createOneQuery(() => ({
|
|
300
252
|
...getOptions(),
|
|
301
|
-
id: showId,
|
|
302
|
-
}));
|
|
253
|
+
...definedOptions({ id: showId }),
|
|
254
|
+
}), getDecoder);
|
|
303
255
|
|
|
304
|
-
return
|
|
256
|
+
return extendReactiveMembers(result,{ get showId() { return showId; },setShowId });
|
|
305
257
|
}
|
|
306
258
|
|
|
307
259
|
// ─── useMany ───────────────────────────────────────────────────
|
|
308
260
|
|
|
309
|
-
export interface UseManyOptions<_TData extends BaseRecord
|
|
261
|
+
export interface UseManyOptions<_TData extends BaseRecord=BaseRecord,_TError=HttpError> extends ContractOptions {
|
|
310
262
|
resource: KnownResources;
|
|
311
|
-
ids: (string
|
|
312
|
-
meta?: Record<string,
|
|
263
|
+
ids: (string|number)[];
|
|
264
|
+
meta?: Record<string,unknown>;
|
|
313
265
|
dataProviderName?: string;
|
|
314
266
|
queryOptions?: { staleTime?: number; enabled?: boolean; gcTime?: number; refetchOnWindowFocus?: boolean };
|
|
315
267
|
successNotification?: NotificationConfig;
|
|
316
268
|
errorNotification?: NotificationConfig;
|
|
317
269
|
liveMode?: LiveMode;
|
|
318
270
|
onLiveEvent?: (event: LiveEvent) => void;
|
|
319
|
-
liveParams?: Record<string,
|
|
271
|
+
liveParams?: Record<string,unknown>;
|
|
320
272
|
overtimeOptions?: OvertimeOptions;
|
|
321
273
|
}
|
|
322
274
|
|
|
323
|
-
export function useMany
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const getOptions = () => typeof optionsOrGetter === 'function' ? optionsOrGetter() : optionsOrGetter;
|
|
275
|
+
export function useMany(optionsOrGetter: MaybeGetter<UseManyOptions>) {
|
|
276
|
+
return createManyQuery(optionsOrGetter, () => decodeBaseRecord);
|
|
277
|
+
}
|
|
327
278
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
279
|
+
export function createManyQuery<TData extends BaseRecord>(
|
|
280
|
+
optionsOrGetter: MaybeGetter<UseManyOptions>, getDecoder: () => RecordDecoder<TData>,
|
|
281
|
+
) {
|
|
282
|
+
const adminContext=captureContractContext(optionsOrGetter);
|
|
283
|
+
const adminOptions=getAdminOptions();
|
|
284
|
+
const getOptions=() => typeof optionsOrGetter==='function'? optionsOrGetter():optionsOrGetter;
|
|
285
|
+
|
|
286
|
+
const query=createSessionQuery<GetManyResult<TData>>(adminContext,() => {
|
|
287
|
+
const decode=getDecoder();
|
|
288
|
+
const opts=getOptions();
|
|
289
|
+
const checkId=opts.contract!==undefined;
|
|
290
|
+
const { resource,ids,meta,dataProviderName,queryOptions }=opts;
|
|
291
|
+
const captured=captureQueryProvider(adminContext,{
|
|
292
|
+
resource,...definedOptions({ dataProviderName,contract: opts.contract,meta }),
|
|
293
|
+
});
|
|
294
|
+
const params=snapshotManyParams({
|
|
295
|
+
resource,ids,...definedOptions({ meta: captured.meta }),
|
|
296
|
+
});
|
|
297
|
+
const expected=new Set<unknown>(params.ids);
|
|
298
|
+
const decodeResult=(value: unknown): GetManyResult<TData> => {
|
|
299
|
+
const result=decodeManyResult(value,decode);
|
|
300
|
+
if(checkId && (expected.size!==params.ids.length || result.data.length!==expected.size ||
|
|
301
|
+
new Set(result.data.map(row => row['id'])).size!==result.data.length ||
|
|
302
|
+
result.data.some(row => !expected.has(row['id'])))) return rejectProviderResponse();
|
|
303
|
+
return result;
|
|
304
|
+
};
|
|
332
305
|
|
|
333
306
|
return {
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
307
|
+
resource, successNotification: opts.successNotification, errorNotification: opts.errorNotification,
|
|
308
|
+
decode: decodeResult,
|
|
309
|
+
queryKey: adminContext.queryKeys(resource,dataProviderName).data.many(resource,{
|
|
310
|
+
ids: params.ids,
|
|
311
|
+
...definedOptions({ meta: params.meta }),
|
|
312
|
+
source: captured.source,
|
|
337
313
|
}),
|
|
338
|
-
queryFn: async () => {
|
|
339
|
-
if
|
|
340
|
-
if
|
|
341
|
-
return provider.getMany
|
|
314
|
+
queryFn: async (signal) => {
|
|
315
|
+
if(!params.ids.length) return { data: [] };
|
|
316
|
+
if(captured.provider.getMany) {
|
|
317
|
+
return decodeResult(await captured.provider.getMany(snapshotManyParams({ ...params, ...definedOptions({ signal }) })));
|
|
342
318
|
}
|
|
343
|
-
const results
|
|
344
|
-
|
|
319
|
+
const results=await Promise.all(params.ids.map(async id => decodeOneResult(await captured.provider.getOne(snapshotOneParams({
|
|
320
|
+
resource,...definedOptions({
|
|
321
|
+
id,meta: params.meta,signal
|
|
322
|
+
})
|
|
323
|
+
})),decode)));
|
|
324
|
+
return decodeResult({ data: results.map(r => r.data) });
|
|
345
325
|
},
|
|
346
|
-
enabled: (queryOptions?.enabled
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
326
|
+
enabled: (queryOptions?.enabled??true)&¶ms.ids.length>0,
|
|
327
|
+
...definedOptions({
|
|
328
|
+
staleTime: queryOptions?.staleTime??adminOptions.reactQuery?.staleTime,
|
|
329
|
+
gcTime: queryOptions?.gcTime??adminOptions.reactQuery?.gcTime,
|
|
330
|
+
refetchOnWindowFocus: queryOptions?.refetchOnWindowFocus??adminOptions.reactQuery?.refetchOnWindowFocus,
|
|
331
|
+
}),
|
|
350
332
|
};
|
|
351
333
|
});
|
|
352
334
|
|
|
353
|
-
const overtime
|
|
335
|
+
const overtime=createOvertimeTracker(() => query.isLoading,typeof optionsOrGetter==='function'? optionsOrGetter().overtimeOptions:optionsOrGetter.overtimeOptions??adminOptions.overtime);
|
|
354
336
|
|
|
355
337
|
createLiveSubscription((): LiveSubscriptionParams => {
|
|
356
|
-
const opts
|
|
338
|
+
const opts=getOptions();
|
|
357
339
|
return {
|
|
358
340
|
resource: opts.resource,
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
opts.
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
341
|
+
...definedOptions({
|
|
342
|
+
liveProvider: adminContext.liveProvider,
|
|
343
|
+
liveMode: opts.liveMode??adminOptions.liveMode,
|
|
344
|
+
contract: opts.contract,
|
|
345
|
+
onLiveEvent: opts.onLiveEvent,
|
|
346
|
+
onGlobalLiveEvent: adminOptions.onLiveEvent,
|
|
347
|
+
}),
|
|
348
|
+
...definedOptions({ liveParams: opts.liveParams }),
|
|
349
|
+
enabled: (opts.queryOptions?.enabled??true)&&opts.ids.length>0,
|
|
350
|
+
...definedOptions({ dataProviderName: opts.dataProviderName }),
|
|
368
351
|
};
|
|
369
352
|
});
|
|
370
353
|
|
|
371
|
-
|
|
372
|
-
let lastErrorAt = 0;
|
|
373
|
-
$effect(() => {
|
|
374
|
-
const opts = getOptions();
|
|
375
|
-
if (query.isSuccess && query.dataUpdatedAt > lastSuccessAt) {
|
|
376
|
-
lastSuccessAt = query.dataUpdatedAt;
|
|
377
|
-
if (opts.successNotification) {
|
|
378
|
-
fireSuccessNotification({
|
|
379
|
-
config: opts.successNotification,
|
|
380
|
-
defaultMessage: '',
|
|
381
|
-
data: query.data,
|
|
382
|
-
resource: opts.resource,
|
|
383
|
-
provider: adminContext.notificationProvider,
|
|
384
|
-
});
|
|
385
|
-
}
|
|
386
|
-
} else if (query.isError && query.errorUpdatedAt > lastErrorAt) {
|
|
387
|
-
lastErrorAt = query.errorUpdatedAt;
|
|
388
|
-
checkError(query.error, adminContext);
|
|
389
|
-
fireErrorNotification({
|
|
390
|
-
config: opts.errorNotification,
|
|
391
|
-
defaultMessage: 'Fetch failed',
|
|
392
|
-
error: query.error,
|
|
393
|
-
resource: opts.resource,
|
|
394
|
-
provider: adminContext.notificationProvider,
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
return extendQuery(query, { overtime });
|
|
354
|
+
return extendReactiveMembers(query,{ overtime });
|
|
400
355
|
}
|
|
401
356
|
|
|
402
357
|
export function useApiUrl(dataProviderName?: string): string {
|