- Build an order document and its ETag
- Sort and total a sales array
- Extract and clean a config subtree
- Feature flags stored in a node
- Invoice numbers from a sequence
- Merge the pages of a paginated API without duplicates
- Validate a payload against field rules and answer 422
- Export a JSON array to a CSV file and read it back
- Diff the configuration of staging and production
- A report cached in a node with a time to live
Build an order document and its ETag
An e-commerce API builds an order in memory: id, status, lines and tags, then fixes the status and a tag. The script returns the JSON document with an MD5 ETag and a SHA-256 fingerprint for HTTP caching.
#1. Start an empty order document in memory; json load "rx_order" "{}"; json iobject "rx_order" / id (concat "ORD-" (date format "2026-11-01" "yyyy-MM-dd" "yyyyMMdd") "-0042") STR; json iobject "rx_order" / status "draft" STR; json iobject "rx_order" / paid false BOOL2; json iobject "rx_order" / lines "[]" ARRAY; json iobject "rx_order" / tags "[]" ARRAY; #2. Add lines, insert a tag at the top, then fix values; json iarray "rx_order" /lines "{\"sku\": \"EC27\", \"qty\": 1, \"price\": 249.0}" OBJ; json iarray "rx_order" /lines "{\"sku\": \"KB01\", \"qty\": 2, \"price\": 39.9}" OBJ; json iarray "rx_order" /tags "web" STR; json iarray "rx_order" /tags 0 "priority" STR; json uobject "rx_order" / status "confirmed" STR; json uarray "rx_order" /tags 1 "mobile" STR; #3. ETag of the final document for HTTP caching; -> "[doc]" (json doc "rx_order"); json iobject "rx_order" / etag (string md5 [doc]) STR; json iobject "rx_order" / sha256 (string left (string sha_256 [doc]) 16) STR; -> "[doc]" (json doc "rx_order"); json unload "rx_order"; [doc]
{
"sha256": "57ad441fc739cdb4",
"paid": false,
"etag": "e38a1dfc5ba8f0746fcd1b812d6fc6a3",
"id": "ORD-20261101-0042",
"lines": [
{
"price": 249.0,
"qty": 1,
"sku": "EC27"
},
{
"price": 39.9,
"qty": 2,
"sku": "KB01"
}
],
"status": "confirmed",
"tags": [
"priority",
"mobile"
]
}- Compute the total with
json parse_arrayon /lines string md5drops the leading zeros of the hash: pad it withstring lpadto 32 characters
Sort and total a sales array
An API returns the sales of four reps. The script sorts the rows by amount, walks the array to total it and to rank the reps, and checks the structure of the document.
#1. Sales returned by an API; json load "rx_sales" "{\"region\": \"Reunion\", \"rows\": [{\"rep\": \"Lea\", \"amount\": 1200}, {\"rep\": \"Tom\", \"amount\": 3400}, {\"rep\": \"Ana\", \"amount\": 2150}, {\"rep\": \"Max\", \"amount\": 800}]}"; #2. Sort by amount, then walk the array to total it; json sort "rx_sales" /rows amount DESC; -> "[total]" 0; -> "[names]" ""; json parse_array "rx_sales" "/rows" "[row]" { json load "rx_row" [row]; -> "[total]" (+ [total] (json select "rx_row" /amount)); -> "[names]" (concat [names] (json select "rx_row" /rep) " "); }; #3. Structure checks and a summary; -> "[out]" (concat "{\"loaded\": " (json show) ", \"exists\": " (json exist "rx_sales") ", \"fields\": " (string replace (string replace (json fields "rx_sales" /) [_n_] "") " " "") ", \"rows_is_array\": " (json is array "rx_sales" /rows) ", \"root_is_object\": " (json is object "rx_sales" /) ", \"count\": " (json count "rx_sales" /rows) ", \"best\": \"" (json select "rx_sales" /rows[0]/rep) "\", \"ranking\": \"" (string trim [names]) "\", \"total\": " [total] "}"); json unload "rx_row"; json unload "rx_sales"; [out]
{"loaded": [
"rx_row",
"rx_sales"
], "exists": 1, "fields": ["region","rows"], "rows_is_array": 1, "root_is_object": 1, "count": 4, "best": "Tom", "ranking": "Tom Ana Lea Max", "total": 7550}- Chart the ranking with
plot xy(rep index, amount) after the sort json counton /rows gives the number of reps for an average
Extract and clean a config subtree
A config file holds a db section with a password, a debug flag and a legacy module. The script reads the port directly, extracts the db subtree by reference, removes the secret, the flag and the legacy module, lists the db settings and unloads all documents.
#1. A config file: read one value directly and extract the db subtree; -> "[conf]" "{\"app\": \"shop\", \"debug\": true, \"db\": {\"host\": \"localhost\", \"port\": 3306, \"password\": \"secret\"}, \"modules\": [\"cart\", \"legacy\", \"pay\"]}"; -> "[port]" (json load select "rx_conf" [conf] /db/port); json load by ref "rx_conf" /db "rx_db"; #2. Remove secrets, the debug flag and the legacy module; json dobject "rx_db" / password; json dobject "rx_conf" / debug; json darray "rx_conf" /modules 1; #3. List the db settings as key=value lines; -> "[kv]" ""; json parse_obj "rx_db" "/" "[k]" "[v]" { -> "[kv]" (concat [kv] [k] "=" [v] ";"); }; -> "[out]" (concat "port=" [port] " | db: " [kv] " | modules: " (json count "rx_conf" /modules) " | conf_loaded: " (json exist "rx_conf")); json unload_all; concat [out] " | after unload_all: " (json exist "rx_conf")
port=3306 | db: port=3306;host=localhost; | modules: 2 | conf_loaded: 1 | after unload_all: 0
- Write the cleaned config with
file createunder tmp/ before sharing it json darrayshifts the indexes: delete from the end when you remove several items
Feature flags stored in a node
An app keeps its feature flags in a persistent node: a version, a dark mode switch, a list of beta testers and an old flag. The script creates the node, edits the list and the version, removes the old flag, reads it back with a fingerprint and deletes the test node.
#1. Persist the feature flags of an app in a node; node create "rx_flags_shop"; node iobject "rx_flags_shop" / version 1 INT; node iobject "rx_flags_shop" / dark_mode true BOOL2; node iobject "rx_flags_shop" / beta "[]" ARRAY; node iobject "rx_flags_shop" / legacy_menu true BOOL2; #2. Beta testers list, a new release and removal of the old flag; node iarray "rx_flags_shop" /beta "lea@shop.re" STR; node iarray "rx_flags_shop" /beta "tom@shop.re" STR; node iarray "rx_flags_shop" /beta 0 "ana@shop.re" STR; node uobject "rx_flags_shop" / version 2 INT; node uarray "rx_flags_shop" /beta 2 "max@shop.re" STR; node darray "rx_flags_shop" /beta 1; node dobject "rx_flags_shop" / legacy_menu; #3. Read back, fingerprint, then delete the test node; -> "[out]" (concat "{\"exists\": " (node exist "rx_flags_shop") ", \"fields\": " (string replace (string replace (node fields "rx_flags_shop" /) [_n_] "") " " "") ", \"version\": " (node select "rx_flags_shop" /version) ", \"beta_is_array\": " (node is array "rx_flags_shop" /beta) ", \"root_is_object\": " (node is object "rx_flags_shop" /) ", \"testers\": " (node count "rx_flags_shop" /beta) ", \"sha1\": \"" (string sha (node show "rx_flags_shop")) "\", \"sha512_prefix\": \"" (string left (string sha_512 (node show "rx_flags_shop")) 12) "\"}"); -> "[node]" (node show "rx_flags_shop"); node delete "rx_flags_shop"; concat [out] [_n_] [node] [_n_] "deleted: " (not (node exist "rx_flags_shop"))
{"exists": 1, "fields": ["dark_mode","version","key","beta"], "version": 2, "beta_is_array": 1, "root_is_object": 1, "testers": 2, "sha1": "b890e74c84ebf07d53565de9b82a1b3f5b46f945", "sha512_prefix": "43d7cc8713aa"}
{
"dark_mode": true,
"version": 2,
"key": "rx_flags_shop",
"beta": [
"ana@shop.re",
"max@shop.re"
]
}
deleted: 1- Real nodes are kept across restarts: here the rx_ node is deleted at the end
- Compare
string shaof the node with a stored value to detect changes
Invoice numbers from a sequence
Invoices need gap-free numbers. The script creates a sequence, takes two numbers, jumps after a data migration, generates the MQL to restore the counter elsewhere and removes the test sequence.
#1. A sequence for the invoice numbers of 2026; if (not (sequence exist "rx_invoice_2026")) {sequence add "rx_invoice_2026" 0;}; -> "[a]" (sequence increment "rx_invoice_2026"); -> "[b]" (sequence increment "rx_invoice_2026"); #2. Jump after a migration, then take the next value; sequence update "rx_invoice_2026" 675; -> "[c]" (sequence increment "rx_invoice_2026"); -> "[cur]" (sequence get current "rx_invoice_2026"); #3. Script to restore it elsewhere, then clean up; -> "[restore]" (sequence generate update "rx_invoice_2026"); -> "[listed]" (string contains (sequence show) "rx_invoice_2026"); sequence remove "rx_invoice_2026"; concat "{\"numbers\": [\"INV-2026-" (string lpad [a] "0" 4) "\", \"INV-2026-" (string lpad [b] "0" 4) "\", \"INV-2026-" (string lpad [c] "0" 4) "\"], \"current\": \"" [cur] "\", \"restore\": \"" (string json_escape [restore]) "\", \"was_listed\": " [listed] ", \"exists_after_remove\": " (sequence exist "rx_invoice_2026") "}"
{"numbers": ["INV-2026-0001", "INV-2026-0002", "INV-2026-0676"], "current": "676", "restore": "sequence update \"rx_invoice_2026\" \"676\";", "was_listed": 1, "exists_after_remove": 0}- Use one sequence per year and build the number with
string lpad sequence showlists all counters for a backup
Merge the pages of a paginated API without duplicates
A REST API returns its customers page by page with a next cursor, and two pages overlap. The script follows the cursor with a while loop, keeps each id once, sorts the merged array by update date and summarises it.
#1. Three pages returned by a paginated API, page 2 and 3 overlap; -> "[pages]" "{\"items\": [{\"id\": 101, \"name\": \"Lea\", \"updated\": \"2026-10-28\"}, {\"id\": 102, \"name\": \"Tom\", \"updated\": \"2026-10-30\"}], \"next\": 2}|{\"items\": [{\"id\": 103, \"name\": \"Ana\", \"updated\": \"2026-10-25\"}, {\"id\": 104, \"name\": \"Max\", \"updated\": \"2026-10-31\"}], \"next\": 3}|{\"items\": [{\"id\": 104, \"name\": \"Max\", \"updated\": \"2026-10-31\"}, {\"id\": 105, \"name\": \"Zoe\", \"updated\": \"2026-10-29\"}], \"next\": null}"; #2. Follow the next cursor and append the items not seen yet; json load "rx_p4m_all" "[]"; -> "[seen]" ""; -> "[page]" 1; -> "[calls]" 0; while (not (is null [page])) { ++ "[calls]"; json load "rx_p4m_page" (atom get [pages] [page] "|"); json parse_array "rx_p4m_page" "/items" "[item]" { json load "rx_p4m_item" [item]; -> "[id]" (json select "rx_p4m_item" /id); if (> (atom find [seen] [id] ",") 0) {continue;}; -> "[seen]" (concat [seen] [id] ","); json iarray "rx_p4m_all" / [item] OBJ; }; -> "[page]" (json select "rx_p4m_page" /next); }; #3. Newest first, then a summary; json sort "rx_p4m_all" / updated DESC; -> "[out]" (concat "API calls: " [calls] ", unique items: " (json count "rx_p4m_all" /) ", newest: " (json select "rx_p4m_all" /[0]/name) " " (json select "rx_p4m_all" /[0]/updated) [_n_] (string regexp_replace (json doc "rx_p4m_all") "\s+" "")); json unload "rx_p4m_item"; json unload "rx_p4m_page"; json unload "rx_p4m_all"; [out]
API calls: 3, unique items: 5, newest: Max 2026-10-31
[{"name":"Max","id":104,"updated":"2026-10-31"},{"name":"Tom","id":102,"updated":"2026-10-30"},{"name":"Zoe","id":105,"updated":"2026-10-29"},{"name":"Lea","id":101,"updated":"2026-10-28"},{"name":"Ana","id":103,"updated":"2026-10-25"}]- In a real client, replace the
atom geton the sample pages byrest httpswith the page number in the URL. continueskips the rest of ajson parse_arrayiteration: here it ignores a duplicate without nesting an if.
Validate a payload against field rules and answer 422
Before saving a sign-up request, a web service checks each field against a small rules document (email, integer, date, enum, required) and refuses unknown fields. It returns a 422 body that lists every error at once.
#1. The incoming payload and the rules of the endpoint; json load "rx_p4m_in" "{\"email\": \"lea.payet@example\", \"age\": \"34\", \"birth\": \"1992-02-30\", \"plan\": \"gold\", \"debug\": true}"; json load "rx_p4m_rules" "{\"email\": \"email\", \"age\": \"integer\", \"birth\": \"date\", \"plan\": \"enum:free,pro,team\", \"country\": \"required\"}"; -> "[fields]" (json fields "rx_p4m_in" /); json load "rx_p4m_err" "[]"; #2. Check every rule, collect the errors; json parse_obj "rx_p4m_rules" "/" "[field]" "[rule]" { if (not (string contains [fields] (concat "\"" [field] "\""))) { json iarray "rx_p4m_err" / (concat "{\"field\": \"" [field] "\", \"error\": \"missing\"}") OBJ; continue; }; -> "[v]" (json select "rx_p4m_in" (concat "/" [field])); -> "[ok]" (case ((equal [rule] "email")) {type is_email [v]} ((equal [rule] "integer")) {type is_integer [v] 3} ((equal [rule] "date")) {type is_date [v]} ((string starts_with [rule] "enum:")) {type is_enum [v] (string substring [rule] 5)} {1} ;); if (not [ok]) { json iarray "rx_p4m_err" / (concat "{\"field\": \"" [field] "\", \"error\": \"" (string json_escape (concat "not a valid " [rule] ": " [v])) "\"}") OBJ; }; }; #3. Unknown fields are refused too; json parse_obj "rx_p4m_in" "/" "[field]" "[v]" { if (not (string contains (json fields "rx_p4m_rules" /) (concat "\"" [field] "\""))) { json iarray "rx_p4m_err" / (concat "{\"field\": \"" [field] "\", \"error\": \"unknown field\"}") OBJ; }; }; #4. The HTTP answer; -> "[n]" (json count "rx_p4m_err" /); -> "[out]" (concat "{\"status\": " (if (> [n] 0) {422} {200}) ", \"errors\": " (string regexp_replace (json doc "rx_p4m_err") "\n\s*" "") "}"); json unload "rx_p4m_in"; json unload "rx_p4m_rules"; json unload "rx_p4m_err"; [out]
{"status": 422, "errors": [{"field": "country","error": "missing"},{"field": "birth","error": "not a valid date: 1992-02-30"},{"field": "plan","error": "not a valid enum:free,pro,team: gold"},{"field": "email","error": "not a valid email: lea.payet@example"},{"field": "debug","error": "unknown field"}]}type is_daterefuses 1992-02-30: the calendar is checked, not only the format.- Put the rules document in a server parameter with
parameter get valueto share it between the POST and the PUT.
Export a JSON array to a CSV file and read it back
An accounting tool wants the customers of the API as CSV. The script streams one line per object with a file writer, quotes the values that need it with string csv_value, then parses the file back as the consumer would.
#1. Customers returned by the API, some values contain the separator or quotes; json load "rx_p4m_cust" "[{\"id\": 1, \"name\": \"Payet, Lea\", \"city\": \"Saint-Denis\", \"note\": \"VIP\"}, {\"id\": 2, \"name\": \"Tom Hoarau\", \"city\": \"Le Port\", \"note\": \"said 'call me'\"}, {\"id\": 3, \"name\": \"Ana Grondin\", \"city\": \"Saint-Pierre\", \"note\": \"\"}]"; #2. The column order of the export; -> "[cols]" "id,name,city,note"; #3. Stream one CSV line per object with a writer, it does not add the line end; file writer_open "rx_p4m_w" "tmp/tuto_p4m_customers.csv" false TEXT "utf-8"; file writer_add_line "rx_p4m_w" (concat [cols] [_n_]); json parse_array "rx_p4m_cust" "/" "[row]" { json load "rx_p4m_row" [row]; -> "[line]" ""; for (-> "[c]" 1) (<= [c] (atom size [cols] ",")) (++ "[c]") { -> "[cell]" (string csv_value (json select "rx_p4m_row" (concat "/" (atom get [cols] [c] ","))) "," "\""); -> "[line]" (concat [line] (if (equal [c] 1) {""} {","}) [cell]); }; file writer_add_line "rx_p4m_w" (concat [line] [_n_]); }; file writer_close "rx_p4m_w"; #4. Read it back as a consumer would; -> "[check]" ""; csv parse (mql "C") (mql "tmp/tuto_p4m_customers.csv") (mql ",") (mql "\"") (mql "") { -> "[check]" (concat [check] [C_id] " | " [C_name] " | " [C_note] [_n_]); }; -> "[out]" (concat (file load "tmp/tuto_p4m_customers.csv") "--- parsed back, " (file count_lines "tmp/tuto_p4m_customers.csv") " lines:" [_n_] [check]); file delete "tmp/tuto_p4m_customers.csv"; json unload "rx_p4m_row"; json unload "rx_p4m_cust"; [out]
id,name,city,note 1,"Payet, Lea",Saint-Denis,VIP 2,Tom Hoarau,Le Port,said 'call me' 3,Ana Grondin,Saint-Pierre, --- parsed back, 4 lines: 1 | Payet, Lea | VIP 2 | Tom Hoarau | said 'call me' 3 | Ana Grondin |
file writer_add_linewrites the string as is: add[_n_]yourself, or[_r_][_n_]for Excel on Windows.- With an empty forceColumnNames,
csv parsetakes the column names from the first line:[C_id],[C_name]...
Diff the configuration of staging and production
Before a release, the developer compares two configuration documents section by section: values that differ, keys present only on one side and whole missing sections, like a JSON diff.
#1. The config of staging and of production; json load "rx_p4m_stg" "{\"api\": {\"timeout\": 30, \"retries\": 5, \"debug\": true}, \"db\": {\"pool\": 10, \"host\": \"db-stg\"}, \"cache\": {\"ttl\": 60}}"; json load "rx_p4m_prd" "{\"api\": {\"timeout\": 30, \"retries\": 3}, \"db\": {\"pool\": 50, \"host\": \"db-prd\", \"ssl\": true}, \"mail\": {\"from\": \"noreply\"}}"; -> "[diff]" ""; #2. Walk the sections of staging, compare each key with production; json parse_obj "rx_p4m_stg" "/" "[sec]" "[obj]" { if (not (string contains (json fields "rx_p4m_prd" /) (concat "\"" [sec] "\""))) { -> "[diff]" (concat [diff] "- " [sec] " section only in staging" [_n_]); continue; }; #A copy of the production section, not a reference: dobject must not touch production; json load "rx_p4m_rest" (json select "rx_p4m_prd" (concat "/" [sec])); json load "rx_p4m_s" [obj]; json parse_obj "rx_p4m_s" "/" "[k]" "[v]" { -> "[p]" (concat "/" [sec] "/" [k]); if (not (string contains (json fields "rx_p4m_rest" /) (concat "\"" [k] "\""))) { -> "[diff]" (concat [diff] "- " [p] " = " [v] " only in staging" [_n_]); } { -> "[pv]" (json select "rx_p4m_prd" [p]); if (not (equal [v] [pv])) {-> "[diff]" (concat [diff] "~ " [p] ": " [v] " -> " [pv] [_n_]);}; json dobject "rx_p4m_rest" / [k]; }; }; #What is left in the copy exists only in production; json parse_obj "rx_p4m_rest" "/" "[k]" "[v]" {-> "[diff]" (concat [diff] "+ /" [sec] "/" [k] " = " [v] " only in production" [_n_]);}; }; #3. Sections added in production; json parse_obj "rx_p4m_prd" "/" "[sec]" "[obj]" { if (not (string contains (json fields "rx_p4m_stg" /) (concat "\"" [sec] "\""))) {-> "[diff]" (concat [diff] "+ " [sec] " section only in production" [_n_]);}; }; -> "[out]" (concat [diff] "production untouched: " (json select "rx_p4m_prd" /db/pool) " connections, ssl " (json select "rx_p4m_prd" /db/ssl)); json unload "rx_p4m_s"; json unload "rx_p4m_rest"; json unload "rx_p4m_stg"; json unload "rx_p4m_prd"; [out]
- cache section only in staging ~ /api/retries: 5 -> 3 - /api/debug = true only in staging ~ /db/pool: 10 -> 50 ~ /db/host: db-stg -> db-prd + /db/ssl = true only in production + mail section only in production production untouched: 50 connections, ssl true
json load by refwould share the section with production: ajson dobjecton it deletes the key in production too. Load a copy withjson selectwhen you modify it.- Write the diff lines in the release log with
log writebefore deploying.
A report cached in a node with a time to live
A slow report is computed once and stored in a persistent node with its build time. Calls within the TTL are served from the node, and the node keeps only the last three accesses for debugging.
#1. A slow report, cached in a node for ttl seconds; script create exe "tuto.p4m.report.cached" false 1 (param (var "[ttl]" {type is_integer [ttl] 4} "Time to live of the cache in seconds" is_null:false is_empty:false "3") ;) "Sales report, served from the cache when it is fresh" { -> "[now]" (date current_ms); if (not (node exist "tuto_p4m_cache")) { node create "tuto_p4m_cache"; node iobject "tuto_p4m_cache" / built 0 NUM; node iobject "tuto_p4m_cache" / log "[]" ARRAY; }; -> "[age]" (/ (- [now] (node select "tuto_p4m_cache" /built)) 1000); if (> [age] [ttl]) { #Miss: compute and store the report; wait 400; node uobject "tuto_p4m_cache" / report (stat summary "[120, 340, 95, 410, 280]") OBJ; node uobject "tuto_p4m_cache" / built [now] NUM; -> "[state]" "miss"; } { -> "[state]" "hit"; }; #Keep the last 3 accesses only; node iarray "tuto_p4m_cache" /log (concat [state] " in " (- (date current_ms) [now]) " ms") STR; if (> (node count "tuto_p4m_cache" /log) 3) {node darray "tuto_p4m_cache" /log 0;}; concat [state] ", mean " (node select "tuto_p4m_cache" /report/mean) ", median " (node select "tuto_p4m_cache" /report/median); } "The report and the cache state"; #2. Four calls: miss, hit, hit, then after the ttl a miss again; -> "[out]" ""; for (-> "[i]" 1) (<= [i] 4) (++ "[i]") { if (equal [i] 4) {wait 3500;}; -> "[out]" (concat [out] "call " [i] ": " (execute "tuto.p4m.report.cached.exe" "[ttl]" 3) [_n_]); }; -> "[out]" (concat [out] "last " (node count "tuto_p4m_cache" /log) " accesses: " (string regexp_replace (node select "tuto_p4m_cache" /log) "\n\s*" "")); node delete "tuto_p4m_cache"; script delete "tuto.p4m.report.cached.exe"; [out]
call 1: miss, mean 249, median 280 call 2: hit, mean 249, median 280 call 3: hit, mean 249, median 280 call 4: miss, mean 249, median 280 last 3 accesses: ["hit in 1 ms","hit in 0 ms","miss in 413 ms"]
- A node survives a server restart, unlike a
json loadkey: use it for caches shared by all sessions. - Invalidate the cache from another script with
node uobject "tuto_p4m_cache" / built 0 NUM.
