@salesforce/webapp-experimental 1.21.0 → 1.22.1

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.
@@ -7,7 +7,7 @@ export declare const API_VERSION: string;
7
7
  declare class ApiClient {
8
8
  private readonly baseURL;
9
9
  private readonly defaultOptions;
10
- constructor(baseURL: string, defaultOptions?: RequestInit);
10
+ constructor(baseURL: string);
11
11
  private isJsonContentType;
12
12
  private fetch;
13
13
  private processBody;
@@ -17,16 +17,6 @@ declare class ApiClient {
17
17
  patch(url: string, body?: unknown, options?: RequestInit): Promise<Response>;
18
18
  delete(url: string, options?: RequestInit): Promise<Response>;
19
19
  }
20
- export declare const baseClient: ApiClient;
21
- export declare const baseDataClient: ApiClient;
22
20
  export declare const uiApiClient: ApiClient;
23
- /**
24
- * Creates an ApiClient with the provided base URL and default options.
25
- *
26
- * @param baseURL - The base URL for the client
27
- * @param defaultOptions - Default RequestInit options
28
- * @returns A configured ApiClient
29
- */
30
- export declare function createClient(baseURL: string, defaultOptions?: RequestInit): ApiClient;
31
21
  export {};
32
22
  //# sourceMappingURL=clients.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"clients.d.ts","sourceRoot":"","sources":["../../src/api/clients.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH,eAAO,MAAM,WAAW,QAA+B,CAAC;AAmBxD,cAAM,SAAS;IACd,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAc;gBAEjC,OAAO,EAAE,MAAM,EAAE,cAAc,GAAE,WAAgB;IAY7D,OAAO,CAAC,iBAAiB;YASX,KAAK;IAsBnB,OAAO,CAAC,WAAW;IAgBb,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAQ/E,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAI9D,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAQ9E,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAQhF,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC;CAGvE;AAGD,eAAO,MAAM,UAAU,WAAyB,CAAC;AAGjD,eAAO,MAAM,cAAc,WAA8B,CAAC;AAG1D,eAAO,MAAM,WAAW,WAA2B,CAAC;AAEpD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,cAAc,GAAE,WAAgB,GAAG,SAAS,CAEzF"}
1
+ {"version":3,"file":"clients.d.ts","sourceRoot":"","sources":["../../src/api/clients.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,eAAO,MAAM,WAAW,QAA+B,CAAC;AAQxD,cAAM,SAAS;IACd,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAK7B;gBAEU,OAAO,EAAE,MAAM;IAI3B,OAAO,CAAC,iBAAiB;YASX,KAAK;IAenB,OAAO,CAAC,WAAW;IAgBb,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAQ/E,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAI9D,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAQ9E,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC;IAQhF,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC;CAGvE;AAGD,eAAO,MAAM,WAAW,WAA4B,CAAC"}
@@ -3,45 +3,31 @@
3
3
  * All rights reserved.
4
4
  * For full license text, see the LICENSE.txt file
5
5
  */
6
- import { ConduitClient } from "@conduit-client/salesforce-lightning-service-worker";
7
- const BASE_URL = __SF_SERVER_BASE_PATH__ || "";
6
+ import { sdk } from "@salesforce/sdk-data";
8
7
  // Project standard API version from environment variable with fallback
9
8
  export const API_VERSION = __SF_API_VERSION__ || "65.0";
10
- const BASE_SERVICES_URL = `${BASE_URL}/services`;
11
- const BASE_DATA_URL = `${BASE_SERVICES_URL}/data/v${API_VERSION}`;
9
+ const BASE_DATA_URL = `/services/data/v${API_VERSION}`;
12
10
  const UI_API_URL = `${BASE_DATA_URL}/ui-api`;
13
- const TOKEN_ENDPOINT = `${UI_API_URL}/session/csrf`;
14
- const CACHE_VERSION = 1;
15
- const CACHE_NAME = `vibe-coding-starter-${CACHE_VERSION}`;
16
- ConduitClient.initialize({
17
- csrf: {
18
- endpoint: TOKEN_ENDPOINT,
19
- cacheName: CACHE_NAME,
20
- protectedUrls: ["/services/data/v", "/lwr/apex/v"],
21
- },
22
- });
23
- const conduitClient = ConduitClient.instance();
11
+ const MIME_TYPE_JSON = "application/json";
12
+ const HEADER_CONTENT_TYPE = "Content-Type";
24
13
  class ApiClient {
25
14
  baseURL;
26
- defaultOptions;
27
- constructor(baseURL, defaultOptions = {}) {
15
+ defaultOptions = {
16
+ headers: {
17
+ [HEADER_CONTENT_TYPE]: MIME_TYPE_JSON,
18
+ Accept: MIME_TYPE_JSON,
19
+ },
20
+ };
21
+ constructor(baseURL) {
28
22
  this.baseURL = baseURL;
29
- this.defaultOptions = {
30
- ...defaultOptions,
31
- headers: {
32
- "Content-Type": "application/json",
33
- Accept: "application/json",
34
- ...(defaultOptions.headers || {}),
35
- },
36
- };
37
23
  }
38
24
  isJsonContentType(headers) {
39
25
  // default to JSON if no headers specified
40
26
  if (!headers)
41
27
  return true;
42
28
  const headersObj = new Headers(headers);
43
- const contentType = headersObj.get("content-type");
44
- return !contentType || contentType.includes("application/json");
29
+ const contentType = headersObj.get(HEADER_CONTENT_TYPE);
30
+ return !contentType || contentType.includes(MIME_TYPE_JSON);
45
31
  }
46
32
  async fetch(info, options = {}) {
47
33
  const fullURL = info instanceof URL || info.startsWith("http") ? info : `${this.baseURL}${info}`;
@@ -53,12 +39,7 @@ class ApiClient {
53
39
  ...(options.headers || {}),
54
40
  },
55
41
  };
56
- const response = await conduitClient.fetch(fullURL, mergedOptions);
57
- // handle 401/403 with page reload
58
- if (response.status === 401 || response.status === 403) {
59
- window.location.reload();
60
- }
61
- return response;
42
+ return await sdk.fetch(fullURL, mergedOptions);
62
43
  }
63
44
  processBody(body, options) {
64
45
  if (body === undefined || body === null) {
@@ -77,45 +58,31 @@ class ApiClient {
77
58
  }
78
59
  async post(url, body, options = {}) {
79
60
  return this.fetch(url, {
61
+ ...options,
80
62
  method: "POST",
81
63
  body: this.processBody(body, options),
82
- ...options,
83
64
  });
84
65
  }
85
66
  async get(url, options = {}) {
86
- return this.fetch(url, { method: "GET", ...options });
67
+ return this.fetch(url, { ...options, method: "GET" });
87
68
  }
88
69
  async put(url, body, options = {}) {
89
70
  return this.fetch(url, {
71
+ ...options,
90
72
  method: "PUT",
91
73
  body: this.processBody(body, options),
92
- ...options,
93
74
  });
94
75
  }
95
76
  async patch(url, body, options = {}) {
96
77
  return this.fetch(url, {
78
+ ...options,
97
79
  method: "PATCH",
98
80
  body: this.processBody(body, options),
99
- ...options,
100
81
  });
101
82
  }
102
83
  async delete(url, options = {}) {
103
- return this.fetch(url, { method: "DELETE", ...options });
84
+ return this.fetch(url, { ...options, method: "DELETE" });
104
85
  }
105
86
  }
106
- // Base fetch client
107
- export const baseClient = createClient(BASE_URL);
108
- // Extension of the base client that by default points to this project's standard version of the Salesforce API
109
- export const baseDataClient = createClient(BASE_DATA_URL);
110
87
  // Client for Salesforce UI API access
111
- export const uiApiClient = createClient(UI_API_URL);
112
- /**
113
- * Creates an ApiClient with the provided base URL and default options.
114
- *
115
- * @param baseURL - The base URL for the client
116
- * @param defaultOptions - Default RequestInit options
117
- * @returns A configured ApiClient
118
- */
119
- export function createClient(baseURL, defaultOptions = {}) {
120
- return new ApiClient(baseURL, defaultOptions);
121
- }
88
+ export const uiApiClient = new ApiClient(UI_API_URL);
@@ -3,18 +3,17 @@
3
3
  * All rights reserved.
4
4
  * For full license text, see the LICENSE.txt file
5
5
  */
6
- // @vitest-environment jsdom
7
- import { ConduitClient } from "@conduit-client/salesforce-lightning-service-worker";
6
+ import { sdk } from "@salesforce/sdk-data";
8
7
  import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
9
- import { createClient, baseClient, baseDataClient, uiApiClient } from "./clients.js";
10
- vi.mock("@conduit-client/salesforce-lightning-service-worker", () => {
8
+ import { uiApiClient } from "./clients.js";
9
+ vi.mock("@salesforce/sdk-data", () => {
11
10
  return {
12
- ConduitClient: {
13
- initialize: vi.fn(),
14
- instance: vi.fn().mockReturnValue({ fetch: vi.fn() }),
11
+ sdk: {
12
+ fetch: vi.fn(),
15
13
  },
16
14
  };
17
15
  });
16
+ vi.stubGlobal("window", {});
18
17
  // Mock window.location.reload
19
18
  const mockReload = vi.fn();
20
19
  Object.defineProperty(window, "location", {
@@ -24,219 +23,145 @@ Object.defineProperty(window, "location", {
24
23
  describe("clients", () => {
25
24
  let mockFetch;
26
25
  beforeEach(() => {
27
- const mockClient = vi.mocked(ConduitClient.instance());
28
- mockFetch = mockClient.fetch;
26
+ mockFetch = vi.mocked(sdk.fetch);
29
27
  mockFetch.mockClear();
30
28
  mockReload.mockClear();
31
29
  });
32
30
  afterEach(() => {
33
31
  vi.clearAllMocks();
34
32
  });
35
- it("should have configured ConduitClient", () => {
36
- expect(ConduitClient.initialize).toHaveBeenCalledWith({
37
- csrf: {
38
- endpoint: "/services/data/v99.0/ui-api/session/csrf",
39
- cacheName: "vibe-coding-starter-1",
40
- protectedUrls: ["/services/data/v", "/lwr/apex/v"],
41
- },
42
- });
43
- });
44
- describe("constructor and default options", () => {
45
- it("should create client with default JSON headers", () => {
46
- const client = createClient("/api");
47
- expect(client).toBeDefined();
48
- });
33
+ describe("uiApiClient", () => {
49
34
  it("should merge custom headers with defaults", async () => {
50
- const client = createClient("/api", {
51
- headers: { "Custom-Header": "value" },
52
- });
53
35
  mockFetch.mockResolvedValueOnce({
54
36
  status: 200,
55
37
  json: () => Promise.resolve({ success: true }),
56
38
  });
57
- await client.get("/test");
58
- expect(mockFetch).toHaveBeenCalledWith("/api/test", {
59
- method: "GET",
60
- headers: {
61
- "Content-Type": "application/json",
62
- Accept: "application/json",
63
- "Custom-Header": "value",
64
- },
65
- });
66
- });
67
- });
68
- describe("URL construction", () => {
69
- it("should combine base URL with relative paths", async () => {
70
- const client = createClient("/api/v1");
71
- mockFetch.mockResolvedValueOnce({
72
- status: 200,
73
- json: () => Promise.resolve({}),
74
- });
75
- await client.get("/users");
76
- expect(mockFetch).toHaveBeenCalledWith("/api/v1/users", expect.any(Object));
77
- });
78
- it("should use absolute URLs as-is", async () => {
79
- const client = createClient("/api");
80
- mockFetch.mockResolvedValueOnce({
81
- status: 200,
82
- json: () => Promise.resolve({}),
83
- });
84
- await client.get("https://external.com/data");
85
- expect(mockFetch).toHaveBeenCalledWith("https://external.com/data", expect.any(Object));
86
- });
87
- });
88
- describe("HTTP methods", () => {
89
- let client;
90
- beforeEach(() => {
91
- client = createClient("/api");
92
- mockFetch.mockResolvedValue({
93
- status: 200,
94
- json: () => Promise.resolve({ success: true }),
39
+ await uiApiClient.get("/test", {
40
+ headers: { "Custom-Header": "value" },
95
41
  });
96
- });
97
- it("should make GET requests", async () => {
98
- await client.get("/users");
99
- expect(mockFetch).toHaveBeenCalledWith("/api/users", {
42
+ expect(mockFetch).toHaveBeenCalledWith("/services/data/v99.0/ui-api/test", {
100
43
  method: "GET",
101
44
  headers: {
102
45
  "Content-Type": "application/json",
103
46
  Accept: "application/json",
47
+ "Custom-Header": "value",
104
48
  },
105
49
  });
106
50
  });
107
- it("should make POST requests with JSON body", async () => {
108
- const data = { name: "John" };
109
- await client.post("/users", data);
110
- expect(mockFetch).toHaveBeenCalledWith("/api/users", {
111
- method: "POST",
112
- body: JSON.stringify(data),
113
- headers: {
114
- "Content-Type": "application/json",
115
- Accept: "application/json",
116
- },
117
- });
118
- });
119
- it("should make PUT requests with JSON body", async () => {
120
- const data = { name: "John Updated" };
121
- await client.put("/users/1", data);
122
- expect(mockFetch).toHaveBeenCalledWith("/api/users/1", {
123
- method: "PUT",
124
- body: JSON.stringify(data),
125
- headers: {
126
- "Content-Type": "application/json",
127
- Accept: "application/json",
128
- },
129
- });
130
- });
131
- it("should make PATCH requests with JSON body", async () => {
132
- const data = { name: "John Patched" };
133
- await client.patch("/users/1", data);
134
- expect(mockFetch).toHaveBeenCalledWith("/api/users/1", {
135
- method: "PATCH",
136
- body: JSON.stringify(data),
137
- headers: {
138
- "Content-Type": "application/json",
139
- Accept: "application/json",
140
- },
141
- });
142
- });
143
- it("should make DELETE requests", async () => {
144
- await client.delete("/users/1");
145
- expect(mockFetch).toHaveBeenCalledWith("/api/users/1", {
146
- method: "DELETE",
147
- headers: {
148
- "Content-Type": "application/json",
149
- Accept: "application/json",
150
- },
151
- });
152
- });
153
- });
154
- describe("body processing", () => {
155
- let client;
156
- beforeEach(() => {
157
- client = createClient("/api");
158
- mockFetch.mockResolvedValue({
159
- status: 200,
160
- json: () => Promise.resolve({}),
161
- });
162
- });
163
- it("should stringify object bodies for JSON content-type", async () => {
164
- const data = { name: "John" };
165
- await client.post("/users", data);
166
- expect(mockFetch).toHaveBeenCalledWith("/api/users", expect.objectContaining({
167
- body: JSON.stringify(data),
168
- }));
169
- });
170
- it("should not stringify non-object bodies", async () => {
171
- const formData = new FormData();
172
- await client.post("/upload", formData);
173
- expect(mockFetch).toHaveBeenCalledWith("/api/upload", expect.objectContaining({
174
- body: formData,
175
- }));
176
- });
177
- it("should not stringify when content-type is not JSON", async () => {
178
- const data = { name: "John" };
179
- await client.post("/users", data, {
180
- headers: { "Content-Type": "multipart/form-data" },
181
- });
182
- // When content-type is not JSON and body is a plain object (not FormData/string),
183
- // processBody returns undefined since it can't serialize non-JSON content
184
- expect(mockFetch).toHaveBeenCalledWith("/api/users", expect.objectContaining({
185
- body: undefined,
186
- }));
187
- });
188
- });
189
- describe("error handling", () => {
190
- let client;
191
- beforeEach(() => {
192
- client = createClient("/api");
193
- });
194
- it("should reload page on 401 status", async () => {
195
- mockFetch.mockResolvedValueOnce({
196
- status: 401,
197
- json: () => Promise.resolve({}),
198
- });
199
- await client.get("/protected");
200
- expect(mockReload).toHaveBeenCalled();
201
- });
202
- it("should reload page on 403 status", async () => {
203
- mockFetch.mockResolvedValueOnce({
204
- status: 403,
205
- json: () => Promise.resolve({}),
206
- });
207
- await client.get("/protected");
208
- expect(mockReload).toHaveBeenCalled();
209
- });
210
- it("should not reload page on other status codes", async () => {
211
- mockFetch.mockResolvedValueOnce({
212
- status: 404,
213
- json: () => Promise.resolve({}),
214
- });
215
- await client.get("/notfound");
216
- expect(mockReload).not.toHaveBeenCalled();
217
- });
218
- });
219
- describe("exported clients", () => {
220
- it("should export baseClient with correct base URL", () => {
221
- expect(baseClient).toBeDefined();
222
- });
223
- it("should export baseDataClient with correct base URL", () => {
224
- expect(baseDataClient).toBeDefined();
225
- });
226
- it("should export uiApiClient with correct base URL", () => {
227
- expect(uiApiClient).toBeDefined();
228
- });
229
- });
230
- describe("createClient factory", () => {
231
- it("should create client with provided base URL", () => {
232
- const client = createClient("/custom");
233
- expect(client).toBeDefined();
234
- });
235
- it("should create client with default options", () => {
236
- const client = createClient("/api", {
237
- headers: { Authorization: "Bearer token" },
51
+ describe("URL construction", () => {
52
+ it("should combine base URL with relative paths", async () => {
53
+ mockFetch.mockResolvedValueOnce({
54
+ status: 200,
55
+ json: () => Promise.resolve({}),
56
+ });
57
+ await uiApiClient.get("/users");
58
+ expect(mockFetch).toHaveBeenCalledWith("/services/data/v99.0/ui-api/users", expect.any(Object));
59
+ });
60
+ it("should use absolute URLs as-is", async () => {
61
+ mockFetch.mockResolvedValueOnce({
62
+ status: 200,
63
+ json: () => Promise.resolve({}),
64
+ });
65
+ await uiApiClient.get("https://external.com/data");
66
+ expect(mockFetch).toHaveBeenCalledWith("https://external.com/data", expect.any(Object));
67
+ });
68
+ });
69
+ describe("HTTP methods", () => {
70
+ beforeEach(() => {
71
+ mockFetch.mockResolvedValue({
72
+ status: 200,
73
+ json: () => Promise.resolve({ success: true }),
74
+ });
75
+ });
76
+ it("should make GET requests", async () => {
77
+ await uiApiClient.get("/users");
78
+ expect(mockFetch).toHaveBeenCalledWith("/services/data/v99.0/ui-api/users", {
79
+ method: "GET",
80
+ headers: {
81
+ "Content-Type": "application/json",
82
+ Accept: "application/json",
83
+ },
84
+ });
85
+ });
86
+ it("should make POST requests with JSON body", async () => {
87
+ const data = { name: "John" };
88
+ await uiApiClient.post("/users", data);
89
+ expect(mockFetch).toHaveBeenCalledWith("/services/data/v99.0/ui-api/users", {
90
+ method: "POST",
91
+ body: JSON.stringify(data),
92
+ headers: {
93
+ "Content-Type": "application/json",
94
+ Accept: "application/json",
95
+ },
96
+ });
97
+ });
98
+ it("should make PUT requests with JSON body", async () => {
99
+ const data = { name: "John Updated" };
100
+ await uiApiClient.put("/users/1", data);
101
+ expect(mockFetch).toHaveBeenCalledWith("/services/data/v99.0/ui-api/users/1", {
102
+ method: "PUT",
103
+ body: JSON.stringify(data),
104
+ headers: {
105
+ "Content-Type": "application/json",
106
+ Accept: "application/json",
107
+ },
108
+ });
109
+ });
110
+ it("should make PATCH requests with JSON body", async () => {
111
+ const data = { name: "John Patched" };
112
+ await uiApiClient.patch("/users/1", data);
113
+ expect(mockFetch).toHaveBeenCalledWith("/services/data/v99.0/ui-api/users/1", {
114
+ method: "PATCH",
115
+ body: JSON.stringify(data),
116
+ headers: {
117
+ "Content-Type": "application/json",
118
+ Accept: "application/json",
119
+ },
120
+ });
121
+ });
122
+ it("should make DELETE requests", async () => {
123
+ await uiApiClient.delete("/users/1");
124
+ expect(mockFetch).toHaveBeenCalledWith("/services/data/v99.0/ui-api/users/1", {
125
+ method: "DELETE",
126
+ headers: {
127
+ "Content-Type": "application/json",
128
+ Accept: "application/json",
129
+ },
130
+ });
131
+ });
132
+ });
133
+ describe("body processing", () => {
134
+ beforeEach(() => {
135
+ mockFetch.mockResolvedValue({
136
+ status: 200,
137
+ json: () => Promise.resolve({}),
138
+ });
139
+ });
140
+ it("should stringify object bodies for JSON content-type", async () => {
141
+ const data = { name: "John" };
142
+ await uiApiClient.post("/users", data);
143
+ expect(mockFetch).toHaveBeenCalledWith("/services/data/v99.0/ui-api/users", expect.objectContaining({
144
+ body: JSON.stringify(data),
145
+ }));
146
+ });
147
+ it("should not stringify non-object bodies", async () => {
148
+ const formData = new FormData();
149
+ await uiApiClient.post("/upload", formData);
150
+ expect(mockFetch).toHaveBeenCalledWith("/services/data/v99.0/ui-api/upload", expect.objectContaining({
151
+ body: formData,
152
+ }));
153
+ });
154
+ it("should not stringify when content-type is not JSON", async () => {
155
+ const data = { name: "John" };
156
+ await uiApiClient.post("/users", data, {
157
+ headers: { "Content-Type": "multipart/form-data" },
158
+ });
159
+ // When content-type is not JSON and body is a plain object (not FormData/string),
160
+ // processBody returns undefined since it can't serialize non-JSON content
161
+ expect(mockFetch).toHaveBeenCalledWith("/services/data/v99.0/ui-api/users", expect.objectContaining({
162
+ body: undefined,
163
+ }));
238
164
  });
239
- expect(client).toBeDefined();
240
165
  });
241
166
  });
242
167
  });