@infracraft/pulumi 1.17.4 → 1.19.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.

Potentially problematic release.


This version of @infracraft/pulumi might be problematic. Click here for more details.

package/README.md CHANGED
@@ -97,7 +97,7 @@ new RailwayDeploy("api-deploy", {
97
97
  | `RailwayVariable` | — | Batch upsert; uses `skipDeploys` to avoid snapshot errors |
98
98
  | `RailwayVolume` | `.id` | Persistent volume; `mountPath` must be absolute |
99
99
  | `RailwayProjectToken` | `.token` (secret) | Environment-scoped deploy token; feed into `RailwayDeploy` |
100
- | `RailwayDeploy` | | Runs `railway up --detach`, then monitors the deployment via the Railway API |
100
+ | `RailwayDeploy` | `.deploymentUrl` | Runs `railway up --detach`, then monitors the deployment via the Railway API |
101
101
 
102
102
  **Enums:** `RailwayBuilder` (`RAILPACK`, `NIXPACKS`, `DOCKERFILE`, `HEROKU`, `PAKETO`), `RailwayRestartPolicy` (`ON_FAILURE`, `ALWAYS`, `NEVER`)
103
103
 
@@ -182,7 +182,7 @@ const project = new VercelProject("web", {
182
182
  rootDirectory: "apps/web",
183
183
  }, { provider })
184
184
 
185
- // project.url resolves to the custom domain or <name>.vercel.app
185
+ // project.url is a full https:// URL — custom domain or <name>.vercel.app
186
186
  export const url = project.url
187
187
 
188
188
  const vars = new VercelVariable("web-vars", {
@@ -217,9 +217,9 @@ new VercelResourceConnection("kv-conn", {
217
217
  | Class | Key outputs | Notes |
218
218
  |---|---|---|
219
219
  | `VercelProvider` | — | `token` + `teamId` |
220
- | `VercelProject` | `.id`, `.url` | `.url` prefers custom domain over `*.vercel.app` |
220
+ | `VercelProject` | `.id`, `.url` | `.url` is a full `https://` URL; prefers the custom production domain over `<name>.vercel.app` |
221
221
  | `VercelVariable` | `.contentHash` | Use as a deploy trigger to redeploy on env var changes |
222
- | `VercelDeploy` | | Runs `vercel deploy --prod --yes` |
222
+ | `VercelDeploy` | `.deploymentUrl` | Runs `vercel deploy --prod --yes` |
223
223
  | `VercelIntegration` | `.configurationId` (`icfg_…`) | Resolves an installed marketplace integration by slug |
224
224
  | `VercelMarketplaceResource` | `.id`, `.externalResourceId`, `.status` | Provisions a marketplace store |
225
225
  | `VercelResourceConnection` | — | Wires a store to a project; injects env vars into target environments |
@@ -298,12 +298,13 @@ new FlyDeploy("api-deploy", {
298
298
 
299
299
  | Class | Key outputs | Notes |
300
300
  |---|---|---|
301
+ | `FlyProvider` | — | Pass as `provider` option to every Fly resource |
301
302
  | `FlyApp` | `.id` (app name) | Adopt-or-create |
302
303
  | `FlySecret` | `.version` | Feed into `FlyDeploy` triggers to redeploy on secret changes |
303
304
  | `FlyVolume` | `.id` (`vol_…`) | `sizeGb` can only grow |
304
305
  | `FlyCertificate` | `.id` (hostname), `.configured`, `.dnsRequirements` | `.dnsRequirements` contains ACME validation records |
305
306
  | `FlyIp` | `.id` (IP address) | `type`: `FlyIpType.V4`, `V6`, `SHARED_V4`, `PRIVATE_V6` |
306
- | `FlyDeploy` | | Writes fly.toml at deploy time; triggers on config + source hash |
307
+ | `FlyDeploy` | `.deploymentUrl` | Writes fly.toml at deploy time; triggers on config + source hash |
307
308
 
308
309
  **Enums:** `FlyIpType`, `FlyDeployStrategy` (`ROLLING`, `IMMEDIATE`, `CANARY`, `BLUEGREEN`), `FlyRestartPolicy` (`ALWAYS`, `ON_FAILURE`, `NEVER`), `FlyAutoStopMachines` (`OFF`, `STOP`, `SUSPEND`), `FlyConcurrencyType` (`CONNECTIONS`, `REQUESTS`), `FlyServiceProtocol` (`TCP`, `UDP`), `FlyPortHandler` (`HTTP`, `TLS`, `PG_TLS`, `PROXY_PROTO`, `EDGE_HTTP`), `FlyCpuKind` (`SHARED`, `PERFORMANCE`), `FlyCheckType` (`HTTP`, `TCP`)
309
310
 
@@ -5,6 +5,18 @@ let _pulumi_pulumi = require("@pulumi/pulumi");
5
5
  _pulumi_pulumi = require_chunk.__toESM(_pulumi_pulumi, 1);
6
6
 
7
7
  //#region src/railway/domain.ts
8
+ /**
9
+ * Picks the CNAME record Railway expects for routing traffic to a custom domain out
10
+ * of its full DNS record list (which also includes e.g. an ACME challenge TXT record).
11
+ */
12
+ function extractCnameTarget(dnsRecords) {
13
+ return dnsRecords?.find((record) => record.recordType === "DNS_RECORD_TYPE_CNAME" && record.purpose === "DNS_RECORD_PURPOSE_TRAFFIC_ROUTE")?.requiredValue;
14
+ }
15
+ const DOMAIN_STATUS_FIELDS = `
16
+ status {
17
+ dnsRecords { recordType purpose requiredValue }
18
+ }
19
+ `;
8
20
  const SERVICE_DOMAINS_QUERY = `
9
21
  query($projectId: String!, $serviceId: String!, $environmentId: String!) {
10
22
  domains(
@@ -13,7 +25,7 @@ const SERVICE_DOMAINS_QUERY = `
13
25
  environmentId: $environmentId
14
26
  ) {
15
27
  serviceDomains { id domain }
16
- customDomains { id domain }
28
+ customDomains { id domain ${DOMAIN_STATUS_FIELDS} }
17
29
  }
18
30
  }
19
31
  `;
@@ -24,7 +36,7 @@ const SERVICE_DOMAIN_CREATE = `
24
36
  `;
25
37
  const CUSTOM_DOMAIN_CREATE = `
26
38
  mutation($input: CustomDomainCreateInput!) {
27
- customDomainCreate(input: $input) { id domain }
39
+ customDomainCreate(input: $input) { id domain ${DOMAIN_STATUS_FIELDS} }
28
40
  }
29
41
  `;
30
42
  const CUSTOM_DOMAIN_DELETE = `
@@ -48,6 +60,8 @@ async function findExistingDomains(client, projectId, serviceId, environmentId)
48
60
  *
49
61
  * Uses adopt-or-create: queries existing domains before creating new ones.
50
62
  * Uses the FQDN as the Pulumi resource ID.
63
+ *
64
+ * @internal Exported only for unit testing; not part of the public API surface.
51
65
  */
52
66
  var RailwayDomainResourceProvider = class {
53
67
  async create(inputs) {
@@ -62,7 +76,8 @@ var RailwayDomainResourceProvider = class {
62
76
  outs: {
63
77
  ...inputs,
64
78
  domainId: found.id,
65
- fqdn: found.domain
79
+ fqdn: found.domain,
80
+ cnameTarget: extractCnameTarget(found.status?.dnsRecords)
66
81
  }
67
82
  };
68
83
  }
@@ -77,7 +92,8 @@ var RailwayDomainResourceProvider = class {
77
92
  outs: {
78
93
  ...inputs,
79
94
  domainId: result.customDomainCreate.id,
80
- fqdn: result.customDomainCreate.domain
95
+ fqdn: result.customDomainCreate.domain,
96
+ cnameTarget: extractCnameTarget(result.customDomainCreate.status?.dnsRecords)
81
97
  }
82
98
  };
83
99
  }
@@ -116,7 +132,8 @@ var RailwayDomainResourceProvider = class {
116
132
  props: {
117
133
  ...props,
118
134
  domainId: found.id,
119
- fqdn: found.domain
135
+ fqdn: found.domain,
136
+ cnameTarget: extractCnameTarget(found.status?.dnsRecords)
120
137
  }
121
138
  };
122
139
  }
@@ -160,20 +177,29 @@ var RailwayDomainResource = class extends _pulumi_pulumi.dynamic.Resource {
160
177
  super(new RailwayDomainResourceProvider(), name, {
161
178
  ...args,
162
179
  domainId: void 0,
163
- fqdn: void 0
180
+ fqdn: void 0,
181
+ cnameTarget: void 0
164
182
  }, opts);
165
183
  }
166
184
  };
167
185
  /**
168
186
  * Manages a Railway domain (service or custom) with adopt-or-create semantics.
169
187
  *
188
+ * A service can carry more than one custom domain — declare one `RailwayDomain` per
189
+ * domain; each instance adopts, creates, and deletes only its own domain.
190
+ *
170
191
  * @example
171
192
  * ```typescript
172
- * const domain = new RailwayDomain("api-domain", {}, {
193
+ * const apiDomain = new RailwayDomain("api-domain", { customDomain: "api.example.com" }, {
194
+ * provider, project, environment, service,
195
+ * });
196
+ * const wwwDomain = new RailwayDomain("www-domain", { customDomain: "www.example.com" }, {
173
197
  * provider, project, environment, service,
174
198
  * });
175
199
  *
176
- * const url = pulumi.interpolate`https://${domain.fqdn}`;
200
+ * // Point each domain's DNS CNAME at its own target.
201
+ * const apiCnameTarget = apiDomain.cnameTarget;
202
+ * const wwwCnameTarget = wwwDomain.cnameTarget;
177
203
  * ```
178
204
  */
179
205
  var RailwayDomain = class extends _pulumi_pulumi.ComponentResource {
@@ -188,10 +214,15 @@ var RailwayDomain = class extends _pulumi_pulumi.ComponentResource {
188
214
  customDomain: args.customDomain
189
215
  }, { parent: this });
190
216
  this.fqdn = resource.fqdn;
191
- this.registerOutputs({ fqdn: this.fqdn });
217
+ this.cnameTarget = resource.cnameTarget;
218
+ this.registerOutputs({
219
+ fqdn: this.fqdn,
220
+ cnameTarget: this.cnameTarget
221
+ });
192
222
  }
193
223
  };
194
224
 
195
225
  //#endregion
196
226
  exports.RailwayDomain = RailwayDomain;
227
+ exports.RailwayDomainResourceProvider = RailwayDomainResourceProvider;
197
228
  //# sourceMappingURL=domain.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"domain.cjs","names":["RailwayClient","pulumi"],"sources":["../../src/railway/domain.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { RailwayClient } from \"./client\";\nimport type { RailwayEnvironment } from \"./environment\";\nimport type { RailwayProject } from \"./project\";\nimport type { RailwayProvider } from \"./provider\";\nimport type { RailwayService } from \"./service\";\n\n/** Resolved inputs for the Railway domain dynamic provider. */\ninterface RailwayDomainInputs {\n\t/** Railway API bearer token. */\n\ttoken: string;\n\n\t/** Railway project UUID. */\n\tprojectId: string;\n\n\t/** Railway service UUID to attach the domain to. */\n\tserviceId: string;\n\n\t/** Railway environment UUID (e.g. production). */\n\tenvironmentId: string;\n\n\t/** Custom domain FQDN (e.g. `\"api.example.com\"`). Omit for auto-generated Railway domain. */\n\tcustomDomain?: string;\n}\n\n/** Persisted state for the Railway domain, extending inputs with Railway-assigned identifiers. */\ninterface RailwayDomainOutputs extends RailwayDomainInputs {\n\t/** Railway-assigned domain UUID (used for deletion API calls). */\n\tdomainId: string;\n\n\t/** Fully qualified domain name (e.g. `\"api-production-abc.up.railway.app\"`). */\n\tfqdn: string;\n}\n\nconst SERVICE_DOMAINS_QUERY = `\n query($projectId: String!, $serviceId: String!, $environmentId: String!) {\n domains(\n projectId: $projectId\n serviceId: $serviceId\n environmentId: $environmentId\n ) {\n serviceDomains { id domain }\n customDomains { id domain }\n }\n }\n`;\n\nconst SERVICE_DOMAIN_CREATE = `\n mutation($input: ServiceDomainCreateInput!) {\n serviceDomainCreate(input: $input) { id domain }\n }\n`;\n\nconst CUSTOM_DOMAIN_CREATE = `\n mutation($input: CustomDomainCreateInput!) {\n customDomainCreate(input: $input) { id domain }\n }\n`;\n\nconst CUSTOM_DOMAIN_DELETE = `\n mutation($id: String!) { customDomainDelete(id: $id) }\n`;\n\nconst SERVICE_DOMAIN_DELETE = `\n mutation($id: String!) { serviceDomainDelete(id: $id) }\n`;\n\n/**\n * Queries all existing domains (service and custom) for a Railway service.\n */\nasync function findExistingDomains(\n\tclient: RailwayClient,\n\tprojectId: string,\n\tserviceId: string,\n\tenvironmentId: string,\n): Promise<{\n\tserviceDomains: Array<{ id: string; domain: string }>;\n\tcustomDomains: Array<{ id: string; domain: string }>;\n}> {\n\tconst result = await client.query<{\n\t\tdomains: {\n\t\t\tserviceDomains: Array<{ id: string; domain: string }>;\n\t\t\tcustomDomains: Array<{ id: string; domain: string }>;\n\t\t};\n\t}>(SERVICE_DOMAINS_QUERY, { projectId, serviceId, environmentId });\n\n\treturn result.domains;\n}\n\n/**\n * Dynamic provider implementing CRUD for Railway domains.\n *\n * Uses adopt-or-create: queries existing domains before creating new ones.\n * Uses the FQDN as the Pulumi resource ID.\n */\nclass RailwayDomainResourceProvider implements pulumi.dynamic.ResourceProvider {\n\tasync create(\n\t\tinputs: RailwayDomainInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new RailwayClient(inputs.token);\n\n\t\tconst existing = await findExistingDomains(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.serviceId,\n\t\t\tinputs.environmentId,\n\t\t);\n\n\t\tif (inputs.customDomain) {\n\t\t\tconst found = existing.customDomains.find(\n\t\t\t\t(d) => d.domain === inputs.customDomain,\n\t\t\t);\n\n\t\t\tif (found) {\n\t\t\t\tpulumi.log.info(`Adopting existing custom domain \"${found.domain}\"`);\n\n\t\t\t\treturn {\n\t\t\t\t\tid: found.domain,\n\t\t\t\t\touts: { ...inputs, domainId: found.id, fqdn: found.domain },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst result = await client.query<{\n\t\t\t\tcustomDomainCreate: { id: string; domain: string };\n\t\t\t}>(CUSTOM_DOMAIN_CREATE, {\n\t\t\t\tinput: {\n\t\t\t\t\tprojectId: inputs.projectId,\n\t\t\t\t\tserviceId: inputs.serviceId,\n\t\t\t\t\tenvironmentId: inputs.environmentId,\n\t\t\t\t\tdomain: inputs.customDomain,\n\t\t\t\t},\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\tid: result.customDomainCreate.domain,\n\t\t\t\touts: {\n\t\t\t\t\t...inputs,\n\t\t\t\t\tdomainId: result.customDomainCreate.id,\n\t\t\t\t\tfqdn: result.customDomainCreate.domain,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tif (existing.serviceDomains.length > 0) {\n\t\t\tconst found = existing.serviceDomains[0];\n\n\t\t\tpulumi.log.info(`Adopting existing service domain \"${found.domain}\"`);\n\n\t\t\treturn {\n\t\t\t\tid: found.domain,\n\t\t\t\touts: { ...inputs, domainId: found.id, fqdn: found.domain },\n\t\t\t};\n\t\t}\n\n\t\tconst result = await client.query<{\n\t\t\tserviceDomainCreate: { id: string; domain: string };\n\t\t}>(SERVICE_DOMAIN_CREATE, {\n\t\t\tinput: {\n\t\t\t\tserviceId: inputs.serviceId,\n\t\t\t\tenvironmentId: inputs.environmentId,\n\t\t\t},\n\t\t});\n\n\t\treturn {\n\t\t\tid: result.serviceDomainCreate.domain,\n\t\t\touts: {\n\t\t\t\t...inputs,\n\t\t\t\tdomainId: result.serviceDomainCreate.id,\n\t\t\t\tfqdn: result.serviceDomainCreate.domain,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync read(\n\t\t_id: string,\n\t\tprops: RailwayDomainOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\tconst existing = await findExistingDomains(\n\t\t\tclient,\n\t\t\tprops.projectId,\n\t\t\tprops.serviceId,\n\t\t\tprops.environmentId,\n\t\t);\n\n\t\tif (props.customDomain) {\n\t\t\tconst found = existing.customDomains.find(\n\t\t\t\t(d) => d.domain === props.customDomain,\n\t\t\t);\n\n\t\t\tif (!found) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Custom domain \"${props.customDomain}\" not found during refresh`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tid: found.domain,\n\t\t\t\tprops: { ...props, domainId: found.id, fqdn: found.domain },\n\t\t\t};\n\t\t}\n\n\t\tif (existing.serviceDomains.length > 0) {\n\t\t\tconst found = existing.serviceDomains[0];\n\n\t\t\treturn {\n\t\t\t\tid: found.domain,\n\t\t\t\tprops: { ...props, domainId: found.id, fqdn: found.domain },\n\t\t\t};\n\t\t}\n\n\t\tthrow new Error(\"Railway domain not found during refresh\");\n\t}\n\n\tasync delete(_id: string, props: RailwayDomainOutputs): Promise<void> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\tconst mutation = props.customDomain\n\t\t\t? CUSTOM_DOMAIN_DELETE\n\t\t\t: SERVICE_DOMAIN_DELETE;\n\n\t\ttry {\n\t\t\tawait client.query(mutation, { id: props.domainId });\n\t\t} catch {\n\t\t\tpulumi.log.warn(\n\t\t\t\t\"Failed to delete Railway domain (may already be deleted)\",\n\t\t\t);\n\t\t}\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: RailwayDomainOutputs,\n\t\tnews: RailwayDomainInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.serviceId !== news.serviceId) {\n\t\t\treplaces.push(\"serviceId\");\n\t\t}\n\n\t\tif (olds.customDomain !== news.customDomain) {\n\t\t\treplaces.push(\"customDomain\");\n\t\t}\n\n\t\tif (olds.environmentId !== news.environmentId) {\n\t\t\treplaces.push(\"environmentId\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass RailwayDomainResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly fqdn: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tserviceId: pulumi.Input<string>;\n\t\t\tenvironmentId: pulumi.Input<string>;\n\t\t\tcustomDomain?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew RailwayDomainResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, domainId: undefined, fqdn: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for RailwayDomain — replaces Pulumi's native `provider` field. */\ntype RailwayDomainOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Railway authentication context. */\n\tprovider: RailwayProvider;\n\n\t/** Railway project context. */\n\tproject: RailwayProject;\n\n\t/** Railway environment context. */\n\tenvironment: RailwayEnvironment;\n\n\t/** Railway service context. */\n\tservice: RailwayService;\n};\n\n/** Args for RailwayDomain. */\nexport interface RailwayDomainArgs {\n\t/** Custom domain FQDN. Omit to create an auto-generated Railway service domain. */\n\tcustomDomain?: pulumi.Input<string>;\n}\n\n/**\n * Manages a Railway domain (service or custom) with adopt-or-create semantics.\n *\n * @example\n * ```typescript\n * const domain = new RailwayDomain(\"api-domain\", {}, {\n * provider, project, environment, service,\n * });\n *\n * const url = pulumi.interpolate`https://${domain.fqdn}`;\n * ```\n */\nexport class RailwayDomain extends pulumi.ComponentResource {\n\t/** Fully qualified domain name. */\n\tpublic readonly fqdn: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: RailwayDomainArgs,\n\t\topts: RailwayDomainOptions,\n\t) {\n\t\tconst { provider, project, environment, service, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:railway:Domain\", name, {}, pulumiOpts);\n\n\t\tconst resource = new RailwayDomainResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tprojectId: project.id,\n\t\t\t\tserviceId: service.id,\n\t\t\t\tenvironmentId: environment.id,\n\t\t\t\tcustomDomain: args.customDomain,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.fqdn = resource.fqdn;\n\n\t\tthis.registerOutputs({ fqdn: this.fqdn });\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"domain.cjs","names":["RailwayClient","pulumi"],"sources":["../../src/railway/domain.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { RailwayClient } from \"./client\";\nimport type { RailwayEnvironment } from \"./environment\";\nimport type { RailwayProject } from \"./project\";\nimport type { RailwayProvider } from \"./provider\";\nimport type { RailwayService } from \"./service\";\n\n/** Resolved inputs for the Railway domain dynamic provider. */\ninterface RailwayDomainInputs {\n\t/** Railway API bearer token. */\n\ttoken: string;\n\n\t/** Railway project UUID. */\n\tprojectId: string;\n\n\t/** Railway service UUID to attach the domain to. */\n\tserviceId: string;\n\n\t/** Railway environment UUID (e.g. production). */\n\tenvironmentId: string;\n\n\t/** Custom domain FQDN (e.g. `\"api.example.com\"`). Omit for auto-generated Railway domain. */\n\tcustomDomain?: string;\n}\n\n/** Persisted state for the Railway domain, extending inputs with Railway-assigned identifiers. */\ninterface RailwayDomainOutputs extends RailwayDomainInputs {\n\t/** Railway-assigned domain UUID (used for deletion API calls). */\n\tdomainId: string;\n\n\t/** Fully qualified domain name (e.g. `\"api-production-abc.up.railway.app\"`). */\n\tfqdn: string;\n\n\t/**\n\t * The value to CNAME `customDomain` to. Only present for custom domains — a\n\t * service's auto-generated `*.up.railway.app` domain needs no DNS record of its\n\t * own. `undefined` if Railway hasn't returned a traffic-routing CNAME record yet.\n\t */\n\tcnameTarget?: string;\n}\n\n/** A single DNS record Railway expects for a custom domain (CNAME target, ACME challenge, ...). */\ninterface DomainDnsRecord {\n\trecordType: string;\n\tpurpose: string;\n\trequiredValue: string;\n}\n\n/** Shape returned for a custom domain, including its required DNS records. */\ninterface CustomDomainEntry {\n\tid: string;\n\tdomain: string;\n\tstatus?: { dnsRecords: DomainDnsRecord[] };\n}\n\n/**\n * Picks the CNAME record Railway expects for routing traffic to a custom domain out\n * of its full DNS record list (which also includes e.g. an ACME challenge TXT record).\n */\nfunction extractCnameTarget(\n\tdnsRecords: DomainDnsRecord[] | undefined,\n): string | undefined {\n\treturn dnsRecords?.find(\n\t\t(record) =>\n\t\t\trecord.recordType === \"DNS_RECORD_TYPE_CNAME\" &&\n\t\t\trecord.purpose === \"DNS_RECORD_PURPOSE_TRAFFIC_ROUTE\",\n\t)?.requiredValue;\n}\n\nconst DOMAIN_STATUS_FIELDS = `\n status {\n dnsRecords { recordType purpose requiredValue }\n }\n`;\n\nconst SERVICE_DOMAINS_QUERY = `\n query($projectId: String!, $serviceId: String!, $environmentId: String!) {\n domains(\n projectId: $projectId\n serviceId: $serviceId\n environmentId: $environmentId\n ) {\n serviceDomains { id domain }\n customDomains { id domain ${DOMAIN_STATUS_FIELDS} }\n }\n }\n`;\n\nconst SERVICE_DOMAIN_CREATE = `\n mutation($input: ServiceDomainCreateInput!) {\n serviceDomainCreate(input: $input) { id domain }\n }\n`;\n\nconst CUSTOM_DOMAIN_CREATE = `\n mutation($input: CustomDomainCreateInput!) {\n customDomainCreate(input: $input) { id domain ${DOMAIN_STATUS_FIELDS} }\n }\n`;\n\nconst CUSTOM_DOMAIN_DELETE = `\n mutation($id: String!) { customDomainDelete(id: $id) }\n`;\n\nconst SERVICE_DOMAIN_DELETE = `\n mutation($id: String!) { serviceDomainDelete(id: $id) }\n`;\n\n/**\n * Queries all existing domains (service and custom) for a Railway service.\n */\nasync function findExistingDomains(\n\tclient: RailwayClient,\n\tprojectId: string,\n\tserviceId: string,\n\tenvironmentId: string,\n): Promise<{\n\tserviceDomains: Array<{ id: string; domain: string }>;\n\tcustomDomains: CustomDomainEntry[];\n}> {\n\tconst result = await client.query<{\n\t\tdomains: {\n\t\t\tserviceDomains: Array<{ id: string; domain: string }>;\n\t\t\tcustomDomains: CustomDomainEntry[];\n\t\t};\n\t}>(SERVICE_DOMAINS_QUERY, { projectId, serviceId, environmentId });\n\n\treturn result.domains;\n}\n\n/**\n * Dynamic provider implementing CRUD for Railway domains.\n *\n * Uses adopt-or-create: queries existing domains before creating new ones.\n * Uses the FQDN as the Pulumi resource ID.\n *\n * @internal Exported only for unit testing; not part of the public API surface.\n */\nexport class RailwayDomainResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(\n\t\tinputs: RailwayDomainInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new RailwayClient(inputs.token);\n\n\t\tconst existing = await findExistingDomains(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.serviceId,\n\t\t\tinputs.environmentId,\n\t\t);\n\n\t\tif (inputs.customDomain) {\n\t\t\tconst found = existing.customDomains.find(\n\t\t\t\t(d) => d.domain === inputs.customDomain,\n\t\t\t);\n\n\t\t\tif (found) {\n\t\t\t\tpulumi.log.info(`Adopting existing custom domain \"${found.domain}\"`);\n\n\t\t\t\treturn {\n\t\t\t\t\tid: found.domain,\n\t\t\t\t\touts: {\n\t\t\t\t\t\t...inputs,\n\t\t\t\t\t\tdomainId: found.id,\n\t\t\t\t\t\tfqdn: found.domain,\n\t\t\t\t\t\tcnameTarget: extractCnameTarget(found.status?.dnsRecords),\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst result = await client.query<{\n\t\t\t\tcustomDomainCreate: CustomDomainEntry;\n\t\t\t}>(CUSTOM_DOMAIN_CREATE, {\n\t\t\t\tinput: {\n\t\t\t\t\tprojectId: inputs.projectId,\n\t\t\t\t\tserviceId: inputs.serviceId,\n\t\t\t\t\tenvironmentId: inputs.environmentId,\n\t\t\t\t\tdomain: inputs.customDomain,\n\t\t\t\t},\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\tid: result.customDomainCreate.domain,\n\t\t\t\touts: {\n\t\t\t\t\t...inputs,\n\t\t\t\t\tdomainId: result.customDomainCreate.id,\n\t\t\t\t\tfqdn: result.customDomainCreate.domain,\n\t\t\t\t\tcnameTarget: extractCnameTarget(\n\t\t\t\t\t\tresult.customDomainCreate.status?.dnsRecords,\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tif (existing.serviceDomains.length > 0) {\n\t\t\tconst found = existing.serviceDomains[0];\n\n\t\t\tpulumi.log.info(`Adopting existing service domain \"${found.domain}\"`);\n\n\t\t\treturn {\n\t\t\t\tid: found.domain,\n\t\t\t\touts: { ...inputs, domainId: found.id, fqdn: found.domain },\n\t\t\t};\n\t\t}\n\n\t\tconst result = await client.query<{\n\t\t\tserviceDomainCreate: { id: string; domain: string };\n\t\t}>(SERVICE_DOMAIN_CREATE, {\n\t\t\tinput: {\n\t\t\t\tserviceId: inputs.serviceId,\n\t\t\t\tenvironmentId: inputs.environmentId,\n\t\t\t},\n\t\t});\n\n\t\treturn {\n\t\t\tid: result.serviceDomainCreate.domain,\n\t\t\touts: {\n\t\t\t\t...inputs,\n\t\t\t\tdomainId: result.serviceDomainCreate.id,\n\t\t\t\tfqdn: result.serviceDomainCreate.domain,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync read(\n\t\t_id: string,\n\t\tprops: RailwayDomainOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\tconst existing = await findExistingDomains(\n\t\t\tclient,\n\t\t\tprops.projectId,\n\t\t\tprops.serviceId,\n\t\t\tprops.environmentId,\n\t\t);\n\n\t\tif (props.customDomain) {\n\t\t\tconst found = existing.customDomains.find(\n\t\t\t\t(d) => d.domain === props.customDomain,\n\t\t\t);\n\n\t\t\tif (!found) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Custom domain \"${props.customDomain}\" not found during refresh`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tid: found.domain,\n\t\t\t\tprops: {\n\t\t\t\t\t...props,\n\t\t\t\t\tdomainId: found.id,\n\t\t\t\t\tfqdn: found.domain,\n\t\t\t\t\tcnameTarget: extractCnameTarget(found.status?.dnsRecords),\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tif (existing.serviceDomains.length > 0) {\n\t\t\tconst found = existing.serviceDomains[0];\n\n\t\t\treturn {\n\t\t\t\tid: found.domain,\n\t\t\t\tprops: { ...props, domainId: found.id, fqdn: found.domain },\n\t\t\t};\n\t\t}\n\n\t\tthrow new Error(\"Railway domain not found during refresh\");\n\t}\n\n\tasync delete(_id: string, props: RailwayDomainOutputs): Promise<void> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\tconst mutation = props.customDomain\n\t\t\t? CUSTOM_DOMAIN_DELETE\n\t\t\t: SERVICE_DOMAIN_DELETE;\n\n\t\ttry {\n\t\t\tawait client.query(mutation, { id: props.domainId });\n\t\t} catch {\n\t\t\tpulumi.log.warn(\n\t\t\t\t\"Failed to delete Railway domain (may already be deleted)\",\n\t\t\t);\n\t\t}\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: RailwayDomainOutputs,\n\t\tnews: RailwayDomainInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.serviceId !== news.serviceId) {\n\t\t\treplaces.push(\"serviceId\");\n\t\t}\n\n\t\tif (olds.customDomain !== news.customDomain) {\n\t\t\treplaces.push(\"customDomain\");\n\t\t}\n\n\t\tif (olds.environmentId !== news.environmentId) {\n\t\t\treplaces.push(\"environmentId\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass RailwayDomainResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly fqdn: pulumi.Output<string>;\n\tpublic declare readonly cnameTarget: pulumi.Output<string | undefined>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tserviceId: pulumi.Input<string>;\n\t\t\tenvironmentId: pulumi.Input<string>;\n\t\t\tcustomDomain?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew RailwayDomainResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, domainId: undefined, fqdn: undefined, cnameTarget: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for RailwayDomain — replaces Pulumi's native `provider` field. */\ntype RailwayDomainOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Railway authentication context. */\n\tprovider: RailwayProvider;\n\n\t/** Railway project context. */\n\tproject: RailwayProject;\n\n\t/** Railway environment context. */\n\tenvironment: RailwayEnvironment;\n\n\t/** Railway service context. */\n\tservice: RailwayService;\n};\n\n/** Args for RailwayDomain. */\nexport interface RailwayDomainArgs {\n\t/** Custom domain FQDN. Omit to create an auto-generated Railway service domain. */\n\tcustomDomain?: pulumi.Input<string>;\n}\n\n/**\n * Manages a Railway domain (service or custom) with adopt-or-create semantics.\n *\n * A service can carry more than one custom domain — declare one `RailwayDomain` per\n * domain; each instance adopts, creates, and deletes only its own domain.\n *\n * @example\n * ```typescript\n * const apiDomain = new RailwayDomain(\"api-domain\", { customDomain: \"api.example.com\" }, {\n * provider, project, environment, service,\n * });\n * const wwwDomain = new RailwayDomain(\"www-domain\", { customDomain: \"www.example.com\" }, {\n * provider, project, environment, service,\n * });\n *\n * // Point each domain's DNS CNAME at its own target.\n * const apiCnameTarget = apiDomain.cnameTarget;\n * const wwwCnameTarget = wwwDomain.cnameTarget;\n * ```\n */\nexport class RailwayDomain extends pulumi.ComponentResource {\n\t/** Fully qualified domain name. */\n\tpublic readonly fqdn: pulumi.Output<string>;\n\n\t/**\n\t * The value to CNAME `customDomain` to. Only set for custom domains — `undefined`\n\t * for a service's auto-generated `*.up.railway.app` domain, or if Railway hasn't\n\t * returned a traffic-routing CNAME record for it yet.\n\t */\n\tpublic readonly cnameTarget: pulumi.Output<string | undefined>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: RailwayDomainArgs,\n\t\topts: RailwayDomainOptions,\n\t) {\n\t\tconst { provider, project, environment, service, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:railway:Domain\", name, {}, pulumiOpts);\n\n\t\tconst resource = new RailwayDomainResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tprojectId: project.id,\n\t\t\t\tserviceId: service.id,\n\t\t\t\tenvironmentId: environment.id,\n\t\t\t\tcustomDomain: args.customDomain,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.fqdn = resource.fqdn;\n\t\tthis.cnameTarget = resource.cnameTarget;\n\n\t\tthis.registerOutputs({ fqdn: this.fqdn, cnameTarget: this.cnameTarget });\n\t}\n}\n"],"mappings":";;;;;;;;;;;AA2DA,SAAS,mBACR,YACqB;CACrB,OAAO,YAAY,MACjB,WACA,OAAO,eAAe,2BACtB,OAAO,YAAY,kCACrB,GAAG;AACJ;AAEA,MAAM,uBAAuB;;;;;AAM7B,MAAM,wBAAwB;;;;;;;;kCAQI,qBAAqB;;;;AAKvD,MAAM,wBAAwB;;;;;AAM9B,MAAM,uBAAuB;;oDAEuB,qBAAqB;;;AAIzE,MAAM,uBAAuB;;;AAI7B,MAAM,wBAAwB;;;;;;AAO9B,eAAe,oBACd,QACA,WACA,WACA,eAIE;CAQF,QAAO,MAPc,OAAO,MAKzB,uBAAuB;EAAE;EAAW;EAAW;CAAc,CAAC,GAEnD;AACf;;;;;;;;;AAUA,IAAa,gCAAb,MAEA;CACC,MAAM,OACL,QACuC;EACvC,MAAM,SAAS,IAAIA,qCAAc,OAAO,KAAK;EAE7C,MAAM,WAAW,MAAM,oBACtB,QACA,OAAO,WACP,OAAO,WACP,OAAO,aACR;EAEA,IAAI,OAAO,cAAc;GACxB,MAAM,QAAQ,SAAS,cAAc,MACnC,MAAM,EAAE,WAAW,OAAO,YAC5B;GAEA,IAAI,OAAO;IACV,eAAO,IAAI,KAAK,oCAAoC,MAAM,OAAO,EAAE;IAEnE,OAAO;KACN,IAAI,MAAM;KACV,MAAM;MACL,GAAG;MACH,UAAU,MAAM;MAChB,MAAM,MAAM;MACZ,aAAa,mBAAmB,MAAM,QAAQ,UAAU;KACzD;IACD;GACD;GAEA,MAAM,SAAS,MAAM,OAAO,MAEzB,sBAAsB,EACxB,OAAO;IACN,WAAW,OAAO;IAClB,WAAW,OAAO;IAClB,eAAe,OAAO;IACtB,QAAQ,OAAO;GAChB,EACD,CAAC;GAED,OAAO;IACN,IAAI,OAAO,mBAAmB;IAC9B,MAAM;KACL,GAAG;KACH,UAAU,OAAO,mBAAmB;KACpC,MAAM,OAAO,mBAAmB;KAChC,aAAa,mBACZ,OAAO,mBAAmB,QAAQ,UACnC;IACD;GACD;EACD;EAEA,IAAI,SAAS,eAAe,SAAS,GAAG;GACvC,MAAM,QAAQ,SAAS,eAAe;GAEtC,eAAO,IAAI,KAAK,qCAAqC,MAAM,OAAO,EAAE;GAEpE,OAAO;IACN,IAAI,MAAM;IACV,MAAM;KAAE,GAAG;KAAQ,UAAU,MAAM;KAAI,MAAM,MAAM;IAAO;GAC3D;EACD;EAEA,MAAM,SAAS,MAAM,OAAO,MAEzB,uBAAuB,EACzB,OAAO;GACN,WAAW,OAAO;GAClB,eAAe,OAAO;EACvB,EACD,CAAC;EAED,OAAO;GACN,IAAI,OAAO,oBAAoB;GAC/B,MAAM;IACL,GAAG;IACH,UAAU,OAAO,oBAAoB;IACrC,MAAM,OAAO,oBAAoB;GAClC;EACD;CACD;CAEA,MAAM,KACL,KACA,OACqC;EAGrC,MAAM,WAAW,MAAM,oBACtB,IAHkBA,qCAAc,MAAM,KAGjC,GACL,MAAM,WACN,MAAM,WACN,MAAM,aACP;EAEA,IAAI,MAAM,cAAc;GACvB,MAAM,QAAQ,SAAS,cAAc,MACnC,MAAM,EAAE,WAAW,MAAM,YAC3B;GAEA,IAAI,CAAC,OACJ,MAAM,IAAI,MACT,kBAAkB,MAAM,aAAa,2BACtC;GAGD,OAAO;IACN,IAAI,MAAM;IACV,OAAO;KACN,GAAG;KACH,UAAU,MAAM;KAChB,MAAM,MAAM;KACZ,aAAa,mBAAmB,MAAM,QAAQ,UAAU;IACzD;GACD;EACD;EAEA,IAAI,SAAS,eAAe,SAAS,GAAG;GACvC,MAAM,QAAQ,SAAS,eAAe;GAEtC,OAAO;IACN,IAAI,MAAM;IACV,OAAO;KAAE,GAAG;KAAO,UAAU,MAAM;KAAI,MAAM,MAAM;IAAO;GAC3D;EACD;EAEA,MAAM,IAAI,MAAM,yCAAyC;CAC1D;CAEA,MAAM,OAAO,KAAa,OAA4C;EACrE,MAAM,SAAS,IAAIA,qCAAc,MAAM,KAAK;EAE5C,MAAM,WAAW,MAAM,eACpB,uBACA;EAEH,IAAI;GACH,MAAM,OAAO,MAAM,UAAU,EAAE,IAAI,MAAM,SAAS,CAAC;EACpD,QAAQ;GACP,eAAO,IAAI,KACV,0DACD;EACD;CACD;CAEA,MAAM,KACL,KACA,MACA,MACqC;EACrC,MAAM,WAAqB,CAAC;EAE5B,IAAI,KAAK,cAAc,KAAK,WAC3B,SAAS,KAAK,WAAW;EAG1B,IAAI,KAAK,iBAAiB,KAAK,cAC9B,SAAS,KAAK,cAAc;EAG7B,IAAI,KAAK,kBAAkB,KAAK,eAC/B,SAAS,KAAK,eAAe;EAG9B,OAAO;GACN,SAAS,SAAS,SAAS;GAC3B;GACA,qBAAqB;EACtB;CACD;AACD;;AAGA,IAAM,wBAAN,cAAoCC,eAAO,QAAQ,SAAS;CAI3D,YACC,MACA,MAOA,MACC;EACD,MACC,IAAI,8BAA8B,GAClC,MACA;GAAE,GAAG;GAAM,UAAU;GAAW,MAAM;GAAW,aAAa;EAAU,GACxE,IACD;CACD;AACD;;;;;;;;;;;;;;;;;;;;;AA8CA,IAAa,gBAAb,cAAmCA,eAAO,kBAAkB;CAW3D,YACC,MACA,MACA,MACC;EACD,MAAM,EAAE,UAAU,SAAS,aAAa,SAAS,GAAG,eAAe;EAEnE,MAAM,6BAA6B,MAAM,CAAC,GAAG,UAAU;EAEvD,MAAM,WAAW,IAAI,sBACpB,GAAG,KAAK,YACR;GACC,OAAO,SAAS;GAChB,WAAW,QAAQ;GACnB,WAAW,QAAQ;GACnB,eAAe,YAAY;GAC3B,cAAc,KAAK;EACpB,GACA,EAAE,QAAQ,KAAK,CAChB;EAEA,KAAK,OAAO,SAAS;EACrB,KAAK,cAAc,SAAS;EAE5B,KAAK,gBAAgB;GAAE,MAAM,KAAK;GAAM,aAAa,KAAK;EAAY,CAAC;CACxE;AACD"}
@@ -6,6 +6,46 @@ import { RailwayService } from "./service.cjs";
6
6
  import * as pulumi from "@pulumi/pulumi";
7
7
 
8
8
  //#region src/railway/domain.d.ts
9
+ /** Resolved inputs for the Railway domain dynamic provider. */
10
+ interface RailwayDomainInputs {
11
+ /** Railway API bearer token. */
12
+ token: string;
13
+ /** Railway project UUID. */
14
+ projectId: string;
15
+ /** Railway service UUID to attach the domain to. */
16
+ serviceId: string;
17
+ /** Railway environment UUID (e.g. production). */
18
+ environmentId: string;
19
+ /** Custom domain FQDN (e.g. `"api.example.com"`). Omit for auto-generated Railway domain. */
20
+ customDomain?: string;
21
+ }
22
+ /** Persisted state for the Railway domain, extending inputs with Railway-assigned identifiers. */
23
+ interface RailwayDomainOutputs extends RailwayDomainInputs {
24
+ /** Railway-assigned domain UUID (used for deletion API calls). */
25
+ domainId: string;
26
+ /** Fully qualified domain name (e.g. `"api-production-abc.up.railway.app"`). */
27
+ fqdn: string;
28
+ /**
29
+ * The value to CNAME `customDomain` to. Only present for custom domains — a
30
+ * service's auto-generated `*.up.railway.app` domain needs no DNS record of its
31
+ * own. `undefined` if Railway hasn't returned a traffic-routing CNAME record yet.
32
+ */
33
+ cnameTarget?: string;
34
+ }
35
+ /**
36
+ * Dynamic provider implementing CRUD for Railway domains.
37
+ *
38
+ * Uses adopt-or-create: queries existing domains before creating new ones.
39
+ * Uses the FQDN as the Pulumi resource ID.
40
+ *
41
+ * @internal Exported only for unit testing; not part of the public API surface.
42
+ */
43
+ declare class RailwayDomainResourceProvider implements pulumi.dynamic.ResourceProvider {
44
+ create(inputs: RailwayDomainInputs): Promise<pulumi.dynamic.CreateResult>;
45
+ read(_id: string, props: RailwayDomainOutputs): Promise<pulumi.dynamic.ReadResult>;
46
+ delete(_id: string, props: RailwayDomainOutputs): Promise<void>;
47
+ diff(_id: string, olds: RailwayDomainOutputs, news: RailwayDomainInputs): Promise<pulumi.dynamic.DiffResult>;
48
+ }
9
49
  /** Options type for RailwayDomain — replaces Pulumi's native `provider` field. */
10
50
  type RailwayDomainOptions = Omit<pulumi.ComponentResourceOptions, "provider"> & {
11
51
  /** Railway authentication context. */provider: RailwayProvider; /** Railway project context. */
@@ -21,20 +61,34 @@ interface RailwayDomainArgs {
21
61
  /**
22
62
  * Manages a Railway domain (service or custom) with adopt-or-create semantics.
23
63
  *
64
+ * A service can carry more than one custom domain — declare one `RailwayDomain` per
65
+ * domain; each instance adopts, creates, and deletes only its own domain.
66
+ *
24
67
  * @example
25
68
  * ```typescript
26
- * const domain = new RailwayDomain("api-domain", {}, {
69
+ * const apiDomain = new RailwayDomain("api-domain", { customDomain: "api.example.com" }, {
70
+ * provider, project, environment, service,
71
+ * });
72
+ * const wwwDomain = new RailwayDomain("www-domain", { customDomain: "www.example.com" }, {
27
73
  * provider, project, environment, service,
28
74
  * });
29
75
  *
30
- * const url = pulumi.interpolate`https://${domain.fqdn}`;
76
+ * // Point each domain's DNS CNAME at its own target.
77
+ * const apiCnameTarget = apiDomain.cnameTarget;
78
+ * const wwwCnameTarget = wwwDomain.cnameTarget;
31
79
  * ```
32
80
  */
33
81
  declare class RailwayDomain extends pulumi.ComponentResource {
34
82
  /** Fully qualified domain name. */
35
83
  readonly fqdn: pulumi.Output<string>;
84
+ /**
85
+ * The value to CNAME `customDomain` to. Only set for custom domains — `undefined`
86
+ * for a service's auto-generated `*.up.railway.app` domain, or if Railway hasn't
87
+ * returned a traffic-routing CNAME record for it yet.
88
+ */
89
+ readonly cnameTarget: pulumi.Output<string | undefined>;
36
90
  constructor(name: string, args: RailwayDomainArgs, opts: RailwayDomainOptions);
37
91
  }
38
92
  //#endregion
39
- export { RailwayDomain, RailwayDomainArgs };
93
+ export { RailwayDomain, RailwayDomainArgs, RailwayDomainResourceProvider };
40
94
  //# sourceMappingURL=domain.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"domain.d.cts","names":[],"sources":["../../src/railway/domain.ts"],"mappings":";;;;;;;;;KA2RK,oBAAA,GAAuB,IAAA,CAC3B,MAAA,CAAO,wBAAA;wCAIP,QAAA,EAAU,eAAA,EALc;EAQxB,OAAA,EAAS,cAAA,EAPT;EAUA,WAAA,EAAa,kBAAA,EANH;EASV,OAAA,EAAS,cAAA;AAAA;;UAIO,iBAAA;EAJO;EAMvB,YAAA,GAAe,MAAA,CAAO,KAAK;AAAA;;;;;;;;;;;AANJ;AAIxB;cAiBa,aAAA,SAAsB,MAAA,CAAO,iBAAA;;WAEzB,IAAA,EAAM,MAAA,CAAO,MAAA;cAG5B,IAAA,UACA,IAAA,EAAM,iBAAA,EACN,IAAA,EAAM,oBAAA;AAAA"}
1
+ {"version":3,"file":"domain.d.cts","names":[],"sources":["../../src/railway/domain.ts"],"mappings":";;;;;;;;;UAQU,mBAAA;;EAET,KAAA;EAF4B;EAK5B,SAAA;EAL4B;EAQ5B,SAAA;EAHA;EAMA,aAAA;EAAA;EAGA,YAAA;AAAA;AAAY;AAAA,UAIH,oBAAA,SAA6B,mBAAmB;EAA3B;EAE9B,QAAA;EAFyD;EAKzD,IAAA;EAHA;;;;AAUW;EAAX,WAAA;AAAA;;;;;;;;;cAoGY,6BAAA,YACD,MAAA,CAAO,OAAA,CAAQ,gBAAA;EAEpB,MAAA,CACL,MAAA,EAAQ,mBAAA,GACN,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EAmFpB,IAAA,CACL,GAAA,UACA,KAAA,EAAO,oBAAA,GACL,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;EA4CpB,MAAA,CAAO,GAAA,UAAa,KAAA,EAAO,oBAAA,GAAuB,OAAA;EAgBlD,IAAA,CACL,GAAA,UACA,IAAA,EAAM,oBAAA,EACN,IAAA,EAAM,mBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;AAAA;;KAiDtB,oBAAA,GAAuB,IAAA,CAC3B,MAAA,CAAO,wBAAA;EA5MI,sCAgNX,QAAA,EAAU,eAAA,EAhNgB;EAmN1B,OAAA,EAAS,cAAA,EAhNA;EAmNT,WAAA,EAAa,kBAAA,EAlNV;EAqNH,OAAA,EAAS,cAAA;AAAA;;UAIO,iBAAA;EArIf;EAuID,YAAA,GAAe,MAAA,CAAO,KAAK;AAAA;;;;;;;;;;;;;;;;;;;;;cAuBf,aAAA,SAAsB,MAAA,CAAO,iBAAA;EA3CrC;EAAA,SA6CY,IAAA,EAAM,MAAA,CAAO,MAAA;;;;;;WAOb,WAAA,EAAa,MAAA,CAAO,MAAA;cAGnC,IAAA,UACA,IAAA,EAAM,iBAAA,EACN,IAAA,EAAM,oBAAA;AAAA"}
@@ -6,6 +6,46 @@ import { RailwayService } from "./service.mjs";
6
6
  import * as pulumi from "@pulumi/pulumi";
7
7
 
8
8
  //#region src/railway/domain.d.ts
9
+ /** Resolved inputs for the Railway domain dynamic provider. */
10
+ interface RailwayDomainInputs {
11
+ /** Railway API bearer token. */
12
+ token: string;
13
+ /** Railway project UUID. */
14
+ projectId: string;
15
+ /** Railway service UUID to attach the domain to. */
16
+ serviceId: string;
17
+ /** Railway environment UUID (e.g. production). */
18
+ environmentId: string;
19
+ /** Custom domain FQDN (e.g. `"api.example.com"`). Omit for auto-generated Railway domain. */
20
+ customDomain?: string;
21
+ }
22
+ /** Persisted state for the Railway domain, extending inputs with Railway-assigned identifiers. */
23
+ interface RailwayDomainOutputs extends RailwayDomainInputs {
24
+ /** Railway-assigned domain UUID (used for deletion API calls). */
25
+ domainId: string;
26
+ /** Fully qualified domain name (e.g. `"api-production-abc.up.railway.app"`). */
27
+ fqdn: string;
28
+ /**
29
+ * The value to CNAME `customDomain` to. Only present for custom domains — a
30
+ * service's auto-generated `*.up.railway.app` domain needs no DNS record of its
31
+ * own. `undefined` if Railway hasn't returned a traffic-routing CNAME record yet.
32
+ */
33
+ cnameTarget?: string;
34
+ }
35
+ /**
36
+ * Dynamic provider implementing CRUD for Railway domains.
37
+ *
38
+ * Uses adopt-or-create: queries existing domains before creating new ones.
39
+ * Uses the FQDN as the Pulumi resource ID.
40
+ *
41
+ * @internal Exported only for unit testing; not part of the public API surface.
42
+ */
43
+ declare class RailwayDomainResourceProvider implements pulumi.dynamic.ResourceProvider {
44
+ create(inputs: RailwayDomainInputs): Promise<pulumi.dynamic.CreateResult>;
45
+ read(_id: string, props: RailwayDomainOutputs): Promise<pulumi.dynamic.ReadResult>;
46
+ delete(_id: string, props: RailwayDomainOutputs): Promise<void>;
47
+ diff(_id: string, olds: RailwayDomainOutputs, news: RailwayDomainInputs): Promise<pulumi.dynamic.DiffResult>;
48
+ }
9
49
  /** Options type for RailwayDomain — replaces Pulumi's native `provider` field. */
10
50
  type RailwayDomainOptions = Omit<pulumi.ComponentResourceOptions, "provider"> & {
11
51
  /** Railway authentication context. */provider: RailwayProvider; /** Railway project context. */
@@ -21,20 +61,34 @@ interface RailwayDomainArgs {
21
61
  /**
22
62
  * Manages a Railway domain (service or custom) with adopt-or-create semantics.
23
63
  *
64
+ * A service can carry more than one custom domain — declare one `RailwayDomain` per
65
+ * domain; each instance adopts, creates, and deletes only its own domain.
66
+ *
24
67
  * @example
25
68
  * ```typescript
26
- * const domain = new RailwayDomain("api-domain", {}, {
69
+ * const apiDomain = new RailwayDomain("api-domain", { customDomain: "api.example.com" }, {
70
+ * provider, project, environment, service,
71
+ * });
72
+ * const wwwDomain = new RailwayDomain("www-domain", { customDomain: "www.example.com" }, {
27
73
  * provider, project, environment, service,
28
74
  * });
29
75
  *
30
- * const url = pulumi.interpolate`https://${domain.fqdn}`;
76
+ * // Point each domain's DNS CNAME at its own target.
77
+ * const apiCnameTarget = apiDomain.cnameTarget;
78
+ * const wwwCnameTarget = wwwDomain.cnameTarget;
31
79
  * ```
32
80
  */
33
81
  declare class RailwayDomain extends pulumi.ComponentResource {
34
82
  /** Fully qualified domain name. */
35
83
  readonly fqdn: pulumi.Output<string>;
84
+ /**
85
+ * The value to CNAME `customDomain` to. Only set for custom domains — `undefined`
86
+ * for a service's auto-generated `*.up.railway.app` domain, or if Railway hasn't
87
+ * returned a traffic-routing CNAME record for it yet.
88
+ */
89
+ readonly cnameTarget: pulumi.Output<string | undefined>;
36
90
  constructor(name: string, args: RailwayDomainArgs, opts: RailwayDomainOptions);
37
91
  }
38
92
  //#endregion
39
- export { RailwayDomain, RailwayDomainArgs };
93
+ export { RailwayDomain, RailwayDomainArgs, RailwayDomainResourceProvider };
40
94
  //# sourceMappingURL=domain.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"domain.d.mts","names":[],"sources":["../../src/railway/domain.ts"],"mappings":";;;;;;;;;KA2RK,oBAAA,GAAuB,IAAA,CAC3B,MAAA,CAAO,wBAAA;wCAIP,QAAA,EAAU,eAAA,EALc;EAQxB,OAAA,EAAS,cAAA,EAPT;EAUA,WAAA,EAAa,kBAAA,EANH;EASV,OAAA,EAAS,cAAA;AAAA;;UAIO,iBAAA;EAJO;EAMvB,YAAA,GAAe,MAAA,CAAO,KAAK;AAAA;;;;;;;;;;;AANJ;AAIxB;cAiBa,aAAA,SAAsB,MAAA,CAAO,iBAAA;;WAEzB,IAAA,EAAM,MAAA,CAAO,MAAA;cAG5B,IAAA,UACA,IAAA,EAAM,iBAAA,EACN,IAAA,EAAM,oBAAA;AAAA"}
1
+ {"version":3,"file":"domain.d.mts","names":[],"sources":["../../src/railway/domain.ts"],"mappings":";;;;;;;;;UAQU,mBAAA;;EAET,KAAA;EAF4B;EAK5B,SAAA;EAL4B;EAQ5B,SAAA;EAHA;EAMA,aAAA;EAAA;EAGA,YAAA;AAAA;AAAY;AAAA,UAIH,oBAAA,SAA6B,mBAAmB;EAA3B;EAE9B,QAAA;EAFyD;EAKzD,IAAA;EAHA;;;;AAUW;EAAX,WAAA;AAAA;;;;;;;;;cAoGY,6BAAA,YACD,MAAA,CAAO,OAAA,CAAQ,gBAAA;EAEpB,MAAA,CACL,MAAA,EAAQ,mBAAA,GACN,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,YAAA;EAmFpB,IAAA,CACL,GAAA,UACA,KAAA,EAAO,oBAAA,GACL,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;EA4CpB,MAAA,CAAO,GAAA,UAAa,KAAA,EAAO,oBAAA,GAAuB,OAAA;EAgBlD,IAAA,CACL,GAAA,UACA,IAAA,EAAM,oBAAA,EACN,IAAA,EAAM,mBAAA,GACJ,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,UAAA;AAAA;;KAiDtB,oBAAA,GAAuB,IAAA,CAC3B,MAAA,CAAO,wBAAA;EA5MI,sCAgNX,QAAA,EAAU,eAAA,EAhNgB;EAmN1B,OAAA,EAAS,cAAA,EAhNA;EAmNT,WAAA,EAAa,kBAAA,EAlNV;EAqNH,OAAA,EAAS,cAAA;AAAA;;UAIO,iBAAA;EArIf;EAuID,YAAA,GAAe,MAAA,CAAO,KAAK;AAAA;;;;;;;;;;;;;;;;;;;;;cAuBf,aAAA,SAAsB,MAAA,CAAO,iBAAA;EA3CrC;EAAA,SA6CY,IAAA,EAAM,MAAA,CAAO,MAAA;;;;;;WAOb,WAAA,EAAa,MAAA,CAAO,MAAA;cAGnC,IAAA,UACA,IAAA,EAAM,iBAAA,EACN,IAAA,EAAM,oBAAA;AAAA"}
@@ -3,6 +3,18 @@ import { RailwayClient } from "./client.mjs";
3
3
  import * as pulumi from "@pulumi/pulumi";
4
4
 
5
5
  //#region src/railway/domain.ts
6
+ /**
7
+ * Picks the CNAME record Railway expects for routing traffic to a custom domain out
8
+ * of its full DNS record list (which also includes e.g. an ACME challenge TXT record).
9
+ */
10
+ function extractCnameTarget(dnsRecords) {
11
+ return dnsRecords?.find((record) => record.recordType === "DNS_RECORD_TYPE_CNAME" && record.purpose === "DNS_RECORD_PURPOSE_TRAFFIC_ROUTE")?.requiredValue;
12
+ }
13
+ const DOMAIN_STATUS_FIELDS = `
14
+ status {
15
+ dnsRecords { recordType purpose requiredValue }
16
+ }
17
+ `;
6
18
  const SERVICE_DOMAINS_QUERY = `
7
19
  query($projectId: String!, $serviceId: String!, $environmentId: String!) {
8
20
  domains(
@@ -11,7 +23,7 @@ const SERVICE_DOMAINS_QUERY = `
11
23
  environmentId: $environmentId
12
24
  ) {
13
25
  serviceDomains { id domain }
14
- customDomains { id domain }
26
+ customDomains { id domain ${DOMAIN_STATUS_FIELDS} }
15
27
  }
16
28
  }
17
29
  `;
@@ -22,7 +34,7 @@ const SERVICE_DOMAIN_CREATE = `
22
34
  `;
23
35
  const CUSTOM_DOMAIN_CREATE = `
24
36
  mutation($input: CustomDomainCreateInput!) {
25
- customDomainCreate(input: $input) { id domain }
37
+ customDomainCreate(input: $input) { id domain ${DOMAIN_STATUS_FIELDS} }
26
38
  }
27
39
  `;
28
40
  const CUSTOM_DOMAIN_DELETE = `
@@ -46,6 +58,8 @@ async function findExistingDomains(client, projectId, serviceId, environmentId)
46
58
  *
47
59
  * Uses adopt-or-create: queries existing domains before creating new ones.
48
60
  * Uses the FQDN as the Pulumi resource ID.
61
+ *
62
+ * @internal Exported only for unit testing; not part of the public API surface.
49
63
  */
50
64
  var RailwayDomainResourceProvider = class {
51
65
  async create(inputs) {
@@ -60,7 +74,8 @@ var RailwayDomainResourceProvider = class {
60
74
  outs: {
61
75
  ...inputs,
62
76
  domainId: found.id,
63
- fqdn: found.domain
77
+ fqdn: found.domain,
78
+ cnameTarget: extractCnameTarget(found.status?.dnsRecords)
64
79
  }
65
80
  };
66
81
  }
@@ -75,7 +90,8 @@ var RailwayDomainResourceProvider = class {
75
90
  outs: {
76
91
  ...inputs,
77
92
  domainId: result.customDomainCreate.id,
78
- fqdn: result.customDomainCreate.domain
93
+ fqdn: result.customDomainCreate.domain,
94
+ cnameTarget: extractCnameTarget(result.customDomainCreate.status?.dnsRecords)
79
95
  }
80
96
  };
81
97
  }
@@ -114,7 +130,8 @@ var RailwayDomainResourceProvider = class {
114
130
  props: {
115
131
  ...props,
116
132
  domainId: found.id,
117
- fqdn: found.domain
133
+ fqdn: found.domain,
134
+ cnameTarget: extractCnameTarget(found.status?.dnsRecords)
118
135
  }
119
136
  };
120
137
  }
@@ -158,20 +175,29 @@ var RailwayDomainResource = class extends pulumi.dynamic.Resource {
158
175
  super(new RailwayDomainResourceProvider(), name, {
159
176
  ...args,
160
177
  domainId: void 0,
161
- fqdn: void 0
178
+ fqdn: void 0,
179
+ cnameTarget: void 0
162
180
  }, opts);
163
181
  }
164
182
  };
165
183
  /**
166
184
  * Manages a Railway domain (service or custom) with adopt-or-create semantics.
167
185
  *
186
+ * A service can carry more than one custom domain — declare one `RailwayDomain` per
187
+ * domain; each instance adopts, creates, and deletes only its own domain.
188
+ *
168
189
  * @example
169
190
  * ```typescript
170
- * const domain = new RailwayDomain("api-domain", {}, {
191
+ * const apiDomain = new RailwayDomain("api-domain", { customDomain: "api.example.com" }, {
192
+ * provider, project, environment, service,
193
+ * });
194
+ * const wwwDomain = new RailwayDomain("www-domain", { customDomain: "www.example.com" }, {
171
195
  * provider, project, environment, service,
172
196
  * });
173
197
  *
174
- * const url = pulumi.interpolate`https://${domain.fqdn}`;
198
+ * // Point each domain's DNS CNAME at its own target.
199
+ * const apiCnameTarget = apiDomain.cnameTarget;
200
+ * const wwwCnameTarget = wwwDomain.cnameTarget;
175
201
  * ```
176
202
  */
177
203
  var RailwayDomain = class extends pulumi.ComponentResource {
@@ -186,10 +212,14 @@ var RailwayDomain = class extends pulumi.ComponentResource {
186
212
  customDomain: args.customDomain
187
213
  }, { parent: this });
188
214
  this.fqdn = resource.fqdn;
189
- this.registerOutputs({ fqdn: this.fqdn });
215
+ this.cnameTarget = resource.cnameTarget;
216
+ this.registerOutputs({
217
+ fqdn: this.fqdn,
218
+ cnameTarget: this.cnameTarget
219
+ });
190
220
  }
191
221
  };
192
222
 
193
223
  //#endregion
194
- export { RailwayDomain };
224
+ export { RailwayDomain, RailwayDomainResourceProvider };
195
225
  //# sourceMappingURL=domain.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"domain.mjs","names":[],"sources":["../../src/railway/domain.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { RailwayClient } from \"./client\";\nimport type { RailwayEnvironment } from \"./environment\";\nimport type { RailwayProject } from \"./project\";\nimport type { RailwayProvider } from \"./provider\";\nimport type { RailwayService } from \"./service\";\n\n/** Resolved inputs for the Railway domain dynamic provider. */\ninterface RailwayDomainInputs {\n\t/** Railway API bearer token. */\n\ttoken: string;\n\n\t/** Railway project UUID. */\n\tprojectId: string;\n\n\t/** Railway service UUID to attach the domain to. */\n\tserviceId: string;\n\n\t/** Railway environment UUID (e.g. production). */\n\tenvironmentId: string;\n\n\t/** Custom domain FQDN (e.g. `\"api.example.com\"`). Omit for auto-generated Railway domain. */\n\tcustomDomain?: string;\n}\n\n/** Persisted state for the Railway domain, extending inputs with Railway-assigned identifiers. */\ninterface RailwayDomainOutputs extends RailwayDomainInputs {\n\t/** Railway-assigned domain UUID (used for deletion API calls). */\n\tdomainId: string;\n\n\t/** Fully qualified domain name (e.g. `\"api-production-abc.up.railway.app\"`). */\n\tfqdn: string;\n}\n\nconst SERVICE_DOMAINS_QUERY = `\n query($projectId: String!, $serviceId: String!, $environmentId: String!) {\n domains(\n projectId: $projectId\n serviceId: $serviceId\n environmentId: $environmentId\n ) {\n serviceDomains { id domain }\n customDomains { id domain }\n }\n }\n`;\n\nconst SERVICE_DOMAIN_CREATE = `\n mutation($input: ServiceDomainCreateInput!) {\n serviceDomainCreate(input: $input) { id domain }\n }\n`;\n\nconst CUSTOM_DOMAIN_CREATE = `\n mutation($input: CustomDomainCreateInput!) {\n customDomainCreate(input: $input) { id domain }\n }\n`;\n\nconst CUSTOM_DOMAIN_DELETE = `\n mutation($id: String!) { customDomainDelete(id: $id) }\n`;\n\nconst SERVICE_DOMAIN_DELETE = `\n mutation($id: String!) { serviceDomainDelete(id: $id) }\n`;\n\n/**\n * Queries all existing domains (service and custom) for a Railway service.\n */\nasync function findExistingDomains(\n\tclient: RailwayClient,\n\tprojectId: string,\n\tserviceId: string,\n\tenvironmentId: string,\n): Promise<{\n\tserviceDomains: Array<{ id: string; domain: string }>;\n\tcustomDomains: Array<{ id: string; domain: string }>;\n}> {\n\tconst result = await client.query<{\n\t\tdomains: {\n\t\t\tserviceDomains: Array<{ id: string; domain: string }>;\n\t\t\tcustomDomains: Array<{ id: string; domain: string }>;\n\t\t};\n\t}>(SERVICE_DOMAINS_QUERY, { projectId, serviceId, environmentId });\n\n\treturn result.domains;\n}\n\n/**\n * Dynamic provider implementing CRUD for Railway domains.\n *\n * Uses adopt-or-create: queries existing domains before creating new ones.\n * Uses the FQDN as the Pulumi resource ID.\n */\nclass RailwayDomainResourceProvider implements pulumi.dynamic.ResourceProvider {\n\tasync create(\n\t\tinputs: RailwayDomainInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new RailwayClient(inputs.token);\n\n\t\tconst existing = await findExistingDomains(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.serviceId,\n\t\t\tinputs.environmentId,\n\t\t);\n\n\t\tif (inputs.customDomain) {\n\t\t\tconst found = existing.customDomains.find(\n\t\t\t\t(d) => d.domain === inputs.customDomain,\n\t\t\t);\n\n\t\t\tif (found) {\n\t\t\t\tpulumi.log.info(`Adopting existing custom domain \"${found.domain}\"`);\n\n\t\t\t\treturn {\n\t\t\t\t\tid: found.domain,\n\t\t\t\t\touts: { ...inputs, domainId: found.id, fqdn: found.domain },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst result = await client.query<{\n\t\t\t\tcustomDomainCreate: { id: string; domain: string };\n\t\t\t}>(CUSTOM_DOMAIN_CREATE, {\n\t\t\t\tinput: {\n\t\t\t\t\tprojectId: inputs.projectId,\n\t\t\t\t\tserviceId: inputs.serviceId,\n\t\t\t\t\tenvironmentId: inputs.environmentId,\n\t\t\t\t\tdomain: inputs.customDomain,\n\t\t\t\t},\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\tid: result.customDomainCreate.domain,\n\t\t\t\touts: {\n\t\t\t\t\t...inputs,\n\t\t\t\t\tdomainId: result.customDomainCreate.id,\n\t\t\t\t\tfqdn: result.customDomainCreate.domain,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tif (existing.serviceDomains.length > 0) {\n\t\t\tconst found = existing.serviceDomains[0];\n\n\t\t\tpulumi.log.info(`Adopting existing service domain \"${found.domain}\"`);\n\n\t\t\treturn {\n\t\t\t\tid: found.domain,\n\t\t\t\touts: { ...inputs, domainId: found.id, fqdn: found.domain },\n\t\t\t};\n\t\t}\n\n\t\tconst result = await client.query<{\n\t\t\tserviceDomainCreate: { id: string; domain: string };\n\t\t}>(SERVICE_DOMAIN_CREATE, {\n\t\t\tinput: {\n\t\t\t\tserviceId: inputs.serviceId,\n\t\t\t\tenvironmentId: inputs.environmentId,\n\t\t\t},\n\t\t});\n\n\t\treturn {\n\t\t\tid: result.serviceDomainCreate.domain,\n\t\t\touts: {\n\t\t\t\t...inputs,\n\t\t\t\tdomainId: result.serviceDomainCreate.id,\n\t\t\t\tfqdn: result.serviceDomainCreate.domain,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync read(\n\t\t_id: string,\n\t\tprops: RailwayDomainOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\tconst existing = await findExistingDomains(\n\t\t\tclient,\n\t\t\tprops.projectId,\n\t\t\tprops.serviceId,\n\t\t\tprops.environmentId,\n\t\t);\n\n\t\tif (props.customDomain) {\n\t\t\tconst found = existing.customDomains.find(\n\t\t\t\t(d) => d.domain === props.customDomain,\n\t\t\t);\n\n\t\t\tif (!found) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Custom domain \"${props.customDomain}\" not found during refresh`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tid: found.domain,\n\t\t\t\tprops: { ...props, domainId: found.id, fqdn: found.domain },\n\t\t\t};\n\t\t}\n\n\t\tif (existing.serviceDomains.length > 0) {\n\t\t\tconst found = existing.serviceDomains[0];\n\n\t\t\treturn {\n\t\t\t\tid: found.domain,\n\t\t\t\tprops: { ...props, domainId: found.id, fqdn: found.domain },\n\t\t\t};\n\t\t}\n\n\t\tthrow new Error(\"Railway domain not found during refresh\");\n\t}\n\n\tasync delete(_id: string, props: RailwayDomainOutputs): Promise<void> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\tconst mutation = props.customDomain\n\t\t\t? CUSTOM_DOMAIN_DELETE\n\t\t\t: SERVICE_DOMAIN_DELETE;\n\n\t\ttry {\n\t\t\tawait client.query(mutation, { id: props.domainId });\n\t\t} catch {\n\t\t\tpulumi.log.warn(\n\t\t\t\t\"Failed to delete Railway domain (may already be deleted)\",\n\t\t\t);\n\t\t}\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: RailwayDomainOutputs,\n\t\tnews: RailwayDomainInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.serviceId !== news.serviceId) {\n\t\t\treplaces.push(\"serviceId\");\n\t\t}\n\n\t\tif (olds.customDomain !== news.customDomain) {\n\t\t\treplaces.push(\"customDomain\");\n\t\t}\n\n\t\tif (olds.environmentId !== news.environmentId) {\n\t\t\treplaces.push(\"environmentId\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass RailwayDomainResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly fqdn: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tserviceId: pulumi.Input<string>;\n\t\t\tenvironmentId: pulumi.Input<string>;\n\t\t\tcustomDomain?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew RailwayDomainResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, domainId: undefined, fqdn: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for RailwayDomain — replaces Pulumi's native `provider` field. */\ntype RailwayDomainOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Railway authentication context. */\n\tprovider: RailwayProvider;\n\n\t/** Railway project context. */\n\tproject: RailwayProject;\n\n\t/** Railway environment context. */\n\tenvironment: RailwayEnvironment;\n\n\t/** Railway service context. */\n\tservice: RailwayService;\n};\n\n/** Args for RailwayDomain. */\nexport interface RailwayDomainArgs {\n\t/** Custom domain FQDN. Omit to create an auto-generated Railway service domain. */\n\tcustomDomain?: pulumi.Input<string>;\n}\n\n/**\n * Manages a Railway domain (service or custom) with adopt-or-create semantics.\n *\n * @example\n * ```typescript\n * const domain = new RailwayDomain(\"api-domain\", {}, {\n * provider, project, environment, service,\n * });\n *\n * const url = pulumi.interpolate`https://${domain.fqdn}`;\n * ```\n */\nexport class RailwayDomain extends pulumi.ComponentResource {\n\t/** Fully qualified domain name. */\n\tpublic readonly fqdn: pulumi.Output<string>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: RailwayDomainArgs,\n\t\topts: RailwayDomainOptions,\n\t) {\n\t\tconst { provider, project, environment, service, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:railway:Domain\", name, {}, pulumiOpts);\n\n\t\tconst resource = new RailwayDomainResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tprojectId: project.id,\n\t\t\t\tserviceId: service.id,\n\t\t\t\tenvironmentId: environment.id,\n\t\t\t\tcustomDomain: args.customDomain,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.fqdn = resource.fqdn;\n\n\t\tthis.registerOutputs({ fqdn: this.fqdn });\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"domain.mjs","names":[],"sources":["../../src/railway/domain.ts"],"sourcesContent":["import * as pulumi from \"@pulumi/pulumi\";\nimport { RailwayClient } from \"./client\";\nimport type { RailwayEnvironment } from \"./environment\";\nimport type { RailwayProject } from \"./project\";\nimport type { RailwayProvider } from \"./provider\";\nimport type { RailwayService } from \"./service\";\n\n/** Resolved inputs for the Railway domain dynamic provider. */\ninterface RailwayDomainInputs {\n\t/** Railway API bearer token. */\n\ttoken: string;\n\n\t/** Railway project UUID. */\n\tprojectId: string;\n\n\t/** Railway service UUID to attach the domain to. */\n\tserviceId: string;\n\n\t/** Railway environment UUID (e.g. production). */\n\tenvironmentId: string;\n\n\t/** Custom domain FQDN (e.g. `\"api.example.com\"`). Omit for auto-generated Railway domain. */\n\tcustomDomain?: string;\n}\n\n/** Persisted state for the Railway domain, extending inputs with Railway-assigned identifiers. */\ninterface RailwayDomainOutputs extends RailwayDomainInputs {\n\t/** Railway-assigned domain UUID (used for deletion API calls). */\n\tdomainId: string;\n\n\t/** Fully qualified domain name (e.g. `\"api-production-abc.up.railway.app\"`). */\n\tfqdn: string;\n\n\t/**\n\t * The value to CNAME `customDomain` to. Only present for custom domains — a\n\t * service's auto-generated `*.up.railway.app` domain needs no DNS record of its\n\t * own. `undefined` if Railway hasn't returned a traffic-routing CNAME record yet.\n\t */\n\tcnameTarget?: string;\n}\n\n/** A single DNS record Railway expects for a custom domain (CNAME target, ACME challenge, ...). */\ninterface DomainDnsRecord {\n\trecordType: string;\n\tpurpose: string;\n\trequiredValue: string;\n}\n\n/** Shape returned for a custom domain, including its required DNS records. */\ninterface CustomDomainEntry {\n\tid: string;\n\tdomain: string;\n\tstatus?: { dnsRecords: DomainDnsRecord[] };\n}\n\n/**\n * Picks the CNAME record Railway expects for routing traffic to a custom domain out\n * of its full DNS record list (which also includes e.g. an ACME challenge TXT record).\n */\nfunction extractCnameTarget(\n\tdnsRecords: DomainDnsRecord[] | undefined,\n): string | undefined {\n\treturn dnsRecords?.find(\n\t\t(record) =>\n\t\t\trecord.recordType === \"DNS_RECORD_TYPE_CNAME\" &&\n\t\t\trecord.purpose === \"DNS_RECORD_PURPOSE_TRAFFIC_ROUTE\",\n\t)?.requiredValue;\n}\n\nconst DOMAIN_STATUS_FIELDS = `\n status {\n dnsRecords { recordType purpose requiredValue }\n }\n`;\n\nconst SERVICE_DOMAINS_QUERY = `\n query($projectId: String!, $serviceId: String!, $environmentId: String!) {\n domains(\n projectId: $projectId\n serviceId: $serviceId\n environmentId: $environmentId\n ) {\n serviceDomains { id domain }\n customDomains { id domain ${DOMAIN_STATUS_FIELDS} }\n }\n }\n`;\n\nconst SERVICE_DOMAIN_CREATE = `\n mutation($input: ServiceDomainCreateInput!) {\n serviceDomainCreate(input: $input) { id domain }\n }\n`;\n\nconst CUSTOM_DOMAIN_CREATE = `\n mutation($input: CustomDomainCreateInput!) {\n customDomainCreate(input: $input) { id domain ${DOMAIN_STATUS_FIELDS} }\n }\n`;\n\nconst CUSTOM_DOMAIN_DELETE = `\n mutation($id: String!) { customDomainDelete(id: $id) }\n`;\n\nconst SERVICE_DOMAIN_DELETE = `\n mutation($id: String!) { serviceDomainDelete(id: $id) }\n`;\n\n/**\n * Queries all existing domains (service and custom) for a Railway service.\n */\nasync function findExistingDomains(\n\tclient: RailwayClient,\n\tprojectId: string,\n\tserviceId: string,\n\tenvironmentId: string,\n): Promise<{\n\tserviceDomains: Array<{ id: string; domain: string }>;\n\tcustomDomains: CustomDomainEntry[];\n}> {\n\tconst result = await client.query<{\n\t\tdomains: {\n\t\t\tserviceDomains: Array<{ id: string; domain: string }>;\n\t\t\tcustomDomains: CustomDomainEntry[];\n\t\t};\n\t}>(SERVICE_DOMAINS_QUERY, { projectId, serviceId, environmentId });\n\n\treturn result.domains;\n}\n\n/**\n * Dynamic provider implementing CRUD for Railway domains.\n *\n * Uses adopt-or-create: queries existing domains before creating new ones.\n * Uses the FQDN as the Pulumi resource ID.\n *\n * @internal Exported only for unit testing; not part of the public API surface.\n */\nexport class RailwayDomainResourceProvider\n\timplements pulumi.dynamic.ResourceProvider\n{\n\tasync create(\n\t\tinputs: RailwayDomainInputs,\n\t): Promise<pulumi.dynamic.CreateResult> {\n\t\tconst client = new RailwayClient(inputs.token);\n\n\t\tconst existing = await findExistingDomains(\n\t\t\tclient,\n\t\t\tinputs.projectId,\n\t\t\tinputs.serviceId,\n\t\t\tinputs.environmentId,\n\t\t);\n\n\t\tif (inputs.customDomain) {\n\t\t\tconst found = existing.customDomains.find(\n\t\t\t\t(d) => d.domain === inputs.customDomain,\n\t\t\t);\n\n\t\t\tif (found) {\n\t\t\t\tpulumi.log.info(`Adopting existing custom domain \"${found.domain}\"`);\n\n\t\t\t\treturn {\n\t\t\t\t\tid: found.domain,\n\t\t\t\t\touts: {\n\t\t\t\t\t\t...inputs,\n\t\t\t\t\t\tdomainId: found.id,\n\t\t\t\t\t\tfqdn: found.domain,\n\t\t\t\t\t\tcnameTarget: extractCnameTarget(found.status?.dnsRecords),\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst result = await client.query<{\n\t\t\t\tcustomDomainCreate: CustomDomainEntry;\n\t\t\t}>(CUSTOM_DOMAIN_CREATE, {\n\t\t\t\tinput: {\n\t\t\t\t\tprojectId: inputs.projectId,\n\t\t\t\t\tserviceId: inputs.serviceId,\n\t\t\t\t\tenvironmentId: inputs.environmentId,\n\t\t\t\t\tdomain: inputs.customDomain,\n\t\t\t\t},\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\tid: result.customDomainCreate.domain,\n\t\t\t\touts: {\n\t\t\t\t\t...inputs,\n\t\t\t\t\tdomainId: result.customDomainCreate.id,\n\t\t\t\t\tfqdn: result.customDomainCreate.domain,\n\t\t\t\t\tcnameTarget: extractCnameTarget(\n\t\t\t\t\t\tresult.customDomainCreate.status?.dnsRecords,\n\t\t\t\t\t),\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tif (existing.serviceDomains.length > 0) {\n\t\t\tconst found = existing.serviceDomains[0];\n\n\t\t\tpulumi.log.info(`Adopting existing service domain \"${found.domain}\"`);\n\n\t\t\treturn {\n\t\t\t\tid: found.domain,\n\t\t\t\touts: { ...inputs, domainId: found.id, fqdn: found.domain },\n\t\t\t};\n\t\t}\n\n\t\tconst result = await client.query<{\n\t\t\tserviceDomainCreate: { id: string; domain: string };\n\t\t}>(SERVICE_DOMAIN_CREATE, {\n\t\t\tinput: {\n\t\t\t\tserviceId: inputs.serviceId,\n\t\t\t\tenvironmentId: inputs.environmentId,\n\t\t\t},\n\t\t});\n\n\t\treturn {\n\t\t\tid: result.serviceDomainCreate.domain,\n\t\t\touts: {\n\t\t\t\t...inputs,\n\t\t\t\tdomainId: result.serviceDomainCreate.id,\n\t\t\t\tfqdn: result.serviceDomainCreate.domain,\n\t\t\t},\n\t\t};\n\t}\n\n\tasync read(\n\t\t_id: string,\n\t\tprops: RailwayDomainOutputs,\n\t): Promise<pulumi.dynamic.ReadResult> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\tconst existing = await findExistingDomains(\n\t\t\tclient,\n\t\t\tprops.projectId,\n\t\t\tprops.serviceId,\n\t\t\tprops.environmentId,\n\t\t);\n\n\t\tif (props.customDomain) {\n\t\t\tconst found = existing.customDomains.find(\n\t\t\t\t(d) => d.domain === props.customDomain,\n\t\t\t);\n\n\t\t\tif (!found) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Custom domain \"${props.customDomain}\" not found during refresh`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tid: found.domain,\n\t\t\t\tprops: {\n\t\t\t\t\t...props,\n\t\t\t\t\tdomainId: found.id,\n\t\t\t\t\tfqdn: found.domain,\n\t\t\t\t\tcnameTarget: extractCnameTarget(found.status?.dnsRecords),\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tif (existing.serviceDomains.length > 0) {\n\t\t\tconst found = existing.serviceDomains[0];\n\n\t\t\treturn {\n\t\t\t\tid: found.domain,\n\t\t\t\tprops: { ...props, domainId: found.id, fqdn: found.domain },\n\t\t\t};\n\t\t}\n\n\t\tthrow new Error(\"Railway domain not found during refresh\");\n\t}\n\n\tasync delete(_id: string, props: RailwayDomainOutputs): Promise<void> {\n\t\tconst client = new RailwayClient(props.token);\n\n\t\tconst mutation = props.customDomain\n\t\t\t? CUSTOM_DOMAIN_DELETE\n\t\t\t: SERVICE_DOMAIN_DELETE;\n\n\t\ttry {\n\t\t\tawait client.query(mutation, { id: props.domainId });\n\t\t} catch {\n\t\t\tpulumi.log.warn(\n\t\t\t\t\"Failed to delete Railway domain (may already be deleted)\",\n\t\t\t);\n\t\t}\n\t}\n\n\tasync diff(\n\t\t_id: string,\n\t\tolds: RailwayDomainOutputs,\n\t\tnews: RailwayDomainInputs,\n\t): Promise<pulumi.dynamic.DiffResult> {\n\t\tconst replaces: string[] = [];\n\n\t\tif (olds.serviceId !== news.serviceId) {\n\t\t\treplaces.push(\"serviceId\");\n\t\t}\n\n\t\tif (olds.customDomain !== news.customDomain) {\n\t\t\treplaces.push(\"customDomain\");\n\t\t}\n\n\t\tif (olds.environmentId !== news.environmentId) {\n\t\t\treplaces.push(\"environmentId\");\n\t\t}\n\n\t\treturn {\n\t\t\tchanges: replaces.length > 0,\n\t\t\treplaces,\n\t\t\tdeleteBeforeReplace: true,\n\t\t};\n\t}\n}\n\n/** Internal dynamic resource — not part of the public API. */\nclass RailwayDomainResource extends pulumi.dynamic.Resource {\n\tpublic declare readonly fqdn: pulumi.Output<string>;\n\tpublic declare readonly cnameTarget: pulumi.Output<string | undefined>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: {\n\t\t\ttoken: pulumi.Input<string>;\n\t\t\tprojectId: pulumi.Input<string>;\n\t\t\tserviceId: pulumi.Input<string>;\n\t\t\tenvironmentId: pulumi.Input<string>;\n\t\t\tcustomDomain?: pulumi.Input<string>;\n\t\t},\n\t\topts?: pulumi.CustomResourceOptions,\n\t) {\n\t\tsuper(\n\t\t\tnew RailwayDomainResourceProvider(),\n\t\t\tname,\n\t\t\t{ ...args, domainId: undefined, fqdn: undefined, cnameTarget: undefined },\n\t\t\topts,\n\t\t);\n\t}\n}\n\n/** Options type for RailwayDomain — replaces Pulumi's native `provider` field. */\ntype RailwayDomainOptions = Omit<\n\tpulumi.ComponentResourceOptions,\n\t\"provider\"\n> & {\n\t/** Railway authentication context. */\n\tprovider: RailwayProvider;\n\n\t/** Railway project context. */\n\tproject: RailwayProject;\n\n\t/** Railway environment context. */\n\tenvironment: RailwayEnvironment;\n\n\t/** Railway service context. */\n\tservice: RailwayService;\n};\n\n/** Args for RailwayDomain. */\nexport interface RailwayDomainArgs {\n\t/** Custom domain FQDN. Omit to create an auto-generated Railway service domain. */\n\tcustomDomain?: pulumi.Input<string>;\n}\n\n/**\n * Manages a Railway domain (service or custom) with adopt-or-create semantics.\n *\n * A service can carry more than one custom domain — declare one `RailwayDomain` per\n * domain; each instance adopts, creates, and deletes only its own domain.\n *\n * @example\n * ```typescript\n * const apiDomain = new RailwayDomain(\"api-domain\", { customDomain: \"api.example.com\" }, {\n * provider, project, environment, service,\n * });\n * const wwwDomain = new RailwayDomain(\"www-domain\", { customDomain: \"www.example.com\" }, {\n * provider, project, environment, service,\n * });\n *\n * // Point each domain's DNS CNAME at its own target.\n * const apiCnameTarget = apiDomain.cnameTarget;\n * const wwwCnameTarget = wwwDomain.cnameTarget;\n * ```\n */\nexport class RailwayDomain extends pulumi.ComponentResource {\n\t/** Fully qualified domain name. */\n\tpublic readonly fqdn: pulumi.Output<string>;\n\n\t/**\n\t * The value to CNAME `customDomain` to. Only set for custom domains — `undefined`\n\t * for a service's auto-generated `*.up.railway.app` domain, or if Railway hasn't\n\t * returned a traffic-routing CNAME record for it yet.\n\t */\n\tpublic readonly cnameTarget: pulumi.Output<string | undefined>;\n\n\tconstructor(\n\t\tname: string,\n\t\targs: RailwayDomainArgs,\n\t\topts: RailwayDomainOptions,\n\t) {\n\t\tconst { provider, project, environment, service, ...pulumiOpts } = opts;\n\n\t\tsuper(\"infracraft:railway:Domain\", name, {}, pulumiOpts);\n\n\t\tconst resource = new RailwayDomainResource(\n\t\t\t`${name}-resource`,\n\t\t\t{\n\t\t\t\ttoken: provider.token,\n\t\t\t\tprojectId: project.id,\n\t\t\t\tserviceId: service.id,\n\t\t\t\tenvironmentId: environment.id,\n\t\t\t\tcustomDomain: args.customDomain,\n\t\t\t},\n\t\t\t{ parent: this },\n\t\t);\n\n\t\tthis.fqdn = resource.fqdn;\n\t\tthis.cnameTarget = resource.cnameTarget;\n\n\t\tthis.registerOutputs({ fqdn: this.fqdn, cnameTarget: this.cnameTarget });\n\t}\n}\n"],"mappings":"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"}
@@ -124,6 +124,8 @@ async function applyInstanceConfig(client, serviceId, environmentId, inputs) {
124
124
  * Uses adopt-or-create on `create()`: queries services by project ID and name
125
125
  * before creating a new one. Service instance configuration (builder, commands,
126
126
  * healthcheck) is applied via `serviceInstanceUpdate` after create or update.
127
+ *
128
+ * @internal Exported only for unit testing; not part of the public API surface.
127
129
  */
128
130
  var RailwayServiceResourceProvider = class {
129
131
  /**
@@ -136,6 +138,7 @@ var RailwayServiceResourceProvider = class {
136
138
  else {
137
139
  const createInput = {
138
140
  projectId: inputs.projectId,
141
+ environmentId: inputs.environmentId,
139
142
  name: inputs.name
140
143
  };
141
144
  if (inputs.source) createInput.source = { image: inputs.source.image };
@@ -277,4 +280,5 @@ var RailwayService = class extends _pulumi_pulumi.ComponentResource {
277
280
  exports.RailwayBuilder = RailwayBuilder;
278
281
  exports.RailwayRestartPolicy = RailwayRestartPolicy;
279
282
  exports.RailwayService = RailwayService;
283
+ exports.RailwayServiceResourceProvider = RailwayServiceResourceProvider;
280
284
  //# sourceMappingURL=service.cjs.map