Profiles / Programming
SD

Script developer

Full script lifecycle in the MQL editor: typed parameters, read back, generate, patch, deploy, copy, grant, delete.

10 recipes on this page · open in the interactive view

Recipes
  1. A script with typed parameters rejects bad calls
  2. Optional parameters and default values
  3. Read a script back to build a form
  4. Patch a script with its generated update request
  5. Idempotent deployment with script merge
  6. Back up a script before deleting it
  7. Copy, rename and delete a module by prefix
  8. Stack and delay options of a batch script
  9. Grant a script to a group, then revert
  10. Business types on the parameters of a service
executed on MentDB

A script with typed parameters rejects bad calls

An invoice-line service must refuse bad input before its code runs. The script declares four typed parameters (varchar, integer, decimal, enum), then one valid call and four invalid ones show the exact messages the server returns.

Pipeline
Script
admin
#1. A POST script with typed parameters;
script create post "tuto.sc.invoice.line" false 1
	(param
		(var "[sku]" {type is_varchar [sku] 12} "Product code, 12 chars max" is_null:false is_empty:false "BK-0042")
		(var "[qty]" {type is_integer [qty] 4} "Quantity, 4 digits max" is_null:false is_empty:false "3")
		(var "[price]" {type is_decimal [price] 8 2} "Unit price" is_null:false is_empty:false "19.90")
		(var "[vat]" {type is_enum [vat] "0,5.5,10,20"} "VAT rate in percent" is_null:false is_empty:false "20")
	;)
	"Compute a gross invoice line"
{
	-> "[net]" (math round (* [qty] [price]) 2);
	concat [sku] ": " [qty] " x " [price] " = " [net] " net, " (math round (* [net] (+ 1 (/ [vat] 100))) 2) " gross";
} "A sentence with the net and gross amounts";
#2. A valid call;
-> "[ok]" (execute "tuto.sc.invoice.line.post" "[sku]" "BK-0042" "[qty]" 3 "[price]" 19.90 "[vat]" 20);
#3. Invalid calls: the server rejects them before the code runs;
-> "[errors]" "";
for (-> "[i]" 1) (<= [i] 4) (++ "[i]") {
	try {
		case
			(== [i] 1) {execute "tuto.sc.invoice.line.post" "[sku]" "BK-0042" "[qty]" "three" "[price]" 19.90 "[vat]" 20;}
			(== [i] 2) {execute "tuto.sc.invoice.line.post" "[sku]" "BK-0042" "[qty]" 3 "[price]" 19.90 "[vat]" 21;}
			(== [i] 3) {execute "tuto.sc.invoice.line.post" "[sku]" "" "[qty]" 3 "[price]" 19.90 "[vat]" 20;}
			(== [i] 4) {execute "tuto.sc.invoice.line.post" "[sku]" "BK-0042" "[qty]" 3;}
		;
	} {
		-> "[errors]" (concat [errors] [_n_] "- " (string regexp_extract [err] "\[\[\[ Sorry, (.*) \]\]\]" 1));
	} "[err]";
};
#4. Remove the tutorial script;
script delete "tuto.sc.invoice.line.post";
concat "OK: " [ok] [_n_] "Rejected:" [errors];
Result
mentdb
OK: BK-0042: 3 x 19.90 = 59.7 net, 71.64 gross
Rejected:
- the variable '[qty]' in the script 'tuto.sc.invoice.line.post' is not valid (type is_integer [qty] 4;).
- the variable '[vat]' in the script 'tuto.sc.invoice.line.post' is not valid (type is_enum [vat] "0,5.5,10,20";).
- the variable '[sku]' in the script 'tuto.sc.invoice.line.post' cannot not be empty.
- the script 'tuto.sc.invoice.line.post' does not has a valid number of parameters.
Go further
executed on MentDB

Optional parameters and default values

A greeting service has a required name and two optional parameters. A null optional value skips the type check, the defaults are applied in the code, and the is_null, is_empty and type rules refuse the bad calls.

Pipeline
Script
admin
#1. Optional parameters: null allowed, the type check runs only on a value;
script create post "tuto.sc.greeting" false 1
	(param
		(var "[name]" {type is_varchar [name] 30} "Customer name" is_null:false is_empty:false "Alice")
		(var "[lang]" {type is_enum [lang] "en,fr,es"} "Language, en when null" is_null:true is_empty:false "fr")
		(var "[title]" {type is_varchar [title] 10} "Title, may be null or empty" is_null:true is_empty:true "Dr")
	;)
	"Build a greeting with default values"
{
	#2. Apply the defaults inside the script;
	if (is null [lang]) {-> "[lang]" "en";};
	if (is null or empty [title]) {-> "[who]" [name];} {-> "[who]" (concat [title] " " [name]);};
	concat (switch [lang] ("fr") {"Bonjour"} ("es") {"Hola"} {"Hello"}) " " [who] " - lang " [lang];
} "The greeting";
#3. Same script, three calls;
-> "[r]" (concat
	(execute "tuto.sc.greeting.post" "[name]" "Alice" "[lang]" "fr" "[title]" "Dr") [_n_]
	(execute "tuto.sc.greeting.post" "[name]" "Bob" "[lang]" null "[title]" null) [_n_]
	(execute "tuto.sc.greeting.post" "[name]" "Carmen" "[lang]" "es" "[title]" "")
);
#4. Refused: a null or empty name, an empty language, a language out of the list;
json load "tuto_sc_bad" "[[null, \"en\"], [\"\", \"en\"], [\"Dan\", \"\"], [\"Dan\", \"de\"]]";
for (-> "[i]" 0) (< [i] 4) (++ "[i]") {
	try {
		execute "tuto.sc.greeting.post" "[name]" (json select "tuto_sc_bad" (concat "/[" [i] "]/[0]")) "[lang]" (json select "tuto_sc_bad" (concat "/[" [i] "]/[1]")) "[title]" null;
	} {
		-> "[r]" (concat [r] [_n_] "refused: " (string regexp_extract [err] "\[\[\[ (.+?) \]\]\]" 1));
	} "[err]";
};
json unload "tuto_sc_bad";
script delete "tuto.sc.greeting.post";
[r];
Result
mentdb
Bonjour Dr Alice - lang fr
Hello Bob - lang en
Hola Carmen - lang es
refused: Sorry, the variable '[name]' in the script 'tuto.sc.greeting.post' cannot not be null.
refused: Sorry, the variable '[name]' in the script 'tuto.sc.greeting.post' cannot not be empty.
refused: Sorry, the variable '[lang]' in the script 'tuto.sc.greeting.post' cannot not be empty.
refused: Sorry, the variable '[lang]' in the script 'tuto.sc.greeting.post' is not valid (type is_enum [lang] "en,fr,es";).
Go further
  • The type check runs only on a value: with is_null:true a null skips it, no or (is null [x]) ... guard is needed.
  • is_null and is_empty are independent: [lang] accepts null but refuses an empty string.
  • string empty_if_null is another way to normalise the input.
executed on MentDB

Read a script back to build a form

A front-end developer needs a form for a search service. The script definition and its parameters are read back with `script get` and `script show params`, then turned into a JSON form description: field, label, required flag and check.

Pipeline
Script
admin
#1. A script to describe;
script create get "tuto.sc.customer.search" false 1
	(param
		(var "[q]" {type is_varchar [q] 50} "Text to search in the name" is_null:false is_empty:false "dupont")
		(var "[page]" {type is_integer [page] 3} "Page number" is_null:false is_empty:false "1")
		(var "[email]" {or (is null [email]) (type is_email [email])} "Exact email filter" is_null:true is_empty:true "a@b.test")
	;)
	"Search the customers"
{
	concat "searching " [q];
} "A JSON array of customers";
#2. Read it back: definition and parameters;
-> "[name]" "tuto.sc.customer.search.get";
json load "tuto_sc_def" (script get [name]);
json load "tuto_sc_par" (script show params [name]);
#3. Build a form description for a front-end;
json load "tuto_sc_form" "{\"fields\": []}";
json iobject "tuto_sc_form" "/" "script" (json select "tuto_sc_def" "/k") STR;
json iobject "tuto_sc_form" "/" "title" (json select "tuto_sc_def" "/desc") STR;
json iobject "tuto_sc_form" "/" "returns" (json select "tuto_sc_def" "/return") STR;
for (-> "[i]" 0) (< [i] (json count "tuto_sc_par" "/")) (++ "[i]") {
	-> "[p]" (concat "/[" [i] "]");
	json iarray "tuto_sc_form" "/fields" (concat
		"{\"field\": \"" (string regexp_extract (json select "tuto_sc_par" (concat [p] "/name")) "\[(.*)\]" 1)
		"\", \"label\": \"" (json select "tuto_sc_par" (concat [p] "/desc"))
		"\", \"required\": " (== (json select "tuto_sc_par" (concat [p] "/isNull")) "0")
		", \"check\": \"" (string json_escape (json select "tuto_sc_par" (concat [p] "/type")))
		"\"}") OBJ;
};
-> "[form]" (json doc "tuto_sc_form");
#4. Clean up;
json unload "tuto_sc_def";
json unload "tuto_sc_par";
json unload "tuto_sc_form";
script delete [name];
[form];
Result
mentdb
{
  "returns": "A JSON array of customers",
  "fields": [
    {
      "field": "q",
      "label": "Text to search in the name",
      "check": "type is_varchar [q] 50;",
      "required": 1
    },
    {
      "field": "page",
      "label": "Page number",
      "check": "type is_integer [page] 3;",
      "required": 1
    },
    {
      "field": "email",
      "label": "Exact email filter",
      "check": "or (is null [email]) (type is_email [email]);",
      "required": 0
    }
  ],
  "title": "Search the customers",
  "script": "tuto.sc.customer.search.get"
}
Go further
  • The same loop can produce an HTML form with concat.
  • script show users tells you who will be able to submit the form.
  • The vars field of script get holds the raw param block.
executed on MentDB

Patch a script with its generated update request

The shipping rate changed and parcels over 30 kg become free. The update request is generated from the running script, patched with string replacements, applied with `eval`, and the new behaviour is checked.

Pipeline
Script
admin
#1. Version 1: a shipping fee with a hard-coded rate;
script create post "tuto.sc.shipping.fee" false 1
	(param
		(var "[kg]" {type is_double [kg]} "Weight in kg" is_null:false is_empty:false "2.5")
	;)
	"Shipping fee of a parcel"
{
	math round (+ 4.90 (* [kg] 1.20)) 2;
} "The fee in EUR";
-> "[v1]" (execute "tuto.sc.shipping.fee.post" "[kg]" 2.5);
#2. Generate the update request, exactly what the editor shows;
-> "[code]" (script generate update "tuto.sc.shipping.fee.post");
#3. Patch it: new rate, logging on, a free shipping threshold;
-> "[code]" (string replace [code] "1.20" "1.35");
-> "[code]" (string replace [code] "math round" "if (> [kg] 30) {0;} {math round");
-> "[code]" (string replace [code] "2;" "2;};");
-> "[code]" (string replace [code] "\"tuto.sc.shipping.fee.post\" false" "\"tuto.sc.shipping.fee.post\" true");
#4. Apply the update and check the new behaviour;
-> "[applied]" (eval [code]);
-> "[v2]" (execute "tuto.sc.shipping.fee.post" "[kg]" 2.5);
-> "[v3]" (execute "tuto.sc.shipping.fee.post" "[kg]" 35);
-> "[log]" (json load select "tuto_sc_s" (script get "tuto.sc.shipping.fee.post") "/activateLog");
json unload "tuto_sc_s";
script delete "tuto.sc.shipping.fee.post";
concat "v1: " [v1] " EUR" [_n_] [applied] [_n_] "v2: " [v2] " EUR, 35 kg: " [v3] " EUR, activity log " [log] [_n_] "Request applied:" [_n_] [code];
Result
mentdb
v1: 7.9 EUR
Script updated with successful.
v2: 8.28 EUR, 35 kg: 0 EUR, activity log 1
Request applied:
script update "tuto.sc.shipping.fee.post" true 1
  (param
		(var "[kg]" {type is_double [kg]} "Weight in kg" is_null:false is_empty:false "2.5")
	;)
  "Shipping fee of a parcel"
{
	if (> [kg] 30) {0;} {math round (+ 4.90 (* [kg] 1.35)) 2;};
} "The fee in EUR";
Go further
  • Keep the generated request in your versioning tool before changing it.
  • script get then /mql shows only the code, without the header.
  • For many scripts, see script generate merge and the versioning functions.
executed on MentDB

Idempotent deployment with script merge

A deployment request must work on a fresh server and on a server that already has the script. `script merge` creates the script the first time and updates it the second time; the generated create, update and merge headers are compared.

Pipeline
Script
admin
#1. Deployment request kept in the project: merge creates or updates;
-> "[deploy]" "script merge \"tuto.sc.discount.rate.post\" false 1
  (param
	(var \"[segment]\" {type is_enum [segment] \"retail,pro,vip\"} \"Customer segment\" is_null:false is_empty:false \"pro\")
  ;)
  \"Discount rate of a segment\"
{
	case (equal [segment] \"vip\") {15;} (equal [segment] \"pro\") {8;} {0;};
} \"Rate in percent\";";
#2. First deployment: the script does not exist yet;
-> "[before]" (script exist "tuto.sc.discount.rate.post");
-> "[run1]" (eval [deploy]);
-> "[r1]" (execute "tuto.sc.discount.rate.post" "[segment]" "vip");
#3. Second deployment of a new version: same request, no error;
-> "[deploy]" (string replace [deploy] "{15;}" "{20;}");
-> "[run2]" (eval [deploy]);
-> "[r2]" (execute "tuto.sc.discount.rate.post" "[segment]" "vip");
#4. The three requests the server can generate for this script;
-> "[gen]" (concat
	"create: " (atom get_first (script generate create "tuto.sc.discount.rate.post") [_n_]) [_n_]
	"update: " (atom get_first (script generate update "tuto.sc.discount.rate.post") [_n_]) [_n_]
	"merge:  " (atom get_first (script generate merge "tuto.sc.discount.rate.post") [_n_]));
script delete "tuto.sc.discount.rate.post";
concat "exists before: " [before] [_n_] "deploy 1: " [run1] " vip=" [r1] "%" [_n_] "deploy 2: " [run2] " vip=" [r2] "%" [_n_] [gen] [_n_] "exists after delete: " (script exist "tuto.sc.discount.rate.post");
Result
mentdb
exists before: 0
deploy 1: Script merged with successful. vip=15%
deploy 2: Script merged with successful. vip=20%
create: script create post "tuto.sc.discount.rate" false 1
update: script update "tuto.sc.discount.rate.post" false 1
merge:  script merge "tuto.sc.discount.rate.post" false 1
exists after delete: 0
Go further
  • script generate merge gives the merge request of any existing script.
  • Chain several merge requests in one file to deploy a module.
  • After a merge, read the stack and delay options back with script get.
executed on MentDB

Back up a script before deleting it

Before a risky change, the create request of a password policy script is kept as a backup with its SHA-256. The script is deleted, restored with `eval`, and the definition is proven identical.

Pipeline
Script
admin
#1. A script someone is about to delete by mistake;
script create post "tuto.sc.password.policy" false 1
	(param
		(var "[pwd]" {type is_varchar [pwd] 64} "Candidate password" is_null:false is_empty:false "S3cure!pass")
	;)
	"Check the password policy"
{
	-> "[ok]" (and (>= (string length [pwd]) 10) (and (string matches [pwd] ".*[0-9].*") (string matches [pwd] ".*[A-Z].*")));
	if [ok] {"accepted";} {"rejected";};
} "accepted or rejected";
#2. Backup: the create request holds everything;
-> "[backup]" (script generate create "tuto.sc.password.policy.post");
-> "[sha]" (string sha_256 [backup]);
#3. Oops;
script delete "tuto.sc.password.policy.post";
-> "[gone]" (script exist "tuto.sc.password.policy.post");
#4. Restore from the backup and test again;
-> "[restored]" (eval [backup]);
-> "[same]" (equal [sha] (string sha_256 (script generate create "tuto.sc.password.policy.post")));
-> "[t]" (concat (execute "tuto.sc.password.policy.post" "[pwd]" "S3cure!passw0rd") " / " (execute "tuto.sc.password.policy.post" "[pwd]" "short1A"));
script delete "tuto.sc.password.policy.post";
concat "after delete: exists=" [gone] [_n_] "restore: " [restored] [_n_] "identical definition: " [same] [_n_] "tests: " [t];
Result
mentdb
after delete: exists=0
restore: Script added with successful.
identical definition: 1
tests: accepted / rejected
Go further
  • Store the backup in a tuto_ node or a file under tmp/ to keep it across sessions.
  • Compare two servers by hashing the generated create requests.
  • string matches is used for the password rules; string bcrypt_hash to store a password.
executed on MentDB

Copy, rename and delete a module by prefix

A team branches a small module: one script is copied to another method and renamed, the whole module is copied to v2 then promoted to stable, the module is listed from `script show`, and everything is deleted by prefix.

Pipeline
Script
admin
#1. A small module of two scripts;
script create post "tuto.sc.release.v1.price" false 1 (param (var "[x]" {type is_double [x]} "Net" is_null:false is_empty:false "10");) "Price" {math round (* [x] 1.2) 2;} "Gross";
script create get "tuto.sc.release.v1.stock" false 1 (param (var "[sku]" {type is_varchar [sku] 12} "Code" is_null:false is_empty:false "A1");) "Stock" {concat [sku] ": 12 in stock";} "Text";
#2. Copy one script to another method, then rename the copy;
script copy "tuto.sc.release.v1.price.post" get "tuto.sc.release.v1.price_preview";
script rename "tuto.sc.release.v1.price_preview.get" get "tuto.sc.release.v1.quote";
#3. Branch the module into v2, then promote v2 to stable - both prefixes end with a dot;
script copy all "tuto.sc.release.v1." "tuto.sc.release.v2.";
script rename all "tuto.sc.release.v2." "tuto.sc.release.stable.";
#4. List the scripts of the module;
json load "tuto_sc_all" (script show);
-> "[list]" "";
-> "[keys]" (json fields "tuto_sc_all" "/");
json load "tuto_sc_k" [keys];
for (-> "[i]" 0) (< [i] (json count "tuto_sc_k" "/")) (++ "[i]") {
	-> "[k]" (json select "tuto_sc_k" (concat "/[" [i] "]"));
	if (string matches [k] "tuto\.sc\.release\..*") {-> "[list]" (concat [list] [_n_] "- " [k]);};
};
json unload "tuto_sc_k";
json unload "tuto_sc_all";
#5. The stable copy answers like the original;
-> "[test]" (concat (execute "tuto.sc.release.stable.price.post" "[x]" 100) " / " (execute "tuto.sc.release.v1.quote.get" "[x]" 100));
#6. Delete the module by its prefix;
script delete all "tuto.sc.release.";
concat "Scripts:" [list] [_n_] "scripts without any group: " (script show ghost) [_n_] "stable price and quote of 100: " [test] [_n_] "left after delete all: " (script exist "tuto.sc.release.v1.price.post") " " (script exist "tuto.sc.release.stable.stock.get");
Result
mentdb
Scripts:
- tuto.sc.release.stable.quote.get
- tuto.sc.release.stable.price.post
- tuto.sc.release.stable.stock.get
- tuto.sc.release.v1.price.post
- tuto.sc.release.v1.quote.get
- tuto.sc.release.v1.stock.get
scripts without any group: {}
stable price and quote of 100: 120 / 120
left after delete all: 0 0
Go further
  • script copy all and script rename all keep the final dot: tuto.sc.release.v1. to tuto.sc.release.v2. turns v1.price.post into v2.price.post.
  • Use a prefix that only your scripts have before any all function.
  • script show ghost lists scripts without a group after such moves.
executed on MentDB

Stack and delay options of a batch script

A nightly export is run through the stack: priority 5, two runs in parallel, replayable after an error, and a delay condition that postpones it by 10 minutes before 1 am. The options are read back and the requests to replay them elsewhere are generated.

Pipeline
Script
admin
#1. A nightly export that may be retried 3 times by the stack;
script create post "tuto.sc.export.nightly" true 3
	(param
		(var "[day]" {type is_date [day]} "Day to export" is_null:false is_empty:false "2026-11-01")
	;)
	"Export the orders of one day"
{
	concat "export of " [day] " done";
} "A status sentence";
#2. Stack options: priority 5, 2 runs in parallel max, replayable after an error;
script set stack "tuto.sc.export.nightly.post" 5 2 true;
#3. Delay: run only after 1 am, otherwise the stack retries 10 minutes later;
script set delay "tuto.sc.export.nightly.post" 10 min {
	>= (date hour (date systime)) 1;
};
#4. Read the options back;
json load "tuto_sc_opt" (script get "tuto.sc.export.nightly.post");
-> "[opt]" (concat "priority=" (json select "tuto_sc_opt" "/priority") ", threads=" (json select "tuto_sc_opt" "/nb_in_thread") ", replayable=" (json select "tuto_sc_opt" "/is_replayable") ", attempts=" (json select "tuto_sc_opt" "/nbAttempt") ", log=" (json select "tuto_sc_opt" "/activateLog") ", delay=" (json select "tuto_sc_opt" "/delay_value") " " (json select "tuto_sc_opt" "/delay_type"));
json unload "tuto_sc_opt";
#5. The requests to replay these options on another server, and a stack call;
-> "[delay]" (string replace (script generate delay "tuto.sc.export.nightly.post") "j23i88m90m76i39t04r09y35p14a96y09e57t36" "");
-> "[stack]" (string regexp_replace (script generate stack "tuto.sc.export.nightly.post") "[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9:]{8}" "YYYY-MM-DD hh:mm:ss");
script delete "tuto.sc.export.nightly.post";
concat [opt] [_n_] [delay] [_n_] [stack];
Result
mentdb
priority=5, threads=2, replayable=1, attempts=3, log=1, delay=10 min
script set delay "tuto.sc.export.nightly.post" 10 min {
	>= (date hour (date systime)) 1;
};
script set stack "tuto.sc.export.nightly.post" 5 2 true;
stack_execute "YYYY-MM-DD hh:mm:ss" "tuto.sc.export.nightly.post"
	"[day]" "2026-11-01"
;
Go further
executed on MentDB

Grant a script to a group, then revert

A new price service is published to the existing group sample: groups and users before and after, the grant check, the user's rights, then the grant is revoked and the script deleted.

Pipeline
Script
admin
#1. A new web service and its default rights;
script create get "tuto.sc.catalog.price" false 1 (param (var "[sku]" {type is_varchar [sku] 12} "Product code" is_null:false is_empty:false "BK-0042");) "Public price of a product" {concat "{\"sku\": \"" [sku] "\", \"price\": 19.9}";} "JSON";
-> "[s]" "tuto.sc.catalog.price.get";
-> "[g]" "sample";
json load "tuto_sc_grp" (group get script [g]);
-> "[n0]" (json count "tuto_sc_grp" "/");
json unload "tuto_sc_grp";
json load "tuto_sc_g" (script show groups [s]);
-> "[before]" (concat "groups " (string regexp_replace (json fields "tuto_sc_g" "/") "\s+" "") ", in " [g] ": " (group is granted script [s] [g]));
json unload "tuto_sc_g";
#2. Publish it to the existing group sample;
-> "[grant]" (group grant script [s] [g]);
json load "tuto_sc_grp" (group get script [g]);
json load "tuto_sc_g" (script show groups [s]);
-> "[after]" (concat "groups " (string regexp_replace (json fields "tuto_sc_g" "/") "\s+" "") ", in " [g] ": " (group is granted script [s] [g]) ", scripts of " [g] ": " [n0] " -> " (json count "tuto_sc_grp" "/"));
json unload "tuto_sc_grp";
json unload "tuto_sc_g";
#3. Who can run it now;
json load "tuto_sc_usr" (script show users [s]);
-> "[users]" (concat "granted users " (string regexp_replace (json fields "tuto_sc_usr" "/") "\s+" "") ", admin granted: " (script is granted [s] "admin") ", admin in " [g] ": " (group is granted user "admin" [g]));
json unload "tuto_sc_usr";
#4. Revert: ungrant, check, delete;
-> "[revoke]" (group ungrant script [s] [g]);
-> "[final]" (concat "in " [g] ": " (group is granted script [s] [g]));
script delete [s];
concat [before] [_n_] [grant] [_n_] [after] [_n_] [users] [_n_] [revoke] [_n_] [final];
Result
mentdb
groups ["admin","sys"], in sample: 0
Script granted with successful.
groups ["admin","sys","sample"], in sample: 1, scripts of sample: 41 -> 42
granted users ["ai","admin","mentdb"], admin granted: 1, admin in sample: 1
Script ungranted with successful.
in sample: 0
Go further
executed on MentDB

Business types on the parameters of a service

A supplier onboarding service types its eleven parameters with business checks: SIRET, IBAN, BIC, French phone, URL, IPv4, date, hour, small int, Y/N boolean and JSON. The valid examples pass, then each field is sent once with a wrong value to see which check refuses it.

Pipeline
Script
admin
#1. A supplier onboarding service: every parameter has a business type;
script create post "tuto.sc.supplier.onboard" false 1
	(param
		(var "[siret]" {type is_siret [siret]} "SIRET of the company" is_null:false is_empty:false "732 829 320 00074")
		(var "[iban]" {type is_iban [iban]} "Bank account" is_null:false is_empty:false "FR76 3000 6000 0112 3456 7890 189")
		(var "[bic]" {type is_bic [bic]} "Bank BIC" is_null:false is_empty:false "BNPAFRPPXXX")
		(var "[phone]" {type is_phone_fr [phone]} "Phone" is_null:false is_empty:false "+262 692 12 34 56")
		(var "[site]" {type is_url [site]} "Web site" is_null:false is_empty:false "https://www.acme.test")
		(var "[ip]" {type is_ipv4 [ip]} "IP allowed to call the API" is_null:false is_empty:false "192.168.1.254")
		(var "[start]" {type is_valid_date [start] "yyyy-MM-dd"} "Contract start" is_null:false is_empty:false "2026-12-01")
		(var "[cutoff]" {type is_hour_without_sec [cutoff]} "Daily order cutoff" is_null:false is_empty:false "17:30")
		(var "[days]" {type is_small_int [days]} "Payment terms in days" is_null:false is_empty:false "30")
		(var "[vat]" {type is_bool [vat] "Y" "N"} "Subject to VAT, Y or N" is_null:false is_empty:false "Y")
		(var "[meta]" {type is_json [meta]} "Extra data" is_null:false is_empty:false "{\"tier\": 2}")
	;)
	"Register a supplier"
{
	concat "supplier " (string regexp_replace [siret] "\s" "") " registered, terms " [days] " days";
} "A confirmation";
#2. The valid example values, then one wrong value at a time;
-> "[good]" "732 829 320 00074|FR76 3000 6000 0112 3456 7890 189|BNPAFRPPXXX|+262 692 12 34 56|https://www.acme.test|192.168.1.254|2026-12-01|17:30|30|Y|{\"tier\": 2}";
-> "[bad]" "732 829 320 00075|FR76 3000 6000 0112 3456 7890 180|BNP1|0262 12|www acme|192.168.1.300|2026-02-30|17:75|70000|maybe|{tier: 2";
-> "[names]" "siret,iban,bic,phone,site,ip,start,cutoff,days,vat,meta";
-> "[out]" (concat "all valid: " (execute "tuto.sc.supplier.onboard.post" "[siret]" (atom get [good] 1 "|") "[iban]" (atom get [good] 2 "|") "[bic]" (atom get [good] 3 "|") "[phone]" (atom get [good] 4 "|") "[site]" (atom get [good] 5 "|") "[ip]" (atom get [good] 6 "|") "[start]" (atom get [good] 7 "|") "[cutoff]" (atom get [good] 8 "|") "[days]" (atom get [good] 9 "|") "[vat]" (atom get [good] 10 "|") "[meta]" (atom get [good] 11 "|")));
for (-> "[f]" 1) (<= [f] 11) (++ "[f]") {
	#3. Replace field f by its wrong value;
	for (-> "[i]" 1) (<= [i] 11) (++ "[i]") {
		env set var (concat "[v" [i] "]") (if (== [i] [f]) {atom get [bad] [i] "|"} {atom get [good] [i] "|"});
	};
	try {
		execute "tuto.sc.supplier.onboard.post" "[siret]" [v1] "[iban]" [v2] "[bic]" [v3] "[phone]" [v4] "[site]" [v5] "[ip]" [v6] "[start]" [v7] "[cutoff]" [v8] "[days]" [v9] "[vat]" [v10] "[meta]" [v11];
		-> "[out]" (concat [out] [_n_] (atom get [names] [f] ",") ": accepted?");
	} {
		-> "[out]" (concat [out] [_n_] (atom get [names] [f] ",") " '" (atom get [bad] [f] "|") "' refused by " (string regexp_extract [err] "\((type [a-z_0-9]+)" 1));
	} "[err]";
};
script delete "tuto.sc.supplier.onboard.post";
[out];
Result
mentdb
all valid: supplier 73282932000074 registered, terms 30 days
siret '732 829 320 00075' refused by type is_siret
iban 'FR76 3000 6000 0112 3456 7890 180' refused by type is_iban
bic 'BNP1' refused by type is_bic
phone '0262 12' refused by type is_phone_fr
site 'www acme' refused by type is_url
ip '192.168.1.300' refused by type is_ipv4
start '2026-02-30' refused by type is_valid_date
cutoff '17:75' refused by type is_hour_without_sec
days '70000' refused by type is_small_int
vat 'maybe' refused by type is_bool
meta '{tier: 2' refused by type is_json
Go further
  • SIRET and IBAN carry a check key: one wrong digit is enough to be refused.
  • type is_valid_date takes a Java pattern: 2026-02-30 is refused.
  • Use type is_bool [x] "Y" "N" to accept your own true and false words.

Also in Programming

MB
MQL beginner
Variables, operators, if, loops and your first try/exception: learn MQL step by step.
10 recipes
MD
MQL developer
Atom lists, data types, constants, translations, session variables and error patterns.
10 recipes
BW
Backend & web service developer
Typed scripts, execute / call / include, generated calls and URLs, parameters and logs.
10 recipes
DT
Date & time developer
Calendars, business days, time zones, ISO and epoch, durations, quarters and week numbers.
10 recipes
SE
String & encoding developer
Cleaning, case styles, regex, URL/HTML/Base64 encodings, number bases and encryption.
10 recipes
DV
Data validation developer
IBAN, BIC, SIRET, Luhn, email, IP, UUID, JSON and date checks gathered in a validation report.
10 recipes
JD
JSON & document store developer
In-memory JSON documents, persistent nodes and sequences: build, walk, sort, clean up.
10 recipes
EM
Execution modes
execute, call, include, sub_include, parallel, stack: which environment, which checks, which logs.
11 recipes
WS
Web service developer
A small REST resource with get, post, put and delete scripts: validation, JSON answers, codes, grants, URLs and API docs.
10 recipes
AD
Async developer
Asynchronous processing with the stack: push runs now or later, priorities and parallel limits, errors and replays, flows and cleanup.
10 recipes
SD
Scheduler developer
Cron jobs and delayed runs: add, resume, pause, update and remove jobs, prove they ran, and postpone stack runs with a delay condition.
10 recipes
ED
Event developer
Event-driven scripts: file watchers that push each file event to the stack, validation, archiving, errors and safe restarts.
10 recipes
CC
Configuration & connections
Parameters (private, described, locked, deployed), environment variables, sessions and connections used by your scripts.
12 recipes
VD
Versioning & deployment
Group your scripts, commit, compare, roll back, export, import and package a release.
10 recipes
DE
Debugging & error handling
Coded errors with try/exception, logs with correlation ids, timing, metrics and defensive checks.
10 recipes
SD
SQL developer in MQL
Connections, DDL and DML with sql encode, transactions, cursors, parse, and exports to JSON, CSV, Excel and PDF on a real local database. Run the recipes in order: the first one creates the tuto_db_h2 database.
10 recipes
CD
CRUD & data API developer
Generate SCRUD scripts from a table, patch and install them, write typed read and write APIs, check data quality and manage connections per environment. Uses the tuto_db_h2 database of the SQL developer recipes; the last recipe removes it.
10 recipes
WA
Web application developer
Create a web app, its host name, pages, ajax forms, menu and grants, then remove it all.
10 recipes
AI
Algorithms in MQL
Sorting, searching, recursion, data structures and number algorithms: MQL is a real programming language.
10 recipes
CF
Control flow
if, if force, case, switch, mode, for, while, repeat, break, continue, parallel and synchronized on real cases.
10 recipes
TM
The MQL language in depth
Data types and null, booleans, comments, variables and scope, eval and mql blocks, coded errors and try.
10 recipes
SU
Screen & UI developer (sajax)
Build the screens of MentDB web apps in MQL: Bootstrap grids, forms and every control, ajax buttons, alerts, refreshes and charts.
10 recipes