@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.
- package/AGENTS.md +70 -0
- package/dist/index.d.ts +1770 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +44772 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
- package/scripts/generate-proto.sh +46 -0
- package/src/bindings/AGENTS.md +105 -0
- package/src/bindings/artifact-registry.ts +314 -0
- package/src/bindings/build.ts +195 -0
- package/src/bindings/function.ts +164 -0
- package/src/bindings/index.ts +12 -0
- package/src/bindings/kv.ts +240 -0
- package/src/bindings/queue.ts +191 -0
- package/src/bindings/service-account.ts +113 -0
- package/src/bindings/storage.ts +535 -0
- package/src/bindings/vault.ts +133 -0
- package/src/channel.ts +94 -0
- package/src/commands.ts +76 -0
- package/src/context.ts +368 -0
- package/src/errors.ts +259 -0
- package/src/events.ts +463 -0
- package/src/generated/artifact_registry.ts +1934 -0
- package/src/generated/build.ts +1263 -0
- package/src/generated/container.ts +485 -0
- package/src/generated/control.ts +1574 -0
- package/src/generated/function.ts +741 -0
- package/src/generated/google/protobuf/duration.ts +196 -0
- package/src/generated/google/protobuf/timestamp.ts +225 -0
- package/src/generated/kv.ts +1137 -0
- package/src/generated/queue.ts +729 -0
- package/src/generated/service_account.ts +766 -0
- package/src/generated/storage.ts +3653 -0
- package/src/generated/vault.ts +519 -0
- package/src/generated/wait_until.ts +781 -0
- package/src/global.ts +316 -0
- package/src/grpc-utils.ts +159 -0
- package/src/index.ts +156 -0
- package/src/types.ts +384 -0
- package/src/wait-until.ts +257 -0
- package/tsconfig.json +8 -0
- package/tsdown.config.ts +14 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build binding implementation.
|
|
3
|
+
*
|
|
4
|
+
* Provides build execution capabilities.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { BuildStatus } from "@alienplatform/core"
|
|
8
|
+
import { type Channel, createClient } from "nice-grpc"
|
|
9
|
+
import {
|
|
10
|
+
type BuildConfig as BuildConfigProto,
|
|
11
|
+
type BuildExecution as BuildExecutionProto,
|
|
12
|
+
BuildServiceDefinition,
|
|
13
|
+
BuildStatus as BuildStatusProto,
|
|
14
|
+
ComputeType,
|
|
15
|
+
type BuildServiceClient as GeneratedBuildServiceClient,
|
|
16
|
+
} from "../generated/build.js"
|
|
17
|
+
import { wrapGrpcCall } from "../grpc-utils.js"
|
|
18
|
+
import type { BuildExecution, BuildStartConfig } from "../types.js"
|
|
19
|
+
|
|
20
|
+
// Map proto status to core BuildStatus (matches Rust: Unspecified → Failed)
|
|
21
|
+
const statusMap: Record<number, BuildStatus> = {
|
|
22
|
+
[BuildStatusProto.BUILD_STATUS_UNSPECIFIED]: "FAILED",
|
|
23
|
+
[BuildStatusProto.BUILD_STATUS_QUEUED]: "QUEUED",
|
|
24
|
+
[BuildStatusProto.BUILD_STATUS_RUNNING]: "RUNNING",
|
|
25
|
+
[BuildStatusProto.BUILD_STATUS_SUCCEEDED]: "SUCCEEDED",
|
|
26
|
+
[BuildStatusProto.BUILD_STATUS_FAILED]: "FAILED",
|
|
27
|
+
[BuildStatusProto.BUILD_STATUS_CANCELLED]: "CANCELLED",
|
|
28
|
+
[BuildStatusProto.BUILD_STATUS_TIMED_OUT]: "TIMED_OUT",
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Map compute type to proto
|
|
32
|
+
const computeTypeMap: Record<string, ComputeType> = {
|
|
33
|
+
small: ComputeType.COMPUTE_TYPE_SMALL,
|
|
34
|
+
medium: ComputeType.COMPUTE_TYPE_MEDIUM,
|
|
35
|
+
large: ComputeType.COMPUTE_TYPE_LARGE,
|
|
36
|
+
"x-large": ComputeType.COMPUTE_TYPE_XLARGE,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Build binding for executing build operations.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* import { build } from "@alienplatform/bindings"
|
|
45
|
+
*
|
|
46
|
+
* const builder = build("my-builder")
|
|
47
|
+
*
|
|
48
|
+
* // Start a build
|
|
49
|
+
* const execution = await builder.start({
|
|
50
|
+
* script: "npm run build",
|
|
51
|
+
* computeType: "medium",
|
|
52
|
+
* timeoutSeconds: 600,
|
|
53
|
+
* })
|
|
54
|
+
*
|
|
55
|
+
* // Wait for completion
|
|
56
|
+
* const result = await builder.waitForCompletion(execution.id)
|
|
57
|
+
* console.log("Build status:", result.status)
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export class Build {
|
|
61
|
+
private readonly client: GeneratedBuildServiceClient
|
|
62
|
+
private readonly bindingName: string
|
|
63
|
+
|
|
64
|
+
constructor(channel: Channel, bindingName: string) {
|
|
65
|
+
this.client = createClient(BuildServiceDefinition, channel)
|
|
66
|
+
this.bindingName = bindingName
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Start a new build.
|
|
71
|
+
*
|
|
72
|
+
* @param config - Build configuration
|
|
73
|
+
* @returns Build execution information
|
|
74
|
+
*/
|
|
75
|
+
async start(config: BuildStartConfig): Promise<BuildExecution> {
|
|
76
|
+
const protoConfig = this.toProtoConfig(config)
|
|
77
|
+
|
|
78
|
+
return await wrapGrpcCall(
|
|
79
|
+
"BuildService",
|
|
80
|
+
"StartBuild",
|
|
81
|
+
async () => {
|
|
82
|
+
const response = await this.client.startBuild({
|
|
83
|
+
bindingName: this.bindingName,
|
|
84
|
+
config: protoConfig,
|
|
85
|
+
})
|
|
86
|
+
return this.fromProtoExecution(response.execution!)
|
|
87
|
+
},
|
|
88
|
+
{ bindingName: this.bindingName },
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get the status of a build.
|
|
94
|
+
*
|
|
95
|
+
* @param buildId - Build execution ID
|
|
96
|
+
* @returns Build execution information
|
|
97
|
+
*/
|
|
98
|
+
async getStatus(buildId: string): Promise<BuildExecution> {
|
|
99
|
+
return await wrapGrpcCall(
|
|
100
|
+
"BuildService",
|
|
101
|
+
"GetBuildStatus",
|
|
102
|
+
async () => {
|
|
103
|
+
const response = await this.client.getBuildStatus({
|
|
104
|
+
bindingName: this.bindingName,
|
|
105
|
+
buildId,
|
|
106
|
+
})
|
|
107
|
+
return this.fromProtoExecution(response.execution!)
|
|
108
|
+
},
|
|
109
|
+
{ bindingName: this.bindingName },
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Stop a running build.
|
|
115
|
+
*
|
|
116
|
+
* @param buildId - Build execution ID
|
|
117
|
+
*/
|
|
118
|
+
async stop(buildId: string): Promise<void> {
|
|
119
|
+
await wrapGrpcCall(
|
|
120
|
+
"BuildService",
|
|
121
|
+
"StopBuild",
|
|
122
|
+
async () => {
|
|
123
|
+
await this.client.stopBuild({
|
|
124
|
+
bindingName: this.bindingName,
|
|
125
|
+
buildId,
|
|
126
|
+
})
|
|
127
|
+
},
|
|
128
|
+
{ bindingName: this.bindingName },
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Wait for a build to complete.
|
|
134
|
+
*
|
|
135
|
+
* @param buildId - Build execution ID
|
|
136
|
+
* @param options - Polling options
|
|
137
|
+
* @returns Final build execution information
|
|
138
|
+
*/
|
|
139
|
+
async waitForCompletion(
|
|
140
|
+
buildId: string,
|
|
141
|
+
options?: { pollIntervalMs?: number; timeoutMs?: number },
|
|
142
|
+
): Promise<BuildExecution> {
|
|
143
|
+
const pollInterval = options?.pollIntervalMs ?? 5000
|
|
144
|
+
const timeout = options?.timeoutMs ?? 30 * 60 * 1000 // 30 minutes default
|
|
145
|
+
const startTime = Date.now()
|
|
146
|
+
|
|
147
|
+
while (Date.now() - startTime < timeout) {
|
|
148
|
+
const execution = await this.getStatus(buildId)
|
|
149
|
+
|
|
150
|
+
if (
|
|
151
|
+
execution.status === "SUCCEEDED" ||
|
|
152
|
+
execution.status === "FAILED" ||
|
|
153
|
+
execution.status === "CANCELLED" ||
|
|
154
|
+
execution.status === "TIMED_OUT"
|
|
155
|
+
) {
|
|
156
|
+
return execution
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval))
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
throw new Error(`Build ${buildId} did not complete within timeout`)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Private helpers
|
|
166
|
+
|
|
167
|
+
private toProtoConfig(config: BuildStartConfig): BuildConfigProto {
|
|
168
|
+
return {
|
|
169
|
+
script: config.script,
|
|
170
|
+
environment: config.environment ?? {},
|
|
171
|
+
computeType: config.computeType
|
|
172
|
+
? (computeTypeMap[config.computeType] ?? ComputeType.COMPUTE_TYPE_UNSPECIFIED)
|
|
173
|
+
: ComputeType.COMPUTE_TYPE_UNSPECIFIED,
|
|
174
|
+
timeoutSeconds: config.timeoutSeconds,
|
|
175
|
+
monitoring: config.monitoring
|
|
176
|
+
? {
|
|
177
|
+
endpoint: config.monitoring.endpoint,
|
|
178
|
+
headers: config.monitoring.headers ?? {},
|
|
179
|
+
logsUri: config.monitoring.logsUri ?? "/v1/logs",
|
|
180
|
+
tlsEnabled: config.monitoring.tlsEnabled ?? true,
|
|
181
|
+
tlsVerify: config.monitoring.tlsVerify ?? true,
|
|
182
|
+
}
|
|
183
|
+
: undefined,
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private fromProtoExecution(proto: BuildExecutionProto): BuildExecution {
|
|
188
|
+
return {
|
|
189
|
+
id: proto.id,
|
|
190
|
+
status: statusMap[proto.status] ?? "QUEUED",
|
|
191
|
+
startTime: proto.startTime ? new Date(proto.startTime) : undefined,
|
|
192
|
+
endTime: proto.endTime ? new Date(proto.endTime) : undefined,
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
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"
|
|
@@ -0,0 +1,240 @@
|
|
|
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
|
+
}
|