@volter/twin-upstash 0.1.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 (162) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +202 -0
  3. package/api/src/fetch.ts +54 -0
  4. package/api/src/generated/surface.gen.json +1 -0
  5. package/api/src/generated/ui.gen.json +1 -0
  6. package/api/src/index.ts +19 -0
  7. package/api/src/key-gate.ts +30 -0
  8. package/api/src/manifest.ts +103 -0
  9. package/api/src/screens/developer-api.tsx +106 -0
  10. package/api/src/screens/qstash.tsx +99 -0
  11. package/api/src/screens/session.tsx +125 -0
  12. package/api/src/screens/teams.tsx +114 -0
  13. package/api/src/semantics/backups.ts +90 -0
  14. package/api/src/semantics/index.ts +191 -0
  15. package/api/src/semantics/shared.ts +42 -0
  16. package/api/src/semantics/teams.ts +108 -0
  17. package/api/src/semantics/time.ts +40 -0
  18. package/dist/api/src/fetch.d.ts +15 -0
  19. package/dist/api/src/fetch.js +44 -0
  20. package/dist/api/src/fetch.ts +54 -0
  21. package/dist/api/src/generated/surface.gen.json +1 -0
  22. package/dist/api/src/generated/ui.gen.json +1 -0
  23. package/dist/api/src/index.ts +19 -0
  24. package/dist/api/src/key-gate.d.ts +3 -0
  25. package/dist/api/src/key-gate.js +30 -0
  26. package/dist/api/src/key-gate.ts +30 -0
  27. package/dist/api/src/manifest.d.ts +2 -0
  28. package/dist/api/src/manifest.js +81 -0
  29. package/dist/api/src/manifest.ts +103 -0
  30. package/dist/api/src/screens/developer-api.d.ts +3 -0
  31. package/dist/api/src/screens/developer-api.js +101 -0
  32. package/dist/api/src/screens/developer-api.tsx +106 -0
  33. package/dist/api/src/screens/qstash.d.ts +3 -0
  34. package/dist/api/src/screens/qstash.js +92 -0
  35. package/dist/api/src/screens/qstash.tsx +99 -0
  36. package/dist/api/src/screens/session.d.ts +9 -0
  37. package/dist/api/src/screens/session.js +118 -0
  38. package/dist/api/src/screens/session.tsx +125 -0
  39. package/dist/api/src/screens/teams.d.ts +3 -0
  40. package/dist/api/src/screens/teams.js +99 -0
  41. package/dist/api/src/screens/teams.tsx +114 -0
  42. package/dist/api/src/semantics/backups.d.ts +7 -0
  43. package/dist/api/src/semantics/backups.js +75 -0
  44. package/dist/api/src/semantics/backups.ts +90 -0
  45. package/dist/api/src/semantics/index.d.ts +10 -0
  46. package/dist/api/src/semantics/index.js +191 -0
  47. package/dist/api/src/semantics/index.ts +191 -0
  48. package/dist/api/src/semantics/shared.d.ts +21 -0
  49. package/dist/api/src/semantics/shared.js +34 -0
  50. package/dist/api/src/semantics/shared.ts +42 -0
  51. package/dist/api/src/semantics/teams.d.ts +13 -0
  52. package/dist/api/src/semantics/teams.js +100 -0
  53. package/dist/api/src/semantics/teams.ts +108 -0
  54. package/dist/api/src/semantics/time.d.ts +2 -0
  55. package/dist/api/src/semantics/time.js +34 -0
  56. package/dist/api/src/semantics/time.ts +40 -0
  57. package/dist/qstash/src/doors.d.ts +6 -0
  58. package/dist/qstash/src/doors.js +33 -0
  59. package/dist/qstash/src/doors.ts +51 -0
  60. package/dist/qstash/src/egress.d.ts +7 -0
  61. package/dist/qstash/src/egress.js +66 -0
  62. package/dist/qstash/src/egress.ts +58 -0
  63. package/dist/qstash/src/fetch.d.ts +7 -0
  64. package/dist/qstash/src/fetch.js +48 -0
  65. package/dist/qstash/src/fetch.ts +46 -0
  66. package/dist/qstash/src/generated/surface.gen.json +1 -0
  67. package/dist/qstash/src/generated/ui.gen.json +1 -0
  68. package/dist/qstash/src/index.ts +35 -0
  69. package/dist/qstash/src/manifest.d.ts +10 -0
  70. package/dist/qstash/src/manifest.js +105 -0
  71. package/dist/qstash/src/manifest.ts +134 -0
  72. package/dist/qstash/src/semantics/account.d.ts +29 -0
  73. package/dist/qstash/src/semantics/account.js +91 -0
  74. package/dist/qstash/src/semantics/account.ts +98 -0
  75. package/dist/qstash/src/semantics/delivery.d.ts +17 -0
  76. package/dist/qstash/src/semantics/delivery.js +274 -0
  77. package/dist/qstash/src/semantics/delivery.ts +264 -0
  78. package/dist/qstash/src/semantics/dlq.d.ts +4 -0
  79. package/dist/qstash/src/semantics/dlq.js +51 -0
  80. package/dist/qstash/src/semantics/dlq.ts +61 -0
  81. package/dist/qstash/src/semantics/index.d.ts +2 -0
  82. package/dist/qstash/src/semantics/index.js +10 -0
  83. package/dist/qstash/src/semantics/index.ts +13 -0
  84. package/dist/qstash/src/semantics/keys.d.ts +2 -0
  85. package/dist/qstash/src/semantics/keys.js +9 -0
  86. package/dist/qstash/src/semantics/keys.ts +14 -0
  87. package/dist/qstash/src/semantics/messages.d.ts +74 -0
  88. package/dist/qstash/src/semantics/messages.js +233 -0
  89. package/dist/qstash/src/semantics/messages.ts +249 -0
  90. package/dist/qstash/src/semantics/queues.d.ts +2 -0
  91. package/dist/qstash/src/semantics/queues.js +60 -0
  92. package/dist/qstash/src/semantics/queues.ts +66 -0
  93. package/dist/qstash/src/semantics/schedules.d.ts +19 -0
  94. package/dist/qstash/src/semantics/schedules.js +125 -0
  95. package/dist/qstash/src/semantics/schedules.ts +132 -0
  96. package/dist/qstash/src/semantics/shared.d.ts +45 -0
  97. package/dist/qstash/src/semantics/shared.js +115 -0
  98. package/dist/qstash/src/semantics/shared.ts +121 -0
  99. package/dist/qstash/src/semantics/urlgroups.d.ts +2 -0
  100. package/dist/qstash/src/semantics/urlgroups.js +58 -0
  101. package/dist/qstash/src/semantics/urlgroups.ts +69 -0
  102. package/dist/qstash/src/semantics/workflows.d.ts +44 -0
  103. package/dist/qstash/src/semantics/workflows.js +379 -0
  104. package/dist/qstash/src/semantics/workflows.ts +401 -0
  105. package/dist/qstash/src/signing.d.ts +4 -0
  106. package/dist/qstash/src/signing.js +16 -0
  107. package/dist/qstash/src/signing.ts +19 -0
  108. package/dist/src/cli.d.ts +2 -0
  109. package/dist/src/cli.js +35 -0
  110. package/dist/src/generated/surface.gen.json +1 -0
  111. package/dist/src/index.d.ts +18 -0
  112. package/dist/src/index.js +124 -0
  113. package/dist/src/manifest.d.ts +14 -0
  114. package/dist/src/manifest.js +8 -0
  115. package/dist/src/upstash-budget.d.ts +85 -0
  116. package/dist/src/upstash-budget.js +440 -0
  117. package/dist/src/upstash-capabilities.d.ts +4 -0
  118. package/dist/src/upstash-capabilities.js +1286 -0
  119. package/dist/src/upstash-conformance.d.ts +7 -0
  120. package/dist/src/upstash-conformance.js +119 -0
  121. package/dist/src/upstash-connector.d.ts +115 -0
  122. package/dist/src/upstash-connector.js +309 -0
  123. package/dist/src/upstash-lua.d.ts +140 -0
  124. package/dist/src/upstash-lua.js +1229 -0
  125. package/dist/src/upstash-server.d.ts +29 -0
  126. package/dist/src/upstash-server.js +81 -0
  127. package/dist/src/upstash-store.d.ts +114 -0
  128. package/dist/src/upstash-store.js +1663 -0
  129. package/dist/src/upstash-twin.d.ts +73 -0
  130. package/dist/src/upstash-twin.js +437 -0
  131. package/package.json +59 -0
  132. package/qstash/src/doors.ts +51 -0
  133. package/qstash/src/egress.ts +58 -0
  134. package/qstash/src/fetch.ts +46 -0
  135. package/qstash/src/generated/surface.gen.json +1 -0
  136. package/qstash/src/generated/ui.gen.json +1 -0
  137. package/qstash/src/index.ts +35 -0
  138. package/qstash/src/manifest.ts +134 -0
  139. package/qstash/src/semantics/account.ts +98 -0
  140. package/qstash/src/semantics/delivery.ts +264 -0
  141. package/qstash/src/semantics/dlq.ts +61 -0
  142. package/qstash/src/semantics/index.ts +13 -0
  143. package/qstash/src/semantics/keys.ts +14 -0
  144. package/qstash/src/semantics/messages.ts +249 -0
  145. package/qstash/src/semantics/queues.ts +66 -0
  146. package/qstash/src/semantics/schedules.ts +132 -0
  147. package/qstash/src/semantics/shared.ts +121 -0
  148. package/qstash/src/semantics/urlgroups.ts +69 -0
  149. package/qstash/src/semantics/workflows.ts +401 -0
  150. package/qstash/src/signing.ts +19 -0
  151. package/src/cli.ts +36 -0
  152. package/src/generated/surface.gen.json +1 -0
  153. package/src/index.ts +203 -0
  154. package/src/manifest.ts +26 -0
  155. package/src/upstash-budget.ts +486 -0
  156. package/src/upstash-capabilities.ts +1418 -0
  157. package/src/upstash-conformance.ts +131 -0
  158. package/src/upstash-connector.ts +340 -0
  159. package/src/upstash-lua.ts +1120 -0
  160. package/src/upstash-server.ts +103 -0
  161. package/src/upstash-store.ts +1437 -0
  162. package/src/upstash-twin.ts +465 -0
@@ -0,0 +1 @@
1
+ {"generatedBy":"scripts/derive-pack.ts","spec":"openapi.yaml.gz","format":"openapi","version":"1.0.0","basePath":"/v2","resources":[{"name":"AuditLog","schema":"AuditLog","fields":[{"name":"action","type":"integer","nullable":false},{"name":"action_string","type":"string","nullable":false},{"name":"actor","type":"string","nullable":false},{"name":"customer_id","type":"string","nullable":false},{"name":"entity","type":"string","nullable":false},{"name":"ip","type":"string","nullable":false},{"name":"log_id","type":"string","nullable":false},{"name":"notes","type":"string","nullable":false},{"name":"readable_format","type":"string","nullable":false},{"name":"reason","type":"string","nullable":false},{"name":"secondary_entity","type":"string","nullable":false},{"name":"source","type":"string","nullable":false},{"name":"third_entity","type":"string","nullable":false},{"name":"timestamp","type":"integer","nullable":false},{"name":"ttl","type":"integer","nullable":false}],"stateCandidates":[],"expandable":[]},{"name":"Backup","schema":"Backup","fields":[{"name":"backup_id","type":"string","nullable":false},{"name":"backup_size","type":"integer","nullable":false},{"name":"creation_time","type":"integer","nullable":false},{"name":"customer_id","type":"string","nullable":false},{"name":"daily_backup","type":"string","nullable":false,"enum":["true","false"]},{"name":"database_id","type":"string","nullable":false},{"name":"hourly_backup","type":"string","nullable":false,"enum":["true","false"]},{"name":"internal_backup_tag","type":"string","nullable":false},{"name":"name","type":"string","nullable":false},{"name":"state","type":"string","nullable":false,"enum":["pending","completed","failed"]}],"stateCandidates":[{"field":"daily_backup","values":["false","true"]},{"field":"hourly_backup","values":["false","true"]},{"field":"state","values":["completed","failed","pending"]}],"expandable":[]},{"name":"Database","schema":"Database","fields":[{"name":"all_members","type":"array","nullable":false},{"name":"auto_upgrade","type":"boolean","nullable":false},{"name":"budget","type":"integer","nullable":false},{"name":"consistent","type":"boolean","nullable":false},{"name":"creation_time","type":"integer","nullable":false},{"name":"customer_id","type":"string","nullable":false},{"name":"daily_backup_enabled","type":"boolean","nullable":false},{"name":"database_id","type":"string","nullable":false},{"name":"database_name","type":"string","nullable":false},{"name":"db_acl_default_user_status","type":"string","nullable":false,"enum":["true","false"]},{"name":"db_acl_enabled","type":"string","nullable":false,"enum":["true","false"]},{"name":"db_conn_idle_timeout","type":"integer","nullable":false},{"name":"db_disk_threshold","type":"integer","nullable":false},{"name":"db_eviction","type":"boolean","nullable":false},{"name":"db_lua_credits_per_min","type":"integer","nullable":false},{"name":"db_lua_timeout","type":"integer","nullable":false},{"name":"db_max_clients","type":"integer","nullable":false},{"name":"db_max_commands_per_second","type":"integer","nullable":false},{"name":"db_max_entry_size","type":"integer","nullable":false},{"name":"db_max_loads_per_sec","type":"integer","nullable":false},{"name":"db_max_request_size","type":"integer","nullable":false},{"name":"db_memory_threshold","type":"integer","nullable":false},{"name":"db_request_limit","type":"integer","nullable":false},{"name":"db_resource_size","type":"string","nullable":false,"enum":["S","M","L","XL","XXL","3XL"]},{"name":"db_store_max_idle","type":"integer","nullable":false},{"name":"db_type","type":"string","nullable":false,"enum":["bolt","badger","pebble"]},{"name":"endpoint","type":"string","nullable":false},{"name":"eviction","type":"boolean","nullable":false},{"name":"last_plan_upgrade_time","type":"integer","nullable":false},{"name":"modifying_state","type":"string","nullable":false},{"name":"password","type":"string","nullable":false},{"name":"port","type":"integer","nullable":false},{"name":"primary_members","type":"array","nullable":false},{"name":"primary_region","type":"string","nullable":false,"enum":["us-east-1","us-east-2","us-west-1","us-west-2","ca-central-1","eu-central-1","eu-west-1","eu-west-2","sa-east-1","ap-south-1","ap-northeast-1","ap-southeast-1","ap-southeast-2","af-south-1","us-central1","us-east4","europe-west1","asia-northeast1"]},{"name":"prod_pack_enabled","type":"boolean","nullable":false},{"name":"prometheus_enabled","type":"string","nullable":false,"enum":["true","false"]},{"name":"read_only_rest_token","type":"string","nullable":false},{"name":"read_regions","type":"array","nullable":false},{"name":"region","type":"string","nullable":false,"enum":["global"]},{"name":"replicas","type":"integer","nullable":false},{"name":"rest_token","type":"string","nullable":false},{"name":"securityAddons","type":"object","nullable":false},{"name":"state","type":"string","nullable":false,"enum":["active","suspended","passive"]},{"name":"tls","type":"boolean","nullable":false},{"name":"type","type":"string","nullable":false,"enum":["free","payg","pro","paid"]}],"stateCandidates":[{"field":"db_acl_default_user_status","values":["false","true"]},{"field":"db_acl_enabled","values":["false","true"]},{"field":"db_resource_size","values":["3XL","L","M","S","XL","XXL"]},{"field":"db_type","values":["badger","bolt","pebble"]},{"field":"primary_region","values":["af-south-1","ap-northeast-1","ap-south-1","ap-southeast-1","ap-southeast-2","asia-northeast1","ca-central-1","eu-central-1","eu-west-1","eu-west-2","europe-west1","sa-east-1","us-central1","us-east-1","us-east-2","us-east4","us-west-1","us-west-2"]},{"field":"prometheus_enabled","values":["false","true"]},{"field":"state","values":["active","passive","suspended"]},{"field":"type","values":["free","paid","payg","pro"]}],"expandable":[]},{"name":"QStashUser","schema":"QStashUser","fields":[{"name":"active","type":"boolean","nullable":false},{"name":"budget","type":"integer","nullable":false},{"name":"created_by","type":"string","nullable":false},{"name":"creation_time","type":"integer","nullable":false},{"name":"customer_id","type":"string","nullable":false},{"name":"deletion_time","type":"integer","nullable":false},{"name":"enterprise","type":"object","nullable":false},{"name":"id","type":"string","nullable":false},{"name":"last_plan_upgrade_time","type":"integer","nullable":false},{"name":"max_bandwidth_per_second","type":"integer","nullable":false},{"name":"max_delay","type":"integer","nullable":false},{"name":"max_dlq_retention_time_milis","type":"integer","nullable":false},{"name":"max_dlq_size","type":"integer","nullable":false},{"name":"max_endpoints_per_topic","type":"integer","nullable":false},{"name":"max_events_size","type":"integer","nullable":false},{"name":"max_global_parallelism","type":"integer","nullable":false},{"name":"max_message_size","type":"integer","nullable":false},{"name":"max_parallelism","type":"integer","nullable":false},{"name":"max_queues","type":"integer","nullable":false},{"name":"max_requests_per_day","type":"integer","nullable":false},{"name":"max_requests_per_day_hard","type":"integer","nullable":false},{"name":"max_requests_per_second","type":"integer","nullable":false},{"name":"max_retries","type":"integer","nullable":false},{"name":"max_schedules","type":"integer","nullable":false},{"name":"max_topics","type":"integer","nullable":false},{"name":"modifying_state","type":"string","nullable":false},{"name":"prod_pack_enabled","type":"boolean","nullable":false},{"name":"prometheus_enabled","type":"string","nullable":false,"enum":["true","false"]},{"name":"read_only_token","type":"string","nullable":false},{"name":"region","type":"string","nullable":false,"enum":["eu-central-1","us-east-1"]},{"name":"reserved_price","type":"integer","nullable":false},{"name":"reserved_type","type":"string","nullable":false},{"name":"state","type":"string","nullable":false},{"name":"timeout","type":"integer","nullable":false},{"name":"token","type":"string","nullable":false},{"name":"type","type":"string","nullable":false,"enum":["free","paid"]}],"stateCandidates":[{"field":"active","values":["false","true"]},{"field":"prometheus_enabled","values":["false","true"]},{"field":"region","values":["eu-central-1","us-east-1"]},{"field":"state","values":[]},{"field":"type","values":["free","paid"]}],"expandable":[]},{"name":"SearchIndex","schema":"SearchIndex","fields":[{"name":"creation_time","type":"integer","nullable":false},{"name":"customer_id","type":"string","nullable":false},{"name":"endpoint","type":"string","nullable":false},{"name":"id","type":"string","nullable":false},{"name":"input_enrichment_enabled","type":"boolean","nullable":false},{"name":"max_daily_queries","type":"integer","nullable":false},{"name":"max_daily_updates","type":"integer","nullable":false},{"name":"max_monthly_bandwidth","type":"integer","nullable":false},{"name":"max_monthly_reranks","type":"integer","nullable":false},{"name":"max_query_per_second","type":"integer","nullable":false},{"name":"max_reads_per_request","type":"integer","nullable":false},{"name":"max_vector_count","type":"integer","nullable":false},{"name":"max_writes_per_request","type":"integer","nullable":false},{"name":"max_writes_per_second","type":"integer","nullable":false},{"name":"name","type":"string","nullable":false},{"name":"read_only_token","type":"string","nullable":false},{"name":"region","type":"string","nullable":false,"enum":["eu-west-1","us-central1"]},{"name":"throughput_vector","type":"array","nullable":false},{"name":"token","type":"string","nullable":false},{"name":"type","type":"string","nullable":false,"enum":["free","payg","fixed"]},{"name":"vercel_email","type":"string","nullable":false}],"stateCandidates":[{"field":"region","values":["eu-west-1","us-central1"]},{"field":"type","values":["fixed","free","payg"]}],"expandable":[]},{"name":"Team","schema":"Team","fields":[{"name":"copy_cc","type":"boolean","nullable":false},{"name":"team_id","type":"string","nullable":false},{"name":"team_name","type":"string","nullable":false}],"stateCandidates":[],"expandable":[]},{"name":"TeamMember","schema":"TeamMember","fields":[{"name":"copy_cc","type":"boolean","nullable":false},{"name":"member_email","type":"string","nullable":false},{"name":"member_role","type":"string","nullable":false,"enum":["owner","admin","dev","finance"]},{"name":"team_id","type":"string","nullable":false},{"name":"team_name","type":"string","nullable":false}],"stateCandidates":[{"field":"member_role","values":["admin","dev","finance","owner"]}],"expandable":[]},{"name":"VectorIndex","schema":"VectorIndex","fields":[{"name":"creation_time","type":"number","nullable":false},{"name":"customer_id","type":"string","nullable":false},{"name":"dimension_count","type":"number","nullable":false},{"name":"embedding_model","type":"string","nullable":false,"enum":["BGE_SMALL_EN_V1_5","BGE_BASE_EN_V1_5","BGE_LARGE_EN_V1_5","BGE_M3"]},{"name":"endpoint","type":"string","nullable":false},{"name":"id","type":"string","nullable":false},{"name":"index_type","type":"string","nullable":false,"enum":["DENSE","SPARSE","HYBRID"]},{"name":"max_daily_queries","type":"number","nullable":false},{"name":"max_daily_updates","type":"number","nullable":false},{"name":"max_monthly_bandwidth","type":"number","nullable":false},{"name":"max_query_per_second","type":"number","nullable":false},{"name":"max_reads_per_request","type":"number","nullable":false},{"name":"max_total_metadata_size","type":"number","nullable":false},{"name":"max_vector_count","type":"number","nullable":false},{"name":"max_writes_per_request","type":"number","nullable":false},{"name":"max_writes_per_second","type":"number","nullable":false},{"name":"name","type":"string","nullable":false},{"name":"read_only_token","type":"string","nullable":false},{"name":"region","type":"string","nullable":false,"enum":["eu-west-1","us-east-1","us-central1"]},{"name":"reserved_price","type":"number","nullable":false},{"name":"similarity_function","type":"string","nullable":false,"enum":["COSINE","EUCLIDEAN","DOT_PRODUCT"]},{"name":"sparse_embedding_model","type":"string","nullable":false,"enum":["BM25","BGE_M3"]},{"name":"throughput_vector","type":"array","nullable":false},{"name":"token","type":"string","nullable":false},{"name":"type","type":"string","nullable":false,"enum":["free","payg","fixed"]}],"stateCandidates":[{"field":"embedding_model","values":["BGE_BASE_EN_V1_5","BGE_LARGE_EN_V1_5","BGE_M3","BGE_SMALL_EN_V1_5"]},{"field":"index_type","values":["DENSE","HYBRID","SPARSE"]},{"field":"region","values":["eu-west-1","us-central1","us-east-1"]},{"field":"similarity_function","values":["COSINE","DOT_PRODUCT","EUCLIDEAN"]},{"field":"sparse_embedding_model","values":["BGE_M3","BM25"]},{"field":"type","values":["fixed","free","payg"]}],"expandable":[]}],"operations":[{"id":"listAuditLogs","method":"get","path":"/auditlogs","pathParams":[],"query":[],"basePath":"","body":[],"bodyEncoding":"none","successStatus":200,"answers":{"resource":"AuditLog","list":true},"class":"list","resource":"AuditLog"},{"id":"disableQStashProdPack","method":"post","path":"/qstash/disable-prodpack/{id}","pathParams":["id"],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"class":"non-resource"},{"id":"enableQStashProdPack","method":"post","path":"/qstash/enable-prodpack/{id}","pathParams":["id"],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"class":"non-resource"},{"id":"getQStashIPv4","method":"get","path":"/qstash/ipv4","pathParams":[],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"class":"computed"},{"id":"moveQStashToTeam","method":"post","path":"/qstash/move-to-team","pathParams":[],"query":[],"body":[{"name":"qstash_id","type":"string","required":true},{"name":"target_team_id","type":"string","required":true}],"bodyEncoding":"json","successStatus":200,"class":"non-resource"},{"id":"resetQStashToken","method":"post","path":"/qstash/rotate-token/{id}","pathParams":["id"],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"answers":{"resource":"QStashUser","list":false},"class":"action","resource":"QStashUser"},{"id":"setQStashPlan","method":"post","path":"/qstash/set-plan/{id}","pathParams":["id"],"query":[],"body":[{"name":"plan_name","type":"string","required":true}],"bodyEncoding":"json","successStatus":200,"class":"non-resource"},{"id":"getQStashStats","method":"get","path":"/qstash/stats/{id}","pathParams":["id"],"query":[{"name":"period","type":"string","required":false}],"body":[],"bodyEncoding":"none","successStatus":200,"class":"computed"},{"id":"updateQStashBudget","method":"patch","path":"/qstash/update-budget/{id}","pathParams":["id"],"query":[],"body":[{"name":"budget","type":"integer","required":true}],"bodyEncoding":"json","successStatus":200,"class":"non-resource"},{"id":"getQStashUser","method":"get","path":"/qstash/user/{id}","pathParams":["id"],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"answers":{"resource":"QStashUser","list":false},"class":"retrieve","resource":"QStashUser"},{"id":"listQStashUsers","method":"get","path":"/qstash/users","pathParams":[],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"answers":{"resource":"QStashUser","list":true},"class":"list","resource":"QStashUser"},{"id":"changePlan","method":"post","path":"/redis/{id}/change-plan","pathParams":["id"],"query":[],"body":[{"name":"auto_upgrade","type":"boolean","required":false},{"name":"database_id","type":"string","required":false},{"name":"plan_name","type":"string","required":true},{"name":"prod_pack_enabled","type":"boolean","required":false}],"bodyEncoding":"json","successStatus":200,"class":"non-resource"},{"id":"createBackup","method":"post","path":"/redis/create-backup/{id}","pathParams":["id"],"query":[],"body":[{"name":"name","type":"string","required":true}],"bodyEncoding":"json","successStatus":200,"class":"non-resource"},{"id":"createDatabase","method":"post","path":"/redis/database","pathParams":[],"query":[],"body":[{"name":"budget","type":"integer","required":false},{"name":"database_name","type":"string","required":true},{"name":"eviction","type":"boolean","required":false},{"name":"plan","type":"string","required":false},{"name":"platform","type":"string","required":false},{"name":"primary_region","type":"string","required":true},{"name":"read_regions","type":"array","required":false},{"name":"region","type":"string","required":false},{"name":"tls","type":"boolean","required":false}],"bodyEncoding":"json","successStatus":200,"answers":{"resource":"Database","list":false},"class":"create","resource":"Database"},{"id":"getDatabase","method":"get","path":"/redis/database/{id}","pathParams":["id"],"query":[{"name":"credentials","type":"string","required":false}],"body":[],"bodyEncoding":"none","successStatus":200,"answers":{"resource":"Database","list":false},"class":"retrieve","resource":"Database"},{"id":"deleteDatabase","method":"delete","path":"/redis/database/{id}","pathParams":["id"],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"class":"delete","resource":"Database"},{"id":"listDatabases","method":"get","path":"/redis/databases","pathParams":[],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"answers":{"resource":"Database","list":true},"class":"list","resource":"Database"},{"id":"deleteBackup","method":"delete","path":"/redis/delete-backup/{id}/{backup_id}","pathParams":["id","backup_id"],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"class":"delete"},{"id":"disableAutoUpgrade","method":"post","path":"/redis/disable-autoupgrade/{id}","pathParams":["id"],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"class":"non-resource"},{"id":"disableDailyBackup","method":"patch","path":"/redis/disable-dailybackup/{id}","pathParams":["id"],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"class":"non-resource"},{"id":"disableEviction","method":"post","path":"/redis/disable-eviction/{id}","pathParams":["id"],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"class":"non-resource"},{"id":"enableAutoUpgrade","method":"post","path":"/redis/enable-autoupgrade/{id}","pathParams":["id"],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"class":"non-resource"},{"id":"enableDailyBackup","method":"patch","path":"/redis/enable-dailybackup/{id}","pathParams":["id"],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"class":"non-resource"},{"id":"enableEviction","method":"post","path":"/redis/enable-eviction/{id}","pathParams":["id"],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"class":"non-resource"},{"id":"enableTls","method":"post","path":"/redis/enable-tls/{id}","pathParams":["id"],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"answers":{"resource":"Database","list":false},"class":"action","resource":"Database"},{"id":"listBackup","method":"get","path":"/redis/list-backup/{id}","pathParams":["id"],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"answers":{"resource":"Backup","list":true},"class":"list","resource":"Backup"},{"id":"moveToTeam","method":"post","path":"/redis/move-to-team","pathParams":[],"query":[],"body":[{"name":"database_id","type":"string","required":true},{"name":"team_id","type":"string","required":true}],"bodyEncoding":"json","successStatus":200,"class":"non-resource"},{"id":"renameDatabase","method":"post","path":"/redis/rename/{id}","pathParams":["id"],"query":[],"body":[{"name":"name","type":"string","required":true}],"bodyEncoding":"json","successStatus":200,"answers":{"resource":"Database","list":false},"class":"action","resource":"Database"},{"id":"resetPassword","method":"post","path":"/redis/reset-password/{id}","pathParams":["id"],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"answers":{"resource":"Database","list":false},"class":"action","resource":"Database"},{"id":"restoreBackup","method":"post","path":"/redis/restore-backup/{id}","pathParams":["id"],"query":[],"body":[{"name":"backup_id","type":"string","required":true}],"bodyEncoding":"json","successStatus":200,"class":"non-resource"},{"id":"getDatabaseStats","method":"get","path":"/redis/stats/{id}","pathParams":["id"],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"class":"computed"},{"id":"updateBudget","method":"patch","path":"/redis/update-budget/{id}","pathParams":["id"],"query":[],"body":[{"name":"budget","type":"integer","required":true}],"bodyEncoding":"json","successStatus":200,"class":"non-resource"},{"id":"updateRegions","method":"post","path":"/redis/update-regions/{id}","pathParams":["id"],"query":[],"body":[{"name":"read_regions","type":"array","required":true}],"bodyEncoding":"json","successStatus":200,"class":"non-resource"},{"id":"listSearchIndexes","method":"get","path":"/search","pathParams":[],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"answers":{"resource":"SearchIndex","list":true},"class":"computed","resource":"SearchIndex"},{"id":"createSearchIndex","method":"post","path":"/search","pathParams":[],"query":[],"body":[{"name":"name","type":"string","required":true},{"name":"region","type":"string","required":true},{"name":"type","type":"string","required":true}],"bodyEncoding":"json","successStatus":200,"answers":{"resource":"SearchIndex","list":false},"class":"create","resource":"SearchIndex"},{"id":"getSearchIndex","method":"get","path":"/search/{id}","pathParams":["id"],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"answers":{"resource":"SearchIndex","list":false},"class":"retrieve","resource":"SearchIndex"},{"id":"deleteSearchIndex","method":"delete","path":"/search/{id}","pathParams":["id"],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"class":"delete","resource":"SearchIndex"},{"id":"renameSearchIndex","method":"post","path":"/search/{id}/rename","pathParams":["id"],"query":[],"body":[{"name":"name","type":"string","required":true}],"bodyEncoding":"json","successStatus":200,"class":"action","resource":"SearchIndex"},{"id":"resetSearchPassword","method":"post","path":"/search/{id}/reset-password","pathParams":["id"],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"class":"action","resource":"SearchIndex"},{"id":"getSearchIndexStats","method":"get","path":"/search/{id}/stats","pathParams":["id"],"query":[{"name":"period","type":"string","required":false}],"body":[],"bodyEncoding":"none","successStatus":200,"class":"computed","resource":"SearchIndex"},{"id":"transferSearchIndex","method":"post","path":"/search/{id}/transfer","pathParams":["id"],"query":[],"body":[{"name":"target_account","type":"string","required":true}],"bodyEncoding":"json","successStatus":200,"class":"action","resource":"SearchIndex"},{"id":"getGlobalSearchStats","method":"get","path":"/search/stats","pathParams":[],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"class":"computed"},{"id":"createTeam","method":"post","path":"/team","pathParams":[],"query":[],"body":[{"name":"copy_cc","type":"boolean","required":true},{"name":"team_name","type":"string","required":true}],"bodyEncoding":"json","successStatus":200,"answers":{"resource":"Team","list":false},"class":"action","resource":"Team"},{"id":"deleteTeam","method":"delete","path":"/team/{id}","pathParams":["id"],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"class":"delete"},{"id":"listTeams","method":"get","path":"/teams","pathParams":[],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"answers":{"resource":"Team","list":true},"class":"list","resource":"Team"},{"id":"getTeamMembers","method":"get","path":"/teams/{team_id}","pathParams":["team_id"],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"answers":{"resource":"TeamMember","list":true},"class":"list","resource":"TeamMember"},{"id":"addTeamMember","method":"post","path":"/teams/member","pathParams":[],"query":[],"body":[{"name":"member_email","type":"string","required":true},{"name":"member_role","type":"string","required":true},{"name":"team_id","type":"string","required":true}],"bodyEncoding":"json","successStatus":200,"answers":{"resource":"TeamMember","list":false},"class":"action","resource":"TeamMember"},{"id":"deleteTeamMember","method":"delete","path":"/teams/member","pathParams":[],"query":[],"body":[{"name":"member_email","type":"string","required":true},{"name":"team_id","type":"string","required":true}],"bodyEncoding":"json","successStatus":200,"class":"action"},{"id":"listIndices","method":"get","path":"/vector/index","pathParams":[],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"answers":{"resource":"VectorIndex","list":true},"class":"list","resource":"VectorIndex"},{"id":"createIndex","method":"post","path":"/vector/index","pathParams":[],"query":[],"body":[{"name":"dimension_count","type":"number","required":true},{"name":"embedding_model","type":"string","required":false},{"name":"index_type","type":"string","required":false},{"name":"name","type":"string","required":true},{"name":"region","type":"string","required":true},{"name":"similarity_function","type":"string","required":true},{"name":"sparse_embedding_model","type":"string","required":false},{"name":"type","type":"string","required":false}],"bodyEncoding":"json","successStatus":200,"answers":{"resource":"VectorIndex","list":false},"class":"create","resource":"VectorIndex"},{"id":"getIndex","method":"get","path":"/vector/index/{id}","pathParams":["id"],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"answers":{"resource":"VectorIndex","list":false},"class":"retrieve","resource":"VectorIndex"},{"id":"deleteIndex","method":"delete","path":"/vector/index/{id}","pathParams":["id"],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"class":"delete","resource":"VectorIndex"},{"id":"renameIndex","method":"post","path":"/vector/index/{id}/rename","pathParams":["id"],"query":[],"body":[{"name":"name","type":"string","required":true}],"bodyEncoding":"json","successStatus":200,"class":"action","resource":"VectorIndex"},{"id":"resetIndexPasswords","method":"post","path":"/vector/index/{id}/reset-password","pathParams":["id"],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"class":"action","resource":"VectorIndex"},{"id":"setIndexPlan","method":"post","path":"/vector/index/{id}/setplan","pathParams":["id"],"query":[],"body":[{"name":"target_plan","type":"string","required":true}],"bodyEncoding":"json","successStatus":200,"class":"action","resource":"VectorIndex"},{"id":"getVectorIndexStats","method":"get","path":"/vector/index/{id}/stats","pathParams":["id"],"query":[{"name":"period","type":"string","required":false}],"body":[],"bodyEncoding":"none","successStatus":200,"class":"computed","resource":"VectorIndex"},{"id":"transferIndex","method":"post","path":"/vector/index/{id}/transfer","pathParams":["id"],"query":[],"body":[{"name":"target_account","type":"string","required":true}],"bodyEncoding":"json","successStatus":200,"class":"action","resource":"VectorIndex"},{"id":"getGlobalVectorStats","method":"get","path":"/vector/index/stats","pathParams":[],"query":[],"body":[],"bodyEncoding":"none","successStatus":200,"class":"computed"}]}
@@ -0,0 +1 @@
1
+ {"generatedBy":"scripts/derive-pack.ts","resources":{"Database":{"retrieve":"/v2/redis/database/{id}","create":{"path":"/v2/redis/database","encoding":"json","body":[{"name":"budget","type":"integer","required":false},{"name":"database_name","type":"string","required":true},{"name":"eviction","type":"boolean","required":false},{"name":"plan","type":"string","required":false},{"name":"platform","type":"string","required":false},{"name":"primary_region","type":"string","required":true},{"name":"read_regions","type":"array","required":false},{"name":"region","type":"string","required":false},{"name":"tls","type":"boolean","required":false}]},"actions":[],"states":{},"embeds":{}},"Backup":{"list":"/v2/redis/list-backup/{id}","actions":[],"states":{"state":["completed","pending"]},"embeds":{}},"Team":{"actions":[],"states":{},"embeds":{}},"TeamMember":{"actions":[],"states":{},"embeds":{}}}}
@@ -0,0 +1,19 @@
1
+ // The Developer API lane of the upstash pack (api.upstash.com and console.upstash.com), as a plain fetch: the whole pack's
2
+ // fetch (the Redis REST wire, the Developer API and the console's pages, and QStash), whose owners are the operations this
3
+ // lane serves. Its life walks through all of them: a customer makes its API keys on the console, makes and changes its
4
+ // databases, backups and team through the Developer API, and its game reads and writes the databases through the REST API
5
+ // and publishes through QStash (journeys/customer-life.outline.md).
6
+ import type { DerivedFetch } from '@volter/world-core';
7
+ import { createUpstashRedisTwinFetch } from '../../src/upstash-server.ts';
8
+ import { createUpstashApiLaneFetch } from './fetch.ts';
9
+
10
+ export { manifest } from './manifest.ts';
11
+ export { createUpstashApiLaneFetch, API_HOST, CONSOLE_HOST, type UpstashRedisApiFetch } from './fetch.ts';
12
+ // how a journey reads a REST answer sent with Upstash-Encoding: base64: the pack's own view (../../src/index.ts)
13
+ export { answerViews } from '../../src/index.ts';
14
+
15
+ export function createUpstashApiTwinFetch(options: { root?: string; readOnly?: boolean } = {}): DerivedFetch {
16
+ const lane = createUpstashApiLaneFetch(options);
17
+ const pack = createUpstashRedisTwinFetch({ ...(options.root !== undefined ? { root: options.root } : {}), ...(options.readOnly !== undefined ? { readOnly: options.readOnly } : {}) });
18
+ return Object.assign((request: Request) => pack(request), { owners: () => lane.owners() });
19
+ }
@@ -0,0 +1,3 @@
1
+ import type { SemanticsContext } from '@volter/world-core';
2
+ /** The gate's answer for a request, or undefined when it may pass. */
3
+ export declare function keyGate(ctx: SemanticsContext, request: Request): Response | undefined;
@@ -0,0 +1,30 @@
1
+ // The Developer API lane's key gate, in front of its dispatch. "Upstash API uses HTTP Basic authentication. You should
2
+ // pass EMAIL and API_KEY as basic authentication username and password respectively"
3
+ // (https://upstash.com/docs/devops/developer-api/authentication); a key is made on the console's Developer API tab with a
4
+ // name, a permission (Read/Write or Read Only) and an expiration (https://upstash.com/docs/devops/developer-api/introduction).
5
+ //
6
+ // The gate admits exactly an email and a key this lane issued, live and unexpired: a key made on the console's Developer
7
+ // API tab (screens/developer-api.tsx) or, for an account a World needs of its own, one the World issues through
8
+ // `POST /_twin/users/:email/api-key` (screens/session.tsx). Where the reference stops and the twin decides: anything
9
+ // else (no well-formed Basic credential, an email or key the lane never issued, a deleted or expired key) answers 401
10
+ // {code: unauthorized}; a Read Only key asking for anything but a read answers 403 {code: forbidden}.
11
+ import { createHash } from 'node:crypto';
12
+ const refuse = (status, code, message) => Response.json({ code, message }, { status });
13
+ /** The gate's answer for a request, or undefined when it may pass. */
14
+ export function keyGate(ctx, request) {
15
+ const m = /^basic\s+(\S+)$/i.exec(request.headers.get('authorization') ?? '');
16
+ const decoded = m ? Buffer.from(m[1], 'base64').toString('utf8') : '';
17
+ const colon = decoded.indexOf(':');
18
+ if (colon <= 0)
19
+ return refuse(401, 'unauthorized', 'Unauthorized: send EMAIL:API_KEY as HTTP Basic authentication');
20
+ const email = decoded.slice(0, colon).toLowerCase();
21
+ const key = decoded.slice(colon + 1);
22
+ const sha = createHash('sha256').update(key).digest('hex');
23
+ const held = ctx.rowsRaw('_api_key').find((k) => k.email === email && k.key_sha256 === sha && k.deleted !== true
24
+ && (k.expires_at === null || String(k.expires_at) > ctx.occurredAt));
25
+ if (!held)
26
+ return refuse(401, 'unauthorized', 'Unauthorized: the API key is not valid');
27
+ if (held.permission === 'read_only' && request.method !== 'GET')
28
+ return refuse(403, 'forbidden', 'Forbidden: this API key is Read Only');
29
+ return undefined;
30
+ }
@@ -0,0 +1,30 @@
1
+ // The Developer API lane's key gate, in front of its dispatch. "Upstash API uses HTTP Basic authentication. You should
2
+ // pass EMAIL and API_KEY as basic authentication username and password respectively"
3
+ // (https://upstash.com/docs/devops/developer-api/authentication); a key is made on the console's Developer API tab with a
4
+ // name, a permission (Read/Write or Read Only) and an expiration (https://upstash.com/docs/devops/developer-api/introduction).
5
+ //
6
+ // The gate admits exactly an email and a key this lane issued, live and unexpired: a key made on the console's Developer
7
+ // API tab (screens/developer-api.tsx) or, for an account a World needs of its own, one the World issues through
8
+ // `POST /_twin/users/:email/api-key` (screens/session.tsx). Where the reference stops and the twin decides: anything
9
+ // else (no well-formed Basic credential, an email or key the lane never issued, a deleted or expired key) answers 401
10
+ // {code: unauthorized}; a Read Only key asking for anything but a read answers 403 {code: forbidden}.
11
+ import { createHash } from 'node:crypto';
12
+ import type { SemanticsContext } from '@volter/world-core';
13
+
14
+ const refuse = (status: number, code: string, message: string): Response => Response.json({ code, message }, { status });
15
+
16
+ /** The gate's answer for a request, or undefined when it may pass. */
17
+ export function keyGate(ctx: SemanticsContext, request: Request): Response | undefined {
18
+ const m = /^basic\s+(\S+)$/i.exec(request.headers.get('authorization') ?? '');
19
+ const decoded = m ? Buffer.from(m[1]!, 'base64').toString('utf8') : '';
20
+ const colon = decoded.indexOf(':');
21
+ if (colon <= 0) return refuse(401, 'unauthorized', 'Unauthorized: send EMAIL:API_KEY as HTTP Basic authentication');
22
+ const email = decoded.slice(0, colon).toLowerCase();
23
+ const key = decoded.slice(colon + 1);
24
+ const sha = createHash('sha256').update(key).digest('hex');
25
+ const held = ctx.rowsRaw('_api_key').find((k) => k.email === email && k.key_sha256 === sha && k.deleted !== true
26
+ && (k.expires_at === null || String(k.expires_at) > ctx.occurredAt));
27
+ if (!held) return refuse(401, 'unauthorized', 'Unauthorized: the API key is not valid');
28
+ if (held.permission === 'read_only' && request.method !== 'GET') return refuse(403, 'forbidden', 'Forbidden: this API key is Read Only');
29
+ return undefined;
30
+ }
@@ -0,0 +1,2 @@
1
+ import type { DerivedManifest } from '@volter/world-core';
2
+ export declare const manifest: DerivedManifest;
@@ -0,0 +1,81 @@
1
+ /** A backup's `state` (the Backup schema: pending, completed, failed). A backup is `pending` when it is made and
2
+ * `completed` two minutes later, on the World clock (semantics/time.ts): the duration is the twin's decision; Upstash's
3
+ * backup page says only that "Backup process will start and will appear in the backups table"
4
+ * (https://upstash.com/docs/redis/features/backup). Not made by the twin: failed. */
5
+ const backupState = {
6
+ initial: 'pending',
7
+ transitions: [
8
+ { actor: 'time', from: ['pending'], to: 'completed', source: 'spec:/components/schemas/Backup/properties/state "State of the backup"' },
9
+ ],
10
+ };
11
+ export const manifest = {
12
+ vendor: 'upstash',
13
+ service: 'upstash',
14
+ body: { json: 'always' },
15
+ // database ids are UUIDs (the Database schema's example, 96ad0856-03b1-4ee7-9666-e81abd0349e1); the handler mints one
16
+ // per database from the World's instant and name (semantics/index.ts), so a World mints the same ids every run
17
+ ids: { template: '{uuid}' },
18
+ // `creation_time`: "Creation time of the database as Unix time"
19
+ time: 'unix',
20
+ error: { code: '{code}', message: '{message}' },
21
+ readOnly: { status: 403, code: 'forbidden', message: 'The twin is read-only' },
22
+ malformedBody: { status: 400, code: 'bad_request', message: 'The request body is not valid JSON' },
23
+ notFound: { status: 404, code: 'not_found', message: 'database not found' },
24
+ // listDatabases answers a bare array, which the envelope cannot state; it is the gap. Required by the type.
25
+ list: { style: 'envelope', envelope: { data: '{data}' }, limit: { param: 'limit', default: 100, max: 100 } },
26
+ // deleteDatabase answers the string "OK" (its 200 schema: type string, example OK)
27
+ deleted: 'OK',
28
+ // the operations of a declared resource the lane does not serve: the gap
29
+ unmodeled: [
30
+ 'listDatabases', 'renameDatabase', 'enableTls', 'enableEviction', 'disableEviction', 'enableAutoUpgrade',
31
+ 'disableAutoUpgrade', 'changePlan', 'updateBudget', 'getDatabaseStats', 'listTeams', 'getTeamMembers',
32
+ ],
33
+ screens: [{
34
+ id: 'sign-in', kind: 'flow', host: 'console.upstash.com', path: '/login',
35
+ demand: 'every console page a person reaches: each asks who is signed in', status: 'done',
36
+ controls: ['Email', 'Password', 'Sign in'], source: 'https://upstash.com/docs/devops/developer-api/introduction',
37
+ }, {
38
+ id: 'developer-api', kind: 'flow', host: 'console.upstash.com', path: '/account/api',
39
+ demand: 'the Developer API needs an API key, and a key is made only here',
40
+ status: 'done', controls: ['Create API Key', 'Name', 'Permissions', 'Expiration', 'Submit', 'Delete'],
41
+ source: 'https://upstash.com/docs/devops/developer-api/introduction',
42
+ }, {
43
+ id: 'qstash', kind: 'flow', host: 'console.upstash.com', path: '/qstash',
44
+ demand: "QStash's token and signing keys are shown, and the token reset, only here (the qstash lane's life)",
45
+ status: 'done', controls: ['Select', 'Reset token', 'Roll keys'], source: 'https://upstash.com/docs/qstash/howto/reset-token',
46
+ }, {
47
+ id: 'teams', kind: 'workspace', host: 'console.upstash.com', path: '/account/teams',
48
+ demand: 'a pending member is shown, and a pending invitation revoked, only here', status: 'done',
49
+ controls: ['Pending', 'Revoke Invite'], source: 'https://upstash.com/docs/common/account/teams',
50
+ }, {
51
+ id: 'team-invite', kind: 'flow', host: 'console.upstash.com', path: '/invite/{token}',
52
+ demand: 'a person joins a team by the acceptance link in the invitation email; no API operation accepts one',
53
+ status: 'done', controls: ['Accept invitation'], source: 'https://upstash.com/docs/common/account/teams',
54
+ }],
55
+ resources: {
56
+ Database: {
57
+ storedAs: '_database',
58
+ idPrefix: '',
59
+ // Not state the twin moves. `state` (active, suspended, passive): a database is active from its create (the
60
+ // getDatabase example's "state": "active"); suspension is Upstash's act on an account, which the twin does not
61
+ // model. `type` is the plan and `primary_region`, `db_type`, `db_resource_size` are set when it is made;
62
+ // `db_acl_enabled`, `db_acl_default_user_status` and `prometheus_enabled` are settings no served operation changes.
63
+ // Fields no page states a value for, answered with the Database schema's example values as the reference page
64
+ // renders them (https://upstash.com/docs/devops/developer-api/redis/get_database), the twin's choice: `replicas`
65
+ // (5, whatever the regions; no page says how Upstash counts them), `securityAddons` (all false), and
66
+ // `db_conn_idle_timeout`, `db_lua_timeout`, `db_lua_credits_per_min`, `db_store_max_idle`, `db_max_loads_per_sec`.
67
+ // Also the twin's, no page stating them: `db_memory_threshold` (256 MB on every plan), `db_max_clients` (10000), and
68
+ // how "Unlimited" monthly commands are written, `db_request_limit` 8024278031528737000 on pay-as-you-go and fixed
69
+ // plans (the schema's example value, a paid database's). Only the data size and the limits the pricing table gives
70
+ // (monthly commands, commands per second, request and record size) follow https://upstash.com/pricing/redis, per
71
+ // plan (semantics/index.ts, PLAN_LIMITS).
72
+ notState: ['state', 'type', 'primary_region', 'db_type', 'db_resource_size', 'db_acl_enabled', 'db_acl_default_user_status', 'prometheus_enabled'],
73
+ },
74
+ // `daily_backup` is whether the daily schedule made it, and `hourly_backup` is never set: both fixed when it is made
75
+ Backup: { storedAs: '_backup', idPrefix: '', notState: ['daily_backup', 'hourly_backup'], state: { state: backupState } },
76
+ Team: { storedAs: '_team', idPrefix: '' },
77
+ // a member's role is fixed: "If you want to change a user's role, you will need to delete and re-add them"
78
+ // (https://upstash.com/docs/common/account/teams); whether they are pending is the lane's bookkeeping (`_pending`)
79
+ TeamMember: { storedAs: '_team_member', idPrefix: '', notState: ['member_role'] },
80
+ },
81
+ };
@@ -0,0 +1,103 @@
1
+ // Upstash's Developer API manifest (the upstash pack's `api` lane; docs/contributing/architecture.md, "Other wires:
2
+ // lanes"): the vendor facts its published OpenAPI document (../spec/openapi.yaml) does not carry, and the resource whose
3
+ // state the lane moves. The surface is generated (./generated/surface.gen.json, by `bun scripts/derive-pack.ts
4
+ // upstash/api`), at `https://api.upstash.com/v2`.
5
+ //
6
+ // The lane serves the slice the customers' lives reach: a Redis database's create, read and delete, its read regions and
7
+ // password, its backups (semantics/backups.ts, with the daily backup on the World clock, semantics/time.ts), and teams
8
+ // (semantics/teams.ts). Every other operation of the document (renaming, plans, stats, audit logs, listing teams and
9
+ // members, Vector, Search, QStash) is the gap. The REST data plane (the pack
10
+ // root) reads the tokens this lane issues, so both write the one kernel service `upstash`; a database is a
11
+ // control-plane record, stored under the `_database` type, beside the root's `key` and `script` subjects.
12
+ //
13
+ // NO `auth`: the Developer API reads HTTP Basic with the account's email and an API key made in the console
14
+ // (https://upstash.com/docs/devops/developer-api/authentication), and a key's owner and its permission (Read/Write or
15
+ // Read Only) are the lane's records, which the kernel's gate cannot read. The lane's key gate (key-gate.ts) sits in front
16
+ // of the dispatch instead.
17
+ //
18
+ // THE ERROR BODY is the twin's decision: the document declares no error response for any operation. The twin answers in
19
+ // the document's own `Error` schema ({code, message}), which no operation references.
20
+ import type { DerivedManifest, StateField } from '@volter/world-core';
21
+
22
+ /** A backup's `state` (the Backup schema: pending, completed, failed). A backup is `pending` when it is made and
23
+ * `completed` two minutes later, on the World clock (semantics/time.ts): the duration is the twin's decision; Upstash's
24
+ * backup page says only that "Backup process will start and will appear in the backups table"
25
+ * (https://upstash.com/docs/redis/features/backup). Not made by the twin: failed. */
26
+ const backupState: StateField = {
27
+ initial: 'pending',
28
+ transitions: [
29
+ { actor: 'time', from: ['pending'], to: 'completed', source: 'spec:/components/schemas/Backup/properties/state "State of the backup"' },
30
+ ],
31
+ };
32
+
33
+ export const manifest: DerivedManifest = {
34
+ vendor: 'upstash',
35
+ service: 'upstash',
36
+ body: { json: 'always' },
37
+ // database ids are UUIDs (the Database schema's example, 96ad0856-03b1-4ee7-9666-e81abd0349e1); the handler mints one
38
+ // per database from the World's instant and name (semantics/index.ts), so a World mints the same ids every run
39
+ ids: { template: '{uuid}' },
40
+ // `creation_time`: "Creation time of the database as Unix time"
41
+ time: 'unix',
42
+ error: { code: '{code}', message: '{message}' },
43
+ readOnly: { status: 403, code: 'forbidden', message: 'The twin is read-only' },
44
+ malformedBody: { status: 400, code: 'bad_request', message: 'The request body is not valid JSON' },
45
+ notFound: { status: 404, code: 'not_found', message: 'database not found' },
46
+ // listDatabases answers a bare array, which the envelope cannot state; it is the gap. Required by the type.
47
+ list: { style: 'envelope', envelope: { data: '{data}' }, limit: { param: 'limit', default: 100, max: 100 } },
48
+ // deleteDatabase answers the string "OK" (its 200 schema: type string, example OK)
49
+ deleted: 'OK',
50
+ // the operations of a declared resource the lane does not serve: the gap
51
+ unmodeled: [
52
+ 'listDatabases', 'renameDatabase', 'enableTls', 'enableEviction', 'disableEviction', 'enableAutoUpgrade',
53
+ 'disableAutoUpgrade', 'changePlan', 'updateBudget', 'getDatabaseStats', 'listTeams', 'getTeamMembers',
54
+ ],
55
+ screens: [{
56
+ id: 'sign-in', kind: 'flow', host: 'console.upstash.com', path: '/login',
57
+ demand: 'every console page a person reaches: each asks who is signed in', status: 'done',
58
+ controls: ['Email', 'Password', 'Sign in'], source: 'https://upstash.com/docs/devops/developer-api/introduction',
59
+ }, {
60
+ id: 'developer-api', kind: 'flow', host: 'console.upstash.com', path: '/account/api',
61
+ demand: 'the Developer API needs an API key, and a key is made only here',
62
+ status: 'done', controls: ['Create API Key', 'Name', 'Permissions', 'Expiration', 'Submit', 'Delete'],
63
+ source: 'https://upstash.com/docs/devops/developer-api/introduction',
64
+ }, {
65
+ id: 'qstash', kind: 'flow', host: 'console.upstash.com', path: '/qstash',
66
+ demand: "QStash's token and signing keys are shown, and the token reset, only here (the qstash lane's life)",
67
+ status: 'done', controls: ['Select', 'Reset token', 'Roll keys'], source: 'https://upstash.com/docs/qstash/howto/reset-token',
68
+ }, {
69
+ id: 'teams', kind: 'workspace', host: 'console.upstash.com', path: '/account/teams',
70
+ demand: 'a pending member is shown, and a pending invitation revoked, only here', status: 'done',
71
+ controls: ['Pending', 'Revoke Invite'], source: 'https://upstash.com/docs/common/account/teams',
72
+ }, {
73
+ id: 'team-invite', kind: 'flow', host: 'console.upstash.com', path: '/invite/{token}',
74
+ demand: 'a person joins a team by the acceptance link in the invitation email; no API operation accepts one',
75
+ status: 'done', controls: ['Accept invitation'], source: 'https://upstash.com/docs/common/account/teams',
76
+ }],
77
+ resources: {
78
+ Database: {
79
+ storedAs: '_database',
80
+ idPrefix: '',
81
+ // Not state the twin moves. `state` (active, suspended, passive): a database is active from its create (the
82
+ // getDatabase example's "state": "active"); suspension is Upstash's act on an account, which the twin does not
83
+ // model. `type` is the plan and `primary_region`, `db_type`, `db_resource_size` are set when it is made;
84
+ // `db_acl_enabled`, `db_acl_default_user_status` and `prometheus_enabled` are settings no served operation changes.
85
+ // Fields no page states a value for, answered with the Database schema's example values as the reference page
86
+ // renders them (https://upstash.com/docs/devops/developer-api/redis/get_database), the twin's choice: `replicas`
87
+ // (5, whatever the regions; no page says how Upstash counts them), `securityAddons` (all false), and
88
+ // `db_conn_idle_timeout`, `db_lua_timeout`, `db_lua_credits_per_min`, `db_store_max_idle`, `db_max_loads_per_sec`.
89
+ // Also the twin's, no page stating them: `db_memory_threshold` (256 MB on every plan), `db_max_clients` (10000), and
90
+ // how "Unlimited" monthly commands are written, `db_request_limit` 8024278031528737000 on pay-as-you-go and fixed
91
+ // plans (the schema's example value, a paid database's). Only the data size and the limits the pricing table gives
92
+ // (monthly commands, commands per second, request and record size) follow https://upstash.com/pricing/redis, per
93
+ // plan (semantics/index.ts, PLAN_LIMITS).
94
+ notState: ['state', 'type', 'primary_region', 'db_type', 'db_resource_size', 'db_acl_enabled', 'db_acl_default_user_status', 'prometheus_enabled'],
95
+ },
96
+ // `daily_backup` is whether the daily schedule made it, and `hourly_backup` is never set: both fixed when it is made
97
+ Backup: { storedAs: '_backup', idPrefix: '', notState: ['daily_backup', 'hourly_backup'], state: { state: backupState } },
98
+ Team: { storedAs: '_team', idPrefix: '' },
99
+ // a member's role is fixed: "If you want to change a user's role, you will need to delete and re-add them"
100
+ // (https://upstash.com/docs/common/account/teams); whether they are pending is the lane's bookkeeping (`_pending`)
101
+ TeamMember: { storedAs: '_team_member', idPrefix: '', notState: ['member_role'] },
102
+ },
103
+ };
@@ -0,0 +1,3 @@
1
+ import type { SemanticsContext } from '@volter/world-core';
2
+ /** The Developer API tab, or undefined for any other request. `contextFor` gives the lane's semantics context. */
3
+ export declare function developerApiPage(request: Request, contextFor: (request: Request) => Promise<SemanticsContext>): Promise<Response | undefined>;
@@ -0,0 +1,101 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // UPSTASH CONSOLE: ACCOUNT > DEVELOPER API — a flow (docs/contributing/architecture.md, "Screens"): the page where a person
3
+ // makes the API key the Developer API reads. No API operation makes a key, so the first can only come from here.
4
+ // https://upstash.com/docs/devops/developer-api/introduction: "Log in to the console, then open Account and click the
5
+ // Developer API tab. Click the Create API Key button. Enter a name for your key (you can not use the same name for
6
+ // multiple keys), choose the permissions (Read/Write or Read Only) and an expiration, then click Submit. You need to
7
+ // download or copy/save your API key. Upstash does not remember or keep your API for security reasons." and "By default
8
+ // one user can create up to 37 API keys." Built from @volter/world-ui's settings layout (Portal) under Upstash's skin;
9
+ // nothing of Upstash's page is copied. Its controls are the ones the pack's lives need: Create API Key with its Name,
10
+ // Permissions and Expiration, and Submit; and each key's Delete ("You can delete the API keys in the `Account > Developer
11
+ // API` tab").
12
+ //
13
+ // The person on the page is who signed in at /login (screens/session.tsx): a visitor nobody signed in as is sent there
14
+ // first. Where the documentation stops and the twin decides: the tab is at /account/api; the expirations offered are
15
+ // 30 days, 90 days and 1 year (the page says a key has one, and names none); a name the person already used, and a 38th key, answer the page again with a
16
+ // notice (422); the new key is shown once in the page's notice.
17
+ import { flowPage, Portal, PORTAL_CSS } from '@volter/world-ui';
18
+ import { makeApiKey } from "../semantics/index.js";
19
+ import { signedIn, toSignIn } from "./session.js";
20
+ const PATH = '/account/api';
21
+ const EXPIRATIONS = { '30d': 30, '90d': 90, '1y': 365 };
22
+ const MAX_KEYS = 37;
23
+ // Upstash's skin over the settings layout: its stone greys and green primary button
24
+ const SKIN = `
25
+ body { background: #ffffff; color: #1c1917; }
26
+ .portal-side { background: #fafaf9; border-right: 1px solid #e7e5e4; }
27
+ .portal-section h2 { border-color: #e7e5e4; color: #57534e; }
28
+ .portal-notice { background: #ecfdf5; border: 1px solid #a7f3d0; font-family: ui-monospace, monospace; word-break: break-all; }
29
+ .portal-button { background: #ffffff; border: 1px solid #d6d3d1; border-radius: 6px; padding: 6px 12px; }
30
+ .portal-primary { background: #00c98d; border-color: #00c98d; color: #0c0a09; }
31
+ `;
32
+ function page(ctx, email, notice, status = 200) {
33
+ const keys = ctx.rowsRaw('_api_key').filter((k) => k.email === email && k.deleted !== true);
34
+ return flowPage({
35
+ title: 'Developer API · Account · Upstash Console',
36
+ css: [PORTAL_CSS, SKIN],
37
+ status,
38
+ body: (_jsx(Portal, { merchant: `${email} · Account`, back: { href: '/', label: 'Back to console' }, ...(notice ? { notice } : {}), sections: [{
39
+ heading: 'Developer API',
40
+ empty: 'No API keys yet.',
41
+ items: keys.map((k) => ({
42
+ title: String(k.name),
43
+ detail: k.permission === 'read_only' ? 'Read Only' : 'Read/Write',
44
+ note: k.expires_at ? `Expires ${String(k.expires_at).slice(0, 10)}` : 'Never expires',
45
+ actions: [{ label: 'Delete', action: `${PATH}/delete`, tone: 'danger', fields: { name: String(k.name) } }],
46
+ })),
47
+ }], forms: [{
48
+ heading: 'Create API Key',
49
+ action: PATH,
50
+ fields: [
51
+ { id: 'name', label: 'Name' },
52
+ { id: 'permission', label: 'Permissions', options: [{ value: 'read_write', label: 'Read/Write' }, { value: 'read_only', label: 'Read Only' }] },
53
+ { id: 'expiration', label: 'Expiration', options: [{ value: '30d', label: '30 days' }, { value: '90d', label: '90 days' }, { value: '1y', label: '1 year' }] },
54
+ ],
55
+ submit: { label: 'Submit' },
56
+ }] })),
57
+ });
58
+ }
59
+ /** Delete: the key stops working at once (key-gate.ts admits no deleted key); "When an API key is exposed ... or not
60
+ * being used anymore; you should delete it. You can delete the API keys in the `Account > Developer API` tab." */
61
+ async function deleteKey(ctx, email, name) {
62
+ const key = ctx.rowsRaw('_api_key').find((k) => k.email === email && k.name === name && k.deleted !== true);
63
+ if (!key)
64
+ return page(ctx, email, `You have no API key named ${name}.`, 422);
65
+ await ctx.record('_api_key', { deleted: true, deleted_at: ctx.occurredAt }, `apikey:${email}:${name}`);
66
+ return page(ctx, email, `API key ${name} deleted.`);
67
+ }
68
+ async function formOf(request) {
69
+ return Object.fromEntries(new URLSearchParams(await request.text()));
70
+ }
71
+ /** The Developer API tab, or undefined for any other request. `contextFor` gives the lane's semantics context. */
72
+ export async function developerApiPage(request, contextFor) {
73
+ const url = new URL(request.url);
74
+ const path = url.pathname.replace(/\/+$/, '');
75
+ if (path !== PATH && path !== `${PATH}/delete`)
76
+ return undefined;
77
+ const person = signedIn(await contextFor(new Request(request.url)), request);
78
+ if (!person)
79
+ return toSignIn(request);
80
+ if (request.method === 'GET' && path === PATH)
81
+ return page(await contextFor(new Request(request.url)), person.email);
82
+ if (request.method !== 'POST')
83
+ return undefined;
84
+ const form = await formOf(request);
85
+ const ctx = await contextFor(new Request(request.url));
86
+ if (path !== PATH)
87
+ return deleteKey(ctx, person.email, (form.name ?? '').trim());
88
+ const name = (form.name ?? '').trim();
89
+ const held = ctx.rowsRaw('_api_key').filter((k) => k.email === person.email && k.deleted !== true);
90
+ if (!name)
91
+ return page(ctx, person.email, 'An API key needs a name.', 422);
92
+ if (held.some((k) => k.name === name))
93
+ return page(ctx, person.email, `You already have an API key named ${name}.`, 422);
94
+ if (held.length >= MAX_KEYS)
95
+ return page(ctx, person.email, `You can create up to ${MAX_KEYS} API keys.`, 422);
96
+ const permission = form.permission === 'read_only' ? 'read_only' : 'read_write';
97
+ const days = form.expiration !== undefined && form.expiration in EXPIRATIONS ? EXPIRATIONS[form.expiration] : EXPIRATIONS['1y'];
98
+ const expiresAt = new Date(Date.parse(ctx.occurredAt) + days * 86_400_000).toISOString();
99
+ const { key } = await makeApiKey(ctx, person.email, name, permission, expiresAt);
100
+ return page(await contextFor(new Request(request.url)), person.email, `API key ${name} created: ${key} — copy it now: Upstash does not keep it.`);
101
+ }