@ankhorage/data-sources 1.0.1 → 2.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.
- package/CHANGELOG.md +12 -0
- package/README.md +6 -41
- package/dist/ankh.provider.js +1 -1
- package/dist/ankh.provider.js.map +1 -1
- package/dist/discovery/http.d.ts +10 -0
- package/dist/discovery/http.d.ts.map +1 -0
- package/dist/discovery/http.js +35 -0
- package/dist/discovery/http.js.map +1 -0
- package/dist/discovery/index.d.ts +9 -9
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/index.js +27 -55
- package/dist/discovery/index.js.map +1 -1
- package/dist/graphql/index.d.ts +8 -76
- package/dist/graphql/index.d.ts.map +1 -1
- package/dist/graphql/index.js +17 -197
- package/dist/graphql/index.js.map +1 -1
- package/dist/graphql/introspection.d.ts +5 -0
- package/dist/graphql/introspection.d.ts.map +1 -0
- package/dist/graphql/introspection.js +136 -0
- package/dist/graphql/introspection.js.map +1 -0
- package/dist/graphql/operation.d.ts +5 -0
- package/dist/graphql/operation.d.ts.map +1 -0
- package/dist/graphql/operation.js +39 -0
- package/dist/graphql/operation.js.map +1 -0
- package/dist/graphql/types.d.ts +71 -0
- package/dist/graphql/types.d.ts.map +1 -0
- package/dist/graphql/types.js +2 -0
- package/dist/graphql/types.js.map +1 -0
- package/dist/index.d.ts +1 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -34
- package/dist/index.js.map +1 -1
- package/dist/openapi/endpoints.d.ts +5 -0
- package/dist/openapi/endpoints.d.ts.map +1 -0
- package/dist/openapi/endpoints.js +77 -0
- package/dist/openapi/endpoints.js.map +1 -0
- package/dist/openapi/index.d.ts +7 -87
- package/dist/openapi/index.d.ts.map +1 -1
- package/dist/openapi/index.js +8 -267
- package/dist/openapi/index.js.map +1 -1
- package/dist/openapi/operation.d.ts +5 -0
- package/dist/openapi/operation.d.ts.map +1 -0
- package/dist/openapi/operation.js +141 -0
- package/dist/openapi/operation.js.map +1 -0
- package/dist/openapi/schema.d.ts +5 -0
- package/dist/openapi/schema.d.ts.map +1 -0
- package/dist/openapi/schema.js +84 -0
- package/dist/openapi/schema.js.map +1 -0
- package/dist/openapi/types.d.ts +82 -0
- package/dist/openapi/types.d.ts.map +1 -0
- package/dist/openapi/types.js +2 -0
- package/dist/openapi/types.js.map +1 -0
- package/dist/packageInfo.d.ts +18 -0
- package/dist/packageInfo.d.ts.map +1 -0
- package/dist/packageInfo.js +30 -0
- package/dist/packageInfo.js.map +1 -0
- package/dist/rest/index.d.ts +5 -5
- package/dist/rest/index.d.ts.map +1 -1
- package/dist/rest/index.js +49 -48
- package/dist/rest/index.js.map +1 -1
- package/dist/test-runner/index.d.ts +2 -57
- package/dist/test-runner/index.d.ts.map +1 -1
- package/dist/test-runner/index.js +58 -160
- package/dist/test-runner/index.js.map +1 -1
- package/dist/test-runner/response.d.ts +3 -0
- package/dist/test-runner/response.d.ts.map +1 -0
- package/dist/test-runner/response.js +29 -0
- package/dist/test-runner/response.js.map +1 -0
- package/dist/test-runner/result.d.ts +6 -0
- package/dist/test-runner/result.d.ts.map +1 -0
- package/dist/test-runner/result.js +17 -0
- package/dist/test-runner/result.js.map +1 -0
- package/dist/test-runner/serialization.d.ts +12 -0
- package/dist/test-runner/serialization.d.ts.map +1 -0
- package/dist/test-runner/serialization.js +99 -0
- package/dist/test-runner/serialization.js.map +1 -0
- package/dist/test-runner/types.d.ts +58 -0
- package/dist/test-runner/types.d.ts.map +1 -0
- package/dist/test-runner/types.js +2 -0
- package/dist/test-runner/types.js.map +1 -0
- package/package.json +12 -13
- package/src/ankh.provider.ts +1 -1
- package/src/api-phase1.contract.test.ts +24 -0
- package/src/discovery/http.ts +38 -0
- package/src/discovery/index.test.ts +76 -75
- package/src/discovery/index.ts +49 -72
- package/src/graphql/index.test.ts +10 -11
- package/src/graphql/index.ts +35 -315
- package/src/graphql/introspection.ts +163 -0
- package/src/graphql/operation.ts +60 -0
- package/src/graphql/types.ts +79 -0
- package/src/index.test.ts +21 -13
- package/src/index.ts +1 -54
- package/src/openapi/endpoints.ts +124 -0
- package/src/openapi/index.test.ts +47 -47
- package/src/openapi/index.ts +12 -467
- package/src/openapi/operation.ts +231 -0
- package/src/openapi/schema.ts +127 -0
- package/src/openapi/types.ts +93 -0
- package/src/packageInfo.ts +50 -0
- package/src/rest/index.test.ts +17 -14
- package/src/rest/index.ts +67 -61
- package/src/test-runner/index.test.ts +153 -120
- package/src/test-runner/index.ts +97 -299
- package/src/test-runner/response.ts +32 -0
- package/src/test-runner/result.ts +35 -0
- package/src/test-runner/serialization.ts +151 -0
- package/src/test-runner/types.ts +81 -0
- package/dist/generated-api/index.d.ts +0 -10
- package/dist/generated-api/index.d.ts.map +0 -1
- package/dist/generated-api/index.js +0 -274
- package/dist/generated-api/index.js.map +0 -1
- package/dist/generated-api/validateGeneratedApiCollection.d.ts +0 -3
- package/dist/generated-api/validateGeneratedApiCollection.d.ts.map +0 -1
- package/dist/generated-api/validateGeneratedApiCollection.js +0 -35
- package/dist/generated-api/validateGeneratedApiCollection.js.map +0 -1
- package/src/generated-api/index.test.ts +0 -173
- package/src/generated-api/index.ts +0 -414
- package/src/generated-api/validateGeneratedApiCollection.test.ts +0 -91
- package/src/generated-api/validateGeneratedApiCollection.ts +0 -76
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DataContractValue,
|
|
3
|
+
DataOperationConfig,
|
|
4
|
+
DataOperationParameter,
|
|
5
|
+
DataSourceDiagnostic,
|
|
6
|
+
} from '@ankhorage/contracts/data';
|
|
7
|
+
|
|
8
|
+
import { createDiagnostic } from './result';
|
|
9
|
+
import type { EndpointTestHeaders, EndpointTestInput, EndpointTestInputValues } from './types';
|
|
10
|
+
|
|
11
|
+
export function interpolatePath(
|
|
12
|
+
path: string,
|
|
13
|
+
parameters: readonly DataOperationParameter[],
|
|
14
|
+
values: EndpointTestInputValues,
|
|
15
|
+
input: EndpointTestInput,
|
|
16
|
+
diagnostics: DataSourceDiagnostic[],
|
|
17
|
+
): string {
|
|
18
|
+
let next = path;
|
|
19
|
+
for (const parameter of parameters.filter((item) => item.location === 'path')) {
|
|
20
|
+
const value = readValue(values, parameter.name) ?? parameter.default;
|
|
21
|
+
if (value === undefined) {
|
|
22
|
+
diagnostics.push(
|
|
23
|
+
createDiagnostic(
|
|
24
|
+
input,
|
|
25
|
+
'invalid-config',
|
|
26
|
+
`Missing required path parameter '${parameter.name}'.`,
|
|
27
|
+
),
|
|
28
|
+
);
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const encoded = encodeURIComponent(serializeEndpointUrlValue(value));
|
|
32
|
+
next = next
|
|
33
|
+
.replaceAll(`{${parameter.name}}`, encoded)
|
|
34
|
+
.replaceAll(`:${parameter.name}`, encoded);
|
|
35
|
+
}
|
|
36
|
+
return next;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function collectQuery(
|
|
40
|
+
parameters: readonly DataOperationParameter[],
|
|
41
|
+
values: EndpointTestInputValues,
|
|
42
|
+
credentialQuery: EndpointTestInputValues | undefined,
|
|
43
|
+
): EndpointTestInputValues {
|
|
44
|
+
const parameterEntries = parameters.flatMap((parameter) => {
|
|
45
|
+
if (parameter.location !== 'query') return [];
|
|
46
|
+
const value = readValue(values, parameter.name) ?? parameter.default;
|
|
47
|
+
return value === undefined ? [] : [[parameter.name, value] as const];
|
|
48
|
+
});
|
|
49
|
+
return Object.fromEntries([...Object.entries(credentialQuery ?? {}), ...parameterEntries]);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function collectHeaders(
|
|
53
|
+
parameters: readonly DataOperationParameter[],
|
|
54
|
+
values: EndpointTestInputValues,
|
|
55
|
+
credentialHeaders: EndpointTestHeaders | undefined,
|
|
56
|
+
): Record<string, string> {
|
|
57
|
+
const parameterEntries = parameters.flatMap((parameter) => {
|
|
58
|
+
if (parameter.location !== 'header') return [];
|
|
59
|
+
const value = readValue(values, parameter.name) ?? parameter.default;
|
|
60
|
+
return value === undefined ? [] : [[parameter.name, serializeEndpointUrlValue(value)] as const];
|
|
61
|
+
});
|
|
62
|
+
return Object.fromEntries([...Object.entries(credentialHeaders ?? {}), ...parameterEntries]);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function createHttpBody(
|
|
66
|
+
operation: DataOperationConfig,
|
|
67
|
+
parameters: readonly DataOperationParameter[],
|
|
68
|
+
values: EndpointTestInputValues,
|
|
69
|
+
): string | undefined {
|
|
70
|
+
if (operation.method === 'GET' || operation.method === 'HEAD') return undefined;
|
|
71
|
+
const explicitBody = readValue(values, 'body');
|
|
72
|
+
if (explicitBody !== undefined) return JSON.stringify(explicitBody);
|
|
73
|
+
|
|
74
|
+
const entries = parameters.flatMap((parameter) => {
|
|
75
|
+
if (parameter.location !== 'body') return [];
|
|
76
|
+
const value = readValue(values, parameter.name) ?? parameter.default;
|
|
77
|
+
return value === undefined ? [] : [[parameter.name, value] as const];
|
|
78
|
+
});
|
|
79
|
+
return entries.length === 0 ? undefined : JSON.stringify(Object.fromEntries(entries));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function appendQuery(url: string, query: EndpointTestInputValues): string {
|
|
83
|
+
const entries = Object.entries(query);
|
|
84
|
+
if (entries.length === 0) return url;
|
|
85
|
+
const search = entries
|
|
86
|
+
.map(
|
|
87
|
+
([key, value]) =>
|
|
88
|
+
`${encodeURIComponent(key)}=${encodeURIComponent(serializeEndpointUrlValue(value))}`,
|
|
89
|
+
)
|
|
90
|
+
.join('&');
|
|
91
|
+
return `${url}${url.includes('?') ? '&' : '?'}${search}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function joinUrl(baseUrl: string, path: string): string {
|
|
95
|
+
const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
96
|
+
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
|
97
|
+
return `${normalizedBaseUrl}${normalizedPath}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function getStringValue(
|
|
101
|
+
values: EndpointTestInputValues | undefined,
|
|
102
|
+
key: string,
|
|
103
|
+
): string | undefined {
|
|
104
|
+
const value = readValue(values, key);
|
|
105
|
+
return typeof value === 'string' ? value : undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function getRecordValue(
|
|
109
|
+
values: EndpointTestInputValues | undefined,
|
|
110
|
+
key: string,
|
|
111
|
+
): DataContractValue | undefined {
|
|
112
|
+
const value = readValue(values, key);
|
|
113
|
+
return isDataContractValue(value) ? value : undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function getStringMetadataValue(
|
|
117
|
+
metadata: DataContractValue | undefined,
|
|
118
|
+
key: string,
|
|
119
|
+
): string | undefined {
|
|
120
|
+
if (!isDataContractRecord(metadata)) return undefined;
|
|
121
|
+
const value = readValue(metadata, key);
|
|
122
|
+
return typeof value === 'string' ? value : undefined;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function serializeEndpointUrlValue(value: DataContractValue): string {
|
|
126
|
+
if (typeof value === 'string') return value;
|
|
127
|
+
if (typeof value === 'number' || typeof value === 'boolean') return value.toString();
|
|
128
|
+
if (value === null) return '';
|
|
129
|
+
return JSON.stringify(value);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function readValue(
|
|
133
|
+
values: Readonly<Record<string, DataContractValue>> | undefined,
|
|
134
|
+
key: string,
|
|
135
|
+
): DataContractValue | undefined {
|
|
136
|
+
return Object.entries(values ?? {}).find(([entryKey]) => entryKey === key)?.[1];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function isDataContractValue(value: unknown): value is DataContractValue {
|
|
140
|
+
if (value === null || ['string', 'number', 'boolean'].includes(typeof value)) return true;
|
|
141
|
+
if (Array.isArray(value)) return value.every(isDataContractValue);
|
|
142
|
+
return typeof value === 'object' && Object.values(value).every(isDataContractValue);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function isDataContractRecord(
|
|
146
|
+
value: DataContractValue | undefined,
|
|
147
|
+
): value is Record<string, DataContractValue> {
|
|
148
|
+
return (
|
|
149
|
+
value !== undefined && typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
150
|
+
);
|
|
151
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ApiDefinition,
|
|
3
|
+
CredentialRef,
|
|
4
|
+
DataContractValue,
|
|
5
|
+
DataSourceDiagnostic,
|
|
6
|
+
EndpointId,
|
|
7
|
+
OperationId,
|
|
8
|
+
} from '@ankhorage/contracts/data';
|
|
9
|
+
|
|
10
|
+
export type EndpointTestInputValues = Readonly<Record<string, DataContractValue>>;
|
|
11
|
+
export type EndpointTestHeaders = Readonly<Record<string, string>>;
|
|
12
|
+
|
|
13
|
+
export interface EndpointTestCredential {
|
|
14
|
+
readonly headers?: EndpointTestHeaders;
|
|
15
|
+
readonly query?: EndpointTestInputValues;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type EndpointTestCredentialResolver = (
|
|
19
|
+
credential: CredentialRef,
|
|
20
|
+
) => EndpointTestCredential | Promise<EndpointTestCredential | undefined> | undefined;
|
|
21
|
+
|
|
22
|
+
export interface EndpointTestFetchInit {
|
|
23
|
+
readonly method: string;
|
|
24
|
+
readonly headers: EndpointTestHeaders;
|
|
25
|
+
readonly body?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface EndpointTestFetchResponse {
|
|
29
|
+
readonly status: number;
|
|
30
|
+
readonly headers?: EndpointTestHeaders;
|
|
31
|
+
text(): Promise<string>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type EndpointTestFetch = (
|
|
35
|
+
url: string,
|
|
36
|
+
init: EndpointTestFetchInit,
|
|
37
|
+
) => Promise<EndpointTestFetchResponse>;
|
|
38
|
+
|
|
39
|
+
export interface EndpointTestInput {
|
|
40
|
+
readonly api: ApiDefinition;
|
|
41
|
+
readonly endpointId: EndpointId;
|
|
42
|
+
readonly operationId: OperationId;
|
|
43
|
+
readonly values?: EndpointTestInputValues;
|
|
44
|
+
readonly dryRun?: boolean;
|
|
45
|
+
readonly fetch?: EndpointTestFetch;
|
|
46
|
+
readonly credentialResolver?: EndpointTestCredentialResolver;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface EndpointTestRequestDiagnostic {
|
|
50
|
+
readonly apiId: string;
|
|
51
|
+
readonly endpointId: EndpointId;
|
|
52
|
+
readonly operationId: OperationId;
|
|
53
|
+
readonly url: string;
|
|
54
|
+
readonly method: string;
|
|
55
|
+
readonly headers: EndpointTestHeaders;
|
|
56
|
+
readonly body?: string;
|
|
57
|
+
readonly dryRun: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface EndpointTestResponseDiagnostic {
|
|
61
|
+
readonly status: number;
|
|
62
|
+
readonly ok: boolean;
|
|
63
|
+
readonly headers?: EndpointTestHeaders;
|
|
64
|
+
readonly bodyText?: string;
|
|
65
|
+
readonly parsedBody?: DataContractValue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export type EndpointTestResult =
|
|
69
|
+
| {
|
|
70
|
+
readonly ok: true;
|
|
71
|
+
readonly request: EndpointTestRequestDiagnostic;
|
|
72
|
+
readonly response?: EndpointTestResponseDiagnostic;
|
|
73
|
+
readonly data?: DataContractValue;
|
|
74
|
+
readonly diagnostics: readonly DataSourceDiagnostic[];
|
|
75
|
+
}
|
|
76
|
+
| {
|
|
77
|
+
readonly ok: false;
|
|
78
|
+
readonly request?: EndpointTestRequestDiagnostic;
|
|
79
|
+
readonly response?: EndpointTestResponseDiagnostic;
|
|
80
|
+
readonly diagnostics: readonly DataSourceDiagnostic[];
|
|
81
|
+
};
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { DataEndpointConfig, DataOperationConfig, DataSchema, DataSourceDiagnostic, DataSourceDiagnosticResult, GeneratedApiCrudOperation, GeneratedApiDefinition, GeneratedApiResourceDefinition, GeneratedRestApiDataSourceConfig, OperationId } from '@ankhorage/contracts/data';
|
|
2
|
-
import type { DbCollectionDefinition } from '@ankhorage/contracts/db';
|
|
3
|
-
export declare function createGeneratedApiDataSource(definition: GeneratedApiDefinition): DataSourceDiagnosticResult<GeneratedRestApiDataSourceConfig>;
|
|
4
|
-
export declare function validateGeneratedApiDefinition(definition: GeneratedApiDefinition): readonly DataSourceDiagnostic[];
|
|
5
|
-
export declare function normalizeGeneratedApiDataSource(definition: GeneratedApiDefinition): GeneratedRestApiDataSourceConfig;
|
|
6
|
-
export declare function createGeneratedApiEndpoint(definition: GeneratedApiDefinition, resource: GeneratedApiResourceDefinition): DataEndpointConfig;
|
|
7
|
-
export declare function createGeneratedApiOperation(definition: GeneratedApiDefinition, resource: GeneratedApiResourceDefinition, operation: GeneratedApiCrudOperation): DataOperationConfig;
|
|
8
|
-
export declare function createGeneratedApiOperationId(resourceId: string, operation: GeneratedApiCrudOperation): OperationId;
|
|
9
|
-
export declare function createGeneratedApiResourceSchema(collection: DbCollectionDefinition): DataSchema;
|
|
10
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/generated-api/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,kBAAkB,EAClB,mBAAmB,EAGnB,UAAU,EACV,oBAAoB,EACpB,0BAA0B,EAE1B,yBAAyB,EACzB,sBAAsB,EAEtB,8BAA8B,EAC9B,gCAAgC,EAChC,WAAW,EACZ,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EACV,sBAAsB,EAGvB,MAAM,yBAAyB,CAAC;AAIjC,wBAAgB,4BAA4B,CAC1C,UAAU,EAAE,sBAAsB,GACjC,0BAA0B,CAAC,gCAAgC,CAAC,CAW9D;AAED,wBAAgB,8BAA8B,CAC5C,UAAU,EAAE,sBAAsB,GACjC,SAAS,oBAAoB,EAAE,CA6BjC;AAED,wBAAgB,+BAA+B,CAC7C,UAAU,EAAE,sBAAsB,GACjC,gCAAgC,CAsBlC;AAED,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,sBAAsB,EAClC,QAAQ,EAAE,8BAA8B,GACvC,kBAAkB,CAiBpB;AAED,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,sBAAsB,EAClC,QAAQ,EAAE,8BAA8B,EACxC,SAAS,EAAE,yBAAyB,GACnC,mBAAmB,CAqBrB;AAED,wBAAgB,6BAA6B,CAC3C,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,yBAAyB,GACnC,WAAW,CAEb;AAED,wBAAgB,gCAAgC,CAAC,UAAU,EAAE,sBAAsB,GAAG,UAAU,CAe/F"}
|
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
import { validateGeneratedApiCollection } from './validateGeneratedApiCollection';
|
|
2
|
-
export function createGeneratedApiDataSource(definition) {
|
|
3
|
-
const diagnostics = validateGeneratedApiDefinition(definition);
|
|
4
|
-
if (diagnostics.some((diagnostic) => diagnostic.severity === 'error')) {
|
|
5
|
-
return { ok: false, diagnostics };
|
|
6
|
-
}
|
|
7
|
-
return {
|
|
8
|
-
ok: true,
|
|
9
|
-
data: normalizeGeneratedApiDataSource(definition),
|
|
10
|
-
diagnostics,
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
export function validateGeneratedApiDefinition(definition) {
|
|
14
|
-
const diagnostics = [];
|
|
15
|
-
const resourceIds = new Set();
|
|
16
|
-
if (definition.basePath.trim().length === 0 || !definition.basePath.startsWith('/')) {
|
|
17
|
-
diagnostics.push({
|
|
18
|
-
code: 'invalid-config',
|
|
19
|
-
dataSourceId: definition.id,
|
|
20
|
-
message: 'Generated API basePath must be a non-empty absolute path.',
|
|
21
|
-
path: 'basePath',
|
|
22
|
-
severity: 'error',
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
if (definition.resources.length === 0) {
|
|
26
|
-
diagnostics.push({
|
|
27
|
-
code: 'invalid-config',
|
|
28
|
-
dataSourceId: definition.id,
|
|
29
|
-
message: 'Generated API requires at least one resource.',
|
|
30
|
-
path: 'resources',
|
|
31
|
-
severity: 'error',
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
for (const resource of definition.resources) {
|
|
35
|
-
validateGeneratedApiResource(definition, resource, resourceIds, diagnostics);
|
|
36
|
-
}
|
|
37
|
-
return diagnostics;
|
|
38
|
-
}
|
|
39
|
-
export function normalizeGeneratedApiDataSource(definition) {
|
|
40
|
-
const endpoints = {};
|
|
41
|
-
const schemas = {};
|
|
42
|
-
for (const resource of definition.resources) {
|
|
43
|
-
endpoints[resource.id] = createGeneratedApiEndpoint(definition, resource);
|
|
44
|
-
schemas[resource.id] = createGeneratedApiResourceSchema(resource.collection);
|
|
45
|
-
}
|
|
46
|
-
return {
|
|
47
|
-
id: definition.id,
|
|
48
|
-
kind: 'api',
|
|
49
|
-
origin: 'generated',
|
|
50
|
-
protocol: 'rest',
|
|
51
|
-
generatedApiId: definition.id,
|
|
52
|
-
name: definition.name,
|
|
53
|
-
description: definition.description,
|
|
54
|
-
adapter: definition.database,
|
|
55
|
-
endpoints,
|
|
56
|
-
schemas,
|
|
57
|
-
metadata: createGeneratedApiSourceMetadata(definition),
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
export function createGeneratedApiEndpoint(definition, resource) {
|
|
61
|
-
const operations = {};
|
|
62
|
-
for (const operation of resource.operations) {
|
|
63
|
-
const config = createGeneratedApiOperation(definition, resource, operation);
|
|
64
|
-
operations[config.id] = config;
|
|
65
|
-
}
|
|
66
|
-
return {
|
|
67
|
-
id: resource.id,
|
|
68
|
-
kind: 'database',
|
|
69
|
-
name: resource.name,
|
|
70
|
-
description: resource.description,
|
|
71
|
-
path: resource.path,
|
|
72
|
-
operations,
|
|
73
|
-
metadata: createGeneratedApiResourceMetadata(definition, resource),
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
export function createGeneratedApiOperation(definition, resource, operation) {
|
|
77
|
-
const operationId = createGeneratedApiOperationId(resource.id, operation);
|
|
78
|
-
const primaryKey = resolveGeneratedApiPrimaryKey(resource.collection);
|
|
79
|
-
const primaryKeyParameter = primaryKey === undefined ? undefined : createPrimaryKeyParameter(primaryKey);
|
|
80
|
-
return {
|
|
81
|
-
id: operationId,
|
|
82
|
-
endpointId: resource.id,
|
|
83
|
-
name: createGeneratedApiOperationName(resource, operation),
|
|
84
|
-
protocol: 'database',
|
|
85
|
-
intent: mapGeneratedApiOperationIntent(operation),
|
|
86
|
-
path: resource.path,
|
|
87
|
-
request: createGeneratedApiOperationRequest(resource.collection, operation, primaryKeyParameter),
|
|
88
|
-
response: createGeneratedApiOperationResponse(resource.collection, operation),
|
|
89
|
-
metadata: createGeneratedApiOperationMetadata(definition, resource, operation),
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
export function createGeneratedApiOperationId(resourceId, operation) {
|
|
93
|
-
return `${resourceId}.${operation}`;
|
|
94
|
-
}
|
|
95
|
-
export function createGeneratedApiResourceSchema(collection) {
|
|
96
|
-
const properties = {};
|
|
97
|
-
const required = [];
|
|
98
|
-
for (const field of collection.fields) {
|
|
99
|
-
properties[field.name] = createGeneratedApiFieldSchema(field);
|
|
100
|
-
if (field.required === true)
|
|
101
|
-
required.push(field.name);
|
|
102
|
-
}
|
|
103
|
-
return {
|
|
104
|
-
type: 'object',
|
|
105
|
-
title: collection.name,
|
|
106
|
-
required: required.length > 0 ? required : undefined,
|
|
107
|
-
properties,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
function validateGeneratedApiResource(definition, resource, resourceIds, diagnostics) {
|
|
111
|
-
if (resource.id.trim().length === 0) {
|
|
112
|
-
diagnostics.push(resourceDiagnostic(definition, resource, 'Resource IDs must be non-empty.', 'id'));
|
|
113
|
-
}
|
|
114
|
-
else if (resourceIds.has(resource.id)) {
|
|
115
|
-
diagnostics.push(resourceDiagnostic(definition, resource, `Resource ID '${resource.id}' is duplicated.`, 'id'));
|
|
116
|
-
}
|
|
117
|
-
resourceIds.add(resource.id);
|
|
118
|
-
if (!resource.path.startsWith('/')) {
|
|
119
|
-
diagnostics.push(resourceDiagnostic(definition, resource, 'Generated API resource paths must start with `/`.', 'path'));
|
|
120
|
-
}
|
|
121
|
-
if (resource.collection.name.trim().length === 0) {
|
|
122
|
-
diagnostics.push(resourceDiagnostic(definition, resource, 'Generated API resource collections must have a non-empty name.', 'collection.name'));
|
|
123
|
-
}
|
|
124
|
-
diagnostics.push(...validateGeneratedApiCollection(definition, resource));
|
|
125
|
-
if (resource.operations.length === 0) {
|
|
126
|
-
diagnostics.push(resourceDiagnostic(definition, resource, 'Generated API resources require at least one operation.', 'operations'));
|
|
127
|
-
}
|
|
128
|
-
const primaryKey = resolveGeneratedApiPrimaryKey(resource.collection);
|
|
129
|
-
if (primaryKey === undefined &&
|
|
130
|
-
resource.operations.some((operation) => operation !== 'list' && operation !== 'create')) {
|
|
131
|
-
diagnostics.push(resourceDiagnostic(definition, resource, 'Generated API resources need a primaryKey field for read/update/delete operations.', 'collection.primaryKey'));
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
function resourceDiagnostic(definition, resource, message, path) {
|
|
135
|
-
return {
|
|
136
|
-
code: 'invalid-config',
|
|
137
|
-
dataSourceId: definition.id,
|
|
138
|
-
endpointId: resource.id,
|
|
139
|
-
message,
|
|
140
|
-
path: `resources.${resource.id}.${path}`,
|
|
141
|
-
severity: 'error',
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
function createGeneratedApiSourceMetadata(definition) {
|
|
145
|
-
return {
|
|
146
|
-
...readMetadataRecord(definition.metadata),
|
|
147
|
-
basePath: definition.basePath,
|
|
148
|
-
source: 'generated-api',
|
|
149
|
-
auth: createGeneratedApiAuthMetadata(definition.auth),
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
function createGeneratedApiResourceMetadata(definition, resource) {
|
|
153
|
-
return {
|
|
154
|
-
...readMetadataRecord(resource.metadata),
|
|
155
|
-
adapterId: definition.database.id,
|
|
156
|
-
collection: resource.collection.name,
|
|
157
|
-
schema: resource.collection.schema ?? null,
|
|
158
|
-
source: 'generated-api',
|
|
159
|
-
seed: resource.seed ?? [],
|
|
160
|
-
policies: createGeneratedApiPolicyMetadata(resource.policies),
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
function createGeneratedApiOperationMetadata(definition, resource, operation) {
|
|
164
|
-
return {
|
|
165
|
-
adapterId: definition.database.id,
|
|
166
|
-
collection: resource.collection.name,
|
|
167
|
-
operation,
|
|
168
|
-
schema: resource.collection.schema ?? null,
|
|
169
|
-
source: 'generated-api',
|
|
170
|
-
policies: createGeneratedApiPolicyMetadata(resource.policies?.filter((policy) => policy.operation === undefined || policy.operation === operation)),
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
function createGeneratedApiAuthMetadata(auth) {
|
|
174
|
-
if (auth === undefined)
|
|
175
|
-
return null;
|
|
176
|
-
const metadata = {};
|
|
177
|
-
if (auth.required !== undefined)
|
|
178
|
-
metadata.required = auth.required;
|
|
179
|
-
if (auth.roles !== undefined)
|
|
180
|
-
metadata.roles = auth.roles;
|
|
181
|
-
if (auth.permissions !== undefined)
|
|
182
|
-
metadata.permissions = auth.permissions;
|
|
183
|
-
if (auth.policy !== undefined)
|
|
184
|
-
metadata.policy = auth.policy;
|
|
185
|
-
if (auth.metadata !== undefined)
|
|
186
|
-
metadata.metadata = auth.metadata;
|
|
187
|
-
return metadata;
|
|
188
|
-
}
|
|
189
|
-
function createGeneratedApiPolicyMetadata(policies) {
|
|
190
|
-
return (policies ?? []).map((policy) => {
|
|
191
|
-
const metadata = { id: policy.id };
|
|
192
|
-
if (policy.operation !== undefined)
|
|
193
|
-
metadata.operation = policy.operation;
|
|
194
|
-
return metadata;
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
function createGeneratedApiOperationName(resource, operation) {
|
|
198
|
-
return `${operation} ${resource.name ?? resource.id}`;
|
|
199
|
-
}
|
|
200
|
-
function mapGeneratedApiOperationIntent(operation) {
|
|
201
|
-
if (operation === 'list' || operation === 'read')
|
|
202
|
-
return 'read';
|
|
203
|
-
if (operation === 'create')
|
|
204
|
-
return 'create';
|
|
205
|
-
if (operation === 'update')
|
|
206
|
-
return 'update';
|
|
207
|
-
return 'delete';
|
|
208
|
-
}
|
|
209
|
-
function createGeneratedApiOperationRequest(collection, operation, primaryKeyParameter) {
|
|
210
|
-
if (operation === 'list') {
|
|
211
|
-
return {
|
|
212
|
-
parameters: [
|
|
213
|
-
{ name: 'limit', location: 'query', schema: { type: 'integer' } },
|
|
214
|
-
{ name: 'offset', location: 'query', schema: { type: 'integer' } },
|
|
215
|
-
],
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
if (operation === 'read' || operation === 'delete') {
|
|
219
|
-
return primaryKeyParameter === undefined ? undefined : { parameters: [primaryKeyParameter] };
|
|
220
|
-
}
|
|
221
|
-
const parameters = operation === 'update' && primaryKeyParameter !== undefined ? [primaryKeyParameter] : undefined;
|
|
222
|
-
return {
|
|
223
|
-
parameters,
|
|
224
|
-
schema: createGeneratedApiResourceSchema(collection),
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
function createGeneratedApiOperationResponse(collection, operation) {
|
|
228
|
-
const schema = createGeneratedApiResourceSchema(collection);
|
|
229
|
-
if (operation === 'list')
|
|
230
|
-
return { schema: { type: 'array', items: schema } };
|
|
231
|
-
if (operation === 'delete') {
|
|
232
|
-
return { schema: { type: 'object', properties: { deleted: { type: 'boolean' } } } };
|
|
233
|
-
}
|
|
234
|
-
return { schema };
|
|
235
|
-
}
|
|
236
|
-
function createPrimaryKeyParameter(primaryKey) {
|
|
237
|
-
return {
|
|
238
|
-
name: primaryKey,
|
|
239
|
-
location: 'path',
|
|
240
|
-
required: true,
|
|
241
|
-
schema: { type: 'string' },
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
function resolveGeneratedApiPrimaryKey(collection) {
|
|
245
|
-
return collection.primaryKey ?? collection.fields.find((field) => field.unique === true)?.name;
|
|
246
|
-
}
|
|
247
|
-
function createGeneratedApiFieldSchema(field) {
|
|
248
|
-
return {
|
|
249
|
-
...mapDbFieldTypeToDataSchema(field.type),
|
|
250
|
-
default: field.defaultValue,
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
function mapDbFieldTypeToDataSchema(type) {
|
|
254
|
-
if (type === 'boolean')
|
|
255
|
-
return { type: 'boolean' };
|
|
256
|
-
if (type === 'datetime')
|
|
257
|
-
return { type: 'string', format: 'date-time' };
|
|
258
|
-
if (type === 'json')
|
|
259
|
-
return { type: 'object' };
|
|
260
|
-
if (type === 'number')
|
|
261
|
-
return { type: 'number' };
|
|
262
|
-
if (type === 'uuid')
|
|
263
|
-
return { type: 'string', format: 'uuid' };
|
|
264
|
-
return { type: 'string' };
|
|
265
|
-
}
|
|
266
|
-
function readMetadataRecord(value) {
|
|
267
|
-
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
268
|
-
return {};
|
|
269
|
-
const metadata = {};
|
|
270
|
-
for (const [key, item] of Object.entries(value))
|
|
271
|
-
metadata[key] = item;
|
|
272
|
-
return metadata;
|
|
273
|
-
}
|
|
274
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/generated-api/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n DataContractValue,\n DataEndpointConfig,\n DataOperationConfig,\n DataOperationIntent,\n DataOperationParameter,\n DataSchema,\n DataSourceDiagnostic,\n DataSourceDiagnosticResult,\n GeneratedApiAuthRequirement,\n GeneratedApiCrudOperation,\n GeneratedApiDefinition,\n GeneratedApiOperationPolicyRef,\n GeneratedApiResourceDefinition,\n GeneratedRestApiDataSourceConfig,\n OperationId,\n} from '@ankhorage/contracts/data';\nimport type {\n DbCollectionDefinition,\n DbFieldDefinition,\n DbFieldType,\n} from '@ankhorage/contracts/db';\n\nimport { validateGeneratedApiCollection } from './validateGeneratedApiCollection';\n\nexport function createGeneratedApiDataSource(\n definition: GeneratedApiDefinition,\n): DataSourceDiagnosticResult<GeneratedRestApiDataSourceConfig> {\n const diagnostics = validateGeneratedApiDefinition(definition);\n if (diagnostics.some((diagnostic) => diagnostic.severity === 'error')) {\n return { ok: false, diagnostics };\n }\n\n return {\n ok: true,\n data: normalizeGeneratedApiDataSource(definition),\n diagnostics,\n };\n}\n\nexport function validateGeneratedApiDefinition(\n definition: GeneratedApiDefinition,\n): readonly DataSourceDiagnostic[] {\n const diagnostics: DataSourceDiagnostic[] = [];\n const resourceIds = new Set<string>();\n\n if (definition.basePath.trim().length === 0 || !definition.basePath.startsWith('/')) {\n diagnostics.push({\n code: 'invalid-config',\n dataSourceId: definition.id,\n message: 'Generated API basePath must be a non-empty absolute path.',\n path: 'basePath',\n severity: 'error',\n });\n }\n\n if (definition.resources.length === 0) {\n diagnostics.push({\n code: 'invalid-config',\n dataSourceId: definition.id,\n message: 'Generated API requires at least one resource.',\n path: 'resources',\n severity: 'error',\n });\n }\n\n for (const resource of definition.resources) {\n validateGeneratedApiResource(definition, resource, resourceIds, diagnostics);\n }\n\n return diagnostics;\n}\n\nexport function normalizeGeneratedApiDataSource(\n definition: GeneratedApiDefinition,\n): GeneratedRestApiDataSourceConfig {\n const endpoints: Record<string, DataEndpointConfig> = {};\n const schemas: Record<string, DataSchema> = {};\n\n for (const resource of definition.resources) {\n endpoints[resource.id] = createGeneratedApiEndpoint(definition, resource);\n schemas[resource.id] = createGeneratedApiResourceSchema(resource.collection);\n }\n\n return {\n id: definition.id,\n kind: 'api',\n origin: 'generated',\n protocol: 'rest',\n generatedApiId: definition.id,\n name: definition.name,\n description: definition.description,\n adapter: definition.database,\n endpoints,\n schemas,\n metadata: createGeneratedApiSourceMetadata(definition),\n };\n}\n\nexport function createGeneratedApiEndpoint(\n definition: GeneratedApiDefinition,\n resource: GeneratedApiResourceDefinition,\n): DataEndpointConfig {\n const operations: Record<OperationId, DataOperationConfig> = {};\n\n for (const operation of resource.operations) {\n const config = createGeneratedApiOperation(definition, resource, operation);\n operations[config.id] = config;\n }\n\n return {\n id: resource.id,\n kind: 'database',\n name: resource.name,\n description: resource.description,\n path: resource.path,\n operations,\n metadata: createGeneratedApiResourceMetadata(definition, resource),\n };\n}\n\nexport function createGeneratedApiOperation(\n definition: GeneratedApiDefinition,\n resource: GeneratedApiResourceDefinition,\n operation: GeneratedApiCrudOperation,\n): DataOperationConfig {\n const operationId = createGeneratedApiOperationId(resource.id, operation);\n const primaryKey = resolveGeneratedApiPrimaryKey(resource.collection);\n const primaryKeyParameter =\n primaryKey === undefined ? undefined : createPrimaryKeyParameter(primaryKey);\n\n return {\n id: operationId,\n endpointId: resource.id,\n name: createGeneratedApiOperationName(resource, operation),\n protocol: 'database',\n intent: mapGeneratedApiOperationIntent(operation),\n path: resource.path,\n request: createGeneratedApiOperationRequest(\n resource.collection,\n operation,\n primaryKeyParameter,\n ),\n response: createGeneratedApiOperationResponse(resource.collection, operation),\n metadata: createGeneratedApiOperationMetadata(definition, resource, operation),\n };\n}\n\nexport function createGeneratedApiOperationId(\n resourceId: string,\n operation: GeneratedApiCrudOperation,\n): OperationId {\n return `${resourceId}.${operation}`;\n}\n\nexport function createGeneratedApiResourceSchema(collection: DbCollectionDefinition): DataSchema {\n const properties: Record<string, DataSchema> = {};\n const required: string[] = [];\n\n for (const field of collection.fields) {\n properties[field.name] = createGeneratedApiFieldSchema(field);\n if (field.required === true) required.push(field.name);\n }\n\n return {\n type: 'object',\n title: collection.name,\n required: required.length > 0 ? required : undefined,\n properties,\n };\n}\n\nfunction validateGeneratedApiResource(\n definition: GeneratedApiDefinition,\n resource: GeneratedApiResourceDefinition,\n resourceIds: Set<string>,\n diagnostics: DataSourceDiagnostic[],\n): void {\n if (resource.id.trim().length === 0) {\n diagnostics.push(\n resourceDiagnostic(definition, resource, 'Resource IDs must be non-empty.', 'id'),\n );\n } else if (resourceIds.has(resource.id)) {\n diagnostics.push(\n resourceDiagnostic(definition, resource, `Resource ID '${resource.id}' is duplicated.`, 'id'),\n );\n }\n resourceIds.add(resource.id);\n\n if (!resource.path.startsWith('/')) {\n diagnostics.push(\n resourceDiagnostic(\n definition,\n resource,\n 'Generated API resource paths must start with `/`.',\n 'path',\n ),\n );\n }\n\n if (resource.collection.name.trim().length === 0) {\n diagnostics.push(\n resourceDiagnostic(\n definition,\n resource,\n 'Generated API resource collections must have a non-empty name.',\n 'collection.name',\n ),\n );\n }\n\n diagnostics.push(...validateGeneratedApiCollection(definition, resource));\n\n if (resource.operations.length === 0) {\n diagnostics.push(\n resourceDiagnostic(\n definition,\n resource,\n 'Generated API resources require at least one operation.',\n 'operations',\n ),\n );\n }\n\n const primaryKey = resolveGeneratedApiPrimaryKey(resource.collection);\n if (\n primaryKey === undefined &&\n resource.operations.some((operation) => operation !== 'list' && operation !== 'create')\n ) {\n diagnostics.push(\n resourceDiagnostic(\n definition,\n resource,\n 'Generated API resources need a primaryKey field for read/update/delete operations.',\n 'collection.primaryKey',\n ),\n );\n }\n}\n\nfunction resourceDiagnostic(\n definition: GeneratedApiDefinition,\n resource: GeneratedApiResourceDefinition,\n message: string,\n path: string,\n): DataSourceDiagnostic {\n return {\n code: 'invalid-config',\n dataSourceId: definition.id,\n endpointId: resource.id,\n message,\n path: `resources.${resource.id}.${path}`,\n severity: 'error',\n };\n}\n\nfunction createGeneratedApiSourceMetadata(definition: GeneratedApiDefinition): DataContractValue {\n return {\n ...readMetadataRecord(definition.metadata),\n basePath: definition.basePath,\n source: 'generated-api',\n auth: createGeneratedApiAuthMetadata(definition.auth),\n };\n}\n\nfunction createGeneratedApiResourceMetadata(\n definition: GeneratedApiDefinition,\n resource: GeneratedApiResourceDefinition,\n): DataContractValue {\n return {\n ...readMetadataRecord(resource.metadata),\n adapterId: definition.database.id,\n collection: resource.collection.name,\n schema: resource.collection.schema ?? null,\n source: 'generated-api',\n seed: resource.seed ?? [],\n policies: createGeneratedApiPolicyMetadata(resource.policies),\n };\n}\n\nfunction createGeneratedApiOperationMetadata(\n definition: GeneratedApiDefinition,\n resource: GeneratedApiResourceDefinition,\n operation: GeneratedApiCrudOperation,\n): DataContractValue {\n return {\n adapterId: definition.database.id,\n collection: resource.collection.name,\n operation,\n schema: resource.collection.schema ?? null,\n source: 'generated-api',\n policies: createGeneratedApiPolicyMetadata(\n resource.policies?.filter(\n (policy) => policy.operation === undefined || policy.operation === operation,\n ),\n ),\n };\n}\n\nfunction createGeneratedApiAuthMetadata(\n auth: GeneratedApiAuthRequirement | undefined,\n): DataContractValue {\n if (auth === undefined) return null;\n\n const metadata: Record<string, DataContractValue> = {};\n if (auth.required !== undefined) metadata.required = auth.required;\n if (auth.roles !== undefined) metadata.roles = auth.roles;\n if (auth.permissions !== undefined) metadata.permissions = auth.permissions;\n if (auth.policy !== undefined) metadata.policy = auth.policy;\n if (auth.metadata !== undefined) metadata.metadata = auth.metadata;\n return metadata;\n}\n\nfunction createGeneratedApiPolicyMetadata(\n policies: readonly GeneratedApiOperationPolicyRef[] | undefined,\n): readonly DataContractValue[] {\n return (policies ?? []).map((policy) => {\n const metadata: Record<string, DataContractValue> = { id: policy.id };\n if (policy.operation !== undefined) metadata.operation = policy.operation;\n return metadata;\n });\n}\n\nfunction createGeneratedApiOperationName(\n resource: GeneratedApiResourceDefinition,\n operation: GeneratedApiCrudOperation,\n): string {\n return `${operation} ${resource.name ?? resource.id}`;\n}\n\nfunction mapGeneratedApiOperationIntent(operation: GeneratedApiCrudOperation): DataOperationIntent {\n if (operation === 'list' || operation === 'read') return 'read';\n if (operation === 'create') return 'create';\n if (operation === 'update') return 'update';\n return 'delete';\n}\n\nfunction createGeneratedApiOperationRequest(\n collection: DbCollectionDefinition,\n operation: GeneratedApiCrudOperation,\n primaryKeyParameter: DataOperationParameter | undefined,\n): DataOperationConfig['request'] {\n if (operation === 'list') {\n return {\n parameters: [\n { name: 'limit', location: 'query', schema: { type: 'integer' } },\n { name: 'offset', location: 'query', schema: { type: 'integer' } },\n ],\n };\n }\n\n if (operation === 'read' || operation === 'delete') {\n return primaryKeyParameter === undefined ? undefined : { parameters: [primaryKeyParameter] };\n }\n\n const parameters =\n operation === 'update' && primaryKeyParameter !== undefined ? [primaryKeyParameter] : undefined;\n\n return {\n parameters,\n schema: createGeneratedApiResourceSchema(collection),\n };\n}\n\nfunction createGeneratedApiOperationResponse(\n collection: DbCollectionDefinition,\n operation: GeneratedApiCrudOperation,\n): DataOperationConfig['response'] {\n const schema = createGeneratedApiResourceSchema(collection);\n\n if (operation === 'list') return { schema: { type: 'array', items: schema } };\n if (operation === 'delete') {\n return { schema: { type: 'object', properties: { deleted: { type: 'boolean' } } } };\n }\n return { schema };\n}\n\nfunction createPrimaryKeyParameter(primaryKey: string): DataOperationParameter {\n return {\n name: primaryKey,\n location: 'path',\n required: true,\n schema: { type: 'string' },\n };\n}\n\nfunction resolveGeneratedApiPrimaryKey(collection: DbCollectionDefinition): string | undefined {\n return collection.primaryKey ?? collection.fields.find((field) => field.unique === true)?.name;\n}\n\nfunction createGeneratedApiFieldSchema(field: DbFieldDefinition): DataSchema {\n return {\n ...mapDbFieldTypeToDataSchema(field.type),\n default: field.defaultValue,\n };\n}\n\nfunction mapDbFieldTypeToDataSchema(type: DbFieldType): DataSchema {\n if (type === 'boolean') return { type: 'boolean' };\n if (type === 'datetime') return { type: 'string', format: 'date-time' };\n if (type === 'json') return { type: 'object' };\n if (type === 'number') return { type: 'number' };\n if (type === 'uuid') return { type: 'string', format: 'uuid' };\n return { type: 'string' };\n}\n\nfunction readMetadataRecord(\n value: DataContractValue | undefined,\n): Record<string, DataContractValue> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return {};\n const metadata: Record<string, DataContractValue> = {};\n for (const [key, item] of Object.entries(value)) metadata[key] = item;\n return metadata;\n}\n"]}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import type { DataSourceDiagnostic, GeneratedApiDefinition, GeneratedApiResourceDefinition } from '@ankhorage/contracts/data';
|
|
2
|
-
export declare function validateGeneratedApiCollection(definition: GeneratedApiDefinition, resource: GeneratedApiResourceDefinition): readonly DataSourceDiagnostic[];
|
|
3
|
-
//# sourceMappingURL=validateGeneratedApiCollection.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"validateGeneratedApiCollection.d.ts","sourceRoot":"","sources":["../../src/generated-api/validateGeneratedApiCollection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oBAAoB,EACpB,sBAAsB,EACtB,8BAA8B,EAC/B,MAAM,2BAA2B,CAAC;AAEnC,wBAAgB,8BAA8B,CAC5C,UAAU,EAAE,sBAAsB,EAClC,QAAQ,EAAE,8BAA8B,GACvC,SAAS,oBAAoB,EAAE,CAkDjC"}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
export function validateGeneratedApiCollection(definition, resource) {
|
|
2
|
-
const diagnostics = [];
|
|
3
|
-
const { fields, primaryKey } = resource.collection;
|
|
4
|
-
const fieldNames = new Set();
|
|
5
|
-
if (fields.length === 0) {
|
|
6
|
-
diagnostics.push(diagnostic(definition, resource, 'Generated API resources require fields.', 'fields'));
|
|
7
|
-
}
|
|
8
|
-
fields.forEach((field, index) => {
|
|
9
|
-
const name = field.name.trim();
|
|
10
|
-
if (!name) {
|
|
11
|
-
diagnostics.push(diagnostic(definition, resource, 'Generated API field names must be non-empty.', `fields.${index}.name`));
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
if (fieldNames.has(name)) {
|
|
15
|
-
diagnostics.push(diagnostic(definition, resource, `Generated API field '${name}' is duplicated.`, `fields.${index}.name`));
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
fieldNames.add(name);
|
|
19
|
-
});
|
|
20
|
-
if (primaryKey !== undefined && !fields.some((field) => field.name === primaryKey)) {
|
|
21
|
-
diagnostics.push(diagnostic(definition, resource, `Primary key '${primaryKey}' does not match a collection field.`, 'primaryKey'));
|
|
22
|
-
}
|
|
23
|
-
return diagnostics;
|
|
24
|
-
}
|
|
25
|
-
function diagnostic(definition, resource, message, path) {
|
|
26
|
-
return {
|
|
27
|
-
code: 'invalid-config',
|
|
28
|
-
dataSourceId: definition.id,
|
|
29
|
-
endpointId: resource.id,
|
|
30
|
-
message,
|
|
31
|
-
path: `resources.${resource.id}.collection.${path}`,
|
|
32
|
-
severity: 'error',
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
//# sourceMappingURL=validateGeneratedApiCollection.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"validateGeneratedApiCollection.js","sourceRoot":"","sources":["../../src/generated-api/validateGeneratedApiCollection.ts"],"names":[],"mappings":"AAMA,MAAM,UAAU,8BAA8B,CAC5C,UAAkC,EAClC,QAAwC;IAExC,MAAM,WAAW,GAA2B,EAAE,CAAC;IAC/C,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,QAAQ,CAAC,UAAU,CAAC;IACnD,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IAErC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,WAAW,CAAC,IAAI,CACd,UAAU,CAAC,UAAU,EAAE,QAAQ,EAAE,yCAAyC,EAAE,QAAQ,CAAC,CACtF,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QAC9B,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAC/B,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,WAAW,CAAC,IAAI,CACd,UAAU,CACR,UAAU,EACV,QAAQ,EACR,8CAA8C,EAC9C,UAAU,KAAK,OAAO,CACvB,CACF,CAAC;YACF,OAAO;QACT,CAAC;QACD,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACzB,WAAW,CAAC,IAAI,CACd,UAAU,CACR,UAAU,EACV,QAAQ,EACR,wBAAwB,IAAI,kBAAkB,EAC9C,UAAU,KAAK,OAAO,CACvB,CACF,CAAC;YACF,OAAO;QACT,CAAC;QACD,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,IAAI,UAAU,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC,EAAE,CAAC;QACnF,WAAW,CAAC,IAAI,CACd,UAAU,CACR,UAAU,EACV,QAAQ,EACR,gBAAgB,UAAU,sCAAsC,EAChE,YAAY,CACb,CACF,CAAC;IACJ,CAAC;IAED,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,SAAS,UAAU,CACjB,UAAkC,EAClC,QAAwC,EACxC,OAAe,EACf,IAAY;IAEZ,OAAO;QACL,IAAI,EAAE,gBAAgB;QACtB,YAAY,EAAE,UAAU,CAAC,EAAE;QAC3B,UAAU,EAAE,QAAQ,CAAC,EAAE;QACvB,OAAO;QACP,IAAI,EAAE,aAAa,QAAQ,CAAC,EAAE,eAAe,IAAI,EAAE;QACnD,QAAQ,EAAE,OAAO;KAClB,CAAC;AACJ,CAAC","sourcesContent":["import type {\n DataSourceDiagnostic,\n GeneratedApiDefinition,\n GeneratedApiResourceDefinition,\n} from '@ankhorage/contracts/data';\n\nexport function validateGeneratedApiCollection(\n definition: GeneratedApiDefinition,\n resource: GeneratedApiResourceDefinition,\n): readonly DataSourceDiagnostic[] {\n const diagnostics: DataSourceDiagnostic[] = [];\n const { fields, primaryKey } = resource.collection;\n const fieldNames = new Set<string>();\n\n if (fields.length === 0) {\n diagnostics.push(\n diagnostic(definition, resource, 'Generated API resources require fields.', 'fields'),\n );\n }\n\n fields.forEach((field, index) => {\n const name = field.name.trim();\n if (!name) {\n diagnostics.push(\n diagnostic(\n definition,\n resource,\n 'Generated API field names must be non-empty.',\n `fields.${index}.name`,\n ),\n );\n return;\n }\n if (fieldNames.has(name)) {\n diagnostics.push(\n diagnostic(\n definition,\n resource,\n `Generated API field '${name}' is duplicated.`,\n `fields.${index}.name`,\n ),\n );\n return;\n }\n fieldNames.add(name);\n });\n\n if (primaryKey !== undefined && !fields.some((field) => field.name === primaryKey)) {\n diagnostics.push(\n diagnostic(\n definition,\n resource,\n `Primary key '${primaryKey}' does not match a collection field.`,\n 'primaryKey',\n ),\n );\n }\n\n return diagnostics;\n}\n\nfunction diagnostic(\n definition: GeneratedApiDefinition,\n resource: GeneratedApiResourceDefinition,\n message: string,\n path: string,\n): DataSourceDiagnostic {\n return {\n code: 'invalid-config',\n dataSourceId: definition.id,\n endpointId: resource.id,\n message,\n path: `resources.${resource.id}.collection.${path}`,\n severity: 'error',\n };\n}\n"]}
|