zuplo 6.71.20 → 6.71.21

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.
@@ -5,22 +5,35 @@ sidebar_label: WebSocket Handler
5
5
 
6
6
  :::note
7
7
 
8
- This is an Enterprise only feature at this time. Please contact us to trial this
9
- or sign up for an Enterprise account.
8
+ WebSocket handlers are an Enterprise-only feature at this time. Please contact
9
+ us to trial this or sign up for an Enterprise account.
10
10
 
11
11
  :::
12
12
 
13
- The WebSocket Handler enables you to manage WebSocket connections to your
14
- backend WebSocket APIs. It can be configured alongside other existing policies
15
- like [Rate Limiting](../policies/rate-limit-inbound.mdx),
16
- [API Keys](../policies/api-key-inbound.mdx), etc. and is available for use on
13
+ Zuplo provides two handlers for proxying WebSocket connections to your backend
14
+ WebSocket APIs:
15
+
16
+ - **`webSocketHandler`** proxies WebSocket traffic straight through to your
17
+ backend without inspecting the messages. Use this when you only need to
18
+ authenticate, rate limit, or route the connection.
19
+ - **`webSocketPipelineHandler`** does everything `webSocketHandler` does and
20
+ additionally runs every message through a policy pipeline, so you can inspect,
21
+ transform, or drop individual messages in either direction. See the
22
+ [WebSocket Pipeline Handler](./websocket-pipeline-handler.mdx).
23
+
24
+ Both handlers can be configured alongside other existing policies like
25
+ [Rate Limiting](../policies/rate-limit-inbound.mdx),
26
+ [API Keys](../policies/api-key-inbound.mdx), etc. and are available for use on
17
27
  all environments.
18
28
 
19
- This handler is only configurable via the JSON View on a project's Route
29
+ These handlers are only configurable via the JSON View on a project's Route
20
30
  Designer or directly in your project's `*.oas.json` file.
21
31
 
22
32
  ## Setup in `routes.oas.json`
23
33
 
34
+ This section covers the passthrough `webSocketHandler`. To intercept messages,
35
+ see the [WebSocket Pipeline Handler](./websocket-pipeline-handler.mdx).
36
+
24
37
  Configuration of the WebSocket Handler is similar to other available handlers.
25
38
  Set the name of the path that your WebSocket API route will use, set the use of
26
39
  the `webSocketHandler` export from `@zuplo/runtime` module in the handler
@@ -62,6 +75,9 @@ The WebSocket Handler accepts the following options in the `options` property:
62
75
  - **`rewritePattern`** (required): The URL pattern for the backend WebSocket
63
76
  endpoint. Supports JavaScript string interpolation syntax for dynamic URL
64
77
  construction based on request data and environment variables.
78
+ - **`policies`** (optional, `webSocketPipelineHandler` only): Configures the
79
+ message-interception policies that run on each WebSocket frame. See the
80
+ [WebSocket Pipeline Handler](./websocket-pipeline-handler.mdx).
65
81
 
66
82
  Similar to other handlers using `rewritePattern`, it supports JavaScript string
67
83
  interpolation syntax and can be used to shape the URL based on data from the
@@ -0,0 +1,189 @@
1
+ ---
2
+ title: WebSocket Pipeline Handler
3
+ sidebar_label: WebSocket Pipeline Handler
4
+ ---
5
+
6
+ :::note
7
+
8
+ The WebSocket Pipeline Handler is an Enterprise-only feature. Please contact us
9
+ to trial this or sign up for an Enterprise account.
10
+
11
+ :::
12
+
13
+ The `webSocketPipelineHandler` proxies WebSocket connections exactly like the
14
+ [WebSocket Handler](./websocket-handler.mdx), but additionally passes every
15
+ WebSocket message through a pipeline of **policy functions** before forwarding
16
+ it. Each policy can inspect, transform, or drop the message. Use this to redact
17
+ sensitive fields, enforce a message schema, filter events, or add observability
18
+ to real-time traffic.
19
+
20
+ Messages are intercepted in both directions, configured independently:
21
+
22
+ - **`inbound`** policies process messages traveling from the client to your
23
+ backend.
24
+ - **`outbound`** policies process messages traveling from your backend to the
25
+ client.
26
+
27
+ :::note
28
+
29
+ The pipeline only intercepts **message** events. Connection lifecycle events are
30
+ handled automatically: when either side closes, the other side is closed, and
31
+ socket errors are logged and forwarded. There is no policy hook for `close` or
32
+ `error` events.
33
+
34
+ :::
35
+
36
+ ## Configuration
37
+
38
+ Use the `webSocketPipelineHandler` export and add an `inbound` and/or `outbound`
39
+ array under `options.policies`. Each entry points to an exported function in one
40
+ of your project's modules.
41
+
42
+ ```json
43
+ "/my-websocket": {
44
+ "x-zuplo-path": {
45
+ "pathMode": "open-api"
46
+ },
47
+ "get": {
48
+ "summary": "WebSocket route with message interception",
49
+ "x-zuplo-route": {
50
+ "corsPolicy": "none",
51
+ "handler": {
52
+ "export": "webSocketPipelineHandler",
53
+ "module": "$import(@zuplo/runtime)",
54
+ "options": {
55
+ "rewritePattern": "https://myservice.com/websocket",
56
+ "policies": {
57
+ "inbound": [
58
+ {
59
+ "module": "$import(./modules/websocket-policies)",
60
+ "export": "redactInbound"
61
+ }
62
+ ],
63
+ "outbound": [
64
+ {
65
+ "module": "$import(./modules/websocket-policies)",
66
+ "export": "filterOutbound"
67
+ }
68
+ ]
69
+ }
70
+ }
71
+ },
72
+ "policies": {
73
+ "inbound": []
74
+ }
75
+ },
76
+ "operationId": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e"
77
+ }
78
+ }
79
+ ```
80
+
81
+ :::caution{title="Two different policies blocks"}
82
+
83
+ The `policies` object inside `handler.options` configures the **message**
84
+ policies that run on each WebSocket frame. This is separate from the route-level
85
+ `policies` block (the `inbound` array next to `handler`), which configures the
86
+ standard request policies — such as [API Key](../policies/api-key-inbound.mdx)
87
+ or [Rate Limiting](../policies/rate-limit-inbound.mdx) — that run once during
88
+ the initial connection upgrade.
89
+
90
+ :::
91
+
92
+ The `rewritePattern` option behaves identically to the
93
+ [WebSocket Handler](./websocket-handler.mdx#handler-options), including
94
+ JavaScript string interpolation.
95
+
96
+ ## Writing a message policy
97
+
98
+ A message policy is an exported function that matches the following signature:
99
+
100
+ ```ts
101
+ import { ZuploContext, ZuploRequest } from "@zuplo/runtime";
102
+
103
+ async function webSocketPolicy(
104
+ data: string,
105
+ target: WebSocket,
106
+ source: WebSocket,
107
+ request: ZuploRequest,
108
+ context: ZuploContext,
109
+ ): Promise<unknown>;
110
+ ```
111
+
112
+ | Parameter | Description |
113
+ | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
114
+ | `data` | The message payload. For text protocols this is a string; binary frames arrive in the platform's binary form (for example, an `ArrayBuffer`). For an `inbound` policy this is the message from the client; for an `outbound` policy it is the message from the backend. |
115
+ | `target` | The destination socket the message is being forwarded to. For `inbound` this is the backend connection; for `outbound` it is the client connection. |
116
+ | `source` | The socket the message originated from. Call `source.send(...)` to send a message back to the originator, such as an acknowledgement or error. |
117
+ | `request` | The original [`ZuploRequest`](../programmable-api/zuplo-request.mdx) from the connection upgrade. |
118
+ | `context` | The [`ZuploContext`](../programmable-api/zuplo-context.mdx) for the connection. |
119
+
120
+ The return value controls what happens next:
121
+
122
+ - **Return the data** (modified or unchanged) to forward it to `target`. When
123
+ multiple policies are configured, the return value is passed as `data` to the
124
+ next policy in the array.
125
+ - **Return `undefined`** to drop the message. It is not forwarded, and no
126
+ further policies run for that message.
127
+
128
+ Policies run in the order they appear in the `inbound` / `outbound` array, and
129
+ each policy may be asynchronous. If a policy throws, Zuplo logs the error and
130
+ drops the message.
131
+
132
+ ## Example: redact fields from inbound messages
133
+
134
+ This inbound policy parses each JSON message from the client, removes a
135
+ sensitive field, and forwards the result to the backend.
136
+
137
+ ```ts
138
+ import { ZuploContext, ZuploRequest } from "@zuplo/runtime";
139
+
140
+ export async function redactInbound(
141
+ data: string,
142
+ target: WebSocket,
143
+ source: WebSocket,
144
+ request: ZuploRequest,
145
+ context: ZuploContext,
146
+ ) {
147
+ let message: Record<string, unknown>;
148
+ try {
149
+ message = JSON.parse(data);
150
+ } catch (err) {
151
+ context.log.warn("Dropping non-JSON WebSocket message");
152
+ return undefined; // drop messages that aren't valid JSON
153
+ }
154
+
155
+ delete message.ssn;
156
+
157
+ return JSON.stringify(message);
158
+ }
159
+ ```
160
+
161
+ ## Example: filter outbound messages
162
+
163
+ This outbound policy inspects messages from the backend and drops internal
164
+ events so they never reach the client. Other messages pass through unchanged.
165
+
166
+ ```ts
167
+ import { ZuploContext, ZuploRequest } from "@zuplo/runtime";
168
+
169
+ export async function filterOutbound(
170
+ data: string,
171
+ target: WebSocket,
172
+ source: WebSocket,
173
+ request: ZuploRequest,
174
+ context: ZuploContext,
175
+ ) {
176
+ const message = JSON.parse(data);
177
+
178
+ if (message.type === "internal") {
179
+ return undefined; // drop internal events; the client never sees them
180
+ }
181
+
182
+ return data;
183
+ }
184
+ ```
185
+
186
+ You can configure multiple policies in each direction to compose behavior — for
187
+ example, one policy to validate a message against a schema and a second to
188
+ redact fields. Because each policy receives the previous policy's output, order
189
+ matters.
@@ -10,9 +10,8 @@ description: |
10
10
 
11
11
  :::note{title="Beta"}
12
12
 
13
- Cross App Access support is in beta and builds on the
14
- [MCP Gateway](../introduction.mdx), which is also in beta. The configuration
15
- model and policy options may change before general availability.
13
+ Cross App Access support is in beta. The configuration model and policy options
14
+ may change before general availability.
16
15
 
17
16
  :::
18
17
 
@@ -7,15 +7,6 @@ description:
7
7
  analytics.
8
8
  ---
9
9
 
10
- :::note{title="Beta"}
11
-
12
- The MCP Gateway is in beta. The configuration model and APIs may change before
13
- general availability. Build with it, share feedback — just pin a
14
- [compatibility date](./code-config/compatibility-dates.mdx) and expect the
15
- occasional rough edge.
16
-
17
- :::
18
-
19
10
  The Zuplo MCP Gateway fronts one or more remote
20
11
  [Model Context Protocol](https://modelcontextprotocol.io) (MCP) servers with a
21
12
  single, OAuth-protected endpoint that AI clients connect to. Users sign in once
@@ -99,6 +99,7 @@
99
99
  | sleep-inbound | Sleep / Delay | Add a delay to the incoming request. Useful for testing. | api-gateway |
100
100
  | stripe-webhook-verification-inbound | Stripe Webhook Auth | The Stripe Webhook policy validates the authenticity of an incoming Stripe webhook. | api-gateway |
101
101
  | supabase-jwt-auth-inbound | Supabase JWT Auth | The Supabase JWT Authentication policy supports user JWT tokens created by Supabase. | api-gateway |
102
+ | traffic-splitting-inbound | Traffic Splitting | Splits traffic randomly across a set of weighted base paths. On each request one base path is selected (weighted by `weight`) and written to the request custom context at `customOutputProperty`. Reference it from a later URL Rewrite `rewritePattern` or URL Forward `baseUrl`, e.g. `${context.custom.trafficSplitting.basePath}`. | api-gateway |
102
103
  | transform-body-inbound | Transform Request Body | Transform the body of an incoming request. | api-gateway |
103
104
  | transform-body-outbound | Transform Response Body | Transform the body of an outgoing response. | api-gateway |
104
105
  | upstream-azure-ad-service-auth-inbound | Upstream Azure AD Service Auth | Uses Azure Active Directory to add an Authorization header to the request in order to authenticate requests using Azure identity. | api-gateway |
@@ -96,14 +96,16 @@
96
96
  {
97
97
  "rateLimitBy": "ip",
98
98
  "requestsAllowed": 2,
99
- "timeWindowMinutes": 1
99
+ "timeWindowMinutes": 1,
100
+ "mode": "async"
100
101
  },
101
102
  {
102
103
  "rateLimitBy": "function",
103
104
  "identifier": {
104
105
  "export": "$import(./modules/my-module)",
105
106
  "module": "default"
106
- }
107
+ },
108
+ "mode": "async"
107
109
  }
108
110
  ]
109
111
  }
@@ -115,7 +117,8 @@
115
117
  "options": {
116
118
  "rateLimitBy": "ip",
117
119
  "requestsAllowed": 2,
118
- "timeWindowMinutes": 1
120
+ "timeWindowMinutes": 1,
121
+ "mode": "async"
119
122
  }
120
123
  },
121
124
  {
@@ -126,7 +129,8 @@
126
129
  "identifier": {
127
130
  "export": "$import(./modules/my-module)",
128
131
  "module": "default"
129
- }
132
+ },
133
+ "mode": "async"
130
134
  }
131
135
  }
132
136
  ]
@@ -0,0 +1,67 @@
1
+ On each request this policy selects one of the configured `basePaths` at random,
2
+ weighted by each entry's `weight`. Weights are relative — they do not need to
3
+ add up to 100. The selected URL is written to the request custom context at the
4
+ path given by `customOutputProperty`.
5
+
6
+ ### Using the selected base path
7
+
8
+ The selected URL is stored on `context.custom` and is intended to be consumed by
9
+ a later handler on the same route. Reference it using the `customOutputProperty`
10
+ path you configured. For example, with
11
+ `"customOutputProperty": "trafficSplitting.basePath"`:
12
+
13
+ ```json
14
+ // URL Rewrite handler
15
+ {
16
+ "handler": {
17
+ "export": "urlRewriteHandler",
18
+ "module": "$import(@zuplo/runtime)",
19
+ "options": {
20
+ "rewritePattern": "${context.custom.trafficSplitting.basePath}/users/${params.id}"
21
+ }
22
+ }
23
+ }
24
+ ```
25
+
26
+ ```json
27
+ // URL Forward handler
28
+ {
29
+ "handler": {
30
+ "export": "urlForwardHandler",
31
+ "module": "$import(@zuplo/runtime)",
32
+ "options": {
33
+ "baseUrl": "${context.custom.trafficSplitting.basePath}"
34
+ }
35
+ }
36
+ }
37
+ ```
38
+
39
+ > The `redirect` handler's `location` is not interpolated — use the URL Rewrite
40
+ > or URL Forward handler to route to the selected base path.
41
+
42
+ ### Only one value is in effect
43
+
44
+ `customOutputProperty` resolves to a single value. If more than one Traffic
45
+ Splitting policy on a route writes to the same property, the **last policy to
46
+ run wins** — its selection is the one the handler sees. In practice you should
47
+ configure a single Traffic Splitting policy per output property.
48
+
49
+ ### Environment variables
50
+
51
+ Because each `url` is a string value, you can reference environment variables in
52
+ it, including mixed strings:
53
+
54
+ ```json
55
+ {
56
+ "basePaths": [
57
+ { "url": "$env(STABLE_BASE_URL)", "weight": 90 },
58
+ { "url": "$env(CANARY_BASE_URL)/v2", "weight": 10 }
59
+ ],
60
+ "customOutputProperty": "trafficSplitting.basePath"
61
+ }
62
+ ```
63
+
64
+ ### Logging the selection
65
+
66
+ Set `"logSelection": true` to log which base path was selected on each request.
67
+ This is off by default.
@@ -0,0 +1,5 @@
1
+ The traffic splitting policy randomly distributes incoming requests across a set
2
+ of weighted base paths. It selects one base path per request and writes it to
3
+ the request custom context, where a URL Rewrite or URL Forward handler can use
4
+ it to route the request. This is useful for blue/green rollouts, canary
5
+ releases, or splitting traffic between backends.
@@ -0,0 +1,121 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft-07/schema",
3
+ "$id": "https://cdn.zuplo.com/policies/runtime/schemas/traffic-splitting-inbound.json",
4
+ "type": "object",
5
+ "title": "Traffic Splitting",
6
+ "isDeprecated": false,
7
+ "isPaidAddOn": false,
8
+ "isEnterprise": false,
9
+ "isInternal": false,
10
+ "isBeta": false,
11
+ "isHidden": false,
12
+ "requiresAI": false,
13
+ "products": ["api-gateway"],
14
+ "description": "Splits traffic randomly across a set of weighted base paths. On each request one base path is selected (weighted by `weight`) and written to the request custom context at `customOutputProperty`. Reference it from a later URL Rewrite `rewritePattern` or URL Forward `baseUrl`, e.g. `${context.custom.trafficSplitting.basePath}`.",
15
+ "deprecatedMessage": "",
16
+ "required": ["handler"],
17
+ "properties": {
18
+ "handler": {
19
+ "type": "object",
20
+ "default": {},
21
+ "required": ["export", "module", "options"],
22
+ "properties": {
23
+ "export": {
24
+ "const": "TrafficSplittingInboundPolicy",
25
+ "description": "The name of the exported type"
26
+ },
27
+ "module": {
28
+ "const": "$import(@zuplo/runtime)",
29
+ "description": "The module containing the policy"
30
+ },
31
+ "options": {
32
+ "title": "TrafficSplittingInboundPolicyOptions",
33
+ "type": "object",
34
+ "description": "The options for this policy.",
35
+ "additionalProperties": false,
36
+ "required": ["basePaths", "customOutputProperty"],
37
+ "properties": {
38
+ "basePaths": {
39
+ "type": "array",
40
+ "description": "The set of base paths (URLs) to split traffic across. One entry is selected at random per request, weighted by its `weight`.",
41
+ "items": {
42
+ "type": "object",
43
+ "additionalProperties": false,
44
+ "required": ["url", "weight"],
45
+ "properties": {
46
+ "url": {
47
+ "type": "string",
48
+ "examples": ["https://api-v1.example.com"],
49
+ "description": "The base path (URL) to route to when this entry is selected. Supports environment variables, e.g. `$env(BASE_URL)/v2`."
50
+ },
51
+ "weight": {
52
+ "type": "number",
53
+ "minimum": 0,
54
+ "examples": [80],
55
+ "description": "The relative weight for this base path. Higher weights receive proportionally more traffic. Weights are relative and do not need to add up to 100."
56
+ }
57
+ }
58
+ }
59
+ },
60
+ "customOutputProperty": {
61
+ "type": "string",
62
+ "pattern": "^[A-Za-z_$][A-Za-z0-9_$]*(\\.[A-Za-z_$][A-Za-z0-9_$]*)*$",
63
+ "examples": ["trafficSplitting.basePath"],
64
+ "description": "A simple dotted property path under the request custom context where the selected URL is written (e.g. `trafficSplitting.basePath`). Reference it later in a URL Rewrite `rewritePattern` or URL Forward `baseUrl` as `${context.custom.trafficSplitting.basePath}`. Only one value is in effect; if multiple Traffic Splitting policies write the same property, the last one to run wins. Array indexes and brackets are not allowed."
65
+ },
66
+ "logSelection": {
67
+ "type": "boolean",
68
+ "default": false,
69
+ "x-show-example": false,
70
+ "description": "When `true`, logs which base path was selected for each request. Defaults to `false`."
71
+ }
72
+ },
73
+ "examples": [
74
+ {
75
+ "basePaths": [
76
+ {
77
+ "url": "https://api-v1.example.com",
78
+ "weight": 80
79
+ },
80
+ {
81
+ "url": "https://api-v2.example.com",
82
+ "weight": 15
83
+ },
84
+ {
85
+ "url": "$env(CANARY_BASE_URL)",
86
+ "weight": 5
87
+ }
88
+ ],
89
+ "customOutputProperty": "trafficSplitting.basePath",
90
+ "logSelection": true
91
+ }
92
+ ]
93
+ }
94
+ },
95
+ "examples": [
96
+ {
97
+ "export": "TrafficSplittingInboundPolicy",
98
+ "module": "$import(@zuplo/runtime)",
99
+ "options": {
100
+ "basePaths": [
101
+ {
102
+ "url": "https://api-v1.example.com",
103
+ "weight": 80
104
+ },
105
+ {
106
+ "url": "https://api-v2.example.com",
107
+ "weight": 15
108
+ },
109
+ {
110
+ "url": "$env(CANARY_BASE_URL)",
111
+ "weight": 5
112
+ }
113
+ ],
114
+ "customOutputProperty": "trafficSplitting.basePath",
115
+ "logSelection": true
116
+ }
117
+ }
118
+ ]
119
+ }
120
+ }
121
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zuplo",
3
- "version": "6.71.20",
3
+ "version": "6.71.21",
4
4
  "type": "module",
5
5
  "description": "The programmable API Gateway",
6
6
  "author": "Zuplo, Inc.",
@@ -19,9 +19,9 @@
19
19
  "zuplo": "zuplo.js"
20
20
  },
21
21
  "dependencies": {
22
- "@zuplo/cli": "6.71.20",
23
- "@zuplo/core": "6.71.20",
24
- "@zuplo/runtime": "6.71.20",
22
+ "@zuplo/cli": "6.71.21",
23
+ "@zuplo/core": "6.71.21",
24
+ "@zuplo/runtime": "6.71.21",
25
25
  "@zuplo/test": "1.4.0"
26
26
  }
27
27
  }