Skip to content
This repository was archived by the owner on May 17, 2025. It is now read-only.

Commit c0aaf31

Browse files
authored
chore: a bunch of refactors (#37)
- renamed connectionParams to connectionInitPayload everywhere as it's more descriptive - allow promises for configuration values to ease the boilerplate when pulling tables names and connection info from a configuration system - rename gatewayHandler to webSocketHandler - only export external facing types BREAKING: renamed a bunch of core methods, changed exports and core types
1 parent 02507d6 commit c0aaf31

27 files changed

+171
-107
lines changed

README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Graphql Lambda Subscriptions
2+
23
[![Release](https://github.com/reconbot/graphql-lambda-subscriptions/actions/workflows/test.yml/badge.svg)](https://github.com/reconbot/graphql-lambda-subscriptions/actions/workflows/test.yml)
34

45
This is a fork of [`subscriptionless`](https://github.com/andyrichardson/subscriptionless) and is a Amazon Lambda Serverless equivalent to [graphQL-ws](https://github.com/enisdenjo/graphql-ws). It follows the [`graphql-ws prototcol`](https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md). It is tested with the [Architect Sandbox](https://arc.codes/docs/en/reference/cli/sandbox) against `graphql-ws` directly and run in production today. For many applications `graphql-lambda-subscriptions` should do what `graphql-ws` does for you today without having to run a server.
@@ -17,6 +18,7 @@ I had different requirements and needed more features. This project wouldn't exi
1718
- Provides a Pub/Sub system to broadcast events to subscriptions
1819
- Provides hooks for the full lifecycle of a subscription
1920
- Type compatible with GraphQL and [`nexus.js`](https://nexusjs.org)
21+
- Optional Logging
2022

2123
## Quick Start
2224

@@ -203,7 +205,7 @@ resources:
203205

204206
```tf
205207
resource "aws_dynamodb_table" "connections-table" {
206-
name = "subscriptionless_connections"
208+
name = "graphql_connections"
207209
billing_mode = "PROVISIONED"
208210
read_capacity = 1
209211
write_capacity = 1
@@ -216,7 +218,7 @@ resource "aws_dynamodb_table" "connections-table" {
216218
}
217219
218220
resource "aws_dynamodb_table" "subscriptions-table" {
219-
name = "subscriptionless_subscriptions"
221+
name = "graphql_subscriptions"
220222
billing_mode = "PROVISIONED"
221223
read_capacity = 1
222224
write_capacity = 1
@@ -370,7 +372,7 @@ Context values are accessible in all resolver level functions (`resolve`, `subsc
370372

371373
<summary>📖 Default value</summary>
372374

373-
Assuming no `context` argument is provided, the default value is an object containing a `connectionParams` attribute.
375+
Assuming no `context` argument is provided, the default value is an object containing a `connectionInitPayload` attribute.
374376

375377
This attribute contains the [(optionally parsed)](#events) payload from `connection_init`.
376378

@@ -379,7 +381,7 @@ export const resolver = {
379381
Subscribe: {
380382
mySubscription: {
381383
resolve: (event, args, context) => {
382-
console.log(context.connectionParams); // payload from connection_init
384+
console.log(context.connectionInitPayload); // payload from connection_init
383385
},
384386
},
385387
},
@@ -418,9 +420,9 @@ The default context value is passed as an argument.
418420
```ts
419421
const instance = createInstance({
420422
/* ... */
421-
context: ({ connectionParams }) => ({
423+
context: ({ connectionInitPayload }) => ({
422424
myAttr: 'hello',
423-
user: connectionParams.user,
425+
user: connectionInitPayload.user,
424426
}),
425427
});
426428
```

lib/handleStateMachineEvent.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,41 @@
11
import { MessageType } from 'graphql-ws'
22
import { ServerClosure, ServerInstance } from './types'
3-
import { sendMessage } from './utils/sendMessage'
3+
import { postToConnection } from './utils/postToConnection'
44
import { deleteConnection } from './utils/deleteConnection'
55

6-
export const handleStateMachineEvent = (c: ServerClosure): ServerInstance['stateMachineHandler'] => async (input) => {
7-
if (!c.pingpong) {
6+
export const handleStateMachineEvent = (serverPromise: Promise<ServerClosure>): ServerInstance['stateMachineHandler'] => async (input) => {
7+
const server = await serverPromise
8+
if (!server.pingpong) {
89
throw new Error('Invalid pingpong settings')
910
}
10-
const connection = Object.assign(new c.model.Connection(), {
11+
const connection = Object.assign(new server.model.Connection(), {
1112
id: input.connectionId,
1213
})
1314

1415
// Initial state - send ping message
1516
if (input.state === 'PING') {
16-
await sendMessage(c)({ ...input, message: { type: MessageType.Ping } })
17-
await c.mapper.update(Object.assign(connection, { hasPonged: false }), {
17+
await postToConnection(server)({ ...input, message: { type: MessageType.Ping } })
18+
await server.mapper.update(Object.assign(connection, { hasPonged: false }), {
1819
onMissing: 'skip',
1920
})
2021
return {
2122
...input,
2223
state: 'REVIEW',
23-
seconds: c.pingpong.delay,
24+
seconds: server.pingpong.delay,
2425
}
2526
}
2627

2728
// Follow up state - check if pong was returned
28-
const conn = await c.mapper.get(connection)
29+
const conn = await server.mapper.get(connection)
2930
if (conn.hasPonged) {
3031
return {
3132
...input,
3233
state: 'PING',
33-
seconds: c.pingpong.timeout,
34+
seconds: server.pingpong.timeout,
3435
}
3536
}
3637

37-
await deleteConnection(c)({ ...input })
38+
await deleteConnection(server)({ ...input })
3839
return {
3940
...input,
4041
state: 'ABORT',

lib/handleGatewayEvent.ts renamed to lib/handleWebSocketEvent.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,18 @@ import { subscribe } from './messages/subscribe'
77
import { connection_init } from './messages/connection_init'
88
import { pong } from './messages/pong'
99

10-
export const handleGatewayEvent = (server: ServerClosure): ServerInstance['gatewayHandler'] => async (event) => {
10+
export const handleWebSocketEvent = (serverPromise: Promise<ServerClosure>): ServerInstance['webSocketHandler'] => async (event) => {
11+
const server = await serverPromise
1112
if (!event.requestContext) {
12-
server.log('handleGatewayEvent unknown')
13+
server.log('handleWebSocketEvent unknown')
1314
return {
1415
statusCode: 200,
1516
body: '',
1617
}
1718
}
1819

1920
if (event.requestContext.eventType === 'CONNECT') {
20-
server.log('handleGatewayEvent CONNECT', { connectionId: event.requestContext.connectionId })
21+
server.log('handleWebSocketEvent CONNECT', { connectionId: event.requestContext.connectionId })
2122
await server.onConnect?.({ event })
2223
return {
2324
statusCode: 200,
@@ -30,7 +31,7 @@ export const handleGatewayEvent = (server: ServerClosure): ServerInstance['gatew
3031

3132
if (event.requestContext.eventType === 'MESSAGE') {
3233
const message = event.body === null ? null : JSON.parse(event.body)
33-
server.log('handleGatewayEvent MESSAGE', { connectionId: event.requestContext.connectionId, type: message.type })
34+
server.log('handleWebSocketEvent MESSAGE', { connectionId: event.requestContext.connectionId, type: message.type })
3435

3536
if (message.type === MessageType.ConnectionInit) {
3637
await connection_init({ server, event, message })
@@ -74,15 +75,15 @@ export const handleGatewayEvent = (server: ServerClosure): ServerInstance['gatew
7475
}
7576

7677
if (event.requestContext.eventType === 'DISCONNECT') {
77-
server.log('handleGatewayEvent DISCONNECT', { connectionId: event.requestContext.connectionId })
78+
server.log('handleWebSocketEvent DISCONNECT', { connectionId: event.requestContext.connectionId })
7879
await disconnect({ server, event, message: null })
7980
return {
8081
statusCode: 200,
8182
body: '',
8283
}
8384
}
8485

85-
server.log('handleGatewayEvent UNKNOWN', { connectionId: event.requestContext.connectionId })
86+
server.log('handleWebSocketEvent UNKNOWN', { connectionId: event.requestContext.connectionId })
8687
return {
8788
statusCode: 200,
8889
body: '',

lib/index-test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import { Handler } from 'aws-lambda'
33
import { tables } from '@architect/sandbox'
44
import { createInstance } from '.'
55
import { mockServerArgs } from './test/mockServer'
6-
import { APIGatewayWebSocketEvent, WebsocketResponse } from './types'
6+
import { APIGatewayWebSocketEvent, WebSocketResponse } from './types'
77

88
describe('createInstance', () => {
9-
describe('gatewayHandler', () => {
9+
describe('webSocketHandler', () => {
1010
before(async () => {
1111
await tables.start({ cwd: './mocks/arc-basic-events', quiet: true })
1212
})
@@ -18,8 +18,8 @@ describe('createInstance', () => {
1818
it('is type compatible with aws-lambda handler', async () => {
1919
const server = createInstance(await mockServerArgs())
2020

21-
const gatewayHandler: Handler<APIGatewayWebSocketEvent, WebsocketResponse> = server.gatewayHandler
22-
assert.ok(gatewayHandler)
21+
const webSocketHandler: Handler<APIGatewayWebSocketEvent, WebSocketResponse> = server.webSocketHandler
22+
assert.ok(webSocketHandler)
2323
})
2424
})
2525
})

lib/index.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,41 @@
11
import { ServerArgs, ServerClosure, ServerInstance } from './types'
22
import { publish } from './pubsub/publish'
33
import { complete } from './pubsub/complete'
4-
import { handleGatewayEvent } from './handleGatewayEvent'
4+
import { handleWebSocketEvent } from './handleWebSocketEvent'
55
import { handleStateMachineEvent } from './handleStateMachineEvent'
66
import { makeServerClosure } from './makeServerClosure'
77

88
export const createInstance = (opts: ServerArgs): ServerInstance => {
9-
const closure: ServerClosure = makeServerClosure(opts)
9+
const closure: Promise<ServerClosure> = makeServerClosure(opts)
1010

1111
return {
12-
gatewayHandler: handleGatewayEvent(closure),
12+
webSocketHandler: handleWebSocketEvent(closure),
1313
stateMachineHandler: handleStateMachineEvent(closure),
1414
publish: publish(closure),
1515
complete: complete(closure),
1616
}
1717
}
1818

1919
export * from './pubsub/subscribe'
20-
export * from './types'
20+
export {
21+
ServerArgs,
22+
ServerInstance,
23+
APIGatewayWebSocketRequestContext,
24+
SubscribeOptions,
25+
SubscribeArgs,
26+
SubscribePseudoIterable,
27+
MaybePromise,
28+
ApiGatewayManagementApiSubset,
29+
TableNames,
30+
APIGatewayWebSocketEvent,
31+
LoggerFunction,
32+
ApiSebSocketHandler,
33+
WebSocketResponse,
34+
StateFunctionInput,
35+
PubSubEvent,
36+
PartialBy,
37+
SubscriptionDefinition,
38+
SubscriptionFilter,
39+
} from './types'
2140
export { Subscription } from './model/Subscription'
2241
export { Connection } from './model/Connection'

lib/makeServerClosure.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,30 @@ import { ServerArgs, ServerClosure } from './types'
33
import { createModel } from './model/createModel'
44
import { Subscription } from './model/Subscription'
55
import { Connection } from './model/Connection'
6-
import { log } from './utils/logger'
6+
import { log as debugLogger } from './utils/logger'
77

8-
export function makeServerClosure(opts: ServerArgs): ServerClosure {
8+
export const makeServerClosure = async (opts: ServerArgs): Promise<ServerClosure> => {
9+
const {
10+
tableNames,
11+
log = debugLogger,
12+
dynamodb,
13+
apiGatewayManagementApi,
14+
...rest
15+
} = opts
916
return {
10-
log: log,
11-
...opts,
17+
...rest,
18+
apiGatewayManagementApi: await apiGatewayManagementApi,
19+
log,
1220
model: {
1321
Subscription: createModel({
1422
model: Subscription,
15-
table: opts.tableNames?.subscriptions || 'subscriptionless_subscriptions',
23+
table: (await tableNames)?.subscriptions || 'graphql_subscriptions',
1624
}),
1725
Connection: createModel({
1826
model: Connection,
19-
table: opts.tableNames?.connections || 'subscriptionless_connections',
27+
table: (await tableNames)?.connections || 'graphql_connections',
2028
}),
2129
},
22-
mapper: new DataMapper({ client: opts.dynamodb }),
30+
mapper: new DataMapper({ client: await dynamodb }),
2331
}
2432
}

lib/messages/complete.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export const complete: MessageHandler<CompleteMessage> =
2626
server.schema,
2727
parse(sub.subscription.query),
2828
undefined,
29-
await constructContext({ server, connectionParams: sub.connectionParams, connectionId: sub.connectionId }),
29+
await constructContext({ server, connectionInitPayload: sub.connectionInitPayload, connectionId: sub.connectionId }),
3030
sub.subscription.variables,
3131
sub.subscription.operationName,
3232
undefined,

lib/messages/connection_init.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { StepFunctions } from 'aws-sdk'
22
import { ConnectionInitMessage, MessageType } from 'graphql-ws'
33
import { StateFunctionInput, MessageHandler } from '../types'
4-
import { sendMessage } from '../utils/sendMessage'
4+
import { postToConnection } from '../utils/postToConnection'
55
import { deleteConnection } from '../utils/deleteConnection'
66

77
/** Handler function for 'connection_init' message. */
@@ -34,7 +34,7 @@ export const connection_init: MessageHandler<ConnectionInitMessage> =
3434
payload,
3535
})
3636
await server.mapper.put(connection)
37-
return sendMessage(server)({
37+
return postToConnection(server)({
3838
...event.requestContext,
3939
message: { type: MessageType.ConnectionAck },
4040
})

lib/messages/disconnect.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export const disconnect: MessageHandler<null> = async ({ server, event }) => {
3636
server.schema,
3737
parse(sub.subscription.query),
3838
undefined,
39-
await constructContext({ server, connectionParams: sub.connectionParams, connectionId: sub.connectionId }),
39+
await constructContext({ server, connectionInitPayload: sub.connectionInitPayload, connectionId: sub.connectionId }),
4040
sub.subscription.variables,
4141
sub.subscription.operationName,
4242
undefined,

lib/messages/ping.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { PingMessage, MessageType } from 'graphql-ws'
2-
import { sendMessage } from '../utils/sendMessage'
2+
import { postToConnection } from '../utils/postToConnection'
33
import { deleteConnection } from '../utils/deleteConnection'
44
import { MessageHandler } from '../types'
55

66
/** Handler function for 'ping' message. */
77
export const ping: MessageHandler<PingMessage> = async ({ server, event, message }) => {
88
try {
99
await server.onPing?.({ event, message })
10-
return sendMessage(server)({
10+
return postToConnection(server)({
1111
...event.requestContext,
1212
message: { type: MessageType.Pong },
1313
})

0 commit comments

Comments
 (0)