@svadmin/core 0.50.0 → 0.53.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/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 -776
- package/src/http.ts +88 -87
- package/src/i18n.svelte.ts +64 -2
- package/src/import-contract.ts +106 -0
- package/src/import-hooks.svelte.ts +310 -0
- package/src/index.ts +77 -30
- package/src/inferencer-graphql.ts +7 -6
- package/src/inferencer.ts +9 -0
- 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 -288
- package/src/query-invalidation.ts +172 -0
- package/src/query-keys.ts +221 -110
- package/src/query-session.svelte.ts +220 -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 +103 -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 +190 -180
- 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 +68 -110
- 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
- package/src/schema-form.ts +0 -31
|
@@ -1,404 +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
|
-
|
|
124
|
-
|
|
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,
|
|
125
115
|
}),
|
|
126
|
-
enabled: queryOptions?.enabled ?? true,
|
|
127
|
-
staleTime: queryOptions?.staleTime ?? adminOptions.reactQuery?.staleTime,
|
|
128
|
-
gcTime: queryOptions?.gcTime ?? adminOptions.reactQuery?.gcTime,
|
|
129
|
-
refetchOnWindowFocus: queryOptions?.refetchOnWindowFocus ?? adminOptions.reactQuery?.refetchOnWindowFocus,
|
|
130
116
|
};
|
|
131
117
|
});
|
|
132
118
|
|
|
133
|
-
const overtime
|
|
119
|
+
const overtime=createOvertimeTracker(() => query.isLoading,typeof optionsOrGetter==='function'? optionsOrGetter().overtimeOptions:optionsOrGetter.overtimeOptions??adminOptions.overtime);
|
|
134
120
|
|
|
135
121
|
createLiveSubscription((): LiveSubscriptionParams => {
|
|
136
|
-
const opts
|
|
122
|
+
const opts=getOptions();
|
|
137
123
|
return {
|
|
138
124
|
resource: getResource(),
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
opts.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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 }),
|
|
148
135
|
};
|
|
149
136
|
});
|
|
150
137
|
|
|
151
|
-
|
|
152
|
-
let lastSuccessAt = 0;
|
|
153
|
-
let lastErrorAt = 0;
|
|
154
|
-
$effect(() => {
|
|
155
|
-
const opts = getOptions();
|
|
156
|
-
if (query.isSuccess && query.dataUpdatedAt > lastSuccessAt) {
|
|
157
|
-
lastSuccessAt = query.dataUpdatedAt;
|
|
158
|
-
if (opts.successNotification) {
|
|
159
|
-
fireSuccessNotification({
|
|
160
|
-
config: opts.successNotification,
|
|
161
|
-
defaultMessage: '',
|
|
162
|
-
data: query.data,
|
|
163
|
-
resource: getResource(),
|
|
164
|
-
provider: adminContext.notificationProvider,
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
} else if (query.isError && query.errorUpdatedAt > lastErrorAt) {
|
|
168
|
-
lastErrorAt = query.errorUpdatedAt;
|
|
169
|
-
checkError(query.error, adminContext);
|
|
170
|
-
fireErrorNotification({
|
|
171
|
-
config: opts.errorNotification,
|
|
172
|
-
defaultMessage: 'Fetch failed',
|
|
173
|
-
error: query.error,
|
|
174
|
-
resource: getResource(),
|
|
175
|
-
provider: adminContext.notificationProvider,
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
return extendQuery(query, { overtime });
|
|
138
|
+
return extendReactiveMembers(query,{ overtime });
|
|
181
139
|
}
|
|
182
140
|
|
|
183
141
|
// ─── useOne ────────────────────────────────────────────────────
|
|
184
142
|
|
|
185
|
-
export interface UseOneOptions<_TData extends BaseRecord
|
|
143
|
+
export interface UseOneOptions<_TData extends BaseRecord=BaseRecord,_TError=HttpError> extends ContractOptions {
|
|
186
144
|
resource?: KnownResources;
|
|
187
|
-
id?: string
|
|
188
|
-
meta?: Record<string,
|
|
145
|
+
id?: string|number;
|
|
146
|
+
meta?: Record<string,unknown>;
|
|
189
147
|
dataProviderName?: string;
|
|
190
148
|
queryOptions?: { staleTime?: number; enabled?: boolean; gcTime?: number; refetchOnWindowFocus?: boolean };
|
|
191
149
|
successNotification?: NotificationConfig;
|
|
192
150
|
errorNotification?: NotificationConfig;
|
|
193
151
|
liveMode?: LiveMode;
|
|
194
152
|
onLiveEvent?: (event: LiveEvent) => void;
|
|
195
|
-
liveParams?: Record<string,
|
|
153
|
+
liveParams?: Record<string,unknown>;
|
|
196
154
|
overtimeOptions?: OvertimeOptions;
|
|
197
155
|
}
|
|
198
156
|
|
|
199
|
-
export function useOne
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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;
|
|
214
185
|
|
|
215
186
|
return {
|
|
216
|
-
|
|
217
|
-
|
|
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,
|
|
218
196
|
}),
|
|
219
|
-
queryFn: async (
|
|
220
|
-
if
|
|
221
|
-
const result
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
meta: opts.meta,
|
|
226
|
-
});
|
|
227
|
-
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;
|
|
228
203
|
},
|
|
229
|
-
enabled: (queryOptions?.enabled
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
+
}),
|
|
233
210
|
};
|
|
234
211
|
});
|
|
235
212
|
|
|
236
|
-
const overtime
|
|
213
|
+
const overtime=createOvertimeTracker(() => query.isLoading,typeof optionsOrGetter==='function'? optionsOrGetter().overtimeOptions:optionsOrGetter.overtimeOptions??adminOptions.overtime);
|
|
237
214
|
|
|
238
215
|
createLiveSubscription((): LiveSubscriptionParams => {
|
|
239
|
-
const opts
|
|
240
|
-
const resource
|
|
216
|
+
const opts=getOptions();
|
|
217
|
+
const resource=getResource();
|
|
241
218
|
return {
|
|
242
219
|
resource,
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
opts.
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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 }),
|
|
252
230
|
};
|
|
253
231
|
});
|
|
254
232
|
|
|
255
|
-
|
|
256
|
-
let lastErrorAt = 0;
|
|
257
|
-
$effect(() => {
|
|
258
|
-
const opts = getOptions();
|
|
259
|
-
if (query.isSuccess && query.dataUpdatedAt > lastSuccessAt) {
|
|
260
|
-
lastSuccessAt = query.dataUpdatedAt;
|
|
261
|
-
if (opts.successNotification) {
|
|
262
|
-
fireSuccessNotification({
|
|
263
|
-
config: opts.successNotification,
|
|
264
|
-
defaultMessage: '',
|
|
265
|
-
data: query.data,
|
|
266
|
-
resource: getResource(),
|
|
267
|
-
provider: adminContext.notificationProvider,
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
} else if (query.isError && query.errorUpdatedAt > lastErrorAt) {
|
|
271
|
-
lastErrorAt = query.errorUpdatedAt;
|
|
272
|
-
checkError(query.error, adminContext);
|
|
273
|
-
fireErrorNotification({
|
|
274
|
-
config: opts.errorNotification,
|
|
275
|
-
defaultMessage: 'Fetch failed',
|
|
276
|
-
error: query.error,
|
|
277
|
-
resource: getResource(),
|
|
278
|
-
provider: adminContext.notificationProvider,
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
return extendQuery(query, { overtime });
|
|
233
|
+
return extendReactiveMembers(query,{ overtime });
|
|
284
234
|
}
|
|
285
235
|
|
|
286
236
|
// ─── useShow ──────────────────────────────────────────────────
|
|
287
|
-
export function
|
|
288
|
-
optionsOrGetter: MaybeGetter<UseOneOptions
|
|
237
|
+
export function createShowQuery<TData extends BaseRecord>(
|
|
238
|
+
optionsOrGetter: MaybeGetter<UseOneOptions>, getDecoder: () => RecordDecoder<TData>,
|
|
289
239
|
) {
|
|
290
|
-
const parsed
|
|
291
|
-
const getOptions
|
|
292
|
-
|
|
293
|
-
let _showId
|
|
294
|
-
|
|
240
|
+
const parsed=useParsed();
|
|
241
|
+
const getOptions=() => typeof optionsOrGetter==='function'? optionsOrGetter():optionsOrGetter;
|
|
242
|
+
|
|
243
|
+
let _showId=$state<string|number|undefined>(undefined);
|
|
244
|
+
|
|
295
245
|
// showId prioritizes explicit setShowId, then options.id, then URL id.
|
|
296
|
-
const showId
|
|
246
|
+
const showId=$derived(_showId??getOptions().id??parsed.id);
|
|
297
247
|
|
|
298
|
-
function setShowId(id: string
|
|
248
|
+
function setShowId(id: string|number) { _showId=id; }
|
|
299
249
|
|
|
300
250
|
// We wrap it in a getter so useOne evaluates it dynamically.
|
|
301
|
-
const result
|
|
251
|
+
const result=createOneQuery(() => ({
|
|
302
252
|
...getOptions(),
|
|
303
|
-
id: showId,
|
|
304
|
-
}));
|
|
253
|
+
...definedOptions({ id: showId }),
|
|
254
|
+
}), getDecoder);
|
|
305
255
|
|
|
306
|
-
return
|
|
256
|
+
return extendReactiveMembers(result,{ get showId() { return showId; },setShowId });
|
|
307
257
|
}
|
|
308
258
|
|
|
309
259
|
// ─── useMany ───────────────────────────────────────────────────
|
|
310
260
|
|
|
311
|
-
export interface UseManyOptions<_TData extends BaseRecord
|
|
261
|
+
export interface UseManyOptions<_TData extends BaseRecord=BaseRecord,_TError=HttpError> extends ContractOptions {
|
|
312
262
|
resource: KnownResources;
|
|
313
|
-
ids: (string
|
|
314
|
-
meta?: Record<string,
|
|
263
|
+
ids: (string|number)[];
|
|
264
|
+
meta?: Record<string,unknown>;
|
|
315
265
|
dataProviderName?: string;
|
|
316
266
|
queryOptions?: { staleTime?: number; enabled?: boolean; gcTime?: number; refetchOnWindowFocus?: boolean };
|
|
317
267
|
successNotification?: NotificationConfig;
|
|
318
268
|
errorNotification?: NotificationConfig;
|
|
319
269
|
liveMode?: LiveMode;
|
|
320
270
|
onLiveEvent?: (event: LiveEvent) => void;
|
|
321
|
-
liveParams?: Record<string,
|
|
271
|
+
liveParams?: Record<string,unknown>;
|
|
322
272
|
overtimeOptions?: OvertimeOptions;
|
|
323
273
|
}
|
|
324
274
|
|
|
325
|
-
export function useMany
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const getOptions = () => typeof optionsOrGetter === 'function' ? optionsOrGetter() : optionsOrGetter;
|
|
275
|
+
export function useMany(optionsOrGetter: MaybeGetter<UseManyOptions>) {
|
|
276
|
+
return createManyQuery(optionsOrGetter, () => decodeBaseRecord);
|
|
277
|
+
}
|
|
329
278
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
+
};
|
|
334
305
|
|
|
335
306
|
return {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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,
|
|
339
313
|
}),
|
|
340
|
-
queryFn: async (
|
|
341
|
-
if
|
|
342
|
-
if
|
|
343
|
-
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 }) })));
|
|
344
318
|
}
|
|
345
|
-
const results
|
|
346
|
-
|
|
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) });
|
|
347
325
|
},
|
|
348
|
-
enabled: (queryOptions?.enabled
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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
|
+
}),
|
|
352
332
|
};
|
|
353
333
|
});
|
|
354
334
|
|
|
355
|
-
const overtime
|
|
335
|
+
const overtime=createOvertimeTracker(() => query.isLoading,typeof optionsOrGetter==='function'? optionsOrGetter().overtimeOptions:optionsOrGetter.overtimeOptions??adminOptions.overtime);
|
|
356
336
|
|
|
357
337
|
createLiveSubscription((): LiveSubscriptionParams => {
|
|
358
|
-
const opts
|
|
338
|
+
const opts=getOptions();
|
|
359
339
|
return {
|
|
360
340
|
resource: opts.resource,
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
opts.
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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 }),
|
|
370
351
|
};
|
|
371
352
|
});
|
|
372
353
|
|
|
373
|
-
|
|
374
|
-
let lastErrorAt = 0;
|
|
375
|
-
$effect(() => {
|
|
376
|
-
const opts = getOptions();
|
|
377
|
-
if (query.isSuccess && query.dataUpdatedAt > lastSuccessAt) {
|
|
378
|
-
lastSuccessAt = query.dataUpdatedAt;
|
|
379
|
-
if (opts.successNotification) {
|
|
380
|
-
fireSuccessNotification({
|
|
381
|
-
config: opts.successNotification,
|
|
382
|
-
defaultMessage: '',
|
|
383
|
-
data: query.data,
|
|
384
|
-
resource: opts.resource,
|
|
385
|
-
provider: adminContext.notificationProvider,
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
} else if (query.isError && query.errorUpdatedAt > lastErrorAt) {
|
|
389
|
-
lastErrorAt = query.errorUpdatedAt;
|
|
390
|
-
checkError(query.error, adminContext);
|
|
391
|
-
fireErrorNotification({
|
|
392
|
-
config: opts.errorNotification,
|
|
393
|
-
defaultMessage: 'Fetch failed',
|
|
394
|
-
error: query.error,
|
|
395
|
-
resource: opts.resource,
|
|
396
|
-
provider: adminContext.notificationProvider,
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
return extendQuery(query, { overtime });
|
|
354
|
+
return extendReactiveMembers(query,{ overtime });
|
|
402
355
|
}
|
|
403
356
|
|
|
404
357
|
export function useApiUrl(dataProviderName?: string): string {
|