@jskit-ai/users-web 0.1.70 → 0.1.72

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
@@ -0,0 +1,107 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+
4
+ import {
5
+ buildEndpointReadRequestOptions,
6
+ buildEndpointWriteRequestOptions
7
+ } from "../src/client/composables/runtime/useEndpointResource.js";
8
+ import { buildListRequestOptions } from "../src/client/composables/runtime/useListCore.js";
9
+
10
+ test("endpoint read request options include transport only when provided", () => {
11
+ assert.deepEqual(
12
+ buildEndpointReadRequestOptions({
13
+ method: "get"
14
+ }),
15
+ {
16
+ method: "GET"
17
+ }
18
+ );
19
+
20
+ assert.deepEqual(
21
+ buildEndpointReadRequestOptions({
22
+ method: "get",
23
+ transport: {
24
+ kind: "jsonapi-resource",
25
+ responseType: "user-settings",
26
+ responseKind: "record"
27
+ }
28
+ }),
29
+ {
30
+ method: "GET",
31
+ transport: {
32
+ kind: "jsonapi-resource",
33
+ responseType: "user-settings",
34
+ responseKind: "record"
35
+ }
36
+ }
37
+ );
38
+ });
39
+
40
+ test("endpoint write request options preserve body/options and append transport", () => {
41
+ assert.deepEqual(
42
+ buildEndpointWriteRequestOptions({
43
+ method: "patch",
44
+ body: {
45
+ theme: "dark"
46
+ },
47
+ options: {
48
+ headers: {
49
+ "x-demo": "1"
50
+ }
51
+ },
52
+ transport: {
53
+ kind: "jsonapi-resource",
54
+ requestType: "user-preferences",
55
+ responseType: "user-settings",
56
+ responseKind: "record"
57
+ }
58
+ }),
59
+ {
60
+ method: "PATCH",
61
+ headers: {
62
+ "x-demo": "1"
63
+ },
64
+ body: {
65
+ theme: "dark"
66
+ },
67
+ transport: {
68
+ kind: "jsonapi-resource",
69
+ requestType: "user-preferences",
70
+ responseType: "user-settings",
71
+ responseKind: "record"
72
+ }
73
+ }
74
+ );
75
+ });
76
+
77
+ test("list request options stay GET and carry transport when provided", () => {
78
+ assert.deepEqual(
79
+ buildListRequestOptions({
80
+ requestOptions: {
81
+ headers: {
82
+ "x-demo": "1"
83
+ }
84
+ },
85
+ pageParam: "cursor_2",
86
+ transport: {
87
+ kind: "jsonapi-resource",
88
+ responseType: "contacts",
89
+ responseKind: "collection"
90
+ }
91
+ }),
92
+ {
93
+ method: "GET",
94
+ headers: {
95
+ "x-demo": "1"
96
+ },
97
+ query: {
98
+ cursor: "cursor_2"
99
+ },
100
+ transport: {
101
+ kind: "jsonapi-resource",
102
+ responseType: "contacts",
103
+ responseKind: "collection"
104
+ }
105
+ }
106
+ );
107
+ });
@@ -0,0 +1,124 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { QueryClient, VueQueryPlugin } from "@tanstack/vue-query";
4
+ import { computed, createSSRApp, effectScope, h, reactive, ref } from "vue";
5
+ import { useAddEditCore } from "../src/client/composables/runtime/useAddEditCore.js";
6
+
7
+ test("useAddEditCore writes saved payloads to the submit-time query key", async () => {
8
+ const queryClient = new QueryClient();
9
+ const model = reactive({
10
+ status: "draft"
11
+ });
12
+ const payload = {
13
+ id: 42,
14
+ status: "published"
15
+ };
16
+ const resource = {
17
+ isSaving: ref(false),
18
+ async save() {
19
+ return payload;
20
+ }
21
+ };
22
+ let runtime = null;
23
+ const app = createSSRApp({
24
+ render() {
25
+ return h("div");
26
+ }
27
+ });
28
+ app.use(VueQueryPlugin, {
29
+ queryClient
30
+ });
31
+ const scope = effectScope();
32
+
33
+ app.runWithContext(() => {
34
+ scope.run(() => {
35
+ runtime = useAddEditCore({
36
+ model,
37
+ resource,
38
+ queryKey: computed(() => ["products", model.status]),
39
+ canSave: ref(true),
40
+ fieldBag: {
41
+ clear() {},
42
+ apply() {}
43
+ },
44
+ feedback: {
45
+ clear() {},
46
+ success() {},
47
+ error() {}
48
+ },
49
+ mapLoadedToModel(target = {}, loaded = {}) {
50
+ target.status = loaded.status;
51
+ }
52
+ });
53
+ });
54
+ });
55
+
56
+ await runtime.submit();
57
+ scope.stop();
58
+
59
+ assert.equal(model.status, "published");
60
+ assert.deepEqual(queryClient.getQueryData(["products", "draft"]), payload);
61
+ assert.equal(queryClient.getQueryData(["products", "published"]), undefined);
62
+ });
63
+
64
+ test("useAddEditCore snapshots the cache key after payload normalization", async () => {
65
+ const queryClient = new QueryClient();
66
+ const model = reactive({
67
+ status: "draft"
68
+ });
69
+ const payload = {
70
+ id: 42,
71
+ status: "published"
72
+ };
73
+ const resource = {
74
+ isSaving: ref(false),
75
+ async save() {
76
+ return payload;
77
+ }
78
+ };
79
+ let runtime = null;
80
+ const app = createSSRApp({
81
+ render() {
82
+ return h("div");
83
+ }
84
+ });
85
+ app.use(VueQueryPlugin, {
86
+ queryClient
87
+ });
88
+ const scope = effectScope();
89
+
90
+ app.runWithContext(() => {
91
+ scope.run(() => {
92
+ runtime = useAddEditCore({
93
+ model,
94
+ resource,
95
+ queryKey: computed(() => ["products", model.status]),
96
+ canSave: ref(true),
97
+ fieldBag: {
98
+ clear() {},
99
+ apply() {}
100
+ },
101
+ feedback: {
102
+ clear() {},
103
+ success() {},
104
+ error() {}
105
+ },
106
+ buildRawPayload() {
107
+ model.status = "normalized";
108
+ return {};
109
+ },
110
+ mapLoadedToModel(target = {}, loaded = {}) {
111
+ target.status = loaded.status;
112
+ }
113
+ });
114
+ });
115
+ });
116
+
117
+ await runtime.submit();
118
+ scope.stop();
119
+
120
+ assert.equal(model.status, "published");
121
+ assert.equal(queryClient.getQueryData(["products", "draft"]), undefined);
122
+ assert.deepEqual(queryClient.getQueryData(["products", "normalized"]), payload);
123
+ assert.equal(queryClient.getQueryData(["products", "published"]), undefined);
124
+ });
@@ -0,0 +1,162 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { computed, reactive, ref, shallowRef } from "vue";
4
+ import { createRequestQueryRuntime } from "../src/client/composables/support/requestQueryRuntimeSupport.js";
5
+
6
+ test("add/edit request query params resolve stable cache tokens and request query objects", () => {
7
+ const sourceQueryKey = ref(["products", "dev-admin"]);
8
+ const context = ref(Object.freeze({
9
+ surfaceId: "admin",
10
+ scopeParamValue: "dev-admin",
11
+ ownershipFilter: "workspace",
12
+ recordId: "product-42",
13
+ model: {
14
+ status: "draft"
15
+ }
16
+ }));
17
+ const runtime = createRequestQueryRuntime({
18
+ requestQueryParams(callbackContext = {}) {
19
+ assert.deepEqual(callbackContext, context.value);
20
+
21
+ return {
22
+ include: "serviceId,bookingSteps,bookingSteps.requiredRoleId",
23
+ preview: callbackContext.model.status === "draft"
24
+ };
25
+ },
26
+ context,
27
+ sourceQueryKey
28
+ });
29
+
30
+ assert.deepEqual(runtime.activeRequestQueryParamEntries.value, [
31
+ {
32
+ key: "include",
33
+ values: ["serviceId,bookingSteps,bookingSteps.requiredRoleId"]
34
+ },
35
+ {
36
+ key: "preview",
37
+ values: ["1"]
38
+ }
39
+ ]);
40
+ assert.equal(
41
+ runtime.activeRequestQueryParamsToken.value,
42
+ "include=serviceId,bookingSteps,bookingSteps.requiredRoleId&preview=1"
43
+ );
44
+ assert.deepEqual(
45
+ runtime.queryKey.value,
46
+ [
47
+ "products",
48
+ "dev-admin",
49
+ "__request_query__",
50
+ "include=serviceId,bookingSteps,bookingSteps.requiredRoleId&preview=1"
51
+ ]
52
+ );
53
+ assert.deepEqual(runtime.requestQuery.value, {
54
+ include: "serviceId,bookingSteps,bookingSteps.requiredRoleId",
55
+ preview: "1"
56
+ });
57
+ });
58
+
59
+ test("add/edit request query params react to callback context changes", () => {
60
+ const recordId = ref("product-42");
61
+ const model = reactive({
62
+ status: "draft"
63
+ });
64
+ const context = computed(() => Object.freeze({
65
+ surfaceId: "admin",
66
+ scopeParamValue: "dev-admin",
67
+ ownershipFilter: "workspace",
68
+ recordId: recordId.value,
69
+ model
70
+ }));
71
+ const runtime = createRequestQueryRuntime({
72
+ requestQueryParams(callbackContext = {}) {
73
+ return {
74
+ include: "bookingSteps",
75
+ preview: callbackContext.model.status === "draft",
76
+ recordId: callbackContext.recordId
77
+ };
78
+ },
79
+ context,
80
+ sourceQueryKey: computed(() => ["products", context.value.scopeParamValue])
81
+ });
82
+
83
+ assert.deepEqual(
84
+ runtime.queryKey.value,
85
+ [
86
+ "products",
87
+ "dev-admin",
88
+ "__request_query__",
89
+ "include=bookingSteps&preview=1&recordId=product-42"
90
+ ]
91
+ );
92
+ assert.deepEqual(runtime.requestQuery.value, {
93
+ include: "bookingSteps",
94
+ preview: "1",
95
+ recordId: "product-42"
96
+ });
97
+
98
+ model.status = "published";
99
+ recordId.value = "product-99";
100
+
101
+ assert.deepEqual(
102
+ runtime.queryKey.value,
103
+ [
104
+ "products",
105
+ "dev-admin",
106
+ "__request_query__",
107
+ "include=bookingSteps&recordId=product-99"
108
+ ]
109
+ );
110
+ assert.deepEqual(runtime.requestQuery.value, {
111
+ include: "bookingSteps",
112
+ recordId: "product-99"
113
+ });
114
+ });
115
+
116
+ test("request query runtime preserves scalar base query keys", () => {
117
+ const runtime = createRequestQueryRuntime({
118
+ requestQueryParams: {
119
+ include: "bookingSteps"
120
+ },
121
+ sourceQueryKey: ref("products"),
122
+ sourcePath: "/api/products/42"
123
+ });
124
+
125
+ assert.deepEqual(
126
+ runtime.queryKey.value,
127
+ [
128
+ "products",
129
+ "__request_query__",
130
+ "include=bookingSteps"
131
+ ]
132
+ );
133
+ });
134
+
135
+ test("request query runtime leaves keys and paths unchanged without active request params", () => {
136
+ const sourceQueryKey = ["products"];
137
+ const runtime = createRequestQueryRuntime({
138
+ requestQueryParams: {
139
+ include: "",
140
+ archived: false
141
+ },
142
+ sourceQueryKey: shallowRef(sourceQueryKey)
143
+ });
144
+
145
+ assert.deepEqual(runtime.activeRequestQueryParamEntries.value, []);
146
+ assert.equal(runtime.activeRequestQueryParamsToken.value, "");
147
+ assert.deepEqual(runtime.queryKey.value, ["products"]);
148
+ assert.equal(runtime.queryKey.value, sourceQueryKey);
149
+ assert.equal(runtime.requestQuery.value, null);
150
+ });
151
+
152
+ test("request query runtime preserves inactive scalar query keys", () => {
153
+ const runtime = createRequestQueryRuntime({
154
+ requestQueryParams: {
155
+ include: ""
156
+ },
157
+ sourceQueryKey: ref("products")
158
+ });
159
+
160
+ assert.equal(runtime.queryKey.value, "products");
161
+ assert.equal(runtime.requestQuery.value, null);
162
+ });
@@ -1,7 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
3
  import { reactive } from "vue";
4
- import { Type } from "typebox";
5
4
  import {
6
5
  normalizeCrudFormFields,
7
6
  createCrudFormModel,
@@ -9,8 +8,7 @@ import {
9
8
  applyCrudPayloadToForm,
10
9
  resolveCrudRouteBoundFieldValues,
11
10
  applyCrudRouteBoundFieldValues,
12
- resolveCrudFieldErrors,
13
- parseCrudResourceOperationInput
11
+ resolveCrudFieldErrors
14
12
  } from "../src/client/composables/crud/crudSchemaFormHelpers.js";
15
13
 
16
14
  test("normalizeCrudFormFields trims keys, removes invalid entries, and deduplicates", () => {
@@ -290,51 +288,3 @@ test("resolveCrudFieldErrors returns Vuetify-compatible error arrays", () => {
290
288
  assert.deepEqual(resolveCrudFieldErrors({ name: "Name is required." }, "name"), ["Name is required."]);
291
289
  assert.deepEqual(resolveCrudFieldErrors({ name: "Name is required." }, "email"), []);
292
290
  });
293
-
294
- test("parseCrudResourceOperationInput validates and normalizes operation body payloads", () => {
295
- const resource = {
296
- operations: {
297
- create: {
298
- bodyValidator: {
299
- schema: Type.Object(
300
- {
301
- name: Type.String({ minLength: 1 }),
302
- age: Type.Integer({ minimum: 1 })
303
- },
304
- { additionalProperties: false }
305
- ),
306
- normalize(payload = {}) {
307
- return {
308
- name: String(payload.name || "").trim(),
309
- age: Number(payload.age)
310
- };
311
- }
312
- }
313
- }
314
- }
315
- };
316
-
317
- const validResult = parseCrudResourceOperationInput({
318
- resource,
319
- operationName: "create",
320
- rawPayload: {
321
- name: " Ada ",
322
- age: "2"
323
- }
324
- });
325
- assert.equal(validResult.ok, true);
326
- assert.deepEqual(validResult.value, {
327
- name: "Ada",
328
- age: 2
329
- });
330
-
331
- const invalidResult = parseCrudResourceOperationInput({
332
- resource,
333
- operationName: "create",
334
- rawPayload: {
335
- name: " ",
336
- age: "0"
337
- }
338
- });
339
- assert.equal(invalidResult.ok, false);
340
- });
@@ -53,7 +53,7 @@ test("useCrudListFilters manages values, query params, chips, and presets", asyn
53
53
 
54
54
  assert.equal(filters.queryParams.onlyStaff.value, true);
55
55
  assert.deepEqual(filters.queryParams.status.value, ["active"]);
56
- assert.equal(filters.queryParams.arrivalDateFrom.value, "2026-04-01");
56
+ assert.equal(filters.queryParams.arrivalDate.value, "2026-04-01..");
57
57
  assert.equal(filters.hasActiveFilters.value, true);
58
58
  assert.deepEqual(
59
59
  filters.activeChips.value.map((chip) => chip.label),
@@ -131,6 +131,7 @@ test("useCrudListFilters supports dynamic presets and preset matching", async ()
131
131
 
132
132
  assert.equal(filters.values.arrivalDate.from, "2026-04-18");
133
133
  assert.equal(filters.values.arrivalDate.to, "2026-04-18");
134
+ assert.equal(filters.queryParams.arrivalDate.value, "2026-04-18");
134
135
  assert.deepEqual(filters.values.status, ["archived"]);
135
136
  assert.equal(filters.matchesPreset("today"), true);
136
137
  assert.equal(filters.matchesPreset("all-dates"), false);
@@ -145,7 +146,7 @@ test("useCrudListFilters supports dynamic presets and preset matching", async ()
145
146
  assert.equal(filters.matchesPreset("all-dates"), true);
146
147
  });
147
148
 
148
- test("useCrudListFilters preset matching rejects extra enumMany values present in current state", async () => {
149
+ test("useCrudListFilters preset matching ignores invalid extra enumMany values outside the shared contract", async () => {
149
150
  const { useCrudListFilters } = await import("@jskit-ai/users-web/client/composables/useCrudListFilters");
150
151
 
151
152
  const filters = useCrudListFilters(
@@ -174,9 +175,37 @@ test("useCrudListFilters preset matching rejects extra enumMany values present i
174
175
 
175
176
  filters.values.status = ["archived", "bogus"];
176
177
 
177
- assert.equal(filters.matchesPreset("archived-only"), false);
178
+ assert.equal(filters.matchesPreset("archived-only"), true);
179
+ assert.deepEqual(filters.queryParams.status.value, ["archived"]);
178
180
  assert.deepEqual(
179
181
  filters.activeChips.value.map((chip) => chip.label),
180
- ["Status: Archived", "Status: bogus"]
182
+ ["Status: Archived"]
181
183
  );
182
184
  });
185
+
186
+ test("useCrudListFilters hydrates single-key range query params into structured values", async () => {
187
+ const { useCrudListFilters } = await import("@jskit-ai/users-web/client/composables/useCrudListFilters");
188
+
189
+ const filters = useCrudListFilters({
190
+ arrivalDate: {
191
+ type: "dateRange",
192
+ label: "Arrival Date"
193
+ },
194
+ weight: {
195
+ type: "numberRange",
196
+ label: "Weight"
197
+ }
198
+ });
199
+
200
+ filters.queryParams.arrivalDate.value = "..2026-04-30";
201
+ filters.queryParams.weight.value = "12.5..18";
202
+
203
+ assert.deepEqual(filters.values.arrivalDate, {
204
+ from: "",
205
+ to: "2026-04-30"
206
+ });
207
+ assert.deepEqual(filters.values.weight, {
208
+ min: "12.5",
209
+ max: "18"
210
+ });
211
+ });
@@ -1,5 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
+ import { createSchema } from "json-rest-schema";
3
4
  import { resolveCrudListParentDescriptor, resolveCrudListParentRecordTitle, resolveCrudListParentTitleFromItems } from "../src/client/composables/internal/crudListParentTitleSupport.js";
4
5
 
5
6
  const contactChildResource = Object.freeze({
@@ -8,24 +9,30 @@ const contactChildResource = Object.freeze({
8
9
  containerKey: "lookups"
9
10
  }
10
11
  },
11
- fieldMeta: Object.freeze([
12
- Object.freeze({
13
- key: "contactId",
14
- relation: Object.freeze({
15
- kind: "lookup",
16
- namespace: "contacts",
17
- valueKey: "id"
18
- })
19
- }),
20
- Object.freeze({
21
- key: "serviceId",
22
- relation: Object.freeze({
23
- kind: "lookup",
24
- namespace: "services",
25
- valueKey: "id"
26
- })
27
- })
28
- ])
12
+ operations: {
13
+ view: {
14
+ output: {
15
+ schema: createSchema({
16
+ contactId: {
17
+ type: "integer",
18
+ relation: {
19
+ kind: "lookup",
20
+ namespace: "contacts",
21
+ valueKey: "id"
22
+ }
23
+ },
24
+ serviceId: {
25
+ type: "integer",
26
+ relation: {
27
+ kind: "lookup",
28
+ namespace: "services",
29
+ valueKey: "id"
30
+ }
31
+ }
32
+ })
33
+ }
34
+ }
35
+ }
29
36
  });
30
37
 
31
38
  test("resolveCrudListParentDescriptor selects the nearest lookup route parent", () => {
@@ -116,17 +123,23 @@ test("resolveCrudListParentRecordTitle falls back to entity label plus id", () =
116
123
  test("resolveCrudListParentDescriptor supports parentRouteParamKey aliases", () => {
117
124
  const descriptor = resolveCrudListParentDescriptor({
118
125
  resource: {
119
- fieldMeta: [
120
- {
121
- key: "staffContactId",
122
- parentRouteParamKey: "contactId",
123
- relation: {
124
- kind: "lookup",
125
- namespace: "contacts",
126
- valueKey: "id"
126
+ operations: {
127
+ view: {
128
+ output: {
129
+ schema: createSchema({
130
+ staffContactId: {
131
+ type: "integer",
132
+ parentRouteParamKey: "contactId",
133
+ relation: {
134
+ kind: "lookup",
135
+ namespace: "contacts",
136
+ valueKey: "id"
137
+ }
138
+ }
139
+ })
127
140
  }
128
141
  }
129
- ]
142
+ }
130
143
  },
131
144
  route: {
132
145
  matched: [{ path: "/w/:workspaceSlug/admin/contacts/:contactId/availabilities" }],
@@ -1,20 +1,20 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
- import { appendRequestQueryEntriesToPath } from "../src/client/composables/support/requestQueryPathSupport.js";
3
+ import { buildRequestQueryObject } from "../src/client/composables/support/requestQueryRuntimeSupport.js";
4
4
 
5
- test("appendRequestQueryEntriesToPath appends request query params deterministically", () => {
6
- assert.equal(
7
- appendRequestQueryEntriesToPath("/api/w/dev-admin/contacts/1", [
5
+ test("buildRequestQueryObject preserves repeated keys as arrays deterministically", () => {
6
+ assert.deepEqual(
7
+ buildRequestQueryObject([
8
8
  { key: "include", values: ["vetId", "linkedUserId", "pets", "pets.breedId"] },
9
9
  { key: "limit", values: ["10"] }
10
10
  ]),
11
- "/api/w/dev-admin/contacts/1?include=vetId&include=linkedUserId&include=pets&include=pets.breedId&limit=10"
11
+ {
12
+ include: ["vetId", "linkedUserId", "pets", "pets.breedId"],
13
+ limit: "10"
14
+ }
12
15
  );
13
16
  });
14
17
 
15
- test("appendRequestQueryEntriesToPath leaves the base path unchanged when no request params are active", () => {
16
- assert.equal(
17
- appendRequestQueryEntriesToPath("/api/w/dev-admin/contacts/1", []),
18
- "/api/w/dev-admin/contacts/1"
19
- );
18
+ test("buildRequestQueryObject returns an empty object when no request params are active", () => {
19
+ assert.deepEqual(buildRequestQueryObject([]), {});
20
20
  });
@@ -1,31 +0,0 @@
1
- import { appendQueryString } from "@jskit-ai/kernel/shared/support";
2
-
3
- function appendRequestQueryEntriesToPath(path = "", entries = []) {
4
- const normalizedPath = String(path || "").trim();
5
- if (!normalizedPath) {
6
- return "";
7
- }
8
-
9
- const sourceEntries = Array.isArray(entries) ? entries : [];
10
- const searchParams = new URLSearchParams();
11
- for (const entry of sourceEntries) {
12
- const key = String(entry?.key || "").trim();
13
- const values = Array.isArray(entry?.values) ? entry.values : [];
14
- if (!key || values.length < 1) {
15
- continue;
16
- }
17
-
18
- for (const value of values) {
19
- searchParams.append(key, value);
20
- }
21
- }
22
-
23
- const serializedSearch = searchParams.toString();
24
- if (!serializedSearch) {
25
- return normalizedPath;
26
- }
27
-
28
- return appendQueryString(normalizedPath, serializedSearch);
29
- }
30
-
31
- export { appendRequestQueryEntriesToPath };