@alienplatform/bindings 1.0.0 → 2.0.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 (56) hide show
  1. package/LICENSE.md +105 -0
  2. package/README.md +59 -0
  3. package/dist/errors.d.ts +56 -0
  4. package/dist/errors.d.ts.map +1 -0
  5. package/dist/factories.d.ts +26 -0
  6. package/dist/factories.d.ts.map +1 -0
  7. package/dist/index.d.ts +19 -1768
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +23 -44772
  10. package/dist/loader.d.ts +171 -0
  11. package/dist/loader.d.ts.map +1 -0
  12. package/dist/loader.js +3712 -0
  13. package/dist/native.d.ts +41 -0
  14. package/dist/native.d.ts.map +1 -0
  15. package/dist/native.js +46 -0
  16. package/dist/types.d.ts +150 -0
  17. package/dist/types.d.ts.map +1 -0
  18. package/package.json +39 -21
  19. package/AGENTS.md +0 -70
  20. package/dist/index.js.map +0 -1
  21. package/scripts/generate-proto.sh +0 -46
  22. package/src/bindings/AGENTS.md +0 -105
  23. package/src/bindings/artifact-registry.ts +0 -314
  24. package/src/bindings/build.ts +0 -195
  25. package/src/bindings/function.ts +0 -164
  26. package/src/bindings/index.ts +0 -12
  27. package/src/bindings/kv.ts +0 -240
  28. package/src/bindings/queue.ts +0 -191
  29. package/src/bindings/service-account.ts +0 -113
  30. package/src/bindings/storage.ts +0 -535
  31. package/src/bindings/vault.ts +0 -133
  32. package/src/channel.ts +0 -94
  33. package/src/commands.ts +0 -76
  34. package/src/context.ts +0 -368
  35. package/src/errors.ts +0 -259
  36. package/src/events.ts +0 -463
  37. package/src/generated/artifact_registry.ts +0 -1934
  38. package/src/generated/build.ts +0 -1263
  39. package/src/generated/container.ts +0 -485
  40. package/src/generated/control.ts +0 -1574
  41. package/src/generated/function.ts +0 -741
  42. package/src/generated/google/protobuf/duration.ts +0 -196
  43. package/src/generated/google/protobuf/timestamp.ts +0 -225
  44. package/src/generated/kv.ts +0 -1137
  45. package/src/generated/queue.ts +0 -729
  46. package/src/generated/service_account.ts +0 -766
  47. package/src/generated/storage.ts +0 -3653
  48. package/src/generated/vault.ts +0 -519
  49. package/src/generated/wait_until.ts +0 -781
  50. package/src/global.ts +0 -316
  51. package/src/grpc-utils.ts +0 -159
  52. package/src/index.ts +0 -156
  53. package/src/types.ts +0 -384
  54. package/src/wait-until.ts +0 -257
  55. package/tsconfig.json +0 -8
  56. package/tsdown.config.ts +0 -14
@@ -1,164 +0,0 @@
1
- /**
2
- * Function binding implementation.
3
- *
4
- * Provides direct function-to-function invocation.
5
- */
6
-
7
- import { type Channel, createClient } from "nice-grpc"
8
- import {
9
- FunctionServiceDefinition,
10
- type FunctionServiceClient as GeneratedClient,
11
- } from "../generated/function.js"
12
- import { wrapGrpcCall } from "../grpc-utils.js"
13
- import type { FunctionInvokeRequest, FunctionInvokeResponse } from "../types.js"
14
-
15
- /**
16
- * Function binding for direct function invocation.
17
- *
18
- * @example
19
- * ```typescript
20
- * import { func } from "@alienplatform/bindings"
21
- *
22
- * const processor = func("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 function URL
32
- * const url = await processor.getUrl()
33
- * ```
34
- */
35
- export class FunctionBinding {
36
- private readonly client: GeneratedClient
37
- private readonly bindingName: string
38
-
39
- constructor(channel: Channel, bindingName: string) {
40
- this.client = createClient(FunctionServiceDefinition, channel)
41
- this.bindingName = bindingName
42
- }
43
-
44
- /**
45
- * Invoke a function with raw request data.
46
- *
47
- * @param request - Invocation request
48
- * @returns Function response
49
- */
50
- async invoke(request: FunctionInvokeRequest): Promise<FunctionInvokeResponse> {
51
- return await wrapGrpcCall(
52
- "FunctionService",
53
- "Invoke",
54
- async () => {
55
- const response = await this.client.invoke({
56
- bindingName: this.bindingName,
57
- targetFunction: request.targetFunction,
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 function with a JSON body.
76
- *
77
- * @param targetFunction - Target function 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
- targetFunction: 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
- targetFunction,
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(`Function 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 function with GET request.
115
- *
116
- * @param targetFunction - Target function identifier
117
- * @param path - Request path
118
- * @param options - Optional request options
119
- * @returns Parsed JSON response
120
- */
121
- async get<TResponse = unknown>(
122
- targetFunction: string,
123
- path = "/",
124
- options?: {
125
- headers?: Record<string, string>
126
- timeoutMs?: number
127
- },
128
- ): Promise<TResponse> {
129
- const response = await this.invoke({
130
- targetFunction,
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(`Function 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 function.
148
- *
149
- * @returns Function URL if available
150
- */
151
- async getUrl(): Promise<string | undefined> {
152
- return await wrapGrpcCall(
153
- "FunctionService",
154
- "GetFunctionUrl",
155
- async () => {
156
- const response = await this.client.getFunctionUrl({
157
- bindingName: this.bindingName,
158
- })
159
- return response.url
160
- },
161
- { bindingName: this.bindingName },
162
- )
163
- }
164
- }
@@ -1,12 +0,0 @@
1
- /**
2
- * Binding exports.
3
- */
4
-
5
- export { Storage } from "./storage.js"
6
- export { Kv } from "./kv.js"
7
- export { Queue } from "./queue.js"
8
- export { Vault } from "./vault.js"
9
- export { Build } from "./build.js"
10
- export { ArtifactRegistry } from "./artifact-registry.js"
11
- export { FunctionBinding } from "./function.js"
12
- export { ServiceAccount } from "./service-account.js"
@@ -1,240 +0,0 @@
1
- /**
2
- * KV binding implementation.
3
- *
4
- * Provides key-value storage operations with TTL support.
5
- */
6
-
7
- import { type Channel, createClient } from "nice-grpc"
8
- import {
9
- type KvServiceClient as GeneratedKvServiceClient,
10
- KvServiceDefinition,
11
- } from "../generated/kv.js"
12
- import { wrapGrpcCall } from "../grpc-utils.js"
13
- import type { KvPutOptions, KvScanResult } from "../types.js"
14
-
15
- /**
16
- * KV binding for key-value storage operations.
17
- *
18
- * @example
19
- * ```typescript
20
- * import { kv } from "@alienplatform/bindings"
21
- *
22
- * const cache = kv("my-cache")
23
- *
24
- * // Set a value
25
- * await cache.set("user:123", { name: "John", email: "john@example.com" })
26
- *
27
- * // Get a value
28
- * const user = await cache.getJson<User>("user:123")
29
- *
30
- * // Set with TTL (expires in 1 hour)
31
- * await cache.set("session:abc", sessionData, { ttlMs: 60 * 60 * 1000 })
32
- *
33
- * // Delete
34
- * await cache.delete("user:123")
35
- *
36
- * // Scan by prefix
37
- * for await (const { key, value } of cache.scan("user:")) {
38
- * console.log(key, value)
39
- * }
40
- * ```
41
- */
42
- export class Kv {
43
- private readonly client: GeneratedKvServiceClient
44
- private readonly bindingName: string
45
-
46
- constructor(channel: Channel, bindingName: string) {
47
- this.client = createClient(KvServiceDefinition, channel)
48
- this.bindingName = bindingName
49
- }
50
-
51
- /**
52
- * Get a value by key.
53
- *
54
- * @param key - Key to retrieve
55
- * @returns Value as Uint8Array, or undefined if not found
56
- */
57
- async get(key: string): Promise<Uint8Array | undefined> {
58
- return await wrapGrpcCall(
59
- "KvService",
60
- "Get",
61
- async () => {
62
- const response = await this.client.get({
63
- bindingName: this.bindingName,
64
- key,
65
- })
66
- return response.value
67
- },
68
- { bindingName: this.bindingName, key },
69
- )
70
- }
71
-
72
- /**
73
- * Get a value as a UTF-8 string.
74
- *
75
- * @param key - Key to retrieve
76
- * @returns Value as string, or undefined if not found
77
- */
78
- async getText(key: string): Promise<string | undefined> {
79
- const value = await this.get(key)
80
- return value ? new TextDecoder().decode(value) : undefined
81
- }
82
-
83
- /**
84
- * Get a value and parse as JSON.
85
- *
86
- * @param key - Key to retrieve
87
- * @returns Parsed JSON value, or undefined if not found
88
- */
89
- async getJson<T = unknown>(key: string): Promise<T | undefined> {
90
- const text = await this.getText(key)
91
- return text ? (JSON.parse(text) as T) : undefined
92
- }
93
-
94
- /**
95
- * Set a value.
96
- *
97
- * @param key - Key to store
98
- * @param value - Value to store (string, Uint8Array, or object for JSON)
99
- * @param options - Optional put options (TTL, ifNotExists)
100
- * @returns True if stored, false if ifNotExists was true and key already exists
101
- */
102
- async set(
103
- key: string,
104
- value: string | Uint8Array | object,
105
- options?: KvPutOptions,
106
- ): Promise<boolean> {
107
- let bytes: Uint8Array
108
-
109
- if (typeof value === "string") {
110
- bytes = new TextEncoder().encode(value)
111
- } else if (value instanceof Uint8Array) {
112
- bytes = value
113
- } else {
114
- bytes = new TextEncoder().encode(JSON.stringify(value))
115
- }
116
-
117
- return await wrapGrpcCall(
118
- "KvService",
119
- "Put",
120
- async () => {
121
- const response = await this.client.put({
122
- bindingName: this.bindingName,
123
- key,
124
- value: bytes,
125
- options: options
126
- ? {
127
- ttlSeconds: options.ttlMs ? Math.floor(options.ttlMs / 1000) : undefined,
128
- ifNotExists: options.ifNotExists ?? false,
129
- }
130
- : undefined,
131
- })
132
- return response.success
133
- },
134
- { bindingName: this.bindingName, key },
135
- )
136
- }
137
-
138
- /**
139
- * Delete a key.
140
- *
141
- * @param key - Key to delete
142
- */
143
- async delete(key: string): Promise<void> {
144
- await wrapGrpcCall(
145
- "KvService",
146
- "Delete",
147
- async () => {
148
- await this.client.delete({
149
- bindingName: this.bindingName,
150
- key,
151
- })
152
- },
153
- { bindingName: this.bindingName, key },
154
- )
155
- }
156
-
157
- /**
158
- * Check if a key exists.
159
- *
160
- * @param key - Key to check
161
- * @returns True if the key exists
162
- */
163
- async exists(key: string): Promise<boolean> {
164
- return await wrapGrpcCall(
165
- "KvService",
166
- "Exists",
167
- async () => {
168
- const response = await this.client.exists({
169
- bindingName: this.bindingName,
170
- key,
171
- })
172
- return response.exists
173
- },
174
- { bindingName: this.bindingName, key },
175
- )
176
- }
177
-
178
- /**
179
- * Scan keys with a prefix.
180
- *
181
- * @param prefix - Prefix to scan for
182
- * @param options - Optional scan options (limit)
183
- * @returns Async iterable of key-value pairs
184
- *
185
- * @example
186
- * ```typescript
187
- * // Scan all users
188
- * for await (const { key, value } of cache.scan("user:")) {
189
- * console.log(key, new TextDecoder().decode(value))
190
- * }
191
- * ```
192
- */
193
- async *scan(
194
- prefix: string,
195
- options?: { limit?: number },
196
- ): AsyncIterable<{ key: string; value: Uint8Array }> {
197
- let cursor: string | undefined
198
-
199
- do {
200
- const result = await this.scanPage(prefix, options?.limit, cursor)
201
-
202
- for (const item of result.items) {
203
- yield item
204
- }
205
-
206
- cursor = result.nextCursor
207
- } while (cursor)
208
- }
209
-
210
- /**
211
- * Scan a single page of results.
212
- *
213
- * @param prefix - Prefix to scan for
214
- * @param limit - Maximum items to return
215
- * @param cursor - Pagination cursor
216
- * @returns Scan result with items and next cursor
217
- */
218
- async scanPage(prefix: string, limit?: number, cursor?: string): Promise<KvScanResult> {
219
- return await wrapGrpcCall(
220
- "KvService",
221
- "ScanPrefix",
222
- async () => {
223
- const response = await this.client.scanPrefix({
224
- bindingName: this.bindingName,
225
- prefix,
226
- limit,
227
- cursor,
228
- })
229
- return {
230
- items: response.items.map(item => ({
231
- key: item.key,
232
- value: item.value,
233
- })),
234
- nextCursor: response.nextCursor,
235
- }
236
- },
237
- { bindingName: this.bindingName },
238
- )
239
- }
240
- }
@@ -1,191 +0,0 @@
1
- /**
2
- * Queue binding implementation.
3
- *
4
- * Provides message queue operations for async task processing.
5
- */
6
-
7
- import { type Channel, createClient } from "nice-grpc"
8
- import {
9
- type QueueServiceClient as GeneratedQueueServiceClient,
10
- type MessagePayload,
11
- type QueueMessage as QueueMessageProto,
12
- QueueServiceDefinition,
13
- } from "../generated/queue.js"
14
- import { wrapGrpcCall } from "../grpc-utils.js"
15
- import type { ReceivedQueueMessage } from "../types.js"
16
-
17
- /**
18
- * Queue binding for message queue operations.
19
- *
20
- * @example
21
- * ```typescript
22
- * import { queue } from "@alienplatform/bindings"
23
- *
24
- * const tasks = queue("task-queue")
25
- *
26
- * // Send a message (JSON)
27
- * await tasks.send("process-image", { imageId: "123", format: "webp" })
28
- *
29
- * // Receive messages
30
- * const messages = await tasks.receive("process-image", 10)
31
- * for (const msg of messages) {
32
- * await processImage(msg.payload)
33
- * await tasks.ack("process-image", msg.receiptHandle)
34
- * }
35
- * ```
36
- */
37
- export class Queue {
38
- private readonly client: GeneratedQueueServiceClient
39
- private readonly bindingName: string
40
-
41
- constructor(channel: Channel, bindingName: string) {
42
- this.client = createClient(QueueServiceDefinition, channel)
43
- this.bindingName = bindingName
44
- }
45
-
46
- /**
47
- * Send a message to a queue.
48
- *
49
- * @param queueName - Name of the queue
50
- * @param payload - Message payload (object for JSON, string for text)
51
- */
52
- async send(queueName: string, payload: unknown): Promise<void> {
53
- const message = this.toProtoPayload(payload)
54
-
55
- await wrapGrpcCall(
56
- "QueueService",
57
- "Send",
58
- async () => {
59
- await this.client.send({
60
- bindingName: this.bindingName,
61
- queue: queueName,
62
- message,
63
- })
64
- },
65
- { bindingName: this.bindingName },
66
- )
67
- }
68
-
69
- /**
70
- * Send a text message to a queue.
71
- *
72
- * @param queueName - Name of the queue
73
- * @param text - Text message
74
- */
75
- async sendText(queueName: string, text: string): Promise<void> {
76
- await wrapGrpcCall(
77
- "QueueService",
78
- "Send",
79
- async () => {
80
- await this.client.send({
81
- bindingName: this.bindingName,
82
- queue: queueName,
83
- message: { text },
84
- })
85
- },
86
- { bindingName: this.bindingName },
87
- )
88
- }
89
-
90
- /**
91
- * Receive messages from a queue.
92
- *
93
- * @param queueName - Name of the queue
94
- * @param maxMessages - Maximum number of messages to receive (1-10)
95
- * @returns Array of messages with receipt handles
96
- */
97
- async receive<T = unknown>(
98
- queueName: string,
99
- maxMessages = 1,
100
- ): Promise<ReceivedQueueMessage<T>[]> {
101
- return await wrapGrpcCall(
102
- "QueueService",
103
- "Receive",
104
- async () => {
105
- const response = await this.client.receive({
106
- bindingName: this.bindingName,
107
- queue: queueName,
108
- maxMessages: Math.min(Math.max(1, maxMessages), 10),
109
- })
110
- return response.messages.map(msg => this.fromProtoMessage<T>(msg))
111
- },
112
- { bindingName: this.bindingName },
113
- )
114
- }
115
-
116
- /**
117
- * Acknowledge a message (remove it from the queue).
118
- *
119
- * @param queueName - Name of the queue
120
- * @param receiptHandle - Receipt handle from the received message
121
- */
122
- async ack(queueName: string, receiptHandle: string): Promise<void> {
123
- await wrapGrpcCall(
124
- "QueueService",
125
- "Ack",
126
- async () => {
127
- await this.client.ack({
128
- bindingName: this.bindingName,
129
- queue: queueName,
130
- receiptHandle,
131
- })
132
- },
133
- { bindingName: this.bindingName },
134
- )
135
- }
136
-
137
- /**
138
- * Process messages from a queue with automatic acknowledgment.
139
- *
140
- * @param queueName - Name of the queue
141
- * @param handler - Handler function for each message
142
- * @param options - Processing options
143
- *
144
- * @example
145
- * ```typescript
146
- * await tasks.process("image-tasks", async (payload) => {
147
- * await processImage(payload.imageId)
148
- * })
149
- * ```
150
- */
151
- async process<T = unknown>(
152
- queueName: string,
153
- handler: (payload: T) => Promise<void>,
154
- options?: { maxMessages?: number },
155
- ): Promise<void> {
156
- const maxMessages = options?.maxMessages ?? 10
157
-
158
- const messages = await this.receive<T>(queueName, maxMessages)
159
-
160
- for (const message of messages) {
161
- await handler(message.payload)
162
- await this.ack(queueName, message.receiptHandle)
163
- }
164
- }
165
-
166
- // Private helpers
167
-
168
- private toProtoPayload(payload: unknown): MessagePayload {
169
- if (typeof payload === "string") {
170
- return { text: payload }
171
- }
172
- return { json: JSON.stringify(payload) }
173
- }
174
-
175
- private fromProtoMessage<T>(proto: QueueMessageProto): ReceivedQueueMessage<T> {
176
- let payload: unknown
177
-
178
- if (proto.payload?.json) {
179
- payload = JSON.parse(proto.payload.json)
180
- } else if (proto.payload?.text) {
181
- payload = proto.payload.text
182
- } else {
183
- payload = null
184
- }
185
-
186
- return {
187
- payload: payload as T,
188
- receiptHandle: proto.receiptHandle,
189
- }
190
- }
191
- }
@@ -1,113 +0,0 @@
1
- /**
2
- * ServiceAccount binding implementation.
3
- *
4
- * Provides identity and impersonation capabilities.
5
- */
6
-
7
- import { type Channel, createClient } from "nice-grpc"
8
- import {
9
- type ServiceAccountServiceClient as GeneratedClient,
10
- type ServiceAccountInfo as ServiceAccountInfoProto,
11
- ServiceAccountServiceDefinition,
12
- } from "../generated/service_account.js"
13
- import { wrapGrpcCall } from "../grpc-utils.js"
14
- import type { ImpersonationRequest, ServiceAccountInfo } from "../types.js"
15
-
16
- /**
17
- * ServiceAccount binding for identity and impersonation.
18
- *
19
- * @example
20
- * ```typescript
21
- * import { serviceAccount } from "@alienplatform/bindings"
22
- *
23
- * const sa = serviceAccount("deployment-account")
24
- *
25
- * // Get account info
26
- * const info = await sa.getInfo()
27
- * if (info.platform === "aws") {
28
- * console.log("Role ARN:", info.roleArn)
29
- * }
30
- *
31
- * // Impersonate the service account
32
- * const config = await sa.impersonate({ durationSeconds: 3600 })
33
- * ```
34
- */
35
- export class ServiceAccount {
36
- private readonly client: GeneratedClient
37
- private readonly bindingName: string
38
-
39
- constructor(channel: Channel, bindingName: string) {
40
- this.client = createClient(ServiceAccountServiceDefinition, channel)
41
- this.bindingName = bindingName
42
- }
43
-
44
- /**
45
- * Get information about the service account.
46
- *
47
- * @returns Platform-specific service account information
48
- */
49
- async getInfo(): Promise<ServiceAccountInfo> {
50
- return await wrapGrpcCall(
51
- "ServiceAccountService",
52
- "GetInfo",
53
- async () => {
54
- const response = await this.client.getInfo({
55
- bindingName: this.bindingName,
56
- })
57
- return this.fromProtoInfo(response.info!)
58
- },
59
- { bindingName: this.bindingName, bindingType: "ServiceAccount" },
60
- )
61
- }
62
-
63
- /**
64
- * Impersonate the service account.
65
- *
66
- * @param request - Impersonation options
67
- * @returns Client configuration for the impersonated identity
68
- */
69
- async impersonate(request?: ImpersonationRequest): Promise<unknown> {
70
- return await wrapGrpcCall(
71
- "ServiceAccountService",
72
- "Impersonate",
73
- async () => {
74
- const response = await this.client.impersonate({
75
- bindingName: this.bindingName,
76
- sessionName: request?.sessionName,
77
- durationSeconds: request?.durationSeconds,
78
- scopes: request?.scopes ?? [],
79
- })
80
- return JSON.parse(response.clientConfigJson)
81
- },
82
- { bindingName: this.bindingName, bindingType: "ServiceAccount" },
83
- )
84
- }
85
-
86
- // Private helpers
87
-
88
- private fromProtoInfo(proto: ServiceAccountInfoProto): ServiceAccountInfo {
89
- if (proto.aws) {
90
- return {
91
- platform: "aws",
92
- roleName: proto.aws.roleName,
93
- roleArn: proto.aws.roleArn,
94
- }
95
- }
96
- if (proto.gcp) {
97
- return {
98
- platform: "gcp",
99
- email: proto.gcp.email,
100
- uniqueId: proto.gcp.uniqueId,
101
- }
102
- }
103
- if (proto.azure) {
104
- return {
105
- platform: "azure",
106
- clientId: proto.azure.clientId,
107
- resourceId: proto.azure.resourceId,
108
- principalId: proto.azure.principalId,
109
- }
110
- }
111
- throw new Error("Unknown service account platform")
112
- }
113
- }