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