@pikku/lambda 0.12.10 → 0.12.12

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.
@@ -1,31 +0,0 @@
1
- /**
2
- * Lambda handler for EventBridge scheduled events.
3
- *
4
- * Since each Lambda corresponds to a single unit, and each unit has
5
- * specific scheduled tasks registered, we run all tasks in this unit
6
- * when the schedule fires.
7
- */
8
-
9
- import type { ScheduledEvent } from 'aws-lambda'
10
- import { runScheduledTask } from '@pikku/core/scheduler'
11
- import { getScheduledTasks } from '@pikku/core/scheduler'
12
-
13
- export const runLambdaScheduled = async (
14
- _event: ScheduledEvent
15
- ): Promise<void> => {
16
- const scheduledTasks = getScheduledTasks()
17
-
18
- // Run all scheduled tasks registered in this unit.
19
- // Typically one task per unit, but iterate all to be safe.
20
- for (const [name] of scheduledTasks) {
21
- const traceId = `cron-${crypto.randomUUID()}`
22
- try {
23
- await runScheduledTask({ name, traceId })
24
- } catch (err) {
25
- console.error(
26
- `Scheduled task '${name}' (traceId: ${traceId}) failed:`,
27
- err
28
- )
29
- }
30
- }
31
- }
@@ -1,92 +0,0 @@
1
- /**
2
- * SQS implementation of QueueService (producer side).
3
- *
4
- * Sends messages to SQS queues. Queue URLs are resolved from environment
5
- * variables following the convention: SQS_QUEUE_URL_<SCREAMING_SNAKE_NAME>.
6
- *
7
- * The consumer side is handled by sqs-worker.ts (runSQSQueueWorker).
8
- */
9
-
10
- import { SQSClient, SendMessageCommand } from '@aws-sdk/client-sqs'
11
- import type { QueueService, QueueJob } from '@pikku/core/queue'
12
- import type { JobOptions } from '@pikku/core/queue'
13
-
14
- export class SQSQueueService implements QueueService {
15
- readonly supportsResults = false
16
- private client: SQSClient
17
-
18
- constructor(private queueUrlMap?: Record<string, string>) {
19
- this.client = new SQSClient({})
20
- }
21
-
22
- async add<T>(
23
- queueName: string,
24
- data: T,
25
- options?: JobOptions
26
- ): Promise<string> {
27
- const queueUrl = this.resolveQueueUrl(queueName)
28
- const messageId = crypto.randomUUID()
29
-
30
- const message = {
31
- id: messageId,
32
- queueName,
33
- data,
34
- pikkuUserId: options?.pikkuUserId,
35
- }
36
-
37
- const result = await this.client.send(
38
- new SendMessageCommand({
39
- QueueUrl: queueUrl,
40
- MessageBody: JSON.stringify(message),
41
- DelaySeconds: options?.delay
42
- ? Math.min(Math.ceil(options.delay / 1000), 900)
43
- : undefined,
44
- })
45
- )
46
-
47
- return result.MessageId ?? messageId
48
- }
49
-
50
- async getJob<T, R>(
51
- _queueName: string,
52
- _jobId: string
53
- ): Promise<QueueJob<T, R> | null> {
54
- throw new Error(
55
- 'SQSQueueService does not support getJob(). ' +
56
- 'SQS is fire-and-forget. ' +
57
- 'Use BullMQ or PgBoss if you need job result tracking.'
58
- )
59
- }
60
-
61
- /**
62
- * Resolves the SQS queue URL for a given queue name.
63
- *
64
- * Checks in order:
65
- * 1. Explicit queueUrlMap passed in constructor
66
- * 2. Environment variable SQS_QUEUE_URL_<SCREAMING_SNAKE>
67
- */
68
- private resolveQueueUrl(queueName: string): string {
69
- if (this.queueUrlMap?.[queueName]) {
70
- return this.queueUrlMap[queueName]
71
- }
72
-
73
- const envKey = `SQS_QUEUE_URL_${toScreamingSnake(queueName)}`
74
- const url = process.env[envKey]
75
-
76
- if (!url) {
77
- throw new Error(
78
- `SQS queue URL not found for queue "${queueName}". ` +
79
- `Set environment variable ${envKey} or pass queueUrlMap to constructor.`
80
- )
81
- }
82
-
83
- return url
84
- }
85
- }
86
-
87
- function toScreamingSnake(name: string): string {
88
- return name
89
- .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
90
- .replace(/-/g, '_')
91
- .toUpperCase()
92
- }
@@ -1,4 +0,0 @@
1
- export * from './websocket-message.js'
2
- export * from './websocket-connect.js'
3
- export * from './websocket-disconnect.js'
4
- export * from './lambda-eventhub-service.js'
@@ -1,58 +0,0 @@
1
- import type { CoreUserSession } from '@pikku/core/types'
2
- import type { ChannelStore } from '@pikku/core/channel'
3
- import type {
4
- BinaryData,
5
- PikkuChannelHandlerFactory,
6
- } from '@pikku/core/channel'
7
- import { PikkuAbstractChannelHandler } from '@pikku/core/channel'
8
- import { sendMessage } from './utils.js'
9
- import type { ApiGatewayManagementApiClient } from '@aws-sdk/client-apigatewaymanagementapi'
10
- import type { Logger } from '@pikku/core/services'
11
-
12
- class LambdaChannelHandler<
13
- UserSession extends CoreUserSession = CoreUserSession,
14
- OpeningData = unknown,
15
- Out = unknown,
16
- > extends PikkuAbstractChannelHandler<OpeningData, Out> {
17
- constructor(
18
- private logger: Logger,
19
- private callbackAPI: ApiGatewayManagementApiClient,
20
- channelId: string,
21
- channelName: string,
22
- openingData: OpeningData
23
- ) {
24
- super(channelId, channelName, openingData)
25
- }
26
-
27
- public async send(message: Out, isBinary?: boolean) {
28
- if (isBinary) {
29
- throw new Error('Binary data is not supported on serverless lambdas')
30
- }
31
- const data = JSON.stringify(message)
32
- await sendMessage(this.logger, this.callbackAPI, this.channelId, data)
33
- }
34
-
35
- public sendBinary(_data: BinaryData): void {
36
- throw new Error('Binary data is not supported on serverless lambdas')
37
- }
38
- }
39
-
40
- export const createLambdaChannelHandlerFactory = (
41
- logger: Logger,
42
- channelStore: ChannelStore,
43
- callbackAPI: ApiGatewayManagementApiClient
44
- ) => {
45
- const factory: PikkuChannelHandlerFactory = (
46
- channelId,
47
- channelName,
48
- openingData
49
- ) =>
50
- new LambdaChannelHandler(
51
- logger,
52
- callbackAPI,
53
- channelId,
54
- channelName,
55
- openingData
56
- )
57
- return factory
58
- }
@@ -1,66 +0,0 @@
1
- import type { ApiGatewayManagementApiClient } from '@aws-sdk/client-apigatewaymanagementapi'
2
- import type { ChannelStore, EventHubService } from '@pikku/core/channel'
3
- import type { EventHubStore } from '@pikku/core/channel'
4
- import { getApiGatewayManagementApiClient, sendMessages } from './utils.js'
5
- import type { Logger } from '@pikku/core/services'
6
- import type { APIGatewayEvent } from 'aws-lambda'
7
-
8
- export class LambdaEventHubService<
9
- EventTopics extends Record<string, any> = {},
10
- > implements EventHubService<EventTopics> {
11
- private callbackAPI: ApiGatewayManagementApiClient
12
-
13
- constructor(
14
- private logger: Logger,
15
- event: APIGatewayEvent,
16
- private channelStore: ChannelStore,
17
- private eventHubStore: EventHubStore<EventTopics>
18
- ) {
19
- this.callbackAPI = getApiGatewayManagementApiClient(logger, event)
20
- }
21
-
22
- async subscribe<T extends keyof EventTopics>(
23
- topic: T,
24
- channelId: string
25
- ): Promise<void> {
26
- await this.eventHubStore.subscribe(topic, channelId)
27
- }
28
-
29
- async unsubscribe<T extends keyof EventTopics>(
30
- topic: T,
31
- channelId: string
32
- ): Promise<void> {
33
- await this.eventHubStore.unsubscribe(topic, channelId)
34
- }
35
-
36
- async publish<T extends keyof EventTopics>(
37
- topic: T,
38
- channelId: string | null,
39
- data: EventTopics[T],
40
- isBinary?: boolean
41
- ): Promise<void> {
42
- const channelIds = await this.eventHubStore.getChannelIdsForTopic(
43
- topic as string
44
- )
45
- if (channelId) {
46
- await this.sendMessages(channelIds, channelId, data, isBinary)
47
- }
48
- }
49
-
50
- private async sendMessages(
51
- channelIds: string[],
52
- fromChannelId: string,
53
- data: EventTopics[keyof EventTopics],
54
- isBinary?: boolean
55
- ): Promise<void> {
56
- await sendMessages(
57
- this.logger,
58
- this.channelStore,
59
- this.callbackAPI,
60
- fromChannelId,
61
- channelIds,
62
- data,
63
- isBinary
64
- )
65
- }
66
- }
@@ -1,96 +0,0 @@
1
- import {
2
- ApiGatewayManagementApiClient,
3
- PostToConnectionCommand,
4
- } from '@aws-sdk/client-apigatewaymanagementapi'
5
- import type { Logger } from '@pikku/core/services'
6
- import { getSingletonServices } from '@pikku/core/state'
7
- import type { APIGatewayEvent } from 'aws-lambda'
8
- import { createLambdaChannelHandlerFactory } from './lambda-channel-handler.js'
9
- import type { ChannelStore } from '@pikku/core/channel'
10
-
11
- export const sendMessage = async (
12
- logger: Logger,
13
- callbackAPI: ApiGatewayManagementApiClient,
14
- ConnectionId: string,
15
- Data: string
16
- ): Promise<boolean> => {
17
- try {
18
- await callbackAPI.send(new PostToConnectionCommand({ ConnectionId, Data }))
19
- return true
20
- } catch (e: any) {
21
- // TODO: We need to check if it's a 410 and remove the connection if it is
22
- // Otherwise we need to log the error
23
- logger.error(e.message)
24
- return false
25
- }
26
- }
27
-
28
- export const sendMessages = async (
29
- logger: Logger,
30
- channelStore: ChannelStore,
31
- callbackAPI: ApiGatewayManagementApiClient,
32
- fromChannelId: string,
33
- channelIds: string[],
34
- data: unknown,
35
- isBinary?: boolean
36
- ): Promise<void> => {
37
- if (isBinary) {
38
- throw new Error('Binary data is not supported on serverless lambdas')
39
- }
40
- const staleWebsockets: string[] = []
41
- const Data = JSON.stringify(data)
42
- await Promise.all(
43
- channelIds.map(async (channelId) => {
44
- if (channelId !== fromChannelId) {
45
- const success = await sendMessage(logger, callbackAPI, channelId, Data)
46
- if (!success) {
47
- staleWebsockets.push(channelId)
48
- }
49
- }
50
- })
51
- )
52
- if (staleWebsockets.length > 0) {
53
- await channelStore.removeChannels(staleWebsockets)
54
- }
55
- }
56
-
57
- export const getApiGatewayManagementApiClient = (
58
- logger: Logger,
59
- event: APIGatewayEvent
60
- ) => {
61
- let endpoint: string
62
- if (process.env.IS_OFFLINE) {
63
- logger.info('Assuming serverless offline mode due to IS_OFFLINE')
64
- endpoint = `http://localhost:3001`
65
- } else if (event.requestContext.domainName?.includes('amazonaws.com')) {
66
- logger.info(
67
- 'Assuming we are using the default endpoint mode due to amazonaws.com domain'
68
- )
69
- endpoint = `https://${event.requestContext.domainName}/${event.requestContext.stage}`
70
- } else {
71
- logger.info('Assuming we are using a custom domain mode')
72
- endpoint = `https://${event.requestContext.domainName}`
73
- }
74
- return new ApiGatewayManagementApiClient({
75
- apiVersion: '2018-11-29',
76
- endpoint,
77
- })
78
- }
79
-
80
- export const getServerlessDependencies = (
81
- channelStore: ChannelStore,
82
- event: APIGatewayEvent
83
- ) => {
84
- const { logger } = getSingletonServices()
85
- const channelId = event.requestContext.connectionId
86
- if (!channelId) {
87
- throw new Error('No connectionId found in requestContext')
88
- }
89
- const callbackAPI = getApiGatewayManagementApiClient(logger, event)
90
- const channelHandlerFactory = createLambdaChannelHandlerFactory(
91
- logger,
92
- channelStore,
93
- callbackAPI
94
- )
95
- return { channelId, callbackAPI, channelHandlerFactory, channelStore }
96
- }
@@ -1,23 +0,0 @@
1
- import { PikkuFetchHTTPRequest, PikkuFetchHTTPResponse } from '@pikku/core/http'
2
- import type { APIGatewayEvent, APIGatewayProxyResult } from 'aws-lambda'
3
- import { responseToLambdaResult } from '../response-converter.js'
4
- import { runChannelConnect } from '@pikku/core/channel/serverless'
5
- import { getServerlessDependencies } from './utils.js'
6
- import type { WebsocketParams } from './websocket-types.js'
7
- import { lambdaEventToRequest } from '../request-converter.js'
8
-
9
- export const connectWebsocket = async (
10
- event: APIGatewayEvent,
11
- { channelStore }: WebsocketParams
12
- ): Promise<APIGatewayProxyResult> => {
13
- const runnerParams = getServerlessDependencies(channelStore, event)
14
- const request = new PikkuFetchHTTPRequest(lambdaEventToRequest(event))
15
- const response = new PikkuFetchHTTPResponse()
16
- await runChannelConnect({
17
- ...runnerParams,
18
- request,
19
- response,
20
- route: event.path || '/',
21
- })
22
- return responseToLambdaResult(response.toResponse())
23
- }
@@ -1,14 +0,0 @@
1
- import { runChannelDisconnect } from '@pikku/core/channel/serverless'
2
- import type { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'
3
-
4
- import { getServerlessDependencies } from './utils.js'
5
- import type { WebsocketParams } from './websocket-types.js'
6
-
7
- export const disconnectWebsocket = async (
8
- event: APIGatewayProxyEvent,
9
- { channelStore }: WebsocketParams
10
- ): Promise<APIGatewayProxyResult> => {
11
- const runnerParams = getServerlessDependencies(channelStore, event)
12
- await runChannelDisconnect(runnerParams)
13
- return { statusCode: 200, body: '' }
14
- }
@@ -1,23 +0,0 @@
1
- import { PikkuFetchHTTPResponse } from '@pikku/core/http'
2
- import { runChannelMessage } from '@pikku/core/channel/serverless'
3
- import type { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'
4
- import { getServerlessDependencies } from './utils.js'
5
- import type { WebsocketParams } from './websocket-types.js'
6
- import { responseToLambdaResult } from '../response-converter.js'
7
-
8
- export const processWebsocketMessage = async (
9
- event: APIGatewayProxyEvent,
10
- { channelStore }: WebsocketParams
11
- ): Promise<APIGatewayProxyResult> => {
12
- const runnerParams = getServerlessDependencies(channelStore, event)
13
- const response = new PikkuFetchHTTPResponse()
14
- try {
15
- const result = await runChannelMessage(runnerParams, event.body)
16
- if (result) {
17
- response.json(result as any)
18
- }
19
- } catch (e) {
20
- console.error(e)
21
- }
22
- return responseToLambdaResult(response.toResponse())
23
- }
@@ -1,5 +0,0 @@
1
- import type { ChannelStore } from '@pikku/core/channel'
2
-
3
- export type WebsocketParams = {
4
- channelStore: ChannelStore
5
- }
package/tsconfig.json DELETED
@@ -1,17 +0,0 @@
1
- {
2
- "extends": "../../../tsconfig.json",
3
- "compilerOptions": {
4
- "rootDir": "./src",
5
- "module": "Node18",
6
- "outDir": "dist",
7
- "target": "esnext",
8
- "declaration": true
9
- },
10
- "include": ["bin/**/*.ts", "src/**/*.ts"],
11
- "exclude": ["**/*.test.ts", "node_modules", "bin/dist"],
12
- "references": [
13
- {
14
- "path": "../../core/tsconfig.json"
15
- }
16
- ]
17
- }
@@ -1 +0,0 @@
1
- {"root":["./src/handler-factories.ts","./src/index.ts","./src/lambda-deployment-service.ts","./src/request-converter.ts","./src/response-converter.ts","./src/sqs-queue-service.ts","./src/http/index.ts","./src/http/request-converter-v2.ts","./src/http/response-converter-v2.ts","./src/http/run-fetch-v2.ts","./src/http/run-fetch.ts","./src/queue/index.ts","./src/queue/sqs-worker.ts","./src/scheduled/index.ts","./src/scheduled/run-scheduled.ts","./src/websocket/index.ts","./src/websocket/lambda-channel-handler.ts","./src/websocket/lambda-eventhub-service.ts","./src/websocket/utils.ts","./src/websocket/websocket-connect.ts","./src/websocket/websocket-disconnect.ts","./src/websocket/websocket-message.ts","./src/websocket/websocket-types.ts"],"version":"6.0.3"}