Openstatus www.openstatus.dev

deprecate fly regions 😭 (#1402)

* wip

* ci: apply automated fixes

* 😂

* chore: fly depreciation

* 🧪

* ✏️

* 😭

* fix: format

* 🔥

* chore: disable deprecated checkbox

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Maximilian Kaske <maximilian@kaske.org>

authored by

Thibault Le Ouay
autofix-ci[bot]
Maximilian Kaske
and committed by
GitHub
e6a0d9ae c145c32a

+421 -26
+1 -1
apps/dashboard/src/components/dialogs/export-code.tsx
··· 19 19 active: true 20 20 public: false 21 21 frequency: "10m" 22 - regions: ["ams", "fra", "gru", "hkg", "iad"] 22 + regions: ["ams", "fra", "gru", "sin", "iad"] 23 23 kind: "http" 24 24 request: 25 25 url: https://api.openstatus.dev
+85 -21
apps/dashboard/src/components/forms/monitor/form-scheduling-regions.tsx
··· 36 36 import { IconCloudProviderTooltip } from "@/components/common/icon-cloud-provider"; 37 37 import { Note, NoteButton } from "@/components/common/note"; 38 38 import { UpgradeDialog } from "@/components/dialogs/upgrade"; 39 + import { 40 + Tooltip, 41 + TooltipContent, 42 + TooltipProvider, 43 + TooltipTrigger, 44 + } from "@/components/ui/tooltip"; 39 45 import { useTRPC } from "@/lib/trpc/client"; 40 46 import { formatRegionCode, groupByContinent } from "@openstatus/utils"; 41 47 import { useQuery } from "@tanstack/react-query"; ··· 265 271 : !allowedRegions.includes( 266 272 region.code, 267 273 ) || isMaxed; 274 + const deprecated = region.deprecated; 268 275 return ( 269 276 <FormItem 270 277 key={region.code} ··· 273 280 <Checkbox 274 281 id={region.code} 275 282 checked={checked || false} 276 - disabled={disabled} 283 + disabled={ 284 + disabled || 285 + (deprecated && !checked) 286 + } 277 287 onCheckedChange={(checked) => { 278 - console.log( 279 - checked, 280 - field.value, 281 - ); 282 288 if (checked) { 283 289 field.onChange([ 284 290 ...field.value, ··· 293 299 } 294 300 }} 295 301 /> 296 - <FormLabel 297 - htmlFor={region.code} 298 - className="w-full truncate font-mono font-normal text-sm" 299 - > 300 - <span className="text-nowrap"> 301 - {formatRegionCode(region.code)}{" "} 302 - {region.flag} 303 - </span> 304 - <span className="truncate font-normal text-muted-foreground text-xs leading-[inherit]"> 305 - {region.location} 306 - </span> 307 - <IconCloudProviderTooltip 308 - provider={region.provider} 309 - className="size-3" 310 - /> 311 - </FormLabel> 302 + {deprecated ? ( 303 + <TooltipProvider> 304 + <Tooltip> 305 + <TooltipTrigger 306 + disabled={!deprecated} 307 + asChild 308 + > 309 + <FormLabel 310 + htmlFor={region.code} 311 + className={cn( 312 + "w-full truncate font-mono font-normal text-sm", 313 + )} 314 + > 315 + <span className="text-nowrap text-destructive"> 316 + {formatRegionCode( 317 + region.code, 318 + )}{" "} 319 + {region.flag} 320 + </span> 321 + <span className="truncate font-normal text-xs leading-[inherit] line-through decoration-foreground/70"> 322 + {region.location} 323 + </span> 324 + <IconCloudProviderTooltip 325 + provider={ 326 + region.provider 327 + } 328 + className="size-3" 329 + /> 330 + </FormLabel> 331 + </TooltipTrigger> 332 + <TooltipContent> 333 + <p> 334 + This region is deprecated 335 + and will be removed in the 336 + future. 337 + </p> 338 + </TooltipContent> 339 + </Tooltip> 340 + </TooltipProvider> 341 + ) : ( 342 + <FormLabel 343 + htmlFor={region.code} 344 + className="w-full truncate font-mono font-normal text-sm" 345 + > 346 + <span className="text-nowrap"> 347 + {formatRegionCode( 348 + region.code, 349 + )}{" "} 350 + {region.flag} 351 + </span> 352 + <span className="truncate font-normal text-muted-foreground text-xs leading-[inherit]"> 353 + {region.location} 354 + </span> 355 + <IconCloudProviderTooltip 356 + provider={region.provider} 357 + className="size-3" 358 + /> 359 + </FormLabel> 360 + )} 312 361 </FormItem> 313 362 ); 314 363 }} ··· 325 374 </FormItem> 326 375 )} 327 376 /> 377 + <Note color="info"> 378 + <Info /> 379 + <div> 380 + Unfortunately,{" "} 381 + <span className="font-medium text-destructive"> 382 + red regions are deprecated 383 + </span>{" "} 384 + and will be removed in the future. We are routing traffic to the 385 + nearest regions. To compensate for the loss, we have added{" "} 386 + <span className="font-medium"> 387 + new regions from Koyeb and Railway 388 + </span> 389 + . 390 + </div> 391 + </Note> 328 392 </FormCardContent> 329 393 <FormCardFooter> 330 394 <FormCardFooterInfo>
+13
apps/web/src/app/api/checker/cron/_cron.ts
··· 112 112 for (const region of row.regions) { 113 113 const status = 114 114 monitorStatus.data.find((m) => region === m.region)?.status || "active"; 115 + 116 + const r = regionDict[region as keyof typeof regionDict]; 117 + 118 + if (!r) { 119 + console.error(`Invalid region ${region}`); 120 + continue; 121 + } 122 + if (r.deprecated) { 123 + // Let's uncomment this when we are ready to remove deprecated regions 124 + // We should not use deprecated regions anymore 125 + // console.error(`Deprecated region ${region}`); 126 + // continue; 127 + } 115 128 const response = createCronTask({ 116 129 row, 117 130 timestamp,
+213
packages/db/script/region-migration.test.ts
··· 1 + import { beforeEach, describe, expect, test } from "bun:test"; 2 + import type { z } from "zod"; 3 + import type { monitorRegionSchema } from "../src/schema/constants"; 4 + import { updateRegion } from "./region-migration"; 5 + 6 + // Import the types we need 7 + 8 + describe("updateRegion", () => { 9 + let regions: z.infer<typeof monitorRegionSchema>[]; 10 + 11 + beforeEach(() => { 12 + // Reset regions array before each test 13 + regions = ["ams", "hkg", "fra", "lax"]; 14 + }); 15 + 16 + describe("when old region exists in array", () => { 17 + test("should replace old region with 'sin' when new region does not exist in array", () => { 18 + updateRegion("hkg", "sin", regions); 19 + 20 + expect(regions).toEqual(["ams", "sin", "fra", "lax"]); 21 + expect(regions).toHaveLength(4); 22 + }); 23 + 24 + test("should remove old region when new region already exists in array", () => { 25 + updateRegion("hkg", "ams", regions); 26 + 27 + expect(regions).toEqual(["ams", "fra", "lax"]); 28 + expect(regions).toHaveLength(3); 29 + }); 30 + 31 + test("should handle replacing first element", () => { 32 + updateRegion("ams", "sin", regions); 33 + 34 + expect(regions).toEqual(["sin", "hkg", "fra", "lax"]); 35 + }); 36 + 37 + test("should handle replacing last element", () => { 38 + updateRegion("lax", "sin", regions); 39 + 40 + expect(regions).toEqual(["ams", "hkg", "fra", "sin"]); 41 + }); 42 + 43 + test("should handle case where old and new region are the same", () => { 44 + updateRegion("hkg", "hkg", regions); 45 + 46 + // Since hkg exists, it should be removed (newRegionIndex !== -1) 47 + expect(regions).toEqual(["ams", "fra", "lax"]); 48 + expect(regions).toHaveLength(3); 49 + }); 50 + }); 51 + 52 + describe("when old region does not exist in array", () => { 53 + test("should not modify the array when old region is not found", () => { 54 + const originalRegions = [...regions]; 55 + updateRegion("sin", "syd", regions); 56 + 57 + expect(regions).toEqual(originalRegions); 58 + expect(regions).toHaveLength(4); 59 + }); 60 + }); 61 + 62 + describe("edge cases", () => { 63 + test("should handle empty regions array", () => { 64 + const emptyRegions: z.infer<typeof monitorRegionSchema>[] = []; 65 + updateRegion("hkg", "sin", emptyRegions); 66 + 67 + expect(emptyRegions).toEqual([]); 68 + expect(emptyRegions).toHaveLength(0); 69 + }); 70 + 71 + test("should handle single element array - replace scenario", () => { 72 + const singleRegion: z.infer<typeof monitorRegionSchema>[] = ["hkg"]; 73 + updateRegion("hkg", "sin", singleRegion); 74 + 75 + expect(singleRegion).toEqual(["sin"]); 76 + }); 77 + 78 + test("should handle single element array - remove scenario", () => { 79 + const singleRegion: z.infer<typeof monitorRegionSchema>[] = ["hkg"]; 80 + updateRegion("hkg", "hkg", singleRegion); 81 + 82 + expect(singleRegion).toEqual([]); 83 + }); 84 + 85 + test("should handle array with duplicate regions", () => { 86 + const duplicateRegions: z.infer<typeof monitorRegionSchema>[] = [ 87 + "ams", 88 + "hkg", 89 + "ams", 90 + "fra", 91 + ]; 92 + updateRegion("hkg", "sin", duplicateRegions); 93 + 94 + // Should only replace the first occurrence of hkg 95 + expect(duplicateRegions).toEqual(["ams", "sin", "ams", "fra"]); 96 + }); 97 + 98 + test("should handle multiple occurrences of old region", () => { 99 + const multipleOldRegions: z.infer<typeof monitorRegionSchema>[] = [ 100 + "hkg", 101 + "ams", 102 + "hkg", 103 + "fra", 104 + ]; 105 + updateRegion("hkg", "sin", multipleOldRegions); 106 + 107 + // Should only replace the first occurrence 108 + expect(multipleOldRegions).toEqual(["sin", "ams", "hkg", "fra"]); 109 + }); 110 + 111 + describe("function mutates original array", () => { 112 + test("should modify the original regions array reference", () => { 113 + const originalReference = regions; 114 + updateRegion("hkg", "sin", regions); 115 + 116 + // Should be the same reference (mutated) 117 + expect(regions).toBe(originalReference); 118 + expect(regions).toEqual(["ams", "sin", "fra", "lax"]); 119 + }); 120 + }); 121 + 122 + describe("full migrations", () => { 123 + test("should modify the original regions array reference", () => { 124 + const newRegions = [ 125 + "ams", 126 + "arn", 127 + "atl", 128 + "bog", 129 + "bom", 130 + "bos", 131 + "cdg", 132 + "den", 133 + "dfw", 134 + "ewr", 135 + "eze", 136 + "fra", 137 + "gdl", 138 + "gig", 139 + "gru", 140 + "hkg", 141 + "iad", 142 + "jnb", 143 + "lax", 144 + "lhr", 145 + "mad", 146 + "mia", 147 + "nrt", 148 + "ord", 149 + "otp", 150 + "phx", 151 + "qro", 152 + "sin", 153 + "scl", 154 + "sjc", 155 + "sea", 156 + "sin", 157 + "syd", 158 + "waw", 159 + "yul", 160 + "yyz", 161 + ] as z.infer<typeof monitorRegionSchema>[]; 162 + // Asia Pacific 163 + updateRegion("hkg", "sin", newRegions); 164 + 165 + // North America 166 + updateRegion("atl", "dfw", newRegions); 167 + updateRegion("mia", "dfw", newRegions); 168 + updateRegion("gdl", "dfw", newRegions); 169 + updateRegion("qro", "dfw", newRegions); 170 + updateRegion("bos", "ewr", newRegions); 171 + updateRegion("phx", "lax", newRegions); 172 + updateRegion("sea", "sjc", newRegions); 173 + updateRegion("yul", "yyz", newRegions); 174 + 175 + // Europe 176 + updateRegion("waw", "ams", newRegions); 177 + updateRegion("mad", "cdg", newRegions); 178 + updateRegion("otp", "fra", newRegions); 179 + 180 + // South America 181 + updateRegion("bog", "gru", newRegions); 182 + updateRegion("gig", "gru", newRegions); 183 + updateRegion("scl", "gru", newRegions); 184 + updateRegion("eze", "gru", newRegions); 185 + 186 + // Should be the same reference (mutated) 187 + 188 + expect(newRegions).toEqual([ 189 + "ams", 190 + "arn", 191 + "bom", 192 + "cdg", 193 + "den", 194 + "dfw", 195 + "ewr", 196 + "fra", 197 + "gru", 198 + "iad", 199 + "jnb", 200 + "lax", 201 + "lhr", 202 + "nrt", 203 + "ord", 204 + "sin", 205 + "sjc", 206 + "sin", 207 + "syd", 208 + "yyz", 209 + ]); 210 + }); 211 + }); 212 + }); 213 + });
+59
packages/db/script/region-migration.ts
··· 1 + import { z } from "zod"; 2 + import { db, eq, schema } from "../src"; 3 + import { selectMonitorSchema } from "../src/schema"; 4 + import type { monitorRegionSchema } from "../src/schema/constants"; 5 + 6 + const rawMonitors = await db.select().from(schema.monitor); 7 + 8 + const monitors = z.array(selectMonitorSchema).parse(rawMonitors); 9 + for (const monitor of monitors) { 10 + const regions = monitor.regions.slice(); 11 + 12 + // Asia Pacific 13 + updateRegion("hkg", "sin", regions); 14 + 15 + // North America 16 + updateRegion("atl", "dfw", regions); 17 + updateRegion("mia", "dfw", regions); 18 + updateRegion("gdl", "dfw", regions); 19 + updateRegion("qro", "dfw", regions); 20 + updateRegion("bos", "ewr", regions); 21 + updateRegion("phx", "lax", regions); 22 + updateRegion("sea", "sjc", regions); 23 + updateRegion("yul", "yyz", regions); 24 + 25 + // Europe 26 + updateRegion("waw", "ams", regions); 27 + updateRegion("mad", "cdg", regions); 28 + updateRegion("otp", "fra", regions); 29 + 30 + // South America 31 + updateRegion("bog", "gru", regions); 32 + updateRegion("gig", "gru", regions); 33 + updateRegion("scl", "gru", regions); 34 + updateRegion("eze", "gru", regions); 35 + 36 + const newRegions = regions.join(","); 37 + await db 38 + .update(schema.monitor) 39 + .set({ regions: newRegions }) 40 + .where(eq(schema.monitor.id, monitor.id)) 41 + .execute(); 42 + } 43 + 44 + export function updateRegion( 45 + oldRegion: z.infer<typeof monitorRegionSchema>, 46 + newRegion: z.infer<typeof monitorRegionSchema>, 47 + regions: z.infer<typeof monitorRegionSchema>[], 48 + ) { 49 + const regionIndex = regions.indexOf(oldRegion); 50 + if (regionIndex !== -1) { 51 + const newRegionIndex = regions.indexOf(newRegion); 52 + if (newRegionIndex === -1) { 53 + regions[regionIndex] = newRegion; 54 + } 55 + if (newRegionIndex !== -1) { 56 + regions.splice(regionIndex, 1); 57 + } 58 + } 59 + }
+3 -3
packages/db/src/schema/plan/config.ts
··· 51 51 "notification-channels": 1, 52 52 members: 1, 53 53 "audit-log": false, 54 - regions: ["ams", "gru", "iad", "jnb", "hkg", "syd"] satisfies Region[], 54 + regions: ["ams", "gru", "iad", "jnb", "sin", "syd"] satisfies Region[], 55 55 "private-locations": false, 56 56 }, 57 57 }, ··· 89 89 "notification-channels": 10, 90 90 members: "Unlimited", 91 91 "audit-log": false, 92 - regions: [...monitorRegions], 92 + regions: [...monitorRegions] satisfies Region[], 93 93 "private-locations": false, 94 94 }, 95 95 }, ··· 127 127 "notification-channels": 20, 128 128 members: "Unlimited", 129 129 "audit-log": true, 130 - regions: [...monitorRegions], 130 + regions: [...monitorRegions] satisfies Region[], 131 131 "private-locations": false, 132 132 }, 133 133 },
+1 -1
packages/db/src/schema/plan/schema.ts
··· 19 19 .default("14 days"), 20 20 regions: monitorRegionSchema 21 21 .array() 22 - .default(["ams", "gru", "iad", "jnb", "hkg", "syd"]), 22 + .default(["ams", "gru", "iad", "jnb", "sin", "syd"]), 23 23 "private-locations": z.boolean().default(false), 24 24 screenshots: z.boolean().default(false), 25 25 "response-logs": z.boolean().default(false),
+46
packages/utils/index.ts
··· 18 18 location: string; 19 19 flag: string; 20 20 continent: Continent; 21 + deprecated: boolean; 21 22 provider: "fly" | "koyeb" | "railway"; 22 23 }; 23 24 ··· 37 38 location: "Amsterdam, Netherlands", 38 39 flag: "🇳🇱", 39 40 continent: "Europe", 41 + deprecated: false, 40 42 provider: "fly", 41 43 }, 42 44 arn: { ··· 44 46 location: "Stockholm, Sweden", 45 47 flag: "🇸🇪", 46 48 continent: "Europe", 49 + deprecated: false, 47 50 provider: "fly", 48 51 }, 49 52 ··· 52 55 location: "Atlanta, Georgia, USA", 53 56 flag: "🇺🇸", 54 57 continent: "North America", 58 + deprecated: true, 55 59 provider: "fly", 56 60 }, 57 61 bog: { ··· 59 63 location: "Bogotá, Colombia", 60 64 flag: "🇨🇴", 61 65 continent: "South America", 66 + deprecated: true, 62 67 provider: "fly", 63 68 }, 64 69 bom: { ··· 66 71 location: "Mumbai, India", 67 72 flag: "🇮🇳", 68 73 continent: "Asia", 74 + deprecated: false, 69 75 provider: "fly", 70 76 }, 71 77 bos: { ··· 73 79 location: "Boston, Massachusetts, USA", 74 80 flag: "🇺🇸", 75 81 continent: "North America", 82 + deprecated: true, 76 83 provider: "fly", 77 84 }, 78 85 cdg: { ··· 80 87 location: "Paris, France", 81 88 flag: "🇫🇷", 82 89 continent: "Europe", 90 + deprecated: false, 83 91 provider: "fly", 84 92 }, 85 93 den: { ··· 87 95 location: "Denver, Colorado, USA", 88 96 flag: "🇺🇸", 89 97 continent: "North America", 98 + deprecated: true, 90 99 provider: "fly", 91 100 }, 92 101 dfw: { ··· 94 103 location: "Dallas, Texas, USA", 95 104 flag: "🇺🇸", 96 105 continent: "North America", 106 + deprecated: false, 97 107 provider: "fly", 98 108 }, 99 109 ewr: { ··· 101 111 location: "Secaucus, New Jersey, USA", 102 112 flag: "🇺🇸", 103 113 continent: "North America", 114 + deprecated: false, 104 115 provider: "fly", 105 116 }, 106 117 eze: { ··· 108 119 location: "Ezeiza, Argentina", 109 120 flag: "🇦🇷", 110 121 continent: "South America", 122 + deprecated: true, 111 123 provider: "fly", 112 124 }, 113 125 fra: { ··· 115 127 location: "Frankfurt, Germany", 116 128 flag: "🇩🇪", 117 129 continent: "Europe", 130 + deprecated: false, 118 131 provider: "fly", 119 132 }, 120 133 gdl: { ··· 122 135 location: "Guadalajara, Mexico", 123 136 flag: "🇲🇽", 124 137 continent: "North America", 138 + deprecated: true, 125 139 provider: "fly", 126 140 }, 127 141 gig: { ··· 129 143 location: "Rio de Janeiro, Brazil", 130 144 flag: "🇧🇷", 131 145 continent: "South America", 146 + deprecated: true, 132 147 provider: "fly", 133 148 }, 134 149 gru: { ··· 136 151 location: "Sao Paulo, Brazil", 137 152 flag: "🇧🇷", 138 153 continent: "South America", 154 + deprecated: false, 139 155 provider: "fly", 140 156 }, 141 157 hkg: { ··· 143 159 location: "Hong Kong, Hong Kong", 144 160 flag: "🇭🇰", 145 161 continent: "Asia", 162 + deprecated: true, 146 163 provider: "fly", 147 164 }, 148 165 iad: { ··· 150 167 location: "Ashburn, Virginia, USA", 151 168 flag: "🇺🇸", 152 169 continent: "North America", 170 + deprecated: false, 153 171 provider: "fly", 154 172 }, 155 173 jnb: { ··· 157 175 location: "Johannesburg, South Africa", 158 176 flag: "🇿🇦", 159 177 continent: "Africa", 178 + deprecated: false, 160 179 provider: "fly", 161 180 }, 162 181 lax: { ··· 164 183 location: "Los Angeles, California, USA", 165 184 flag: "🇺🇸", 166 185 continent: "North America", 186 + deprecated: false, 167 187 provider: "fly", 168 188 }, 169 189 lhr: { ··· 171 191 location: "London, United Kingdom", 172 192 flag: "🇬🇧", 173 193 continent: "Europe", 194 + deprecated: false, 174 195 provider: "fly", 175 196 }, 176 197 mad: { ··· 178 199 location: "Madrid, Spain", 179 200 flag: "🇪🇸", 180 201 continent: "Europe", 202 + deprecated: true, 181 203 provider: "fly", 182 204 }, 183 205 mia: { ··· 185 207 location: "Miami, Florida, USA", 186 208 flag: "🇺🇸", 187 209 continent: "North America", 210 + deprecated: true, 188 211 provider: "fly", 189 212 }, 190 213 nrt: { ··· 192 215 location: "Tokyo, Japan", 193 216 flag: "🇯🇵", 194 217 continent: "Asia", 218 + deprecated: false, 195 219 provider: "fly", 196 220 }, 197 221 ord: { ··· 199 223 location: "Chicago, Illinois, USA", 200 224 flag: "🇺🇸", 201 225 continent: "North America", 226 + deprecated: false, 202 227 provider: "fly", 203 228 }, 204 229 otp: { ··· 206 231 location: "Bucharest, Romania", 207 232 flag: "🇷🇴", 208 233 continent: "Europe", 234 + deprecated: true, 209 235 provider: "fly", 210 236 }, 211 237 phx: { ··· 213 239 location: "Phoenix, Arizona, USA", 214 240 flag: "🇺🇸", 215 241 continent: "North America", 242 + deprecated: true, 216 243 provider: "fly", 217 244 }, 218 245 qro: { ··· 220 247 location: "Querétaro, Mexico", 221 248 flag: "🇲🇽", 222 249 continent: "North America", 250 + deprecated: true, 223 251 provider: "fly", 224 252 }, 225 253 scl: { ··· 227 255 location: "Santiago, Chile", 228 256 flag: "🇨🇱", 229 257 continent: "South America", 258 + deprecated: true, 230 259 provider: "fly", 231 260 }, 232 261 sjc: { ··· 234 263 location: "San Jose, California, USA", 235 264 flag: "🇺🇸", 236 265 continent: "North America", 266 + deprecated: true, 237 267 provider: "fly", 238 268 }, 239 269 sea: { ··· 241 271 location: "Seattle, Washington, USA", 242 272 flag: "🇺🇸", 243 273 continent: "North America", 274 + deprecated: true, 244 275 provider: "fly", 245 276 }, 246 277 sin: { ··· 248 279 location: "Singapore, Singapore", 249 280 flag: "🇸🇬", 250 281 continent: "Asia", 282 + deprecated: false, 251 283 provider: "fly", 252 284 }, 253 285 syd: { ··· 255 287 location: "Sydney, Australia", 256 288 flag: "🇦🇺", 257 289 continent: "Oceania", 290 + deprecated: false, 258 291 provider: "fly", 259 292 }, 260 293 waw: { ··· 262 295 location: "Warsaw, Poland", 263 296 flag: "🇵🇱", 264 297 continent: "Europe", 298 + deprecated: true, 265 299 provider: "fly", 266 300 }, 267 301 yul: { ··· 269 303 location: "Montreal, Canada", 270 304 flag: "🇨🇦", 271 305 continent: "North America", 306 + deprecated: true, 272 307 provider: "fly", 273 308 }, 274 309 yyz: { ··· 276 311 location: "Toronto, Canada", 277 312 flag: "🇨🇦", 278 313 continent: "North America", 314 + deprecated: false, 279 315 provider: "fly", 280 316 }, 281 317 koyeb_fra: { ··· 283 319 location: "Frankfurt, Germany", 284 320 flag: "🇩🇪", 285 321 continent: "Europe", 322 + deprecated: false, 286 323 provider: "koyeb", 287 324 }, 288 325 koyeb_par: { ··· 290 327 location: "Paris, France", 291 328 flag: "🇫🇷", 292 329 continent: "Europe", 330 + deprecated: false, 293 331 provider: "koyeb", 294 332 }, 295 333 koyeb_sfo: { ··· 297 335 location: "San Francisco, USA", 298 336 flag: "🇺🇸", 299 337 continent: "North America", 338 + deprecated: false, 300 339 provider: "koyeb", 301 340 }, 302 341 koyeb_sin: { ··· 304 343 location: "Singapore, Singapore", 305 344 flag: "🇸🇬", 306 345 continent: "Asia", 346 + deprecated: false, 307 347 provider: "koyeb", 308 348 }, 309 349 koyeb_tyo: { ··· 311 351 location: "Tokyo, Japan", 312 352 flag: "🇯🇵", 313 353 continent: "Asia", 354 + deprecated: false, 314 355 provider: "koyeb", 315 356 }, 316 357 koyeb_was: { ··· 318 359 location: "Washington, USA", 319 360 flag: "🇺🇸", 320 361 continent: "North America", 362 + deprecated: false, 321 363 provider: "koyeb", 322 364 }, 323 365 "railway_us-west2": { ··· 325 367 location: "California, USA", 326 368 flag: "🇺🇸", 327 369 continent: "North America", 370 + deprecated: false, 328 371 provider: "railway", 329 372 }, 330 373 "railway_us-east4-eqdc4a": { ··· 332 375 location: "Virginia, USA", 333 376 flag: "🇺🇸", 334 377 continent: "North America", 378 + deprecated: false, 335 379 provider: "railway", 336 380 }, 337 381 "railway_europe-west4-drams3a": { ··· 339 383 location: "Amsterdam, Netherlands", 340 384 flag: "🇳🇱", 341 385 continent: "Europe", 386 + deprecated: false, 342 387 provider: "railway", 343 388 }, 344 389 "railway_asia-southeast1-eqsg3a": { ··· 346 391 location: "Singapore, Singapore", 347 392 flag: "🇸🇬", 348 393 continent: "Asia", 394 + deprecated: false, 349 395 provider: "railway", 350 396 }, 351 397 } as const;