adapty 0.5.0 → 0.6.0-beta.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.
- package/README.md +15 -1
- package/dist/commands/asa/connect.js +2 -1
- package/dist/commands/auth/login.js +7 -5
- package/dist/commands/flows/config/get.d.ts +14 -0
- package/dist/commands/flows/config/get.js +25 -0
- package/dist/commands/flows/config/update.d.ts +21 -0
- package/dist/commands/flows/config/update.js +84 -0
- package/dist/commands/flows/create.d.ts +12 -0
- package/dist/commands/flows/create.js +22 -0
- package/dist/commands/flows/get.d.ts +14 -0
- package/dist/commands/flows/get.js +25 -0
- package/dist/commands/flows/list.d.ts +14 -0
- package/dist/commands/flows/list.js +20 -0
- package/dist/lib/api-schemas.d.ts +28 -0
- package/dist/lib/errors.d.ts +2 -0
- package/dist/lib/errors.js +19 -1
- package/oclif.manifest.json +1352 -1078
- package/package.json +7 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
# Adapty CLI
|
|
6
6
|
|
|
7
|
-
[Adapty Developer CLI](https://adapty.io/docs/developer-cli). Manage apps, products, paywalls, placements, and access levels from your terminal.
|
|
7
|
+
[Adapty Developer CLI](https://adapty.io/docs/developer-cli). Manage apps, products, paywalls, placements, flows, and access levels from your terminal.
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
@@ -77,6 +77,20 @@ adapty placements create --app UUID [flags]
|
|
|
77
77
|
adapty placements update --app UUID PLACEMENT_ID [flags]
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
+
### Flows
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
adapty flows list --app UUID [--page N] [--page-size N]
|
|
84
|
+
adapty flows get --app UUID FLOW_ID
|
|
85
|
+
adapty flows create --app UUID --name "Name"
|
|
86
|
+
adapty flows config get --app UUID FLOW_ID
|
|
87
|
+
adapty flows config update --app UUID FLOW_ID (--config JSON | --config-file PATH|-) [--remote-configs JSON] [--expected-updated-at MS]
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
A freshly created flow has no config until the first `flows config update`; `flows config get` returns 404
|
|
91
|
+
until then. Pass `--expected-updated-at` (the `updated_at` from a prior `config get`) to fail instead of
|
|
92
|
+
overwriting a concurrent dashboard edit.
|
|
93
|
+
|
|
80
94
|
### Access Levels
|
|
81
95
|
|
|
82
96
|
```sh
|
|
@@ -17,7 +17,8 @@ export default class AsaConnect extends Command {
|
|
|
17
17
|
this.log(`If the browser doesn't open, visit: ${authUrl}\n`);
|
|
18
18
|
this.log('The link is valid for one hour. Sign in to the Adapty dashboard in that browser first — the last');
|
|
19
19
|
this.log('step is authorized by the dashboard session, not by this CLI.');
|
|
20
|
-
|
|
20
|
+
if (process.stdin.isTTY === true)
|
|
21
|
+
await open(authUrl).catch(() => false);
|
|
21
22
|
if (!flags.wait)
|
|
22
23
|
return { auth_url: authUrl };
|
|
23
24
|
const deadline = Date.now() + flags.timeout * 1000;
|
|
@@ -29,11 +29,13 @@ export default class AuthLogin extends Command {
|
|
|
29
29
|
}
|
|
30
30
|
this.log(`\nYour code: ${device.user_code}\n`);
|
|
31
31
|
this.log(`If browser doesn't open, visit: ${device.verification_uri_complete}\n`);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
if (process.stdin.isTTY === true) {
|
|
33
|
+
try {
|
|
34
|
+
await open(device.verification_uri_complete);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// browser open failed silently — URL already printed
|
|
38
|
+
}
|
|
37
39
|
}
|
|
38
40
|
this.log('Waiting for authorization...');
|
|
39
41
|
let interval = Math.max((device.interval_seconds || 5) * 1000, 5000);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import type { FlowConfigDTO } from '../../../lib/api-schemas.js';
|
|
3
|
+
export default class FlowsConfigGet extends Command {
|
|
4
|
+
static args: {
|
|
5
|
+
flow_id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
6
|
+
};
|
|
7
|
+
static description: string;
|
|
8
|
+
static enableJsonFlag: boolean;
|
|
9
|
+
static examples: string[];
|
|
10
|
+
static flags: {
|
|
11
|
+
app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<FlowConfigDTO>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Args, Command } from '@oclif/core';
|
|
2
|
+
import { createAuthenticatedClient } from '../../../lib/client-from-config.js';
|
|
3
|
+
import { appFlag, isValidUuid } from '../../../lib/flags.js';
|
|
4
|
+
import { printResponse } from '../../../lib/output.js';
|
|
5
|
+
export default class FlowsConfigGet extends Command {
|
|
6
|
+
static args = {
|
|
7
|
+
flow_id: Args.string({ description: 'Flow ID (UUID)', required: true }),
|
|
8
|
+
};
|
|
9
|
+
static description = 'Read the flow builder config (404 until the config has been written at least once)';
|
|
10
|
+
static enableJsonFlag = true;
|
|
11
|
+
static examples = ['<%= config.bin %> flows config get --app UUID 550e8400-e29b-41d4-a716-446655440000'];
|
|
12
|
+
static flags = {
|
|
13
|
+
...appFlag,
|
|
14
|
+
};
|
|
15
|
+
async run() {
|
|
16
|
+
const { args, flags } = await this.parse(FlowsConfigGet);
|
|
17
|
+
if (!isValidUuid(args.flow_id)) {
|
|
18
|
+
this.error('Invalid flow ID format.', { exit: 2 });
|
|
19
|
+
}
|
|
20
|
+
const client = await createAuthenticatedClient(this.config);
|
|
21
|
+
const result = await client.get(`/apps/${flags.app}/flows/${args.flow_id}/config`);
|
|
22
|
+
printResponse(result, this.log.bind(this));
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import type { FlowConfigDTO } from '../../../lib/api-schemas.js';
|
|
3
|
+
export default class FlowsConfigUpdate extends Command {
|
|
4
|
+
static args: {
|
|
5
|
+
flow_id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
6
|
+
};
|
|
7
|
+
static description: string;
|
|
8
|
+
static enableJsonFlag: boolean;
|
|
9
|
+
static examples: string[];
|
|
10
|
+
static flags: {
|
|
11
|
+
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
'config-file': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
'expected-updated-at': import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
'remote-configs': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
};
|
|
17
|
+
run(): Promise<FlowConfigDTO>;
|
|
18
|
+
private parseJson;
|
|
19
|
+
private readConfig;
|
|
20
|
+
private readStdin;
|
|
21
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { createAuthenticatedClient } from '../../../lib/client-from-config.js';
|
|
4
|
+
import { appFlag, isValidUuid } from '../../../lib/flags.js';
|
|
5
|
+
import { printResponse } from '../../../lib/output.js';
|
|
6
|
+
export default class FlowsConfigUpdate extends Command {
|
|
7
|
+
static args = {
|
|
8
|
+
flow_id: Args.string({ description: 'Flow ID (UUID)', required: true }),
|
|
9
|
+
};
|
|
10
|
+
static description = 'Write the flow builder config (creates the first version, or edits/forks the current one)';
|
|
11
|
+
static enableJsonFlag = true;
|
|
12
|
+
static examples = [
|
|
13
|
+
'<%= config.bin %> flows config update --app UUID FLOW_UUID --config-file config.json',
|
|
14
|
+
'cat config.json | <%= config.bin %> flows config update --app UUID FLOW_UUID --config-file -',
|
|
15
|
+
'<%= config.bin %> flows config update --app UUID FLOW_UUID --config \'{"screens":[],"locales":[]}\' --expected-updated-at 1755001800000',
|
|
16
|
+
];
|
|
17
|
+
static flags = {
|
|
18
|
+
...appFlag,
|
|
19
|
+
config: Flags.string({
|
|
20
|
+
description: 'Builder config as a JSON string',
|
|
21
|
+
exactlyOne: ['config', 'config-file'],
|
|
22
|
+
}),
|
|
23
|
+
'config-file': Flags.string({
|
|
24
|
+
description: 'JSON file with the builder config, or - to read stdin',
|
|
25
|
+
exactlyOne: ['config', 'config-file'],
|
|
26
|
+
}),
|
|
27
|
+
'expected-updated-at': Flags.integer({
|
|
28
|
+
description: 'Optimistic lock: the `updated_at` from a prior config read. Omit for last-write-wins.',
|
|
29
|
+
}),
|
|
30
|
+
'remote-configs': Flags.string({
|
|
31
|
+
description: 'JSON array of remote config entries: [{locale, data}]',
|
|
32
|
+
}),
|
|
33
|
+
};
|
|
34
|
+
async run() {
|
|
35
|
+
const { args, flags } = await this.parse(FlowsConfigUpdate);
|
|
36
|
+
if (!isValidUuid(args.flow_id)) {
|
|
37
|
+
this.error('Invalid flow ID format.', { exit: 2 });
|
|
38
|
+
}
|
|
39
|
+
const config = await this.readConfig(flags);
|
|
40
|
+
const body = { config };
|
|
41
|
+
if (flags['remote-configs'] !== undefined) {
|
|
42
|
+
body.remote_configs = this.parseJson(flags['remote-configs'], '--remote-configs');
|
|
43
|
+
}
|
|
44
|
+
if (flags['expected-updated-at'] !== undefined) {
|
|
45
|
+
body.expected_updated_at = flags['expected-updated-at'];
|
|
46
|
+
}
|
|
47
|
+
const client = await createAuthenticatedClient(this.config);
|
|
48
|
+
const result = await client.put(`/apps/${flags.app}/flows/${args.flow_id}/config`, body);
|
|
49
|
+
this.log('Flow config saved!');
|
|
50
|
+
printResponse(result, this.log.bind(this));
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
parseJson(raw, label) {
|
|
54
|
+
try {
|
|
55
|
+
return JSON.parse(raw);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
this.error(`Invalid ${label} JSON: ${error instanceof Error ? error.message : String(error)}`, { exit: 2 });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async readConfig(flags) {
|
|
62
|
+
if (flags.config !== undefined) {
|
|
63
|
+
return this.parseJson(flags.config, '--config');
|
|
64
|
+
}
|
|
65
|
+
const path = flags['config-file'];
|
|
66
|
+
if (path === undefined) {
|
|
67
|
+
this.error('Provide --config or --config-file.', { exit: 2 });
|
|
68
|
+
}
|
|
69
|
+
let raw;
|
|
70
|
+
try {
|
|
71
|
+
raw = path === '-' ? await this.readStdin() : await readFile(path, 'utf8');
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
this.error(`Could not read ${path}.`, { exit: 2 });
|
|
75
|
+
}
|
|
76
|
+
return this.parseJson(raw, path);
|
|
77
|
+
}
|
|
78
|
+
async readStdin() {
|
|
79
|
+
const chunks = [];
|
|
80
|
+
for await (const chunk of process.stdin)
|
|
81
|
+
chunks.push(chunk);
|
|
82
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import type { FlowDTO } from '../../lib/api-schemas.js';
|
|
3
|
+
export default class FlowsCreate extends Command {
|
|
4
|
+
static description: string;
|
|
5
|
+
static enableJsonFlag: boolean;
|
|
6
|
+
static examples: string[];
|
|
7
|
+
static flags: {
|
|
8
|
+
name: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<FlowDTO>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { createAuthenticatedClient } from '../../lib/client-from-config.js';
|
|
3
|
+
import { appFlag } from '../../lib/flags.js';
|
|
4
|
+
import { printResponse } from '../../lib/output.js';
|
|
5
|
+
export default class FlowsCreate extends Command {
|
|
6
|
+
static description = 'Create a flow (row only — write its config with `flows config update`)';
|
|
7
|
+
static enableJsonFlag = true;
|
|
8
|
+
static examples = ['<%= config.bin %> flows create --app UUID --name "Onboarding"'];
|
|
9
|
+
static flags = {
|
|
10
|
+
...appFlag,
|
|
11
|
+
name: Flags.string({ description: 'Flow name', required: true }),
|
|
12
|
+
};
|
|
13
|
+
async run() {
|
|
14
|
+
const { flags } = await this.parse(FlowsCreate);
|
|
15
|
+
const client = await createAuthenticatedClient(this.config);
|
|
16
|
+
const body = { name: flags.name };
|
|
17
|
+
const result = await client.post(`/apps/${flags.app}/flows`, body);
|
|
18
|
+
this.log('Flow created!');
|
|
19
|
+
printResponse(result, this.log.bind(this));
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import type { FlowDTO } from '../../lib/api-schemas.js';
|
|
3
|
+
export default class FlowsGet extends Command {
|
|
4
|
+
static args: {
|
|
5
|
+
flow_id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
6
|
+
};
|
|
7
|
+
static description: string;
|
|
8
|
+
static enableJsonFlag: boolean;
|
|
9
|
+
static examples: string[];
|
|
10
|
+
static flags: {
|
|
11
|
+
app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<FlowDTO>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Args, Command } from '@oclif/core';
|
|
2
|
+
import { createAuthenticatedClient } from '../../lib/client-from-config.js';
|
|
3
|
+
import { appFlag, isValidUuid } from '../../lib/flags.js';
|
|
4
|
+
import { printResponse } from '../../lib/output.js';
|
|
5
|
+
export default class FlowsGet extends Command {
|
|
6
|
+
static args = {
|
|
7
|
+
flow_id: Args.string({ description: 'Flow ID (UUID)', required: true }),
|
|
8
|
+
};
|
|
9
|
+
static description = 'Get flow details';
|
|
10
|
+
static enableJsonFlag = true;
|
|
11
|
+
static examples = ['<%= config.bin %> flows get --app UUID 550e8400-e29b-41d4-a716-446655440000'];
|
|
12
|
+
static flags = {
|
|
13
|
+
...appFlag,
|
|
14
|
+
};
|
|
15
|
+
async run() {
|
|
16
|
+
const { args, flags } = await this.parse(FlowsGet);
|
|
17
|
+
if (!isValidUuid(args.flow_id)) {
|
|
18
|
+
this.error('Invalid flow ID format.', { exit: 2 });
|
|
19
|
+
}
|
|
20
|
+
const client = await createAuthenticatedClient(this.config);
|
|
21
|
+
const result = await client.get(`/apps/${flags.app}/flows/${args.flow_id}`);
|
|
22
|
+
printResponse(result, this.log.bind(this));
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import type { FlowDTO } from '../../lib/api-schemas.js';
|
|
3
|
+
import { type PaginatedResponse } from '../../lib/flags.js';
|
|
4
|
+
export default class FlowsList extends Command {
|
|
5
|
+
static description: string;
|
|
6
|
+
static enableJsonFlag: boolean;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
page: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
'page-size': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<PaginatedResponse<FlowDTO>>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { createAuthenticatedClient } from '../../lib/client-from-config.js';
|
|
3
|
+
import { appFlag, paginationFlags, paginationParams } from '../../lib/flags.js';
|
|
4
|
+
import { printList } from '../../lib/output.js';
|
|
5
|
+
export default class FlowsList extends Command {
|
|
6
|
+
static description = 'List flows for an app';
|
|
7
|
+
static enableJsonFlag = true;
|
|
8
|
+
static examples = ['<%= config.bin %> flows list --app 550e8400-...'];
|
|
9
|
+
static flags = {
|
|
10
|
+
...appFlag,
|
|
11
|
+
...paginationFlags,
|
|
12
|
+
};
|
|
13
|
+
async run() {
|
|
14
|
+
const { flags } = await this.parse(FlowsList);
|
|
15
|
+
const client = await createAuthenticatedClient(this.config);
|
|
16
|
+
const result = await client.get(`/apps/${flags.app}/flows`, paginationParams(flags));
|
|
17
|
+
printList(result.data, this.log.bind(this), result.meta.pagination);
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -73,6 +73,34 @@ export interface PaywallWriteRequestDTO {
|
|
|
73
73
|
product_ids?: string[];
|
|
74
74
|
title: string;
|
|
75
75
|
}
|
|
76
|
+
/** Flow lifecycle status. Values come from the server-side `FlowStatus` enum (e.g. draft, dirty, published). */
|
|
77
|
+
export type FlowStatus = string;
|
|
78
|
+
export interface FlowDTO {
|
|
79
|
+
id: string;
|
|
80
|
+
name: string;
|
|
81
|
+
status: FlowStatus;
|
|
82
|
+
updated_at: string;
|
|
83
|
+
}
|
|
84
|
+
export interface FlowRemoteConfigDTO {
|
|
85
|
+
data: string;
|
|
86
|
+
locale: string;
|
|
87
|
+
}
|
|
88
|
+
export interface FlowConfigDTO {
|
|
89
|
+
config: Record<string, unknown>;
|
|
90
|
+
remote_configs: FlowRemoteConfigDTO[];
|
|
91
|
+
status: FlowStatus;
|
|
92
|
+
/** Millisecond timestamp of the last content change; the value `expected_updated_at` is compared against on write. */
|
|
93
|
+
updated_at: number;
|
|
94
|
+
}
|
|
95
|
+
export interface FlowWriteRequestDTO {
|
|
96
|
+
name: string;
|
|
97
|
+
}
|
|
98
|
+
export interface FlowConfigWriteRequestDTO {
|
|
99
|
+
config: Record<string, unknown>;
|
|
100
|
+
/** Optimistic lock: the `updated_at` from a prior config read. Omit for last-write-wins. */
|
|
101
|
+
expected_updated_at?: null | number;
|
|
102
|
+
remote_configs?: FlowRemoteConfigDTO[];
|
|
103
|
+
}
|
|
76
104
|
export interface SegmentDTO {
|
|
77
105
|
description: null | string;
|
|
78
106
|
id: string;
|
package/dist/lib/errors.d.ts
CHANGED
|
@@ -29,9 +29,11 @@ export declare class ApiError extends Error {
|
|
|
29
29
|
statusCode: number;
|
|
30
30
|
errorCode: string;
|
|
31
31
|
fieldErrors: Record<string, string[]>;
|
|
32
|
+
code?: string;
|
|
32
33
|
detail?: string;
|
|
33
34
|
retryAfterSeconds?: number;
|
|
34
35
|
constructor(statusCode: number, errorCode: string, fieldErrors: Record<string, string[]>, opts?: ApiErrorOptions);
|
|
36
|
+
private static humanMessage;
|
|
35
37
|
toHuman(): string;
|
|
36
38
|
toJSON(): ApiErrorBody;
|
|
37
39
|
}
|
package/dist/lib/errors.js
CHANGED
|
@@ -8,16 +8,34 @@ export class ApiError extends Error {
|
|
|
8
8
|
statusCode;
|
|
9
9
|
errorCode;
|
|
10
10
|
fieldErrors;
|
|
11
|
+
// Read by oclif's prettyPrint to render a `Code: ...` line under the message.
|
|
12
|
+
code;
|
|
11
13
|
detail;
|
|
12
14
|
retryAfterSeconds;
|
|
13
15
|
constructor(statusCode, errorCode, fieldErrors, opts = {}) {
|
|
14
|
-
|
|
16
|
+
// `message` is what oclif's default error renderer prints. Prefer the server's human text (an ASA
|
|
17
|
+
// `detail` or the developer API's field errors) over the bare code, which reads as gibberish on its own.
|
|
18
|
+
super(ApiError.humanMessage(errorCode, fieldErrors, opts.detail));
|
|
15
19
|
this.statusCode = statusCode;
|
|
16
20
|
this.errorCode = errorCode;
|
|
17
21
|
this.fieldErrors = fieldErrors;
|
|
18
22
|
this.name = 'ApiError';
|
|
19
23
|
this.detail = opts.detail;
|
|
20
24
|
this.retryAfterSeconds = opts.retryAfterSeconds;
|
|
25
|
+
// Surface real server codes as a secondary line; hide synthetic `http_<status>` fallbacks.
|
|
26
|
+
if (!errorCode.startsWith('http_'))
|
|
27
|
+
this.code = errorCode;
|
|
28
|
+
}
|
|
29
|
+
static humanMessage(errorCode, fieldErrors, detail) {
|
|
30
|
+
if (detail)
|
|
31
|
+
return detail;
|
|
32
|
+
const parts = [];
|
|
33
|
+
for (const [field, msgs] of Object.entries(fieldErrors)) {
|
|
34
|
+
for (const msg of msgs) {
|
|
35
|
+
parts.push(field === 'non_field_errors' ? msg : `${field}: ${msg}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return parts.length > 0 ? parts.join('; ') : errorCode;
|
|
21
39
|
}
|
|
22
40
|
toHuman() {
|
|
23
41
|
const lines = [`Error: ${this.errorCode}`];
|