iT邦幫忙

2023 iThome 鐵人賽

DAY 9
0

今天的目標是設計一個提供 GraphQL Query 查詢人類圖的介面。

試著讓每個 field 都有 resolver,如果 Query 不是完整的人類圖時,則可減少運算。

Query

首先從 BodyGraphResolver 開始:

@Resolver(() => BodyGraphDto)
export class BodyGraphResolver {
  public constructor(
    private readonly bodyGraphService: BodyGraphService,
  ) {}
}

BodyGraph > Imprint > Substructure 這樣的結構中,BodyGraph 提供出生時間,Imprint 提供出生時的尤利安曆時間,Substructure 提供天球位置,以計算閘門和爻線。

BodyGraphResolver

BodyGraphResolver 中,首先先定義一個 Query:

@Query(() => BodyGraphDto, { name: 'bodyGraph' })
public getBodyGraph(@Args() birth: DateTimeDto): Pick<BodyGraphDto, 'birth'> {
  return { birth };
}

BodyGraphResolver 中的 FieldResolver 提供的是 Imprint,因此從上級 BodyGraph 中取得出生時間,換算成尤利安曆時間。


@ResolveField('design', () => ImprintDto)
public getDesign(
  @Parent() bodyGraph: Pick<BodyGraphDto, 'birth'>,
): Pick<ImprintDto, 'jd'> {
  const jd = this.bodyGraphService.getDesignDate(bodyGraph.birth);

  return { jd };
}

@ResolveField('personality', () => ImprintDto)
public getPersonality(
  @Parent() bodyGraph: Pick<BodyGraphDto, 'birth'>,
): Pick<ImprintDto, 'jd'> {
  const jd = this.bodyGraphService.getPersonalityDate(bodyGraph.birth);

  return { jd };
}

ImprintResolver

先定義 ImprintResolver

@Resolver(() => ImprintDto)
export class ImprintResolver {
  public constructor(private readonly bodyGraphService: BodyGraphService) {}
}

順便將 ResolveField 裝飾器先綁定第二個參數,以簡化後續程式碼:

const ResolveSubstructure = (name: string): MethodDecorator =>
  ResolveField(name, () => SubstructureDto);

這邊對前幾天 BodyGraphService 的一些函數做了整理,首先新增 getPosition()

public getPosition(jd: number, ipl: BodyGraphPlanet): number {
  const calcResult = swe_calc_ut(jd, ipl, this.SWE_IFLAG);

  if (!('longitude' in calcResult)) {
    this.assertSwissephResult(calcResult);

    throw new Error('invalid result of swe_calc_ut()');
  }

  return calcResult.longitude;
}

並且調整了 getOppositePosition() 的參數:

public getOppositePosition(jd: number, ipl: BodyGraphPlanet): number {
  return swe_degnorm(this.getPosition(jd, ipl) + 180).x360;
}

未來可以考慮對 getPosition() 做 memoize。

如果希望個別 request 分開暫存,memoize storage 可以考慮用 ALS (Async Local Storage)

實作十三個星球的 FieldResolver,分別利用尤利安時間計算星球位置。

@ResolveSubstructure('sun')
public getSubstructureSun(
  @Parent() { jd }: Pick<ImprintDto, 'jd'>,
): Pick<SubstructureDto, 'longitude'> {
  return {
    longitude: this.bodyGraphService.getPosition(jd, BodyGraphPlanet.SUN),
  };
}

@ResolveSubstructure('earth')
public getSubstructureEarth(
  @Parent() { jd }: Pick<ImprintDto, 'jd'>,
): Pick<SubstructureDto, 'longitude'> {
  return {
    longitude: this.bodyGraphService.getOppositePosition(
      jd,
      BodyGraphPlanet.SUN,
    ),
  };
}

@ResolveSubstructure('moon')
public getSubstructureMoon(
  @Parent() { jd }: Pick<ImprintDto, 'jd'>,
): Pick<SubstructureDto, 'longitude'> {
  return {
    longitude: this.bodyGraphService.getPosition(jd, BodyGraphPlanet.MOON),
  };
}

@ResolveSubstructure('northNode')
public getSubstructureNorthNode(
  @Parent() { jd }: Pick<ImprintDto, 'jd'>,
): Pick<SubstructureDto, 'longitude'> {
  return {
    longitude: this.bodyGraphService.getPosition(
      jd,
      BodyGraphPlanet.TRUE_NODE,
    ),
  };
}

@ResolveSubstructure('southNode')
public getSubstructureSouthNode(
  @Parent() { jd }: Pick<ImprintDto, 'jd'>,
): Pick<SubstructureDto, 'longitude'> {
  return {
    longitude: this.bodyGraphService.getOppositePosition(
      jd,
      BodyGraphPlanet.TRUE_NODE,
    ),
  };
}

@ResolveSubstructure('mercury')
public getSubstructureMercury(
  @Parent() { jd }: Pick<ImprintDto, 'jd'>,
): Pick<SubstructureDto, 'longitude'> {
  return {
    longitude: this.bodyGraphService.getPosition(jd, BodyGraphPlanet.MERCURY),
  };
}

@ResolveSubstructure('venus')
public getSubstructureVenus(
  @Parent() { jd }: Pick<ImprintDto, 'jd'>,
): Pick<SubstructureDto, 'longitude'> {
  return {
    longitude: this.bodyGraphService.getPosition(jd, BodyGraphPlanet.VENUS),
  };
}

@ResolveSubstructure('mars')
public getSubstructureMars(
  @Parent() { jd }: Pick<ImprintDto, 'jd'>,
): Pick<SubstructureDto, 'longitude'> {
  return {
    longitude: this.bodyGraphService.getPosition(jd, BodyGraphPlanet.MARS),
  };
}

@ResolveSubstructure('jupiter')
public getSubstructureJupiter(
  @Parent() { jd }: Pick<ImprintDto, 'jd'>,
): Pick<SubstructureDto, 'longitude'> {
  return {
    longitude: this.bodyGraphService.getPosition(jd, BodyGraphPlanet.JUPITER),
  };
}

@ResolveSubstructure('saturn')
public getSubstructureSaturn(
  @Parent() { jd }: Pick<ImprintDto, 'jd'>,
): Pick<SubstructureDto, 'longitude'> {
  return {
    longitude: this.bodyGraphService.getPosition(jd, BodyGraphPlanet.SATURN),
  };
}

@ResolveSubstructure('uranus')
public getSubstructureUranus(
  @Parent() { jd }: Pick<ImprintDto, 'jd'>,
): Pick<SubstructureDto, 'longitude'> {
  return {
    longitude: this.bodyGraphService.getPosition(jd, BodyGraphPlanet.URANUS),
  };
}

@ResolveSubstructure('neptune')
public getSubstructureNeptune(
  @Parent() { jd }: Pick<ImprintDto, 'jd'>,
): Pick<SubstructureDto, 'longitude'> {
  return {
    longitude: this.bodyGraphService.getPosition(jd, BodyGraphPlanet.NEPTUNE),
  };
}

@ResolveSubstructure('pluto')
public getSubstructurePluto(
  @Parent() { jd }: Pick<ImprintDto, 'jd'>,
): Pick<SubstructureDto, 'longitude'> {
  return {
    longitude: this.bodyGraphService.getPosition(jd, BodyGraphPlanet.PLUTO),
  };
}

SubstructureResolver

定義 SubstructureResolver

@Resolver(() => SubstructureDto)
export class SubstructureResolver {
  public constructor(private readonly bodyGraphService: BodyGraphService) {}
}

BodyGraphService 上新增兩個函數 getGate()getLine()

public getGate(longitude: number): string {
  const { x360 } = swe_degnorm(longitude + 58);
  const gateIndex = Math.trunc(x360 / this.NUM_DEGS_PER_GATE);

  return this.INDEXED_GATES[gateIndex];
}

public getLine(longitude: number): string {
  const { x360 } = swe_degnorm(longitude + 58);
  const remain = x360 % this.NUM_DEGS_PER_GATE;
  const lineIndex = Math.trunc(remain / this.NUM_DEGS_PER_LINE) + 1;

  return String(lineIndex);
}

const { x360 } = swe_degnorm(longitude + 58); 這一行在兩個函數重複計算了,之後可以獨立抽出來成一個函數,並做 memoize。

最後實作 gate 和 line 的 FieldResolver:

@ResolveField('gate')
public getGate(
  @Parent() { longitude }: Pick<SubstructureDto, 'longitude'>,
): string {
  return this.bodyGraphService.getGate(longitude);
}

@ResolveField('line')
public getLine(
  @Parent() { longitude }: Pick<SubstructureDto, 'longitude'>,
): string {
  return this.bodyGraphService.getLine(longitude);
}

打開 Playground 看看成果:

https://ithelp.ithome.com.tw/upload/images/20230924/20155318croF1X9Pks.png


晚安,瑪卡巴卡。


上一篇
人類圖基礎:從生辰到星盤(6)
下一篇
看懂自己的說明書 1:閘門與通道
系列文
「莫忘初衷,從猴子到超人」:一個獻給自由精靈的社群媒合引擎18
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言