Profile img

Hi, I'm who's behind Fedify, Hollo, BotKit, and this website, Hackers' Pub! My main account is at @hongminhee洪 民憙 (Hong Minhee).

Fedify, Hollo, BotKit, 그리고 보고 계신 이 사이트 Hackers' Pub을 만들고 있습니다. 제 메인 계정은: @hongminhee洪 民憙 (Hong Minhee).

FedifyHolloBotKit、そしてこのサイト、Hackers' Pubを作っています。私のメインアカウントは「@hongminhee洪 民憙 (Hong Minhee)」に。

Website
hongminhee.org
GitHub
@dahlia
Hollo
@hongminhee@hollo.social
DEV
@hongminhee
velog
@hongminhee
Qiita
@hongminhee
Zenn
@hongminhee
Matrix
@hongminhee:matrix.org
X
@hongminhee
1
2
1
1
0
2
0
1
5
5

投稿(Note)と記事(Article)の両方でMarkdownをサポートしているだけでなく、シンタックスハイライトとTeX数式にも対応しているという点で、[1]Hackers' Pubはフェディバースで最もソフトウェアプログラマーに適したプラットフォームであると自負しております。

そんな皆さんのために、Hackers' Pubの招待状を共有します。(笑)


  1. ちなみに、Hackers' Pubはかなり多様なMarkdown拡張構文をサポートしています。 ↩︎

1

단문(Note)과 긴 게시글(Article) 모두에서 Markdown을 지원할 뿐만 아니라 구문강조와 TeX 수식을 지원한다는 점에서 Hackers' Pub은 연합우주에서 가장 소프트웨어 프로그래머가 쓰기에 적합한 플랫폼이라고 자부합니다.

단문(Note)과 긴 게시글(Article) 모두에서 Markdown을 지원할 뿐만 아니라 구문강조와 TeX 수식을 지원한다는 점에서 Hackers' Pub은 연합우주에서 가장 소프트웨어 프로그래머가 쓰기에 적합한 플랫폼이라고 자부합니다.

11
1
0

Open source projects I'm currently maintaining:

  • Fedify, an ActivityPub server framework for TypeScript
  • Hollo, an ActivityPub-enabled single-user microblogging software
  • BotKit, an ActivityPub bot framework for TypeScript
  • LogTape, a modern logging library for TypeScript
  • Upyo, a simple and modern email sending library for TypeScript
  • Optique, a type-safe combinatorial CLI parser for TypeScript
7
2
1
8
2
6
3

GitHub Classroom을 사용해서 온라인 저지 시스템을 구축해본 후기입니다.

https://theeluwin.github.io/article/online-judge-with-github-classroom/

실제로 사용하고 싶었으나, 제 결론은... 안쓰는쪽으로 났습니다. 이유는 결국, 문제 출제하는 UI/UX가 좋지 않다는 것입니다. 제가 고생을 덜 해야만 하니까요(,,,,) 암튼 GitHub Classroom이 문서도 제대로 안되어있기도 하고, 기능이 아직 좀 오락가락 합니다. 없는 기능도 너무 많구요. 제 검토를 참고해주십시오, , ,

2
1

해커스펍 너무 낯설고 익숙하지가 않은데 이거는 마스토돈 프로토콜(?)이 아닌 건가? 마스토돈 클라이언트에서 로그인해서 쓰고 싶은데.

@yg1ee 네, Mastodon API를 구현하지 않고 있어요. 하지만 Mastodon 서버과 소통은 가능하고요. 기술적으로는 Mastodon 서버끼리는 ActivityPub으로 소통하는데, Hackers' Pub도 ActivityPub을 구현하고 있기 때문에 Mastodon 서버들과 소통이 가능합니다. Mastodon 클라이언트 앱이 Mastodon 서버와 통신할 때는 Mastodon API라는 별도의 프로토콜을 사용하는데, Hackers' Pub은 그건 구현하지 않습니다. 그래서 Mastodon 클라이언트 앱으로 Hackers' Pub을 사용할 수는 없어요.

Hackers' Pub 모바일 앱은 추후 개발 예정입니다!

0
5
3

https://github.com/makachanm/questionbasket

셀프호스트 1인용 익명 질문함 서비스의 백엔드까지는 전부 짜긴 했지만 프론트엔드를 맡아줄 사람이 없어서 그냥 백엔드 API만 작동하는 정도로 남겨두었다. 언젠간 완성할수도 있지만... 일단은 이정도 완성시킨걸로 만족하기로 하자. Gemini 없었으면 4달 전에 진행하다 버린 프로젝트를 오늘 완성시킬순 없었을거다.

4
9
5
2
5
4
5
3

오늘의 서커스 내용 세션의 정보를 가져오는데 세션의 앱은 ApplicationGrants를 받아야 하고 세션의 프로필 ID는 ApplicationGrantProfiles으로 프로필별 승인을 받거나 전체 프로필 승인(ApplicationGrantProfiles.profileId == null)을 받야아 가져올 수 있는데 세션의 프로필을 헤더로 오버라이드할 수 있고 오버라이드된 프로필 역시 위 조건을 지키는 경우만 리턴되게 하는 쿼리를 짰어요

const headerProfileId = c.req.header('X-Actor-Profile-Id');
    const session = await db
      .select({
        id: Sessions.id,
        applicationId: Sessions.applicationId,
        accountId: Sessions.accountId,
        scopes: Sessions.scopes,
        languages: Accounts.languages,
        profileId: Profiles.id,
      })
      .from(Sessions)
      .innerJoin(Accounts, eq(Sessions.accountId, Accounts.id))
      .innerJoin(ApplicationGrants, eq(Sessions.applicationId, ApplicationGrants.applicationId))
      .leftJoin(
        ApplicationGrantProfiles,
        eq(ApplicationGrants.id, ApplicationGrantProfiles.applicationGrantId),
      )
      .leftJoin(
        Profiles,
        and(
          eq(Profiles.id, headerProfileId ?? Sessions.profileId),
          eq(Profiles.state, ProfileState.ACTIVE),
          isNotNull(ApplicationGrantProfiles.id),
          or(
            eq(ApplicationGrantProfiles.profileId, Profiles.id),
            isNull(ApplicationGrantProfiles.profileId),
          ),
        ),
      )
      .where(eq(Sessions.token, accessToken))
      .limit(1)
      .then(first);
1

오늘의 서커스 내용 세션의 정보를 가져오는데 세션의 앱은 ApplicationGrants를 받아야 하고 세션의 프로필 ID는 ApplicationGrantProfiles으로 프로필별 승인을 받거나 전체 프로필 승인(ApplicationGrantProfiles.profileId == null)을 받야아 가져올 수 있는데 세션의 프로필을 헤더로 오버라이드할 수 있고 오버라이드된 프로필 역시 위 조건을 지키는 경우만 리턴되게 하는 쿼리를 짰어요

const headerProfileId = c.req.header('X-Actor-Profile-Id');
    const session = await db
      .select({
        id: Sessions.id,
        applicationId: Sessions.applicationId,
        accountId: Sessions.accountId,
        scopes: Sessions.scopes,
        languages: Accounts.languages,
        profileId: Profiles.id,
      })
      .from(Sessions)
      .innerJoin(Accounts, eq(Sessions.accountId, Accounts.id))
      .innerJoin(ApplicationGrants, eq(Sessions.applicationId, ApplicationGrants.applicationId))
      .leftJoin(
        ApplicationGrantProfiles,
        eq(ApplicationGrants.id, ApplicationGrantProfiles.applicationGrantId),
      )
      .leftJoin(
        Profiles,
        and(
          eq(Profiles.id, headerProfileId ?? Sessions.profileId),
          eq(Profiles.state, ProfileState.ACTIVE),
          isNotNull(ApplicationGrantProfiles.id),
          or(
            eq(ApplicationGrantProfiles.profileId, Profiles.id),
            isNull(ApplicationGrantProfiles.profileId),
          ),
        ),
      )
      .where(eq(Sessions.token, accessToken))
      .limit(1)
      .then(first);
3
5
3
2
5
2
2

튜링에서 한 프로세스를 실행하고, 다음 프로세스를 실행하는 걸 "명시"하지 않는데, 이를 명시적으로 어떤 combinator로 보고, 전역 변수등은 또 람다로 감싸서, 람다 컨텍스트에 있는 변수를 보게 하면, 람다와 다를게 없다.
튜링과 람다가 철학이 다르다기 보다, 마치 튜링에서 암묵적으로 하던 동작들을 모두 명시적으로 바꿔 놓은 게 람다 대수인 건 아닐까?

1
3
7
1

I've recently been working on Optique, a CLI parser combinator for TypeScript. Optique allows you to describe complex CLIs by combining smaller parts. You can also handle the CLI parsing results in a type-safe manner (see code below). The idea came from Haskell's optparse-applicative, but since TypeScript's API style is so different from Haskell's, I referenced Zod and similar libraries for the API design. For a more detailed introduction, please refer to the article I posted on Hackers' Pub!

const parser = or(
  object({
    type: constant("network"),
    host: option(
      "-h", "--host",
      string({ metavar: "HOST" }),
    ),
    port: option(
      "-p", "--port",
      integer({ metavar: "PORT", min: 0, max: 65535 }),
    ),
  }),
  object({
    type: constant("local"),
    file: option(
      "-s", "--socket-file",
      string({ metavar: "FILE" }),
    ),
  }),
)

type Result = InferValue<typeof parser>;

// The above type is inferred as:
type Result = {
    readonly type: "network";
    readonly host: string;
    readonly port: number;
} | {
    readonly type: "local";
    readonly file: string;
}

最近、OptiqueというTypeScript向けのCLIパーサー「コンビネーター」を作っています。Optiqueは、複雑なCLIを小さなパーツの組み合わせで記述できる様にしてくれます。そして、そのCLIのパース結果を型安全に扱う事が出来ます。(下記のコード参照)アイデアはHaskellのoptparse-applicative から得ましたが、TypeScriptはHaskellとAPIのスタイルがかなり異なる為、APIの面ではZod等を参考にしました。詳しい紹介はHackers' Pubに投稿した記事をご覧ください!

const parser = or(
  object({
    type: constant("network"),
    host: option(
      "-h", "--host",
      string({ metavar: "HOST" }),
    ),
    port: option(
      "-p", "--port",
      integer({ metavar: "PORT", min: 0, max: 65535 }),
    ),
  }),
  object({
    type: constant("local"),
    file: option(
      "-s", "--socket-file",
      string({ metavar: "FILE" }),
    ),
  }),
)

type Result = InferValue<typeof parser>;

// Resultの推論された型
type Result = {
    readonly type: "network";
    readonly host: string;
    readonly port: number;
} | {
    readonly type: "local";
    readonly file: string;
}
0

I've recently been working on Optique, a CLI parser combinator for TypeScript. Optique allows you to describe complex CLIs by combining smaller parts. You can also handle the CLI parsing results in a type-safe manner (see code below). The idea came from Haskell's optparse-applicative, but since TypeScript's API style is so different from Haskell's, I referenced Zod and similar libraries for the API design. For a more detailed introduction, please refer to the article I posted on Hackers' Pub!

const parser = or(
  object({
    type: constant("network"),
    host: option(
      "-h", "--host",
      string({ metavar: "HOST" }),
    ),
    port: option(
      "-p", "--port",
      integer({ metavar: "PORT", min: 0, max: 65535 }),
    ),
  }),
  object({
    type: constant("local"),
    file: option(
      "-s", "--socket-file",
      string({ metavar: "FILE" }),
    ),
  }),
)

type Result = InferValue<typeof parser>;

// The above type is inferred as:
type Result = {
    readonly type: "network";
    readonly host: string;
    readonly port: number;
} | {
    readonly type: "local";
    readonly file: string;
}
3

회사의 다른 레포들 보고 있는데 zero install 방식을 사용하고 있는 곳이 보인다. 근데 아무리봐도 캐시에다가 패키지 파일을 쌓아두는게 썩 좋은 방법 같아보이지는 않는데 내가 모르는 뭔가가 있는건가

1
2

mcp에 툴을 계속 쥐어주는게 불만이다. 불안하지도 않나? 아니 pg를 관리하는 mcp를 쓰는것보다 pg를 관리하는데 사용되는 명령어를 물어보고 그걸 내가 확인 후 실행하는게 맞지 않나? deno로 권한 다 뺏고서 mcp를 실행하면 좀 방지가 되려나?

3