@mono-labs/dev 0.1.256 → 0.1.257
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/README.md +294 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# @mono-labs/dev
|
|
2
|
+
|
|
3
|
+
Local development server and WebSocket adapter for mono-labs. Maps Lambda handlers to Express routes, emulates API Gateway WebSocket connections, and provides a Redis caching abstraction.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
yarn add @mono-labs/dev
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Optional Peer Dependencies
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# For Redis-backed channel store and CacheRelay
|
|
15
|
+
yarn add ioredis
|
|
16
|
+
|
|
17
|
+
# For API Gateway WebSocket mode (SocketGatewayClient)
|
|
18
|
+
yarn add @aws-sdk/client-apigatewaymanagementapi
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## API Reference
|
|
22
|
+
|
|
23
|
+
### LocalServer
|
|
24
|
+
|
|
25
|
+
Express-based HTTP server that maps Lambda handlers to local routes.
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { LocalServer } from '@mono-labs/dev'
|
|
29
|
+
|
|
30
|
+
const server = new LocalServer({ debug: true })
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
- **`constructor(config?)`** -- Creates a new server. `LocalServerConfig` options: `debug?: boolean`, `useRedis?: boolean`, `redis?: RedisConfig`.
|
|
34
|
+
- **`.app`** -- The underlying `express.Express` instance for custom middleware.
|
|
35
|
+
|
|
36
|
+
#### `.lambda(path, handler, options?)`
|
|
37
|
+
|
|
38
|
+
Registers a Lambda handler at the given HTTP path. Supports two event types:
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
// API Gateway V2 (default)
|
|
42
|
+
server.lambda('/api/*', myApiGatewayHandler)
|
|
43
|
+
server.lambda('/api/*', myApiGatewayHandler, { eventType: 'api-gateway' })
|
|
44
|
+
|
|
45
|
+
// Application Load Balancer
|
|
46
|
+
server.lambda('/alb/*', myALBHandler, { eventType: 'alb' })
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
#### `.attachSocket(adapterFn, config?)`
|
|
50
|
+
|
|
51
|
+
Attaches the WebSocket adapter to the server's HTTP instance:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { attachSocketAdapter } from '@mono-labs/dev'
|
|
55
|
+
|
|
56
|
+
const socket = server.attachSocket(attachSocketAdapter, {
|
|
57
|
+
debug: true,
|
|
58
|
+
connectHandler: async (connectionId, { token }) => ({
|
|
59
|
+
response: { statusCode: 200 },
|
|
60
|
+
userContext: { userId: '123', organizationId: 'org-1' }
|
|
61
|
+
}),
|
|
62
|
+
routes: {
|
|
63
|
+
ping: async (body, ctx) => ({ statusCode: 200, body: 'pong' })
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Returns the same object as `attachSocketAdapter()` (see below).
|
|
69
|
+
|
|
70
|
+
#### `.listen(port, hostname?)`
|
|
71
|
+
|
|
72
|
+
Starts the HTTP server:
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
server.listen(3000)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### WebSocket System
|
|
81
|
+
|
|
82
|
+
#### `attachSocketAdapter(wss, config?)`
|
|
83
|
+
|
|
84
|
+
Wires a `WebSocketServer` instance with connection tracking, action routing, and event synthesis:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import { WebSocketServer } from 'ws'
|
|
88
|
+
import { attachSocketAdapter } from '@mono-labs/dev'
|
|
89
|
+
|
|
90
|
+
const wss = new WebSocketServer({ noServer: true })
|
|
91
|
+
const {
|
|
92
|
+
postToConnection,
|
|
93
|
+
connectionRegistry,
|
|
94
|
+
actionRouter,
|
|
95
|
+
channelStore,
|
|
96
|
+
socketEmitter,
|
|
97
|
+
getConnectionId,
|
|
98
|
+
} = attachSocketAdapter(wss, { debug: true })
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**`SocketAdapterConfig`** options:
|
|
102
|
+
- `domainName?: string` -- Domain for synthesized events (default: `'localhost'`)
|
|
103
|
+
- `stage?: string` -- Stage for synthesized events (default: `'local'`)
|
|
104
|
+
- `debug?: boolean` -- Enable debug logging
|
|
105
|
+
- `connectHandler?: ConnectHandlerFn` -- Custom $connect handler
|
|
106
|
+
- `disconnectHandler?: DisconnectHandlerFn` -- Custom $disconnect handler
|
|
107
|
+
- `routes?: Record<string, ActionHandler>` -- Action routes
|
|
108
|
+
- `defaultHandler?: ActionHandler` -- Fallback for unmatched actions
|
|
109
|
+
- `channelStore?: ChannelStore` -- Custom channel store implementation
|
|
110
|
+
- `useRedis?: boolean` -- Use Redis-backed channel store
|
|
111
|
+
- `redis?: RedisConfig` -- Redis connection options
|
|
112
|
+
|
|
113
|
+
#### `ConnectionRegistry`
|
|
114
|
+
|
|
115
|
+
Tracks WebSocket connections and their user contexts:
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
registry.register(ws): ConnectionId
|
|
119
|
+
registry.unregister(connectionId): void
|
|
120
|
+
registry.get(connectionId): WebSocket | undefined
|
|
121
|
+
registry.getAll(): ConnectionId[]
|
|
122
|
+
registry.setUserContext(connectionId, ctx): void
|
|
123
|
+
registry.getUserContext(connectionId): WebSocketUserContext | undefined
|
|
124
|
+
registry.getConnectionsByUserId(userId): ConnectionId[]
|
|
125
|
+
registry.getConnectionsByOrgId(orgId): ConnectionId[]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### `ActionRouter`
|
|
129
|
+
|
|
130
|
+
Routes incoming WebSocket messages to handlers based on the `action` field:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
router.addRoute('chat:send', async (body, ctx) => {
|
|
134
|
+
return { statusCode: 200, body: 'ok' }
|
|
135
|
+
})
|
|
136
|
+
router.setDefaultHandler(async (body, ctx) => {
|
|
137
|
+
return { statusCode: 400, body: 'Unknown action' }
|
|
138
|
+
})
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**`ActionHandlerContext`**: `{ connectionId, requestContext, postToConnection, userContext }`
|
|
142
|
+
|
|
143
|
+
#### `SocketGatewayClient`
|
|
144
|
+
|
|
145
|
+
Dual-mode client for posting messages to WebSocket connections. Accepts either a `ConnectionRegistry` for local mode or a URL string for AWS API Gateway mode:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { SocketGatewayClient } from '@mono-labs/dev'
|
|
149
|
+
|
|
150
|
+
// Local mode (uses ConnectionRegistry to find WebSocket instances)
|
|
151
|
+
const localClient = new SocketGatewayClient(connectionRegistry)
|
|
152
|
+
|
|
153
|
+
// API Gateway mode (uses @aws-sdk/client-apigatewaymanagementapi)
|
|
154
|
+
const awsClient = new SocketGatewayClient('https://abc123.execute-api.us-east-1.amazonaws.com/prod')
|
|
155
|
+
|
|
156
|
+
await client.postToConnection(connectionId, { message: 'hello' })
|
|
157
|
+
|
|
158
|
+
// Get a bound function for dependency injection
|
|
159
|
+
const postFn = client.asFunction()
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
#### `SocketEmitter`
|
|
163
|
+
|
|
164
|
+
High-level emit API targeting users, orgs, connections, channels, or broadcast:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import type { EmitTarget } from '@mono-labs/dev'
|
|
168
|
+
|
|
169
|
+
await socketEmitter.emit({ userId: 'user-1' }, { event: 'update' })
|
|
170
|
+
await socketEmitter.emit({ orgId: 'org-1' }, { event: 'notify' })
|
|
171
|
+
await socketEmitter.emit({ connectionId: 'conn-1' }, data)
|
|
172
|
+
await socketEmitter.emit({ channel: 'room-1' }, data)
|
|
173
|
+
await socketEmitter.emit('broadcast', data)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
#### `ChannelStore`
|
|
177
|
+
|
|
178
|
+
Manages pub/sub channel subscriptions. Two implementations:
|
|
179
|
+
|
|
180
|
+
- **`InMemoryChannelStore`** -- Default. In-memory Maps, no dependencies.
|
|
181
|
+
- **`RedisChannelStore`** -- Redis-backed. Requires `ioredis` peer dependency.
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
interface ChannelStore {
|
|
185
|
+
subscribe(connectionId, channel): Promise<void>
|
|
186
|
+
unsubscribe(connectionId, channel): Promise<void>
|
|
187
|
+
getSubscribers(channel): Promise<ConnectionId[]>
|
|
188
|
+
removeAll(connectionId): Promise<void>
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
### CacheRelay
|
|
195
|
+
|
|
196
|
+
Redis abstraction with namespaced operations. Requires `ioredis` peer dependency.
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
import { initCacheRelay, getCacheRelay } from '@mono-labs/dev'
|
|
200
|
+
|
|
201
|
+
// Initialize (defaults to localhost:6379)
|
|
202
|
+
initCacheRelay()
|
|
203
|
+
|
|
204
|
+
// Or with a connection string (bare hostnames auto-normalized to redis://)
|
|
205
|
+
initCacheRelay('my-redis-host:6379')
|
|
206
|
+
|
|
207
|
+
// Get the singleton instance
|
|
208
|
+
const cache = getCacheRelay()
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Top-level convenience methods:**
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
await cache.set('key', { foo: 'bar' }, { ttlSeconds: 300 })
|
|
215
|
+
const value = await cache.get('key') // string | null
|
|
216
|
+
const parsed = await cache.gett<MyType>('key') // T | null
|
|
217
|
+
await cache.del('key1', 'key2')
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**Namespaced operations:**
|
|
221
|
+
|
|
222
|
+
| Namespace | Operations |
|
|
223
|
+
|-----------|-----------|
|
|
224
|
+
| `cache.strings` | `get`, `set`, `mget`, `mset`, `incr`, `incrby`, `decr`, `decrby`, `append`, `strlen` |
|
|
225
|
+
| `cache.hashes` | `get`, `set`, `getAll`, `del`, `exists`, `keys`, `vals`, `len`, `mset`, `mget`, `incrby` |
|
|
226
|
+
| `cache.lists` | `push`, `lpush`, `pop`, `lpop`, `range`, `len`, `trim`, `index`, `set`, `rem` |
|
|
227
|
+
| `cache.sets` | `add`, `rem`, `members`, `isMember`, `card`, `union`, `inter`, `diff` |
|
|
228
|
+
| `cache.sortedSets` | `add`, `rem`, `range`, `rangeWithScores`, `rangeByScore`, `revRange`, `score`, `rank`, `card`, `incrby`, `remRangeByRank`, `remRangeByScore` |
|
|
229
|
+
| `cache.keys` | `exists`, `expire`, `ttl`, `pttl`, `persist`, `rename`, `type`, `scan` |
|
|
230
|
+
| `cache.pubsub` | `publish`, `subscribe`, `unsubscribe` |
|
|
231
|
+
| `cache.transactions` | `multi`, `exec` |
|
|
232
|
+
| `cache.scripts` | `eval`, `evalsha` |
|
|
233
|
+
| `cache.geo` | `add`, `pos`, `dist`, `radius` |
|
|
234
|
+
| `cache.hyperloglog` | `add`, `count`, `merge` |
|
|
235
|
+
| `cache.bitmaps` | `setbit`, `getbit`, `count`, `op` |
|
|
236
|
+
| `cache.streams` | `add`, `read`, `len`, `range`, `trim` |
|
|
237
|
+
|
|
238
|
+
Access the raw `ioredis` client via `cache.raw`.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Types
|
|
243
|
+
|
|
244
|
+
All exported types from `@mono-labs/dev`:
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
// LocalServer
|
|
248
|
+
type ApiGatewayHandler
|
|
249
|
+
type ALBHandler
|
|
250
|
+
interface LocalServerConfig
|
|
251
|
+
|
|
252
|
+
// WebSocket
|
|
253
|
+
type ConnectionId
|
|
254
|
+
type PostToConnectionFn
|
|
255
|
+
interface SocketAdapterConfig
|
|
256
|
+
interface RedisConfig
|
|
257
|
+
type ConnectHandlerFn
|
|
258
|
+
type DisconnectHandlerFn
|
|
259
|
+
type ActionHandler
|
|
260
|
+
interface ActionHandlerContext
|
|
261
|
+
interface ActionHandlerResult
|
|
262
|
+
interface LocalRequestContext
|
|
263
|
+
interface WebSocketUserContext
|
|
264
|
+
interface ChannelStore
|
|
265
|
+
type EmitTarget
|
|
266
|
+
|
|
267
|
+
// CacheRelay
|
|
268
|
+
interface CacheRelay
|
|
269
|
+
interface StringOps
|
|
270
|
+
interface HashOps
|
|
271
|
+
interface ListOps
|
|
272
|
+
interface SetOps
|
|
273
|
+
interface SortedSetOps
|
|
274
|
+
interface KeyOps
|
|
275
|
+
interface PubSubOps
|
|
276
|
+
interface TransactionOps
|
|
277
|
+
interface ScriptOps
|
|
278
|
+
interface GeoOps
|
|
279
|
+
interface HyperLogLogOps
|
|
280
|
+
interface BitmapOps
|
|
281
|
+
interface StreamOps
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Development
|
|
285
|
+
|
|
286
|
+
Build the dev package:
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
yarn build:dev
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
This package has no internal `@mono-labs` dependencies -- it is independent in the dependency graph.
|
|
293
|
+
|
|
294
|
+
See the [Contributing guide](../../CONTRIBUTING.md) for full development setup.
|