@alienplatform/bindings 1.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 (42) hide show
  1. package/AGENTS.md +70 -0
  2. package/dist/index.d.ts +1770 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +44772 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +56 -0
  7. package/scripts/generate-proto.sh +46 -0
  8. package/src/bindings/AGENTS.md +105 -0
  9. package/src/bindings/artifact-registry.ts +314 -0
  10. package/src/bindings/build.ts +195 -0
  11. package/src/bindings/function.ts +164 -0
  12. package/src/bindings/index.ts +12 -0
  13. package/src/bindings/kv.ts +240 -0
  14. package/src/bindings/queue.ts +191 -0
  15. package/src/bindings/service-account.ts +113 -0
  16. package/src/bindings/storage.ts +535 -0
  17. package/src/bindings/vault.ts +133 -0
  18. package/src/channel.ts +94 -0
  19. package/src/commands.ts +76 -0
  20. package/src/context.ts +368 -0
  21. package/src/errors.ts +259 -0
  22. package/src/events.ts +463 -0
  23. package/src/generated/artifact_registry.ts +1934 -0
  24. package/src/generated/build.ts +1263 -0
  25. package/src/generated/container.ts +485 -0
  26. package/src/generated/control.ts +1574 -0
  27. package/src/generated/function.ts +741 -0
  28. package/src/generated/google/protobuf/duration.ts +196 -0
  29. package/src/generated/google/protobuf/timestamp.ts +225 -0
  30. package/src/generated/kv.ts +1137 -0
  31. package/src/generated/queue.ts +729 -0
  32. package/src/generated/service_account.ts +766 -0
  33. package/src/generated/storage.ts +3653 -0
  34. package/src/generated/vault.ts +519 -0
  35. package/src/generated/wait_until.ts +781 -0
  36. package/src/global.ts +316 -0
  37. package/src/grpc-utils.ts +159 -0
  38. package/src/index.ts +156 -0
  39. package/src/types.ts +384 -0
  40. package/src/wait-until.ts +257 -0
  41. package/tsconfig.json +8 -0
  42. package/tsdown.config.ts +14 -0
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@alienplatform/bindings",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": {
7
+ "import": "./dist/index.js",
8
+ "types": "./dist/index.d.ts"
9
+ }
10
+ },
11
+ "keywords": [
12
+ "alien",
13
+ "bindings",
14
+ "grpc",
15
+ "sdk"
16
+ ],
17
+ "author": "",
18
+ "license": "ISC",
19
+ "description": "TypeScript SDK for Alien bindings - gRPC client for Storage, KV, Queue, Vault, and more",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/alienplatform/alien.git",
23
+ "directory": "packages/bindings"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "devDependencies": {
29
+ "@biomejs/biome": "^1.9.4",
30
+ "@types/node": "^24.0.15",
31
+ "ts-proto": "^2.7.0",
32
+ "tsdown": "^0.13.0",
33
+ "typescript": "^5.8.3",
34
+ "vitest": "^3.1.4",
35
+ "@alienplatform/typescript-config": "^1.0.0"
36
+ },
37
+ "dependencies": {
38
+ "@bufbuild/protobuf": "^2.5.3",
39
+ "@grpc/grpc-js": "^1.13.4",
40
+ "long": "^5.3.2",
41
+ "nice-grpc": "^2.1.11",
42
+ "nice-grpc-common": "^2.0.2",
43
+ "protobufjs": "^7.5.3",
44
+ "ts-pattern": "^5.7.1",
45
+ "zod": "4.3.2",
46
+ "@alienplatform/core": "^1.0.1"
47
+ },
48
+ "scripts": {
49
+ "build": "tsdown",
50
+ "test": "vitest run --passWithNoTests",
51
+ "test:ts": "tsc --noEmit",
52
+ "generate": "./scripts/generate-proto.sh",
53
+ "format-and-lint": "biome check .",
54
+ "format-and-lint:fix": "biome check . --write"
55
+ }
56
+ }
@@ -0,0 +1,46 @@
1
+ #!/bin/bash
2
+
3
+ # Generate TypeScript types from proto files using ts-proto + nice-grpc
4
+
5
+ set -e
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ PACKAGE_DIR="$(dirname "$SCRIPT_DIR")"
9
+ PROTO_DIR="$PACKAGE_DIR/../../crates/alien-bindings/proto"
10
+ OUT_DIR="$PACKAGE_DIR/src/generated"
11
+
12
+ # Clean and create output directory
13
+ rm -rf "$OUT_DIR"
14
+ mkdir -p "$OUT_DIR"
15
+
16
+ # Find protoc - prefer system installation
17
+ if command -v protoc &> /dev/null; then
18
+ PROTOC="protoc"
19
+ else
20
+ echo "Error: protoc not found. Install via 'brew install protobuf' on macOS."
21
+ exit 1
22
+ fi
23
+
24
+ # Find ts-proto plugin
25
+ TS_PROTO_PLUGIN="$PACKAGE_DIR/node_modules/.bin/protoc-gen-ts_proto"
26
+
27
+ if [ ! -f "$TS_PROTO_PLUGIN" ]; then
28
+ echo "Error: protoc-gen-ts_proto not found. Run 'pnpm install' first."
29
+ exit 1
30
+ fi
31
+
32
+ echo "Using protoc: $PROTOC"
33
+ echo "Using ts-proto plugin: $TS_PROTO_PLUGIN"
34
+ echo "Generating TypeScript types from proto files..."
35
+
36
+ # Generate types for all proto files
37
+ # Using nice-grpc options as per the playbook
38
+ "$PROTOC" \
39
+ --plugin="protoc-gen-ts_proto=$TS_PROTO_PLUGIN" \
40
+ --ts_proto_out="$OUT_DIR" \
41
+ --ts_proto_opt=outputServices=nice-grpc,outputServices=generic-definitions,useExactTypes=false,esModuleInterop=true,importSuffix=.js \
42
+ --proto_path="$PROTO_DIR" \
43
+ "$PROTO_DIR"/*.proto
44
+
45
+ echo "Generated TypeScript types in $OUT_DIR"
46
+ echo "Done!"
@@ -0,0 +1,105 @@
1
+ # Binding Implementations
2
+
3
+ Each TypeScript binding must match its Rust counterpart in `alien-bindings/src/providers/<name>/grpc.rs`.
4
+
5
+ **Examples:**
6
+ - `kv.ts` → `providers/kv/grpc.rs`
7
+ - `storage.ts` → `providers/storage/grpc.rs`
8
+ - `build.ts` → `providers/build/grpc.rs`
9
+
10
+ ## Core Pattern: Public API → gRPC → Public API
11
+
12
+ ```
13
+ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
14
+ │ Public API │ → │ gRPC Proto │ → │ Public API │
15
+ │ (SDK types) │ │ (wire format) │ │ (SDK types) │
16
+ └─────────────────┘ └─────────────────┘ └─────────────────┘
17
+ User calls Internal only User receives
18
+ ```
19
+
20
+ **Public API types** come from `@alienplatform/core` or `../types.ts`. Proto types are **internal only** - never exposed to users.
21
+
22
+ ```typescript
23
+ // ❌ Bad - exposing proto types
24
+ async getStatus(buildId: string): Promise<BuildExecutionProto>
25
+
26
+ // ✅ Good - convert proto to public types
27
+ async getStatus(buildId: string): Promise<BuildExecution> {
28
+ const response = await this.client.getBuildStatus({ ... })
29
+ return this.fromProtoExecution(response.execution!) // Transform here
30
+ }
31
+ ```
32
+
33
+ ## Guidelines
34
+
35
+ ### 1. Match All Rust Trait Methods
36
+ Every method in the Rust trait must have a TypeScript equivalent. Check for missing methods when adding bindings.
37
+
38
+ ### 2. Match Rust Enum Fallbacks Exactly
39
+ Rust often maps unknown/unspecified enum values to a specific fallback. TypeScript must match:
40
+
41
+ ```rust
42
+ // Rust: maps Unspecified → Failed
43
+ BuildStatus::Unspecified => BuildStatus::Failed,
44
+ ```
45
+ ```typescript
46
+ // TypeScript: must match
47
+ [BuildStatusProto.BUILD_STATUS_UNSPECIFIED]: "FAILED", // NOT "QUEUED"
48
+ ```
49
+
50
+ ### 3. Transform Types in Private Helpers
51
+ Keep transformations consistent and testable:
52
+
53
+ ```typescript
54
+ private fromProtoExecution(proto: BuildExecutionProto): BuildExecution {
55
+ return {
56
+ id: proto.id,
57
+ status: statusMap[proto.status] ?? "FAILED",
58
+ startTime: proto.startTime ? new Date(proto.startTime) : undefined,
59
+ }
60
+ }
61
+
62
+ private toProtoConfig(config: BuildStartConfig): BuildConfigProto {
63
+ return {
64
+ script: config.script,
65
+ computeType: computeTypeMap[config.computeType] ?? ComputeType.UNSPECIFIED,
66
+ }
67
+ }
68
+ ```
69
+
70
+ ### 4. Convert Units Consistently
71
+ Rust uses `Duration`, TypeScript uses milliseconds:
72
+
73
+ ```typescript
74
+ ttlSeconds: options.ttlMs ? Math.floor(options.ttlMs / 1000) : undefined
75
+ timeoutSeconds: request.timeoutMs ? Math.floor(request.timeoutMs / 1000) : undefined
76
+ ```
77
+
78
+ ### 5. Use wrapGrpcCall for Error Handling
79
+ Always wrap gRPC calls with context for proper error mapping:
80
+
81
+ ```typescript
82
+ return await wrapGrpcCall(
83
+ "BuildService",
84
+ "GetBuildStatus",
85
+ async () => { ... },
86
+ { bindingName: this.bindingName }, // Context for error mapping
87
+ )
88
+ ```
89
+
90
+ ### 6. Handle Optional Proto Fields
91
+ Rust `Option<T>` becomes `T | undefined`. Use nullish coalescing:
92
+
93
+ ```typescript
94
+ uri: proto.uri ?? "",
95
+ createdAt: proto.createdAt ? new Date(proto.createdAt) : undefined,
96
+ ```
97
+
98
+ ## Convenience Methods
99
+
100
+ TypeScript may add convenience methods not in Rust (e.g., `getJson()`, `exists()`, `waitForCompletion()`). These are fine if they build on core methods.
101
+
102
+ ## See Also
103
+
104
+ - Parent: `packages/bindings/AGENTS.md` - Package overview and type architecture
105
+ - Proto definitions: `crates/alien-bindings/proto/`
@@ -0,0 +1,314 @@
1
+ /**
2
+ * ArtifactRegistry binding implementation.
3
+ *
4
+ * Provides container image repository management.
5
+ */
6
+
7
+ import { type Channel, createClient } from "nice-grpc"
8
+ import {
9
+ ArtifactRegistryServiceDefinition,
10
+ ComputeServiceType as ComputeServiceTypeProto,
11
+ type Credentials,
12
+ type CrossAccountAccess as CrossAccountAccessProto,
13
+ type CrossAccountPermissions as CrossAccountPermissionsProto,
14
+ type ArtifactRegistryServiceClient as GeneratedClient,
15
+ ArtifactRegistryPermissions as PermissionsProto,
16
+ type RepositoryResult,
17
+ } from "../generated/artifact_registry.js"
18
+ import { wrapGrpcCall } from "../grpc-utils.js"
19
+ import type {
20
+ ArtifactRegistryCredentials,
21
+ ArtifactRegistryPermissions,
22
+ ComputeServiceType,
23
+ CrossAccountAccess,
24
+ CrossAccountPermissions,
25
+ RepositoryInfo,
26
+ } from "../types.js"
27
+
28
+ // Permission map
29
+ const permissionMap: Record<ArtifactRegistryPermissions, PermissionsProto> = {
30
+ pull: PermissionsProto.PULL,
31
+ "push-pull": PermissionsProto.PUSH_PULL,
32
+ }
33
+
34
+ /**
35
+ * ArtifactRegistry binding for container repository management.
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * import { artifactRegistry } from "@alienplatform/bindings"
40
+ *
41
+ * const registry = artifactRegistry("my-registry")
42
+ *
43
+ * // Create a repository
44
+ * const repo = await registry.createRepository("my-app")
45
+ * console.log("Repository URI:", repo.uri)
46
+ *
47
+ * // Generate push credentials
48
+ * const creds = await registry.generateCredentials("my-app", "push-pull")
49
+ * console.log("Username:", creds.username)
50
+ * ```
51
+ */
52
+ export class ArtifactRegistry {
53
+ private readonly client: GeneratedClient
54
+ private readonly bindingName: string
55
+
56
+ constructor(channel: Channel, bindingName: string) {
57
+ this.client = createClient(ArtifactRegistryServiceDefinition, channel)
58
+ this.bindingName = bindingName
59
+ }
60
+
61
+ /**
62
+ * Create a repository.
63
+ *
64
+ * @param repoName - Name of the repository
65
+ * @returns Repository information
66
+ */
67
+ async createRepository(repoName: string): Promise<RepositoryInfo> {
68
+ return await wrapGrpcCall(
69
+ "ArtifactRegistryService",
70
+ "CreateRepository",
71
+ async () => {
72
+ const response = await this.client.createRepository({
73
+ bindingName: this.bindingName,
74
+ repoName,
75
+ })
76
+ return this.fromProtoResult(response.result!)
77
+ },
78
+ { bindingName: this.bindingName },
79
+ )
80
+ }
81
+
82
+ /**
83
+ * Get repository details.
84
+ *
85
+ * @param repoId - Repository ID
86
+ * @returns Repository information
87
+ */
88
+ async getRepository(repoId: string): Promise<RepositoryInfo> {
89
+ return await wrapGrpcCall(
90
+ "ArtifactRegistryService",
91
+ "GetRepository",
92
+ async () => {
93
+ const response = await this.client.getRepository({
94
+ bindingName: this.bindingName,
95
+ repoId,
96
+ })
97
+ return this.fromProtoResult(response.result!)
98
+ },
99
+ { bindingName: this.bindingName },
100
+ )
101
+ }
102
+
103
+ /**
104
+ * Add cross-account access to a repository.
105
+ *
106
+ * @param repoId - Repository ID
107
+ * @param access - Cross-account access configuration
108
+ */
109
+ async addCrossAccountAccess(repoId: string, access: CrossAccountAccess): Promise<void> {
110
+ await wrapGrpcCall(
111
+ "ArtifactRegistryService",
112
+ "AddCrossAccountAccess",
113
+ async () => {
114
+ await this.client.addCrossAccountAccess({
115
+ bindingName: this.bindingName,
116
+ repoId,
117
+ access: this.toProtoCrossAccountAccess(access),
118
+ })
119
+ },
120
+ { bindingName: this.bindingName },
121
+ )
122
+ }
123
+
124
+ /**
125
+ * Remove cross-account access from a repository.
126
+ *
127
+ * @param repoId - Repository ID
128
+ * @param access - Cross-account access configuration to remove
129
+ */
130
+ async removeCrossAccountAccess(repoId: string, access: CrossAccountAccess): Promise<void> {
131
+ await wrapGrpcCall(
132
+ "ArtifactRegistryService",
133
+ "RemoveCrossAccountAccess",
134
+ async () => {
135
+ await this.client.removeCrossAccountAccess({
136
+ bindingName: this.bindingName,
137
+ repoId,
138
+ access: this.toProtoCrossAccountAccess(access),
139
+ })
140
+ },
141
+ { bindingName: this.bindingName },
142
+ )
143
+ }
144
+
145
+ /**
146
+ * Get current cross-account access permissions for a repository.
147
+ *
148
+ * @param repoId - Repository ID
149
+ * @returns Current cross-account permissions
150
+ */
151
+ async getCrossAccountAccess(repoId: string): Promise<CrossAccountPermissions> {
152
+ return await wrapGrpcCall(
153
+ "ArtifactRegistryService",
154
+ "GetCrossAccountAccess",
155
+ async () => {
156
+ const response = await this.client.getCrossAccountAccess({
157
+ bindingName: this.bindingName,
158
+ repoId,
159
+ })
160
+ return this.fromProtoCrossAccountPermissions(response.permissions!)
161
+ },
162
+ { bindingName: this.bindingName },
163
+ )
164
+ }
165
+
166
+ /**
167
+ * Generate credentials for repository access.
168
+ *
169
+ * @param repoId - Repository ID
170
+ * @param permissions - Permission level (pull or push-pull)
171
+ * @param ttlSeconds - Optional TTL in seconds
172
+ * @returns Repository credentials
173
+ */
174
+ async generateCredentials(
175
+ repoId: string,
176
+ permissions: ArtifactRegistryPermissions,
177
+ ttlSeconds?: number,
178
+ ): Promise<ArtifactRegistryCredentials> {
179
+ return await wrapGrpcCall(
180
+ "ArtifactRegistryService",
181
+ "GenerateCredentials",
182
+ async () => {
183
+ const response = await this.client.generateCredentials({
184
+ bindingName: this.bindingName,
185
+ repoId,
186
+ permissions: permissionMap[permissions],
187
+ ttlSeconds,
188
+ })
189
+ return this.fromProtoCredentials(response.credentials!)
190
+ },
191
+ { bindingName: this.bindingName },
192
+ )
193
+ }
194
+
195
+ /**
196
+ * Delete a repository.
197
+ *
198
+ * @param repoId - Repository ID
199
+ */
200
+ async deleteRepository(repoId: string): Promise<void> {
201
+ await wrapGrpcCall(
202
+ "ArtifactRegistryService",
203
+ "DeleteRepository",
204
+ async () => {
205
+ await this.client.deleteRepository({
206
+ bindingName: this.bindingName,
207
+ repoId,
208
+ })
209
+ },
210
+ { bindingName: this.bindingName },
211
+ )
212
+ }
213
+
214
+ // Private helpers
215
+
216
+ private fromProtoResult(proto: RepositoryResult): RepositoryInfo {
217
+ return {
218
+ name: proto.name,
219
+ uri: proto.uri ?? "",
220
+ createdAt: proto.createdAt ? new Date(proto.createdAt) : undefined,
221
+ }
222
+ }
223
+
224
+ private fromProtoCredentials(proto: Credentials): ArtifactRegistryCredentials {
225
+ return {
226
+ username: proto.username,
227
+ password: proto.password,
228
+ expiresAt: proto.expiresAt ? new Date(proto.expiresAt) : undefined,
229
+ }
230
+ }
231
+
232
+ private toProtoCrossAccountAccess(access: CrossAccountAccess): CrossAccountAccessProto {
233
+ if (access.type === "aws") {
234
+ return {
235
+ aws: {
236
+ accountIds: access.aws.accountIds,
237
+ allowedServiceTypes: access.aws.allowedServiceTypes.map(st =>
238
+ this.computeServiceTypeToProto(st),
239
+ ),
240
+ roleArns: access.aws.roleArns,
241
+ },
242
+ gcp: undefined,
243
+ }
244
+ }
245
+ return {
246
+ aws: undefined,
247
+ gcp: {
248
+ projectNumbers: access.gcp.projectNumbers,
249
+ allowedServiceTypes: access.gcp.allowedServiceTypes.map(st =>
250
+ this.computeServiceTypeToProto(st),
251
+ ),
252
+ serviceAccountEmails: access.gcp.serviceAccountEmails,
253
+ },
254
+ }
255
+ }
256
+
257
+ private fromProtoCrossAccountAccess(proto: CrossAccountAccessProto): CrossAccountAccess {
258
+ if (proto.aws) {
259
+ return {
260
+ type: "aws",
261
+ aws: {
262
+ accountIds: proto.aws.accountIds,
263
+ allowedServiceTypes: proto.aws.allowedServiceTypes
264
+ .map(st => this.protoToComputeServiceType(st))
265
+ .filter((st): st is ComputeServiceType => st !== undefined),
266
+ roleArns: proto.aws.roleArns,
267
+ },
268
+ }
269
+ }
270
+ if (proto.gcp) {
271
+ return {
272
+ type: "gcp",
273
+ gcp: {
274
+ projectNumbers: proto.gcp.projectNumbers,
275
+ allowedServiceTypes: proto.gcp.allowedServiceTypes
276
+ .map(st => this.protoToComputeServiceType(st))
277
+ .filter((st): st is ComputeServiceType => st !== undefined),
278
+ serviceAccountEmails: proto.gcp.serviceAccountEmails,
279
+ },
280
+ }
281
+ }
282
+ throw new Error("Invalid CrossAccountAccess: neither aws nor gcp set")
283
+ }
284
+
285
+ private fromProtoCrossAccountPermissions(
286
+ proto: CrossAccountPermissionsProto,
287
+ ): CrossAccountPermissions {
288
+ if (!proto.access) {
289
+ throw new Error("Invalid CrossAccountPermissions: access is missing")
290
+ }
291
+ return {
292
+ access: this.fromProtoCrossAccountAccess(proto.access),
293
+ lastUpdated: proto.lastUpdated,
294
+ }
295
+ }
296
+
297
+ private computeServiceTypeToProto(st: ComputeServiceType): ComputeServiceTypeProto {
298
+ switch (st) {
299
+ case "function":
300
+ return ComputeServiceTypeProto.FUNCTION
301
+ default:
302
+ return ComputeServiceTypeProto.UNRECOGNIZED
303
+ }
304
+ }
305
+
306
+ private protoToComputeServiceType(st: ComputeServiceTypeProto): ComputeServiceType | undefined {
307
+ switch (st) {
308
+ case ComputeServiceTypeProto.FUNCTION:
309
+ return "function"
310
+ default:
311
+ return undefined
312
+ }
313
+ }
314
+ }