@pellux/goodvibes-transport-http 0.30.2 → 0.33.0

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 CHANGED
@@ -1,8 +1,8 @@
1
1
  # @pellux/goodvibes-transport-http
2
2
 
3
- Internal workspace package backing `@pellux/goodvibes-sdk/transport-http`.
3
+ Public GoodVibes HTTP transport package for JSON requests, auth headers, retry/backoff, SSE streams, and contract route invocation.
4
4
 
5
- Consumers should install `@pellux/goodvibes-sdk` and import this surface from the umbrella package.
5
+ Most applications should install `@pellux/goodvibes-sdk` and import `@pellux/goodvibes-sdk/transport-http`. Install this package directly when you only need the HTTP transport subset.
6
6
 
7
7
  Exports include:
8
8
  - contract route invocation helpers
@@ -15,11 +15,11 @@ Exports include:
15
15
  Use this surface when you need lower-level HTTP/SSE control or when you are building a custom GoodVibes client on top of the synced contracts.
16
16
 
17
17
  ```ts
18
- import { createJsonRequestInit, requestJson } from '@pellux/goodvibes-sdk/transport-http';
18
+ import { createJsonRequestInit, requestJsonRaw } from '@pellux/goodvibes-sdk/transport-http';
19
19
 
20
- const body = await requestJson(
20
+ const body = await requestJsonRaw(
21
21
  fetch,
22
- 'http://127.0.0.1:3210/api/control-plane/auth',
22
+ 'http://127.0.0.1:3421/api/control-plane/auth',
23
23
  createJsonRequestInit(process.env.GOODVIBES_TOKEN ?? null),
24
24
  );
25
25
  ```
package/dist/auth.d.ts CHANGED
@@ -8,7 +8,23 @@ export type AuthTokenInput = string | {
8
8
  readonly token: string;
9
9
  } | AuthTokenResolver | (() => string | null | undefined | Promise<string | null | undefined>) | undefined;
10
10
  export type HeaderResolver = () => MaybePromise<HeadersInit | undefined>;
11
+ /**
12
+ * Merge header inputs into a `Headers` instance.
13
+ *
14
+ * Use this when a streaming/browser API expects mutable `Headers`, such as
15
+ * EventSource-style setup or abort-aware fetch helpers. For ordinary JSON HTTP
16
+ * requests, prefer `mergeHeaderRecord` so callers receive a plain object that is
17
+ * cheap to serialize, inspect, and pass through middleware.
18
+ */
11
19
  export declare function mergeHeaders(...sources: Array<HeadersInit | undefined>): Headers;
20
+ /**
21
+ * Merge header inputs into a lower-case plain record.
22
+ *
23
+ * Preferred for normal HTTP transport requests because the result is a stable
24
+ * `Record<string, string>` for middleware, trace injection, and JSON request
25
+ * construction. Use `mergeHeaders` only when a consumer specifically needs a
26
+ * platform `Headers` object.
27
+ */
12
28
  export declare function mergeHeaderRecord(...sources: Array<HeadersInit | undefined>): Record<string, string>;
13
29
  /**
14
30
  * Accepts any supported auth token form and returns a canonical async resolver.
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAE7C,MAAM,MAAM,iBAAiB,GAAG,MAAM,YAAY,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AAE9E;;;GAGG;AACH,MAAM,MAAM,cAAc,GACtB,MAAM,GACN;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC1B,iBAAiB,GACjB,CAAC,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC,GACtE,SAAS,CAAC;AAEd,MAAM,MAAM,cAAc,GAAG,MAAM,YAAY,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;AA0CzE,wBAAgB,YAAY,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC,WAAW,GAAG,SAAS,CAAC,GAAG,OAAO,CAMhF;AAED,wBAAgB,iBAAiB,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC,WAAW,GAAG,SAAS,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAIpG;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,cAAc,GAAG,iBAAiB,CAY3E;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACpC,YAAY,CAAC,EAAE,iBAAiB,GAC/B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAMxB;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,WAAW,GAAG,SAAS,EAChC,UAAU,CAAC,EAAE,cAAc,GAC1B,OAAO,CAAC,OAAO,CAAC,CAGlB"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAE7C,MAAM,MAAM,iBAAiB,GAAG,MAAM,YAAY,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;AAE9E;;;GAGG;AACH,MAAM,MAAM,cAAc,GACtB,MAAM,GACN;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC1B,iBAAiB,GACjB,CAAC,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC,GACtE,SAAS,CAAC;AAEd,MAAM,MAAM,cAAc,GAAG,MAAM,YAAY,CAAC,WAAW,GAAG,SAAS,CAAC,CAAC;AA0CzE;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC,WAAW,GAAG,SAAS,CAAC,GAAG,OAAO,CAMhF;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC,WAAW,GAAG,SAAS,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAIpG;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,cAAc,GAAG,iBAAiB,CAY3E;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACpC,YAAY,CAAC,EAAE,iBAAiB,GAC/B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAMxB;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,WAAW,GAAG,SAAS,EAChC,UAAU,CAAC,EAAE,cAAc,GAC1B,OAAO,CAAC,OAAO,CAAC,CAGlB"}
package/dist/auth.js CHANGED
@@ -39,6 +39,14 @@ function appendHeaderRecord(target, headers) {
39
39
  target[key.toLowerCase()] = value;
40
40
  }
41
41
  }
42
+ /**
43
+ * Merge header inputs into a `Headers` instance.
44
+ *
45
+ * Use this when a streaming/browser API expects mutable `Headers`, such as
46
+ * EventSource-style setup or abort-aware fetch helpers. For ordinary JSON HTTP
47
+ * requests, prefer `mergeHeaderRecord` so callers receive a plain object that is
48
+ * cheap to serialize, inspect, and pass through middleware.
49
+ */
42
50
  export function mergeHeaders(...sources) {
43
51
  const headers = new Headers();
44
52
  for (const source of sources) {
@@ -46,6 +54,14 @@ export function mergeHeaders(...sources) {
46
54
  }
47
55
  return headers;
48
56
  }
57
+ /**
58
+ * Merge header inputs into a lower-case plain record.
59
+ *
60
+ * Preferred for normal HTTP transport requests because the result is a stable
61
+ * `Record<string, string>` for middleware, trace injection, and JSON request
62
+ * construction. Use `mergeHeaders` only when a consumer specifically needs a
63
+ * platform `Headers` object.
64
+ */
49
65
  export function mergeHeaderRecord(...sources) {
50
66
  const headers = {};
51
67
  for (const source of sources)
@@ -69,7 +85,7 @@ export function normalizeAuthToken(input) {
69
85
  return async () => input;
70
86
  }
71
87
  if (typeof input === 'function') {
72
- return async () => await input() ?? undefined;
88
+ return async () => (await input()) ?? undefined;
73
89
  }
74
90
  // { token: string } wrapper object
75
91
  return async () => input.token;
package/dist/backoff.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  export interface BackoffPolicy {
2
- readonly maxAttempts?: number;
3
- readonly baseDelayMs?: number;
4
- readonly maxDelayMs?: number;
5
- readonly backoffFactor?: number;
2
+ readonly maxAttempts?: number | undefined;
3
+ readonly baseDelayMs?: number | undefined;
4
+ readonly maxDelayMs?: number | undefined;
5
+ readonly backoffFactor?: number | undefined;
6
6
  }
7
7
  export interface ResolvedBackoffPolicy {
8
8
  readonly maxAttempts: number;
@@ -1 +1 @@
1
- {"version":3,"file":"backoff.d.ts","sourceRoot":"","sources":["../src/backoff.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,aAAa,GAAG,SAAS,EACjC,QAAQ,EAAE,qBAAqB,GAC9B,qBAAqB,CAOvB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,qBAAqB,GAC5B,MAAM,CAKR;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAqBf"}
1
+ {"version":3,"file":"backoff.d.ts","sourceRoot":"","sources":["../src/backoff.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7C;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,aAAa,GAAG,SAAS,EACjC,QAAQ,EAAE,qBAAqB,GAC9B,qBAAqB,CAOvB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,qBAAqB,GAC5B,MAAM,CAKR;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAqBf"}
@@ -28,5 +28,5 @@ export interface JsonSchemaValidationFailure {
28
28
  readonly expected: string;
29
29
  readonly received: string;
30
30
  }
31
- export declare function firstJsonSchemaFailure(schema: Record<string, unknown>, value: unknown, path?: string, root?: Record<string, unknown>): JsonSchemaValidationFailure | undefined;
31
+ export declare function firstJsonSchemaFailure(schema: Record<string, unknown>, value: unknown, path?: string, root?: Record<string, unknown>, _depth?: number): JsonSchemaValidationFailure | undefined;
32
32
  //# sourceMappingURL=client-plumbing.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client-plumbing.d.ts","sourceRoot":"","sources":["../src/client-plumbing.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,MAAM,IAAI;KAC1C,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,EAAE,SAAS,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC;CACpD,CAAC,MAAM,CAAC,CAAC,CAAC;AAEX;;;;GAIG;AACH,MAAM,MAAM,UAAU,CAAC,MAAM,EAAE,QAAQ,IACrC;IAAC,MAAM;CAAC,SAAS,CAAC,SAAS,CAAC,GACxB,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,GACvC,MAAM,SAAS,MAAM,GACnB,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GACpC,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,GACpC,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,GACrC,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;AAE5C,mFAAmF;AACnF,MAAM,MAAM,WAAW,CAAC,MAAM,EAAE,KAAK,SAAS,WAAW,IACvD;IAAC,MAAM;CAAC,SAAS,CAAC,SAAS,CAAC,GACxB,SAAS,GACT,MAAM,SAAS,MAAM,GACnB,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,MAAM,EAAE,KAAK,CAAC,CAAC,GAC1C,MAAM,CAAC;AAEf;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,QAAQ,EAC9C,IAAI,EAAE,SAAS,OAAO,EAAE,GACvB,SAAS,CAAC,MAAM,GAAG,SAAS,EAAE,QAAQ,GAAG,SAAS,CAAC,CAKrD;AAED,oGAAoG;AACpG,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAIxG;AAED,2EAA2E;AAC3E,wBAAgB,gBAAgB,CAAC,MAAM,EACrC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAKzB;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,KAAK,EAAE,OAAO,EACd,IAAI,SAAM,EACV,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAU,GACrC,2BAA2B,GAAG,SAAS,CA2FzC"}
1
+ {"version":3,"file":"client-plumbing.d.ts","sourceRoot":"","sources":["../src/client-plumbing.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,MAAM,IAAI;KAC1C,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,EAAE,SAAS,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC;CACpD,CAAC,MAAM,CAAC,CAAC,CAAC;AAEX;;;;GAIG;AACH,MAAM,MAAM,UAAU,CAAC,MAAM,EAAE,QAAQ,IACrC;IAAC,MAAM;CAAC,SAAS,CAAC,SAAS,CAAC,GACxB,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,GACvC,MAAM,SAAS,MAAM,GACnB,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GACpC,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,GACpC,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,GACrC,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;AAE5C,mFAAmF;AACnF,MAAM,MAAM,WAAW,CAAC,MAAM,EAAE,KAAK,SAAS,WAAW,IACvD;IAAC,MAAM;CAAC,SAAS,CAAC,SAAS,CAAC,GACxB,SAAS,GACT,MAAM,SAAS,MAAM,GACnB,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,MAAM,EAAE,KAAK,CAAC,CAAC,GAC1C,MAAM,CAAC;AAEf;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,QAAQ,EAC9C,IAAI,EAAE,SAAS,OAAO,EAAE,GACvB,SAAS,CAAC,MAAM,GAAG,SAAS,EAAE,QAAQ,GAAG,SAAS,CAAC,CAMrD;AAED,oGAAoG;AACpG,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAIxG;AAED,2EAA2E;AAC3E,wBAAgB,gBAAgB,CAAC,MAAM,EACrC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAKzB;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAID,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,KAAK,EAAE,OAAO,EACd,IAAI,SAAM,EACV,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAU,EACtC,MAAM,SAAI,GACT,2BAA2B,GAAG,SAAS,CA+FzC"}
@@ -1,3 +1,13 @@
1
+ import { ContractError } from '@pellux/goodvibes-errors';
2
+ const MAX_SCHEMA_PATTERN_CHARS = 512;
3
+ const MAX_SCHEMA_PATTERN_INPUT_CHARS = 50_000;
4
+ const RISKY_SCHEMA_PATTERN_CHECKS = [
5
+ /(^|[^\\])\\[1-9]/,
6
+ /\((?:[^()\\]|\\.)*[+*{][^)]*\)\s*[+*{]/,
7
+ /\.\*(?:[^|)]{0,64})\.\*/,
8
+ // Lookbehind assertions (?<=...) can also produce pathological backtracking.
9
+ /\(\?<[=!]/,
10
+ ];
1
11
  /**
2
12
  * Splits a generated client helper's rest tuple into input and options.
3
13
  * The tuple type already proves the argument shape; runtime work is only the
@@ -7,6 +17,7 @@ export function splitClientArgs(args) {
7
17
  if (args.length > 2) {
8
18
  throw new ContractError(`Contract client helper expected at most 2 arguments but received ${args.length}.`);
9
19
  }
20
+ // drop misleading non-null assertions — args[0]/args[1] may be undefined.
10
21
  return [args[0], args[1]];
11
22
  }
12
23
  /** Convert a typed client input object into the record shape required by contract route helpers. */
@@ -22,26 +33,30 @@ export function mergeClientInput(fixed, input) {
22
33
  ...(clientInputRecord(input) ?? {}),
23
34
  };
24
35
  }
25
- export function firstJsonSchemaFailure(schema, value, path = '$', root = schema) {
36
+ const MAX_SCHEMA_WALK_DEPTH = 32;
37
+ export function firstJsonSchemaFailure(schema, value, path = '$', root = schema, _depth = 0) {
38
+ // Guard against cyclic $ref chains.
39
+ if (_depth >= MAX_SCHEMA_WALK_DEPTH)
40
+ return undefined;
26
41
  if (typeof schema.$ref === 'string') {
27
42
  const resolved = resolveLocalSchemaRef(root, schema.$ref);
28
- return resolved ? firstJsonSchemaFailure(resolved, value, path, root) : undefined;
43
+ return resolved ? firstJsonSchemaFailure(resolved, value, path, root, _depth + 1) : undefined;
29
44
  }
30
45
  const allOf = readSchemaList(schema.allOf);
31
46
  for (const child of allOf) {
32
- const failure = firstJsonSchemaFailure(child, value, path, root);
47
+ const failure = firstJsonSchemaFailure(child, value, path, root, _depth + 1);
33
48
  if (failure)
34
49
  return failure;
35
50
  }
36
51
  const anyOf = readSchemaList(schema.anyOf);
37
52
  if (anyOf.length > 0) {
38
- const failures = anyOf.map((child) => firstJsonSchemaFailure(child, value, path, root));
53
+ const failures = anyOf.map((child) => firstJsonSchemaFailure(child, value, path, root, _depth + 1));
39
54
  if (failures.every(Boolean))
40
55
  return bestSchemaFailure(failures) ?? { path, expected: 'one matching schema', received: typeOfJsonValue(value) };
41
56
  }
42
57
  const oneOf = readSchemaList(schema.oneOf);
43
58
  if (oneOf.length > 0) {
44
- const matches = oneOf.filter((child) => !firstJsonSchemaFailure(child, value, path, root)).length;
59
+ const matches = oneOf.filter((child) => !firstJsonSchemaFailure(child, value, path, root, _depth + 1)).length;
45
60
  if (matches !== 1)
46
61
  return { path, expected: 'exactly one matching schema', received: `${matches} matches` };
47
62
  }
@@ -73,8 +88,8 @@ export function firstJsonSchemaFailure(schema, value, path = '$', root = schema)
73
88
  return { path, expected: `length <= ${maxLength}`, received: `length ${value.length}` };
74
89
  }
75
90
  if (typeof value === 'string' && typeof schema.pattern === 'string') {
76
- const pattern = new RegExp(schema.pattern);
77
- if (!pattern.test(value))
91
+ const pattern = compileContractPattern(schema.pattern);
92
+ if (!contractPatternMatches(pattern, value))
78
93
  return { path, expected: `pattern ${schema.pattern}`, received: 'non-matching string' };
79
94
  }
80
95
  if (typeof value === 'string' && typeof schema.format === 'string' && !stringMatchesJsonSchemaFormat(value, schema.format)) {
@@ -86,7 +101,8 @@ export function firstJsonSchemaFailure(schema, value, path = '$', root = schema)
86
101
  const itemSchema = schema.items;
87
102
  if (itemSchema && typeof itemSchema === 'object' && !Array.isArray(itemSchema)) {
88
103
  for (let index = 0; index < value.length; index++) {
89
- const failure = firstJsonSchemaFailure(itemSchema, value[index], `${path}[${index}]`, root);
104
+ // pass _depth + 1 so depth-reset through array items is prevented.
105
+ const failure = firstJsonSchemaFailure(itemSchema, value[index], `${path}[${index}]`, root, _depth + 1);
90
106
  if (failure)
91
107
  return failure;
92
108
  }
@@ -113,7 +129,8 @@ export function firstJsonSchemaFailure(schema, value, path = '$', root = schema)
113
129
  continue;
114
130
  if (!propertySchema || typeof propertySchema !== 'object' || Array.isArray(propertySchema))
115
131
  continue;
116
- const failure = firstJsonSchemaFailure(propertySchema, objectValue[key], `${path}.${key}`, root);
132
+ // pass _depth + 1 so nested property recursion respects the walk depth limit.
133
+ const failure = firstJsonSchemaFailure(propertySchema, objectValue[key], `${path}.${key}`, root, _depth + 1);
117
134
  if (failure)
118
135
  return failure;
119
136
  }
@@ -158,6 +175,24 @@ function readSchemaTypes(type) {
158
175
  return type.filter((entry) => typeof entry === 'string');
159
176
  return [];
160
177
  }
178
+ function compileContractPattern(source) {
179
+ if (source.length > MAX_SCHEMA_PATTERN_CHARS) {
180
+ throw new ContractError(`Contract schema pattern exceeds ${MAX_SCHEMA_PATTERN_CHARS} characters.`);
181
+ }
182
+ for (const pattern of RISKY_SCHEMA_PATTERN_CHECKS) {
183
+ if (pattern.test(source)) {
184
+ throw new ContractError('Contract schema pattern is too expensive to evaluate safely.');
185
+ }
186
+ }
187
+ return new RegExp(source);
188
+ }
189
+ function contractPatternMatches(pattern, value) {
190
+ if (value.length > MAX_SCHEMA_PATTERN_INPUT_CHARS) {
191
+ throw new ContractError(`Contract schema pattern input exceeds ${MAX_SCHEMA_PATTERN_INPUT_CHARS} characters.`);
192
+ }
193
+ pattern.lastIndex = 0;
194
+ return pattern.test(value);
195
+ }
161
196
  function valueMatchesJsonType(value, type) {
162
197
  switch (type) {
163
198
  case 'null': return value === null;
@@ -173,7 +208,10 @@ function valueMatchesJsonType(value, type) {
173
208
  function stringMatchesJsonSchemaFormat(value, format) {
174
209
  switch (format) {
175
210
  case 'date-time':
176
- return !Number.isNaN(Date.parse(value)) && /\dT\d/.test(value);
211
+ // JSON Schema date-time requires a full date/time separator; Date.parse
212
+ // alone accepts date-only strings in some runtimes.
213
+ return /^\d{4}-\d{2}-\d{2}T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/.test(value)
214
+ && !Number.isNaN(Date.parse(value));
177
215
  case 'date':
178
216
  return /^\d{4}-\d{2}-\d{2}$/.test(value) && !Number.isNaN(Date.parse(`${value}T00:00:00.000Z`));
179
217
  case 'time':
@@ -240,4 +278,3 @@ function typeOfJsonValue(value) {
240
278
  return 'array';
241
279
  return typeof value;
242
280
  }
243
- import { ContractError } from '@pellux/goodvibes-errors';
@@ -8,23 +8,22 @@ export interface ContractRouteDefinition {
8
8
  export interface ContractRouteLike {
9
9
  readonly id: string;
10
10
  /** When true, this route is safe to retry on 5xx even for mutating HTTP verbs. */
11
- readonly idempotent?: boolean;
11
+ readonly idempotent?: boolean | undefined;
12
12
  }
13
13
  export interface ContractInvokeOptions {
14
- readonly signal?: AbortSignal;
15
- readonly headers?: HeadersInit;
14
+ readonly signal?: AbortSignal | undefined;
15
+ readonly headers?: HeadersInit | undefined;
16
16
  /**
17
17
  * Optional Zod v4 schema to validate the parsed response body against.
18
18
  * When provided, a failed parse throws a {@link ContractError} with
19
19
  * field-level detail: operation, field path, expected type, and a
20
20
  * recovery hint.
21
21
  */
22
- readonly responseSchema?: ZodType;
22
+ readonly responseSchema?: ZodType | undefined;
23
23
  }
24
24
  export interface ContractStreamOptions extends ContractInvokeOptions {
25
25
  readonly handlers: ServerSentEventHandlers;
26
26
  }
27
- export declare function buildContractInput(primaryKey: string, primaryValue: string, input?: Record<string, unknown>): Record<string, unknown>;
28
27
  export declare function requireContractRoute<TRoute extends ContractRouteLike>(routes: readonly TRoute[], routeId: string, kind: string): TRoute;
29
28
  export declare function invokeContractRoute<T = unknown>(transport: HttpTransport, route: ContractRouteDefinition & ContractRouteLike, input?: Record<string, unknown>, options?: ContractInvokeOptions): Promise<T>;
30
29
  export declare function openContractRouteStream(transport: HttpTransport, route: ContractRouteDefinition, input: Record<string, unknown> | undefined, options: ContractStreamOptions): Promise<() => void>;
@@ -1 +1 @@
1
- {"version":3,"file":"contract-client.d.ts","sourceRoot":"","sources":["../src/contract-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAgC,KAAK,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAE7F,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,kFAAkF;IAClF,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;CACnC;AAED,MAAM,WAAW,qBAAsB,SAAQ,qBAAqB;IAClE,QAAQ,CAAC,QAAQ,EAAE,uBAAuB,CAAC;CAC5C;AAED,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAKzB;AAED,wBAAgB,oBAAoB,CAAC,MAAM,SAAS,iBAAiB,EACnE,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,GACX,MAAM,CAMR;AAED,wBAAsB,mBAAmB,CAAC,CAAC,GAAG,OAAO,EACnD,SAAS,EAAE,aAAa,EACxB,KAAK,EAAE,uBAAuB,GAAG,iBAAiB,EAClD,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,CAAC,CAAC,CAyBZ;AAED,wBAAsB,uBAAuB,CAC3C,SAAS,EAAE,aAAa,EACxB,KAAK,EAAE,uBAAuB,EAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC1C,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,MAAM,IAAI,CAAC,CAYrB"}
1
+ {"version":3,"file":"contract-client.d.ts","sourceRoot":"","sources":["../src/contract-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAgC,KAAK,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAE7F,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,kFAAkF;IAClF,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC3C;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC/C;AAED,MAAM,WAAW,qBAAsB,SAAQ,qBAAqB;IAClE,QAAQ,CAAC,QAAQ,EAAE,uBAAuB,CAAC;CAC5C;AAED,wBAAgB,oBAAoB,CAAC,MAAM,SAAS,iBAAiB,EACnE,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,GACX,MAAM,CAMR;AAED,wBAAsB,mBAAmB,CAAC,CAAC,GAAG,OAAO,EACnD,SAAS,EAAE,aAAa,EACxB,KAAK,EAAE,uBAAuB,GAAG,iBAAiB,EAClD,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,CAAC,CAAC,CAyBZ;AAED,wBAAsB,uBAAuB,CAC3C,SAAS,EAAE,aAAa,EACxB,KAAK,EAAE,uBAAuB,EAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC1C,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,MAAM,IAAI,CAAC,CAYrB"}
@@ -1,11 +1,5 @@
1
1
  import { ContractError, GoodVibesSdkError } from '@pellux/goodvibes-errors';
2
2
  import { openRawServerSentEventStream } from './sse-stream.js';
3
- export function buildContractInput(primaryKey, primaryValue, input) {
4
- return {
5
- [primaryKey]: primaryValue,
6
- ...(input ?? {}),
7
- };
8
- }
9
3
  export function requireContractRoute(routes, routeId, kind) {
10
4
  const route = routes.find((candidate) => candidate.id === routeId);
11
5
  if (!route) {
@@ -5,9 +5,8 @@ import { type TransportPaths } from './paths.js';
5
5
  import { type TransportMiddleware, type TransportObserver } from '@pellux/goodvibes-transport-core';
6
6
  export type { HttpRetryPolicy, PerMethodRetryPolicy } from './retry.js';
7
7
  export type { TransportContext, TransportMiddleware } from '@pellux/goodvibes-transport-core';
8
- export type JsonValue = string | number | boolean | null | {
9
- readonly [key: string]: JsonValue;
10
- } | readonly JsonValue[];
8
+ export type { JsonValue } from '@pellux/goodvibes-contracts';
9
+ import type { JsonValue } from '@pellux/goodvibes-contracts';
11
10
  export type JsonObject = {
12
11
  readonly [key: string]: JsonValue;
13
12
  };
@@ -19,53 +18,71 @@ export type JsonObject = {
19
18
  export declare function generateIdempotencyKey(): string;
20
19
  export interface HttpJsonTransportOptions {
21
20
  readonly baseUrl: string;
22
- readonly authToken?: string | null;
23
- readonly getAuthToken?: AuthTokenResolver;
24
- readonly fetch?: typeof fetch;
25
- readonly fetchImpl?: typeof fetch;
26
- readonly headers?: HeadersInit;
27
- readonly getHeaders?: HeaderResolver;
28
- readonly retry?: HttpRetryPolicy;
29
- readonly observer?: TransportObserver;
21
+ readonly authToken?: string | null | undefined;
22
+ readonly getAuthToken?: AuthTokenResolver | undefined;
23
+ readonly fetch?: typeof fetch | undefined;
24
+ readonly fetchImpl?: typeof fetch | undefined;
25
+ readonly headers?: HeadersInit | undefined;
26
+ readonly getHeaders?: HeaderResolver | undefined;
27
+ readonly retry?: HttpRetryPolicy | undefined;
28
+ readonly observer?: TransportObserver | undefined;
30
29
  /** Middleware chain applied to every HTTP request/response cycle. */
31
- readonly middleware?: readonly TransportMiddleware[];
30
+ readonly middleware?: readonly TransportMiddleware[] | undefined;
31
+ /**
32
+ * Optional callback invoked when the retry loop decides to back off.
33
+ * Fires immediately before the sleep delay. Use this to emit TRANSPORT_RETRY_SCHEDULED.
34
+ */
35
+ readonly onRetryScheduled?: ((info: {
36
+ attempt: number;
37
+ maxAttempts: number;
38
+ backoffMs: number;
39
+ reason: string;
40
+ }) => void) | undefined;
41
+ /**
42
+ * Optional callback invoked when the sleep delay completes and the next attempt is
43
+ * about to start. Fires immediately after the sleep. Use to emit TRANSPORT_RETRY_EXECUTED.
44
+ */
45
+ readonly onRetryExecuted?: ((info: {
46
+ attempt: number;
47
+ maxAttempts: number;
48
+ }) => void) | undefined;
32
49
  }
33
50
  export interface HttpJsonRequestOptions {
34
- readonly method?: string;
35
- readonly body?: unknown;
36
- readonly headers?: HeadersInit;
37
- readonly signal?: AbortSignal;
38
- readonly retry?: false | HttpRetryPolicy;
51
+ readonly method?: string | undefined;
52
+ readonly body?: unknown | undefined;
53
+ readonly headers?: HeadersInit | undefined;
54
+ readonly signal?: AbortSignal | undefined;
55
+ readonly retry?: false | HttpRetryPolicy | undefined;
39
56
  /**
40
57
  * Contract method / endpoint ID used to look up per-method retry policy overrides.
41
58
  * Populated automatically by `invokeContractRoute`; callers outside the contract
42
59
  * layer may also set this to opt into `perMethodPolicy` overrides.
43
60
  */
44
- readonly methodId?: string;
61
+ readonly methodId?: string | undefined;
45
62
  /**
46
63
  * When `true`, this call is considered idempotent even if the HTTP verb is a
47
64
  * mutating method (POST/PUT/PATCH/DELETE). Enables retry-on-5xx for the call.
48
65
  * Populated automatically from `contract.idempotent`; takes lower precedence
49
66
  * than an explicit `perMethodPolicy` override.
50
67
  */
51
- readonly idempotent?: boolean;
68
+ readonly idempotent?: boolean | undefined;
52
69
  }
53
70
  export interface ResolvedContractRequest {
54
71
  readonly url: string;
55
72
  readonly method: string;
56
- readonly body?: Record<string, unknown>;
73
+ readonly body?: Record<string, unknown> | undefined;
57
74
  }
58
75
  export interface TransportJsonError {
59
76
  readonly status: number;
60
77
  readonly body: unknown;
61
78
  readonly url: string;
62
79
  readonly method: string;
63
- readonly retryAfterMs?: number;
64
- readonly cause?: unknown;
80
+ readonly retryAfterMs?: number | undefined;
81
+ readonly cause?: unknown | undefined;
65
82
  }
66
83
  export interface HttpJsonTransport {
67
84
  readonly baseUrl: string;
68
- readonly authToken?: string | null;
85
+ readonly authToken?: string | null | undefined;
69
86
  readonly fetchImpl: typeof fetch;
70
87
  readonly paths: TransportPaths;
71
88
  buildUrl(path: string): string;
@@ -87,10 +104,12 @@ export declare const createJsonInit: typeof createJsonRequestInit;
87
104
  export declare function createFetch(fetchImpl?: typeof fetch, fallbackFetch?: typeof fetch): typeof fetch;
88
105
  export declare function readJsonBody(response: Response): Promise<unknown>;
89
106
  /**
90
- * @internal Low-level one-shot helper retained for legacy internal callers.
91
- * Public code should use `createHttpTransport(...).requestJson()` so auth,
92
- * middleware, retry policy, idempotency keys, and observers are applied.
107
+ * Low-level one-shot JSON request helper.
108
+ *
109
+ * This deliberately bypasses transport auth, middleware, retry policy,
110
+ * idempotency keys, and observers. Prefer
111
+ * `createHttpTransport(...).requestJson()` for normal application code.
93
112
  */
94
- export declare function requestJson<T>(fetchImpl: typeof fetch, url: string, init?: RequestInit): Promise<T>;
113
+ export declare function requestJsonRaw<T>(fetchImpl: typeof fetch, url: string, init?: RequestInit): Promise<T>;
95
114
  export declare function createHttpJsonTransport(options: HttpJsonTransportOptions): HttpJsonTransport;
96
115
  //# sourceMappingURL=http-core.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"http-core.d.ts","sourceRoot":"","sources":["../src/http-core.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"http-core.d.ts","sourceRoot":"","sources":["../src/http-core.ts"],"names":[],"mappings":"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"}
package/dist/http-core.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { ConfigurationError, ContractError, GoodVibesSdkError, HttpStatusError, createHttpStatusError } from '@pellux/goodvibes-errors';
2
2
  import { sleepWithSignal } from './backoff.js';
3
- import { mergeHeaderRecord, normalizeAuthToken, resolveAuthToken, resolveHeaders } from './auth.js';
3
+ import { mergeHeaderRecord, normalizeAuthToken, resolveHeaders } from './auth.js';
4
4
  import { applyPerMethodPolicy, getHttpRetryDelay, isRetryableHttpStatus, isRetryableNetworkError, resolveHttpRetryPolicy, } from './retry.js';
5
- import { buildUrl, createTransportPaths } from './paths.js';
5
+ import { assertSameOriginAbsoluteUrl, buildUrl, createTransportPaths } from './paths.js';
6
6
  import { composeMiddleware, createUuidV4, injectTraceparentAsync, invokeTransportObserver, transportErrorFromUnknown, } from '@pellux/goodvibes-transport-core';
7
7
  /**
8
8
  * Generate a UUID v4 idempotency key.
@@ -57,10 +57,15 @@ export function createNetworkTransportError(error, url, method) {
57
57
  ? error.message.trim()
58
58
  : `Transport request failed before receiving a response for ${url}`;
59
59
  const hint = `Transport could not reach ${url}. Verify the baseUrl is reachable.`;
60
+ // only mark network/connection errors as recoverable (i.e. fetch-thrown).
61
+ // TypeError or other programmer errors should not trigger retries.
62
+ const isNetworkError = error instanceof TypeError
63
+ || (error instanceof Error && /^(?:EAI_AGAIN|ECONNRESET|ECONNREFUSED|ETIMEDOUT|ENOTFOUND|EPIPE|ECONNABORTED)$/.test(error.code ?? ''))
64
+ || (error instanceof Error && /^UND_ERR_/.test(error.code ?? ''));
60
65
  const networkError = new HttpStatusError(message, {
61
66
  category: 'network',
62
67
  source: 'transport',
63
- recoverable: true,
68
+ recoverable: isNetworkError,
64
69
  url,
65
70
  method,
66
71
  body: { error: message },
@@ -92,11 +97,11 @@ function addQueryValue(url, key, value) {
92
97
  return;
93
98
  }
94
99
  if (typeof value === 'object') {
95
- // Contract query parameters are primitive or repeated primitive values.
96
- // Object values are preserved as JSON strings so callers do not silently
97
- // lose structured filters when a route explicitly accepts them.
98
- url.searchParams.append(key, JSON.stringify(value));
99
- return;
100
+ // object query values cannot be reliably round-tripped through URL
101
+ // query strings (no daemon route parses JSON-stringified query params).
102
+ // Throw a ContractError instead of implicitly serialising; callers must
103
+ // decompose objects into primitive fields before passing as query parameters.
104
+ throw new ContractError(`Contract query parameter "${key}" is an object, which cannot be safely serialised as a URL query value. Decompose it into primitive fields.`);
100
105
  }
101
106
  url.searchParams.append(key, String(value));
102
107
  }
@@ -106,10 +111,16 @@ function hasHeader(headers, name) {
106
111
  }
107
112
  function splitContractInput(path, input = {}) {
108
113
  const remaining = { ...input };
109
- const interpolatedPath = path.replace(/\{([A-Za-z_][A-Za-z0-9_.-]*)\}/g, (_match, key) => {
114
+ // forbid '.' in path-param names to prevent ambiguous flat-key lookup.
115
+ // Contract generators must not emit dotted param names like {foo.bar}.
116
+ const interpolatedPath = path.replace(/\{([A-Za-z_][A-Za-z0-9_-]*)\}/g, (_match, key) => {
110
117
  const value = toStringValue(remaining[key], key);
111
118
  delete remaining[key];
112
- return encodeURIComponent(value);
119
+ // encodeURIComponent leaves RFC 3986 sub-delimiters (!'()*~) unencoded.
120
+ // If the server uses regex routing, an unencoded `!`, `(`, `)`, `*`, `~`, or
121
+ // `'` in a path segment could match against a route pattern unexpectedly.
122
+ // Encode those characters explicitly after the standard percent-encoding pass.
123
+ return encodeURIComponent(value).replace(/[!'()*~]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
113
124
  });
114
125
  if (/[{}]/.test(interpolatedPath)) {
115
126
  throw new ContractError(`Malformed contract path "${path}". Path parameters must use "{name}" with identifier-like names.`);
@@ -120,7 +131,7 @@ export function createJsonRequestInit(token, body, method = 'GET', headers = {},
120
131
  return {
121
132
  method,
122
133
  credentials: 'include',
123
- signal,
134
+ ...(signal !== undefined ? { signal } : {}),
124
135
  headers: mergeHeaderRecord(defaultHeaders, token ? { Authorization: `Bearer ${token}` } : undefined, body !== undefined ? { 'Content-Type': 'application/json' } : undefined, headers),
125
136
  ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
126
137
  };
@@ -133,20 +144,21 @@ export function createFetch(fetchImpl, fallbackFetch) {
133
144
  }
134
145
  return resolved.bind(globalThis);
135
146
  }
147
+ const MAX_RETRY_AFTER_MS = 10 * 60 * 1000; // 10 minutes
136
148
  function parseRetryAfterMs(headers) {
137
149
  const retryAfter = headers.get('retry-after');
138
150
  if (!retryAfter)
139
151
  return undefined;
140
- // Numeric seconds
152
+ // Numeric seconds — cap to prevent hostile/buggy huge values.
141
153
  const seconds = Number(retryAfter);
142
- if (!Number.isNaN(seconds) && seconds >= 0) {
143
- return Math.ceil(seconds * 1000);
154
+ if (!Number.isNaN(seconds) && seconds > 0) {
155
+ return Math.min(Math.ceil(seconds * 1000), MAX_RETRY_AFTER_MS);
144
156
  }
145
157
  // HTTP-date
146
158
  const date = new Date(retryAfter);
147
159
  if (!Number.isNaN(date.getTime())) {
148
160
  const ms = date.getTime() - Date.now();
149
- return ms > 0 ? ms : 0;
161
+ return ms > 0 ? Math.min(ms, MAX_RETRY_AFTER_MS) : 0;
150
162
  }
151
163
  return undefined;
152
164
  }
@@ -157,17 +169,18 @@ export async function readJsonBody(response) {
157
169
  try {
158
170
  return JSON.parse(text);
159
171
  }
160
- catch (error) {
161
- void error;
172
+ catch {
162
173
  return text;
163
174
  }
164
175
  }
165
176
  /**
166
- * @internal Low-level one-shot helper retained for legacy internal callers.
167
- * Public code should use `createHttpTransport(...).requestJson()` so auth,
168
- * middleware, retry policy, idempotency keys, and observers are applied.
177
+ * Low-level one-shot JSON request helper.
178
+ *
179
+ * This deliberately bypasses transport auth, middleware, retry policy,
180
+ * idempotency keys, and observers. Prefer
181
+ * `createHttpTransport(...).requestJson()` for normal application code.
169
182
  */
170
- export async function requestJson(fetchImpl, url, init = {}) {
183
+ export async function requestJsonRaw(fetchImpl, url, init = {}) {
171
184
  let response;
172
185
  try {
173
186
  response = await fetchImpl(url, init);
@@ -192,11 +205,13 @@ export function createHttpJsonTransport(options) {
192
205
  const retryPolicy = options.retry;
193
206
  const paths = createTransportPaths(baseUrl);
194
207
  const observer = options.observer;
208
+ const onRetryScheduled = options.onRetryScheduled;
209
+ const onRetryExecuted = options.onRetryExecuted;
195
210
  // Persistent middleware chain — mutated via use().
196
211
  const middlewareChain = [...(options.middleware ?? [])];
197
212
  const requestJsonForTransport = async (pathOrUrl, requestOptions = {}) => {
198
213
  const url = pathOrUrl.startsWith('http://') || pathOrUrl.startsWith('https://')
199
- ? pathOrUrl
214
+ ? assertSameOriginAbsoluteUrl(pathOrUrl, baseUrl)
200
215
  : buildUrl(baseUrl, pathOrUrl);
201
216
  const method = requestOptions.method ?? (requestOptions.body === undefined ? 'GET' : 'POST');
202
217
  const methodId = requestOptions.methodId;
@@ -208,7 +223,17 @@ export function createHttpJsonTransport(options) {
208
223
  // Determine idempotency: non-GET mutations without an explicit idempotent flag do NOT retry.
209
224
  // This is enforced below by gating the retry check on method type.
210
225
  const isMutatingMethod = IDEMPOTENCY_KEY_METHODS.has(method.toUpperCase());
211
- const idempotencyKey = isMutatingMethod ? generateIdempotencyKey() : undefined;
226
+ // pin traceparent once before the retry loop so all retries share one trace span.
227
+ const pinnedTraceHeaders = {};
228
+ await injectTraceparentAsync(pinnedTraceHeaders);
229
+ // only generate an idempotency key when the call is actually idempotent
230
+ // (contract-marked or has a per-method policy override). Sending keys on all
231
+ // mutating methods could cause duplicate suppression for a non-retried request
232
+ // if a proxy retries it outside the SDK's control.
233
+ const hasPerMethodOverride = methodId !== undefined && resolveHttpRetryPolicy(retryPolicy, requestOptions.retry).perMethodPolicy[methodId] !== undefined;
234
+ const idempotencyKey = isMutatingMethod && (contractIdempotent || hasPerMethodOverride)
235
+ ? generateIdempotencyKey()
236
+ : undefined;
212
237
  let attempt = 0;
213
238
  while (true) {
214
239
  attempt += 1;
@@ -222,15 +247,17 @@ export function createHttpJsonTransport(options) {
222
247
  if (requestOptions.body !== undefined) {
223
248
  mergedHeaders['Content-Type'] = 'application/json';
224
249
  }
225
- // Inject W3C traceparent if OTel is active. This path is async so pure
226
- // ESM OpenTelemetry providers can be loaded before the request is sent.
227
- await injectTraceparentAsync(mergedHeaders);
250
+ // merge pre-pinned traceparent headers (captured once before the retry loop)
251
+ // so all retry attempts share a single logical trace span.
252
+ for (const [k, v] of Object.entries(pinnedTraceHeaders)) {
253
+ mergedHeaders[k] = v;
254
+ }
228
255
  // Inject idempotency key for mutating methods.
229
256
  if (idempotencyKey && !hasHeader(mergedHeaders, 'Idempotency-Key')) {
230
257
  mergedHeaders['Idempotency-Key'] = idempotencyKey;
231
258
  }
232
259
  // Notify observer before dispatching the request.
233
- invokeTransportObserver(() => observer?.onTransportActivity?.({ direction: 'send', url, kind: 'http' }));
260
+ invokeTransportObserver(() => observer?.onTransportActivity?.({ direction: 'send', url, kind: 'http' }), observer?.onObserverError);
234
261
  const sendAt = Date.now();
235
262
  // Build the middleware context for this attempt.
236
263
  const ctx = {
@@ -241,12 +268,14 @@ export function createHttpJsonTransport(options) {
241
268
  options: requestOptions,
242
269
  signal: requestOptions.signal,
243
270
  };
271
+ // stash parsed body here so the no-middleware fast path avoids re-parsing.
272
+ let _parsedBodyCache = undefined;
244
273
  // Build the inner fetch that middleware wraps (and also used directly without middleware).
245
274
  const innerFetch = async (c) => {
246
275
  const init = {
247
276
  method: c.method,
248
277
  credentials: 'include',
249
- signal: c.signal,
278
+ ...(c.signal !== undefined ? { signal: c.signal } : {}),
250
279
  headers: c.headers,
251
280
  ...(c.body !== undefined ? { body: JSON.stringify(c.body) } : {}),
252
281
  };
@@ -262,8 +291,17 @@ export function createHttpJsonTransport(options) {
262
291
  const retryAfterMs = parseRetryAfterMs(response.headers);
263
292
  throw createTransportError(response.status, c.url, c.method, body, retryAfterMs);
264
293
  }
265
- // Return synthetic Response carrying parsed body so callers can .json() it.
266
- return new Response(JSON.stringify(body), { status: response.status });
294
+ // stash the already-parsed body so the no-middleware fast path can
295
+ // read it directly without a JSON.stringify JSON.parse round-trip.
296
+ _parsedBodyCache = body;
297
+ // Also expose on ctx so middleware callers can read parsedBody directly.
298
+ ctx.parsedBody = body;
299
+ // Return synthetic Response carrying parsed body so callers (middleware) can .json().
300
+ return new Response(JSON.stringify(body), {
301
+ status: response.status,
302
+ statusText: response.statusText,
303
+ headers: response.headers,
304
+ });
267
305
  };
268
306
  try {
269
307
  if (middlewareChain.length > 0) {
@@ -281,25 +319,27 @@ export function createHttpJsonTransport(options) {
281
319
  method,
282
320
  });
283
321
  }
284
- const result = await ctx.response.json();
322
+ // use parsedBody stashed by innerFetch to avoid a JSON round-trip.
323
+ const result = (ctx.parsedBody !== undefined ? ctx.parsedBody : await ctx.response.json());
285
324
  invokeTransportObserver(() => observer?.onTransportActivity?.({
286
325
  direction: 'recv',
287
326
  url,
288
327
  kind: 'http',
289
328
  durationMs: ctx.durationMs,
290
- }));
329
+ }), observer?.onObserverError);
291
330
  return result;
292
331
  }
293
332
  // No-middleware fast path — directly invoke innerFetch with ctx.
294
- const rawResponse = await innerFetch(ctx);
295
- const result = await rawResponse.json();
333
+ await innerFetch(ctx);
334
+ // use the cached parsed body (set by innerFetch) to avoid JSON round-trip.
335
+ const result = _parsedBodyCache;
296
336
  // Notify observer after a successful response.
297
337
  invokeTransportObserver(() => observer?.onTransportActivity?.({
298
338
  direction: 'recv',
299
339
  url,
300
340
  kind: 'http',
301
341
  durationMs: Date.now() - sendAt,
302
- }));
342
+ }), observer?.onObserverError);
303
343
  return result;
304
344
  }
305
345
  catch (error) {
@@ -323,7 +363,7 @@ export function createHttpJsonTransport(options) {
323
363
  return error;
324
364
  })();
325
365
  // Notify observer of the transport error before deciding to retry or rethrow.
326
- invokeTransportObserver(() => observer?.onError?.(transportErrorFromUnknown(wrappedError, 'HTTP transport error')));
366
+ invokeTransportObserver(() => observer?.onError?.(transportErrorFromUnknown(wrappedError, 'HTTP transport error')), observer?.onObserverError);
327
367
  const status = typeof wrappedError === 'object' && wrappedError !== null && 'transport' in wrappedError
328
368
  ? wrappedError.transport?.status
329
369
  : undefined;
@@ -337,7 +377,13 @@ export function createHttpJsonTransport(options) {
337
377
  if (!shouldRetry) {
338
378
  throw wrappedError;
339
379
  }
340
- await sleepWithSignal(getHttpRetryDelay(attempt + 1, resolvedRetry), requestOptions.signal);
380
+ const backoffMs = getHttpRetryDelay(attempt, resolvedRetry);
381
+ const retryReason = typeof status === 'number' && status > 0 ? `http-${status}` : 'network-error';
382
+ // Notify callers before the retry sleep starts.
383
+ onRetryScheduled?.({ attempt, maxAttempts: resolvedRetry.maxAttempts, backoffMs, reason: retryReason });
384
+ await sleepWithSignal(backoffMs, requestOptions.signal);
385
+ // Notify callers after the retry sleep completes.
386
+ onRetryExecuted?.({ attempt, maxAttempts: resolvedRetry.maxAttempts });
341
387
  }
342
388
  }
343
389
  };
package/dist/http.d.ts CHANGED
@@ -2,11 +2,11 @@ import { type AuthTokenResolver, type HeaderResolver, type MaybePromise, mergeHe
2
2
  import { type BackoffPolicy, type ResolvedBackoffPolicy, computeBackoffDelay, normalizeBackoffPolicy, sleepWithSignal } from './backoff.js';
3
3
  import { type HttpRetryPolicy, type ResolvedHttpRetryPolicy, DEFAULT_HTTP_RETRY_POLICY, getHttpRetryDelay, isRetryableHttpStatus, isRetryableNetworkError, normalizeHttpRetryPolicy, resolveHttpRetryPolicy } from './retry.js';
4
4
  import { type ResolvedStreamReconnectPolicy, type StreamReconnectPolicy, DEFAULT_STREAM_RECONNECT_POLICY, getStreamReconnectDelay, normalizeStreamReconnectPolicy } from './reconnect.js';
5
- import { createFetch, createJsonInit, createJsonRequestInit, generateIdempotencyKey, readJsonBody, requestJson, type HttpJsonRequestOptions, type HttpJsonTransport, type HttpJsonTransportOptions, type JsonObject, type JsonValue, type ResolvedContractRequest, type TransportContext, type TransportMiddleware, type TransportJsonError } from './http-core.js';
5
+ import { createFetch, createJsonInit, createJsonRequestInit, generateIdempotencyKey, readJsonBody, requestJsonRaw, type HttpJsonRequestOptions, type HttpJsonTransport, type HttpJsonTransportOptions, type JsonObject, type JsonValue, type ResolvedContractRequest, type TransportContext, type TransportMiddleware, type TransportJsonError } from './http-core.js';
6
6
  export type { AuthTokenResolver, BackoffPolicy, HeaderResolver, HttpJsonRequestOptions, HttpRetryPolicy, JsonObject, JsonValue, MaybePromise, ResolvedBackoffPolicy, ResolvedContractRequest, ResolvedHttpRetryPolicy, ResolvedStreamReconnectPolicy, StreamReconnectPolicy, TransportContext, TransportJsonError, TransportMiddleware, };
7
7
  export type HttpTransportOptions = HttpJsonTransportOptions;
8
8
  export type HttpTransport = HttpJsonTransport;
9
- export { createFetch, createJsonInit, createJsonRequestInit, generateIdempotencyKey, readJsonBody, requestJson, mergeHeaders, resolveAuthToken, resolveHeaders, computeBackoffDelay, normalizeBackoffPolicy, sleepWithSignal, DEFAULT_HTTP_RETRY_POLICY, getHttpRetryDelay, isRetryableHttpStatus, isRetryableNetworkError, normalizeHttpRetryPolicy, resolveHttpRetryPolicy, DEFAULT_STREAM_RECONNECT_POLICY, getStreamReconnectDelay, normalizeStreamReconnectPolicy, };
9
+ export { createFetch, createJsonInit, createJsonRequestInit, generateIdempotencyKey, readJsonBody, requestJsonRaw, mergeHeaders, resolveAuthToken, resolveHeaders, computeBackoffDelay, normalizeBackoffPolicy, sleepWithSignal, DEFAULT_HTTP_RETRY_POLICY, getHttpRetryDelay, isRetryableHttpStatus, isRetryableNetworkError, normalizeHttpRetryPolicy, resolveHttpRetryPolicy, DEFAULT_STREAM_RECONNECT_POLICY, getStreamReconnectDelay, normalizeStreamReconnectPolicy, };
10
10
  export declare function normalizeTransportError(error: unknown): Error;
11
11
  export declare function createHttpTransport(options: HttpTransportOptions): HttpTransport;
12
12
  //# sourceMappingURL=http.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACf,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,qBAAqB,EAC1B,mBAAmB,EACnB,sBAAsB,EACtB,eAAe,EAChB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,yBAAyB,EACzB,iBAAiB,EACjB,qBAAqB,EACrB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,EACvB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,KAAK,6BAA6B,EAClC,KAAK,qBAAqB,EAC1B,+BAA+B,EAC/B,uBAAuB,EACvB,8BAA8B,EAC/B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,WAAW,EAEX,cAAc,EACd,qBAAqB,EACrB,sBAAsB,EAEtB,YAAY,EACZ,WAAW,EACX,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,uBAAuB,EAC5B,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACxB,MAAM,gBAAgB,CAAC;AAExB,YAAY,EACV,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,sBAAsB,EACtB,eAAe,EACf,UAAU,EACV,SAAS,EACT,YAAY,EACZ,qBAAqB,EACrB,uBAAuB,EACvB,uBAAuB,EACvB,6BAA6B,EAC7B,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,GACpB,CAAC;AACF,MAAM,MAAM,oBAAoB,GAAG,wBAAwB,CAAC;AAC5D,MAAM,MAAM,aAAa,GAAG,iBAAiB,CAAC;AAC9C,OAAO,EACL,WAAW,EACX,cAAc,EACd,qBAAqB,EACrB,sBAAsB,EACtB,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,mBAAmB,EACnB,sBAAsB,EACtB,eAAe,EACf,yBAAyB,EACzB,iBAAiB,EACjB,qBAAqB,EACrB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,EACtB,+BAA+B,EAC/B,uBAAuB,EACvB,8BAA8B,GAC/B,CAAC;AAwBF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CA8D7D;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,aAAa,CAmBhF"}
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACf,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,qBAAqB,EAC1B,mBAAmB,EACnB,sBAAsB,EACtB,eAAe,EAChB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,uBAAuB,EAC5B,yBAAyB,EACzB,iBAAiB,EACjB,qBAAqB,EACrB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,EACvB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,KAAK,6BAA6B,EAClC,KAAK,qBAAqB,EAC1B,+BAA+B,EAC/B,uBAAuB,EACvB,8BAA8B,EAC/B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,WAAW,EAEX,cAAc,EACd,qBAAqB,EACrB,sBAAsB,EAEtB,YAAY,EACZ,cAAc,EACd,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,uBAAuB,EAC5B,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACxB,MAAM,gBAAgB,CAAC;AAExB,YAAY,EACV,iBAAiB,EACjB,aAAa,EACb,cAAc,EACd,sBAAsB,EACtB,eAAe,EACf,UAAU,EACV,SAAS,EACT,YAAY,EACZ,qBAAqB,EACrB,uBAAuB,EACvB,uBAAuB,EACvB,6BAA6B,EAC7B,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,GACpB,CAAC;AACF,MAAM,MAAM,oBAAoB,GAAG,wBAAwB,CAAC;AAC5D,MAAM,MAAM,aAAa,GAAG,iBAAiB,CAAC;AAC9C,OAAO,EACL,WAAW,EACX,cAAc,EACd,qBAAqB,EACrB,sBAAsB,EACtB,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,mBAAmB,EACnB,sBAAsB,EACtB,eAAe,EACf,yBAAyB,EACzB,iBAAiB,EACjB,qBAAqB,EACrB,uBAAuB,EACvB,wBAAwB,EACxB,sBAAsB,EACtB,+BAA+B,EAC/B,uBAAuB,EACvB,8BAA8B,GAC/B,CAAC;AAwBF,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CA8D7D;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,aAAa,CAmBhF"}
package/dist/http.js CHANGED
@@ -3,8 +3,8 @@ import { mergeHeaders, resolveAuthToken, resolveHeaders, } from './auth.js';
3
3
  import { computeBackoffDelay, normalizeBackoffPolicy, sleepWithSignal, } from './backoff.js';
4
4
  import { DEFAULT_HTTP_RETRY_POLICY, getHttpRetryDelay, isRetryableHttpStatus, isRetryableNetworkError, normalizeHttpRetryPolicy, resolveHttpRetryPolicy, } from './retry.js';
5
5
  import { DEFAULT_STREAM_RECONNECT_POLICY, getStreamReconnectDelay, normalizeStreamReconnectPolicy, } from './reconnect.js';
6
- import { createFetch, createHttpJsonTransport, createJsonInit, createJsonRequestInit, generateIdempotencyKey, inferTransportHint, readJsonBody, requestJson, } from './http-core.js';
7
- export { createFetch, createJsonInit, createJsonRequestInit, generateIdempotencyKey, readJsonBody, requestJson, mergeHeaders, resolveAuthToken, resolveHeaders, computeBackoffDelay, normalizeBackoffPolicy, sleepWithSignal, DEFAULT_HTTP_RETRY_POLICY, getHttpRetryDelay, isRetryableHttpStatus, isRetryableNetworkError, normalizeHttpRetryPolicy, resolveHttpRetryPolicy, DEFAULT_STREAM_RECONNECT_POLICY, getStreamReconnectDelay, normalizeStreamReconnectPolicy, };
6
+ import { createFetch, createHttpJsonTransport, createJsonInit, createJsonRequestInit, generateIdempotencyKey, inferTransportHint, readJsonBody, requestJsonRaw, } from './http-core.js';
7
+ export { createFetch, createJsonInit, createJsonRequestInit, generateIdempotencyKey, readJsonBody, requestJsonRaw, mergeHeaders, resolveAuthToken, resolveHeaders, computeBackoffDelay, normalizeBackoffPolicy, sleepWithSignal, DEFAULT_HTTP_RETRY_POLICY, getHttpRetryDelay, isRetryableHttpStatus, isRetryableNetworkError, normalizeHttpRetryPolicy, resolveHttpRetryPolicy, DEFAULT_STREAM_RECONNECT_POLICY, getStreamReconnectDelay, normalizeStreamReconnectPolicy, };
8
8
  function isTransportError(error) {
9
9
  return Boolean(error
10
10
  && typeof error === 'object'
@@ -51,7 +51,7 @@ export function normalizeTransportError(error) {
51
51
  });
52
52
  }
53
53
  if (error instanceof Error) {
54
- // Defensive string-match fallback for non-SDK errors that slip through.
54
+ // Defensive string-match path for non-SDK errors that slip through.
55
55
  // With structured throws in http-core.ts, these paths are rarely exercised.
56
56
  if (error.message === 'Fetch implementation is required' || error.message === 'Transport baseUrl is required') {
57
57
  return new ConfigurationError(error.message);
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  export type { ContractInvokeOptions, ContractRouteDefinition, ContractRouteLike, ContractStreamOptions, } from './contract-client.js';
2
- export { buildContractInput, invokeContractRoute, openContractRouteStream, requireContractRoute, } from './contract-client.js';
2
+ export { invokeContractRoute, openContractRouteStream, requireContractRoute, } from './contract-client.js';
3
3
  export type { JsonSchemaValidationFailure, MethodArgs, RequiredKeys, WithoutKeys } from './client-plumbing.js';
4
4
  export { clientInputRecord, firstJsonSchemaFailure, mergeClientInput, splitClientArgs } from './client-plumbing.js';
5
5
  export type { HttpJsonRequestOptions, HttpTransport, HttpTransportOptions, JsonObject, JsonValue, ResolvedContractRequest, TransportJsonError, } from './http.js';
6
- export { createFetch, createHttpTransport, createJsonInit, createJsonRequestInit, normalizeTransportError, readJsonBody, requestJson, } from './http.js';
7
- export type { TransportContext, TransportMiddleware } from './http-core.js';
6
+ export { createFetch, createHttpTransport, createJsonInit, createJsonRequestInit, normalizeTransportError, readJsonBody, requestJsonRaw, } from './http.js';
7
+ export type { HttpJsonTransport, HttpJsonTransportOptions, TransportContext, TransportMiddleware } from './http-core.js';
8
8
  export { generateIdempotencyKey } from './http-core.js';
9
9
  export type { ServerSentEventHandlers, ServerSentEventOptions } from './sse.js';
10
10
  export { openServerSentEventStream } from './sse.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,qBAAqB,EACrB,uBAAuB,EACvB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,2BAA2B,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC/G,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACpH,YAAY,EACV,sBAAsB,EACtB,aAAa,EACb,oBAAoB,EACpB,UAAU,EACV,SAAS,EACT,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,YAAY,EACZ,WAAW,GACZ,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,YAAY,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAChF,OAAO,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AACrD,YAAY,EAAE,uBAAuB,IAAI,0BAA0B,EAAE,sBAAsB,IAAI,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAClJ,OAAO,EAAE,4BAA4B,EAAE,MAAM,iBAAiB,CAAC;AAC/D,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACjG,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAClH,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC5F,YAAY,EAAE,eAAe,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AACjG,OAAO,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAClM,YAAY,EAAE,qBAAqB,EAAE,6BAA6B,EAAE,MAAM,gBAAgB,CAAC;AAC3F,OAAO,EAAE,+BAA+B,EAAE,uBAAuB,EAAE,8BAA8B,EAAE,MAAM,gBAAgB,CAAC;AAC1H,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,qBAAqB,EACrB,uBAAuB,EACvB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,2BAA2B,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC/G,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACpH,YAAY,EACV,sBAAsB,EACtB,aAAa,EACb,oBAAoB,EACpB,UAAU,EACV,SAAS,EACT,uBAAuB,EACvB,kBAAkB,GACnB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,YAAY,EACZ,cAAc,GACf,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,iBAAiB,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACzH,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,YAAY,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC;AAChF,OAAO,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AACrD,YAAY,EAAE,uBAAuB,IAAI,0BAA0B,EAAE,sBAAsB,IAAI,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AAClJ,OAAO,EAAE,4BAA4B,EAAE,MAAM,iBAAiB,CAAC;AAC/D,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACjG,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAClH,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC5F,YAAY,EAAE,eAAe,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AACjG,OAAO,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAClM,YAAY,EAAE,qBAAqB,EAAE,6BAA6B,EAAE,MAAM,gBAAgB,CAAC;AAC3F,OAAO,EAAE,+BAA+B,EAAE,uBAAuB,EAAE,8BAA8B,EAAE,MAAM,gBAAgB,CAAC;AAC1H,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- export { buildContractInput, invokeContractRoute, openContractRouteStream, requireContractRoute, } from './contract-client.js';
1
+ export { invokeContractRoute, openContractRouteStream, requireContractRoute, } from './contract-client.js';
2
2
  export { clientInputRecord, firstJsonSchemaFailure, mergeClientInput, splitClientArgs } from './client-plumbing.js';
3
- export { createFetch, createHttpTransport, createJsonInit, createJsonRequestInit, normalizeTransportError, readJsonBody, requestJson, } from './http.js';
3
+ export { createFetch, createHttpTransport, createJsonInit, createJsonRequestInit, normalizeTransportError, readJsonBody, requestJsonRaw, } from './http.js';
4
4
  export { generateIdempotencyKey } from './http-core.js';
5
5
  export { openServerSentEventStream } from './sse.js';
6
6
  export { openRawServerSentEventStream } from './sse-stream.js';
package/dist/paths.d.ts CHANGED
@@ -25,8 +25,22 @@ export interface TransportPaths {
25
25
  readonly peerRequestsUrl: string;
26
26
  readonly peerListUrl: string;
27
27
  readonly remoteWorkUrl: string;
28
+ /**
29
+ * Alias for `controlPlaneUrl`. Provided for convenience.
30
+ */
31
+ readonly controlUrl: string;
28
32
  }
29
33
  export declare function normalizeBaseUrl(baseUrl: string): string;
30
34
  export declare function buildUrl(baseUrl: string, path: string): string;
35
+ /**
36
+ * Assert that an absolute URL has the same origin as a reference URL (or baseUrl).
37
+ *
38
+ * Used by transport call sites that allow absolute URLs as a convenience but
39
+ * MUST NOT leak the bearer Authorization header to a different origin. Throws
40
+ * `ConfigurationError SDK_TRANSPORT_CROSS_ORIGIN` when origins diverge.
41
+ *
42
+ * Returns the validated absolute URL unchanged on success.
43
+ */
44
+ export declare function assertSameOriginAbsoluteUrl(absoluteUrl: string, originReferenceUrl: string): string;
31
45
  export declare function createTransportPaths(baseUrl: string): TransportPaths;
32
46
  //# sourceMappingURL=paths.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAC;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMxD;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAG9D;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CA8BpE"}
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAC;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;;OAEG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAoCxD;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAU9D;AAED;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CAAC,WAAW,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,GAAG,MAAM,CA6BnG;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CA+BpE"}
package/dist/paths.js CHANGED
@@ -4,11 +4,91 @@ export function normalizeBaseUrl(baseUrl) {
4
4
  if (!normalized) {
5
5
  throw new ConfigurationError('Transport baseUrl is required. Pass a non-empty baseUrl string to your transport or SDK options.', { code: 'SDK_TRANSPORT_BASE_URL_REQUIRED' });
6
6
  }
7
+ let parsed;
8
+ try {
9
+ parsed = new URL(normalized);
10
+ }
11
+ catch (cause) {
12
+ throw new ConfigurationError('Transport baseUrl must be an absolute URL.', {
13
+ code: 'SDK_TRANSPORT_BASE_URL_INVALID',
14
+ source: 'transport',
15
+ hint: 'Pass a full URL such as https://goodvibes.example.com.',
16
+ cause,
17
+ });
18
+ }
19
+ const protocol = parsed.protocol;
20
+ if (protocol !== 'https:' && protocol !== 'wss:' && protocol !== 'http:' && protocol !== 'ws:') {
21
+ throw new ConfigurationError(`Unsupported transport baseUrl protocol: ${protocol}`, {
22
+ code: 'SDK_TRANSPORT_BASE_URL_PROTOCOL_UNSUPPORTED',
23
+ source: 'transport',
24
+ hint: 'Use https:// or wss://. http:// and ws:// are accepted only for local development or explicit insecure deployments.',
25
+ });
26
+ }
27
+ const host = parsed.hostname.toLowerCase();
28
+ const local = host === 'localhost' || host === '::1' || host === '127.0.0.1' || host.startsWith('127.');
29
+ const runtimeProcess = globalThis.process;
30
+ const allowInsecure = runtimeProcess?.env?.GOODVIBES_ALLOW_INSECURE_TRANSPORT === 'true';
31
+ if ((protocol === 'http:' || protocol === 'ws:') && !local && !allowInsecure) {
32
+ throw new ConfigurationError('Refusing insecure non-local GoodVibes transport baseUrl.', {
33
+ code: 'SDK_TRANSPORT_INSECURE_BASE_URL',
34
+ source: 'transport',
35
+ hint: 'Use https:// or wss://, or set GOODVIBES_ALLOW_INSECURE_TRANSPORT=true for an intentional non-local development deployment.',
36
+ });
37
+ }
7
38
  return normalized.replace(/\/+$/, '');
8
39
  }
9
40
  export function buildUrl(baseUrl, path) {
10
41
  const normalized = normalizeBaseUrl(baseUrl);
11
- return new URL(path, `${normalized}/`).toString();
42
+ if (/^[a-z][a-z0-9+\-.]*:/i.test(path)) {
43
+ throw new ConfigurationError(`Absolute path not allowed: ${path}`, { code: 'SDK_TRANSPORT_PATH_ABSOLUTE' });
44
+ }
45
+ try {
46
+ return new URL(path, `${normalized}/`).toString();
47
+ }
48
+ catch (cause) {
49
+ throw new ConfigurationError(`Invalid transport path: ${path}`, { code: 'SDK_TRANSPORT_PATH_INVALID', cause });
50
+ }
51
+ }
52
+ /**
53
+ * Assert that an absolute URL has the same origin as a reference URL (or baseUrl).
54
+ *
55
+ * Used by transport call sites that allow absolute URLs as a convenience but
56
+ * MUST NOT leak the bearer Authorization header to a different origin. Throws
57
+ * `ConfigurationError SDK_TRANSPORT_CROSS_ORIGIN` when origins diverge.
58
+ *
59
+ * Returns the validated absolute URL unchanged on success.
60
+ */
61
+ export function assertSameOriginAbsoluteUrl(absoluteUrl, originReferenceUrl) {
62
+ let parsed;
63
+ try {
64
+ parsed = new URL(absoluteUrl);
65
+ }
66
+ catch (cause) {
67
+ throw new ConfigurationError(`Invalid absolute transport URL: ${absoluteUrl}`, {
68
+ code: 'SDK_TRANSPORT_URL_INVALID',
69
+ source: 'transport',
70
+ cause,
71
+ });
72
+ }
73
+ let referenceOrigin;
74
+ try {
75
+ referenceOrigin = new URL(originReferenceUrl).origin;
76
+ }
77
+ catch (cause) {
78
+ throw new ConfigurationError(`Invalid transport origin reference URL: ${originReferenceUrl}`, {
79
+ code: 'SDK_TRANSPORT_URL_INVALID',
80
+ source: 'transport',
81
+ cause,
82
+ });
83
+ }
84
+ if (parsed.origin !== referenceOrigin) {
85
+ throw new ConfigurationError(`Cross-origin transport request rejected: ${parsed.origin} does not match transport baseUrl origin ${referenceOrigin}`, {
86
+ code: 'SDK_TRANSPORT_CROSS_ORIGIN',
87
+ source: 'transport',
88
+ hint: 'The transport bearer token would be sent to a different origin. Use a same-origin URL or open a separate transport for the cross-origin endpoint.',
89
+ });
90
+ }
91
+ return absoluteUrl;
12
92
  }
13
93
  export function createTransportPaths(baseUrl) {
14
94
  const normalized = normalizeBaseUrl(baseUrl);
@@ -39,5 +119,6 @@ export function createTransportPaths(baseUrl) {
39
119
  peerRequestsUrl: buildUrl(normalized, '/api/remote/pair/requests'),
40
120
  peerListUrl: buildUrl(normalized, '/api/remote/peers'),
41
121
  remoteWorkUrl: buildUrl(normalized, '/api/remote/work'),
122
+ controlUrl: buildUrl(normalized, '/api/control-plane'),
42
123
  };
43
124
  }
@@ -1,6 +1,6 @@
1
1
  import { type BackoffPolicy, type ResolvedBackoffPolicy } from './backoff.js';
2
2
  export interface StreamReconnectPolicy extends BackoffPolicy {
3
- readonly enabled?: boolean;
3
+ readonly enabled?: boolean | undefined;
4
4
  }
5
5
  export interface ResolvedStreamReconnectPolicy extends ResolvedBackoffPolicy {
6
6
  readonly enabled: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"reconnect.d.ts","sourceRoot":"","sources":["../src/reconnect.ts"],"names":[],"mappings":"AAAA,OAAO,EAA+C,KAAK,aAAa,EAAE,KAAK,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE3H,MAAM,WAAW,qBAAsB,SAAQ,aAAa;IAC1D,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,6BAA8B,SAAQ,qBAAqB;IAC1E,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,gGAAgG;AAChG,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAE9C,eAAO,MAAM,+BAA+B,EAAE,6BAM7C,CAAC;AAEF,wBAAgB,8BAA8B,CAC5C,MAAM,CAAC,EAAE,qBAAqB,GAC7B,6BAA6B,CAM/B;AAED,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,6BAA6B,GACpC,MAAM,CAER"}
1
+ {"version":3,"file":"reconnect.d.ts","sourceRoot":"","sources":["../src/reconnect.ts"],"names":[],"mappings":"AAAA,OAAO,EAA+C,KAAK,aAAa,EAAE,KAAK,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE3H,MAAM,WAAW,qBAAsB,SAAQ,aAAa;IAC1D,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACxC;AAED,MAAM,WAAW,6BAA8B,SAAQ,qBAAqB;IAC1E,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,gGAAgG;AAChG,eAAO,MAAM,2BAA2B,KAAK,CAAC;AAE9C,eAAO,MAAM,+BAA+B,EAAE,6BAM7C,CAAC;AAEF,wBAAgB,8BAA8B,CAC5C,MAAM,CAAC,EAAE,qBAAqB,GAC7B,6BAA6B,CAM/B;AAED,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,6BAA6B,GACpC,MAAM,CAER"}
package/dist/retry.d.ts CHANGED
@@ -1,16 +1,16 @@
1
1
  import { type BackoffPolicy, type ResolvedBackoffPolicy } from './backoff.js';
2
2
  export interface PerMethodRetryPolicy {
3
- readonly maxAttempts?: number;
4
- readonly baseDelayMs?: number;
5
- readonly maxDelayMs?: number;
6
- readonly backoffFactor?: number;
3
+ readonly maxAttempts?: number | undefined;
4
+ readonly baseDelayMs?: number | undefined;
5
+ readonly maxDelayMs?: number | undefined;
6
+ readonly backoffFactor?: number | undefined;
7
7
  }
8
8
  export interface HttpRetryPolicy extends BackoffPolicy {
9
- readonly retryOnStatuses?: readonly number[];
10
- readonly retryOnMethods?: readonly string[];
11
- readonly retryOnNetworkError?: boolean;
9
+ readonly retryOnStatuses?: readonly number[] | undefined;
10
+ readonly retryOnMethods?: readonly string[] | undefined;
11
+ readonly retryOnNetworkError?: boolean | undefined;
12
12
  /** Per-method retry policy overrides keyed by method ID. */
13
- readonly perMethodPolicy?: Readonly<Record<string, PerMethodRetryPolicy>>;
13
+ readonly perMethodPolicy?: Readonly<Record<string, PerMethodRetryPolicy>> | undefined;
14
14
  }
15
15
  export interface ResolvedHttpRetryPolicy extends ResolvedBackoffPolicy {
16
16
  readonly retryOnStatuses: readonly number[];
@@ -1 +1 @@
1
- {"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AACA,OAAO,EAA+C,KAAK,aAAa,EAAE,KAAK,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE3H,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IACvC,4DAA4D;IAC5D,QAAQ,CAAC,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC;CAC3E;AAED,MAAM,WAAW,uBAAwB,SAAQ,qBAAqB;IACpE,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;IACtC,4DAA4D;IAC5D,QAAQ,CAAC,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC;CAC1E;AAED,eAAO,MAAM,yBAAyB,EAAE,uBASvC,CAAC;AAEF,wBAAgB,wBAAwB,CACtC,MAAM,CAAC,EAAE,eAAe,GACvB,uBAAuB,CASzB;AAED,wBAAgB,sBAAsB,CACpC,aAAa,CAAC,EAAE,eAAe,EAC/B,QAAQ,CAAC,EAAE,KAAK,GAAG,eAAe,GACjC,uBAAuB,CAczB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,uBAAuB,EAC7B,QAAQ,EAAE,MAAM,GACf,uBAAuB,CAUzB;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,uBAAuB,GAC9B,MAAM,CAER;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAGT;AAED,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAET"}
1
+ {"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AACA,OAAO,EAA+C,KAAK,aAAa,EAAE,KAAK,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAE3H,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7C;AAED,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACzD,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACxD,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACnD,4DAA4D;IAC5D,QAAQ,CAAC,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,GAAG,SAAS,CAAC;CACvF;AAED,MAAM,WAAW,uBAAwB,SAAQ,qBAAqB;IACpE,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;IACtC,4DAA4D;IAC5D,QAAQ,CAAC,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC;CAC1E;AAED,eAAO,MAAM,yBAAyB,EAAE,uBASvC,CAAC;AAEF,wBAAgB,wBAAwB,CACtC,MAAM,CAAC,EAAE,eAAe,GACvB,uBAAuB,CASzB;AAED,wBAAgB,sBAAsB,CACpC,aAAa,CAAC,EAAE,eAAe,EAC/B,QAAQ,CAAC,EAAE,KAAK,GAAG,eAAe,GACjC,uBAAuB,CAczB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,uBAAuB,EAC7B,QAAQ,EAAE,MAAM,GACf,uBAAuB,CAUzB;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,uBAAuB,GAC9B,MAAM,CAER;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAGT;AAED,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,uBAAuB,GAC9B,OAAO,CAET"}
@@ -2,26 +2,26 @@ import { type AuthTokenResolver } from './auth.js';
2
2
  import { type StreamReconnectPolicy } from './reconnect.js';
3
3
  import { isAbortError } from '@pellux/goodvibes-transport-core';
4
4
  export interface ServerSentEventHandlers {
5
- readonly onEvent?: (eventName: string, payload: unknown) => void;
6
- readonly onReady?: (payload: unknown) => void;
7
- readonly onError?: (error: unknown) => void;
5
+ readonly onEvent?: ((eventName: string, payload: unknown) => void) | undefined;
6
+ readonly onReady?: ((payload: unknown) => void) | undefined;
7
+ readonly onError?: ((error: unknown) => void) | undefined;
8
8
  readonly onReconnect?: (input: {
9
9
  readonly attempt: number;
10
10
  readonly delayMs: number;
11
11
  }) => void;
12
- readonly onClose?: () => void;
12
+ readonly onClose?: (() => void) | undefined;
13
13
  readonly onTerminate?: (input: {
14
14
  readonly error: unknown;
15
15
  readonly reconnectAttempts: number;
16
16
  }) => void;
17
17
  }
18
18
  export interface ServerSentEventOptions {
19
- readonly signal?: AbortSignal;
20
- readonly headers?: HeadersInit;
21
- readonly authToken?: string | null;
22
- readonly getAuthToken?: AuthTokenResolver;
23
- readonly lastEventId?: string | null;
24
- readonly reconnect?: StreamReconnectPolicy;
19
+ readonly signal?: AbortSignal | undefined;
20
+ readonly headers?: HeadersInit | undefined;
21
+ readonly authToken?: string | null | undefined;
22
+ readonly getAuthToken?: AuthTokenResolver | undefined;
23
+ readonly lastEventId?: string | null | undefined;
24
+ readonly reconnect?: StreamReconnectPolicy | undefined;
25
25
  }
26
26
  export { isAbortError };
27
27
  export { openRawServerSentEventStream as openServerSentEventStream };
@@ -1 +1 @@
1
- {"version":3,"file":"sse-stream.d.ts","sourceRoot":"","sources":["../src/sse-stream.ts"],"names":[],"mappings":"AACA,OAAO,EAAkC,KAAK,iBAAiB,EAAE,MAAM,WAAW,CAAC;AACnF,OAAO,EAGL,KAAK,qBAAqB,EAC3B,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAGhE,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACjE,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9C,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAC5C,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC/F,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAC9B,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACzG;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC;IAC/B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,YAAY,CAAC,EAAE,iBAAiB,CAAC;IAC1C,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,SAAS,CAAC,EAAE,qBAAqB,CAAC;CAC5C;AAYD,OAAO,EAAE,YAAY,EAAE,CAAC;AAwCxB,OAAO,EAAE,4BAA4B,IAAI,yBAAyB,EAAE,CAAC;AAMrE,wBAAsB,4BAA4B,CAChD,SAAS,EAAE,OAAO,KAAK,EACvB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,uBAAuB,EACjC,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,MAAM,IAAI,CAAC,CAyLrB"}
1
+ {"version":3,"file":"sse-stream.d.ts","sourceRoot":"","sources":["../src/sse-stream.ts"],"names":[],"mappings":"AACA,OAAO,EAAkC,KAAK,iBAAiB,EAAE,MAAM,WAAW,CAAC;AACnF,OAAO,EAGL,KAAK,qBAAqB,EAC3B,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAC;AAGhE,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAC/E,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAC5D,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC;IAC1D,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC/F,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;IAC5C,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACzG;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAC/C,QAAQ,CAAC,YAAY,CAAC,EAAE,iBAAiB,GAAG,SAAS,CAAC;IACtD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACjD,QAAQ,CAAC,SAAS,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;CACxD;AAWD,OAAO,EAAE,YAAY,EAAE,CAAC;AAwCxB,OAAO,EAAE,4BAA4B,IAAI,yBAAyB,EAAE,CAAC;AAMrE,wBAAsB,4BAA4B,CAChD,SAAS,EAAE,OAAO,KAAK,EACvB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,uBAAuB,EACjC,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,MAAM,IAAI,CAAC,CA6LrB"}
@@ -4,13 +4,12 @@ import { getStreamReconnectDelay, normalizeStreamReconnectPolicy, } from './reco
4
4
  import { createHttpStatusError, HttpStatusError } from '@pellux/goodvibes-errors';
5
5
  import { isAbortError } from '@pellux/goodvibes-transport-core';
6
6
  function readEventPayload(data) {
7
- if (!data.trim())
7
+ if (!data.trimEnd())
8
8
  return null;
9
9
  try {
10
10
  return JSON.parse(data);
11
11
  }
12
- catch (error) {
13
- void error;
12
+ catch {
14
13
  return data;
15
14
  }
16
15
  }
@@ -137,18 +136,18 @@ export async function openRawServerSentEventStream(fetchImpl, url, handlers, opt
137
136
  if (line.startsWith(':'))
138
137
  return;
139
138
  if (line.startsWith('id:')) {
140
- const candidate = line.slice(3).trim();
139
+ const candidate = line.slice(3).replace(/^ /, '');
141
140
  if (candidate) {
142
141
  lastEventId = candidate;
143
142
  }
144
143
  return;
145
144
  }
146
145
  if (line.startsWith('event:')) {
147
- eventName = line.slice(6).trim();
146
+ eventName = line.slice(6).replace(/^ /, '');
148
147
  return;
149
148
  }
150
149
  if (line.startsWith('data:')) {
151
- data += `${data ? '\n' : ''}${line.slice(5).trim()}`;
150
+ data += `${data ? '\n' : ''}${line.slice(5).replace(/^ /, '')}`;
152
151
  }
153
152
  };
154
153
  try {
@@ -169,6 +168,10 @@ export async function openRawServerSentEventStream(fetchImpl, url, handlers, opt
169
168
  consumeLine(buffer.replace(/\r$/, ''));
170
169
  flush();
171
170
  }
171
+ // Abort and remote-close can land in the same microtask. Yield once so
172
+ // the abort listener can mark the stream stopped before reconnect logic
173
+ // treats the close as unexpected.
174
+ await Promise.resolve();
172
175
  if (reconnectPolicy.enabled && !controller.signal.aborted && !outerController.signal.aborted && !stopped) {
173
176
  throw createStreamError(response.status, url, 'Stream closed unexpectedly');
174
177
  }
package/dist/sse.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../src/sse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgC,KAAK,uBAAuB,EAAE,KAAK,sBAAsB,IAAI,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AACxJ,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C,YAAY,EAAE,uBAAuB,EAAE,CAAC;AACxC,MAAM,WAAW,sBAAuB,SAAQ,IAAI,CAAC,0BAA0B,EAAE,WAAW,CAAC;CAAG;AAEhG,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,aAAa,EACxB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,uBAAuB,EACjC,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,MAAM,IAAI,CAAC,CASrB"}
1
+ {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../src/sse.ts"],"names":[],"mappings":"AACA,OAAO,EAAgC,KAAK,uBAAuB,EAAE,KAAK,sBAAsB,IAAI,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AACxJ,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE/C,YAAY,EAAE,uBAAuB,EAAE,CAAC;AACxC,MAAM,WAAW,sBAAuB,SAAQ,IAAI,CAAC,0BAA0B,EAAE,WAAW,CAAC;CAAG;AAEhG,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,aAAa,EACxB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,uBAAuB,EACjC,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,MAAM,IAAI,CAAC,CASrB"}
package/dist/sse.js CHANGED
@@ -1,7 +1,8 @@
1
+ import { assertSameOriginAbsoluteUrl } from './paths.js';
1
2
  import { openRawServerSentEventStream } from './sse-stream.js';
2
3
  export async function openServerSentEventStream(transport, pathOrUrl, handlers, options = {}) {
3
4
  const url = pathOrUrl.startsWith('http://') || pathOrUrl.startsWith('https://')
4
- ? pathOrUrl
5
+ ? assertSameOriginAbsoluteUrl(pathOrUrl, transport.buildUrl('/'))
5
6
  : transport.buildUrl(pathOrUrl);
6
7
  return await openRawServerSentEventStream(transport.fetchImpl, url, handlers, {
7
8
  ...options,
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-transport-http",
3
- "version": "0.30.2",
3
+ "version": "0.33.0",
4
4
  "engines": {
5
- "node": ">=20.0.0"
5
+ "bun": "1.3.10",
6
+ "node": ">=22.0.0"
6
7
  },
7
8
  "description": "HTTP, JSON, path, and SSE transport primitives for GoodVibes client integrations.",
8
9
  "type": "module",
@@ -80,8 +81,9 @@
80
81
  "transport"
81
82
  ],
82
83
  "dependencies": {
83
- "@pellux/goodvibes-errors": "0.30.2",
84
- "@pellux/goodvibes-transport-core": "0.30.2",
84
+ "@pellux/goodvibes-contracts": "0.33.0",
85
+ "@pellux/goodvibes-errors": "0.33.0",
86
+ "@pellux/goodvibes-transport-core": "0.33.0",
85
87
  "zod": "^4.3.6"
86
88
  },
87
89
  "publishConfig": {