@awsless/awsless 0.0.250 → 0.0.251
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/bin.js +928 -928
- package/package.json +9 -9
package/dist/bin.js
CHANGED
|
@@ -1686,7 +1686,7 @@ var bootstrap = (program2) => {
|
|
|
1686
1686
|
// src/app.ts
|
|
1687
1687
|
import { App, Stack } from "@awsless/formation";
|
|
1688
1688
|
|
|
1689
|
-
// src/feature/
|
|
1689
|
+
// src/feature/auth/index.ts
|
|
1690
1690
|
import { constantCase as constantCase2 } from "change-case";
|
|
1691
1691
|
|
|
1692
1692
|
// src/feature.ts
|
|
@@ -1819,76 +1819,11 @@ var formatLocalResourceName = (appName, stackName, ns, id, seperator = "--") =>
|
|
|
1819
1819
|
].map((v) => paramCase3(v)).join(seperator);
|
|
1820
1820
|
};
|
|
1821
1821
|
|
|
1822
|
-
// src/feature/
|
|
1823
|
-
import { Node, aws } from "@awsless/formation";
|
|
1824
|
-
var typeGenCode = `
|
|
1825
|
-
import { Cluster, CommandOptions } from '@awsless/redis'
|
|
1826
|
-
|
|
1827
|
-
type Callback<T> = (redis: Cluster) => T
|
|
1828
|
-
|
|
1829
|
-
type Command = {
|
|
1830
|
-
readonly host: string
|
|
1831
|
-
readonly port: number
|
|
1832
|
-
<T>(callback: Callback<T>): T
|
|
1833
|
-
<T>(options:Omit<CommandOptions, 'cluster'>, callback: Callback<T>): T
|
|
1834
|
-
}`;
|
|
1835
|
-
var cacheFeature = defineFeature({
|
|
1836
|
-
name: "cache",
|
|
1837
|
-
async onTypeGen(ctx) {
|
|
1838
|
-
const gen = new TypeFile("@awsless/awsless");
|
|
1839
|
-
const resources = new TypeObject(1);
|
|
1840
|
-
for (const stack of ctx.stackConfigs) {
|
|
1841
|
-
const resource2 = new TypeObject(2);
|
|
1842
|
-
for (const name of Object.keys(stack.caches || {})) {
|
|
1843
|
-
resource2.addType(name, `Command`);
|
|
1844
|
-
}
|
|
1845
|
-
resources.addType(stack.name, resource2);
|
|
1846
|
-
}
|
|
1847
|
-
gen.addCode(typeGenCode);
|
|
1848
|
-
gen.addInterface("CacheResources", resources);
|
|
1849
|
-
await ctx.write("cache.d.ts", gen, true);
|
|
1850
|
-
},
|
|
1851
|
-
onStack(ctx) {
|
|
1852
|
-
for (const [id, props] of Object.entries(ctx.stackConfig.caches ?? {})) {
|
|
1853
|
-
const group = new Node(ctx.stack, this.name, id);
|
|
1854
|
-
const name = formatLocalResourceName(ctx.appConfig.name, ctx.stack.name, this.name, id, "-");
|
|
1855
|
-
const subnetGroup = new aws.memorydb.SubnetGroup(group, "subnets", {
|
|
1856
|
-
name,
|
|
1857
|
-
subnetIds: [
|
|
1858
|
-
//
|
|
1859
|
-
ctx.shared.get("vpc-private-subnet-id-1"),
|
|
1860
|
-
ctx.shared.get("vpc-private-subnet-id-2")
|
|
1861
|
-
]
|
|
1862
|
-
});
|
|
1863
|
-
const securityGroup = new aws.ec2.SecurityGroup(group, "security", {
|
|
1864
|
-
name,
|
|
1865
|
-
vpcId: ctx.shared.get(`vpc-id`),
|
|
1866
|
-
description: name
|
|
1867
|
-
});
|
|
1868
|
-
const port = aws.ec2.Port.tcp(props.port);
|
|
1869
|
-
securityGroup.addIngressRule({ port, peer: aws.ec2.Peer.anyIpv4() });
|
|
1870
|
-
securityGroup.addIngressRule({ port, peer: aws.ec2.Peer.anyIpv6() });
|
|
1871
|
-
const cluster = new aws.memorydb.Cluster(group, "cluster", {
|
|
1872
|
-
name,
|
|
1873
|
-
aclName: "open-access",
|
|
1874
|
-
securityGroupIds: [securityGroup.id],
|
|
1875
|
-
subnetGroupName: subnetGroup.name,
|
|
1876
|
-
...props
|
|
1877
|
-
});
|
|
1878
|
-
ctx.onFunction(({ lambda }) => {
|
|
1879
|
-
const prefix = `CACHE_${constantCase2(ctx.stack.name)}_${constantCase2(id)}`;
|
|
1880
|
-
lambda.addEnvironment(`${prefix}_HOST`, cluster.address);
|
|
1881
|
-
lambda.addEnvironment(`${prefix}_PORT`, props.port.toString());
|
|
1882
|
-
});
|
|
1883
|
-
}
|
|
1884
|
-
}
|
|
1885
|
-
});
|
|
1886
|
-
|
|
1887
|
-
// src/feature/cron/index.ts
|
|
1888
|
-
import { Node as Node3, aws as aws3 } from "@awsless/formation";
|
|
1822
|
+
// src/feature/auth/index.ts
|
|
1823
|
+
import { Node as Node2, aws as aws2 } from "@awsless/formation";
|
|
1889
1824
|
|
|
1890
1825
|
// src/feature/function/util.ts
|
|
1891
|
-
import { Asset, aws
|
|
1826
|
+
import { Asset, aws } from "@awsless/formation";
|
|
1892
1827
|
import deepmerge from "deepmerge";
|
|
1893
1828
|
import { basename as basename4, dirname as dirname6, extname as extname3 } from "path";
|
|
1894
1829
|
import { exec } from "promisify-child-process";
|
|
@@ -2163,7 +2098,7 @@ var createLambdaFunction = (group, ctx, ns, id, local2) => {
|
|
|
2163
2098
|
};
|
|
2164
2099
|
});
|
|
2165
2100
|
});
|
|
2166
|
-
code = new
|
|
2101
|
+
code = new aws.s3.BucketObject(group, "code", {
|
|
2167
2102
|
bucket: ctx.shared.get("function-bucket-name"),
|
|
2168
2103
|
key: `/lambda/${name}.zip`,
|
|
2169
2104
|
body: Asset.fromFile(getBuildPath("function", name, "bundle.zip"))
|
|
@@ -2184,7 +2119,7 @@ var createLambdaFunction = (group, ctx, ns, id, local2) => {
|
|
|
2184
2119
|
);
|
|
2185
2120
|
});
|
|
2186
2121
|
});
|
|
2187
|
-
const image = new
|
|
2122
|
+
const image = new aws.ecr.Image(group, "image", {
|
|
2188
2123
|
repository: ctx.shared.get("function-repository-name"),
|
|
2189
2124
|
name,
|
|
2190
2125
|
tag: name
|
|
@@ -2195,12 +2130,12 @@ var createLambdaFunction = (group, ctx, ns, id, local2) => {
|
|
|
2195
2130
|
} else {
|
|
2196
2131
|
throw new Error("Unknown Lambda Function type.");
|
|
2197
2132
|
}
|
|
2198
|
-
const role = new
|
|
2133
|
+
const role = new aws.iam.Role(group, "role", {
|
|
2199
2134
|
name,
|
|
2200
2135
|
assumedBy: "lambda.amazonaws.com"
|
|
2201
2136
|
// policies: inlinePolicies,
|
|
2202
2137
|
});
|
|
2203
|
-
const policy = new
|
|
2138
|
+
const policy = new aws.iam.RolePolicy(group, "policy", {
|
|
2204
2139
|
role: role.name,
|
|
2205
2140
|
name: "lambda-policy",
|
|
2206
2141
|
version: "2012-10-17",
|
|
@@ -2212,7 +2147,7 @@ var createLambdaFunction = (group, ctx, ns, id, local2) => {
|
|
|
2212
2147
|
}
|
|
2213
2148
|
]
|
|
2214
2149
|
});
|
|
2215
|
-
const lambda = new
|
|
2150
|
+
const lambda = new aws.lambda.Function(group, `function`, {
|
|
2216
2151
|
...props,
|
|
2217
2152
|
name,
|
|
2218
2153
|
role: role.arn,
|
|
@@ -2227,7 +2162,7 @@ var createLambdaFunction = (group, ctx, ns, id, local2) => {
|
|
|
2227
2162
|
lambda.addEnvironment("STACK", ctx.stackConfig.name);
|
|
2228
2163
|
}
|
|
2229
2164
|
if (props.log.retention.value > 0n) {
|
|
2230
|
-
const logGroup = new
|
|
2165
|
+
const logGroup = new aws.cloudWatch.LogGroup(group, "log", {
|
|
2231
2166
|
name: lambda.name.apply((name2) => `/aws/lambda/${name2}`),
|
|
2232
2167
|
retention: props.log.retention
|
|
2233
2168
|
});
|
|
@@ -2249,7 +2184,7 @@ var createLambdaFunction = (group, ctx, ns, id, local2) => {
|
|
|
2249
2184
|
policy.addStatement(...local2.permissions);
|
|
2250
2185
|
}
|
|
2251
2186
|
if (props.warm) {
|
|
2252
|
-
const rule = new
|
|
2187
|
+
const rule = new aws.events.Rule(group, "warm", {
|
|
2253
2188
|
name: `${name}--warm`,
|
|
2254
2189
|
schedule: "rate(5 minutes)",
|
|
2255
2190
|
enabled: true,
|
|
@@ -2264,7 +2199,7 @@ var createLambdaFunction = (group, ctx, ns, id, local2) => {
|
|
|
2264
2199
|
}
|
|
2265
2200
|
]
|
|
2266
2201
|
});
|
|
2267
|
-
new
|
|
2202
|
+
new aws.lambda.Permission(group, `warm`, {
|
|
2268
2203
|
action: "lambda:InvokeFunction",
|
|
2269
2204
|
principal: "events.amazonaws.com",
|
|
2270
2205
|
functionArn: lambda.arn,
|
|
@@ -2279,7 +2214,7 @@ var createLambdaFunction = (group, ctx, ns, id, local2) => {
|
|
|
2279
2214
|
ctx.shared.get(`vpc-public-subnet-id-2`)
|
|
2280
2215
|
]
|
|
2281
2216
|
});
|
|
2282
|
-
const vpcPolicy = new
|
|
2217
|
+
const vpcPolicy = new aws.iam.RolePolicy(group, "vpc-policy", {
|
|
2283
2218
|
role: role.name,
|
|
2284
2219
|
name: "lambda-vpc-policy",
|
|
2285
2220
|
version: "2012-10-17",
|
|
@@ -2308,7 +2243,7 @@ var createAsyncLambdaFunction = (group, ctx, ns, id, local2) => {
|
|
|
2308
2243
|
const result = createLambdaFunction(group, ctx, ns, id, local2);
|
|
2309
2244
|
const props = deepmerge(ctx.appConfig.defaults.function, local2);
|
|
2310
2245
|
result.lambda.addEnvironment("LOG_VIEWABLE_ERROR", "1");
|
|
2311
|
-
const invokeConfig = new
|
|
2246
|
+
const invokeConfig = new aws.lambda.EventInvokeConfig(group, "async", {
|
|
2312
2247
|
functionArn: result.lambda.arn,
|
|
2313
2248
|
retryAttempts: props.retryAttempts,
|
|
2314
2249
|
onFailure: getGlobalOnFailure(ctx)
|
|
@@ -2323,139 +2258,180 @@ var createAsyncLambdaFunction = (group, ctx, ns, id, local2) => {
|
|
|
2323
2258
|
return result;
|
|
2324
2259
|
};
|
|
2325
2260
|
|
|
2326
|
-
// src/feature/
|
|
2327
|
-
var
|
|
2328
|
-
name: "
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
const
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
{
|
|
2339
|
-
id: "default",
|
|
2340
|
-
arn: lambda.arn,
|
|
2341
|
-
input: props.payload
|
|
2342
|
-
}
|
|
2343
|
-
]
|
|
2344
|
-
});
|
|
2345
|
-
new aws3.lambda.Permission(group, "permission", {
|
|
2346
|
-
action: "lambda:InvokeFunction",
|
|
2347
|
-
principal: "events.amazonaws.com",
|
|
2348
|
-
functionArn: lambda.arn,
|
|
2349
|
-
sourceArn: rule.arn
|
|
2350
|
-
});
|
|
2351
|
-
}
|
|
2352
|
-
}
|
|
2353
|
-
});
|
|
2354
|
-
|
|
2355
|
-
// src/feature/domain/index.ts
|
|
2356
|
-
import { Node as Node4, aws as aws4 } from "@awsless/formation";
|
|
2357
|
-
import { minutes as minutes3 } from "@awsless/duration";
|
|
2358
|
-
var domainFeature = defineFeature({
|
|
2359
|
-
name: "domain",
|
|
2360
|
-
onApp(ctx) {
|
|
2361
|
-
const domains = Object.entries(ctx.appConfig.defaults.domains ?? {});
|
|
2362
|
-
if (domains.length === 0) {
|
|
2363
|
-
return;
|
|
2261
|
+
// src/feature/auth/index.ts
|
|
2262
|
+
var authFeature = defineFeature({
|
|
2263
|
+
name: "auth",
|
|
2264
|
+
async onTypeGen(ctx) {
|
|
2265
|
+
const gen = new TypeFile("@awsless/awsless");
|
|
2266
|
+
const resources = new TypeObject(1);
|
|
2267
|
+
for (const name of Object.keys(ctx.appConfig.defaults.auth)) {
|
|
2268
|
+
const authName = formatGlobalResourceName(ctx.appConfig.name, "auth", name);
|
|
2269
|
+
resources.addType(
|
|
2270
|
+
name,
|
|
2271
|
+
`{ readonly name: '${authName}', readonly userPoolId: string, readonly clientId: string }`
|
|
2272
|
+
);
|
|
2364
2273
|
}
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
const
|
|
2374
|
-
const
|
|
2375
|
-
|
|
2376
|
-
})
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2274
|
+
gen.addInterface("AuthResources", resources);
|
|
2275
|
+
await ctx.write("auth.d.ts", gen, true);
|
|
2276
|
+
},
|
|
2277
|
+
onStack(ctx) {
|
|
2278
|
+
for (const [id, props] of Object.entries(ctx.stackConfig.auth ?? {})) {
|
|
2279
|
+
const group = new Node2(ctx.stack, "auth", id);
|
|
2280
|
+
const userPoolId = ctx.shared.get(`auth-${id}-user-pool-id`);
|
|
2281
|
+
const userPoolArn = ctx.shared.get(`auth-${id}-user-pool-arn`);
|
|
2282
|
+
const clientId = ctx.shared.get(`auth-${id}-client-id`);
|
|
2283
|
+
const triggers = {};
|
|
2284
|
+
const list4 = {};
|
|
2285
|
+
for (const [trigger, triggerProps] of Object.entries(props.triggers ?? {})) {
|
|
2286
|
+
const triggerGroup = new Node2(group, "trigger", trigger);
|
|
2287
|
+
const { lambda, policy } = createAsyncLambdaFunction(
|
|
2288
|
+
triggerGroup,
|
|
2289
|
+
ctx,
|
|
2290
|
+
"auth",
|
|
2291
|
+
`${id}-${trigger}`,
|
|
2292
|
+
triggerProps
|
|
2293
|
+
);
|
|
2294
|
+
triggers[trigger] = lambda.arn;
|
|
2295
|
+
list4[trigger] = {
|
|
2296
|
+
trigger,
|
|
2297
|
+
group: triggerGroup,
|
|
2298
|
+
lambda,
|
|
2299
|
+
policy
|
|
2300
|
+
};
|
|
2301
|
+
}
|
|
2302
|
+
new aws2.cognito.LambdaTriggers(group, "lambda-triggers", {
|
|
2303
|
+
userPoolId,
|
|
2304
|
+
triggers
|
|
2386
2305
|
});
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2306
|
+
for (const item of Object.values(list4)) {
|
|
2307
|
+
new aws2.lambda.Permission(item.group, `permission`, {
|
|
2308
|
+
action: "lambda:InvokeFunction",
|
|
2309
|
+
principal: "cognito-idp.amazonaws.com",
|
|
2310
|
+
functionArn: item.lambda.arn,
|
|
2311
|
+
sourceArn: userPoolArn
|
|
2393
2312
|
});
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2313
|
+
item.lambda.addEnvironment(`AUTH_${constantCase2(id)}_USER_POOL_ID`, userPoolId);
|
|
2314
|
+
item.lambda.addEnvironment(`AUTH_${constantCase2(id)}_CLIENT_ID`, clientId);
|
|
2315
|
+
item.policy.addStatement({
|
|
2316
|
+
actions: ["cognito:*"],
|
|
2317
|
+
resources: [
|
|
2318
|
+
// Not yet known if this is correct way to grant access to all resources
|
|
2319
|
+
userPoolArn
|
|
2320
|
+
// userPoolId.apply<aws.ARN>(
|
|
2321
|
+
// id => `arn:aws:cognito-idp:${ctx.appConfig.region}:${ctx.accountId}:userpool/${id}`
|
|
2322
|
+
// ),
|
|
2323
|
+
// userPoolId.apply<aws.ARN>(
|
|
2324
|
+
// id => `arn:aws:cognito-idp:${ctx.appConfig.region}:${ctx.accountId}:userpool/${id}*`
|
|
2325
|
+
// ),
|
|
2326
|
+
]
|
|
2399
2327
|
});
|
|
2400
|
-
ctx.shared.set(`global-certificate-${id}-arn`, globalValidation.arn);
|
|
2401
|
-
} else {
|
|
2402
|
-
ctx.shared.set(`global-certificate-${id}-arn`, validation.arn);
|
|
2403
2328
|
}
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2329
|
+
}
|
|
2330
|
+
},
|
|
2331
|
+
onApp(ctx) {
|
|
2332
|
+
for (const [id, props] of Object.entries(ctx.appConfig.defaults.auth ?? {})) {
|
|
2333
|
+
const group = new Node2(ctx.base, "auth", id);
|
|
2334
|
+
let emailConfig;
|
|
2335
|
+
if (props.messaging) {
|
|
2336
|
+
const [_, domainName] = props.messaging.fromEmail.split("@");
|
|
2337
|
+
emailConfig = {
|
|
2338
|
+
type: "developer",
|
|
2339
|
+
replyTo: props.messaging.replyTo,
|
|
2340
|
+
sourceArn: ctx.shared.get(`mail-${domainName}-arn`),
|
|
2341
|
+
configurationSet: ctx.shared.get("mail-configuration-set"),
|
|
2342
|
+
from: props.messaging.fromName ? `${props.messaging.fromName} <${props.messaging.fromEmail}>` : props.messaging.fromEmail
|
|
2343
|
+
};
|
|
2417
2344
|
}
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
name
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
hostedZoneId: hostedZone.id,
|
|
2427
|
-
name: `mail.${props.domain}`,
|
|
2428
|
-
type: "TXT",
|
|
2429
|
-
ttl: minutes3(5),
|
|
2430
|
-
records: ['"v=spf1 include:amazonses.com -all"']
|
|
2431
|
-
});
|
|
2432
|
-
new aws4.route53.RecordSet(group2, `DMARC`, {
|
|
2433
|
-
hostedZoneId: hostedZone.id,
|
|
2434
|
-
name: `_dmarc.${props.domain}`,
|
|
2435
|
-
type: "TXT",
|
|
2436
|
-
ttl: minutes3(5),
|
|
2437
|
-
records: ['"v=DMARC1; p=none;"']
|
|
2345
|
+
const name = formatGlobalResourceName(ctx.appConfig.name, "auth", id);
|
|
2346
|
+
const userPool = new aws2.cognito.UserPool(group, "user-pool", {
|
|
2347
|
+
name,
|
|
2348
|
+
// deletionProtection: true,
|
|
2349
|
+
allowUserRegistration: props.allowUserRegistration,
|
|
2350
|
+
username: props.username,
|
|
2351
|
+
password: props.password,
|
|
2352
|
+
email: emailConfig
|
|
2438
2353
|
});
|
|
2439
|
-
const
|
|
2440
|
-
|
|
2354
|
+
const client = new aws2.cognito.UserPoolClient(group, "client", {
|
|
2355
|
+
userPoolId: userPool.id,
|
|
2356
|
+
name,
|
|
2357
|
+
validity: props.validity,
|
|
2358
|
+
supportedIdentityProviders: ["cognito"],
|
|
2359
|
+
authFlows: {
|
|
2360
|
+
userSrp: true
|
|
2361
|
+
}
|
|
2362
|
+
});
|
|
2363
|
+
ctx.bindEnv(`AUTH_${constantCase2(id)}_USER_POOL_ID`, userPool.id);
|
|
2364
|
+
ctx.bindEnv(`AUTH_${constantCase2(id)}_CLIENT_ID`, client.id);
|
|
2365
|
+
ctx.shared.set(`auth-${id}-user-pool-arn`, userPool.arn);
|
|
2366
|
+
ctx.shared.set(`auth-${id}-user-pool-id`, userPool.id);
|
|
2367
|
+
ctx.shared.set(`auth-${id}-client-id`, client.id);
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
});
|
|
2371
|
+
|
|
2372
|
+
// src/feature/cache/index.ts
|
|
2373
|
+
import { constantCase as constantCase3 } from "change-case";
|
|
2374
|
+
import { Node as Node3, aws as aws3 } from "@awsless/formation";
|
|
2375
|
+
var typeGenCode = `
|
|
2376
|
+
import { Cluster, CommandOptions } from '@awsless/redis'
|
|
2377
|
+
|
|
2378
|
+
type Callback<T> = (redis: Cluster) => T
|
|
2379
|
+
|
|
2380
|
+
type Command = {
|
|
2381
|
+
readonly host: string
|
|
2382
|
+
readonly port: number
|
|
2383
|
+
<T>(callback: Callback<T>): T
|
|
2384
|
+
<T>(options:Omit<CommandOptions, 'cluster'>, callback: Callback<T>): T
|
|
2385
|
+
}`;
|
|
2386
|
+
var cacheFeature = defineFeature({
|
|
2387
|
+
name: "cache",
|
|
2388
|
+
async onTypeGen(ctx) {
|
|
2389
|
+
const gen = new TypeFile("@awsless/awsless");
|
|
2390
|
+
const resources = new TypeObject(1);
|
|
2391
|
+
for (const stack of ctx.stackConfigs) {
|
|
2392
|
+
const resource2 = new TypeObject(2);
|
|
2393
|
+
for (const name of Object.keys(stack.caches || {})) {
|
|
2394
|
+
resource2.addType(name, `Command`);
|
|
2395
|
+
}
|
|
2396
|
+
resources.addType(stack.name, resource2);
|
|
2397
|
+
}
|
|
2398
|
+
gen.addCode(typeGenCode);
|
|
2399
|
+
gen.addInterface("CacheResources", resources);
|
|
2400
|
+
await ctx.write("cache.d.ts", gen, true);
|
|
2401
|
+
},
|
|
2402
|
+
onStack(ctx) {
|
|
2403
|
+
for (const [id, props] of Object.entries(ctx.stackConfig.caches ?? {})) {
|
|
2404
|
+
const group = new Node3(ctx.stack, this.name, id);
|
|
2405
|
+
const name = formatLocalResourceName(ctx.appConfig.name, ctx.stack.name, this.name, id, "-");
|
|
2406
|
+
const subnetGroup = new aws3.memorydb.SubnetGroup(group, "subnets", {
|
|
2407
|
+
name,
|
|
2408
|
+
subnetIds: [
|
|
2409
|
+
//
|
|
2410
|
+
ctx.shared.get("vpc-private-subnet-id-1"),
|
|
2411
|
+
ctx.shared.get("vpc-private-subnet-id-2")
|
|
2412
|
+
]
|
|
2413
|
+
});
|
|
2414
|
+
const securityGroup = new aws3.ec2.SecurityGroup(group, "security", {
|
|
2415
|
+
name,
|
|
2416
|
+
vpcId: ctx.shared.get(`vpc-id`),
|
|
2417
|
+
description: name
|
|
2418
|
+
});
|
|
2419
|
+
const port = aws3.ec2.Port.tcp(props.port);
|
|
2420
|
+
securityGroup.addIngressRule({ port, peer: aws3.ec2.Peer.anyIpv4() });
|
|
2421
|
+
securityGroup.addIngressRule({ port, peer: aws3.ec2.Peer.anyIpv6() });
|
|
2422
|
+
const cluster = new aws3.memorydb.Cluster(group, "cluster", {
|
|
2423
|
+
name,
|
|
2424
|
+
aclName: "open-access",
|
|
2425
|
+
securityGroupIds: [securityGroup.id],
|
|
2426
|
+
subnetGroupName: subnetGroup.name,
|
|
2427
|
+
...props
|
|
2428
|
+
});
|
|
2429
|
+
ctx.onFunction(({ lambda }) => {
|
|
2430
|
+
const prefix = `CACHE_${constantCase3(ctx.stack.name)}_${constantCase3(id)}`;
|
|
2431
|
+
lambda.addEnvironment(`${prefix}_HOST`, cluster.address);
|
|
2432
|
+
lambda.addEnvironment(`${prefix}_PORT`, props.port.toString());
|
|
2441
2433
|
});
|
|
2442
|
-
ctx.shared.set(`mail-${id}-arn`, mailIdentityArn);
|
|
2443
|
-
ctx.shared.set(`mail-${props.domain}-arn`, mailIdentityArn);
|
|
2444
|
-
for (const record of props.dns ?? []) {
|
|
2445
|
-
const name = record.name ?? props.domain;
|
|
2446
|
-
new aws4.route53.RecordSet(group2, `${name}-${record.type}`, {
|
|
2447
|
-
hostedZoneId: hostedZone.id,
|
|
2448
|
-
name,
|
|
2449
|
-
...record
|
|
2450
|
-
});
|
|
2451
|
-
}
|
|
2452
2434
|
}
|
|
2453
|
-
ctx.onFunction(
|
|
2454
|
-
({ policy }) => policy.addStatement({
|
|
2455
|
-
actions: ["ses:*"],
|
|
2456
|
-
resources: [`arn:aws:ses:${ctx.appConfig.region}:${ctx.accountId}:identity/*`]
|
|
2457
|
-
})
|
|
2458
|
-
);
|
|
2459
2435
|
}
|
|
2460
2436
|
});
|
|
2461
2437
|
|
|
@@ -2601,8 +2577,145 @@ var configFeature = defineFeature({
|
|
|
2601
2577
|
}
|
|
2602
2578
|
});
|
|
2603
2579
|
|
|
2580
|
+
// src/feature/cron/index.ts
|
|
2581
|
+
import { Node as Node4, aws as aws4 } from "@awsless/formation";
|
|
2582
|
+
var cronFeature = defineFeature({
|
|
2583
|
+
name: "cron",
|
|
2584
|
+
onStack(ctx) {
|
|
2585
|
+
for (const [id, props] of Object.entries(ctx.stackConfig.crons ?? {})) {
|
|
2586
|
+
const group = new Node4(ctx.stack, "cron", id);
|
|
2587
|
+
const { lambda } = createAsyncLambdaFunction(group, ctx, "cron", id, props.consumer);
|
|
2588
|
+
const rule = new aws4.events.Rule(group, "rule", {
|
|
2589
|
+
name: formatLocalResourceName(ctx.app.name, ctx.stack.name, this.name, id),
|
|
2590
|
+
schedule: props.schedule,
|
|
2591
|
+
enabled: props.enabled,
|
|
2592
|
+
targets: [
|
|
2593
|
+
{
|
|
2594
|
+
id: "default",
|
|
2595
|
+
arn: lambda.arn,
|
|
2596
|
+
input: props.payload
|
|
2597
|
+
}
|
|
2598
|
+
]
|
|
2599
|
+
});
|
|
2600
|
+
new aws4.lambda.Permission(group, "permission", {
|
|
2601
|
+
action: "lambda:InvokeFunction",
|
|
2602
|
+
principal: "events.amazonaws.com",
|
|
2603
|
+
functionArn: lambda.arn,
|
|
2604
|
+
sourceArn: rule.arn
|
|
2605
|
+
});
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2608
|
+
});
|
|
2609
|
+
|
|
2610
|
+
// src/feature/domain/index.ts
|
|
2611
|
+
import { Node as Node5, aws as aws5 } from "@awsless/formation";
|
|
2612
|
+
import { minutes as minutes3 } from "@awsless/duration";
|
|
2613
|
+
var domainFeature = defineFeature({
|
|
2614
|
+
name: "domain",
|
|
2615
|
+
onApp(ctx) {
|
|
2616
|
+
const domains = Object.entries(ctx.appConfig.defaults.domains ?? {});
|
|
2617
|
+
if (domains.length === 0) {
|
|
2618
|
+
return;
|
|
2619
|
+
}
|
|
2620
|
+
const group = new Node5(ctx.base, "domain", "mail");
|
|
2621
|
+
const configurationSet = new aws5.ses.ConfigurationSet(group, "config", {
|
|
2622
|
+
name: ctx.app.name,
|
|
2623
|
+
engagementMetrics: true,
|
|
2624
|
+
reputationMetrics: true
|
|
2625
|
+
});
|
|
2626
|
+
ctx.shared.set(`mail-configuration-set`, configurationSet.name);
|
|
2627
|
+
for (const [id, props] of domains) {
|
|
2628
|
+
const group2 = new Node5(ctx.base, "domain", id);
|
|
2629
|
+
const hostedZone = new aws5.route53.HostedZone(group2, "zone", {
|
|
2630
|
+
name: props.domain
|
|
2631
|
+
});
|
|
2632
|
+
ctx.shared.set(`hosted-zone-${id}-id`, hostedZone.id);
|
|
2633
|
+
const certificate = new aws5.acm.Certificate(group2, "local", {
|
|
2634
|
+
domainName: props.domain,
|
|
2635
|
+
alternativeNames: [`*.${props.domain}`]
|
|
2636
|
+
});
|
|
2637
|
+
hostedZone.addRecord("local-cert-1", certificate.validationRecord(0));
|
|
2638
|
+
hostedZone.addRecord("local-cert-2", certificate.validationRecord(1));
|
|
2639
|
+
const validation = new aws5.acm.CertificateValidation(group2, "local", {
|
|
2640
|
+
certificateArn: certificate.arn
|
|
2641
|
+
});
|
|
2642
|
+
ctx.shared.set(`local-certificate-${id}-arn`, validation.arn);
|
|
2643
|
+
if (ctx.appConfig.region !== "us-east-1") {
|
|
2644
|
+
const globalCertificate = new aws5.acm.Certificate(group2, "global", {
|
|
2645
|
+
domainName: props.domain,
|
|
2646
|
+
alternativeNames: [`*.${props.domain}`],
|
|
2647
|
+
region: "us-east-1"
|
|
2648
|
+
});
|
|
2649
|
+
hostedZone.addRecord("global-cert-1", globalCertificate.validationRecord(0));
|
|
2650
|
+
hostedZone.addRecord("global-cert-2", globalCertificate.validationRecord(1));
|
|
2651
|
+
const globalValidation = new aws5.acm.CertificateValidation(group2, "global", {
|
|
2652
|
+
certificateArn: globalCertificate.arn,
|
|
2653
|
+
region: "us-east-1"
|
|
2654
|
+
});
|
|
2655
|
+
ctx.shared.set(`global-certificate-${id}-arn`, globalValidation.arn);
|
|
2656
|
+
} else {
|
|
2657
|
+
ctx.shared.set(`global-certificate-${id}-arn`, validation.arn);
|
|
2658
|
+
}
|
|
2659
|
+
const emailIdentity = new aws5.ses.EmailIdentity(group2, "mail", {
|
|
2660
|
+
emailIdentity: props.domain,
|
|
2661
|
+
mailFromDomain: `mail.${props.domain}`,
|
|
2662
|
+
configurationSetName: configurationSet.name,
|
|
2663
|
+
feedback: true,
|
|
2664
|
+
rejectOnMxFailure: true
|
|
2665
|
+
});
|
|
2666
|
+
let i = 0;
|
|
2667
|
+
for (const record of emailIdentity.dkimRecords) {
|
|
2668
|
+
new aws5.route53.RecordSet(group2, `dkim-${++i}`, {
|
|
2669
|
+
hostedZoneId: hostedZone.id,
|
|
2670
|
+
...record
|
|
2671
|
+
});
|
|
2672
|
+
}
|
|
2673
|
+
new aws5.route53.RecordSet(group2, `MX`, {
|
|
2674
|
+
hostedZoneId: hostedZone.id,
|
|
2675
|
+
name: `mail.${props.domain}`,
|
|
2676
|
+
type: "MX",
|
|
2677
|
+
ttl: minutes3(5),
|
|
2678
|
+
records: [`10 feedback-smtp.${ctx.appConfig.region}.amazonses.com`]
|
|
2679
|
+
});
|
|
2680
|
+
new aws5.route53.RecordSet(group2, `SPF`, {
|
|
2681
|
+
hostedZoneId: hostedZone.id,
|
|
2682
|
+
name: `mail.${props.domain}`,
|
|
2683
|
+
type: "TXT",
|
|
2684
|
+
ttl: minutes3(5),
|
|
2685
|
+
records: ['"v=spf1 include:amazonses.com -all"']
|
|
2686
|
+
});
|
|
2687
|
+
new aws5.route53.RecordSet(group2, `DMARC`, {
|
|
2688
|
+
hostedZoneId: hostedZone.id,
|
|
2689
|
+
name: `_dmarc.${props.domain}`,
|
|
2690
|
+
type: "TXT",
|
|
2691
|
+
ttl: minutes3(5),
|
|
2692
|
+
records: ['"v=DMARC1; p=none;"']
|
|
2693
|
+
});
|
|
2694
|
+
const mailIdentityArn = emailIdentity.output(() => {
|
|
2695
|
+
return `arn:aws:ses:${ctx.appConfig.region}:${ctx.accountId}:identity/${props.domain}`;
|
|
2696
|
+
});
|
|
2697
|
+
ctx.shared.set(`mail-${id}-arn`, mailIdentityArn);
|
|
2698
|
+
ctx.shared.set(`mail-${props.domain}-arn`, mailIdentityArn);
|
|
2699
|
+
for (const record of props.dns ?? []) {
|
|
2700
|
+
const name = record.name ?? props.domain;
|
|
2701
|
+
new aws5.route53.RecordSet(group2, `${name}-${record.type}`, {
|
|
2702
|
+
hostedZoneId: hostedZone.id,
|
|
2703
|
+
name,
|
|
2704
|
+
...record
|
|
2705
|
+
});
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
ctx.onFunction(
|
|
2709
|
+
({ policy }) => policy.addStatement({
|
|
2710
|
+
actions: ["ses:*"],
|
|
2711
|
+
resources: [`arn:aws:ses:${ctx.appConfig.region}:${ctx.accountId}:identity/*`]
|
|
2712
|
+
})
|
|
2713
|
+
);
|
|
2714
|
+
}
|
|
2715
|
+
});
|
|
2716
|
+
|
|
2604
2717
|
// src/feature/function/index.ts
|
|
2605
|
-
import { aws as
|
|
2718
|
+
import { aws as aws6, Node as Node6 } from "@awsless/formation";
|
|
2606
2719
|
import { camelCase as camelCase3 } from "change-case";
|
|
2607
2720
|
import { relative } from "path";
|
|
2608
2721
|
var typeGenCode2 = `
|
|
@@ -2655,14 +2768,14 @@ var functionFeature = defineFeature({
|
|
|
2655
2768
|
await ctx.write("function.d.ts", types2, true);
|
|
2656
2769
|
},
|
|
2657
2770
|
onApp(ctx) {
|
|
2658
|
-
const group = new
|
|
2659
|
-
const bucket = new
|
|
2771
|
+
const group = new Node6(ctx.base, "function", "asset");
|
|
2772
|
+
const bucket = new aws6.s3.Bucket(group, "bucket", {
|
|
2660
2773
|
name: formatGlobalResourceName(ctx.appConfig.name, "function", "assets"),
|
|
2661
2774
|
versioning: true,
|
|
2662
2775
|
forceDelete: true
|
|
2663
2776
|
});
|
|
2664
2777
|
ctx.shared.set("function-bucket-name", bucket.name);
|
|
2665
|
-
const repository = new
|
|
2778
|
+
const repository = new aws6.ecr.Repository(group, "repository", {
|
|
2666
2779
|
name: formatGlobalResourceName(ctx.appConfig.name, "function", "repository", "-")
|
|
2667
2780
|
});
|
|
2668
2781
|
ctx.shared.set("function-repository-name", repository.name);
|
|
@@ -2670,19 +2783,19 @@ var functionFeature = defineFeature({
|
|
|
2670
2783
|
},
|
|
2671
2784
|
onStack(ctx) {
|
|
2672
2785
|
for (const [id, props] of Object.entries(ctx.stackConfig.functions || {})) {
|
|
2673
|
-
const group = new
|
|
2786
|
+
const group = new Node6(ctx.stack, "function", id);
|
|
2674
2787
|
createLambdaFunction(group, ctx, "function", id, props);
|
|
2675
2788
|
}
|
|
2676
2789
|
}
|
|
2677
2790
|
});
|
|
2678
2791
|
|
|
2679
2792
|
// src/feature/graphql/index.ts
|
|
2680
|
-
import { constantCase as
|
|
2793
|
+
import { constantCase as constantCase4, paramCase as paramCase5 } from "change-case";
|
|
2681
2794
|
import { mergeTypeDefs } from "@graphql-tools/merge";
|
|
2682
2795
|
import { generate } from "@awsless/graphql";
|
|
2683
2796
|
import { buildSchema, print } from "graphql";
|
|
2684
2797
|
import { readFile as readFile5 } from "fs/promises";
|
|
2685
|
-
import { Asset as Asset2, Node as
|
|
2798
|
+
import { Asset as Asset2, Node as Node7, aws as aws7 } from "@awsless/formation";
|
|
2686
2799
|
|
|
2687
2800
|
// src/feature/domain/util.ts
|
|
2688
2801
|
var getDomainNameById = (config2, id) => {
|
|
@@ -2922,14 +3035,14 @@ var graphqlFeature = defineFeature({
|
|
|
2922
3035
|
},
|
|
2923
3036
|
onApp(ctx) {
|
|
2924
3037
|
for (const [id, props] of Object.entries(ctx.appConfig.defaults.graphql ?? {})) {
|
|
2925
|
-
const group = new
|
|
3038
|
+
const group = new Node7(ctx.base, "graphql", id);
|
|
2926
3039
|
let authorizer;
|
|
2927
3040
|
if (typeof props.auth === "object") {
|
|
2928
3041
|
const { lambda } = createLambdaFunction(group, ctx, "graphql-auth", id, props.auth.authorizer);
|
|
2929
3042
|
authorizer = lambda;
|
|
2930
3043
|
}
|
|
2931
3044
|
const name = formatGlobalResourceName(ctx.app.name, "graphql", id);
|
|
2932
|
-
const api = new
|
|
3045
|
+
const api = new aws7.appsync.GraphQLApi(group, "api", {
|
|
2933
3046
|
name,
|
|
2934
3047
|
type: "graphql",
|
|
2935
3048
|
auth: {
|
|
@@ -2947,7 +3060,7 @@ var graphqlFeature = defineFeature({
|
|
|
2947
3060
|
}
|
|
2948
3061
|
});
|
|
2949
3062
|
if (typeof props.auth === "object") {
|
|
2950
|
-
new
|
|
3063
|
+
new aws7.lambda.Permission(group, "authorizer", {
|
|
2951
3064
|
functionArn: authorizer.arn,
|
|
2952
3065
|
principal: "appsync.amazonaws.com",
|
|
2953
3066
|
action: "lambda:InvokeFunction"
|
|
@@ -2976,7 +3089,7 @@ var graphqlFeature = defineFeature({
|
|
|
2976
3089
|
};
|
|
2977
3090
|
});
|
|
2978
3091
|
});
|
|
2979
|
-
new
|
|
3092
|
+
new aws7.appsync.GraphQLSchema(group, "schema", {
|
|
2980
3093
|
apiId: api.id,
|
|
2981
3094
|
definition: Asset2.fromFile(getBuildPath("graphql-schema", name, "schema.gql"))
|
|
2982
3095
|
});
|
|
@@ -2998,16 +3111,16 @@ var graphqlFeature = defineFeature({
|
|
|
2998
3111
|
}
|
|
2999
3112
|
if (props.domain) {
|
|
3000
3113
|
const domainName = formatFullDomainName(ctx.appConfig, props.domain, props.subDomain);
|
|
3001
|
-
const domainGroup = new
|
|
3002
|
-
const domain = new
|
|
3114
|
+
const domainGroup = new Node7(group, "domain", domainName);
|
|
3115
|
+
const domain = new aws7.appsync.DomainName(domainGroup, "domain", {
|
|
3003
3116
|
domainName,
|
|
3004
3117
|
certificateArn: ctx.shared.get(`global-certificate-${props.domain}-arn`)
|
|
3005
3118
|
});
|
|
3006
|
-
new
|
|
3119
|
+
new aws7.appsync.DomainNameApiAssociation(domainGroup, "association", {
|
|
3007
3120
|
apiId: api.id,
|
|
3008
3121
|
domainName: domain.domainName
|
|
3009
3122
|
});
|
|
3010
|
-
new
|
|
3123
|
+
new aws7.route53.RecordSet(domainGroup, "record", {
|
|
3011
3124
|
hostedZoneId: ctx.shared.get(`hosted-zone-${props.domain}-id`),
|
|
3012
3125
|
type: "A",
|
|
3013
3126
|
name: domainName,
|
|
@@ -3017,7 +3130,7 @@ var graphqlFeature = defineFeature({
|
|
|
3017
3130
|
evaluateTargetHealth: false
|
|
3018
3131
|
}
|
|
3019
3132
|
});
|
|
3020
|
-
ctx.bindEnv(`GRAPHQL_${
|
|
3133
|
+
ctx.bindEnv(`GRAPHQL_${constantCase4(id)}_ENDPOINT`, domainName);
|
|
3021
3134
|
}
|
|
3022
3135
|
}
|
|
3023
3136
|
},
|
|
@@ -3030,18 +3143,18 @@ var graphqlFeature = defineFeature({
|
|
|
3030
3143
|
`GraphQL definition is not defined on app level for "${id}"`
|
|
3031
3144
|
);
|
|
3032
3145
|
}
|
|
3033
|
-
const group = new
|
|
3146
|
+
const group = new Node7(ctx.stack, "graphql", id);
|
|
3034
3147
|
const apiId = ctx.shared.get(`graphql-${id}-id`);
|
|
3035
3148
|
for (const [typeName, fields] of Object.entries(props.resolvers ?? {})) {
|
|
3036
3149
|
for (const [fieldName, props2] of Object.entries(fields ?? {})) {
|
|
3037
3150
|
const name = `${typeName}__${fieldName}`;
|
|
3038
|
-
const resolverGroup = new
|
|
3151
|
+
const resolverGroup = new Node7(group, "resolver", name);
|
|
3039
3152
|
const entryId = paramCase5(`${id}-${shortId(`${typeName}-${fieldName}`)}`);
|
|
3040
3153
|
const { lambda } = createLambdaFunction(resolverGroup, ctx, `graphql`, entryId, {
|
|
3041
3154
|
...props2.consumer,
|
|
3042
3155
|
description: `${id} ${typeName}.${fieldName}`
|
|
3043
3156
|
});
|
|
3044
|
-
const role = new
|
|
3157
|
+
const role = new aws7.iam.Role(resolverGroup, "source-role", {
|
|
3045
3158
|
assumedBy: "appsync.amazonaws.com",
|
|
3046
3159
|
policies: [
|
|
3047
3160
|
{
|
|
@@ -3055,7 +3168,7 @@ var graphqlFeature = defineFeature({
|
|
|
3055
3168
|
}
|
|
3056
3169
|
]
|
|
3057
3170
|
});
|
|
3058
|
-
const source = new
|
|
3171
|
+
const source = new aws7.appsync.DataSource(resolverGroup, "source", {
|
|
3059
3172
|
apiId,
|
|
3060
3173
|
name,
|
|
3061
3174
|
role: role.arn,
|
|
@@ -3068,13 +3181,13 @@ var graphqlFeature = defineFeature({
|
|
|
3068
3181
|
} else if (defaultProps.resolver) {
|
|
3069
3182
|
code = Asset2.fromFile(getBuildPath("graphql-resolver", id, "resolver.js"));
|
|
3070
3183
|
}
|
|
3071
|
-
const config2 = new
|
|
3184
|
+
const config2 = new aws7.appsync.FunctionConfiguration(resolverGroup, "config", {
|
|
3072
3185
|
apiId,
|
|
3073
3186
|
name,
|
|
3074
3187
|
code,
|
|
3075
3188
|
dataSourceName: source.name
|
|
3076
3189
|
});
|
|
3077
|
-
new
|
|
3190
|
+
new aws7.appsync.Resolver(resolverGroup, "resolver", {
|
|
3078
3191
|
apiId,
|
|
3079
3192
|
typeName,
|
|
3080
3193
|
fieldName,
|
|
@@ -3087,32 +3200,200 @@ var graphqlFeature = defineFeature({
|
|
|
3087
3200
|
}
|
|
3088
3201
|
});
|
|
3089
3202
|
|
|
3090
|
-
// src/feature/
|
|
3091
|
-
import { Node as
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3203
|
+
// src/feature/http/index.ts
|
|
3204
|
+
import { Node as Node8, aws as aws8 } from "@awsless/formation";
|
|
3205
|
+
import { camelCase as camelCase4, constantCase as constantCase5 } from "change-case";
|
|
3206
|
+
import { relative as relative2 } from "path";
|
|
3207
|
+
var parseRoute = (route) => {
|
|
3208
|
+
const [method, ...paths] = route.split(" ");
|
|
3209
|
+
const path = paths.join(" ");
|
|
3210
|
+
return { method, path };
|
|
3211
|
+
};
|
|
3212
|
+
var strToInt = (str) => {
|
|
3213
|
+
return parseInt(Buffer.from(str, "utf8").toString("hex"), 16);
|
|
3214
|
+
};
|
|
3215
|
+
var generatePriority = (stackName, route) => {
|
|
3216
|
+
const start = strToInt(stackName) % 500 + 1;
|
|
3217
|
+
const end = strToInt(route) % 100;
|
|
3218
|
+
const priority = start + "" + end;
|
|
3219
|
+
return parseInt(priority, 10);
|
|
3220
|
+
};
|
|
3221
|
+
var httpFeature = defineFeature({
|
|
3222
|
+
name: "http",
|
|
3223
|
+
async onTypeGen(ctx) {
|
|
3224
|
+
const types2 = new TypeFile("@awsless/awsless");
|
|
3225
|
+
const resources = new TypeObject(1);
|
|
3226
|
+
const api = {};
|
|
3227
|
+
for (const stack of ctx.stackConfigs) {
|
|
3228
|
+
for (const [id, routes] of Object.entries(stack.http ?? {})) {
|
|
3229
|
+
if (!(id in api))
|
|
3230
|
+
api[id] = {};
|
|
3231
|
+
for (const [route, props] of Object.entries(routes)) {
|
|
3232
|
+
const { path, method } = parseRoute(route);
|
|
3233
|
+
const file = typeof props === "string" ? props : props.file;
|
|
3234
|
+
if (!(method in api[id])) {
|
|
3235
|
+
api[id][method] = {};
|
|
3236
|
+
}
|
|
3237
|
+
api[id][method][path] = file;
|
|
3238
|
+
}
|
|
3239
|
+
}
|
|
3097
3240
|
}
|
|
3098
|
-
const
|
|
3099
|
-
|
|
3100
|
-
|
|
3241
|
+
for (const [id, routes] of Object.entries(api)) {
|
|
3242
|
+
const idType = new TypeObject(2);
|
|
3243
|
+
for (const [method, paths] of Object.entries(routes)) {
|
|
3244
|
+
const methodType = new TypeObject(3);
|
|
3245
|
+
for (const [path, file] of Object.entries(paths)) {
|
|
3246
|
+
const paramType = new TypeObject(4);
|
|
3247
|
+
for (const param of path.matchAll(/{([a-z0-9]+)}/g)) {
|
|
3248
|
+
paramType.addType(param[0], "string | number");
|
|
3249
|
+
}
|
|
3250
|
+
const varName = camelCase4(`${id}-${path}-${method}`);
|
|
3251
|
+
const relFile = relative2(directories.types, file);
|
|
3252
|
+
types2.addImport(varName, relFile);
|
|
3253
|
+
methodType.add(`'${path}'`, `Route<typeof ${varName}, ${paramType.toString() || "never"}>`);
|
|
3254
|
+
}
|
|
3255
|
+
idType.addConst(method, methodType);
|
|
3256
|
+
}
|
|
3257
|
+
resources.addType(id, idType);
|
|
3101
3258
|
}
|
|
3102
|
-
const
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3259
|
+
const code = [
|
|
3260
|
+
`import { InvokeResponse } from '@awsless/lambda'`,
|
|
3261
|
+
`type Function = (...args: any) => any`,
|
|
3262
|
+
`type Event<F extends Function> = Parameters<F>[0]`,
|
|
3263
|
+
`type RequestWithQuery = { request: { queryStringParameters: any } }`,
|
|
3264
|
+
`type RequestWithBody = { request: { body: any } }`,
|
|
3265
|
+
`type ResponseWithBody = { statusCode: number, body: any }`,
|
|
3266
|
+
`type Query<F extends Function> = Event<F> extends RequestWithQuery ? Event<F>['request']['queryStringParameters'] : never`,
|
|
3267
|
+
`type Body<F extends Function> = Event<F> extends RequestWithBody ? Exclude<Event<F>['request']['body'], string> : never`,
|
|
3268
|
+
`type Response<F extends Function> = Awaited<InvokeResponse<F>> extends ResponseWithBody ? Promise<Awaited<InvokeResponse<F>>['body']> : Promise<never>`,
|
|
3269
|
+
`type Route<F extends Function, P> = { param: P; query: Query<F>; body: Body<F>; response: Response<F> }`
|
|
3270
|
+
];
|
|
3271
|
+
code.map((code2) => types2.addCode(code2));
|
|
3272
|
+
types2.addInterface("HTTP", resources);
|
|
3273
|
+
await ctx.write("http.d.ts", types2, true);
|
|
3106
3274
|
},
|
|
3107
|
-
|
|
3108
|
-
|
|
3275
|
+
onApp(ctx) {
|
|
3276
|
+
if (Object.keys(ctx.appConfig.defaults?.http ?? {}).length === 0) {
|
|
3277
|
+
return;
|
|
3278
|
+
}
|
|
3279
|
+
const group = new Node8(ctx.base, "http", "main");
|
|
3280
|
+
const securityGroup = new aws8.ec2.SecurityGroup(group, "http", {
|
|
3281
|
+
vpcId: ctx.shared.get(`vpc-id`),
|
|
3282
|
+
name: formatGlobalResourceName(ctx.app.name, "http", "http"),
|
|
3283
|
+
description: `Global security group for HTTP api.`
|
|
3284
|
+
});
|
|
3285
|
+
const port = aws8.ec2.Port.tcp(443);
|
|
3286
|
+
securityGroup.addIngressRule({ port, peer: aws8.ec2.Peer.anyIpv4() });
|
|
3287
|
+
securityGroup.addIngressRule({ port, peer: aws8.ec2.Peer.anyIpv6() });
|
|
3288
|
+
for (const [id, props] of Object.entries(ctx.appConfig.defaults?.http ?? {})) {
|
|
3289
|
+
const group2 = new Node8(ctx.base, "http", id);
|
|
3290
|
+
const loadBalancer = new aws8.elb.LoadBalancer(group2, "balancer", {
|
|
3291
|
+
name: formatGlobalResourceName(ctx.app.name, "http", id),
|
|
3292
|
+
type: "application",
|
|
3293
|
+
securityGroups: [securityGroup.id],
|
|
3294
|
+
subnets: [
|
|
3295
|
+
//
|
|
3296
|
+
ctx.shared.get(`vpc-public-subnet-id-1`),
|
|
3297
|
+
ctx.shared.get(`vpc-public-subnet-id-2`)
|
|
3298
|
+
]
|
|
3299
|
+
});
|
|
3300
|
+
const listener = new aws8.elb.Listener(group2, "listener", {
|
|
3301
|
+
loadBalancerArn: loadBalancer.arn,
|
|
3302
|
+
port: 443,
|
|
3303
|
+
protocol: "https",
|
|
3304
|
+
certificates: [ctx.shared.get(`local-certificate-${props.domain}-arn`)],
|
|
3305
|
+
defaultActions: [
|
|
3306
|
+
aws8.elb.ListenerAction.fixedResponse({
|
|
3307
|
+
statusCode: 404,
|
|
3308
|
+
contentType: "application/json",
|
|
3309
|
+
messageBody: JSON.stringify({
|
|
3310
|
+
message: "Route not found"
|
|
3311
|
+
})
|
|
3312
|
+
})
|
|
3313
|
+
]
|
|
3314
|
+
});
|
|
3315
|
+
ctx.shared.set(`http-${id}-listener-arn`, listener.arn);
|
|
3316
|
+
const domainName = formatFullDomainName(ctx.appConfig, props.domain, props.subDomain);
|
|
3317
|
+
new aws8.route53.RecordSet(group2, domainName, {
|
|
3318
|
+
hostedZoneId: ctx.shared.get(`hosted-zone-${props.domain}-id`),
|
|
3319
|
+
name: domainName,
|
|
3320
|
+
type: "A",
|
|
3321
|
+
alias: {
|
|
3322
|
+
evaluateTargetHealth: false,
|
|
3323
|
+
hostedZoneId: loadBalancer.hostedZoneId,
|
|
3324
|
+
dnsName: loadBalancer.dnsName
|
|
3325
|
+
}
|
|
3326
|
+
});
|
|
3327
|
+
ctx.bindEnv(`HTTP_${constantCase5(id)}_ENDPOINT`, domainName);
|
|
3328
|
+
}
|
|
3329
|
+
},
|
|
3330
|
+
onStack(ctx) {
|
|
3331
|
+
for (const [id, routes] of Object.entries(ctx.stackConfig.http ?? {})) {
|
|
3332
|
+
const props = ctx.appConfig.defaults.http?.[id];
|
|
3333
|
+
if (!props) {
|
|
3334
|
+
throw new Error(`Http definition is not defined on app level for "${id}"`);
|
|
3335
|
+
}
|
|
3336
|
+
const group = new Node8(ctx.stack, "http", id);
|
|
3337
|
+
for (const [routeKey, routeProps] of Object.entries(routes)) {
|
|
3338
|
+
const routeGroup = new Node8(group, "route", routeKey);
|
|
3339
|
+
const { method, path } = parseRoute(routeKey);
|
|
3340
|
+
const routeId = shortId(routeKey);
|
|
3341
|
+
const { lambda } = createLambdaFunction(routeGroup, ctx, "http", `${id}-${routeId}`, {
|
|
3342
|
+
...routeProps,
|
|
3343
|
+
description: routeKey
|
|
3344
|
+
});
|
|
3345
|
+
const name = formatLocalResourceName(ctx.app.name, ctx.stack.name, "http", routeId);
|
|
3346
|
+
const permission = new aws8.lambda.Permission(routeGroup, id, {
|
|
3347
|
+
action: "lambda:InvokeFunction",
|
|
3348
|
+
principal: "elasticloadbalancing.amazonaws.com",
|
|
3349
|
+
functionArn: lambda.arn
|
|
3350
|
+
// sourceArn: `arn:aws:elasticloadbalancing:${ctx.appConfig.region}:*:targetgroup/${name}/*`,
|
|
3351
|
+
});
|
|
3352
|
+
const target = new aws8.elb.TargetGroup(routeGroup, id, {
|
|
3353
|
+
name,
|
|
3354
|
+
type: "lambda",
|
|
3355
|
+
targets: [lambda.arn]
|
|
3356
|
+
}).dependsOn(permission);
|
|
3357
|
+
new aws8.elb.ListenerRule(routeGroup, id, {
|
|
3358
|
+
listenerArn: ctx.shared.get(`http-${id}-listener-arn`),
|
|
3359
|
+
priority: generatePriority(ctx.stackConfig.name, routeKey),
|
|
3360
|
+
conditions: [
|
|
3361
|
+
aws8.elb.ListenerCondition.httpRequestMethods([method]),
|
|
3362
|
+
aws8.elb.ListenerCondition.pathPatterns([path])
|
|
3363
|
+
],
|
|
3364
|
+
actions: [aws8.elb.ListenerAction.forward([target.arn])]
|
|
3365
|
+
}).dependsOn(target);
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
});
|
|
3370
|
+
|
|
3371
|
+
// src/feature/on-failure/index.ts
|
|
3372
|
+
import { Node as Node9, aws as aws9 } from "@awsless/formation";
|
|
3373
|
+
var onFailureFeature = defineFeature({
|
|
3374
|
+
name: "on-failure",
|
|
3375
|
+
onApp(ctx) {
|
|
3376
|
+
if (!hasOnFailure(ctx.stackConfigs)) {
|
|
3377
|
+
return;
|
|
3378
|
+
}
|
|
3379
|
+
const count = ctx.stackConfigs.filter((s) => s.onFailure).length;
|
|
3380
|
+
if (count > 1) {
|
|
3381
|
+
throw new TypeError("Only 1 onFailure configuration is allowed in your app.");
|
|
3382
|
+
}
|
|
3383
|
+
const queue2 = new aws9.sqs.Queue(ctx.base, "on-failure", {
|
|
3384
|
+
name: formatGlobalResourceName(ctx.appConfig.name, "on-failure", "failure")
|
|
3385
|
+
});
|
|
3386
|
+
ctx.shared.set("on-failure-queue-arn", queue2.arn);
|
|
3387
|
+
},
|
|
3388
|
+
onStack(ctx) {
|
|
3389
|
+
const onFailure = ctx.stackConfig.onFailure;
|
|
3109
3390
|
if (!onFailure) {
|
|
3110
3391
|
return;
|
|
3111
3392
|
}
|
|
3112
3393
|
const queueArn = ctx.shared.get("on-failure-queue-arn");
|
|
3113
|
-
const group = new
|
|
3394
|
+
const group = new Node9(ctx.stack, "on-failure", "failure");
|
|
3114
3395
|
const { lambda, policy } = createLambdaFunction(group, ctx, "on-failure", "failure", onFailure);
|
|
3115
|
-
const source = new
|
|
3396
|
+
const source = new aws9.lambda.EventSourceMapping(group, "on-failure", {
|
|
3116
3397
|
functionArn: lambda.arn,
|
|
3117
3398
|
sourceArn: queueArn,
|
|
3118
3399
|
batchSize: 10
|
|
@@ -3132,7 +3413,7 @@ var onFailureFeature = defineFeature({
|
|
|
3132
3413
|
});
|
|
3133
3414
|
|
|
3134
3415
|
// src/feature/pubsub/index.ts
|
|
3135
|
-
import { Node as
|
|
3416
|
+
import { Node as Node10, aws as aws10 } from "@awsless/formation";
|
|
3136
3417
|
var pubsubFeature = defineFeature({
|
|
3137
3418
|
name: "pubsub",
|
|
3138
3419
|
onApp(ctx) {
|
|
@@ -3145,16 +3426,16 @@ var pubsubFeature = defineFeature({
|
|
|
3145
3426
|
},
|
|
3146
3427
|
onStack(ctx) {
|
|
3147
3428
|
for (const [id, props] of Object.entries(ctx.stackConfig.pubsub ?? {})) {
|
|
3148
|
-
const group = new
|
|
3429
|
+
const group = new Node10(ctx.stack, "pubsub", id);
|
|
3149
3430
|
const { lambda } = createAsyncLambdaFunction(group, ctx, `pubsub`, id, props.consumer);
|
|
3150
3431
|
const name = formatLocalResourceName(ctx.app.name, ctx.stack.name, "pubsub", id);
|
|
3151
|
-
const topic = new
|
|
3432
|
+
const topic = new aws10.iot.TopicRule(group, "rule", {
|
|
3152
3433
|
name: name.replaceAll("-", "_"),
|
|
3153
3434
|
sql: props.sql,
|
|
3154
3435
|
sqlVersion: props.sqlVersion,
|
|
3155
3436
|
actions: [{ lambda: { functionArn: lambda.arn } }]
|
|
3156
3437
|
});
|
|
3157
|
-
new
|
|
3438
|
+
new aws10.lambda.Permission(group, "permission", {
|
|
3158
3439
|
action: "lambda:InvokeFunction",
|
|
3159
3440
|
principal: "iot.amazonaws.com",
|
|
3160
3441
|
functionArn: lambda.arn,
|
|
@@ -3165,10 +3446,10 @@ var pubsubFeature = defineFeature({
|
|
|
3165
3446
|
});
|
|
3166
3447
|
|
|
3167
3448
|
// src/feature/queue/index.ts
|
|
3168
|
-
import { camelCase as
|
|
3169
|
-
import { relative as
|
|
3449
|
+
import { camelCase as camelCase5, constantCase as constantCase6 } from "change-case";
|
|
3450
|
+
import { relative as relative3 } from "path";
|
|
3170
3451
|
import deepmerge2 from "deepmerge";
|
|
3171
|
-
import { Node as
|
|
3452
|
+
import { Node as Node11, aws as aws11 } from "@awsless/formation";
|
|
3172
3453
|
var typeGenCode3 = `
|
|
3173
3454
|
import { SendMessageOptions, SendMessageBatchOptions, BatchItem } from '@awsless/sqs'
|
|
3174
3455
|
import type { Mock } from 'vitest'
|
|
@@ -3198,10 +3479,10 @@ var queueFeature = defineFeature({
|
|
|
3198
3479
|
const mock = new TypeObject(2);
|
|
3199
3480
|
const mockResponse = new TypeObject(2);
|
|
3200
3481
|
for (const [name, fileOrProps] of Object.entries(stack.queues || {})) {
|
|
3201
|
-
const varName =
|
|
3482
|
+
const varName = camelCase5(`${stack.name}-${name}`);
|
|
3202
3483
|
const queueName = formatLocalResourceName(ctx.appConfig.name, stack.name, "queue", name);
|
|
3203
3484
|
const file = typeof fileOrProps === "string" ? fileOrProps : typeof fileOrProps.consumer === "string" ? fileOrProps.consumer : fileOrProps.consumer.file;
|
|
3204
|
-
const relFile =
|
|
3485
|
+
const relFile = relative3(directories.types, file);
|
|
3205
3486
|
gen.addImport(varName, relFile);
|
|
3206
3487
|
mock.addType(name, `MockBuilder<typeof ${varName}>`);
|
|
3207
3488
|
resource2.addType(name, `Send<'${queueName}', typeof ${varName}>`);
|
|
@@ -3220,15 +3501,15 @@ var queueFeature = defineFeature({
|
|
|
3220
3501
|
onStack(ctx) {
|
|
3221
3502
|
for (const [id, local2] of Object.entries(ctx.stackConfig.queues || {})) {
|
|
3222
3503
|
const props = deepmerge2(ctx.appConfig.defaults.queue, local2);
|
|
3223
|
-
const group = new
|
|
3224
|
-
const queue2 = new
|
|
3504
|
+
const group = new Node11(ctx.stack, "queue", id);
|
|
3505
|
+
const queue2 = new aws11.sqs.Queue(group, "queue", {
|
|
3225
3506
|
name: formatLocalResourceName(ctx.appConfig.name, ctx.stack.name, "queue", id),
|
|
3226
3507
|
deadLetterArn: getGlobalOnFailure(ctx),
|
|
3227
3508
|
...props
|
|
3228
3509
|
});
|
|
3229
3510
|
const { lambda, policy } = createLambdaFunction(group, ctx, `queue`, id, props.consumer);
|
|
3230
3511
|
lambda.addEnvironment("LOG_VIEWABLE_ERROR", "1");
|
|
3231
|
-
new
|
|
3512
|
+
new aws11.lambda.EventSourceMapping(group, "event", {
|
|
3232
3513
|
functionArn: lambda.arn,
|
|
3233
3514
|
sourceArn: queue2.arn,
|
|
3234
3515
|
batchSize: props.batchSize,
|
|
@@ -3241,612 +3522,129 @@ var queueFeature = defineFeature({
|
|
|
3241
3522
|
});
|
|
3242
3523
|
ctx.onFunction(({ lambda: lambda2, policy: policy2 }) => {
|
|
3243
3524
|
policy2.addStatement(queue2.permissions);
|
|
3244
|
-
lambda2.addEnvironment(`QUEUE_${
|
|
3525
|
+
lambda2.addEnvironment(`QUEUE_${constantCase6(ctx.stack.name)}_${constantCase6(id)}_URL`, queue2.url);
|
|
3245
3526
|
});
|
|
3246
3527
|
}
|
|
3247
3528
|
}
|
|
3248
3529
|
});
|
|
3249
3530
|
|
|
3250
|
-
// src/feature/
|
|
3251
|
-
import {
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3531
|
+
// src/feature/rest/index.ts
|
|
3532
|
+
import { Node as Node12, aws as aws12 } from "@awsless/formation";
|
|
3533
|
+
import { constantCase as constantCase7 } from "change-case";
|
|
3534
|
+
var restFeature = defineFeature({
|
|
3535
|
+
name: "rest",
|
|
3536
|
+
onApp(ctx) {
|
|
3537
|
+
for (const [id, props] of Object.entries(ctx.appConfig.defaults?.rest ?? {})) {
|
|
3538
|
+
const group = new Node12(ctx.base, "rest", id);
|
|
3539
|
+
const api = new aws12.apiGatewayV2.Api(group, "api", {
|
|
3540
|
+
name: formatGlobalResourceName(ctx.app.name, "rest", id),
|
|
3541
|
+
protocolType: "HTTP"
|
|
3542
|
+
});
|
|
3543
|
+
const stage = new aws12.apiGatewayV2.Stage(group, "stage", {
|
|
3544
|
+
name: "v1",
|
|
3545
|
+
apiId: api.id
|
|
3546
|
+
});
|
|
3547
|
+
ctx.shared.set(`rest-${id}-id`, api.id);
|
|
3548
|
+
if (props.domain) {
|
|
3549
|
+
const domainName = formatFullDomainName(ctx.appConfig, props.domain, props.subDomain);
|
|
3550
|
+
const hostedZoneId = ctx.shared.get(`hosted-zone-${props.domain}-id`);
|
|
3551
|
+
const certificateArn = ctx.shared.get(`certificate-${props.domain}-arn`);
|
|
3552
|
+
const domain = new aws12.apiGatewayV2.DomainName(group, "domain", {
|
|
3553
|
+
name: domainName,
|
|
3554
|
+
certificates: [
|
|
3555
|
+
{
|
|
3556
|
+
certificateArn
|
|
3557
|
+
}
|
|
3558
|
+
]
|
|
3559
|
+
});
|
|
3560
|
+
const mapping = new aws12.apiGatewayV2.ApiMapping(group, "mapping", {
|
|
3561
|
+
apiId: api.id,
|
|
3562
|
+
domainName: domain.name,
|
|
3563
|
+
stage: stage.name
|
|
3564
|
+
});
|
|
3565
|
+
const record = new aws12.route53.RecordSet(group, "record", {
|
|
3566
|
+
hostedZoneId,
|
|
3567
|
+
type: "A",
|
|
3568
|
+
name: domainName,
|
|
3569
|
+
alias: {
|
|
3570
|
+
dnsName: domain.regionalDomainName,
|
|
3571
|
+
hostedZoneId: domain.regionalHostedZoneId,
|
|
3572
|
+
evaluateTargetHealth: false
|
|
3573
|
+
}
|
|
3574
|
+
});
|
|
3575
|
+
record.dependsOn(domain, mapping);
|
|
3576
|
+
ctx.bindEnv(`REST_${constantCase7(id)}_ENDPOINT`, domainName);
|
|
3276
3577
|
}
|
|
3277
|
-
resources.addType(stack.name, list4);
|
|
3278
3578
|
}
|
|
3279
|
-
gen.addCode(typeGenCode4);
|
|
3280
|
-
gen.addInterface("StoreResources", resources);
|
|
3281
|
-
await ctx.write("store.d.ts", gen, true);
|
|
3282
3579
|
},
|
|
3283
3580
|
onStack(ctx) {
|
|
3284
|
-
for (const [id,
|
|
3285
|
-
const
|
|
3286
|
-
const
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
"removed:delete": "s3:ObjectRemoved:Delete",
|
|
3296
|
-
"removed:marker": "s3:ObjectRemoved:DeleteMarkerCreated"
|
|
3297
|
-
};
|
|
3298
|
-
for (const [event, funcProps] of Object.entries(props.events ?? {})) {
|
|
3299
|
-
const eventGroup = new Node10(group, "event", event);
|
|
3300
|
-
const { lambda } = createAsyncLambdaFunction(eventGroup, ctx, `store`, id, funcProps);
|
|
3301
|
-
new aws10.lambda.Permission(eventGroup, "permission", {
|
|
3581
|
+
for (const [id, routes] of Object.entries(ctx.stackConfig.rest ?? {})) {
|
|
3582
|
+
const restGroup = new Node12(ctx.stack, "rest", id);
|
|
3583
|
+
for (const [routeKey, props] of Object.entries(routes)) {
|
|
3584
|
+
const group = new Node12(restGroup, "route", routeKey);
|
|
3585
|
+
const apiId = ctx.shared.get(`rest-${id}-id`);
|
|
3586
|
+
const routeId = shortId(routeKey);
|
|
3587
|
+
const { lambda } = createLambdaFunction(group, ctx, "rest", `${id}-${routeId}`, {
|
|
3588
|
+
...props,
|
|
3589
|
+
description: `${id} ${routeKey}`
|
|
3590
|
+
});
|
|
3591
|
+
const permission = new aws12.lambda.Permission(group, "permission", {
|
|
3302
3592
|
action: "lambda:InvokeFunction",
|
|
3303
|
-
principal: "
|
|
3304
|
-
functionArn: lambda.arn
|
|
3305
|
-
sourceArn: `arn:aws:s3:::${bucketName}`
|
|
3593
|
+
principal: "apigateway.amazonaws.com",
|
|
3594
|
+
functionArn: lambda.arn
|
|
3306
3595
|
});
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3596
|
+
const integration = new aws12.apiGatewayV2.Integration(group, "integration", {
|
|
3597
|
+
apiId,
|
|
3598
|
+
description: `${id} ${routeKey}`,
|
|
3599
|
+
method: "POST",
|
|
3600
|
+
payloadFormatVersion: "2.0",
|
|
3601
|
+
type: "AWS_PROXY",
|
|
3602
|
+
uri: lambda.arn.apply((arn) => {
|
|
3603
|
+
return `arn:aws:apigateway:${ctx.appConfig.region}:lambda:path/2015-03-31/functions/${arn}/invocations`;
|
|
3604
|
+
})
|
|
3310
3605
|
});
|
|
3606
|
+
const route = new aws12.apiGatewayV2.Route(group, "route", {
|
|
3607
|
+
apiId,
|
|
3608
|
+
routeKey,
|
|
3609
|
+
target: integration.id.apply((id2) => `integrations/${id2}`)
|
|
3610
|
+
});
|
|
3611
|
+
route.dependsOn(lambda, permission);
|
|
3311
3612
|
}
|
|
3312
|
-
const bucket = new aws10.s3.Bucket(group, "store", {
|
|
3313
|
-
name: bucketName,
|
|
3314
|
-
versioning: props.versioning,
|
|
3315
|
-
lambdaConfigs,
|
|
3316
|
-
cors: [
|
|
3317
|
-
// ---------------------------------------------
|
|
3318
|
-
// Support for presigned post requests
|
|
3319
|
-
// ---------------------------------------------
|
|
3320
|
-
{
|
|
3321
|
-
origins: ["*"],
|
|
3322
|
-
methods: ["POST"]
|
|
3323
|
-
}
|
|
3324
|
-
// ---------------------------------------------
|
|
3325
|
-
]
|
|
3326
|
-
});
|
|
3327
|
-
ctx.onFunction(({ policy }) => {
|
|
3328
|
-
policy.addStatement(bucket.permissions);
|
|
3329
|
-
});
|
|
3330
3613
|
}
|
|
3331
3614
|
}
|
|
3332
3615
|
});
|
|
3333
3616
|
|
|
3334
|
-
// src/feature/
|
|
3335
|
-
import { Node as
|
|
3336
|
-
|
|
3337
|
-
|
|
3617
|
+
// src/feature/search/index.ts
|
|
3618
|
+
import { Node as Node13, aws as aws13 } from "@awsless/formation";
|
|
3619
|
+
import { constantCase as constantCase8 } from "change-case";
|
|
3620
|
+
var typeGenCode4 = `
|
|
3621
|
+
import { AnyStruct, Table } from '@awsless/open-search'
|
|
3622
|
+
|
|
3623
|
+
type Search = {
|
|
3624
|
+
readonly domain: string
|
|
3625
|
+
readonly defineTable: <N extends stringm S extends AnyStruct>(tableName: N, schema: S) => Table<N, S>
|
|
3626
|
+
}
|
|
3627
|
+
`;
|
|
3628
|
+
var searchFeature = defineFeature({
|
|
3629
|
+
name: "search",
|
|
3338
3630
|
async onTypeGen(ctx) {
|
|
3339
3631
|
const gen = new TypeFile("@awsless/awsless");
|
|
3340
3632
|
const resources = new TypeObject(1);
|
|
3341
3633
|
for (const stack of ctx.stackConfigs) {
|
|
3342
3634
|
const list4 = new TypeObject(2);
|
|
3343
|
-
for (const
|
|
3344
|
-
|
|
3345
|
-
list4.addType(name, `'${tableName}'`);
|
|
3635
|
+
for (const id of Object.keys(stack.searchs ?? {})) {
|
|
3636
|
+
list4.addType(id, `Search`);
|
|
3346
3637
|
}
|
|
3347
3638
|
resources.addType(stack.name, list4);
|
|
3348
3639
|
}
|
|
3349
|
-
gen.
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
onStack(ctx) {
|
|
3353
|
-
for (const [id, props] of Object.entries(ctx.stackConfig.tables ?? {})) {
|
|
3354
|
-
const group = new Node11(ctx.stack, "table", id);
|
|
3355
|
-
const table2 = new aws11.dynamodb.Table(group, "table", {
|
|
3356
|
-
...props,
|
|
3357
|
-
name: formatLocalResourceName(ctx.appConfig.name, ctx.stackConfig.name, "table", id),
|
|
3358
|
-
stream: props.stream?.type
|
|
3359
|
-
});
|
|
3360
|
-
if (props.stream) {
|
|
3361
|
-
const { lambda, policy } = createLambdaFunction(group, ctx, "table", id, props.stream.consumer);
|
|
3362
|
-
lambda.addEnvironment("LOG_VIEWABLE_ERROR", "1");
|
|
3363
|
-
const onFailure = getGlobalOnFailure(ctx);
|
|
3364
|
-
const source = new aws11.lambda.EventSourceMapping(group, id, {
|
|
3365
|
-
functionArn: lambda.arn,
|
|
3366
|
-
sourceArn: table2.streamArn,
|
|
3367
|
-
batchSize: 100,
|
|
3368
|
-
bisectBatchOnError: true,
|
|
3369
|
-
// retryAttempts: props.stream.consumer.retryAttempts ?? -1,
|
|
3370
|
-
parallelizationFactor: 1,
|
|
3371
|
-
startingPosition: "latest",
|
|
3372
|
-
onFailure
|
|
3373
|
-
});
|
|
3374
|
-
policy.addStatement(table2.streamPermissions);
|
|
3375
|
-
source.dependsOn(policy);
|
|
3376
|
-
if (onFailure) {
|
|
3377
|
-
policy.addStatement({
|
|
3378
|
-
actions: ["sqs:SendMessage", "sqs:GetQueueUrl"],
|
|
3379
|
-
resources: [onFailure]
|
|
3380
|
-
});
|
|
3381
|
-
}
|
|
3382
|
-
}
|
|
3383
|
-
ctx.onFunction(({ policy }) => {
|
|
3384
|
-
policy.addStatement(...table2.permissions);
|
|
3385
|
-
});
|
|
3386
|
-
}
|
|
3387
|
-
}
|
|
3388
|
-
});
|
|
3389
|
-
|
|
3390
|
-
// src/feature/test/index.ts
|
|
3391
|
-
var testFeature = defineFeature({
|
|
3392
|
-
name: "test",
|
|
3393
|
-
onStack(ctx) {
|
|
3394
|
-
if (ctx.stackConfig.tests) {
|
|
3395
|
-
ctx.registerTest(ctx.stackConfig.name, ctx.stackConfig.tests);
|
|
3396
|
-
}
|
|
3397
|
-
}
|
|
3398
|
-
});
|
|
3399
|
-
|
|
3400
|
-
// src/feature/topic/index.ts
|
|
3401
|
-
import { Node as Node12, aws as aws12 } from "@awsless/formation";
|
|
3402
|
-
var typeGenCode5 = `
|
|
3403
|
-
import type { PublishOptions } from '@awsless/sns'
|
|
3404
|
-
import type { Mock } from 'vitest'
|
|
3405
|
-
|
|
3406
|
-
type Publish<Name extends string> = {
|
|
3407
|
-
readonly name: Name
|
|
3408
|
-
(payload: unknown, options?: Omit<PublishOptions, 'topic' | 'payload'>): Promise<void>
|
|
3409
|
-
}
|
|
3410
|
-
|
|
3411
|
-
type MockHandle = (payload: unknown) => void
|
|
3412
|
-
type MockBuilder = (handle?: MockHandle) => void
|
|
3413
|
-
`;
|
|
3414
|
-
var topicFeature = defineFeature({
|
|
3415
|
-
name: "topic",
|
|
3416
|
-
async onTypeGen(ctx) {
|
|
3417
|
-
const gen = new TypeFile("@awsless/awsless");
|
|
3418
|
-
const resources = new TypeObject(1);
|
|
3419
|
-
const mocks = new TypeObject(1);
|
|
3420
|
-
const mockResponses = new TypeObject(1);
|
|
3421
|
-
for (const stack of ctx.stackConfigs) {
|
|
3422
|
-
for (const topic of stack.topics || []) {
|
|
3423
|
-
const name = formatGlobalResourceName(ctx.appConfig.name, "topic", topic);
|
|
3424
|
-
mockResponses.addType(topic, "Mock");
|
|
3425
|
-
resources.addType(topic, `Publish<'${name}'>`);
|
|
3426
|
-
mocks.addType(topic, `MockBuilder`);
|
|
3427
|
-
}
|
|
3428
|
-
}
|
|
3429
|
-
gen.addCode(typeGenCode5);
|
|
3430
|
-
gen.addInterface("TopicResources", resources);
|
|
3431
|
-
gen.addInterface("TopicMock", mocks);
|
|
3432
|
-
gen.addInterface("TopicMockResponse", mockResponses);
|
|
3433
|
-
await ctx.write("topic.d.ts", gen, true);
|
|
3434
|
-
},
|
|
3435
|
-
onApp(ctx) {
|
|
3436
|
-
for (const stack of ctx.stackConfigs) {
|
|
3437
|
-
for (const id of stack.topics ?? []) {
|
|
3438
|
-
const group = new Node12(ctx.base, "topic", id);
|
|
3439
|
-
const topic = new aws12.sns.Topic(group, "topic", {
|
|
3440
|
-
name: formatGlobalResourceName(ctx.appConfig.name, "topic", id)
|
|
3441
|
-
});
|
|
3442
|
-
ctx.shared.set(`topic-${id}-arn`, topic.arn);
|
|
3443
|
-
}
|
|
3444
|
-
}
|
|
3445
|
-
},
|
|
3446
|
-
onStack(ctx) {
|
|
3447
|
-
for (const id of ctx.stackConfig.topics ?? []) {
|
|
3448
|
-
ctx.onFunction(({ policy }) => {
|
|
3449
|
-
policy.addStatement({
|
|
3450
|
-
actions: ["sns:Publish"],
|
|
3451
|
-
resources: [ctx.shared.get(`topic-${id}-arn`)]
|
|
3452
|
-
});
|
|
3453
|
-
});
|
|
3454
|
-
}
|
|
3455
|
-
for (const [id, props] of Object.entries(ctx.stackConfig.subscribers ?? {})) {
|
|
3456
|
-
const group = new Node12(ctx.stack, "topic", id);
|
|
3457
|
-
const topicArn = ctx.shared.get(`topic-${id}-arn`);
|
|
3458
|
-
if (typeof props === "string" && isEmail(props)) {
|
|
3459
|
-
new aws12.sns.Subscription(group, id, {
|
|
3460
|
-
topicArn,
|
|
3461
|
-
protocol: "email",
|
|
3462
|
-
endpoint: props
|
|
3463
|
-
});
|
|
3464
|
-
} else if (typeof props === "object") {
|
|
3465
|
-
const { lambda } = createAsyncLambdaFunction(group, ctx, `topic`, id, props);
|
|
3466
|
-
new aws12.sns.Subscription(group, id, {
|
|
3467
|
-
topicArn,
|
|
3468
|
-
protocol: "lambda",
|
|
3469
|
-
endpoint: lambda.arn
|
|
3470
|
-
});
|
|
3471
|
-
new aws12.lambda.Permission(group, id, {
|
|
3472
|
-
action: "lambda:InvokeFunction",
|
|
3473
|
-
principal: "sns.amazonaws.com",
|
|
3474
|
-
functionArn: lambda.arn,
|
|
3475
|
-
sourceArn: topicArn
|
|
3476
|
-
});
|
|
3477
|
-
}
|
|
3478
|
-
}
|
|
3479
|
-
}
|
|
3480
|
-
});
|
|
3481
|
-
|
|
3482
|
-
// src/feature/vpc/index.ts
|
|
3483
|
-
import { Node as Node13, all, aws as aws13 } from "@awsless/formation";
|
|
3484
|
-
var vpcFeature = defineFeature({
|
|
3485
|
-
name: "vpc",
|
|
3486
|
-
onApp(ctx) {
|
|
3487
|
-
const group = new Node13(ctx.base, "vpc", "main");
|
|
3488
|
-
const vpc = new aws13.ec2.Vpc(group, "vpc", {
|
|
3489
|
-
name: ctx.app.name,
|
|
3490
|
-
cidrBlock: aws13.ec2.Peer.ipv4("10.0.0.0/16")
|
|
3491
|
-
});
|
|
3492
|
-
const privateRouteTable = new aws13.ec2.RouteTable(group, "private", {
|
|
3493
|
-
vpcId: vpc.id,
|
|
3494
|
-
name: "private"
|
|
3495
|
-
});
|
|
3496
|
-
const publicRouteTable = new aws13.ec2.RouteTable(group, "public", {
|
|
3497
|
-
vpcId: vpc.id,
|
|
3498
|
-
name: "public"
|
|
3499
|
-
});
|
|
3500
|
-
const gateway = new aws13.ec2.InternetGateway(group, "gateway");
|
|
3501
|
-
const attachment = new aws13.ec2.VPCGatewayAttachment(group, "attachment", {
|
|
3502
|
-
vpcId: vpc.id,
|
|
3503
|
-
internetGatewayId: gateway.id
|
|
3504
|
-
});
|
|
3505
|
-
new aws13.ec2.Route(group, "route", {
|
|
3506
|
-
gatewayId: gateway.id,
|
|
3507
|
-
routeTableId: publicRouteTable.id,
|
|
3508
|
-
destination: aws13.ec2.Peer.anyIpv4()
|
|
3509
|
-
});
|
|
3510
|
-
ctx.shared.set(
|
|
3511
|
-
"vpc-id",
|
|
3512
|
-
// Some resources require the internet gateway to be attached.
|
|
3513
|
-
all([vpc.id, attachment.internetGatewayId]).apply(([id]) => id)
|
|
3514
|
-
);
|
|
3515
|
-
ctx.shared.set("vpc-security-group-id", vpc.defaultSecurityGroup);
|
|
3516
|
-
const zones = ["a", "b"];
|
|
3517
|
-
const tables = [privateRouteTable, publicRouteTable];
|
|
3518
|
-
let block = 0;
|
|
3519
|
-
for (const table2 of tables) {
|
|
3520
|
-
for (const i in zones) {
|
|
3521
|
-
const index = Number(i) + 1;
|
|
3522
|
-
const id = `${table2.identifier}-${index}`;
|
|
3523
|
-
const subnet = new aws13.ec2.Subnet(group, id, {
|
|
3524
|
-
vpcId: vpc.id,
|
|
3525
|
-
cidrBlock: aws13.ec2.Peer.ipv4(`10.0.${block++}.0/24`),
|
|
3526
|
-
availabilityZone: ctx.appConfig.region + zones[i]
|
|
3527
|
-
});
|
|
3528
|
-
new aws13.ec2.SubnetRouteTableAssociation(group, id, {
|
|
3529
|
-
routeTableId: table2.id,
|
|
3530
|
-
subnetId: subnet.id
|
|
3531
|
-
});
|
|
3532
|
-
ctx.shared.set(`vpc-${table2.identifier}-subnet-id-${index}`, subnet.id);
|
|
3533
|
-
}
|
|
3534
|
-
}
|
|
3535
|
-
}
|
|
3536
|
-
});
|
|
3537
|
-
|
|
3538
|
-
// src/feature/auth/index.ts
|
|
3539
|
-
import { constantCase as constantCase5 } from "change-case";
|
|
3540
|
-
import { Node as Node14, aws as aws14 } from "@awsless/formation";
|
|
3541
|
-
var authFeature = defineFeature({
|
|
3542
|
-
name: "auth",
|
|
3543
|
-
async onTypeGen(ctx) {
|
|
3544
|
-
const gen = new TypeFile("@awsless/awsless");
|
|
3545
|
-
const resources = new TypeObject(1);
|
|
3546
|
-
for (const name of Object.keys(ctx.appConfig.defaults.auth)) {
|
|
3547
|
-
const authName = formatGlobalResourceName(ctx.appConfig.name, "auth", name);
|
|
3548
|
-
resources.addType(
|
|
3549
|
-
name,
|
|
3550
|
-
`{ readonly name: '${authName}', readonly userPoolId: string, readonly clientId: string }`
|
|
3551
|
-
);
|
|
3552
|
-
}
|
|
3553
|
-
gen.addInterface("AuthResources", resources);
|
|
3554
|
-
await ctx.write("auth.d.ts", gen, true);
|
|
3555
|
-
},
|
|
3556
|
-
onStack(ctx) {
|
|
3557
|
-
for (const [id, props] of Object.entries(ctx.stackConfig.auth ?? {})) {
|
|
3558
|
-
const group = new Node14(ctx.stack, "auth", id);
|
|
3559
|
-
const userPoolId = ctx.shared.get(`auth-${id}-user-pool-id`);
|
|
3560
|
-
const userPoolArn = ctx.shared.get(`auth-${id}-user-pool-arn`);
|
|
3561
|
-
const clientId = ctx.shared.get(`auth-${id}-client-id`);
|
|
3562
|
-
const triggers = {};
|
|
3563
|
-
const list4 = {};
|
|
3564
|
-
for (const [trigger, triggerProps] of Object.entries(props.triggers ?? {})) {
|
|
3565
|
-
const triggerGroup = new Node14(group, "trigger", trigger);
|
|
3566
|
-
const { lambda, policy } = createAsyncLambdaFunction(
|
|
3567
|
-
triggerGroup,
|
|
3568
|
-
ctx,
|
|
3569
|
-
"auth",
|
|
3570
|
-
`${id}-${trigger}`,
|
|
3571
|
-
triggerProps
|
|
3572
|
-
);
|
|
3573
|
-
triggers[trigger] = lambda.arn;
|
|
3574
|
-
list4[trigger] = {
|
|
3575
|
-
trigger,
|
|
3576
|
-
group: triggerGroup,
|
|
3577
|
-
lambda,
|
|
3578
|
-
policy
|
|
3579
|
-
};
|
|
3580
|
-
}
|
|
3581
|
-
new aws14.cognito.LambdaTriggers(group, "lambda-triggers", {
|
|
3582
|
-
userPoolId,
|
|
3583
|
-
triggers
|
|
3584
|
-
});
|
|
3585
|
-
for (const item of Object.values(list4)) {
|
|
3586
|
-
new aws14.lambda.Permission(item.group, `permission`, {
|
|
3587
|
-
action: "lambda:InvokeFunction",
|
|
3588
|
-
principal: "cognito-idp.amazonaws.com",
|
|
3589
|
-
functionArn: item.lambda.arn,
|
|
3590
|
-
sourceArn: userPoolArn
|
|
3591
|
-
});
|
|
3592
|
-
item.lambda.addEnvironment(`AUTH_${constantCase5(id)}_USER_POOL_ID`, userPoolId);
|
|
3593
|
-
item.lambda.addEnvironment(`AUTH_${constantCase5(id)}_CLIENT_ID`, clientId);
|
|
3594
|
-
item.policy.addStatement({
|
|
3595
|
-
actions: ["cognito:*"],
|
|
3596
|
-
resources: [
|
|
3597
|
-
// Not yet known if this is correct way to grant access to all resources
|
|
3598
|
-
userPoolArn
|
|
3599
|
-
// userPoolId.apply<aws.ARN>(
|
|
3600
|
-
// id => `arn:aws:cognito-idp:${ctx.appConfig.region}:${ctx.accountId}:userpool/${id}`
|
|
3601
|
-
// ),
|
|
3602
|
-
// userPoolId.apply<aws.ARN>(
|
|
3603
|
-
// id => `arn:aws:cognito-idp:${ctx.appConfig.region}:${ctx.accountId}:userpool/${id}*`
|
|
3604
|
-
// ),
|
|
3605
|
-
]
|
|
3606
|
-
});
|
|
3607
|
-
}
|
|
3608
|
-
}
|
|
3609
|
-
},
|
|
3610
|
-
onApp(ctx) {
|
|
3611
|
-
for (const [id, props] of Object.entries(ctx.appConfig.defaults.auth ?? {})) {
|
|
3612
|
-
const group = new Node14(ctx.base, "auth", id);
|
|
3613
|
-
let emailConfig;
|
|
3614
|
-
if (props.messaging) {
|
|
3615
|
-
const [_, domainName] = props.messaging.fromEmail.split("@");
|
|
3616
|
-
emailConfig = {
|
|
3617
|
-
type: "developer",
|
|
3618
|
-
replyTo: props.messaging.replyTo,
|
|
3619
|
-
sourceArn: ctx.shared.get(`mail-${domainName}-arn`),
|
|
3620
|
-
configurationSet: ctx.shared.get("mail-configuration-set"),
|
|
3621
|
-
from: props.messaging.fromName ? `${props.messaging.fromName} <${props.messaging.fromEmail}>` : props.messaging.fromEmail
|
|
3622
|
-
};
|
|
3623
|
-
}
|
|
3624
|
-
const name = formatGlobalResourceName(ctx.appConfig.name, "auth", id);
|
|
3625
|
-
const userPool = new aws14.cognito.UserPool(group, "user-pool", {
|
|
3626
|
-
name,
|
|
3627
|
-
// deletionProtection: true,
|
|
3628
|
-
allowUserRegistration: props.allowUserRegistration,
|
|
3629
|
-
username: props.username,
|
|
3630
|
-
password: props.password,
|
|
3631
|
-
email: emailConfig
|
|
3632
|
-
});
|
|
3633
|
-
const client = new aws14.cognito.UserPoolClient(group, "client", {
|
|
3634
|
-
userPoolId: userPool.id,
|
|
3635
|
-
name,
|
|
3636
|
-
validity: props.validity,
|
|
3637
|
-
supportedIdentityProviders: ["cognito"],
|
|
3638
|
-
authFlows: {
|
|
3639
|
-
userSrp: true
|
|
3640
|
-
}
|
|
3641
|
-
});
|
|
3642
|
-
ctx.bindEnv(`AUTH_${constantCase5(id)}_USER_POOL_ID`, userPool.id);
|
|
3643
|
-
ctx.bindEnv(`AUTH_${constantCase5(id)}_CLIENT_ID`, client.id);
|
|
3644
|
-
ctx.shared.set(`auth-${id}-user-pool-arn`, userPool.arn);
|
|
3645
|
-
ctx.shared.set(`auth-${id}-user-pool-id`, userPool.id);
|
|
3646
|
-
ctx.shared.set(`auth-${id}-client-id`, client.id);
|
|
3647
|
-
}
|
|
3648
|
-
}
|
|
3649
|
-
});
|
|
3650
|
-
|
|
3651
|
-
// src/feature/http/index.ts
|
|
3652
|
-
import { Node as Node15, aws as aws15 } from "@awsless/formation";
|
|
3653
|
-
import { camelCase as camelCase5, constantCase as constantCase6 } from "change-case";
|
|
3654
|
-
import { relative as relative3 } from "path";
|
|
3655
|
-
var parseRoute = (route) => {
|
|
3656
|
-
const [method, ...paths] = route.split(" ");
|
|
3657
|
-
const path = paths.join(" ");
|
|
3658
|
-
return { method, path };
|
|
3659
|
-
};
|
|
3660
|
-
var strToInt = (str) => {
|
|
3661
|
-
return parseInt(Buffer.from(str, "utf8").toString("hex"), 16);
|
|
3662
|
-
};
|
|
3663
|
-
var generatePriority = (stackName, route) => {
|
|
3664
|
-
const start = strToInt(stackName) % 500 + 1;
|
|
3665
|
-
const end = strToInt(route) % 100;
|
|
3666
|
-
const priority = start + "" + end;
|
|
3667
|
-
return parseInt(priority, 10);
|
|
3668
|
-
};
|
|
3669
|
-
var httpFeature = defineFeature({
|
|
3670
|
-
name: "http",
|
|
3671
|
-
async onTypeGen(ctx) {
|
|
3672
|
-
const types2 = new TypeFile("@awsless/awsless");
|
|
3673
|
-
const resources = new TypeObject(1);
|
|
3674
|
-
const api = {};
|
|
3675
|
-
for (const stack of ctx.stackConfigs) {
|
|
3676
|
-
for (const [id, routes] of Object.entries(stack.http ?? {})) {
|
|
3677
|
-
if (!(id in api))
|
|
3678
|
-
api[id] = {};
|
|
3679
|
-
for (const [route, props] of Object.entries(routes)) {
|
|
3680
|
-
const { path, method } = parseRoute(route);
|
|
3681
|
-
const file = typeof props === "string" ? props : props.file;
|
|
3682
|
-
if (!(method in api[id])) {
|
|
3683
|
-
api[id][method] = {};
|
|
3684
|
-
}
|
|
3685
|
-
api[id][method][path] = file;
|
|
3686
|
-
}
|
|
3687
|
-
}
|
|
3688
|
-
}
|
|
3689
|
-
for (const [id, routes] of Object.entries(api)) {
|
|
3690
|
-
const idType = new TypeObject(2);
|
|
3691
|
-
for (const [method, paths] of Object.entries(routes)) {
|
|
3692
|
-
const methodType = new TypeObject(3);
|
|
3693
|
-
for (const [path, file] of Object.entries(paths)) {
|
|
3694
|
-
const paramType = new TypeObject(4);
|
|
3695
|
-
for (const param of path.matchAll(/{([a-z0-9]+)}/g)) {
|
|
3696
|
-
paramType.addType(param[0], "string | number");
|
|
3697
|
-
}
|
|
3698
|
-
const varName = camelCase5(`${id}-${path}-${method}`);
|
|
3699
|
-
const relFile = relative3(directories.types, file);
|
|
3700
|
-
types2.addImport(varName, relFile);
|
|
3701
|
-
methodType.add(`'${path}'`, `Route<typeof ${varName}, ${paramType.toString() || "never"}>`);
|
|
3702
|
-
}
|
|
3703
|
-
idType.addConst(method, methodType);
|
|
3704
|
-
}
|
|
3705
|
-
resources.addType(id, idType);
|
|
3706
|
-
}
|
|
3707
|
-
const code = [
|
|
3708
|
-
`import { InvokeResponse } from '@awsless/lambda'`,
|
|
3709
|
-
`type Function = (...args: any) => any`,
|
|
3710
|
-
`type Event<F extends Function> = Parameters<F>[0]`,
|
|
3711
|
-
`type RequestWithQuery = { request: { queryStringParameters: any } }`,
|
|
3712
|
-
`type RequestWithBody = { request: { body: any } }`,
|
|
3713
|
-
`type ResponseWithBody = { statusCode: number, body: any }`,
|
|
3714
|
-
`type Query<F extends Function> = Event<F> extends RequestWithQuery ? Event<F>['request']['queryStringParameters'] : never`,
|
|
3715
|
-
`type Body<F extends Function> = Event<F> extends RequestWithBody ? Exclude<Event<F>['request']['body'], string> : never`,
|
|
3716
|
-
`type Response<F extends Function> = Awaited<InvokeResponse<F>> extends ResponseWithBody ? Promise<Awaited<InvokeResponse<F>>['body']> : Promise<never>`,
|
|
3717
|
-
`type Route<F extends Function, P> = { param: P; query: Query<F>; body: Body<F>; response: Response<F> }`
|
|
3718
|
-
];
|
|
3719
|
-
code.map((code2) => types2.addCode(code2));
|
|
3720
|
-
types2.addInterface("HTTP", resources);
|
|
3721
|
-
await ctx.write("http.d.ts", types2, true);
|
|
3722
|
-
},
|
|
3723
|
-
onApp(ctx) {
|
|
3724
|
-
if (Object.keys(ctx.appConfig.defaults?.http ?? {}).length === 0) {
|
|
3725
|
-
return;
|
|
3726
|
-
}
|
|
3727
|
-
const group = new Node15(ctx.base, "http", "main");
|
|
3728
|
-
const securityGroup = new aws15.ec2.SecurityGroup(group, "http", {
|
|
3729
|
-
vpcId: ctx.shared.get(`vpc-id`),
|
|
3730
|
-
name: formatGlobalResourceName(ctx.app.name, "http", "http"),
|
|
3731
|
-
description: `Global security group for HTTP api.`
|
|
3732
|
-
});
|
|
3733
|
-
const port = aws15.ec2.Port.tcp(443);
|
|
3734
|
-
securityGroup.addIngressRule({ port, peer: aws15.ec2.Peer.anyIpv4() });
|
|
3735
|
-
securityGroup.addIngressRule({ port, peer: aws15.ec2.Peer.anyIpv6() });
|
|
3736
|
-
for (const [id, props] of Object.entries(ctx.appConfig.defaults?.http ?? {})) {
|
|
3737
|
-
const group2 = new Node15(ctx.base, "http", id);
|
|
3738
|
-
const loadBalancer = new aws15.elb.LoadBalancer(group2, "balancer", {
|
|
3739
|
-
name: formatGlobalResourceName(ctx.app.name, "http", id),
|
|
3740
|
-
type: "application",
|
|
3741
|
-
securityGroups: [securityGroup.id],
|
|
3742
|
-
subnets: [
|
|
3743
|
-
//
|
|
3744
|
-
ctx.shared.get(`vpc-public-subnet-id-1`),
|
|
3745
|
-
ctx.shared.get(`vpc-public-subnet-id-2`)
|
|
3746
|
-
]
|
|
3747
|
-
});
|
|
3748
|
-
const listener = new aws15.elb.Listener(group2, "listener", {
|
|
3749
|
-
loadBalancerArn: loadBalancer.arn,
|
|
3750
|
-
port: 443,
|
|
3751
|
-
protocol: "https",
|
|
3752
|
-
certificates: [ctx.shared.get(`local-certificate-${props.domain}-arn`)],
|
|
3753
|
-
defaultActions: [
|
|
3754
|
-
aws15.elb.ListenerAction.fixedResponse({
|
|
3755
|
-
statusCode: 404,
|
|
3756
|
-
contentType: "application/json",
|
|
3757
|
-
messageBody: JSON.stringify({
|
|
3758
|
-
message: "Route not found"
|
|
3759
|
-
})
|
|
3760
|
-
})
|
|
3761
|
-
]
|
|
3762
|
-
});
|
|
3763
|
-
ctx.shared.set(`http-${id}-listener-arn`, listener.arn);
|
|
3764
|
-
const domainName = formatFullDomainName(ctx.appConfig, props.domain, props.subDomain);
|
|
3765
|
-
new aws15.route53.RecordSet(group2, domainName, {
|
|
3766
|
-
hostedZoneId: ctx.shared.get(`hosted-zone-${props.domain}-id`),
|
|
3767
|
-
name: domainName,
|
|
3768
|
-
type: "A",
|
|
3769
|
-
alias: {
|
|
3770
|
-
evaluateTargetHealth: false,
|
|
3771
|
-
hostedZoneId: loadBalancer.hostedZoneId,
|
|
3772
|
-
dnsName: loadBalancer.dnsName
|
|
3773
|
-
}
|
|
3774
|
-
});
|
|
3775
|
-
ctx.bindEnv(`HTTP_${constantCase6(id)}_ENDPOINT`, domainName);
|
|
3776
|
-
}
|
|
3777
|
-
},
|
|
3778
|
-
onStack(ctx) {
|
|
3779
|
-
for (const [id, routes] of Object.entries(ctx.stackConfig.http ?? {})) {
|
|
3780
|
-
const props = ctx.appConfig.defaults.http?.[id];
|
|
3781
|
-
if (!props) {
|
|
3782
|
-
throw new Error(`Http definition is not defined on app level for "${id}"`);
|
|
3783
|
-
}
|
|
3784
|
-
const group = new Node15(ctx.stack, "http", id);
|
|
3785
|
-
for (const [routeKey, routeProps] of Object.entries(routes)) {
|
|
3786
|
-
const routeGroup = new Node15(group, "route", routeKey);
|
|
3787
|
-
const { method, path } = parseRoute(routeKey);
|
|
3788
|
-
const routeId = shortId(routeKey);
|
|
3789
|
-
const { lambda } = createLambdaFunction(routeGroup, ctx, "http", `${id}-${routeId}`, {
|
|
3790
|
-
...routeProps,
|
|
3791
|
-
description: routeKey
|
|
3792
|
-
});
|
|
3793
|
-
const name = formatLocalResourceName(ctx.app.name, ctx.stack.name, "http", routeId);
|
|
3794
|
-
const permission = new aws15.lambda.Permission(routeGroup, id, {
|
|
3795
|
-
action: "lambda:InvokeFunction",
|
|
3796
|
-
principal: "elasticloadbalancing.amazonaws.com",
|
|
3797
|
-
functionArn: lambda.arn
|
|
3798
|
-
// sourceArn: `arn:aws:elasticloadbalancing:${ctx.appConfig.region}:*:targetgroup/${name}/*`,
|
|
3799
|
-
});
|
|
3800
|
-
const target = new aws15.elb.TargetGroup(routeGroup, id, {
|
|
3801
|
-
name,
|
|
3802
|
-
type: "lambda",
|
|
3803
|
-
targets: [lambda.arn]
|
|
3804
|
-
}).dependsOn(permission);
|
|
3805
|
-
new aws15.elb.ListenerRule(routeGroup, id, {
|
|
3806
|
-
listenerArn: ctx.shared.get(`http-${id}-listener-arn`),
|
|
3807
|
-
priority: generatePriority(ctx.stackConfig.name, routeKey),
|
|
3808
|
-
conditions: [
|
|
3809
|
-
aws15.elb.ListenerCondition.httpRequestMethods([method]),
|
|
3810
|
-
aws15.elb.ListenerCondition.pathPatterns([path])
|
|
3811
|
-
],
|
|
3812
|
-
actions: [aws15.elb.ListenerAction.forward([target.arn])]
|
|
3813
|
-
}).dependsOn(target);
|
|
3814
|
-
}
|
|
3815
|
-
}
|
|
3816
|
-
}
|
|
3817
|
-
});
|
|
3818
|
-
|
|
3819
|
-
// src/feature/search/index.ts
|
|
3820
|
-
import { Node as Node16, aws as aws16 } from "@awsless/formation";
|
|
3821
|
-
import { constantCase as constantCase7 } from "change-case";
|
|
3822
|
-
var typeGenCode6 = `
|
|
3823
|
-
import { AnyStruct, Table } from '@awsless/open-search'
|
|
3824
|
-
|
|
3825
|
-
type Search = {
|
|
3826
|
-
readonly domain: string
|
|
3827
|
-
readonly defineTable: <N extends stringm S extends AnyStruct>(tableName: N, schema: S) => Table<N, S>
|
|
3828
|
-
}
|
|
3829
|
-
`;
|
|
3830
|
-
var searchFeature = defineFeature({
|
|
3831
|
-
name: "search",
|
|
3832
|
-
async onTypeGen(ctx) {
|
|
3833
|
-
const gen = new TypeFile("@awsless/awsless");
|
|
3834
|
-
const resources = new TypeObject(1);
|
|
3835
|
-
for (const stack of ctx.stackConfigs) {
|
|
3836
|
-
const list4 = new TypeObject(2);
|
|
3837
|
-
for (const id of Object.keys(stack.searchs ?? {})) {
|
|
3838
|
-
list4.addType(id, `Search`);
|
|
3839
|
-
}
|
|
3840
|
-
resources.addType(stack.name, list4);
|
|
3841
|
-
}
|
|
3842
|
-
gen.addCode(typeGenCode6);
|
|
3843
|
-
gen.addInterface("SearchResources", resources);
|
|
3844
|
-
await ctx.write("search.d.ts", gen, true);
|
|
3640
|
+
gen.addCode(typeGenCode4);
|
|
3641
|
+
gen.addInterface("SearchResources", resources);
|
|
3642
|
+
await ctx.write("search.d.ts", gen, true);
|
|
3845
3643
|
},
|
|
3846
3644
|
onStack(ctx) {
|
|
3847
3645
|
for (const [id, props] of Object.entries(ctx.stackConfig.searchs ?? {})) {
|
|
3848
|
-
const group = new
|
|
3849
|
-
const openSearch = new
|
|
3646
|
+
const group = new Node13(ctx.stack, "search", id);
|
|
3647
|
+
const openSearch = new aws13.openSearch.Domain(group, "domain", {
|
|
3850
3648
|
// name: formatLocalResourceName(ctx.app.name, ctx.stack.name, this.name, id),
|
|
3851
3649
|
version: props.version,
|
|
3852
3650
|
storageSize: props.storage,
|
|
@@ -3874,7 +3672,7 @@ var searchFeature = defineFeature({
|
|
|
3874
3672
|
}
|
|
3875
3673
|
ctx.onFunction(({ lambda, policy }) => {
|
|
3876
3674
|
lambda.addEnvironment(
|
|
3877
|
-
`SEARCH_${
|
|
3675
|
+
`SEARCH_${constantCase8(ctx.stack.name)}_${constantCase8(id)}_DOMAIN`,
|
|
3878
3676
|
openSearch.domainEndpoint
|
|
3879
3677
|
);
|
|
3880
3678
|
policy.addStatement({
|
|
@@ -3887,7 +3685,7 @@ var searchFeature = defineFeature({
|
|
|
3887
3685
|
});
|
|
3888
3686
|
|
|
3889
3687
|
// src/feature/site/index.ts
|
|
3890
|
-
import { Asset as Asset3, Node as
|
|
3688
|
+
import { Asset as Asset3, Node as Node14, aws as aws14 } from "@awsless/formation";
|
|
3891
3689
|
import { days as days3, seconds as seconds3 } from "@awsless/duration";
|
|
3892
3690
|
import { glob as glob2 } from "glob";
|
|
3893
3691
|
import { join as join8 } from "path";
|
|
@@ -3917,7 +3715,7 @@ var siteFeature = defineFeature({
|
|
|
3917
3715
|
name: "site",
|
|
3918
3716
|
onStack(ctx) {
|
|
3919
3717
|
for (const [id, props] of Object.entries(ctx.stackConfig.sites ?? {})) {
|
|
3920
|
-
const group = new
|
|
3718
|
+
const group = new Node14(ctx.stack, "site", id);
|
|
3921
3719
|
const name = formatLocalResourceName(ctx.app.name, ctx.stack.name, "site", id);
|
|
3922
3720
|
const origins = [];
|
|
3923
3721
|
const originGroups = [];
|
|
@@ -3927,7 +3725,7 @@ var siteFeature = defineFeature({
|
|
|
3927
3725
|
const { lambda, code } = createLambdaFunction(group, ctx, `site`, id, props.ssr);
|
|
3928
3726
|
versions.push(code.version);
|
|
3929
3727
|
ctx.registerSiteFunction(lambda);
|
|
3930
|
-
new
|
|
3728
|
+
new aws14.lambda.Permission(group, "permission", {
|
|
3931
3729
|
principal: "*",
|
|
3932
3730
|
// principal: 'cloudfront.amazonaws.com',
|
|
3933
3731
|
action: "lambda:InvokeFunctionUrl",
|
|
@@ -3936,7 +3734,7 @@ var siteFeature = defineFeature({
|
|
|
3936
3734
|
// urlAuthType: 'aws-iam',
|
|
3937
3735
|
// sourceArn: distribution.arn,
|
|
3938
3736
|
});
|
|
3939
|
-
const url = new
|
|
3737
|
+
const url = new aws14.lambda.Url(group, "url", {
|
|
3940
3738
|
targetArn: lambda.arn,
|
|
3941
3739
|
authType: "none"
|
|
3942
3740
|
// authType: 'aws-iam',
|
|
@@ -3948,7 +3746,7 @@ var siteFeature = defineFeature({
|
|
|
3948
3746
|
});
|
|
3949
3747
|
}
|
|
3950
3748
|
if (props.static) {
|
|
3951
|
-
bucket = new
|
|
3749
|
+
bucket = new aws14.s3.Bucket(group, "bucket", {
|
|
3952
3750
|
name,
|
|
3953
3751
|
forceDelete: true,
|
|
3954
3752
|
website: {
|
|
@@ -3965,7 +3763,7 @@ var siteFeature = defineFeature({
|
|
|
3965
3763
|
]
|
|
3966
3764
|
});
|
|
3967
3765
|
bucket.deletionPolicy = "after-deployment";
|
|
3968
|
-
const accessControl = new
|
|
3766
|
+
const accessControl = new aws14.cloudFront.OriginAccessControl(group, `access`, {
|
|
3969
3767
|
name,
|
|
3970
3768
|
type: "s3",
|
|
3971
3769
|
behavior: "always",
|
|
@@ -3977,7 +3775,7 @@ var siteFeature = defineFeature({
|
|
|
3977
3775
|
nodir: true
|
|
3978
3776
|
});
|
|
3979
3777
|
for (const file of files) {
|
|
3980
|
-
const object = new
|
|
3778
|
+
const object = new aws14.s3.BucketObject(group, file, {
|
|
3981
3779
|
bucket: bucket.name,
|
|
3982
3780
|
key: file,
|
|
3983
3781
|
body: Asset3.fromFile(join8(props.static, file)),
|
|
@@ -4000,14 +3798,14 @@ var siteFeature = defineFeature({
|
|
|
4000
3798
|
statusCodes: [403, 404]
|
|
4001
3799
|
});
|
|
4002
3800
|
}
|
|
4003
|
-
const cache = new
|
|
3801
|
+
const cache = new aws14.cloudFront.CachePolicy(group, "cache", {
|
|
4004
3802
|
name,
|
|
4005
3803
|
minTtl: seconds3(1),
|
|
4006
3804
|
maxTtl: days3(365),
|
|
4007
3805
|
defaultTtl: days3(1),
|
|
4008
3806
|
...props.cache
|
|
4009
3807
|
});
|
|
4010
|
-
const originRequest = new
|
|
3808
|
+
const originRequest = new aws14.cloudFront.OriginRequestPolicy(group, "request", {
|
|
4011
3809
|
name,
|
|
4012
3810
|
header: {
|
|
4013
3811
|
behavior: "all-except",
|
|
@@ -4015,7 +3813,7 @@ var siteFeature = defineFeature({
|
|
|
4015
3813
|
}
|
|
4016
3814
|
});
|
|
4017
3815
|
const domainName = formatFullDomainName(ctx.appConfig, props.domain, props.subDomain);
|
|
4018
|
-
const responseHeaders = new
|
|
3816
|
+
const responseHeaders = new aws14.cloudFront.ResponseHeadersPolicy(group, "response", {
|
|
4019
3817
|
name,
|
|
4020
3818
|
cors: props.cors,
|
|
4021
3819
|
remove: ["server"]
|
|
@@ -4023,7 +3821,7 @@ var siteFeature = defineFeature({
|
|
|
4023
3821
|
// override: true,
|
|
4024
3822
|
// },
|
|
4025
3823
|
});
|
|
4026
|
-
const distribution = new
|
|
3824
|
+
const distribution = new aws14.cloudFront.Distribution(group, "distribution", {
|
|
4027
3825
|
name,
|
|
4028
3826
|
certificateArn: ctx.shared.get(`global-certificate-${props.domain}-arn`),
|
|
4029
3827
|
compress: true,
|
|
@@ -4051,13 +3849,13 @@ var siteFeature = defineFeature({
|
|
|
4051
3849
|
};
|
|
4052
3850
|
})
|
|
4053
3851
|
});
|
|
4054
|
-
new
|
|
3852
|
+
new aws14.cloudFront.InvalidateCache(group, "invalidate", {
|
|
4055
3853
|
distributionId: distribution.id,
|
|
4056
3854
|
paths: ["/*"],
|
|
4057
3855
|
versions
|
|
4058
3856
|
});
|
|
4059
3857
|
if (props.static) {
|
|
4060
|
-
new
|
|
3858
|
+
new aws14.s3.BucketPolicy(group, `policy`, {
|
|
4061
3859
|
bucketName: bucket.name,
|
|
4062
3860
|
statements: [
|
|
4063
3861
|
{
|
|
@@ -4083,7 +3881,7 @@ var siteFeature = defineFeature({
|
|
|
4083
3881
|
]
|
|
4084
3882
|
});
|
|
4085
3883
|
}
|
|
4086
|
-
new
|
|
3884
|
+
new aws14.route53.RecordSet(group, `record`, {
|
|
4087
3885
|
hostedZoneId: ctx.shared.get(`hosted-zone-${props.domain}-id`),
|
|
4088
3886
|
type: "A",
|
|
4089
3887
|
name: domainName,
|
|
@@ -4097,88 +3895,142 @@ var siteFeature = defineFeature({
|
|
|
4097
3895
|
}
|
|
4098
3896
|
});
|
|
4099
3897
|
|
|
4100
|
-
// src/feature/
|
|
4101
|
-
import {
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
3898
|
+
// src/feature/store/index.ts
|
|
3899
|
+
import { aws as aws15, Node as Node15 } from "@awsless/formation";
|
|
3900
|
+
var typeGenCode5 = `
|
|
3901
|
+
import { Body, PutObjectProps, BodyStream, createPresignedPost } from '@awsless/s3'
|
|
3902
|
+
import { Size } from '@awsless/size'
|
|
3903
|
+
import { Duration } from '@awsless/duration'
|
|
3904
|
+
import { PresignedPost } from '@aws-sdk/s3-presigned-post'
|
|
3905
|
+
|
|
3906
|
+
type Store<Name extends string> = {
|
|
3907
|
+
readonly name: Name
|
|
3908
|
+
readonly put: (key: string, body: Body, options?: Pick<PutObjectProps, 'metadata' | 'storageClass'>) => Promise<void>
|
|
3909
|
+
readonly get: (key: string) => Promise<BodyStream | undefined>
|
|
3910
|
+
readonly delete: (key: string) => Promise<void>
|
|
3911
|
+
readonly createPresignedPost: (key: string, contentLengthRange: [Size, Size], expires?: Duration, fields?: Record<string, string>) => Promise<PresignedPost>
|
|
3912
|
+
}
|
|
3913
|
+
`;
|
|
3914
|
+
var storeFeature = defineFeature({
|
|
3915
|
+
name: "store",
|
|
3916
|
+
async onTypeGen(ctx) {
|
|
3917
|
+
const gen = new TypeFile("@awsless/awsless");
|
|
3918
|
+
const resources = new TypeObject(1);
|
|
3919
|
+
for (const stack of ctx.stackConfigs) {
|
|
3920
|
+
const list4 = new TypeObject(2);
|
|
3921
|
+
for (const id of Object.keys(stack.stores ?? {})) {
|
|
3922
|
+
const storeName = formatLocalResourceName(ctx.appConfig.name, stack.name, "store", id);
|
|
3923
|
+
list4.addType(id, `Store<'${storeName}'>`);
|
|
3924
|
+
}
|
|
3925
|
+
resources.addType(stack.name, list4);
|
|
3926
|
+
}
|
|
3927
|
+
gen.addCode(typeGenCode5);
|
|
3928
|
+
gen.addInterface("StoreResources", resources);
|
|
3929
|
+
await ctx.write("store.d.ts", gen, true);
|
|
3930
|
+
},
|
|
3931
|
+
onStack(ctx) {
|
|
3932
|
+
for (const [id, props] of Object.entries(ctx.stackConfig.stores ?? {})) {
|
|
3933
|
+
const group = new Node15(ctx.stack, "store", id);
|
|
3934
|
+
const bucketName = formatLocalResourceName(ctx.appConfig.name, ctx.stack.name, "store", id);
|
|
3935
|
+
const lambdaConfigs = [];
|
|
3936
|
+
const eventMap = {
|
|
3937
|
+
"created:*": "s3:ObjectCreated:*",
|
|
3938
|
+
"created:put": "s3:ObjectCreated:Put",
|
|
3939
|
+
"created:post": "s3:ObjectCreated:Post",
|
|
3940
|
+
"created:copy": "s3:ObjectCreated:Copy",
|
|
3941
|
+
"created:upload": "s3:ObjectCreated:CompleteMultipartUpload",
|
|
3942
|
+
"removed:*": "s3:ObjectRemoved:*",
|
|
3943
|
+
"removed:delete": "s3:ObjectRemoved:Delete",
|
|
3944
|
+
"removed:marker": "s3:ObjectRemoved:DeleteMarkerCreated"
|
|
3945
|
+
};
|
|
3946
|
+
for (const [event, funcProps] of Object.entries(props.events ?? {})) {
|
|
3947
|
+
const eventGroup = new Node15(group, "event", event);
|
|
3948
|
+
const { lambda } = createAsyncLambdaFunction(eventGroup, ctx, `store`, id, funcProps);
|
|
3949
|
+
new aws15.lambda.Permission(eventGroup, "permission", {
|
|
3950
|
+
action: "lambda:InvokeFunction",
|
|
3951
|
+
principal: "s3.amazonaws.com",
|
|
3952
|
+
functionArn: lambda.arn,
|
|
3953
|
+
sourceArn: `arn:aws:s3:::${bucketName}`
|
|
3954
|
+
});
|
|
3955
|
+
lambdaConfigs.push({
|
|
3956
|
+
event: eventMap[event],
|
|
3957
|
+
function: lambda.arn
|
|
3958
|
+
});
|
|
3959
|
+
}
|
|
3960
|
+
const bucket = new aws15.s3.Bucket(group, "store", {
|
|
3961
|
+
name: bucketName,
|
|
3962
|
+
versioning: props.versioning,
|
|
3963
|
+
lambdaConfigs,
|
|
3964
|
+
cors: [
|
|
3965
|
+
// ---------------------------------------------
|
|
3966
|
+
// Support for presigned post requests
|
|
3967
|
+
// ---------------------------------------------
|
|
3968
|
+
{
|
|
3969
|
+
origins: ["*"],
|
|
3970
|
+
methods: ["POST"]
|
|
3971
|
+
}
|
|
3972
|
+
// ---------------------------------------------
|
|
3973
|
+
]
|
|
3974
|
+
});
|
|
3975
|
+
ctx.onFunction(({ policy }) => {
|
|
3976
|
+
policy.addStatement(bucket.permissions);
|
|
3977
|
+
});
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3980
|
+
});
|
|
3981
|
+
|
|
3982
|
+
// src/feature/table/index.ts
|
|
3983
|
+
import { Node as Node16, aws as aws16 } from "@awsless/formation";
|
|
3984
|
+
var tableFeature = defineFeature({
|
|
3985
|
+
name: "table",
|
|
3986
|
+
async onTypeGen(ctx) {
|
|
3987
|
+
const gen = new TypeFile("@awsless/awsless");
|
|
3988
|
+
const resources = new TypeObject(1);
|
|
3989
|
+
for (const stack of ctx.stackConfigs) {
|
|
3990
|
+
const list4 = new TypeObject(2);
|
|
3991
|
+
for (const name of Object.keys(stack.tables || {})) {
|
|
3992
|
+
const tableName = formatLocalResourceName(ctx.appConfig.name, stack.name, "table", name);
|
|
3993
|
+
list4.addType(name, `'${tableName}'`);
|
|
4146
3994
|
}
|
|
3995
|
+
resources.addType(stack.name, list4);
|
|
4147
3996
|
}
|
|
3997
|
+
gen.addInterface("TableResources", resources);
|
|
3998
|
+
await ctx.write("table.d.ts", gen, true);
|
|
4148
3999
|
},
|
|
4149
4000
|
onStack(ctx) {
|
|
4150
|
-
for (const [id,
|
|
4151
|
-
const
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
const
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
type: "AWS_PROXY",
|
|
4171
|
-
uri: lambda.arn.apply((arn) => {
|
|
4172
|
-
return `arn:aws:apigateway:${ctx.appConfig.region}:lambda:path/2015-03-31/functions/${arn}/invocations`;
|
|
4173
|
-
})
|
|
4174
|
-
});
|
|
4175
|
-
const route = new aws18.apiGatewayV2.Route(group, "route", {
|
|
4176
|
-
apiId,
|
|
4177
|
-
routeKey,
|
|
4178
|
-
target: integration.id.apply((id2) => `integrations/${id2}`)
|
|
4001
|
+
for (const [id, props] of Object.entries(ctx.stackConfig.tables ?? {})) {
|
|
4002
|
+
const group = new Node16(ctx.stack, "table", id);
|
|
4003
|
+
const table2 = new aws16.dynamodb.Table(group, "table", {
|
|
4004
|
+
...props,
|
|
4005
|
+
name: formatLocalResourceName(ctx.appConfig.name, ctx.stackConfig.name, "table", id),
|
|
4006
|
+
stream: props.stream?.type
|
|
4007
|
+
});
|
|
4008
|
+
if (props.stream) {
|
|
4009
|
+
const { lambda, policy } = createLambdaFunction(group, ctx, "table", id, props.stream.consumer);
|
|
4010
|
+
lambda.addEnvironment("LOG_VIEWABLE_ERROR", "1");
|
|
4011
|
+
const onFailure = getGlobalOnFailure(ctx);
|
|
4012
|
+
const source = new aws16.lambda.EventSourceMapping(group, id, {
|
|
4013
|
+
functionArn: lambda.arn,
|
|
4014
|
+
sourceArn: table2.streamArn,
|
|
4015
|
+
batchSize: 100,
|
|
4016
|
+
bisectBatchOnError: true,
|
|
4017
|
+
// retryAttempts: props.stream.consumer.retryAttempts ?? -1,
|
|
4018
|
+
parallelizationFactor: 1,
|
|
4019
|
+
startingPosition: "latest",
|
|
4020
|
+
onFailure
|
|
4179
4021
|
});
|
|
4180
|
-
|
|
4022
|
+
policy.addStatement(table2.streamPermissions);
|
|
4023
|
+
source.dependsOn(policy);
|
|
4024
|
+
if (onFailure) {
|
|
4025
|
+
policy.addStatement({
|
|
4026
|
+
actions: ["sqs:SendMessage", "sqs:GetQueueUrl"],
|
|
4027
|
+
resources: [onFailure]
|
|
4028
|
+
});
|
|
4029
|
+
}
|
|
4181
4030
|
}
|
|
4031
|
+
ctx.onFunction(({ policy }) => {
|
|
4032
|
+
policy.addStatement(...table2.permissions);
|
|
4033
|
+
});
|
|
4182
4034
|
}
|
|
4183
4035
|
}
|
|
4184
4036
|
});
|
|
@@ -4186,8 +4038,8 @@ var restFeature = defineFeature({
|
|
|
4186
4038
|
// src/feature/task/index.ts
|
|
4187
4039
|
import { camelCase as camelCase6 } from "change-case";
|
|
4188
4040
|
import { relative as relative4 } from "path";
|
|
4189
|
-
import { Node as
|
|
4190
|
-
var
|
|
4041
|
+
import { Node as Node17 } from "@awsless/formation";
|
|
4042
|
+
var typeGenCode6 = `
|
|
4191
4043
|
import { InvokeOptions } from '@awsless/lambda'
|
|
4192
4044
|
import type { Mock } from 'vitest'
|
|
4193
4045
|
|
|
@@ -4226,7 +4078,7 @@ var taskFeature = defineFeature({
|
|
|
4226
4078
|
resources.addType(stack.name, resource2);
|
|
4227
4079
|
mockResponses.addType(stack.name, mockResponse);
|
|
4228
4080
|
}
|
|
4229
|
-
types2.addCode(
|
|
4081
|
+
types2.addCode(typeGenCode6);
|
|
4230
4082
|
types2.addInterface("TaskResources", resources);
|
|
4231
4083
|
types2.addInterface("TaskMock", mocks);
|
|
4232
4084
|
types2.addInterface("TaskMockResponse", mockResponses);
|
|
@@ -4234,12 +4086,160 @@ var taskFeature = defineFeature({
|
|
|
4234
4086
|
},
|
|
4235
4087
|
onStack(ctx) {
|
|
4236
4088
|
for (const [id, props] of Object.entries(ctx.stackConfig.tasks ?? {})) {
|
|
4237
|
-
const group = new
|
|
4089
|
+
const group = new Node17(ctx.stack, "task", id);
|
|
4238
4090
|
createAsyncLambdaFunction(group, ctx, "task", id, props.consumer);
|
|
4239
4091
|
}
|
|
4240
4092
|
}
|
|
4241
4093
|
});
|
|
4242
4094
|
|
|
4095
|
+
// src/feature/test/index.ts
|
|
4096
|
+
var testFeature = defineFeature({
|
|
4097
|
+
name: "test",
|
|
4098
|
+
onStack(ctx) {
|
|
4099
|
+
if (ctx.stackConfig.tests) {
|
|
4100
|
+
ctx.registerTest(ctx.stackConfig.name, ctx.stackConfig.tests);
|
|
4101
|
+
}
|
|
4102
|
+
}
|
|
4103
|
+
});
|
|
4104
|
+
|
|
4105
|
+
// src/feature/topic/index.ts
|
|
4106
|
+
import { Node as Node18, aws as aws17 } from "@awsless/formation";
|
|
4107
|
+
var typeGenCode7 = `
|
|
4108
|
+
import type { PublishOptions } from '@awsless/sns'
|
|
4109
|
+
import type { Mock } from 'vitest'
|
|
4110
|
+
|
|
4111
|
+
type Publish<Name extends string> = {
|
|
4112
|
+
readonly name: Name
|
|
4113
|
+
(payload: unknown, options?: Omit<PublishOptions, 'topic' | 'payload'>): Promise<void>
|
|
4114
|
+
}
|
|
4115
|
+
|
|
4116
|
+
type MockHandle = (payload: unknown) => void
|
|
4117
|
+
type MockBuilder = (handle?: MockHandle) => void
|
|
4118
|
+
`;
|
|
4119
|
+
var topicFeature = defineFeature({
|
|
4120
|
+
name: "topic",
|
|
4121
|
+
async onTypeGen(ctx) {
|
|
4122
|
+
const gen = new TypeFile("@awsless/awsless");
|
|
4123
|
+
const resources = new TypeObject(1);
|
|
4124
|
+
const mocks = new TypeObject(1);
|
|
4125
|
+
const mockResponses = new TypeObject(1);
|
|
4126
|
+
for (const stack of ctx.stackConfigs) {
|
|
4127
|
+
for (const topic of stack.topics || []) {
|
|
4128
|
+
const name = formatGlobalResourceName(ctx.appConfig.name, "topic", topic);
|
|
4129
|
+
mockResponses.addType(topic, "Mock");
|
|
4130
|
+
resources.addType(topic, `Publish<'${name}'>`);
|
|
4131
|
+
mocks.addType(topic, `MockBuilder`);
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
gen.addCode(typeGenCode7);
|
|
4135
|
+
gen.addInterface("TopicResources", resources);
|
|
4136
|
+
gen.addInterface("TopicMock", mocks);
|
|
4137
|
+
gen.addInterface("TopicMockResponse", mockResponses);
|
|
4138
|
+
await ctx.write("topic.d.ts", gen, true);
|
|
4139
|
+
},
|
|
4140
|
+
onApp(ctx) {
|
|
4141
|
+
for (const stack of ctx.stackConfigs) {
|
|
4142
|
+
for (const id of stack.topics ?? []) {
|
|
4143
|
+
const group = new Node18(ctx.base, "topic", id);
|
|
4144
|
+
const topic = new aws17.sns.Topic(group, "topic", {
|
|
4145
|
+
name: formatGlobalResourceName(ctx.appConfig.name, "topic", id)
|
|
4146
|
+
});
|
|
4147
|
+
ctx.shared.set(`topic-${id}-arn`, topic.arn);
|
|
4148
|
+
}
|
|
4149
|
+
}
|
|
4150
|
+
},
|
|
4151
|
+
onStack(ctx) {
|
|
4152
|
+
for (const id of ctx.stackConfig.topics ?? []) {
|
|
4153
|
+
ctx.onFunction(({ policy }) => {
|
|
4154
|
+
policy.addStatement({
|
|
4155
|
+
actions: ["sns:Publish"],
|
|
4156
|
+
resources: [ctx.shared.get(`topic-${id}-arn`)]
|
|
4157
|
+
});
|
|
4158
|
+
});
|
|
4159
|
+
}
|
|
4160
|
+
for (const [id, props] of Object.entries(ctx.stackConfig.subscribers ?? {})) {
|
|
4161
|
+
const group = new Node18(ctx.stack, "topic", id);
|
|
4162
|
+
const topicArn = ctx.shared.get(`topic-${id}-arn`);
|
|
4163
|
+
if (typeof props === "string" && isEmail(props)) {
|
|
4164
|
+
new aws17.sns.Subscription(group, id, {
|
|
4165
|
+
topicArn,
|
|
4166
|
+
protocol: "email",
|
|
4167
|
+
endpoint: props
|
|
4168
|
+
});
|
|
4169
|
+
} else if (typeof props === "object") {
|
|
4170
|
+
const { lambda } = createAsyncLambdaFunction(group, ctx, `topic`, id, props);
|
|
4171
|
+
new aws17.sns.Subscription(group, id, {
|
|
4172
|
+
topicArn,
|
|
4173
|
+
protocol: "lambda",
|
|
4174
|
+
endpoint: lambda.arn
|
|
4175
|
+
});
|
|
4176
|
+
new aws17.lambda.Permission(group, id, {
|
|
4177
|
+
action: "lambda:InvokeFunction",
|
|
4178
|
+
principal: "sns.amazonaws.com",
|
|
4179
|
+
functionArn: lambda.arn,
|
|
4180
|
+
sourceArn: topicArn
|
|
4181
|
+
});
|
|
4182
|
+
}
|
|
4183
|
+
}
|
|
4184
|
+
}
|
|
4185
|
+
});
|
|
4186
|
+
|
|
4187
|
+
// src/feature/vpc/index.ts
|
|
4188
|
+
import { Node as Node19, all, aws as aws18 } from "@awsless/formation";
|
|
4189
|
+
var vpcFeature = defineFeature({
|
|
4190
|
+
name: "vpc",
|
|
4191
|
+
onApp(ctx) {
|
|
4192
|
+
const group = new Node19(ctx.base, "vpc", "main");
|
|
4193
|
+
const vpc = new aws18.ec2.Vpc(group, "vpc", {
|
|
4194
|
+
name: ctx.app.name,
|
|
4195
|
+
cidrBlock: aws18.ec2.Peer.ipv4("10.0.0.0/16")
|
|
4196
|
+
});
|
|
4197
|
+
const privateRouteTable = new aws18.ec2.RouteTable(group, "private", {
|
|
4198
|
+
vpcId: vpc.id,
|
|
4199
|
+
name: "private"
|
|
4200
|
+
});
|
|
4201
|
+
const publicRouteTable = new aws18.ec2.RouteTable(group, "public", {
|
|
4202
|
+
vpcId: vpc.id,
|
|
4203
|
+
name: "public"
|
|
4204
|
+
});
|
|
4205
|
+
const gateway = new aws18.ec2.InternetGateway(group, "gateway");
|
|
4206
|
+
const attachment = new aws18.ec2.VPCGatewayAttachment(group, "attachment", {
|
|
4207
|
+
vpcId: vpc.id,
|
|
4208
|
+
internetGatewayId: gateway.id
|
|
4209
|
+
});
|
|
4210
|
+
new aws18.ec2.Route(group, "route", {
|
|
4211
|
+
gatewayId: gateway.id,
|
|
4212
|
+
routeTableId: publicRouteTable.id,
|
|
4213
|
+
destination: aws18.ec2.Peer.anyIpv4()
|
|
4214
|
+
});
|
|
4215
|
+
ctx.shared.set(
|
|
4216
|
+
"vpc-id",
|
|
4217
|
+
// Some resources require the internet gateway to be attached.
|
|
4218
|
+
all([vpc.id, attachment.internetGatewayId]).apply(([id]) => id)
|
|
4219
|
+
);
|
|
4220
|
+
ctx.shared.set("vpc-security-group-id", vpc.defaultSecurityGroup);
|
|
4221
|
+
const zones = ["a", "b"];
|
|
4222
|
+
const tables = [privateRouteTable, publicRouteTable];
|
|
4223
|
+
let block = 0;
|
|
4224
|
+
for (const table2 of tables) {
|
|
4225
|
+
for (const i in zones) {
|
|
4226
|
+
const index = Number(i) + 1;
|
|
4227
|
+
const id = `${table2.identifier}-${index}`;
|
|
4228
|
+
const subnet = new aws18.ec2.Subnet(group, id, {
|
|
4229
|
+
vpcId: vpc.id,
|
|
4230
|
+
cidrBlock: aws18.ec2.Peer.ipv4(`10.0.${block++}.0/24`),
|
|
4231
|
+
availabilityZone: ctx.appConfig.region + zones[i]
|
|
4232
|
+
});
|
|
4233
|
+
new aws18.ec2.SubnetRouteTableAssociation(group, id, {
|
|
4234
|
+
routeTableId: table2.id,
|
|
4235
|
+
subnetId: subnet.id
|
|
4236
|
+
});
|
|
4237
|
+
ctx.shared.set(`vpc-${table2.identifier}-subnet-id-${index}`, subnet.id);
|
|
4238
|
+
}
|
|
4239
|
+
}
|
|
4240
|
+
}
|
|
4241
|
+
});
|
|
4242
|
+
|
|
4243
4243
|
// src/feature/index.ts
|
|
4244
4244
|
var features = [
|
|
4245
4245
|
// 1
|