@zuplo/cli 6.70.53 → 6.70.55
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/node_modules/@zuplo/core/customer.cli.minified.js +222 -222
- package/node_modules/@zuplo/core/index.minified.js +229 -229
- package/node_modules/@zuplo/core/package.json +1 -1
- package/node_modules/@zuplo/editor/node_modules/lru-cache/dist/commonjs/browser/index.d.ts +2 -2
- package/node_modules/@zuplo/editor/node_modules/lru-cache/dist/commonjs/browser/index.d.ts.map +1 -1
- package/node_modules/@zuplo/editor/node_modules/lru-cache/dist/commonjs/browser/index.js.map +1 -1
- package/node_modules/@zuplo/editor/node_modules/lru-cache/dist/commonjs/browser/index.min.js.map +1 -1
- package/node_modules/@zuplo/editor/node_modules/lru-cache/dist/commonjs/index.d.ts +2 -2
- package/node_modules/@zuplo/editor/node_modules/lru-cache/dist/commonjs/index.d.ts.map +1 -1
- package/node_modules/@zuplo/editor/node_modules/lru-cache/dist/commonjs/index.js.map +1 -1
- package/node_modules/@zuplo/editor/node_modules/lru-cache/dist/commonjs/index.min.js.map +1 -1
- package/node_modules/@zuplo/editor/node_modules/lru-cache/dist/commonjs/node/index.d.ts +2 -2
- package/node_modules/@zuplo/editor/node_modules/lru-cache/dist/commonjs/node/index.d.ts.map +1 -1
- package/node_modules/@zuplo/editor/node_modules/lru-cache/dist/commonjs/node/index.js.map +1 -1
- package/node_modules/@zuplo/editor/node_modules/lru-cache/dist/commonjs/node/index.min.js.map +1 -1
- package/node_modules/@zuplo/editor/node_modules/lru-cache/dist/esm/browser/index.d.ts +2 -2
- package/node_modules/@zuplo/editor/node_modules/lru-cache/dist/esm/browser/index.d.ts.map +1 -1
- package/node_modules/@zuplo/editor/node_modules/lru-cache/dist/esm/browser/index.js.map +1 -1
- package/node_modules/@zuplo/editor/node_modules/lru-cache/dist/esm/browser/index.min.js.map +1 -1
- package/node_modules/@zuplo/editor/node_modules/lru-cache/dist/esm/index.d.ts +2 -2
- package/node_modules/@zuplo/editor/node_modules/lru-cache/dist/esm/index.d.ts.map +1 -1
- package/node_modules/@zuplo/editor/node_modules/lru-cache/dist/esm/index.js.map +1 -1
- package/node_modules/@zuplo/editor/node_modules/lru-cache/dist/esm/index.min.js.map +1 -1
- package/node_modules/@zuplo/editor/node_modules/lru-cache/dist/esm/node/index.d.ts +2 -2
- package/node_modules/@zuplo/editor/node_modules/lru-cache/dist/esm/node/index.d.ts.map +1 -1
- package/node_modules/@zuplo/editor/node_modules/lru-cache/dist/esm/node/index.js.map +1 -1
- package/node_modules/@zuplo/editor/node_modules/lru-cache/dist/esm/node/index.min.js.map +1 -1
- package/node_modules/@zuplo/editor/node_modules/lru-cache/package.json +1 -1
- package/node_modules/@zuplo/graphql/package.json +1 -1
- package/node_modules/@zuplo/openapi-tools/package.json +1 -1
- package/node_modules/@zuplo/otel/package.json +1 -1
- package/node_modules/@zuplo/runtime/out/esm/{chunk-ORBTGJIA.js → chunk-IASCNBZZ.js} +76 -76
- package/node_modules/@zuplo/runtime/out/esm/chunk-IASCNBZZ.js.map +1 -0
- package/node_modules/@zuplo/runtime/out/esm/index.js +1 -1
- package/node_modules/@zuplo/runtime/out/esm/mcp-gateway/index.js +12 -12
- package/node_modules/@zuplo/runtime/out/esm/mcp-gateway/index.js.map +1 -1
- package/node_modules/@zuplo/runtime/out/types/index.d.ts +32 -11
- package/node_modules/@zuplo/runtime/out/types/mcp-gateway/index.d.ts +60 -112
- package/node_modules/@zuplo/runtime/package.json +1 -1
- package/node_modules/eventsource-parser/README.md +31 -0
- package/node_modules/eventsource-parser/dist/index.cjs +21 -10
- package/node_modules/eventsource-parser/dist/index.cjs.map +1 -1
- package/node_modules/eventsource-parser/dist/index.d.cts +33 -10
- package/node_modules/eventsource-parser/dist/index.d.ts +33 -10
- package/node_modules/eventsource-parser/dist/index.js +21 -10
- package/node_modules/eventsource-parser/dist/index.js.map +1 -1
- package/node_modules/eventsource-parser/dist/stream.cjs +4 -3
- package/node_modules/eventsource-parser/dist/stream.cjs.map +1 -1
- package/node_modules/eventsource-parser/dist/stream.d.cts +16 -3
- package/node_modules/eventsource-parser/dist/stream.d.ts +16 -3
- package/node_modules/eventsource-parser/dist/stream.js +4 -3
- package/node_modules/eventsource-parser/dist/stream.js.map +1 -1
- package/node_modules/eventsource-parser/package.json +8 -8
- package/node_modules/eventsource-parser/src/errors.ts +1 -1
- package/node_modules/eventsource-parser/src/index.ts +6 -1
- package/node_modules/eventsource-parser/src/parse.ts +55 -13
- package/node_modules/eventsource-parser/src/stream.ts +24 -5
- package/node_modules/eventsource-parser/src/types.ts +25 -0
- package/node_modules/rimraf/node_modules/lru-cache/dist/commonjs/browser/index.d.ts +2 -2
- package/node_modules/rimraf/node_modules/lru-cache/dist/commonjs/browser/index.d.ts.map +1 -1
- package/node_modules/rimraf/node_modules/lru-cache/dist/commonjs/browser/index.js.map +1 -1
- package/node_modules/rimraf/node_modules/lru-cache/dist/commonjs/browser/index.min.js.map +1 -1
- package/node_modules/rimraf/node_modules/lru-cache/dist/commonjs/index.d.ts +2 -2
- package/node_modules/rimraf/node_modules/lru-cache/dist/commonjs/index.d.ts.map +1 -1
- package/node_modules/rimraf/node_modules/lru-cache/dist/commonjs/index.js.map +1 -1
- package/node_modules/rimraf/node_modules/lru-cache/dist/commonjs/index.min.js.map +1 -1
- package/node_modules/rimraf/node_modules/lru-cache/dist/commonjs/node/index.d.ts +2 -2
- package/node_modules/rimraf/node_modules/lru-cache/dist/commonjs/node/index.d.ts.map +1 -1
- package/node_modules/rimraf/node_modules/lru-cache/dist/commonjs/node/index.js.map +1 -1
- package/node_modules/rimraf/node_modules/lru-cache/dist/commonjs/node/index.min.js.map +1 -1
- package/node_modules/rimraf/node_modules/lru-cache/dist/esm/browser/index.d.ts +2 -2
- package/node_modules/rimraf/node_modules/lru-cache/dist/esm/browser/index.d.ts.map +1 -1
- package/node_modules/rimraf/node_modules/lru-cache/dist/esm/browser/index.js.map +1 -1
- package/node_modules/rimraf/node_modules/lru-cache/dist/esm/browser/index.min.js.map +1 -1
- package/node_modules/rimraf/node_modules/lru-cache/dist/esm/index.d.ts +2 -2
- package/node_modules/rimraf/node_modules/lru-cache/dist/esm/index.d.ts.map +1 -1
- package/node_modules/rimraf/node_modules/lru-cache/dist/esm/index.js.map +1 -1
- package/node_modules/rimraf/node_modules/lru-cache/dist/esm/index.min.js.map +1 -1
- package/node_modules/rimraf/node_modules/lru-cache/dist/esm/node/index.d.ts +2 -2
- package/node_modules/rimraf/node_modules/lru-cache/dist/esm/node/index.d.ts.map +1 -1
- package/node_modules/rimraf/node_modules/lru-cache/dist/esm/node/index.js.map +1 -1
- package/node_modules/rimraf/node_modules/lru-cache/dist/esm/node/index.min.js.map +1 -1
- package/node_modules/rimraf/node_modules/lru-cache/package.json +1 -1
- package/package.json +6 -6
- package/node_modules/@zuplo/runtime/out/esm/browser-login-idp-QZEGTRKY.js +0 -26
- package/node_modules/@zuplo/runtime/out/esm/browser-login-idp-QZEGTRKY.js.map +0 -1
- package/node_modules/@zuplo/runtime/out/esm/chunk-ORBTGJIA.js.map +0 -1
- package/node_modules/@zuplo/runtime/out/esm/chunk-WASXKKBJ.js +0 -26
- package/node_modules/@zuplo/runtime/out/esm/chunk-WASXKKBJ.js.map +0 -1
- /package/node_modules/@zuplo/runtime/out/esm/{chunk-ORBTGJIA.js.LEGAL.txt → chunk-IASCNBZZ.js.LEGAL.txt} +0 -0
|
@@ -250,6 +250,7 @@ export declare interface AIGatewayOpenAIToAnthropicOutboundPolicyOptions {}
|
|
|
250
250
|
* @title AI Gateway Semantic Cache
|
|
251
251
|
* @product ai-gateway
|
|
252
252
|
* @hidden
|
|
253
|
+
* @requiresAI
|
|
253
254
|
* @param request - The ZuploRequest
|
|
254
255
|
* @param context - The ZuploContext
|
|
255
256
|
* @param options - The policy options set in policies.json
|
|
@@ -277,6 +278,7 @@ export declare interface AIGatewaySemanticCacheInboundPolicyOptions {}
|
|
|
277
278
|
* @title AI Gateway Semantic Cache
|
|
278
279
|
* @product ai-gateway
|
|
279
280
|
* @hidden
|
|
281
|
+
* @requiresAI
|
|
280
282
|
* @param response - The response from the upstream service
|
|
281
283
|
* @param request - The original request
|
|
282
284
|
* @param context - The ZuploContext
|
|
@@ -350,6 +352,7 @@ export declare function AIGatewayUsageTrackerPolicy(
|
|
|
350
352
|
* Akamai AI Firewall Inbound Policy
|
|
351
353
|
* @title Akamai AI Firewall
|
|
352
354
|
* @product ai-gateway
|
|
355
|
+
* @requiresAI
|
|
353
356
|
*
|
|
354
357
|
* This policy integrates with Akamai's AI Firewall service to detect
|
|
355
358
|
* and block malicious AI inputs and outputs. It's self-contained and
|
|
@@ -487,6 +490,7 @@ export declare interface AkamaiApiSecurityPluginOptions {
|
|
|
487
490
|
*
|
|
488
491
|
* @title Akamai Firewall for AI
|
|
489
492
|
* @public
|
|
493
|
+
* @requiresAI
|
|
490
494
|
* @param request - The ZuploRequest
|
|
491
495
|
* @param context - The ZuploContext
|
|
492
496
|
* @param options - The policy options set in policies.json
|
|
@@ -535,6 +539,7 @@ export declare interface AkamaiFirewallForAiInboundPolicyOptions {
|
|
|
535
539
|
*
|
|
536
540
|
* @title Akamai Firewall for AI
|
|
537
541
|
* @public
|
|
542
|
+
* @requiresAI
|
|
538
543
|
* @param response - The outgoing Response from the handler
|
|
539
544
|
* @param request - The original incoming Request
|
|
540
545
|
* @param context - The current context of the Request
|
|
@@ -6284,18 +6289,28 @@ declare const mcpOAuthRuntimeConfigSchema: z.ZodObject<
|
|
|
6284
6289
|
},
|
|
6285
6290
|
z.core.$strict
|
|
6286
6291
|
>;
|
|
6287
|
-
gateway: z.
|
|
6288
|
-
z.
|
|
6289
|
-
z.
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
z.core.$strict
|
|
6297
|
-
>
|
|
6292
|
+
gateway: z.ZodPipe<
|
|
6293
|
+
z.ZodDefault<
|
|
6294
|
+
z.ZodObject<
|
|
6295
|
+
{
|
|
6296
|
+
accessTokenTtlSeconds: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
6297
|
+
refreshTokenTtlSeconds: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
6298
|
+
cimdEnabled: z.ZodDefault<z.ZodBoolean>;
|
|
6299
|
+
},
|
|
6300
|
+
z.core.$strict
|
|
6298
6301
|
>
|
|
6302
|
+
>,
|
|
6303
|
+
z.ZodTransform<
|
|
6304
|
+
{
|
|
6305
|
+
accessTokenTtlSeconds: number;
|
|
6306
|
+
refreshTokenTtlSeconds: number;
|
|
6307
|
+
downstreamCimdEnabled: boolean;
|
|
6308
|
+
},
|
|
6309
|
+
{
|
|
6310
|
+
accessTokenTtlSeconds: number;
|
|
6311
|
+
refreshTokenTtlSeconds: number;
|
|
6312
|
+
cimdEnabled: boolean;
|
|
6313
|
+
}
|
|
6299
6314
|
>
|
|
6300
6315
|
>;
|
|
6301
6316
|
},
|
|
@@ -6968,6 +6983,10 @@ export declare interface MonetizationInboundPolicyOptions {
|
|
|
6968
6983
|
* A list of successful status codes and ranges "200-299, 304" that should trigger a metering call.
|
|
6969
6984
|
*/
|
|
6970
6985
|
meterOnStatusCodes?: string | number[];
|
|
6986
|
+
/**
|
|
6987
|
+
* A list of entitlement keys that the subscription must have access to (hasAccess=true) for the request to be allowed. If any required entitlement is missing or does not have access, the request will be rejected with a 403 Forbidden.
|
|
6988
|
+
*/
|
|
6989
|
+
requiredEntitlements?: string[];
|
|
6971
6990
|
}
|
|
6972
6991
|
|
|
6973
6992
|
/**
|
|
@@ -8532,6 +8551,7 @@ export declare class ProblemResponseFormatter {
|
|
|
8532
8551
|
* @product mcp-gateway
|
|
8533
8552
|
* @public
|
|
8534
8553
|
* @enterprise
|
|
8554
|
+
* @requiresAI
|
|
8535
8555
|
* @param request - The ZuploRequest
|
|
8536
8556
|
* @param context - The ZuploContext
|
|
8537
8557
|
* @param options - The policy options set in policies.json
|
|
@@ -9661,6 +9681,7 @@ export declare interface SecretMaskingOutboundPolicyOptions {
|
|
|
9661
9681
|
* @product ai-gateway
|
|
9662
9682
|
* @beta
|
|
9663
9683
|
* @enterprise
|
|
9684
|
+
* @requiresAI
|
|
9664
9685
|
* @param request - The ZuploRequest
|
|
9665
9686
|
* @param context - The ZuploContext
|
|
9666
9687
|
* @param options - The policy options set in policies.json
|
|
@@ -1734,18 +1734,28 @@ declare const mcpOAuthRuntimeConfigSchema: z.ZodObject<
|
|
|
1734
1734
|
},
|
|
1735
1735
|
z.core.$strict
|
|
1736
1736
|
>;
|
|
1737
|
-
gateway: z.
|
|
1738
|
-
z.
|
|
1739
|
-
z.
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
z.core.$strict
|
|
1747
|
-
>
|
|
1737
|
+
gateway: z.ZodPipe<
|
|
1738
|
+
z.ZodDefault<
|
|
1739
|
+
z.ZodObject<
|
|
1740
|
+
{
|
|
1741
|
+
accessTokenTtlSeconds: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
1742
|
+
refreshTokenTtlSeconds: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
1743
|
+
cimdEnabled: z.ZodDefault<z.ZodBoolean>;
|
|
1744
|
+
},
|
|
1745
|
+
z.core.$strict
|
|
1748
1746
|
>
|
|
1747
|
+
>,
|
|
1748
|
+
z.ZodTransform<
|
|
1749
|
+
{
|
|
1750
|
+
accessTokenTtlSeconds: number;
|
|
1751
|
+
refreshTokenTtlSeconds: number;
|
|
1752
|
+
downstreamCimdEnabled: boolean;
|
|
1753
|
+
},
|
|
1754
|
+
{
|
|
1755
|
+
accessTokenTtlSeconds: number;
|
|
1756
|
+
refreshTokenTtlSeconds: number;
|
|
1757
|
+
cimdEnabled: boolean;
|
|
1758
|
+
}
|
|
1749
1759
|
>
|
|
1750
1760
|
>;
|
|
1751
1761
|
},
|
|
@@ -2038,22 +2048,7 @@ export declare interface McpTokenExchangeInboundPolicyOptions {
|
|
|
2038
2048
|
*/
|
|
2039
2049
|
scopeDelimiter?: string;
|
|
2040
2050
|
/**
|
|
2041
|
-
* OAuth client
|
|
2042
|
-
*/
|
|
2043
|
-
clientId?: string;
|
|
2044
|
-
/**
|
|
2045
|
-
* OAuth client secret (for OAuth modes with manual registration). Use `$env(VAR_NAME)` to source from an environment variable.
|
|
2046
|
-
*/
|
|
2047
|
-
clientSecret?: string;
|
|
2048
|
-
/**
|
|
2049
|
-
* Token endpoint authentication method (for OAuth modes with manual registration).
|
|
2050
|
-
*/
|
|
2051
|
-
tokenEndpointAuthMethod?:
|
|
2052
|
-
| "client_secret_basic"
|
|
2053
|
-
| "client_secret_post"
|
|
2054
|
-
| "none";
|
|
2055
|
-
/**
|
|
2056
|
-
* OAuth client registration mode. Defaults to `auto` (Dynamic Client Registration).
|
|
2051
|
+
* OAuth client registration mode. Defaults to `auto`, which uses Client ID Metadata Documents when the upstream advertises support and falls back to Dynamic Client Registration otherwise.
|
|
2057
2052
|
*/
|
|
2058
2053
|
clientRegistration?:
|
|
2059
2054
|
| {
|
|
@@ -3188,91 +3183,44 @@ declare const upstreamTokenExchangePolicyOptionsSchema: z.ZodObject<
|
|
|
3188
3183
|
"user-oauth": "user-oauth";
|
|
3189
3184
|
"shared-oauth": "shared-oauth";
|
|
3190
3185
|
}>;
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
z.core.$strict
|
|
3230
|
-
>;
|
|
3231
|
-
},
|
|
3232
|
-
z.core.$strict
|
|
3233
|
-
>,
|
|
3234
|
-
z.ZodObject<
|
|
3235
|
-
{
|
|
3236
|
-
mode: z.ZodLiteral<"user-oauth">;
|
|
3237
|
-
oauth: z.ZodObject<
|
|
3238
|
-
{
|
|
3239
|
-
scopes: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
3240
|
-
scopeDelimiter: z.ZodDefault<z.ZodString>;
|
|
3241
|
-
clientRegistration: z.ZodDefault<
|
|
3242
|
-
z.ZodDiscriminatedUnion<
|
|
3243
|
-
[
|
|
3244
|
-
z.ZodObject<
|
|
3245
|
-
{
|
|
3246
|
-
mode: z.ZodLiteral<"auto">;
|
|
3247
|
-
},
|
|
3248
|
-
z.core.$strict
|
|
3249
|
-
>,
|
|
3250
|
-
z.ZodObject<
|
|
3251
|
-
{
|
|
3252
|
-
mode: z.ZodLiteral<"manual">;
|
|
3253
|
-
clientId: z.ZodString;
|
|
3254
|
-
clientSecret: z.ZodOptional<z.ZodString>;
|
|
3255
|
-
tokenEndpointAuthMethod: z.ZodDefault<
|
|
3256
|
-
z.ZodEnum<{
|
|
3257
|
-
none: "none";
|
|
3258
|
-
client_secret_basic: "client_secret_basic";
|
|
3259
|
-
client_secret_post: "client_secret_post";
|
|
3260
|
-
}>
|
|
3261
|
-
>;
|
|
3262
|
-
},
|
|
3263
|
-
z.core.$strict
|
|
3264
|
-
>,
|
|
3265
|
-
]
|
|
3266
|
-
>
|
|
3267
|
-
>;
|
|
3268
|
-
redirectPath: z.ZodString;
|
|
3269
|
-
},
|
|
3270
|
-
z.core.$strict
|
|
3271
|
-
>;
|
|
3272
|
-
},
|
|
3273
|
-
z.core.$strict
|
|
3274
|
-
>,
|
|
3275
|
-
]
|
|
3186
|
+
ownerMode: z.ZodEnum<{
|
|
3187
|
+
user: "user";
|
|
3188
|
+
shared: "shared";
|
|
3189
|
+
}>;
|
|
3190
|
+
authConfig: z.ZodObject<
|
|
3191
|
+
{
|
|
3192
|
+
scopes: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
3193
|
+
scopeDelimiter: z.ZodDefault<z.ZodString>;
|
|
3194
|
+
clientRegistration: z.ZodDefault<
|
|
3195
|
+
z.ZodDiscriminatedUnion<
|
|
3196
|
+
[
|
|
3197
|
+
z.ZodObject<
|
|
3198
|
+
{
|
|
3199
|
+
mode: z.ZodLiteral<"auto">;
|
|
3200
|
+
},
|
|
3201
|
+
z.core.$strict
|
|
3202
|
+
>,
|
|
3203
|
+
z.ZodObject<
|
|
3204
|
+
{
|
|
3205
|
+
mode: z.ZodLiteral<"manual">;
|
|
3206
|
+
clientId: z.ZodString;
|
|
3207
|
+
clientSecret: z.ZodOptional<z.ZodString>;
|
|
3208
|
+
tokenEndpointAuthMethod: z.ZodDefault<
|
|
3209
|
+
z.ZodEnum<{
|
|
3210
|
+
none: "none";
|
|
3211
|
+
client_secret_basic: "client_secret_basic";
|
|
3212
|
+
client_secret_post: "client_secret_post";
|
|
3213
|
+
}>
|
|
3214
|
+
>;
|
|
3215
|
+
},
|
|
3216
|
+
z.core.$strict
|
|
3217
|
+
>,
|
|
3218
|
+
]
|
|
3219
|
+
>
|
|
3220
|
+
>;
|
|
3221
|
+
redirectPath: z.ZodString;
|
|
3222
|
+
},
|
|
3223
|
+
z.core.$strict
|
|
3276
3224
|
>;
|
|
3277
3225
|
},
|
|
3278
3226
|
z.core.$strict
|
|
@@ -109,6 +109,28 @@ const parser = createParser({
|
|
|
109
109
|
> [!NOTE]
|
|
110
110
|
> Leading whitespace is not stripped from comments, eg `: comment` will give ` comment` as the comment value, not `comment` (note the leading space).
|
|
111
111
|
|
|
112
|
+
### Limiting buffered memory (`maxBufferSize`)
|
|
113
|
+
|
|
114
|
+
By default the parser buffers data indefinitely until a server completes an event. A server (or proxy) that never terminates a line, or that keeps appending `data:` lines without ever sending a blank line to dispatch the event, can therefore grow the parser's buffers without bound.
|
|
115
|
+
|
|
116
|
+
Pass a `maxBufferSize` (in characters) to `createParser` to cap this. If the combined size of the pending line buffer and the in-progress event's data buffer exceeds the limit, the parser emits a `ParseError` with `type: 'max-buffer-size-exceeded'` and becomes terminated: subsequent calls to `feed()` will throw until `reset()` is called.
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
const parser = createParser({
|
|
120
|
+
maxBufferSize: 1024 * 1024, // 1 MB
|
|
121
|
+
onEvent(event) {
|
|
122
|
+
// …
|
|
123
|
+
},
|
|
124
|
+
onError(error) {
|
|
125
|
+
if (error.type === 'max-buffer-size-exceeded') {
|
|
126
|
+
// Stream peer is misbehaving — typically you'd close the connection.
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
})
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The same option is available on the [stream variant](#stream-usage); the stream is always errored when this limit is exceeded, regardless of the `onError` setting (since the underlying parser is unrecoverable without a `reset()`).
|
|
133
|
+
|
|
112
134
|
## Stream usage
|
|
113
135
|
|
|
114
136
|
```ts
|
|
@@ -119,6 +141,15 @@ const eventStream = response.body
|
|
|
119
141
|
.pipeThrough(new EventSourceParserStream())
|
|
120
142
|
```
|
|
121
143
|
|
|
144
|
+
The stream constructor accepts a subset of the `createParser` options (`onComment`, `onRetry`, `maxBufferSize`) plus an `onError` that can either be a function or set to `'terminate'` to error the stream on parse errors. Events are delivered through the stream itself rather than via an `onEvent` callback:
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
new EventSourceParserStream({
|
|
148
|
+
maxBufferSize: 1024 * 1024,
|
|
149
|
+
onError: 'terminate',
|
|
150
|
+
})
|
|
151
|
+
```
|
|
152
|
+
|
|
122
153
|
Note that the TransformStream is exposed under a separate export (`eventsource-parser/stream`), in order to maximize compatibility with environments that do not have the `TransformStream` constructor available.
|
|
123
154
|
|
|
124
155
|
## License
|
|
@@ -8,29 +8,40 @@ class ParseError extends Error {
|
|
|
8
8
|
const LF = 10, CR = 13, SPACE = 32;
|
|
9
9
|
function noop(_arg) {
|
|
10
10
|
}
|
|
11
|
-
function createParser(
|
|
12
|
-
if (typeof
|
|
11
|
+
function createParser(config) {
|
|
12
|
+
if (typeof config == "function")
|
|
13
13
|
throw new TypeError(
|
|
14
|
-
"`
|
|
14
|
+
"`config` must be an object, got a function instead. Did you mean `createParser({onEvent: fn})`?"
|
|
15
15
|
);
|
|
16
|
-
const { onEvent = noop, onError = noop, onRetry = noop, onComment } =
|
|
17
|
-
let isFirstChunk = !0, id, data = "", dataLines = 0, eventType;
|
|
16
|
+
const { onEvent = noop, onError = noop, onRetry = noop, onComment, maxBufferSize } = config, pendingFragments = [];
|
|
17
|
+
let pendingFragmentsLength = 0, isFirstChunk = !0, id, data = "", dataLines = 0, eventType, terminated = !1;
|
|
18
18
|
function feed(chunk) {
|
|
19
|
+
if (terminated)
|
|
20
|
+
throw new Error(
|
|
21
|
+
"Cannot feed parser: it was terminated after exceeding the configured max buffer size. Call `reset()` to resume parsing."
|
|
22
|
+
);
|
|
19
23
|
if (isFirstChunk && (isFirstChunk = !1, chunk.charCodeAt(0) === 239 && chunk.charCodeAt(1) === 187 && chunk.charCodeAt(2) === 191 && (chunk = chunk.slice(3))), pendingFragments.length === 0) {
|
|
20
24
|
const trailing2 = processLines(chunk);
|
|
21
|
-
trailing2 !== "" && pendingFragments.push(trailing2);
|
|
25
|
+
trailing2 !== "" && (pendingFragments.push(trailing2), pendingFragmentsLength = trailing2.length), checkBufferSize();
|
|
22
26
|
return;
|
|
23
27
|
}
|
|
24
28
|
if (chunk.indexOf(`
|
|
25
29
|
`) === -1 && chunk.indexOf("\r") === -1) {
|
|
26
|
-
pendingFragments.push(chunk);
|
|
30
|
+
pendingFragments.push(chunk), pendingFragmentsLength += chunk.length, checkBufferSize();
|
|
27
31
|
return;
|
|
28
32
|
}
|
|
29
33
|
pendingFragments.push(chunk);
|
|
30
34
|
const input = pendingFragments.join("");
|
|
31
|
-
pendingFragments.length = 0;
|
|
35
|
+
pendingFragments.length = 0, pendingFragmentsLength = 0;
|
|
32
36
|
const trailing = processLines(input);
|
|
33
|
-
trailing !== "" && pendingFragments.push(trailing);
|
|
37
|
+
trailing !== "" && (pendingFragments.push(trailing), pendingFragmentsLength = trailing.length), checkBufferSize();
|
|
38
|
+
}
|
|
39
|
+
function checkBufferSize() {
|
|
40
|
+
maxBufferSize !== void 0 && (pendingFragmentsLength + data.length <= maxBufferSize || (terminated = !0, pendingFragments.length = 0, pendingFragmentsLength = 0, id = void 0, data = "", dataLines = 0, eventType = void 0, onError(
|
|
41
|
+
new ParseError(`Buffered data exceeded max buffer size of ${maxBufferSize} characters`, {
|
|
42
|
+
type: "max-buffer-size-exceeded"
|
|
43
|
+
})
|
|
44
|
+
)));
|
|
34
45
|
}
|
|
35
46
|
function processLines(chunk) {
|
|
36
47
|
let searchIndex = 0;
|
|
@@ -151,7 +162,7 @@ ${value}`, dataLines++;
|
|
|
151
162
|
const incompleteLine = pendingFragments.join("");
|
|
152
163
|
parseLine(incompleteLine, 0, incompleteLine.length);
|
|
153
164
|
}
|
|
154
|
-
isFirstChunk = !0, id = void 0, data = "", dataLines = 0, eventType = void 0, pendingFragments.length = 0;
|
|
165
|
+
isFirstChunk = !0, id = void 0, data = "", dataLines = 0, eventType = void 0, pendingFragments.length = 0, pendingFragmentsLength = 0, terminated = !1;
|
|
155
166
|
}
|
|
156
167
|
return { feed, reset };
|
|
157
168
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../src/errors.ts","../src/parse.ts"],"sourcesContent":["/**\n * The type of error that occurred.\n * @public\n */\nexport type ErrorType = 'invalid-retry' | 'unknown-field'\n\n/**\n * Error thrown when encountering an issue during parsing.\n *\n * @public\n */\nexport class ParseError extends Error {\n /**\n * The type of error that occurred.\n */\n type: ErrorType\n\n /**\n * In the case of an unknown field encountered in the stream, this will be the field name.\n */\n field?: string | undefined\n\n /**\n * In the case of an unknown field encountered in the stream, this will be the value of the field.\n */\n value?: string | undefined\n\n /**\n * The line that caused the error, if available.\n */\n line?: string | undefined\n\n constructor(\n message: string,\n options: {type: ErrorType; field?: string; value?: string; line?: string},\n ) {\n super(message)\n this.name = 'ParseError'\n this.type = options.type\n this.field = options.field\n this.value = options.value\n this.line = options.line\n }\n}\n","/**\n * EventSource/Server-Sent Events parser\n * @see https://html.spec.whatwg.org/multipage/server-sent-events.html\n */\nimport {ParseError} from './errors.ts'\nimport type {EventSourceParser, ParserCallbacks} from './types.ts'\n\n// ASCII codes used in the hot parsing paths.\nconst LF = 10\nconst CR = 13\nconst SPACE = 32\n\n// oxlint-disable-next-line no-unused-vars\nfunction noop(_arg: unknown) {\n // intentional noop\n}\n\n/**\n * Creates a new EventSource parser.\n *\n * @param callbacks - Callbacks to invoke on different parsing events:\n * - `onEvent` when a new event is parsed\n * - `onError` when an error occurs\n * - `onRetry` when a new reconnection interval has been sent from the server\n * - `onComment` when a comment is encountered in the stream\n *\n * @returns A new EventSource parser, with `parse` and `reset` methods.\n * @public\n */\nexport function createParser(callbacks: ParserCallbacks): EventSourceParser {\n if (typeof callbacks === 'function') {\n throw new TypeError(\n '`callbacks` must be an object, got a function instead. Did you mean `{onEvent: fn}`?',\n )\n }\n\n const {onEvent = noop, onError = noop, onRetry = noop, onComment} = callbacks\n\n // Trailing bytes from prior `feed()` calls that did not yet form a complete line.\n // Stored as an array of fragments and only joined when a line terminator arrives.\n // Concatenating per-feed (`prefix + chunk`) is O(N²) when a single SSE line spans\n // many chunks (e.g. a large `data:` payload streamed in tiny slices, or an MCP-style\n // server that emits one giant content block). Buffering as fragments + joining once\n // makes the same workload linear.\n const pendingFragments: string[] = []\n\n let isFirstChunk = true\n let id: string | undefined\n let data = ''\n let dataLines = 0\n let eventType: string | undefined\n\n /**\n * Feeds a chunk of the SSE stream to the parser. Any trailing bytes that do\n * not yet form a complete line are held back and prepended to the next chunk,\n * so callers can pass arbitrary slices of the stream without worrying about\n * line boundaries.\n *\n * Per the SSE spec, a UTF-8 BOM (0xEF 0xBB 0xBF) at the start of the very\n * first chunk is stripped before parsing.\n *\n * @see https://html.spec.whatwg.org/multipage/server-sent-events.html#parsing-an-event-stream\n */\n function feed(chunk: string) {\n if (isFirstChunk) {\n isFirstChunk = false\n // Match and strip UTF-8 BOM from the start of the stream, if present.\n // (Per the spec, this is only valid at the very start of the stream)\n if (\n chunk.charCodeAt(0) === 0xef &&\n chunk.charCodeAt(1) === 0xbb &&\n chunk.charCodeAt(2) === 0xbf\n ) {\n chunk = chunk.slice(3)\n }\n }\n\n // Hot path: no buffered prefix from a prior partial line. Hand the chunk\n // straight to `processLines`, exactly like the original implementation.\n // Zero new work in the common case (every chunk ends with `\\n\\n`).\n if (pendingFragments.length === 0) {\n const trailing = processLines(chunk)\n if (trailing !== '') pendingFragments.push(trailing)\n return\n }\n\n // We have a buffered prefix. If this chunk also has no terminator, append\n // to the buffer without concatenating — that's the O(N²) trap we're\n // avoiding (large single `data:` payload split across many tiny chunks).\n if (chunk.indexOf('\\n') === -1 && chunk.indexOf('\\r') === -1) {\n pendingFragments.push(chunk)\n return\n }\n\n // Terminator arrived. Join the accumulated fragments + this chunk once,\n // process, and buffer any new trailing partial line.\n pendingFragments.push(chunk)\n const input = pendingFragments.join('')\n pendingFragments.length = 0\n const trailing = processLines(input)\n if (trailing !== '') pendingFragments.push(trailing)\n }\n\n /**\n * Splits `chunk` into SSE lines and dispatches each to the appropriate handler.\n * Returns any trailing bytes that did not terminate with a line break, so the\n * caller can prepend them to the next chunk.\n *\n * The SSE spec permits three line terminators: `\\n`, `\\r`, and `\\r\\n`. Real-world\n * streams almost always use plain `\\n`, so we take a fast path when no `\\r` is\n * present in the chunk. The slow path is spec-correct but does more work per line.\n */\n function processLines(chunk: string): string {\n let searchIndex = 0\n\n // Fast path: LF-only chunk (the common case for typical SSE servers).\n // We can scan forward with a single `indexOf('\\n')` per line and inline\n // the hot-path branches for `data:` and `event:` without the CR bookkeeping\n // the slow path needs.\n if (chunk.indexOf('\\r') === -1) {\n let lfIndex = chunk.indexOf('\\n', searchIndex)\n while (lfIndex !== -1) {\n // Blank line: end-of-event marker. Dispatch the accumulated event (if any)\n // and reset the buffered fields. This is hoisted out of `parseLine` because\n // it's the single most common line shape after `data:` lines.\n if (searchIndex === lfIndex) {\n if (dataLines > 0) {\n onEvent({id, event: eventType, data})\n }\n id = undefined\n data = ''\n dataLines = 0\n eventType = undefined\n searchIndex = lfIndex + 1\n lfIndex = chunk.indexOf('\\n', searchIndex)\n continue\n }\n const firstCharCode = chunk.charCodeAt(searchIndex)\n if (isDataPrefix(chunk, searchIndex, firstCharCode)) {\n // `data:` line — append the value to the event's data buffer.\n // 'data:'.length === 5, 'data: '.length === 6\n const valueStart =\n chunk.charCodeAt(searchIndex + 5) === SPACE ? searchIndex + 6 : searchIndex + 5\n const value = chunk.slice(valueStart, lfIndex)\n // Fast path within a fast path: if this is the first data line AND the\n // next char is another LF (i.e. `data:foo\\n\\n`), dispatch immediately\n // without ever writing to the `data` buffer. This is the shape of a\n // typical single-line SSE event (ChatGPT-style streams, etc.) and is\n // hot enough to be worth the duplication.\n if (dataLines === 0 && chunk.charCodeAt(lfIndex + 1) === LF) {\n onEvent({id, event: eventType, data: value})\n id = undefined\n data = ''\n eventType = undefined\n searchIndex = lfIndex + 2\n lfIndex = chunk.indexOf('\\n', searchIndex)\n continue\n }\n // Multi-line data: concatenate with newline separator per spec.\n data = dataLines === 0 ? value : `${data}\\n${value}`\n dataLines++\n } else if (isEventPrefix(chunk, searchIndex, firstCharCode)) {\n // `event:` line — set the event type for the next dispatch. Per spec,\n // an empty value resets `event type` to its default (undefined here).\n // 'event:'.length === 6, 'event: '.length === 7\n eventType =\n chunk.slice(\n chunk.charCodeAt(searchIndex + 6) === SPACE ? searchIndex + 7 : searchIndex + 6,\n lfIndex,\n ) || undefined\n } else {\n // Everything else: `id:`, `retry:`, comment lines (`:` prefix), unknown\n // fields, or malformed lines. These are rarer and go through the full\n // per-line parser, which handles the SSE field grammar in detail.\n parseLine(chunk, searchIndex, lfIndex)\n }\n searchIndex = lfIndex + 1\n lfIndex = chunk.indexOf('\\n', searchIndex)\n }\n return chunk.slice(searchIndex)\n }\n\n // Slow path: the chunk contains at least one `\\r`, so lines may be terminated\n // by `\\r`, `\\n`, or `\\r\\n`. We locate the next terminator by looking at both\n // the nearest `\\r` and `\\n` and picking whichever comes first.\n while (searchIndex < chunk.length) {\n const crIndex = chunk.indexOf('\\r', searchIndex)\n const lfIndex = chunk.indexOf('\\n', searchIndex)\n\n let lineEnd = -1\n if (crIndex !== -1 && lfIndex !== -1) {\n lineEnd = crIndex < lfIndex ? crIndex : lfIndex\n } else if (crIndex !== -1) {\n // A trailing `\\r` at the very end of the chunk is ambiguous: it could be\n // a bare-CR terminator, or the first half of a `\\r\\n` whose `\\n` arrives\n // in the next chunk. Defer until we see more input.\n if (crIndex === chunk.length - 1) {\n lineEnd = -1\n } else {\n lineEnd = crIndex\n }\n } else if (lfIndex !== -1) {\n lineEnd = lfIndex\n }\n\n if (lineEnd === -1) {\n break\n }\n\n parseLine(chunk, searchIndex, lineEnd)\n searchIndex = lineEnd + 1\n // If we just consumed a `\\r` and the next char is `\\n`, skip it so the\n // pair is treated as a single terminator rather than an empty line.\n if (chunk.charCodeAt(searchIndex - 1) === CR && chunk.charCodeAt(searchIndex) === LF) {\n searchIndex++\n }\n }\n\n return chunk.slice(searchIndex)\n }\n\n function parseLine(chunk: string, start: number, end: number) {\n if (start === end) {\n dispatchEvent()\n return\n }\n\n const firstCharCode = chunk.charCodeAt(start)\n\n if (isDataPrefix(chunk, start, firstCharCode)) {\n // 'data:'.length === 5, 'data: '.length === 6\n const valueStart = chunk.charCodeAt(start + 5) === SPACE ? start + 6 : start + 5\n const value = chunk.slice(valueStart, end)\n data = dataLines === 0 ? value : `${data}\\n${value}`\n dataLines++\n return\n }\n\n if (isEventPrefix(chunk, start, firstCharCode)) {\n // 'event:'.length === 6, 'event: '.length === 7\n eventType =\n chunk.slice(chunk.charCodeAt(start + 6) === SPACE ? start + 7 : start + 6, end) || undefined\n return\n }\n\n // Fast path for \"id:\" — 'i' = 105, 'd' = 100, ':' = 58\n if (\n firstCharCode === 105 &&\n chunk.charCodeAt(start + 1) === 100 &&\n chunk.charCodeAt(start + 2) === 58\n ) {\n // 'id:'.length === 3, 'id: '.length === 4\n const value = chunk.slice(chunk.charCodeAt(start + 3) === SPACE ? start + 4 : start + 3, end)\n id = value.includes('\\0') ? undefined : value\n return\n }\n\n // Comment line — ':' = 58\n if (firstCharCode === 58) {\n if (onComment) {\n const line = chunk.slice(start, end)\n // skip ':' (+1), or ': ' (+2) when a space follows\n onComment(line.slice(chunk.charCodeAt(start + 1) === SPACE ? 2 : 1))\n }\n return\n }\n\n const line = chunk.slice(start, end)\n const fieldSeparatorIndex = line.indexOf(':')\n if (fieldSeparatorIndex === -1) {\n processField(line, '', line)\n return\n }\n\n const field = line.slice(0, fieldSeparatorIndex)\n // skip ':' (+1), or ': ' (+2) when a space follows\n const offset = line.charCodeAt(fieldSeparatorIndex + 1) === SPACE ? 2 : 1\n const value = line.slice(fieldSeparatorIndex + offset)\n processField(field, value, line)\n }\n\n function processField(field: string, value: string, line: string) {\n // Field names must be compared literally, with no case folding performed.\n switch (field) {\n case 'event':\n // Set the `event type` buffer to field value\n eventType = value || undefined\n break\n case 'data':\n data = dataLines === 0 ? value : `${data}\\n${value}`\n dataLines++\n break\n case 'id':\n // If the field value does not contain U+0000 NULL, then set the `ID` buffer to\n // the field value. Otherwise, ignore the field.\n id = value.includes('\\0') ? undefined : value\n break\n case 'retry':\n // If the field value consists of only ASCII digits, then interpret the field value as an\n // integer in base ten, and set the event stream's reconnection time to that integer.\n // Otherwise, ignore the field.\n if (/^\\d+$/.test(value)) {\n onRetry(parseInt(value, 10))\n } else {\n onError(\n new ParseError(`Invalid \\`retry\\` value: \"${value}\"`, {\n type: 'invalid-retry',\n value,\n line,\n }),\n )\n }\n break\n default:\n // Otherwise, the field is ignored.\n onError(\n new ParseError(\n `Unknown field \"${field.length > 20 ? `${field.slice(0, 20)}…` : field}\"`,\n {type: 'unknown-field', field, value, line},\n ),\n )\n break\n }\n }\n\n function dispatchEvent() {\n if (dataLines > 0) {\n onEvent({\n id,\n event: eventType,\n data,\n })\n }\n\n id = undefined\n data = ''\n dataLines = 0\n eventType = undefined\n }\n\n function reset(options: {consume?: boolean} = {}) {\n if (options.consume && pendingFragments.length > 0) {\n const incompleteLine = pendingFragments.join('')\n parseLine(incompleteLine, 0, incompleteLine.length)\n }\n\n isFirstChunk = true\n id = undefined\n data = ''\n dataLines = 0\n eventType = undefined\n pendingFragments.length = 0\n }\n\n return {feed, reset}\n}\n\n/**\n * Checks if `chunk` starts with the literal `data:` at index `i`.\n *\n * Equivalent to `chunk.startsWith('data:', i)`, but benchmarks show this\n * hand-unrolled char-code comparison is ~20% faster on common event types.\n * The caller passes `firstCharCode` (the code at `i`) so it can be reused\n * across prefix checks.\n *\n * ASCII: 'd' = 100, 'a' = 97, 't' = 116, 'a' = 97, ':' = 58\n */\nfunction isDataPrefix(chunk: string, i: number, firstCharCode: number): boolean {\n return (\n firstCharCode === 100 &&\n chunk.charCodeAt(i + 1) === 97 &&\n chunk.charCodeAt(i + 2) === 116 &&\n chunk.charCodeAt(i + 3) === 97 &&\n chunk.charCodeAt(i + 4) === 58\n )\n}\n\n/**\n * Checks if `chunk` starts with the literal `event:` at index `i`.\n *\n * See {@link isDataPrefix} for why this is hand-unrolled rather than using\n * `String.prototype.startsWith`.\n *\n * ASCII: 'e' = 101, 'v' = 118, 'e' = 101, 'n' = 110, 't' = 116, ':' = 58\n */\nfunction isEventPrefix(chunk: string, i: number, firstCharCode: number): boolean {\n return (\n firstCharCode === 101 &&\n chunk.charCodeAt(i + 1) === 118 &&\n chunk.charCodeAt(i + 2) === 101 &&\n chunk.charCodeAt(i + 3) === 110 &&\n chunk.charCodeAt(i + 4) === 116 &&\n chunk.charCodeAt(i + 5) === 58\n )\n}\n"],"names":["trailing","value","line"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/errors.ts","../src/parse.ts"],"sourcesContent":["/**\n * The type of error that occurred.\n * @public\n */\nexport type ErrorType = 'invalid-retry' | 'unknown-field' | 'max-buffer-size-exceeded'\n\n/**\n * Error thrown when encountering an issue during parsing.\n *\n * @public\n */\nexport class ParseError extends Error {\n /**\n * The type of error that occurred.\n */\n type: ErrorType\n\n /**\n * In the case of an unknown field encountered in the stream, this will be the field name.\n */\n field?: string | undefined\n\n /**\n * In the case of an unknown field encountered in the stream, this will be the value of the field.\n */\n value?: string | undefined\n\n /**\n * The line that caused the error, if available.\n */\n line?: string | undefined\n\n constructor(\n message: string,\n options: {type: ErrorType; field?: string; value?: string; line?: string},\n ) {\n super(message)\n this.name = 'ParseError'\n this.type = options.type\n this.field = options.field\n this.value = options.value\n this.line = options.line\n }\n}\n","/**\n * EventSource/Server-Sent Events parser\n * @see https://html.spec.whatwg.org/multipage/server-sent-events.html\n */\nimport {ParseError} from './errors.ts'\nimport type {EventSourceParser, ParserConfig} from './types.ts'\n\n// ASCII codes used in the hot parsing paths.\nconst LF = 10\nconst CR = 13\nconst SPACE = 32\n\n// oxlint-disable-next-line no-unused-vars\nfunction noop(_arg: unknown) {\n // intentional noop\n}\n\n/**\n * Creates a new EventSource parser.\n *\n * @param config - Parser configuration. Accepts callbacks (see {@link ParserCallbacks})\n * and options like `maxBufferSize` (see {@link ParserConfig}).\n *\n * @returns A new EventSource parser, with `feed` and `reset` methods.\n * @public\n */\nexport function createParser(config: ParserConfig): EventSourceParser {\n if (typeof config === 'function') {\n throw new TypeError(\n '`config` must be an object, got a function instead. Did you mean `createParser({onEvent: fn})`?',\n )\n }\n\n const {onEvent = noop, onError = noop, onRetry = noop, onComment, maxBufferSize} = config\n\n // Trailing bytes from prior `feed()` calls that did not yet form a complete line.\n // Stored as an array of fragments and only joined when a line terminator arrives.\n // Concatenating per-feed (`prefix + chunk`) is O(N²) when a single SSE line spans\n // many chunks (e.g. a large `data:` payload streamed in tiny slices, or an MCP-style\n // server that emits one giant content block). Buffering as fragments + joining once\n // makes the same workload linear.\n const pendingFragments: string[] = []\n\n // Running total of `pendingFragments` lengths, kept in sync with the array so the\n // `maxBufferSize` check doesn't have to walk the fragment list on every feed.\n let pendingFragmentsLength = 0\n\n let isFirstChunk = true\n let id: string | undefined\n let data = ''\n let dataLines = 0\n let eventType: string | undefined\n\n // Set after a `maxBufferSize` overflow. Once tripped, `feed()` throws until\n // `reset()` is called — see the comment on `maxBufferSize` in `ParserConfig`.\n let terminated = false\n\n /**\n * Feeds a chunk of the SSE stream to the parser. Any trailing bytes that do\n * not yet form a complete line are held back and prepended to the next chunk,\n * so callers can pass arbitrary slices of the stream without worrying about\n * line boundaries.\n *\n * Per the SSE spec, a UTF-8 BOM (0xEF 0xBB 0xBF) at the start of the very\n * first chunk is stripped before parsing.\n *\n * @see https://html.spec.whatwg.org/multipage/server-sent-events.html#parsing-an-event-stream\n */\n function feed(chunk: string) {\n if (terminated) {\n throw new Error(\n 'Cannot feed parser: it was terminated after exceeding the configured max buffer size. Call `reset()` to resume parsing.',\n )\n }\n\n if (isFirstChunk) {\n isFirstChunk = false\n // Match and strip UTF-8 BOM from the start of the stream, if present.\n // (Per the spec, this is only valid at the very start of the stream)\n if (\n chunk.charCodeAt(0) === 0xef &&\n chunk.charCodeAt(1) === 0xbb &&\n chunk.charCodeAt(2) === 0xbf\n ) {\n chunk = chunk.slice(3)\n }\n }\n\n // Hot path: no buffered prefix from a prior partial line. Hand the chunk\n // straight to `processLines`, exactly like the original implementation.\n // Zero new work in the common case (every chunk ends with `\\n\\n`).\n if (pendingFragments.length === 0) {\n const trailing = processLines(chunk)\n if (trailing !== '') {\n pendingFragments.push(trailing)\n pendingFragmentsLength = trailing.length\n }\n checkBufferSize()\n return\n }\n\n // We have a buffered prefix. If this chunk also has no terminator, append\n // to the buffer without concatenating — that's the O(N²) trap we're\n // avoiding (large single `data:` payload split across many tiny chunks).\n if (chunk.indexOf('\\n') === -1 && chunk.indexOf('\\r') === -1) {\n pendingFragments.push(chunk)\n pendingFragmentsLength += chunk.length\n checkBufferSize()\n return\n }\n\n // Terminator arrived. Join the accumulated fragments + this chunk once,\n // process, and buffer any new trailing partial line.\n pendingFragments.push(chunk)\n const input = pendingFragments.join('')\n pendingFragments.length = 0\n pendingFragmentsLength = 0\n const trailing = processLines(input)\n if (trailing !== '') {\n pendingFragments.push(trailing)\n pendingFragmentsLength = trailing.length\n }\n checkBufferSize()\n }\n\n function checkBufferSize() {\n if (maxBufferSize === undefined) return\n if (pendingFragmentsLength + data.length <= maxBufferSize) return\n\n terminated = true\n pendingFragments.length = 0\n pendingFragmentsLength = 0\n id = undefined\n data = ''\n dataLines = 0\n eventType = undefined\n onError(\n new ParseError(`Buffered data exceeded max buffer size of ${maxBufferSize} characters`, {\n type: 'max-buffer-size-exceeded',\n }),\n )\n }\n\n /**\n * Splits `chunk` into SSE lines and dispatches each to the appropriate handler.\n * Returns any trailing bytes that did not terminate with a line break, so the\n * caller can prepend them to the next chunk.\n *\n * The SSE spec permits three line terminators: `\\n`, `\\r`, and `\\r\\n`. Real-world\n * streams almost always use plain `\\n`, so we take a fast path when no `\\r` is\n * present in the chunk. The slow path is spec-correct but does more work per line.\n */\n function processLines(chunk: string): string {\n let searchIndex = 0\n\n // Fast path: LF-only chunk (the common case for typical SSE servers).\n // We can scan forward with a single `indexOf('\\n')` per line and inline\n // the hot-path branches for `data:` and `event:` without the CR bookkeeping\n // the slow path needs.\n if (chunk.indexOf('\\r') === -1) {\n let lfIndex = chunk.indexOf('\\n', searchIndex)\n while (lfIndex !== -1) {\n // Blank line: end-of-event marker. Dispatch the accumulated event (if any)\n // and reset the buffered fields. This is hoisted out of `parseLine` because\n // it's the single most common line shape after `data:` lines.\n if (searchIndex === lfIndex) {\n if (dataLines > 0) {\n onEvent({id, event: eventType, data})\n }\n id = undefined\n data = ''\n dataLines = 0\n eventType = undefined\n searchIndex = lfIndex + 1\n lfIndex = chunk.indexOf('\\n', searchIndex)\n continue\n }\n const firstCharCode = chunk.charCodeAt(searchIndex)\n if (isDataPrefix(chunk, searchIndex, firstCharCode)) {\n // `data:` line — append the value to the event's data buffer.\n // 'data:'.length === 5, 'data: '.length === 6\n const valueStart =\n chunk.charCodeAt(searchIndex + 5) === SPACE ? searchIndex + 6 : searchIndex + 5\n const value = chunk.slice(valueStart, lfIndex)\n // Fast path within a fast path: if this is the first data line AND the\n // next char is another LF (i.e. `data:foo\\n\\n`), dispatch immediately\n // without ever writing to the `data` buffer. This is the shape of a\n // typical single-line SSE event (ChatGPT-style streams, etc.) and is\n // hot enough to be worth the duplication.\n if (dataLines === 0 && chunk.charCodeAt(lfIndex + 1) === LF) {\n onEvent({id, event: eventType, data: value})\n id = undefined\n data = ''\n eventType = undefined\n searchIndex = lfIndex + 2\n lfIndex = chunk.indexOf('\\n', searchIndex)\n continue\n }\n // Multi-line data: concatenate with newline separator per spec.\n data = dataLines === 0 ? value : `${data}\\n${value}`\n dataLines++\n } else if (isEventPrefix(chunk, searchIndex, firstCharCode)) {\n // `event:` line — set the event type for the next dispatch. Per spec,\n // an empty value resets `event type` to its default (undefined here).\n // 'event:'.length === 6, 'event: '.length === 7\n eventType =\n chunk.slice(\n chunk.charCodeAt(searchIndex + 6) === SPACE ? searchIndex + 7 : searchIndex + 6,\n lfIndex,\n ) || undefined\n } else {\n // Everything else: `id:`, `retry:`, comment lines (`:` prefix), unknown\n // fields, or malformed lines. These are rarer and go through the full\n // per-line parser, which handles the SSE field grammar in detail.\n parseLine(chunk, searchIndex, lfIndex)\n }\n searchIndex = lfIndex + 1\n lfIndex = chunk.indexOf('\\n', searchIndex)\n }\n return chunk.slice(searchIndex)\n }\n\n // Slow path: the chunk contains at least one `\\r`, so lines may be terminated\n // by `\\r`, `\\n`, or `\\r\\n`. We locate the next terminator by looking at both\n // the nearest `\\r` and `\\n` and picking whichever comes first.\n while (searchIndex < chunk.length) {\n const crIndex = chunk.indexOf('\\r', searchIndex)\n const lfIndex = chunk.indexOf('\\n', searchIndex)\n\n let lineEnd = -1\n if (crIndex !== -1 && lfIndex !== -1) {\n lineEnd = crIndex < lfIndex ? crIndex : lfIndex\n } else if (crIndex !== -1) {\n // A trailing `\\r` at the very end of the chunk is ambiguous: it could be\n // a bare-CR terminator, or the first half of a `\\r\\n` whose `\\n` arrives\n // in the next chunk. Defer until we see more input.\n if (crIndex === chunk.length - 1) {\n lineEnd = -1\n } else {\n lineEnd = crIndex\n }\n } else if (lfIndex !== -1) {\n lineEnd = lfIndex\n }\n\n if (lineEnd === -1) {\n break\n }\n\n parseLine(chunk, searchIndex, lineEnd)\n searchIndex = lineEnd + 1\n // If we just consumed a `\\r` and the next char is `\\n`, skip it so the\n // pair is treated as a single terminator rather than an empty line.\n if (chunk.charCodeAt(searchIndex - 1) === CR && chunk.charCodeAt(searchIndex) === LF) {\n searchIndex++\n }\n }\n\n return chunk.slice(searchIndex)\n }\n\n function parseLine(chunk: string, start: number, end: number) {\n if (start === end) {\n dispatchEvent()\n return\n }\n\n const firstCharCode = chunk.charCodeAt(start)\n\n if (isDataPrefix(chunk, start, firstCharCode)) {\n // 'data:'.length === 5, 'data: '.length === 6\n const valueStart = chunk.charCodeAt(start + 5) === SPACE ? start + 6 : start + 5\n const value = chunk.slice(valueStart, end)\n data = dataLines === 0 ? value : `${data}\\n${value}`\n dataLines++\n return\n }\n\n if (isEventPrefix(chunk, start, firstCharCode)) {\n // 'event:'.length === 6, 'event: '.length === 7\n eventType =\n chunk.slice(chunk.charCodeAt(start + 6) === SPACE ? start + 7 : start + 6, end) || undefined\n return\n }\n\n // Fast path for \"id:\" — 'i' = 105, 'd' = 100, ':' = 58\n if (\n firstCharCode === 105 &&\n chunk.charCodeAt(start + 1) === 100 &&\n chunk.charCodeAt(start + 2) === 58\n ) {\n // 'id:'.length === 3, 'id: '.length === 4\n const value = chunk.slice(chunk.charCodeAt(start + 3) === SPACE ? start + 4 : start + 3, end)\n id = value.includes('\\0') ? undefined : value\n return\n }\n\n // Comment line — ':' = 58\n if (firstCharCode === 58) {\n if (onComment) {\n const line = chunk.slice(start, end)\n // skip ':' (+1), or ': ' (+2) when a space follows\n onComment(line.slice(chunk.charCodeAt(start + 1) === SPACE ? 2 : 1))\n }\n return\n }\n\n const line = chunk.slice(start, end)\n const fieldSeparatorIndex = line.indexOf(':')\n if (fieldSeparatorIndex === -1) {\n processField(line, '', line)\n return\n }\n\n const field = line.slice(0, fieldSeparatorIndex)\n // skip ':' (+1), or ': ' (+2) when a space follows\n const offset = line.charCodeAt(fieldSeparatorIndex + 1) === SPACE ? 2 : 1\n const value = line.slice(fieldSeparatorIndex + offset)\n processField(field, value, line)\n }\n\n function processField(field: string, value: string, line: string) {\n // Field names must be compared literally, with no case folding performed.\n switch (field) {\n case 'event':\n // Set the `event type` buffer to field value\n eventType = value || undefined\n break\n case 'data':\n data = dataLines === 0 ? value : `${data}\\n${value}`\n dataLines++\n break\n case 'id':\n // If the field value does not contain U+0000 NULL, then set the `ID` buffer to\n // the field value. Otherwise, ignore the field.\n id = value.includes('\\0') ? undefined : value\n break\n case 'retry':\n // If the field value consists of only ASCII digits, then interpret the field value as an\n // integer in base ten, and set the event stream's reconnection time to that integer.\n // Otherwise, ignore the field.\n if (/^\\d+$/.test(value)) {\n onRetry(parseInt(value, 10))\n } else {\n onError(\n new ParseError(`Invalid \\`retry\\` value: \"${value}\"`, {\n type: 'invalid-retry',\n value,\n line,\n }),\n )\n }\n break\n default:\n // Otherwise, the field is ignored.\n onError(\n new ParseError(\n `Unknown field \"${field.length > 20 ? `${field.slice(0, 20)}…` : field}\"`,\n {type: 'unknown-field', field, value, line},\n ),\n )\n break\n }\n }\n\n function dispatchEvent() {\n if (dataLines > 0) {\n onEvent({\n id,\n event: eventType,\n data,\n })\n }\n\n id = undefined\n data = ''\n dataLines = 0\n eventType = undefined\n }\n\n function reset(options: {consume?: boolean} = {}) {\n if (options.consume && pendingFragments.length > 0) {\n const incompleteLine = pendingFragments.join('')\n parseLine(incompleteLine, 0, incompleteLine.length)\n }\n\n isFirstChunk = true\n id = undefined\n data = ''\n dataLines = 0\n eventType = undefined\n pendingFragments.length = 0\n pendingFragmentsLength = 0\n terminated = false\n }\n\n return {feed, reset}\n}\n\n/**\n * Checks if `chunk` starts with the literal `data:` at index `i`.\n *\n * Equivalent to `chunk.startsWith('data:', i)`, but benchmarks show this\n * hand-unrolled char-code comparison is ~20% faster on common event types.\n * The caller passes `firstCharCode` (the code at `i`) so it can be reused\n * across prefix checks.\n *\n * ASCII: 'd' = 100, 'a' = 97, 't' = 116, 'a' = 97, ':' = 58\n */\nfunction isDataPrefix(chunk: string, i: number, firstCharCode: number): boolean {\n return (\n firstCharCode === 100 &&\n chunk.charCodeAt(i + 1) === 97 &&\n chunk.charCodeAt(i + 2) === 116 &&\n chunk.charCodeAt(i + 3) === 97 &&\n chunk.charCodeAt(i + 4) === 58\n )\n}\n\n/**\n * Checks if `chunk` starts with the literal `event:` at index `i`.\n *\n * See {@link isDataPrefix} for why this is hand-unrolled rather than using\n * `String.prototype.startsWith`.\n *\n * ASCII: 'e' = 101, 'v' = 118, 'e' = 101, 'n' = 110, 't' = 116, ':' = 58\n */\nfunction isEventPrefix(chunk: string, i: number, firstCharCode: number): boolean {\n return (\n firstCharCode === 101 &&\n chunk.charCodeAt(i + 1) === 118 &&\n chunk.charCodeAt(i + 2) === 101 &&\n chunk.charCodeAt(i + 3) === 110 &&\n chunk.charCodeAt(i + 4) === 116 &&\n chunk.charCodeAt(i + 5) === 58\n )\n}\n"],"names":["trailing","value","line"],"mappings":"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"}
|
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Creates a new EventSource parser.
|
|
3
3
|
*
|
|
4
|
-
* @param
|
|
5
|
-
*
|
|
6
|
-
* - `onError` when an error occurs
|
|
7
|
-
* - `onRetry` when a new reconnection interval has been sent from the server
|
|
8
|
-
* - `onComment` when a comment is encountered in the stream
|
|
4
|
+
* @param config - Parser configuration. Accepts callbacks (see {@link ParserCallbacks})
|
|
5
|
+
* and options like `maxBufferSize` (see {@link ParserConfig}).
|
|
9
6
|
*
|
|
10
|
-
* @returns A new EventSource parser, with `
|
|
7
|
+
* @returns A new EventSource parser, with `feed` and `reset` methods.
|
|
11
8
|
* @public
|
|
12
9
|
*/
|
|
13
|
-
export declare function createParser(
|
|
14
|
-
callbacks: ParserCallbacks,
|
|
15
|
-
): EventSourceParser;
|
|
10
|
+
export declare function createParser(config: ParserConfig): EventSourceParser;
|
|
16
11
|
|
|
17
12
|
/**
|
|
18
13
|
* The type of error that occurred.
|
|
19
14
|
* @public
|
|
20
15
|
*/
|
|
21
|
-
export declare type ErrorType =
|
|
16
|
+
export declare type ErrorType =
|
|
17
|
+
| "invalid-retry"
|
|
18
|
+
| "unknown-field"
|
|
19
|
+
| "max-buffer-size-exceeded";
|
|
22
20
|
|
|
23
21
|
/**
|
|
24
22
|
* A parsed EventSource message event
|
|
@@ -143,4 +141,29 @@ export declare interface ParserCallbacks {
|
|
|
143
141
|
onError?: ((error: ParseError) => void) | undefined;
|
|
144
142
|
}
|
|
145
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Configuration accepted by {@link createParser}. Extends {@link ParserCallbacks} with
|
|
146
|
+
* additional options that control parser behavior.
|
|
147
|
+
*
|
|
148
|
+
* @public
|
|
149
|
+
*/
|
|
150
|
+
export declare interface ParserConfig extends ParserCallbacks {
|
|
151
|
+
/**
|
|
152
|
+
* Maximum number of characters the parser is allowed to buffer across calls to `feed()`.
|
|
153
|
+
*
|
|
154
|
+
* Two unbounded surfaces exist in a streaming SSE parser:
|
|
155
|
+
* - A partial line that has not yet been terminated by `\n`, `\r`, or `\r\n`.
|
|
156
|
+
* - A multi-line event whose terminating blank line has not yet arrived (each `data:`
|
|
157
|
+
* field gets appended to the buffered event).
|
|
158
|
+
*
|
|
159
|
+
* When the combined size of these buffers exceeds `maxBufferSize`, the parser emits a
|
|
160
|
+
* `ParseError` with `type: 'max-buffer-size-exceeded'` to `onError` and becomes
|
|
161
|
+
* terminated — subsequent calls to `feed()` will throw until `reset()` is called.
|
|
162
|
+
* This protects against unbounded memory growth from malformed or malicious streams.
|
|
163
|
+
*
|
|
164
|
+
* @defaultValue `undefined` (unbounded)
|
|
165
|
+
*/
|
|
166
|
+
maxBufferSize?: number | undefined;
|
|
167
|
+
}
|
|
168
|
+
|
|
146
169
|
export {};
|
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Creates a new EventSource parser.
|
|
3
3
|
*
|
|
4
|
-
* @param
|
|
5
|
-
*
|
|
6
|
-
* - `onError` when an error occurs
|
|
7
|
-
* - `onRetry` when a new reconnection interval has been sent from the server
|
|
8
|
-
* - `onComment` when a comment is encountered in the stream
|
|
4
|
+
* @param config - Parser configuration. Accepts callbacks (see {@link ParserCallbacks})
|
|
5
|
+
* and options like `maxBufferSize` (see {@link ParserConfig}).
|
|
9
6
|
*
|
|
10
|
-
* @returns A new EventSource parser, with `
|
|
7
|
+
* @returns A new EventSource parser, with `feed` and `reset` methods.
|
|
11
8
|
* @public
|
|
12
9
|
*/
|
|
13
|
-
export declare function createParser(
|
|
14
|
-
callbacks: ParserCallbacks,
|
|
15
|
-
): EventSourceParser;
|
|
10
|
+
export declare function createParser(config: ParserConfig): EventSourceParser;
|
|
16
11
|
|
|
17
12
|
/**
|
|
18
13
|
* The type of error that occurred.
|
|
19
14
|
* @public
|
|
20
15
|
*/
|
|
21
|
-
export declare type ErrorType =
|
|
16
|
+
export declare type ErrorType =
|
|
17
|
+
| "invalid-retry"
|
|
18
|
+
| "unknown-field"
|
|
19
|
+
| "max-buffer-size-exceeded";
|
|
22
20
|
|
|
23
21
|
/**
|
|
24
22
|
* A parsed EventSource message event
|
|
@@ -143,4 +141,29 @@ export declare interface ParserCallbacks {
|
|
|
143
141
|
onError?: ((error: ParseError) => void) | undefined;
|
|
144
142
|
}
|
|
145
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Configuration accepted by {@link createParser}. Extends {@link ParserCallbacks} with
|
|
146
|
+
* additional options that control parser behavior.
|
|
147
|
+
*
|
|
148
|
+
* @public
|
|
149
|
+
*/
|
|
150
|
+
export declare interface ParserConfig extends ParserCallbacks {
|
|
151
|
+
/**
|
|
152
|
+
* Maximum number of characters the parser is allowed to buffer across calls to `feed()`.
|
|
153
|
+
*
|
|
154
|
+
* Two unbounded surfaces exist in a streaming SSE parser:
|
|
155
|
+
* - A partial line that has not yet been terminated by `\n`, `\r`, or `\r\n`.
|
|
156
|
+
* - A multi-line event whose terminating blank line has not yet arrived (each `data:`
|
|
157
|
+
* field gets appended to the buffered event).
|
|
158
|
+
*
|
|
159
|
+
* When the combined size of these buffers exceeds `maxBufferSize`, the parser emits a
|
|
160
|
+
* `ParseError` with `type: 'max-buffer-size-exceeded'` to `onError` and becomes
|
|
161
|
+
* terminated — subsequent calls to `feed()` will throw until `reset()` is called.
|
|
162
|
+
* This protects against unbounded memory growth from malformed or malicious streams.
|
|
163
|
+
*
|
|
164
|
+
* @defaultValue `undefined` (unbounded)
|
|
165
|
+
*/
|
|
166
|
+
maxBufferSize?: number | undefined;
|
|
167
|
+
}
|
|
168
|
+
|
|
146
169
|
export {};
|