Skip to content

Commit c5ae2d5

Browse files
feat: improve error formating for service errors (#33)
1 parent a81df21 commit c5ae2d5

3 files changed

Lines changed: 53 additions & 6 deletions

File tree

.changeset/whole-teeth-juggle.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'powersync': patch
3+
---
4+
5+
Improved formatting for PowerSync management service call errors.

packages/cli-core/src/command-types/PowerSyncCommand.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import { JourneyError } from '@journeyapps-labs/micro-errors';
22
import { Command, ux } from '@oclif/core';
33
import { join } from 'node:path';
44

5+
import {
6+
formatPowersyncServiceErrorDisplay,
7+
isPowersyncAuthServiceError
8+
} from '../utils/format-powersync-service-error-display.js';
59
import { CommandHelpGroup } from './HelpGroup.js';
610

711
export type StyledErrorParams = {
@@ -35,20 +39,25 @@ export abstract class PowerSyncCommand extends Command {
3539
styledError(params: StyledErrorParams): never {
3640
const { error, exitCode = 1, message, suggestions } = params;
3741
// Journey SDK errors contain additional fields that we want to pass to the error handler.
38-
const journeyError =
42+
const serviceError =
3943
error != null && typeof error === 'object' && 'is_journey_error' in error ? (error as JourneyError) : undefined;
40-
const journeyErrorMessage = journeyError ? JSON.stringify(journeyError.toJSON(), null, '\t') : undefined;
44+
const serviceErrorDetails = serviceError ? formatPowersyncServiceErrorDisplay(serviceError) : undefined;
4145

4246
const errorDetails =
43-
journeyErrorMessage ?? (error == null ? '' : error instanceof Error ? error.message : String(error));
47+
serviceErrorDetails ?? (error == null ? '' : error instanceof Error ? error.message : String(error));
4448
const displayMessage = errorDetails ? `${message}, :: ${errorDetails}` : message;
4549

50+
const authSuggestions =
51+
serviceError && isPowersyncAuthServiceError(serviceError) && !suggestions?.length
52+
? ['Run `powersync login` to refresh your credentials.']
53+
: suggestions;
54+
4655
this.error(ux.colorize('red', displayMessage), {
4756
...(error instanceof Error ? error : {}),
48-
...(journeyError?.errorData?.code && { code: journeyError.errorData.code }),
57+
...(serviceError?.errorData?.code && { code: serviceError.errorData.code }),
4958
exit: exitCode,
50-
message: journeyErrorMessage ?? message,
51-
...(suggestions?.length && { suggestions })
59+
message: serviceErrorDetails ?? message,
60+
...(authSuggestions?.length && { suggestions: authSuggestions })
5261
});
5362
}
5463
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { JourneyError } from '@journeyapps-labs/micro-errors';
2+
3+
const AUTH_HINT = 'Authentication failed. Your PAT TOKEN might be invalid — run `powersync login` to refresh.';
4+
5+
/** HTTP 401-style and explicit authorization failures from microservice clients. */
6+
export function isPowersyncAuthServiceError(err: JourneyError): boolean {
7+
const { code, status } = err.errorData;
8+
return status === 401 || code === 'AUTHORIZATION' || err.name === 'AuthorizationError';
9+
}
10+
11+
function displayNameFor(err: JourneyError): string {
12+
if (isPowersyncAuthServiceError(err)) {
13+
return 'PowerSyncAuthError';
14+
}
15+
16+
const n = err.errorData.name ?? err.name;
17+
return n === 'JourneyError' ? 'PowerSyncError' : (n ?? 'PowerSyncError');
18+
}
19+
20+
/**
21+
* Serializes microservice {@link JourneyError} for CLI output with PowerSync branding
22+
* and extra context for authentication failures.
23+
*/
24+
export function formatPowersyncServiceErrorDisplay(err: JourneyError): string {
25+
const base = { ...(err.toJSON() as Record<string, unknown>) };
26+
base.name = displayNameFor(err);
27+
if (isPowersyncAuthServiceError(err)) {
28+
base.category = 'authentication';
29+
base.hint = AUTH_HINT;
30+
}
31+
32+
return JSON.stringify(base, null, '\t');
33+
}

0 commit comments

Comments
 (0)