@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,1255 @@
1
+ ---
2
+ applyTo: "apis/**/*.xs"
3
+ ---
4
+
5
+ # Xanoscript API Query Examples
6
+
7
+ Below are some examples of API queries that can be made using Xanoscript.
8
+
9
+ ## random_text_selector
10
+
11
+ ## blog_posts_list
12
+
13
+ ```xs
14
+ query "blog_posts" verb=GET {
15
+ description = "Returns a paginated list of all blog posts with paging and sorting capabilities. Includes author information, comment counts, and like counts for each post."
16
+ input {
17
+ int page?=1 {
18
+ description = "Page number for pagination"
19
+ }
20
+
21
+ int per_page?=10 {
22
+ description = "Number of items per page (max 100)"
23
+ }
24
+
25
+ json sort? {
26
+ description = "External sorting configuration array with orderBy and sortBy fields"
27
+ }
28
+ }
29
+
30
+ stack {
31
+ db.query blog_post {
32
+ join = {
33
+ user: {
34
+ table: "user"
35
+ where: $db.blog_post.author_id == $db.user.id
36
+ }
37
+ }
38
+
39
+ sort = {blog_post.publication_date: "desc"}
40
+ override_sort = $input.sort
41
+ eval = {authorName: $db.user.name, status: $db.user.status}
42
+ return = {
43
+ type : "list"
44
+ paging: {
45
+ page : $input.page
46
+ per_page: $input.per_page
47
+ totals : true
48
+ }
49
+ }
50
+
51
+ addon = [
52
+ {
53
+ name : "blog_post_likes"
54
+ input: {blog_post_id: $output.id}
55
+ as : "items.blog_post_likes"
56
+ }
57
+ {
58
+ name : "blog_post_comments"
59
+ input: {blog_post_id: $output.id}
60
+ as : "items.blog_post_comments"
61
+ }
62
+ ]
63
+ } as $posts
64
+ }
65
+
66
+ response = $posts
67
+
68
+ history = false
69
+ }
70
+ ```
71
+
72
+ ## review_by_product
73
+
74
+ ```xs
75
+ query "reviews/by_product/{product_id}" verb=GET {
76
+ description = "Retrieve all reviews for a given product with optional minimum rating, sorted by review_date descending, and paginated."
77
+ input {
78
+ int product_id {
79
+ description = "ID of the product to filter reviews"
80
+ }
81
+
82
+ decimal min_rating? {
83
+ description = "Optional minimum rating to filter reviews"
84
+ }
85
+ }
86
+
87
+ stack {
88
+ debug.log {
89
+ value = "Requester IP: " ~ $env.$remote_ip
90
+ }
91
+
92
+ conditional {
93
+ if ($input.min_rating != null) {
94
+ db.query "product_reviews" {
95
+ where = $db.product_reviews.product_id == $input.product_id && $db.product_reviews.rating >= $input.min_rating
96
+ sort = {product_reviews.review_date: "desc"}
97
+ return = {type: "list", paging: {page: 1, per_page: 25, totals: true}}
98
+ } as $reviews
99
+ }
100
+
101
+ else {
102
+ db.query "product_reviews" {
103
+ where = $db.product_reviews.product_id == $input.product_id
104
+ sort = {product_reviews.review_date: "desc"}
105
+ return = {type: "list", paging: {page: 1, per_page: 25, totals: true}}
106
+ } as $reviews
107
+ }
108
+ }
109
+ }
110
+
111
+ response = $reviews
112
+
113
+ history = 100
114
+ }
115
+ ```
116
+
117
+ ## last_five_blog_posts
118
+
119
+ ```xs
120
+ query "posts/recent" verb=GET {
121
+ description = "Retrieve the 5 most recent published blog posts, sorted by publication_date in descending order."
122
+ input {
123
+ }
124
+
125
+ stack {
126
+ db.query "blog_post" {
127
+ where = $db.blog_post.is_draft == false
128
+ sort = {blog_post.publication_date: "desc"}
129
+ return = {
130
+ type : "list"
131
+ paging: {page: 1, per_page: 5, metadata: false}
132
+ }
133
+ } as $recent_posts
134
+ }
135
+
136
+ response = $recent_posts
137
+
138
+ history = "inherit"
139
+ }
140
+ ```
141
+
142
+ ## booking_reservation_system
143
+
144
+ ```xs
145
+ query "create_booking" verb=POST {
146
+ description = "Create a new booking reservation with availability checking and conflict resolution"
147
+ auth = "user"
148
+ input {
149
+ int resource_id filters=min:1 {
150
+ description = "ID of the resource to book"
151
+ }
152
+
153
+ text title filters=trim|min:1 {
154
+ description = "Title or purpose of the booking"
155
+ }
156
+
157
+ text description? filters=trim {
158
+ description = "Detailed description of the booking purpose"
159
+ }
160
+
161
+ timestamp start_time {
162
+ description = "When the booking should begin (ISO format)"
163
+ }
164
+
165
+ timestamp end_time {
166
+ description = "When the booking should end (ISO format)"
167
+ }
168
+
169
+ int attendee_count?=1 filters=min:1 {
170
+ description = "Number of people attending (default: 1)"
171
+ }
172
+
173
+ email contact_email {
174
+ description = "Contact email for booking notifications"
175
+ }
176
+
177
+ text contact_phone? filters=trim {
178
+ description = "Contact phone number"
179
+ }
180
+
181
+ text special_requests? filters=trim {
182
+ description = "Any special requirements or requests"
183
+ }
184
+
185
+ bool send_confirmation?=1 {
186
+ description = "Whether to send email confirmation (default: true)"
187
+ }
188
+ }
189
+
190
+ stack {
191
+ precondition ($input.start_time < $input.end_time) {
192
+ description = "Validate booking time range is logical"
193
+ error_type = "inputerror"
194
+ error = "Start time must be before end time."
195
+ }
196
+
197
+ precondition ($input.start_time > now) {
198
+ description = "Validate booking is not in the past"
199
+ error_type = "inputerror"
200
+ error = "Cannot create bookings in the past."
201
+ }
202
+
203
+ db.query "resources" {
204
+ description = "Get the resource being booked"
205
+ where = $db.resources.id == $input.resource_id && $db.resources.is_active
206
+ return = {type: "list"}
207
+ } as $resource_data
208
+
209
+ precondition (($resource_data|count) > 0) {
210
+ description = "Validate resource exists and is active"
211
+ error_type = "inputerror"
212
+ error = "Resource not found or is not available for booking."
213
+ }
214
+
215
+ var $resource {
216
+ description = "Store the resource record"
217
+ value = $resource_data|first
218
+ }
219
+
220
+ precondition ($input.attendee_count <= $resource.capacity) {
221
+ description = "Validate attendee count does not exceed resource capacity"
222
+ error_type = "inputerror"
223
+ error = "Attendee count (" ~ $input.attendee_count ~ ") exceeds resource capacity (" ~ $resource.capacity ~ ")."
224
+ }
225
+
226
+ db.query "bookings" {
227
+ description = "Get all existing bookings for this resource to check conflicts"
228
+ where = $db.bookings.resource_id == $input.resource_id
229
+ return = {type: "list"}
230
+ } as $all_bookings
231
+
232
+ var $existing_bookings {
233
+ description = "Filter bookings to only confirmed or pending ones"
234
+ value = $all_bookings|filter:($this.status == "confirmed" || $this.status == "pending")
235
+ }
236
+
237
+ var $conflicts {
238
+ description = "Find conflicting bookings that overlap with requested time"
239
+ value = $existing_bookings|filter:(($this.start_time < $input.end_time) && ($this.end_time > $input.start_time))
240
+ }
241
+
242
+ precondition (($conflicts|count) == 0) {
243
+ description = "Validate no time conflicts exist"
244
+ error_type = "inputerror"
245
+ error = "Time slot conflicts with existing booking. Please choose a different time."
246
+ }
247
+
248
+ var $booking_duration {
249
+ description = "Calculate booking duration in hours"
250
+ value = ($input.end_time - $input.start_time) / 3600
251
+ }
252
+
253
+ var $total_cost {
254
+ description = "Calculate total cost based on hourly rate and duration"
255
+ value = $booking_duration * $resource.hourly_rate
256
+ }
257
+
258
+ var $booking_reference {
259
+ description = "Generate unique booking reference code"
260
+ value = "BK-" ~ $input.resource_id ~ "-" ~ (now|to_timestamp) ~ "-" ~ ($auth.id|to_text)
261
+ }
262
+
263
+ var $initial_status {
264
+ description = "Set initial status based on whether resource requires approval"
265
+ value = ($resource.requires_approval == true) ? "pending" : "confirmed"
266
+ }
267
+
268
+ db.add bookings {
269
+ description = "Create the new booking record"
270
+ data = {
271
+ resource_id : $input.resource_id
272
+ user_id : $auth.id
273
+ title : $input.title
274
+ description : $input.description
275
+ start_time : $input.start_time
276
+ end_time : $input.end_time
277
+ attendee_count : $input.attendee_count
278
+ contact_email : $input.contact_email
279
+ contact_phone : $input.contact_phone
280
+ status : $initial_status
281
+ total_cost : $total_cost
282
+ special_requests : $input.special_requests
283
+ booking_reference: $booking_reference
284
+ created_at : now
285
+ updated_at : now
286
+ }
287
+ } as $new_booking
288
+
289
+ conditional {
290
+ description = "Send confirmation email if requested"
291
+ if ($input.send_confirmation) {
292
+ var $email_subject {
293
+ description = "Create email subject line"
294
+ value = "Booking Confirmation - " ~ $resource.name ~ " (" ~ $booking_reference ~ ")"
295
+ }
296
+
297
+ util.template_engine {
298
+ description = "Create email body using template"
299
+ value = """
300
+ Dear Customer,
301
+
302
+ Your booking has been {{ initial_status == 'confirmed' ? 'confirmed' : 'submitted for approval' }}.
303
+
304
+ Booking Details:
305
+ - Resource: {{ resource.name }}
306
+ - Date/Time: {{ input.start_time }} - {{ input.end_time }}
307
+ - Duration: {{ booking_duration }} hours
308
+ - Attendees: {{ input.attendee_count }}
309
+ - Total Cost: ${{ total_cost }}
310
+ - Reference: {{ booking_reference }}
311
+
312
+ Thank you for your booking!
313
+ """
314
+ } as $email_body
315
+
316
+ util.send_email {
317
+ description = "Send booking confirmation email to user"
318
+ service_provider = "resend"
319
+ api_key = $env.secret_key
320
+ from = "no-reply@example.com"
321
+ to = $input.contact_email
322
+ subject = $email_subject
323
+ message = $email_body
324
+ } as $confirmation_email
325
+ }
326
+ }
327
+
328
+ var $booking_result {
329
+ description = "Prepare booking result summary"
330
+ value = {
331
+ booking_id: $new_booking.id
332
+ booking_reference: $booking_reference
333
+ resource_name: $resource.name
334
+ status: $initial_status
335
+ start_time: $input.start_time
336
+ end_time: $input.end_time
337
+ duration_hours: $booking_duration
338
+ total_cost: $total_cost
339
+ requires_approval: $resource.requires_approval
340
+ confirmation_sent: $input.send_confirmation
341
+ }
342
+ }
343
+
344
+ debug.log {
345
+ description = "Log successful booking creation"
346
+ value = {
347
+ action: "booking_created_successfully"
348
+ booking_id: $new_booking.id
349
+ booking_reference: $booking_reference
350
+ status: $initial_status
351
+ total_cost: $total_cost
352
+ }
353
+ }
354
+ }
355
+
356
+ response = $booking_result
357
+
358
+ history = "all"
359
+ }
360
+ ```
361
+
362
+ ## audio_transcription_endpoint
363
+
364
+ ```xs
365
+ query "transcribe/audio" verb=POST {
366
+ auth = "user"
367
+ description = "Upload an audio file to be transcribed using ElevenLabs API, store the file in cloud storage, and save transcription in the database."
368
+
369
+ input {
370
+ file audio {
371
+ description = "Audio file to be transcribed"
372
+ }
373
+
374
+ text ?language {
375
+ description = "Target language for transcription (optional, auto-detect if not provided)"
376
+ }
377
+
378
+ text access_level?=private filters=trim {
379
+ description = "Access level for the audio file (public or private)"
380
+ }
381
+ }
382
+
383
+ stack {
384
+ try_catch {
385
+ try {
386
+ conditional {
387
+ if ($input.access_level == "public") {
388
+ storage.create_attachment {
389
+ value = $input.audio
390
+ access = "public"
391
+ filename = $filename
392
+ description = "Store audio in cloud storage as public"
393
+ } as $audio_storage
394
+ }
395
+
396
+ else {
397
+ return {
398
+ value = "This function is built for public files. Please set access_level to public"
399
+ }
400
+ }
401
+ }
402
+ }
403
+
404
+ catch {
405
+ throw {
406
+ name = "Audio file issue"
407
+ value = ""
408
+ }
409
+ }
410
+ }
411
+
412
+ group {
413
+ description = "Handle audio upload and transcription process"
414
+ stack {
415
+ db.get "user" {
416
+ field_name = "id"
417
+ field_value = $auth.id
418
+ description = "Verify user exists"
419
+ } as $user
420
+
421
+ conditional {
422
+ if ($user == "") {
423
+ throw {
424
+ name = "inputerror"
425
+ value = "Invalid user ID provided."
426
+ }
427
+ }
428
+ }
429
+
430
+ var $allowed_audio_types {
431
+ value = ["audio/mpeg", "audio/mp3", "audio/wav", "audio/m4a", "audio/aac", "audio/ogg", "audio/flac"]
432
+ description = "List of allowed audio MIME types"
433
+ }
434
+
435
+ var $file_mime {
436
+ value = $audio_storage.mime
437
+ description = "MIME type of uploaded audio file"
438
+ }
439
+
440
+ var $file_size {
441
+ value = $audio_storage.size
442
+ description = "Size of uploaded audio file in bytes"
443
+ }
444
+
445
+ var $filename {
446
+ value = $audio_storage.name
447
+ description = "Original filename"
448
+ }
449
+
450
+ conditional {
451
+ if ($allowed_audio_types|contains:$file_mime) {
452
+ throw {
453
+ name = "inputerror"
454
+ value = "Invalid file type. Only audio files (MP3, WAV, M4A, AAC, OGG, FLAC) are allowed."
455
+ }
456
+ }
457
+ }
458
+
459
+ conditional {
460
+ if ($file_size > 26214400) {
461
+ throw {
462
+ name = "inputerror"
463
+ value = "File size too large. Maximum size is 25MB."
464
+ }
465
+ }
466
+ }
467
+
468
+ var $transcription_params {
469
+ value = {}
470
+ description = "Parameters for transcription request"
471
+ }
472
+
473
+ conditional {
474
+ if ($input.language != "") {
475
+ var.update $transcription_params {
476
+ value = $transcription_params|set:"language":$input.language
477
+ }
478
+ }
479
+ }
480
+
481
+ api.request {
482
+ url = "https://api.elevenlabs.io/v1/speech-to-text"
483
+ method = "POST"
484
+ params = {}|set:"file":$input.audio|set:"model_id":"scribe_v1"
485
+ headers = []|push:"xi-api-key: " ~ $env.elevenlabs_audio_key
486
+ timeout = 120
487
+ verify_host = false
488
+ verify_peer = false
489
+ description = "Send audio to ElevenLabs for transcription"
490
+ } as $transcription_response
491
+
492
+ var $transcript_text {
493
+ value = $transcription_response.response.result.text
494
+ description = "Extracted transcript text"
495
+ }
496
+
497
+ var $detected_language {
498
+ value = $transcription_response.response.result.language_code
499
+ description = "Language detected by ElevenLabs"
500
+ }
501
+
502
+ var $confidence {
503
+ value = $transcription_response.response.result.language_probability
504
+ description = "Transcription language confidence score"
505
+ }
506
+
507
+ db.add audio_file {
508
+ data = {
509
+ created_at : "now"
510
+ user : $auth.id
511
+ filename : $filename
512
+ file_type : $file_mime
513
+ file_size : $file_size
514
+ transcription: $transcript_text
515
+ status : "completed"
516
+ metadata : $audio_storage
517
+ audio_file : $audio_storage
518
+ }
519
+ } as $audio_file
520
+
521
+ var $result {
522
+ value = {
523
+ event: "audio_transcription_success",
524
+ audio_id: $audio_file.id,
525
+ user_id: $auth.id,
526
+ filename: $filename,
527
+ transcript_length: ($transcript_text|strlen)
528
+ }
529
+ }
530
+
531
+ debug.log {
532
+ value = {
533
+ event: "audio_transcription_success",
534
+ audio_id: $audio_file.id,
535
+ user_id: $auth.id,
536
+ filename: $filename,
537
+ transcript_length: ($transcript_text|strlen)
538
+ }
539
+
540
+ description = "Log successful transcription"
541
+ }
542
+ }
543
+ }
544
+ }
545
+
546
+ response = $result
547
+
548
+ history = false
549
+ }
550
+ ```
551
+
552
+ ## upload_image_endpoint
553
+
554
+ ```xs
555
+ query "upload/image" verb=POST {
556
+ auth = "user"
557
+ input {
558
+ file? image
559
+ text access_level?=public filters=trim {
560
+ description = "Access level for the image (public or private)"
561
+ }
562
+ }
563
+
564
+ stack {
565
+ try_catch {
566
+ try {
567
+ conditional {
568
+ if ($input.access_level == "private") {
569
+ storage.create_image {
570
+ description = "Store image in cloud storage as private"
571
+ value = $input.image
572
+ access = "private"
573
+ filename = $filename
574
+ } as $image_storage
575
+ }
576
+
577
+ else {
578
+ storage.create_image {
579
+ description = "Store image in cloud storage as public"
580
+ value = $input.image
581
+ access = "public"
582
+ filename = $filename
583
+ } as $image_storage
584
+ }
585
+ }
586
+ }
587
+
588
+ catch {
589
+ throw {
590
+ name = "Invalid image"
591
+ value = $error.message
592
+ }
593
+ }
594
+ }
595
+
596
+ !debug.stop {
597
+ value = $image_storage
598
+ }
599
+
600
+ try_catch {
601
+ try {
602
+ var $allowed_types {
603
+ description = "List of allowed image MIME types"
604
+ value = ["image/jpeg", "image/jpg", "image/png", "image/gif", "image/webp"]
605
+ }
606
+
607
+ var $file_mime {
608
+ description = "MIME type of uploaded file"
609
+ value = $image_storage.mime
610
+ }
611
+
612
+ var $file_size {
613
+ description = "Size of uploaded file in bytes"
614
+ value = $image_storage.size
615
+ }
616
+
617
+ var $filename {
618
+ description = "Original filename"
619
+ value = $image_storage.name
620
+ }
621
+
622
+ conditional {
623
+ if ($allowed_types|contains:$file_mime) {
624
+ throw {
625
+ name = "ValidationError"
626
+ value = "Invalid file type. Only JPEG, PNG, GIF, and WebP images are allowed."
627
+ }
628
+ }
629
+ }
630
+
631
+ conditional {
632
+ if ($file_size > 10485760) {
633
+ throw {
634
+ name = "ValidationError"
635
+ value = "File size too large. Maximum size is 10MB."
636
+ }
637
+ }
638
+ }
639
+
640
+ db.get "user" {
641
+ description = "Verify user exists"
642
+ field_name = "id"
643
+ field_value = $auth.id
644
+ } as $user
645
+
646
+ conditional {
647
+ if ($user == "") {
648
+ throw {
649
+ name = "ValidationError"
650
+ value = "Invalid user ID provided."
651
+ }
652
+ }
653
+ }
654
+
655
+ db.add images {
656
+ description = "Save image metadata to database"
657
+ data = {
658
+ created_at : "now"
659
+ user_id : $auth.id
660
+ filename : $filename
661
+ file_type : $file_mime
662
+ file_size : $file_size
663
+ image_url : $image_storage.path
664
+ access_level: $input.access_level
665
+ is_active : true
666
+ image_meta : $image_storage
667
+ }
668
+ } as $image_record
669
+ }
670
+
671
+ catch {
672
+ debug.log {
673
+ description = "Log upload failure"
674
+ value = "Image upload failed:"|concat:$error.message:" "
675
+ }
676
+ }
677
+ }
678
+ }
679
+
680
+ response = {
681
+ success : true
682
+ image_id : $image_record.id
683
+ image_url: $image_storage.path
684
+ }
685
+
686
+ history = "inherit"
687
+ }
688
+ ```
689
+
690
+ ## retrieve_active_user_profile
691
+
692
+ ````xs
693
+ // API endpoint to retrieve active user profile details based on user_id
694
+ query "user_profile/{user_id}" verb=GET {
695
+ api_group = "profiles"
696
+
697
+ input {
698
+ // Unique identifier for the user
699
+ int user_id
700
+ }
701
+
702
+ stack {
703
+ // Retrieve active user profile details
704
+ db.query user {
705
+ where = $db.user.id == $input.user_id && $db.user.is_active
706
+ return = {type: "single"}
707
+ mock = {
708
+ "should return null for inactive": null
709
+ "should return active profile" : ```
710
+ {
711
+ "id": 7,
712
+ "user": 42,
713
+ "full_name": "Jane Doe",
714
+ "email": "jane@example.com",
715
+ "is_active": true
716
+ }
717
+ ```
718
+ }
719
+ } as $profile
720
+ }
721
+
722
+ response = $profile
723
+ history = 10000
724
+
725
+ test "should return null for inactive" {
726
+ input = {user_id: 99}
727
+
728
+ expect.to_be_null ($response)
729
+ }
730
+
731
+ test "should return active profile" {
732
+ input = {user_id: 42}
733
+
734
+ expect.to_equal ($response) {
735
+ value = {
736
+ id : 7
737
+ user : 42
738
+ full_name: "Jane Doe"
739
+ email : "jane@example.com"
740
+ is_active: true
741
+ }
742
+ }
743
+ }
744
+ }
745
+ ````
746
+
747
+ ## blog_posts_by_user
748
+
749
+ ```xs
750
+ query "blog_posts_by_user/{user_id}" verb=GET {
751
+ description = "list all post by a given user and the number of comments and likes it received"
752
+ auth = "user"
753
+ input {
754
+ int user_id
755
+ }
756
+
757
+ stack {
758
+ db.query blog_post {
759
+ where = $db.blog_post.author_id == $input.user_id
760
+ sort = {blog_post.publication_date: "desc"}
761
+ return = {type: "list", paging: {page: 1, per_page: 25, totals: true}}
762
+ addon = [
763
+ {
764
+ name : "blog_post_likes"
765
+ input: {blog_post_id: $output.id}
766
+ as : "items.blog_post_likes"
767
+ }
768
+ {
769
+ name : "blog_post_comments"
770
+ input: {blog_post_id: $output.id}
771
+ as : "items.blog_post_comments"
772
+ }
773
+ ]
774
+ } as $posts
775
+ }
776
+
777
+ response = $posts
778
+
779
+ history = "inherit"
780
+ }
781
+ ```
782
+
783
+ ## notification_system_endpoint
784
+
785
+ ```xs
786
+ query "send_notification" verb=POST {
787
+ description = "Sends notifications to users via multiple channels (push, email, SMS). It validates input, checks user notification preferences, routes to appropriate external services, tracks delivery status, and logs all notification attempts with their outcomes."
788
+ input {
789
+ int user_id
790
+ text notification_type {
791
+ description = "Type of notification: push, email, sms"
792
+ }
793
+
794
+ text title filters=trim {
795
+ description = "Notification title"
796
+ }
797
+
798
+ text message filters=trim {
799
+ description = "Notification message content"
800
+ }
801
+
802
+ json data {
803
+ description = "Additional notification data"
804
+ }
805
+
806
+ bool force? {
807
+ description = "Force send even if user has disabled this notification type"
808
+ }
809
+ }
810
+
811
+ stack {
812
+ debug.log {
813
+ description = "Log notification attempt start"
814
+ value = "Starting notification send for user " ~ $input.user_id
815
+ }
816
+
817
+ precondition ($input.notification_type == "push" || $input.notification_type == "email" || $input.notification_type == "sms") {
818
+ description = "Validate that notification type is one of the three supported types"
819
+ error_type = "inputerror"
820
+ error = "Invalid notification type. Must be: push, email, or sms"
821
+ }
822
+
823
+ precondition (($input.title|strlen) > 0) {
824
+ description = "Ensure the notification title is not empty"
825
+ error_type = "inputerror"
826
+ error = "Notification title is required"
827
+ }
828
+
829
+ db.query "notification_preference" {
830
+ description = "Check user notification preferences"
831
+ where = $db.notification_preference.user_id == $input.user_id && $db.notification_preference.notification_type == $input.notification_type
832
+ return = {type: "list"}
833
+ } as $user_preferences
834
+
835
+ conditional {
836
+ description = "If no preferences exist, create default preferences with notifications enabled. Otherwise, get the user's enabled status from existing preferences"
837
+ if (($user_preferences|count) == 0) {
838
+ db.add notification_preference {
839
+ description = "Create default notification preference"
840
+ data = {
841
+ user_id : $input.user_id
842
+ notification_type: $input.notification_type
843
+ enabled : true
844
+ preferences : "{}"
845
+ }
846
+ } as $new_preference
847
+
848
+ var $user_enabled {
849
+ value = true
850
+ }
851
+ }
852
+
853
+ elseif ($input.notification_type == "asdfasdf") {
854
+ }
855
+
856
+ else {
857
+ var $user_enabled {
858
+ value = ($user_preferences|first).enabled
859
+ }
860
+ }
861
+ }
862
+
863
+ conditional {
864
+ description = "Set up different API calls based on notification type"
865
+ if ($input.notification_type == "push") {
866
+ api.request {
867
+ description = "Sample API call"
868
+ url = "https://mandrillapp.com/api/1.0/messages/send"
869
+ method = "POST"
870
+ params = {}|set:"key":$env.your_api_key|set:"message":({}|set:"from_email":"you@yourdomain.com"|set:"from_name":"Your Name"|set:"subject":"Test Transactional Email"|set:"text":"Hello, this is a transactional email!"|set:"to":([]|push:({}|set:"email":"recipient@example.com"|set:"name":"Recipient Name"|set:"type":"to")))
871
+ headers = []|push:"Content-Type: application/json"
872
+ } as $api_endpoint
873
+ }
874
+
875
+ elseif ($input.notification_type == "email") {
876
+ api.request {
877
+ description = "Sample API call"
878
+ url = "https://mandrillapp.com/api/1.0/messages/send"
879
+ method = "POST"
880
+ params = {}|set:"key":$env.your_api_key|set:"message":({}|set:"from_email":"you@yourdomain.com"|set:"from_name":"Your Name"|set:"subject":"Test Transactional Email"|set:"text":"Hello, this is a transactional email!"|set:"to":([]|push:({}|set:"email":"recipient@example.com"|set:"name":"Recipient Name"|set:"type":"to")))
881
+ headers = []|push:"Content-Type: application/json"
882
+ } as $api_endpoint
883
+ }
884
+
885
+ elseif ($input.notification_type == "sms") {
886
+ api.request {
887
+ description = "Sample API call"
888
+ url = "https://api.twilio.com/2010-04-01/Accounts/ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Messages"
889
+ method = "POST"
890
+ params = {}|set:"To":" 1234567890"|set:"From":" 1987654321"|set:"Body":"Hello from Twilio!"
891
+ headers = []|push:("Authorization: Basic %s"|sprintf:("ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX:your_auth_token"|base64_encode))
892
+ } as $api_endpoint
893
+ }
894
+ }
895
+
896
+ conditional {
897
+ description = "Skip user if not enabled AND force send is false"
898
+ if ($user_enabled == false && $input.force == false) {
899
+ var $status_result {
900
+ value = "skipped"
901
+ }
902
+
903
+ var $external_ref {
904
+ value = ""
905
+ }
906
+
907
+ debug.log {
908
+ value = "Skipped user"
909
+ }
910
+ }
911
+ }
912
+
913
+ conditional {
914
+ description = "Process the API response and set status"
915
+ if ($api_response.status_code >= 200 && $api_response.status_code < 300) {
916
+ var $status_result {
917
+ value = "sent"
918
+ }
919
+
920
+ var $external_ref {
921
+ value = $api_response.body.id
922
+ }
923
+ }
924
+
925
+ elseif ($status_result == "skipped") {
926
+ debug.log {
927
+ value = "Status already set above, do nothing"
928
+ }
929
+ }
930
+
931
+ else {
932
+ var $status_result {
933
+ value = "failed"
934
+ }
935
+
936
+ var $external_ref {
937
+ value = ""
938
+ }
939
+ }
940
+ }
941
+
942
+ db.add notification_log {
943
+ description = "Log the notification attempt to the database"
944
+ data = {
945
+ created_at : "now"
946
+ user_id : $input.user_id
947
+ notification_type: $input.notification_type
948
+ title : $input.title
949
+ message : $input.message
950
+ response_data : $api_endpoint
951
+ }
952
+ } as $notification_log
953
+ }
954
+
955
+ response = null
956
+
957
+ history = 1000
958
+ }
959
+ ```
960
+
961
+ ## products_search_endpoint
962
+
963
+ Searches products with optional filters and pagination. Notice how the `category_id` filter is optional and the search term will be ignored if not provided (thanks to the operator `==?`).
964
+
965
+ ```xs
966
+ query "products/search" verb=GET {
967
+ input {
968
+ text search_term? filters=trim {
969
+ description = "Search term to match against product name and description"
970
+ }
971
+
972
+ int category_id? filters=min:0 {
973
+ description = "Optional category ID to filter products"
974
+ }
975
+
976
+ int page?=1 filters=min:1 {
977
+ description = "Page number for pagination (starts at 1)"
978
+ }
979
+
980
+ int per_page?=20 filters=min:1|max:100 {
981
+ description = "Number of items per page (max 100)"
982
+ }
983
+ }
984
+
985
+ stack {
986
+ db.query "product" {
987
+ description = "Search products with filters and pagination"
988
+ where = $db.product.category_id ==? $input.category_id && ($db.product.name includes? $input.search_term || $db.product.description includes? $input.search_term)
989
+ sort = {product.price: "asc"}
990
+ return = {type: "list"}
991
+ } as $products
992
+ }
993
+
994
+ response = $products
995
+
996
+ history = "inherit"
997
+ }
998
+ ```
999
+
1000
+ ## signup_endpoint
1001
+
1002
+ ```xs
1003
+ query "auth/signup" verb=POST {
1004
+ description = "Signup and retrieve an authentication token"
1005
+ input {
1006
+ text name? {
1007
+ description = "User's name (optional)"
1008
+ }
1009
+
1010
+ email email? {
1011
+ description = "User's email address (required for signup)"
1012
+ sensitive = true
1013
+ }
1014
+
1015
+ text password? {
1016
+ description = "User's password (required for signup)"
1017
+ sensitive = true
1018
+ }
1019
+ }
1020
+
1021
+ stack {
1022
+ db.get "user" {
1023
+ description = "Check if a user with this email already exists"
1024
+ field_name = "email"
1025
+ field_value = $input.email
1026
+ } as $user
1027
+
1028
+ precondition ($user == null) {
1029
+ description = "Prevent duplicate signup with the same email"
1030
+ error_type = "accessdenied"
1031
+ error = "This account is already in use."
1032
+ }
1033
+
1034
+ db.add user {
1035
+ description = "Create a new user record with provided details"
1036
+ data = {
1037
+ created_at: "now"
1038
+ name : $input.name
1039
+ email : $input.email
1040
+ password : $input.password
1041
+ }
1042
+ } as $user
1043
+
1044
+ security.create_auth_token {
1045
+ description = "Generate an authentication token for the new user"
1046
+ table = "user"
1047
+ extras = {}
1048
+ expiration = 86400
1049
+ id = $user.id
1050
+ } as $authToken
1051
+ }
1052
+
1053
+ response = {authToken: $authToken}
1054
+
1055
+ history = 1000
1056
+ }
1057
+ ```
1058
+
1059
+ ## search_users_endpoint
1060
+
1061
+ ```xs
1062
+ query "users" verb=GET {
1063
+ auth = "user"
1064
+ input {
1065
+ text filter
1066
+ }
1067
+
1068
+ stack {
1069
+ db.query "user" {
1070
+ description = "Search users"
1071
+ where = $db.user.name includes $input.filter
1072
+ sort = {user.name: "asc"}
1073
+ return = {type: "list"}
1074
+ } as $users
1075
+ }
1076
+
1077
+ response = $users
1078
+
1079
+ history = "inherit"
1080
+ }
1081
+ ```
1082
+
1083
+ ```xs
1084
+ query "webhook_receiver" verb=POST {
1085
+ input {
1086
+ text webhook_id
1087
+ text signature?
1088
+ }
1089
+
1090
+ stack {
1091
+ util.get_input {
1092
+ encoding = "json"
1093
+ } as $payload_data
1094
+
1095
+ conditional {
1096
+ if ($payload_data.body.event_type == "user.created") {
1097
+ db.add webhook_logs {
1098
+ data = {
1099
+ webhook_id : $input.webhook_id
1100
+ event_type : "user.created"
1101
+ status : "processed"
1102
+ payload : $payload_data
1103
+ signature : $input.signature
1104
+ user_id : $payload_data.body.user.id
1105
+ received_at : now
1106
+ processed_at: now
1107
+ retry_count : 0
1108
+ max_retries : 3
1109
+ }
1110
+ } as $webhook_record
1111
+ }
1112
+
1113
+ elseif ($payload_data.body.event_type == "payment.completed") {
1114
+ db.add webhook_logs {
1115
+ data = {
1116
+ webhook_id : $input.webhook_id
1117
+ event_type : "payment.completed"
1118
+ status : "processed"
1119
+ payload : $payload_data
1120
+ signature : $input.signature
1121
+ payment_id : $payload_data.payment.id
1122
+ amount : $payload_data.payment.amount
1123
+ received_at : now
1124
+ processed_at: now
1125
+ retry_count : 0
1126
+ max_retries : 3
1127
+ }
1128
+ } as $webhook_record
1129
+ }
1130
+
1131
+ else {
1132
+ db.add webhook_logs {
1133
+ data = {
1134
+ webhook_id : $input.webhook_id
1135
+ event_type : $payload_data.event_type
1136
+ status : "processed"
1137
+ payload : $payload_data
1138
+ signature : $input.signature
1139
+ received_at : now
1140
+ processed_at: now
1141
+ retry_count : 0
1142
+ max_retries : 3
1143
+ }
1144
+ } as $webhook_record
1145
+ }
1146
+ }
1147
+ }
1148
+
1149
+ response = {
1150
+ webhook_processed: true
1151
+ message : "Webhook processed successfully"
1152
+ webhook_id : $input.webhook_id
1153
+ event_type : $payload_data.body.event_type
1154
+ }
1155
+
1156
+ history = 100
1157
+ }
1158
+ ```
1159
+
1160
+ ```
1161
+ query "list_orders" verb=GET {
1162
+ description = "Display all the order in a html table view"
1163
+ input {
1164
+ int year?=2025
1165
+ }
1166
+
1167
+ stack {
1168
+ var $start_of_year {
1169
+ description = "Start of the year for filtering"
1170
+ value = $input.year ~ "-01-01T00:00:00.000-08:00"
1171
+ }
1172
+
1173
+ var $end_of_year {
1174
+ description = "End of the year for filtering (exclusive)"
1175
+ value = ($input.year + 1) ~ "-01-01T00:00:00.000-08:00"
1176
+ }
1177
+
1178
+ db.query orders {
1179
+ where = $db.orders.order_date >= $start_of_year && $db.orders.order_date < $end_of_year
1180
+ return = {type: "list"}
1181
+ addon = [
1182
+ {
1183
+ name : "customer"
1184
+ input: {customer_id: $output.customer_id}
1185
+ as : "_customer"
1186
+ }
1187
+ ]
1188
+ } as $orders
1189
+
1190
+ util.template_engine {
1191
+ description = "Initialize HTML output with table structure"
1192
+ value = """
1193
+ <!DOCTYPE html>
1194
+ <html lang="en">
1195
+ <head>
1196
+ <meta charset="UTF-8">
1197
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1198
+ <title>Order List</title>
1199
+ <!-- Bootstrap CSS CDN -->
1200
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
1201
+ </head>
1202
+ <body>
1203
+ <div class="container mt-5">
1204
+ <h1 class="mb-4">Customer Orders</h1>
1205
+
1206
+ {% if $var.orders is defined and $var.orders|length > 0 %}
1207
+ <table class="table table-striped table-bordered table-hover">
1208
+ <thead class="table-dark">
1209
+ <tr>
1210
+ <th scope="col">Order ID</th>
1211
+ <th scope="col">Customer Address</th>
1212
+ <th scope="col">Product Name</th>
1213
+ <th scope="col">Price</th>
1214
+ <th scope="col">Status</th>
1215
+ </tr>
1216
+ </thead>
1217
+ <tbody>
1218
+ {% for order in $var.orders %}
1219
+ <tr>
1220
+ <td>{{ order.id }}</td>
1221
+ <td>{{ order._customer.street_address }}{% if order._customer.city %}, {{ order._customer.city }}{% endif %}{% if order._customer.zip_code %}, {{ order._customer.zip_code }}{% endif %}</td>
1222
+ <td>{{ order.product_name }}</td>
1223
+ <td>${{ order.order_total|number_format(2) }}</td>
1224
+ <td><span class="badge bg-{% if order.status == 'completed' %}success{% elseif order.status == 'pending' %}warning{% else %}danger{% endif %}">{{ order.status|capitalize }}</span></td>
1225
+
1226
+ </tr>
1227
+ {% endfor %}
1228
+ </tbody>
1229
+ </table>
1230
+ {% else %}
1231
+ <div class="alert alert-info" role="alert">
1232
+ No orders found.
1233
+ </div>
1234
+ {% endif %}
1235
+
1236
+ </div>
1237
+
1238
+ <!-- Bootstrap JS and Popper.js CDN (optional, for interactive components) -->
1239
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
1240
+ </body>
1241
+ </html>
1242
+ """
1243
+ } as $html_output
1244
+
1245
+ util.set_header {
1246
+ value = "Content-Type: text/html; charset=utf-8"
1247
+ duplicates = "replace"
1248
+ }
1249
+ }
1250
+
1251
+ response = $html_output
1252
+
1253
+ history = "inherit"
1254
+ }
1255
+ ```