@reventlessdev/rescript-pulumi-aws 2.4.0-alpha.43

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.
Files changed (169) hide show
  1. package/CHANGELOG.md +366 -0
  2. package/LICENSE +202 -0
  3. package/README.md +19 -0
  4. package/package.json +52 -0
  5. package/rescript.json +27 -0
  6. package/scripts/bundle-resolvers.mjs +77 -0
  7. package/src/AWS/Aws_AvailabilityZone.res +9 -0
  8. package/src/AWS/Aws_AvailabilityZone.res.mjs +9 -0
  9. package/src/AWS/Aws_AvailabilityZone.resi +9 -0
  10. package/src/AWS/Aws_Aws.res +29 -0
  11. package/src/AWS/Aws_Aws.res.mjs +32 -0
  12. package/src/AWS/Aws_Common.res +2 -0
  13. package/src/AWS/Aws_Common.res.mjs +2 -0
  14. package/src/AWS/Aws_Provider.res +10 -0
  15. package/src/AWS/Aws_Provider.res.mjs +2 -0
  16. package/src/Acm/Acm.res +5 -0
  17. package/src/Acm/Acm.res.mjs +12 -0
  18. package/src/Acm/Acm_Certificate.res +27 -0
  19. package/src/Acm/Acm_Certificate.res.mjs +2 -0
  20. package/src/Acm/Acm_CertificateValidation.res +22 -0
  21. package/src/Acm/Acm_CertificateValidation.res.mjs +2 -0
  22. package/src/AppSync/AppSync.res +7 -0
  23. package/src/AppSync/AppSync.res.mjs +18 -0
  24. package/src/AppSync/AppSync_DataSource.res +93 -0
  25. package/src/AppSync/AppSync_DataSource.res.mjs +42 -0
  26. package/src/AppSync/AppSync_Function.res +75 -0
  27. package/src/AppSync/AppSync_Function.res.mjs +36 -0
  28. package/src/AppSync/AppSync_GraphQLApi.res +65 -0
  29. package/src/AppSync/AppSync_GraphQLApi.res.mjs +20 -0
  30. package/src/AppSync/AppSync_Resolver.res +229 -0
  31. package/src/AppSync/AppSync_Resolver.res.mjs +113 -0
  32. package/src/AppSync/AppSync_ResolverRuntime.res +214 -0
  33. package/src/AppSync/AppSync_ResolverRuntime.res.mjs +15 -0
  34. package/src/AppSync/AppSync_Resolver_Functions.res +998 -0
  35. package/src/AppSync/AppSync_Resolver_Functions.res.mjs +913 -0
  36. package/src/Aws.res +5 -0
  37. package/src/Aws.res.mjs +16 -0
  38. package/src/AwsNative/AppSync/AwsNative_AppSync_Api.res +92 -0
  39. package/src/AwsNative/AppSync/AwsNative_AppSync_Api.res.mjs +12 -0
  40. package/src/AwsNative/AppSync/AwsNative_AppSync_ChannelNamespace.res +81 -0
  41. package/src/AwsNative/AppSync/AwsNative_AppSync_ChannelNamespace.res.mjs +18 -0
  42. package/src/AwsNative/AppSync/AwsNative_AppSync_Resolver.res +39 -0
  43. package/src/AwsNative/AppSync/AwsNative_AppSync_Resolver.res.mjs +12 -0
  44. package/src/AwsNative/AwsNative.res +10 -0
  45. package/src/AwsNative/AwsNative.res.mjs +13 -0
  46. package/src/CloudFront/CloudFront.res +5 -0
  47. package/src/CloudFront/CloudFront.res.mjs +12 -0
  48. package/src/CloudFront/CloudFront_Distribution.res +81 -0
  49. package/src/CloudFront/CloudFront_Distribution.res.mjs +2 -0
  50. package/src/CloudFront/CloudFront_OriginAccessControl.res +20 -0
  51. package/src/CloudFront/CloudFront_OriginAccessControl.res.mjs +2 -0
  52. package/src/Cloudwatch/Cloudwatch.res +7 -0
  53. package/src/Cloudwatch/Cloudwatch.res.mjs +18 -0
  54. package/src/Cloudwatch/Cloudwatch_EventRule.res +52 -0
  55. package/src/Cloudwatch/Cloudwatch_EventRule.res.mjs +37 -0
  56. package/src/Cloudwatch/Cloudwatch_EventTarget.res +28 -0
  57. package/src/Cloudwatch/Cloudwatch_EventTarget.res.mjs +25 -0
  58. package/src/Cloudwatch/Cloudwatch_LogGroup.res +14 -0
  59. package/src/Cloudwatch/Cloudwatch_LogGroup.res.mjs +2 -0
  60. package/src/Cloudwatch/Cloudwatch_LogMetricFilter.res +28 -0
  61. package/src/Cloudwatch/Cloudwatch_LogMetricFilter.res.mjs +2 -0
  62. package/src/Cognito/Cognito.res +8 -0
  63. package/src/Cognito/Cognito.res.mjs +21 -0
  64. package/src/Cognito/Cognito_IdentityPool.res +148 -0
  65. package/src/Cognito/Cognito_IdentityPool.res.mjs +65 -0
  66. package/src/Cognito/Cognito_IdentityPoolRoleAttachment.res +18 -0
  67. package/src/Cognito/Cognito_IdentityPoolRoleAttachment.res.mjs +2 -0
  68. package/src/Cognito/Cognito_UserGroup.res +16 -0
  69. package/src/Cognito/Cognito_UserGroup.res.mjs +2 -0
  70. package/src/Cognito/Cognito_UserPool.res +125 -0
  71. package/src/Cognito/Cognito_UserPool.res.mjs +44 -0
  72. package/src/Cognito/Cognito_UserPoolClient.res +33 -0
  73. package/src/Cognito/Cognito_UserPoolClient.res.mjs +2 -0
  74. package/src/DynamoDb/DynamoDb.res +5 -0
  75. package/src/DynamoDb/DynamoDb.res.mjs +12 -0
  76. package/src/DynamoDb/DynamoDb_Stream.res +41 -0
  77. package/src/DynamoDb/DynamoDb_Stream.res.mjs +2 -0
  78. package/src/DynamoDb/DynamoDb_Table.res +66 -0
  79. package/src/DynamoDb/DynamoDb_Table.res.mjs +2 -0
  80. package/src/EC2/EC2.res +13 -0
  81. package/src/EC2/EC2.res.mjs +36 -0
  82. package/src/EC2/EC2_Common.res +13 -0
  83. package/src/EC2/EC2_Common.res.mjs +22 -0
  84. package/src/EC2/EC2_DefaultSecurityGroup.res +22 -0
  85. package/src/EC2/EC2_DefaultSecurityGroup.res.mjs +19 -0
  86. package/src/EC2/EC2_Eip.res +24 -0
  87. package/src/EC2/EC2_Eip.res.mjs +18 -0
  88. package/src/EC2/EC2_InternetGateway.res +25 -0
  89. package/src/EC2/EC2_InternetGateway.res.mjs +18 -0
  90. package/src/EC2/EC2_NatGateway.res +30 -0
  91. package/src/EC2/EC2_NatGateway.res.mjs +18 -0
  92. package/src/EC2/EC2_RouteTable.res +49 -0
  93. package/src/EC2/EC2_RouteTable.res.mjs +38 -0
  94. package/src/EC2/EC2_RouteTableAssociation.res +30 -0
  95. package/src/EC2/EC2_RouteTableAssociation.res.mjs +18 -0
  96. package/src/EC2/EC2_SecurityGroup.res +44 -0
  97. package/src/EC2/EC2_SecurityGroup.res.mjs +49 -0
  98. package/src/EC2/EC2_Subnet.res +31 -0
  99. package/src/EC2/EC2_Subnet.res.mjs +19 -0
  100. package/src/EC2/EC2_Vpc.res +25 -0
  101. package/src/EC2/EC2_Vpc.res.mjs +18 -0
  102. package/src/EC2/EC2_VpcEndpoint.res +43 -0
  103. package/src/EC2/EC2_VpcEndpoint.res.mjs +22 -0
  104. package/src/ECS/ECS.res +6 -0
  105. package/src/ECS/ECS.res.mjs +15 -0
  106. package/src/ECS/ECS_Cluster.res +10 -0
  107. package/src/ECS/ECS_Cluster.res.mjs +2 -0
  108. package/src/ECS/ECS_Container.res +58 -0
  109. package/src/ECS/ECS_Container.res.mjs +2 -0
  110. package/src/ECS/ECS_TaskDefinition.res +31 -0
  111. package/src/ECS/ECS_TaskDefinition.res.mjs +2 -0
  112. package/src/IAM/IAM.res +148 -0
  113. package/src/IAM/IAM.res.mjs +61 -0
  114. package/src/IAM/IAM_ManagedPolicies.res +32 -0
  115. package/src/IAM/IAM_ManagedPolicies.res.mjs +2 -0
  116. package/src/IAM/PolicyDocument.res +163 -0
  117. package/src/IAM/PolicyDocument.res.mjs +164 -0
  118. package/src/Kinesis/Kinesis.res +4 -0
  119. package/src/Kinesis/Kinesis.res.mjs +9 -0
  120. package/src/Kinesis/Kinesis_Stream.res +70 -0
  121. package/src/Kinesis/Kinesis_Stream.res.mjs +9 -0
  122. package/src/Lambda/EventSourceMapping.res +78 -0
  123. package/src/Lambda/EventSourceMapping.res.mjs +2 -0
  124. package/src/Lambda/FunctionUrl.res +41 -0
  125. package/src/Lambda/FunctionUrl.res.mjs +2 -0
  126. package/src/Lambda/Lambda.res +270 -0
  127. package/src/Lambda/Lambda.res.mjs +94 -0
  128. package/src/Route53/Route53.res +4 -0
  129. package/src/Route53/Route53.res.mjs +9 -0
  130. package/src/Route53/Route53_Record.res +36 -0
  131. package/src/Route53/Route53_Record.res.mjs +2 -0
  132. package/src/S3/S3.res +10 -0
  133. package/src/S3/S3.res.mjs +27 -0
  134. package/src/S3/S3_Bucket.res +236 -0
  135. package/src/S3/S3_Bucket.res.mjs +2 -0
  136. package/src/S3/S3_BucketAclV2.res +24 -0
  137. package/src/S3/S3_BucketAclV2.res.mjs +2 -0
  138. package/src/S3/S3_BucketObject.res +22 -0
  139. package/src/S3/S3_BucketObject.res.mjs +2 -0
  140. package/src/S3/S3_BucketOwnershipControls.res +19 -0
  141. package/src/S3/S3_BucketOwnershipControls.res.mjs +2 -0
  142. package/src/S3/S3_BucketPolicy.res +13 -0
  143. package/src/S3/S3_BucketPolicy.res.mjs +2 -0
  144. package/src/S3/S3_BucketPublicAccessBlock.res +16 -0
  145. package/src/S3/S3_BucketPublicAccessBlock.res.mjs +2 -0
  146. package/src/S3/S3_BucketV2.res +11 -0
  147. package/src/S3/S3_BucketV2.res.mjs +2 -0
  148. package/src/SES/SES.bs.js +11 -0
  149. package/src/SES/SES.res +25 -0
  150. package/src/SES/SES.res.mjs +12 -0
  151. package/src/SNS/SNS.res +5 -0
  152. package/src/SNS/SNS.res.mjs +12 -0
  153. package/src/SNS/SNS_Topic.res +71 -0
  154. package/src/SNS/SNS_Topic.res.mjs +2 -0
  155. package/src/SNS/SNS_TopicSubscription.res +21 -0
  156. package/src/SNS/SNS_TopicSubscription.res.mjs +2 -0
  157. package/src/SQS/SQS.res +5 -0
  158. package/src/SQS/SQS.res.mjs +12 -0
  159. package/src/SQS/SQS_Queue.res +92 -0
  160. package/src/SQS/SQS_Queue.res.mjs +19 -0
  161. package/src/SQS/SQS_QueuePolicy.res +13 -0
  162. package/src/SQS/SQS_QueuePolicy.res.mjs +2 -0
  163. package/src/SecretsManager/GetSecretVersion.res +37 -0
  164. package/src/SecretsManager/GetSecretVersion.res.mjs +23 -0
  165. package/src/example/CustomDomainExample.res +71 -0
  166. package/src/example/CustomDomainExample.res.mjs +57 -0
  167. package/tests/AppSync_Resolver_FunctionsTest.mjs +597 -0
  168. package/tests/__mocks__/appsync-utils.mjs +26 -0
  169. package/tests/resolverTestHelper.mjs +46 -0
@@ -0,0 +1,998 @@
1
+ /** AppSync JS resolver code for the APPSYNC_JS runtime.
2
+
3
+ Each value is a Pulumi.Input.t<string> containing a complete ES module with
4
+ exported `request` and `response` functions, ready to be passed as the `code`
5
+ field of AppSync_Resolver.makeUnitJsResolver / makePipelineJsResolver.
6
+ */
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // Shared response snippets (inlined into each resolver's template literal)
10
+ // ---------------------------------------------------------------------------
11
+
12
+ let resultResponseCode = `
13
+ export function response(ctx) {
14
+ if (ctx.error) util.error(ctx.error.message, ctx.error.type);
15
+ return ctx.result;
16
+ }`
17
+
18
+ let firstResultResponseCode = `
19
+ export function response(ctx) {
20
+ if (ctx.error) util.error(ctx.error.message, ctx.error.type);
21
+ return ctx.result.items[0] ?? null;
22
+ }`
23
+
24
+ let resultListResponseCode = `
25
+ export function response(ctx) {
26
+ if (ctx.error) util.error(ctx.error.message, ctx.error.type);
27
+ return ctx.result.items;
28
+ }`
29
+
30
+ let importUtil = `import { util } from '@aws-appsync/utils';`
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Pipeline resolver pass-through (no before/after processing)
34
+ // ---------------------------------------------------------------------------
35
+
36
+ /** Pipeline resolver code with no pre-processing and a standard error-check response. */
37
+ let pipelinePassThrough =
38
+ `${importUtil}
39
+ export function request(ctx) { return {}; }
40
+ ${resultResponseCode}
41
+ `->Pulumi.Input.make
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // Relay Node resolver — pipeline functions for node(id: ID!) query
45
+ // ---------------------------------------------------------------------------
46
+
47
+ /** Pipeline function (NONE datasource): decodes global ID and stashes typeName + localId. */
48
+ let nodeDecodeGlobalId =
49
+ `${importUtil}
50
+ export function request(ctx) {
51
+ const globalId = ctx.args.id;
52
+ try {
53
+ const decoded = util.base64Decode(globalId);
54
+ const colonIdx = decoded.indexOf(':');
55
+ if (colonIdx > 0) {
56
+ ctx.stash.typeName = decoded.substring(0, colonIdx);
57
+ ctx.stash.localId = decoded.substring(colonIdx + 1);
58
+ }
59
+ } catch (e) {
60
+ ctx.stash.typeName = null;
61
+ ctx.stash.localId = null;
62
+ }
63
+ return { payload: null };
64
+ }
65
+ export function response(ctx) {
66
+ return ctx.stash;
67
+ }
68
+ `->Pulumi.Input.make
69
+
70
+ /** Pipeline function (DynamoDB datasource): fetches item if typeName matches, skips otherwise.
71
+ Generated per entity type at deploy time. */
72
+ let nodeGetItemForType = (~typeName: string): Pulumi.Input.t<string> =>
73
+ `${importUtil}
74
+ export function request(ctx) {
75
+ if (ctx.stash.typeName !== '${typeName}') return runtime.earlyReturn(ctx.prev.result);
76
+ return {
77
+ operation: 'GetItem',
78
+ key: { id: util.dynamodb.toDynamoDB(ctx.stash.localId) }
79
+ };
80
+ }
81
+ export function response(ctx) {
82
+ if (ctx.error) util.error(ctx.error.message, ctx.error.type);
83
+ if (!ctx.result) return null;
84
+ return { ...ctx.result, __typename: '${typeName}', id: ctx.args.id };
85
+ }
86
+ `->Pulumi.Input.make
87
+
88
+ // ---------------------------------------------------------------------------
89
+ // DynamoDB read — by primary key
90
+ // ---------------------------------------------------------------------------
91
+
92
+ let getItemById =
93
+ `${importUtil}
94
+ export function request(ctx) {
95
+ return {
96
+ operation: 'GetItem',
97
+ key: { id: util.dynamodb.toDynamoDB(ctx.args.id) }
98
+ };
99
+ }
100
+ ${resultResponseCode}
101
+ `->Pulumi.Input.make
102
+
103
+ let queryById =
104
+ `${importUtil}
105
+ export function request(ctx) {
106
+ return {
107
+ operation: 'Query',
108
+ query: {
109
+ expression: 'id = :id',
110
+ expressionValues: { ':id': util.dynamodb.toDynamoDB(ctx.args.id) }
111
+ }
112
+ };
113
+ }
114
+ ${resultResponseCode}
115
+ `->Pulumi.Input.make
116
+
117
+ /** Query by partition key with sort-key conditions and Relay cursor pagination.
118
+ Reads from `args.filter` object: `prefix`, `from`, `to`, `eq`, `order` (ASC/DESC).
119
+ Relay pagination: `first`/`after` (forward) or `last`/`before` (backward).
120
+ Cursor is base64 of the sort key value.
121
+ Returns a Relay `{ edges, pageInfo }` shape reusing the entity's `Connection` type. */
122
+ let queryItemsWithSortConditions = (sortField: string) =>
123
+ `${importUtil}
124
+ const encodeCursor = (skValue) => util.base64Encode(skValue);
125
+ const decodeCursor = (cursor) => util.base64Decode(cursor);
126
+ export function request(ctx) {
127
+ const args = ctx.args;
128
+ const filter = args.filter ?? {};
129
+ const isBackward = args.last != null;
130
+ const expressionNames = { '#id': 'id' };
131
+ const expressionValues = { ':id': util.dynamodb.toDynamoDB(args.id) };
132
+ let skCondition;
133
+ if (filter.eq != null) {
134
+ expressionValues[':eq'] = util.dynamodb.toDynamoDB(filter.eq);
135
+ skCondition = '#sk = :eq';
136
+ } else if (filter.prefix != null) {
137
+ expressionValues[':prefix'] = util.dynamodb.toDynamoDB(filter.prefix);
138
+ skCondition = 'begins_with(#sk, :prefix)';
139
+ } else if (filter.from != null && filter.to != null) {
140
+ expressionValues[':from'] = util.dynamodb.toDynamoDB(filter.from);
141
+ expressionValues[':to'] = util.dynamodb.toDynamoDB(filter.to);
142
+ skCondition = '#sk BETWEEN :from AND :to';
143
+ } else if (filter.from != null) {
144
+ expressionValues[':from'] = util.dynamodb.toDynamoDB(filter.from);
145
+ skCondition = '#sk >= :from';
146
+ } else if (filter.to != null) {
147
+ expressionValues[':to'] = util.dynamodb.toDynamoDB(filter.to);
148
+ skCondition = '#sk <= :to';
149
+ }
150
+ if (isBackward && args.before != null) {
151
+ const beforeKey = decodeCursor(args.before);
152
+ expressionValues[':cursor'] = util.dynamodb.toDynamoDB(beforeKey);
153
+ const cursorCond = '#sk < :cursor';
154
+ skCondition = skCondition ? \`(\${skCondition}) AND \${cursorCond}\` : cursorCond;
155
+ } else if (!isBackward && args.after != null) {
156
+ const afterKey = decodeCursor(args.after);
157
+ expressionValues[':cursor'] = util.dynamodb.toDynamoDB(afterKey);
158
+ const cursorCond = '#sk > :cursor';
159
+ skCondition = skCondition ? \`(\${skCondition}) AND \${cursorCond}\` : cursorCond;
160
+ }
161
+ if (skCondition) expressionNames['#sk'] = '${sortField}';
162
+ const expression = skCondition ? \`#id = :id AND \${skCondition}\` : '#id = :id';
163
+ const orderDesc = filter.order === 'DESC';
164
+ const scanForward = isBackward ? orderDesc : !orderDesc;
165
+ const pageSize = isBackward ? (args.last ?? 50) : (args.first ?? 50);
166
+ return {
167
+ operation: 'Query',
168
+ query: { expression, expressionNames, expressionValues },
169
+ scanIndexForward: scanForward,
170
+ limit: pageSize + 1,
171
+ };
172
+ }
173
+ export function response(ctx) {
174
+ if (ctx.error) util.error(ctx.error.message, ctx.error.type);
175
+ const args = ctx.args;
176
+ const filter = args.filter ?? {};
177
+ const isBackward = args.last != null;
178
+ const pageSize = isBackward ? (args.last ?? 50) : (args.first ?? 50);
179
+ let items = ctx.result.items ?? [];
180
+ const hasMore = items.length > pageSize;
181
+ if (hasMore) items = items.slice(0, pageSize);
182
+ if (isBackward) items = items.reverse();
183
+ const edges = items.map(item => ({ node: item, cursor: encodeCursor(item['${sortField}'] ?? '') }));
184
+ const startCursor = edges.length > 0 ? edges[0].cursor : null;
185
+ const endCursor = edges.length > 0 ? edges[edges.length - 1].cursor : null;
186
+ return {
187
+ edges,
188
+ pageInfo: {
189
+ hasNextPage: !isBackward && hasMore,
190
+ hasPreviousPage: isBackward && hasMore,
191
+ startCursor,
192
+ endCursor,
193
+ },
194
+ };
195
+ }
196
+ `->Pulumi.Input.make
197
+
198
+ let queryByIdSort = (sortField: string) =>
199
+ `${importUtil}
200
+ export function request(ctx) {
201
+ return {
202
+ operation: 'Query',
203
+ query: {
204
+ expression: 'id = :id AND #${sortField} = :${sortField}',
205
+ expressionNames: { '#${sortField}': '${sortField}' },
206
+ expressionValues: {
207
+ ':id': util.dynamodb.toDynamoDB(ctx.args.id),
208
+ ':${sortField}': util.dynamodb.toDynamoDB(ctx.args.${sortField})
209
+ }
210
+ }
211
+ };
212
+ }
213
+ ${firstResultResponseCode}
214
+ `->Pulumi.Input.make
215
+
216
+ // ---------------------------------------------------------------------------
217
+ // DynamoDB read — by index
218
+ // ---------------------------------------------------------------------------
219
+
220
+ let queryByIndex = (index: string) =>
221
+ `${importUtil}
222
+ export function request(ctx) {
223
+ return {
224
+ operation: 'Query',
225
+ query: {
226
+ expression: '#${index} = :${index}',
227
+ expressionNames: { '#${index}': '${index}' },
228
+ expressionValues: { ':${index}': util.dynamodb.toDynamoDB(ctx.args.${index}) }
229
+ },
230
+ index: '${index}'
231
+ };
232
+ }
233
+ ${resultResponseCode}
234
+ `->Pulumi.Input.make
235
+
236
+ let queryByIndexDeletable = (index: string) =>
237
+ `${importUtil}
238
+ export function request(ctx) {
239
+ const req = {
240
+ operation: 'Query',
241
+ query: {
242
+ expression: '#${index} = :${index}',
243
+ expressionNames: { '#${index}': '${index}' },
244
+ expressionValues: { ':${index}': util.dynamodb.toDynamoDB(ctx.args.${index}) }
245
+ },
246
+ index: '${index}'
247
+ };
248
+ if (ctx.args.hideDeleted) {
249
+ req.filter = {
250
+ expression: '#deleted = :false',
251
+ expressionNames: { '#deleted': 'deleted' },
252
+ expressionValues: { ':false': util.dynamodb.toBoolean(false) }
253
+ };
254
+ }
255
+ return req;
256
+ }
257
+ ${resultResponseCode}
258
+ `->Pulumi.Input.make
259
+
260
+ let queryByIndexSort = (~index: string, ~idField: string, ~sortField: string) =>
261
+ `${importUtil}
262
+ export function request(ctx) {
263
+ const query = ctx.args.${sortField}
264
+ ? {
265
+ expression: '#${idField} = :${idField} AND #${sortField} = :${sortField}',
266
+ expressionNames: { '#${idField}': '${idField}', '#${sortField}': '${sortField}' },
267
+ expressionValues: {
268
+ ':${idField}': util.dynamodb.toDynamoDB(ctx.args.${idField}),
269
+ ':${sortField}': util.dynamodb.toDynamoDB(ctx.args.${sortField})
270
+ }
271
+ }
272
+ : {
273
+ expression: '#${idField} = :${idField}',
274
+ expressionNames: { '#${idField}': '${idField}' },
275
+ expressionValues: { ':${idField}': util.dynamodb.toDynamoDB(ctx.args.${idField}) }
276
+ };
277
+ return { operation: 'Query', query, index: '${index}' };
278
+ }
279
+ ${resultResponseCode}
280
+ `->Pulumi.Input.make
281
+
282
+ // AppSync JS runtime restrictions (APPSYNC_JS 1.0.0):
283
+ // - No `for` loops (for/for-of/for-in all fail validation)
284
+ // - No String() / .toString() — use '' + value instead
285
+ // - Object.keys().forEach() works for iteration
286
+ let queryByIndexFiltered = (~index: string, ~idField: string) =>
287
+ `${importUtil}
288
+ export function request(ctx) {
289
+ const args = ctx.args;
290
+ const query = {
291
+ expression: '#${idField} = :${idField}',
292
+ expressionNames: { '#${idField}': '${idField}' },
293
+ expressionValues: { ':${idField}': util.dynamodb.toDynamoDB(args.${idField}) }
294
+ };
295
+ let expression = '';
296
+ const names = {};
297
+ const values = {};
298
+ Object.keys(args).forEach(key => {
299
+ const value = args[key];
300
+ if (key === '${idField}' || key === 'limit' || key === 'nextToken' || key === 'forward') return;
301
+ if (value == null || value === '') return;
302
+ if (expression) expression += ' AND';
303
+ if (key === 'hideDeleted') {
304
+ if (value === true) {
305
+ expression += ' #deleted = :false';
306
+ names['#deleted'] = 'deleted';
307
+ values[':false'] = false;
308
+ }
309
+ } else {
310
+ expression += ' contains(#' + key + ', :' + key + ')';
311
+ names['#' + key] = key;
312
+ values[':' + key] = '' + value;
313
+ }
314
+ });
315
+ const result = {
316
+ operation: 'Query',
317
+ query,
318
+ index: '${index}',
319
+ limit: (args.limit ?? 50),
320
+ nextToken: (args.nextToken ?? null),
321
+ scanIndexForward: (args.forward ?? true)
322
+ };
323
+ if (expression) {
324
+ result.filter = { expression, expressionNames: names, expressionValues: util.dynamodb.toMapValues(values) };
325
+ }
326
+ return result;
327
+ }
328
+ ${resultResponseCode}
329
+ `->Pulumi.Input.make
330
+
331
+ let queryByIndexSortFiltered = (~index: string, ~idField: string, ~sortField: string) =>
332
+ `${importUtil}
333
+ export function request(ctx) {
334
+ const args = ctx.args;
335
+ const query = args.${sortField}
336
+ ? {
337
+ expression: '#${idField} = :${idField} AND #${sortField} = :${sortField}',
338
+ expressionNames: { '#${idField}': '${idField}', '#${sortField}': '${sortField}' },
339
+ expressionValues: {
340
+ ':${idField}': util.dynamodb.toDynamoDB(args.${idField}),
341
+ ':${sortField}': util.dynamodb.toDynamoDB(args.${sortField})
342
+ }
343
+ }
344
+ : {
345
+ expression: '#${idField} = :${idField}',
346
+ expressionNames: { '#${idField}': '${idField}' },
347
+ expressionValues: { ':${idField}': util.dynamodb.toDynamoDB(args.${idField}) }
348
+ };
349
+ let expression = '';
350
+ const names = {};
351
+ const values = {};
352
+ Object.keys(args).forEach(key => {
353
+ const value = args[key];
354
+ if (key === '${idField}' || key === '${sortField}' || key === 'limit' || key === 'nextToken' || key === 'forward') return;
355
+ if (value == null || value === '') return;
356
+ if (expression) expression += ' AND';
357
+ if (key === 'hideDeleted') {
358
+ if (value === true) {
359
+ expression += ' #deleted = :false';
360
+ names['#deleted'] = 'deleted';
361
+ values[':false'] = false;
362
+ }
363
+ } else {
364
+ expression += ' contains(#' + key + ', :' + key + ')';
365
+ names['#' + key] = key;
366
+ values[':' + key] = '' + value;
367
+ }
368
+ });
369
+ const result = {
370
+ operation: 'Query',
371
+ query,
372
+ index: '${index}',
373
+ limit: (args.limit ?? 50),
374
+ nextToken: (args.nextToken ?? null),
375
+ scanIndexForward: (args.forward ?? true)
376
+ };
377
+ if (expression) {
378
+ result.filter = { expression, expressionNames: names, expressionValues: util.dynamodb.toMapValues(values) };
379
+ }
380
+ return result;
381
+ }
382
+ ${resultResponseCode}
383
+ `->Pulumi.Input.make
384
+
385
+ // ---------------------------------------------------------------------------
386
+ // DynamoDB read — list all
387
+ // ---------------------------------------------------------------------------
388
+
389
+ let listAllItems =
390
+ `${importUtil}
391
+ export function request(ctx) {
392
+ return {
393
+ operation: 'Scan',
394
+ limit: (ctx.args.limit ?? 50),
395
+ nextToken: (ctx.args.nextToken ?? null)
396
+ };
397
+ }
398
+ ${resultResponseCode}
399
+ `->Pulumi.Input.make
400
+
401
+ // ---------------------------------------------------------------------------
402
+ // DynamoDB read — list all (Relay Connection spec)
403
+ // ---------------------------------------------------------------------------
404
+
405
+ /**
406
+ * Scan with optional `filter` arg: `{search?, searchPrefix?, ids?, <field>Eq?, <field>From?, <field>To?}`
407
+ * and optional `orderBy: {field, direction}`.
408
+ *
409
+ * `search` → `contains(labelField, :v)` — case-sensitive on DynamoDB. Callers that
410
+ * need case-insensitive matching should project a lowercased label column
411
+ * (future Phase 6.1 / external full-text search).
412
+ * `searchPrefix` → `begins_with(labelField, :v)` — case-sensitive. Scan-only here;
413
+ * Phase 6 `@searchable` provisions a GSI to promote this to a query.
414
+ * `ids` → FilterExpression `#id IN (:id0, :id1, …)`. Simple scan-based
415
+ * path; BatchGetItem optimisation is deferred (open question 1).
416
+ * `<field>Eq` → FilterExpression `#<field> = :<field>Eq`, one per `~filterFields` entry.
417
+ * `<field>From` → FilterExpression `#<field> >= :<field>From`, one per `~rangeFields` entry.
418
+ * `<field>To` → FilterExpression `#<field> <= :<field>To`, one per `~rangeFields` entry.
419
+ * `orderBy` → JS-runtime sort over the returned page when `orderBy.field` is in
420
+ * `~sortFields`. **Per-page only**, not global — DynamoDB Scan returns
421
+ * items in indeterminate order and `ScanIndexForward` does not apply
422
+ * to Scan. Index-routed Query (v1.5) lifts this caveat for indexed
423
+ * sort fields; `@scanSort` on a non-indexed field is per-page even then.
424
+ *
425
+ * Empty-string and null filter values are treated as "no filter" — consistent with the
426
+ * in-memory adapter so clients don't need to conditionally omit keys.
427
+ */
428
+ let listAllItemsConnection = (
429
+ ~labelField: string,
430
+ ~filterFields: array<string>=[],
431
+ ~rangeFields: array<string>=[],
432
+ ~sortFields: array<string>=[],
433
+ ) => {
434
+ let filterClauses =
435
+ filterFields
436
+ ->Array.map(f => `
437
+ if (filter.${f}Eq !== undefined && filter.${f}Eq !== null && filter.${f}Eq !== '') {
438
+ names['#${f}'] = '${f}';
439
+ values[':${f}Eq'] = util.dynamodb.toDynamoDB(filter.${f}Eq);
440
+ parts.push('#${f} = :${f}Eq');
441
+ }`)
442
+ ->Array.join("")
443
+ let rangeClauses =
444
+ rangeFields
445
+ ->Array.map(f => `
446
+ if (filter.${f}From !== undefined && filter.${f}From !== null && filter.${f}From !== '') {
447
+ names['#${f}'] = '${f}';
448
+ values[':${f}From'] = util.dynamodb.toDynamoDB(filter.${f}From);
449
+ parts.push('#${f} >= :${f}From');
450
+ }
451
+ if (filter.${f}To !== undefined && filter.${f}To !== null && filter.${f}To !== '') {
452
+ names['#${f}'] = '${f}';
453
+ values[':${f}To'] = util.dynamodb.toDynamoDB(filter.${f}To);
454
+ parts.push('#${f} <= :${f}To');
455
+ }`)
456
+ ->Array.join("")
457
+ let sortFieldsLiteral =
458
+ sortFields->Array.map(f => `'${f}'`)->Array.join(", ")
459
+ let sortBlock = if sortFields->Array.length == 0 {
460
+ ""
461
+ } else {
462
+ // APPSYNC_JS 1.0.0 forbids: Array.prototype.sort(comparator), arrow/function
463
+ // expressions passed to sort, for/while loops, recursion, and ++/--. So we
464
+ // can't run a comparator-driven sort and we can't write our own loop. Use a
465
+ // schwartzian transform: encode each item as `<sortKey>\x01<json>`, run the
466
+ // no-comparator default sort (lexicographic), reverse for DESC, and decode.
467
+ // Numeric fields get zero-padded so lex order matches numeric order for
468
+ // non-negative values (typical for IDs, counts, timestamps). Negatives sort
469
+ // lexicographically — acceptable since DynamoDB sort keys are rarely signed
470
+ // numbers. Nulls split out and append to the end regardless of direction.
471
+ `
472
+ // Per-page sort (Scan returns items in indeterminate order; ScanIndexForward
473
+ // does not apply to Scan). Global ordering across pages requires v1.5 index
474
+ // promotion; @scanSort is per-page even then.
475
+ const orderBy = ctx.args.orderBy;
476
+ const sortFields = [${sortFieldsLiteral}];
477
+ if (orderBy && orderBy.field && sortFields.indexOf(orderBy.field) >= 0) {
478
+ const field = orderBy.field;
479
+ const nulls = items.filter(it => it[field] === null || it[field] === undefined);
480
+ const nonNulls = items.filter(it => it[field] !== null && it[field] !== undefined);
481
+ const encoded = nonNulls.map(it => {
482
+ const v = it[field];
483
+ const key = (typeof v === 'number')
484
+ ? ('0000000000000000000000' + v).slice(-22)
485
+ : ('' + v);
486
+ return key + '\\x01' + JSON.stringify(it);
487
+ });
488
+ encoded.sort();
489
+ if (orderBy.direction === 'DESC') encoded.reverse();
490
+ items = encoded.map(e => JSON.parse(e.split('\\x01')[1])).concat(nulls);
491
+ }`
492
+ }
493
+ `${importUtil}
494
+ export function request(ctx) {
495
+ const filter = ctx.args.filter ?? {};
496
+ const names = {};
497
+ const values = {};
498
+ const parts = [];
499
+ if (typeof filter.search === 'string' && filter.search.length > 0) {
500
+ names['#label'] = '${labelField}';
501
+ values[':search'] = util.dynamodb.toDynamoDB(filter.search);
502
+ parts.push('contains(#label, :search)');
503
+ }
504
+ if (typeof filter.searchPrefix === 'string' && filter.searchPrefix.length > 0) {
505
+ names['#label'] = '${labelField}';
506
+ values[':searchPrefix'] = util.dynamodb.toDynamoDB(filter.searchPrefix);
507
+ parts.push('begins_with(#label, :searchPrefix)');
508
+ }
509
+ if (Array.isArray(filter.ids) && filter.ids.length > 0) {
510
+ names['#id'] = 'id';
511
+ const placeholders = filter.ids.map((id, i) => {
512
+ const key = ':id' + i;
513
+ values[key] = util.dynamodb.toDynamoDB(id);
514
+ return key;
515
+ });
516
+ parts.push('#id IN (' + placeholders.join(', ') + ')');
517
+ }${filterClauses}${rangeClauses}
518
+ const req = {
519
+ operation: 'Scan',
520
+ limit: (ctx.args.first ?? 50),
521
+ nextToken: (ctx.args.after ?? null),
522
+ };
523
+ if (parts.length > 0) {
524
+ req.filter = {
525
+ expression: parts.join(' AND '),
526
+ expressionNames: names,
527
+ expressionValues: values,
528
+ };
529
+ }
530
+ return req;
531
+ }
532
+ export function response(ctx) {
533
+ if (ctx.error) util.error(ctx.error.message, ctx.error.type);
534
+ let items = ctx.result?.items ?? [];${sortBlock}
535
+ const edges = items.map((item, i) => ({
536
+ node: item,
537
+ cursor: ctx.args.after ? ctx.args.after + '_' + i : '' + i,
538
+ }));
539
+ return {
540
+ edges,
541
+ pageInfo: {
542
+ hasNextPage: !!ctx.result?.nextToken,
543
+ hasPreviousPage: !!ctx.args.after,
544
+ startCursor: edges.length > 0 ? edges[0].cursor : null,
545
+ endCursor: edges.length > 0 ? edges[edges.length - 1].cursor : null,
546
+ },
547
+ };
548
+ }
549
+ `->Pulumi.Input.make
550
+ }
551
+
552
+ // ---------------------------------------------------------------------------
553
+ // DynamoDB nested resolvers — resolve linked item(s) by ID
554
+ // ---------------------------------------------------------------------------
555
+
556
+ let resolveId = (~sourceIdField: string) =>
557
+ `${importUtil}
558
+ export function request(ctx) {
559
+ if (!ctx.source.${sourceIdField}) return runtime.earlyReturn(null);
560
+ return {
561
+ operation: 'Query',
562
+ query: {
563
+ expression: '#id = :id',
564
+ expressionNames: { '#id': 'id' },
565
+ expressionValues: { ':id': util.dynamodb.toDynamoDB(ctx.source.${sourceIdField}) }
566
+ },
567
+ limit: (ctx.args.limit ?? 50),
568
+ nextToken: (ctx.args.nextToken ?? null),
569
+ scanIndexForward: (ctx.args.forward ?? true)
570
+ };
571
+ }
572
+ ${firstResultResponseCode}
573
+ `->Pulumi.Input.make
574
+
575
+ let resolveIdSort = (~sourceIdField: string, ~sourceSortField: string, ~targetSortField: string) =>
576
+ `${importUtil}
577
+ export function request(ctx) {
578
+ return {
579
+ operation: 'Query',
580
+ query: {
581
+ expression: '#id = :id AND #${targetSortField} = :${targetSortField}',
582
+ expressionNames: { '#id': 'id', '#${targetSortField}': '${targetSortField}' },
583
+ expressionValues: {
584
+ ':id': util.dynamodb.toDynamoDB(ctx.source.${sourceIdField}),
585
+ ':${targetSortField}': util.dynamodb.toDynamoDB(ctx.source.${sourceSortField})
586
+ }
587
+ },
588
+ limit: (ctx.args.limit ?? 50),
589
+ nextToken: (ctx.args.nextToken ?? null),
590
+ scanIndexForward: (ctx.args.forward ?? true)
591
+ };
592
+ }
593
+ ${firstResultResponseCode}
594
+ `->Pulumi.Input.make
595
+
596
+ let resolveIdSortArgument = (
597
+ ~sourceIdField: string,
598
+ ~sourceSortArgument: string,
599
+ ~targetSortField: string,
600
+ ) =>
601
+ `${importUtil}
602
+ export function request(ctx) {
603
+ const query = ctx.args.${sourceSortArgument}
604
+ ? {
605
+ expression: '#id = :id AND #${targetSortField} = :${targetSortField}',
606
+ expressionNames: { '#id': 'id', '#${targetSortField}': '${targetSortField}' },
607
+ expressionValues: {
608
+ ':id': util.dynamodb.toDynamoDB(ctx.source.${sourceIdField}),
609
+ ':${targetSortField}': util.dynamodb.toDynamoDB(ctx.args.${sourceSortArgument})
610
+ }
611
+ }
612
+ : {
613
+ expression: '#id = :id',
614
+ expressionNames: { '#id': 'id' },
615
+ expressionValues: { ':id': util.dynamodb.toDynamoDB(ctx.source.${sourceIdField}) }
616
+ };
617
+ return {
618
+ operation: 'Query',
619
+ query,
620
+ limit: (ctx.args.limit ?? 50),
621
+ nextToken: (ctx.args.nextToken ?? null),
622
+ scanIndexForward: (ctx.args.forward ?? true)
623
+ };
624
+ }
625
+ ${firstResultResponseCode}
626
+ `->Pulumi.Input.make
627
+
628
+ // ---------------------------------------------------------------------------
629
+ // DynamoDB nested resolvers — resolve by index
630
+ // ---------------------------------------------------------------------------
631
+
632
+ let resolveIdByIndex = (~index: string, ~sourceIdField: string, ~targetIdField: string) =>
633
+ `${importUtil}
634
+ export function request(ctx) {
635
+ return {
636
+ operation: 'Query',
637
+ query: {
638
+ expression: '#${targetIdField} = :${targetIdField}',
639
+ expressionNames: { '#${targetIdField}': '${targetIdField}' },
640
+ expressionValues: { ':${targetIdField}': util.dynamodb.toDynamoDB(ctx.source.${sourceIdField}) }
641
+ },
642
+ index: '${index}',
643
+ limit: (ctx.args.limit ?? 50),
644
+ nextToken: (ctx.args.nextToken ?? null),
645
+ scanIndexForward: (ctx.args.forward ?? true)
646
+ };
647
+ }
648
+ ${firstResultResponseCode}
649
+ `->Pulumi.Input.make
650
+
651
+ let resolveIdByIndexSort = (
652
+ ~index: string,
653
+ ~sourceIdField: string,
654
+ ~sourceSortField: string,
655
+ ~targetIdField: string,
656
+ ~targetSortField: string,
657
+ ) =>
658
+ `${importUtil}
659
+ export function request(ctx) {
660
+ return {
661
+ operation: 'Query',
662
+ query: {
663
+ expression: '#${targetIdField} = :${targetIdField} AND #${targetSortField} = :${targetSortField}',
664
+ expressionNames: { '#${targetIdField}': '${targetIdField}', '#${targetSortField}': '${targetSortField}' },
665
+ expressionValues: {
666
+ ':${targetIdField}': util.dynamodb.toDynamoDB(ctx.source.${sourceIdField}),
667
+ ':${targetSortField}': util.dynamodb.toDynamoDB(ctx.source.${sourceSortField})
668
+ }
669
+ },
670
+ index: '${index}',
671
+ limit: (ctx.args.limit ?? 50),
672
+ nextToken: (ctx.args.nextToken ?? null),
673
+ scanIndexForward: (ctx.args.forward ?? true)
674
+ };
675
+ }
676
+ ${firstResultResponseCode}
677
+ `->Pulumi.Input.make
678
+
679
+ let resolveIdByIndexSortArgument = (
680
+ ~index: string,
681
+ ~sourceIdField: string,
682
+ ~sourceSortArgument: string,
683
+ ~targetIdField: string,
684
+ ~targetSortField: string,
685
+ ) =>
686
+ `${importUtil}
687
+ export function request(ctx) {
688
+ const query = ctx.args.${sourceSortArgument}
689
+ ? {
690
+ expression: '#${targetIdField} = :${targetIdField} AND #${targetSortField} = :${targetSortField}',
691
+ expressionNames: { '#${targetIdField}': '${targetIdField}', '#${targetSortField}': '${targetSortField}' },
692
+ expressionValues: {
693
+ ':${targetIdField}': util.dynamodb.toDynamoDB(ctx.source.${sourceIdField}),
694
+ ':${targetSortField}': util.dynamodb.toDynamoDB(ctx.args.${sourceSortArgument})
695
+ }
696
+ }
697
+ : {
698
+ expression: '#${targetIdField} = :${targetIdField}',
699
+ expressionNames: { '#${targetIdField}': '${targetIdField}' },
700
+ expressionValues: { ':${targetIdField}': util.dynamodb.toDynamoDB(ctx.source.${sourceIdField}) }
701
+ };
702
+ return {
703
+ operation: 'Query',
704
+ query,
705
+ index: '${index}',
706
+ limit: (ctx.args.limit ?? 50),
707
+ nextToken: (ctx.args.nextToken ?? null),
708
+ scanIndexForward: (ctx.args.forward ?? true)
709
+ };
710
+ }
711
+ ${firstResultResponseCode}
712
+ `->Pulumi.Input.make
713
+
714
+ // resolveIds — returns a plain string (table name is interpolated by the adapter
715
+ // via Pulumi.Output.apply)
716
+ let resolveIds = (tableName: string, ~idsField: string, ~sortField: option<string>) => {
717
+ let keysCode = switch sortField {
718
+ | Some(sf) =>
719
+ `id => ({ id: util.dynamodb.toString(id.id), ${sf}: util.dynamodb.toString(id.${sf}) })`
720
+ | None => `id => ({ id: util.dynamodb.toString(id) })`
721
+ }
722
+ `${importUtil}
723
+ import { runtime } from '@aws-appsync/utils';
724
+ export function request(ctx) {
725
+ const idList = ctx.source.${idsField};
726
+ if (idList && idList.length > 0) {
727
+ return {
728
+ operation: 'BatchGetItem',
729
+ tables: {
730
+ '${tableName}': {
731
+ keys: idList.map(${keysCode}),
732
+ consistentRead: true
733
+ }
734
+ }
735
+ };
736
+ }
737
+ return { operation: 'GetItem', key: { id: util.dynamodb.toString(ctx.source.id) } };
738
+ }
739
+ export function response(ctx) {
740
+ if (ctx.error) util.error(ctx.error.message, ctx.error.type);
741
+ return ctx.result;
742
+ }
743
+ `
744
+ }
745
+
746
+ /** Batched-by-ids — top-level Query resolver reading `ctx.args.ids: [String!]!`
747
+ and returning the matching items via a single BatchGetItem. Missing ids drop
748
+ out (BatchGetItem does not preserve cardinality); empty input short-circuits
749
+ to an empty result without hitting DDB. Table name is interpolated at deploy
750
+ time, since BatchGetItem's `tables` map keys on the literal table name.
751
+ Single-key tables only — composite-key BatchGetItem needs both pk + sk per
752
+ key entry, which this template doesn't construct. */
753
+ let batchGetItemsByIds = (tableName: string) =>
754
+ `${importUtil}
755
+ import { runtime } from '@aws-appsync/utils';
756
+ export function request(ctx) {
757
+ const ids = ctx.args.ids ?? [];
758
+ if (ids.length === 0) return runtime.earlyReturn([]);
759
+ return {
760
+ operation: 'BatchGetItem',
761
+ tables: {
762
+ '${tableName}': {
763
+ keys: ids.map(id => ({ id: util.dynamodb.toDynamoDB(id) })),
764
+ consistentRead: true,
765
+ }
766
+ }
767
+ };
768
+ }
769
+ export function response(ctx) {
770
+ if (ctx.error) util.error(ctx.error.message, ctx.error.type);
771
+ // BatchGetItem returns null in the result array for keys that don't exist
772
+ // in the table, preserving index correspondence with the input. The SDL
773
+ // returns this field as \`[T!]!\` (non-null element list), so any single
774
+ // missing id makes the entire field fail with "Cannot return null for
775
+ // non-nullable type" and the caller sees data=null. Filter the nulls so
776
+ // the field returns just the items that were found.
777
+ return (ctx.result?.data?.['${tableName}'] ?? []).filter(item => item !== null);
778
+ }
779
+ `
780
+
781
+ // ---------------------------------------------------------------------------
782
+ // DynamoDB write
783
+ // ---------------------------------------------------------------------------
784
+
785
+ let putItem =
786
+ `${importUtil}
787
+ export function request(ctx) {
788
+ return {
789
+ operation: 'PutItem',
790
+ key: { id: util.dynamodb.toDynamoDB(ctx.args.id) },
791
+ attributeValues: util.dynamodb.toMapValues(ctx.args)
792
+ };
793
+ }
794
+ ${resultResponseCode}
795
+ `->Pulumi.Input.make
796
+
797
+ let addItemToList = (~listName: string, ~itemName: string) =>
798
+ `${importUtil}
799
+ export function request(ctx) {
800
+ return {
801
+ operation: 'UpdateItem',
802
+ key: { id: util.dynamodb.toDynamoDB(ctx.args.id) },
803
+ update: {
804
+ expression: 'SET #list = list_append(#list, :item)',
805
+ expressionNames: { '#list': '${listName}' },
806
+ expressionValues: { ':item': util.dynamodb.toList([util.dynamodb.toDynamoDB(ctx.args.${itemName})]) }
807
+ }
808
+ };
809
+ }
810
+ ${resultResponseCode}
811
+ `->Pulumi.Input.make
812
+
813
+ let deleteItem =
814
+ `${importUtil}
815
+ export function request(ctx) {
816
+ return {
817
+ operation: 'DeleteItem',
818
+ key: { id: util.dynamodb.toString(ctx.args.id) }
819
+ };
820
+ }
821
+ ${resultResponseCode}
822
+ `->Pulumi.Input.make
823
+
824
+ // ---------------------------------------------------------------------------
825
+ // Lambda invocations
826
+ // ---------------------------------------------------------------------------
827
+
828
+ /** Invoke a CommandGenerator Lambda. `command` is the Aggregate command name. */
829
+ let invokeCommandGenerator = (command: string) =>
830
+ `${importUtil}
831
+ export function request(ctx) {
832
+ const id = ctx.identity;
833
+ const isCognito = id != null && id.sub != null;
834
+ return {
835
+ operation: 'Invoke',
836
+ payload: {
837
+ command: '${command}',
838
+ arguments: ctx.args,
839
+ meta: {
840
+ ip: id?.sourceIp ?? null,
841
+ user: id?.username ?? null,
842
+ info: ctx.info.parentTypeName + '.' + ctx.info.fieldName
843
+ },
844
+ identity: isCognito
845
+ ? {
846
+ userId: id.sub,
847
+ username: id.username,
848
+ groups: id.claims?.['cognito:groups'] ?? [],
849
+ claims: id.claims,
850
+ provider: 'Cognito'
851
+ }
852
+ : id != null
853
+ ? {
854
+ userArn: id.userArn ?? null,
855
+ accountId: id.accountId ?? null,
856
+ username: id.username ?? null,
857
+ provider: 'IAM'
858
+ }
859
+ : null
860
+ }
861
+ };
862
+ }
863
+ ${resultResponseCode}
864
+ `->Pulumi.Input.make
865
+
866
+ /** Invoke a DCB Lambda. `tag` is the DCB entity tag. */
867
+ let invokeDcbMutation = (tag: string) =>
868
+ `${importUtil}
869
+ export function request(ctx) {
870
+ const id = ctx.identity;
871
+ const isCognito = id != null && id.sub != null;
872
+ return {
873
+ operation: 'Invoke',
874
+ payload: {
875
+ command: '${tag}',
876
+ arguments: ctx.args,
877
+ meta: {
878
+ ip: id?.sourceIp ?? null,
879
+ user: id?.username ?? null,
880
+ info: ctx.info.parentTypeName + '.' + ctx.info.fieldName
881
+ },
882
+ identity: isCognito
883
+ ? {
884
+ userId: id.sub,
885
+ username: id.username,
886
+ groups: id.claims?.['cognito:groups'] ?? [],
887
+ claims: id.claims,
888
+ provider: 'Cognito'
889
+ }
890
+ : id != null
891
+ ? {
892
+ userArn: id.userArn ?? null,
893
+ accountId: id.accountId ?? null,
894
+ username: id.username ?? null,
895
+ provider: 'IAM'
896
+ }
897
+ : null
898
+ }
899
+ };
900
+ }
901
+ ${resultResponseCode}
902
+ `->Pulumi.Input.make
903
+
904
+ /** Invoke an InboundTranslation Lambda. `fieldName` is the mutation field. */
905
+ let invokeInboundTranslation = (fieldName: string) =>
906
+ `${importUtil}
907
+ export function request(ctx) {
908
+ return {
909
+ operation: 'Invoke',
910
+ payload: {
911
+ __inboundTranslation: true,
912
+ fieldName: '${fieldName}',
913
+ arguments: ctx.args
914
+ }
915
+ };
916
+ }
917
+ ${resultResponseCode}
918
+ `->Pulumi.Input.make
919
+
920
+ // ---------------------------------------------------------------------------
921
+ // Authorization (pipeline function fragments)
922
+ // ---------------------------------------------------------------------------
923
+
924
+ let uncapitalize = str =>
925
+ switch String.get(str, 0) {
926
+ | None => ""
927
+ | Some(first) => String.concat(first->String.toLowerCase, String.slice(str, ~start=1))
928
+ }
929
+
930
+ /** Combined pipeline function: checks group membership, fetches the item, and
931
+ verifies the item's owner matches the requesting user.
932
+ Replaces the two separate VTL authorizeIndexedAccessRequest/Response templates,
933
+ which were the request and response halves of the same AppSync Function. */
934
+ let authorizeIndexedAccess = (~index: string, ~group: string) => {
935
+ let authIdName = group->uncapitalize ++ "Id"
936
+ `${importUtil}
937
+ import { runtime } from '@aws-appsync/utils';
938
+ export function request(ctx) {
939
+ const groups = ctx.identity.claims?.['cognito:groups'] ?? [];
940
+ if (groups.includes('${group}')) {
941
+ return { operation: 'GetItem', key: { id: util.dynamodb.toDynamoDB(ctx.args.${index}) } };
942
+ }
943
+ return runtime.earlyReturn({ ${authIdName}: [ctx.identity.username] });
944
+ }
945
+ export function response(ctx) {
946
+ if (ctx.error) util.error(ctx.error.message, ctx.error.type);
947
+ if (ctx.result.${authIdName} === ctx.identity.username) return ctx.result;
948
+ return util.unauthorized();
949
+ }
950
+ `->Pulumi.Input.make
951
+ }
952
+
953
+ /** @deprecated Use authorizeIndexedAccess — kept for naming continuity with VTL templates. */
954
+ let authorizeIndexedAccessRequest = authorizeIndexedAccess
955
+
956
+ /** @deprecated The response check is now part of authorizeIndexedAccess. */
957
+ let authorizeIndexedAccessResponse = (~group as _: string) =>
958
+ resultResponseCode->Pulumi.Input.make
959
+
960
+ // ---------------------------------------------------------------------------
961
+ // Response-only helpers
962
+ // (kept for adapter compatibility; these are now embedded in each resolver above)
963
+ // ---------------------------------------------------------------------------
964
+
965
+ let result = resultResponseCode->Pulumi.Input.make
966
+ let firstResult = firstResultResponseCode->Pulumi.Input.make
967
+ let resultList = resultListResponseCode->Pulumi.Input.make
968
+
969
+ let resolveIdResult = (tableName: string, ~idField: string) =>
970
+ `
971
+ export function response(ctx) {
972
+ if (ctx.error) util.error(ctx.error.message, ctx.error.type);
973
+ if (ctx.source.${idField}) return ctx.result.data['${tableName}'][0] ?? null;
974
+ return null;
975
+ }
976
+ `->Pulumi.Input.make
977
+
978
+ let resolveIdResults = (tableName: string, ~idField: string) =>
979
+ `
980
+ export function response(ctx) {
981
+ if (ctx.error) util.error(ctx.error.message, ctx.error.type);
982
+ if (ctx.source.${idField}) return ctx.result.data['${tableName}'] ?? null;
983
+ return null;
984
+ }
985
+ `
986
+
987
+ let resolveIdsResult = (tableName: string, ~idsField: string) =>
988
+ `
989
+ export function response(ctx) {
990
+ if (ctx.error) util.error(ctx.error.message, ctx.error.type);
991
+ const idList = ctx.source.${idsField};
992
+ return (idList && idList.length > 0) ? ctx.result.data['${tableName}'] : [];
993
+ }
994
+ `
995
+
996
+ let null = `
997
+ export function response(ctx) { return null; }
998
+ `->Pulumi.Input.make