洪 民憙 (Hong Minhee) :nonbinary:'s avatar

洪 民憙 (Hong Minhee) :nonbinary:

@hongminhee@hollo.social

1,107 following1,897 followers

An intersectionalist, feminist, and socialist living in Seoul (UTC+09:00). @tokolovesme's spouse. Who's behind @fedify, @hollo, and @botkit. Write some free software in , , , & . They/them.

서울에 사는 交叉女性主義者이자 社會主義者. 金剛兔(@tokolovesme)의 配偶者. @fedify, @hollo, @botkit 메인테이너. , , , 等으로 自由 소프트웨어 만듦.

()

Pinned

@hongminhee@hollo.social

Hello! I'm Hong Minhee (洪 民憙), an open source software engineer in my late 30s, living in Seoul, Korea. I'm bisexual and non-binary (they/them), and an enthusiastic advocate of free/open source software and the fediverse.

I work full-time on @fedify, an ActivityPub server framework in TypeScript, funded by @sovtechfund. I'm also the creator of @hollo, a single-user ActivityPub microblog; @botkit, an ActivityPub bot framework; Hackers' Pub, a fediverse platform for software developers; and LogTape, a logging library for JavaScript and TypeScript.

I have a long interest in East Asian languages (CJK) and Unicode. I post mostly in English here, though occasionally in Japanese or in mixed-script Korean (國漢文混用體), a traditional writing style that interleaves Chinese characters with the native Korean alphabet. Wanting to write in that style was actually one of the reasons I joined the fediverse. Feel free to talk to me in English, Korean, Japanese, or even Literary Chinese!

en.wikipedia.org

Korean mixed script - Wikipedia

Pinned

はじめまして!ソウル在住の30代後半のオープンソースソフトウェアエンジニア、洪 民憙ホン・ミンヒと申します。バイセクシュアル(bisexual)・ノンバイナリー(non-binary)で、自由・オープンソースソフトウェア(F/OSS)とフェディバース(fediverse)の熱烈な支持者です。

STF(@sovtechfund)の支援を受け、TypeScript用ActivityPubサーバーフレームワーク「@fedify」の開発に専念しています。他にも、おひとり様向けのActivityPubマイクロブログ「@hollo」、ActivityPubボットフレームワーク「@botkit」、ソフトウェア開発者向けフェディバースプラットフォームHackers' Pub、JavaScript・TypeScript用ロギングライブラリLogTapeなどの制作者でもあります。

東アジア言語(いわゆるCJK)とUnicodeにも興味があります。このアカウントでは主に英語で投稿していますが、時々日本語や国漢文混用体(漢字ハングル混じり文)の韓国語でも書いています。実はこの文体で書きたくてフェディバースを始めた、という経緯もあります。日本語、英語、韓国語、漢文でも気軽に話しかけてください!

speakerdeck.com

国漢文混用体からHolloまで

本発表では、韓国語の「国漢文混用体」(漢字ハングル混じり文)を自分のフェディバース投稿に実装したいという小さな目標から始まった旅路を共有します。 この目標を達成するために、ActivityPubのJSON-LDの複雑さやHTTP Signatures、WebFingerなどの仕様を理解する必要性に…

Pinned

安寧(안녕)하세요! 저는 서울에 살고 있는 30() 後半(후반)의 오픈 소스 소프트웨어 엔지니어 洪民憙(홍민희)입니다. 兩性愛者(양성애자)(bisexual)이자 논바이너리(non-binary)이며, 自由(자유)·오픈 소스 소프트웨어(F/OSS)와 聯合宇宙(연합우주)(fediverse)의 熱烈(열렬)支持者(지지자)이기도 합니다.

STF(@sovtechfund)의 支援(지원)을 받아 TypeScript() ActivityPub 서버 프레임워크 @fedify 開發(개발)專業(전업)으로 ()하고 있습니다. 그 ()에도 싱글 유저() ActivityPub 마이크로블로그 @hollo, ActivityPub 봇 프레임워크 @botkit, 소프트웨어 開發者(개발자)를 위한 聯合宇宙(연합우주) 플랫폼 Hackers' Pub, JavaScript·TypeScript() 로깅 라이브러리 LogTape ()製作者(제작자)이기도 합니다.

()아시아 言語(언어)(이른바 CJK)와 Unicode에도 關心(관심)이 많습니다. 이 計定(계정)에서는 ()英語(영어)로 포스팅하지만, 때때로 日本語(일본어)國漢文混用體(국한문 혼용체) 韓國語(한국어)로도 씁니다. 聯合宇宙(연합우주)에 오게 된 動機(동기) () 하나가 바로 國漢文混用體(국한문 혼용체)로 글을 쓰고 싶었기 때문이기도 하고요. 韓國語(한국어), 英語(영어), 日本語(일본어), 아니면 漢文(한문)으로도 말을 걸어주세요!

logtape.org

LogTape

Unobtrusive logging library with zero dependencies—library-first design for Deno, Node.js, Bun, browsers, and edge functions

@yichm@g0v.social

滅人器

在滅火器指示牌的「火」字,因為頂上的兩點特別暗淡,使得整字形似「人」字。於是,「滅火器」三字成了「滅人器」。
ALT text

在滅火器指示牌的「火」字,因為頂上的兩點特別暗淡,使得整字形似「人」字。於是,「滅火器」三字成了「滅人器」。

在滅火器指示牌的「火」字,因為頂上的兩點特別暗淡,使得整字形似「人」字。於是,「滅火器」三字成了「滅人器」。
ALT text

在滅火器指示牌的「火」字,因為頂上的兩點特別暗淡,使得整字形似「人」字。於是,「滅火器」三字成了「滅人器」。

The update was hard; the jsonld.js library was choking on the FEP's context document because Codeberg Pages, where FEP context documents are hosted, is unreliable.

I ended up adding a feature to the `activitystrea.ms` NodeJS library to let you pre-cache a context document, so it didn't have to go fetch it at run time. While I was under the hood, I also fixed a couple of bugs that made it hard to work with extra context documents.

The bot server is now working; I'm going to add more contexts.

I updated the server software to use FEP-5711, the inverse properties FEP. It is a way to declare two-way relations; if X has an `inbox` property with value Y, then Y has an `inboxOf` property with value X. It helps with navigation when you're using the ActivityPub API, and it also helps with spoofing (since both objects agree on the relationship).

w3id.org/fep/5711

codeberg.org

fep/fep/5711/fep-5711.md at main

fep - Fediverse Enhancement Proposals

@jiyu@hackers.pub

밋업때 뭔가 보여줘야 할 거 같아서 열심히 달리는중

We've just published an experimental pre-release version 1.9.0-pr.431.1597 that adds CommonJS support to all npm packages in the Fedify ecosystem! 🧪

What's new

This experimental build addresses one of the most requested features—better compatibility with CommonJS-based Node.js applications, especially NestJS projects. The pre-release eliminates the need for Node.js's --experimental-require-module flag and resolves dual package hazard issues.

Note: While we now support CommonJS for legacy project compatibility, we still recommend using ESM or migrating to Deno for the best experience with Fedify.

Who should test this?

  • NestJS developers using Fedify who need CommonJS compatibility
  • Legacy CommonJS-based Node.js projects that had trouble integrating Fedify
  • Anyone who previously needed experimental Node.js flags to use Fedify

How to test

Install the experimental pre-release version:

npm install @fedify/fedify@1.9.0-pr.431.1597
# or for specific integrations
npm install @fedify/nestjs@1.9.0-pr.431.1597

You should now be able to use standard require() syntax without any experimental flags.

What we're looking for

  • Does CommonJS import work correctly in your legacy project?
  • Are you able to remove the --experimental-require-module flag?
  • Any issues or regressions compared to the current stable version?

Your feedback on this experimental build is invaluable for ensuring this major compatibility improvement works smoothly before the official 1.9.0 release!

nodejs.org

Modules: ECMAScript modules | Node.js v24.8.0 Documentation

@erincandescent@erincandescent.net

Whoa, as of 10 days ago we have functions to go between a Uint8Array and a Base64 Normal/URL Padded/Unpadded string in JavaScript without having to go via a String of “bytes” and manually juggle the padding in every major browser.

What a time to be alive.

developer.mozilla.org

Uint8Array.prototype.toBase64() - JavaScript | MDN

The toBase64() method of Uint8Array instances returns a base64-encoded string based on the data in this Uint8Array object.

@2chanhaeng@hackers.pub

페디파이 빡시게 기여해서 2.0 뜨면 블로그 만들어서 블스 떠야지

@jiyu@hackers.pub

무슨 연합우주 스캐너 같은 게 있나...?

/api/v1/instance
/.well-known/host-meta
/status.php
/statistics.json
ALT text

/api/v1/instance /.well-known/host-meta /status.php /statistics.json

@evan @hongminhee more and more i am thinking that Link was a bad idea from a data modeling perspective. "assume bare href instead of bare id" is something that can never make sense. if we really want to maintain validity of Link then it should *always* be embedded as an anonymous object:

icon: {
type: Image
url:
{
type: Link
href: foo
height: 400
width: 400
mediaType: image/png
}
}

here, Image.url means "representation of the Image"

@hongminhee@hackers.pub

일본의 기술 블로깅 플랫폼인 Zenn 첫 페이지에 내가 Optique에 관해 썼던 글인 「TypeScriptの型推論でCLIバリデーションをなくせた話」(TypeScript의 타입 추론으로 CLI 유효성 검사를 필요 없게 만든 이야기)가 떴다!

일본 기술 블로깅 플랫폼 Zenn의 Tech 섹션 스크린샷. 페이지에는 여러 기술 관련 글들이 나열되어 있으며, 그 중 빨간 원으로 강조된 「TypeScriptの型推論でCLIバリデーションをなくせた話」(TypeScript의 타입 추론으로 CLI 유효성 검사를 필요 없게 만든 이야기) 제목의 글이 20개의 좋아요와 12개의 댓글을 받으며 주목받고 있다. 해당 글은 TypeScript와 CLI 파서 라이브러리인 Optique에 관한 내용을 다루고 있다.
ALT text

일본 기술 블로깅 플랫폼 Zenn의 Tech 섹션 스크린샷. 페이지에는 여러 기술 관련 글들이 나열되어 있으며, 그 중 빨간 원으로 강조된 「TypeScriptの型推論でCLIバリデーションをなくせた話」(TypeScript의 타입 추론으로 CLI 유효성 검사를 필요 없게 만든 이야기) 제목의 글이 20개의 좋아요와 12개의 댓글을 받으며 주목받고 있다. 해당 글은 TypeScript와 CLI 파서 라이브러리인 Optique에 관한 내용을 다루고 있다.

@hongminhee@hollo.social

Today I discovered an interesting inconsistency in Activity Streams specs while investigating a Fedify issue.

The question: How should we interpret URLs like "icon": "https://example.com/avatar.png"?

JSON-LD context (https://www.w3.org/ns/activitystreams): @type: "@id" → “This is an IRI reference, dereference it to fetch an ActivityStreams object.”

Activity Streams Primer: “assume that a bare string is the href of a Link object, not an id” (no dereferencing)

Result: JSON-LD processor-based implementations try to parse PNG files as JSON and fail.

Turns out w3c/activitystreams#595 already discusses the same issue for href properties. I added a note that icon, image, etc. have the same problem.

Once again reminded of how tricky spec work can be…

github.com

`href` should be `@type: xsd:anyURI` in the context document · Issue #595 · w3c/activitystreams

w3c/activitypub#375 (comment) In the AS2-Vocab normative recommendation, href is defined to always be xsd:anyURI In the AS2 context document, it is not defined as such Conclusion: Make the context ...

@quillmatiq@mastodon.social
@hongminhee@hollo.social

LogTape 1.1.0 is out! This release brings “fingers crossed” logging—buffer debug logs quietly, then get the full context when errors occur. Plus a new emit() API for integrating external log sources. Check it out:

https://hackers.pub/@hongminhee/2025/logtape-110

hackers.pub

LogTape 1.1.0: Smarter buffering, seamless integration

LogTape 1.1.0 introduces smarter and more flexible logging with two major features. The first is "fingers crossed" logging, which buffers debug and low-level logs in memory and only outputs them when an error occurs, providing a complete sequence of events leading up to the problem. Category isolation prevents one component's errors from flushing unrelated logs, keeping logs focused and relevant. The second feature is direct log emission via the `Logger.emit()` method, which allows feeding logs from external systems like Kafka directly into LogTape while preserving original timestamps and metadata. This release also includes bug fixes and improvements across the ecosystem, such as fixes for potential data loss during high-volume logging and improved cross-runtime compatibility. Upgrading to 1.1.0 is backward-compatible and enhances debugging in production by providing complete context for every error without constant verbose logging.

@hongminhee@hackers.pub

LogTape 1.1.0 is here, and it focuses on making your logging smarter and more flexible. This release introduces two major features we think you'll love.

Introducing fingers crossed logging

Tired of noisy production logs? Wish you had the full story when an error finally pops up? Our new “fingers crossed” logging pattern was built for exactly that.

With fingers crossed logging, LogTape buffers your debug and low-level logs in memory instead of immediately outputting them. When an error occurs, it flushes the entire buffer—giving you the complete sequence of events leading up to the problem. Once the error is logged, subsequent logs pass through normally until the next trigger event.

import { configure, fingersCrossed, getConsoleSink } from "@logtape/logtape";

await configure({
  sinks: {
    console: fingersCrossed(getConsoleSink(), {
      triggerLevel: "error",  // Buffer until an error occurs
      maxBufferSize: 500,     // Keep last 500 records
    }),
  },
  loggers: [
    { category: [], sinks: ["console"], lowestLevel: "debug" },
  ],
});

It's the best of both worlds: clean logs when things are running smoothly, and rich, detailed context the moment an issue occurs. Stop choosing between too much noise and not enough information.

Category isolation for complex applications

For applications with multiple modules or services, the new category isolation feature prevents one component's errors from flushing unrelated logs:

fingersCrossed(getConsoleSink(), {
  isolateByCategory: "descendant",  // Only flush related categories
})

You can choose how categories relate to each other—flush child categories when a parent triggers, parent categories when a child triggers, or both. This surgical precision keeps your logs focused and relevant.

Direct log emission for external systems

Integrating logs from external systems just got a lot easier. With the new Logger.emit() method, you can now feed logs from Kafka, legacy applications, or any other source directly into LogTape while preserving their original timestamps and metadata.

const logger = getLogger(["my-app", "integration"]);

// Preserve the original timestamp from an external system
logger.emit({
  timestamp: kafkaMessage.originalTimestamp,
  level: "info",
  message: [kafkaMessage.content],
  rawMessage: kafkaMessage.content,
  properties: {
    source: "kafka",
    partition: kafkaMessage.partition,
    offset: kafkaMessage.offset,
  },
});

This new low-level API gives you full control over the log record, allowing you to leverage LogTape's powerful filtering, formatting, and sink ecosystem for any log source. It's particularly valuable for:

  • Migrating from other logging systems while preserving historical context
  • Aggregating logs from multiple sources with accurate timestamps
  • Building custom adapters for specialized logging scenarios

Bug fixes and improvements

Beyond the headline features, we've strengthened LogTape's reliability across the ecosystem. Check out the full changelog for complete details.

@logtape/file

  • Fixed potential data loss during high-volume logging by ensuring all buffered logs are fully written before disposal
  • Changed getStreamFileSink() to properly implement AsyncDisposable for cleaner resource management

@logtape/sentry

  • Improved cross-runtime compatibility by introducing a structural interface that avoids type conflicts
  • Fixed template metadata preservation for better Sentry breadcrumb formatting
  • Removed unnecessary Node.js dependencies for broader runtime support

@logtape/pretty

  • Added support for displaying structured data properties directly in pretty-formatted output (thanks to Matthias Feist for the contribution)

Why upgrade?

Upgrading to 1.1.0 is a no-brainer. It's fully backward-compatible and makes your setup more powerful. The fingers crossed feature alone will change how you debug in production. Imagine getting a complete stack trace with full context for every error, without the performance hit of constant verbose logging.

If you're new to LogTape, this release shows what we're all about: building tools that solve real-world problems. We don't think you should have to choose between noisy logs and insufficient context. LogTape adapts to what you need, buffering logs when things are quiet and providing rich detail when it matters most.

Getting started

Upgrade to LogTape 1.1.0:

npm update @logtape/logtape
# or
deno add jsr:@logtape/logtape@^1.1.0

The new features are opt-in, so your existing configuration continues working exactly as before. When you're ready, explore fingers crossed logging for cleaner production logs or use emit() for advanced integration scenarios.

Let us know what you think of the new features! We're always active in our GitHub discussions and would love to hear your feedback.

github.com

dahlia/logtape · Discussions

Explore the GitHub Discussions forum for dahlia logtape. Discuss code, ask questions & collaborate with the developer community.

@hongminhee@hackers.pub

LogTape 1.1.0 is here, and it focuses on making your logging smarter and more flexible. This release introduces two major features we think you'll love.

Introducing fingers crossed logging

Tired of noisy production logs? Wish you had the full story when an error finally pops up? Our new “fingers crossed” logging pattern was built for exactly that.

With fingers crossed logging, LogTape buffers your debug and low-level logs in memory instead of immediately outputting them. When an error occurs, it flushes the entire buffer—giving you the complete sequence of events leading up to the problem. Once the error is logged, subsequent logs pass through normally until the next trigger event.

import { configure, fingersCrossed, getConsoleSink } from "@logtape/logtape";

await configure({
  sinks: {
    console: fingersCrossed(getConsoleSink(), {
      triggerLevel: "error",  // Buffer until an error occurs
      maxBufferSize: 500,     // Keep last 500 records
    }),
  },
  loggers: [
    { category: [], sinks: ["console"], lowestLevel: "debug" },
  ],
});

It's the best of both worlds: clean logs when things are running smoothly, and rich, detailed context the moment an issue occurs. Stop choosing between too much noise and not enough information.

Category isolation for complex applications

For applications with multiple modules or services, the new category isolation feature prevents one component's errors from flushing unrelated logs:

fingersCrossed(getConsoleSink(), {
  isolateByCategory: "descendant",  // Only flush related categories
})

You can choose how categories relate to each other—flush child categories when a parent triggers, parent categories when a child triggers, or both. This surgical precision keeps your logs focused and relevant.

Direct log emission for external systems

Integrating logs from external systems just got a lot easier. With the new Logger.emit() method, you can now feed logs from Kafka, legacy applications, or any other source directly into LogTape while preserving their original timestamps and metadata.

const logger = getLogger(["my-app", "integration"]);

// Preserve the original timestamp from an external system
logger.emit({
  timestamp: kafkaMessage.originalTimestamp,
  level: "info",
  message: [kafkaMessage.content],
  rawMessage: kafkaMessage.content,
  properties: {
    source: "kafka",
    partition: kafkaMessage.partition,
    offset: kafkaMessage.offset,
  },
});

This new low-level API gives you full control over the log record, allowing you to leverage LogTape's powerful filtering, formatting, and sink ecosystem for any log source. It's particularly valuable for:

  • Migrating from other logging systems while preserving historical context
  • Aggregating logs from multiple sources with accurate timestamps
  • Building custom adapters for specialized logging scenarios

Bug fixes and improvements

Beyond the headline features, we've strengthened LogTape's reliability across the ecosystem. Check out the full changelog for complete details.

@logtape/file

  • Fixed potential data loss during high-volume logging by ensuring all buffered logs are fully written before disposal
  • Changed getStreamFileSink() to properly implement AsyncDisposable for cleaner resource management

@logtape/sentry

  • Improved cross-runtime compatibility by introducing a structural interface that avoids type conflicts
  • Fixed template metadata preservation for better Sentry breadcrumb formatting
  • Removed unnecessary Node.js dependencies for broader runtime support

@logtape/pretty

  • Added support for displaying structured data properties directly in pretty-formatted output (thanks to Matthias Feist for the contribution)

Why upgrade?

Upgrading to 1.1.0 is a no-brainer. It's fully backward-compatible and makes your setup more powerful. The fingers crossed feature alone will change how you debug in production. Imagine getting a complete stack trace with full context for every error, without the performance hit of constant verbose logging.

If you're new to LogTape, this release shows what we're all about: building tools that solve real-world problems. We don't think you should have to choose between noisy logs and insufficient context. LogTape adapts to what you need, buffering logs when things are quiet and providing rich detail when it matters most.

Getting started

Upgrade to LogTape 1.1.0:

npm update @logtape/logtape
# or
deno add jsr:@logtape/logtape@^1.1.0

The new features are opt-in, so your existing configuration continues working exactly as before. When you're ready, explore fingers crossed logging for cleaner production logs or use emit() for advanced integration scenarios.

Let us know what you think of the new features! We're always active in our GitHub discussions and would love to hear your feedback.

github.com

dahlia/logtape · Discussions

Explore the GitHub Discussions forum for dahlia logtape. Discuss code, ask questions & collaborate with the developer community.

@hongminhee@hollo.social

TypeScriptの型推論を活用してCLIのバリデーションコードを削除できた話を書きました。「バリデーションせずパースせよ」(Parse, don't validate )の考え方をCLIパーサーに適用したOptiqueというライブラリを作った経緯について。

TypeScriptの型推論でCLIバリデーションをなくせた話

zenn.dev

TypeScriptの型推論でCLIバリデーションをなくせた話

@hongminhee@hollo.social

TypeScriptの型推論を活用してCLIのバリデーションコードを削除できた話を書きました。「バリデーションせずパースせよ」(Parse, don't validate )の考え方をCLIパーサーに適用したOptiqueというライブラリを作った経緯について。

TypeScriptの型推論でCLIバリデーションをなくせた話

zenn.dev

TypeScriptの型推論でCLIバリデーションをなくせた話