@awsless/awsless 0.0.502 โ 0.0.503
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/README.MD +10 -12
- package/dist/bin.js +162 -43
- package/dist/prebuild/images/HASH +1 -1
- package/dist/prebuild/images/bundle.zip +0 -0
- package/dist/prebuild/rpc/HASH +1 -1
- package/dist/prebuild/rpc/bundle.zip +0 -0
- package/dist/server.js +3 -3
- package/dist/test-global-setup.js +2 -0
- package/package.json +14 -14
package/README.MD
CHANGED
|
@@ -4,10 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.org/package/@awsless/awsless)
|
|
6
6
|
|
|
7
|
-
|
|
8
7
|
[](https://npm-stat.com/charts.html?package=@awsless/awsless)
|
|
9
8
|
|
|
10
|
-
|
|
11
9
|
</div>
|
|
12
10
|
|
|
13
11
|
## Table of Contents
|
|
@@ -83,7 +81,7 @@ $ pnpm i @awsless/awsless
|
|
|
83
81
|
|
|
84
82
|
|
|
85
83
|
## Getting Started
|
|
86
|
-
In an
|
|
84
|
+
In an Awsless project, your infrastructure is organized into modular stack files, each with its own purpose, and a shared base configuration. Before you begin, make sure you have the AWS CLI installed.
|
|
87
85
|
|
|
88
86
|
|
|
89
87
|
### Base Configuration
|
|
@@ -161,7 +159,7 @@ $ pnpm awsless deploy base
|
|
|
161
159
|
```
|
|
162
160
|
|
|
163
161
|
### Stack Updates
|
|
164
|
-
|
|
162
|
+
Awsless makes it simple to remove infrastructure when it's no longer needed.
|
|
165
163
|
|
|
166
164
|
๐งผ Delete a Specific Stack
|
|
167
165
|
```bash
|
|
@@ -175,7 +173,7 @@ pnpm awsless delete
|
|
|
175
173
|
โ ๏ธ This will remove all deployed resources associated with your stacks. Use with caution!
|
|
176
174
|
|
|
177
175
|
โป๏ธ Updating or Replacing Resources
|
|
178
|
-
When you modify a stack file and run a deployment again,
|
|
176
|
+
When you modify a stack file and run a deployment again, Awsless will:
|
|
179
177
|
|
|
180
178
|
* Update existing resources if changes are detected.
|
|
181
179
|
|
|
@@ -186,7 +184,7 @@ When you modify a stack file and run a deployment again, AWSless will:
|
|
|
186
184
|
* This ensures your infrastructure always reflects the current state of your stack configuration โ no manual cleanup required.
|
|
187
185
|
|
|
188
186
|
### Smart Helpers: Call Your Infra Like Functions
|
|
189
|
-
Once your stacks are defined,
|
|
187
|
+
Once your stacks are defined, Awsless provides built-in helpers like Fn, Queue, and Task to let you interact with your infrastructure directly from your application code โ no wiring or manual setup needed.
|
|
190
188
|
|
|
191
189
|
To enable full type support for these resources, run:
|
|
192
190
|
```bash
|
|
@@ -212,7 +210,7 @@ await Queue.notifications.send({ userId: 123 })
|
|
|
212
210
|
|
|
213
211
|
|
|
214
212
|
## Testing Stacks
|
|
215
|
-
|
|
213
|
+
Awsless supports built-in testing for your stacks to ensure everything works as expected before deployment.
|
|
216
214
|
|
|
217
215
|
You can define test folder in each stack
|
|
218
216
|
```json
|
|
@@ -240,7 +238,7 @@ $ pnpm awsless test rate
|
|
|
240
238
|
This will look for a defined test folder inside the rate stack directory and run any defined tests inside here.
|
|
241
239
|
|
|
242
240
|
๐ก๏ธ Tests Run Automatically Before Deploy
|
|
243
|
-
Whenever you run `pnpm awsless deploy`,
|
|
241
|
+
Whenever you run `pnpm awsless deploy`, Awsless will automatically:
|
|
244
242
|
|
|
245
243
|
* Check if a test/ folder exists for each stack
|
|
246
244
|
|
|
@@ -273,7 +271,7 @@ You can also customize your Lambda function with additional parameters like memo
|
|
|
273
271
|
```
|
|
274
272
|
|
|
275
273
|
### Task
|
|
276
|
-
A Task in
|
|
274
|
+
A Task in Awsless is an asynchronous Lambda function designed for background processing. You can trigger a task and immediately move on โ Awsless handles the execution in the background, including retries and logging on failure.
|
|
277
275
|
|
|
278
276
|
|
|
279
277
|
#### Basic usage
|
|
@@ -298,7 +296,7 @@ You can also customize your Lambda function with additional parameters like memo
|
|
|
298
296
|
```
|
|
299
297
|
|
|
300
298
|
### Table
|
|
301
|
-
The tables feature in
|
|
299
|
+
The tables feature in Awsless allows you to define fully managed, serverless DynamoDB tables directly in your stack configuration.
|
|
302
300
|
|
|
303
301
|
|
|
304
302
|
#### Usage
|
|
@@ -358,7 +356,7 @@ This allows you to define a topic along with the subscriber lambda.
|
|
|
358
356
|
```
|
|
359
357
|
|
|
360
358
|
### Crons
|
|
361
|
-
|
|
359
|
+
Awsless uses AWS EventBridge to provide fully managed, serverless cron jobs โ perfect for running scheduled tasks like cleanups, reports, or recurring syncs.
|
|
362
360
|
|
|
363
361
|
#### Usage
|
|
364
362
|
```json
|
|
@@ -379,7 +377,7 @@ AWSless uses AWS EventBridge to provide fully managed, serverless cron jobs โ
|
|
|
379
377
|
|
|
380
378
|
|
|
381
379
|
#### RPC
|
|
382
|
-
|
|
380
|
+
Awsless allows you to easily expose any Lambda function as a type-safe RPC (Remote Procedure Call) endpoint for your frontend.
|
|
383
381
|
|
|
384
382
|
The request and response types are automatically inferred from your Lambda function's payload and return value, giving you end-to-end type safety.
|
|
385
383
|
|
package/dist/bin.js
CHANGED
|
@@ -3220,26 +3220,53 @@ var createLambdaFunction = (parentGroup, ctx, ns, id, local) => {
|
|
|
3220
3220
|
statements.push(...local.permissions);
|
|
3221
3221
|
}
|
|
3222
3222
|
if (props.warm) {
|
|
3223
|
-
const
|
|
3223
|
+
const scheduleRole = new $3.aws.iam.Role(group, "warm", {
|
|
3224
3224
|
name: `${shortName}--warm`,
|
|
3225
|
-
description: name
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3225
|
+
description: `${name} warmer`,
|
|
3226
|
+
assumeRolePolicy: JSON.stringify({
|
|
3227
|
+
Version: "2012-10-17",
|
|
3228
|
+
Statement: [
|
|
3229
|
+
{
|
|
3230
|
+
Action: "sts:AssumeRole",
|
|
3231
|
+
Effect: "Allow",
|
|
3232
|
+
Principal: {
|
|
3233
|
+
Service: "scheduler.amazonaws.com"
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
]
|
|
3237
|
+
}),
|
|
3238
|
+
inlinePolicy: [
|
|
3239
|
+
{
|
|
3240
|
+
name: "InvokeFunction",
|
|
3241
|
+
policy: lambda.arn.pipe(
|
|
3242
|
+
(arn) => JSON.stringify({
|
|
3243
|
+
Version: "2012-10-17",
|
|
3244
|
+
Statement: [
|
|
3245
|
+
{
|
|
3246
|
+
Action: ["lambda:InvokeFunction"],
|
|
3247
|
+
Effect: "Allow",
|
|
3248
|
+
Resource: arn
|
|
3249
|
+
}
|
|
3250
|
+
]
|
|
3251
|
+
})
|
|
3252
|
+
)
|
|
3253
|
+
}
|
|
3254
|
+
]
|
|
3237
3255
|
});
|
|
3238
|
-
new $3.aws.
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3256
|
+
new $3.aws.scheduler.Schedule(group, "warm", {
|
|
3257
|
+
name: shortName,
|
|
3258
|
+
groupName: ctx.shared.get("function", "warm-group-name"),
|
|
3259
|
+
description: `${name} warmer`,
|
|
3260
|
+
scheduleExpression: "rate(5 minutes)",
|
|
3261
|
+
flexibleTimeWindow: { mode: "OFF" },
|
|
3262
|
+
target: {
|
|
3263
|
+
arn: lambda.arn,
|
|
3264
|
+
roleArn: scheduleRole.arn,
|
|
3265
|
+
input: JSON.stringify({
|
|
3266
|
+
warmer: true,
|
|
3267
|
+
concurrency: props.warm
|
|
3268
|
+
})
|
|
3269
|
+
}
|
|
3243
3270
|
});
|
|
3244
3271
|
}
|
|
3245
3272
|
return {
|
|
@@ -3257,7 +3284,7 @@ var createLambdaFunction = (parentGroup, ctx, ns, id, local) => {
|
|
|
3257
3284
|
};
|
|
3258
3285
|
};
|
|
3259
3286
|
var createAsyncLambdaFunction = (group, ctx, ns, id, local) => {
|
|
3260
|
-
const result = createLambdaFunction(group, ctx, ns, id, local);
|
|
3287
|
+
const result = createLambdaFunction(group, ctx, ns, id, { ...local, warm: 0 });
|
|
3261
3288
|
const props = deepmerge(ctx.appConfig.defaults.function, local);
|
|
3262
3289
|
result.setEnvironment("LOG_VIEWABLE_ERROR", "1");
|
|
3263
3290
|
const onFailure = getGlobalOnFailure(ctx);
|
|
@@ -3287,36 +3314,77 @@ var createAsyncLambdaFunction = (group, ctx, ns, id, local) => {
|
|
|
3287
3314
|
// src/feature/cron/index.ts
|
|
3288
3315
|
var cronFeature = defineFeature({
|
|
3289
3316
|
name: "cron",
|
|
3317
|
+
onApp(ctx) {
|
|
3318
|
+
const found = ctx.stackConfigs.find((stackConfig) => Object.keys(stackConfig.crons ?? {}).length > 0);
|
|
3319
|
+
if (found) {
|
|
3320
|
+
const group = new $4.aws.scheduler.ScheduleGroup(ctx.base, "cron", {
|
|
3321
|
+
name: formatGlobalResourceName({
|
|
3322
|
+
appName: ctx.app.name,
|
|
3323
|
+
resourceType: "cron",
|
|
3324
|
+
resourceName: "group"
|
|
3325
|
+
}),
|
|
3326
|
+
tags: {
|
|
3327
|
+
app: ctx.app.name
|
|
3328
|
+
}
|
|
3329
|
+
});
|
|
3330
|
+
ctx.shared.set("cron", "group-name", group.name);
|
|
3331
|
+
}
|
|
3332
|
+
},
|
|
3290
3333
|
onStack(ctx) {
|
|
3291
3334
|
for (const [id, props] of Object.entries(ctx.stackConfig.crons ?? {})) {
|
|
3292
3335
|
const group = new Group4(ctx.stack, "cron", id);
|
|
3293
|
-
const { lambda } = createAsyncLambdaFunction(group, ctx, "cron", id,
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3336
|
+
const { lambda } = createAsyncLambdaFunction(group, ctx, "cron", id, props.consumer);
|
|
3337
|
+
const name = formatLocalResourceName({
|
|
3338
|
+
appName: ctx.app.name,
|
|
3339
|
+
stackName: ctx.stack.name,
|
|
3340
|
+
resourceType: "cron",
|
|
3341
|
+
resourceName: shortId(id)
|
|
3297
3342
|
});
|
|
3298
|
-
const
|
|
3299
|
-
name
|
|
3300
|
-
appName: ctx.app.name,
|
|
3301
|
-
stackName: ctx.stack.name,
|
|
3302
|
-
resourceType: "cron",
|
|
3303
|
-
resourceName: shortId(id)
|
|
3304
|
-
}),
|
|
3343
|
+
const scheduleRole = new $4.aws.iam.Role(group, "warm", {
|
|
3344
|
+
name,
|
|
3305
3345
|
description: `Cron ${ctx.stack.name} ${id}`,
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3346
|
+
assumeRolePolicy: JSON.stringify({
|
|
3347
|
+
Version: "2012-10-17",
|
|
3348
|
+
Statement: [
|
|
3349
|
+
{
|
|
3350
|
+
Action: "sts:AssumeRole",
|
|
3351
|
+
Effect: "Allow",
|
|
3352
|
+
Principal: {
|
|
3353
|
+
Service: "scheduler.amazonaws.com"
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
]
|
|
3357
|
+
}),
|
|
3358
|
+
inlinePolicy: [
|
|
3359
|
+
{
|
|
3360
|
+
name: "InvokeFunction",
|
|
3361
|
+
policy: lambda.arn.pipe(
|
|
3362
|
+
(arn) => JSON.stringify({
|
|
3363
|
+
Version: "2012-10-17",
|
|
3364
|
+
Statement: [
|
|
3365
|
+
{
|
|
3366
|
+
Action: ["lambda:InvokeFunction"],
|
|
3367
|
+
Effect: "Allow",
|
|
3368
|
+
Resource: arn
|
|
3369
|
+
}
|
|
3370
|
+
]
|
|
3371
|
+
})
|
|
3372
|
+
)
|
|
3373
|
+
}
|
|
3374
|
+
]
|
|
3314
3375
|
});
|
|
3315
|
-
new $4.aws.
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3376
|
+
new $4.aws.scheduler.Schedule(group, "warm", {
|
|
3377
|
+
name,
|
|
3378
|
+
state: props.enabled ? "ENABLED" : "DISABLED",
|
|
3379
|
+
groupName: ctx.shared.get("cron", "group-name"),
|
|
3380
|
+
description: `${ctx.stack.name} ${id}`,
|
|
3381
|
+
scheduleExpression: props.schedule,
|
|
3382
|
+
flexibleTimeWindow: { mode: "OFF" },
|
|
3383
|
+
target: {
|
|
3384
|
+
arn: lambda.arn,
|
|
3385
|
+
roleArn: scheduleRole.arn,
|
|
3386
|
+
input: JSON.stringify(props.payload)
|
|
3387
|
+
}
|
|
3320
3388
|
});
|
|
3321
3389
|
}
|
|
3322
3390
|
}
|
|
@@ -3590,6 +3658,14 @@ var functionFeature = defineFeature({
|
|
|
3590
3658
|
// forceDelete: true,
|
|
3591
3659
|
});
|
|
3592
3660
|
ctx.shared.set("function", "bucket-name", bucket.bucket);
|
|
3661
|
+
const warmGroup = new $6.aws.scheduler.ScheduleGroup(ctx.base, "warm", {
|
|
3662
|
+
name: formatGlobalResourceName({
|
|
3663
|
+
appName: ctx.app.name,
|
|
3664
|
+
resourceType: "function",
|
|
3665
|
+
resourceName: "warm"
|
|
3666
|
+
})
|
|
3667
|
+
});
|
|
3668
|
+
ctx.shared.set("function", "warm-group-name", warmGroup.name);
|
|
3593
3669
|
},
|
|
3594
3670
|
onApp(ctx) {
|
|
3595
3671
|
ctx.addGlobalPermission({
|
|
@@ -5523,6 +5599,49 @@ var taskFeature = defineFeature({
|
|
|
5523
5599
|
types2.addInterface("TaskMockResponse", mockResponses);
|
|
5524
5600
|
await ctx.write("task.d.ts", types2, true);
|
|
5525
5601
|
},
|
|
5602
|
+
// onApp(ctx) {
|
|
5603
|
+
// const group = new Group(ctx.base, 'task', 'main')
|
|
5604
|
+
// const scheduleRole = new $.aws.iam.Role(group, 'schedule', {
|
|
5605
|
+
// name: formatGlobalResourceName({
|
|
5606
|
+
// appName: ctx.app.name,
|
|
5607
|
+
// resourceType: 'task',
|
|
5608
|
+
// resourceName: 'schedule',
|
|
5609
|
+
// }),
|
|
5610
|
+
// description: `Task schedule ${ctx.app.name}`,
|
|
5611
|
+
// assumeRolePolicy: JSON.stringify({
|
|
5612
|
+
// Version: '2012-10-17',
|
|
5613
|
+
// Statement: [
|
|
5614
|
+
// {
|
|
5615
|
+
// Action: 'sts:AssumeRole',
|
|
5616
|
+
// Effect: 'Allow',
|
|
5617
|
+
// Principal: {
|
|
5618
|
+
// Service: 'scheduler.amazonaws.com',
|
|
5619
|
+
// },
|
|
5620
|
+
// },
|
|
5621
|
+
// ],
|
|
5622
|
+
// }),
|
|
5623
|
+
// inlinePolicy: [
|
|
5624
|
+
// {
|
|
5625
|
+
// name: 'Invoke function',
|
|
5626
|
+
// policy: JSON.stringify({
|
|
5627
|
+
// Version: '2012-10-17',
|
|
5628
|
+
// Statement: [
|
|
5629
|
+
// {
|
|
5630
|
+
// Action: ['lambda:InvokeFunction'],
|
|
5631
|
+
// Effect: 'Allow',
|
|
5632
|
+
// Resource: [`arn:aws:lambda:*:*:function:${ctx.appConfig.name}--*--task--*`],
|
|
5633
|
+
// },
|
|
5634
|
+
// ],
|
|
5635
|
+
// }),
|
|
5636
|
+
// },
|
|
5637
|
+
// ],
|
|
5638
|
+
// })
|
|
5639
|
+
// ctx.addGlobalPermission({
|
|
5640
|
+
// actions: ['schedule:CreateSchedule'],
|
|
5641
|
+
// resources: [`arn:aws:schedule:*:*:schedule:${ctx.appConfig.name}--*`],
|
|
5642
|
+
// })
|
|
5643
|
+
// ctx.addEnv('TASK_SCHEDULE_ROLE', scheduleRole.arn)
|
|
5644
|
+
// },
|
|
5526
5645
|
onStack(ctx) {
|
|
5527
5646
|
for (const [id, props] of Object.entries(ctx.stackConfig.tasks ?? {})) {
|
|
5528
5647
|
const group = new Group18(ctx.stack, "task", id);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
581067396e660b14247f0da7ac7979a71a1ea798
|
|
Binary file
|
package/dist/prebuild/rpc/HASH
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
067ac22da538619c808461cfb4c14803a7a8917d
|
|
Binary file
|
package/dist/server.js
CHANGED
|
@@ -116,15 +116,15 @@ var Function = /* @__PURE__ */ createProxy((stackName) => {
|
|
|
116
116
|
}
|
|
117
117
|
};
|
|
118
118
|
const call = ctx[name];
|
|
119
|
-
call.cached =
|
|
119
|
+
call.cached = (payload, options = {}) => {
|
|
120
120
|
const cacheKey = JSON.stringify({ name, payload, options });
|
|
121
121
|
if (!cache.has(cacheKey)) {
|
|
122
|
-
const
|
|
122
|
+
const promise = invoke({
|
|
123
123
|
...options,
|
|
124
124
|
name,
|
|
125
125
|
payload
|
|
126
126
|
});
|
|
127
|
-
cache.set(cacheKey,
|
|
127
|
+
cache.set(cacheKey, promise);
|
|
128
128
|
}
|
|
129
129
|
return cache.get(cacheKey);
|
|
130
130
|
};
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// src/test/test-global-setup.ts
|
|
2
2
|
import { BigFloat, eq } from "@awsless/big-float";
|
|
3
|
+
import { $mockdate, setGlobalTypes } from "@awsless/json";
|
|
3
4
|
beforeAll(() => {
|
|
4
5
|
process.env.TZ = "UTC";
|
|
6
|
+
setGlobalTypes({ $mockdate });
|
|
5
7
|
expect.addEqualityTesters([areBigFloatsEqual]);
|
|
6
8
|
});
|
|
7
9
|
var areBigFloatsEqual = (a, b) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@awsless/awsless",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.503",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -35,18 +35,18 @@
|
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"@awsless/big-float": "^0.0.6",
|
|
37
37
|
"@awsless/dynamodb": "^0.1.5",
|
|
38
|
-
"@awsless/json": "^0.0.
|
|
39
|
-
"@awsless/lambda": "^0.0.32",
|
|
38
|
+
"@awsless/json": "^0.0.10",
|
|
40
39
|
"@awsless/iot": "^0.0.3",
|
|
40
|
+
"@awsless/lambda": "^0.0.33",
|
|
41
41
|
"@awsless/mqtt": "^0.0.2",
|
|
42
|
-
"@awsless/s3": "^0.0.21",
|
|
43
42
|
"@awsless/redis": "^0.0.14",
|
|
44
|
-
"@awsless/validate": "^0.0.19",
|
|
45
43
|
"@awsless/open-search": "^0.0.17",
|
|
46
44
|
"@awsless/sns": "^0.0.10",
|
|
45
|
+
"@awsless/s3": "^0.0.21",
|
|
46
|
+
"@awsless/validate": "^0.0.19",
|
|
47
|
+
"@awsless/ssm": "^0.0.7",
|
|
47
48
|
"@awsless/sqs": "^0.0.8",
|
|
48
|
-
"@awsless/weak-cache": "^0.0.1"
|
|
49
|
-
"@awsless/ssm": "^0.0.7"
|
|
49
|
+
"@awsless/weak-cache": "^0.0.1"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@arcanyx/cidr-slicer": "^0.3.0",
|
|
@@ -127,15 +127,15 @@
|
|
|
127
127
|
"zip-a-folder": "^3.1.6",
|
|
128
128
|
"zod": "^3.24.2",
|
|
129
129
|
"zod-to-json-schema": "^3.24.3",
|
|
130
|
-
"@awsless/big-float": "^0.0.6",
|
|
131
|
-
"@awsless/json": "^0.0.8",
|
|
132
|
-
"@awsless/graphql": "^0.0.9",
|
|
133
130
|
"@awsless/formation": "^0.0.77",
|
|
134
|
-
"@awsless/
|
|
135
|
-
"@awsless/
|
|
131
|
+
"@awsless/duration": "^0.0.3",
|
|
132
|
+
"@awsless/graphql": "^0.0.9",
|
|
133
|
+
"@awsless/json": "^0.0.10",
|
|
134
|
+
"@awsless/big-float": "^0.0.6",
|
|
136
135
|
"@awsless/size": "^0.0.2",
|
|
137
|
-
"@awsless/
|
|
138
|
-
"@awsless/
|
|
136
|
+
"@awsless/validate": "^0.0.19",
|
|
137
|
+
"@awsless/ts-file-cache": "^0.0.12",
|
|
138
|
+
"@awsless/code": "^0.0.10"
|
|
139
139
|
},
|
|
140
140
|
"devDependencies": {
|
|
141
141
|
"@node-rs/bcrypt": "^1.10.5",
|