@alienplatform/sdk 1.14.1 → 2.0.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.
Files changed (160) hide show
  1. package/dist/dist.js +371 -275
  2. package/dist/index.d.ts +22 -28
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +3 -14356
  5. package/dist/native.d.ts +26 -0
  6. package/dist/native.d.ts.map +1 -0
  7. package/dist/native.js +2 -0
  8. package/dist/worker-runtime/channel.d.ts +25 -0
  9. package/dist/worker-runtime/channel.d.ts.map +1 -0
  10. package/dist/worker-runtime/errors.d.ts +79 -0
  11. package/dist/worker-runtime/errors.d.ts.map +1 -0
  12. package/dist/worker-runtime/event-loop.d.ts +43 -0
  13. package/dist/worker-runtime/event-loop.d.ts.map +1 -0
  14. package/dist/{generated → worker-runtime/generated}/control.d.ts +15 -15
  15. package/dist/worker-runtime/generated/control.d.ts.map +1 -0
  16. package/dist/worker-runtime/generated/google/protobuf/duration.d.ts.map +1 -0
  17. package/dist/worker-runtime/generated/google/protobuf/timestamp.d.ts.map +1 -0
  18. package/dist/{generated → worker-runtime/generated}/wait_until.d.ts +29 -29
  19. package/dist/worker-runtime/generated/wait_until.d.ts.map +1 -0
  20. package/dist/worker-runtime/grpc-utils.d.ts +23 -0
  21. package/dist/worker-runtime/grpc-utils.d.ts.map +1 -0
  22. package/dist/worker-runtime/index.d.ts +33 -0
  23. package/dist/worker-runtime/index.d.ts.map +1 -0
  24. package/dist/{src.js → worker-runtime/index.js} +9370 -4035
  25. package/dist/worker-runtime/registry.d.ts +169 -0
  26. package/dist/worker-runtime/registry.d.ts.map +1 -0
  27. package/dist/worker-runtime/service-definitions.d.ts +12 -0
  28. package/dist/worker-runtime/service-definitions.d.ts.map +1 -0
  29. package/dist/worker-runtime/wait-until-manager.d.ts +34 -0
  30. package/dist/worker-runtime/wait-until-manager.d.ts.map +1 -0
  31. package/package.json +18 -38
  32. package/.turbo/turbo-build.log +0 -30
  33. package/AGENTS.md +0 -70
  34. package/dist/bearerTokenAuthenticationPolicy.js +0 -3028
  35. package/dist/bindings/artifact-registry.d.ts +0 -88
  36. package/dist/bindings/artifact-registry.d.ts.map +0 -1
  37. package/dist/bindings/build.d.ts +0 -67
  38. package/dist/bindings/build.d.ts.map +0 -1
  39. package/dist/bindings/index.d.ts +0 -13
  40. package/dist/bindings/index.d.ts.map +0 -1
  41. package/dist/bindings/kv.d.ts +0 -113
  42. package/dist/bindings/kv.d.ts.map +0 -1
  43. package/dist/bindings/postgres.d.ts +0 -90
  44. package/dist/bindings/postgres.d.ts.map +0 -1
  45. package/dist/bindings/postgres.test.d.ts +0 -2
  46. package/dist/bindings/postgres.test.d.ts.map +0 -1
  47. package/dist/bindings/queue.d.ts +0 -81
  48. package/dist/bindings/queue.d.ts.map +0 -1
  49. package/dist/bindings/service-account.d.ts +0 -46
  50. package/dist/bindings/service-account.d.ts.map +0 -1
  51. package/dist/bindings/storage.d.ts +0 -164
  52. package/dist/bindings/storage.d.ts.map +0 -1
  53. package/dist/bindings/vault.d.ts +0 -65
  54. package/dist/bindings/vault.d.ts.map +0 -1
  55. package/dist/bindings/worker.d.ts +0 -72
  56. package/dist/bindings/worker.d.ts.map +0 -1
  57. package/dist/channel.d.ts +0 -39
  58. package/dist/channel.d.ts.map +0 -1
  59. package/dist/checksum.js +0 -6176
  60. package/dist/commands/client.d.ts +0 -47
  61. package/dist/commands/client.d.ts.map +0 -1
  62. package/dist/commands/errors.d.ts +0 -198
  63. package/dist/commands/errors.d.ts.map +0 -1
  64. package/dist/commands/index.d.ts +0 -23
  65. package/dist/commands/index.d.ts.map +0 -1
  66. package/dist/commands/index.js +0 -387
  67. package/dist/commands/types.d.ts +0 -40
  68. package/dist/commands/types.d.ts.map +0 -1
  69. package/dist/commands.d.ts +0 -55
  70. package/dist/commands.d.ts.map +0 -1
  71. package/dist/context.d.ts +0 -182
  72. package/dist/context.d.ts.map +0 -1
  73. package/dist/dist-cjs.js +0 -15258
  74. package/dist/dist2.js +0 -1239
  75. package/dist/errors.d.ts +0 -359
  76. package/dist/errors.d.ts.map +0 -1
  77. package/dist/es5.js +0 -1720
  78. package/dist/esm.js +0 -18068
  79. package/dist/esm2.js +0 -3166
  80. package/dist/event-streams.js +0 -770
  81. package/dist/events.d.ts +0 -163
  82. package/dist/events.d.ts.map +0 -1
  83. package/dist/generated/artifact_registry.d.ts +0 -302
  84. package/dist/generated/artifact_registry.d.ts.map +0 -1
  85. package/dist/generated/build.d.ts +0 -184
  86. package/dist/generated/build.d.ts.map +0 -1
  87. package/dist/generated/container.d.ts +0 -101
  88. package/dist/generated/container.d.ts.map +0 -1
  89. package/dist/generated/control.d.ts.map +0 -1
  90. package/dist/generated/google/protobuf/duration.d.ts.map +0 -1
  91. package/dist/generated/google/protobuf/timestamp.d.ts.map +0 -1
  92. package/dist/generated/kv.d.ts +0 -182
  93. package/dist/generated/kv.d.ts.map +0 -1
  94. package/dist/generated/queue.d.ts +0 -127
  95. package/dist/generated/queue.d.ts.map +0 -1
  96. package/dist/generated/service_account.d.ts +0 -112
  97. package/dist/generated/service_account.d.ts.map +0 -1
  98. package/dist/generated/storage.d.ts +0 -784
  99. package/dist/generated/storage.d.ts.map +0 -1
  100. package/dist/generated/vault.d.ts +0 -107
  101. package/dist/generated/vault.d.ts.map +0 -1
  102. package/dist/generated/wait_until.d.ts.map +0 -1
  103. package/dist/generated/worker.d.ts +0 -107
  104. package/dist/generated/worker.d.ts.map +0 -1
  105. package/dist/global.d.ts +0 -208
  106. package/dist/global.d.ts.map +0 -1
  107. package/dist/grpc-utils.d.ts +0 -25
  108. package/dist/grpc-utils.d.ts.map +0 -1
  109. package/dist/jws.js +0 -611
  110. package/dist/open.js +0 -461
  111. package/dist/rolldown-runtime.js +0 -44
  112. package/dist/src2.js +0 -114032
  113. package/dist/types.d.ts +0 -337
  114. package/dist/types.d.ts.map +0 -1
  115. package/dist/wait-until.d.ts +0 -115
  116. package/dist/wait-until.d.ts.map +0 -1
  117. package/scripts/generate-proto.sh +0 -46
  118. package/src/bindings/AGENTS.md +0 -105
  119. package/src/bindings/artifact-registry.ts +0 -317
  120. package/src/bindings/build.ts +0 -195
  121. package/src/bindings/index.ts +0 -13
  122. package/src/bindings/kv.ts +0 -240
  123. package/src/bindings/postgres.test.ts +0 -17
  124. package/src/bindings/postgres.ts +0 -446
  125. package/src/bindings/queue.ts +0 -191
  126. package/src/bindings/service-account.ts +0 -113
  127. package/src/bindings/storage.ts +0 -536
  128. package/src/bindings/vault.ts +0 -133
  129. package/src/bindings/worker.ts +0 -164
  130. package/src/channel.ts +0 -102
  131. package/src/commands/client.ts +0 -446
  132. package/src/commands/errors.ts +0 -126
  133. package/src/commands/index.ts +0 -41
  134. package/src/commands/types.ts +0 -52
  135. package/src/commands.ts +0 -76
  136. package/src/context.ts +0 -368
  137. package/src/errors.ts +0 -280
  138. package/src/events.ts +0 -511
  139. package/src/generated/artifact_registry.ts +0 -2096
  140. package/src/generated/build.ts +0 -1311
  141. package/src/generated/container.ts +0 -503
  142. package/src/generated/control.ts +0 -2018
  143. package/src/generated/google/protobuf/duration.ts +0 -196
  144. package/src/generated/google/protobuf/timestamp.ts +0 -226
  145. package/src/generated/kv.ts +0 -1169
  146. package/src/generated/queue.ts +0 -753
  147. package/src/generated/service_account.ts +0 -814
  148. package/src/generated/storage.ts +0 -3820
  149. package/src/generated/vault.ts +0 -543
  150. package/src/generated/wait_until.ts +0 -829
  151. package/src/generated/worker.ts +0 -759
  152. package/src/global.ts +0 -287
  153. package/src/grpc-utils.ts +0 -159
  154. package/src/index.ts +0 -160
  155. package/src/types.ts +0 -397
  156. package/src/wait-until.ts +0 -273
  157. package/tsconfig.json +0 -8
  158. package/tsdown.config.ts +0 -17
  159. /package/dist/{generated → worker-runtime/generated}/google/protobuf/duration.d.ts +0 -0
  160. /package/dist/{generated → worker-runtime/generated}/google/protobuf/timestamp.d.ts +0 -0
@@ -1,164 +0,0 @@
1
- /**
2
- * Worker binding implementation.
3
- *
4
- * Provides direct worker-to-worker invocation.
5
- */
6
-
7
- import { type Channel, createClient } from "nice-grpc"
8
- import {
9
- type WorkerServiceClient as GeneratedClient,
10
- WorkerServiceDefinition,
11
- } from "../generated/worker.js"
12
- import { wrapGrpcCall } from "../grpc-utils.js"
13
- import type { WorkerInvokeRequest, WorkerInvokeResponse } from "../types.js"
14
-
15
- /**
16
- * Worker binding for direct worker invocation.
17
- *
18
- * @example
19
- * ```typescript
20
- * import { worker } from "@alienplatform/sdk"
21
- *
22
- * const processor = worker("image-processor")
23
- *
24
- * // Invoke with JSON body
25
- * const response = await processor.invokeJson("resize-image", {
26
- * imageUrl: "https://...",
27
- * width: 800,
28
- * height: 600,
29
- * })
30
- *
31
- * // Get worker URL
32
- * const url = await processor.getUrl()
33
- * ```
34
- */
35
- export class WorkerBinding {
36
- private readonly client: GeneratedClient
37
- private readonly bindingName: string
38
-
39
- constructor(channel: Channel, bindingName: string) {
40
- this.client = createClient(WorkerServiceDefinition, channel)
41
- this.bindingName = bindingName
42
- }
43
-
44
- /**
45
- * Invoke a worker with raw request data.
46
- *
47
- * @param request - Invocation request
48
- * @returns Worker response
49
- */
50
- async invoke(request: WorkerInvokeRequest): Promise<WorkerInvokeResponse> {
51
- return await wrapGrpcCall(
52
- "WorkerService",
53
- "Invoke",
54
- async () => {
55
- const response = await this.client.invoke({
56
- bindingName: this.bindingName,
57
- targetWorker: request.targetWorker,
58
- method: request.method,
59
- path: request.path,
60
- headers: request.headers ?? {},
61
- body: request.body ?? new Uint8Array(),
62
- timeoutSeconds: request.timeoutMs ? Math.floor(request.timeoutMs / 1000) : undefined,
63
- })
64
- return {
65
- status: response.status,
66
- headers: response.headers,
67
- body: response.body,
68
- }
69
- },
70
- { bindingName: this.bindingName },
71
- )
72
- }
73
-
74
- /**
75
- * Invoke a worker with a JSON body.
76
- *
77
- * @param targetWorker - Target worker identifier
78
- * @param body - JSON body
79
- * @param options - Optional request options
80
- * @returns Parsed JSON response
81
- */
82
- async invokeJson<TRequest, TResponse = unknown>(
83
- targetWorker: string,
84
- body: TRequest,
85
- options?: {
86
- method?: string
87
- path?: string
88
- headers?: Record<string, string>
89
- timeoutMs?: number
90
- },
91
- ): Promise<TResponse> {
92
- const response = await this.invoke({
93
- targetWorker,
94
- method: options?.method ?? "POST",
95
- path: options?.path ?? "/",
96
- headers: {
97
- "content-type": "application/json",
98
- ...options?.headers,
99
- },
100
- body: new TextEncoder().encode(JSON.stringify(body)),
101
- timeoutMs: options?.timeoutMs,
102
- })
103
-
104
- if (response.status >= 400) {
105
- const errorText = new TextDecoder().decode(response.body)
106
- throw new Error(`Worker invocation failed with status ${response.status}: ${errorText}`)
107
- }
108
-
109
- const responseText = new TextDecoder().decode(response.body)
110
- return JSON.parse(responseText) as TResponse
111
- }
112
-
113
- /**
114
- * Invoke a worker with GET request.
115
- *
116
- * @param targetWorker - Target worker identifier
117
- * @param path - Request path
118
- * @param options - Optional request options
119
- * @returns Parsed JSON response
120
- */
121
- async get<TResponse = unknown>(
122
- targetWorker: string,
123
- path = "/",
124
- options?: {
125
- headers?: Record<string, string>
126
- timeoutMs?: number
127
- },
128
- ): Promise<TResponse> {
129
- const response = await this.invoke({
130
- targetWorker,
131
- method: "GET",
132
- path,
133
- headers: options?.headers,
134
- timeoutMs: options?.timeoutMs,
135
- })
136
-
137
- if (response.status >= 400) {
138
- const errorText = new TextDecoder().decode(response.body)
139
- throw new Error(`Worker GET failed with status ${response.status}: ${errorText}`)
140
- }
141
-
142
- const responseText = new TextDecoder().decode(response.body)
143
- return JSON.parse(responseText) as TResponse
144
- }
145
-
146
- /**
147
- * Get the public URL of the worker.
148
- *
149
- * @returns Worker URL if available
150
- */
151
- async getUrl(): Promise<string | undefined> {
152
- return await wrapGrpcCall(
153
- "WorkerService",
154
- "GetWorkerUrl",
155
- async () => {
156
- const response = await this.client.getWorkerUrl({
157
- bindingName: this.bindingName,
158
- })
159
- return response.url
160
- },
161
- { bindingName: this.bindingName },
162
- )
163
- }
164
- }
package/src/channel.ts DELETED
@@ -1,102 +0,0 @@
1
- /**
2
- * gRPC channel management for the Alien bindings SDK.
3
- */
4
-
5
- import { AlienError } from "@alienplatform/core"
6
- import type { ChannelOptions } from "@grpc/grpc-js"
7
- import { type Channel, createChannel } from "nice-grpc"
8
- import { GrpcConnectionError, MissingEnvVarError } from "./errors.js"
9
-
10
- /** Default channel options for gRPC connections */
11
- const DEFAULT_CHANNEL_OPTIONS: ChannelOptions = {
12
- // Explicit message size limits (128 MB) to avoid platform-specific defaults
13
- "grpc.max_send_message_length": 128 * 1024 * 1024,
14
- "grpc.max_receive_message_length": 128 * 1024 * 1024,
15
- }
16
-
17
- /** Environment variable containing the gRPC endpoint */
18
- const GRPC_ENDPOINT_VAR = "ALIEN_BINDINGS_GRPC_ADDRESS"
19
-
20
- /** Cached channels by address */
21
- const channelCache = new Map<string, Channel>()
22
-
23
- /** Re-export the Channel type */
24
- export type GrpcChannel = Channel
25
-
26
- /**
27
- * Get the gRPC endpoint from environment variables.
28
- */
29
- export function getGrpcEndpoint(): string {
30
- const endpoint = process.env[GRPC_ENDPOINT_VAR]
31
- if (!endpoint) {
32
- throw new AlienError(
33
- MissingEnvVarError.create({
34
- variable: GRPC_ENDPOINT_VAR,
35
- description:
36
- "This variable is set by alien-runtime when running inside the Alien environment.",
37
- }),
38
- )
39
- }
40
- return endpoint
41
- }
42
-
43
- /**
44
- * Create a gRPC channel to the specified address.
45
- *
46
- * @param address - The gRPC server address
47
- * @returns The created channel
48
- */
49
- export async function createGrpcChannel(address: string): Promise<Channel> {
50
- try {
51
- return createChannel(address, undefined, DEFAULT_CHANNEL_OPTIONS)
52
- } catch (error) {
53
- throw (await AlienError.from(error)).withContext(
54
- GrpcConnectionError.create({
55
- endpoint: address,
56
- reason: error instanceof Error ? error.message : String(error),
57
- }),
58
- )
59
- }
60
- }
61
-
62
- /**
63
- * Get or create a cached gRPC channel to the specified address.
64
- *
65
- * @param address - The gRPC server address
66
- * @returns The cached or newly created channel
67
- */
68
- export async function getOrCreateChannel(address: string): Promise<Channel> {
69
- let channel = channelCache.get(address)
70
- if (!channel) {
71
- channel = await createGrpcChannel(address)
72
- channelCache.set(address, channel)
73
- }
74
- return channel
75
- }
76
-
77
- /**
78
- * Get a gRPC channel to the alien-runtime using the default env var.
79
- * The channel is cached for reuse across all bindings.
80
- */
81
- export async function getChannel(): Promise<Channel> {
82
- const endpoint = getGrpcEndpoint()
83
- return await getOrCreateChannel(endpoint)
84
- }
85
-
86
- /**
87
- * Close all cached gRPC channels.
88
- * This should be called when shutting down the application.
89
- */
90
- export function closeChannel(): void {
91
- for (const channel of channelCache.values()) {
92
- channel.close()
93
- }
94
- channelCache.clear()
95
- }
96
-
97
- /**
98
- * Reset the cached channels (useful for testing).
99
- */
100
- export function resetChannel(): void {
101
- channelCache.clear()
102
- }
@@ -1,446 +0,0 @@
1
- /**
2
- * Commands Client - Lightweight client for deployment command invocation
3
- *
4
- * Handles command invocation with automatic:
5
- * - Base64 encoding/decoding of params and responses
6
- * - Large payload upload/download via storage
7
- * - Polling with exponential backoff
8
- * - Error handling and timeout
9
- */
10
-
11
- import { AlienError } from "@alienplatform/core"
12
- import {
13
- CommandCreationFailedError,
14
- CommandExpiredError,
15
- CommandTimeoutError,
16
- DeploymentCommandError,
17
- ManagerHttpError,
18
- ResponseDecodingFailedError,
19
- StorageOperationFailedError,
20
- } from "./errors.js"
21
- import type {
22
- BodySpec,
23
- CommandResponse,
24
- CommandState,
25
- CommandStatusResponse,
26
- CommandsClientConfig,
27
- CreateCommandResponse,
28
- InvokeOptions,
29
- } from "./types.js"
30
-
31
- /**
32
- * Base64 encode data
33
- */
34
- function base64Encode(data: string | Record<string, unknown>): string {
35
- const json = typeof data === "string" ? data : JSON.stringify(data)
36
- if (typeof btoa !== "undefined") {
37
- // Browser
38
- return btoa(json)
39
- }
40
- // Node.js
41
- return Buffer.from(json, "utf-8").toString("base64")
42
- }
43
-
44
- /**
45
- * Base64 decode data
46
- */
47
- function base64Decode(encoded: string): string {
48
- if (typeof atob !== "undefined") {
49
- // Browser
50
- return atob(encoded)
51
- }
52
- // Node.js
53
- return Buffer.from(encoded, "base64").toString("utf-8")
54
- }
55
-
56
- /**
57
- * Create inline body spec. Always sends inline — the server handles storage
58
- * decisions transparently (auto-promoting to blob if needed).
59
- */
60
- function createBodySpec(data: string | Record<string, unknown>): BodySpec {
61
- const json = typeof data === "string" ? data : JSON.stringify(data)
62
- return {
63
- mode: "inline",
64
- inlineBase64: base64Encode(json),
65
- }
66
- }
67
-
68
- /**
69
- * Decode response body spec
70
- */
71
- async function decodeBodySpec(
72
- body: BodySpec,
73
- commandId: string,
74
- command: string,
75
- allowLocalStorage: boolean,
76
- ): Promise<unknown> {
77
- if (body.mode === "inline") {
78
- const json = base64Decode(body.inlineBase64)
79
- return JSON.parse(json)
80
- }
81
-
82
- if (body.mode === "storage") {
83
- // Storage mode - download from presigned URL
84
- if (!body.storageGetRequest) {
85
- throw new AlienError(
86
- ResponseDecodingFailedError.create({
87
- commandId,
88
- command,
89
- reason: "Storage response missing storageGetRequest",
90
- }),
91
- )
92
- }
93
-
94
- const request = body.storageGetRequest
95
-
96
- // Check if request has expired
97
- const expiration = new Date(request.expiration)
98
- if (Date.now() > expiration.getTime()) {
99
- throw new AlienError(
100
- StorageOperationFailedError.create({
101
- operation: "download",
102
- url: request.backend.type === "http" ? request.backend.url : "local",
103
- reason: `Presigned request expired at ${expiration.toISOString()}`,
104
- }),
105
- )
106
- }
107
-
108
- // Handle different backend types
109
- if (request.backend.type === "http") {
110
- // HTTP backend (AWS S3, GCP GCS, Azure Blob)
111
- try {
112
- const response = await fetch(request.backend.url, {
113
- method: request.backend.method,
114
- headers: request.backend.headers,
115
- })
116
-
117
- if (!response.ok) {
118
- throw new AlienError(
119
- StorageOperationFailedError.create({
120
- operation: "download",
121
- url: request.backend.url,
122
- reason: `HTTP ${response.status} ${response.statusText}`,
123
- }),
124
- )
125
- }
126
-
127
- const text = await response.text()
128
- return JSON.parse(text)
129
- } catch (error) {
130
- if (error instanceof AlienError) {
131
- throw error
132
- }
133
-
134
- // Wrap fetch/parse errors
135
- const alienError = await AlienError.from(error)
136
- throw alienError.withContext(
137
- StorageOperationFailedError.create({
138
- operation: "download",
139
- url: request.backend.url,
140
- reason: error instanceof Error ? error.message : String(error),
141
- }),
142
- )
143
- }
144
- } else if (request.backend.type === "local") {
145
- // Local backend: Only enabled when explicitly allowed (local dev/testing)
146
- if (!allowLocalStorage) {
147
- throw new AlienError(
148
- StorageOperationFailedError.create({
149
- operation: "download",
150
- url: `local://${request.backend.filePath}`,
151
- reason: "Local storage backend not enabled (set allowLocalStorage: true for local dev)",
152
- }),
153
- )
154
- }
155
-
156
- // Validate path doesn't contain path traversal attempts
157
- const filePath = request.backend.filePath
158
- if (filePath.includes("..")) {
159
- throw new AlienError(
160
- StorageOperationFailedError.create({
161
- operation: "download",
162
- url: `local://${filePath}`,
163
- reason: "Path traversal not allowed in local storage paths",
164
- }),
165
- )
166
- }
167
-
168
- // Dynamically import fs/promises only when needed (local dev)
169
- try {
170
- const { readFile } = await import("node:fs/promises")
171
- const content = await readFile(filePath, "utf-8")
172
- return JSON.parse(content)
173
- } catch (error) {
174
- if (error instanceof AlienError) {
175
- throw error
176
- }
177
-
178
- // Wrap filesystem/parse errors
179
- const alienError = await AlienError.from(error)
180
- throw alienError.withContext(
181
- StorageOperationFailedError.create({
182
- operation: "download",
183
- url: `local://${filePath}`,
184
- reason: error instanceof Error ? error.message : String(error),
185
- }),
186
- )
187
- }
188
- } else {
189
- throw new AlienError(
190
- StorageOperationFailedError.create({
191
- operation: "download",
192
- url: "unknown",
193
- reason: `Unknown storage backend type: ${(request.backend as any).type}`,
194
- }),
195
- )
196
- }
197
- }
198
-
199
- throw new AlienError(
200
- ResponseDecodingFailedError.create({
201
- commandId,
202
- command,
203
- reason: `Unknown body mode: ${(body as any).mode}`,
204
- }),
205
- )
206
- }
207
-
208
- /**
209
- * Check if state is terminal
210
- */
211
- function isTerminalState(state: CommandState): boolean {
212
- return state === "SUCCEEDED" || state === "FAILED" || state === "EXPIRED"
213
- }
214
-
215
- /**
216
- * Commands client for invoking deployment commands
217
- */
218
- export class CommandsClient {
219
- private readonly managerUrl: string
220
- private readonly deploymentId: string
221
- private readonly token: string
222
- private readonly defaultTimeout: number
223
- private readonly allowLocalStorage: boolean
224
-
225
- constructor(config: CommandsClientConfig) {
226
- this.managerUrl = config.managerUrl.replace(/\/+$/, "")
227
- this.deploymentId = config.deploymentId
228
- this.token = config.token
229
- this.defaultTimeout = config.timeout ?? 60_000
230
- this.allowLocalStorage = config.allowLocalStorage ?? false
231
- }
232
-
233
- /**
234
- * Invoke a command on the deployment and wait for response
235
- *
236
- * @param command - Command name (e.g., "generate-report")
237
- * @param params - Command parameters (JSON-serializable)
238
- * @param options - Invocation options
239
- * @returns Decoded response data
240
- */
241
- async invoke<TParams = unknown, TResponse = unknown>(
242
- command: string,
243
- params: TParams,
244
- options?: InvokeOptions,
245
- ): Promise<TResponse> {
246
- const timeout = options?.timeout ?? this.defaultTimeout
247
- const startTime = Date.now()
248
-
249
- // Step 1: Create command
250
- const createResponse = await this.createCommand(command, params, options)
251
-
252
- // Step 2: Poll for completion
253
- const pollInterval = options?.pollInterval ?? 500
254
- const maxPollInterval = options?.maxPollInterval ?? 5000
255
- const pollBackoff = options?.pollBackoff ?? 1.5
256
- let currentInterval = pollInterval
257
-
258
- while (Date.now() - startTime < timeout) {
259
- await this.sleep(currentInterval)
260
-
261
- const status = await this.getCommandStatus(createResponse.commandId)
262
-
263
- if (isTerminalState(status.state)) {
264
- return await this.handleTerminalState(command, status)
265
- }
266
-
267
- // Exponential backoff
268
- currentInterval = Math.min(currentInterval * pollBackoff, maxPollInterval)
269
- }
270
-
271
- // Timeout
272
- const finalStatus = await this.getCommandStatus(createResponse.commandId)
273
- throw new AlienError(
274
- CommandTimeoutError.create({
275
- commandId: createResponse.commandId,
276
- command,
277
- timeoutMs: timeout,
278
- lastState: finalStatus.state,
279
- }),
280
- )
281
- }
282
-
283
- /**
284
- * Create a command
285
- */
286
- private async createCommand(
287
- command: string,
288
- params: unknown,
289
- options?: InvokeOptions,
290
- ): Promise<CreateCommandResponse> {
291
- const url = `${this.managerUrl}/v1/commands`
292
- const body = {
293
- deploymentId: this.deploymentId,
294
- command,
295
- params: createBodySpec(params as string | Record<string, unknown>),
296
- deadline: options?.deadline?.toISOString(),
297
- idempotencyKey: options?.idempotencyKey,
298
- }
299
-
300
- try {
301
- const response = await fetch(url, {
302
- method: "POST",
303
- headers: {
304
- "Content-Type": "application/json",
305
- Authorization: `Bearer ${this.token}`,
306
- },
307
- body: JSON.stringify(body),
308
- })
309
-
310
- if (!response.ok) {
311
- const errorBody = await response.text().catch(() => "")
312
- throw new AlienError(
313
- ManagerHttpError.create({
314
- method: "POST",
315
- url,
316
- status: response.status,
317
- statusText: response.statusText,
318
- body: errorBody,
319
- }),
320
- )
321
- }
322
-
323
- return (await response.json()) as CreateCommandResponse
324
- } catch (error) {
325
- if (error instanceof AlienError) {
326
- throw error
327
- }
328
-
329
- const alienError = await AlienError.from(error)
330
- throw alienError.withContext(
331
- CommandCreationFailedError.create({
332
- deploymentId: this.deploymentId,
333
- command,
334
- reason: error instanceof Error ? error.message : String(error),
335
- }),
336
- )
337
- }
338
- }
339
-
340
- /**
341
- * Get command status
342
- */
343
- private async getCommandStatus(commandId: string): Promise<CommandStatusResponse> {
344
- const url = `${this.managerUrl}/v1/commands/${commandId}`
345
-
346
- try {
347
- const response = await fetch(url, {
348
- method: "GET",
349
- headers: {
350
- Authorization: `Bearer ${this.token}`,
351
- },
352
- })
353
-
354
- if (!response.ok) {
355
- const errorBody = await response.text().catch(() => "")
356
- throw new AlienError(
357
- ManagerHttpError.create({
358
- method: "GET",
359
- url,
360
- status: response.status,
361
- statusText: response.statusText,
362
- body: errorBody,
363
- }),
364
- )
365
- }
366
-
367
- return (await response.json()) as CommandStatusResponse
368
- } catch (error) {
369
- if (error instanceof AlienError) {
370
- throw error
371
- }
372
-
373
- throw await AlienError.from(error)
374
- }
375
- }
376
-
377
- /**
378
- * Handle terminal state and return response
379
- */
380
- private async handleTerminalState<TResponse>(
381
- command: string,
382
- status: CommandStatusResponse,
383
- ): Promise<TResponse> {
384
- if (status.state === "EXPIRED") {
385
- throw new AlienError(
386
- CommandExpiredError.create({
387
- commandId: status.commandId,
388
- command,
389
- }),
390
- )
391
- }
392
-
393
- if (!status.response) {
394
- throw new AlienError(
395
- ResponseDecodingFailedError.create({
396
- commandId: status.commandId,
397
- command,
398
- reason: "Terminal state but no response present",
399
- }),
400
- )
401
- }
402
-
403
- const response: CommandResponse = status.response
404
-
405
- if (response.status === "error") {
406
- throw new AlienError(
407
- DeploymentCommandError.create({
408
- commandId: status.commandId,
409
- command,
410
- errorCode: response.code,
411
- errorMessage: response.message,
412
- errorDetails: response.details ?? undefined,
413
- }),
414
- )
415
- }
416
-
417
- // Decode success response
418
- try {
419
- return (await decodeBodySpec(
420
- response.response,
421
- status.commandId,
422
- command,
423
- this.allowLocalStorage,
424
- )) as TResponse
425
- } catch (error) {
426
- if (error instanceof AlienError) {
427
- throw error
428
- }
429
-
430
- throw new AlienError(
431
- ResponseDecodingFailedError.create({
432
- commandId: status.commandId,
433
- command,
434
- reason: error instanceof Error ? error.message : String(error),
435
- }),
436
- )
437
- }
438
- }
439
-
440
- /**
441
- * Sleep helper
442
- */
443
- private sleep(ms: number): Promise<void> {
444
- return new Promise(resolve => setTimeout(resolve, ms))
445
- }
446
- }