@jskit-ai/http-web 0.1.92 → 0.1.93

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 (51) hide show
  1. package/package.json +6 -6
  2. package/patterns/crud-screen-set/PATTERN.md +11 -1
  3. package/src/client/bulkActions.js +13 -40
  4. package/src/client/components/CrudAddEditScreen.vue +1 -9
  5. package/src/client/components/CrudListBulkActionSurface.vue +2 -5
  6. package/src/client/components/CrudListFilterControls.vue +175 -0
  7. package/src/client/components/CrudListFilterSurface.vue +5 -248
  8. package/src/client/components/CrudListScreen.vue +1 -28
  9. package/src/client/components/CrudViewScreen.vue +1 -9
  10. package/src/client/composables/crud/crudJsonApiTransportSupport.js +20 -42
  11. package/src/client/composables/crud/crudLookupFieldLabelSupport.js +2 -13
  12. package/src/client/composables/crud/crudLookupFieldRuntime.js +2 -27
  13. package/src/client/composables/crud/crudSchemaFormHelpers.js +1 -7
  14. package/src/client/composables/internal/crudListFilterLookupSupport.js +2 -1
  15. package/src/client/composables/internal/useOperationScope.js +5 -7
  16. package/src/client/composables/records/useAddEdit.js +12 -11
  17. package/src/client/composables/records/useCrudAddEdit.js +74 -22
  18. package/src/client/composables/records/useCrudList.js +0 -6
  19. package/src/client/composables/records/useCrudView.js +0 -6
  20. package/src/client/composables/records/useList.js +2 -8
  21. package/src/client/composables/records/useView.js +3 -8
  22. package/src/client/composables/runtime/addEditUiRuntime.js +12 -60
  23. package/src/client/composables/runtime/listUiRuntime.js +11 -36
  24. package/src/client/composables/runtime/useFieldErrorBag.js +3 -3
  25. package/src/client/composables/runtime/viewUiRuntime.js +14 -62
  26. package/src/client/composables/support/errorMessageHelpers.js +6 -48
  27. package/src/client/composables/support/resourceLoadStateHelpers.js +67 -156
  28. package/src/client/composables/support/routeTemplateHelpers.js +57 -0
  29. package/src/client/composables/useAccess.js +12 -17
  30. package/src/client/composables/useCommand.js +0 -1
  31. package/src/client/composables/useCrudDeleteAction.js +7 -5
  32. package/src/client/composables/useCrudListBulkActions.js +9 -21
  33. package/src/client/composables/useCrudListFilterLookups.js +1 -3
  34. package/src/client/composables/useCrudListParentTitle.js +0 -2
  35. package/src/client/composables/useCrudListRowActions.js +8 -24
  36. package/src/client/composables/useCrudListScreen.js +4 -5
  37. package/src/client/composables/useCrudViewScreen.js +2 -3
  38. package/src/client/composables/useRealtimeQueryInvalidation.js +3 -10
  39. package/src/client/composables/useScopeRuntime.js +2 -5
  40. package/src/client/rowActions.js +13 -40
  41. package/src/client/support/crudListActionSupport.js +54 -0
  42. package/test/crudAddEditActions.test.js +59 -19
  43. package/test/crudListFilterSurface.test.js +7 -4
  44. package/test/listUiRuntime.test.js +3 -1
  45. package/test/requestTransportOptions.test.js +24 -0
  46. package/test/resourceLoadStateHelpers.test.js +32 -0
  47. package/test/useAccess.test.js +62 -0
  48. package/test/useCrudDeleteAction.test.js +57 -2
  49. package/test/useCrudListScreen.test.js +45 -0
  50. package/test/useListRouteInitialization.test.js +5 -44
  51. package/src/client/composables/runtime/operationAdapters.js +0 -34
@@ -4,9 +4,10 @@ import { registerHooks } from "node:module";
4
4
  import test from "node:test";
5
5
  import { compileScript, parse } from "@vue/compiler-sfc";
6
6
  import { QueryClient, VueQueryPlugin } from "@tanstack/vue-query";
7
- import { computed, createRenderer, defineComponent, h, nextTick, ref, unref } from "vue";
8
- import { createMemoryHistory, createRouter, useRoute } from "vue-router";
7
+ import { createRenderer, defineComponent, h, nextTick, ref } from "vue";
8
+ import { createMemoryHistory, createRouter } from "vue-router";
9
9
  import { createSchema } from "json-rest-schema";
10
+ import { SHELL_WEB_PLACEMENT_RUNTIME_KEY } from "@jskit-ai/shell-web/client/placement";
10
11
  import { useCrudAddEditScreen } from "../src/client/composables/useCrudAddEditScreen.js";
11
12
 
12
13
  const componentUrl = new URL("../src/client/components/CrudAddEditScreen.vue", import.meta.url).href;
@@ -24,7 +25,7 @@ const hooks = registerHooks({
24
25
  const { default: CrudAddEditScreen } = await import(componentUrl);
25
26
  hooks.deregister();
26
27
 
27
- async function fixture(t, { actions, onSaveSuccess, request, ...options } = {}) {
28
+ async function fixture(t, { actions, onSaveSuccess, request, screenResource = null, ...options } = {}) {
28
29
  const node = (type, text = "") => ({ type, text, props: {}, children: [], parent: null });
29
30
  const renderer = createRenderer({
30
31
  createElement: node,
@@ -51,7 +52,18 @@ async function fixture(t, { actions, onSaveSuccess, request, ...options } = {})
51
52
  });
52
53
  await router.push("/books/new?source=list");
53
54
  const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
54
- const canSave = ref(true);
55
+ // Save permission comes from the web placement context, as in a real shell.
56
+ let placementContext = Object.freeze({ permissions: ["books.save"] });
57
+ const placementListeners = new Set();
58
+ const canSave = {
59
+ get value() {
60
+ return placementContext.permissions.includes("books.save");
61
+ },
62
+ set value(allowed) {
63
+ placementContext = Object.freeze({ permissions: allowed ? ["books.save"] : [] });
64
+ for (const listener of placementListeners) listener();
65
+ }
66
+ };
55
67
  const reports = [];
56
68
  const calls = [];
57
69
  let screen;
@@ -59,6 +71,7 @@ async function fixture(t, { actions, onSaveSuccess, request, ...options } = {})
59
71
  setup() {
60
72
  screen = useCrudAddEditScreen({
61
73
  mode: "new",
74
+ resource: screenResource,
62
75
  saveLabel: "Save book",
63
76
  cancelTo: "/books/:bookId",
64
77
  preserveCancelQuery: true,
@@ -80,21 +93,10 @@ async function fixture(t, { actions, onSaveSuccess, request, ...options } = {})
80
93
  return request ? request(path, requestOptions) : { id: calls.length, ...requestOptions.body };
81
94
  }
82
95
  },
83
- adapter: {
84
- useOperationScope() {
85
- return {
86
- routeContext: { route: useRoute(), currentSurfaceId: ref("admin") },
87
- scopeParamValue: ref(""),
88
- normalizedOwnershipFilter: "none",
89
- apiPath: ref("/api/books"),
90
- queryKey: ref(["books", "new"]),
91
- queryCanRun: () => ref(false),
92
- permissionGate: (key) => key === "save" ? canSave : ref(true),
93
- loadError: (value) => computed(() => unref(value)),
94
- isLoading: (value) => computed(() => unref(value))
95
- };
96
- }
97
- },
96
+ apiSuffix: "/books",
97
+ queryKeyFactory: () => ["books", "new"],
98
+ ownershipFilter: "public",
99
+ savePermissions: ["books.save"],
98
100
  onSaveSuccess: onSaveSuccess ? (payload, context) => onSaveSuccess(screen, payload, context) : undefined,
99
101
  ...options
100
102
  },
@@ -111,6 +113,16 @@ async function fixture(t, { actions, onSaveSuccess, request, ...options } = {})
111
113
  });
112
114
  app.use(router);
113
115
  app.use(VueQueryPlugin, { queryClient });
116
+ app.provide(SHELL_WEB_PLACEMENT_RUNTIME_KEY, {
117
+ getContext: () => placementContext,
118
+ getPlacements: () => [],
119
+ setContext: () => placementContext,
120
+ subscribe(listener) {
121
+ placementListeners.add(listener);
122
+ return () => placementListeners.delete(listener);
123
+ },
124
+ getRevision: () => 0
125
+ });
114
126
  app.provide("jskit.shell-web.runtime.web-error.client", {
115
127
  report(value) { reports.push(value); },
116
128
  dismiss() {}
@@ -310,3 +322,31 @@ test("route cleanup resets validation attempts unless clearOnRouteChange is disa
310
322
  });
311
323
  }
312
324
  });
325
+
326
+ test("a save failure reports the resource's saveError from both the endpoint and the feedback", async (t) => {
327
+ const { screen, reports } = await fixture(t, {
328
+ screenResource: { messages: { saveError: "Could not save the book." } },
329
+ request() { throw Object.assign(new Error(""), { status: 500 }); }
330
+ });
331
+ Object.assign(screen.formState, { title: "Book", author: "Author" });
332
+ await screen.addEdit.submit();
333
+
334
+ assert.equal(screen.addEdit.resource.saveError.value, "Could not save the book.");
335
+ assert.equal(reports.length, 1);
336
+ assert.equal(reports[0].message, "Could not save the book.");
337
+ });
338
+
339
+ test("addEditOptions cannot become a second source for top-level form facts", async (t) => {
340
+ for (const [key, topLevel] of [
341
+ ["resource", "resource"],
342
+ ["input", "input"],
343
+ ["buildRawPayload", "buildPayload"],
344
+ ["mapLoadedToModel", "mapPayloadToModel"]
345
+ ]) {
346
+ await assert.rejects(
347
+ fixture(t, { [key]: key === "resource" || key === "input" ? {} : () => ({}) }),
348
+ new RegExp(`takes ${topLevel} as a top-level option, not addEditOptions\\.${key}`)
349
+ );
350
+ }
351
+ await assert.rejects(fixture(t, { listUrlTemplat: "/books" }), /does not support addEditOptions\.listUrlTemplat/);
352
+ });
@@ -26,10 +26,13 @@ test("http-web exposes client-side CRUD list filter definition helpers", async (
26
26
  });
27
27
 
28
28
  test("CrudListFilterSurface provides adaptive controls without owning server semantics", async () => {
29
- const source = await readFile(
30
- path.join(PACKAGE_DIR, "src", "client", "components", "CrudListFilterSurface.vue"),
31
- "utf8"
32
- );
29
+ const componentsDir = path.join(PACKAGE_DIR, "src", "client", "components");
30
+ const surfaceSource = await readFile(path.join(componentsDir, "CrudListFilterSurface.vue"), "utf8");
31
+ const controlsSource = await readFile(path.join(componentsDir, "CrudListFilterControls.vue"), "utf8");
32
+ const source = `${surfaceSource}\n${controlsSource}`;
33
+
34
+ assert.match(surfaceSource, /<CrudListFilterControls[^>]*:runtime="runtime"[^>]*\/>/);
35
+ assert.match(surfaceSource, /<CrudListFilterControls[^>]*\bstacked\b[^>]*\/>/);
33
36
 
34
37
  assert.match(source, /defineProps/);
35
38
  assert.match(source, /useDisplay/);
@@ -15,7 +15,9 @@ test("createListUiRuntime resolves row keys and relative route templates from st
15
15
  editUrlTemplate: "./:contactId/edit"
16
16
  });
17
17
 
18
- assert.equal(runtime.actionColumnCount, 2);
18
+ assert.equal(runtime.hasViewUrl, true);
19
+ assert.equal(runtime.hasEditUrl, true);
20
+ assert.equal(Object.hasOwn(runtime, "actionColumnCount"), false);
19
21
  assert.equal(runtime.resolveRowKey(items.value[0], 0), "abc 123");
20
22
  assert.equal(runtime.resolveParams("./new"), "/w/acme/admin/contacts/new");
21
23
  assert.equal(runtime.resolveViewUrl(items.value[0]), "/w/acme/admin/contacts/abc%20123");
@@ -293,6 +293,30 @@ test("endpoint resource reads attach request recovery metadata to query options"
293
293
  });
294
294
  });
295
295
 
296
+ test("realtime invalidation honours a function-form enabled option like the other operation options", async () => {
297
+ const subscribed = [];
298
+ const socket = {
299
+ on(eventName) { subscribed.push(eventName); },
300
+ off() {}
301
+ };
302
+ const app = createSSRApp({
303
+ setup() {
304
+ useEndpointResource({
305
+ queryKey: ["realtime-disabled"],
306
+ path: "/api/realtime-disabled",
307
+ client: { async request() { return {}; } },
308
+ realtime: { event: "records.changed", enabled: () => false }
309
+ });
310
+ return () => h("div");
311
+ }
312
+ });
313
+ app.use(VueQueryPlugin, { queryClient: new QueryClient() });
314
+ app.provide("jskit.realtime.runtime.client.socket", socket);
315
+ await renderToString(app);
316
+
317
+ assert.deepEqual(subscribed, []);
318
+ });
319
+
296
320
  for (const kind of ["endpoint", "list", "list with caller signal"]) {
297
321
  test(`repeated ${kind} refreshes abort superseded HTTP requests`, async (t) => {
298
322
  const paginated = kind !== "endpoint";
@@ -187,3 +187,35 @@ test("mergeRequestRecoveryQueryMeta preserves disabled recovery metadata", () =>
187
187
  }
188
188
  );
189
189
  });
190
+
191
+ test("request recovery accepts only its short option names", () => {
192
+ assert.deepEqual(buildRequestRecoveryMeta({ enabled: false, label: "Ignored" }), {
193
+ jskit: { requestRecovery: false }
194
+ });
195
+ assert.deepEqual(buildRequestRecoveryMeta({ dedupeWindowMs: "-5" }, { label: "Default" }), {
196
+ jskit: { requestRecoveryLabel: "Default", requestRecoveryDedupeWindowMs: 0 }
197
+ });
198
+ assert.throws(
199
+ () => buildRequestRecoveryMeta({ requestRecoveryLabel: "Long name" }),
200
+ /requestRecovery\.requestRecoveryLabel is not supported/
201
+ );
202
+ });
203
+
204
+ test("composable defaults fill only the recovery hints the query options leave unset", () => {
205
+ assert.deepEqual(
206
+ mergeRequestRecoveryQueryMeta(
207
+ { meta: { jskit: { requestRecoveryDedupeWindowMs: 250 } } },
208
+ null,
209
+ { label: "Default label", method: "get", dedupeWindowMs: 5000 }
210
+ ),
211
+ {
212
+ meta: {
213
+ jskit: {
214
+ requestRecoveryDedupeWindowMs: 250,
215
+ requestRecoveryLabel: "Default label",
216
+ requestRecoveryMethod: "GET"
217
+ }
218
+ }
219
+ }
220
+ );
221
+ });
@@ -0,0 +1,62 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { createSSRApp, h } from "vue";
4
+ import { renderToString } from "vue/server-renderer";
5
+ import { SHELL_WEB_PLACEMENT_RUNTIME_KEY } from "@jskit-ai/shell-web/client/placement";
6
+ import { useAccess } from "../src/client/composables/useAccess.js";
7
+
8
+ async function renderAccess(placementContext, options = {}) {
9
+ let access = null;
10
+ const app = createSSRApp({
11
+ setup() {
12
+ access = useAccess(options);
13
+ return () => h("div");
14
+ }
15
+ });
16
+ app.provide(SHELL_WEB_PLACEMENT_RUNTIME_KEY, {
17
+ getContext: () => placementContext,
18
+ getPlacements: () => [],
19
+ setContext: () => placementContext,
20
+ subscribe: () => () => {},
21
+ getRevision: () => 0
22
+ });
23
+ await renderToString(app);
24
+ return access;
25
+ }
26
+
27
+ test("useAccess reads permissions from the placement context only", async () => {
28
+ const access = await renderAccess({ permissions: ["workspace.members.view"] });
29
+
30
+ assert.deepEqual(access.permissions.value, ["workspace.members.view"]);
31
+ assert.equal(access.can("workspace.members.view"), true);
32
+ assert.equal(access.canAny(["workspace.members.manage", "workspace.members.view"]), true);
33
+ assert.equal(access.canAll(["workspace.members.manage", "workspace.members.view"]), false);
34
+ assert.equal(access.awaitingPlacementPermissions.value, false);
35
+ assert.equal(access.permissionsUnavailableError.value, "");
36
+ assert.deepEqual(Object.keys(access).sort(), [
37
+ "accessMode",
38
+ "accessRequired",
39
+ "awaitingPlacementPermissions",
40
+ "can",
41
+ "canAll",
42
+ "canAny",
43
+ "permissions",
44
+ "permissionsUnavailableError"
45
+ ]);
46
+ });
47
+
48
+ test("useAccess awaits placement permissions until the context carries them", async () => {
49
+ const access = await renderAccess({});
50
+
51
+ assert.deepEqual(access.permissions.value, []);
52
+ assert.equal(access.can("workspace.members.view"), false);
53
+ assert.equal(access.awaitingPlacementPermissions.value, true);
54
+ assert.equal(access.permissionsUnavailableError.value, "Permissions are unavailable in placement context.");
55
+ });
56
+
57
+ test("useAccess reports nothing when disabled", async () => {
58
+ const access = await renderAccess({}, { enabled: false });
59
+
60
+ assert.equal(access.awaitingPlacementPermissions.value, false);
61
+ assert.equal(access.permissionsUnavailableError.value, "");
62
+ });
@@ -1,7 +1,7 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
- import { QueryClient, VueQueryPlugin } from "@tanstack/vue-query";
4
- import { createSSRApp, h, nextTick, ref } from "vue";
3
+ import { QueryClient, QueryObserver, VueQueryPlugin } from "@tanstack/vue-query";
4
+ import { createSSRApp, effectScope, h, nextTick, ref, watch } from "vue";
5
5
  import { renderToString } from "vue/server-renderer";
6
6
  import { createMemoryHistory, createRouter } from "vue-router";
7
7
  import { configureHttpWebClient, resetHttpWebClientForTests } from "../src/client/lib/httpClient.js";
@@ -111,6 +111,61 @@ test("useCrudDeleteAction confirms one DELETE, invalidates CRUD state, and retur
111
111
  ]);
112
112
  });
113
113
 
114
+ test("useCrudDeleteAction leaves the deleted record's view before CRUD state is refetched", async () => {
115
+ const queryClient = new QueryClient({
116
+ defaultOptions: { queries: { retry: false } }
117
+ });
118
+ const viewQueryKey = ["crud", "notes", "view", "42"];
119
+ let viewFetches = 0;
120
+ let recordDeleted = false;
121
+ const viewObserver = new QueryObserver(queryClient, {
122
+ queryKey: viewQueryKey,
123
+ async queryFn() {
124
+ viewFetches += 1;
125
+ if (recordDeleted) {
126
+ throw Object.assign(new Error("Record not found."), { status: 404 });
127
+ }
128
+ return { id: "42" };
129
+ }
130
+ });
131
+ const stopObservingView = viewObserver.subscribe(() => {});
132
+ await queryClient.fetchQuery({ queryKey: viewQueryKey, queryFn: () => ({ id: "42" }) });
133
+ viewFetches = 0;
134
+
135
+ const client = {
136
+ async request() {
137
+ recordDeleted = true;
138
+ return null;
139
+ }
140
+ };
141
+ const { deleteAction, router } = await renderDeleteAction({ client, queryClient });
142
+
143
+ // The view screen unmounts (stops observing its query) when the route leaves it,
144
+ // in the same scheduler flush that re-renders the router view.
145
+ const scope = effectScope();
146
+ scope.run(() => {
147
+ watch(() => router.currentRoute.value.path, (path) => {
148
+ if (path === "/notes") {
149
+ stopObservingView();
150
+ }
151
+ });
152
+ });
153
+
154
+ try {
155
+ deleteAction.request();
156
+ await deleteAction.confirm();
157
+
158
+ assert.equal(router.currentRoute.value.path, "/notes");
159
+ assert.equal(viewFetches, 0);
160
+ assert.equal(queryClient.getQueryState(viewQueryKey)?.isInvalidated, true);
161
+ assert.equal(queryClient.getQueryState(viewQueryKey)?.error, null);
162
+ } finally {
163
+ scope.stop();
164
+ stopObservingView();
165
+ queryClient.clear();
166
+ }
167
+ });
168
+
114
169
  test("useCrudDeleteAction keeps the record dialog open with useful error feedback", async () => {
115
170
  const client = {
116
171
  async request() {
@@ -0,0 +1,45 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { createSSRApp, h } from "vue";
4
+ import { renderToString } from "vue/server-renderer";
5
+ import { createMemoryHistory, createRouter } from "vue-router";
6
+ import { QueryClient, VueQueryPlugin } from "@tanstack/vue-query";
7
+ import { useCrudListScreen } from "../src/client/composables/useCrudListScreen.js";
8
+
9
+ async function renderListScreen(options) {
10
+ const router = createRouter({ history: createMemoryHistory(), routes: [{ path: "/books", component: { render: () => h("div") } }] });
11
+ await router.push("/books");
12
+ const queryClient = new QueryClient();
13
+ let screen;
14
+ const app = createSSRApp({
15
+ setup() {
16
+ screen = useCrudListScreen({
17
+ resourceNamespace: "books",
18
+ apiSuffix: "/books",
19
+ readEnabled: false,
20
+ client: { async request() { return { items: [], nextCursor: null }; } },
21
+ ...options
22
+ });
23
+ return () => h("div");
24
+ }
25
+ });
26
+ app.use(router);
27
+ app.use(VueQueryPlugin, { queryClient });
28
+ await renderToString(app);
29
+ queryClient.clear();
30
+ return screen;
31
+ }
32
+
33
+ test("list screen link availability is the list runtime's single answer", async () => {
34
+ const blank = await renderListScreen({ viewUrlTemplate: " ", editUrlTemplate: "" });
35
+ assert.equal(blank.hasViewUrl, false);
36
+ assert.equal(blank.hasEditUrl, false);
37
+ assert.equal(blank.hasViewUrl, blank.records.hasViewUrl);
38
+
39
+ const linked = await renderListScreen({ viewUrlTemplate: "/books/:recordId", editUrlTemplate: "/books/:recordId/edit" });
40
+ assert.equal(linked.hasViewUrl, true);
41
+ assert.equal(linked.hasEditUrl, true);
42
+ assert.equal(Object.hasOwn(linked.records, "actionColumnCount"), false);
43
+ assert.equal(Object.hasOwn(linked.records, "resolveFieldDisplay"), false);
44
+ assert.equal(linked.resolveRecordTitle({ name: "Dune" }), "Dune");
45
+ });
@@ -1,17 +1,8 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
3
  import { QueryClient, VueQueryPlugin } from "@tanstack/vue-query";
4
- import {
5
- computed,
6
- createRenderer,
7
- h,
8
- unref
9
- } from "vue";
10
- import {
11
- createMemoryHistory,
12
- createRouter,
13
- useRoute
14
- } from "vue-router";
4
+ import { createRenderer, h } from "vue";
5
+ import { createMemoryHistory, createRouter } from "vue-router";
15
6
 
16
7
  import { useList } from "../src/client/composables/records/useList.js";
17
8
  import { useCrudListFilters } from "../src/client/composables/useCrudListFilters.js";
@@ -137,37 +128,6 @@ function createTestRenderer() {
137
128
  });
138
129
  }
139
130
 
140
- function createListTestAdapter() {
141
- return Object.freeze({
142
- useOperationScope() {
143
- const route = useRoute();
144
-
145
- return Object.freeze({
146
- routeContext: Object.freeze({
147
- route,
148
- currentSurfaceId: computed(() => "admin")
149
- }),
150
- scopeParamValue: computed(() => ""),
151
- normalizedOwnershipFilter: "none",
152
- apiPath: computed(() => "/contacts"),
153
- queryKey: computed(() => ["contacts"]),
154
- queryCanRun(gate = true) {
155
- return computed(() => Boolean(unref(gate)));
156
- },
157
- permissionGate() {
158
- return computed(() => true);
159
- },
160
- loadError(error = "") {
161
- return computed(() => String(unref(error) || ""));
162
- },
163
- isLoading(loading = false) {
164
- return computed(() => Boolean(unref(loading)));
165
- }
166
- });
167
- }
168
- });
169
- }
170
-
171
131
  async function mountRouteSynchronizedList(initialLocation) {
172
132
  const calls = [];
173
133
  const client = {
@@ -206,7 +166,8 @@ async function mountRouteSynchronizedList(initialLocation) {
206
166
  setup() {
207
167
  filterRuntime = useCrudListFilters(CURRENTNESS_FILTERS);
208
168
  useList({
209
- adapter: createListTestAdapter(),
169
+ apiSuffix: "/contacts",
170
+ ownershipFilter: "public",
210
171
  client,
211
172
  queryKeyFactory: () => ["contacts"],
212
173
  queryParams: filterRuntime.queryParams,
@@ -260,7 +221,7 @@ test("route-synchronized lists make one initial request with the hydrated route
260
221
  assert.ok(signal instanceof AbortSignal);
261
222
  assert.equal(signal.aborted, false);
262
223
  assert.deepEqual(runtime.calls[0], {
263
- path: "/contacts",
224
+ path: "/api/contacts",
264
225
  options: {
265
226
  method: "GET",
266
227
  signal,
@@ -1,34 +0,0 @@
1
- import { useOperationScope } from "../internal/useOperationScope.js";
2
-
3
- const WEB_OPERATION_ADAPTER_ID = "http-web-default";
4
-
5
- function createWebOperationAdapter() {
6
- return Object.freeze({
7
- id: WEB_OPERATION_ADAPTER_ID,
8
- useOperationScope(options = {}) {
9
- return useOperationScope(options);
10
- }
11
- });
12
- }
13
-
14
- const defaultWebOperationAdapter = createWebOperationAdapter();
15
-
16
- function resolveOperationAdapter(adapter, { context = "http-web operation adapter" } = {}) {
17
- if (adapter == null) {
18
- return defaultWebOperationAdapter;
19
- }
20
-
21
- if (!adapter || typeof adapter !== "object" || Array.isArray(adapter)) {
22
- throw new TypeError(`${context} must be an object when provided.`);
23
- }
24
- if (typeof adapter.useOperationScope !== "function") {
25
- throw new TypeError(`${context} must expose useOperationScope(options).`);
26
- }
27
-
28
- return adapter;
29
- }
30
-
31
- export {
32
- createWebOperationAdapter,
33
- resolveOperationAdapter
34
- };