@superblocksteam/sdk-api 2.0.119 → 2.0.120-next.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.
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Tests for GraphQLClientImpl.
3
+ *
4
+ * Validates:
5
+ * - The proto request built by query()/mutation() matches the
6
+ * graphql.v1.Plugin shape expected by the orchestrator.
7
+ * - Optional per-request headers are forwarded as the proto's repeated
8
+ * `headers` field (key/value Property entries) so dynamic values like
9
+ * Authorization tokens can be sent from API code.
10
+ * - Variables are serialized into the proto `custom.variables` Property.
11
+ * - Trace metadata is passed through to the executeQuery callback.
12
+ * - Response Zod validation throws RestApiValidationError on mismatch.
13
+ */
14
+ import { describe, it, expect, vi } from "vitest";
15
+ import { z } from "zod";
16
+ import { RestApiValidationError } from "../../errors.js";
17
+ import { GraphQLClientImpl } from "./client.js";
18
+ const TEST_CONFIG = {
19
+ id: "graphql-test-id",
20
+ name: "Test GraphQL",
21
+ pluginId: "graphqlintegration",
22
+ configuration: {},
23
+ };
24
+ function createClient(mockResult) {
25
+ const executeQuery = vi.fn().mockResolvedValue(mockResult);
26
+ const client = new GraphQLClientImpl(TEST_CONFIG, executeQuery);
27
+ return { client, executeQuery };
28
+ }
29
+ const UserResponseSchema = z.object({
30
+ data: z.object({
31
+ user: z.object({
32
+ id: z.string(),
33
+ name: z.string(),
34
+ }),
35
+ }),
36
+ });
37
+ const SAMPLE_QUERY = `query GetUser($id: ID!) {
38
+ user(id: $id) { id name }
39
+ }`;
40
+ const SAMPLE_MUTATION = `mutation CreateUser($name: String!) {
41
+ createUser(name: $name) { id name }
42
+ }`;
43
+ describe("GraphQLClientImpl", () => {
44
+ describe("query()", () => {
45
+ it("returns validated data on a successful response", async () => {
46
+ const { client } = createClient({
47
+ data: { user: { id: "u1", name: "Alice" } },
48
+ });
49
+ const result = await client.query(SAMPLE_QUERY, { response: UserResponseSchema }, { id: "u1" });
50
+ expect(result.data.user).toEqual({ id: "u1", name: "Alice" });
51
+ });
52
+ it("builds the proto request with body, variables, and defaults", async () => {
53
+ const { client, executeQuery } = createClient({
54
+ data: { user: { id: "u1", name: "Alice" } },
55
+ });
56
+ await client.query(SAMPLE_QUERY, { response: UserResponseSchema }, { id: "u1" });
57
+ expect(executeQuery).toHaveBeenCalledOnce();
58
+ const request = executeQuery.mock.calls[0][0];
59
+ expect(request.body).toBe(SAMPLE_QUERY);
60
+ expect(request.verboseHttpOutput).toBe(false);
61
+ expect(request.failOnGraphqlErrors).toBe(true);
62
+ expect(request.custom).toEqual({
63
+ variables: {
64
+ key: "variables",
65
+ value: JSON.stringify({ id: "u1" }),
66
+ },
67
+ });
68
+ expect(request.headers).toBeUndefined();
69
+ });
70
+ it("omits custom when no variables are provided", async () => {
71
+ const { client, executeQuery } = createClient({
72
+ data: { user: { id: "u1", name: "Alice" } },
73
+ });
74
+ await client.query(SAMPLE_QUERY, { response: UserResponseSchema });
75
+ const request = executeQuery.mock.calls[0][0];
76
+ expect(request.custom).toBeUndefined();
77
+ });
78
+ it("forwards per-request headers into the proto headers field", async () => {
79
+ const { client, executeQuery } = createClient({
80
+ data: { user: { id: "u1", name: "Alice" } },
81
+ });
82
+ await client.query(SAMPLE_QUERY, { response: UserResponseSchema }, { id: "u1" }, undefined, {
83
+ Authorization: "Bearer abc123",
84
+ "X-Trace-Id": "trace-1",
85
+ });
86
+ const request = executeQuery.mock.calls[0][0];
87
+ expect(request.headers).toEqual([
88
+ { key: "Authorization", value: "Bearer abc123" },
89
+ { key: "X-Trace-Id", value: "trace-1" },
90
+ ]);
91
+ });
92
+ it("does not set headers when an empty headers object is passed", async () => {
93
+ const { client, executeQuery } = createClient({
94
+ data: { user: { id: "u1", name: "Alice" } },
95
+ });
96
+ await client.query(SAMPLE_QUERY, { response: UserResponseSchema }, undefined, undefined, {});
97
+ const request = executeQuery.mock.calls[0][0];
98
+ expect(request.headers).toBeUndefined();
99
+ });
100
+ it("passes trace metadata through to executeQuery", async () => {
101
+ const { client, executeQuery } = createClient({
102
+ data: { user: { id: "u1", name: "Alice" } },
103
+ });
104
+ await client.query(SAMPLE_QUERY, { response: UserResponseSchema }, undefined, { label: "graphql.getUser", description: "Fetch a user by id" });
105
+ expect(executeQuery).toHaveBeenCalledWith(expect.any(Object), undefined, {
106
+ label: "graphql.getUser",
107
+ description: "Fetch a user by id",
108
+ });
109
+ });
110
+ it("throws RestApiValidationError when the response fails schema validation", async () => {
111
+ const { client } = createClient({
112
+ data: { user: { id: "u1" /* missing name */ } },
113
+ });
114
+ await expect(client.query(SAMPLE_QUERY, { response: UserResponseSchema })).rejects.toThrow(RestApiValidationError);
115
+ });
116
+ });
117
+ describe("mutation()", () => {
118
+ it("returns validated data on a successful response", async () => {
119
+ const { client } = createClient({
120
+ data: { createUser: { id: "u2", name: "Bob" } },
121
+ });
122
+ const Schema = z.object({
123
+ data: z.object({
124
+ createUser: z.object({ id: z.string(), name: z.string() }),
125
+ }),
126
+ });
127
+ const result = await client.mutation(SAMPLE_MUTATION, { response: Schema }, { name: "Bob" });
128
+ expect(result.data.createUser).toEqual({ id: "u2", name: "Bob" });
129
+ });
130
+ it("forwards per-request headers into the proto headers field", async () => {
131
+ const { client, executeQuery } = createClient({
132
+ data: { createUser: { id: "u2", name: "Bob" } },
133
+ });
134
+ const Schema = z.object({
135
+ data: z.object({
136
+ createUser: z.object({ id: z.string(), name: z.string() }),
137
+ }),
138
+ });
139
+ await client.mutation(SAMPLE_MUTATION, { response: Schema }, { name: "Bob" }, undefined, { Authorization: "Bearer xyz" });
140
+ const request = executeQuery.mock.calls[0][0];
141
+ expect(request.body).toBe(SAMPLE_MUTATION);
142
+ expect(request.headers).toEqual([
143
+ { key: "Authorization", value: "Bearer xyz" },
144
+ ]);
145
+ });
146
+ });
147
+ });
148
+ //# sourceMappingURL=client.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.test.js","sourceRoot":"","sources":["../../../src/integrations/graphql/client.test.ts"],"names":[],"mappings":"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"}
@@ -59,6 +59,17 @@ import type { TraceMetadata } from "../registry.js";
59
59
  * { response: MutationResponseSchema },
60
60
  * { name: 'John Doe' }
61
61
  * );
62
+ *
63
+ * // Send per-request headers (e.g. a dynamic Authorization token).
64
+ * // Static headers and auth configured on the integration apply automatically;
65
+ * // use this parameter only for values that vary per request.
66
+ * await graphql.query(
67
+ * `query { me { id } }`,
68
+ * { response: z.object({ data: z.object({ me: z.object({ id: z.string() }) }) }) },
69
+ * undefined,
70
+ * undefined,
71
+ * { Authorization: `Bearer ${token}` },
72
+ * );
62
73
  * ```
63
74
  */
64
75
  export interface GraphQLClient extends BaseIntegrationClient {
@@ -71,6 +82,10 @@ export interface GraphQLClient extends BaseIntegrationClient {
71
82
  * `metadata` accept plain objects, pass `undefined` for variables when you only need metadata:
72
83
  * `query(q, schema, undefined, { label: "..." })`
73
84
  * @param metadata - Optional trace metadata for observability
85
+ * @param headers - Optional HTTP headers to include on this request. Static
86
+ * headers and auth configured on the integration apply automatically; use
87
+ * this parameter only for values that vary per request (e.g. a bearer token
88
+ * derived from the API's input).
74
89
  * @returns Validated query result
75
90
  *
76
91
  * @example
@@ -89,7 +104,7 @@ export interface GraphQLClient extends BaseIntegrationClient {
89
104
  */
90
105
  query<TResponse>(query: string, schema: {
91
106
  response: z.ZodSchema<TResponse>;
92
- }, variables?: Record<string, unknown>, metadata?: TraceMetadata): Promise<TResponse>;
107
+ }, variables?: Record<string, unknown>, metadata?: TraceMetadata, headers?: Record<string, string>): Promise<TResponse>;
93
108
  /**
94
109
  * Execute a GraphQL mutation.
95
110
  *
@@ -99,6 +114,10 @@ export interface GraphQLClient extends BaseIntegrationClient {
99
114
  * `metadata` accept plain objects, pass `undefined` for variables when you only need metadata:
100
115
  * `mutation(m, schema, undefined, { label: "..." })`
101
116
  * @param metadata - Optional trace metadata for observability
117
+ * @param headers - Optional HTTP headers to include on this request. Static
118
+ * headers and auth configured on the integration apply automatically; use
119
+ * this parameter only for values that vary per request (e.g. a bearer token
120
+ * derived from the API's input).
102
121
  * @returns Validated mutation result
103
122
  *
104
123
  * @example
@@ -118,6 +137,6 @@ export interface GraphQLClient extends BaseIntegrationClient {
118
137
  */
119
138
  mutation<TResponse>(mutation: string, schema: {
120
139
  response: z.ZodSchema<TResponse>;
121
- }, variables?: Record<string, unknown>, metadata?: TraceMetadata): Promise<TResponse>;
140
+ }, variables?: Record<string, unknown>, metadata?: TraceMetadata, headers?: Record<string, string>): Promise<TResponse>;
122
141
  }
123
142
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/integrations/graphql/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AACH,MAAM,WAAW,aAAc,SAAQ,qBAAqB;IAC1D;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,KAAK,CAAC,SAAS,EACb,KAAK,EAAE,MAAM,EACb,MAAM,EAAE;QAAE,QAAQ,EAAE,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAA;KAAE,EAC5C,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,QAAQ,CAAC,EAAE,aAAa,GACvB,OAAO,CAAC,SAAS,CAAC,CAAC;IAEtB;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,QAAQ,CAAC,SAAS,EAChB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE;QAAE,QAAQ,EAAE,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAA;KAAE,EAC5C,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,QAAQ,CAAC,EAAE,aAAa,GACvB,OAAO,CAAC,SAAS,CAAC,CAAC;CACvB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/integrations/graphql/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmEG;AACH,MAAM,WAAW,aAAc,SAAQ,qBAAqB;IAC1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,KAAK,CAAC,SAAS,EACb,KAAK,EAAE,MAAM,EACb,MAAM,EAAE;QAAE,QAAQ,EAAE,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAA;KAAE,EAC5C,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,QAAQ,CAAC,EAAE,aAAa,EACxB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,OAAO,CAAC,SAAS,CAAC,CAAC;IAEtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,QAAQ,CAAC,SAAS,EAChB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE;QAAE,QAAQ,EAAE,CAAC,CAAC,SAAS,CAAC,SAAS,CAAC,CAAA;KAAE,EAC5C,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,QAAQ,CAAC,EAAE,aAAa,EACxB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,OAAO,CAAC,SAAS,CAAC,CAAC;CACvB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superblocksteam/sdk-api",
3
- "version": "2.0.119",
3
+ "version": "2.0.120-next.0",
4
4
  "description": "Superblocks SDK for TypeScript-based API definitions",
5
5
  "license": "Superblocks Community Software License",
6
6
  "files": [
@@ -28,8 +28,8 @@
28
28
  "eslint": "^9.39.1",
29
29
  "eslint-config-prettier": "^10.1.8",
30
30
  "tsx": "^4.19.0",
31
- "typescript": "^5.9.3",
32
- "typescript-eslint": "^8.47.0",
31
+ "typescript": "^6.0.3",
32
+ "typescript-eslint": "^8.59.2",
33
33
  "uuid": "^9.0.0",
34
34
  "vitest": "^2.0.0"
35
35
  },
@@ -5,8 +5,11 @@
5
5
  * Handles proto message construction and response validation.
6
6
  */
7
7
 
8
+ import type { PartialMessage } from "@bufbuild/protobuf";
8
9
  import { z } from "zod";
9
10
 
11
+ import type { Property } from "@superblocksteam/types/dist/src/common/v1/plugin_pb";
12
+
10
13
  import { RestApiValidationError } from "../../errors.js";
11
14
  import type { QueryExecutor, TraceMetadata } from "../registry.js";
12
15
  import type { IntegrationConfig, IntegrationClientImpl } from "../types.js";
@@ -19,6 +22,8 @@ import type { IntegrationConfig, IntegrationClientImpl } from "../types.js";
19
22
  * - Executing queries and mutations through the orchestrator
20
23
  * - Required Zod schema validation on full GraphQL response
21
24
  * - Variables serialization for parameterized queries
25
+ * - Optional per-request HTTP headers (in addition to any headers configured
26
+ * on the integration)
22
27
  */
23
28
  export abstract class GraphQLIntegrationClient implements IntegrationClientImpl {
24
29
  readonly name: string;
@@ -40,6 +45,7 @@ export abstract class GraphQLIntegrationClient implements IntegrationClientImpl
40
45
  * @param schema - Zod schema for full response validation (REQUIRED)
41
46
  * @param variables - Optional variables for the query
42
47
  * @param metadata - Optional trace metadata for observability
48
+ * @param headers - Optional HTTP headers to send with the request
43
49
  * @returns Validated query result
44
50
  */
45
51
  async query<TResponse>(
@@ -47,8 +53,9 @@ export abstract class GraphQLIntegrationClient implements IntegrationClientImpl
47
53
  schema: { response: z.ZodSchema<TResponse> },
48
54
  variables?: Record<string, unknown>,
49
55
  metadata?: TraceMetadata,
56
+ headers?: Record<string, string>,
50
57
  ): Promise<TResponse> {
51
- return this.executeGraphQL(query, schema, variables, metadata);
58
+ return this.executeGraphQL(query, schema, variables, metadata, headers);
52
59
  }
53
60
 
54
61
  /**
@@ -58,6 +65,7 @@ export abstract class GraphQLIntegrationClient implements IntegrationClientImpl
58
65
  * @param schema - Zod schema for full response validation (REQUIRED)
59
66
  * @param variables - Optional variables for the mutation
60
67
  * @param metadata - Optional trace metadata for observability
68
+ * @param headers - Optional HTTP headers to send with the request
61
69
  * @returns Validated mutation result
62
70
  */
63
71
  async mutation<TResponse>(
@@ -65,8 +73,9 @@ export abstract class GraphQLIntegrationClient implements IntegrationClientImpl
65
73
  schema: { response: z.ZodSchema<TResponse> },
66
74
  variables?: Record<string, unknown>,
67
75
  metadata?: TraceMetadata,
76
+ headers?: Record<string, string>,
68
77
  ): Promise<TResponse> {
69
- return this.executeGraphQL(mutation, schema, variables, metadata);
78
+ return this.executeGraphQL(mutation, schema, variables, metadata, headers);
70
79
  }
71
80
 
72
81
  /**
@@ -80,9 +89,17 @@ export abstract class GraphQLIntegrationClient implements IntegrationClientImpl
80
89
  schema: { response: z.ZodSchema<TResponse> },
81
90
  variables?: Record<string, unknown>,
82
91
  metadata?: TraceMetadata,
92
+ headers?: Record<string, string>,
83
93
  ): Promise<TResponse> {
94
+ const headerProps: PartialMessage<Property>[] = [];
95
+ if (headers) {
96
+ for (const [key, value] of Object.entries(headers)) {
97
+ headerProps.push({ key, value });
98
+ }
99
+ }
100
+
84
101
  // Build graphql.v1.Plugin proto message
85
- const request = {
102
+ const request: Record<string, unknown> = {
86
103
  body: queryString,
87
104
  custom: variables
88
105
  ? {
@@ -96,6 +113,10 @@ export abstract class GraphQLIntegrationClient implements IntegrationClientImpl
96
113
  failOnGraphqlErrors: true,
97
114
  };
98
115
 
116
+ if (headerProps.length > 0) {
117
+ request.headers = headerProps;
118
+ }
119
+
99
120
  // Execute query through orchestrator
100
121
  const response = await this.executeQuery(request, undefined, metadata);
101
122
 
@@ -659,7 +659,27 @@ describe("resolveIntegrationDocumentation", () => {
659
659
  }
660
660
  });
661
661
 
662
- it("throws when overlay entry has empty versionRange", async () => {
662
+ it("throws when overlay entry has neither versionRange nor sdkVersionRange", async () => {
663
+ const integrationsDirectory = createPluginDocsFixture("dropbox", {
664
+ "README.md": "base",
665
+ "docs.manifest.json": JSON.stringify({
666
+ overlays: [{ file: "overlays/01.md" }],
667
+ }),
668
+ });
669
+
670
+ try {
671
+ await expect(
672
+ resolveIntegrationDocumentation("dropbox", {
673
+ pluginVersion: "0.4.0",
674
+ integrationsDirectory,
675
+ }),
676
+ ).rejects.toThrowError(/Invalid overlay entry/);
677
+ } finally {
678
+ rmSync(integrationsDirectory, { recursive: true, force: true });
679
+ }
680
+ });
681
+
682
+ it("throws when overlay entry has empty versionRange and no sdkVersionRange", async () => {
663
683
  const integrationsDirectory = createPluginDocsFixture("dropbox", {
664
684
  "README.md": "base",
665
685
  "docs.manifest.json": JSON.stringify({
@@ -680,6 +700,144 @@ describe("resolveIntegrationDocumentation", () => {
680
700
  });
681
701
  });
682
702
 
703
+ describe("sdkVersionRange", () => {
704
+ it("applies overlay when sdkVersion matches sdkVersionRange", async () => {
705
+ const integrationsDirectory = createPluginDocsFixture("graphql", {
706
+ "README.md": "base-graphql-docs",
707
+ "docs.manifest.json": JSON.stringify({
708
+ overlays: [
709
+ { file: "overlays/headers.md", sdkVersionRange: ">=0.0.2" },
710
+ ],
711
+ }),
712
+ "overlays/headers.md": "dynamic-headers-docs",
713
+ });
714
+
715
+ try {
716
+ const docs = await resolveIntegrationDocumentation("graphql", {
717
+ sdkVersion: "0.0.2",
718
+ integrationsDirectory,
719
+ });
720
+ expect(docs).toBe("base-graphql-docs\n\ndynamic-headers-docs");
721
+ } finally {
722
+ rmSync(integrationsDirectory, { recursive: true, force: true });
723
+ }
724
+ });
725
+
726
+ it("skips overlay when sdkVersion does not match sdkVersionRange", async () => {
727
+ const integrationsDirectory = createPluginDocsFixture("graphql", {
728
+ "README.md": "base-graphql-docs",
729
+ "docs.manifest.json": JSON.stringify({
730
+ overlays: [
731
+ { file: "overlays/headers.md", sdkVersionRange: ">=0.0.2" },
732
+ ],
733
+ }),
734
+ "overlays/headers.md": "dynamic-headers-docs",
735
+ });
736
+
737
+ try {
738
+ const docs = await resolveIntegrationDocumentation("graphql", {
739
+ sdkVersion: "0.0.1",
740
+ integrationsDirectory,
741
+ });
742
+ expect(docs).toBe("base-graphql-docs");
743
+ } finally {
744
+ rmSync(integrationsDirectory, { recursive: true, force: true });
745
+ }
746
+ });
747
+
748
+ it("skips overlay when sdkVersion is not provided", async () => {
749
+ const integrationsDirectory = createPluginDocsFixture("graphql", {
750
+ "README.md": "base-graphql-docs",
751
+ "docs.manifest.json": JSON.stringify({
752
+ overlays: [
753
+ { file: "overlays/headers.md", sdkVersionRange: ">=0.0.2" },
754
+ ],
755
+ }),
756
+ "overlays/headers.md": "dynamic-headers-docs",
757
+ });
758
+
759
+ try {
760
+ const docs = await resolveIntegrationDocumentation("graphql", {
761
+ pluginVersion: "0.0.10",
762
+ integrationsDirectory,
763
+ });
764
+ expect(docs).toBe("base-graphql-docs");
765
+ } finally {
766
+ rmSync(integrationsDirectory, { recursive: true, force: true });
767
+ }
768
+ });
769
+
770
+ it("requires both versionRange and sdkVersionRange to match when both are specified", async () => {
771
+ const integrationsDirectory = createPluginDocsFixture("graphql", {
772
+ "README.md": "base",
773
+ "docs.manifest.json": JSON.stringify({
774
+ overlays: [
775
+ {
776
+ file: "overlays/both.md",
777
+ versionRange: ">=0.0.10",
778
+ sdkVersionRange: ">=0.0.2",
779
+ },
780
+ ],
781
+ }),
782
+ "overlays/both.md": "both-match-overlay",
783
+ });
784
+
785
+ try {
786
+ // Both match
787
+ expect(
788
+ await resolveIntegrationDocumentation("graphql", {
789
+ pluginVersion: "0.0.10",
790
+ sdkVersion: "0.0.2",
791
+ integrationsDirectory,
792
+ }),
793
+ ).toBe("base\n\nboth-match-overlay");
794
+
795
+ // Only plugin matches
796
+ expect(
797
+ await resolveIntegrationDocumentation("graphql", {
798
+ pluginVersion: "0.0.10",
799
+ sdkVersion: "0.0.1",
800
+ integrationsDirectory,
801
+ }),
802
+ ).toBe("base");
803
+
804
+ // Only sdk matches
805
+ expect(
806
+ await resolveIntegrationDocumentation("graphql", {
807
+ pluginVersion: "0.0.9",
808
+ sdkVersion: "0.0.2",
809
+ integrationsDirectory,
810
+ }),
811
+ ).toBe("base");
812
+ } finally {
813
+ rmSync(integrationsDirectory, { recursive: true, force: true });
814
+ }
815
+ });
816
+
817
+ it("allows overlay with only sdkVersionRange (no versionRange)", async () => {
818
+ const integrationsDirectory = createPluginDocsFixture("graphql", {
819
+ "README.md": "base",
820
+ "docs.manifest.json": JSON.stringify({
821
+ overlays: [
822
+ { file: "overlays/sdk-only.md", sdkVersionRange: ">=0.0.2" },
823
+ ],
824
+ }),
825
+ "overlays/sdk-only.md": "sdk-gated-overlay",
826
+ });
827
+
828
+ try {
829
+ // No pluginVersion needed — only sdkVersion matters
830
+ const docs = await resolveIntegrationDocumentation("graphql", {
831
+ sdkVersion: "0.0.3",
832
+ integrationsDirectory,
833
+ });
834
+ expect(docs).toBe("base\n\nsdk-gated-overlay");
835
+ } finally {
836
+ rmSync(integrationsDirectory, { recursive: true, force: true });
837
+ }
838
+ });
839
+ });
840
+
683
841
  it("blocks overlay paths outside the plugin directory", async () => {
684
842
  const integrationsDirectory = createPluginDocsFixture("dropbox", {
685
843
  "README.md": "base",
@@ -11,7 +11,13 @@ const DOC_DIRECTORY_ALIASES = {
11
11
 
12
12
  interface DocumentationOverlay {
13
13
  file: string;
14
- versionRange: string;
14
+ versionRange?: string;
15
+ /** When set, checks against the sdk-api version ("javascriptsdkapi") running
16
+ * in the orchestrator instead of the plugin-specific version. Use this to
17
+ * gate overlays on sdk-api features that ship independently of plugin
18
+ * version bumps. Both versionRange and sdkVersionRange can be specified
19
+ * together — the overlay is applied only when all specified ranges match. */
20
+ sdkVersionRange?: string;
15
21
  }
16
22
 
17
23
  interface DocumentationManifest {
@@ -32,6 +38,7 @@ type OverlayOperation =
32
38
 
33
39
  export interface ResolveIntegrationDocumentationOptions {
34
40
  pluginVersion?: SemVer;
41
+ sdkVersion?: SemVer;
35
42
  integrationsDirectory?: string;
36
43
  }
37
44
 
@@ -82,16 +89,17 @@ async function parseDocumentationManifest(
82
89
  typeof overlay !== "object" ||
83
90
  overlay === null ||
84
91
  !("file" in overlay) ||
85
- !("versionRange" in overlay)
92
+ (!("versionRange" in overlay) && !("sdkVersionRange" in overlay))
86
93
  ) {
87
94
  throw new Error(
88
95
  `Invalid overlay entry at index ${index} in ${manifestPath}.`,
89
96
  );
90
97
  }
91
98
 
92
- const { file, versionRange } = overlay as {
99
+ const { file, versionRange, sdkVersionRange } = overlay as {
93
100
  file: unknown;
94
101
  versionRange: unknown;
102
+ sdkVersionRange: unknown;
95
103
  };
96
104
 
97
105
  if (typeof file !== "string" || file.trim().length === 0) {
@@ -99,18 +107,35 @@ async function parseDocumentationManifest(
99
107
  `Invalid "file" for overlay index ${index} in ${manifestPath}.`,
100
108
  );
101
109
  }
102
- if (
103
- typeof versionRange !== "string" ||
104
- versionRange.trim().length === 0
105
- ) {
110
+
111
+ const hasVersionRange =
112
+ typeof versionRange === "string" && versionRange.trim().length > 0;
113
+ const hasSdkVersionRange =
114
+ typeof sdkVersionRange === "string" &&
115
+ sdkVersionRange.trim().length > 0;
116
+
117
+ if ("versionRange" in overlay && !hasVersionRange) {
106
118
  throw new Error(
107
119
  `Invalid "versionRange" for overlay index ${index} in ${manifestPath}.`,
108
120
  );
109
121
  }
122
+ if ("sdkVersionRange" in overlay && !hasSdkVersionRange) {
123
+ throw new Error(
124
+ `Invalid "sdkVersionRange" for overlay index ${index} in ${manifestPath}.`,
125
+ );
126
+ }
127
+ if (!hasVersionRange && !hasSdkVersionRange) {
128
+ throw new Error(
129
+ `Overlay at index ${index} in ${manifestPath} must specify at least one of "versionRange" or "sdkVersionRange".`,
130
+ );
131
+ }
110
132
 
111
133
  return {
112
134
  file,
113
- versionRange,
135
+ ...(hasVersionRange ? { versionRange: versionRange as string } : {}),
136
+ ...(hasSdkVersionRange
137
+ ? { sdkVersionRange: sdkVersionRange as string }
138
+ : {}),
114
139
  };
115
140
  },
116
141
  );
@@ -380,11 +405,38 @@ function ensureOverlayPathIsWithinPluginDir(
380
405
  }
381
406
  }
382
407
 
408
+ function overlayMatchesVersions(
409
+ overlay: DocumentationOverlay,
410
+ pluginVersion: SemVer | undefined,
411
+ sdkVersion: SemVer | undefined,
412
+ ): boolean {
413
+ if (
414
+ overlay.versionRange &&
415
+ (!pluginVersion ||
416
+ !versionMatchesRange(pluginVersion, overlay.versionRange))
417
+ ) {
418
+ return false;
419
+ }
420
+
421
+ if (
422
+ overlay.sdkVersionRange &&
423
+ (!sdkVersion || !versionMatchesRange(sdkVersion, overlay.sdkVersionRange))
424
+ ) {
425
+ return false;
426
+ }
427
+
428
+ return true;
429
+ }
430
+
383
431
  export async function resolveIntegrationDocumentation(
384
432
  pluginId: string,
385
433
  options: ResolveIntegrationDocumentationOptions = {},
386
434
  ): Promise<string> {
387
- const { pluginVersion, integrationsDirectory = INTEGRATIONS_DIR } = options;
435
+ const {
436
+ pluginVersion,
437
+ sdkVersion,
438
+ integrationsDirectory = INTEGRATIONS_DIR,
439
+ } = options;
388
440
 
389
441
  const pluginDirectory = resolve(
390
442
  integrationsDirectory,
@@ -397,13 +449,13 @@ export async function resolveIntegrationDocumentation(
397
449
  const baseDocumentationPath = resolve(pluginDirectory, baseFileName);
398
450
  const baseDocumentation = await readFile(baseDocumentationPath, "utf8");
399
451
 
400
- if (!manifest || !pluginVersion) {
452
+ if (!manifest) {
401
453
  return baseDocumentation;
402
454
  }
403
455
 
404
456
  let resolvedDocumentation = baseDocumentation;
405
457
  for (const overlay of manifest.overlays) {
406
- if (!versionMatchesRange(pluginVersion, overlay.versionRange)) {
458
+ if (!overlayMatchesVersions(overlay, pluginVersion, sdkVersion)) {
407
459
  continue;
408
460
  }
409
461