@openhi/constructs 0.0.135 → 0.0.136

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.
package/lib/index.mjs CHANGED
@@ -358,12 +358,14 @@ import {
358
358
  } from "@codedrifters/utils";
359
359
  import { RemovalPolicy, Stack, Tags } from "aws-cdk-lib";
360
360
  import { paramCase } from "change-case";
361
+ var DEFAULT_RELEASE_BRANCH = "main";
362
+ var CHILD_ZONE_PREFIX_MAX_LENGTH = 56;
361
363
  var OPENHI_TAG_SUFFIX_REPO_NAME = "repo-name";
362
364
  var OPENHI_TAG_SUFFIX_BRANCH_NAME = "branch-name";
363
365
  var OPENHI_TAG_SUFFIX_SERVICE_TYPE = "service-type";
364
366
  var OPENHI_TAG_SUFFIX_STAGE_TYPE = "stage-type";
365
367
  var openHiTagKey = (appName, suffix) => `${appName}:${suffix}`;
366
- var OpenHiService = class extends Stack {
368
+ var OpenHiService = class _OpenHiService extends Stack {
367
369
  /**
368
370
  * Creates a new OpenHI service stack.
369
371
  *
@@ -383,8 +385,12 @@ var OpenHiService = class extends Stack {
383
385
  }
384
386
  const appName = props.appName ?? ohEnv.ohStage.ohApp.appName ?? "openhi";
385
387
  const repoName = props.repoName ?? findGitRepoName();
386
- const defaultReleaseBranch = props.defaultReleaseBranch ?? "main";
387
- const branchName = props.branchName ?? (process.env.JEST_WORKER_ID ? "test-branch" : process.env.GIT_BRANCH_NAME?.trim() || (ohEnv.ohStage.stageType === import_config3.OPEN_HI_STAGE.DEV ? findGitBranch() : defaultReleaseBranch));
388
+ const { branchName, defaultReleaseBranch } = _OpenHiService.resolveBranchContext(ohEnv, {
389
+ ...props.branchName !== void 0 && { branchName: props.branchName },
390
+ ...props.defaultReleaseBranch !== void 0 && {
391
+ defaultReleaseBranch: props.defaultReleaseBranch
392
+ }
393
+ });
388
394
  const environmentHash = hashString2(
389
395
  [appName, ohEnv.deploymentTargetRole, account, region].join("-"),
390
396
  6
@@ -459,6 +465,50 @@ var OpenHiService = class extends Stack {
459
465
  ohEnv.ohStage.stageType.slice(0, 255)
460
466
  );
461
467
  }
468
+ /**
469
+ * Compose the full per-deploy domain for an OpenHI service.
470
+ *
471
+ * On the release branch (`branchName === defaultReleaseBranch`), the
472
+ * full domain is `<domainPrefix>.<zoneName>`. On every other branch
473
+ * the per-PR preview hostname is
474
+ * `<domainPrefix>-<childZonePrefix>.<zoneName>`.
475
+ *
476
+ * Pure helper — reads no environment state. Subclasses expose thin
477
+ * statics (`composeFullDomain`) that fill in `domainPrefix` from their
478
+ * own service constant and delegate here.
479
+ */
480
+ static composeServiceDomain(opts) {
481
+ const isRelease = opts.branchName === opts.defaultReleaseBranch;
482
+ const subDomain = isRelease ? opts.domainPrefix : `${opts.domainPrefix}-${opts.childZonePrefix}`;
483
+ return `${subDomain}.${opts.zoneName}`;
484
+ }
485
+ /**
486
+ * Compute the `childZonePrefix` segment for a given branch — kebab-cased
487
+ * and truncated to {@link CHILD_ZONE_PREFIX_MAX_LENGTH}. Matches the
488
+ * per-instance {@link OpenHiService.childZonePrefix} getter so consumers
489
+ * can compose hostnames identical to the service's own without
490
+ * instantiating it.
491
+ */
492
+ static computeChildZonePrefix(branchName) {
493
+ return paramCase(branchName).slice(0, CHILD_ZONE_PREFIX_MAX_LENGTH);
494
+ }
495
+ /**
496
+ * Resolve the branch context the service would compute internally given
497
+ * an environment and optional overrides. Mirrors the same defaulting
498
+ * (props override → JEST sentinel → `GIT_BRANCH_NAME` env → git
499
+ * detection on DEV → release branch on stage/prod) the
500
+ * {@link OpenHiService} constructor uses.
501
+ *
502
+ * Consumers (e.g. sibling stack entries) call this to predict the
503
+ * branch values a service will see at synth time so they can compose
504
+ * hostnames against the same inputs.
505
+ */
506
+ static resolveBranchContext(ohEnv, overrides = {}) {
507
+ const defaultReleaseBranch = overrides.defaultReleaseBranch ?? DEFAULT_RELEASE_BRANCH;
508
+ const branchName = overrides.branchName ?? (process.env.JEST_WORKER_ID ? "test-branch" : process.env.GIT_BRANCH_NAME?.trim() || (ohEnv.ohStage.stageType === import_config3.OPEN_HI_STAGE.DEV ? findGitBranch() : defaultReleaseBranch));
509
+ const childZonePrefix = _OpenHiService.computeChildZonePrefix(branchName);
510
+ return { branchName, defaultReleaseBranch, childZonePrefix };
511
+ }
462
512
  /**
463
513
  * DNS prefix for this branche's child zone. Capped at 56 chars so
464
514
  * that a `<service>-<prefix>` hostname segment stays under the 63-byte
@@ -467,7 +517,7 @@ var OpenHiService = class extends Stack {
467
517
  * headroom on the longer side.
468
518
  */
469
519
  get childZonePrefix() {
470
- return paramCase(this.branchName).slice(0, 56);
520
+ return _OpenHiService.computeChildZonePrefix(this.branchName);
471
521
  }
472
522
  };
473
523
 
@@ -2880,6 +2930,7 @@ _OpenHiAuthService.SERVICE_TYPE = "auth";
2880
2930
  var OpenHiAuthService = _OpenHiAuthService;
2881
2931
 
2882
2932
  // src/services/open-hi-rest-api-service.ts
2933
+ var import_config5 = __toESM(require_lib2());
2883
2934
  import {
2884
2935
  CorsHttpMethod,
2885
2936
  DomainName,
@@ -2970,7 +3021,31 @@ var RestApiLambda = class extends Construct20 {
2970
3021
  // src/services/open-hi-rest-api-service.ts
2971
3022
  var REST_API_BASE_URL_SSM_NAME = "REST_API_BASE_URL";
2972
3023
  var REST_API_DOMAIN_NAME_SSM_NAME = "REST_API_DOMAIN_NAME";
3024
+ var DEV_CORS_ALLOW_ORIGINS = [
3025
+ "http://localhost:3000",
3026
+ "https://localhost:3000",
3027
+ "http://localhost:5173",
3028
+ "https://localhost:5173",
3029
+ "http://127.0.0.1:3000",
3030
+ "https://127.0.0.1:3000",
3031
+ "http://127.0.0.1:5173",
3032
+ "https://127.0.0.1:5173"
3033
+ ];
2973
3034
  var _OpenHiRestApiService = class _OpenHiRestApiService extends OpenHiService {
3035
+ /**
3036
+ * Compose the REST API's full per-deploy domain. Thin wrapper over
3037
+ * {@link OpenHiService.composeServiceDomain} that pins `domainPrefix`
3038
+ * to {@link API_DOMAIN_PREFIX}.
3039
+ *
3040
+ * Use from sibling stacks that need to predict the API's hostname
3041
+ * before the REST API stack is synthesised.
3042
+ */
3043
+ static composeFullDomain(opts) {
3044
+ return OpenHiService.composeServiceDomain({
3045
+ ...opts,
3046
+ domainPrefix: _OpenHiRestApiService.API_DOMAIN_PREFIX
3047
+ });
3048
+ }
2974
3049
  /**
2975
3050
  * Returns an IHttpApi by looking up the REST API stack's HTTP API ID from SSM.
2976
3051
  */
@@ -3054,11 +3129,18 @@ var _OpenHiRestApiService = class _OpenHiRestApiService extends OpenHiService {
3054
3129
  }
3055
3130
  /**
3056
3131
  * Returns the API domain name string (e.g. api.example.com or api-\{prefix\}.example.com).
3132
+ * Delegates to {@link OpenHiRestApiService.composeFullDomain} so the
3133
+ * release-vs-feature composition stays in one place; picks up
3134
+ * `this.defaultReleaseBranch` (not a hard-coded `"main"`).
3057
3135
  * Override to customize.
3058
3136
  */
3059
3137
  createApiDomainNameString(hostedZone) {
3060
- const apiPrefix = this.branchName === "main" ? `api` : `api-${this.childZonePrefix}`;
3061
- return [apiPrefix, hostedZone.zoneName].join(".");
3138
+ return _OpenHiRestApiService.composeFullDomain({
3139
+ branchName: this.branchName,
3140
+ defaultReleaseBranch: this.defaultReleaseBranch,
3141
+ childZonePrefix: this.childZonePrefix,
3142
+ zoneName: hostedZone.zoneName
3143
+ });
3062
3144
  }
3063
3145
  /**
3064
3146
  * Creates the SSM parameter for the REST API base URL.
@@ -3117,7 +3199,7 @@ var _OpenHiRestApiService = class _OpenHiRestApiService extends OpenHiService {
3117
3199
  postgresSecretArn,
3118
3200
  postgresDatabase,
3119
3201
  postgresSchema,
3120
- ...extraEnvironment !== void 0 && { extraEnvironment }
3202
+ extraEnvironment
3121
3203
  });
3122
3204
  lambda.addToRolePolicy(
3123
3205
  new PolicyStatement7({
@@ -3207,7 +3289,10 @@ var _OpenHiRestApiService = class _OpenHiRestApiService extends OpenHiService {
3207
3289
  routeKey: HttpRouteKey.with("/{proxy+}", HttpMethod.ANY),
3208
3290
  integration
3209
3291
  });
3210
- const apiPrefix = this.branchName === "main" ? `api` : `api-${this.childZonePrefix}`;
3292
+ const apiPrefix = this.apiDomainName.slice(
3293
+ 0,
3294
+ -(hostedZone.zoneName.length + 1)
3295
+ );
3211
3296
  new ARecord3(this, `api-a-record-${apiPrefix}`, {
3212
3297
  zone: hostedZone,
3213
3298
  recordName: apiPrefix,
@@ -3233,27 +3318,10 @@ var _OpenHiRestApiService = class _OpenHiRestApiService extends OpenHiService {
3233
3318
  { userPoolClients: [userPoolClient] }
3234
3319
  );
3235
3320
  const { corsPreflight: cors, ...restRootHttpApiProps } = this.props.rootHttpApiProps ?? {};
3236
- const corsPreflight = cors !== void 0 ? {
3237
- allowOrigins: cors.allowOrigins,
3238
- allowMethods: cors.allowMethods ?? [
3239
- CorsHttpMethod.GET,
3240
- CorsHttpMethod.HEAD,
3241
- CorsHttpMethod.POST,
3242
- CorsHttpMethod.PUT,
3243
- CorsHttpMethod.PATCH,
3244
- CorsHttpMethod.DELETE,
3245
- CorsHttpMethod.OPTIONS
3246
- ],
3247
- allowHeaders: cors.allowHeaders ?? [
3248
- "Content-Type",
3249
- "Authorization"
3250
- ],
3251
- allowCredentials: cors.allowCredentials ?? true,
3252
- maxAge: cors.maxAge ?? Duration10.days(1),
3253
- ...cors.exposeHeaders !== void 0 && {
3254
- exposeHeaders: cors.exposeHeaders
3255
- }
3256
- } : void 0;
3321
+ const isNonProd = this.ohEnv.ohStage.stageType !== import_config5.OPEN_HI_STAGE.PROD;
3322
+ const callerOrigins = cors?.allowOrigins ?? [];
3323
+ const mergedOrigins = isNonProd ? Array.from(/* @__PURE__ */ new Set([...callerOrigins, ...DEV_CORS_ALLOW_ORIGINS])) : callerOrigins;
3324
+ const corsPreflight = cors !== void 0 || isNonProd ? this.buildCorsPreflightOptions(mergedOrigins, cors) : void 0;
3257
3325
  const rootHttpApi = new RootHttpApi(this, {
3258
3326
  ...restRootHttpApiProps,
3259
3327
  ...corsPreflight !== void 0 && { corsPreflight },
@@ -3270,21 +3338,43 @@ var _OpenHiRestApiService = class _OpenHiRestApiService extends OpenHiService {
3270
3338
  });
3271
3339
  return rootHttpApi;
3272
3340
  }
3341
+ /**
3342
+ * Builds the full `CorsPreflightOptions` from a merged origins array,
3343
+ * filling defaults for `allowMethods`/`allowHeaders`/`allowCredentials`/
3344
+ * `maxAge` from the caller-supplied block when present.
3345
+ */
3346
+ buildCorsPreflightOptions(allowOrigins, cors) {
3347
+ return {
3348
+ allowOrigins: [...allowOrigins],
3349
+ allowMethods: cors?.allowMethods ?? [
3350
+ CorsHttpMethod.GET,
3351
+ CorsHttpMethod.HEAD,
3352
+ CorsHttpMethod.POST,
3353
+ CorsHttpMethod.PUT,
3354
+ CorsHttpMethod.PATCH,
3355
+ CorsHttpMethod.DELETE,
3356
+ CorsHttpMethod.OPTIONS
3357
+ ],
3358
+ allowHeaders: cors?.allowHeaders ?? ["Content-Type", "Authorization"],
3359
+ allowCredentials: cors?.allowCredentials ?? true,
3360
+ maxAge: cors?.maxAge ?? Duration10.days(1),
3361
+ ...cors?.exposeHeaders !== void 0 && {
3362
+ exposeHeaders: cors.exposeHeaders
3363
+ }
3364
+ };
3365
+ }
3273
3366
  /**
3274
3367
  * Builds the `OPENHI_RUNTIME_CONFIG_*` env-var map the REST API Lambda
3275
- * exposes through `GET /control/runtime-config`. Returns `undefined` when
3276
- * the `runtimeConfig` prop is omitted so no env vars are set.
3277
- *
3278
- * The three Cognito IDs are resolved via SSM lookups against the auth
3279
- * stack from a dedicated sub-scope (`runtime-config`) so they don't
3280
- * collide with the user-pool / user-pool-client constructs already
3281
- * created in {@link createRootHttpApi}. `apiBaseUrl` is derived from
3282
- * this stack's own custom domain so callers don't have to hardcode it.
3368
+ * exposes through `GET /control/runtime-config`. The four values are
3369
+ * always populated the three Cognito IDs are resolved via SSM lookups
3370
+ * against the auth stack from a dedicated sub-scope (`runtime-config`)
3371
+ * so they don't collide with the user-pool / user-pool-client constructs
3372
+ * already created in {@link createRootHttpApi}, and `apiBaseUrl` is
3373
+ * derived from this stack's own custom domain. The OAuth callback URL
3374
+ * is no longer plumbed through the API — the admin-console derives it
3375
+ * client-side from `window.location.origin`.
3283
3376
  */
3284
3377
  resolveRuntimeConfigEnvVars() {
3285
- if (this.props.runtimeConfig === void 0) {
3286
- return void 0;
3287
- }
3288
3378
  const cognitoScope = new Construct21(this, "runtime-config");
3289
3379
  const userPool = OpenHiAuthService.userPoolFromConstruct(cognitoScope);
3290
3380
  const userPoolClient = OpenHiAuthService.userPoolClientFromConstruct(cognitoScope);
@@ -3293,12 +3383,16 @@ var _OpenHiRestApiService = class _OpenHiRestApiService extends OpenHiService {
3293
3383
  OPENHI_RUNTIME_CONFIG_COGNITO_USER_POOL_ID: userPool.userPoolId,
3294
3384
  OPENHI_RUNTIME_CONFIG_COGNITO_USER_POOL_CLIENT_ID: userPoolClient.userPoolClientId,
3295
3385
  OPENHI_RUNTIME_CONFIG_COGNITO_DOMAIN_URL: cognitoDomainUrl,
3296
- OPENHI_RUNTIME_CONFIG_COGNITO_REDIRECT_URI: this.props.runtimeConfig.cognitoRedirectUri,
3297
3386
  OPENHI_RUNTIME_CONFIG_API_BASE_URL: `https://${this.apiDomainName}`
3298
3387
  };
3299
3388
  }
3300
3389
  };
3301
3390
  _OpenHiRestApiService.SERVICE_TYPE = "rest-api";
3391
+ /**
3392
+ * Sub-domain prefix used by the REST API. Release-branch hostname is
3393
+ * `api.<zone>`; per-PR preview hostname is `api-<childZonePrefix>.<zone>`.
3394
+ */
3395
+ _OpenHiRestApiService.API_DOMAIN_PREFIX = "api";
3302
3396
  var OpenHiRestApiService = _OpenHiRestApiService;
3303
3397
 
3304
3398
  // src/services/open-hi-graphql-service.ts
@@ -3342,10 +3436,26 @@ _OpenHiGraphqlService.SERVICE_TYPE = "graphql-api";
3342
3436
  var OpenHiGraphqlService = _OpenHiGraphqlService;
3343
3437
 
3344
3438
  // src/services/open-hi-website-service.ts
3345
- var import_config5 = __toESM(require_lib2());
3439
+ var import_config6 = __toESM(require_lib2());
3346
3440
  import { Bucket as Bucket3 } from "aws-cdk-lib/aws-s3";
3347
3441
  var SSM_PARAM_NAME_FULL_DOMAIN = "WEBSITE_FULL_DOMAIN";
3442
+ var ADMIN_DOMAIN_PREFIX = "admin";
3348
3443
  var _OpenHiWebsiteService = class _OpenHiWebsiteService extends OpenHiService {
3444
+ /**
3445
+ * Compose the website's full per-deploy domain. Thin wrapper over
3446
+ * {@link OpenHiService.composeServiceDomain} that fills in
3447
+ * {@link DEFAULT_DOMAIN_PREFIX} when `domainPrefix` is omitted.
3448
+ *
3449
+ * Use from sibling stacks that need to predict the website's hostname
3450
+ * before the website stack is synthesised — e.g. the REST API stack
3451
+ * computing its CORS `allowOrigins` for the admin-console.
3452
+ */
3453
+ static composeFullDomain(opts) {
3454
+ return OpenHiService.composeServiceDomain({
3455
+ ...opts,
3456
+ domainPrefix: opts.domainPrefix ?? _OpenHiWebsiteService.DEFAULT_DOMAIN_PREFIX
3457
+ });
3458
+ }
3349
3459
  /**
3350
3460
  * Looks up the static-hosting bucket ARN published by the release-branch
3351
3461
  * deploy of this service.
@@ -3464,16 +3574,24 @@ var _OpenHiWebsiteService = class _OpenHiWebsiteService extends OpenHiService {
3464
3574
  * every other deploy serves a per-PR preview at
3465
3575
  * `\<domainPrefix\>-\<childZonePrefix\>.\<zone\>`
3466
3576
  * (e.g. `admin-feat-1093-patient-migration.dev.openhi.org`).
3577
+ *
3578
+ * Delegates to {@link OpenHiWebsiteService.composeFullDomain} so the
3579
+ * release-vs-feature composition stays in one place.
3467
3580
  */
3468
3581
  computeFullDomain(hostedZone) {
3469
- const subDomain = this.computeSubDomain();
3470
- return [subDomain, hostedZone.zoneName].join(".");
3582
+ return _OpenHiWebsiteService.composeFullDomain({
3583
+ domainPrefix: this.props.domainPrefix,
3584
+ branchName: this.branchName,
3585
+ defaultReleaseBranch: this.defaultReleaseBranch,
3586
+ childZonePrefix: this.childZonePrefix,
3587
+ zoneName: hostedZone.zoneName
3588
+ });
3471
3589
  }
3472
3590
  /**
3473
3591
  * Returns the sub-domain label (left of the zone) for the current
3474
- * deploy. Used both for {@link fullDomain} and for the per-branch S3
3475
- * key prefix passed to {@link StaticContent} so the upload prefix
3476
- * always matches the served hostname.
3592
+ * deploy. Used for the per-branch S3 key prefix passed to
3593
+ * {@link StaticContent} so the upload prefix always matches the
3594
+ * served hostname.
3477
3595
  *
3478
3596
  * Non-release deploys compose the per-PR slug as
3479
3597
  * `\<domainPrefix\>-\<childZonePrefix\>`, mirroring the REST API's
@@ -3484,7 +3602,7 @@ var _OpenHiWebsiteService = class _OpenHiWebsiteService extends OpenHiService {
3484
3602
  */
3485
3603
  computeSubDomain() {
3486
3604
  const isReleaseBranch = this.branchName === this.defaultReleaseBranch;
3487
- const domainPrefix = this.props.domainPrefix ?? "www";
3605
+ const domainPrefix = this.props.domainPrefix ?? _OpenHiWebsiteService.DEFAULT_DOMAIN_PREFIX;
3488
3606
  if (isReleaseBranch) {
3489
3607
  return domainPrefix;
3490
3608
  }
@@ -3511,7 +3629,7 @@ var _OpenHiWebsiteService = class _OpenHiWebsiteService extends OpenHiService {
3511
3629
  domainNames: [this.fullDomain, wildcardSan],
3512
3630
  description: `OpenHI website (${this.fullDomain})`,
3513
3631
  prefixPattern: PER_BRANCH_PREVIEW_PREFIX,
3514
- enablePreviewLifecycle: this.ohEnv.ohStage.stageType !== import_config5.OPEN_HI_STAGE.PROD,
3632
+ enablePreviewLifecycle: this.ohEnv.ohStage.stageType !== import_config6.OPEN_HI_STAGE.PROD,
3515
3633
  ...restApi !== void 0 && { restApi }
3516
3634
  });
3517
3635
  }
@@ -3597,6 +3715,12 @@ var _OpenHiWebsiteService = class _OpenHiWebsiteService extends OpenHiService {
3597
3715
  }
3598
3716
  };
3599
3717
  _OpenHiWebsiteService.SERVICE_TYPE = "website";
3718
+ /**
3719
+ * Default `domainPrefix` for this service when none is supplied.
3720
+ * Release-branch hostname is `www.<zone>`; per-PR preview hostname is
3721
+ * `www-<childZonePrefix>.<zone>`.
3722
+ */
3723
+ _OpenHiWebsiteService.DEFAULT_DOMAIN_PREFIX = "www";
3600
3724
  var OpenHiWebsiteService = _OpenHiWebsiteService;
3601
3725
 
3602
3726
  // src/workflows/control-plane/owning-delete-cascade/owning-delete-cascade-lambdas.ts
@@ -4114,6 +4238,7 @@ var export_OWNING_ENTITY_TYPE = import_workflows3.OWNING_ENTITY_TYPE;
4114
4238
  var export_PlatformDeploymentCompletedV1 = import_workflows2.PlatformDeploymentCompletedV1;
4115
4239
  var export_RENAMABLE_ENTITY_TYPE = import_workflows4.RENAMABLE_ENTITY_TYPE;
4116
4240
  export {
4241
+ ADMIN_DOMAIN_PREFIX,
4117
4242
  BRIDGED_STATUSES,
4118
4243
  CLOUDFORMATION_EVENT_SOURCE,
4119
4244
  CLOUDFORMATION_STACK_STATUS_CHANGE_DETAIL_TYPE,
@@ -4138,6 +4263,7 @@ export {
4138
4263
  DEMO_PERIOD,
4139
4264
  DEMO_TENANT_SPECS,
4140
4265
  DEMO_URN_SYSTEM,
4266
+ DEV_CORS_ALLOW_ORIGINS,
4141
4267
  DEV_USERS,
4142
4268
  DataEventBus,
4143
4269
  DataStoreHistoricalArchive,