infrahub-sdk 0.0.1

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.
Files changed (53) hide show
  1. package/.eslintrc.js +18 -0
  2. package/.prettierrc +6 -0
  3. package/LICENSE +201 -0
  4. package/README.md +47 -0
  5. package/dist/branch.d.ts +36 -0
  6. package/dist/branch.js +136 -0
  7. package/dist/cjs/index.js +65 -0
  8. package/dist/cjs/index.js.map +7 -0
  9. package/dist/client.d.ts +6 -0
  10. package/dist/client.js +35 -0
  11. package/dist/constants.d.ts +0 -0
  12. package/dist/constants.js +1 -0
  13. package/dist/esm/index.js +32 -0
  14. package/dist/esm/index.js.map +7 -0
  15. package/dist/graphql.d.ts +31 -0
  16. package/dist/graphql.js +174 -0
  17. package/dist/index.d.ts +47 -0
  18. package/dist/index.js +115 -0
  19. package/dist/index.test.d.ts +1 -0
  20. package/dist/index.test.js +62 -0
  21. package/dist/types/client.d.ts +7 -0
  22. package/dist/types/client.d.ts.map +1 -0
  23. package/dist/types/constants.d.ts +1 -0
  24. package/dist/types/constants.d.ts.map +1 -0
  25. package/dist/types/index.d.ts +2 -0
  26. package/dist/types/index.d.ts.map +1 -0
  27. package/dist/types/utils/auth.d.ts +1 -0
  28. package/dist/types/utils/auth.d.ts.map +1 -0
  29. package/dist/types/utils/error-handling.d.ts +1 -0
  30. package/dist/types/utils/error-handling.d.ts.map +1 -0
  31. package/dist/types/utils/index.d.ts +1 -0
  32. package/dist/types/utils/index.d.ts.map +1 -0
  33. package/dist/types/utils/validation.d.ts +1 -0
  34. package/dist/types/utils/validation.d.ts.map +1 -0
  35. package/dist/types.d.ts +3144 -0
  36. package/dist/types.js +6 -0
  37. package/dist/utils/auth.d.ts +0 -0
  38. package/dist/utils/auth.js +1 -0
  39. package/dist/utils/error-handling.d.ts +0 -0
  40. package/dist/utils/error-handling.js +1 -0
  41. package/dist/utils/index.d.ts +0 -0
  42. package/dist/utils/index.js +1 -0
  43. package/dist/utils/validation.d.ts +0 -0
  44. package/dist/utils/validation.js +1 -0
  45. package/eslint.config.mjs +9 -0
  46. package/jest.config.js +6 -0
  47. package/package.json +33 -0
  48. package/src/branch.ts +161 -0
  49. package/src/graphql.ts +266 -0
  50. package/src/index.test.ts +74 -0
  51. package/src/index.ts +144 -0
  52. package/src/types.ts +3169 -0
  53. package/tsconfig.json +14 -0
package/dist/types.js ADDED
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ /**
3
+ * This file was auto-generated by openapi-typescript.
4
+ * Do not make direct changes to the file.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
File without changes
@@ -0,0 +1 @@
1
+ "use strict";
File without changes
@@ -0,0 +1 @@
1
+ "use strict";
File without changes
@@ -0,0 +1 @@
1
+ "use strict";
File without changes
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1,9 @@
1
+ // @ts-check
2
+
3
+ import eslint from '@eslint/js';
4
+ import tseslint from 'typescript-eslint';
5
+
6
+ export default tseslint.config(
7
+ eslint.configs.recommended,
8
+ tseslint.configs.recommended,
9
+ );
package/jest.config.js ADDED
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ preset: 'ts-jest',
3
+ testEnvironment: 'node',
4
+ testMatch: ['**/*.test.ts'],
5
+ moduleFileExtensions: ['ts', 'js', 'json', 'node'],
6
+ };
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "infrahub-sdk",
3
+ "version": "0.0.1",
4
+ "description": "A client SDK for the Infrahub API.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "test": "jest --config=jest.config.js --runInBand",
10
+ "format": "prettier --write \"src/**/*.{ts,js,json}\"",
11
+ "lint": "eslint --ext .ts,.js src"
12
+ },
13
+ "keywords": [
14
+ "sdk",
15
+ "infrahub"
16
+ ],
17
+ "author": "",
18
+ "license": "Apache-2.0",
19
+ "dependencies": {
20
+ "graphql-request": "^6.1.0",
21
+ "openapi-fetch": "^0.9.4"
22
+ },
23
+ "devDependencies": {
24
+ "@eslint/js": "^9.31.0",
25
+ "eslint": "^9.31.0",
26
+ "eslint-config-prettier": "^10.1.5",
27
+ "eslint-plugin-prettier": "^5.5.1",
28
+ "prettier": "3.6.2",
29
+ "ts-jest": "^29.4.0",
30
+ "typescript": "^5.8.3",
31
+ "typescript-eslint": "^8.37.0"
32
+ }
33
+ }
package/src/branch.ts ADDED
@@ -0,0 +1,161 @@
1
+ import { InfrahubClient, gql } from './index';
2
+ import { Mutation } from './graphql';
3
+
4
+ interface Branch {
5
+ id: string;
6
+ name: string;
7
+ description: string;
8
+ origin_branch: string;
9
+ branched_from: string;
10
+ is_default: boolean;
11
+ sync_with_git: boolean;
12
+ has_schema_changes: boolean;
13
+ }
14
+
15
+ export interface BranchCreateInput {
16
+ name: string;
17
+ description?: string;
18
+ sync_with_git?: boolean;
19
+ wait_until_completion?: boolean;
20
+ }
21
+
22
+ export class BranchNotFoundError extends Error {
23
+ constructor(identifier: string) {
24
+ super(`Branch not found: ${identifier}`);
25
+ this.name = 'BranchNotFoundError';
26
+ }
27
+ }
28
+
29
+ const MUTATION_QUERY_TASK = { ok: null, task: { id: null } };
30
+ const MUTATION_QUERY_DATA = {
31
+ ok: null,
32
+ object: {
33
+ id: null,
34
+ name: null,
35
+ description: null,
36
+ origin_branch: null,
37
+ branched_from: null,
38
+ is_default: null,
39
+ sync_with_git: null,
40
+ has_schema_changes: null
41
+ }
42
+ };
43
+
44
+ // InfrahubBranchManager class to manage branches via GraphQL
45
+ export class InfrahubBranchManager {
46
+ private client: InfrahubClient;
47
+
48
+ constructor(client: InfrahubClient) {
49
+ this.client = client;
50
+ }
51
+
52
+ /**
53
+ * Fetches all branches using GraphQL and returns an object mapping branch names to branch data.
54
+ */
55
+ async all(): Promise<Record<string, Branch>> {
56
+ const query = gql`
57
+ query BranchQuery {
58
+ Branch {
59
+ id
60
+ name
61
+ description
62
+ origin_branch
63
+ branched_from
64
+ is_default
65
+ sync_with_git
66
+ has_schema_changes
67
+ }
68
+ }
69
+ `;
70
+ const response = await this.client.executeGraphQL(query);
71
+ const branches: Branch[] = response.Branch || [];
72
+ const result: Record<string, Branch> = {};
73
+ for (const branch of branches) {
74
+ result[branch.name] = branch;
75
+ }
76
+ return result;
77
+ }
78
+
79
+ /**
80
+ * Fetches a specific branch by name.
81
+ * @param branchName The name of the branch to fetch.
82
+ */
83
+ async get(branchName: string): Promise<Branch | null> {
84
+ const query = gql`
85
+ query BranchQuery($name: String!) {
86
+ Branch(name: $name) {
87
+ id
88
+ name
89
+ description
90
+ origin_branch
91
+ branched_from
92
+ is_default
93
+ sync_with_git
94
+ has_schema_changes
95
+ }
96
+ }
97
+ `;
98
+ const response = await this.client.executeGraphQL(query, {
99
+ name: branchName
100
+ });
101
+ const branchArr: Branch[] = response.Branch || [];
102
+ if (!branchArr.length) {
103
+ throw new BranchNotFoundError(branchName);
104
+ }
105
+ return branchArr[0];
106
+ }
107
+
108
+ async create(input: BranchCreateInput): Promise<Branch> {
109
+ const inputData = {
110
+ background_execution: input.wait_until_completion,
111
+ data: {
112
+ name: input.name,
113
+ description: input.description,
114
+ sync_with_git: input.sync_with_git
115
+ }
116
+ };
117
+
118
+ // set mutation query to MUTATION_QUERY_TASK if brackground_execution is true
119
+ const mutationQuery = input.wait_until_completion
120
+ ? MUTATION_QUERY_TASK
121
+ : MUTATION_QUERY_DATA;
122
+
123
+ const mutation = new Mutation({
124
+ mutation: 'BranchCreate',
125
+ inputData: inputData,
126
+ query: mutationQuery,
127
+ name: 'CreateBranch'
128
+ });
129
+ // You may want to remove this log in production
130
+ console.log(mutation.render());
131
+ const response = await this.client.executeGraphQL(mutation.render(), {
132
+ input: inputData
133
+ });
134
+ // Adjust the response path as needed based on your API
135
+ const branchData =
136
+ response.BranchCreate?.object || response.BranchCreate?.task || null;
137
+ if (!branchData) {
138
+ throw new Error('Branch creation failed');
139
+ }
140
+ return branchData as Branch;
141
+ }
142
+
143
+ async delete(branchName: string): Promise<boolean> {
144
+ const inputData = {
145
+ data: {
146
+ name: branchName
147
+ }
148
+ };
149
+ const mutation = new Mutation({
150
+ mutation: 'BranchDelete',
151
+ inputData: inputData,
152
+ query: { ok: null },
153
+ name: 'DeleteBranch'
154
+ });
155
+ const response = await this.client.executeGraphQL(mutation.render(), {
156
+ input: inputData
157
+ });
158
+ console.log(response);
159
+ return !!(response.BranchDelete && response.BranchDelete.ok);
160
+ }
161
+ }
package/src/graphql.ts ADDED
@@ -0,0 +1,266 @@
1
+ export type VariableType =
2
+ | string
3
+ | number
4
+ | boolean
5
+ | null
6
+ | undefined
7
+ | VariableType[]
8
+ | Record<string, any>;
9
+
10
+ export function convertToGraphqlAsString(
11
+ value: VariableType,
12
+ convertEnum: boolean = false
13
+ ): string {
14
+ if (typeof value === 'string' && value.startsWith('$')) {
15
+ return value;
16
+ }
17
+ if (
18
+ typeof value === 'object' &&
19
+ value &&
20
+ 'constructor' in value &&
21
+ value.constructor.name === 'Enum'
22
+ ) {
23
+ // TypeScript enums are not runtime objects, so you may need to handle this differently
24
+ return convertEnum
25
+ ? convertToGraphqlAsString((value as any).value, true)
26
+ : (value as any).name;
27
+ }
28
+ if (typeof value === 'string') {
29
+ return `"${value}"`;
30
+ }
31
+ if (typeof value === 'boolean') {
32
+ return value ? 'true' : 'false';
33
+ }
34
+ if (Array.isArray(value)) {
35
+ const valuesAsString = value.map((item) =>
36
+ convertToGraphqlAsString(item, convertEnum)
37
+ );
38
+ return `[${valuesAsString.join(', ')}]`;
39
+ }
40
+ if (typeof value === 'object' && value !== null) {
41
+ const entries = Object.entries(value).map(
42
+ ([key, val]) => `${key}: ${convertToGraphqlAsString(val, convertEnum)}`
43
+ );
44
+ return `{ ${entries.join(', ')} }`;
45
+ }
46
+ return String(value);
47
+ }
48
+
49
+ const VARIABLE_TYPE_MAPPING: [any, string][] = [
50
+ [String, 'String!'],
51
+ [Number, 'Int!'],
52
+ [Boolean, 'Boolean!']
53
+ ];
54
+
55
+ export function renderVariablesToString(data: Record<string, any>): string {
56
+ const varsDict: Record<string, string> = {};
57
+ for (const [key, value] of Object.entries(data)) {
58
+ for (const [classType, varString] of VARIABLE_TYPE_MAPPING) {
59
+ if (value === classType) {
60
+ varsDict[`$${key}`] = varString;
61
+ }
62
+ }
63
+ }
64
+ return Object.entries(varsDict)
65
+ .map(([key, value]) => `${key}: ${value}`)
66
+ .join(', ');
67
+ }
68
+
69
+ export function renderQueryBlock(
70
+ data: Record<string, any>,
71
+ offset: number = 4,
72
+ indentation: number = 4,
73
+ convertEnum: boolean = false
74
+ ): string[] {
75
+ const FILTERS_KEY = '@filters';
76
+ const ALIAS_KEY = '@alias';
77
+ const KEYWORDS_TO_SKIP = [FILTERS_KEY, ALIAS_KEY];
78
+
79
+ const offsetStr = ' '.repeat(offset);
80
+ const lines: string[] = [];
81
+ for (const [key, value] of Object.entries(data)) {
82
+ if (KEYWORDS_TO_SKIP.includes(key)) continue;
83
+ if (value === null || value === undefined) {
84
+ lines.push(`${offsetStr}${key}`);
85
+ } else if (
86
+ typeof value === 'object' &&
87
+ value !== null &&
88
+ Object.keys(value).length === 1 &&
89
+ value[ALIAS_KEY]
90
+ ) {
91
+ lines.push(`${offsetStr}${value[ALIAS_KEY]}: ${key}`);
92
+ } else if (typeof value === 'object' && value !== null) {
93
+ const keyStr = value[ALIAS_KEY] ? `${value[ALIAS_KEY]}: ${key}` : key;
94
+ if (value[FILTERS_KEY]) {
95
+ const filtersStr = Object.entries(value[FILTERS_KEY])
96
+ .map(
97
+ ([k2, v2]) =>
98
+ `${k2}: ${convertToGraphqlAsString(v2 as VariableType, convertEnum)}`
99
+ )
100
+ .join(', ');
101
+ lines.push(`${offsetStr}${keyStr}(${filtersStr}) {`);
102
+ } else {
103
+ lines.push(`${offsetStr}${keyStr} {`);
104
+ }
105
+ lines.push(
106
+ ...renderQueryBlock(
107
+ value,
108
+ offset + indentation,
109
+ indentation,
110
+ convertEnum
111
+ )
112
+ );
113
+ lines.push(offsetStr + '}');
114
+ } else {
115
+ lines.push(`${offsetStr}${key}`);
116
+ }
117
+ }
118
+ return lines;
119
+ }
120
+
121
+ export function renderInputBlock(
122
+ data: Record<string, any>,
123
+ offset: number = 4,
124
+ indentation: number = 4,
125
+ convertEnum: boolean = false
126
+ ): string[] {
127
+ const offsetStr = ' '.repeat(offset);
128
+ const lines: string[] = [];
129
+ for (const [key, value] of Object.entries(data)) {
130
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
131
+ lines.push(`${offsetStr}${key}: {`);
132
+ lines.push(
133
+ ...renderInputBlock(
134
+ value,
135
+ offset + indentation,
136
+ indentation,
137
+ convertEnum
138
+ )
139
+ );
140
+ lines.push(offsetStr + '}');
141
+ } else if (Array.isArray(value)) {
142
+ lines.push(`${offsetStr}${key}: [`);
143
+ for (const item of value) {
144
+ if (typeof item === 'object' && item !== null) {
145
+ lines.push(`${offsetStr}${' '.repeat(indentation)}{`);
146
+ lines.push(
147
+ ...renderInputBlock(
148
+ item,
149
+ offset + indentation * 2,
150
+ indentation,
151
+ convertEnum
152
+ )
153
+ );
154
+ lines.push(`${offsetStr}${' '.repeat(indentation)}},`);
155
+ } else {
156
+ lines.push(
157
+ `${offsetStr}${' '.repeat(indentation)}${convertToGraphqlAsString(item, convertEnum)},`
158
+ );
159
+ }
160
+ }
161
+ lines.push(offsetStr + ']');
162
+ } else {
163
+ lines.push(
164
+ `${offsetStr}${key}: ${convertToGraphqlAsString(value, convertEnum)}`
165
+ );
166
+ }
167
+ }
168
+ return lines;
169
+ }
170
+
171
+ export class BaseGraphQLQuery {
172
+ queryType: string = 'not-defined';
173
+ indentation: number = 4;
174
+ query: Record<string, any>;
175
+ variables?: Record<string, any>;
176
+ name: string;
177
+
178
+ constructor(
179
+ query: Record<string, any>,
180
+ variables?: Record<string, any>,
181
+ name?: string
182
+ ) {
183
+ this.query = query;
184
+ this.variables = variables;
185
+ this.name = name || '';
186
+ }
187
+
188
+ renderFirstLine(): string {
189
+ let firstLine = this.queryType;
190
+ if (this.name) {
191
+ firstLine += ' ' + this.name;
192
+ }
193
+ if (this.variables) {
194
+ firstLine += ` (${renderVariablesToString(this.variables)})`;
195
+ }
196
+ firstLine += ' {';
197
+ return firstLine;
198
+ }
199
+ }
200
+
201
+ export class Query extends BaseGraphQLQuery {
202
+ queryType = 'query';
203
+
204
+ render(convertEnum: boolean = false): string {
205
+ const lines: string[] = [this.renderFirstLine()];
206
+ lines.push(
207
+ ...renderQueryBlock(
208
+ this.query,
209
+ this.indentation,
210
+ this.indentation,
211
+ convertEnum
212
+ )
213
+ );
214
+ lines.push('}');
215
+ return '\n' + lines.join('\n') + '\n';
216
+ }
217
+ }
218
+
219
+ export class Mutation extends BaseGraphQLQuery {
220
+ queryType = 'mutation';
221
+ inputData: Record<string, any>;
222
+ mutation: string;
223
+
224
+ constructor({
225
+ mutation,
226
+ inputData,
227
+ query,
228
+ variables,
229
+ name
230
+ }: {
231
+ mutation: string;
232
+ inputData: Record<string, any>;
233
+ query: Record<string, any>;
234
+ variables?: Record<string, any>;
235
+ name?: string;
236
+ }) {
237
+ super(query, variables, name);
238
+ this.inputData = inputData;
239
+ this.mutation = mutation;
240
+ }
241
+
242
+ render(convertEnum: boolean = false): string {
243
+ const lines: string[] = [this.renderFirstLine()];
244
+ lines.push(' '.repeat(this.indentation) + `${this.mutation}(`);
245
+ lines.push(
246
+ ...renderInputBlock(
247
+ this.inputData,
248
+ this.indentation,
249
+ this.indentation * 2,
250
+ convertEnum
251
+ )
252
+ );
253
+ lines.push(' '.repeat(this.indentation) + '){');
254
+ lines.push(
255
+ ...renderQueryBlock(
256
+ this.query,
257
+ this.indentation,
258
+ this.indentation * 2,
259
+ convertEnum
260
+ )
261
+ );
262
+ lines.push(' '.repeat(this.indentation) + '}');
263
+ lines.push('}');
264
+ return '\n' + lines.join('\n') + '\n';
265
+ }
266
+ }
@@ -0,0 +1,74 @@
1
+ import { InfrahubClient, InfrahubClientOptions } from './index';
2
+ import { GraphQLClient } from 'graphql-request';
3
+ // Import expect for type checking and editor support
4
+ import { beforeEach, describe, expect, it, jest } from '@jest/globals';
5
+
6
+ describe('InfrahubClient', () => {
7
+ const baseURL = 'https://example.com';
8
+ const token = 'test-token';
9
+ let client: InfrahubClient;
10
+
11
+ // Initialize the client before each test
12
+
13
+ beforeEach(() => {
14
+ const options: InfrahubClientOptions = {
15
+ address: baseURL,
16
+ token: token
17
+ };
18
+ client = new InfrahubClient(options);
19
+ });
20
+
21
+ it('should initialize with baseURL and token', () => {
22
+ expect((client as any).baseUrl).toBe(baseURL);
23
+ expect((client as any).token).toBe(token);
24
+ });
25
+
26
+ it('should set auth token and update GraphQL headers', () => {
27
+ const newToken = 'new-token';
28
+ client.setAuthToken(newToken);
29
+ expect((client as any).token).toBe(newToken);
30
+ // GraphQLClient headers are private, so we check no error is thrown
31
+ expect(() => client.executeGraphQL('{ __typename }')).not.toThrow();
32
+ });
33
+
34
+ it('should call graphqlQuery and handle errors', async () => {
35
+ const mockRequest = jest
36
+ .fn()
37
+ .mockImplementationOnce(() => Promise.reject(new Error('GraphQL error')));
38
+ (client as any).graphqlClient.request = mockRequest;
39
+ const consoleErrorSpy = jest
40
+ .spyOn(console, 'error')
41
+ .mockImplementation(() => {});
42
+ await expect(client.executeGraphQL('{ test }')).rejects.toThrow(
43
+ 'GraphQL error'
44
+ );
45
+ consoleErrorSpy.mockRestore();
46
+ });
47
+
48
+ it('should initialize rest client with baseUrl', () => {
49
+ expect((client as any).rest).toBeDefined();
50
+ });
51
+
52
+ it('should have a GraphQL client initialized', () => {
53
+ expect((client as any).graphqlClient).toBeInstanceOf(GraphQLClient);
54
+ });
55
+
56
+ it('should have a rest client initialized', () => {
57
+ expect((client as any).rest).toBeDefined();
58
+ });
59
+
60
+ it('should have a default branch set', () => {
61
+ expect((client as any).defaultBranch).toBe('main');
62
+ });
63
+
64
+ it('should have a graphqlUrl method that returns the correct URL', () => {
65
+ const url = (client as any)._graphqlUrl();
66
+ expect(url).toBe(`${baseURL}/graphql/main`);
67
+ });
68
+
69
+ it('graphqlUrl should return the correct URL for a specific branch', () => {
70
+ const branchName = 'test-branch';
71
+ const url = (client as any)._graphqlUrl(branchName);
72
+ expect(url).toBe(`${baseURL}/graphql/${branchName}`);
73
+ });
74
+ });