@svadmin/core 0.50.0 → 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.
Files changed (97) hide show
  1. package/package.json +21 -4
  2. package/src/access-control-contract.ts +250 -0
  3. package/src/adapters/casbin.ts +20 -14
  4. package/src/adapters/casl.ts +22 -19
  5. package/src/admin-tool.ts +7 -7
  6. package/src/audit-contract.ts +105 -0
  7. package/src/audit.ts +88 -128
  8. package/src/auth-hooks.svelte.ts +593 -261
  9. package/src/auth-mutation-contract.ts +143 -0
  10. package/src/auth-query-contract.ts +97 -0
  11. package/src/captured-mutation.svelte.ts +45 -0
  12. package/src/chatProvider.svelte.ts +1 -1
  13. package/src/command-contract.ts +195 -0
  14. package/src/context.svelte.ts +217 -205
  15. package/src/contract-context.svelte.ts +37 -0
  16. package/src/contract-form.svelte.ts +566 -0
  17. package/src/create-contract.ts +56 -0
  18. package/src/create-hooks.svelte.ts +347 -0
  19. package/src/create-many-contract.ts +90 -0
  20. package/src/create-many-hooks.svelte.ts +396 -0
  21. package/src/data-transfer.svelte.ts +1 -215
  22. package/src/defined-options.ts +50 -0
  23. package/src/delete-contract.ts +50 -0
  24. package/src/delete-hooks.svelte.ts +403 -0
  25. package/src/delete-many-contract.ts +60 -0
  26. package/src/delete-many-hooks.svelte.ts +402 -0
  27. package/src/enterprise.ts +4 -4
  28. package/src/export-format.ts +16 -14
  29. package/src/export-hooks.svelte.ts +191 -0
  30. package/src/feature-gate-contract.ts +53 -0
  31. package/src/hook-utils.svelte.ts +53 -105
  32. package/src/hooks.svelte.ts +19 -776
  33. package/src/http.ts +88 -87
  34. package/src/i18n.svelte.ts +56 -2
  35. package/src/import-contract.ts +106 -0
  36. package/src/import-hooks.svelte.ts +310 -0
  37. package/src/index.ts +77 -30
  38. package/src/inferencer-graphql.ts +7 -6
  39. package/src/infinite-query.svelte.ts +228 -0
  40. package/src/invalidation-contract.ts +26 -0
  41. package/src/live-sse.ts +91 -127
  42. package/src/live-subscription.svelte.ts +130 -0
  43. package/src/live-transport.ts +127 -0
  44. package/src/live-websocket.ts +85 -118
  45. package/src/live.svelte.ts +66 -68
  46. package/src/notification.svelte.ts +17 -15
  47. package/src/permission-catalog.ts +121 -0
  48. package/src/permission-hints-contract.ts +48 -0
  49. package/src/permissions.svelte.ts +112 -59
  50. package/src/plain-data.ts +61 -0
  51. package/src/provider-bundle.ts +85 -79
  52. package/src/provider-response.ts +35 -0
  53. package/src/query-hooks.svelte.ts +241 -288
  54. package/src/query-invalidation.ts +172 -0
  55. package/src/query-keys.ts +221 -110
  56. package/src/query-session.svelte.ts +216 -0
  57. package/src/query-snapshot.ts +144 -0
  58. package/src/reactive-projection.ts +53 -0
  59. package/src/record-decoder.ts +63 -0
  60. package/src/resource-binding.svelte.ts +19 -0
  61. package/src/resource-contract.ts +595 -0
  62. package/src/resource-mutation-types.ts +30 -0
  63. package/src/resource-schemas.ts +371 -0
  64. package/src/resource-transport-config.ts +156 -0
  65. package/src/route-parsing.ts +11 -11
  66. package/src/router-provider.ts +64 -59
  67. package/src/router.ts +8 -3
  68. package/src/routing-hooks.svelte.ts +90 -62
  69. package/src/schema-validation.ts +92 -0
  70. package/src/schema.ts +13 -0
  71. package/src/select-options.ts +95 -0
  72. package/src/select-query.svelte.ts +191 -0
  73. package/src/session-query.svelte.ts +88 -0
  74. package/src/strict-command-hooks.svelte.ts +333 -0
  75. package/src/strict-hooks.svelte.ts +198 -0
  76. package/src/table-hooks.svelte.ts +180 -174
  77. package/src/table-state.svelte.ts +117 -117
  78. package/src/task-contract.ts +87 -0
  79. package/src/task-hooks.svelte.ts +193 -149
  80. package/src/task-provider.ts +115 -0
  81. package/src/task-subscription.ts +132 -0
  82. package/src/theme.svelte.ts +155 -154
  83. package/src/toast.svelte.ts +38 -37
  84. package/src/types.ts +55 -108
  85. package/src/unsafe.ts +7 -0
  86. package/src/update-contract.ts +65 -0
  87. package/src/update-hooks.svelte.ts +347 -0
  88. package/src/update-many-contract.ts +79 -0
  89. package/src/update-many-hooks.svelte.ts +373 -0
  90. package/src/url-sync.ts +11 -10
  91. package/src/useCan.ts +96 -31
  92. package/src/useParsed.svelte.ts +18 -14
  93. package/src/useStepsForm.svelte.ts +123 -63
  94. package/src/utility-hooks.svelte.ts +101 -158
  95. package/src/form-hooks.svelte.ts +0 -889
  96. package/src/mutation-hooks.svelte.ts +0 -561
  97. package/src/schema-form.ts +0 -31
@@ -0,0 +1,310 @@
1
+ import { useQueryClient } from '@tanstack/svelte-query';
2
+ import { captureAdminContext } from './context.svelte';
3
+ import { captureQueryProvider } from './query-snapshot';
4
+ import { contractKey, requireContractCreateSchema, parseContractCreateInput, parseContractRecord, parseContractId,
5
+ type ResourceContract, type ContractSchemas, type ContractCreateInput, type ContractRecord } from './resource-contract';
6
+ import { decodeBaseRecord } from './record-decoder';
7
+ import { snapshotPlainData } from './plain-data';
8
+ import { createInputId } from './create-contract';
9
+ import { definedOptions } from './defined-options';
10
+ import { invalidateOwnedQueries } from './query-invalidation';
11
+ import { captureAuthLiveScope, clearAuthQueries, handleAuthError } from './auth-hooks.svelte';
12
+ import { HttpError } from './types';
13
+ import { decodeImportReceipt, invalidImportInput, parseImportRows, snapshotImportFile,
14
+ newImportProgress, snapshotImportProgress, type ImportOperationProgress, type ImportFormat } from './import-contract';
15
+
16
+ export type ImportProgress = Pick<ImportOperationProgress, 'totalAmount' | 'processedAmount'>;
17
+ export interface ImportResult<S extends ContractSchemas> {
18
+ succeeded: ContractRecord<S>[];
19
+ errored: { row: number; request: ContractCreateInput<S>; error: HttpError }[];
20
+ }
21
+ export interface UseImportOptions<S extends ContractSchemas> {
22
+ resource: ResourceContract<S> & ([ContractCreateInput<NoInfer<S>>] extends [never] ? never : unknown);
23
+ format?: ImportFormat;
24
+ mapData?: (item: Record<string, unknown>) => NoInfer<ContractCreateInput<S>>;
25
+ batchSize?: number;
26
+ enabled?: boolean;
27
+ meta?: Record<string, unknown>;
28
+ dataProviderName?: string;
29
+ onFinish?: (result: ImportResult<NoInfer<S>>) => void | Promise<void>;
30
+ onProgress?: (info: ImportProgress) => void | Promise<void>;
31
+ }
32
+
33
+ function cancelled(progress: ImportOperationProgress = newImportProgress()): HttpError {
34
+ const checked = snapshotImportProgress(progress);
35
+ return new HttpError('Import scope changed', 409, undefined, {
36
+ code: 'IMPORT_CANCELLED', details: { writeMayHaveSucceeded: checked.attemptedAmount > 0, ...checked },
37
+ });
38
+ }
39
+ function importFailure(cause: unknown, dispatched = false): HttpError {
40
+ let code: unknown;
41
+ let status: unknown;
42
+ try {
43
+ if (typeof cause === 'object' && cause !== null) {
44
+ code = Object.getOwnPropertyDescriptor(cause, 'code')?.value;
45
+ status = Object.getOwnPropertyDescriptor(cause, 'statusCode')?.value;
46
+ }
47
+ } catch {
48
+ // Error objects are untrusted diagnostic sources.
49
+ }
50
+ const input = code === 'INVALID_RESOURCE_INPUT' && !dispatched;
51
+ const contract = code === 'INVALID_RESOURCE_CONTRACT' && !dispatched;
52
+ const response = code === 'INVALID_PROVIDER_RESPONSE';
53
+ return new HttpError(input ? 'Invalid import input' : contract ? 'A create schema is required'
54
+ : response ? 'Invalid provider response' : 'Import failed', input ? 422 : contract ? 400 : response ? 502
55
+ : status === 401 || status === 403 ? status : 500, undefined, {
56
+ code: input ? 'INVALID_RESOURCE_INPUT' : contract ? 'INVALID_RESOURCE_CONTRACT'
57
+ : response ? 'INVALID_PROVIDER_RESPONSE' : 'IMPORT_FAILED',
58
+ details: { phase: dispatched ? response ? 'response' : 'request' : 'input', writeMayHaveSucceeded: dispatched },
59
+ });
60
+ }
61
+ function copyFailure(error: HttpError): HttpError {
62
+ return new HttpError(error.message, error.statusCode, undefined, {
63
+ ...definedOptions({ code: error.code }), details: snapshotPlainData(error.details ?? {}),
64
+ });
65
+ }
66
+ function notify<T>(callback: ((value: T) => void | Promise<void>) | undefined, value: T): void {
67
+ try { void Promise.resolve(callback?.(value)).catch(() => {}); }
68
+ catch { /* Observers cannot turn completed writes into a second import attempt. */ }
69
+ }
70
+
71
+ /** A whole-file preflight precedes writes; each provider receipt remains untrusted until checked. */
72
+ export function useImport<S extends ContractSchemas>(options: UseImportOptions<S>) {
73
+ const context = captureAdminContext();
74
+ const client = useQueryClient();
75
+ contractKey(options.resource);
76
+ let isLoading = $state(false);
77
+ let mutationResult = $state.raw<ImportResult<S> | null>(null);
78
+ let progress = $state.raw<ImportProgress>({ totalAmount: 0, processedAmount: 0 });
79
+ let error = $state.raw<HttpError | null>(null);
80
+ let mounted = true;
81
+
82
+ function captureOrigin() {
83
+ const authProvider = context.authProvider;
84
+ return { contract: options.resource, authProvider, authScope: captureAuthLiveScope(authProvider),
85
+ router: context.routerProvider, tenant: context.tenantCacheKey?.__svadminTenant };
86
+ }
87
+ type Origin = ReturnType<typeof captureOrigin>;
88
+ function targetOriginCurrent(origin: Origin): boolean {
89
+ return mounted && options.enabled !== false && origin.contract === options.resource &&
90
+ origin.authProvider === context.authProvider && origin.router === context.routerProvider &&
91
+ origin.tenant === context.tenantCacheKey?.__svadminTenant;
92
+ }
93
+ function originCurrent(origin: Origin): boolean {
94
+ return targetOriginCurrent(origin) && origin.authScope.isCurrent();
95
+ }
96
+ function captureScope() {
97
+ const origin = captureOrigin();
98
+ const contract = origin.contract;
99
+ const captured = captureQueryProvider(context, {
100
+ resource: contract.name, ...definedOptions({ dataProviderName: options.dataProviderName, meta: options.meta }),
101
+ });
102
+ const name = context.resolveDataProviderName(contract.name, options.dataProviderName);
103
+ const raw = context.providers?.[name];
104
+ if (!raw) throw new HttpError('Data provider is unavailable', 400, undefined, { code: 'DATA_PROVIDER_REQUIRED' });
105
+ return {
106
+ ...origin, source: captured.source, meta: captured.meta,
107
+ create: raw.create.bind(raw), createMany: raw.createMany?.bind(raw),
108
+ matcher: { ...context.queryKeyMatcher(contract.name, options.dataProviderName), contract: contractKey(contract) },
109
+ key: JSON.stringify([contractKey(contract), name, captured.source, context.tenantCacheKey?.__svadminTenant, captured.meta]),
110
+ };
111
+ }
112
+ type Scope = ReturnType<typeof captureScope>;
113
+ let current: { token: object; scope: Scope; file: File; promise: Promise<ImportResult<S>>; progress: ImportOperationProgress } | undefined;
114
+ let latest = $state.raw<{ token: object; origin: Origin; scope: Scope | undefined }>();
115
+ let latestToken: object | undefined;
116
+ function targetCurrent(scope: Scope): boolean {
117
+ if (!targetOriginCurrent(scope)) return false;
118
+ try { return captureScope().key === scope.key; }
119
+ catch { return false; }
120
+ }
121
+ function sessionCurrent(scope: Scope): boolean {
122
+ return context.authProvider === scope.authProvider && scope.authScope.isCurrent();
123
+ }
124
+ function isCurrent(token: object, scope: Scope): boolean {
125
+ return latestToken === token && targetCurrent(scope) && sessionCurrent(scope);
126
+ }
127
+ function viewCurrent(): boolean {
128
+ return latest !== undefined && latestToken === latest.token && originCurrent(latest.origin) &&
129
+ (latest.scope === undefined || targetCurrent(latest.scope));
130
+ }
131
+ function clearState() {
132
+ current = undefined;
133
+ latest = undefined;
134
+ isLoading = false;
135
+ mutationResult = null;
136
+ error = null;
137
+ progress = { totalAmount: 0, processedAmount: 0 };
138
+ }
139
+ function reset() {
140
+ latestToken = undefined;
141
+ clearState();
142
+ }
143
+ $effect.pre(() => {
144
+ if (latest && !viewCurrent()) {
145
+ // Auth retirement alone must not cancel the delegate completing its own logout.
146
+ if (!targetOriginCurrent(latest.origin) || (latest.scope && !targetCurrent(latest.scope))) latestToken = undefined;
147
+ clearState();
148
+ }
149
+ });
150
+ $effect(() => () => { mounted = false; reset(); });
151
+
152
+ async function refresh(scope: Scope) {
153
+ try {
154
+ await invalidateOwnedQueries({
155
+ client, matcher: { ...scope.matcher, resource: scope.contract.name },
156
+ owner: { source: scope.source, authSession: scope.authScope.cacheKey },
157
+ scopes: ['list', 'many'], current: () => sessionCurrent(scope),
158
+ });
159
+ } catch { /* Cache failures cannot change completed write receipts into failures. */ }
160
+ }
161
+ function copyResult(scope: Scope, result: ImportResult<S>): ImportResult<S> {
162
+ return {
163
+ succeeded: result.succeeded.map(row => parseContractRecord(scope.contract, row)),
164
+ errored: result.errored.map(item => ({
165
+ row: item.row, request: parseContractCreateInput(scope.contract, item.request),
166
+ error: copyFailure(item.error),
167
+ })),
168
+ };
169
+ }
170
+ function detached(operation: NonNullable<typeof current>): Promise<ImportResult<S>> {
171
+ return operation.promise.then(result => {
172
+ if (!isCurrent(operation.token, operation.scope)) throw cancelled(operation.progress);
173
+ return copyResult(operation.scope, result);
174
+ }, (cause: unknown) => {
175
+ if (!isCurrent(operation.token, operation.scope)) throw cancelled(operation.progress);
176
+ throw cause instanceof HttpError ? copyFailure(cause) : importFailure(cause, operation.progress.attemptedAmount > 0);
177
+ });
178
+ }
179
+
180
+ function handleChange(info: { file: File }): Promise<ImportResult<S>> {
181
+ if (!mounted) return Promise.reject(cancelled());
182
+ const origin = captureOrigin();
183
+ const token = {};
184
+ if (!originCurrent(origin)) {
185
+ latestToken = token;
186
+ return Promise.reject(cancelled());
187
+ }
188
+ let captured: Scope | undefined;
189
+ try {
190
+ const file = snapshotImportFile(info);
191
+ const scope = captureScope();
192
+ captured = scope;
193
+ if (!originCurrent(origin)) return Promise.reject(cancelled());
194
+ if (current && isCurrent(current.token, current.scope)) {
195
+ if (current.file === file.file) return detached(current);
196
+ return Promise.reject(new HttpError('Import is already running', 409, undefined, { code: 'IMPORT_BUSY' }));
197
+ }
198
+ requireContractCreateSchema(scope.contract);
199
+ const batchSize = options.batchSize ?? 1;
200
+ const format = options.format ?? 'auto';
201
+ const mapData = options.mapData;
202
+ const onFinish = options.onFinish;
203
+ const onProgress = options.onProgress;
204
+ if (!Number.isSafeInteger(batchSize) || batchSize < 1 || !['auto', 'csv', 'json'].includes(format) ||
205
+ (options.enabled !== undefined && typeof options.enabled !== 'boolean') ||
206
+ [mapData, onFinish, onProgress].some(callback => callback !== undefined && typeof callback !== 'function')) invalidImportInput();
207
+ if (!originCurrent(origin)) return Promise.reject(cancelled());
208
+ latestToken = token;
209
+ latest = Object.freeze({ token, origin, scope });
210
+ const tracked = newImportProgress();
211
+ const ensureActive = () => { if (!isCurrent(token, scope) || current?.token !== token) throw cancelled(tracked); };
212
+ isLoading = true;
213
+ error = null;
214
+ mutationResult = null;
215
+ progress = { totalAmount: 0, processedAmount: 0 };
216
+ const promise = Promise.resolve().then(async () => {
217
+ const result: ImportResult<S> = { succeeded: [], errored: [] };
218
+ try {
219
+ ensureActive();
220
+ const text = await file.read();
221
+ ensureActive();
222
+ const records = parseImportRows(text, file.name, format).map((row, index) => {
223
+ ensureActive();
224
+ try {
225
+ const input = mapData ? mapData(decodeBaseRecord(snapshotPlainData(row))) : row;
226
+ ensureActive();
227
+ const checked = parseContractCreateInput(scope.contract, input);
228
+ createInputId(scope.contract, checked);
229
+ return checked;
230
+ } catch { return invalidImportInput(index + 1); }
231
+ });
232
+ ensureActive();
233
+ const seen = new Set<string | number>();
234
+ tracked.totalAmount = records.length;
235
+ progress = { totalAmount: records.length, processedAmount: 0 };
236
+ for (let start = 0; start < records.length; start += scope.createMany ? batchSize : 1) {
237
+ ensureActive();
238
+ const batch = records.slice(start, start + (scope.createMany ? batchSize : 1));
239
+ try {
240
+ const request = { resource: scope.contract.name, ...definedOptions({
241
+ meta: scope.meta === undefined ? undefined : decodeBaseRecord(snapshotPlainData(scope.meta)),
242
+ }) };
243
+ tracked.attemptedAmount += batch.length;
244
+ const response = batchSize > 1 && scope.createMany
245
+ ? await scope.createMany({ ...request, variables: batch.map(row => snapshotPlainData(row)) })
246
+ : await scope.create({ ...request, variables: snapshotPlainData(batch[0]) });
247
+ if (!sessionCurrent(scope)) throw cancelled(tracked);
248
+ const rows = decodeImportReceipt(scope.contract, response, batch, batchSize > 1 && !!scope.createMany, seen);
249
+ for (const row of rows) seen.add(parseContractId(scope.contract, row.id));
250
+ result.succeeded.push(...rows);
251
+ } catch (cause) {
252
+ if (!sessionCurrent(scope)) throw cancelled(tracked);
253
+ const failure = importFailure(cause, true);
254
+ for (const [index, request] of batch.entries()) result.errored.push({ row: start + index + 1, request, error: failure });
255
+ }
256
+ tracked.processedAmount += batch.length;
257
+ ensureActive();
258
+ progress = { totalAmount: tracked.totalAmount, processedAmount: tracked.processedAmount };
259
+ notify(onProgress, { ...progress });
260
+ ensureActive();
261
+ }
262
+ } catch (cause) {
263
+ if (!isCurrent(token, scope) || current?.token !== token) throw cancelled(tracked);
264
+ const failure = importFailure(cause, tracked.attemptedAmount > 0);
265
+ error = failure;
266
+ throw failure;
267
+ } finally {
268
+ if (tracked.attemptedAmount > 0 && sessionCurrent(scope)) await refresh(scope);
269
+ }
270
+ ensureActive();
271
+ mutationResult = copyResult(scope, result);
272
+ error = result.errored[0] ? copyFailure(result.errored[0].error) : null;
273
+ const authError = result.errored.find(item => item.error.statusCode === 401 || item.error.statusCode === 403)?.error ?? error;
274
+ if (authError) notify(async () => {
275
+ await handleAuthError(copyFailure(authError), context, () => targetCurrent(scope) && latestToken === token,
276
+ () => clearAuthQueries(client, scope.authProvider), scope.authScope,
277
+ () => mounted && latestToken !== token);
278
+ }, undefined);
279
+ if (isCurrent(token, scope)) notify(onFinish, copyResult(scope, result));
280
+ ensureActive();
281
+ return copyResult(scope, result);
282
+ }).finally(() => {
283
+ if (current?.token === token) { current = undefined; isLoading = false; }
284
+ });
285
+ current = { token, scope, file: file.file, promise, progress: tracked };
286
+ return detached(current);
287
+ } catch (cause) {
288
+ const failure = importFailure(cause);
289
+ if (current && isCurrent(current.token, current.scope)) return Promise.reject(copyFailure(failure));
290
+ latestToken = token;
291
+ latest = Object.freeze({ token, origin, scope: captured });
292
+ error = failure;
293
+ mutationResult = null;
294
+ isLoading = false;
295
+ progress = { totalAmount: 0, processedAmount: 0 };
296
+ return Promise.resolve().then(() => {
297
+ if (!originCurrent(origin) || latestToken !== token) throw cancelled();
298
+ throw copyFailure(failure);
299
+ });
300
+ }
301
+ }
302
+ return {
303
+ inputProps: { type: 'file' as const, accept: '.csv,.json' },
304
+ handleChange, reset,
305
+ get isLoading() { return viewCurrent() && isLoading; },
306
+ get mutationResult() { return viewCurrent() && latest?.scope && mutationResult ? copyResult(latest.scope, mutationResult) : null; },
307
+ get progress(): Readonly<ImportProgress> { return viewCurrent() ? { ...progress } : { totalAmount: 0, processedAmount: 0 }; },
308
+ get error() { return viewCurrent() && error ? copyFailure(error) : null; },
309
+ };
310
+ }
package/src/index.ts CHANGED
@@ -11,7 +11,7 @@ export {
11
11
  provideTenantContext, getTenantContext, getProviderBundle,
12
12
  resetContext,
13
13
  } from './context.svelte';
14
- export type { DataProviderInput, AdminContextSource, AdminContextValue, AdminContextAccessor } from './context.svelte';
14
+ export type { DataProviderInput, AdminContextSource, AdminContextValue, AdminContextAccessor, ResolvedProviderBundle } from './context.svelte';
15
15
  export {
16
16
  createProviderBundle,
17
17
  createTenantCacheKey,
@@ -50,24 +50,36 @@ export type {
50
50
  TenantId,
51
51
  } from './provider-bundle';
52
52
  export {
53
- useList, useInfiniteList,
54
- useOne, useShow,
55
- useSelect, useMany,
56
- useCustom, useApiUrl,
57
- useCustomMutation, useInvalidate,
58
- useCreate, useCreateMany,
59
- useUpdate, useUpdateMany,
60
- useDelete, useDeleteMany,
61
- useForm, useTable,
53
+ useApiUrl,
62
54
  useNavigation, useGo, useBack,
63
55
  useGetToPath, useLink,
64
56
  useResource,
65
- useModalForm, useDrawerForm, useModal, useCheckboxGroup, useRadioGroup, useAutocomplete,
66
- useOvertime, useRelation,
67
- useNotification, useDataProvider,
57
+ useModal,
58
+ useOvertime,
59
+ useNotification,
68
60
  useMenu, useBreadcrumb, useThemedLayoutContext,
69
- publishLiveEvent, resetSidebarCollapsed,
61
+ resetSidebarCollapsed,
70
62
  } from './hooks.svelte';
63
+ export {
64
+ useList, useOne, useShow, useMany, useInfiniteList, useSelect, useTable,
65
+ useCreate, useUpdate, useDelete, useCreateMany, useUpdateMany, useDeleteMany, useForm,
66
+ useInvalidate,
67
+ } from './strict-hooks.svelte';
68
+ export { defineResource, getContractFormFields } from './resource-contract';
69
+ export { UpdateManyPartialError } from './update-many-contract';
70
+ export { CreateManyPartialError } from './create-many-contract';
71
+ export type { ContractCreateOptions as UseCreateOptions } from './create-hooks.svelte';
72
+ export type { ContractCreateParams as UseCreateMutateParams } from './create-contract';
73
+ export type { ContractUpdateOptions as UseUpdateOptions } from './update-hooks.svelte';
74
+ export type { ContractUpdateParams as UseUpdateMutateParams } from './update-contract';
75
+ export type { ContractDeleteOptions as UseDeleteOptions } from './delete-hooks.svelte';
76
+ export type { ContractDeleteParams as UseDeleteMutateParams } from './delete-contract';
77
+ export type { ContractFormAction, ContractFormDraft, ContractFormValues } from './resource-contract';
78
+ export type { ResourceContract, ContractSchemas, ContractRecord, ContractId, ContractInput } from './resource-contract';
79
+ export type { ContractFilter, ContractSort } from './strict-hooks.svelte';
80
+ export { defineCommand } from './command-contract';
81
+ export type { CommandContract, CommandInput, CommandOutput } from './command-contract';
82
+ export { useCustom, useCustomMutation } from './strict-command-hooks.svelte';
71
83
  export { matchRoute, navigate, currentPath, setActiveRouterProvider, beforeEach, afterEach, resetRouter } from './router';
72
84
  export type { RouteGuard } from './router';
73
85
  export {
@@ -77,7 +89,19 @@ export {
77
89
  sanitizeListQueryParams,
78
90
  writeURLState,
79
91
  } from './url-sync';
80
- export { setAccessControlProvider, getAccessControlProvider, getAccessControlOptions, canAccessAsync, createFeatureGate } from './permissions.svelte';
92
+ export { setAccessControlProvider, getAccessControlProvider, getAccessControlOptions, resetAccessControlProvider, canAccessAsync, createFeatureGate } from './permissions.svelte';
93
+ export {
94
+ canonicalPermission,
95
+ createPermissionAccessControlProvider,
96
+ decodePermissionCatalog,
97
+ decodePermissionSnapshot,
98
+ } from './permission-catalog';
99
+ export type {
100
+ PermissionCatalog,
101
+ PermissionCatalogBinding,
102
+ PermissionProjectionOptions,
103
+ PermissionSnapshot,
104
+ } from './permission-catalog';
81
105
  export { useLive, useSubscription, usePublish } from './live.svelte';
82
106
  export { toast, getToastQueue, consumeToastQueue, getPromiseQueue, consumePromiseQueue, getToasts, removeToast, resetToast } from './toast.svelte';
83
107
  export {
@@ -90,8 +114,11 @@ export {
90
114
  export type { NotificationParams } from './notification.svelte';
91
115
  export { t, setLocale, getLocale, getAvailableLocales, addTranslations, useTranslation, setI18nProvider, getI18nProvider, createI18nScope, provideI18nScope, getI18nScope, resetI18n } from './i18n.svelte';
92
116
  export type { I18nProvider, I18nScope, I18nScopeOptions } from './i18n.svelte';
93
- export { audit, auditWithProvider, writeAuditEntry, recordMutationRollback, setAuditHandler, setAuditLogProvider, getAuditLogProvider } from './audit';
94
- export type { AuditLogProvider } from './audit';
117
+ export { audit, auditWithProvider, writeAuditEntry, recordMutationRollback, setAuditHandler, setAuditLogProvider, getAuditLogProvider, withValidatedAuditProvider, AuditError } from './audit';
118
+ export type { AuditLogProvider, AuditLogTransport, AuditCreateParams, AuditQueryParams, AuditDraft } from './audit';
119
+ export { TaskError } from './task-contract';
120
+ export { withValidatedTaskProvider } from './task-provider';
121
+ export type { TaskTransport } from './task-provider';
95
122
  export type {
96
123
  ApiCredentialSummary,
97
124
  CreatedApiCredential,
@@ -131,6 +158,8 @@ export { checkError } from './hook-utils.svelte';
131
158
  export type { NotificationConfig, OvertimeOptions, OvertimeResult, LiveSubscriptionParams } from './hook-utils.svelte';
132
159
 
133
160
  export { DeleteManyPartialError, HttpError, UndoError } from './types';
161
+ export { withResourceSchemas } from './resource-schemas';
162
+ export type { ResourceSchemas, ResourceSchemaMap, InferSchemaResourceMap, InferSchemaInputMap } from './resource-schemas';
134
163
  export type {
135
164
  DataProvider, AuthProvider, NotificationProvider, MutationMode,
136
165
  ValidationErrors, HttpErrorOptions, CrudOperator, LogicalFilter, FieldFilter,
@@ -148,20 +177,24 @@ export type {
148
177
  ResourceDefinition, ResourceProviderConfig, ResourceTransportConfig, ResourceAdapterConfig,
149
178
  FieldDefinition, MenuItem,
150
179
  AuthActionResult, CheckResult,
151
- ResourceTypeMap, KnownResources, InferData,
180
+ ResourceTypeMap, ResourceInputMap, ResourceInputOperation, InferResourceInput, KnownResources, InferData,
152
181
  BaseRecord, Role, AuditLog
153
182
  } from './types';
154
183
  export type { InvalidateScope } from './options.svelte';
155
184
  export type { LiveProvider, LiveEvent, LiveMode } from './live.svelte';
156
- export type { Action, CanParams, CanResult, AccessControlProvider, FeatureGateConfig, FeatureGateUser } from './permissions.svelte';
185
+ export type { Action, CanParams, CanResult, AccessControlProvider, RegisteredAccessControlProvider, AccessControlOptions, FeatureGateConfig, FeatureGateUser } from './permissions.svelte';
157
186
  export type { AuditEntry, AuditHandler, AuditAction } from './audit';
158
187
  export { useCan } from './useCan';
159
188
  export type { UseCanOptions, UseCanResult } from './useCan';
160
189
  export { createCaslAccessControl } from './adapters/casl';
161
190
  export { createCasbinAccessControl } from './adapters/casbin';
162
191
  export type { CasbinAdapterOptions } from './adapters/casbin';
163
- export { useExport, useImport, downloadData, toCsv, toJson, toXlsx, parseCSV } from './data-transfer.svelte';
164
- export type { UseExportOptions, UseImportOptions, ExportFormat } from './data-transfer.svelte';
192
+ export { downloadData, toCsv, toJson, toXlsx, parseCSV } from './data-transfer.svelte';
193
+ export { useExport } from './export-hooks.svelte';
194
+ export type { UseExportOptions } from './export-hooks.svelte';
195
+ export { useImport } from './import-hooks.svelte';
196
+ export type { UseImportOptions, ImportProgress, ImportResult } from './import-hooks.svelte';
197
+ export type { ExportFormat } from './data-transfer.svelte';
165
198
  export {
166
199
  useLogin, useLogout,
167
200
  useRegister, useForgotPassword, useUpdatePassword,
@@ -169,16 +202,25 @@ export {
169
202
  useGetIdentity, useIsAuthenticated,
170
203
  useOnError, usePermissions,
171
204
  getLogoutVersion, resetLogoutVersion,
205
+ captureAuthLiveScope as captureAuthSession,
172
206
  } from './auth-hooks.svelte';
173
207
  export type { AuthNotificationOptions } from './auth-hooks.svelte';
208
+ export { AuthQueryError, AuthErrorHandlingError } from './auth-query-contract';
209
+ export type { AuthErrorHandlingResult } from './auth-query-contract';
210
+ export { AuthMutationError } from './auth-mutation-contract';
211
+ export { useResourceContract } from './resource-binding.svelte';
212
+ export { PermissionHintsError } from './permission-hints-contract';
213
+ export type { PermissionHints } from './permission-hints-contract';
174
214
  export {
175
215
  useSubmitTask,
176
216
  useTask,
177
217
  useTaskList,
178
218
  useTaskSubscription,
179
219
  } from './task-hooks.svelte';
220
+ export type { UseSubmitTaskOptions, UseSubmitTaskMutateParams } from './task-hooks.svelte';
180
221
  export { useParsed, resetGlobalPath, syncGlobalPath } from './useParsed.svelte';
181
- export * from './useStepsForm.svelte';
222
+ export type { UseStepsFormOptions, UseStepsFormReturn } from './useStepsForm.svelte';
223
+ export { useStepsForm } from './useStepsForm.svelte';
182
224
  export { createHashRouterProvider, createHistoryRouterProvider } from './router-provider';
183
225
  export type { RouterNavigationResult, RouterProvider } from './router-provider';
184
226
  export {
@@ -210,12 +252,19 @@ export {
210
252
  createTypeBoxValidator,
211
253
  } from './helpers';
212
254
  export type { TypeBoxValidatorLike } from './helpers';
213
- export { TableState } from './table-state.svelte';
214
- export type { TableStateOptions } from './table-state.svelte';
215
- export type { UseInfiniteListOptions } from './hooks.svelte';
216
- export type { UseSelectOptions } from './hooks.svelte';
217
- export type { UseDeleteManyOptions, UseDeleteManyMutateParams } from './hooks.svelte';
218
- export type { UseFormReturn } from './form-hooks.svelte';
255
+ export type {
256
+ StrictUseListOptions as UseListOptions,
257
+ StrictUseOneOptions as UseOneOptions,
258
+ StrictUseManyOptions as UseManyOptions,
259
+ StrictUseInfiniteListOptions as UseInfiniteListOptions,
260
+ StrictUseSelectOptions as UseSelectOptions,
261
+ StrictUseDeleteManyOptions as UseDeleteManyOptions,
262
+ StrictUseDeleteManyMutateParams as UseDeleteManyMutateParams,
263
+ StrictUseCreateManyMutateParams as UseCreateManyMutateParams,
264
+ StrictUseUpdateManyMutateParams as UseUpdateManyMutateParams,
265
+ StrictUseFormOptions as UseFormOptions,
266
+ StrictUseFormReturn as UseFormReturn,
267
+ } from './strict-hooks.svelte';
219
268
  export type {
220
269
  TaskProvider,
221
270
  TaskDateValue,
@@ -230,5 +279,3 @@ export type {
230
279
  // HTTP fetch utilities
231
280
  export { createFetchWithInterceptor, fetchWithInterceptor } from './http';
232
281
  export type { FetchWithInterceptor, FetchWithInterceptorOptions } from './http';
233
- export { createSchemaFormValidator } from './schema-form';
234
- export type { SchemaFormIssue, SchemaFormOptions } from './schema-form';
@@ -208,8 +208,9 @@ function parseGraphQLSDL(sdl: string): GraphQLSchemaBody {
208
208
  const enumRegex = /enum\s+([A-Za-z0-9_]+)\s*\{([^}]+)\}/g;
209
209
  let match: RegExpExecArray | null;
210
210
  while ((match = enumRegex.exec(cleanSDL)) !== null) {
211
- const enumName = match[1];
212
- const rawValues = match[2].split(/\s+/).filter(Boolean);
211
+ const [, enumName, enumBody] = match;
212
+ if (enumName === undefined || enumBody === undefined) continue;
213
+ const rawValues = enumBody.split(/\s+/).filter(Boolean);
213
214
  types.push({
214
215
  kind: 'ENUM',
215
216
  name: enumName,
@@ -220,8 +221,8 @@ function parseGraphQLSDL(sdl: string): GraphQLSchemaBody {
220
221
  // Extract types: type Post { id: ID! title: String ... }
221
222
  const typeRegex = /type\s+([A-Za-z0-9_]+)\s*(?:implements\s+[A-Za-z0-9_&,\s]+)?\s*\{([^}]+)\}/g;
222
223
  while ((match = typeRegex.exec(cleanSDL)) !== null) {
223
- const typeName = match[1];
224
- const fieldsBody = match[2];
224
+ const [, typeName, fieldsBody] = match;
225
+ if (typeName === undefined || fieldsBody === undefined) continue;
225
226
  const fieldLines = fieldsBody.split('\n').map(l => l.trim()).filter(Boolean);
226
227
  const fields: GraphQLFieldDescriptor[] = [];
227
228
 
@@ -230,8 +231,8 @@ function parseGraphQLSDL(sdl: string): GraphQLSchemaBody {
230
231
  const fieldMatch = /^([A-Za-z0-9_]+)(?:\([^)]*\))?\s*:\s*([[\]A-Za-z0-9_!]+)/.exec(line);
231
232
  if (!fieldMatch) continue;
232
233
 
233
- const fName = fieldMatch[1];
234
- const rawType = fieldMatch[2];
234
+ const [, fName, rawType] = fieldMatch;
235
+ if (fName === undefined || rawType === undefined) continue;
235
236
 
236
237
  const isNonNullable = rawType.endsWith('!');
237
238
  const cleanType = isNonNullable ? rawType.slice(0, -1) : rawType;