@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
package/src/errors.ts DELETED
@@ -1,259 +0,0 @@
1
- /**
2
- * Error definitions for the Alien bindings SDK.
3
- * These errors are thrown when binding operations fail.
4
- */
5
-
6
- import { defineError } from "@alienplatform/core"
7
- import * as z from "zod/v4"
8
-
9
- /**
10
- * Error thrown when the gRPC connection to the alien-runtime fails.
11
- */
12
- export const GrpcConnectionError = defineError({
13
- code: "GRPC_CONNECTION_ERROR",
14
- context: z.object({
15
- endpoint: z.string(),
16
- reason: z.string(),
17
- }),
18
- message: ({ endpoint, reason }) =>
19
- `Failed to connect to alien-runtime at '${endpoint}': ${reason}`,
20
- retryable: true,
21
- internal: false,
22
- httpStatusCode: 503,
23
- })
24
-
25
- /**
26
- * Error thrown when a gRPC call fails.
27
- */
28
- export const GrpcCallError = defineError({
29
- code: "GRPC_CALL_ERROR",
30
- context: z.object({
31
- service: z.string(),
32
- method: z.string(),
33
- grpcCode: z.string(),
34
- details: z.string(),
35
- }),
36
- message: ({ service, method, grpcCode, details }) =>
37
- `gRPC call ${service}.${method} failed with code ${grpcCode}: ${details}`,
38
- retryable: false,
39
- internal: false,
40
- httpStatusCode: 500,
41
- })
42
-
43
- /**
44
- * Error thrown when a binding is not found.
45
- */
46
- export const BindingNotFoundError = defineError({
47
- code: "BINDING_NOT_FOUND",
48
- context: z.object({
49
- bindingName: z.string(),
50
- bindingType: z.string(),
51
- }),
52
- message: ({ bindingName, bindingType }) => `${bindingType} binding '${bindingName}' not found`,
53
- retryable: false,
54
- internal: false,
55
- httpStatusCode: 404,
56
- })
57
-
58
- /**
59
- * Error thrown when the AlienContext is not initialized.
60
- */
61
- export const ContextNotInitializedError = defineError({
62
- code: "CONTEXT_NOT_INITIALIZED",
63
- context: z.object({
64
- operation: z.string(),
65
- }),
66
- message: ({ operation }) =>
67
- `AlienContext not initialized. Call AlienContext.fromEnv() before using ${operation}`,
68
- retryable: false,
69
- internal: false,
70
- httpStatusCode: 500,
71
- })
72
-
73
- /**
74
- * Error thrown when a required environment variable is missing.
75
- */
76
- export const MissingEnvVarError = defineError({
77
- code: "MISSING_ENV_VAR",
78
- context: z.object({
79
- variable: z.string(),
80
- description: z.string(),
81
- }),
82
- message: ({ variable, description }) =>
83
- `Required environment variable '${variable}' is not set. ${description}`,
84
- retryable: false,
85
- internal: false,
86
- httpStatusCode: 500,
87
- })
88
-
89
- /**
90
- * Error thrown when a storage object is not found.
91
- */
92
- export const StorageObjectNotFoundError = defineError({
93
- code: "STORAGE_OBJECT_NOT_FOUND",
94
- context: z.object({
95
- bindingName: z.string(),
96
- path: z.string(),
97
- }),
98
- message: ({ bindingName, path }) =>
99
- `Object '${path}' not found in storage binding '${bindingName}'`,
100
- retryable: false,
101
- internal: false,
102
- httpStatusCode: 404,
103
- })
104
-
105
- /**
106
- * Error thrown when a storage precondition fails.
107
- */
108
- export const StoragePreconditionError = defineError({
109
- code: "STORAGE_PRECONDITION_FAILED",
110
- context: z.object({
111
- bindingName: z.string(),
112
- path: z.string(),
113
- condition: z.string(),
114
- }),
115
- message: ({ bindingName, path, condition }) =>
116
- `Precondition failed for '${path}' in storage binding '${bindingName}': ${condition}`,
117
- retryable: false,
118
- internal: false,
119
- httpStatusCode: 412,
120
- })
121
-
122
- /**
123
- * Error thrown when a storage object already exists (for create mode).
124
- */
125
- export const StorageObjectExistsError = defineError({
126
- code: "STORAGE_OBJECT_EXISTS",
127
- context: z.object({
128
- bindingName: z.string(),
129
- path: z.string(),
130
- }),
131
- message: ({ bindingName, path }) =>
132
- `Object '${path}' already exists in storage binding '${bindingName}'`,
133
- retryable: false,
134
- internal: false,
135
- httpStatusCode: 409,
136
- })
137
-
138
- /**
139
- * Error thrown when a KV key is not found.
140
- */
141
- export const KvKeyNotFoundError = defineError({
142
- code: "KV_KEY_NOT_FOUND",
143
- context: z.object({
144
- bindingName: z.string(),
145
- key: z.string(),
146
- }),
147
- message: ({ bindingName, key }) => `Key '${key}' not found in KV binding '${bindingName}'`,
148
- retryable: false,
149
- internal: false,
150
- httpStatusCode: 404,
151
- })
152
-
153
- /**
154
- * Error thrown when a KV key is invalid.
155
- */
156
- export const KvInvalidKeyError = defineError({
157
- code: "KV_INVALID_KEY",
158
- context: z.object({
159
- key: z.string(),
160
- reason: z.string(),
161
- }),
162
- message: ({ key, reason }) => `Invalid KV key '${key}': ${reason}`,
163
- retryable: false,
164
- internal: false,
165
- httpStatusCode: 400,
166
- })
167
-
168
- /**
169
- * Error thrown when a KV value is invalid.
170
- */
171
- export const KvInvalidValueError = defineError({
172
- code: "KV_INVALID_VALUE",
173
- context: z.object({
174
- reason: z.string(),
175
- }),
176
- message: ({ reason }) => `Invalid KV value: ${reason}`,
177
- retryable: false,
178
- internal: false,
179
- httpStatusCode: 400,
180
- })
181
-
182
- /**
183
- * Error thrown when a secret is not found.
184
- */
185
- export const SecretNotFoundError = defineError({
186
- code: "SECRET_NOT_FOUND",
187
- context: z.object({
188
- bindingName: z.string(),
189
- secretName: z.string(),
190
- }),
191
- message: ({ bindingName, secretName }) =>
192
- `Secret '${secretName}' not found in vault binding '${bindingName}'`,
193
- retryable: false,
194
- internal: false,
195
- httpStatusCode: 404,
196
- })
197
-
198
- /**
199
- * Error thrown when a queue operation fails.
200
- */
201
- export const QueueOperationError = defineError({
202
- code: "QUEUE_OPERATION_ERROR",
203
- context: z.object({
204
- bindingName: z.string(),
205
- queue: z.string(),
206
- operation: z.string(),
207
- reason: z.string(),
208
- }),
209
- message: ({ bindingName, queue, operation, reason }) =>
210
- `Queue ${operation} failed for queue '${queue}' in binding '${bindingName}': ${reason}`,
211
- retryable: true,
212
- internal: false,
213
- httpStatusCode: 500,
214
- })
215
-
216
- /**
217
- * Error thrown when an event handler is already registered.
218
- */
219
- export const EventHandlerAlreadyRegisteredError = defineError({
220
- code: "EVENT_HANDLER_ALREADY_REGISTERED",
221
- context: z.object({
222
- handlerType: z.string(),
223
- resourceName: z.string(),
224
- }),
225
- message: ({ handlerType, resourceName }) =>
226
- `Event handler for ${handlerType} '${resourceName}' is already registered`,
227
- retryable: false,
228
- internal: false,
229
- httpStatusCode: 409,
230
- })
231
-
232
- /**
233
- * Error thrown when a command is already registered.
234
- */
235
- export const CommandAlreadyRegisteredError = defineError({
236
- code: "COMMAND_ALREADY_REGISTERED",
237
- context: z.object({
238
- commandName: z.string(),
239
- }),
240
- message: ({ commandName }) => `Command '${commandName}' is already registered`,
241
- retryable: false,
242
- internal: false,
243
- httpStatusCode: 409,
244
- })
245
-
246
- /**
247
- * Error thrown when the binding configuration is invalid.
248
- */
249
- export const InvalidBindingConfigError = defineError({
250
- code: "INVALID_BINDING_CONFIG",
251
- context: z.object({
252
- message: z.string(),
253
- suggestion: z.string().optional(),
254
- }),
255
- message: ({ message, suggestion }) => (suggestion ? `${message}. ${suggestion}` : message),
256
- retryable: false,
257
- internal: false,
258
- httpStatusCode: 400,
259
- })
package/src/events.ts DELETED
@@ -1,463 +0,0 @@
1
- /**
2
- * Event handling for Alien bindings.
3
- *
4
- * Provides handlers for storage events, cron/scheduled events, and queue messages.
5
- */
6
-
7
- // Import canonical event types from core
8
- import type {
9
- QueueMessage,
10
- ScheduledEvent,
11
- StorageEvent,
12
- StorageEventType,
13
- } from "@alienplatform/core"
14
- import { type Channel, createClient } from "nice-grpc"
15
- import { getCommands, runCommand } from "./commands.js"
16
- import {
17
- ControlServiceDefinition,
18
- type ControlServiceClient as GeneratedClient,
19
- type ArcCommand as ProtoArcCommand,
20
- type CronEvent as ProtoCronEvent,
21
- type QueueMessage as ProtoQueueMessage,
22
- type StorageEvent as ProtoStorageEvent,
23
- type Task,
24
- } from "./generated/control.js"
25
- import { wrapGrpcCall } from "./grpc-utils.js"
26
-
27
- // Re-export core types for convenience
28
- export type { StorageEvent, StorageEventType, ScheduledEvent, QueueMessage }
29
-
30
- /**
31
- * Cron/scheduled event with schedule info.
32
- * Extends ScheduledEvent with the schedule name.
33
- */
34
- export interface CronEvent extends ScheduledEvent {
35
- /** Schedule name */
36
- scheduleName: string
37
- }
38
-
39
- /**
40
- * Queue message event with queue context.
41
- */
42
- export interface QueueMessageEvent<T = unknown> {
43
- /** Message ID */
44
- id: string
45
- /** Source queue name */
46
- source: string
47
- /** Receipt handle for acknowledgment */
48
- receiptHandle: string
49
- /** Message payload */
50
- payload: T
51
- /** Delivery attempt count */
52
- attemptCount: number
53
- /** Message timestamp */
54
- timestamp: Date
55
- }
56
-
57
- /**
58
- * Event handler registration.
59
- */
60
- export interface EventRegistration {
61
- /** Handler ID */
62
- id: string
63
- /** Event type */
64
- type: "storage" | "cron" | "queue"
65
- /** Source filter (bucket name, queue name, etc.) */
66
- source: string
67
- /** Optional prefix filter (for storage events) */
68
- prefix?: string
69
- }
70
-
71
- // Internal storage for event handlers
72
- const eventHandlers: Map<
73
- string,
74
- {
75
- registration: EventRegistration
76
- handler: (event: unknown) => Promise<void>
77
- }
78
- > = new Map()
79
-
80
- let handlerIdCounter = 0
81
-
82
- /**
83
- * Register a storage event handler.
84
- *
85
- * @param bucket - Bucket name to listen for events
86
- * @param handler - Handler function for storage events
87
- * @param options - Handler options
88
- * @returns Unsubscribe function
89
- *
90
- * @example
91
- * ```typescript
92
- * import { onStorageEvent } from "@alienplatform/bindings"
93
- *
94
- * onStorageEvent("uploads", async (event) => {
95
- * console.log("File uploaded:", event.objectKey)
96
- * await processFile(event.bucketName, event.objectKey)
97
- * })
98
- * ```
99
- */
100
- export function onStorageEvent(
101
- bucket: string,
102
- handler: (event: StorageEvent) => Promise<void>,
103
- options?: { prefix?: string },
104
- ): () => void {
105
- const id = `storage-${++handlerIdCounter}`
106
-
107
- eventHandlers.set(id, {
108
- registration: {
109
- id,
110
- type: "storage",
111
- source: bucket,
112
- prefix: options?.prefix,
113
- },
114
- handler: handler as (event: unknown) => Promise<void>,
115
- })
116
-
117
- return () => {
118
- eventHandlers.delete(id)
119
- }
120
- }
121
-
122
- /**
123
- * Register a cron/scheduled event handler.
124
- *
125
- * @param scheduleName - Schedule name to listen for
126
- * @param handler - Handler function for scheduled events
127
- * @returns Unsubscribe function
128
- *
129
- * @example
130
- * ```typescript
131
- * import { onCronEvent } from "@alienplatform/bindings"
132
- *
133
- * onCronEvent("hourly-task", async (event) => {
134
- * console.log("Scheduled run at:", event.timestamp)
135
- * await runHourlyTask()
136
- * })
137
- * ```
138
- */
139
- export function onCronEvent(
140
- scheduleName: string,
141
- handler: (event: CronEvent) => Promise<void>,
142
- ): () => void {
143
- const id = `cron-${++handlerIdCounter}`
144
-
145
- eventHandlers.set(id, {
146
- registration: {
147
- id,
148
- type: "cron",
149
- source: scheduleName,
150
- },
151
- handler: handler as (event: unknown) => Promise<void>,
152
- })
153
-
154
- return () => {
155
- eventHandlers.delete(id)
156
- }
157
- }
158
-
159
- /**
160
- * Register a queue message handler.
161
- *
162
- * @param queueName - Queue name to listen for messages
163
- * @param handler - Handler function for queue messages
164
- * @returns Unsubscribe function
165
- *
166
- * @example
167
- * ```typescript
168
- * import { onQueueMessage } from "@alienplatform/bindings"
169
- *
170
- * onQueueMessage("tasks", async (message) => {
171
- * console.log("Processing task:", message.payload)
172
- * await processTask(message.payload)
173
- * })
174
- * ```
175
- */
176
- export function onQueueMessage<T = unknown>(
177
- queueName: string,
178
- handler: (message: QueueMessageEvent<T>) => Promise<void>,
179
- ): () => void {
180
- const id = `queue-${++handlerIdCounter}`
181
-
182
- eventHandlers.set(id, {
183
- registration: {
184
- id,
185
- type: "queue",
186
- source: queueName,
187
- },
188
- handler: handler as (event: unknown) => Promise<void>,
189
- })
190
-
191
- return () => {
192
- eventHandlers.delete(id)
193
- }
194
- }
195
-
196
- /**
197
- * Get all registered event handlers.
198
- *
199
- * @internal
200
- */
201
- export function getEventHandlers(): Map<
202
- string,
203
- {
204
- registration: EventRegistration
205
- handler: (event: unknown) => Promise<void>
206
- }
207
- > {
208
- return eventHandlers
209
- }
210
-
211
- /**
212
- * Event loop runner for processing tasks from the control plane.
213
- *
214
- * @internal
215
- */
216
- export class EventLoop {
217
- private readonly client: GeneratedClient
218
- private readonly applicationId: string
219
- private running = false
220
-
221
- constructor(channel: Channel, applicationId: string) {
222
- this.client = createClient(ControlServiceDefinition, channel)
223
- this.applicationId = applicationId
224
- }
225
-
226
- /**
227
- * Register all event handlers with the control plane.
228
- */
229
- async registerHandlers(): Promise<void> {
230
- // Register event handlers
231
- for (const { registration } of eventHandlers.values()) {
232
- await wrapGrpcCall(
233
- "ControlService",
234
- "RegisterEventHandler",
235
- async () => {
236
- await this.client.registerEventHandler({
237
- handlerType: registration.type,
238
- resourceName: registration.source,
239
- })
240
- },
241
- {},
242
- )
243
- }
244
-
245
- // Register command handlers
246
- for (const command of getCommands().values()) {
247
- await wrapGrpcCall(
248
- "ControlService",
249
- "RegisterEventHandler",
250
- async () => {
251
- await this.client.registerEventHandler({
252
- handlerType: "command",
253
- resourceName: command.name,
254
- })
255
- },
256
- {},
257
- )
258
- }
259
- }
260
-
261
- /**
262
- * Start the event loop.
263
- */
264
- async start(): Promise<void> {
265
- this.running = true
266
-
267
- while (this.running) {
268
- try {
269
- await this.processTasks()
270
- } catch (error) {
271
- // Log error and continue
272
- console.error("Error processing tasks:", error)
273
- await new Promise(resolve => setTimeout(resolve, 1000))
274
- }
275
- }
276
- }
277
-
278
- /**
279
- * Stop the event loop.
280
- */
281
- stop(): void {
282
- this.running = false
283
- }
284
-
285
- private async processTasks(): Promise<void> {
286
- const stream = this.client.waitForTasks({
287
- applicationId: this.applicationId,
288
- })
289
-
290
- for await (const task of stream) {
291
- await this.handleTask(task)
292
- }
293
- }
294
-
295
- private async handleTask(task: Task): Promise<void> {
296
- try {
297
- // Handle ARC commands
298
- if (task.arcCommand) {
299
- const result = await this.handleArcCommand(task.arcCommand)
300
- await this.sendTaskResult(task.taskId, { success: true, data: result })
301
- return
302
- }
303
-
304
- // Handle events (storage, cron, queue)
305
- let matchedEntry: { handler: (event: unknown) => Promise<void> } | undefined
306
-
307
- for (const entry of eventHandlers.values()) {
308
- if (task.storageEvent && entry.registration.type === "storage") {
309
- if (entry.registration.source === task.storageEvent.bucket) {
310
- matchedEntry = entry
311
- break
312
- }
313
- } else if (task.cronEvent && entry.registration.type === "cron") {
314
- if (entry.registration.source === task.cronEvent.scheduleName) {
315
- matchedEntry = entry
316
- break
317
- }
318
- } else if (task.queueMessage && entry.registration.type === "queue") {
319
- if (entry.registration.source === task.queueMessage.source) {
320
- matchedEntry = entry
321
- break
322
- }
323
- }
324
- }
325
-
326
- if (!matchedEntry) {
327
- console.warn(`No handler found for task: ${task.taskId}`)
328
- return
329
- }
330
-
331
- if (task.storageEvent) {
332
- const storageEvent = this.fromProtoStorageEvent(task.storageEvent)
333
- await matchedEntry.handler(storageEvent)
334
- } else if (task.cronEvent) {
335
- const cronEvent = this.fromProtoCronEvent(task.cronEvent)
336
- await matchedEntry.handler(cronEvent)
337
- } else if (task.queueMessage) {
338
- const queueEvent = this.fromProtoQueueMessage(task.queueMessage)
339
- await matchedEntry.handler(queueEvent)
340
- }
341
-
342
- // Report success
343
- await this.sendTaskResult(task.taskId, { success: true })
344
- } catch (error) {
345
- // Report error
346
- await this.sendTaskResult(task.taskId, {
347
- success: false,
348
- error: error instanceof Error ? error.message : String(error),
349
- })
350
- }
351
- }
352
-
353
- private async handleArcCommand(command: ProtoArcCommand): Promise<unknown> {
354
- // Parse command parameters
355
- let params: unknown = {}
356
- if (command.params && command.params.length > 0) {
357
- try {
358
- const text = new TextDecoder().decode(command.params)
359
- params = JSON.parse(text)
360
- } catch {
361
- // If not valid JSON, use empty object
362
- params = {}
363
- }
364
- }
365
-
366
- // Run the command
367
- return await runCommand(command.commandName, params)
368
- }
369
-
370
- private async sendTaskResult(
371
- taskId: string,
372
- result: { success: boolean; error?: string; data?: unknown },
373
- ): Promise<void> {
374
- await wrapGrpcCall(
375
- "ControlService",
376
- "SendTaskResult",
377
- async () => {
378
- if (result.success) {
379
- const responseData = result.data
380
- ? new TextEncoder().encode(JSON.stringify(result.data))
381
- : new Uint8Array()
382
- await this.client.sendTaskResult({
383
- taskId,
384
- success: { responseData },
385
- })
386
- } else {
387
- await this.client.sendTaskResult({
388
- taskId,
389
- error: { code: "ERROR", message: result.error ?? "Unknown error" },
390
- })
391
- }
392
- },
393
- {},
394
- )
395
- }
396
-
397
- /**
398
- * Transform proto StorageEvent to core StorageEvent type.
399
- */
400
- private fromProtoStorageEvent(proto: ProtoStorageEvent): StorageEvent {
401
- const eventTypeMap: Record<string, StorageEventType> = {
402
- created: "created",
403
- deleted: "deleted",
404
- copied: "copied",
405
- metadata_updated: "metadataUpdated",
406
- restored: "restored",
407
- tier_changed: "tierChanged",
408
- }
409
-
410
- return {
411
- eventType: eventTypeMap[proto.eventType] ?? ("unknown" as StorageEventType),
412
- bucketName: proto.bucket,
413
- objectKey: proto.key,
414
- timestamp: proto.timestamp?.toISOString() ?? new Date().toISOString(),
415
- size: proto.size ? Number(proto.size) : undefined,
416
- contentType: proto.contentType || undefined,
417
- // These fields are not in the proto but are in the core type
418
- etag: undefined,
419
- metadata: undefined,
420
- copySource: undefined,
421
- previousTier: undefined,
422
- currentTier: undefined,
423
- region: undefined,
424
- versionId: undefined,
425
- }
426
- }
427
-
428
- /**
429
- * Transform proto CronEvent to CronEvent type.
430
- */
431
- private fromProtoCronEvent(proto: ProtoCronEvent): CronEvent {
432
- return {
433
- scheduleName: proto.scheduleName,
434
- timestamp: proto.scheduledTime?.toISOString() ?? new Date().toISOString(),
435
- }
436
- }
437
-
438
- /**
439
- * Transform proto QueueMessage to QueueMessageEvent type.
440
- */
441
- private fromProtoQueueMessage<T>(proto: ProtoQueueMessage): QueueMessageEvent<T> {
442
- // Proto payload is Uint8Array (JSON bytes), parse it
443
- let payload: unknown = null
444
- if (proto.payload && proto.payload.length > 0) {
445
- try {
446
- const text = new TextDecoder().decode(proto.payload)
447
- payload = JSON.parse(text)
448
- } catch {
449
- // If not valid JSON, use raw bytes
450
- payload = proto.payload
451
- }
452
- }
453
-
454
- return {
455
- id: proto.id,
456
- source: proto.source,
457
- receiptHandle: proto.receiptHandle,
458
- payload: payload as T,
459
- attemptCount: proto.attemptCount,
460
- timestamp: proto.timestamp ?? new Date(),
461
- }
462
- }
463
- }