@markwharton/pwa-core 1.7.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/dist/{client/api.d.ts → client.d.ts} +85 -9
  2. package/dist/{client/api.js → client.js} +159 -56
  3. package/dist/index.d.ts +10 -2
  4. package/dist/index.js +14 -6
  5. package/dist/server.d.ts +283 -0
  6. package/dist/server.js +476 -0
  7. package/dist/shared.d.ts +150 -0
  8. package/dist/shared.js +124 -0
  9. package/package.json +11 -12
  10. package/dist/__tests__/auth/apiKey.test.d.ts +0 -1
  11. package/dist/__tests__/auth/apiKey.test.js +0 -80
  12. package/dist/__tests__/auth/token.test.d.ts +0 -1
  13. package/dist/__tests__/auth/token.test.js +0 -212
  14. package/dist/__tests__/auth/types.test.d.ts +0 -1
  15. package/dist/__tests__/auth/types.test.js +0 -77
  16. package/dist/__tests__/client/api.test.d.ts +0 -1
  17. package/dist/__tests__/client/api.test.js +0 -369
  18. package/dist/__tests__/client/apiError.test.d.ts +0 -1
  19. package/dist/__tests__/client/apiError.test.js +0 -91
  20. package/dist/__tests__/http/responses.test.d.ts +0 -1
  21. package/dist/__tests__/http/responses.test.js +0 -112
  22. package/dist/__tests__/http/status.test.d.ts +0 -1
  23. package/dist/__tests__/http/status.test.js +0 -27
  24. package/dist/__tests__/server/auth/apiKey.test.d.ts +0 -1
  25. package/dist/__tests__/server/auth/apiKey.test.js +0 -80
  26. package/dist/__tests__/server/auth/token.test.d.ts +0 -1
  27. package/dist/__tests__/server/auth/token.test.js +0 -299
  28. package/dist/__tests__/server/http/responses.test.d.ts +0 -1
  29. package/dist/__tests__/server/http/responses.test.js +0 -112
  30. package/dist/__tests__/server/storage/client.test.d.ts +0 -1
  31. package/dist/__tests__/server/storage/client.test.js +0 -173
  32. package/dist/__tests__/server/storage/keys.test.d.ts +0 -1
  33. package/dist/__tests__/server/storage/keys.test.js +0 -47
  34. package/dist/__tests__/shared/auth/types.test.d.ts +0 -1
  35. package/dist/__tests__/shared/auth/types.test.js +0 -77
  36. package/dist/__tests__/shared/http/status.test.d.ts +0 -1
  37. package/dist/__tests__/shared/http/status.test.js +0 -29
  38. package/dist/__tests__/storage/client.test.d.ts +0 -1
  39. package/dist/__tests__/storage/client.test.js +0 -173
  40. package/dist/__tests__/storage/keys.test.d.ts +0 -1
  41. package/dist/__tests__/storage/keys.test.js +0 -47
  42. package/dist/__tests__/types.test.d.ts +0 -1
  43. package/dist/__tests__/types.test.js +0 -56
  44. package/dist/auth/apiKey.d.ts +0 -44
  45. package/dist/auth/apiKey.js +0 -59
  46. package/dist/auth/index.d.ts +0 -3
  47. package/dist/auth/index.js +0 -22
  48. package/dist/auth/token.d.ts +0 -56
  49. package/dist/auth/token.js +0 -104
  50. package/dist/auth/types.d.ts +0 -63
  51. package/dist/auth/types.js +0 -41
  52. package/dist/client/apiError.d.ts +0 -48
  53. package/dist/client/apiError.js +0 -65
  54. package/dist/client/index.d.ts +0 -3
  55. package/dist/client/index.js +0 -14
  56. package/dist/client/types.d.ts +0 -12
  57. package/dist/client/types.js +0 -5
  58. package/dist/http/index.d.ts +0 -3
  59. package/dist/http/index.js +0 -14
  60. package/dist/http/responses.d.ts +0 -82
  61. package/dist/http/responses.js +0 -132
  62. package/dist/http/status.d.ts +0 -17
  63. package/dist/http/status.js +0 -19
  64. package/dist/http/types.d.ts +0 -10
  65. package/dist/http/types.js +0 -5
  66. package/dist/server/auth/apiKey.d.ts +0 -44
  67. package/dist/server/auth/apiKey.js +0 -59
  68. package/dist/server/auth/index.d.ts +0 -3
  69. package/dist/server/auth/index.js +0 -19
  70. package/dist/server/auth/token.d.ts +0 -102
  71. package/dist/server/auth/token.js +0 -158
  72. package/dist/server/http/index.d.ts +0 -1
  73. package/dist/server/http/index.js +0 -12
  74. package/dist/server/http/responses.d.ts +0 -82
  75. package/dist/server/http/responses.js +0 -132
  76. package/dist/server/index.d.ts +0 -4
  77. package/dist/server/index.js +0 -37
  78. package/dist/server/storage/client.d.ts +0 -48
  79. package/dist/server/storage/client.js +0 -107
  80. package/dist/server/storage/index.d.ts +0 -2
  81. package/dist/server/storage/index.js +0 -11
  82. package/dist/server/storage/keys.d.ts +0 -8
  83. package/dist/server/storage/keys.js +0 -14
  84. package/dist/shared/auth/index.d.ts +0 -2
  85. package/dist/shared/auth/index.js +0 -7
  86. package/dist/shared/auth/types.d.ts +0 -63
  87. package/dist/shared/auth/types.js +0 -41
  88. package/dist/shared/http/index.d.ts +0 -3
  89. package/dist/shared/http/index.js +0 -5
  90. package/dist/shared/http/status.d.ts +0 -19
  91. package/dist/shared/http/status.js +0 -21
  92. package/dist/shared/http/types.d.ts +0 -10
  93. package/dist/shared/http/types.js +0 -5
  94. package/dist/shared/index.d.ts +0 -5
  95. package/dist/shared/index.js +0 -10
  96. package/dist/storage/client.d.ts +0 -48
  97. package/dist/storage/client.js +0 -107
  98. package/dist/storage/index.d.ts +0 -2
  99. package/dist/storage/index.js +0 -11
  100. package/dist/storage/keys.d.ts +0 -8
  101. package/dist/storage/keys.js +0 -14
  102. package/dist/types.d.ts +0 -48
  103. package/dist/types.js +0 -41
@@ -1,11 +1,80 @@
1
- import { ApiResponse } from './types';
1
+ /**
2
+ * pwa-core/client - Browser-side API client utilities
3
+ *
4
+ * This module has NO Node.js dependencies and is designed for browser use.
5
+ */
6
+ /**
7
+ * API response wrapper for safe calls
8
+ */
9
+ export interface ApiResponse<T> {
10
+ ok: boolean;
11
+ status: number;
12
+ data?: T;
13
+ error?: string;
14
+ }
15
+ /**
16
+ * Configuration for initApiClient
17
+ */
18
+ export interface ApiClientConfig {
19
+ /** Function that returns the current auth token (or null) */
20
+ getToken: () => string | null;
21
+ /** Optional callback for 401 responses (e.g., redirect to login) */
22
+ onUnauthorized?: () => void;
23
+ /** Optional request timeout in milliseconds (default: 30000) */
24
+ timeout?: number;
25
+ }
26
+ /**
27
+ * Custom error class for API errors.
28
+ * Preserves HTTP status code and error message from the server.
29
+ * @example
30
+ * try {
31
+ * await apiGet('/users/123');
32
+ * } catch (error) {
33
+ * if (error instanceof ApiError && error.isNotFound()) {
34
+ * console.log('User not found');
35
+ * }
36
+ * }
37
+ */
38
+ export declare class ApiError extends Error {
39
+ status: number;
40
+ details?: string | undefined;
41
+ /**
42
+ * Creates a new ApiError instance.
43
+ * @param status - The HTTP status code
44
+ * @param message - The error message
45
+ * @param details - Optional additional error details
46
+ */
47
+ constructor(status: number, message: string, details?: string | undefined);
48
+ /**
49
+ * Checks if this is a 401 Unauthorized error.
50
+ * @returns True if status is 401
51
+ */
52
+ isUnauthorized(): boolean;
53
+ /**
54
+ * Checks if this is a 404 Not Found error.
55
+ * @returns True if status is 404
56
+ */
57
+ isNotFound(): boolean;
58
+ /**
59
+ * Checks if this is a 400 Bad Request error.
60
+ * @returns True if status is 400
61
+ */
62
+ isBadRequest(): boolean;
63
+ /**
64
+ * Checks if this is a client error (4xx status).
65
+ * @returns True if status is 400-499
66
+ */
67
+ isClientError(): boolean;
68
+ /**
69
+ * Checks if this is a server error (5xx status).
70
+ * @returns True if status is 500-599
71
+ */
72
+ isServerError(): boolean;
73
+ }
2
74
  /**
3
75
  * Initializes the API client with token retrieval and optional configuration.
4
76
  * Call once at application startup.
5
77
  * @param config - Client configuration
6
- * @param config.getToken - Function that returns the current auth token (or null)
7
- * @param config.onUnauthorized - Optional callback for 401 responses (e.g., redirect to login)
8
- * @param config.timeout - Optional request timeout in milliseconds (default: 30000)
9
78
  * @example
10
79
  * initApiClient({
11
80
  * getToken: () => localStorage.getItem('token'),
@@ -13,11 +82,7 @@ import { ApiResponse } from './types';
13
82
  * timeout: 60000 // 60 seconds
14
83
  * });
15
84
  */
16
- export declare function initApiClient(config: {
17
- getToken: () => string | null;
18
- onUnauthorized?: () => void;
19
- timeout?: number;
20
- }): void;
85
+ export declare function initApiClient(config: ApiClientConfig): void;
21
86
  /**
22
87
  * Makes an authenticated API call. Throws ApiError on non-2xx responses.
23
88
  * @typeParam T - The expected response data type
@@ -82,6 +147,17 @@ export declare function apiPatch<T>(url: string, body?: unknown): Promise<T>;
82
147
  * await apiDelete('/api/users/123');
83
148
  */
84
149
  export declare function apiDelete<T>(url: string): Promise<T>;
150
+ /**
151
+ * Makes an authenticated API call expecting no response body.
152
+ * Use for DELETE, POST, or PUT actions that return 204 No Content.
153
+ * @param url - The API endpoint URL
154
+ * @param options - Optional fetch options (method, body, headers)
155
+ * @throws ApiError on non-2xx HTTP status or timeout
156
+ * @example
157
+ * await apiCallVoid('/api/users/123', { method: 'DELETE' });
158
+ * await apiCallVoid('/api/action', { method: 'POST' });
159
+ */
160
+ export declare function apiCallVoid(url: string, options?: RequestInit): Promise<void>;
85
161
  /**
86
162
  * Makes an authenticated API call with Result-style error handling.
87
163
  * Unlike apiCall, this never throws - errors are returned in the response.
@@ -1,5 +1,11 @@
1
1
  "use strict";
2
+ /**
3
+ * pwa-core/client - Browser-side API client utilities
4
+ *
5
+ * This module has NO Node.js dependencies and is designed for browser use.
6
+ */
2
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.ApiError = void 0;
3
9
  exports.initApiClient = initApiClient;
4
10
  exports.apiCall = apiCall;
5
11
  exports.apiGet = apiGet;
@@ -7,24 +13,130 @@ exports.apiPost = apiPost;
7
13
  exports.apiPut = apiPut;
8
14
  exports.apiPatch = apiPatch;
9
15
  exports.apiDelete = apiDelete;
16
+ exports.apiCallVoid = apiCallVoid;
10
17
  exports.apiCallSafe = apiCallSafe;
11
- const apiError_1 = require("./apiError");
18
+ // =============================================================================
19
+ // ApiError Class
20
+ // =============================================================================
12
21
  /**
13
- * Client-side API utilities for authenticated requests
22
+ * Custom error class for API errors.
23
+ * Preserves HTTP status code and error message from the server.
24
+ * @example
25
+ * try {
26
+ * await apiGet('/users/123');
27
+ * } catch (error) {
28
+ * if (error instanceof ApiError && error.isNotFound()) {
29
+ * console.log('User not found');
30
+ * }
31
+ * }
14
32
  */
33
+ class ApiError extends Error {
34
+ /**
35
+ * Creates a new ApiError instance.
36
+ * @param status - The HTTP status code
37
+ * @param message - The error message
38
+ * @param details - Optional additional error details
39
+ */
40
+ constructor(status, message, details) {
41
+ super(message);
42
+ this.status = status;
43
+ this.details = details;
44
+ this.name = 'ApiError';
45
+ }
46
+ /**
47
+ * Checks if this is a 401 Unauthorized error.
48
+ * @returns True if status is 401
49
+ */
50
+ isUnauthorized() {
51
+ return this.status === 401;
52
+ }
53
+ /**
54
+ * Checks if this is a 404 Not Found error.
55
+ * @returns True if status is 404
56
+ */
57
+ isNotFound() {
58
+ return this.status === 404;
59
+ }
60
+ /**
61
+ * Checks if this is a 400 Bad Request error.
62
+ * @returns True if status is 400
63
+ */
64
+ isBadRequest() {
65
+ return this.status === 400;
66
+ }
67
+ /**
68
+ * Checks if this is a client error (4xx status).
69
+ * @returns True if status is 400-499
70
+ */
71
+ isClientError() {
72
+ return this.status >= 400 && this.status < 500;
73
+ }
74
+ /**
75
+ * Checks if this is a server error (5xx status).
76
+ * @returns True if status is 500-599
77
+ */
78
+ isServerError() {
79
+ return this.status >= 500 && this.status < 600;
80
+ }
81
+ }
82
+ exports.ApiError = ApiError;
83
+ // =============================================================================
84
+ // Module State
85
+ // =============================================================================
15
86
  // Token getter function - set by consuming app
16
87
  let getToken = null;
17
88
  // Callback for 401 responses (e.g., redirect to login)
18
89
  let onUnauthorized = null;
19
90
  // Request timeout in milliseconds (default: 30 seconds)
20
91
  let requestTimeout = 30000;
92
+ // =============================================================================
93
+ // Internal Helpers
94
+ // =============================================================================
95
+ /** Prepare headers with optional auth token */
96
+ function prepareHeaders(options) {
97
+ const token = getToken?.();
98
+ const headers = {
99
+ 'Content-Type': 'application/json',
100
+ ...options.headers
101
+ };
102
+ if (token) {
103
+ headers['Authorization'] = `Bearer ${token}`;
104
+ }
105
+ return headers;
106
+ }
107
+ /** Extract error message from failed response */
108
+ async function extractErrorMessage(response) {
109
+ try {
110
+ const errorData = (await response.json());
111
+ return errorData.error || 'Request failed';
112
+ }
113
+ catch {
114
+ return 'Request failed';
115
+ }
116
+ }
117
+ /** Handle 401 unauthorized callback */
118
+ function handleUnauthorized(status) {
119
+ if (status === 401 && onUnauthorized) {
120
+ onUnauthorized();
121
+ }
122
+ }
123
+ /** Handle catch block for throwing API functions */
124
+ function handleApiError(error) {
125
+ if (error instanceof ApiError) {
126
+ throw error;
127
+ }
128
+ if (error instanceof Error && error.name === 'AbortError') {
129
+ throw new ApiError(0, 'Request timeout');
130
+ }
131
+ throw error;
132
+ }
133
+ // =============================================================================
134
+ // Public API
135
+ // =============================================================================
21
136
  /**
22
137
  * Initializes the API client with token retrieval and optional configuration.
23
138
  * Call once at application startup.
24
139
  * @param config - Client configuration
25
- * @param config.getToken - Function that returns the current auth token (or null)
26
- * @param config.onUnauthorized - Optional callback for 401 responses (e.g., redirect to login)
27
- * @param config.timeout - Optional request timeout in milliseconds (default: 30000)
28
140
  * @example
29
141
  * initApiClient({
30
142
  * getToken: () => localStorage.getItem('token'),
@@ -48,15 +160,7 @@ function initApiClient(config) {
48
160
  * const user = await apiCall<User>('/api/users/123');
49
161
  */
50
162
  async function apiCall(url, options = {}) {
51
- const token = getToken?.();
52
- const headers = {
53
- 'Content-Type': 'application/json',
54
- ...options.headers
55
- };
56
- if (token) {
57
- headers['Authorization'] = `Bearer ${token}`;
58
- }
59
- // Setup timeout with AbortController
163
+ const headers = prepareHeaders(options);
60
164
  const controller = new AbortController();
61
165
  const timeoutId = setTimeout(() => controller.abort(), requestTimeout);
62
166
  try {
@@ -66,34 +170,18 @@ async function apiCall(url, options = {}) {
66
170
  signal: controller.signal
67
171
  });
68
172
  if (!response.ok) {
69
- if (response.status === 401 && onUnauthorized) {
70
- onUnauthorized();
71
- }
72
- let errorMessage = 'Request failed';
73
- try {
74
- const errorData = (await response.json());
75
- errorMessage = errorData.error || errorMessage;
76
- }
77
- catch {
78
- // Ignore JSON parse errors
79
- }
80
- throw new apiError_1.ApiError(response.status, errorMessage);
173
+ handleUnauthorized(response.status);
174
+ const errorMessage = await extractErrorMessage(response);
175
+ throw new ApiError(response.status, errorMessage);
81
176
  }
82
- // Handle empty responses
83
177
  const text = await response.text();
84
178
  if (!text) {
85
- return {};
179
+ throw new ApiError(response.status, 'Empty response body');
86
180
  }
87
181
  return JSON.parse(text);
88
182
  }
89
183
  catch (error) {
90
- if (error instanceof apiError_1.ApiError) {
91
- throw error;
92
- }
93
- if (error instanceof Error && error.name === 'AbortError') {
94
- throw new apiError_1.ApiError(0, 'Request timeout');
95
- }
96
- throw error;
184
+ handleApiError(error);
97
185
  }
98
186
  finally {
99
187
  clearTimeout(timeoutId);
@@ -171,6 +259,39 @@ async function apiPatch(url, body) {
171
259
  async function apiDelete(url) {
172
260
  return apiCall(url, { method: 'DELETE' });
173
261
  }
262
+ /**
263
+ * Makes an authenticated API call expecting no response body.
264
+ * Use for DELETE, POST, or PUT actions that return 204 No Content.
265
+ * @param url - The API endpoint URL
266
+ * @param options - Optional fetch options (method, body, headers)
267
+ * @throws ApiError on non-2xx HTTP status or timeout
268
+ * @example
269
+ * await apiCallVoid('/api/users/123', { method: 'DELETE' });
270
+ * await apiCallVoid('/api/action', { method: 'POST' });
271
+ */
272
+ async function apiCallVoid(url, options = {}) {
273
+ const headers = prepareHeaders(options);
274
+ const controller = new AbortController();
275
+ const timeoutId = setTimeout(() => controller.abort(), requestTimeout);
276
+ try {
277
+ const response = await fetch(url, {
278
+ ...options,
279
+ headers,
280
+ signal: controller.signal
281
+ });
282
+ if (!response.ok) {
283
+ handleUnauthorized(response.status);
284
+ const errorMessage = await extractErrorMessage(response);
285
+ throw new ApiError(response.status, errorMessage);
286
+ }
287
+ }
288
+ catch (error) {
289
+ handleApiError(error);
290
+ }
291
+ finally {
292
+ clearTimeout(timeoutId);
293
+ }
294
+ }
174
295
  /**
175
296
  * Makes an authenticated API call with Result-style error handling.
176
297
  * Unlike apiCall, this never throws - errors are returned in the response.
@@ -188,15 +309,7 @@ async function apiDelete(url) {
188
309
  * }
189
310
  */
190
311
  async function apiCallSafe(url, options = {}) {
191
- const token = getToken?.();
192
- const headers = {
193
- 'Content-Type': 'application/json',
194
- ...options.headers
195
- };
196
- if (token) {
197
- headers['Authorization'] = `Bearer ${token}`;
198
- }
199
- // Setup timeout with AbortController
312
+ const headers = prepareHeaders(options);
200
313
  const controller = new AbortController();
201
314
  const timeoutId = setTimeout(() => controller.abort(), requestTimeout);
202
315
  try {
@@ -206,20 +319,10 @@ async function apiCallSafe(url, options = {}) {
206
319
  signal: controller.signal
207
320
  });
208
321
  if (!response.ok) {
209
- if (response.status === 401 && onUnauthorized) {
210
- onUnauthorized();
211
- }
212
- let errorMessage = 'Request failed';
213
- try {
214
- const errorData = (await response.json());
215
- errorMessage = errorData.error || errorMessage;
216
- }
217
- catch {
218
- // Ignore JSON parse errors
219
- }
322
+ handleUnauthorized(response.status);
323
+ const errorMessage = await extractErrorMessage(response);
220
324
  return { ok: false, status: response.status, error: errorMessage };
221
325
  }
222
- // Handle empty responses
223
326
  const text = await response.text();
224
327
  const data = text ? JSON.parse(text) : undefined;
225
328
  return { ok: true, status: response.status, data };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,11 @@
1
- export * from './types';
2
- export * from './server';
1
+ /**
2
+ * pwa-core - Shared patterns for Azure PWA projects
3
+ *
4
+ * Import paths:
5
+ * - '@markwharton/pwa-core/shared' - Types, HTTP_STATUS, Result pattern (browser-safe)
6
+ * - '@markwharton/pwa-core/server' - JWT, storage, HTTP responses (Node.js only)
7
+ * - '@markwharton/pwa-core/client' - API client utilities (browser only)
8
+ * - '@markwharton/pwa-core' - Server + shared (for convenience in Node.js)
9
+ */
3
10
  export * from './shared';
11
+ export * from './server';
package/dist/index.js CHANGED
@@ -1,4 +1,13 @@
1
1
  "use strict";
2
+ /**
3
+ * pwa-core - Shared patterns for Azure PWA projects
4
+ *
5
+ * Import paths:
6
+ * - '@markwharton/pwa-core/shared' - Types, HTTP_STATUS, Result pattern (browser-safe)
7
+ * - '@markwharton/pwa-core/server' - JWT, storage, HTTP responses (Node.js only)
8
+ * - '@markwharton/pwa-core/client' - API client utilities (browser only)
9
+ * - '@markwharton/pwa-core' - Server + shared (for convenience in Node.js)
10
+ */
2
11
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
12
  if (k2 === undefined) k2 = k;
4
13
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -14,10 +23,9 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
23
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
24
  };
16
25
  Object.defineProperty(exports, "__esModule", { value: true });
17
- // Core types (universal)
18
- __exportStar(require("./types"), exports);
19
- // Server utilities (for convenience, but prefer ./server)
20
- __exportStar(require("./server"), exports);
21
- // Shared utilities
26
+ // Re-export shared (types, constants, utilities)
22
27
  __exportStar(require("./shared"), exports);
23
- // Note: Client utilities available via ./client subpath
28
+ // Re-export server utilities (for backend convenience)
29
+ __exportStar(require("./server"), exports);
30
+ // Note: Client utilities should be imported from '@markwharton/pwa-core/client'
31
+ // to avoid including browser-only code in Node.js bundles