@ankhorage/contracts 4.0.0 → 4.0.2
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 +6 -0
- package/README.md +1 -1
- package/dist/bindings.d.ts +1 -8
- package/dist/bindings.d.ts.map +1 -1
- package/dist/bindings.js.map +1 -1
- package/dist/data/apis.d.ts +32 -58
- package/dist/data/apis.d.ts.map +1 -1
- package/dist/data/apis.js +3 -15
- package/dist/data/apis.js.map +1 -1
- package/dist/data/refs.d.ts +3 -0
- package/dist/data/refs.d.ts.map +1 -1
- package/dist/data/refs.js.map +1 -1
- package/dist/data/sources.d.ts +26 -26
- package/dist/data/sources.d.ts.map +1 -1
- package/dist/data/sources.js.map +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/src/bindings.test.ts +19 -29
- package/src/bindings.ts +1 -11
- package/src/data/apis.test.ts +52 -129
- package/src/data/apis.ts +34 -77
- package/src/data/data.test.ts +75 -190
- package/src/data/refs.ts +4 -0
- package/src/data/sources.ts +30 -32
- package/src/types.ts +2 -2
package/src/data/data.test.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { describe, expect, it } from 'bun:test';
|
|
2
2
|
|
|
3
3
|
import type {
|
|
4
|
+
DatabaseDataSourceConfig,
|
|
4
5
|
DataSourceConfig,
|
|
5
6
|
DataSourceDiagnostic,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
RestDataSourceConfig,
|
|
7
|
+
ExternalGraphQlApiDataSourceConfig,
|
|
8
|
+
ExternalRestApiDataSourceConfig,
|
|
9
|
+
GeneratedRestApiDataSourceConfig,
|
|
10
10
|
} from './index';
|
|
11
11
|
|
|
12
12
|
function assertSerializable<TValue>(value: TValue): void {
|
|
@@ -14,10 +14,12 @@ function assertSerializable<TValue>(value: TValue): void {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
describe('data source contracts', () => {
|
|
17
|
-
it('serializes
|
|
18
|
-
const source:
|
|
17
|
+
it('serializes an external REST API with manual operations', () => {
|
|
18
|
+
const source: ExternalRestApiDataSourceConfig = {
|
|
19
19
|
id: 'cms-rest',
|
|
20
|
-
kind: '
|
|
20
|
+
kind: 'api',
|
|
21
|
+
origin: 'external',
|
|
22
|
+
protocol: 'rest',
|
|
21
23
|
name: 'CMS REST API',
|
|
22
24
|
baseUrl: 'https://cms.example.com',
|
|
23
25
|
credential: {
|
|
@@ -25,17 +27,6 @@ describe('data source contracts', () => {
|
|
|
25
27
|
kind: 'apiKey',
|
|
26
28
|
label: 'CMS API key',
|
|
27
29
|
},
|
|
28
|
-
schemas: {
|
|
29
|
-
post: {
|
|
30
|
-
type: 'object',
|
|
31
|
-
required: ['id', 'title'],
|
|
32
|
-
properties: {
|
|
33
|
-
id: { type: 'string' },
|
|
34
|
-
title: { type: 'string' },
|
|
35
|
-
published: { type: 'boolean', default: false },
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
30
|
endpoints: {
|
|
40
31
|
posts: {
|
|
41
32
|
id: 'posts',
|
|
@@ -49,43 +40,6 @@ describe('data source contracts', () => {
|
|
|
49
40
|
intent: 'read',
|
|
50
41
|
method: 'GET',
|
|
51
42
|
path: '/posts',
|
|
52
|
-
request: {
|
|
53
|
-
parameters: [
|
|
54
|
-
{
|
|
55
|
-
name: 'limit',
|
|
56
|
-
location: 'query',
|
|
57
|
-
schema: { type: 'integer', default: 20 },
|
|
58
|
-
},
|
|
59
|
-
],
|
|
60
|
-
},
|
|
61
|
-
response: {
|
|
62
|
-
status: 200,
|
|
63
|
-
schema: {
|
|
64
|
-
type: 'array',
|
|
65
|
-
items: { ref: { id: 'post' } },
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
pagination: {
|
|
69
|
-
kind: 'limit-offset',
|
|
70
|
-
limitParameter: 'limit',
|
|
71
|
-
offsetParameter: 'offset',
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
'posts.create': {
|
|
75
|
-
id: 'posts.create',
|
|
76
|
-
endpointId: 'posts',
|
|
77
|
-
protocol: 'http',
|
|
78
|
-
intent: 'create',
|
|
79
|
-
method: 'POST',
|
|
80
|
-
path: '/posts',
|
|
81
|
-
request: {
|
|
82
|
-
contentType: 'application/json',
|
|
83
|
-
schema: { ref: { id: 'post' } },
|
|
84
|
-
},
|
|
85
|
-
response: {
|
|
86
|
-
status: 201,
|
|
87
|
-
schemaRef: { id: 'post' },
|
|
88
|
-
},
|
|
89
43
|
},
|
|
90
44
|
},
|
|
91
45
|
},
|
|
@@ -94,74 +48,38 @@ describe('data source contracts', () => {
|
|
|
94
48
|
|
|
95
49
|
assertSerializable(source);
|
|
96
50
|
expect(source.endpoints.posts?.operations['posts.list']?.intent).toBe('read');
|
|
97
|
-
expect(source.endpoints.posts?.operations['posts.create']?.intent).toBe('create');
|
|
98
51
|
});
|
|
99
52
|
|
|
100
|
-
it('
|
|
101
|
-
const source:
|
|
102
|
-
id: 'shop-
|
|
103
|
-
kind: '
|
|
104
|
-
|
|
53
|
+
it('models OpenAPI as optional metadata on an external REST API', () => {
|
|
54
|
+
const source: ExternalRestApiDataSourceConfig = {
|
|
55
|
+
id: 'shop-api',
|
|
56
|
+
kind: 'api',
|
|
57
|
+
origin: 'external',
|
|
58
|
+
protocol: 'rest',
|
|
59
|
+
name: 'Shop API',
|
|
105
60
|
baseUrl: 'https://shop.example.com/api',
|
|
106
|
-
|
|
61
|
+
openApi: {
|
|
107
62
|
url: 'https://shop.example.com/openapi.json',
|
|
108
|
-
version: '2026-
|
|
109
|
-
},
|
|
110
|
-
endpoints: {
|
|
111
|
-
products: {
|
|
112
|
-
id: 'products',
|
|
113
|
-
kind: 'http',
|
|
114
|
-
path: '/products/{productId}',
|
|
115
|
-
operations: {
|
|
116
|
-
getProduct: {
|
|
117
|
-
id: 'getProduct',
|
|
118
|
-
endpointId: 'products',
|
|
119
|
-
protocol: 'http',
|
|
120
|
-
intent: 'read',
|
|
121
|
-
method: 'GET',
|
|
122
|
-
path: '/products/{productId}',
|
|
123
|
-
request: {
|
|
124
|
-
parameters: [
|
|
125
|
-
{
|
|
126
|
-
name: 'productId',
|
|
127
|
-
location: 'path',
|
|
128
|
-
required: true,
|
|
129
|
-
schema: { type: 'string' },
|
|
130
|
-
},
|
|
131
|
-
],
|
|
132
|
-
},
|
|
133
|
-
response: {
|
|
134
|
-
status: 200,
|
|
135
|
-
schema: {
|
|
136
|
-
type: 'object',
|
|
137
|
-
properties: {
|
|
138
|
-
id: { type: 'string' },
|
|
139
|
-
name: { type: 'string' },
|
|
140
|
-
price: { type: 'number' },
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
},
|
|
144
|
-
},
|
|
145
|
-
},
|
|
146
|
-
},
|
|
63
|
+
version: '2026-08-06',
|
|
147
64
|
},
|
|
65
|
+
endpoints: {},
|
|
148
66
|
};
|
|
149
67
|
|
|
150
68
|
assertSerializable(source);
|
|
151
|
-
expect(source.
|
|
152
|
-
expect(
|
|
153
|
-
source.endpoints.products?.operations.getProduct?.request?.parameters?.[0]?.location,
|
|
154
|
-
).toBe('path');
|
|
69
|
+
expect(source.openApi?.url).toContain('openapi.json');
|
|
70
|
+
expect(source.kind).toBe('api');
|
|
155
71
|
});
|
|
156
72
|
|
|
157
|
-
it('serializes
|
|
158
|
-
const source:
|
|
73
|
+
it('serializes an external GraphQL API independently of origin', () => {
|
|
74
|
+
const source: ExternalGraphQlApiDataSourceConfig = {
|
|
159
75
|
id: 'content-graphql',
|
|
160
|
-
kind: '
|
|
76
|
+
kind: 'api',
|
|
77
|
+
origin: 'external',
|
|
78
|
+
protocol: 'graphql',
|
|
161
79
|
endpointUrl: 'https://content.example.com/graphql',
|
|
162
80
|
introspection: {
|
|
163
81
|
enabled: true,
|
|
164
|
-
schemaVersion: '2026-
|
|
82
|
+
schemaVersion: '2026-08-06',
|
|
165
83
|
},
|
|
166
84
|
endpoints: {
|
|
167
85
|
graphql: {
|
|
@@ -173,48 +91,6 @@ describe('data source contracts', () => {
|
|
|
173
91
|
endpointId: 'graphql',
|
|
174
92
|
protocol: 'graphql',
|
|
175
93
|
intent: 'read',
|
|
176
|
-
request: {
|
|
177
|
-
schema: {
|
|
178
|
-
type: 'object',
|
|
179
|
-
properties: {
|
|
180
|
-
search: { type: 'string' },
|
|
181
|
-
},
|
|
182
|
-
},
|
|
183
|
-
},
|
|
184
|
-
response: {
|
|
185
|
-
schema: {
|
|
186
|
-
type: 'object',
|
|
187
|
-
properties: {
|
|
188
|
-
posts: {
|
|
189
|
-
type: 'array',
|
|
190
|
-
items: { type: 'object' },
|
|
191
|
-
},
|
|
192
|
-
},
|
|
193
|
-
},
|
|
194
|
-
},
|
|
195
|
-
},
|
|
196
|
-
CreatePost: {
|
|
197
|
-
id: 'CreatePost',
|
|
198
|
-
endpointId: 'graphql',
|
|
199
|
-
protocol: 'graphql',
|
|
200
|
-
intent: 'create',
|
|
201
|
-
request: {
|
|
202
|
-
schema: {
|
|
203
|
-
type: 'object',
|
|
204
|
-
required: ['title'],
|
|
205
|
-
properties: {
|
|
206
|
-
title: { type: 'string' },
|
|
207
|
-
},
|
|
208
|
-
},
|
|
209
|
-
},
|
|
210
|
-
response: {
|
|
211
|
-
schema: {
|
|
212
|
-
type: 'object',
|
|
213
|
-
properties: {
|
|
214
|
-
createPost: { type: 'object' },
|
|
215
|
-
},
|
|
216
|
-
},
|
|
217
|
-
},
|
|
218
94
|
},
|
|
219
95
|
},
|
|
220
96
|
},
|
|
@@ -222,52 +98,50 @@ describe('data source contracts', () => {
|
|
|
222
98
|
};
|
|
223
99
|
|
|
224
100
|
assertSerializable(source);
|
|
225
|
-
expect(source.
|
|
226
|
-
expect(source.endpoints.graphql?.operations.
|
|
101
|
+
expect(source.protocol).toBe('graphql');
|
|
102
|
+
expect(source.endpoints.graphql?.operations.PostsQuery?.intent).toBe('read');
|
|
227
103
|
});
|
|
228
104
|
|
|
229
|
-
it('serializes a
|
|
230
|
-
const source:
|
|
231
|
-
id: '
|
|
232
|
-
kind: '
|
|
233
|
-
|
|
105
|
+
it('serializes a normalized generated REST API projection', () => {
|
|
106
|
+
const source: GeneratedRestApiDataSourceConfig = {
|
|
107
|
+
id: 'catalog-api',
|
|
108
|
+
kind: 'api',
|
|
109
|
+
origin: 'generated',
|
|
110
|
+
protocol: 'rest',
|
|
111
|
+
generatedApiId: 'catalog-api',
|
|
112
|
+
name: 'Catalog API',
|
|
234
113
|
adapter: {
|
|
235
114
|
id: 'primary-db',
|
|
236
115
|
kind: 'database',
|
|
237
116
|
packageName: '@ankhorage/supabase-db',
|
|
238
117
|
},
|
|
239
|
-
|
|
240
|
-
{
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
fields: [
|
|
247
|
-
{ name: 'id', type: 'uuid', required: true, unique: true },
|
|
248
|
-
{ name: 'title', type: 'text', required: true },
|
|
249
|
-
{ name: 'published', type: 'boolean', defaultValue: false },
|
|
250
|
-
],
|
|
118
|
+
schemas: {
|
|
119
|
+
products: {
|
|
120
|
+
type: 'object',
|
|
121
|
+
required: ['id', 'name'],
|
|
122
|
+
properties: {
|
|
123
|
+
id: { type: 'string', format: 'uuid' },
|
|
124
|
+
name: { type: 'string' },
|
|
251
125
|
},
|
|
252
|
-
operations: ['list', 'read', 'create', 'update', 'delete'],
|
|
253
126
|
},
|
|
254
|
-
|
|
127
|
+
},
|
|
255
128
|
endpoints: {
|
|
256
|
-
|
|
257
|
-
id: '
|
|
129
|
+
products: {
|
|
130
|
+
id: 'products',
|
|
258
131
|
kind: 'database',
|
|
132
|
+
path: '/products',
|
|
259
133
|
operations: {
|
|
260
|
-
'
|
|
261
|
-
id: '
|
|
262
|
-
endpointId: '
|
|
134
|
+
'products.list': {
|
|
135
|
+
id: 'products.list',
|
|
136
|
+
endpointId: 'products',
|
|
263
137
|
protocol: 'database',
|
|
264
138
|
intent: 'read',
|
|
265
139
|
},
|
|
266
|
-
'
|
|
267
|
-
id: '
|
|
268
|
-
endpointId: '
|
|
140
|
+
'products.create': {
|
|
141
|
+
id: 'products.create',
|
|
142
|
+
endpointId: 'products',
|
|
269
143
|
protocol: 'database',
|
|
270
|
-
intent: '
|
|
144
|
+
intent: 'create',
|
|
271
145
|
},
|
|
272
146
|
},
|
|
273
147
|
},
|
|
@@ -276,17 +150,28 @@ describe('data source contracts', () => {
|
|
|
276
150
|
|
|
277
151
|
assertSerializable(source);
|
|
278
152
|
expect(source.adapter.kind).toBe('database');
|
|
279
|
-
expect(source.
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
153
|
+
expect(source.origin).toBe('generated');
|
|
154
|
+
expect(source.endpoints.products?.operations['products.create']?.intent).toBe('create');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('keeps database sources separate from API origin and protocol', () => {
|
|
158
|
+
const source: DatabaseDataSourceConfig = {
|
|
159
|
+
id: 'primary-db',
|
|
160
|
+
kind: 'database',
|
|
161
|
+
adapter: { id: 'supabase-db', kind: 'database' },
|
|
162
|
+
endpoints: {},
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
assertSerializable(source);
|
|
166
|
+
expect(source.kind).toBe('database');
|
|
284
167
|
});
|
|
285
168
|
|
|
286
|
-
it('accepts a provider-neutral
|
|
169
|
+
it('accepts a provider-neutral source union and diagnostics', () => {
|
|
287
170
|
const source: DataSourceConfig = {
|
|
288
|
-
id: 'public-
|
|
289
|
-
kind: '
|
|
171
|
+
id: 'public-api',
|
|
172
|
+
kind: 'api',
|
|
173
|
+
origin: 'external',
|
|
174
|
+
protocol: 'rest',
|
|
290
175
|
baseUrl: 'https://public.example.com',
|
|
291
176
|
endpoints: {},
|
|
292
177
|
};
|
|
@@ -294,7 +179,7 @@ describe('data source contracts', () => {
|
|
|
294
179
|
code: 'missing-operation',
|
|
295
180
|
severity: 'error',
|
|
296
181
|
message: 'Operation not found.',
|
|
297
|
-
dataSourceId: 'public-
|
|
182
|
+
dataSourceId: 'public-api',
|
|
298
183
|
endpointId: 'posts',
|
|
299
184
|
operationId: 'posts.list',
|
|
300
185
|
};
|
package/src/data/refs.ts
CHANGED
package/src/data/sources.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import type { DbCollectionDefinition } from '../db';
|
|
2
1
|
import type { DataEndpointRegistry } from './endpoints';
|
|
3
2
|
import type { DataSourceId } from './ids';
|
|
4
|
-
import type {
|
|
3
|
+
import type { CredentialRef, DatabaseAdapterRef } from './refs';
|
|
5
4
|
import type { DataSchemaRegistry } from './schemas';
|
|
6
5
|
import type { DataContractValue } from './values';
|
|
7
6
|
|
|
8
|
-
export type DataSourceKind = '
|
|
7
|
+
export type DataSourceKind = 'api' | 'database';
|
|
8
|
+
export type ApiOrigin = 'external' | 'generated';
|
|
9
|
+
export type ApiProtocol = 'graphql' | 'rest';
|
|
9
10
|
|
|
10
11
|
export interface DataSourceBaseConfig {
|
|
11
12
|
readonly id: DataSourceId;
|
|
@@ -18,25 +19,28 @@ export interface DataSourceBaseConfig {
|
|
|
18
19
|
readonly metadata?: DataContractValue;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
export interface
|
|
22
|
-
readonly kind: '
|
|
23
|
-
readonly
|
|
22
|
+
export interface ApiDataSourceBaseConfig extends DataSourceBaseConfig {
|
|
23
|
+
readonly kind: 'api';
|
|
24
|
+
readonly origin: ApiOrigin;
|
|
25
|
+
readonly protocol: ApiProtocol;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
export interface
|
|
28
|
+
export interface OpenApiDocumentRef {
|
|
27
29
|
readonly url?: string;
|
|
28
30
|
readonly documentId?: string;
|
|
29
31
|
readonly version?: string;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
export interface
|
|
33
|
-
readonly
|
|
34
|
-
readonly
|
|
35
|
-
readonly
|
|
34
|
+
export interface ExternalRestApiDataSourceConfig extends ApiDataSourceBaseConfig {
|
|
35
|
+
readonly origin: 'external';
|
|
36
|
+
readonly protocol: 'rest';
|
|
37
|
+
readonly baseUrl: string;
|
|
38
|
+
readonly openApi?: OpenApiDocumentRef;
|
|
36
39
|
}
|
|
37
40
|
|
|
38
|
-
export interface
|
|
39
|
-
readonly
|
|
41
|
+
export interface ExternalGraphQlApiDataSourceConfig extends ApiDataSourceBaseConfig {
|
|
42
|
+
readonly origin: 'external';
|
|
43
|
+
readonly protocol: 'graphql';
|
|
40
44
|
readonly endpointUrl: string;
|
|
41
45
|
readonly introspection?: {
|
|
42
46
|
readonly enabled: boolean;
|
|
@@ -44,29 +48,23 @@ export interface GraphQlDataSourceConfig extends DataSourceBaseConfig {
|
|
|
44
48
|
};
|
|
45
49
|
}
|
|
46
50
|
|
|
47
|
-
export interface
|
|
48
|
-
readonly
|
|
49
|
-
readonly
|
|
51
|
+
export interface GeneratedRestApiDataSourceConfig extends ApiDataSourceBaseConfig {
|
|
52
|
+
readonly origin: 'generated';
|
|
53
|
+
readonly protocol: 'rest';
|
|
54
|
+
readonly generatedApiId: string;
|
|
55
|
+
readonly adapter: DatabaseAdapterRef;
|
|
50
56
|
}
|
|
51
57
|
|
|
52
|
-
export
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
readonly metadata?: DataContractValue;
|
|
57
|
-
}
|
|
58
|
+
export type ApiDataSourceConfig =
|
|
59
|
+
| ExternalGraphQlApiDataSourceConfig
|
|
60
|
+
| ExternalRestApiDataSourceConfig
|
|
61
|
+
| GeneratedRestApiDataSourceConfig;
|
|
58
62
|
|
|
59
|
-
export interface
|
|
60
|
-
readonly kind: '
|
|
61
|
-
readonly adapter:
|
|
62
|
-
readonly resources: readonly ManagedApiResourceConfig[];
|
|
63
|
+
export interface DatabaseDataSourceConfig extends DataSourceBaseConfig {
|
|
64
|
+
readonly kind: 'database';
|
|
65
|
+
readonly adapter: DatabaseAdapterRef;
|
|
63
66
|
}
|
|
64
67
|
|
|
65
|
-
export type DataSourceConfig =
|
|
66
|
-
| DatabaseDataSourceConfig
|
|
67
|
-
| GraphQlDataSourceConfig
|
|
68
|
-
| ManagedApiDataSourceConfig
|
|
69
|
-
| OpenApiDataSourceConfig
|
|
70
|
-
| RestDataSourceConfig;
|
|
68
|
+
export type DataSourceConfig = ApiDataSourceConfig | DatabaseDataSourceConfig;
|
|
71
69
|
|
|
72
70
|
export type DataSourceRegistry = Readonly<Record<DataSourceId, DataSourceConfig>>;
|
package/src/types.ts
CHANGED
|
@@ -6,7 +6,7 @@ import type {
|
|
|
6
6
|
ComponentDataBindingRegistry,
|
|
7
7
|
ScreenDataLoaderDefinition,
|
|
8
8
|
} from './bindings';
|
|
9
|
-
import type {
|
|
9
|
+
import type { DataSourceRegistry, GeneratedApiRegistry } from './data';
|
|
10
10
|
import type { ScreenRequirements } from './requirements';
|
|
11
11
|
|
|
12
12
|
export interface ThemeModeConfig {
|
|
@@ -389,7 +389,7 @@ export interface AppManifest {
|
|
|
389
389
|
infra: InfraManifest;
|
|
390
390
|
navigator: NavigatorSpec;
|
|
391
391
|
screens: Record<string, ScreenSpec>;
|
|
392
|
-
|
|
392
|
+
generatedApis?: GeneratedApiRegistry;
|
|
393
393
|
dataSources?: DataSourceRegistry;
|
|
394
394
|
dataBindings?: ComponentDataBindingRegistry;
|
|
395
395
|
settings: AppSettings;
|