@markwharton/pwa-core 2.0.0 → 2.1.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.
- package/dist/server.d.ts +58 -2
- package/dist/server.js +101 -11
- package/package.json +1 -1
package/dist/server.d.ts
CHANGED
|
@@ -186,20 +186,62 @@ export declare function notFoundResponse(resource: string): HttpResponseInit;
|
|
|
186
186
|
* if (existingUser) return conflictResponse('Email already registered');
|
|
187
187
|
*/
|
|
188
188
|
export declare function conflictResponse(message: string): HttpResponseInit;
|
|
189
|
+
/**
|
|
190
|
+
* Validates that a required parameter is present.
|
|
191
|
+
* Returns an error response if missing, null if valid.
|
|
192
|
+
* @param value - The value to check
|
|
193
|
+
* @param paramName - The parameter name for the error message
|
|
194
|
+
* @returns HttpResponseInit if invalid, null if valid
|
|
195
|
+
* @example
|
|
196
|
+
* const error = validateRequired(userId, 'userId');
|
|
197
|
+
* if (error) return error;
|
|
198
|
+
* // userId is guaranteed to be defined here
|
|
199
|
+
*/
|
|
200
|
+
export declare function validateRequired(value: string | undefined, paramName: string): HttpResponseInit | null;
|
|
201
|
+
/**
|
|
202
|
+
* Callback type for error handling hooks (e.g., alerting, monitoring).
|
|
203
|
+
*/
|
|
204
|
+
export type ErrorCallback = (operation: string, message: string) => void;
|
|
205
|
+
/**
|
|
206
|
+
* Sets a callback to be invoked when handleFunctionError is called.
|
|
207
|
+
* Use this to integrate with alerting or monitoring systems.
|
|
208
|
+
* @param callback - The callback to invoke (fire-and-forget)
|
|
209
|
+
* @example
|
|
210
|
+
* setErrorCallback((operation, message) => {
|
|
211
|
+
* sendAlert(operation, message); // Fire-and-forget
|
|
212
|
+
* });
|
|
213
|
+
*/
|
|
214
|
+
export declare function setErrorCallback(callback: ErrorCallback | null): void;
|
|
189
215
|
/**
|
|
190
216
|
* Handles unexpected errors safely by logging details and returning a generic message.
|
|
191
217
|
* Use in catch blocks to avoid exposing internal error details to clients.
|
|
192
218
|
* @param error - The caught error
|
|
193
219
|
* @param context - Azure Functions InvocationContext for logging
|
|
220
|
+
* @param operation - Optional operation name for error callback
|
|
194
221
|
* @returns Azure Functions HttpResponseInit with 500 status
|
|
195
222
|
* @example
|
|
196
223
|
* try {
|
|
197
224
|
* await riskyOperation();
|
|
198
225
|
* } catch (error) {
|
|
199
|
-
* return handleFunctionError(error, context);
|
|
226
|
+
* return handleFunctionError(error, context, 'riskyOperation');
|
|
227
|
+
* }
|
|
228
|
+
*/
|
|
229
|
+
export declare function handleFunctionError(error: unknown, context: InvocationContext, operation?: string): HttpResponseInit;
|
|
230
|
+
/**
|
|
231
|
+
* Checks if an error is an Azure Table Storage error with a specific status code.
|
|
232
|
+
* @param error - The caught error
|
|
233
|
+
* @param statusCode - The HTTP status code to check for
|
|
234
|
+
* @returns True if error has the specified statusCode
|
|
235
|
+
* @example
|
|
236
|
+
* try {
|
|
237
|
+
* await tableClient.upsertEntity(entity);
|
|
238
|
+
* } catch (error) {
|
|
239
|
+
* if (isTableStorageError(error, 412)) {
|
|
240
|
+
* // Precondition failed - ETag mismatch
|
|
241
|
+
* }
|
|
200
242
|
* }
|
|
201
243
|
*/
|
|
202
|
-
export declare function
|
|
244
|
+
export declare function isTableStorageError(error: unknown, statusCode: number): boolean;
|
|
203
245
|
/**
|
|
204
246
|
* Checks if an error is an Azure Table Storage "not found" error.
|
|
205
247
|
* @param error - The caught error
|
|
@@ -273,6 +315,20 @@ export declare function getTableClient(tableName: string): Promise<TableClient>;
|
|
|
273
315
|
* });
|
|
274
316
|
*/
|
|
275
317
|
export declare function clearTableClientCache(): void;
|
|
318
|
+
/**
|
|
319
|
+
* Gets an entity from Azure Table Storage if it exists, or returns null.
|
|
320
|
+
* Eliminates the common try-catch-isNotFoundError pattern.
|
|
321
|
+
* @typeParam T - The entity type (extends object)
|
|
322
|
+
* @param client - The TableClient instance
|
|
323
|
+
* @param partitionKey - The partition key
|
|
324
|
+
* @param rowKey - The row key
|
|
325
|
+
* @returns The entity if found, null if not found
|
|
326
|
+
* @throws Re-throws non-404 errors
|
|
327
|
+
* @example
|
|
328
|
+
* const user = await getEntityIfExists<UserEntity>(client, 'users', id);
|
|
329
|
+
* if (!user) return notFoundResponse('User');
|
|
330
|
+
*/
|
|
331
|
+
export declare function getEntityIfExists<T extends object>(client: TableClient, partitionKey: string, rowKey: string): Promise<T | null>;
|
|
276
332
|
/**
|
|
277
333
|
* Generate a unique row key from an identifier string.
|
|
278
334
|
* Uses SHA-256 hash for consistent, URL-safe keys.
|
package/dist/server.js
CHANGED
|
@@ -26,7 +26,10 @@ exports.unauthorizedResponse = unauthorizedResponse;
|
|
|
26
26
|
exports.forbiddenResponse = forbiddenResponse;
|
|
27
27
|
exports.notFoundResponse = notFoundResponse;
|
|
28
28
|
exports.conflictResponse = conflictResponse;
|
|
29
|
+
exports.validateRequired = validateRequired;
|
|
30
|
+
exports.setErrorCallback = setErrorCallback;
|
|
29
31
|
exports.handleFunctionError = handleFunctionError;
|
|
32
|
+
exports.isTableStorageError = isTableStorageError;
|
|
30
33
|
exports.isNotFoundError = isNotFoundError;
|
|
31
34
|
exports.isConflictError = isConflictError;
|
|
32
35
|
exports.initStorage = initStorage;
|
|
@@ -34,6 +37,7 @@ exports.initStorageFromEnv = initStorageFromEnv;
|
|
|
34
37
|
exports.useManagedIdentity = useManagedIdentity;
|
|
35
38
|
exports.getTableClient = getTableClient;
|
|
36
39
|
exports.clearTableClientCache = clearTableClientCache;
|
|
40
|
+
exports.getEntityIfExists = getEntityIfExists;
|
|
37
41
|
exports.generateRowKey = generateRowKey;
|
|
38
42
|
const crypto_1 = require("crypto");
|
|
39
43
|
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
@@ -300,27 +304,86 @@ function conflictResponse(message) {
|
|
|
300
304
|
jsonBody: { error: message }
|
|
301
305
|
};
|
|
302
306
|
}
|
|
307
|
+
/**
|
|
308
|
+
* Validates that a required parameter is present.
|
|
309
|
+
* Returns an error response if missing, null if valid.
|
|
310
|
+
* @param value - The value to check
|
|
311
|
+
* @param paramName - The parameter name for the error message
|
|
312
|
+
* @returns HttpResponseInit if invalid, null if valid
|
|
313
|
+
* @example
|
|
314
|
+
* const error = validateRequired(userId, 'userId');
|
|
315
|
+
* if (error) return error;
|
|
316
|
+
* // userId is guaranteed to be defined here
|
|
317
|
+
*/
|
|
318
|
+
function validateRequired(value, paramName) {
|
|
319
|
+
if (!value) {
|
|
320
|
+
return badRequestResponse(`${paramName} is required`);
|
|
321
|
+
}
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
let errorCallback = null;
|
|
325
|
+
/**
|
|
326
|
+
* Sets a callback to be invoked when handleFunctionError is called.
|
|
327
|
+
* Use this to integrate with alerting or monitoring systems.
|
|
328
|
+
* @param callback - The callback to invoke (fire-and-forget)
|
|
329
|
+
* @example
|
|
330
|
+
* setErrorCallback((operation, message) => {
|
|
331
|
+
* sendAlert(operation, message); // Fire-and-forget
|
|
332
|
+
* });
|
|
333
|
+
*/
|
|
334
|
+
function setErrorCallback(callback) {
|
|
335
|
+
errorCallback = callback;
|
|
336
|
+
}
|
|
303
337
|
/**
|
|
304
338
|
* Handles unexpected errors safely by logging details and returning a generic message.
|
|
305
339
|
* Use in catch blocks to avoid exposing internal error details to clients.
|
|
306
340
|
* @param error - The caught error
|
|
307
341
|
* @param context - Azure Functions InvocationContext for logging
|
|
342
|
+
* @param operation - Optional operation name for error callback
|
|
308
343
|
* @returns Azure Functions HttpResponseInit with 500 status
|
|
309
344
|
* @example
|
|
310
345
|
* try {
|
|
311
346
|
* await riskyOperation();
|
|
312
347
|
* } catch (error) {
|
|
313
|
-
* return handleFunctionError(error, context);
|
|
348
|
+
* return handleFunctionError(error, context, 'riskyOperation');
|
|
314
349
|
* }
|
|
315
350
|
*/
|
|
316
|
-
function handleFunctionError(error, context) {
|
|
351
|
+
function handleFunctionError(error, context, operation) {
|
|
317
352
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
318
353
|
context.error(`Function error: ${message}`);
|
|
354
|
+
// Fire-and-forget callback (for alerting, monitoring)
|
|
355
|
+
if (errorCallback && operation) {
|
|
356
|
+
try {
|
|
357
|
+
errorCallback(operation, message);
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
// Swallow callback errors to prevent cascading failures
|
|
361
|
+
}
|
|
362
|
+
}
|
|
319
363
|
return {
|
|
320
364
|
status: shared_1.HTTP_STATUS.INTERNAL_ERROR,
|
|
321
365
|
jsonBody: { error: 'Internal server error' }
|
|
322
366
|
};
|
|
323
367
|
}
|
|
368
|
+
/**
|
|
369
|
+
* Checks if an error is an Azure Table Storage error with a specific status code.
|
|
370
|
+
* @param error - The caught error
|
|
371
|
+
* @param statusCode - The HTTP status code to check for
|
|
372
|
+
* @returns True if error has the specified statusCode
|
|
373
|
+
* @example
|
|
374
|
+
* try {
|
|
375
|
+
* await tableClient.upsertEntity(entity);
|
|
376
|
+
* } catch (error) {
|
|
377
|
+
* if (isTableStorageError(error, 412)) {
|
|
378
|
+
* // Precondition failed - ETag mismatch
|
|
379
|
+
* }
|
|
380
|
+
* }
|
|
381
|
+
*/
|
|
382
|
+
function isTableStorageError(error, statusCode) {
|
|
383
|
+
return (error instanceof Error &&
|
|
384
|
+
'statusCode' in error &&
|
|
385
|
+
error.statusCode === statusCode);
|
|
386
|
+
}
|
|
324
387
|
/**
|
|
325
388
|
* Checks if an error is an Azure Table Storage "not found" error.
|
|
326
389
|
* @param error - The caught error
|
|
@@ -334,9 +397,7 @@ function handleFunctionError(error, context) {
|
|
|
334
397
|
* }
|
|
335
398
|
*/
|
|
336
399
|
function isNotFoundError(error) {
|
|
337
|
-
return (error
|
|
338
|
-
'statusCode' in error &&
|
|
339
|
-
error.statusCode === 404);
|
|
400
|
+
return isTableStorageError(error, shared_1.HTTP_STATUS.NOT_FOUND);
|
|
340
401
|
}
|
|
341
402
|
/**
|
|
342
403
|
* Checks if an error is an Azure Table Storage "conflict" error.
|
|
@@ -351,15 +412,21 @@ function isNotFoundError(error) {
|
|
|
351
412
|
* }
|
|
352
413
|
*/
|
|
353
414
|
function isConflictError(error) {
|
|
354
|
-
return (error
|
|
355
|
-
'statusCode' in error &&
|
|
356
|
-
error.statusCode === 409);
|
|
415
|
+
return isTableStorageError(error, shared_1.HTTP_STATUS.CONFLICT);
|
|
357
416
|
}
|
|
358
417
|
// =============================================================================
|
|
359
418
|
// Azure Table Storage
|
|
360
419
|
// =============================================================================
|
|
361
420
|
// Cache table clients to avoid repeated connections
|
|
362
421
|
const tableClients = new Map();
|
|
422
|
+
// Shared credential instance for managed identity (avoids repeated instantiation)
|
|
423
|
+
let credential = null;
|
|
424
|
+
function getCredential() {
|
|
425
|
+
if (!credential) {
|
|
426
|
+
credential = new identity_1.DefaultAzureCredential();
|
|
427
|
+
}
|
|
428
|
+
return credential;
|
|
429
|
+
}
|
|
363
430
|
// Configuration
|
|
364
431
|
let storageAccountName;
|
|
365
432
|
let storageConnectionString;
|
|
@@ -416,10 +483,9 @@ async function getTableClient(tableName) {
|
|
|
416
483
|
}
|
|
417
484
|
let client;
|
|
418
485
|
if (useManagedIdentity()) {
|
|
419
|
-
// Azure: Use managed identity
|
|
420
|
-
const credential = new identity_1.DefaultAzureCredential();
|
|
486
|
+
// Azure: Use managed identity (credential is cached)
|
|
421
487
|
const endpoint = `https://${storageAccountName}.table.core.windows.net`;
|
|
422
|
-
client = new data_tables_1.TableClient(endpoint, tableName,
|
|
488
|
+
client = new data_tables_1.TableClient(endpoint, tableName, getCredential());
|
|
423
489
|
}
|
|
424
490
|
else if (storageConnectionString) {
|
|
425
491
|
// Local/Azurite: Use connection string
|
|
@@ -453,6 +519,30 @@ async function getTableClient(tableName) {
|
|
|
453
519
|
function clearTableClientCache() {
|
|
454
520
|
tableClients.clear();
|
|
455
521
|
}
|
|
522
|
+
/**
|
|
523
|
+
* Gets an entity from Azure Table Storage if it exists, or returns null.
|
|
524
|
+
* Eliminates the common try-catch-isNotFoundError pattern.
|
|
525
|
+
* @typeParam T - The entity type (extends object)
|
|
526
|
+
* @param client - The TableClient instance
|
|
527
|
+
* @param partitionKey - The partition key
|
|
528
|
+
* @param rowKey - The row key
|
|
529
|
+
* @returns The entity if found, null if not found
|
|
530
|
+
* @throws Re-throws non-404 errors
|
|
531
|
+
* @example
|
|
532
|
+
* const user = await getEntityIfExists<UserEntity>(client, 'users', id);
|
|
533
|
+
* if (!user) return notFoundResponse('User');
|
|
534
|
+
*/
|
|
535
|
+
async function getEntityIfExists(client, partitionKey, rowKey) {
|
|
536
|
+
try {
|
|
537
|
+
return await client.getEntity(partitionKey, rowKey);
|
|
538
|
+
}
|
|
539
|
+
catch (error) {
|
|
540
|
+
if (isNotFoundError(error)) {
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
throw error;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
456
546
|
/**
|
|
457
547
|
* Generate a unique row key from an identifier string.
|
|
458
548
|
* Uses SHA-256 hash for consistent, URL-safe keys.
|