@xano/developer-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +261 -0
  3. package/api_docs/addon.md +193 -0
  4. package/api_docs/agent.md +154 -0
  5. package/api_docs/api_group.md +236 -0
  6. package/api_docs/authentication.md +68 -0
  7. package/api_docs/file.md +190 -0
  8. package/api_docs/function.md +217 -0
  9. package/api_docs/history.md +263 -0
  10. package/api_docs/index.md +104 -0
  11. package/api_docs/mcp_server.md +139 -0
  12. package/api_docs/middleware.md +205 -0
  13. package/api_docs/realtime.md +153 -0
  14. package/api_docs/table.md +151 -0
  15. package/api_docs/task.md +191 -0
  16. package/api_docs/tool.md +216 -0
  17. package/api_docs/triggers.md +344 -0
  18. package/api_docs/workspace.md +246 -0
  19. package/dist/index.d.ts +2 -0
  20. package/dist/index.js +495 -0
  21. package/package.json +49 -0
  22. package/xanoscript_docs/README.md +1 -0
  23. package/xanoscript_docs/api_query_examples.md +1255 -0
  24. package/xanoscript_docs/api_query_guideline.md +129 -0
  25. package/xanoscript_docs/build_from_lovable.md +715 -0
  26. package/xanoscript_docs/db_query_guideline.md +427 -0
  27. package/xanoscript_docs/ephemeral_environment_guideline.md +529 -0
  28. package/xanoscript_docs/expression_guideline.md +1086 -0
  29. package/xanoscript_docs/frontend_guideline.md +67 -0
  30. package/xanoscript_docs/function_examples.md +1406 -0
  31. package/xanoscript_docs/function_guideline.md +130 -0
  32. package/xanoscript_docs/functions.md +2155 -0
  33. package/xanoscript_docs/input_guideline.md +227 -0
  34. package/xanoscript_docs/mcp_server_examples.md +36 -0
  35. package/xanoscript_docs/mcp_server_guideline.md +69 -0
  36. package/xanoscript_docs/query_filter.md +489 -0
  37. package/xanoscript_docs/table_examples.md +586 -0
  38. package/xanoscript_docs/table_guideline.md +137 -0
  39. package/xanoscript_docs/task_examples.md +511 -0
  40. package/xanoscript_docs/task_guideline.md +103 -0
  41. package/xanoscript_docs/tips_and_tricks.md +144 -0
  42. package/xanoscript_docs/tool_examples.md +69 -0
  43. package/xanoscript_docs/tool_guideline.md +139 -0
  44. package/xanoscript_docs/unit_testing_guideline.md +328 -0
  45. package/xanoscript_docs/version.json +3 -0
  46. package/xanoscript_docs/workspace.md +17 -0
@@ -0,0 +1,1406 @@
1
+ ---
2
+ applyTo: "functions/**/*.xs"
3
+ ---
4
+
5
+ # Xanoscript Function Examples
6
+
7
+ Below are some examples of custom functions defined in Xanoscript.
8
+
9
+ Function can be called using
10
+
11
+ ```xs
12
+ function.run "add_numbers" {
13
+ input = { a: 5, b: $input.b }
14
+ } as $return_value
15
+ ```
16
+
17
+ ## youtube_url_analyzer_utility
18
+
19
+ ```xs
20
+ function "utilities/youtube_url_parser" {
21
+ description = "This utility function parses various YouTube URL formats (including short, mobile, and embed links) to extract the video ID. It then uses the ID to generate a list of corresponding thumbnail URLs in multiple quality options, all while providing robust input validation."
22
+ input {
23
+ text youtube_url? filters=trim
24
+ }
25
+
26
+ stack {
27
+ var $youtube_id {
28
+ value = ("/^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$/"|regex_get_all_matches:$input.youtube_url)[5]|first
29
+ }
30
+
31
+ precondition (($youtube_id|strlen) > 5) {
32
+ error = "No valid ID found"
33
+ }
34
+
35
+ var $default_thumbnail {
36
+ description = "Default quality thumbnail URL"
37
+ value = "https://img.youtube.com/vi/" ~ $youtube_id ~ "/default.jpg"
38
+ }
39
+
40
+ var $medium_thumbnail {
41
+ description = "Medium quality thumbnail URL"
42
+ value = "https://img.youtube.com/vi/" ~ $youtube_id ~ "/mqdefault.jpg"
43
+ }
44
+
45
+ var $high_thumbnail {
46
+ description = "High quality thumbnail URL"
47
+ value = "https://img.youtube.com/vi/" ~ $youtube_id ~ "/hqdefault.jpg"
48
+ }
49
+
50
+ var $standard_thumbnail {
51
+ description = "Standard quality thumbnail URL"
52
+ value = "https://img.youtube.com/vi/" ~ $youtube_id ~ "/sddefault.jpg"
53
+ }
54
+
55
+ var $maxres_thumbnail {
56
+ description = "Maximum resolution thumbnail URL"
57
+ value = "https://img.youtube.com/vi/" ~ $youtube_id ~ "/maxresdefault.jpg"
58
+ }
59
+
60
+ var $thumbnail_urls {
61
+ description = "Object containing different quality thumbnail URLs"
62
+ value = {
63
+ default: $default_thumbnail
64
+ medium: $medium_thumbnail
65
+ high: $high_thumbnail
66
+ standard: $standard_thumbnail
67
+ maxres: $maxres_thumbnail
68
+ }
69
+ }
70
+ }
71
+
72
+ response = {
73
+ youtube_id: $youtube_id,
74
+ thumbnail_urls: $thumbnail_urls
75
+ }
76
+ }
77
+ ```
78
+
79
+ ## get_linear_labels_paginated
80
+
81
+ ```xs
82
+ function "Linear/GetLabelsPaginated" {
83
+ input {
84
+ }
85
+
86
+ stack {
87
+ group {
88
+ description = "Declare Initial Variables"
89
+ stack {
90
+ var $labels {
91
+ description = "Empty array to collect the labels with each loop"
92
+ value = []
93
+ }
94
+
95
+ var $has_next_page {
96
+ description = "Start with a true value for the initial loop"
97
+ value = true
98
+ }
99
+
100
+ var $cursor {
101
+ description = "The cursor will be provided by the first API call"
102
+ value = null
103
+ }
104
+ }
105
+ }
106
+
107
+ while ($has_next_page) {
108
+ each {
109
+ api.request {
110
+ description = "Get Labels from Linear"
111
+ url = "https://api.linear.app/graphql"
112
+ method = "POST"
113
+ params = {}|set:"query":"""
114
+ query ($per_page: Int, $after: String) {
115
+ issueLabels(first:$per_page, after:$after) {
116
+ pageInfo {
117
+ hasNextPage
118
+ endCursor
119
+ }
120
+ nodes {
121
+ id
122
+ name
123
+ parent {
124
+ id
125
+ name
126
+ }
127
+ team {
128
+ name
129
+ id
130
+ }
131
+ }
132
+ }
133
+ }
134
+ """|set:"variables":({}|set:"per_page":50|set:"after":$cursor)
135
+
136
+ headers = []|push:"Content-Type: application/json"|push:"Authorization: " ~ $env.linear_key
137
+ } as $api_labels
138
+
139
+ var $has_next_page {
140
+ description = "Check the pagination to see if there's another page"
141
+ value = $api_labels|get:"data.issueLabels.pageInfo.hasNextPage":null
142
+ }
143
+
144
+ var $cursor {
145
+ description = "Get the current page cursor and store it for the next loop"
146
+ value = $api_labels|get:"data.issueLabels.pageInfo.endCursor":null
147
+ }
148
+
149
+ array.merge $labels {
150
+ description = "Merge the labels from this loop into the main Labels array"
151
+ value = $api_labels|get:"data.issueLabels.nodes":null
152
+ }
153
+ }
154
+ }
155
+ }
156
+
157
+ response = $labels
158
+ }
159
+ ```
160
+
161
+ ## phone_number_formatter
162
+
163
+ ```xs
164
+ function "utilities/phone_number_formatter" {
165
+ description = "Format phone numbers into standardized US format with basic validation"
166
+ input {
167
+ text phone_number filters=min:10|max:10|digitOk {
168
+ description = "Raw phone number string (10 digits only for this version)"
169
+ }
170
+
171
+ enum? format_type?=standard {
172
+ values = ["standard", "dotted", "spaced"]
173
+ description = "Format type: 'standard', 'dotted', 'spaced'. Defaults to 'standard'"
174
+ }
175
+ }
176
+
177
+ stack {
178
+ var $area_code {
179
+ description = "Extract area code (first 3 digits)"
180
+ value = $input.phone_number|substr:0:3
181
+ }
182
+
183
+ var $exchange {
184
+ description = "Extract exchange code (next 3 digits)"
185
+ value = $input.phone_number|substr:3:3
186
+ }
187
+
188
+ var $number {
189
+ description = "Extract last 4 digits"
190
+ value = $input.phone_number|substr:6:4
191
+ }
192
+
193
+ var $formatted_phone {
194
+ description = "Initialize formatted phone variable"
195
+ value = ""
196
+ }
197
+
198
+ switch ($input.format_type) {
199
+ case ("dotted") {
200
+ var.update $formatted_phone {
201
+ description = "Format as 123.456.7890"
202
+ value = $area_code ~ "." ~ $exchange ~ "." ~ $number
203
+ }
204
+ } break
205
+
206
+ case ("spaced") {
207
+ var.update $formatted_phone {
208
+ description = "Format as 123 456 7890"
209
+ value = $area_code ~ " " ~ $exchange ~ " " ~ $number
210
+ }
211
+ } break
212
+
213
+ default {
214
+ var.update $formatted_phone {
215
+ description = "Format as (123) 456-7890"
216
+ value = "(" ~ $area_code ~ ") " ~ $exchange ~ "-" ~ $number
217
+ }
218
+ }
219
+ }
220
+ }
221
+
222
+ response = {
223
+ formatted_phone: $formatted_phone
224
+ original_input : $input.phone_number
225
+ format_type : $input.format_type
226
+ area_code : $area_code
227
+ exchange : $exchange
228
+ number : $number
229
+ }
230
+ }
231
+ ```
232
+
233
+ ## weighted_average_function
234
+
235
+ ```xs
236
+ function "maths/weighted_average" {
237
+ description = "Calculates the weighted average of values in an array"
238
+ input {
239
+ decimal[] values {
240
+ description = "Array of numbers representing the values to be averaged"
241
+ }
242
+
243
+ decimal[] weights {
244
+ description = "Array of numbers representing the weights for each value"
245
+ }
246
+ }
247
+
248
+ stack {
249
+ var $values_length {
250
+ description = "Get the length of the values array"
251
+ value = $input.values|count
252
+ }
253
+
254
+ var $weights_length {
255
+ description = "Get the length of the weights array"
256
+ value = $input.weights|count
257
+ }
258
+
259
+ precondition ($values_length == $weights_length) {
260
+ error_type = "inputerror"
261
+ payload = "Values and weights arrays must have the same length"
262
+ }
263
+
264
+ var $has_negative_weights {
265
+ description = "Check if any weights are negative"
266
+ value = $input.weights|some:$$ < 0
267
+ }
268
+
269
+ precondition ($has_negative_weights == false) {
270
+ error_type = "inputerror"
271
+ payload = "All weights must be non-negative"
272
+ }
273
+
274
+ var $weighted_sum {
275
+ description = "Initialize the weighted sum"
276
+ value = 0
277
+ }
278
+
279
+ var $total_weight {
280
+ description = "Initialize the total weight"
281
+ value = 0
282
+ }
283
+
284
+ var $size {
285
+ description = "Size of the arrays for iteration"
286
+ value = $values_length
287
+ }
288
+
289
+ for ($size) {
290
+ each as $index {
291
+ var $current_value {
292
+ description = "Get the value at the current index"
293
+ value = $input.values[$index]
294
+ }
295
+
296
+ var $current_weight {
297
+ description = "Get the weight at the current index"
298
+ value = $input.weights[$index]
299
+ }
300
+
301
+ math.add $weighted_sum {
302
+ description = "Add the weighted value to the sum"
303
+ value = $current_value * $current_weight
304
+ }
305
+
306
+ math.add $total_weight {
307
+ description = "Add the current weight to the total"
308
+ value = $current_weight
309
+ }
310
+ }
311
+ }
312
+
313
+ precondition ($total_weight > 0) {
314
+ error_type = "inputerror"
315
+ payload = "Total weight cannot be zero"
316
+ }
317
+
318
+ var $weighted_average {
319
+ description = "Calculate the weighted average"
320
+ value = $weighted_sum / $total_weight
321
+ }
322
+ }
323
+
324
+ response = $weighted_average
325
+ }
326
+ ```
327
+
328
+ ## url_parser_utility
329
+
330
+ ```xs
331
+ function "utilities/url_parser_utility" {
332
+ description = "Parse and validate URLs, extracting protocol, domain, path, and basic components"
333
+ input {
334
+ text url filters=trim|min:1 {
335
+ description = "The URL to parse and validate"
336
+ }
337
+ }
338
+
339
+ stack {
340
+ var $url_lower {
341
+ description = "Convert to lowercase for consistent processing"
342
+ value = $input.url|to_lower
343
+ }
344
+
345
+ var $protocol {
346
+ description = "Default protocol"
347
+ value = "http"
348
+ }
349
+
350
+ var $domain {
351
+ description = "Extract domain from URL"
352
+ value = ""
353
+ }
354
+
355
+ var $path {
356
+ description = "Extract path from URL"
357
+ value = ""
358
+ }
359
+
360
+ var $is_valid {
361
+ description = "URL validation result"
362
+ value = false
363
+ }
364
+
365
+ var $validation_errors {
366
+ description = "List of validation errors"
367
+ value = []
368
+ }
369
+
370
+ conditional {
371
+ description = "Check if URL contains protocol"
372
+ if ($url_lower|contains:"://") {
373
+ var $url_parts {
374
+ description = "Split URL by protocol separator"
375
+ value = $url_lower|split:"://"
376
+ }
377
+
378
+ var.update $protocol {
379
+ description = "Extract protocol"
380
+ value = $url_parts[0]
381
+ }
382
+
383
+ var $remaining_url {
384
+ description = "Get URL without protocol"
385
+ value = $url_parts[1]
386
+ }
387
+
388
+ conditional {
389
+ description = "Check if URL contains path"
390
+ if ($remaining_url|contains:"/") {
391
+ var $path_parts {
392
+ description = "Split by first slash"
393
+ value = $remaining_url|split:"/"
394
+ }
395
+
396
+ var.update $domain {
397
+ description = "Extract domain"
398
+ value = $path_parts[0]
399
+ }
400
+
401
+ var.update $path {
402
+ description = "Extract path"
403
+ value = "/" ~ ($path_parts|slice:1:($path_parts|count)|join:"/")
404
+ }
405
+ }
406
+
407
+ else {
408
+ var.update $domain {
409
+ description = "No path, entire remaining is domain"
410
+ value = $remaining_url
411
+ }
412
+ }
413
+ }
414
+ }
415
+
416
+ else {
417
+ var.update $domain {
418
+ description = "Use entire URL as domain"
419
+ value = $url_lower
420
+ }
421
+ }
422
+ }
423
+
424
+ conditional {
425
+ description = "Validate protocol"
426
+ if ($protocol != "") {
427
+ var $valid_protocols {
428
+ description = "List of valid protocols"
429
+ value = ["http", "https", "ftp", "sftp", "ws", "wss"]
430
+ }
431
+
432
+ conditional {
433
+ description = "Check if protocol is valid"
434
+ if ($valid_protocols|in:$protocol) {
435
+ var $protocol_valid {
436
+ description = "Protocol is valid"
437
+ value = true
438
+ }
439
+ }
440
+
441
+ else {
442
+ var $protocol_valid {
443
+ description = "Protocol is not valid"
444
+ value = false
445
+ }
446
+ }
447
+ }
448
+
449
+ conditional {
450
+ description = "Add protocol error if invalid"
451
+ if ($protocol_valid == false) {
452
+ var.update $validation_errors {
453
+ description = "Add protocol error to validation errors"
454
+ value = $validation_errors|append:"Invalid protocol: " ~ $protocol
455
+ }
456
+ }
457
+ }
458
+ }
459
+ }
460
+
461
+ conditional {
462
+ description = "Validate domain"
463
+ if ($domain != "") {
464
+ var $domain_pattern {
465
+ description = "Domain validation pattern"
466
+ value = "/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/"
467
+ }
468
+
469
+ conditional {
470
+ description = "Check if domain matches pattern"
471
+ if (($domain_pattern|regex_matches:$domain) == false) {
472
+ var.update $validation_errors {
473
+ description = "Add domain error to validation errors"
474
+ value = $validation_errors|append:"Invalid domain format: " ~ $domain
475
+ }
476
+ }
477
+ }
478
+ }
479
+ }
480
+
481
+ var.update $is_valid {
482
+ description = "Set validation result based on error count"
483
+ value = ($validation_errors|count) == 0
484
+ }
485
+ }
486
+
487
+ response = {
488
+ url : $input.url
489
+ is_valid : $is_valid
490
+ protocol : $protocol
491
+ domain : $domain
492
+ path : $path
493
+ validation_errors: $validation_errors
494
+ base_url : $protocol ~ "://" ~ $domain
495
+ full_url : $protocol ~ "://" ~ $domain ~ $path
496
+ }
497
+ }
498
+ ```
499
+
500
+ ## random_text_selector
501
+
502
+ ```xs
503
+ function "utilities/random_text_selector" {
504
+ description = "Select a random text value from an array with optional exclusion of previously used index"
505
+ input {
506
+ json text_array {
507
+ description = "Array of text values to choose from"
508
+ }
509
+
510
+ int? last_used_index? {
511
+ description = "Optional index of previously used value to exclude from selection"
512
+ }
513
+ }
514
+
515
+ stack {
516
+ var $text_array {
517
+ description = "Convert input to safe array"
518
+ value = $input.text_array|safe_array
519
+ }
520
+
521
+ precondition (($text_array|count) > 0) {
522
+ error_type = "inputerror"
523
+ error = "Text array cannot be empty"
524
+ }
525
+
526
+ var $available_options {
527
+ description = "Start with full array"
528
+ value = $text_array
529
+ }
530
+
531
+ conditional {
532
+ description = "Filter out last used index if provided"
533
+ if ($input.last_used_index !== null) {
534
+ var.update $available_options {
535
+ description = "Remove last used index from options"
536
+ value = $text_array|filter:"return $index != " ~ $input.last_used_index ~ ";"
537
+ }
538
+ }
539
+ }
540
+
541
+ var $selected_text {
542
+ description = "Get random text from available options"
543
+ value = $available_options|shuffle|first
544
+ }
545
+ }
546
+
547
+ response = {
548
+ selected_text : $selected_text
549
+ total_options : $available_options|count
550
+ available_options: $available_options
551
+ }
552
+ }
553
+ ```
554
+
555
+ ## process_csv_import
556
+
557
+ ```xs
558
+ function "csv_import/process_customers" {
559
+ description = "Processes a CSV file to import customer data. Creates user records for email/name data and customer records with address information. Handles duplicate detection and provides import statistics."
560
+ input {
561
+ file csv_file {
562
+ description = "The CSV file containing customer data."
563
+ }
564
+ }
565
+
566
+ stack {
567
+ var $successful_imports {
568
+ value = 0
569
+ description = "Counter for successfully imported records."
570
+ }
571
+
572
+ var $failed_imports {
573
+ value = 0
574
+ description = "Counter for failed imports."
575
+ }
576
+
577
+ var $total_processed {
578
+ value = 0
579
+ description = "Total number of records processed."
580
+ }
581
+
582
+ debug.log {
583
+ value = "CSV import process started."
584
+ description = "Log the start of the import process."
585
+ }
586
+
587
+ stream.from_csv {
588
+ value = $input.csv_file
589
+ separator = ","
590
+ enclosure = '"'
591
+ escape_char = '"'
592
+ description = "CSV File Data"
593
+ } as $csv_data
594
+
595
+ foreach ($csv_data) {
596
+ each as $row {
597
+ var.update $total_processed {
598
+ value = $total_processed + 1
599
+ description = "Increment total processed records."
600
+ }
601
+
602
+ var $email {
603
+ value = ($row|get:"email":null)|trim|lower
604
+ description = "Extract and normalize email from the current row."
605
+ }
606
+
607
+ try_catch {
608
+ description = "Process customer record with error handling."
609
+ try {
610
+ var $full_name {
611
+ value = (($row|get:"first_name":null)|trim) ~ " " ~ (($row|get:"last_name":null)|trim)
612
+ description = "Combine first and last name for user record."
613
+ }
614
+
615
+ group {
616
+ description = "Check for existing user and customer records. Add new record if they do not exist."
617
+ stack {
618
+ db.get "user" {
619
+ field_name = "email"
620
+ field_value = $email
621
+ description = "Check if user already exists by email."
622
+ } as $user
623
+
624
+ conditional {
625
+ description = "Create user if they don't already exist."
626
+ if ($user == null) {
627
+ db.add user {
628
+ data = {
629
+ created_at: "now"
630
+ name : $full_name
631
+ email : $email
632
+ is_active : true
633
+ }
634
+
635
+ description = "Create new user record with name and email."
636
+ } as $new_user
637
+
638
+ var.update $user {
639
+ value = $new_user
640
+ description = "Update user variable with newly created user record."
641
+ }
642
+ }
643
+ }
644
+
645
+ db.get "customer" {
646
+ field_name = "user_id"
647
+ field_value = $user.id
648
+ description = "Check if user is already a customer"
649
+ } as $customer
650
+
651
+ conditional {
652
+ description = "Create customer record if user is not already a customer"
653
+ if ($customer == null) {
654
+ db.add customer {
655
+ data = {
656
+ created_at : "now"
657
+ user_id : $user.id
658
+ customer_type : "individual"
659
+ street_address: ($row|get:"street_address":null)|trim
660
+ zip_code : ($row|get:"zip_code":null)|trim
661
+ city : ($row|get:"city":null)|trim
662
+ }
663
+
664
+ description = "Create new customer record linked to user."
665
+ } as $customer
666
+ }
667
+ }
668
+ }
669
+ }
670
+
671
+ var.update $successful_imports {
672
+ value = $successful_imports + 1
673
+ description = "Increment successful imports counter."
674
+ }
675
+
676
+ debug.log {
677
+ value = "Successfully imported customer: " ~ $email ~ " (User ID: " ~ $user_record.id ~ ")"
678
+ description = "Log successful customer import."
679
+ }
680
+ }
681
+
682
+ catch {
683
+ var.update $failed_imports {
684
+ value = $failed_imports + 1
685
+ description = "Increment failed imports on error."
686
+ }
687
+
688
+ debug.log {
689
+ value = "Failed to process row: " ~ ($row|json_encode) ~ ", Error: " ~ $error
690
+ description = "Log row processing failure."
691
+ }
692
+ }
693
+ }
694
+ }
695
+ }
696
+
697
+ var $result {
698
+ value = {
699
+ total_processed : $total_processed
700
+ successful_imports: $successful_imports
701
+ failed_imports : $failed_imports
702
+ message : "CSV import completed successfully."
703
+ }
704
+ }
705
+ }
706
+
707
+ response = $result
708
+ }
709
+ ```
710
+
711
+ ## survey_analytics_system
712
+
713
+ ```xs
714
+ function "analytics/survey_analytics" {
715
+ description = "Calculate survey completion rates, response statistics, and question-level analytics for survey system analysis"
716
+ input {
717
+ int survey_id {
718
+ description = "The ID of the survey to analyze"
719
+ }
720
+ }
721
+
722
+ stack {
723
+ precondition ($input.survey_id > 0) {
724
+ error_type = "inputerror"
725
+ error = "Survey ID must be positive"
726
+ description = "Validate survey_id is valid"
727
+ }
728
+
729
+ db.get "surveys" {
730
+ field_name = "id"
731
+ field_value = $input.survey_id
732
+ description = "Get survey information and verify it exists"
733
+ } as $survey_info
734
+
735
+ precondition ($survey_info != null) {
736
+ error_type = "notfound"
737
+ error = "Survey not found"
738
+ description = "Ensure survey exists"
739
+ }
740
+
741
+ db.query "survey_questions" {
742
+ description = "Get all questions for this survey ordered by display order"
743
+ where = $db.survey_questions.survey_id == $input.survey_id
744
+ } as $questions
745
+
746
+ db.query "survey_responses" {
747
+ description = "Get all responses for this survey"
748
+ where = $db.survey_responses.survey_id == $input.survey_id
749
+ } as $all_responses
750
+
751
+ var $total_questions {
752
+ value = $questions|count
753
+ description = "Total number of questions in survey"
754
+ }
755
+
756
+ db.query "survey_responses" {
757
+ description = "Get all responses for this survey"
758
+ where = $db.survey_responses.survey_id == $input.survey_id
759
+ } as $responses
760
+
761
+ var $unique_respondents {
762
+ value = $all_responses.respondent_id|unique
763
+ description = "Get unique respondent IDs"
764
+ }
765
+
766
+ var $total_respondents {
767
+ value = $unique_respondents|count
768
+ description = "Total number of unique respondents"
769
+ }
770
+
771
+ var $completion_analysis {
772
+ value = []
773
+ description = "Array to store completion analysis for each respondent"
774
+ }
775
+
776
+ foreach ($unique_respondents) {
777
+ each as $respondent_id {
778
+ var $respondent_responses {
779
+ value = $all_responses|filter:"return $this.respondent_id == '" ~ $respondent_id ~ "';"
780
+ description = "Get responses for current respondent"
781
+ }
782
+
783
+ var $response_count {
784
+ value = $respondent_responses|count
785
+ description = "Count responses for this respondent"
786
+ }
787
+
788
+ var $completion_percentage {
789
+ value = 0
790
+ description = "Initialize completion percentage"
791
+ }
792
+
793
+ conditional {
794
+ if ($total_questions > 0) {
795
+ var.update $completion_percentage {
796
+ value = (($response_count / $total_questions) * 100)|number_format:2:".":","
797
+ description = "Calculate completion percentage"
798
+ }
799
+ }
800
+ }
801
+
802
+ var $is_complete {
803
+ value = ($response_count == $total_questions)
804
+ description = "Check if survey is complete"
805
+ }
806
+
807
+ var $respondent_analysis {
808
+ value = {
809
+ respondent_id: $respondent_id
810
+ responses_given: $response_count
811
+ completion_percentage: $completion_percentage
812
+ is_complete: $is_complete
813
+ }
814
+
815
+ description = "Analysis for current respondent"
816
+ }
817
+
818
+ array.push $completion_analysis {
819
+ value = $respondent_analysis
820
+ description = "Add respondent analysis to results"
821
+ }
822
+ }
823
+ }
824
+
825
+ var $completed_surveys {
826
+ value = $completion_analysis|filter:"return $this.is_complete == true;"
827
+ description = "Filter to only completed surveys"
828
+ }
829
+
830
+ var $completion_rate {
831
+ value = 0
832
+ description = "Overall completion rate percentage"
833
+ }
834
+
835
+ conditional {
836
+ if ($total_respondents > 0) {
837
+ var $completed_count {
838
+ value = $completed_surveys|count
839
+ description = "Number of completed surveys"
840
+ }
841
+
842
+ var.update $completion_rate {
843
+ value = (($completed_count / $total_respondents) * 100)|number_format:2:".":","
844
+ description = "Calculate completion rate"
845
+ }
846
+ }
847
+ }
848
+
849
+ var $question_stats {
850
+ value = []
851
+ description = "Array to store response statistics for each question"
852
+ }
853
+
854
+ foreach ($questions) {
855
+ each as $question {
856
+ var $question_responses {
857
+ value = $all_responses|filter:"return $this.question_id == " ~ $question.id ~ ";"
858
+ description = "Get responses for current question"
859
+ }
860
+
861
+ var $response_count {
862
+ value = $question_responses|count
863
+ description = "Count responses for this question"
864
+ }
865
+
866
+ var $response_rate {
867
+ value = 0
868
+ description = "Initialize response rate"
869
+ }
870
+
871
+ conditional {
872
+ if ($total_respondents > 0) {
873
+ var.update $response_rate {
874
+ value = (($response_count / $total_respondents) * 100)|number_format:2:".":","
875
+ description = "Calculate response rate percentage"
876
+ }
877
+ }
878
+ }
879
+
880
+ var $question_analysis {
881
+ value = {
882
+ question_id: $question.id
883
+ question_text: $question.question_text
884
+ question_type: $question.question_type
885
+ is_required: $question.is_required
886
+ total_responses: $response_count
887
+ response_rate: $response_rate
888
+ }
889
+
890
+ description = "Analysis for current question"
891
+ }
892
+
893
+ array.push $question_stats {
894
+ value = $question_analysis
895
+ description = "Add question analysis to results"
896
+ }
897
+ }
898
+ }
899
+
900
+ var $average_answered {
901
+ value = 0
902
+ description = "Average questions answered per respondent"
903
+ }
904
+
905
+ conditional {
906
+ if ($total_respondents > 0) {
907
+ var $total_responses_given {
908
+ value = $completion_analysis.responses_given|sum
909
+ description = "Sum of all responses given"
910
+ }
911
+
912
+ var.update $average_answered {
913
+ value = ($total_responses_given / $total_respondents)|number_format:2:".":","
914
+ description = "Calculate average questions answered"
915
+ }
916
+ }
917
+ }
918
+
919
+ var $analytics_summary {
920
+ value = {
921
+ survey_id: $input.survey_id
922
+ survey_title: $survey_info.title
923
+ survey_status: $survey_info.status
924
+ total_questions: $total_questions
925
+ total_respondents: $total_respondents
926
+ completed_responses: ($completed_surveys|count)
927
+ completion_rate_percent: $completion_rate
928
+ partial_responses: ($total_respondents - ($completed_surveys|count))
929
+ average_questions_answered: $average_answered
930
+ }
931
+
932
+ description = "Summary analytics for the survey"
933
+ }
934
+
935
+ debug.log {
936
+ value = "Survey analytics calculated for survey " ~ $input.survey_id ~ " - " ~ $total_respondents ~ " respondents, " ~ $completion_rate ~ "% completion rate"
937
+ description = "Log analytics completion"
938
+ }
939
+ }
940
+
941
+ response = {
942
+ summary : $analytics_summary
943
+ question_breakdown: $question_stats
944
+ completion_details: $completion_analysis
945
+ generated_at : "now"
946
+ }
947
+ }
948
+ ```
949
+
950
+ ## calculate_customer_lifetime_value
951
+
952
+ ```xs
953
+ function "analytics/calculate_customer_lifetime_value" {
954
+ description = "Calculates the total monetary value of all orders made by a specific customer, providing a measure of their lifetime value to the business. This function queries the customer's order history and sums up all order totals to determine their overall spending."
955
+ input {
956
+ int customer_id {
957
+ description = "The ID of the customer to calculate lifetime value for"
958
+ }
959
+ }
960
+
961
+ stack {
962
+ db.query "customer" {
963
+ description = "Query the customer table to get customer information"
964
+ where = $db.customer.id == $input.customer_id
965
+ mock = {
966
+ "should return customer": [{id: 5, name: "John"}]
967
+ }
968
+ } as $customer
969
+
970
+ db.query "orders" {
971
+ description = "Query the orders table to get all orders for this customer"
972
+ where = $db.orders.customer_id == $input.customer_id
973
+ mock = {
974
+ "should return customer": [
975
+ {id: 1, customer_id: 5, order_total: 100},
976
+ {id: 2, customer_id: 5, order_total: 50}
977
+ ],
978
+ "should return zero for no orders": []
979
+ }
980
+ } as $orders
981
+
982
+ var $total {
983
+ value = $orders[$$].order_total|sum
984
+ description = "Calculate the total value by summing all order totals"
985
+ }
986
+ }
987
+
988
+ response = $total
989
+
990
+ test "should return customer" {
991
+ input = {customer_id: 5}
992
+ expect.to_equal ($response) {
993
+ value = 150
994
+ }
995
+ }
996
+
997
+ test "should return zero for no orders" {
998
+ input = {customer_id: 99}
999
+ expect.to_equal ($response) {
1000
+ value = 0
1001
+ }
1002
+ }
1003
+ }
1004
+ ```
1005
+
1006
+ ## employee_performance_bonus_calculator
1007
+
1008
+ ```xs
1009
+ function "hr/employee_performance_bonus_calculator" {
1010
+ description = "Calculates employee performance bonuses based on performance score, years of service, department, and additional factors"
1011
+ input {
1012
+ decimal performance_score filters=min:0|max:100 {
1013
+ description = "Employee's performance score (0-100)"
1014
+ }
1015
+
1016
+ int years_of_service filters=min:0|max:50 {
1017
+ description = "Number of years the employee has worked"
1018
+ }
1019
+
1020
+ text department {
1021
+ description = "Employee's department (engineering, sales, management, marketing, hr, finance)"
1022
+ }
1023
+
1024
+ decimal base_salary filters=min:0 {
1025
+ description = "Employee's annual base salary"
1026
+ }
1027
+
1028
+ bool exceeded_targets? {
1029
+ description = "Whether employee exceeded their annual targets"
1030
+ }
1031
+
1032
+ text performance_tier? {
1033
+ description = "Performance tier: exceptional, high, satisfactory, needs_improvement"
1034
+ }
1035
+
1036
+ decimal bonus_cap_percentage?=25 filters=min:0|max:50 {
1037
+ description = "Maximum bonus as percentage of base salary (default 25%)"
1038
+ }
1039
+ }
1040
+
1041
+ stack {
1042
+ var $department_multiplier {
1043
+ value = 1
1044
+ description = "Initialize department multiplier"
1045
+ }
1046
+
1047
+ switch ($input.department|to_lower) {
1048
+ case ("engineering") {
1049
+ var.update $department_multiplier {
1050
+ value = 1.2
1051
+ description = "Engineering department gets 20% multiplier"
1052
+ }
1053
+ } break
1054
+
1055
+ case ("sales") {
1056
+ var.update $department_multiplier {
1057
+ value = 1.3
1058
+ description = "Sales department gets 30% multiplier"
1059
+ }
1060
+ } break
1061
+
1062
+ case ("management") {
1063
+ var.update $department_multiplier {
1064
+ value = 1.4
1065
+ description = "Management gets 40% multiplier"
1066
+ }
1067
+ } break
1068
+
1069
+ case ("marketing") {
1070
+ var.update $department_multiplier {
1071
+ value = 1.15
1072
+ description = "Marketing gets 15% multiplier"
1073
+ }
1074
+ } break
1075
+
1076
+ case ("hr") {
1077
+ var.update $department_multiplier {
1078
+ value = 1.1
1079
+ description = "HR gets 10% multiplier"
1080
+ }
1081
+ } break
1082
+
1083
+ case ("finance") {
1084
+ var.update $department_multiplier {
1085
+ value = 1.25
1086
+ description = "Finance gets 25% multiplier"
1087
+ }
1088
+ } break
1089
+
1090
+ default {
1091
+ var.update $department_multiplier {
1092
+ value = 1
1093
+ description = "Default multiplier for other departments"
1094
+ }
1095
+ }
1096
+ }
1097
+
1098
+ var $years_multiplier {
1099
+ value = 1
1100
+ description = "Initialize years of service multiplier"
1101
+ }
1102
+
1103
+ conditional {
1104
+ if ($input.years_of_service >= 20) {
1105
+ var.update $years_multiplier {
1106
+ value = 1.5
1107
+ description = "20+ years gets 50% multiplier"
1108
+ }
1109
+ }
1110
+
1111
+ elseif ($input.years_of_service >= 15) {
1112
+ var.update $years_multiplier {
1113
+ value = 1.4
1114
+ description = "15+ years gets 40% multiplier"
1115
+ }
1116
+ }
1117
+
1118
+ elseif ($input.years_of_service >= 10) {
1119
+ var.update $years_multiplier {
1120
+ value = 1.3
1121
+ description = "10+ years gets 30% multiplier"
1122
+ }
1123
+ }
1124
+
1125
+ elseif ($input.years_of_service >= 5) {
1126
+ var.update $years_multiplier {
1127
+ value = 1.2
1128
+ description = "5+ years gets 20% multiplier"
1129
+ }
1130
+ }
1131
+
1132
+ elseif ($input.years_of_service >= 2) {
1133
+ var.update $years_multiplier {
1134
+ value = 1.1
1135
+ description = "2+ years gets 10% multiplier"
1136
+ }
1137
+ }
1138
+ }
1139
+
1140
+ var $performance_multiplier {
1141
+ value = $input.performance_score / 100
1142
+ description = "Convert performance score to decimal multiplier"
1143
+ }
1144
+
1145
+ var $target_bonus {
1146
+ value = 0
1147
+ description = "Initialize target achievement bonus"
1148
+ }
1149
+
1150
+ conditional {
1151
+ if ($input.exceeded_targets) {
1152
+ var.update $target_bonus {
1153
+ value = $input.base_salary * 0.05
1154
+ description = "5% bonus for exceeding targets"
1155
+ }
1156
+ }
1157
+ }
1158
+
1159
+ var $tier_multiplier {
1160
+ value = 1
1161
+ description = "Initialize performance tier multiplier"
1162
+ }
1163
+
1164
+ conditional {
1165
+ if ($input.performance_tier != "") {
1166
+ switch ($input.performance_tier|to_lower) {
1167
+ case ("exceptional") {
1168
+ var.update $tier_multiplier {
1169
+ value = 1.25
1170
+ description = "Exceptional performance gets 25% multiplier"
1171
+ }
1172
+ } break
1173
+
1174
+ case ("high") {
1175
+ var.update $tier_multiplier {
1176
+ value = 1.15
1177
+ description = "High performance gets 15% multiplier"
1178
+ }
1179
+ } break
1180
+
1181
+ case ("satisfactory") {
1182
+ var.update $tier_multiplier {
1183
+ value = 1
1184
+ description = "Satisfactory performance gets standard multiplier"
1185
+ }
1186
+ } break
1187
+
1188
+ case ("needs_improvement") {
1189
+ var.update $tier_multiplier {
1190
+ value = 0.5
1191
+ description = "Needs improvement gets reduced multiplier"
1192
+ }
1193
+ } break
1194
+ }
1195
+ }
1196
+ }
1197
+
1198
+ var $calculated_bonus {
1199
+ value = $input.base_salary * $performance_multiplier * $department_multiplier * $years_multiplier * $tier_multiplier
1200
+ description = "Calculate base bonus before caps and additions"
1201
+ }
1202
+
1203
+ var $total_bonus_before_cap {
1204
+ value = $calculated_bonus + $target_bonus
1205
+ description = "Add target achievement bonus"
1206
+ }
1207
+
1208
+ var $bonus_cap {
1209
+ value = $input.base_salary * ($input.bonus_cap_percentage / 100)
1210
+ description = "Calculate maximum allowed bonus"
1211
+ }
1212
+
1213
+ var $final_bonus {
1214
+ value = ($total_bonus_before_cap > $bonus_cap) ? $bonus_cap : $total_bonus_before_cap
1215
+ description = "Apply bonus cap if necessary"
1216
+ }
1217
+
1218
+ var $was_capped {
1219
+ value = $total_bonus_before_cap > $bonus_cap
1220
+ description = "Track if bonus was capped"
1221
+ }
1222
+
1223
+ var $bonus_as_percentage {
1224
+ value = ($final_bonus / $input.base_salary) * 100
1225
+ description = "Calculate bonus as percentage of base salary"
1226
+ }
1227
+
1228
+ var $result {
1229
+ value = {
1230
+ final_bonus_amount: $final_bonus
1231
+ bonus_percentage: $bonus_as_percentage
1232
+ was_capped: $was_capped
1233
+ calculation_details: {
1234
+ base_salary: $input.base_salary
1235
+ performance_score: $input.performance_score
1236
+ years_of_service: $input.years_of_service
1237
+ department: $input.department
1238
+ performance_tier: $input.performance_tier
1239
+ exceeded_targets: $input.exceeded_targets
1240
+ }
1241
+ multipliers_applied: {
1242
+ department_multiplier: $department_multiplier
1243
+ years_multiplier: $years_multiplier
1244
+ performance_multiplier: $performance_multiplier
1245
+ tier_multiplier: $tier_multiplier
1246
+ }
1247
+ bonus_breakdown: {
1248
+ calculated_base_bonus: $calculated_bonus
1249
+ target_achievement_bonus: $target_bonus
1250
+ total_before_cap: $total_bonus_before_cap
1251
+ bonus_cap: $bonus_cap
1252
+ final_bonus: $final_bonus
1253
+ }
1254
+ }
1255
+
1256
+ description = "Comprehensive bonus calculation result with detailed breakdown"
1257
+ }
1258
+ }
1259
+
1260
+ response = $result
1261
+ }
1262
+ ```
1263
+
1264
+ ## compute_user_engagement_score
1265
+
1266
+ ```xs
1267
+ function "analytics/compute_user_engagement_score" {
1268
+ description = "Computes a user engagement score based on their activity data. Accepts a user ID and activity array, validates inputs, calculates weighted engagement score (posts * 10 + comments * 5 + likes * 2), and ensures all values are non-negative using array validation."
1269
+ input {
1270
+ int user_id {
1271
+ description = "Unique identifier for the user"
1272
+ }
1273
+ decimal[] posts {
1274
+ description = "Array of post counts for each period"
1275
+ }
1276
+ decimal[] comments {
1277
+ description = "Array of comment counts for each period"
1278
+ }
1279
+ decimal[] likes {
1280
+ description = "Array of like counts for each period"
1281
+ }
1282
+ decimal post_weight?=10 {
1283
+ description = "Weight multiplier for posts (default: 10)"
1284
+ }
1285
+ decimal comment_weight?=5 {
1286
+ description = "Weight multiplier for comments (default: 5)"
1287
+ }
1288
+ decimal like_weight?=2 {
1289
+ description = "Weight multiplier for likes (default: 2)"
1290
+ }
1291
+ }
1292
+ stack {
1293
+ precondition ($input.user_id > 0) {
1294
+ error_type = "inputerror"
1295
+ error = "User ID must be positive"
1296
+ description = "Validate user_id is positive"
1297
+ }
1298
+ var $posts_length {
1299
+ value = $input.posts|count
1300
+ description = "Length of posts array"
1301
+ }
1302
+ var $comments_length {
1303
+ value = $input.comments|count
1304
+ description = "Length of comments array"
1305
+ }
1306
+ var $likes_length {
1307
+ value = $input.likes|count
1308
+ description = "Length of likes array"
1309
+ }
1310
+ precondition ($posts_length > 0) {
1311
+ error_type = "inputerror"
1312
+ error = "Posts array cannot be empty"
1313
+ description = "Validate arrays are not empty"
1314
+ }
1315
+ precondition ($posts_length == $comments_length && $posts_length == $likes_length) {
1316
+ error_type = "inputerror"
1317
+ error = "All activity arrays must have the same length"
1318
+ description = "Validate all arrays have the same length"
1319
+ }
1320
+ precondition ($input.post_weight >= 0) {
1321
+ error_type = "inputerror"
1322
+ error = "Post weight must be non-negative"
1323
+ description = "Validate post weight is non-negative"
1324
+ }
1325
+ precondition ($input.comment_weight >= 0) {
1326
+ error_type = "inputerror"
1327
+ error = "Comment weight must be non-negative"
1328
+ description = "Validate comment weight is non-negative"
1329
+ }
1330
+ precondition ($input.like_weight >= 0) {
1331
+ error_type = "inputerror"
1332
+ error = "Like weight must be non-negative"
1333
+ description = "Validate like weight is non-negative"
1334
+ }
1335
+ var $min_post_value {
1336
+ value = $input.posts|min
1337
+ description = "Minimum value in posts array"
1338
+ }
1339
+ var $min_comment_value {
1340
+ value = $input.comments|min
1341
+ description = "Minimum value in comments array"
1342
+ }
1343
+ var $min_like_value {
1344
+ value = $input.likes|min
1345
+ description = "Minimum value in likes array"
1346
+ }
1347
+ precondition ($min_post_value >= 0) {
1348
+ error_type = "inputerror"
1349
+ error = "All post counts must be non-negative"
1350
+ description = "Check if all post counts are valid"
1351
+ }
1352
+ precondition ($min_comment_value >= 0) {
1353
+ error_type = "inputerror"
1354
+ error = "All comment counts must be non-negative"
1355
+ description = "Check if all comment counts are valid"
1356
+ }
1357
+ precondition ($min_like_value >= 0) {
1358
+ error_type = "inputerror"
1359
+ error = "All like counts must be non-negative"
1360
+ description = "Check if all like counts are valid"
1361
+ }
1362
+ var $total_score {
1363
+ value = 0
1364
+ description = "Total engagement score"
1365
+ }
1366
+ for ($posts_length) {
1367
+ each as $index {
1368
+ var $period_score {
1369
+ value = ($input.posts[$index] * $input.post_weight) + ($input.comments[$index] * $input.comment_weight) + ($input.likes[$index] * $input.like_weight)
1370
+ description = "Weighted score for period $index"
1371
+ }
1372
+ math.add $total_score {
1373
+ value = $period_score
1374
+ description = "Add period score to total"
1375
+ }
1376
+ }
1377
+ }
1378
+ }
1379
+
1380
+ response = $total_score
1381
+
1382
+ test "should calculate correct engagement score" {
1383
+ input = {
1384
+ user_id: 1,
1385
+ posts: [2, 1],
1386
+ comments: [5, 3],
1387
+ likes: [10, 4]
1388
+ }
1389
+ expect.to_equal ($response) {
1390
+ value = 2*10+5*5+10*2 + 1*10+3*5+4*2
1391
+ }
1392
+ }
1393
+
1394
+ test "should throw error for negative post count" {
1395
+ input = {
1396
+ user_id: 1,
1397
+ posts: [-1, 2],
1398
+ comments: [1, 2],
1399
+ likes: [1, 2]
1400
+ }
1401
+ expect.to_throw {
1402
+ value = "All post counts must be non-negative"
1403
+ }
1404
+ }
1405
+ }
1406
+ ```