@jskit-ai/users-web 0.1.69 → 0.1.71

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 (29) hide show
  1. package/package.descriptor.mjs +7 -7
  2. package/package.json +11 -8
  3. package/src/client/bootstrap/user-bootstrap-handler.js +53 -0
  4. package/src/client/composables/crud/crudSchemaFormHelpers.js +1 -22
  5. package/src/client/composables/internal/crudListParentTitleSupport.js +14 -15
  6. package/src/client/composables/records/useAddEdit.js +23 -4
  7. package/src/client/composables/records/useCrudAddEdit.js +5 -20
  8. package/src/client/composables/records/useList.js +22 -22
  9. package/src/client/composables/records/useView.js +13 -27
  10. package/src/client/composables/runtime/operationValidationHelpers.js +18 -19
  11. package/src/client/composables/runtime/useAddEditCore.js +6 -10
  12. package/src/client/composables/runtime/useCommandCore.js +3 -10
  13. package/src/client/composables/runtime/useEndpointResource.js +75 -14
  14. package/src/client/composables/runtime/useListCore.js +45 -17
  15. package/src/client/composables/support/requestQueryRuntimeSupport.js +100 -0
  16. package/src/client/composables/useAccountSettingsRuntime.js +26 -19
  17. package/src/client/composables/useCommand.js +4 -2
  18. package/src/client/composables/useCrudListFilters.js +58 -255
  19. package/src/client/providers/UsersWebClientProvider.js +4 -2
  20. package/test/bootstrap.test.js +130 -0
  21. package/test/operationValidationHelpers.test.js +64 -0
  22. package/test/requestTransportOptions.test.js +107 -0
  23. package/test/useAddEditCore.test.js +124 -0
  24. package/test/useAddEditRequestQueryParams.test.js +162 -0
  25. package/test/useCrudAddEdit.test.js +1 -51
  26. package/test/useCrudListFilters.test.js +33 -4
  27. package/test/useCrudListParentTitle.test.js +40 -27
  28. package/test/useViewRequestQueryParams.test.js +10 -10
  29. package/src/client/composables/support/requestQueryPathSupport.js +0 -31
@@ -3,7 +3,7 @@ import { HOME_COG_OUTLET } from "./src/shared/toolsOutletContracts.js";
3
3
  export default Object.freeze({
4
4
  packageVersion: 1,
5
5
  packageId: "@jskit-ai/users-web",
6
- version: "0.1.69",
6
+ version: "0.1.71",
7
7
  kind: "runtime",
8
8
  description: "Users web module: account/profile UI plus shared users web widgets.",
9
9
  dependsOn: [
@@ -159,12 +159,12 @@ export default Object.freeze({
159
159
  runtime: {
160
160
  "@tanstack/vue-query": "5.92.12",
161
161
  "@mdi/js": "^7.4.47",
162
- "@jskit-ai/http-runtime": "0.1.53",
163
- "@jskit-ai/realtime": "0.1.53",
164
- "@jskit-ai/kernel": "0.1.54",
165
- "@jskit-ai/shell-web": "0.1.53",
166
- "@jskit-ai/uploads-image-web": "0.1.32",
167
- "@jskit-ai/users-core": "0.1.64",
162
+ "@jskit-ai/http-runtime": "0.1.55",
163
+ "@jskit-ai/realtime": "0.1.55",
164
+ "@jskit-ai/kernel": "0.1.56",
165
+ "@jskit-ai/shell-web": "0.1.55",
166
+ "@jskit-ai/uploads-image-web": "0.1.34",
167
+ "@jskit-ai/users-core": "0.1.66",
168
168
  vuetify: "^4.0.0"
169
169
  },
170
170
  dev: {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jskit-ai/users-web",
3
- "version": "0.1.69",
3
+ "version": "0.1.71",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
@@ -28,19 +28,22 @@
28
28
  "./client/composables/usePaths": "./src/client/composables/usePaths.js",
29
29
  "./client/composables/runtime/useUiFeedback": "./src/client/composables/runtime/useUiFeedback.js",
30
30
  "./client/lib/httpClient": "./src/client/lib/httpClient.js",
31
- "./client/lib/bootstrap": "./src/client/lib/bootstrap.js",
32
31
  "./client/lib/permissions": "./src/client/lib/permissions.js",
33
32
  "./client/support/contractGuards": "./src/client/support/contractGuards.js"
34
33
  },
35
34
  "dependencies": {
36
35
  "@tanstack/vue-query": "5.92.12",
37
36
  "@mdi/js": "^7.4.47",
38
- "@jskit-ai/http-runtime": "0.1.53",
39
- "@jskit-ai/kernel": "0.1.54",
40
- "@jskit-ai/realtime": "0.1.53",
41
- "@jskit-ai/shell-web": "0.1.53",
42
- "@jskit-ai/uploads-image-web": "0.1.32",
43
- "@jskit-ai/users-core": "0.1.64",
37
+ "@jskit-ai/http-runtime": "0.1.55",
38
+ "@jskit-ai/kernel": "0.1.56",
39
+ "@jskit-ai/realtime": "0.1.55",
40
+ "@jskit-ai/shell-web": "0.1.55",
41
+ "@jskit-ai/uploads-image-web": "0.1.34",
42
+ "@jskit-ai/users-core": "0.1.66",
44
43
  "vuetify": "^4.0.0"
44
+ },
45
+ "peerDependencies": {
46
+ "vue": "^3.5.13",
47
+ "vue-router": "^5.0.4"
45
48
  }
46
49
  }
@@ -0,0 +1,53 @@
1
+ import {
2
+ registerBootstrapPayloadHandler,
3
+ resolveBootstrapErrorStatusCode
4
+ } from "@jskit-ai/shell-web/client/bootstrap";
5
+ import { resolvePlacementUserFromBootstrapPayload } from "../lib/bootstrap.js";
6
+
7
+ function createUsersBootstrapUserHandler() {
8
+ return Object.freeze({
9
+ handlerId: "users.web.bootstrap.user",
10
+ order: 50,
11
+ applyBootstrapPayload({ payload = {}, placementRuntime, source } = {}) {
12
+ if (!placementRuntime || typeof placementRuntime.setContext !== "function") {
13
+ return;
14
+ }
15
+
16
+ const context = placementRuntime.getContext?.() || {};
17
+ placementRuntime.setContext(
18
+ {
19
+ user: resolvePlacementUserFromBootstrapPayload(payload, context?.user)
20
+ },
21
+ {
22
+ source
23
+ }
24
+ );
25
+ },
26
+ handleBootstrapError({ error, placementRuntime, source } = {}) {
27
+ if (!placementRuntime || typeof placementRuntime.setContext !== "function") {
28
+ return;
29
+ }
30
+ if (resolveBootstrapErrorStatusCode(error) !== 401) {
31
+ return;
32
+ }
33
+
34
+ placementRuntime.setContext(
35
+ {
36
+ user: null
37
+ },
38
+ {
39
+ source
40
+ }
41
+ );
42
+ }
43
+ });
44
+ }
45
+
46
+ function registerUsersBootstrapPayloadHandlers(app) {
47
+ registerBootstrapPayloadHandler(app, "users.web.bootstrap.user-handler", () => createUsersBootstrapUserHandler());
48
+ }
49
+
50
+ export {
51
+ createUsersBootstrapUserHandler,
52
+ registerUsersBootstrapPayloadHandlers
53
+ };
@@ -1,4 +1,3 @@
1
- import { validateOperationSection } from "@jskit-ai/http-runtime/shared/validators/operationValidation";
2
1
  import { asPlainObject } from "../support/scopeHelpers.js";
3
2
  import { toRouteParamValue } from "../support/routeTemplateHelpers.js";
4
3
 
@@ -337,25 +336,6 @@ function resolveCrudFieldErrors(fieldErrors = {}, fieldKey = "") {
337
336
  return resolveStableFieldErrorList(key, message);
338
337
  }
339
338
 
340
- function parseCrudResourceOperationInput({
341
- resource = null,
342
- operationName = "",
343
- rawPayload = {},
344
- context = {}
345
- } = {}) {
346
- const normalizedOperationName = String(operationName || "").trim();
347
- const operations = asPlainObject(asPlainObject(resource).operations);
348
- const operation = asPlainObject(operations[normalizedOperationName]);
349
-
350
- const parsed = validateOperationSection({
351
- operation,
352
- section: "bodyValidator",
353
- value: rawPayload,
354
- context
355
- });
356
- return parsed;
357
- }
358
-
359
339
  export {
360
340
  normalizeCrudFormFields,
361
341
  createCrudFormModel,
@@ -363,6 +343,5 @@ export {
363
343
  applyCrudPayloadToForm,
364
344
  resolveCrudRouteBoundFieldValues,
365
345
  applyCrudRouteBoundFieldValues,
366
- resolveCrudFieldErrors,
367
- parseCrudResourceOperationInput
346
+ resolveCrudFieldErrors
368
347
  };
@@ -3,6 +3,7 @@ import {
3
3
  resolveCrudLookupApiPathFromNamespace,
4
4
  resolveCrudLookupFieldKeyFromRouteParam
5
5
  } from "@jskit-ai/kernel/shared/support/crudLookup";
6
+ import { buildCrudFieldContractMap } from "@jskit-ai/kernel/shared/support/crudFieldContract";
6
7
  import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
7
8
  import { resolveLookupFieldDisplayValue, resolveRecordTitle } from "../crud/crudLookupFieldLabelSupport.js";
8
9
  import { resolveRouteParamNamesInOrder, toRouteParamValue } from "../support/routeTemplateHelpers.js";
@@ -58,21 +59,19 @@ function resolveLookupFieldMeta(resource = {}, fieldKey = "") {
58
59
  return null;
59
60
  }
60
61
 
61
- const entries = Array.isArray(resource?.fieldMeta) ? resource.fieldMeta : [];
62
- for (const entry of entries) {
63
- if (normalizeText(entry?.key) !== normalizedFieldKey) {
64
- continue;
65
- }
66
-
67
- const relation = entry?.relation;
68
- if (!relation || normalizeText(relation.kind).toLowerCase() !== "lookup") {
69
- return null;
70
- }
62
+ const entry = buildCrudFieldContractMap(resource, {
63
+ context: "crud list parent title resource field contract"
64
+ })[normalizedFieldKey];
65
+ if (!entry) {
66
+ return null;
67
+ }
71
68
 
72
- return entry;
69
+ const relation = entry?.relation;
70
+ if (!relation || normalizeText(relation.kind).toLowerCase() !== "lookup") {
71
+ return null;
73
72
  }
74
73
 
75
- return null;
74
+ return entry;
76
75
  }
77
76
 
78
77
  function resolveCrudListParentDescriptor({ resource = {}, route = null, recordIdParam = "recordId" } = {}) {
@@ -92,12 +91,12 @@ function resolveCrudListParentDescriptor({ resource = {}, route = null, recordId
92
91
  continue;
93
92
  }
94
93
 
95
- const fieldMeta = resolveLookupFieldMeta(resource, fieldKey);
96
- if (!fieldMeta) {
94
+ const fieldContractEntry = resolveLookupFieldMeta(resource, fieldKey);
95
+ if (!fieldContractEntry) {
97
96
  continue;
98
97
  }
99
98
 
100
- const relation = fieldMeta.relation || {};
99
+ const relation = fieldContractEntry.relation || {};
101
100
  const relationNamespace = normalizeText(relation.namespace);
102
101
  const containerKey = normalizeCrudLookupContainerKey(relation.containerKey, {
103
102
  defaultValue: resource?.contract?.lookup?.containerKey || "lookups"
@@ -14,6 +14,7 @@ import {
14
14
  import {
15
15
  resolveResourceMessages
16
16
  } from "../support/scopeHelpers.js";
17
+ import { createRequestQueryRuntime } from "../support/requestQueryRuntimeSupport.js";
17
18
  import { resolveRouteParamNamesInOrder } from "../support/routeTemplateHelpers.js";
18
19
 
19
20
  function useAddEdit({
@@ -28,17 +29,19 @@ function useAddEdit({
28
29
  readMethod = "GET",
29
30
  readEnabled = true,
30
31
  writeMethod = "PATCH",
32
+ transport = null,
31
33
  placementSource = "users-web.add-edit",
32
34
  fallbackLoadError = "Unable to load resource.",
33
35
  fallbackSaveError = "Unable to save resource.",
34
36
  fieldErrorKeys = [],
35
37
  clearOnRouteChange = true,
36
38
  model,
37
- parseInput,
39
+ input,
38
40
  mapLoadedToModel,
39
41
  buildRawPayload,
40
42
  buildSavePayload,
41
43
  onSaveSuccess,
44
+ requestQueryParams = null,
42
45
  recordIdParam = "recordId",
43
46
  routeParams = null,
44
47
  routeRecordId = null,
@@ -95,13 +98,29 @@ function useAddEdit({
95
98
  const canView = operationScope.permissionGate("view");
96
99
  const canSave = operationScope.permissionGate("save");
97
100
  const queryCanRun = operationScope.queryCanRun(canView);
101
+ const queryParamsContext = computed(() => {
102
+ return Object.freeze({
103
+ surfaceId: operationScope.routeContext.currentSurfaceId.value,
104
+ scopeParamValue: operationScope.scopeParamValue.value,
105
+ ownershipFilter: operationScope.normalizedOwnershipFilter,
106
+ recordId: addEditUiRuntime.recordId.value,
107
+ model
108
+ });
109
+ });
110
+ const requestQueryRuntime = createRequestQueryRuntime({
111
+ requestQueryParams,
112
+ context: queryParamsContext,
113
+ sourceQueryKey: operationScope.queryKey
114
+ });
98
115
 
99
116
  const endpointResource = useEndpointResource({
100
- queryKey: operationScope.queryKey,
117
+ queryKey: requestQueryRuntime.queryKey,
101
118
  path: operationScope.apiPath,
102
119
  enabled: queryCanRun,
103
120
  readMethod,
104
121
  writeMethod,
122
+ readQuery: requestQueryRuntime.requestQuery,
123
+ transport,
105
124
  fallbackLoadError,
106
125
  fallbackSaveError: String(fallbackSaveError || effectiveMessages.saveError || "Unable to save resource.")
107
126
  });
@@ -114,11 +133,11 @@ function useAddEdit({
114
133
  const addEdit = useAddEditCore({
115
134
  model,
116
135
  resource: endpointResource,
117
- queryKey: operationScope.queryKey,
136
+ queryKey: requestQueryRuntime.queryKey,
118
137
  canSave,
119
138
  fieldBag,
120
139
  feedback,
121
- parseInput,
140
+ input,
122
141
  mapLoadedToModel,
123
142
  buildRawPayload,
124
143
  buildSavePayload,
@@ -11,8 +11,7 @@ import {
11
11
  buildCrudFormPayload,
12
12
  applyCrudPayloadToForm,
13
13
  resolveCrudRouteBoundFieldValues,
14
- resolveCrudFieldErrors,
15
- parseCrudResourceOperationInput
14
+ resolveCrudFieldErrors
16
15
  } from "../crud/crudSchemaFormHelpers.js";
17
16
  import { hasResolvedQueryData } from "../support/resourceLoadStateHelpers.js";
18
17
 
@@ -49,7 +48,7 @@ function useCrudAddEdit({
49
48
  createModel = null,
50
49
  buildPayload = null,
51
50
  mapPayloadToModel = null,
52
- parseInput = null
51
+ input = null
53
52
  } = {}) {
54
53
  const router = useRouter();
55
54
  const route = useRoute();
@@ -91,9 +90,7 @@ function useCrudAddEdit({
91
90
  deep: true
92
91
  }
93
92
  );
94
- const parseInputOverride = typeof parseInput === "function"
95
- ? parseInput
96
- : (typeof normalizedAddEditOptions.parseInput === "function" ? normalizedAddEditOptions.parseInput : null);
93
+ const inputOverride = input || normalizedAddEditOptions.input || null;
97
94
  const buildPayloadOverride = typeof buildPayload === "function"
98
95
  ? buildPayload
99
96
  : (typeof normalizedAddEditOptions.buildRawPayload === "function" ? normalizedAddEditOptions.buildRawPayload : null);
@@ -105,19 +102,7 @@ function useCrudAddEdit({
105
102
  : null;
106
103
  const shouldApplyDefaultMapPayload = normalizedAddEditOptions.readEnabled !== false;
107
104
  const resolvedResource = normalizedAddEditOptions.resource || resource;
108
-
109
- function resolveParseInput(rawPayload = {}, context = {}) {
110
- if (parseInputOverride) {
111
- return parseInputOverride(rawPayload, context);
112
- }
113
-
114
- return parseCrudResourceOperationInput({
115
- resource: resolvedResource,
116
- operationName,
117
- rawPayload,
118
- context
119
- });
120
- }
105
+ const resolvedInput = inputOverride || resolvedResource?.operations?.[operationName]?.body || null;
121
106
 
122
107
  function resolveBuildRawPayload(model = {}, context = {}) {
123
108
  const payload = buildPayloadOverride
@@ -187,7 +172,7 @@ function useCrudAddEdit({
187
172
  resource: resolvedResource,
188
173
  model: form,
189
174
  fieldErrorKeys,
190
- parseInput: resolveParseInput,
175
+ input: resolvedInput,
191
176
  buildRawPayload: resolveBuildRawPayload,
192
177
  mapLoadedToModel: effectiveMapLoadedToModel,
193
178
  onSaveSuccess: handleSaveSuccess
@@ -1,6 +1,5 @@
1
1
  import { computed, onScopeDispose, proxyRefs, ref, watch } from "vue";
2
2
  import { useRouter } from "vue-router";
3
- import { appendQueryString } from "@jskit-ai/kernel/shared/support";
4
3
  import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
5
4
  import { ROUTE_VISIBILITY_WORKSPACE } from "@jskit-ai/kernel/shared/support/visibility";
6
5
  import { useListCore } from "../runtime/useListCore.js";
@@ -8,6 +7,7 @@ import { resolveOperationAdapter } from "../runtime/operationAdapters.js";
8
7
  import { setupOperationErrorReporting } from "../runtime/operationUiHelpers.js";
9
8
  import { createListUiRuntime } from "../runtime/listUiRuntime.js";
10
9
  import { asPlainObject } from "../support/scopeHelpers.js";
10
+ import { buildRequestQueryObject } from "../support/requestQueryRuntimeSupport.js";
11
11
  import {
12
12
  normalizeListSearchConfig,
13
13
  matchesLocalSearch
@@ -43,6 +43,7 @@ function useList({
43
43
  initialPageParam = null,
44
44
  getNextPageParam,
45
45
  selectItems,
46
+ transport = null,
46
47
  requestOptions,
47
48
  queryOptions,
48
49
  realtime = null,
@@ -169,36 +170,34 @@ function useList({
169
170
  });
170
171
  const querySearchEnabled = computed(() => searchConfig.enabled === true && searchConfig.mode === "query");
171
172
  const listPath = computed(() => {
172
- const basePath = normalizeText(operationScope.apiPath.value);
173
- if (!basePath) {
174
- return "";
175
- }
173
+ return normalizeText(operationScope.apiPath.value);
174
+ });
175
+ const listRequestOptions = computed(() => {
176
+ const entries = [];
176
177
 
177
- const searchParams = new URLSearchParams();
178
178
  if (querySearchEnabled.value) {
179
179
  const queryValue = activeSearchQuery.value;
180
180
  if (queryValue) {
181
- searchParams.set(searchConfig.queryParam, queryValue);
181
+ entries.push({
182
+ key: searchConfig.queryParam,
183
+ values: [queryValue]
184
+ });
182
185
  }
183
186
  }
184
187
 
185
- for (const entry of activeRequestQueryParamEntries.value) {
186
- for (const value of entry.values) {
187
- searchParams.append(entry.key, value);
188
- }
189
- }
190
- for (const entry of activeQueryParamEntries.value) {
191
- for (const value of entry.values) {
192
- searchParams.append(entry.key, value);
193
- }
194
- }
188
+ entries.push(...activeRequestQueryParamEntries.value);
189
+ entries.push(...activeQueryParamEntries.value);
195
190
 
196
- const serializedSearch = searchParams.toString();
197
- if (!serializedSearch) {
198
- return basePath;
191
+ const query = buildRequestQueryObject(entries);
192
+ const baseOptions = asPlainObject(requestOptions);
193
+ if (Object.keys(query).length < 1) {
194
+ return baseOptions;
199
195
  }
200
196
 
201
- return appendQueryString(basePath, serializedSearch);
197
+ return {
198
+ ...baseOptions,
199
+ query
200
+ };
202
201
  });
203
202
  const listQueryKey = computed(() => {
204
203
  const sourceQueryKey = operationScope.queryKey.value;
@@ -223,10 +222,11 @@ function useList({
223
222
  queryKey: listQueryKey,
224
223
  path: listPath,
225
224
  enabled: operationScope.queryCanRun(canView),
225
+ transport,
226
226
  initialPageParam,
227
227
  getNextPageParam,
228
228
  selectItems,
229
- requestOptions,
229
+ requestOptions: listRequestOptions,
230
230
  queryOptions,
231
231
  fallbackLoadError
232
232
  });
@@ -6,12 +6,7 @@ import { useEndpointResource } from "../runtime/useEndpointResource.js";
6
6
  import { resolveOperationAdapter } from "../runtime/operationAdapters.js";
7
7
  import { setupOperationErrorReporting } from "../runtime/operationUiHelpers.js";
8
8
  import { createViewUiRuntime } from "../runtime/viewUiRuntime.js";
9
- import {
10
- resolveQueryParamDescriptors,
11
- resolveActiveQueryParamEntries,
12
- buildQueryParamEntriesToken
13
- } from "../support/listQueryParamSupport.js";
14
- import { appendRequestQueryEntriesToPath } from "../support/requestQueryPathSupport.js";
9
+ import { createRequestQueryRuntime } from "../support/requestQueryRuntimeSupport.js";
15
10
  import { resolveRouteParamNamesInOrder } from "../support/routeTemplateHelpers.js";
16
11
 
17
12
  function useView({
@@ -23,6 +18,7 @@ function useView({
23
18
  viewPermissions = [],
24
19
  readMethod = "GET",
25
20
  readEnabled = true,
21
+ transport = null,
26
22
  placementSource = "users-web.view",
27
23
  fallbackLoadError = "Unable to load resource.",
28
24
  notFoundStatuses = [404],
@@ -73,24 +69,13 @@ function useView({
73
69
  return Object.freeze({
74
70
  surfaceId: operationScope.routeContext.currentSurfaceId.value,
75
71
  scopeParamValue: operationScope.scopeParamValue.value,
76
- ownershipFilter: operationScope.normalizedOwnershipFilter
72
+ ownershipFilter: operationScope.normalizedOwnershipFilter,
73
+ recordId: viewUiRuntime.recordId.value
77
74
  });
78
75
  });
79
- const requestQueryParamDescriptors = computed(() => {
80
- return resolveQueryParamDescriptors(requestQueryParams, queryParamsContext.value);
81
- });
82
- const activeRequestQueryParamEntries = computed(() => {
83
- return resolveActiveQueryParamEntries(requestQueryParamDescriptors.value);
84
- });
85
- const activeRequestQueryParamsToken = computed(() => {
86
- return buildQueryParamEntriesToken(activeRequestQueryParamEntries.value);
87
- });
88
- const queryKey = computed(() => {
76
+ const baseQueryKey = computed(() => {
89
77
  const source = Array.isArray(operationScope.queryKey.value) ? operationScope.queryKey.value : [];
90
78
  const next = [...source];
91
- if (activeRequestQueryParamsToken.value) {
92
- next.push("__request_query__", activeRequestQueryParamsToken.value);
93
- }
94
79
  if (!includeRecordIdInQueryKey) {
95
80
  return next;
96
81
  }
@@ -98,19 +83,20 @@ function useView({
98
83
  const recordIdToken = String(viewUiRuntime.recordId.value || "").trim();
99
84
  return [...next, recordIdToken];
100
85
  });
101
- const requestPath = computed(() => {
102
- return appendRequestQueryEntriesToPath(
103
- operationScope.apiPath.value,
104
- activeRequestQueryParamEntries.value
105
- );
86
+ const requestQueryRuntime = createRequestQueryRuntime({
87
+ requestQueryParams,
88
+ context: queryParamsContext,
89
+ sourceQueryKey: baseQueryKey
106
90
  });
107
91
  const canView = operationScope.permissionGate("view");
108
92
 
109
93
  const resource = useEndpointResource({
110
- queryKey,
111
- path: requestPath,
94
+ queryKey: requestQueryRuntime.queryKey,
95
+ path: operationScope.apiPath,
112
96
  enabled: operationScope.queryCanRun(canView),
113
97
  readMethod,
98
+ readQuery: requestQueryRuntime.requestQuery,
99
+ transport,
114
100
  fallbackLoadError
115
101
  });
116
102
 
@@ -2,51 +2,50 @@ import {
2
2
  createValidationFailure,
3
3
  resolveFieldErrors
4
4
  } from "@jskit-ai/http-runtime/client";
5
+ import { validateSchemaPayload } from "@jskit-ai/kernel/shared/validators";
5
6
 
6
7
  function validateOperationInput({
7
- parseInput,
8
+ input,
8
9
  rawPayload = {},
9
- context = {},
10
10
  fieldBag = null,
11
11
  feedback = null,
12
12
  validationMessage = "Validation failed."
13
13
  } = {}) {
14
- if (typeof parseInput !== "function") {
14
+ if (!input) {
15
15
  return {
16
16
  ok: true,
17
- parseResult: null,
18
17
  parsedInput: rawPayload
19
18
  };
20
19
  }
21
20
 
22
- const parseResult = parseInput(rawPayload, context);
23
- if (!parseResult || typeof parseResult !== "object" || typeof parseResult.ok !== "boolean") {
24
- throw new TypeError(
25
- "parseInput(rawPayload, context) must return validateOperationSection-compatible result with boolean ok."
26
- );
27
- }
21
+ try {
22
+ const parsedInput = validateSchemaPayload(input, rawPayload, {
23
+ phase: "input",
24
+ context: "operation input"
25
+ });
26
+
27
+ return {
28
+ ok: true,
29
+ parsedInput
30
+ };
31
+ } catch (error) {
32
+ if (!error?.fieldErrors || typeof error.fieldErrors !== "object") {
33
+ throw error;
34
+ }
28
35
 
29
- if (!parseResult.ok) {
30
36
  const failure = createValidationFailure({
31
37
  error: String(validationMessage || "Validation failed."),
32
38
  code: "validation_failed",
33
- fieldErrors: parseResult.fieldErrors
39
+ fieldErrors: error?.fieldErrors
34
40
  });
35
41
  fieldBag?.apply?.(resolveFieldErrors(failure));
36
42
  feedback?.error?.(failure, failure.error);
37
43
  return {
38
44
  ok: false,
39
45
  failure,
40
- parseResult,
41
46
  parsedInput: null
42
47
  };
43
48
  }
44
-
45
- return {
46
- ok: true,
47
- parseResult,
48
- parsedInput: parseResult.value
49
- };
50
49
  }
51
50
 
52
51
  export { validateOperationInput };
@@ -10,7 +10,7 @@ function useAddEditCore({
10
10
  canSave,
11
11
  fieldBag,
12
12
  feedback,
13
- parseInput,
13
+ input,
14
14
  mapLoadedToModel,
15
15
  buildRawPayload,
16
16
  buildSavePayload,
@@ -49,12 +49,8 @@ function useAddEditCore({
49
49
  }) : {};
50
50
 
51
51
  const validationResult = validateOperationInput({
52
- parseInput,
52
+ input,
53
53
  rawPayload,
54
- context: {
55
- queryClient,
56
- resource
57
- },
58
54
  fieldBag,
59
55
  feedback,
60
56
  validationMessage: String(messages.validation || "Validation failed.")
@@ -63,7 +59,7 @@ function useAddEditCore({
63
59
  return;
64
60
  }
65
61
 
66
- const { parseResult, parsedInput } = validationResult;
62
+ const { parsedInput } = validationResult;
67
63
  const savePayload = typeof buildSavePayload === "function"
68
64
  ? buildSavePayload(parsedInput, {
69
65
  rawPayload,
@@ -73,6 +69,7 @@ function useAddEditCore({
73
69
  : parsedInput;
74
70
 
75
71
  try {
72
+ const queryKeySnapshot = queryKey?.value;
76
73
  const payload = await resource.save(savePayload);
77
74
 
78
75
  if (typeof mapLoadedToModel === "function") {
@@ -82,15 +79,14 @@ function useAddEditCore({
82
79
  });
83
80
  }
84
81
 
85
- if (queryKey?.value !== undefined) {
86
- queryClient.setQueryData(queryKey.value, payload);
82
+ if (queryKeySnapshot !== undefined) {
83
+ queryClient.setQueryData(queryKeySnapshot, payload);
87
84
  }
88
85
 
89
86
  if (typeof onSaveSuccess === "function") {
90
87
  await onSaveSuccess(payload, {
91
88
  queryClient,
92
89
  parsed: parsedInput,
93
- parseResult,
94
90
  rawPayload,
95
91
  savePayload,
96
92
  resource