@suss/manifest-aws 0.0.1 → 0.1.0

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 CHANGED
@@ -33,6 +33,7 @@ __export(index_exports, {
33
33
  CLOUDFORMATION_YAML_TAGS: () => CLOUDFORMATION_YAML_TAGS,
34
34
  loadCloudFormationTemplate: () => loadCloudFormationTemplate,
35
35
  parseHandler: () => parseHandler,
36
+ readAppSyncResolvers: () => readAppSyncResolvers,
36
37
  readServerlessFunctions: () => readServerlessFunctions,
37
38
  refTarget: () => refTarget
38
39
  });
@@ -100,6 +101,165 @@ function refTarget(value) {
100
101
  return null;
101
102
  }
102
103
 
104
+ // src/appsyncResolvers.ts
105
+ var LAMBDA_CATEGORIES = ["Lambda", "Lambdas"];
106
+ function readAppSyncResolvers(template) {
107
+ const resources = template.Resources ?? {};
108
+ return [
109
+ ...fromServerlessShorthand(resources),
110
+ ...fromRawResources(resources)
111
+ ];
112
+ }
113
+ function fromServerlessShorthand(resources) {
114
+ const bindings = [];
115
+ for (const resource of Object.values(resources)) {
116
+ if (resource?.Type !== "AWS::Serverless::GraphQLApi") {
117
+ continue;
118
+ }
119
+ const props = asRecord(resource.Properties) ?? {};
120
+ const lambdaByDataSource = shorthandDataSources(props.DataSources);
121
+ const dataSourceByFunction = shorthandFunctions(props.Functions);
122
+ for (const [typeName, fields] of entriesOf(props.Resolvers)) {
123
+ for (const [fieldName, config] of entriesOf(fields)) {
124
+ const dataSources = dataSourcesReached(config, dataSourceByFunction);
125
+ bindings.push({
126
+ typeName,
127
+ fieldName,
128
+ lambdaFunctionLogicalIds: dataSources.map((name) => lambdaByDataSource.get(name) ?? null).filter((id) => id !== null)
129
+ });
130
+ }
131
+ }
132
+ }
133
+ return bindings;
134
+ }
135
+ function shorthandDataSources(block) {
136
+ const out = /* @__PURE__ */ new Map();
137
+ for (const [category, entries] of entriesOf(block)) {
138
+ if (!LAMBDA_CATEGORIES.includes(category)) {
139
+ continue;
140
+ }
141
+ for (const [name, config] of entriesOf(entries)) {
142
+ const props = asRecord(config);
143
+ out.set(name, props === null ? null : refTarget(props.FunctionArn));
144
+ }
145
+ }
146
+ return out;
147
+ }
148
+ function shorthandFunctions(block) {
149
+ const out = /* @__PURE__ */ new Map();
150
+ for (const [name, config] of entriesOf(block)) {
151
+ const props = asRecord(config);
152
+ const dataSource = props === null ? null : stringOf(props.DataSource);
153
+ if (dataSource !== null) {
154
+ out.set(name, dataSource);
155
+ }
156
+ }
157
+ return out;
158
+ }
159
+ function dataSourcesReached(config, dataSourceByFunction) {
160
+ const props = asRecord(config);
161
+ if (props === null) {
162
+ return [];
163
+ }
164
+ const direct = stringOf(props.DataSource);
165
+ if (direct !== null) {
166
+ return [direct];
167
+ }
168
+ const pipeline = Array.isArray(props.Pipeline) ? props.Pipeline : [];
169
+ return pipeline.map((name) => dataSourceByFunction.get(String(name))).filter((name) => name !== void 0);
170
+ }
171
+ function fromRawResources(resources) {
172
+ const lambdaByLogicalId = /* @__PURE__ */ new Map();
173
+ const lambdaByApiAndName = /* @__PURE__ */ new Map();
174
+ for (const [logicalId, resource] of Object.entries(resources)) {
175
+ if (resource?.Type !== "AWS::AppSync::DataSource") {
176
+ continue;
177
+ }
178
+ const props = asRecord(resource.Properties) ?? {};
179
+ const lambdaConfig = asRecord(props.LambdaConfig);
180
+ const lambda = lambdaConfig === null ? null : refTarget(lambdaConfig.LambdaFunctionArn);
181
+ if (lambda === null) {
182
+ continue;
183
+ }
184
+ lambdaByLogicalId.set(logicalId, lambda);
185
+ const name = stringOf(props.Name);
186
+ if (name !== null) {
187
+ lambdaByApiAndName.set(
188
+ apiScopedKey(refTarget(props.ApiId), name),
189
+ lambda
190
+ );
191
+ }
192
+ }
193
+ const dataSourceByLogicalId = /* @__PURE__ */ new Map();
194
+ const dataSourceByApiAndName = /* @__PURE__ */ new Map();
195
+ for (const [logicalId, resource] of Object.entries(resources)) {
196
+ if (resource?.Type !== "AWS::AppSync::FunctionConfiguration") {
197
+ continue;
198
+ }
199
+ const props = asRecord(resource.Properties) ?? {};
200
+ const dataSource = refTarget(props.DataSourceName);
201
+ if (dataSource === null) {
202
+ continue;
203
+ }
204
+ dataSourceByLogicalId.set(logicalId, dataSource);
205
+ const name = stringOf(props.Name);
206
+ if (name !== null) {
207
+ dataSourceByApiAndName.set(
208
+ apiScopedKey(refTarget(props.ApiId), name),
209
+ dataSource
210
+ );
211
+ }
212
+ }
213
+ const bindings = [];
214
+ for (const resource of Object.values(resources)) {
215
+ if (resource?.Type !== "AWS::AppSync::Resolver") {
216
+ continue;
217
+ }
218
+ const props = asRecord(resource.Properties) ?? {};
219
+ const typeName = stringOf(props.TypeName);
220
+ const fieldName = stringOf(props.FieldName);
221
+ if (typeName === null || fieldName === null) {
222
+ continue;
223
+ }
224
+ const apiId = refTarget(props.ApiId);
225
+ const lookup = (byLogicalId, byName) => (reference) => byLogicalId.get(reference) ?? byName.get(apiScopedKey(apiId, reference));
226
+ bindings.push({
227
+ typeName,
228
+ fieldName,
229
+ lambdaFunctionLogicalIds: rawDataSourcesReached(
230
+ props,
231
+ lookup(dataSourceByLogicalId, dataSourceByApiAndName)
232
+ ).map(lookup(lambdaByLogicalId, lambdaByApiAndName)).filter((id) => id !== void 0)
233
+ });
234
+ }
235
+ return bindings;
236
+ }
237
+ function apiScopedKey(apiId, name) {
238
+ return `${apiId ?? ""}::${name}`;
239
+ }
240
+ function rawDataSourcesReached(props, dataSourceOf) {
241
+ const direct = refTarget(props.DataSourceName);
242
+ if (direct !== null) {
243
+ return [direct];
244
+ }
245
+ const pipeline = asRecord(props.PipelineConfig);
246
+ const functions = Array.isArray(pipeline?.Functions) ? pipeline.Functions : [];
247
+ return functions.map((fn) => {
248
+ const reference = refTarget(fn);
249
+ return reference === null ? void 0 : dataSourceOf(reference);
250
+ }).filter((name) => name !== void 0);
251
+ }
252
+ function asRecord(value) {
253
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
254
+ }
255
+ function entriesOf(value) {
256
+ const record = asRecord(value);
257
+ return record === null ? [] : Object.entries(record);
258
+ }
259
+ function stringOf(value) {
260
+ return typeof value === "string" ? value : null;
261
+ }
262
+
103
263
  // src/serverlessFunctions.ts
104
264
  function parseHandler(handler) {
105
265
  const trimmed = handler.trim();
@@ -201,6 +361,7 @@ function readServerlessFunctions(template) {
201
361
  CLOUDFORMATION_YAML_TAGS,
202
362
  loadCloudFormationTemplate,
203
363
  parseHandler,
364
+ readAppSyncResolvers,
204
365
  readServerlessFunctions,
205
366
  refTarget
206
367
  });
package/dist/index.d.cts CHANGED
@@ -44,6 +44,24 @@ declare function loadCloudFormationTemplate(templatePath: string): CloudFormatio
44
44
  */
45
45
  declare function refTarget(value: unknown): string | null;
46
46
 
47
+ /** One GraphQL field and the Lambdas behind it. */
48
+ interface AppSyncResolverBinding {
49
+ typeName: string;
50
+ fieldName: string;
51
+ /**
52
+ * Every Lambda a request for this field runs, in pipeline order.
53
+ * Usually one. A pipeline can chain several, and nothing in the
54
+ * template says which of them is the one that produces the value:
55
+ * the first is as often an auth step as it is the resolver, and the
56
+ * last is as often a formatter. So all of them are reported, and a
57
+ * reader that needs to pick says so itself.
58
+ *
59
+ * Empty when the field is served by something other than a Lambda.
60
+ */
61
+ lambdaFunctionLogicalIds: string[];
62
+ }
63
+ declare function readAppSyncResolvers(template: CloudFormationTemplate): AppSyncResolverBinding[];
64
+
47
65
  /** A route-bearing SAM Event (`Type: Api` or `Type: HttpApi`). */
48
66
  interface ServerlessHttpRoute {
49
67
  /** Key of the event in the function's `Events` map. */
@@ -104,4 +122,4 @@ declare function parseHandler(handler: string): ParsedHandler | null;
104
122
  */
105
123
  declare function readServerlessFunctions(template: CloudFormationTemplate): ServerlessFunctionInfo[];
106
124
 
107
- export { CLOUDFORMATION_YAML_TAGS, type CloudFormationResource, type CloudFormationTemplate, type ParsedHandler, type ServerlessFunctionInfo, type ServerlessHttpRoute, type ServerlessNonHttpEvent, loadCloudFormationTemplate, parseHandler, readServerlessFunctions, refTarget };
125
+ export { type AppSyncResolverBinding, CLOUDFORMATION_YAML_TAGS, type CloudFormationResource, type CloudFormationTemplate, type ParsedHandler, type ServerlessFunctionInfo, type ServerlessHttpRoute, type ServerlessNonHttpEvent, loadCloudFormationTemplate, parseHandler, readAppSyncResolvers, readServerlessFunctions, refTarget };
package/dist/index.d.ts CHANGED
@@ -44,6 +44,24 @@ declare function loadCloudFormationTemplate(templatePath: string): CloudFormatio
44
44
  */
45
45
  declare function refTarget(value: unknown): string | null;
46
46
 
47
+ /** One GraphQL field and the Lambdas behind it. */
48
+ interface AppSyncResolverBinding {
49
+ typeName: string;
50
+ fieldName: string;
51
+ /**
52
+ * Every Lambda a request for this field runs, in pipeline order.
53
+ * Usually one. A pipeline can chain several, and nothing in the
54
+ * template says which of them is the one that produces the value:
55
+ * the first is as often an auth step as it is the resolver, and the
56
+ * last is as often a formatter. So all of them are reported, and a
57
+ * reader that needs to pick says so itself.
58
+ *
59
+ * Empty when the field is served by something other than a Lambda.
60
+ */
61
+ lambdaFunctionLogicalIds: string[];
62
+ }
63
+ declare function readAppSyncResolvers(template: CloudFormationTemplate): AppSyncResolverBinding[];
64
+
47
65
  /** A route-bearing SAM Event (`Type: Api` or `Type: HttpApi`). */
48
66
  interface ServerlessHttpRoute {
49
67
  /** Key of the event in the function's `Events` map. */
@@ -104,4 +122,4 @@ declare function parseHandler(handler: string): ParsedHandler | null;
104
122
  */
105
123
  declare function readServerlessFunctions(template: CloudFormationTemplate): ServerlessFunctionInfo[];
106
124
 
107
- export { CLOUDFORMATION_YAML_TAGS, type CloudFormationResource, type CloudFormationTemplate, type ParsedHandler, type ServerlessFunctionInfo, type ServerlessHttpRoute, type ServerlessNonHttpEvent, loadCloudFormationTemplate, parseHandler, readServerlessFunctions, refTarget };
125
+ export { type AppSyncResolverBinding, CLOUDFORMATION_YAML_TAGS, type CloudFormationResource, type CloudFormationTemplate, type ParsedHandler, type ServerlessFunctionInfo, type ServerlessHttpRoute, type ServerlessNonHttpEvent, loadCloudFormationTemplate, parseHandler, readAppSyncResolvers, readServerlessFunctions, refTarget };
package/dist/index.js CHANGED
@@ -60,6 +60,165 @@ function refTarget(value) {
60
60
  return null;
61
61
  }
62
62
 
63
+ // src/appsyncResolvers.ts
64
+ var LAMBDA_CATEGORIES = ["Lambda", "Lambdas"];
65
+ function readAppSyncResolvers(template) {
66
+ const resources = template.Resources ?? {};
67
+ return [
68
+ ...fromServerlessShorthand(resources),
69
+ ...fromRawResources(resources)
70
+ ];
71
+ }
72
+ function fromServerlessShorthand(resources) {
73
+ const bindings = [];
74
+ for (const resource of Object.values(resources)) {
75
+ if (resource?.Type !== "AWS::Serverless::GraphQLApi") {
76
+ continue;
77
+ }
78
+ const props = asRecord(resource.Properties) ?? {};
79
+ const lambdaByDataSource = shorthandDataSources(props.DataSources);
80
+ const dataSourceByFunction = shorthandFunctions(props.Functions);
81
+ for (const [typeName, fields] of entriesOf(props.Resolvers)) {
82
+ for (const [fieldName, config] of entriesOf(fields)) {
83
+ const dataSources = dataSourcesReached(config, dataSourceByFunction);
84
+ bindings.push({
85
+ typeName,
86
+ fieldName,
87
+ lambdaFunctionLogicalIds: dataSources.map((name) => lambdaByDataSource.get(name) ?? null).filter((id) => id !== null)
88
+ });
89
+ }
90
+ }
91
+ }
92
+ return bindings;
93
+ }
94
+ function shorthandDataSources(block) {
95
+ const out = /* @__PURE__ */ new Map();
96
+ for (const [category, entries] of entriesOf(block)) {
97
+ if (!LAMBDA_CATEGORIES.includes(category)) {
98
+ continue;
99
+ }
100
+ for (const [name, config] of entriesOf(entries)) {
101
+ const props = asRecord(config);
102
+ out.set(name, props === null ? null : refTarget(props.FunctionArn));
103
+ }
104
+ }
105
+ return out;
106
+ }
107
+ function shorthandFunctions(block) {
108
+ const out = /* @__PURE__ */ new Map();
109
+ for (const [name, config] of entriesOf(block)) {
110
+ const props = asRecord(config);
111
+ const dataSource = props === null ? null : stringOf(props.DataSource);
112
+ if (dataSource !== null) {
113
+ out.set(name, dataSource);
114
+ }
115
+ }
116
+ return out;
117
+ }
118
+ function dataSourcesReached(config, dataSourceByFunction) {
119
+ const props = asRecord(config);
120
+ if (props === null) {
121
+ return [];
122
+ }
123
+ const direct = stringOf(props.DataSource);
124
+ if (direct !== null) {
125
+ return [direct];
126
+ }
127
+ const pipeline = Array.isArray(props.Pipeline) ? props.Pipeline : [];
128
+ return pipeline.map((name) => dataSourceByFunction.get(String(name))).filter((name) => name !== void 0);
129
+ }
130
+ function fromRawResources(resources) {
131
+ const lambdaByLogicalId = /* @__PURE__ */ new Map();
132
+ const lambdaByApiAndName = /* @__PURE__ */ new Map();
133
+ for (const [logicalId, resource] of Object.entries(resources)) {
134
+ if (resource?.Type !== "AWS::AppSync::DataSource") {
135
+ continue;
136
+ }
137
+ const props = asRecord(resource.Properties) ?? {};
138
+ const lambdaConfig = asRecord(props.LambdaConfig);
139
+ const lambda = lambdaConfig === null ? null : refTarget(lambdaConfig.LambdaFunctionArn);
140
+ if (lambda === null) {
141
+ continue;
142
+ }
143
+ lambdaByLogicalId.set(logicalId, lambda);
144
+ const name = stringOf(props.Name);
145
+ if (name !== null) {
146
+ lambdaByApiAndName.set(
147
+ apiScopedKey(refTarget(props.ApiId), name),
148
+ lambda
149
+ );
150
+ }
151
+ }
152
+ const dataSourceByLogicalId = /* @__PURE__ */ new Map();
153
+ const dataSourceByApiAndName = /* @__PURE__ */ new Map();
154
+ for (const [logicalId, resource] of Object.entries(resources)) {
155
+ if (resource?.Type !== "AWS::AppSync::FunctionConfiguration") {
156
+ continue;
157
+ }
158
+ const props = asRecord(resource.Properties) ?? {};
159
+ const dataSource = refTarget(props.DataSourceName);
160
+ if (dataSource === null) {
161
+ continue;
162
+ }
163
+ dataSourceByLogicalId.set(logicalId, dataSource);
164
+ const name = stringOf(props.Name);
165
+ if (name !== null) {
166
+ dataSourceByApiAndName.set(
167
+ apiScopedKey(refTarget(props.ApiId), name),
168
+ dataSource
169
+ );
170
+ }
171
+ }
172
+ const bindings = [];
173
+ for (const resource of Object.values(resources)) {
174
+ if (resource?.Type !== "AWS::AppSync::Resolver") {
175
+ continue;
176
+ }
177
+ const props = asRecord(resource.Properties) ?? {};
178
+ const typeName = stringOf(props.TypeName);
179
+ const fieldName = stringOf(props.FieldName);
180
+ if (typeName === null || fieldName === null) {
181
+ continue;
182
+ }
183
+ const apiId = refTarget(props.ApiId);
184
+ const lookup = (byLogicalId, byName) => (reference) => byLogicalId.get(reference) ?? byName.get(apiScopedKey(apiId, reference));
185
+ bindings.push({
186
+ typeName,
187
+ fieldName,
188
+ lambdaFunctionLogicalIds: rawDataSourcesReached(
189
+ props,
190
+ lookup(dataSourceByLogicalId, dataSourceByApiAndName)
191
+ ).map(lookup(lambdaByLogicalId, lambdaByApiAndName)).filter((id) => id !== void 0)
192
+ });
193
+ }
194
+ return bindings;
195
+ }
196
+ function apiScopedKey(apiId, name) {
197
+ return `${apiId ?? ""}::${name}`;
198
+ }
199
+ function rawDataSourcesReached(props, dataSourceOf) {
200
+ const direct = refTarget(props.DataSourceName);
201
+ if (direct !== null) {
202
+ return [direct];
203
+ }
204
+ const pipeline = asRecord(props.PipelineConfig);
205
+ const functions = Array.isArray(pipeline?.Functions) ? pipeline.Functions : [];
206
+ return functions.map((fn) => {
207
+ const reference = refTarget(fn);
208
+ return reference === null ? void 0 : dataSourceOf(reference);
209
+ }).filter((name) => name !== void 0);
210
+ }
211
+ function asRecord(value) {
212
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
213
+ }
214
+ function entriesOf(value) {
215
+ const record = asRecord(value);
216
+ return record === null ? [] : Object.entries(record);
217
+ }
218
+ function stringOf(value) {
219
+ return typeof value === "string" ? value : null;
220
+ }
221
+
63
222
  // src/serverlessFunctions.ts
64
223
  function parseHandler(handler) {
65
224
  const trimmed = handler.trim();
@@ -160,6 +319,7 @@ export {
160
319
  CLOUDFORMATION_YAML_TAGS,
161
320
  loadCloudFormationTemplate,
162
321
  parseHandler,
322
+ readAppSyncResolvers,
163
323
  readServerlessFunctions,
164
324
  refTarget
165
325
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suss/manifest-aws",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "type": "module",
5
5
  "description": "Parse AWS CloudFormation / SAM templates into plain data — the shared facts layer contract readers and manifest-driven framework packs both consume.",
6
6
  "keywords": [