@suss/contract-appsync 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.
- package/LICENSE +205 -0
- package/dist/index.cjs +750 -0
- package/dist/index.d.cts +35 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +717 -0
- package/package.json +62 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,750 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
appsyncFileToSummaries: () => appsyncFileToSummaries,
|
|
34
|
+
appsyncToSummaries: () => appsyncToSummaries
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
var import_node_fs = __toESM(require("fs"), 1);
|
|
38
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
39
|
+
var import_yaml = __toESM(require("yaml"), 1);
|
|
40
|
+
|
|
41
|
+
// src/refs.ts
|
|
42
|
+
function resolveLogicalRef(value) {
|
|
43
|
+
if (typeof value === "string") {
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
if (value === null || typeof value !== "object") {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const obj = value;
|
|
50
|
+
const ref = obj.Ref;
|
|
51
|
+
if (typeof ref === "string") {
|
|
52
|
+
return ref;
|
|
53
|
+
}
|
|
54
|
+
const getAtt = obj["Fn::GetAtt"];
|
|
55
|
+
if (Array.isArray(getAtt) && getAtt.length > 0 && typeof getAtt[0] === "string") {
|
|
56
|
+
return getAtt[0];
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
function stringField(value) {
|
|
61
|
+
return typeof value === "string" ? value : null;
|
|
62
|
+
}
|
|
63
|
+
function asRecord(value) {
|
|
64
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/sam.ts
|
|
71
|
+
var RESOLVER_ROOT_TYPES = ["Query", "Mutation", "Subscription"];
|
|
72
|
+
var DATA_SOURCE_CATEGORIES = {
|
|
73
|
+
Lambdas: "lambda",
|
|
74
|
+
DynamoDb: "dynamodb",
|
|
75
|
+
DynamoDBs: "dynamodb",
|
|
76
|
+
OpenSearch: "opensearch",
|
|
77
|
+
ElasticSearch: "elasticsearch",
|
|
78
|
+
Http: "http",
|
|
79
|
+
EventBridge: "eventbridge",
|
|
80
|
+
RelationalDatabase: "relational",
|
|
81
|
+
RelationalDatabases: "relational",
|
|
82
|
+
None: "none"
|
|
83
|
+
};
|
|
84
|
+
function readServerlessGraphQLApis(resources) {
|
|
85
|
+
const out = {
|
|
86
|
+
apis: [],
|
|
87
|
+
resolvers: [],
|
|
88
|
+
functions: [],
|
|
89
|
+
dataSources: []
|
|
90
|
+
};
|
|
91
|
+
for (const [logicalId, resource] of Object.entries(resources)) {
|
|
92
|
+
if (resource?.Type !== "AWS::Serverless::GraphQLApi") {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
readOne(logicalId, resource.Properties ?? {}, out);
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
function readOne(apiLogicalId, props, out) {
|
|
100
|
+
out.apis.push({
|
|
101
|
+
logicalId: apiLogicalId,
|
|
102
|
+
name: stringField(props.Name),
|
|
103
|
+
schemaSource: readSchemaSource(props),
|
|
104
|
+
authenticationType: readAuthType(props.Auth)
|
|
105
|
+
});
|
|
106
|
+
out.dataSources.push(...readDataSources(apiLogicalId, props.DataSources));
|
|
107
|
+
out.functions.push(...readFunctions(apiLogicalId, props.Functions));
|
|
108
|
+
out.resolvers.push(...readResolvers(apiLogicalId, props.Resolvers));
|
|
109
|
+
}
|
|
110
|
+
function readSchemaSource(props) {
|
|
111
|
+
const inline = stringField(props.SchemaInline);
|
|
112
|
+
if (inline !== null) {
|
|
113
|
+
return { kind: "inline", sdl: inline };
|
|
114
|
+
}
|
|
115
|
+
const location = stringField(props.SchemaUri);
|
|
116
|
+
if (location !== null) {
|
|
117
|
+
return { kind: "location", location };
|
|
118
|
+
}
|
|
119
|
+
return { kind: "absent" };
|
|
120
|
+
}
|
|
121
|
+
function readAuthType(auth) {
|
|
122
|
+
const record = asRecord(auth);
|
|
123
|
+
return record === null ? null : stringField(record.Type);
|
|
124
|
+
}
|
|
125
|
+
function readDataSources(apiLogicalId, raw) {
|
|
126
|
+
const dataSources = asRecord(raw);
|
|
127
|
+
if (dataSources === null) {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
const out = [];
|
|
131
|
+
for (const [category, entriesRaw] of Object.entries(dataSources)) {
|
|
132
|
+
const type = DATA_SOURCE_CATEGORIES[category] ?? "unknown";
|
|
133
|
+
const entries = asRecord(entriesRaw);
|
|
134
|
+
if (entries === null) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
for (const [name, defRaw] of Object.entries(entries)) {
|
|
138
|
+
const def = asRecord(defRaw);
|
|
139
|
+
out.push({
|
|
140
|
+
logicalId: dataSourceId(apiLogicalId, name),
|
|
141
|
+
apiLogicalId,
|
|
142
|
+
type,
|
|
143
|
+
lambdaFunctionLogicalId: type === "lambda" && def !== null ? resolveLogicalRef(def.FunctionArn) : null
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
function readFunctions(apiLogicalId, raw) {
|
|
150
|
+
const functions = asRecord(raw);
|
|
151
|
+
if (functions === null) {
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
const out = [];
|
|
155
|
+
for (const [name, defRaw] of Object.entries(functions)) {
|
|
156
|
+
const def = asRecord(defRaw) ?? {};
|
|
157
|
+
out.push({
|
|
158
|
+
logicalId: functionId(apiLogicalId, name),
|
|
159
|
+
apiLogicalId,
|
|
160
|
+
name,
|
|
161
|
+
dataSourceLogicalId: referencedDataSourceId(apiLogicalId, def.DataSource),
|
|
162
|
+
codeUri: stringField(def.CodeUri),
|
|
163
|
+
runtime: readRuntime(def.Runtime)
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return out;
|
|
167
|
+
}
|
|
168
|
+
function readResolvers(apiLogicalId, raw) {
|
|
169
|
+
const byType = asRecord(raw);
|
|
170
|
+
if (byType === null) {
|
|
171
|
+
return [];
|
|
172
|
+
}
|
|
173
|
+
const out = [];
|
|
174
|
+
for (const typeName of RESOLVER_ROOT_TYPES) {
|
|
175
|
+
const fields = asRecord(byType[typeName]);
|
|
176
|
+
if (fields === null) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
for (const [fieldName, defRaw] of Object.entries(fields)) {
|
|
180
|
+
const def = asRecord(defRaw) ?? {};
|
|
181
|
+
out.push(buildResolver(apiLogicalId, typeName, fieldName, def));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
function buildResolver(apiLogicalId, typeName, fieldName, def) {
|
|
187
|
+
const pipeline = readPipeline(apiLogicalId, def.Pipeline);
|
|
188
|
+
const isPipeline = pipeline.length > 0;
|
|
189
|
+
return {
|
|
190
|
+
logicalId: `${apiLogicalId}${typeName}${fieldName}Resolver`,
|
|
191
|
+
apiLogicalId,
|
|
192
|
+
typeName,
|
|
193
|
+
fieldName,
|
|
194
|
+
dataSourceLogicalId: isPipeline ? null : referencedDataSourceId(apiLogicalId, def.DataSource),
|
|
195
|
+
kind: isPipeline ? "PIPELINE" : "UNIT",
|
|
196
|
+
pipelineFunctionLogicalIds: pipeline,
|
|
197
|
+
codeUri: stringField(def.CodeUri),
|
|
198
|
+
runtime: readRuntime(def.Runtime)
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function readPipeline(apiLogicalId, raw) {
|
|
202
|
+
if (!Array.isArray(raw)) {
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
const out = [];
|
|
206
|
+
for (const entry of raw) {
|
|
207
|
+
const name = stringField(entry) ?? resolveLogicalRef(entry);
|
|
208
|
+
if (name !== null) {
|
|
209
|
+
out.push(functionId(apiLogicalId, name));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return out;
|
|
213
|
+
}
|
|
214
|
+
function readRuntime(raw) {
|
|
215
|
+
const record = asRecord(raw);
|
|
216
|
+
return record === null ? null : stringField(record.Name);
|
|
217
|
+
}
|
|
218
|
+
function referencedDataSourceId(apiLogicalId, raw) {
|
|
219
|
+
const name = stringField(raw);
|
|
220
|
+
return name === null ? null : dataSourceId(apiLogicalId, name);
|
|
221
|
+
}
|
|
222
|
+
function dataSourceId(apiLogicalId, name) {
|
|
223
|
+
return `${apiLogicalId}${name}`;
|
|
224
|
+
}
|
|
225
|
+
function functionId(apiLogicalId, name) {
|
|
226
|
+
return `${apiLogicalId}${name}`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/cfn.ts
|
|
230
|
+
function readAppSyncFromCfn(template) {
|
|
231
|
+
const resources = template.Resources ?? {};
|
|
232
|
+
const raw = {
|
|
233
|
+
apis: collectApis(resources),
|
|
234
|
+
resolvers: collectResolvers(resources),
|
|
235
|
+
functions: collectFunctions(resources),
|
|
236
|
+
dataSources: collectDataSources(resources)
|
|
237
|
+
};
|
|
238
|
+
const sam = readServerlessGraphQLApis(resources);
|
|
239
|
+
return {
|
|
240
|
+
apis: [...raw.apis, ...sam.apis],
|
|
241
|
+
resolvers: [...raw.resolvers, ...sam.resolvers],
|
|
242
|
+
functions: [...raw.functions, ...sam.functions],
|
|
243
|
+
dataSources: [...raw.dataSources, ...sam.dataSources]
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function collectApis(resources) {
|
|
247
|
+
const apis = [];
|
|
248
|
+
const schemaByApi = indexSchemasByApi(resources);
|
|
249
|
+
for (const [logicalId, resource] of Object.entries(resources)) {
|
|
250
|
+
if (resource?.Type !== "AWS::AppSync::GraphQLApi") {
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const props = resource.Properties ?? {};
|
|
254
|
+
apis.push({
|
|
255
|
+
logicalId,
|
|
256
|
+
name: stringField(props.Name),
|
|
257
|
+
schemaSource: schemaByApi.get(logicalId) ?? { kind: "absent" },
|
|
258
|
+
authenticationType: stringField(props.AuthenticationType)
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
return apis;
|
|
262
|
+
}
|
|
263
|
+
function indexSchemasByApi(resources) {
|
|
264
|
+
const out = /* @__PURE__ */ new Map();
|
|
265
|
+
for (const resource of Object.values(resources)) {
|
|
266
|
+
if (resource?.Type !== "AWS::AppSync::GraphQLSchema") {
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
const props = resource.Properties ?? {};
|
|
270
|
+
const apiRef = resolveLogicalRef(props.ApiId);
|
|
271
|
+
if (apiRef === null) {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const inline = stringField(props.Definition);
|
|
275
|
+
if (inline !== null) {
|
|
276
|
+
out.set(apiRef, { kind: "inline", sdl: inline });
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
const location = stringField(props.DefinitionS3Location);
|
|
280
|
+
if (location !== null) {
|
|
281
|
+
out.set(apiRef, { kind: "location", location });
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return out;
|
|
285
|
+
}
|
|
286
|
+
function collectResolvers(resources) {
|
|
287
|
+
const out = [];
|
|
288
|
+
for (const [logicalId, resource] of Object.entries(resources)) {
|
|
289
|
+
if (resource?.Type !== "AWS::AppSync::Resolver") {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
const props = resource.Properties ?? {};
|
|
293
|
+
const typeName = stringField(props.TypeName);
|
|
294
|
+
const fieldName = stringField(props.FieldName);
|
|
295
|
+
if (typeName === null || fieldName === null) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
out.push({
|
|
299
|
+
logicalId,
|
|
300
|
+
apiLogicalId: resolveLogicalRef(props.ApiId),
|
|
301
|
+
typeName,
|
|
302
|
+
fieldName,
|
|
303
|
+
dataSourceLogicalId: resolveLogicalRef(props.DataSourceName),
|
|
304
|
+
kind: resolverKind(stringField(props.Kind)),
|
|
305
|
+
pipelineFunctionLogicalIds: pipelineFunctionIds(props.PipelineConfig),
|
|
306
|
+
codeUri: null,
|
|
307
|
+
runtime: null
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
return out;
|
|
311
|
+
}
|
|
312
|
+
function pipelineFunctionIds(pipelineConfig) {
|
|
313
|
+
const config = asRecord(pipelineConfig);
|
|
314
|
+
if (config === null) {
|
|
315
|
+
return [];
|
|
316
|
+
}
|
|
317
|
+
const functions = config.Functions;
|
|
318
|
+
if (!Array.isArray(functions)) {
|
|
319
|
+
return [];
|
|
320
|
+
}
|
|
321
|
+
const out = [];
|
|
322
|
+
for (const entry of functions) {
|
|
323
|
+
const ref = resolveLogicalRef(entry);
|
|
324
|
+
if (ref !== null) {
|
|
325
|
+
out.push(ref);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return out;
|
|
329
|
+
}
|
|
330
|
+
function collectFunctions(resources) {
|
|
331
|
+
const out = [];
|
|
332
|
+
for (const [logicalId, resource] of Object.entries(resources)) {
|
|
333
|
+
if (resource?.Type !== "AWS::AppSync::FunctionConfiguration") {
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
const props = resource.Properties ?? {};
|
|
337
|
+
out.push({
|
|
338
|
+
logicalId,
|
|
339
|
+
apiLogicalId: resolveLogicalRef(props.ApiId),
|
|
340
|
+
name: stringField(props.Name),
|
|
341
|
+
dataSourceLogicalId: resolveLogicalRef(props.DataSourceName),
|
|
342
|
+
codeUri: null,
|
|
343
|
+
runtime: null
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
return out;
|
|
347
|
+
}
|
|
348
|
+
function collectDataSources(resources) {
|
|
349
|
+
const out = [];
|
|
350
|
+
for (const [logicalId, resource] of Object.entries(resources)) {
|
|
351
|
+
if (resource?.Type !== "AWS::AppSync::DataSource") {
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
const props = resource.Properties ?? {};
|
|
355
|
+
const lambdaConfig = asRecord(props.LambdaConfig);
|
|
356
|
+
out.push({
|
|
357
|
+
logicalId,
|
|
358
|
+
apiLogicalId: resolveLogicalRef(props.ApiId),
|
|
359
|
+
type: dataSourceType(stringField(props.Type)),
|
|
360
|
+
lambdaFunctionLogicalId: lambdaConfig === null ? null : resolveLogicalRef(lambdaConfig.LambdaFunctionArn)
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
return out;
|
|
364
|
+
}
|
|
365
|
+
var DATA_SOURCE_TYPES = {
|
|
366
|
+
AWS_LAMBDA: "lambda",
|
|
367
|
+
AMAZON_DYNAMODB: "dynamodb",
|
|
368
|
+
AMAZON_ELASTICSEARCH: "elasticsearch",
|
|
369
|
+
AMAZON_OPENSEARCH_SERVICE: "opensearch",
|
|
370
|
+
HTTP: "http",
|
|
371
|
+
RELATIONAL_DATABASE: "relational",
|
|
372
|
+
AMAZON_EVENTBRIDGE: "eventbridge",
|
|
373
|
+
NONE: "none"
|
|
374
|
+
};
|
|
375
|
+
function dataSourceType(raw) {
|
|
376
|
+
if (raw === null) {
|
|
377
|
+
return "unknown";
|
|
378
|
+
}
|
|
379
|
+
return DATA_SOURCE_TYPES[raw] ?? "unknown";
|
|
380
|
+
}
|
|
381
|
+
function resolverKind(raw) {
|
|
382
|
+
if (raw === "PIPELINE") {
|
|
383
|
+
return "PIPELINE";
|
|
384
|
+
}
|
|
385
|
+
if (raw === "UNIT" || raw === null) {
|
|
386
|
+
return "UNIT";
|
|
387
|
+
}
|
|
388
|
+
return "UNKNOWN";
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// src/schema.ts
|
|
392
|
+
var import_graphql = require("graphql");
|
|
393
|
+
function schemaKey(typeName, fieldName) {
|
|
394
|
+
return `${typeName}.${fieldName}`;
|
|
395
|
+
}
|
|
396
|
+
function parseSchema(sdl) {
|
|
397
|
+
const index = /* @__PURE__ */ new Map();
|
|
398
|
+
const doc = safeParse(sdl);
|
|
399
|
+
if (doc === null) {
|
|
400
|
+
return index;
|
|
401
|
+
}
|
|
402
|
+
for (const def of doc.definitions) {
|
|
403
|
+
if (def.kind !== import_graphql.Kind.OBJECT_TYPE_DEFINITION && def.kind !== import_graphql.Kind.OBJECT_TYPE_EXTENSION && def.kind !== import_graphql.Kind.INTERFACE_TYPE_DEFINITION) {
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
const typeName = def.name.value;
|
|
407
|
+
for (const field of def.fields ?? []) {
|
|
408
|
+
const info = describeField(typeName, field);
|
|
409
|
+
index.set(schemaKey(typeName, info.fieldName), info);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return index;
|
|
413
|
+
}
|
|
414
|
+
function safeParse(sdl) {
|
|
415
|
+
try {
|
|
416
|
+
return (0, import_graphql.parse)(sdl);
|
|
417
|
+
} catch {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function describeField(typeName, field) {
|
|
422
|
+
return {
|
|
423
|
+
typeName,
|
|
424
|
+
fieldName: field.name.value,
|
|
425
|
+
returnShape: typeReference(sourceOfType(field.type)),
|
|
426
|
+
args: (field.arguments ?? []).map((arg) => ({
|
|
427
|
+
name: arg.name.value,
|
|
428
|
+
shape: typeReference(sourceOfType(arg.type)),
|
|
429
|
+
required: isNonNull(arg.type)
|
|
430
|
+
}))
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
function sourceOfType(node) {
|
|
434
|
+
if (node.kind === import_graphql.Kind.NON_NULL_TYPE) {
|
|
435
|
+
return `${sourceOfType(node.type)}!`;
|
|
436
|
+
}
|
|
437
|
+
if (node.kind === import_graphql.Kind.LIST_TYPE) {
|
|
438
|
+
return `[${sourceOfType(node.type)}]`;
|
|
439
|
+
}
|
|
440
|
+
return node.name.value;
|
|
441
|
+
}
|
|
442
|
+
function isNonNull(node) {
|
|
443
|
+
return node.kind === import_graphql.Kind.NON_NULL_TYPE;
|
|
444
|
+
}
|
|
445
|
+
function typeReference(name) {
|
|
446
|
+
return { type: "ref", name };
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// src/schemaSource.ts
|
|
450
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
451
|
+
var import_contract_graphql = require("@suss/contract-graphql");
|
|
452
|
+
var REMOTE_SCHEME = /^(s3|https?):\/\//i;
|
|
453
|
+
function resolveSchemaSource(raw, baseDir) {
|
|
454
|
+
if (raw.kind === "inline") {
|
|
455
|
+
return { status: "inline", sdl: raw.sdl };
|
|
456
|
+
}
|
|
457
|
+
if (raw.kind === "absent") {
|
|
458
|
+
return { status: "absent" };
|
|
459
|
+
}
|
|
460
|
+
return resolveLocation(raw.location, baseDir);
|
|
461
|
+
}
|
|
462
|
+
function resolveLocation(location, baseDir) {
|
|
463
|
+
if (REMOTE_SCHEME.test(location)) {
|
|
464
|
+
return { status: "unresolved", location, reason: "remote" };
|
|
465
|
+
}
|
|
466
|
+
const resolvedPath = toLocalPath(location, baseDir);
|
|
467
|
+
if (resolvedPath === null) {
|
|
468
|
+
return { status: "unresolved", location, reason: "no-base-dir" };
|
|
469
|
+
}
|
|
470
|
+
const sdl = (0, import_contract_graphql.loadSdlFile)(resolvedPath);
|
|
471
|
+
if (sdl === null) {
|
|
472
|
+
return { status: "unresolved", location, reason: "not-found" };
|
|
473
|
+
}
|
|
474
|
+
return { status: "external-file", sdl, location };
|
|
475
|
+
}
|
|
476
|
+
function toLocalPath(location, baseDir) {
|
|
477
|
+
if (import_node_path.default.isAbsolute(location)) {
|
|
478
|
+
return location;
|
|
479
|
+
}
|
|
480
|
+
if (baseDir === null) {
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
return import_node_path.default.resolve(baseDir, location);
|
|
484
|
+
}
|
|
485
|
+
function resolvedSdl(resolved) {
|
|
486
|
+
if (resolved.status === "inline" || resolved.status === "external-file") {
|
|
487
|
+
return resolved.sdl;
|
|
488
|
+
}
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// src/summaryBuilder.ts
|
|
493
|
+
var import_behavioral_ir = require("@suss/behavioral-ir");
|
|
494
|
+
function buildResolverSummaries(config, resolvedByApi, schemasByApi, options = {}) {
|
|
495
|
+
const sourceFile = options.source ?? "appsync";
|
|
496
|
+
const indexes = {
|
|
497
|
+
apiById: byLogicalId(config.apis),
|
|
498
|
+
functionById: byLogicalId(config.functions),
|
|
499
|
+
dataSourceById: byLogicalId(config.dataSources)
|
|
500
|
+
};
|
|
501
|
+
return config.resolvers.map(
|
|
502
|
+
(resolver) => buildOne(resolver, indexes, resolvedByApi, schemasByApi, sourceFile)
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
function byLogicalId(items) {
|
|
506
|
+
const out = /* @__PURE__ */ new Map();
|
|
507
|
+
for (const item of items) {
|
|
508
|
+
out.set(item.logicalId, item);
|
|
509
|
+
}
|
|
510
|
+
return out;
|
|
511
|
+
}
|
|
512
|
+
function buildOne(resolver, indexes, resolvedByApi, schemasByApi, sourceFile) {
|
|
513
|
+
const api = resolver.apiLogicalId === null ? null : indexes.apiById.get(resolver.apiLogicalId) ?? null;
|
|
514
|
+
const resolved = resolver.apiLogicalId === null ? null : resolvedByApi.get(resolver.apiLogicalId) ?? null;
|
|
515
|
+
const schema = resolver.apiLogicalId === null ? null : schemasByApi.get(resolver.apiLogicalId) ?? null;
|
|
516
|
+
const field = schema?.get(schemaKey(resolver.typeName, resolver.fieldName)) ?? null;
|
|
517
|
+
const ownerKey = `${resolver.typeName}.${resolver.fieldName}`;
|
|
518
|
+
const schemaSdl = resolved === null ? null : resolvedSdl(resolved);
|
|
519
|
+
return {
|
|
520
|
+
kind: "resolver",
|
|
521
|
+
location: {
|
|
522
|
+
file: `${sourceFile}:${resolver.logicalId}`,
|
|
523
|
+
range: { start: 0, end: 0 },
|
|
524
|
+
exportName: null
|
|
525
|
+
},
|
|
526
|
+
identity: {
|
|
527
|
+
name: ownerKey,
|
|
528
|
+
exportPath: null,
|
|
529
|
+
boundaryBinding: (0, import_behavioral_ir.graphqlResolverBinding)({
|
|
530
|
+
// AppSync is invoked over HTTPS-to-AWS. Keeping transport
|
|
531
|
+
// explicit here matches the aws-apigateway reader's posture
|
|
532
|
+
// and leaves room for a future AWS-SDK-direct transport
|
|
533
|
+
// ("aws-sdk") once Lambda-invoke semantics land.
|
|
534
|
+
transport: "aws-https",
|
|
535
|
+
recognition: "appsync",
|
|
536
|
+
typeName: resolver.typeName,
|
|
537
|
+
fieldName: resolver.fieldName
|
|
538
|
+
})
|
|
539
|
+
},
|
|
540
|
+
inputs: buildInputs(field),
|
|
541
|
+
transitions: buildTransitions(ownerKey, resolver, field),
|
|
542
|
+
gaps: [],
|
|
543
|
+
confidence: { source: "derived", level: "high" },
|
|
544
|
+
metadata: {
|
|
545
|
+
appsync: buildAppsyncMetadata(resolver, api, resolved, field, indexes),
|
|
546
|
+
// Surface the resolved SDL so the checker can resolve nested
|
|
547
|
+
// selections against this resolver's return type. Repeated across
|
|
548
|
+
// every resolver from the same API by design — each summary
|
|
549
|
+
// travels independently; keeping the SDL on-hand is simpler than
|
|
550
|
+
// cross-summary schema lookup, and the checker caches parses
|
|
551
|
+
// per-SDL.
|
|
552
|
+
...schemaSdl !== null ? { graphql: { schemaSdl } } : {}
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
function buildAppsyncMetadata(resolver, api, resolved, field, indexes) {
|
|
557
|
+
const pipelineFunctions = resolver.pipelineFunctionLogicalIds.map(
|
|
558
|
+
(logicalId) => pipelineFunctionMeta(logicalId, indexes)
|
|
559
|
+
);
|
|
560
|
+
return {
|
|
561
|
+
apiLogicalId: resolver.apiLogicalId,
|
|
562
|
+
apiName: api?.name ?? null,
|
|
563
|
+
dataSourceLogicalId: resolver.dataSourceLogicalId,
|
|
564
|
+
// Lambda behind this resolver's own data source (UNIT resolvers).
|
|
565
|
+
// PIPELINE resolvers attribute per-function below; their top-level
|
|
566
|
+
// data source is null, so this is null too.
|
|
567
|
+
lambdaFunctionLogicalId: lambdaBehind(
|
|
568
|
+
resolver.dataSourceLogicalId,
|
|
569
|
+
indexes
|
|
570
|
+
),
|
|
571
|
+
kind: resolver.kind,
|
|
572
|
+
authenticationType: api?.authenticationType ?? null,
|
|
573
|
+
// SAM JS/VTL resolver code location + runtime (null for raw
|
|
574
|
+
// AWS::AppSync::Resolver resources).
|
|
575
|
+
codeUri: resolver.codeUri,
|
|
576
|
+
runtime: resolver.runtime,
|
|
577
|
+
// Distinguish "schema said X" from "we didn't see a schema at all"
|
|
578
|
+
// (field-level), and record how the SDL itself was obtained
|
|
579
|
+
// (source-level) so a genuinely-remote schema is never silent.
|
|
580
|
+
schemaMatched: field !== null,
|
|
581
|
+
schemaSource: schemaSourceMetadata(resolved),
|
|
582
|
+
// For PIPELINE resolvers, surface the ordered function chain so
|
|
583
|
+
// downstream tools can show the dispatch path. Empty for UNIT
|
|
584
|
+
// resolvers; empty with `kind: "PIPELINE"` means the Functions
|
|
585
|
+
// array was dynamically-referenced and we couldn't resolve it.
|
|
586
|
+
...pipelineFunctions.length > 0 ? { pipelineFunctions } : {}
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
function pipelineFunctionMeta(logicalId, indexes) {
|
|
590
|
+
const fn = indexes.functionById.get(logicalId) ?? null;
|
|
591
|
+
return {
|
|
592
|
+
logicalId,
|
|
593
|
+
name: fn?.name ?? null,
|
|
594
|
+
dataSourceLogicalId: fn?.dataSourceLogicalId ?? null,
|
|
595
|
+
lambdaFunctionLogicalId: lambdaBehind(
|
|
596
|
+
fn?.dataSourceLogicalId ?? null,
|
|
597
|
+
indexes
|
|
598
|
+
),
|
|
599
|
+
codeUri: fn?.codeUri ?? null,
|
|
600
|
+
runtime: fn?.runtime ?? null
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
function lambdaBehind(dataSourceLogicalId, indexes) {
|
|
604
|
+
if (dataSourceLogicalId === null) {
|
|
605
|
+
return null;
|
|
606
|
+
}
|
|
607
|
+
return indexes.dataSourceById.get(dataSourceLogicalId)?.lambdaFunctionLogicalId ?? null;
|
|
608
|
+
}
|
|
609
|
+
function schemaSourceMetadata(resolved) {
|
|
610
|
+
if (resolved === null || resolved.status === "absent") {
|
|
611
|
+
return { status: "absent" };
|
|
612
|
+
}
|
|
613
|
+
if (resolved.status === "inline") {
|
|
614
|
+
return { status: "inline" };
|
|
615
|
+
}
|
|
616
|
+
if (resolved.status === "external-file") {
|
|
617
|
+
return { status: "external-file", location: resolved.location };
|
|
618
|
+
}
|
|
619
|
+
return {
|
|
620
|
+
status: "unresolved",
|
|
621
|
+
location: resolved.location,
|
|
622
|
+
reason: resolved.reason
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
function buildInputs(field) {
|
|
626
|
+
if (field === null) {
|
|
627
|
+
return [];
|
|
628
|
+
}
|
|
629
|
+
return field.args.map((arg, index) => ({
|
|
630
|
+
type: "parameter",
|
|
631
|
+
name: arg.name,
|
|
632
|
+
position: index,
|
|
633
|
+
role: "args",
|
|
634
|
+
shape: arg.shape
|
|
635
|
+
}));
|
|
636
|
+
}
|
|
637
|
+
function buildTransitions(ownerKey, resolver, field) {
|
|
638
|
+
const returnShape = field?.returnShape ?? { type: "unknown" };
|
|
639
|
+
const successSource = field !== null ? "aws::appsync::resolver.success" : "aws::appsync::resolver.success-no-schema";
|
|
640
|
+
return [
|
|
641
|
+
{
|
|
642
|
+
id: `${ownerKey}:return:success`,
|
|
643
|
+
conditions: [],
|
|
644
|
+
output: { type: "return", value: returnShape },
|
|
645
|
+
effects: [],
|
|
646
|
+
location: { start: 0, end: 0 },
|
|
647
|
+
isDefault: true,
|
|
648
|
+
confidence: { source: "derived", level: "high" },
|
|
649
|
+
metadata: {
|
|
650
|
+
source: successSource,
|
|
651
|
+
resolverKind: resolver.kind
|
|
652
|
+
}
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
id: `${ownerKey}:throw:error`,
|
|
656
|
+
conditions: [
|
|
657
|
+
{
|
|
658
|
+
type: "opaque",
|
|
659
|
+
sourceText: "aws:appsync:resolver-error",
|
|
660
|
+
reason: "externalFunction"
|
|
661
|
+
}
|
|
662
|
+
],
|
|
663
|
+
output: {
|
|
664
|
+
type: "throw",
|
|
665
|
+
exceptionType: null,
|
|
666
|
+
message: null
|
|
667
|
+
},
|
|
668
|
+
effects: [],
|
|
669
|
+
location: { start: 0, end: 0 },
|
|
670
|
+
isDefault: false,
|
|
671
|
+
confidence: { source: "derived", level: "medium" },
|
|
672
|
+
metadata: {
|
|
673
|
+
source: "aws::appsync::resolver.error-path"
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
];
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// src/index.ts
|
|
680
|
+
function appsyncToSummaries(template, options = {}) {
|
|
681
|
+
const config = readAppSyncFromCfn(template);
|
|
682
|
+
const baseDir = options.baseDir ?? null;
|
|
683
|
+
const resolvedByApi = resolveSchemas(config, baseDir);
|
|
684
|
+
const schemasByApi = indexSchemas(resolvedByApi);
|
|
685
|
+
return buildResolverSummaries(config, resolvedByApi, schemasByApi, {
|
|
686
|
+
...options.source !== void 0 ? { source: options.source } : {}
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
function resolveSchemas(config, baseDir) {
|
|
690
|
+
const out = /* @__PURE__ */ new Map();
|
|
691
|
+
for (const api of config.apis) {
|
|
692
|
+
out.set(api.logicalId, resolveSchemaSource(api.schemaSource, baseDir));
|
|
693
|
+
}
|
|
694
|
+
return out;
|
|
695
|
+
}
|
|
696
|
+
function indexSchemas(resolvedByApi) {
|
|
697
|
+
const out = /* @__PURE__ */ new Map();
|
|
698
|
+
for (const [logicalId, resolved] of resolvedByApi) {
|
|
699
|
+
if (resolved.status === "inline" || resolved.status === "external-file") {
|
|
700
|
+
out.set(logicalId, parseSchema(resolved.sdl));
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
return out;
|
|
704
|
+
}
|
|
705
|
+
function appsyncFileToSummaries(filePath, options = {}) {
|
|
706
|
+
const raw = import_node_fs.default.readFileSync(filePath, "utf8");
|
|
707
|
+
const template = parseTemplate(filePath, raw);
|
|
708
|
+
return appsyncToSummaries(template, {
|
|
709
|
+
source: options.source ?? import_node_path2.default.relative(process.cwd(), filePath),
|
|
710
|
+
baseDir: options.baseDir ?? import_node_path2.default.dirname(filePath)
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
var CLOUDFORMATION_YAML_TAGS = [
|
|
714
|
+
{ tag: "!Ref", resolve: (value) => ({ Ref: value }) },
|
|
715
|
+
{
|
|
716
|
+
tag: "!GetAtt",
|
|
717
|
+
resolve: (value) => ({
|
|
718
|
+
"Fn::GetAtt": value.includes(".") ? value.split(".") : [value]
|
|
719
|
+
})
|
|
720
|
+
},
|
|
721
|
+
...[
|
|
722
|
+
"!Sub",
|
|
723
|
+
"!Join",
|
|
724
|
+
"!Select",
|
|
725
|
+
"!Split",
|
|
726
|
+
"!FindInMap",
|
|
727
|
+
"!ImportValue",
|
|
728
|
+
"!Base64",
|
|
729
|
+
"!Cidr",
|
|
730
|
+
"!If",
|
|
731
|
+
"!Not",
|
|
732
|
+
"!And",
|
|
733
|
+
"!Or",
|
|
734
|
+
"!Equals"
|
|
735
|
+
].map((tag) => ({ tag, resolve: (value) => value }))
|
|
736
|
+
];
|
|
737
|
+
function parseTemplate(filePath, raw) {
|
|
738
|
+
const ext = import_node_path2.default.extname(filePath).toLowerCase();
|
|
739
|
+
if (ext === ".json") {
|
|
740
|
+
return JSON.parse(raw);
|
|
741
|
+
}
|
|
742
|
+
return import_yaml.default.parse(raw, {
|
|
743
|
+
customTags: CLOUDFORMATION_YAML_TAGS
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
747
|
+
0 && (module.exports = {
|
|
748
|
+
appsyncFileToSummaries,
|
|
749
|
+
appsyncToSummaries
|
|
750
|
+
});
|