Skip to content

Commit 07f1c9d

Browse files
author
Ryan Miville
committed
sqs example
1 parent 25d6bde commit 07f1c9d

16 files changed

+324
-0
lines changed
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: test
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
- main
8+
pull_request:
9+
10+
jobs:
11+
test:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
- uses: erlef/setup-beam@v1
16+
with:
17+
otp-version: "26.0.2"
18+
gleam-version: "1.5.1"
19+
rebar3-version: "3"
20+
# elixir-version: "1.15.4"
21+
- run: gleam deps download
22+
- run: gleam test
23+
- run: gleam format --check src test

examples/sqs/.gitignore

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
*.beam
2+
*.ez
3+
/build
4+
erl_crash.dump
5+
6+
# sst
7+
.sst
8+
9+
node_modules
10+
.DS_Store

examples/sqs/README.md

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# sqs
2+
3+
[![Package Version](https://img.shields.io/hexpm/v/sqs)](https://hex.pm/packages/sqs)
4+
[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/sqs/)
5+
6+
```sh
7+
gleam add sqs@1
8+
```
9+
```gleam
10+
import sqs
11+
12+
pub fn main() {
13+
// TODO: An example of the project in use
14+
}
15+
```
16+
17+
Further documentation can be found at <https://hexdocs.pm/sqs>.
18+
19+
## Development
20+
21+
```sh
22+
gleam run # Run the project
23+
gleam test # Run the tests
24+
```

examples/sqs/bun.lockb

48.8 KB
Binary file not shown.

examples/sqs/gleam.toml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
name = "sqs"
2+
version = "1.0.0"
3+
target = "javascript"
4+
5+
[dependencies]
6+
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
7+
glambda = { path = "../.." }
8+
gleam_http = ">= 3.7.0 and < 4.0.0"
9+
gleam_javascript = ">= 0.13.0 and < 1.0.0"
10+
11+
12+
[dev-dependencies]
13+
gleeunit = ">= 1.0.0 and < 2.0.0"

examples/sqs/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log("Hello via Bun!");

examples/sqs/manifest.toml

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# This file was generated by Gleam
2+
# You typically do not need to edit this file
3+
4+
packages = [
5+
{ name = "glambda", version = "0.1.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_javascript", "gleam_stdlib"], source = "local", path = "../.." },
6+
{ name = "gleam_http", version = "3.7.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "EA66440C2269F7CED0F6845E5BD0DB68095775D627FA709A841CA78A398D6D56" },
7+
{ name = "gleam_javascript", version = "0.13.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_javascript", source = "hex", outer_checksum = "F98328FCF573DA6F3A35D7F6CB3F9FF19FD5224CCBA9151FCBEAA0B983AF2F58" },
8+
{ name = "gleam_stdlib", version = "0.40.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "86606B75A600BBD05E539EB59FABC6E307EEEA7B1E5865AFB6D980A93BCB2181" },
9+
{ name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" },
10+
]
11+
12+
[requirements]
13+
glambda = { path = "../.." }
14+
gleam_http = { version = ">= 3.7.0 and < 4.0.0" }
15+
gleam_javascript = { version = ">= 0.13.0 and < 1.0.0" }
16+
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
17+
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }

examples/sqs/package.json

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "sqs",
3+
"type": "module",
4+
"scripts": {
5+
"dev": "gleam build && sst dev"
6+
},
7+
"devDependencies": {
8+
"@types/aws-lambda": "8.10.145",
9+
"@types/bun": "latest"
10+
},
11+
"peerDependencies": {
12+
"typescript": "^5.0.0"
13+
},
14+
"dependencies": {
15+
"@aws-sdk/client-sqs": "^3.678.0",
16+
"sst": "3.2.48"
17+
}
18+
}

examples/sqs/publisher.js

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Resource } from "sst";
2+
import { SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs";
3+
const client = new SQSClient();
4+
5+
export const handler = async (event) => {
6+
// send a message
7+
await client.send(
8+
new SendMessageCommand({
9+
QueueUrl: Resource.MyQueue.url,
10+
MessageBody: "Hello from the subscriber",
11+
}),
12+
);
13+
14+
return {
15+
statusCode: 200,
16+
body: JSON.stringify({ status: "sent" }, null, 2),
17+
};
18+
};

examples/sqs/src/sqs.gleam

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import glambda.{
2+
type Context, type SqsBatchResponse, type SqsEvent, SqsBatchResponse,
3+
}
4+
import gleam/io
5+
import gleam/javascript/promise.{type Promise}
6+
import gleam/option.{type Option, None}
7+
8+
pub fn handle_request(
9+
event: SqsEvent,
10+
_ctx: Context,
11+
) -> Promise(Option(SqsBatchResponse)) {
12+
io.debug(event)
13+
promise.resolve(None)
14+
}
15+
16+
pub fn handler(event, ctx) {
17+
glambda.sqs_handler(handle_request)(event, ctx)
18+
}

examples/sqs/sst-env.d.ts

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/* This file is auto-generated by SST. Do not edit. */
2+
/* tslint:disable */
3+
/* eslint-disable */
4+
/* deno-fmt-ignore-file */
5+
import "sst"
6+
export {}
7+
declare module "sst" {
8+
export interface Resource {
9+
"MyApp": {
10+
"name": string
11+
"type": "sst.aws.Function"
12+
"url": string
13+
}
14+
"MyQueue": {
15+
"type": "sst.aws.Queue"
16+
"url": string
17+
}
18+
}
19+
}

examples/sqs/sst.config.ts

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/// <reference path="./.sst/platform/config.d.ts" />
2+
3+
export default $config({
4+
app(input) {
5+
return {
6+
name: "sqs",
7+
removal: input?.stage === "production" ? "retain" : "remove",
8+
home: "aws",
9+
providers: {
10+
aws: {
11+
region: "us-east-1",
12+
profile: "personal",
13+
},
14+
},
15+
};
16+
},
17+
async run() {
18+
const queue = new sst.aws.Queue("MyQueue");
19+
queue.subscribe("build/dev/javascript/sqs/sqs.handler");
20+
21+
const app = new sst.aws.Function("MyApp", {
22+
handler: "publisher.handler",
23+
link: [queue],
24+
url: true,
25+
});
26+
27+
return {
28+
app: app.url,
29+
queue: queue.url,
30+
};
31+
},
32+
});

examples/sqs/test/sqs_test.gleam

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import gleeunit
2+
import gleeunit/should
3+
4+
pub fn main() {
5+
gleeunit.main()
6+
}
7+
8+
// gleeunit test functions end in `_test`
9+
pub fn hello_world_test() {
10+
1
11+
|> should.equal(1)
12+
}

examples/sqs/tsconfig.json

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"compilerOptions": {
3+
// Enable latest features
4+
"lib": ["ESNext", "DOM"],
5+
"target": "ESNext",
6+
"module": "ESNext",
7+
"moduleDetection": "force",
8+
"jsx": "react-jsx",
9+
"allowJs": true,
10+
11+
// Bundler mode
12+
"moduleResolution": "bundler",
13+
"allowImportingTsExtensions": true,
14+
"verbatimModuleSyntax": true,
15+
"noEmit": true,
16+
17+
// Best practices
18+
"strict": true,
19+
"skipLibCheck": true,
20+
"noFallthroughCasesInSwitch": true,
21+
22+
// Some stricter flags (disabled by default)
23+
"noUnusedLocals": false,
24+
"noUnusedParameters": false,
25+
"noPropertyAccessFromIndexSignature": false
26+
}
27+
}

src/glambda.gleam

+22
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,22 @@ pub fn eventbridge_handler(handler: Handler(EventBridgeEvent, Nil)) -> JsHandler
364364
}
365365
}
366366

367+
pub fn sqs_handler(
368+
handler: Handler(SqsEvent, Option(SqsBatchResponse)),
369+
) -> JsHandler {
370+
fn(event: JsEvent, ctx: JsContext) -> Promise(JsResult) {
371+
let event = to_sqs_event(event)
372+
let ctx = to_context(ctx)
373+
handler(event, ctx)
374+
|> promise.map(fn(resp) {
375+
case resp {
376+
Some(resp) -> from_sqs_batch_response(resp)
377+
None -> Void
378+
}
379+
})
380+
}
381+
}
382+
367383
// --- FFI --------------------------------------------------------------------
368384

369385
@external(javascript, "./glambda_ffi.mjs", "toApiGatewayProxyEventV2")
@@ -379,3 +395,9 @@ fn to_context(ctx: JsContext) -> Context
379395

380396
@external(javascript, "./glambda_ffi.mjs", "toEventBridgeEvent")
381397
pub fn to_eventbridge_event(event: JsEvent) -> EventBridgeEvent
398+
399+
@external(javascript, "./glambda_ffi.mjs", "toSqsEvent")
400+
pub fn to_sqs_event(event: JsEvent) -> SqsEvent
401+
402+
@external(javascript, "./glambda_ffi.mjs", "fromSqsBatchResponse")
403+
pub fn from_sqs_batch_response(result: SqsBatchResponse) -> JsResult

src/glambda_ffi.mjs

+70
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ import {
1919
ClientContextEnv,
2020
ApiGatewayEventValidity,
2121
EventBridgeEvent,
22+
SqsEvent,
23+
SqsRecord,
24+
SqsRecordAttributes,
25+
SqsMessageAttribute,
26+
SqsBatchResponse,
27+
SqsBatchItemFailure,
2228
} from "./glambda.mjs";
2329

2430
export function toApiGatewayProxyEventV2(event) {
@@ -225,3 +231,67 @@ export function toEventBridgeEvent(event) {
225231
event.detail,
226232
);
227233
}
234+
235+
export function toSqsEvent(event) {
236+
return new SqsEvent(List.fromArray(event.Records.map(toSqsRecord)));
237+
}
238+
239+
function toSqsRecord(record) {
240+
return new SqsRecord(
241+
record.messageId,
242+
record.receiptHandle,
243+
record.body,
244+
toSqsRecordAttributes(record.attributes),
245+
toMessageAttributes(record.messageAttributes),
246+
record.md5OfBody,
247+
record.eventSource,
248+
record.eventSourceARN,
249+
record.awsRegion,
250+
);
251+
}
252+
253+
function toSqsRecordAttributes(attrs) {
254+
return new SqsRecordAttributes(
255+
maybe(attrs.AWSTraceHeader),
256+
attrs.ApproximateReceiveCount,
257+
attrs.SentTimestamp,
258+
attrs.SenderId,
259+
attrs.ApproximateFirstReceiveTimestamp,
260+
maybe(attrs.SequenceNumber),
261+
maybe(attrs.MessageGroupId),
262+
maybe(attrs.MessageDeduplicationId),
263+
maybe(attrs.DeadLetterQueueSourceArn),
264+
);
265+
}
266+
267+
function toMessageAttributes(attrs) {
268+
let entries = Object.entries(attrs).map(([key, value]) => {
269+
let v = toSqsMessageAttribute(value);
270+
return [key, v];
271+
});
272+
return $dict.from_list(List.fromArray(entries));
273+
}
274+
275+
function toSqsMessageAttribute(attr) {
276+
return new SqsMessageAttribute(
277+
maybe(attr.stringValue),
278+
maybe(attr.binaryValue),
279+
maybe(attr.stringListValue),
280+
maybe(attr.binaryListValue),
281+
attr.dataType,
282+
);
283+
}
284+
285+
export function fromSqsBatchResponse(response) {
286+
return {
287+
batchItemFailures: response.batch_item_failures
288+
.toArray()
289+
.map(fromSqsBatchItemFailure),
290+
};
291+
}
292+
293+
function fromSqsBatchItemFailure(failure) {
294+
return {
295+
itemIdentifier: failure.item_identifier,
296+
};
297+
}

0 commit comments

Comments
 (0)