@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.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
+ });