@ankhorage/data-sources 0.5.5 → 1.0.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/CHANGELOG.md +12 -0
- package/README.md +12 -5
- package/dist/discovery/index.d.ts +64 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/index.js +195 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/generated-api/index.d.ts +10 -0
- package/dist/generated-api/index.d.ts.map +1 -0
- package/dist/generated-api/index.js +272 -0
- package/dist/generated-api/index.js.map +1 -0
- package/dist/graphql/index.d.ts +3 -3
- package/dist/graphql/index.d.ts.map +1 -1
- package/dist/graphql/index.js +24 -60
- package/dist/graphql/index.js.map +1 -1
- package/dist/index.d.ts +10 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -4
- package/dist/index.js.map +1 -1
- package/dist/openapi/index.d.ts +2 -2
- package/dist/openapi/index.d.ts.map +1 -1
- package/dist/openapi/index.js +19 -29
- package/dist/openapi/index.js.map +1 -1
- package/dist/rest/index.d.ts +3 -3
- package/dist/rest/index.d.ts.map +1 -1
- package/dist/rest/index.js +51 -51
- package/dist/rest/index.js.map +1 -1
- package/dist/test-runner/index.d.ts.map +1 -1
- package/dist/test-runner/index.js +60 -104
- package/dist/test-runner/index.js.map +1 -1
- package/package.json +2 -2
- package/src/discovery/index.test.ts +133 -0
- package/src/discovery/index.ts +340 -0
- package/src/{managed-api → generated-api}/index.test.ts +59 -67
- package/src/generated-api/index.ts +410 -0
- package/src/graphql/index.test.ts +28 -134
- package/src/graphql/index.ts +29 -89
- package/src/index.test.ts +25 -18
- package/src/index.ts +29 -7
- package/src/openapi/index.test.ts +36 -246
- package/src/openapi/index.ts +22 -60
- package/src/rest/index.test.ts +22 -173
- package/src/rest/index.ts +62 -59
- package/src/test-runner/index.test.ts +82 -150
- package/src/test-runner/index.ts +73 -143
- package/dist/managed-api/index.d.ts +0 -30
- package/dist/managed-api/index.d.ts.map +0 -1
- package/dist/managed-api/index.js +0 -257
- package/dist/managed-api/index.js.map +0 -1
- package/src/managed-api/index.ts +0 -364
|
@@ -3,10 +3,12 @@ import { describe, expect, it } from 'bun:test';
|
|
|
3
3
|
|
|
4
4
|
import { buildEndpointTestRequest, type EndpointTestFetch, testEndpoint } from './index';
|
|
5
5
|
|
|
6
|
-
function
|
|
6
|
+
function restSource(): DataSourceConfig {
|
|
7
7
|
return {
|
|
8
8
|
id: 'cms',
|
|
9
|
-
kind: '
|
|
9
|
+
kind: 'api',
|
|
10
|
+
origin: 'external',
|
|
11
|
+
protocol: 'rest',
|
|
10
12
|
baseUrl: 'https://cms.example.com',
|
|
11
13
|
credential: { id: 'cms-token', kind: 'bearer' },
|
|
12
14
|
endpoints: {
|
|
@@ -30,19 +32,9 @@ function createRestDataSource(): DataSourceConfig {
|
|
|
30
32
|
required: true,
|
|
31
33
|
schema: { type: 'string' },
|
|
32
34
|
},
|
|
33
|
-
{
|
|
34
|
-
name: 'preview',
|
|
35
|
-
location: 'query',
|
|
36
|
-
schema: { type: 'boolean' },
|
|
37
|
-
},
|
|
35
|
+
{ name: 'preview', location: 'query', schema: { type: 'boolean' } },
|
|
38
36
|
],
|
|
39
37
|
},
|
|
40
|
-
response: {
|
|
41
|
-
status: 200,
|
|
42
|
-
schema: {
|
|
43
|
-
type: 'object',
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
38
|
},
|
|
47
39
|
'posts.create': {
|
|
48
40
|
id: 'posts.create',
|
|
@@ -51,14 +43,6 @@ function createRestDataSource(): DataSourceConfig {
|
|
|
51
43
|
intent: 'create',
|
|
52
44
|
method: 'POST',
|
|
53
45
|
path: '/posts',
|
|
54
|
-
request: {
|
|
55
|
-
contentType: 'application/json',
|
|
56
|
-
schema: { type: 'object' },
|
|
57
|
-
},
|
|
58
|
-
response: {
|
|
59
|
-
status: 201,
|
|
60
|
-
schema: { type: 'object' },
|
|
61
|
-
},
|
|
62
46
|
},
|
|
63
47
|
},
|
|
64
48
|
},
|
|
@@ -66,10 +50,12 @@ function createRestDataSource(): DataSourceConfig {
|
|
|
66
50
|
};
|
|
67
51
|
}
|
|
68
52
|
|
|
69
|
-
function
|
|
53
|
+
function graphQlSource(): DataSourceConfig {
|
|
70
54
|
return {
|
|
71
55
|
id: 'content',
|
|
72
|
-
kind: '
|
|
56
|
+
kind: 'api',
|
|
57
|
+
origin: 'external',
|
|
58
|
+
protocol: 'graphql',
|
|
73
59
|
endpointUrl: 'https://content.example.com/graphql',
|
|
74
60
|
endpoints: {
|
|
75
61
|
graphql: {
|
|
@@ -81,12 +67,7 @@ function createGraphQlDataSource(): DataSourceConfig {
|
|
|
81
67
|
endpointId: 'graphql',
|
|
82
68
|
protocol: 'graphql',
|
|
83
69
|
intent: 'read',
|
|
84
|
-
|
|
85
|
-
response: { schema: { type: 'object' } },
|
|
86
|
-
metadata: {
|
|
87
|
-
document: 'query Viewer($id: ID!) { viewer(id: $id) { id } }',
|
|
88
|
-
selectionPath: '$.data.viewer',
|
|
89
|
-
},
|
|
70
|
+
metadata: { document: 'query Viewer($id: ID!) { viewer(id: $id) { id } }' },
|
|
90
71
|
},
|
|
91
72
|
},
|
|
92
73
|
},
|
|
@@ -95,172 +76,123 @@ function createGraphQlDataSource(): DataSourceConfig {
|
|
|
95
76
|
}
|
|
96
77
|
|
|
97
78
|
describe('endpoint test runner', () => {
|
|
98
|
-
it('builds
|
|
79
|
+
it('builds REST dry runs with params and credential headers', async () => {
|
|
99
80
|
const result = await buildEndpointTestRequest({
|
|
100
|
-
dataSource:
|
|
81
|
+
dataSource: restSource(),
|
|
101
82
|
endpointId: 'posts',
|
|
102
83
|
operationId: 'posts.get',
|
|
103
84
|
dryRun: true,
|
|
104
|
-
values: {
|
|
105
|
-
|
|
106
|
-
preview: true,
|
|
107
|
-
},
|
|
108
|
-
credentialResolver: () => ({
|
|
109
|
-
headers: { authorization: 'Bearer token' },
|
|
110
|
-
}),
|
|
85
|
+
values: { postId: 'post 1', preview: true },
|
|
86
|
+
credentialResolver: () => ({ headers: { authorization: 'Bearer token' } }),
|
|
111
87
|
});
|
|
112
|
-
|
|
113
88
|
expect(result.ok).toBe(true);
|
|
114
89
|
if (result.ok) {
|
|
115
90
|
expect(result.request.url).toBe('https://cms.example.com/posts/post%201?preview=true');
|
|
116
|
-
expect(result.request.method).toBe('GET');
|
|
117
91
|
expect(result.request.headers.authorization).toBe('Bearer token');
|
|
118
|
-
expect(result.request.dryRun).toBe(true);
|
|
119
92
|
}
|
|
120
93
|
});
|
|
121
94
|
|
|
122
|
-
it('executes
|
|
123
|
-
const fetch: EndpointTestFetch = (
|
|
124
|
-
|
|
125
|
-
expect(init.method).toBe('GET');
|
|
126
|
-
return Promise.resolve({
|
|
127
|
-
status: 200,
|
|
128
|
-
headers: { 'content-type': 'application/json' },
|
|
129
|
-
text: () => Promise.resolve('{"id":"post-1"}'),
|
|
130
|
-
});
|
|
131
|
-
};
|
|
132
|
-
|
|
95
|
+
it('executes REST requests and parses responses', async () => {
|
|
96
|
+
const fetch: EndpointTestFetch = () =>
|
|
97
|
+
Promise.resolve({ status: 200, text: () => Promise.resolve('{"id":"post-1"}') });
|
|
133
98
|
const result = await testEndpoint({
|
|
134
|
-
dataSource:
|
|
99
|
+
dataSource: restSource(),
|
|
135
100
|
endpointId: 'posts',
|
|
136
101
|
operationId: 'posts.get',
|
|
137
102
|
values: { postId: 'post-1' },
|
|
138
103
|
credentialResolver: () => ({ headers: { authorization: 'Bearer token' } }),
|
|
139
104
|
fetch,
|
|
140
105
|
});
|
|
141
|
-
|
|
142
106
|
expect(result.ok).toBe(true);
|
|
143
|
-
if (result.ok) {
|
|
144
|
-
expect(result.response?.status).toBe(200);
|
|
145
|
-
expect(result.data).toEqual({ id: 'post-1' });
|
|
146
|
-
}
|
|
107
|
+
if (result.ok) expect(result.data).toEqual({ id: 'post-1' });
|
|
147
108
|
});
|
|
148
109
|
|
|
149
|
-
it('returns
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
status: 404,
|
|
153
|
-
text: () => Promise.resolve('{"error":"Not found"}'),
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
const result = await testEndpoint({
|
|
157
|
-
dataSource: createRestDataSource(),
|
|
110
|
+
it('returns HTTP, network, credential and input diagnostics', async () => {
|
|
111
|
+
const http = await testEndpoint({
|
|
112
|
+
dataSource: restSource(),
|
|
158
113
|
endpointId: 'posts',
|
|
159
114
|
operationId: 'posts.get',
|
|
160
115
|
values: { postId: 'missing' },
|
|
161
|
-
credentialResolver: () => ({ headers: {
|
|
162
|
-
fetch,
|
|
116
|
+
credentialResolver: () => ({ headers: {} }),
|
|
117
|
+
fetch: () => Promise.resolve({ status: 404, text: () => Promise.resolve('{}') }),
|
|
163
118
|
});
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if (!result.ok) {
|
|
167
|
-
expect(result.response?.status).toBe(404);
|
|
168
|
-
expect(result.diagnostics.map((diagnostic) => diagnostic.code)).toContain('http-error');
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it('returns structured diagnostics for network errors', async () => {
|
|
173
|
-
const fetch: EndpointTestFetch = () => Promise.reject(new Error('Connection failed'));
|
|
174
|
-
|
|
175
|
-
const result = await testEndpoint({
|
|
176
|
-
dataSource: createRestDataSource(),
|
|
119
|
+
const network = await testEndpoint({
|
|
120
|
+
dataSource: restSource(),
|
|
177
121
|
endpointId: 'posts',
|
|
178
122
|
operationId: 'posts.get',
|
|
179
|
-
values: { postId: '
|
|
180
|
-
credentialResolver: () => ({ headers: {
|
|
181
|
-
fetch,
|
|
123
|
+
values: { postId: 'x' },
|
|
124
|
+
credentialResolver: () => ({ headers: {} }),
|
|
125
|
+
fetch: () => Promise.reject(new Error('Connection failed')),
|
|
182
126
|
});
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (!result.ok) {
|
|
186
|
-
expect(result.diagnostics.map((diagnostic) => diagnostic.code)).toContain('network-error');
|
|
187
|
-
expect(result.diagnostics.map((diagnostic) => diagnostic.message)).toContain(
|
|
188
|
-
'Connection failed',
|
|
189
|
-
);
|
|
190
|
-
}
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it('returns structured diagnostics for missing credentials', async () => {
|
|
194
|
-
const result = await buildEndpointTestRequest({
|
|
195
|
-
dataSource: createRestDataSource(),
|
|
127
|
+
const credential = await buildEndpointTestRequest({
|
|
128
|
+
dataSource: restSource(),
|
|
196
129
|
endpointId: 'posts',
|
|
197
130
|
operationId: 'posts.get',
|
|
198
|
-
values: { postId: '
|
|
131
|
+
values: { postId: 'x' },
|
|
199
132
|
});
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if (!result.ok) {
|
|
203
|
-
expect(result.diagnostics.map((diagnostic) => diagnostic.code)).toContain(
|
|
204
|
-
'missing-credential',
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
it('returns structured diagnostics for invalid input', async () => {
|
|
210
|
-
const result = await buildEndpointTestRequest({
|
|
211
|
-
dataSource: createRestDataSource(),
|
|
133
|
+
const input = await buildEndpointTestRequest({
|
|
134
|
+
dataSource: restSource(),
|
|
212
135
|
endpointId: 'posts',
|
|
213
136
|
operationId: 'posts.get',
|
|
214
|
-
credentialResolver: () => ({ headers: {
|
|
137
|
+
credentialResolver: () => ({ headers: {} }),
|
|
215
138
|
});
|
|
216
|
-
|
|
217
|
-
expect(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
"Missing required path parameter 'postId'.",
|
|
221
|
-
);
|
|
222
|
-
}
|
|
139
|
+
expect(http.ok).toBe(false);
|
|
140
|
+
expect(network.ok).toBe(false);
|
|
141
|
+
expect(credential.ok).toBe(false);
|
|
142
|
+
expect(input.ok).toBe(false);
|
|
223
143
|
});
|
|
224
144
|
|
|
225
|
-
it('builds POST
|
|
226
|
-
const
|
|
227
|
-
dataSource:
|
|
145
|
+
it('builds POST bodies and GraphQL requests', async () => {
|
|
146
|
+
const post = await buildEndpointTestRequest({
|
|
147
|
+
dataSource: restSource(),
|
|
228
148
|
endpointId: 'posts',
|
|
229
149
|
operationId: 'posts.create',
|
|
230
150
|
dryRun: true,
|
|
231
|
-
values: {
|
|
232
|
-
|
|
233
|
-
},
|
|
234
|
-
credentialResolver: () => ({ headers: { authorization: 'Bearer token' } }),
|
|
151
|
+
values: { body: { title: 'Hello' } },
|
|
152
|
+
credentialResolver: () => ({ headers: {} }),
|
|
235
153
|
});
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
if (result.ok) {
|
|
239
|
-
expect(result.request.method).toBe('POST');
|
|
240
|
-
expect(result.request.body).toBe('{"title":"Hello"}');
|
|
241
|
-
expect(result.request.headers['content-type']).toBe('application/json');
|
|
242
|
-
}
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
it('builds dry-run GraphQL requests from operation metadata', async () => {
|
|
246
|
-
const result = await buildEndpointTestRequest({
|
|
247
|
-
dataSource: createGraphQlDataSource(),
|
|
154
|
+
const graphql = await buildEndpointTestRequest({
|
|
155
|
+
dataSource: graphQlSource(),
|
|
248
156
|
endpointId: 'graphql',
|
|
249
157
|
operationId: 'query.viewer',
|
|
250
158
|
dryRun: true,
|
|
251
|
-
values: {
|
|
252
|
-
variables: { id: 'viewer-1' },
|
|
253
|
-
},
|
|
159
|
+
values: { variables: { id: 'viewer-1' } },
|
|
254
160
|
});
|
|
161
|
+
expect(post.ok).toBe(true);
|
|
162
|
+
if (post.ok) expect(post.request.body).toBe('{"title":"Hello"}');
|
|
163
|
+
expect(graphql.ok).toBe(true);
|
|
164
|
+
if (graphql.ok) expect(graphql.request.url).toBe('https://content.example.com/graphql');
|
|
165
|
+
});
|
|
255
166
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
167
|
+
it('does not pretend database-backed generated operations are HTTP endpoints', async () => {
|
|
168
|
+
const source: DataSourceConfig = {
|
|
169
|
+
id: 'catalog',
|
|
170
|
+
kind: 'api',
|
|
171
|
+
origin: 'generated',
|
|
172
|
+
protocol: 'rest',
|
|
173
|
+
generatedApiId: 'catalog',
|
|
174
|
+
adapter: { id: 'db', kind: 'database' },
|
|
175
|
+
endpoints: {
|
|
176
|
+
products: {
|
|
177
|
+
id: 'products',
|
|
178
|
+
kind: 'database',
|
|
179
|
+
operations: {
|
|
180
|
+
'products.list': {
|
|
181
|
+
id: 'products.list',
|
|
182
|
+
endpointId: 'products',
|
|
183
|
+
protocol: 'database',
|
|
184
|
+
intent: 'read',
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
const result = await buildEndpointTestRequest({
|
|
191
|
+
dataSource: source,
|
|
192
|
+
endpointId: 'products',
|
|
193
|
+
operationId: 'products.list',
|
|
194
|
+
});
|
|
195
|
+
expect(result.ok).toBe(false);
|
|
196
|
+
if (!result.ok) expect(result.diagnostics[0]?.message).toContain('adapter executor');
|
|
265
197
|
});
|
|
266
198
|
});
|