ymlr
v2.0.0
Published
A platform helps to do everythings base on a yaml script file
Readme
ymlr
A platform helps to run everythings base on a yaml script file
Installation
Install via npm
npm install -g ymlrInstall via yarn
yarn global add ymlrSupported tags
- ymlr-mqtt Pub/sub messages to channels in mqtt
- ymlr-redis Handle redis into ymlr platform
- ymlr-telegram Send telegram text, photo..., support "listen", "command", "hears"... in telegram bot
- ymlr-sql Execute query to mysql, postgresql, orable, sqlite...
- ymlr-cron Schedule jobs to do something base on cron pattern
Run a scene
Run a scene file
ymlr $PATH_TO_SCENE_FILERun a encrypted scene file with a password
ymlr $PATH_TO_SCENE_FILE $PASSWORDOverride env variables then run
ymlr -e "port=80" -e "log=error" -- $PATH_TO_SCENE_FILECLI
Show helps
ymlr -hShow all tags version
ymlrAdd new external tags, libraries which is used in the scene
ymlr add ymlr-telegram@latestUpgrade external tags, libraries which is used in the scene
ymlr up ymlr-telegram@latestRemove external tags, libraries which is used in the scene
ymlr rm ymlr-telegram@latestCustomize source paths which are registed tags in your application
ymlr --tag-dirs /myapp1 --tag-dirs /myapp2 -- myapp.yaml # "/myapp1", "/myapp2" are includes source codeOverride show debug log for all of tags
ymlr --debug=all -- myapp.yamlDocker
Docker image file: circle2jt/ymlr
Run a scene file.
Default is /script/index.yaml, you can override it in commands$PASSWORD is optional when run a encrypted scene file
docker run -v $PATH_TO_SCENE_FILE:/scripts/index.yaml --rm -it circle2jt/ymlr /scripts/index.yaml $PASSWORDRun a specific file
docker run -v $PATH_TO_SCENE_DIR:/scripts --rm -it circle2jt/ymlr /scripts/$PATH_TO_SCENE_FILEExample
- Create a scene file at
test.yaml
name: Test scene
runs:
- echo: Hello world
- name: Get post data
http'get:
url: http://localhost:3000/posts
vars: postData
- echo: ${ $vars.postData }- Run
ymlr test.yamlVisual Studio Code extensions
ymlr-vscode Play ymlr scenarios in vscode.
After install the extension, please open a scenario file then pressshift+alt+rto run the scenario.Hightlight variables, utils, constants... in yaml scenarios.
Add the below configuration to your
settings.json{ "highlight.regexes": { "(\\$v\\.)|(\\$vars\\.)": { "regexFlags": "gm", "filterLanguageRegex": "yaml", "filterFileRegex": ".*\\.yaml", "decorations": [ { "color": "#98ffa0", "fontWeight": "bold" }, { "color": "#98ffa0", "fontWeight": "bold" } ] }, "(\\$ps\\.)|(\\$parentState\\.)|(this\\.)": { "regexFlags": "g", "filterLanguageRegex": "yaml", "filterFileRegex": ".*\\.yaml", "decorations": [ { "color": "#55b85d", "fontWeight": "bold" }, { "color": "#55b85d", "fontWeight": "bold" } ] }, "(\\$loopValue[\\. ])|(\\$lv[\\. ])|(\\.loopValue)|(\\$loopKey[\\. ])|(\\$lk[\\. ]|(\\.loopKey))": { "regexFlags": "g", "filterLanguageRegex": "yaml", "filterFileRegex": ".*\\.yaml", "decorations": [ { "color": "#40723a", "fontWeight": "bold" }, { "color": "#40723a", "fontWeight": "bold" }, { "color": "#509549", "fontWeight": "bold" }, { "color": "#509549", "fontWeight": "bold" } ] }, "(\\$u\\.)|(\\$utils\\.)": { "regexFlags": "g", "filterLanguageRegex": "yaml", "filterFileRegex": ".*\\.yaml", "decorations": [ { "color": "#70aeff", "fontWeight": "bold" }, { "color": "#70aeff", "fontWeight": "bold" } ] }, "(\\$\\{[^}]+\\})": { // A regex will be created from this string, don't forget to double escape it "regexFlags": "g", // Flags used when building this regex "filterLanguageRegex": "yaml", "filterFileRegex": ".*\\.yaml", "decorations": [ { "color": "#6267f8", "fontStyle": "italic" } ] } } }
Common tags which is used in the program
| Tags | Description | |---|---| | md'doc | Generate comment in a file to document | | echo | Print to console screen | | echo'debug | Add more information when print to console screen | | clear | Clear console screen | | event'emit | Send data via global event | | event'on | Handle global events in app | | fn-debounce | Debounce function (#Ref: lodash.debounce) | | fn-debounce'cancel | Cancel debounce function (#Ref: lodash.debounce) | | fn-debounce'del | Cancel & remove debounce function (#Ref: lodash.debounce) | | fn-debounce'flush | Force to call debounce function ASAP if it's called before that (#Ref: lodash.debounce) | | fn-debounce'touch | touch debounce function. Reused last agruments(#Ref: lodash.debounce) | | fn-queue | Register a queue job | | fn-queue'del | Stop and remove a queue | | fn-singleton | This is locked before run and unlock after done. When it's called many time, this is only run after unlock | | fn-singleton'del | Remove singleton function | | fn-throttle | Throttle function (#Ref: lodash.throttle) | | fn-throttle'cancel | Cancel throttle function (#Ref: lodash.throttle) | | fn-throttle'del | Cancel & remove throttle function (#Ref: lodash.throttle) | | fn-throttle'flush | Force to call throttle function ASAP if it's called before that (#Ref: lodash.throttle) | | fn-throttle'touch | touch throttle function. Reused last agruments (#Ref: lodash.throttle) | | exit | Stop then quit the program | | fetch'del | Send a http request with DELETE method | | fetch'get | Send a http request with GET method | | fetch'head | Send a http request with HEAD method | | fetch'patch | Send a http request with PATCH method | | fetch'post | Send a http request with POST method | | fetch'put | Send a http request with PUT method | | file'read | Read a file then load data into a variable | | file'store | Store data to file | | file'write | Write data to file | | http'del | Send a http request with DELETE method | | http'get | Send a http request with GET method | | http'head | Send a http request with HEAD method | | http'patch | Send a http request with PATCH method | | http'post | Send a http request with POST method | | http'put | Send a http request with PUT method | | http'server | Create a http server to serve content via http | | include | Include a scene file or list scene files in a folder | | input'confirm | Get user confirm (yes/no) | | input'multiselect | Suggest a list of choices for user then allow pick multiple choices | | input'number | Get user input from keyboard then convert to number | | input'password | Get user input from keyboard but hide them then convert to text | | input'select | Suggest a list of choices for user then allow pick a choice | | input'suggest | Suggest a list of choices for user then allow pick a choice or create a new one | | input'text | Get user input from keyboard then convert to text | | js | Execute a nodejs code | | npm'install | Install librarries to use in the scene. | | npm'uninstall | Uninstall librarries to use in the scene. | | pause | Pause the program then wait to user enter to continue | | runs | Group elements | | scene | Load another scene into the running program | | scene'returns | Return value to parent scene | | scene'thread | Same "scene" but it run in a new thread | | sh | Execute a bash script or shell file | | sleep | Sleep the program then wait to user enter to continue | | tag'register | Register custom tags from code or npm module, github.... | | test | Check conditions in the program | | view'flow | View flows in a scene | | ymlr'load | Merge, replace env variable in yaml files to a yaml file |
Root scene
It's a scene file
Root scene file includes all of steps to run
Example:
name: Scene name # Scene name
description: Scene description # Scene description
summary: true # Show log result after finished
debug: info # Show log when run. Default is info. [silent, error, warn, info, debug, trace, all]
password: # Encrypted this file with the password. To run this file, need to provides a password in the command line
vars: # Declare global variables which are used in the program.
env: production # |- Only the variables which are declared in the top of root scene just can be overrided by environment variables
env: # Set value to environment variable (process.env)
DEBUG: all
NODE_ENV: production
env: dev # It overrides to $vars.env
# - NODE_ENV=production
envFiles: # Load env variable from files (string | string[])
- .env
- .env.dev
varsFiles: # Load vars from json or yaml files (string | string[])
- ./var1.json
- ./var2.yaml
runs: # Defined all of steps which will be run in the scene
- echo: Hello world
- test: test props
catch:
- name: handle error here ${ $wps.deref().error }
finally:
- name: always handle these steps before exit->
It's a property in a tag
Expose item properties for others extends
Example:
Use a shortcut ;
- ->: helloTemplate
;echo: Hello # Not run. ";" is same "template: true"
- <-: helloTemplate # => Hello
Use template
- ->: helloTemplate # Not run
template: true
echo: Hello
- <-: helloTemplate # => Hello
Use template
- ->: hiTemplate # Not run
;event'emit:
name: test-event
data: Hi
- <-: hiTemplate # => send "Hi" to "test-event"
- <-: hiTemplate # => send "Hello" to "test-event"
props:
data: Hello# @include
It's a yaml comment type
Include the content file to current position.
This is will be read a file then copy file content into current position
If you want to use expresion ${}, you can use tag "include".
Useful for import var file ....
Example:
- vars:
# @include ./.env.env file is
ENV: production
APP: test<-
It's a property in a tag
Copy properties from others (a item, or list items)
Example:
- ->: baseRequest
;props:
baseURL: http://localhost
- <-: baseRequest
->: user1Request
;props:
headers:
authorization: Bearer user1_token
- ->: user2RequestWithoutBaseURL
;props:
headers:
authorization: Bearer user2_token
- <-: user1Request
http'get: # Send a get request with baseURL is "http://localhost" and headers.authorization is "Bearer user1_token"
url: /posts
vars: user1Posts
- <-: [baseRequest, user2RequestWithoutBaseURL]
http'get: # Send a get request with baseURL is "http://localhost" and headers.authorization is "Bearer user2_token"
url: /posts
vars: user2Postsasync
It's a property in a tag
Execute parallel tasks
Example:
- async: true
http'get:
url: /categories
vars: categories
- ~http'get: # Can use shortcut by add "~"" before tag name
url: /product/1
vars: product
- name: The product ${$vars.product.name} is in the categories ${$vars.categories.map(c => c.name)}case
It's a property in a tag
shortcut for if + skipNext
Example:
- vars:
number: 11
- case: ${$vars.number === 11} # When reach the conditional then execute and skip the next steps
echo: Value is 11
- case: ${$vars.number > 10}
echo: Value is greater than 10 # When reach the conditional then execute and skip the next steps
- echo: Done # Never echo when reach the any conditionalcatch
It's a property in a tag
After retried and keep failing, it will execute steps in this block and ignore throw error.
If you want throw error, you need throw in catch
Example:
- js: throw new Error('Should error here')
failure:
restart: 2
catch:
- name: Print the rror is ${ $ws().error.message } after retried 2 times # => Should error here
js: throw $ws().error # Throw errorcontext
It's a property in a tag
Context logger name which is allow filter log by cli "ymlr --debug-context context_name=level --"
Example:
- name: Get list user
context: userapi
debug: warn
http'get: ...
- name: Get user details
context: userapi
debug: warn
http'get: ...
- name: Get product details
context: productapi
debug: warn
http'get: ...Now, we have 2 choices to debug all of user APIs and product APIs
- Replace all "debug: warn" to "debug: debug"
- Only run cli as below
ymlr --debug-context userapi=debug --debug-context productapi=trace -- $SCENE_FILE.yamldebug
It's a property in a tag
How to print log details for each of item.
Default is info
Value must be in:
true: isdebugall: Sametracetrace: Print all of messagesdebug: Print ofdebug,info,warn,error,fatalmessagesinfo: Printinfo,warn,error,fatalmessageswarn: Printwarn,error,fatalmessageserror: Printerror,fatalmessagesfatal: Printfatalmessagessecret: Only show secret log. Example config, password, keys...silent: Not print anything
Example:
- name: Get data from a API
debug: debug
http'get:
url: http://...../data.jsondetach
It's a property in a tag
Push the tag execution to background jobs to run async, the next steps will be run ASAP. Before the program is exited, it will be released
Example:
- name: job1
detach: true
loop: ${[1,2,3]}
runs:
- echo: Hello ${this.parentProxy.loopValue}
- sleep: 1s
- name: job2
echo: first
- name: job3
echo: secondIn above example, job2, job3 will run step by step, but job1 run in background, the program will wait job1 done then finish the program
else
It's a property in a tag
Check condition before run the item and skip the next cases when it passed
Example:
- vars:
number: 11
- if: ${$vars.number === 11}
echo: Value is 11 # => Value is 11
- elseif: ${$vars.number > 10}
echo: Value is greater than 10 # =>
- else:
echo: Value is lessthan than 10 # =>
- echo: Done # => Doneelseif
It's a property in a tag
Check condition before run the item and skip the next cases when it passed
Example:
- vars:
number: 11
- if: ${$vars.number === 11}
echo: Value is 11 # => Value is 11
- elseif: ${$vars.number > 10}
echo: Value is greater than 10 # =>
- elseif: ${$vars.number < 10}
echo: Value is lessthan than 10 # =>
- echo: Done # => Donefailure
It's a property in a tag
Handle error when do something wrong. Default it will exit app when something error.
- ignore: Ignore error then keep playing the next
- restart: max: 3 When got something error, it will be restarted automatically ASAP (-1/0 is same) sleep: 3000
Example:
- failure:
debug: warn # Show warning when failed
filterDebug: |- # Filter error. Example: error message includes the text "ignore print error here" then not show log
return !error?.message?.includes('ignore print error here')
restart: # Try to restart 3 time before exit app. Each of retry, it will be sleep 3s before restart
max: 3
sleep: 3s # wait 3s before retry
sequence: # all of failed elements in same name will be restart sequence after sleep time
name: test
sleep: 1s
retryEvent: retryIt # After could not restart (reach of limit restart) then it wait event "retryIt" then retry. (Use "event'emit" for the trigger)
ignore: true # After retry 3 time failed, it keeps playing, not exit
js: |
const a = 1/0
- failure:
ignore: true # Ignore error then play the next
js: |
const a = 1/0finally
It's a property in a tag
Always run this block when finish success or error
Example:
- js: throw new Error('Should error here')
failure:
restart: 2
catch:
- name: Print the error is ${ $ws().error.message } after retried 2 times # => Should error here
js: throw new Error('Error in catch')
finally:
- name: Should show error in catch is ${ $ws().error.message } # => Error in catchicon
It's a property in a tag
Icon which is prepended before the step name
Example:
- ->: sleepID
icon: ⏳
;sleep: 1000
- name: Sleep in 1s # => ⏳ Sleep in 1s
<-: sleepID
props: 1s
- name: Sleep in 2s # => ⏳ Sleep in 2s
<-: sleepID
props: 2sid
It's a property in a tag
ID Reference to element object in the $vars
Example:
- id: echo1
echo: Hello
- js: |
this.logger.debug($vars.echo1.content)
if
It's a property in a tag
Check condition before run the item
Example:
- vars:
number: 11
- if: ${$vars.number === 11}
echo: Value is 11 # => Value is 11
- if: ${$vars.number > 10}
echo: Value is greater than 10 # => Value is greater than 10
- if: ${$vars.number < 10}
echo: Value is lessthan than 10 # =>
- echo: Done # => Doneloop
It's a property in a tag
Loop to run items with a condition.
Variables:
$lv,$loopValue: Get loop value$lk,$loopKey: Get loop key
Example:
Loop in array
- vars:
arrs: [1,2,3,4]
- loop: ${$vars.arrs}
echo: Index is ${$loopKey}, value is ${$loopValue} # $loopKey ~ this.loopKey AND $loopValue ~ this.loopValue
# =>
# Index is 0, value is 1
# Index is 1, value is 2
# Index is 2, value is 3
# Index is 3, value is 4Loop in object
- vars:
obj: {
"name": "thanh",
"sex": "male"
}
- loop: ${$vars.obj}
echo: Key is ${$loopKey}, value is ${$loopValue}
# =>
# Key is name, value is thanh
# Key is sex, value is maleDynamic loop in a condition
- vars:
i: 0
- loop: ${$vars.i < 3}
echo: value is ${$vars.i++}
# =>
# value is 0
# value is 1
# value is 2Loop in nested items
- vars:
arrs: [1,2,3]
- loop: ${$vars.arrs}
name: group ${$loopValue}
runs:
- echo: value is ${$loopValue} # => item value is "1" then "2" then "3"
- loop: ${ [4,5,6] }
runs:
- echo: value is ${$loopValue} # => item value is "4" then "5" then "6"
- echo: parent is ${this.parentProxy.parentProxy.loopValue} # => item value is "1" then "2" then "3"
# =>
# group 1
# item value is 1name
It's a property in a tag
Step name
Example:
- name: Sleep in 1s
sleep: 1000only
It's a property in a tag
Only run this
Example:
- echo: Hi # No print "hi"
- only: true
echo: Hello # Only print "Hello"
- echo: world # No print "world"
- only: true
echo: Bye # Only print "Bye"parentState
It's used in js code
- Set/Get value to context variables. Used in tags support
runsand support parentState Variables: $wps.deref(),$ws(): Reference to context state
Example:
- name: listen to handle an events
event'on:
name: test-event
runs:
- echo: ${ $ws().eventData } # => { name: Test event, data: Hello }
- echo: ${ $ws().eventOpts } # => [ params 1, params 2 ]
- event'emit:
name: test-event
data:
name: Test event
data: Hello
opts:
- params 1
- params 2Access $ws() incursive
- name: Connect to redis
ymlr-redis:
uri: redis://localhost:6379
runs:
- name: access redis
js: |
await $ws().redis.client.publish('test-event/ping', 'level 1')
- name: after redis is connected, start listening to handle an events
event'on:
name: test-event
runs:
- echo: ${ $ws().eventData } # => { name: Test event, data: Hello }
- echo: ${ $ws().eventOpts } # => [ params 1, params 2 ]
- name: access redis
js: |
await $ws().redis.client.publish('test-event/ping', 'level 2')
- event'emit:
name: test-event
data:
name: Test event
data: Hello
opts:
- params 1
- params 2placeholder
It's a property in a tag
It store values which are overrided when inherit a template
Example:
- ->: printMyLog
placeholder:
name: ""
;js: this.logger("[MyLog] %s", this.placeholder.name)
- <-: printMyLog # expected print to "[MyLog] Test 1"
placeholder:
name: Test 1
- <-: printMyLog # expected print to "[MyLog] Test 2"
placeholder:
name: Test 2Prefix path
Global Notes
Prefix path which is support in all of tags.
Can used in code by: proxy.getPath(pathOfFile: string)
Example:
cd /app
yaml /scene/my-root-scene.yaml~~/: map to run dir/app/~/: map to root scene dir/scene/~./: map to current scene dir../: map to parent directory of the current working file./: map to directory of the current working file/: absolute path
runs
It's a property in a tag
Steps will be run in the running element
Example:
- http'server:
address: 0.0.0.0:1234
runs:
- echo: Do something when a request comes
- echo: Do something when a request comes...
...
skip
It's a property in a tag
No run this
Example:
- echo: Hi # Print "hi"
- skip: true
echo: Hello # No print "Hello"
- echo: world # Print "world"
- ->: testEvent
;event'emit:
name: test_event
data:
hello: ${ this.$.cusName }
- name: say hello to A
<-: testEvent
props:
cusName: A
- name: say hello to B
<-: testEvent
props:
cusName: BskipNext
It's a property in a tag
Skip the next steps in the same parent group when done this
Example:
- loop: ${ [1,2,3] }
runs:
- echo: begin # Always print begin
- echo: ${ this.parentProxy.loopValue }
skipNext: ${ this.parentProxy.loopValue === 2 } # When $loopValue is 2, skip the next step
- echo: end # Only print end when $loopValue is not equals 2template
It's a property in a tag
Declare a template to extends later
Example:
- ->: localhost
template: true # Template is true then it will be not run
props:
baseURL: http://localhost:3000
- ->: localhost
;props: # Same with "template: true" then it will be not run
baseURL: http://localhost:3000
- <-: localhost # Auto inherits "baseURL" from localhost
http'get: # => make a http request to http://localhost:3000/items
url: /itemsvars
It's a property in a tag
- Set value in the item to global vars to reused later
- Declare and set value to variables to reused in the scene/global scope
- If the first character is uppercase, it's auto assigned to global which is used in the program (all of scenes)
- If the first character is NOT uppercase, it will be assigned to scene scope which is only used in the scene Variables:
$v,$vars: Reference to variables
Example:
A main scene file
- echo: Hello world
vars: helloText # Save output from echo to global variable "helloText"
- echo: ${$vars.helloText} # => Hello world
- vars:
MainName: global var # Is used in all of scenes
mainName: local var # Only used in this scene
- name: test weak ref variables in parent state
runs:
- vars:
url@wps: http://localhost/data.json
- fetch'get:
url: ${ $wps.deref().url } # Refer to "url@wps". It will be revoked after "test weak ref variables" finished
vars:
weakResponseData@wps: ${ this.$.response.data } # Create a "weakResponseData" weakref state which is only available in "test weak ref variables" lifecycle
- echo: ${ $wps.deref().weakResponseData }
- name: test weak ref variables
runs:
- vars:
url@wps: http://localhost/data.json
- fetch'get:
url: ${ $wps.deref().url } # Refer to "url@wps". It will be revoked after "test weak ref variables" finished
vars:
weakResponseData@wps: ${ this.$.response.data } # Create a "weakResponseData" weakref state which is only available in "test weak ref variables" lifecycle
- scene:
path: ./child.scene.yaml
- fetch'get:
url: http://localhost/data.json
vars:
myResponseData: ${ this.$.response.data } # Assign response data to scene variable
MyResponseData: ${ this.$.response.data } # Assign response data to global variable
_: ${ $ws().responseDataInContext = this.$.response.data } # Assign response data to context variable
- echo: ${$vars.MainName} # => global var
- echo: ${$vars.mainName} # => local var
- echo: ${$vars.name} # => undefined
- echo: ${$vars.Name} # => global name hereA scene file child.scene.yaml is:
- vars:
Name: global name here
name: scene name here # Only used in this scene
- echo: ${$vars.MainName} # => global var
- echo: ${$vars.mainName} # => undefined
- echo: ${$vars.name} # => scene name here
- echo: ${$vars.Name} # => global name heremd'doc
doc
Generate comment in a file to document
Example:
- doc'md:
includeDirs:
- /workspaces/ymlr/src/components/doc/md.ts
includePattern: "^(?!.*\\.spec\\.ts$)"
excludeDirs:
- node_modules
prependMDs: # Prepend content in the document (Optional)
- path: ../INSTALLATION.md # |- {path}: Read file content then copy it into document
- --- # |- string: Markdown content
appendMDs: # Append content in the document
- ---
- "### Have fun :)"
saveTo: /workspaces/ymlr/test/thanh.doc.mdDeclare doc in file Example
echo
Print to console screen
Example:
Print a message
- echo: Hello world
- echo:
if: ${true}
content: HelloPrint a variable
- vars:
name: thanh
- echo: ${ $vars.name }Print text with custom type. (Follow "chalk")
- echo: Color is white
- echo:
styles: [red]
content: Color is red
- echo:
styles: [red, bold]
content: Content is red and boldecho'debug
Add more information when print to console screen
Example:
Print a message
# Default prepend execution time into log
- echo'debug: Hello world # => 01:01:01.101 Hello world
- echo'debug:
formatTime: YYYY/MM/DD hh:mm:ss.ms # Default format is "hh:mm:ss.ms"
content: Hello # => 2023/01/01 01:01:01.101 Helloclear
Clear console screen
Example:
- clear:event'emit
Send data via global event
Example:
- name: send data to an event
event'emit:
name: test-event
data:
name: Test event
data: Hello
opts:
- params 1
- params 2
- name: send data to multiple events
event'emit:
names:
- test-event1
- test-event2
- test-event3
data:
name: Test event
data: Hello
opts:
- params 1
- params 2
- name: quick to emit multiple event with eventData is empty
event'emit: [test-event1, test-event2]
- name: quick to emit an event with eventData is empty
event'emit: test-event1event'on
Handle global events in app
Example:
- name: listen to handle an events
event'on:
name: test-event
runs:
- echo: ${ $ws().eventData } # => { name: Test event, data: Hello }
- echo: ${ $ws().eventOpts } # => [ params 1, params 2 ]
- name: listen to handle multiple events
event'on:
names:
- test-event1
- test-event2
- test-event3
runs:
- echo: ${ $ws().eventName } # => test-event1 or test-event2 or test-event3
- echo: ${ $ws().eventData } # => { name: Test event, data: Hello }
- echo: ${ $ws().eventOpts } # => [ params 1, params 2 ] - event'emit:
name: test-event
data:
name: Test event
data: Hello
opts:
- params 1
- params 2fn-debounce
Debounce function (#Ref: lodash.debounce)
- Without "wait" and "runs" then it's only touch with last agruments
- Specific "wait" and "runs" then it's run with new agruments
Example:
- fn-debounce:
name: Delay to do something
wait: 1s # The number of milliseconds to delay.
trailing: true # Specify invoking on the trailing edge of the timeout. Default is true
leading: false # Specify invoking on the leading edge of the timeout. Default is false
maxWait: 2s # The maximum time func is allowed to be delayed before it's invoked.
autoRemove: true # Auto remove it when reached the event. Default is false.
debounceData: # Pass input debounceData to debounce to do async task
dataFromParentState: ${ $ws().channelData.name }
runs:
- name: Do this when it's free for 1s
echo: ${ $ws().debounceData.dataFromParentState }
# touch if debounce is existed
- fn-debounce: # Touch the existed throttle with last agruments
name: Delay to do something
# OR
- fn-debounce: Delay to do something # Touch the existed throttle with last agrumentsfn-debounce'cancel
Cancel debounce function (#Ref: lodash.debounce)
Example:
- fn-debounce'cancel:
name: Delay to do something # Debounce name to cancel
# OR
- fn-debounce'cancel: Delay to do something # Debounce name to cancel
# OR
- fn-debounce'cancel:
- delay1
- delay2fn-debounce'del
Cancel & remove debounce function (#Ref: lodash.debounce)
Example:
- fn-debounce'del:
name: Delay to do something # Debounce name to delete
# OR
- fn-debounce'del: Delay to do something # Debounce name to delete
# OR
- fn-debounce'del:
- delay1
- delay2fn-debounce'flush
Force to call debounce function ASAP if it's called before that (#Ref: lodash.debounce)
Example:
- fn-debounce'flush:
name: Delay to do something # Debounce name to delete
# OR
- fn-debounce'flush: Delay to do something # Debounce name to delete
# OR
- fn-debounce'flush:
- delay1
- delay2fn-debounce'touch
touch debounce function. Reused last agruments(#Ref: lodash.debounce)
Example:
- fn-debounce'touch:
name: Delay to do something # Debounce name to touch
# OR
- fn-debounce'touch: Delay to do something # Debounce name to touch
# OR
- fn-debounce'touch:
- delay1
- delay2fn-queue
Register a queue job
Example:
- id: myQueue
fn-queue:
name: My Queue 1 # Use stateless queue, not reload after startup
concurrent: 2
startup: true # Run ASAP. Default is true. If its false then it only declare job, not run yet, need call $v.myQueue.$.start() to manual start.
timeout: 1000 # Timeout for each job. Default is 0 (no timeout)
queueFilter:
expiredJobAfter: 6000 # When jobs are pending after 6s, then auto be removed
autoRemove: true # Auto remove queue after finshed all of jobs. Default is false
queueData: # Pass input data to queue to do async task
dataFromParentState: ${ $ws().channelData.name }
runs:
- echo: ${ $ws().queueData.key1 } is ${ $ws().queueData.value1 }
- echo: ${ $ws().queueData.dataFromParentState }
- echo: ${ $ws().queueData } # Queue data
- echo: ${ $ws().queueErrorCount } # Num of error when retry this job
- echo: ${ $ws().queueCreatedAt } # Time which a queue is created for the first time
- echo: ${ $ws().queueCount } # Count of queue which not done
- echo: ${ $ws().queueName } # Queue name
- fn-queue:
name: My Queue 1
queueData:
key1: value1
key2: value 2File Store
- fn-queue:
name: My Queue 1
concurrent: 2
skipError: false # Not throw error when a job failed
db: # Optional: Statefull queue, it's will reload after startup
path: /tmp/db # - Optional: Default is "tempdir/queuename"
password: abc # - Optional: Default is no encrypted by password
runs:
- echo: ${ $ws().queueData.key1 } is ${ $ws().queueData.value1 }
- fn-queue:
name: My Queue 1
queueData:
key1: value1
key2: value 2External Store
- id: fileDataStore
file'store:
path: /tmp/data.json # Path to store data
password: # Password to encrypt/decrypt data content
initData: [] # Default data will be stored when file not found
- fn-queue:
name: My Queue 1
concurrent: 2
skipError: false # Not throw error when a job failed
store: ${ $v.fileDataStore.$.store }
runs:
- echo: ${ $ws().queueData.key1 } is ${ $ws().queueData.value1 }
- fn-queue:
name: My Queue 1
queueData:
key1: value1
key2: value 2fn-queue'del
Stop and remove a queue
Example:
- fn-queue'del:
name: My Queue 1 # Queue name to delete
- fn-queue'del: My Queue 1 # Queue name to delete
- fn-queue'del:
- My Queue 1 # List Queues name to delete
- My Queue 2fn-singleton
This is locked before run and unlock after done. When it's called many time, this is only run after unlock
Example:
- fn-singleton:
name: Only run 1 time
trailing: true # In the processing which not finished yet, if it's called by others, it keeps the last params to cached then make the last call before done
autoRemove: true # Auto remove after done
singletonData: # Pass input data to singleton to do async task
dataFromParentState: ${ $ws().channelData.name }
runs:
- echo: Do this when it's free for 1sfn-singleton'del
Remove singleton function
Example:
- fn-singleton'del:
name: Delay to do something # Singleton name to delete
# OR
- fn-singleton'del: Delay to do something # Singleton name to deletefn-throttle
Throttle function (#Ref: lodash.throttle)
- Without "wait" and "runs" then it's only touch with last agruments
- Specific "wait" and "runs" then it's run with new agruments
Example:
- fn-throttle:
name: Delay to do something
wait: 1s # The number of milliseconds to throttle invocations to.
trailing: true # Specify invoking on the trailing edge of the timeout. Default is true
leading: true # Specify invoking on the leading edge of the timeout. Default is true
autoRemove: true # Auto remove it when reached the event. Default is false
throttleData: # Pass input debounceData to debounce to do async task
dataFromParentState: ${ $ws().channelData.name }
runs:
- name: Do this ASAP and do again when it's called more than 1 times
echo: ${ $ws().throttleData.dataFromParentState }
# Call if throttle is existed
- fn-throttle: # Touch the existed throttle with last agruments
name: Delay to do something
# OR
- fn-throttle: Delay to do something # Touch the existed throttle with last agrumentsfn-throttle'cancel
Cancel throttle function (#Ref: lodash.throttle)
Example:
- fn-throttle'cancel:
name: Delay to do something # Throttle name to cancel
# OR
- fn-throttle'cancel: Delay to do something # Throttle name to cancel
# OR
- fn-throttle'cancel:
- delay1
- delay2fn-throttle'del
Cancel & remove throttle function (#Ref: lodash.throttle)
Example:
- fn-throttle'del:
name: Delay to do something # Throttle name to delete
# OR
- fn-throttle'del: Delay to do something # Throttle name to delete
# OR
- fn-throttle'del:
- delay1
- delay2fn-throttle'flush
Force to call throttle function ASAP if it's called before that (#Ref: lodash.throttle)
Example:
- fn-throttle'flush:
name: Delay to do something # Throttle name to delete
# OR
- fn-throttle'flush: Delay to do something # Throttle name to delete
# OR
- fn-throttle'flush:
- delay1
- delay2fn-throttle'touch
touch throttle function. Reused last agruments (#Ref: lodash.throttle)
Example:
- fn-throttle'touch:
name: Delay to do something # Throttle name to touch
# OR
- fn-throttle'touch: Delay to do something # Throttle name to touch
# OR
- fn-throttle'touch:
- delay1
- delay2exit
Stop then quit the program
Example:
- exit: 0
- name: Throw error
exit: 1fetch'del
Send a http request with DELETE method
Example:
# DELETE http://localhost:3000/posts/1?method=check_existed
- name: Delete a post
fetch'del:
url: /posts/1
baseURL: http://localhost:3000 # !optional - Request base url
query: # !optional - Request query string
method: check_existed
headers: # !optional - Request headers
authorization: Bearer TOKEN
timeout: 5000 # !optional - Request timeout. Default is no timeout
validStatus: [200, 204, 400] # !optional - Expect these response status codes is success and not throw error
vars: # !optional - Global variable which store value after executed
status: ${this.$.response.status}fetch'get
Send a http request with GET method
Example:
Get data from API then store value in vars.posts
# GET http://localhost:3000/posts?category=users
- name: Get list posts
fetch'get:
url: /posts
timeout: 5000 # !optional - Request timeout. Default is no timeout
baseURL: http://localhost:3000 # !optional - Request base url
query: # !optional - Request query string
category: users
headers: # !optional - Request headers
authorization: Bearer TOKEN
responseType: json # !optional - Default is json ['json' | 'blob' | 'text' | 'buffer' | 'none']
validStatus: [200, 204, 400] # !optional - Expect these response status codes is success and not throw error
vars: posts # !optional - Global variable which store value after executedDownload file from a API
# GET http://localhost:3000/posts?category=users
- name: Download a file
fetch'get:
baseURL: http://localhost:3000
url: /posts
query:
category: users
headers:
authorization: Bearer TOKEN
saveTo: /tmp/post.jsonfetch'head
Send a http request with HEAD method
Example:
# HEAD http://localhost:3000/posts/1?method=check_existed
- name: Check post is existed or not
fetch'head:
baseURL: http://localhost:
timeout: 5000 # !optional - Request timeout. Default is no timeout
# supported: d h m s ~ day, hour, minute, seconds
# example: 1h2m3s ~ 1 hour, 2 minutes, 3 seconds
url: /posts/1
query:
method: check_existed
headers:
authorization: Bearer TOKEN
validStatus: [200, 204, 400] # !optional - Expect these response status codes is success and not throw error
vars:
status: ${this.response?.status}fetch'patch
Send a http request with PATCH method
Example:
Update apart of data to API then store value in vars.posts
# PATCH http://localhost:3000/posts/ID?category=users
- name: Update a post
fetch'patch:
baseURL: http://localhost:3000
url: /posts/ID
query:
category: users
headers:
authorization: Bearer TOKEN
type: json # 'json' | 'form' | 'raw' | 'multipart' | 'text'
timeout: 5000 # !optional - Request timeout. Default is no timeout
body: {
"title": "My title",
"description": "My description"
}
responseType: json # 'json' | 'blob' | 'text' | 'buffer' | 'none'
validStatus: [200, 204, 400] # !optional - Expect these response status codes is success and not throw error
vars: newPostUpload file to server
# PATCH http://localhost:3000/upload/ID_UPLOADER_TO_REPLACE
- name: Upload and update data
fetch'patch:
baseURL: http://localhost:3000
url: /upload/ID_UPLOADER_TO_REPLACE
headers:
authorization: Bearer TOKEN
type: multipart
body: {
"file": { # File upload must includes path of file, name is optional
"path": "/tmp/new_my_avatar.jpg",
"name": "thanh_avatar"
}
}
vars:
status: ${this.$.response.status}fetch'post
Send a http request with POST method
Example:
Post data to API then store value in vars.posts
# POST http://localhost:3000/posts?category=users
- name: Create a new post
fetch'post:
baseURL: http://localhost:3000
url: /posts
query:
category: users
headers:
authorization: Bearer TOKEN
type: json # 'json' | 'form' | 'raw' | 'multipart' | 'text'
timeout: 5000 # !optional - Request timeout. Default is no timeout
body: {
"title": "My title",
"description": "My description"
}
responseType: json # 'json' | 'blob' | 'text' | 'buffer' | 'none'
vars: newPostUpload file to server
# POST http://localhost:3000/upload
- name: Upload a new avatar
fetch'post:
baseURL: http://localhost:3000
url: /upload
headers:
authorization: Bearer TOKEN
type: multipart
body: {
"category": "avatar",
"file": { # File upload must includes path of file, name is optional
"path": "/tmp/my_avatar.jpg",
"name": "thanh_avatar"
}
}
validStatus: [200, 204, 400] # !optional - Expect these response status codes is success and not throw error
vars:
status: ${this.$.response.status}fetch'put
Send a http request with PUT method
Example:
Update data to API then store value in vars.posts
# PUT http://localhost:3000/posts/ID?category=users
- name: Update a post
fetch'put:
baseURL: http://localhost:3000
url: /posts/ID
query:
category: users
headers:
authorization: Bearer TOKEN
type: json # 'json' | 'form' | 'raw' | 'multipart' | 'text'
timeout: 5000 # !optional - Request timeout. Default is no timeout
body: {
"title": "My title",
"description": "My description"
}
responseType: json # 'json' | 'blob' | 'text' | 'buffer' | 'none'
validStatus: [200, 204, 400] # !optional - Expect these response status codes is success and not throw error
vars: newPostUpload file to server
# PUT http://localhost:3000/upload/ID_UPLOADER_TO_REPLACE
- name: Upload and update data
fetch'put:
baseURL: http://localhost:3000
url: /upload/ID_UPLOADER_TO_REPLACE
headers:
authorization: Bearer TOKEN
type: multipart
body: {
"category": "avatar updated",
"file": { # File upload must includes path of file, name is optional
"path": "/tmp/new_my_avatar.jpg",
"name": "thanh_avatar"
}
}
vars:
status: ${this.$.response.status}file'read
Read a file then load data into a variable
Example:
Read a json file
- file'read:
path: /tmp/data.json
format: json # !optional
vars: fileDataRead a yaml file
- file'read:
path: /tmp/data.yaml
format: yaml # !optional
vars: fileDataRead a text file
- file'read:
path: /tmp/data.txt
vars: fileContentfile'store
Store data to file
Example:
- file'store:
path: /tmp/data.json # Path to store data
password: # Password to encrypt/decrypt data content
initData: [] # Default data will be stored when file not foundUse in global by reference
- file'store:
path: /tmp/data.yaml
initData: []
vars:
fileDB: ${this} # Store this element to "fileDB" in vars
- js: |
const { fileDB } = vars
fileDB.data.push('item 1')
fileDB.data.push('item 2')
// Save data to file
fileDB.save()
- echo: ${$vars.fileDB.data} # => ['item 1', 'item 2']file'write
Write data to file
Example:
Write a json file
- file'write:
path: /tmp/data.json
content: {
"say": "hello"
}
format: json # !optional
pretty: true # !optional
opts: # ref: https://nodejs.org/api/fs.html#fswritefilefile-data-options-callback
mode: 775
flag: r # ref: https://nodejs.org/api/fs.html#file-system-flagsWrite a yaml file
- file'write:
path: /tmp/data.yaml
content: ${$vars.fileData}
format: yaml # !optionalWrite a text file
- file'write:
path: /tmp/data.txt
content: Hello worldhttp'del
Send a http request with DELETE method
Example:
# DELETE http://localhost:3000/posts/1?method=check_existed
- name: Delete a post
http'del:
url: /posts/1
baseURL: http://localhost:3000 # !optional - Request base url
query: # !optional - Request query string
method: check_existed
headers: # !optional - Request headers
authorization: Bearer TOKEN
timeout: 5000 # !optional - Request timeout. Default is no timeout
validStatus: [200, 204, 400] # !optional - Expect these response status codes is success and not throw error
vars: # !optional - Global variable which store value after executed
status: ${this.$.response.status}http'get
Send a http request with GET method
Example:
Get data from API then store value in vars.posts
# GET http://localhost:3000/posts?category=users
- name: Get list posts
http'get:
url: /posts
timeout: 5000 # !optional - Request timeout. Default is no timeout
baseURL: http://localhost:3000 # !optional - Request base url
query: # !optional - Request query string
category: users
headers: # !optional - Request headers
authorization: Bearer TOKEN
responseType: json # !optional - Default is json ['json' | 'blob' | 'text' | 'buffer' | 'none']
validStatus: [200, 204, 400] # !optional - Expect these response status codes is success and not throw error
vars: posts # !optional - Global variable which store value after executedDownload file from a API
# GET http://localhost:3000/posts?category=users
- name: Download a file
http'get:
baseURL: http://localhost:3000
url: /posts
query:
category: users
headers:
authorization: Bearer TOKEN
saveTo: /tmp/post.jsonhttp'head
Send a http request with HEAD method
Example:
# HEAD http://localhost:3000/posts/1?method=check_existed
- name: Check post is existed or not
http'head:
baseURL: http://localhost:
timeout: 5000 # !optional - Request timeout. Default is no timeout
# supported: d h m s ~ day, hour, minute, seconds
# example: 1h2m3s ~ 1 hour, 2 minutes, 3 seconds
url: /posts/1
query:
method: check_existed
headers:
authorization: Bearer TOKEN
validStatus: [200, 204, 400] # !optional - Expect these response status codes is success and not throw error
vars:
status: ${this.response?.status}http'patch
Send a http request with PATCH method
Example:
Update apart of data to API then store value in vars.posts
# PATCH http://localhost:3000/posts/ID?category=users
- name: Update a post
http'patch:
baseURL: http://localhost:3000
url: /posts/ID
query:
category: users
headers:
authorization: Bearer TOKEN
type: json # 'json' | 'form' | 'raw' | 'multipart' | 'text'
timeout: 5000 # !optional - Request timeout. Default is no timeout
body: {
"title": "My title",
"description": "My description"
}
responseType: json # 'json' | 'blob' | 'text' | 'buffer' | 'none'
validStatus: [200, 204, 400] # !optional - Expect these response status codes is success and not throw error
vars: newPostUpload file to server
# PATCH http://localhost:3000/upload/ID_UPLOADER_TO_REPLACE
- name: Upload and update data
http'patch:
baseURL: http://localhost:3000
url: /upload/ID_UPLOADER_TO_REPLACE
headers:
authorization: Bearer TOKEN
type: multipart
body: {
"file": { # File upload must includes path of file, name is optional
"path": "/tmp/new_my_avatar.jpg",
"name": "thanh_avatar"
}
}
vars:
status: ${this.$.response.status}http'post
Send a http request with POST method
Example:
Post data to API then store value in vars.posts
# POST http://localhost:3000/posts?category=users
- name: Create a new post
http'post:
baseURL: http://localhost:3000
url: /posts
query:
category: users
headers:
authorization: Bearer TOKEN
type: json # 'json' | 'form' | 'raw' | 'multipart' | 'text'
timeout: 5000 # !optional - Request timeout. Default is no timeout
body: {
"title": "My title",
"description": "My description"
}
responseType: json # 'json' | 'blob' | 'text' | 'buffer' | 'none'
validStatus: [200, 204, 400] # !optional - Expect these response status codes is success and not throw error
vars: newPostUpload file to server
# POST http://localhost:3000/upload
- name: Upload a new avatar
http'post:
baseURL: http://localhost:3000
url: /upload
headers:
authorization: Bearer TOKEN
type: multipart
body: {
"category": "avatar",
"file": { # File upload must includes path of file, name is optional
"path": "/tmp/my_avatar.jpg",
"name": "thanh_avatar"
}
}
vars:
status: ${this.$.response.status}http'put
Send a http request with PUT method
Example:
Update data to API then store value in vars.posts
# PUT http://localhost:3000/posts/ID?category=users
- name: Update a post
http'put:
baseURL: http://localhost:3000
url: /posts/ID
query:
category: users
headers:
authorization: Bearer TOKEN
type: json # 'json' | 'form' | 'raw' | 'multipart' | 'text'
timeout: 5000 # !optional - Request timeout. Default is no timeout
body: {
"title": "My title",
"description": "My description"
}
responseType: json # 'json' | 'blob' | 'text' | 'buffer' | 'none'
validStatus: [200, 204, 400] # !optional - Expect these response status codes is success and not throw error
vars: newPostUpload file to server
# PUT http://localhost:3000/upload/ID_UPLOADER_TO_REPLACE
- name: Upload and update data
http'put:
baseURL: http://localhost:3000
url: /upload/ID_UPLOADER_TO_REPLACE
headers:
authorization: Bearer TOKEN
type: multipart
body: {
"category": "avatar updated",
"file": { # File upload must includes path of file, name is optional
"path": "/tmp/new_my_avatar.jpg",
"name": "thanh_avatar"
}
}
vars:
status: ${this.$.response.status}http'server
Create a http server to serve content via http
Example:
- http'server:
address: 0.0.0.0:8811 # Address to listen
auth: # Check authentication
basic: # 'Basic ' + base64(`${username}:${password}`)
username: username
password: password
custom:
secret: 'SERVER_SECRET_TOKEN'
secretKey: SECRET_HEADER_KEY
onCheck: |
return $ws().headers[this.secretKey] === this.secret
// cors: {} # enable all cors requests
cors: # Ref: https://www.npmjs.com/package/cors#configuring-cors
origin: '*'
methods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE']
allowedHeaders: ['Content-Type', 'Authorization']
exposedHeaders: ['Content-Range', 'X-Content-Range']
credentials?: boolean | undefined;
maxAge?: number | undefined;
preflightContinue: false
optionsSuccessStatus: 204
opts:
timeout: 0 # The number of milliseconds of inactivity before a socket is presumed to have timed out.
keepAliveTimeout: 0 # The number of milliseconds of inactivity a server needs to wait for additional incoming data, after it has finished writing the last response, before a socket will be destroyed
headersTimeout: 0 # Limit the amount of time the parser will wait to receive the complete HTTP headers.
maxConnections: 0 # Set this property to reject connections when the server's connection count gets high.
maxHeadersCount: 0 # Limits maximum incoming headers count. If set to 0, no limit will be applied.
maxRequestsPerSocket: 0 # The maximum number of requests socket can handle before closing keep alive connection.
requestTimeout: 0 # Sets the timeout value in milliseconds for receiving the entire request from the client.
runs: # Execute when a request comes
- echo: ${ $ws().httpRequest.path } # Get request path
- echo: ${ $ws().httpRequest.method } # Get request method
- echo: ${ $ws().httpRequest.headers } # Get request headers
- echo: ${ $ws().httpRequest.query } # Get request query string
- echo: ${ $ws().httpRequest.body } # Get request body
- echo: ${ $ws().httpRequest.response } # Set response data
# - status: 200 - http response status
# - statusMessage: OK - http response status message
# - headers: {} - Set response headers
# - data: {} - Set response data
- echo: ${ $ws().httpRequest.req } # Ref to req in http.IncomingMessage in nodejs
- echo: ${ $ws().httpRequest.res } # Ref to res in http.ServerResponse in nodejs
- js: | # Handle response by yourself (When $ws().response is undefined)
$ws().httpRequest.res.status = 200
$ws().httpRequest.res.statusMessage = 'OK'
$ws().httpRequest.res.write('OK')
$ws().httpRequest.res.end()include
Include a scene file or list scene files in a folder
Example:
- include: ./my-scenes/scene1.yaml # Includes a only file "scene1.yaml"
- include:
cached: true # Load file for the first time, the next will get from caches
files: ./my-scenes # Includes all of files (.yaml, .yml) which in the directory (./my-scenes)
validFilePattern: ^[a-zA-Z0-9].*?\.stack\.ya?ml$ # Only load files which ends with .stack.yaml
validDirPattern: ^[a-zA-Z0-9] # Only scan files in these valid directories
maxDeepLevel: 0 # Max deep child directories to scan files
- include:
- file1.yaml
- file2.yaml
- file3.yaml
- include:
cached: true
files:
- file1.yaml
- file2.yaml
- file3.yamlinput'confirm
Get user confirm (yes/no)
Example:
# - input'conf:
- input'confirm:
title: Are you sure to delete it ?
default: false # !optional
required: true # !optional
vars: userWantToDeleteinput'multiselect
Suggest a list of choices for user then allow pick multiple choices
Example:
# - input'msel:
- input'multiselect:
title: Please select your hobbies ?
choices:
- title: Tennis
value: tn
- title: Football
value: fb
- title: Basket ball
value: bb
default: [tn, fb] # !optional
required: true # !optional
vars: hobbiesinput'number
Get user input from keyboard then convert to number
Example:
# - input'num:
- input'number:
title: Enter your age ?
default: 18 # !optional
required: true # !optional
vars: ageinput'password
Get user input from keyboard but hide them then convert to text
Example:
# - input'pwd:
- input'password:
title: Enter your password ?
required: true # !optional
vars: passwordinput'select
Suggest a list of choices for user then allow pick a choice
Example:
# - input'sel:
- input'select:
title: Your sex ?
choices:
- title: male
value: m
- title: female
value: f
default: m # !optional
required: true # !optional
vars: sexinput'suggest
Suggest a list of choices for user then allow pick a choice or create a new one
Example:
# - input'sug:
- input'suggest:
title: Your hobby
choices:
- title: Football
value: football
- title: Basket Ball
value: backetball
default: football 