adapty 0.8.8 → 0.9.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 (204) hide show
  1. package/README.md +147 -2
  2. package/dist/cli/base/adapty/adapty-command.d.ts +3 -2
  3. package/dist/cli/base/adapty/adapty-command.js +8 -5
  4. package/dist/cli/base/adapty/build.d.ts +6 -1
  5. package/dist/cli/base/adapty/build.js +1 -0
  6. package/dist/cli/base/adapty/index.d.ts +1 -0
  7. package/dist/cli/base/adapty/index.js +1 -0
  8. package/dist/cli/base/adapty/migration-command.d.ts +11 -0
  9. package/dist/cli/base/adapty/migration-command.js +17 -0
  10. package/dist/cli/base/base-command.d.ts +6 -0
  11. package/dist/cli/base/base-command.js +14 -0
  12. package/dist/cli/commands/apps/create.d.ts +1 -1
  13. package/dist/cli/commands/apps/get.js +1 -1
  14. package/dist/cli/commands/apps/list.js +1 -1
  15. package/dist/cli/commands/apps/update.js +1 -1
  16. package/dist/cli/commands/attribution/report/command.d.ts +18 -0
  17. package/dist/cli/commands/attribution/report/command.js +93 -0
  18. package/dist/cli/commands/attribution/report/index.d.ts +1 -18
  19. package/dist/cli/commands/attribution/report/index.js +1 -93
  20. package/dist/cli/commands/attribution/values.js +1 -1
  21. package/dist/cli/commands/auth/login.js +1 -0
  22. package/dist/cli/commands/auth/{logout.d.ts → logout/command.d.ts} +1 -1
  23. package/dist/cli/commands/auth/logout/command.js +33 -0
  24. package/dist/cli/commands/auth/logout/index.d.ts +1 -0
  25. package/dist/cli/commands/auth/logout/index.js +1 -0
  26. package/dist/cli/commands/auth/logout/lib/cleanup.d.ts +3 -0
  27. package/dist/cli/commands/auth/logout/lib/cleanup.js +21 -0
  28. package/dist/cli/commands/auth/{revoke.d.ts → revoke/command.d.ts} +1 -1
  29. package/dist/cli/commands/auth/{revoke.js → revoke/command.js} +7 -7
  30. package/dist/cli/commands/auth/revoke/index.d.ts +1 -0
  31. package/dist/cli/commands/auth/revoke/index.js +1 -0
  32. package/dist/cli/commands/auth/revoke/lib/cleanup.d.ts +4 -0
  33. package/dist/cli/commands/auth/revoke/lib/cleanup.js +29 -0
  34. package/dist/cli/commands/auth/status/command.d.ts +7 -0
  35. package/dist/cli/commands/auth/status/command.js +22 -0
  36. package/dist/cli/commands/auth/status/index.d.ts +1 -7
  37. package/dist/cli/commands/auth/status/index.js +1 -22
  38. package/dist/cli/commands/migrations/close/command.d.ts +16 -0
  39. package/dist/cli/commands/migrations/close/command.js +58 -0
  40. package/dist/cli/commands/migrations/close/index.d.ts +1 -0
  41. package/dist/cli/commands/migrations/close/index.js +1 -0
  42. package/dist/cli/commands/migrations/create/command.d.ts +20 -0
  43. package/dist/cli/commands/migrations/create/command.js +86 -0
  44. package/dist/cli/commands/migrations/create/index.d.ts +1 -0
  45. package/dist/cli/commands/migrations/create/index.js +1 -0
  46. package/dist/cli/commands/migrations/current/command.d.ts +12 -0
  47. package/dist/cli/commands/migrations/current/command.js +25 -0
  48. package/dist/cli/commands/migrations/current/index.d.ts +1 -0
  49. package/dist/cli/commands/migrations/current/index.js +1 -0
  50. package/dist/cli/commands/migrations/list/command.d.ts +11 -0
  51. package/dist/cli/commands/migrations/list/command.js +27 -0
  52. package/dist/cli/commands/migrations/list/index.d.ts +1 -0
  53. package/dist/cli/commands/migrations/list/index.js +1 -0
  54. package/dist/cli/commands/migrations/list/lib/render.d.ts +3 -0
  55. package/dist/cli/commands/migrations/list/lib/render.js +77 -0
  56. package/dist/cli/commands/migrations/run/command.d.ts +25 -0
  57. package/dist/cli/commands/migrations/run/command.js +141 -0
  58. package/dist/cli/commands/migrations/run/index.d.ts +1 -0
  59. package/dist/cli/commands/migrations/run/index.js +1 -0
  60. package/dist/cli/commands/migrations/run/lib/action-view.d.ts +7 -0
  61. package/dist/cli/commands/migrations/run/lib/action-view.js +2 -0
  62. package/dist/cli/commands/migrations/run/lib/actions.d.ts +4 -0
  63. package/dist/cli/commands/migrations/run/lib/actions.js +20 -0
  64. package/dist/cli/commands/migrations/run/lib/https.d.ts +1 -0
  65. package/dist/cli/commands/migrations/run/lib/https.js +8 -0
  66. package/dist/cli/commands/migrations/run/lib/input.d.ts +10 -0
  67. package/dist/cli/commands/migrations/run/lib/input.js +51 -0
  68. package/dist/cli/commands/migrations/run/lib/open-link.d.ts +7 -0
  69. package/dist/cli/commands/migrations/run/lib/open-link.js +11 -0
  70. package/dist/cli/commands/migrations/show/command.d.ts +17 -0
  71. package/dist/cli/commands/migrations/show/command.js +47 -0
  72. package/dist/cli/commands/migrations/show/index.d.ts +1 -0
  73. package/dist/cli/commands/migrations/show/index.js +1 -0
  74. package/dist/cli/commands/migrations/show/lib/render.d.ts +4 -0
  75. package/dist/cli/commands/migrations/show/lib/render.js +27 -0
  76. package/dist/cli/commands/migrations/status/command.d.ts +18 -0
  77. package/dist/cli/commands/migrations/status/command.js +54 -0
  78. package/dist/cli/commands/migrations/status/index.d.ts +1 -0
  79. package/dist/cli/commands/migrations/status/index.js +1 -0
  80. package/dist/cli/commands/migrations/status/lib/flags.d.ts +8 -0
  81. package/dist/cli/commands/migrations/status/lib/flags.js +29 -0
  82. package/dist/cli/commands/migrations/status/lib/notice.d.ts +3 -0
  83. package/dist/cli/commands/migrations/status/lib/notice.js +10 -0
  84. package/dist/cli/commands/migrations/steps/command.d.ts +14 -0
  85. package/dist/cli/commands/migrations/steps/command.js +34 -0
  86. package/dist/cli/commands/migrations/steps/index.d.ts +1 -0
  87. package/dist/cli/commands/migrations/steps/index.js +1 -0
  88. package/dist/cli/commands/migrations/steps/lib/render.d.ts +3 -0
  89. package/dist/cli/commands/migrations/steps/lib/render.js +28 -0
  90. package/dist/cli/commands/migrations/unuse/command.d.ts +11 -0
  91. package/dist/cli/commands/migrations/unuse/command.js +23 -0
  92. package/dist/cli/commands/migrations/unuse/index.d.ts +1 -0
  93. package/dist/cli/commands/migrations/unuse/index.js +1 -0
  94. package/dist/cli/commands/migrations/use/command.d.ts +14 -0
  95. package/dist/cli/commands/migrations/use/command.js +24 -0
  96. package/dist/cli/commands/migrations/use/index.d.ts +1 -0
  97. package/dist/cli/commands/migrations/use/index.js +1 -0
  98. package/dist/cli/context/migration/current.d.ts +32 -0
  99. package/dist/cli/context/migration/current.js +30 -0
  100. package/dist/cli/context/migration/index.d.ts +4 -0
  101. package/dist/cli/context/migration/index.js +2 -0
  102. package/dist/cli/context/migration/model.d.ts +14 -0
  103. package/dist/cli/context/migration/model.js +30 -0
  104. package/dist/cli/context/migration/resolve.d.ts +16 -0
  105. package/dist/cli/context/migration/resolve.js +26 -0
  106. package/dist/cli/context/migration/store.d.ts +9 -0
  107. package/dist/cli/context/migration/store.js +99 -0
  108. package/dist/cli/errors/wizard.d.ts +5 -0
  109. package/dist/cli/errors/wizard.js +54 -0
  110. package/dist/cli/errors.d.ts +37 -4
  111. package/dist/cli/errors.js +58 -4
  112. package/dist/cli/input/app.d.ts +8 -0
  113. package/dist/cli/{flags.js → input/app.js} +2 -9
  114. package/dist/cli/input/migration.d.ts +7 -0
  115. package/dist/cli/input/migration.js +17 -0
  116. package/dist/cli/input/pagination.d.ts +11 -0
  117. package/dist/cli/input/pagination.js +8 -0
  118. package/dist/cli/views/migrations/envelope/action-block.d.ts +2 -0
  119. package/dist/cli/views/migrations/envelope/action-block.js +32 -0
  120. package/dist/cli/views/migrations/envelope/envelope.d.ts +3 -0
  121. package/dist/cli/views/migrations/envelope/envelope.js +40 -0
  122. package/dist/cli/views/migrations/envelope/issue-block.d.ts +2 -0
  123. package/dist/cli/views/migrations/envelope/issue-block.js +13 -0
  124. package/dist/cli/views/migrations/notices.d.ts +7 -0
  125. package/dist/cli/views/migrations/notices.js +7 -0
  126. package/dist/commands/access-levels/create.js +1 -1
  127. package/dist/commands/access-levels/get.js +1 -1
  128. package/dist/commands/access-levels/list.js +1 -1
  129. package/dist/commands/access-levels/update.js +1 -1
  130. package/dist/commands/asa/campaigns/create.d.ts +0 -1
  131. package/dist/commands/asa/campaigns/create.js +0 -2
  132. package/dist/commands/asa/campaigns/update.d.ts +0 -1
  133. package/dist/commands/asa/campaigns/update.js +1 -5
  134. package/dist/commands/auth/logout.d.ts +1 -1
  135. package/dist/commands/auth/logout.js +1 -1
  136. package/dist/commands/auth/revoke.d.ts +1 -1
  137. package/dist/commands/auth/revoke.js +1 -1
  138. package/dist/commands/flows/config/get.js +1 -1
  139. package/dist/commands/flows/config/update.js +1 -1
  140. package/dist/commands/flows/config/validate.js +1 -1
  141. package/dist/commands/flows/create.js +1 -1
  142. package/dist/commands/flows/get.js +1 -1
  143. package/dist/commands/flows/list.js +1 -1
  144. package/dist/commands/flows/media/upload.js +1 -1
  145. package/dist/commands/flows/publish.js +1 -1
  146. package/dist/commands/flows/update.js +1 -1
  147. package/dist/commands/migrations/close.d.ts +1 -0
  148. package/dist/commands/migrations/close.js +2 -0
  149. package/dist/commands/migrations/create.d.ts +1 -0
  150. package/dist/commands/migrations/create.js +2 -0
  151. package/dist/commands/migrations/current.d.ts +1 -0
  152. package/dist/commands/migrations/current.js +1 -0
  153. package/dist/commands/migrations/list.d.ts +1 -0
  154. package/dist/commands/migrations/list.js +2 -0
  155. package/dist/commands/migrations/run.d.ts +1 -0
  156. package/dist/commands/migrations/run.js +2 -0
  157. package/dist/commands/migrations/show.d.ts +1 -0
  158. package/dist/commands/migrations/show.js +2 -0
  159. package/dist/commands/migrations/status.d.ts +1 -0
  160. package/dist/commands/migrations/status.js +2 -0
  161. package/dist/commands/migrations/steps.d.ts +1 -0
  162. package/dist/commands/migrations/steps.js +2 -0
  163. package/dist/commands/migrations/unuse.d.ts +1 -0
  164. package/dist/commands/migrations/unuse.js +1 -0
  165. package/dist/commands/migrations/use.d.ts +1 -0
  166. package/dist/commands/migrations/use.js +1 -0
  167. package/dist/commands/paywalls/create.js +1 -1
  168. package/dist/commands/paywalls/get.js +1 -1
  169. package/dist/commands/paywalls/list.js +1 -1
  170. package/dist/commands/paywalls/placements.js +1 -1
  171. package/dist/commands/paywalls/update.js +1 -1
  172. package/dist/commands/placements/create.js +1 -1
  173. package/dist/commands/placements/get.js +1 -1
  174. package/dist/commands/placements/list.js +1 -1
  175. package/dist/commands/placements/update.js +1 -1
  176. package/dist/commands/products/create.js +1 -1
  177. package/dist/commands/products/get.js +1 -1
  178. package/dist/commands/products/list.js +1 -1
  179. package/dist/commands/products/update.js +1 -1
  180. package/dist/commands/segments/get.js +1 -1
  181. package/dist/commands/segments/list.js +1 -1
  182. package/dist/lib/asa-flags.js +1 -3
  183. package/dist/sdk/adapty/errors.d.ts +4 -0
  184. package/dist/sdk/adapty/errors.js +25 -0
  185. package/dist/sdk/adapty/index.d.ts +5 -0
  186. package/dist/sdk/adapty/index.js +29 -3
  187. package/dist/sdk/adapty/migrations/action.d.ts +15 -0
  188. package/dist/sdk/adapty/migrations/action.js +17 -0
  189. package/dist/sdk/adapty/migrations/close.d.ts +16 -0
  190. package/dist/sdk/adapty/migrations/close.js +7 -0
  191. package/dist/sdk/adapty/migrations/create.d.ts +16 -0
  192. package/dist/sdk/adapty/migrations/create.js +43 -0
  193. package/dist/sdk/adapty/migrations/index.d.ts +9 -0
  194. package/dist/sdk/adapty/migrations/index.js +4 -0
  195. package/dist/sdk/adapty/migrations/model.d.ts +100 -0
  196. package/dist/sdk/adapty/migrations/model.js +1 -0
  197. package/dist/sdk/adapty/migrations/resource.d.ts +27 -0
  198. package/dist/sdk/adapty/migrations/resource.js +52 -0
  199. package/dist/sdk/adapty/migrations/wait.d.ts +6 -0
  200. package/dist/sdk/adapty/migrations/wait.js +9 -0
  201. package/oclif.manifest.json +2119 -1528
  202. package/package.json +4 -1
  203. package/dist/cli/commands/auth/logout.js +0 -28
  204. package/dist/cli/flags.d.ts +0 -20
@@ -5,6 +5,10 @@
5
5
  *
6
6
  * Wired once in createAdapty: which shapes a service speaks is product knowledge, and ASA speaks
7
7
  * another — which is why the transport takes the parser as a parameter.
8
+ *
9
+ * The Wizard Service sits behind the same transport (section 4.5 of the CLI–WS contract) and
10
+ * words its rejection as `{ error: { code, message } }` (`WizardError`) — one shape this parser
11
+ * now reads too, alongside the ones the developer API sends.
8
12
  */
9
13
  export const developerErrorParser = (_status, body) => {
10
14
  if (typeof body !== 'object' || body === null) {
@@ -16,11 +20,32 @@ export const developerErrorParser = (_status, body) => {
16
20
  // than an HTTP status when the server sends no field errors
17
21
  return { code: errorCode, message: fieldMessages(errors) ?? errorCode };
18
22
  }
23
+ if (typeof error === 'object' && error !== null) {
24
+ const { code, message } = error;
25
+ if (typeof code === 'string' && code !== '') {
26
+ return { code, message: typeof message === 'string' && message !== '' ? message : code };
27
+ }
28
+ }
19
29
  if (typeof error === 'string' && error !== '') {
20
30
  return { code: error, message: error };
21
31
  }
32
+ if (Array.isArray(errors)) {
33
+ return listedErrors(errors);
34
+ }
22
35
  return {};
23
36
  };
37
+ /** `{ errors: [{ detail, code, status }] }`: how the API turns a token away, `not_authenticated` on a 403. */
38
+ const listedErrors = (errors) => {
39
+ const items = errors.filter(item => typeof item === 'object' && item !== null);
40
+ const details = items.map(item => item.detail).filter(isText);
41
+ const code = items.map(item => item.code).find(isText);
42
+ const message = details.length > 0 ? details.join('; ') : code;
43
+ if (message === undefined) {
44
+ return {};
45
+ }
46
+ return code === undefined ? { message } : { code, message };
47
+ };
48
+ const isText = (value) => typeof value === 'string' && value !== '';
24
49
  /** `non_field_errors` is the server's name for "about the request as a whole": printed bare. */
25
50
  const fieldMessages = (errors) => {
26
51
  if (typeof errors !== 'object' || errors === null) {
@@ -1,6 +1,7 @@
1
1
  import type { AccessLevelsApi } from './access-levels.js';
2
2
  import type { AppsApi } from './apps/index.js';
3
3
  import type { AuthApi } from './auth/index.js';
4
+ import type { MigrationApi } from './migrations/index.js';
4
5
  import type { Clock } from '../core/clock.js';
5
6
  import type { RetryAttempt } from '../core/http/index.js';
6
7
  export { platforms } from './apps/index.js';
@@ -8,6 +9,7 @@ export { developerErrorParser } from './errors.js';
8
9
  export type { AccessLevel, AccessLevelList, AccessLevelsApi } from './access-levels.js';
9
10
  export type { AppDetail, AppsApi, AppSummary, CreateAppInput, UpdateAppInput } from './apps/index.js';
10
11
  export type { AuthApi, AuthUser, IssuedToken } from './auth/index.js';
12
+ export type { Action, ActionKind, AvailableFlow, CreateMigrationInput, Envelope, Issue, JsonSchema, Migration, MigrationApi, MigrationList, MigrationState, Progress, ResourceRef, Step, StepStatus, WaitOptions, WizardError, } from './migrations/index.js';
11
13
  export type { PageParams, Paginated, Pagination } from './pagination.js';
12
14
  /** Exported because the adapter compares the resolved URL with it and warns when they differ. */
13
15
  export declare const DEFAULT_ADAPTY_API_URL = "https://api-admin.adapty.io/api/v1/developer";
@@ -20,6 +22,8 @@ export type AdaptyOptions = {
20
22
  baseUrl?: string | undefined;
21
23
  clock?: Clock | undefined;
22
24
  fetch?: typeof globalThis.fetch | undefined;
25
+ /** Whether a person is watching the caller, for the server's audit trail. */
26
+ interactive?: boolean | undefined;
23
27
  onRetry?: ((info: RetryAttempt) => void) | undefined;
24
28
  signal?: AbortSignal | undefined;
25
29
  /** Without a token only auth is usable — that is how login builds the sdk. */
@@ -31,6 +35,7 @@ export type Adapty = {
31
35
  accessLevels: AccessLevelsApi;
32
36
  apps: AppsApi;
33
37
  auth: AuthApi;
38
+ migrations: MigrationApi;
34
39
  };
35
40
  /** The assembly point of the developer API: one transport, resources on top of it. */
36
41
  export declare const createAdapty: (options?: AdaptyOptions) => Adapty;
@@ -1,27 +1,53 @@
1
+ import { systemClock } from '../core/clock.js';
1
2
  import { createHttp } from '../core/http/index.js';
2
3
  import { accessLevels } from './access-levels.js';
3
4
  import { apps } from './apps/index.js';
4
5
  import { auth } from './auth/index.js';
5
6
  import { developerErrorParser } from './errors.js';
7
+ import { migrations } from './migrations/index.js';
6
8
  export { platforms } from './apps/index.js';
7
9
  export { developerErrorParser } from './errors.js';
8
10
  /** Exported because the adapter compares the resolved URL with it and warns when they differ. */
9
11
  export const DEFAULT_ADAPTY_API_URL = 'https://api-admin.adapty.io/api/v1/developer';
12
+ /**
13
+ * Who is calling, on every request of both clients: `User-Agent` says which program, section 3's
14
+ * `X-Adapty-Interactive` says whether a person is watching it. Both are claims a client makes about
15
+ * itself, so the server may log them and choose a format by them, never grant anything on them.
16
+ */
17
+ const callerHeaders = (options) => {
18
+ const headers = {};
19
+ if (options.userAgent !== undefined) {
20
+ headers['user-agent'] = options.userAgent;
21
+ }
22
+ // Sent as `false` too: "nobody was watching" is the half of the audit trail that matters.
23
+ if (options.interactive !== undefined) {
24
+ headers['x-adapty-interactive'] = String(options.interactive);
25
+ }
26
+ return Object.keys(headers).length === 0 ? undefined : headers;
27
+ };
10
28
  /** The assembly point of the developer API: one transport, resources on top of it. */
11
29
  export const createAdapty = (options = {}) => {
12
- const http = createHttp({
30
+ const transport = {
13
31
  baseUrl: options.baseUrl ?? DEFAULT_ADAPTY_API_URL,
14
32
  clock: options.clock,
15
33
  fetch: options.fetch,
16
- headers: options.userAgent === undefined ? undefined : { 'user-agent': options.userAgent },
34
+ headers: callerHeaders(options),
17
35
  onRetry: options.onRetry,
18
36
  parseError: developerErrorParser,
19
37
  signal: options.signal,
20
38
  token: options.token,
21
- });
39
+ };
40
+ const http = createHttp(transport);
41
+ // Same host, same token, another service: /migrations is proxied through to the Wizard
42
+ // Service, which is not Django and answers 404 to the trailing slash the rest of this API
43
+ // requires. One client per convention, so neither resource has to remember the other's.
44
+ const wizard = createHttp({ ...transport, trailingSlash: false });
22
45
  return {
23
46
  accessLevels: accessLevels(http),
24
47
  apps: apps(http),
25
48
  auth: auth(http),
49
+ // The clock is a dependency here too, not only in the transport: `waitFor` sleeps
50
+ // between polls, and a test must be able to do that instantly.
51
+ migrations: migrations(wizard, options.clock ?? systemClock),
26
52
  };
27
53
  };
@@ -0,0 +1,15 @@
1
+ import type { Issue } from '../../core/errors.js';
2
+ /** Pass the revision you read; the server rejects stale revisions with 409 revision_conflict. */
3
+ export type RunActionInput = {
4
+ expectedRevision: number;
5
+ input?: unknown;
6
+ };
7
+ type RunActionRequest = {
8
+ expected_revision: number;
9
+ input: Record<string, unknown>;
10
+ };
11
+ /** Validate the input shape locally; the server validates its fields against the action schema. */
12
+ export declare const validateActionInput: (input: unknown) => Issue[];
13
+ /** Omitted input is sent as an empty object. */
14
+ export declare const toRunActionRequest: ({ expectedRevision, input }: RunActionInput) => RunActionRequest;
15
+ export {};
@@ -0,0 +1,17 @@
1
+ const isPlainObject = (value) => {
2
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
3
+ };
4
+ /** Validate the input shape locally; the server validates its fields against the action schema. */
5
+ export const validateActionInput = (input) => {
6
+ if (input === undefined || isPlainObject(input)) {
7
+ return [];
8
+ }
9
+ return [{ message: 'must be a JSON object', path: 'input' }];
10
+ };
11
+ /** Omitted input is sent as an empty object. */
12
+ export const toRunActionRequest = ({ expectedRevision, input }) => {
13
+ return {
14
+ expected_revision: expectedRevision,
15
+ input: isPlainObject(input) ? input : {},
16
+ };
17
+ };
@@ -0,0 +1,16 @@
1
+ import type { Issue } from '../../core/errors.js';
2
+ /** Both outcomes permanently close the migration. */
3
+ export type CloseOutcome = 'cancel' | 'finish';
4
+ /** Accept strings so invalid outcomes produce a validation error for any caller. */
5
+ export type CloseMigrationInput = {
6
+ expectedRevision: number;
7
+ outcome: string;
8
+ };
9
+ type CloseMigrationRequest = {
10
+ expected_revision: number;
11
+ outcome: string;
12
+ };
13
+ export declare const validateCloseMigration: ({ outcome }: CloseMigrationInput) => Issue[];
14
+ /** The server rejects a stale revision with 409. */
15
+ export declare const toCloseRequest: ({ expectedRevision, outcome }: CloseMigrationInput) => CloseMigrationRequest;
16
+ export {};
@@ -0,0 +1,7 @@
1
+ const OUTCOMES = new Set(['cancel', 'finish']);
2
+ export const validateCloseMigration = ({ outcome }) => (OUTCOMES.has(outcome) ? [] : [{ message: 'must be `finish` or `cancel`', path: 'outcome' }]);
3
+ /** The server rejects a stale revision with 409. */
4
+ export const toCloseRequest = ({ expectedRevision, outcome }) => ({
5
+ expected_revision: expectedRevision,
6
+ outcome,
7
+ });
@@ -0,0 +1,16 @@
1
+ import type { Issue } from '../../core/errors.js';
2
+ /** Keep fields optional so incomplete input produces a validation error. */
3
+ export type CreateMigrationInput = {
4
+ appId?: string | undefined;
5
+ appName?: string | undefined;
6
+ flow?: string | undefined;
7
+ };
8
+ type CreateMigrationRequest = {
9
+ app_id?: string;
10
+ app_name?: string;
11
+ flow: string;
12
+ };
13
+ /** Accept either a new app name or an existing app ID with a flow. Issue paths identify CLI flags. */
14
+ export declare const validateCreateMigration: (input: CreateMigrationInput) => Issue[];
15
+ export declare const toCreateRequest: (input: CreateMigrationInput) => CreateMigrationRequest;
16
+ export {};
@@ -0,0 +1,43 @@
1
+ /** The main flow creates the Adapty app; other flows require an existing app. */
2
+ const MAIN_FLOW = 'main';
3
+ /** Accept either a new app name or an existing app ID with a flow. Issue paths identify CLI flags. */
4
+ export const validateCreateMigration = (input) => {
5
+ const { appId, appName, flow } = input;
6
+ if (appName === undefined && flow === undefined && appId === undefined) {
7
+ return [{ message: 'pass --name to migrate into a new app, or --flow with --app for an existing one' }];
8
+ }
9
+ const issues = [];
10
+ if (appName !== undefined) {
11
+ if (appName.trim() === '') {
12
+ issues.push({ message: 'must not be empty', path: 'name' });
13
+ }
14
+ if (flow !== undefined || appId !== undefined) {
15
+ issues.push({ message: 'starts the main flow and names the app it creates: --flow and --app do not apply', path: 'name' });
16
+ }
17
+ return issues;
18
+ }
19
+ if (flow === undefined) {
20
+ issues.push({ message: 'required unless --name is given', path: 'flow' });
21
+ }
22
+ else if (flow.trim() === '') {
23
+ issues.push({ message: 'must not be empty', path: 'flow' });
24
+ }
25
+ else if (flow === MAIN_FLOW) {
26
+ issues.push({ message: 'main always creates a new Adapty app: use --name instead of --flow main --app', path: 'flow' });
27
+ }
28
+ if (appId === undefined) {
29
+ issues.push({ message: 'required with --flow: the app the flow runs for', path: 'app' });
30
+ }
31
+ return issues;
32
+ };
33
+ export const toCreateRequest = (input) => {
34
+ const { appId, appName, flow } = input;
35
+ if (appName !== undefined) {
36
+ return { app_name: appName, flow: MAIN_FLOW };
37
+ }
38
+ if (appId === undefined || flow === undefined) {
39
+ // The resource validates first; reaching this branch means a caller skipped validation.
40
+ throw new Error('createMigration needs either appName, or appId with flow');
41
+ }
42
+ return { app_id: appId, flow };
43
+ };
@@ -0,0 +1,9 @@
1
+ export { validateActionInput } from './action.js';
2
+ export { validateCloseMigration } from './close.js';
3
+ export { validateCreateMigration } from './create.js';
4
+ export { migrations } from './resource.js';
5
+ export type { RunActionInput } from './action.js';
6
+ export type { CloseMigrationInput, CloseOutcome } from './close.js';
7
+ export type { CreateMigrationInput } from './create.js';
8
+ export type { MigrationApi, WaitOptions } from './resource.js';
9
+ export type { Action, ActionKind, AvailableFlow, Envelope, Issue, JsonSchema, Migration, MigrationList, MigrationState, Progress, ResourceRef, Step, StepStatus, WizardError, } from './model.js';
@@ -0,0 +1,4 @@
1
+ export { validateActionInput } from './action.js';
2
+ export { validateCloseMigration } from './close.js';
3
+ export { validateCreateMigration } from './create.js';
4
+ export { migrations } from './resource.js';
@@ -0,0 +1,100 @@
1
+ export type MigrationState = 'running' | 'action_required' | 'completed' | 'failed' | 'canceled';
2
+ export type ActionKind = 'input' | 'upload' | 'external';
3
+ export type StepStatus = 'locked' | 'active' | 'done';
4
+ export type JsonSchema = Record<string, unknown>;
5
+ export type Envelope<TResult = unknown> = {
6
+ migration: Migration;
7
+ steps: Step[];
8
+ issues: Issue[];
9
+ next_actions: Action[];
10
+ available_actions: Action[];
11
+ resources: ResourceRef[];
12
+ result: TResult | null;
13
+ };
14
+ export type MigrationList = {
15
+ items: Migration[];
16
+ available: AvailableFlow[];
17
+ };
18
+ export type AvailableFlow = {
19
+ flow: string;
20
+ title: string;
21
+ detail: string | null;
22
+ app: {
23
+ id: string;
24
+ name: string;
25
+ };
26
+ };
27
+ export type Migration = {
28
+ id: string;
29
+ flow: string;
30
+ revision: number;
31
+ /** Known values are MigrationState; newer servers may introduce others. */
32
+ state: string;
33
+ app: {
34
+ id: string;
35
+ name: string;
36
+ } | null;
37
+ poll_after_seconds: number;
38
+ progress: Progress | null;
39
+ summary: string;
40
+ created_at: string;
41
+ updated_at: string;
42
+ };
43
+ export type Progress = {
44
+ done: number;
45
+ total: number | null;
46
+ unit: string;
47
+ };
48
+ export type Step = {
49
+ step_id: string;
50
+ title: string;
51
+ status: StepStatus;
52
+ summary: string | null;
53
+ };
54
+ export type Issue = {
55
+ code: string;
56
+ title: string;
57
+ detail: string | null;
58
+ step_id: string | null;
59
+ action_id: string | null;
60
+ };
61
+ type ActionBase = {
62
+ action_id: string;
63
+ step_id: string;
64
+ title: string;
65
+ detail: string | null;
66
+ reads: string[];
67
+ confirm: string | null;
68
+ };
69
+ export type Action = ActionBase & ({
70
+ kind: 'input';
71
+ input_schema: JsonSchema | null;
72
+ } | {
73
+ kind: 'upload';
74
+ } | {
75
+ kind: 'external';
76
+ href: string;
77
+ } | {
78
+ kind: string;
79
+ href?: string;
80
+ });
81
+ export type ResourceRef = {
82
+ name: string;
83
+ title: string;
84
+ };
85
+ export type WizardError = {
86
+ error: {
87
+ code: string;
88
+ message: string;
89
+ detail: string | null;
90
+ next_step: string | null;
91
+ retryable: boolean;
92
+ retry_after_seconds: number | null;
93
+ fields: {
94
+ path: string;
95
+ message: string;
96
+ }[];
97
+ request_id: string;
98
+ };
99
+ };
100
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ import type { RunActionInput } from './action.js';
2
+ import type { CloseMigrationInput } from './close.js';
3
+ import type { CreateMigrationInput } from './create.js';
4
+ import type { Envelope, MigrationList } from './model.js';
5
+ import type { Clock } from '../../core/clock.js';
6
+ import type { Http } from '../../core/http/index.js';
7
+ export type WaitOptions = {
8
+ /** Called before each pause with the latest response and the upcoming delay. */
9
+ onPoll?: ((envelope: Envelope, delayMs: number) => void) | undefined;
10
+ signal?: AbortSignal | undefined;
11
+ timeoutMs?: number | undefined;
12
+ };
13
+ export declare const migrations: (http: Http, clock: Clock) => {
14
+ get: (id: string) => Promise<Envelope>;
15
+ list: () => Promise<MigrationList>;
16
+ resource: <TResult = unknown>(id: string, name: string) => Promise<Envelope<TResult>>;
17
+ create: (input: CreateMigrationInput) => Promise<Envelope>;
18
+ /** Closing does not require an offered action. */
19
+ close: (id: string, input: CloseMigrationInput) => Promise<Envelope>;
20
+ /**
21
+ * Poll until the revision changes or the state leaves running.
22
+ * Return the last response when the wait budget runs out.
23
+ */
24
+ waitFor: (id: string, options?: WaitOptions) => Promise<Envelope>;
25
+ runAction: (id: string, actionId: string, input: RunActionInput) => Promise<Envelope>;
26
+ };
27
+ export type MigrationApi = ReturnType<typeof migrations>;
@@ -0,0 +1,52 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { assertValid } from '../../core/validation.js';
3
+ import { toRunActionRequest, validateActionInput } from './action.js';
4
+ import { toCloseRequest, validateCloseMigration } from './close.js';
5
+ import { toCreateRequest, validateCreateMigration } from './create.js';
6
+ import { DEFAULT_TIMEOUT_MS, hasMoved, pollDelayMs } from './wait.js';
7
+ /** Reuse one idempotency key across retries to avoid applying the same write twice. */
8
+ const write = () => ({
9
+ headers: { 'idempotency-key': randomUUID() },
10
+ idempotent: true,
11
+ });
12
+ export const migrations = (http, clock) => ({
13
+ get: (id) => http.get(`/migrations/${id}`),
14
+ list: () => http.get('/migrations'),
15
+ resource: (id, name) => {
16
+ return http.get(`/migrations/${id}/resources/${name}`);
17
+ },
18
+ create: async (input) => {
19
+ assertValid(validateCreateMigration(input));
20
+ return http.post('/migrations', toCreateRequest(input), write());
21
+ },
22
+ /** Closing does not require an offered action. */
23
+ close: async (id, input) => {
24
+ assertValid(validateCloseMigration(input));
25
+ return http.post(`/migrations/${id}/close`, toCloseRequest(input), write());
26
+ },
27
+ /**
28
+ * Poll until the revision changes or the state leaves running.
29
+ * Return the last response when the wait budget runs out.
30
+ */
31
+ waitFor: async (id, options = {}) => {
32
+ const deadline = clock.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
33
+ let envelope = await http.get(`/migrations/${id}`);
34
+ const baseline = envelope.migration.revision;
35
+ while (!hasMoved(envelope, baseline)) {
36
+ const delayMs = pollDelayMs(envelope);
37
+ // Stop if the next pause would exceed the deadline.
38
+ if (clock.now() + delayMs > deadline) {
39
+ return envelope;
40
+ }
41
+ options.onPoll?.(envelope, delayMs);
42
+ await clock.sleep(delayMs, options.signal);
43
+ envelope = await http.get(`/migrations/${id}`);
44
+ }
45
+ return envelope;
46
+ },
47
+ runAction: async (id, actionId, input) => {
48
+ assertValid(validateActionInput(input.input));
49
+ const path = `/migrations/${id}/actions/${actionId}`;
50
+ return http.post(path, toRunActionRequest(input), write());
51
+ },
52
+ });
@@ -0,0 +1,6 @@
1
+ import type { Envelope } from './model.js';
2
+ export declare const DEFAULT_TIMEOUT_MS = 120000;
3
+ /** Use the server's delay, with a five-second minimum to avoid rapid polling. */
4
+ export declare const pollDelayMs: (envelope: Envelope) => number;
5
+ /** Unknown states also end the wait. */
6
+ export declare const hasMoved: (envelope: Envelope, baselineRevision: number) => boolean;
@@ -0,0 +1,9 @@
1
+ const MIN_POLL_SECONDS = 5;
2
+ export const DEFAULT_TIMEOUT_MS = 120_000;
3
+ /** Use the server's delay, with a five-second minimum to avoid rapid polling. */
4
+ export const pollDelayMs = (envelope) => Math.max(envelope.migration.poll_after_seconds, MIN_POLL_SECONDS) * 1000;
5
+ /** Unknown states also end the wait. */
6
+ export const hasMoved = (envelope, baselineRevision) => {
7
+ const { revision, state } = envelope.migration;
8
+ return revision !== baselineRevision || state !== 'running';
9
+ };